From 18da3ffcd7f3c8a0c5f790c801b5813503c2273d Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Fri, 19 Apr 2024 19:03:56 +0200 Subject: Adding upstream version 31+20240202. Signed-off-by: Daniel Baumann --- libkmod/.gitignore | 6 + libkmod/COPYING | 504 +++++++ libkmod/Makefile | 13 + libkmod/README | 58 + libkmod/docs/.gitignore | 14 + libkmod/docs/Makefile.am | 34 + libkmod/docs/libkmod-docs.xml | 27 + libkmod/docs/libkmod-sections.txt | 108 ++ libkmod/docs/version.xml.in | 1 + libkmod/libkmod-builtin.c | 330 ++++ libkmod/libkmod-config.c | 1252 ++++++++++++++++ libkmod/libkmod-elf.c | 1219 +++++++++++++++ libkmod/libkmod-file.c | 541 +++++++ libkmod/libkmod-index.c | 1082 ++++++++++++++ libkmod/libkmod-index.h | 48 + libkmod/libkmod-internal.h | 210 +++ libkmod/libkmod-list.c | 314 ++++ libkmod/libkmod-module.c | 2986 +++++++++++++++++++++++++++++++++++++ libkmod/libkmod-signature.c | 358 +++++ libkmod/libkmod.c | 1024 +++++++++++++ libkmod/libkmod.h | 270 ++++ libkmod/libkmod.pc.in | 11 + libkmod/libkmod.sym | 94 ++ 23 files changed, 10504 insertions(+) create mode 100644 libkmod/.gitignore create mode 100644 libkmod/COPYING create mode 100644 libkmod/Makefile create mode 100644 libkmod/README create mode 100644 libkmod/docs/.gitignore create mode 100644 libkmod/docs/Makefile.am create mode 100644 libkmod/docs/libkmod-docs.xml create mode 100644 libkmod/docs/libkmod-sections.txt create mode 100644 libkmod/docs/version.xml.in create mode 100644 libkmod/libkmod-builtin.c create mode 100644 libkmod/libkmod-config.c create mode 100644 libkmod/libkmod-elf.c create mode 100644 libkmod/libkmod-file.c create mode 100644 libkmod/libkmod-index.c create mode 100644 libkmod/libkmod-index.h create mode 100644 libkmod/libkmod-internal.h create mode 100644 libkmod/libkmod-list.c create mode 100644 libkmod/libkmod-module.c create mode 100644 libkmod/libkmod-signature.c create mode 100644 libkmod/libkmod.c create mode 100644 libkmod/libkmod.h create mode 100644 libkmod/libkmod.pc.in create mode 100644 libkmod/libkmod.sym (limited to 'libkmod') diff --git a/libkmod/.gitignore b/libkmod/.gitignore new file mode 100644 index 0000000..826fd62 --- /dev/null +++ b/libkmod/.gitignore @@ -0,0 +1,6 @@ +.dirstamp +.deps/ +.libs/ +*.la +*.lo +libkmod.pc diff --git a/libkmod/COPYING b/libkmod/COPYING new file mode 100644 index 0000000..8add30a --- /dev/null +++ b/libkmod/COPYING @@ -0,0 +1,504 @@ + GNU LESSER GENERAL PUBLIC LICENSE + Version 2.1, February 1999 + + Copyright (C) 1991, 1999 Free Software Foundation, Inc. + 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + +[This is the first released version of the Lesser GPL. It also counts + as the successor of the GNU Library Public License, version 2, hence + the version number 2.1.] + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +Licenses are intended to guarantee your freedom to share and change +free software--to make sure the software is free for all its users. + + This license, the Lesser General Public License, applies to some +specially designated software packages--typically libraries--of the +Free Software Foundation and other authors who decide to use it. You +can use it too, but we suggest you first think carefully about whether +this license or the ordinary General Public License is the better +strategy to use in any particular case, based on the explanations below. + + When we speak of free software, we are referring to freedom of use, +not price. Our General Public Licenses are designed to make sure that +you have the freedom to distribute copies of free software (and charge +for this service if you wish); that you receive source code or can get +it if you want it; that you can change the software and use pieces of +it in new free programs; and that you are informed that you can do +these things. + + To protect your rights, we need to make restrictions that forbid +distributors to deny you these rights or to ask you to surrender these +rights. These restrictions translate to certain responsibilities for +you if you distribute copies of the library or if you modify it. + + For example, if you distribute copies of the library, whether gratis +or for a fee, you must give the recipients all the rights that we gave +you. You must make sure that they, too, receive or can get the source +code. If you link other code with the library, you must provide +complete object files to the recipients, so that they can relink them +with the library after making changes to the library and recompiling +it. And you must show them these terms so they know their rights. + + We protect your rights with a two-step method: (1) we copyright the +library, and (2) we offer you this license, which gives you legal +permission to copy, distribute and/or modify the library. + + To protect each distributor, we want to make it very clear that +there is no warranty for the free library. Also, if the library is +modified by someone else and passed on, the recipients should know +that what they have is not the original version, so that the original +author's reputation will not be affected by problems that might be +introduced by others. + + Finally, software patents pose a constant threat to the existence of +any free program. We wish to make sure that a company cannot +effectively restrict the users of a free program by obtaining a +restrictive license from a patent holder. Therefore, we insist that +any patent license obtained for a version of the library must be +consistent with the full freedom of use specified in this license. + + Most GNU software, including some libraries, is covered by the +ordinary GNU General Public License. This license, the GNU Lesser +General Public License, applies to certain designated libraries, and +is quite different from the ordinary General Public License. We use +this license for certain libraries in order to permit linking those +libraries into non-free programs. + + When a program is linked with a library, whether statically or using +a shared library, the combination of the two is legally speaking a +combined work, a derivative of the original library. The ordinary +General Public License therefore permits such linking only if the +entire combination fits its criteria of freedom. The Lesser General +Public License permits more lax criteria for linking other code with +the library. + + We call this license the "Lesser" General Public License because it +does Less to protect the user's freedom than the ordinary General +Public License. It also provides other free software developers Less +of an advantage over competing non-free programs. These disadvantages +are the reason we use the ordinary General Public License for many +libraries. However, the Lesser license provides advantages in certain +special circumstances. + + For example, on rare occasions, there may be a special need to +encourage the widest possible use of a certain library, so that it becomes +a de-facto standard. To achieve this, non-free programs must be +allowed to use the library. A more frequent case is that a free +library does the same job as widely used non-free libraries. In this +case, there is little to gain by limiting the free library to free +software only, so we use the Lesser General Public License. + + In other cases, permission to use a particular library in non-free +programs enables a greater number of people to use a large body of +free software. For example, permission to use the GNU C Library in +non-free programs enables many more people to use the whole GNU +operating system, as well as its variant, the GNU/Linux operating +system. + + Although the Lesser General Public License is Less protective of the +users' freedom, it does ensure that the user of a program that is +linked with the Library has the freedom and the wherewithal to run +that program using a modified version of the Library. + + The precise terms and conditions for copying, distribution and +modification follow. Pay close attention to the difference between a +"work based on the library" and a "work that uses the library". The +former contains code derived from the library, whereas the latter must +be combined with the library in order to run. + + GNU LESSER GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License Agreement applies to any software library or other +program which contains a notice placed by the copyright holder or +other authorized party saying it may be distributed under the terms of +this Lesser General Public License (also called "this License"). +Each licensee is addressed as "you". + + A "library" means a collection of software functions and/or data +prepared so as to be conveniently linked with application programs +(which use some of those functions and data) to form executables. + + The "Library", below, refers to any such software library or work +which has been distributed under these terms. A "work based on the +Library" means either the Library or any derivative work under +copyright law: that is to say, a work containing the Library or a +portion of it, either verbatim or with modifications and/or translated +straightforwardly into another language. (Hereinafter, translation is +included without limitation in the term "modification".) + + "Source code" for a work means the preferred form of the work for +making modifications to it. For a library, complete source code means +all the source code for all modules it contains, plus any associated +interface definition files, plus the scripts used to control compilation +and installation of the library. + + Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running a program using the Library is not restricted, and output from +such a program is covered only if its contents constitute a work based +on the Library (independent of the use of the Library in a tool for +writing it). Whether that is true depends on what the Library does +and what the program that uses the Library does. + + 1. You may copy and distribute verbatim copies of the Library's +complete source code as you receive it, in any medium, provided that +you conspicuously and appropriately publish on each copy an +appropriate copyright notice and disclaimer of warranty; keep intact +all the notices that refer to this License and to the absence of any +warranty; and distribute a copy of this License along with the +Library. + + You may charge a fee for the physical act of transferring a copy, +and you may at your option offer warranty protection in exchange for a +fee. + + 2. You may modify your copy or copies of the Library or any portion +of it, thus forming a work based on the Library, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) The modified work must itself be a software library. + + b) You must cause the files modified to carry prominent notices + stating that you changed the files and the date of any change. + + c) You must cause the whole of the work to be licensed at no + charge to all third parties under the terms of this License. + + d) If a facility in the modified Library refers to a function or a + table of data to be supplied by an application program that uses + the facility, other than as an argument passed when the facility + is invoked, then you must make a good faith effort to ensure that, + in the event an application does not supply such function or + table, the facility still operates, and performs whatever part of + its purpose remains meaningful. + + (For example, a function in a library to compute square roots has + a purpose that is entirely well-defined independent of the + application. Therefore, Subsection 2d requires that any + application-supplied function or table used by this function must + be optional: if the application does not supply it, the square + root function must still compute square roots.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Library, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Library, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote +it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Library. + +In addition, mere aggregation of another work not based on the Library +with the Library (or with a work based on the Library) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may opt to apply the terms of the ordinary GNU General Public +License instead of this License to a given copy of the Library. To do +this, you must alter all the notices that refer to this License, so +that they refer to the ordinary GNU General Public License, version 2, +instead of to this License. (If a newer version than version 2 of the +ordinary GNU General Public License has appeared, then you can specify +that version instead if you wish.) Do not make any other change in +these notices. + + Once this change is made in a given copy, it is irreversible for +that copy, so the ordinary GNU General Public License applies to all +subsequent copies and derivative works made from that copy. + + This option is useful when you wish to copy part of the code of +the Library into a program that is not a library. + + 4. You may copy and distribute the Library (or a portion or +derivative of it, under Section 2) in object code or executable form +under the terms of Sections 1 and 2 above provided that you accompany +it with the complete corresponding machine-readable source code, which +must be distributed under the terms of Sections 1 and 2 above on a +medium customarily used for software interchange. + + If distribution of object code is made by offering access to copy +from a designated place, then offering equivalent access to copy the +source code from the same place satisfies the requirement to +distribute the source code, even though third parties are not +compelled to copy the source along with the object code. + + 5. A program that contains no derivative of any portion of the +Library, but is designed to work with the Library by being compiled or +linked with it, is called a "work that uses the Library". Such a +work, in isolation, is not a derivative work of the Library, and +therefore falls outside the scope of this License. + + However, linking a "work that uses the Library" with the Library +creates an executable that is a derivative of the Library (because it +contains portions of the Library), rather than a "work that uses the +library". The executable is therefore covered by this License. +Section 6 states terms for distribution of such executables. + + When a "work that uses the Library" uses material from a header file +that is part of the Library, the object code for the work may be a +derivative work of the Library even though the source code is not. +Whether this is true is especially significant if the work can be +linked without the Library, or if the work is itself a library. The +threshold for this to be true is not precisely defined by law. + + If such an object file uses only numerical parameters, data +structure layouts and accessors, and small macros and small inline +functions (ten lines or less in length), then the use of the object +file is unrestricted, regardless of whether it is legally a derivative +work. (Executables containing this object code plus portions of the +Library will still fall under Section 6.) + + Otherwise, if the work is a derivative of the Library, you may +distribute the object code for the work under the terms of Section 6. +Any executables containing that work also fall under Section 6, +whether or not they are linked directly with the Library itself. + + 6. As an exception to the Sections above, you may also combine or +link a "work that uses the Library" with the Library to produce a +work containing portions of the Library, and distribute that work +under terms of your choice, provided that the terms permit +modification of the work for the customer's own use and reverse +engineering for debugging such modifications. + + You must give prominent notice with each copy of the work that the +Library is used in it and that the Library and its use are covered by +this License. You must supply a copy of this License. If the work +during execution displays copyright notices, you must include the +copyright notice for the Library among them, as well as a reference +directing the user to the copy of this License. Also, you must do one +of these things: + + a) Accompany the work with the complete corresponding + machine-readable source code for the Library including whatever + changes were used in the work (which must be distributed under + Sections 1 and 2 above); and, if the work is an executable linked + with the Library, with the complete machine-readable "work that + uses the Library", as object code and/or source code, so that the + user can modify the Library and then relink to produce a modified + executable containing the modified Library. (It is understood + that the user who changes the contents of definitions files in the + Library will not necessarily be able to recompile the application + to use the modified definitions.) + + b) Use a suitable shared library mechanism for linking with the + Library. A suitable mechanism is one that (1) uses at run time a + copy of the library already present on the user's computer system, + rather than copying library functions into the executable, and (2) + will operate properly with a modified version of the library, if + the user installs one, as long as the modified version is + interface-compatible with the version that the work was made with. + + c) Accompany the work with a written offer, valid for at + least three years, to give the same user the materials + specified in Subsection 6a, above, for a charge no more + than the cost of performing this distribution. + + d) If distribution of the work is made by offering access to copy + from a designated place, offer equivalent access to copy the above + specified materials from the same place. + + e) Verify that the user has already received a copy of these + materials or that you have already sent this user a copy. + + For an executable, the required form of the "work that uses the +Library" must include any data and utility programs needed for +reproducing the executable from it. However, as a special exception, +the materials to be distributed need not include anything that is +normally distributed (in either source or binary form) with the major +components (compiler, kernel, and so on) of the operating system on +which the executable runs, unless that component itself accompanies +the executable. + + It may happen that this requirement contradicts the license +restrictions of other proprietary libraries that do not normally +accompany the operating system. Such a contradiction means you cannot +use both them and the Library together in an executable that you +distribute. + + 7. You may place library facilities that are a work based on the +Library side-by-side in a single library together with other library +facilities not covered by this License, and distribute such a combined +library, provided that the separate distribution of the work based on +the Library and of the other library facilities is otherwise +permitted, and provided that you do these two things: + + a) Accompany the combined library with a copy of the same work + based on the Library, uncombined with any other library + facilities. This must be distributed under the terms of the + Sections above. + + b) Give prominent notice with the combined library of the fact + that part of it is a work based on the Library, and explaining + where to find the accompanying uncombined form of the same work. + + 8. You may not copy, modify, sublicense, link with, or distribute +the Library except as expressly provided under this License. Any +attempt otherwise to copy, modify, sublicense, link with, or +distribute the Library is void, and will automatically terminate your +rights under this License. However, parties who have received copies, +or rights, from you under this License will not have their licenses +terminated so long as such parties remain in full compliance. + + 9. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Library or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Library (or any work based on the +Library), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Library or works based on it. + + 10. Each time you redistribute the Library (or any work based on the +Library), the recipient automatically receives a license from the +original licensor to copy, distribute, link with or modify the Library +subject to these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties with +this License. + + 11. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Library at all. For example, if a patent +license would not permit royalty-free redistribution of the Library by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Library. + +If any portion of this section is held invalid or unenforceable under any +particular circumstance, the balance of the section is intended to apply, +and the section as a whole is intended to apply in other circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 12. If the distribution and/or use of the Library is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Library under this License may add +an explicit geographical distribution limitation excluding those countries, +so that distribution is permitted only in or among countries not thus +excluded. In such case, this License incorporates the limitation as if +written in the body of this License. + + 13. The Free Software Foundation may publish revised and/or new +versions of the Lesser General Public License from time to time. +Such new versions will be similar in spirit to the present version, +but may differ in detail to address new problems or concerns. + +Each version is given a distinguishing version number. If the Library +specifies a version number of this License which applies to it and +"any later version", you have the option of following the terms and +conditions either of that version or of any later version published by +the Free Software Foundation. If the Library does not specify a +license version number, you may choose any version ever published by +the Free Software Foundation. + + 14. If you wish to incorporate parts of the Library into other free +programs whose distribution conditions are incompatible with these, +write to the author to ask for permission. For software which is +copyrighted by the Free Software Foundation, write to the Free +Software Foundation; we sometimes make exceptions for this. Our +decision will be guided by the two goals of preserving the free status +of all derivatives of our free software and of promoting the sharing +and reuse of software generally. + + NO WARRANTY + + 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO +WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. +EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR +OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY +KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE +LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME +THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN +WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY +AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU +FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR +CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE +LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING +RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A +FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF +SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH +DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Libraries + + If you develop a new library, and you want it to be of the greatest +possible use to the public, we recommend making it free software that +everyone can redistribute and change. You can do so by permitting +redistribution under these terms (or, alternatively, under the terms of the +ordinary General Public License). + + To apply these terms, attach the following notices to the library. It is +safest to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least the +"copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +Also add information on how to contact you by electronic and paper mail. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the library, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the + library `Frob' (a library for tweaking knobs) written by James Random Hacker. + + , 1 April 1990 + Ty Coon, President of Vice + +That's all there is to it! + + diff --git a/libkmod/Makefile b/libkmod/Makefile new file mode 100644 index 0000000..223bec2 --- /dev/null +++ b/libkmod/Makefile @@ -0,0 +1,13 @@ +# Copyright 2010 Lennart Poettering +# +# This file has been copied from systemd. It is a dirty trick to simplify +# compilation when CWD is not the root of the source tree. This file is not +# intended to be distributed. So, don't touch it, even better ignore it! + +all: + $(MAKE) -C .. + +clean: + $(MAKE) -C .. clean + +.PHONY: all clean diff --git a/libkmod/README b/libkmod/README new file mode 100644 index 0000000..3e1c8dc --- /dev/null +++ b/libkmod/README @@ -0,0 +1,58 @@ +libkmod - linux kernel module handling library + +ABSTRACT +======== + +libkmod was created to allow programs to easily insert, remove and +list modules, also checking its properties, dependencies and aliases. + +there is no shared/global context information and it can be used by +multiple sites on a single program, also being able to be used from +threads, although it's not thread safe (you must lock explicitly). + + +OVERVIEW +======== + +Every user should create and manage it's own library context with: + + struct kmod_ctx *ctx = kmod_new(kernel_dirname); + kmod_unref(ctx); + + +Modules can be created by various means: + + struct kmod_module *mod; + int err; + + err = kmod_module_new_from_path(ctx, path, &mod); + if (err < 0) { + /* code */ + } else { + /* code */ + kmod_module_unref(mod); + } + + err = kmod_module_new_from_name(ctx, name, &mod); + if (err < 0) { + /* code */ + } else { + /* code */ + kmod_module_unref(mod); + } + + +Or could be resolved from a known alias to a list of alternatives: + + struct kmod_list *list, *itr; + int err; + err = kmod_module_new_from_lookup(ctx, alias, &list); + if (err < 0) { + /* code */ + } else { + kmod_list_foreach(itr, list) { + struct kmod_module *mod = kmod_module_get_module(itr); + /* code */ + } + } + diff --git a/libkmod/docs/.gitignore b/libkmod/docs/.gitignore new file mode 100644 index 0000000..7514b08 --- /dev/null +++ b/libkmod/docs/.gitignore @@ -0,0 +1,14 @@ +*.bak +*.stamp +*.sgml +libkmod.* +libkmod-*.xml +!libkmod-docs.xml +libkmod-*.txt +!libkmod-sections.txt +version.xml +xml +html +gtk-doc.make +Makefile +Makefile.in diff --git a/libkmod/docs/Makefile.am b/libkmod/docs/Makefile.am new file mode 100644 index 0000000..c4f3d69 --- /dev/null +++ b/libkmod/docs/Makefile.am @@ -0,0 +1,34 @@ + +AUTOMAKE_OPTIONS = 1.11 + +DOC_MODULE = libkmod + +DOC_MODULE_VERSION = 3 + +DOC_MAIN_SGML_FILE = $(DOC_MODULE)-docs.xml + +DOC_SOURCE_DIR = $(top_srcdir)/libkmod + +SCAN_OPTIONS = --ignore-decorators="__must_check|KMOD_EXPORT" + +MKDB_OPTIONS = --xml-mode --output-format=xml --name-space kmod --tmpl-dir=. + +MKTMPL_OPTIONS = --output-dir=. + +MKHTML_OPTIONS = --path=$(abs_srcdir)/doc --path=$(abs_builddir)/doc + +HFILE_GLOB = $(top_srcdir)/libkmod/libkmod.h +CFILE_GLOB = $(top_srcdir)/libkmod/libkmod.c $(top_srcdir)/libkmod/libkmod-module.c $(top_srcdir)/libkmod/libkmod-list.c + +IGNORE_HFILES = libkmod-internal.h \ + libkmod-index.h + +content_files = version.xml + +EXTRA_DIST = + +if ENABLE_GTK_DOC +include $(top_srcdir)/libkmod/docs/gtk-doc.make +else +EXTRA_DIST += libkmod-docs.xml libkmod-sections.txt +endif diff --git a/libkmod/docs/libkmod-docs.xml b/libkmod/docs/libkmod-docs.xml new file mode 100644 index 0000000..fd17506 --- /dev/null +++ b/libkmod/docs/libkmod-docs.xml @@ -0,0 +1,27 @@ + + + +]> + + + libkmod Reference Manual + for libkmod version &version; + + + + libkmod + + + + + + + + + API Index + + + diff --git a/libkmod/docs/libkmod-sections.txt b/libkmod/docs/libkmod-sections.txt new file mode 100644 index 0000000..33d9eec --- /dev/null +++ b/libkmod/docs/libkmod-sections.txt @@ -0,0 +1,108 @@ +
+libkmod +kmod_ctx +kmod_new +kmod_ref +kmod_unref + +kmod_load_resources +kmod_unload_resources +kmod_validate_resources +kmod_dump_index + +kmod_set_log_priority +kmod_get_log_priority +kmod_set_log_fn +kmod_get_userdata +kmod_set_userdata +kmod_get_dirname +
+ +
+libkmod-list +kmod_list +kmod_list_foreach +kmod_list_foreach_reverse +kmod_list_last +kmod_list_next +kmod_list_prev +
+ +
+libkmod-config +kmod_config_iter +kmod_config_get_blacklists +kmod_config_get_install_commands +kmod_config_get_remove_commands +kmod_config_get_aliases +kmod_config_get_options +kmod_config_get_softdeps +kmod_config_iter_get_key +kmod_config_iter_get_value +kmod_config_iter_next +kmod_config_iter_free_iter +
+ +
+libkmod-module +kmod_module +kmod_module_new_from_lookup +kmod_module_new_from_name_lookup +kmod_module_new_from_name +kmod_module_new_from_path + +kmod_module_ref +kmod_module_unref +kmod_module_unref_list + +kmod_module_insert_module +kmod_module_probe_insert_module +kmod_module_remove_module + +kmod_module_get_module +kmod_module_get_dependencies +kmod_module_get_softdeps +kmod_module_apply_filter +kmod_module_get_filtered_blacklist +kmod_module_get_install_commands +kmod_module_get_remove_commands +kmod_module_get_name +kmod_module_get_options +kmod_module_get_path + +kmod_module_get_dependency_symbols +kmod_module_dependency_symbol_get_bind +kmod_module_dependency_symbol_get_crc +kmod_module_dependency_symbol_get_symbol +kmod_module_dependency_symbols_free_list + +kmod_module_get_sections +kmod_module_section_free_list +kmod_module_section_get_address +kmod_module_section_get_name + +kmod_module_get_symbols +kmod_module_symbol_get_crc +kmod_module_symbol_get_symbol +kmod_module_symbols_free_list + +kmod_module_get_versions +kmod_module_version_get_crc +kmod_module_version_get_symbol +kmod_module_versions_free_list + +kmod_module_get_info +kmod_module_info_free_list +kmod_module_info_get_key +kmod_module_info_get_value +
+ +
+libkmod-loaded +kmod_module_new_from_loaded +kmod_module_get_initstate +kmod_module_initstate_str +kmod_module_get_size +kmod_module_get_refcnt +kmod_module_get_holders +
diff --git a/libkmod/docs/version.xml.in b/libkmod/docs/version.xml.in new file mode 100644 index 0000000..d78bda9 --- /dev/null +++ b/libkmod/docs/version.xml.in @@ -0,0 +1 @@ +@VERSION@ diff --git a/libkmod/libkmod-builtin.c b/libkmod/libkmod-builtin.c new file mode 100644 index 0000000..65334a8 --- /dev/null +++ b/libkmod/libkmod-builtin.c @@ -0,0 +1,330 @@ +/* + * libkmod - interface to kernel built-in modules + * + * Copyright (C) 2019 Alexey Gladkov + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, see . + */ + +#include +#include + +#include +#include +#include +#include +#include + +#include "libkmod.h" +#include "libkmod-internal.h" + +#define MODULES_BUILTIN_MODINFO "modules.builtin.modinfo" + +struct kmod_builtin_iter { + struct kmod_ctx *ctx; + + // The file descriptor. + int file; + + // The total size in bytes. + ssize_t size; + + // The offset of current module. + off_t pos; + + // The offset at which the next module is located. + off_t next; + + // Number of strings in the current block. + ssize_t nstrings; + + // Internal buffer and its size. + size_t bufsz; + char *buf; +}; + +static struct kmod_builtin_iter *kmod_builtin_iter_new(struct kmod_ctx *ctx) +{ + char path[PATH_MAX]; + int file, sv_errno; + struct stat sb; + struct kmod_builtin_iter *iter = NULL; + const char *dirname = kmod_get_dirname(ctx); + size_t len = strlen(dirname); + + file = -1; + + if ((len + 1 + strlen(MODULES_BUILTIN_MODINFO) + 1) >= PATH_MAX) { + sv_errno = ENAMETOOLONG; + goto fail; + } + + snprintf(path, PATH_MAX, "%s/%s", dirname, MODULES_BUILTIN_MODINFO); + + file = open(path, O_RDONLY|O_CLOEXEC); + if (file < 0) { + sv_errno = errno; + goto fail; + } + + if (fstat(file, &sb) < 0) { + sv_errno = errno; + goto fail; + } + + iter = malloc(sizeof(*iter)); + if (!iter) { + sv_errno = ENOMEM; + goto fail; + } + + iter->ctx = ctx; + iter->file = file; + iter->size = sb.st_size; + iter->nstrings = 0; + iter->pos = 0; + iter->next = 0; + iter->bufsz = 0; + iter->buf = NULL; + + return iter; +fail: + if (file >= 0) + close(file); + + errno = sv_errno; + + return iter; +} + +static void kmod_builtin_iter_free(struct kmod_builtin_iter *iter) +{ + close(iter->file); + free(iter->buf); + free(iter); +} + +static off_t get_string(struct kmod_builtin_iter *iter, off_t offset, + char **line, size_t *size) +{ + int sv_errno; + char *nullp = NULL; + size_t linesz = 0; + + while (!nullp) { + char buf[BUFSIZ]; + ssize_t sz; + size_t partsz; + + sz = pread(iter->file, buf, BUFSIZ, offset); + if (sz < 0) { + sv_errno = errno; + goto fail; + } else if (sz == 0) { + offset = 0; + break; + } + + nullp = memchr(buf, '\0', (size_t) sz); + partsz = (size_t)((nullp) ? (nullp - buf) + 1 : sz); + offset += (off_t) partsz; + + if (iter->bufsz < linesz + partsz) { + iter->bufsz = linesz + partsz; + iter->buf = realloc(iter->buf, iter->bufsz); + + if (!iter->buf) { + sv_errno = errno; + goto fail; + } + } + + strncpy(iter->buf + linesz, buf, partsz); + linesz += partsz; + } + + if (linesz) { + *line = iter->buf; + *size = linesz; + } + + return offset; +fail: + errno = sv_errno; + return -1; +} + +static bool kmod_builtin_iter_next(struct kmod_builtin_iter *iter) +{ + char *line, *modname; + size_t linesz; + off_t pos, offset, modlen; + + modname = NULL; + + iter->nstrings = 0; + offset = pos = iter->next; + + while (offset < iter->size) { + char *dot; + off_t len; + + offset = get_string(iter, pos, &line, &linesz); + if (offset <= 0) { + if (offset) + ERR(iter->ctx, "get_string: %s\n", strerror(errno)); + pos = iter->size; + break; + } + + dot = strchr(line, '.'); + if (!dot) { + ERR(iter->ctx, "kmod_builtin_iter_next: unexpected string without modname prefix\n"); + pos = iter->size; + break; + } + + len = dot - line; + + if (!modname) { + modname = strdup(line); + modlen = len; + } else if (modlen != len || strncmp(modname, line, len)) { + break; + } + + iter->nstrings++; + pos = offset; + } + + iter->pos = iter->next; + iter->next = pos; + + free(modname); + + return (iter->pos < iter->size); +} + +static bool kmod_builtin_iter_get_modname(struct kmod_builtin_iter *iter, + char modname[static PATH_MAX]) +{ + int sv_errno; + char *line, *dot; + size_t linesz, len; + off_t offset; + + if (iter->pos == iter->size) + return false; + + line = NULL; + + offset = get_string(iter, iter->pos, &line, &linesz); + if (offset <= 0) { + sv_errno = errno; + if (offset) + ERR(iter->ctx, "get_string: %s\n", strerror(errno)); + goto fail; + } + + dot = strchr(line, '.'); + if (!dot) { + sv_errno = errno; + ERR(iter->ctx, "kmod_builtin_iter_get_modname: unexpected string without modname prefix\n"); + goto fail; + } + + len = dot - line; + + if (len >= PATH_MAX) { + sv_errno = ENAMETOOLONG; + goto fail; + } + + strncpy(modname, line, len); + modname[len] = '\0'; + + return true; +fail: + errno = sv_errno; + return false; +} + +/* array will be allocated with strings in a single malloc, just free *array */ +ssize_t kmod_builtin_get_modinfo(struct kmod_ctx *ctx, const char *modname, + char ***modinfo) +{ + ssize_t count = 0; + char *s, *line = NULL; + size_t i, n, linesz, modlen, size; + off_t pos, offset; + + char *name = NULL; + char buf[PATH_MAX]; + + struct kmod_builtin_iter *iter = kmod_builtin_iter_new(ctx); + + if (!iter) + return -errno; + + while (!name && kmod_builtin_iter_next(iter)) { + if (!kmod_builtin_iter_get_modname(iter, buf)) { + count = -errno; + goto fail; + } + + if (strcmp(modname, buf)) + continue; + + name = buf; + } + + if (!name) { + count = -ENOSYS; + goto fail; + } + + modlen = strlen(modname) + 1; + count = iter->nstrings; + size = iter->next - iter->pos - (modlen * count); + + *modinfo = malloc(size + sizeof(char *) * (count + 1)); + if (!*modinfo) { + count = -errno; + goto fail; + } + + s = (char *)(*modinfo + count + 1); + i = 0; + + n = 0; + offset = pos = iter->pos; + + while (offset < iter->next) { + offset = get_string(iter, pos, &line, &linesz); + if (offset <= 0) { + count = (offset) ? -errno : -EINVAL; + free(*modinfo); + goto fail; + } + + strcpy(s + i, line + modlen); + (*modinfo)[n++] = s + i; + i += linesz - modlen; + + pos = offset; + } +fail: + kmod_builtin_iter_free(iter); + return count; +} diff --git a/libkmod/libkmod-config.c b/libkmod/libkmod-config.c new file mode 100644 index 0000000..e83621b --- /dev/null +++ b/libkmod/libkmod-config.c @@ -0,0 +1,1252 @@ +/* + * libkmod - interface to kernel module operations + * + * Copyright (C) 2011-2013 ProFUSION embedded systems + * Copyright (C) 2013 Intel Corporation. All rights reserved. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, see . + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "libkmod.h" +#include "libkmod-internal.h" + +struct kmod_alias { + char *name; + char modname[]; +}; + +struct kmod_options { + char *options; + char modname[]; +}; + +struct kmod_command { + char *command; + char modname[]; +}; + +struct kmod_softdep { + char *name; + const char **pre; + const char **post; + unsigned int n_pre; + unsigned int n_post; +}; + +const char *kmod_blacklist_get_modname(const struct kmod_list *l) +{ + return l->data; +} + +const char *kmod_alias_get_name(const struct kmod_list *l) { + const struct kmod_alias *alias = l->data; + return alias->name; +} + +const char *kmod_alias_get_modname(const struct kmod_list *l) { + const struct kmod_alias *alias = l->data; + return alias->modname; +} + +const char *kmod_option_get_options(const struct kmod_list *l) { + const struct kmod_options *alias = l->data; + return alias->options; +} + +const char *kmod_option_get_modname(const struct kmod_list *l) { + const struct kmod_options *alias = l->data; + return alias->modname; +} + +const char *kmod_command_get_command(const struct kmod_list *l) { + const struct kmod_command *alias = l->data; + return alias->command; +} + +const char *kmod_command_get_modname(const struct kmod_list *l) { + const struct kmod_command *alias = l->data; + return alias->modname; +} + +const char *kmod_softdep_get_name(const struct kmod_list *l) { + const struct kmod_softdep *dep = l->data; + return dep->name; +} + +const char * const *kmod_softdep_get_pre(const struct kmod_list *l, unsigned int *count) { + const struct kmod_softdep *dep = l->data; + *count = dep->n_pre; + return dep->pre; +} + +const char * const *kmod_softdep_get_post(const struct kmod_list *l, unsigned int *count) { + const struct kmod_softdep *dep = l->data; + *count = dep->n_post; + return dep->post; +} + +static int kmod_config_add_command(struct kmod_config *config, + const char *modname, + const char *command, + const char *command_name, + struct kmod_list **list) +{ + _cleanup_free_ struct kmod_command *cmd; + struct kmod_list *l; + size_t modnamelen = strlen(modname) + 1; + size_t commandlen = strlen(command) + 1; + + DBG(config->ctx, "modname='%s' cmd='%s %s'\n", modname, command_name, + command); + + cmd = malloc(sizeof(*cmd) + modnamelen + commandlen); + if (!cmd) + return -ENOMEM; + + cmd->command = sizeof(*cmd) + modnamelen + (char *)cmd; + memcpy(cmd->modname, modname, modnamelen); + memcpy(cmd->command, command, commandlen); + + l = kmod_list_append(*list, cmd); + if (!l) + return -ENOMEM; + + *list = l; + cmd = NULL; + return 0; +} + +static void kmod_config_free_command(struct kmod_config *config, + struct kmod_list *l, + struct kmod_list **list) +{ + struct kmod_command *cmd = l->data; + + free(cmd); + *list = kmod_list_remove(l); +} + +static int kmod_config_add_options(struct kmod_config *config, + const char *modname, const char *options) +{ + _cleanup_free_ struct kmod_options *opt; + struct kmod_list *list; + size_t modnamelen = strlen(modname) + 1; + size_t optionslen = strlen(options) + 1; + + DBG(config->ctx, "modname='%s' options='%s'\n", modname, options); + + opt = malloc(sizeof(*opt) + modnamelen + optionslen); + if (!opt) + return -ENOMEM; + + opt->options = sizeof(*opt) + modnamelen + (char *)opt; + + memcpy(opt->modname, modname, modnamelen); + memcpy(opt->options, options, optionslen); + strchr_replace(opt->options, '\t', ' '); + + list = kmod_list_append(config->options, opt); + if (!list) + return -ENOMEM; + + opt = NULL; + config->options = list; + return 0; +} + +static void kmod_config_free_options(struct kmod_config *config, + struct kmod_list *l) +{ + struct kmod_options *opt = l->data; + + free(opt); + + config->options = kmod_list_remove(l); +} + +static int kmod_config_add_alias(struct kmod_config *config, + const char *name, const char *modname) +{ + _cleanup_free_ struct kmod_alias *alias; + struct kmod_list *list; + size_t namelen = strlen(name) + 1, modnamelen = strlen(modname) + 1; + + DBG(config->ctx, "name=%s modname=%s\n", name, modname); + + alias = malloc(sizeof(*alias) + namelen + modnamelen); + if (!alias) + return -ENOMEM; + + alias->name = sizeof(*alias) + modnamelen + (char *)alias; + + memcpy(alias->modname, modname, modnamelen); + memcpy(alias->name, name, namelen); + + list = kmod_list_append(config->aliases, alias); + if (!list) + return -ENOMEM; + + alias = NULL; + config->aliases = list; + return 0; +} + +static void kmod_config_free_alias(struct kmod_config *config, + struct kmod_list *l) +{ + struct kmod_alias *alias = l->data; + + free(alias); + + config->aliases = kmod_list_remove(l); +} + +static int kmod_config_add_blacklist(struct kmod_config *config, + const char *modname) +{ + _cleanup_free_ char *p; + struct kmod_list *list; + + DBG(config->ctx, "modname=%s\n", modname); + + p = strdup(modname); + if (!p) + return -ENOMEM; + + list = kmod_list_append(config->blacklists, p); + if (!list) + return -ENOMEM; + + p = NULL; + config->blacklists = list; + return 0; +} + +static void kmod_config_free_blacklist(struct kmod_config *config, + struct kmod_list *l) +{ + free(l->data); + config->blacklists = kmod_list_remove(l); +} + +static int kmod_config_add_softdep(struct kmod_config *config, + const char *modname, + const char *line) +{ + struct kmod_list *list; + struct kmod_softdep *dep; + const char *s, *p; + char *itr; + unsigned int n_pre = 0, n_post = 0; + size_t modnamelen = strlen(modname) + 1; + size_t buflen = 0; + bool was_space = false; + enum { S_NONE, S_PRE, S_POST } mode = S_NONE; + + DBG(config->ctx, "modname=%s\n", modname); + + /* analyze and count */ + for (p = s = line; ; s++) { + size_t plen; + + if (*s != '\0') { + if (!isspace(*s)) { + was_space = false; + continue; + } + + if (was_space) { + p = s + 1; + continue; + } + was_space = true; + + if (p >= s) + continue; + } + plen = s - p; + + if (plen == sizeof("pre:") - 1 && + memcmp(p, "pre:", sizeof("pre:") - 1) == 0) + mode = S_PRE; + else if (plen == sizeof("post:") - 1 && + memcmp(p, "post:", sizeof("post:") - 1) == 0) + mode = S_POST; + else if (*s != '\0' || (*s == '\0' && !was_space)) { + if (mode == S_PRE) { + buflen += plen + 1; + n_pre++; + } else if (mode == S_POST) { + buflen += plen + 1; + n_post++; + } + } + p = s + 1; + if (*s == '\0') + break; + } + + DBG(config->ctx, "%u pre, %u post\n", n_pre, n_post); + + dep = malloc(sizeof(struct kmod_softdep) + modnamelen + + n_pre * sizeof(const char *) + + n_post * sizeof(const char *) + + buflen); + if (dep == NULL) { + ERR(config->ctx, "out-of-memory modname=%s\n", modname); + return -ENOMEM; + } + dep->n_pre = n_pre; + dep->n_post = n_post; + dep->pre = (const char **)((char *)dep + sizeof(struct kmod_softdep)); + dep->post = dep->pre + n_pre; + dep->name = (char *)(dep->post + n_post); + + memcpy(dep->name, modname, modnamelen); + + /* copy strings */ + itr = dep->name + modnamelen; + n_pre = 0; + n_post = 0; + mode = S_NONE; + was_space = false; + for (p = s = line; ; s++) { + size_t plen; + + if (*s != '\0') { + if (!isspace(*s)) { + was_space = false; + continue; + } + + if (was_space) { + p = s + 1; + continue; + } + was_space = true; + + if (p >= s) + continue; + } + plen = s - p; + + if (plen == sizeof("pre:") - 1 && + memcmp(p, "pre:", sizeof("pre:") - 1) == 0) + mode = S_PRE; + else if (plen == sizeof("post:") - 1 && + memcmp(p, "post:", sizeof("post:") - 1) == 0) + mode = S_POST; + else if (*s != '\0' || (*s == '\0' && !was_space)) { + if (mode == S_PRE) { + dep->pre[n_pre] = itr; + memcpy(itr, p, plen); + itr[plen] = '\0'; + itr += plen + 1; + n_pre++; + } else if (mode == S_POST) { + dep->post[n_post] = itr; + memcpy(itr, p, plen); + itr[plen] = '\0'; + itr += plen + 1; + n_post++; + } + } + p = s + 1; + if (*s == '\0') + break; + } + + list = kmod_list_append(config->softdeps, dep); + if (list == NULL) { + free(dep); + return -ENOMEM; + } + config->softdeps = list; + + return 0; +} + +static char *softdep_to_char(struct kmod_softdep *dep) { + const size_t sz_preprefix = sizeof("pre: ") - 1; + const size_t sz_postprefix = sizeof("post: ") - 1; + size_t sz = 1; /* at least '\0' */ + size_t sz_pre, sz_post; + const char *start, *end; + char *s, *itr; + + /* + * Rely on the fact that dep->pre[] and dep->post[] are strv's that + * point to a contiguous buffer + */ + if (dep->n_pre > 0) { + start = dep->pre[0]; + end = dep->pre[dep->n_pre - 1] + + strlen(dep->pre[dep->n_pre - 1]); + sz_pre = end - start; + sz += sz_pre + sz_preprefix; + } else + sz_pre = 0; + + if (dep->n_post > 0) { + start = dep->post[0]; + end = dep->post[dep->n_post - 1] + + strlen(dep->post[dep->n_post - 1]); + sz_post = end - start; + sz += sz_post + sz_postprefix; + } else + sz_post = 0; + + itr = s = malloc(sz); + if (s == NULL) + return NULL; + + if (sz_pre) { + char *p; + + memcpy(itr, "pre: ", sz_preprefix); + itr += sz_preprefix; + + /* include last '\0' */ + memcpy(itr, dep->pre[0], sz_pre + 1); + for (p = itr; p < itr + sz_pre; p++) { + if (*p == '\0') + *p = ' '; + } + itr = p; + } + + if (sz_post) { + char *p; + + memcpy(itr, "post: ", sz_postprefix); + itr += sz_postprefix; + + /* include last '\0' */ + memcpy(itr, dep->post[0], sz_post + 1); + for (p = itr; p < itr + sz_post; p++) { + if (*p == '\0') + *p = ' '; + } + itr = p; + } + + *itr = '\0'; + + return s; +} + +static void kmod_config_free_softdep(struct kmod_config *config, + struct kmod_list *l) +{ + free(l->data); + config->softdeps = kmod_list_remove(l); +} + +static void kcmdline_parse_result(struct kmod_config *config, char *modname, + char *param, char *value) +{ + if (modname == NULL || param == NULL) + return; + + DBG(config->ctx, "%s %s\n", modname, param); + + if (streq(modname, "modprobe") && !strncmp(param, "blacklist=", 10)) { + for (;;) { + char *t = strsep(&value, ","); + if (t == NULL) + break; + + kmod_config_add_blacklist(config, t); + } + } else { + if (underscores(modname) < 0) { + ERR(config->ctx, "Ignoring bad option on kernel command line while parsing module name: '%s'\n", + modname); + } else { + kmod_config_add_options(config, modname, param); + } + } +} + +static int kmod_config_parse_kcmdline(struct kmod_config *config) +{ + char buf[KCMD_LINE_SIZE]; + int fd, err; + char *p, *p_quote_start, *modname, *param = NULL, *value = NULL; + bool is_quoted = false, iter = true; + enum state { + STATE_IGNORE, + STATE_MODNAME, + STATE_PARAM, + STATE_VALUE, + STATE_COMPLETE, + } state; + + fd = open("/proc/cmdline", O_RDONLY|O_CLOEXEC); + if (fd < 0) { + err = -errno; + DBG(config->ctx, "could not open '/proc/cmdline' for reading: %m\n"); + return err; + } + + err = read_str_safe(fd, buf, sizeof(buf)); + close(fd); + if (err < 0) { + ERR(config->ctx, "could not read from '/proc/cmdline': %s\n", + strerror(-err)); + return err; + } + + state = STATE_MODNAME; + p_quote_start = NULL; + for (p = buf, modname = buf; iter; p++) { + switch (*p) { + case '"': + is_quoted = !is_quoted; + + /* + * only allow starting quote as first char when looking + * for a modname: anything else is considered ill-formed + */ + if (is_quoted && state == STATE_MODNAME && p == modname) { + p_quote_start = p; + modname = p + 1; + } else if (state != STATE_VALUE) { + state = STATE_IGNORE; + } + + break; + case '\0': + iter = false; + /* fall-through */ + case ' ': + case '\n': + case '\t': + case '\v': + case '\f': + case '\r': + if (is_quoted && state == STATE_VALUE) { + /* no state change*/; + } else if (is_quoted) { + /* spaces are only allowed in the value part */ + state = STATE_IGNORE; + } else if (state == STATE_VALUE || state == STATE_PARAM) { + *p = '\0'; + state = STATE_COMPLETE; + } else { + /* + * go to next option, ignoring any possible + * partial match we have + */ + modname = p + 1; + state = STATE_MODNAME; + p_quote_start = NULL; + } + break; + case '.': + if (state == STATE_MODNAME) { + *p = '\0'; + param = p + 1; + state = STATE_PARAM; + } else if (state == STATE_PARAM) { + state = STATE_IGNORE; + } + break; + case '=': + if (state == STATE_PARAM) { + /* + * Don't set *p to '\0': the value var shadows + * param + */ + value = p + 1; + state = STATE_VALUE; + } else if (state == STATE_MODNAME) { + state = STATE_IGNORE; + } + break; + } + + if (state == STATE_COMPLETE) { + /* + * We may need to re-quote to unmangle what the + * bootloader passed. Example: grub passes the option as + * "parport.dyndbg=file drivers/parport/ieee1284_ops.c +mpf" + * instead of + * parport.dyndbg="file drivers/parport/ieee1284_ops.c +mpf" + */ + if (p_quote_start && p_quote_start < modname) { + /* + * p_quote_start + * | + * |modname param value + * || | | + * vv v v + * "parport\0dyndbg=file drivers/parport/ieee1284_ops.c +mpf" */ + memmove(p_quote_start, modname, value - modname); + value--; modname--; param--; + *value = '"'; + } + kcmdline_parse_result(config, modname, param, value); + /* start over on next iteration */ + modname = p + 1; + state = STATE_MODNAME; + p_quote_start = NULL; + } + } + + return 0; +} + +/* + * Take an fd and own it. It will be closed on return. filename is used only + * for debug messages + */ +static int kmod_config_parse(struct kmod_config *config, int fd, + const char *filename) +{ + struct kmod_ctx *ctx = config->ctx; + char *line; + FILE *fp; + unsigned int linenum = 0; + int err; + + fp = fdopen(fd, "r"); + if (fp == NULL) { + err = -errno; + ERR(config->ctx, "fd %d: %m\n", fd); + close(fd); + return err; + } + + while ((line = freadline_wrapped(fp, &linenum)) != NULL) { + char *cmd, *saveptr; + + if (line[0] == '\0' || line[0] == '#') + goto done_next; + + cmd = strtok_r(line, "\t ", &saveptr); + if (cmd == NULL) + goto done_next; + + if (streq(cmd, "alias")) { + char *alias = strtok_r(NULL, "\t ", &saveptr); + char *modname = strtok_r(NULL, "\t ", &saveptr); + + if (underscores(alias) < 0 || underscores(modname) < 0) + goto syntax_error; + + kmod_config_add_alias(config, alias, modname); + } else if (streq(cmd, "blacklist")) { + char *modname = strtok_r(NULL, "\t ", &saveptr); + + if (underscores(modname) < 0) + goto syntax_error; + + kmod_config_add_blacklist(config, modname); + } else if (streq(cmd, "options")) { + char *modname = strtok_r(NULL, "\t ", &saveptr); + char *options = strtok_r(NULL, "\0", &saveptr); + + if (underscores(modname) < 0 || options == NULL) + goto syntax_error; + + kmod_config_add_options(config, modname, options); + } else if (streq(cmd, "install")) { + char *modname = strtok_r(NULL, "\t ", &saveptr); + char *installcmd = strtok_r(NULL, "\0", &saveptr); + + if (underscores(modname) < 0 || installcmd == NULL) + goto syntax_error; + + kmod_config_add_command(config, modname, installcmd, + cmd, &config->install_commands); + } else if (streq(cmd, "remove")) { + char *modname = strtok_r(NULL, "\t ", &saveptr); + char *removecmd = strtok_r(NULL, "\0", &saveptr); + + if (underscores(modname) < 0 || removecmd == NULL) + goto syntax_error; + + kmod_config_add_command(config, modname, removecmd, + cmd, &config->remove_commands); + } else if (streq(cmd, "softdep")) { + char *modname = strtok_r(NULL, "\t ", &saveptr); + char *softdeps = strtok_r(NULL, "\0", &saveptr); + + if (underscores(modname) < 0 || softdeps == NULL) + goto syntax_error; + + kmod_config_add_softdep(config, modname, softdeps); + } else if (streq(cmd, "include") + || streq(cmd, "config")) { + ERR(ctx, "%s: command %s is deprecated and not parsed anymore\n", + filename, cmd); + } else { +syntax_error: + ERR(ctx, "%s line %u: ignoring bad line starting with '%s'\n", + filename, linenum, cmd); + } + +done_next: + free(line); + } + + fclose(fp); + + return 0; +} + +void kmod_config_free(struct kmod_config *config) +{ + while (config->aliases) + kmod_config_free_alias(config, config->aliases); + + while (config->blacklists) + kmod_config_free_blacklist(config, config->blacklists); + + while (config->options) + kmod_config_free_options(config, config->options); + + while (config->install_commands) { + kmod_config_free_command(config, config->install_commands, + &config->install_commands); + } + + while (config->remove_commands) { + kmod_config_free_command(config, config->remove_commands, + &config->remove_commands); + } + + while (config->softdeps) + kmod_config_free_softdep(config, config->softdeps); + + for (; config->paths != NULL; + config->paths = kmod_list_remove(config->paths)) + free(config->paths->data); + + free(config); +} + +static bool conf_files_filter_out(struct kmod_ctx *ctx, DIR *d, + const char *path, const char *fn) +{ + size_t len = strlen(fn); + struct stat st; + + if (fn[0] == '.') + return true; + + if (len < 6 || (!streq(&fn[len - 5], ".conf") + && !streq(&fn[len - 6], ".alias"))) + return true; + + fstatat(dirfd(d), fn, &st, 0); + + if (S_ISDIR(st.st_mode)) { + ERR(ctx, "Directories inside directories are not supported: " + "%s/%s\n", path, fn); + return true; + } + + return false; +} + +struct conf_file { + const char *path; + bool is_single; + char name[]; +}; + +static int conf_files_insert_sorted(struct kmod_ctx *ctx, + struct kmod_list **list, + const char *path, const char *name) +{ + struct kmod_list *lpos, *tmp; + struct conf_file *cf; + size_t namelen; + int cmp = -1; + bool is_single = false; + + if (name == NULL) { + name = basename(path); + is_single = true; + } + + kmod_list_foreach(lpos, *list) { + cf = lpos->data; + + if ((cmp = strcmp(name, cf->name)) <= 0) + break; + } + + if (cmp == 0) { + DBG(ctx, "Ignoring duplicate config file: %s/%s\n", path, + name); + return -EEXIST; + } + + namelen = strlen(name); + cf = malloc(sizeof(*cf) + namelen + 1); + if (cf == NULL) + return -ENOMEM; + + memcpy(cf->name, name, namelen + 1); + cf->path = path; + cf->is_single = is_single; + + if (lpos == NULL) + tmp = kmod_list_append(*list, cf); + else if (lpos == *list) + tmp = kmod_list_prepend(*list, cf); + else + tmp = kmod_list_insert_before(lpos, cf); + + if (tmp == NULL) { + free(cf); + return -ENOMEM; + } + + if (lpos == NULL || lpos == *list) + *list = tmp; + + return 0; +} + +/* + * Insert configuration files in @list, ignoring duplicates + */ +static int conf_files_list(struct kmod_ctx *ctx, struct kmod_list **list, + const char *path, + unsigned long long *path_stamp) +{ + DIR *d; + int err; + struct stat st; + struct dirent *dent; + + if (stat(path, &st) != 0) { + err = -errno; + DBG(ctx, "could not stat '%s': %m\n", path); + return err; + } + + *path_stamp = stat_mstamp(&st); + + if (!S_ISDIR(st.st_mode)) { + conf_files_insert_sorted(ctx, list, path, NULL); + return 0; + } + + d = opendir(path); + if (d == NULL) { + ERR(ctx, "opendir(%s): %m\n", path); + return -EINVAL; + } + + for (dent = readdir(d); dent != NULL; dent = readdir(d)) { + if (conf_files_filter_out(ctx, d, path, dent->d_name)) + continue; + + conf_files_insert_sorted(ctx, list, path, dent->d_name); + } + + closedir(d); + return 0; +} + +int kmod_config_new(struct kmod_ctx *ctx, struct kmod_config **p_config, + const char * const *config_paths) +{ + struct kmod_config *config; + struct kmod_list *list = NULL; + struct kmod_list *path_list = NULL; + size_t i; + + conf_files_insert_sorted(ctx, &list, kmod_get_dirname(ctx), "modules.softdep"); + + for (i = 0; config_paths[i] != NULL; i++) { + const char *path = config_paths[i]; + unsigned long long path_stamp = 0; + size_t pathlen; + struct kmod_list *tmp; + struct kmod_config_path *cf; + + if (conf_files_list(ctx, &list, path, &path_stamp) < 0) + continue; + + pathlen = strlen(path) + 1; + cf = malloc(sizeof(*cf) + pathlen); + if (cf == NULL) + goto oom; + + cf->stamp = path_stamp; + memcpy(cf->path, path, pathlen); + + tmp = kmod_list_append(path_list, cf); + if (tmp == NULL) { + free(cf); + goto oom; + } + path_list = tmp; + } + + *p_config = config = calloc(1, sizeof(struct kmod_config)); + if (config == NULL) + goto oom; + + config->paths = path_list; + config->ctx = ctx; + + for (; list != NULL; list = kmod_list_remove(list)) { + char buf[PATH_MAX]; + const char *fn = buf; + struct conf_file *cf = list->data; + int fd; + + if (cf->is_single) { + fn = cf->path; + } else if (snprintf(buf, sizeof(buf), "%s/%s", + cf->path, cf->name) >= (int)sizeof(buf)) { + ERR(ctx, "Error parsing %s/%s: path too long\n", + cf->path, cf->name); + free(cf); + continue; + } + + fd = open(fn, O_RDONLY|O_CLOEXEC); + DBG(ctx, "parsing file '%s' fd=%d\n", fn, fd); + + if (fd >= 0) + kmod_config_parse(config, fd, fn); + + free(cf); + } + + kmod_config_parse_kcmdline(config); + + return 0; + +oom: + for (; list != NULL; list = kmod_list_remove(list)) + free(list->data); + + for (; path_list != NULL; path_list = kmod_list_remove(path_list)) + free(path_list->data); + + return -ENOMEM; +} + +/********************************************************************** + * struct kmod_config_iter functions + **********************************************************************/ + +enum config_type { + CONFIG_TYPE_BLACKLIST = 0, + CONFIG_TYPE_INSTALL, + CONFIG_TYPE_REMOVE, + CONFIG_TYPE_ALIAS, + CONFIG_TYPE_OPTION, + CONFIG_TYPE_SOFTDEP, +}; + +struct kmod_config_iter { + enum config_type type; + bool intermediate; + const struct kmod_list *list; + const struct kmod_list *curr; + void *data; + const char *(*get_key)(const struct kmod_list *l); + const char *(*get_value)(const struct kmod_list *l); +}; + +static const char *softdep_get_plain_softdep(const struct kmod_list *l) +{ + char *s = softdep_to_char(l->data); + return s; +} + +static struct kmod_config_iter *kmod_config_iter_new(const struct kmod_ctx* ctx, + enum config_type type) +{ + struct kmod_config_iter *iter = calloc(1, sizeof(*iter)); + const struct kmod_config *config = kmod_get_config(ctx); + + if (iter == NULL) + return NULL; + + iter->type = type; + + switch (type) { + case CONFIG_TYPE_BLACKLIST: + iter->list = config->blacklists; + iter->get_key = kmod_blacklist_get_modname; + break; + case CONFIG_TYPE_INSTALL: + iter->list = config->install_commands; + iter->get_key = kmod_command_get_modname; + iter->get_value = kmod_command_get_command; + break; + case CONFIG_TYPE_REMOVE: + iter->list = config->remove_commands; + iter->get_key = kmod_command_get_modname; + iter->get_value = kmod_command_get_command; + break; + case CONFIG_TYPE_ALIAS: + iter->list = config->aliases; + iter->get_key = kmod_alias_get_name; + iter->get_value = kmod_alias_get_modname; + break; + case CONFIG_TYPE_OPTION: + iter->list = config->options; + iter->get_key = kmod_option_get_modname; + iter->get_value = kmod_option_get_options; + break; + case CONFIG_TYPE_SOFTDEP: + iter->list = config->softdeps; + iter->get_key = kmod_softdep_get_name; + iter->get_value = softdep_get_plain_softdep; + iter->intermediate = true; + break; + } + + return iter; +} + +/** + * SECTION:libkmod-config + * @short_description: retrieve current libkmod configuration + */ + +/** + * kmod_config_get_blacklists: + * @ctx: kmod library context + * + * Retrieve an iterator to deal with the blacklist maintained inside the + * library. See kmod_config_iter_get_key(), kmod_config_iter_get_value() and + * kmod_config_iter_next(). At least one call to kmod_config_iter_next() must + * be made to initialize the iterator and check if it's valid. + * + * Returns: a new iterator over the blacklists or NULL on failure. Free it + * with kmod_config_iter_free_iter(). + */ +KMOD_EXPORT struct kmod_config_iter *kmod_config_get_blacklists(const struct kmod_ctx *ctx) +{ + if (ctx == NULL) + return NULL;; + + return kmod_config_iter_new(ctx, CONFIG_TYPE_BLACKLIST); +} + +/** + * kmod_config_get_install_commands: + * @ctx: kmod library context + * + * Retrieve an iterator to deal with the install commands maintained inside the + * library. See kmod_config_iter_get_key(), kmod_config_iter_get_value() and + * kmod_config_iter_next(). At least one call to kmod_config_iter_next() must + * be made to initialize the iterator and check if it's valid. + * + * Returns: a new iterator over the install commands or NULL on failure. Free + * it with kmod_config_iter_free_iter(). + */ +KMOD_EXPORT struct kmod_config_iter *kmod_config_get_install_commands(const struct kmod_ctx *ctx) +{ + if (ctx == NULL) + return NULL;; + + return kmod_config_iter_new(ctx, CONFIG_TYPE_INSTALL); +} + +/** + * kmod_config_get_remove_commands: + * @ctx: kmod library context + * + * Retrieve an iterator to deal with the remove commands maintained inside the + * library. See kmod_config_iter_get_key(), kmod_config_iter_get_value() and + * kmod_config_iter_next(). At least one call to kmod_config_iter_next() must + * be made to initialize the iterator and check if it's valid. + * + * Returns: a new iterator over the remove commands or NULL on failure. Free + * it with kmod_config_iter_free_iter(). + */ +KMOD_EXPORT struct kmod_config_iter *kmod_config_get_remove_commands(const struct kmod_ctx *ctx) +{ + if (ctx == NULL) + return NULL;; + + return kmod_config_iter_new(ctx, CONFIG_TYPE_REMOVE); +} + +/** + * kmod_config_get_aliases: + * @ctx: kmod library context + * + * Retrieve an iterator to deal with the aliases maintained inside the + * library. See kmod_config_iter_get_key(), kmod_config_iter_get_value() and + * kmod_config_iter_next(). At least one call to kmod_config_iter_next() must + * be made to initialize the iterator and check if it's valid. + * + * Returns: a new iterator over the aliases or NULL on failure. Free it with + * kmod_config_iter_free_iter(). + */ +KMOD_EXPORT struct kmod_config_iter *kmod_config_get_aliases(const struct kmod_ctx *ctx) +{ + if (ctx == NULL) + return NULL;; + + return kmod_config_iter_new(ctx, CONFIG_TYPE_ALIAS); +} + +/** + * kmod_config_get_options: + * @ctx: kmod library context + * + * Retrieve an iterator to deal with the options maintained inside the + * library. See kmod_config_iter_get_key(), kmod_config_iter_get_value() and + * kmod_config_iter_next(). At least one call to kmod_config_iter_next() must + * be made to initialize the iterator and check if it's valid. + * + * Returns: a new iterator over the options or NULL on failure. Free it with + * kmod_config_iter_free_iter(). + */ +KMOD_EXPORT struct kmod_config_iter *kmod_config_get_options(const struct kmod_ctx *ctx) +{ + if (ctx == NULL) + return NULL;; + + return kmod_config_iter_new(ctx, CONFIG_TYPE_OPTION); +} + +/** + * kmod_config_get_softdeps: + * @ctx: kmod library context + * + * Retrieve an iterator to deal with the softdeps maintained inside the + * library. See kmod_config_iter_get_key(), kmod_config_iter_get_value() and + * kmod_config_iter_next(). At least one call to kmod_config_iter_next() must + * be made to initialize the iterator and check if it's valid. + * + * Returns: a new iterator over the softdeps or NULL on failure. Free it with + * kmod_config_iter_free_iter(). + */ +KMOD_EXPORT struct kmod_config_iter *kmod_config_get_softdeps(const struct kmod_ctx *ctx) +{ + if (ctx == NULL) + return NULL;; + + return kmod_config_iter_new(ctx, CONFIG_TYPE_SOFTDEP); +} + +/** + * kmod_config_iter_get_key: + * @iter: iterator over a certain configuration + * + * When using a new allocated iterator, user must perform a call to + * kmod_config_iter_next() to initialize iterator's position and check if it's + * valid. + * + * Returns: the key of the current configuration pointed by @iter. + */ +KMOD_EXPORT const char *kmod_config_iter_get_key(const struct kmod_config_iter *iter) +{ + if (iter == NULL || iter->curr == NULL) + return NULL; + + return iter->get_key(iter->curr); +} + +/** + * kmod_config_iter_get_value: + * @iter: iterator over a certain configuration + * + * When using a new allocated iterator, user must perform a call to + * kmod_config_iter_next() to initialize iterator's position and check if it's + * valid. + * + * Returns: the value of the current configuration pointed by @iter. + */ +KMOD_EXPORT const char *kmod_config_iter_get_value(const struct kmod_config_iter *iter) +{ + const char *s; + + if (iter == NULL || iter->curr == NULL) + return NULL; + + if (iter->get_value == NULL) + return NULL; + + if (iter->intermediate) { + struct kmod_config_iter *i = (struct kmod_config_iter *)iter; + + free(i->data); + s = i->data = (void *) iter->get_value(iter->curr); + } else + s = iter->get_value(iter->curr); + + return s; +} + +/** + * kmod_config_iter_next: + * @iter: iterator over a certain configuration + * + * Make @iter point to the next item of a certain configuration. It's an + * automatically recycling iterator. When it reaches the end, false is + * returned; then if user wants to iterate again, it's sufficient to call this + * function once more. + * + * Returns: true if next position of @iter is valid or false if its end is + * reached. + */ +KMOD_EXPORT bool kmod_config_iter_next(struct kmod_config_iter *iter) +{ + if (iter == NULL) + return false; + + if (iter->curr == NULL) { + iter->curr = iter->list; + return iter->curr != NULL; + } + + iter->curr = kmod_list_next(iter->list, iter->curr); + + return iter->curr != NULL; +} + +/** + * kmod_config_iter_free_iter: + * @iter: iterator over a certain configuration + * + * Free resources used by the iterator. + */ +KMOD_EXPORT void kmod_config_iter_free_iter(struct kmod_config_iter *iter) +{ + free(iter->data); + free(iter); +} diff --git a/libkmod/libkmod-elf.c b/libkmod/libkmod-elf.c new file mode 100644 index 0000000..933825b --- /dev/null +++ b/libkmod/libkmod-elf.c @@ -0,0 +1,1219 @@ +/* + * libkmod - interface to kernel module operations + * + * Copyright (C) 2011-2013 ProFUSION embedded systems + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, see . + */ + +#include +#include +#include +#include +#include + +#include + +#include "libkmod.h" +#include "libkmod-internal.h" + +enum kmod_elf_class { + KMOD_ELF_32 = (1 << 1), + KMOD_ELF_64 = (1 << 2), + KMOD_ELF_LSB = (1 << 3), + KMOD_ELF_MSB = (1 << 4) +}; + +/* as defined in module-init-tools */ +struct kmod_modversion32 { + uint32_t crc; + char name[64 - sizeof(uint32_t)]; +}; + +struct kmod_modversion64 { + uint64_t crc; + char name[64 - sizeof(uint64_t)]; +}; + +struct kmod_elf { + const uint8_t *memory; + uint8_t *changed; + uint64_t size; + enum kmod_elf_class class; + struct kmod_elf_header { + struct { + uint64_t offset; + uint16_t count; + uint16_t entry_size; + } section; + struct { + uint16_t section; /* index of the strings section */ + uint64_t size; + uint64_t offset; + uint32_t nameoff; /* offset in strings itself */ + } strings; + uint16_t machine; + } header; +}; + +//#define ENABLE_ELFDBG 1 + +#if defined(ENABLE_LOGGING) && defined(ENABLE_ELFDBG) +#define ELFDBG(elf, ...) \ + _elf_dbg(elf, __FILE__, __LINE__, __func__, __VA_ARGS__); + +static inline void _elf_dbg(const struct kmod_elf *elf, const char *fname, unsigned line, const char *func, const char *fmt, ...) +{ + va_list args; + + fprintf(stderr, "ELFDBG-%d%c: %s:%u %s() ", + (elf->class & KMOD_ELF_32) ? 32 : 64, + (elf->class & KMOD_ELF_MSB) ? 'M' : 'L', + fname, line, func); + va_start(args, fmt); + vfprintf(stderr, fmt, args); + va_end(args); +} +#else +#define ELFDBG(elf, ...) +#endif + + +static int elf_identify(const void *memory, uint64_t size) +{ + const uint8_t *p = memory; + int class = 0; + + if (size <= EI_NIDENT || memcmp(p, ELFMAG, SELFMAG) != 0) + return -ENOEXEC; + + switch (p[EI_CLASS]) { + case ELFCLASS32: + if (size <= sizeof(Elf32_Ehdr)) + return -EINVAL; + class |= KMOD_ELF_32; + break; + case ELFCLASS64: + if (size <= sizeof(Elf64_Ehdr)) + return -EINVAL; + class |= KMOD_ELF_64; + break; + default: + return -EINVAL; + } + + switch (p[EI_DATA]) { + case ELFDATA2LSB: + class |= KMOD_ELF_LSB; + break; + case ELFDATA2MSB: + class |= KMOD_ELF_MSB; + break; + default: + return -EINVAL; + } + + return class; +} + +static inline uint64_t elf_get_uint(const struct kmod_elf *elf, uint64_t offset, uint16_t size) +{ + const uint8_t *p; + uint64_t ret = 0; + size_t i; + + assert(size <= sizeof(uint64_t)); + assert(offset + size <= elf->size); + if (offset + size > elf->size) { + ELFDBG(elf, "out of bounds: %"PRIu64" + %"PRIu16" = %"PRIu64"> %"PRIu64" (ELF size)\n", + offset, size, offset + size, elf->size); + return (uint64_t)-1; + } + + p = elf->memory + offset; + if (elf->class & KMOD_ELF_MSB) { + for (i = 0; i < size; i++) + ret = (ret << 8) | p[i]; + } else { + for (i = 1; i <= size; i++) + ret = (ret << 8) | p[size - i]; + } + + ELFDBG(elf, "size=%"PRIu16" offset=%"PRIu64" value=%"PRIu64"\n", + size, offset, ret); + + return ret; +} + +static inline int elf_set_uint(struct kmod_elf *elf, uint64_t offset, uint64_t size, uint64_t value) +{ + uint8_t *p; + size_t i; + + ELFDBG(elf, "size=%"PRIu16" offset=%"PRIu64" value=%"PRIu64" write memory=%p\n", + size, offset, value, elf->changed); + + assert(size <= sizeof(uint64_t)); + assert(offset + size <= elf->size); + if (offset + size > elf->size) { + ELFDBG(elf, "out of bounds: %"PRIu64" + %"PRIu16" = %"PRIu64"> %"PRIu64" (ELF size)\n", + offset, size, offset + size, elf->size); + return -1; + } + + if (elf->changed == NULL) { + elf->changed = malloc(elf->size); + if (elf->changed == NULL) + return -errno; + memcpy(elf->changed, elf->memory, elf->size); + elf->memory = elf->changed; + ELFDBG(elf, "copied memory to allow writing.\n"); + } + + p = elf->changed + offset; + if (elf->class & KMOD_ELF_MSB) { + for (i = 1; i <= size; i++) { + p[size - i] = value & 0xff; + value = (value & 0xffffffffffffff00) >> 8; + } + } else { + for (i = 0; i < size; i++) { + p[i] = value & 0xff; + value = (value & 0xffffffffffffff00) >> 8; + } + } + + return 0; +} + +static inline const void *elf_get_mem(const struct kmod_elf *elf, uint64_t offset) +{ + assert(offset < elf->size); + if (offset >= elf->size) { + ELFDBG(elf, "out-of-bounds: %"PRIu64" >= %"PRIu64" (ELF size)\n", + offset, elf->size); + return NULL; + } + return elf->memory + offset; +} + +static inline const void *elf_get_section_header(const struct kmod_elf *elf, uint16_t idx) +{ + assert(idx != SHN_UNDEF); + assert(idx < elf->header.section.count); + if (idx == SHN_UNDEF || idx >= elf->header.section.count) { + ELFDBG(elf, "invalid section number: %"PRIu16", last=%"PRIu16"\n", + idx, elf->header.section.count); + return NULL; + } + return elf_get_mem(elf, elf->header.section.offset + + (uint64_t)(idx * elf->header.section.entry_size)); +} + +static inline int elf_get_section_info(const struct kmod_elf *elf, uint16_t idx, uint64_t *offset, uint64_t *size, uint32_t *nameoff) +{ + const uint8_t *p = elf_get_section_header(elf, idx); + uint64_t min_size, off = p - elf->memory; + + if (p == NULL) { + ELFDBG(elf, "no section at %"PRIu16"\n", idx); + *offset = 0; + *size = 0; + *nameoff = 0; + return -EINVAL; + } + +#define READV(field) \ + elf_get_uint(elf, off + offsetof(typeof(*hdr), field), sizeof(hdr->field)) + + if (elf->class & KMOD_ELF_32) { + const Elf32_Shdr *hdr _unused_ = (const Elf32_Shdr *)p; + *size = READV(sh_size); + *offset = READV(sh_offset); + *nameoff = READV(sh_name); + } else { + const Elf64_Shdr *hdr _unused_ = (const Elf64_Shdr *)p; + *size = READV(sh_size); + *offset = READV(sh_offset); + *nameoff = READV(sh_name); + } +#undef READV + + if (addu64_overflow(*offset, *size, &min_size) + || min_size > elf->size) { + ELFDBG(elf, "out-of-bounds: %"PRIu64" >= %"PRIu64" (ELF size)\n", + min_size, elf->size); + return -EINVAL; + } + + ELFDBG(elf, "section=%"PRIu16" is: offset=%"PRIu64" size=%"PRIu64" nameoff=%"PRIu32"\n", + idx, *offset, *size, *nameoff); + + return 0; +} + +static const char *elf_get_strings_section(const struct kmod_elf *elf, uint64_t *size) +{ + *size = elf->header.strings.size; + return elf_get_mem(elf, elf->header.strings.offset); +} + +struct kmod_elf *kmod_elf_new(const void *memory, off_t size) +{ + struct kmod_elf *elf; + uint64_t min_size; + size_t shdrs_size, shdr_size; + int class; + + assert_cc(sizeof(uint16_t) == sizeof(Elf32_Half)); + assert_cc(sizeof(uint16_t) == sizeof(Elf64_Half)); + assert_cc(sizeof(uint32_t) == sizeof(Elf32_Word)); + assert_cc(sizeof(uint32_t) == sizeof(Elf64_Word)); + + if (!memory) { + errno = -EINVAL; + return NULL; + } + + class = elf_identify(memory, size); + if (class < 0) { + errno = -class; + return NULL; + } + + elf = malloc(sizeof(struct kmod_elf)); + if (elf == NULL) { + return NULL; + } + + elf->memory = memory; + elf->changed = NULL; + elf->size = size; + elf->class = class; + +#define READV(field) \ + elf_get_uint(elf, offsetof(typeof(*hdr), field), sizeof(hdr->field)) + +#define LOAD_HEADER \ + elf->header.section.offset = READV(e_shoff); \ + elf->header.section.count = READV(e_shnum); \ + elf->header.section.entry_size = READV(e_shentsize); \ + elf->header.strings.section = READV(e_shstrndx); \ + elf->header.machine = READV(e_machine) + if (elf->class & KMOD_ELF_32) { + const Elf32_Ehdr *hdr _unused_ = elf_get_mem(elf, 0); + LOAD_HEADER; + shdr_size = sizeof(Elf32_Shdr); + } else { + const Elf64_Ehdr *hdr _unused_ = elf_get_mem(elf, 0); + LOAD_HEADER; + shdr_size = sizeof(Elf64_Shdr); + } +#undef LOAD_HEADER +#undef READV + + ELFDBG(elf, "section: offset=%"PRIu64" count=%"PRIu16" entry_size=%"PRIu16" strings index=%"PRIu16"\n", + elf->header.section.offset, + elf->header.section.count, + elf->header.section.entry_size, + elf->header.strings.section); + + if (elf->header.section.entry_size != shdr_size) { + ELFDBG(elf, "unexpected section entry size: %"PRIu16", expected %"PRIu16"\n", + elf->header.section.entry_size, shdr_size); + goto invalid; + } + shdrs_size = shdr_size * elf->header.section.count; + if (addu64_overflow(shdrs_size, elf->header.section.offset, &min_size) + || min_size > elf->size) { + ELFDBG(elf, "file is too short to hold sections\n"); + goto invalid; + } + + if (elf_get_section_info(elf, elf->header.strings.section, + &elf->header.strings.offset, + &elf->header.strings.size, + &elf->header.strings.nameoff) < 0) { + ELFDBG(elf, "could not get strings section\n"); + goto invalid; + } else { + uint64_t slen; + const char *s = elf_get_strings_section(elf, &slen); + if (slen == 0 || s[slen - 1] != '\0') { + ELFDBG(elf, "strings section does not ends with \\0\n"); + goto invalid; + } + } + + return elf; + +invalid: + free(elf); + errno = EINVAL; + return NULL; +} + +void kmod_elf_unref(struct kmod_elf *elf) +{ + free(elf->changed); + free(elf); +} + +const void *kmod_elf_get_memory(const struct kmod_elf *elf) +{ + return elf->memory; +} + +static int elf_find_section(const struct kmod_elf *elf, const char *section) +{ + uint64_t nameslen; + const char *names = elf_get_strings_section(elf, &nameslen); + uint16_t i; + + for (i = 1; i < elf->header.section.count; i++) { + uint64_t off, size; + uint32_t nameoff; + const char *n; + int err = elf_get_section_info(elf, i, &off, &size, &nameoff); + if (err < 0) + continue; + if (nameoff >= nameslen) + continue; + n = names + nameoff; + if (!streq(section, n)) + continue; + + return i; + } + + return -ENODATA; +} + +int kmod_elf_get_section(const struct kmod_elf *elf, const char *section, const void **buf, uint64_t *buf_size) +{ + uint64_t nameslen; + const char *names = elf_get_strings_section(elf, &nameslen); + uint16_t i; + + *buf = NULL; + *buf_size = 0; + + for (i = 1; i < elf->header.section.count; i++) { + uint64_t off, size; + uint32_t nameoff; + const char *n; + int err = elf_get_section_info(elf, i, &off, &size, &nameoff); + if (err < 0) + continue; + if (nameoff >= nameslen) + continue; + n = names + nameoff; + if (!streq(section, n)) + continue; + + *buf = elf_get_mem(elf, off); + *buf_size = size; + return 0; + } + + return -ENODATA; +} + +/* array will be allocated with strings in a single malloc, just free *array */ +int kmod_elf_get_strings(const struct kmod_elf *elf, const char *section, char ***array) +{ + size_t i, j, count; + uint64_t size; + const void *buf; + const char *strings; + char *s, **a; + int err; + + *array = NULL; + + err = kmod_elf_get_section(elf, section, &buf, &size); + if (err < 0) + return err; + + strings = buf; + if (strings == NULL || size == 0) + return 0; + + /* skip zero padding */ + while (strings[0] == '\0' && size > 1) { + strings++; + size--; + } + + if (size <= 1) + return 0; + + for (i = 0, count = 0; i < size; ) { + if (strings[i] != '\0') { + i++; + continue; + } + + while (strings[i] == '\0' && i < size) + i++; + + count++; + } + + if (strings[i - 1] != '\0') + count++; + + *array = a = malloc(size + 1 + sizeof(char *) * (count + 1)); + if (*array == NULL) + return -errno; + + s = (char *)(a + count + 1); + memcpy(s, strings, size); + + /* make sure the last string is NULL-terminated */ + s[size] = '\0'; + a[count] = NULL; + a[0] = s; + + for (i = 0, j = 1; j < count && i < size; ) { + if (s[i] != '\0') { + i++; + continue; + } + + while (strings[i] == '\0' && i < size) + i++; + + a[j] = &s[i]; + j++; + } + + return count; +} + +/* array will be allocated with strings in a single malloc, just free *array */ +int kmod_elf_get_modversions(const struct kmod_elf *elf, struct kmod_modversion **array) +{ + size_t off, offcrc, slen; + uint64_t size; + struct kmod_modversion *a; + const void *buf; + char *itr; + int i, count, err; +#define MODVERSION_SEC_SIZE (sizeof(struct kmod_modversion64)) + + assert_cc(sizeof(struct kmod_modversion64) == + sizeof(struct kmod_modversion32)); + + if (elf->class & KMOD_ELF_32) + offcrc = sizeof(uint32_t); + else + offcrc = sizeof(uint64_t); + + *array = NULL; + + err = kmod_elf_get_section(elf, "__versions", &buf, &size); + if (err < 0) + return err; + + if (buf == NULL || size == 0) + return 0; + + if (size % MODVERSION_SEC_SIZE != 0) + return -EINVAL; + + count = size / MODVERSION_SEC_SIZE; + + off = (const uint8_t *)buf - elf->memory; + slen = 0; + + for (i = 0; i < count; i++, off += MODVERSION_SEC_SIZE) { + const char *symbol = elf_get_mem(elf, off + offcrc); + + if (symbol[0] == '.') + symbol++; + + slen += strlen(symbol) + 1; + } + + *array = a = malloc(sizeof(struct kmod_modversion) * count + slen); + if (*array == NULL) + return -errno; + + itr = (char *)(a + count); + off = (const uint8_t *)buf - elf->memory; + + for (i = 0; i < count; i++, off += MODVERSION_SEC_SIZE) { + uint64_t crc = elf_get_uint(elf, off, offcrc); + const char *symbol = elf_get_mem(elf, off + offcrc); + size_t symbollen; + + if (symbol[0] == '.') + symbol++; + + a[i].crc = crc; + a[i].bind = KMOD_SYMBOL_UNDEF; + a[i].symbol = itr; + symbollen = strlen(symbol) + 1; + memcpy(itr, symbol, symbollen); + itr += symbollen; + } + + return count; +} + +int kmod_elf_strip_section(struct kmod_elf *elf, const char *section) +{ + uint64_t off, size; + const void *buf; + int idx = elf_find_section(elf, section); + uint64_t val; + + if (idx < 0) + return idx; + + buf = elf_get_section_header(elf, idx); + off = (const uint8_t *)buf - elf->memory; + + if (elf->class & KMOD_ELF_32) { + off += offsetof(Elf32_Shdr, sh_flags); + size = sizeof(((Elf32_Shdr *)buf)->sh_flags); + } else { + off += offsetof(Elf64_Shdr, sh_flags); + size = sizeof(((Elf64_Shdr *)buf)->sh_flags); + } + + val = elf_get_uint(elf, off, size); + val &= ~(uint64_t)SHF_ALLOC; + + return elf_set_uint(elf, off, size, val); +} + +int kmod_elf_strip_vermagic(struct kmod_elf *elf) +{ + uint64_t i, size; + const void *buf; + const char *strings; + int err; + + err = kmod_elf_get_section(elf, ".modinfo", &buf, &size); + if (err < 0) + return err; + strings = buf; + if (strings == NULL || size == 0) + return 0; + + /* skip zero padding */ + while (strings[0] == '\0' && size > 1) { + strings++; + size--; + } + if (size <= 1) + return 0; + + for (i = 0; i < size; i++) { + const char *s; + size_t off, len; + + if (strings[i] == '\0') + continue; + if (i + 1 >= size) + continue; + + s = strings + i; + len = sizeof("vermagic=") - 1; + if (i + len >= size) + continue; + if (strncmp(s, "vermagic=", len) != 0) { + i += strlen(s); + continue; + } + off = (const uint8_t *)s - elf->memory; + + if (elf->changed == NULL) { + elf->changed = malloc(elf->size); + if (elf->changed == NULL) + return -errno; + memcpy(elf->changed, elf->memory, elf->size); + elf->memory = elf->changed; + ELFDBG(elf, "copied memory to allow writing.\n"); + } + + len = strlen(s); + ELFDBG(elf, "clear .modinfo vermagic \"%s\" (%zd bytes)\n", + s, len); + memset(elf->changed + off, '\0', len); + return 0; + } + + ELFDBG(elf, "no vermagic found in .modinfo\n"); + return -ENODATA; +} + + +static int kmod_elf_get_symbols_symtab(const struct kmod_elf *elf, struct kmod_modversion **array) +{ + uint64_t i, last, size; + const void *buf; + const char *strings; + char *itr; + struct kmod_modversion *a; + int count, err; + + *array = NULL; + + err = kmod_elf_get_section(elf, "__ksymtab_strings", &buf, &size); + if (err < 0) + return err; + strings = buf; + if (strings == NULL || size == 0) + return 0; + + /* skip zero padding */ + while (strings[0] == '\0' && size > 1) { + strings++; + size--; + } + if (size <= 1) + return 0; + + last = 0; + for (i = 0, count = 0; i < size; i++) { + if (strings[i] == '\0') { + if (last == i) { + last = i + 1; + continue; + } + count++; + last = i + 1; + } + } + if (strings[i - 1] != '\0') + count++; + + *array = a = malloc(size + 1 + sizeof(struct kmod_modversion) * count); + if (*array == NULL) + return -errno; + + itr = (char *)(a + count); + last = 0; + for (i = 0, count = 0; i < size; i++) { + if (strings[i] == '\0') { + size_t slen = i - last; + if (last == i) { + last = i + 1; + continue; + } + a[count].crc = 0; + a[count].bind = KMOD_SYMBOL_GLOBAL; + a[count].symbol = itr; + memcpy(itr, strings + last, slen); + itr[slen] = '\0'; + itr += slen + 1; + count++; + last = i + 1; + } + } + if (strings[i - 1] != '\0') { + size_t slen = i - last; + a[count].crc = 0; + a[count].bind = KMOD_SYMBOL_GLOBAL; + a[count].symbol = itr; + memcpy(itr, strings + last, slen); + itr[slen] = '\0'; + count++; + } + + return count; +} + +static inline uint8_t kmod_symbol_bind_from_elf(uint8_t elf_value) +{ + switch (elf_value) { + case STB_LOCAL: + return KMOD_SYMBOL_LOCAL; + case STB_GLOBAL: + return KMOD_SYMBOL_GLOBAL; + case STB_WEAK: + return KMOD_SYMBOL_WEAK; + default: + return KMOD_SYMBOL_NONE; + } +} + +static uint64_t kmod_elf_resolve_crc(const struct kmod_elf *elf, uint64_t crc, uint16_t shndx) +{ + int err; + uint64_t off, size; + uint32_t nameoff; + + if (shndx == SHN_ABS || shndx == SHN_UNDEF) + return crc; + + err = elf_get_section_info(elf, shndx, &off, &size, &nameoff); + if (err < 0) { + ELFDBG("Cound not find section index %"PRIu16" for crc", shndx); + return (uint64_t)-1; + } + + if (crc > (size - sizeof(uint32_t))) { + ELFDBG("CRC offset %"PRIu64" is too big, section %"PRIu16" size is %"PRIu64"\n", + crc, shndx, size); + return (uint64_t)-1; + } + + crc = elf_get_uint(elf, off + crc, sizeof(uint32_t)); + return crc; +} + +/* array will be allocated with strings in a single malloc, just free *array */ +int kmod_elf_get_symbols(const struct kmod_elf *elf, struct kmod_modversion **array) +{ + static const char crc_str[] = "__crc_"; + static const size_t crc_strlen = sizeof(crc_str) - 1; + uint64_t strtablen, symtablen, str_off, sym_off; + const void *strtab, *symtab; + struct kmod_modversion *a; + char *itr; + size_t slen, symlen; + int i, count, symcount, err; + + err = kmod_elf_get_section(elf, ".strtab", &strtab, &strtablen); + if (err < 0) { + ELFDBG(elf, "no .strtab found.\n"); + goto fallback; + } + + err = kmod_elf_get_section(elf, ".symtab", &symtab, &symtablen); + if (err < 0) { + ELFDBG(elf, "no .symtab found.\n"); + goto fallback; + } + + if (elf->class & KMOD_ELF_32) + symlen = sizeof(Elf32_Sym); + else + symlen = sizeof(Elf64_Sym); + + if (symtablen % symlen != 0) { + ELFDBG(elf, "unexpected .symtab of length %"PRIu64", not multiple of %"PRIu64" as expected.\n", symtablen, symlen); + goto fallback; + } + + symcount = symtablen / symlen; + count = 0; + slen = 0; + str_off = (const uint8_t *)strtab - elf->memory; + sym_off = (const uint8_t *)symtab - elf->memory + symlen; + for (i = 1; i < symcount; i++, sym_off += symlen) { + const char *name; + uint32_t name_off; + +#define READV(field) \ + elf_get_uint(elf, sym_off + offsetof(typeof(*s), field),\ + sizeof(s->field)) + if (elf->class & KMOD_ELF_32) { + Elf32_Sym *s; + name_off = READV(st_name); + } else { + Elf64_Sym *s; + name_off = READV(st_name); + } +#undef READV + if (name_off >= strtablen) { + ELFDBG(elf, ".strtab is %"PRIu64" bytes, but .symtab entry %d wants to access offset %"PRIu32".\n", strtablen, i, name_off); + goto fallback; + } + + name = elf_get_mem(elf, str_off + name_off); + + if (strncmp(name, crc_str, crc_strlen) != 0) + continue; + slen += strlen(name + crc_strlen) + 1; + count++; + } + + if (count == 0) + goto fallback; + + *array = a = malloc(sizeof(struct kmod_modversion) * count + slen); + if (*array == NULL) + return -errno; + + itr = (char *)(a + count); + count = 0; + str_off = (const uint8_t *)strtab - elf->memory; + sym_off = (const uint8_t *)symtab - elf->memory + symlen; + for (i = 1; i < symcount; i++, sym_off += symlen) { + const char *name; + uint32_t name_off; + uint64_t crc; + uint8_t info, bind; + uint16_t shndx; + +#define READV(field) \ + elf_get_uint(elf, sym_off + offsetof(typeof(*s), field),\ + sizeof(s->field)) + if (elf->class & KMOD_ELF_32) { + Elf32_Sym *s; + name_off = READV(st_name); + crc = READV(st_value); + info = READV(st_info); + shndx = READV(st_shndx); + } else { + Elf64_Sym *s; + name_off = READV(st_name); + crc = READV(st_value); + info = READV(st_info); + shndx = READV(st_shndx); + } +#undef READV + name = elf_get_mem(elf, str_off + name_off); + if (strncmp(name, crc_str, crc_strlen) != 0) + continue; + name += crc_strlen; + + if (elf->class & KMOD_ELF_32) + bind = ELF32_ST_BIND(info); + else + bind = ELF64_ST_BIND(info); + + a[count].crc = kmod_elf_resolve_crc(elf, crc, shndx); + a[count].bind = kmod_symbol_bind_from_elf(bind); + a[count].symbol = itr; + slen = strlen(name); + memcpy(itr, name, slen); + itr[slen] = '\0'; + itr += slen + 1; + count++; + } + return count; + +fallback: + ELFDBG(elf, "Falling back to __ksymtab_strings!\n"); + return kmod_elf_get_symbols_symtab(elf, array); +} + +static int kmod_elf_crc_find(const struct kmod_elf *elf, const void *versions, uint64_t versionslen, const char *name, uint64_t *crc) +{ + size_t verlen, crclen, off; + uint64_t i; + + if (elf->class & KMOD_ELF_32) { + struct kmod_modversion32 *mv; + verlen = sizeof(*mv); + crclen = sizeof(mv->crc); + } else { + struct kmod_modversion64 *mv; + verlen = sizeof(*mv); + crclen = sizeof(mv->crc); + } + + off = (const uint8_t *)versions - elf->memory; + for (i = 0; i < versionslen; i += verlen) { + const char *symbol = elf_get_mem(elf, off + i + crclen); + if (!streq(name, symbol)) + continue; + *crc = elf_get_uint(elf, off + i, crclen); + return i / verlen; + } + + ELFDBG(elf, "could not find crc for symbol '%s'\n", name); + *crc = 0; + return -1; +} + +/* from module-init-tools:elfops_core.c */ +#ifndef STT_REGISTER +#define STT_REGISTER 13 /* Global register reserved to app. */ +#endif + +/* array will be allocated with strings in a single malloc, just free *array */ +int kmod_elf_get_dependency_symbols(const struct kmod_elf *elf, struct kmod_modversion **array) +{ + uint64_t versionslen, strtablen, symtablen, str_off, sym_off, ver_off; + const void *versions, *strtab, *symtab; + struct kmod_modversion *a; + char *itr; + size_t slen, verlen, symlen, crclen; + int i, count, symcount, vercount, err; + bool handle_register_symbols; + uint8_t *visited_versions; + uint64_t *symcrcs; + + err = kmod_elf_get_section(elf, "__versions", &versions, &versionslen); + if (err < 0) { + versions = NULL; + versionslen = 0; + verlen = 0; + crclen = 0; + } else { + if (elf->class & KMOD_ELF_32) { + struct kmod_modversion32 *mv; + verlen = sizeof(*mv); + crclen = sizeof(mv->crc); + } else { + struct kmod_modversion64 *mv; + verlen = sizeof(*mv); + crclen = sizeof(mv->crc); + } + if (versionslen % verlen != 0) { + ELFDBG(elf, "unexpected __versions of length %"PRIu64", not multiple of %zd as expected.\n", versionslen, verlen); + versions = NULL; + versionslen = 0; + } + } + + err = kmod_elf_get_section(elf, ".strtab", &strtab, &strtablen); + if (err < 0) { + ELFDBG(elf, "no .strtab found.\n"); + return -EINVAL; + } + + err = kmod_elf_get_section(elf, ".symtab", &symtab, &symtablen); + if (err < 0) { + ELFDBG(elf, "no .symtab found.\n"); + return -EINVAL; + } + + if (elf->class & KMOD_ELF_32) + symlen = sizeof(Elf32_Sym); + else + symlen = sizeof(Elf64_Sym); + + if (symtablen % symlen != 0) { + ELFDBG(elf, "unexpected .symtab of length %"PRIu64", not multiple of %"PRIu64" as expected.\n", symtablen, symlen); + return -EINVAL; + } + + if (versionslen == 0) { + vercount = 0; + visited_versions = NULL; + } else { + vercount = versionslen / verlen; + visited_versions = calloc(vercount, sizeof(uint8_t)); + if (visited_versions == NULL) + return -ENOMEM; + } + + handle_register_symbols = (elf->header.machine == EM_SPARC || + elf->header.machine == EM_SPARCV9); + + symcount = symtablen / symlen; + count = 0; + slen = 0; + str_off = (const uint8_t *)strtab - elf->memory; + sym_off = (const uint8_t *)symtab - elf->memory + symlen; + + symcrcs = calloc(symcount, sizeof(uint64_t)); + if (symcrcs == NULL) { + free(visited_versions); + return -ENOMEM; + } + + for (i = 1; i < symcount; i++, sym_off += symlen) { + const char *name; + uint64_t crc; + uint32_t name_off; + uint16_t secidx; + uint8_t info; + int idx; + +#define READV(field) \ + elf_get_uint(elf, sym_off + offsetof(typeof(*s), field),\ + sizeof(s->field)) + if (elf->class & KMOD_ELF_32) { + Elf32_Sym *s; + name_off = READV(st_name); + secidx = READV(st_shndx); + info = READV(st_info); + } else { + Elf64_Sym *s; + name_off = READV(st_name); + secidx = READV(st_shndx); + info = READV(st_info); + } +#undef READV + if (secidx != SHN_UNDEF) + continue; + + if (handle_register_symbols) { + uint8_t type; + if (elf->class & KMOD_ELF_32) + type = ELF32_ST_TYPE(info); + else + type = ELF64_ST_TYPE(info); + + /* Not really undefined: sparc gcc 3.3 creates + * U references when you have global asm + * variables, to avoid anyone else misusing + * them. + */ + if (type == STT_REGISTER) + continue; + } + + if (name_off >= strtablen) { + ELFDBG(elf, ".strtab is %"PRIu64" bytes, but .symtab entry %d wants to access offset %"PRIu32".\n", strtablen, i, name_off); + free(visited_versions); + free(symcrcs); + return -EINVAL; + } + + name = elf_get_mem(elf, str_off + name_off); + if (name[0] == '\0') { + ELFDBG(elf, "empty symbol name at index %"PRIu64"\n", i); + continue; + } + + slen += strlen(name) + 1; + count++; + + idx = kmod_elf_crc_find(elf, versions, versionslen, name, &crc); + if (idx >= 0 && visited_versions != NULL) + visited_versions[idx] = 1; + symcrcs[i] = crc; + } + + if (visited_versions != NULL) { + /* module_layout/struct_module are not visited, but needed */ + ver_off = (const uint8_t *)versions - elf->memory; + for (i = 0; i < vercount; i++) { + if (visited_versions[i] == 0) { + const char *name; + name = elf_get_mem(elf, ver_off + i * verlen + crclen); + slen += strlen(name) + 1; + + count++; + } + } + } + + if (count == 0) { + free(visited_versions); + free(symcrcs); + *array = NULL; + return 0; + } + + *array = a = malloc(sizeof(struct kmod_modversion) * count + slen); + if (*array == NULL) { + free(visited_versions); + free(symcrcs); + return -errno; + } + + itr = (char *)(a + count); + count = 0; + str_off = (const uint8_t *)strtab - elf->memory; + sym_off = (const uint8_t *)symtab - elf->memory + symlen; + for (i = 1; i < symcount; i++, sym_off += symlen) { + const char *name; + uint64_t crc; + uint32_t name_off; + uint16_t secidx; + uint8_t info, bind; + +#define READV(field) \ + elf_get_uint(elf, sym_off + offsetof(typeof(*s), field),\ + sizeof(s->field)) + if (elf->class & KMOD_ELF_32) { + Elf32_Sym *s; + name_off = READV(st_name); + secidx = READV(st_shndx); + info = READV(st_info); + } else { + Elf64_Sym *s; + name_off = READV(st_name); + secidx = READV(st_shndx); + info = READV(st_info); + } +#undef READV + if (secidx != SHN_UNDEF) + continue; + + if (handle_register_symbols) { + uint8_t type; + if (elf->class & KMOD_ELF_32) + type = ELF32_ST_TYPE(info); + else + type = ELF64_ST_TYPE(info); + + /* Not really undefined: sparc gcc 3.3 creates + * U references when you have global asm + * variables, to avoid anyone else misusing + * them. + */ + if (type == STT_REGISTER) + continue; + } + + name = elf_get_mem(elf, str_off + name_off); + if (name[0] == '\0') { + ELFDBG(elf, "empty symbol name at index %"PRIu64"\n", i); + continue; + } + + if (elf->class & KMOD_ELF_32) + bind = ELF32_ST_BIND(info); + else + bind = ELF64_ST_BIND(info); + if (bind == STB_WEAK) + bind = KMOD_SYMBOL_WEAK; + else + bind = KMOD_SYMBOL_UNDEF; + + slen = strlen(name); + crc = symcrcs[i]; + + a[count].crc = crc; + a[count].bind = bind; + a[count].symbol = itr; + memcpy(itr, name, slen); + itr[slen] = '\0'; + itr += slen + 1; + + count++; + } + + free(symcrcs); + + if (visited_versions == NULL) + return count; + + /* add unvisited (module_layout/struct_module) */ + ver_off = (const uint8_t *)versions - elf->memory; + for (i = 0; i < vercount; i++) { + const char *name; + uint64_t crc; + + if (visited_versions[i] != 0) + continue; + + name = elf_get_mem(elf, ver_off + i * verlen + crclen); + slen = strlen(name); + crc = elf_get_uint(elf, ver_off + i * verlen, crclen); + + a[count].crc = crc; + a[count].bind = KMOD_SYMBOL_UNDEF; + a[count].symbol = itr; + memcpy(itr, name, slen); + itr[slen] = '\0'; + itr += slen + 1; + + count++; + } + free(visited_versions); + return count; +} diff --git a/libkmod/libkmod-file.c b/libkmod/libkmod-file.c new file mode 100644 index 0000000..b138e7e --- /dev/null +++ b/libkmod/libkmod-file.c @@ -0,0 +1,541 @@ +/* + * libkmod - interface to kernel module operations + * + * Copyright (C) 2011-2013 ProFUSION embedded systems + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, see . + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#ifdef ENABLE_ZSTD +#include +#endif +#ifdef ENABLE_XZ +#include +#endif +#ifdef ENABLE_ZLIB +#include +#endif + +#include + +#include "libkmod.h" +#include "libkmod-internal.h" + +struct kmod_file; +struct file_ops { + int (*load)(struct kmod_file *file); + void (*unload)(struct kmod_file *file); +}; + +struct kmod_file { +#ifdef ENABLE_ZSTD + bool zstd_used; +#endif +#ifdef ENABLE_XZ + bool xz_used; +#endif +#ifdef ENABLE_ZLIB + gzFile gzf; +#endif + int fd; + enum kmod_file_compression_type compression; + off_t size; + void *memory; + const struct file_ops *ops; + const struct kmod_ctx *ctx; + struct kmod_elf *elf; +}; + +#ifdef ENABLE_ZSTD +static int zstd_read_block(struct kmod_file *file, size_t block_size, + ZSTD_inBuffer *input, size_t *input_capacity) +{ + ssize_t rdret; + int ret; + + if (*input_capacity < block_size) { + free((void *)input->src); + input->src = malloc(block_size); + if (input->src == NULL) { + ret = -errno; + ERR(file->ctx, "zstd: %m\n"); + return ret; + } + *input_capacity = block_size; + } + + rdret = read(file->fd, (void *)input->src, block_size); + if (rdret < 0) { + ret = -errno; + ERR(file->ctx, "zstd: %m\n"); + return ret; + } + + input->pos = 0; + input->size = rdret; + return 0; +} + +static int zstd_ensure_outbuffer_space(ZSTD_outBuffer *buffer, size_t min_free) +{ + uint8_t *old_buffer = buffer->dst; + int ret = 0; + + if (buffer->size - buffer->pos >= min_free) + return 0; + + buffer->size += min_free; + buffer->dst = realloc(buffer->dst, buffer->size); + if (buffer->dst == NULL) { + ret = -errno; + free(old_buffer); + } + + return ret; +} + +static int zstd_decompress_block(struct kmod_file *file, ZSTD_DStream *dstr, + ZSTD_inBuffer *input, ZSTD_outBuffer *output, + size_t *next_block_size) +{ + size_t out_buf_min_size = ZSTD_DStreamOutSize(); + int ret = 0; + + do { + ssize_t dsret; + + ret = zstd_ensure_outbuffer_space(output, out_buf_min_size); + if (ret) { + ERR(file->ctx, "zstd: %s\n", strerror(-ret)); + break; + } + + dsret = ZSTD_decompressStream(dstr, output, input); + if (ZSTD_isError(dsret)) { + ret = -EINVAL; + ERR(file->ctx, "zstd: %s\n", ZSTD_getErrorName(dsret)); + break; + } + if (dsret > 0) + *next_block_size = (size_t)dsret; + } while (input->pos < input->size + || output->pos > output->size + || output->size - output->pos < out_buf_min_size); + + return ret; +} + +static int load_zstd(struct kmod_file *file) +{ + ZSTD_DStream *dstr; + size_t next_block_size; + size_t zst_inb_capacity = 0; + ZSTD_inBuffer zst_inb = { 0 }; + ZSTD_outBuffer zst_outb = { 0 }; + int ret; + + dstr = ZSTD_createDStream(); + if (dstr == NULL) { + ret = -EINVAL; + ERR(file->ctx, "zstd: Failed to create decompression stream\n"); + goto out; + } + + next_block_size = ZSTD_initDStream(dstr); + + while (true) { + ret = zstd_read_block(file, next_block_size, &zst_inb, + &zst_inb_capacity); + if (ret != 0) + goto out; + if (zst_inb.size == 0) /* EOF */ + break; + + ret = zstd_decompress_block(file, dstr, &zst_inb, &zst_outb, + &next_block_size); + if (ret != 0) + goto out; + } + + ZSTD_freeDStream(dstr); + free((void *)zst_inb.src); + file->zstd_used = true; + file->memory = zst_outb.dst; + file->size = zst_outb.pos; + return 0; +out: + if (dstr != NULL) + ZSTD_freeDStream(dstr); + free((void *)zst_inb.src); + free((void *)zst_outb.dst); + return ret; +} + +static void unload_zstd(struct kmod_file *file) +{ + if (!file->zstd_used) + return; + free(file->memory); +} + +static const char magic_zstd[] = {0x28, 0xB5, 0x2F, 0xFD}; +#endif + +#ifdef ENABLE_XZ +static void xz_uncompress_belch(struct kmod_file *file, lzma_ret ret) +{ + switch (ret) { + case LZMA_MEM_ERROR: + ERR(file->ctx, "xz: %s\n", strerror(ENOMEM)); + break; + case LZMA_FORMAT_ERROR: + ERR(file->ctx, "xz: File format not recognized\n"); + break; + case LZMA_OPTIONS_ERROR: + ERR(file->ctx, "xz: Unsupported compression options\n"); + break; + case LZMA_DATA_ERROR: + ERR(file->ctx, "xz: File is corrupt\n"); + break; + case LZMA_BUF_ERROR: + ERR(file->ctx, "xz: Unexpected end of input\n"); + break; + default: + ERR(file->ctx, "xz: Internal error (bug)\n"); + break; + } +} + +static int xz_uncompress(lzma_stream *strm, struct kmod_file *file) +{ + uint8_t in_buf[BUFSIZ], out_buf[BUFSIZ]; + lzma_action action = LZMA_RUN; + lzma_ret ret; + void *p = NULL; + size_t total = 0; + + strm->avail_in = 0; + strm->next_out = out_buf; + strm->avail_out = sizeof(out_buf); + + while (true) { + if (strm->avail_in == 0) { + ssize_t rdret = read(file->fd, in_buf, sizeof(in_buf)); + if (rdret < 0) { + ret = -errno; + goto out; + } + strm->next_in = in_buf; + strm->avail_in = rdret; + if (rdret == 0) + action = LZMA_FINISH; + } + ret = lzma_code(strm, action); + if (strm->avail_out == 0 || ret != LZMA_OK) { + size_t write_size = BUFSIZ - strm->avail_out; + char *tmp = realloc(p, total + write_size); + if (tmp == NULL) { + ret = -errno; + goto out; + } + memcpy(tmp + total, out_buf, write_size); + total += write_size; + p = tmp; + strm->next_out = out_buf; + strm->avail_out = BUFSIZ; + } + if (ret == LZMA_STREAM_END) + break; + if (ret != LZMA_OK) { + xz_uncompress_belch(file, ret); + ret = -EINVAL; + goto out; + } + } + file->xz_used = true; + file->memory = p; + file->size = total; + return 0; + out: + free(p); + return ret; +} + +static int load_xz(struct kmod_file *file) +{ + lzma_stream strm = LZMA_STREAM_INIT; + lzma_ret lzret; + int ret; + + lzret = lzma_stream_decoder(&strm, UINT64_MAX, LZMA_CONCATENATED); + if (lzret == LZMA_MEM_ERROR) { + ERR(file->ctx, "xz: %s\n", strerror(ENOMEM)); + return -ENOMEM; + } else if (lzret != LZMA_OK) { + ERR(file->ctx, "xz: Internal error (bug)\n"); + return -EINVAL; + } + ret = xz_uncompress(&strm, file); + lzma_end(&strm); + return ret; +} + +static void unload_xz(struct kmod_file *file) +{ + if (!file->xz_used) + return; + free(file->memory); +} + +static const char magic_xz[] = {0xfd, '7', 'z', 'X', 'Z', 0}; +#endif + +#ifdef ENABLE_ZLIB +#define READ_STEP (4 * 1024 * 1024) +static int load_zlib(struct kmod_file *file) +{ + int err = 0; + off_t did = 0, total = 0; + _cleanup_free_ unsigned char *p = NULL; + + errno = 0; + file->gzf = gzdopen(file->fd, "rb"); + if (file->gzf == NULL) + return -errno; + file->fd = -1; /* now owned by gzf due gzdopen() */ + + for (;;) { + int r; + + if (did == total) { + void *tmp = realloc(p, total + READ_STEP); + if (tmp == NULL) { + err = -errno; + goto error; + } + total += READ_STEP; + p = tmp; + } + + r = gzread(file->gzf, p + did, total - did); + if (r == 0) + break; + else if (r < 0) { + int gzerr; + const char *gz_errmsg = gzerror(file->gzf, &gzerr); + + ERR(file->ctx, "gzip: %s\n", gz_errmsg); + + /* gzip might not set errno here */ + err = gzerr == Z_ERRNO ? -errno : -EINVAL; + goto error; + } + did += r; + } + + file->memory = p; + file->size = did; + p = NULL; + return 0; + +error: + gzclose(file->gzf); + return err; +} + +static void unload_zlib(struct kmod_file *file) +{ + if (file->gzf == NULL) + return; + free(file->memory); + gzclose(file->gzf); /* closes file->fd */ +} + +static const char magic_zlib[] = {0x1f, 0x8b}; +#endif + +static const struct comp_type { + size_t magic_size; + enum kmod_file_compression_type compression; + const char *magic_bytes; + const struct file_ops ops; +} comp_types[] = { +#ifdef ENABLE_ZSTD + {sizeof(magic_zstd), KMOD_FILE_COMPRESSION_ZSTD, magic_zstd, {load_zstd, unload_zstd}}, +#endif +#ifdef ENABLE_XZ + {sizeof(magic_xz), KMOD_FILE_COMPRESSION_XZ, magic_xz, {load_xz, unload_xz}}, +#endif +#ifdef ENABLE_ZLIB + {sizeof(magic_zlib), KMOD_FILE_COMPRESSION_ZLIB, magic_zlib, {load_zlib, unload_zlib}}, +#endif + {0, KMOD_FILE_COMPRESSION_NONE, NULL, {NULL, NULL}} +}; + +static int load_reg(struct kmod_file *file) +{ + struct stat st; + + if (fstat(file->fd, &st) < 0) + return -errno; + + file->size = st.st_size; + file->memory = mmap(NULL, file->size, PROT_READ, MAP_PRIVATE, + file->fd, 0); + if (file->memory == MAP_FAILED) + return -errno; + + return 0; +} + +static void unload_reg(struct kmod_file *file) +{ + munmap(file->memory, file->size); +} + +static const struct file_ops reg_ops = { + load_reg, unload_reg +}; + +struct kmod_elf *kmod_file_get_elf(struct kmod_file *file) +{ + if (file->elf) + return file->elf; + + kmod_file_load_contents(file); + file->elf = kmod_elf_new(file->memory, file->size); + return file->elf; +} + +struct kmod_file *kmod_file_open(const struct kmod_ctx *ctx, + const char *filename) +{ + struct kmod_file *file = calloc(1, sizeof(struct kmod_file)); + const struct comp_type *itr; + size_t magic_size_max = 0; + int err = 0; + + if (file == NULL) + return NULL; + + file->fd = open(filename, O_RDONLY|O_CLOEXEC); + if (file->fd < 0) { + err = -errno; + goto error; + } + + for (itr = comp_types; itr->ops.load != NULL; itr++) { + if (magic_size_max < itr->magic_size) + magic_size_max = itr->magic_size; + } + + if (magic_size_max > 0) { + char *buf = alloca(magic_size_max + 1); + ssize_t sz; + + if (buf == NULL) { + err = -errno; + goto error; + } + sz = read_str_safe(file->fd, buf, magic_size_max + 1); + lseek(file->fd, 0, SEEK_SET); + if (sz != (ssize_t)magic_size_max) { + if (sz < 0) + err = sz; + else + err = -EINVAL; + goto error; + } + + for (itr = comp_types; itr->ops.load != NULL; itr++) { + if (memcmp(buf, itr->magic_bytes, itr->magic_size) == 0) { + file->ops = &itr->ops; + file->compression = itr->compression; + break; + } + } + } + + if (file->ops == NULL) { + file->ops = ®_ops; + file->compression = KMOD_FILE_COMPRESSION_NONE; + } + + file->ctx = ctx; + +error: + if (err < 0) { + if (file->fd >= 0) + close(file->fd); + free(file); + errno = -err; + return NULL; + } + + return file; +} + +/* + * Callers should just check file->memory got updated + */ +void kmod_file_load_contents(struct kmod_file *file) +{ + if (file->memory) + return; + + /* The load functions already log possible errors. */ + file->ops->load(file); +} + +void *kmod_file_get_contents(const struct kmod_file *file) +{ + return file->memory; +} + +off_t kmod_file_get_size(const struct kmod_file *file) +{ + return file->size; +} + +enum kmod_file_compression_type kmod_file_get_compression(const struct kmod_file *file) +{ + return file->compression; +} + +int kmod_file_get_fd(const struct kmod_file *file) +{ + return file->fd; +} + +void kmod_file_unref(struct kmod_file *file) +{ + if (file->elf) + kmod_elf_unref(file->elf); + + if (file->memory) + file->ops->unload(file); + + if (file->fd >= 0) + close(file->fd); + free(file); +} diff --git a/libkmod/libkmod-index.c b/libkmod/libkmod-index.c new file mode 100644 index 0000000..6a34c8d --- /dev/null +++ b/libkmod/libkmod-index.c @@ -0,0 +1,1082 @@ +/* + * libkmod - interface to kernel module operations + * + * Copyright (C) 2011-2013 ProFUSION embedded systems + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, see . + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "libkmod-internal.h" +#include "libkmod-index.h" + +/* libkmod-index.c: module index file implementation + * + * Integers are stored as 32 bit unsigned in "network" order, i.e. MSB first. + * All files start with a magic number. + * + * Magic spells "BOOTFAST". Second one used on newer versioned binary files. + * #define INDEX_MAGIC_OLD 0xB007FA57 + * + * We use a version string to keep track of changes to the binary format + * This is stored in the form: INDEX_MAJOR (hi) INDEX_MINOR (lo) just in + * case we ever decide to have minor changes that are not incompatible. + */ +#define INDEX_MAGIC 0xB007F457 +#define INDEX_VERSION_MAJOR 0x0002 +#define INDEX_VERSION_MINOR 0x0001 +#define INDEX_VERSION ((INDEX_VERSION_MAJOR<<16)|INDEX_VERSION_MINOR) + +/* The index file maps keys to values. Both keys and values are ASCII strings. + * Each key can have multiple values. Values are sorted by an integer priority. + * + * The reader also implements a wildcard search (including range expressions) + * where the keys in the index are treated as patterns. + * This feature is required for module aliases. + */ +#define INDEX_CHILDMAX 128 + +/* Disk format: + * + * uint32_t magic = INDEX_MAGIC; + * uint32_t version = INDEX_VERSION; + * uint32_t root_offset; + * + * (node_offset & INDEX_NODE_MASK) specifies the file offset of nodes: + * + * char[] prefix; // nul terminated + * + * char first; + * char last; + * uint32_t children[last - first + 1]; + * + * uint32_t value_count; + * struct { + * uint32_t priority; + * char[] value; // nul terminated + * } values[value_count]; + * + * (node_offset & INDEX_NODE_FLAGS) indicates which fields are present. + * Empty prefixes are omitted, leaf nodes omit the three child-related fields. + * + * This could be optimised further by adding a sparse child format + * (indicated using a new flag). + * + * + * Implementation is based on a radix tree, or "trie". + * Each arc from parent to child is labelled with a character. + * Each path from the root represents a string. + * + * == Example strings == + * + * ask + * ate + * on + * once + * one + * + * == Key == + * + Normal node + * * Marked node, representing a key and it's values. + * + * + + * |-a-+-s-+-k-* + * | | + * | `-t-+-e-* + * | + * `-o-+-n-*-c-+-e-* + * | + * `-e-* + * + * Naive implementations tend to be very space inefficient; child pointers + * are stored in arrays indexed by character, but most child pointers are null. + * + * Our implementation uses a scheme described by Wikipedia as a Patrica trie, + * + * "easiest to understand as a space-optimized trie where + * each node with only one child is merged with its child" + * + * + + * |-a-+-sk-* + * | | + * | `-te-* + * | + * `-on-*-ce-* + * | + * `-e-* + * + * We still use arrays of child pointers indexed by a single character; + * the remaining characters of the label are stored as a "prefix" in the child. + * + * The paper describing the original Patrica trie works on individiual bits - + * each node has a maximum of two children, which increases space efficiency. + * However for this application it is simpler to use the ASCII character set. + * Since the index file is read-only, it can be compressed by omitting null + * child pointers at the start and end of arrays. + */ + +/* Format of node offsets within index file */ +enum node_offset { + INDEX_NODE_FLAGS = 0xF0000000, /* Flags in high nibble */ + INDEX_NODE_PREFIX = 0x80000000, + INDEX_NODE_VALUES = 0x40000000, + INDEX_NODE_CHILDS = 0x20000000, + + INDEX_NODE_MASK = 0x0FFFFFFF, /* Offset value */ +}; + +void index_values_free(struct index_value *values) +{ + while (values) { + struct index_value *value = values; + + values = value->next; + free(value); + } +} + +static int add_value(struct index_value **values, + const char *value, unsigned len, unsigned int priority) +{ + struct index_value *v; + + /* find position to insert value */ + while (*values && (*values)->priority < priority) + values = &(*values)->next; + + v = malloc(sizeof(struct index_value) + len + 1); + if (!v) + return -1; + v->next = *values; + v->priority = priority; + v->len = len; + memcpy(v->value, value, len); + v->value[len] = '\0'; + *values = v; + + return 0; +} + +static void read_error(void) +{ + fatal("Module index: unexpected error: %s\n" + "Try re-running depmod\n", errno ? strerror(errno) : "EOF"); +} + +static int read_char(FILE *in) +{ + int ch; + + errno = 0; + ch = getc_unlocked(in); + if (ch == EOF) + read_error(); + return ch; +} + +static uint32_t read_long(FILE *in) +{ + uint32_t l; + + errno = 0; + if (fread(&l, sizeof(uint32_t), 1, in) != sizeof(uint32_t)) + read_error(); + return ntohl(l); +} + +static unsigned buf_freadchars(struct strbuf *buf, FILE *in) +{ + unsigned i = 0; + int ch; + + while ((ch = read_char(in))) { + if (!strbuf_pushchar(buf, ch)) + break; + i++; + } + + return i; +} + +/* + * Index file searching + */ +struct index_node_f { + FILE *file; + char *prefix; /* path compression */ + struct index_value *values; + unsigned char first; /* range of child nodes */ + unsigned char last; + uint32_t children[0]; +}; + +static struct index_node_f *index_read(FILE *in, uint32_t offset) +{ + struct index_node_f *node; + char *prefix; + int i, child_count = 0; + + if ((offset & INDEX_NODE_MASK) == 0) + return NULL; + + if (fseek(in, offset & INDEX_NODE_MASK, SEEK_SET) < 0) + return NULL; + + if (offset & INDEX_NODE_PREFIX) { + struct strbuf buf; + strbuf_init(&buf); + buf_freadchars(&buf, in); + prefix = strbuf_steal(&buf); + } else + prefix = NOFAIL(strdup("")); + + if (offset & INDEX_NODE_CHILDS) { + char first = read_char(in); + char last = read_char(in); + child_count = last - first + 1; + + node = NOFAIL(malloc(sizeof(struct index_node_f) + + sizeof(uint32_t) * child_count)); + + node->first = first; + node->last = last; + + for (i = 0; i < child_count; i++) + node->children[i] = read_long(in); + } else { + node = NOFAIL(malloc(sizeof(struct index_node_f))); + node->first = INDEX_CHILDMAX; + node->last = 0; + } + + node->values = NULL; + if (offset & INDEX_NODE_VALUES) { + int value_count; + struct strbuf buf; + const char *value; + unsigned int priority; + + value_count = read_long(in); + + strbuf_init(&buf); + while (value_count--) { + priority = read_long(in); + buf_freadchars(&buf, in); + value = strbuf_str(&buf); + add_value(&node->values, value, buf.used, priority); + strbuf_clear(&buf); + } + strbuf_release(&buf); + } + + node->prefix = prefix; + node->file = in; + return node; +} + +static void index_close(struct index_node_f *node) +{ + free(node->prefix); + index_values_free(node->values); + free(node); +} + +struct index_file { + FILE *file; + uint32_t root_offset; +}; + +struct index_file *index_file_open(const char *filename) +{ + FILE *file; + uint32_t magic, version; + struct index_file *new; + + file = fopen(filename, "re"); + if (!file) + return NULL; + errno = EINVAL; + + magic = read_long(file); + if (magic != INDEX_MAGIC) { + fclose(file); + return NULL; + } + + version = read_long(file); + if (version >> 16 != INDEX_VERSION_MAJOR) { + fclose(file); + return NULL; + } + + new = NOFAIL(malloc(sizeof(struct index_file))); + new->file = file; + new->root_offset = read_long(new->file); + + errno = 0; + return new; +} + +void index_file_close(struct index_file *idx) +{ + fclose(idx->file); + free(idx); +} + +static struct index_node_f *index_readroot(struct index_file *in) +{ + return index_read(in->file, in->root_offset); +} + +static struct index_node_f *index_readchild(const struct index_node_f *parent, + int ch) +{ + if (parent->first <= ch && ch <= parent->last) { + return index_read(parent->file, + parent->children[ch - parent->first]); + } + + return NULL; +} + +static void index_dump_node(struct index_node_f *node, struct strbuf *buf, + int fd) +{ + struct index_value *v; + int ch, pushed; + + pushed = strbuf_pushchars(buf, node->prefix); + + for (v = node->values; v != NULL; v = v->next) { + write_str_safe(fd, buf->bytes, buf->used); + write_str_safe(fd, " ", 1); + write_str_safe(fd, v->value, strlen(v->value)); + write_str_safe(fd, "\n", 1); + } + + for (ch = node->first; ch <= node->last; ch++) { + struct index_node_f *child = index_readchild(node, ch); + + if (!child) + continue; + + strbuf_pushchar(buf, ch); + index_dump_node(child, buf, fd); + strbuf_popchar(buf); + } + + strbuf_popchars(buf, pushed); + index_close(node); +} + +void index_dump(struct index_file *in, int fd, const char *prefix) +{ + struct index_node_f *root; + struct strbuf buf; + + root = index_readroot(in); + if (root == NULL) + return; + + strbuf_init(&buf); + strbuf_pushchars(&buf, prefix); + index_dump_node(root, &buf, fd); + strbuf_release(&buf); +} + +static char *index_search__node(struct index_node_f *node, const char *key, int i) +{ + char *value; + struct index_node_f *child; + int ch; + int j; + + while(node) { + for (j = 0; node->prefix[j]; j++) { + ch = node->prefix[j]; + + if (ch != key[i+j]) { + index_close(node); + return NULL; + } + } + + i += j; + + if (key[i] == '\0') { + value = node->values != NULL + ? strdup(node->values[0].value) + : NULL; + + index_close(node); + return value; + } + + child = index_readchild(node, key[i]); + index_close(node); + node = child; + i++; + } + + return NULL; +} + +/* + * Search the index for a key + * + * Returns the value of the first match + * + * The recursive functions free their node argument (using index_close). + */ +char *index_search(struct index_file *in, const char *key) +{ +// FIXME: return value by reference instead of strdup + struct index_node_f *root; + char *value; + + root = index_readroot(in); + value = index_search__node(root, key, 0); + + return value; +} + + + +/* Level 4: add all the values from a matching node */ +static void index_searchwild__allvalues(struct index_node_f *node, + struct index_value **out) +{ + struct index_value *v; + + for (v = node->values; v != NULL; v = v->next) + add_value(out, v->value, v->len, v->priority); + + index_close(node); +} + +/* + * Level 3: traverse a sub-keyspace which starts with a wildcard, + * looking for matches. + */ +static void index_searchwild__all(struct index_node_f *node, int j, + struct strbuf *buf, + const char *subkey, + struct index_value **out) +{ + int pushed = 0; + int ch; + + while (node->prefix[j]) { + ch = node->prefix[j]; + + strbuf_pushchar(buf, ch); + pushed++; + j++; + } + + for (ch = node->first; ch <= node->last; ch++) { + struct index_node_f *child = index_readchild(node, ch); + + if (!child) + continue; + + strbuf_pushchar(buf, ch); + index_searchwild__all(child, 0, buf, subkey, out); + strbuf_popchar(buf); + } + + if (node->values) { + if (fnmatch(strbuf_str(buf), subkey, 0) == 0) + index_searchwild__allvalues(node, out); + else + index_close(node); + } else { + index_close(node); + } + + strbuf_popchars(buf, pushed); +} + +/* Level 2: descend the tree (until we hit a wildcard) */ +static void index_searchwild__node(struct index_node_f *node, + struct strbuf *buf, + const char *key, int i, + struct index_value **out) +{ + struct index_node_f *child; + int j; + int ch; + + while(node) { + for (j = 0; node->prefix[j]; j++) { + ch = node->prefix[j]; + + if (ch == '*' || ch == '?' || ch == '[') { + index_searchwild__all(node, j, buf, + &key[i+j], out); + return; + } + + if (ch != key[i+j]) { + index_close(node); + return; + } + } + + i += j; + + child = index_readchild(node, '*'); + if (child) { + strbuf_pushchar(buf, '*'); + index_searchwild__all(child, 0, buf, &key[i], out); + strbuf_popchar(buf); + } + + child = index_readchild(node, '?'); + if (child) { + strbuf_pushchar(buf, '?'); + index_searchwild__all(child, 0, buf, &key[i], out); + strbuf_popchar(buf); + } + + child = index_readchild(node, '['); + if (child) { + strbuf_pushchar(buf, '['); + index_searchwild__all(child, 0, buf, &key[i], out); + strbuf_popchar(buf); + } + + if (key[i] == '\0') { + index_searchwild__allvalues(node, out); + + return; + } + + child = index_readchild(node, key[i]); + index_close(node); + node = child; + i++; + } +} + +/* + * Search the index for a key. The index may contain wildcards. + * + * Returns a list of all the values of matching keys. + */ +struct index_value *index_searchwild(struct index_file *in, const char *key) +{ + struct index_node_f *root = index_readroot(in); + struct strbuf buf; + struct index_value *out = NULL; + + strbuf_init(&buf); + index_searchwild__node(root, &buf, key, 0, &out); + strbuf_release(&buf); + return out; +} + +/**************************************************************************/ +/* + * Alternative implementation, using mmap to map all the file to memory when + * starting + */ +#include +#include +#include + +static const char _idx_empty_str[] = ""; + +struct index_mm { + const struct kmod_ctx *ctx; + void *mm; + uint32_t root_offset; + size_t size; +}; + +struct index_mm_value { + unsigned int priority; + unsigned int len; + const char *value; +}; + +struct index_mm_value_array { + struct index_mm_value *values; + unsigned int len; +}; + +struct index_mm_node { + struct index_mm *idx; + const char *prefix; /* mmape'd value */ + struct index_mm_value_array values; + unsigned char first; + unsigned char last; + uint32_t children[]; +}; + +static inline uint32_t read_long_mm(void **p) +{ + uint8_t *addr = *(uint8_t **)p; + uint32_t v; + + /* addr may be unalined to uint32_t */ + v = get_unaligned((uint32_t *) addr); + + *p = addr + sizeof(uint32_t); + return ntohl(v); +} + +static inline uint8_t read_char_mm(void **p) +{ + uint8_t *addr = *(uint8_t **)p; + uint8_t v = *addr; + *p = addr + sizeof(uint8_t); + return v; +} + +static inline char *read_chars_mm(void **p, unsigned *rlen) +{ + char *addr = *(char **)p; + size_t len = *rlen = strlen(addr); + *p = addr + len + 1; + return addr; +} + +static struct index_mm_node *index_mm_read_node(struct index_mm *idx, + uint32_t offset) { + void *p = idx->mm; + struct index_mm_node *node; + const char *prefix; + int i, child_count, value_count, children_padding; + uint32_t children[INDEX_CHILDMAX]; + char first, last; + + if ((offset & INDEX_NODE_MASK) == 0) + return NULL; + + p = (char *)p + (offset & INDEX_NODE_MASK); + + if (offset & INDEX_NODE_PREFIX) { + unsigned len; + prefix = read_chars_mm(&p, &len); + } else + prefix = _idx_empty_str; + + if (offset & INDEX_NODE_CHILDS) { + first = read_char_mm(&p); + last = read_char_mm(&p); + child_count = last - first + 1; + for (i = 0; i < child_count; i++) + children[i] = read_long_mm(&p); + } else { + first = INDEX_CHILDMAX; + last = 0; + child_count = 0; + } + + children_padding = (sizeof(struct index_mm_node) + + (sizeof(uint32_t) * child_count)) % sizeof(void *); + + if (offset & INDEX_NODE_VALUES) + value_count = read_long_mm(&p); + else + value_count = 0; + + node = malloc(sizeof(struct index_mm_node) + + sizeof(uint32_t) * child_count + children_padding + + sizeof(struct index_mm_value) * value_count); + if (node == NULL) + return NULL; + + node->idx = idx; + node->prefix = prefix; + if (value_count == 0) + node->values.values = NULL; + else { + node->values.values = (struct index_mm_value *) + ((char *)node + sizeof(struct index_mm_node) + + sizeof(uint32_t) * child_count + children_padding); + } + node->values.len = value_count; + node->first = first; + node->last = last; + memcpy(node->children, children, sizeof(uint32_t) * child_count); + + for (i = 0; i < value_count; i++) { + struct index_mm_value *v = node->values.values + i; + v->priority = read_long_mm(&p); + v->value = read_chars_mm(&p, &v->len); + } + + return node; +} + +static void index_mm_free_node(struct index_mm_node *node) +{ + free(node); +} + +int index_mm_open(const struct kmod_ctx *ctx, const char *filename, + unsigned long long *stamp, struct index_mm **pidx) +{ + int fd, err; + struct stat st; + struct index_mm *idx; + struct { + uint32_t magic; + uint32_t version; + uint32_t root_offset; + } hdr; + void *p; + + assert(pidx != NULL); + + DBG(ctx, "file=%s\n", filename); + + idx = malloc(sizeof(*idx)); + if (idx == NULL) { + ERR(ctx, "malloc: %m\n"); + return -ENOMEM; + } + + if ((fd = open(filename, O_RDONLY|O_CLOEXEC)) < 0) { + DBG(ctx, "open(%s, O_RDONLY|O_CLOEXEC): %m\n", filename); + err = -errno; + goto fail_open; + } + + if (fstat(fd, &st) < 0 || (size_t) st.st_size < sizeof(hdr)) { + err = -EINVAL; + goto fail_nommap; + } + + idx->mm = mmap(NULL, st.st_size, PROT_READ, MAP_PRIVATE, fd, 0); + if (idx->mm == MAP_FAILED) { + ERR(ctx, "mmap(NULL, %"PRIu64", PROT_READ, %d, MAP_PRIVATE, 0): %m\n", + st.st_size, fd); + err = -errno; + goto fail_nommap; + } + + p = idx->mm; + hdr.magic = read_long_mm(&p); + hdr.version = read_long_mm(&p); + hdr.root_offset = read_long_mm(&p); + + if (hdr.magic != INDEX_MAGIC) { + ERR(ctx, "magic check fail: %x instead of %x\n", hdr.magic, + INDEX_MAGIC); + err = -EINVAL; + goto fail; + } + + if (hdr.version >> 16 != INDEX_VERSION_MAJOR) { + ERR(ctx, "major version check fail: %u instead of %u\n", + hdr.version >> 16, INDEX_VERSION_MAJOR); + err = -EINVAL; + goto fail; + } + + idx->root_offset = hdr.root_offset; + idx->size = st.st_size; + idx->ctx = ctx; + close(fd); + + *stamp = stat_mstamp(&st); + *pidx = idx; + + return 0; + +fail: + munmap(idx->mm, st.st_size); +fail_nommap: + close(fd); +fail_open: + free(idx); + return err; +} + +void index_mm_close(struct index_mm *idx) +{ + munmap(idx->mm, idx->size); + free(idx); +} + +static struct index_mm_node *index_mm_readroot(struct index_mm *idx) +{ + return index_mm_read_node(idx, idx->root_offset); +} + +static struct index_mm_node *index_mm_readchild(const struct index_mm_node *parent, + int ch) +{ + if (parent->first <= ch && ch <= parent->last) { + return index_mm_read_node(parent->idx, + parent->children[ch - parent->first]); + } + + return NULL; +} + +static void index_mm_dump_node(struct index_mm_node *node, struct strbuf *buf, + int fd) +{ + struct index_mm_value *itr, *itr_end; + int ch, pushed; + + pushed = strbuf_pushchars(buf, node->prefix); + + itr = node->values.values; + itr_end = itr + node->values.len; + for (; itr < itr_end; itr++) { + write_str_safe(fd, buf->bytes, buf->used); + write_str_safe(fd, " ", 1); + write_str_safe(fd, itr->value, itr->len); + write_str_safe(fd, "\n", 1); + } + + for (ch = node->first; ch <= node->last; ch++) { + struct index_mm_node *child = index_mm_readchild(node, ch); + + if (child == NULL) + continue; + + strbuf_pushchar(buf, ch); + index_mm_dump_node(child, buf, fd); + strbuf_popchar(buf); + } + + strbuf_popchars(buf, pushed); + index_mm_free_node(node); +} + +void index_mm_dump(struct index_mm *idx, int fd, const char *prefix) +{ + struct index_mm_node *root; + struct strbuf buf; + + root = index_mm_readroot(idx); + if (root == NULL) + return; + + strbuf_init(&buf); + strbuf_pushchars(&buf, prefix); + index_mm_dump_node(root, &buf, fd); + strbuf_release(&buf); +} + +static char *index_mm_search_node(struct index_mm_node *node, const char *key, + int i) +{ + char *value; + struct index_mm_node *child; + int ch; + int j; + + while(node) { + for (j = 0; node->prefix[j]; j++) { + ch = node->prefix[j]; + + if (ch != key[i+j]) { + index_mm_free_node(node); + return NULL; + } + } + + i += j; + + if (key[i] == '\0') { + value = node->values.len > 0 + ? strdup(node->values.values[0].value) + : NULL; + + index_mm_free_node(node); + return value; + } + + child = index_mm_readchild(node, key[i]); + index_mm_free_node(node); + node = child; + i++; + } + + return NULL; +} + +/* + * Search the index for a key + * + * Returns the value of the first match + * + * The recursive functions free their node argument (using index_close). + */ +char *index_mm_search(struct index_mm *idx, const char *key) +{ +// FIXME: return value by reference instead of strdup + struct index_mm_node *root; + char *value; + + root = index_mm_readroot(idx); + value = index_mm_search_node(root, key, 0); + + return value; +} + +/* Level 4: add all the values from a matching node */ +static void index_mm_searchwild_allvalues(struct index_mm_node *node, + struct index_value **out) +{ + struct index_mm_value *itr, *itr_end; + + itr = node->values.values; + itr_end = itr + node->values.len; + for (; itr < itr_end; itr++) + add_value(out, itr->value, itr->len, itr->priority); + + index_mm_free_node(node); +} + +/* + * Level 3: traverse a sub-keyspace which starts with a wildcard, + * looking for matches. + */ +static void index_mm_searchwild_all(struct index_mm_node *node, int j, + struct strbuf *buf, + const char *subkey, + struct index_value **out) +{ + int pushed = 0; + int ch; + + while (node->prefix[j]) { + ch = node->prefix[j]; + + strbuf_pushchar(buf, ch); + pushed++; + j++; + } + + for (ch = node->first; ch <= node->last; ch++) { + struct index_mm_node *child = index_mm_readchild(node, ch); + + if (!child) + continue; + + strbuf_pushchar(buf, ch); + index_mm_searchwild_all(child, 0, buf, subkey, out); + strbuf_popchar(buf); + } + + if (node->values.len > 0) { + if (fnmatch(strbuf_str(buf), subkey, 0) == 0) + index_mm_searchwild_allvalues(node, out); + else + index_mm_free_node(node); + } else { + index_mm_free_node(node); + } + + strbuf_popchars(buf, pushed); +} + +/* Level 2: descend the tree (until we hit a wildcard) */ +static void index_mm_searchwild_node(struct index_mm_node *node, + struct strbuf *buf, + const char *key, int i, + struct index_value **out) +{ + struct index_mm_node *child; + int j; + int ch; + + while(node) { + for (j = 0; node->prefix[j]; j++) { + ch = node->prefix[j]; + + if (ch == '*' || ch == '?' || ch == '[') { + index_mm_searchwild_all(node, j, buf, + &key[i+j], out); + return; + } + + if (ch != key[i+j]) { + index_mm_free_node(node); + return; + } + } + + i += j; + + child = index_mm_readchild(node, '*'); + if (child) { + strbuf_pushchar(buf, '*'); + index_mm_searchwild_all(child, 0, buf, &key[i], out); + strbuf_popchar(buf); + } + + child = index_mm_readchild(node, '?'); + if (child) { + strbuf_pushchar(buf, '?'); + index_mm_searchwild_all(child, 0, buf, &key[i], out); + strbuf_popchar(buf); + } + + child = index_mm_readchild(node, '['); + if (child) { + strbuf_pushchar(buf, '['); + index_mm_searchwild_all(child, 0, buf, &key[i], out); + strbuf_popchar(buf); + } + + if (key[i] == '\0') { + index_mm_searchwild_allvalues(node, out); + + return; + } + + child = index_mm_readchild(node, key[i]); + index_mm_free_node(node); + node = child; + i++; + } +} + +/* + * Search the index for a key. The index may contain wildcards. + * + * Returns a list of all the values of matching keys. + */ +struct index_value *index_mm_searchwild(struct index_mm *idx, const char *key) +{ + struct index_mm_node *root = index_mm_readroot(idx); + struct strbuf buf; + struct index_value *out = NULL; + + strbuf_init(&buf); + index_mm_searchwild_node(root, &buf, key, 0, &out); + strbuf_release(&buf); + return out; +} diff --git a/libkmod/libkmod-index.h b/libkmod/libkmod-index.h new file mode 100644 index 0000000..db671b0 --- /dev/null +++ b/libkmod/libkmod-index.h @@ -0,0 +1,48 @@ +/* + * libkmod - interface to kernel module operations + * + * Copyright (C) 2011-2013 ProFUSION embedded systems + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, see . + */ + +#pragma once + +#include + +struct index_value { + struct index_value *next; + unsigned int priority; + unsigned int len; + char value[0]; +}; + +/* In-memory index (depmod only) */ +struct index_file; +struct index_file *index_file_open(const char *filename); +void index_file_close(struct index_file *idx); +char *index_search(struct index_file *idx, const char *key); +void index_dump(struct index_file *in, int fd, const char *prefix); +struct index_value *index_searchwild(struct index_file *idx, const char *key); + +void index_values_free(struct index_value *values); + +/* Implementation using mmap */ +struct index_mm; +int index_mm_open(const struct kmod_ctx *ctx, const char *filename, + unsigned long long *stamp, struct index_mm **pidx); +void index_mm_close(struct index_mm *index); +char *index_mm_search(struct index_mm *idx, const char *key); +struct index_value *index_mm_searchwild(struct index_mm *idx, const char *key); +void index_mm_dump(struct index_mm *idx, int fd, const char *prefix); diff --git a/libkmod/libkmod-internal.h b/libkmod/libkmod-internal.h new file mode 100644 index 0000000..26a7e28 --- /dev/null +++ b/libkmod/libkmod-internal.h @@ -0,0 +1,210 @@ +#pragma once + +#include +#include +#include +#include + +#include +#include + +#include "libkmod.h" + +static _always_inline_ _printf_format_(2, 3) void + kmod_log_null(const struct kmod_ctx *ctx, const char *format, ...) {} + +#define kmod_log_cond(ctx, prio, arg...) \ + do { \ + if (kmod_get_log_priority(ctx) >= prio) \ + kmod_log(ctx, prio, __FILE__, __LINE__, __func__, ## arg);\ + } while (0) + +#ifdef ENABLE_LOGGING +# ifdef ENABLE_DEBUG +# define DBG(ctx, arg...) kmod_log_cond(ctx, LOG_DEBUG, ## arg) +# else +# define DBG(ctx, arg...) kmod_log_null(ctx, ## arg) +# endif +# define NOTICE(ctx, arg...) kmod_log_cond(ctx, LOG_NOTICE, ## arg) +# define INFO(ctx, arg...) kmod_log_cond(ctx, LOG_INFO, ## arg) +# define ERR(ctx, arg...) kmod_log_cond(ctx, LOG_ERR, ## arg) +#else +# define DBG(ctx, arg...) kmod_log_null(ctx, ## arg) +# define NOTICE(ctx, arg...) kmod_log_null(ctx, ## arg) +# define INFO(ctx, arg...) kmod_log_null(ctx, ## arg) +# define ERR(ctx, arg...) kmod_log_null(ctx, ## arg) +#endif + +#define KMOD_EXPORT __attribute__ ((visibility("default"))) + +#define KCMD_LINE_SIZE 4096 + +#ifndef HAVE_SECURE_GETENV +# ifdef HAVE___SECURE_GETENV +# define secure_getenv __secure_getenv +# else +# warning neither secure_getenv nor __secure_getenv is available +# define secure_getenv getenv +# endif +#endif + +void kmod_log(const struct kmod_ctx *ctx, + int priority, const char *file, int line, const char *fn, + const char *format, ...) __attribute__((format(printf, 6, 7))) __attribute__((nonnull(1, 3, 5))); + +struct list_node { + struct list_node *next, *prev; +}; + +struct kmod_list { + struct list_node node; + void *data; +}; + +enum kmod_file_compression_type { + KMOD_FILE_COMPRESSION_NONE = 0, + KMOD_FILE_COMPRESSION_ZSTD, + KMOD_FILE_COMPRESSION_XZ, + KMOD_FILE_COMPRESSION_ZLIB, +}; + +struct kmod_list *kmod_list_append(struct kmod_list *list, const void *data) _must_check_ __attribute__((nonnull(2))); +struct kmod_list *kmod_list_prepend(struct kmod_list *list, const void *data) _must_check_ __attribute__((nonnull(2))); +struct kmod_list *kmod_list_remove(struct kmod_list *list) _must_check_; +struct kmod_list *kmod_list_remove_data(struct kmod_list *list, + const void *data) _must_check_ __attribute__((nonnull(2))); +struct kmod_list *kmod_list_remove_n_latest(struct kmod_list *list, + unsigned int n) _must_check_; +struct kmod_list *kmod_list_insert_after(struct kmod_list *list, const void *data) __attribute__((nonnull(2))); +struct kmod_list *kmod_list_insert_before(struct kmod_list *list, const void *data) __attribute__((nonnull(2))); +struct kmod_list *kmod_list_append_list(struct kmod_list *list1, struct kmod_list *list2) _must_check_; + +#undef kmod_list_foreach +#define kmod_list_foreach(list_entry, first_entry) \ + for (list_entry = ((first_entry) == NULL) ? NULL : (first_entry); \ + list_entry != NULL; \ + list_entry = (list_entry->node.next == &((first_entry)->node)) ? NULL : \ + container_of(list_entry->node.next, struct kmod_list, node)) + +#undef kmod_list_foreach_reverse +#define kmod_list_foreach_reverse(list_entry, first_entry) \ + for (list_entry = (((first_entry) == NULL) ? NULL : container_of(first_entry->node.prev, struct kmod_list, node)); \ + list_entry != NULL; \ + list_entry = ((list_entry == first_entry) ? NULL : \ + container_of(list_entry->node.prev, struct kmod_list, node))) + +/* libkmod.c */ +int kmod_lookup_alias_from_config(struct kmod_ctx *ctx, const char *name, struct kmod_list **list) __attribute__((nonnull(1, 2, 3))); +int kmod_lookup_alias_from_symbols_file(struct kmod_ctx *ctx, const char *name, struct kmod_list **list) __attribute__((nonnull(1, 2, 3))); +int kmod_lookup_alias_from_aliases_file(struct kmod_ctx *ctx, const char *name, struct kmod_list **list) __attribute__((nonnull(1, 2, 3))); +int kmod_lookup_alias_from_moddep_file(struct kmod_ctx *ctx, const char *name, struct kmod_list **list) __attribute__((nonnull(1, 2, 3))); +int kmod_lookup_alias_from_kernel_builtin_file(struct kmod_ctx *ctx, const char *name, struct kmod_list **list) __attribute__((nonnull(1, 2, 3))); +int kmod_lookup_alias_from_builtin_file(struct kmod_ctx *ctx, const char *name, struct kmod_list **list) __attribute__((nonnull(1, 2, 3))); +bool kmod_lookup_alias_is_builtin(struct kmod_ctx *ctx, const char *name) __attribute__((nonnull(1, 2))); +int kmod_lookup_alias_from_commands(struct kmod_ctx *ctx, const char *name, struct kmod_list **list) __attribute__((nonnull(1, 2, 3))); +void kmod_set_modules_visited(struct kmod_ctx *ctx, bool visited) __attribute__((nonnull((1)))); +void kmod_set_modules_required(struct kmod_ctx *ctx, bool required) __attribute__((nonnull((1)))); + +char *kmod_search_moddep(struct kmod_ctx *ctx, const char *name) __attribute__((nonnull(1,2))); + +struct kmod_module *kmod_pool_get_module(struct kmod_ctx *ctx, const char *key) __attribute__((nonnull(1,2))); +void kmod_pool_add_module(struct kmod_ctx *ctx, struct kmod_module *mod, const char *key) __attribute__((nonnull(1, 2, 3))); +void kmod_pool_del_module(struct kmod_ctx *ctx, struct kmod_module *mod, const char *key) __attribute__((nonnull(1, 2, 3))); + +const struct kmod_config *kmod_get_config(const struct kmod_ctx *ctx) __attribute__((nonnull(1))); +enum kmod_file_compression_type kmod_get_kernel_compression(const struct kmod_ctx *ctx) __attribute__((nonnull(1))); + +/* libkmod-config.c */ +struct kmod_config_path { + unsigned long long stamp; + char path[]; +}; + +struct kmod_config { + struct kmod_ctx *ctx; + struct kmod_list *aliases; + struct kmod_list *blacklists; + struct kmod_list *options; + struct kmod_list *remove_commands; + struct kmod_list *install_commands; + struct kmod_list *softdeps; + + struct kmod_list *paths; +}; + +int kmod_config_new(struct kmod_ctx *ctx, struct kmod_config **config, const char * const *config_paths) __attribute__((nonnull(1, 2,3))); +void kmod_config_free(struct kmod_config *config) __attribute__((nonnull(1))); +const char *kmod_blacklist_get_modname(const struct kmod_list *l) __attribute__((nonnull(1))); +const char *kmod_alias_get_name(const struct kmod_list *l) __attribute__((nonnull(1))); +const char *kmod_alias_get_modname(const struct kmod_list *l) __attribute__((nonnull(1))); +const char *kmod_option_get_options(const struct kmod_list *l) __attribute__((nonnull(1))); +const char *kmod_option_get_modname(const struct kmod_list *l) __attribute__((nonnull(1))); +const char *kmod_command_get_command(const struct kmod_list *l) __attribute__((nonnull(1))); +const char *kmod_command_get_modname(const struct kmod_list *l) __attribute__((nonnull(1))); + +const char *kmod_softdep_get_name(const struct kmod_list *l) __attribute__((nonnull(1))); +const char * const *kmod_softdep_get_pre(const struct kmod_list *l, unsigned int *count) __attribute__((nonnull(1, 2))); +const char * const *kmod_softdep_get_post(const struct kmod_list *l, unsigned int *count); + + +/* libkmod-module.c */ +int kmod_module_new_from_alias(struct kmod_ctx *ctx, const char *alias, const char *name, struct kmod_module **mod); +int kmod_module_parse_depline(struct kmod_module *mod, char *line) __attribute__((nonnull(1, 2))); +void kmod_module_set_install_commands(struct kmod_module *mod, const char *cmd) __attribute__((nonnull(1))); +void kmod_module_set_remove_commands(struct kmod_module *mod, const char *cmd) __attribute__((nonnull(1))); +void kmod_module_set_visited(struct kmod_module *mod, bool visited) __attribute__((nonnull(1))); +void kmod_module_set_builtin(struct kmod_module *mod, bool builtin) __attribute__((nonnull((1)))); +void kmod_module_set_required(struct kmod_module *mod, bool required) __attribute__((nonnull(1))); +bool kmod_module_is_builtin(struct kmod_module *mod) __attribute__((nonnull(1))); + +/* libkmod-file.c */ +struct kmod_file *kmod_file_open(const struct kmod_ctx *ctx, const char *filename) _must_check_ __attribute__((nonnull(1,2))); +struct kmod_elf *kmod_file_get_elf(struct kmod_file *file) __attribute__((nonnull(1))); +void kmod_file_load_contents(struct kmod_file *file) __attribute__((nonnull(1))); +void *kmod_file_get_contents(const struct kmod_file *file) _must_check_ __attribute__((nonnull(1))); +off_t kmod_file_get_size(const struct kmod_file *file) _must_check_ __attribute__((nonnull(1))); +enum kmod_file_compression_type kmod_file_get_compression(const struct kmod_file *file) _must_check_ __attribute__((nonnull(1))); +int kmod_file_get_fd(const struct kmod_file *file) _must_check_ __attribute__((nonnull(1))); +void kmod_file_unref(struct kmod_file *file) __attribute__((nonnull(1))); + +/* libkmod-elf.c */ +struct kmod_elf; +struct kmod_modversion { + uint64_t crc; + enum kmod_symbol_bind bind; + char *symbol; +}; + +struct kmod_elf *kmod_elf_new(const void *memory, off_t size) _must_check_; +void kmod_elf_unref(struct kmod_elf *elf) __attribute__((nonnull(1))); +const void *kmod_elf_get_memory(const struct kmod_elf *elf) _must_check_ __attribute__((nonnull(1))); +int kmod_elf_get_strings(const struct kmod_elf *elf, const char *section, char ***array) _must_check_ __attribute__((nonnull(1,2,3))); +int kmod_elf_get_modversions(const struct kmod_elf *elf, struct kmod_modversion **array) _must_check_ __attribute__((nonnull(1,2))); +int kmod_elf_get_symbols(const struct kmod_elf *elf, struct kmod_modversion **array) _must_check_ __attribute__((nonnull(1,2))); +int kmod_elf_get_dependency_symbols(const struct kmod_elf *elf, struct kmod_modversion **array) _must_check_ __attribute__((nonnull(1,2))); +int kmod_elf_strip_section(struct kmod_elf *elf, const char *section) _must_check_ __attribute__((nonnull(1,2))); +int kmod_elf_strip_vermagic(struct kmod_elf *elf) _must_check_ __attribute__((nonnull(1))); + +/* + * Debug mock lib need to find section ".gnu.linkonce.this_module" in order to + * get modname + */ +int kmod_elf_get_section(const struct kmod_elf *elf, const char *section, const void **buf, uint64_t *buf_size) _must_check_ __attribute__((nonnull(1,2,3,4))); + +/* libkmod-signature.c */ +struct kmod_signature_info { + const char *signer; + size_t signer_len; + const char *key_id; + size_t key_id_len; + const char *algo, *hash_algo, *id_type; + const char *sig; + size_t sig_len; + void (*free)(void *); + void *private; +}; +bool kmod_module_signature_info(const struct kmod_file *file, struct kmod_signature_info *sig_info) _must_check_ __attribute__((nonnull(1, 2))); +void kmod_module_signature_info_free(struct kmod_signature_info *sig_info) __attribute__((nonnull)); + +/* libkmod-builtin.c */ +ssize_t kmod_builtin_get_modinfo(struct kmod_ctx *ctx, const char *modname, char ***modinfo) __attribute__((nonnull(1, 2, 3))); diff --git a/libkmod/libkmod-list.c b/libkmod/libkmod-list.c new file mode 100644 index 0000000..7623693 --- /dev/null +++ b/libkmod/libkmod-list.c @@ -0,0 +1,314 @@ +/* + * libkmod - interface to kernel module operations + * + * Copyright (C) 2011-2013 ProFUSION embedded systems + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, see . + */ + +#include + +#include "libkmod.h" +#include "libkmod-internal.h" + +/** + * SECTION:libkmod-list + * @short_description: general purpose list + */ + +static inline struct list_node *list_node_init(struct list_node *node) +{ + node->next = node; + node->prev = node; + + return node; +} + +static inline void list_node_append(struct list_node *list, + struct list_node *node) +{ + if (list == NULL) { + list_node_init(node); + return; + } + + node->prev = list->prev; + list->prev->next = node; + list->prev = node; + node->next = list; +} + +static inline struct list_node *list_node_remove(struct list_node *node) +{ + if (node->prev == node || node->next == node) + return NULL; + + node->prev->next = node->next; + node->next->prev = node->prev; + + return node->next; +} + +static inline void list_node_insert_after(struct list_node *list, + struct list_node *node) +{ + if (list == NULL) { + list_node_init(node); + return; + } + + node->prev = list; + node->next = list->next; + list->next->prev = node; + list->next = node; +} + +static inline void list_node_insert_before(struct list_node *list, + struct list_node *node) +{ + if (list == NULL) { + list_node_init(node); + return; + } + + node->next = list; + node->prev = list->prev; + list->prev->next = node; + list->prev = node; +} + +static inline void list_node_append_list(struct list_node *list1, + struct list_node *list2) +{ + struct list_node *list1_last; + + if (list1 == NULL) { + list_node_init(list2); + return; + } + + list1->prev->next = list2; + list2->prev->next = list1; + + /* cache the last, because we will lose the pointer */ + list1_last = list1->prev; + + list1->prev = list2->prev; + list2->prev = list1_last; +} + +struct kmod_list *kmod_list_append(struct kmod_list *list, const void *data) +{ + struct kmod_list *new; + + new = malloc(sizeof(*new)); + if (new == NULL) + return NULL; + + new->data = (void *)data; + list_node_append(list ? &list->node : NULL, &new->node); + + return list ? list : new; +} + +struct kmod_list *kmod_list_insert_after(struct kmod_list *list, + const void *data) +{ + struct kmod_list *new; + + if (list == NULL) + return kmod_list_append(list, data); + + new = malloc(sizeof(*new)); + if (new == NULL) + return NULL; + + new->data = (void *)data; + list_node_insert_after(&list->node, &new->node); + + return list; +} + +struct kmod_list *kmod_list_insert_before(struct kmod_list *list, + const void *data) +{ + struct kmod_list *new; + + if (list == NULL) + return kmod_list_append(list, data); + + new = malloc(sizeof(*new)); + if (new == NULL) + return NULL; + + new->data = (void *)data; + list_node_insert_before(&list->node, &new->node); + + return new; +} + +struct kmod_list *kmod_list_append_list(struct kmod_list *list1, + struct kmod_list *list2) +{ + if (list1 == NULL) + return list2; + + if (list2 == NULL) + return list1; + + list_node_append_list(&list1->node, &list2->node); + + return list1; +} + +struct kmod_list *kmod_list_prepend(struct kmod_list *list, const void *data) +{ + struct kmod_list *new; + + new = malloc(sizeof(*new)); + if (new == NULL) + return NULL; + + new->data = (void *)data; + list_node_append(list ? &list->node : NULL, &new->node); + + return new; +} + +struct kmod_list *kmod_list_remove(struct kmod_list *list) +{ + struct list_node *node; + + if (list == NULL) + return NULL; + + node = list_node_remove(&list->node); + free(list); + + if (node == NULL) + return NULL; + + return container_of(node, struct kmod_list, node); +} + +struct kmod_list *kmod_list_remove_data(struct kmod_list *list, + const void *data) +{ + struct kmod_list *itr; + struct list_node *node; + + for (itr = list; itr != NULL; itr = kmod_list_next(list, itr)) { + if (itr->data == data) + break; + } + + if (itr == NULL) + return list; + + node = list_node_remove(&itr->node); + free(itr); + + if (node == NULL) + return NULL; + + return container_of(node, struct kmod_list, node); +} + +/* + * n must be greater to or equal the number of elements (we don't check the + * condition) + */ +struct kmod_list *kmod_list_remove_n_latest(struct kmod_list *list, + unsigned int n) +{ + struct kmod_list *l = list; + unsigned int i; + + for (i = 0; i < n; i++) { + l = kmod_list_last(l); + l = kmod_list_remove(l); + } + + return l; +} + +/** + * kmod_list_prev: + * @list: the head of the list + * @curr: the current node in the list + * + * Get the previous node in @list relative to @curr as if @list was not a + * circular list. I.e.: the previous of the head is NULL. It can be used to + * iterate a list by checking for NULL return to know when all elements were + * iterated. + * + * Returns: node previous to @curr or NULL if either this node is the head of + * the list or the list is empty. + */ +KMOD_EXPORT struct kmod_list *kmod_list_prev(const struct kmod_list *list, + const struct kmod_list *curr) +{ + if (list == NULL || curr == NULL) + return NULL; + + if (list == curr) + return NULL; + + return container_of(curr->node.prev, struct kmod_list, node); +} + +/** + * kmod_list_next: + * @list: the head of the list + * @curr: the current node in the list + * + * Get the next node in @list relative to @curr as if @list was not a circular + * list. I.e. calling this function in the last node of the list returns + * NULL.. It can be used to iterate a list by checking for NULL return to know + * when all elements were iterated. + * + * Returns: node next to @curr or NULL if either this node is the last of or + * list is empty. + */ +KMOD_EXPORT struct kmod_list *kmod_list_next(const struct kmod_list *list, + const struct kmod_list *curr) +{ + if (list == NULL || curr == NULL) + return NULL; + + if (curr->node.next == &list->node) + return NULL; + + return container_of(curr->node.next, struct kmod_list, node); +} + +/** + * kmod_list_last: + * @list: the head of the list + * + * Get the last element of the @list. As @list is a circular list, + * this is a cheap operation O(1) with the last element being the + * previous element. + * + * If the list has a single element it will return the list itself (as + * expected, and this is what differentiates from kmod_list_prev()). + * + * Returns: last node at @list or NULL if the list is empty. + */ +KMOD_EXPORT struct kmod_list *kmod_list_last(const struct kmod_list *list) +{ + if (list == NULL) + return NULL; + return container_of(list->node.prev, struct kmod_list, node); +} diff --git a/libkmod/libkmod-module.c b/libkmod/libkmod-module.c new file mode 100644 index 0000000..585da41 --- /dev/null +++ b/libkmod/libkmod-module.c @@ -0,0 +1,2986 @@ +/* + * libkmod - interface to kernel module operations + * + * Copyright (C) 2011-2013 ProFUSION embedded systems + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, see . + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#ifdef HAVE_LINUX_MODULE_H +#include +#endif + +#include + +#include "libkmod.h" +#include "libkmod-internal.h" + +/** + * SECTION:libkmod-module + * @short_description: operate on kernel modules + */ + +enum kmod_module_builtin { + KMOD_MODULE_BUILTIN_UNKNOWN, + KMOD_MODULE_BUILTIN_NO, + KMOD_MODULE_BUILTIN_YES, +}; + +/** + * kmod_module: + * + * Opaque object representing a module. + */ +struct kmod_module { + struct kmod_ctx *ctx; + char *hashkey; + char *name; + char *path; + struct kmod_list *dep; + char *options; + const char *install_commands; /* owned by kmod_config */ + const char *remove_commands; /* owned by kmod_config */ + char *alias; /* only set if this module was created from an alias */ + struct kmod_file *file; + int n_dep; + int refcount; + struct { + bool dep : 1; + bool options : 1; + bool install_commands : 1; + bool remove_commands : 1; + } init; + + /* + * mark if module is builtin, i.e. it's present on modules.builtin + * file. This is set as soon as it is needed or as soon as we know + * about it, i.e. the module was created from builtin lookup. + */ + enum kmod_module_builtin builtin; + + /* + * private field used by kmod_module_get_probe_list() to detect + * dependency loops + */ + bool visited : 1; + + /* + * set by kmod_module_get_probe_list: indicates for probe_insert() + * whether the module's command and softdep should be ignored + */ + bool ignorecmd : 1; + + /* + * set by kmod_module_get_probe_list: indicates whether this is the + * module the user asked for or its dependency, or whether this + * is a softdep only + */ + bool required : 1; +}; + +static inline const char *path_join(const char *path, size_t prefixlen, + char buf[PATH_MAX]) +{ + size_t pathlen; + + if (path[0] == '/') + return path; + + pathlen = strlen(path); + if (prefixlen + pathlen + 1 >= PATH_MAX) + return NULL; + + memcpy(buf + prefixlen, path, pathlen + 1); + return buf; +} + +static inline bool module_is_inkernel(struct kmod_module *mod) +{ + int state = kmod_module_get_initstate(mod); + + if (state == KMOD_MODULE_LIVE || + state == KMOD_MODULE_BUILTIN) + return true; + + return false; +} + +int kmod_module_parse_depline(struct kmod_module *mod, char *line) +{ + struct kmod_ctx *ctx = mod->ctx; + struct kmod_list *list = NULL; + const char *dirname; + char buf[PATH_MAX]; + char *p, *saveptr; + int err = 0, n = 0; + size_t dirnamelen; + + if (mod->init.dep) + return mod->n_dep; + assert(mod->dep == NULL); + mod->init.dep = true; + + p = strchr(line, ':'); + if (p == NULL) + return 0; + + *p = '\0'; + dirname = kmod_get_dirname(mod->ctx); + dirnamelen = strlen(dirname); + if (dirnamelen + 2 >= PATH_MAX) + return 0; + + memcpy(buf, dirname, dirnamelen); + buf[dirnamelen] = '/'; + dirnamelen++; + buf[dirnamelen] = '\0'; + + if (mod->path == NULL) { + const char *str = path_join(line, dirnamelen, buf); + if (str == NULL) + return 0; + mod->path = strdup(str); + if (mod->path == NULL) + return 0; + } + + p++; + for (p = strtok_r(p, " \t", &saveptr); p != NULL; + p = strtok_r(NULL, " \t", &saveptr)) { + struct kmod_module *depmod = NULL; + const char *path; + + path = path_join(p, dirnamelen, buf); + if (path == NULL) { + ERR(ctx, "could not join path '%s' and '%s'.\n", + dirname, p); + goto fail; + } + + err = kmod_module_new_from_path(ctx, path, &depmod); + if (err < 0) { + ERR(ctx, "ctx=%p path=%s error=%s\n", + ctx, path, strerror(-err)); + goto fail; + } + + DBG(ctx, "add dep: %s\n", path); + + list = kmod_list_prepend(list, depmod); + n++; + } + + DBG(ctx, "%d dependencies for %s\n", n, mod->name); + + mod->dep = list; + mod->n_dep = n; + return n; + +fail: + kmod_module_unref_list(list); + mod->init.dep = false; + return err; +} + +void kmod_module_set_visited(struct kmod_module *mod, bool visited) +{ + mod->visited = visited; +} + +void kmod_module_set_builtin(struct kmod_module *mod, bool builtin) +{ + mod->builtin = + builtin ? KMOD_MODULE_BUILTIN_YES : KMOD_MODULE_BUILTIN_NO; +} + +void kmod_module_set_required(struct kmod_module *mod, bool required) +{ + mod->required = required; +} + +bool kmod_module_is_builtin(struct kmod_module *mod) +{ + if (mod->builtin == KMOD_MODULE_BUILTIN_UNKNOWN) { + kmod_module_set_builtin(mod, + kmod_lookup_alias_is_builtin(mod->ctx, mod->name)); + } + + return mod->builtin == KMOD_MODULE_BUILTIN_YES; +} +/* + * Memory layout with alias: + * + * struct kmod_module { + * hashkey -----. + * alias -----. | + * name ----. | | + * } | | | + * name <----------' | | + * alias <-----------' | + * name\alias <--------' + * + * Memory layout without alias: + * + * struct kmod_module { + * hashkey ---. + * alias -----|----> NULL + * name ----. | + * } | | + * name <----------'-' + * + * @key is "name\alias" or "name" (in which case alias == NULL) + */ +static int kmod_module_new(struct kmod_ctx *ctx, const char *key, + const char *name, size_t namelen, + const char *alias, size_t aliaslen, + struct kmod_module **mod) +{ + struct kmod_module *m; + size_t keylen; + + m = kmod_pool_get_module(ctx, key); + if (m != NULL) { + *mod = kmod_module_ref(m); + return 0; + } + + if (alias == NULL) + keylen = namelen; + else + keylen = namelen + aliaslen + 1; + + m = malloc(sizeof(*m) + (alias == NULL ? 1 : 2) * (keylen + 1)); + if (m == NULL) + return -ENOMEM; + + memset(m, 0, sizeof(*m)); + + m->ctx = kmod_ref(ctx); + m->name = (char *)m + sizeof(*m); + memcpy(m->name, key, keylen + 1); + if (alias == NULL) { + m->hashkey = m->name; + m->alias = NULL; + } else { + m->name[namelen] = '\0'; + m->alias = m->name + namelen + 1; + m->hashkey = m->name + keylen + 1; + memcpy(m->hashkey, key, keylen + 1); + } + + m->refcount = 1; + kmod_pool_add_module(ctx, m, m->hashkey); + *mod = m; + + return 0; +} + +/** + * kmod_module_new_from_name: + * @ctx: kmod library context + * @name: name of the module + * @mod: where to save the created struct kmod_module + * + * Create a new struct kmod_module using the module name. @name can not be an + * alias, file name or anything else; it must be a module name. There's no + * check if the module exists in the system. + * + * This function is also used internally by many others that return a new + * struct kmod_module or a new list of modules. + * + * The initial refcount is 1, and needs to be decremented to release the + * resources of the kmod_module. Since libkmod keeps track of all + * kmod_modules created, they are all released upon @ctx destruction too. Do + * not unref @ctx before all the desired operations with the returned + * kmod_module are done. + * + * Returns: 0 on success or < 0 otherwise. It fails if name is not a valid + * module name or if memory allocation failed. + */ +KMOD_EXPORT int kmod_module_new_from_name(struct kmod_ctx *ctx, + const char *name, + struct kmod_module **mod) +{ + size_t namelen; + char name_norm[PATH_MAX]; + + if (ctx == NULL || name == NULL || mod == NULL) + return -ENOENT; + + modname_normalize(name, name_norm, &namelen); + + return kmod_module_new(ctx, name_norm, name_norm, namelen, NULL, 0, mod); +} + +int kmod_module_new_from_alias(struct kmod_ctx *ctx, const char *alias, + const char *name, struct kmod_module **mod) +{ + int err; + char key[PATH_MAX]; + size_t namelen = strlen(name); + size_t aliaslen = strlen(alias); + + if (namelen + aliaslen + 2 > PATH_MAX) + return -ENAMETOOLONG; + + memcpy(key, name, namelen); + memcpy(key + namelen + 1, alias, aliaslen + 1); + key[namelen] = '\\'; + + err = kmod_module_new(ctx, key, name, namelen, alias, aliaslen, mod); + if (err < 0) + return err; + + return 0; +} + +/** + * kmod_module_new_from_path: + * @ctx: kmod library context + * @path: path where to find the given module + * @mod: where to save the created struct kmod_module + * + * Create a new struct kmod_module using the module path. @path must be an + * existent file with in the filesystem and must be accessible to libkmod. + * + * The initial refcount is 1, and needs to be decremented to release the + * resources of the kmod_module. Since libkmod keeps track of all + * kmod_modules created, they are all released upon @ctx destruction too. Do + * not unref @ctx before all the desired operations with the returned + * kmod_module are done. + * + * If @path is relative, it's treated as relative to the current working + * directory. Otherwise, give an absolute path. + * + * Returns: 0 on success or < 0 otherwise. It fails if file does not exist, if + * it's not a valid file for a kmod_module or if memory allocation failed. + */ +KMOD_EXPORT int kmod_module_new_from_path(struct kmod_ctx *ctx, + const char *path, + struct kmod_module **mod) +{ + struct kmod_module *m; + int err; + struct stat st; + char name[PATH_MAX]; + char *abspath; + size_t namelen; + + if (ctx == NULL || path == NULL || mod == NULL) + return -ENOENT; + + abspath = path_make_absolute_cwd(path); + if (abspath == NULL) { + DBG(ctx, "no absolute path for %s\n", path); + return -ENOMEM; + } + + err = stat(abspath, &st); + if (err < 0) { + err = -errno; + DBG(ctx, "stat %s: %s\n", path, strerror(errno)); + free(abspath); + return err; + } + + if (path_to_modname(path, name, &namelen) == NULL) { + DBG(ctx, "could not get modname from path %s\n", path); + free(abspath); + return -ENOENT; + } + + m = kmod_pool_get_module(ctx, name); + if (m != NULL) { + if (m->path == NULL) + m->path = abspath; + else if (streq(m->path, abspath)) + free(abspath); + else { + ERR(ctx, "kmod_module '%s' already exists with different path: new-path='%s' old-path='%s'\n", + name, abspath, m->path); + free(abspath); + return -EEXIST; + } + + kmod_module_ref(m); + } else { + err = kmod_module_new(ctx, name, name, namelen, NULL, 0, &m); + if (err < 0) { + free(abspath); + return err; + } + + m->path = abspath; + } + + m->builtin = KMOD_MODULE_BUILTIN_NO; + *mod = m; + + return 0; +} + +/** + * kmod_module_unref: + * @mod: kmod module + * + * Drop a reference of the kmod module. If the refcount reaches zero, its + * resources are released. + * + * Returns: NULL if @mod is NULL or if the module was released. Otherwise it + * returns the passed @mod with its refcount decremented. + */ +KMOD_EXPORT struct kmod_module *kmod_module_unref(struct kmod_module *mod) +{ + if (mod == NULL) + return NULL; + + if (--mod->refcount > 0) + return mod; + + DBG(mod->ctx, "kmod_module %p released\n", mod); + + kmod_pool_del_module(mod->ctx, mod, mod->hashkey); + kmod_module_unref_list(mod->dep); + + if (mod->file) + kmod_file_unref(mod->file); + + kmod_unref(mod->ctx); + free(mod->options); + free(mod->path); + free(mod); + return NULL; +} + +/** + * kmod_module_ref: + * @mod: kmod module + * + * Take a reference of the kmod module, incrementing its refcount. + * + * Returns: the passed @module with its refcount incremented. + */ +KMOD_EXPORT struct kmod_module *kmod_module_ref(struct kmod_module *mod) +{ + if (mod == NULL) + return NULL; + + mod->refcount++; + + return mod; +} + +typedef int (*lookup_func)(struct kmod_ctx *ctx, const char *name, struct kmod_list **list) __attribute__((nonnull(1, 2, 3))); + +static int __kmod_module_new_from_lookup(struct kmod_ctx *ctx, const lookup_func lookup[], + size_t lookup_count, const char *s, + struct kmod_list **list) +{ + unsigned int i; + + for (i = 0; i < lookup_count; i++) { + int err; + + err = lookup[i](ctx, s, list); + if (err < 0 && err != -ENOSYS) + return err; + else if (*list != NULL) + return 0; + } + + return 0; +} + +/** + * kmod_module_new_from_lookup: + * @ctx: kmod library context + * @given_alias: alias to look for + * @list: an empty list where to save the list of modules matching + * @given_alias + * + * Create a new list of kmod modules using an alias or module name and lookup + * libkmod's configuration files and indexes in order to find the module. + * Once it's found in one of the places, it stops searching and create the + * list of modules that is saved in @list. + * + * The search order is: 1. aliases in configuration file; 2. module names in + * modules.dep index; 3. symbol aliases in modules.symbols index; 4. aliases + * from install commands; 5. builtin indexes from kernel. + * + * The initial refcount is 1, and needs to be decremented to release the + * resources of the kmod_module. The returned @list must be released by + * calling kmod_module_unref_list(). Since libkmod keeps track of all + * kmod_modules created, they are all released upon @ctx destruction too. Do + * not unref @ctx before all the desired operations with the returned list are + * completed. + * + * Returns: 0 on success or < 0 otherwise. It fails if any of the lookup + * methods failed, which is basically due to memory allocation fail. If module + * is not found, it still returns 0, but @list is an empty list. + */ +KMOD_EXPORT int kmod_module_new_from_lookup(struct kmod_ctx *ctx, + const char *given_alias, + struct kmod_list **list) +{ + static const lookup_func lookup[] = { + kmod_lookup_alias_from_config, + kmod_lookup_alias_from_moddep_file, + kmod_lookup_alias_from_symbols_file, + kmod_lookup_alias_from_commands, + kmod_lookup_alias_from_aliases_file, + kmod_lookup_alias_from_builtin_file, + kmod_lookup_alias_from_kernel_builtin_file, + }; + char alias[PATH_MAX]; + int err; + + if (ctx == NULL || given_alias == NULL) + return -ENOENT; + + if (list == NULL || *list != NULL) { + ERR(ctx, "An empty list is needed to create lookup\n"); + return -ENOSYS; + } + + if (alias_normalize(given_alias, alias, NULL) < 0) { + DBG(ctx, "invalid alias: %s\n", given_alias); + return -EINVAL; + } + + DBG(ctx, "input alias=%s, normalized=%s\n", given_alias, alias); + + err = __kmod_module_new_from_lookup(ctx, lookup, ARRAY_SIZE(lookup), + alias, list); + + DBG(ctx, "lookup=%s found=%d\n", alias, err >= 0 && *list); + + if (err < 0) { + kmod_module_unref_list(*list); + *list = NULL; + } + + return err; +} + +/** + * kmod_module_new_from_name_lookup: + * @ctx: kmod library context + * @modname: module name to look for + * @mod: returned module on success + * + * Lookup by module name, without considering possible aliases. This is similar + * to kmod_module_new_from_lookup(), but don't consider as source indexes and + * configurations that work with aliases. When succesful, this always resolves + * to one and only one module. + * + * The search order is: 1. module names in modules.dep index; + * 2. builtin indexes from kernel. + * + * The initial refcount is 1, and needs to be decremented to release the + * resources of the kmod_module. Since libkmod keeps track of all + * kmod_modules created, they are all released upon @ctx destruction too. Do + * not unref @ctx before all the desired operations with the returned list are + * completed. + * + * Returns: 0 on success or < 0 otherwise. It fails if any of the lookup + * methods failed, which is basically due to memory allocation failure. If + * module is not found, it still returns 0, but @mod is left untouched. + */ +KMOD_EXPORT int kmod_module_new_from_name_lookup(struct kmod_ctx *ctx, + const char *modname, + struct kmod_module **mod) +{ + static const lookup_func lookup[] = { + kmod_lookup_alias_from_moddep_file, + kmod_lookup_alias_from_builtin_file, + kmod_lookup_alias_from_kernel_builtin_file, + }; + char name_norm[PATH_MAX]; + struct kmod_list *list = NULL; + int err; + + if (ctx == NULL || modname == NULL || mod == NULL) + return -ENOENT; + + modname_normalize(modname, name_norm, NULL); + + DBG(ctx, "input modname=%s, normalized=%s\n", modname, name_norm); + + err = __kmod_module_new_from_lookup(ctx, lookup, ARRAY_SIZE(lookup), + name_norm, &list); + + DBG(ctx, "lookup=%s found=%d\n", name_norm, err >= 0 && list); + + if (err >= 0 && list != NULL) + *mod = kmod_module_get_module(list); + + kmod_module_unref_list(list); + + return err; +} + +/** + * kmod_module_unref_list: + * @list: list of kmod modules + * + * Drop a reference of each kmod module in @list and releases the resources + * taken by the list itself. + * + * Returns: 0 + */ +KMOD_EXPORT int kmod_module_unref_list(struct kmod_list *list) +{ + for (; list != NULL; list = kmod_list_remove(list)) + kmod_module_unref(list->data); + + return 0; +} + +/** + * kmod_module_get_filtered_blacklist: + * @ctx: kmod library context + * @input: list of kmod_module to be filtered with blacklist + * @output: where to save the new list + * + * This function should not be used. Use kmod_module_apply_filter instead. + * + * Given a list @input, this function filter it out with config's blacklist + * and save it in @output. + * + * Returns: 0 on success or < 0 otherwise. @output is saved with the updated + * list. + */ +KMOD_EXPORT int kmod_module_get_filtered_blacklist(const struct kmod_ctx *ctx, + const struct kmod_list *input, + struct kmod_list **output) +{ + return kmod_module_apply_filter(ctx, KMOD_FILTER_BLACKLIST, input, output); +} + +static const struct kmod_list *module_get_dependencies_noref(const struct kmod_module *mod) +{ + if (!mod->init.dep) { + /* lazy init */ + char *line = kmod_search_moddep(mod->ctx, mod->name); + + if (line == NULL) + return NULL; + + kmod_module_parse_depline((struct kmod_module *)mod, line); + free(line); + + if (!mod->init.dep) + return NULL; + } + + return mod->dep; +} + +/** + * kmod_module_get_dependencies: + * @mod: kmod module + * + * Search the modules.dep index to find the dependencies of the given @mod. + * The result is cached in @mod, so subsequent calls to this function will + * return the already searched list of modules. + * + * Returns: NULL on failure. Otherwise it returns a list of kmod modules + * that can be released by calling kmod_module_unref_list(). + */ +KMOD_EXPORT struct kmod_list *kmod_module_get_dependencies(const struct kmod_module *mod) +{ + struct kmod_list *l, *l_new, *list_new = NULL; + + if (mod == NULL) + return NULL; + + module_get_dependencies_noref(mod); + + kmod_list_foreach(l, mod->dep) { + l_new = kmod_list_append(list_new, kmod_module_ref(l->data)); + if (l_new == NULL) { + kmod_module_unref(l->data); + goto fail; + } + + list_new = l_new; + } + + return list_new; + +fail: + ERR(mod->ctx, "out of memory\n"); + kmod_module_unref_list(list_new); + return NULL; +} + +/** + * kmod_module_get_module: + * @entry: an entry in a list of kmod modules. + * + * Get the kmod module of this @entry in the list, increasing its refcount. + * After it's used, unref it. Since the refcount is incremented upon return, + * you still have to call kmod_module_unref_list() to release the list of kmod + * modules. + * + * Returns: NULL on failure or the kmod_module contained in this list entry + * with its refcount incremented. + */ +KMOD_EXPORT struct kmod_module *kmod_module_get_module(const struct kmod_list *entry) +{ + if (entry == NULL) + return NULL; + + return kmod_module_ref(entry->data); +} + +/** + * kmod_module_get_name: + * @mod: kmod module + * + * Get the name of this kmod module. Name is always available, independently + * if it was created by kmod_module_new_from_name() or another function and + * it's always normalized (dashes are replaced with underscores). + * + * Returns: the name of this kmod module. + */ +KMOD_EXPORT const char *kmod_module_get_name(const struct kmod_module *mod) +{ + if (mod == NULL) + return NULL; + + return mod->name; +} + +/** + * kmod_module_get_path: + * @mod: kmod module + * + * Get the path of this kmod module. If this kmod module was not created by + * path, it can search the modules.dep index in order to find out the module + * under context's dirname. + * + * Returns: the path of this kmod module or NULL if such information is not + * available. + */ +KMOD_EXPORT const char *kmod_module_get_path(const struct kmod_module *mod) +{ + char *line; + + if (mod == NULL) + return NULL; + + DBG(mod->ctx, "name='%s' path='%s'\n", mod->name, mod->path); + + if (mod->path != NULL) + return mod->path; + if (mod->init.dep) + return NULL; + + /* lazy init */ + line = kmod_search_moddep(mod->ctx, mod->name); + if (line == NULL) + return NULL; + + kmod_module_parse_depline((struct kmod_module *) mod, line); + free(line); + + return mod->path; +} + + +extern long delete_module(const char *name, unsigned int flags); + +/** + * kmod_module_remove_module: + * @mod: kmod module + * @flags: flags used when removing the module. + * KMOD_REMOVE_FORCE: force remove module regardless if it's still in + * use by a kernel subsystem or other process; passed directly to Linux kernel + * KMOD_REMOVE_NOWAIT: is always enforced, causing us to pass O_NONBLOCK to + * delete_module(2). + * KMOD_REMOVE_NOLOG: when module removal fails, do not log anything as the + * caller may want to handle retries and log when appropriate. + * + * Remove a module from Linux kernel. + * + * Returns: 0 on success or < 0 on failure. + */ +KMOD_EXPORT int kmod_module_remove_module(struct kmod_module *mod, + unsigned int flags) +{ + unsigned int libkmod_flags = flags & 0xff; + + int err; + + if (mod == NULL) + return -ENOENT; + + /* Filter out other flags and force ONONBLOCK */ + flags &= KMOD_REMOVE_FORCE; + flags |= KMOD_REMOVE_NOWAIT; + + err = delete_module(mod->name, flags); + if (err != 0) { + err = -errno; + if (!(libkmod_flags & KMOD_REMOVE_NOLOG)) + ERR(mod->ctx, "could not remove '%s': %m\n", mod->name); + } + + return err; +} + +extern long init_module(const void *mem, unsigned long len, const char *args); + +static int do_finit_module(struct kmod_module *mod, unsigned int flags, + const char *args) +{ + enum kmod_file_compression_type compression, kernel_compression; + unsigned int kernel_flags = 0; + int err; + + /* + * When module is not compressed or its compression type matches the + * one in use by the kernel, there is no need to read the file + * in userspace. Otherwise, re-use ENOSYS to trigger the same fallback + * as when finit_module() is not supported. + */ + compression = kmod_file_get_compression(mod->file); + kernel_compression = kmod_get_kernel_compression(mod->ctx); + if (!(compression == KMOD_FILE_COMPRESSION_NONE || + compression == kernel_compression)) + return -ENOSYS; + + if (compression != KMOD_FILE_COMPRESSION_NONE) + kernel_flags |= MODULE_INIT_COMPRESSED_FILE; + + if (flags & KMOD_INSERT_FORCE_VERMAGIC) + kernel_flags |= MODULE_INIT_IGNORE_VERMAGIC; + if (flags & KMOD_INSERT_FORCE_MODVERSION) + kernel_flags |= MODULE_INIT_IGNORE_MODVERSIONS; + + err = finit_module(kmod_file_get_fd(mod->file), args, kernel_flags); + if (err < 0) + err = -errno; + + return err; +} + +static int do_init_module(struct kmod_module *mod, unsigned int flags, + const char *args) +{ + struct kmod_elf *elf; + const void *mem; + off_t size; + int err; + + kmod_file_load_contents(mod->file); + + if (flags & (KMOD_INSERT_FORCE_VERMAGIC | KMOD_INSERT_FORCE_MODVERSION)) { + elf = kmod_file_get_elf(mod->file); + if (elf == NULL) { + err = -errno; + return err; + } + + if (flags & KMOD_INSERT_FORCE_MODVERSION) { + err = kmod_elf_strip_section(elf, "__versions"); + if (err < 0) + INFO(mod->ctx, "Failed to strip modversion: %s\n", strerror(-err)); + } + + if (flags & KMOD_INSERT_FORCE_VERMAGIC) { + err = kmod_elf_strip_vermagic(elf); + if (err < 0) + INFO(mod->ctx, "Failed to strip vermagic: %s\n", strerror(-err)); + } + + mem = kmod_elf_get_memory(elf); + } else { + mem = kmod_file_get_contents(mod->file); + } + size = kmod_file_get_size(mod->file); + + err = init_module(mem, size, args); + if (err < 0) + err = -errno; + + return err; +} + +/** + * kmod_module_insert_module: + * @mod: kmod module + * @flags: flags are not passed to Linux Kernel, but instead they dictate the + * behavior of this function, valid flags are + * KMOD_INSERT_FORCE_VERMAGIC: ignore kernel version magic; + * KMOD_INSERT_FORCE_MODVERSION: ignore symbol version hashes. + * @options: module's options to pass to Linux Kernel. + * + * Insert a module in Linux kernel. It opens the file pointed by @mod, + * mmap'ing it and passing to kernel. + * + * Returns: 0 on success or < 0 on failure. If module is already loaded it + * returns -EEXIST. + */ +KMOD_EXPORT int kmod_module_insert_module(struct kmod_module *mod, + unsigned int flags, + const char *options) +{ + int err; + const char *path; + const char *args = options ? options : ""; + + if (mod == NULL) + return -ENOENT; + + path = kmod_module_get_path(mod); + if (path == NULL) { + ERR(mod->ctx, "could not find module by name='%s'\n", mod->name); + return -ENOENT; + } + + if (!mod->file) { + mod->file = kmod_file_open(mod->ctx, path); + if (mod->file == NULL) { + err = -errno; + return err; + } + } + + err = do_finit_module(mod, flags, args); + if (err == -ENOSYS) + err = do_init_module(mod, flags, args); + + if (err < 0) + INFO(mod->ctx, "Failed to insert module '%s': %s\n", + path, strerror(-err)); + + return err; +} + +static bool module_is_blacklisted(struct kmod_module *mod) +{ + struct kmod_ctx *ctx = mod->ctx; + const struct kmod_config *config = kmod_get_config(ctx); + const struct kmod_list *bl = config->blacklists; + const struct kmod_list *l; + + kmod_list_foreach(l, bl) { + const char *modname = kmod_blacklist_get_modname(l); + + if (streq(modname, mod->name)) + return true; + } + + return false; +} + +/** + * kmod_module_apply_filter + * @ctx: kmod library context + * @filter_type: bitmask to filter modules out, valid types are + * KMOD_FILTER_BLACKLIST: filter modules in blacklist out; + * KMOD_FILTER_BUILTIN: filter builtin modules out. + * @input: list of kmod_module to be filtered + * @output: where to save the new list + * + * Given a list @input, this function filter it out by the filter mask + * and save it in @output. + * + * Returns: 0 on success or < 0 otherwise. @output is saved with the updated + * list. + */ +KMOD_EXPORT int kmod_module_apply_filter(const struct kmod_ctx *ctx, + enum kmod_filter filter_type, + const struct kmod_list *input, + struct kmod_list **output) +{ + const struct kmod_list *li; + + if (ctx == NULL || output == NULL) + return -ENOENT; + + *output = NULL; + if (input == NULL) + return 0; + + kmod_list_foreach(li, input) { + struct kmod_module *mod = li->data; + struct kmod_list *node; + + if ((filter_type & KMOD_FILTER_BLACKLIST) && + module_is_blacklisted(mod)) + continue; + + if ((filter_type & KMOD_FILTER_BUILTIN) + && kmod_module_is_builtin(mod)) + continue; + + node = kmod_list_append(*output, mod); + if (node == NULL) + goto fail; + + *output = node; + kmod_module_ref(mod); + } + + return 0; + +fail: + kmod_module_unref_list(*output); + *output = NULL; + return -ENOMEM; +} + +static int command_do(struct kmod_module *mod, const char *type, + const char *cmd) +{ + const char *modname = kmod_module_get_name(mod); + int err; + + DBG(mod->ctx, "%s %s\n", type, cmd); + + setenv("MODPROBE_MODULE", modname, 1); + err = system(cmd); + unsetenv("MODPROBE_MODULE"); + + if (err == -1) { + ERR(mod->ctx, "Could not run %s command '%s' for module %s: %m\n", + type, cmd, modname); + return -EINVAL; + } + + if (WEXITSTATUS(err)) { + ERR(mod->ctx, "Error running %s command '%s' for module %s: retcode %d\n", + type, cmd, modname, WEXITSTATUS(err)); + return -EINVAL; + } + + return 0; +} + +struct probe_insert_cb { + int (*run_install)(struct kmod_module *m, const char *cmd, void *data); + void *data; +}; + +static int module_do_install_commands(struct kmod_module *mod, + const char *options, + struct probe_insert_cb *cb) +{ + const char *command = kmod_module_get_install_commands(mod); + char *p; + _cleanup_free_ char *cmd; + int err; + size_t cmdlen, options_len, varlen; + + assert(command); + + if (options == NULL) + options = ""; + + options_len = strlen(options); + cmdlen = strlen(command); + varlen = sizeof("$CMDLINE_OPTS") - 1; + + cmd = memdup(command, cmdlen + 1); + if (cmd == NULL) + return -ENOMEM; + + while ((p = strstr(cmd, "$CMDLINE_OPTS")) != NULL) { + size_t prefixlen = p - cmd; + size_t suffixlen = cmdlen - prefixlen - varlen; + size_t slen = cmdlen - varlen + options_len; + char *suffix = p + varlen; + char *s = malloc(slen + 1); + if (!s) + return -ENOMEM; + + memcpy(s, cmd, p - cmd); + memcpy(s + prefixlen, options, options_len); + memcpy(s + prefixlen + options_len, suffix, suffixlen); + s[slen] = '\0'; + + free(cmd); + cmd = s; + cmdlen = slen; + } + + if (cb->run_install != NULL) + err = cb->run_install(mod, cmd, cb->data); + else + err = command_do(mod, "install", cmd); + + return err; +} + +static char *module_options_concat(const char *opt, const char *xopt) +{ + // TODO: we might need to check if xopt overrides options on opt + size_t optlen = opt == NULL ? 0 : strlen(opt); + size_t xoptlen = xopt == NULL ? 0 : strlen(xopt); + char *r; + + if (optlen == 0 && xoptlen == 0) + return NULL; + + r = malloc(optlen + xoptlen + 2); + + if (opt != NULL) { + memcpy(r, opt, optlen); + r[optlen] = ' '; + optlen++; + } + + if (xopt != NULL) + memcpy(r + optlen, xopt, xoptlen); + + r[optlen + xoptlen] = '\0'; + + return r; +} + +static int __kmod_module_get_probe_list(struct kmod_module *mod, + bool required, + bool ignorecmd, + struct kmod_list **list); + +/* re-entrant */ +static int __kmod_module_fill_softdep(struct kmod_module *mod, + struct kmod_list **list) +{ + struct kmod_list *pre = NULL, *post = NULL, *l; + int err; + + err = kmod_module_get_softdeps(mod, &pre, &post); + if (err < 0) { + ERR(mod->ctx, "could not get softdep: %s\n", + strerror(-err)); + goto fail; + } + + kmod_list_foreach(l, pre) { + struct kmod_module *m = l->data; + err = __kmod_module_get_probe_list(m, false, false, list); + if (err < 0) + goto fail; + } + + l = kmod_list_append(*list, kmod_module_ref(mod)); + if (l == NULL) { + kmod_module_unref(mod); + err = -ENOMEM; + goto fail; + } + *list = l; + mod->ignorecmd = (pre != NULL || post != NULL); + + kmod_list_foreach(l, post) { + struct kmod_module *m = l->data; + err = __kmod_module_get_probe_list(m, false, false, list); + if (err < 0) + goto fail; + } + +fail: + kmod_module_unref_list(pre); + kmod_module_unref_list(post); + + return err; +} + +/* re-entrant */ +static int __kmod_module_get_probe_list(struct kmod_module *mod, + bool required, + bool ignorecmd, + struct kmod_list **list) +{ + struct kmod_list *dep, *l; + int err = 0; + + if (mod->visited) { + DBG(mod->ctx, "Ignore module '%s': already visited\n", + mod->name); + return 0; + } + mod->visited = true; + + dep = kmod_module_get_dependencies(mod); + if (required) { + /* + * Called from kmod_module_probe_insert_module(); set the + * ->required flag on mod and all its dependencies before + * they are possibly visited through some softdeps. + */ + mod->required = true; + kmod_list_foreach(l, dep) { + struct kmod_module *m = l->data; + m->required = true; + } + } + + kmod_list_foreach(l, dep) { + struct kmod_module *m = l->data; + err = __kmod_module_fill_softdep(m, list); + if (err < 0) + goto finish; + } + + if (ignorecmd) { + l = kmod_list_append(*list, kmod_module_ref(mod)); + if (l == NULL) { + kmod_module_unref(mod); + err = -ENOMEM; + goto finish; + } + *list = l; + mod->ignorecmd = true; + } else + err = __kmod_module_fill_softdep(mod, list); + +finish: + kmod_module_unref_list(dep); + return err; +} + +static int kmod_module_get_probe_list(struct kmod_module *mod, + bool ignorecmd, + struct kmod_list **list) +{ + int err; + + assert(mod != NULL); + assert(list != NULL && *list == NULL); + + /* + * Make sure we don't get screwed by previous calls to this function + */ + kmod_set_modules_visited(mod->ctx, false); + kmod_set_modules_required(mod->ctx, false); + + err = __kmod_module_get_probe_list(mod, true, ignorecmd, list); + if (err < 0) { + kmod_module_unref_list(*list); + *list = NULL; + } + + return err; +} + +/** + * kmod_module_probe_insert_module: + * @mod: kmod module + * @flags: flags are not passed to Linux Kernel, but instead they dictate the + * behavior of this function, valid flags are + * KMOD_PROBE_FORCE_VERMAGIC: ignore kernel version magic; + * KMOD_PROBE_FORCE_MODVERSION: ignore symbol version hashes; + * KMOD_PROBE_IGNORE_COMMAND: whether the probe should ignore install + * commands and softdeps configured in the system; + * KMOD_PROBE_IGNORE_LOADED: do not check whether the module is already + * live in kernel or not; + * KMOD_PROBE_DRY_RUN: dry run, do not insert module, just call the + * associated callback function; + * KMOD_PROBE_FAIL_ON_LOADED: if KMOD_PROBE_IGNORE_LOADED is not specified + * and the module is already live in kernel, the function will fail if this + * flag is specified; + * KMOD_PROBE_APPLY_BLACKLIST_ALL: probe will apply KMOD_FILTER_BLACKLIST + * filter to this module and its dependencies. If any of the dependencies (or + * the module) is blacklisted, the probe will fail, unless the blacklisted + * module is already live in kernel; + * KMOD_PROBE_APPLY_BLACKLIST: probe will fail if the module is blacklisted; + * KMOD_PROBE_APPLY_BLACKLIST_ALIAS_ONLY: probe will fail if the module is an + * alias and is blacklisted. + * @extra_options: module's options to pass to Linux Kernel. It applies only + * to @mod, not to its dependencies. + * @run_install: function to run when @mod is backed by an install command. + * @data: data to give back to @run_install callback + * @print_action: function to call with the action being taken (install or + * insmod). It's useful for tools like modprobe when running with verbose + * output or in dry-run mode. + * + * Insert a module in Linux kernel resolving dependencies, soft dependencies, + * install commands and applying blacklist. + * + * If @run_install is NULL, this function will fork and exec by calling + * system(3). Don't pass a NULL argument in @run_install if your binary is + * setuid/setgid (see warning in system(3)). If you need control over the + * execution of an install command, give a callback function instead. + * + * Returns: 0 on success, > 0 if stopped by a reason given in @flags or < 0 on + * failure. + */ +KMOD_EXPORT int kmod_module_probe_insert_module(struct kmod_module *mod, + unsigned int flags, const char *extra_options, + int (*run_install)(struct kmod_module *m, + const char *cmd, void *data), + const void *data, + void (*print_action)(struct kmod_module *m, + bool install, + const char *options)) +{ + struct kmod_list *list = NULL, *l; + struct probe_insert_cb cb; + int err; + + if (mod == NULL) + return -ENOENT; + + if (!(flags & KMOD_PROBE_IGNORE_LOADED) + && module_is_inkernel(mod)) { + if (flags & KMOD_PROBE_FAIL_ON_LOADED) + return -EEXIST; + else + return 0; + } + + /* + * Ugly assignement + check. We need to check if we were told to check + * blacklist and also return the reason why we failed. + * KMOD_PROBE_APPLY_BLACKLIST_ALIAS_ONLY will take effect only if the + * module is an alias, so we also need to check it + */ + if ((mod->alias != NULL && ((err = flags & KMOD_PROBE_APPLY_BLACKLIST_ALIAS_ONLY))) + || (err = flags & KMOD_PROBE_APPLY_BLACKLIST_ALL) + || (err = flags & KMOD_PROBE_APPLY_BLACKLIST)) { + if (module_is_blacklisted(mod)) + return err; + } + + err = kmod_module_get_probe_list(mod, + !!(flags & KMOD_PROBE_IGNORE_COMMAND), &list); + if (err < 0) + return err; + + if (flags & KMOD_PROBE_APPLY_BLACKLIST_ALL) { + struct kmod_list *filtered = NULL; + + err = kmod_module_apply_filter(mod->ctx, + KMOD_FILTER_BLACKLIST, list, &filtered); + if (err < 0) + return err; + + kmod_module_unref_list(list); + if (filtered == NULL) + return KMOD_PROBE_APPLY_BLACKLIST_ALL; + + list = filtered; + } + + cb.run_install = run_install; + cb.data = (void *) data; + + kmod_list_foreach(l, list) { + struct kmod_module *m = l->data; + const char *moptions = kmod_module_get_options(m); + const char *cmd = kmod_module_get_install_commands(m); + char *options; + + if (!(flags & KMOD_PROBE_IGNORE_LOADED) + && module_is_inkernel(m)) { + DBG(mod->ctx, "Ignoring module '%s': already loaded\n", + m->name); + err = -EEXIST; + goto finish_module; + } + + options = module_options_concat(moptions, + m == mod ? extra_options : NULL); + + if (cmd != NULL && !m->ignorecmd) { + if (print_action != NULL) + print_action(m, true, options ?: ""); + + if (!(flags & KMOD_PROBE_DRY_RUN)) + err = module_do_install_commands(m, options, + &cb); + } else { + if (print_action != NULL) + print_action(m, false, options ?: ""); + + if (!(flags & KMOD_PROBE_DRY_RUN)) + err = kmod_module_insert_module(m, flags, + options); + } + + free(options); + +finish_module: + /* + * Treat "already loaded" error. If we were told to stop on + * already loaded and the module being loaded is not a softdep + * or dep, bail out. Otherwise, just ignore and continue. + * + * We need to check here because of race conditions. We + * checked first if module was already loaded but it may have + * been loaded between the check and the moment we try to + * insert it. + */ + if (err == -EEXIST && m == mod && + (flags & KMOD_PROBE_FAIL_ON_LOADED)) + break; + + /* + * Ignore errors from softdeps + */ + if (err == -EEXIST || !m->required) + err = 0; + + else if (err < 0) + break; + } + + kmod_module_unref_list(list); + return err; +} + +/** + * kmod_module_get_options: + * @mod: kmod module + * + * Get options of this kmod module. Options come from the configuration file + * and are cached in @mod. The first call to this function will search for + * this module in configuration and subsequent calls return the cached string. + * + * Returns: a string with all the options separated by spaces. This string is + * owned by @mod, do not free it. + */ +KMOD_EXPORT const char *kmod_module_get_options(const struct kmod_module *mod) +{ + if (mod == NULL) + return NULL; + + if (!mod->init.options) { + /* lazy init */ + struct kmod_module *m = (struct kmod_module *)mod; + const struct kmod_list *l; + const struct kmod_config *config; + char *opts = NULL; + size_t optslen = 0; + + config = kmod_get_config(mod->ctx); + + kmod_list_foreach(l, config->options) { + const char *modname = kmod_option_get_modname(l); + const char *str; + size_t len; + void *tmp; + + DBG(mod->ctx, "modname=%s mod->name=%s mod->alias=%s\n", modname, mod->name, mod->alias); + if (!(streq(modname, mod->name) || (mod->alias != NULL && + streq(modname, mod->alias)))) + continue; + + DBG(mod->ctx, "passed = modname=%s mod->name=%s mod->alias=%s\n", modname, mod->name, mod->alias); + str = kmod_option_get_options(l); + len = strlen(str); + if (len < 1) + continue; + + tmp = realloc(opts, optslen + len + 2); + if (tmp == NULL) { + free(opts); + goto failed; + } + + opts = tmp; + + if (optslen > 0) { + opts[optslen] = ' '; + optslen++; + } + + memcpy(opts + optslen, str, len); + optslen += len; + opts[optslen] = '\0'; + } + + m->init.options = true; + m->options = opts; + } + + return mod->options; + +failed: + ERR(mod->ctx, "out of memory\n"); + return NULL; +} + +/** + * kmod_module_get_install_commands: + * @mod: kmod module + * + * Get install commands for this kmod module. Install commands come from the + * configuration file and are cached in @mod. The first call to this function + * will search for this module in configuration and subsequent calls return + * the cached string. The install commands are returned as they were in the + * configuration, concatenated by ';'. No other processing is made in this + * string. + * + * Returns: a string with all install commands separated by semicolons. This + * string is owned by @mod, do not free it. + */ +KMOD_EXPORT const char *kmod_module_get_install_commands(const struct kmod_module *mod) +{ + if (mod == NULL) + return NULL; + + if (!mod->init.install_commands) { + /* lazy init */ + struct kmod_module *m = (struct kmod_module *)mod; + const struct kmod_list *l; + const struct kmod_config *config; + + config = kmod_get_config(mod->ctx); + + kmod_list_foreach(l, config->install_commands) { + const char *modname = kmod_command_get_modname(l); + + if (fnmatch(modname, mod->name, 0) != 0) + continue; + + m->install_commands = kmod_command_get_command(l); + + /* + * find only the first command, as modprobe from + * module-init-tools does + */ + break; + } + + m->init.install_commands = true; + } + + return mod->install_commands; +} + +void kmod_module_set_install_commands(struct kmod_module *mod, const char *cmd) +{ + mod->init.install_commands = true; + mod->install_commands = cmd; +} + +static struct kmod_list *lookup_softdep(struct kmod_ctx *ctx, const char * const * array, unsigned int count) +{ + struct kmod_list *ret = NULL; + unsigned i; + + for (i = 0; i < count; i++) { + const char *depname = array[i]; + struct kmod_list *lst = NULL; + int err; + + err = kmod_module_new_from_lookup(ctx, depname, &lst); + if (err < 0) { + ERR(ctx, "failed to lookup soft dependency '%s', continuing anyway.\n", depname); + continue; + } else if (lst != NULL) + ret = kmod_list_append_list(ret, lst); + } + return ret; +} + +/** + * kmod_module_get_softdeps: + * @mod: kmod module + * @pre: where to save the list of preceding soft dependencies. + * @post: where to save the list of post soft dependencies. + * + * Get soft dependencies for this kmod module. Soft dependencies come + * from configuration file and are not cached in @mod because it may include + * dependency cycles that would make we leak kmod_module. Any call + * to this function will search for this module in configuration, allocate a + * list and return the result. + * + * Both @pre and @post are newly created list of kmod_module and + * should be unreferenced with kmod_module_unref_list(). + * + * Returns: 0 on success or < 0 otherwise. + */ +KMOD_EXPORT int kmod_module_get_softdeps(const struct kmod_module *mod, + struct kmod_list **pre, + struct kmod_list **post) +{ + const struct kmod_list *l; + const struct kmod_config *config; + + if (mod == NULL || pre == NULL || post == NULL) + return -ENOENT; + + assert(*pre == NULL); + assert(*post == NULL); + + config = kmod_get_config(mod->ctx); + + kmod_list_foreach(l, config->softdeps) { + const char *modname = kmod_softdep_get_name(l); + const char * const *array; + unsigned count; + + if (fnmatch(modname, mod->name, 0) != 0) + continue; + + array = kmod_softdep_get_pre(l, &count); + *pre = lookup_softdep(mod->ctx, array, count); + array = kmod_softdep_get_post(l, &count); + *post = lookup_softdep(mod->ctx, array, count); + + /* + * find only the first command, as modprobe from + * module-init-tools does + */ + break; + } + + return 0; +} + +/** + * kmod_module_get_remove_commands: + * @mod: kmod module + * + * Get remove commands for this kmod module. Remove commands come from the + * configuration file and are cached in @mod. The first call to this function + * will search for this module in configuration and subsequent calls return + * the cached string. The remove commands are returned as they were in the + * configuration, concatenated by ';'. No other processing is made in this + * string. + * + * Returns: a string with all remove commands separated by semicolons. This + * string is owned by @mod, do not free it. + */ +KMOD_EXPORT const char *kmod_module_get_remove_commands(const struct kmod_module *mod) +{ + if (mod == NULL) + return NULL; + + if (!mod->init.remove_commands) { + /* lazy init */ + struct kmod_module *m = (struct kmod_module *)mod; + const struct kmod_list *l; + const struct kmod_config *config; + + config = kmod_get_config(mod->ctx); + + kmod_list_foreach(l, config->remove_commands) { + const char *modname = kmod_command_get_modname(l); + + if (fnmatch(modname, mod->name, 0) != 0) + continue; + + m->remove_commands = kmod_command_get_command(l); + + /* + * find only the first command, as modprobe from + * module-init-tools does + */ + break; + } + + m->init.remove_commands = true; + } + + return mod->remove_commands; +} + +void kmod_module_set_remove_commands(struct kmod_module *mod, const char *cmd) +{ + mod->init.remove_commands = true; + mod->remove_commands = cmd; +} + +/** + * SECTION:libkmod-loaded + * @short_description: currently loaded modules + * + * Information about currently loaded modules, as reported by Linux kernel. + * These information are not cached by libkmod and are always read from /sys + * and /proc/modules. + */ + +/** + * kmod_module_new_from_loaded: + * @ctx: kmod library context + * @list: where to save the list of loaded modules + * + * Create a new list of kmod modules with all modules currently loaded in + * kernel. It uses /proc/modules to get the names of loaded modules and to + * create kmod modules by calling kmod_module_new_from_name() in each of them. + * They are put in @list in no particular order. + * + * The initial refcount is 1, and needs to be decremented to release the + * resources of the kmod_module. The returned @list must be released by + * calling kmod_module_unref_list(). Since libkmod keeps track of all + * kmod_modules created, they are all released upon @ctx destruction too. Do + * not unref @ctx before all the desired operations with the returned list are + * completed. + * + * Returns: 0 on success or < 0 on error. + */ +KMOD_EXPORT int kmod_module_new_from_loaded(struct kmod_ctx *ctx, + struct kmod_list **list) +{ + struct kmod_list *l = NULL; + FILE *fp; + char line[4096]; + + if (ctx == NULL || list == NULL) + return -ENOENT; + + fp = fopen("/proc/modules", "re"); + if (fp == NULL) { + int err = -errno; + ERR(ctx, "could not open /proc/modules: %s\n", strerror(errno)); + return err; + } + + while (fgets(line, sizeof(line), fp)) { + struct kmod_module *m; + struct kmod_list *node; + int err; + size_t len = strlen(line); + char *saveptr, *name = strtok_r(line, " \t", &saveptr); + + err = kmod_module_new_from_name(ctx, name, &m); + if (err < 0) { + ERR(ctx, "could not get module from name '%s': %s\n", + name, strerror(-err)); + goto eat_line; + } + + node = kmod_list_append(l, m); + if (node) + l = node; + else { + ERR(ctx, "out of memory\n"); + kmod_module_unref(m); + } +eat_line: + while (line[len - 1] != '\n' && fgets(line, sizeof(line), fp)) + len = strlen(line); + } + + fclose(fp); + *list = l; + + return 0; +} + +/** + * kmod_module_initstate_str: + * @state: the state as returned by kmod_module_get_initstate() + * + * Translate a initstate to a string. + * + * Returns: the string associated to the @state. This string is statically + * allocated, do not free it. + */ +KMOD_EXPORT const char *kmod_module_initstate_str(enum kmod_module_initstate state) +{ + switch (state) { + case KMOD_MODULE_BUILTIN: + return "builtin"; + case KMOD_MODULE_LIVE: + return "live"; + case KMOD_MODULE_COMING: + return "coming"; + case KMOD_MODULE_GOING: + return "going"; + default: + return NULL; + } +} + +/** + * kmod_module_get_initstate: + * @mod: kmod module + * + * Get the initstate of this @mod, as returned by Linux Kernel, by reading + * /sys filesystem. + * + * Returns: < 0 on error or module state if module is found in kernel, valid states are + * KMOD_MODULE_BUILTIN: module is builtin; + * KMOD_MODULE_LIVE: module is live in kernel; + * KMOD_MODULE_COMING: module is being loaded; + * KMOD_MODULE_GOING: module is being unloaded. + */ +KMOD_EXPORT int kmod_module_get_initstate(const struct kmod_module *mod) +{ + char path[PATH_MAX], buf[32]; + int fd, err, pathlen; + + if (mod == NULL) + return -ENOENT; + + /* remove const: this can only change internal state */ + if (kmod_module_is_builtin((struct kmod_module *)mod)) + return KMOD_MODULE_BUILTIN; + + pathlen = snprintf(path, sizeof(path), + "/sys/module/%s/initstate", mod->name); + if (pathlen >= (int)sizeof(path)) { + /* Too long path was truncated */ + return -ENAMETOOLONG; + } + fd = open(path, O_RDONLY|O_CLOEXEC); + if (fd < 0) { + err = -errno; + + DBG(mod->ctx, "could not open '%s': %s\n", + path, strerror(-err)); + + if (pathlen > (int)sizeof("/initstate") - 1) { + struct stat st; + path[pathlen - (sizeof("/initstate") - 1)] = '\0'; + if (stat(path, &st) == 0 && S_ISDIR(st.st_mode)) + return KMOD_MODULE_COMING; + } + + DBG(mod->ctx, "could not open '%s': %s\n", + path, strerror(-err)); + return err; + } + + err = read_str_safe(fd, buf, sizeof(buf)); + close(fd); + if (err < 0) { + ERR(mod->ctx, "could not read from '%s': %s\n", + path, strerror(-err)); + return err; + } + + if (streq(buf, "live\n")) + return KMOD_MODULE_LIVE; + else if (streq(buf, "coming\n")) + return KMOD_MODULE_COMING; + else if (streq(buf, "going\n")) + return KMOD_MODULE_GOING; + + ERR(mod->ctx, "unknown %s: '%s'\n", path, buf); + return -EINVAL; +} + +/** + * kmod_module_get_size: + * @mod: kmod module + * + * Get the size of this kmod module as returned by Linux kernel. If supported, + * the size is read from the coresize attribute in /sys/module. For older + * kernels, this falls back on /proc/modules and searches for the specified + * module to get its size. + * + * Returns: the size of this kmod module. + */ +KMOD_EXPORT long kmod_module_get_size(const struct kmod_module *mod) +{ + FILE *fp; + char line[4096]; + int lineno = 0; + long size = -ENOENT; + int dfd, cfd; + + if (mod == NULL) + return -ENOENT; + + /* try to open the module dir in /sys. If this fails, don't + * bother trying to find the size as we know the module isn't + * loaded. + */ + snprintf(line, sizeof(line), "/sys/module/%s", mod->name); + dfd = open(line, O_RDONLY|O_CLOEXEC); + if (dfd < 0) + return -errno; + + /* available as of linux 3.3.x */ + cfd = openat(dfd, "coresize", O_RDONLY|O_CLOEXEC); + if (cfd >= 0) { + if (read_str_long(cfd, &size, 10) < 0) + ERR(mod->ctx, "failed to read coresize from %s\n", line); + close(cfd); + goto done; + } + + /* fall back on parsing /proc/modules */ + fp = fopen("/proc/modules", "re"); + if (fp == NULL) { + int err = -errno; + ERR(mod->ctx, + "could not open /proc/modules: %s\n", strerror(errno)); + close(dfd); + return err; + } + + while (fgets(line, sizeof(line), fp)) { + size_t len = strlen(line); + char *saveptr, *endptr, *tok = strtok_r(line, " \t", &saveptr); + long value; + + lineno++; + if (tok == NULL || !streq(tok, mod->name)) + goto eat_line; + + tok = strtok_r(NULL, " \t", &saveptr); + if (tok == NULL) { + ERR(mod->ctx, + "invalid line format at /proc/modules:%d\n", lineno); + break; + } + + value = strtol(tok, &endptr, 10); + if (endptr == tok || *endptr != '\0') { + ERR(mod->ctx, + "invalid line format at /proc/modules:%d\n", lineno); + break; + } + + size = value; + break; +eat_line: + while (line[len - 1] != '\n' && fgets(line, sizeof(line), fp)) + len = strlen(line); + } + fclose(fp); + +done: + close(dfd); + return size; +} + +/** + * kmod_module_get_refcnt: + * @mod: kmod module + * + * Get the ref count of this @mod, as returned by Linux Kernel, by reading + * /sys filesystem. + * + * Returns: the reference count on success or < 0 on failure. + */ +KMOD_EXPORT int kmod_module_get_refcnt(const struct kmod_module *mod) +{ + char path[PATH_MAX]; + long refcnt; + int fd, err; + + if (mod == NULL) + return -ENOENT; + + snprintf(path, sizeof(path), "/sys/module/%s/refcnt", mod->name); + fd = open(path, O_RDONLY|O_CLOEXEC); + if (fd < 0) { + err = -errno; + DBG(mod->ctx, "could not open '%s': %s\n", + path, strerror(errno)); + return err; + } + + err = read_str_long(fd, &refcnt, 10); + close(fd); + if (err < 0) { + ERR(mod->ctx, "could not read integer from '%s': '%s'\n", + path, strerror(-err)); + return err; + } + + return (int)refcnt; +} + +/** + * kmod_module_get_holders: + * @mod: kmod module + * + * Get a list of kmod modules that are holding this @mod, as returned by Linux + * Kernel. After use, free the @list by calling kmod_module_unref_list(). + * + * Returns: a new list of kmod modules on success or NULL on failure. + */ +KMOD_EXPORT struct kmod_list *kmod_module_get_holders(const struct kmod_module *mod) +{ + char dname[PATH_MAX]; + struct kmod_list *list = NULL; + struct dirent *dent; + DIR *d; + + if (mod == NULL || mod->ctx == NULL) + return NULL; + + snprintf(dname, sizeof(dname), "/sys/module/%s/holders", mod->name); + + d = opendir(dname); + if (d == NULL) { + ERR(mod->ctx, "could not open '%s': %s\n", + dname, strerror(errno)); + return NULL; + } + + for (dent = readdir(d); dent != NULL; dent = readdir(d)) { + struct kmod_module *holder; + struct kmod_list *l; + int err; + + if (dent->d_name[0] == '.') { + if (dent->d_name[1] == '\0' || + (dent->d_name[1] == '.' && dent->d_name[2] == '\0')) + continue; + } + + err = kmod_module_new_from_name(mod->ctx, dent->d_name, + &holder); + if (err < 0) { + ERR(mod->ctx, "could not create module for '%s': %s\n", + dent->d_name, strerror(-err)); + goto fail; + } + + l = kmod_list_append(list, holder); + if (l != NULL) { + list = l; + } else { + ERR(mod->ctx, "out of memory\n"); + kmod_module_unref(holder); + goto fail; + } + } + + closedir(d); + return list; + +fail: + closedir(d); + kmod_module_unref_list(list); + return NULL; +} + +struct kmod_module_section { + unsigned long address; + char name[]; +}; + +static void kmod_module_section_free(struct kmod_module_section *section) +{ + free(section); +} + +/** + * kmod_module_get_sections: + * @mod: kmod module + * + * Get a list of kmod sections of this @mod, as returned by Linux Kernel. The + * structure contained in this list is internal to libkmod and their fields + * can be obtained by calling kmod_module_section_get_name() and + * kmod_module_section_get_address(). + * + * After use, free the @list by calling kmod_module_section_free_list(). + * + * Returns: a new list of kmod module sections on success or NULL on failure. + */ +KMOD_EXPORT struct kmod_list *kmod_module_get_sections(const struct kmod_module *mod) +{ + char dname[PATH_MAX]; + struct kmod_list *list = NULL; + struct dirent *dent; + DIR *d; + int dfd; + + if (mod == NULL) + return NULL; + + snprintf(dname, sizeof(dname), "/sys/module/%s/sections", mod->name); + + d = opendir(dname); + if (d == NULL) { + ERR(mod->ctx, "could not open '%s': %s\n", + dname, strerror(errno)); + return NULL; + } + + dfd = dirfd(d); + + for (dent = readdir(d); dent; dent = readdir(d)) { + struct kmod_module_section *section; + struct kmod_list *l; + unsigned long address; + size_t namesz; + int fd, err; + + if (dent->d_name[0] == '.') { + if (dent->d_name[1] == '\0' || + (dent->d_name[1] == '.' && dent->d_name[2] == '\0')) + continue; + } + + fd = openat(dfd, dent->d_name, O_RDONLY|O_CLOEXEC); + if (fd < 0) { + ERR(mod->ctx, "could not open '%s/%s': %m\n", + dname, dent->d_name); + goto fail; + } + + err = read_str_ulong(fd, &address, 16); + close(fd); + if (err < 0) { + ERR(mod->ctx, "could not read long from '%s/%s': %m\n", + dname, dent->d_name); + goto fail; + } + + namesz = strlen(dent->d_name) + 1; + section = malloc(sizeof(*section) + namesz); + + if (section == NULL) { + ERR(mod->ctx, "out of memory\n"); + goto fail; + } + + section->address = address; + memcpy(section->name, dent->d_name, namesz); + + l = kmod_list_append(list, section); + if (l != NULL) { + list = l; + } else { + ERR(mod->ctx, "out of memory\n"); + free(section); + goto fail; + } + } + + closedir(d); + return list; + +fail: + closedir(d); + kmod_module_unref_list(list); + return NULL; +} + +/** + * kmod_module_section_get_module_name: + * @entry: a list entry representing a kmod module section + * + * Get the name of a kmod module section. + * + * After use, free the @list by calling kmod_module_section_free_list(). + * + * Returns: the name of this kmod module section on success or NULL on + * failure. The string is owned by the section, do not free it. + */ +KMOD_EXPORT const char *kmod_module_section_get_name(const struct kmod_list *entry) +{ + struct kmod_module_section *section; + + if (entry == NULL) + return NULL; + + section = entry->data; + return section->name; +} + +/** + * kmod_module_section_get_address: + * @entry: a list entry representing a kmod module section + * + * Get the address of a kmod module section. + * + * After use, free the @list by calling kmod_module_section_free_list(). + * + * Returns: the address of this kmod module section on success or ULONG_MAX + * on failure. + */ +KMOD_EXPORT unsigned long kmod_module_section_get_address(const struct kmod_list *entry) +{ + struct kmod_module_section *section; + + if (entry == NULL) + return (unsigned long)-1; + + section = entry->data; + return section->address; +} + +/** + * kmod_module_section_free_list: + * @list: kmod module section list + * + * Release the resources taken by @list + */ +KMOD_EXPORT void kmod_module_section_free_list(struct kmod_list *list) +{ + while (list) { + kmod_module_section_free(list->data); + list = kmod_list_remove(list); + } +} + +static struct kmod_elf *kmod_module_get_elf(const struct kmod_module *mod) +{ + if (mod->file == NULL) { + const char *path = kmod_module_get_path(mod); + + if (path == NULL) { + errno = ENOENT; + return NULL; + } + + ((struct kmod_module *)mod)->file = kmod_file_open(mod->ctx, + path); + if (mod->file == NULL) + return NULL; + } + + return kmod_file_get_elf(mod->file); +} + +struct kmod_module_info { + char *key; + char value[]; +}; + +static struct kmod_module_info *kmod_module_info_new(const char *key, size_t keylen, const char *value, size_t valuelen) +{ + struct kmod_module_info *info; + + info = malloc(sizeof(struct kmod_module_info) + keylen + valuelen + 2); + if (info == NULL) + return NULL; + + info->key = (char *)info + sizeof(struct kmod_module_info) + + valuelen + 1; + memcpy(info->key, key, keylen); + info->key[keylen] = '\0'; + memcpy(info->value, value, valuelen); + info->value[valuelen] = '\0'; + return info; +} + +static void kmod_module_info_free(struct kmod_module_info *info) +{ + free(info); +} + +static struct kmod_list *kmod_module_info_append(struct kmod_list **list, const char *key, size_t keylen, const char *value, size_t valuelen) +{ + struct kmod_module_info *info; + struct kmod_list *n; + + info = kmod_module_info_new(key, keylen, value, valuelen); + if (info == NULL) + return NULL; + n = kmod_list_append(*list, info); + if (n != NULL) + *list = n; + else + kmod_module_info_free(info); + return n; +} + +static char *kmod_module_hex_to_str(const char *hex, size_t len) +{ + char *str; + int i; + int j; + const size_t line_limit = 20; + size_t str_len; + + str_len = len * 3; /* XX: or XX\0 */ + str_len += ((str_len + line_limit - 1) / line_limit - 1) * 3; /* \n\t\t */ + + str = malloc(str_len); + if (str == NULL) + return NULL; + + for (i = 0, j = 0; i < (int)len; i++) { + j += sprintf(str + j, "%02X", (unsigned char)hex[i]); + if (i < (int)len - 1) { + str[j++] = ':'; + + if ((i + 1) % line_limit == 0) + j += sprintf(str + j, "\n\t\t"); + } + } + return str; +} + +static struct kmod_list *kmod_module_info_append_hex(struct kmod_list **list, + const char *key, + size_t keylen, + const char *value, + size_t valuelen) +{ + char *hex; + struct kmod_list *n; + + if (valuelen > 0) { + /* Display as 01:12:DE:AD:BE:EF:... */ + hex = kmod_module_hex_to_str(value, valuelen); + if (hex == NULL) + goto list_error; + n = kmod_module_info_append(list, key, keylen, hex, strlen(hex)); + free(hex); + if (n == NULL) + goto list_error; + } else { + n = kmod_module_info_append(list, key, keylen, NULL, 0); + if (n == NULL) + goto list_error; + } + + return n; + +list_error: + return NULL; +} + +/** + * kmod_module_get_info: + * @mod: kmod module + * @list: where to return list of module information. Use + * kmod_module_info_get_key() and + * kmod_module_info_get_value(). Release this list with + * kmod_module_info_free_list() + * + * Get a list of entries in ELF section ".modinfo", these contain + * alias, license, depends, vermagic and other keys with respective + * values. If the module is signed (CONFIG_MODULE_SIG), information + * about the module signature is included as well: signer, + * sig_key and sig_hashalgo. + * + * After use, free the @list by calling kmod_module_info_free_list(). + * + * Returns: number of entries in @list on success or < 0 otherwise. + */ +KMOD_EXPORT int kmod_module_get_info(const struct kmod_module *mod, struct kmod_list **list) +{ + struct kmod_elf *elf; + char **strings; + int i, count, ret = -ENOMEM; + struct kmod_signature_info sig_info = {}; + + if (mod == NULL || list == NULL) + return -ENOENT; + + assert(*list == NULL); + + /* remove const: this can only change internal state */ + if (kmod_module_is_builtin((struct kmod_module *)mod)) { + count = kmod_builtin_get_modinfo(mod->ctx, + kmod_module_get_name(mod), + &strings); + if (count < 0) + return count; + } else { + elf = kmod_module_get_elf(mod); + if (elf == NULL) + return -errno; + + count = kmod_elf_get_strings(elf, ".modinfo", &strings); + if (count < 0) + return count; + } + + for (i = 0; i < count; i++) { + struct kmod_list *n; + const char *key, *value; + size_t keylen, valuelen; + + key = strings[i]; + value = strchr(key, '='); + if (value == NULL) { + keylen = strlen(key); + valuelen = 0; + value = key; + } else { + keylen = value - key; + value++; + valuelen = strlen(value); + } + + n = kmod_module_info_append(list, key, keylen, value, valuelen); + if (n == NULL) + goto list_error; + } + + if (mod->file && kmod_module_signature_info(mod->file, &sig_info)) { + struct kmod_list *n; + + n = kmod_module_info_append(list, "sig_id", strlen("sig_id"), + sig_info.id_type, strlen(sig_info.id_type)); + if (n == NULL) + goto list_error; + count++; + + n = kmod_module_info_append(list, "signer", strlen("signer"), + sig_info.signer, sig_info.signer_len); + if (n == NULL) + goto list_error; + count++; + + + n = kmod_module_info_append_hex(list, "sig_key", strlen("sig_key"), + sig_info.key_id, + sig_info.key_id_len); + if (n == NULL) + goto list_error; + count++; + + n = kmod_module_info_append(list, + "sig_hashalgo", strlen("sig_hashalgo"), + sig_info.hash_algo, strlen(sig_info.hash_algo)); + if (n == NULL) + goto list_error; + count++; + + /* + * Omit sig_info.algo for now, as these + * are currently constant. + */ + n = kmod_module_info_append_hex(list, "signature", + strlen("signature"), + sig_info.sig, + sig_info.sig_len); + + if (n == NULL) + goto list_error; + count++; + + } + ret = count; + +list_error: + /* aux structures freed in normal case also */ + kmod_module_signature_info_free(&sig_info); + + if (ret < 0) { + kmod_module_info_free_list(*list); + *list = NULL; + } + free(strings); + return ret; +} + +/** + * kmod_module_info_get_key: + * @entry: a list entry representing a kmod module info + * + * Get the key of a kmod module info. + * + * Returns: the key of this kmod module info on success or NULL on + * failure. The string is owned by the info, do not free it. + */ +KMOD_EXPORT const char *kmod_module_info_get_key(const struct kmod_list *entry) +{ + struct kmod_module_info *info; + + if (entry == NULL) + return NULL; + + info = entry->data; + return info->key; +} + +/** + * kmod_module_info_get_value: + * @entry: a list entry representing a kmod module info + * + * Get the value of a kmod module info. + * + * Returns: the value of this kmod module info on success or NULL on + * failure. The string is owned by the info, do not free it. + */ +KMOD_EXPORT const char *kmod_module_info_get_value(const struct kmod_list *entry) +{ + struct kmod_module_info *info; + + if (entry == NULL) + return NULL; + + info = entry->data; + return info->value; +} + +/** + * kmod_module_info_free_list: + * @list: kmod module info list + * + * Release the resources taken by @list + */ +KMOD_EXPORT void kmod_module_info_free_list(struct kmod_list *list) +{ + while (list) { + kmod_module_info_free(list->data); + list = kmod_list_remove(list); + } +} + +struct kmod_module_version { + uint64_t crc; + char symbol[]; +}; + +static struct kmod_module_version *kmod_module_versions_new(uint64_t crc, const char *symbol) +{ + struct kmod_module_version *mv; + size_t symbollen = strlen(symbol) + 1; + + mv = malloc(sizeof(struct kmod_module_version) + symbollen); + if (mv == NULL) + return NULL; + + mv->crc = crc; + memcpy(mv->symbol, symbol, symbollen); + return mv; +} + +static void kmod_module_version_free(struct kmod_module_version *version) +{ + free(version); +} + +/** + * kmod_module_get_versions: + * @mod: kmod module + * @list: where to return list of module versions. Use + * kmod_module_version_get_symbol() and + * kmod_module_version_get_crc(). Release this list with + * kmod_module_versions_free_list() + * + * Get a list of entries in ELF section "__versions". + * + * After use, free the @list by calling kmod_module_versions_free_list(). + * + * Returns: 0 on success or < 0 otherwise. + */ +KMOD_EXPORT int kmod_module_get_versions(const struct kmod_module *mod, struct kmod_list **list) +{ + struct kmod_elf *elf; + struct kmod_modversion *versions; + int i, count, ret = 0; + + if (mod == NULL || list == NULL) + return -ENOENT; + + assert(*list == NULL); + + elf = kmod_module_get_elf(mod); + if (elf == NULL) + return -errno; + + count = kmod_elf_get_modversions(elf, &versions); + if (count < 0) + return count; + + for (i = 0; i < count; i++) { + struct kmod_module_version *mv; + struct kmod_list *n; + + mv = kmod_module_versions_new(versions[i].crc, versions[i].symbol); + if (mv == NULL) { + ret = -errno; + kmod_module_versions_free_list(*list); + *list = NULL; + goto list_error; + } + + n = kmod_list_append(*list, mv); + if (n != NULL) + *list = n; + else { + kmod_module_version_free(mv); + kmod_module_versions_free_list(*list); + *list = NULL; + ret = -ENOMEM; + goto list_error; + } + } + ret = count; + +list_error: + free(versions); + return ret; +} + +/** + * kmod_module_version_get_symbol: + * @entry: a list entry representing a kmod module versions + * + * Get the symbol of a kmod module versions. + * + * Returns: the symbol of this kmod module versions on success or NULL + * on failure. The string is owned by the versions, do not free it. + */ +KMOD_EXPORT const char *kmod_module_version_get_symbol(const struct kmod_list *entry) +{ + struct kmod_module_version *version; + + if (entry == NULL || entry->data == NULL) + return NULL; + + version = entry->data; + return version->symbol; +} + +/** + * kmod_module_version_get_crc: + * @entry: a list entry representing a kmod module version + * + * Get the crc of a kmod module version. + * + * Returns: the crc of this kmod module version if available, otherwise default to 0. + */ +KMOD_EXPORT uint64_t kmod_module_version_get_crc(const struct kmod_list *entry) +{ + struct kmod_module_version *version; + + if (entry == NULL || entry->data == NULL) + return 0; + + version = entry->data; + return version->crc; +} + +/** + * kmod_module_versions_free_list: + * @list: kmod module versions list + * + * Release the resources taken by @list + */ +KMOD_EXPORT void kmod_module_versions_free_list(struct kmod_list *list) +{ + while (list) { + kmod_module_version_free(list->data); + list = kmod_list_remove(list); + } +} + +struct kmod_module_symbol { + uint64_t crc; + char symbol[]; +}; + +static struct kmod_module_symbol *kmod_module_symbols_new(uint64_t crc, const char *symbol) +{ + struct kmod_module_symbol *mv; + size_t symbollen = strlen(symbol) + 1; + + mv = malloc(sizeof(struct kmod_module_symbol) + symbollen); + if (mv == NULL) + return NULL; + + mv->crc = crc; + memcpy(mv->symbol, symbol, symbollen); + return mv; +} + +static void kmod_module_symbol_free(struct kmod_module_symbol *symbol) +{ + free(symbol); +} + +/** + * kmod_module_get_symbols: + * @mod: kmod module + * @list: where to return list of module symbols. Use + * kmod_module_symbol_get_symbol() and + * kmod_module_symbol_get_crc(). Release this list with + * kmod_module_symbols_free_list() + * + * Get a list of entries in ELF section ".symtab" or "__ksymtab_strings". + * + * After use, free the @list by calling kmod_module_symbols_free_list(). + * + * Returns: 0 on success or < 0 otherwise. + */ +KMOD_EXPORT int kmod_module_get_symbols(const struct kmod_module *mod, struct kmod_list **list) +{ + struct kmod_elf *elf; + struct kmod_modversion *symbols; + int i, count, ret = 0; + + if (mod == NULL || list == NULL) + return -ENOENT; + + assert(*list == NULL); + + elf = kmod_module_get_elf(mod); + if (elf == NULL) + return -errno; + + count = kmod_elf_get_symbols(elf, &symbols); + if (count < 0) + return count; + + for (i = 0; i < count; i++) { + struct kmod_module_symbol *mv; + struct kmod_list *n; + + mv = kmod_module_symbols_new(symbols[i].crc, symbols[i].symbol); + if (mv == NULL) { + ret = -errno; + kmod_module_symbols_free_list(*list); + *list = NULL; + goto list_error; + } + + n = kmod_list_append(*list, mv); + if (n != NULL) + *list = n; + else { + kmod_module_symbol_free(mv); + kmod_module_symbols_free_list(*list); + *list = NULL; + ret = -ENOMEM; + goto list_error; + } + } + ret = count; + +list_error: + free(symbols); + return ret; +} + +/** + * kmod_module_symbol_get_symbol: + * @entry: a list entry representing a kmod module symbols + * + * Get the symbol of a kmod module symbols. + * + * Returns: the symbol of this kmod module symbols on success or NULL + * on failure. The string is owned by the symbols, do not free it. + */ +KMOD_EXPORT const char *kmod_module_symbol_get_symbol(const struct kmod_list *entry) +{ + struct kmod_module_symbol *symbol; + + if (entry == NULL || entry->data == NULL) + return NULL; + + symbol = entry->data; + return symbol->symbol; +} + +/** + * kmod_module_symbol_get_crc: + * @entry: a list entry representing a kmod module symbol + * + * Get the crc of a kmod module symbol. + * + * Returns: the crc of this kmod module symbol if available, otherwise default to 0. + */ +KMOD_EXPORT uint64_t kmod_module_symbol_get_crc(const struct kmod_list *entry) +{ + struct kmod_module_symbol *symbol; + + if (entry == NULL || entry->data == NULL) + return 0; + + symbol = entry->data; + return symbol->crc; +} + +/** + * kmod_module_symbols_free_list: + * @list: kmod module symbols list + * + * Release the resources taken by @list + */ +KMOD_EXPORT void kmod_module_symbols_free_list(struct kmod_list *list) +{ + while (list) { + kmod_module_symbol_free(list->data); + list = kmod_list_remove(list); + } +} + +struct kmod_module_dependency_symbol { + uint64_t crc; + uint8_t bind; + char symbol[]; +}; + +static struct kmod_module_dependency_symbol *kmod_module_dependency_symbols_new(uint64_t crc, uint8_t bind, const char *symbol) +{ + struct kmod_module_dependency_symbol *mv; + size_t symbollen = strlen(symbol) + 1; + + mv = malloc(sizeof(struct kmod_module_dependency_symbol) + symbollen); + if (mv == NULL) + return NULL; + + mv->crc = crc; + mv->bind = bind; + memcpy(mv->symbol, symbol, symbollen); + return mv; +} + +static void kmod_module_dependency_symbol_free(struct kmod_module_dependency_symbol *dependency_symbol) +{ + free(dependency_symbol); +} + +/** + * kmod_module_get_dependency_symbols: + * @mod: kmod module + * @list: where to return list of module dependency_symbols. Use + * kmod_module_dependency_symbol_get_symbol() and + * kmod_module_dependency_symbol_get_crc(). Release this list with + * kmod_module_dependency_symbols_free_list() + * + * Get a list of entries in ELF section ".symtab" or "__ksymtab_strings". + * + * After use, free the @list by calling + * kmod_module_dependency_symbols_free_list(). + * + * Returns: 0 on success or < 0 otherwise. + */ +KMOD_EXPORT int kmod_module_get_dependency_symbols(const struct kmod_module *mod, struct kmod_list **list) +{ + struct kmod_elf *elf; + struct kmod_modversion *symbols; + int i, count, ret = 0; + + if (mod == NULL || list == NULL) + return -ENOENT; + + assert(*list == NULL); + + elf = kmod_module_get_elf(mod); + if (elf == NULL) + return -errno; + + count = kmod_elf_get_dependency_symbols(elf, &symbols); + if (count < 0) + return count; + + for (i = 0; i < count; i++) { + struct kmod_module_dependency_symbol *mv; + struct kmod_list *n; + + mv = kmod_module_dependency_symbols_new(symbols[i].crc, + symbols[i].bind, + symbols[i].symbol); + if (mv == NULL) { + ret = -errno; + kmod_module_dependency_symbols_free_list(*list); + *list = NULL; + goto list_error; + } + + n = kmod_list_append(*list, mv); + if (n != NULL) + *list = n; + else { + kmod_module_dependency_symbol_free(mv); + kmod_module_dependency_symbols_free_list(*list); + *list = NULL; + ret = -ENOMEM; + goto list_error; + } + } + ret = count; + +list_error: + free(symbols); + return ret; +} + +/** + * kmod_module_dependency_symbol_get_symbol: + * @entry: a list entry representing a kmod module dependency_symbols + * + * Get the dependency symbol of a kmod module + * + * Returns: the symbol of this kmod module dependency_symbols on success or NULL + * on failure. The string is owned by the dependency_symbols, do not free it. + */ +KMOD_EXPORT const char *kmod_module_dependency_symbol_get_symbol(const struct kmod_list *entry) +{ + struct kmod_module_dependency_symbol *dependency_symbol; + + if (entry == NULL || entry->data == NULL) + return NULL; + + dependency_symbol = entry->data; + return dependency_symbol->symbol; +} + +/** + * kmod_module_dependency_symbol_get_crc: + * @entry: a list entry representing a kmod module dependency_symbol + * + * Get the crc of a kmod module dependency_symbol. + * + * Returns: the crc of this kmod module dependency_symbol if available, otherwise default to 0. + */ +KMOD_EXPORT uint64_t kmod_module_dependency_symbol_get_crc(const struct kmod_list *entry) +{ + struct kmod_module_dependency_symbol *dependency_symbol; + + if (entry == NULL || entry->data == NULL) + return 0; + + dependency_symbol = entry->data; + return dependency_symbol->crc; +} + +/** + * kmod_module_dependency_symbol_get_bind: + * @entry: a list entry representing a kmod module dependency_symbol + * + * Get the bind type of a kmod module dependency_symbol. + * + * Returns: the bind of this kmod module dependency_symbol on success + * or < 0 on failure. + */ +KMOD_EXPORT int kmod_module_dependency_symbol_get_bind(const struct kmod_list *entry) +{ + struct kmod_module_dependency_symbol *dependency_symbol; + + if (entry == NULL || entry->data == NULL) + return 0; + + dependency_symbol = entry->data; + return dependency_symbol->bind; +} + +/** + * kmod_module_dependency_symbols_free_list: + * @list: kmod module dependency_symbols list + * + * Release the resources taken by @list + */ +KMOD_EXPORT void kmod_module_dependency_symbols_free_list(struct kmod_list *list) +{ + while (list) { + kmod_module_dependency_symbol_free(list->data); + list = kmod_list_remove(list); + } +} diff --git a/libkmod/libkmod-signature.c b/libkmod/libkmod-signature.c new file mode 100644 index 0000000..80f6447 --- /dev/null +++ b/libkmod/libkmod-signature.c @@ -0,0 +1,358 @@ +/* + * libkmod - module signature display + * + * Copyright (C) 2013 Michal Marek, SUSE + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, see . + */ + +#include +#include +#ifdef ENABLE_OPENSSL +#include +#include +#endif +#include +#include +#include + +#include +#include + +#include "libkmod-internal.h" + +/* These types and tables were copied from the 3.7 kernel sources. + * As this is just description of the signature format, it should not be + * considered derived work (so libkmod can use the LGPL license). + */ +enum pkey_algo { + PKEY_ALGO_DSA, + PKEY_ALGO_RSA, + PKEY_ALGO__LAST +}; + +static const char *const pkey_algo[PKEY_ALGO__LAST] = { + [PKEY_ALGO_DSA] = "DSA", + [PKEY_ALGO_RSA] = "RSA", +}; + +enum pkey_hash_algo { + PKEY_HASH_MD4, + PKEY_HASH_MD5, + PKEY_HASH_SHA1, + PKEY_HASH_RIPE_MD_160, + PKEY_HASH_SHA256, + PKEY_HASH_SHA384, + PKEY_HASH_SHA512, + PKEY_HASH_SHA224, + PKEY_HASH_SM3, + PKEY_HASH__LAST +}; + +const char *const pkey_hash_algo[PKEY_HASH__LAST] = { + [PKEY_HASH_MD4] = "md4", + [PKEY_HASH_MD5] = "md5", + [PKEY_HASH_SHA1] = "sha1", + [PKEY_HASH_RIPE_MD_160] = "rmd160", + [PKEY_HASH_SHA256] = "sha256", + [PKEY_HASH_SHA384] = "sha384", + [PKEY_HASH_SHA512] = "sha512", + [PKEY_HASH_SHA224] = "sha224", + [PKEY_HASH_SM3] = "sm3", +}; + +enum pkey_id_type { + PKEY_ID_PGP, /* OpenPGP generated key ID */ + PKEY_ID_X509, /* X.509 arbitrary subjectKeyIdentifier */ + PKEY_ID_PKCS7, /* Signature in PKCS#7 message */ + PKEY_ID_TYPE__LAST +}; + +const char *const pkey_id_type[PKEY_ID_TYPE__LAST] = { + [PKEY_ID_PGP] = "PGP", + [PKEY_ID_X509] = "X509", + [PKEY_ID_PKCS7] = "PKCS#7", +}; + +/* + * Module signature information block. + */ +struct module_signature { + uint8_t algo; /* Public-key crypto algorithm [enum pkey_algo] */ + uint8_t hash; /* Digest algorithm [enum pkey_hash_algo] */ + uint8_t id_type; /* Key identifier type [enum pkey_id_type] */ + uint8_t signer_len; /* Length of signer's name */ + uint8_t key_id_len; /* Length of key identifier */ + uint8_t __pad[3]; + uint32_t sig_len; /* Length of signature data (big endian) */ +}; + +static bool fill_default(const char *mem, off_t size, + const struct module_signature *modsig, size_t sig_len, + struct kmod_signature_info *sig_info) +{ + size -= sig_len; + sig_info->sig = mem + size; + sig_info->sig_len = sig_len; + + size -= modsig->key_id_len; + sig_info->key_id = mem + size; + sig_info->key_id_len = modsig->key_id_len; + + size -= modsig->signer_len; + sig_info->signer = mem + size; + sig_info->signer_len = modsig->signer_len; + + sig_info->algo = pkey_algo[modsig->algo]; + sig_info->hash_algo = pkey_hash_algo[modsig->hash]; + sig_info->id_type = pkey_id_type[modsig->id_type]; + + return true; +} + +#ifdef ENABLE_OPENSSL + +struct pkcs7_private { + PKCS7 *pkcs7; + unsigned char *key_id; + BIGNUM *sno; + char *hash_algo; +}; + +static void pkcs7_free(void *s) +{ + struct kmod_signature_info *si = s; + struct pkcs7_private *pvt = si->private; + + PKCS7_free(pvt->pkcs7); + BN_free(pvt->sno); + free(pvt->key_id); + free(pvt->hash_algo); + free(pvt); + si->private = NULL; +} + +static const char *x509_name_to_str(X509_NAME *name) +{ + int i; + X509_NAME_ENTRY *e; + ASN1_STRING *d; + ASN1_OBJECT *o; + int nid = -1; + const char *str; + + for (i = 0; i < X509_NAME_entry_count(name); i++) { + e = X509_NAME_get_entry(name, i); + o = X509_NAME_ENTRY_get_object(e); + nid = OBJ_obj2nid(o); + if (nid == NID_commonName) + break; + } + if (nid == -1) + return NULL; + + d = X509_NAME_ENTRY_get_data(e); + str = (const char *)ASN1_STRING_get0_data(d); + + return str; +} + +static bool fill_pkcs7(const char *mem, off_t size, + const struct module_signature *modsig, size_t sig_len, + struct kmod_signature_info *sig_info) +{ + const char *pkcs7_raw; + PKCS7 *pkcs7; + STACK_OF(PKCS7_SIGNER_INFO) *sis; + PKCS7_SIGNER_INFO *si; + PKCS7_ISSUER_AND_SERIAL *is; + X509_NAME *issuer; + ASN1_INTEGER *sno; + ASN1_OCTET_STRING *sig; + BIGNUM *sno_bn; + X509_ALGOR *dig_alg; + X509_ALGOR *sig_alg; + const ASN1_OBJECT *o; + BIO *in; + int len; + unsigned char *key_id_str; + struct pkcs7_private *pvt; + const char *issuer_str; + char *hash_algo; + int hash_algo_len; + + size -= sig_len; + pkcs7_raw = mem + size; + + in = BIO_new_mem_buf(pkcs7_raw, sig_len); + + pkcs7 = d2i_PKCS7_bio(in, NULL); + if (pkcs7 == NULL) { + BIO_free(in); + return false; + } + + BIO_free(in); + + sis = PKCS7_get_signer_info(pkcs7); + if (sis == NULL) + goto err; + + si = sk_PKCS7_SIGNER_INFO_value(sis, 0); + if (si == NULL) + goto err; + + is = si->issuer_and_serial; + if (is == NULL) + goto err; + issuer = is->issuer; + sno = is->serial; + + sig = si->enc_digest; + if (sig == NULL) + goto err; + + PKCS7_SIGNER_INFO_get0_algs(si, NULL, &dig_alg, &sig_alg); + + sig_info->sig = (const char *)ASN1_STRING_get0_data(sig); + sig_info->sig_len = ASN1_STRING_length(sig); + + sno_bn = ASN1_INTEGER_to_BN(sno, NULL); + if (sno_bn == NULL) + goto err; + + len = BN_num_bytes(sno_bn); + key_id_str = malloc(len); + if (key_id_str == NULL) + goto err2; + BN_bn2bin(sno_bn, key_id_str); + + sig_info->key_id = (const char *)key_id_str; + sig_info->key_id_len = len; + + issuer_str = x509_name_to_str(issuer); + if (issuer_str != NULL) { + sig_info->signer = issuer_str; + sig_info->signer_len = strlen(issuer_str); + } + + X509_ALGOR_get0(&o, NULL, NULL, dig_alg); + + // Use OBJ_obj2txt to calculate string length + hash_algo_len = OBJ_obj2txt(NULL, 0, o, 0); + if (hash_algo_len < 0) + goto err3; + hash_algo = malloc(hash_algo_len + 1); + if (hash_algo == NULL) + goto err3; + hash_algo_len = OBJ_obj2txt(hash_algo, hash_algo_len + 1, o, 0); + if (hash_algo_len < 0) + goto err4; + + // Assign libcrypto hash algo string or number + sig_info->hash_algo = hash_algo; + + sig_info->id_type = pkey_id_type[modsig->id_type]; + + pvt = malloc(sizeof(*pvt)); + if (pvt == NULL) + goto err4; + + pvt->pkcs7 = pkcs7; + pvt->key_id = key_id_str; + pvt->sno = sno_bn; + pvt->hash_algo = hash_algo; + sig_info->private = pvt; + + sig_info->free = pkcs7_free; + + return true; +err4: + free(hash_algo); +err3: + free(key_id_str); +err2: + BN_free(sno_bn); +err: + PKCS7_free(pkcs7); + return false; +} + +#else /* ENABLE OPENSSL */ + +static bool fill_pkcs7(const char *mem, off_t size, + const struct module_signature *modsig, size_t sig_len, + struct kmod_signature_info *sig_info) +{ + sig_info->hash_algo = "unknown"; + sig_info->id_type = pkey_id_type[modsig->id_type]; + return true; +} + +#endif /* ENABLE OPENSSL */ + +#define SIG_MAGIC "~Module signature appended~\n" + +/* + * A signed module has the following layout: + * + * [ module ] + * [ signer's name ] + * [ key identifier ] + * [ signature data ] + * [ struct module_signature ] + * [ SIG_MAGIC ] + */ + +bool kmod_module_signature_info(const struct kmod_file *file, struct kmod_signature_info *sig_info) +{ + const char *mem; + off_t size; + const struct module_signature *modsig; + size_t sig_len; + + size = kmod_file_get_size(file); + mem = kmod_file_get_contents(file); + if (size < (off_t)strlen(SIG_MAGIC)) + return false; + size -= strlen(SIG_MAGIC); + if (memcmp(SIG_MAGIC, mem + size, strlen(SIG_MAGIC)) != 0) + return false; + + if (size < (off_t)sizeof(struct module_signature)) + return false; + size -= sizeof(struct module_signature); + modsig = (struct module_signature *)(mem + size); + if (modsig->algo >= PKEY_ALGO__LAST || + modsig->hash >= PKEY_HASH__LAST || + modsig->id_type >= PKEY_ID_TYPE__LAST) + return false; + sig_len = be32toh(get_unaligned(&modsig->sig_len)); + if (sig_len == 0 || + size < (int64_t)(modsig->signer_len + modsig->key_id_len + sig_len)) + return false; + + switch (modsig->id_type) { + case PKEY_ID_PKCS7: + return fill_pkcs7(mem, size, modsig, sig_len, sig_info); + default: + return fill_default(mem, size, modsig, sig_len, sig_info); + } +} + +void kmod_module_signature_info_free(struct kmod_signature_info *sig_info) +{ + if (sig_info->free) + sig_info->free(sig_info); +} diff --git a/libkmod/libkmod.c b/libkmod/libkmod.c new file mode 100644 index 0000000..213b424 --- /dev/null +++ b/libkmod/libkmod.c @@ -0,0 +1,1024 @@ +/* + * libkmod - interface to kernel module operations + * + * Copyright (C) 2011-2013 ProFUSION embedded systems + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, see . + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "libkmod.h" +#include "libkmod-internal.h" +#include "libkmod-index.h" + +#define KMOD_HASH_SIZE (256) +#define KMOD_LRU_MAX (128) +#define _KMOD_INDEX_MODULES_SIZE KMOD_INDEX_MODULES_BUILTIN + 1 + +/** + * SECTION:libkmod + * @short_description: libkmod context + * + * The context contains the default values for the library user, + * and is passed to all library operations. + */ + +static const struct { + const char *fn; + const char *prefix; +} index_files[] = { + [KMOD_INDEX_MODULES_DEP] = { .fn = "modules.dep", .prefix = "" }, + [KMOD_INDEX_MODULES_ALIAS] = { .fn = "modules.alias", .prefix = "alias " }, + [KMOD_INDEX_MODULES_SYMBOL] = { .fn = "modules.symbols", .prefix = "alias "}, + [KMOD_INDEX_MODULES_BUILTIN_ALIAS] = { .fn = "modules.builtin.alias", .prefix = "" }, + [KMOD_INDEX_MODULES_BUILTIN] = { .fn = "modules.builtin", .prefix = ""}, +}; + +static const char *const default_config_paths[] = { + SYSCONFDIR "/modprobe.d", + "/run/modprobe.d", + "/usr/local/lib/modprobe.d", + DISTCONFDIR "/modprobe.d", + "/lib/modprobe.d", + NULL +}; + +/** + * kmod_ctx: + * + * Opaque object representing the library context. + */ +struct kmod_ctx { + int refcount; + int log_priority; + void (*log_fn)(void *data, + int priority, const char *file, int line, + const char *fn, const char *format, va_list args); + void *log_data; + const void *userdata; + char *dirname; + enum kmod_file_compression_type kernel_compression; + struct kmod_config *config; + struct hash *modules_by_name; + struct index_mm *indexes[_KMOD_INDEX_MODULES_SIZE]; + unsigned long long indexes_stamp[_KMOD_INDEX_MODULES_SIZE]; +}; + +void kmod_log(const struct kmod_ctx *ctx, + int priority, const char *file, int line, const char *fn, + const char *format, ...) +{ + va_list args; + + if (ctx->log_fn == NULL) + return; + + va_start(args, format); + ctx->log_fn(ctx->log_data, priority, file, line, fn, format, args); + va_end(args); +} + +_printf_format_(6, 0) +static void log_filep(void *data, + int priority, const char *file, int line, + const char *fn, const char *format, va_list args) +{ + FILE *fp = data; +#ifdef ENABLE_DEBUG + char buf[16]; + const char *priname; + switch (priority) { + case LOG_EMERG: + priname = "EMERGENCY"; + break; + case LOG_ALERT: + priname = "ALERT"; + break; + case LOG_CRIT: + priname = "CRITICAL"; + break; + case LOG_ERR: + priname = "ERROR"; + break; + case LOG_WARNING: + priname = "WARNING"; + break; + case LOG_NOTICE: + priname = "NOTICE"; + break; + case LOG_INFO: + priname = "INFO"; + break; + case LOG_DEBUG: + priname = "DEBUG"; + break; + default: + snprintf(buf, sizeof(buf), "L:%d", priority); + priname = buf; + } + fprintf(fp, "libkmod: %s %s:%d %s: ", priname, file, line, fn); +#else + fprintf(fp, "libkmod: %s: ", fn); +#endif + vfprintf(fp, format, args); +} + + +/** + * kmod_get_dirname: + * @ctx: kmod library context + * + * Retrieve the absolute path used for linux modules in this context. The path + * is computed from the arguments to kmod_new(). + */ +KMOD_EXPORT const char *kmod_get_dirname(const struct kmod_ctx *ctx) +{ + return ctx->dirname; +} + +/** + * kmod_get_userdata: + * @ctx: kmod library context + * + * Retrieve stored data pointer from library context. This might be useful + * to access from callbacks. + * + * Returns: stored userdata + */ +KMOD_EXPORT void *kmod_get_userdata(const struct kmod_ctx *ctx) +{ + if (ctx == NULL) + return NULL; + return (void *)ctx->userdata; +} + +/** + * kmod_set_userdata: + * @ctx: kmod library context + * @userdata: data pointer + * + * Store custom @userdata in the library context. + */ +KMOD_EXPORT void kmod_set_userdata(struct kmod_ctx *ctx, const void *userdata) +{ + if (ctx == NULL) + return; + ctx->userdata = userdata; +} + +static int log_priority(const char *priority) +{ + char *endptr; + int prio; + + prio = strtol(priority, &endptr, 10); + if (endptr[0] == '\0' || isspace(endptr[0])) + return prio; + if (strncmp(priority, "err", 3) == 0) + return LOG_ERR; + if (strncmp(priority, "info", 4) == 0) + return LOG_INFO; + if (strncmp(priority, "debug", 5) == 0) + return LOG_DEBUG; + return 0; +} + +static const char *dirname_default_prefix = MODULE_DIRECTORY; + +static char *get_kernel_release(const char *dirname) +{ + struct utsname u; + char *p; + + if (dirname != NULL) + return path_make_absolute_cwd(dirname); + + if (uname(&u) < 0) + return NULL; + + if (asprintf(&p, "%s/%s", dirname_default_prefix, u.release) < 0) + return NULL; + + return p; +} + +static enum kmod_file_compression_type get_kernel_compression(struct kmod_ctx *ctx) +{ + const char *path = "/sys/module/compression"; + char buf[16]; + int fd; + int err; + + fd = open(path, O_RDONLY|O_CLOEXEC); + if (fd < 0) { + /* Not having the file is not an error: kernel may be too old */ + DBG(ctx, "could not open '%s' for reading: %m\n", path); + return KMOD_FILE_COMPRESSION_NONE; + } + + err = read_str_safe(fd, buf, sizeof(buf)); + close(fd); + if (err < 0) { + ERR(ctx, "could not read from '%s': %s\n", + path, strerror(-err)); + return KMOD_FILE_COMPRESSION_NONE; + } + + if (streq(buf, "zstd\n")) + return KMOD_FILE_COMPRESSION_ZSTD; + else if (streq(buf, "xz\n")) + return KMOD_FILE_COMPRESSION_XZ; + else if (streq(buf, "gzip\n")) + return KMOD_FILE_COMPRESSION_ZLIB; + + ERR(ctx, "unknown kernel compression %s", buf); + + return KMOD_FILE_COMPRESSION_NONE; +} + +/** + * kmod_new: + * @dirname: what to consider as linux module's directory, if NULL + * defaults to $MODULE_DIRECTORY/`uname -r`. If it's relative, + * it's treated as relative to the current working directory. + * Otherwise, give an absolute dirname. + * @config_paths: ordered array of paths (directories or files) where + * to load from user-defined configuration parameters such as + * alias, blacklists, commands (install, remove). If NULL + * defaults to /etc/modprobe.d, /run/modprobe.d, + * /usr/local/lib/modprobe.d, DISTCONFDIR/modprobe.d, and + * /lib/modprobe.d. Give an empty vector if configuration should + * not be read. This array must be null terminated. + * + * Create kmod library context. This reads the kmod configuration + * and fills in the default values. + * + * The initial refcount is 1, and needs to be decremented to + * release the resources of the kmod library context. + * + * Returns: a new kmod library context + */ +KMOD_EXPORT struct kmod_ctx *kmod_new(const char *dirname, + const char * const *config_paths) +{ + const char *env; + struct kmod_ctx *ctx; + int err; + + ctx = calloc(1, sizeof(struct kmod_ctx)); + if (!ctx) + return NULL; + + ctx->refcount = 1; + ctx->log_fn = log_filep; + ctx->log_data = stderr; + ctx->log_priority = LOG_ERR; + + ctx->dirname = get_kernel_release(dirname); + + /* environment overwrites config */ + env = secure_getenv("KMOD_LOG"); + if (env != NULL) + kmod_set_log_priority(ctx, log_priority(env)); + + ctx->kernel_compression = get_kernel_compression(ctx); + + if (config_paths == NULL) + config_paths = default_config_paths; + err = kmod_config_new(ctx, &ctx->config, config_paths); + if (err < 0) { + ERR(ctx, "could not create config\n"); + goto fail; + } + + ctx->modules_by_name = hash_new(KMOD_HASH_SIZE, NULL); + if (ctx->modules_by_name == NULL) { + ERR(ctx, "could not create by-name hash\n"); + goto fail; + } + + INFO(ctx, "ctx %p created\n", ctx); + DBG(ctx, "log_priority=%d\n", ctx->log_priority); + + return ctx; + +fail: + free(ctx->modules_by_name); + free(ctx->dirname); + free(ctx); + return NULL; +} + +/** + * kmod_ref: + * @ctx: kmod library context + * + * Take a reference of the kmod library context. + * + * Returns: the passed kmod library context + */ +KMOD_EXPORT struct kmod_ctx *kmod_ref(struct kmod_ctx *ctx) +{ + if (ctx == NULL) + return NULL; + ctx->refcount++; + return ctx; +} + +/** + * kmod_unref: + * @ctx: kmod library context + * + * Drop a reference of the kmod library context. If the refcount + * reaches zero, the resources of the context will be released. + * + * Returns: the passed kmod library context or NULL if it's freed + */ +KMOD_EXPORT struct kmod_ctx *kmod_unref(struct kmod_ctx *ctx) +{ + if (ctx == NULL) + return NULL; + + if (--ctx->refcount > 0) + return ctx; + + INFO(ctx, "context %p released\n", ctx); + + kmod_unload_resources(ctx); + hash_free(ctx->modules_by_name); + free(ctx->dirname); + if (ctx->config) + kmod_config_free(ctx->config); + + free(ctx); + return NULL; +} + +/** + * kmod_set_log_fn: + * @ctx: kmod library context + * @log_fn: function to be called for logging messages + * @data: data to pass to log function + * + * The built-in logging writes to stderr. It can be + * overridden by a custom function, to plug log messages + * into the user's logging functionality. + */ +KMOD_EXPORT void kmod_set_log_fn(struct kmod_ctx *ctx, + void (*log_fn)(void *data, + int priority, const char *file, + int line, const char *fn, + const char *format, va_list args), + const void *data) +{ + if (ctx == NULL) + return; + ctx->log_fn = log_fn; + ctx->log_data = (void *)data; + INFO(ctx, "custom logging function %p registered\n", log_fn); +} + +/** + * kmod_get_log_priority: + * @ctx: kmod library context + * + * Returns: the current logging priority + */ +KMOD_EXPORT int kmod_get_log_priority(const struct kmod_ctx *ctx) +{ + if (ctx == NULL) + return -1; + return ctx->log_priority; +} + +/** + * kmod_set_log_priority: + * @ctx: kmod library context + * @priority: the new logging priority + * + * Set the current logging priority. The value controls which messages + * are logged. + */ +KMOD_EXPORT void kmod_set_log_priority(struct kmod_ctx *ctx, int priority) +{ + if (ctx == NULL) + return; + ctx->log_priority = priority; +} + +struct kmod_module *kmod_pool_get_module(struct kmod_ctx *ctx, + const char *key) +{ + struct kmod_module *mod; + + mod = hash_find(ctx->modules_by_name, key); + + DBG(ctx, "get module name='%s' found=%p\n", key, mod); + + return mod; +} + +void kmod_pool_add_module(struct kmod_ctx *ctx, struct kmod_module *mod, + const char *key) +{ + DBG(ctx, "add %p key='%s'\n", mod, key); + + hash_add(ctx->modules_by_name, key, mod); +} + +void kmod_pool_del_module(struct kmod_ctx *ctx, struct kmod_module *mod, + const char *key) +{ + DBG(ctx, "del %p key='%s'\n", mod, key); + + hash_del(ctx->modules_by_name, key); +} + +static int kmod_lookup_alias_from_alias_bin(struct kmod_ctx *ctx, + enum kmod_index index_number, + const char *name, + struct kmod_list **list) +{ + int err, nmatch = 0; + struct index_file *idx; + struct index_value *realnames, *realname; + + if (ctx->indexes[index_number] != NULL) { + DBG(ctx, "use mmaped index '%s' for name=%s\n", + index_files[index_number].fn, name); + realnames = index_mm_searchwild(ctx->indexes[index_number], + name); + } else { + char fn[PATH_MAX]; + + snprintf(fn, sizeof(fn), "%s/%s.bin", ctx->dirname, + index_files[index_number].fn); + + DBG(ctx, "file=%s name=%s\n", fn, name); + + idx = index_file_open(fn); + if (idx == NULL) + return -ENOSYS; + + realnames = index_searchwild(idx, name); + index_file_close(idx); + } + + for (realname = realnames; realname; realname = realname->next) { + struct kmod_module *mod; + + err = kmod_module_new_from_alias(ctx, name, realname->value, &mod); + if (err < 0) { + ERR(ctx, "Could not create module for alias=%s realname=%s: %s\n", + name, realname->value, strerror(-err)); + goto fail; + } + + *list = kmod_list_append(*list, mod); + nmatch++; + } + + index_values_free(realnames); + return nmatch; + +fail: + *list = kmod_list_remove_n_latest(*list, nmatch); + index_values_free(realnames); + return err; + +} + +int kmod_lookup_alias_from_symbols_file(struct kmod_ctx *ctx, const char *name, + struct kmod_list **list) +{ + if (!strstartswith(name, "symbol:")) + return 0; + + return kmod_lookup_alias_from_alias_bin(ctx, KMOD_INDEX_MODULES_SYMBOL, + name, list); +} + +int kmod_lookup_alias_from_aliases_file(struct kmod_ctx *ctx, const char *name, + struct kmod_list **list) +{ + return kmod_lookup_alias_from_alias_bin(ctx, KMOD_INDEX_MODULES_ALIAS, + name, list); +} + +static char *lookup_builtin_file(struct kmod_ctx *ctx, const char *name) +{ + char *line; + + if (ctx->indexes[KMOD_INDEX_MODULES_BUILTIN]) { + DBG(ctx, "use mmaped index '%s' modname=%s\n", + index_files[KMOD_INDEX_MODULES_BUILTIN].fn, + name); + line = index_mm_search(ctx->indexes[KMOD_INDEX_MODULES_BUILTIN], + name); + } else { + struct index_file *idx; + char fn[PATH_MAX]; + + snprintf(fn, sizeof(fn), "%s/%s.bin", ctx->dirname, + index_files[KMOD_INDEX_MODULES_BUILTIN].fn); + DBG(ctx, "file=%s modname=%s\n", fn, name); + + idx = index_file_open(fn); + if (idx == NULL) { + DBG(ctx, "could not open builtin file '%s'\n", fn); + return NULL; + } + + line = index_search(idx, name); + index_file_close(idx); + } + + return line; +} + +int kmod_lookup_alias_from_kernel_builtin_file(struct kmod_ctx *ctx, + const char *name, + struct kmod_list **list) +{ + struct kmod_list *l; + int ret; + + assert(*list == NULL); + + ret = kmod_lookup_alias_from_alias_bin(ctx, + KMOD_INDEX_MODULES_BUILTIN_ALIAS, + name, list); + + kmod_list_foreach(l, *list) { + struct kmod_module *mod = l->data; + kmod_module_set_builtin(mod, true); + } + + return ret; +} + +int kmod_lookup_alias_from_builtin_file(struct kmod_ctx *ctx, const char *name, + struct kmod_list **list) +{ + char *line; + int err = 0; + + assert(*list == NULL); + + line = lookup_builtin_file(ctx, name); + if (line != NULL) { + struct kmod_module *mod; + + err = kmod_module_new_from_name(ctx, name, &mod); + if (err < 0) { + ERR(ctx, "Could not create module from name %s: %s\n", + name, strerror(-err)); + goto finish; + } + + /* already mark it as builtin since it's being created from + * this index */ + kmod_module_set_builtin(mod, true); + *list = kmod_list_append(*list, mod); + if (*list == NULL) + err = -ENOMEM; + } + +finish: + free(line); + return err; +} + +bool kmod_lookup_alias_is_builtin(struct kmod_ctx *ctx, const char *name) +{ + _cleanup_free_ char *line; + + line = lookup_builtin_file(ctx, name); + + return line != NULL; +} + +char *kmod_search_moddep(struct kmod_ctx *ctx, const char *name) +{ + struct index_file *idx; + char fn[PATH_MAX]; + char *line; + + if (ctx->indexes[KMOD_INDEX_MODULES_DEP]) { + DBG(ctx, "use mmaped index '%s' modname=%s\n", + index_files[KMOD_INDEX_MODULES_DEP].fn, name); + return index_mm_search(ctx->indexes[KMOD_INDEX_MODULES_DEP], + name); + } + + snprintf(fn, sizeof(fn), "%s/%s.bin", ctx->dirname, + index_files[KMOD_INDEX_MODULES_DEP].fn); + + DBG(ctx, "file=%s modname=%s\n", fn, name); + + idx = index_file_open(fn); + if (idx == NULL) { + DBG(ctx, "could not open moddep file '%s'\n", fn); + return NULL; + } + + line = index_search(idx, name); + index_file_close(idx); + + return line; +} + +int kmod_lookup_alias_from_moddep_file(struct kmod_ctx *ctx, const char *name, + struct kmod_list **list) +{ + char *line; + int n = 0; + + /* + * Module names do not contain ':'. Return early if we know it will + * not be found. + */ + if (strchr(name, ':')) + return 0; + + line = kmod_search_moddep(ctx, name); + if (line != NULL) { + struct kmod_module *mod; + + n = kmod_module_new_from_name(ctx, name, &mod); + if (n < 0) { + ERR(ctx, "Could not create module from name %s: %s\n", + name, strerror(-n)); + goto finish; + } + + *list = kmod_list_append(*list, mod); + kmod_module_parse_depline(mod, line); + } + +finish: + free(line); + + return n; +} + +int kmod_lookup_alias_from_config(struct kmod_ctx *ctx, const char *name, + struct kmod_list **list) +{ + struct kmod_config *config = ctx->config; + struct kmod_list *l; + int err, nmatch = 0; + + kmod_list_foreach(l, config->aliases) { + const char *aliasname = kmod_alias_get_name(l); + const char *modname = kmod_alias_get_modname(l); + + if (fnmatch(aliasname, name, 0) == 0) { + struct kmod_module *mod; + + err = kmod_module_new_from_alias(ctx, aliasname, + modname, &mod); + if (err < 0) { + ERR(ctx, "Could not create module for alias=%s modname=%s: %s\n", + name, modname, strerror(-err)); + goto fail; + } + + *list = kmod_list_append(*list, mod); + nmatch++; + } + } + + return nmatch; + +fail: + *list = kmod_list_remove_n_latest(*list, nmatch); + return err; +} + +int kmod_lookup_alias_from_commands(struct kmod_ctx *ctx, const char *name, + struct kmod_list **list) +{ + struct kmod_config *config = ctx->config; + struct kmod_list *l, *node; + int err, nmatch = 0; + + kmod_list_foreach(l, config->install_commands) { + const char *modname = kmod_command_get_modname(l); + + if (streq(modname, name)) { + const char *cmd = kmod_command_get_command(l); + struct kmod_module *mod; + + err = kmod_module_new_from_name(ctx, modname, &mod); + if (err < 0) { + ERR(ctx, "Could not create module from name %s: %s\n", + modname, strerror(-err)); + return err; + } + + node = kmod_list_append(*list, mod); + if (node == NULL) { + ERR(ctx, "out of memory\n"); + return -ENOMEM; + } + + *list = node; + nmatch = 1; + + kmod_module_set_install_commands(mod, cmd); + + /* + * match only the first one, like modprobe from + * module-init-tools does + */ + break; + } + } + + if (nmatch) + return nmatch; + + kmod_list_foreach(l, config->remove_commands) { + const char *modname = kmod_command_get_modname(l); + + if (streq(modname, name)) { + const char *cmd = kmod_command_get_command(l); + struct kmod_module *mod; + + err = kmod_module_new_from_name(ctx, modname, &mod); + if (err < 0) { + ERR(ctx, "Could not create module from name %s: %s\n", + modname, strerror(-err)); + return err; + } + + node = kmod_list_append(*list, mod); + if (node == NULL) { + ERR(ctx, "out of memory\n"); + return -ENOMEM; + } + + *list = node; + nmatch = 1; + + kmod_module_set_remove_commands(mod, cmd); + + /* + * match only the first one, like modprobe from + * module-init-tools does + */ + break; + } + } + + return nmatch; +} + +void kmod_set_modules_visited(struct kmod_ctx *ctx, bool visited) +{ + struct hash_iter iter; + const void *v; + + hash_iter_init(ctx->modules_by_name, &iter); + while (hash_iter_next(&iter, NULL, &v)) + kmod_module_set_visited((struct kmod_module *)v, visited); +} + +void kmod_set_modules_required(struct kmod_ctx *ctx, bool required) +{ + struct hash_iter iter; + const void *v; + + hash_iter_init(ctx->modules_by_name, &iter); + while (hash_iter_next(&iter, NULL, &v)) + kmod_module_set_required((struct kmod_module *)v, required); +} + +static bool is_cache_invalid(const char *path, unsigned long long stamp) +{ + struct stat st; + + if (stat(path, &st) < 0) + return true; + + if (stamp != stat_mstamp(&st)) + return true; + + return false; +} + +/** + * kmod_validate_resources: + * @ctx: kmod library context + * + * Check if indexes and configuration files changed on disk and the current + * context is not valid anymore. + * + * Returns: KMOD_RESOURCES_OK if resources are still valid, + * KMOD_RESOURCES_MUST_RELOAD if it's sufficient to call + * kmod_unload_resources() and kmod_load_resources() or + * KMOD_RESOURCES_MUST_RECREATE if @ctx must be re-created. + */ +KMOD_EXPORT int kmod_validate_resources(struct kmod_ctx *ctx) +{ + struct kmod_list *l; + size_t i; + + if (ctx == NULL || ctx->config == NULL) + return KMOD_RESOURCES_MUST_RECREATE; + + kmod_list_foreach(l, ctx->config->paths) { + struct kmod_config_path *cf = l->data; + + if (is_cache_invalid(cf->path, cf->stamp)) + return KMOD_RESOURCES_MUST_RECREATE; + } + + for (i = 0; i < _KMOD_INDEX_MODULES_SIZE; i++) { + char path[PATH_MAX]; + + if (ctx->indexes[i] == NULL) + continue; + + snprintf(path, sizeof(path), "%s/%s.bin", ctx->dirname, + index_files[i].fn); + + if (is_cache_invalid(path, ctx->indexes_stamp[i])) + return KMOD_RESOURCES_MUST_RELOAD; + } + + return KMOD_RESOURCES_OK; +} + +/** + * kmod_load_resources: + * @ctx: kmod library context + * + * Load indexes and keep them open in @ctx. This way it's faster to lookup + * information within the indexes. If this function is not called before a + * search, the necessary index is always opened and closed. + * + * If user will do more than one or two lookups, insertions, deletions, most + * likely it's good to call this function first. Particularly in a daemon like + * udev that on bootup issues hundreds of calls to lookup the index, calling + * this function will speedup the searches. + * + * Returns: 0 on success or < 0 otherwise. + */ +KMOD_EXPORT int kmod_load_resources(struct kmod_ctx *ctx) +{ + int ret = 0; + size_t i; + + if (ctx == NULL) + return -ENOENT; + + for (i = 0; i < _KMOD_INDEX_MODULES_SIZE; i++) { + char path[PATH_MAX]; + + if (ctx->indexes[i] != NULL) { + INFO(ctx, "Index %s already loaded\n", + index_files[i].fn); + continue; + } + + snprintf(path, sizeof(path), "%s/%s.bin", ctx->dirname, + index_files[i].fn); + ret = index_mm_open(ctx, path, &ctx->indexes_stamp[i], + &ctx->indexes[i]); + + /* + * modules.builtin.alias are considered optional since it's + * recently added and older installations may not have it; + * we allow failing for any reason + */ + if (ret) { + if (i != KMOD_INDEX_MODULES_BUILTIN_ALIAS) + break; + ret = 0; + } + } + + if (ret) + kmod_unload_resources(ctx); + + return ret; +} + +/** + * kmod_unload_resources: + * @ctx: kmod library context + * + * Unload all the indexes. This will free the resources to maintain the index + * open and all subsequent searches will need to open and close the index. + * + * User is free to call kmod_load_resources() and kmod_unload_resources() as + * many times as wanted during the lifecycle of @ctx. For example, if a daemon + * knows that when starting up it will lookup a lot of modules, it could call + * kmod_load_resources() and after the first burst of searches is gone, it + * could free the resources by calling kmod_unload_resources(). + * + * Returns: 0 on success or < 0 otherwise. + */ +KMOD_EXPORT void kmod_unload_resources(struct kmod_ctx *ctx) +{ + size_t i; + + if (ctx == NULL) + return; + + for (i = 0; i < _KMOD_INDEX_MODULES_SIZE; i++) { + if (ctx->indexes[i] != NULL) { + index_mm_close(ctx->indexes[i]); + ctx->indexes[i] = NULL; + ctx->indexes_stamp[i] = 0; + } + } +} + +/** + * kmod_dump_index: + * @ctx: kmod library context + * @type: index to dump, valid indexes are + * KMOD_INDEX_MODULES_DEP: index of module dependencies; + * KMOD_INDEX_MODULES_ALIAS: index of module aliases; + * KMOD_INDEX_MODULES_SYMBOL: index of symbol aliases; + * KMOD_INDEX_MODULES_BUILTIN: index of builtin module. + * @fd: file descriptor to dump index to + * + * Dump index to file descriptor. Note that this function doesn't use stdio.h + * so call fflush() before calling this function to be sure data is written in + * order. + * + * Returns: 0 on success or < 0 otherwise. + */ +KMOD_EXPORT int kmod_dump_index(struct kmod_ctx *ctx, enum kmod_index type, + int fd) +{ + if (ctx == NULL) + return -ENOSYS; + + if (type < 0 || type >= _KMOD_INDEX_MODULES_SIZE) + return -ENOENT; + + if (ctx->indexes[type] != NULL) { + DBG(ctx, "use mmaped index '%s'\n", index_files[type].fn); + index_mm_dump(ctx->indexes[type], fd, + index_files[type].prefix); + } else { + char fn[PATH_MAX]; + struct index_file *idx; + + snprintf(fn, sizeof(fn), "%s/%s.bin", ctx->dirname, + index_files[type].fn); + + DBG(ctx, "file=%s\n", fn); + + idx = index_file_open(fn); + if (idx == NULL) + return -ENOSYS; + + index_dump(idx, fd, index_files[type].prefix); + index_file_close(idx); + } + + return 0; +} + +const struct kmod_config *kmod_get_config(const struct kmod_ctx *ctx) +{ + return ctx->config; +} + +enum kmod_file_compression_type kmod_get_kernel_compression(const struct kmod_ctx *ctx) +{ + return ctx->kernel_compression; +} diff --git a/libkmod/libkmod.h b/libkmod/libkmod.h new file mode 100644 index 0000000..7251aa7 --- /dev/null +++ b/libkmod/libkmod.h @@ -0,0 +1,270 @@ +/* + * libkmod - interface to kernel module operations + * + * Copyright (C) 2011-2013 ProFUSION embedded systems + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, see . + */ + +#pragma once +#ifndef _LIBKMOD_H_ +#define _LIBKMOD_H_ + +#include +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/* + * kmod_ctx + * + * library user context - reads the config and system + * environment, user variables, allows custom logging + */ +struct kmod_ctx; +struct kmod_ctx *kmod_new(const char *dirname, const char * const *config_paths); +struct kmod_ctx *kmod_ref(struct kmod_ctx *ctx); +struct kmod_ctx *kmod_unref(struct kmod_ctx *ctx); +void kmod_set_log_fn(struct kmod_ctx *ctx, + void (*log_fn)(void *log_data, + int priority, const char *file, int line, + const char *fn, const char *format, + va_list args), + const void *data); +int kmod_get_log_priority(const struct kmod_ctx *ctx); +void kmod_set_log_priority(struct kmod_ctx *ctx, int priority); +void *kmod_get_userdata(const struct kmod_ctx *ctx); +void kmod_set_userdata(struct kmod_ctx *ctx, const void *userdata); + +const char *kmod_get_dirname(const struct kmod_ctx *ctx); + +/* + * Management of libkmod's resources + */ +int kmod_load_resources(struct kmod_ctx *ctx); +void kmod_unload_resources(struct kmod_ctx *ctx); + +enum kmod_resources { + KMOD_RESOURCES_OK = 0, + KMOD_RESOURCES_MUST_RELOAD = 1, + KMOD_RESOURCES_MUST_RECREATE = 2, +}; +int kmod_validate_resources(struct kmod_ctx *ctx); + +enum kmod_index { + KMOD_INDEX_MODULES_DEP = 0, + KMOD_INDEX_MODULES_ALIAS, + KMOD_INDEX_MODULES_SYMBOL, + KMOD_INDEX_MODULES_BUILTIN_ALIAS, + KMOD_INDEX_MODULES_BUILTIN, + /* Padding to make sure enum is not mapped to char */ + _KMOD_INDEX_PAD = 1U << 31, +}; +int kmod_dump_index(struct kmod_ctx *ctx, enum kmod_index type, int fd); + +/* + * kmod_list + * + * access to kmod generated lists + */ +struct kmod_list; +struct kmod_list *kmod_list_next(const struct kmod_list *list, + const struct kmod_list *curr); +struct kmod_list *kmod_list_prev(const struct kmod_list *list, + const struct kmod_list *curr); +struct kmod_list *kmod_list_last(const struct kmod_list *list); + +#define kmod_list_foreach(list_entry, first_entry) \ + for (list_entry = first_entry; \ + list_entry != NULL; \ + list_entry = kmod_list_next(first_entry, list_entry)) + +#define kmod_list_foreach_reverse(list_entry, first_entry) \ + for (list_entry = kmod_list_last(first_entry); \ + list_entry != NULL; \ + list_entry = kmod_list_prev(first_entry, list_entry)) + +/* + * kmod_config_iter + * + * access to configuration lists - it allows to get each configuration's + * key/value stored by kmod + */ +struct kmod_config_iter; +struct kmod_config_iter *kmod_config_get_blacklists(const struct kmod_ctx *ctx); +struct kmod_config_iter *kmod_config_get_install_commands(const struct kmod_ctx *ctx); +struct kmod_config_iter *kmod_config_get_remove_commands(const struct kmod_ctx *ctx); +struct kmod_config_iter *kmod_config_get_aliases(const struct kmod_ctx *ctx); +struct kmod_config_iter *kmod_config_get_options(const struct kmod_ctx *ctx); +struct kmod_config_iter *kmod_config_get_softdeps(const struct kmod_ctx *ctx); +const char *kmod_config_iter_get_key(const struct kmod_config_iter *iter); +const char *kmod_config_iter_get_value(const struct kmod_config_iter *iter); +bool kmod_config_iter_next(struct kmod_config_iter *iter); +void kmod_config_iter_free_iter(struct kmod_config_iter *iter); + +/* + * kmod_module + * + * Operate on kernel modules + */ +struct kmod_module; +int kmod_module_new_from_name(struct kmod_ctx *ctx, const char *name, + struct kmod_module **mod); +int kmod_module_new_from_path(struct kmod_ctx *ctx, const char *path, + struct kmod_module **mod); +int kmod_module_new_from_lookup(struct kmod_ctx *ctx, const char *given_alias, + struct kmod_list **list); +int kmod_module_new_from_name_lookup(struct kmod_ctx *ctx, + const char *modname, + struct kmod_module **mod); +int kmod_module_new_from_loaded(struct kmod_ctx *ctx, + struct kmod_list **list); + +struct kmod_module *kmod_module_ref(struct kmod_module *mod); +struct kmod_module *kmod_module_unref(struct kmod_module *mod); +int kmod_module_unref_list(struct kmod_list *list); +struct kmod_module *kmod_module_get_module(const struct kmod_list *entry); + + +/* Removal flags */ +enum kmod_remove { + KMOD_REMOVE_FORCE = O_TRUNC, + KMOD_REMOVE_NOWAIT = O_NONBLOCK, /* always set */ + /* libkmod-only defines, not passed to kernel */ + KMOD_REMOVE_NOLOG = 1, +}; + +/* Insertion flags */ +enum kmod_insert { + KMOD_INSERT_FORCE_VERMAGIC = 0x1, + KMOD_INSERT_FORCE_MODVERSION = 0x2, +}; + +/* Flags to kmod_module_probe_insert_module() */ +enum kmod_probe { + KMOD_PROBE_FORCE_VERMAGIC = 0x00001, + KMOD_PROBE_FORCE_MODVERSION = 0x00002, + KMOD_PROBE_IGNORE_COMMAND = 0x00004, + KMOD_PROBE_IGNORE_LOADED = 0x00008, + KMOD_PROBE_DRY_RUN = 0x00010, + KMOD_PROBE_FAIL_ON_LOADED = 0x00020, + + /* codes below can be used in return value, too */ + KMOD_PROBE_APPLY_BLACKLIST_ALL = 0x10000, + KMOD_PROBE_APPLY_BLACKLIST = 0x20000, + KMOD_PROBE_APPLY_BLACKLIST_ALIAS_ONLY = 0x40000, +}; + +/* Flags to kmod_module_apply_filter() */ +enum kmod_filter { + KMOD_FILTER_BLACKLIST = 0x00001, + KMOD_FILTER_BUILTIN = 0x00002, +}; + +int kmod_module_remove_module(struct kmod_module *mod, unsigned int flags); +int kmod_module_insert_module(struct kmod_module *mod, unsigned int flags, + const char *options); +int kmod_module_probe_insert_module(struct kmod_module *mod, + unsigned int flags, const char *extra_options, + int (*run_install)(struct kmod_module *m, + const char *cmdline, void *data), + const void *data, + void (*print_action)(struct kmod_module *m, bool install, + const char *options)); + + +const char *kmod_module_get_name(const struct kmod_module *mod); +const char *kmod_module_get_path(const struct kmod_module *mod); +const char *kmod_module_get_options(const struct kmod_module *mod); +const char *kmod_module_get_install_commands(const struct kmod_module *mod); +const char *kmod_module_get_remove_commands(const struct kmod_module *mod); +struct kmod_list *kmod_module_get_dependencies(const struct kmod_module *mod); +int kmod_module_get_softdeps(const struct kmod_module *mod, + struct kmod_list **pre, struct kmod_list **post); +int kmod_module_get_filtered_blacklist(const struct kmod_ctx *ctx, + const struct kmod_list *input, + struct kmod_list **output) __attribute__ ((deprecated)); +int kmod_module_apply_filter(const struct kmod_ctx *ctx, + enum kmod_filter filter_type, + const struct kmod_list *input, + struct kmod_list **output); + + + +/* + * Information regarding "live information" from module's state, as returned + * by kernel + */ + +enum kmod_module_initstate { + KMOD_MODULE_BUILTIN = 0, + KMOD_MODULE_LIVE, + KMOD_MODULE_COMING, + KMOD_MODULE_GOING, + /* Padding to make sure enum is not mapped to char */ + _KMOD_MODULE_PAD = 1U << 31, +}; +const char *kmod_module_initstate_str(enum kmod_module_initstate state); +int kmod_module_get_initstate(const struct kmod_module *mod); +int kmod_module_get_refcnt(const struct kmod_module *mod); +struct kmod_list *kmod_module_get_holders(const struct kmod_module *mod); +struct kmod_list *kmod_module_get_sections(const struct kmod_module *mod); +const char *kmod_module_section_get_name(const struct kmod_list *entry); +unsigned long kmod_module_section_get_address(const struct kmod_list *entry); +void kmod_module_section_free_list(struct kmod_list *list); +long kmod_module_get_size(const struct kmod_module *mod); + + + +/* + * Information retrieved from ELF headers and sections + */ + +int kmod_module_get_info(const struct kmod_module *mod, struct kmod_list **list); +const char *kmod_module_info_get_key(const struct kmod_list *entry); +const char *kmod_module_info_get_value(const struct kmod_list *entry); +void kmod_module_info_free_list(struct kmod_list *list); + +int kmod_module_get_versions(const struct kmod_module *mod, struct kmod_list **list); +const char *kmod_module_version_get_symbol(const struct kmod_list *entry); +uint64_t kmod_module_version_get_crc(const struct kmod_list *entry); +void kmod_module_versions_free_list(struct kmod_list *list); + +int kmod_module_get_symbols(const struct kmod_module *mod, struct kmod_list **list); +const char *kmod_module_symbol_get_symbol(const struct kmod_list *entry); +uint64_t kmod_module_symbol_get_crc(const struct kmod_list *entry); +void kmod_module_symbols_free_list(struct kmod_list *list); + +enum kmod_symbol_bind { + KMOD_SYMBOL_NONE = '\0', + KMOD_SYMBOL_LOCAL = 'L', + KMOD_SYMBOL_GLOBAL = 'G', + KMOD_SYMBOL_WEAK = 'W', + KMOD_SYMBOL_UNDEF = 'U' +}; + +int kmod_module_get_dependency_symbols(const struct kmod_module *mod, struct kmod_list **list); +const char *kmod_module_dependency_symbol_get_symbol(const struct kmod_list *entry); +int kmod_module_dependency_symbol_get_bind(const struct kmod_list *entry); +uint64_t kmod_module_dependency_symbol_get_crc(const struct kmod_list *entry); +void kmod_module_dependency_symbols_free_list(struct kmod_list *list); + +#ifdef __cplusplus +} /* extern "C" */ +#endif +#endif diff --git a/libkmod/libkmod.pc.in b/libkmod/libkmod.pc.in new file mode 100644 index 0000000..3acca6a --- /dev/null +++ b/libkmod/libkmod.pc.in @@ -0,0 +1,11 @@ +prefix=@prefix@ +exec_prefix=@exec_prefix@ +libdir=@libdir@ +includedir=@includedir@ + +Name: libkmod +Description: Library to deal with kernel modules +Version: @VERSION@ +Libs: -L${libdir} -lkmod +Libs.private: @libzstd_LIBS@ @liblzma_LIBS@ @zlib_LIBS@ +Cflags: -I${includedir} diff --git a/libkmod/libkmod.sym b/libkmod/libkmod.sym new file mode 100644 index 0000000..0c04fda --- /dev/null +++ b/libkmod/libkmod.sym @@ -0,0 +1,94 @@ +LIBKMOD_5 { +global: + kmod_get_log_priority; + kmod_get_userdata; + kmod_new; + kmod_ref; + kmod_set_log_fn; + kmod_set_log_priority; + kmod_set_userdata; + kmod_unref; + kmod_list_next; + kmod_list_prev; + kmod_list_last; + + kmod_load_resources; + kmod_unload_resources; + kmod_validate_resources; + kmod_config_get_blacklists; + kmod_config_get_install_commands; + kmod_config_get_remove_commands; + kmod_config_get_aliases; + kmod_config_get_options; + kmod_config_get_softdeps; + kmod_config_iter_get_key; + kmod_config_iter_get_value; + kmod_config_iter_next; + kmod_config_iter_free_iter; + kmod_dump_index; + + kmod_module_new_from_name; + kmod_module_new_from_path; + kmod_module_new_from_lookup; + kmod_module_new_from_name_lookup; + kmod_module_new_from_loaded; + kmod_module_ref; + kmod_module_unref; + kmod_module_unref_list; + kmod_module_get_module; + kmod_module_remove_module; + kmod_module_insert_module; + kmod_module_probe_insert_module; + + kmod_module_get_dependencies; + kmod_module_get_softdeps; + kmod_module_get_filtered_blacklist; + + kmod_module_get_name; + kmod_module_get_path; + + kmod_module_initstate_str; + kmod_module_get_initstate; + kmod_module_get_refcnt; + kmod_module_get_sections; + kmod_module_section_free_list; + kmod_module_section_get_name; + kmod_module_section_get_address; + kmod_module_get_holders; + kmod_module_get_size; + + kmod_module_get_options; + kmod_module_get_install_commands; + kmod_module_get_remove_commands; + + kmod_module_get_info; + kmod_module_info_get_key; + kmod_module_info_get_value; + kmod_module_info_free_list; + kmod_module_get_versions; + kmod_module_version_get_symbol; + kmod_module_version_get_crc; + kmod_module_versions_free_list; + kmod_module_get_symbols; + kmod_module_symbol_get_symbol; + kmod_module_symbol_get_crc; + kmod_module_symbols_free_list; + + kmod_module_get_dependency_symbols; + kmod_module_dependency_symbol_get_symbol; + kmod_module_dependency_symbol_get_crc; + kmod_module_dependency_symbol_get_bind; + kmod_module_dependency_symbols_free_list; +local: + *; +}; + +LIBKMOD_6 { +global: + kmod_module_apply_filter; +} LIBKMOD_5; + +LIBKMOD_22 { +global: + kmod_get_dirname; +} LIBKMOD_6; -- cgit v1.2.3