pax_global_header00006660000000000000000000000064147744011020014512gustar00rootroot0000000000000052 comment=3689cbc2349bff05807d2f939146e92eb1bfaea1 erofs-utils-1.8.6/000077500000000000000000000000001477440110200140025ustar00rootroot00000000000000erofs-utils-1.8.6/.github/000077500000000000000000000000001477440110200153425ustar00rootroot00000000000000erofs-utils-1.8.6/.github/ISSUE_TEMPLATE.txt000066400000000000000000000005601477440110200202670ustar00rootroot00000000000000Please **do not** send pull-requests or open new issues on Github. Besides, the current erofs-utils repo is: git://git.kernel.org/pub/scm/linux/kernel/git/xiang/erofs-utils.git Github is not _the place_ for EROFS development, and some mirrors are actually unofficial and not frequently monitored. * Send bug reports and/or feedback to: linux-erofs@lists.ozlabs.org erofs-utils-1.8.6/.github/PULL_REQUEST_TEMPLATE.txt000066400000000000000000000005601477440110200213630ustar00rootroot00000000000000Please **do not** send pull-requests or open new issues on Github. Besides, the current erofs-utils repo is: git://git.kernel.org/pub/scm/linux/kernel/git/xiang/erofs-utils.git Github is not _the place_ for EROFS development, and some mirrors are actually unofficial and not frequently monitored. * Send bug reports and/or feedback to: linux-erofs@lists.ozlabs.org erofs-utils-1.8.6/.gitignore000066400000000000000000000004411477440110200157710ustar00rootroot00000000000000.* *~ *.[ao] *.diff *.la *.lo *.mod.c *.orig *.patch *.rej *.so *.so.dbg *.tar.* # # Generated files # aclocal.m4 autom4te.cache config.* Makefile Makefile.in config/ m4/ configure configure.scan libtool stamp-h stamp-h1 /mkfs/mkfs.erofs /fuse/erofsfuse /dump/dump.erofs /fsck/fsck.erofs erofs-utils-1.8.6/AUTHORS000066400000000000000000000004211477440110200150470ustar00rootroot00000000000000EROFS USERSPACE UTILITIES M: Li Guifu M: Gao Xiang M: Huang Jianan R: Chao Yu R: Miao Xie R: Fang Wei S: Maintained L: linux-erofs@lists.ozlabs.org erofs-utils-1.8.6/COPYING000066400000000000000000000011111477440110200150270ustar00rootroot00000000000000erofs-utils uses two different license patterns: - most liberofs files in `lib` and `include` directories use GPL-2.0+ OR Apache-2.0 dual license; - all other files use GPL-2.0+ license, unless explicitly stated otherwise. Relevant licenses can be found in the LICENSES directory. This model is selected to emphasize that files in `lib` and `include` directory are designed to be included into 3rd-party applications, while all other files, are intended to be used "as is", as part of their intended scenarios, with no intention to support 3rd-party integration use cases. erofs-utils-1.8.6/ChangeLog000066400000000000000000000233021477440110200155540ustar00rootroot00000000000000erofs-utils 1.8.6 * This release includes the following updates: - (mkfs.erofs) Support per-segment reaper for multi-threaded compression; - (mkfs.erofs) Support multi-threaded fragments; - (mkfs.erofs) Support extent-based deduplication for `-Efragments`; - (mkfs.erofs) Optimize space allocation performance; - Several minor bugfixes. -- Gao Xiang Sun, 06 Apr 2025 00:00:00 +0800 erofs-utils 1.8.5 * Another maintenance release includes the following fixes: - (mkfs.erofs) Support `-Efragdedupe=inode` for multithreading; - (dump.erofs) Add `--cat` to show file contents (Juan Hernandez); - (mkfs.erofs) Fix inefficient fragment deduplication; - (fsck.erofs/erofsfuse) Introduce fragment cache; - (fsck.erofs) Preserve S{U,G}ID bits properly on extraction; - (mkfs.erofs, tarerofs) Align non-existent directories with their parents; - Several minor bugfixes. -- Gao Xiang Mon, 10 Feb 2025 00:00:00 +0800 erofs-utils 1.8.4 * Another maintenance release includes the following fixes: - (mkfs.erofs) Fix unusual PAX header handling for tarerofs; - (mkfs.erofs) Fix LIBARCHIVE.xattr decoding; - (mkfs.erofs) Speed up handling of incompressible data; - (mkfs.erofs) Add a `-E^fragdedupe` option to explicitly disable fragment deduplication; - (mkfs.erofs) Fixes around `-Eall-fragments` and `-Ededupe`; - Use external xxhash library if possible. -- Gao Xiang Fri, 03 Jan 2025 00:00:00 +0800 erofs-utils 1.8.3 * Another maintenance release includes the following fixes: - (mkfs.erofs) Fix multi-threaded compression with `-Eall-fragments`; - (mkfs.erofs) Fix large chunk-based image generation; - (mkfs.erofs) Avoid large arrays on the stack (Jianan Huang); - (mkfs.erofs) Fix PAX format parsing in headerball mode (Mike Baynton); - (mkfs.erofs) Several fixes for incremental builds (Hongzhen Luo); - (mkfs.erofs) Fix reproducible builds due to `i_ino` (Jooyung Han); - Use pkg-config for liblz4 configuration; - Get rid of pthread_cancel() dependencies; - (mkfs.erofs) Add `-U ` support; - (mkfs.erofs) Add `--hard-dereference` for NixOS reproducibility (Paul Meyer); - Several minor random fixes. -- Gao Xiang Sat, 14 Dec 2024 00:00:00 +0800 erofs-utils 1.8.2 * Another maintenance release includes the following fixes: - (mkfs.erofs) Fix build on GNU/Hurd (Ahelenia Ziemiańska); - (mkfs.erofs) Fix maximum volume label length (Naoto Yamaguchi); - (mkfs.erofs) Correctly skip unidentified xattrs (Sandeep Dhavale); - (fsck.erofs) Support exporting xattrs optionally (Hongzhen Luo); - (mkfs.erofs) Correctly sort shared xattrs (Sheng Yong); - (mkfs.erofs) Allow pax headers with empty names; - (mkfs.erofs) Add `--sort=none` option for tarballs; - (mkfs.erofs) Fix broken compressed packed inodes (Danny Lin); - Several minor random fixes. -- Gao Xiang Tue, 24 Sep 2024 00:00:00 +0800 erofs-utils 1.8.1 * A quick maintenance release includes the following fixes: - (mkfs.erofs) fix unexpected data truncation of large uncompressed files; - (erofsfuse) fix decompression errors when using libdeflate compressor; - (mkfs.erofs) fix an out-of-bound memory read issue with kite-deflate. -- Gao Xiang Sat, 10 Aug 2024 00:00:00 +0800 erofs-utils 1.8 * This release includes the following updates: - (mkfs.erofs) support multi-threaded compression (Yifan Zhao); - support Intel IAA hardware accelerator with Intel QPL; - add preliminary Zstandard support; - (erofsfuse) use FUSE low-level APIs and support multi-threading (Li Yiyan); - (mkfs.erofs) support tar source without data (Mike Baynton); - (mkfs.erofs) support incremental builds (incomplete, EXPERIMENTAL); - (mkfs.erofs) other build performance improvements; - (erofsfuse) support building erofsfuse as a static library (ComixHe); - various bugfixes and cleanups (Sandeep Dhavale, Noboru Asai, Luke T. Shumaker, Yifan Zhao, Hongzhen Luo and Tianyi Liu). -- Gao Xiang Fri, 09 Aug 2024 00:00:00 +0800 erofs-utils 1.7.1 * A quick maintenance release includes the following fixes: - fix a build issue of cross-compilation with autoconf (Sandeep Dhavale); - fix an invalid error code in lib/tar.c (Erik Sjölund); - fix corrupted directories with hardlinks. -- Gao Xiang Fri, 20 Oct 2023 00:00:00 +0800 erofs-utils 1.7 * This release includes the following updates: - support arbitrary valid block sizes in addition to page size; - (mkfs.erofs) arrange on-disk meta with Breadth-First Traversal instead; - support long xattr name prefixes (Jingbo Xu); - support UUID functionality without libuuid (Norbert Lange); - (mkfs.erofs, experimental) add DEFLATE algorithm support; - (mkfs.erofs, experimental) support building images directly from tarballs; - (dump.erofs) print more superblock fields (Guo Xuenan); - (mkfs.erofs, experimental) introduce preliminary rebuild mode (Jingbo Xu); - various bugfixes and cleanups (Sandeep Dhavale, Guo Xuenan, Yue Hu, Weizhao Ouyang, Kelvin Zhang, Noboru Asai, Yifan Zhao and Li Yiyan); -- Gao Xiang Thu, 21 Sep 2023 00:00:00 +0800 erofs-utils 1.6 * This release includes the following updates: - support fragments by using `-Efragments` (Yue Hu); - support compressed data deduplication by using `-Ededupe` (Ziyang Zhang); - (erofsfuse) support extended attributes (Huang Jianan); - (mkfs.erofs) support multiple algorithms in a single image (Gao Xiang); - (mkfs.erofs) support chunk-based sparse files (Gao Xiang); - (mkfs.erofs) add volume-label setting support (Naoto Yamaguchi); - (mkfs.erofs) add uid/gid offsetting support (Naoto Yamaguchi); - (mkfs.erofs) pack files entirely by using `-Eall-fragments` (Gao Xiang); - various bugfixes and cleanups; -- Gao Xiang Sun, 12 Mar 2023 00:00:00 +0800 erofs-utils 1.5 * This release includes the following updates: - (fsck.erofs) support filesystem extraction (Igor Ostapenko); - support ztailpacking inline feature for compressed files (Yue Hu); - (dump.erofs) support listing directories; - more liberofs APIs (including iterate APIs) (me, Kelvin Zhang); - use mtime to allow more control over the timestamps (David Anderson); - switch to GPL-2.0+ OR Apache-2.0 dual license for liberofs; - various bugfixes and cleanups; -- Gao Xiang Mon, 13 Jun 2022 00:00:00 +0800 erofs-utils 1.4 * This release includes the following updates: - (experimental) introduce preliminary dump.erofs (Wang Qi, Guo Xuenan); - (experimental) introduce preliminary fsck.erofs (Daeho Jeong); - introduce MicroLZMA compression support (thanks to Lasse Collin); - support chunk-based uncompressed files for deduplication; - support multiple devices for multi-blob CAS container images; - (mkfs.erofs, AOSP) add block list support (Yue Hu, David Anderson); - (mkfs.erofs) support per-inode compress pcluster hints (Huang Jianan); - (mkfs.erofs) add "noinline_data" extended option for DAX; - (mkfs.erofs) introduce --quiet option (suggested by nl6720); - complete MacOS build & functionality; - various bugfixes and cleanups; -- Gao Xiang Mon, 22 Nov 2021 00:00:00 +0800 erofs-utils 1.3 * This release includes the following updates: - support new big pcluster feature together with Linux 5.13+; - optimize buffer allocation logic (Hu Weiwen); - optimize build performance for large directories (Hu Weiwen); - add support to override uid / gid (Hu Weiwen); - add support to adjust lz4 history window size (Huang Jianan); - add a manual for erofsfuse; - add support to limit max decompressed extent size; - various bugfixes and cleanups; -- Gao Xiang Tue, 01 Jun 2021 00:00:00 +0800 erofs-utils (1.2.1-1) unstable; urgency=medium * A quick maintenance release includes the following updates: - fix reported build issues due to different configurations; - (mkfs.erofs, AOSP) fix sub-directory prefix for canned fs_config; - update some obsoleted email address; -- Gao Xiang Sun, 10 Jan 2021 00:00:00 +0800 erofs-utils (1.2-1) unstable; urgency=medium * This release includes the following features and bugfixes: - (mkfs.erofs) support selinux file contexts; - (mkfs.erofs) support $SOURCE_DATE_EPOCH; - (mkfs.erofs) support a pre-defined UUID; - (mkfs.erofs) fix random padding for reproducable builds; - (mkfs.erofs) several fixes around hard links; - (mkfs.erofs) minor code cleanups; - (mkfs.erofs, AOSP) support Android fs_config; - (experimental, disabled by default) add erofsfuse approach; -- Gao Xiang Sun, 06 Dec 2020 00:00:00 +0800 erofs-utils (1.1-1) unstable; urgency=low * a maintenance release includes the following updates: - (mkfs.erofs) add a manual for mkfs.erofs; - (mkfs.erofs) add superblock checksum support; - (mkfs.erofs) add filesystem UUID support; - (mkfs.erofs) add exclude files support; - (mkfs.erofs) fix compiling issues under specific conditions; - (mkfs.erofs) minor code cleanups; -- Gao Xiang Tue, 14 Apr 2020 00:00:00 +0800 erofs-utils (1.0-1) unstable; urgency=low * first release with the following new features: - (mkfs.erofs) uncompressed file support; - (mkfs.erofs) uncompressed tail-end packing inline data support; - (mkfs.erofs) lz4 / lz4HC compressed file support; - (mkfs.erofs) special file support; - (mkfs.erofs) inline / shared xattrs support; - (mkfs.erofs) Posix ACL support; -- Gao Xiang Thu, 24 Oct 2019 00:00:00 +0800 erofs-utils-1.8.6/LICENSES/000077500000000000000000000000001477440110200152075ustar00rootroot00000000000000erofs-utils-1.8.6/LICENSES/Apache-2.0000066400000000000000000000234241477440110200166150ustar00rootroot00000000000000Valid-License-Identifier: Apache-2.0 SPDX-URL: https://spdx.org/licenses/Apache-2.0.html Usage-Guide: The Apache-2.0 may only be used for dual-licensed files where the other license is GPL2 compatible. If you end up using this it MUST be used together with a GPL2 compatible license using "OR". To use the Apache License version 2.0 put the following SPDX tag/value pair into a comment according to the placement guidelines in the licensing rules documentation: SPDX-License-Identifier: Apache-2.0 License-Text: Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: a. You must give any other recipients of the Work or Derivative Works a copy of this License; and b. You must cause any modified files to carry prominent notices stating that You changed the files; and c. You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and d. If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS erofs-utils-1.8.6/LICENSES/GPL-2.0000066400000000000000000000444441477440110200160630ustar00rootroot00000000000000Valid-License-Identifier: GPL-2.0 Valid-License-Identifier: GPL-2.0-only Valid-License-Identifier: GPL-2.0+ Valid-License-Identifier: GPL-2.0-or-later SPDX-URL: https://spdx.org/licenses/GPL-2.0.html Usage-Guide: To use this license in source code, put one of the following SPDX tag/value pairs into a comment according to the placement guidelines in the licensing rules documentation. For 'GNU General Public License (GPL) version 2 only' use: SPDX-License-Identifier: GPL-2.0 or SPDX-License-Identifier: GPL-2.0-only For 'GNU General Public License (GPL) version 2 or any later version' use: SPDX-License-Identifier: GPL-2.0+ or SPDX-License-Identifier: GPL-2.0-or-later License-Text: GNU GENERAL PUBLIC LICENSE Version 2, June 1991 Copyright (C) 1989, 1991 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. Preamble The licenses for most software are designed to take away your freedom to share and change it. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change free software--to make sure the software is free for all its users. This General Public License applies to most of the Free Software Foundation's software and to any other program whose authors commit to using it. (Some other Free Software Foundation software is covered by the GNU Library General Public License instead.) You can apply it to your programs, too. When we speak of free software, we are referring to freedom, 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 or use pieces of it in new free programs; and that you know you can do these things. To protect your rights, we need to make restrictions that forbid anyone to deny you these rights or to ask you to surrender the rights. These restrictions translate to certain responsibilities for you if you distribute copies of the software, or if you modify it. For example, if you distribute copies of such a program, whether gratis or for a fee, you must give the recipients all the rights that you have. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights. We protect your rights with two steps: (1) copyright the software, and (2) offer you this license which gives you legal permission to copy, distribute and/or modify the software. Also, for each author's protection and ours, we want to make certain that everyone understands that there is no warranty for this free software. If the software is modified by someone else and passed on, we want its recipients to know that what they have is not the original, so that any problems introduced by others will not reflect on the original authors' reputations. Finally, any free program is threatened constantly by software patents. We wish to avoid the danger that redistributors of a free program will individually obtain patent licenses, in effect making the program proprietary. To prevent this, we have made it clear that any patent must be licensed for everyone's free use or not licensed at all. The precise terms and conditions for copying, distribution and modification follow. GNU GENERAL PUBLIC LICENSE TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 0. This License applies to any program or other work which contains a notice placed by the copyright holder saying it may be distributed under the terms of this General Public License. The "Program", below, refers to any such program or work, and a "work based on the Program" means either the Program or any derivative work under copyright law: that is to say, a work containing the Program or a portion of it, either verbatim or with modifications and/or translated into another language. (Hereinafter, translation is included without limitation in the term "modification".) Each licensee is addressed as "you". Activities other than copying, distribution and modification are not covered by this License; they are outside its scope. The act of running the Program is not restricted, and the output from the Program is covered only if its contents constitute a work based on the Program (independent of having been made by running the Program). Whether that is true depends on what the Program does. 1. You may copy and distribute verbatim copies of the Program's 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 give any other recipients of the Program a copy of this License along with the Program. 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 Program or any portion of it, thus forming a work based on the Program, 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) You must cause the modified files to carry prominent notices stating that you changed the files and the date of any change. b) You must cause any work that you distribute or publish, that in whole or in part contains or is derived from the Program or any part thereof, to be licensed as a whole at no charge to all third parties under the terms of this License. c) If the modified program normally reads commands interactively when run, you must cause it, when started running for such interactive use in the most ordinary way, to print or display an announcement including an appropriate copyright notice and a notice that there is no warranty (or else, saying that you provide a warranty) and that users may redistribute the program under these conditions, and telling the user how to view a copy of this License. (Exception: if the Program itself is interactive but does not normally print such an announcement, your work based on the Program is not required to print an announcement.) These requirements apply to the modified work as a whole. If identifiable sections of that work are not derived from the Program, 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 Program, 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 Program. In addition, mere aggregation of another work not based on the Program with the Program (or with a work based on the Program) on a volume of a storage or distribution medium does not bring the other work under the scope of this License. 3. You may copy and distribute the Program (or a work based on it, under Section 2) in object code or executable form under the terms of Sections 1 and 2 above provided that you also do one of the following: a) 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; or, b) Accompany it with a written offer, valid for at least three years, to give any third party, for a charge no more than your cost of physically performing source distribution, a complete machine-readable copy of the corresponding source code, to be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, c) Accompany it with the information you received as to the offer to distribute corresponding source code. (This alternative is allowed only for noncommercial distribution and only if you received the program in object code or executable form with such an offer, in accord with Subsection b above.) The source code for a work means the preferred form of the work for making modifications to it. For an executable work, 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 executable. However, as a special exception, the source code 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. If distribution of executable or 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 counts as distribution of the source code, even though third parties are not compelled to copy the source along with the object code. 4. You may not copy, modify, sublicense, or distribute the Program except as expressly provided under this License. Any attempt otherwise to copy, modify, sublicense or distribute the Program 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. 5. 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 Program or its derivative works. These actions are prohibited by law if you do not accept this License. Therefore, by modifying or distributing the Program (or any work based on the Program), you indicate your acceptance of this License to do so, and all its terms and conditions for copying, distributing or modifying the Program or works based on it. 6. Each time you redistribute the Program (or any work based on the Program), the recipient automatically receives a license from the original licensor to copy, distribute or modify the Program 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 to this License. 7. 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 Program at all. For example, if a patent license would not permit royalty-free redistribution of the Program 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 Program. 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. 8. If the distribution and/or use of the Program is restricted in certain countries either by patents or by copyrighted interfaces, the original copyright holder who places the Program 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. 9. The Free Software Foundation may publish revised and/or new versions of the 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 Program 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 Program does not specify a version number of this License, you may choose any version ever published by the Free Software Foundation. 10. If you wish to incorporate parts of the Program into other free programs whose distribution conditions are different, 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 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "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 PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 12. 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 PROGRAM 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 PROGRAM (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 PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), 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 Programs If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms. To do so, attach the following notices to the program. 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 program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program 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 General Public License for more details. You should have received a copy of the GNU General Public License along with this program; 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. If the program is interactive, make it output a short notice like this when it starts in an interactive mode: Gnomovision version 69, Copyright (C) year name of author Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details. The hypothetical commands `show w' and `show c' should show the appropriate parts of the General Public License. Of course, the commands you use may be called something other than `show w' and `show c'; they could even be mouse-clicks or menu items--whatever suits your program. You should also get your employer (if you work as a programmer) or your school, if any, to sign a "copyright disclaimer" for the program, if necessary. Here is a sample; alter the names: Yoyodyne, Inc., hereby disclaims all copyright interest in the program `Gnomovision' (which makes passes at compilers) written by James Hacker. , 1 April 1989 Ty Coon, President of Vice This General Public License does not permit incorporating your program into proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do, use the GNU Library General Public License instead of this License. erofs-utils-1.8.6/Makefile.am000066400000000000000000000002271477440110200160370ustar00rootroot00000000000000# SPDX-License-Identifier: GPL-2.0+ ACLOCAL_AMFLAGS = -I m4 SUBDIRS = man lib mkfs dump fsck if ENABLE_FUSE SUBDIRS += fuse endif SUBDIRS += contrib erofs-utils-1.8.6/README000066400000000000000000000231771477440110200146740ustar00rootroot00000000000000erofs-utils =========== Userspace tools for EROFS filesystem, currently including: mkfs.erofs filesystem formatter erofsfuse FUSE daemon alternative dump.erofs filesystem analyzer fsck.erofs filesystem compatibility & consistency checker as well as extractor EROFS filesystem overview ------------------------- EROFS filesystem stands for Enhanced Read-Only File System. It aims to form a generic read-only filesystem solution for various read-only use cases instead of just focusing on storage space saving without considering any side effects of runtime performance. Typically EROFS could be considered in the following use scenarios: - Firmwares in performance-sensitive systems, such as system partitions of Android smartphones; - Mountable immutable images such as container images for effective metadata & data access compared with tar, cpio or other local filesystems (e.g. ext4, XFS, btrfs, etc.) - FSDAX-enabled rootfs for secure containers (Linux 5.15+); - Live CDs which need a set of files with another high-performance algorithm to optimize startup time; others files for archival purposes only are not needed; - and more. Note that all EROFS metadata is uncompressed by design, so that you could take EROFS as a drop-in read-only replacement of ext4, XFS, btrfs, etc. without any compression-based dependencies and EROFS can bring more effective filesystem accesses to users with reduced metadata. For more details of EROFS filesystem itself, please refer to: https://www.kernel.org/doc/html/next/filesystems/erofs.html For more details on how to build erofs-utils, see `docs/INSTALL.md`. For more details about filesystem performance, see `docs/PERFORMANCE.md`. mkfs.erofs ---------- Two main kinds of EROFS images can be generated: (un)compressed images. - For uncompressed images, there will be no compressed files in these images. However, an EROFS image can contain files which consist of various aligned data blocks and then a tail that is stored inline in order to compact images [1]. - For compressed images, it will try to use the given algorithms first for each regular file and see if storage space can be saved with compression. If not, it will fall back to an uncompressed file. Note that EROFS supports per-file compression configuration, proper configuration options need to be enabled to parse compressed files by the Linux kernel. How to generate EROFS images ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Compression algorithms could be specified with the command-line option `-z` to build a compressed EROFS image from a local directory: $ mkfs.erofs -zlz4hc foo.erofs.img foo/ Supported algorithms by the Linux kernel: - LZ4 (Linux 5.3+); - LZMA (Linux 5.16+); - DEFLATE (Linux 6.6+); - Zstandard (Linux 6.10+). Alternatively, generate an uncompressed EROFS from a local directory: $ mkfs.erofs foo.erofs.img foo/ Additionally, you can specify a higher compression level to get a (slightly) smaller image than the default level: $ mkfs.erofs -zlz4hc,12 foo.erofs.img foo/ Multi-threaded support can be explicitly enabled with the ./configure option `--enable-multithreading`; otherwise, single-threaded compression will be used for now. It may take more time on multiprocessor platforms if multi-threaded support is not enabled. Currently, both `-Efragments` (not `-Eall-fragments`) and `-Ededupe` don't support multi-threading due to time limitations. Reproducible builds ~~~~~~~~~~~~~~~~~~~ Reproducible builds are typically used for verification and security, ensuring the same binaries/distributions to be reproduced in a deterministic way. Images generated by the same version of `mkfs.erofs` will be identical to previous runs if the same input is specified, and the same options are used. Specifically, variable timestamps and filesystem UUIDs can result in unreproducible EROFS images. `-T` and `-U` can be used to fix them. How to generate EROFS big pcluster images (Linux 5.13+) ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ By default, EROFS formatter compresses data into separate one-block (e.g. 4KiB) filesystem physical clusters for outstanding random read performance. In other words, each EROFS filesystem block can be independently decompressed. However, other similar filesystems typically compress data into "blocks" of 128KiB or more for much smaller images. Users may prefer smaller images for archiving purposes, even if random performance is compromised with those configurations, and even worse when using 4KiB blocks. In order to fulfill users' needs, big plusters has been introduced since Linux 5.13, in which each physical clusters will be more than one blocks. Specifically, `-C` is used to specify the maximum size of each pcluster in bytes: $ mkfs.erofs -zlz4hc -C65536 foo.erofs.img foo/ Thus, in this case, pcluster sizes can be up to 64KiB. Note that large pcluster size can degrade random performance (though it may improve sequential read performance for typical storage devices), so please evaluate carefully in advance. Alternatively, you can make per-(sub)file compression strategies according to file access patterns if needed. How to generate EROFS images with multiple algorithms ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ It's possible to generate an EROFS image with files in different algorithms due to various purposes. For example, LZMA for archival purposes and LZ4 for runtime purposes. In order to use alternative algorithms, just specify two or more compressing configurations together separated by ':' like below: -zlzma:lz4hc,12:lzma,9 -C32768 Although mkfs still choose the first one by default, you could try to write a compress-hints file like below: 4096 1 .*\.so$ 32768 2 .*\.txt$ 4096 sbin/.*$ 16384 0 .* and specify with `--compress-hints=` so that ".so" files will use "lz4hc,12" compression with 4k pclusters, ".txt" files will use "lzma,9" compression with 32k pclusters, files under "/sbin" will use the default "lzma" compression with 4k plusters and other files will use "lzma" compression with 16k pclusters. Note that the largest pcluster size should be specified with the "-C" option (here 32k pcluster size), otherwise all larger pclusters will be limited. How to generate well-compressed EROFS images ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Even if EROFS is not designed for such purposes in the beginning, it could still produce some smaller images (not always) compared to other approaches with better performance (see `docs/PERFORMANCE.md`). In order to build well-compressed EROFS images, try the following options: -C1048576 (5.13+) -Eztailpacking (5.16+) -Efragments / -Eall-fragments ( 6.1+); -Ededupe ( 6.1+). Also EROFS uses lz4hc level 9 by default, whereas some other approaches use lz4hc level 12 by default. So please explicitly specify `-zlz4hc,12 ` for comparison purposes. How to generate legacy EROFS images (Linux 4.19+) ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Decompression inplace and compacted indexes have been introduced in Linux v5.3, which are not forward-compatible with older kernels. In order to generate _legacy_ EROFS images for old kernels, consider adding "-E legacy-compress" to the command line, e.g. $ mkfs.erofs -E legacy-compress -zlz4hc foo.erofs.img foo/ For Linux kernel >= 5.3, legacy EROFS images are _NOT recommended_ due to runtime performance loss compared with non-legacy images. Obsoleted erofs.mkfs ~~~~~~~~~~~~~~~~~~~~ There is an original erofs.mkfs version developed by Li Guifu, which was replaced by the new erofs-utils implementation. git://git.kernel.org/pub/scm/linux/kernel/git/xiang/erofs-utils.git -b obsoleted_mkfs PLEASE NOTE: This version is highly _NOT recommended_ now. erofsfuse --------- erofsfuse is introduced to support EROFS format for various platforms (including older linux kernels) and new on-disk features iteration. It can also be used as an unpacking tool for unprivileged users. It supports fixed-sized output decompression *without* any in-place I/O or in-place decompression optimization. Also like the other FUSE implementations, it suffers from most common performance issues (e.g. significant I/O overhead, double caching, etc.) Therefore, NEVER use it if performance is the top concern. How to mount an EROFS image with erofsfuse ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ As the other FUSE implementations, it's quite easy to mount by using erofsfuse, e.g.: $ erofsfuse foo.erofs.img foo/ Alternatively, to make it run in foreground (with debugging level 3): $ erofsfuse -f --dbglevel=3 foo.erofs.img foo/ To debug erofsfuse (also automatically run in foreground): $ erofsfuse -d foo.erofs.img foo/ To unmount an erofsfuse mountpoint as a non-root user: $ fusermount -u foo/ dump.erofs and fsck.erofs ------------------------- dump.erofs and fsck.erofs are used to analyze, check, and extract EROFS filesystems. Note that extended attributes and ACLs are still unsupported when extracting images with fsck.erofs. Note that fragment extraction with fsck.erofs could be slow now and it needs to be optimized later. If you are interested, contribution is, as always, welcome. Contribution ------------ erofs-utils is a part of EROFS filesystem project, which is completely community-driven open source software. If you have interest in EROFS, feel free to send feedback and/or patches to: linux-erofs mailing list Comments -------- [1] According to the EROFS on-disk format, the tail blocks of files could be inlined aggressively with their metadata (called tail-packing) in order to minimize the extra I/Os and the storage space. erofs-utils-1.8.6/VERSION000066400000000000000000000000211477440110200150430ustar00rootroot000000000000001.8.6 2025-04-06 erofs-utils-1.8.6/autogen.sh000077500000000000000000000002761477440110200160100ustar00rootroot00000000000000#!/bin/sh # SPDX-License-Identifier: GPL-2.0+ aclocal && \ autoheader && \ autoconf && \ case `uname` in Darwin*) glibtoolize --copy ;; \ *) libtoolize --copy ;; esac && \ automake -a -c erofs-utils-1.8.6/configure.ac000066400000000000000000000465741477440110200163100ustar00rootroot00000000000000# -*- Autoconf -*- # Process this file with autoconf to produce a configure script. AC_PREREQ([2.69]) m4_define([erofs_utils_version], m4_esyscmd_s([scripts/get-version-number])) m4_define([erofs_utils_date], m4_esyscmd([sed -n '2p' VERSION | tr -d '\n'])) AC_INIT([erofs-utils], [erofs_utils_version], [linux-erofs@lists.ozlabs.org]) AC_CONFIG_SRCDIR([config.h.in]) AC_CONFIG_HEADERS([config.h]) AC_CONFIG_MACRO_DIR([m4]) AC_CONFIG_AUX_DIR(config) AC_CANONICAL_TARGET build_linux=no case "${target_os}" in linux*) build_linux=yes ;; esac # OS-specific treatment AM_CONDITIONAL([OS_LINUX], [test "$build_linux" = "yes"]) AM_INIT_AUTOMAKE([foreign -Wall]) # Checks for programs. AM_PROG_AR AC_PROG_CC AC_PROG_INSTALL LT_INIT # Test presence of pkg-config AC_MSG_CHECKING([pkg-config m4 macros]) if test m4_ifdef([PKG_CHECK_MODULES], [yes], [no]) = "yes"; then AC_MSG_RESULT([yes]); else AC_MSG_RESULT([no]); AC_MSG_ERROR([pkg-config is required. See pkg-config.freedesktop.org]) fi dnl Check if the flag is supported by compiler dnl CC_CHECK_CFLAGS_SILENT([FLAG], [ACTION-IF-FOUND],[ACTION-IF-NOT-FOUND]) AC_DEFUN([CC_CHECK_CFLAGS_SILENT], [ AC_CACHE_VAL(AS_TR_SH([cc_cv_cflags_$1]), [ac_save_CFLAGS="$CFLAGS" CFLAGS="$CFLAGS $1" AC_LINK_IFELSE([AC_LANG_SOURCE([int main() { return 0; }])], [eval "AS_TR_SH([cc_cv_cflags_$1])='yes'"], [eval "AS_TR_SH([cc_cv_cflags_$1])='no'"]) CFLAGS="$ac_save_CFLAGS" ]) AS_IF([eval test x$]AS_TR_SH([cc_cv_cflags_$1])[ = xyes], [$2], [$3]) ]) dnl Check if the flag is supported by compiler (cacheable) dnl CC_CHECK_CFLAG([FLAG], [ACTION-IF-FOUND],[ACTION-IF-NOT-FOUND]) AC_DEFUN([CC_CHECK_CFLAG], [ AC_CACHE_CHECK([if $CC supports $1 flag], AS_TR_SH([cc_cv_cflags_$1]), CC_CHECK_CFLAGS_SILENT([$1]) dnl Don't execute actions here! ) AS_IF([eval test x$]AS_TR_SH([cc_cv_cflags_$1])[ = xyes], [$2], [$3]) ]) dnl CC_CHECK_CFLAGS([FLAG1 FLAG2], [action-if-found], [action-if-not]) AC_DEFUN([CC_CHECK_CFLAGS], [ for flag in $1; do CC_CHECK_CFLAG($flag, [$2], [$3]) done ]) dnl EROFS_UTILS_PARSE_DIRECTORY dnl Input: $1 = a string to a relative or absolute directory dnl Output: $2 = the variable to set with the absolute directory AC_DEFUN([EROFS_UTILS_PARSE_DIRECTORY], [ dnl Check if argument is a directory if test -d $1 ; then dnl Get the absolute path of the directory dnl in case of relative directory. dnl If realpath is not a valid command, dnl an error is produced and we keep the given path. local_tmp=`realpath $1 2>/dev/null` if test "$local_tmp" != "" ; then if test -d "$local_tmp" ; then $2="$local_tmp" else $2=$1 fi else $2=$1 fi dnl Check for space in the directory if test `echo $1|cut -d' ' -f1` != $1 ; then AC_MSG_ERROR($1 directory shall not contain any space.) fi else AC_MSG_ERROR($1 shall be a valid directory) fi ]) AC_ARG_VAR([MAX_BLOCK_SIZE], [The maximum block size which erofs-utils supports]) AC_MSG_CHECKING([whether to enable multi-threading support]) AC_ARG_ENABLE([multithreading], AS_HELP_STRING([--enable-multithreading], [enable multi-threading support (EXPERIMENTAL) @<:@default=no@:>@]), [enable_multithreading="$enableval"], [enable_multithreading="no"]) AC_MSG_RESULT([$enable_multithreading]) AC_ARG_ENABLE([debug], [AS_HELP_STRING([--enable-debug], [enable debugging mode @<:@default=no@:>@])], [enable_debug="$enableval"], [enable_debug="no"]) AC_ARG_ENABLE([werror], [AS_HELP_STRING([--enable-werror], [enable -Werror @<:@default=no@:>@])], [enable_werror="$enableval"], [enable_werror="no"]) AC_ARG_ENABLE([fuzzing], [AS_HELP_STRING([--enable-fuzzing], [set up fuzzing mode @<:@default=no@:>@])], [enable_fuzzing="$enableval"], [enable_fuzzing="no"]) AC_ARG_ENABLE(lz4, [AS_HELP_STRING([--disable-lz4], [disable LZ4 compression support @<:@default=auto@:>@])], [enable_lz4="$enableval"]) AC_ARG_ENABLE(lzma, [AS_HELP_STRING([--disable-lzma], [disable LZMA compression support @<:@default=auto@:>@])], [enable_lzma="$enableval"]) AC_ARG_WITH(zlib, [AS_HELP_STRING([--without-zlib], [Ignore presence of zlib inflate support @<:@default=auto@:>@])]) AC_ARG_WITH(libdeflate, [AS_HELP_STRING([--with-libdeflate], [Enable and build with libdeflate inflate support @<:@default=disabled@:>@])], [], [with_libdeflate="no"]) AC_ARG_WITH(libzstd, [AS_HELP_STRING([--with-libzstd], [Enable and build with of libzstd support @<:@default=auto@:>@])]) AC_ARG_WITH(qpl, [AS_HELP_STRING([--with-qpl], [Enable and build with Intel QPL support @<:@default=disabled@:>@])], [], [with_qpl="no"]) AC_ARG_WITH(xxhash, [AS_HELP_STRING([--with-xxhash], [Enable and build with libxxhash support @<:@default=auto@:>@])]) AC_ARG_ENABLE(fuse, [AS_HELP_STRING([--enable-fuse], [enable erofsfuse @<:@default=no@:>@])], [enable_fuse="$enableval"], [enable_fuse="no"]) AC_ARG_ENABLE([static-fuse], [AS_HELP_STRING([--enable-static-fuse], [build erofsfuse as a static library @<:@default=no@:>@])], [enable_static_fuse="$enableval"], [enable_static_fuse="no"]) AC_ARG_WITH(uuid, [AS_HELP_STRING([--without-uuid], [Ignore presence of libuuid and disable uuid support @<:@default=enabled@:>@])]) AC_ARG_WITH(selinux, [AS_HELP_STRING([--with-selinux], [enable and build with selinux support @<:@default=no@:>@])], [case "$with_selinux" in yes|no) ;; *) AC_MSG_ERROR([invalid argument to --with-selinux]) ;; esac], [with_selinux=no]) # Checks for libraries. # Checks for header files. AC_CHECK_HEADERS(m4_flatten([ dirent.h execinfo.h fcntl.h getopt.h inttypes.h linux/aufs_type.h linux/falloc.h linux/fs.h linux/types.h linux/xattr.h limits.h stddef.h stdint.h stdlib.h string.h sys/ioctl.h sys/mman.h sys/random.h sys/sendfile.h sys/stat.h sys/statfs.h sys/sysmacros.h sys/time.h unistd.h ])) AC_HEADER_TIOCGWINSZ # Checks for typedefs, structures, and compiler characteristics. AC_C_INLINE AC_TYPE_INT64_T AC_TYPE_SIZE_T AC_TYPE_SSIZE_T AC_CHECK_MEMBERS([struct stat.st_rdev]) AC_CHECK_MEMBERS([struct stat.st_atim]) AC_CHECK_MEMBERS([struct stat.st_atimensec]) AC_TYPE_UINT64_T # # Check to see if llseek() is declared in unistd.h. On some libc's # it is, and on others it isn't..... Thank you glibc developers.... # AC_CHECK_DECL(llseek, [AC_DEFINE(HAVE_LLSEEK_PROTOTYPE, 1, [Define to 1 if llseek declared in unistd.h])],, [#include ]) # # Check to see if lseek64() is declared in unistd.h. Glibc's header files # are so convoluted that I can't tell whether it will always be defined, # and if it isn't defined while lseek64 is defined in the library, # disaster will strike. # # Warning! Use of --enable-gcc-wall may throw off this test. # AC_CHECK_DECL(lseek64,[AC_DEFINE(HAVE_LSEEK64_PROTOTYPE, 1, [Define to 1 if lseek64 declared in unistd.h])],, [#define _LARGEFILE_SOURCE #define _LARGEFILE64_SOURCE #include ]) AC_CHECK_DECL(memrchr,[AC_DEFINE(HAVE_MEMRCHR, 1, [Define to 1 if memrchr declared in string.h])],, [#define _GNU_SOURCE #include ]) # Checks for library functions. AC_CHECK_FUNCS(m4_flatten([ backtrace copy_file_range fallocate gettimeofday lgetxattr llistxattr lsetxattr memset realpath lseek64 ftello64 pread64 pwrite64 posix_fadvise fstatfs sendfile strdup strerror strrchr strtoull sysconf utimensat])) # Detect maximum block size if necessary AS_IF([test "x$MAX_BLOCK_SIZE" = "x"], [ AC_CACHE_CHECK([sysconf (_SC_PAGESIZE)], [erofs_cv_max_block_size], AC_RUN_IFELSE([AC_LANG_PROGRAM( [[ #include #include ]], [[ int result; FILE *f; result = sysconf(_SC_PAGESIZE); if (result < 0) return 1; f = fopen("conftest.out", "w"); if (!f) return 1; fprintf(f, "%d", result); fclose(f); return 0; ]])], [erofs_cv_max_block_size=`cat conftest.out`], [erofs_cv_max_block_size=4096], [erofs_cv_max_block_size=4096])) ], [erofs_cv_max_block_size=$MAX_BLOCK_SIZE]) # Configure multi-threading support AS_IF([test "x$enable_multithreading" != "xno"], [ AC_CHECK_HEADERS([pthread.h]) AC_CHECK_LIB([pthread], [pthread_mutex_lock], [], AC_MSG_ERROR([libpthread is required for multi-threaded build])) AC_DEFINE(EROFS_MT_ENABLED, 1, [Enable multi-threading support]) ], []) # Configure debug mode AS_IF([test "x$enable_debug" != "xno"], [], [ dnl Turn off all assert checking. CPPFLAGS="$CPPFLAGS -DNDEBUG" ]) # Configure -Werror AS_IF([test "x$enable_werror" != "xyes"], [], [ CPPFLAGS="$CPPFLAGS -Werror" ]) # Configure libuuid AS_IF([test "x$with_uuid" != "xno"], [ PKG_CHECK_MODULES([libuuid], [uuid]) # Paranoia: don't trust the result reported by pkgconfig before trying out saved_LIBS="$LIBS" saved_CPPFLAGS=${CPPFLAGS} CPPFLAGS="${libuuid_CFLAGS} ${CPPFLAGS}" LIBS="${libuuid_LIBS} $LIBS" AC_MSG_CHECKING([libuuid usability]) AC_TRY_LINK([ #include ], [ uuid_t tmp; uuid_generate(tmp); return 0; ], [have_uuid="yes" AC_MSG_RESULT([yes])], [ have_uuid="no" AC_MSG_RESULT([no]) AC_MSG_ERROR([libuuid doesn't work properly])]) LIBS="${saved_LIBS}" CPPFLAGS="${saved_CPPFLAGS}"], [have_uuid="no"]) # Configure selinux AS_IF([test "x$with_selinux" != "xno"], [ PKG_CHECK_MODULES([libselinux], [libselinux]) # Paranoia: don't trust the result reported by pkgconfig before trying out saved_LIBS="$LIBS" saved_CPPFLAGS=${CPPFLAGS} CPPFLAGS="${libselinux_CFLAGS} ${CPPFLAGS}" LIBS="${libselinux_LIBS} $LIBS" AC_CHECK_LIB(selinux, selabel_lookup, [ have_selinux="yes" ], [ AC_MSG_ERROR([libselinux doesn't work properly])]) LIBS="${saved_LIBS}" CPPFLAGS="${saved_CPPFLAGS}"], [have_selinux="no"]) # Configure fuse AS_IF([test "x$enable_fuse" != "xno"], [ # Paranoia: don't trust the result reported by pkgconfig before trying out saved_LIBS="$LIBS" saved_CPPFLAGS=${CPPFLAGS} PKG_CHECK_MODULES([libfuse3], [fuse3 >= 3.0], [ PKG_CHECK_MODULES([libfuse3_0], [fuse3 >= 3.0 fuse3 < 3.2], [ AC_DEFINE([FUSE_USE_VERSION], [30], [used FUSE API version]) ], [ PKG_CHECK_MODULES([libfuse3_2], [fuse3 >= 3.2], [ AC_DEFINE([FUSE_USE_VERSION], [32], [used FUSE API version]) ]) ]) CPPFLAGS="${libfuse3_CFLAGS} ${CPPFLAGS}" LIBS="${libfuse3_LIBS} $LIBS" AC_CHECK_LIB(fuse3, fuse_session_new, [], [ AC_MSG_ERROR([libfuse3 (>= 3.0) doesn't work properly for lowlevel api])]) have_fuse="yes" ], [ PKG_CHECK_MODULES([libfuse2], [fuse >= 2.6], [ AC_DEFINE([FUSE_USE_VERSION], [26], [used FUSE API version]) CPPFLAGS="${libfuse2_CFLAGS} ${CPPFLAGS}" LIBS="${libfuse2_LIBS} $LIBS" AC_CHECK_LIB(fuse, fuse_lowlevel_new, [], [ AC_MSG_ERROR([libfuse (>= 2.6) doesn't work properly for lowlevel api])]) have_fuse="yes" ], [have_fuse="no"]) ]) LIBS="${saved_LIBS}" CPPFLAGS="${saved_CPPFLAGS}"], [have_fuse="no"]) # Configure lz4 AS_IF([test "x$enable_lz4" != "xno"], [ saved_CPPFLAGS=${CPPFLAGS} PKG_CHECK_MODULES([liblz4], [liblz4], [ # Paranoia: don't trust the result reported by pkgconfig before trying out saved_LIBS="$LIBS" saved_CPPFLAGS=${CPPFLAGS} CPPFLAGS="${liblz4_CFLAGS} ${CPPFLAGS}" LIBS="${liblz4_LIBS} $LIBS" AC_CHECK_HEADERS([lz4.h],[ AC_CHECK_LIB(lz4, LZ4_compress_destSize, [ AC_CHECK_DECL(LZ4_compress_destSize, [have_lz4="yes"], [], [[ #include ]]) ]) AC_CHECK_LIB(lz4, LZ4_compress_HC_destSize, [ AC_CHECK_DECL(LZ4_compress_HC_destSize, [have_lz4hc="yes"], [], [[ #include ]]) ]) ]) LIBS="${saved_LIBS}" CPPFLAGS="${saved_CPPFLAGS}" ], [[]]) AS_IF([test "x$enable_lz4" = "xyes" -a "x$have_lz4" != "xyes"], [ AC_MSG_ERROR([Cannot find a proper liblz4 version]) ]) ]) # Configure liblzma have_liblzma="no" AS_IF([test "x$enable_lzma" != "xno"], [ saved_CPPFLAGS=${CPPFLAGS} PKG_CHECK_MODULES([liblzma], [liblzma], [ # Paranoia: don't trust the result reported by pkgconfig before trying out saved_LIBS="$LIBS" saved_CPPFLAGS=${CPPFLAGS} CPPFLAGS="${liblzma_CFLAGS} ${CPPFLAGS}" LIBS="${liblzma_LIBS} $LIBS" AC_CHECK_HEADERS([lzma.h],[ AC_CHECK_LIB(lzma, lzma_microlzma_encoder, [ AC_CHECK_DECL(lzma_microlzma_encoder, [have_liblzma="yes"], [], [[ #include ]]) ]) ]) LIBS="${saved_LIBS}" CPPFLAGS="${saved_CPPFLAGS}" ], [[]]) AS_IF([test "x$enable_lzma" = "xyes" -a "x$have_liblzma" != "xyes"], [ AC_MSG_ERROR([Cannot find a proper liblzma version]) ]) ]) # Configure zlib have_zlib="no" AS_IF([test "x$with_zlib" != "xno"], [ PKG_CHECK_MODULES([zlib], [zlib], [ # Paranoia: don't trust the result reported by pkgconfig before trying out saved_LIBS="$LIBS" saved_CPPFLAGS=${CPPFLAGS} CPPFLAGS="${zlib_CFLAGS} ${CPPFLAGS}" LIBS="${zlib_LIBS} $LIBS" AC_CHECK_HEADERS([zlib.h],[ AC_CHECK_LIB(z, inflate, [], [ AC_MSG_ERROR([zlib doesn't work properly])]) AC_CHECK_DECL(inflate, [have_zlib="yes"], [AC_MSG_ERROR([zlib doesn't work properly])], [[ #include ]]) ]) LIBS="${saved_LIBS}" CPPFLAGS="${saved_CPPFLAGS}"], [ AS_IF([test "x$with_zlib" = "xyes"], [ AC_MSG_ERROR([Cannot find proper zlib]) ]) ]) ]) # Configure libdeflate AS_IF([test "x$with_libdeflate" != "xno"], [ PKG_CHECK_MODULES([libdeflate], [libdeflate]) # Paranoia: don't trust the result reported by pkgconfig before trying out saved_LIBS="$LIBS" saved_CPPFLAGS=${CPPFLAGS} CPPFLAGS="${libdeflate_CFLAGS} ${CPPFLAGS}" LIBS="${libdeflate_LIBS} $LIBS" AC_CHECK_LIB(deflate, libdeflate_deflate_decompress, [ have_libdeflate="yes" ], [ AC_MSG_ERROR([libdeflate doesn't work properly])]) LIBS="${saved_LIBS}" CPPFLAGS="${saved_CPPFLAGS}"], [have_libdeflate="no"]) # Configure libzstd have_libzstd="no" AS_IF([test "x$with_libzstd" != "xno"], [ PKG_CHECK_MODULES([libzstd], [libzstd >= 1.4.0], [ # Paranoia: don't trust the result reported by pkgconfig before trying out saved_LIBS="$LIBS" saved_CPPFLAGS=${CPPFLAGS} CPPFLAGS="${libzstd_CFLAGS} ${CPPFLAGS}" LIBS="${libzstd_LIBS} $LIBS" AC_CHECK_HEADERS([zstd.h],[ AC_CHECK_LIB(zstd, ZSTD_compress2, [], [ AC_MSG_ERROR([libzstd doesn't work properly])]) AC_CHECK_DECL(ZSTD_compress2, [have_libzstd="yes"], [AC_MSG_ERROR([libzstd doesn't work properly])], [[ #include ]]) AC_CHECK_FUNCS([ZSTD_getFrameContentSize]) ]) LIBS="${saved_LIBS}" CPPFLAGS="${saved_CPPFLAGS}"], [ AS_IF([test "x$with_libzstd" = "xyes"], [ AC_MSG_ERROR([Cannot find proper libzstd]) ]) ]) ]) # Configure Intel QPL have_qpl="no" AS_IF([test "x$with_qpl" != "xno"], [ PKG_CHECK_MODULES([libqpl], [qpl >= 1.5.0], [ # Paranoia: don't trust the result reported by pkgconfig before trying out saved_LIBS="$LIBS" saved_CPPFLAGS=${CPPFLAGS} CPPFLAGS="${libqpl_CFLAGS} ${CPPFLAGS}" LIBS="${libqpl_LIBS} $LIBS" AC_CHECK_HEADERS([qpl/qpl.h],[ AC_CHECK_LIB(qpl, qpl_execute_job, [], [ AC_MSG_ERROR([libqpl doesn't work properly])]) AC_CHECK_DECL(qpl_execute_job, [have_qpl="yes"], [AC_MSG_ERROR([libqpl doesn't work properly])], [[ #include ]]) ]) LIBS="${saved_LIBS}" CPPFLAGS="${saved_CPPFLAGS}"], [ AS_IF([test "x$with_qpl" = "xyes"], [ AC_MSG_ERROR([Cannot find proper libqpl]) ]) ]) ]) # Configure libxxhash have_xxhash="no" AS_IF([test "x$with_xxhash" != "xno"], [ PKG_CHECK_MODULES([libxxhash], [libxxhash], [ # Paranoia: don't trust the result reported by pkgconfig before trying out saved_LIBS="$LIBS" saved_CPPFLAGS=${CPPFLAGS} CPPFLAGS="${libxxhash_CFLAGS} ${CPPFLAGS}" LIBS="${libxxhash_LIBS} $LIBS" AC_CHECK_HEADERS([xxhash.h],[ AC_CHECK_LIB(xxhash, XXH32, [], [ AC_MSG_ERROR([libxxhash doesn't work properly])]) AC_CHECK_DECL(XXH32, [have_xxhash="yes"], [AC_MSG_ERROR([libxxhash doesn't work properly])], [[ #include ]]) ]) LIBS="${saved_LIBS}" CPPFLAGS="${saved_CPPFLAGS}"], [ AS_IF([test "x$with_xxhash" = "xyes"], [ AC_MSG_ERROR([Cannot find proper libxxhash]) ]) ]) ]) # Enable 64-bit off_t CFLAGS+=" -D_LARGEFILE_SOURCE -D_FILE_OFFSET_BITS=64" # Configure fuzzing mode AS_IF([test "x$enable_fuzzing" != "xyes"], [], [ CC_CHECK_CFLAGS(["-fsanitize=address,fuzzer-no-link"], [ CFLAGS="$CFLAGS -g -O1 -fsanitize=address,fuzzer-no-link" ], [ AC_MSG_ERROR([Compiler doesn't support `-fsanitize=address,fuzzer-no-link`]) ]) ]) AM_CONDITIONAL([ENABLE_FUZZING], [test "x${enable_fuzzing}" = "xyes"]) # Set up needed symbols, conditionals and compiler/linker flags AM_CONDITIONAL([ENABLE_EROFS_MT], [test "x${enable_multithreading}" != "xno"]) AM_CONDITIONAL([ENABLE_LZ4], [test "x${have_lz4}" = "xyes"]) AM_CONDITIONAL([ENABLE_LZ4HC], [test "x${have_lz4hc}" = "xyes"]) AM_CONDITIONAL([ENABLE_FUSE], [test "x${have_fuse}" = "xyes"]) AM_CONDITIONAL([ENABLE_LIBLZMA], [test "x${have_liblzma}" = "xyes"]) AM_CONDITIONAL([ENABLE_LIBDEFLATE], [test "x${have_libdeflate}" = "xyes"]) AM_CONDITIONAL([ENABLE_LIBZSTD], [test "x${have_libzstd}" = "xyes"]) AM_CONDITIONAL([ENABLE_QPL], [test "x${have_qpl}" = "xyes"]) AM_CONDITIONAL([ENABLE_XXHASH], [test "x${have_xxhash}" = "xyes"]) AM_CONDITIONAL([ENABLE_STATIC_FUSE], [test "x${enable_static_fuse}" = "xyes"]) if test "x$have_uuid" = "xyes"; then AC_DEFINE([HAVE_LIBUUID], 1, [Define to 1 if libuuid is found]) fi if test "x$have_selinux" = "xyes"; then AC_DEFINE([HAVE_LIBSELINUX], 1, [Define to 1 if libselinux is found]) fi if test "x${have_lz4}" = "xyes"; then AC_DEFINE([LZ4_ENABLED], [1], [Define to 1 if lz4 is enabled.]) if test "x${have_lz4hc}" = "xyes"; then AC_DEFINE([LZ4HC_ENABLED], [1], [Define to 1 if lz4hc is enabled.]) fi fi if test "x${have_liblzma}" = "xyes"; then AC_DEFINE([HAVE_LIBLZMA], [1], [Define to 1 if liblzma is enabled.]) liblzma_LIBS="-llzma" test -z "${with_liblzma_libdir}" || liblzma_LIBS="-L${with_liblzma_libdir} $liblzma_LIBS" test -z "${with_liblzma_incdir}" || liblzma_CFLAGS="-I${with_liblzma_incdir}" AC_SUBST([liblzma_LIBS]) AC_SUBST([liblzma_CFLAGS]) fi if test "x$have_zlib" = "xyes"; then AC_DEFINE([HAVE_ZLIB], 1, [Define to 1 if zlib is found]) fi if test "x$have_libdeflate" = "xyes"; then AC_DEFINE([HAVE_LIBDEFLATE], 1, [Define to 1 if libdeflate is found]) fi if test "x$have_libzstd" = "xyes"; then AC_DEFINE([HAVE_LIBZSTD], 1, [Define to 1 if libzstd is found]) fi if test "x$have_qpl" = "xyes"; then AC_DEFINE([HAVE_QPL], 1, [Define to 1 if qpl is found]) AC_SUBST([libqpl_LIBS]) AC_SUBST([libqpl_CFLAGS]) fi if test "x$have_xxhash" = "xyes"; then AC_DEFINE([HAVE_XXHASH], 1, [Define to 1 if xxhash is found]) fi # Dump maximum block size AS_IF([test "x$erofs_cv_max_block_size" = "x"], [$erofs_cv_max_block_size = 4096], []) AC_DEFINE_UNQUOTED([EROFS_MAX_BLOCK_SIZE], [$erofs_cv_max_block_size], [The maximum block size which erofs-utils supports]) AC_CONFIG_FILES([Makefile man/Makefile lib/Makefile mkfs/Makefile dump/Makefile fuse/Makefile fsck/Makefile contrib/Makefile]) AC_OUTPUT erofs-utils-1.8.6/contrib/000077500000000000000000000000001477440110200154425ustar00rootroot00000000000000erofs-utils-1.8.6/contrib/Makefile.am000066400000000000000000000003041477440110200174730ustar00rootroot00000000000000# SPDX-License-Identifier: GPL-2.0+ # Makefile.am AUTOMAKE_OPTIONS = foreign if OS_LINUX noinst_PROGRAMS = stress stress_CFLAGS = -Wall -I$(top_srcdir)/include stress_SOURCES = stress.c endif erofs-utils-1.8.6/contrib/mkstress.sh000077500000000000000000000003161477440110200176540ustar00rootroot00000000000000#!/bin/sh [ "x$CC" = 'x' ] && CC=gcc cd $(dirname $0) if [ -f ../config.h ]; then $CC -o stress -DHAVE_CONFIG_H -I.. -I../include stress.c else $CC -o stress -DHAVE_LINUX_TYPES_H -I../include stress.c fi erofs-utils-1.8.6/contrib/stress.c000066400000000000000000000436121477440110200171370ustar00rootroot00000000000000// SPDX-License-Identifier: GPL-2.0+ /* * stress test for EROFS filesystem * * Copyright (C) 2019-2025 Gao Xiang */ #define _GNU_SOURCE #include "erofs/defs.h" #include #include #include #include #include #include #include #include #include #include #include #define MAX_CHUNKSIZE (2 * 1024 * 1024) #define MAX_SCAN_CHUNKSIZE (256 * 1024) bool superuser; unsigned int nprocs = 1, loops = 1, r_seed; unsigned int procid; volatile sig_atomic_t should_stop; enum { DROP_PAGE_CACHE, DROP_SLAB_CACHE, COMPACT_MEMORY, }; enum { OP_GETDENTS, OP_READLINK, OP_SEQREAD_ALIGNED, OP_SEQREAD_UNALIGNED, OP_READ, OP_FADVISE, OP_DROP_CACHES, }; struct opdesc { char *name; int (*func)(int op, unsigned int sn); int freq; bool requireroot; }; extern struct opdesc ops[]; static int drop_caches_f(int op, unsigned int sn) { static const char *procfile[] = { [DROP_PAGE_CACHE] = "/proc/sys/vm/drop_caches", [DROP_SLAB_CACHE] = "/proc/sys/vm/drop_caches", [COMPACT_MEMORY] = "/proc/sys/vm/compact_memory", }; static const char *val[] = { [DROP_PAGE_CACHE] = "1\n", [DROP_SLAB_CACHE] = "2\n", [COMPACT_MEMORY] = "1\n", }; int mode = random() % ARRAY_SIZE(val); FILE *f; clock_t start; if (!procfile[mode]) return -EINVAL; printf("%d[%u]/%u %s: %s=%s", getpid(), procid, sn, __func__, procfile[mode], val[mode]); f = fopen(procfile[mode], "w"); if (!f) return -errno; start = clock(); while (clock() < start + CLOCKS_PER_SEC) { fputs(val[mode], f); (void)sched_yield(); } fclose(f); return 0; } struct fent { char *subpath; int fd, chkfd; }; #define FT_DIR 0 #define FT_DIRm (1 << FT_DIR) #define FT_REG 1 #define FT_REGm (1 << FT_REG) #define FT_SYM 2 #define FT_SYMm (1 << FT_SYM) #define FT_DEV 3 #define FT_DEVm (1 << FT_DEV) #define FT_nft 4 #define FT_ANYm ((1 << FT_nft) - 1) #define FLIST_SLOT_INCR 16 struct flist { int nfiles, nslots; struct fent *fents; } flists[FT_nft]; static struct fent *add_to_flist(int type, char *subpath) { struct fent *fep; struct flist *ftp; ftp = &flists[type]; if (ftp->nfiles >= ftp->nslots) { ftp->nslots += FLIST_SLOT_INCR; ftp->fents = realloc(ftp->fents, ftp->nslots * sizeof(struct fent)); if (!ftp->fents) return NULL; } fep = &ftp->fents[ftp->nfiles++]; fep->subpath = strdup(subpath); fep->fd = -1; fep->chkfd = -1; return fep; } static inline bool is_dot_dotdot(const char *name) { if (name[0] != '.') return false; return name[1] == '\0' || (name[1] == '.' && name[2] == '\0'); } static int walkdir(struct fent *ent) { const char *dirpath = ent->subpath; int ret = 0; struct dirent *dp; DIR *_dir; _dir = opendir(dirpath); if (!_dir) { fprintf(stderr, "failed to opendir at %s: %s\n", dirpath, strerror(errno)); return -errno; } while (1) { char subpath[PATH_MAX]; struct stat st; /* * set errno to 0 before calling readdir() in order to * distinguish end of stream and from an error. */ errno = 0; dp = readdir(_dir); if (!dp) break; if (is_dot_dotdot(dp->d_name)) continue; sprintf(subpath, "%s/%s", dirpath, dp->d_name); if (lstat(subpath, &st)) continue; switch (st.st_mode & S_IFMT) { case S_IFDIR: ent = add_to_flist(FT_DIR, subpath); if (ent == NULL) { ret = -ENOMEM; goto err_closedir; } ret = walkdir(ent); if (ret) goto err_closedir; break; case S_IFREG: ent = add_to_flist(FT_REG, subpath); if (ent == NULL) { ret = -ENOMEM; goto err_closedir; } break; case S_IFLNK: ent = add_to_flist(FT_SYM, subpath); if (ent == NULL) { ret = -ENOMEM; goto err_closedir; } break; default: break; } } if (errno) ret = -errno; err_closedir: closedir(_dir); return ret; } static int init_filetable(int testdir_fd) { struct fent *fent; fent = add_to_flist(FT_DIR, "."); if (!fent) return -ENOMEM; if (fchdir(testdir_fd) < 0) { perror("failed to fchdir"); return -errno; } return walkdir(fent); } static struct fent *getfent(int which, int r) { int totalsum = 0; /* total number of matching files */ int partialsum = 0; /* partial sum of matching files */ struct flist *flp; int i, x; totalsum = 0; for (i = 0, flp = flists; i < FT_nft; ++i, ++flp) if (which & (1 << i)) totalsum += flp->nfiles; if (!totalsum) return NULL; /* * Now we have possible matches between 0..totalsum-1. * And we use r to help us choose which one we want, * which when bounded by totalsum becomes x. */ x = (int)(r % totalsum); for (i = 0, flp = flists; i < FT_nft; i++, flp++) { if (which & (1 << i)) { if (x < partialsum + flp->nfiles) return &flp->fents[x - partialsum]; partialsum += flp->nfiles; } } fprintf(stderr, "%s failure\n", __func__); return NULL; } static int testdir_fd = -1, chkdir_fd = -1; static char *dumpfile; static int __getdents_f(unsigned int sn, struct fent *fe) { int dfd; DIR *dir; dfd = openat(testdir_fd, fe->subpath, O_DIRECTORY); if (dfd < 0) { fprintf(stderr, "%d[%u]/%u getdents_f: failed to open directory %s", getpid(), procid, sn, fe->subpath); return -errno; } dir = fdopendir(dfd); while (readdir64(dir) != NULL) continue; closedir(dir); return 0; } static int getdents_f(int op, unsigned int sn) { struct fent *fe; fe = getfent(FT_DIRm, random()); if (!fe) return 0; printf("%d[%u]/%u %s: %s\n", getpid(), procid, sn, __func__, fe->subpath); return __getdents_f(sn, fe); } static void baddump(unsigned int sn, const char *op, char *buf1, char *buf2, unsigned int sz) { int fd, err, i; char *fn = dumpfile; if (!fn) return; for (i = 0;;) { fd = open(fn, O_CREAT | O_EXCL | O_WRONLY, 0644); if (fd >= 0) { printf("%d[%u]/%u %s: dump inconsistent data to \"%s\" of %u bytes\n", getpid(), procid, sn, op, fn, sz); if (fn != dumpfile) free(fn); break; } if (fd < 0 && errno != EEXIST) { fprintf(stderr, "%d[%u]/%u: failed to create dumpfile %s\n", getpid(), procid, sn, fn); if (fn != dumpfile) free(fn); return; } if (fn != dumpfile) free(fn); err = asprintf(&fn, "%s.%d", dumpfile, ++i); if (err < 0) { fprintf(stderr, "%d[%u]/%u: failed to allocate filename\n", getpid(), procid, sn); return; } } if (write(fd, buf1, sz) != sz) fprintf(stderr, "%d[%u]/%u: failed to write buffer1 @ %u\n", getpid(), procid, sn, sz); if (write(fd, buf2, sz) != sz) fprintf(stderr, "%d[%u]/%u: failed to write buffer2 @ %u\n", getpid(), procid, sn, sz); close(fd); } static int readlink_f(int op, unsigned int sn) { char buf1[PATH_MAX], buf2[PATH_MAX]; struct fent *fe; ssize_t sz; fe = getfent(FT_SYMm, random()); if (!fe) return 0; printf("%d[%u]/%u %s: %s\n", getpid(), procid, sn, __func__, fe->subpath); sz = readlinkat(testdir_fd, fe->subpath, buf1, PATH_MAX - 1); if (sz < 0) { fprintf(stderr, "%d[%u]/%u %s: failed to readlinkat %s: %d", getpid(), procid, sn, __func__, fe->subpath, errno); return -errno; } if (chkdir_fd >= 0) { if (sz != readlinkat(testdir_fd, fe->subpath, buf2, PATH_MAX - 1)) { fprintf(stderr, "%d[%u]/%u %s: symlink length mismatch @%s\n", getpid(), procid, sn, __func__, fe->subpath); return -E2BIG; } if (memcmp(buf1, buf2, sz)) { fprintf(stderr, "%d[%u]/%u %s: symlink mismatch @%s\n", getpid(), procid, sn, __func__, fe->subpath); baddump(sn, "readlink_f", buf1, buf2, sz); return -EBADMSG; } } return 0; } static int tryopen(unsigned int sn, const char *op, struct fent *fe) { if (fe->fd < 0) { fe->fd = openat(testdir_fd, fe->subpath, O_RDONLY); if (fe->fd < 0) { fprintf(stderr, "%d[%u]/%u %s: failed to open %s: %d", getpid(), procid, sn, op, fe->subpath, errno); return -errno; } /* use force_page_cache_readahead for every read request */ posix_fadvise(fe->fd, 0, 0, POSIX_FADV_RANDOM); } if (chkdir_fd >= 0 && fe->chkfd < 0) fe->chkfd = openat(chkdir_fd, fe->subpath, O_RDONLY); return 0; } static int fadvise_f(int op, unsigned int sn) { struct fent *fe; int ret; fe = getfent(FT_REGm, random()); if (!fe) return 0; ret = tryopen(sn, __func__, fe); if (ret) return ret; printf("%d[%u]/%u %s: %s\n", getpid(), procid, sn, __func__, fe->subpath); ret = posix_fadvise(fe->fd, 0, 0, POSIX_FADV_DONTNEED); if (!ret) return 0; fprintf(stderr, "%d(%u)/%u %s: posix_fadvise %s failed %d\n", getpid(), procid, sn, __func__, fe->subpath, errno); return -errno; } static int __read_f(unsigned int sn, struct fent *fe, uint64_t filesize) { static char buf[MAX_CHUNKSIZE], chkbuf[MAX_CHUNKSIZE]; uint64_t lr, off, len, trimmed; size_t nread, nread2; lr = ((uint64_t) random() << 32) + random(); off = lr % filesize; len = (random() % MAX_CHUNKSIZE) + 1; trimmed = len; if (off + len > filesize) { uint64_t a = filesize - off + 16 * getpagesize(); if (len > a) len %= a; trimmed = len <= filesize - off ? len : filesize - off; } printf("%d[%u]/%u read_f: %llu bytes @ %llu of %s\n", getpid(), procid, sn, len | 0ULL, off | 0ULL, fe->subpath); nread = pread64(fe->fd, buf, len, off); if (nread != trimmed) { fprintf(stderr, "%d[%u]/%u read_f: failed to read %llu bytes @ %llu of %s\n", getpid(), procid, sn, len | 0ULL, off | 0ULL, fe->subpath); return -errno; } if (fe->chkfd < 0) return 0; nread2 = pread64(fe->chkfd, chkbuf, len, off); if (nread2 <= 0) { fprintf(stderr, "%d[%u]/%u read_f: failed to check %llu bytes @ %llu of %s\n", getpid(), procid, sn, len | 0ULL, off | 0ULL, fe->subpath); return -errno; } if (nread != nread2) { fprintf(stderr, "%d[%u]/%u read_f: size mismatch %llu bytes @ %llu of %s\n", getpid(), procid, sn, len | 0ULL, off | 0ULL, fe->subpath); return -EFBIG; } if (memcmp(buf, chkbuf, nread)) { fprintf(stderr, "%d[%u]/%u read_f: data mismatch %llu bytes @ %llu of %s\n", getpid(), procid, sn, len | 0ULL, off | 0ULL, fe->subpath); baddump(sn, "read_f", buf, chkbuf, nread); return -EBADMSG; } return 0; } static int read_f(int op, unsigned int sn) { struct fent *fe; ssize_t fsz; int ret; fe = getfent(FT_REGm, random()); if (!fe) return 0; ret = tryopen(sn, __func__, fe); if (ret) return ret; fsz = lseek64(fe->fd, 0, SEEK_END); if (fsz <= 0) { if (!fsz) { printf("%d[%u]/%u %s: zero size @ %s\n", getpid(), procid, sn, __func__, fe->subpath); return 0; } fprintf(stderr, "%d[%u]/%u %s: lseek64 %s failed %d\n", getpid(), procid, sn, __func__, fe->subpath, errno); return -errno; } return __read_f(sn, fe, fsz); } static int __doscan_f(unsigned int sn, const char *op, struct fent *fe, uint64_t filesize, uint64_t chunksize) { static char buf[MAX_SCAN_CHUNKSIZE], chkbuf[MAX_SCAN_CHUNKSIZE]; uint64_t pos; printf("%d[%u]/%u %s: filesize %llu, chunksize %llu @ %s\n", getpid(), procid, sn, op, (unsigned long long)filesize, (unsigned long long)chunksize, fe->subpath); for (pos = 0; pos < filesize; pos += chunksize) { ssize_t nread, nread2; nread = pread64(fe->fd, buf, chunksize, pos); if (nread <= 0) return -errno; if (nread < chunksize && nread != filesize - pos) return -ERANGE; if (fe->chkfd < 0) continue; nread2 = pread64(fe->chkfd, chkbuf, chunksize, pos); if (nread2 <= 0) return -errno; if (nread != nread2) return -EFBIG; if (memcmp(buf, chkbuf, nread)) { fprintf(stderr, "%d[%u]/%u %s: %llu bytes mismatch @ %llu of %s\n", getpid(), procid, sn, op, chunksize | 0ULL, pos | 0ULL, fe->subpath); baddump(sn, op, buf, chkbuf, nread); return -EBADMSG; } } return 0; } static int doscan_f(int op, unsigned int sn) { struct fent *fe; uint64_t chunksize; ssize_t fsz; int ret; fe = getfent(FT_REGm, random()); if (!fe) return 0; ret = tryopen(sn, __func__, fe); if (ret) return ret; fsz = lseek64(fe->fd, 0, SEEK_END); if (fsz <= 0) { if (!fsz) { printf("%d[%u]/%u %s: zero size @ %s\n", getpid(), procid, sn, __func__, fe->subpath); return 0; } fprintf(stderr, "%d[%u]/%u %s: lseek64 %s failed %d\n", getpid(), procid, sn, __func__, fe->subpath, errno); return -errno; } chunksize = ((uint64_t)random() * random() % MAX_SCAN_CHUNKSIZE) + 1; return __doscan_f(sn, __func__, fe, fsz, chunksize); } static int doscan_aligned_f(int op, unsigned int sn) { const int psz = getpagesize(); struct fent *fe; uint64_t chunksize, maxchunksize; ssize_t fsz; int ret; fe = getfent(FT_REGm, random()); if (!fe) return 0; ret = tryopen(sn, __func__, fe); if (ret) return ret; fsz = lseek64(fe->fd, 0, SEEK_END); if (fsz <= psz) { if (fsz >= 0) { printf("%d[%u]/%u %s: size too small %lld @ %s\n", getpid(), procid, sn, __func__, fsz | 0LL, fe->subpath); return 0; } fprintf(stderr, "%d[%u]/%u %s: lseek64 %s failed %d\n", getpid(), procid, sn, __func__, fe->subpath, errno); return -errno; } maxchunksize = (fsz - psz > MAX_SCAN_CHUNKSIZE ? MAX_SCAN_CHUNKSIZE : fsz - psz); chunksize = random() * random() % maxchunksize; chunksize = (((chunksize - 1) / psz) + 1) * psz; if (!chunksize) chunksize = psz; return __doscan_f(sn, __func__, fe, fsz, chunksize); } void randomdelay(void) { uint64_t lr = ((uint64_t) random() << 32) + random(); clock_t start; clock_t length = (lr % CLOCKS_PER_SEC) >> 1; start = clock(); while (clock() < start + length) (void)sched_yield(); } void sg_handler(int signum) { switch (signum) { case SIGTERM: should_stop = 1; break; default: break; } } struct opdesc ops[] = { [OP_GETDENTS] = { "getdents", getdents_f, 5, false }, [OP_READLINK] = { "readlink", readlink_f, 5, false }, [OP_SEQREAD_ALIGNED] = { "readscan_aligned", doscan_aligned_f, 10, false }, [OP_SEQREAD_UNALIGNED] = { "readscan_unaligned", doscan_f, 10, false }, [OP_READ] = { "read", read_f, 30, false}, [OP_FADVISE] = { "fadvise", fadvise_f, 3, false}, [OP_DROP_CACHES] = { "drop_caches", drop_caches_f, 1, true}, }; static int parse_options(int argc, char *argv[]) { char *testdir, *chkdir; int opt; while ((opt = getopt(argc, argv, "d:l:p:s:")) != -1) { switch (opt) { case 'l': loops = atoi(optarg); if (loops < 0) { fprintf(stderr, "invalid loops %d\n", loops); return -EINVAL; } break; case 'p': nprocs = atoi(optarg); if (nprocs < 0) { fprintf(stderr, "invalid workers %d\n", nprocs); return -EINVAL; } break; case 's': r_seed = atoi(optarg); if (r_seed < 0) { fprintf(stderr, "invalid random seed %d\n", r_seed); return -EINVAL; } break; case 'd': if (!*optarg) { fprintf(stderr, "invalid dump file\n"); return -EINVAL; } dumpfile = optarg; break; default: /* '?' */ return -EINVAL; } } if (optind >= argc) return -EINVAL; testdir = argv[optind++]; if (testdir) { testdir_fd = open(testdir, O_PATH); if (testdir_fd < 0) { fprintf(stderr, "cannot open testdir fd @ %s: %s\n", testdir, strerror(errno)); return 1; } } if (argc > optind) { chkdir = argv[optind++]; chkdir_fd = open(chkdir, O_PATH); if (chkdir_fd < 0) { fprintf(stderr, "cannot open checkdir fd @ %s: %s\n", chkdir, strerror(errno)); return 1; } } return 0; } static void usage(void) { fputs("usage: [options] TESTDIR [COMPRDIR]\n\n" "Stress test for EROFS filesystem, where TESTDIR is the directory to test and\n" "COMPRDIR (optional) serves as a directory for data comparison.\n" " -l# Number of times each worker should loop (0 for infinite, default: 1)\n" " -p# Number of parallel worker processes (default: 1)\n" " -s# Seed for random generator (default: random)\n" " -d Specify a dumpfile for the inconsistent data\n", stderr); } unsigned int *freq_table; int freq_table_size; static void doproc(void) { unsigned int sn; srandom(r_seed + procid); for (sn = 0; !should_stop && (!loops || sn < loops); ++sn) { int op, err; op = freq_table[random() % freq_table_size]; if (op >= ARRAY_SIZE(ops)) { fprintf(stderr, "%d[%u]/%u %s: internal error\n", getpid(), procid, sn, __func__); abort(); } if (sn && op != OP_DROP_CACHES) randomdelay(); err = ops[op].func(op, sn); if (err) { fprintf(stderr, "%d[%u]/%u test failed (%d): %s\n", getpid(), procid, sn, err, strerror(-err)); exit(1); } } } static void make_freq_table(void) { int f, i; struct opdesc *p; for (p = ops, f = 0; p < ops + ARRAY_SIZE(ops); p++) { if (!superuser && p->requireroot) continue; f += p->freq; } freq_table = malloc(f * sizeof(*freq_table)); freq_table_size = f; for (p = ops, i = 0; p < ops + ARRAY_SIZE(ops); p++) { if (!superuser && p->requireroot) continue; for (f = 0; f < p->freq; f++, i++) freq_table[i] = p - ops; } } int main(int argc, char *argv[]) { unsigned int i; int err, stat; struct sigaction action; err = parse_options(argc, argv); if (err) { if (err == -EINVAL) usage(); return 1; } err = init_filetable(testdir_fd); if (err) { fprintf(stderr, "cannot initialize file table: %s\n", strerror(errno)); return 1; } superuser = (geteuid() == 0); setpgid(0, 0); action.sa_handler = sg_handler; action.sa_flags = 0; if (sigaction(SIGTERM, &action, 0)) { perror("sigaction failed"); exit(1); } if (!r_seed) r_seed = (time(NULL) ? : 1); make_freq_table(); /* spawn nprocs processes */ for (i = 0; i < nprocs; ++i) { if (fork() == 0) { action.sa_handler = SIG_DFL; sigemptyset(&action.sa_mask); if (sigaction(SIGTERM, &action, 0)) { perror("sigaction failed"); exit(1); } procid = i; doproc(); return 0; } } err = 0; while (wait(&stat) > 0 && !should_stop) { if (!WIFEXITED(stat)) { err = 1; break; } if (WEXITSTATUS(stat)) { err = WEXITSTATUS(stat); break; } } action.sa_flags = SA_RESTART; sigaction(SIGTERM, &action, 0); kill(-getpid(), SIGTERM); /* wait until all children exit */ while (wait(&stat) > 0) continue; return err; } erofs-utils-1.8.6/docs/000077500000000000000000000000001477440110200147325ustar00rootroot00000000000000erofs-utils-1.8.6/docs/INSTALL.md000066400000000000000000000035531477440110200163700ustar00rootroot00000000000000This document describes how to configure and build erofs-utils from source. See the [README](../README) file in the top level directory about the brief overview of erofs-utils. ## Dependencies & build LZ4 1.9.3+ for LZ4(HC) enabled [^1]. [XZ Utils 5.3.2alpha+](https://tukaani.org/xz/xz-5.3.2alpha.tar.gz) for LZMA enabled, [XZ Utils 5.4+](https://tukaani.org/xz/xz-5.4.1.tar.gz) highly recommended. libfuse 2.6+ for erofsfuse enabled. [^1]: It's not recommended to use LZ4 versions under 1.9.3 since unexpected crashes could make trouble to end users due to broken LZ4_compress_destSize() (fixed in v1.9.2), [LZ4_compress_HC_destSize()](https://github.com/lz4/lz4/commit/660d21272e4c8a0f49db5fc1e6853f08713dff82) or [LZ4_decompress_safe_partial()](https://github.com/lz4/lz4/issues/783). ## How to build with LZ4 To build, the following commands can be used in order: ``` sh $ ./autogen.sh $ ./configure $ make ``` `mkfs.erofs`, `dump.erofs` and `fsck.erofs` binaries will be generated under the corresponding folders. ## How to build with liblzma In order to enable LZMA support, build with the following commands: ``` sh $ ./configure --enable-lzma $ make ``` Additionally, you could specify liblzma target paths with `--with-liblzma-incdir` and `--with-liblzma-libdir` manually. ## How to build erofsfuse It's disabled by default as an experimental feature for now due to the extra libfuse dependency, to enable and build it manually: ``` sh $ ./configure --enable-fuse $ make ``` `erofsfuse` binary will be generated under `fuse` folder. ## How to install erofs-utils manually Use the following command to install erofs-utils binaries: ``` sh # make install ``` By default, `make install` will install all the files in `/usr/local/bin`, `/usr/local/lib` etc. You can specify an installation prefix other than `/usr/local` using `--prefix`, for instance `--prefix=$HOME`. erofs-utils-1.8.6/docs/PERFORMANCE.md000066400000000000000000000223241477440110200170200ustar00rootroot00000000000000# Test setup Processor: x86_64, Intel(R) Xeon(R) Platinum 8369B CPU @ 2.70GHz * 2 VCores Storage: Cloud disk, 3000 IOPS upper limit OS Kernel: Linux 6.2 Software: LZ4 1.9.3, erofs-utils 1.6, squashfs-tools 4.5.1 Disclaimer: Test results could be varied from different hardware and/or data patterns. Therefore, the following results are **ONLY for reference**. # Benchmark on multiple files [Rootfs of Debian docker image](https://github.com/debuerreotype/docker-debian-artifacts/blob/dist-amd64/bullseye/rootfs.tar.xz?raw=true) is used as the dataset, which contains 7000+ files and directories. Note that that dataset can be replaced regularly, and the SHA1 of the snapshot "rootfs.tar.xz" used here is "aee9b01a530078dbef8f08521bfcabe65b244955". ## Image size | Size | Filesystem | Cluster size | Build options | |-----------|------------|--------------|----------------------------------------------------------------| | 124669952 | erofs | uncompressed | -T0 [^1] | | 124522496 | squashfs | uncompressed | -noD -noI -noX -noF -no-xattrs -all-time 0 -no-duplicates [^2] | | 73601024 | squashfs | 4096 | -b 4096 -comp lz4 -Xhc -no-xattrs -all-time 0 | | 73121792 | erofs | 4096 | -zlz4hc,12 [^3] -C4096 -Efragments -T0 | | 67162112 | squashfs | 16384 | -b 16384 -comp lz4 -Xhc -no-xattrs -all-time 0 | | 65478656 | erofs | 16384 | -zlz4hc,12 -C16384 -Efragments -T0 | | 61456384 | squashfs | 65536 | -b 65536 -comp lz4 -Xhc -no-xattrs -all-time 0 | | 59834368 | erofs | 65536 | -zlz4hc,12 -C65536 -Efragments -T0 | | 59150336 | squashfs | 131072 | -b 131072 -comp lz4 -Xhc -no-xattrs -all-time 0 | | 58515456 | erofs | 131072 | -zlz4hc,12 -C131072 -Efragments -T0 | [^1]: Forcely reset all timestamps to match squashfs on-disk basic inodes for now. [^2]: Currently erofs-utils doesn't actively de-duplicate identical files although the on-disk format supports this. [^3]: Because squashfs uses level 12 for LZ4HC by default. ## Sequential data access ```bash hyperfine -p "echo 3 > /proc/sys/vm/drop_caches; sleep 1" "tar cf - . | cat > /dev/null" ``` | Filesystem | Cluster size | Time | |------------|--------------|---------------------------------| | squashfs | 4096 | 10.257 s ± 0.031 s | | erofs | uncompressed | 1.111 s ± 0.022 s | | squashfs | uncompressed | 1.034 s ± 0.020 s | | squashfs | 131072 | 941.3 ms ± 7.5 ms | | erofs | 4096 | 848.1 ms ± 17.8 ms | | erofs | 131072 | 724.2 ms ± 11.0 ms | ## Sequential metadata access ```bash hyperfine -p "echo 3 > /proc/sys/vm/drop_caches; sleep 1" "tar cf /dev/null ." ``` | Filesystem | Cluster size | Time | |------------|--------------|---------------------------------| | erofs | uncompressed | 419.6 ms ± 8.2 ms | | squashfs | 4096 | 142.5 ms ± 5.4 ms | | squashfs | uncompressed | 129.2 ms ± 3.9 ms | | squashfs | 131072 | 125.4 ms ± 4.0 ms | | erofs | 4096 | 75.5 ms ± 3.5 ms | | erofs | 131072 | 65.8 ms ± 3.6 ms | [ Note that erofs-utils currently doesn't perform quite well for such cases due to metadata arrangement when building. It will be fixed in the later versions. ] ## Small random data access (~7%) ```bash find mnt -type f -printf "%p\n" | sort -R | head -n 500 > list.txt hyperfine -p "echo 3 > /proc/sys/vm/drop_caches; sleep 1" "cat list.txt | xargs cat > /dev/null" ``` | Filesystem | Cluster size | Time | |------------|--------------|---------------------------------| | squashfs | 4096 | 1.386 s ± 0.032 s | | squashfs | uncompressed | 1.083 s ± 0.044 s | | squashfs | 131072 | 1.067 s ± 0.046 s | | erofs | 4096 | 249.6 ms ± 6.5 ms | | erofs | uncompressed | 237.8 ms ± 6.3 ms | | erofs | 131072 | 189.6 ms ± 7.8 ms | ## Small random metadata access (~7%) ```bash find mnt -type f -printf "%p\n" | sort -R | head -n 500 > list.txt hyperfine -p "echo 3 > /proc/sys/vm/drop_caches; sleep 1" "cat list.txt | xargs stat" ``` | Filesystem | Cluster size | Time | |------------|--------------|---------------------------------| | squashfs | 4096 | 817.0 ms ± 34.5 ms | | squashfs | 131072 | 801.0 ms ± 40.1 ms | | squashfs | uncompressed | 741.3 ms ± 18.2 ms | | erofs | uncompressed | 197.8 ms ± 4.1 ms | | erofs | 4096 | 63.1 ms ± 2.0 ms | | erofs | 131072 | 60.7 ms ± 3.6 ms | ## Full random data access (~100%) ```bash find mnt -type f -printf "%p\n" | sort -R > list.txt hyperfine -p "echo 3 > /proc/sys/vm/drop_caches; sleep 1" "cat list.txt | xargs cat > /dev/null" ``` | Filesystem | Cluster size | Time | |------------|--------------|---------------------------------| | squashfs | 4096 | 20.668 s ± 0.040 s | | squashfs | uncompressed | 12.543 s ± 0.041 s | | squashfs | 131072 | 11.753 s ± 0.412 s | | erofs | uncompressed | 1.493 s ± 0.023 s | | erofs | 4096 | 1.223 s ± 0.013 s | | erofs | 131072 | 598.2 ms ± 6.6 ms | ## Full random metadata access (~100%) ```bash find mnt -type f -printf "%p\n" | sort -R > list.txt hyperfine -p "echo 3 > /proc/sys/vm/drop_caches; sleep 1" "cat list.txt | xargs stat" ``` | Filesystem | Cluster size | Time | |------------|--------------|---------------------------------| | squashfs | 131072 | 9.212 s ± 0.467 s | | squashfs | 4096 | 8.905 s ± 0.147 s | | squashfs | uncompressed | 7.961 s ± 0.045 s | | erofs | 4096 | 661.2 ms ± 14.9 ms | | erofs | uncompressed | 125.8 ms ± 6.6 ms | | erofs | 131072 | 119.6 ms ± 5.5 ms | # FIO benchmark on a single large file `silesia.tar` (203M) is used to benchmark, which could be generated from unzipping [silesia.zip](http://mattmahoney.net/dc/silesia.zip) and tar. ## Image size | Size | Filesystem | Cluster size | Build options | |-----------|------------|--------------|-----------------------------------------------------------| | 114339840 | squashfs | 4096 | -b 4096 -comp lz4 -Xhc -no-xattrs | | 104972288 | erofs | 4096 | -zlz4hc,12 -C4096 | | 98033664 | squashfs | 16384 | -b 16384 -comp lz4 -Xhc -no-xattrs | | 89571328 | erofs | 16384 | -zlz4hc,12 -C16384 | | 85143552 | squashfs | 65536 | -b 65536 -comp lz4 -Xhc -no-xattrs | | 81211392 | squashfs | 131072 | -b 131072 -comp lz4 -Xhc -no-xattrs | | 80519168 | erofs | 65536 | -zlz4hc,12 -C65536 | | 78888960 | erofs | 131072 | -zlz4hc,12 -C131072 | ## Sequential I/Os ```bash fio -filename=silesia.tar -bs=4k -rw=read -name=job1 ``` | Filesystem | Cluster size | Bandwidth | |------------|--------------|-----------| | erofs | 65536 | 624 MiB/s | | erofs | 16384 | 600 MiB/s | | erofs | 4096 | 569 MiB/s | | erofs | 131072 | 535 MiB/s | | squashfs | 131072 | 236 MiB/s | | squashfs | 65536 | 157 MiB/s | | squashfs | 16384 | 55.2MiB/s | | squashfs | 4096 | 12.5MiB/s | ## Full Random I/Os ```bash fio -filename=silesia.tar -bs=4k -rw=randread -name=job1 ``` | Filesystem | Cluster size | Bandwidth | |------------|--------------|-----------| | erofs | 131072 | 242 MiB/s | | squashfs | 131072 | 232 MiB/s | | erofs | 65536 | 198 MiB/s | | squashfs | 65536 | 150 MiB/s | | erofs | 16384 | 96.4MiB/s | | squashfs | 16384 | 49.5MiB/s | | erofs | 4096 | 33.7MiB/s | | squashfs | 4096 | 6817KiB/s | ## Small Random I/Os (~5%) ```bash fio -filename=silesia.tar -bs=4k -rw=randread --io_size=10m -name=job1 ``` | Filesystem | Cluster size | Bandwidth | |------------|--------------|-----------| | erofs | 131072 | 19.2MiB/s | | erofs | 65536 | 16.9MiB/s | | squashfs | 131072 | 15.1MiB/s | | erofs | 16384 | 14.7MiB/s | | squashfs | 65536 | 13.8MiB/s | | erofs | 4096 | 13.0MiB/s | | squashfs | 16384 | 11.7MiB/s | | squashfs | 4096 | 4376KiB/s | erofs-utils-1.8.6/docs/compress-hints.example000066400000000000000000000005531477440110200212700ustar00rootroot00000000000000# https://github.com/debuerreotype/docker-debian-artifacts/blob/dist-amd64/bullseye/rootfs.tar.xz?raw=true # -zlzma:lz4hc,12:lzma,109 -C131072 --compress-hints=compress-hints.example image size: 66M # -zlz4hc,12 image size: 76M 4096 1 .*\.so.*$ 4096 1 bin/ 4096 1 sbin/ 131072 2 etc/ erofs-utils-1.8.6/dump/000077500000000000000000000000001477440110200147475ustar00rootroot00000000000000erofs-utils-1.8.6/dump/Makefile.am000066400000000000000000000006261477440110200170070ustar00rootroot00000000000000# SPDX-License-Identifier: GPL-2.0+ # Makefile.am AUTOMAKE_OPTIONS = foreign bin_PROGRAMS = dump.erofs AM_CPPFLAGS = ${libuuid_CFLAGS} dump_erofs_SOURCES = main.c dump_erofs_CFLAGS = -Wall -I$(top_srcdir)/include dump_erofs_LDADD = $(top_builddir)/lib/liberofs.la ${libselinux_LIBS} \ ${liblz4_LIBS} ${liblzma_LIBS} ${zlib_LIBS} ${libdeflate_LIBS} \ ${libzstd_LIBS} ${libqpl_LIBS} ${libxxhash_LIBS} erofs-utils-1.8.6/dump/main.c000066400000000000000000000516731477440110200160530ustar00rootroot00000000000000// SPDX-License-Identifier: GPL-2.0+ /* * Copyright (C) 2021-2022 HUAWEI, Inc. * http://www.huawei.com/ * Created by Wang Qi * Guo Xuenan */ #define _GNU_SOURCE #include #include #include #include #include "erofs/print.h" #include "erofs/inode.h" #include "erofs/dir.h" #include "erofs/compress.h" #include "erofs/fragments.h" #include "../lib/liberofs_private.h" #include "../lib/liberofs_uuid.h" struct erofsdump_cfg { unsigned int totalshow; bool show_inode; bool show_extent; bool show_superblock; bool show_statistics; bool show_subdirectories; bool show_file_content; erofs_nid_t nid; const char *inode_path; }; static struct erofsdump_cfg dumpcfg; static const char chart_format[] = "%-16s %-11d %8.2f%% |%-50s|\n"; static const char header_format[] = "%-16s %11s %16s |%-50s|\n"; static char *file_types[] = { ".txt", ".so", ".xml", ".apk", ".odex", ".vdex", ".oat", ".rc", ".otf", "others", }; #define OTHERFILETYPE ARRAY_SIZE(file_types) /* (1 << FILE_MAX_SIZE_BITS)KB */ #define FILE_MAX_SIZE_BITS 16 static const char * const file_category_types[] = { [EROFS_FT_UNKNOWN] = "unknown type", [EROFS_FT_REG_FILE] = "regular file", [EROFS_FT_DIR] = "directory", [EROFS_FT_CHRDEV] = "char dev", [EROFS_FT_BLKDEV] = "block dev", [EROFS_FT_FIFO] = "FIFO file", [EROFS_FT_SOCK] = "SOCK file", [EROFS_FT_SYMLINK] = "symlink file", }; struct erofs_statistics { unsigned long files; unsigned long compressed_files; unsigned long uncompressed_files; unsigned long files_total_size; unsigned long files_total_origin_size; double compress_rate; /* [statistics] # of files based on inode_info->flags */ unsigned long file_category_stat[EROFS_FT_MAX]; /* [statistics] # of files based on file name extensions */ unsigned int file_type_stat[OTHERFILETYPE]; /* [statistics] # of files based on the original size of files */ unsigned int file_original_size[FILE_MAX_SIZE_BITS + 1]; /* [statistics] # of files based on the compressed size of files */ unsigned int file_comp_size[FILE_MAX_SIZE_BITS + 1]; }; static struct erofs_statistics stats; static struct option long_options[] = { {"version", no_argument, NULL, 'V'}, {"help", no_argument, NULL, 'h'}, {"nid", required_argument, NULL, 2}, {"device", required_argument, NULL, 3}, {"path", required_argument, NULL, 4}, {"ls", no_argument, NULL, 5}, {"offset", required_argument, NULL, 6}, {"cat", no_argument, NULL, 7}, {0, 0, 0, 0}, }; struct erofsdump_feature { bool compat; u32 flag; const char *name; }; static struct erofsdump_feature feature_lists[] = { { true, EROFS_FEATURE_COMPAT_SB_CHKSUM, "sb_csum" }, { true, EROFS_FEATURE_COMPAT_MTIME, "mtime" }, { true, EROFS_FEATURE_COMPAT_XATTR_FILTER, "xattr_filter" }, { false, EROFS_FEATURE_INCOMPAT_ZERO_PADDING, "0padding" }, { false, EROFS_FEATURE_INCOMPAT_COMPR_CFGS, "compr_cfgs" }, { false, EROFS_FEATURE_INCOMPAT_BIG_PCLUSTER, "big_pcluster" }, { false, EROFS_FEATURE_INCOMPAT_CHUNKED_FILE, "chunked_file" }, { false, EROFS_FEATURE_INCOMPAT_DEVICE_TABLE, "device_table" }, { false, EROFS_FEATURE_INCOMPAT_ZTAILPACKING, "ztailpacking" }, { false, EROFS_FEATURE_INCOMPAT_FRAGMENTS, "fragments" }, { false, EROFS_FEATURE_INCOMPAT_DEDUPE, "dedupe" }, { false, EROFS_FEATURE_INCOMPAT_XATTR_PREFIXES, "xattr_prefixes" }, }; static int erofsdump_readdir(struct erofs_dir_context *ctx); static void usage(int argc, char **argv) { // " 1 2 3 4 5 6 7 8 " // "12345678901234567890123456789012345678901234567890123456789012345678901234567890\n" printf( "Usage: %s [OPTIONS] IMAGE\n" "Dump erofs layout from IMAGE.\n" "\n" "General options:\n" " -V, --version print the version number of dump.erofs and exit\n" " -h, --help display this help and exit\n" "\n" " -S show statistic information of the image\n" " -e show extent info (INODE required)\n" " -s show information about superblock\n" " --device=X specify an extra device to be used together\n" " --ls show directory contents (INODE required)\n" " --cat show file contents (INODE required)\n" " --nid=# show the target inode info of nid #\n" " --offset=# skip # bytes at the beginning of IMAGE\n" " --path=X show the target inode info of path X\n", argv[0]); } static void erofsdump_print_version(void) { printf("dump.erofs (erofs-utils) %s\n", cfg.c_version); } static int erofsdump_parse_options_cfg(int argc, char **argv) { int opt, err; char *endptr; while ((opt = getopt_long(argc, argv, "SVesh", long_options, NULL)) != -1) { switch (opt) { case 'e': dumpcfg.show_extent = true; ++dumpcfg.totalshow; break; case 's': dumpcfg.show_superblock = true; ++dumpcfg.totalshow; break; case 'S': dumpcfg.show_statistics = true; ++dumpcfg.totalshow; break; case 'V': erofsdump_print_version(); exit(0); case 2: dumpcfg.show_inode = true; dumpcfg.nid = (erofs_nid_t)atoll(optarg); ++dumpcfg.totalshow; break; case 'h': usage(argc, argv); exit(0); case 3: err = erofs_blob_open_ro(&g_sbi, optarg); if (err) return err; ++g_sbi.extra_devices; break; case 4: dumpcfg.inode_path = optarg; dumpcfg.show_inode = true; ++dumpcfg.totalshow; break; case 5: dumpcfg.show_subdirectories = true; break; case 6: g_sbi.bdev.offset = strtoull(optarg, &endptr, 0); if (*endptr != '\0') { erofs_err("invalid disk offset %s", optarg); return -EINVAL; } break; case 7: dumpcfg.show_file_content = true; break; default: return -EINVAL; } } if (optind >= argc) { erofs_err("missing argument: IMAGE"); return -EINVAL; } cfg.c_img_path = strdup(argv[optind++]); if (!cfg.c_img_path) return -ENOMEM; if (optind < argc) { erofs_err("unexpected argument: %s\n", argv[optind]); return -EINVAL; } return 0; } static int erofsdump_get_occupied_size(struct erofs_inode *inode, erofs_off_t *size) { *size = 0; switch (inode->datalayout) { case EROFS_INODE_FLAT_INLINE: case EROFS_INODE_FLAT_PLAIN: case EROFS_INODE_CHUNK_BASED: stats.uncompressed_files++; *size = inode->i_size; break; case EROFS_INODE_COMPRESSED_FULL: case EROFS_INODE_COMPRESSED_COMPACT: stats.compressed_files++; *size = inode->u.i_blocks * erofs_blksiz(inode->sbi); break; default: erofs_err("unknown datalayout"); return -ENOTSUP; } return 0; } static void inc_file_extension_count(const char *dname, unsigned int len) { char *postfix = memrchr(dname, '.', len); int type; if (!postfix) { type = OTHERFILETYPE - 1; } else { for (type = 0; type < OTHERFILETYPE - 1; ++type) if (!strncmp(postfix, file_types[type], len - (postfix - dname))) break; } ++stats.file_type_stat[type]; } static void update_file_size_statistics(erofs_off_t size, bool original) { unsigned int *file_size = original ? stats.file_original_size : stats.file_comp_size; int size_mark = 0; size >>= 10; while (size) { size >>= 1; size_mark++; } if (size_mark >= FILE_MAX_SIZE_BITS) file_size[FILE_MAX_SIZE_BITS]++; else file_size[size_mark]++; } static int erofsdump_ls_dirent_iter(struct erofs_dir_context *ctx) { char fname[EROFS_NAME_LEN + 1]; strncpy(fname, ctx->dname, ctx->de_namelen); fname[ctx->de_namelen] = '\0'; fprintf(stdout, "%10llu %u %s\n", ctx->de_nid | 0ULL, ctx->de_ftype, fname); return 0; } static int erofsdump_dirent_iter(struct erofs_dir_context *ctx) { /* skip "." and ".." dentry */ if (ctx->dot_dotdot) return 0; return erofsdump_readdir(ctx); } static int erofsdump_read_packed_inode(void) { int err; erofs_off_t occupied_size = 0; struct erofs_inode vi = { .sbi = &g_sbi, .nid = g_sbi.packed_nid }; if (!(erofs_sb_has_fragments(&g_sbi) && g_sbi.packed_nid > 0)) return 0; err = erofs_read_inode_from_disk(&vi); if (err) { erofs_err("failed to read packed file inode from disk"); return err; } err = erofsdump_get_occupied_size(&vi, &occupied_size); if (err) { erofs_err("failed to get the file size of packed inode"); return err; } stats.files_total_size += occupied_size; update_file_size_statistics(occupied_size, false); return 0; } static int erofsdump_readdir(struct erofs_dir_context *ctx) { int err; erofs_off_t occupied_size = 0; struct erofs_inode vi = { .sbi = &g_sbi, .nid = ctx->de_nid }; err = erofs_read_inode_from_disk(&vi); if (err) { erofs_err("failed to read file inode from disk"); return err; } stats.files++; stats.file_category_stat[erofs_mode_to_ftype(vi.i_mode)]++; err = erofsdump_get_occupied_size(&vi, &occupied_size); if (err) { erofs_err("get file size failed"); return err; } if (S_ISREG(vi.i_mode)) { stats.files_total_origin_size += vi.i_size; inc_file_extension_count(ctx->dname, ctx->de_namelen); stats.files_total_size += occupied_size; update_file_size_statistics(vi.i_size, true); update_file_size_statistics(occupied_size, false); } /* XXXX: the dir depth should be restricted in order to avoid loops */ if (S_ISDIR(vi.i_mode)) { struct erofs_dir_context nctx = { .flags = ctx->dir ? EROFS_READDIR_VALID_PNID : 0, .pnid = ctx->dir ? ctx->dir->nid : 0, .dir = &vi, .cb = erofsdump_dirent_iter, }; return erofs_iterate_dir(&nctx, false); } return 0; } static void erofsdump_show_fileinfo(bool show_extent) { const char *ext_fmt[] = { "%4d: %8" PRIu64 "..%8" PRIu64 " | %7" PRIu64 " : %10" PRIu64 "..%10" PRIu64 " | %7" PRIu64 "\n", "%4d: %8" PRIu64 "..%8" PRIu64 " | %7" PRIu64 " : %10" PRIu64 "..%10" PRIu64 " | %7" PRIu64 " # device %u\n" }; int err, i; erofs_off_t size; u16 access_mode; struct erofs_inode inode = { .sbi = &g_sbi, .nid = dumpcfg.nid }; char path[PATH_MAX]; char access_mode_str[] = "rwxrwxrwx"; char timebuf[128] = {0}; unsigned int extent_count = 0; struct erofs_map_blocks map = { .index = UINT_MAX, .m_la = 0, }; if (dumpcfg.inode_path) { err = erofs_ilookup(dumpcfg.inode_path, &inode); if (err) { erofs_err("read inode failed @ %s", dumpcfg.inode_path); return; } } else { err = erofs_read_inode_from_disk(&inode); if (err) { erofs_err("read inode failed @ nid %llu", inode.nid | 0ULL); return; } } err = erofs_get_occupied_size(&inode, &size); if (err) { erofs_err("get file size failed @ nid %llu", inode.nid | 0ULL); return; } err = erofs_get_pathname(inode.sbi, inode.nid, path, sizeof(path)); if (err < 0) { strncpy(path, "(not found)", sizeof(path) - 1); path[sizeof(path) - 1] = '\0'; } strftime(timebuf, sizeof(timebuf), "%Y-%m-%d %H:%M:%S", localtime((time_t *)&inode.i_mtime)); access_mode = inode.i_mode & 0777; for (i = 8; i >= 0; i--) if (((access_mode >> i) & 1) == 0) access_mode_str[8 - i] = '-'; fprintf(stdout, "Path : %s\n", erofs_is_packed_inode(&inode) ? "(packed file)" : path); fprintf(stdout, "Size: %" PRIu64" On-disk size: %" PRIu64 " %s\n", inode.i_size, size, file_category_types[erofs_mode_to_ftype(inode.i_mode)]); fprintf(stdout, "NID: %" PRIu64 " ", inode.nid); fprintf(stdout, "Links: %u ", inode.i_nlink); fprintf(stdout, "Layout: %d Compression ratio: %.2f%%\n", inode.datalayout, (double)(100 * size) / (double)(inode.i_size)); fprintf(stdout, "Inode size: %d ", inode.inode_isize); fprintf(stdout, "Xattr size: %u\n", inode.xattr_isize); fprintf(stdout, "Uid: %u Gid: %u ", inode.i_uid, inode.i_gid); fprintf(stdout, "Access: %04o/%s\n", access_mode, access_mode_str); fprintf(stdout, "Timestamp: %s.%09d\n", timebuf, inode.i_mtime_nsec); if (dumpcfg.show_subdirectories) { struct erofs_dir_context ctx = { .flags = EROFS_READDIR_VALID_PNID, .pnid = inode.nid, .dir = &inode, .cb = erofsdump_ls_dirent_iter, .de_nid = 0, .dname = "", .de_namelen = 0, }; fprintf(stdout, "\n NID TYPE FILENAME\n"); err = erofs_iterate_dir(&ctx, false); if (err) { erofs_err("failed to list directory contents"); return; } } if (!dumpcfg.show_extent) return; fprintf(stdout, "\n Ext: logical offset | length : physical offset | length\n"); while (map.m_la < inode.i_size) { struct erofs_map_dev mdev; err = erofs_map_blocks(&inode, &map, EROFS_GET_BLOCKS_FIEMAP); if (err) { erofs_err("failed to get file blocks range"); return; } mdev = (struct erofs_map_dev) { .m_deviceid = map.m_deviceid, .m_pa = map.m_pa, }; err = erofs_map_dev(inode.sbi, &mdev); if (err) { erofs_err("failed to map device"); return; } if (map.m_flags & EROFS_MAP_FRAGMENT) fprintf(stdout, ext_fmt[!!mdev.m_deviceid], extent_count++, map.m_la, map.m_la + map.m_llen, map.m_llen, 0, 0, 0, mdev.m_deviceid); else fprintf(stdout, ext_fmt[!!mdev.m_deviceid], extent_count++, map.m_la, map.m_la + map.m_llen, map.m_llen, mdev.m_pa, mdev.m_pa + map.m_plen, map.m_plen, mdev.m_deviceid); map.m_la += map.m_llen; } fprintf(stdout, "%s: %d extents found\n", erofs_is_packed_inode(&inode) ? "(packed file)" : path, extent_count); } static void erofsdump_filesize_distribution(const char *title, unsigned int *file_counts, unsigned int len) { char col1[30]; unsigned int col2, i, lowerbound, upperbound; double col3; char col4[400]; lowerbound = 0; upperbound = 1; fprintf(stdout, "\n%s file size distribution:\n", title); fprintf(stdout, header_format, ">=(KB) .. <(KB) ", "count", "ratio", "distribution"); for (i = 0; i < len; i++) { memset(col1, 0, sizeof(col1)); memset(col4, 0, sizeof(col4)); if (i == len - 1) sprintf(col1, "%6d ..", lowerbound); else if (i <= 6) sprintf(col1, "%6d .. %-6d", lowerbound, upperbound); else sprintf(col1, "%6d .. %-6d", lowerbound, upperbound); col2 = file_counts[i]; if (stats.file_category_stat[EROFS_FT_REG_FILE]) col3 = (double)(100 * col2) / stats.file_category_stat[EROFS_FT_REG_FILE]; else col3 = 0.0; memset(col4, '#', col3 / 2); fprintf(stdout, chart_format, col1, col2, col3, col4); lowerbound = upperbound; upperbound <<= 1; } } static void erofsdump_filetype_distribution(char **file_types, unsigned int len) { char col1[30]; unsigned int col2, i; double col3; char col4[401]; fprintf(stdout, "\nFile type distribution:\n"); fprintf(stdout, header_format, "type", "count", "ratio", "distribution"); for (i = 0; i < len; i++) { memset(col1, 0, sizeof(col1)); memset(col4, 0, sizeof(col4)); sprintf(col1, "%-17s", file_types[i]); col2 = stats.file_type_stat[i]; if (stats.file_category_stat[EROFS_FT_REG_FILE]) col3 = (double)(100 * col2) / stats.file_category_stat[EROFS_FT_REG_FILE]; else col3 = 0.0; memset(col4, '#', col3 / 2); fprintf(stdout, chart_format, col1, col2, col3, col4); } } static void erofsdump_file_statistic(void) { unsigned int i; fprintf(stdout, "Filesystem total file count: %lu\n", stats.files); for (i = 0; i < EROFS_FT_MAX; i++) fprintf(stdout, "Filesystem %s count: %lu\n", file_category_types[i], stats.file_category_stat[i]); stats.compress_rate = (double)(100 * stats.files_total_size) / (double)(stats.files_total_origin_size); fprintf(stdout, "Filesystem compressed files: %lu\n", stats.compressed_files); fprintf(stdout, "Filesystem uncompressed files: %lu\n", stats.uncompressed_files); fprintf(stdout, "Filesystem total original file size: %lu Bytes\n", stats.files_total_origin_size); fprintf(stdout, "Filesystem total file size: %lu Bytes\n", stats.files_total_size); fprintf(stdout, "Filesystem compress rate: %.2f%%\n", stats.compress_rate); } static void erofsdump_print_statistic(void) { int err; struct erofs_dir_context ctx = { .flags = 0, .pnid = 0, .dir = NULL, .cb = erofsdump_dirent_iter, .de_nid = g_sbi.root_nid, .dname = "", .de_namelen = 0, }; err = erofsdump_readdir(&ctx); if (err) { erofs_err("read dir failed"); return; } err = erofsdump_read_packed_inode(); if (err) { erofs_err("failed to read packed inode"); return; } erofsdump_file_statistic(); erofsdump_filesize_distribution("Original", stats.file_original_size, ARRAY_SIZE(stats.file_original_size)); erofsdump_filesize_distribution("On-disk", stats.file_comp_size, ARRAY_SIZE(stats.file_comp_size)); erofsdump_filetype_distribution(file_types, OTHERFILETYPE); } static void erofsdump_print_supported_compressors(FILE *f, unsigned int mask) { unsigned int i = 0; bool comma = false; const char *s; while ((s = z_erofs_list_supported_algorithms(i++, &mask)) != NULL) { if (*s == '\0') continue; if (comma) fputs(", ", f); fputs(s, f); comma = true; } fputc('\n', f); } static void erofsdump_show_superblock(void) { time_t time = g_sbi.build_time; char uuid_str[37]; int i = 0; fprintf(stdout, "Filesystem magic number: 0x%04X\n", EROFS_SUPER_MAGIC_V1); fprintf(stdout, "Filesystem blocksize: %u\n", erofs_blksiz(&g_sbi)); fprintf(stdout, "Filesystem blocks: %llu\n", g_sbi.total_blocks | 0ULL); fprintf(stdout, "Filesystem inode metadata start block: %u\n", g_sbi.meta_blkaddr); fprintf(stdout, "Filesystem shared xattr metadata start block: %u\n", g_sbi.xattr_blkaddr); fprintf(stdout, "Filesystem root nid: %llu\n", g_sbi.root_nid | 0ULL); if (erofs_sb_has_fragments(&g_sbi) && g_sbi.packed_nid > 0) fprintf(stdout, "Filesystem packed nid: %llu\n", g_sbi.packed_nid | 0ULL); if (erofs_sb_has_compr_cfgs(&g_sbi)) { fprintf(stdout, "Filesystem compr_algs: "); erofsdump_print_supported_compressors(stdout, g_sbi.available_compr_algs); } else { fprintf(stdout, "Filesystem lz4_max_distance: %u\n", g_sbi.lz4.max_distance | 0U); } fprintf(stdout, "Filesystem sb_size: %u\n", g_sbi.sb_size | 0U); fprintf(stdout, "Filesystem inode count: %llu\n", g_sbi.inos | 0ULL); fprintf(stdout, "Filesystem created: %s", ctime(&time)); fprintf(stdout, "Filesystem features: "); for (; i < ARRAY_SIZE(feature_lists); i++) { u32 feat = le32_to_cpu(feature_lists[i].compat ? g_sbi.feature_compat : g_sbi.feature_incompat); if (feat & feature_lists[i].flag) fprintf(stdout, "%s ", feature_lists[i].name); } erofs_uuid_unparse_lower(g_sbi.uuid, uuid_str); fprintf(stdout, "\nFilesystem UUID: %s\n", uuid_str); } static void erofsdump_show_file_content(void) { int err; struct erofs_inode inode = { .sbi = &g_sbi, .nid = dumpcfg.nid }; size_t buffer_size; char *buffer_ptr; erofs_off_t pending_size; erofs_off_t read_offset; erofs_off_t read_size; if (dumpcfg.inode_path) { err = erofs_ilookup(dumpcfg.inode_path, &inode); if (err) { erofs_err("read inode failed @ %s", dumpcfg.inode_path); return; } } else { err = erofs_read_inode_from_disk(&inode); if (err) { erofs_err("read inode failed @ nid %llu", inode.nid | 0ULL); return; } } buffer_size = erofs_blksiz(inode.sbi); buffer_ptr = malloc(buffer_size); if (!buffer_ptr) { erofs_err("buffer allocation failed @ nid %llu", inode.nid | 0ULL); return; } pending_size = inode.i_size; read_offset = 0; while (pending_size > 0) { read_size = pending_size > buffer_size? buffer_size: pending_size; err = erofs_pread(&inode, buffer_ptr, read_size, read_offset); if (err) { erofs_err("read file failed @ nid %llu", inode.nid | 0ULL); goto out; } pending_size -= read_size; read_offset += read_size; fwrite(buffer_ptr, read_size, 1, stdout); } fflush(stdout); out: free(buffer_ptr); } int main(int argc, char **argv) { int err; erofs_init_configure(); err = erofsdump_parse_options_cfg(argc, argv); if (err) { if (err == -EINVAL) fprintf(stderr, "Try '%s --help' for more information.\n", argv[0]); goto exit; } err = erofs_dev_open(&g_sbi, cfg.c_img_path, O_RDONLY | O_TRUNC); if (err) { erofs_err("failed to open image file"); goto exit; } err = erofs_read_superblock(&g_sbi); if (err) { erofs_err("failed to read superblock"); goto exit_dev_close; } if (dumpcfg.show_file_content) { if (dumpcfg.show_superblock || dumpcfg.show_statistics || dumpcfg.show_subdirectories) { fprintf(stderr, "The '--cat' flag is incompatible with '-S', '-e', '-s' and '--ls'.\n"); goto exit_dev_close; } erofsdump_show_file_content(); goto exit_dev_close; } if (!dumpcfg.totalshow) { dumpcfg.show_superblock = true; dumpcfg.totalshow = 1; } if (dumpcfg.show_superblock) erofsdump_show_superblock(); if (dumpcfg.show_statistics) erofsdump_print_statistic(); if (dumpcfg.show_extent && !dumpcfg.show_inode) { fprintf(stderr, "Try '%s --help' for more information.\n", argv[0]); goto exit_put_super; } if (dumpcfg.show_inode) erofsdump_show_fileinfo(dumpcfg.show_extent); exit_put_super: erofs_put_super(&g_sbi); exit_dev_close: erofs_dev_close(&g_sbi); exit: erofs_blob_closeall(&g_sbi); erofs_exit_configure(); return err; } erofs-utils-1.8.6/fsck/000077500000000000000000000000001477440110200147305ustar00rootroot00000000000000erofs-utils-1.8.6/fsck/Makefile.am000066400000000000000000000014451477440110200167700ustar00rootroot00000000000000# SPDX-License-Identifier: GPL-2.0+ # Makefile.am AUTOMAKE_OPTIONS = foreign bin_PROGRAMS = fsck.erofs AM_CPPFLAGS = ${libuuid_CFLAGS} fsck_erofs_SOURCES = main.c fsck_erofs_CFLAGS = -Wall -I$(top_srcdir)/include fsck_erofs_LDADD = $(top_builddir)/lib/liberofs.la ${libselinux_LIBS} \ ${liblz4_LIBS} ${liblzma_LIBS} ${zlib_LIBS} ${libdeflate_LIBS} \ ${libzstd_LIBS} ${libqpl_LIBS} ${libxxhash_LIBS} if ENABLE_FUZZING noinst_PROGRAMS = fuzz_erofsfsck fuzz_erofsfsck_SOURCES = main.c fuzz_erofsfsck_CFLAGS = -Wall -I$(top_srcdir)/include -DFUZZING fuzz_erofsfsck_LDFLAGS = -fsanitize=address,fuzzer fuzz_erofsfsck_LDADD = $(top_builddir)/lib/liberofs.la ${libselinux_LIBS} \ ${liblz4_LIBS} ${liblzma_LIBS} ${zlib_LIBS} ${libdeflate_LIBS} \ ${libzstd_LIBS} ${libqpl_LIBS} ${libxxhash_LIBS} endif erofs-utils-1.8.6/fsck/main.c000066400000000000000000000674471477440110200160420ustar00rootroot00000000000000// SPDX-License-Identifier: GPL-2.0+ /* * Copyright 2021 Google LLC * Author: Daeho Jeong */ #include #include #include #include #include #include #include #include "erofs/print.h" #include "erofs/compress.h" #include "erofs/decompress.h" #include "erofs/dir.h" #include "erofs/xattr.h" #include "../lib/compressor.h" #include "erofs/fragments.h" static int erofsfsck_check_inode(erofs_nid_t pnid, erofs_nid_t nid); struct erofsfsck_dirstack { erofs_nid_t dirs[PATH_MAX]; int top; }; struct erofsfsck_cfg { struct erofsfsck_dirstack dirstack; u64 physical_blocks; u64 logical_blocks; char *extract_path; size_t extract_pos; mode_t umask; bool superuser; bool corrupted; bool print_comp_ratio; bool check_decomp; bool force; bool overwrite; bool preserve_owner; bool preserve_perms; bool dump_xattrs; }; static struct erofsfsck_cfg fsckcfg; static struct option long_options[] = { {"version", no_argument, 0, 'V'}, {"help", no_argument, 0, 'h'}, {"extract", optional_argument, 0, 2}, {"device", required_argument, 0, 3}, {"force", no_argument, 0, 4}, {"overwrite", no_argument, 0, 5}, {"preserve", no_argument, 0, 6}, {"preserve-owner", no_argument, 0, 7}, {"preserve-perms", no_argument, 0, 8}, {"no-preserve", no_argument, 0, 9}, {"no-preserve-owner", no_argument, 0, 10}, {"no-preserve-perms", no_argument, 0, 11}, {"offset", required_argument, 0, 12}, {"xattrs", no_argument, 0, 13}, {"no-xattrs", no_argument, 0, 14}, {0, 0, 0, 0}, }; #define NR_HARDLINK_HASHTABLE 16384 struct erofsfsck_hardlink_entry { struct list_head list; erofs_nid_t nid; char *path; }; static struct list_head erofsfsck_link_hashtable[NR_HARDLINK_HASHTABLE]; static void print_available_decompressors(FILE *f, const char *delim) { int i = 0; bool comma = false; const struct erofs_algorithm *s; while ((s = z_erofs_list_available_compressors(&i)) != NULL) { if (comma) fputs(delim, f); fputs(s->name, f); comma = true; } fputc('\n', f); } static void usage(int argc, char **argv) { // " 1 2 3 4 5 6 7 8 " // "12345678901234567890123456789012345678901234567890123456789012345678901234567890\n" printf( "Usage: %s [OPTIONS] IMAGE\n" "Check erofs filesystem compatibility and integrity of IMAGE.\n" "\n" "This version of fsck.erofs is capable of checking images that use any of the\n" "following algorithms: ", argv[0]); print_available_decompressors(stdout, ", "); printf("\n" "General options:\n" " -V, --version print the version number of fsck.erofs and exit\n" " -h, --help display this help and exit\n" "\n" " -d<0-9> set output verbosity; 0=quiet, 9=verbose (default=%i)\n" " -p print total compression ratio of all files\n" " --device=X specify an extra device to be used together\n" " --extract[=X] check if all files are well encoded, optionally\n" " extract to X\n" " --offset=# skip # bytes at the beginning of IMAGE\n" " --[no-]xattrs whether to dump extended attributes (default off)\n" "\n" " -a, -A, -y no-op, for compatibility with fsck of other filesystems\n" "\n" "Extraction options (--extract=X is required):\n" " --force allow extracting to root\n" " --overwrite overwrite files that already exist\n" " --[no-]preserve same as --[no-]preserve-owner --[no-]preserve-perms\n" " --[no-]preserve-owner whether to preserve the ownership from the\n" " filesystem (default for superuser), or to extract as\n" " yourself (default for ordinary users)\n" " --[no-]preserve-perms whether to preserve the exact permissions from the\n" " filesystem without applying umask (default for\n" " superuser), or to modify the permissions by applying\n" " umask (default for ordinary users)\n", EROFS_WARN); } static void erofsfsck_print_version(void) { printf("fsck.erofs (erofs-utils) %s\navailable decompressors: ", cfg.c_version); print_available_decompressors(stdout, ", "); } static int erofsfsck_parse_options_cfg(int argc, char **argv) { char *endptr; int opt, ret; bool has_opt_preserve = false; while ((opt = getopt_long(argc, argv, "Vd:phaAy", long_options, NULL)) != -1) { switch (opt) { case 'V': erofsfsck_print_version(); exit(0); case 'd': ret = atoi(optarg); if (ret < EROFS_MSG_MIN || ret > EROFS_MSG_MAX) { erofs_err("invalid debug level %d", ret); return -EINVAL; } cfg.c_dbg_lvl = ret; break; case 'p': fsckcfg.print_comp_ratio = true; break; case 'h': usage(argc, argv); exit(0); case 'a': case 'A': case 'y': break; case 2: fsckcfg.check_decomp = true; if (optarg) { size_t len = strlen(optarg); if (len == 0) { erofs_err("empty value given for --extract=X"); return -EINVAL; } /* remove trailing slashes except root */ while (len > 1 && optarg[len - 1] == '/') len--; if (len >= PATH_MAX) { erofs_err("target directory name too long!"); return -ENAMETOOLONG; } fsckcfg.extract_path = malloc(PATH_MAX); if (!fsckcfg.extract_path) return -ENOMEM; strncpy(fsckcfg.extract_path, optarg, len); fsckcfg.extract_path[len] = '\0'; /* if path is root, start writing from position 0 */ if (len == 1 && fsckcfg.extract_path[0] == '/') len = 0; fsckcfg.extract_pos = len; } break; case 3: ret = erofs_blob_open_ro(&g_sbi, optarg); if (ret) return ret; ++g_sbi.extra_devices; break; case 4: fsckcfg.force = true; break; case 5: fsckcfg.overwrite = true; break; case 6: fsckcfg.preserve_owner = fsckcfg.preserve_perms = true; has_opt_preserve = true; break; case 7: fsckcfg.preserve_owner = true; has_opt_preserve = true; break; case 8: fsckcfg.preserve_perms = true; has_opt_preserve = true; break; case 9: fsckcfg.preserve_owner = fsckcfg.preserve_perms = false; has_opt_preserve = true; break; case 10: fsckcfg.preserve_owner = false; has_opt_preserve = true; break; case 11: fsckcfg.preserve_perms = false; has_opt_preserve = true; break; case 12: g_sbi.bdev.offset = strtoull(optarg, &endptr, 0); if (*endptr != '\0') { erofs_err("invalid disk offset %s", optarg); return -EINVAL; } break; case 13: fsckcfg.dump_xattrs = true; break; case 14: fsckcfg.dump_xattrs = false; break; default: return -EINVAL; } } if (fsckcfg.extract_path) { if (!fsckcfg.extract_pos && !fsckcfg.force) { erofs_err("--extract=/ must be used together with --force"); return -EINVAL; } } else { if (fsckcfg.force) { erofs_err("--force must be used together with --extract=X"); return -EINVAL; } if (fsckcfg.overwrite) { erofs_err("--overwrite must be used together with --extract=X"); return -EINVAL; } if (has_opt_preserve) { erofs_err("--[no-]preserve[-owner/-perms] must be used together with --extract=X"); return -EINVAL; } } if (optind >= argc) { erofs_err("missing argument: IMAGE"); return -EINVAL; } cfg.c_img_path = strdup(argv[optind++]); if (!cfg.c_img_path) return -ENOMEM; if (optind < argc) { erofs_err("unexpected argument: %s", argv[optind]); return -EINVAL; } return 0; } static void erofsfsck_set_attributes(struct erofs_inode *inode, char *path) { int ret; /* don't apply attributes when fsck is used without extraction */ if (!fsckcfg.extract_path) return; #ifdef HAVE_UTIMENSAT if (utimensat(AT_FDCWD, path, (struct timespec []) { [0] = { .tv_sec = inode->i_mtime, .tv_nsec = inode->i_mtime_nsec }, [1] = { .tv_sec = inode->i_mtime, .tv_nsec = inode->i_mtime_nsec }, }, AT_SYMLINK_NOFOLLOW) < 0) #else if (utime(path, &((struct utimbuf){.actime = inode->i_mtime, .modtime = inode->i_mtime})) < 0) #endif erofs_warn("failed to set times: %s", path); if (fsckcfg.preserve_owner) { ret = lchown(path, inode->i_uid, inode->i_gid); if (ret < 0) erofs_warn("failed to change ownership: %s", path); } if (!S_ISLNK(inode->i_mode)) { if (fsckcfg.preserve_perms) ret = chmod(path, inode->i_mode); else ret = chmod(path, inode->i_mode & ~fsckcfg.umask); if (ret < 0) erofs_warn("failed to set permissions: %s", path); } } static int erofs_check_sb_chksum(void) { #ifndef FUZZING u8 buf[EROFS_MAX_BLOCK_SIZE]; u32 crc; struct erofs_super_block *sb; int ret; ret = erofs_blk_read(&g_sbi, 0, buf, 0, 1); if (ret) { erofs_err("failed to read superblock to check checksum: %d", ret); return -1; } sb = (struct erofs_super_block *)(buf + EROFS_SUPER_OFFSET); sb->checksum = 0; crc = erofs_crc32c(~0, (u8 *)sb, erofs_blksiz(&g_sbi) - EROFS_SUPER_OFFSET); if (crc != g_sbi.checksum) { erofs_err("superblock chksum doesn't match: saved(%08xh) calculated(%08xh)", g_sbi.checksum, crc); fsckcfg.corrupted = true; return -1; } #endif return 0; } static int erofs_verify_xattr(struct erofs_inode *inode) { struct erofs_sb_info *sbi = inode->sbi; unsigned int xattr_hdr_size = sizeof(struct erofs_xattr_ibody_header); unsigned int xattr_entry_size = sizeof(struct erofs_xattr_entry); erofs_off_t addr; unsigned int ofs, xattr_shared_count; struct erofs_xattr_ibody_header *ih; struct erofs_xattr_entry *entry; int i, remaining = inode->xattr_isize, ret = 0; char buf[EROFS_MAX_BLOCK_SIZE]; if (inode->xattr_isize == xattr_hdr_size) { erofs_err("xattr_isize %d of nid %llu is not supported yet", inode->xattr_isize, inode->nid | 0ULL); ret = -EFSCORRUPTED; goto out; } else if (inode->xattr_isize < xattr_hdr_size) { if (inode->xattr_isize) { erofs_err("bogus xattr ibody @ nid %llu", inode->nid | 0ULL); ret = -EFSCORRUPTED; goto out; } } addr = erofs_iloc(inode) + inode->inode_isize; ret = erofs_dev_read(sbi, 0, buf, addr, xattr_hdr_size); if (ret < 0) { erofs_err("failed to read xattr header @ nid %llu: %d", inode->nid | 0ULL, ret); goto out; } ih = (struct erofs_xattr_ibody_header *)buf; xattr_shared_count = ih->h_shared_count; ofs = erofs_blkoff(sbi, addr) + xattr_hdr_size; addr += xattr_hdr_size; remaining -= xattr_hdr_size; for (i = 0; i < xattr_shared_count; ++i) { if (ofs >= erofs_blksiz(sbi)) { if (ofs != erofs_blksiz(sbi)) { erofs_err("unaligned xattr entry in xattr shared area @ nid %llu", inode->nid | 0ULL); ret = -EFSCORRUPTED; goto out; } ofs = 0; } ofs += xattr_entry_size; addr += xattr_entry_size; remaining -= xattr_entry_size; } while (remaining > 0) { unsigned int entry_sz; ret = erofs_dev_read(sbi, 0, buf, addr, xattr_entry_size); if (ret) { erofs_err("failed to read xattr entry @ nid %llu: %d", inode->nid | 0ULL, ret); goto out; } entry = (struct erofs_xattr_entry *)buf; entry_sz = erofs_xattr_entry_size(entry); if (remaining < entry_sz) { erofs_err("xattr on-disk corruption: xattr entry beyond xattr_isize @ nid %llu", inode->nid | 0ULL); ret = -EFSCORRUPTED; goto out; } addr += entry_sz; remaining -= entry_sz; } out: return ret; } static int erofsfsck_dump_xattrs(struct erofs_inode *inode) { static bool ignore_xattrs = false; char *keylst, *key; ssize_t kllen; int ret; kllen = erofs_listxattr(inode, NULL, 0); if (kllen <= 0) return kllen; keylst = malloc(kllen); if (!keylst) return -ENOMEM; ret = erofs_listxattr(inode, keylst, kllen); if (ret != kllen) { erofs_err("failed to list xattrs @ nid %llu", inode->nid | 0ULL); ret = -EINVAL; goto out; } ret = 0; for (key = keylst; key < keylst + kllen; key += strlen(key) + 1) { unsigned int index, len; void *value = NULL; size_t size = 0; ret = erofs_getxattr(inode, key, NULL, 0); if (ret <= 0) { DBG_BUGON(1); erofs_err("failed to get xattr value size of `%s` @ nid %llu", key, inode->nid | 0ULL); break; } size = ret; value = malloc(size); if (!value) { ret = -ENOMEM; break; } ret = erofs_getxattr(inode, key, value, size); if (ret < 0) { erofs_err("failed to get xattr `%s` @ nid %llu, because of `%s`", key, inode->nid | 0ULL, erofs_strerror(ret)); free(value); break; } if (fsckcfg.extract_path) #ifdef HAVE_LSETXATTR ret = lsetxattr(fsckcfg.extract_path, key, value, size, 0); #elif defined(__APPLE__) ret = setxattr(fsckcfg.extract_path, key, value, size, 0, XATTR_NOFOLLOW); #else ret = -EOPNOTSUPP; #endif else ret = 0; free(value); if (ret == -EPERM && !fsckcfg.superuser) { if (__erofs_unlikely(!erofs_xattr_prefix_matches(key, &index, &len))) { erofs_err("failed to match the prefix of `%s` @ nid %llu", key, inode->nid | 0ULL); ret = -EINVAL; break; } if (index != EROFS_XATTR_INDEX_USER) { if (!ignore_xattrs) { erofs_warn("ignored xattr `%s` @ nid %llu, due to non-superuser", key, inode->nid | 0ULL); ignore_xattrs = true; } ret = 0; continue; } } if (ret) { erofs_err("failed to set xattr `%s` @ nid %llu because of `%s`", key, inode->nid | 0ULL, erofs_strerror(ret)); break; } } out: free(keylst); return ret; } static int erofs_verify_inode_data(struct erofs_inode *inode, int outfd) { struct erofs_map_blocks map = { .index = UINT_MAX, }; bool needdecode = fsckcfg.check_decomp && !erofs_is_packed_inode(inode); int ret = 0; bool compressed; erofs_off_t pos = 0; u64 pchunk_len = 0; unsigned int raw_size = 0, buffer_size = 0; char *raw = NULL, *buffer = NULL; erofs_dbg("verify data chunk of nid(%llu): type(%d)", inode->nid | 0ULL, inode->datalayout); compressed = erofs_inode_is_data_compressed(inode->datalayout); while (pos < inode->i_size) { unsigned int alloc_rawsize; map.m_la = pos; ret = erofs_map_blocks(inode, &map, EROFS_GET_BLOCKS_FIEMAP); if (ret) goto out; if (!compressed && map.m_llen != map.m_plen) { erofs_err("broken chunk length m_la %" PRIu64 " m_llen %" PRIu64 " m_plen %" PRIu64, map.m_la, map.m_llen, map.m_plen); ret = -EFSCORRUPTED; goto out; } /* the last lcluster can be divided into 3 parts */ if (map.m_la + map.m_llen > inode->i_size) map.m_llen = inode->i_size - map.m_la; pchunk_len += map.m_plen; pos += map.m_llen; /* should skip decomp? */ if (map.m_la >= inode->i_size || !needdecode) continue; if (outfd >= 0 && !(map.m_flags & EROFS_MAP_MAPPED)) { ret = lseek(outfd, map.m_llen, SEEK_CUR); if (ret < 0) { ret = -errno; goto out; } continue; } if (map.m_plen > Z_EROFS_PCLUSTER_MAX_SIZE) { if (compressed) { erofs_err("invalid pcluster size %" PRIu64 " @ offset %" PRIu64 " of nid %" PRIu64, map.m_plen, map.m_la, inode->nid | 0ULL); ret = -EFSCORRUPTED; goto out; } alloc_rawsize = Z_EROFS_PCLUSTER_MAX_SIZE; } else { alloc_rawsize = map.m_plen; } if (alloc_rawsize > raw_size) { char *newraw = realloc(raw, alloc_rawsize); if (!newraw) { ret = -ENOMEM; goto out; } raw = newraw; raw_size = alloc_rawsize; } if (compressed) { if (map.m_llen > buffer_size) { char *newbuffer; buffer_size = map.m_llen; newbuffer = realloc(buffer, buffer_size); if (!newbuffer) { ret = -ENOMEM; goto out; } buffer = newbuffer; } ret = z_erofs_read_one_data(inode, &map, raw, buffer, 0, map.m_llen, false); if (ret) goto out; if (outfd >= 0 && write(outfd, buffer, map.m_llen) < 0) goto fail_eio; } else { u64 p = 0; do { u64 count = min_t(u64, alloc_rawsize, map.m_llen); ret = erofs_read_one_data(inode, &map, raw, p, count); if (ret) goto out; if (outfd >= 0 && write(outfd, raw, count) < 0) goto fail_eio; map.m_llen -= count; p += count; } while (map.m_llen); } } if (fsckcfg.print_comp_ratio) { if (!erofs_is_packed_inode(inode)) fsckcfg.logical_blocks += BLK_ROUND_UP(inode->sbi, inode->i_size); fsckcfg.physical_blocks += BLK_ROUND_UP(inode->sbi, pchunk_len); } out: if (raw) free(raw); if (buffer) free(buffer); return ret < 0 ? ret : 0; fail_eio: erofs_err("I/O error occurred when verifying data chunk @ nid %llu", inode->nid | 0ULL); ret = -EIO; goto out; } static inline int erofs_extract_dir(struct erofs_inode *inode) { int ret; erofs_dbg("create directory %s", fsckcfg.extract_path); /* verify data chunk layout */ ret = erofs_verify_inode_data(inode, -1); if (ret) return ret; /* * Make directory with default user rwx permissions rather than * the permissions from the filesystem, as these may not have * write/execute permission. These are fixed up later in * erofsfsck_set_attributes(). */ if (mkdir(fsckcfg.extract_path, 0700) < 0) { struct stat st; if (errno != EEXIST) { erofs_err("failed to create directory: %s (%s)", fsckcfg.extract_path, strerror(errno)); return -errno; } if (lstat(fsckcfg.extract_path, &st) || !S_ISDIR(st.st_mode)) { erofs_err("path is not a directory: %s", fsckcfg.extract_path); return -ENOTDIR; } /* * Try to change permissions of existing directory so * that we can write to it */ if (chmod(fsckcfg.extract_path, 0700) < 0) { erofs_err("failed to set permissions: %s (%s)", fsckcfg.extract_path, strerror(errno)); return -errno; } } return 0; } static char *erofsfsck_hardlink_find(erofs_nid_t nid) { struct list_head *head = &erofsfsck_link_hashtable[nid % NR_HARDLINK_HASHTABLE]; struct erofsfsck_hardlink_entry *entry; list_for_each_entry(entry, head, list) if (entry->nid == nid) return entry->path; return NULL; } static int erofsfsck_hardlink_insert(erofs_nid_t nid, const char *path) { struct erofsfsck_hardlink_entry *entry; entry = malloc(sizeof(*entry)); if (!entry) return -ENOMEM; entry->nid = nid; entry->path = strdup(path); if (!entry->path) { free(entry); return -ENOMEM; } list_add_tail(&entry->list, &erofsfsck_link_hashtable[nid % NR_HARDLINK_HASHTABLE]); return 0; } static void erofsfsck_hardlink_init(void) { unsigned int i; for (i = 0; i < NR_HARDLINK_HASHTABLE; ++i) init_list_head(&erofsfsck_link_hashtable[i]); } static void erofsfsck_hardlink_exit(void) { struct erofsfsck_hardlink_entry *entry, *n; struct list_head *head; unsigned int i; for (i = 0; i < NR_HARDLINK_HASHTABLE; ++i) { head = &erofsfsck_link_hashtable[i]; list_for_each_entry_safe(entry, n, head, list) { if (entry->path) free(entry->path); free(entry); } } } static inline int erofs_extract_file(struct erofs_inode *inode) { bool tryagain = true; int ret, fd; erofs_dbg("extract file to path: %s", fsckcfg.extract_path); again: fd = open(fsckcfg.extract_path, O_WRONLY | O_CREAT | O_NOFOLLOW | (fsckcfg.overwrite ? O_TRUNC : O_EXCL), 0700); if (fd < 0) { if (fsckcfg.overwrite && tryagain) { if (errno == EISDIR) { erofs_warn("try to forcely remove directory %s", fsckcfg.extract_path); if (rmdir(fsckcfg.extract_path) < 0) { erofs_err("failed to remove: %s (%s)", fsckcfg.extract_path, strerror(errno)); return -EISDIR; } } else if (errno == EACCES && chmod(fsckcfg.extract_path, 0700) < 0) { erofs_err("failed to set permissions: %s (%s)", fsckcfg.extract_path, strerror(errno)); return -errno; } tryagain = false; goto again; } erofs_err("failed to open: %s (%s)", fsckcfg.extract_path, strerror(errno)); return -errno; } /* verify data chunk layout */ ret = erofs_verify_inode_data(inode, fd); close(fd); return ret; } static inline int erofs_extract_symlink(struct erofs_inode *inode) { bool tryagain = true; int ret; char *buf = NULL; erofs_dbg("extract symlink to path: %s", fsckcfg.extract_path); /* verify data chunk layout */ ret = erofs_verify_inode_data(inode, -1); if (ret) return ret; buf = malloc(inode->i_size + 1); if (!buf) { ret = -ENOMEM; goto out; } ret = erofs_pread(inode, buf, inode->i_size, 0); if (ret) { erofs_err("I/O error occurred when reading symlink @ nid %llu: %d", inode->nid | 0ULL, ret); goto out; } buf[inode->i_size] = '\0'; again: if (symlink(buf, fsckcfg.extract_path) < 0) { if (errno == EEXIST && fsckcfg.overwrite && tryagain) { erofs_warn("try to forcely remove file %s", fsckcfg.extract_path); if (unlink(fsckcfg.extract_path) < 0) { erofs_err("failed to remove: %s", fsckcfg.extract_path); ret = -errno; goto out; } tryagain = false; goto again; } erofs_err("failed to create symlink: %s", fsckcfg.extract_path); ret = -errno; } out: if (buf) free(buf); return ret; } static int erofs_extract_special(struct erofs_inode *inode) { bool tryagain = true; int ret; erofs_dbg("extract special to path: %s", fsckcfg.extract_path); /* verify data chunk layout */ ret = erofs_verify_inode_data(inode, -1); if (ret) return ret; again: if (mknod(fsckcfg.extract_path, inode->i_mode, inode->u.i_rdev) < 0) { if (errno == EEXIST && fsckcfg.overwrite && tryagain) { erofs_warn("try to forcely remove file %s", fsckcfg.extract_path); if (unlink(fsckcfg.extract_path) < 0) { erofs_err("failed to remove: %s", fsckcfg.extract_path); return -errno; } tryagain = false; goto again; } if (errno == EEXIST || fsckcfg.superuser) { erofs_err("failed to create special file: %s", fsckcfg.extract_path); ret = -errno; } else { erofs_warn("failed to create special file: %s, skipped", fsckcfg.extract_path); ret = -ECANCELED; } } return ret; } static int erofsfsck_dirent_iter(struct erofs_dir_context *ctx) { int ret; size_t prev_pos, curr_pos; if (ctx->dot_dotdot) return 0; prev_pos = fsckcfg.extract_pos; curr_pos = prev_pos; if (prev_pos + ctx->de_namelen >= PATH_MAX) { erofs_err("unable to fsck since the path is too long (%llu)", (curr_pos + ctx->de_namelen) | 0ULL); return -EOPNOTSUPP; } if (fsckcfg.extract_path) { fsckcfg.extract_path[curr_pos++] = '/'; strncpy(fsckcfg.extract_path + curr_pos, ctx->dname, ctx->de_namelen); curr_pos += ctx->de_namelen; fsckcfg.extract_path[curr_pos] = '\0'; } else { curr_pos += ctx->de_namelen; } fsckcfg.extract_pos = curr_pos; ret = erofsfsck_check_inode(ctx->dir->nid, ctx->de_nid); if (fsckcfg.extract_path) fsckcfg.extract_path[prev_pos] = '\0'; fsckcfg.extract_pos = prev_pos; return ret; } static int erofsfsck_extract_inode(struct erofs_inode *inode) { int ret; char *oldpath; if (!fsckcfg.extract_path || erofs_is_packed_inode(inode)) { verify: /* verify data chunk layout */ return erofs_verify_inode_data(inode, -1); } oldpath = erofsfsck_hardlink_find(inode->nid); if (oldpath) { if (link(oldpath, fsckcfg.extract_path) == -1) { erofs_err("failed to extract hard link: %s (%s)", fsckcfg.extract_path, strerror(errno)); return -errno; } return 0; } switch (inode->i_mode & S_IFMT) { case S_IFDIR: ret = erofs_extract_dir(inode); break; case S_IFREG: ret = erofs_extract_file(inode); break; case S_IFLNK: ret = erofs_extract_symlink(inode); break; case S_IFCHR: case S_IFBLK: case S_IFIFO: case S_IFSOCK: ret = erofs_extract_special(inode); break; default: /* TODO */ goto verify; } if (ret && ret != -ECANCELED) return ret; /* record nid and old path for hardlink */ if (inode->i_nlink > 1 && !S_ISDIR(inode->i_mode)) ret = erofsfsck_hardlink_insert(inode->nid, fsckcfg.extract_path); return ret; } static int erofsfsck_check_inode(erofs_nid_t pnid, erofs_nid_t nid) { int ret, i; struct erofs_inode inode = {.sbi = &g_sbi, .nid = nid}; erofs_dbg("check inode: nid(%llu)", nid | 0ULL); ret = erofs_read_inode_from_disk(&inode); if (ret) { if (ret == -EIO) erofs_err("I/O error occurred when reading nid(%llu)", nid | 0ULL); goto out; } if (!(fsckcfg.check_decomp && fsckcfg.dump_xattrs)) { /* verify xattr field */ ret = erofs_verify_xattr(&inode); if (ret) goto out; } ret = erofsfsck_extract_inode(&inode); if (ret && ret != -ECANCELED) goto out; if (fsckcfg.check_decomp && fsckcfg.dump_xattrs) { ret = erofsfsck_dump_xattrs(&inode); if (ret) return ret; } if (S_ISDIR(inode.i_mode)) { struct erofs_dir_context ctx = { .flags = EROFS_READDIR_VALID_PNID, .pnid = pnid, .dir = &inode, .cb = erofsfsck_dirent_iter, }; /* XXX: support the deeper cases later */ if (fsckcfg.dirstack.top >= ARRAY_SIZE(fsckcfg.dirstack.dirs)) return -ENAMETOOLONG; for (i = 0; i < fsckcfg.dirstack.top; ++i) if (inode.nid == fsckcfg.dirstack.dirs[i]) return -ELOOP; fsckcfg.dirstack.dirs[fsckcfg.dirstack.top++] = pnid; ret = erofs_iterate_dir(&ctx, true); --fsckcfg.dirstack.top; } if (!ret && !erofs_is_packed_inode(&inode)) erofsfsck_set_attributes(&inode, fsckcfg.extract_path); if (ret == -ECANCELED) ret = 0; out: if (ret && ret != -EIO) fsckcfg.corrupted = true; return ret; } #ifdef FUZZING int erofsfsck_fuzz_one(int argc, char *argv[]) #else int main(int argc, char *argv[]) #endif { int err; erofs_init_configure(); fsckcfg.physical_blocks = 0; fsckcfg.logical_blocks = 0; fsckcfg.extract_path = NULL; fsckcfg.extract_pos = 0; fsckcfg.umask = umask(0); fsckcfg.superuser = geteuid() == 0; fsckcfg.corrupted = false; fsckcfg.print_comp_ratio = false; fsckcfg.check_decomp = false; fsckcfg.force = false; fsckcfg.overwrite = false; fsckcfg.preserve_owner = fsckcfg.superuser; fsckcfg.preserve_perms = fsckcfg.superuser; fsckcfg.dump_xattrs = false; err = erofsfsck_parse_options_cfg(argc, argv); if (err) { if (err == -EINVAL) fprintf(stderr, "Try '%s --help' for more information.\n", argv[0]); goto exit; } #ifdef FUZZING cfg.c_dbg_lvl = -1; #endif err = erofs_dev_open(&g_sbi, cfg.c_img_path, O_RDONLY); if (err) { erofs_err("failed to open image file"); goto exit; } err = erofs_read_superblock(&g_sbi); if (err) { erofs_err("failed to read superblock"); goto exit_dev_close; } if (erofs_sb_has_sb_chksum(&g_sbi) && erofs_check_sb_chksum()) { erofs_err("failed to verify superblock checksum"); goto exit_put_super; } if (fsckcfg.extract_path) erofsfsck_hardlink_init(); if (erofs_sb_has_fragments(&g_sbi) && g_sbi.packed_nid > 0) { err = erofs_packedfile_init(&g_sbi, false); if (err) { erofs_err("failed to initialize packedfile: %s", erofs_strerror(err)); goto exit_hardlink; } err = erofsfsck_check_inode(g_sbi.packed_nid, g_sbi.packed_nid); if (err) { erofs_err("failed to verify packed file"); goto exit_packedinode; } } err = erofsfsck_check_inode(g_sbi.root_nid, g_sbi.root_nid); if (fsckcfg.corrupted) { if (!fsckcfg.extract_path) erofs_err("Found some filesystem corruption"); else erofs_err("Failed to extract filesystem"); err = -EFSCORRUPTED; } else if (!err) { if (!fsckcfg.extract_path) erofs_info("No errors found"); else erofs_info("Extracted filesystem successfully"); if (fsckcfg.print_comp_ratio) { double comp_ratio = (double)fsckcfg.physical_blocks * 100 / (double)fsckcfg.logical_blocks; erofs_info("Compression ratio: %.2f(%%)", comp_ratio); } } exit_packedinode: erofs_packedfile_exit(&g_sbi); exit_hardlink: if (fsckcfg.extract_path) erofsfsck_hardlink_exit(); exit_put_super: erofs_put_super(&g_sbi); exit_dev_close: erofs_dev_close(&g_sbi); exit: erofs_blob_closeall(&g_sbi); erofs_exit_configure(); return err ? 1 : 0; } #ifdef FUZZING int LLVMFuzzerTestOneInput(const uint8_t *Data, size_t Size) { int fd, ret; char filename[] = "/tmp/erofsfsck_libfuzzer_XXXXXX"; char *argv[] = { "fsck.erofs", "--extract", filename, }; fd = mkstemp(filename); if (fd < 0) return -errno; if (write(fd, Data, Size) != Size) { close(fd); return -EIO; } close(fd); ret = erofsfsck_fuzz_one(ARRAY_SIZE(argv), argv); unlink(filename); return ret ? -1 : 0; } #endif erofs-utils-1.8.6/fuse/000077500000000000000000000000001477440110200147445ustar00rootroot00000000000000erofs-utils-1.8.6/fuse/Makefile.am000066400000000000000000000014771477440110200170110ustar00rootroot00000000000000# SPDX-License-Identifier: GPL-2.0+ AUTOMAKE_OPTIONS = foreign noinst_HEADERS = $(top_srcdir)/fuse/macosx.h bin_PROGRAMS = erofsfuse erofsfuse_SOURCES = main.c erofsfuse_CFLAGS = -Wall -I$(top_srcdir)/include erofsfuse_CFLAGS += ${libfuse2_CFLAGS} ${libfuse3_CFLAGS} ${libselinux_CFLAGS} erofsfuse_LDADD = $(top_builddir)/lib/liberofs.la ${libfuse2_LIBS} ${libfuse3_LIBS} ${liblz4_LIBS} \ ${libselinux_LIBS} ${liblzma_LIBS} ${zlib_LIBS} ${libdeflate_LIBS} ${libzstd_LIBS} \ ${libqpl_LIBS} ${libxxhash_LIBS} if ENABLE_STATIC_FUSE lib_LTLIBRARIES = liberofsfuse.la liberofsfuse_la_SOURCES = main.c liberofsfuse_la_CFLAGS = -Wall -I$(top_srcdir)/include liberofsfuse_la_CFLAGS += -Dmain=erofsfuse_main ${libfuse2_CFLAGS} ${libfuse3_CFLAGS} ${libselinux_CFLAGS} liberofsfuse_la_LIBADD = $(top_builddir)/lib/liberofs.la endif erofs-utils-1.8.6/fuse/macosx.h000066400000000000000000000001211477440110200164010ustar00rootroot00000000000000/* SPDX-License-Identifier: GPL-2.0+ */ #ifdef __APPLE__ #undef LIST_HEAD #endif erofs-utils-1.8.6/fuse/main.c000066400000000000000000000414321477440110200160400ustar00rootroot00000000000000// SPDX-License-Identifier: GPL-2.0+ /* * Created by Li Guifu * Lowlevel added by Li Yiyan */ #include #include #include #include #include "macosx.h" #include "erofs/config.h" #include "erofs/print.h" #include "erofs/dir.h" #include "erofs/inode.h" #include "erofs/fragments.h" #include #include #include #define EROFSFUSE_TIMEOUT DBL_MAX struct erofsfuse_readdir_context { struct erofs_dir_context ctx; fuse_req_t req; void *buf; int is_plus; size_t index; size_t buf_rem; size_t offset; struct fuse_file_info *fi; }; struct erofsfuse_lookupdir_context { struct erofs_dir_context ctx; const char *target_name; struct fuse_entry_param *ent; }; static inline erofs_nid_t erofsfuse_to_nid(fuse_ino_t ino) { if (ino == FUSE_ROOT_ID) return g_sbi.root_nid; return (erofs_nid_t)(ino - FUSE_ROOT_ID); } static inline fuse_ino_t erofsfuse_to_ino(erofs_nid_t nid) { if (nid == g_sbi.root_nid) return FUSE_ROOT_ID; return (nid + FUSE_ROOT_ID); } static void erofsfuse_fill_stat(struct erofs_inode *vi, struct stat *stbuf) { if (S_ISBLK(vi->i_mode) || S_ISCHR(vi->i_mode)) stbuf->st_rdev = vi->u.i_rdev; stbuf->st_mode = vi->i_mode; stbuf->st_nlink = vi->i_nlink; stbuf->st_size = vi->i_size; stbuf->st_blocks = roundup(vi->i_size, erofs_blksiz(&g_sbi)) >> 9; stbuf->st_uid = vi->i_uid; stbuf->st_gid = vi->i_gid; stbuf->st_ctime = vi->i_mtime; stbuf->st_mtime = stbuf->st_ctime; stbuf->st_atime = stbuf->st_ctime; } static int erofsfuse_add_dentry(struct erofs_dir_context *ctx) { size_t entsize = 0; char dname[EROFS_NAME_LEN + 1]; struct erofsfuse_readdir_context *readdir_ctx = (void *)ctx; if (readdir_ctx->index < readdir_ctx->offset) { readdir_ctx->index++; return 0; } strncpy(dname, ctx->dname, ctx->de_namelen); dname[ctx->de_namelen] = '\0'; if (!readdir_ctx->is_plus) { /* fuse 3 still use non-plus readdir */ struct stat st = { 0 }; st.st_mode = erofs_ftype_to_mode(ctx->de_ftype, 0); st.st_ino = erofsfuse_to_ino(ctx->de_nid); entsize = fuse_add_direntry(readdir_ctx->req, readdir_ctx->buf, readdir_ctx->buf_rem, dname, &st, readdir_ctx->index + 1); } else { #if FUSE_MAJOR_VERSION >= 3 int ret; struct erofs_inode vi = { .sbi = &g_sbi, .nid = ctx->de_nid }; ret = erofs_read_inode_from_disk(&vi); if (ret < 0) return ret; struct fuse_entry_param param = { .ino = erofsfuse_to_ino(ctx->de_nid), .attr.st_ino = erofsfuse_to_ino(ctx->de_nid), .generation = 0, .attr_timeout = EROFSFUSE_TIMEOUT, .entry_timeout = EROFSFUSE_TIMEOUT, }; erofsfuse_fill_stat(&vi, &(param.attr)); entsize = fuse_add_direntry_plus(readdir_ctx->req, readdir_ctx->buf, readdir_ctx->buf_rem, dname, ¶m, readdir_ctx->index + 1); #else return -EOPNOTSUPP; #endif } if (entsize > readdir_ctx->buf_rem) return 1; readdir_ctx->index++; readdir_ctx->buf += entsize; readdir_ctx->buf_rem -= entsize; return 0; } static int erofsfuse_lookup_dentry(struct erofs_dir_context *ctx) { struct erofsfuse_lookupdir_context *lookup_ctx = (void *)ctx; if (lookup_ctx->ent->ino != 0 || strlen(lookup_ctx->target_name) != ctx->de_namelen) return 0; if (!strncmp(lookup_ctx->target_name, ctx->dname, ctx->de_namelen)) { int ret; struct erofs_inode vi = { .sbi = &g_sbi, .nid = (erofs_nid_t)ctx->de_nid, }; ret = erofs_read_inode_from_disk(&vi); if (ret < 0) return ret; lookup_ctx->ent->ino = erofsfuse_to_ino(ctx->de_nid); lookup_ctx->ent->attr.st_ino = erofsfuse_to_ino(ctx->de_nid); erofsfuse_fill_stat(&vi, &(lookup_ctx->ent->attr)); } return 0; } static inline void erofsfuse_readdir_general(fuse_req_t req, fuse_ino_t ino, size_t size, off_t off, struct fuse_file_info *fi, int plus) { int ret = 0; char *buf = NULL; struct erofsfuse_readdir_context ctx = { 0 }; struct erofs_inode *vi = (struct erofs_inode *)fi->fh; erofs_dbg("readdir(%llu): size: %zu, off: %lu, plus: %d", ino | 0ULL, size, off, plus); buf = malloc(size); if (!buf) { fuse_reply_err(req, ENOMEM); return; } ctx.ctx.dir = vi; ctx.ctx.cb = erofsfuse_add_dentry; ctx.fi = fi; ctx.buf = buf; ctx.buf_rem = size; ctx.req = req; ctx.index = 0; ctx.offset = off; ctx.is_plus = plus; #ifdef NDEBUG ret = erofs_iterate_dir(&ctx.ctx, false); #else ret = erofs_iterate_dir(&ctx.ctx, true); #endif if (ret < 0) /* if buffer insufficient, return 1 */ fuse_reply_err(req, -ret); else fuse_reply_buf(req, buf, size - ctx.buf_rem); free(buf); } static void erofsfuse_readdir(fuse_req_t req, fuse_ino_t ino, size_t size, off_t off, struct fuse_file_info *fi) { erofsfuse_readdir_general(req, ino, size, off, fi, 0); } #if FUSE_MAJOR_VERSION >= 3 static void erofsfuse_readdirplus(fuse_req_t req, fuse_ino_t ino, size_t size, off_t off, struct fuse_file_info *fi) { erofsfuse_readdir_general(req, ino, size, off, fi, 1); } #endif static void erofsfuse_init(void *userdata, struct fuse_conn_info *conn) { erofs_info("Using FUSE protocol %d.%d", conn->proto_major, conn->proto_minor); } static void erofsfuse_open(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi) { int ret = 0; struct erofs_inode *vi; if (fi->flags & (O_WRONLY | O_RDWR)) { fuse_reply_err(req, EROFS); return; } vi = calloc(1, sizeof(struct erofs_inode)); if (!vi) { fuse_reply_err(req, ENOMEM); return; } vi->sbi = &g_sbi; vi->nid = erofsfuse_to_nid(ino); ret = erofs_read_inode_from_disk(vi); if (ret < 0) { fuse_reply_err(req, -ret); goto out; } if (!S_ISREG(vi->i_mode)) { fuse_reply_err(req, EISDIR); } else { fi->fh = (uint64_t)vi; fi->keep_cache = 1; fuse_reply_open(req, fi); return; } out: free(vi); } static void erofsfuse_getattr(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi) { int ret; struct stat stbuf = { 0 }; struct erofs_inode vi = { .sbi = &g_sbi, .nid = erofsfuse_to_nid(ino) }; ret = erofs_read_inode_from_disk(&vi); if (ret < 0) fuse_reply_err(req, -ret); erofsfuse_fill_stat(&vi, &stbuf); stbuf.st_ino = ino; fuse_reply_attr(req, &stbuf, EROFSFUSE_TIMEOUT); } static void erofsfuse_opendir(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi) { int ret; struct erofs_inode *vi; vi = calloc(1, sizeof(struct erofs_inode)); if (!vi) { fuse_reply_err(req, ENOMEM); return; } vi->sbi = &g_sbi; vi->nid = erofsfuse_to_nid(ino); ret = erofs_read_inode_from_disk(vi); if (ret < 0) { fuse_reply_err(req, -ret); goto out; } if (!S_ISDIR(vi->i_mode)) { fuse_reply_err(req, ENOTDIR); goto out; } fi->fh = (uint64_t)vi; fuse_reply_open(req, fi); return; out: free(vi); } static void erofsfuse_release(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi) { free((struct erofs_inode *)fi->fh); fi->fh = 0; fuse_reply_err(req, 0); } static void erofsfuse_lookup(fuse_req_t req, fuse_ino_t parent, const char *name) { int ret; struct erofs_inode *vi; struct fuse_entry_param fentry = { 0 }; struct erofsfuse_lookupdir_context ctx = { 0 }; vi = calloc(1, sizeof(struct erofs_inode)); if (!vi) { fuse_reply_err(req, ENOMEM); return; } vi->sbi = &g_sbi; vi->nid = erofsfuse_to_nid(parent); ret = erofs_read_inode_from_disk(vi); if (ret < 0) { fuse_reply_err(req, -ret); goto out; } memset(&fentry, 0, sizeof(fentry)); fentry.ino = 0; fentry.attr_timeout = fentry.entry_timeout = EROFSFUSE_TIMEOUT; ctx.ctx.dir = vi; ctx.ctx.cb = erofsfuse_lookup_dentry; ctx.ent = &fentry; ctx.target_name = name; #ifdef NDEBUG ret = erofs_iterate_dir(&ctx.ctx, false); #else ret = erofs_iterate_dir(&ctx.ctx, true); #endif if (ret < 0) { fuse_reply_err(req, -ret); goto out; } fuse_reply_entry(req, &fentry); out: free(vi); } static void erofsfuse_read(fuse_req_t req, fuse_ino_t ino, size_t size, off_t off, struct fuse_file_info *fi) { int ret; char *buf = NULL; struct erofs_inode *vi = (struct erofs_inode *)fi->fh; erofs_dbg("read(%llu): size = %zu, off = %lu", ino | 0ULL, size, off); buf = malloc(size); if (!buf) { fuse_reply_err(req, ENOMEM); return; } ret = erofs_pread(vi, buf, size, off); if (ret) { fuse_reply_err(req, -ret); goto out; } if (off >= vi->i_size) ret = 0; else if (off + size > vi->i_size) ret = vi->i_size - off; else ret = size; fuse_reply_buf(req, buf, ret); out: free(buf); } static void erofsfuse_readlink(fuse_req_t req, fuse_ino_t ino) { int ret; char *buf = NULL; struct erofs_inode vi = { .sbi = &g_sbi, .nid = erofsfuse_to_nid(ino) }; ret = erofs_read_inode_from_disk(&vi); if (ret < 0) { fuse_reply_err(req, -ret); return; } buf = malloc(vi.i_size + 1); if (!buf) { fuse_reply_err(req, ENOMEM); return; } ret = erofs_pread(&vi, buf, vi.i_size, 0); if (ret < 0) { fuse_reply_err(req, -ret); goto out; } buf[vi.i_size] = '\0'; fuse_reply_readlink(req, buf); out: free(buf); } static void erofsfuse_getxattr(fuse_req_t req, fuse_ino_t ino, const char *name, size_t size #ifdef __APPLE__ , uint32_t position) #else ) #endif { int ret; char *buf = NULL; struct erofs_inode vi = { .sbi = &g_sbi, .nid = erofsfuse_to_nid(ino) }; erofs_dbg("getattr(%llu): name = %s, size = %zu", ino | 0ULL, name, size); ret = erofs_read_inode_from_disk(&vi); if (ret < 0) { fuse_reply_err(req, -ret); return; } if (size != 0) { buf = malloc(size); if (!buf) { fuse_reply_err(req, ENOMEM); return; } } ret = erofs_getxattr(&vi, name, buf, size); if (ret < 0) fuse_reply_err(req, -ret); else if (size == 0) fuse_reply_xattr(req, ret); else fuse_reply_buf(req, buf, ret); free(buf); } static void erofsfuse_listxattr(fuse_req_t req, fuse_ino_t ino, size_t size) { int ret; char *buf = NULL; struct erofs_inode vi = { .sbi = &g_sbi, .nid = erofsfuse_to_nid(ino) }; erofs_dbg("listxattr(%llu): size = %zu", ino | 0ULL, size); ret = erofs_read_inode_from_disk(&vi); if (ret < 0) { fuse_reply_err(req, -ret); return; } if (size != 0) { buf = malloc(size); if (!buf) { fuse_reply_err(req, ENOMEM); return; } } ret = erofs_listxattr(&vi, buf, size); if (ret < 0) fuse_reply_err(req, -ret); else if (size == 0) fuse_reply_xattr(req, ret); else fuse_reply_buf(req, buf, ret); free(buf); } static struct fuse_lowlevel_ops erofsfuse_lops = { .getxattr = erofsfuse_getxattr, .opendir = erofsfuse_opendir, .releasedir = erofsfuse_release, .release = erofsfuse_release, .lookup = erofsfuse_lookup, .listxattr = erofsfuse_listxattr, .readlink = erofsfuse_readlink, .getattr = erofsfuse_getattr, .readdir = erofsfuse_readdir, #if FUSE_MAJOR_VERSION >= 3 .readdirplus = erofsfuse_readdirplus, #endif .open = erofsfuse_open, .read = erofsfuse_read, .init = erofsfuse_init, }; static struct options { const char *disk; const char *mountpoint; u64 offset; unsigned int debug_lvl; bool show_help; bool show_version; bool odebug; } fusecfg; #define OPTION(t, p) { t, offsetof(struct options, p), 1 } static const struct fuse_opt option_spec[] = { OPTION("--offset=%lu", offset), OPTION("--dbglevel=%u", debug_lvl), OPTION("--help", show_help), OPTION("--version", show_version), FUSE_OPT_KEY("--device=", 1), FUSE_OPT_END }; static void usage(void) { #if FUSE_MAJOR_VERSION < 3 struct fuse_args args = FUSE_ARGS_INIT(0, NULL); #else fuse_lowlevel_version(); #endif fputs("usage: [options] IMAGE MOUNTPOINT\n\n" "Options:\n" " --offset=# skip # bytes at the beginning of IMAGE\n" " --dbglevel=# set output message level to # (maximum 9)\n" " --device=# specify an extra device to be used together\n" #if FUSE_MAJOR_VERSION < 3 " --help display this help and exit\n" " --version display erofsfuse version\n" #endif "\n", stderr); #if FUSE_MAJOR_VERSION >= 3 fputs("\nFUSE options:\n", stderr); fuse_cmdline_help(); #else fuse_opt_add_arg(&args, ""); /* progname */ fuse_opt_add_arg(&args, "-ho"); /* progname */ fuse_parse_cmdline(&args, NULL, NULL, NULL); #endif exit(EXIT_FAILURE); } static int optional_opt_func(void *data, const char *arg, int key, struct fuse_args *outargs) { int ret; switch (key) { case 1: ret = erofs_blob_open_ro(&g_sbi, arg + sizeof("--device=") - 1); if (ret) return -1; ++g_sbi.extra_devices; return 0; case FUSE_OPT_KEY_NONOPT: if (fusecfg.mountpoint) return -1; /* Too many args */ if (!fusecfg.disk) { fusecfg.disk = strdup(arg); return 0; } if (!fusecfg.mountpoint) fusecfg.mountpoint = strdup(arg); case FUSE_OPT_KEY_OPT: if (!strcmp(arg, "-d")) fusecfg.odebug = true; if (!strcmp(arg, "-h")) fusecfg.show_help = true; if (!strcmp(arg, "-V")) fusecfg.show_version = true; } return 1; // keep arg } #if defined(HAVE_EXECINFO_H) && defined(HAVE_BACKTRACE) #include static void signal_handle_sigsegv(int signal) { void *array[10]; size_t nptrs; char **strings; size_t i; erofs_dump("========================================\n"); erofs_dump("Segmentation Fault. Starting backtrace:\n"); nptrs = backtrace(array, 10); strings = backtrace_symbols(array, nptrs); if (strings) { for (i = 0; i < nptrs; i++) erofs_dump("%s\n", strings[i]); free(strings); } erofs_dump("========================================\n"); abort(); } #endif #define EROFSFUSE_MOUNT_MSG \ erofs_warn("%s mounted on %s with offset %u", \ fusecfg.disk, fusecfg.mountpoint, fusecfg.offset); int main(int argc, char *argv[]) { int ret; struct fuse_session *se; struct fuse_args args = FUSE_ARGS_INIT(argc, argv); #if FUSE_MAJOR_VERSION >= 3 struct fuse_cmdline_opts opts = {}; #else struct fuse_chan *ch; struct { char *mountpoint; int mt, foreground; } opts = {}; #endif erofs_init_configure(); fusecfg.debug_lvl = cfg.c_dbg_lvl; printf("erofsfuse %s\n", cfg.c_version); #if defined(HAVE_EXECINFO_H) && defined(HAVE_BACKTRACE) if (signal(SIGSEGV, signal_handle_sigsegv) == SIG_ERR) { fprintf(stderr, "failed to initialize signals\n"); ret = -errno; goto err; } #endif /* parse options */ ret = fuse_opt_parse(&args, &fusecfg, option_spec, optional_opt_func); if (ret) goto err; #if FUSE_MAJOR_VERSION >= 3 ret = fuse_parse_cmdline(&args, &opts); #else ret = (fuse_parse_cmdline(&args, &opts.mountpoint, &opts.mt, &opts.foreground) < 0); #endif if (ret) goto err_fuse_free_args; if (fusecfg.show_help || fusecfg.show_version || !opts.mountpoint) usage(); cfg.c_dbg_lvl = fusecfg.debug_lvl; if (fusecfg.odebug && cfg.c_dbg_lvl < EROFS_DBG) cfg.c_dbg_lvl = EROFS_DBG; g_sbi.bdev.offset = fusecfg.offset; ret = erofs_dev_open(&g_sbi, fusecfg.disk, O_RDONLY); if (ret) { fprintf(stderr, "failed to open: %s\n", fusecfg.disk); goto err_fuse_free_args; } ret = erofs_read_superblock(&g_sbi); if (ret) { fprintf(stderr, "failed to read erofs super block\n"); goto err_dev_close; } if (erofs_sb_has_fragments(&g_sbi) && g_sbi.packed_nid > 0) { ret = erofs_packedfile_init(&g_sbi, false); if (ret) { erofs_err("failed to initialize packedfile: %s", erofs_strerror(ret)); goto err_super_put; } } #if FUSE_MAJOR_VERSION >= 3 se = fuse_session_new(&args, &erofsfuse_lops, sizeof(erofsfuse_lops), NULL); if (!se) goto err_packedinode; if (fuse_session_mount(se, opts.mountpoint) >= 0) { EROFSFUSE_MOUNT_MSG if (fuse_daemonize(opts.foreground) >= 0) { if (fuse_set_signal_handlers(se) >= 0) { if (opts.singlethread) { ret = fuse_session_loop(se); } else { #if FUSE_USE_VERSION == 30 ret = fuse_session_loop_mt(se, opts.clone_fd); #elif FUSE_USE_VERSION == 32 struct fuse_loop_config config = { .clone_fd = opts.clone_fd, .max_idle_threads = opts.max_idle_threads }; ret = fuse_session_loop_mt(se, &config); #else #error "FUSE_USE_VERSION not supported" #endif } fuse_remove_signal_handlers(se); } fuse_session_unmount(se); fuse_session_destroy(se); } } #else ch = fuse_mount(opts.mountpoint, &args); if (!ch) goto err_packedinode; EROFSFUSE_MOUNT_MSG se = fuse_lowlevel_new(&args, &erofsfuse_lops, sizeof(erofsfuse_lops), NULL); if (se) { if (fuse_daemonize(opts.foreground) != -1) { if (fuse_set_signal_handlers(se) != -1) { fuse_session_add_chan(se, ch); if (opts.mt) ret = fuse_session_loop_mt(se); else ret = fuse_session_loop(se); fuse_remove_signal_handlers(se); fuse_session_remove_chan(ch); } } fuse_session_destroy(se); } fuse_unmount(opts.mountpoint, ch); #endif err_packedinode: erofs_packedfile_exit(&g_sbi); err_super_put: erofs_put_super(&g_sbi); err_dev_close: erofs_blob_closeall(&g_sbi); erofs_dev_close(&g_sbi); err_fuse_free_args: free(opts.mountpoint); fuse_opt_free_args(&args); err: erofs_exit_configure(); return ret ? EXIT_FAILURE : EXIT_SUCCESS; } erofs-utils-1.8.6/include/000077500000000000000000000000001477440110200154255ustar00rootroot00000000000000erofs-utils-1.8.6/include/erofs/000077500000000000000000000000001477440110200165435ustar00rootroot00000000000000erofs-utils-1.8.6/include/erofs/atomic.h000066400000000000000000000022021477440110200201640ustar00rootroot00000000000000/* SPDX-License-Identifier: GPL-2.0+ OR Apache-2.0 */ /* * Copyright (C) 2024 Alibaba Cloud */ #ifndef __EROFS_ATOMIC_H #define __EROFS_ATOMIC_H /* * Just use GCC/clang built-in functions for now * See: https://gcc.gnu.org/onlinedocs/gcc/_005f_005fatomic-Builtins.html */ typedef unsigned long erofs_atomic_t; typedef char erofs_atomic_bool_t; #define erofs_atomic_read(ptr) ({ \ typeof(*ptr) __n; \ __atomic_load(ptr, &__n, __ATOMIC_RELAXED); \ __n;}) #define erofs_atomic_set(ptr, n) do { \ typeof(*ptr) __n = (n); \ __atomic_store(ptr, &__n, __ATOMIC_RELAXED); \ } while(0) #define erofs_atomic_set_bit(bit, ptr) do { \ typeof(*ptr) __n = (1 << bit); \ __atomic_or_fetch(ptr, __n, __ATOMIC_ACQ_REL); \ } while(0) #define erofs_atomic_test_and_set(ptr) \ __atomic_test_and_set(ptr, __ATOMIC_RELAXED) #define erofs_atomic_add_return(ptr, i) \ __atomic_add_fetch(ptr, i, __ATOMIC_RELAXED) #define erofs_atomic_sub_return(ptr, i) \ __atomic_sub_fetch(ptr, i, __ATOMIC_RELAXED) #define erofs_atomic_inc_return(ptr) erofs_atomic_add_return(ptr, 1) #define erofs_atomic_dec_return(ptr) erofs_atomic_sub_return(ptr, 1) #endif erofs-utils-1.8.6/include/erofs/bitops.h000066400000000000000000000015071477440110200202170ustar00rootroot00000000000000/* SPDX-License-Identifier: GPL-2.0+ OR Apache-2.0 */ #ifndef __EROFS_BITOPS_H #define __EROFS_BITOPS_H #ifdef __cplusplus extern "C" { #endif #include "defs.h" static inline void __erofs_set_bit(int nr, volatile unsigned long *addr) { unsigned long mask = BIT_MASK(nr); unsigned long *p = ((unsigned long *)addr) + BIT_WORD(nr); *p |= mask; } static inline void __erofs_clear_bit(int nr, volatile unsigned long *addr) { unsigned long mask = BIT_MASK(nr); unsigned long *p = ((unsigned long *)addr) + BIT_WORD(nr); *p &= ~mask; } static inline int __erofs_test_bit(int nr, const volatile unsigned long *addr) { return 1UL & (addr[BIT_WORD(nr)] >> (nr & (BITS_PER_LONG-1))); } unsigned long erofs_find_next_bit(const unsigned long *addr, unsigned long nbits, unsigned long start); #ifdef __cplusplus } #endif #endif erofs-utils-1.8.6/include/erofs/blobchunk.h000066400000000000000000000017021477440110200206630ustar00rootroot00000000000000/* SPDX-License-Identifier: GPL-2.0+ OR Apache-2.0 */ /* * erofs-utils/lib/blobchunk.h * * Copyright (C) 2021, Alibaba Cloud */ #ifndef __EROFS_BLOBCHUNK_H #define __EROFS_BLOBCHUNK_H #ifdef __cplusplus extern "C" { #endif #include "erofs/internal.h" struct erofs_blobchunk *erofs_get_unhashed_chunk(unsigned int device_id, erofs_blk_t blkaddr, erofs_off_t sourceoffset); int erofs_blob_write_chunk_indexes(struct erofs_inode *inode, erofs_off_t off); int erofs_blob_write_chunked_file(struct erofs_inode *inode, int fd, erofs_off_t startoff); int erofs_write_zero_inode(struct erofs_inode *inode); int tarerofs_write_chunkes(struct erofs_inode *inode, erofs_off_t data_offset); int erofs_mkfs_dump_blobs(struct erofs_sb_info *sbi); void erofs_blob_exit(void); int erofs_blob_init(const char *blobfile_path, erofs_off_t chunksize); int erofs_mkfs_init_devices(struct erofs_sb_info *sbi, unsigned int devices); #ifdef __cplusplus } #endif #endif erofs-utils-1.8.6/include/erofs/block_list.h000066400000000000000000000024721477440110200210460ustar00rootroot00000000000000/* SPDX-License-Identifier: GPL-2.0+ OR Apache-2.0 */ /* * Copyright (C), 2021, Coolpad Group Limited. * Created by Yue Hu */ #ifndef __EROFS_BLOCK_LIST_H #define __EROFS_BLOCK_LIST_H #ifdef __cplusplus extern "C" { #endif #include "internal.h" int erofs_blocklist_open(FILE *fp, bool srcmap); FILE *erofs_blocklist_close(void); void tarerofs_blocklist_write(erofs_blk_t blkaddr, erofs_blk_t nblocks, erofs_off_t srcoff, unsigned int zeroedlen); #ifdef WITH_ANDROID void erofs_droid_blocklist_write(struct erofs_inode *inode, erofs_blk_t blk_start, erofs_blk_t nblocks); void erofs_droid_blocklist_write_tail_end(struct erofs_inode *inode, erofs_blk_t blkaddr); void erofs_droid_blocklist_write_extent(struct erofs_inode *inode, erofs_blk_t blk_start, erofs_blk_t nblocks, bool first_extent, bool last_extent); #else static inline void erofs_droid_blocklist_write(struct erofs_inode *inode, erofs_blk_t blk_start, erofs_blk_t nblocks) {} static inline void erofs_droid_blocklist_write_tail_end(struct erofs_inode *inode, erofs_blk_t blkaddr) {} static inline void erofs_droid_blocklist_write_extent(struct erofs_inode *inode, erofs_blk_t blk_start, erofs_blk_t nblocks, bool first_extent, bool last_extent) {} #endif #ifdef __cplusplus } #endif #endif erofs-utils-1.8.6/include/erofs/cache.h000066400000000000000000000067051477440110200177670ustar00rootroot00000000000000/* SPDX-License-Identifier: GPL-2.0+ OR Apache-2.0 */ /* * Copyright (C) 2018 HUAWEI, Inc. * http://www.huawei.com/ * Created by Miao Xie * with heavy changes by Gao Xiang */ #ifndef __EROFS_CACHE_H #define __EROFS_CACHE_H #ifdef __cplusplus extern "C" { #endif #include "internal.h" struct erofs_buffer_head; struct erofs_buffer_block; #define DATA 0 #define META 1 /* including inline xattrs, extent */ #define INODE 2 /* directory data */ #define DIRA 3 /* shared xattrs */ #define XATTR 4 /* device table */ #define DEVT 5 struct erofs_bhops { int (*flush)(struct erofs_buffer_head *bh); }; struct erofs_buffer_head { struct list_head list; union { struct { struct erofs_buffer_block *block; const struct erofs_bhops *op; }; erofs_blk_t nblocks; }; erofs_off_t off; void *fsprivate; }; struct erofs_buffer_block { struct list_head list; struct list_head sibling; /* blocks of the same waterline */ erofs_blk_t blkaddr; int type; struct erofs_buffer_head buffers; }; struct erofs_bufmgr { struct erofs_sb_info *sbi; /* buckets for all buffer blocks to boost up allocation */ struct list_head watermeter[META + 1][2][EROFS_MAX_BLOCK_SIZE]; unsigned long bktmap[META + 1][2][EROFS_MAX_BLOCK_SIZE / BITS_PER_LONG]; struct erofs_buffer_block blkh; erofs_blk_t tail_blkaddr, metablkcnt; /* last mapped buffer block to accelerate erofs_mapbh() */ struct erofs_buffer_block *last_mapped_block; /* align data block addresses to multiples of `dsunit` */ unsigned int dsunit; }; static inline const int get_alignsize(struct erofs_sb_info *sbi, int type, int *type_ret) { if (type == DATA) return erofs_blksiz(sbi); if (type == INODE) { *type_ret = META; return sizeof(struct erofs_inode_compact); } else if (type == DIRA) { *type_ret = META; return erofs_blksiz(sbi); } else if (type == XATTR) { *type_ret = META; return sizeof(struct erofs_xattr_entry); } else if (type == DEVT) { *type_ret = META; return EROFS_DEVT_SLOT_SIZE; } if (type == META) return 1; return -EINVAL; } extern const struct erofs_bhops erofs_drop_directly_bhops; extern const struct erofs_bhops erofs_skip_write_bhops; static inline erofs_off_t erofs_btell(struct erofs_buffer_head *bh, bool end) { const struct erofs_buffer_block *bb = bh->block; struct erofs_bufmgr *bmgr = (struct erofs_bufmgr *)bb->buffers.fsprivate; if (bb->blkaddr == NULL_ADDR) return NULL_ADDR_UL; return erofs_pos(bmgr->sbi, bb->blkaddr) + (end ? list_next_entry(bh, list)->off : bh->off); } static inline int erofs_bh_flush_generic_end(struct erofs_buffer_head *bh) { list_del(&bh->list); free(bh); return 0; } struct erofs_bufmgr *erofs_buffer_init(struct erofs_sb_info *sbi, erofs_blk_t startblk); int erofs_bh_balloon(struct erofs_buffer_head *bh, erofs_off_t incr); struct erofs_buffer_head *erofs_balloc(struct erofs_bufmgr *bmgr, int type, erofs_off_t size, unsigned int inline_ext); struct erofs_buffer_head *erofs_battach(struct erofs_buffer_head *bh, int type, unsigned int size); erofs_blk_t erofs_mapbh(struct erofs_bufmgr *bmgr, struct erofs_buffer_block *bb); int erofs_bflush(struct erofs_bufmgr *bmgr, struct erofs_buffer_block *bb); void erofs_bdrop(struct erofs_buffer_head *bh, bool tryrevoke); erofs_blk_t erofs_total_metablocks(struct erofs_bufmgr *bmgr); void erofs_buffer_exit(struct erofs_bufmgr *bmgr); #ifdef __cplusplus } #endif #endif erofs-utils-1.8.6/include/erofs/compress.h000066400000000000000000000023571477440110200205560ustar00rootroot00000000000000/* SPDX-License-Identifier: GPL-2.0+ OR Apache-2.0 */ /* * Copyright (C) 2019 HUAWEI, Inc. * http://www.huawei.com/ * Created by Gao Xiang */ #ifndef __EROFS_COMPRESS_H #define __EROFS_COMPRESS_H #ifdef __cplusplus extern "C" { #endif #include "internal.h" #define EROFS_CONFIG_COMPR_MAX_SZ (4000 * 1024) #define Z_EROFS_COMPR_QUEUE_SZ (EROFS_CONFIG_COMPR_MAX_SZ * 2) struct z_erofs_compress_ictx; void z_erofs_drop_inline_pcluster(struct erofs_inode *inode); void *erofs_begin_compressed_file(struct erofs_inode *inode, int fd, u64 fpos); int erofs_write_compressed_file(struct z_erofs_compress_ictx *ictx); int z_erofs_compress_init(struct erofs_sb_info *sbi, struct erofs_buffer_head *bh); int z_erofs_compress_exit(void); const char *z_erofs_list_supported_algorithms(int i, unsigned int *mask); const struct erofs_algorithm *z_erofs_list_available_compressors(int *i); static inline bool erofs_is_packed_inode(struct erofs_inode *inode) { erofs_nid_t packed_nid = inode->sbi->packed_nid; if (inode->nid == EROFS_PACKED_NID_UNALLOCATED) { DBG_BUGON(packed_nid != EROFS_PACKED_NID_UNALLOCATED); return true; } return (packed_nid > 0 && inode->nid == packed_nid); } #ifdef __cplusplus } #endif #endif erofs-utils-1.8.6/include/erofs/compress_hints.h000066400000000000000000000012541477440110200217560ustar00rootroot00000000000000/* SPDX-License-Identifier: GPL-2.0+ OR Apache-2.0 */ /* * Copyright (C), 2008-2021, OPPO Mobile Comm Corp., Ltd. * Created by Huang Jianan */ #ifndef __EROFS_COMPRESS_HINTS_H #define __EROFS_COMPRESS_HINTS_H #ifdef __cplusplus extern "C" { #endif #include "erofs/internal.h" #include #include struct erofs_compress_hints { struct list_head list; regex_t reg; unsigned int physical_clusterblks; unsigned char algorithmtype; }; bool z_erofs_apply_compress_hints(struct erofs_inode *inode); void erofs_cleanup_compress_hints(void); int erofs_load_compress_hints(struct erofs_sb_info *sbi); #ifdef __cplusplus } #endif #endif erofs-utils-1.8.6/include/erofs/config.h000066400000000000000000000053311477440110200201630ustar00rootroot00000000000000/* SPDX-License-Identifier: GPL-2.0+ OR Apache-2.0 */ /* * Copyright (C) 2018-2019 HUAWEI, Inc. * http://www.huawei.com/ * Created by Li Guifu */ #ifndef __EROFS_CONFIG_H #define __EROFS_CONFIG_H #ifdef __cplusplus extern "C" { #endif #include "defs.h" #include "err.h" enum { FORCE_INODE_COMPACT = 1, FORCE_INODE_EXTENDED, }; enum { FORCE_INODE_BLOCK_MAP = 1, FORCE_INODE_CHUNK_INDEXES, }; enum { TIMESTAMP_UNSPECIFIED, TIMESTAMP_NONE, TIMESTAMP_FIXED, TIMESTAMP_CLAMPING, }; enum { FRAGDEDUPE_FULL, FRAGDEDUPE_INODE, FRAGDEDUPE_OFF, }; #define EROFS_MAX_COMPR_CFGS 64 struct erofs_compr_opts { char *alg; int level; u32 dict_size; }; struct erofs_configure { const char *c_version; int c_dbg_lvl; bool c_dry_run; bool c_legacy_compress; char c_timeinherit; char c_chunkbits; bool c_inline_data; bool c_ztailpacking; bool c_fragments; bool c_all_fragments; bool c_dedupe; char c_fragdedupe; bool c_ignore_mtime; bool c_showprogress; bool c_extra_ea_name_prefixes; bool c_xattr_name_filter; bool c_ovlfs_strip; bool c_hard_dereference; #ifdef HAVE_LIBSELINUX struct selabel_handle *sehnd; #endif /* related arguments for mkfs.erofs */ char *c_img_path; char *c_src_path; char *c_blobdev_path; char *c_compress_hints_file; struct erofs_compr_opts c_compr_opts[EROFS_MAX_COMPR_CFGS]; char c_force_inodeversion; char c_force_chunkformat; /* < 0, xattr disabled and INT_MAX, always use inline xattrs */ int c_inline_xattr_tolerance; #ifdef EROFS_MT_ENABLED u64 c_mkfs_segment_size; u32 c_mt_workers; #endif u32 c_mkfs_pclustersize_max; u32 c_mkfs_pclustersize_def; u32 c_mkfs_pclustersize_packed; u32 c_max_decompressed_extent_bytes; u64 c_unix_timestamp; u32 c_uid, c_gid; const char *mount_point; long long c_uid_offset, c_gid_offset; u32 c_root_xattr_isize; #ifdef WITH_ANDROID char *target_out_path; char *fs_config_file; char *block_list_file; #endif #ifndef NDEBUG bool c_random_pclusterblks; bool c_random_algorithms; #endif }; extern struct erofs_configure cfg; void erofs_init_configure(void); void erofs_show_config(void); void erofs_exit_configure(void); /* (will be deprecated) temporary helper for updating global the cfg */ struct erofs_configure *erofs_get_configure(); void erofs_set_fs_root(const char *rootdir); const char *erofs_fspath(const char *fullpath); #ifdef HAVE_LIBSELINUX int erofs_selabel_open(const char *file_contexts); #else static inline int erofs_selabel_open(const char *file_contexts) { return -EINVAL; } #endif void erofs_update_progressinfo(const char *fmt, ...); char *erofs_trim_for_progressinfo(const char *str, int placeholder); unsigned int erofs_get_available_processors(void); #ifdef __cplusplus } #endif #endif erofs-utils-1.8.6/include/erofs/decompress.h000066400000000000000000000014761477440110200210700ustar00rootroot00000000000000/* SPDX-License-Identifier: GPL-2.0+ OR Apache-2.0 */ /* * Copyright (C), 2008-2020, OPPO Mobile Comm Corp., Ltd. * Created by Huang Jianan */ #ifndef __EROFS_DECOMPRESS_H #define __EROFS_DECOMPRESS_H #ifdef __cplusplus extern "C" { #endif #include "internal.h" struct z_erofs_decompress_req { struct erofs_sb_info *sbi; char *in, *out; /* * initial decompressed bytes that need to be skipped * when finally copying to output buffer */ unsigned int decodedskip; unsigned int inputsize, decodedlength; /* cut point of interlaced uncompressed data */ unsigned int interlaced_offset; /* indicate the algorithm will be used for decompression */ unsigned int alg; bool partial_decoding; }; int z_erofs_decompress(struct z_erofs_decompress_req *rq); #ifdef __cplusplus } #endif #endif erofs-utils-1.8.6/include/erofs/dedupe.h000066400000000000000000000020241477440110200201600ustar00rootroot00000000000000/* SPDX-License-Identifier: GPL-2.0+ OR Apache-2.0 */ /* * Copyright (C) 2022 Alibaba Cloud */ #ifndef __EROFS_DEDUPE_H #define __EROFS_DEDUPE_H #ifdef __cplusplus extern "C" { #endif #include "internal.h" struct z_erofs_inmem_extent { erofs_off_t pstart; unsigned int plen; unsigned int length; bool raw, partial, inlined; }; struct z_erofs_dedupe_ctx { u8 *start, *end; u8 *cur; struct z_erofs_inmem_extent e; }; int z_erofs_dedupe_match(struct z_erofs_dedupe_ctx *ctx); int z_erofs_dedupe_insert(struct z_erofs_inmem_extent *e, void *original_data); void z_erofs_dedupe_commit(bool drop); int z_erofs_dedupe_init(unsigned int wsiz); void z_erofs_dedupe_exit(void); int z_erofs_dedupe_ext_insert(struct z_erofs_inmem_extent *e, u64 hash); erofs_blk_t z_erofs_dedupe_ext_match(struct erofs_sb_info *sbi, u8 *encoded, unsigned int size, bool raw, u64 *hash); void z_erofs_dedupe_ext_commit(bool drop); int z_erofs_dedupe_ext_init(void); void z_erofs_dedupe_ext_exit(void); #ifdef __cplusplus } #endif #endif erofs-utils-1.8.6/include/erofs/defs.h000066400000000000000000000224541477440110200176440ustar00rootroot00000000000000/* SPDX-License-Identifier: GPL-2.0+ OR Apache-2.0 */ /* * Copyright (C) 2018 HUAWEI, Inc. * http://www.huawei.com/ * Created by Li Guifu * Modified by Gao Xiang */ #ifndef __EROFS_DEFS_H #define __EROFS_DEFS_H #ifdef __cplusplus extern "C" { #endif #include #include #include #include #include #include #ifdef HAVE_CONFIG_H #include #endif #ifdef HAVE_LINUX_TYPES_H #include #endif /* * container_of - cast a member of a structure out to the containing structure * @ptr: the pointer to the member. * @type: the type of the container struct this is embedded in. * @member: the name of the member within the struct. */ #define container_of(ptr, type, member) ({ \ const typeof(((type *)0)->member) *__mptr = (ptr); \ (type *)((char *)__mptr - offsetof(type, member)); }) typedef uint8_t u8; typedef uint16_t u16; typedef uint32_t u32; typedef uint64_t u64; #ifndef HAVE_LINUX_TYPES_H typedef u8 __u8; typedef u16 __u16; typedef u32 __u32; typedef u64 __u64; typedef u16 __le16; typedef u32 __le32; typedef u64 __le64; typedef u16 __be16; typedef u32 __be32; typedef u64 __be64; #endif typedef int8_t s8; typedef int16_t s16; typedef int32_t s32; typedef int64_t s64; #if __BYTE_ORDER == __LITTLE_ENDIAN /* * The host byte order is the same as network byte order, * so these functions are all just identity. */ #define cpu_to_le16(x) ((__u16)(x)) #define cpu_to_le32(x) ((__u32)(x)) #define cpu_to_le64(x) ((__u64)(x)) #define le16_to_cpu(x) ((__u16)(x)) #define le32_to_cpu(x) ((__u32)(x)) #define le64_to_cpu(x) ((__u64)(x)) #else #if __BYTE_ORDER == __BIG_ENDIAN #define cpu_to_le16(x) (__builtin_bswap16(x)) #define cpu_to_le32(x) (__builtin_bswap32(x)) #define cpu_to_le64(x) (__builtin_bswap64(x)) #define le16_to_cpu(x) (__builtin_bswap16(x)) #define le32_to_cpu(x) (__builtin_bswap32(x)) #define le64_to_cpu(x) (__builtin_bswap64(x)) #else #pragma error #endif #endif #ifdef __cplusplus #define BUILD_BUG_ON(condition) static_assert(!(condition)) #elif !defined(__OPTIMIZE__) #define BUILD_BUG_ON(condition) ((void)sizeof(char[1 - 2 * !!(condition)])) #else #define BUILD_BUG_ON(condition) assert(!(condition)) #endif #define DIV_ROUND_UP(n, d) (((n) + (d) - 1) / (d)) #define __round_mask(x, y) ((__typeof__(x))((y)-1)) #define round_up(x, y) ((((x)-1) | __round_mask(x, y))+1) #define round_down(x, y) ((x) & ~__round_mask(x, y)) #ifndef roundup /* The `const' in roundup() prevents gcc-3.3 from calling __divdi3 */ #define roundup(x, y) ( \ { \ const typeof(y) __y = y; \ (((x) + (__y - 1)) / __y) * __y; \ } \ ) #endif #define rounddown(x, y) ( \ { \ typeof(x) __x = (x); \ __x - (__x % (y)); \ } \ ) /* Can easily conflict with C++'s std::min */ #ifndef __cplusplus #define min(x, y) ({ \ typeof(x) _min1 = (x); \ typeof(y) _min2 = (y); \ (void) (&_min1 == &_min2); \ _min1 < _min2 ? _min1 : _min2; }) #define max(x, y) ({ \ typeof(x) _max1 = (x); \ typeof(y) _max2 = (y); \ (void) (&_max1 == &_max2); \ _max1 > _max2 ? _max1 : _max2; }) #endif /* * ..and if you can't take the strict types, you can specify one yourself. * Or don't use min/max at all, of course. */ #define min_t(type, x, y) ({ \ type __min1 = (x); \ type __min2 = (y); \ __min1 < __min2 ? __min1: __min2; }) #define max_t(type, x, y) ({ \ type __max1 = (x); \ type __max2 = (y); \ __max1 > __max2 ? __max1: __max2; }) #define cmpsgn(x, y) ({ \ typeof(x) _x = (x); \ typeof(y) _y = (y); \ (_x > _y) - (_x < _y); }) #define ARRAY_SIZE(arr) (sizeof(arr) / sizeof((arr)[0])) #define BIT(nr) (1UL << (nr)) #define BIT_ULL(nr) (1ULL << (nr)) #define BIT_MASK(nr) (1UL << ((nr) % BITS_PER_LONG)) #define BIT_WORD(nr) ((nr) / BITS_PER_LONG) #define BIT_ULL_MASK(nr) (1ULL << ((nr) % BITS_PER_LONG_LONG)) #define BIT_ULL_WORD(nr) ((nr) / BITS_PER_LONG_LONG) #define BITS_PER_BYTE 8 #define BITS_TO_LONGS(nr) DIV_ROUND_UP(nr, BITS_PER_BYTE * sizeof(long)) #ifdef __SIZEOF_LONG__ #define BITS_PER_LONG (__CHAR_BIT__ * __SIZEOF_LONG__) #else #define BITS_PER_LONG __WORDSIZE #endif #define BUG_ON(cond) assert(!(cond)) #ifdef NDEBUG #define DBG_BUGON(condition) ((void)(condition)) #else #define DBG_BUGON(condition) BUG_ON(condition) #endif #ifndef __maybe_unused #define __maybe_unused __attribute__((__unused__)) #endif #define __packed __attribute__((__packed__)) #define __get_unaligned_t(type, ptr) ({ \ const struct { type x; } __packed *__pptr = (typeof(__pptr))(ptr); \ __pptr->x; \ }) #define __put_unaligned_t(type, val, ptr) do { \ struct { type x; } __packed *__pptr = (typeof(__pptr))(ptr); \ __pptr->x = (val); \ } while (0) #define get_unaligned(ptr) __get_unaligned_t(typeof(*(ptr)), (ptr)) #define put_unaligned(val, ptr) __put_unaligned_t(typeof(*(ptr)), (val), (ptr)) static inline u32 get_unaligned_le32(const void *p) { return le32_to_cpu(__get_unaligned_t(__le32, p)); } static inline void put_unaligned_le32(u32 val, void *p) { __put_unaligned_t(__le32, cpu_to_le32(val), p); } static inline u32 get_unaligned_le64(const void *p) { return le64_to_cpu(__get_unaligned_t(__le64, p)); } /** * ilog2 - log of base 2 of 32-bit or a 64-bit unsigned value * @n - parameter * * constant-capable log of base 2 calculation * - this can be used to initialise global variables from constant data, hence * the massive ternary operator construction * * selects the appropriately-sized optimised version depending on sizeof(n) */ #define ilog2(n) \ ( \ (n) & (1ULL << 63) ? 63 : \ (n) & (1ULL << 62) ? 62 : \ (n) & (1ULL << 61) ? 61 : \ (n) & (1ULL << 60) ? 60 : \ (n) & (1ULL << 59) ? 59 : \ (n) & (1ULL << 58) ? 58 : \ (n) & (1ULL << 57) ? 57 : \ (n) & (1ULL << 56) ? 56 : \ (n) & (1ULL << 55) ? 55 : \ (n) & (1ULL << 54) ? 54 : \ (n) & (1ULL << 53) ? 53 : \ (n) & (1ULL << 52) ? 52 : \ (n) & (1ULL << 51) ? 51 : \ (n) & (1ULL << 50) ? 50 : \ (n) & (1ULL << 49) ? 49 : \ (n) & (1ULL << 48) ? 48 : \ (n) & (1ULL << 47) ? 47 : \ (n) & (1ULL << 46) ? 46 : \ (n) & (1ULL << 45) ? 45 : \ (n) & (1ULL << 44) ? 44 : \ (n) & (1ULL << 43) ? 43 : \ (n) & (1ULL << 42) ? 42 : \ (n) & (1ULL << 41) ? 41 : \ (n) & (1ULL << 40) ? 40 : \ (n) & (1ULL << 39) ? 39 : \ (n) & (1ULL << 38) ? 38 : \ (n) & (1ULL << 37) ? 37 : \ (n) & (1ULL << 36) ? 36 : \ (n) & (1ULL << 35) ? 35 : \ (n) & (1ULL << 34) ? 34 : \ (n) & (1ULL << 33) ? 33 : \ (n) & (1ULL << 32) ? 32 : \ (n) & (1ULL << 31) ? 31 : \ (n) & (1ULL << 30) ? 30 : \ (n) & (1ULL << 29) ? 29 : \ (n) & (1ULL << 28) ? 28 : \ (n) & (1ULL << 27) ? 27 : \ (n) & (1ULL << 26) ? 26 : \ (n) & (1ULL << 25) ? 25 : \ (n) & (1ULL << 24) ? 24 : \ (n) & (1ULL << 23) ? 23 : \ (n) & (1ULL << 22) ? 22 : \ (n) & (1ULL << 21) ? 21 : \ (n) & (1ULL << 20) ? 20 : \ (n) & (1ULL << 19) ? 19 : \ (n) & (1ULL << 18) ? 18 : \ (n) & (1ULL << 17) ? 17 : \ (n) & (1ULL << 16) ? 16 : \ (n) & (1ULL << 15) ? 15 : \ (n) & (1ULL << 14) ? 14 : \ (n) & (1ULL << 13) ? 13 : \ (n) & (1ULL << 12) ? 12 : \ (n) & (1ULL << 11) ? 11 : \ (n) & (1ULL << 10) ? 10 : \ (n) & (1ULL << 9) ? 9 : \ (n) & (1ULL << 8) ? 8 : \ (n) & (1ULL << 7) ? 7 : \ (n) & (1ULL << 6) ? 6 : \ (n) & (1ULL << 5) ? 5 : \ (n) & (1ULL << 4) ? 4 : \ (n) & (1ULL << 3) ? 3 : \ (n) & (1ULL << 2) ? 2 : \ (n) & (1ULL << 1) ? 1 : 0 \ ) static inline unsigned int ffs_long(unsigned long s) { return __builtin_ctzl(s); } static inline unsigned int fls_long(unsigned long x) { return x ? sizeof(x) * 8 - __builtin_clzl(x) : 0; } static inline unsigned long lowbit(unsigned long n) { return n & -n; } /** * __roundup_pow_of_two() - round up to nearest power of two * @n: value to round up */ static inline __attribute__((const)) unsigned long __roundup_pow_of_two(unsigned long n) { return 1UL << fls_long(n - 1); } /** * roundup_pow_of_two - round the given value up to nearest power of two * @n: parameter * * round the given value up to the nearest power of two * - the result is undefined when n == 0 * - this can be used to initialise global variables from constant data */ #define roundup_pow_of_two(n) \ ( \ __builtin_constant_p(n) ? ( \ ((n) == 1) ? 1 : \ (1UL << (ilog2((n) - 1) + 1)) \ ) : \ __roundup_pow_of_two(n) \ ) #ifndef __always_inline #define __always_inline inline #endif #ifdef HAVE_STRUCT_STAT_ST_ATIM /* Linux */ #define ST_ATIM_NSEC(stbuf) ((stbuf)->st_atim.tv_nsec) #define ST_CTIM_NSEC(stbuf) ((stbuf)->st_ctim.tv_nsec) #define ST_MTIM_NSEC(stbuf) ((stbuf)->st_mtim.tv_nsec) #define ST_MTIM_NSEC_SET(stbuf, val) (stbuf)->st_mtim.tv_nsec = (val) #elif defined(HAVE_STRUCT_STAT_ST_ATIMENSEC) /* macOS */ #define ST_ATIM_NSEC(stbuf) ((stbuf)->st_atimensec) #define ST_CTIM_NSEC(stbuf) ((stbuf)->st_ctimensec) #define ST_MTIM_NSEC(stbuf) ((stbuf)->st_mtimensec) #define ST_MTIM_NSEC_SET(stbuf, val) (stbuf)->st_mtimensec = (val) #else #define ST_ATIM_NSEC(stbuf) 0 #define ST_CTIM_NSEC(stbuf) 0 #define ST_MTIM_NSEC(stbuf) 0 #define ST_MTIM_NSEC_SET(stbuf, val) do { } while (0) #endif #define __erofs_likely(x) __builtin_expect(!!(x), 1) #define __erofs_unlikely(x) __builtin_expect(!!(x), 0) #ifdef __cplusplus } #endif #endif erofs-utils-1.8.6/include/erofs/dir.h000066400000000000000000000043231477440110200174740ustar00rootroot00000000000000/* SPDX-License-Identifier: GPL-2.0+ OR Apache-2.0 */ #ifndef __EROFS_DIR_H #define __EROFS_DIR_H #ifdef __cplusplus extern "C" { #endif #include "internal.h" #define EROFS_READDIR_VALID_PNID 0x0001 #define EROFS_READDIR_DOTDOT_FOUND 0x0002 #define EROFS_READDIR_DOT_FOUND 0x0004 #define EROFS_READDIR_ALL_SPECIAL_FOUND \ (EROFS_READDIR_DOTDOT_FOUND | EROFS_READDIR_DOT_FOUND) struct erofs_dir_context; /* callback function for iterating over inodes of EROFS */ typedef int (*erofs_readdir_cb)(struct erofs_dir_context *); /* * Callers could use a wrapper to contain extra information. * * Note that callback can reuse `struct erofs_dir_context' with care * to avoid stack overflow due to deep recursion: * - if fsck is true, |pnid|, |flags|, (optional)|cb| SHOULD be saved * to ensure the original state; * - if fsck is false, EROFS_READDIR_VALID_PNID SHOULD NOT be * set if |pnid| is inaccurate. * * Another way is to allocate a `struct erofs_dir_context' wraper * with `struct inode' on heap, and chain them together for * multi-level traversal to completely avoid recursion. * * |dname| may be WITHOUT the trailing '\0' and it's ONLY valid in * the callback context. |de_namelen| is the exact dirent name length. */ struct erofs_dir_context { /* * During execution of |erofs_iterate_dir|, the function needs to * read the values inside |erofs_inode* dir|. So it is important * that the callback function does not modify struct pointed by * |dir|. It is OK to repoint |dir| to other objects. * Unfortunately, it's not possible to enforce this restriction * with const keyword, as |erofs_iterate_dir| needs to modify * struct pointed by |dir|. */ struct erofs_inode *dir; erofs_readdir_cb cb; erofs_nid_t pnid; /* optional */ /* [OUT] the dirent which is under processing */ const char *dname; /* please see the comment above */ erofs_nid_t de_nid; u8 de_namelen, de_ftype, flags; bool dot_dotdot; }; /* Iterate over inodes that are in directory */ int erofs_iterate_dir(struct erofs_dir_context *ctx, bool fsck); /* Get a full pathname of the inode NID */ int erofs_get_pathname(struct erofs_sb_info *sbi, erofs_nid_t nid, char *buf, size_t size); #ifdef __cplusplus } #endif #endif erofs-utils-1.8.6/include/erofs/diskbuf.h000066400000000000000000000011771477440110200203510ustar00rootroot00000000000000/* SPDX-License-Identifier: GPL-2.0+ OR Apache-2.0 */ #ifndef __EROFS_DISKBUF_H #define __EROFS_DISKBUF_H #ifdef __cplusplus extern "C" { #endif #include "erofs/defs.h" struct erofs_diskbuf { void *sp; /* internal stream pointer */ u64 offset; /* internal offset */ }; int erofs_diskbuf_getfd(struct erofs_diskbuf *db, u64 *off); int erofs_diskbuf_reserve(struct erofs_diskbuf *db, int sid, u64 *off); void erofs_diskbuf_commit(struct erofs_diskbuf *db, u64 len); void erofs_diskbuf_close(struct erofs_diskbuf *db); int erofs_diskbuf_init(unsigned int nstrms); void erofs_diskbuf_exit(void); #ifdef __cplusplus } #endif #endif erofs-utils-1.8.6/include/erofs/err.h000066400000000000000000000014621477440110200175070ustar00rootroot00000000000000/* SPDX-License-Identifier: GPL-2.0+ OR Apache-2.0 */ /* * Copyright (C) 2018 HUAWEI, Inc. * http://www.huawei.com/ * Created by Li Guifu */ #ifndef __EROFS_ERR_H #define __EROFS_ERR_H #ifdef __cplusplus extern "C" { #endif #include #define MAX_ERRNO (4095) #define IS_ERR_VALUE(x) \ ((unsigned long)(void *)(x) >= (unsigned long)-MAX_ERRNO) static inline void *ERR_PTR(long error) { return (void *)error; } static inline int IS_ERR(const void *ptr) { return IS_ERR_VALUE((unsigned long)ptr); } static inline long PTR_ERR(const void *ptr) { return (long) ptr; } static inline void * ERR_CAST(const void *ptr) { /* cast away the const */ return (void *) ptr; } #ifdef __cplusplus } #endif #endif erofs-utils-1.8.6/include/erofs/exclude.h000066400000000000000000000011451477440110200203460ustar00rootroot00000000000000/* SPDX-License-Identifier: GPL-2.0+ OR Apache-2.0 */ /* * Created by Li Guifu */ #ifndef __EROFS_EXCLUDE_H #define __EROFS_EXCLUDE_H #ifdef __cplusplus extern "C" { #endif #include #include struct erofs_exclude_rule { struct list_head list; char *pattern; regex_t reg; }; void erofs_exclude_set_root(const char *rootdir); void erofs_cleanup_exclude_rules(void); int erofs_parse_exclude_path(const char *args, bool is_regex); struct erofs_exclude_rule *erofs_is_exclude_path(const char *dir, const char *name); #ifdef __cplusplus } #endif #endif erofs-utils-1.8.6/include/erofs/flex-array.h000066400000000000000000000106551477440110200207750ustar00rootroot00000000000000/* SPDX-License-Identifier: GPL-2.0 */ #ifndef __EROFS_FLEX_ARRAY_H #define __EROFS_FLEX_ARRAY_H #ifdef __cplusplus extern "C" { #endif #include #include #include #include #include "defs.h" #include "print.h" /* * flex-array.h * * Some notes to make sense of the code. * * Flex-arrays: * - Flex-arrays became standard in C99 and are defined by "array[]" (at the * end of a struct) * - Pre-C99 flex-arrays can be accomplished by "array[1]" * - There is a GNU extension where they are defined using "array[0]" * Allegedly there is/was a bug in gcc whereby foo[1] generated incorrect * code, so it's safest to use [0] (https://lkml.org/lkml/2015/2/18/407). * * For C89 and C90, __STDC__ is 1 * For later standards, __STDC_VERSION__ is defined according to the standard. * For example: 199901L or 201112L * * Whilst we're on the subject, in version 5 of gcc, the default std was * changed from gnu89 to gnu11. In jgmenu, CFLAGS therefore contains -std=gnu89 * You can check your default gcc std by doing: * gcc -dM -E - = 199901L) && \ (!defined(__SUNPRO_C) || (__SUNPRO_C > 0x580)) # define FLEX_ARRAY /* empty */ #elif defined(__GNUC__) # if (__GNUC__ >= 3) # define FLEX_ARRAY /* empty */ # else # define FLEX_ARRAY 0 /* older GNU extension */ # endif #endif /* Otherwise, default to safer but a bit wasteful traditional style */ #ifndef FLEX_ARRAY # define FLEX_ARRAY 1 #endif #endif #define bitsizeof(x) (CHAR_BIT * sizeof(x)) #define maximum_signed_value_of_type(a) \ (INTMAX_MAX >> (bitsizeof(intmax_t) - bitsizeof(a))) #define maximum_unsigned_value_of_type(a) \ (UINTMAX_MAX >> (bitsizeof(uintmax_t) - bitsizeof(a))) /* * Signed integer overflow is undefined in C, so here's a helper macro * to detect if the sum of two integers will overflow. * Requires: a >= 0, typeof(a) equals typeof(b) */ #define signed_add_overflows(a, b) \ ((b) > maximum_signed_value_of_type(a) - (a)) #define unsigned_add_overflows(a, b) \ ((b) > maximum_unsigned_value_of_type(a) - (a)) static inline size_t st_add(size_t a, size_t b) { if (unsigned_add_overflows(a, b)) { erofs_err("size_t overflow: %llu + %llu", a | 0ULL, b | 0ULL); BUG_ON(1); return -1; } return a + b; } #define st_add3(a, b, c) st_add(st_add((a), (b)), (c)) #define st_add4(a, b, c, d) st_add(st_add3((a), (b), (c)), (d)) /* * These functions help you allocate structs with flex arrays, and copy * the data directly into the array. For example, if you had: * * struct foo { * int bar; * char name[FLEX_ARRAY]; * }; * * you can do: * * struct foo *f; * FLEX_ALLOC_MEM(f, name, src, len); * * to allocate a "foo" with the contents of "src" in the "name" field. * The resulting struct is automatically zero'd, and the flex-array field * is NUL-terminated (whether the incoming src buffer was or not). * * The FLEXPTR_* variants operate on structs that don't use flex-arrays, * but do want to store a pointer to some extra data in the same allocated * block. For example, if you have: * * struct foo { * char *name; * int bar; * }; * * you can do: * * struct foo *f; * FLEXPTR_ALLOC_STR(f, name, src); * * and "name" will point to a block of memory after the struct, which will be * freed along with the struct (but the pointer can be repointed anywhere). * * The *_STR variants accept a string parameter rather than a ptr/len * combination. * * Note that these macros will evaluate the first parameter multiple * times, and it must be assignable as an lvalue. */ #define FLEX_ALLOC_MEM(x, flexname, buf, len) do { \ size_t flex_array_len_ = (len); \ (x) = calloc(1, st_add3(sizeof(*(x)), flex_array_len_, 1)); \ BUG_ON(!(x)); \ memcpy((void *)(x)->flexname, (buf), flex_array_len_); \ } while (0) #define FLEXPTR_ALLOC_MEM(x, ptrname, buf, len) do { \ size_t flex_array_len_ = (len); \ (x) = xcalloc(1, st_add3(sizeof(*(x)), flex_array_len_, 1)); \ memcpy((x) + 1, (buf), flex_array_len_); \ (x)->ptrname = (void *)((x) + 1); \ } while (0) #define FLEX_ALLOC_STR(x, flexname, str) \ FLEX_ALLOC_MEM((x), flexname, (str), strlen(str)) #define FLEXPTR_ALLOC_STR(x, ptrname, str) \ FLEXPTR_ALLOC_MEM((x), ptrname, (str), strlen(str)) #ifdef __cplusplus } #endif #endif erofs-utils-1.8.6/include/erofs/fragments.h000066400000000000000000000017071477440110200207070ustar00rootroot00000000000000/* SPDX-License-Identifier: GPL-2.0+ OR Apache-2.0 */ /* * Copyright (C), 2022, Coolpad Group Limited. */ #ifndef __EROFS_FRAGMENTS_H #define __EROFS_FRAGMENTS_H #ifdef __cplusplus extern "C" { #endif #include "erofs/internal.h" extern const char *erofs_frags_packedname; #define EROFS_PACKED_INODE erofs_frags_packedname int z_erofs_fragments_dedupe(struct erofs_inode *inode, int fd, u32 *tofcrc); int z_erofs_pack_file_from_fd(struct erofs_inode *inode, int fd, u32 tofcrc); int z_erofs_pack_fragments(struct erofs_inode *inode, void *data, unsigned int len, u32 tofcrc); int erofs_flush_packed_inode(struct erofs_sb_info *sbi); int erofs_packedfile(struct erofs_sb_info *sbi); int erofs_packedfile_init(struct erofs_sb_info *sbi, bool fragments_mkfs); void erofs_packedfile_exit(struct erofs_sb_info *sbi); int erofs_packedfile_read(struct erofs_sb_info *sbi, void *buf, erofs_off_t len, erofs_off_t pos); #ifdef __cplusplus } #endif #endif erofs-utils-1.8.6/include/erofs/hashmap.h000066400000000000000000000052521477440110200203410ustar00rootroot00000000000000/* SPDX-License-Identifier: GPL-2.0 */ #ifndef __EROFS_HASHMAP_H #define __EROFS_HASHMAP_H #ifdef __cplusplus extern "C" { #endif /* Copied from https://github.com/git/git.git */ #include #include #include #include "flex-array.h" /* * Generic implementation of hash-based key-value mappings. * See Documentation/technical/api-hashmap.txt. */ /* FNV-1 functions */ unsigned int strhash(const char *str); unsigned int strihash(const char *str); unsigned int memhash(const void *buf, size_t len); unsigned int memihash(const void *buf, size_t len); static inline unsigned int sha1hash(const unsigned char *sha1) { /* * Equivalent to 'return *(unsigned int *)sha1;', but safe on * platforms that don't support unaligned reads. */ unsigned int hash; memcpy(&hash, sha1, sizeof(hash)); return hash; } /* data structures */ struct hashmap_entry { struct hashmap_entry *next; unsigned int hash; }; typedef int (*hashmap_cmp_fn)(const void *entry, const void *entry_or_key, const void *keydata); struct hashmap { struct hashmap_entry **table; hashmap_cmp_fn cmpfn; unsigned int size, tablesize, grow_at, shrink_at; }; struct hashmap_iter { struct hashmap *map; struct hashmap_entry *next; unsigned int tablepos; }; /* hashmap functions */ void hashmap_init(struct hashmap *map, hashmap_cmp_fn equals_function, size_t initial_size); int hashmap_free(struct hashmap *map); /* hashmap_entry functions */ static inline void hashmap_entry_init(void *entry, unsigned int hash) { struct hashmap_entry *e = entry; e->hash = hash; e->next = NULL; } void *hashmap_get(const struct hashmap *map, const void *key, const void *keydata); void *hashmap_get_next(const struct hashmap *map, const void *entry); void hashmap_add(struct hashmap *map, void *entry); void *hashmap_remove(struct hashmap *map, const void *key); static inline void *hashmap_get_from_hash(const struct hashmap *map, unsigned int hash, const void *keydata) { struct hashmap_entry key; hashmap_entry_init(&key, hash); return hashmap_get(map, &key, keydata); } /* hashmap_iter functions */ void hashmap_iter_init(struct hashmap *map, struct hashmap_iter *iter); void *hashmap_iter_next(struct hashmap_iter *iter); static inline void *hashmap_iter_first(struct hashmap *map, struct hashmap_iter *iter) { hashmap_iter_init(map, iter); return hashmap_iter_next(iter); } static inline void hashmap_disable_shrink(struct hashmap * map) { map->shrink_at = 0; } /* string interning */ const void *memintern(const void *data, size_t len); static inline const char *strintern(const char *string) { return memintern(string, strlen(string)); } #ifdef __cplusplus } #endif #endif erofs-utils-1.8.6/include/erofs/hashtable.h000066400000000000000000000253311477440110200206530ustar00rootroot00000000000000/* SPDX-License-Identifier: GPL-2.0 */ /* * Original code taken from 'linux/include/linux/hash{,table}.h' */ #ifndef __EROFS_HASHTABLE_H #define __EROFS_HASHTABLE_H #ifdef __cplusplus extern "C" { #endif /* * Fast hashing routine for ints, longs and pointers. * (C) 2002 Nadia Yvette Chambers, IBM */ /* * Statically sized hash table implementation * (C) 2012 Sasha Levin */ #include "defs.h" /* * The "GOLDEN_RATIO_PRIME" is used in ifs/btrfs/brtfs_inode.h and * fs/inode.c. It's not actually prime any more (the previous primes * were actively bad for hashing), but the name remains. */ #if BITS_PER_LONG == 32 #define GOLDEN_RATIO_PRIME GOLDEN_RATIO_32 #define hash_long(val, bits) hash_32(val, bits) #elif BITS_PER_LONG == 64 #define hash_long(val, bits) hash_64(val, bits) #define GOLDEN_RATIO_PRIME GOLDEN_RATIO_64 #else #error Wordsize not 32 or 64 #endif /* * This hash multiplies the input by a large odd number and takes the * high bits. Since multiplication propagates changes to the most * significant end only, it is essential that the high bits of the * product be used for the hash value. * * Chuck Lever verified the effectiveness of this technique: * http://www.citi.umich.edu/techreports/reports/citi-tr-00-1.pdf * * Although a random odd number will do, it turns out that the golden * ratio phi = (sqrt(5)-1)/2, or its negative, has particularly nice * properties. (See Knuth vol 3, section 6.4, exercise 9.) * * These are the negative, (1 - phi) = phi**2 = (3 - sqrt(5))/2, * which is very slightly easier to multiply by and makes no * difference to the hash distribution. */ #define GOLDEN_RATIO_32 0x61C88647 #define GOLDEN_RATIO_64 0x61C8864680B583EBull struct hlist_head { struct hlist_node *first; }; struct hlist_node { struct hlist_node *next, **pprev; }; /* * Architectures might want to move the poison pointer offset * into some well-recognized area such as 0xdead000000000000, * that is also not mappable by user-space exploits: */ #ifdef CONFIG_ILLEGAL_POINTER_VALUE # define POISON_POINTER_DELTA _AC(CONFIG_ILLEGAL_POINTER_VALUE, UL) #else # define POISON_POINTER_DELTA 0 #endif /* * These are non-NULL pointers that will result in page faults * under normal circumstances, used to verify that nobody uses * non-initialized list entries. */ #define LIST_POISON1 ((void *) 0x00100100 + POISON_POINTER_DELTA) #define LIST_POISON2 ((void *) 0x00200200 + POISON_POINTER_DELTA) /* * Double linked lists with a single pointer list head. * Mostly useful for hash tables where the two pointer list head is * too wasteful. * You lose the ability to access the tail in O(1). */ #define HLIST_HEAD_INIT { .first = NULL } #define HLIST_HEAD(name) struct hlist_head name = { .first = NULL } #define INIT_HLIST_HEAD(ptr) ((ptr)->first = NULL) static inline void INIT_HLIST_NODE(struct hlist_node *h) { h->next = NULL; h->pprev = NULL; } static inline int hlist_unhashed(const struct hlist_node *h) { return !h->pprev; } static inline int hlist_empty(const struct hlist_head *h) { return !h->first; } static inline void __hlist_del(struct hlist_node *n) { struct hlist_node *next = n->next; struct hlist_node **pprev = n->pprev; *pprev = next; if (next) next->pprev = pprev; } static inline void hlist_del(struct hlist_node *n) { __hlist_del(n); n->next = LIST_POISON1; n->pprev = LIST_POISON2; } static inline void hlist_del_init(struct hlist_node *n) { if (!hlist_unhashed(n)) { __hlist_del(n); INIT_HLIST_NODE(n); } } static inline void hlist_add_head(struct hlist_node *n, struct hlist_head *h) { struct hlist_node *first = h->first; n->next = first; if (first) first->pprev = &n->next; h->first = n; n->pprev = &h->first; } /* next must be != NULL */ static inline void hlist_add_before(struct hlist_node *n, struct hlist_node *next) { n->pprev = next->pprev; n->next = next; next->pprev = &n->next; *(n->pprev) = n; } static inline void hlist_add_behind(struct hlist_node *n, struct hlist_node *prev) { n->next = prev->next; prev->next = n; n->pprev = &prev->next; if (n->next) n->next->pprev = &n->next; } /* after that we'll appear to be on some hlist and hlist_del will work */ static inline void hlist_add_fake(struct hlist_node *n) { n->pprev = &n->next; } /* * Move a list from one list head to another. Fixup the pprev * reference of the first entry if it exists. */ static inline void hlist_move_list(struct hlist_head *old, struct hlist_head *new) { new->first = old->first; if (new->first) new->first->pprev = &new->first; old->first = NULL; } #define hlist_entry(ptr, type, member) container_of(ptr, type, member) #define hlist_for_each(pos, head) \ for (pos = (head)->first; pos; pos = pos->next) #define hlist_for_each_safe(pos, n, head) \ for (pos = (head)->first; pos && ({ n = pos->next; 1; }); \ pos = n) #define hlist_entry_safe(ptr, type, member) \ ({ typeof(ptr) ____ptr = (ptr); \ ____ptr ? hlist_entry(____ptr, type, member) : NULL; \ }) /** * hlist_for_each_entry - iterate over list of given type * @pos:the type * to use as a loop cursor. * @head:the head for your list. * @member:the name of the hlist_node within the struct. */ #define hlist_for_each_entry(pos, head, member) \ for (pos = hlist_entry_safe((head)->first, typeof(*(pos)), member);\ pos; \ pos = hlist_entry_safe((pos)->member.next, typeof(*(pos)), member)) /** * hlist_for_each_entry_continue * iterate over a hlist continuing after current point * @pos:the type * to use as a loop cursor. * @member:the name of the hlist_node within the struct. */ #define hlist_for_each_entry_continue(pos, member) \ for (pos = hlist_entry_safe((pos)->member.next, typeof(*(pos)), member);\ pos; \ pos = hlist_entry_safe((pos)->member.next, typeof(*(pos)), member)) /** * hlist_for_each_entry_from * iterate over a hlist continuing from current point * @pos: the type * to use as a loop cursor. * @member: the name of the hlist_node within the struct. */ #define hlist_for_each_entry_from(pos, member) \ for (; pos; \ pos = hlist_entry_safe((pos)->member.next, typeof(*(pos)), member)) /** * hlist_for_each_entry_safe * iterate over list of given type safe against removal of list entry * @pos:the type * to use as a loop cursor. * @n:another &struct hlist_node to use as temporary storage * @head:the head for your list. * @member:the name of the hlist_node within the struct. */ #define hlist_for_each_entry_safe(pos, n, head, member) \ for (pos = hlist_entry_safe((head)->first, typeof(*pos), member);\ pos && ({ n = pos->member.next; 1; }); \ pos = hlist_entry_safe(n, typeof(*pos), member)) static inline u32 __hash_32(u32 val) { return val * GOLDEN_RATIO_32; } static inline u32 hash_32(u32 val, unsigned int bits) { /* High bits are more random, so use them. */ return __hash_32(val) >> (32 - bits); } static inline u32 hash_64(u64 val, unsigned int bits) { #if BITS_PER_LONG == 64 /* 64x64-bit multiply is efficient on all 64-bit processors */ return val * GOLDEN_RATIO_64 >> (64 - bits); #else /* Hash 64 bits using only 32x32-bit multiply. */ return hash_32((u32)val ^ __hash_32(val >> 32), bits); #endif } #define DEFINE_HASHTABLE(name, bits) \ struct hlist_head name[1 << (bits)] = \ { [0 ... ((1 << (bits)) - 1)] = HLIST_HEAD_INIT } #define DECLARE_HASHTABLE(name, bits) \ struct hlist_head name[1 << (bits)] #define HASH_SIZE(name) (ARRAY_SIZE(name)) #define HASH_BITS(name) ilog2(HASH_SIZE(name)) /* Use hash_32 when possible to allow for fast 32bit hashing in 64bit kernels*/ #define hash_min(val, bits) \ (sizeof(val) <= 4 ? hash_32(val, bits) : hash_long(val, bits)) static inline void __hash_init(struct hlist_head *ht, unsigned int sz) { unsigned int i; for (i = 0; i < sz; i++) INIT_HLIST_HEAD(&ht[i]); } /** * hash_init - initialize a hash table * @hashtable: hashtable to be initialized * * Calculates the size of the hashtable from the given parameter, otherwise * same as hash_init_size. * * This has to be a macro since HASH_BITS() will not work on pointers since * it calculates the size during preprocessing. */ #define hash_init(hashtable) __hash_init(hashtable, HASH_SIZE(hashtable)) /** * hash_add - add an object to a hashtable * @hashtable: hashtable to add to * @node: the &struct hlist_node of the object to be added * @key: the key of the object to be added */ #define hash_add(hashtable, node, key) \ hlist_add_head(node, &hashtable[hash_min(key, HASH_BITS(hashtable))]) /** * hash_hashed - check whether an object is in any hashtable * @node: the &struct hlist_node of the object to be checked */ static inline bool hash_hashed(struct hlist_node *node) { return !hlist_unhashed(node); } static inline bool __hash_empty(struct hlist_head *ht, unsigned int sz) { unsigned int i; for (i = 0; i < sz; i++) if (!hlist_empty(&ht[i])) return false; return true; } /** * hash_empty - check whether a hashtable is empty * @hashtable: hashtable to check * * This has to be a macro since HASH_BITS() will not work on pointers since * it calculates the size during preprocessing. */ #define hash_empty(hashtable) __hash_empty(hashtable, HASH_SIZE(hashtable)) /** * hash_del - remove an object from a hashtable * @node: &struct hlist_node of the object to remove */ static inline void hash_del(struct hlist_node *node) { hlist_del_init(node); } /** * hash_for_each - iterate over a hashtable * @name: hashtable to iterate * @bkt: integer to use as bucket loop cursor * @obj: the type * to use as a loop cursor for each entry * @member: the name of the hlist_node within the struct */ #define hash_for_each(name, bkt, obj, member) \ for ((bkt) = 0, obj = NULL; obj == NULL && (bkt) < HASH_SIZE(name);\ (bkt)++)\ hlist_for_each_entry(obj, &name[bkt], member) /** * hash_for_each_safe - iterate over a hashtable safe against removal of * hash entry * @name: hashtable to iterate * @bkt: integer to use as bucket loop cursor * @tmp: a &struct used for temporary storage * @obj: the type * to use as a loop cursor for each entry * @member: the name of the hlist_node within the struct */ #define hash_for_each_safe(name, bkt, tmp, obj, member) \ for ((bkt) = 0, obj = NULL; obj == NULL && (bkt) < HASH_SIZE(name);\ (bkt)++)\ hlist_for_each_entry_safe(obj, tmp, &name[bkt], member) /** * hash_for_each_possible - iterate over all possible objects hashing to the * same bucket * @name: hashtable to iterate * @obj: the type * to use as a loop cursor for each entry * @member: the name of the hlist_node within the struct * @key: the key of the objects to iterate over */ #define hash_for_each_possible(name, obj, member, key) \ hlist_for_each_entry(obj, &name[hash_min(key, HASH_BITS(name))], member) #ifdef __cplusplus } #endif #endif erofs-utils-1.8.6/include/erofs/inode.h000066400000000000000000000037031477440110200200150ustar00rootroot00000000000000/* SPDX-License-Identifier: GPL-2.0+ OR Apache-2.0 */ /* * Copyright (C) 2018-2019 HUAWEI, Inc. * http://www.huawei.com/ * Created by Li Guifu * with heavy changes by Gao Xiang */ #ifndef __EROFS_INODE_H #define __EROFS_INODE_H #ifdef __cplusplus extern "C" { #endif #include "erofs/internal.h" static inline struct erofs_inode *erofs_igrab(struct erofs_inode *inode) { (void)erofs_atomic_inc_return(&inode->i_count); return inode; } u32 erofs_new_encode_dev(dev_t dev); unsigned char erofs_mode_to_ftype(umode_t mode); umode_t erofs_ftype_to_mode(unsigned int ftype, unsigned int perm); unsigned char erofs_ftype_to_dtype(unsigned int filetype); void erofs_inode_manager_init(void); void erofs_insert_ihash(struct erofs_inode *inode); struct erofs_inode *erofs_iget(dev_t dev, ino_t ino); struct erofs_inode *erofs_iget_by_nid(erofs_nid_t nid); unsigned int erofs_iput(struct erofs_inode *inode); erofs_nid_t erofs_lookupnid(struct erofs_inode *inode); int erofs_iflush(struct erofs_inode *inode); struct erofs_dentry *erofs_d_alloc(struct erofs_inode *parent, const char *name); int erofs_allocate_inode_bh_data(struct erofs_inode *inode, erofs_blk_t nblocks); bool erofs_dentry_is_wht(struct erofs_sb_info *sbi, struct erofs_dentry *d); int erofs_rebuild_dump_tree(struct erofs_inode *dir, bool incremental); int erofs_init_empty_dir(struct erofs_inode *dir); int __erofs_fill_inode(struct erofs_inode *inode, struct stat *st, const char *path); struct erofs_inode *erofs_new_inode(struct erofs_sb_info *sbi); struct erofs_inode *erofs_mkfs_build_tree_from_path(struct erofs_sb_info *sbi, const char *path); struct erofs_inode *erofs_mkfs_build_special_from_fd(struct erofs_sb_info *sbi, int fd, const char *name); int erofs_fixup_root_inode(struct erofs_inode *root); struct erofs_inode *erofs_rebuild_make_root(struct erofs_sb_info *sbi); #ifdef __cplusplus } #endif #endif erofs-utils-1.8.6/include/erofs/internal.h000066400000000000000000000323001477440110200205260ustar00rootroot00000000000000/* SPDX-License-Identifier: GPL-2.0+ OR Apache-2.0 */ /* * Copyright (C) 2019 HUAWEI, Inc. * http://www.huawei.com/ * Created by Gao Xiang */ #ifndef __EROFS_INTERNAL_H #define __EROFS_INTERNAL_H #ifdef __cplusplus extern "C" { #endif #include "list.h" #include "err.h" typedef unsigned short umode_t; #include "erofs_fs.h" #include #include /* for off_t definition */ #include /* for S_ISCHR definition */ #include #ifdef HAVE_PTHREAD_H #include #endif #include "atomic.h" #include "io.h" #ifndef PATH_MAX #define PATH_MAX 4096 /* # chars in a path name including nul */ #endif #ifndef EROFS_MAX_BLOCK_SIZE #define EROFS_MAX_BLOCK_SIZE 4096 #endif #define EROFS_ISLOTBITS 5 #define EROFS_SLOTSIZE (1U << EROFS_ISLOTBITS) typedef u64 erofs_off_t; typedef u64 erofs_nid_t; /* data type for filesystem-wide blocks number */ typedef u32 erofs_blk_t; #define NULL_ADDR ((unsigned int)-1) #define NULL_ADDR_UL ((unsigned long)-1) /* global sbi */ extern struct erofs_sb_info g_sbi; #define erofs_blksiz(sbi) (1u << (sbi)->blkszbits) #define erofs_blknr(sbi, addr) ((addr) >> (sbi)->blkszbits) #define erofs_blkoff(sbi, addr) ((addr) & (erofs_blksiz(sbi) - 1)) #define erofs_pos(sbi, nr) ((erofs_off_t)(nr) << (sbi)->blkszbits) #define BLK_ROUND_UP(sbi, addr) \ (roundup(addr, erofs_blksiz(sbi)) >> (sbi)->blkszbits) struct erofs_buffer_head; struct erofs_bufmgr; struct erofs_device_info { u8 tag[64]; u32 blocks; u32 mapped_blkaddr; }; /* all filesystem-wide lz4 configurations */ struct erofs_sb_lz4_info { u16 max_distance; /* maximum possible blocks for pclusters in the filesystem */ u16 max_pclusterblks; }; struct erofs_xattr_prefix_item { struct erofs_xattr_long_prefix *prefix; u8 infix_len; }; #define EROFS_PACKED_NID_UNALLOCATED -1 struct erofs_mkfs_dfops; struct erofs_packed_inode; struct erofs_sb_info { struct erofs_sb_lz4_info lz4; struct erofs_device_info *devs; char *devname; u64 total_blocks; u64 primarydevice_blocks; erofs_blk_t meta_blkaddr; erofs_blk_t xattr_blkaddr; u32 feature_compat; u32 feature_incompat; unsigned char islotbits; unsigned char blkszbits; u32 sb_size; /* total superblock size */ u32 build_time_nsec; u64 build_time; /* what we really care is nid, rather than ino.. */ erofs_nid_t root_nid; /* used for statfs, f_files - f_favail */ u64 inos; u8 uuid[16]; char volume_name[16]; u32 checksum; u16 available_compr_algs; u16 extra_devices; union { u16 devt_slotoff; /* used for mkfs */ u16 device_id_mask; /* used for others */ }; erofs_nid_t packed_nid; u32 xattr_prefix_start; u8 xattr_prefix_count; struct erofs_xattr_prefix_item *xattr_prefixes; struct erofs_vfile bdev; int devblksz; u64 devsz; dev_t dev; unsigned int nblobs; unsigned int blobfd[256]; struct list_head list; u64 saved_by_deduplication; #ifdef EROFS_MT_ENABLED pthread_t dfops_worker; struct erofs_mkfs_dfops *mkfs_dfops; #endif struct erofs_bufmgr *bmgr; struct erofs_packed_inode *packedinode; bool useqpl; }; #define EROFS_SUPER_END (EROFS_SUPER_OFFSET + sizeof(struct erofs_super_block)) /* make sure that any user of the erofs headers has atleast 64bit off_t type */ extern int erofs_assert_largefile[sizeof(off_t)-8]; #define EROFS_FEATURE_FUNCS(name, compat, feature) \ static inline bool erofs_sb_has_##name(struct erofs_sb_info *sbi) \ { \ return sbi->feature_##compat & EROFS_FEATURE_##feature; \ } \ static inline void erofs_sb_set_##name(struct erofs_sb_info *sbi) \ { \ sbi->feature_##compat |= EROFS_FEATURE_##feature; \ } \ static inline void erofs_sb_clear_##name(struct erofs_sb_info *sbi) \ { \ sbi->feature_##compat &= ~EROFS_FEATURE_##feature; \ } EROFS_FEATURE_FUNCS(lz4_0padding, incompat, INCOMPAT_ZERO_PADDING) EROFS_FEATURE_FUNCS(compr_cfgs, incompat, INCOMPAT_COMPR_CFGS) EROFS_FEATURE_FUNCS(big_pcluster, incompat, INCOMPAT_BIG_PCLUSTER) EROFS_FEATURE_FUNCS(chunked_file, incompat, INCOMPAT_CHUNKED_FILE) EROFS_FEATURE_FUNCS(device_table, incompat, INCOMPAT_DEVICE_TABLE) EROFS_FEATURE_FUNCS(ztailpacking, incompat, INCOMPAT_ZTAILPACKING) EROFS_FEATURE_FUNCS(fragments, incompat, INCOMPAT_FRAGMENTS) EROFS_FEATURE_FUNCS(dedupe, incompat, INCOMPAT_DEDUPE) EROFS_FEATURE_FUNCS(xattr_prefixes, incompat, INCOMPAT_XATTR_PREFIXES) EROFS_FEATURE_FUNCS(sb_chksum, compat, COMPAT_SB_CHKSUM) EROFS_FEATURE_FUNCS(xattr_filter, compat, COMPAT_XATTR_FILTER) #define EROFS_I_EA_INITED_BIT 0 #define EROFS_I_Z_INITED_BIT 1 #define EROFS_I_EA_INITED (1 << EROFS_I_EA_INITED_BIT) #define EROFS_I_Z_INITED (1 << EROFS_I_Z_INITED_BIT) struct erofs_diskbuf; #define EROFS_INODE_DATA_SOURCE_NONE 0 #define EROFS_INODE_DATA_SOURCE_LOCALPATH 1 #define EROFS_INODE_DATA_SOURCE_DISKBUF 2 #define EROFS_INODE_DATA_SOURCE_RESVSP 3 struct erofs_inode { struct list_head i_hash, i_subdirs, i_xattrs; union { /* (erofsfuse) runtime flags */ erofs_atomic_t flags; /* (mkfs.erofs) next pointer for directory dumping */ struct erofs_inode *next_dirwrite; }; erofs_atomic_t i_count; struct erofs_sb_info *sbi; struct erofs_inode *i_parent; /* (mkfs.erofs) device ID containing source file */ u32 dev; umode_t i_mode; erofs_off_t i_size; u64 i_ino[2]; u32 i_uid; u32 i_gid; u64 i_mtime; u32 i_mtime_nsec; u32 i_nlink; union { u32 i_blkaddr; u32 i_blocks; u32 i_rdev; struct { unsigned short chunkformat; unsigned char chunkbits; }; } u; char *i_srcpath; union { char *i_link; struct erofs_diskbuf *i_diskbuf; }; unsigned char datalayout; unsigned char inode_isize; /* inline tail-end packing size */ unsigned short idata_size; char datasource; bool compressed_idata; bool lazy_tailblock; bool opaque; /* OVL: non-merge dir that may contain whiteout entries */ bool whiteouts; unsigned int xattr_isize; unsigned int extent_isize; unsigned int xattr_shared_count; unsigned int *xattr_shared_xattrs; erofs_nid_t nid; struct erofs_buffer_head *bh; struct erofs_buffer_head *bh_inline, *bh_data; void *idata; /* (ztailpacking) in order to recover uncompressed EOF data */ void *eof_tailraw; unsigned int eof_tailrawsize; union { void *chunkindexes; struct { uint16_t z_advise; uint8_t z_algorithmtype[2]; uint8_t z_logical_clusterbits; uint8_t z_physical_clusterblks; union { uint64_t z_tailextent_headlcn; erofs_off_t fragment_size; }; union { unsigned int z_idataoff; erofs_off_t fragmentoff; }; #define z_idata_size idata_size }; }; void *compressmeta; #ifdef WITH_ANDROID uint64_t capabilities; #endif }; static inline erofs_off_t erofs_iloc(struct erofs_inode *inode) { struct erofs_sb_info *sbi = inode->sbi; return erofs_pos(sbi, sbi->meta_blkaddr) + (inode->nid << sbi->islotbits); } static inline bool is_inode_layout_compression(struct erofs_inode *inode) { return erofs_inode_is_data_compressed(inode->datalayout); } static inline unsigned int erofs_inode_version(unsigned int ifmt) { return (ifmt >> EROFS_I_VERSION_BIT) & EROFS_I_VERSION_MASK; } static inline unsigned int erofs_inode_datalayout(unsigned int ifmt) { return (ifmt >> EROFS_I_DATALAYOUT_BIT) & EROFS_I_DATALAYOUT_MASK; } static inline struct erofs_inode *erofs_parent_inode(struct erofs_inode *inode) { return (struct erofs_inode *)((unsigned long)inode->i_parent & ~1UL); } #define IS_ROOT(x) ((x) == erofs_parent_inode(x)) #define EROFS_DENTRY_NAME_ALIGNMENT 4 struct erofs_dentry { struct list_head d_child; /* child of parent list */ union { struct erofs_inode *inode; erofs_nid_t nid; }; u8 namelen; u8 type; bool validnid; char name[]; }; static inline bool is_dot_dotdot_len(const char *name, unsigned int len) { if (len >= 1 && name[0] != '.') return false; return len == 1 || (len == 2 && name[1] == '.'); } static inline bool is_dot_dotdot(const char *name) { if (name[0] != '.') return false; return name[1] == '\0' || (name[1] == '.' && name[2] == '\0'); } #include #include static inline const char *erofs_strerror(int err) { static char msg[256]; sprintf(msg, "[Error %d] %s", -err, strerror(-err)); return msg; } enum { BH_Meta, BH_Mapped, BH_Encoded, BH_FullMapped, BH_Fragment, BH_Partialref, }; /* Has a disk mapping */ #define EROFS_MAP_MAPPED (1 << BH_Mapped) /* Located in metadata (could be copied from bd_inode) */ #define EROFS_MAP_META (1 << BH_Meta) /* The extent is encoded */ #define EROFS_MAP_ENCODED (1 << BH_Encoded) /* The length of extent is full */ #define EROFS_MAP_FULL_MAPPED (1 << BH_FullMapped) /* Located in the special packed inode */ #define EROFS_MAP_FRAGMENT (1 << BH_Fragment) /* The extent refers to partial decompressed data */ #define EROFS_MAP_PARTIAL_REF (1 << BH_Partialref) struct erofs_map_blocks { char mpage[EROFS_MAX_BLOCK_SIZE]; erofs_off_t m_pa, m_la; u64 m_plen, m_llen; unsigned short m_deviceid; char m_algorithmformat; unsigned int m_flags; erofs_blk_t index; }; /* * Used to get the exact decompressed length, e.g. fiemap (consider lookback * approach instead if possible since it's more metadata lightweight.) */ #define EROFS_GET_BLOCKS_FIEMAP 0x0002 /* Used to map tail extent for tailpacking inline or fragment pcluster */ #define EROFS_GET_BLOCKS_FINDTAIL 0x0008 enum { Z_EROFS_COMPRESSION_SHIFTED = Z_EROFS_COMPRESSION_MAX, Z_EROFS_COMPRESSION_INTERLACED, Z_EROFS_COMPRESSION_RUNTIME_MAX }; struct erofs_map_dev { erofs_off_t m_pa; unsigned int m_deviceid; }; /* super.c */ int erofs_read_superblock(struct erofs_sb_info *sbi); void erofs_put_super(struct erofs_sb_info *sbi); int erofs_writesb(struct erofs_sb_info *sbi, struct erofs_buffer_head *sb_bh, erofs_blk_t *blocks); struct erofs_buffer_head *erofs_reserve_sb(struct erofs_bufmgr *bmgr); int erofs_enable_sb_chksum(struct erofs_sb_info *sbi, u32 *crc); /* namei.c */ int erofs_read_inode_from_disk(struct erofs_inode *vi); int erofs_ilookup(const char *path, struct erofs_inode *vi); /* data.c */ int erofs_pread(struct erofs_inode *inode, char *buf, erofs_off_t count, erofs_off_t offset); int erofs_map_blocks(struct erofs_inode *inode, struct erofs_map_blocks *map, int flags); int erofs_map_dev(struct erofs_sb_info *sbi, struct erofs_map_dev *map); int erofs_read_one_data(struct erofs_inode *inode, struct erofs_map_blocks *map, char *buffer, u64 offset, size_t len); int z_erofs_read_one_data(struct erofs_inode *inode, struct erofs_map_blocks *map, char *raw, char *buffer, erofs_off_t skip, erofs_off_t length, bool trimmed); void *erofs_read_metadata(struct erofs_sb_info *sbi, erofs_nid_t nid, erofs_off_t *offset, int *lengthp); int z_erofs_parse_cfgs(struct erofs_sb_info *sbi, struct erofs_super_block *dsb); static inline int erofs_get_occupied_size(const struct erofs_inode *inode, erofs_off_t *size) { *size = 0; switch (inode->datalayout) { case EROFS_INODE_FLAT_INLINE: case EROFS_INODE_FLAT_PLAIN: case EROFS_INODE_CHUNK_BASED: *size = inode->i_size; break; case EROFS_INODE_COMPRESSED_FULL: case EROFS_INODE_COMPRESSED_COMPACT: *size = inode->u.i_blocks * erofs_blksiz(inode->sbi); break; default: return -EOPNOTSUPP; } return 0; } /* data.c */ int erofs_getxattr(struct erofs_inode *vi, const char *name, char *buffer, size_t buffer_size); int erofs_listxattr(struct erofs_inode *vi, char *buffer, size_t buffer_size); /* zmap.c */ int z_erofs_map_blocks_iter(struct erofs_inode *vi, struct erofs_map_blocks *map, int flags); /* io.c */ int erofs_dev_open(struct erofs_sb_info *sbi, const char *dev, int flags); void erofs_dev_close(struct erofs_sb_info *sbi); void erofs_blob_closeall(struct erofs_sb_info *sbi); int erofs_blob_open_ro(struct erofs_sb_info *sbi, const char *dev); ssize_t erofs_dev_read(struct erofs_sb_info *sbi, int device_id, void *buf, u64 offset, size_t len); static inline int erofs_dev_write(struct erofs_sb_info *sbi, const void *buf, u64 offset, size_t len) { if (erofs_io_pwrite(&sbi->bdev, buf, offset, len) != (ssize_t)len) return -EIO; return 0; } static inline int erofs_dev_fillzero(struct erofs_sb_info *sbi, u64 offset, size_t len, bool pad) { return erofs_io_fallocate(&sbi->bdev, offset, len, pad); } static inline int erofs_dev_resize(struct erofs_sb_info *sbi, erofs_blk_t blocks) { return erofs_io_ftruncate(&sbi->bdev, (u64)blocks * erofs_blksiz(sbi)); } static inline int erofs_blk_write(struct erofs_sb_info *sbi, const void *buf, erofs_blk_t blkaddr, u32 nblocks) { return erofs_dev_write(sbi, buf, erofs_pos(sbi, blkaddr), erofs_pos(sbi, nblocks)); } static inline int erofs_blk_read(struct erofs_sb_info *sbi, int device_id, void *buf, erofs_blk_t start, u32 nblocks) { return erofs_dev_read(sbi, device_id, buf, erofs_pos(sbi, start), erofs_pos(sbi, nblocks)); } #ifdef EUCLEAN #define EFSCORRUPTED EUCLEAN /* Filesystem is corrupted */ #else #define EFSCORRUPTED EIO #endif #define CRC32C_POLY_LE 0x82F63B78 static inline u32 erofs_crc32c(u32 crc, const u8 *in, size_t len) { int i; while (len--) { crc ^= *in++; for (i = 0; i < 8; i++) crc = (crc >> 1) ^ ((crc & 1) ? CRC32C_POLY_LE : 0); } return crc; } #define EROFS_WHITEOUT_DEV 0 static inline bool erofs_inode_is_whiteout(struct erofs_inode *inode) { return S_ISCHR(inode->i_mode) && inode->u.i_rdev == EROFS_WHITEOUT_DEV; } #ifdef __cplusplus } #endif #endif erofs-utils-1.8.6/include/erofs/io.h000066400000000000000000000042061477440110200173250ustar00rootroot00000000000000/* SPDX-License-Identifier: GPL-2.0+ OR Apache-2.0 */ /* * Copyright (C) 2018-2019 HUAWEI, Inc. * http://www.huawei.com/ * Created by Li Guifu */ #ifndef __EROFS_IO_H #define __EROFS_IO_H #ifdef __cplusplus extern "C" { #endif #ifndef _GNU_SOURCE #define _GNU_SOURCE #endif #include #include "defs.h" #ifndef O_BINARY #define O_BINARY 0 #endif struct erofs_vfile; struct erofs_vfops { ssize_t (*pread)(struct erofs_vfile *vf, void *buf, u64 offset, size_t len); ssize_t (*pwrite)(struct erofs_vfile *vf, const void *buf, u64 offset, size_t len); int (*fsync)(struct erofs_vfile *vf); int (*fallocate)(struct erofs_vfile *vf, u64 offset, size_t len, bool pad); int (*ftruncate)(struct erofs_vfile *vf, u64 length); ssize_t (*read)(struct erofs_vfile *vf, void *buf, size_t len); off_t (*lseek)(struct erofs_vfile *vf, u64 offset, int whence); int (*fstat)(struct erofs_vfile *vf, struct stat *buf); int (*xcopy)(struct erofs_vfile *vout, off_t pos, struct erofs_vfile *vin, unsigned int len, bool noseek); }; /* don't extend this; instead, use payload for any extra information */ struct erofs_vfile { struct erofs_vfops *ops; union { struct { u64 offset; int fd; }; u8 payload[16]; }; }; ssize_t __erofs_io_write(int fd, const void *buf, size_t len); int erofs_io_fstat(struct erofs_vfile *vf, struct stat *buf); ssize_t erofs_io_pwrite(struct erofs_vfile *vf, const void *buf, u64 pos, size_t len); int erofs_io_fsync(struct erofs_vfile *vf); ssize_t erofs_io_fallocate(struct erofs_vfile *vf, u64 offset, size_t len, bool pad); int erofs_io_ftruncate(struct erofs_vfile *vf, u64 length); ssize_t erofs_io_pread(struct erofs_vfile *vf, void *buf, u64 offset, size_t len); ssize_t erofs_io_read(struct erofs_vfile *vf, void *buf, size_t len); off_t erofs_io_lseek(struct erofs_vfile *vf, u64 offset, int whence); ssize_t erofs_copy_file_range(int fd_in, u64 *off_in, int fd_out, u64 *off_out, size_t length); int erofs_io_xcopy(struct erofs_vfile *vout, off_t pos, struct erofs_vfile *vin, unsigned int len, bool noseek); #ifdef __cplusplus } #endif #endif // EROFS_IO_H_ erofs-utils-1.8.6/include/erofs/list.h000066400000000000000000000076451477440110200177030ustar00rootroot00000000000000/* SPDX-License-Identifier: GPL-2.0+ OR Apache-2.0 */ /* * Copyright (C) 2018 HUAWEI, Inc. * http://www.huawei.com/ * Created by Li Guifu */ #ifndef __EROFS_LIST_HEAD_H #define __EROFS_LIST_HEAD_H #ifdef __cplusplus extern "C" { #endif #include "defs.h" struct list_head { struct list_head *prev; struct list_head *next; }; #define LIST_HEAD_INIT(name) \ { \ &(name), &(name) \ } #define LIST_HEAD(name) struct list_head name = LIST_HEAD_INIT(name) static inline void init_list_head(struct list_head *list) { list->prev = list; list->next = list; } static inline void __list_add(struct list_head *entry, struct list_head *prev, struct list_head *next) { entry->prev = prev; entry->next = next; prev->next = entry; next->prev = entry; } static inline void list_add(struct list_head *entry, struct list_head *head) { __list_add(entry, head, head->next); } static inline void list_add_tail(struct list_head *entry, struct list_head *head) { __list_add(entry, head->prev, head); } static inline void __list_del(struct list_head *prev, struct list_head *next) { prev->next = next; next->prev = prev; } static inline void list_del(struct list_head *entry) { __list_del(entry->prev, entry->next); entry->prev = entry->next = NULL; } static inline int list_empty(struct list_head *head) { return head->next == head; } static inline void __list_splice(struct list_head *list, struct list_head *prev, struct list_head *next) { struct list_head *first = list->next; struct list_head *last = list->prev; first->prev = prev; prev->next = first; last->next = next; next->prev = last; } static inline void list_splice_tail(struct list_head *list, struct list_head *head) { if (!list_empty(list)) __list_splice(list, head->prev, head); } #define list_entry(ptr, type, member) container_of(ptr, type, member) #define list_first_entry(ptr, type, member) \ list_entry((ptr)->next, type, member) #define list_last_entry(ptr, type, member) \ list_entry((ptr)->prev, type, member) #define list_next_entry(pos, member) \ list_entry((pos)->member.next, typeof(*(pos)), member) #define list_prev_entry(pos, member) \ list_entry((pos)->member.prev, typeof(*(pos)), member) #define list_for_each(pos, head) \ for (pos = (head)->next; pos != (head); pos = pos->next) #define list_for_each_safe(pos, n, head) \ for (pos = (head)->next, n = pos->next; pos != (head); \ pos = n, n = pos->next) #define list_for_each_entry(pos, head, member) \ for (pos = list_first_entry(head, typeof(*pos), member); \ &pos->member != (head); \ pos = list_next_entry(pos, member)) #define list_for_each_entry_reverse(pos, head, member) \ for (pos = list_last_entry(head, typeof(*pos), member); \ &pos->member != (head); \ pos = list_prev_entry(pos, member)) #define list_for_each_entry_from(pos, head, member) \ for (; &pos->member != (head); pos = list_next_entry(pos, member)) #define list_for_each_entry_safe(pos, n, head, member) \ for (pos = list_first_entry(head, typeof(*pos), member), \ n = list_next_entry(pos, member); \ &pos->member != (head); \ pos = n, n = list_next_entry(n, member)) #ifdef __cplusplus } #endif #endif erofs-utils-1.8.6/include/erofs/print.h000066400000000000000000000033651477440110200200570ustar00rootroot00000000000000/* SPDX-License-Identifier: GPL-2.0+ OR Apache-2.0 */ /* * Copyright (C) 2018-2019 HUAWEI, Inc. * http://www.huawei.com/ * Created by Li Guifu */ #ifndef __EROFS_PRINT_H #define __EROFS_PRINT_H #ifdef __cplusplus extern "C" { #endif #include "config.h" #include enum { EROFS_MSG_MIN = 0, EROFS_ERR = 0, EROFS_WARN = 2, EROFS_INFO = 3, EROFS_DBG = 7, EROFS_MSG_MAX = 9 }; #ifndef EROFS_MODNAME #define EROFS_MODNAME "erofs" #endif #define FUNC_LINE_FMT "%s() Line[%d] " #ifdef NDEBUG #ifndef pr_fmt #define pr_fmt(fmt) EROFS_MODNAME ": " fmt "\n" #endif #define PR_FMT_FUNC_LINE(fmt) pr_fmt(fmt) #else #ifndef pr_fmt #define pr_fmt(fmt) EROFS_MODNAME ": " FUNC_LINE_FMT fmt "\n" #endif #define PR_FMT_FUNC_LINE(fmt) pr_fmt(fmt), __func__, __LINE__ #endif void erofs_msg(int dbglv, const char *fmt, ...); #define erofs_dbg(fmt, ...) do { \ if (cfg.c_dbg_lvl >= EROFS_DBG) { \ erofs_msg(EROFS_DBG, \ " " PR_FMT_FUNC_LINE(fmt), \ ##__VA_ARGS__); \ } \ } while (0) #define erofs_info(fmt, ...) do { \ if (cfg.c_dbg_lvl >= EROFS_INFO) { \ erofs_msg(EROFS_INFO, \ " " PR_FMT_FUNC_LINE(fmt), \ ##__VA_ARGS__); \ fflush(stdout); \ } \ } while (0) #define erofs_warn(fmt, ...) do { \ if (cfg.c_dbg_lvl >= EROFS_WARN) { \ erofs_msg(EROFS_WARN, \ " " PR_FMT_FUNC_LINE(fmt), \ ##__VA_ARGS__); \ fflush(stdout); \ } \ } while (0) #define erofs_err(fmt, ...) do { \ if (cfg.c_dbg_lvl >= EROFS_ERR) { \ erofs_msg(EROFS_ERR, \ " " PR_FMT_FUNC_LINE(fmt), \ ##__VA_ARGS__); \ } \ } while (0) #define erofs_dump(fmt, ...) fprintf(stderr, fmt, ##__VA_ARGS__) #ifdef __cplusplus } #endif #endif erofs-utils-1.8.6/include/erofs/rebuild.h000066400000000000000000000011121477440110200203350ustar00rootroot00000000000000/* SPDX-License-Identifier: GPL-2.0+ OR Apache-2.0 */ #ifndef __EROFS_REBUILD_H #define __EROFS_REBUILD_H #ifdef __cplusplus extern "C" { #endif #include "internal.h" enum erofs_rebuild_datamode { EROFS_REBUILD_DATA_BLOB_INDEX, EROFS_REBUILD_DATA_RESVSP, EROFS_REBUILD_DATA_FULL, }; struct erofs_dentry *erofs_rebuild_get_dentry(struct erofs_inode *pwd, char *path, bool aufs, bool *whout, bool *opq, bool to_head); int erofs_rebuild_load_tree(struct erofs_inode *root, struct erofs_sb_info *sbi, enum erofs_rebuild_datamode mode); #ifdef __cplusplus } #endif #endif erofs-utils-1.8.6/include/erofs/tar.h000066400000000000000000000022661477440110200175100ustar00rootroot00000000000000/* SPDX-License-Identifier: GPL-2.0+ OR Apache-2.0 */ #ifndef __EROFS_TAR_H #define __EROFS_TAR_H #ifdef __cplusplus extern "C" { #endif #include #include "internal.h" struct erofs_pax_header { struct stat st; struct list_head xattrs; bool use_mtime; bool use_size; bool use_uid; bool use_gid; char *path, *link; }; #define EROFS_IOS_DECODER_NONE 0 #define EROFS_IOS_DECODER_GZIP 1 #define EROFS_IOS_DECODER_LIBLZMA 2 struct erofs_iostream_liblzma; struct erofs_iostream { union { struct erofs_vfile vf; void *handler; #ifdef HAVE_LIBLZMA struct erofs_iostream_liblzma *lzma; #endif }; u64 sz; char *buffer; unsigned int head, tail, bufsize; int decoder, dumpfd; bool feof; }; struct erofs_tarfile { struct erofs_pax_header global; struct erofs_iostream ios; char *mapfile, *dumpfile; u32 dev; int fd; u64 offset; bool index_mode, headeronly_mode, rvsp_mode, aufs; bool ddtaridx_mode; bool try_no_reorder; }; void erofs_iostream_close(struct erofs_iostream *ios); int erofs_iostream_open(struct erofs_iostream *ios, int fd, int decoder); int tarerofs_parse_tar(struct erofs_inode *root, struct erofs_tarfile *tar); #ifdef __cplusplus } #endif #endif erofs-utils-1.8.6/include/erofs/trace.h000066400000000000000000000006111477440110200200100ustar00rootroot00000000000000/* SPDX-License-Identifier: GPL-2.0+ OR Apache-2.0 */ /* * Copyright (C) 2020 Gao Xiang */ #ifndef __EROFS_TRACE_H #define __EROFS_TRACE_H #ifdef __cplusplus extern "C" { #endif #define trace_erofs_map_blocks_flatmode_enter(inode, map, flags) ((void)0) #define trace_erofs_map_blocks_flatmode_exit(inode, map, flags, ret) ((void)0) #ifdef __cplusplus } #endif #endif erofs-utils-1.8.6/include/erofs/workqueue.h000066400000000000000000000016211477440110200207430ustar00rootroot00000000000000/* SPDX-License-Identifier: GPL-2.0+ OR Apache-2.0 */ #ifndef __EROFS_WORKQUEUE_H #define __EROFS_WORKQUEUE_H #include "internal.h" struct erofs_workqueue; typedef void *(*erofs_wq_func_t)(struct erofs_workqueue *, void *); struct erofs_work { struct erofs_work *next; void (*fn)(struct erofs_work *work, void *tlsp); }; struct erofs_workqueue { struct erofs_work *head, *tail; pthread_mutex_t lock; pthread_cond_t cond_empty; pthread_cond_t cond_full; pthread_t *workers; unsigned int nworker; unsigned int max_jobs; unsigned int job_count; bool shutdown; erofs_wq_func_t on_start, on_exit; }; int erofs_alloc_workqueue(struct erofs_workqueue *wq, unsigned int nworker, unsigned int max_jobs, erofs_wq_func_t on_start, erofs_wq_func_t on_exit); int erofs_queue_work(struct erofs_workqueue *wq, struct erofs_work *work); int erofs_destroy_workqueue(struct erofs_workqueue *wq); #endif erofs-utils-1.8.6/include/erofs/xattr.h000066400000000000000000000040761477440110200200650ustar00rootroot00000000000000/* SPDX-License-Identifier: GPL-2.0+ OR Apache-2.0 */ /* * Originally contributed by an anonymous person, * heavily changed by Li Guifu * and Gao Xiang */ #ifndef __EROFS_XATTR_H #define __EROFS_XATTR_H #ifdef __cplusplus extern "C" { #endif #include "internal.h" #ifndef ENOATTR #define ENOATTR ENODATA #endif static inline unsigned int inlinexattr_header_size(struct erofs_inode *vi) { return sizeof(struct erofs_xattr_ibody_header) + sizeof(u32) * vi->xattr_shared_count; } static inline erofs_blk_t xattrblock_addr(struct erofs_inode *vi, unsigned int xattr_id) { return vi->sbi->xattr_blkaddr + erofs_blknr(vi->sbi, xattr_id * sizeof(__u32)); } static inline unsigned int xattrblock_offset(struct erofs_inode *vi, unsigned int xattr_id) { return erofs_blkoff(vi->sbi, xattr_id * sizeof(__u32)); } #define EROFS_INODE_XATTR_ICOUNT(_size) ({\ u32 __size = le16_to_cpu(_size); \ ((__size) == 0) ? 0 : \ (_size - sizeof(struct erofs_xattr_ibody_header)) / \ sizeof(struct erofs_xattr_entry) + 1; }) int erofs_scan_file_xattrs(struct erofs_inode *inode); int erofs_prepare_xattr_ibody(struct erofs_inode *inode, bool noroom); char *erofs_export_xattr_ibody(struct erofs_inode *inode); int erofs_build_shared_xattrs_from_path(struct erofs_sb_info *sbi, const char *path); int erofs_xattr_insert_name_prefix(const char *prefix); void erofs_xattr_cleanup_name_prefixes(void); int erofs_xattr_flush_name_prefixes(struct erofs_sb_info *sbi); void erofs_xattr_prefixes_cleanup(struct erofs_sb_info *sbi); int erofs_xattr_prefixes_init(struct erofs_sb_info *sbi); int erofs_setxattr(struct erofs_inode *inode, char *key, const void *value, size_t size); int erofs_set_opaque_xattr(struct erofs_inode *inode); void erofs_clear_opaque_xattr(struct erofs_inode *inode); int erofs_set_origin_xattr(struct erofs_inode *inode); int erofs_read_xattrs_from_disk(struct erofs_inode *inode); bool erofs_xattr_prefix_matches(const char *key, unsigned int *index, unsigned int *len); #ifdef __cplusplus } #endif #endif erofs-utils-1.8.6/include/erofs_fs.h000066400000000000000000000363211477440110200174110ustar00rootroot00000000000000/* SPDX-License-Identifier: GPL-2.0-only OR Apache-2.0 */ /* * EROFS (Enhanced ROM File System) on-disk format definition * * Copyright (C) 2017-2018 HUAWEI, Inc. * https://www.huawei.com/ * Copyright (C) 2021, Alibaba Cloud */ #ifndef __EROFS_FS_H #define __EROFS_FS_H #define EROFS_SUPER_MAGIC_V1 0xE0F5E1E2 /* to allow for x86 boot sectors and other oddities. */ #define EROFS_SUPER_OFFSET 1024 #define EROFS_FEATURE_COMPAT_SB_CHKSUM 0x00000001 #define EROFS_FEATURE_COMPAT_MTIME 0x00000002 #define EROFS_FEATURE_COMPAT_XATTR_FILTER 0x00000004 /* * Any bits that aren't in EROFS_ALL_FEATURE_INCOMPAT should * be incompatible with this kernel version. */ #define EROFS_FEATURE_INCOMPAT_ZERO_PADDING 0x00000001 #define EROFS_FEATURE_INCOMPAT_COMPR_CFGS 0x00000002 #define EROFS_FEATURE_INCOMPAT_BIG_PCLUSTER 0x00000002 #define EROFS_FEATURE_INCOMPAT_CHUNKED_FILE 0x00000004 #define EROFS_FEATURE_INCOMPAT_DEVICE_TABLE 0x00000008 #define EROFS_FEATURE_INCOMPAT_COMPR_HEAD2 0x00000008 #define EROFS_FEATURE_INCOMPAT_ZTAILPACKING 0x00000010 #define EROFS_FEATURE_INCOMPAT_FRAGMENTS 0x00000020 #define EROFS_FEATURE_INCOMPAT_DEDUPE 0x00000020 #define EROFS_FEATURE_INCOMPAT_XATTR_PREFIXES 0x00000040 #define EROFS_ALL_FEATURE_INCOMPAT \ (EROFS_FEATURE_INCOMPAT_ZERO_PADDING | \ EROFS_FEATURE_INCOMPAT_COMPR_CFGS | \ EROFS_FEATURE_INCOMPAT_BIG_PCLUSTER | \ EROFS_FEATURE_INCOMPAT_CHUNKED_FILE | \ EROFS_FEATURE_INCOMPAT_DEVICE_TABLE | \ EROFS_FEATURE_INCOMPAT_COMPR_HEAD2 | \ EROFS_FEATURE_INCOMPAT_ZTAILPACKING | \ EROFS_FEATURE_INCOMPAT_FRAGMENTS | \ EROFS_FEATURE_INCOMPAT_DEDUPE | \ EROFS_FEATURE_INCOMPAT_XATTR_PREFIXES) #define EROFS_SB_EXTSLOT_SIZE 16 struct erofs_deviceslot { u8 tag[64]; /* digest(sha256), etc. */ __le32 blocks; /* total fs blocks of this device */ __le32 mapped_blkaddr; /* map starting at mapped_blkaddr */ u8 reserved[56]; }; #define EROFS_DEVT_SLOT_SIZE sizeof(struct erofs_deviceslot) /* erofs on-disk super block (currently 128 bytes) */ struct erofs_super_block { __le32 magic; /* file system magic number */ __le32 checksum; /* crc32c to avoid unexpected on-disk overlap */ __le32 feature_compat; __u8 blkszbits; /* filesystem block size in bit shift */ __u8 sb_extslots; /* superblock size = 128 + sb_extslots * 16 */ __le16 root_nid; /* nid of root directory */ __le64 inos; /* total valid ino # (== f_files - f_favail) */ __le64 build_time; /* compact inode time derivation */ __le32 build_time_nsec; /* compact inode time derivation in ns scale */ __le32 blocks; /* used for statfs */ __le32 meta_blkaddr; /* start block address of metadata area */ __le32 xattr_blkaddr; /* start block address of shared xattr area */ __u8 uuid[16]; /* 128-bit uuid for volume */ __u8 volume_name[16]; /* volume name */ __le32 feature_incompat; union { /* bitmap for available compression algorithms */ __le16 available_compr_algs; /* customized sliding window size instead of 64k by default */ __le16 lz4_max_distance; } __packed u1; __le16 extra_devices; /* # of devices besides the primary device */ __le16 devt_slotoff; /* startoff = devt_slotoff * devt_slotsize */ __u8 dirblkbits; /* directory block size in bit shift */ __u8 xattr_prefix_count; /* # of long xattr name prefixes */ __le32 xattr_prefix_start; /* start of long xattr prefixes */ __le64 packed_nid; /* nid of the special packed inode */ __u8 xattr_filter_reserved; /* reserved for xattr name filter */ __u8 reserved2[23]; }; /* * EROFS inode datalayout (i_format in on-disk inode): * 0 - uncompressed flat inode without tail-packing inline data: * 1 - compressed inode with non-compact indexes: * 2 - uncompressed flat inode with tail-packing inline data: * 3 - compressed inode with compact indexes: * 4 - chunk-based inode with (optional) multi-device support: * 5~7 - reserved */ enum { EROFS_INODE_FLAT_PLAIN = 0, EROFS_INODE_COMPRESSED_FULL = 1, EROFS_INODE_FLAT_INLINE = 2, EROFS_INODE_COMPRESSED_COMPACT = 3, EROFS_INODE_CHUNK_BASED = 4, EROFS_INODE_DATALAYOUT_MAX }; static inline bool erofs_inode_is_data_compressed(unsigned int datamode) { return datamode == EROFS_INODE_COMPRESSED_COMPACT || datamode == EROFS_INODE_COMPRESSED_FULL; } /* bit definitions of inode i_format */ #define EROFS_I_VERSION_MASK 0x01 #define EROFS_I_DATALAYOUT_MASK 0x07 #define EROFS_I_VERSION_BIT 0 #define EROFS_I_DATALAYOUT_BIT 1 #define EROFS_I_ALL_BIT 4 #define EROFS_I_ALL ((1 << EROFS_I_ALL_BIT) - 1) /* indicate chunk blkbits, thus 'chunksize = blocksize << chunk blkbits' */ #define EROFS_CHUNK_FORMAT_BLKBITS_MASK 0x001F /* with chunk indexes or just a 4-byte blkaddr array */ #define EROFS_CHUNK_FORMAT_INDEXES 0x0020 #define EROFS_CHUNK_FORMAT_ALL \ (EROFS_CHUNK_FORMAT_BLKBITS_MASK | EROFS_CHUNK_FORMAT_INDEXES) /* 32-byte on-disk inode */ #define EROFS_INODE_LAYOUT_COMPACT 0 /* 64-byte on-disk inode */ #define EROFS_INODE_LAYOUT_EXTENDED 1 struct erofs_inode_chunk_info { __le16 format; /* chunk blkbits, etc. */ __le16 reserved; }; union erofs_inode_i_u { /* total compressed blocks for compressed inodes */ __le32 compressed_blocks; /* block address for uncompressed flat inodes */ __le32 raw_blkaddr; /* for device files, used to indicate old/new device # */ __le32 rdev; /* for chunk-based files, it contains the summary info */ struct erofs_inode_chunk_info c; }; /* 32-byte reduced form of an ondisk inode */ struct erofs_inode_compact { __le16 i_format; /* inode format hints */ /* 1 header + n-1 * 4 bytes inline xattr to keep continuity */ __le16 i_xattr_icount; __le16 i_mode; __le16 i_nlink; __le32 i_size; __le32 i_reserved; union erofs_inode_i_u i_u; __le32 i_ino; /* only used for 32-bit stat compatibility */ __le16 i_uid; __le16 i_gid; __le32 i_reserved2; }; /* 64-byte complete form of an ondisk inode */ struct erofs_inode_extended { __le16 i_format; /* inode format hints */ /* 1 header + n-1 * 4 bytes inline xattr to keep continuity */ __le16 i_xattr_icount; __le16 i_mode; __le16 i_reserved; __le64 i_size; union erofs_inode_i_u i_u; __le32 i_ino; /* only used for 32-bit stat compatibility */ __le32 i_uid; __le32 i_gid; __le64 i_mtime; __le32 i_mtime_nsec; __le32 i_nlink; __u8 i_reserved2[16]; }; /* * inline xattrs (n == i_xattr_icount): * erofs_xattr_ibody_header(1) + (n - 1) * 4 bytes * 12 bytes / \ * / \ * /-----------------------\ * | erofs_xattr_entries+ | * +-----------------------+ * inline xattrs must starts in erofs_xattr_ibody_header, * for read-only fs, no need to introduce h_refcount */ struct erofs_xattr_ibody_header { __le32 h_name_filter; /* bit value 1 indicates not-present */ __u8 h_shared_count; __u8 h_reserved2[7]; __le32 h_shared_xattrs[0]; /* shared xattr id array */ }; /* Name indexes */ #define EROFS_XATTR_INDEX_USER 1 #define EROFS_XATTR_INDEX_POSIX_ACL_ACCESS 2 #define EROFS_XATTR_INDEX_POSIX_ACL_DEFAULT 3 #define EROFS_XATTR_INDEX_TRUSTED 4 #define EROFS_XATTR_INDEX_LUSTRE 5 #define EROFS_XATTR_INDEX_SECURITY 6 /* * bit 7 of e_name_index is set when it refers to a long xattr name prefix, * while the remained lower bits represent the index of the prefix. */ #define EROFS_XATTR_LONG_PREFIX 0x80 #define EROFS_XATTR_LONG_PREFIX_MASK 0x7f #define EROFS_XATTR_FILTER_BITS 32 #define EROFS_XATTR_FILTER_DEFAULT UINT32_MAX #define EROFS_XATTR_FILTER_SEED 0x25BBE08F /* xattr entry (for both inline & shared xattrs) */ struct erofs_xattr_entry { __u8 e_name_len; /* length of name */ __u8 e_name_index; /* attribute name index */ __le16 e_value_size; /* size of attribute value */ /* followed by e_name and e_value */ char e_name[0]; /* attribute name */ }; /* long xattr name prefix */ struct erofs_xattr_long_prefix { __u8 base_index; /* short xattr name prefix index */ char infix[0]; /* infix apart from short prefix */ }; static inline unsigned int erofs_xattr_ibody_size(__le16 i_xattr_icount) { if (!i_xattr_icount) return 0; return sizeof(struct erofs_xattr_ibody_header) + sizeof(__u32) * (le16_to_cpu(i_xattr_icount) - 1); } #define EROFS_XATTR_ALIGN(size) round_up(size, sizeof(struct erofs_xattr_entry)) static inline unsigned int erofs_xattr_entry_size(struct erofs_xattr_entry *e) { return EROFS_XATTR_ALIGN(sizeof(struct erofs_xattr_entry) + e->e_name_len + le16_to_cpu(e->e_value_size)); } /* represent a zeroed chunk (hole) */ #define EROFS_NULL_ADDR -1 /* 4-byte block address array */ #define EROFS_BLOCK_MAP_ENTRY_SIZE sizeof(__le32) /* 8-byte inode chunk indexes */ struct erofs_inode_chunk_index { __le16 advise; /* always 0, don't care for now */ __le16 device_id; /* back-end storage id (with bits masked) */ __le32 blkaddr; /* start block address of this inode chunk */ }; /* dirent sorts in alphabet order, thus we can do binary search */ struct erofs_dirent { __le64 nid; /* node number */ __le16 nameoff; /* start offset of file name */ __u8 file_type; /* file type */ __u8 reserved; /* reserved */ } __packed; /* file types used in inode_info->flags */ enum { EROFS_FT_UNKNOWN, EROFS_FT_REG_FILE, EROFS_FT_DIR, EROFS_FT_CHRDEV, EROFS_FT_BLKDEV, EROFS_FT_FIFO, EROFS_FT_SOCK, EROFS_FT_SYMLINK, EROFS_FT_MAX }; #define EROFS_NAME_LEN 255 /* maximum supported encoded size of a physical compressed cluster */ #define Z_EROFS_PCLUSTER_MAX_SIZE (1024 * 1024) /* maximum supported decoded size of a physical compressed cluster */ #define Z_EROFS_PCLUSTER_MAX_DSIZE (12 * 1024 * 1024) /* available compression algorithm types (for h_algorithmtype) */ enum { Z_EROFS_COMPRESSION_LZ4 = 0, Z_EROFS_COMPRESSION_LZMA = 1, Z_EROFS_COMPRESSION_DEFLATE = 2, Z_EROFS_COMPRESSION_ZSTD = 3, Z_EROFS_COMPRESSION_MAX }; #define Z_EROFS_ALL_COMPR_ALGS ((1 << Z_EROFS_COMPRESSION_MAX) - 1) /* 14 bytes (+ length field = 16 bytes) */ struct z_erofs_lz4_cfgs { __le16 max_distance; __le16 max_pclusterblks; u8 reserved[10]; } __packed; /* 14 bytes (+ length field = 16 bytes) */ struct z_erofs_lzma_cfgs { __le32 dict_size; __le16 format; u8 reserved[8]; } __packed; #define Z_EROFS_LZMA_MAX_DICT_SIZE (8 * Z_EROFS_PCLUSTER_MAX_SIZE) /* 6 bytes (+ length field = 8 bytes) */ struct z_erofs_deflate_cfgs { u8 windowbits; /* 8..15 for DEFLATE */ u8 reserved[5]; } __packed; /* 6 bytes (+ length field = 8 bytes) */ struct z_erofs_zstd_cfgs { u8 format; u8 windowlog; /* windowLog - ZSTD_WINDOWLOG_ABSOLUTEMIN(10) */ u8 reserved[4]; } __packed; #define Z_EROFS_ZSTD_MAX_DICT_SIZE Z_EROFS_PCLUSTER_MAX_SIZE /* * bit 0 : COMPACTED_2B indexes (0 - off; 1 - on) * e.g. for 4k logical cluster size, 4B if compacted 2B is off; * (4B) + 2B + (4B) if compacted 2B is on. * bit 1 : HEAD1 big pcluster (0 - off; 1 - on) * bit 2 : HEAD2 big pcluster (0 - off; 1 - on) * bit 3 : tailpacking inline pcluster (0 - off; 1 - on) * bit 4 : interlaced plain pcluster (0 - off; 1 - on) * bit 5 : fragment pcluster (0 - off; 1 - on) */ #define Z_EROFS_ADVISE_COMPACTED_2B 0x0001 #define Z_EROFS_ADVISE_BIG_PCLUSTER_1 0x0002 #define Z_EROFS_ADVISE_BIG_PCLUSTER_2 0x0004 #define Z_EROFS_ADVISE_INLINE_PCLUSTER 0x0008 #define Z_EROFS_ADVISE_INTERLACED_PCLUSTER 0x0010 #define Z_EROFS_ADVISE_FRAGMENT_PCLUSTER 0x0020 #define Z_EROFS_FRAGMENT_INODE_BIT 7 struct z_erofs_map_header { union { /* fragment data offset in the packed inode */ __le32 h_fragmentoff; struct { __le16 h_reserved1; /* indicates the encoded size of tailpacking data */ __le16 h_idata_size; }; }; __le16 h_advise; /* * bit 0-3 : algorithm type of head 1 (logical cluster type 01); * bit 4-7 : algorithm type of head 2 (logical cluster type 11). */ __u8 h_algorithmtype; /* * bit 0-2 : logical cluster bits - 12, e.g. 0 for 4096; * bit 3-6 : reserved; * bit 7 : move the whole file into packed inode or not. */ __u8 h_clusterbits; }; /* * On-disk logical cluster type: * 0 - literal (uncompressed) lcluster * 1,3 - compressed lcluster (for HEAD lclusters) * 2 - compressed lcluster (for NONHEAD lclusters) * * In detail, * 0 - literal (uncompressed) lcluster, * di_advise = 0 * di_clusterofs = the literal data offset of the lcluster * di_blkaddr = the blkaddr of the literal pcluster * * 1,3 - compressed lcluster (for HEAD lclusters) * di_advise = 1 or 3 * di_clusterofs = the decompressed data offset of the lcluster * di_blkaddr = the blkaddr of the compressed pcluster * * 2 - compressed lcluster (for NONHEAD lclusters) * di_advise = 2 * di_clusterofs = * the decompressed data offset in its own HEAD lcluster * di_u.delta[0] = distance to this HEAD lcluster * di_u.delta[1] = distance to the next HEAD lcluster */ enum { Z_EROFS_LCLUSTER_TYPE_PLAIN = 0, Z_EROFS_LCLUSTER_TYPE_HEAD1 = 1, Z_EROFS_LCLUSTER_TYPE_NONHEAD = 2, Z_EROFS_LCLUSTER_TYPE_HEAD2 = 3, Z_EROFS_LCLUSTER_TYPE_MAX }; #define Z_EROFS_LI_LCLUSTER_TYPE_MASK (Z_EROFS_LCLUSTER_TYPE_MAX - 1) /* (noncompact only, HEAD) This pcluster refers to partial decompressed data */ #define Z_EROFS_LI_PARTIAL_REF (1 << 15) /* * D0_CBLKCNT will be marked _only_ at the 1st non-head lcluster to store the * compressed block count of a compressed extent (in logical clusters, aka. * block count of a pcluster). */ #define Z_EROFS_LI_D0_CBLKCNT (1 << 11) struct z_erofs_lcluster_index { __le16 di_advise; /* where to decompress in the head lcluster */ __le16 di_clusterofs; union { /* for the HEAD lclusters */ __le32 blkaddr; /* * for the NONHEAD lclusters * [0] - distance to its HEAD lcluster * [1] - distance to the next HEAD lcluster */ __le16 delta[2]; } di_u; }; #define Z_EROFS_FULL_INDEX_ALIGN(end) \ (round_up(end, 8) + sizeof(struct z_erofs_map_header) + 8) /* check the EROFS on-disk layout strictly at compile time */ static inline void erofs_check_ondisk_layout_definitions(void) { #ifndef __cplusplus const union { struct z_erofs_map_header h; __le64 v; } fmh __maybe_unused = { .h.h_clusterbits = 1 << Z_EROFS_FRAGMENT_INODE_BIT, }; #endif BUILD_BUG_ON(sizeof(struct erofs_super_block) != 128); BUILD_BUG_ON(sizeof(struct erofs_inode_compact) != 32); BUILD_BUG_ON(sizeof(struct erofs_inode_extended) != 64); BUILD_BUG_ON(sizeof(struct erofs_xattr_ibody_header) != 12); BUILD_BUG_ON(sizeof(struct erofs_xattr_entry) != 4); BUILD_BUG_ON(sizeof(struct erofs_inode_chunk_info) != 4); BUILD_BUG_ON(sizeof(struct erofs_inode_chunk_index) != 8); BUILD_BUG_ON(sizeof(struct z_erofs_map_header) != 8); BUILD_BUG_ON(sizeof(struct z_erofs_lcluster_index) != 8); BUILD_BUG_ON(sizeof(struct erofs_dirent) != 12); /* keep in sync between 2 index structures for better extendibility */ BUILD_BUG_ON(sizeof(struct erofs_inode_chunk_index) != sizeof(struct z_erofs_lcluster_index)); BUILD_BUG_ON(sizeof(struct erofs_deviceslot) != 128); #ifndef __cplusplus /* exclude old compiler versions like gcc 7.5.0 */ BUILD_BUG_ON(__builtin_constant_p(fmh.v) ? fmh.v != cpu_to_le64(1ULL << 63) : 0); #endif } #endif erofs-utils-1.8.6/lib/000077500000000000000000000000001477440110200145505ustar00rootroot00000000000000erofs-utils-1.8.6/lib/Makefile.am000066400000000000000000000047711477440110200166150ustar00rootroot00000000000000# SPDX-License-Identifier: GPL-2.0+ OR Apache-2.0 noinst_LTLIBRARIES = liberofs.la noinst_HEADERS = $(top_srcdir)/include/erofs_fs.h \ $(top_srcdir)/include/erofs/blobchunk.h \ $(top_srcdir)/include/erofs/block_list.h \ $(top_srcdir)/include/erofs/cache.h \ $(top_srcdir)/include/erofs/compress.h \ $(top_srcdir)/include/erofs/config.h \ $(top_srcdir)/include/erofs/decompress.h \ $(top_srcdir)/include/erofs/defs.h \ $(top_srcdir)/include/erofs/diskbuf.h \ $(top_srcdir)/include/erofs/err.h \ $(top_srcdir)/include/erofs/exclude.h \ $(top_srcdir)/include/erofs/flex-array.h \ $(top_srcdir)/include/erofs/hashmap.h \ $(top_srcdir)/include/erofs/hashtable.h \ $(top_srcdir)/include/erofs/inode.h \ $(top_srcdir)/include/erofs/internal.h \ $(top_srcdir)/include/erofs/io.h \ $(top_srcdir)/include/erofs/list.h \ $(top_srcdir)/include/erofs/print.h \ $(top_srcdir)/include/erofs/bitops.h \ $(top_srcdir)/include/erofs/tar.h \ $(top_srcdir)/include/erofs/trace.h \ $(top_srcdir)/include/erofs/xattr.h \ $(top_srcdir)/include/erofs/compress_hints.h \ $(top_srcdir)/include/erofs/fragments.h \ $(top_srcdir)/include/erofs/rebuild.h \ $(top_srcdir)/lib/liberofs_private.h \ $(top_srcdir)/lib/liberofs_xxhash.h noinst_HEADERS += compressor.h liberofs_la_SOURCES = config.c io.c cache.c super.c inode.c xattr.c exclude.c \ namei.c data.c compress.c compressor.c zmap.c decompress.c \ compress_hints.c hashmap.c sha256.c blobchunk.c dir.c \ fragments.c dedupe.c uuid_unparse.c uuid.c tar.c \ block_list.c rebuild.c diskbuf.c bitops.c dedupe_ext.c liberofs_la_CFLAGS = -Wall ${libuuid_CFLAGS} -I$(top_srcdir)/include if ENABLE_LZ4 liberofs_la_CFLAGS += ${liblz4_CFLAGS} liberofs_la_SOURCES += compressor_lz4.c if ENABLE_LZ4HC liberofs_la_SOURCES += compressor_lz4hc.c endif endif if ENABLE_LIBLZMA liberofs_la_CFLAGS += ${liblzma_CFLAGS} liberofs_la_SOURCES += compressor_liblzma.c endif liberofs_la_SOURCES += kite_deflate.c compressor_deflate.c if ENABLE_LIBDEFLATE liberofs_la_CFLAGS += ${libdeflate_CFLAGS} liberofs_la_SOURCES += compressor_libdeflate.c endif if ENABLE_LIBZSTD liberofs_la_CFLAGS += ${libzstd_CFLAGS} liberofs_la_SOURCES += compressor_libzstd.c endif if ENABLE_XXHASH liberofs_la_CFLAGS += ${libxxhash_CFLAGS} else liberofs_la_SOURCES += xxhash.c endif if ENABLE_EROFS_MT liberofs_la_LDFLAGS = -lpthread liberofs_la_SOURCES += workqueue.c endif erofs-utils-1.8.6/lib/bitops.c000066400000000000000000000011761477440110200162210ustar00rootroot00000000000000// SPDX-License-Identifier: GPL-2.0+ OR Apache-2.0 /* * erofs-utils/lib/bitops.c * * Copyright (C) 2025, Alibaba Cloud */ #include unsigned long erofs_find_next_bit(const unsigned long *addr, unsigned long nbits, unsigned long start) { unsigned long tmp; if (__erofs_unlikely(start >= nbits)) return nbits; tmp = addr[start / BITS_PER_LONG]; tmp &= ~0UL << ((start) & (BITS_PER_LONG - 1)); start = round_down(start, BITS_PER_LONG); while (!tmp) { start += BITS_PER_LONG; if (start >= nbits) return nbits; tmp = addr[start / BITS_PER_LONG]; } return min(start + ffs_long(tmp), nbits); } erofs-utils-1.8.6/lib/blobchunk.c000066400000000000000000000425501477440110200166710ustar00rootroot00000000000000// SPDX-License-Identifier: GPL-2.0+ OR Apache-2.0 /* * erofs-utils/lib/blobchunk.c * * Copyright (C) 2021, Alibaba Cloud */ #define _GNU_SOURCE #include "erofs/hashmap.h" #include "erofs/blobchunk.h" #include "erofs/block_list.h" #include "erofs/cache.h" #include "liberofs_private.h" #include "sha256.h" #include struct erofs_blobchunk { union { struct hashmap_entry ent; struct list_head list; }; char sha256[32]; unsigned int device_id; union { erofs_off_t chunksize; erofs_off_t sourceoffset; }; erofs_blk_t blkaddr; }; static struct hashmap blob_hashmap; static int blobfile = -1; static erofs_blk_t remapped_base; static erofs_off_t datablob_size; static bool multidev; static struct erofs_buffer_head *bh_devt; struct erofs_blobchunk erofs_holechunk = { .blkaddr = EROFS_NULL_ADDR, }; static LIST_HEAD(unhashed_blobchunks); struct erofs_blobchunk *erofs_get_unhashed_chunk(unsigned int device_id, erofs_blk_t blkaddr, erofs_off_t sourceoffset) { struct erofs_blobchunk *chunk; chunk = calloc(1, sizeof(struct erofs_blobchunk)); if (!chunk) return ERR_PTR(-ENOMEM); chunk->device_id = device_id; chunk->blkaddr = blkaddr; chunk->sourceoffset = sourceoffset; list_add_tail(&chunk->list, &unhashed_blobchunks); return chunk; } static struct erofs_blobchunk *erofs_blob_getchunk(struct erofs_sb_info *sbi, u8 *buf, erofs_off_t chunksize) { static u8 zeroed[EROFS_MAX_BLOCK_SIZE]; struct erofs_blobchunk *chunk; unsigned int hash, padding; u8 sha256[32]; erofs_off_t blkpos; int ret; erofs_sha256(buf, chunksize, sha256); hash = memhash(sha256, sizeof(sha256)); chunk = hashmap_get_from_hash(&blob_hashmap, hash, sha256); if (chunk) { DBG_BUGON(chunksize != chunk->chunksize); sbi->saved_by_deduplication += chunksize; if (chunk->blkaddr == erofs_holechunk.blkaddr) { chunk = &erofs_holechunk; erofs_dbg("Found duplicated hole chunk"); } else { erofs_dbg("Found duplicated chunk at %u", chunk->blkaddr); } return chunk; } chunk = malloc(sizeof(struct erofs_blobchunk)); if (!chunk) return ERR_PTR(-ENOMEM); chunk->chunksize = chunksize; memcpy(chunk->sha256, sha256, sizeof(sha256)); blkpos = lseek(blobfile, 0, SEEK_CUR); DBG_BUGON(erofs_blkoff(sbi, blkpos)); if (sbi->extra_devices) chunk->device_id = 1; else chunk->device_id = 0; chunk->blkaddr = erofs_blknr(sbi, blkpos); erofs_dbg("Writing chunk (%llu bytes) to %u", chunksize | 0ULL, chunk->blkaddr); ret = __erofs_io_write(blobfile, buf, chunksize); if (ret == chunksize) { padding = erofs_blkoff(sbi, chunksize); if (padding) { padding = erofs_blksiz(sbi) - padding; ret = __erofs_io_write(blobfile, zeroed, padding); if (ret > 0 && ret != padding) ret = -EIO; } } else if (ret >= 0) { ret = -EIO; } if (ret < 0) { free(chunk); return ERR_PTR(ret); } hashmap_entry_init(&chunk->ent, hash); hashmap_add(&blob_hashmap, chunk); return chunk; } static int erofs_blob_hashmap_cmp(const void *a, const void *b, const void *key) { const struct erofs_blobchunk *ec1 = container_of((struct hashmap_entry *)a, struct erofs_blobchunk, ent); const struct erofs_blobchunk *ec2 = container_of((struct hashmap_entry *)b, struct erofs_blobchunk, ent); return memcmp(ec1->sha256, key ? key : ec2->sha256, sizeof(ec1->sha256)); } int erofs_blob_write_chunk_indexes(struct erofs_inode *inode, erofs_off_t off) { struct erofs_sb_info *sbi = inode->sbi; erofs_blk_t remaining_blks = BLK_ROUND_UP(sbi, inode->i_size); struct erofs_inode_chunk_index idx = {0}; erofs_blk_t extent_start = EROFS_NULL_ADDR; erofs_blk_t extent_end, chunkblks; erofs_off_t source_offset; unsigned int dst, src, unit, zeroedlen; bool first_extent = true; if (inode->u.chunkformat & EROFS_CHUNK_FORMAT_INDEXES) unit = sizeof(struct erofs_inode_chunk_index); else unit = EROFS_BLOCK_MAP_ENTRY_SIZE; chunkblks = 1U << (inode->u.chunkformat & EROFS_CHUNK_FORMAT_BLKBITS_MASK); for (dst = src = 0; dst < inode->extent_isize; src += sizeof(void *), dst += unit) { struct erofs_blobchunk *chunk; chunk = *(void **)(inode->chunkindexes + src); if (chunk->blkaddr == EROFS_NULL_ADDR) { idx.blkaddr = EROFS_NULL_ADDR; } else if (chunk->device_id) { DBG_BUGON(!(inode->u.chunkformat & EROFS_CHUNK_FORMAT_INDEXES)); idx.blkaddr = chunk->blkaddr; extent_start = EROFS_NULL_ADDR; } else { idx.blkaddr = remapped_base + chunk->blkaddr; } if (extent_start == EROFS_NULL_ADDR || idx.blkaddr != extent_end) { if (extent_start != EROFS_NULL_ADDR) { remaining_blks -= extent_end - extent_start; tarerofs_blocklist_write(extent_start, extent_end - extent_start, source_offset, 0); erofs_droid_blocklist_write_extent(inode, extent_start, extent_end - extent_start, first_extent, false); first_extent = false; } extent_start = idx.blkaddr; source_offset = chunk->sourceoffset; } extent_end = idx.blkaddr + chunkblks; idx.device_id = cpu_to_le16(chunk->device_id); idx.blkaddr = cpu_to_le32(idx.blkaddr); if (unit == EROFS_BLOCK_MAP_ENTRY_SIZE) memcpy(inode->chunkindexes + dst, &idx.blkaddr, unit); else memcpy(inode->chunkindexes + dst, &idx, sizeof(idx)); } off = roundup(off, unit); extent_end = min(extent_end, extent_start + remaining_blks); if (extent_start != EROFS_NULL_ADDR) { zeroedlen = inode->i_size & (erofs_blksiz(sbi) - 1); if (zeroedlen) zeroedlen = erofs_blksiz(sbi) - zeroedlen; tarerofs_blocklist_write(extent_start, extent_end - extent_start, source_offset, zeroedlen); } erofs_droid_blocklist_write_extent(inode, extent_start, extent_start == EROFS_NULL_ADDR ? 0 : extent_end - extent_start, first_extent, true); return erofs_dev_write(inode->sbi, inode->chunkindexes, off, inode->extent_isize); } int erofs_blob_mergechunks(struct erofs_inode *inode, unsigned int chunkbits, unsigned int new_chunkbits) { struct erofs_sb_info *sbi = inode->sbi; unsigned int dst, src, unit, count; if (new_chunkbits - sbi->blkszbits > EROFS_CHUNK_FORMAT_BLKBITS_MASK) new_chunkbits = EROFS_CHUNK_FORMAT_BLKBITS_MASK + sbi->blkszbits; if (chunkbits >= new_chunkbits) /* no need to merge */ goto out; if (inode->u.chunkformat & EROFS_CHUNK_FORMAT_INDEXES) unit = sizeof(struct erofs_inode_chunk_index); else unit = EROFS_BLOCK_MAP_ENTRY_SIZE; count = round_up(inode->i_size, 1ULL << new_chunkbits) >> new_chunkbits; for (dst = src = 0; dst < count; ++dst) { *((void **)inode->chunkindexes + dst) = *((void **)inode->chunkindexes + src); src += 1U << (new_chunkbits - chunkbits); } DBG_BUGON(count * unit >= inode->extent_isize); inode->extent_isize = count * unit; chunkbits = new_chunkbits; out: inode->u.chunkformat = (chunkbits - sbi->blkszbits) | (inode->u.chunkformat & ~EROFS_CHUNK_FORMAT_BLKBITS_MASK); return 0; } static void erofs_update_minextblks(struct erofs_sb_info *sbi, erofs_off_t start, erofs_off_t end, erofs_blk_t *minextblks) { erofs_blk_t lb; lb = lowbit((end - start) >> sbi->blkszbits); if (lb && lb < *minextblks) *minextblks = lb; } static bool erofs_blob_can_merge(struct erofs_sb_info *sbi, struct erofs_blobchunk *lastch, struct erofs_blobchunk *chunk) { if (!lastch) return true; if (lastch == &erofs_holechunk && chunk == &erofs_holechunk) return true; if (lastch->device_id == chunk->device_id && erofs_pos(sbi, lastch->blkaddr) + lastch->chunksize == erofs_pos(sbi, chunk->blkaddr)) return true; return false; } int erofs_blob_write_chunked_file(struct erofs_inode *inode, int fd, erofs_off_t startoff) { struct erofs_sb_info *sbi = inode->sbi; unsigned int chunkbits = cfg.c_chunkbits; unsigned int count, unit; struct erofs_blobchunk *chunk, *lastch; struct erofs_inode_chunk_index *idx; erofs_off_t pos, len, chunksize, interval_start; erofs_blk_t minextblks; u8 *chunkdata; int ret; #ifdef SEEK_DATA /* if the file is fully sparsed, use one big chunk instead */ if (lseek(fd, startoff, SEEK_DATA) < 0 && errno == ENXIO) { chunkbits = ilog2(inode->i_size - 1) + 1; if (chunkbits < sbi->blkszbits) chunkbits = sbi->blkszbits; } #endif if (chunkbits - sbi->blkszbits > EROFS_CHUNK_FORMAT_BLKBITS_MASK) chunkbits = EROFS_CHUNK_FORMAT_BLKBITS_MASK + sbi->blkszbits; chunksize = 1ULL << chunkbits; count = DIV_ROUND_UP(inode->i_size, chunksize); if (sbi->extra_devices) inode->u.chunkformat |= EROFS_CHUNK_FORMAT_INDEXES; if (inode->u.chunkformat & EROFS_CHUNK_FORMAT_INDEXES) unit = sizeof(struct erofs_inode_chunk_index); else unit = EROFS_BLOCK_MAP_ENTRY_SIZE; chunkdata = malloc(chunksize); if (!chunkdata) return -ENOMEM; inode->extent_isize = count * unit; inode->chunkindexes = malloc(count * max(sizeof(*idx), sizeof(void *))); if (!inode->chunkindexes) { ret = -ENOMEM; goto err; } idx = inode->chunkindexes; lastch = NULL; minextblks = BLK_ROUND_UP(sbi, inode->i_size); interval_start = 0; for (pos = 0; pos < inode->i_size; pos += len) { #ifdef SEEK_DATA off_t offset = lseek(fd, pos + startoff, SEEK_DATA); if (offset < 0) { if (errno != ENXIO) offset = pos; else offset = ((pos >> chunkbits) + 1) << chunkbits; } else { offset -= startoff; if (offset != (offset & ~(chunksize - 1))) { offset &= ~(chunksize - 1); if (lseek(fd, offset + startoff, SEEK_SET) != startoff + offset) { ret = -EIO; goto err; } } } if (offset > pos) { if (!erofs_blob_can_merge(sbi, lastch, &erofs_holechunk)) { erofs_update_minextblks(sbi, interval_start, pos, &minextblks); interval_start = pos; } do { *(void **)idx++ = &erofs_holechunk; pos += chunksize; } while (pos < offset); DBG_BUGON(pos != offset); lastch = &erofs_holechunk; len = 0; continue; } #endif len = min_t(u64, inode->i_size - pos, chunksize); ret = read(fd, chunkdata, len); if (ret < len) { ret = -EIO; goto err; } chunk = erofs_blob_getchunk(sbi, chunkdata, len); if (IS_ERR(chunk)) { ret = PTR_ERR(chunk); goto err; } if (!erofs_blob_can_merge(sbi, lastch, chunk)) { erofs_update_minextblks(sbi, interval_start, pos, &minextblks); interval_start = pos; } *(void **)idx++ = chunk; lastch = chunk; } erofs_update_minextblks(sbi, interval_start, pos, &minextblks); inode->datalayout = EROFS_INODE_CHUNK_BASED; free(chunkdata); return erofs_blob_mergechunks(inode, chunkbits, ilog2(minextblks) + sbi->blkszbits); err: free(inode->chunkindexes); inode->chunkindexes = NULL; free(chunkdata); return ret; } int erofs_write_zero_inode(struct erofs_inode *inode) { struct erofs_sb_info *sbi = inode->sbi; unsigned int chunkbits = ilog2(inode->i_size - 1) + 1; unsigned int count; erofs_off_t chunksize, len, pos; struct erofs_inode_chunk_index *idx; if (chunkbits < sbi->blkszbits) chunkbits = sbi->blkszbits; if (chunkbits - sbi->blkszbits > EROFS_CHUNK_FORMAT_BLKBITS_MASK) chunkbits = EROFS_CHUNK_FORMAT_BLKBITS_MASK + sbi->blkszbits; inode->u.chunkformat |= chunkbits - sbi->blkszbits; chunksize = 1ULL << chunkbits; count = DIV_ROUND_UP(inode->i_size, chunksize); inode->extent_isize = count * EROFS_BLOCK_MAP_ENTRY_SIZE; idx = calloc(count, max(sizeof(*idx), sizeof(void *))); if (!idx) return -ENOMEM; inode->chunkindexes = idx; for (pos = 0; pos < inode->i_size; pos += len) { struct erofs_blobchunk *chunk; len = min_t(erofs_off_t, inode->i_size - pos, chunksize); chunk = erofs_get_unhashed_chunk(0, EROFS_NULL_ADDR, -1); if (IS_ERR(chunk)) { free(inode->chunkindexes); inode->chunkindexes = NULL; return PTR_ERR(chunk); } *(void **)idx++ = chunk; } inode->datalayout = EROFS_INODE_CHUNK_BASED; return 0; } int tarerofs_write_chunkes(struct erofs_inode *inode, erofs_off_t data_offset) { struct erofs_sb_info *sbi = inode->sbi; unsigned int chunkbits = ilog2(inode->i_size - 1) + 1; unsigned int count, unit, device_id; erofs_off_t chunksize, len, pos; erofs_blk_t blkaddr; struct erofs_inode_chunk_index *idx; if (chunkbits < sbi->blkszbits) chunkbits = sbi->blkszbits; if (chunkbits - sbi->blkszbits > EROFS_CHUNK_FORMAT_BLKBITS_MASK) chunkbits = EROFS_CHUNK_FORMAT_BLKBITS_MASK + sbi->blkszbits; inode->u.chunkformat |= chunkbits - sbi->blkszbits; if (sbi->extra_devices) { device_id = 1; inode->u.chunkformat |= EROFS_CHUNK_FORMAT_INDEXES; unit = sizeof(struct erofs_inode_chunk_index); DBG_BUGON(erofs_blkoff(sbi, data_offset)); blkaddr = erofs_blknr(sbi, data_offset); } else { device_id = 0; unit = EROFS_BLOCK_MAP_ENTRY_SIZE; DBG_BUGON(erofs_blkoff(sbi, datablob_size)); blkaddr = erofs_blknr(sbi, datablob_size); datablob_size += round_up(inode->i_size, erofs_blksiz(sbi)); } chunksize = 1ULL << chunkbits; count = DIV_ROUND_UP(inode->i_size, chunksize); inode->extent_isize = count * unit; idx = calloc(count, max(sizeof(*idx), sizeof(void *))); if (!idx) return -ENOMEM; inode->chunkindexes = idx; for (pos = 0; pos < inode->i_size; pos += len) { struct erofs_blobchunk *chunk; len = min_t(erofs_off_t, inode->i_size - pos, chunksize); chunk = erofs_get_unhashed_chunk(device_id, blkaddr, data_offset); if (IS_ERR(chunk)) { free(inode->chunkindexes); inode->chunkindexes = NULL; return PTR_ERR(chunk); } *(void **)idx++ = chunk; blkaddr += erofs_blknr(sbi, len); data_offset += len; } inode->datalayout = EROFS_INODE_CHUNK_BASED; return 0; } int erofs_mkfs_dump_blobs(struct erofs_sb_info *sbi) { struct erofs_buffer_head *bh; ssize_t length, ret; u64 pos_in, pos_out; if (blobfile >= 0) { length = lseek(blobfile, 0, SEEK_CUR); if (length < 0) return -errno; if (sbi->extra_devices) sbi->devs[0].blocks = erofs_blknr(sbi, length); else datablob_size = length; } if (sbi->extra_devices) { unsigned int i, ret; erofs_blk_t nblocks; nblocks = erofs_mapbh(sbi->bmgr, NULL); pos_out = erofs_btell(bh_devt, false); i = 0; do { struct erofs_deviceslot dis = { .mapped_blkaddr = cpu_to_le32(nblocks), .blocks = cpu_to_le32(sbi->devs[i].blocks), }; memcpy(dis.tag, sbi->devs[i].tag, sizeof(dis.tag)); ret = erofs_dev_write(sbi, &dis, pos_out, sizeof(dis)); if (ret) return ret; pos_out += sizeof(dis); nblocks += sbi->devs[i].blocks; } while (++i < sbi->extra_devices); bh_devt->op = &erofs_drop_directly_bhops; erofs_bdrop(bh_devt, false); return 0; } bh = erofs_balloc(sbi->bmgr, DATA, datablob_size, 0); if (IS_ERR(bh)) return PTR_ERR(bh); erofs_mapbh(NULL, bh->block); pos_out = erofs_btell(bh, false); remapped_base = erofs_blknr(sbi, pos_out); pos_out += sbi->bdev.offset; if (blobfile >= 0) { pos_in = 0; do { length = min_t(erofs_off_t, datablob_size, SSIZE_MAX); ret = erofs_copy_file_range(blobfile, &pos_in, sbi->bdev.fd, &pos_out, length); } while (ret > 0 && (datablob_size -= ret)); if (ret >= 0) { if (datablob_size) { erofs_err("failed to append the remaining %llu-byte chunk data", datablob_size); ret = -EIO; } else { ret = 0; } } } else { ret = erofs_io_ftruncate(&sbi->bdev, pos_out + datablob_size); } bh->op = &erofs_drop_directly_bhops; erofs_bdrop(bh, false); return ret; } void erofs_blob_exit(void) { struct hashmap_iter iter; struct hashmap_entry *e; struct erofs_blobchunk *bc, *n; if (blobfile >= 0) close(blobfile); /* Disable hashmap shrink, effectively disabling rehash. * This way we can iterate over entire hashmap efficiently * and safely by using hashmap_iter_next() */ hashmap_disable_shrink(&blob_hashmap); e = hashmap_iter_first(&blob_hashmap, &iter); while (e) { bc = container_of((struct hashmap_entry *)e, struct erofs_blobchunk, ent); DBG_BUGON(hashmap_remove(&blob_hashmap, e) != e); free(bc); e = hashmap_iter_next(&iter); } DBG_BUGON(hashmap_free(&blob_hashmap)); list_for_each_entry_safe(bc, n, &unhashed_blobchunks, list) { list_del(&bc->list); free(bc); } } static int erofs_insert_zerochunk(erofs_off_t chunksize) { u8 *zeros; struct erofs_blobchunk *chunk; u8 sha256[32]; unsigned int hash; int ret = 0; zeros = calloc(1, chunksize); if (!zeros) return -ENOMEM; erofs_sha256(zeros, chunksize, sha256); free(zeros); hash = memhash(sha256, sizeof(sha256)); chunk = malloc(sizeof(struct erofs_blobchunk)); if (!chunk) return -ENOMEM; chunk->chunksize = chunksize; /* treat chunk filled with zeros as hole */ chunk->blkaddr = erofs_holechunk.blkaddr; memcpy(chunk->sha256, sha256, sizeof(sha256)); hashmap_entry_init(&chunk->ent, hash); hashmap_add(&blob_hashmap, chunk); return ret; } int erofs_blob_init(const char *blobfile_path, erofs_off_t chunksize) { if (!blobfile_path) { blobfile = erofs_tmpfile(); multidev = false; } else { blobfile = open(blobfile_path, O_WRONLY | O_BINARY); multidev = true; } if (blobfile < 0) return -errno; hashmap_init(&blob_hashmap, erofs_blob_hashmap_cmp, 0); return erofs_insert_zerochunk(chunksize); } int erofs_mkfs_init_devices(struct erofs_sb_info *sbi, unsigned int devices) { if (!devices) return 0; sbi->devs = calloc(devices, sizeof(sbi->devs[0])); if (!sbi->devs) return -ENOMEM; bh_devt = erofs_balloc(sbi->bmgr, DEVT, sizeof(struct erofs_deviceslot) * devices, 0); if (IS_ERR(bh_devt)) { free(sbi->devs); return PTR_ERR(bh_devt); } erofs_mapbh(NULL, bh_devt->block); bh_devt->op = &erofs_skip_write_bhops; sbi->devt_slotoff = erofs_btell(bh_devt, false) / EROFS_DEVT_SLOT_SIZE; sbi->extra_devices = devices; erofs_sb_set_device_table(sbi); return 0; } erofs-utils-1.8.6/lib/block_list.c000066400000000000000000000054401477440110200170440ustar00rootroot00000000000000// SPDX-License-Identifier: GPL-2.0+ OR Apache-2.0 /* * Copyright (C), 2021, Coolpad Group Limited. * Created by Yue Hu */ #include #include #include "erofs/block_list.h" #define EROFS_MODNAME "erofs block_list" #include "erofs/print.h" static FILE *block_list_fp; bool srcmap_enabled; int erofs_blocklist_open(FILE *fp, bool srcmap) { if (!fp) return -ENOENT; block_list_fp = fp; srcmap_enabled = srcmap; return 0; } FILE *erofs_blocklist_close(void) { FILE *fp = block_list_fp; block_list_fp = NULL; return fp; } /* XXX: really need to be cleaned up */ void tarerofs_blocklist_write(erofs_blk_t blkaddr, erofs_blk_t nblocks, erofs_off_t srcoff, unsigned int zeroedlen) { if (!block_list_fp || !nblocks || !srcmap_enabled) return; if (zeroedlen) fprintf(block_list_fp, "%08x %8x %08" PRIx64 " %08u\n", blkaddr, nblocks, srcoff, zeroedlen); else fprintf(block_list_fp, "%08x %8x %08" PRIx64 "\n", blkaddr, nblocks, srcoff); } #ifdef WITH_ANDROID static void blocklist_write(const char *path, erofs_blk_t blk_start, erofs_blk_t nblocks, bool first_extent, bool last_extent) { const char *fspath = erofs_fspath(path); if (first_extent) { fprintf(block_list_fp, "/%s", cfg.mount_point); if (fspath[0] != '/') fprintf(block_list_fp, "/"); fprintf(block_list_fp, "%s", fspath); } if (nblocks == 1) fprintf(block_list_fp, " %u", blk_start); else fprintf(block_list_fp, " %u-%u", blk_start, blk_start + nblocks - 1); if (last_extent) fprintf(block_list_fp, "\n"); } void erofs_droid_blocklist_write_extent(struct erofs_inode *inode, erofs_blk_t blk_start, erofs_blk_t nblocks, bool first_extent, bool last_extent) { if (!block_list_fp || !cfg.mount_point) return; if (!nblocks) { if (last_extent) fprintf(block_list_fp, "\n"); return; } blocklist_write(inode->i_srcpath, blk_start, nblocks, first_extent, last_extent); } void erofs_droid_blocklist_write(struct erofs_inode *inode, erofs_blk_t blk_start, erofs_blk_t nblocks) { if (!block_list_fp || !cfg.mount_point || !nblocks) return; blocklist_write(inode->i_srcpath, blk_start, nblocks, true, !inode->idata_size); } void erofs_droid_blocklist_write_tail_end(struct erofs_inode *inode, erofs_blk_t blkaddr) { if (!block_list_fp || !cfg.mount_point) return; /* XXX: a bit hacky.. may need a better approach */ if (S_ISDIR(inode->i_mode) || S_ISLNK(inode->i_mode)) return; /* XXX: another hack, which means it has been outputed before */ if (erofs_blknr(inode->sbi, inode->i_size)) { if (blkaddr == NULL_ADDR) fprintf(block_list_fp, "\n"); else fprintf(block_list_fp, " %u\n", blkaddr); return; } if (blkaddr != NULL_ADDR) blocklist_write(inode->i_srcpath, blkaddr, 1, true, true); } #endif erofs-utils-1.8.6/lib/cache.c000066400000000000000000000321411477440110200157600ustar00rootroot00000000000000// SPDX-License-Identifier: GPL-2.0+ OR Apache-2.0 /* * Copyright (C) 2018-2019 HUAWEI, Inc. * http://www.huawei.com/ * Created by Miao Xie * with heavy changes by Gao Xiang */ #include #include #include #include "erofs/print.h" static int erofs_bh_flush_drop_directly(struct erofs_buffer_head *bh) { return erofs_bh_flush_generic_end(bh); } const struct erofs_bhops erofs_drop_directly_bhops = { .flush = erofs_bh_flush_drop_directly, }; static int erofs_bh_flush_skip_write(struct erofs_buffer_head *bh) { return -EBUSY; } const struct erofs_bhops erofs_skip_write_bhops = { .flush = erofs_bh_flush_skip_write, }; struct erofs_bufmgr *erofs_buffer_init(struct erofs_sb_info *sbi, erofs_blk_t startblk) { unsigned int blksiz = erofs_blksiz(sbi); struct erofs_bufmgr *bmgr; int i, j, k; bmgr = malloc(sizeof(struct erofs_bufmgr)); if (!bmgr) return NULL; bmgr->sbi = sbi; for (i = 0; i < ARRAY_SIZE(bmgr->watermeter); i++) { for (j = 0; j < ARRAY_SIZE(bmgr->watermeter[0]); j++) { for (k = 0; k < blksiz; k++) init_list_head(&bmgr->watermeter[i][j][k]); memset(bmgr->bktmap[i][j], 0, (blksiz / BITS_PER_LONG) * sizeof(unsigned long)); } } init_list_head(&bmgr->blkh.list); bmgr->blkh.blkaddr = NULL_ADDR; bmgr->tail_blkaddr = startblk; bmgr->last_mapped_block = &bmgr->blkh; bmgr->dsunit = 0; return bmgr; } static void erofs_clear_bbktmap(struct erofs_bufmgr *bmgr, int type, bool mapped, int nr) { int bit = erofs_blksiz(bmgr->sbi) - (nr + 1); DBG_BUGON(bit < 0); __erofs_clear_bit(bit, bmgr->bktmap[type][mapped]); } static void erofs_set_bbktmap(struct erofs_bufmgr *bmgr, int type, bool mapped, int nr) { int bit = erofs_blksiz(bmgr->sbi) - (nr + 1); DBG_BUGON(bit < 0); __erofs_set_bit(bit, bmgr->bktmap[type][mapped]); } static void erofs_update_bwatermeter(struct erofs_buffer_block *bb, bool free) { struct erofs_bufmgr *bmgr = bb->buffers.fsprivate; struct erofs_sb_info *sbi = bmgr->sbi; unsigned int blksiz = erofs_blksiz(sbi); bool mapped = bb->blkaddr != NULL_ADDR; struct list_head *h = bmgr->watermeter[bb->type][mapped]; unsigned int nr; if (bb->sibling.next == bb->sibling.prev) { if ((uintptr_t)(bb->sibling.next - h) < blksiz) { nr = bb->sibling.next - h; erofs_clear_bbktmap(bmgr, bb->type, mapped, nr); } else if (bb->sibling.next != &bb->sibling) { nr = bb->sibling.next - bmgr->watermeter[bb->type][!mapped]; erofs_clear_bbktmap(bmgr, bb->type, !mapped, nr); } } list_del(&bb->sibling); if (free) return; nr = bb->buffers.off & (blksiz - 1); list_add_tail(&bb->sibling, h + nr); erofs_set_bbktmap(bmgr, bb->type, mapped, nr); } /* return occupied bytes in specific buffer block if succeed */ static int __erofs_battach(struct erofs_buffer_block *bb, struct erofs_buffer_head *bh, erofs_off_t incr, unsigned int alignsize, unsigned int inline_ext, bool dryrun) { struct erofs_bufmgr *bmgr = bb->buffers.fsprivate; struct erofs_sb_info *sbi = bmgr->sbi; const unsigned int blkmask = erofs_blksiz(sbi) - 1; erofs_off_t boff = bb->buffers.off; const erofs_off_t alignedoffset = round_up(boff, alignsize); bool tailupdate = false; erofs_blk_t blkaddr; int oob; DBG_BUGON(alignsize & (alignsize - 1)); /* inline data must never span block boundaries */ if (erofs_blkoff(sbi, alignedoffset + incr + blkmask) + inline_ext > blkmask) return -ENOSPC; oob = cmpsgn(alignedoffset + incr + inline_ext, bb->buffers.nblocks << sbi->blkszbits); if (oob >= 0) { /* the next buffer block should be NULL_ADDR all the time */ if (oob && list_next_entry(bb, list)->blkaddr != NULL_ADDR) return -EINVAL; blkaddr = bb->blkaddr; if (blkaddr != NULL_ADDR) { tailupdate = (bmgr->tail_blkaddr == blkaddr + bb->buffers.nblocks); if (oob && !tailupdate) return -EINVAL; } } if (!dryrun) { if (bh) { bh->off = alignedoffset; bh->block = bb; list_add_tail(&bh->list, &bb->buffers.list); } boff = alignedoffset + incr; bb->buffers.off = boff; bb->buffers.nblocks = max_t(erofs_blk_t, bb->buffers.nblocks, BLK_ROUND_UP(sbi, boff)); /* need to update the tail_blkaddr */ if (tailupdate) bmgr->tail_blkaddr = blkaddr + bb->buffers.nblocks; erofs_update_bwatermeter(bb, false); } return ((alignedoffset + incr + blkmask) & blkmask) + 1; } int erofs_bh_balloon(struct erofs_buffer_head *bh, erofs_off_t incr) { struct erofs_buffer_block *const bb = bh->block; /* should be the tail bh in the corresponding buffer block */ if (bh->list.next != &bb->buffers.list) return -EINVAL; return __erofs_battach(bb, NULL, incr, 1, 0, false); } static bool __find_next_bucket(struct erofs_bufmgr *bmgr, int type, bool mapped, unsigned int *index, unsigned int end) { const unsigned int blksiz = erofs_blksiz(bmgr->sbi); const unsigned int blkmask = blksiz - 1; unsigned int l = *index, r; if (l <= end) { DBG_BUGON(l < end); return false; } l = blkmask - (l & blkmask); r = blkmask - (end & blkmask); if (l >= r) { l = erofs_find_next_bit(bmgr->bktmap[type][mapped], blksiz, l); if (l < blksiz) { *index = round_down(*index, blksiz) + blkmask - l; return true; } l = 0; *index -= blksiz; } l = erofs_find_next_bit(bmgr->bktmap[type][mapped], r, l); if (l >= r) return false; *index = round_down(*index, blksiz) + blkmask - l; return true; } static int erofs_bfind_for_attach(struct erofs_bufmgr *bmgr, int type, erofs_off_t size, unsigned int inline_ext, unsigned int alignsize, struct erofs_buffer_block **bbp) { const unsigned int blksiz = erofs_blksiz(bmgr->sbi); const unsigned int blkmask = blksiz - 1; struct erofs_buffer_block *cur, *bb; unsigned int index, used0, end, mapped; unsigned int usedmax, used; int ret; if (alignsize == blksiz) { *bbp = NULL; return 0; } usedmax = 0; bb = NULL; mapped = ARRAY_SIZE(bmgr->watermeter); used0 = rounddown(blksiz - ((size + inline_ext) & blkmask), alignsize); if (__erofs_unlikely(bmgr->dsunit > 1)) { end = used0 + alignsize - 1; } else { end = blksiz; if (size + inline_ext >= blksiz) --mapped; } index = used0 + blksiz; while (mapped) { --mapped; for (; __find_next_bucket(bmgr, type, mapped, &index, end); --index) { struct list_head *bt; used = index & blkmask; bt = bmgr->watermeter[type][mapped] + used; DBG_BUGON(list_empty(bt)); cur = list_first_entry(bt, struct erofs_buffer_block, sibling); /* skip the last mapped block */ if (mapped && list_next_entry(cur, list)->blkaddr == NULL_ADDR) { DBG_BUGON(cur != bmgr->last_mapped_block); cur = list_next_entry(cur, sibling); if (&cur->sibling == bt) continue; } DBG_BUGON(cur->type != type); DBG_BUGON((cur->blkaddr != NULL_ADDR) ^ mapped); DBG_BUGON(used != (cur->buffers.off & blkmask)); ret = __erofs_battach(cur, NULL, size, alignsize, inline_ext, true); if (ret < 0) { DBG_BUGON(mapped && !(bmgr->dsunit > 1)); continue; } used = ret + inline_ext; /* should contain all data in the current block */ DBG_BUGON(used > blksiz); if (used > usedmax) { usedmax = used; bb = cur; } break; } end = used0 + alignsize - 1; index = used0 + blksiz; /* try the last mapped block independently */ cur = bmgr->last_mapped_block; if (mapped && cur != &bmgr->blkh && cur->type == type) { ret = __erofs_battach(cur, NULL, size, alignsize, inline_ext, true); if (ret >= 0) { used = ret + inline_ext; DBG_BUGON(used > blksiz); if (used > usedmax) { usedmax = used; bb = cur; } } } } *bbp = bb; return 0; } struct erofs_buffer_head *erofs_balloc(struct erofs_bufmgr *bmgr, int type, erofs_off_t size, unsigned int inline_ext) { struct erofs_buffer_block *bb; struct erofs_buffer_head *bh; unsigned int alignsize; int ret; ret = get_alignsize(bmgr->sbi, type, &type); if (ret < 0) return ERR_PTR(ret); DBG_BUGON(type < 0 || type > META); alignsize = ret; /* try to find if we could reuse an allocated buffer block */ ret = erofs_bfind_for_attach(bmgr, type, size, inline_ext, alignsize, &bb); if (ret) return ERR_PTR(ret); if (bb) { bh = malloc(sizeof(struct erofs_buffer_head)); if (!bh) return ERR_PTR(-ENOMEM); } else { /* get a new buffer block instead */ bb = malloc(sizeof(struct erofs_buffer_block)); if (!bb) return ERR_PTR(-ENOMEM); bb->type = type; bb->blkaddr = NULL_ADDR; bb->buffers.off = 0; bb->buffers.nblocks = 0; bb->buffers.fsprivate = bmgr; init_list_head(&bb->buffers.list); if (type == DATA) list_add(&bb->list, &bmgr->last_mapped_block->list); else list_add_tail(&bb->list, &bmgr->blkh.list); init_list_head(&bb->sibling); bh = malloc(sizeof(struct erofs_buffer_head)); if (!bh) { free(bb); return ERR_PTR(-ENOMEM); } } ret = __erofs_battach(bb, bh, size, alignsize, inline_ext, false); if (ret < 0) { free(bh); return ERR_PTR(ret); } return bh; } struct erofs_buffer_head *erofs_battach(struct erofs_buffer_head *bh, int type, unsigned int size) { struct erofs_buffer_block *const bb = bh->block; struct erofs_bufmgr *bmgr = bb->buffers.fsprivate; struct erofs_buffer_head *nbh; unsigned int alignsize; int ret = get_alignsize(bmgr->sbi, type, &type); if (ret < 0) return ERR_PTR(ret); alignsize = ret; /* should be the tail bh in the corresponding buffer block */ if (bh->list.next != &bb->buffers.list) return ERR_PTR(-EINVAL); nbh = malloc(sizeof(*nbh)); if (!nbh) return ERR_PTR(-ENOMEM); ret = __erofs_battach(bb, nbh, size, alignsize, 0, false); if (ret < 0) { free(nbh); return ERR_PTR(ret); } return nbh; } static void __erofs_mapbh(struct erofs_buffer_block *bb) { struct erofs_bufmgr *bmgr = bb->buffers.fsprivate; erofs_blk_t blkaddr = bmgr->tail_blkaddr; if (bb->blkaddr == NULL_ADDR) { bb->blkaddr = blkaddr; if (__erofs_unlikely(bmgr->dsunit > 1) && bb->type == DATA) { struct erofs_buffer_block *pb = list_prev_entry(bb, list); bb->blkaddr = roundup(blkaddr, bmgr->dsunit); if (pb != &bmgr->blkh && pb->blkaddr + pb->buffers.nblocks >= blkaddr) { DBG_BUGON(pb->blkaddr + pb->buffers.nblocks > blkaddr); pb->buffers.nblocks = bb->blkaddr - pb->blkaddr; } } bmgr->last_mapped_block = bb; erofs_update_bwatermeter(bb, false); } blkaddr = bb->blkaddr + bb->buffers.nblocks; if (blkaddr > bmgr->tail_blkaddr) bmgr->tail_blkaddr = blkaddr; } erofs_blk_t erofs_mapbh(struct erofs_bufmgr *bmgr, struct erofs_buffer_block *bb) { struct erofs_buffer_block *t; if (!bmgr) bmgr = bb->buffers.fsprivate; t = bmgr->last_mapped_block; if (bb && bb->blkaddr != NULL_ADDR) return bb->blkaddr; do { t = list_next_entry(t, list); if (t == &bmgr->blkh) break; DBG_BUGON(t->blkaddr != NULL_ADDR); __erofs_mapbh(t); } while (t != bb); return bmgr->tail_blkaddr; } static void erofs_bfree(struct erofs_buffer_block *bb) { struct erofs_bufmgr *bmgr = bb->buffers.fsprivate; DBG_BUGON(!list_empty(&bb->buffers.list)); if (bb == bmgr->last_mapped_block) bmgr->last_mapped_block = list_prev_entry(bb, list); erofs_update_bwatermeter(bb, true); list_del(&bb->list); free(bb); } int erofs_bflush(struct erofs_bufmgr *bmgr, struct erofs_buffer_block *bb) { struct erofs_sb_info *sbi = bmgr->sbi; const unsigned int blksiz = erofs_blksiz(sbi); struct erofs_buffer_block *p, *n; erofs_blk_t blkaddr; list_for_each_entry_safe(p, n, &bmgr->blkh.list, list) { struct erofs_buffer_head *bh, *nbh; unsigned int padding; bool skip = false; int ret; if (p == bb) break; __erofs_mapbh(p); blkaddr = p->blkaddr + BLK_ROUND_UP(sbi, p->buffers.off); list_for_each_entry_safe(bh, nbh, &p->buffers.list, list) { if (bh->op == &erofs_skip_write_bhops) { skip = true; continue; } /* flush and remove bh */ ret = bh->op->flush(bh); if (ret < 0) return ret; } if (skip) continue; padding = blksiz - (p->buffers.off & (blksiz - 1)); if (padding != blksiz) erofs_dev_fillzero(sbi, erofs_pos(sbi, blkaddr) - padding, padding, true); if (p->type != DATA) bmgr->metablkcnt += p->buffers.nblocks; erofs_dbg("block %u to %u flushed", p->blkaddr, blkaddr - 1); erofs_bfree(p); } return 0; } void erofs_bdrop(struct erofs_buffer_head *bh, bool tryrevoke) { struct erofs_buffer_block *const bb = bh->block; struct erofs_bufmgr *bmgr = bb->buffers.fsprivate; const erofs_blk_t blkaddr = bh->block->blkaddr; bool rollback = false; /* tail_blkaddr could be rolled back after revoking all bhs */ if (tryrevoke && blkaddr != NULL_ADDR && bmgr->tail_blkaddr == blkaddr + bb->buffers.nblocks) rollback = true; bh->op = &erofs_drop_directly_bhops; erofs_bh_flush_generic_end(bh); if (!list_empty(&bb->buffers.list)) return; if (!rollback && bb->type != DATA) bmgr->metablkcnt += bb->buffers.nblocks; erofs_bfree(bb); if (rollback) bmgr->tail_blkaddr = blkaddr; } erofs_blk_t erofs_total_metablocks(struct erofs_bufmgr *bmgr) { return bmgr->metablkcnt; } void erofs_buffer_exit(struct erofs_bufmgr *bmgr) { free(bmgr); } erofs-utils-1.8.6/lib/compress.c000066400000000000000000001447571477440110200165710ustar00rootroot00000000000000// SPDX-License-Identifier: GPL-2.0+ OR Apache-2.0 /* * Copyright (C) 2018-2019 HUAWEI, Inc. * http://www.huawei.com/ * Created by Miao Xie * with heavy changes by Gao Xiang */ #ifndef _LARGEFILE64_SOURCE #define _LARGEFILE64_SOURCE #endif #ifdef EROFS_MT_ENABLED #include #endif #include #include #include #include "erofs/print.h" #include "erofs/cache.h" #include "erofs/compress.h" #include "erofs/dedupe.h" #include "compressor.h" #include "erofs/block_list.h" #include "erofs/compress_hints.h" #include "erofs/fragments.h" #ifdef EROFS_MT_ENABLED #include "erofs/workqueue.h" #endif #define Z_EROFS_DESTBUF_SZ (Z_EROFS_PCLUSTER_MAX_SIZE + EROFS_MAX_BLOCK_SIZE * 2) /* compressing configuration specified by users */ struct erofs_compress_cfg { struct erofs_compress handle; unsigned int algorithmtype; bool enable; } erofs_ccfg[EROFS_MAX_COMPR_CFGS]; struct z_erofs_extent_item { struct list_head list; struct z_erofs_inmem_extent e; }; struct z_erofs_compress_ictx { /* inode context */ struct erofs_inode *inode; struct erofs_compress_cfg *ccfg; int fd; u64 fpos; u32 tof_chksum; bool fix_dedupedfrag; bool fragemitted; bool dedupe; /* fields for write indexes */ u8 *metacur; struct list_head extents; u16 clusterofs; int seg_num; #if EROFS_MT_ENABLED pthread_mutex_t mutex; pthread_cond_t cond; struct erofs_compress_work *mtworks; #endif }; struct z_erofs_compress_sctx { /* segment context */ struct list_head extents; struct z_erofs_compress_ictx *ictx; u8 *queue; struct z_erofs_extent_item *pivot; struct erofs_compress *chandle; char *destbuf; erofs_off_t remaining; unsigned int head, tail; unsigned int pclustersize; erofs_off_t pstart; u16 clusterofs; int seg_idx; void *membuf; erofs_off_t memoff; }; #ifdef EROFS_MT_ENABLED struct erofs_compress_wq_tls { u8 *queue; char *destbuf; struct erofs_compress_cfg *ccfg; }; struct erofs_compress_work { /* Note: struct erofs_work must be the first member */ struct erofs_work work; struct z_erofs_compress_sctx ctx; pthread_cond_t cond; struct erofs_compress_work *next; unsigned int alg_id; char *alg_name; unsigned int comp_level; unsigned int dict_size; int errcode; }; static struct { struct erofs_workqueue wq, fwq; struct erofs_compress_work *idle; pthread_mutex_t mutex; bool hasfwq; } z_erofs_mt_ctrl; #endif static bool z_erofs_mt_enabled; #define Z_EROFS_LEGACY_MAP_HEADER_SIZE Z_EROFS_FULL_INDEX_ALIGN(0) static void z_erofs_fini_full_indexes(struct z_erofs_compress_ictx *ctx) { const unsigned int type = Z_EROFS_LCLUSTER_TYPE_PLAIN; struct z_erofs_lcluster_index di; if (!ctx->clusterofs) return; di.di_clusterofs = cpu_to_le16(ctx->clusterofs); di.di_u.blkaddr = 0; di.di_advise = cpu_to_le16(type); memcpy(ctx->metacur, &di, sizeof(di)); ctx->metacur += sizeof(di); } static void z_erofs_write_full_indexes(struct z_erofs_compress_ictx *ctx, struct z_erofs_inmem_extent *e) { struct erofs_inode *inode = ctx->inode; struct erofs_sb_info *sbi = inode->sbi; unsigned int clusterofs = ctx->clusterofs; unsigned int count = e->length; unsigned int bbits = sbi->blkszbits; unsigned int d0 = 0, d1 = (clusterofs + count) >> bbits; struct z_erofs_lcluster_index di; unsigned int type, advise; DBG_BUGON(!count); DBG_BUGON(e->pstart & (BIT(bbits) - 1)); di.di_clusterofs = cpu_to_le16(ctx->clusterofs); /* whether the tail-end (un)compressed block or not */ if (!d1) { /* * A lcluster cannot have three parts with the middle one which * is well-compressed for !ztailpacking cases. */ DBG_BUGON(!e->raw && !cfg.c_ztailpacking && !cfg.c_fragments); DBG_BUGON(e->partial); type = e->raw ? Z_EROFS_LCLUSTER_TYPE_PLAIN : Z_EROFS_LCLUSTER_TYPE_HEAD1; di.di_advise = cpu_to_le16(type); if (inode->datalayout == EROFS_INODE_COMPRESSED_FULL && !e->plen) di.di_u.blkaddr = cpu_to_le32(inode->fragmentoff >> 32); else di.di_u.blkaddr = cpu_to_le32(e->pstart >> bbits); memcpy(ctx->metacur, &di, sizeof(di)); ctx->metacur += sizeof(di); /* don't add the final index if the tail-end block exists */ ctx->clusterofs = 0; return; } do { advise = 0; /* XXX: big pcluster feature should be per-inode */ if (d0 == 1 && erofs_sb_has_big_pcluster(sbi)) { type = Z_EROFS_LCLUSTER_TYPE_NONHEAD; di.di_u.delta[0] = cpu_to_le16((e->plen >> bbits) | Z_EROFS_LI_D0_CBLKCNT); di.di_u.delta[1] = cpu_to_le16(d1); } else if (d0) { type = Z_EROFS_LCLUSTER_TYPE_NONHEAD; /* * If the |Z_EROFS_VLE_DI_D0_CBLKCNT| bit is set, parser * will interpret |delta[0]| as size of pcluster, rather * than distance to last head cluster. Normally this * isn't a problem, because uncompressed extent size are * below Z_EROFS_VLE_DI_D0_CBLKCNT * BLOCK_SIZE = 8MB. * But with large pcluster it's possible to go over this * number, resulting in corrupted compressed indices. * To solve this, we replace d0 with * Z_EROFS_VLE_DI_D0_CBLKCNT-1. */ if (d0 >= Z_EROFS_LI_D0_CBLKCNT) di.di_u.delta[0] = cpu_to_le16( Z_EROFS_LI_D0_CBLKCNT - 1); else di.di_u.delta[0] = cpu_to_le16(d0); di.di_u.delta[1] = cpu_to_le16(d1); } else { type = e->raw ? Z_EROFS_LCLUSTER_TYPE_PLAIN : Z_EROFS_LCLUSTER_TYPE_HEAD1; if (inode->datalayout == EROFS_INODE_COMPRESSED_FULL && !e->plen) di.di_u.blkaddr = cpu_to_le32(inode->fragmentoff >> 32); else di.di_u.blkaddr = cpu_to_le32(e->pstart >> bbits); if (e->partial) { DBG_BUGON(e->raw); advise |= Z_EROFS_LI_PARTIAL_REF; } } di.di_advise = cpu_to_le16(advise | type); memcpy(ctx->metacur, &di, sizeof(di)); ctx->metacur += sizeof(di); count -= (1 << bbits) - clusterofs; clusterofs = 0; ++d0; --d1; } while (clusterofs + count >= 1 << bbits); ctx->clusterofs = clusterofs + count; } static bool z_erofs_need_refill(struct z_erofs_compress_sctx *ctx) { const bool final = !ctx->remaining; unsigned int qh_aligned, qh_after; struct erofs_inode *inode = ctx->ictx->inode; if (final || ctx->head < EROFS_CONFIG_COMPR_MAX_SZ) return false; qh_aligned = round_down(ctx->head, erofs_blksiz(inode->sbi)); qh_after = ctx->head - qh_aligned; memmove(ctx->queue, ctx->queue + qh_aligned, ctx->tail - qh_aligned); ctx->tail -= qh_aligned; ctx->head = qh_after; return true; } static struct z_erofs_extent_item dummy_pivot = { .e.length = 0 }; static void z_erofs_commit_extent(struct z_erofs_compress_sctx *ctx, struct z_erofs_extent_item *ei) { if (ei == &dummy_pivot) return; list_add_tail(&ei->list, &ctx->extents); ctx->clusterofs = (ctx->clusterofs + ei->e.length) & (erofs_blksiz(ctx->ictx->inode->sbi) - 1); } static int z_erofs_compress_dedupe(struct z_erofs_compress_sctx *ctx) { struct erofs_inode *inode = ctx->ictx->inode; const unsigned int lclustermask = (1 << inode->z_logical_clusterbits) - 1; struct erofs_sb_info *sbi = inode->sbi; struct z_erofs_extent_item *ei = ctx->pivot; if (!ei) return 0; /* * No need dedupe for packed inode since it is composed of * fragments which have already been deduplicated. */ if (erofs_is_packed_inode(inode)) goto out; do { struct z_erofs_dedupe_ctx dctx = { .start = ctx->queue + ctx->head - ({ int rc; if (ei->e.length <= erofs_blksiz(sbi)) rc = 0; else if (ei->e.length - erofs_blksiz(sbi) >= ctx->head) rc = ctx->head; else rc = ei->e.length - erofs_blksiz(sbi); rc; }), .end = ctx->queue + ctx->tail, .cur = ctx->queue + ctx->head, }; int delta; if (z_erofs_dedupe_match(&dctx)) break; DBG_BUGON(dctx.e.inlined); delta = ctx->queue + ctx->head - dctx.cur; /* * For big pcluster dedupe, leave two indices at least to store * CBLKCNT as the first step. Even laterly, an one-block * decompresssion could be done as another try in practice. */ if (dctx.e.plen > erofs_blksiz(sbi) && ((ctx->clusterofs + ei->e.length - delta) & lclustermask) + dctx.e.length < 2 * (lclustermask + 1)) break; ctx->pivot = malloc(sizeof(struct z_erofs_extent_item)); if (!ctx->pivot) { z_erofs_commit_extent(ctx, ei); return -ENOMEM; } if (delta) { DBG_BUGON(delta < 0); DBG_BUGON(!ei->e.length); /* * For big pcluster dedupe, if we decide to shorten the * previous big pcluster, make sure that the previous * CBLKCNT is still kept. */ if (ei->e.plen > erofs_blksiz(sbi) && (ctx->clusterofs & lclustermask) + ei->e.length - delta < 2 * (lclustermask + 1)) break; ei->e.partial = true; ei->e.length -= delta; } ctx->ictx->dedupe = true; erofs_sb_set_dedupe(sbi); sbi->saved_by_deduplication += dctx.e.plen; erofs_dbg("Dedupe %u %scompressed data (delta %d) to %llu of %u bytes", dctx.e.length, dctx.e.raw ? "un" : "", delta, dctx.e.pstart | 0ULL, dctx.e.plen); z_erofs_commit_extent(ctx, ei); ei = ctx->pivot; init_list_head(&ei->list); ei->e = dctx.e; ctx->head += dctx.e.length - delta; DBG_BUGON(ctx->head > ctx->tail); if (z_erofs_need_refill(ctx)) return 1; } while (ctx->tail > ctx->head); out: z_erofs_commit_extent(ctx, ei); ctx->pivot = NULL; return 0; } /* TODO: reset clusterofs to 0 if permitted */ static int write_uncompressed_block(struct z_erofs_compress_sctx *ctx, unsigned int len, char *dst) { struct erofs_inode *inode = ctx->ictx->inode; struct erofs_sb_info *sbi = inode->sbi; unsigned int count = min(erofs_blksiz(sbi), len); unsigned int interlaced_offset, rightpart; int ret; /* write interlaced uncompressed data if needed */ if (inode->z_advise & Z_EROFS_ADVISE_INTERLACED_PCLUSTER) interlaced_offset = ctx->clusterofs; else interlaced_offset = 0; rightpart = min(erofs_blksiz(sbi) - interlaced_offset, count); memset(dst, 0, erofs_blksiz(sbi)); memcpy(dst + interlaced_offset, ctx->queue + ctx->head, rightpart); memcpy(dst, ctx->queue + ctx->head + rightpart, count - rightpart); if (ctx->membuf) { erofs_dbg("Writing %u uncompressed data of %s", count, inode->i_srcpath); memcpy(ctx->membuf + ctx->memoff, dst, erofs_blksiz(sbi)); ctx->memoff += erofs_blksiz(sbi); } else { erofs_dbg("Writing %u uncompressed data to %llu", count, ctx->pstart | 0ULL); ret = erofs_dev_write(sbi, dst, ctx->pstart, erofs_blksiz(sbi)); if (ret) return ret; } return count; } static int write_uncompressed_extents(struct z_erofs_compress_sctx *ctx, unsigned int size, unsigned int processed, char *dst) { struct erofs_inode *inode = ctx->ictx->inode; unsigned int lclustersize = 1 << inode->z_logical_clusterbits; struct z_erofs_extent_item *ei; int count; while (1) { count = write_uncompressed_block(ctx, size, dst); if (count < 0) return count; size -= count; if (processed < lclustersize + count) break; processed -= count; ei = malloc(sizeof(*ei)); if (!ei) return -ENOMEM; init_list_head(&ei->list); ei->e = (struct z_erofs_inmem_extent) { .length = count, .plen = round_up(count, erofs_blksiz(inode->sbi)), .raw = true, .pstart = ctx->pstart, }; if (ctx->pstart != EROFS_NULL_ADDR) ctx->pstart += ei->e.plen; z_erofs_commit_extent(ctx, ei); ctx->head += count; } return count; } static unsigned int z_erofs_get_max_pclustersize(struct erofs_inode *inode) { if (erofs_is_packed_inode(inode)) { return cfg.c_mkfs_pclustersize_packed; #ifndef NDEBUG } else if (cfg.c_random_pclusterblks) { unsigned int pclusterblks = cfg.c_mkfs_pclustersize_max >> inode->sbi->blkszbits; return (1 + rand() % pclusterblks) << inode->sbi->blkszbits; #endif } else if (cfg.c_compress_hints_file) { z_erofs_apply_compress_hints(inode); DBG_BUGON(!inode->z_physical_clusterblks); return inode->z_physical_clusterblks << inode->sbi->blkszbits; } return cfg.c_mkfs_pclustersize_def; } static int z_erofs_fill_inline_data(struct erofs_inode *inode, void *data, unsigned int len, bool raw) { inode->z_advise |= Z_EROFS_ADVISE_INLINE_PCLUSTER; inode->idata_size = len; inode->compressed_idata = !raw; inode->idata = malloc(inode->idata_size); if (!inode->idata) return -ENOMEM; erofs_dbg("Recording %u %scompressed inline data", inode->idata_size, raw ? "un" : ""); memcpy(inode->idata, data, inode->idata_size); return len; } static int tryrecompress_trailing(struct z_erofs_compress_sctx *ctx, struct erofs_compress *ec, void *in, unsigned int *insize, void *out, unsigned int *compressedsize) { struct erofs_sb_info *sbi = ctx->ictx->inode->sbi; char *tmp; unsigned int count; int ret = *compressedsize; /* no need to recompress */ if (!(ret & (erofs_blksiz(sbi) - 1))) return 0; tmp = malloc(Z_EROFS_PCLUSTER_MAX_SIZE); if (!tmp) return -ENOMEM; count = *insize; ret = erofs_compress_destsize(ec, in, &count, (void *)tmp, rounddown(ret, erofs_blksiz(sbi))); if (ret <= 0 || ret + (*insize - count) >= roundup(*compressedsize, erofs_blksiz(sbi))) goto out; /* replace the original compressed data if any gain */ memcpy(out, tmp, ret); *insize = count; *compressedsize = ret; out: free(tmp); return 0; } static bool z_erofs_fixup_deduped_fragment(struct z_erofs_compress_sctx *ctx) { struct z_erofs_compress_ictx *ictx = ctx->ictx; struct erofs_inode *inode = ictx->inode; struct erofs_sb_info *sbi = inode->sbi; const unsigned int newsize = ctx->remaining + ctx->tail - ctx->head; DBG_BUGON(!inode->fragment_size); /* try to fix again if it gets larger (should be rare) */ if (inode->fragment_size < newsize) { ctx->pclustersize = min_t(erofs_off_t, z_erofs_get_max_pclustersize(inode), roundup(newsize - inode->fragment_size, erofs_blksiz(sbi))); return false; } inode->fragmentoff += inode->fragment_size - newsize; inode->fragment_size = newsize; erofs_dbg("Reducing fragment size to %llu at %llu", inode->fragment_size | 0ULL, inode->fragmentoff | 0ULL); /* it's the end */ DBG_BUGON(ctx->tail - ctx->head + ctx->remaining != newsize); ctx->head = ctx->tail; ctx->remaining = 0; return true; } static int __z_erofs_compress_one(struct z_erofs_compress_sctx *ctx, struct z_erofs_inmem_extent *e) { static char g_dstbuf[Z_EROFS_DESTBUF_SZ]; char *dstbuf = ctx->destbuf ?: g_dstbuf; struct z_erofs_compress_ictx *ictx = ctx->ictx; struct erofs_inode *inode = ictx->inode; struct erofs_sb_info *sbi = inode->sbi; unsigned int blksz = erofs_blksiz(sbi); char *const dst = dstbuf + blksz; struct erofs_compress *const h = ctx->chandle; unsigned int len = ctx->tail - ctx->head; bool is_packed_inode = erofs_is_packed_inode(inode); bool tsg = (ctx->seg_idx + 1 >= ictx->seg_num), final = !ctx->remaining; bool may_packing = (cfg.c_fragments && tsg && final && !is_packed_inode); bool may_inline = (cfg.c_ztailpacking && tsg && final && !may_packing); unsigned int compressedsize; int ret; DBG_BUGON(ctx->pivot); *e = (struct z_erofs_inmem_extent){}; if (len <= ctx->pclustersize) { if (!final || !len) return 1; if (may_packing) { if (inode->fragment_size && !ictx->fix_dedupedfrag) { ctx->pclustersize = roundup(len, blksz); goto fix_dedupedfrag; } e->length = len; goto frag_packing; } if (!may_inline && len <= blksz) { e->length = len; goto nocompression; } } e->length = min(len, cfg.c_max_decompressed_extent_bytes); ret = erofs_compress_destsize(h, ctx->queue + ctx->head, &e->length, dst, ctx->pclustersize); if (ret <= 0) { erofs_err("failed to compress %s: %s", inode->i_srcpath, erofs_strerror(ret)); return ret; } compressedsize = ret; /* even compressed size is smaller, there is no real gain */ if (!(may_inline && e->length == len && ret < blksz)) ret = roundup(ret, blksz); /* check if there is enough gain to keep the compressed data */ if (ret * h->compress_threshold / 100 >= e->length) { if (may_inline && len < blksz) { ret = z_erofs_fill_inline_data(inode, ctx->queue + ctx->head, len, true); if (ret < 0) return ret; e->inlined = true; } else { may_inline = false; may_packing = false; e->length = min_t(u32, e->length, ret); nocompression: if (cfg.c_dedupe) ret = write_uncompressed_block(ctx, len, dst); else ret = write_uncompressed_extents(ctx, len, e->length, dst); if (ret < 0) return ret; } e->length = ret; /* * XXX: For now, we have to leave `ctx->compressedblk = 1' * since there is no way to generate compressed indexes after * the time that ztailpacking is decided. */ e->plen = blksz; e->raw = true; } else if (may_packing && len == e->length && compressedsize < ctx->pclustersize && (!inode->fragment_size || ictx->fix_dedupedfrag)) { frag_packing: ret = z_erofs_pack_fragments(inode, ctx->queue + ctx->head, len, ictx->tof_chksum); if (ret < 0) return ret; e->plen = 0; /* indicate a fragment */ e->raw = false; ictx->fragemitted = true; /* tailpcluster should be less than 1 block */ } else if (may_inline && len == e->length && compressedsize < blksz) { if (ctx->clusterofs + len <= blksz) { inode->eof_tailraw = malloc(len); if (!inode->eof_tailraw) return -ENOMEM; memcpy(inode->eof_tailraw, ctx->queue + ctx->head, len); inode->eof_tailrawsize = len; } ret = z_erofs_fill_inline_data(inode, dst, compressedsize, false); if (ret < 0) return ret; e->inlined = true; e->plen = blksz; e->raw = false; } else { unsigned int tailused, padding; /* * If there's space left for the last round when deduping * fragments, try to read the fragment and recompress a little * more to check whether it can be filled up. Fix the fragment * if succeeds. Otherwise, just drop it and go on packing. */ if (may_packing && len == e->length && (compressedsize & (blksz - 1)) && ctx->tail < Z_EROFS_COMPR_QUEUE_SZ) { ctx->pclustersize = roundup(compressedsize, blksz); goto fix_dedupedfrag; } if (may_inline && len == e->length) { ret = tryrecompress_trailing(ctx, h, ctx->queue + ctx->head, &e->length, dst, &compressedsize); if (ret) return ret; } e->plen = round_up(compressedsize, blksz); DBG_BUGON(e->plen >= e->length); padding = 0; tailused = compressedsize & (blksz - 1); if (tailused) padding = blksz - tailused; /* zero out garbage trailing data for non-0padding */ if (!erofs_sb_has_lz4_0padding(sbi)) { memset(dst + compressedsize, 0, padding); padding = 0; } /* write compressed data */ if (ctx->membuf) { erofs_dbg("Writing %u compressed data of %u bytes of %s", e->length, e->plen, inode->i_srcpath); memcpy(ctx->membuf + ctx->memoff, dst - padding, e->plen); ctx->memoff += e->plen; } else { erofs_dbg("Writing %u compressed data to %llu of %u bytes", e->length, ctx->pstart, e->plen); ret = erofs_dev_write(sbi, dst - padding, ctx->pstart, e->plen); if (ret) return ret; } e->raw = false; may_inline = false; may_packing = false; } e->partial = false; e->pstart = ctx->pstart; if (ctx->pstart != EROFS_NULL_ADDR) ctx->pstart += e->plen; if (!may_inline && !may_packing && !is_packed_inode) (void)z_erofs_dedupe_insert(e, ctx->queue + ctx->head); ctx->head += e->length; return 0; fix_dedupedfrag: DBG_BUGON(!inode->fragment_size); ctx->remaining += inode->fragment_size; ictx->fix_dedupedfrag = true; return 1; } static int z_erofs_compress_one(struct z_erofs_compress_sctx *ctx) { struct z_erofs_compress_ictx *ictx = ctx->ictx; bool tsg = ctx->seg_idx + 1 >= ictx->seg_num; struct z_erofs_extent_item *ei; while (ctx->tail > ctx->head) { int ret = z_erofs_compress_dedupe(ctx); if (ret < 0) return ret; if (ret > 0) break; ei = malloc(sizeof(*ei)); if (!ei) return -ENOMEM; init_list_head(&ei->list); ret = __z_erofs_compress_one(ctx, &ei->e); if (ret) { free(ei); if (ret > 0) break; /* need more data */ return ret; } ctx->pivot = ei; if (tsg && ictx->fix_dedupedfrag && !ictx->fragemitted && z_erofs_fixup_deduped_fragment(ctx)) break; if (z_erofs_need_refill(ctx)) break; } return 0; } struct z_erofs_compressindex_vec { union { erofs_blk_t blkaddr; u16 delta[2]; } u; u16 clusterofs; u8 clustertype; }; static void *parse_legacy_indexes(struct z_erofs_compressindex_vec *cv, unsigned int nr, void *metacur) { struct z_erofs_lcluster_index *const db = metacur; unsigned int i; for (i = 0; i < nr; ++i, ++cv) { struct z_erofs_lcluster_index *const di = db + i; const unsigned int advise = le16_to_cpu(di->di_advise); cv->clustertype = advise & Z_EROFS_LI_LCLUSTER_TYPE_MASK; cv->clusterofs = le16_to_cpu(di->di_clusterofs); if (cv->clustertype == Z_EROFS_LCLUSTER_TYPE_NONHEAD) { cv->u.delta[0] = le16_to_cpu(di->di_u.delta[0]); cv->u.delta[1] = le16_to_cpu(di->di_u.delta[1]); } else { cv->u.blkaddr = le32_to_cpu(di->di_u.blkaddr); } } return db + nr; } static void *write_compacted_indexes(u8 *out, struct z_erofs_compressindex_vec *cv, erofs_blk_t *blkaddr_ret, unsigned int destsize, unsigned int lclusterbits, bool final, bool *dummy_head, bool update_blkaddr) { unsigned int vcnt, lobits, encodebits, pos, i, cblks; erofs_blk_t blkaddr; if (destsize == 4) vcnt = 2; else if (destsize == 2 && lclusterbits <= 12) vcnt = 16; else return ERR_PTR(-EINVAL); lobits = max(lclusterbits, ilog2(Z_EROFS_LI_D0_CBLKCNT) + 1U); encodebits = (vcnt * destsize * 8 - 32) / vcnt; blkaddr = *blkaddr_ret; pos = 0; for (i = 0; i < vcnt; ++i) { unsigned int offset, v; u8 ch, rem; if (cv[i].clustertype == Z_EROFS_LCLUSTER_TYPE_NONHEAD) { if (cv[i].u.delta[0] & Z_EROFS_LI_D0_CBLKCNT) { cblks = cv[i].u.delta[0] & ~Z_EROFS_LI_D0_CBLKCNT; offset = cv[i].u.delta[0]; blkaddr += cblks; *dummy_head = false; } else if (i + 1 == vcnt) { offset = min_t(u16, cv[i].u.delta[1], Z_EROFS_LI_D0_CBLKCNT - 1); } else { offset = cv[i].u.delta[0]; } } else { offset = cv[i].clusterofs; if (*dummy_head) { ++blkaddr; if (update_blkaddr) *blkaddr_ret = blkaddr; } *dummy_head = true; update_blkaddr = false; if (cv[i].u.blkaddr != blkaddr) { if (i + 1 != vcnt) DBG_BUGON(!final); DBG_BUGON(cv[i].u.blkaddr); } } v = (cv[i].clustertype << lobits) | offset; rem = pos & 7; ch = out[pos / 8] & ((1 << rem) - 1); out[pos / 8] = (v << rem) | ch; out[pos / 8 + 1] = v >> (8 - rem); out[pos / 8 + 2] = v >> (16 - rem); pos += encodebits; } DBG_BUGON(destsize * vcnt * 8 != pos + 32); *(__le32 *)(out + destsize * vcnt - 4) = cpu_to_le32(*blkaddr_ret); *blkaddr_ret = blkaddr; return out + destsize * vcnt; } int z_erofs_convert_to_compacted_format(struct erofs_inode *inode, erofs_off_t pstart, unsigned int legacymetasize, void *compressmeta) { const unsigned int mpos = roundup(inode->inode_isize + inode->xattr_isize, 8) + sizeof(struct z_erofs_map_header); const unsigned int totalidx = (legacymetasize - Z_EROFS_LEGACY_MAP_HEADER_SIZE) / sizeof(struct z_erofs_lcluster_index); const unsigned int logical_clusterbits = inode->z_logical_clusterbits; u8 *out, *in; struct z_erofs_compressindex_vec cv[16]; struct erofs_sb_info *sbi = inode->sbi; /* # of 8-byte units so that it can be aligned with 32 bytes */ unsigned int compacted_4b_initial, compacted_4b_end; unsigned int compacted_2b; bool dummy_head; bool big_pcluster = erofs_sb_has_big_pcluster(sbi); erofs_blk_t blkaddr; if (logical_clusterbits < sbi->blkszbits) return -EINVAL; if (pstart & (erofs_blksiz(sbi) - 1)) return -EINVAL; if (logical_clusterbits > 14) { erofs_err("compact format is unsupported for lcluster size %u", 1 << logical_clusterbits); return -EOPNOTSUPP; } blkaddr = pstart >> sbi->blkszbits; if (inode->z_advise & Z_EROFS_ADVISE_COMPACTED_2B) { if (logical_clusterbits > 12) { erofs_err("compact 2B is unsupported for lcluster size %u", 1 << logical_clusterbits); return -EINVAL; } compacted_4b_initial = (32 - mpos % 32) / 4; if (compacted_4b_initial == 32 / 4) compacted_4b_initial = 0; if (compacted_4b_initial > totalidx) { compacted_4b_initial = compacted_2b = 0; compacted_4b_end = totalidx; } else { compacted_2b = rounddown(totalidx - compacted_4b_initial, 16); compacted_4b_end = totalidx - compacted_4b_initial - compacted_2b; } } else { compacted_2b = compacted_4b_initial = 0; compacted_4b_end = totalidx; } out = in = compressmeta; out += sizeof(struct z_erofs_map_header); in += Z_EROFS_LEGACY_MAP_HEADER_SIZE; dummy_head = false; /* prior to bigpcluster, blkaddr was bumped up once coming into HEAD */ if (!big_pcluster) { --blkaddr; dummy_head = true; } /* generate compacted_4b_initial */ while (compacted_4b_initial) { in = parse_legacy_indexes(cv, 2, in); out = write_compacted_indexes(out, cv, &blkaddr, 4, logical_clusterbits, false, &dummy_head, big_pcluster); compacted_4b_initial -= 2; } DBG_BUGON(compacted_4b_initial); /* generate compacted_2b */ while (compacted_2b) { in = parse_legacy_indexes(cv, 16, in); out = write_compacted_indexes(out, cv, &blkaddr, 2, logical_clusterbits, false, &dummy_head, big_pcluster); compacted_2b -= 16; } DBG_BUGON(compacted_2b); /* generate compacted_4b_end */ while (compacted_4b_end > 1) { in = parse_legacy_indexes(cv, 2, in); out = write_compacted_indexes(out, cv, &blkaddr, 4, logical_clusterbits, false, &dummy_head, big_pcluster); compacted_4b_end -= 2; } /* generate final compacted_4b_end if needed */ if (compacted_4b_end) { memset(cv, 0, sizeof(cv)); in = parse_legacy_indexes(cv, 1, in); out = write_compacted_indexes(out, cv, &blkaddr, 4, logical_clusterbits, true, &dummy_head, big_pcluster); } inode->extent_isize = out - (u8 *)compressmeta; return 0; } static void z_erofs_write_mapheader(struct erofs_inode *inode, void *compressmeta) { struct erofs_sb_info *sbi = inode->sbi; struct z_erofs_map_header h = { .h_advise = cpu_to_le16(inode->z_advise), .h_algorithmtype = inode->z_algorithmtype[1] << 4 | inode->z_algorithmtype[0], /* lclustersize */ .h_clusterbits = inode->z_logical_clusterbits - sbi->blkszbits, }; if (inode->z_advise & Z_EROFS_ADVISE_FRAGMENT_PCLUSTER) h.h_fragmentoff = cpu_to_le32(inode->fragmentoff); else h.h_idata_size = cpu_to_le16(inode->idata_size); memset(compressmeta, 0, Z_EROFS_LEGACY_MAP_HEADER_SIZE); /* write out map header */ memcpy(compressmeta, &h, sizeof(struct z_erofs_map_header)); } static void *z_erofs_write_indexes(struct z_erofs_compress_ictx *ctx) { struct erofs_inode *inode = ctx->inode; struct erofs_sb_info *sbi = inode->sbi; struct z_erofs_extent_item *ei, *n; void *metabuf; /* * If the packed inode is larger than 4GiB, the full fragmentoff * will be recorded by switching to the noncompact layout anyway. */ if (inode->fragment_size && inode->fragmentoff >> 32) { inode->datalayout = EROFS_INODE_COMPRESSED_FULL; } else if (!cfg.c_legacy_compress && !ctx->dedupe && inode->z_logical_clusterbits <= 14) { if (inode->z_logical_clusterbits <= 12) inode->z_advise |= Z_EROFS_ADVISE_COMPACTED_2B; inode->datalayout = EROFS_INODE_COMPRESSED_COMPACT; } else { inode->datalayout = EROFS_INODE_COMPRESSED_FULL; } if (erofs_sb_has_big_pcluster(sbi)) { inode->z_advise |= Z_EROFS_ADVISE_BIG_PCLUSTER_1; if (inode->datalayout == EROFS_INODE_COMPRESSED_COMPACT) inode->z_advise |= Z_EROFS_ADVISE_BIG_PCLUSTER_2; } metabuf = malloc(BLK_ROUND_UP(inode->sbi, inode->i_size) * sizeof(struct z_erofs_lcluster_index) + Z_EROFS_LEGACY_MAP_HEADER_SIZE); if (!metabuf) return ERR_PTR(-ENOMEM); ctx->metacur = metabuf + Z_EROFS_LEGACY_MAP_HEADER_SIZE; ctx->clusterofs = 0; list_for_each_entry_safe(ei, n, &ctx->extents, list) { z_erofs_write_full_indexes(ctx, &ei->e); list_del(&ei->list); free(ei); } z_erofs_fini_full_indexes(ctx); z_erofs_write_mapheader(inode, metabuf); return metabuf; } void z_erofs_drop_inline_pcluster(struct erofs_inode *inode) { struct erofs_sb_info *sbi = inode->sbi; const unsigned int type = Z_EROFS_LCLUSTER_TYPE_PLAIN; struct z_erofs_map_header *h = inode->compressmeta; h->h_advise = cpu_to_le16(le16_to_cpu(h->h_advise) & ~Z_EROFS_ADVISE_INLINE_PCLUSTER); h->h_idata_size = 0; if (!inode->eof_tailraw) return; DBG_BUGON(inode->compressed_idata != true); /* patch the EOF lcluster to uncompressed type first */ if (inode->datalayout == EROFS_INODE_COMPRESSED_FULL) { struct z_erofs_lcluster_index *di = (inode->compressmeta + inode->extent_isize) - sizeof(struct z_erofs_lcluster_index); di->di_advise = cpu_to_le16(type); } else if (inode->datalayout == EROFS_INODE_COMPRESSED_COMPACT) { /* handle the last compacted 4B pack */ unsigned int eofs, base, pos, v, lo; u8 *out; eofs = inode->extent_isize - (4 << (BLK_ROUND_UP(sbi, inode->i_size) & 1)); base = round_down(eofs, 8); pos = 16 /* encodebits */ * ((eofs - base) / 4); out = inode->compressmeta + base; lo = erofs_blkoff(sbi, get_unaligned_le32(out + pos / 8)); v = (type << sbi->blkszbits) | lo; out[pos / 8] = v & 0xff; out[pos / 8 + 1] = v >> 8; } else { DBG_BUGON(1); return; } free(inode->idata); /* replace idata with prepared uncompressed data */ inode->idata = inode->eof_tailraw; inode->idata_size = inode->eof_tailrawsize; inode->compressed_idata = false; inode->eof_tailraw = NULL; } int z_erofs_compress_segment(struct z_erofs_compress_sctx *ctx, u64 offset, erofs_off_t pstart) { struct z_erofs_compress_ictx *ictx = ctx->ictx; struct erofs_inode *inode = ictx->inode; bool frag = cfg.c_fragments && !erofs_is_packed_inode(inode) && ctx->seg_idx >= ictx->seg_num - 1; int fd = ictx->fd; int ret; DBG_BUGON(offset != -1 && frag && inode->fragment_size); if (offset != -1 && frag && !inode->fragment_size && cfg.c_fragdedupe != FRAGDEDUPE_OFF) { ret = z_erofs_fragments_dedupe(inode, fd, &ictx->tof_chksum); if (ret < 0) return ret; if (inode->fragment_size > ctx->remaining) inode->fragment_size = ctx->remaining; ctx->remaining -= inode->fragment_size; } ctx->pstart = pstart; while (ctx->remaining) { const u64 rx = min_t(u64, ctx->remaining, Z_EROFS_COMPR_QUEUE_SZ - ctx->tail); int ret; ret = (offset == -1 ? read(fd, ctx->queue + ctx->tail, rx) : pread(fd, ctx->queue + ctx->tail, rx, ictx->fpos + offset)); if (ret != rx) return -errno; ctx->remaining -= rx; ctx->tail += rx; if (offset != -1) offset += rx; ret = z_erofs_compress_one(ctx); if (ret) return ret; } DBG_BUGON(ctx->head != ctx->tail); if (ctx->pivot) { z_erofs_commit_extent(ctx, ctx->pivot); ctx->pivot = NULL; } /* generate an extra extent for the deduplicated fragment */ if (frag && inode->fragment_size && !ictx->fragemitted) { struct z_erofs_extent_item *ei; ei = malloc(sizeof(*ei)); if (!ei) return -ENOMEM; ei->e = (struct z_erofs_inmem_extent) { .length = inode->fragment_size, .plen = 0, .raw = false, .partial = false, .pstart = ctx->pstart, }; init_list_head(&ei->list); z_erofs_commit_extent(ctx, ei); } return 0; } int erofs_commit_compressed_file(struct z_erofs_compress_ictx *ictx, struct erofs_buffer_head *bh, erofs_off_t pstart, erofs_off_t ptotal) { struct erofs_inode *inode = ictx->inode; struct erofs_sb_info *sbi = inode->sbi; unsigned int legacymetasize, bbits = sbi->blkszbits; u8 *compressmeta; int ret; if (inode->fragment_size) { inode->z_advise |= Z_EROFS_ADVISE_FRAGMENT_PCLUSTER; erofs_sb_set_fragments(inode->sbi); } /* fall back to no compression mode */ DBG_BUGON(pstart < (!!inode->idata_size) << bbits); ptotal -= (u64)(!!inode->idata_size) << bbits; compressmeta = z_erofs_write_indexes(ictx); if (!compressmeta) { ret = -ENOMEM; goto err_free_idata; } legacymetasize = ictx->metacur - compressmeta; /* estimate if data compression saves space or not */ if (!inode->fragment_size && ptotal + inode->idata_size + legacymetasize >= inode->i_size) { z_erofs_dedupe_commit(true); ret = -ENOSPC; goto err_free_meta; } z_erofs_dedupe_ext_commit(false); z_erofs_dedupe_commit(false); if (!ictx->fragemitted) sbi->saved_by_deduplication += inode->fragment_size; /* if the entire file is a fragment, a simplified form is used. */ if (inode->i_size <= inode->fragment_size) { DBG_BUGON(inode->i_size < inode->fragment_size); DBG_BUGON(inode->fragmentoff >> 63); *(__le64 *)compressmeta = cpu_to_le64(inode->fragmentoff | 1ULL << 63); inode->datalayout = EROFS_INODE_COMPRESSED_FULL; legacymetasize = Z_EROFS_LEGACY_MAP_HEADER_SIZE; } if (ptotal) { (void)erofs_bh_balloon(bh, ptotal); } else { if (!cfg.c_fragments && !cfg.c_dedupe) DBG_BUGON(!inode->idata_size); } erofs_info("compressed %s (%llu bytes) into %llu bytes", inode->i_srcpath, inode->i_size | 0ULL, ptotal | 0ULL); if (inode->idata_size) { bh->op = &erofs_skip_write_bhops; inode->bh_data = bh; } else { erofs_bdrop(bh, false); } inode->u.i_blocks = BLK_ROUND_UP(sbi, ptotal); if (inode->datalayout == EROFS_INODE_COMPRESSED_FULL) { inode->extent_isize = legacymetasize; } else { ret = z_erofs_convert_to_compacted_format(inode, pstart, legacymetasize, compressmeta); DBG_BUGON(ret); } inode->compressmeta = compressmeta; if (!erofs_is_packed_inode(inode)) erofs_droid_blocklist_write(inode, pstart >> bbits, inode->u.i_blocks); return 0; err_free_meta: free(compressmeta); inode->compressmeta = NULL; err_free_idata: erofs_bdrop(bh, true); /* revoke buffer */ if (inode->idata) { free(inode->idata); inode->idata = NULL; } return ret; } static struct z_erofs_compress_ictx g_ictx; #ifdef EROFS_MT_ENABLED void *z_erofs_mt_wq_tls_alloc(struct erofs_workqueue *wq, void *ptr) { struct erofs_compress_wq_tls *tls; tls = calloc(1, sizeof(*tls)); if (!tls) return NULL; tls->queue = malloc(Z_EROFS_COMPR_QUEUE_SZ); if (!tls->queue) goto err_free_priv; tls->destbuf = calloc(1, Z_EROFS_DESTBUF_SZ); if (!tls->destbuf) goto err_free_queue; tls->ccfg = calloc(EROFS_MAX_COMPR_CFGS, sizeof(*tls->ccfg)); if (!tls->ccfg) goto err_free_destbuf; return tls; err_free_destbuf: free(tls->destbuf); err_free_queue: free(tls->queue); err_free_priv: free(tls); return NULL; } int z_erofs_mt_wq_tls_init_compr(struct erofs_sb_info *sbi, struct erofs_compress_wq_tls *tls, unsigned int alg_id, char *alg_name, unsigned int comp_level, unsigned int dict_size) { struct erofs_compress_cfg *lc = &tls->ccfg[alg_id]; int ret; if (__erofs_likely(lc->enable)) return 0; ret = erofs_compressor_init(sbi, &lc->handle, alg_name, comp_level, dict_size); if (ret) return ret; lc->algorithmtype = alg_id; lc->enable = true; return 0; } void *z_erofs_mt_wq_tls_free(struct erofs_workqueue *wq, void *priv) { struct erofs_compress_wq_tls *tls = priv; int i; for (i = 0; i < EROFS_MAX_COMPR_CFGS; i++) if (tls->ccfg[i].enable) erofs_compressor_exit(&tls->ccfg[i].handle); free(tls->ccfg); free(tls->destbuf); free(tls->queue); free(tls); return NULL; } void z_erofs_mt_workfn(struct erofs_work *work, void *tlsp) { struct erofs_compress_work *cwork = (struct erofs_compress_work *)work; struct erofs_compress_wq_tls *tls = tlsp; struct z_erofs_compress_sctx *sctx = &cwork->ctx; struct z_erofs_compress_ictx *ictx = sctx->ictx; struct erofs_inode *inode = ictx->inode; struct erofs_sb_info *sbi = inode->sbi; int ret = 0; ret = z_erofs_mt_wq_tls_init_compr(sbi, tls, cwork->alg_id, cwork->alg_name, cwork->comp_level, cwork->dict_size); if (ret) goto out; sctx->pclustersize = z_erofs_get_max_pclustersize(inode); DBG_BUGON(sctx->pclustersize > Z_EROFS_PCLUSTER_MAX_SIZE); sctx->queue = tls->queue; sctx->destbuf = tls->destbuf; sctx->chandle = &tls->ccfg[cwork->alg_id].handle; erofs_compressor_reset(sctx->chandle); sctx->membuf = malloc(round_up(sctx->remaining, erofs_blksiz(sbi))); if (!sctx->membuf) { ret = -ENOMEM; goto out; } sctx->memoff = 0; ret = z_erofs_compress_segment(sctx, sctx->seg_idx * cfg.c_mkfs_segment_size, EROFS_NULL_ADDR); out: DBG_BUGON(ret > 0); pthread_mutex_lock(&ictx->mutex); cwork->errcode = ret; pthread_cond_signal(&cwork->cond); pthread_mutex_unlock(&ictx->mutex); } int z_erofs_merge_segment(struct z_erofs_compress_ictx *ictx, struct z_erofs_compress_sctx *sctx) { struct z_erofs_extent_item *ei, *n; struct erofs_sb_info *sbi = ictx->inode->sbi; bool dedupe_ext = cfg.c_fragments; erofs_off_t off = 0; int ret = 0, ret2; erofs_blk_t dupb; u64 hash; list_for_each_entry_safe(ei, n, &sctx->extents, list) { list_del(&ei->list); list_add_tail(&ei->list, &ictx->extents); if (ei->e.pstart != EROFS_NULL_ADDR) /* deduped extents */ continue; ei->e.pstart = sctx->pstart; sctx->pstart += ei->e.plen; /* skip write data but leave blkaddr for inline fallback */ if (ei->e.inlined || !ei->e.plen) continue; if (dedupe_ext) { dupb = z_erofs_dedupe_ext_match(sbi, sctx->membuf + off, ei->e.plen, ei->e.raw, &hash); if (dupb != EROFS_NULL_ADDR) { ei->e.pstart = dupb; sctx->pstart -= ei->e.plen; off += ei->e.plen; ictx->dedupe = true; erofs_sb_set_dedupe(sbi); sbi->saved_by_deduplication += ei->e.plen; erofs_dbg("Dedupe %u %scompressed data to %llu of %u bytes", ei->e.length, ei->e.raw ? "un" : "", ei->e.pstart | 0ULL, ei->e.plen); continue; } } ret2 = erofs_dev_write(sbi, sctx->membuf + off, ei->e.pstart, ei->e.plen); off += ei->e.plen; if (ret2) ret = ret2; else if (dedupe_ext) z_erofs_dedupe_ext_insert(&ei->e, hash); } free(sctx->membuf); sctx->membuf = NULL; return ret; } int z_erofs_mt_compress(struct z_erofs_compress_ictx *ictx) { struct erofs_compress_work *cur, *head = NULL, **last = &head; struct erofs_compress_cfg *ccfg = ictx->ccfg; struct erofs_inode *inode = ictx->inode; unsigned int segsz = cfg.c_mkfs_segment_size; int nsegs, i; nsegs = DIV_ROUND_UP(inode->i_size - inode->fragment_size, segsz); if (!nsegs) nsegs = 1; ictx->seg_num = nsegs; pthread_mutex_init(&ictx->mutex, NULL); pthread_cond_init(&ictx->cond, NULL); for (i = 0; i < nsegs; i++) { pthread_mutex_lock(&z_erofs_mt_ctrl.mutex); cur = z_erofs_mt_ctrl.idle; if (cur) { z_erofs_mt_ctrl.idle = cur->next; cur->next = NULL; } pthread_mutex_unlock(&z_erofs_mt_ctrl.mutex); if (!cur) { cur = calloc(1, sizeof(*cur)); if (!cur) return -ENOMEM; pthread_cond_init(&cur->cond, NULL); } *last = cur; last = &cur->next; cur->ctx = (struct z_erofs_compress_sctx) { .ictx = ictx, .seg_idx = i, .pivot = &dummy_pivot, }; init_list_head(&cur->ctx.extents); cur->alg_id = ccfg->handle.alg->id; cur->alg_name = ccfg->handle.alg->name; cur->comp_level = ccfg->handle.compression_level; cur->dict_size = ccfg->handle.dict_size; cur->errcode = 1; /* mark as "in progress" */ cur->work.fn = z_erofs_mt_workfn; if (i >= nsegs - 1) { cur->ctx.remaining = inode->i_size - inode->fragment_size - (u64)i * segsz; if (z_erofs_mt_ctrl.hasfwq) { erofs_queue_work(&z_erofs_mt_ctrl.fwq, &cur->work); continue; } } else { cur->ctx.remaining = segsz; } erofs_queue_work(&z_erofs_mt_ctrl.wq, &cur->work); } ictx->mtworks = head; return 0; } int erofs_mt_write_compressed_file(struct z_erofs_compress_ictx *ictx) { struct erofs_sb_info *sbi = ictx->inode->sbi; struct erofs_buffer_head *bh = NULL; struct erofs_compress_work *head = ictx->mtworks, *cur; erofs_off_t pstart, ptotal = 0; int ret; bh = erofs_balloc(sbi->bmgr, DATA, 0, 0); if (IS_ERR(bh)) { ret = PTR_ERR(bh); goto out; } DBG_BUGON(!head); pstart = erofs_pos(sbi, erofs_mapbh(NULL, bh->block)); ret = 0; do { cur = head; head = cur->next; pthread_mutex_lock(&ictx->mutex); while ((ret = cur->errcode) > 0) pthread_cond_wait(&cur->cond, &ictx->mutex); pthread_mutex_unlock(&ictx->mutex); if (!ret) { int ret2; cur->ctx.pstart = pstart; ret2 = z_erofs_merge_segment(ictx, &cur->ctx); if (ret2) ret = ret2; ptotal += cur->ctx.pstart - pstart; pstart = cur->ctx.pstart; } pthread_mutex_lock(&z_erofs_mt_ctrl.mutex); cur->next = z_erofs_mt_ctrl.idle; z_erofs_mt_ctrl.idle = cur; pthread_mutex_unlock(&z_erofs_mt_ctrl.mutex); } while (head); if (ret) goto out; ret = erofs_commit_compressed_file(ictx, bh, pstart - ptotal, ptotal); out: close(ictx->fd); free(ictx); return ret; } static int z_erofs_mt_init(void) { unsigned int workers = cfg.c_mt_workers; int ret; if (workers < 1) return 0; if (workers >= 1 && cfg.c_dedupe) { erofs_warn("multi-threaded dedupe is NOT implemented for now"); cfg.c_mt_workers = 0; } else { if (cfg.c_fragments && workers > 1) { ret = erofs_alloc_workqueue(&z_erofs_mt_ctrl.fwq, 1, 32, z_erofs_mt_wq_tls_alloc, z_erofs_mt_wq_tls_free); if (ret) return ret; z_erofs_mt_ctrl.hasfwq = true; --workers; } ret = erofs_alloc_workqueue(&z_erofs_mt_ctrl.wq, workers, workers << 2, z_erofs_mt_wq_tls_alloc, z_erofs_mt_wq_tls_free); if (ret) return ret; z_erofs_mt_enabled = true; } pthread_mutex_init(&g_ictx.mutex, NULL); pthread_cond_init(&g_ictx.cond, NULL); return 0; } #else static int z_erofs_mt_init(void) { return 0; } #endif void *erofs_begin_compressed_file(struct erofs_inode *inode, int fd, u64 fpos) { struct erofs_sb_info *sbi = inode->sbi; struct z_erofs_compress_ictx *ictx; bool all_fragments = cfg.c_all_fragments && !erofs_is_packed_inode(inode); int ret; /* initialize per-file compression setting */ inode->z_advise = 0; inode->z_logical_clusterbits = sbi->blkszbits; if (cfg.c_fragments && !cfg.c_dedupe) inode->z_advise |= Z_EROFS_ADVISE_INTERLACED_PCLUSTER; #ifndef NDEBUG if (cfg.c_random_algorithms) { while (1) { inode->z_algorithmtype[0] = rand() % EROFS_MAX_COMPR_CFGS; if (erofs_ccfg[inode->z_algorithmtype[0]].enable) break; } } #endif inode->idata_size = 0; inode->fragment_size = 0; if (!z_erofs_mt_enabled || all_fragments) { #ifdef EROFS_MT_ENABLED pthread_mutex_lock(&g_ictx.mutex); if (g_ictx.seg_num) pthread_cond_wait(&g_ictx.cond, &g_ictx.mutex); g_ictx.seg_num = 1; pthread_mutex_unlock(&g_ictx.mutex); #endif ictx = &g_ictx; ictx->fd = fd; } else { ictx = malloc(sizeof(*ictx)); if (!ictx) return ERR_PTR(-ENOMEM); ictx->fd = dup(fd); } ictx->ccfg = &erofs_ccfg[inode->z_algorithmtype[0]]; inode->z_algorithmtype[0] = ictx->ccfg->algorithmtype; inode->z_algorithmtype[1] = 0; /* * Handle tails in advance to avoid writing duplicated * parts into the packed inode. */ if (cfg.c_fragments && !erofs_is_packed_inode(inode) && ictx == &g_ictx && cfg.c_fragdedupe != FRAGDEDUPE_OFF) { ret = z_erofs_fragments_dedupe(inode, fd, &ictx->tof_chksum); if (ret < 0) goto err_free_ictx; if (cfg.c_fragdedupe == FRAGDEDUPE_INODE && inode->fragment_size < inode->i_size) { erofs_dbg("Discard the sub-inode tail fragment of %s", inode->i_srcpath); inode->fragment_size = 0; } } ictx->inode = inode; ictx->fpos = fpos; init_list_head(&ictx->extents); ictx->fix_dedupedfrag = false; ictx->fragemitted = false; ictx->dedupe = false; if (all_fragments && !inode->fragment_size) { ret = z_erofs_pack_file_from_fd(inode, fd, ictx->tof_chksum); if (ret) goto err_free_idata; } #ifdef EROFS_MT_ENABLED if (ictx != &g_ictx) { ret = z_erofs_mt_compress(ictx); if (ret) goto err_free_idata; } #endif return ictx; err_free_idata: if (inode->idata) { free(inode->idata); inode->idata = NULL; } err_free_ictx: if (ictx != &g_ictx) free(ictx); return ERR_PTR(ret); } int erofs_write_compressed_file(struct z_erofs_compress_ictx *ictx) { static u8 g_queue[Z_EROFS_COMPR_QUEUE_SZ]; struct erofs_buffer_head *bh; static struct z_erofs_compress_sctx sctx; struct erofs_compress_cfg *ccfg = ictx->ccfg; struct erofs_inode *inode = ictx->inode; struct erofs_sb_info *sbi = inode->sbi; erofs_off_t pstart; int ret; #ifdef EROFS_MT_ENABLED if (ictx != &g_ictx) return erofs_mt_write_compressed_file(ictx); #endif /* allocate main data buffer */ bh = erofs_balloc(inode->sbi->bmgr, DATA, 0, 0); if (IS_ERR(bh)) { ret = PTR_ERR(bh); goto err_free_idata; } pstart = erofs_pos(sbi, erofs_mapbh(NULL, bh->block)); ictx->seg_num = 1; sctx = (struct z_erofs_compress_sctx) { .ictx = ictx, .queue = g_queue, .chandle = &ccfg->handle, .remaining = inode->i_size - inode->fragment_size, .seg_idx = 0, .pivot = &dummy_pivot, .pclustersize = z_erofs_get_max_pclustersize(inode), }; init_list_head(&sctx.extents); ret = z_erofs_compress_segment(&sctx, -1, pstart); if (ret) goto err_free_idata; list_splice_tail(&sctx.extents, &ictx->extents); ret = erofs_commit_compressed_file(ictx, bh, pstart, sctx.pstart - pstart); goto out; err_free_idata: erofs_bdrop(bh, true); /* revoke buffer */ if (inode->idata) { free(inode->idata); inode->idata = NULL; } out: #ifdef EROFS_MT_ENABLED pthread_mutex_lock(&ictx->mutex); ictx->seg_num = 0; pthread_cond_signal(&ictx->cond); pthread_mutex_unlock(&ictx->mutex); #endif return ret; } static int z_erofs_build_compr_cfgs(struct erofs_sb_info *sbi, struct erofs_buffer_head *sb_bh, u32 *max_dict_size) { struct erofs_buffer_head *bh = sb_bh; int ret = 0; if (sbi->available_compr_algs & (1 << Z_EROFS_COMPRESSION_LZ4)) { struct { __le16 size; struct z_erofs_lz4_cfgs lz4; } __packed lz4alg = { .size = cpu_to_le16(sizeof(struct z_erofs_lz4_cfgs)), .lz4 = { .max_distance = cpu_to_le16(sbi->lz4.max_distance), .max_pclusterblks = cfg.c_mkfs_pclustersize_max >> sbi->blkszbits, } }; bh = erofs_battach(bh, META, sizeof(lz4alg)); if (IS_ERR(bh)) { DBG_BUGON(1); return PTR_ERR(bh); } erofs_mapbh(NULL, bh->block); ret = erofs_dev_write(sbi, &lz4alg, erofs_btell(bh, false), sizeof(lz4alg)); bh->op = &erofs_drop_directly_bhops; } #ifdef HAVE_LIBLZMA if (sbi->available_compr_algs & (1 << Z_EROFS_COMPRESSION_LZMA)) { struct { __le16 size; struct z_erofs_lzma_cfgs lzma; } __packed lzmaalg = { .size = cpu_to_le16(sizeof(struct z_erofs_lzma_cfgs)), .lzma = { .dict_size = cpu_to_le32( max_dict_size [Z_EROFS_COMPRESSION_LZMA]), } }; bh = erofs_battach(bh, META, sizeof(lzmaalg)); if (IS_ERR(bh)) { DBG_BUGON(1); return PTR_ERR(bh); } erofs_mapbh(NULL, bh->block); ret = erofs_dev_write(sbi, &lzmaalg, erofs_btell(bh, false), sizeof(lzmaalg)); bh->op = &erofs_drop_directly_bhops; } #endif if (sbi->available_compr_algs & (1 << Z_EROFS_COMPRESSION_DEFLATE)) { struct { __le16 size; struct z_erofs_deflate_cfgs z; } __packed zalg = { .size = cpu_to_le16(sizeof(struct z_erofs_deflate_cfgs)), .z = { .windowbits = cpu_to_le32(ilog2( max_dict_size [Z_EROFS_COMPRESSION_DEFLATE])), } }; bh = erofs_battach(bh, META, sizeof(zalg)); if (IS_ERR(bh)) { DBG_BUGON(1); return PTR_ERR(bh); } erofs_mapbh(NULL, bh->block); ret = erofs_dev_write(sbi, &zalg, erofs_btell(bh, false), sizeof(zalg)); bh->op = &erofs_drop_directly_bhops; } #ifdef HAVE_LIBZSTD if (sbi->available_compr_algs & (1 << Z_EROFS_COMPRESSION_ZSTD)) { struct { __le16 size; struct z_erofs_zstd_cfgs z; } __packed zalg = { .size = cpu_to_le16(sizeof(struct z_erofs_zstd_cfgs)), .z = { .windowlog = ilog2(max_dict_size[Z_EROFS_COMPRESSION_ZSTD]) - 10, } }; bh = erofs_battach(bh, META, sizeof(zalg)); if (IS_ERR(bh)) { DBG_BUGON(1); return PTR_ERR(bh); } erofs_mapbh(NULL, bh->block); ret = erofs_dev_write(sbi, &zalg, erofs_btell(bh, false), sizeof(zalg)); bh->op = &erofs_drop_directly_bhops; } #endif return ret; } int z_erofs_compress_init(struct erofs_sb_info *sbi, struct erofs_buffer_head *sb_bh) { int i, ret, id; u32 max_dict_size[Z_EROFS_COMPRESSION_MAX] = {}; u32 available_compr_algs = 0; for (i = 0; cfg.c_compr_opts[i].alg; ++i) { struct erofs_compress *c = &erofs_ccfg[i].handle; ret = erofs_compressor_init(sbi, c, cfg.c_compr_opts[i].alg, cfg.c_compr_opts[i].level, cfg.c_compr_opts[i].dict_size); if (ret) return ret; id = z_erofs_get_compress_algorithm_id(c); erofs_ccfg[i].algorithmtype = id; erofs_ccfg[i].enable = true; available_compr_algs |= 1 << erofs_ccfg[i].algorithmtype; if (erofs_ccfg[i].algorithmtype != Z_EROFS_COMPRESSION_LZ4) erofs_sb_set_compr_cfgs(sbi); if (c->dict_size > max_dict_size[id]) max_dict_size[id] = c->dict_size; } /* * if primary algorithm is empty (e.g. compression off), * clear 0PADDING feature for old kernel compatibility. */ if (!available_compr_algs || (cfg.c_legacy_compress && available_compr_algs == 1)) erofs_sb_clear_lz4_0padding(sbi); if (!available_compr_algs) return 0; if (!sb_bh) { u32 dalg = available_compr_algs & (~sbi->available_compr_algs); if (dalg) { erofs_err("unavailable algorithms 0x%x on incremental builds", dalg); return -EOPNOTSUPP; } if (available_compr_algs & (1 << Z_EROFS_COMPRESSION_LZ4) && sbi->lz4.max_pclusterblks << sbi->blkszbits < cfg.c_mkfs_pclustersize_max) { erofs_err("pclustersize %u is too large on incremental builds", cfg.c_mkfs_pclustersize_max); return -EOPNOTSUPP; } } else { sbi->available_compr_algs = available_compr_algs; } /* * if big pcluster is enabled, an extra CBLKCNT lcluster index needs * to be loaded in order to get those compressed block counts. */ if (cfg.c_mkfs_pclustersize_max > erofs_blksiz(sbi)) { if (cfg.c_mkfs_pclustersize_max > Z_EROFS_PCLUSTER_MAX_SIZE) { erofs_err("unsupported pclustersize %u (too large)", cfg.c_mkfs_pclustersize_max); return -EINVAL; } erofs_sb_set_big_pcluster(sbi); } if (cfg.c_mkfs_pclustersize_packed > cfg.c_mkfs_pclustersize_max) { erofs_err("invalid pclustersize for the packed file %u", cfg.c_mkfs_pclustersize_packed); return -EINVAL; } if (sb_bh && erofs_sb_has_compr_cfgs(sbi)) { ret = z_erofs_build_compr_cfgs(sbi, sb_bh, max_dict_size); if (ret) return ret; } z_erofs_mt_enabled = false; return z_erofs_mt_init(); } int z_erofs_compress_exit(void) { int i, ret; for (i = 0; cfg.c_compr_opts[i].alg; ++i) { ret = erofs_compressor_exit(&erofs_ccfg[i].handle); if (ret) return ret; } if (z_erofs_mt_enabled) { #ifdef EROFS_MT_ENABLED ret = erofs_destroy_workqueue(&z_erofs_mt_ctrl.wq); if (ret) return ret; ret = erofs_destroy_workqueue(&z_erofs_mt_ctrl.fwq); if (ret) return ret; while (z_erofs_mt_ctrl.idle) { struct erofs_compress_work *tmp = z_erofs_mt_ctrl.idle->next; free(z_erofs_mt_ctrl.idle); z_erofs_mt_ctrl.idle = tmp; } #endif } return 0; } erofs-utils-1.8.6/lib/compress_hints.c000066400000000000000000000072251477440110200177620ustar00rootroot00000000000000// SPDX-License-Identifier: GPL-2.0+ OR Apache-2.0 /* * Copyright (C), 2008-2021, OPPO Mobile Comm Corp., Ltd. * Created by Huang Jianan */ #include #include #include "erofs/err.h" #include "erofs/list.h" #include "erofs/print.h" #include "erofs/compress_hints.h" static LIST_HEAD(compress_hints_head); static void dump_regerror(int errcode, const char *s, const regex_t *preg) { char str[512]; regerror(errcode, preg, str, sizeof(str)); erofs_err("invalid regex %s (%s)\n", s, str); } /* algorithmtype is actually ccfg # here */ static int erofs_insert_compress_hints(const char *s, unsigned int blks, unsigned int algorithmtype) { struct erofs_compress_hints *ch; int ret; ch = malloc(sizeof(struct erofs_compress_hints)); if (!ch) return -ENOMEM; ret = regcomp(&ch->reg, s, REG_EXTENDED|REG_NOSUB); if (ret) { dump_regerror(ret, s, &ch->reg); free(ch); return ret; } ch->physical_clusterblks = blks; ch->algorithmtype = algorithmtype; list_add_tail(&ch->list, &compress_hints_head); erofs_info("compress hint %s (%u) is inserted", s, blks); return ret; } bool z_erofs_apply_compress_hints(struct erofs_inode *inode) { const char *s; struct erofs_compress_hints *r; unsigned int pclusterblks, algorithmtype; if (inode->z_physical_clusterblks) return true; s = erofs_fspath(inode->i_srcpath); pclusterblks = cfg.c_mkfs_pclustersize_def >> inode->sbi->blkszbits; algorithmtype = 0; list_for_each_entry(r, &compress_hints_head, list) { int ret = regexec(&r->reg, s, (size_t)0, NULL, 0); if (!ret) { pclusterblks = r->physical_clusterblks; algorithmtype = r->algorithmtype; break; } if (ret != REG_NOMATCH) dump_regerror(ret, s, &r->reg); } inode->z_physical_clusterblks = pclusterblks; inode->z_algorithmtype[0] = algorithmtype; /* pclusterblks is 0 means this file shouldn't be compressed */ return pclusterblks != 0; } void erofs_cleanup_compress_hints(void) { struct erofs_compress_hints *r, *n; list_for_each_entry_safe(r, n, &compress_hints_head, list) { list_del(&r->list); free(r); } } int erofs_load_compress_hints(struct erofs_sb_info *sbi) { char buf[PATH_MAX + 100]; FILE *f; unsigned int line, max_pclustersize = 0; int ret = 0; if (!cfg.c_compress_hints_file) return 0; f = fopen(cfg.c_compress_hints_file, "r"); if (!f) return -errno; for (line = 1; fgets(buf, sizeof(buf), f); ++line) { unsigned int pclustersize, ccfg; char *alg, *pattern; if (*buf == '#' || *buf == '\n') continue; pclustersize = atoi(strtok(buf, "\t ")); alg = strtok(NULL, "\n\t "); pattern = strtok(NULL, "\n"); if (!pattern) { pattern = alg; alg = NULL; } if (!pattern || *pattern == '\0') { erofs_err("cannot find a match pattern at line %u", line); ret = -EINVAL; goto out; } if (!alg || *alg == '\0') { ccfg = 0; } else { ccfg = atoi(alg); if (ccfg >= EROFS_MAX_COMPR_CFGS || !cfg.c_compr_opts[ccfg].alg) { erofs_err("invalid compressing configuration \"%s\" at line %u", alg, line); ret = -EINVAL; goto out; } } if (pclustersize % erofs_blksiz(sbi)) { erofs_warn("invalid physical clustersize %u, " "use default pclusterblks %u", pclustersize, cfg.c_mkfs_pclustersize_def); continue; } erofs_insert_compress_hints(pattern, pclustersize / erofs_blksiz(sbi), ccfg); if (pclustersize > max_pclustersize) max_pclustersize = pclustersize; } if (cfg.c_mkfs_pclustersize_max < max_pclustersize) { cfg.c_mkfs_pclustersize_max = max_pclustersize; erofs_warn("update max pclustersize to %u", cfg.c_mkfs_pclustersize_max); } out: fclose(f); return ret; } erofs-utils-1.8.6/lib/compressor.c000066400000000000000000000067161477440110200171220ustar00rootroot00000000000000// SPDX-License-Identifier: GPL-2.0+ OR Apache-2.0 /* * Copyright (C) 2018-2019 HUAWEI, Inc. * http://www.huawei.com/ * Created by Gao Xiang */ #include "erofs/internal.h" #include "compressor.h" #include "erofs/print.h" static const struct erofs_algorithm erofs_algs[] = { { "lz4", #if LZ4_ENABLED &erofs_compressor_lz4, #else NULL, #endif Z_EROFS_COMPRESSION_LZ4, false }, #if LZ4HC_ENABLED { "lz4hc", &erofs_compressor_lz4hc, Z_EROFS_COMPRESSION_LZ4, true }, #endif { "lzma", #if HAVE_LIBLZMA &erofs_compressor_lzma, #else NULL, #endif Z_EROFS_COMPRESSION_LZMA, false }, { "deflate", &erofs_compressor_deflate, Z_EROFS_COMPRESSION_DEFLATE, false }, #if HAVE_LIBDEFLATE { "libdeflate", &erofs_compressor_libdeflate, Z_EROFS_COMPRESSION_DEFLATE, true }, #endif { "zstd", #ifdef HAVE_LIBZSTD &erofs_compressor_libzstd, #else NULL, #endif Z_EROFS_COMPRESSION_ZSTD, false }, }; int z_erofs_get_compress_algorithm_id(const struct erofs_compress *c) { DBG_BUGON(!c->alg); return c->alg->id; } const char *z_erofs_list_supported_algorithms(int i, unsigned int *mask) { if (i >= ARRAY_SIZE(erofs_algs)) return NULL; if (!erofs_algs[i].optimisor && (*mask & (1 << erofs_algs[i].id))) { *mask ^= 1 << erofs_algs[i].id; return erofs_algs[i].name; } return ""; } const struct erofs_algorithm *z_erofs_list_available_compressors(int *i) { for (;*i < ARRAY_SIZE(erofs_algs); ++*i) { if (!erofs_algs[*i].c) continue; return &erofs_algs[(*i)++]; } return NULL; } int erofs_compress_destsize(const struct erofs_compress *c, const void *src, unsigned int *srcsize, void *dst, unsigned int dstsize) { DBG_BUGON(!c->alg); if (!c->alg->c->compress_destsize) return -EOPNOTSUPP; return c->alg->c->compress_destsize(c, src, srcsize, dst, dstsize); } int erofs_compressor_init(struct erofs_sb_info *sbi, struct erofs_compress *c, char *alg_name, int compression_level, u32 dict_size) { int ret, i; c->sbi = sbi; /* should be written in "minimum compression ratio * 100" */ c->compress_threshold = 100; c->compression_level = -1; c->dict_size = 0; if (!alg_name) { c->alg = NULL; return 0; } ret = -EINVAL; for (i = 0; i < ARRAY_SIZE(erofs_algs); ++i) { if (alg_name && strcmp(alg_name, erofs_algs[i].name)) continue; if (!erofs_algs[i].c) continue; if (erofs_algs[i].c->setlevel) { ret = erofs_algs[i].c->setlevel(c, compression_level); if (ret) { erofs_err("failed to set compression level %d for %s", compression_level, alg_name); return ret; } } else if (compression_level >= 0) { erofs_err("compression level %d is not supported for %s", compression_level, alg_name); return -EINVAL; } if (erofs_algs[i].c->setdictsize) { ret = erofs_algs[i].c->setdictsize(c, dict_size); if (ret) { erofs_err("failed to set dict size %u for %s", dict_size, alg_name); return ret; } } else if (dict_size) { erofs_err("dict size is not supported for %s", alg_name); return -EINVAL; } ret = erofs_algs[i].c->init(c); if (ret) return ret; if (!ret) { c->alg = &erofs_algs[i]; return 0; } } erofs_err("Cannot find a valid compressor %s", alg_name); return ret; } int erofs_compressor_exit(struct erofs_compress *c) { if (c->alg && c->alg->c->exit) return c->alg->c->exit(c); return 0; } void erofs_compressor_reset(struct erofs_compress *c) { if (c->alg && c->alg->c->reset) c->alg->c->reset(c); } erofs-utils-1.8.6/lib/compressor.h000066400000000000000000000040171477440110200171170ustar00rootroot00000000000000/* SPDX-License-Identifier: GPL-2.0+ OR Apache-2.0 */ /* * Copyright (C) 2018-2019 HUAWEI, Inc. * http://www.huawei.com/ * Created by Gao Xiang */ #ifndef __EROFS_LIB_COMPRESSOR_H #define __EROFS_LIB_COMPRESSOR_H #include "erofs/defs.h" struct erofs_compress; struct erofs_compressor { int default_level; int best_level; u32 default_dictsize; u32 max_dictsize; int (*init)(struct erofs_compress *c); int (*exit)(struct erofs_compress *c); void (*reset)(struct erofs_compress *c); int (*setlevel)(struct erofs_compress *c, int compression_level); int (*setdictsize)(struct erofs_compress *c, u32 dict_size); int (*compress_destsize)(const struct erofs_compress *c, const void *src, unsigned int *srcsize, void *dst, unsigned int dstsize); }; struct erofs_algorithm { char *name; const struct erofs_compressor *c; unsigned int id; /* its name won't be shown as a supported algorithm */ bool optimisor; }; struct erofs_compress { struct erofs_sb_info *sbi; const struct erofs_algorithm *alg; unsigned int compress_threshold; unsigned int compression_level; unsigned int dict_size; void *private_data; }; /* list of compression algorithms */ extern const struct erofs_compressor erofs_compressor_lz4; extern const struct erofs_compressor erofs_compressor_lz4hc; extern const struct erofs_compressor erofs_compressor_lzma; extern const struct erofs_compressor erofs_compressor_deflate; extern const struct erofs_compressor erofs_compressor_libdeflate; extern const struct erofs_compressor erofs_compressor_libzstd; int z_erofs_get_compress_algorithm_id(const struct erofs_compress *c); int erofs_compress_destsize(const struct erofs_compress *c, const void *src, unsigned int *srcsize, void *dst, unsigned int dstsize); int erofs_compressor_init(struct erofs_sb_info *sbi, struct erofs_compress *c, char *alg_name, int compression_level, u32 dict_size); int erofs_compressor_exit(struct erofs_compress *c); void erofs_compressor_reset(struct erofs_compress *c); #endif erofs-utils-1.8.6/lib/compressor_deflate.c000066400000000000000000000052541477440110200206020ustar00rootroot00000000000000// SPDX-License-Identifier: GPL-2.0+ OR Apache-2.0 /* * Copyright (C) 2023, Alibaba Cloud * Copyright (C) 2023, Gao Xiang */ #include "erofs/internal.h" #include "erofs/print.h" #include "erofs/config.h" #include "compressor.h" #include "erofs/atomic.h" void *kite_deflate_init(int level, unsigned int dict_size); void kite_deflate_end(void *s); int kite_deflate_destsize(void *s, const u8 *in, u8 *out, unsigned int *srcsize, unsigned int target_dstsize); static int deflate_compress_destsize(const struct erofs_compress *c, const void *src, unsigned int *srcsize, void *dst, unsigned int dstsize) { int rc = kite_deflate_destsize(c->private_data, src, dst, srcsize, dstsize); if (rc <= 0) return -EFAULT; return rc; } static int compressor_deflate_exit(struct erofs_compress *c) { if (!c->private_data) return -EINVAL; kite_deflate_end(c->private_data); return 0; } static int compressor_deflate_init(struct erofs_compress *c) { static erofs_atomic_bool_t __warnonce; if (c->private_data) { kite_deflate_end(c->private_data); c->private_data = NULL; } c->private_data = kite_deflate_init(c->compression_level, c->dict_size); if (IS_ERR_VALUE(c->private_data)) return PTR_ERR(c->private_data); if (!erofs_atomic_test_and_set(&__warnonce)) { erofs_warn("EXPERIMENTAL DEFLATE algorithm in use. Use at your own risk!"); erofs_warn("*Carefully* check filesystem data correctness to avoid corruption!"); erofs_warn("Please send a report to if something is wrong."); } return 0; } static int erofs_compressor_deflate_setlevel(struct erofs_compress *c, int compression_level) { if (compression_level < 0) compression_level = erofs_compressor_deflate.default_level; if (compression_level > erofs_compressor_deflate.best_level) { erofs_err("invalid compression level %d", compression_level); return -EINVAL; } c->compression_level = compression_level; return 0; } static int erofs_compressor_deflate_setdictsize(struct erofs_compress *c, u32 dict_size) { if (!dict_size) dict_size = erofs_compressor_deflate.default_dictsize; if (dict_size > erofs_compressor_deflate.max_dictsize) { erofs_err("dictionary size %u is too large", dict_size); return -EINVAL; } c->dict_size = dict_size; return 0; } const struct erofs_compressor erofs_compressor_deflate = { .default_level = 1, .best_level = 9, .default_dictsize = 1 << 15, .max_dictsize = 1 << 15, .init = compressor_deflate_init, .exit = compressor_deflate_exit, .setlevel = erofs_compressor_deflate_setlevel, .setdictsize = erofs_compressor_deflate_setdictsize, .compress_destsize = deflate_compress_destsize, }; erofs-utils-1.8.6/lib/compressor_libdeflate.c000066400000000000000000000074731477440110200212760ustar00rootroot00000000000000// SPDX-License-Identifier: GPL-2.0+ OR Apache-2.0 #include "erofs/internal.h" #include "erofs/print.h" #include "erofs/config.h" #include #include #include "compressor.h" #include "erofs/atomic.h" struct erofs_libdeflate_context { struct libdeflate_compressor *strm; size_t last_uncompressed_size; }; static int libdeflate_compress_destsize(const struct erofs_compress *c, const void *src, unsigned int *srcsize, void *dst, unsigned int dstsize) { struct erofs_libdeflate_context *ctx = c->private_data; size_t l = 0; /* largest input that fits so far */ size_t l_csize = 0; size_t r = *srcsize + 1; /* smallest input that doesn't fit so far */ size_t m; u8 tmpbuf[dstsize + 9]; if (ctx->last_uncompressed_size) m = ctx->last_uncompressed_size * 15 / 16; else m = dstsize * 4; for (;;) { size_t csize; m = max(m, l + 1); m = min(m, r - 1); csize = libdeflate_deflate_compress(ctx->strm, src, m, tmpbuf, dstsize + 9); /*printf("Tried %zu => %zu\n", m, csize);*/ if (csize > 0 && csize <= dstsize) { /* Fits */ memcpy(dst, tmpbuf, csize); l = m; l_csize = csize; if (r <= l + 1 || csize + (22 - 2*(int)c->compression_level) >= dstsize) break; /* * Estimate needed input prefix size based on current * compression ratio. */ m = (dstsize * m) / csize; } else { /* Doesn't fit */ r = m; if (r <= l + 1) break; m = (l + r) / 2; } } /* * Since generic EROFS on-disk compressed data will be filled with * leading 0s (but no more than one block, 4KB for example, even the * whole pcluster is 128KB) if not filled, it will be used to identify * the actual compressed length as well without taking more reserved * compressed bytes or some extra metadata to record this. * * DEFLATE streams can also be used in this way, if it starts from a * non-last stored block, flag an unused bit instead to avoid the zero * byte. It's still a valid one according to the DEFLATE specification. */ if (l_csize && !((u8 *)dst)[0]) ((u8 *)dst)[0] = 1 << (2 + 1); /*printf("Choosing %zu => %zu\n", l, l_csize);*/ *srcsize = l; ctx->last_uncompressed_size = l; return l_csize; } static int compressor_libdeflate_exit(struct erofs_compress *c) { struct erofs_libdeflate_context *ctx = c->private_data; if (!ctx) return -EINVAL; libdeflate_free_compressor(ctx->strm); free(ctx); return 0; } static int compressor_libdeflate_init(struct erofs_compress *c) { static erofs_atomic_bool_t __warnonce; struct erofs_libdeflate_context *ctx; DBG_BUGON(c->private_data); ctx = calloc(1, sizeof(struct erofs_libdeflate_context)); if (!ctx) return -ENOMEM; ctx->strm = libdeflate_alloc_compressor(c->compression_level); if (!ctx->strm) { free(ctx); return -ENOMEM; } c->private_data = ctx; if (!erofs_atomic_test_and_set(&__warnonce)) erofs_warn("EXPERIMENTAL libdeflate compressor in use. Use at your own risk!"); return 0; } static void compressor_libdeflate_reset(struct erofs_compress *c) { struct erofs_libdeflate_context *ctx = c->private_data; ctx->last_uncompressed_size = 0; } static int erofs_compressor_libdeflate_setlevel(struct erofs_compress *c, int compression_level) { if (compression_level < 0) compression_level = erofs_compressor_libdeflate.default_level; if (compression_level > erofs_compressor_libdeflate.best_level) { erofs_err("invalid compression level %d", compression_level); return -EINVAL; } c->compression_level = compression_level; return 0; } const struct erofs_compressor erofs_compressor_libdeflate = { .default_level = 1, .best_level = 12, .init = compressor_libdeflate_init, .exit = compressor_libdeflate_exit, .reset = compressor_libdeflate_reset, .setlevel = erofs_compressor_libdeflate_setlevel, .compress_destsize = libdeflate_compress_destsize, }; erofs-utils-1.8.6/lib/compressor_liblzma.c000066400000000000000000000060651477440110200206310ustar00rootroot00000000000000// SPDX-License-Identifier: GPL-2.0+ OR Apache-2.0 /* * Copyright (C) 2021 Gao Xiang */ #include #include "config.h" #ifdef HAVE_LIBLZMA #include #include "erofs/config.h" #include "erofs/print.h" #include "erofs/internal.h" #include "erofs/atomic.h" #include "compressor.h" struct erofs_liblzma_context { lzma_options_lzma opt; lzma_stream strm; }; static int erofs_liblzma_compress_destsize(const struct erofs_compress *c, const void *src, unsigned int *srcsize, void *dst, unsigned int dstsize) { struct erofs_liblzma_context *ctx = c->private_data; lzma_stream *strm = &ctx->strm; lzma_ret ret = lzma_microlzma_encoder(strm, &ctx->opt); if (ret != LZMA_OK) return -EFAULT; strm->next_in = src; strm->avail_in = *srcsize; strm->next_out = dst; strm->avail_out = dstsize; ret = lzma_code(strm, LZMA_FINISH); if (ret != LZMA_STREAM_END) return -EBADMSG; *srcsize = strm->total_in; return strm->total_out; } static int erofs_compressor_liblzma_exit(struct erofs_compress *c) { struct erofs_liblzma_context *ctx = c->private_data; if (!ctx) return -EINVAL; lzma_end(&ctx->strm); free(ctx); return 0; } static int erofs_compressor_liblzma_setlevel(struct erofs_compress *c, int compression_level) { if (compression_level < 0) compression_level = erofs_compressor_lzma.default_level; if (compression_level > erofs_compressor_lzma.best_level) { erofs_err("invalid compression level %d", compression_level); return -EINVAL; } c->compression_level = compression_level; return 0; } static int erofs_compressor_liblzma_setdictsize(struct erofs_compress *c, u32 dict_size) { if (!dict_size) { if (erofs_compressor_lzma.default_dictsize) { dict_size = erofs_compressor_lzma.default_dictsize; } else { dict_size = min_t(u32, Z_EROFS_LZMA_MAX_DICT_SIZE, cfg.c_mkfs_pclustersize_max << 2); if (dict_size < 32768) dict_size = 32768; } } if (dict_size > Z_EROFS_LZMA_MAX_DICT_SIZE || dict_size < 4096) { erofs_err("invalid dictionary size %u", dict_size); return -EINVAL; } c->dict_size = dict_size; return 0; } static int erofs_compressor_liblzma_init(struct erofs_compress *c) { struct erofs_liblzma_context *ctx; u32 preset; ctx = malloc(sizeof(*ctx)); if (!ctx) return -ENOMEM; ctx->strm = (lzma_stream)LZMA_STREAM_INIT; if (c->compression_level < 0) preset = LZMA_PRESET_DEFAULT; else if (c->compression_level >= 100) preset = (c->compression_level - 100) | LZMA_PRESET_EXTREME; else preset = c->compression_level; if (lzma_lzma_preset(&ctx->opt, preset)) return -EINVAL; ctx->opt.dict_size = c->dict_size; c->private_data = ctx; return 0; } const struct erofs_compressor erofs_compressor_lzma = { .default_level = LZMA_PRESET_DEFAULT, .best_level = 109, .max_dictsize = Z_EROFS_LZMA_MAX_DICT_SIZE, .init = erofs_compressor_liblzma_init, .exit = erofs_compressor_liblzma_exit, .setlevel = erofs_compressor_liblzma_setlevel, .setdictsize = erofs_compressor_liblzma_setdictsize, .compress_destsize = erofs_liblzma_compress_destsize, }; #endif erofs-utils-1.8.6/lib/compressor_libzstd.c000066400000000000000000000074601477440110200206520ustar00rootroot00000000000000// SPDX-License-Identifier: GPL-2.0+ OR Apache-2.0 #include "erofs/internal.h" #include "erofs/print.h" #include "erofs/config.h" #include #include #include #include "compressor.h" #include "erofs/atomic.h" static int libzstd_compress_destsize(const struct erofs_compress *c, const void *src, unsigned int *srcsize, void *dst, unsigned int dstsize) { ZSTD_CCtx *cctx = c->private_data; size_t l = 0; /* largest input that fits so far */ size_t l_csize = 0; size_t r = *srcsize + 1; /* smallest input that doesn't fit so far */ size_t m; u8 *fitblk_buffer = alloca(dstsize + 32); m = dstsize * 4; for (;;) { size_t csize; m = max(m, l + 1); m = min(m, r - 1); csize = ZSTD_compress2(cctx, fitblk_buffer, dstsize + 32, src, m); if (ZSTD_isError(csize)) { if (ZSTD_getErrorCode(csize) == ZSTD_error_dstSize_tooSmall) goto doesnt_fit; return -EFAULT; } if (csize > 0 && csize <= dstsize) { /* Fits */ memcpy(dst, fitblk_buffer, csize); l = m; l_csize = csize; if (r <= l + 1 || csize + 1 >= dstsize) break; /* * Estimate needed input prefix size based on current * compression ratio. */ m = (dstsize * m) / csize; } else { doesnt_fit: /* Doesn't fit */ r = m; if (r <= l + 1) break; m = (l + r) / 2; } } *srcsize = l; return l_csize; } static int compressor_libzstd_exit(struct erofs_compress *c) { if (!c->private_data) return -EINVAL; ZSTD_freeCCtx(c->private_data); return 0; } static int erofs_compressor_libzstd_setlevel(struct erofs_compress *c, int compression_level) { if (compression_level > erofs_compressor_libzstd.best_level) { erofs_err("invalid compression level %d", compression_level); return -EINVAL; } c->compression_level = compression_level; return 0; } static int erofs_compressor_libzstd_setdictsize(struct erofs_compress *c, u32 dict_size) { if (!dict_size) { if (erofs_compressor_libzstd.default_dictsize) { dict_size = erofs_compressor_libzstd.default_dictsize; } else { dict_size = min_t(u32, Z_EROFS_ZSTD_MAX_DICT_SIZE, cfg.c_mkfs_pclustersize_max << 3); dict_size = 1 << ilog2(dict_size); } } if (dict_size != 1 << ilog2(dict_size) || dict_size > Z_EROFS_ZSTD_MAX_DICT_SIZE) { erofs_err("invalid dictionary size %u", dict_size); return -EINVAL; } c->dict_size = dict_size; return 0; } static int compressor_libzstd_init(struct erofs_compress *c) { static erofs_atomic_bool_t __warnonce; ZSTD_CCtx *cctx = c->private_data; size_t err; ZSTD_freeCCtx(cctx); cctx = ZSTD_createCCtx(); if (!cctx) return -ENOMEM; err = ZSTD_CCtx_setParameter(cctx, ZSTD_c_compressionLevel, c->compression_level); if (ZSTD_isError(err)) { erofs_err("failed to set compression level: %s", ZSTD_getErrorName(err)); return -EINVAL; } err = ZSTD_CCtx_setParameter(cctx, ZSTD_c_windowLog, ilog2(c->dict_size)); if (ZSTD_isError(err)) { erofs_err("failed to set window log: %s", ZSTD_getErrorName(err)); return -EINVAL; } c->private_data = cctx; if (!erofs_atomic_test_and_set(&__warnonce)) { erofs_warn("EXPERIMENTAL libzstd compressor in use. Note that `fitblk` isn't supported by upstream zstd for now."); erofs_warn("Therefore it will takes more time in order to get the optimal result."); erofs_info("You could clarify further needs in zstd repository for reference too."); } return 0; } const struct erofs_compressor erofs_compressor_libzstd = { .default_level = ZSTD_CLEVEL_DEFAULT, .best_level = 22, .max_dictsize = Z_EROFS_ZSTD_MAX_DICT_SIZE, .init = compressor_libzstd_init, .exit = compressor_libzstd_exit, .setlevel = erofs_compressor_libzstd_setlevel, .setdictsize = erofs_compressor_libzstd_setdictsize, .compress_destsize = libzstd_compress_destsize, }; erofs-utils-1.8.6/lib/compressor_lz4.c000066400000000000000000000021001477440110200176720ustar00rootroot00000000000000// SPDX-License-Identifier: GPL-2.0+ OR Apache-2.0 /* * Copyright (C) 2018-2019 HUAWEI, Inc. * http://www.huawei.com/ * Created by Gao Xiang */ #include #include "erofs/internal.h" #include "compressor.h" #ifndef LZ4_DISTANCE_MAX /* history window size */ #define LZ4_DISTANCE_MAX 65535 /* set to maximum value by default */ #endif static int lz4_compress_destsize(const struct erofs_compress *c, const void *src, unsigned int *srcsize, void *dst, unsigned int dstsize) { int srcSize = (int)*srcsize; int rc = LZ4_compress_destSize(src, dst, &srcSize, (int)dstsize); if (!rc) return -EFAULT; *srcsize = srcSize; return rc; } static int compressor_lz4_exit(struct erofs_compress *c) { return 0; } static int compressor_lz4_init(struct erofs_compress *c) { c->sbi->lz4.max_distance = max_t(u16, c->sbi->lz4.max_distance, LZ4_DISTANCE_MAX); return 0; } const struct erofs_compressor erofs_compressor_lz4 = { .init = compressor_lz4_init, .exit = compressor_lz4_exit, .compress_destsize = lz4_compress_destsize, }; erofs-utils-1.8.6/lib/compressor_lz4hc.c000066400000000000000000000034251477440110200202200ustar00rootroot00000000000000// SPDX-License-Identifier: GPL-2.0+ OR Apache-2.0 /* * Copyright (C) 2018-2019 HUAWEI, Inc. * http://www.huawei.com/ * Created by Gao Xiang */ #include #include "erofs/internal.h" #include "erofs/print.h" #include "compressor.h" #ifndef LZ4_DISTANCE_MAX /* history window size */ #define LZ4_DISTANCE_MAX 65535 /* set to maximum value by default */ #endif static int lz4hc_compress_destsize(const struct erofs_compress *c, const void *src, unsigned int *srcsize, void *dst, unsigned int dstsize) { int srcSize = (int)*srcsize; int rc = LZ4_compress_HC_destSize(c->private_data, src, dst, &srcSize, (int)dstsize, c->compression_level); if (!rc) return -EFAULT; *srcsize = srcSize; return rc; } static int compressor_lz4hc_exit(struct erofs_compress *c) { if (!c->private_data) return -EINVAL; LZ4_freeStreamHC(c->private_data); return 0; } static int compressor_lz4hc_init(struct erofs_compress *c) { c->private_data = LZ4_createStreamHC(); if (!c->private_data) return -ENOMEM; c->sbi->lz4.max_distance = max_t(u16, c->sbi->lz4.max_distance, LZ4_DISTANCE_MAX); return 0; } static int compressor_lz4hc_setlevel(struct erofs_compress *c, int compression_level) { if (compression_level > erofs_compressor_lz4hc.best_level) { erofs_err("invalid compression level %d", compression_level); return -EINVAL; } c->compression_level = compression_level < 0 ? LZ4HC_CLEVEL_DEFAULT : compression_level; return 0; } const struct erofs_compressor erofs_compressor_lz4hc = { .default_level = LZ4HC_CLEVEL_DEFAULT, .best_level = LZ4HC_CLEVEL_MAX, .init = compressor_lz4hc_init, .exit = compressor_lz4hc_exit, .setlevel = compressor_lz4hc_setlevel, .compress_destsize = lz4hc_compress_destsize, }; erofs-utils-1.8.6/lib/config.c000066400000000000000000000074111477440110200161640ustar00rootroot00000000000000// SPDX-License-Identifier: GPL-2.0+ OR Apache-2.0 /* * Copyright (C) 2018-2019 HUAWEI, Inc. * http://www.huawei.com/ * Created by Li Guifu */ #include #include #include #include #include "erofs/print.h" #include "erofs/internal.h" #include "liberofs_private.h" #ifdef HAVE_SYS_IOCTL_H #include #endif #ifdef HAVE_UNISTD_H #include #endif struct erofs_configure cfg; struct erofs_sb_info g_sbi; bool erofs_stdout_tty; void erofs_init_configure(void) { memset(&cfg, 0, sizeof(cfg)); cfg.c_dbg_lvl = EROFS_WARN; cfg.c_version = PACKAGE_VERSION; cfg.c_dry_run = false; cfg.c_ignore_mtime = false; cfg.c_force_inodeversion = 0; cfg.c_inline_xattr_tolerance = 2; cfg.c_unix_timestamp = -1; cfg.c_uid = -1; cfg.c_gid = -1; cfg.c_max_decompressed_extent_bytes = -1; erofs_stdout_tty = isatty(STDOUT_FILENO); } void erofs_show_config(void) { const struct erofs_configure *c = &cfg; if (c->c_dbg_lvl < EROFS_INFO) return; erofs_dump("\tc_version: [%8s]\n", c->c_version); erofs_dump("\tc_dbg_lvl: [%8d]\n", c->c_dbg_lvl); erofs_dump("\tc_dry_run: [%8d]\n", c->c_dry_run); } void erofs_exit_configure(void) { int i; #ifdef HAVE_LIBSELINUX if (cfg.sehnd) selabel_close(cfg.sehnd); #endif if (cfg.c_img_path) free(cfg.c_img_path); if (cfg.c_src_path) free(cfg.c_src_path); for (i = 0; i < EROFS_MAX_COMPR_CFGS && cfg.c_compr_opts[i].alg; i++) free(cfg.c_compr_opts[i].alg); } struct erofs_configure *erofs_get_configure() { return &cfg; } static unsigned int fullpath_prefix; /* root directory prefix length */ void erofs_set_fs_root(const char *rootdir) { fullpath_prefix = strlen(rootdir); } const char *erofs_fspath(const char *fullpath) { const char *s = fullpath + fullpath_prefix; while (*s == '/') s++; return s; } #ifdef HAVE_LIBSELINUX int erofs_selabel_open(const char *file_contexts) { struct selinux_opt seopts[] = { { .type = SELABEL_OPT_PATH, .value = file_contexts } }; if (cfg.sehnd) { erofs_info("ignore duplicated file contexts \"%s\"", file_contexts); return -EBUSY; } cfg.sehnd = selabel_open(SELABEL_CTX_FILE, seopts, 1); if (!cfg.sehnd) { erofs_err("failed to open file contexts \"%s\"", file_contexts); return -EINVAL; } return 0; } #endif static bool __erofs_is_progressmsg; char *erofs_trim_for_progressinfo(const char *str, int placeholder) { int col, len; if (!erofs_stdout_tty) { return strdup(str); } else { #ifdef GWINSZ_IN_SYS_IOCTL struct winsize winsize; if(ioctl(STDOUT_FILENO, TIOCGWINSZ, &winsize) >= 0 && winsize.ws_col > 0) col = winsize.ws_col; else #endif col = 80; } if (col <= placeholder) return strdup(""); len = strlen(str); /* omit over long prefixes */ if (len > col - placeholder) { char *s = strdup(str + len - (col - placeholder)); if (col > placeholder + 2) { s[0] = '['; s[1] = ']'; } return s; } return strdup(str); } void erofs_msg(int dbglv, const char *fmt, ...) { va_list ap; FILE *f = dbglv >= EROFS_ERR ? stderr : stdout; if (__erofs_is_progressmsg) { fputc('\n', stdout); __erofs_is_progressmsg = false; } va_start(ap, fmt); vfprintf(f, fmt, ap); va_end(ap); } void erofs_update_progressinfo(const char *fmt, ...) { char msg[8192]; va_list ap; if (cfg.c_dbg_lvl >= EROFS_INFO || !cfg.c_showprogress) return; va_start(ap, fmt); vsprintf(msg, fmt, ap); va_end(ap); if (erofs_stdout_tty) { printf("\r\033[K%s", msg); __erofs_is_progressmsg = true; fflush(stdout); return; } fputs(msg, stdout); fputc('\n', stdout); } unsigned int erofs_get_available_processors(void) { #if defined(HAVE_UNISTD_H) && defined(HAVE_SYSCONF) return sysconf(_SC_NPROCESSORS_ONLN); #else return 0; #endif } erofs-utils-1.8.6/lib/data.c000066400000000000000000000255611477440110200156360ustar00rootroot00000000000000// SPDX-License-Identifier: GPL-2.0+ OR Apache-2.0 /* * Copyright (C) 2020 Gao Xiang * Compression support by Huang Jianan */ #include #include "erofs/print.h" #include "erofs/internal.h" #include "erofs/trace.h" #include "erofs/decompress.h" #include "erofs/fragments.h" static int erofs_map_blocks_flatmode(struct erofs_inode *inode, struct erofs_map_blocks *map, int flags) { int err = 0; erofs_blk_t nblocks, lastblk; u64 offset = map->m_la; struct erofs_inode *vi = inode; struct erofs_sb_info *sbi = inode->sbi; bool tailendpacking = (vi->datalayout == EROFS_INODE_FLAT_INLINE); trace_erofs_map_blocks_flatmode_enter(inode, map, flags); nblocks = BLK_ROUND_UP(sbi, inode->i_size); lastblk = nblocks - tailendpacking; /* there is no hole in flatmode */ map->m_flags = EROFS_MAP_MAPPED; if (offset < erofs_pos(sbi, lastblk)) { map->m_pa = erofs_pos(sbi, vi->u.i_blkaddr) + map->m_la; map->m_plen = erofs_pos(sbi, lastblk) - offset; } else if (tailendpacking) { /* 2 - inode inline B: inode, [xattrs], inline last blk... */ map->m_pa = erofs_iloc(vi) + vi->inode_isize + vi->xattr_isize + erofs_blkoff(sbi, map->m_la); map->m_plen = inode->i_size - offset; /* inline data should be located in the same meta block */ if (erofs_blkoff(sbi, map->m_pa) + map->m_plen > erofs_blksiz(sbi)) { erofs_err("inline data cross block boundary @ nid %" PRIu64, vi->nid); DBG_BUGON(1); err = -EFSCORRUPTED; goto err_out; } map->m_flags |= EROFS_MAP_META; } else { erofs_err("internal error @ nid: %" PRIu64 " (size %llu), m_la 0x%" PRIx64, vi->nid, (unsigned long long)inode->i_size, map->m_la); DBG_BUGON(1); err = -EIO; goto err_out; } map->m_llen = map->m_plen; err_out: trace_erofs_map_blocks_flatmode_exit(inode, map, flags, 0); return err; } int __erofs_map_blocks(struct erofs_inode *inode, struct erofs_map_blocks *map, int flags) { struct erofs_inode *vi = inode; struct erofs_sb_info *sbi = inode->sbi; struct erofs_inode_chunk_index *idx; u8 buf[EROFS_MAX_BLOCK_SIZE]; u64 chunknr; unsigned int unit; erofs_off_t pos; int err = 0; map->m_deviceid = 0; if (map->m_la >= inode->i_size) { /* leave out-of-bound access unmapped */ map->m_flags = 0; map->m_plen = 0; goto out; } if (vi->datalayout != EROFS_INODE_CHUNK_BASED) return erofs_map_blocks_flatmode(inode, map, flags); if (vi->u.chunkformat & EROFS_CHUNK_FORMAT_INDEXES) unit = sizeof(*idx); /* chunk index */ else unit = EROFS_BLOCK_MAP_ENTRY_SIZE; /* block map */ chunknr = map->m_la >> vi->u.chunkbits; pos = roundup(erofs_iloc(vi) + vi->inode_isize + vi->xattr_isize, unit) + unit * chunknr; err = erofs_blk_read(sbi, 0, buf, erofs_blknr(sbi, pos), 1); if (err < 0) return -EIO; map->m_la = chunknr << vi->u.chunkbits; map->m_plen = min_t(erofs_off_t, 1UL << vi->u.chunkbits, roundup(inode->i_size - map->m_la, erofs_blksiz(sbi))); /* handle block map */ if (!(vi->u.chunkformat & EROFS_CHUNK_FORMAT_INDEXES)) { __le32 *blkaddr = (void *)buf + erofs_blkoff(sbi, pos); if (le32_to_cpu(*blkaddr) == EROFS_NULL_ADDR) { map->m_flags = 0; } else { map->m_pa = erofs_pos(sbi, le32_to_cpu(*blkaddr)); map->m_flags = EROFS_MAP_MAPPED; } goto out; } /* parse chunk indexes */ idx = (void *)buf + erofs_blkoff(sbi, pos); switch (le32_to_cpu(idx->blkaddr)) { case EROFS_NULL_ADDR: map->m_flags = 0; break; default: map->m_deviceid = le16_to_cpu(idx->device_id) & sbi->device_id_mask; map->m_pa = erofs_pos(sbi, le32_to_cpu(idx->blkaddr)); map->m_flags = EROFS_MAP_MAPPED; break; } out: map->m_llen = map->m_plen; return err; } int erofs_map_blocks(struct erofs_inode *inode, struct erofs_map_blocks *map, int flags) { if (erofs_inode_is_data_compressed(inode->datalayout)) return z_erofs_map_blocks_iter(inode, map, flags); return __erofs_map_blocks(inode, map, flags); } int erofs_map_dev(struct erofs_sb_info *sbi, struct erofs_map_dev *map) { struct erofs_device_info *dif; int id; if (map->m_deviceid) { if (sbi->extra_devices < map->m_deviceid) return -ENODEV; } else if (sbi->extra_devices) { for (id = 0; id < sbi->extra_devices; ++id) { erofs_off_t startoff, length; dif = sbi->devs + id; if (!dif->mapped_blkaddr) continue; startoff = erofs_pos(sbi, dif->mapped_blkaddr); length = erofs_pos(sbi, dif->blocks); if (map->m_pa >= startoff && map->m_pa < startoff + length) { map->m_pa -= startoff; break; } } } return 0; } int erofs_read_one_data(struct erofs_inode *inode, struct erofs_map_blocks *map, char *buffer, u64 offset, size_t len) { struct erofs_sb_info *sbi = inode->sbi; struct erofs_map_dev mdev; int ret; mdev = (struct erofs_map_dev) { .m_deviceid = map->m_deviceid, .m_pa = map->m_pa, }; ret = erofs_map_dev(sbi, &mdev); if (ret) return ret; ret = erofs_dev_read(sbi, mdev.m_deviceid, buffer, mdev.m_pa + offset, len); if (ret < 0) return -EIO; return 0; } static int erofs_read_raw_data(struct erofs_inode *inode, char *buffer, erofs_off_t size, erofs_off_t offset) { struct erofs_map_blocks map = { .index = UINT_MAX, }; int ret; erofs_off_t ptr = offset; while (ptr < offset + size) { char *const estart = buffer + ptr - offset; erofs_off_t eend, moff = 0; map.m_la = ptr; ret = erofs_map_blocks(inode, &map, 0); if (ret) return ret; DBG_BUGON(map.m_plen != map.m_llen); /* trim extent */ eend = min(offset + size, map.m_la + map.m_llen); DBG_BUGON(ptr < map.m_la); if (!(map.m_flags & EROFS_MAP_MAPPED)) { if (!map.m_llen) { /* reached EOF */ memset(estart, 0, offset + size - ptr); ptr = offset + size; continue; } memset(estart, 0, eend - ptr); ptr = eend; continue; } if (ptr > map.m_la) { moff = ptr - map.m_la; map.m_la = ptr; } ret = erofs_read_one_data(inode, &map, estart, moff, eend - map.m_la); if (ret) return ret; ptr = eend; } return 0; } int z_erofs_read_one_data(struct erofs_inode *inode, struct erofs_map_blocks *map, char *raw, char *buffer, erofs_off_t skip, erofs_off_t length, bool trimmed) { struct erofs_sb_info *sbi = inode->sbi; struct erofs_map_dev mdev; int ret = 0; if (map->m_flags & EROFS_MAP_FRAGMENT) { return erofs_packedfile_read(sbi, buffer, length - skip, inode->fragmentoff + skip); } /* no device id here, thus it will always succeed */ mdev = (struct erofs_map_dev) { .m_pa = map->m_pa, }; ret = erofs_map_dev(sbi, &mdev); if (ret) { DBG_BUGON(1); return ret; } ret = erofs_dev_read(sbi, mdev.m_deviceid, raw, mdev.m_pa, map->m_plen); if (ret < 0) return ret; ret = z_erofs_decompress(&(struct z_erofs_decompress_req) { .sbi = sbi, .in = raw, .out = buffer, .decodedskip = skip, .interlaced_offset = map->m_algorithmformat == Z_EROFS_COMPRESSION_INTERLACED ? erofs_blkoff(sbi, map->m_la) : 0, .inputsize = map->m_plen, .decodedlength = length, .alg = map->m_algorithmformat, .partial_decoding = trimmed ? true : !(map->m_flags & EROFS_MAP_FULL_MAPPED) || (map->m_flags & EROFS_MAP_PARTIAL_REF), }); if (ret < 0) return ret; return 0; } static int z_erofs_read_data(struct erofs_inode *inode, char *buffer, erofs_off_t size, erofs_off_t offset) { erofs_off_t end, length, skip; struct erofs_map_blocks map = { .index = UINT_MAX, }; bool trimmed; unsigned int bufsize = 0; char *raw = NULL; int ret = 0; end = offset + size; while (end > offset) { map.m_la = end - 1; ret = z_erofs_map_blocks_iter(inode, &map, 0); if (ret) break; /* * trim to the needed size if the returned extent is quite * larger than requested, and set up partial flag as well. */ if (end < map.m_la + map.m_llen) { length = end - map.m_la; trimmed = true; } else { DBG_BUGON(end != map.m_la + map.m_llen); length = map.m_llen; trimmed = false; } if (map.m_la < offset) { skip = offset - map.m_la; end = offset; } else { skip = 0; end = map.m_la; } if (!(map.m_flags & EROFS_MAP_MAPPED)) { memset(buffer + end - offset, 0, length - skip); end = map.m_la; continue; } if (map.m_plen > bufsize) { char *newraw; bufsize = map.m_plen; newraw = realloc(raw, bufsize); if (!newraw) { ret = -ENOMEM; break; } raw = newraw; } ret = z_erofs_read_one_data(inode, &map, raw, buffer + end - offset, skip, length, trimmed); if (ret < 0) break; } if (raw) free(raw); return ret < 0 ? ret : 0; } int erofs_pread(struct erofs_inode *inode, char *buf, erofs_off_t count, erofs_off_t offset) { switch (inode->datalayout) { case EROFS_INODE_FLAT_PLAIN: case EROFS_INODE_FLAT_INLINE: case EROFS_INODE_CHUNK_BASED: return erofs_read_raw_data(inode, buf, count, offset); case EROFS_INODE_COMPRESSED_FULL: case EROFS_INODE_COMPRESSED_COMPACT: return z_erofs_read_data(inode, buf, count, offset); default: break; } return -EINVAL; } static void *erofs_read_metadata_nid(struct erofs_sb_info *sbi, erofs_nid_t nid, erofs_off_t *offset, int *lengthp) { struct erofs_inode vi = { .sbi = sbi, .nid = nid }; __le16 __len; int ret, len; char *buffer; ret = erofs_read_inode_from_disk(&vi); if (ret) return ERR_PTR(ret); *offset = round_up(*offset, 4); ret = erofs_pread(&vi, (void *)&__len, sizeof(__le16), *offset); if (ret) return ERR_PTR(ret); len = le16_to_cpu(__len); if (!len) return ERR_PTR(-EFSCORRUPTED); buffer = malloc(len); if (!buffer) return ERR_PTR(-ENOMEM); *offset += sizeof(__le16); *lengthp = len; ret = erofs_pread(&vi, buffer, len, *offset); if (ret) { free(buffer); return ERR_PTR(ret); } *offset += len; return buffer; } static void *erofs_read_metadata_bdi(struct erofs_sb_info *sbi, erofs_off_t *offset, int *lengthp) { int ret, len, i, cnt; void *buffer; u8 data[EROFS_MAX_BLOCK_SIZE]; *offset = round_up(*offset, 4); ret = erofs_blk_read(sbi, 0, data, erofs_blknr(sbi, *offset), 1); if (ret) return ERR_PTR(ret); len = le16_to_cpu(*(__le16 *)(data + erofs_blkoff(sbi, *offset))); if (!len) return ERR_PTR(-EFSCORRUPTED); buffer = malloc(len); if (!buffer) return ERR_PTR(-ENOMEM); *offset += sizeof(__le16); *lengthp = len; for (i = 0; i < len; i += cnt) { cnt = min_t(int, erofs_blksiz(sbi) - erofs_blkoff(sbi, *offset), len - i); ret = erofs_blk_read(sbi, 0, data, erofs_blknr(sbi, *offset), 1); if (ret) { free(buffer); return ERR_PTR(ret); } memcpy(buffer + i, data + erofs_blkoff(sbi, *offset), cnt); *offset += cnt; } return buffer; } /* * read variable-sized metadata, offset will be aligned by 4-byte * * @nid is 0 if metadata is in meta inode */ void *erofs_read_metadata(struct erofs_sb_info *sbi, erofs_nid_t nid, erofs_off_t *offset, int *lengthp) { if (nid) return erofs_read_metadata_nid(sbi, nid, offset, lengthp); return erofs_read_metadata_bdi(sbi, offset, lengthp); } erofs-utils-1.8.6/lib/decompress.c000066400000000000000000000355541477440110200170740ustar00rootroot00000000000000// SPDX-License-Identifier: GPL-2.0+ OR Apache-2.0 /* * Copyright (C), 2008-2020, OPPO Mobile Comm Corp., Ltd. * Created by Huang Jianan */ #include #include "erofs/decompress.h" #include "erofs/err.h" #include "erofs/print.h" static unsigned int z_erofs_fixup_insize(const u8 *padbuf, unsigned int padbufsize) { unsigned int inputmargin; for (inputmargin = 0; inputmargin < padbufsize && !padbuf[inputmargin]; ++inputmargin); return inputmargin; } #ifdef HAVE_LIBZSTD #include #include /* also a very preliminary userspace version */ static int z_erofs_decompress_zstd(struct z_erofs_decompress_req *rq) { int ret = 0; char *dest = rq->out; char *src = rq->in; char *buff = NULL; unsigned int inputmargin = 0; unsigned long long total; inputmargin = z_erofs_fixup_insize((u8 *)src, rq->inputsize); if (inputmargin >= rq->inputsize) return -EFSCORRUPTED; #ifdef HAVE_ZSTD_GETFRAMECONTENTSIZE total = ZSTD_getFrameContentSize(src + inputmargin, rq->inputsize - inputmargin); if (total == ZSTD_CONTENTSIZE_UNKNOWN || total == ZSTD_CONTENTSIZE_ERROR) return -EFSCORRUPTED; #else total = ZSTD_getDecompressedSize(src + inputmargin, rq->inputsize - inputmargin); #endif if (rq->decodedskip || total != rq->decodedlength) { buff = malloc(total); if (!buff) return -ENOMEM; dest = buff; } ret = ZSTD_decompress(dest, total, src + inputmargin, rq->inputsize - inputmargin); if (ZSTD_isError(ret)) { erofs_err("ZSTD decompress failed %d: %s", ZSTD_getErrorCode(ret), ZSTD_getErrorName(ret)); ret = -EIO; goto out; } if (ret != (int)total) { erofs_err("ZSTD decompress length mismatch %d, expected %d", ret, total); goto out; } if (rq->decodedskip || total != rq->decodedlength) memcpy(rq->out, dest + rq->decodedskip, rq->decodedlength - rq->decodedskip); out: if (buff) free(buff); return ret; } #endif #ifdef HAVE_QPL #include struct z_erofs_qpl_job { struct z_erofs_qpl_job *next; u8 job[]; }; static struct z_erofs_qpl_job *z_erofs_qpl_jobs; static unsigned int z_erofs_qpl_reclaim_quot; #ifdef HAVE_PTHREAD_H static pthread_mutex_t z_erofs_qpl_mutex; #endif int z_erofs_load_deflate_config(struct erofs_sb_info *sbi, struct erofs_super_block *dsb, void *data, int size) { struct z_erofs_deflate_cfgs *dfl = data; static erofs_atomic_bool_t inited; if (!dfl || size < sizeof(struct z_erofs_deflate_cfgs)) { erofs_err("invalid deflate cfgs, size=%u", size); return -EINVAL; } /* * In Intel QPL, decompression is supported for DEFLATE streams where * the size of the history buffer is no more than 4 KiB, otherwise * QPL_STS_BAD_DIST_ERR code is returned. */ sbi->useqpl = (dfl->windowbits <= 12); if (sbi->useqpl) { if (!erofs_atomic_test_and_set(&inited)) z_erofs_qpl_reclaim_quot = erofs_get_available_processors(); erofs_info("Intel QPL will be used for DEFLATE decompression"); } return 0; } static qpl_job *z_erofs_qpl_get_job(void) { qpl_path_t execution_path = qpl_path_auto; struct z_erofs_qpl_job *job; int32_t jobsize = 0; qpl_status status; #ifdef HAVE_PTHREAD_H pthread_mutex_lock(&z_erofs_qpl_mutex); #endif job = z_erofs_qpl_jobs; if (job) z_erofs_qpl_jobs = job->next; #ifdef HAVE_PTHREAD_H pthread_mutex_unlock(&z_erofs_qpl_mutex); #endif if (!job) { status = qpl_get_job_size(execution_path, &jobsize); if (status != QPL_STS_OK) { erofs_err("failed to get job size: %d", status); return ERR_PTR(-EOPNOTSUPP); } job = malloc(jobsize + sizeof(struct z_erofs_qpl_job)); if (!job) return ERR_PTR(-ENOMEM); status = qpl_init_job(execution_path, (qpl_job *)job->job); if (status != QPL_STS_OK) { erofs_err("failed to initialize job: %d", status); return ERR_PTR(-EOPNOTSUPP); } erofs_atomic_dec_return(&z_erofs_qpl_reclaim_quot); } return (qpl_job *)job->job; } static bool z_erofs_qpl_put_job(qpl_job *qjob) { struct z_erofs_qpl_job *job = container_of((void *)qjob, struct z_erofs_qpl_job, job); if (erofs_atomic_inc_return(&z_erofs_qpl_reclaim_quot) <= 0) { qpl_status status = qpl_fini_job(qjob); free(job); if (status != QPL_STS_OK) erofs_err("failed to finalize job: %d", status); return status == QPL_STS_OK; } #ifdef HAVE_PTHREAD_H pthread_mutex_lock(&z_erofs_qpl_mutex); #endif job->next = z_erofs_qpl_jobs; z_erofs_qpl_jobs = job; #ifdef HAVE_PTHREAD_H pthread_mutex_unlock(&z_erofs_qpl_mutex); #endif return true; } static int z_erofs_decompress_qpl(struct z_erofs_decompress_req *rq) { u8 *dest = (u8 *)rq->out; u8 *src = (u8 *)rq->in; u8 *buff = NULL; unsigned int inputmargin; qpl_status status; qpl_job *job; int ret; job = z_erofs_qpl_get_job(); if (IS_ERR(job)) return PTR_ERR(job); inputmargin = z_erofs_fixup_insize(src, rq->inputsize); if (inputmargin >= rq->inputsize) return -EFSCORRUPTED; if (rq->decodedskip) { buff = malloc(rq->decodedlength); if (!buff) return -ENOMEM; dest = buff; } job->op = qpl_op_decompress; job->next_in_ptr = src + inputmargin; job->next_out_ptr = dest; job->available_in = rq->inputsize - inputmargin; job->available_out = rq->decodedlength; job->flags = QPL_FLAG_FIRST | QPL_FLAG_LAST; status = qpl_execute_job(job); if (status != QPL_STS_OK) { erofs_err("failed to decompress: %d", status); ret = -EIO; goto out_inflate_end; } if (rq->decodedskip) memcpy(rq->out, dest + rq->decodedskip, rq->decodedlength - rq->decodedskip); ret = 0; out_inflate_end: if (!z_erofs_qpl_put_job(job)) ret = -EFAULT; if (buff) free(buff); return ret; } #else int z_erofs_load_deflate_config(struct erofs_sb_info *sbi, struct erofs_super_block *dsb, void *data, int size) { return 0; } #endif #ifdef HAVE_LIBDEFLATE /* if libdeflate is available, use libdeflate instead. */ #include static int z_erofs_decompress_deflate(struct z_erofs_decompress_req *rq) { u8 *dest = (u8 *)rq->out; u8 *src = (u8 *)rq->in; u8 *buff = NULL; size_t actual_out; unsigned int inputmargin; struct libdeflate_decompressor *inf; enum libdeflate_result ret; unsigned int decodedcapacity; inputmargin = z_erofs_fixup_insize(src, rq->inputsize); if (inputmargin >= rq->inputsize) return -EFSCORRUPTED; decodedcapacity = rq->decodedlength << (4 * rq->partial_decoding); if (rq->decodedskip || rq->partial_decoding) { buff = malloc(decodedcapacity); if (!buff) return -ENOMEM; dest = buff; } inf = libdeflate_alloc_decompressor(); if (!inf) { ret = -ENOMEM; goto out_free_mem; } if (rq->partial_decoding) { while (1) { ret = libdeflate_deflate_decompress(inf, src + inputmargin, rq->inputsize - inputmargin, dest, decodedcapacity, &actual_out); if (ret == LIBDEFLATE_SUCCESS) break; if (ret != LIBDEFLATE_INSUFFICIENT_SPACE) { ret = -EIO; goto out_inflate_end; } decodedcapacity = decodedcapacity << 1; dest = realloc(buff, decodedcapacity); if (!dest) { ret = -ENOMEM; goto out_inflate_end; } buff = dest; } if (actual_out < rq->decodedlength) { ret = -EIO; goto out_inflate_end; } } else { ret = libdeflate_deflate_decompress(inf, src + inputmargin, rq->inputsize - inputmargin, dest, rq->decodedlength, NULL); if (ret != LIBDEFLATE_SUCCESS) { ret = -EIO; goto out_inflate_end; } } if (rq->decodedskip || rq->partial_decoding) memcpy(rq->out, dest + rq->decodedskip, rq->decodedlength - rq->decodedskip); out_inflate_end: libdeflate_free_decompressor(inf); out_free_mem: if (buff) free(buff); return ret; } #elif defined(HAVE_ZLIB) #include /* report a zlib or i/o error */ static int zerr(int ret) { switch (ret) { case Z_STREAM_ERROR: return -EINVAL; case Z_DATA_ERROR: return -EIO; case Z_MEM_ERROR: return -ENOMEM; case Z_ERRNO: case Z_VERSION_ERROR: default: return -EFAULT; } } static int z_erofs_decompress_deflate(struct z_erofs_decompress_req *rq) { u8 *dest = (u8 *)rq->out; u8 *src = (u8 *)rq->in; u8 *buff = NULL; unsigned int inputmargin; z_stream strm; int ret; inputmargin = z_erofs_fixup_insize(src, rq->inputsize); if (inputmargin >= rq->inputsize) return -EFSCORRUPTED; if (rq->decodedskip) { buff = malloc(rq->decodedlength); if (!buff) return -ENOMEM; dest = buff; } /* allocate inflate state */ strm.zalloc = Z_NULL; strm.zfree = Z_NULL; strm.opaque = Z_NULL; strm.avail_in = 0; strm.next_in = Z_NULL; ret = inflateInit2(&strm, -15); if (ret != Z_OK) { free(buff); return zerr(ret); } strm.next_in = src + inputmargin; strm.avail_in = rq->inputsize - inputmargin; strm.next_out = dest; strm.avail_out = rq->decodedlength; ret = inflate(&strm, rq->partial_decoding ? Z_SYNC_FLUSH : Z_FINISH); if (ret != Z_STREAM_END || strm.total_out != rq->decodedlength) { if (ret != Z_OK || !rq->partial_decoding) { ret = zerr(ret); goto out_inflate_end; } } if (rq->decodedskip) memcpy(rq->out, dest + rq->decodedskip, rq->decodedlength - rq->decodedskip); out_inflate_end: inflateEnd(&strm); if (buff) free(buff); return ret; } #endif #ifdef HAVE_LIBLZMA #include static int z_erofs_decompress_lzma(struct z_erofs_decompress_req *rq) { int ret = 0; u8 *dest = (u8 *)rq->out; u8 *src = (u8 *)rq->in; u8 *buff = NULL; unsigned int inputmargin; lzma_stream strm; lzma_ret ret2; inputmargin = z_erofs_fixup_insize(src, rq->inputsize); if (inputmargin >= rq->inputsize) return -EFSCORRUPTED; if (rq->decodedskip) { buff = malloc(rq->decodedlength); if (!buff) return -ENOMEM; dest = buff; } strm = (lzma_stream)LZMA_STREAM_INIT; strm.next_in = src + inputmargin; strm.avail_in = rq->inputsize - inputmargin; strm.next_out = dest; strm.avail_out = rq->decodedlength; ret2 = lzma_microlzma_decoder(&strm, strm.avail_in, rq->decodedlength, !rq->partial_decoding, Z_EROFS_LZMA_MAX_DICT_SIZE); if (ret2 != LZMA_OK) { erofs_err("fail to initialize lzma decoder %u", ret2 | 0U); ret = -EFAULT; goto out; } ret2 = lzma_code(&strm, LZMA_FINISH); if (ret2 != LZMA_STREAM_END) { ret = -EFSCORRUPTED; goto out_lzma_end; } if (rq->decodedskip) memcpy(rq->out, dest + rq->decodedskip, rq->decodedlength - rq->decodedskip); out_lzma_end: lzma_end(&strm); out: if (buff) free(buff); return ret; } #endif #ifdef LZ4_ENABLED #include static int z_erofs_decompress_lz4(struct z_erofs_decompress_req *rq) { int ret = 0; char *dest = rq->out; char *src = rq->in; char *buff = NULL; bool support_0padding = false; unsigned int inputmargin = 0; struct erofs_sb_info *sbi = rq->sbi; if (erofs_sb_has_lz4_0padding(sbi)) { support_0padding = true; inputmargin = z_erofs_fixup_insize((u8 *)src, rq->inputsize); if (inputmargin >= rq->inputsize) return -EFSCORRUPTED; } if (rq->decodedskip) { buff = malloc(rq->decodedlength); if (!buff) return -ENOMEM; dest = buff; } if (rq->partial_decoding || !support_0padding) ret = LZ4_decompress_safe_partial(src + inputmargin, dest, rq->inputsize - inputmargin, rq->decodedlength, rq->decodedlength); else ret = LZ4_decompress_safe(src + inputmargin, dest, rq->inputsize - inputmargin, rq->decodedlength); if (ret != (int)rq->decodedlength) { erofs_err("failed to %s decompress %d in[%u, %u] out[%u]", rq->partial_decoding ? "partial" : "full", ret, rq->inputsize, inputmargin, rq->decodedlength); ret = -EIO; goto out; } if (rq->decodedskip) memcpy(rq->out, dest + rq->decodedskip, rq->decodedlength - rq->decodedskip); out: if (buff) free(buff); return ret; } #endif int z_erofs_decompress(struct z_erofs_decompress_req *rq) { struct erofs_sb_info *sbi = rq->sbi; if (rq->alg == Z_EROFS_COMPRESSION_INTERLACED) { unsigned int count, rightpart, skip; /* XXX: should support inputsize >= erofs_blksiz(sbi) later */ if (rq->inputsize > erofs_blksiz(sbi)) return -EFSCORRUPTED; if (rq->decodedlength > erofs_blksiz(sbi)) return -EFSCORRUPTED; if (rq->decodedlength < rq->decodedskip) return -EFSCORRUPTED; count = rq->decodedlength - rq->decodedskip; skip = erofs_blkoff(sbi, rq->interlaced_offset + rq->decodedskip); rightpart = min(erofs_blksiz(sbi) - skip, count); memcpy(rq->out, rq->in + skip, rightpart); memcpy(rq->out + rightpart, rq->in, count - rightpart); return 0; } else if (rq->alg == Z_EROFS_COMPRESSION_SHIFTED) { if (rq->decodedlength > rq->inputsize) return -EFSCORRUPTED; DBG_BUGON(rq->decodedlength < rq->decodedskip); memcpy(rq->out, rq->in + rq->decodedskip, rq->decodedlength - rq->decodedskip); return 0; } #ifdef LZ4_ENABLED if (rq->alg == Z_EROFS_COMPRESSION_LZ4) return z_erofs_decompress_lz4(rq); #endif #ifdef HAVE_LIBLZMA if (rq->alg == Z_EROFS_COMPRESSION_LZMA) return z_erofs_decompress_lzma(rq); #endif #ifdef HAVE_QPL if (rq->alg == Z_EROFS_COMPRESSION_DEFLATE && rq->sbi->useqpl) if (!z_erofs_decompress_qpl(rq)) return 0; #endif #if defined(HAVE_ZLIB) || defined(HAVE_LIBDEFLATE) if (rq->alg == Z_EROFS_COMPRESSION_DEFLATE) return z_erofs_decompress_deflate(rq); #endif #ifdef HAVE_LIBZSTD if (rq->alg == Z_EROFS_COMPRESSION_ZSTD) return z_erofs_decompress_zstd(rq); #endif return -EOPNOTSUPP; } static int z_erofs_load_lz4_config(struct erofs_sb_info *sbi, struct erofs_super_block *dsb, void *data, int size) { struct z_erofs_lz4_cfgs *lz4 = data; u16 distance; if (lz4) { if (size < sizeof(struct z_erofs_lz4_cfgs)) { erofs_err("invalid lz4 cfgs, size=%u", size); return -EINVAL; } distance = le16_to_cpu(lz4->max_distance); sbi->lz4.max_pclusterblks = le16_to_cpu(lz4->max_pclusterblks); if (!sbi->lz4.max_pclusterblks) sbi->lz4.max_pclusterblks = 1; /* reserved case */ } else { distance = le16_to_cpu(dsb->u1.lz4_max_distance); sbi->lz4.max_pclusterblks = 1; } sbi->lz4.max_distance = distance; return 0; } int z_erofs_parse_cfgs(struct erofs_sb_info *sbi, struct erofs_super_block *dsb) { unsigned int algs, alg; erofs_off_t offset; int size, ret = 0; if (!erofs_sb_has_compr_cfgs(sbi)) { sbi->available_compr_algs = 1 << Z_EROFS_COMPRESSION_LZ4; return z_erofs_load_lz4_config(sbi, dsb, NULL, 0); } sbi->available_compr_algs = le16_to_cpu(dsb->u1.available_compr_algs); if (sbi->available_compr_algs & ~Z_EROFS_ALL_COMPR_ALGS) { erofs_err("unidentified algorithms %x, please upgrade erofs-utils", sbi->available_compr_algs & ~Z_EROFS_ALL_COMPR_ALGS); return -EOPNOTSUPP; } offset = EROFS_SUPER_OFFSET + sbi->sb_size; alg = 0; for (algs = sbi->available_compr_algs; algs; algs >>= 1, ++alg) { void *data; if (!(algs & 1)) continue; data = erofs_read_metadata(sbi, 0, &offset, &size); if (IS_ERR(data)) { ret = PTR_ERR(data); break; } ret = 0; if (alg == Z_EROFS_COMPRESSION_LZ4) ret = z_erofs_load_lz4_config(sbi, dsb, data, size); else if (alg == Z_EROFS_COMPRESSION_DEFLATE) ret = z_erofs_load_deflate_config(sbi, dsb, data, size); free(data); if (ret) break; } return ret; } erofs-utils-1.8.6/lib/dedupe.c000066400000000000000000000116361477440110200161710ustar00rootroot00000000000000// SPDX-License-Identifier: GPL-2.0+ OR Apache-2.0 /* * Copyright (C) 2022 Alibaba Cloud */ #include #include "erofs/dedupe.h" #include "erofs/print.h" #include "rolling_hash.h" #include "liberofs_xxhash.h" #include "sha256.h" unsigned long erofs_memcmp2(const u8 *s1, const u8 *s2, unsigned long sz) { const unsigned long *a1, *a2; unsigned long n = sz; if (sz < sizeof(long)) goto out_bytes; if (((long)s1 & (sizeof(long) - 1)) == ((long)s2 & (sizeof(long) - 1))) { while ((long)s1 & (sizeof(long) - 1)) { if (*s1 != *s2) break; ++s1; ++s2; --sz; } a1 = (const unsigned long *)s1; a2 = (const unsigned long *)s2; while (sz >= sizeof(long)) { if (*a1 != *a2) break; ++a1; ++a2; sz -= sizeof(long); } } else { a1 = (const unsigned long *)s1; a2 = (const unsigned long *)s2; do { if (get_unaligned(a1) != get_unaligned(a2)) break; ++a1; ++a2; sz -= sizeof(long); } while (sz >= sizeof(long)); } s1 = (const u8 *)a1; s2 = (const u8 *)a2; out_bytes: while (sz) { if (*s1 != *s2) break; ++s1; ++s2; --sz; } return n - sz; } static unsigned int window_size, rollinghash_rm; static struct list_head dedupe_tree[65536]; struct z_erofs_dedupe_item *dedupe_subtree; struct z_erofs_dedupe_item { struct list_head list; struct z_erofs_dedupe_item *chain; long long hash; u8 prefix_sha256[32]; u64 prefix_xxh64; erofs_off_t pstart; unsigned int plen; int original_length; bool partial, raw; u8 extra_data[]; }; int z_erofs_dedupe_match(struct z_erofs_dedupe_ctx *ctx) { struct z_erofs_dedupe_item e_find; u8 *cur; bool initial = true; if (!window_size) return -ENOENT; if (ctx->cur > ctx->end - window_size) cur = ctx->end - window_size; else cur = ctx->cur; /* move backward byte-by-byte */ for (; cur >= ctx->start; --cur) { struct list_head *p; struct z_erofs_dedupe_item *e; unsigned int extra = 0; u64 xxh64_csum = 0; u8 sha256[32]; if (initial) { /* initial try */ e_find.hash = erofs_rolling_hash_init(cur, window_size, true); initial = false; } else { e_find.hash = erofs_rolling_hash_advance(e_find.hash, rollinghash_rm, cur[window_size], cur[0]); } p = &dedupe_tree[e_find.hash & (ARRAY_SIZE(dedupe_tree) - 1)]; list_for_each_entry(e, p, list) { if (e->hash != e_find.hash) continue; if (!extra) { xxh64_csum = xxh64(cur, window_size, 0); extra = 1; } if (e->prefix_xxh64 == xxh64_csum) break; } if (&e->list == p) continue; erofs_sha256(cur, window_size, sha256); if (memcmp(sha256, e->prefix_sha256, sizeof(sha256))) continue; extra = min_t(unsigned int, ctx->end - cur - window_size, e->original_length - window_size); extra = erofs_memcmp2(cur + window_size, e->extra_data, extra); if (window_size + extra <= ctx->cur - cur) continue; ctx->cur = cur; ctx->e.length = window_size + extra; ctx->e.partial = e->partial || (window_size + extra < e->original_length); ctx->e.raw = e->raw; ctx->e.inlined = false; ctx->e.pstart = e->pstart; ctx->e.plen = e->plen; return 0; } return -ENOENT; } int z_erofs_dedupe_insert(struct z_erofs_inmem_extent *e, void *original_data) { struct list_head *p; struct z_erofs_dedupe_item *di, *k; if (!window_size || e->length < window_size) return 0; di = malloc(sizeof(*di) + e->length - window_size); if (!di) return -ENOMEM; di->original_length = e->length; erofs_sha256(original_data, window_size, di->prefix_sha256); di->prefix_xxh64 = xxh64(original_data, window_size, 0); di->hash = erofs_rolling_hash_init(original_data, window_size, true); memcpy(di->extra_data, original_data + window_size, e->length - window_size); di->pstart = e->pstart; di->plen = e->plen; di->partial = e->partial; di->raw = e->raw; /* skip the same xxh64 hash */ p = &dedupe_tree[di->hash & (ARRAY_SIZE(dedupe_tree) - 1)]; list_for_each_entry(k, p, list) { if (k->prefix_xxh64 == di->prefix_xxh64) { free(di); return 0; } } di->chain = dedupe_subtree; dedupe_subtree = di; list_add_tail(&di->list, p); return 0; } void z_erofs_dedupe_commit(bool drop) { if (!dedupe_subtree) return; if (drop) { struct z_erofs_dedupe_item *di, *n; for (di = dedupe_subtree; di; di = n) { n = di->chain; list_del(&di->list); free(di); } } dedupe_subtree = NULL; } int z_erofs_dedupe_init(unsigned int wsiz) { struct list_head *p; for (p = dedupe_tree; p < dedupe_tree + ARRAY_SIZE(dedupe_tree); ++p) init_list_head(p); window_size = wsiz; rollinghash_rm = erofs_rollinghash_calc_rm(window_size); return 0; } void z_erofs_dedupe_exit(void) { struct z_erofs_dedupe_item *di, *n; struct list_head *p; if (!window_size) return; z_erofs_dedupe_commit(true); for (p = dedupe_tree; p < dedupe_tree + ARRAY_SIZE(dedupe_tree); ++p) { list_for_each_entry_safe(di, n, p, list) { list_del(&di->list); free(di); } } dedupe_subtree = NULL; } erofs-utils-1.8.6/lib/dedupe_ext.c000066400000000000000000000042541477440110200170470ustar00rootroot00000000000000// SPDX-License-Identifier: GPL-2.0+ OR Apache-2.0 #include "erofs/dedupe.h" #include "liberofs_xxhash.h" #include struct z_erofs_dedupe_ext_item { struct list_head list; struct z_erofs_dedupe_ext_item *revoke; struct z_erofs_inmem_extent e; u64 xxh64; }; static struct list_head dupl_ext[65536]; static struct z_erofs_dedupe_ext_item *revoke_list; int z_erofs_dedupe_ext_insert(struct z_erofs_inmem_extent *e, u64 hash) { struct z_erofs_dedupe_ext_item *item; struct list_head *p; item = malloc(sizeof(struct z_erofs_dedupe_ext_item)); if (!item) return -ENOMEM; item->e = *e; item->xxh64 = hash; p = &dupl_ext[hash & (ARRAY_SIZE(dupl_ext) - 1)]; list_add_tail(&item->list, p); item->revoke = revoke_list; revoke_list = item; return 0; } erofs_blk_t z_erofs_dedupe_ext_match(struct erofs_sb_info *sbi, u8 *encoded, unsigned int len, bool raw, u64 *hash) { struct z_erofs_dedupe_ext_item *item; struct list_head *p; u64 _xxh64; char *memb; int ret; *hash = _xxh64 = xxh64(encoded, len, 0); p = &dupl_ext[_xxh64 & (ARRAY_SIZE(dupl_ext) - 1)]; list_for_each_entry(item, p, list) { if (item->xxh64 == _xxh64 && item->e.plen == len && item->e.raw == raw) { memb = malloc(len); if (!memb) break; ret = erofs_dev_read(sbi, 0, memb, item->e.pstart, len); if (ret < 0 || memcmp(memb, encoded, len)) { free(memb); break; } free(memb); return item->e.pstart; } } return EROFS_NULL_ADDR; } void z_erofs_dedupe_ext_commit(bool drop) { if (drop) { struct z_erofs_dedupe_ext_item *item, *n; for (item = revoke_list; item; item = n) { n = item->revoke; list_del(&item->list); free(item); } } revoke_list = NULL; } int z_erofs_dedupe_ext_init(void) { struct list_head *p; for (p = dupl_ext; p < dupl_ext + ARRAY_SIZE(dupl_ext); ++p) init_list_head(p); return 0; } void z_erofs_dedupe_ext_exit(void) { struct z_erofs_dedupe_ext_item *item, *n; struct list_head *p; if (!dupl_ext[0].next) return; z_erofs_dedupe_commit(true); for (p = dupl_ext; p < dupl_ext + ARRAY_SIZE(dupl_ext); ++p) { list_for_each_entry_safe(item, n, p, list) { list_del(&item->list); free(item); } } } erofs-utils-1.8.6/lib/dir.c000066400000000000000000000161351477440110200155000ustar00rootroot00000000000000// SPDX-License-Identifier: GPL-2.0+ OR Apache-2.0 #include #include #include "erofs/print.h" #include "erofs/dir.h" /* filename should not have a '/' in the name string */ static bool erofs_validate_filename(const char *dname, int size) { char *name = (char *)dname; while (name - dname < size && *name != '\0') { if (*name == '/') return false; ++name; } return true; } static int traverse_dirents(struct erofs_dir_context *ctx, void *dentry_blk, unsigned int lblk, unsigned int next_nameoff, unsigned int maxsize, bool fsck) { struct erofs_sb_info *sbi = ctx->dir->sbi; struct erofs_dirent *de = dentry_blk; const struct erofs_dirent *end = dentry_blk + next_nameoff; const char *prev_name = NULL; const char *errmsg; unsigned int prev_namelen = 0; int ret = 0; bool silent = false; while (de < end) { const char *de_name; unsigned int de_namelen; unsigned int nameoff; nameoff = le16_to_cpu(de->nameoff); de_name = (char *)dentry_blk + nameoff; /* the last dirent check */ if (de + 1 >= end) de_namelen = strnlen(de_name, maxsize - nameoff); else de_namelen = le16_to_cpu(de[1].nameoff) - nameoff; ctx->de_nid = le64_to_cpu(de->nid); erofs_dbg("traversed nid (%llu)", ctx->de_nid | 0ULL); ret = -EFSCORRUPTED; /* corrupted entry check */ if (nameoff != next_nameoff) { errmsg = "bogus dirent nameoff"; break; } if (nameoff + de_namelen > maxsize || !de_namelen || de_namelen > EROFS_NAME_LEN) { errmsg = "bogus dirent namelen"; break; } if (fsck && prev_name) { int cmp = strncmp(prev_name, de_name, min(prev_namelen, de_namelen)); if (cmp > 0 || (cmp == 0 && prev_namelen >= de_namelen)) { errmsg = "wrong dirent name order"; break; } } if (fsck && de->file_type >= EROFS_FT_MAX) { errmsg = "invalid file type %u"; break; } ctx->dname = de_name; ctx->de_namelen = de_namelen; ctx->de_ftype = de->file_type; ctx->dot_dotdot = is_dot_dotdot_len(de_name, de_namelen); if (ctx->dot_dotdot) { switch (de_namelen) { case 2: if (fsck && (ctx->flags & EROFS_READDIR_DOTDOT_FOUND)) { errmsg = "duplicated `..' dirent"; goto out; } ctx->flags |= EROFS_READDIR_DOTDOT_FOUND; if (sbi->root_nid == ctx->dir->nid) { ctx->pnid = sbi->root_nid; ctx->flags |= EROFS_READDIR_VALID_PNID; } if (fsck && (ctx->flags & EROFS_READDIR_VALID_PNID) && ctx->de_nid != ctx->pnid) { errmsg = "corrupted `..' dirent"; goto out; } break; case 1: if (fsck && (ctx->flags & EROFS_READDIR_DOT_FOUND)) { errmsg = "duplicated `.' dirent"; goto out; } ctx->flags |= EROFS_READDIR_DOT_FOUND; if (fsck && ctx->de_nid != ctx->dir->nid) { errmsg = "corrupted `.' dirent"; goto out; } break; } } else if (fsck && !erofs_validate_filename(de_name, de_namelen)) { errmsg = "corrupted dirent with illegal filename"; goto out; } ret = ctx->cb(ctx); if (ret) { silent = true; break; } prev_name = de_name; prev_namelen = de_namelen; next_nameoff += de_namelen; ++de; } out: if (ret && !silent) erofs_err("%s @ nid %llu, lblk %u, index %lu", errmsg, ctx->dir->nid | 0ULL, lblk, (de - (struct erofs_dirent *)dentry_blk) | 0UL); return ret; } int erofs_iterate_dir(struct erofs_dir_context *ctx, bool fsck) { struct erofs_inode *dir = ctx->dir; struct erofs_sb_info *sbi = dir->sbi; int err = 0; erofs_off_t pos; char buf[EROFS_MAX_BLOCK_SIZE]; if (!S_ISDIR(dir->i_mode)) return -ENOTDIR; ctx->flags &= ~EROFS_READDIR_ALL_SPECIAL_FOUND; pos = 0; while (pos < dir->i_size) { erofs_blk_t lblk = erofs_blknr(sbi, pos); erofs_off_t maxsize = min_t(erofs_off_t, dir->i_size - pos, erofs_blksiz(sbi)); const struct erofs_dirent *de = (const void *)buf; unsigned int nameoff; err = erofs_pread(dir, buf, maxsize, pos); if (err) { erofs_err("I/O error occurred when reading dirents @ nid %llu, lblk %u: %d", dir->nid | 0ULL, lblk, err); return err; } nameoff = le16_to_cpu(de->nameoff); if (nameoff < sizeof(struct erofs_dirent) || nameoff >= erofs_blksiz(sbi)) { erofs_err("invalid de[0].nameoff %u @ nid %llu, lblk %u", nameoff, dir->nid | 0ULL, lblk); return -EFSCORRUPTED; } err = traverse_dirents(ctx, buf, lblk, nameoff, maxsize, fsck); if (err) break; pos += maxsize; } if (fsck && (ctx->flags & EROFS_READDIR_ALL_SPECIAL_FOUND) != EROFS_READDIR_ALL_SPECIAL_FOUND && !err) { erofs_err("`.' or `..' dirent is missing @ nid %llu", dir->nid | 0ULL); return -EFSCORRUPTED; } return err; } #define EROFS_PATHNAME_FOUND 1 struct erofs_get_pathname_context { struct erofs_dir_context ctx; erofs_nid_t target_nid; char *buf; size_t size; size_t pos; }; static int erofs_get_pathname_iter(struct erofs_dir_context *ctx) { int ret; struct erofs_get_pathname_context *pathctx = (void *)ctx; const char *dname = ctx->dname; size_t len = ctx->de_namelen; size_t pos = pathctx->pos; if (ctx->dot_dotdot) return 0; if (ctx->de_nid == pathctx->target_nid) { if (pos + len + 2 > pathctx->size) { erofs_err("get_pathname buffer not large enough: len %zd, size %zd", pos + len + 2, pathctx->size); return -ERANGE; } pathctx->buf[pos++] = '/'; strncpy(pathctx->buf + pos, dname, len); pathctx->buf[pos + len] = '\0'; return EROFS_PATHNAME_FOUND; } if (ctx->de_ftype == EROFS_FT_DIR || ctx->de_ftype == EROFS_FT_UNKNOWN) { struct erofs_inode dir = { .sbi = ctx->dir->sbi, .nid = ctx->de_nid }; ret = erofs_read_inode_from_disk(&dir); if (ret) { erofs_err("read inode failed @ nid %llu", dir.nid | 0ULL); return ret; } if (S_ISDIR(dir.i_mode)) { struct erofs_get_pathname_context nctx = { .ctx.flags = 0, .ctx.dir = &dir, .ctx.cb = erofs_get_pathname_iter, .target_nid = pathctx->target_nid, .buf = pathctx->buf, .size = pathctx->size, .pos = pos + len + 1, }; ret = erofs_iterate_dir(&nctx.ctx, false); if (ret == EROFS_PATHNAME_FOUND) { pathctx->buf[pos++] = '/'; strncpy(pathctx->buf + pos, dname, len); } return ret; } else if (ctx->de_ftype == EROFS_FT_DIR) { erofs_err("i_mode and file_type are inconsistent @ nid %llu", dir.nid | 0ULL); } } return 0; } int erofs_get_pathname(struct erofs_sb_info *sbi, erofs_nid_t nid, char *buf, size_t size) { int ret; struct erofs_inode root = { .sbi = sbi, .nid = sbi->root_nid, }; struct erofs_get_pathname_context pathctx = { .ctx.flags = 0, .ctx.dir = &root, .ctx.cb = erofs_get_pathname_iter, .target_nid = nid, .buf = buf, .size = size, .pos = 0, }; if (nid == root.nid) { if (size < 2) { erofs_err("get_pathname buffer not large enough: len 2, size %zd", size); return -ERANGE; } buf[0] = '/'; buf[1] = '\0'; return 0; } ret = erofs_read_inode_from_disk(&root); if (ret) { erofs_err("read inode failed @ nid %llu", root.nid | 0ULL); return ret; } ret = erofs_iterate_dir(&pathctx.ctx, false); if (ret == EROFS_PATHNAME_FOUND) return 0; if (!ret) return -ENOENT; return ret; } erofs-utils-1.8.6/lib/diskbuf.c000066400000000000000000000061351477440110200163500ustar00rootroot00000000000000// SPDX-License-Identifier: GPL-2.0+ OR Apache-2.0 #include "erofs/diskbuf.h" #include "erofs/internal.h" #include "erofs/print.h" #include #include #include #include #include /* A simple approach to avoid creating too many temporary files */ static struct erofs_diskbufstrm { erofs_atomic_t count; u64 tailoffset, devpos; int fd; unsigned int alignsize; bool locked; } *dbufstrm; int erofs_diskbuf_getfd(struct erofs_diskbuf *db, u64 *fpos) { const struct erofs_diskbufstrm *strm = db->sp; u64 offset; if (!strm) return -1; offset = db->offset + strm->devpos; if (fpos) *fpos = offset; return strm->fd; } int erofs_diskbuf_reserve(struct erofs_diskbuf *db, int sid, u64 *off) { struct erofs_diskbufstrm *strm = dbufstrm + sid; if (strm->tailoffset & (strm->alignsize - 1)) { strm->tailoffset = round_up(strm->tailoffset, strm->alignsize); if (lseek(strm->fd, strm->tailoffset + strm->devpos, SEEK_SET) != strm->tailoffset + strm->devpos) return -EIO; } db->offset = strm->tailoffset; if (off) *off = db->offset + strm->devpos; db->sp = strm; (void)erofs_atomic_inc_return(&strm->count); strm->locked = true; /* TODO: need a real lock for MT */ return strm->fd; } void erofs_diskbuf_commit(struct erofs_diskbuf *db, u64 len) { struct erofs_diskbufstrm *strm = db->sp; DBG_BUGON(!strm); DBG_BUGON(!strm->locked); DBG_BUGON(strm->tailoffset != db->offset); strm->tailoffset += len; } void erofs_diskbuf_close(struct erofs_diskbuf *db) { struct erofs_diskbufstrm *strm = db->sp; DBG_BUGON(!strm); DBG_BUGON(erofs_atomic_read(&strm->count) <= 1); (void)erofs_atomic_dec_return(&strm->count); db->sp = NULL; } int erofs_tmpfile(void) { #define TRAILER "tmp.XXXXXXXXXX" char buf[PATH_MAX]; int fd; umode_t u; (void)snprintf(buf, sizeof(buf), "%s/" TRAILER, getenv("TMPDIR") ?: "/tmp"); fd = mkstemp(buf); if (fd < 0) return -errno; unlink(buf); u = umask(0); (void)umask(u); (void)fchmod(fd, 0666 & ~u); return fd; } int erofs_diskbuf_init(unsigned int nstrms) { struct erofs_diskbufstrm *strm; strm = calloc(nstrms + 1, sizeof(*strm)); if (!strm) return -ENOMEM; strm[nstrms].fd = -1; dbufstrm = strm; for (; strm < dbufstrm + nstrms; ++strm) { struct stat st; /* try to use the devfd for regfiles on stream 0 */ if (strm == dbufstrm && !g_sbi.bdev.ops) { strm->devpos = 1ULL << 40; if (!ftruncate(g_sbi.bdev.fd, strm->devpos << 1)) { strm->fd = dup(g_sbi.bdev.fd); if (lseek(strm->fd, strm->devpos, SEEK_SET) != strm->devpos) return -EIO; goto setupone; } } strm->devpos = 0; strm->fd = erofs_tmpfile(); if (strm->fd < 0) return -ENOSPC; setupone: strm->tailoffset = 0; erofs_atomic_set(&strm->count, 1); if (fstat(strm->fd, &st)) return -errno; strm->alignsize = max_t(u32, st.st_blksize, getpagesize()); } return 0; } void erofs_diskbuf_exit(void) { struct erofs_diskbufstrm *strm; if (!dbufstrm) return; for (strm = dbufstrm; strm->fd >= 0; ++strm) { DBG_BUGON(erofs_atomic_read(&strm->count) != 1); close(strm->fd); strm->fd = -1; } } erofs-utils-1.8.6/lib/exclude.c000066400000000000000000000051021477440110200163430ustar00rootroot00000000000000// SPDX-License-Identifier: GPL-2.0+ OR Apache-2.0 /* * Created by Li Guifu */ #include #include #include "erofs/err.h" #include "erofs/list.h" #include "erofs/print.h" #include "erofs/exclude.h" #include "erofs/internal.h" #define EXCLUDE_RULE_EXACT_SIZE offsetof(struct erofs_exclude_rule, reg) #define EXCLUDE_RULE_REGEX_SIZE sizeof(struct erofs_exclude_rule) static LIST_HEAD(exclude_head); static LIST_HEAD(regex_exclude_head); static void dump_regerror(int errcode, const char *s, const regex_t *preg) { char str[512]; regerror(errcode, preg, str, sizeof(str)); erofs_err("invalid regex %s (%s)\n", s, str); } static struct erofs_exclude_rule *erofs_insert_exclude(const char *s, bool is_regex) { struct erofs_exclude_rule *r; int ret; struct list_head *h; r = malloc(is_regex ? EXCLUDE_RULE_REGEX_SIZE : EXCLUDE_RULE_EXACT_SIZE); if (!r) return ERR_PTR(-ENOMEM); r->pattern = strdup(s); if (!r->pattern) { ret = -ENOMEM; goto err_rule; } if (is_regex) { ret = regcomp(&r->reg, s, REG_EXTENDED|REG_NOSUB); if (ret) { dump_regerror(ret, s, &r->reg); goto err_rule; } h = ®ex_exclude_head; } else { h = &exclude_head; } list_add_tail(&r->list, h); erofs_info("insert exclude %s: %s\n", is_regex ? "regex" : "path", s); return r; err_rule: if (r->pattern) free(r->pattern); free(r); return ERR_PTR(ret); } void erofs_cleanup_exclude_rules(void) { struct erofs_exclude_rule *r, *n; struct list_head *h; h = &exclude_head; list_for_each_entry_safe(r, n, h, list) { list_del(&r->list); free(r->pattern); free(r); } h = ®ex_exclude_head; list_for_each_entry_safe(r, n, h, list) { list_del(&r->list); free(r->pattern); regfree(&r->reg); free(r); } } int erofs_parse_exclude_path(const char *args, bool is_regex) { struct erofs_exclude_rule *r = erofs_insert_exclude(args, is_regex); if (IS_ERR(r)) { erofs_cleanup_exclude_rules(); return PTR_ERR(r); } return 0; } struct erofs_exclude_rule *erofs_is_exclude_path(const char *dir, const char *name) { char buf[PATH_MAX]; const char *s; struct erofs_exclude_rule *r; if (!dir) { /* no prefix */ s = name; } else { sprintf(buf, "%s/%s", dir, name); s = buf; } s = erofs_fspath(s); list_for_each_entry(r, &exclude_head, list) { if (!strcmp(r->pattern, s)) return r; } list_for_each_entry(r, ®ex_exclude_head, list) { int ret = regexec(&r->reg, s, (size_t)0, NULL, 0); if (!ret) return r; if (ret != REG_NOMATCH) dump_regerror(ret, s, &r->reg); } return NULL; } erofs-utils-1.8.6/lib/fragments.c000066400000000000000000000300421477440110200167010ustar00rootroot00000000000000// SPDX-License-Identifier: GPL-2.0+ OR Apache-2.0 /* * Copyright (C), 2022, Coolpad Group Limited. * Created by Yue Hu */ #ifndef _LARGEFILE64_SOURCE #define _LARGEFILE64_SOURCE #endif #ifndef _FILE_OFFSET_BITS #define _FILE_OFFSET_BITS 64 #endif #ifndef _GNU_SOURCE #define _GNU_SOURCE #endif #include #include #include #include "erofs/err.h" #include "erofs/inode.h" #include "erofs/compress.h" #include "erofs/print.h" #include "erofs/internal.h" #include "erofs/fragments.h" #include "erofs/bitops.h" #include "liberofs_private.h" struct erofs_fragment_dedupe_item { struct list_head list; unsigned int length; erofs_off_t pos; u8 data[]; }; #define EROFS_FRAGMENT_INMEM_SZ_MAX (256 * 1024) #define EROFS_TOF_HASHLEN 16 #define FRAGMENT_HASHSIZE 65536 #define FRAGMENT_HASH(c) ((c) & (FRAGMENT_HASHSIZE - 1)) struct erofs_packed_inode { struct list_head *hash; int fd; unsigned long *uptodate; #if EROFS_MT_ENABLED pthread_mutex_t mutex; #endif u64 uptodate_bits; }; const char *erofs_frags_packedname = "packed_file"; #ifndef HAVE_LSEEK64 #define erofs_lseek64 lseek #else #define erofs_lseek64 lseek64 #endif static int z_erofs_fragments_dedupe_find(struct erofs_inode *inode, int fd, u32 crc) { struct erofs_packed_inode *epi = inode->sbi->packedinode; struct erofs_fragment_dedupe_item *cur, *di = NULL; struct list_head *head = &epi->hash[FRAGMENT_HASH(crc)]; unsigned int s1, e1; erofs_off_t deduped; u8 *data; int ret; if (list_empty(head)) return 0; s1 = min_t(u64, EROFS_FRAGMENT_INMEM_SZ_MAX, inode->i_size); data = malloc(s1); if (!data) return -ENOMEM; ret = pread(fd, data, s1, inode->i_size - s1); if (ret != s1) { free(data); return -errno; } e1 = s1 - EROFS_TOF_HASHLEN; deduped = 0; list_for_each_entry(cur, head, list) { unsigned int e2, mn; erofs_off_t i, pos; DBG_BUGON(cur->length <= EROFS_TOF_HASHLEN); e2 = cur->length - EROFS_TOF_HASHLEN; if (memcmp(data + e1, cur->data + e2, EROFS_TOF_HASHLEN)) continue; i = 0; mn = min(e1, e2); while (i < mn && cur->data[e2 - i - 1] == data[e1 - i - 1]) ++i; i += EROFS_TOF_HASHLEN; if (i >= s1) { /* full short match */ DBG_BUGON(i > s1); pos = cur->pos + cur->length - s1; while (i < inode->i_size && pos) { char buf[2][16384]; unsigned int sz; sz = min_t(u64, pos, sizeof(buf[0])); sz = min_t(u64, sz, inode->i_size - i); if (pread(epi->fd, buf[0], sz, pos - sz) != sz) break; if (pread(fd, buf[1], sz, inode->i_size - i - sz) != sz) break; if (memcmp(buf[0], buf[1], sz)) break; pos -= sz; i += sz; } } if (i <= deduped) continue; di = cur; deduped = i; if (deduped == inode->i_size) break; } free(data); if (deduped) { DBG_BUGON(!di); inode->fragment_size = deduped; inode->fragmentoff = di->pos + di->length - deduped; erofs_dbg("Dedupe %llu tail data at %llu", inode->fragment_size | 0ULL, inode->fragmentoff | 0ULL); } return 0; } int z_erofs_fragments_dedupe(struct erofs_inode *inode, int fd, u32 *tofcrc) { u8 data_to_hash[EROFS_TOF_HASHLEN]; int ret; if (inode->i_size <= EROFS_TOF_HASHLEN) return 0; ret = pread(fd, data_to_hash, EROFS_TOF_HASHLEN, inode->i_size - EROFS_TOF_HASHLEN); if (ret != EROFS_TOF_HASHLEN) return -errno; *tofcrc = erofs_crc32c(~0, data_to_hash, EROFS_TOF_HASHLEN); return z_erofs_fragments_dedupe_find(inode, fd, *tofcrc); } static int z_erofs_fragments_dedupe_insert(struct list_head *hash, void *data, unsigned int len, erofs_off_t pos) { struct erofs_fragment_dedupe_item *di; if (len <= EROFS_TOF_HASHLEN) return 0; if (len > EROFS_FRAGMENT_INMEM_SZ_MAX) { data += len - EROFS_FRAGMENT_INMEM_SZ_MAX; pos += len - EROFS_FRAGMENT_INMEM_SZ_MAX; len = EROFS_FRAGMENT_INMEM_SZ_MAX; } di = malloc(sizeof(*di) + len); if (!di) return -ENOMEM; memcpy(di->data, data, len); di->length = len; di->pos = pos; list_add_tail(&di->list, hash); return 0; } int z_erofs_pack_file_from_fd(struct erofs_inode *inode, int fd, u32 tofcrc) { struct erofs_packed_inode *epi = inode->sbi->packedinode; s64 offset, rc; char *memblock; offset = lseek(epi->fd, 0, SEEK_CUR); if (offset < 0) return -errno; inode->fragmentoff = (erofs_off_t)offset; inode->fragment_size = inode->i_size; memblock = mmap(NULL, inode->i_size, PROT_READ, MAP_SHARED, fd, 0); if (memblock == MAP_FAILED || !memblock) { unsigned long long remaining = inode->fragment_size; memblock = NULL; while (remaining) { char buf[32768]; unsigned int sz = min_t(unsigned int, remaining, sizeof(buf)); rc = read(fd, buf, sz); if (rc != sz) { if (rc <= 0) { if (!rc) rc = -EIO; else rc = -errno; goto out; } sz = rc; } rc = __erofs_io_write(epi->fd, buf, sz); if (rc != sz) { if (rc >= 0) rc = -EIO; goto out; } remaining -= sz; } rc = lseek(fd, 0, SEEK_SET); if (rc < 0) { rc = -errno; goto out; } } else { rc = __erofs_io_write(epi->fd, memblock, inode->fragment_size); if (rc != inode->fragment_size) { if (rc >= 0) rc = -EIO; goto out; } } erofs_dbg("Recording %llu fragment data at %llu of %s", inode->fragment_size | 0ULL, inode->fragmentoff | 0ULL, inode->i_srcpath); if (memblock) rc = z_erofs_fragments_dedupe_insert( &epi->hash[FRAGMENT_HASH(tofcrc)], memblock, inode->fragment_size, inode->fragmentoff); else rc = 0; out: if (rc) erofs_err("Failed to record %llu-byte fragment data @ %llu for nid %llu: %d", inode->fragment_size | 0ULL, inode->fragmentoff | 0ULL, inode->nid | 0ULL, (int)rc); if (memblock) munmap(memblock, inode->i_size); return rc; } int z_erofs_pack_fragments(struct erofs_inode *inode, void *data, unsigned int len, u32 tofcrc) { struct erofs_packed_inode *epi = inode->sbi->packedinode; s64 offset = lseek(epi->fd, 0, SEEK_CUR); int ret; if (offset < 0) return -errno; inode->fragmentoff = (erofs_off_t)offset; inode->fragment_size = len; ret = write(epi->fd, data, len); if (ret != len) { if (ret < 0) return -errno; return -EIO; } erofs_dbg("Recording %llu fragment data at %llu of %s", inode->fragment_size | 0ULL, inode->fragmentoff | 0ULL, inode->i_srcpath); ret = z_erofs_fragments_dedupe_insert(&epi->hash[FRAGMENT_HASH(tofcrc)], data, len, inode->fragmentoff); if (ret) return ret; return len; } int erofs_flush_packed_inode(struct erofs_sb_info *sbi) { struct erofs_packed_inode *epi = sbi->packedinode; struct erofs_inode *inode; if (!epi || !erofs_sb_has_fragments(sbi)) return -EINVAL; if (lseek(epi->fd, 0, SEEK_CUR) <= 0) return 0; inode = erofs_mkfs_build_special_from_fd(sbi, epi->fd, EROFS_PACKED_INODE); sbi->packed_nid = erofs_lookupnid(inode); erofs_iput(inode); return 0; } int erofs_packedfile(struct erofs_sb_info *sbi) { return sbi->packedinode->fd; } void erofs_packedfile_exit(struct erofs_sb_info *sbi) { struct erofs_packed_inode *epi = sbi->packedinode; struct erofs_fragment_dedupe_item *di, *n; int i; if (!epi) return; if (epi->uptodate) free(epi->uptodate); if (epi->hash) { for (i = 0; i < FRAGMENT_HASHSIZE; ++i) list_for_each_entry_safe(di, n, &epi->hash[i], list) free(di); free(epi->hash); } if (epi->fd >= 0) close(epi->fd); free(epi); sbi->packedinode = NULL; } int erofs_packedfile_init(struct erofs_sb_info *sbi, bool fragments_mkfs) { struct erofs_packed_inode *epi; int err, i; if (sbi->packedinode) return -EINVAL; epi = calloc(1, sizeof(*epi)); if (!epi) return -ENOMEM; sbi->packedinode = epi; if (fragments_mkfs) { epi->hash = malloc(sizeof(*epi->hash) * FRAGMENT_HASHSIZE); if (!epi->hash) { err = -ENOMEM; goto err_out; } for (i = 0; i < FRAGMENT_HASHSIZE; ++i) init_list_head(&epi->hash[i]); } epi->fd = erofs_tmpfile(); if (epi->fd < 0) { err = epi->fd; goto err_out; } if (erofs_sb_has_fragments(sbi) && sbi->packed_nid > 0) { struct erofs_inode ei = { .sbi = sbi, .nid = sbi->packed_nid, }; s64 offset; err = erofs_read_inode_from_disk(&ei); if (err) { erofs_err("failed to read packed inode from disk: %s", erofs_strerror(-errno)); goto err_out; } offset = lseek(epi->fd, ei.i_size, SEEK_SET); if (offset < 0) { err = -errno; goto err_out; } epi->uptodate_bits = round_up(BLK_ROUND_UP(sbi, ei.i_size), sizeof(epi->uptodate) * 8); epi->uptodate = calloc(1, epi->uptodate_bits >> 3); if (!epi->uptodate) { err = -ENOMEM; goto err_out; } } return 0; err_out: erofs_packedfile_exit(sbi); return err; } static int erofs_load_packedinode_from_disk(struct erofs_inode *pi) { struct erofs_sb_info *sbi = pi->sbi; int err; if (pi->nid) return 0; pi->nid = sbi->packed_nid; err = erofs_read_inode_from_disk(pi); if (err) { erofs_err("failed to read packed inode from disk: %s", erofs_strerror(err)); return err; } return 0; } static void *erofs_packedfile_preload(struct erofs_inode *pi, struct erofs_map_blocks *map) { struct erofs_sb_info *sbi = pi->sbi; struct erofs_packed_inode *epi = sbi->packedinode; unsigned int bsz = erofs_blksiz(sbi); char *buffer; erofs_off_t pos, end; ssize_t err; err = erofs_load_packedinode_from_disk(pi); if (err) return ERR_PTR(err); pos = map->m_la; err = erofs_map_blocks(pi, map, EROFS_GET_BLOCKS_FIEMAP); if (err) return ERR_PTR(err); end = round_up(map->m_la + map->m_llen, bsz); if (map->m_la < pos) map->m_la = round_up(map->m_la, bsz); else DBG_BUGON(map->m_la > pos); map->m_llen = end - map->m_la; DBG_BUGON(!map->m_llen); buffer = malloc(map->m_llen); if (!buffer) return ERR_PTR(-ENOMEM); err = erofs_pread(pi, buffer, map->m_llen, map->m_la); if (err) goto err_out; err = pwrite(epi->fd, buffer, map->m_llen, map->m_la); if (err < 0) { err = -errno; if (err == -ENOSPC) { memset(epi->uptodate, 0, epi->uptodate_bits >> 3); (void)!ftruncate(epi->fd, 0); } goto err_out; } if (err != map->m_llen) { err = -EIO; goto err_out; } for (pos = map->m_la; pos < end; pos += bsz) __erofs_set_bit(erofs_blknr(sbi, pos), epi->uptodate); return buffer; err_out: free(buffer); map->m_llen = 0; return ERR_PTR(err); } int erofs_packedfile_read(struct erofs_sb_info *sbi, void *buf, erofs_off_t len, erofs_off_t pos) { struct erofs_packed_inode *epi = sbi->packedinode; struct erofs_inode pi = { .sbi = sbi, }; struct erofs_map_blocks map = { .index = UINT_MAX, }; unsigned int bsz = erofs_blksiz(sbi); erofs_off_t end = pos + len; char *buffer = NULL; int err; if (!epi) { err = erofs_load_packedinode_from_disk(&pi); if (!err) err = erofs_pread(&pi, buf, len, pos); return err; } err = 0; while (pos < end) { if (pos >= map.m_la && pos < map.m_la + map.m_llen) { len = min_t(erofs_off_t, end - pos, map.m_la + map.m_llen - pos); memcpy(buf, buffer + pos - map.m_la, len); } else { erofs_blk_t bnr = erofs_blknr(sbi, pos); bool uptodate; if (__erofs_unlikely(bnr >= epi->uptodate_bits)) { erofs_err("packed inode EOF exceeded @ %llu", pos | 0ULL); return -EFSCORRUPTED; } map.m_la = round_down(pos, bsz); len = min_t(erofs_off_t, bsz - (pos & (bsz - 1)), end - pos); uptodate = __erofs_test_bit(bnr, epi->uptodate); if (!uptodate) { #if EROFS_MT_ENABLED pthread_mutex_lock(&epi->mutex); uptodate = __erofs_test_bit(bnr, epi->uptodate); if (!uptodate) { #endif free(buffer); buffer = erofs_packedfile_preload(&pi, &map); if (IS_ERR(buffer)) { #if EROFS_MT_ENABLED pthread_mutex_unlock(&epi->mutex); #endif buffer = NULL; goto fallback; } #if EROFS_MT_ENABLED } pthread_mutex_unlock(&epi->mutex); #endif } if (!uptodate) continue; err = pread(epi->fd, buf, len, pos); if (err < 0) break; if (err == len) { err = 0; } else { fallback: err = erofs_load_packedinode_from_disk(&pi); if (!err) err = erofs_pread(&pi, buf, len, pos); if (err) break; } map.m_llen = 0; } buf += len; pos += len; } free(buffer); return err; } erofs-utils-1.8.6/lib/hashmap.c000066400000000000000000000143601477440110200163410ustar00rootroot00000000000000// SPDX-License-Identifier: GPL-2.0 /* * Copied from https://github.com/git/git.git * Generic implementation of hash-based key value mappings. */ #include "erofs/hashmap.h" #define FNV32_BASE ((unsigned int)0x811c9dc5) #define FNV32_PRIME ((unsigned int)0x01000193) unsigned int strhash(const char *str) { unsigned int c, hash = FNV32_BASE; while ((c = (unsigned char)*str++)) hash = (hash * FNV32_PRIME) ^ c; return hash; } unsigned int strihash(const char *str) { unsigned int c, hash = FNV32_BASE; while ((c = (unsigned char)*str++)) { if (c >= 'a' && c <= 'z') c -= 'a' - 'A'; hash = (hash * FNV32_PRIME) ^ c; } return hash; } unsigned int memhash(const void *buf, size_t len) { unsigned int hash = FNV32_BASE; unsigned char *ucbuf = (unsigned char *)buf; while (len--) { unsigned int c = *ucbuf++; hash = (hash * FNV32_PRIME) ^ c; } return hash; } unsigned int memihash(const void *buf, size_t len) { unsigned int hash = FNV32_BASE; unsigned char *ucbuf = (unsigned char *)buf; while (len--) { unsigned int c = *ucbuf++; if (c >= 'a' && c <= 'z') c -= 'a' - 'A'; hash = (hash * FNV32_PRIME) ^ c; } return hash; } #define HASHMAP_INITIAL_SIZE 64 /* grow / shrink by 2^2 */ #define HASHMAP_RESIZE_BITS 2 /* load factor in percent */ #define HASHMAP_LOAD_FACTOR 80 static void alloc_table(struct hashmap *map, unsigned int size) { map->tablesize = size; map->table = calloc(size, sizeof(struct hashmap_entry *)); BUG_ON(!map->table); /* calculate resize thresholds for new size */ map->grow_at = (unsigned int)((uint64_t)size * HASHMAP_LOAD_FACTOR / 100); if (size <= HASHMAP_INITIAL_SIZE) map->shrink_at = 0; else /* * The shrink-threshold must be slightly smaller than * (grow-threshold / resize-factor) to prevent erratic resizing, * thus we divide by (resize-factor + 1). */ map->shrink_at = map->grow_at / ((1 << HASHMAP_RESIZE_BITS) + 1); } static inline int entry_equals(const struct hashmap *map, const struct hashmap_entry *e1, const struct hashmap_entry *e2, const void *keydata) { return (e1 == e2) || (e1->hash == e2->hash && !map->cmpfn(e1, e2, keydata)); } static inline unsigned int bucket(const struct hashmap *map, const struct hashmap_entry *key) { return key->hash & (map->tablesize - 1); } static void rehash(struct hashmap *map, unsigned int newsize) { unsigned int i, oldsize = map->tablesize; struct hashmap_entry **oldtable = map->table; alloc_table(map, newsize); for (i = 0; i < oldsize; i++) { struct hashmap_entry *e = oldtable[i]; while (e) { struct hashmap_entry *next = e->next; unsigned int b = bucket(map, e); e->next = map->table[b]; map->table[b] = e; e = next; } } free(oldtable); } static inline struct hashmap_entry **find_entry_ptr(const struct hashmap *map, const struct hashmap_entry *key, const void *keydata) { struct hashmap_entry **e = &map->table[bucket(map, key)]; while (*e && !entry_equals(map, *e, key, keydata)) e = &(*e)->next; return e; } static int always_equal(const void *unused1, const void *unused2, const void *unused3) { return 0; } void hashmap_init(struct hashmap *map, hashmap_cmp_fn equals_function, size_t initial_size) { unsigned int size = HASHMAP_INITIAL_SIZE; map->size = 0; map->cmpfn = equals_function ? equals_function : always_equal; /* calculate initial table size and allocate the table */ initial_size = (unsigned int)((uint64_t)initial_size * 100 / HASHMAP_LOAD_FACTOR); while (initial_size > size) size <<= HASHMAP_RESIZE_BITS; alloc_table(map, size); } int hashmap_free(struct hashmap *map) { if (map && map->table) { struct hashmap_iter iter; struct hashmap_entry *e; hashmap_iter_init(map, &iter); e = hashmap_iter_next(&iter); if (e) return -EBUSY; free(map->table); memset(map, 0, sizeof(*map)); } return 0; } void *hashmap_get(const struct hashmap *map, const void *key, const void *keydata) { return *find_entry_ptr(map, key, keydata); } void *hashmap_get_next(const struct hashmap *map, const void *entry) { struct hashmap_entry *e = ((struct hashmap_entry *)entry)->next; for (; e; e = e->next) if (entry_equals(map, entry, e, NULL)) return e; return NULL; } void hashmap_add(struct hashmap *map, void *entry) { unsigned int b = bucket(map, entry); /* add entry */ ((struct hashmap_entry *)entry)->next = map->table[b]; map->table[b] = entry; /* fix size and rehash if appropriate */ map->size++; if (map->size > map->grow_at) rehash(map, map->tablesize << HASHMAP_RESIZE_BITS); } void *hashmap_remove(struct hashmap *map, const void *entry) { struct hashmap_entry *old; struct hashmap_entry **e = &map->table[bucket(map, entry)]; while (*e && *e != entry) e = &(*e)->next; if (!*e) return NULL; /* remove existing entry */ old = *e; *e = old->next; old->next = NULL; /* fix size and rehash if appropriate */ map->size--; if (map->size < map->shrink_at) rehash(map, map->tablesize >> HASHMAP_RESIZE_BITS); return old; } void hashmap_iter_init(struct hashmap *map, struct hashmap_iter *iter) { iter->map = map; iter->tablepos = 0; iter->next = NULL; } void *hashmap_iter_next(struct hashmap_iter *iter) { struct hashmap_entry *current = iter->next; for (;;) { if (current) { iter->next = current->next; return current; } if (iter->tablepos >= iter->map->tablesize) return NULL; current = iter->map->table[iter->tablepos++]; } } struct pool_entry { struct hashmap_entry ent; size_t len; unsigned char data[FLEX_ARRAY]; }; static int pool_entry_cmp(const struct pool_entry *e1, const struct pool_entry *e2, const unsigned char *keydata) { return e1->data != keydata && (e1->len != e2->len || memcmp(e1->data, keydata, e1->len)); } const void *memintern(const void *data, size_t len) { static struct hashmap map; struct pool_entry key, *e; /* initialize string pool hashmap */ if (!map.tablesize) hashmap_init(&map, (hashmap_cmp_fn)pool_entry_cmp, 0); /* lookup interned string in pool */ hashmap_entry_init(&key, memhash(data, len)); key.len = len; e = hashmap_get(&map, &key, data); if (!e) { /* not found: create it */ FLEX_ALLOC_MEM(e, data, data, len); hashmap_entry_init(e, key.ent.hash); e->len = len; hashmap_add(&map, e); } return e->data; } erofs-utils-1.8.6/lib/inode.c000066400000000000000000001412241477440110200160160ustar00rootroot00000000000000// SPDX-License-Identifier: GPL-2.0+ OR Apache-2.0 /* * Copyright (C) 2018-2019 HUAWEI, Inc. * http://www.huawei.com/ * Created by Li Guifu * with heavy changes by Gao Xiang */ #define _GNU_SOURCE #ifdef EROFS_MT_ENABLED #include #endif #include #include #include #include #include #if defined(HAVE_SYS_SYSMACROS_H) #include #endif #include #include "erofs/print.h" #include "erofs/diskbuf.h" #include "erofs/inode.h" #include "erofs/cache.h" #include "erofs/compress.h" #include "erofs/xattr.h" #include "erofs/exclude.h" #include "erofs/block_list.h" #include "erofs/compress_hints.h" #include "erofs/blobchunk.h" #include "erofs/fragments.h" #include "liberofs_private.h" #define S_SHIFT 12 static unsigned char erofs_ftype_by_mode[S_IFMT >> S_SHIFT] = { [S_IFREG >> S_SHIFT] = EROFS_FT_REG_FILE, [S_IFDIR >> S_SHIFT] = EROFS_FT_DIR, [S_IFCHR >> S_SHIFT] = EROFS_FT_CHRDEV, [S_IFBLK >> S_SHIFT] = EROFS_FT_BLKDEV, [S_IFIFO >> S_SHIFT] = EROFS_FT_FIFO, [S_IFSOCK >> S_SHIFT] = EROFS_FT_SOCK, [S_IFLNK >> S_SHIFT] = EROFS_FT_SYMLINK, }; unsigned char erofs_mode_to_ftype(umode_t mode) { return erofs_ftype_by_mode[(mode & S_IFMT) >> S_SHIFT]; } static const unsigned char erofs_dtype_by_ftype[EROFS_FT_MAX] = { [EROFS_FT_UNKNOWN] = DT_UNKNOWN, [EROFS_FT_REG_FILE] = DT_REG, [EROFS_FT_DIR] = DT_DIR, [EROFS_FT_CHRDEV] = DT_CHR, [EROFS_FT_BLKDEV] = DT_BLK, [EROFS_FT_FIFO] = DT_FIFO, [EROFS_FT_SOCK] = DT_SOCK, [EROFS_FT_SYMLINK] = DT_LNK }; static const umode_t erofs_dtype_by_umode[EROFS_FT_MAX] = { [EROFS_FT_UNKNOWN] = S_IFMT, [EROFS_FT_REG_FILE] = S_IFREG, [EROFS_FT_DIR] = S_IFDIR, [EROFS_FT_CHRDEV] = S_IFCHR, [EROFS_FT_BLKDEV] = S_IFBLK, [EROFS_FT_FIFO] = S_IFIFO, [EROFS_FT_SOCK] = S_IFSOCK, [EROFS_FT_SYMLINK] = S_IFLNK }; umode_t erofs_ftype_to_mode(unsigned int ftype, unsigned int perm) { if (ftype >= EROFS_FT_MAX) ftype = EROFS_FT_UNKNOWN; return erofs_dtype_by_umode[ftype] | perm; } unsigned char erofs_ftype_to_dtype(unsigned int filetype) { if (filetype >= EROFS_FT_MAX) return DT_UNKNOWN; return erofs_dtype_by_ftype[filetype]; } #define NR_INODE_HASHTABLE 16384 struct list_head inode_hashtable[NR_INODE_HASHTABLE]; void erofs_inode_manager_init(void) { unsigned int i; for (i = 0; i < NR_INODE_HASHTABLE; ++i) init_list_head(&inode_hashtable[i]); } void erofs_insert_ihash(struct erofs_inode *inode) { unsigned int nr = (inode->i_ino[1] ^ inode->dev) % NR_INODE_HASHTABLE; list_add(&inode->i_hash, &inode_hashtable[nr]); } /* get the inode from the (source) inode # */ struct erofs_inode *erofs_iget(dev_t dev, ino_t ino) { struct list_head *head = &inode_hashtable[(ino ^ dev) % NR_INODE_HASHTABLE]; struct erofs_inode *inode; list_for_each_entry(inode, head, i_hash) if (inode->i_ino[1] == ino && inode->dev == dev) return erofs_igrab(inode); return NULL; } struct erofs_inode *erofs_iget_by_nid(erofs_nid_t nid) { struct list_head *head = &inode_hashtable[nid % NR_INODE_HASHTABLE]; struct erofs_inode *inode; list_for_each_entry(inode, head, i_hash) if (inode->nid == nid) return erofs_igrab(inode); return NULL; } unsigned int erofs_iput(struct erofs_inode *inode) { struct erofs_dentry *d, *t; unsigned long got = erofs_atomic_dec_return(&inode->i_count); if (got >= 1) return got; list_for_each_entry_safe(d, t, &inode->i_subdirs, d_child) free(d); free(inode->compressmeta); free(inode->eof_tailraw); list_del(&inode->i_hash); free(inode->i_srcpath); if (inode->datasource == EROFS_INODE_DATA_SOURCE_DISKBUF) { erofs_diskbuf_close(inode->i_diskbuf); free(inode->i_diskbuf); } else { free(inode->i_link); } free(inode); return 0; } struct erofs_dentry *erofs_d_alloc(struct erofs_inode *parent, const char *name) { unsigned int namelen = strlen(name); unsigned int fsz = round_up(namelen + 1, EROFS_DENTRY_NAME_ALIGNMENT); struct erofs_dentry *d; if (namelen > EROFS_NAME_LEN) { DBG_BUGON(1); return ERR_PTR(-ENAMETOOLONG); } d = malloc(sizeof(*d) + fsz); if (!d) return ERR_PTR(-ENOMEM); memcpy(d->name, name, namelen); memset(d->name + namelen, 0, fsz - namelen); d->inode = NULL; d->namelen = namelen; d->type = EROFS_FT_UNKNOWN; d->validnid = false; list_add_tail(&d->d_child, &parent->i_subdirs); return d; } /* allocate main data for an inode */ int erofs_allocate_inode_bh_data(struct erofs_inode *inode, erofs_blk_t nblocks) { struct erofs_bufmgr *bmgr = inode->sbi->bmgr; struct erofs_buffer_head *bh; int ret, type; if (!nblocks) { /* it has only tail-end data */ inode->u.i_blkaddr = NULL_ADDR; return 0; } /* allocate main data buffer */ type = S_ISDIR(inode->i_mode) ? DIRA : DATA; bh = erofs_balloc(bmgr, type, erofs_pos(inode->sbi, nblocks), 0); if (IS_ERR(bh)) return PTR_ERR(bh); bh->op = &erofs_skip_write_bhops; inode->bh_data = bh; /* get blkaddr of the bh */ ret = erofs_mapbh(NULL, bh->block); DBG_BUGON(ret < 0); /* write blocks except for the tail-end block */ inode->u.i_blkaddr = bh->block->blkaddr; return 0; } static int comp_subdir(const void *a, const void *b) { const struct erofs_dentry *da, *db; int commonlen, sign; da = *((const struct erofs_dentry **)a); db = *((const struct erofs_dentry **)b); commonlen = min(round_up(da->namelen, EROFS_DENTRY_NAME_ALIGNMENT), round_up(db->namelen, EROFS_DENTRY_NAME_ALIGNMENT)); sign = memcmp(da->name, db->name, commonlen); if (sign) return sign; return cmpsgn(da->namelen, db->namelen); } int erofs_init_empty_dir(struct erofs_inode *dir) { struct erofs_dentry *d; /* dot is pointed to the current dir inode */ d = erofs_d_alloc(dir, "."); if (IS_ERR(d)) return PTR_ERR(d); d->inode = erofs_igrab(dir); d->type = EROFS_FT_DIR; /* dotdot is pointed to the parent dir */ d = erofs_d_alloc(dir, ".."); if (IS_ERR(d)) return PTR_ERR(d); d->inode = erofs_igrab(erofs_parent_inode(dir)); d->type = EROFS_FT_DIR; dir->i_nlink = 2; return 0; } static int erofs_prepare_dir_file(struct erofs_inode *dir, unsigned int nr_subdirs) { struct erofs_sb_info *sbi = dir->sbi; struct erofs_dentry *d, *n, **sorted_d; unsigned int i; unsigned int d_size = 0; sorted_d = malloc(nr_subdirs * sizeof(d)); if (!sorted_d) return -ENOMEM; i = 0; list_for_each_entry_safe(d, n, &dir->i_subdirs, d_child) { list_del(&d->d_child); sorted_d[i++] = d; } DBG_BUGON(i != nr_subdirs); qsort(sorted_d, nr_subdirs, sizeof(d), comp_subdir); for (i = 0; i < nr_subdirs; i++) list_add_tail(&sorted_d[i]->d_child, &dir->i_subdirs); free(sorted_d); /* let's calculate dir size */ list_for_each_entry(d, &dir->i_subdirs, d_child) { int len = d->namelen + sizeof(struct erofs_dirent); if (erofs_blkoff(sbi, d_size) + len > erofs_blksiz(sbi)) d_size = round_up(d_size, erofs_blksiz(sbi)); d_size += len; } dir->i_size = d_size; /* no compression for all dirs */ dir->datalayout = EROFS_INODE_FLAT_INLINE; /* it will be used in erofs_prepare_inode_buffer */ dir->idata_size = d_size % erofs_blksiz(sbi); return 0; } static void fill_dirblock(char *buf, unsigned int size, unsigned int q, struct erofs_dentry *head, struct erofs_dentry *end) { unsigned int p = 0; /* write out all erofs_dirents + filenames */ while (head != end) { const unsigned int namelen = head->namelen; struct erofs_dirent d = { .nid = cpu_to_le64(head->nid), .nameoff = cpu_to_le16(q), .file_type = head->type, }; memcpy(buf + p, &d, sizeof(d)); memcpy(buf + q, head->name, namelen); p += sizeof(d); q += namelen; head = list_next_entry(head, d_child); } memset(buf + q, 0, size - q); } static int write_dirblock(struct erofs_sb_info *sbi, unsigned int q, struct erofs_dentry *head, struct erofs_dentry *end, erofs_blk_t blkaddr) { char buf[EROFS_MAX_BLOCK_SIZE]; fill_dirblock(buf, erofs_blksiz(sbi), q, head, end); return erofs_blk_write(sbi, buf, blkaddr, 1); } erofs_nid_t erofs_lookupnid(struct erofs_inode *inode) { struct erofs_buffer_head *const bh = inode->bh; struct erofs_sb_info *sbi = inode->sbi; erofs_off_t off, meta_offset; if (bh && (long long)inode->nid <= 0) { erofs_mapbh(NULL, bh->block); off = erofs_btell(bh, false); meta_offset = erofs_pos(sbi, sbi->meta_blkaddr); DBG_BUGON(off < meta_offset); inode->nid = (off - meta_offset) >> EROFS_ISLOTBITS; erofs_dbg("Assign nid %llu to file %s (mode %05o)", inode->nid, inode->i_srcpath, inode->i_mode); } if (__erofs_unlikely(IS_ROOT(inode)) && inode->nid > 0xffff) return sbi->root_nid; return inode->nid; } static void erofs_d_invalidate(struct erofs_dentry *d) { struct erofs_inode *const inode = d->inode; if (d->validnid) return; d->nid = erofs_lookupnid(inode); d->validnid = true; erofs_iput(inode); } static int erofs_rebuild_inode_fix_pnid(struct erofs_inode *parent, erofs_nid_t nid) { struct erofs_inode dir = { .sbi = parent->sbi, .nid = nid }; unsigned int bsz = erofs_blksiz(dir.sbi); unsigned int err, isz; erofs_off_t boff, off; erofs_nid_t pnid; bool fixed = false; err = erofs_read_inode_from_disk(&dir); if (err) return err; if (!S_ISDIR(dir.i_mode)) return -ENOTDIR; if (dir.datalayout != EROFS_INODE_FLAT_INLINE && dir.datalayout != EROFS_INODE_FLAT_PLAIN) return -EOPNOTSUPP; pnid = erofs_lookupnid(parent); isz = dir.inode_isize + dir.xattr_isize; boff = erofs_pos(dir.sbi, dir.u.i_blkaddr); for (off = 0; off < dir.i_size; off += bsz) { char buf[EROFS_MAX_BLOCK_SIZE]; struct erofs_dirent *de = (struct erofs_dirent *)buf; unsigned int nameoff, count, de_nameoff; count = min_t(erofs_off_t, bsz, dir.i_size - off); err = erofs_pread(&dir, buf, count, off); if (err) return err; nameoff = le16_to_cpu(de->nameoff); if (nameoff < sizeof(struct erofs_dirent) || nameoff >= count) { erofs_err("invalid de[0].nameoff %u @ nid %llu, offset %llu", nameoff, dir.nid | 0ULL, off | 0ULL); return -EFSCORRUPTED; } while ((char *)de < buf + nameoff) { de_nameoff = le16_to_cpu(de->nameoff); if (((char *)(de + 1) >= buf + nameoff ? strnlen(buf + de_nameoff, count - de_nameoff) == 2 : le16_to_cpu(de[1].nameoff) == de_nameoff + 2) && !memcmp(buf + de_nameoff, "..", 2)) { if (de->nid == cpu_to_le64(pnid)) return 0; de->nid = cpu_to_le64(pnid); fixed = true; break; } ++de; } if (!fixed) continue; err = erofs_dev_write(dir.sbi, buf, (off + bsz > dir.i_size && dir.datalayout == EROFS_INODE_FLAT_INLINE ? erofs_iloc(&dir) + isz : boff + off), count); erofs_dbg("directory %llu pNID is updated to %llu", nid | 0ULL, pnid | 0ULL); break; } if (err || fixed) return err; erofs_err("directory data %llu is corrupted (\"..\" not found)", nid | 0ULL); return -EFSCORRUPTED; } static int erofs_write_dir_file(struct erofs_inode *dir) { struct erofs_dentry *head = list_first_entry(&dir->i_subdirs, struct erofs_dentry, d_child); struct erofs_sb_info *sbi = dir->sbi; struct erofs_dentry *d; int ret; unsigned int q, used, blkno; q = used = blkno = 0; /* allocate dir main data */ ret = erofs_allocate_inode_bh_data(dir, erofs_blknr(sbi, dir->i_size)); if (ret) return ret; list_for_each_entry(d, &dir->i_subdirs, d_child) { unsigned int len = d->namelen + sizeof(struct erofs_dirent); /* XXX: a bit hacky, but to avoid another traversal */ if (d->validnid && d->type == EROFS_FT_DIR) { ret = erofs_rebuild_inode_fix_pnid(dir, d->nid); if (ret) return ret; } erofs_d_invalidate(d); if (used + len > erofs_blksiz(sbi)) { ret = write_dirblock(sbi, q, head, d, dir->u.i_blkaddr + blkno); if (ret) return ret; head = d; q = used = 0; ++blkno; } used += len; q += sizeof(struct erofs_dirent); } DBG_BUGON(used > erofs_blksiz(sbi)); if (used == erofs_blksiz(sbi)) { DBG_BUGON(dir->i_size % erofs_blksiz(sbi)); DBG_BUGON(dir->idata_size); return write_dirblock(sbi, q, head, d, dir->u.i_blkaddr + blkno); } DBG_BUGON(used != dir->i_size % erofs_blksiz(sbi)); if (used) { /* fill tail-end dir block */ dir->idata = malloc(used); if (!dir->idata) return -ENOMEM; DBG_BUGON(used != dir->idata_size); fill_dirblock(dir->idata, dir->idata_size, q, head, d); } return 0; } int erofs_write_file_from_buffer(struct erofs_inode *inode, char *buf) { struct erofs_sb_info *sbi = inode->sbi; const unsigned int nblocks = erofs_blknr(sbi, inode->i_size); int ret; inode->datalayout = EROFS_INODE_FLAT_INLINE; ret = erofs_allocate_inode_bh_data(inode, nblocks); if (ret) return ret; if (nblocks) erofs_blk_write(sbi, buf, inode->u.i_blkaddr, nblocks); inode->idata_size = inode->i_size % erofs_blksiz(sbi); if (inode->idata_size) { inode->idata = malloc(inode->idata_size); if (!inode->idata) return -ENOMEM; memcpy(inode->idata, buf + erofs_pos(sbi, nblocks), inode->idata_size); } return 0; } /* rules to decide whether a file could be compressed or not */ static bool erofs_file_is_compressible(struct erofs_inode *inode) { if (cfg.c_compress_hints_file) return z_erofs_apply_compress_hints(inode); return true; } static int write_uncompressed_file_from_fd(struct erofs_inode *inode, int fd) { struct erofs_sb_info *sbi = inode->sbi; erofs_blk_t nblocks, i; unsigned int len; int ret; inode->datalayout = EROFS_INODE_FLAT_INLINE; nblocks = inode->i_size >> sbi->blkszbits; ret = erofs_allocate_inode_bh_data(inode, nblocks); if (ret) return ret; for (i = 0; i < nblocks; i += (len >> sbi->blkszbits)) { len = min_t(u64, round_down(UINT_MAX, 1U << sbi->blkszbits), erofs_pos(sbi, nblocks - i)); ret = erofs_io_xcopy(&sbi->bdev, erofs_pos(sbi, inode->u.i_blkaddr + i), &((struct erofs_vfile){ .fd = fd }), len, inode->datasource == EROFS_INODE_DATA_SOURCE_DISKBUF); if (ret) return ret; } /* read the tail-end data */ inode->idata_size = inode->i_size % erofs_blksiz(sbi); if (inode->idata_size) { inode->idata = malloc(inode->idata_size); if (!inode->idata) return -ENOMEM; ret = read(fd, inode->idata, inode->idata_size); if (ret < inode->idata_size) { free(inode->idata); inode->idata = NULL; return -EIO; } } erofs_droid_blocklist_write(inode, inode->u.i_blkaddr, nblocks); return 0; } int erofs_write_unencoded_file(struct erofs_inode *inode, int fd, u64 fpos) { if (cfg.c_chunkbits) { inode->u.chunkbits = cfg.c_chunkbits; /* chunk indexes when explicitly specified */ inode->u.chunkformat = 0; if (cfg.c_force_chunkformat == FORCE_INODE_CHUNK_INDEXES) inode->u.chunkformat = EROFS_CHUNK_FORMAT_INDEXES; return erofs_blob_write_chunked_file(inode, fd, fpos); } /* fallback to all data uncompressed */ return write_uncompressed_file_from_fd(inode, fd); } int erofs_iflush(struct erofs_inode *inode) { const u16 icount = EROFS_INODE_XATTR_ICOUNT(inode->xattr_isize); struct erofs_sb_info *sbi = inode->sbi; erofs_off_t off; union { struct erofs_inode_compact dic; struct erofs_inode_extended die; } u = {}; union erofs_inode_i_u u1; int ret; if (inode->bh) off = erofs_btell(inode->bh, false); else off = erofs_iloc(inode); if (S_ISCHR(inode->i_mode) || S_ISBLK(inode->i_mode) || S_ISFIFO(inode->i_mode) || S_ISSOCK(inode->i_mode)) u1.rdev = cpu_to_le32(inode->u.i_rdev); else if (is_inode_layout_compression(inode)) u1.compressed_blocks = cpu_to_le32(inode->u.i_blocks); else if (inode->datalayout == EROFS_INODE_CHUNK_BASED) u1.c.format = cpu_to_le16(inode->u.chunkformat); else u1.raw_blkaddr = cpu_to_le32(inode->u.i_blkaddr); switch (inode->inode_isize) { case sizeof(struct erofs_inode_compact): u.dic.i_format = cpu_to_le16(0 | (inode->datalayout << 1)); u.dic.i_xattr_icount = cpu_to_le16(icount); u.dic.i_mode = cpu_to_le16(inode->i_mode); u.dic.i_nlink = cpu_to_le16(inode->i_nlink); u.dic.i_size = cpu_to_le32((u32)inode->i_size); u.dic.i_ino = cpu_to_le32(inode->i_ino[0]); u.dic.i_uid = cpu_to_le16((u16)inode->i_uid); u.dic.i_gid = cpu_to_le16((u16)inode->i_gid); u.dic.i_u = u1; break; case sizeof(struct erofs_inode_extended): u.die.i_format = cpu_to_le16(1 | (inode->datalayout << 1)); u.die.i_xattr_icount = cpu_to_le16(icount); u.die.i_mode = cpu_to_le16(inode->i_mode); u.die.i_nlink = cpu_to_le32(inode->i_nlink); u.die.i_size = cpu_to_le64(inode->i_size); u.die.i_ino = cpu_to_le32(inode->i_ino[0]); u.die.i_uid = cpu_to_le32(inode->i_uid); u.die.i_gid = cpu_to_le32(inode->i_gid); u.die.i_mtime = cpu_to_le64(inode->i_mtime); u.die.i_mtime_nsec = cpu_to_le32(inode->i_mtime_nsec); u.die.i_u = u1; break; default: erofs_err("unsupported on-disk inode version of nid %llu", (unsigned long long)inode->nid); BUG_ON(1); } ret = erofs_dev_write(sbi, &u, off, inode->inode_isize); if (ret) return ret; off += inode->inode_isize; if (inode->xattr_isize) { char *xattrs = erofs_export_xattr_ibody(inode); if (IS_ERR(xattrs)) return PTR_ERR(xattrs); ret = erofs_dev_write(sbi, xattrs, off, inode->xattr_isize); free(xattrs); if (ret) return ret; off += inode->xattr_isize; } if (inode->extent_isize) { if (inode->datalayout == EROFS_INODE_CHUNK_BASED) { ret = erofs_blob_write_chunk_indexes(inode, off); if (ret) return ret; } else { /* write compression metadata */ off = roundup(off, 8); ret = erofs_dev_write(sbi, inode->compressmeta, off, inode->extent_isize); if (ret) return ret; } } return 0; } static int erofs_bh_flush_write_inode(struct erofs_buffer_head *bh) { struct erofs_inode *inode = bh->fsprivate; int ret; DBG_BUGON(inode->bh != bh); ret = erofs_iflush(inode); if (ret) return ret; inode->bh = NULL; erofs_iput(inode); return erofs_bh_flush_generic_end(bh); } static struct erofs_bhops erofs_write_inode_bhops = { .flush = erofs_bh_flush_write_inode, }; static int erofs_prepare_tail_block(struct erofs_inode *inode) { struct erofs_sb_info *sbi = inode->sbi; struct erofs_buffer_head *bh; int ret; if (!inode->idata_size) return 0; bh = inode->bh_data; if (bh) { /* expend a block as the tail block (should be successful) */ ret = erofs_bh_balloon(bh, erofs_blksiz(sbi)); if (ret != erofs_blksiz(sbi)) { DBG_BUGON(1); return -EIO; } } else { inode->lazy_tailblock = true; } if (is_inode_layout_compression(inode)) inode->u.i_blocks += 1; return 0; } static int erofs_prepare_inode_buffer(struct erofs_inode *inode) { struct erofs_bufmgr *bmgr = inode->sbi->bmgr; unsigned int inodesize; struct erofs_buffer_head *bh, *ibh; DBG_BUGON(inode->bh || inode->bh_inline); inodesize = inode->inode_isize + inode->xattr_isize; if (inode->extent_isize) inodesize = roundup(inodesize, 8) + inode->extent_isize; if (inode->datalayout == EROFS_INODE_FLAT_PLAIN) goto noinline; /* TODO: tailpacking inline of chunk-based format isn't finalized */ if (inode->datalayout == EROFS_INODE_CHUNK_BASED) goto noinline; if (!is_inode_layout_compression(inode)) { if (!cfg.c_inline_data && S_ISREG(inode->i_mode)) { inode->datalayout = EROFS_INODE_FLAT_PLAIN; goto noinline; } /* * If the file sizes of uncompressed files are block-aligned, * should use the EROFS_INODE_FLAT_PLAIN data layout. */ if (!inode->idata_size) inode->datalayout = EROFS_INODE_FLAT_PLAIN; } bh = erofs_balloc(bmgr, INODE, inodesize, inode->idata_size); if (bh == ERR_PTR(-ENOSPC)) { int ret; if (is_inode_layout_compression(inode)) z_erofs_drop_inline_pcluster(inode); else inode->datalayout = EROFS_INODE_FLAT_PLAIN; noinline: /* expend an extra block for tail-end data */ ret = erofs_prepare_tail_block(inode); if (ret) return ret; bh = erofs_balloc(bmgr, INODE, inodesize, 0); if (IS_ERR(bh)) return PTR_ERR(bh); DBG_BUGON(inode->bh_inline); } else if (IS_ERR(bh)) { return PTR_ERR(bh); } else if (inode->idata_size) { if (is_inode_layout_compression(inode)) { DBG_BUGON(!cfg.c_ztailpacking); erofs_dbg("Inline %scompressed data (%u bytes) to %s", inode->compressed_idata ? "" : "un", inode->idata_size, inode->i_srcpath); erofs_sb_set_ztailpacking(inode->sbi); } else { inode->datalayout = EROFS_INODE_FLAT_INLINE; erofs_dbg("Inline tail-end data (%u bytes) to %s", inode->idata_size, inode->i_srcpath); } /* allocate inline buffer */ ibh = erofs_battach(bh, META, inode->idata_size); if (IS_ERR(ibh)) return PTR_ERR(ibh); ibh->op = &erofs_skip_write_bhops; inode->bh_inline = ibh; } bh->fsprivate = erofs_igrab(inode); bh->op = &erofs_write_inode_bhops; inode->bh = bh; inode->i_ino[0] = ++inode->sbi->inos; /* inode serial number */ return 0; } static int erofs_bh_flush_write_inline(struct erofs_buffer_head *bh) { struct erofs_inode *const inode = bh->fsprivate; const erofs_off_t off = erofs_btell(bh, false); int ret; ret = erofs_dev_write(inode->sbi, inode->idata, off, inode->idata_size); if (ret) return ret; free(inode->idata); inode->idata = NULL; erofs_iput(inode); return erofs_bh_flush_generic_end(bh); } static struct erofs_bhops erofs_write_inline_bhops = { .flush = erofs_bh_flush_write_inline, }; static int erofs_write_tail_end(struct erofs_inode *inode) { struct erofs_sb_info *sbi = inode->sbi; struct erofs_buffer_head *bh, *ibh; bh = inode->bh_data; if (!inode->idata_size) goto out; DBG_BUGON(!inode->idata); /* have enough room to inline data */ if (inode->bh_inline) { ibh = inode->bh_inline; ibh->fsprivate = erofs_igrab(inode); ibh->op = &erofs_write_inline_bhops; erofs_droid_blocklist_write_tail_end(inode, NULL_ADDR); } else { int ret; erofs_off_t pos, zero_pos; if (!bh) { bh = erofs_balloc(sbi->bmgr, S_ISDIR(inode->i_mode) ? DIRA: DATA, erofs_blksiz(sbi), 0); if (IS_ERR(bh)) return PTR_ERR(bh); bh->op = &erofs_skip_write_bhops; /* get blkaddr of bh */ ret = erofs_mapbh(NULL, bh->block); inode->u.i_blkaddr = bh->block->blkaddr; inode->bh_data = bh; } else { if (inode->lazy_tailblock) { /* expend a tail block (should be successful) */ ret = erofs_bh_balloon(bh, erofs_blksiz(sbi)); if (ret != erofs_blksiz(sbi)) { DBG_BUGON(1); return -EIO; } inode->lazy_tailblock = false; } ret = erofs_mapbh(NULL, bh->block); } DBG_BUGON(ret < 0); pos = erofs_btell(bh, true) - erofs_blksiz(sbi); /* 0'ed data should be padded at head for 0padding conversion */ if (erofs_sb_has_lz4_0padding(sbi) && inode->compressed_idata) { zero_pos = pos; pos += erofs_blksiz(sbi) - inode->idata_size; } else { /* pad 0'ed data for the other cases */ zero_pos = pos + inode->idata_size; } ret = erofs_dev_write(sbi, inode->idata, pos, inode->idata_size); if (ret) return ret; DBG_BUGON(inode->idata_size > erofs_blksiz(sbi)); if (inode->idata_size < erofs_blksiz(sbi)) { ret = erofs_dev_fillzero(sbi, zero_pos, erofs_blksiz(sbi) - inode->idata_size, false); if (ret) return ret; } inode->idata_size = 0; free(inode->idata); inode->idata = NULL; erofs_droid_blocklist_write_tail_end(inode, erofs_blknr(sbi, pos)); } out: /* now bh_data can drop directly */ if (bh) { /* * Don't leave DATA buffers which were written in the global * buffer list. It will make balloc() slowly. */ erofs_bdrop(bh, false); inode->bh_data = NULL; } return 0; } static bool erofs_should_use_inode_extended(struct erofs_inode *inode) { if (cfg.c_force_inodeversion == FORCE_INODE_EXTENDED) return true; if (inode->i_size > UINT_MAX) return true; if (erofs_is_packed_inode(inode)) return false; if (inode->i_uid > USHRT_MAX) return true; if (inode->i_gid > USHRT_MAX) return true; if (inode->i_nlink > USHRT_MAX) return true; if ((inode->i_mtime != inode->sbi->build_time || inode->i_mtime_nsec != inode->sbi->build_time_nsec) && !cfg.c_ignore_mtime) return true; return false; } u32 erofs_new_encode_dev(dev_t dev) { const unsigned int major = major(dev); const unsigned int minor = minor(dev); return (minor & 0xff) | (major << 8) | ((minor & ~0xff) << 12); } #ifdef WITH_ANDROID int erofs_droid_inode_fsconfig(struct erofs_inode *inode, struct stat *st, const char *path) { /* filesystem_config does not preserve file type bits */ mode_t stat_file_type_mask = st->st_mode & S_IFMT; unsigned int uid = 0, gid = 0, mode = 0; const char *fspath; char *decorated = NULL; inode->capabilities = 0; if (!cfg.fs_config_file && !cfg.mount_point) return 0; /* avoid loading special inodes */ if (path == EROFS_PACKED_INODE) return 0; if (!cfg.mount_point || /* have to drop the mountpoint for rootdir of canned fsconfig */ (cfg.fs_config_file && erofs_fspath(path)[0] == '\0')) { fspath = erofs_fspath(path); } else { if (asprintf(&decorated, "%s/%s", cfg.mount_point, erofs_fspath(path)) <= 0) return -ENOMEM; fspath = decorated; } if (cfg.fs_config_file) canned_fs_config(fspath, S_ISDIR(st->st_mode), cfg.target_out_path, &uid, &gid, &mode, &inode->capabilities); else fs_config(fspath, S_ISDIR(st->st_mode), cfg.target_out_path, &uid, &gid, &mode, &inode->capabilities); erofs_dbg("/%s -> mode = 0x%x, uid = 0x%x, gid = 0x%x, capabilities = 0x%" PRIx64, fspath, mode, uid, gid, inode->capabilities); if (decorated) free(decorated); st->st_uid = uid; st->st_gid = gid; st->st_mode = mode | stat_file_type_mask; return 0; } #else static int erofs_droid_inode_fsconfig(struct erofs_inode *inode, struct stat *st, const char *path) { return 0; } #endif int __erofs_fill_inode(struct erofs_inode *inode, struct stat *st, const char *path) { int err = erofs_droid_inode_fsconfig(inode, st, path); struct erofs_sb_info *sbi = inode->sbi; if (err) return err; inode->i_uid = cfg.c_uid == -1 ? st->st_uid : cfg.c_uid; inode->i_gid = cfg.c_gid == -1 ? st->st_gid : cfg.c_gid; if (inode->i_uid + cfg.c_uid_offset < 0) erofs_err("uid overflow @ %s", path); inode->i_uid += cfg.c_uid_offset; if (inode->i_gid + cfg.c_gid_offset < 0) erofs_err("gid overflow @ %s", path); inode->i_gid += cfg.c_gid_offset; inode->i_mtime = st->st_mtime; inode->i_mtime_nsec = ST_MTIM_NSEC(st); switch (cfg.c_timeinherit) { case TIMESTAMP_CLAMPING: if (inode->i_mtime < sbi->build_time) break; case TIMESTAMP_FIXED: inode->i_mtime = sbi->build_time; inode->i_mtime_nsec = sbi->build_time_nsec; default: break; } return 0; } static int erofs_fill_inode(struct erofs_inode *inode, struct stat *st, const char *path) { int err = __erofs_fill_inode(inode, st, path); if (err) return err; inode->i_mode = st->st_mode; inode->i_nlink = 1; /* fix up later if needed */ switch (inode->i_mode & S_IFMT) { case S_IFCHR: case S_IFBLK: case S_IFIFO: case S_IFSOCK: inode->u.i_rdev = erofs_new_encode_dev(st->st_rdev); case S_IFDIR: inode->i_size = 0; break; case S_IFREG: case S_IFLNK: inode->i_size = st->st_size; break; default: return -EINVAL; } inode->i_srcpath = strdup(path); if (!inode->i_srcpath) return -ENOMEM; if (erofs_should_use_inode_extended(inode)) { if (cfg.c_force_inodeversion == FORCE_INODE_COMPACT) { erofs_err("file %s cannot be in compact form", inode->i_srcpath); return -EINVAL; } inode->inode_isize = sizeof(struct erofs_inode_extended); } else { inode->inode_isize = sizeof(struct erofs_inode_compact); } inode->dev = st->st_dev; inode->i_ino[1] = st->st_ino; erofs_insert_ihash(inode); return 0; } struct erofs_inode *erofs_new_inode(struct erofs_sb_info *sbi) { struct erofs_inode *inode; inode = calloc(1, sizeof(struct erofs_inode)); if (!inode) return ERR_PTR(-ENOMEM); inode->sbi = sbi; inode->i_count = 1; inode->datalayout = EROFS_INODE_FLAT_PLAIN; init_list_head(&inode->i_hash); init_list_head(&inode->i_subdirs); init_list_head(&inode->i_xattrs); return inode; } /* get the inode from the source path */ static struct erofs_inode *erofs_iget_from_srcpath(struct erofs_sb_info *sbi, const char *path) { struct stat st; struct erofs_inode *inode; int ret; ret = lstat(path, &st); if (ret) return ERR_PTR(-errno); /* * lookup in hash table first, if it already exists we have a * hard-link, just return it. Also don't lookup for directories * since hard-link directory isn't allowed. */ if (!S_ISDIR(st.st_mode) && (!cfg.c_hard_dereference)) { inode = erofs_iget(st.st_dev, st.st_ino); if (inode) return inode; } /* cannot find in the inode cache */ inode = erofs_new_inode(sbi); if (IS_ERR(inode)) return inode; ret = erofs_fill_inode(inode, &st, path); if (ret) { erofs_iput(inode); return ERR_PTR(ret); } return inode; } static void erofs_fixup_meta_blkaddr(struct erofs_inode *rootdir) { const erofs_off_t rootnid_maxoffset = 0xffff << EROFS_ISLOTBITS; struct erofs_buffer_head *const bh = rootdir->bh; struct erofs_sb_info *sbi = rootdir->sbi; erofs_off_t off, meta_offset; erofs_mapbh(NULL, bh->block); off = erofs_btell(bh, false); if (off > rootnid_maxoffset) meta_offset = round_up(off - rootnid_maxoffset, erofs_blksiz(sbi)); else meta_offset = 0; sbi->meta_blkaddr = erofs_blknr(sbi, meta_offset); rootdir->nid = (off - meta_offset) >> EROFS_ISLOTBITS; } static int erofs_inode_reserve_data_blocks(struct erofs_inode *inode) { struct erofs_sb_info *sbi = inode->sbi; erofs_off_t alignedsz = round_up(inode->i_size, erofs_blksiz(sbi)); erofs_blk_t nblocks = alignedsz >> sbi->blkszbits; struct erofs_buffer_head *bh; /* allocate data blocks */ bh = erofs_balloc(sbi->bmgr, DATA, alignedsz, 0); if (IS_ERR(bh)) return PTR_ERR(bh); /* get blkaddr of the bh */ (void)erofs_mapbh(NULL, bh->block); /* write blocks except for the tail-end block */ inode->u.i_blkaddr = bh->block->blkaddr; erofs_bdrop(bh, false); inode->datalayout = EROFS_INODE_FLAT_PLAIN; tarerofs_blocklist_write(inode->u.i_blkaddr, nblocks, inode->i_ino[1], alignedsz - inode->i_size); return 0; } struct erofs_mkfs_job_ndir_ctx { struct erofs_inode *inode; void *ictx; int fd; u64 fpos; }; static int erofs_mkfs_job_write_file(struct erofs_mkfs_job_ndir_ctx *ctx) { struct erofs_inode *inode = ctx->inode; int ret; if (inode->datasource == EROFS_INODE_DATA_SOURCE_DISKBUF && lseek(ctx->fd, ctx->fpos, SEEK_SET) < 0) { ret = -errno; goto out; } if (ctx->ictx) { ret = erofs_write_compressed_file(ctx->ictx); if (ret != -ENOSPC) goto out; if (lseek(ctx->fd, ctx->fpos, SEEK_SET) < 0) { ret = -errno; goto out; } } /* fallback to all data uncompressed */ ret = erofs_write_unencoded_file(inode, ctx->fd, ctx->fpos); out: if (inode->datasource == EROFS_INODE_DATA_SOURCE_DISKBUF) { erofs_diskbuf_close(inode->i_diskbuf); free(inode->i_diskbuf); inode->i_diskbuf = NULL; inode->datasource = EROFS_INODE_DATA_SOURCE_NONE; } else { close(ctx->fd); } return ret; } static int erofs_mkfs_handle_nondirectory(struct erofs_mkfs_job_ndir_ctx *ctx) { struct erofs_inode *inode = ctx->inode; int ret = 0; if (S_ISLNK(inode->i_mode)) { char *symlink = inode->i_link; if (!symlink) { symlink = malloc(inode->i_size); if (!symlink) return -ENOMEM; ret = readlink(inode->i_srcpath, symlink, inode->i_size); if (ret < 0) { free(symlink); return -errno; } } ret = erofs_write_file_from_buffer(inode, symlink); free(symlink); inode->i_link = NULL; } else if (inode->i_size) { if (inode->datasource == EROFS_INODE_DATA_SOURCE_RESVSP) ret = erofs_inode_reserve_data_blocks(inode); else if (ctx->fd >= 0) ret = erofs_mkfs_job_write_file(ctx); } if (ret) return ret; erofs_prepare_inode_buffer(inode); erofs_write_tail_end(inode); return 0; } enum erofs_mkfs_jobtype { /* ordered job types */ EROFS_MKFS_JOB_NDIR, EROFS_MKFS_JOB_DIR, EROFS_MKFS_JOB_DIR_BH, EROFS_MKFS_JOB_MAX }; struct erofs_mkfs_jobitem { enum erofs_mkfs_jobtype type; union { struct erofs_inode *inode; struct erofs_mkfs_job_ndir_ctx ndir; } u; }; static int erofs_mkfs_jobfn(struct erofs_mkfs_jobitem *item) { struct erofs_inode *inode = item->u.inode; int ret; if (item->type == EROFS_MKFS_JOB_NDIR) return erofs_mkfs_handle_nondirectory(&item->u.ndir); if (item->type == EROFS_MKFS_JOB_DIR) { ret = erofs_prepare_inode_buffer(inode); if (ret) return ret; inode->bh->op = &erofs_skip_write_bhops; return 0; } if (item->type == EROFS_MKFS_JOB_DIR_BH) { ret = erofs_write_dir_file(inode); if (ret) return ret; erofs_write_tail_end(inode); inode->bh->op = &erofs_write_inode_bhops; erofs_iput(inode); return 0; } return -EINVAL; } #ifdef EROFS_MT_ENABLED struct erofs_mkfs_dfops { pthread_t worker; pthread_mutex_t lock; pthread_cond_t full, empty, drain; struct erofs_mkfs_jobitem *queue; unsigned int entries, head, tail; bool idle; /* initialize as false before the dfops worker runs */ }; #define EROFS_MT_QUEUE_SIZE 256 static void erofs_mkfs_flushjobs(struct erofs_sb_info *sbi) { struct erofs_mkfs_dfops *q = sbi->mkfs_dfops; pthread_mutex_lock(&q->lock); if (!q->idle) pthread_cond_wait(&q->drain, &q->lock); pthread_mutex_unlock(&q->lock); } static void *erofs_mkfs_pop_jobitem(struct erofs_mkfs_dfops *q) { struct erofs_mkfs_jobitem *item; pthread_mutex_lock(&q->lock); while (q->head == q->tail) { /* the worker has handled everything only if sleeping here */ q->idle = true; pthread_cond_signal(&q->drain); pthread_cond_wait(&q->empty, &q->lock); } item = q->queue + q->head; q->head = (q->head + 1) & (q->entries - 1); pthread_cond_signal(&q->full); pthread_mutex_unlock(&q->lock); return item; } static void *z_erofs_mt_dfops_worker(void *arg) { struct erofs_sb_info *sbi = arg; int ret = 0; while (1) { struct erofs_mkfs_jobitem *item; item = erofs_mkfs_pop_jobitem(sbi->mkfs_dfops); if (item->type >= EROFS_MKFS_JOB_MAX) break; ret = erofs_mkfs_jobfn(item); if (ret) break; } pthread_exit((void *)(uintptr_t)ret); } static int erofs_mkfs_go(struct erofs_sb_info *sbi, enum erofs_mkfs_jobtype type, void *elem, int size) { struct erofs_mkfs_jobitem *item; struct erofs_mkfs_dfops *q = sbi->mkfs_dfops; pthread_mutex_lock(&q->lock); while (((q->tail + 1) & (q->entries - 1)) == q->head) pthread_cond_wait(&q->full, &q->lock); item = q->queue + q->tail; item->type = type; if (size) memcpy(&item->u, elem, size); q->tail = (q->tail + 1) & (q->entries - 1); q->idle = false; pthread_cond_signal(&q->empty); pthread_mutex_unlock(&q->lock); return 0; } #else static int erofs_mkfs_go(struct erofs_sb_info *sbi, enum erofs_mkfs_jobtype type, void *elem, int size) { struct erofs_mkfs_jobitem item; item.type = type; memcpy(&item.u, elem, size); return erofs_mkfs_jobfn(&item); } static void erofs_mkfs_flushjobs(struct erofs_sb_info *sbi) { } #endif static int erofs_mkfs_handle_directory(struct erofs_inode *dir) { struct erofs_sb_info *sbi = dir->sbi; DIR *_dir; struct dirent *dp; struct erofs_dentry *d; unsigned int nr_subdirs, i_nlink; int ret; _dir = opendir(dir->i_srcpath); if (!_dir) { erofs_err("failed to opendir at %s: %s", dir->i_srcpath, erofs_strerror(-errno)); return -errno; } nr_subdirs = 0; i_nlink = 0; while (1) { char buf[PATH_MAX]; struct erofs_inode *inode; /* * set errno to 0 before calling readdir() in order to * distinguish end of stream and from an error. */ errno = 0; dp = readdir(_dir); if (!dp) { if (!errno) break; ret = -errno; goto err_closedir; } if (is_dot_dotdot(dp->d_name)) { ++i_nlink; continue; } /* skip if it's a exclude file */ if (erofs_is_exclude_path(dir->i_srcpath, dp->d_name)) continue; d = erofs_d_alloc(dir, dp->d_name); if (IS_ERR(d)) { ret = PTR_ERR(d); goto err_closedir; } ret = snprintf(buf, PATH_MAX, "%s/%s", dir->i_srcpath, d->name); if (ret < 0 || ret >= PATH_MAX) goto err_closedir; inode = erofs_iget_from_srcpath(sbi, buf); if (IS_ERR(inode)) { ret = PTR_ERR(inode); goto err_closedir; } d->inode = inode; d->type = erofs_mode_to_ftype(inode->i_mode); i_nlink += S_ISDIR(inode->i_mode); erofs_dbg("file %s added (type %u)", buf, d->type); nr_subdirs++; } closedir(_dir); ret = erofs_init_empty_dir(dir); if (ret) return ret; ret = erofs_prepare_dir_file(dir, nr_subdirs + 2); /* sort subdirs */ if (ret) return ret; /* * if there're too many subdirs as compact form, set nlink=1 * rather than upgrade to use extented form instead. */ if (i_nlink > USHRT_MAX && dir->inode_isize == sizeof(struct erofs_inode_compact)) dir->i_nlink = 1; else dir->i_nlink = i_nlink; return erofs_mkfs_go(sbi, EROFS_MKFS_JOB_DIR, &dir, sizeof(dir)); err_closedir: closedir(_dir); return ret; } int erofs_rebuild_load_basedir(struct erofs_inode *dir); bool erofs_dentry_is_wht(struct erofs_sb_info *sbi, struct erofs_dentry *d) { if (!d->validnid) return erofs_inode_is_whiteout(d->inode); if (d->type == EROFS_FT_CHRDEV) { struct erofs_inode ei = { .sbi = sbi, .nid = d->nid }; int ret; ret = erofs_read_inode_from_disk(&ei); if (ret) { erofs_err("failed to check DT_WHT: %s", erofs_strerror(ret)); DBG_BUGON(1); return false; } return erofs_inode_is_whiteout(&ei); } return false; } static int erofs_rebuild_handle_directory(struct erofs_inode *dir, bool incremental) { struct erofs_sb_info *sbi = dir->sbi; struct erofs_dentry *d, *n; unsigned int nr_subdirs, i_nlink; bool delwht = cfg.c_ovlfs_strip && dir->whiteouts; int ret; nr_subdirs = 0; i_nlink = 0; list_for_each_entry_safe(d, n, &dir->i_subdirs, d_child) { if (delwht && erofs_dentry_is_wht(sbi, d)) { erofs_dbg("remove whiteout %s", d->inode->i_srcpath); list_del(&d->d_child); erofs_d_invalidate(d); free(d); continue; } i_nlink += (d->type == EROFS_FT_DIR); ++nr_subdirs; } DBG_BUGON(i_nlink < 2); /* should have `.` and `..` */ DBG_BUGON(nr_subdirs < i_nlink); ret = erofs_prepare_dir_file(dir, nr_subdirs); if (ret) return ret; if (IS_ROOT(dir) && incremental) dir->datalayout = EROFS_INODE_FLAT_PLAIN; /* * if there're too many subdirs as compact form, set nlink=1 * rather than upgrade to use extented form instead. */ if (i_nlink > USHRT_MAX && dir->inode_isize == sizeof(struct erofs_inode_compact)) dir->i_nlink = 1; else dir->i_nlink = i_nlink; return erofs_mkfs_go(sbi, EROFS_MKFS_JOB_DIR, &dir, sizeof(dir)); } static int erofs_mkfs_handle_inode(struct erofs_inode *inode) { const char *relpath = erofs_fspath(inode->i_srcpath); char *trimmed; int ret; trimmed = erofs_trim_for_progressinfo(relpath[0] ? relpath : "/", sizeof("Processing ...") - 1); erofs_update_progressinfo("Processing %s ...", trimmed); free(trimmed); ret = erofs_scan_file_xattrs(inode); if (ret < 0) return ret; ret = erofs_prepare_xattr_ibody(inode, false); if (ret < 0) return ret; if (!S_ISDIR(inode->i_mode)) { struct erofs_mkfs_job_ndir_ctx ctx = { .inode = inode }; if (!S_ISLNK(inode->i_mode) && inode->i_size) { ctx.fd = open(inode->i_srcpath, O_RDONLY | O_BINARY); if (ctx.fd < 0) return -errno; if (cfg.c_compr_opts[0].alg && erofs_file_is_compressible(inode)) { ctx.ictx = erofs_begin_compressed_file(inode, ctx.fd, 0); if (IS_ERR(ctx.ictx)) return PTR_ERR(ctx.ictx); } } ret = erofs_mkfs_go(inode->sbi, EROFS_MKFS_JOB_NDIR, &ctx, sizeof(ctx)); } else { ret = erofs_mkfs_handle_directory(inode); } erofs_info("file /%s dumped (mode %05o)", relpath, inode->i_mode); return ret; } static int erofs_rebuild_handle_inode(struct erofs_inode *inode, bool incremental) { char *trimmed; int ret; trimmed = erofs_trim_for_progressinfo(erofs_fspath(inode->i_srcpath), sizeof("Processing ...") - 1); erofs_update_progressinfo("Processing %s ...", trimmed); free(trimmed); if (erofs_should_use_inode_extended(inode)) { if (cfg.c_force_inodeversion == FORCE_INODE_COMPACT) { erofs_err("file %s cannot be in compact form", inode->i_srcpath); return -EINVAL; } inode->inode_isize = sizeof(struct erofs_inode_extended); } else { inode->inode_isize = sizeof(struct erofs_inode_compact); } if (incremental && S_ISDIR(inode->i_mode) && inode->dev == inode->sbi->dev && !inode->opaque) { ret = erofs_rebuild_load_basedir(inode); if (ret) return ret; } /* strip all unnecessary overlayfs xattrs when ovlfs_strip is enabled */ if (cfg.c_ovlfs_strip) erofs_clear_opaque_xattr(inode); else if (inode->whiteouts) erofs_set_origin_xattr(inode); ret = erofs_prepare_xattr_ibody(inode, incremental && IS_ROOT(inode)); if (ret < 0) return ret; if (!S_ISDIR(inode->i_mode)) { struct erofs_mkfs_job_ndir_ctx ctx = { .inode = inode, .fd = -1 }; if (S_ISREG(inode->i_mode) && inode->i_size && inode->datasource == EROFS_INODE_DATA_SOURCE_DISKBUF) { ctx.fd = erofs_diskbuf_getfd(inode->i_diskbuf, &ctx.fpos); if (ctx.fd < 0) return ret; if (cfg.c_compr_opts[0].alg && erofs_file_is_compressible(inode)) { ctx.ictx = erofs_begin_compressed_file(inode, ctx.fd, ctx.fpos); if (IS_ERR(ctx.ictx)) return PTR_ERR(ctx.ictx); } } ret = erofs_mkfs_go(inode->sbi, EROFS_MKFS_JOB_NDIR, &ctx, sizeof(ctx)); } else { ret = erofs_rebuild_handle_directory(inode, incremental); } erofs_info("file %s dumped (mode %05o)", erofs_fspath(inode->i_srcpath), inode->i_mode); return ret; } static bool erofs_inode_visited(struct erofs_inode *inode) { return (unsigned long)inode->i_parent & 1UL; } static void erofs_mark_parent_inode(struct erofs_inode *inode, struct erofs_inode *dir) { inode->i_parent = (void *)((unsigned long)dir | 1); } static int erofs_mkfs_dump_tree(struct erofs_inode *root, bool rebuild, bool incremental) { struct erofs_sb_info *sbi = root->sbi; struct erofs_inode *dumpdir = erofs_igrab(root); int err, err2; erofs_mark_parent_inode(root, root); /* rootdir mark */ root->next_dirwrite = NULL; /* update dev/i_ino[1] to keep track of the base image */ if (incremental) { root->dev = root->sbi->dev; root->i_ino[1] = sbi->root_nid; list_del(&root->i_hash); erofs_insert_ihash(root); } else if (cfg.c_root_xattr_isize) { if (cfg.c_root_xattr_isize > EROFS_XATTR_ALIGN( UINT16_MAX - sizeof(struct erofs_xattr_entry))) { erofs_err("Invalid configuration for c_root_xattr_isize: %u (too large)", cfg.c_root_xattr_isize); return -EINVAL; } root->xattr_isize = cfg.c_root_xattr_isize; } err = !rebuild ? erofs_mkfs_handle_inode(root) : erofs_rebuild_handle_inode(root, incremental); if (err) return err; /* assign root NID immediately for non-incremental builds */ if (!incremental) { erofs_mkfs_flushjobs(sbi); erofs_fixup_meta_blkaddr(root); sbi->root_nid = root->nid; } do { struct erofs_inode *dir = dumpdir; /* used for adding sub-directories in reverse order due to FIFO */ struct erofs_inode *head, **last = &head; struct erofs_dentry *d; dumpdir = dir->next_dirwrite; list_for_each_entry(d, &dir->i_subdirs, d_child) { struct erofs_inode *inode = d->inode; if (is_dot_dotdot(d->name) || d->validnid) continue; if (!erofs_inode_visited(inode)) { DBG_BUGON(rebuild && (inode->i_nlink == 1 || S_ISDIR(inode->i_mode)) && erofs_parent_inode(inode) != dir); erofs_mark_parent_inode(inode, dir); if (!rebuild) err = erofs_mkfs_handle_inode(inode); else err = erofs_rebuild_handle_inode(inode, incremental); if (err) break; if (S_ISDIR(inode->i_mode)) { *last = inode; last = &inode->next_dirwrite; (void)erofs_igrab(inode); } } else if (!rebuild) { ++inode->i_nlink; } } *last = dumpdir; /* fixup the last (or the only) one */ dumpdir = head; err2 = erofs_mkfs_go(sbi, EROFS_MKFS_JOB_DIR_BH, &dir, sizeof(dir)); if (err || err2) return err ? err : err2; } while (dumpdir); return err; } struct erofs_mkfs_buildtree_ctx { struct erofs_sb_info *sbi; union { const char *path; struct erofs_inode *root; } u; bool incremental; }; #ifndef EROFS_MT_ENABLED #define __erofs_mkfs_build_tree erofs_mkfs_build_tree #endif static int __erofs_mkfs_build_tree(struct erofs_mkfs_buildtree_ctx *ctx) { bool from_path = !!ctx->sbi; struct erofs_inode *root; int err; if (from_path) { root = erofs_iget_from_srcpath(ctx->sbi, ctx->u.path); if (IS_ERR(root)) return PTR_ERR(root); } else { root = ctx->u.root; } err = erofs_mkfs_dump_tree(root, !from_path, ctx->incremental); if (err) { if (from_path) erofs_iput(root); return err; } ctx->u.root = root; return 0; } #ifdef EROFS_MT_ENABLED static int erofs_mkfs_build_tree(struct erofs_mkfs_buildtree_ctx *ctx) { struct erofs_mkfs_dfops *q; int err, err2; struct erofs_sb_info *sbi = ctx->sbi ? ctx->sbi : ctx->u.root->sbi; q = calloc(1, sizeof(*q)); if (!q) return -ENOMEM; q->entries = EROFS_MT_QUEUE_SIZE; q->queue = malloc(q->entries * sizeof(*q->queue)); if (!q->queue) { free(q); return -ENOMEM; } pthread_mutex_init(&q->lock, NULL); pthread_cond_init(&q->empty, NULL); pthread_cond_init(&q->full, NULL); pthread_cond_init(&q->drain, NULL); sbi->mkfs_dfops = q; err = pthread_create(&sbi->dfops_worker, NULL, z_erofs_mt_dfops_worker, sbi); if (err) goto fail; err = __erofs_mkfs_build_tree(ctx); erofs_mkfs_go(sbi, ~0, NULL, 0); err2 = pthread_join(sbi->dfops_worker, NULL); if (!err) err = err2; fail: pthread_cond_destroy(&q->empty); pthread_cond_destroy(&q->full); pthread_cond_destroy(&q->drain); pthread_mutex_destroy(&q->lock); free(q->queue); free(q); return err; } #endif struct erofs_inode *erofs_mkfs_build_tree_from_path(struct erofs_sb_info *sbi, const char *path) { struct erofs_mkfs_buildtree_ctx ctx = { .sbi = sbi, .u.path = path, }; int err; if (!sbi) return ERR_PTR(-EINVAL); err = erofs_mkfs_build_tree(&ctx); if (err) return ERR_PTR(err); return ctx.u.root; } int erofs_rebuild_dump_tree(struct erofs_inode *root, bool incremental) { return erofs_mkfs_build_tree(&((struct erofs_mkfs_buildtree_ctx) { .sbi = NULL, .u.root = root, .incremental = incremental, })); } struct erofs_inode *erofs_mkfs_build_special_from_fd(struct erofs_sb_info *sbi, int fd, const char *name) { struct stat st; struct erofs_inode *inode; void *ictx; int ret; ret = lseek(fd, 0, SEEK_SET); if (ret < 0) return ERR_PTR(-errno); ret = fstat(fd, &st); if (ret) return ERR_PTR(-errno); inode = erofs_new_inode(sbi); if (IS_ERR(inode)) return inode; if (name == EROFS_PACKED_INODE) { st.st_uid = st.st_gid = 0; st.st_nlink = 0; } ret = erofs_fill_inode(inode, &st, name); if (ret) { free(inode); return ERR_PTR(ret); } if (name == EROFS_PACKED_INODE) { inode->sbi->packed_nid = EROFS_PACKED_NID_UNALLOCATED; inode->nid = inode->sbi->packed_nid; } if (cfg.c_compr_opts[0].alg && erofs_file_is_compressible(inode)) { ictx = erofs_begin_compressed_file(inode, fd, 0); if (IS_ERR(ictx)) return ERR_CAST(ictx); DBG_BUGON(!ictx); ret = erofs_write_compressed_file(ictx); if (!ret) goto out; if (ret != -ENOSPC) return ERR_PTR(ret); ret = lseek(fd, 0, SEEK_SET); if (ret < 0) return ERR_PTR(-errno); } ret = write_uncompressed_file_from_fd(inode, fd); if (ret) return ERR_PTR(ret); out: erofs_prepare_inode_buffer(inode); erofs_write_tail_end(inode); return inode; } int erofs_fixup_root_inode(struct erofs_inode *root) { struct erofs_sb_info *sbi = root->sbi; struct erofs_inode oi; unsigned int ondisk_capacity, ondisk_size; char *ibuf; int err; if (sbi->root_nid == root->nid) /* for most mkfs cases */ return 0; if (root->nid <= 0xffff) { sbi->root_nid = root->nid; return 0; } oi = (struct erofs_inode){ .sbi = sbi, .nid = sbi->root_nid }; err = erofs_read_inode_from_disk(&oi); if (err) { erofs_err("failed to read root inode: %s", erofs_strerror(err)); return err; } if (oi.datalayout != EROFS_INODE_FLAT_INLINE && oi.datalayout != EROFS_INODE_FLAT_PLAIN) return -EOPNOTSUPP; ondisk_capacity = oi.inode_isize + oi.xattr_isize; if (oi.datalayout == EROFS_INODE_FLAT_INLINE) ondisk_capacity += erofs_blkoff(sbi, oi.i_size); ondisk_size = root->inode_isize + root->xattr_isize; if (root->extent_isize) ondisk_size = roundup(ondisk_size, 8) + root->extent_isize; ondisk_size += root->idata_size; if (ondisk_size > ondisk_capacity) { erofs_err("no enough room for the root inode from nid %llu", root->nid); return -ENOSPC; } ibuf = malloc(ondisk_size); if (!ibuf) return -ENOMEM; err = erofs_dev_read(sbi, 0, ibuf, erofs_iloc(root), ondisk_size); if (err >= 0) err = erofs_dev_write(sbi, ibuf, erofs_iloc(&oi), ondisk_size); free(ibuf); return err; } struct erofs_inode *erofs_rebuild_make_root(struct erofs_sb_info *sbi) { struct erofs_inode *root; root = erofs_new_inode(sbi); if (IS_ERR(root)) return root; root->i_srcpath = strdup("/"); root->i_mode = S_IFDIR | 0777; root->i_parent = root; root->i_mtime = root->sbi->build_time; root->i_mtime_nsec = root->sbi->build_time_nsec; erofs_init_empty_dir(root); return root; } erofs-utils-1.8.6/lib/io.c000066400000000000000000000270511477440110200153300ustar00rootroot00000000000000// SPDX-License-Identifier: GPL-2.0+ OR Apache-2.0 /* * Copyright (C) 2018 HUAWEI, Inc. * http://www.huawei.com/ * Created by Li Guifu */ #ifndef _LARGEFILE64_SOURCE #define _LARGEFILE64_SOURCE #endif #ifndef _GNU_SOURCE #define _GNU_SOURCE #endif #include #include #include #include "erofs/internal.h" #ifdef HAVE_LINUX_FS_H #include #endif #ifdef HAVE_LINUX_FALLOC_H #include #endif #ifdef HAVE_SYS_STATFS_H #include #endif #define EROFS_MODNAME "erofs_io" #include "erofs/print.h" ssize_t __erofs_io_write(int fd, const void *buf, size_t len) { ssize_t ret, written = 0; do { ret = write(fd, buf, len - written); if (ret <= 0) { if (!ret) break; if (errno != EINTR) { erofs_err("failed to write: %s", strerror(errno)); return -errno; } ret = 0; } buf += ret; written += ret; } while (written < len); return written; } int erofs_io_fstat(struct erofs_vfile *vf, struct stat *buf) { if (__erofs_unlikely(cfg.c_dry_run)) { buf->st_size = 0; buf->st_mode = S_IFREG | 0777; return 0; } if (vf->ops) return vf->ops->fstat(vf, buf); return fstat(vf->fd, buf); } ssize_t erofs_io_pwrite(struct erofs_vfile *vf, const void *buf, u64 pos, size_t len) { ssize_t ret, written = 0; if (__erofs_unlikely(cfg.c_dry_run)) return 0; if (vf->ops) return vf->ops->pwrite(vf, buf, pos, len); pos += vf->offset; do { #ifdef HAVE_PWRITE64 ret = pwrite64(vf->fd, buf, len, (off64_t)pos); #else ret = pwrite(vf->fd, buf, len, (off_t)pos); #endif if (ret <= 0) { if (!ret) break; if (errno != EINTR) { erofs_err("failed to write: %s", strerror(errno)); return -errno; } ret = 0; } buf += ret; pos += ret; written += ret; } while (written < len); return written; } int erofs_io_fsync(struct erofs_vfile *vf) { int ret; if (__erofs_unlikely(cfg.c_dry_run)) return 0; if (vf->ops) return vf->ops->fsync(vf); ret = fsync(vf->fd); if (ret) { erofs_err("failed to fsync(!): %s", strerror(errno)); return -errno; } return 0; } ssize_t erofs_io_fallocate(struct erofs_vfile *vf, u64 offset, size_t len, bool zeroout) { static const char zero[EROFS_MAX_BLOCK_SIZE] = {0}; ssize_t ret; if (__erofs_unlikely(cfg.c_dry_run)) return 0; if (vf->ops) return vf->ops->fallocate(vf, offset, len, zeroout); #if defined(HAVE_FALLOCATE) && defined(FALLOC_FL_PUNCH_HOLE) if (!zeroout && fallocate(vf->fd, FALLOC_FL_PUNCH_HOLE | FALLOC_FL_KEEP_SIZE, offset + vf->offset, len) >= 0) return 0; #endif while (len > EROFS_MAX_BLOCK_SIZE) { ret = erofs_io_pwrite(vf, zero, offset, EROFS_MAX_BLOCK_SIZE); if (ret < 0) return ret; len -= ret; offset += ret; } return erofs_io_pwrite(vf, zero, offset, len) == len ? 0 : -EIO; } int erofs_io_ftruncate(struct erofs_vfile *vf, u64 length) { int ret; struct stat st; if (__erofs_unlikely(cfg.c_dry_run)) return 0; if (vf->ops) return vf->ops->ftruncate(vf, length); ret = fstat(vf->fd, &st); if (ret) { erofs_err("failed to fstat: %s", strerror(errno)); return -errno; } length += vf->offset; if (S_ISBLK(st.st_mode) || st.st_size == length) return 0; return ftruncate(vf->fd, length); } ssize_t erofs_io_pread(struct erofs_vfile *vf, void *buf, u64 pos, size_t len) { ssize_t ret, read = 0; if (__erofs_unlikely(cfg.c_dry_run)) return 0; if (vf->ops) return vf->ops->pread(vf, buf, pos, len); pos += vf->offset; do { #ifdef HAVE_PREAD64 ret = pread64(vf->fd, buf, len, (off64_t)pos); #else ret = pread(vf->fd, buf, len, (off_t)pos); #endif if (ret <= 0) { if (!ret) break; if (errno != EINTR) { erofs_err("failed to read: %s", strerror(errno)); return -errno; } ret = 0; } pos += ret; buf += ret; read += ret; } while (read < len); return read; } static int erofs_get_bdev_size(int fd, u64 *bytes) { errno = ENOTSUP; #ifdef BLKGETSIZE64 if (ioctl(fd, BLKGETSIZE64, bytes) >= 0) return 0; #endif #ifdef BLKGETSIZE { unsigned long size; if (ioctl(fd, BLKGETSIZE, &size) >= 0) { *bytes = ((u64)size << 9); return 0; } } #endif return -errno; } #if defined(__linux__) && !defined(BLKDISCARD) #define BLKDISCARD _IO(0x12, 119) #endif static int erofs_bdev_discard(int fd, u64 block, u64 count) { #ifdef BLKDISCARD u64 range[2] = { block, count }; return ioctl(fd, BLKDISCARD, &range); #else return -EOPNOTSUPP; #endif } int erofs_dev_open(struct erofs_sb_info *sbi, const char *dev, int flags) { bool ro = (flags & O_ACCMODE) == O_RDONLY; bool truncate = flags & O_TRUNC; struct stat st; int fd, ret; #if defined(HAVE_SYS_STATFS_H) && defined(HAVE_FSTATFS) bool again = false; repeat: #endif fd = open(dev, (ro ? O_RDONLY : O_RDWR | O_CREAT) | O_BINARY, 0644); if (fd < 0) { erofs_err("failed to open %s: %s", dev, strerror(errno)); return -errno; } if (ro || !truncate) goto out; ret = fstat(fd, &st); if (ret) { erofs_err("failed to fstat(%s): %s", dev, strerror(errno)); close(fd); return -errno; } switch (st.st_mode & S_IFMT) { case S_IFBLK: ret = erofs_get_bdev_size(fd, &sbi->devsz); if (ret) { erofs_err("failed to get block device size(%s): %s", dev, strerror(errno)); close(fd); return ret; } sbi->devsz = round_down(sbi->devsz, erofs_blksiz(sbi)); ret = erofs_bdev_discard(fd, 0, sbi->devsz); if (ret) erofs_err("failed to erase block device(%s): %s", dev, erofs_strerror(ret)); break; case S_IFREG: if (st.st_size) { #if defined(HAVE_SYS_STATFS_H) && defined(HAVE_FSTATFS) struct statfs stfs; if (again) { close(fd); return -ENOTEMPTY; } /* * fses like EXT4 and BTRFS will flush dirty blocks * after truncate(0) even after the writeback happens * (see kernel commit 7d8f9f7d150d and ccd2506bd431), * which is NOT our intention. Let's work around this. */ if (!fstatfs(fd, &stfs) && (stfs.f_type == 0xEF53 || stfs.f_type == 0x9123683E)) { close(fd); unlink(dev); again = true; goto repeat; } #endif ret = ftruncate(fd, 0); if (ret) { erofs_err("failed to ftruncate(%s).", dev); close(fd); return -errno; } } sbi->devblksz = st.st_blksize; break; default: erofs_err("bad file type (%s, %o).", dev, st.st_mode); close(fd); return -EINVAL; } out: sbi->devname = strdup(dev); if (!sbi->devname) { close(fd); return -ENOMEM; } sbi->bdev.fd = fd; erofs_info("successfully to open %s", dev); return 0; } void erofs_dev_close(struct erofs_sb_info *sbi) { if (!sbi->bdev.ops) close(sbi->bdev.fd); free(sbi->devname); sbi->devname = NULL; sbi->bdev.fd = -1; } void erofs_blob_closeall(struct erofs_sb_info *sbi) { unsigned int i; for (i = 0; i < sbi->nblobs; ++i) close(sbi->blobfd[i]); sbi->nblobs = 0; } int erofs_blob_open_ro(struct erofs_sb_info *sbi, const char *dev) { int fd = open(dev, O_RDONLY | O_BINARY); if (fd < 0) { erofs_err("failed to open(%s).", dev); return -errno; } sbi->blobfd[sbi->nblobs] = fd; erofs_info("successfully to open blob%u %s", sbi->nblobs, dev); ++sbi->nblobs; return 0; } ssize_t erofs_dev_read(struct erofs_sb_info *sbi, int device_id, void *buf, u64 offset, size_t len) { ssize_t read; if (device_id) { if (device_id > sbi->nblobs) { erofs_err("invalid device id %d", device_id); return -EIO; } read = erofs_io_pread(&((struct erofs_vfile) { .fd = sbi->blobfd[device_id - 1], }), buf, offset, len); } else { read = erofs_io_pread(&sbi->bdev, buf, offset, len); } if (read < 0) return read; if (read < len) { erofs_info("reach EOF of device @ %llu, pading with zeroes", offset | 0ULL); memset(buf + read, 0, len - read); } return 0; } static ssize_t __erofs_copy_file_range(int fd_in, u64 *off_in, int fd_out, u64 *off_out, size_t length) { size_t copied = 0; char buf[8192]; /* * Main copying loop. The buffer size is arbitrary and is a * trade-off between stack size consumption, cache usage, and * amortization of system call overhead. */ while (length > 0) { size_t to_read; ssize_t read_count; char *end, *p; to_read = min_t(size_t, length, sizeof(buf)); #ifdef HAVE_PREAD64 read_count = pread64(fd_in, buf, to_read, *off_in); #else read_count = pread(fd_in, buf, to_read, *off_in); #endif if (read_count == 0) /* End of file reached prematurely. */ return copied; if (read_count < 0) { /* Report the number of bytes copied so far. */ if (copied > 0) return copied; return -1; } *off_in += read_count; /* Write the buffer part which was read to the destination. */ end = buf + read_count; for (p = buf; p < end; ) { ssize_t write_count; #ifdef HAVE_PWRITE64 write_count = pwrite64(fd_out, p, end - p, *off_out); #else write_count = pwrite(fd_out, p, end - p, *off_out); #endif if (write_count < 0) { /* * Adjust the input read position to match what * we have written, so that the caller can pick * up after the error. */ size_t written = p - buf; /* * NB: This needs to be signed so that we can * form the negative value below. */ ssize_t overread = read_count - written; *off_in -= overread; /* Report the number of bytes copied so far. */ if (copied + written > 0) return copied + written; return -1; } p += write_count; *off_out += write_count; } /* Write loop. */ copied += read_count; length -= read_count; } return copied; } ssize_t erofs_copy_file_range(int fd_in, u64 *off_in, int fd_out, u64 *off_out, size_t length) { #ifdef HAVE_COPY_FILE_RANGE off64_t off64_in = *off_in, off64_out = *off_out; ssize_t ret; ret = copy_file_range(fd_in, &off64_in, fd_out, &off64_out, length, 0); if (ret >= 0) goto out; if (errno != ENOSYS && errno != EXDEV) { ret = -errno; out: *off_in = off64_in; *off_out = off64_out; return ret; } #endif return __erofs_copy_file_range(fd_in, off_in, fd_out, off_out, length); } ssize_t erofs_io_read(struct erofs_vfile *vf, void *buf, size_t bytes) { ssize_t i = 0; if (vf->ops) return vf->ops->read(vf, buf, bytes); while (bytes) { int len = bytes > INT_MAX ? INT_MAX : bytes; int ret; ret = read(vf->fd, buf + i, len); if (ret < 1) { if (ret == 0) { break; } else if (errno != EINTR) { erofs_err("failed to read : %s", strerror(errno)); return -errno; } } bytes -= ret; i += ret; } return i; } #ifdef HAVE_SYS_SENDFILE_H #include #endif off_t erofs_io_lseek(struct erofs_vfile *vf, u64 offset, int whence) { if (vf->ops) return vf->ops->lseek(vf, offset, whence); return lseek(vf->fd, offset, whence); } int erofs_io_xcopy(struct erofs_vfile *vout, off_t pos, struct erofs_vfile *vin, unsigned int len, bool noseek) { if (vout->ops) return vout->ops->xcopy(vout, pos, vin, len, noseek); if (len && !vin->ops) { off_t ret __maybe_unused; #ifdef HAVE_COPY_FILE_RANGE ret = copy_file_range(vin->fd, NULL, vout->fd, &pos, len, 0); if (ret > 0) len -= ret; #endif #if defined(HAVE_SYS_SENDFILE_H) && defined(HAVE_SENDFILE) if (len && !noseek) { ret = lseek(vout->fd, pos, SEEK_SET); if (ret == pos) { ret = sendfile(vout->fd, vin->fd, NULL, len); if (ret > 0) { pos += ret; len -= ret; } } } #endif } do { char buf[32768]; int ret = min_t(unsigned int, len, sizeof(buf)); ret = erofs_io_read(vin, buf, ret); if (ret < 0) return ret; if (ret > 0) { ret = erofs_io_pwrite(vout, buf, pos, ret); if (ret < 0) return ret; pos += ret; } len -= ret; } while (len); return 0; } erofs-utils-1.8.6/lib/kite_deflate.c000066400000000000000000001007521477440110200173410ustar00rootroot00000000000000// SPDX-License-Identifier: GPL-2.0+ OR Apache-2.0 /* * erofs-utils/lib/kite_deflate.c * * Copyright (C) 2023, Alibaba Cloud * Copyright (C) 2023, Gao Xiang */ #include "erofs/defs.h" #include "erofs/print.h" #include #include #include #include #include unsigned long erofs_memcmp2(const u8 *s1, const u8 *s2, unsigned long sz); #ifdef TEST #define kite_dbg(x, ...) fprintf(stderr, x "\n", ##__VA_ARGS__) #else #define kite_dbg(x, ...) #endif #define kHistorySize32 (1U << 15) #define kNumLenSymbols32 256 #define kNumLenSymbolsMax kNumLenSymbols32 #define kSymbolEndOfBlock 256 #define kSymbolMatch (kSymbolEndOfBlock + 1) #define kNumLenSlots 29 #define kMainTableSize (kSymbolMatch + kNumLenSlots) #define kFixedLenTableSize (kSymbolMatch + 31) #define FixedDistTableSize 32 #define kMainTableSize (kSymbolMatch + kNumLenSlots) #define kDistTableSize32 30 #define kNumLitLenCodesMin 257 #define kNumDistCodesMin 1 #define kNumLensCodesMin 4 #define kLensTableSize 19 #define kMatchMinLen 3 #define kMatchMaxLen32 kNumLenSymbols32 + kMatchMinLen - 1 #define kTableDirectLevels 16 #define kBitLensRepNumber_3_6 kTableDirectLevels #define kBitLens0Number_3_10 (kBitLensRepNumber_3_6 + 1) #define kBitLens0Number_11_138 (kBitLens0Number_3_10 + 1) static u32 kstaticHuff_mainCodes[kFixedLenTableSize]; static const u8 kstaticHuff_litLenLevels[kFixedLenTableSize] = { [0 ... 143] = 8, [144 ... 255] = 9, [256 ... 279] = 7, [280 ... 287] = 8, }; static u32 kstaticHuff_distCodes[kFixedLenTableSize]; const u8 kLenStart32[kNumLenSlots] = {0,1,2,3,4,5,6,7,8,10,12,14,16,20,24,28,32,40,48,56,64,80,96,112,128,160,192,224, 255}; const u8 kLenExtraBits32[kNumLenSlots] = {0,0,0,0,0,0,0,0,1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5, 0}; /* First normalized distance for each code (0 = distance of 1) */ const u32 kDistStart[kDistTableSize32] = {0,1,2,3,4,6,8,12,16,24,32,48,64,96,128,192,256,384,512,768, 1024,1536,2048,3072,4096,6144,8192,12288,16384,24576}; /* extra bits for each distance code */ const u8 kDistExtraBits[kDistTableSize32] = {0,0,0,0,1,1,2,2,3,3,4,4,5,5,6,6,7,7,8,8,9,9,10,10,11,11,12,12,13,13}; const u8 kCodeLengthAlphabetOrder[kLensTableSize] = {16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15}; const u8 kLevelExtraBits[3] = {2, 3, 7}; #define kStored 0 #define kFixedHuffman 1 #define kDynamicHuffman 2 struct kite_deflate_symbol { u16 len, dist; }; struct kite_deflate_table { u32 mainCodes[kMainTableSize]; u8 litLenLevels[kMainTableSize]; u32 distCodes[kDistTableSize32]; u8 distLevels[kDistTableSize32]; u32 levelCodes[kLensTableSize]; u8 levelLens[kLensTableSize]; u8 numdistlens, numblcodes; u16 numlitlens; }; struct kite_deflate { struct kite_deflate_table *tab; const u8 *in; u8 *out; u32 inlen, outlen; u32 pos_in, pos_out; u32 inflightbits; u8 bitpos; u8 numHuffBits; u32 symbols; u32 costbits, startpos; u8 encode_mode; bool freq_changed, lastblock; /* Previous match for lazy matching */ bool prev_valid; u16 prev_longest; u32 mainFreqs[kMainTableSize]; u32 distFreqs[kDistTableSize32]; struct kite_deflate_table tables[2]; /* don't reset the following fields */ struct kite_matchfinder *mf; struct kite_deflate_symbol *sym; u32 max_symbols; bool lazy_search; }; #define ZLIB_DISTANCE_TOO_FAR 4096 static u8 g_LenSlots[kNumLenSymbolsMax]; #define kNumLogBits 9 // do not change it static u8 g_FastPos[1 << kNumLogBits]; static void writebits(struct kite_deflate *s, unsigned int v, u8 bits) { unsigned int rem = sizeof(s->inflightbits) * 8 - s->bitpos; s->inflightbits |= (v << s->bitpos) & (!rem - 1); if (bits > rem) { u8 *out = s->out + s->pos_out; out[0] = s->inflightbits & 0xff; out[1] = (s->inflightbits >> 8) & 0xff; out[2] = (s->inflightbits >> 16) & 0xff; out[3] = (s->inflightbits >> 24) & 0xff; s->pos_out += 4; DBG_BUGON(s->pos_out > s->outlen); s->inflightbits = v >> rem; s->bitpos = bits - rem; return; } s->bitpos += bits; } static void flushbits(struct kite_deflate *s) { u8 *out = s->out + s->pos_out; if (!s->bitpos) return; out[0] = s->inflightbits & 0xff; if (s->bitpos >= 8) { out[1] = (s->inflightbits >> 8) & 0xff; if (s->bitpos >= 16) { out[2] = (s->inflightbits >> 16) & 0xff; if (s->bitpos >= 24) out[3] = (s->inflightbits >> 24) & 0xff; } } s->pos_out += round_up(s->bitpos, 8) >> 3; DBG_BUGON(s->pos_out > s->outlen); s->bitpos = 0; s->inflightbits = 0; } #define kMaxLen 16 static void deflate_genhuffcodes(const u8 *lens, u32 *p, unsigned int nr_codes, const u32 *bl_count) { u32 nextCodes[kMaxLen + 1]; /* next code value for each bit length */ unsigned int code = 0; /* running code value */ unsigned int bits, k; for (bits = 1; bits <= kMaxLen; ++bits) { code = (code + bl_count[bits - 1]) << 1; nextCodes[bits] = code; } DBG_BUGON(code + bl_count[kMaxLen] != 1 << kMaxLen); for (k = 0; k < nr_codes; ++k) p[k] = nextCodes[lens[k]]++; } static u32 deflate_reversebits_one(u32 code, u8 bits) { unsigned int x = code; x = ((x & 0x5555) << 1) | ((x & 0xAAAA) >> 1); x = ((x & 0x3333) << 2) | ((x & 0xCCCC) >> 2); x = ((x & 0x0F0F) << 4) | ((x & 0xF0F0) >> 4); return (((x & 0x00FF) << 8) | ((x & 0xFF00) >> 8)) >> (16 - bits); } static void Huffman_ReverseBits(u32 *codes, const u8 *lens, unsigned int n) { while (n) { u32 code = *codes; *codes++ = deflate_reversebits_one(code, *lens++); --n; } } static void kite_deflate_init_once(void) { static const u32 static_bl_count[kMaxLen + 1] = { [7] = 279 - 256 + 1, [8] = (143 + 1) + (287 - 280 + 1), [9] = 255 - 144 + 1, }; unsigned int i, c, j, k; if (kstaticHuff_distCodes[31]) return; deflate_genhuffcodes(kstaticHuff_litLenLevels, kstaticHuff_mainCodes, kFixedLenTableSize, static_bl_count); Huffman_ReverseBits(kstaticHuff_mainCodes, kstaticHuff_litLenLevels, kFixedLenTableSize); for (i = 0; i < ARRAY_SIZE(kstaticHuff_distCodes); ++i) kstaticHuff_distCodes[i] = deflate_reversebits_one(i, 5); for (i = 0; i < kNumLenSlots; i++) { c = kLenStart32[i]; j = 1 << kLenExtraBits32[i]; for (k = 0; k < j; k++, c++) g_LenSlots[c] = (u8)i; } c = 0; for (i = 0; i < /*kFastSlots*/ kNumLogBits * 2; i++) { k = 1 << kDistExtraBits[i]; for (j = 0; j < k; j++) g_FastPos[c++] = i; } } static void kite_deflate_scanlens(unsigned int numlens, u8 *lens, u32 *freqs) { int n; /* iterates over all tree elements */ int prevlen = -1; /* last emitted length */ int curlen; /* length of current code */ int nextlen = lens[0]; /* length of next code */ int count = 0; /* repeat count of the current code */ int max_count = 7; /* max repeat count */ int min_count = 4; /* min repeat count */ if (!nextlen) max_count = 138, min_count = 3; for (n = 0; n < numlens; n++) { curlen = nextlen; nextlen = n + 1 < numlens ? lens[n + 1] : -1; ++count; if (count < max_count && curlen == nextlen) continue; if (count < min_count) { freqs[curlen] += count; } else if (curlen != 0) { if (curlen != prevlen) freqs[curlen]++; freqs[kBitLensRepNumber_3_6]++; } else if (count <= 10) { freqs[kBitLens0Number_3_10]++; } else { freqs[kBitLens0Number_11_138]++; } count = 0; prevlen = curlen; if (!nextlen) max_count = 138, min_count = 3; else if (curlen == nextlen) max_count = 6, min_count = 3; else max_count = 7, min_count = 4; } } static void kite_deflate_sendtree(struct kite_deflate *s, const u8 *lens, unsigned int numlens) { int n; /* iterates over all tree elements */ int prevlen = -1; /* last emitted length */ int curlen; /* length of current code */ int nextlen = lens[0]; /* length of next code */ int count = 0; /* repeat count of the current code */ int max_count = 7; /* max repeat count */ int min_count = 4; /* min repeat count */ const u8 *bl_lens = s->tab->levelLens; const u32 *bl_codes = s->tab->levelCodes; if (!nextlen) max_count = 138, min_count = 3; for (n = 0; n < numlens; n++) { curlen = nextlen; nextlen = n + 1 < numlens ? lens[n + 1] : -1; ++count; if (count < max_count && curlen == nextlen) continue; if (count < min_count) { do { writebits(s, bl_codes[curlen], bl_lens[curlen]); } while (--count); } else if (curlen) { if (curlen != prevlen) { writebits(s, bl_codes[curlen], bl_lens[curlen]); count--; } writebits(s, bl_codes[kBitLensRepNumber_3_6], bl_lens[kBitLensRepNumber_3_6]); writebits(s, count - 3, 2); } else if (count <= 10) { writebits(s, bl_codes[kBitLens0Number_3_10], bl_lens[kBitLens0Number_3_10]); writebits(s, count - 3, 3); } else { writebits(s, bl_codes[kBitLens0Number_11_138], bl_lens[kBitLens0Number_11_138]); writebits(s, count - 11, 7); } count = 0; prevlen = curlen; if (!nextlen) max_count = 138, min_count = 3; else if (curlen == nextlen) max_count = 6, min_count = 3; else max_count = 7, min_count = 4; } } static void kite_deflate_setfixedtrees(struct kite_deflate *s) { writebits(s, (kFixedHuffman << 1) + s->lastblock, 3); } static void kite_deflate_sendtrees(struct kite_deflate *s) { struct kite_deflate_table *t = s->tab; unsigned int i; writebits(s, (kDynamicHuffman << 1) + s->lastblock, 3); writebits(s, t->numlitlens - kNumLitLenCodesMin, 5); writebits(s, t->numdistlens - kNumDistCodesMin, 5); writebits(s, t->numblcodes - kNumLensCodesMin, 4); for (i = 0; i < t->numblcodes; i++) writebits(s, t->levelLens[kCodeLengthAlphabetOrder[i]], 3); Huffman_ReverseBits(t->levelCodes, t->levelLens, kLensTableSize); kite_deflate_sendtree(s, t->litLenLevels, t->numlitlens); kite_deflate_sendtree(s, t->distLevels, t->numdistlens); } static inline unsigned int deflateDistSlot(unsigned int pos) { const unsigned int zz = (kNumLogBits - 1) & ((((1U << kNumLogBits) - 1) - pos) >> (31 - 3)); return g_FastPos[pos >> zz] + (zz * 2); } static void kite_deflate_writeblock(struct kite_deflate *s, bool fixed) { int i; u32 *mainCodes, *distCodes; const u8 *litLenLevels, *distLevels; if (!fixed) { struct kite_deflate_table *t = s->tab; mainCodes = t->mainCodes; distCodes = t->distCodes; litLenLevels = t->litLenLevels; distLevels = t->distLevels; Huffman_ReverseBits(mainCodes, litLenLevels, kMainTableSize); Huffman_ReverseBits(distCodes, distLevels, kDistTableSize32); } else { mainCodes = kstaticHuff_mainCodes; distCodes = kstaticHuff_distCodes; litLenLevels = kstaticHuff_litLenLevels; distLevels = NULL; } for (i = 0; i < s->symbols; ++i) { struct kite_deflate_symbol *sym = &s->sym[i]; if (sym->len < kMatchMinLen) { /* literal */ writebits(s, mainCodes[sym->dist], litLenLevels[sym->dist]); } else { unsigned int lenSlot, distSlot; unsigned int lc = sym->len - kMatchMinLen; lenSlot = g_LenSlots[lc]; writebits(s, mainCodes[kSymbolMatch + lenSlot], litLenLevels[kSymbolMatch + lenSlot]); writebits(s, lc - kLenStart32[lenSlot], kLenExtraBits32[lenSlot]); distSlot = deflateDistSlot(sym->dist - 1); writebits(s, distCodes[distSlot], fixed ? 5 : distLevels[distSlot]); writebits(s, sym->dist - 1 - kDistStart[distSlot], kDistExtraBits[distSlot]); } } writebits(s, mainCodes[kSymbolEndOfBlock], litLenLevels[kSymbolEndOfBlock]); } static u32 Huffman_GetPrice(const u32 *freqs, const u8 *lens, u32 num) { u32 price = 0; while (num) { price += (*lens++) * (*freqs++); --num; } return price; } static u32 Huffman_GetPriceEx(const u32 *freqs, const u8 *lens, u32 num, const u8 *extraBits, u32 extraBase) { return Huffman_GetPrice(freqs, lens, num) + Huffman_GetPrice(freqs + extraBase, extraBits, num - extraBase); } /* Adapted from C/HuffEnc.c (7zip) for now */ #define HeapSortDown(p, k, size, temp) \ { for (;;) { \ size_t s = (k << 1); \ if (s > size) break; \ if (s < size && p[s + 1] > p[s]) s++; \ if (temp >= p[s]) break; \ p[k] = p[s]; k = s; \ } p[k] = temp; } static void HeapSort(u32 *p, size_t size) { if (size <= 1) return; p--; { size_t i = size / 2; do { u32 temp = p[i]; size_t k = i; HeapSortDown(p, k, size, temp) } while (--i != 0); } /* do { size_t k = 1; UInt32 temp = p[size]; p[size--] = p[1]; HeapSortDown(p, k, size, temp) } while (size > 1); */ while (size > 3) { u32 temp = p[size]; size_t k = (p[3] > p[2]) ? 3 : 2; p[size--] = p[1]; p[1] = p[k]; HeapSortDown(p, k, size, temp) } { u32 temp = p[size]; p[size] = p[1]; if (size > 2 && p[2] < temp) { p[1] = p[2]; p[2] = temp; } else p[1] = temp; } } #define NUM_BITS 10 #define MASK (((unsigned)1 << NUM_BITS) - 1) static void Huffman_Generate(const u32 *freqs, u32 *p, u8 *lens, unsigned int numSymbols, unsigned int maxLen) { u32 num, i; num = 0; /* if (maxLen > 10) maxLen = 10; */ for (i = 0; i < numSymbols; i++) { u32 freq = freqs[i]; if (!freq) lens[i] = 0; else p[num++] = i | (freq << NUM_BITS); } HeapSort(p, num); if (num < 2) { unsigned int minCode = 0, maxCode = 1; if (num == 1) { maxCode = (unsigned int)p[0] & MASK; if (!maxCode) maxCode++; } p[minCode] = 0; p[maxCode] = 1; lens[minCode] = lens[maxCode] = 1; return; } { u32 b, e, i; i = b = e = 0; do { u32 n, m, freq; n = (i != num && (b == e || (p[i] >> NUM_BITS) <= (p[b] >> NUM_BITS))) ? i++ : b++; freq = (p[n] & ~MASK); p[n] = (p[n] & MASK) | (e << NUM_BITS); m = (i != num && (b == e || (p[i] >> NUM_BITS) <= (p[b] >> NUM_BITS))) ? i++ : b++; freq += (p[m] & ~MASK); p[m] = (p[m] & MASK) | (e << NUM_BITS); p[e] = (p[e] & MASK) | freq; e++; } while (num - e > 1); { u32 lenCounters[kMaxLen + 1]; for (i = 0; i <= kMaxLen; i++) lenCounters[i] = 0; p[--e] &= MASK; lenCounters[1] = 2; while (e > 0) { u32 len = (p[p[--e] >> NUM_BITS] >> NUM_BITS) + 1; p[e] = (p[e] & MASK) | (len << NUM_BITS); if (len >= maxLen) for (len = maxLen - 1; lenCounters[len] == 0; len--); lenCounters[len]--; lenCounters[(size_t)len + 1] += 2; } { u32 len; i = 0; for (len = maxLen; len != 0; len--) { u32 k; for (k = lenCounters[len]; k != 0; k--) lens[p[i++] & MASK] = (u8)len; } } deflate_genhuffcodes(lens, p, numSymbols, lenCounters); } } } static void kite_deflate_fixdynblock(struct kite_deflate *s) { struct kite_deflate_table *t = s->tab; unsigned int numlitlens, numdistlens, numblcodes; u32 levelFreqs[kLensTableSize] = {0}; u32 opt_mainlen; if (!s->freq_changed) return; /* in order to match zlib */ s->numHuffBits = kMaxLen; // s->numHuffBits = (s->symbols > 18000 ? 12 : // (s->symbols > 7000 ? 11 : (s->symbols > 2000 ? 10 : 9))); Huffman_Generate(s->mainFreqs, t->mainCodes, t->litLenLevels, kMainTableSize, s->numHuffBits); Huffman_Generate(s->distFreqs, t->distCodes, t->distLevels, kDistTableSize32, s->numHuffBits); /* code lengths for the literal/length alphabet */ numlitlens = kMainTableSize; while (numlitlens > kNumLitLenCodesMin && !t->litLenLevels[numlitlens - 1]) --numlitlens; /* code lengths for the distance alphabet */ numdistlens = kDistTableSize32; while (numdistlens > kNumDistCodesMin && !t->distLevels[numdistlens - 1]) --numdistlens; kite_deflate_scanlens(numlitlens, t->litLenLevels, levelFreqs); kite_deflate_scanlens(numdistlens, t->distLevels, levelFreqs); Huffman_Generate(levelFreqs, t->levelCodes, t->levelLens, kLensTableSize, 7); numblcodes = kLensTableSize; while (numblcodes > kNumLensCodesMin && !t->levelLens[kCodeLengthAlphabetOrder[numblcodes - 1]]) --numblcodes; t->numlitlens = numlitlens; t->numdistlens = numdistlens; t->numblcodes = numblcodes; opt_mainlen = Huffman_GetPriceEx(s->mainFreqs, t->litLenLevels, kMainTableSize, kLenExtraBits32, kSymbolMatch) + Huffman_GetPriceEx(s->distFreqs, t->distLevels, kDistTableSize32, kDistExtraBits, 0); s->costbits = 3 + 5 + 5 + 4 + 3 * numblcodes + Huffman_GetPriceEx(levelFreqs, t->levelLens, kLensTableSize, kLevelExtraBits, kTableDirectLevels) + opt_mainlen; s->freq_changed = false; } /* * an array used used by the LZ-based encoder to hold the length-distance pairs * found by LZ matchfinder. */ struct kite_match { unsigned int len; unsigned int dist; }; struct kite_matchfinder { /* pointer to buffer with data to be compressed */ const u8 *buffer; /* indicate the first byte that doesn't contain valid input data */ const u8 *end; /* LZ matchfinder hash chain representation */ u32 *hash, *chain; u32 base; /* indicate the next byte to run through the match finder */ u32 offset; u32 cyclic_pos; /* maximum length of a match that the matchfinder will try to find. */ u16 nice_len; /* the total sliding window size */ u16 wsiz; /* how many rounds a matchfinder searches on a hash chain for */ u16 depth; /* do not perform lazy search no less than this match length */ u16 max_lazy; /* reduce lazy search no less than this match length */ u8 good_len; /* current match for lazy matching */ struct kite_match *matches; struct kite_match matches_matrix[2][4]; }; /* * This mysterious table is just the CRC of each possible byte. It can be * computed using the standard bit-at-a-time methods. The polynomial can * be seen in entry 128, 0x8408. This corresponds to x^0 + x^5 + x^12. * Add the implicit x^16, and you have the standard CRC-CCITT. */ u16 const crc_ccitt_table[256] __attribute__((__aligned__(128))) = { 0x0000, 0x1189, 0x2312, 0x329b, 0x4624, 0x57ad, 0x6536, 0x74bf, 0x8c48, 0x9dc1, 0xaf5a, 0xbed3, 0xca6c, 0xdbe5, 0xe97e, 0xf8f7, 0x1081, 0x0108, 0x3393, 0x221a, 0x56a5, 0x472c, 0x75b7, 0x643e, 0x9cc9, 0x8d40, 0xbfdb, 0xae52, 0xdaed, 0xcb64, 0xf9ff, 0xe876, 0x2102, 0x308b, 0x0210, 0x1399, 0x6726, 0x76af, 0x4434, 0x55bd, 0xad4a, 0xbcc3, 0x8e58, 0x9fd1, 0xeb6e, 0xfae7, 0xc87c, 0xd9f5, 0x3183, 0x200a, 0x1291, 0x0318, 0x77a7, 0x662e, 0x54b5, 0x453c, 0xbdcb, 0xac42, 0x9ed9, 0x8f50, 0xfbef, 0xea66, 0xd8fd, 0xc974, 0x4204, 0x538d, 0x6116, 0x709f, 0x0420, 0x15a9, 0x2732, 0x36bb, 0xce4c, 0xdfc5, 0xed5e, 0xfcd7, 0x8868, 0x99e1, 0xab7a, 0xbaf3, 0x5285, 0x430c, 0x7197, 0x601e, 0x14a1, 0x0528, 0x37b3, 0x263a, 0xdecd, 0xcf44, 0xfddf, 0xec56, 0x98e9, 0x8960, 0xbbfb, 0xaa72, 0x6306, 0x728f, 0x4014, 0x519d, 0x2522, 0x34ab, 0x0630, 0x17b9, 0xef4e, 0xfec7, 0xcc5c, 0xddd5, 0xa96a, 0xb8e3, 0x8a78, 0x9bf1, 0x7387, 0x620e, 0x5095, 0x411c, 0x35a3, 0x242a, 0x16b1, 0x0738, 0xffcf, 0xee46, 0xdcdd, 0xcd54, 0xb9eb, 0xa862, 0x9af9, 0x8b70, 0x8408, 0x9581, 0xa71a, 0xb693, 0xc22c, 0xd3a5, 0xe13e, 0xf0b7, 0x0840, 0x19c9, 0x2b52, 0x3adb, 0x4e64, 0x5fed, 0x6d76, 0x7cff, 0x9489, 0x8500, 0xb79b, 0xa612, 0xd2ad, 0xc324, 0xf1bf, 0xe036, 0x18c1, 0x0948, 0x3bd3, 0x2a5a, 0x5ee5, 0x4f6c, 0x7df7, 0x6c7e, 0xa50a, 0xb483, 0x8618, 0x9791, 0xe32e, 0xf2a7, 0xc03c, 0xd1b5, 0x2942, 0x38cb, 0x0a50, 0x1bd9, 0x6f66, 0x7eef, 0x4c74, 0x5dfd, 0xb58b, 0xa402, 0x9699, 0x8710, 0xf3af, 0xe226, 0xd0bd, 0xc134, 0x39c3, 0x284a, 0x1ad1, 0x0b58, 0x7fe7, 0x6e6e, 0x5cf5, 0x4d7c, 0xc60c, 0xd785, 0xe51e, 0xf497, 0x8028, 0x91a1, 0xa33a, 0xb2b3, 0x4a44, 0x5bcd, 0x6956, 0x78df, 0x0c60, 0x1de9, 0x2f72, 0x3efb, 0xd68d, 0xc704, 0xf59f, 0xe416, 0x90a9, 0x8120, 0xb3bb, 0xa232, 0x5ac5, 0x4b4c, 0x79d7, 0x685e, 0x1ce1, 0x0d68, 0x3ff3, 0x2e7a, 0xe70e, 0xf687, 0xc41c, 0xd595, 0xa12a, 0xb0a3, 0x8238, 0x93b1, 0x6b46, 0x7acf, 0x4854, 0x59dd, 0x2d62, 0x3ceb, 0x0e70, 0x1ff9, 0xf78f, 0xe606, 0xd49d, 0xc514, 0xb1ab, 0xa022, 0x92b9, 0x8330, 0x7bc7, 0x6a4e, 0x58d5, 0x495c, 0x3de3, 0x2c6a, 0x1ef1, 0x0f78 }; int kite_mf_getmatches_hc3(struct kite_matchfinder *mf, u16 depth, u16 bestlen) { const u8 *cur = mf->buffer + mf->offset; const u8 *qbase = mf->buffer - mf->base; u32 curMatch; unsigned int v, hv, i, k, p, wsiz; if (mf->end - cur < bestlen + 1) return -1; v = get_unaligned((u16 *)cur); hv = v ^ crc_ccitt_table[cur[2]]; curMatch = mf->hash[hv]; p = mf->base + mf->offset; mf->hash[hv] = p; mf->chain[mf->cyclic_pos] = curMatch; wsiz = mf->wsiz; k = 1; if (depth) { unsigned int wpos = wsiz + mf->cyclic_pos; hv = min_t(unsigned int, mf->nice_len, mf->end - cur); DBG_BUGON(hv > kMatchMaxLen32); do { unsigned int diff = p - curMatch; const u8 *q; if (diff >= wsiz) break; q = qbase + curMatch; curMatch = mf->chain[(wpos - diff) & (wsiz - 1)]; if (v == get_unaligned((u16 *)q) && (bestlen < 3 || ( get_unaligned((u16 *)(cur + bestlen - 1)) == get_unaligned((u16 *)(q + bestlen - 1)) && !memcmp(cur + 3, q + 3, bestlen - 3)))) { DBG_BUGON(cur[2] != q[2]); i = erofs_memcmp2(cur + bestlen + 1, q + bestlen + 1, hv - bestlen - 1); bestlen += 1 + i; k -= (k >= ARRAY_SIZE(mf->matches_matrix[0])); mf->matches[k++] = (struct kite_match) { .len = bestlen, .dist = diff, }; if (bestlen >= hv) break; } } while (--depth); } mf->offset++; mf->cyclic_pos = (mf->cyclic_pos + 1) & (wsiz - 1); return k - 1; } static void kite_mf_hc3_skip(struct kite_matchfinder *mf) { if (kite_mf_getmatches_hc3(mf, 0, 2) >= 0) return; mf->offset++; /* mf->cyclic_pos = (mf->cyclic_pos + 1) & (mf->wsiz - 1); */ } /* let's align with zlib */ static const struct kite_matchfinder_cfg { u16 good_length; /* reduce lazy search above this match length */ u16 max_lazy; /* do not perform lazy search above this match length */ u16 nice_length; /* quit search above this match length */ u16 depth; bool lazy_search; } kite_mfcfg[10] = { /* good lazy nice depth */ /* 0 */ {0, 0, 0, 0, false}, /* store only [unsupported] */ /* 1 */ {4, 4, 8, 4, false}, /* maximum speed, no lazy matches */ /* 2 */ {4, 5, 16, 8, false}, /* 3 */ {4, 6, 32, 32, false}, /* 4 */ {4, 4, 16, 16, true}, /* lazy matches */ /* 5 */ {8, 16, 32, 32, true}, /* 6 */ {8, 16, 128, 128, true}, /* 7 */ {8, 32, 128, 256, true}, /* 8 */ {32, 128, 258, 1024, true}, /* 9 */ {32, 258, 258, 4096, true}, /* maximum compression */ }; static int kite_mf_init(struct kite_matchfinder *mf, unsigned int wsiz, int level) { const struct kite_matchfinder_cfg *cfg; if (!level || level >= ARRAY_SIZE(kite_mfcfg)) return -EINVAL; cfg = &kite_mfcfg[level]; if (wsiz > kHistorySize32 || (wsiz & (wsiz - 1))) return -EINVAL; mf->hash = calloc(0x10000, sizeof(mf->hash[0])); if (!mf->hash) return -ENOMEM; mf->chain = malloc(sizeof(mf->chain[0]) * wsiz); if (!mf->chain) { free(mf->hash); mf->hash = NULL; return -ENOMEM; } mf->wsiz = wsiz; mf->good_len = cfg->good_length; mf->nice_len = cfg->nice_length; mf->depth = cfg->depth; mf->max_lazy = cfg->max_lazy; return cfg->lazy_search; } static void kite_mf_reset(struct kite_matchfinder *mf, const void *buffer, const void *end) { mf->buffer = buffer; mf->end = end; /* * Set the initial value as max_distance + 1. This would avoid hash * zero initialization. */ mf->base += mf->offset + kHistorySize32 + 1; /* * Unlike other LZ encoders like liblzma [1], we simply reset the hash * chain instead of normalization. This avoids extra complexity, as we * don't consider extreme large input buffers in one go. * * [1] https://github.com/tukaani-project/xz/blob/v5.4.0/src/liblzma/lz/lz_encoder_mf.c#L94 */ if (__erofs_unlikely(mf->base > ((typeof(mf->base))-1) >> 1)) { mf->base = kHistorySize32 + 1; memset(mf->hash, 0, 0x10000 * sizeof(mf->hash[0])); } mf->offset = 0; mf->cyclic_pos = 0; mf->matches = mf->matches_matrix[0]; mf->matches_matrix[0][0].len = mf->matches_matrix[1][0].len = kMatchMinLen - 1; } static bool deflate_count_code(struct kite_deflate *s, bool literal, unsigned int lenSlot, unsigned int distSlot) { struct kite_deflate_table *t = s->tab; unsigned int lenbase = (literal ? 0 : kSymbolMatch); u64 rem = (s->outlen - s->pos_out) * 8ULL - s->bitpos; bool recalc = false; unsigned int bits; s->freq_changed = true; ++s->mainFreqs[lenbase + lenSlot]; if (!literal) ++s->distFreqs[distSlot]; if (s->encode_mode == 1) { if (literal) { bits = kstaticHuff_litLenLevels[lenSlot]; goto out; } bits = kstaticHuff_litLenLevels[kSymbolMatch + lenSlot] + kLenExtraBits32[lenSlot] + 5 + kDistExtraBits[distSlot]; goto out; } /* XXX: more ideas to be done later */ recalc |= (!literal && !t->distLevels[distSlot]); recalc |= !t->litLenLevels[lenbase + lenSlot]; if (recalc) { kite_dbg("recalc %c lS %u dS %u", literal ? 'l' : 'm', lenSlot, distSlot); s->tab = s->tables + (s->tab == s->tables); kite_deflate_fixdynblock(s); bits = 0; goto out; } if (literal) { bits = t->litLenLevels[lenSlot]; goto out; } bits = t->distLevels[distSlot] + kDistExtraBits[distSlot] + t->litLenLevels[kSymbolMatch + lenSlot] + kLenExtraBits32[lenSlot]; out: if (rem < s->costbits + bits) { --s->mainFreqs[lenbase + lenSlot]; if (!literal) --s->distFreqs[distSlot]; if (recalc) s->tab = s->tables + (s->tab == s->tables); return false; } s->costbits += bits; return true; } static bool kite_deflate_tally(struct kite_deflate *s, struct kite_match *match) { struct kite_deflate_symbol *sym = s->sym + s->symbols; u32 fixedcost = ~0; bool hassp; *sym = (struct kite_deflate_symbol) { .len = match->len, .dist = match->dist, }; retry: if (sym->len < kMatchMinLen) { hassp = deflate_count_code(s, true, sym->dist, 0); } else { unsigned int lc = sym->len - kMatchMinLen; unsigned int lenSlot = g_LenSlots[lc]; unsigned int distSlot = deflateDistSlot(sym->dist - 1); hassp = deflate_count_code(s, false, lenSlot, distSlot); } if (!hassp) { if (s->encode_mode == 1) { fixedcost = s->costbits; s->encode_mode = 2; goto retry; } s->lastblock = true; if (fixedcost <= s->costbits) s->encode_mode = 1; return true; } ++s->symbols; return false; } static void kite_deflate_writestore(struct kite_deflate *s) { bool fb = !s->startpos && !s->bitpos; unsigned int totalsiz = s->pos_in - s->prev_valid - s->startpos; do { unsigned int len = min_t(unsigned int, totalsiz, 65535); totalsiz -= len; writebits(s, (fb << 3) | (kStored << 1) | (s->lastblock && !totalsiz), 3 + fb); flushbits(s); writebits(s, len, 16); writebits(s, len ^ 0xffff, 16); flushbits(s); memcpy(s->out + s->pos_out, s->in + s->startpos, len); s->pos_out += len; s->startpos += len; } while (totalsiz); } static void kite_deflate_endblock(struct kite_deflate *s) { if (s->encode_mode == 1) { u32 fixedcost = s->costbits; unsigned int storelen, storeblocks, storecost; kite_deflate_fixdynblock(s); if (fixedcost > s->costbits) s->encode_mode = 2; else s->costbits = fixedcost; storelen = s->pos_in - s->prev_valid - s->startpos; storeblocks = max(DIV_ROUND_UP(storelen, 65535), 1U); storecost = (8 - s->bitpos) + storeblocks - 1 + storeblocks * 32 + storelen * 8; if (s->costbits > storecost) { s->costbits = storecost; s->encode_mode = 0; } } s->lastblock |= (s->costbits + s->bitpos >= (s->outlen - s->pos_out) * 8); } static void kite_deflate_startblock(struct kite_deflate *s) { memset(s->mainFreqs, 0, sizeof(s->mainFreqs)); memset(s->distFreqs, 0, sizeof(s->distFreqs)); memset(s->tables, 0, sizeof(s->tables[0])); s->symbols = 0; s->mainFreqs[kSymbolEndOfBlock]++; s->encode_mode = 1; s->tab = s->tables; s->costbits = 3 + kstaticHuff_litLenLevels[kSymbolEndOfBlock]; } static bool kite_deflate_commitblock(struct kite_deflate *s) { if (s->encode_mode == 1) { kite_deflate_setfixedtrees(s); kite_deflate_writeblock(s, true); } else if (s->encode_mode == 2) { kite_deflate_sendtrees(s); kite_deflate_writeblock(s, false); } else { kite_deflate_writestore(s); } s->startpos = s->pos_in - s->prev_valid; return s->lastblock; } static bool kite_deflate_fast(struct kite_deflate *s) { struct kite_matchfinder *mf = s->mf; kite_deflate_startblock(s); while (1) { int matches = kite_mf_getmatches_hc3(mf, mf->depth, kMatchMinLen - 1); if (matches > 0) { unsigned int len = mf->matches[matches].len; unsigned int dist = mf->matches[matches].dist; if (len == kMatchMinLen && dist > ZLIB_DISTANCE_TOO_FAR) goto nomatch; kite_dbg("%u matches found: longest [%u,%u] of distance %u", matches, s->pos_in, s->pos_in + len - 1, dist); if (kite_deflate_tally(s, mf->matches + matches)) break; s->pos_in += len; /* skip the rest bytes */ while (--len) kite_mf_hc3_skip(mf); } else { nomatch: mf->matches[0].dist = s->in[s->pos_in]; if (isprint(s->in[s->pos_in])) kite_dbg("literal %c pos_in %u", s->in[s->pos_in], s->pos_in); else kite_dbg("literal %x pos_in %u", s->in[s->pos_in], s->pos_in); if (kite_deflate_tally(s, mf->matches)) break; ++s->pos_in; } s->lastblock |= (s->pos_in >= s->inlen); if (s->pos_in >= s->inlen || s->symbols >= s->max_symbols) { kite_deflate_endblock(s); break; } } return kite_deflate_commitblock(s); } static bool kite_deflate_slow(struct kite_deflate *s) { struct kite_matchfinder *mf = s->mf; bool flush = false; kite_deflate_startblock(s); while (1) { struct kite_match *prev_matches = mf->matches; unsigned int len = kMatchMinLen - 1; int matches; unsigned int len0; mf->matches = mf->matches_matrix[ mf->matches == mf->matches_matrix[0]]; mf->matches[0].dist = s->in[s->pos_in]; len0 = prev_matches[s->prev_longest].len; if (len0 < mf->max_lazy) { matches = kite_mf_getmatches_hc3(mf, mf->depth >> (len0 >= mf->good_len), len0); if (matches > 0) { len = mf->matches[matches].len; if (len == kMatchMinLen && mf->matches[matches].dist > ZLIB_DISTANCE_TOO_FAR) { matches = 0; len = kMatchMinLen - 1; } } else { matches = 0; } } else { matches = 0; kite_mf_hc3_skip(mf); } if (len < len0) { if (kite_deflate_tally(s, prev_matches + s->prev_longest)) break; s->pos_in += --len0; /* skip the rest bytes */ while (--len0) kite_mf_hc3_skip(mf); s->prev_valid = false; s->prev_longest = 0; } else { if (!s->prev_valid) s->prev_valid = true; else if (kite_deflate_tally(s, prev_matches)) break; ++s->pos_in; s->prev_longest = matches; } s->lastblock |= (s->pos_in >= s->inlen); if (s->pos_in >= s->inlen) { flush = true; break; } if (s->symbols >= s->max_symbols) { kite_deflate_endblock(s); break; } } if (flush && s->prev_valid) { (void)kite_deflate_tally(s, mf->matches + s->prev_longest); s->prev_valid = false; } return kite_deflate_commitblock(s); } void kite_deflate_end(struct kite_deflate *s) { if (s->mf) { if (s->mf->hash) free(s->mf->hash); if (s->mf->chain) free(s->mf->chain); free(s->mf); } if (s->sym) free(s->sym); free(s); } struct kite_deflate *kite_deflate_init(int level, unsigned int dict_size) { struct kite_deflate *s; int err; kite_deflate_init_once(); s = calloc(1, sizeof(*s)); if (!s) return ERR_PTR(-ENOMEM); s->max_symbols = 16384; s->sym = malloc(sizeof(s->sym[0]) * s->max_symbols); if (!s->sym) { err = -ENOMEM; goto err_out; } s->mf = malloc(sizeof(*s->mf)); if (!s->mf) { err = -ENOMEM; goto err_out; } if (!dict_size) dict_size = kHistorySize32; err = kite_mf_init(s->mf, dict_size, level); if (err < 0) goto err_out; s->lazy_search = err; return s; err_out: if (s->mf) free(s->mf); if (s->sym) free(s->sym); free(s); return ERR_PTR(err); } int kite_deflate_destsize(struct kite_deflate *s, const u8 *in, u8 *out, unsigned int *srcsize, unsigned int target_dstsize) { memset(s, 0, offsetof(struct kite_deflate, mainFreqs)); s->in = in; s->inlen = *srcsize; s->out = out; s->outlen = target_dstsize; kite_mf_reset(s->mf, in, in + s->inlen); if (s->lazy_search) while (!kite_deflate_slow(s)); else while (!kite_deflate_fast(s)); flushbits(s); *srcsize = s->startpos; return s->pos_out; } #if TEST #include #include #include int main(int argc, char *argv[]) { int fd; u64 filelength; u8 out[1048576], *buf; int dstsize = 4096; unsigned int srcsize, outsize; struct kite_deflate *s; fd = open(argv[1], O_RDONLY); if (fd < 0) return -errno; if (argc > 2) dstsize = atoi(argv[2]); filelength = lseek(fd, 0, SEEK_END); s = kite_deflate_init(9, 0); if (IS_ERR(s)) return PTR_ERR(s); filelength = lseek(fd, 0, SEEK_END); buf = mmap(NULL, filelength, PROT_READ, MAP_SHARED, fd, 0); if (buf == MAP_FAILED) return -errno; close(fd); srcsize = filelength; outsize = kite_deflate_destsize(s, buf, out, &srcsize, dstsize); fd = open("out.txt", O_WRONLY | O_CREAT | O_TRUNC, 0644); write(fd, out, outsize); close(fd); kite_deflate_end(s); return 0; } #endif erofs-utils-1.8.6/lib/liberofs_private.h000066400000000000000000000010671477440110200202640ustar00rootroot00000000000000/* SPDX-License-Identifier: GPL-2.0-only OR Apache-2.0 */ #ifdef HAVE_LIBSELINUX #include #include #endif #ifdef WITH_ANDROID #include #include #include #include #endif #ifndef HAVE_MEMRCHR static inline void *memrchr(const void *s, int c, size_t n) { const unsigned char *p = (const unsigned char *)s; for (p += n; n > 0; n--) if (*--p == c) return (void*)p; return NULL; } #endif int erofs_tmpfile(void); erofs-utils-1.8.6/lib/liberofs_uuid.h000066400000000000000000000004401477440110200175520ustar00rootroot00000000000000/* SPDX-License-Identifier: GPL-2.0+ OR Apache-2.0 */ #ifndef __EROFS_LIB_UUID_H #define __EROFS_LIB_UUID_H void erofs_uuid_generate(unsigned char *out); void erofs_uuid_unparse_lower(const unsigned char *buf, char *out); int erofs_uuid_parse(const char *in, unsigned char *uu); #endif erofs-utils-1.8.6/lib/liberofs_xxhash.h000066400000000000000000000024121477440110200201100ustar00rootroot00000000000000/* SPDX-License-Identifier: BSD-2-Clause OR GPL-2.0+ */ #ifndef __EROFS_LIB_XXHASH_H #define __EROFS_LIB_XXHASH_H #ifdef __cplusplus extern "C" { #endif #include #ifdef HAVE_XXHASH_H #include #endif #ifdef HAVE_XXHASH static inline uint32_t xxh32(const void *input, size_t length, uint32_t seed) { return XXH32(input, length, seed); } static inline uint64_t xxh64(const void *input, const size_t len, const uint64_t seed) { return XXH64(input, len, seed); } #else /* * xxh32() - calculate the 32-bit hash of the input with a given seed. * * @input: The data to hash. * @length: The length of the data to hash. * @seed: The seed can be used to alter the result predictably. * * Return: The 32-bit hash of the data. */ uint32_t xxh32(const void *input, size_t length, uint32_t seed); /* * xxh64() - calculate the 64-bit hash of the input with a given seed. * * @input: The data to hash. * @length: The length of the data to hash. * @seed: The seed can be used to alter the result predictably. * * This function runs 2x faster on 64-bit systems, but slower on 32-bit systems. * * Return: The 64-bit hash of the data. */ uint64_t xxh64(const void *input, const size_t len, const uint64_t seed); #endif #ifdef __cplusplus } #endif #endif erofs-utils-1.8.6/lib/namei.c000066400000000000000000000142661477440110200160160ustar00rootroot00000000000000// SPDX-License-Identifier: GPL-2.0+ OR Apache-2.0 /* * Created by Li Guifu */ #include #include #include #include #include #include #if defined(HAVE_SYS_SYSMACROS_H) #include #endif #include "erofs/print.h" #include "erofs/internal.h" static dev_t erofs_new_decode_dev(u32 dev) { const unsigned int major = (dev & 0xfff00) >> 8; const unsigned int minor = (dev & 0xff) | ((dev >> 12) & 0xfff00); return makedev(major, minor); } int erofs_read_inode_from_disk(struct erofs_inode *vi) { int ret, ifmt; char buf[sizeof(struct erofs_inode_extended)]; erofs_off_t inode_loc = erofs_iloc(vi); struct erofs_sb_info *sbi = vi->sbi; struct erofs_inode_compact *dic; struct erofs_inode_extended *die; union erofs_inode_i_u iu; DBG_BUGON(!sbi); ret = erofs_dev_read(sbi, 0, buf, inode_loc, sizeof(*dic)); if (ret < 0) return -EIO; dic = (struct erofs_inode_compact *)buf; ifmt = le16_to_cpu(dic->i_format); vi->datalayout = erofs_inode_datalayout(ifmt); if (vi->datalayout >= EROFS_INODE_DATALAYOUT_MAX) { erofs_err("unsupported datalayout %u of nid %llu", vi->datalayout, vi->nid | 0ULL); return -EOPNOTSUPP; } switch (erofs_inode_version(ifmt)) { case EROFS_INODE_LAYOUT_EXTENDED: vi->inode_isize = sizeof(struct erofs_inode_extended); ret = erofs_dev_read(sbi, 0, buf + sizeof(*dic), inode_loc + sizeof(*dic), sizeof(*die) - sizeof(*dic)); if (ret < 0) return -EIO; die = (struct erofs_inode_extended *)buf; vi->xattr_isize = erofs_xattr_ibody_size(die->i_xattr_icount); vi->i_mode = le16_to_cpu(die->i_mode); vi->i_ino[0] = le32_to_cpu(die->i_ino); iu = die->i_u; vi->i_uid = le32_to_cpu(die->i_uid); vi->i_gid = le32_to_cpu(die->i_gid); vi->i_nlink = le32_to_cpu(die->i_nlink); vi->i_mtime = le64_to_cpu(die->i_mtime); vi->i_mtime_nsec = le64_to_cpu(die->i_mtime_nsec); vi->i_size = le64_to_cpu(die->i_size); break; case EROFS_INODE_LAYOUT_COMPACT: vi->inode_isize = sizeof(struct erofs_inode_compact); vi->xattr_isize = erofs_xattr_ibody_size(dic->i_xattr_icount); vi->i_mode = le16_to_cpu(dic->i_mode); vi->i_ino[0] = le32_to_cpu(dic->i_ino); iu = dic->i_u; vi->i_uid = le16_to_cpu(dic->i_uid); vi->i_gid = le16_to_cpu(dic->i_gid); vi->i_nlink = le16_to_cpu(dic->i_nlink); vi->i_mtime = sbi->build_time; vi->i_mtime_nsec = sbi->build_time_nsec; vi->i_size = le32_to_cpu(dic->i_size); break; default: erofs_err("unsupported on-disk inode version %u of nid %llu", erofs_inode_version(ifmt), vi->nid | 0ULL); return -EOPNOTSUPP; } switch (vi->i_mode & S_IFMT) { case S_IFREG: case S_IFDIR: case S_IFLNK: vi->u.i_blkaddr = le32_to_cpu(iu.raw_blkaddr); break; case S_IFCHR: case S_IFBLK: vi->u.i_rdev = erofs_new_decode_dev(le32_to_cpu(iu.rdev)); break; case S_IFIFO: case S_IFSOCK: vi->u.i_rdev = 0; break; default: erofs_err("bogus i_mode (%o) @ nid %llu", vi->i_mode, vi->nid | 0ULL); return -EFSCORRUPTED; } vi->flags = 0; if (vi->datalayout == EROFS_INODE_CHUNK_BASED) { /* fill chunked inode summary info */ vi->u.chunkformat = le16_to_cpu(iu.c.format); if (vi->u.chunkformat & ~EROFS_CHUNK_FORMAT_ALL) { erofs_err("unsupported chunk format %x of nid %llu", vi->u.chunkformat, vi->nid | 0ULL); return -EOPNOTSUPP; } vi->u.chunkbits = sbi->blkszbits + (vi->u.chunkformat & EROFS_CHUNK_FORMAT_BLKBITS_MASK); } return 0; } struct erofs_dirent *find_target_dirent(erofs_nid_t pnid, void *dentry_blk, const char *name, unsigned int len, unsigned int nameoff, unsigned int maxsize) { struct erofs_dirent *de = dentry_blk; const struct erofs_dirent *end = dentry_blk + nameoff; while (de < end) { const char *de_name; unsigned int de_namelen; nameoff = le16_to_cpu(de->nameoff); de_name = (char *)dentry_blk + nameoff; /* the last dirent in the block? */ if (de + 1 >= end) de_namelen = strnlen(de_name, maxsize - nameoff); else de_namelen = le16_to_cpu(de[1].nameoff) - nameoff; /* a corrupted entry is found */ if (nameoff + de_namelen > maxsize || de_namelen > EROFS_NAME_LEN) { erofs_err("bogus dirent @ nid %llu", pnid | 0ULL); DBG_BUGON(1); return ERR_PTR(-EFSCORRUPTED); } if (len == de_namelen && !memcmp(de_name, name, de_namelen)) return de; ++de; } return NULL; } struct nameidata { struct erofs_sb_info *sbi; erofs_nid_t nid; unsigned int ftype; }; int erofs_namei(struct nameidata *nd, const char *name, unsigned int len) { erofs_nid_t nid = nd->nid; int ret; char buf[EROFS_MAX_BLOCK_SIZE]; struct erofs_sb_info *sbi = nd->sbi; struct erofs_inode vi = { .sbi = sbi, .nid = nid }; erofs_off_t offset; ret = erofs_read_inode_from_disk(&vi); if (ret) return ret; offset = 0; while (offset < vi.i_size) { erofs_off_t maxsize = min_t(erofs_off_t, vi.i_size - offset, erofs_blksiz(sbi)); struct erofs_dirent *de = (void *)buf; unsigned int nameoff; ret = erofs_pread(&vi, buf, maxsize, offset); if (ret) return ret; nameoff = le16_to_cpu(de->nameoff); if (nameoff < sizeof(struct erofs_dirent) || nameoff >= erofs_blksiz(sbi)) { erofs_err("invalid de[0].nameoff %u @ nid %llu", nameoff, nid | 0ULL); return -EFSCORRUPTED; } de = find_target_dirent(nid, buf, name, len, nameoff, maxsize); if (IS_ERR(de)) return PTR_ERR(de); if (de) { nd->nid = le64_to_cpu(de->nid); return 0; } offset += maxsize; } return -ENOENT; } static int link_path_walk(const char *name, struct nameidata *nd) { nd->nid = nd->sbi->root_nid; while (*name == '/') name++; /* At this point we know we have a real path component. */ while (*name != '\0') { const char *p = name; int ret; do { ++p; } while (*p != '\0' && *p != '/'); DBG_BUGON(p <= name); ret = erofs_namei(nd, name, p - name); if (ret) return ret; /* Skip until no more slashes. */ for (name = p; *name == '/'; ++name) ; } return 0; } int erofs_ilookup(const char *path, struct erofs_inode *vi) { int ret; struct nameidata nd = { .sbi = vi->sbi }; ret = link_path_walk(path, &nd); if (ret) return ret; vi->nid = nd.nid; return erofs_read_inode_from_disk(vi); } erofs-utils-1.8.6/lib/rebuild.c000066400000000000000000000310151477440110200163420ustar00rootroot00000000000000// SPDX-License-Identifier: GPL-2.0+ OR Apache-2.0 #define _GNU_SOURCE #include #include #include #include #include #if defined(HAVE_SYS_SYSMACROS_H) #include #endif #include "erofs/print.h" #include "erofs/inode.h" #include "erofs/rebuild.h" #include "erofs/dir.h" #include "erofs/xattr.h" #include "erofs/blobchunk.h" #include "erofs/internal.h" #include "liberofs_uuid.h" #ifdef HAVE_LINUX_AUFS_TYPE_H #include #else #define AUFS_WH_PFX ".wh." #define AUFS_DIROPQ_NAME AUFS_WH_PFX ".opq" #define AUFS_WH_DIROPQ AUFS_WH_PFX AUFS_DIROPQ_NAME #endif /* * These non-existent parent directories are created with the same permissions * as their parent directories. It is expected that a call to create these * parent directories with the correct permissions will be made later, at which * point the permissions will be updated. We handle mtime in the same way. * Also see: https://github.com/containerd/containerd/issues/3017 * https://github.com/containerd/containerd/pull/3528 */ static struct erofs_dentry *erofs_rebuild_mkdir(struct erofs_inode *dir, const char *s) { struct erofs_inode *inode; struct erofs_dentry *d; inode = erofs_new_inode(dir->sbi); if (IS_ERR(inode)) return ERR_CAST(inode); if (asprintf(&inode->i_srcpath, "%s/%s", dir->i_srcpath ? : "", s) < 0) { erofs_iput(inode); return ERR_PTR(-ENOMEM); } inode->i_mode = S_IFDIR | 0755; if (dir->i_mode & S_IWGRP) inode->i_mode |= S_IWGRP; if (dir->i_mode & S_IWOTH) inode->i_mode |= S_IWOTH; inode->i_parent = dir; inode->i_uid = dir->i_uid; inode->i_gid = dir->i_gid; inode->i_mtime = dir->i_mtime; inode->i_mtime_nsec = dir->i_mtime_nsec; inode->dev = dir->dev; erofs_init_empty_dir(inode); d = erofs_d_alloc(dir, s); if (IS_ERR(d)) { erofs_iput(inode); } else { d->type = EROFS_FT_DIR; d->inode = inode; } return d; } struct erofs_dentry *erofs_rebuild_get_dentry(struct erofs_inode *pwd, char *path, bool aufs, bool *whout, bool *opq, bool to_head) { struct erofs_dentry *d = NULL; unsigned int len = strlen(path); char *s = path; *whout = false; *opq = false; while (s < path + len) { char *slash = memchr(s, '/', path + len - s); if (slash) { if (s == slash) { while (*++s == '/'); /* skip '//...' */ continue; } *slash = '\0'; } if (!memcmp(s, ".", 2)) { /* null */ } else if (!memcmp(s, "..", 3)) { pwd = pwd->i_parent; } else { struct erofs_inode *inode = NULL; if (aufs && !slash) { if (!memcmp(s, AUFS_WH_DIROPQ, sizeof(AUFS_WH_DIROPQ))) { *opq = true; break; } if (!memcmp(s, AUFS_WH_PFX, sizeof(AUFS_WH_PFX) - 1)) { s += sizeof(AUFS_WH_PFX) - 1; *whout = true; } } list_for_each_entry(d, &pwd->i_subdirs, d_child) { if (!strcmp(d->name, s)) { if (d->type != EROFS_FT_DIR && slash) return ERR_PTR(-EIO); inode = d->inode; break; } } if (inode) { if (to_head) { list_del(&d->d_child); list_add(&d->d_child, &pwd->i_subdirs); } pwd = inode; } else if (!slash) { d = erofs_d_alloc(pwd, s); if (IS_ERR(d)) return d; d->type = EROFS_FT_UNKNOWN; d->inode = pwd; } else { d = erofs_rebuild_mkdir(pwd, s); if (IS_ERR(d)) return d; pwd = d->inode; } } if (slash) { *slash = '/'; s = slash + 1; } else { break; } } return d; } static int erofs_rebuild_write_blob_index(struct erofs_sb_info *dst_sb, struct erofs_inode *inode) { int ret; unsigned int count, unit, chunkbits, i; struct erofs_inode_chunk_index *idx; erofs_off_t chunksize; erofs_blk_t blkaddr; /* TODO: fill data map in other layouts */ if (inode->datalayout == EROFS_INODE_CHUNK_BASED) { chunkbits = inode->u.chunkbits; if (chunkbits < dst_sb->blkszbits) { erofs_err("%s: chunk size %u is smaller than the target block size %u", inode->i_srcpath, 1U << chunkbits, 1U << dst_sb->blkszbits); return -EINVAL; } } else if (inode->datalayout == EROFS_INODE_FLAT_PLAIN) { chunkbits = ilog2(inode->i_size - 1) + 1; if (chunkbits < dst_sb->blkszbits) chunkbits = dst_sb->blkszbits; if (chunkbits - dst_sb->blkszbits > EROFS_CHUNK_FORMAT_BLKBITS_MASK) chunkbits = EROFS_CHUNK_FORMAT_BLKBITS_MASK + dst_sb->blkszbits; } else { erofs_err("%s: unsupported datalayout %d ", inode->i_srcpath, inode->datalayout); return -EOPNOTSUPP; } chunksize = 1ULL << chunkbits; count = DIV_ROUND_UP(inode->i_size, chunksize); unit = sizeof(struct erofs_inode_chunk_index); inode->extent_isize = count * unit; idx = malloc(max(sizeof(*idx), sizeof(void *))); if (!idx) return -ENOMEM; inode->chunkindexes = idx; for (i = 0; i < count; i++) { struct erofs_blobchunk *chunk; struct erofs_map_blocks map = { .index = UINT_MAX, }; map.m_la = i << chunkbits; ret = erofs_map_blocks(inode, &map, 0); if (ret) goto err; blkaddr = erofs_blknr(dst_sb, map.m_pa); chunk = erofs_get_unhashed_chunk(inode->dev, blkaddr, 0); if (IS_ERR(chunk)) { ret = PTR_ERR(chunk); goto err; } *(void **)idx++ = chunk; } inode->datalayout = EROFS_INODE_CHUNK_BASED; inode->u.chunkformat = EROFS_CHUNK_FORMAT_INDEXES; inode->u.chunkformat |= chunkbits - dst_sb->blkszbits; return 0; err: free(inode->chunkindexes); inode->chunkindexes = NULL; return ret; } static int erofs_rebuild_update_inode(struct erofs_sb_info *dst_sb, struct erofs_inode *inode, enum erofs_rebuild_datamode datamode) { int err = 0; switch (inode->i_mode & S_IFMT) { case S_IFCHR: if (erofs_inode_is_whiteout(inode)) inode->i_parent->whiteouts = true; /* fallthrough */ case S_IFBLK: case S_IFIFO: case S_IFSOCK: inode->i_size = 0; erofs_dbg("\tdev: %d %d", major(inode->u.i_rdev), minor(inode->u.i_rdev)); inode->u.i_rdev = erofs_new_encode_dev(inode->u.i_rdev); break; case S_IFDIR: err = erofs_init_empty_dir(inode); break; case S_IFLNK: inode->i_link = malloc(inode->i_size + 1); if (!inode->i_link) return -ENOMEM; err = erofs_pread(inode, inode->i_link, inode->i_size, 0); erofs_dbg("\tsymlink: %s -> %s", inode->i_srcpath, inode->i_link); break; case S_IFREG: if (!inode->i_size) { inode->u.i_blkaddr = NULL_ADDR; break; } if (datamode == EROFS_REBUILD_DATA_BLOB_INDEX) err = erofs_rebuild_write_blob_index(dst_sb, inode); else if (datamode == EROFS_REBUILD_DATA_RESVSP) inode->datasource = EROFS_INODE_DATA_SOURCE_RESVSP; else err = -EOPNOTSUPP; break; default: return -EINVAL; } return err; } /* * @mergedir: parent directory in the merged tree * @ctx.dir: parent directory when itering erofs_iterate_dir() * @datamode: indicate how to import inode data */ struct erofs_rebuild_dir_context { struct erofs_dir_context ctx; struct erofs_inode *mergedir; enum erofs_rebuild_datamode datamode; }; static int erofs_rebuild_dirent_iter(struct erofs_dir_context *ctx) { struct erofs_rebuild_dir_context *rctx = (void *)ctx; struct erofs_inode *mergedir = rctx->mergedir; struct erofs_inode *dir = ctx->dir; struct erofs_inode *inode, *candidate; struct erofs_inode src; struct erofs_dentry *d; char *path, *dname; bool dumb; int ret; if (ctx->dot_dotdot) return 0; ret = asprintf(&path, "%s/%.*s", rctx->mergedir->i_srcpath, ctx->de_namelen, ctx->dname); if (ret < 0) return ret; erofs_dbg("parsing %s", path); dname = path + strlen(mergedir->i_srcpath) + 1; d = erofs_rebuild_get_dentry(mergedir, dname, false, &dumb, &dumb, false); if (IS_ERR(d)) { ret = PTR_ERR(d); goto out; } ret = 0; if (d->type != EROFS_FT_UNKNOWN) { /* * bail out if the file exists in the upper layers. (Note that * extended attributes won't be merged too even for dirs.) */ if (!S_ISDIR(d->inode->i_mode) || d->inode->opaque) goto out; /* merge directory entries */ src = (struct erofs_inode) { .sbi = dir->sbi, .nid = ctx->de_nid }; ret = erofs_read_inode_from_disk(&src); if (ret || !S_ISDIR(src.i_mode)) goto out; mergedir = d->inode; inode = dir = &src; } else { u64 nid; DBG_BUGON(mergedir != d->inode); inode = erofs_new_inode(dir->sbi); if (IS_ERR(inode)) { ret = PTR_ERR(inode); goto out; } /* reuse i_ino[0] to read nid in source fs */ nid = inode->i_ino[0]; inode->sbi = dir->sbi; inode->nid = ctx->de_nid; ret = erofs_read_inode_from_disk(inode); if (ret) goto out; /* restore nid in new generated fs */ inode->i_ino[1] = inode->i_ino[0]; inode->i_ino[0] = nid; inode->dev = inode->sbi->dev; if (S_ISREG(inode->i_mode) && inode->i_nlink > 1 && (candidate = erofs_iget(inode->dev, ctx->de_nid))) { /* hardlink file */ erofs_iput(inode); inode = candidate; if (S_ISDIR(inode->i_mode)) { erofs_err("hardlink directory not supported"); ret = -EISDIR; goto out; } inode->i_nlink++; erofs_dbg("\thardlink: %s -> %s", path, inode->i_srcpath); } else { ret = erofs_read_xattrs_from_disk(inode); if (ret) { erofs_iput(inode); goto out; } inode->i_parent = d->inode; inode->i_srcpath = path; path = NULL; inode->i_ino[1] = inode->nid; inode->i_nlink = 1; ret = erofs_rebuild_update_inode(&g_sbi, inode, rctx->datamode); if (ret) { erofs_iput(inode); goto out; } erofs_insert_ihash(inode); mergedir = dir = inode; } d->inode = inode; d->type = erofs_mode_to_ftype(inode->i_mode); } if (S_ISDIR(inode->i_mode)) { struct erofs_rebuild_dir_context nctx = *rctx; nctx.mergedir = mergedir; nctx.ctx.dir = dir; ret = erofs_iterate_dir(&nctx.ctx, false); if (ret) goto out; } /* reset sbi, nid after subdirs are all loaded for the final dump */ inode->sbi = &g_sbi; inode->nid = 0; out: free(path); return ret; } int erofs_rebuild_load_tree(struct erofs_inode *root, struct erofs_sb_info *sbi, enum erofs_rebuild_datamode mode) { struct erofs_inode inode = {}; struct erofs_rebuild_dir_context ctx; char uuid_str[37]; char *fsid = sbi->devname; int ret; if (!fsid) { erofs_uuid_unparse_lower(sbi->uuid, uuid_str); fsid = uuid_str; } ret = erofs_read_superblock(sbi); if (ret) { erofs_err("failed to read superblock of %s", fsid); return ret; } inode.nid = sbi->root_nid; inode.sbi = sbi; ret = erofs_read_inode_from_disk(&inode); if (ret) { erofs_err("failed to read root inode of %s", fsid); return ret; } inode.i_srcpath = strdup("/"); ctx = (struct erofs_rebuild_dir_context) { .ctx.dir = &inode, .ctx.cb = erofs_rebuild_dirent_iter, .mergedir = root, .datamode = mode, }; ret = erofs_iterate_dir(&ctx.ctx, false); free(inode.i_srcpath); return ret; } static int erofs_rebuild_basedir_dirent_iter(struct erofs_dir_context *ctx) { struct erofs_rebuild_dir_context *rctx = (void *)ctx; struct erofs_inode *dir = ctx->dir; struct erofs_inode *mergedir = rctx->mergedir; struct erofs_dentry *d; char *dname; bool dumb; int ret; if (ctx->dot_dotdot) return 0; dname = strndup(ctx->dname, ctx->de_namelen); if (!dname) return -ENOMEM; d = erofs_rebuild_get_dentry(mergedir, dname, false, &dumb, &dumb, false); if (IS_ERR(d)) { ret = PTR_ERR(d); goto out; } if (d->type == EROFS_FT_UNKNOWN) { d->nid = ctx->de_nid; d->type = ctx->de_ftype; d->validnid = true; if (!mergedir->whiteouts && erofs_dentry_is_wht(dir->sbi, d)) mergedir->whiteouts = true; } else { struct erofs_inode *inode = d->inode; /* update sub-directories only for recursively loading */ if (S_ISDIR(inode->i_mode) && (ctx->de_ftype == EROFS_FT_DIR || ctx->de_ftype == EROFS_FT_UNKNOWN)) { list_del(&inode->i_hash); inode->dev = dir->sbi->dev; inode->i_ino[1] = ctx->de_nid; erofs_insert_ihash(inode); } } ret = 0; out: free(dname); return ret; } int erofs_rebuild_load_basedir(struct erofs_inode *dir) { struct erofs_inode fakeinode = { .sbi = dir->sbi, .nid = dir->i_ino[1], }; struct erofs_rebuild_dir_context ctx; int ret; ret = erofs_read_inode_from_disk(&fakeinode); if (ret) { erofs_err("failed to read inode @ %llu", fakeinode.nid); return ret; } /* Inherit the maximum xattr size for the root directory */ if (__erofs_unlikely(IS_ROOT(dir))) dir->xattr_isize = fakeinode.xattr_isize; /* * May be triggered if ftype == EROFS_FT_UNKNOWN, which is impossible * with the current mkfs. */ if (__erofs_unlikely(!S_ISDIR(fakeinode.i_mode))) { DBG_BUGON(1); return 0; } ctx = (struct erofs_rebuild_dir_context) { .ctx.dir = &fakeinode, .ctx.cb = erofs_rebuild_basedir_dirent_iter, .mergedir = dir, }; return erofs_iterate_dir(&ctx.ctx, false); } erofs-utils-1.8.6/lib/rolling_hash.h000066400000000000000000000025441477440110200173770ustar00rootroot00000000000000/* SPDX-License-Identifier: GPL-2.0+ OR Apache-2.0 */ /* * Copyright (C) 2022 Alibaba Cloud */ #ifndef __ROLLING_HASH_H__ #define __ROLLING_HASH_H__ #include #define PRIME_NUMBER 4294967295LL #define RADIX 256 static inline long long erofs_rolling_hash_init(u8 *input, int len, bool backwards) { long long hash = 0; if (!backwards) { int i; for (i = 0; i < len; ++i) hash = (RADIX * hash + input[i]) % PRIME_NUMBER; } else { while (len) hash = (RADIX * hash + input[--len]) % PRIME_NUMBER; } return hash; } /* RM = R ^ (M-1) % Q */ /* * NOTE: value of "hash" could be negative so we cannot use unsiged types for "hash" * "long long" is used here and PRIME_NUMBER can be ULONG_MAX */ static inline long long erofs_rolling_hash_advance(long long old_hash, unsigned long long RM, u8 to_remove, u8 to_add) { long long hash = old_hash; long long to_remove_val = (to_remove * RM) % PRIME_NUMBER; hash = RADIX * (old_hash - to_remove_val) % PRIME_NUMBER; hash = (hash + to_add) % PRIME_NUMBER; /* We might get negative value of hash, converting it to positive */ if (hash < 0) hash += PRIME_NUMBER; return hash; } static inline long long erofs_rollinghash_calc_rm(int window_size) { int i; long long RM = 1; for (i = 0; i < window_size - 1; ++i) RM = (RM * RADIX) % PRIME_NUMBER; return RM; } #endif erofs-utils-1.8.6/lib/sha256.c000066400000000000000000000167141477440110200157350ustar00rootroot00000000000000// SPDX-License-Identifier: Unlicense /* * sha256.c --- The sha256 algorithm * * (copied from LibTomCrypt with adaption.) */ #include "sha256.h" #include /* This is based on SHA256 implementation in LibTomCrypt that was released into * public domain by Tom St Denis. */ /* the K array */ static const unsigned long K[64] = { 0x428a2f98UL, 0x71374491UL, 0xb5c0fbcfUL, 0xe9b5dba5UL, 0x3956c25bUL, 0x59f111f1UL, 0x923f82a4UL, 0xab1c5ed5UL, 0xd807aa98UL, 0x12835b01UL, 0x243185beUL, 0x550c7dc3UL, 0x72be5d74UL, 0x80deb1feUL, 0x9bdc06a7UL, 0xc19bf174UL, 0xe49b69c1UL, 0xefbe4786UL, 0x0fc19dc6UL, 0x240ca1ccUL, 0x2de92c6fUL, 0x4a7484aaUL, 0x5cb0a9dcUL, 0x76f988daUL, 0x983e5152UL, 0xa831c66dUL, 0xb00327c8UL, 0xbf597fc7UL, 0xc6e00bf3UL, 0xd5a79147UL, 0x06ca6351UL, 0x14292967UL, 0x27b70a85UL, 0x2e1b2138UL, 0x4d2c6dfcUL, 0x53380d13UL, 0x650a7354UL, 0x766a0abbUL, 0x81c2c92eUL, 0x92722c85UL, 0xa2bfe8a1UL, 0xa81a664bUL, 0xc24b8b70UL, 0xc76c51a3UL, 0xd192e819UL, 0xd6990624UL, 0xf40e3585UL, 0x106aa070UL, 0x19a4c116UL, 0x1e376c08UL, 0x2748774cUL, 0x34b0bcb5UL, 0x391c0cb3UL, 0x4ed8aa4aUL, 0x5b9cca4fUL, 0x682e6ff3UL, 0x748f82eeUL, 0x78a5636fUL, 0x84c87814UL, 0x8cc70208UL, 0x90befffaUL, 0xa4506cebUL, 0xbef9a3f7UL, 0xc67178f2UL }; /* Various logical functions */ #define RORc(x, y) \ ( ((((unsigned long) (x) & 0xFFFFFFFFUL) >> (unsigned long) ((y) & 31)) | \ ((unsigned long) (x) << (unsigned long) (32 - ((y) & 31)))) & 0xFFFFFFFFUL) #define Ch(x,y,z) (z ^ (x & (y ^ z))) #define Maj(x,y,z) (((x | y) & z) | (x & y)) #define S(x, n) RORc((x), (n)) #define R(x, n) (((x)&0xFFFFFFFFUL)>>(n)) #define Sigma0(x) (S(x, 2) ^ S(x, 13) ^ S(x, 22)) #define Sigma1(x) (S(x, 6) ^ S(x, 11) ^ S(x, 25)) #define Gamma0(x) (S(x, 7) ^ S(x, 18) ^ R(x, 3)) #define Gamma1(x) (S(x, 17) ^ S(x, 19) ^ R(x, 10)) #ifndef MIN #define MIN(x, y) (((x) < (y)) ? (x) : (y)) #endif #define STORE64H(x, y) \ do { \ (y)[0] = (unsigned char)(((x)>>56)&255);\ (y)[1] = (unsigned char)(((x)>>48)&255);\ (y)[2] = (unsigned char)(((x)>>40)&255);\ (y)[3] = (unsigned char)(((x)>>32)&255);\ (y)[4] = (unsigned char)(((x)>>24)&255);\ (y)[5] = (unsigned char)(((x)>>16)&255);\ (y)[6] = (unsigned char)(((x)>>8)&255);\ (y)[7] = (unsigned char)((x)&255); } while(0) #define STORE32H(x, y) \ do { (y)[0] = (unsigned char)(((x)>>24)&255); (y)[1] = (unsigned char)(((x)>>16)&255); \ (y)[2] = (unsigned char)(((x)>>8)&255); (y)[3] = (unsigned char)((x)&255); } while(0) #define LOAD32H(x, y) \ do { x = ((u32)((y)[0] & 255)<<24) | \ ((u32)((y)[1] & 255)<<16) | \ ((u32)((y)[2] & 255)<<8) | \ ((u32)((y)[3] & 255)); } while(0) /* compress 512-bits */ static int sha256_compress(struct sha256_state *md, unsigned char *buf) { u32 S[8], W[64], t0, t1; u32 t; int i; /* copy state into S */ for (i = 0; i < 8; i++) { S[i] = md->state[i]; } /* copy the state into 512-bits into W[0..15] */ for (i = 0; i < 16; i++) LOAD32H(W[i], buf + (4 * i)); /* fill W[16..63] */ for (i = 16; i < 64; i++) { W[i] = Gamma1(W[i - 2]) + W[i - 7] + Gamma0(W[i - 15]) + W[i - 16]; } /* Compress */ #define RND(a,b,c,d,e,f,g,h,i) \ t0 = h + Sigma1(e) + Ch(e, f, g) + K[i] + W[i]; \ t1 = Sigma0(a) + Maj(a, b, c); \ d += t0; \ h = t0 + t1; for (i = 0; i < 64; ++i) { RND(S[0], S[1], S[2], S[3], S[4], S[5], S[6], S[7], i); t = S[7]; S[7] = S[6]; S[6] = S[5]; S[5] = S[4]; S[4] = S[3]; S[3] = S[2]; S[2] = S[1]; S[1] = S[0]; S[0] = t; } /* feedback */ for (i = 0; i < 8; i++) { md->state[i] = md->state[i] + S[i]; } return 0; } /* Initialize the hash state */ void erofs_sha256_init(struct sha256_state *md) { md->curlen = 0; md->length = 0; md->state[0] = 0x6A09E667UL; md->state[1] = 0xBB67AE85UL; md->state[2] = 0x3C6EF372UL; md->state[3] = 0xA54FF53AUL; md->state[4] = 0x510E527FUL; md->state[5] = 0x9B05688CUL; md->state[6] = 0x1F83D9ABUL; md->state[7] = 0x5BE0CD19UL; } /** Process a block of memory though the hash @param md The hash state @param in The data to hash @param inlen The length of the data (octets) @return CRYPT_OK if successful */ int erofs_sha256_process(struct sha256_state *md, const unsigned char *in, unsigned long inlen) { unsigned long n; #define block_size 64 if (md->curlen > sizeof(md->buf)) return -1; while (inlen > 0) { if (md->curlen == 0 && inlen >= block_size) { if (sha256_compress(md, (unsigned char *) in) < 0) return -1; md->length += block_size * 8; in += block_size; inlen -= block_size; } else { n = MIN(inlen, (block_size - md->curlen)); memcpy(md->buf + md->curlen, in, n); md->curlen += n; in += n; inlen -= n; if (md->curlen == block_size) { if (sha256_compress(md, md->buf) < 0) return -1; md->length += 8 * block_size; md->curlen = 0; } } } return 0; } /** Terminate the hash to get the digest @param md The hash state @param out [out] The destination of the hash (32 bytes) @return CRYPT_OK if successful */ int erofs_sha256_done(struct sha256_state *md, unsigned char *out) { int i; if (md->curlen >= sizeof(md->buf)) return -1; /* increase the length of the message */ md->length += md->curlen * 8; /* append the '1' bit */ md->buf[md->curlen++] = (unsigned char) 0x80; /* if the length is currently above 56 bytes we append zeros * then compress. Then we can fall back to padding zeros and length * encoding like normal. */ if (md->curlen > 56) { while (md->curlen < 64) { md->buf[md->curlen++] = (unsigned char) 0; } sha256_compress(md, md->buf); md->curlen = 0; } /* pad upto 56 bytes of zeroes */ while (md->curlen < 56) { md->buf[md->curlen++] = (unsigned char) 0; } /* store length */ STORE64H(md->length, md->buf+56); sha256_compress(md, md->buf); /* copy output */ for (i = 0; i < 8; i++) STORE32H(md->state[i], out + (4 * i)); return 0; } void erofs_sha256(const unsigned char *in, unsigned long in_size, unsigned char out[32]) { struct sha256_state md; erofs_sha256_init(&md); erofs_sha256_process(&md, in, in_size); erofs_sha256_done(&md, out); } #ifdef UNITTEST #include static const struct { char *msg; unsigned char hash[32]; } tests[] = { { "", { 0xe3, 0xb0, 0xc4, 0x42, 0x98, 0xfc, 0x1c, 0x14, 0x9a, 0xfb, 0xf4, 0xc8, 0x99, 0x6f, 0xb9, 0x24, 0x27, 0xae, 0x41, 0xe4, 0x64, 0x9b, 0x93, 0x4c, 0xa4, 0x95, 0x99, 0x1b, 0x78, 0x52, 0xb8, 0x55 } }, { "abc", { 0xba, 0x78, 0x16, 0xbf, 0x8f, 0x01, 0xcf, 0xea, 0x41, 0x41, 0x40, 0xde, 0x5d, 0xae, 0x22, 0x23, 0xb0, 0x03, 0x61, 0xa3, 0x96, 0x17, 0x7a, 0x9c, 0xb4, 0x10, 0xff, 0x61, 0xf2, 0x00, 0x15, 0xad } }, { "abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq", { 0x24, 0x8d, 0x6a, 0x61, 0xd2, 0x06, 0x38, 0xb8, 0xe5, 0xc0, 0x26, 0x93, 0x0c, 0x3e, 0x60, 0x39, 0xa3, 0x3c, 0xe4, 0x59, 0x64, 0xff, 0x21, 0x67, 0xf6, 0xec, 0xed, 0xd4, 0x19, 0xdb, 0x06, 0xc1 } }, }; int main(int argc, char **argv) { int i; int errors = 0; unsigned char tmp[32]; for (i = 0; i < (int)(sizeof(tests) / sizeof(tests[0])); i++) { unsigned char *msg = (unsigned char *) tests[i].msg; int len = strlen(tests[i].msg); erofs_sha256(msg, len, tmp); printf("SHA256 test message %d: ", i); if (memcmp(tmp, tests[i].hash, 32) != 0) { printf("FAILED\n"); errors++; } else printf("OK\n"); } return errors; } #endif /* UNITTEST */ erofs-utils-1.8.6/lib/sha256.h000066400000000000000000000010241477440110200157260ustar00rootroot00000000000000/* SPDX-License-Identifier: GPL-2.0+ OR Apache-2.0 */ #ifndef __EROFS_LIB_SHA256_H #define __EROFS_LIB_SHA256_H #include "erofs/defs.h" struct sha256_state { u64 length; u32 state[8], curlen; u8 buf[64]; }; void erofs_sha256_init(struct sha256_state *md); int erofs_sha256_process(struct sha256_state *md, const unsigned char *in, unsigned long inlen); int erofs_sha256_done(struct sha256_state *md, unsigned char *out); void erofs_sha256(const unsigned char *in, unsigned long in_size, unsigned char out[32]); #endif erofs-utils-1.8.6/lib/super.c000066400000000000000000000175041477440110200160610ustar00rootroot00000000000000// SPDX-License-Identifier: GPL-2.0+ OR Apache-2.0 /* * Created by Li Guifu */ #include #include #include "erofs/print.h" #include "erofs/xattr.h" #include "erofs/cache.h" static bool check_layout_compatibility(struct erofs_sb_info *sbi, struct erofs_super_block *dsb) { const unsigned int feature = le32_to_cpu(dsb->feature_incompat); sbi->feature_incompat = feature; /* check if current kernel meets all mandatory requirements */ if (feature & ~EROFS_ALL_FEATURE_INCOMPAT) { erofs_err("unidentified incompatible feature %x, please upgrade kernel version", feature & ~EROFS_ALL_FEATURE_INCOMPAT); return false; } return true; } static int erofs_init_devices(struct erofs_sb_info *sbi, struct erofs_super_block *dsb) { unsigned int ondisk_extradevs, i; erofs_off_t pos; sbi->total_blocks = sbi->primarydevice_blocks; if (!erofs_sb_has_device_table(sbi)) ondisk_extradevs = 0; else ondisk_extradevs = le16_to_cpu(dsb->extra_devices); if (sbi->extra_devices && ondisk_extradevs != sbi->extra_devices) { erofs_err("extra devices don't match (ondisk %u, given %u)", ondisk_extradevs, sbi->extra_devices); return -EINVAL; } if (!ondisk_extradevs) return 0; sbi->extra_devices = ondisk_extradevs; sbi->device_id_mask = roundup_pow_of_two(ondisk_extradevs + 1) - 1; sbi->devs = calloc(ondisk_extradevs, sizeof(*sbi->devs)); if (!sbi->devs) return -ENOMEM; pos = le16_to_cpu(dsb->devt_slotoff) * EROFS_DEVT_SLOT_SIZE; for (i = 0; i < ondisk_extradevs; ++i) { struct erofs_deviceslot dis; int ret; ret = erofs_dev_read(sbi, 0, &dis, pos, sizeof(dis)); if (ret < 0) { free(sbi->devs); sbi->devs = NULL; return ret; } sbi->devs[i].mapped_blkaddr = le32_to_cpu(dis.mapped_blkaddr); sbi->devs[i].blocks = le32_to_cpu(dis.blocks); memcpy(sbi->devs[i].tag, dis.tag, sizeof(dis.tag)); sbi->total_blocks += sbi->devs[i].blocks; pos += EROFS_DEVT_SLOT_SIZE; } return 0; } int erofs_read_superblock(struct erofs_sb_info *sbi) { u8 data[EROFS_MAX_BLOCK_SIZE]; struct erofs_super_block *dsb; int ret; sbi->blkszbits = ilog2(EROFS_MAX_BLOCK_SIZE); ret = erofs_blk_read(sbi, 0, data, 0, erofs_blknr(sbi, sizeof(data))); if (ret < 0) { erofs_err("cannot read erofs superblock: %d", ret); return -EIO; } dsb = (struct erofs_super_block *)(data + EROFS_SUPER_OFFSET); ret = -EINVAL; if (le32_to_cpu(dsb->magic) != EROFS_SUPER_MAGIC_V1) { erofs_err("cannot find valid erofs superblock"); return ret; } sbi->feature_compat = le32_to_cpu(dsb->feature_compat); sbi->blkszbits = dsb->blkszbits; if (sbi->blkszbits < 9 || sbi->blkszbits > ilog2(EROFS_MAX_BLOCK_SIZE)) { erofs_err("blksize %llu isn't supported on this platform", erofs_blksiz(sbi) | 0ULL); return ret; } else if (!check_layout_compatibility(sbi, dsb)) { return ret; } sbi->sb_size = 128 + dsb->sb_extslots * EROFS_SB_EXTSLOT_SIZE; if (sbi->sb_size > (1 << sbi->blkszbits) - EROFS_SUPER_OFFSET) { erofs_err("invalid sb_extslots %u (more than a fs block)", dsb->sb_extslots); return -EINVAL; } sbi->primarydevice_blocks = le32_to_cpu(dsb->blocks); sbi->meta_blkaddr = le32_to_cpu(dsb->meta_blkaddr); sbi->xattr_blkaddr = le32_to_cpu(dsb->xattr_blkaddr); sbi->xattr_prefix_start = le32_to_cpu(dsb->xattr_prefix_start); sbi->xattr_prefix_count = dsb->xattr_prefix_count; sbi->islotbits = EROFS_ISLOTBITS; sbi->root_nid = le16_to_cpu(dsb->root_nid); sbi->packed_nid = le64_to_cpu(dsb->packed_nid); sbi->inos = le64_to_cpu(dsb->inos); sbi->checksum = le32_to_cpu(dsb->checksum); sbi->build_time = le64_to_cpu(dsb->build_time); sbi->build_time_nsec = le32_to_cpu(dsb->build_time_nsec); memcpy(&sbi->uuid, dsb->uuid, sizeof(dsb->uuid)); ret = z_erofs_parse_cfgs(sbi, dsb); if (ret) return ret; ret = erofs_init_devices(sbi, dsb); if (ret) return ret; ret = erofs_xattr_prefixes_init(sbi); if (ret && sbi->devs) { free(sbi->devs); sbi->devs = NULL; } return ret; } void erofs_put_super(struct erofs_sb_info *sbi) { if (sbi->devs) { free(sbi->devs); sbi->devs = NULL; } erofs_xattr_prefixes_cleanup(sbi); if (sbi->bmgr) { erofs_buffer_exit(sbi->bmgr); sbi->bmgr = NULL; } } int erofs_writesb(struct erofs_sb_info *sbi, struct erofs_buffer_head *sb_bh, erofs_blk_t *blocks) { struct erofs_super_block sb = { .magic = cpu_to_le32(EROFS_SUPER_MAGIC_V1), .blkszbits = sbi->blkszbits, .root_nid = cpu_to_le16(sbi->root_nid), .inos = cpu_to_le64(sbi->inos), .build_time = cpu_to_le64(sbi->build_time), .build_time_nsec = cpu_to_le32(sbi->build_time_nsec), .meta_blkaddr = cpu_to_le32(sbi->meta_blkaddr), .xattr_blkaddr = cpu_to_le32(sbi->xattr_blkaddr), .xattr_prefix_count = sbi->xattr_prefix_count, .xattr_prefix_start = cpu_to_le32(sbi->xattr_prefix_start), .feature_incompat = cpu_to_le32(sbi->feature_incompat), .feature_compat = cpu_to_le32(sbi->feature_compat & ~EROFS_FEATURE_COMPAT_SB_CHKSUM), .extra_devices = cpu_to_le16(sbi->extra_devices), .devt_slotoff = cpu_to_le16(sbi->devt_slotoff), .packed_nid = cpu_to_le64(sbi->packed_nid), }; const u32 sb_blksize = round_up(EROFS_SUPER_END, erofs_blksiz(sbi)); char *buf; int ret; *blocks = erofs_mapbh(sbi->bmgr, NULL); sb.blocks = cpu_to_le32(*blocks); memcpy(sb.uuid, sbi->uuid, sizeof(sb.uuid)); memcpy(sb.volume_name, sbi->volume_name, sizeof(sb.volume_name)); if (erofs_sb_has_compr_cfgs(sbi)) sb.u1.available_compr_algs = cpu_to_le16(sbi->available_compr_algs); else sb.u1.lz4_max_distance = cpu_to_le16(sbi->lz4.max_distance); buf = calloc(sb_blksize, 1); if (!buf) { erofs_err("failed to allocate memory for sb: %s", erofs_strerror(-errno)); return -ENOMEM; } memcpy(buf + EROFS_SUPER_OFFSET, &sb, sizeof(sb)); ret = erofs_dev_write(sbi, buf, sb_bh ? erofs_btell(sb_bh, false) : 0, EROFS_SUPER_END); free(buf); if (sb_bh) erofs_bdrop(sb_bh, false); return ret; } struct erofs_buffer_head *erofs_reserve_sb(struct erofs_bufmgr *bmgr) { struct erofs_buffer_head *bh; int err; bh = erofs_balloc(bmgr, META, 0, 0); if (IS_ERR(bh)) { erofs_err("failed to allocate super: %s", erofs_strerror(PTR_ERR(bh))); return bh; } bh->op = &erofs_skip_write_bhops; err = erofs_bh_balloon(bh, EROFS_SUPER_END); if (err < 0) { erofs_err("failed to balloon super: %s", erofs_strerror(err)); goto err_bdrop; } /* make sure that the super block should be the very first blocks */ (void)erofs_mapbh(NULL, bh->block); if (erofs_btell(bh, false) != 0) { erofs_err("failed to pin super block @ 0"); err = -EFAULT; goto err_bdrop; } return bh; err_bdrop: erofs_bdrop(bh, true); return ERR_PTR(err); } int erofs_enable_sb_chksum(struct erofs_sb_info *sbi, u32 *crc) { int ret; u8 buf[EROFS_MAX_BLOCK_SIZE]; unsigned int len; struct erofs_super_block *sb; ret = erofs_blk_read(sbi, 0, buf, 0, erofs_blknr(sbi, EROFS_SUPER_END) + 1); if (ret) { erofs_err("failed to read superblock to set checksum: %s", erofs_strerror(ret)); return ret; } /* * skip the first 1024 bytes, to allow for the installation * of x86 boot sectors and other oddities. */ sb = (struct erofs_super_block *)(buf + EROFS_SUPER_OFFSET); if (le32_to_cpu(sb->magic) != EROFS_SUPER_MAGIC_V1) { erofs_err("internal error: not an erofs valid image"); return -EFAULT; } /* turn on checksum feature */ sb->feature_compat = cpu_to_le32(le32_to_cpu(sb->feature_compat) | EROFS_FEATURE_COMPAT_SB_CHKSUM); if (erofs_blksiz(sbi) > EROFS_SUPER_OFFSET) len = erofs_blksiz(sbi) - EROFS_SUPER_OFFSET; else len = erofs_blksiz(sbi); *crc = erofs_crc32c(~0, (u8 *)sb, len); /* set up checksum field to erofs_super_block */ sb->checksum = cpu_to_le32(*crc); ret = erofs_blk_write(sbi, buf, 0, 1); if (ret) { erofs_err("failed to write checksummed superblock: %s", erofs_strerror(ret)); return ret; } return 0; } erofs-utils-1.8.6/lib/tar.c000066400000000000000000000626371477440110200155200ustar00rootroot00000000000000// SPDX-License-Identifier: GPL-2.0+ OR Apache-2.0 #include #include #include #include #include "erofs/print.h" #include "erofs/cache.h" #include "erofs/diskbuf.h" #include "erofs/inode.h" #include "erofs/list.h" #include "erofs/tar.h" #include "erofs/xattr.h" #include "erofs/blobchunk.h" #include "erofs/rebuild.h" #if defined(HAVE_ZLIB) #include #endif /* This file is a tape/volume header. Ignore it on extraction. */ #define GNUTYPE_VOLHDR 'V' struct tar_header { char name[100]; /* 0-99 */ char mode[8]; /* 100-107 */ char uid[8]; /* 108-115 */ char gid[8]; /* 116-123 */ char size[12]; /* 124-135 */ char mtime[12]; /* 136-147 */ char chksum[8]; /* 148-155 */ char typeflag; /* 156-156 */ char linkname[100]; /* 157-256 */ char magic[6]; /* 257-262 */ char version[2]; /* 263-264 */ char uname[32]; /* 265-296 */ char gname[32]; /* 297-328 */ char devmajor[8]; /* 329-336 */ char devminor[8]; /* 337-344 */ char prefix[155]; /* 345-499 */ char padding[12]; /* 500-512 (pad to exactly the 512 byte) */ }; #ifdef HAVE_LIBLZMA #include struct erofs_iostream_liblzma { u8 inbuf[32768]; lzma_stream strm; int fd; }; #endif void erofs_iostream_close(struct erofs_iostream *ios) { free(ios->buffer); if (ios->decoder == EROFS_IOS_DECODER_GZIP) { #if defined(HAVE_ZLIB) gzclose(ios->handler); #endif return; } else if (ios->decoder == EROFS_IOS_DECODER_LIBLZMA) { #if defined(HAVE_LIBLZMA) lzma_end(&ios->lzma->strm); close(ios->lzma->fd); free(ios->lzma); #endif return; } close(ios->vf.fd); } int erofs_iostream_open(struct erofs_iostream *ios, int fd, int decoder) { s64 fsz; ios->feof = false; ios->tail = ios->head = 0; ios->decoder = decoder; ios->dumpfd = -1; if (decoder == EROFS_IOS_DECODER_GZIP) { #if defined(HAVE_ZLIB) ios->handler = gzdopen(fd, "r"); if (!ios->handler) return -ENOMEM; ios->sz = fsz = 0; ios->bufsize = 32768; #else return -EOPNOTSUPP; #endif } else if (decoder == EROFS_IOS_DECODER_LIBLZMA) { #ifdef HAVE_LIBLZMA lzma_ret ret; ios->lzma = malloc(sizeof(*ios->lzma)); if (!ios->lzma) return -ENOMEM; ios->lzma->fd = fd; ios->lzma->strm = (lzma_stream)LZMA_STREAM_INIT; ret = lzma_auto_decoder(&ios->lzma->strm, UINT64_MAX, LZMA_CONCATENATED); if (ret != LZMA_OK) return -EFAULT; ios->sz = fsz = 0; ios->bufsize = 32768; #else return -EOPNOTSUPP; #endif } else { ios->vf.fd = fd; fsz = lseek(fd, 0, SEEK_END); if (fsz <= 0) { ios->feof = !fsz; ios->sz = 0; } else { ios->sz = fsz; if (lseek(fd, 0, SEEK_SET)) return -EIO; #ifdef HAVE_POSIX_FADVISE if (posix_fadvise(fd, 0, 0, POSIX_FADV_SEQUENTIAL)) erofs_warn("failed to fadvise: %s, ignored.", erofs_strerror(-errno)); #endif } ios->bufsize = 32768; } do { ios->buffer = malloc(ios->bufsize); if (ios->buffer) break; ios->bufsize >>= 1; } while (ios->bufsize >= 1024); if (!ios->buffer) return -ENOMEM; return 0; } int erofs_iostream_read(struct erofs_iostream *ios, void **buf, u64 bytes) { unsigned int rabytes = ios->tail - ios->head; int ret; if (rabytes >= bytes) { *buf = ios->buffer + ios->head; ios->head += bytes; return bytes; } if (ios->head) { memmove(ios->buffer, ios->buffer + ios->head, rabytes); ios->head = 0; ios->tail = rabytes; } if (!ios->feof) { if (ios->decoder == EROFS_IOS_DECODER_GZIP) { #if defined(HAVE_ZLIB) ret = gzread(ios->handler, ios->buffer + rabytes, ios->bufsize - rabytes); if (!ret) { int errnum; const char *errstr; errstr = gzerror(ios->handler, &errnum); if (errnum != Z_STREAM_END) { erofs_err("failed to gzread: %s", errstr); return -EIO; } ios->feof = true; } ios->tail += ret; #else return -EOPNOTSUPP; #endif } else if (ios->decoder == EROFS_IOS_DECODER_LIBLZMA) { #ifdef HAVE_LIBLZMA struct erofs_iostream_liblzma *lzma = ios->lzma; lzma_action action = LZMA_RUN; lzma_ret ret2; if (!lzma->strm.avail_in) { lzma->strm.next_in = lzma->inbuf; ret = read(lzma->fd, lzma->inbuf, sizeof(lzma->inbuf)); if (ret < 0) return -errno; lzma->strm.avail_in = ret; if (ret < sizeof(lzma->inbuf)) action = LZMA_FINISH; } lzma->strm.next_out = (u8 *)ios->buffer + rabytes; lzma->strm.avail_out = ios->bufsize - rabytes; ret2 = lzma_code(&lzma->strm, action); if (ret2 != LZMA_OK) { if (ret2 == LZMA_STREAM_END) ios->feof = true; else return -EIO; } ret = ios->bufsize - rabytes - lzma->strm.avail_out; ios->tail += ret; #else return -EOPNOTSUPP; #endif } else { ret = erofs_io_read(&ios->vf, ios->buffer + rabytes, ios->bufsize - rabytes); if (ret < 0) return ret; ios->tail += ret; if (ret < ios->bufsize - rabytes) ios->feof = true; } if (__erofs_unlikely(ios->dumpfd >= 0)) if (write(ios->dumpfd, ios->buffer + rabytes, ret) < ret) erofs_err("failed to dump %d bytes of the raw stream: %s", ret, erofs_strerror(-errno)); } *buf = ios->buffer; ret = min_t(int, ios->tail, min_t(u64, bytes, INT_MAX)); ios->head = ret; return ret; } int erofs_iostream_bread(struct erofs_iostream *ios, void *buf, u64 bytes) { u64 rem = bytes; void *src; int ret; do { ret = erofs_iostream_read(ios, &src, rem); if (ret < 0) return ret; memcpy(buf, src, ret); rem -= ret; } while (rem && ret); return bytes - rem; } int erofs_iostream_lskip(struct erofs_iostream *ios, u64 sz) { unsigned int rabytes = ios->tail - ios->head; int ret; void *dummy; if (rabytes >= sz) { ios->head += sz; return 0; } sz -= rabytes; ios->head = ios->tail = 0; if (ios->feof) return sz; if (ios->sz && __erofs_likely(ios->dumpfd < 0)) { s64 cur = erofs_io_lseek(&ios->vf, sz, SEEK_CUR); if (cur > ios->sz) return cur - ios->sz; return 0; } do { ret = erofs_iostream_read(ios, &dummy, sz); if (ret < 0) return ret; sz -= ret; } while (!(ios->feof || !ret || !sz)); return sz; } static long long tarerofs_otoi(const char *ptr, int len) { char inp[32]; char *endp = inp; long long val; memcpy(inp, ptr, len); inp[len] = '\0'; errno = 0; val = strtol(inp, &endp, 8); if ((*endp == '\0' && endp == inp) | (*endp != '\0' && *endp != ' ')) errno = EINVAL; return val; } static long long tarerofs_parsenum(const char *ptr, int len) { /* * For fields containing numbers or timestamps that are out of range * for the basic format, the GNU format uses a base-256 representation * instead of an ASCII octal number. */ if (*(char *)ptr == '\200') { long long res = 0; while (--len) res = (res << 8) + (u8)*(++ptr); return res; } return tarerofs_otoi(ptr, len); } struct tarerofs_xattr_item { struct list_head list; char *kv; unsigned int len, namelen; }; int tarerofs_insert_xattr(struct list_head *xattrs, char *kv, int namelen, int len, bool skip) { struct tarerofs_xattr_item *item; char *nv; DBG_BUGON(namelen >= len); list_for_each_entry(item, xattrs, list) { if (!strncmp(item->kv, kv, namelen + 1)) { if (skip) return 0; goto found; } } item = malloc(sizeof(*item)); if (!item) return -ENOMEM; item->kv = NULL; item->namelen = namelen; namelen = 0; list_add_tail(&item->list, xattrs); found: nv = realloc(item->kv, len); if (!nv) return -ENOMEM; item->kv = nv; item->len = len; memcpy(nv + namelen, kv + namelen, len - namelen); return 0; } int tarerofs_merge_xattrs(struct list_head *dst, struct list_head *src) { struct tarerofs_xattr_item *item; list_for_each_entry(item, src, list) { int ret; ret = tarerofs_insert_xattr(dst, item->kv, item->namelen, item->len, true); if (ret) return ret; } return 0; } void tarerofs_remove_xattrs(struct list_head *xattrs) { struct tarerofs_xattr_item *item, *n; list_for_each_entry_safe(item, n, xattrs, list) { DBG_BUGON(!item->kv); free(item->kv); list_del(&item->list); free(item); } } int tarerofs_apply_xattrs(struct erofs_inode *inode, struct list_head *xattrs) { struct tarerofs_xattr_item *item; int ret; list_for_each_entry(item, xattrs, list) { const char *v = item->kv + item->namelen + 1; unsigned int vsz = item->len - item->namelen - 1; if (item->len <= item->namelen - 1) { DBG_BUGON(item->len < item->namelen - 1); continue; } item->kv[item->namelen] = '\0'; erofs_dbg("Recording xattr(%s)=\"%s\" (of %u bytes) to file %s", item->kv, v, vsz, inode->i_srcpath); ret = erofs_setxattr(inode, item->kv, v, vsz); if (ret == -ENODATA) erofs_err("Failed to set xattr(%s)=%s to file %s", item->kv, v, inode->i_srcpath); else if (ret) return ret; } return 0; } static const char lookup_table[65] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; static int base64_decode(const char *src, int len, u8 *dst) { int i, bits = 0, ac = 0; const char *p; u8 *cp = dst; bool padding = false; if(len && !(len % 4)) { /* Check for and ignore any end padding */ if (src[len - 2] == '=' && src[len - 1] == '=') len -= 2; else if (src[len - 1] == '=') --len; padding = true; } for (i = 0; i < len; i++) { p = strchr(lookup_table, src[i]); if (!p || !src[i]) return -2; ac += (p - lookup_table) << bits; bits += 6; if (bits >= 8) { *cp++ = ac & 0xff; ac >>= 8; bits -= 8; } } if (ac) { if (padding || ac > 0xff) return -1; else *cp++ = ac & 0xff; } return cp - dst; } static int tohex(int c) { if (c >= '0' && c <= '9') return c - '0'; else if (c >= 'A' && c <= 'F') return c - 'A' + 10; else if (c >= 'a' && c <= 'f') return c - 'a' + 10; return -1; } static unsigned int url_decode(char *str, unsigned int len) { const char *s = str; char *d = str; int d1, d2; for (; len && *s != '\0' && *s != '%'; ++d, ++s, --len); if (!len || *s == '\0') return d - str; while (len && *s != '\0') { if (*s == '%' && len > 2) { /* Try to convert % escape */ d1 = tohex(s[1]), d2 = tohex(s[2]); /* Look good, consume three chars */ if (d1 >= 0 && d2 >= 0) { s += 3; len -= 3; *d++ = (d1 << 4) | d2; continue; } /* Otherwise, treat '%' as normal char */ } *d++ = *s++; --len; } return d - str; } int tarerofs_parse_pax_header(struct erofs_iostream *ios, struct erofs_pax_header *eh, u32 size) { char *buf, *p; int ret; buf = malloc(size); if (!buf) return -ENOMEM; p = buf; ret = erofs_iostream_bread(ios, buf, size); if (ret != size) goto out; while (p < buf + size) { char *kv, *key, *value; int len, n; /* extended records are of the format: "LEN NAME=VALUE\n" */ ret = sscanf(p, "%d %n", &len, &n); if (ret < 1 || len <= n || len > buf + size - p) { ret = -EIO; goto out; } kv = p + n; p += len; len -= n; if (p[-1] != '\n') { ret = -EIO; goto out; } p[-1] = '\0'; value = memchr(kv, '=', p - kv); if (!value) { ret = -EIO; goto out; } else { long long lln; value++; if (!strncmp(kv, "path=", sizeof("path=") - 1)) { int j = p - 1 - value; free(eh->path); eh->path = strdup(value); while (eh->path[j - 1] == '/') eh->path[--j] = '\0'; } else if (!strncmp(kv, "linkpath=", sizeof("linkpath=") - 1)) { free(eh->link); eh->link = strdup(value); } else if (!strncmp(kv, "mtime=", sizeof("mtime=") - 1)) { ret = sscanf(value, "%lld %n", &lln, &n); if(ret < 1) { ret = -EIO; goto out; } eh->st.st_mtime = lln; if (value[n] == '.') { ret = sscanf(value + n + 1, "%d", &n); if (ret < 1) { ret = -EIO; goto out; } ST_MTIM_NSEC_SET(&eh->st, n); } else { ST_MTIM_NSEC_SET(&eh->st, 0); } eh->use_mtime = true; } else if (!strncmp(kv, "size=", sizeof("size=") - 1)) { ret = sscanf(value, "%lld %n", &lln, &n); if(ret < 1 || value[n] != '\0') { ret = -EIO; goto out; } eh->st.st_size = lln; eh->use_size = true; } else if (!strncmp(kv, "uid=", sizeof("uid=") - 1)) { ret = sscanf(value, "%lld %n", &lln, &n); if(ret < 1 || value[n] != '\0') { ret = -EIO; goto out; } eh->st.st_uid = lln; eh->use_uid = true; } else if (!strncmp(kv, "gid=", sizeof("gid=") - 1)) { ret = sscanf(value, "%lld %n", &lln, &n); if(ret < 1 || value[n] != '\0') { ret = -EIO; goto out; } eh->st.st_gid = lln; eh->use_gid = true; } else if (!strncmp(kv, "SCHILY.xattr.", sizeof("SCHILY.xattr.") - 1)) { key = kv + sizeof("SCHILY.xattr.") - 1; --len; /* p[-1] == '\0' */ ret = tarerofs_insert_xattr(&eh->xattrs, key, value - key - 1, len - (key - kv), false); if (ret) goto out; } else if (!strncmp(kv, "LIBARCHIVE.xattr.", sizeof("LIBARCHIVE.xattr.") - 1)) { int namelen; key = kv + sizeof("LIBARCHIVE.xattr.") - 1; namelen = url_decode(key, value - key - 1); --len; /* p[-1] == '\0' */ ret = base64_decode(value, len - (value - kv), (u8 *)value); if (ret < 0) { ret = -EFSCORRUPTED; goto out; } if (namelen != value - key - 1) { key[namelen] = '='; memmove(key + namelen + 1, value, ret); value = key + namelen + 1; } ret = tarerofs_insert_xattr(&eh->xattrs, key, namelen, namelen + 1 + ret, false); if (ret) goto out; } else { erofs_info("unrecognized pax keyword \"%s\", ignoring", kv); } } } ret = 0; out: free(buf); return ret; } void tarerofs_remove_inode(struct erofs_inode *inode) { struct erofs_dentry *d; --inode->i_nlink; if (!S_ISDIR(inode->i_mode)) return; /* remove all subdirss */ list_for_each_entry(d, &inode->i_subdirs, d_child) { if (!is_dot_dotdot(d->name)) tarerofs_remove_inode(d->inode); erofs_iput(d->inode); d->inode = NULL; } --inode->i_parent->i_nlink; } static int tarerofs_write_uncompressed_file(struct erofs_inode *inode, struct erofs_tarfile *tar) { struct erofs_sb_info *sbi = inode->sbi; erofs_blk_t nblocks; erofs_off_t pos; void *buf; int ret; inode->datalayout = EROFS_INODE_FLAT_PLAIN; nblocks = DIV_ROUND_UP(inode->i_size, 1U << sbi->blkszbits); ret = erofs_allocate_inode_bh_data(inode, nblocks); if (ret) return ret; for (pos = 0; pos < inode->i_size; pos += ret) { ret = erofs_iostream_read(&tar->ios, &buf, inode->i_size - pos); if (ret < 0) break; if (erofs_dev_write(sbi, buf, erofs_pos(sbi, inode->u.i_blkaddr) + pos, ret)) { ret = -EIO; break; } } inode->idata_size = 0; inode->datasource = EROFS_INODE_DATA_SOURCE_NONE; return 0; } static int tarerofs_write_file_data(struct erofs_inode *inode, struct erofs_tarfile *tar) { void *buf; int fd, nread; u64 off, j; if (!inode->i_diskbuf) { inode->i_diskbuf = calloc(1, sizeof(*inode->i_diskbuf)); if (!inode->i_diskbuf) return -ENOSPC; } else { erofs_diskbuf_close(inode->i_diskbuf); } fd = erofs_diskbuf_reserve(inode->i_diskbuf, 0, &off); if (fd < 0) return -EBADF; for (j = inode->i_size; j; ) { nread = erofs_iostream_read(&tar->ios, &buf, j); if (nread < 0) break; if (write(fd, buf, nread) != nread) { nread = -EIO; break; } j -= nread; } erofs_diskbuf_commit(inode->i_diskbuf, inode->i_size); inode->datasource = EROFS_INODE_DATA_SOURCE_DISKBUF; return 0; } int tarerofs_parse_tar(struct erofs_inode *root, struct erofs_tarfile *tar) { char path[PATH_MAX]; struct erofs_pax_header eh = tar->global; struct erofs_sb_info *sbi = root->sbi; bool whout, opq, e = false; struct stat st; mode_t mode; erofs_off_t tar_offset, dataoff; struct tar_header *th; struct erofs_dentry *d; struct erofs_inode *inode; unsigned int j, csum, cksum; int ckksum, ret, rem; root->dev = tar->dev; if (eh.path) eh.path = strdup(eh.path); if (eh.link) eh.link = strdup(eh.link); init_list_head(&eh.xattrs); restart: rem = tar->offset & 511; if (rem) { if (erofs_iostream_lskip(&tar->ios, 512 - rem)) { ret = -EIO; goto out; } tar->offset += 512 - rem; } tar_offset = tar->offset; ret = erofs_iostream_read(&tar->ios, (void **)&th, sizeof(*th)); if (ret != sizeof(*th)) { if (tar->headeronly_mode || tar->ddtaridx_mode) { ret = 1; goto out; } erofs_err("failed to read header block @ %llu", tar_offset); ret = -EIO; goto out; } tar->offset += sizeof(*th); /* chksum field itself treated as ' ' */ csum = tarerofs_otoi(th->chksum, sizeof(th->chksum)); if (errno) { if (*th->name == '\0') { out_eot: if (e) { /* end of tar 2 empty blocks */ ret = 1; goto out; } e = true; /* empty jump to next block */ goto restart; } erofs_err("invalid chksum @ %llu", tar_offset); ret = -EBADMSG; goto out; } cksum = 0; for (j = 0; j < 8; ++j) cksum += (unsigned int)' '; ckksum = cksum; for (j = 0; j < 148; ++j) { cksum += (unsigned int)((u8*)th)[j]; ckksum += (int)((char*)th)[j]; } for (j = 156; j < 500; ++j) { cksum += (unsigned int)((u8*)th)[j]; ckksum += (int)((char*)th)[j]; } if (!tar->ddtaridx_mode && csum != cksum && csum != ckksum) { /* should not bail out here, just in case */ if (*th->name == '\0') { DBG_BUGON(1); goto out_eot; } erofs_err("chksum mismatch @ %llu", tar_offset); ret = -EBADMSG; goto out; } if (th->typeflag == GNUTYPE_VOLHDR) { if (th->size[0]) erofs_warn("GNUTYPE_VOLHDR with non-zeroed size @ %llu", tar_offset); /* anyway, strncpy could cause some GCC warning here */ memcpy(sbi->volume_name, th->name, sizeof(sbi->volume_name)); goto restart; } if (memcmp(th->magic, "ustar", 5)) { erofs_err("invalid tar magic @ %llu", tar_offset); ret = -EIO; goto out; } if (eh.use_size) { st.st_size = eh.st.st_size; } else { st.st_size = tarerofs_parsenum(th->size, sizeof(th->size)); if (errno) goto invalid_tar; } if (th->typeflag <= '7' && !eh.path) { eh.path = path; j = 0; if (*th->prefix) { memcpy(path, th->prefix, sizeof(th->prefix)); path[sizeof(th->prefix)] = '\0'; j = strlen(path); if (path[j - 1] != '/') { path[j] = '/'; path[++j] = '\0'; } } memcpy(path + j, th->name, sizeof(th->name)); path[j + sizeof(th->name)] = '\0'; j = strlen(path); if (__erofs_unlikely(!j)) { erofs_info("substituting '.' for empty filename"); path[0] = '.'; path[1] = '\0'; } else { while (path[j - 1] == '/') path[--j] = '\0'; } } dataoff = tar->offset; tar->offset += st.st_size; st.st_mode = 0; switch(th->typeflag) { case '0': case '7': case '1': st.st_mode = S_IFREG; if (tar->headeronly_mode || tar->ddtaridx_mode) tar->offset -= st.st_size; break; case '2': st.st_mode = S_IFLNK; break; case '3': st.st_mode = S_IFCHR; break; case '4': st.st_mode = S_IFBLK; break; case '5': st.st_mode = S_IFDIR; break; case '6': st.st_mode = S_IFIFO; break; case 'g': ret = tarerofs_parse_pax_header(&tar->ios, &tar->global, st.st_size); if (ret) goto out; if (tar->global.path) { free(eh.path); eh.path = strdup(tar->global.path); } if (tar->global.link) { free(eh.link); eh.link = strdup(tar->global.link); } goto restart; case 'x': ret = tarerofs_parse_pax_header(&tar->ios, &eh, st.st_size); if (ret) goto out; goto restart; case 'L': free(eh.path); eh.path = malloc(st.st_size + 1); if (st.st_size != erofs_iostream_bread(&tar->ios, eh.path, st.st_size)) goto invalid_tar; eh.path[st.st_size] = '\0'; goto restart; case 'K': free(eh.link); eh.link = malloc(st.st_size + 1); if (st.st_size > PATH_MAX || st.st_size != erofs_iostream_bread(&tar->ios, eh.link, st.st_size)) goto invalid_tar; eh.link[st.st_size] = '\0'; goto restart; default: erofs_info("unrecognized typeflag %xh @ %llu - ignoring", th->typeflag, tar_offset); (void)erofs_iostream_lskip(&tar->ios, st.st_size); ret = 0; goto out; } mode = tarerofs_otoi(th->mode, sizeof(th->mode)); if (errno) goto invalid_tar; if (__erofs_unlikely(mode & S_IFMT) && (mode & S_IFMT) != (st.st_mode & S_IFMT)) erofs_warn("invalid ustar mode %05o @ %llu", mode, tar_offset); st.st_mode |= mode & ~S_IFMT; if (eh.use_uid) { st.st_uid = eh.st.st_uid; } else { st.st_uid = tarerofs_parsenum(th->uid, sizeof(th->uid)); if (errno) goto invalid_tar; } if (eh.use_gid) { st.st_gid = eh.st.st_gid; } else { st.st_gid = tarerofs_parsenum(th->gid, sizeof(th->gid)); if (errno) goto invalid_tar; } if (eh.use_mtime) { st.st_mtime = eh.st.st_mtime; ST_MTIM_NSEC_SET(&st, ST_MTIM_NSEC(&eh.st)); } else { st.st_mtime = tarerofs_parsenum(th->mtime, sizeof(th->mtime)); if (errno) goto invalid_tar; ST_MTIM_NSEC_SET(&st, 0); } st.st_rdev = 0; if (S_ISBLK(st.st_mode) || S_ISCHR(st.st_mode)) { int major, minor; major = tarerofs_parsenum(th->devmajor, sizeof(th->devmajor)); if (errno) { erofs_err("invalid device major @ %llu", tar_offset); goto out; } minor = tarerofs_parsenum(th->devminor, sizeof(th->devminor)); if (errno) { erofs_err("invalid device minor @ %llu", tar_offset); goto out; } st.st_rdev = (major << 8) | (minor & 0xff) | ((minor & ~0xff) << 12); } else if (th->typeflag == '1' || th->typeflag == '2') { if (!eh.link) eh.link = strndup(th->linkname, sizeof(th->linkname)); } /* EROFS metadata index referring to the original tar data */ if (tar->index_mode && sbi->extra_devices && erofs_blkoff(sbi, dataoff)) { erofs_err("invalid tar data alignment @ %llu", tar_offset); ret = -EIO; goto out; } erofs_dbg("parsing %s (mode %05o)", eh.path, st.st_mode); d = erofs_rebuild_get_dentry(root, eh.path, tar->aufs, &whout, &opq, true); if (IS_ERR(d)) { ret = PTR_ERR(d); goto out; } if (!d) { /* some tarballs include '.' which indicates the root directory */ if (!S_ISDIR(st.st_mode)) { ret = -ENOTDIR; goto out; } inode = root; } else if (opq) { DBG_BUGON(d->type == EROFS_FT_UNKNOWN); DBG_BUGON(!d->inode); /* * needed if the tar tree is used soon, thus we have no chance * to generate it from xattrs. No impact to mergefs. */ d->inode->opaque = true; ret = erofs_set_opaque_xattr(d->inode); goto out; } else if (th->typeflag == '1') { /* hard link cases */ struct erofs_dentry *d2; bool dumb; if (S_ISDIR(st.st_mode)) { ret = -EISDIR; goto out; } if (d->type != EROFS_FT_UNKNOWN) { tarerofs_remove_inode(d->inode); erofs_iput(d->inode); } d->inode = NULL; d2 = erofs_rebuild_get_dentry(root, eh.link, tar->aufs, &dumb, &dumb, false); if (IS_ERR(d2)) { ret = PTR_ERR(d2); goto out; } if (d2->type == EROFS_FT_UNKNOWN) { ret = -ENOENT; goto out; } if (S_ISDIR(d2->inode->i_mode)) { ret = -EISDIR; goto out; } inode = erofs_igrab(d2->inode); d->inode = inode; d->type = d2->type; ++inode->i_nlink; ret = 0; goto out; } else if (d->type != EROFS_FT_UNKNOWN) { if (d->type != EROFS_FT_DIR || !S_ISDIR(st.st_mode)) { struct erofs_inode *parent = d->inode->i_parent; tarerofs_remove_inode(d->inode); erofs_iput(d->inode); d->inode = parent; goto new_inode; } inode = d->inode; } else { new_inode: inode = erofs_new_inode(sbi); if (IS_ERR(inode)) { ret = PTR_ERR(inode); goto out; } inode->dev = tar->dev; inode->i_parent = d->inode; d->inode = inode; d->type = erofs_mode_to_ftype(st.st_mode); } if (whout) { inode->i_mode = (inode->i_mode & ~S_IFMT) | S_IFCHR; inode->u.i_rdev = EROFS_WHITEOUT_DEV; d->type = EROFS_FT_CHRDEV; /* * Mark the parent directory as copied-up to avoid exposing * whiteouts if mounted. See kernel commit b79e05aaa166 * ("ovl: no direct iteration for dir with origin xattr") */ inode->i_parent->whiteouts = true; } else { inode->i_mode = st.st_mode; if (S_ISBLK(st.st_mode) || S_ISCHR(st.st_mode)) inode->u.i_rdev = erofs_new_encode_dev(st.st_rdev); } inode->i_srcpath = strdup(eh.path); if (!inode->i_srcpath) { ret = -ENOMEM; goto out; } ret = __erofs_fill_inode(inode, &st, eh.path); if (ret) goto out; inode->i_size = st.st_size; if (!S_ISDIR(inode->i_mode)) { if (S_ISLNK(inode->i_mode)) { inode->i_size = strlen(eh.link); inode->i_link = malloc(inode->i_size + 1); memcpy(inode->i_link, eh.link, inode->i_size + 1); } else if (inode->i_size) { if (tar->headeronly_mode) { ret = erofs_write_zero_inode(inode); } else if (tar->ddtaridx_mode) { dataoff = le64_to_cpu(*(__le64 *)(th->devmajor)); if (tar->rvsp_mode) { inode->datasource = EROFS_INODE_DATA_SOURCE_RESVSP; inode->i_ino[1] = dataoff; ret = 0; } else { ret = tarerofs_write_chunkes(inode, dataoff); } } else if (tar->rvsp_mode) { inode->datasource = EROFS_INODE_DATA_SOURCE_RESVSP; inode->i_ino[1] = dataoff; if (erofs_iostream_lskip(&tar->ios, inode->i_size)) ret = -EIO; else ret = 0; } else if (tar->index_mode) { ret = tarerofs_write_chunkes(inode, dataoff); if (!ret && erofs_iostream_lskip(&tar->ios, inode->i_size)) ret = -EIO; } else if (tar->try_no_reorder && !cfg.c_compr_opts[0].alg && !cfg.c_inline_data) { ret = tarerofs_write_uncompressed_file(inode, tar); } else { ret = tarerofs_write_file_data(inode, tar); } if (ret) goto out; } inode->i_nlink++; } else if (!inode->i_nlink) { ret = erofs_init_empty_dir(inode); if (ret) goto out; } ret = tarerofs_merge_xattrs(&eh.xattrs, &tar->global.xattrs); if (ret) goto out; ret = tarerofs_apply_xattrs(inode, &eh.xattrs); out: if (eh.path != path) free(eh.path); free(eh.link); tarerofs_remove_xattrs(&eh.xattrs); return ret; invalid_tar: erofs_err("invalid tar @ %llu", tar_offset); ret = -EIO; goto out; } erofs-utils-1.8.6/lib/uuid.c000066400000000000000000000047301477440110200156660ustar00rootroot00000000000000// SPDX-License-Identifier: GPL-2.0+ OR Apache-2.0 /* * Copyright (C) 2023 Norbert Lange */ #include #include #include "erofs/config.h" #include "erofs/defs.h" #include "liberofs_uuid.h" #ifdef HAVE_LIBUUID #include #else #include #ifdef HAVE_SYS_RANDOM_H #include #else #define _GNU_SOURCE #include #include #endif /* Flags to be used, will be modified if kernel does not support them */ static unsigned int erofs_grnd_flag = #ifdef GRND_INSECURE GRND_INSECURE; #else 0x0004; #endif static int s_getrandom(void *out, unsigned size, bool insecure) { unsigned int kflags = erofs_grnd_flag; unsigned int flags = insecure ? kflags : 0; for (;;) { ssize_t r; int err; #ifdef HAVE_SYS_RANDOM_H r = getrandom(out, size, flags); #elif defined(__NR_getrandom) r = (ssize_t)syscall(__NR_getrandom, out, size, flags); #else r = -1; errno = ENOSYS; (void)flags; #endif if (r == size) break; err = errno; if (err != EINTR) { if (__erofs_unlikely(err == ENOSYS && insecure)) { while (size) { *(u8 *)out++ = rand() % 256; --size; } err = 0; } else if (err == EINVAL && kflags) { // Kernel likely does not support GRND_INSECURE erofs_grnd_flag = 0; kflags = 0; continue; } return -err; } } return 0; } #endif void erofs_uuid_generate(unsigned char *out) { #ifdef HAVE_LIBUUID uuid_t new_uuid; do { uuid_generate(new_uuid); } while (uuid_is_null(new_uuid)); #else unsigned char new_uuid[16]; int res __maybe_unused; res = s_getrandom(new_uuid, sizeof(new_uuid), true); BUG_ON(res != 0); // UID type + version bits new_uuid[0] = (new_uuid[4 + 2] & 0x0f) | 0x40; new_uuid[1] = (new_uuid[4 + 2 + 2] & 0x3f) | 0x80; #endif memcpy(out, new_uuid, sizeof(new_uuid)); } int erofs_uuid_parse(const char *in, unsigned char *uu) { #ifdef HAVE_LIBUUID return uuid_parse((char *)in, uu); #else unsigned char new_uuid[16]; unsigned int hypens = ((1U << 3) | (1U << 5) | (1U << 7) | (1U << 9)); int i; for (i = 0; i < sizeof(new_uuid); hypens >>= 1, i++) { char c[] = { in[0], in[1], '\0' }; char* endptr = c; unsigned long val = strtoul(c, &endptr, 16); if (endptr - c != 2) return -EINVAL; in += 2; if ((hypens & 1U) != 0) { if (*in++ != '-') return -EINVAL; } new_uuid[i] = (unsigned char)val; } if (*in != '\0') return -EINVAL; memcpy(uu, new_uuid, sizeof(new_uuid)); return 0; #endif } erofs-utils-1.8.6/lib/uuid_unparse.c000066400000000000000000000010331477440110200174140ustar00rootroot00000000000000// SPDX-License-Identifier: GPL-2.0+ OR Apache-2.0 /* * Copyright (C) 2023 Norbert Lange */ #include #include "erofs/config.h" #include "liberofs_uuid.h" void erofs_uuid_unparse_lower(const unsigned char *buf, char *out) { sprintf(out, "%04x%04x-%04x-%04x-%04x-%04x%04x%04x", (buf[0] << 8) | buf[1], (buf[2] << 8) | buf[3], (buf[4] << 8) | buf[5], (buf[6] << 8) | buf[7], (buf[8] << 8) | buf[9], (buf[10] << 8) | buf[11], (buf[12] << 8) | buf[13], (buf[14] << 8) | buf[15]); } erofs-utils-1.8.6/lib/workqueue.c000066400000000000000000000050551477440110200167500ustar00rootroot00000000000000// SPDX-License-Identifier: GPL-2.0+ OR Apache-2.0 #include #include #include "erofs/workqueue.h" static void *worker_thread(void *arg) { struct erofs_workqueue *wq = arg; struct erofs_work *work; void *tlsp = NULL; if (wq->on_start) tlsp = (wq->on_start)(wq, NULL); while (true) { pthread_mutex_lock(&wq->lock); while (!wq->job_count && !wq->shutdown) pthread_cond_wait(&wq->cond_empty, &wq->lock); if (!wq->job_count && wq->shutdown) { pthread_mutex_unlock(&wq->lock); break; } work = wq->head; wq->head = work->next; if (!wq->head) wq->tail = NULL; wq->job_count--; if (wq->job_count == wq->max_jobs - 1) pthread_cond_broadcast(&wq->cond_full); pthread_mutex_unlock(&wq->lock); work->fn(work, tlsp); } if (wq->on_exit) (void)(wq->on_exit)(wq, tlsp); return NULL; } int erofs_destroy_workqueue(struct erofs_workqueue *wq) { if (!wq) return -EINVAL; pthread_mutex_lock(&wq->lock); wq->shutdown = true; pthread_cond_broadcast(&wq->cond_empty); pthread_mutex_unlock(&wq->lock); while (wq->nworker) { int ret = -pthread_join(wq->workers[wq->nworker - 1], NULL); if (ret) return ret; --wq->nworker; } free(wq->workers); pthread_mutex_destroy(&wq->lock); pthread_cond_destroy(&wq->cond_empty); pthread_cond_destroy(&wq->cond_full); return 0; } int erofs_alloc_workqueue(struct erofs_workqueue *wq, unsigned int nworker, unsigned int max_jobs, erofs_wq_func_t on_start, erofs_wq_func_t on_exit) { unsigned int i; int ret; if (!wq || nworker <= 0 || max_jobs <= 0) return -EINVAL; wq->head = wq->tail = NULL; wq->max_jobs = max_jobs; wq->job_count = 0; wq->shutdown = false; wq->on_start = on_start; wq->on_exit = on_exit; pthread_mutex_init(&wq->lock, NULL); pthread_cond_init(&wq->cond_empty, NULL); pthread_cond_init(&wq->cond_full, NULL); wq->workers = malloc(nworker * sizeof(pthread_t)); if (!wq->workers) return -ENOMEM; for (i = 0; i < nworker; i++) { ret = -pthread_create(&wq->workers[i], NULL, worker_thread, wq); if (ret) break; } wq->nworker = i; if (ret) erofs_destroy_workqueue(wq); return ret; } int erofs_queue_work(struct erofs_workqueue *wq, struct erofs_work *work) { if (!wq || !work) return -EINVAL; pthread_mutex_lock(&wq->lock); while (wq->job_count == wq->max_jobs) pthread_cond_wait(&wq->cond_full, &wq->lock); work->next = NULL; if (!wq->head) wq->head = work; else wq->tail->next = work; wq->tail = work; wq->job_count++; pthread_cond_signal(&wq->cond_empty); pthread_mutex_unlock(&wq->lock); return 0; } erofs-utils-1.8.6/lib/xattr.c000066400000000000000000001163621477440110200160670ustar00rootroot00000000000000// SPDX-License-Identifier: GPL-2.0+ OR Apache-2.0 /* * Originally contributed by an anonymous person, * heavily changed by Li Guifu * and Gao Xiang */ #define _GNU_SOURCE #include #include #ifdef HAVE_LINUX_XATTR_H #include #endif #include #include #include "erofs/print.h" #include "erofs/hashtable.h" #include "erofs/xattr.h" #include "erofs/cache.h" #include "erofs/fragments.h" #include "liberofs_xxhash.h" #include "liberofs_private.h" #ifndef XATTR_SYSTEM_PREFIX #define XATTR_SYSTEM_PREFIX "system." #endif #ifndef XATTR_SYSTEM_PREFIX_LEN #define XATTR_SYSTEM_PREFIX_LEN (sizeof(XATTR_SYSTEM_PREFIX) - 1) #endif #ifndef XATTR_USER_PREFIX #define XATTR_USER_PREFIX "user." #endif #ifndef XATTR_USER_PREFIX_LEN #define XATTR_USER_PREFIX_LEN (sizeof(XATTR_USER_PREFIX) - 1) #endif #ifndef XATTR_SECURITY_PREFIX #define XATTR_SECURITY_PREFIX "security." #endif #ifndef XATTR_SECURITY_PREFIX_LEN #define XATTR_SECURITY_PREFIX_LEN (sizeof(XATTR_SECURITY_PREFIX) - 1) #endif #ifndef XATTR_TRUSTED_PREFIX #define XATTR_TRUSTED_PREFIX "trusted." #endif #ifndef XATTR_TRUSTED_PREFIX_LEN #define XATTR_TRUSTED_PREFIX_LEN (sizeof(XATTR_TRUSTED_PREFIX) - 1) #endif #ifndef XATTR_NAME_POSIX_ACL_ACCESS #define XATTR_NAME_POSIX_ACL_ACCESS "system.posix_acl_access" #endif #ifndef XATTR_NAME_POSIX_ACL_DEFAULT #define XATTR_NAME_POSIX_ACL_DEFAULT "system.posix_acl_default" #endif #ifndef XATTR_NAME_SECURITY_SELINUX #define XATTR_NAME_SECURITY_SELINUX "security.selinux" #endif #ifndef XATTR_NAME_SECURITY_CAPABILITY #define XATTR_NAME_SECURITY_CAPABILITY "security.capability" #endif #ifndef OVL_XATTR_NAMESPACE #define OVL_XATTR_NAMESPACE "overlay." #endif #ifndef OVL_XATTR_OPAQUE_POSTFIX #define OVL_XATTR_OPAQUE_POSTFIX "opaque" #endif #ifndef OVL_XATTR_ORIGIN_POSTFIX #define OVL_XATTR_ORIGIN_POSTFIX "origin" #endif #ifndef OVL_XATTR_TRUSTED_PREFIX #define OVL_XATTR_TRUSTED_PREFIX XATTR_TRUSTED_PREFIX OVL_XATTR_NAMESPACE #endif #ifndef OVL_XATTR_OPAQUE #define OVL_XATTR_OPAQUE OVL_XATTR_TRUSTED_PREFIX OVL_XATTR_OPAQUE_POSTFIX #endif #ifndef OVL_XATTR_ORIGIN #define OVL_XATTR_ORIGIN OVL_XATTR_TRUSTED_PREFIX OVL_XATTR_ORIGIN_POSTFIX #endif #define EA_HASHTABLE_BITS 16 /* one extra byte for the trailing `\0` of attribute name */ #define EROFS_XATTR_KSIZE(kvlen) (kvlen[0] + 1) #define EROFS_XATTR_KVSIZE(kvlen) (EROFS_XATTR_KSIZE(kvlen) + kvlen[1]) /* * @base_index: the index of the matched predefined short prefix * @prefix: the index of the matched long prefix, if any; * same as base_index otherwise * @prefix_len: the length of the matched long prefix if any; * the length of the matched predefined short prefix otherwise */ struct xattr_item { struct xattr_item *next_shared_xattr; const char *kvbuf; unsigned int hash[2], len[2], count; int shared_xattr_id; unsigned int prefix, base_index, prefix_len; struct hlist_node node; }; struct inode_xattr_node { struct list_head list; struct xattr_item *item; }; static DECLARE_HASHTABLE(ea_hashtable, EA_HASHTABLE_BITS); static struct xattr_item *shared_xattrs_list; static unsigned int shared_xattrs_count; static struct xattr_prefix { const char *prefix; unsigned int prefix_len; } xattr_types[] = { [EROFS_XATTR_INDEX_USER] = { XATTR_USER_PREFIX, XATTR_USER_PREFIX_LEN }, [EROFS_XATTR_INDEX_POSIX_ACL_ACCESS] = { XATTR_NAME_POSIX_ACL_ACCESS, sizeof(XATTR_NAME_POSIX_ACL_ACCESS) - 1 }, [EROFS_XATTR_INDEX_POSIX_ACL_DEFAULT] = { XATTR_NAME_POSIX_ACL_DEFAULT, sizeof(XATTR_NAME_POSIX_ACL_DEFAULT) - 1 }, [EROFS_XATTR_INDEX_TRUSTED] = { XATTR_TRUSTED_PREFIX, XATTR_TRUSTED_PREFIX_LEN }, [EROFS_XATTR_INDEX_SECURITY] = { XATTR_SECURITY_PREFIX, XATTR_SECURITY_PREFIX_LEN } }; struct ea_type_node { struct list_head list; struct xattr_prefix type; unsigned int index, base_index, base_len; }; static LIST_HEAD(ea_name_prefixes); static unsigned int ea_prefix_count; bool erofs_xattr_prefix_matches(const char *key, unsigned int *index, unsigned int *len) { struct xattr_prefix *p; for (p = xattr_types; p < xattr_types + ARRAY_SIZE(xattr_types); ++p) { if (p->prefix && !strncmp(p->prefix, key, p->prefix_len)) { *len = p->prefix_len; *index = p - xattr_types; return true; } } return false; } static unsigned int BKDRHash(char *str, unsigned int len) { const unsigned int seed = 131313; unsigned int hash = 0; while (len) { hash = hash * seed + (*str++); --len; } return hash; } static unsigned int put_xattritem(struct xattr_item *item) { if (item->count > 1) return --item->count; hash_del(&item->node); free(item); return 0; } static struct xattr_item *get_xattritem(char *kvbuf, unsigned int len[2]) { struct xattr_item *item; struct ea_type_node *tnode; unsigned int hash[2], hkey; hash[0] = BKDRHash(kvbuf, len[0]); hash[1] = BKDRHash(kvbuf + EROFS_XATTR_KSIZE(len), len[1]); hkey = hash[0] ^ hash[1]; hash_for_each_possible(ea_hashtable, item, node, hkey) { if (item->len[0] == len[0] && item->len[1] == len[1] && item->hash[0] == hash[0] && item->hash[1] == hash[1] && !memcmp(kvbuf, item->kvbuf, EROFS_XATTR_KVSIZE(len))) { free(kvbuf); ++item->count; return item; } } item = malloc(sizeof(*item)); if (!item) return ERR_PTR(-ENOMEM); if (!erofs_xattr_prefix_matches(kvbuf, &item->base_index, &item->prefix_len)) { free(item); return ERR_PTR(-ENODATA); } DBG_BUGON(len[0] < item->prefix_len); INIT_HLIST_NODE(&item->node); item->count = 1; item->kvbuf = kvbuf; item->len[0] = len[0]; item->len[1] = len[1]; item->hash[0] = hash[0]; item->hash[1] = hash[1]; item->shared_xattr_id = -1; item->prefix = item->base_index; list_for_each_entry(tnode, &ea_name_prefixes, list) { if (item->base_index == tnode->base_index && !strncmp(tnode->type.prefix, kvbuf, tnode->type.prefix_len)) { item->prefix = tnode->index; item->prefix_len = tnode->type.prefix_len; break; } } hash_add(ea_hashtable, &item->node, hkey); return item; } static struct xattr_item *parse_one_xattr(const char *path, const char *key, unsigned int keylen) { ssize_t ret; struct xattr_item *item; unsigned int len[2]; char *kvbuf; erofs_dbg("parse xattr [%s] of %s", path, key); /* length of the key */ len[0] = keylen; /* determine length of the value */ #ifdef HAVE_LGETXATTR ret = lgetxattr(path, key, NULL, 0); #elif defined(__APPLE__) ret = getxattr(path, key, NULL, 0, 0, XATTR_NOFOLLOW); #else return ERR_PTR(-EOPNOTSUPP); #endif if (ret < 0) return ERR_PTR(-errno); len[1] = ret; /* allocate key-value buffer */ kvbuf = malloc(EROFS_XATTR_KVSIZE(len)); if (!kvbuf) return ERR_PTR(-ENOMEM); memcpy(kvbuf, key, EROFS_XATTR_KSIZE(len)); if (len[1]) { /* copy value to buffer */ #ifdef HAVE_LGETXATTR ret = lgetxattr(path, key, kvbuf + EROFS_XATTR_KSIZE(len), len[1]); #elif defined(__APPLE__) ret = getxattr(path, key, kvbuf + EROFS_XATTR_KSIZE(len), len[1], 0, XATTR_NOFOLLOW); #else ret = -EOPNOTSUPP; goto out; #endif if (ret < 0) { ret = -errno; goto out; } if (len[1] != ret) { erofs_warn("size of xattr value got changed just now (%u-> %ld)", len[1], (long)ret); len[1] = ret; } } item = get_xattritem(kvbuf, len); if (!IS_ERR(item)) return item; if (item == ERR_PTR(-ENODATA)) { erofs_warn("skipped unidentified xattr: %s", key); ret = 0; } else { ret = PTR_ERR(item); } out: free(kvbuf); return ERR_PTR(ret); } static struct xattr_item *erofs_get_selabel_xattr(const char *srcpath, mode_t mode) { #ifdef HAVE_LIBSELINUX if (cfg.sehnd) { char *secontext; int ret; unsigned int len[2]; char *kvbuf, *fspath; struct xattr_item *item; if (cfg.mount_point) ret = asprintf(&fspath, "/%s/%s", cfg.mount_point, erofs_fspath(srcpath)); else ret = asprintf(&fspath, "/%s", erofs_fspath(srcpath)); if (ret <= 0) return ERR_PTR(-ENOMEM); ret = selabel_lookup(cfg.sehnd, &secontext, fspath, mode); free(fspath); if (ret) { ret = -errno; if (ret != -ENOENT) { erofs_err("failed to lookup selabel for %s: %s", srcpath, erofs_strerror(ret)); return ERR_PTR(ret); } /* secontext = "u:object_r:unlabeled:s0"; */ return NULL; } len[0] = sizeof(XATTR_NAME_SECURITY_SELINUX) - 1; len[1] = strlen(secontext); kvbuf = malloc(EROFS_XATTR_KVSIZE(len)); if (!kvbuf) { freecon(secontext); return ERR_PTR(-ENOMEM); } sprintf(kvbuf, "%s", XATTR_NAME_SECURITY_SELINUX); memcpy(kvbuf + EROFS_XATTR_KSIZE(len), secontext, len[1]); freecon(secontext); item = get_xattritem(kvbuf, len); if (IS_ERR(item)) free(kvbuf); return item; } #endif return NULL; } static int inode_xattr_add(struct list_head *hlist, struct xattr_item *item) { struct inode_xattr_node *node = malloc(sizeof(*node)); if (!node) return -ENOMEM; init_list_head(&node->list); node->item = item; list_add(&node->list, hlist); return 0; } static int shared_xattr_add(struct xattr_item *item) { item->next_shared_xattr = shared_xattrs_list; shared_xattrs_list = item; return ++shared_xattrs_count; } static int erofs_xattr_add(struct list_head *ixattrs, struct xattr_item *item) { if (ixattrs) return inode_xattr_add(ixattrs, item); if (item->count == cfg.c_inline_xattr_tolerance + 1) { int ret = shared_xattr_add(item); if (ret < 0) return ret; } return 0; } static bool erofs_is_skipped_xattr(const char *key) { #ifdef HAVE_LIBSELINUX /* if sehnd is valid, selabels will be overridden */ if (cfg.sehnd && !strcmp(key, XATTR_SECURITY_PREFIX "selinux")) return true; #endif return false; } static int read_xattrs_from_file(const char *path, mode_t mode, struct list_head *ixattrs) { #ifdef HAVE_LLISTXATTR ssize_t kllen = llistxattr(path, NULL, 0); #elif defined(__APPLE__) ssize_t kllen = listxattr(path, NULL, 0, XATTR_NOFOLLOW); #else ssize_t kllen = 0; #endif int ret; char *keylst, *key, *klend; unsigned int keylen; struct xattr_item *item; if (kllen < 0 && errno != ENODATA && errno != EOPNOTSUPP) { erofs_err("llistxattr to get the size of names for %s failed", path); return -errno; } ret = 0; if (kllen <= 1) goto out; keylst = malloc(kllen); if (!keylst) return -ENOMEM; /* copy the list of attribute keys to the buffer.*/ #ifdef HAVE_LLISTXATTR kllen = llistxattr(path, keylst, kllen); #elif defined(__APPLE__) kllen = listxattr(path, keylst, kllen, XATTR_NOFOLLOW); if (kllen < 0) { erofs_err("llistxattr to get names for %s failed", path); ret = -errno; goto err; } #else ret = -EOPNOTSUPP; goto err; #endif /* * loop over the list of zero terminated strings with the * attribute keys. Use the remaining buffer length to determine * the end of the list. */ klend = keylst + kllen; ret = 0; for (key = keylst; key != klend; key += keylen + 1) { keylen = strlen(key); if (erofs_is_skipped_xattr(key)) continue; item = parse_one_xattr(path, key, keylen); if (IS_ERR(item)) { ret = PTR_ERR(item); goto err; } /* skip unidentified xattrs */ if (!item) continue; ret = erofs_xattr_add(ixattrs, item); if (ret < 0) goto err; } free(keylst); out: /* if some selabel is avilable, need to add right now */ item = erofs_get_selabel_xattr(path, mode); if (IS_ERR(item)) return PTR_ERR(item); if (item) ret = erofs_xattr_add(ixattrs, item); return ret; err: free(keylst); return ret; } int erofs_setxattr(struct erofs_inode *inode, char *key, const void *value, size_t size) { char *kvbuf; unsigned int len[2]; struct xattr_item *item; len[0] = strlen(key); len[1] = size; kvbuf = malloc(EROFS_XATTR_KVSIZE(len)); if (!kvbuf) return -ENOMEM; memcpy(kvbuf, key, EROFS_XATTR_KSIZE(len)); memcpy(kvbuf + EROFS_XATTR_KSIZE(len), value, size); item = get_xattritem(kvbuf, len); if (IS_ERR(item)) { free(kvbuf); return PTR_ERR(item); } DBG_BUGON(!item); return erofs_xattr_add(&inode->i_xattrs, item); } static void erofs_removexattr(struct erofs_inode *inode, const char *key) { struct inode_xattr_node *node, *n; list_for_each_entry_safe(node, n, &inode->i_xattrs, list) { if (!strcmp(node->item->kvbuf, key)) { list_del(&node->list); put_xattritem(node->item); free(node); } } } int erofs_set_opaque_xattr(struct erofs_inode *inode) { return erofs_setxattr(inode, OVL_XATTR_OPAQUE, "y", 1); } void erofs_clear_opaque_xattr(struct erofs_inode *inode) { erofs_removexattr(inode, OVL_XATTR_OPAQUE); } int erofs_set_origin_xattr(struct erofs_inode *inode) { return erofs_setxattr(inode, OVL_XATTR_ORIGIN, NULL, 0); } #ifdef WITH_ANDROID static int erofs_droid_xattr_set_caps(struct erofs_inode *inode) { const u64 capabilities = inode->capabilities; char *kvbuf; unsigned int len[2]; struct vfs_cap_data caps; struct xattr_item *item; if (!capabilities) return 0; len[0] = sizeof(XATTR_NAME_SECURITY_CAPABILITY) - 1; len[1] = sizeof(caps); kvbuf = malloc(EROFS_XATTR_KVSIZE(len)); if (!kvbuf) return -ENOMEM; sprintf(kvbuf, "%s", XATTR_NAME_SECURITY_CAPABILITY); caps.magic_etc = VFS_CAP_REVISION_2 | VFS_CAP_FLAGS_EFFECTIVE; caps.data[0].permitted = (u32) capabilities; caps.data[0].inheritable = 0; caps.data[1].permitted = (u32) (capabilities >> 32); caps.data[1].inheritable = 0; memcpy(kvbuf + EROFS_XATTR_KSIZE(len), &caps, len[1]); item = get_xattritem(kvbuf, len); if (IS_ERR(item)) { free(kvbuf); return PTR_ERR(item); } DBG_BUGON(!item); return erofs_xattr_add(&inode->i_xattrs, item); } #else static int erofs_droid_xattr_set_caps(struct erofs_inode *inode) { return 0; } #endif int erofs_scan_file_xattrs(struct erofs_inode *inode) { int ret; struct list_head *ixattrs = &inode->i_xattrs; /* check if xattr is disabled */ if (cfg.c_inline_xattr_tolerance < 0) return 0; ret = read_xattrs_from_file(inode->i_srcpath, inode->i_mode, ixattrs); if (ret < 0) return ret; return erofs_droid_xattr_set_caps(inode); } int erofs_read_xattrs_from_disk(struct erofs_inode *inode) { ssize_t kllen; char *keylst, *key; int ret; init_list_head(&inode->i_xattrs); kllen = erofs_listxattr(inode, NULL, 0); if (kllen < 0) return kllen; if (kllen <= 1) return 0; keylst = malloc(kllen); if (!keylst) return -ENOMEM; ret = erofs_listxattr(inode, keylst, kllen); if (ret < 0) goto out; for (key = keylst; key < keylst + kllen; key += strlen(key) + 1) { void *value = NULL; size_t size = 0; if (!strcmp(key, OVL_XATTR_OPAQUE)) { if (!S_ISDIR(inode->i_mode)) { erofs_dbg("file %s: opaque xattr on non-dir", inode->i_srcpath); ret = -EINVAL; goto out; } inode->opaque = true; } ret = erofs_getxattr(inode, key, NULL, 0); if (ret < 0) goto out; if (ret) { size = ret; value = malloc(size); if (!value) { ret = -ENOMEM; goto out; } ret = erofs_getxattr(inode, key, value, size); if (ret < 0) { free(value); goto out; } DBG_BUGON(ret != size); } else if (S_ISDIR(inode->i_mode) && !strcmp(key, OVL_XATTR_ORIGIN)) { ret = 0; inode->whiteouts = true; continue; } ret = erofs_setxattr(inode, key, value, size); free(value); if (ret) break; } out: free(keylst); return ret; } static inline unsigned int erofs_next_xattr_align(unsigned int pos, struct xattr_item *item) { return EROFS_XATTR_ALIGN(pos + sizeof(struct erofs_xattr_entry) + item->len[0] + item->len[1] - item->prefix_len); } int erofs_prepare_xattr_ibody(struct erofs_inode *inode, bool noroom) { unsigned int target_xattr_isize = inode->xattr_isize; struct list_head *ixattrs = &inode->i_xattrs; struct inode_xattr_node *node; unsigned int h_shared_count; int ret; if (list_empty(ixattrs)) { ret = 0; goto out; } /* get xattr ibody size */ h_shared_count = 0; ret = sizeof(struct erofs_xattr_ibody_header); list_for_each_entry(node, ixattrs, list) { struct xattr_item *item = node->item; if (item->shared_xattr_id >= 0 && h_shared_count < UCHAR_MAX) { ++h_shared_count; ret += sizeof(__le32); continue; } ret = erofs_next_xattr_align(ret, item); } out: while (ret < target_xattr_isize) { ret += sizeof(struct erofs_xattr_entry); if (ret < target_xattr_isize) ret = EROFS_XATTR_ALIGN(ret + min_t(int, target_xattr_isize - ret, UINT16_MAX)); } if (noroom && target_xattr_isize && ret > target_xattr_isize) { erofs_err("no enough space to keep xattrs @ nid %llu", inode->nid | 0ULL); return -ENOSPC; } inode->xattr_isize = ret; return ret; } static int erofs_count_all_xattrs_from_path(const char *path) { int ret; DIR *_dir; struct stat st; _dir = opendir(path); if (!_dir) { erofs_err("failed to opendir at %s: %s", path, erofs_strerror(-errno)); return -errno; } ret = 0; while (1) { struct dirent *dp; char buf[PATH_MAX]; /* * set errno to 0 before calling readdir() in order to * distinguish end of stream and from an error. */ errno = 0; dp = readdir(_dir); if (!dp) break; if (is_dot_dotdot(dp->d_name) || !strncmp(dp->d_name, "lost+found", strlen("lost+found"))) continue; ret = snprintf(buf, PATH_MAX, "%s/%s", path, dp->d_name); if (ret < 0 || ret >= PATH_MAX) { /* ignore the too long path */ ret = -ENOMEM; goto fail; } ret = lstat(buf, &st); if (ret) { ret = -errno; goto fail; } ret = read_xattrs_from_file(buf, st.st_mode, NULL); if (ret) goto fail; if (!S_ISDIR(st.st_mode)) continue; ret = erofs_count_all_xattrs_from_path(buf); if (ret) goto fail; } if (errno) ret = -errno; fail: closedir(_dir); return ret; } static void erofs_cleanxattrs(bool sharedxattrs) { unsigned int i; struct xattr_item *item; struct hlist_node *tmp; hash_for_each_safe(ea_hashtable, i, tmp, item, node) { if (sharedxattrs && item->shared_xattr_id >= 0) continue; hash_del(&item->node); free(item); } if (sharedxattrs) return; shared_xattrs_count = 0; } static int comp_shared_xattr_item(const void *a, const void *b) { const struct xattr_item *ia, *ib; unsigned int la, lb; int ret; ia = *((const struct xattr_item **)a); ib = *((const struct xattr_item **)b); la = EROFS_XATTR_KVSIZE(ia->len); lb = EROFS_XATTR_KVSIZE(ib->len); ret = memcmp(ia->kvbuf, ib->kvbuf, min(la, lb)); if (ret != 0) return ret; return la > lb; } int erofs_xattr_flush_name_prefixes(struct erofs_sb_info *sbi) { int fd = erofs_packedfile(sbi); struct ea_type_node *tnode; s64 offset; int err; if (!ea_prefix_count) return 0; offset = lseek(fd, 0, SEEK_CUR); if (offset < 0) return -errno; offset = round_up(offset, 4); if ((offset >> 2) > UINT32_MAX) return -EOVERFLOW; if (lseek(fd, offset, SEEK_SET) < 0) return -errno; sbi->xattr_prefix_start = (u32)offset >> 2; sbi->xattr_prefix_count = ea_prefix_count; list_for_each_entry(tnode, &ea_name_prefixes, list) { union { struct { __le16 size; struct erofs_xattr_long_prefix prefix; } s; u8 data[EROFS_NAME_LEN + 2 + sizeof(struct erofs_xattr_long_prefix)]; } u; int len, infix_len; u.s.prefix.base_index = tnode->base_index; infix_len = tnode->type.prefix_len - tnode->base_len; memcpy(u.s.prefix.infix, tnode->type.prefix + tnode->base_len, infix_len); len = sizeof(struct erofs_xattr_long_prefix) + infix_len; u.s.size = cpu_to_le16(len); err = __erofs_io_write(fd, &u.s, sizeof(__le16) + len); if (err != sizeof(__le16) + len) { if (err < 0) return -errno; return -EIO; } offset = round_up(offset + sizeof(__le16) + len, 4); if (lseek(fd, offset, SEEK_SET) < 0) return -errno; } erofs_sb_set_fragments(sbi); erofs_sb_set_xattr_prefixes(sbi); return 0; } static void erofs_write_xattr_entry(char *buf, struct xattr_item *item) { struct erofs_xattr_entry entry = { .e_name_index = item->prefix, .e_name_len = item->len[0] - item->prefix_len, .e_value_size = cpu_to_le16(item->len[1]), }; memcpy(buf, &entry, sizeof(entry)); buf += sizeof(struct erofs_xattr_entry); memcpy(buf, item->kvbuf + item->prefix_len, item->len[0] - item->prefix_len); buf += item->len[0] - item->prefix_len; memcpy(buf, item->kvbuf + item->len[0] + 1, item->len[1]); erofs_dbg("writing xattr %d %s (%d %s)", item->base_index, item->kvbuf, item->prefix, item->kvbuf + item->prefix_len); } int erofs_build_shared_xattrs_from_path(struct erofs_sb_info *sbi, const char *path) { int ret; struct erofs_buffer_head *bh; struct xattr_item *item, *n, **sorted_n; char *buf; unsigned int p, i; erofs_off_t off; erofs_off_t shared_xattrs_size = 0; /* check if xattr or shared xattr is disabled */ if (cfg.c_inline_xattr_tolerance < 0 || cfg.c_inline_xattr_tolerance == INT_MAX) return 0; if (shared_xattrs_count) { DBG_BUGON(1); return -EINVAL; } ret = erofs_count_all_xattrs_from_path(path); if (ret) return ret; if (!shared_xattrs_count) goto out; sorted_n = malloc((shared_xattrs_count + 1) * sizeof(n)); if (!sorted_n) return -ENOMEM; i = 0; while (shared_xattrs_list) { item = shared_xattrs_list; sorted_n[i++] = item; shared_xattrs_list = item->next_shared_xattr; shared_xattrs_size = erofs_next_xattr_align(shared_xattrs_size, item); } DBG_BUGON(i != shared_xattrs_count); sorted_n[i] = NULL; qsort(sorted_n, shared_xattrs_count, sizeof(n), comp_shared_xattr_item); buf = calloc(1, shared_xattrs_size); if (!buf) { free(sorted_n); return -ENOMEM; } bh = erofs_balloc(sbi->bmgr, XATTR, shared_xattrs_size, 0); if (IS_ERR(bh)) { free(sorted_n); free(buf); return PTR_ERR(bh); } bh->op = &erofs_skip_write_bhops; erofs_mapbh(NULL, bh->block); off = erofs_btell(bh, false); sbi->xattr_blkaddr = off / erofs_blksiz(sbi); off %= erofs_blksiz(sbi); p = 0; for (i = 0; i < shared_xattrs_count; i++) { item = sorted_n[i]; erofs_write_xattr_entry(buf + p, item); item->next_shared_xattr = sorted_n[i + 1]; item->shared_xattr_id = (off + p) / sizeof(__le32); p = erofs_next_xattr_align(p, item); } shared_xattrs_list = sorted_n[0]; free(sorted_n); bh->op = &erofs_drop_directly_bhops; ret = erofs_dev_write(sbi, buf, erofs_btell(bh, false), shared_xattrs_size); free(buf); erofs_bdrop(bh, false); out: erofs_cleanxattrs(true); return ret; } char *erofs_export_xattr_ibody(struct erofs_inode *inode) { struct list_head *ixattrs = &inode->i_xattrs; unsigned int size = inode->xattr_isize; struct inode_xattr_node *node, *n; struct xattr_item *item; struct erofs_xattr_ibody_header *header; LIST_HEAD(ilst); unsigned int p; char *buf = calloc(1, size); if (!buf) return ERR_PTR(-ENOMEM); header = (struct erofs_xattr_ibody_header *)buf; header->h_shared_count = 0; if (cfg.c_xattr_name_filter) { u32 name_filter = 0; int hashbit; unsigned int base_len; list_for_each_entry(node, ixattrs, list) { item = node->item; base_len = xattr_types[item->base_index].prefix_len; hashbit = xxh32(item->kvbuf + base_len, item->len[0] - base_len, EROFS_XATTR_FILTER_SEED + item->base_index) & (EROFS_XATTR_FILTER_BITS - 1); name_filter |= (1UL << hashbit); } name_filter = EROFS_XATTR_FILTER_DEFAULT & ~name_filter; header->h_name_filter = cpu_to_le32(name_filter); if (header->h_name_filter) erofs_sb_set_xattr_filter(inode->sbi); } p = sizeof(struct erofs_xattr_ibody_header); list_for_each_entry_safe(node, n, ixattrs, list) { item = node->item; list_del(&node->list); /* move inline xattrs to the onstack list */ if (item->shared_xattr_id < 0 || header->h_shared_count >= UCHAR_MAX) { list_add(&node->list, &ilst); continue; } *(__le32 *)(buf + p) = cpu_to_le32(item->shared_xattr_id); p += sizeof(__le32); ++header->h_shared_count; free(node); put_xattritem(item); } list_for_each_entry_safe(node, n, &ilst, list) { item = node->item; erofs_write_xattr_entry(buf + p, item); p = erofs_next_xattr_align(p, item); list_del(&node->list); free(node); put_xattritem(item); } if (p < size) { memset(buf + p, 0, size - p); } else if (__erofs_unlikely(p > size)) { DBG_BUGON(1); free(buf); return ERR_PTR(-EFAULT); } return buf; } struct xattr_iter { char page[EROFS_MAX_BLOCK_SIZE]; void *kaddr; erofs_blk_t blkaddr; unsigned int ofs; struct erofs_sb_info *sbi; }; static int init_inode_xattrs(struct erofs_inode *vi) { struct erofs_sb_info *sbi = vi->sbi; struct xattr_iter it; unsigned int i; struct erofs_xattr_ibody_header *ih; int ret = 0; /* the most case is that xattrs of this inode are initialized. */ if (erofs_atomic_read(&vi->flags) & EROFS_I_EA_INITED) return ret; /* * bypass all xattr operations if ->xattr_isize is not greater than * sizeof(struct erofs_xattr_ibody_header), in detail: * 1) it is not enough to contain erofs_xattr_ibody_header then * ->xattr_isize should be 0 (it means no xattr); * 2) it is just to contain erofs_xattr_ibody_header, which is on-disk * undefined right now (maybe use later with some new sb feature). */ if (vi->xattr_isize == sizeof(struct erofs_xattr_ibody_header)) { erofs_err("xattr_isize %d of nid %llu is not supported yet", vi->xattr_isize, vi->nid); return -EOPNOTSUPP; } else if (vi->xattr_isize < sizeof(struct erofs_xattr_ibody_header)) { if (vi->xattr_isize) { erofs_err("bogus xattr ibody @ nid %llu", vi->nid); DBG_BUGON(1); return -EFSCORRUPTED; /* xattr ondisk layout error */ } return -ENOATTR; } it.blkaddr = erofs_blknr(sbi, erofs_iloc(vi) + vi->inode_isize); it.ofs = erofs_blkoff(sbi, erofs_iloc(vi) + vi->inode_isize); ret = erofs_blk_read(sbi, 0, it.page, it.blkaddr, 1); if (ret < 0) return -EIO; it.kaddr = it.page; ih = (struct erofs_xattr_ibody_header *)(it.kaddr + it.ofs); vi->xattr_shared_count = ih->h_shared_count; vi->xattr_shared_xattrs = malloc(vi->xattr_shared_count * sizeof(uint)); if (!vi->xattr_shared_xattrs) return -ENOMEM; /* let's skip ibody header */ it.ofs += sizeof(struct erofs_xattr_ibody_header); for (i = 0; i < vi->xattr_shared_count; ++i) { if (it.ofs >= erofs_blksiz(sbi)) { /* cannot be unaligned */ DBG_BUGON(it.ofs != erofs_blksiz(sbi)); ret = erofs_blk_read(sbi, 0, it.page, ++it.blkaddr, 1); if (ret < 0) { free(vi->xattr_shared_xattrs); vi->xattr_shared_xattrs = NULL; return -EIO; } it.kaddr = it.page; it.ofs = 0; } vi->xattr_shared_xattrs[i] = le32_to_cpu(*(__le32 *)(it.kaddr + it.ofs)); it.ofs += sizeof(__le32); } erofs_atomic_set_bit(EROFS_I_EA_INITED_BIT, &vi->flags); return ret; } /* * the general idea for these return values is * if 0 is returned, go on processing the current xattr; * 1 (> 0) is returned, skip this round to process the next xattr; * -err (< 0) is returned, an error (maybe ENOXATTR) occurred * and need to be handled */ struct xattr_iter_handlers { int (*entry)(struct xattr_iter *_it, struct erofs_xattr_entry *entry); int (*name)(struct xattr_iter *_it, unsigned int processed, char *buf, unsigned int len); int (*alloc_buffer)(struct xattr_iter *_it, unsigned int value_sz); void (*value)(struct xattr_iter *_it, unsigned int processed, char *buf, unsigned int len); }; static inline int xattr_iter_fixup(struct xattr_iter *it) { struct erofs_sb_info *sbi = it->sbi; int ret; if (it->ofs < erofs_blksiz(sbi)) return 0; it->blkaddr += erofs_blknr(sbi, it->ofs); ret = erofs_blk_read(sbi, 0, it->page, it->blkaddr, 1); if (ret < 0) return -EIO; it->kaddr = it->page; it->ofs = erofs_blkoff(sbi, it->ofs); return 0; } static int inline_xattr_iter_pre(struct xattr_iter *it, struct erofs_inode *vi) { struct erofs_sb_info *sbi = vi->sbi; unsigned int xattr_header_sz, inline_xattr_ofs; int ret; xattr_header_sz = inlinexattr_header_size(vi); if (xattr_header_sz >= vi->xattr_isize) { DBG_BUGON(xattr_header_sz > vi->xattr_isize); return -ENOATTR; } inline_xattr_ofs = vi->inode_isize + xattr_header_sz; it->blkaddr = erofs_blknr(sbi, erofs_iloc(vi) + inline_xattr_ofs); it->ofs = erofs_blkoff(sbi, erofs_iloc(vi) + inline_xattr_ofs); ret = erofs_blk_read(sbi, 0, it->page, it->blkaddr, 1); if (ret < 0) return -EIO; it->kaddr = it->page; return vi->xattr_isize - xattr_header_sz; } /* * Regardless of success or failure, `xattr_foreach' will end up with * `ofs' pointing to the next xattr item rather than an arbitrary position. */ static int xattr_foreach(struct xattr_iter *it, const struct xattr_iter_handlers *op, unsigned int *tlimit) { struct erofs_sb_info *sbi = it->sbi; struct erofs_xattr_entry entry; unsigned int value_sz, processed, slice; int err; /* 0. fixup blkaddr, ofs, ipage */ err = xattr_iter_fixup(it); if (err) return err; /* * 1. read xattr entry to the memory, * since we do EROFS_XATTR_ALIGN * therefore entry should be in the page */ entry = *(struct erofs_xattr_entry *)(it->kaddr + it->ofs); if (tlimit) { unsigned int entry_sz = erofs_xattr_entry_size(&entry); /* xattr on-disk corruption: xattr entry beyond xattr_isize */ if (*tlimit < entry_sz) { DBG_BUGON(1); return -EFSCORRUPTED; } *tlimit -= entry_sz; } it->ofs += sizeof(struct erofs_xattr_entry); value_sz = le16_to_cpu(entry.e_value_size); /* handle entry */ err = op->entry(it, &entry); if (err) { it->ofs += entry.e_name_len + value_sz; goto out; } /* 2. handle xattr name (ofs will finally be at the end of name) */ processed = 0; while (processed < entry.e_name_len) { if (it->ofs >= erofs_blksiz(sbi)) { DBG_BUGON(it->ofs > erofs_blksiz(sbi)); err = xattr_iter_fixup(it); if (err) goto out; it->ofs = 0; } slice = min_t(unsigned int, erofs_blksiz(sbi) - it->ofs, entry.e_name_len - processed); /* handle name */ err = op->name(it, processed, it->kaddr + it->ofs, slice); if (err) { it->ofs += entry.e_name_len - processed + value_sz; goto out; } it->ofs += slice; processed += slice; } /* 3. handle xattr value */ processed = 0; if (op->alloc_buffer) { err = op->alloc_buffer(it, value_sz); if (err) { it->ofs += value_sz; goto out; } } while (processed < value_sz) { if (it->ofs >= erofs_blksiz(sbi)) { DBG_BUGON(it->ofs > erofs_blksiz(sbi)); err = xattr_iter_fixup(it); if (err) goto out; it->ofs = 0; } slice = min_t(unsigned int, erofs_blksiz(sbi) - it->ofs, value_sz - processed); op->value(it, processed, it->kaddr + it->ofs, slice); it->ofs += slice; processed += slice; } out: /* xattrs should be 4-byte aligned (on-disk constraint) */ it->ofs = EROFS_XATTR_ALIGN(it->ofs); return err < 0 ? err : 0; } struct getxattr_iter { struct xattr_iter it; int buffer_size, index, infix_len; char *buffer; const char *name; size_t len; }; static int erofs_xattr_long_entrymatch(struct getxattr_iter *it, struct erofs_xattr_entry *entry) { struct erofs_sb_info *sbi = it->it.sbi; struct erofs_xattr_prefix_item *pf = sbi->xattr_prefixes + (entry->e_name_index & EROFS_XATTR_LONG_PREFIX_MASK); if (pf >= sbi->xattr_prefixes + sbi->xattr_prefix_count) return -ENOATTR; if (it->index != pf->prefix->base_index || it->len != entry->e_name_len + pf->infix_len) return -ENOATTR; if (memcmp(it->name, pf->prefix->infix, pf->infix_len)) return -ENOATTR; it->infix_len = pf->infix_len; return 0; } static int xattr_entrymatch(struct xattr_iter *_it, struct erofs_xattr_entry *entry) { struct getxattr_iter *it = container_of(_it, struct getxattr_iter, it); /* should also match the infix for long name prefixes */ if (entry->e_name_index & EROFS_XATTR_LONG_PREFIX) return erofs_xattr_long_entrymatch(it, entry); if (it->index != entry->e_name_index || it->len != entry->e_name_len) return -ENOATTR; it->infix_len = 0; return 0; } static int xattr_namematch(struct xattr_iter *_it, unsigned int processed, char *buf, unsigned int len) { struct getxattr_iter *it = container_of(_it, struct getxattr_iter, it); if (memcmp(buf, it->name + it->infix_len + processed, len)) return -ENOATTR; return 0; } static int xattr_checkbuffer(struct xattr_iter *_it, unsigned int value_sz) { struct getxattr_iter *it = container_of(_it, struct getxattr_iter, it); int err = it->buffer_size < value_sz ? -ERANGE : 0; it->buffer_size = value_sz; return !it->buffer ? 1 : err; } static void xattr_copyvalue(struct xattr_iter *_it, unsigned int processed, char *buf, unsigned int len) { struct getxattr_iter *it = container_of(_it, struct getxattr_iter, it); memcpy(it->buffer + processed, buf, len); } static const struct xattr_iter_handlers find_xattr_handlers = { .entry = xattr_entrymatch, .name = xattr_namematch, .alloc_buffer = xattr_checkbuffer, .value = xattr_copyvalue }; static int inline_getxattr(struct erofs_inode *vi, struct getxattr_iter *it) { int ret; unsigned int remaining; ret = inline_xattr_iter_pre(&it->it, vi); if (ret < 0) return ret; remaining = ret; while (remaining) { ret = xattr_foreach(&it->it, &find_xattr_handlers, &remaining); if (ret != -ENOATTR) break; } return ret ? ret : it->buffer_size; } static int shared_getxattr(struct erofs_inode *vi, struct getxattr_iter *it) { unsigned int i; int ret = -ENOATTR; for (i = 0; i < vi->xattr_shared_count; ++i) { erofs_blk_t blkaddr = xattrblock_addr(vi, vi->xattr_shared_xattrs[i]); it->it.ofs = xattrblock_offset(vi, vi->xattr_shared_xattrs[i]); if (!i || blkaddr != it->it.blkaddr) { ret = erofs_blk_read(vi->sbi, 0, it->it.page, blkaddr, 1); if (ret < 0) return -EIO; it->it.kaddr = it->it.page; it->it.blkaddr = blkaddr; } ret = xattr_foreach(&it->it, &find_xattr_handlers, NULL); if (ret != -ENOATTR) break; } return ret ? ret : it->buffer_size; } int erofs_getxattr(struct erofs_inode *vi, const char *name, char *buffer, size_t buffer_size) { int ret; unsigned int prefix, prefixlen; struct getxattr_iter it; if (!name) return -EINVAL; ret = init_inode_xattrs(vi); if (ret) return ret; if (!erofs_xattr_prefix_matches(name, &prefix, &prefixlen)) return -ENODATA; it.it.sbi = vi->sbi; it.index = prefix; it.name = name + prefixlen; it.len = strlen(it.name); if (it.len > EROFS_NAME_LEN) return -ERANGE; it.buffer = buffer; it.buffer_size = buffer_size; ret = inline_getxattr(vi, &it); if (ret == -ENOATTR) ret = shared_getxattr(vi, &it); return ret; } struct listxattr_iter { struct xattr_iter it; char *buffer; int buffer_size, buffer_ofs; }; static int xattr_entrylist(struct xattr_iter *_it, struct erofs_xattr_entry *entry) { struct listxattr_iter *it = container_of(_it, struct listxattr_iter, it); unsigned int base_index = entry->e_name_index; unsigned int prefix_len, infix_len = 0; const char *prefix, *infix = NULL; if (entry->e_name_index & EROFS_XATTR_LONG_PREFIX) { struct erofs_sb_info *sbi = _it->sbi; struct erofs_xattr_prefix_item *pf = sbi->xattr_prefixes + (entry->e_name_index & EROFS_XATTR_LONG_PREFIX_MASK); if (pf >= sbi->xattr_prefixes + sbi->xattr_prefix_count) return 1; infix = pf->prefix->infix; infix_len = pf->infix_len; base_index = pf->prefix->base_index; } if (!base_index || base_index >= ARRAY_SIZE(xattr_types)) return 1; prefix = xattr_types[base_index].prefix; prefix_len = xattr_types[base_index].prefix_len; if (!it->buffer) { it->buffer_ofs += prefix_len + infix_len + entry->e_name_len + 1; return 1; } if (it->buffer_ofs + prefix_len + infix_len + entry->e_name_len + 1 > it->buffer_size) return -ERANGE; memcpy(it->buffer + it->buffer_ofs, prefix, prefix_len); memcpy(it->buffer + it->buffer_ofs + prefix_len, infix, infix_len); it->buffer_ofs += prefix_len + infix_len; return 0; } static int xattr_namelist(struct xattr_iter *_it, unsigned int processed, char *buf, unsigned int len) { struct listxattr_iter *it = container_of(_it, struct listxattr_iter, it); memcpy(it->buffer + it->buffer_ofs, buf, len); it->buffer_ofs += len; return 0; } static int xattr_skipvalue(struct xattr_iter *_it, unsigned int value_sz) { struct listxattr_iter *it = container_of(_it, struct listxattr_iter, it); it->buffer[it->buffer_ofs++] = '\0'; return 1; } static const struct xattr_iter_handlers list_xattr_handlers = { .entry = xattr_entrylist, .name = xattr_namelist, .alloc_buffer = xattr_skipvalue, .value = NULL }; static int inline_listxattr(struct erofs_inode *vi, struct listxattr_iter *it) { int ret; unsigned int remaining; ret = inline_xattr_iter_pre(&it->it, vi); if (ret < 0) return ret; remaining = ret; while (remaining) { ret = xattr_foreach(&it->it, &list_xattr_handlers, &remaining); if (ret) break; } return ret ? ret : it->buffer_ofs; } static int shared_listxattr(struct erofs_inode *vi, struct listxattr_iter *it) { unsigned int i; int ret = 0; for (i = 0; i < vi->xattr_shared_count; ++i) { erofs_blk_t blkaddr = xattrblock_addr(vi, vi->xattr_shared_xattrs[i]); it->it.ofs = xattrblock_offset(vi, vi->xattr_shared_xattrs[i]); if (!i || blkaddr != it->it.blkaddr) { ret = erofs_blk_read(vi->sbi, 0, it->it.page, blkaddr, 1); if (ret < 0) return -EIO; it->it.kaddr = it->it.page; it->it.blkaddr = blkaddr; } ret = xattr_foreach(&it->it, &list_xattr_handlers, NULL); if (ret) break; } return ret ? ret : it->buffer_ofs; } int erofs_listxattr(struct erofs_inode *vi, char *buffer, size_t buffer_size) { int ret; struct listxattr_iter it; ret = init_inode_xattrs(vi); if (ret == -ENOATTR) return 0; if (ret) return ret; it.it.sbi = vi->sbi; it.buffer = buffer; it.buffer_size = buffer_size; it.buffer_ofs = 0; ret = inline_listxattr(vi, &it); if (ret < 0 && ret != -ENOATTR) return ret; return shared_listxattr(vi, &it); } int erofs_xattr_insert_name_prefix(const char *prefix) { struct ea_type_node *tnode; if (ea_prefix_count >= 0x80 || strlen(prefix) > UINT8_MAX) return -EOVERFLOW; tnode = calloc(1, sizeof(*tnode)); if (!tnode) return -ENOMEM; if (!erofs_xattr_prefix_matches(prefix, &tnode->base_index, &tnode->base_len)) { free(tnode); return -ENODATA; } tnode->type.prefix_len = strlen(prefix); tnode->type.prefix = strdup(prefix); if (!tnode->type.prefix) { free(tnode); return -ENOMEM; } tnode->index = EROFS_XATTR_LONG_PREFIX | ea_prefix_count; ea_prefix_count++; init_list_head(&tnode->list); list_add_tail(&tnode->list, &ea_name_prefixes); return 0; } void erofs_xattr_cleanup_name_prefixes(void) { struct ea_type_node *tnode, *n; list_for_each_entry_safe(tnode, n, &ea_name_prefixes, list) { list_del(&tnode->list); free((void *)tnode->type.prefix); free(tnode); } } void erofs_xattr_prefixes_cleanup(struct erofs_sb_info *sbi) { int i; if (sbi->xattr_prefixes) { for (i = 0; i < sbi->xattr_prefix_count; i++) free(sbi->xattr_prefixes[i].prefix); free(sbi->xattr_prefixes); sbi->xattr_prefixes = NULL; } } int erofs_xattr_prefixes_init(struct erofs_sb_info *sbi) { erofs_off_t pos = (erofs_off_t)sbi->xattr_prefix_start << 2; struct erofs_xattr_prefix_item *pfs; erofs_nid_t nid = 0; int ret = 0, i, len; void *buf; if (!sbi->xattr_prefix_count) return 0; if (sbi->packed_nid) nid = sbi->packed_nid; pfs = calloc(sbi->xattr_prefix_count, sizeof(*pfs)); if (!pfs) return -ENOMEM; for (i = 0; i < sbi->xattr_prefix_count; i++) { buf = erofs_read_metadata(sbi, nid, &pos, &len); if (IS_ERR(buf)) { ret = PTR_ERR(buf); goto out; } if (len < sizeof(*pfs->prefix) || len > EROFS_NAME_LEN + sizeof(*pfs->prefix)) { free(buf); ret = -EFSCORRUPTED; goto out; } pfs[i].prefix = buf; pfs[i].infix_len = len - sizeof(struct erofs_xattr_long_prefix); } out: sbi->xattr_prefixes = pfs; if (ret) erofs_xattr_prefixes_cleanup(sbi); return ret; } erofs-utils-1.8.6/lib/xxhash.c000066400000000000000000000141531477440110200162230ustar00rootroot00000000000000// SPDX-License-Identifier: BSD-2-Clause OR GPL-2.0-only /* * The xxhash is copied from the linux kernel at: * https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/lib/xxhash.c * * The original copyright is: * * xxHash - Extremely Fast Hash algorithm * Copyright (C) 2012-2016, Yann Collet. * * BSD 2-Clause License (http://www.opensource.org/licenses/bsd-license.php) * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are * met: * * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above * copyright notice, this list of conditions and the following disclaimer * in the documentation and/or other materials provided with the * distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * * This program is free software; you can redistribute it and/or modify it under * the terms of the GNU General Public License version 2 as published by the * Free Software Foundation. This program is dual-licensed; you may select * either version 2 of the GNU General Public License ("GPL") or BSD license * ("BSD"). * * You can contact the author at: * - xxHash homepage: https://cyan4973.github.io/xxHash/ * - xxHash source repository: https://github.com/Cyan4973/xxHash */ #include "erofs/defs.h" #include "liberofs_xxhash.h" /*-************************************* * Macros **************************************/ #define xxh_rotl32(x, r) ((x << r) | (x >> (32 - r))) #define xxh_rotl64(x, r) ((x << r) | (x >> (64 - r))) /*-************************************* * Constants **************************************/ static const uint32_t PRIME32_1 = 2654435761U; static const uint32_t PRIME32_2 = 2246822519U; static const uint32_t PRIME32_3 = 3266489917U; static const uint32_t PRIME32_4 = 668265263U; static const uint32_t PRIME32_5 = 374761393U; static const uint64_t PRIME64_1 = 11400714785074694791ULL; static const uint64_t PRIME64_2 = 14029467366897019727ULL; static const uint64_t PRIME64_3 = 1609587929392839161ULL; static const uint64_t PRIME64_4 = 9650029242287828579ULL; static const uint64_t PRIME64_5 = 2870177450012600261ULL; /*-*************************** * Simple Hash Functions ****************************/ static uint32_t xxh32_round(uint32_t seed, const uint32_t input) { seed += input * PRIME32_2; seed = xxh_rotl32(seed, 13); seed *= PRIME32_1; return seed; } uint32_t xxh32(const void *input, const size_t len, const uint32_t seed) { const uint8_t *p = (const uint8_t *)input; const uint8_t *b_end = p + len; uint32_t h32; if (len >= 16) { const uint8_t *const limit = b_end - 16; uint32_t v1 = seed + PRIME32_1 + PRIME32_2; uint32_t v2 = seed + PRIME32_2; uint32_t v3 = seed + 0; uint32_t v4 = seed - PRIME32_1; do { v1 = xxh32_round(v1, get_unaligned_le32(p)); p += 4; v2 = xxh32_round(v2, get_unaligned_le32(p)); p += 4; v3 = xxh32_round(v3, get_unaligned_le32(p)); p += 4; v4 = xxh32_round(v4, get_unaligned_le32(p)); p += 4; } while (p <= limit); h32 = xxh_rotl32(v1, 1) + xxh_rotl32(v2, 7) + xxh_rotl32(v3, 12) + xxh_rotl32(v4, 18); } else { h32 = seed + PRIME32_5; } h32 += (uint32_t)len; while (p + 4 <= b_end) { h32 += get_unaligned_le32(p) * PRIME32_3; h32 = xxh_rotl32(h32, 17) * PRIME32_4; p += 4; } while (p < b_end) { h32 += (*p) * PRIME32_5; h32 = xxh_rotl32(h32, 11) * PRIME32_1; p++; } h32 ^= h32 >> 15; h32 *= PRIME32_2; h32 ^= h32 >> 13; h32 *= PRIME32_3; h32 ^= h32 >> 16; return h32; } static uint64_t xxh64_round(uint64_t acc, const uint64_t input) { acc += input * PRIME64_2; acc = xxh_rotl64(acc, 31); acc *= PRIME64_1; return acc; } static uint64_t xxh64_merge_round(uint64_t acc, uint64_t val) { val = xxh64_round(0, val); acc ^= val; acc = acc * PRIME64_1 + PRIME64_4; return acc; } uint64_t xxh64(const void *input, const size_t len, const uint64_t seed) { const uint8_t *p = (const uint8_t *)input; const uint8_t *const b_end = p + len; uint64_t h64; if (len >= 32) { const uint8_t *const limit = b_end - 32; uint64_t v1 = seed + PRIME64_1 + PRIME64_2; uint64_t v2 = seed + PRIME64_2; uint64_t v3 = seed + 0; uint64_t v4 = seed - PRIME64_1; do { v1 = xxh64_round(v1, get_unaligned_le64(p)); p += 8; v2 = xxh64_round(v2, get_unaligned_le64(p)); p += 8; v3 = xxh64_round(v3, get_unaligned_le64(p)); p += 8; v4 = xxh64_round(v4, get_unaligned_le64(p)); p += 8; } while (p <= limit); h64 = xxh_rotl64(v1, 1) + xxh_rotl64(v2, 7) + xxh_rotl64(v3, 12) + xxh_rotl64(v4, 18); h64 = xxh64_merge_round(h64, v1); h64 = xxh64_merge_round(h64, v2); h64 = xxh64_merge_round(h64, v3); h64 = xxh64_merge_round(h64, v4); } else { h64 = seed + PRIME64_5; } h64 += (uint64_t)len; while (p + 8 <= b_end) { const uint64_t k1 = xxh64_round(0, get_unaligned_le64(p)); h64 ^= k1; h64 = xxh_rotl64(h64, 27) * PRIME64_1 + PRIME64_4; p += 8; } if (p + 4 <= b_end) { h64 ^= (uint64_t)(get_unaligned_le32(p)) * PRIME64_1; h64 = xxh_rotl64(h64, 23) * PRIME64_2 + PRIME64_3; p += 4; } while (p < b_end) { h64 ^= (*p) * PRIME64_5; h64 = xxh_rotl64(h64, 11) * PRIME64_1; p++; } h64 ^= h64 >> 33; h64 *= PRIME64_2; h64 ^= h64 >> 29; h64 *= PRIME64_3; h64 ^= h64 >> 32; return h64; } erofs-utils-1.8.6/lib/zmap.c000066400000000000000000000446431477440110200156760ustar00rootroot00000000000000// SPDX-License-Identifier: GPL-2.0+ OR Apache-2.0 /* * (a large amount of code was adapted from Linux kernel. ) * * Copyright (C) 2018-2019 HUAWEI, Inc. * https://www.huawei.com/ * Created by Gao Xiang * Modified by Huang Jianan */ #include "erofs/internal.h" #include "erofs/print.h" struct z_erofs_maprecorder { struct erofs_inode *inode; struct erofs_map_blocks *map; void *kaddr; unsigned long lcn; /* compression extent information gathered */ u8 type, headtype; u16 clusterofs; u16 delta[2]; erofs_blk_t pblk, compressedblks; erofs_off_t nextpackoff; bool partialref; }; static int z_erofs_load_full_lcluster(struct z_erofs_maprecorder *m, unsigned long lcn) { struct erofs_inode *const vi = m->inode; struct erofs_sb_info *sbi = vi->sbi; const erofs_off_t pos = Z_EROFS_FULL_INDEX_ALIGN(erofs_iloc(vi) + vi->inode_isize + vi->xattr_isize) + lcn * sizeof(struct z_erofs_lcluster_index); erofs_blk_t eblk = erofs_blknr(sbi, pos); struct z_erofs_lcluster_index *di; unsigned int advise; int err; if (m->map->index != eblk) { err = erofs_blk_read(sbi, 0, m->kaddr, eblk, 1); if (err < 0) return err; m->map->index = eblk; } di = m->kaddr + erofs_blkoff(sbi, pos); m->lcn = lcn; m->nextpackoff = pos + sizeof(struct z_erofs_lcluster_index); advise = le16_to_cpu(di->di_advise); m->type = advise & Z_EROFS_LI_LCLUSTER_TYPE_MASK; if (m->type == Z_EROFS_LCLUSTER_TYPE_NONHEAD) { m->clusterofs = 1 << vi->z_logical_clusterbits; m->delta[0] = le16_to_cpu(di->di_u.delta[0]); if (m->delta[0] & Z_EROFS_LI_D0_CBLKCNT) { if (!(vi->z_advise & (Z_EROFS_ADVISE_BIG_PCLUSTER_1 | Z_EROFS_ADVISE_BIG_PCLUSTER_2))) { DBG_BUGON(1); return -EFSCORRUPTED; } m->compressedblks = m->delta[0] & ~Z_EROFS_LI_D0_CBLKCNT; m->delta[0] = 1; } m->delta[1] = le16_to_cpu(di->di_u.delta[1]); } else { m->partialref = !!(advise & Z_EROFS_LI_PARTIAL_REF); m->clusterofs = le16_to_cpu(di->di_clusterofs); if (m->clusterofs >= 1 << vi->z_logical_clusterbits) { DBG_BUGON(1); return -EFSCORRUPTED; } m->pblk = le32_to_cpu(di->di_u.blkaddr); } return 0; } static unsigned int decode_compactedbits(unsigned int lobits, u8 *in, unsigned int pos, u8 *type) { const unsigned int v = get_unaligned_le32(in + pos / 8) >> (pos & 7); const unsigned int lo = v & ((1 << lobits) - 1); *type = (v >> lobits) & 3; return lo; } static int get_compacted_la_distance(unsigned int lobits, unsigned int encodebits, unsigned int vcnt, u8 *in, int i) { unsigned int lo, d1 = 0; u8 type; DBG_BUGON(i >= vcnt); do { lo = decode_compactedbits(lobits, in, encodebits * i, &type); if (type != Z_EROFS_LCLUSTER_TYPE_NONHEAD) return d1; ++d1; } while (++i < vcnt); /* vcnt - 1 (Z_EROFS_LCLUSTER_TYPE_NONHEAD) item */ if (!(lo & Z_EROFS_LI_D0_CBLKCNT)) d1 += lo - 1; return d1; } static int z_erofs_load_compact_lcluster(struct z_erofs_maprecorder *m, unsigned long lcn, bool lookahead) { struct erofs_inode *const vi = m->inode; struct erofs_sb_info *sbi = vi->sbi; const erofs_off_t ebase = sizeof(struct z_erofs_map_header) + round_up(erofs_iloc(vi) + vi->inode_isize + vi->xattr_isize, 8); const unsigned int lclusterbits = vi->z_logical_clusterbits; const unsigned int totalidx = BLK_ROUND_UP(sbi, vi->i_size); unsigned int compacted_4b_initial, compacted_2b, amortizedshift; unsigned int vcnt, base, lo, lobits, encodebits, nblk, eofs; bool big_pcluster = vi->z_advise & Z_EROFS_ADVISE_BIG_PCLUSTER_1; erofs_blk_t eblk; erofs_off_t pos; u8 *in, type; int i, err; if (lcn >= totalidx || lclusterbits > 14) return -EINVAL; m->lcn = lcn; /* used to align to 32-byte (compacted_2b) alignment */ compacted_4b_initial = ((32 - ebase % 32) / 4) & 7; compacted_2b = 0; if ((vi->z_advise & Z_EROFS_ADVISE_COMPACTED_2B) && compacted_4b_initial < totalidx) compacted_2b = rounddown(totalidx - compacted_4b_initial, 16); pos = ebase; amortizedshift = 2; /* compact_4b */ if (lcn >= compacted_4b_initial) { pos += compacted_4b_initial * 4; lcn -= compacted_4b_initial; if (lcn < compacted_2b) { amortizedshift = 1; } else { pos += compacted_2b * 2; lcn -= compacted_2b; } } pos += lcn * (1 << amortizedshift); /* figure out the lcluster count in this pack */ if (1 << amortizedshift == 4 && lclusterbits <= 14) vcnt = 2; else if (1 << amortizedshift == 2 && lclusterbits <= 12) vcnt = 16; else return -EOPNOTSUPP; eblk = erofs_blknr(sbi, pos); if (m->map->index != eblk) { err = erofs_blk_read(sbi, 0, m->kaddr, eblk, 1); if (err < 0) return err; m->map->index = eblk; } /* it doesn't equal to round_up(..) */ m->nextpackoff = round_down(pos, vcnt << amortizedshift) + (vcnt << amortizedshift); lobits = max(lclusterbits, ilog2(Z_EROFS_LI_D0_CBLKCNT) + 1U); encodebits = ((vcnt << amortizedshift) - sizeof(__le32)) * 8 / vcnt; eofs = erofs_blkoff(sbi, pos); base = round_down(eofs, vcnt << amortizedshift); in = m->kaddr + base; i = (eofs - base) >> amortizedshift; lo = decode_compactedbits(lobits, in, encodebits * i, &type); m->type = type; if (type == Z_EROFS_LCLUSTER_TYPE_NONHEAD) { m->clusterofs = 1 << lclusterbits; /* figure out lookahead_distance: delta[1] if needed */ if (lookahead) m->delta[1] = get_compacted_la_distance(lobits, encodebits, vcnt, in, i); if (lo & Z_EROFS_LI_D0_CBLKCNT) { if (!big_pcluster) { DBG_BUGON(1); return -EFSCORRUPTED; } m->compressedblks = lo & ~Z_EROFS_LI_D0_CBLKCNT; m->delta[0] = 1; return 0; } else if (i + 1 != (int)vcnt) { m->delta[0] = lo; return 0; } /* * since the last lcluster in the pack is special, * of which lo saves delta[1] rather than delta[0]. * Hence, get delta[0] by the previous lcluster indirectly. */ lo = decode_compactedbits(lobits, in, encodebits * (i - 1), &type); if (type != Z_EROFS_LCLUSTER_TYPE_NONHEAD) lo = 0; else if (lo & Z_EROFS_LI_D0_CBLKCNT) lo = 1; m->delta[0] = lo + 1; return 0; } m->clusterofs = lo; m->delta[0] = 0; /* figout out blkaddr (pblk) for HEAD lclusters */ if (!big_pcluster) { nblk = 1; while (i > 0) { --i; lo = decode_compactedbits(lobits, in, encodebits * i, &type); if (type == Z_EROFS_LCLUSTER_TYPE_NONHEAD) i -= lo; if (i >= 0) ++nblk; } } else { nblk = 0; while (i > 0) { --i; lo = decode_compactedbits(lobits, in, encodebits * i, &type); if (type == Z_EROFS_LCLUSTER_TYPE_NONHEAD) { if (lo & Z_EROFS_LI_D0_CBLKCNT) { --i; nblk += lo & ~Z_EROFS_LI_D0_CBLKCNT; continue; } /* bigpcluster shouldn't have plain d0 == 1 */ if (lo <= 1) { DBG_BUGON(1); return -EFSCORRUPTED; } i -= lo - 2; continue; } ++nblk; } } in += (vcnt << amortizedshift) - sizeof(__le32); m->pblk = le32_to_cpu(*(__le32 *)in) + nblk; return 0; } static int z_erofs_load_lcluster_from_disk(struct z_erofs_maprecorder *m, unsigned int lcn, bool lookahead) { switch (m->inode->datalayout) { case EROFS_INODE_COMPRESSED_FULL: return z_erofs_load_full_lcluster(m, lcn); case EROFS_INODE_COMPRESSED_COMPACT: return z_erofs_load_compact_lcluster(m, lcn, lookahead); default: return -EINVAL; } } static int z_erofs_extent_lookback(struct z_erofs_maprecorder *m, unsigned int lookback_distance) { struct erofs_inode *const vi = m->inode; const unsigned int lclusterbits = vi->z_logical_clusterbits; while (m->lcn >= lookback_distance) { unsigned long lcn = m->lcn - lookback_distance; int err; err = z_erofs_load_lcluster_from_disk(m, lcn, false); if (err) return err; switch (m->type) { case Z_EROFS_LCLUSTER_TYPE_NONHEAD: lookback_distance = m->delta[0]; if (!lookback_distance) goto err_bogus; continue; case Z_EROFS_LCLUSTER_TYPE_PLAIN: case Z_EROFS_LCLUSTER_TYPE_HEAD1: case Z_EROFS_LCLUSTER_TYPE_HEAD2: m->headtype = m->type; m->map->m_la = (lcn << lclusterbits) | m->clusterofs; return 0; default: erofs_err("unknown type %u @ lcn %lu of nid %llu", m->type, lcn, vi->nid | 0ULL); DBG_BUGON(1); return -EOPNOTSUPP; } } err_bogus: erofs_err("bogus lookback distance %u @ lcn %lu of nid %llu", lookback_distance, m->lcn | 0ULL, vi->nid); DBG_BUGON(1); return -EFSCORRUPTED; } static int z_erofs_get_extent_compressedlen(struct z_erofs_maprecorder *m, unsigned int initial_lcn) { struct erofs_inode *const vi = m->inode; struct erofs_sb_info *sbi = vi->sbi; bool bigpcl1 = vi->z_advise & Z_EROFS_ADVISE_BIG_PCLUSTER_1; bool bigpcl2 = vi->z_advise & Z_EROFS_ADVISE_BIG_PCLUSTER_2; unsigned long lcn = m->lcn + 1; int err; DBG_BUGON(m->type == Z_EROFS_LCLUSTER_TYPE_NONHEAD); DBG_BUGON(m->type != m->headtype); if ((m->headtype == Z_EROFS_LCLUSTER_TYPE_HEAD1 && !bigpcl1) || ((m->headtype == Z_EROFS_LCLUSTER_TYPE_PLAIN || m->headtype == Z_EROFS_LCLUSTER_TYPE_HEAD2) && !bigpcl2) || (lcn << vi->z_logical_clusterbits) >= vi->i_size) m->compressedblks = 1; if (m->compressedblks) goto out; err = z_erofs_load_lcluster_from_disk(m, lcn, false); if (err) return err; /* * If the 1st NONHEAD lcluster has already been handled initially w/o * valid compressedblks, which means at least it mustn't be CBLKCNT, or * an internal implemenatation error is detected. * * The following code can also handle it properly anyway, but let's * BUG_ON in the debugging mode only for developers to notice that. */ DBG_BUGON(lcn == initial_lcn && m->type == Z_EROFS_LCLUSTER_TYPE_NONHEAD); switch (m->type) { case Z_EROFS_LCLUSTER_TYPE_PLAIN: case Z_EROFS_LCLUSTER_TYPE_HEAD1: case Z_EROFS_LCLUSTER_TYPE_HEAD2: /* * if the 1st NONHEAD lcluster is actually PLAIN or HEAD type * rather than CBLKCNT, it's a 1 block-sized pcluster. */ m->compressedblks = 1; break; case Z_EROFS_LCLUSTER_TYPE_NONHEAD: if (m->delta[0] != 1) goto err_bonus_cblkcnt; if (m->compressedblks) break; /* fallthrough */ default: erofs_err("cannot found CBLKCNT @ lcn %lu of nid %llu", lcn, vi->nid | 0ULL); DBG_BUGON(1); return -EFSCORRUPTED; } out: m->map->m_plen = erofs_pos(sbi, m->compressedblks); return 0; err_bonus_cblkcnt: erofs_err("bogus CBLKCNT @ lcn %lu of nid %llu", lcn, vi->nid | 0ULL); DBG_BUGON(1); return -EFSCORRUPTED; } static int z_erofs_get_extent_decompressedlen(struct z_erofs_maprecorder *m) { struct erofs_inode *const vi = m->inode; struct erofs_map_blocks *map = m->map; unsigned int lclusterbits = vi->z_logical_clusterbits; u64 lcn = m->lcn, headlcn = map->m_la >> lclusterbits; int err; while (1) { /* handle the last EOF pcluster (no next HEAD lcluster) */ if ((lcn << lclusterbits) >= vi->i_size) { map->m_llen = vi->i_size - map->m_la; return 0; } err = z_erofs_load_lcluster_from_disk(m, lcn, true); if (err) return err; if (m->type == Z_EROFS_LCLUSTER_TYPE_NONHEAD) { /* work around invalid d1 generated by pre-1.0 mkfs */ if (__erofs_unlikely(!m->delta[1])) { m->delta[1] = 1; DBG_BUGON(1); } } else if (m->type == Z_EROFS_LCLUSTER_TYPE_PLAIN || m->type == Z_EROFS_LCLUSTER_TYPE_HEAD1 || m->type == Z_EROFS_LCLUSTER_TYPE_HEAD2) { if (lcn != headlcn) break; /* ends at the next HEAD lcluster */ m->delta[1] = 1; } else { erofs_err("unknown type %u @ lcn %llu of nid %llu", m->type, lcn | 0ULL, (unsigned long long)vi->nid); DBG_BUGON(1); return -EOPNOTSUPP; } lcn += m->delta[1]; } map->m_llen = (lcn << lclusterbits) + m->clusterofs - map->m_la; return 0; } static int z_erofs_do_map_blocks(struct erofs_inode *vi, struct erofs_map_blocks *map, int flags) { struct erofs_sb_info *sbi = vi->sbi; bool ztailpacking = vi->z_advise & Z_EROFS_ADVISE_INLINE_PCLUSTER; bool fragment = vi->z_advise & Z_EROFS_ADVISE_FRAGMENT_PCLUSTER; struct z_erofs_maprecorder m = { .inode = vi, .map = map, .kaddr = map->mpage, }; int err = 0; unsigned int lclusterbits, endoff, afmt; unsigned long initial_lcn; unsigned long long ofs, end; lclusterbits = vi->z_logical_clusterbits; ofs = flags & EROFS_GET_BLOCKS_FINDTAIL ? vi->i_size - 1 : map->m_la; initial_lcn = ofs >> lclusterbits; endoff = ofs & ((1 << lclusterbits) - 1); err = z_erofs_load_lcluster_from_disk(&m, initial_lcn, false); if (err) goto out; if (ztailpacking && (flags & EROFS_GET_BLOCKS_FINDTAIL)) vi->z_idataoff = m.nextpackoff; map->m_flags = EROFS_MAP_MAPPED | EROFS_MAP_ENCODED; end = (m.lcn + 1ULL) << lclusterbits; switch (m.type) { case Z_EROFS_LCLUSTER_TYPE_PLAIN: case Z_EROFS_LCLUSTER_TYPE_HEAD1: case Z_EROFS_LCLUSTER_TYPE_HEAD2: if (endoff >= m.clusterofs) { m.headtype = m.type; map->m_la = (m.lcn << lclusterbits) | m.clusterofs; /* * For ztailpacking files, in order to inline data more * effectively, special EOF lclusters are now supported * which can have three parts at most. */ if (ztailpacking && end > vi->i_size) end = vi->i_size; break; } /* m.lcn should be >= 1 if endoff < m.clusterofs */ if (!m.lcn) { erofs_err("invalid logical cluster 0 at nid %llu", (unsigned long long)vi->nid); err = -EFSCORRUPTED; goto out; } end = (m.lcn << lclusterbits) | m.clusterofs; map->m_flags |= EROFS_MAP_FULL_MAPPED; m.delta[0] = 1; /* fallthrough */ case Z_EROFS_LCLUSTER_TYPE_NONHEAD: /* get the corresponding first chunk */ err = z_erofs_extent_lookback(&m, m.delta[0]); if (err) goto out; break; default: erofs_err("unknown type %u @ offset %llu of nid %llu", m.type, ofs, (unsigned long long)vi->nid); err = -EOPNOTSUPP; goto out; } if (m.partialref) map->m_flags |= EROFS_MAP_PARTIAL_REF; map->m_llen = end - map->m_la; if (flags & EROFS_GET_BLOCKS_FINDTAIL) { vi->z_tailextent_headlcn = m.lcn; /* for non-compact indexes, fragmentoff is 64 bits */ if (fragment && vi->datalayout == EROFS_INODE_COMPRESSED_FULL) vi->fragmentoff |= (u64)m.pblk << 32; } if (ztailpacking && m.lcn == vi->z_tailextent_headlcn) { map->m_flags |= EROFS_MAP_META; map->m_pa = vi->z_idataoff; map->m_plen = vi->z_idata_size; } else if (fragment && m.lcn == vi->z_tailextent_headlcn) { map->m_flags |= EROFS_MAP_FRAGMENT; } else { map->m_pa = erofs_pos(sbi, m.pblk); err = z_erofs_get_extent_compressedlen(&m, initial_lcn); if (err) goto out; } if (m.headtype == Z_EROFS_LCLUSTER_TYPE_PLAIN) { if (map->m_llen > map->m_plen) { DBG_BUGON(1); err = -EFSCORRUPTED; goto out; } afmt = vi->z_advise & Z_EROFS_ADVISE_INTERLACED_PCLUSTER ? Z_EROFS_COMPRESSION_INTERLACED : Z_EROFS_COMPRESSION_SHIFTED; } else { afmt = m.headtype == Z_EROFS_LCLUSTER_TYPE_HEAD2 ? vi->z_algorithmtype[1] : vi->z_algorithmtype[0]; if (!(sbi->available_compr_algs & (1 << afmt))) { erofs_err("inconsistent algorithmtype %u for nid %llu", afmt, vi->nid); err = -EFSCORRUPTED; goto out; } } map->m_algorithmformat = afmt; if (flags & EROFS_GET_BLOCKS_FIEMAP) { err = z_erofs_get_extent_decompressedlen(&m); if (!err) map->m_flags |= EROFS_MAP_FULL_MAPPED; } out: erofs_dbg("m_la %" PRIu64 " m_pa %" PRIu64 " m_llen %" PRIu64 " m_plen %" PRIu64 " m_flags 0%o", map->m_la, map->m_pa, map->m_llen, map->m_plen, map->m_flags); return err; } static int z_erofs_fill_inode_lazy(struct erofs_inode *vi) { erofs_off_t pos; struct z_erofs_map_header *h; char buf[sizeof(struct z_erofs_map_header)]; struct erofs_sb_info *sbi = vi->sbi; int err, headnr; if (erofs_atomic_read(&vi->flags) & EROFS_I_Z_INITED) return 0; pos = round_up(erofs_iloc(vi) + vi->inode_isize + vi->xattr_isize, 8); err = erofs_dev_read(sbi, 0, buf, pos, sizeof(buf)); if (err < 0) return -EIO; h = (struct z_erofs_map_header *)buf; /* * if the highest bit of the 8-byte map header is set, the whole file * is stored in the packed inode. The rest bits keeps z_fragmentoff. */ if (h->h_clusterbits >> Z_EROFS_FRAGMENT_INODE_BIT) { vi->z_advise = Z_EROFS_ADVISE_FRAGMENT_PCLUSTER; vi->fragmentoff = le64_to_cpu(*(__le64 *)h) ^ (1ULL << 63); vi->z_tailextent_headlcn = 0; goto out; } vi->z_advise = le16_to_cpu(h->h_advise); vi->z_algorithmtype[0] = h->h_algorithmtype & 15; vi->z_algorithmtype[1] = h->h_algorithmtype >> 4; headnr = 0; if (vi->z_algorithmtype[0] >= Z_EROFS_COMPRESSION_MAX || vi->z_algorithmtype[++headnr] >= Z_EROFS_COMPRESSION_MAX) { erofs_err("unknown HEAD%u format %u for nid %llu", headnr + 1, vi->z_algorithmtype[0], vi->nid | 0ULL); return -EOPNOTSUPP; } vi->z_logical_clusterbits = sbi->blkszbits + (h->h_clusterbits & 7); if (vi->datalayout == EROFS_INODE_COMPRESSED_COMPACT && !(vi->z_advise & Z_EROFS_ADVISE_BIG_PCLUSTER_1) ^ !(vi->z_advise & Z_EROFS_ADVISE_BIG_PCLUSTER_2)) { erofs_err("big pcluster head1/2 of compact indexes should be consistent for nid %llu", vi->nid * 1ULL); return -EFSCORRUPTED; } if (vi->z_advise & Z_EROFS_ADVISE_INLINE_PCLUSTER) { struct erofs_map_blocks map = { .index = UINT_MAX }; vi->idata_size = le16_to_cpu(h->h_idata_size); err = z_erofs_do_map_blocks(vi, &map, EROFS_GET_BLOCKS_FINDTAIL); if (!map.m_plen || erofs_blkoff(sbi, map.m_pa) + map.m_plen > erofs_blksiz(sbi)) { erofs_err("invalid tail-packing pclustersize %llu", map.m_plen | 0ULL); return -EFSCORRUPTED; } if (err < 0) return err; } if (vi->z_advise & Z_EROFS_ADVISE_FRAGMENT_PCLUSTER && !(h->h_clusterbits >> Z_EROFS_FRAGMENT_INODE_BIT)) { struct erofs_map_blocks map = { .index = UINT_MAX }; vi->fragmentoff = le32_to_cpu(h->h_fragmentoff); err = z_erofs_do_map_blocks(vi, &map, EROFS_GET_BLOCKS_FINDTAIL); if (err < 0) return err; } out: erofs_atomic_set_bit(EROFS_I_Z_INITED_BIT, &vi->flags); return 0; } int z_erofs_map_blocks_iter(struct erofs_inode *vi, struct erofs_map_blocks *map, int flags) { int err = 0; if (map->m_la >= vi->i_size) { /* post-EOF unmapped extent */ map->m_llen = map->m_la + 1 - vi->i_size; map->m_la = vi->i_size; map->m_flags = 0; } else { err = z_erofs_fill_inode_lazy(vi); if (!err) { if ((vi->z_advise & Z_EROFS_ADVISE_FRAGMENT_PCLUSTER) && !vi->z_tailextent_headlcn) { map->m_la = 0; map->m_llen = vi->i_size; map->m_flags = EROFS_MAP_MAPPED | EROFS_MAP_FULL_MAPPED | EROFS_MAP_FRAGMENT; } else { err = z_erofs_do_map_blocks(vi, map, flags); } } if (!err && (map->m_flags & EROFS_MAP_ENCODED) && __erofs_unlikely(map->m_plen > Z_EROFS_PCLUSTER_MAX_SIZE || map->m_llen > Z_EROFS_PCLUSTER_MAX_DSIZE)) err = -EOPNOTSUPP; if (err) map->m_llen = 0; } return err; } erofs-utils-1.8.6/man/000077500000000000000000000000001477440110200145555ustar00rootroot00000000000000erofs-utils-1.8.6/man/Makefile.am000066400000000000000000000002421477440110200166070ustar00rootroot00000000000000# SPDX-License-Identifier: GPL-2.0+ dist_man_MANS = mkfs.erofs.1 dump.erofs.1 fsck.erofs.1 EXTRA_DIST = erofsfuse.1 if ENABLE_FUSE man_MANS = erofsfuse.1 endif erofs-utils-1.8.6/man/dump.erofs.1000066400000000000000000000035251477440110200167260ustar00rootroot00000000000000.\" Copyright (c) 2021 Guo Xuenan .\" .TH DUMP.EROFS 1 .SH NAME dump.erofs \- retrieve directory and file entries, show specific file or overall disk statistics information from an EROFS-formatted image. .SH SYNOPSIS \fBdump.erofs\fR [\fIOPTIONS\fR] \fIIMAGE\fR .SH DESCRIPTION .B dump.erofs is used to retrieve erofs metadata and data from \fIIMAGE\fP and demonstrate .br 1) overall disk statistics, .br 2) superblock information, .br 3) file information of the given inode NID, .br 4) file extent information of the given inode NID, .br 5) file content for the given inode NID. .SH OPTIONS .TP .BI "\-\-device=" path Specify an extra device to be used together. You may give multiple .B --device options in the correct order. .TP .BI "\-\-ls" List directory contents. .I NID or .I path required. .TP .BI "\-\-cat" Write file content to standard output. .I NID or .I path required. .TP .BI "\-\-nid=" NID Specify an inode NID in order to print its file information. .TP .BI "\-\-path=" path Specify an inode path in order to print its file information. .TP .BI \-e Show the file extent information. .I NID or .I path required. .TP \fB\-V\fR, \fB\-\-version\fR Print the version number and exit. .TP \fB\-h\fR, \fB\-\-help\fR Display help string and exit. .TP .BI \-s Show superblock information. This is the default if no options are specified. .TP .BI \-S Show image statistics, including file type/size distribution, number of (un)compressed files, compression ratio, etc. .SH AUTHOR Initial code was written by Wang Qi , Guo Xuenan . .PP This manual page was written by Guo Xuenan .SH AVAILABILITY .B dump.erofs is part of erofs-utils package and is available from git://git.kernel.org/pub/scm/linux/kernel/git/xiang/erofs-utils.git. .SH SEE ALSO .BR mkfs.erofs(1), .BR fsck.erofs(1) erofs-utils-1.8.6/man/erofsfuse.1000066400000000000000000000024301477440110200166370ustar00rootroot00000000000000.\" Copyright (c) 2021 Gao Xiang .\" .TH EROFSFUSE 1 .SH NAME erofsfuse \- FUSE file system client for erofs file system .SH SYNOPSIS \fBerofsfuse\fR [\fIOPTIONS\fR] \fIDEVICE\fR \fIMOUNTPOINT\fR .SH DESCRIPTION .B erofsfuse is a FUSE file system client that supports reading from devices or image files containing erofs file system. .SH OPTIONS .SS "general options:" .TP \fB\-o\fR opt,[opt...] mount options .TP \fB\-h\fR \fB\-\-help\fR display help and exit .SS "erofsfuse options:" .TP .BI "\-\-dbglevel=" # Specify the level of debugging messages. The default is 2, which shows basic warning messages. .TP .BI "\-\-device=" path Specify an extra device to be used together. You may give multiple `--device' options in the correct order. .TP .BI "\-\-offset=" # Specify `--offset' bytes to skip when reading image file. The default is 0. .SS "FUSE options:" .TP \fB-d -o\fR debug enable debug output (implies -f) .TP \fB-f\fR foreground operation .TP \fB-s\fR disable multi-threaded operation .P For other FUSE options please see .BR mount.fuse (8) or see the output of .I erofsfuse \-\-help .SH AVAILABILITY \fBerofsfuse\fR is part of erofs-utils package and is available from git://git.kernel.org/pub/scm/linux/kernel/git/xiang/erofs-utils.git. .SH SEE ALSO .BR mount.fuse (8) erofs-utils-1.8.6/man/fsck.erofs.1000066400000000000000000000031541477440110200167050ustar00rootroot00000000000000.\" Copyright (c) 2021 Daeho Jeong .\" .TH FSCK.EROFS 1 .SH NAME fsck.erofs \- tool to check an EROFS filesystem's integrity .SH SYNOPSIS \fBfsck.erofs\fR [\fIOPTIONS\fR] \fIIMAGE\fR .SH DESCRIPTION fsck.erofs is used to scan an EROFS filesystem \fIIMAGE\fR and check the integrity of it. .SH OPTIONS .TP \fB\-V\fR, \fB\-\-version\fR Print the version number of fsck.erofs and exit. .TP .BI "\-d " # Specify the level of debugging messages. The default is 2, which shows basic warning messages. .TP .B \-p Print total compression ratio of all files including compressed and non-compressed files. .TP .BI "\-\-device=" path Specify an extra blob device to be used together. You may give multiple .B --device options in the correct order. .TP .BI "\-\-extract" "[=directory]" Test to extract the whole file system. It scans all inode data, including compressed inode data, which leads to more I/O and CPU load, so it might take a long time depending on the image size. Optionally extract contents of the \fIIMAGE\fR to \fIdirectory\fR. .TP .BI "--[no-]xattrs" Whether to dump extended attributes during extraction (default off). .TP \fB\-h\fR, \fB\-\-help\fR Display help string and exit. .TP \fB\-a\fR, \fB\-A\fR, \fB-y\fR These options do nothing at all; they are provided only for compatibility with the fsck programs of other filesystems. .SH AUTHOR This version of \fBfsck.erofs\fR is written by Daeho Jeong . .SH AVAILABILITY \fBfsck.erofs\fR is part of erofs-utils package and is available from git://git.kernel.org/pub/scm/linux/kernel/git/xiang/erofs-utils.git. .SH SEE ALSO .BR fsck (8). erofs-utils-1.8.6/man/mkfs.erofs.1000066400000000000000000000227221477440110200167210ustar00rootroot00000000000000.\" Copyright (c) 2019 Gao Xiang .\" .TH MKFS.EROFS 1 .SH NAME mkfs.erofs \- tool to create an EROFS filesystem .SH SYNOPSIS \fBmkfs.erofs\fR [\fIOPTIONS\fR] \fIDESTINATION\fR \fISOURCE\fR .SH DESCRIPTION EROFS is a new enhanced lightweight linux read-only filesystem with modern designs (eg. no buffer head, reduced metadata, inline xattrs/data, etc.) for scenarios which need high-performance read-only requirements, e.g. Android OS for smartphones and LIVECDs. .PP It also provides fixed-sized output compression support, which improves storage density, keeps relatively higher compression ratios, which is more useful to achieve high performance for embedded devices with limited memory since it has unnoticable memory overhead and page cache thrashing. .PP mkfs.erofs is used to create such EROFS filesystem \fIDESTINATION\fR image file from \fISOURCE\fR directory or tarball. .SH OPTIONS .TP .BI "\-z " compression-algorithm \fR[\fP, # \fR][\fP: ... \fR]\fP Set a primary algorithm for data compression, which can be set with an optional compression level. Alternative algorithms could be specified and separated by colons. See the output of .B mkfs.erofs \-\-help for a listing of the algorithms that \fBmkfs.erofs\fR is compiled with and what their respective level ranges are. .TP .BI "\-b " block-size Set the fundamental block size of the filesystem in bytes. In other words, specify the smallest amount of data that can be accessed at a time. The default is the system page size. It cannot be less than 512 bytes. .TP .BI "\-C " max-pcluster-size Specify the maximum size of compress physical cluster in bytes. This may cause the big pcluster feature to be enabled (Linux v5.13+). .TP .BI "\-d " # Specify the level of debugging messages. The default is 2, which shows basic warning messages. .TP .BI "\-x " # Limit how many xattrs will be inlined. The default is 2. Disables storing xattrs if < 0. .TP .BI "\-E " [^]extended-option \fR[\fP, ... \fR]\fP Set extended options for the filesystem. Extended options are comma separated, and may take an extra argument using the equals ('=') sign. To disable a feature, usually prefix the extended option name with a caret ('^') character. The following extended options are supported: .RS 1.2i .TP .BI all-fragments Forcely record the whole files into a special inode for better compression and it may take an argument as the pcluster size of the packed inode in bytes. (Linux v6.1+) .TP .BI dedupe Enable global compressed data deduplication to minimize duplicated data in the filesystem. May further reduce image size when used with .BR -E\ fragments . (Linux v6.1+) .TP .BI force-inode-compact Force generation of compact (32-byte) inodes. .TP .BI force-inode-extended Force generation of extended (64-byte) inodes. .TP .BI force-inode-blockmap Force generation of inode chunk format as a 4-byte block address array. .TP .BI force-chunk-indexes Forcely generate inode chunk format as an 8-byte chunk index (with device ID). .TP .BI [^]fragdedupe\fR[\fP= \fR]\fP Set the mode for fragment data deduplication. It's effective only when \fI-E(all)-fragments\fP is used together. If a caret ('^') character is set, fragment data deduplication is disabled. .RS 1.2i .TP .I inode Deduplicate fragment data only when the inode data is identical. This option will result in faster image generation with the current codebase .TP .I full Always deduplicate fragment data if possible .RE .TP .BI fragments\fR[\fP= size \fR]\fP Pack the tail part (pcluster) of compressed files, or entire files, into a special inode for smaller image sizes, and it may take an argument as the pcluster size of the packed inode in bytes. (Linux v6.1+) .TP .BI ^inline_data Don't inline regular files. It's typically useful to enable FSDAX (Linux 5.15+) for those images, however, there could be other use cases too. .TP .BI legacy-compress Disable "inplace decompression" and "compacted indexes", for compatibility with Linux pre-v5.4. .TP .B xattr-name-filter Enable a name filter for extended attributes to optimize negative lookups. (Linux v6.6+). .TP .BI ztailpacking Pack the tail part (pcluster) of compressed files into its metadata to save more space and the tail part I/O. (Linux v5.17+) .RE .TP .BI "\-L " volume-label Set the volume label for the filesystem to .IR volume-label . The maximum length of the volume label is 15 bytes. .TP .BI "\-T " # Specify a UNIX timestamp for image creation time for reproducible builds. If \fI--mkfs-time\fR is not specified, it will behave as \fI--all-time\fR: setting all files to the specified UNIX timestamp instead of using the modification times of the source files. .TP .BI "\-U " UUID Set the universally unique identifier (UUID) of the filesystem to .IR UUID . The format of the UUID is a series of hex digits separated by hyphens, like this: "c1b9d5a2-f162-11cf-9ece-0020afc76f16". The .I UUID parameter may also be one of the following: .RS 1.2i .TP .I clear clear the file system UUID .TP .I random generate a new randomly-generated UUID .RE .TP .B \-\-all-root Make all files owned by root. .TP .B \-\-all-time (used together with \fB-T\fR) set all files to the fixed timestamp. This is the default. .TP .BI "\-\-blobdev " file Specify an extra blob device to store chunk-based data. .TP .BI "\-\-chunksize " # Generate chunk-based files with #-byte chunks. .TP .BI "\-\-compress-hints " file Apply a per-file compression strategy. Each line in .I file is defined by tokens separated by spaces in the following form. Optionally, instead of the given primary algorithm, alternative algorithms can be specified with \fIalgorithm-index\fR explicitly: .RS 1.2i [algorithm-index] .RE .IR match-pattern s are extended regular expressions, matched against absolute paths within the output filesystem, with no leading /. .TP .BI "\-\-dsunit=" # Align all data block addresses to multiples of #. .TP .BI "\-\-exclude-path=" path Ignore file that matches the exact literal path. You may give multiple .B --exclude-path options. .TP .BI "\-\-exclude-regex=" regex Ignore files that match the given extended regular expression. You may give multiple .B --exclude-regex options. .TP .BI "\-\-file-contexts=" file Read SELinux label configuration/overrides from \fIfile\fR in the .BR selinux_file (5) format. .TP .BI "\-\-force-uid=" UID Set all file UIDs to \fIUID\fR. .TP .BI "\-\-force-gid=" GID Set all file GIDs to \fIGID\fR. .TP .BI "\-\-gid-offset=" GIDOFFSET Add \fIGIDOFFSET\fR to all file GIDs. When this option is used together with .BR --force-gid , the final file gids are set to \fIGID\fR + \fIGID-OFFSET\fR. .TP \fB\-V\fR, \fB\-\-version\fR Print the version number and exit. .TP \fB\-h\fR, \fB\-\-help\fR Display help string and exit. .TP .B "\-\-ignore-mtime" Ignore the file modification time whenever it would cause \fBmkfs.erofs\fR to use extended inodes over compact inodes. When not using a fixed timestamp, this can reduce total metadata size. Implied by .BR "-E force-inode-compact" . .TP .BI "\-\-max-extent-bytes " # Specify maximum decompressed extent size in bytes. .TP .B \-\-mkfs-time (used together with \fB-T\fR) the given timestamp is only applied to the build time. .TP .B "\-\-preserve-mtime" Use extended inodes instead of compact inodes if the file modification time would overflow compact inodes. This is the default. Overrides .BR --ignore-mtime . .TP .BI "\-\-sort=" MODE Inode data sorting order for tarballs as input. \fIMODE\fR may be one of \fBnone\fR or \fBpath\fR. \fBnone\fR: No particular data order is specified for the target image to avoid unnecessary overhead; Currently, it takes effect if `-E^inline_data` is specified and no compression is applied. \fBpath\fR: Data order strictly follows the tree generation order. (default) .TP .BI "\-\-tar, \-\-tar=" MODE Treat \fISOURCE\fR as a tarball or tarball-like "headerball" rather than as a directory. \fIMODE\fR may be one of \fBf\fR, \fBi\fR, or \fBheaderball\fR. \fBf\fR: Generate a full EROFS image from a regular tarball. (default) \fBi\fR: Generate a meta-only EROFS image from a regular tarball. Only metadata such as dentries, inodes, and xattrs will be added to the image, without file data. Uses for such images include as a layer in an overlay filesystem with other data-only layers. \fBheaderball\fR: Generate a meta-only EROFS image from a stream identical to a tarball except that file data is not present after each file header. It can improve performance especially when \fISOURCE\fR is not seekable. .TP .BI "\-\-uid-offset=" UIDOFFSET Add \fIUIDOFFSET\fR to all file UIDs. When this option is used together with .BR --force-uid , the final file uids are set to \fIUID\fR + \fIUIDOFFSET\fR. .TP .BI \-\-ungzip\fR[\fP= file \fR]\fP Filter tarball streams through gzip. Optionally, raw streams can be dumped together. .TP .BI \-\-unxz\fR[\fP= file \fR]\fP Filter tarball streams through xz, lzma, or lzip. Optionally, raw streams can be dumped together. .TP .BI "\-\-xattr-prefix=" PREFIX Specify a customized extended attribute namespace prefix for space saving, e.g. "trusted.overlay.". You may give multiple .B --xattr-prefix options (Linux v6.4+). .SH AUTHOR This version of \fBmkfs.erofs\fR is written by Li Guifu , Miao Xie and Gao Xiang with continuously improvements from others. .PP This manual page was written by Gao Xiang . .SH AVAILABILITY \fBmkfs.erofs\fR is part of erofs-utils package and is available from git://git.kernel.org/pub/scm/linux/kernel/git/xiang/erofs-utils.git. .SH SEE ALSO .BR mkfs (8). erofs-utils-1.8.6/mkfs/000077500000000000000000000000001477440110200147425ustar00rootroot00000000000000erofs-utils-1.8.6/mkfs/Makefile.am000066400000000000000000000006331477440110200170000ustar00rootroot00000000000000# SPDX-License-Identifier: GPL-2.0+ AUTOMAKE_OPTIONS = foreign bin_PROGRAMS = mkfs.erofs AM_CPPFLAGS = ${libselinux_CFLAGS} mkfs_erofs_SOURCES = main.c mkfs_erofs_CFLAGS = -Wall -I$(top_srcdir)/include mkfs_erofs_LDADD = $(top_builddir)/lib/liberofs.la ${libselinux_LIBS} \ ${libuuid_LIBS} ${liblz4_LIBS} ${liblzma_LIBS} ${zlib_LIBS} \ ${libdeflate_LIBS} ${libzstd_LIBS} ${libqpl_LIBS} ${libxxhash_LIBS} erofs-utils-1.8.6/mkfs/main.c000066400000000000000000001174361477440110200160460ustar00rootroot00000000000000// SPDX-License-Identifier: GPL-2.0+ /* * Copyright (C) 2018-2019 HUAWEI, Inc. * http://www.huawei.com/ * Created by Li Guifu */ #define _GNU_SOURCE #include #include #include #include #include #include #include #include #include "erofs/config.h" #include "erofs/print.h" #include "erofs/cache.h" #include "erofs/diskbuf.h" #include "erofs/inode.h" #include "erofs/tar.h" #include "erofs/compress.h" #include "erofs/dedupe.h" #include "erofs/xattr.h" #include "erofs/exclude.h" #include "erofs/block_list.h" #include "erofs/compress_hints.h" #include "erofs/blobchunk.h" #include "erofs/fragments.h" #include "erofs/rebuild.h" #include "../lib/liberofs_private.h" #include "../lib/liberofs_uuid.h" #include "../lib/compressor.h" static struct option long_options[] = { {"version", no_argument, 0, 'V'}, {"help", no_argument, 0, 'h'}, {"exclude-path", required_argument, NULL, 2}, {"exclude-regex", required_argument, NULL, 3}, #ifdef HAVE_LIBSELINUX {"file-contexts", required_argument, NULL, 4}, #endif {"force-uid", required_argument, NULL, 5}, {"force-gid", required_argument, NULL, 6}, {"all-root", no_argument, NULL, 7}, #ifndef NDEBUG {"random-pclusterblks", no_argument, NULL, 8}, {"random-algorithms", no_argument, NULL, 18}, #endif {"max-extent-bytes", required_argument, NULL, 9}, {"compress-hints", required_argument, NULL, 10}, {"chunksize", required_argument, NULL, 11}, {"quiet", no_argument, 0, 12}, {"blobdev", required_argument, NULL, 13}, {"ignore-mtime", no_argument, NULL, 14}, {"preserve-mtime", no_argument, NULL, 15}, {"uid-offset", required_argument, NULL, 16}, {"gid-offset", required_argument, NULL, 17}, {"tar", optional_argument, NULL, 20}, {"aufs", no_argument, NULL, 21}, {"mount-point", required_argument, NULL, 512}, {"xattr-prefix", required_argument, NULL, 19}, #ifdef WITH_ANDROID {"product-out", required_argument, NULL, 513}, {"fs-config-file", required_argument, NULL, 514}, {"block-list-file", required_argument, NULL, 515}, #endif {"ovlfs-strip", optional_argument, NULL, 516}, {"offset", required_argument, NULL, 517}, #ifdef HAVE_ZLIB {"gzip", no_argument, NULL, 518}, {"ungzip", optional_argument, NULL, 518}, #endif #ifdef HAVE_LIBLZMA {"unlzma", optional_argument, NULL, 519}, {"unxz", optional_argument, NULL, 519}, #endif #ifdef EROFS_MT_ENABLED {"workers", required_argument, NULL, 520}, #endif {"zfeature-bits", required_argument, NULL, 521}, {"clean", optional_argument, NULL, 522}, {"incremental", optional_argument, NULL, 523}, {"root-xattr-isize", required_argument, NULL, 524}, {"mkfs-time", no_argument, NULL, 525}, {"all-time", no_argument, NULL, 526}, {"sort", required_argument, NULL, 527}, {"hard-dereference", no_argument, NULL, 528}, {"dsunit", required_argument, NULL, 529}, {0, 0, 0, 0}, }; static void print_available_compressors(FILE *f, const char *delim) { int i = 0; bool comma = false; const struct erofs_algorithm *s; while ((s = z_erofs_list_available_compressors(&i)) != NULL) { if (comma) fputs(delim, f); fputs(s->name, f); comma = true; } fputc('\n', f); } static void usage(int argc, char **argv) { int i = 0; const struct erofs_algorithm *s; // " 1 2 3 4 5 6 7 8 " // "12345678901234567890123456789012345678901234567890123456789012345678901234567890\n" printf( "Usage: %s [OPTIONS] FILE SOURCE(s)\n" "Generate EROFS image (FILE) from SOURCE(s).\n" "\n" "General options:\n" " -V, --version print the version number of mkfs.erofs and exit\n" " -h, --help display this help and exit\n" "\n" " -b# set block size to # (# = page size by default)\n" " -d<0-9> set output verbosity; 0=quiet, 9=verbose (default=%i)\n" " -x# set xattr tolerance to # (< 0, disable xattrs; default 2)\n" " -zX[,level=Y] X=compressor (Y=compression level, Z=dictionary size, optional)\n" " [,dictsize=Z] alternative compressors can be separated by colons(:)\n" " [:...] supported compressors and their option ranges are:\n", argv[0], EROFS_WARN); while ((s = z_erofs_list_available_compressors(&i)) != NULL) { const char spaces[] = " "; printf("%s%s\n", spaces, s->name); if (s->c->setlevel) { if (!strcmp(s->name, "lzma")) /* A little kludge to show the range as disjointed * "0-9,100-109" instead of a continuous "0-109", and to * state what those two subranges respectively mean. */ printf("%s [,level=<0-9,100-109>]\t0-9=normal, 100-109=extreme (default=%i)\n", spaces, s->c->default_level); else printf("%s [,level=<0-%i>]\t\t(default=%i)\n", spaces, s->c->best_level, s->c->default_level); } if (s->c->setdictsize) { if (s->c->default_dictsize) printf("%s [,dictsize=]\t(default=%u, max=%u)\n", spaces, s->c->default_dictsize, s->c->max_dictsize); else printf("%s [,dictsize=]\t(default=, max=%u)\n", spaces, s->c->max_dictsize); } } printf( " -C# specify the size of compress physical cluster in bytes\n" " -EX[,...] X=extended options\n" " -L volume-label set the volume label (maximum 15 bytes)\n" " -T# specify a fixed UNIX timestamp # as build time\n" " --all-time the timestamp is also applied to all files (default)\n" " --mkfs-time the timestamp is applied as build time only\n" " -UX use a given filesystem UUID\n" " --all-root make all files owned by root\n" " --blobdev=X specify an extra device X to store chunked data\n" " --chunksize=# generate chunk-based files with #-byte chunks\n" " --clean=X run full clean build (default) or:\n" " --incremental=X run incremental build\n" " (X = data|rvsp; data=full data, rvsp=space is allocated\n" " and filled with zeroes)\n" " --compress-hints=X specify a file to configure per-file compression strategy\n" " --dsunit=# align all data block addresses to multiples of #\n" " --exclude-path=X avoid including file X (X = exact literal path)\n" " --exclude-regex=X avoid including files that match X (X = regular expression)\n" #ifdef HAVE_LIBSELINUX " --file-contexts=X specify a file contexts file to setup selinux labels\n" #endif " --force-uid=# set all file uids to # (# = UID)\n" " --force-gid=# set all file gids to # (# = GID)\n" " --uid-offset=# add offset # to all file uids (# = id offset)\n" " --gid-offset=# add offset # to all file gids (# = id offset)\n" " --hard-dereference dereference hardlinks, add links as separate inodes\n" " --ignore-mtime use build time instead of strict per-file modification time\n" " --max-extent-bytes=# set maximum decompressed extent size # in bytes\n" " --mount-point=X X=prefix of target fs path (default: /)\n" " --preserve-mtime keep per-file modification time strictly\n" " --offset=# skip # bytes at the beginning of IMAGE.\n" " --root-xattr-isize=# ensure the inline xattr size of the root directory is # bytes at least\n" " --aufs replace aufs special files with overlayfs metadata\n" " --sort= data sorting order for tarballs as input (default: path)\n" " --tar=X generate a full or index-only image from a tarball(-ish) source\n" " (X = f|i|headerball; f=full mode, i=index mode,\n" " headerball=file data is omited in the source stream)\n" " --ovlfs-strip=<0,1> strip overlayfs metadata in the target image (e.g. whiteouts)\n" " --quiet quiet execution (do not write anything to standard output.)\n" #ifndef NDEBUG " --random-pclusterblks randomize pclusterblks for big pcluster (debugging only)\n" " --random-algorithms randomize per-file algorithms (debugging only)\n" #endif #ifdef HAVE_ZLIB " --ungzip[=X] try to filter the tarball stream through gzip\n" " (and optionally dump the raw stream to X together)\n" #endif #ifdef HAVE_LIBLZMA " --unxz[=X] try to filter the tarball stream through xz/lzma/lzip\n" " (and optionally dump the raw stream to X together)\n" #endif #ifdef EROFS_MT_ENABLED " --workers=# set the number of worker threads to # (default: %u)\n" #endif " --xattr-prefix=X X=extra xattr name prefix\n" " --zfeature-bits=# toggle filesystem compression features according to given bits #\n" #ifdef WITH_ANDROID "\n" "Android-specific options:\n" " --product-out=X X=product_out directory\n" " --fs-config-file=X X=fs_config file\n" " --block-list-file=X X=block_list file\n" #endif #ifdef EROFS_MT_ENABLED , erofs_get_available_processors() /* --workers= */ #endif ); } static void version(void) { printf("mkfs.erofs (erofs-utils) %s\navailable compressors: ", cfg.c_version); print_available_compressors(stdout, ", "); } static unsigned int pclustersize_packed, pclustersize_max; static struct erofs_tarfile erofstar = { .global.xattrs = LIST_HEAD_INIT(erofstar.global.xattrs) }; static bool tar_mode, rebuild_mode, incremental_mode; enum { EROFS_MKFS_DATA_IMPORT_DEFAULT, EROFS_MKFS_DATA_IMPORT_FULLDATA, EROFS_MKFS_DATA_IMPORT_RVSP, EROFS_MKFS_DATA_IMPORT_SPARSE, } dataimport_mode; static unsigned int rebuild_src_count; static LIST_HEAD(rebuild_src_list); static u8 fixeduuid[16]; static bool valid_fixeduuid; static unsigned int dsunit; static int erofs_mkfs_feat_set_legacy_compress(bool en, const char *val, unsigned int vallen) { if (vallen) return -EINVAL; /* disable compacted indexes and 0padding */ cfg.c_legacy_compress = en; return 0; } static int erofs_mkfs_feat_set_ztailpacking(bool en, const char *val, unsigned int vallen) { if (vallen) return -EINVAL; cfg.c_ztailpacking = en; return 0; } static int erofs_mkfs_feat_set_fragments(bool en, const char *val, unsigned int vallen) { if (!en) { if (vallen) return -EINVAL; cfg.c_fragments = false; return 0; } if (vallen) { char *endptr; u64 i = strtoull(val, &endptr, 0); if (endptr - val != vallen) { erofs_err("invalid pcluster size %s for the packed file", val); return -EINVAL; } pclustersize_packed = i; } cfg.c_fragments = true; return 0; } static int erofs_mkfs_feat_set_all_fragments(bool en, const char *val, unsigned int vallen) { cfg.c_all_fragments = en; return erofs_mkfs_feat_set_fragments(en, val, vallen); } static int erofs_mkfs_feat_set_dedupe(bool en, const char *val, unsigned int vallen) { if (vallen) return -EINVAL; cfg.c_dedupe = en; return 0; } static int erofs_mkfs_feat_set_fragdedupe(bool en, const char *val, unsigned int vallen) { if (!en) { if (vallen) return -EINVAL; cfg.c_fragdedupe = FRAGDEDUPE_OFF; } else if (vallen == sizeof("inode") - 1 && !memcmp(val, "inode", vallen)) { cfg.c_fragdedupe = FRAGDEDUPE_INODE; } else { cfg.c_fragdedupe = FRAGDEDUPE_FULL; } return 0; } static struct { char *feat; int (*set)(bool en, const char *val, unsigned int len); } z_erofs_mkfs_features[] = { {"legacy-compress", erofs_mkfs_feat_set_legacy_compress}, {"ztailpacking", erofs_mkfs_feat_set_ztailpacking}, {"fragments", erofs_mkfs_feat_set_fragments}, {"all-fragments", erofs_mkfs_feat_set_all_fragments}, {"dedupe", erofs_mkfs_feat_set_dedupe}, {"fragdedupe", erofs_mkfs_feat_set_fragdedupe}, {NULL, NULL}, }; static int parse_extended_opts(const char *opts) { #define MATCH_EXTENTED_OPT(opt, token, keylen) \ (keylen == strlen(opt) && !memcmp(token, opt, keylen)) const char *token, *next, *tokenend, *value __maybe_unused; unsigned int keylen, vallen; value = NULL; for (token = opts; *token != '\0'; token = next) { bool clear = false; const char *p = strchr(token, ','); next = NULL; if (p) { next = p + 1; } else { p = token + strlen(token); next = p; } tokenend = memchr(token, '=', p - token); if (tokenend) { keylen = tokenend - token; vallen = p - tokenend - 1; if (!vallen) return -EINVAL; value = tokenend + 1; } else { keylen = p - token; vallen = 0; } if (token[0] == '^') { if (keylen < 2) return -EINVAL; ++token; --keylen; clear = true; } if (MATCH_EXTENTED_OPT("force-inode-compact", token, keylen)) { if (vallen) return -EINVAL; cfg.c_force_inodeversion = FORCE_INODE_COMPACT; cfg.c_ignore_mtime = true; } else if (MATCH_EXTENTED_OPT("force-inode-extended", token, keylen)) { if (vallen) return -EINVAL; cfg.c_force_inodeversion = FORCE_INODE_EXTENDED; } else if (MATCH_EXTENTED_OPT("nosbcrc", token, keylen)) { if (vallen) return -EINVAL; erofs_sb_clear_sb_chksum(&g_sbi); } else if (MATCH_EXTENTED_OPT("noinline_data", token, keylen)) { if (vallen) return -EINVAL; cfg.c_inline_data = false; } else if (MATCH_EXTENTED_OPT("inline_data", token, keylen)) { if (vallen) return -EINVAL; cfg.c_inline_data = !clear; } else if (MATCH_EXTENTED_OPT("force-inode-blockmap", token, keylen)) { if (vallen) return -EINVAL; cfg.c_force_chunkformat = FORCE_INODE_BLOCK_MAP; } else if (MATCH_EXTENTED_OPT("force-chunk-indexes", token, keylen)) { if (vallen) return -EINVAL; cfg.c_force_chunkformat = FORCE_INODE_CHUNK_INDEXES; } else if (MATCH_EXTENTED_OPT("xattr-name-filter", token, keylen)) { if (vallen) return -EINVAL; cfg.c_xattr_name_filter = !clear; } else { int i, err; for (i = 0; z_erofs_mkfs_features[i].feat; ++i) { if (!MATCH_EXTENTED_OPT(z_erofs_mkfs_features[i].feat, token, keylen)) continue; err = z_erofs_mkfs_features[i].set(!clear, value, vallen); if (err) return err; break; } if (!z_erofs_mkfs_features[i].feat) { erofs_err("unknown extended option %.*s", (int)(p - token), token); return -EINVAL; } } } return 0; } static int mkfs_apply_zfeature_bits(uintmax_t bits) { int i; for (i = 0; bits; ++i) { int err; if (!z_erofs_mkfs_features[i].feat) { erofs_err("unsupported zfeature bit %u", i); return -EINVAL; } err = z_erofs_mkfs_features[i].set(bits & 1, NULL, 0); if (err) { erofs_err("failed to apply zfeature %s", z_erofs_mkfs_features[i].feat); return err; } bits >>= 1; } return 0; } static void mkfs_parse_tar_cfg(char *cfg) { char *p; tar_mode = true; if (!cfg) return; p = strchr(cfg, ','); if (p) { *p = '\0'; if ((*++p) != '\0') erofstar.mapfile = strdup(p); } if (!strcmp(cfg, "headerball")) erofstar.headeronly_mode = true; if (erofstar.headeronly_mode || !strcmp(optarg, "i") || !strcmp(optarg, "0")) erofstar.index_mode = true; } static int mkfs_parse_one_compress_alg(char *alg, struct erofs_compr_opts *copts) { char *p, *q, *opt, *endptr; copts->level = -1; copts->dict_size = 0; p = strchr(alg, ','); if (p) { copts->alg = strndup(alg, p - alg); /* support old '-zlzma,9' form */ if (isdigit(*(p + 1))) { copts->level = strtol(p + 1, &endptr, 10); if (*endptr && *endptr != ',') { erofs_err("invalid compression level %s", p + 1); return -EINVAL; } return 0; } } else { copts->alg = strdup(alg); return 0; } opt = p + 1; while (opt) { q = strchr(opt, ','); if (q) *q = '\0'; if ((p = strstr(opt, "level="))) { p += strlen("level="); copts->level = strtol(p, &endptr, 10); if ((endptr == p) || (*endptr && *endptr != ',')) { erofs_err("invalid compression level %s", p); return -EINVAL; } } else if ((p = strstr(opt, "dictsize="))) { p += strlen("dictsize="); copts->dict_size = strtoul(p, &endptr, 10); if (*endptr == 'k' || *endptr == 'K') copts->dict_size <<= 10; else if (*endptr == 'm' || *endptr == 'M') copts->dict_size <<= 20; else if ((endptr == p) || (*endptr && *endptr != ',')) { erofs_err("invalid compression dictsize %s", p); return -EINVAL; } } else { erofs_err("invalid compression option %s", opt); return -EINVAL; } opt = q ? q + 1 : NULL; } return 0; } static int mkfs_parse_compress_algs(char *algs) { unsigned int i; char *s; int ret; for (s = strtok(algs, ":"), i = 0; s; s = strtok(NULL, ":"), ++i) { if (i >= EROFS_MAX_COMPR_CFGS - 1) { erofs_err("too many algorithm types"); return -EINVAL; } ret = mkfs_parse_one_compress_alg(s, &cfg.c_compr_opts[i]); if (ret) return ret; } return 0; } static void erofs_rebuild_cleanup(void) { struct erofs_sb_info *src, *n; list_for_each_entry_safe(src, n, &rebuild_src_list, list) { list_del(&src->list); erofs_put_super(src); erofs_dev_close(src); free(src); } rebuild_src_count = 0; } static int mkfs_parse_options_cfg(int argc, char *argv[]) { char *endptr; int opt, i, err; bool quiet = false; int tarerofs_decoder = 0; bool has_timestamp = false; while ((opt = getopt_long(argc, argv, "C:E:L:T:U:b:d:x:z:Vh", long_options, NULL)) != -1) { switch (opt) { case 'z': i = mkfs_parse_compress_algs(optarg); if (i) return i; break; case 'b': i = atoi(optarg); if (i < 512 || i > EROFS_MAX_BLOCK_SIZE) { erofs_err("invalid block size %s", optarg); return -EINVAL; } g_sbi.blkszbits = ilog2(i); break; case 'd': i = atoi(optarg); if (i < EROFS_MSG_MIN || i > EROFS_MSG_MAX) { erofs_err("invalid debug level %d", i); return -EINVAL; } cfg.c_dbg_lvl = i; break; case 'x': i = strtol(optarg, &endptr, 0); if (*endptr != '\0') { erofs_err("invalid xattr tolerance %s", optarg); return -EINVAL; } cfg.c_inline_xattr_tolerance = i; break; case 'E': opt = parse_extended_opts(optarg); if (opt) return opt; break; case 'L': if (optarg == NULL || strlen(optarg) > (sizeof(g_sbi.volume_name) - 1u)) { erofs_err("invalid volume label"); return -EINVAL; } strncpy(g_sbi.volume_name, optarg, sizeof(g_sbi.volume_name)); break; case 'T': cfg.c_unix_timestamp = strtoull(optarg, &endptr, 0); if (cfg.c_unix_timestamp == -1 || *endptr != '\0') { erofs_err("invalid UNIX timestamp %s", optarg); return -EINVAL; } has_timestamp = true; break; case 'U': if (!strcmp(optarg, "clear")) { memset(fixeduuid, 0, 16); } else if (!strcmp(optarg, "random")) { valid_fixeduuid = false; break; } else if (erofs_uuid_parse(optarg, fixeduuid)) { erofs_err("invalid UUID %s", optarg); return -EINVAL; } valid_fixeduuid = true; break; case 2: opt = erofs_parse_exclude_path(optarg, false); if (opt) { erofs_err("failed to parse exclude path: %s", erofs_strerror(opt)); return opt; } break; case 3: opt = erofs_parse_exclude_path(optarg, true); if (opt) { erofs_err("failed to parse exclude regex: %s", erofs_strerror(opt)); return opt; } break; case 4: opt = erofs_selabel_open(optarg); if (opt && opt != -EBUSY) return opt; break; case 5: cfg.c_uid = strtoul(optarg, &endptr, 0); if (cfg.c_uid == -1 || *endptr != '\0') { erofs_err("invalid uid %s", optarg); return -EINVAL; } break; case 6: cfg.c_gid = strtoul(optarg, &endptr, 0); if (cfg.c_gid == -1 || *endptr != '\0') { erofs_err("invalid gid %s", optarg); return -EINVAL; } break; case 7: cfg.c_uid = cfg.c_gid = 0; break; #ifndef NDEBUG case 8: cfg.c_random_pclusterblks = true; break; case 18: cfg.c_random_algorithms = true; break; #endif case 9: cfg.c_max_decompressed_extent_bytes = strtoul(optarg, &endptr, 0); if (*endptr != '\0') { erofs_err("invalid maximum uncompressed extent size %s", optarg); return -EINVAL; } break; case 10: cfg.c_compress_hints_file = optarg; break; case 512: cfg.mount_point = optarg; /* all trailing '/' should be deleted */ opt = strlen(cfg.mount_point); if (opt && optarg[opt - 1] == '/') optarg[opt - 1] = '\0'; break; #ifdef WITH_ANDROID case 513: cfg.target_out_path = optarg; break; case 514: cfg.fs_config_file = optarg; break; case 515: cfg.block_list_file = optarg; break; #endif case 'C': i = strtoull(optarg, &endptr, 0); if (*endptr != '\0') { erofs_err("invalid physical clustersize %s", optarg); return -EINVAL; } pclustersize_max = i; break; case 11: i = strtol(optarg, &endptr, 0); if (*endptr != '\0') { erofs_err("invalid chunksize %s", optarg); return -EINVAL; } cfg.c_chunkbits = ilog2(i); if ((1 << cfg.c_chunkbits) != i) { erofs_err("chunksize %s must be a power of two", optarg); return -EINVAL; } erofs_sb_set_chunked_file(&g_sbi); break; case 12: quiet = true; break; case 13: cfg.c_blobdev_path = optarg; break; case 14: cfg.c_ignore_mtime = true; break; case 15: cfg.c_ignore_mtime = false; break; case 16: errno = 0; cfg.c_uid_offset = strtoll(optarg, &endptr, 0); if (errno || *endptr != '\0') { erofs_err("invalid uid offset %s", optarg); return -EINVAL; } break; case 17: errno = 0; cfg.c_gid_offset = strtoll(optarg, &endptr, 0); if (errno || *endptr != '\0') { erofs_err("invalid gid offset %s", optarg); return -EINVAL; } break; case 19: errno = 0; opt = erofs_xattr_insert_name_prefix(optarg); if (opt) { erofs_err("failed to parse xattr name prefix: %s", erofs_strerror(opt)); return opt; } cfg.c_extra_ea_name_prefixes = true; break; case 20: mkfs_parse_tar_cfg(optarg); break; case 21: erofstar.aufs = true; break; case 516: if (!optarg || !strcmp(optarg, "1")) cfg.c_ovlfs_strip = true; else cfg.c_ovlfs_strip = false; break; case 517: g_sbi.bdev.offset = strtoull(optarg, &endptr, 0); if (*endptr != '\0') { erofs_err("invalid disk offset %s", optarg); return -EINVAL; } break; case 518: case 519: if (optarg) erofstar.dumpfile = strdup(optarg); tarerofs_decoder = EROFS_IOS_DECODER_GZIP + (opt - 518); break; #ifdef EROFS_MT_ENABLED case 520: { unsigned int processors; errno = 0; cfg.c_mt_workers = strtoul(optarg, &endptr, 0); if (errno || *endptr != '\0') { erofs_err("invalid worker number %s", optarg); return -EINVAL; } processors = erofs_get_available_processors(); if (cfg.c_mt_workers > processors) erofs_warn("%d workers exceed %d processors, potentially impacting performance.", cfg.c_mt_workers, processors); break; } #endif case 521: errno = 0; i = strtol(optarg, &endptr, 0); if (errno || *endptr != '\0') { erofs_err("invalid zfeature bits %s", optarg); return -EINVAL; } err = mkfs_apply_zfeature_bits(i); if (err) return err; break; case 522: case 523: if (!optarg || !strcmp(optarg, "data")) { dataimport_mode = EROFS_MKFS_DATA_IMPORT_FULLDATA; } else if (!strcmp(optarg, "rvsp")) { dataimport_mode = EROFS_MKFS_DATA_IMPORT_RVSP; } else { errno = 0; dataimport_mode = strtol(optarg, &endptr, 0); if (errno || *endptr != '\0') { erofs_err("invalid --%s=%s", opt == 523 ? "incremental" : "clean", optarg); return -EINVAL; } } incremental_mode = (opt == 523); break; case 524: cfg.c_root_xattr_isize = strtoull(optarg, &endptr, 0); if (*endptr != '\0') { erofs_err("invalid the minimum inline xattr size %s", optarg); return -EINVAL; } break; case 525: cfg.c_timeinherit = TIMESTAMP_NONE; break; case 526: cfg.c_timeinherit = TIMESTAMP_FIXED; break; case 527: if (!strcmp(optarg, "none")) erofstar.try_no_reorder = true; break; case 528: cfg.c_hard_dereference = true; break; case 529: dsunit = strtoul(optarg, &endptr, 0); if (*endptr != '\0') { erofs_err("invalid dsunit %s", optarg); return -EINVAL; } break; case 'V': version(); exit(0); case 'h': usage(argc, argv); exit(0); default: /* '?' */ return -EINVAL; } } if (cfg.c_blobdev_path && cfg.c_chunkbits < g_sbi.blkszbits) { erofs_err("--blobdev must be used together with --chunksize"); return -EINVAL; } /* TODO: can be implemented with (deviceslot) mapped_blkaddr */ if (cfg.c_blobdev_path && cfg.c_force_chunkformat == FORCE_INODE_BLOCK_MAP) { erofs_err("--blobdev cannot work with block map currently"); return -EINVAL; } if (optind >= argc) { erofs_err("missing argument: FILE"); return -EINVAL; } cfg.c_img_path = strdup(argv[optind++]); if (!cfg.c_img_path) return -ENOMEM; if (optind >= argc) { if (!tar_mode) { erofs_err("missing argument: SOURCE(s)"); return -EINVAL; } else { int dupfd; dupfd = dup(STDIN_FILENO); if (dupfd < 0) { erofs_err("failed to duplicate STDIN_FILENO: %s", strerror(errno)); return -errno; } err = erofs_iostream_open(&erofstar.ios, dupfd, tarerofs_decoder); if (err) return err; } } else { struct stat st; cfg.c_src_path = realpath(argv[optind++], NULL); if (!cfg.c_src_path) { erofs_err("failed to parse source directory: %s", erofs_strerror(-errno)); return -ENOENT; } if (tar_mode) { int fd = open(cfg.c_src_path, O_RDONLY); if (fd < 0) { erofs_err("failed to open file: %s", cfg.c_src_path); return -errno; } err = erofs_iostream_open(&erofstar.ios, fd, tarerofs_decoder); if (err) return err; if (erofstar.dumpfile) { fd = open(erofstar.dumpfile, O_WRONLY | O_CREAT | O_TRUNC, 0644); if (fd < 0) { erofs_err("failed to open dumpfile: %s", erofstar.dumpfile); return -errno; } erofstar.ios.dumpfd = fd; } } else { err = lstat(cfg.c_src_path, &st); if (err) return -errno; if (S_ISDIR(st.st_mode)) erofs_set_fs_root(cfg.c_src_path); else rebuild_mode = true; } if (rebuild_mode) { char *srcpath = cfg.c_src_path; struct erofs_sb_info *src; do { src = calloc(1, sizeof(struct erofs_sb_info)); if (!src) { erofs_rebuild_cleanup(); return -ENOMEM; } err = erofs_dev_open(src, srcpath, O_RDONLY); if (err) { free(src); erofs_rebuild_cleanup(); return err; } /* extra device index starts from 1 */ src->dev = ++rebuild_src_count; list_add(&src->list, &rebuild_src_list); } while (optind < argc && (srcpath = argv[optind++])); } else if (optind < argc) { erofs_err("unexpected argument: %s\n", argv[optind]); return -EINVAL; } } if (quiet) { cfg.c_dbg_lvl = EROFS_ERR; cfg.c_showprogress = false; } if (pclustersize_max) { if (pclustersize_max < erofs_blksiz(&g_sbi) || pclustersize_max % erofs_blksiz(&g_sbi)) { erofs_err("invalid physical clustersize %u", pclustersize_max); return -EINVAL; } cfg.c_mkfs_pclustersize_max = pclustersize_max; cfg.c_mkfs_pclustersize_def = cfg.c_mkfs_pclustersize_max; } if (cfg.c_chunkbits && cfg.c_chunkbits < g_sbi.blkszbits) { erofs_err("chunksize %u must be larger than block size", 1u << cfg.c_chunkbits); return -EINVAL; } if (pclustersize_packed) { if (pclustersize_packed < erofs_blksiz(&g_sbi) || pclustersize_packed % erofs_blksiz(&g_sbi)) { erofs_err("invalid pcluster size for the packed file %u", pclustersize_packed); return -EINVAL; } cfg.c_mkfs_pclustersize_packed = pclustersize_packed; } if (has_timestamp && cfg.c_timeinherit == TIMESTAMP_UNSPECIFIED) cfg.c_timeinherit = TIMESTAMP_FIXED; return 0; } static void erofs_mkfs_default_options(void) { cfg.c_showprogress = true; cfg.c_legacy_compress = false; cfg.c_inline_data = true; cfg.c_xattr_name_filter = true; #ifdef EROFS_MT_ENABLED cfg.c_mt_workers = erofs_get_available_processors(); cfg.c_mkfs_segment_size = 16ULL * 1024 * 1024; #endif g_sbi.blkszbits = ilog2(min_t(u32, getpagesize(), EROFS_MAX_BLOCK_SIZE)); cfg.c_mkfs_pclustersize_max = erofs_blksiz(&g_sbi); cfg.c_mkfs_pclustersize_def = cfg.c_mkfs_pclustersize_max; g_sbi.feature_incompat = EROFS_FEATURE_INCOMPAT_ZERO_PADDING; g_sbi.feature_compat = EROFS_FEATURE_COMPAT_SB_CHKSUM | EROFS_FEATURE_COMPAT_MTIME; } /* https://reproducible-builds.org/specs/source-date-epoch/ for more details */ int parse_source_date_epoch(void) { char *source_date_epoch; unsigned long long epoch = -1ULL; char *endptr; source_date_epoch = getenv("SOURCE_DATE_EPOCH"); if (!source_date_epoch) return 0; epoch = strtoull(source_date_epoch, &endptr, 10); if (epoch == -1ULL || *endptr != '\0') { erofs_err("environment variable $SOURCE_DATE_EPOCH %s is invalid", source_date_epoch); return -EINVAL; } if (cfg.c_force_inodeversion != FORCE_INODE_EXTENDED) erofs_info("SOURCE_DATE_EPOCH is set, forcely generate extended inodes instead"); cfg.c_force_inodeversion = FORCE_INODE_EXTENDED; cfg.c_unix_timestamp = epoch; cfg.c_timeinherit = TIMESTAMP_CLAMPING; return 0; } void erofs_show_progs(int argc, char *argv[]) { if (cfg.c_dbg_lvl >= EROFS_WARN) printf("%s %s\n", basename(argv[0]), cfg.c_version); } static int erofs_mkfs_rebuild_load_trees(struct erofs_inode *root) { struct erofs_sb_info *src; unsigned int extra_devices = 0; erofs_blk_t nblocks; int ret, idx; enum erofs_rebuild_datamode datamode; switch (dataimport_mode) { case EROFS_MKFS_DATA_IMPORT_DEFAULT: datamode = EROFS_REBUILD_DATA_BLOB_INDEX; break; case EROFS_MKFS_DATA_IMPORT_FULLDATA: datamode = EROFS_REBUILD_DATA_FULL; break; case EROFS_MKFS_DATA_IMPORT_RVSP: datamode = EROFS_REBUILD_DATA_RESVSP; break; default: return -EINVAL; } list_for_each_entry(src, &rebuild_src_list, list) { ret = erofs_rebuild_load_tree(root, src, datamode); if (ret) { erofs_err("failed to load %s", src->devname); return ret; } if (src->extra_devices > 1) { erofs_err("%s: unsupported number %u of extra devices", src->devname, src->extra_devices); return -EOPNOTSUPP; } extra_devices += src->extra_devices; } if (datamode != EROFS_REBUILD_DATA_BLOB_INDEX) return 0; /* Each blob has either no extra device or only one device for TarFS */ if (extra_devices && extra_devices != rebuild_src_count) { erofs_err("extra_devices(%u) is mismatched with source images(%u)", extra_devices, rebuild_src_count); return -EOPNOTSUPP; } ret = erofs_mkfs_init_devices(&g_sbi, rebuild_src_count); if (ret) return ret; list_for_each_entry(src, &rebuild_src_list, list) { u8 *tag = NULL; if (extra_devices) { nblocks = src->devs[0].blocks; tag = src->devs[0].tag; } else { nblocks = src->primarydevice_blocks; } DBG_BUGON(src->dev < 1); idx = src->dev - 1; g_sbi.devs[idx].blocks = nblocks; if (tag && *tag) memcpy(g_sbi.devs[idx].tag, tag, sizeof(g_sbi.devs[0].tag)); else /* convert UUID of the source image to a hex string */ sprintf((char *)g_sbi.devs[idx].tag, "%04x%04x%04x%04x%04x%04x%04x%04x", (src->uuid[0] << 8) | src->uuid[1], (src->uuid[2] << 8) | src->uuid[3], (src->uuid[4] << 8) | src->uuid[5], (src->uuid[6] << 8) | src->uuid[7], (src->uuid[8] << 8) | src->uuid[9], (src->uuid[10] << 8) | src->uuid[11], (src->uuid[12] << 8) | src->uuid[13], (src->uuid[14] << 8) | src->uuid[15]); } return 0; } static void erofs_mkfs_showsummaries(erofs_blk_t nblocks) { char uuid_str[37] = {}; char *incr = incremental_mode ? "new" : "total"; if (!(cfg.c_dbg_lvl > EROFS_ERR && cfg.c_showprogress)) return; erofs_uuid_unparse_lower(g_sbi.uuid, uuid_str); fprintf(stdout, "------\nFilesystem UUID: %s\n" "Filesystem total blocks: %u (of %u-byte blocks)\n" "Filesystem total inodes: %llu\n" "Filesystem %s metadata blocks: %u\n" "Filesystem %s deduplicated bytes (of source files): %llu\n", uuid_str, nblocks, 1U << g_sbi.blkszbits, g_sbi.inos | 0ULL, incr, erofs_total_metablocks(g_sbi.bmgr), incr, g_sbi.saved_by_deduplication | 0ULL); } int main(int argc, char **argv) { int err = 0; struct erofs_buffer_head *sb_bh; struct erofs_inode *root = NULL; erofs_blk_t nblocks = 0; struct timeval t; FILE *blklst = NULL; u32 crc; erofs_init_configure(); erofs_mkfs_default_options(); err = mkfs_parse_options_cfg(argc, argv); erofs_show_progs(argc, argv); if (err) { if (err == -EINVAL) fprintf(stderr, "Try '%s --help' for more information.\n", argv[0]); return 1; } err = parse_source_date_epoch(); if (err) { fprintf(stderr, "Try '%s --help' for more information.\n", argv[0]); return 1; } if (cfg.c_unix_timestamp != -1) { g_sbi.build_time = cfg.c_unix_timestamp; g_sbi.build_time_nsec = 0; } else if (!gettimeofday(&t, NULL)) { g_sbi.build_time = t.tv_sec; g_sbi.build_time_nsec = t.tv_usec; } err = erofs_dev_open(&g_sbi, cfg.c_img_path, O_RDWR | (incremental_mode ? 0 : O_TRUNC)); if (err) { fprintf(stderr, "Try '%s --help' for more information.\n", argv[0]); return 1; } #ifdef WITH_ANDROID if (cfg.fs_config_file && load_canned_fs_config(cfg.fs_config_file) < 0) { erofs_err("failed to load fs config %s", cfg.fs_config_file); return 1; } if (cfg.block_list_file) { blklst = fopen(cfg.block_list_file, "w"); if (!blklst || erofs_blocklist_open(blklst, false)) { erofs_err("failed to open %s", cfg.block_list_file); return 1; } } #endif erofs_show_config(); if (cfg.c_fragments || cfg.c_extra_ea_name_prefixes) { if (!cfg.c_mkfs_pclustersize_packed) cfg.c_mkfs_pclustersize_packed = cfg.c_mkfs_pclustersize_def; err = erofs_packedfile_init(&g_sbi, cfg.c_fragments); if (err) { erofs_err("failed to initialize packedfile: %s", strerror(-err)); return 1; } } #ifndef NDEBUG if (cfg.c_random_pclusterblks) srand(time(NULL)); #endif if (tar_mode) { if (dataimport_mode == EROFS_MKFS_DATA_IMPORT_RVSP) erofstar.rvsp_mode = true; erofstar.dev = rebuild_src_count + 1; if (erofstar.mapfile) { blklst = fopen(erofstar.mapfile, "w"); if (!blklst || erofs_blocklist_open(blklst, true)) { err = -errno; erofs_err("failed to open %s", erofstar.mapfile); goto exit; } } else if (erofstar.index_mode) { /* * If mapfile is unspecified for tarfs index mode, * 512-byte block size is enforced here. */ g_sbi.blkszbits = 9; } } if (rebuild_mode) { struct erofs_sb_info *src; erofs_warn("EXPERIMENTAL rebuild mode in use. Use at your own risk!"); src = list_first_entry(&rebuild_src_list, struct erofs_sb_info, list); if (!src) goto exit; err = erofs_read_superblock(src); if (err) { erofs_err("failed to read superblock of %s", src->devname); goto exit; } g_sbi.blkszbits = src->blkszbits; } if (!incremental_mode) { g_sbi.bmgr = erofs_buffer_init(&g_sbi, 0); if (!g_sbi.bmgr) { err = -ENOMEM; goto exit; } sb_bh = erofs_reserve_sb(g_sbi.bmgr); if (IS_ERR(sb_bh)) { err = PTR_ERR(sb_bh); goto exit; } } else { union { struct stat st; erofs_blk_t startblk; } u; erofs_warn("EXPERIMENTAL incremental build in use. Use at your own risk!"); err = erofs_read_superblock(&g_sbi); if (err) { erofs_err("failed to read superblock of %s", g_sbi.devname); goto exit; } err = erofs_io_fstat(&g_sbi.bdev, &u.st); if (!err && S_ISREG(u.st.st_mode)) u.startblk = DIV_ROUND_UP(u.st.st_size, erofs_blksiz(&g_sbi)); else u.startblk = g_sbi.primarydevice_blocks; g_sbi.bmgr = erofs_buffer_init(&g_sbi, u.startblk); if (!g_sbi.bmgr) { err = -ENOMEM; goto exit; } sb_bh = NULL; } g_sbi.bmgr->dsunit = dsunit; /* Use the user-defined UUID or generate one for clean builds */ if (valid_fixeduuid) memcpy(g_sbi.uuid, fixeduuid, sizeof(g_sbi.uuid)); else if (!incremental_mode) erofs_uuid_generate(g_sbi.uuid); if (tar_mode && !erofstar.index_mode) { err = erofs_diskbuf_init(1); if (err) { erofs_err("failed to initialize diskbuf: %s", strerror(-err)); goto exit; } } err = erofs_load_compress_hints(&g_sbi); if (err) { erofs_err("failed to load compress hints %s", cfg.c_compress_hints_file); goto exit; } err = z_erofs_compress_init(&g_sbi, sb_bh); if (err) { erofs_err("failed to initialize compressor: %s", erofs_strerror(err)); goto exit; } if (cfg.c_dedupe) { if (!cfg.c_compr_opts[0].alg) { erofs_err("Compression is not enabled. Turn on chunk-based data deduplication instead."); cfg.c_chunkbits = g_sbi.blkszbits; } else { err = z_erofs_dedupe_init(erofs_blksiz(&g_sbi)); if (err) { erofs_err("failed to initialize deduplication: %s", erofs_strerror(err)); goto exit; } } } if (cfg.c_fragments) { err = z_erofs_dedupe_ext_init(); if (err) { erofs_err("failed to initialize extent deduplication: %s", erofs_strerror(err)); goto exit; } } if (cfg.c_chunkbits) { err = erofs_blob_init(cfg.c_blobdev_path, 1 << cfg.c_chunkbits); if (err) return 1; } if (((erofstar.index_mode && !erofstar.headeronly_mode) && !erofstar.mapfile) || cfg.c_blobdev_path) { err = erofs_mkfs_init_devices(&g_sbi, 1); if (err) { erofs_err("failed to generate device table: %s", erofs_strerror(err)); goto exit; } } erofs_inode_manager_init(); if (tar_mode) { root = erofs_rebuild_make_root(&g_sbi); if (IS_ERR(root)) { err = PTR_ERR(root); goto exit; } while (!(err = tarerofs_parse_tar(root, &erofstar))); if (err < 0) goto exit; err = erofs_rebuild_dump_tree(root, incremental_mode); if (err < 0) goto exit; } else if (rebuild_mode) { root = erofs_rebuild_make_root(&g_sbi); if (IS_ERR(root)) { err = PTR_ERR(root); goto exit; } err = erofs_mkfs_rebuild_load_trees(root); if (err) goto exit; err = erofs_rebuild_dump_tree(root, incremental_mode); if (err) goto exit; } else { err = erofs_build_shared_xattrs_from_path(&g_sbi, cfg.c_src_path); if (err) { erofs_err("failed to build shared xattrs: %s", erofs_strerror(err)); goto exit; } if (cfg.c_extra_ea_name_prefixes) erofs_xattr_flush_name_prefixes(&g_sbi); root = erofs_mkfs_build_tree_from_path(&g_sbi, cfg.c_src_path); if (IS_ERR(root)) { err = PTR_ERR(root); root = NULL; goto exit; } } if (erofstar.index_mode && g_sbi.extra_devices && !erofstar.mapfile) g_sbi.devs[0].blocks = BLK_ROUND_UP(&g_sbi, erofstar.offset); if ((cfg.c_fragments || cfg.c_extra_ea_name_prefixes) && erofs_sb_has_fragments(&g_sbi)) { erofs_update_progressinfo("Handling packed data ..."); err = erofs_flush_packed_inode(&g_sbi); if (err) goto exit; } if (erofstar.index_mode || cfg.c_chunkbits || g_sbi.extra_devices) { err = erofs_mkfs_dump_blobs(&g_sbi); if (err) goto exit; } /* flush all buffers except for the superblock */ err = erofs_bflush(g_sbi.bmgr, NULL); if (err) goto exit; erofs_fixup_root_inode(root); erofs_iput(root); root = NULL; err = erofs_writesb(&g_sbi, sb_bh, &nblocks); if (err) goto exit; /* flush all remaining buffers */ err = erofs_bflush(g_sbi.bmgr, NULL); if (err) goto exit; err = erofs_dev_resize(&g_sbi, nblocks); if (!err && erofs_sb_has_sb_chksum(&g_sbi)) { err = erofs_enable_sb_chksum(&g_sbi, &crc); if (!err) erofs_info("superblock checksum 0x%08x written", crc); } exit: if (root) erofs_iput(root); z_erofs_compress_exit(); z_erofs_dedupe_exit(); z_erofs_dedupe_ext_exit(); blklst = erofs_blocklist_close(); if (blklst) fclose(blklst); erofs_dev_close(&g_sbi); erofs_cleanup_compress_hints(); erofs_cleanup_exclude_rules(); if (cfg.c_chunkbits) erofs_blob_exit(); erofs_packedfile_exit(&g_sbi); erofs_xattr_cleanup_name_prefixes(); erofs_rebuild_cleanup(); erofs_diskbuf_exit(); erofs_exit_configure(); if (tar_mode) { erofs_iostream_close(&erofstar.ios); if (erofstar.ios.dumpfd >= 0) close(erofstar.ios.dumpfd); } if (err) { erofs_err("\tCould not format the device : %s\n", erofs_strerror(err)); return 1; } erofs_update_progressinfo("Build completed.\n"); erofs_mkfs_showsummaries(nblocks); erofs_put_super(&g_sbi); return 0; } erofs-utils-1.8.6/scripts/000077500000000000000000000000001477440110200154715ustar00rootroot00000000000000erofs-utils-1.8.6/scripts/get-version-number000077500000000000000000000022421477440110200211470ustar00rootroot00000000000000#!/bin/sh # SPDX-License-Identifier: GPL-2.0 scm_version() { # Check for git and a git repo. if test -z "$(git rev-parse --show-cdup 2>/dev/null)" && head="$(git rev-parse --verify HEAD 2>/dev/null)"; then # If we are at a tagged commit, we ignore it. if [ -z "$(git describe --exact-match 2>/dev/null)" ]; then # Add -g and 8 hex chars. printf -- '-g%.8s' "$head" fi # Check for uncommitted changes. # This script must avoid any write attempt to the source tree, # which might be read-only. # You cannot use 'git describe --dirty' because it tries to # create .git/index.lock . # First, with git-status, but --no-optional-locks is only # supported in git >= 2.14, so fall back to git-diff-index if # it fails. Note that git-diff-index does not refresh the # index, so it may give misleading results. See # git-update-index(1), git-diff-index(1), and git-status(1). if { git --no-optional-locks status -uno --porcelain 2>/dev/null || git diff-index --name-only HEAD } | read dummy; then printf '%s' -dirty fi fi } if [ -n "$EROFS_UTILS_VERSION" ]; then echo "$EROFS_UTILS_VERSION" else echo $(head -n1 VERSION)$(scm_version) fi