pax_global_header00006660000000000000000000000064151516026000014506gustar00rootroot0000000000000052 comment=d3fbccba6e409c100843563b416c47cd82c87ca0 erofs-utils-1.9.1/000077500000000000000000000000001515160260000137725ustar00rootroot00000000000000erofs-utils-1.9.1/.github/000077500000000000000000000000001515160260000153325ustar00rootroot00000000000000erofs-utils-1.9.1/.github/ISSUE_TEMPLATE.txt000066400000000000000000000005601515160260000202570ustar00rootroot00000000000000Please **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.9.1/.github/PULL_REQUEST_TEMPLATE.txt000066400000000000000000000005601515160260000213530ustar00rootroot00000000000000Please **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.9.1/.gitignore000066400000000000000000000006261515160260000157660ustar00rootroot00000000000000.* *~ *.[ao] *.diff *.la *.lo *.mod.c *.orig *.patch *.rej *.so *.so.dbg *.tar.* # # We don't want to ignore the following even if they are dot-files # !.mailmap # # 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 /mount/mount.erofs /contrib/stress erofs-utils-1.9.1/.mailmap000066400000000000000000000010571515160260000154160ustar00rootroot00000000000000# # This list is used by git-shortlog to fix a few botched name translations # in the git archive, either because the author's full name was messed up # and/or not always written the same way, making contributions from the # same person appearing not to be so or badly displayed. Also allows for # old email addresses to map to new email addresses. # # For format details, see "man gitmailmap" or "MAPPING AUTHORS" in # "man git-shortlog" on older systems. # # Please keep this list dictionary sorted. # Chengyu Zhu erofs-utils-1.9.1/AUTHORS000066400000000000000000000005371515160260000150470ustar00rootroot00000000000000EROFS USERSPACE UTILITIES M: Li Guifu M: Gao Xiang M: Huang Jianan M: Chengyu Zhu M: Yifan Zhao R: Chao Yu R: Miao Xie R: Fang Wei S: Maintained L: linux-erofs@lists.ozlabs.org erofs-utils-1.9.1/COPYING000066400000000000000000000011111515160260000150170ustar00rootroot00000000000000erofs-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.9.1/ChangeLog000066400000000000000000000327421515160260000155540ustar00rootroot00000000000000erofs-utils 1.9.1 * A quick maintenance release includes the following fixes: - (erofsfuse) Synchronize kernel patches to fix encoded extents and shared xattrs in the metabox; - (mkfs.erofs) Do not set LZ4_0PADDING for plain filesystems; - (fsck.erofs) Support extracting subtrees (Inseob Kim); - (mount.erofs) Fix a flag-clearing bug that can cause mount failures (Yifan Zhao); - (mkfs.erofs) Fix a CPU spin when using `--tar=f` if stdin is closed (David Scott); - (mkfs.erofs) Fix crashes in the rebuild mode when the source images contains xattrs (lishixian); - Miscellaneous fixes. -- Gao Xiang Wed, 04 Mar 2026 00:00:00 +0800 erofs-utils 1.9 * This release includes the following updates: - Add 48-bit layout support for larger filesystems (EXPERIMENTAL); - Add metadata compression support (EXPERIMENTAL); - (mkfs.erofs) Enable image creation from S3 storage (Yifan Zhao); - (mkfs.erofs) Enable image creation from OCI registries (Chengyu Zhu); - Implement an external mount helper for mounting remote EROFS filesystems; - (mount.erofs) Add support for mounting EROFS-native layers in OCI registries (Chengyu Zhu); - (mkfs.erofs) Support directory compression via the `--zD` option; - (mkfs.erofs) Enable isolated metadata zones to concentrate inodes and directories using the `--MZ` option; - (mkfs.erofs) Support customized lc,lp,pb properties for LZMA; - (mkfs.erofs) Add the `--xattr-inode-digest` option to enable page cache sharing by generating per-inode fingerprints; - Miscellaneous fixes and improvements. -- Gao Xiang Wed, 18 Feb 2026 00:00:00 +0800 erofs-utils 1.8.10 * A quick maintenance release that includes the following fixes: - (mkfs.erofs) Fix extent-based deduplication which can cause data corruption if target images are larger than 4GiB; - (mkfs.erofs) Switch to on-heap temporary buffers for libzstd and libdeflate to replace on-stack VLAs; - (fsck.erofs/erofsfuse) Fix large compressed fragment handling, which could be generated by the `-Eall-fragments` option (though rare) and was rejected by mistake; - Miscellaneous minor fixes. -- Gao Xiang Tue, 15 Jul 2025 00:00:00 +0800 erofs-utils 1.8.9 * A quick maintenance release that includes the following fix: - (mkfs.erofs) Fix corrupted small fragments introduced in erofs-utils 1.8.8. -- Gao Xiang Thu, 26 Jun 2025 21:30:00 +0800 erofs-utils 1.8.8 (OBSOLETE) [ PLEASE CONSIDER USING 1.8.9 TO AVOID THAT STUPID FRAGMENT BUG. :-( ] * A quick maintenance release that includes the following fixes: - (mkfs.erofs, tarerofs) Fix AUFS whiteout handling; - (mkfs.erofs, tarerofs) Properly handle negative GNU mtime; - Fix superblock checksum for small fs block size filesystems; - (mkfs.erofs) Fix temporary memory leak from small fragments; - (fsck.erofs) Handle crafted Z_EROFS_COMPRESSION_INTERLACED extents. -- Gao Xiang Thu, 26 Jun 2025 00:00:00 +0800 erofs-utils 1.8.7 (OBSOLETE) [ PLEASE CONSIDER USING 1.8.9 TO AVOID A BUG IN TAREROFS AUFS MODE. ] * This release includes the following updates: - (mkfs.erofs) Speed up multi-threaded `-Efragments` even further; - (mkfs.erofs) Fix DEFLATE due to incorrect maximum Huffman length; - (mkfs.erofs) Support `--fsalignblks` to align filesystem sizes; - (mkfs.erofs) Support `--vmdk-desc` to generate VMDK for flattened block devices; - (mkfs.erofs) Fix image reproducibility of `-E(all-)fragments`; - Other minor fixes. -- Gao Xiang Sun, 22 Jun 2025 00:00:00 +0800 erofs-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.9.1/LICENSES/000077500000000000000000000000001515160260000151775ustar00rootroot00000000000000erofs-utils-1.9.1/LICENSES/Apache-2.0000066400000000000000000000234241515160260000166050ustar00rootroot00000000000000Valid-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.9.1/LICENSES/GPL-2.0000066400000000000000000000444441515160260000160530ustar00rootroot00000000000000Valid-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.9.1/LICENSES/MIT000066400000000000000000000025341515160260000155570ustar00rootroot00000000000000Valid-License-Identifier: MIT SPDX-URL: https://spdx.org/licenses/MIT.html Usage-Guide: To use the MIT License put the following SPDX tag/value pair into a comment according to the placement guidelines in the licensing rules documentation: SPDX-License-Identifier: MIT License-Text: MIT License Copyright (c) Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. erofs-utils-1.9.1/Makefile.am000066400000000000000000000002351515160260000160260ustar00rootroot00000000000000# SPDX-License-Identifier: GPL-2.0+ ACLOCAL_AMFLAGS = -I m4 SUBDIRS = man lib mkfs dump fsck if ENABLE_FUSE SUBDIRS += fuse endif SUBDIRS += mount contrib erofs-utils-1.9.1/README000066400000000000000000000244061515160260000146600ustar00rootroot00000000000000erofs-utils =========== Userspace tools for EROFS filesystem, currently including: mkfs.erofs filesystem formatter mount.erofs mount helper for EROFS 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, `-Ededupe` doesn't support multi-threading due to limited development resources. 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. mount.erofs ----------- mount.erofs is a mount helper for EROFS filesystem, which can be used to mount EROFS images with various backends including direct kernel mount, FUSE-based mount, and NBD for remote sources like OCI images. How to mount an EROFS image ~~~~~~~~~~~~~~~~~~~~~~~~~~~ To mount an EROFS image directly: $ mount.erofs foo.erofs /mnt To mount with FUSE backend: $ mount.erofs -t erofs.fuse foo.erofs /mnt To mount from OCI image with NBD backend: $ mount.erofs -t erofs.nbd -o oci.blob=sha256:... : mnt To unmount an EROFS filesystem: $ mount.erofs -u mnt For more details, see mount.erofs(8) manpage. 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 extraction with fsck.erofs is still single-threaded and will need optimization later. If you are interested, contributions are, 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.9.1/VERSION000066400000000000000000000000171515160260000150400ustar00rootroot000000000000001.9 2026-02-18 erofs-utils-1.9.1/autogen.sh000077500000000000000000000002761515160260000160000ustar00rootroot00000000000000#!/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.9.1/configure.ac000066400000000000000000000626371515160260000162760ustar00rootroot00000000000000# -*- 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 subdir-objects -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([--disable-multithreading], [disable multi-threading support @<:@default=yes@:>@]), [enable_multithreading="$enableval"], [enable_multithreading="yes"]) 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_WITH(libcurl, [AS_HELP_STRING([--with-libcurl], [Enable and build with libcurl support @<:@default=auto@:>@])]) AC_ARG_WITH(openssl, [AS_HELP_STRING([--with-openssl], [Enable and build with openssl support @<:@default=auto@:>@])]) AC_ARG_WITH(libxml2, [AS_HELP_STRING([--with-libxml2], [Enable and build with libxml2 support @<:@default=auto@:>@])]) AC_ARG_WITH(json_c, [AS_HELP_STRING([--with-json-c], [Enable and build with json-c support @<:@default=auto@:>@])]) AC_ARG_WITH(libnl3, [AS_HELP_STRING([--with-libnl3], [Enable and build with libnl3 support @<:@default=auto@:>@])]) AC_ARG_ENABLE(s3, [AS_HELP_STRING([--enable-s3], [enable s3 image generation support @<:@default=no@:>@])], [enable_s3="$enableval"], [enable_s3="no"]) AC_ARG_ENABLE(oci, AS_HELP_STRING([--enable-oci], [enable OCI registry based input support @<:@default=no@:>@]), [enable_oci="$enableval"],[enable_oci="no"]) 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 endian.h fcntl.h getopt.h inttypes.h linux/aufs_type.h linux/falloc.h linux/fs.h linux/loop.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/resource.h sys/sendfile.h sys/stat.h sys/statfs.h sys/sysmacros.h sys/time.h sys/uio.h sys/xattr.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 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 getrandom getrlimit gettimeofday lgetxattr llistxattr lsetxattr memset realpath pread64 pwrite64 pwritev 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])) dnl Ensure aarch64 supports at least 16K AS_CASE([$build_cpu], [aarch64*], [AS_IF([test "$erofs_cv_max_block_size" -lt 16384], [erofs_cv_max_block_size=16384])]) ], [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"]) ]) AC_CHECK_MEMBERS([struct fuse_file_info.cache_readdir, struct fuse_file_info.keep_cache], [], [], [[ #include ]]) 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]) ]) ]) ]) # Configure libcurl have_libcurl="no" AS_IF([test "x$with_libcurl" != "xno"], [ PKG_CHECK_MODULES([libcurl], [libcurl], [ # Paranoia: don't trust the result reported by pkgconfig before trying out saved_LIBS="$LIBS" saved_CPPFLAGS=${CPPFLAGS} CPPFLAGS="${libcurl_CFLAGS} ${CPPFLAGS}" LIBS="${libcurl_LIBS} $LIBS" AC_CHECK_HEADERS([curl/curl.h],[ AC_CHECK_LIB(curl, curl_easy_perform, [], [ AC_MSG_ERROR([libcurl doesn't work properly])]) AC_CHECK_DECL(curl_easy_perform, [have_libcurl="yes"], [AC_MSG_ERROR([libcurl doesn't work properly])], [[ #include ]]) ]) LIBS="${saved_LIBS}" CPPFLAGS="${saved_CPPFLAGS}"], [ AS_IF([test "x$with_libcurl" = "xyes"], [ AC_MSG_ERROR([Cannot find proper libcurl]) ]) ]) ]) # Configure json-c have_json_c="no" AS_IF([test "x$with_json_c" != "xno"], [ PKG_CHECK_MODULES([json_c], [json-c], [ saved_LIBS="$LIBS" saved_CPPFLAGS=${CPPFLAGS} CPPFLAGS="${json_c_CFLAGS} ${CPPFLAGS}" LIBS="${json_c_LIBS} $LIBS" AC_CHECK_HEADERS([json-c/json.h],[ AC_CHECK_DECL(json_tokener_parse, [have_json_c="yes"], [AC_MSG_ERROR([json-c doesn't work properly])], [[ #include ]]) ]) LIBS="${saved_LIBS}" CPPFLAGS="${saved_CPPFLAGS}" ], [ AS_IF([test "x$with_json_c" = "xyes"], [ AC_MSG_ERROR([Cannot find proper json-c]) ]) ]) ]) # Validate dependencies for OCI registry AS_IF([test "x$enable_oci" = "xyes"], [ AS_IF([test "x$have_libcurl" = "xyes" -a "x$have_json_c" = "xyes"], [ have_oci="yes" ], [ have_oci="no" AC_MSG_ERROR([OCI registry disabled: missing libcurl or json-c]) ]) ], [have_oci="no"]) # Configure openssl have_openssl="no" AS_IF([test "x$with_openssl" != "xno"], [ PKG_CHECK_MODULES([openssl], [openssl], [ # Paranoia: don't trust the result reported by pkgconfig before trying out saved_LIBS="$LIBS" saved_CPPFLAGS=${CPPFLAGS} CPPFLAGS="${openssl_CFLAGS} ${CPPFLAGS}" LIBS="${openssl_LIBS} $LIBS" AC_CHECK_HEADERS([openssl/hmac.h],[ AC_CHECK_LIB(ssl, EVP_sha1, [], [ AC_MSG_ERROR([openssl doesn't work properly])]) AC_CHECK_DECL(EVP_sha1, [have_openssl="yes"], [AC_MSG_ERROR([openssl doesn't work properly])], [[ #include ]]) ]) AC_CHECK_HEADERS([openssl/evp.h],[],[]) LIBS="${saved_LIBS}" CPPFLAGS="${saved_CPPFLAGS}"], [ AS_IF([test "x$with_openssl" = "xyes"], [ AC_MSG_ERROR([Cannot find proper openssl]) ]) ]) ]) # Configure libxml2 have_libxml2="no" AS_IF([test "x$with_libxml2" != "xno"], [ PKG_CHECK_MODULES([libxml2], [libxml-2.0], [ # Paranoia: don't trust the result reported by pkgconfig before trying out saved_LIBS="$LIBS" saved_CPPFLAGS=${CPPFLAGS} CPPFLAGS="${libxml2_CFLAGS} ${CPPFLAGS}" LIBS="${libxml2_LIBS} $LIBS" AC_CHECK_HEADERS([libxml/parser.h],[ AC_CHECK_LIB(xml2, xmlReadMemory, [], [ AC_MSG_ERROR([libxml2 doesn't work properly])]) AC_CHECK_DECL(xmlReadMemory, [have_libxml2="yes"], [AC_MSG_ERROR([libxml2 doesn't work properly])], [[ #include ]]) ]) LIBS="${saved_LIBS}" CPPFLAGS="${saved_CPPFLAGS}"], [ AS_IF([test "x$with_libxml2" = "xyes"], [ AC_MSG_ERROR([Cannot find proper libxml2]) ]) ]) ]) # Configure libnl3 have_libnl3="no" AS_IF([test "x$with_libnl3" != "xno"], [ PKG_CHECK_MODULES([libnl3], [libnl-genl-3.0 >= 3.1], [ # Paranoia: don't trust the result reported by pkgconfig before trying out saved_LIBS="$LIBS" saved_CPPFLAGS=${CPPFLAGS} CPPFLAGS="${libnl3_CFLAGS} ${CPPFLAGS}" LIBS="${libnl3_LIBS} $LIBS" AC_CHECK_HEADERS([netlink/genl/genl.h],[ AC_CHECK_LIB(nl-genl-3, genl_connect, [], [ AC_MSG_ERROR([libnl3 doesn't work properly])]) AC_CHECK_DECL(genl_connect, [have_libnl3="yes"], [AC_MSG_ERROR([libnl3 doesn't work properly])], [[ #include ]]) ]) LIBS="${saved_LIBS}" CPPFLAGS="${saved_CPPFLAGS}"], [ AS_IF([test "x$with_libnl3" = "xyes"], [ AC_MSG_ERROR([Cannot find proper libnl3]) ]) ]) ]) AS_IF([test "x$enable_s3" != "xno"], [ AS_IF( [test "x$have_libcurl" = "xyes" && \ test "x$have_openssl" = "xyes" && \ test "x$have_libxml2" = "xyes"], [have_s3="yes"], [have_s3="no" AC_MSG_ERROR([S3 disabled: missing libcurl, openssl, or libxml2])]) ], [have_s3="no"]) # 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_LIBCURL], [test "x${have_libcurl}" = "xyes"]) AM_CONDITIONAL([ENABLE_OPENSSL], [test "x${have_openssl}" = "xyes"]) AM_CONDITIONAL([ENABLE_LIBXML2], [test "x${have_libxml2}" = "xyes"]) AM_CONDITIONAL([ENABLE_S3], [test "x${have_s3}" = "xyes"]) AM_CONDITIONAL([ENABLE_STATIC_FUSE], [test "x${enable_static_fuse}" = "xyes"]) AM_CONDITIONAL([ENABLE_OCI], [test "x${have_oci}" = "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 if test "x$have_libcurl" = "xyes"; then AC_DEFINE([HAVE_LIBCURL], 1, [Define to 1 if libcurl is found]) AC_SUBST([libcurl_LIBS]) AC_SUBST([libcurl_CFLAGS]) fi if test "x$have_openssl" = "xyes"; then AC_DEFINE([HAVE_OPENSSL], 1, [Define to 1 if openssl is found]) AC_SUBST([openssl_LIBS]) AC_SUBST([openssl_CFLAGS]) fi if test "x$have_libxml2" = "xyes"; then AC_DEFINE([HAVE_LIBXML2], 1, [Define to 1 if libxml2 is found]) AC_SUBST([libxml2_LIBS]) AC_SUBST([libxml2_CFLAGS]) fi if test "x$have_s3" = "xyes"; then AC_DEFINE([S3EROFS_ENABLED], 1, [Define to 1 if s3 is enabled]) fi if test "x$have_oci" = "xyes"; then AC_DEFINE([OCIEROFS_ENABLED], 1, [Define to 1 if OCI registry is enabled]) 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 mount/Makefile contrib/Makefile]) AC_OUTPUT erofs-utils-1.9.1/contrib/000077500000000000000000000000001515160260000154325ustar00rootroot00000000000000erofs-utils-1.9.1/contrib/Makefile.am000066400000000000000000000003041515160260000174630ustar00rootroot00000000000000# 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.9.1/contrib/mkstress.sh000077500000000000000000000003161515160260000176440ustar00rootroot00000000000000#!/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.9.1/contrib/stress.c000066400000000000000000000436211515160260000171270ustar00rootroot00000000000000// SPDX-License-Identifier: GPL-2.0+ /* * stress test for EROFS filesystem * * Copyright (C) 2019-2025 Gao Xiang */ #define _FILE_OFFSET_BITS 64 #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 (readdir(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 = pread(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 = pread(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 = lseek(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: lseek %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 = pread(fe->fd, buf, chunksize, pos); if (nread <= 0) return -errno; if (nread < chunksize && nread != filesize - pos) return -ERANGE; if (fe->chkfd < 0) continue; nread2 = pread(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 = lseek(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: lseek %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 = lseek(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: lseek %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.9.1/docs/000077500000000000000000000000001515160260000147225ustar00rootroot00000000000000erofs-utils-1.9.1/docs/INSTALL.md000066400000000000000000000035531515160260000163600ustar00rootroot00000000000000This 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.9.1/docs/PERFORMANCE.md000066400000000000000000000223241515160260000170100ustar00rootroot00000000000000# 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.9.1/docs/compress-hints.example000066400000000000000000000005531515160260000212600ustar00rootroot00000000000000# 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.9.1/dump/000077500000000000000000000000001515160260000147375ustar00rootroot00000000000000erofs-utils-1.9.1/dump/Makefile.am000066400000000000000000000004151515160260000167730ustar00rootroot00000000000000# 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 erofs-utils-1.9.1/dump/main.c000066400000000000000000000562211515160260000160350ustar00rootroot00000000000000// 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 "../lib/liberofs_compress.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; bool show_blkid_udev; 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}, {"blkid-udev", no_argument, NULL, 512}, {0, 0, 0, 0}, }; struct erofsdump_feature { bool compat; u16 lkver; u32 flag; const char *name; }; static struct erofsdump_feature feature_lists[] = { { true, 0, EROFS_FEATURE_COMPAT_SB_CHKSUM, "sb_csum" }, { true, 0, EROFS_FEATURE_COMPAT_MTIME, "mtime" }, { true, 0, EROFS_FEATURE_COMPAT_XATTR_FILTER, "xattr_filter" }, { true, 0, EROFS_FEATURE_COMPAT_SHARED_EA_IN_METABOX, "shared_ea_in_metabox" }, { true, 0, EROFS_FEATURE_COMPAT_PLAIN_XATTR_PFX, "plain_xattr_pfx" }, { true, 0, EROFS_FEATURE_COMPAT_ISHARE_XATTRS, "ishare_xattrs" }, { false, 504U, EROFS_FEATURE_INCOMPAT_LZ4_0PADDING, "lz4_0padding" }, { false, 513U, EROFS_FEATURE_INCOMPAT_COMPR_CFGS, "compr_cfgs" }, { false, 513U, EROFS_FEATURE_INCOMPAT_BIG_PCLUSTER, "big_pcluster" }, { false, 515U, EROFS_FEATURE_INCOMPAT_CHUNKED_FILE, "chunked_file" }, { false, 516U, EROFS_FEATURE_INCOMPAT_DEVICE_TABLE, "device_table" }, { false, 517U, EROFS_FEATURE_INCOMPAT_ZTAILPACKING, "ztailpacking" }, { false, 601U, EROFS_FEATURE_INCOMPAT_FRAGMENTS, "fragments" }, { false, 601U, EROFS_FEATURE_INCOMPAT_DEDUPE, "dedupe" }, { false, 604U, EROFS_FEATURE_INCOMPAT_XATTR_PREFIXES, "xattr_prefixes" }, { false, 615U, EROFS_FEATURE_INCOMPAT_48BIT, "48bit" }, { false, 617U, EROFS_FEATURE_INCOMPAT_METABOX, "metabox" }, }; 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" " --blkid-udev print block device attributes for easy import into the udev environment\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 = strtoull(optarg, &endptr, 0); if (*endptr != '\0') { erofs_err("invalid NID %s", optarg); return -EINVAL; } ++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; case 512: dumpcfg.show_blkid_udev = 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 tm *tm; struct erofs_map_blocks map = { .buf = __EROFS_BUF_INITIALIZER, .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'; } tm = localtime((time_t *)&inode.i_mtime); if (!tm) sprintf(timebuf, "%lld", (s64)inode.i_mtime | 0LL); else strftime(timebuf, sizeof(timebuf), "%Y-%m-%d %H:%M:%S", tm); 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.epoch + g_sbi.build_time; unsigned int minkver = 0; char uuid_str[37]; int i; 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_metabox(&g_sbi)) fprintf(stdout, "Filesystem metabox nid: %llu\n", g_sbi.metabox_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 compatible features: "); for (i = 0; i < ARRAY_SIZE(feature_lists); i++) { if (!feature_lists[i].compat) continue; if (le32_to_cpu(g_sbi.feature_compat) & feature_lists[i].flag) { fprintf(stdout, "%s ", feature_lists[i].name); if (feature_lists[i].lkver > minkver) minkver = feature_lists[i].lkver; } } fprintf(stdout, "\nFilesystem incompatible features: "); for (i = 0; i < ARRAY_SIZE(feature_lists); i++) { if (feature_lists[i].compat) continue; if (le32_to_cpu(g_sbi.feature_incompat) & feature_lists[i].flag) { fprintf(stdout, "%s ", feature_lists[i].name); if (feature_lists[i].lkver > minkver) minkver = feature_lists[i].lkver; } } erofs_uuid_unparse_lower(g_sbi.uuid, uuid_str); fprintf(stdout, "\nFilesystem UUID: %s", uuid_str); if ((g_sbi.available_compr_algs >> Z_EROFS_COMPRESSION_LZMA) & 1) minkver = max(minkver, 516U); if ((g_sbi.available_compr_algs >> Z_EROFS_COMPRESSION_DEFLATE) & 1) minkver = max(minkver, 606U); if ((g_sbi.available_compr_algs >> Z_EROFS_COMPRESSION_ZSTD) & 1) minkver = max(minkver, 610U); if (minkver) fprintf(stdout, "\nRequired upstream Linux kernel version: %u.%u\n", minkver / 100, minkver % 100); else fputs("\n", stdout); } 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; struct erofs_vfile vf; 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_iopen(&vf, &inode); if (err) 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(&vf, 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; } if (dumpcfg.show_blkid_udev) cfg.c_dbg_lvl = -1; 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_blkid_udev) { char uuid_str[37]; erofs_uuid_unparse_lower(g_sbi.uuid, uuid_str); printf("ID_FS_UUID=%s\nID_FS_UUID_ENC=%s\nID_FS_TYPE=erofs\nID_FS_USAGE=filesystem\n", uuid_str, uuid_str); goto exit_put_super; } 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.9.1/fsck/000077500000000000000000000000001515160260000147205ustar00rootroot00000000000000erofs-utils-1.9.1/fsck/Makefile.am000066400000000000000000000010231515160260000167500ustar00rootroot00000000000000# 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 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 endif erofs-utils-1.9.1/fsck/main.c000066400000000000000000000717731515160260000160270ustar00rootroot00000000000000// SPDX-License-Identifier: GPL-2.0+ /* * Copyright 2021 Google LLC * Author: Daeho Jeong */ #include #include #include #include #include #include #include "erofs/print.h" #include "erofs/decompress.h" #include "erofs/dir.h" #include "erofs/xattr.h" #include "../lib/compressor.h" #include "../lib/liberofs_compress.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; erofs_nid_t nid; const char *inode_path; bool nosbcrc; }; 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}, {"nid", required_argument, 0, 15}, {"path", required_argument, 0, 16}, {"no-sbcrc", no_argument, 0, 512}, {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" " --nid=# check or extract from the target inode of nid #\n" " --path=X check or extract from the target inode of path X\n" " --no-sbcrc bypass the superblock checksum verification\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; case 15: fsckcfg.nid = (erofs_nid_t)atoll(optarg); break; case 16: fsckcfg.inode_path = optarg; break; case 512: fsckcfg.nosbcrc = true; 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_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); struct erofs_buf buf = __EROFS_BUF_INITIALIZER; unsigned int ofs, xattr_shared_count; struct erofs_xattr_ibody_header *ih; struct erofs_xattr_entry *entry; int remaining = inode->xattr_isize, ret = 0; erofs_off_t addr; char *ptr; int i; 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); return -EOPNOTSUPP; } else if (inode->xattr_isize < xattr_hdr_size) { if (!inode->xattr_isize) return 0; erofs_err("bogus xattr ibody @ nid %llu", inode->nid | 0ULL); return -EFSCORRUPTED; } addr = erofs_iloc(inode) + inode->inode_isize; ptr = erofs_read_metabuf(&buf, sbi, addr, erofs_inode_in_metabox(inode)); if (IS_ERR(ptr)) { ret = PTR_ERR(ptr); erofs_err("failed to read xattr header @ nid %llu: %d", inode->nid | 0ULL, ret); goto out; } ih = (struct erofs_xattr_ibody_header *)ptr; 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; ptr = erofs_read_metabuf(&buf, sbi, addr, erofs_inode_in_metabox(inode)); if (IS_ERR(ptr)) { ret = PTR_ERR(ptr); erofs_err("failed to read xattr entry @ nid %llu: %d", inode->nid | 0ULL, ret); goto out; } entry = (struct erofs_xattr_entry *)ptr; 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: erofs_put_metabuf(&buf); 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) ret = erofs_sys_lsetxattr(fsckcfg.extract_path, key, value, size); 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 = { .buf = __EROFS_BUF_INITIALIZER, }; 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 && !(map.m_flags & __EROFS_MAP_FRAGMENT)) { 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) { struct erofs_vfile vf; 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_iopen(&vf, inode); if (ret) goto out; ret = erofs_pread(&vf, 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; } struct erofsfsck_get_parent_ctx { struct erofs_dir_context ctx; erofs_nid_t pnid; }; static int erofsfsck_get_parent_cb(struct erofs_dir_context *ctx) { struct erofsfsck_get_parent_ctx *pctx = (void *)ctx; if (ctx->dot_dotdot && ctx->de_namelen == 2) { pctx->pnid = ctx->de_nid; return 1; } return 0; } 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; fsckcfg.nid = 0; fsckcfg.inode_path = NULL; 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; fsckcfg.nosbcrc = true; #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 (!fsckcfg.nosbcrc && erofs_sb_has_sb_chksum(&g_sbi) && erofs_superblock_csum_verify(&g_sbi)) { fsckcfg.corrupted = true; erofs_err("failed to verify superblock checksum"); goto exit_put_super; } if (fsckcfg.extract_path) erofsfsck_hardlink_init(); if (fsckcfg.inode_path) { struct erofs_inode inode = { .sbi = &g_sbi }; err = erofs_ilookup(fsckcfg.inode_path, &inode); if (err) { erofs_err("failed to lookup %s", fsckcfg.inode_path); goto exit_hardlink; } fsckcfg.nid = inode.nid; } else if (!fsckcfg.nid) { fsckcfg.nid = g_sbi.root_nid; } if (!fsckcfg.inode_path && fsckcfg.nid == g_sbi.root_nid) { 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; } } } { erofs_nid_t pnid = fsckcfg.nid; if (fsckcfg.nid != g_sbi.root_nid) { struct erofs_inode inode = { .sbi = &g_sbi, .nid = fsckcfg.nid }; if (!erofs_read_inode_from_disk(&inode) && S_ISDIR(inode.i_mode)) { struct erofsfsck_get_parent_ctx ctx = { .ctx.dir = &inode, .ctx.cb = erofsfsck_get_parent_cb, }; if (erofs_iterate_dir(&ctx.ctx, false) == 1) pnid = ctx.pnid; } } err = erofsfsck_check_inode(pnid, fsckcfg.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.9.1/fuse/000077500000000000000000000000001515160260000147345ustar00rootroot00000000000000erofs-utils-1.9.1/fuse/Makefile.am000066400000000000000000000012661515160260000167750ustar00rootroot00000000000000# 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} 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.9.1/fuse/macosx.h000066400000000000000000000001211515160260000163710ustar00rootroot00000000000000/* SPDX-License-Identifier: GPL-2.0+ */ #ifdef __APPLE__ #undef LIST_HEAD #endif erofs-utils-1.9.1/fuse/main.c000066400000000000000000000421561515160260000160340ustar00rootroot00000000000000// 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 #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; #ifdef HAVE_STRUCT_FUSE_FILE_INFO_CACHE_READDIR fi->cache_readdir = 1; #endif #ifdef HAVE_STRUCT_FUSE_FILE_INFO_KEEP_CACHE fi->keep_cache = 1; #endif 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) { struct erofs_inode *vi = (struct erofs_inode *)fi->fh; struct erofs_vfile vf; char *buf = NULL; int ret; erofs_dbg("read(%llu): size = %zu, off = %lu", ino | 0ULL, size, off); ret = erofs_iopen(&vf, vi); if (ret) { fuse_reply_err(req, -ret); return; } buf = malloc(size); if (!buf) { fuse_reply_err(req, ENOMEM); return; } ret = erofs_pread(&vf, 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) { struct erofs_inode vi = { .sbi = &g_sbi, .nid = erofsfuse_to_nid(ino) }; struct erofs_vfile vf; char *buf = NULL; int ret; ret = erofs_read_inode_from_disk(&vi); if (ret < 0) { fuse_reply_err(req, -ret); return; } ret = erofs_iopen(&vf, &vi); if (ret) { fuse_reply_err(req, -ret); return; } buf = malloc(vi.i_size + 1); if (!buf) { fuse_reply_err(req, ENOMEM); return; } ret = erofs_pread(&vf, 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.9.1/include/000077500000000000000000000000001515160260000154155ustar00rootroot00000000000000erofs-utils-1.9.1/include/erofs/000077500000000000000000000000001515160260000165335ustar00rootroot00000000000000erofs-utils-1.9.1/include/erofs/atomic.h000066400000000000000000000022021515160260000201540ustar00rootroot00000000000000/* 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.9.1/include/erofs/bitops.h000066400000000000000000000015071515160260000202070ustar00rootroot00000000000000/* 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.9.1/include/erofs/blobchunk.h000066400000000000000000000017171515160260000206610ustar00rootroot00000000000000/* 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); void erofs_inode_fixup_chunkformat(struct erofs_inode *inode); int erofs_write_chunk_indexes(struct erofs_inode *inode, struct erofs_vfile *vf, 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); #ifdef __cplusplus } #endif #endif erofs-utils-1.9.1/include/erofs/block_list.h000066400000000000000000000010061515160260000210260ustar00rootroot00000000000000/* 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 __cplusplus } #endif #endif erofs-utils-1.9.1/include/erofs/compress_hints.h000066400000000000000000000013611515160260000217450ustar00rootroot00000000000000/* 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 #include #include "erofs/importer.h" 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_importer *im, struct erofs_inode *inode); void erofs_cleanup_compress_hints(void); int erofs_load_compress_hints(struct erofs_importer *im, struct erofs_sb_info *sbi); #ifdef __cplusplus } #endif #endif erofs-utils-1.9.1/include/erofs/config.h000066400000000000000000000037331515160260000201570ustar00rootroot00000000000000/* 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_BLOCK_MAP = 1, FORCE_INODE_CHUNK_INDEXES, }; enum { TIMESTAMP_UNSPECIFIED, TIMESTAMP_NONE, TIMESTAMP_FIXED, TIMESTAMP_CLAMPING, }; #define EROFS_MAX_COMPR_CFGS 64 struct erofs_configure { const char *c_version; int c_dbg_lvl; bool c_dry_run; char c_timeinherit; char c_chunkbits; char c_dedupe; bool c_showprogress; bool c_extra_ea_name_prefixes; bool c_xattr_name_filter; #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; char c_force_chunkformat; u8 c_mkfs_metabox_algid; const char *mount_point; u32 c_root_xattr_isize; #ifdef EROFS_MT_ENABLED u64 c_mkfs_segment_size; u32 c_mt_workers; #endif #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.9.1/include/erofs/decompress.h000066400000000000000000000014761515160260000210600ustar00rootroot00000000000000/* 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.9.1/include/erofs/dedupe.h000066400000000000000000000020241515160260000201500ustar00rootroot00000000000000/* 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_off_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.9.1/include/erofs/defs.h000066400000000000000000000243221515160260000176300ustar00rootroot00000000000000/* 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_ENDIAN_H #include #else /* Use GNU C predefined macros as a fallback */ #ifndef __BYTE_ORDER #define __BYTE_ORDER __BYTE_ORDER__ #endif #ifndef __LITTLE_ENDIAN #define __LITTLE_ENDIAN __ORDER_LITTLE_ENDIAN__ #endif #ifndef __BIG_ENDIAN #define __BIG_ENDIAN __ORDER_BIG_ENDIAN__ #endif #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)) #define cpu_to_be32(x) ((__be32)__builtin_bswap32(x)) #define cpu_to_be64(x) ((__be64)__builtin_bswap64(x)) #define be32_to_cpu(x) (__builtin_bswap32(x)) #define be64_to_cpu(x) (__builtin_bswap64(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)) #define cpu_to_be32(x) ((__be32)(x)) #define cpu_to_be64(x) ((__be64)(x)) #define be32_to_cpu(x) ((__u32)(x)) #define be64_to_cpu(x) ((__u64)(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) #if __has_attribute(__fallthrough__) # define __erofs_fallthrough __attribute__((__fallthrough__)) #else # define __erofs_fallthrough do {} while (0) /* fallthrough */ #endif #define __erofs_stringify_1(x...) #x #define __erofs_stringify(x...) __erofs_stringify_1(x) #ifdef __cplusplus } #endif #endif erofs-utils-1.9.1/include/erofs/dir.h000066400000000000000000000043231515160260000174640ustar00rootroot00000000000000/* 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.9.1/include/erofs/diskbuf.h000066400000000000000000000011771515160260000203410ustar00rootroot00000000000000/* 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.9.1/include/erofs/err.h000066400000000000000000000020331515160260000174720ustar00rootroot00000000000000/* 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 #include #include #ifndef ENODATA #define ENODATA ENOATTR #endif static inline const char *erofs_strerror(int err) { static char msg[256]; sprintf(msg, "[Error %d] %s", -err, strerror(-err)); return msg; } #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.9.1/include/erofs/exclude.h000066400000000000000000000011451515160260000203360ustar00rootroot00000000000000/* 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.9.1/include/erofs/flex-array.h000066400000000000000000000106551515160260000207650ustar00rootroot00000000000000/* 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.9.1/include/erofs/hashmap.h000066400000000000000000000052521515160260000203310ustar00rootroot00000000000000/* 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.9.1/include/erofs/importer.h000066400000000000000000000032251515160260000205470ustar00rootroot00000000000000/* SPDX-License-Identifier: GPL-2.0+ OR Apache-2.0 */ /* * Copyright (C) 2025 Alibaba Cloud */ #ifndef __EROFS_IMPORTER_H #define __EROFS_IMPORTER_H #ifdef __cplusplus extern "C" { #endif #include "internal.h" enum { EROFS_FORCE_INODE_COMPACT = 1, EROFS_FORCE_INODE_EXTENDED, }; enum { EROFS_DEDUPE_UNSPECIFIED, EROFS_DEDUPE_FORCE_OFF, EROFS_DEDUPE_FORCE_ON, }; enum { EROFS_FRAGDEDUPE_FULL, EROFS_FRAGDEDUPE_INODE, EROFS_FRAGDEDUPE_OFF, }; #define EROFS_COMPRESSED_EXTENT_UNSPECIFIED 0 struct erofs_importer_params { struct z_erofs_paramset *z_paramsets; char *source; u32 mt_async_queue_limit; u32 fixed_uid; u32 fixed_gid; u32 uid_offset; u32 gid_offset; u32 fsalignblks; u32 pclusterblks_max; u32 pclusterblks_def; u32 pclusterblks_packed; s32 pclusterblks_metabox; s32 max_compressed_extent_size; s64 build_time; char force_inodeversion; bool ignore_mtime; bool no_datainline; /* Issue directory data (except inline data) separately from regular inodes */ bool grouped_dirdata; bool dirdata_in_metazone; bool hard_dereference; bool ovlfs_strip; bool dot_omitted; bool no_xattrs; /* don't store extended attributes */ bool no_zcompact; bool ztailpacking; char dedupe; bool fragments; bool all_fragments; bool compress_dir; char fragdedupe; }; struct erofs_importer { struct erofs_importer_params *params; struct erofs_sb_info *sbi; struct erofs_inode *root; }; void erofs_importer_preset(struct erofs_importer_params *params); int erofs_importer_init(struct erofs_importer *im); int erofs_importer_flush_all(struct erofs_importer *im); void erofs_importer_exit(struct erofs_importer *im); #ifdef __cplusplus } #endif #endif erofs-utils-1.9.1/include/erofs/inode.h000066400000000000000000000037151515160260000200100ustar00rootroot00000000000000/* 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" #define EROFS_NID_UNALLOCATED -1ULL static inline struct erofs_inode *erofs_igrab(struct erofs_inode *inode) { (void)erofs_atomic_inc_return(&inode->i_count); return inode; } struct erofs_importer; 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); void erofs_remove_ihash(struct erofs_inode *inode); struct erofs_inode *erofs_iget(dev_t dev, ino_t ino); 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 in_metazone); bool erofs_dentry_is_wht(struct erofs_sb_info *sbi, struct erofs_dentry *d); int __erofs_fill_inode(struct erofs_importer *im, struct erofs_inode *inode, struct stat *st, const char *path); struct erofs_inode *erofs_new_inode(struct erofs_sb_info *sbi); int erofs_importer_load_tree(struct erofs_importer *im, bool rebuild, bool incremental); struct erofs_inode *erofs_mkfs_build_special_from_fd(struct erofs_importer *im, int fd, const char *name); int erofs_fixup_root_inode(struct erofs_inode *root); struct erofs_inode *erofs_make_empty_root_inode(struct erofs_importer *im, struct erofs_sb_info *sbi); #ifdef __cplusplus } #endif #endif erofs-utils-1.9.1/include/erofs/internal.h000066400000000000000000000374211515160260000205270ustar00rootroot00000000000000/* 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 #include #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; typedef u64 erofs_blk_t; /* global sbi */ extern struct erofs_sb_info g_sbi; struct erofs_buf { struct erofs_sb_info *sbi; struct erofs_vfile *vf; erofs_blk_t blocknr; u8 base[EROFS_MAX_BLOCK_SIZE]; }; #define __EROFS_BUF_INITIALIZER ((struct erofs_buf){.blocknr = ~0ULL}) #define erofs_blksiz(sbi) (1u << (sbi)->blkszbits) #define erofs_blknr(sbi, pos) ((pos) >> (sbi)->blkszbits) #define erofs_blkoff(sbi, pos) ((pos) & (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 { char *src_path; u8 tag[64]; erofs_blk_t blocks; erofs_blk_t uniaddr; }; /* 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; }; struct erofs_mkfs_dfops; struct erofs_packed_inode; struct erofs_xattrmgr; struct z_erofs_mgr; struct erofs_metaboxmgr; struct erofs_sb_info { struct erofs_sb_lz4_info lz4; struct erofs_device_info *devs; char *devname; u64 total_blocks; u64 primarydevice_blocks; s32 meta_blkaddr; u32 xattr_blkaddr; u32 feature_compat; u32 feature_incompat; unsigned char blkszbits; u32 sb_size; /* total superblock size */ u32 build_time; u32 fixed_nsec; u64 epoch; /* 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; erofs_nid_t metabox_nid; u32 xattr_prefix_start; u8 xattr_prefix_count; u8 ishare_xattr_prefix_id; 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_xattrmgr *xamgr; struct z_erofs_mgr *zmgr; struct erofs_metamgr *m2gr, *mxgr; struct erofs_packed_inode *packedinode; struct erofs_buffer_head *bh_sb; struct erofs_buffer_head *bh_devt; bool useqpl; bool sb_valid; u32 metazone_startblk; }; /* 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_LZ4_0PADDING) 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(48bit, incompat, INCOMPAT_48BIT) EROFS_FEATURE_FUNCS(metabox, incompat, INCOMPAT_METABOX) EROFS_FEATURE_FUNCS(sb_chksum, compat, COMPAT_SB_CHKSUM) EROFS_FEATURE_FUNCS(xattr_filter, compat, COMPAT_XATTR_FILTER) EROFS_FEATURE_FUNCS(shared_ea_in_metabox, compat, COMPAT_SHARED_EA_IN_METABOX) EROFS_FEATURE_FUNCS(plain_xattr_pfx, compat, COMPAT_PLAIN_XATTR_PFX) EROFS_FEATURE_FUNCS(ishare_xattrs, compat, COMPAT_ISHARE_XATTRS) #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 #define EROFS_I_BLKADDR_DEV_ID_BIT 48 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 { erofs_blk_t i_blkaddr; erofs_blk_t 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 in_metabox; bool compressed_idata; bool lazy_tailblock; bool opaque; /* OVL: non-merge dir that may contain whiteout entries */ bool whiteouts; bool dot_omitted; 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_lclusterbits; uint8_t z_physical_clusterblks; union { uint64_t z_tailextent_headlcn; erofs_off_t fragment_size; }; union { erofs_off_t fragmentoff; erofs_off_t z_fragmentoff; void *fragment; }; u64 z_extents; #define z_idata_size idata_size }; }; void *compressmeta; #ifdef WITH_ANDROID uint64_t capabilities; #endif }; static inline bool erofs_inode_in_metabox(struct erofs_inode *inode) { return inode->nid >> EROFS_DIRENT_NID_METABOX_BIT; } static inline erofs_blk_t erofs_inode_dev_baddr(struct erofs_inode *inode) { return inode->u.i_blkaddr & (BIT_ULL(EROFS_I_BLKADDR_DEV_ID_BIT) - 1); } static inline erofs_off_t erofs_iloc(struct erofs_inode *inode) { struct erofs_sb_info *sbi = inode->sbi; s64 base = erofs_inode_in_metabox(inode) ? 0 : (s64)erofs_pos(sbi, sbi->meta_blkaddr); return base + ((inode->nid & EROFS_DIRENT_NID_MASK) << EROFS_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 #define EROFS_DENTRY_FLAG_VALIDNID 0x01 #define EROFS_DENTRY_FLAG_FIXUP_PNID 0x02 struct erofs_dentry { struct list_head d_child; /* child of parent list */ union { struct erofs_inode *inode; erofs_nid_t nid; }; u8 namelen, type; u8 flags; 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'); } 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) #define EROFS_MAP_FRAGMENT (EROFS_MAP_MAPPED | __EROFS_MAP_FRAGMENT) struct erofs_map_blocks { struct erofs_buf buf; erofs_off_t m_pa, m_la; u64 m_plen, m_llen; unsigned short m_deviceid; char m_algorithmformat; unsigned int m_flags; }; /* * 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; }; struct z_erofs_paramset { char *alg; int clevel; u32 dict_size; char *extraopts; }; int liberofs_global_init(void); void liberofs_global_exit(void); /* 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 *erofs_reserve_sb(struct erofs_bufmgr *bmgr); int erofs_mkfs_init_devices(struct erofs_sb_info *sbi, unsigned int devices); int erofs_write_device_table(struct erofs_sb_info *sbi); int erofs_enable_sb_chksum(struct erofs_sb_info *sbi, u32 *crc); int erofs_superblock_csum_verify(struct erofs_sb_info *sbi); int erofs_mkfs_format_fs(struct erofs_sb_info *sbi, unsigned int blkszbits, unsigned int dsunit, bool metazone); int erofs_mkfs_load_fs(struct erofs_sb_info *sbi, unsigned int dsunit); /* namei.c */ int erofs_read_inode_from_disk(struct erofs_inode *vi); int erofs_ilookup(const char *path, struct erofs_inode *vi); /* data.c */ static inline void erofs_unmap_metabuf(struct erofs_buf *buf) {} static inline void erofs_put_metabuf(struct erofs_buf *buf) {} void *erofs_bread(struct erofs_buf *buf, erofs_off_t offset, bool need_kmap); void erofs_init_metabuf(struct erofs_buf *buf, struct erofs_sb_info *sbi, bool in_mbox); void *erofs_read_metabuf(struct erofs_buf *buf, struct erofs_sb_info *sbi, erofs_off_t offset, bool in_mbox); int erofs_iopen(struct erofs_vfile *vf, struct erofs_inode *inode); 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); const char *z_erofs_list_supported_algorithms(int i, unsigned int *mask); const struct erofs_algorithm *z_erofs_list_available_compressors(int *i); /* 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_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)); } static inline void erofs_free_sensitive(void *ptr, size_t len) { if (!ptr) return; memset(ptr, 0, len); free(ptr); } /* vmdk.c */ int erofs_dump_vmdk_desc(FILE *f, struct erofs_sb_info *sbi); extern const char *erofs_frags_packedname; #define EROFS_PACKED_INODE erofs_frags_packedname static inline bool erofs_is_packed_inode(struct erofs_inode *inode) { return inode->i_srcpath == EROFS_PACKED_INODE; } 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); /* XXX: will find a better way later */ erofs_blk_t erofs_total_metablocks(struct erofs_bufmgr *bmgr); #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.9.1/include/erofs/io.h000066400000000000000000000061351515160260000173200ustar00rootroot00000000000000/* 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 #include #include "defs.h" #ifndef O_BINARY #define O_BINARY 0 #endif /* * seek stuff */ #ifndef SEEK_DATA #define SEEK_DATA 3 #endif #ifndef SEEK_HOLE #define SEEK_HOLE 4 #endif struct erofs_vfile; struct erofs_vfops { ssize_t (*pread)(struct erofs_vfile *vf, void *buf, size_t len, u64 offset); ssize_t (*pwrite)(struct erofs_vfile *vf, const void *buf, u64 offset, size_t len); ssize_t (*pwritev)(struct erofs_vfile *vf, const struct iovec *iov, int iovcnt, u64 pos); 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); ssize_t (*write)(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); ssize_t (*sendfile)(struct erofs_vfile *vout, struct erofs_vfile *vin, off_t *pos, size_t count); int (*xcopy)(struct erofs_vfile *vout, off_t pos, struct erofs_vfile *vin, unsigned int len, bool noseek); void (*close)(struct erofs_vfile *vf); }; /* 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_0write(int fd, 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); ssize_t erofs_io_pwritev(struct erofs_vfile *vf, const struct iovec *iov, int iovcnt, u64 pos); int erofs_io_fsync(struct erofs_vfile *vf); int 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, size_t len, u64 offset); 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); ssize_t erofs_io_sendfile(struct erofs_vfile *vout, struct erofs_vfile *vin, off_t *pos, size_t count); int erofs_io_xcopy(struct erofs_vfile *vout, off_t pos, struct erofs_vfile *vin, unsigned int len, bool noseek); static inline int erofs_pread(struct erofs_vfile *vf, void *buf, size_t len, u64 offset) { ssize_t read; read = erofs_io_pread(vf, buf, len, offset); if (read < 0) return read; return read != len ? -EIO : 0; } void erofs_io_close(struct erofs_vfile *vf); #ifdef __cplusplus } #endif #endif // EROFS_IO_H_ erofs-utils-1.9.1/include/erofs/list.h000066400000000000000000000101451515160260000176600ustar00rootroot00000000000000/* 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)) #define list_for_each_entry_safe_from(pos, n, head, member) \ for (n = list_next_entry(pos, member); \ &pos->member != (head); \ pos = n, n = list_next_entry(n, member)) #ifdef __cplusplus } #endif #endif erofs-utils-1.9.1/include/erofs/lock.h000066400000000000000000000026611515160260000176410ustar00rootroot00000000000000/* SPDX-License-Identifier: GPL-2.0+ OR Apache-2.0 */ #ifndef __EROFS_LOCK_H #define __EROFS_LOCK_H #include "defs.h" #if defined(HAVE_PTHREAD_H) && defined(EROFS_MT_ENABLED) #include typedef pthread_mutex_t erofs_mutex_t; static inline void erofs_mutex_init(erofs_mutex_t *lock) { pthread_mutex_init(lock, NULL); } #define erofs_mutex_lock pthread_mutex_lock #define erofs_mutex_unlock pthread_mutex_unlock #define EROFS_DEFINE_MUTEX(lock) \ erofs_mutex_t lock = PTHREAD_MUTEX_INITIALIZER typedef pthread_rwlock_t erofs_rwsem_t; static inline void erofs_init_rwsem(erofs_rwsem_t *lock) { pthread_rwlock_init(lock, NULL); } #define erofs_down_read pthread_rwlock_rdlock #define erofs_down_write pthread_rwlock_wrlock #define erofs_up_read pthread_rwlock_unlock #define erofs_up_write pthread_rwlock_unlock #else typedef struct {} erofs_mutex_t; static inline void erofs_mutex_init(erofs_mutex_t *lock) {} static inline void erofs_mutex_lock(erofs_mutex_t *lock) {} static inline void erofs_mutex_unlock(erofs_mutex_t *lock) {} #define EROFS_DEFINE_MUTEX(lock) \ erofs_mutex_t lock = {} typedef struct {} erofs_rwsem_t; static inline void erofs_init_rwsem(erofs_rwsem_t *lock) {} static inline void erofs_down_read(erofs_rwsem_t *lock) {} static inline void erofs_down_write(erofs_rwsem_t *lock) {} static inline void erofs_up_read(erofs_rwsem_t *lock) {} static inline void erofs_up_write(erofs_rwsem_t *lock) {} #endif #endif erofs-utils-1.9.1/include/erofs/print.h000066400000000000000000000033651515160260000200470ustar00rootroot00000000000000/* 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.9.1/include/erofs/tar.h000066400000000000000000000025021515160260000174710ustar00rootroot00000000000000/* 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 #define EROFS_IOS_DECODER_GZRAN 3 struct erofs_iostream_liblzma; struct erofs_gzran_builder; struct erofs_iostream { union { struct { struct erofs_vfile vf; struct erofs_gzran_builder *gb; }; 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; }; struct erofs_importer; 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_importer *im, struct erofs_tarfile *tar); #ifdef __cplusplus } #endif #endif erofs-utils-1.9.1/include/erofs/trace.h000066400000000000000000000006111515160260000200000ustar00rootroot00000000000000/* 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.9.1/include/erofs/workqueue.h000066400000000000000000000016211515160260000207330ustar00rootroot00000000000000/* 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.9.1/include/erofs/xattr.h000066400000000000000000000036711515160260000200550ustar00rootroot00000000000000/* SPDX-License-Identifier: GPL-2.0+ OR Apache-2.0 */ #ifndef __EROFS_XATTR_H #define __EROFS_XATTR_H #ifdef __cplusplus extern "C" { #endif #include "internal.h" static inline unsigned int inlinexattr_header_size(struct erofs_inode *vi) { return sizeof(struct erofs_xattr_ibody_header) + sizeof(u32) * vi->xattr_shared_count; } #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; }) struct erofs_importer; ssize_t erofs_sys_lsetxattr(const char *path, const char *name, void *value, size_t size); int erofs_xattr_init(struct erofs_sb_info *sbi); 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_load_shared_xattrs_from_path(struct erofs_sb_info *sbi, const char *path, long inlinexattr_tolerance); int erofs_xattr_insert_name_prefix(const char *prefix); int erofs_xattr_set_ishare_prefix(struct erofs_sb_info *sbi, const char *prefix); void erofs_xattr_cleanup_name_prefixes(void); int erofs_xattr_flush_name_prefixes(struct erofs_importer *im, bool plain); int erofs_xattr_prefixes_init(struct erofs_sb_info *sbi); int erofs_setxattr(struct erofs_inode *inode, int index, const char *name, const void *value, size_t size); int erofs_vfs_setxattr(struct erofs_inode *inode, const char *name, 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); void erofs_xattr_exit(struct erofs_sb_info *sbi); #ifdef __cplusplus } #endif #endif erofs-utils-1.9.1/include/erofs_fs.h000066400000000000000000000367571515160260000174160ustar00rootroot00000000000000/* SPDX-License-Identifier: MIT */ /* * 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 #define EROFS_FEATURE_COMPAT_SHARED_EA_IN_METABOX 0x00000008 #define EROFS_FEATURE_COMPAT_PLAIN_XATTR_PFX 0x00000010 #define EROFS_FEATURE_COMPAT_ISHARE_XATTRS 0x00000020 /* * Any bits that aren't in EROFS_ALL_FEATURE_INCOMPAT should * be incompatible with this kernel version. */ #define EROFS_FEATURE_INCOMPAT_LZ4_0PADDING 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_FEATURE_INCOMPAT_48BIT 0x00000080 #define EROFS_FEATURE_INCOMPAT_METABOX 0x00000100 #define EROFS_ALL_FEATURE_INCOMPAT \ ((EROFS_FEATURE_INCOMPAT_METABOX << 1) - 1) #define EROFS_SB_EXTSLOT_SIZE 16 struct erofs_deviceslot { u8 tag[64]; /* digest(sha256), etc. */ __le32 blocks_lo; /* total blocks count of this device */ __le32 uniaddr_lo; /* unified starting block of this device */ __le32 blocks_hi; /* total blocks count MSB */ __le16 uniaddr_hi; /* unified starting block MSB */ u8 reserved[50]; }; #define EROFS_DEVT_SLOT_SIZE sizeof(struct erofs_deviceslot) /* erofs on-disk super block (currently 144 bytes at maximum) */ 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 */ union { __le16 rootnid_2b; /* nid of root directory */ __le16 blocks_hi; /* (48BIT on) blocks count MSB */ } __packed rb; __le64 inos; /* total valid ino # (== f_files - f_favail) */ __le64 epoch; /* base seconds used for compact inodes */ __le32 fixed_nsec; /* fixed nanoseconds for compact inodes */ __le32 blocks_lo; /* blocks count LSB */ __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 ishare_xattr_prefix_id; __u8 reserved[2]; __le32 build_time; /* seconds added to epoch for mkfs time */ __le64 rootnid_8b; /* (48BIT on) nid of root directory */ __le64 reserved2; __le64 metabox_nid; /* (METABOX on) nid of the metabox inode */ __le64 reserved3; /* [align to extslot 1] */ }; /* * 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_NLINK_1_BIT 4 /* non-directory compact inodes only */ #define EROFS_I_DOT_OMITTED_BIT 4 /* (directories) omit the `.` dirent */ #define EROFS_I_ALL ((1 << (EROFS_I_NLINK_1_BIT + 1)) - 1) /* indicate chunk blkbits, thus 'chunksize = blocksize << chunk blkbits' */ #define EROFS_CHUNK_FORMAT_BLKBITS_MASK 0x001F /* with chunk indexes or just a 4-byte block array */ #define EROFS_CHUNK_FORMAT_INDEXES 0x0020 #define EROFS_CHUNK_FORMAT_48BIT 0x0040 #define EROFS_CHUNK_FORMAT_ALL ((EROFS_CHUNK_FORMAT_48BIT << 1) - 1) /* 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 { __le32 blocks_lo; /* total blocks count (if compressed inodes) */ __le32 startblk_lo; /* starting block number (if flat inodes) */ __le32 rdev; /* device ID (if special inodes) */ struct erofs_inode_chunk_info c; }; union erofs_inode_i_nb { __le16 nlink; /* if EROFS_I_NLINK_1_BIT is unset */ __le16 blocks_hi; /* total blocks count MSB */ __le16 startblk_hi; /* starting block number MSB */ } __packed; /* 32-byte reduced form of an ondisk inode */ struct erofs_inode_compact { __le16 i_format; /* inode format hints */ __le16 i_xattr_icount; __le16 i_mode; union erofs_inode_i_nb i_nb; __le32 i_size; __le32 i_mtime; 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_reserved; }; /* 64-byte complete form of an ondisk inode */ struct erofs_inode_extended { __le16 i_format; /* inode format hints */ __le16 i_xattr_icount; __le16 i_mode; union erofs_inode_i_nb i_nb; __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; /* 1 header + n-1 * 4 bytes inline xattr to keep continuity */ 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 -1ULL /* 4-byte block address array */ #define EROFS_BLOCK_MAP_ENTRY_SIZE sizeof(__le32) /* 8-byte inode chunk index */ struct erofs_inode_chunk_index { __le16 startblk_hi; /* starting block number MSB */ __le16 device_id; /* back-end storage id (with bits masked) */ __le32 startblk_lo; /* starting block number of this chunk */ }; #define EROFS_DIRENT_NID_METABOX_BIT 63 #define EROFS_DIRENT_NID_MASK (BIT_ULL(EROFS_DIRENT_NID_METABOX_BIT) - 1) /* 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 /* * Enable COMPACTED_2B for EROFS_INODE_COMPRESSED_COMPACT inodes: * 4B (disabled) vs 4B+2B+4B (enabled) */ #define Z_EROFS_ADVISE_COMPACTED_2B 0x0001 /* Enable extent metadata for EROFS_INODE_COMPRESSED_FULL inodes */ #define Z_EROFS_ADVISE_EXTENTS 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 /* Indicate the record size for each extent if extent metadata is used */ #define Z_EROFS_ADVISE_EXTRECSZ_BIT 1 #define Z_EROFS_ADVISE_EXTRECSZ_MASK 0x3 #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; }; __le32 h_extents_lo; /* extent count LSB */ }; __le16 h_advise; union { struct { /* algorithm type (bit 0-3: HEAD1; bit 4-7: HEAD2) */ __u8 h_algorithmtype; /* * bit 0-3 : logical cluster bits - blkszbits * bit 4-6 : reserved * bit 7 : pack the whole file into packed inode */ __u8 h_clusterbits; } __packed; __le16 h_extents_hi; /* extent count MSB */ } __packed; }; 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) /* Set on 1st non-head lcluster to store compressed block counti (in blocks) */ #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 { __le32 blkaddr; /* for the HEAD lclusters */ /* * [0] - distance to its HEAD lcluster * [1] - distance to the next HEAD lcluster */ __le16 delta[2]; /* for the NONHEAD lclusters */ } di_u; }; #define Z_EROFS_MAP_HEADER_START(end) round_up(end, 8) #define Z_EROFS_MAP_HEADER_END(end) \ (Z_EROFS_MAP_HEADER_START(end) + sizeof(struct z_erofs_map_header)) #define Z_EROFS_FULL_INDEX_START(end) (Z_EROFS_MAP_HEADER_END(end) + 8) #define Z_EROFS_EXTENT_PLEN_PARTIAL BIT(27) #define Z_EROFS_EXTENT_PLEN_FMT_BIT 28 #define Z_EROFS_EXTENT_PLEN_MASK ((Z_EROFS_PCLUSTER_MAX_SIZE << 1) - 1) struct z_erofs_extent { __le32 plen; /* encoded length */ __le32 pstart_lo; /* physical offset */ __le32 pstart_hi; /* physical offset MSB */ __le32 lstart_lo; /* logical offset */ __le32 lstart_hi; /* logical offset MSB (>= 4GiB inodes) */ __u8 reserved[12]; /* for future use */ }; static inline int z_erofs_extent_recsize(unsigned int advise) { return 4 << ((advise >> Z_EROFS_ADVISE_EXTRECSZ_BIT) & Z_EROFS_ADVISE_EXTRECSZ_MASK); } /* 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) != 144); 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.9.1/lib/000077500000000000000000000000001515160260000145405ustar00rootroot00000000000000erofs-utils-1.9.1/lib/Makefile.am000066400000000000000000000075701515160260000166050ustar00rootroot00000000000000# 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/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/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/importer.h \ $(top_srcdir)/lib/liberofs_base64.h \ $(top_srcdir)/lib/liberofs_cache.h \ $(top_srcdir)/lib/liberofs_compress.h \ $(top_srcdir)/lib/liberofs_fragments.h \ $(top_srcdir)/lib/liberofs_private.h \ $(top_srcdir)/lib/liberofs_rebuild.h \ $(top_srcdir)/lib/liberofs_xxhash.h \ $(top_srcdir)/lib/liberofs_gzran.h \ $(top_srcdir)/lib/liberofs_metabox.h \ $(top_srcdir)/lib/liberofs_nbd.h \ $(top_srcdir)/lib/liberofs_s3.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 \ vmdk.c metabox.c global.c importer.c base64.c liberofs_la_CFLAGS = -Wall ${libuuid_CFLAGS} -I$(top_srcdir)/include liberofs_la_LDFLAGS = ${libselinux_LIBS} ${libuuid_LIBS} ${liblz4_LIBS} \ ${liblzma_LIBS} ${zlib_LIBS} ${libdeflate_LIBS} ${libzstd_LIBS} \ ${libqpl_LIBS} 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} liberofs_la_LDFLAGS += ${libxxhash_LIBS} else liberofs_la_SOURCES += xxhash.c endif liberofs_la_CFLAGS += ${libcurl_CFLAGS} ${openssl_CFLAGS} ${libxml2_CFLAGS} liberofs_la_LDFLAGS += ${libcurl_LIBS} ${openssl_LIBS} if ENABLE_S3 liberofs_la_SOURCES += remotes/s3.c liberofs_la_LDFLAGS += ${libxml2_LIBS} endif if ENABLE_EROFS_MT liberofs_la_LDFLAGS += -lpthread liberofs_la_SOURCES += workqueue.c endif if OS_LINUX liberofs_la_CFLAGS += ${libnl3_CFLAGS} liberofs_la_LDFLAGS += ${libnl3_LIBS} liberofs_la_SOURCES += backends/nbd.c endif liberofs_la_SOURCES += remotes/oci.c remotes/docker_config.c liberofs_la_CFLAGS += ${json_c_CFLAGS} liberofs_la_LDFLAGS += ${json_c_LIBS} liberofs_la_SOURCES += gzran.c if ENABLE_S3 noinst_PROGRAMS = s3erofs_test s3erofs_test_SOURCES = remotes/s3.c s3erofs_test_CFLAGS = -Wall -I$(top_srcdir)/include ${libxml2_CFLAGS} ${openssl_CFLAGS} -DTEST s3erofs_test_LDADD = liberofs.la endif if ENABLE_OCI noinst_PROGRAMS = ocierofs_test ocierofs_test_SOURCES = remotes/oci.c ocierofs_test_CFLAGS = -Wall -I$(top_srcdir)/include ${json_c_CFLAGS} -DTEST ocierofs_test_LDADD = liberofs.la endif erofs-utils-1.9.1/lib/backends/000077500000000000000000000000001515160260000163125ustar00rootroot00000000000000erofs-utils-1.9.1/lib/backends/nbd.c000066400000000000000000000327371515160260000172350ustar00rootroot00000000000000// SPDX-License-Identifier: GPL-2.0+ OR Apache-2.0 /* * Copyright (C) 2025 Alibaba Cloud */ #include #include #include #include #include #include #include #include #include #include #include #include #include "erofs/io.h" #include "erofs/err.h" #include "erofs/print.h" #include "liberofs_nbd.h" #ifdef HAVE_NETLINK_GENL_GENL_H #include #include #include #endif #define NBD_SET_SOCK _IO( 0xab, 0 ) #define NBD_SET_BLKSIZE _IO( 0xab, 1 ) #define NBD_DO_IT _IO( 0xab, 3 ) #define NBD_CLEAR_SOCK _IO( 0xab, 4 ) #define NBD_SET_SIZE_BLOCKS _IO( 0xab, 7 ) #define NBD_DISCONNECT _IO( 0xab, 8 ) #define NBD_SET_TIMEOUT _IO( 0xab, 9 ) #define NBD_SET_FLAGS _IO( 0xab, 10) #define NBD_REQUEST_MAGIC 0x25609513 #define NBD_REPLY_MAGIC 0x67446698 #define NBD_FLAG_READ_ONLY (1 << 1) /* device is read-only */ /* * This is the reply packet that nbd-server sends back to the client after * it has completed an I/O request (or an error occurs). */ struct nbd_reply { __be32 magic; /* NBD_REPLY_MAGIC */ __be32 error; /* 0 = ok, else error */ union { __be64 cookie; /* Opaque identifier from request */ char handle[8]; /* older spelling of cookie */ }; } __packed; long erofs_nbd_in_service(int nbdnum) { int fd, err; char s[32]; (void)snprintf(s, sizeof(s), "/sys/block/nbd%d/size", nbdnum); fd = open(s, O_RDONLY); if (fd < 0) return -errno; err = read(fd, s, sizeof(s)); if (err < 0) { err = -errno; close(fd); return err; } close(fd); if (!memcmp(s, "0\n", sizeof("0\n") - 1)) return -ENOTCONN; (void)snprintf(s, sizeof(s), "/sys/block/nbd%d/pid", nbdnum); fd = open(s, O_RDONLY); if (fd < 0) return -errno; err = read(fd, s, sizeof(s)); if (err < 0) { err = -errno; close(fd); return err; } close(fd); return strtol(s, NULL, 10); } int erofs_nbd_devscan(void) { DIR *_dir; int err; _dir = opendir("/sys/block"); if (!_dir) { fprintf(stderr, "failed to opendir /sys/block: %s\n", strerror(errno)); return -errno; } while (1) { struct dirent *dp; char path[64]; /* * 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) err = -errno; else err = -EBUSY; break; } if (strncmp(dp->d_name, "nbd", 3)) continue; /* Skip nbdX with valid `pid` or `backend` */ err = snprintf(path, sizeof(path), "%s/pid", dp->d_name); if (err < 0) continue; if (!faccessat(dirfd(_dir), path, F_OK, 0)) continue; err = snprintf(path, sizeof(path), "%s/backend", dp->d_name); if (err < 0) continue; if (!faccessat(dirfd(_dir), path, F_OK, 0)) continue; err = atoi(dp->d_name + 3); break; } closedir(_dir); return err; } int erofs_nbd_connect(int nbdfd, int blkbits, u64 blocks) { int sv[2], err; err = socketpair(AF_UNIX, SOCK_STREAM, 0, sv); if (err < 0) return -errno; err = ioctl(nbdfd, NBD_CLEAR_SOCK, 0); if (err < 0) goto err_out; err = ioctl(nbdfd, NBD_SET_BLKSIZE, 1U << blkbits); if (err < 0) goto err_out; err = ioctl(nbdfd, NBD_SET_SIZE_BLOCKS, blocks); if (err < 0) goto err_out; err = ioctl(nbdfd, NBD_SET_TIMEOUT, 0); if (err < 0) goto err_out; err = ioctl(nbdfd, NBD_SET_FLAGS, NBD_FLAG_READ_ONLY); if (err < 0) goto err_out; err = ioctl(nbdfd, NBD_SET_SOCK, sv[1]); if (err < 0) goto err_out; return sv[0]; err_out: close(sv[0]); close(sv[1]); return err; } char *erofs_nbd_get_identifier(int nbdnum) { char s[32], *line = NULL; size_t n; FILE *f; int err; (void)snprintf(s, sizeof(s), "/sys/block/nbd%d/backend", nbdnum); f = fopen(s, "r"); if (!f) { if (errno == ENOENT) return NULL; return ERR_PTR(-errno); } err = getline(&line, &n, f); if (err < 0) err = -errno; fclose(f); if (err < 0) return ERR_PTR(err); if (!err) { free(line); return NULL; } if (line[err - 1] == '\n') line[err - 1] = '\0'; return line; } int erofs_nbd_get_index_from_minor(int minor) { char s[32], *line = NULL; int ret = -ENOENT; size_t n; FILE *f; (void)snprintf(s, sizeof(s), "/sys/dev/block/" __erofs_stringify(EROFS_NBD_MAJOR) ":%d/uevent", minor); f = fopen(s, "r"); if (!f) return -errno; while (getline(&line, &n, f) >= 0) { if (strncmp(line, "DEVNAME=nbd", sizeof("DEVNAME=nbd") - 1)) continue; ret = strtoul(line + sizeof("DEVNAME=nbd") - 1, NULL, 10); break; } free(line); return ret; } #if defined(HAVE_NETLINK_GENL_GENL_H) && defined(HAVE_LIBNL_GENL_3) enum { NBD_ATTR_UNSPEC, NBD_ATTR_INDEX, NBD_ATTR_SIZE_BYTES, NBD_ATTR_BLOCK_SIZE_BYTES, NBD_ATTR_TIMEOUT, NBD_ATTR_SERVER_FLAGS, NBD_ATTR_CLIENT_FLAGS, NBD_ATTR_SOCKETS, NBD_ATTR_DEAD_CONN_TIMEOUT, NBD_ATTR_DEVICE_LIST, NBD_ATTR_BACKEND_IDENTIFIER, __NBD_ATTR_MAX, }; #define NBD_ATTR_MAX (__NBD_ATTR_MAX - 1) enum { NBD_SOCK_ITEM_UNSPEC, NBD_SOCK_ITEM, __NBD_SOCK_ITEM_MAX, }; #define NBD_SOCK_ITEM_MAX (__NBD_SOCK_ITEM_MAX - 1) enum { NBD_SOCK_UNSPEC, NBD_SOCK_FD, __NBD_SOCK_MAX, }; #define NBD_SOCK_MAX (__NBD_SOCK_MAX - 1) enum { NBD_CMD_UNSPEC, NBD_CMD_CONNECT, NBD_CMD_DISCONNECT, NBD_CMD_RECONFIGURE, __NBD_CMD_MAX, }; /* client behavior specific flags */ /* delete the nbd device on disconnect */ #define NBD_CFLAG_DESTROY_ON_DISCONNECT (1 << 0) /* disconnect the nbd device on close by last opener */ #define NBD_CFLAG_DISCONNECT_ON_CLOSE (1 << 1) static struct nl_sock *erofs_nbd_get_nl_sock(int *driver_id) { struct nl_sock *socket; int err; socket = nl_socket_alloc(); if (!socket) { erofs_err("Couldn't allocate netlink socket"); return ERR_PTR(-ENOMEM); } err = genl_connect(socket); if (err) { erofs_err("Couldn't connect to the generic netlink socket"); return ERR_PTR(err); } err = genl_ctrl_resolve(socket, "nbd"); if (err < 0) { erofs_err("Failed to resolve NBD netlink family. Ensure the NBD module is loaded and it supports netlink."); return ERR_PTR(err); } *driver_id = err; return socket; } struct erofs_nbd_nl_cfg_cbctx { int *index; int errcode; }; static int erofs_nbd_nl_cfg_cb(struct nl_msg *msg, void *arg) { struct genlmsghdr *gnlh = nlmsg_data(nlmsg_hdr(msg)); struct nlattr *msg_attr[NBD_ATTR_MAX + 1]; struct erofs_nbd_nl_cfg_cbctx *ctx = arg; int err; err = nla_parse(msg_attr, NBD_ATTR_MAX, genlmsg_attrdata(gnlh, 0), genlmsg_attrlen(gnlh, 0), NULL); if (err) { erofs_err("Invalid response from the kernel"); ctx->errcode = err; } if (!msg_attr[NBD_ATTR_INDEX]) { erofs_err("Did not receive index from the kernel"); ctx->errcode = -EBADMSG; } *ctx->index = nla_get_u32(msg_attr[NBD_ATTR_INDEX]); erofs_dbg("Connected /dev/nbd%d", *ctx->index); ctx->errcode = 0; return NL_OK; } int erofs_nbd_nl_connect(int *index, int blkbits, u64 blocks, const char *identifier) { struct erofs_nbd_nl_cfg_cbctx cbctx = { .index = index, }; struct nlattr *sock_attr = NULL, *sock_opt = NULL; struct nl_sock *socket; struct nl_msg *msg; int sv[2], err; int driver_id; err = socketpair(AF_UNIX, SOCK_STREAM, 0, sv); if (err < 0) return -errno; socket = erofs_nbd_get_nl_sock(&driver_id); if (IS_ERR(socket)) { err = PTR_ERR(socket); goto err_out; } nl_socket_modify_cb(socket, NL_CB_VALID, NL_CB_CUSTOM, erofs_nbd_nl_cfg_cb, &cbctx); msg = nlmsg_alloc(); if (!msg) { erofs_err("Couldn't allocate netlink message"); err = -ENOMEM; goto err_nls_free; } genlmsg_put(msg, NL_AUTO_PORT, NL_AUTO_SEQ, driver_id, 0, 0, NBD_CMD_CONNECT, 0); if (*index >= 0) NLA_PUT_U32(msg, NBD_ATTR_INDEX, *index); NLA_PUT_U64(msg, NBD_ATTR_BLOCK_SIZE_BYTES, 1u << blkbits); NLA_PUT_U64(msg, NBD_ATTR_SIZE_BYTES, blocks << blkbits); NLA_PUT_U64(msg, NBD_ATTR_SERVER_FLAGS, NBD_FLAG_READ_ONLY); NLA_PUT_U64(msg, NBD_ATTR_TIMEOUT, 0); NLA_PUT_U64(msg, NBD_ATTR_DEAD_CONN_TIMEOUT, EROFS_NBD_DEAD_CONN_TIMEOUT); if (identifier) NLA_PUT_STRING(msg, NBD_ATTR_BACKEND_IDENTIFIER, identifier); err = -EINVAL; sock_attr = nla_nest_start(msg, NBD_ATTR_SOCKETS); if (!sock_attr) { erofs_err("Couldn't nest the sockets for our connection"); goto err_nlm_free; } sock_opt = nla_nest_start(msg, NBD_SOCK_ITEM); if (!sock_opt) { nla_nest_cancel(msg, sock_attr); goto err_nlm_free; } NLA_PUT_U32(msg, NBD_SOCK_FD, sv[1]); nla_nest_end(msg, sock_opt); nla_nest_end(msg, sock_attr); err = nl_send_sync(socket, msg); if (err) goto err_out; nl_socket_free(socket); if (cbctx.errcode) return cbctx.errcode; return sv[0]; nla_put_failure: if (sock_opt) nla_nest_cancel(msg, sock_opt); if (sock_attr) nla_nest_cancel(msg, sock_attr); err_nlm_free: nlmsg_free(msg); err_nls_free: nl_socket_free(socket); err_out: close(sv[0]); close(sv[1]); return err; } int erofs_nbd_nl_reconnect(int index, const char *identifier) { struct nlattr *sock_attr = NULL, *sock_opt = NULL; struct nl_sock *socket; struct nl_msg *msg; int sv[2], err; int driver_id; err = socketpair(AF_UNIX, SOCK_STREAM, 0, sv); if (err < 0) return -errno; socket = erofs_nbd_get_nl_sock(&driver_id); if (IS_ERR(socket)) { err = PTR_ERR(socket); goto err_out; } msg = nlmsg_alloc(); if (!msg) { erofs_err("Couldn't allocate netlink message"); err = -ENOMEM; goto err_nls_free; } genlmsg_put(msg, NL_AUTO_PORT, NL_AUTO_SEQ, driver_id, 0, 0, NBD_CMD_RECONFIGURE, 0); NLA_PUT_U32(msg, NBD_ATTR_INDEX, index); if (identifier) NLA_PUT_STRING(msg, NBD_ATTR_BACKEND_IDENTIFIER, identifier); err = -EINVAL; sock_attr = nla_nest_start(msg, NBD_ATTR_SOCKETS); if (!sock_attr) { erofs_err("Couldn't nest the sockets for our connection"); goto err_nlm_free; } sock_opt = nla_nest_start(msg, NBD_SOCK_ITEM); if (!sock_opt) { nla_nest_cancel(msg, sock_attr); goto err_nlm_free; } NLA_PUT_U32(msg, NBD_SOCK_FD, sv[1]); nla_nest_end(msg, sock_opt); nla_nest_end(msg, sock_attr); err = nl_send_sync(socket, msg); if (err) goto err_out; nl_socket_free(socket); return sv[0]; nla_put_failure: if (sock_opt) nla_nest_cancel(msg, sock_opt); if (sock_attr) nla_nest_cancel(msg, sock_attr); err_nlm_free: nlmsg_free(msg); err_nls_free: nl_socket_free(socket); err_out: close(sv[0]); close(sv[1]); return err; } int erofs_nbd_nl_reconfigure(int index, const char *identifier, bool autoclear) { struct nl_sock *socket; struct nl_msg *msg; int err, driver_id; unsigned int cflags; socket = erofs_nbd_get_nl_sock(&driver_id); if (IS_ERR(socket)) return PTR_ERR(socket); msg = nlmsg_alloc(); if (!msg) { erofs_err("Couldn't allocate netlink message"); err = -ENOMEM; goto err_nls_free; } err = -EINVAL; genlmsg_put(msg, NL_AUTO_PORT, NL_AUTO_SEQ, driver_id, 0, 0, NBD_CMD_RECONFIGURE, 0); NLA_PUT_U32(msg, NBD_ATTR_INDEX, index); if (identifier) NLA_PUT_STRING(msg, NBD_ATTR_BACKEND_IDENTIFIER, identifier); cflags = (autoclear ? NBD_CFLAG_DISCONNECT_ON_CLOSE : 0); NLA_PUT_U64(msg, NBD_ATTR_CLIENT_FLAGS, cflags); err = nl_send_sync(socket, msg); nl_socket_free(socket); return err; nla_put_failure: nlmsg_free(msg); err_nls_free: nl_socket_free(socket); return err; } int erofs_nbd_nl_disconnect(int index) { struct nl_sock *socket; struct nl_msg *msg; int driver_id, err; socket= erofs_nbd_get_nl_sock(&driver_id); if (IS_ERR(socket)) return PTR_ERR(socket); msg = nlmsg_alloc(); if (!msg) { erofs_err("Couldn't allocate netlink message"); err = -ENOMEM; goto err_nls_free; } err = -EINVAL; genlmsg_put(msg, NL_AUTO_PORT, NL_AUTO_SEQ, driver_id, 0, 0, NBD_CMD_DISCONNECT, 0); NLA_PUT_U32(msg, NBD_ATTR_INDEX, index); err = nl_send_sync(socket, msg); if (err < 0) erofs_err("Failed to disconnect device %d, check dmesg", err); nl_socket_free(socket); return err; nla_put_failure: erofs_err("Failed to create netlink message"); nlmsg_free(msg); err_nls_free: nl_socket_free(socket); return err; } #else int erofs_nbd_nl_connect(int *index, int blkbits, u64 blocks, const char *identifier) { return -EOPNOTSUPP; } int erofs_nbd_nl_reconnect(int index, const char *identifier) { return -EOPNOTSUPP; } int erofs_nbd_nl_reconfigure(int index, const char *identifier, bool autoclear) { return -EOPNOTSUPP; } int erofs_nbd_nl_disconnect(int index) { return -EOPNOTSUPP; } #endif int erofs_nbd_do_it(int nbdfd) { int err; err = ioctl(nbdfd, NBD_DO_IT, 0); if (err < 0) { if (errno == EPIPE) /* * `ioctl(NBD_DO_IT)` normally returns EPIPE when someone has * disconnected the socket via NBD_DISCONNECT. We do not want * to return 1 in that case. */ err = 0; else err = -errno; } if (err) erofs_err("NBD_DO_IT ends with %s", erofs_strerror(err)); close(nbdfd); return err; } int erofs_nbd_get_request(int skfd, struct erofs_nbd_request *rq) { struct erofs_vfile vf = { .fd = skfd }; int err; err = erofs_io_read(&vf, rq, sizeof(*rq)); if (err < sizeof(*rq)) return -EPIPE; if (rq->magic != cpu_to_be32(NBD_REQUEST_MAGIC)) return -EIO; rq->type = be32_to_cpu((__be32)rq->type); rq->from = be64_to_cpu((__be64)rq->from); rq->len = be32_to_cpu((__be32)rq->len); return 0; } int erofs_nbd_send_reply_header(int skfd, __le64 cookie, int err) { struct nbd_reply reply = { .magic = cpu_to_be32(NBD_REPLY_MAGIC), .error = cpu_to_be32(err), .cookie = cookie, }; int ret; ret = write(skfd, &reply, sizeof(reply)); if (ret == sizeof(reply)) return 0; return ret < 0 ? -errno : -EIO; } int erofs_nbd_disconnect(int nbdfd) { int err, err2; err = ioctl(nbdfd, NBD_DISCONNECT); err2 = ioctl(nbdfd, NBD_CLEAR_SOCK); return err ?: err2; } erofs-utils-1.9.1/lib/base64.c000066400000000000000000000027571515160260000160030ustar00rootroot00000000000000// SPDX-License-Identifier: GPL-2.0+ OR Apache-2.0 #include "liberofs_base64.h" #include static const char lookup_table[65] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; int erofs_base64_encode(const u8 *src, int srclen, char *dst) { u32 ac = 0; int bits = 0; int i; char *cp = dst; for (i = 0; i < srclen; i++) { ac = (ac << 8) | src[i]; bits += 8; do { bits -= 6; *cp++ = lookup_table[(ac >> bits) & 0x3f]; } while (bits >= 6); } if (bits) { *cp++ = lookup_table[(ac << (6 - bits)) & 0x3f]; bits -= 6; } while (bits < 0) { *cp++ = '='; bits += 2; } return cp - dst; } int erofs_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 - 1] == '=') len -= 1 + (len >= 2 && src[len - 2] == '='); padding = true; } for (i = 0; i < len; i++) { p = strchr(lookup_table, src[i]); if (!p || !src[i]) return -2; ac = (ac << 6 | (p - lookup_table)); bits += 6; if (bits >= 8) { bits -= 8; *cp++ = ac >> bits; } } ac &= BIT(bits) - 1; if (ac) { if (padding || ac > 0xff) return -1; else *cp++ = ac; } return cp - dst; } erofs-utils-1.9.1/lib/bitops.c000066400000000000000000000011761515160260000162110ustar00rootroot00000000000000// 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.9.1/lib/blobchunk.c000066400000000000000000000435671515160260000166720ustar00rootroot00000000000000// 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/importer.h" #include "liberofs_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; 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)); if (cfg.c_dedupe != EROFS_DEDUPE_FORCE_OFF) { 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 %llu", chunk->blkaddr | 0ULL); } 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 %llu", chunksize | 0ULL, chunk->blkaddr | 0ULL); 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)); } void erofs_inode_fixup_chunkformat(struct erofs_inode *inode) { unsigned int unit, src; u64 extent_count; bool _48bit; if (inode->u.chunkformat & EROFS_CHUNK_FORMAT_INDEXES) unit = sizeof(struct erofs_inode_chunk_index); else unit = EROFS_BLOCK_MAP_ENTRY_SIZE; _48bit = inode->u.chunkformat & EROFS_CHUNK_FORMAT_48BIT; if (_48bit) return; extent_count = inode->extent_isize / unit; for (src = 0; src < extent_count; ++src) { struct erofs_blobchunk *chunk = *(void **)(inode->chunkindexes + src * sizeof(void *)); if (chunk->blkaddr == EROFS_NULL_ADDR) continue; if (chunk->device_id) { if (chunk->blkaddr > UINT32_MAX) { _48bit = true; break; } } else if (remapped_base + chunk->blkaddr > UINT32_MAX) { _48bit = true; break; } } if (_48bit) inode->u.chunkformat |= EROFS_CHUNK_FORMAT_48BIT; } int erofs_write_chunk_indexes(struct erofs_inode *inode, struct erofs_vfile *vf, 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_end = EROFS_NULL_ADDR, chunkblks, addrmask; erofs_blk_t extent_start = EROFS_NULL_ADDR; erofs_off_t source_offset; unsigned int dst, src, unit, zeroedlen; bool _48bit; if (inode->u.chunkformat & EROFS_CHUNK_FORMAT_INDEXES) unit = sizeof(struct erofs_inode_chunk_index); else unit = EROFS_BLOCK_MAP_ENTRY_SIZE; chunkblks = 1ULL << (inode->u.chunkformat & EROFS_CHUNK_FORMAT_BLKBITS_MASK); _48bit = inode->u.chunkformat & EROFS_CHUNK_FORMAT_48BIT; for (dst = src = 0; dst < inode->extent_isize; src += sizeof(void *), dst += unit) { struct erofs_blobchunk *chunk; erofs_blk_t startblk; chunk = *(void **)(inode->chunkindexes + src); if (chunk->blkaddr == EROFS_NULL_ADDR) { startblk = EROFS_NULL_ADDR; } else if (chunk->device_id) { DBG_BUGON(!(inode->u.chunkformat & EROFS_CHUNK_FORMAT_INDEXES)); startblk = chunk->blkaddr; extent_start = EROFS_NULL_ADDR; } else { startblk = remapped_base + chunk->blkaddr; } if (extent_start == EROFS_NULL_ADDR || startblk != 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); } extent_start = startblk; source_offset = chunk->sourceoffset; } extent_end = startblk + chunkblks; addrmask = _48bit ? BIT_ULL(48) - 1 : BIT_ULL(32) - 1; startblk &= addrmask; idx.device_id = cpu_to_le16(chunk->device_id); idx.startblk_lo = cpu_to_le32(startblk); idx.startblk_hi = cpu_to_le32(startblk >> 32); DBG_BUGON(!_48bit && idx.startblk_hi); if (unit == EROFS_BLOCK_MAP_ENTRY_SIZE) memcpy(inode->chunkindexes + dst, &idx.startblk_lo, unit); else memcpy(inode->chunkindexes + dst, &idx, sizeof(idx)); } if (extent_start != EROFS_NULL_ADDR) { extent_end = min(extent_end, extent_start + remaining_blks); 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); } off = roundup(off, unit); return erofs_io_pwrite(vf, 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; /* 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; } 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; /* * If dsunit <= chunksize, deduplication will not cause misalignment, * so it's uncontroversial to apply the current data alignment policy. */ if (sbi->bmgr->dsunit > 1 && sbi->bmgr->dsunit <= (1u << (chunkbits - sbi->blkszbits))) { off_t off = lseek(blobfile, 0, SEEK_CUR); off = roundup(off, sbi->bmgr->dsunit * erofs_blksiz(sbi)); if (lseek(blobfile, off, SEEK_SET) != off) { ret = -errno; erofs_err("failed to lseek blobdev@0x%llx: %s", off, erofs_strerror(ret)); goto err; } erofs_dbg("Align /%s on block #%d (0x%llx)", erofs_fspath(inode->i_srcpath), erofs_blknr(sbi, off), off); } for (pos = 0; pos < inode->i_size; pos += len) { 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; } 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; } /* * XXX: it's safe for now, but we really need to refactor blobchunk * after 1.9 is out. */ if (blkaddr > UINT32_MAX) { inode->u.chunkformat |= EROFS_CHUNK_FORMAT_48BIT; erofs_info("48-bit block addressin enabled for indexing larger tar"); erofs_sb_set_48bit(sbi); } 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) 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(); else blobfile = open(blobfile_path, O_WRONLY | O_CREAT | O_TRUNC | O_BINARY, 0666); if (blobfile < 0) return -errno; hashmap_init(&blob_hashmap, erofs_blob_hashmap_cmp, 0); return erofs_insert_zerochunk(chunksize); } erofs-utils-1.9.1/lib/block_list.c000066400000000000000000000020301515160260000170240ustar00rootroot00000000000000// 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, "%08llx %8llx %08" PRIx64 " %08u\n", blkaddr | 0ULL, nblocks | 0ULL, srcoff, zeroedlen); else fprintf(block_list_fp, "%08llx %8llx %08" PRIx64 "\n", blkaddr | 0ULL, nblocks | 0ULL, srcoff); } erofs-utils-1.9.1/lib/cache.c000066400000000000000000000333761515160260000157630ustar00rootroot00000000000000// 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 "erofs/print.h" #include "liberofs_cache.h" static int erofs_bh_flush_drop_directly(struct erofs_buffer_head *bh, bool abort) { 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, bool abort) { 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, struct erofs_vfile *vf) { 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 = EROFS_NULL_ADDR; bmgr->tail_blkaddr = startblk; bmgr->last_mapped_block = &bmgr->blkh; bmgr->metablkcnt = 0; bmgr->dsunit = 0; bmgr->vf = vf ?: &sbi->bdev; 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 != EROFS_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 EROFS_NULL_ADDR all the time */ if (oob && list_next_entry(bb, list)->blkaddr != EROFS_NULL_ADDR) return -EINVAL; blkaddr = bb->blkaddr; if (blkaddr != EROFS_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 == EROFS_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 != EROFS_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 = EROFS_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 == EROFS_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 != EROFS_NULL_ADDR) return bb->blkaddr; do { t = list_next_entry(t, list); if (t == &bmgr->blkh) break; DBG_BUGON(t->blkaddr != EROFS_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); } static int __erofs_bflush(struct erofs_bufmgr *bmgr, struct erofs_buffer_block *bb, bool abort) { 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) { if (!abort) { skip = true; continue; } bh->op = &erofs_drop_directly_bhops; } /* flush and remove bh */ ret = bh->op->flush(bh, abort); if (__erofs_unlikely(ret == -EBUSY && !abort)) { skip = true; continue; } if (ret < 0) return ret; } if (skip) continue; padding = blksiz - (p->buffers.off & (blksiz - 1)); if (padding != blksiz) { ret = erofs_io_fallocate(bmgr->vf, erofs_pos(sbi, blkaddr) - padding, padding, true); if (ret < 0) return ret; } if (p->type != DATA) bmgr->metablkcnt += p->buffers.nblocks; erofs_dbg("block %llu to %llu flushed", p->blkaddr | 0ULL, (blkaddr - 1) | 0ULL); erofs_bfree(p); } return 0; } int erofs_bflush(struct erofs_bufmgr *bmgr, struct erofs_buffer_block *bb) { return __erofs_bflush(bmgr, bb, false); } 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 != EROFS_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) { DBG_BUGON(__erofs_bflush(bmgr, NULL, true)); DBG_BUGON(!list_empty(&bmgr->blkh.list)); free(bmgr); } erofs-utils-1.9.1/lib/compress.c000066400000000000000000001723101515160260000165430ustar00rootroot00000000000000// 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/dedupe.h" #include "compressor.h" #include "erofs/block_list.h" #include "erofs/compress_hints.h" #ifdef EROFS_MT_ENABLED #include "erofs/workqueue.h" #endif #include "liberofs_cache.h" #include "liberofs_compress.h" #include "liberofs_fragments.h" #include "liberofs_metabox.h" #define Z_EROFS_DESTBUF_SZ (Z_EROFS_PCLUSTER_MAX_SIZE + EROFS_MAX_BLOCK_SIZE * 2) struct z_erofs_extent_item { struct list_head list; struct z_erofs_inmem_extent e; }; struct z_erofs_compress_ictx { /* inode context */ struct erofs_vfile _vf, *vf; struct erofs_importer *im; struct erofs_inode *inode; struct erofs_compress_cfg *ccfg; int fd; u64 fpos; u32 tofh; bool fix_dedupedfrag; bool fragemitted; bool dedupe; bool data_unaligned; /* fields for write indexes */ u8 *metacur; struct list_head extents; u16 clusterofs; int seg_num; u32 max_compressed_extent_size; #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 */ union { struct list_head extents; struct list_head sibling; }; 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, poff; u16 clusterofs; int seg_idx; void *membuf; }; #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; struct z_erofs_paramset *zset; int errcode; }; static struct { struct erofs_workqueue wq; struct erofs_compress_work *idle; pthread_mutex_t mutex; } z_erofs_mt_ctrl; struct z_erofs_compress_fslot { struct list_head pending; pthread_mutex_t lock; bool inprogress; }; #endif /* compressing configuration specified by users */ struct erofs_compress_cfg { struct erofs_compress handle; struct z_erofs_paramset zset; int algorithmtype; bool enable; }; struct z_erofs_mgr { struct erofs_compress_cfg ccfg[EROFS_MAX_COMPR_CFGS]; #ifdef EROFS_MT_ENABLED struct z_erofs_compress_fslot fslot[1024]; #endif }; static bool z_erofs_mt_enabled; #define Z_EROFS_LEGACY_MAP_HEADER_SIZE Z_EROFS_FULL_INDEX_START(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) { const struct erofs_importer_params *params = ctx->im->params; 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 && !params->ztailpacking && !params->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_lclusterbits) - 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("Recording %u uncompressed data of %s", count, inode->i_srcpath); memcpy(ctx->membuf + ctx->poff, dst, 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; } ctx->poff += erofs_blksiz(sbi); 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_lclusterbits; 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_pclustersize(struct z_erofs_compress_ictx *ictx) { struct erofs_importer_params *params = ictx->im->params; struct erofs_inode *inode = ictx->inode; unsigned int blkszbits = inode->sbi->blkszbits; if (erofs_is_packed_inode(inode)) return params->pclusterblks_packed << blkszbits; if (erofs_is_metabox_inode(inode)) return params->pclusterblks_metabox << blkszbits; #ifndef NDEBUG if (cfg.c_random_pclusterblks) return (1 + rand() % params->pclusterblks_max) << blkszbits; #endif if (cfg.c_compress_hints_file) { z_erofs_apply_compress_hints(ictx->im, inode); DBG_BUGON(!inode->z_physical_clusterblks); return inode->z_physical_clusterblks << blkszbits; } return params->pclusterblks_def << blkszbits; } 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_pclustersize(ictx), roundup(newsize - inode->fragment_size, erofs_blksiz(sbi))); return false; } inode->fragment_size = newsize; erofs_dbg("Reducing fragment size to %llu", inode->fragment_size | 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; const struct erofs_importer_params *params = ictx->im->params; 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 = (params->fragments && tsg && final && !is_packed_inode && !erofs_is_metabox_inode(inode)); bool data_unaligned = ictx->data_unaligned; bool may_inline = (params->ztailpacking && !data_unaligned && 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 && inode->fragment_size && !ictx->fix_dedupedfrag) { ctx->pclustersize = roundup(len, blksz); goto fix_dedupedfrag; } e->length = len; if (may_packing) goto frag_packing; if (!may_inline && len <= blksz) goto nocompression; } e->length = min(len, ictx->max_compressed_extent_size); if (data_unaligned) { ret = erofs_compress(h, ctx->queue + ctx->head, e->length, dst, ctx->pclustersize); if (ret == -EOPNOTSUPP) { data_unaligned = false; goto retry_aligned; } } else { retry_aligned: ret = erofs_compress_destsize(h, ctx->queue + ctx->head, &e->length, dst, ctx->pclustersize); } if (ret > 0) { compressedsize = ret; /* even compressed size is smaller, there is no real gain */ if (!data_unaligned && !(may_inline && e->length == len && ret < blksz)) ret = roundup(ret, blksz); } else if (ret != -ENOSPC) { erofs_err("failed to compress %s: %s", inode->i_srcpath, erofs_strerror(ret)); return ret; } /* check if there is enough gain to keep the compressed data */ if (ret < 0 || 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, ctx->pclustersize); nocompression: if (params->dedupe == EROFS_DEDUPE_FORCE_ON) 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 = erofs_fragment_pack(inode, ctx->queue + ctx->head, ~0ULL, len, ictx->tofh, false); 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 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 (!data_unaligned && 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; } if (data_unaligned) e->plen = compressedsize; else e->plen = round_up(compressedsize, blksz); DBG_BUGON(e->plen >= e->length); padding = e->plen - compressedsize; /* 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("Recording %u compressed data of %u bytes of %s", e->length, e->plen, inode->i_srcpath); memcpy(ctx->membuf + ctx->poff, dst - padding, 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; } ctx->poff += e->plen; 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_lclusterbits; 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; if (inode->datalayout == EROFS_INODE_COMPRESSED_FULL && (inode->z_advise & Z_EROFS_ADVISE_EXTENTS)) { int recsz = z_erofs_extent_recsize(inode->z_advise); if (recsz > offsetof(struct z_erofs_extent, pstart_hi)) { h = (struct z_erofs_map_header) { .h_advise = cpu_to_le16(inode->z_advise), .h_extents_lo = cpu_to_le32(inode->z_extents), }; } else { DBG_BUGON(inode->z_lclusterbits < sbi->blkszbits); h = (struct z_erofs_map_header) { .h_advise = cpu_to_le16(inode->z_advise), .h_clusterbits = inode->z_lclusterbits - sbi->blkszbits, }; } } else { h = (struct z_erofs_map_header) { .h_advise = cpu_to_le16(inode->z_advise), .h_algorithmtype = inode->z_algorithmtype[1] << 4 | inode->z_algorithmtype[0], /* lclustersize */ .h_clusterbits = inode->z_lclusterbits - 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)); } #define EROFS_FULL_INDEXES_SZ(inode) \ (BLK_ROUND_UP(inode->sbi, inode->i_size) * \ sizeof(struct z_erofs_lcluster_index) + Z_EROFS_LEGACY_MAP_HEADER_SIZE) static void *z_erofs_write_extents(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; unsigned int lclusterbits, nexts; bool pstart_hi = false, unaligned_data = false; erofs_off_t pstart, pend, lstart; unsigned int recsz, metasz, moff; void *metabuf; ei = list_first_entry(&ctx->extents, struct z_erofs_extent_item, list); lclusterbits = max_t(u8, ilog2(ei->e.length - 1) + 1, sbi->blkszbits); pend = pstart = ei->e.pstart; nexts = 0; list_for_each_entry(ei, &ctx->extents, list) { pstart_hi |= (ei->e.pstart > UINT32_MAX); if ((ei->e.pstart | ei->e.plen) & ((1U << sbi->blkszbits) - 1)) unaligned_data = true; if (pend != ei->e.pstart) pend = EROFS_NULL_ADDR; else pend += ei->e.plen; if (ei->e.length != 1 << lclusterbits) { if (ei->list.next != &ctx->extents || ei->e.length > 1 << lclusterbits) lclusterbits = 0; } ++nexts; } recsz = inode->i_size > UINT32_MAX ? 32 : 16; if (lclusterbits) { if (pend != EROFS_NULL_ADDR) recsz = 4; else if (recsz <= 16 && !pstart_hi) recsz = 8; } moff = Z_EROFS_MAP_HEADER_END(inode->inode_isize + inode->xattr_isize); moff = round_up(moff, recsz) - Z_EROFS_MAP_HEADER_START(inode->inode_isize + inode->xattr_isize); metasz = moff + recsz * nexts + 8 * (recsz <= 4); if (!unaligned_data && metasz > EROFS_FULL_INDEXES_SZ(inode)) return ERR_PTR(-EAGAIN); metabuf = malloc(metasz); if (!metabuf) return ERR_PTR(-ENOMEM); inode->z_lclusterbits = lclusterbits; inode->z_extents = nexts; ctx->metacur = metabuf + moff; if (recsz <= 4) { *(__le64 *)ctx->metacur = cpu_to_le64(pstart); ctx->metacur += sizeof(__le64); } nexts = 0; lstart = 0; list_for_each_entry_safe(ei, n, &ctx->extents, list) { struct z_erofs_extent de; u32 fmt, plen; plen = ei->e.plen; if (!plen) { plen = inode->fragmentoff; ei->e.pstart = inode->fragmentoff >> 32; } else { fmt = ei->e.raw ? 0 : inode->z_algorithmtype[0] + 1; plen |= fmt << Z_EROFS_EXTENT_PLEN_FMT_BIT; if (ei->e.partial) plen |= Z_EROFS_EXTENT_PLEN_PARTIAL; } de = (struct z_erofs_extent) { .plen = cpu_to_le32(plen), .pstart_lo = cpu_to_le32(ei->e.pstart), .lstart_lo = cpu_to_le32(lstart), .pstart_hi = cpu_to_le32(ei->e.pstart >> 32), .lstart_hi = cpu_to_le32(lstart >> 32), }; memcpy(ctx->metacur, &de, recsz); ctx->metacur += recsz; lstart += ei->e.length; list_del(&ei->list); free(ei); } inode->datalayout = EROFS_INODE_COMPRESSED_FULL; inode->z_advise |= Z_EROFS_ADVISE_EXTENTS | ((ilog2(recsz) - 2) << Z_EROFS_ADVISE_EXTRECSZ_BIT); return metabuf; } static void *z_erofs_write_indexes(struct z_erofs_compress_ictx *ctx) { const struct erofs_importer_params *params = ctx->im->params; struct erofs_inode *inode = ctx->inode; struct erofs_sb_info *sbi = inode->sbi; struct z_erofs_extent_item *ei, *n; void *metabuf; /* TODO: support writing encoded extents for ztailpacking later. */ if (erofs_sb_has_48bit(sbi) && !inode->idata_size) { metabuf = z_erofs_write_extents(ctx); if (metabuf != ERR_PTR(-EAGAIN)) { if (IS_ERR(metabuf)) return metabuf; goto out; } } /* * 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 (!params->no_zcompact && !ctx->dedupe && inode->z_lclusterbits <= 14) { if (inode->z_lclusterbits <= 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) { DBG_BUGON(ei->list.next != &ctx->extents && ctx->clusterofs + ei->e.length < erofs_blksiz(sbi)); z_erofs_write_full_indexes(ctx, &ei->e); list_del(&ei->list); free(ei); } z_erofs_fini_full_indexes(ctx); out: 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; const struct erofs_importer_params *params = ictx->im->params; struct erofs_inode *inode = ictx->inode; bool frag = params->fragments && !erofs_is_packed_inode(inode) && !erofs_is_metabox_inode(inode) && ctx->seg_idx >= ictx->seg_num - 1; struct erofs_vfile *vf = ictx->vf; int ret; DBG_BUGON(offset != -1 && frag && inode->fragment_size); if (offset != -1 && frag && !inode->fragment_size && params->fragdedupe != EROFS_FRAGDEDUPE_OFF) { ret = erofs_fragment_findmatch(inode, vf, ictx->fpos, ictx->tofh); if (ret < 0) return ret; if (inode->fragment_size > ctx->remaining) inode->fragment_size = ctx->remaining; ctx->remaining -= inode->fragment_size; } ctx->pstart = pstart; ctx->poff = 0; while (ctx->remaining) { const u64 rx = min_t(u64, ctx->remaining, Z_EROFS_COMPR_QUEUE_SZ - ctx->tail); int ret; ret = (offset == -1 ? erofs_io_read(vf, ctx->queue + ctx->tail, rx) : erofs_io_pread(vf, 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; const struct erofs_importer_params *params = ictx->im->params; struct erofs_sb_info *sbi = inode->sbi; unsigned int legacymetasize, bbits = sbi->blkszbits; u8 *compressmeta; int ret; if (inode->fragment_size) { ret = erofs_fragment_commit(inode, ictx->tofh); if (ret) { erofs_err("failed to commit fragment for %s: %s", inode->i_srcpath, erofs_strerror(ret)); goto err_free_idata; } inode->z_advise |= Z_EROFS_ADVISE_FRAGMENT_PCLUSTER; erofs_sb_set_fragments(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_ext_commit(true); 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 (!params->fragments && params->dedupe != EROFS_DEDUPE_FORCE_ON) 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; 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; } 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) continue; 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 zsetno = container_of(cwork->zset, struct erofs_compress_cfg, zset) - sbi->zmgr->ccfg; struct erofs_compress_cfg *lc = &tls->ccfg[zsetno]; int ret; if (__erofs_unlikely(!lc->enable)) { unsigned int pclustersize_max = ictx->im->params->pclusterblks_max << sbi->blkszbits; lc->zset = *cwork->zset; ret = erofs_compressor_init(sbi, &lc->handle, &lc->zset, pclustersize_max); if (ret) goto out; lc->algorithmtype = z_erofs_get_compress_algorithm_id(&lc->handle); lc->enable = true; } sctx->pclustersize = z_erofs_get_pclustersize(ictx); DBG_BUGON(sctx->pclustersize > Z_EROFS_PCLUSTER_MAX_SIZE); sctx->queue = tls->queue; sctx->destbuf = tls->destbuf; sctx->chandle = &lc->handle; erofs_compressor_reset(sctx->chandle); sctx->membuf = malloc(round_up(sctx->remaining, erofs_blksiz(sbi))); if (!sctx->membuf) { ret = -ENOMEM; goto out; } 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); } void z_erofs_mt_f_workfn(struct erofs_work *work, void *tlsp) { struct erofs_compress_work *cwork = (struct erofs_compress_work *)work; struct erofs_sb_info *sbi = cwork->ctx.ictx->inode->sbi; u32 tofh = cwork->ctx.ictx->tofh; struct z_erofs_compress_fslot *fs = &sbi->zmgr->fslot[tofh & 1023]; while (1) { z_erofs_mt_workfn(work, tlsp); pthread_mutex_lock(&fs->lock); if (list_empty(&fs->pending)) { fs->inprogress = false; pthread_mutex_unlock(&fs->lock); break; } cwork = list_first_entry(&fs->pending, struct erofs_compress_work, ctx.sibling); list_del(&cwork->ctx.sibling); pthread_mutex_unlock(&fs->lock); init_list_head(&cwork->ctx.extents); work = &cwork->work; } } int z_erofs_merge_segment(struct z_erofs_compress_ictx *ictx, struct z_erofs_compress_sctx *sctx) { struct z_erofs_extent_item *ei, *n; const struct erofs_importer_params *params = ictx->im->params; struct erofs_sb_info *sbi = ictx->inode->sbi; bool dedupe_ext = params->fragments; erofs_off_t off = 0; int ret = 0, ret2; erofs_off_t dpo; 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) { dpo = z_erofs_dedupe_ext_match(sbi, sctx->membuf + off, ei->e.plen, ei->e.raw, &hash); if (dpo) { ei->e.pstart = dpo; 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; } } erofs_dbg("Writing %u %scompressed data of %s to %llu", ei->e.length, ei->e.raw ? "un" : "", ictx->inode->i_srcpath, ei->e.pstart); 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->zset = &ccfg->zset; cur->errcode = 1; /* mark as "in progress" */ if (i >= nsegs - 1) { struct z_erofs_mgr *zmgr = inode->sbi->zmgr; cur->ctx.remaining = inode->i_size - inode->fragment_size - (u64)i * segsz; if (zmgr->fslot[0].pending.next && ictx->tofh != ~0U) { struct z_erofs_compress_fslot *fs = &zmgr->fslot[ictx->tofh & 1023]; pthread_mutex_lock(&fs->lock); if (fs->inprogress) { list_add_tail(&cur->ctx.sibling, &fs->pending); } else { fs->inprogress = true; cur->work.fn = z_erofs_mt_f_workfn; erofs_queue_work(&z_erofs_mt_ctrl.wq, &cur->work); } pthread_mutex_unlock(&fs->lock); continue; } } else { cur->ctx.remaining = segsz; } cur->work.fn = z_erofs_mt_workfn; 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: free(ictx); return ret; } static int z_erofs_mt_global_init(struct erofs_importer *im) { static erofs_atomic_bool_t __initonce; struct erofs_importer_params *params = im->params; unsigned int workers = cfg.c_mt_workers; int ret; if (erofs_atomic_test_and_set(&__initonce)) return 0; z_erofs_mt_enabled = false; if (workers < 1) return 0; /* XXX: `dedupe` is actually not a global option here. */ if (workers >= 1 && params->dedupe == EROFS_DEDUPE_FORCE_ON) { erofs_warn("multi-threaded dedupe is NOT implemented for now"); cfg.c_mt_workers = 0; } else { 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) { erofs_atomic_set(&__initonce, 0); return ret; } z_erofs_mt_enabled = true; } pthread_mutex_init(&g_ictx.mutex, NULL); pthread_cond_init(&g_ictx.cond, NULL); return 0; } int z_erofs_mt_global_exit(void) { struct erofs_compress_work *n; int ret; if (z_erofs_mt_enabled) { ret = erofs_destroy_workqueue(&z_erofs_mt_ctrl.wq); if (ret) return ret; while (z_erofs_mt_ctrl.idle) { n = z_erofs_mt_ctrl.idle->next; free(z_erofs_mt_ctrl.idle); z_erofs_mt_ctrl.idle = n; } z_erofs_mt_enabled = false; } return 0; } #else static int z_erofs_mt_global_init(struct erofs_importer *im) { z_erofs_mt_enabled = false; return 0; } int z_erofs_mt_global_exit(void) { return 0; } #endif void *erofs_prepare_compressed_file(struct erofs_importer *im, struct erofs_inode *inode) { const struct erofs_importer_params *params = im->params; struct erofs_sb_info *sbi = inode->sbi; struct z_erofs_compress_ictx *ictx; bool frag = params->fragments && !erofs_is_packed_inode(inode) && !erofs_is_metabox_inode(inode); bool all_fragments = params->all_fragments && frag; /* initialize per-file compression setting */ inode->z_advise = 0; inode->z_lclusterbits = sbi->blkszbits; #ifndef NDEBUG if (cfg.c_random_algorithms) { while (1) { inode->z_algorithmtype[0] = rand() % EROFS_MAX_COMPR_CFGS; if (sbi->zmgr->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); while (g_ictx.seg_num) { if (g_ictx.seg_num == INT_MAX) { pthread_mutex_unlock(&g_ictx.mutex); return ERR_PTR(-ECHILD); } pthread_cond_wait(&g_ictx.cond, &g_ictx.mutex); } g_ictx.seg_num = 1; pthread_mutex_unlock(&g_ictx.mutex); #endif ictx = &g_ictx; } else { ictx = malloc(sizeof(*ictx)); if (!ictx) return ERR_PTR(-ENOMEM); } ictx->im = im; ictx->inode = inode; if (erofs_is_metabox_inode(inode)) ictx->ccfg = &sbi->zmgr->ccfg[cfg.c_mkfs_metabox_algid]; else ictx->ccfg = &sbi->zmgr->ccfg[inode->z_algorithmtype[0]]; inode->z_algorithmtype[0] = ictx->ccfg->algorithmtype; inode->z_algorithmtype[1] = 0; if (params->max_compressed_extent_size == EROFS_COMPRESSED_EXTENT_UNSPECIFIED) { if (erofs_sb_has_48bit(sbi) && ictx->ccfg->handle.alg->c->compress) { ictx->max_compressed_extent_size = z_erofs_get_pclustersize(ictx); ictx->data_unaligned = true; } else { ictx->max_compressed_extent_size = UINT32_MAX; ictx->data_unaligned = false; } } else if (params->max_compressed_extent_size <= (s32)z_erofs_get_pclustersize(ictx)) { if (params->max_compressed_extent_size < 0) { ictx->max_compressed_extent_size = -params->max_compressed_extent_size; if (!erofs_sb_has_48bit(sbi)) { erofs_err("Unaligned compressed extents must be used with the 48bit encoded extent layout", ictx->max_compressed_extent_size); free(ictx); return ERR_PTR(-EINVAL); } ictx->data_unaligned = true; } else { ictx->max_compressed_extent_size = params->max_compressed_extent_size; if (erofs_sb_has_48bit(sbi)) ictx->data_unaligned = true; } if (ictx->max_compressed_extent_size < erofs_blksiz(sbi)) { erofs_err("Maximum compressed extent size (%u) must be at least the block size (%u)", ictx->max_compressed_extent_size, erofs_blksiz(sbi)); return ERR_PTR(-EINVAL); } } else { ictx->max_compressed_extent_size = params->max_compressed_extent_size; ictx->data_unaligned = false; } if (params->fragments && params->dedupe != EROFS_DEDUPE_FORCE_ON && !ictx->data_unaligned) inode->z_advise |= Z_EROFS_ADVISE_INTERLACED_PCLUSTER; init_list_head(&ictx->extents); ictx->fix_dedupedfrag = false; ictx->fragemitted = false; ictx->dedupe = false; return ictx; } void erofs_bind_compressed_file_with_fd(struct z_erofs_compress_ictx *ictx, int fd, u64 fpos) { ictx->_vf = (struct erofs_vfile){ .fd = fd }; ictx->vf = &ictx->_vf; ictx->fpos = fpos; } int erofs_begin_compressed_file(struct z_erofs_compress_ictx *ictx) { const struct erofs_importer_params *params = ictx->im->params; struct erofs_inode *inode = ictx->inode; bool frag = params->fragments && !erofs_is_packed_inode(inode) && !erofs_is_metabox_inode(inode); bool all_fragments = params->all_fragments && frag; int ret; if (frag) { ictx->tofh = z_erofs_fragments_tofh(inode, ictx->vf, ictx->fpos); if (ictx == &g_ictx && params->fragdedupe != EROFS_FRAGDEDUPE_OFF) { /* * Handle tails in advance to avoid writing duplicated * parts into the packed inode. */ ret = erofs_fragment_findmatch(inode, ictx->vf, ictx->fpos, ictx->tofh); if (ret < 0) goto err_out; if (params->fragdedupe == EROFS_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; } } } if (all_fragments && !inode->fragment_size) { ret = erofs_pack_file_from_fd(inode, ictx->vf, ictx->fpos, ictx->tofh); if (ret) goto err_out; } #ifdef EROFS_MT_ENABLED if (ictx != &g_ictx) { ret = z_erofs_mt_compress(ictx); if (ret) goto err_out; } #endif return 0; err_out: if (inode->idata) { free(inode->idata); inode->idata = NULL; } if (ictx != &g_ictx) free(ictx); return 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_pclustersize(ictx), }; 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 = ret < 0 ? INT_MAX : 0; pthread_cond_signal(&ictx->cond); pthread_mutex_unlock(&ictx->mutex); #endif return ret; } int erofs_begin_compress_dir(struct erofs_importer *im, struct erofs_inode *inode) { if (!im->params->compress_dir || inode->i_size < Z_EROFS_LEGACY_MAP_HEADER_SIZE) return -ENOSPC; inode->z_advise |= Z_EROFS_ADVISE_FRAGMENT_PCLUSTER; erofs_sb_set_fragments(inode->sbi); inode->datalayout = EROFS_INODE_COMPRESSED_FULL; inode->extent_isize = Z_EROFS_LEGACY_MAP_HEADER_SIZE; inode->compressmeta = NULL; return 0; } int erofs_write_compress_dir(struct erofs_inode *inode, struct erofs_vfile *vf) { void *compressmeta; int err; if (inode->datalayout != EROFS_INODE_COMPRESSED_FULL || inode->extent_isize < Z_EROFS_LEGACY_MAP_HEADER_SIZE) { DBG_BUGON(1); return -EINVAL; } err = erofs_pack_file_from_fd(inode, vf, 0, ~0U); if (err || !inode->fragment_size) return err; err = erofs_fragment_commit(inode, ~0); if (err) return err; compressmeta = calloc(1, Z_EROFS_LEGACY_MAP_HEADER_SIZE); if (!compressmeta) return -ENOMEM; *(__le64 *)compressmeta = cpu_to_le64(inode->fragmentoff | 1ULL << 63); inode->compressmeta = compressmeta; return 0; } static int z_erofs_build_compr_cfgs(struct erofs_importer *im, u32 *max_dict_size) { struct erofs_sb_info *sbi = im->sbi; struct erofs_buffer_head *bh = sbi->bh_sb; 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 = im->params->pclusterblks_max, } }; 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_importer *im) { const struct erofs_importer_params *params = im->params; struct erofs_sb_info *sbi = im->sbi; unsigned int pclustersize_max = params->pclusterblks_max << sbi->blkszbits; u32 max_dict_size[Z_EROFS_COMPRESSION_MAX] = {}; u32 available_compr_algs = 0; bool newzmgr __maybe_unused = false; int i, ret, id; if (!sbi->zmgr) { sbi->zmgr = calloc(1, sizeof(*sbi->zmgr)); if (!sbi->zmgr) return -ENOMEM; newzmgr = true; } for (i = 0; params->z_paramsets[i].alg; ++i) { struct erofs_compress_cfg *ccfg = &sbi->zmgr->ccfg[i]; struct erofs_compress *c = &ccfg->handle; const struct z_erofs_paramset *zset = ¶ms->z_paramsets[i]; if (ccfg->enable) return -EINVAL; ccfg->zset = *zset; ccfg->zset.alg = strdup(zset->alg); if (!ccfg->zset.alg) return -ENOMEM; if (zset->extraopts) { ccfg->zset.extraopts = strdup(zset->extraopts); if (!ccfg->zset.extraopts) return -ENOMEM; } ret = erofs_compressor_init(sbi, c, &ccfg->zset, pclustersize_max); if (ret) return ret; id = z_erofs_get_compress_algorithm_id(c); ccfg->algorithmtype = id; ccfg->enable = true; available_compr_algs |= 1 << id; if (id != 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 (!available_compr_algs) return 0; if (sbi->available_compr_algs) { 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 & BIT(Z_EROFS_COMPRESSION_LZ4)) && sbi->lz4.max_pclusterblks < params->pclusterblks_max) { erofs_err("pcluster size (%u blocks) cannot increase on incremental builds", params->pclusterblks_max); return -EOPNOTSUPP; } } else { sbi->available_compr_algs = available_compr_algs; erofs_sb_set_lz4_0padding(sbi); if (available_compr_algs & ~(1 << Z_EROFS_COMPRESSION_LZ4)) erofs_sb_set_compr_cfgs(sbi); } /* * if big pcluster is enabled, an extra CBLKCNT lcluster index needs * to be loaded in order to get those compressed block counts. */ if (params->pclusterblks_max > 1) { if (pclustersize_max > Z_EROFS_PCLUSTER_MAX_SIZE) { erofs_err("pcluster size (%u blocks) is too large", params->pclusterblks_max); return -EINVAL; } erofs_sb_set_big_pcluster(sbi); } if (params->pclusterblks_packed > params->pclusterblks_max) { erofs_err("pcluster size (%u blocks) for packed inode exceeds maximum", params->pclusterblks_packed); return -EINVAL; } if (params->pclusterblks_metabox > (s32)params->pclusterblks_max) { erofs_err("pcluster size (%u blocks) for metabox inode exceeds maximum", params->pclusterblks_metabox, params->pclusterblks_max); return -EINVAL; } if (sbi->bh_sb && erofs_sb_has_compr_cfgs(sbi)) { ret = z_erofs_build_compr_cfgs(im, max_dict_size); if (ret) return ret; } ret = z_erofs_mt_global_init(im); if (ret) return ret; #ifdef EROFS_MT_ENABLED if (params->fragments && cfg.c_mt_workers > 1 && newzmgr) { for (i = 0; i < ARRAY_SIZE(sbi->zmgr->fslot); ++i) { init_list_head(&sbi->zmgr->fslot[i].pending); pthread_mutex_init(&sbi->zmgr->fslot[i].lock, NULL); } } #endif return 0; } int z_erofs_compress_exit(struct erofs_sb_info *sbi) { int i, ret; /* If `zmgr` is uninitialized, return directly. */ if (!sbi->zmgr) return 0; for (i = 0; i < ARRAY_SIZE(sbi->zmgr->ccfg); ++i) { if (!sbi->zmgr->ccfg[i].enable) continue; ret = erofs_compressor_exit(&sbi->zmgr->ccfg[i].handle); if (ret) return ret; free(sbi->zmgr->ccfg[i].zset.alg); free(sbi->zmgr->ccfg[i].zset.extraopts); } free(sbi->zmgr); return 0; } erofs-utils-1.9.1/lib/compress_hints.c000066400000000000000000000075071515160260000177550ustar00rootroot00000000000000// 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_importer *im, struct erofs_inode *inode) { const struct erofs_importer_params *params = im->params; unsigned int pclusterblks = params->pclusterblks_def; unsigned int algorithmtype; struct erofs_compress_hints *r; const char *s; if (inode->z_physical_clusterblks) return true; s = erofs_fspath(inode->i_srcpath); 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_importer *im, struct erofs_sb_info *sbi) { struct erofs_importer_params *params = im->params; char buf[PATH_MAX + 100]; FILE *f; unsigned int line, max_pclusterblks = 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, pclusterblks, 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 || !params->z_paramsets[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 pclustersize (%u blocks)", pclustersize, params->pclusterblks_max); continue; } pclusterblks = pclustersize >> sbi->blkszbits; erofs_insert_compress_hints(pattern, pclusterblks, ccfg); if (pclusterblks > max_pclusterblks) max_pclusterblks = pclusterblks; } if (params->pclusterblks_max < max_pclusterblks) { erofs_warn("update max pclustersize to %u blocks", max_pclusterblks); params->pclusterblks_max = max_pclusterblks; } out: fclose(f); return ret; } erofs-utils-1.9.1/lib/compressor.c000066400000000000000000000104351515160260000171030ustar00rootroot00000000000000// 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_compress(const struct erofs_compress *c, const void *src, unsigned int srcsize, void *dst, unsigned int dstcapacity) { DBG_BUGON(!c->alg); if (!c->alg->c->compress) return -EOPNOTSUPP; return c->alg->c->compress(c, src, srcsize, dst, dstcapacity); } int erofs_compressor_exit(struct erofs_compress *c) { if (c->alg && c->alg->c->exit) return c->alg->c->exit(c); return 0; } int erofs_compressor_init(struct erofs_sb_info *sbi, struct erofs_compress *c, const struct z_erofs_paramset *zset, u32 pclustersize_max) { 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; ret = -EINVAL; for (i = 0; i < ARRAY_SIZE(erofs_algs); ++i) { if (strcmp(zset->alg, erofs_algs[i].name)) continue; if (!erofs_algs[i].c) continue; if (!erofs_algs[i].c->setlevel && zset->clevel >= 0) { erofs_err("compression level %d is not supported for %s", zset->clevel, zset->alg); return -EINVAL; } if (!erofs_algs[i].c->setdictsize && zset->dict_size) { erofs_err("unsupported dict size for %s", zset->alg); return -EINVAL; } if (!erofs_algs[i].c->setextraopts && zset->extraopts) { erofs_err("invalid compression option %s for %s", zset->extraopts, zset->alg); return -EINVAL; } if (erofs_algs[i].c->preinit) { ret = erofs_algs[i].c->preinit(c); if (ret) return ret; } if (erofs_algs[i].c->setlevel) { ret = erofs_algs[i].c->setlevel(c, zset->clevel); if (ret) { erofs_err("failed to set compression level %d for %s", zset->clevel, zset->alg); goto fail; } } if (erofs_algs[i].c->setdictsize) { ret = erofs_algs[i].c->setdictsize(c, zset->dict_size, pclustersize_max); if (ret) { erofs_err("failed to set dict size %u for %s", zset->dict_size, zset->alg); goto fail; } } if (zset->extraopts && erofs_algs[i].c->setextraopts) { ret = erofs_algs[i].c->setextraopts(c, zset->extraopts); if (ret) goto fail; } if (erofs_algs[i].c->init) { ret = erofs_algs[i].c->init(c); if (ret) goto fail; } c->alg = &erofs_algs[i]; return 0; } erofs_err("Cannot find a valid compressor %s", zset->alg); return ret; fail: if (erofs_algs[i].c->preinit && erofs_algs[i].c->exit) erofs_algs[i].c->exit(c); return ret; } void erofs_compressor_reset(struct erofs_compress *c) { if (c->alg && c->alg->c->reset) c->alg->c->reset(c); } erofs-utils-1.9.1/lib/compressor.h000066400000000000000000000046651515160260000171200ustar00rootroot00000000000000/* 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 (*preinit)(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, u32 pclustersize_max); int (*setextraopts)(struct erofs_compress *c, const char *extraopts); int (*init)(struct erofs_compress *c); int (*compress_destsize)(const struct erofs_compress *c, const void *src, unsigned int *srcsize, void *dst, unsigned int dstsize); int (*compress)(const struct erofs_compress *c, const void *src, unsigned int srcsize, void *dst, unsigned int dstcapacity); }; 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_compress(const struct erofs_compress *c, const void *src, unsigned int srcsize, void *dst, unsigned int dstcapacity); int erofs_compressor_init(struct erofs_sb_info *sbi, struct erofs_compress *c, const struct z_erofs_paramset *zset, u32 pclustersize_max); int erofs_compressor_exit(struct erofs_compress *c); void erofs_compressor_reset(struct erofs_compress *c); #endif erofs-utils-1.9.1/lib/compressor_deflate.c000066400000000000000000000045521515160260000205720ustar00rootroot00000000000000// 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) { 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); 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, u32 pclustersize_max) { 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.9.1/lib/compressor_libdeflate.c000066400000000000000000000106621515160260000212600ustar00rootroot00000000000000// 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; u8 *fitblk_buffer; unsigned int fitblk_bufsiz; size_t last_uncompressed_size; }; static int libdeflate_compress(const struct erofs_compress *c, const void *src, unsigned int srcsize, void *dst, unsigned int dstcapacity) { struct erofs_libdeflate_context *ctx = c->private_data; size_t csize; csize = libdeflate_deflate_compress(ctx->strm, src, srcsize, dst, dstcapacity); if (!csize) return -ENOSPC; /* See the comment in libdeflate_compress_destsize() */ if (!((u8 *)dst)[0]) ((u8 *)dst)[0] = 1 << (2 + 1); return csize; } 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; if (dstsize + 9 > ctx->fitblk_bufsiz) { u8 *buf = realloc(ctx->fitblk_buffer, dstsize + 9); if (!buf) return -ENOMEM; ctx->fitblk_bufsiz = dstsize + 9; ctx->fitblk_buffer = buf; } 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, ctx->fitblk_buffer, dstsize + 9); /*printf("Tried %zu => %zu\n", m, csize);*/ if (csize > 0 && csize <= dstsize) { /* Fits */ memcpy(dst, ctx->fitblk_buffer, 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->fitblk_buffer); free(ctx); return 0; } static int compressor_libdeflate_init(struct erofs_compress *c) { 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; 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 = libdeflate_compress, .compress_destsize = libdeflate_compress_destsize, }; erofs-utils-1.9.1/lib/compressor_liblzma.c000066400000000000000000000103451515160260000206150ustar00rootroot00000000000000// 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); c->private_data = NULL; return 0; } static int erofs_compressor_liblzma_preinit(struct erofs_compress *c) { struct erofs_liblzma_context *ctx; ctx = malloc(sizeof(*ctx)); if (!ctx) return -ENOMEM; ctx->strm = (lzma_stream)LZMA_STREAM_INIT; DBG_BUGON(c->private_data); c->private_data = ctx; return 0; } static int erofs_compressor_liblzma_setlevel(struct erofs_compress *c, int compression_level) { struct erofs_liblzma_context *ctx = c->private_data; u32 preset; if (compression_level > erofs_compressor_lzma.best_level) { erofs_err("invalid compression level %d", compression_level); return -EINVAL; } if (compression_level < 0) preset = LZMA_PRESET_DEFAULT; else if (compression_level >= 100) preset = (compression_level - 100) | LZMA_PRESET_EXTREME; else preset = compression_level; if (lzma_lzma_preset(&ctx->opt, preset)) return -EINVAL; c->compression_level = compression_level; return 0; } static int erofs_compressor_liblzma_setdictsize(struct erofs_compress *c, u32 dict_size, u32 pclustersize_max) { struct erofs_liblzma_context *ctx = c->private_data; 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, 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; } ctx->opt.dict_size = c->dict_size = dict_size; return 0; } static int erofs_compressor_liblzma_setextraopts(struct erofs_compress *c, const char *extraopts) { struct erofs_liblzma_context *ctx = c->private_data; const char *token, *next; for (token = extraopts; *token != '\0'; token = next) { const char *p = strchr(token, ','); const char *rhs; char *endptr; unsigned long val; uint32_t *key; next = NULL; if (p) { next = p + 1; } else { p = token + strlen(token); next = p; } if (!strncmp(token, "lc=", sizeof("lc=") - 1)) { key = &ctx->opt.lc; rhs = token + sizeof("lc=") - 1; } else if (!strncmp(token, "lp=", sizeof("lp=") - 1)) { key = &ctx->opt.lp; rhs = token + sizeof("lp=") - 1; } else if (!strncmp(token, "pb=", sizeof("pb=") - 1)) { key = &ctx->opt.pb; rhs = token + sizeof("pb=") - 1; } else { erofs_err("unknown extra options %s", extraopts); return -EINVAL; } val = strtoul(rhs, &endptr, 0); if (val == ULONG_MAX || endptr != p) { erofs_err("invalid option %.*s", p - token, token); return -EINVAL; } *key = val; } 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, .preinit = erofs_compressor_liblzma_preinit, .exit = erofs_compressor_liblzma_exit, .setlevel = erofs_compressor_liblzma_setlevel, .setdictsize = erofs_compressor_liblzma_setdictsize, .setextraopts = erofs_compressor_liblzma_setextraopts, .compress_destsize = erofs_liblzma_compress_destsize, }; #endif erofs-utils-1.9.1/lib/compressor_libzstd.c000066400000000000000000000116611515160260000206400ustar00rootroot00000000000000// 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" struct erofs_libzstd_context { ZSTD_CCtx *cctx; u8 *fitblk_buffer; unsigned int fitblk_bufsiz; }; static int libzstd_compress(const struct erofs_compress *c, const void *src, unsigned int srcsize, void *dst, unsigned int dstcapacity) { struct erofs_libzstd_context *ctx = c->private_data; size_t csize; csize = ZSTD_compress2(ctx->cctx, dst, dstcapacity, src, srcsize); if (ZSTD_isError(csize)) { if (ZSTD_getErrorCode(csize) == ZSTD_error_dstSize_tooSmall) return -ENOSPC; erofs_err("Zstd compress failed: %s", ZSTD_getErrorName(csize)); return -EFAULT; } return csize; } static int libzstd_compress_destsize(const struct erofs_compress *c, const void *src, unsigned int *srcsize, void *dst, unsigned int dstsize) { struct erofs_libzstd_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; if (dstsize + 32 > ctx->fitblk_bufsiz) { u8 *buf = realloc(ctx->fitblk_buffer, dstsize + 32); if (!buf) return -ENOMEM; ctx->fitblk_bufsiz = dstsize + 32; ctx->fitblk_buffer = buf; } m = dstsize * 4; for (;;) { size_t csize; m = max(m, l + 1); m = min(m, r - 1); csize = ZSTD_compress2(ctx->cctx, ctx->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, ctx->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) { struct erofs_libzstd_context *ctx = c->private_data; if (!ctx) return -EINVAL; free(ctx->fitblk_buffer); ZSTD_freeCCtx(ctx->cctx); free(ctx); 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, u32 pclustersize_max) { 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, pclustersize_max << 3); dict_size = 1U << ilog2(dict_size); } } if (dict_size != 1U << 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) { struct erofs_libzstd_context *ctx = c->private_data; static erofs_atomic_bool_t __warnonce; ZSTD_CCtx *cctx; size_t errcode; int err; if (ctx) { ZSTD_freeCCtx(ctx->cctx); ctx->cctx = NULL; c->private_data = NULL; } else { ctx = calloc(1, sizeof(*ctx)); if (!ctx) return -ENOMEM; } cctx = ZSTD_createCCtx(); if (!cctx) { err = -ENOMEM; goto out_err; } err = -EINVAL; errcode = ZSTD_CCtx_setParameter(cctx, ZSTD_c_compressionLevel, c->compression_level); if (ZSTD_isError(errcode)) { erofs_err("failed to set compression level: %s", ZSTD_getErrorName(errcode)); goto out_err; } errcode = ZSTD_CCtx_setParameter(cctx, ZSTD_c_windowLog, ilog2(c->dict_size)); if (ZSTD_isError(errcode)) { erofs_err("failed to set window log: %s", ZSTD_getErrorName(errcode)); goto out_err; } ctx->cctx = cctx; c->private_data = ctx; 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("If unaligned compression isn't used (without -E48bit), it will take more time in order to get the optimal result."); } return 0; out_err: ZSTD_freeCCtx(cctx); free(ctx); return err; } 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 = libzstd_compress, .compress_destsize = libzstd_compress_destsize, }; erofs-utils-1.9.1/lib/compressor_lz4.c000066400000000000000000000021001515160260000176620ustar00rootroot00000000000000// 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.9.1/lib/compressor_lz4hc.c000066400000000000000000000034251515160260000202100ustar00rootroot00000000000000// 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.9.1/lib/config.c000066400000000000000000000067201515160260000161560ustar00rootroot00000000000000// 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; 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) { #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); } 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.9.1/lib/data.c000066400000000000000000000304041515160260000156160ustar00rootroot00000000000000// 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 "liberofs_fragments.h" void *erofs_bread(struct erofs_buf *buf, erofs_off_t offset, bool need_kmap) { struct erofs_sb_info *sbi = buf->sbi; u32 blksiz = erofs_blksiz(sbi); struct erofs_vfile vfm, *vf; struct erofs_inode vi; erofs_blk_t blknr; int err; if (!need_kmap) return NULL; blknr = erofs_blknr(sbi, offset); if (blknr != buf->blocknr) { buf->blocknr = ~0ULL; vf = buf->vf; /* * TODO: introduce a metabox cache like the current fragment * cache to improve userspace metadata performance. */ if (!vf) { if (!erofs_sb_has_metabox(sbi)) return ERR_PTR(-EFSCORRUPTED); vi = (struct erofs_inode) { .sbi = sbi, .nid = sbi->metabox_nid }; err = erofs_read_inode_from_disk(&vi); if (!err) err = erofs_iopen(&vfm, &vi); if (err) return ERR_PTR(err); vf = &vfm; } err = erofs_pread(vf, buf->base, blksiz, round_down(offset, blksiz)); if (err) return ERR_PTR(err); buf->blocknr = blknr; } return buf->base + erofs_blkoff(sbi, offset); } void erofs_init_metabuf(struct erofs_buf *buf, struct erofs_sb_info *sbi, bool in_mbox) { buf->sbi = sbi; buf->vf = in_mbox ? NULL : &sbi->bdev; } void *erofs_read_metabuf(struct erofs_buf *buf, struct erofs_sb_info *sbi, erofs_off_t offset, bool in_mbox) { erofs_init_metabuf(buf, sbi, in_mbox); return erofs_bread(buf, offset, true); } 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; unsigned int unit, blksz = 1 << sbi->blkszbits; struct erofs_inode_chunk_index *idx; u8 buf[EROFS_MAX_BLOCK_SIZE]; erofs_blk_t startblk, addrmask, nblocks; bool tailpacking; erofs_off_t pos; u64 chunknr; int err = 0; map->m_deviceid = 0; map->m_flags = 0; if (map->m_la >= inode->i_size) goto out; if (vi->datalayout != EROFS_INODE_CHUNK_BASED) { tailpacking = (vi->datalayout == EROFS_INODE_FLAT_INLINE); if (!tailpacking && vi->u.i_blkaddr == EROFS_NULL_ADDR) { map->m_llen = inode->i_size - map->m_la; goto out; } nblocks = BLK_ROUND_UP(sbi, inode->i_size); pos = erofs_pos(sbi, nblocks - tailpacking); map->m_flags = EROFS_MAP_MAPPED; if (map->m_la < pos) { map->m_pa = erofs_pos(sbi, vi->u.i_blkaddr) + map->m_la; map->m_llen = pos - map->m_la; } else { map->m_pa = erofs_iloc(inode) + vi->inode_isize + vi->xattr_isize + erofs_blkoff(sbi, map->m_la); map->m_llen = inode->i_size - map->m_la; map->m_flags |= EROFS_MAP_META; } goto out; } 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; idx = (void *)buf + erofs_blkoff(sbi, pos); map->m_la = chunknr << vi->u.chunkbits; map->m_llen = min_t(erofs_off_t, 1ULL << vi->u.chunkbits, round_up(inode->i_size - map->m_la, blksz)); if (vi->u.chunkformat & EROFS_CHUNK_FORMAT_INDEXES) { addrmask = (vi->u.chunkformat & EROFS_CHUNK_FORMAT_48BIT) ? BIT_ULL(48) - 1 : BIT_ULL(32) - 1; startblk = (((u64)le16_to_cpu(idx->startblk_hi) << 32) | le32_to_cpu(idx->startblk_lo)) & addrmask; if ((startblk ^ EROFS_NULL_ADDR) & addrmask) { map->m_deviceid = le16_to_cpu(idx->device_id) & sbi->device_id_mask; map->m_pa = erofs_pos(sbi, startblk); map->m_flags = EROFS_MAP_MAPPED; } } else { startblk = le32_to_cpu(*(__le32 *)idx); if (startblk != EROFS_NULL_ADDR) { map->m_pa = erofs_pos(sbi, startblk); map->m_flags = EROFS_MAP_MAPPED; } } out: if (!err) { map->m_plen = map->m_llen; /* inline data should be located in the same meta block */ if ((map->m_flags & EROFS_MAP_META) && erofs_blkoff(sbi, map->m_pa) + map->m_plen > blksz) { erofs_err("inline data across blocks @ nid %llu", vi->nid); DBG_BUGON(1); return -EFSCORRUPTED; } } 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->uniaddr) continue; startoff = erofs_pos(sbi, dif->uniaddr); 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 = { .buf = __EROFS_BUF_INITIALIZER, }; 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_META) && erofs_inode_in_metabox(inode)) { struct erofs_buf buf = __EROFS_BUF_INITIALIZER; void *src; src = erofs_read_metabuf(&buf, inode->sbi, map.m_pa, true); if (IS_ERR(src)) return PTR_ERR(src); memcpy(estart, src, eend - ptr); erofs_put_metabuf(&buf); ptr = eend; continue; } else 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) { if (__erofs_unlikely(inode->nid == sbi->packed_nid)) { erofs_err("fragment should not exist in the packed inode %llu", sbi->packed_nid | 0ULL); return -EFSCORRUPTED; } 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 = { .buf = __EROFS_BUF_INITIALIZER, }; 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; } ssize_t erofs_preadi(struct erofs_vfile *vf, void *buf, size_t len, u64 offset) { struct erofs_inode *inode = *(struct erofs_inode **)vf->payload; if (erofs_inode_is_data_compressed(inode->datalayout)) return z_erofs_read_data(inode, buf, len, offset) ?: len; return erofs_read_raw_data(inode, buf, len, offset) ?: len; } int erofs_iopen(struct erofs_vfile *vf, struct erofs_inode *inode) { static struct erofs_vfops ops = { .pread = erofs_preadi, }; vf->ops = &ops; *(struct erofs_inode **)vf->payload = inode; return 0; } 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 }; struct erofs_vfile vf; __le16 __len; int ret, len; char *buffer; ret = erofs_read_inode_from_disk(&vi); if (ret) return ERR_PTR(ret); ret = erofs_iopen(&vf, &vi); if (ret) return ERR_PTR(ret); *offset = round_up(*offset, 4); ret = erofs_pread(&vf, (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(&vf, 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.9.1/lib/decompress.c000066400000000000000000000360241515160260000170550ustar00rootroot00000000000000// 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" #if defined(HAVE_LIBZSTD) || defined(HAVE_QPL) || defined(HAVE_LIBDEFLATE) || \ defined(HAVE_ZLIB) || defined(HAVE_LIBLZMA) || defined(LZ4_ENABLED) 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; } #endif #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; if (rq->decodedlength > rq->inputsize) return -EOPNOTSUPP; if (rq->decodedlength < rq->decodedskip) return -EFSCORRUPTED; /* XXX: should support inputsize >= erofs_blksiz(sbi) later */ if (rq->inputsize > erofs_blksiz(sbi)) 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 -EOPNOTSUPP; if (rq->decodedlength < rq->decodedskip) return -EFSCORRUPTED; 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.9.1/lib/dedupe.c000066400000000000000000000116361515160260000161610ustar00rootroot00000000000000// 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->prefix_xxh64 = xxh64(original_data, window_size, 0); di->hash = erofs_rolling_hash_init(original_data, window_size, true); /* 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->original_length = e->length; erofs_sha256(original_data, window_size, di->prefix_sha256); 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; 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.9.1/lib/dedupe_ext.c000066400000000000000000000042661515160260000170420ustar00rootroot00000000000000// 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_off_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 = NULL; 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) { if (!memb) { memb = malloc(len); if (!memb) break; } ret = erofs_dev_read(sbi, 0, memb, item->e.pstart, len); if (ret < 0 || memcmp(memb, encoded, len)) continue; free(memb); return item->e.pstart; } } free(memb); return 0; } 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.9.1/lib/dir.c000066400000000000000000000164221515160260000154670ustar00rootroot00000000000000// 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, erofs_off_t 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 %llu, index %lu", errmsg, ctx->dir->nid | 0ULL, lblk | 0ULL, (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; struct erofs_vfile vf; erofs_off_t pos; char buf[EROFS_MAX_BLOCK_SIZE]; int err = 0; if (!S_ISDIR(dir->i_mode)) return -ENOTDIR; ctx->flags &= ~EROFS_READDIR_ALL_SPECIAL_FOUND; if (dir->dot_omitted) ctx->flags |= EROFS_READDIR_DOT_FOUND; err = erofs_iopen(&vf, dir); if (err) return err; for (pos = 0; 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(&vf, buf, maxsize, pos); if (err) { erofs_err("I/O error when reading dirents @ nid %llu, lblk %llu: %s", dir->nid | 0ULL, lblk | 0ULL, erofs_strerror(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 %llu", nameoff, dir->nid | 0ULL, lblk | 0ULL); 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.9.1/lib/diskbuf.c000066400000000000000000000056321515160260000163410ustar00rootroot00000000000000// 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); } 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); 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; } free(dbufstrm); } erofs-utils-1.9.1/lib/exclude.c000066400000000000000000000051021515160260000163330ustar00rootroot00000000000000// 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.9.1/lib/fragments.c000066400000000000000000000342061515160260000166770ustar00rootroot00000000000000// SPDX-License-Identifier: GPL-2.0+ OR Apache-2.0 /* * Copyright (C), 2022, Coolpad Group Limited. * Created by Yue Hu */ #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/print.h" #include "erofs/internal.h" #include "erofs/bitops.h" #include "erofs/lock.h" #include "erofs/importer.h" #include "liberofs_compress.h" #include "liberofs_fragments.h" #include "liberofs_private.h" #ifdef HAVE_SYS_SENDFILE_H #include #endif struct erofs_fragmentitem { struct list_head list; u8 *data; erofs_off_t length, pos; }; #define EROFS_FRAGMENT_INMEM_SZ_MAX (256 * 1024) #define EROFS_TOF_HASHLEN 64 #define FRAGMENT_HASHSIZE 65536 #define FRAGMENT_HASH(c) ((c) & (FRAGMENT_HASHSIZE - 1)) struct erofs_fragment_bucket { struct list_head hash; erofs_rwsem_t lock; }; struct erofs_packed_inode { struct erofs_fragment_bucket *bks; int fd; unsigned long *uptodate; erofs_mutex_t mutex; u64 uptodate_bits; }; const char *erofs_frags_packedname = "packed_file"; u32 z_erofs_fragments_tofh(struct erofs_inode *inode, struct erofs_vfile *vf, erofs_off_t fpos) { u8 data_to_hash[EROFS_TOF_HASHLEN]; u32 hash; int ret; if (inode->i_size <= EROFS_TOF_HASHLEN) return ~0U; ret = erofs_io_pread(vf, data_to_hash, EROFS_TOF_HASHLEN, fpos + inode->i_size - EROFS_TOF_HASHLEN); if (ret < 0) return ret; if (ret != EROFS_TOF_HASHLEN) { DBG_BUGON(1); return -EIO; } hash = erofs_crc32c(~0, data_to_hash, EROFS_TOF_HASHLEN); return hash != ~0U ? hash : 0; } static erofs_off_t erofs_fragment_longmatch(struct erofs_inode *inode, struct erofs_fragmentitem *fi, erofs_off_t matched, struct erofs_vfile *vf, erofs_off_t fpos) { struct erofs_packed_inode *epi = inode->sbi->packedinode; erofs_off_t total = min_t(erofs_off_t, fi->length, inode->i_size); erofs_off_t pos; bool inmem = false; if (!fi->pos) { inmem = true; pos = fi->length - matched; } else { pos = fi->pos - matched; } while (matched < total) { char buf[2][16384]; unsigned int sz; if (__erofs_unlikely(!inmem && pos <= total - matched)) { DBG_BUGON(1); return matched; } sz = min_t(u64, total - matched, sizeof(buf[0])); if (erofs_io_pread(vf, buf[0], sz, fpos + inode->i_size - matched - sz) != sz) break; if (!inmem) { if (pread(epi->fd, buf[1], sz, pos - sz) != sz) break; if (memcmp(buf[0], buf[1], sz)) break; } else if (memcmp(buf[0], fi->data + pos - sz, sz)) { break; } pos -= sz; matched += sz; } return matched; } int erofs_fragment_findmatch(struct erofs_inode *inode, struct erofs_vfile *vf, erofs_off_t fpos, u32 tofh) { struct erofs_packed_inode *epi = inode->sbi->packedinode; struct erofs_fragmentitem *cur, *fi = NULL; struct erofs_fragment_bucket *bk = &epi->bks[FRAGMENT_HASH(tofh)]; unsigned int s1, e1; erofs_off_t deduped; u8 *data; int ret; if (inode->i_size <= EROFS_TOF_HASHLEN) return 0; if (list_empty(&bk->hash)) return 0; s1 = min_t(u64, EROFS_FRAGMENT_INMEM_SZ_MAX, inode->i_size); data = malloc(s1); if (!data) return -ENOMEM; ret = erofs_io_pread(vf, data, s1, fpos + inode->i_size - s1); if (ret != s1) { free(data); return -errno; } e1 = s1 - EROFS_TOF_HASHLEN; deduped = 0; erofs_down_read(&bk->lock); list_for_each_entry(cur, &bk->hash, list) { unsigned int e2, mn; erofs_off_t inmax, i; DBG_BUGON(cur->length <= EROFS_TOF_HASHLEN); if (cur->pos) inmax = min_t(u64, cur->length, EROFS_FRAGMENT_INMEM_SZ_MAX); else inmax = cur->length; e2 = inmax - 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); i = erofs_fragment_longmatch(inode, cur, s1, vf, fpos); } if (i <= deduped) continue; fi = cur; deduped = i; if (deduped == inode->i_size) break; } erofs_up_read(&bk->lock); free(data); if (deduped) { DBG_BUGON(!fi); inode->fragment_size = deduped; inode->fragment = fi; erofs_dbg("Dedupe %llu tail data of %s", inode->fragment_size | 0ULL, inode->i_srcpath); } return 0; } int erofs_fragment_pack(struct erofs_inode *inode, void *data, erofs_off_t pos, erofs_off_t len, u32 tofh, bool tail) { struct erofs_packed_inode *epi = inode->sbi->packedinode; struct erofs_fragment_bucket *bk = &epi->bks[FRAGMENT_HASH(tofh)]; struct erofs_fragmentitem *fi; bool inmem = (pos == ~0ULL); fi = malloc(sizeof(*fi)); if (!fi) return -ENOMEM; fi->length = len; if (!inmem) { pos += len; if (len > EROFS_FRAGMENT_INMEM_SZ_MAX) { if (!tail) data += len - EROFS_FRAGMENT_INMEM_SZ_MAX; len = EROFS_FRAGMENT_INMEM_SZ_MAX; } } fi->data = malloc(len); if (!fi->data) { free(fi); return -ENOMEM; } memcpy(fi->data, data, len); fi->pos = inmem ? 0 : pos; if (len > EROFS_TOF_HASHLEN) { list_add_tail(&fi->list, &bk->hash); } else { init_list_head(&fi->list); } inode->fragment = fi; inode->fragment_size = fi->length; erofs_dbg("Recording %llu fragment data of %s", fi->length | 0ULL, inode->i_srcpath); return 0; } int erofs_pack_file_from_fd(struct erofs_inode *inode, struct erofs_vfile *vf, erofs_off_t fpos, u32 tofh) { struct erofs_packed_inode *epi = inode->sbi->packedinode; s64 offset, rc, sz; char *memblock; bool onheap = false; if (__erofs_unlikely(!inode->i_size)) return 0; offset = lseek(epi->fd, 0, SEEK_CUR); if (offset < 0) return -errno; if (vf->ops) memblock = NULL; else memblock = mmap(NULL, inode->i_size, PROT_READ, MAP_SHARED, vf->fd, fpos); if (memblock == MAP_FAILED || !memblock) { erofs_off_t remaining = inode->i_size; struct erofs_vfile vout = { .fd = epi->fd }; bool noseek = vf->ops && !vf->ops->pread; off_t pos = fpos; do { sz = min_t(u64, remaining, UINT_MAX); rc = erofs_io_sendfile(&vout, vf, noseek ? NULL : &pos, sz); if (rc <= 0) break; remaining -= rc; } while (remaining); if (remaining && rc >= 0) { rc = -EIO; goto out; } sz = min_t(u64, inode->i_size, EROFS_FRAGMENT_INMEM_SZ_MAX); memblock = malloc(sz); if (!memblock) { rc = -ENOMEM; goto out; } onheap = true; rc = pread(epi->fd, memblock, sz, offset + inode->i_size - sz); if (rc != sz) { if (rc >= 0) { DBG_BUGON(1); rc = -EIO; } goto out; } } else { rc = __erofs_io_write(epi->fd, memblock, inode->i_size); if (rc != inode->i_size) { if (rc >= 0) rc = -EIO; goto out; } } rc = erofs_fragment_pack(inode, memblock, offset, inode->i_size, tofh, onheap); out: if (onheap) free(memblock); else if (memblock) munmap(memblock, inode->i_size); return rc; } int erofs_fragment_commit(struct erofs_inode *inode, u32 tofh) { struct erofs_packed_inode *epi = inode->sbi->packedinode; struct erofs_fragmentitem *fi = inode->fragment; erofs_off_t len = inode->fragment_size; unsigned int sz; s64 offset; int ret; if (!len) { DBG_BUGON(fi); return 0; } if (fi->pos) { inode->fragmentoff = fi->pos - len; return 0; } offset = lseek(epi->fd, 0, SEEK_CUR); if (offset < 0) return -errno; ret = write(epi->fd, fi->data, fi->length); if (ret != fi->length) { if (ret < 0) return -errno; return -EIO; } offset += fi->length; if (!list_empty(&fi->list)) { struct erofs_fragment_bucket *bk = &epi->bks[FRAGMENT_HASH(tofh)]; void *nb; sz = min_t(u64, fi->length, EROFS_FRAGMENT_INMEM_SZ_MAX); erofs_down_write(&bk->lock); memmove(fi->data, fi->data + fi->length - sz, sz); nb = realloc(fi->data, sz); if (!nb) { erofs_up_write(&bk->lock); fi->data = NULL; return -ENOMEM; } fi->data = nb; fi->pos = (erofs_off_t)offset; erofs_up_write(&bk->lock); inode->fragmentoff = fi->pos - len; return 0; } inode->fragmentoff = (erofs_off_t)offset - len; free(fi->data); free(fi); return 0; } int erofs_flush_packed_inode(struct erofs_importer *im) { struct erofs_sb_info *sbi = im->sbi; struct erofs_packed_inode *epi = sbi->packedinode; struct erofs_inode *inode; if (!epi || !erofs_sb_has_fragments(sbi)) return 0; if (lseek(epi->fd, 0, SEEK_CUR) <= 0) return 0; erofs_update_progressinfo("Processing packed data ..."); inode = erofs_mkfs_build_special_from_fd(im, 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_fragmentitem *fi, *n; struct erofs_fragment_bucket *bk; if (!epi) return; if (epi->uptodate) free(epi->uptodate); if (epi->bks) { for (bk = epi->bks; bk < &epi->bks[FRAGMENT_HASHSIZE]; ++bk) { list_for_each_entry_safe(fi, n, &bk->hash, list) { free(fi->data); free(fi); } } free(epi->bks); } 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->bks = malloc(sizeof(*epi->bks) * FRAGMENT_HASHSIZE); if (!epi->bks) { err = -ENOMEM; goto err_out; } for (i = 0; i < FRAGMENT_HASHSIZE; ++i) { init_list_head(&epi->bks[i].hash); erofs_init_rwsem(&epi->bks[i].lock); } } 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; } erofs_mutex_init(&epi->mutex); } 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); struct erofs_vfile vf; 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); err = erofs_iopen(&vf, pi); if (err) return ERR_PTR(err); 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(&vf, 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 = { .buf = __EROFS_BUF_INITIALIZER }; unsigned int bsz = erofs_blksiz(sbi); erofs_off_t end = pos + len; struct erofs_vfile vf; char *buffer = NULL; int err; if (!epi) { err = erofs_load_packedinode_from_disk(&pi); if (!err) { err = erofs_iopen(&vf, &pi); if (!err) err = erofs_pread(&vf, 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 erofs_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)) { erofs_mutex_unlock(&epi->mutex); buffer = NULL; goto fallback; } #if EROFS_MT_ENABLED } erofs_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) break; err = erofs_iopen(&vf, &pi); if (!err) err = erofs_pread(&vf, buf, len, pos); if (err) break; } map.m_llen = 0; } buf += len; pos += len; } free(buffer); return err; } erofs-utils-1.9.1/lib/global.c000066400000000000000000000023151515160260000161450ustar00rootroot00000000000000// SPDX-License-Identifier: GPL-2.0+ OR Apache-2.0 /* * Copyright (C) 2025 Alibaba Cloud */ #include "erofs/lock.h" #ifdef HAVE_CURL_CURL_H #include #endif #ifdef HAVE_LIBXML_PARSER_H #include #endif #include "erofs/err.h" #include "erofs/config.h" #include "liberofs_compress.h" static EROFS_DEFINE_MUTEX(erofs_global_mutex); #ifdef HAVE_LIBCURL static bool erofs_global_curl_initialized; #endif int liberofs_global_init(void) { int err = 0; erofs_mutex_lock(&erofs_global_mutex); erofs_init_configure(); #ifdef S3EROFS_ENABLED xmlInitParser(); #endif #ifdef HAVE_LIBCURL if (!erofs_global_curl_initialized) { if (curl_global_init(CURL_GLOBAL_DEFAULT) != CURLE_OK) { err = -EFAULT; goto out_unlock; } erofs_global_curl_initialized = true; } out_unlock: #endif erofs_mutex_unlock(&erofs_global_mutex); return err; } void liberofs_global_exit(void) { erofs_mutex_lock(&erofs_global_mutex); z_erofs_mt_global_exit(); #ifdef HAVE_LIBCURL if (erofs_global_curl_initialized) { curl_global_cleanup(); erofs_global_curl_initialized = false; } #endif #ifdef S3EROFS_ENABLED xmlCleanupParser(); #endif erofs_exit_configure(); erofs_mutex_unlock(&erofs_global_mutex); } erofs-utils-1.9.1/lib/gzran.c000066400000000000000000000235071515160260000160340ustar00rootroot00000000000000// SPDX-License-Identifier: GPL-2.0+ OR Apache-2.0 /* * Copyright (C) 2025 Alibaba Cloud */ #include "erofs/list.h" #include "erofs/err.h" #include "liberofs_gzran.h" #include #include #ifdef HAVE_ZLIB struct erofs_gzran_cutpoint { u8 window[EROFS_GZRAN_WINSIZE]; /* preceding 32K of uncompressed data */ u64 outpos; /* corresponding offset in uncompressed data */ u64 in_bitpos; /* bit offset in input file of first full byte */ }; struct erofs_gzran_cutpoint_item { struct erofs_gzran_cutpoint cp; struct list_head list; }; struct erofs_gzran_builder { struct list_head items; struct erofs_vfile *vf; z_stream strm; u64 totout, totin; u32 entries; u32 span_size; u8 window[EROFS_GZRAN_WINSIZE]; u8 src[1 << 14]; bool initial; }; struct erofs_gzran_builder *erofs_gzran_builder_init(struct erofs_vfile *vf, u32 span_size) { struct erofs_gzran_builder *gb; z_stream *strm; int ret; gb = malloc(sizeof(*gb)); if (!gb) return ERR_PTR(-ENOMEM); strm = &gb->strm; /* initialize inflate */ strm->zalloc = Z_NULL; strm->zfree = Z_NULL; strm->opaque = Z_NULL; strm->avail_in = 0; strm->next_in = Z_NULL; ret = inflateInit2(strm, 47); /* automatic zlib or gzip decoding */ if (ret != Z_OK) return ERR_PTR(-EFAULT); gb->vf = vf; gb->span_size = span_size; gb->totout = gb->totin = 0; gb->entries = 0; gb->initial = true; init_list_head(&gb->items); return gb; } /* return up to 32K of data at once */ int erofs_gzran_builder_read(struct erofs_gzran_builder *gb, char *window) { struct erofs_gzran_cutpoint_item *ci; struct erofs_gzran_cutpoint *cp; z_stream *strm = &gb->strm; struct erofs_vfile *vf = gb->vf; int read, ret; u64 last; strm->avail_out = sizeof(gb->window); strm->next_out = gb->window; do { if (!strm->avail_in) { read = erofs_io_read(vf, gb->src, sizeof(gb->src)); if (read <= 0) return read; strm->avail_in = read; strm->next_in = gb->src; } gb->totin += strm->avail_in; gb->totout += strm->avail_out; ret = inflate(strm, Z_BLOCK); /* return at end of block */ gb->totin -= strm->avail_in; gb->totout -= strm->avail_out; if (ret == Z_NEED_DICT) ret = Z_DATA_ERROR; if (ret == Z_MEM_ERROR || ret == Z_DATA_ERROR) return -EIO; if (ret == Z_STREAM_END) { inflateReset(strm); gb->initial = true; /* address concatenated gzip streams: e.g. (e)stargz */ if (strm->avail_out < sizeof(gb->window)) break; continue; } ci = list_empty(&gb->items) ? NULL : list_last_entry(&gb->items, struct erofs_gzran_cutpoint_item, list); last = ci ? ci->cp.outpos : 0; if ((strm->data_type & 128) && !(strm->data_type & 64) && (gb->initial || gb->totout - last > gb->span_size)) { ci = malloc(sizeof(*ci)); if (!ci) return -ENOMEM; init_list_head(&ci->list); cp = &ci->cp; cp->in_bitpos = (gb->totin << 3) | (strm->data_type & 7); cp->outpos = gb->totout; read = sizeof(gb->window) - strm->avail_out; if (strm->avail_out) memcpy(cp->window, gb->window + read, strm->avail_out); if (read) memcpy(cp->window + strm->avail_out, gb->window, read); list_add_tail(&ci->list, &gb->items); gb->entries++; gb->initial = false; } } while (strm->avail_out); read = sizeof(gb->window) - strm->avail_out; memcpy(window, gb->window, read); return read; } struct aws_soci_zinfo_header { __le32 have; __le64 span_size; } __packed; struct aws_soci_zinfo_ckpt { __le64 in; __le64 out; __u8 bits; u8 window[EROFS_GZRAN_WINSIZE]; } __packed; /* Generate AWS SOCI-compatible on-disk zinfo version 2 */ int erofs_gzran_builder_export_zinfo(struct erofs_gzran_builder *gb, struct erofs_vfile *zinfo_vf) { union { struct aws_soci_zinfo_header h; struct aws_soci_zinfo_ckpt c; } u; struct erofs_gzran_cutpoint_item *ci; u64 pos; int ret; BUILD_BUG_ON(sizeof(u.h) != 12); u.h = (struct aws_soci_zinfo_header) { .have = cpu_to_le32(gb->entries), .span_size = cpu_to_le64(gb->span_size), }; ret = erofs_io_pwrite(zinfo_vf, &u.h, 0, sizeof(u.h)); if (ret < 0) return ret; if (ret != sizeof(u.h)) return -EIO; pos = sizeof(u.h); list_for_each_entry(ci, &gb->items, list) { BUILD_BUG_ON(sizeof(u.c) != 17 + EROFS_GZRAN_WINSIZE); u.c.in = cpu_to_le64(ci->cp.in_bitpos >> 3); u.c.out = cpu_to_le64(ci->cp.outpos); u.c.bits = ci->cp.in_bitpos & 7; memcpy(u.c.window, ci->cp.window, EROFS_GZRAN_WINSIZE); ret = erofs_io_pwrite(zinfo_vf, &u.c, pos, sizeof(u.c)); if (ret < 0) return ret; if (ret != sizeof(u.c)) return -EIO; pos += sizeof(u.c); } return 0; } int erofs_gzran_builder_final(struct erofs_gzran_builder *gb) { struct erofs_gzran_cutpoint_item *ci, *n; int ret; ret = inflateEnd(&gb->strm); if (ret != Z_OK) return -EFAULT; list_for_each_entry_safe(ci, n, &gb->items, list) { list_del(&ci->list); free(ci); --gb->entries; } DBG_BUGON(gb->entries); free(gb); return 0; } struct erofs_gzran_iostream { struct erofs_vfile *vin; struct erofs_gzran_cutpoint *cp; u32 entries; u32 span_size; }; static void erofs_gzran_ios_vfclose(struct erofs_vfile *vf) { struct erofs_gzran_iostream *ios = (struct erofs_gzran_iostream *)vf->payload; free(ios->cp); free(vf); } static ssize_t erofs_gzran_ios_vfpread(struct erofs_vfile *vf, void *buf, size_t len, u64 offset) { struct erofs_gzran_iostream *ios = (struct erofs_gzran_iostream *)vf->payload; struct erofs_gzran_cutpoint *cp = ios->cp; u8 src[131072], discard[EROFS_GZRAN_WINSIZE]; union { unsigned int bits, i; } u; bool skip = true; u64 inpos, remin; z_stream strm; int ret; if (offset == ~0ULL) { DBG_BUGON(1); return -EIO; } while (cp[1].outpos <= offset) ++cp; for (u.i = 1; cp[u.i].outpos < offset + len; ++u.i); remin = (cp[u.i].in_bitpos >> 3) + !!(cp[u.i].in_bitpos & 7); 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); /* raw inflate */ if (ret != Z_OK) return -EFAULT; u.bits = cp->in_bitpos & 7; inpos = (cp->in_bitpos >> 3) - (u.bits ? 1 : 0); remin -= inpos; ret = erofs_io_pread(ios->vin, src, min(remin, (u64)sizeof(src)), inpos); if (ret < 0) return ret; if (u.bits) { inflatePrime(&strm, u.bits, src[0] >> (8 - u.bits)); strm.next_in = src + 1; strm.avail_in = ret - 1; } else { strm.next_in = src; strm.avail_in = ret; } remin -= ret; inpos += ret; (void)inflateSetDictionary(&strm, cp->window, sizeof(cp->window)); offset -= cp->outpos; do { /* define where to put uncompressed data, and how much */ if (!offset && skip) { /* at offset now */ strm.avail_out = len; strm.next_out = buf; skip = false; /* only do this once */ } else if (offset > sizeof(discard)) { /* skip WINSIZE bytes */ strm.avail_out = sizeof(discard); strm.next_out = discard; offset -= sizeof(discard); } else if (offset) { /* last skip */ strm.avail_out = (unsigned int)offset; strm.next_out = discard; offset = 0; } /* uncompress until avail_out filled, or end of stream */ do { if (!strm.avail_in) { ret = erofs_io_pread(ios->vin, src, min(remin, (u64)sizeof(src)), inpos); if (ret < 0) return ret; if (!ret) return -EIO; inpos += ret; remin -= ret; strm.avail_in = ret; strm.next_in = src; } ret = inflate(&strm, Z_NO_FLUSH); /* normal inflate */ if (ret == Z_NEED_DICT) ret = Z_DATA_ERROR; if (ret == Z_MEM_ERROR || ret == Z_DATA_ERROR) return -EIO; if (ret == Z_STREAM_END) break; } while (strm.avail_out); /* if reach end of stream, then don't keep trying to get more */ if (ret == Z_STREAM_END) break; /* do until offset reached and requested data read, or stream ends */ } while (skip); return len - strm.avail_out; } static struct erofs_vfops erofs_gzran_ios_vfops = { .pread = erofs_gzran_ios_vfpread, .close = erofs_gzran_ios_vfclose, }; struct erofs_vfile *erofs_gzran_zinfo_open(struct erofs_vfile *vin, void *zinfo_buf, unsigned int len) { struct aws_soci_zinfo_header *h; struct aws_soci_zinfo_ckpt *c; struct erofs_vfile *vf; struct erofs_gzran_iostream *ios; unsigned int v2_size, version; int ret, i; if (len && len < sizeof(*h)) return ERR_PTR(-EINVAL); vf = malloc(sizeof(*vf) + sizeof(*ios)); if (!vf) return ERR_PTR(-ENOMEM); ios = (struct erofs_gzran_iostream *)vf->payload; h = zinfo_buf; ios->entries = le32_to_cpu(h->have); ios->span_size = le32_to_cpu(h->span_size); v2_size = sizeof(*c) * ios->entries + sizeof(*h); if (!len || v2_size == len) { version = 2; } else if (v2_size - sizeof(*c) == len) { version = 1; } else { ret = -EOPNOTSUPP; goto err_ios; } ios->cp = malloc(sizeof(*ios->cp) * (ios->entries + 1)); if (!ios->cp) { ret = -ENOMEM; goto err_ios; } i = 0; if (version == 1) { ios->cp[0] = (struct erofs_gzran_cutpoint) { .in_bitpos = 10 << 3, .outpos = 0, }; i = 1; } c = (struct aws_soci_zinfo_ckpt *)(h + 1); for (; i < ios->entries; ++i, ++c) { ios->cp[i].in_bitpos = (le64_to_cpu(c->in) << 3) | c->bits; ios->cp[i].outpos = le64_to_cpu(c->out); memcpy(ios->cp[i].window, c->window, sizeof(c->window)); } ios->cp[i].in_bitpos = -1; ios->cp[i].outpos = ~0ULL; ios->vin = vin; vf->ops = &erofs_gzran_ios_vfops; return vf; err_ios: free(vf); return ERR_PTR(ret); } #else struct erofs_gzran_builder *erofs_gzran_builder_init(struct erofs_vfile *vf, u32 span_size) { return ERR_PTR(-EOPNOTSUPP); } int erofs_gzran_builder_read(struct erofs_gzran_builder *gb, char *window) { return 0; } int erofs_gzran_builder_export_zinfo(struct erofs_gzran_builder *gb, struct erofs_vfile *zinfo_vf) { return -EOPNOTSUPP; } int erofs_gzran_builder_final(struct erofs_gzran_builder *gb) { return 0; } struct erofs_vfile *erofs_gzran_zinfo_open(struct erofs_vfile *vin, void *zinfo_buf, unsigned int len) { return ERR_PTR(-EOPNOTSUPP); } #endif erofs-utils-1.9.1/lib/hashmap.c000066400000000000000000000143601515160260000163310ustar00rootroot00000000000000// 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.9.1/lib/importer.c000066400000000000000000000064551515160260000165570ustar00rootroot00000000000000// SPDX-License-Identifier: GPL-2.0+ OR Apache-2.0 /* * Copyright (C) 2025 Alibaba Cloud */ #include "erofs/importer.h" #include "erofs/config.h" #include "erofs/dedupe.h" #include "erofs/inode.h" #include "erofs/print.h" #include "erofs/lock.h" #include "erofs/xattr.h" #include "liberofs_cache.h" #include "liberofs_compress.h" #include "liberofs_fragments.h" #include "liberofs_metabox.h" static EROFS_DEFINE_MUTEX(erofs_importer_global_mutex); static bool erofs_importer_global_initialized; void erofs_importer_preset(struct erofs_importer_params *params) { *params = (struct erofs_importer_params) { .fixed_uid = -1, .fixed_gid = -1, .fsalignblks = 1, .build_time = -1, .max_compressed_extent_size = EROFS_COMPRESSED_EXTENT_UNSPECIFIED, }; } void erofs_importer_global_init(void) { if (erofs_importer_global_initialized) return; erofs_mutex_lock(&erofs_importer_global_mutex); if (!erofs_importer_global_initialized) { erofs_inode_manager_init(); erofs_importer_global_initialized = true; } erofs_mutex_unlock(&erofs_importer_global_mutex); } int erofs_importer_init(struct erofs_importer *im) { struct erofs_sb_info *sbi = im->sbi; struct erofs_importer_params *params = im->params; const char *subsys = NULL; int err; erofs_importer_global_init(); subsys = "xattr"; err = erofs_xattr_init(sbi); if (err) goto out_err; subsys = "compression"; err = z_erofs_compress_init(im); if (err) goto out_err; if (params->fragments || cfg.c_extra_ea_name_prefixes || params->compress_dir) { subsys = "packedfile"; if (!params->pclusterblks_packed) params->pclusterblks_packed = params->pclusterblks_def; err = erofs_packedfile_init(sbi, params->fragments || params->compress_dir); if (err) goto out_err; } subsys = "metadata"; err = erofs_metadata_init(sbi); if (err) goto out_err; if (params->fragments) { subsys = "dedupe_ext"; err = z_erofs_dedupe_ext_init(); if (err) goto out_err; } if (params->dot_omitted) erofs_sb_set_48bit(sbi); if (params->build_time != -1) { if (erofs_sb_has_48bit(sbi)) { sbi->epoch = max_t(s64, 0, params->build_time - UINT32_MAX); sbi->build_time = params->build_time - sbi->epoch; } else { sbi->epoch = params->build_time; } } return 0; out_err: erofs_err("failed to initialize %s: %s", subsys, erofs_strerror(-err)); return err; } int erofs_importer_flush_all(struct erofs_importer *im) { struct erofs_sb_info *sbi = im->sbi; unsigned int fsalignblks; int err; if (erofs_sb_has_metabox(sbi)) { erofs_update_progressinfo("Handling metabox ..."); err = erofs_metabox_iflush(im); if (err) return err; } err = erofs_flush_packed_inode(im); if (err) return err; err = erofs_metazone_flush(sbi); if (err) return err; fsalignblks = im->params->fsalignblks ? roundup_pow_of_two(im->params->fsalignblks) : 1; sbi->primarydevice_blocks = roundup(erofs_mapbh(sbi->bmgr, NULL), fsalignblks); err = erofs_write_device_table(sbi); if (err) return err; /* flush all buffers except for the superblock */ err = erofs_bflush(sbi->bmgr, NULL); if (err) return err; return erofs_fixup_root_inode(im->root); } void erofs_importer_exit(struct erofs_importer *im) { struct erofs_sb_info *sbi = im->sbi; z_erofs_dedupe_ext_exit(); erofs_metadata_exit(sbi); erofs_packedfile_exit(sbi); } erofs-utils-1.9.1/lib/inode.c000066400000000000000000001736521515160260000160200ustar00rootroot00000000000000// 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 #include #include #include #include #include #if defined(HAVE_SYS_SYSMACROS_H) #include #endif #include #include "erofs/print.h" #include "erofs/lock.h" #include "erofs/diskbuf.h" #include "erofs/inode.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/importer.h" #include "liberofs_cache.h" #include "liberofs_compress.h" #include "liberofs_fragments.h" #include "liberofs_metabox.h" #include "liberofs_private.h" #include "liberofs_rebuild.h" #include "sha256.h" static inline bool erofs_is_special_identifier(const char *path) { return path == EROFS_PACKED_INODE || path == EROFS_METABOX_INODE; } #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]; } static struct list_head erofs_ihash[65536]; static erofs_rwsem_t erofs_ihashlock; void erofs_inode_manager_init(void) { unsigned int i; for (i = 0; i < ARRAY_SIZE(erofs_ihash); ++i) init_list_head(&erofs_ihash[i]); erofs_init_rwsem(&erofs_ihashlock); } void erofs_insert_ihash(struct erofs_inode *inode) { u32 nr = (inode->i_ino[1] ^ inode->dev) % ARRAY_SIZE(erofs_ihash); erofs_down_write(&erofs_ihashlock); list_add(&inode->i_hash, &erofs_ihash[nr]); erofs_up_write(&erofs_ihashlock); } void erofs_remove_ihash(struct erofs_inode *inode) { erofs_down_write(&erofs_ihashlock); list_del(&inode->i_hash); erofs_up_write(&erofs_ihashlock); } /* get the inode from the (source) inode # */ struct erofs_inode *erofs_iget(dev_t dev, ino_t ino) { u32 nr = (ino ^ dev) % ARRAY_SIZE(erofs_ihash); struct list_head *head = &erofs_ihash[nr]; struct erofs_inode *ret = NULL, *inode; erofs_down_read(&erofs_ihashlock); list_for_each_entry(inode, head, i_hash) { if (inode->i_ino[1] == ino && inode->dev == dev) { ret = erofs_igrab(inode); break; } } erofs_up_read(&erofs_ihashlock); return ret; } 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); erofs_remove_ihash(inode); if (!erofs_is_special_identifier(inode->i_srcpath)) 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); } if (inode->datalayout == EROFS_INODE_CHUNK_BASED) free(inode->chunkindexes); 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->flags = 0; 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, bool in_metazone) { struct erofs_sb_info *sbi = inode->sbi; struct erofs_bufmgr *bmgr = in_metazone ? erofs_metadata_bmgr(sbi, false) : sbi->bmgr; struct erofs_buffer_head *bh; int ret, type; if (!nblocks) { /* it has only tail-end data */ inode->u.i_blkaddr = EROFS_NULL_ADDR; return 0; } if (in_metazone && !bmgr) { erofs_err("cannot allocate data in the metazone when unavailable for %s", inode->i_srcpath); return -EINVAL; } /* allocate main data buffer */ type = S_ISDIR(inode->i_mode) ? DIRA : DATA; bh = erofs_balloc(bmgr, type, erofs_pos(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 | (in_metazone ? (sbi->extra_devices + 1ULL) << EROFS_I_BLKADDR_DEV_ID_BIT : 0); return 0; } #define EROFS_DENTRY_MERGESORT_STEP 1 static void erofs_dentry_mergesort(struct list_head *entries, int k) { struct list_head *great = entries->next; BUILD_BUG_ON(EROFS_DENTRY_MERGESORT_STEP > EROFS_DENTRY_NAME_ALIGNMENT); entries->prev->next = NULL; init_list_head(entries); do { struct list_head **greatp = &great; struct erofs_dentry *e0, *d, *n; struct list_head le[2]; int cmp, k1; bool brk; e0 = list_entry(great, struct erofs_dentry, d_child); great = great->next; init_list_head(&le[0]); le[1] = (struct list_head)LIST_HEAD_INIT(e0->d_child); e0->d_child.prev = e0->d_child.next = &le[1]; do { d = list_entry(*greatp, struct erofs_dentry, d_child); cmp = memcmp(d->name + k, e0->name + k, EROFS_DENTRY_MERGESORT_STEP); if (cmp > 0) { greatp = &d->d_child.next; continue; } *greatp = d->d_child.next; list_add_tail(&d->d_child, &le[!cmp]); } while (*greatp); k1 = k + EROFS_DENTRY_MERGESORT_STEP; brk = great || !list_empty(&le[0]); while (e0->name[k1 - 1] != '\0') { if (__erofs_likely(brk)) { if (le[1].prev != le[1].next) erofs_dentry_mergesort(&le[1], k1); break; } e0 = list_first_entry(&le[1], struct erofs_dentry, d_child); d = list_next_entry(e0, d_child); list_for_each_entry_safe_from(d, n, &le[1], d_child) { cmp = memcmp(d->name + k1, e0->name + k1, EROFS_DENTRY_MERGESORT_STEP); if (!cmp) continue; __list_del(d->d_child.prev, d->d_child.next); if (cmp < 0) { list_add_tail(&d->d_child, &le[0]); } else { *greatp = &d->d_child; d->d_child.next = NULL; greatp = &d->d_child.next; } brk = true; } k = k1; k1 += EROFS_DENTRY_MERGESORT_STEP; } if (!list_empty(&le[0])) { if (le[0].prev != le[0].next) erofs_dentry_mergesort(&le[0], k); __list_splice(&le[0], entries->prev, entries); } __list_splice(&le[1], entries->prev, entries); } while (great && great->next); if (great) list_add_tail(great, entries); } static int erofs_prepare_dir_file(struct erofs_importer *im, struct erofs_inode *dir, unsigned int nr_subdirs) { const struct erofs_importer_params *params = im->params; struct erofs_sb_info *sbi = dir->sbi; struct erofs_dentry *d; unsigned int d_size = 0; if (!params->dot_omitted) { /* 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; } dir->dot_omitted = params->dot_omitted; /* 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; if (nr_subdirs) erofs_dentry_mergesort(&dir->i_subdirs, 0); nr_subdirs += 1 + !params->dot_omitted; /* 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; --nr_subdirs; } if (nr_subdirs) { DBG_BUGON(1); return -EFAULT; } dir->i_size = d_size; dir->datalayout = EROFS_INODE_DATALAYOUT_MAX; 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); } 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; s64 meta_offset; erofs_nid_t nid; if (bh && inode->nid == EROFS_NID_UNALLOCATED) { erofs_mapbh(NULL, bh->block); off = erofs_btell(bh, false); if (inode->in_metabox) { meta_offset = 0; } else { meta_offset = (s64)erofs_pos(sbi, sbi->meta_blkaddr); DBG_BUGON(off < meta_offset && !sbi->m2gr); } nid = (off - meta_offset) >> EROFS_ISLOTBITS; inode->nid = nid | (u64)inode->in_metabox << EROFS_DIRENT_NID_METABOX_BIT; erofs_dbg("Assign nid %s%llu to file %s (mode %05o)", inode->in_metabox ? "[M]" : "", nid, inode->i_srcpath, inode->i_mode); } if (__erofs_unlikely(IS_ROOT(inode))) { if (inode->in_metabox) DBG_BUGON(!erofs_sb_has_48bit(sbi)); else if (!erofs_sb_has_48bit(sbi) && 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->flags & EROFS_DENTRY_FLAG_VALIDNID) return; d->nid = erofs_lookupnid(inode); d->flags |= EROFS_DENTRY_FLAG_VALIDNID; 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; struct erofs_vfile vf; 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; err = erofs_iopen(&vf, &dir); if (err) return err; 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(&vf, 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; } struct erofs_dirwriter_vf { struct erofs_vfile vf; struct erofs_inode *dir; struct list_head *head; erofs_off_t offset; char dirdata[]; }; static ssize_t erofs_dirwriter_vfread(struct erofs_vfile *vf, void *buf, size_t len) { struct erofs_dirwriter_vf *dwv = (struct erofs_dirwriter_vf *)vf; struct erofs_inode *dir = dwv->dir; unsigned int bsz = erofs_blksiz(dir->sbi); size_t processed = 0; if (len > dir->i_size - dwv->offset) len = dir->i_size - dwv->offset; while (processed < len) { unsigned int off, dblen, count; off = dwv->offset & (bsz - 1); dblen = min_t(u64, dir->i_size - dwv->offset + off, bsz); /* generate a directory block to `dwv->dirdata` */ if (!off) { struct erofs_dentry *head, *d; unsigned int q, used, len; int err; d = head = list_entry(dwv->head, struct erofs_dentry, d_child); q = used = 0; do { /* XXX: a bit hacky, but avoids another traversal */ if (d->flags & EROFS_DENTRY_FLAG_FIXUP_PNID) { err = erofs_rebuild_inode_fix_pnid(dir, d->nid); if (err) return err; } len = d->namelen + sizeof(struct erofs_dirent); erofs_d_invalidate(d); if ((used += len) > bsz) break; d = list_next_entry(d, d_child); q += sizeof(struct erofs_dirent); } while (&d->d_child != &dir->i_subdirs); fill_dirblock(dwv->dirdata, dblen, q, head, d); dwv->head = &d->d_child; } count = min_t(size_t, dblen - off, len - processed); memcpy(buf + processed, dwv->dirdata + off, count); processed += count; dwv->offset += count; } return processed; } void erofs_dirwriter_vfclose(struct erofs_vfile *vf) { free((void *)vf); } static struct erofs_vfops erofs_dirwriter_vfops = { .read = erofs_dirwriter_vfread, .close = erofs_dirwriter_vfclose, }; static struct erofs_vfile *erofs_dirwriter_open(struct erofs_inode *dir) { struct erofs_dirwriter_vf *dwv; dwv = malloc(sizeof(*dwv) + erofs_blksiz(dir->sbi)); if (!dwv) return ERR_PTR(-ENOMEM); dwv->vf.ops = &erofs_dirwriter_vfops; dwv->dir = dir; dwv->head = dir->i_subdirs.next; dwv->offset = 0; return (struct erofs_vfile *)dwv; } 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, false); 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_importer *im, struct erofs_inode *inode) { if (erofs_is_metabox_inode(inode) && !im->params->pclusterblks_metabox) return false; if (cfg.c_compress_hints_file) return z_erofs_apply_compress_hints(im, inode); return true; } static int erofs_write_unencoded_data(struct erofs_inode *inode, struct erofs_vfile *vf, erofs_off_t fpos, bool noseek, bool in_metazone) { struct erofs_sb_info *sbi = inode->sbi; struct erofs_buffer_head *bh; struct erofs_bufmgr *bmgr; erofs_off_t remaining, pos; unsigned int len; int ret; if (!noseek && erofs_sb_has_48bit(sbi)) { if (erofs_io_lseek(vf, fpos, SEEK_DATA) == -ENXIO) { ret = erofs_allocate_inode_bh_data(inode, 0, false); if (ret) return ret; inode->datalayout = EROFS_INODE_FLAT_PLAIN; return 0; } ret = erofs_io_lseek(vf, fpos, SEEK_SET); if (ret < 0) return ret; if (ret != fpos) return -EIO; } inode->idata_size = inode->i_size % erofs_blksiz(sbi); remaining = inode->i_size - inode->idata_size; ret = erofs_allocate_inode_bh_data(inode, remaining >> sbi->blkszbits, in_metazone); if (ret) return ret; bh = inode->bh_data; if (bh) { bmgr = (struct erofs_bufmgr *)bh->block->buffers.fsprivate; pos = erofs_btell(bh, false); do { len = min_t(u64, remaining, round_down(UINT_MAX, 1U << sbi->blkszbits)); ret = erofs_io_xcopy(bmgr->vf, pos, vf, len, noseek); if (ret) return ret; pos += len; remaining -= len; } while (remaining); } /* read the tail-end data */ if (inode->idata_size) { inode->idata = malloc(inode->idata_size); if (!inode->idata) return -ENOMEM; ret = erofs_io_read(vf, inode->idata, inode->idata_size); if (ret < inode->idata_size) { free(inode->idata); inode->idata = NULL; return -EIO; } } 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); } inode->datalayout = EROFS_INODE_FLAT_INLINE; /* fallback to all data uncompressed */ return erofs_write_unencoded_data(inode, &(struct erofs_vfile){ .fd = fd }, fpos, inode->datasource == EROFS_INODE_DATA_SOURCE_DISKBUF, false); } static int erofs_write_dir_file(const struct erofs_importer *im, struct erofs_inode *dir) { unsigned int bsz = erofs_blksiz(dir->sbi); struct erofs_vfile *vf; int err; vf = erofs_dirwriter_open(dir); if (IS_ERR(vf)) return PTR_ERR(vf); if (erofs_inode_is_data_compressed(dir->datalayout)) { err = erofs_write_compress_dir(dir, vf); } else { DBG_BUGON(dir->idata_size != (dir->i_size & (bsz - 1))); err = erofs_write_unencoded_data(dir, vf, 0, true, im->params->dirdata_in_metazone); } erofs_io_close(vf); return err; } static int erofs_inode_map_flat_blkaddr(struct erofs_inode *inode) { const struct erofs_sb_info *sbi = inode->sbi; erofs_blk_t dev_startblk; int dev_id; if (inode->u.i_blkaddr == EROFS_NULL_ADDR) return 0; dev_id = inode->u.i_blkaddr >> EROFS_I_BLKADDR_DEV_ID_BIT; if (!dev_id) return 0; if (dev_id <= sbi->extra_devices) { if (!sbi->devs[dev_id - 1].uniaddr) { DBG_BUGON(1); /* impossible now */ return -EBUSY; } dev_startblk = sbi->devs[dev_id - 1].uniaddr; } else { if (sbi->metazone_startblk == EROFS_META_NEW_ADDR) { DBG_BUGON(1); /* impossible now */ return -EBUSY; } DBG_BUGON(dev_id != sbi->extra_devices + 1); dev_startblk = sbi->metazone_startblk; } inode->u.i_blkaddr = erofs_inode_dev_baddr(inode) + dev_startblk; return 0; } int erofs_iflush(struct erofs_inode *inode) { u16 icount = EROFS_INODE_XATTR_ICOUNT(inode->xattr_isize); struct erofs_sb_info *sbi = inode->sbi; struct erofs_buffer_head *bh = inode->bh; erofs_off_t off = erofs_iloc(inode); struct erofs_bufmgr *ibmgr = erofs_metadata_bmgr(sbi, inode->in_metabox) ?: sbi->bmgr; union { struct erofs_inode_compact dic; struct erofs_inode_extended die; } u = {}; union erofs_inode_i_u u1; union erofs_inode_i_nb nb; unsigned int iovcnt = 0; struct iovec iov[2]; char *xattrs = NULL; bool nlink_1 = true; int ret, fmt; DBG_BUGON(inode->nid == EROFS_NID_UNALLOCATED); DBG_BUGON(bh && erofs_btell(bh, false) != off); 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.blocks_lo = cpu_to_le32(inode->u.i_blocks); } else if (inode->datalayout == EROFS_INODE_CHUNK_BASED) { erofs_inode_fixup_chunkformat(inode); u1.c.format = cpu_to_le16(inode->u.chunkformat); } else { ret = erofs_inode_map_flat_blkaddr(inode); if (ret) return ret; u1.startblk_lo = cpu_to_le32(inode->u.i_blkaddr); } if (is_inode_layout_compression(inode) && inode->u.i_blocks > UINT32_MAX) { nb.blocks_hi = cpu_to_le16(inode->u.i_blocks >> 32); } else if (inode->datalayout != EROFS_INODE_CHUNK_BASED && inode->u.i_blkaddr > UINT32_MAX) { nb.startblk_hi = cpu_to_le16(inode->u.i_blkaddr >> 32); if (inode->u.i_blkaddr == EROFS_NULL_ADDR) { nlink_1 = false; /* In sync with old non-48bit mkfses */ if (!erofs_sb_has_48bit(sbi)) nb.startblk_hi = 0; } } else { nlink_1 = false; nb = (union erofs_inode_i_nb){}; } fmt = S_ISDIR(inode->i_mode) && inode->dot_omitted ? 1 << EROFS_I_DOT_OMITTED_BIT : 0; switch (inode->inode_isize) { case sizeof(struct erofs_inode_compact): fmt |= 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_nb.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_mtime = cpu_to_le64(inode->i_mtime - sbi->epoch); u.dic.i_u = u1; if (nlink_1) { if (inode->i_nlink != 1) return -EFSCORRUPTED; u.dic.i_nb = nb; fmt |= 1 << EROFS_I_NLINK_1_BIT; } else { u.dic.i_nb.nlink = cpu_to_le16(inode->i_nlink); } u.dic.i_format = cpu_to_le16(fmt); break; case sizeof(struct erofs_inode_extended): fmt |= 1 | (inode->datalayout << 1); u.die.i_format = cpu_to_le16(fmt); 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; u.die.i_nb = nb; break; default: erofs_err("unsupported on-disk inode version of nid %llu", (unsigned long long)inode->nid); DBG_BUGON(1); return -EOPNOTSUPP; } iov[iovcnt++] = (struct iovec){ .iov_base = &u, .iov_len = inode->inode_isize }; if (inode->xattr_isize) { xattrs = erofs_export_xattr_ibody(inode); if (IS_ERR(xattrs)) return PTR_ERR(xattrs); iov[iovcnt++] = (struct iovec){ .iov_base = xattrs, .iov_len = inode->xattr_isize }; } ret = erofs_io_pwritev(ibmgr->vf, iov, iovcnt, off); free(xattrs); if (ret != inode->inode_isize + inode->xattr_isize) return ret < 0 ? ret : -EIO; off += ret; if (inode->extent_isize) { if (inode->datalayout == EROFS_INODE_CHUNK_BASED) { ret = erofs_write_chunk_indexes(inode, ibmgr->vf, off); } else { /* write compression metadata */ off = roundup(off, 8); ret = erofs_io_pwrite(ibmgr->vf, inode->compressmeta, off, inode->extent_isize); } if (ret != inode->extent_isize) return ret < 0 ? ret : -EIO; } return 0; } static int erofs_bh_flush_write_inode(struct erofs_buffer_head *bh, bool abort) { struct erofs_inode *inode = bh->fsprivate; int ret; DBG_BUGON(inode->bh != bh); if (!abort) { 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 bool erofs_inode_need_48bit(struct erofs_inode *inode) { if (inode->datalayout == EROFS_INODE_CHUNK_BASED) { if (inode->u.chunkformat & EROFS_CHUNK_FORMAT_48BIT) return true; } else if (!is_inode_layout_compression(inode)) { if (inode->u.i_blkaddr != EROFS_NULL_ADDR && erofs_inode_dev_baddr(inode) > UINT32_MAX) return true; } return false; } static int erofs_prepare_inode_buffer(struct erofs_importer *im, struct erofs_inode *inode) { const struct erofs_importer_params *params = im->params; struct erofs_sb_info *sbi = im->sbi; struct erofs_bufmgr *ibmgr; unsigned int inodesize; struct erofs_buffer_head *bh, *ibh; DBG_BUGON(inode->bh || inode->bh_inline); if (erofs_inode_need_48bit(inode)) { if (!erofs_sb_has_48bit(sbi)) return -ENOSPC; if (inode->inode_isize == sizeof(struct erofs_inode_compact) && inode->i_nlink != 1) inode->inode_isize = sizeof(struct erofs_inode_extended); } inodesize = inode->inode_isize + inode->xattr_isize; if (inode->extent_isize) inodesize = roundup(inodesize, 8) + inode->extent_isize; if (!erofs_is_special_identifier(inode->i_srcpath) && sbi->mxgr) inode->in_metabox = true; ibmgr = erofs_metadata_bmgr(sbi, inode->in_metabox) ?: sbi->bmgr; 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 (params->no_datainline && 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(ibmgr, 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(ibmgr, 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(!params->ztailpacking); erofs_dbg("Inline %scompressed data (%u bytes) to %s", inode->compressed_idata ? "" : "un", inode->idata_size, inode->i_srcpath); erofs_sb_set_ztailpacking(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] = ++sbi->inos; /* inode serial number */ return 0; } static int erofs_bh_flush_write_inline(struct erofs_buffer_head *bh, bool abort) { struct erofs_inode *const inode = bh->fsprivate; struct erofs_sb_info *sbi = inode->sbi; struct erofs_bufmgr *ibmgr = erofs_metadata_bmgr(sbi, inode->in_metabox) ?: sbi->bmgr; const erofs_off_t off = erofs_btell(bh, false); int ret; if (!abort) { ret = erofs_io_pwrite(ibmgr->vf, inode->idata, off, inode->idata_size); if (ret < 0) return ret; if (ret != inode->idata_size) return -EIO; } 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_importer *im, struct erofs_inode *inode) { static const u8 zeroed[EROFS_MAX_BLOCK_SIZE]; const struct erofs_importer_params *params = im->params; 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; } else { struct erofs_bufmgr *bmgr; struct iovec iov[2]; erofs_off_t pos; int ret; bool h0, in_metazone; if (!bh) { in_metazone = S_ISDIR(inode->i_mode) && params->dirdata_in_metazone; ret = erofs_allocate_inode_bh_data(inode, 1, in_metazone); if (ret) return ret; bh = inode->bh_data; } 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); bmgr = (struct erofs_bufmgr *)bh->block->buffers.fsprivate; pos = erofs_btell(bh, true) - erofs_blksiz(sbi); /* 0'ed data should be padded at head for 0padding conversion */ h0 = erofs_sb_has_lz4_0padding(sbi) && inode->compressed_idata; DBG_BUGON(inode->idata_size > erofs_blksiz(sbi)); iov[h0] = (struct iovec) { .iov_base = inode->idata, .iov_len = inode->idata_size }; iov[!h0] = (struct iovec) { .iov_base = (u8 *)zeroed, erofs_blksiz(sbi) - inode->idata_size }; ret = erofs_io_pwritev(bmgr->vf, iov, 2, pos); if (ret < 0) return ret; else if (ret < erofs_blksiz(sbi)) return -EIO; inode->idata_size = 0; free(inode->idata); inode->idata = NULL; } 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_importer *im, struct erofs_inode *inode, const char *path) { const struct erofs_importer_params *params = im->params; if (params->force_inodeversion == EROFS_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 (!erofs_is_special_identifier(path) && !erofs_sb_has_48bit(inode->sbi) && inode->i_mtime != inode->sbi->epoch) { if (!params->ignore_mtime) return true; inode->i_mtime = inode->sbi->epoch; } 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 (erofs_is_special_identifier(path)) 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_importer *im, struct erofs_inode *inode, struct stat *st, const char *path) { struct erofs_importer_params *params = im->params; struct erofs_sb_info *sbi = inode->sbi; int err; err = erofs_droid_inode_fsconfig(inode, st, path); if (err) return err; inode->i_uid = params->fixed_uid == -1 ? st->st_uid : params->fixed_uid; inode->i_gid = params->fixed_gid == -1 ? st->st_gid : params->fixed_gid; if ((u32)(inode->i_uid + params->uid_offset) < inode->i_uid) erofs_err("uid overflow @ %s", path); inode->i_uid += params->uid_offset; if ((u32)(inode->i_gid + params->gid_offset) < inode->i_gid) erofs_err("gid overflow @ %s", path); inode->i_gid += params->gid_offset; if (erofs_is_special_identifier(path)) { inode->i_mtime = sbi->epoch + sbi->build_time; inode->i_mtime_nsec = sbi->fixed_nsec; return 0; } 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->epoch + sbi->build_time) break; case TIMESTAMP_FIXED: inode->i_mtime = sbi->epoch + sbi->build_time; inode->i_mtime_nsec = sbi->fixed_nsec; default: break; } return 0; } static int erofs_fill_inode(struct erofs_importer *im, struct erofs_inode *inode, struct stat *st, const char *path) { const struct erofs_importer_params *params = im->params; int err; err = __erofs_fill_inode(im, 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; } if (erofs_is_special_identifier(path)) { inode->i_srcpath = (char *)path; } else { inode->i_srcpath = strdup(path); if (!inode->i_srcpath) return -ENOMEM; } if (erofs_should_use_inode_extended(im, inode, path)) { if (params->force_inodeversion == EROFS_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; /* * By default, newly allocated in-memory inodes are associated with * the target filesystem rather than any other foreign sources. */ inode->dev = sbi->dev; inode->i_count = 1; inode->datalayout = EROFS_INODE_FLAT_PLAIN; inode->nid = EROFS_NID_UNALLOCATED; init_list_head(&inode->i_hash); init_list_head(&inode->i_subdirs); init_list_head(&inode->i_xattrs); return inode; } static struct erofs_inode *erofs_iget_from_local(struct erofs_importer *im, const char *path) { const struct erofs_importer_params *params = im->params; struct erofs_sb_info *sbi = im->sbi; struct erofs_inode *inode; struct stat st; 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) && !params->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(im, inode, &st, path); if (ret) { erofs_iput(inode); return ERR_PTR(ret); } inode->datasource = EROFS_INODE_DATA_SOURCE_LOCALPATH; return inode; } static void erofs_fixup_meta_blkaddr(struct erofs_inode *root) { const erofs_off_t rootnid_maxoffset = 0xffff << EROFS_ISLOTBITS; struct erofs_buffer_head *const bh = root->bh; struct erofs_sb_info *sbi = root->sbi; int bsz = erofs_blksiz(sbi); int meta_offset = 0; erofs_off_t off; erofs_mapbh(NULL, bh->block); off = erofs_btell(bh, false); if (!root->in_metabox) { if (!off) { DBG_BUGON(!sbi->m2gr); DBG_BUGON(sbi->meta_blkaddr != -1); meta_offset = -bsz; /* avoid NID 0 */ } else if (off > rootnid_maxoffset) { meta_offset = round_up(off - rootnid_maxoffset, bsz); sbi->meta_blkaddr = erofs_blknr(sbi, meta_offset); } } else if (!erofs_sb_has_48bit(sbi)) { sbi->build_time = sbi->epoch; sbi->epoch = max_t(s64, 0, (s64)sbi->build_time - UINT32_MAX); sbi->build_time -= sbi->epoch; erofs_sb_set_48bit(sbi); } root->nid = ((off - meta_offset) >> EROFS_ISLOTBITS) | ((u64)root->in_metabox << EROFS_DIRENT_NID_METABOX_BIT); } 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 { DBG_BUGON(ctx->fd < 0); close(ctx->fd); } return ret; } struct erofs_mkfs_btctx { struct erofs_importer *im; bool rebuild, incremental; }; static int erofs_mkfs_handle_nondirectory(const struct erofs_mkfs_btctx *btctx, struct erofs_mkfs_job_ndir_ctx *ctx) { struct erofs_inode *inode = ctx->inode; int ret; ret = erofs_prepare_xattr_ibody(inode, btctx->incremental && IS_ROOT(inode)); if (ret) return ret; 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(btctx->im, inode); erofs_write_tail_end(btctx->im, inode); return 0; } static int erofs_mkfs_create_directory(const struct erofs_mkfs_btctx *ctx, struct erofs_inode *inode) { unsigned int bsz = erofs_blksiz(inode->sbi); int ret; ret = erofs_prepare_xattr_ibody(inode, ctx->incremental && IS_ROOT(inode)); if (ret) return ret; if (inode->datalayout == EROFS_INODE_DATALAYOUT_MAX) { inode->datalayout = EROFS_INODE_FLAT_INLINE; ret = erofs_begin_compress_dir(ctx->im, inode); if (ret && ret != -ENOSPC) return ret; } else { DBG_BUGON(inode->datalayout != EROFS_INODE_FLAT_PLAIN); } /* it will be used in erofs_prepare_inode_buffer */ if (inode->datalayout == EROFS_INODE_FLAT_INLINE) inode->idata_size = inode->i_size & (bsz - 1); /* * Directory on-disk inodes should be close to other inodes * in the parent directory since parent directories should * generally be prioritized. */ ret = erofs_prepare_inode_buffer(ctx->im, inode); if (ret) return ret; inode->bh->op = &erofs_skip_write_bhops; 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; unsigned int _usize; union { struct erofs_inode *inode; struct erofs_mkfs_job_ndir_ctx ndir; } u; }; static int erofs_mkfs_jobfn(const struct erofs_mkfs_btctx *ctx, struct erofs_mkfs_jobitem *item) { struct erofs_inode *inode = item->u.inode; int ret; if (item->type >= EROFS_MKFS_JOB_MAX) return 1; if (item->type == EROFS_MKFS_JOB_NDIR) return erofs_mkfs_handle_nondirectory(ctx, &item->u.ndir); if (item->type == EROFS_MKFS_JOB_DIR) return erofs_mkfs_create_directory(ctx, inode); if (item->type == EROFS_MKFS_JOB_DIR_BH) { ret = erofs_write_dir_file(ctx->im, inode); if (ret) return ret; erofs_write_tail_end(ctx->im, 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 */ bool exited; }; 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_top_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->entries - 1)); pthread_mutex_unlock(&q->lock); return item; } static void erofs_mkfs_pop_jobitem(struct erofs_mkfs_dfops *q) { pthread_mutex_lock(&q->lock); DBG_BUGON(q->head == q->tail); ++q->head; pthread_cond_signal(&q->full); pthread_mutex_unlock(&q->lock); } static void *z_erofs_mt_dfops_worker(void *arg) { const struct erofs_mkfs_btctx *ctx = arg; struct erofs_sb_info *sbi = ctx->im->sbi; struct erofs_mkfs_dfops *dfops = sbi->mkfs_dfops; int ret; do { struct erofs_mkfs_jobitem *item; item = erofs_mkfs_top_jobitem(dfops); ret = erofs_mkfs_jobfn(ctx, item); erofs_mkfs_pop_jobitem(dfops); } while (!ret); dfops->exited = true; if (ret < 0) pthread_cond_signal(&dfops->full); pthread_exit((void *)(uintptr_t)(ret < 0 ? ret : 0)); } static int erofs_mkfs_go(const struct erofs_mkfs_btctx *ctx, enum erofs_mkfs_jobtype type, void *elem, int size) { struct erofs_mkfs_dfops *q = ctx->im->sbi->mkfs_dfops; struct erofs_mkfs_jobitem *item; pthread_mutex_lock(&q->lock); while (q->tail - q->head >= q->entries) { if (q->exited) { pthread_mutex_unlock(&q->lock); return -ECHILD; } pthread_cond_wait(&q->full, &q->lock); } item = q->queue + (q->tail++ & (q->entries - 1)); item->type = type; if (size) memcpy(&item->u, elem, size); q->idle = false; pthread_cond_signal(&q->empty); pthread_mutex_unlock(&q->lock); return 0; } #else static int erofs_mkfs_go(const struct erofs_mkfs_btctx *ctx, 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(ctx, &item); } static void erofs_mkfs_flushjobs(struct erofs_sb_info *sbi) { } #endif struct erofs_mkfs_pending_jobitem { struct list_head list; struct erofs_mkfs_jobitem item; }; int erofs_mkfs_push_pending_job(struct list_head *pending, enum erofs_mkfs_jobtype type, void *elem, int size) { struct erofs_mkfs_pending_jobitem *pji; pji = malloc(sizeof(*pji)); if (!pji) return -ENOMEM; pji->item.type = type; if (size) memcpy(&pji->item.u, elem, size); pji->item._usize = size; list_add_tail(&pji->list, pending); return 0; } int erofs_mkfs_flush_pending_jobs(const struct erofs_mkfs_btctx *ctx, struct list_head *q) { struct erofs_mkfs_pending_jobitem *pji, *n; int err2, err; err = 0; list_for_each_entry_safe(pji, n, q, list) { list_del(&pji->list); err2 = erofs_mkfs_go(ctx, pji->item.type, &pji->item.u, pji->item._usize); free(pji); if (!err) err = err2; } return err; } static int erofs_mkfs_import_localdir(struct erofs_importer *im, struct erofs_inode *dir, u64 *nr_subdirs, unsigned int *i_nlink) { unsigned int __nlink; u64 __nr_subdirs; DIR *_dir; int ret; _dir = opendir(dir->i_srcpath); if (!_dir) { ret = -errno; erofs_err("failed to opendir at %s: %s", dir->i_srcpath, erofs_strerror(ret)); return ret; } __nr_subdirs = *nr_subdirs; __nlink = *i_nlink; while (1) { struct erofs_inode *inode; struct erofs_dentry *d; char buf[PATH_MAX]; struct dirent *dp; /* * 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) { ret = -errno; if (!ret) break; goto err_closedir; } if (is_dot_dotdot(dp->d_name)) 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_local(im, buf); if (IS_ERR(inode)) { ret = PTR_ERR(inode); goto err_closedir; } if (!dir->whiteouts && erofs_inode_is_whiteout(inode)) dir->whiteouts = true; d->inode = inode; d->type = erofs_mode_to_ftype(inode->i_mode); __nlink += S_ISDIR(inode->i_mode); erofs_dbg("file %s added (type %u)", buf, d->type); __nr_subdirs++; } closedir(_dir); *nr_subdirs = __nr_subdirs; *i_nlink = __nlink; return 0; err_closedir: closedir(_dir); return ret; } bool erofs_dentry_is_wht(struct erofs_sb_info *sbi, struct erofs_dentry *d) { if (!(d->flags & EROFS_DENTRY_FLAG_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 void erofs_dentry_kill(struct erofs_dentry *d) { list_del(&d->d_child); erofs_d_invalidate(d); free(d); } static int erofs_prepare_dir_inode(const struct erofs_mkfs_btctx *ctx, struct erofs_inode *dir) { struct erofs_importer *im = ctx->im; struct erofs_sb_info *sbi = im->sbi; struct erofs_dentry *d, *n; unsigned int i_nlink; u64 nr_subdirs; int ret; nr_subdirs = 0; i_nlink = 2; list_for_each_entry_safe(d, n, &dir->i_subdirs, d_child) { if (is_dot_dotdot(d->name)) { DBG_BUGON(1); erofs_dentry_kill(d); continue; } i_nlink += (d->type == EROFS_FT_DIR); ++nr_subdirs; } if (!ctx->rebuild) { ret = erofs_mkfs_import_localdir(im, dir, &nr_subdirs, &i_nlink); if (ret) return ret; } if (ctx->incremental && dir->dev == sbi->dev && !dir->opaque) { ret = erofs_rebuild_load_basedir(dir, &nr_subdirs, &i_nlink); if (ret) return ret; } if (im->params->ovlfs_strip && dir->whiteouts) { list_for_each_entry_safe(d, n, &dir->i_subdirs, d_child) { if (erofs_dentry_is_wht(sbi, d)) { erofs_dbg("remove whiteout %s", d->inode->i_srcpath); erofs_dentry_kill(d); --nr_subdirs; continue; } } } DBG_BUGON(nr_subdirs + 2 < i_nlink); ret = erofs_prepare_dir_file(im, dir, nr_subdirs); if (ret) return ret; if (IS_ROOT(dir) && ctx->incremental && !erofs_sb_has_48bit(sbi)) dir->datalayout = EROFS_INODE_FLAT_PLAIN; dir->i_nlink = i_nlink; /* * if there're too many subdirs as compact form, set nlink=1 * rather than upgrade to use extented form instead if possible. */ if (i_nlink > USHRT_MAX && dir->inode_isize == sizeof(struct erofs_inode_compact)) { if (dir->dot_omitted) dir->inode_isize = sizeof(struct erofs_inode_extended); else dir->i_nlink = 1; } return 0; } static int erofs_set_inode_fingerprint(struct erofs_inode *inode, int fd, erofs_off_t pos) { u8 ishare_xattr_prefix_id = inode->sbi->ishare_xattr_prefix_id; erofs_off_t remaining = inode->i_size; struct erofs_vfile vf = { .fd = fd }; struct sha256_state md; u8 out[32 + sizeof("sha256:") - 1]; int ret; if (!ishare_xattr_prefix_id) return 0; erofs_sha256_init(&md); do { u8 buf[32768]; ret = erofs_io_pread(&vf, buf, min_t(u64, remaining, sizeof(buf)), pos); if (ret < 0) return ret; if (ret > 0) erofs_sha256_process(&md, buf, ret); remaining -= ret; pos += ret; } while (remaining); erofs_sha256_done(&md, out + sizeof("sha256:") - 1); memcpy(out, "sha256:", sizeof("sha256:") - 1); return erofs_setxattr(inode, ishare_xattr_prefix_id, "", out, sizeof(out)); } static int erofs_mkfs_begin_nondirectory(const struct erofs_mkfs_btctx *btctx, struct erofs_inode *inode) { struct erofs_importer *im = btctx->im; struct erofs_mkfs_job_ndir_ctx ctx = { .inode = inode, .fd = -1 }; int ret; if (S_ISREG(inode->i_mode) && inode->i_size) { switch (inode->datasource) { case EROFS_INODE_DATA_SOURCE_DISKBUF: ctx.fd = erofs_diskbuf_getfd(inode->i_diskbuf, &ctx.fpos); if (ctx.fd < 0) return ctx.fd; break; case EROFS_INODE_DATA_SOURCE_LOCALPATH: ctx.fd = open(inode->i_srcpath, O_RDONLY | O_BINARY); if (ctx.fd < 0) return -errno; break; default: goto out; } if (S_ISREG(inode->i_mode) && inode->i_size) { ret = erofs_set_inode_fingerprint(inode, ctx.fd, ctx.fpos); if (ret < 0) return ret; } if (inode->sbi->available_compr_algs && erofs_file_is_compressible(im, inode)) { ctx.ictx = erofs_prepare_compressed_file(im, inode); if (IS_ERR(ctx.ictx)) return PTR_ERR(ctx.ictx); erofs_bind_compressed_file_with_fd(ctx.ictx, ctx.fd, ctx.fpos); ret = erofs_begin_compressed_file(ctx.ictx); if (ret) return ret; } } out: return erofs_mkfs_go(btctx, EROFS_MKFS_JOB_NDIR, &ctx, sizeof(ctx)); } static int erofs_mkfs_handle_inode(const struct erofs_mkfs_btctx *ctx, struct erofs_inode *inode) { const char *relpath = erofs_fspath(inode->i_srcpath); struct erofs_importer *im = ctx->im; const struct erofs_importer_params *params = im->params; char *trimmed; int ret; trimmed = erofs_trim_for_progressinfo(*relpath ? relpath : "/", sizeof("Processing ...") - 1); erofs_update_progressinfo("Processing %s ...", trimmed); free(trimmed); if (erofs_should_use_inode_extended(im, inode, inode->i_srcpath)) { if (params->force_inodeversion == EROFS_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 (S_ISDIR(inode->i_mode)) { ret = erofs_prepare_dir_inode(ctx, inode); if (ret < 0) return ret; } if (!ctx->rebuild && !params->no_xattrs) { ret = erofs_scan_file_xattrs(inode); if (ret < 0) return ret; } /* strip all unnecessary overlayfs xattrs when ovlfs_strip is enabled */ if (params->ovlfs_strip) erofs_clear_opaque_xattr(inode); else if (inode->whiteouts) erofs_set_origin_xattr(inode); if (!S_ISDIR(inode->i_mode)) { ret = erofs_mkfs_begin_nondirectory(ctx, inode); } else { ret = erofs_mkfs_go(ctx, EROFS_MKFS_JOB_DIR, &inode, sizeof(inode)); } erofs_info("file %s dumped (mode %05o)", *relpath ? relpath : "/", 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(const struct erofs_mkfs_btctx *ctx) { struct erofs_importer *im = ctx->im; struct erofs_inode *root = im->root; struct erofs_sb_info *sbi = root->sbi; struct erofs_inode *dumpdir = erofs_igrab(root); bool grouped_dirdata = im->params->grouped_dirdata; LIST_HEAD(pending_dirs); 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 (ctx->incremental) { root->dev = root->sbi->dev; root->i_ino[1] = sbi->root_nid; erofs_remove_ihash(root); 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 = erofs_mkfs_handle_inode(ctx, root); if (err) return err; /* assign root NID immediately for non-incremental builds */ if (!ctx->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->flags & EROFS_DENTRY_FLAG_VALIDNID)) continue; if (!erofs_inode_visited(inode)) { DBG_BUGON(ctx->rebuild && (inode->i_nlink == 1 || S_ISDIR(inode->i_mode)) && erofs_parent_inode(inode) != dir); erofs_mark_parent_inode(inode, dir); err = erofs_mkfs_handle_inode(ctx, inode); if (err) break; if (S_ISDIR(inode->i_mode)) { *last = inode; last = &inode->next_dirwrite; (void)erofs_igrab(inode); } } else if (!ctx->rebuild) { ++inode->i_nlink; } } *last = dumpdir; /* fixup the last (or the only) one */ dumpdir = head; err2 = grouped_dirdata ? erofs_mkfs_push_pending_job(&pending_dirs, EROFS_MKFS_JOB_DIR_BH, &dir, sizeof(dir)) : erofs_mkfs_go(ctx, EROFS_MKFS_JOB_DIR_BH, &dir, sizeof(dir)); if (err || err2) { if (!err) err = err2; break; } } while (dumpdir); err2 = erofs_mkfs_flush_pending_jobs(ctx, &pending_dirs); return err ? err : err2; } struct erofs_mkfs_buildtree_ctx { struct erofs_importer *im; bool rebuild, incremental; }; #ifndef EROFS_MT_ENABLED #define __erofs_mkfs_build_tree erofs_mkfs_build_tree #endif static int __erofs_mkfs_build_tree(const struct erofs_mkfs_btctx *ctx) { struct erofs_importer *im = ctx->im; if (!ctx->rebuild) { struct erofs_importer_params *params = im->params; struct stat st; int err; err = lstat(params->source, &st); if (err) return -errno; err = erofs_fill_inode(im, im->root, &st, params->source); if (err) return err; } return erofs_mkfs_dump_tree(ctx); } #ifdef EROFS_MT_ENABLED #ifdef HAVE_SYS_RESOURCE_H #include #endif static int erofs_get_fdlimit(void) { #if defined(HAVE_SYS_RESOURCE_H) && defined(HAVE_GETRLIMIT) struct rlimit rlim; int err; err = getrlimit(RLIMIT_NOFILE, &rlim); if (err < 0) return _POSIX_OPEN_MAX; if (rlim.rlim_cur == RLIM_INFINITY) return 0; return rlim.rlim_cur; #else return _POSIX_OPEN_MAX; #endif } static int erofs_mkfs_build_tree(struct erofs_mkfs_btctx *ctx) { struct erofs_importer *im = ctx->im; struct erofs_importer_params *params = im->params; struct erofs_sb_info *sbi = im->sbi; struct erofs_mkfs_dfops *q; int err, err2; void *retval; q = calloc(1, sizeof(*q)); if (!q) return -ENOMEM; if (params->mt_async_queue_limit) { q->entries = params->mt_async_queue_limit; if (q->entries & (q->entries - 1)) { free(q); return -EINVAL; } } else { err = roundup_pow_of_two(erofs_get_fdlimit()) >> 1; q->entries = err && err < 32768 ? err : 32768; } erofs_dbg("Set the asynchronous queue size to %u", q->entries); 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, ctx); if (err) goto fail; err = __erofs_mkfs_build_tree(ctx); erofs_mkfs_go(ctx, ~0, NULL, 0); err2 = pthread_join(sbi->dfops_worker, &retval); DBG_BUGON(!q->exited); if (!err || err == -ECHILD) { err = err2; if (!err) err = (intptr_t)retval; } 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 int erofs_importer_load_tree(struct erofs_importer *im, bool rebuild, bool incremental) { if (__erofs_unlikely(incremental && erofs_sb_has_metabox(im->sbi))) { erofs_err("Metadata-compressed filesystems don't implement incremental builds for now"); return -EOPNOTSUPP; } return erofs_mkfs_build_tree(&((struct erofs_mkfs_btctx) { .im = im, .rebuild = rebuild, .incremental = incremental, })); } struct erofs_inode *erofs_mkfs_build_special_from_fd(struct erofs_importer *im, int fd, const char *name) { struct erofs_sb_info *sbi = im->sbi; struct erofs_inode *inode; struct stat st; 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 (erofs_is_special_identifier(name)) { st.st_uid = st.st_gid = 0; st.st_nlink = 0; } ret = erofs_fill_inode(im, inode, &st, name); if (ret) { free(inode); return ERR_PTR(ret); } if (sbi->available_compr_algs && erofs_file_is_compressible(im, inode)) { ictx = erofs_prepare_compressed_file(im, inode); if (IS_ERR(ictx)) return ERR_CAST(ictx); erofs_bind_compressed_file_with_fd(ictx, fd, 0); ret = erofs_begin_compressed_file(ictx); if (ret) return ERR_PTR(ret); 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); } inode->datalayout = EROFS_INODE_FLAT_INLINE; ret = erofs_write_unencoded_data(inode, &(struct erofs_vfile){ .fd = fd }, 0, inode->datasource == EROFS_INODE_DATA_SOURCE_DISKBUF, false); if (ret) return ERR_PTR(ret); out: erofs_prepare_inode_buffer(im, inode); erofs_write_tail_end(im, 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 (erofs_sb_has_48bit(sbi) || root->nid <= UINT16_MAX) { 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_make_empty_root_inode(struct erofs_importer *im, struct erofs_sb_info *sbi) { struct erofs_importer_params *params = im ? im->params : NULL; 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_uid = (!params || params->fixed_uid == -1) ? getuid() : params->fixed_uid; root->i_gid = (!params || params->fixed_gid == -1) ? getgid() : params->fixed_gid; root->i_parent = root; root->i_mtime = root->sbi->epoch + root->sbi->build_time; root->i_mtime_nsec = root->sbi->fixed_nsec; root->i_nlink = 2; return root; } erofs-utils-1.9.1/lib/io.c000066400000000000000000000340031515160260000153130ustar00rootroot00000000000000// 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; } ssize_t erofs_io_pwritev(struct erofs_vfile *vf, const struct iovec *iov, int iovcnt, u64 pos) { ssize_t ret, written; int i; if (__erofs_unlikely(cfg.c_dry_run)) return 0; #ifdef HAVE_PWRITEV if (!vf->ops) { ret = pwritev(vf->fd, iov, iovcnt, pos + vf->offset); if (ret < 0) return -errno; return ret; } #endif if (vf->ops && vf->ops->pwritev) return vf->ops->pwritev(vf, iov, iovcnt, pos); written = 0; for (i = 0; i < iovcnt; ++i) { ret = erofs_io_pwrite(vf, iov[i].iov_base, pos, iov[i].iov_len); if (ret < iov[i].iov_len) { if (ret < 0) return ret; return written + ret; } written += iov[i].iov_len; pos += iov[i].iov_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; } static const char erofs_zeroed[EROFS_MAX_BLOCK_SIZE]; int __erofs_0write(int fd, size_t len) { int err = 0; while (len) { u32 count = min_t(u64, sizeof(erofs_zeroed), len); err = write(fd, erofs_zeroed, count); if (err <= 0) { if (err < 0) err = -errno; break; } len -= err; } return err < 0 ? err : len; } int erofs_io_fallocate(struct erofs_vfile *vf, u64 offset, size_t len, bool zeroout) { 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 > sizeof(erofs_zeroed)) { ret = erofs_io_pwrite(vf, erofs_zeroed, offset, sizeof(erofs_zeroed)); if (ret < 0) return (int)ret; len -= ret; offset += ret; } return erofs_io_pwrite(vf, erofs_zeroed, 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, size_t len, u64 pos) { ssize_t ret, read = 0; if (__erofs_unlikely(cfg.c_dry_run)) return 0; if (vf->ops) return vf->ops->pread(vf, buf, len, pos); 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) { erofs_io_close(&sbi->bdev); 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, len, offset); } else { read = erofs_io_pread(&sbi->bdev, buf, len, offset); } 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; } ssize_t erofs_io_write(struct erofs_vfile *vf, void *buf, size_t len) { if (vf->ops) return vf->ops->write(vf, buf, len); return __erofs_io_write(vf->fd, buf, len); } #ifdef HAVE_SYS_SENDFILE_H #include #endif off_t erofs_io_lseek(struct erofs_vfile *vf, u64 offset, int whence) { off_t ret; if (vf->ops) return vf->ops->lseek(vf, offset, whence); ret = lseek(vf->fd, offset, whence); return ret < 0 ? -errno : ret; } ssize_t erofs_io_sendfile(struct erofs_vfile *vout, struct erofs_vfile *vin, off_t *pos, size_t count) { ssize_t read, written; size_t rem = count; if (vin->ops || vout->ops) { if (vin->ops && vin->ops->sendfile) return vin->ops->sendfile(vout, vin, pos, count); if (vout->ops && vout->ops->sendfile) return vout->ops->sendfile(vout, vin, pos, count); written = 0; } #if defined(HAVE_SYS_SENDFILE_H) && defined(HAVE_SENDFILE) else do { written = sendfile(vout->fd, vin->fd, pos, rem); if (written <= 0) { if (written < 0) { written = -errno; if (written == -EOVERFLOW && pos) written = 0; } break; } rem -= written; } while (written); #endif while (rem) { char buf[max(EROFS_MAX_BLOCK_SIZE, 32768)]; read = min_t(u64, rem, sizeof(buf)); if (pos) read = erofs_io_pread(vin, buf, read, *pos); else read = erofs_io_read(vin, buf, read); if (read <= 0) { written = read; break; } rem -= read; if (pos) *pos += read; do { written = erofs_io_write(vout, buf, read); if (written < 0) return written; read -= written; } while (read); } return written < 0 ? written : count - rem; } 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; } void erofs_io_close(struct erofs_vfile *vf) { if (vf->ops) { vf->ops->close(vf); return; } close(vf->fd); vf->fd = -1; } erofs-utils-1.9.1/lib/kite_deflate.c000066400000000000000000001052271515160260000173330ustar00rootroot00000000000000// 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) #define kMaxLen (kTableDirectLevels - 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; } 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) { u64 b = s->outlen - s->pos_out; 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; } } if (s->costbits + s->bitpos + 3 + kstaticHuff_litLenLevels[kSymbolEndOfBlock] >= b * 8) { DBG_BUGON(s->costbits + s->bitpos > b * 8); s->lastblock = true; } } 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, eos = 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; } eos = (s->pos_in >= s->inlen); if (eos || s->symbols >= s->max_symbols) { s->lastblock |= eos; flush = true; break; } } if (flush) { if (eos && s->prev_valid) { if (!kite_deflate_tally(s, mf->matches + s->prev_longest)) s->prev_valid = false; } kite_deflate_endblock(s); } 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 #ifdef HAVE_ZLIB #include static int kite_deflate_decompress_zlib(const u8 *in, size_t inlen, u8 *out, size_t out_capacity) { z_stream z; int res; memset(&z, 0, sizeof(z)); res = inflateInit2(&z, -15); if (res != Z_OK) { DBG_BUGON(1); return -1; } z.next_in = (void *)in; z.avail_in = inlen; z.next_out = (void *)out; z.avail_out = out_capacity; res = inflate(&z, Z_FINISH); if (res != Z_STREAM_END) { DBG_BUGON(1); return -1; } inflateEnd(&z); return out_capacity - z.avail_out; } static void kite_deflate_decompress_test_zlib(const u8 *in, size_t inlen, u8 *out, size_t out_capacity, const u8 *expected_out, size_t expected_outlen) { int outlen; outlen = kite_deflate_decompress_zlib(in, inlen, out, out_capacity); BUG_ON(outlen != expected_outlen); if (expected_outlen) BUG_ON(memcmp(out, expected_out, expected_outlen)); } #endif static void kite_deflate_decompress_test(const u8 *in, size_t inlen, u8 *out, size_t out_capacity, const u8 *expected_out, size_t expected_outlen) { #ifdef HAVE_ZLIB kite_deflate_decompress_test_zlib(in, inlen, out, out_capacity, expected_out, expected_outlen); #endif } static void kite_deflate_test1(void) { struct kite_deflate *s; u8 enc[3], vb[10]; s = kite_deflate_init(1, 0); BUG_ON(!s || IS_ERR(s)); s->out = enc; s->outlen = sizeof(enc); writebits(s, (kFixedHuffman << 1) + 1, 3); writebits(s, kstaticHuff_mainCodes[kSymbolEndOfBlock], kstaticHuff_litLenLevels[kSymbolEndOfBlock]); flushbits(s); kite_deflate_decompress_test(enc, s->pos_out, vb, sizeof(vb), NULL, 0); } int main(int argc, char *argv[]) { unsigned int srcsize, outsize, dstsize, level; struct kite_deflate *s; u8 out[1048576], *buf; u64 filelength; u8 *vbuf __maybe_unused; int fd; if (argc < 2) { kite_deflate_test1(); fprintf(stdout, "PASS\n"); return 0; } dstsize = level = 0; fd = open(argv[1], O_RDONLY); BUG_ON(fd < 0); if (argc > 2) { dstsize = atoi(argv[2]); if (argc > 3) level = atoi(argv[3]); } if (!dstsize || dstsize > sizeof(out)) dstsize = 4096; if (!level) level = 9; s = kite_deflate_init(level, 0); BUG_ON(IS_ERR(s)); filelength = lseek(fd, 0, SEEK_END); buf = mmap(NULL, filelength, PROT_READ, MAP_SHARED, fd, 0); BUG_ON(buf == MAP_FAILED); close(fd); srcsize = filelength; outsize = kite_deflate_destsize(s, buf, out, &srcsize, dstsize); kite_deflate_end(s); #ifdef HAVE_ZLIB vbuf = malloc(srcsize); if (!vbuf) { fprintf(stderr, "failed to allocate test buffer\n"); } else { BUG_ON(kite_deflate_decompress_zlib(out, outsize, vbuf, srcsize) != srcsize); BUG_ON(memcmp(buf, vbuf, srcsize)); free(vbuf); } #endif BUG_ON(fwrite(out, outsize, 1, stdout) != 1); return 0; } #endif erofs-utils-1.9.1/lib/liberofs_base64.h000066400000000000000000000003511515160260000176610ustar00rootroot00000000000000#ifndef __EROFS_LIB_LIBEROFS_BASE64_H #define __EROFS_LIB_LIBEROFS_BASE64_H #include "erofs/defs.h" int erofs_base64_encode(const u8 *src, int srclen, char *dst); int erofs_base64_decode(const char *src, int len, u8 *dst); #endif erofs-utils-1.9.1/lib/liberofs_cache.h000066400000000000000000000067131515160260000176500ustar00rootroot00000000000000/* SPDX-License-Identifier: GPL-2.0+ OR Apache-2.0 */ /* * Copyright (C) 2018 HUAWEI, Inc. * http://www.huawei.com * Copyright (C) 2025 Alibaba Cloud */ #ifndef __EROFS_LIB_LIBEROFS_CACHE_H #define __EROFS_LIB_LIBEROFS_CACHE_H #ifdef __cplusplus extern "C" { #endif #include #include "erofs/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, bool abort); }; 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 { /* 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; struct erofs_sb_info *sbi; struct erofs_vfile *vf; /* last mapped buffer block to accelerate erofs_mapbh() */ struct erofs_buffer_block *last_mapped_block; erofs_blk_t tail_blkaddr, metablkcnt; /* 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 == EROFS_NULL_ADDR) return EROFS_NULL_ADDR; 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, struct erofs_vfile *vf); 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); void erofs_buffer_exit(struct erofs_bufmgr *bmgr); #ifdef __cplusplus } #endif #endif erofs-utils-1.9.1/lib/liberofs_compress.h000066400000000000000000000021751515160260000204360ustar00rootroot00000000000000/* SPDX-License-Identifier: GPL-2.0+ OR Apache-2.0 */ /* * Copyright (C) 2019 HUAWEI, Inc. * http://www.huawei.com/ * Copyright (C) 2025 Alibaba Cloud */ #ifndef __EROFS_LIB_LIBEROFS_COMPRESS_H #define __EROFS_LIB_LIBEROFS_COMPRESS_H #include "erofs/importer.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_prepare_compressed_file(struct erofs_importer *im, struct erofs_inode *inode); void erofs_bind_compressed_file_with_fd(struct z_erofs_compress_ictx *ictx, int fd, u64 fpos); int erofs_begin_compressed_file(struct z_erofs_compress_ictx *ictx); int erofs_write_compressed_file(struct z_erofs_compress_ictx *ictx); int erofs_begin_compress_dir(struct erofs_importer *im, struct erofs_inode *inode); int erofs_write_compress_dir(struct erofs_inode *inode, struct erofs_vfile *vf); int z_erofs_compress_init(struct erofs_importer *im); int z_erofs_compress_exit(struct erofs_sb_info *sbi); int z_erofs_mt_global_exit(void); #endif erofs-utils-1.9.1/lib/liberofs_dockerconfig.h000066400000000000000000000014621515160260000212360ustar00rootroot00000000000000/* SPDX-License-Identifier: GPL-2.0+ OR Apache-2.0 */ /* * Copyright (C) 2026 Tencent, Inc. * http://www.tencent.com/ */ #ifndef __EROFS_DOCKER_CONFIG_H #define __EROFS_DOCKER_CONFIG_H #include "erofs/internal.h" #define DOCKER_REGISTRY "docker.io" #define DOCKER_API_REGISTRY "registry-1.docker.io" #define DOCKER_HUB_AUTH_KEY "https://index.docker.io/v1/" struct erofs_docker_credential { char *username; char *password; }; /** * erofs_docker_config_lookup - look up registry credentials from Docker config * @registry: the registry hostname (e.g. "index.docker.io") * @cred: output credential structure */ int erofs_docker_config_lookup(const char *registry, struct erofs_docker_credential *cred); void erofs_docker_credential_free(struct erofs_docker_credential *cred); #endif erofs-utils-1.9.1/lib/liberofs_fragments.h000066400000000000000000000016221515160260000205650ustar00rootroot00000000000000/* SPDX-License-Identifier: GPL-2.0+ OR Apache-2.0 */ /* * Copyright (C) 2022, Coolpad Group Limited. * Copyright (C) 2025 Alibaba Cloud */ #ifndef __EROFS_LIB_LIBEROFS_FRAGMENTS_H #define __EROFS_LIB_LIBEROFS_FRAGMENTS_H #include "erofs/internal.h" struct erofs_importer; u32 z_erofs_fragments_tofh(struct erofs_inode *inode, struct erofs_vfile *vf, erofs_off_t fpos); int erofs_fragment_findmatch(struct erofs_inode *inode, struct erofs_vfile *vf, erofs_off_t fpos, u32 tofh); int erofs_pack_file_from_fd(struct erofs_inode *inode, struct erofs_vfile *vf, erofs_off_t fpos, u32 tofh); int erofs_fragment_pack(struct erofs_inode *inode, void *data, erofs_off_t pos, erofs_off_t len, u32 tofh, bool tail); int erofs_fragment_commit(struct erofs_inode *inode, u32 tofh); int erofs_flush_packed_inode(struct erofs_importer *im); int erofs_packedfile(struct erofs_sb_info *sbi); #endif erofs-utils-1.9.1/lib/liberofs_gzran.h000066400000000000000000000013331515160260000177170ustar00rootroot00000000000000/* SPDX-License-Identifier: GPL-2.0+ OR Apache-2.0 */ /* * Copyright (C) 2025 Alibaba Cloud */ #ifndef __EROFS_LIB_LIBEROFS_GZRAN_H #define __EROFS_LIB_LIBEROFS_GZRAN_H #include "erofs/io.h" #define EROFS_GZRAN_WINSIZE 32768 struct erofs_gzran_builder; struct erofs_gzran_builder *erofs_gzran_builder_init(struct erofs_vfile *vf, u32 span_size); int erofs_gzran_builder_read(struct erofs_gzran_builder *gb, char *window); int erofs_gzran_builder_export_zinfo(struct erofs_gzran_builder *gb, struct erofs_vfile *zinfo_vf); int erofs_gzran_builder_final(struct erofs_gzran_builder *gb); struct erofs_vfile *erofs_gzran_zinfo_open(struct erofs_vfile *vin, void *zinfo_buf, unsigned int len); #endif erofs-utils-1.9.1/lib/liberofs_metabox.h000066400000000000000000000015631515160260000202420ustar00rootroot00000000000000/* SPDX-License-Identifier: GPL-2.0+ OR Apache-2.0 */ #ifndef __EROFS_LIB_LIBEROFS_METABOX_H #define __EROFS_LIB_LIBEROFS_METABOX_H #include "erofs/internal.h" #define EROFS_META_NEW_ADDR ((u32)-1ULL) extern const char *erofs_metabox_identifier; #define EROFS_METABOX_INODE erofs_metabox_identifier static inline bool erofs_is_metabox_inode(struct erofs_inode *inode) { return inode->i_srcpath == EROFS_METABOX_INODE; } static inline bool erofs_has_meta_zone(struct erofs_sb_info *sbi) { return sbi->m2gr || sbi->meta_blkaddr == EROFS_META_NEW_ADDR; } struct erofs_importer; void erofs_metadata_exit(struct erofs_sb_info *sbi); int erofs_metadata_init(struct erofs_sb_info *sbi); struct erofs_bufmgr *erofs_metadata_bmgr(struct erofs_sb_info *sbi, bool mbox); int erofs_metabox_iflush(struct erofs_importer *im); int erofs_metazone_flush(struct erofs_sb_info *sbi); #endif erofs-utils-1.9.1/lib/liberofs_nbd.h000066400000000000000000000030471515160260000173450ustar00rootroot00000000000000/* SPDX-License-Identifier: GPL-2.0+ OR Apache-2.0 */ /* * Copyright (C) 2025 Alibaba Cloud */ #ifndef __EROFS_LIB_LIBEROFS_NBD_H #define __EROFS_LIB_LIBEROFS_NBD_H #include "erofs/defs.h" #define EROFS_NBD_MAJOR 43 /* Network block device */ /* Supported request types */ enum { EROFS_NBD_CMD_READ = 0, EROFS_NBD_CMD_WRITE = 1, EROFS_NBD_CMD_DISC = 2, EROFS_NBD_CMD_FLUSH = 3, EROFS_NBD_CMD_TRIM = 4, /* userspace defines additional extension commands */ EROFS_NBD_CMD_WRITE_ZEROES = 6, }; struct erofs_nbd_request { __be32 magic; /* NBD_REQUEST_MAGIC */ u32 type; /* See NBD_CMD_* */ union { __be64 cookie; /* Opaque identifier for request */ char handle[8]; /* older spelling of cookie */ }; u64 from; u32 len; } __packed; /* 30-day timeout for NBD recovery */ #define EROFS_NBD_DEAD_CONN_TIMEOUT (3600 * 24 * 30) long erofs_nbd_in_service(int nbdnum); int erofs_nbd_devscan(void); int erofs_nbd_connect(int nbdfd, int blkbits, u64 blocks); char *erofs_nbd_get_identifier(int nbdnum); int erofs_nbd_get_index_from_minor(int minor); int erofs_nbd_do_it(int nbdfd); int erofs_nbd_get_request(int skfd, struct erofs_nbd_request *rq); int erofs_nbd_send_reply_header(int skfd, __le64 cookie, int err); int erofs_nbd_disconnect(int nbdfd); int erofs_nbd_nl_connect(int *index, int blkbits, u64 blocks, const char *identifier); int erofs_nbd_nl_reconnect(int index, const char *identifier); int erofs_nbd_nl_reconfigure(int index, const char *identifier, bool autoclear); int erofs_nbd_nl_disconnect(int index); #endif erofs-utils-1.9.1/lib/liberofs_oci.h000066400000000000000000000043041515160260000173510ustar00rootroot00000000000000/* SPDX-License-Identifier: GPL-2.0+ OR Apache-2.0 */ /* * Copyright (C) 2025 Tencent, Inc. * http://www.tencent.com/ */ #ifndef __EROFS_OCI_H #define __EROFS_OCI_H #include #ifdef __cplusplus extern "C" { #endif struct CURL; struct erofs_importer; /* * struct ocierofs_config - OCI configuration structure * @image_ref: OCI image reference (e.g., "ubuntu:latest", "myregistry.com/app:v1.0") * @platform: target platform in "os/arch" format (e.g., "linux/amd64") * @username: username for authentication (optional) * @password: password for authentication (optional) * @blob_digest: specific blob digest to extract (NULL for all layers) * @layer_index: specific layer index to extract (negative for all layers) * @insecure: use HTTP for registry communication (optional) * * Configuration structure for OCI image parameters including registry * location, image identification, platform specification, and authentication * credentials. */ struct ocierofs_config { char *image_ref; char *platform; char *username; char *password; char *blob_digest; int layer_index; char *tarindex_path; char *zinfo_path; bool insecure; }; struct ocierofs_layer_info { char *digest; char *media_type; u64 size; }; struct ocierofs_ctx { struct CURL *curl; char *auth_header; bool using_basic; char *registry; char *repository; char *platform; char *tag; char *manifest_digest; struct ocierofs_layer_info **layers; char *blob_digest; int layer_count; const char *schema; }; struct ocierofs_iostream { struct ocierofs_ctx *ctx; u64 offset; }; /* * ocierofs_build_trees - Build file trees from OCI container image layers * @importer: erofs importer to populate * @cfg: oci configuration * * Return: 0 on success, negative errno on failure */ int ocierofs_build_trees(struct erofs_importer *importer, const struct ocierofs_config *cfg); int ocierofs_io_open(struct erofs_vfile *vf, const struct ocierofs_config *cfg); char *ocierofs_encode_userpass(const char *username, const char *password); int ocierofs_decode_userpass(const char *b64, char **out_user, char **out_pass); const char *ocierofs_get_platform_spec(void); #ifdef __cplusplus } #endif #endif /* __EROFS_OCI_H */ erofs-utils-1.9.1/lib/liberofs_private.h000066400000000000000000000010671515160260000202540ustar00rootroot00000000000000/* 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.9.1/lib/liberofs_rebuild.h000066400000000000000000000012201515160260000202170ustar00rootroot00000000000000/* SPDX-License-Identifier: GPL-2.0+ OR Apache-2.0 */ #ifndef __EROFS_LIB_LIBEROFS_REBUILD_H #define __EROFS_LIB_LIBEROFS_REBUILD_H #include "erofs/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); int erofs_rebuild_load_basedir(struct erofs_inode *dir, u64 *nr_subdirs, unsigned int *i_nlink); #endif erofs-utils-1.9.1/lib/liberofs_s3.h000066400000000000000000000017771515160260000171370ustar00rootroot00000000000000/* SPDX-License-Identifier: GPL-2.0+ OR Apache-2.0 */ /* * Copyright (C) 2025 HUAWEI, Inc. * http://www.huawei.com/ * Created by Yifan Zhao */ #ifndef __EROFS_S3_H #define __EROFS_S3_H #ifdef __cplusplus extern "C" { #endif enum s3erofs_url_style { S3EROFS_URL_STYLE_PATH, // Path style: https://s3.amazonaws.com/bucket/object S3EROFS_URL_STYLE_VIRTUAL_HOST, // Virtual host style: https://bucket.s3.amazonaws.com/object }; enum s3erofs_signature_version { S3EROFS_SIGNATURE_VERSION_2, S3EROFS_SIGNATURE_VERSION_4, }; #define S3_ACCESS_KEY_LEN 256 #define S3_SECRET_KEY_LEN 256 struct erofs_s3 { void *easy_curl; const char *endpoint; const char *region; char access_key[S3_ACCESS_KEY_LEN + 1]; char secret_key[S3_SECRET_KEY_LEN + 1]; enum s3erofs_url_style url_style; enum s3erofs_signature_version sig; }; int s3erofs_build_trees(struct erofs_importer *im, struct erofs_s3 *s3, const char *path, bool fillzero); #ifdef __cplusplus } #endif #endif erofs-utils-1.9.1/lib/liberofs_uuid.h000066400000000000000000000004401515160260000175420ustar00rootroot00000000000000/* 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.9.1/lib/liberofs_xxhash.h000066400000000000000000000024121515160260000201000ustar00rootroot00000000000000/* 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.9.1/lib/metabox.c000066400000000000000000000065651515160260000163570ustar00rootroot00000000000000// SPDX-License-Identifier: GPL-2.0+ OR Apache-2.0 #include #include "erofs/inode.h" #include "erofs/importer.h" #include "erofs/print.h" #include "liberofs_cache.h" #include "liberofs_private.h" #include "liberofs_metabox.h" const char *erofs_metabox_identifier = "metabox"; struct erofs_metamgr { struct erofs_vfile vf; struct erofs_bufmgr *bmgr; }; static void erofs_metamgr_exit(struct erofs_metamgr *m2gr) { DBG_BUGON(!m2gr->bmgr); erofs_buffer_exit(m2gr->bmgr); erofs_io_close(&m2gr->vf); free(m2gr); } static int erofs_metamgr_init(struct erofs_sb_info *sbi, struct erofs_metamgr *m2gr) { int ret; ret = erofs_tmpfile(); if (ret < 0) return ret; m2gr->vf = (struct erofs_vfile){ .fd = ret }; m2gr->bmgr = erofs_buffer_init(sbi, 0, &m2gr->vf); if (!m2gr->bmgr) return -ENOMEM; return 0; } void erofs_metadata_exit(struct erofs_sb_info *sbi) { if (sbi->m2gr) { erofs_metamgr_exit(sbi->m2gr); sbi->m2gr = NULL; } if (sbi->mxgr) { erofs_metamgr_exit(sbi->mxgr); sbi->mxgr = NULL; } } int erofs_metadata_init(struct erofs_sb_info *sbi) { struct erofs_metamgr *m2gr; int ret; if (!sbi->m2gr && sbi->metazone_startblk == EROFS_META_NEW_ADDR) { m2gr = malloc(sizeof(*m2gr)); if (!m2gr) return -ENOMEM; ret = erofs_metamgr_init(sbi, m2gr); if (ret) goto err_free; sbi->m2gr = m2gr; /* FIXME: sbi->meta_blkaddr should be 0 for 48-bit layouts */ sbi->meta_blkaddr = EROFS_META_NEW_ADDR; } if (!sbi->mxgr && erofs_sb_has_metabox(sbi)) { m2gr = malloc(sizeof(*m2gr)); if (!m2gr) return -ENOMEM; ret = erofs_metamgr_init(sbi, m2gr); if (ret) goto err_free; sbi->mxgr = m2gr; } return 0; err_free: free(m2gr); return ret; } struct erofs_bufmgr *erofs_metadata_bmgr(struct erofs_sb_info *sbi, bool mbox) { if (mbox) { if (sbi->mxgr) return sbi->mxgr->bmgr; } else if (sbi->m2gr) { return sbi->m2gr->bmgr; } return NULL; } int erofs_metabox_iflush(struct erofs_importer *im) { struct erofs_sb_info *sbi = im->sbi; struct erofs_metamgr *mxgr = sbi->mxgr; struct erofs_inode *inode; int err; if (!mxgr || !erofs_sb_has_metabox(sbi)) return -EINVAL; err = erofs_bflush(mxgr->bmgr, NULL); if (err) return err; if (erofs_io_lseek(&mxgr->vf, 0, SEEK_END) <= 0) return 0; inode = erofs_mkfs_build_special_from_fd(im, mxgr->vf.fd, EROFS_METABOX_INODE); sbi->metabox_nid = erofs_lookupnid(inode); erofs_iput(inode); return 0; } int erofs_metazone_flush(struct erofs_sb_info *sbi) { struct erofs_metamgr *m2gr = sbi->m2gr; struct erofs_buffer_head *bh; struct erofs_bufmgr *m2bgr; erofs_blk_t meta_blkaddr; u64 length, pos_out; int ret, count; if (!m2gr) return 0; bh = erofs_balloc(sbi->bmgr, DATA, 0, 0); if (!bh) return PTR_ERR(bh); erofs_mapbh(NULL, bh->block); pos_out = erofs_btell(bh, false); meta_blkaddr = pos_out >> sbi->blkszbits; sbi->metazone_startblk = meta_blkaddr; m2bgr = m2gr->bmgr; ret = erofs_bflush(m2bgr, NULL); if (ret) return ret; length = erofs_mapbh(m2bgr, NULL) << sbi->blkszbits; ret = erofs_bh_balloon(bh, length); if (ret < 0) return ret; do { count = min_t(erofs_off_t, length, INT_MAX); ret = erofs_io_xcopy(sbi->bmgr->vf, pos_out, &m2gr->vf, count, false); if (ret < 0) break; pos_out += count; } while (length -= count); bh->op = &erofs_drop_directly_bhops; erofs_bdrop(bh, false); sbi->meta_blkaddr += meta_blkaddr; return 0; } erofs-utils-1.9.1/lib/namei.c000066400000000000000000000174771515160260000160150ustar00rootroot00000000000000// 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) { struct erofs_sb_info *sbi = vi->sbi; erofs_blk_t blkaddr = erofs_blknr(sbi, erofs_iloc(vi)); unsigned int ofs = erofs_blkoff(sbi, erofs_iloc(vi)); bool in_mbox = erofs_inode_in_metabox(vi); struct erofs_buf buf = __EROFS_BUF_INITIALIZER; erofs_blk_t addrmask = BIT_ULL(48) - 1; struct erofs_inode_extended *die, copied; struct erofs_inode_compact *dic; unsigned int ifmt; void *ptr; int err = 0; ptr = erofs_read_metabuf(&buf, sbi, erofs_pos(sbi, blkaddr), in_mbox); if (IS_ERR(ptr)) { err = PTR_ERR(ptr); erofs_err("failed to get inode (nid: %llu) page, err %d", vi->nid, err); goto err_out; } dic = ptr + ofs; ifmt = le16_to_cpu(dic->i_format); if (ifmt & ~EROFS_I_ALL) { erofs_err("unsupported i_format %u of nid %llu", ifmt, vi->nid); err = -EOPNOTSUPP; goto err_out; } 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); err = -EOPNOTSUPP; goto err_out; } switch (erofs_inode_version(ifmt)) { case EROFS_INODE_LAYOUT_EXTENDED: vi->inode_isize = sizeof(struct erofs_inode_extended); /* check if the extended inode acrosses block boundary */ if (ofs + vi->inode_isize <= erofs_blksiz(sbi)) { ofs += vi->inode_isize; die = (struct erofs_inode_extended *)dic; copied.i_u = die->i_u; copied.i_nb = die->i_nb; } else { const unsigned int gotten = erofs_blksiz(sbi) - ofs; memcpy(&copied, dic, gotten); ptr = erofs_read_metabuf(&buf, sbi, erofs_pos(sbi, blkaddr + 1), in_mbox); if (IS_ERR(ptr)) { err = PTR_ERR(ptr); erofs_err("failed to get inode payload block (nid: %llu), err %d", vi->nid, err); goto err_out; } ofs = vi->inode_isize - gotten; memcpy((u8 *)&copied + gotten, ptr, ofs); die = &copied; } 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); copied.i_u = die->i_u; copied.i_nb = die->i_nb; 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); ofs += vi->inode_isize; 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); copied.i_u = dic->i_u; copied.i_nb = dic->i_nb; vi->i_uid = le16_to_cpu(dic->i_uid); vi->i_gid = le16_to_cpu(dic->i_gid); if (!S_ISDIR(vi->i_mode) && ((ifmt >> EROFS_I_NLINK_1_BIT) & 1)) { vi->i_nlink = 1; copied.i_nb = dic->i_nb; } else { vi->i_nlink = le16_to_cpu(dic->i_nb.nlink); copied.i_nb.startblk_hi = 0; addrmask = BIT_ULL(32) - 1; } vi->i_mtime = sbi->epoch + le32_to_cpu(dic->i_mtime); vi->i_mtime_nsec = sbi->fixed_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); err = -EOPNOTSUPP; goto err_out; } switch (vi->i_mode & S_IFMT) { case S_IFDIR: vi->dot_omitted = (ifmt >> EROFS_I_DOT_OMITTED_BIT) & 1; __erofs_fallthrough; case S_IFREG: case S_IFLNK: vi->u.i_blkaddr = le32_to_cpu(copied.i_u.startblk_lo) | ((u64)le16_to_cpu(copied.i_nb.startblk_hi) << 32); if (vi->datalayout == EROFS_INODE_FLAT_PLAIN && !((vi->u.i_blkaddr ^ EROFS_NULL_ADDR) & addrmask)) vi->u.i_blkaddr = EROFS_NULL_ADDR; break; case S_IFCHR: case S_IFBLK: vi->u.i_rdev = erofs_new_decode_dev(le32_to_cpu(copied.i_u.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); err = -EFSCORRUPTED; goto err_out; } vi->flags = 0; if (vi->datalayout == EROFS_INODE_CHUNK_BASED) { /* fill chunked inode summary info */ vi->u.chunkformat = le16_to_cpu(copied.i_u.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); err = -EOPNOTSUPP; goto err_out; } vi->u.chunkbits = sbi->blkszbits + (vi->u.chunkformat & EROFS_CHUNK_FORMAT_BLKBITS_MASK); } err_out: erofs_put_metabuf(&buf); return err; } 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 }; struct erofs_vfile vf; erofs_off_t offset; ret = erofs_read_inode_from_disk(&vi); if (ret) return ret; ret = erofs_iopen(&vf, &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(&vf, 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.9.1/lib/rebuild.c000066400000000000000000000322151515160260000163350ustar00rootroot00000000000000// 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/dir.h" #include "erofs/xattr.h" #include "erofs/blobchunk.h" #include "erofs/internal.h" #include "liberofs_rebuild.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; inode->i_nlink = 2; 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_d_lookup(struct erofs_inode *dir, const char *name) { struct erofs_dentry *d; list_for_each_entry(d, &dir->i_subdirs, d_child) if (!strcmp(d->name, name)) return d; return NULL; } 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; char *s = path; *whout = false; *opq = false; while (1) { char *slash = strchr(s, '/'); if (slash) { if (s == slash) { while (*++s == '/'); /* skip '//...' */ continue; } *slash = '\0'; } else if (*s == '\0') { break; } if (__erofs_unlikely(is_dot_dotdot(s))) { if (s[1] == '.') { pwd = pwd->i_parent; } } else { if (aufs && !slash) { if (!strcmp(s, AUFS_WH_DIROPQ)) { *opq = true; break; } if (!strncmp(s, AUFS_WH_PFX, sizeof(AUFS_WH_PFX) - 1)) { s += sizeof(AUFS_WH_PFX) - 1; *whout = true; } } d = erofs_d_lookup(pwd, s); if (d) { if (d->type != EROFS_FT_DIR) { if (slash) return ERR_PTR(-ENOTDIR); } else if (to_head) { list_del(&d->d_child); list_add(&d->d_child, &pwd->i_subdirs); } pwd = d->inode; } else if (slash) { d = erofs_rebuild_mkdir(pwd, s); if (IS_ERR(d)) return d; } else { d = erofs_d_alloc(pwd, s); if (IS_ERR(d)) return d; d->type = EROFS_FT_UNKNOWN; d->inode = pwd; } pwd = d->inode; } if (!slash) break; *slash = '/'; s = slash + 1; } 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 = calloc(count, 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 = { .buf = __EROFS_BUF_INITIALIZER, }; 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; __erofs_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: inode->i_nlink = 2; break; case S_IFLNK: { struct erofs_vfile vf; inode->i_link = malloc(inode->i_size + 1); if (!inode->i_link) return -ENOMEM; err = erofs_iopen(&vf, inode); if (err) return err; err = erofs_pread(&vf, 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 = EROFS_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; } struct erofs_rebuild_dir_context { /* @ctx.dir: parent directory when itering erofs_iterate_dir() */ struct erofs_dir_context ctx; struct erofs_inode *mergedir; /* parent directory in the merged tree */ union { /* indicate how to import inode data */ enum erofs_rebuild_datamode datamode; struct { u64 *nr_subdirs; unsigned int *i_nlink; }; }; }; 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 = EROFS_NID_UNALLOCATED; 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 *mergedir = rctx->mergedir; struct erofs_inode *dir = ctx->dir; 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->flags |= EROFS_DENTRY_FLAG_VALIDNID; if (d->type == EROFS_FT_DIR) d->flags |= EROFS_DENTRY_FLAG_FIXUP_PNID; if (!mergedir->whiteouts && erofs_dentry_is_wht(dir->sbi, d)) mergedir->whiteouts = true; *rctx->i_nlink += (ctx->de_ftype == EROFS_FT_DIR); ++*rctx->nr_subdirs; } else if (__erofs_unlikely(d->flags & EROFS_DENTRY_FLAG_VALIDNID)) { /* The base image appears to be corrupted */ DBG_BUGON(1); ret = -EFSCORRUPTED; goto out; } 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)) { erofs_remove_ihash(inode); 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, u64 *nr_subdirs, unsigned int *i_nlink) { 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, .nr_subdirs = nr_subdirs, .i_nlink = i_nlink, }; return erofs_iterate_dir(&ctx.ctx, false); } erofs-utils-1.9.1/lib/remotes/000077500000000000000000000000001515160260000162165ustar00rootroot00000000000000erofs-utils-1.9.1/lib/remotes/docker_config.c000066400000000000000000000115751515160260000211670ustar00rootroot00000000000000// SPDX-License-Identifier: GPL-2.0+ OR Apache-2.0 /* * Copyright (C) 2026 Tencent, Inc. * http://www.tencent.com/ */ #define _GNU_SOURCE #include #include #include #include #include #include "erofs/defs.h" #ifdef HAVE_JSON_C_JSON_H #include #endif #include "erofs/print.h" #include "liberofs_base64.h" #include "liberofs_dockerconfig.h" #ifndef HAVE_JSON_C_JSON_H int erofs_docker_config_lookup(const char *registry, struct erofs_docker_credential *cred) { (void)registry; (void)cred; return -EOPNOTSUPP; } void erofs_docker_credential_free(struct erofs_docker_credential *cred) { (void)cred; } #else /* HAVE_JSON_C_JSON_H */ static char *docker_config_path(void) { const char *dir; char *path = NULL; dir = getenv("DOCKER_CONFIG"); if (dir) { if (!*dir) return NULL; if (asprintf(&path, "%s/config.json", dir) < 0) return NULL; return path; } dir = getenv("HOME"); if (!dir || !*dir) { erofs_dbg("HOME is not set, cannot locate docker config"); return NULL; } if (asprintf(&path, "%s/.docker/config.json", dir) < 0) return NULL; return path; } static char *read_file_to_string(const char *path) { FILE *fp; struct stat st; char *buf; size_t nread; if (stat(path, &st) < 0) return NULL; if (st.st_size <= 0 || st.st_size > (1 << 22)) return NULL; fp = fopen(path, "r"); if (!fp) return NULL; buf = malloc(st.st_size + 1); if (!buf) { fclose(fp); return NULL; } nread = fread(buf, 1, st.st_size, fp); fclose(fp); if ((off_t)nread != st.st_size) { free(buf); return NULL; } buf[nread] = '\0'; return buf; } /* * Check if @key (an auths entry key) matches @registry. * * For Docker Hub: @registry is docker.io or registry-1.docker.io. * The auths key in config.json is always "https://index.docker.io/v1/". * For other registries: the auths key is an exact match against @registry. */ static bool registry_match(const char *key, const char *registry) { if (!key || !registry) return false; if (!strcasecmp(registry, DOCKER_REGISTRY) || !strcasecmp(registry, DOCKER_API_REGISTRY)) return !strcmp(key, DOCKER_HUB_AUTH_KEY); return !strcasecmp(key, registry); } static int decode_auth_field(const char *b64, char **out_user, char **out_pass) { int b64_len = strlen(b64); int decoded_max = b64_len; u8 *decoded; int decoded_len; char *colon; decoded = malloc(decoded_max + 1); if (!decoded) return -ENOMEM; decoded_len = erofs_base64_decode(b64, b64_len, decoded); if (decoded_len <= 0) { free(decoded); return -EINVAL; } decoded[decoded_len] = '\0'; colon = strchr((char *)decoded, ':'); if (!colon) { erofs_free_sensitive(decoded, decoded_len); return -EINVAL; } *colon = '\0'; *out_user = strdup((char *)decoded); *out_pass = strdup(colon + 1); erofs_free_sensitive(decoded, decoded_len); if (!*out_user || !*out_pass) { free(*out_user); free(*out_pass); *out_user = NULL; *out_pass = NULL; return -ENOMEM; } return 0; } int erofs_docker_config_lookup(const char *registry, struct erofs_docker_credential *cred) { char *path = NULL; char *content = NULL; struct json_object *root = NULL, *auths_obj = NULL; int ret = -ENOENT; memset(cred, 0, sizeof(*cred)); path = docker_config_path(); if (!path) return -ENOENT; content = read_file_to_string(path); if (!content) { erofs_dbg("cannot read docker config: %s", path); free(path); return -ENOENT; } free(path); root = json_tokener_parse(content); erofs_free_sensitive(content, strlen(content)); if (!root) { erofs_warn("failed to parse docker config.json"); return -EINVAL; } if (!json_object_object_get_ex(root, "auths", &auths_obj)) { erofs_dbg("no \"auths\" in docker config.json"); json_object_put(root); return -ENOENT; } struct json_object_iterator it = json_object_iter_begin(auths_obj); struct json_object_iterator end = json_object_iter_end(auths_obj); while (!json_object_iter_equal(&it, &end)) { const char *key = json_object_iter_peek_name(&it); struct json_object *entry, *auth_field; const char *b64; if (!registry_match(key, registry)) { json_object_iter_next(&it); continue; } entry = json_object_iter_peek_value(&it); if (json_object_object_get_ex(entry, "auth", &auth_field)) { b64 = json_object_get_string(auth_field); if (b64 && *b64) { ret = decode_auth_field(b64, &cred->username, &cred->password); if (!ret) erofs_dbg("found docker credentials for %s", registry); } } break; } json_object_put(root); return ret; } void erofs_docker_credential_free(struct erofs_docker_credential *cred) { if (cred->username) { erofs_free_sensitive(cred->username, strlen(cred->username)); cred->username = NULL; } if (cred->password) { erofs_free_sensitive(cred->password, strlen(cred->password)); cred->password = NULL; } } #endif /* HAVE_JSON_C_JSON_H */ erofs-utils-1.9.1/lib/remotes/oci.c000066400000000000000000001277361515160260000171540ustar00rootroot00000000000000// SPDX-License-Identifier: GPL-2.0+ OR Apache-2.0 /* * Copyright (C) 2025 Tencent, Inc. * http://www.tencent.com/ */ #define _GNU_SOURCE #include "erofs/internal.h" #include #include #include #include #include #include #include #include #ifdef HAVE_CURL_CURL_H #include #endif #ifdef HAVE_JSON_C_JSON_H #include #endif #include "erofs/importer.h" #include "erofs/internal.h" #include "erofs/io.h" #include "erofs/print.h" #include "erofs/tar.h" #include "liberofs_base64.h" #include "liberofs_oci.h" #include "liberofs_dockerconfig.h" #include "liberofs_private.h" #include "liberofs_gzran.h" #ifdef OCIEROFS_ENABLED #define DOCKER_MEDIATYPE_MANIFEST_V2 \ "application/vnd.docker.distribution.manifest.v2+json" #define DOCKER_MEDIATYPE_MANIFEST_V1 \ "application/vnd.docker.distribution.manifest.v1+json" #define DOCKER_MEDIATYPE_MANIFEST_LIST \ "application/vnd.docker.distribution.manifest.list.v2+json" #define OCI_MEDIATYPE_MANIFEST "application/vnd.oci.image.manifest.v1+json" #define OCI_MEDIATYPE_INDEX "application/vnd.oci.image.index.v1+json" #define OCIEROFS_IO_CHUNK_SIZE 32768 struct ocierofs_request { char *url; struct curl_slist *headers; }; struct ocierofs_response { char *data; size_t size; long http_code; }; struct ocierofs_stream { const char *digest; int blobfd; }; static inline const char *ocierofs_get_api_registry(const char *registry) { if (!registry) return DOCKER_API_REGISTRY; return !strcmp(registry, DOCKER_REGISTRY) ? DOCKER_API_REGISTRY : registry; } static inline bool ocierofs_is_manifest(const char *media_type) { return media_type && (!strcmp(media_type, DOCKER_MEDIATYPE_MANIFEST_V2) || !strcmp(media_type, OCI_MEDIATYPE_MANIFEST)); } static inline void ocierofs_request_cleanup(struct ocierofs_request *req) { if (!req) return; if (req->headers) curl_slist_free_all(req->headers); free(req->url); req->url = NULL; req->headers = NULL; } static inline void ocierofs_response_cleanup(struct ocierofs_response *resp) { if (!resp) return; free(resp->data); resp->data = NULL; resp->size = 0; resp->http_code = 0; } static size_t ocierofs_write_callback(void *contents, size_t size, size_t nmemb, void *userp) { size_t realsize = size * nmemb; struct ocierofs_response *resp = userp; char *ptr; if (!resp->data) resp->size = 0; ptr = realloc(resp->data, resp->size + realsize + 1); if (!ptr) { erofs_err("failed to allocate memory for response data"); return 0; } resp->data = ptr; memcpy(&resp->data[resp->size], contents, realsize); resp->size += realsize; resp->data[resp->size] = '\0'; return realsize; } static size_t ocierofs_layer_write_callback(void *contents, size_t size, size_t nmemb, void *userp) { struct ocierofs_stream *stream = userp; size_t realsize = size * nmemb; const char *buf = contents; size_t written = 0; if (stream->blobfd < 0) return 0; while (written < realsize) { ssize_t n = write(stream->blobfd, buf + written, realsize - written); if (n < 0) { erofs_err("failed to write layer data for layer %s", stream->digest); return 0; } written += n; } return realsize; } static int ocierofs_curl_setup_common_options(struct CURL *curl) { curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L); curl_easy_setopt(curl, CURLOPT_CONNECTTIMEOUT, 30L); curl_easy_setopt(curl, CURLOPT_TIMEOUT, 120L); curl_easy_setopt(curl, CURLOPT_NOSIGNAL, 1L); curl_easy_setopt(curl, CURLOPT_USERAGENT, "ocierofs/" PACKAGE_VERSION); curl_easy_setopt(curl, CURLOPT_ACCEPT_ENCODING, ""); curl_easy_setopt(curl, CURLOPT_TCP_KEEPALIVE, 1L); #if defined(CURLOPT_TCP_KEEPIDLE) curl_easy_setopt(curl, CURLOPT_TCP_KEEPIDLE, 30L); #endif #if defined(CURLOPT_TCP_KEEPINTVL) curl_easy_setopt(curl, CURLOPT_TCP_KEEPINTVL, 15L); #endif return 0; } static int ocierofs_curl_setup_basic_auth(struct CURL *curl, const char *username, const char *password) { char *userpwd; if (asprintf(&userpwd, "%s:%s", username, password) == -1) return -ENOMEM; curl_easy_setopt(curl, CURLOPT_USERPWD, userpwd); curl_easy_setopt(curl, CURLOPT_HTTPAUTH, CURLAUTH_BASIC); free(userpwd); return 0; } static int ocierofs_curl_clear_auth(struct ocierofs_ctx *ctx) { curl_easy_setopt(ctx->curl, CURLOPT_USERPWD, NULL); curl_easy_setopt(ctx->curl, CURLOPT_HTTPAUTH, CURLAUTH_NONE); return 0; } enum ocierofs_http_method { OCIEROFS_HTTP_GET, OCIEROFS_HTTP_HEAD }; static int ocierofs_curl_setup_rq(struct CURL *curl, const char *url, enum ocierofs_http_method method, struct curl_slist *headers, size_t (*write_func)(void *, size_t, size_t, void *), void *write_data, size_t (*header_func)(void *, size_t, size_t, void *), void *header_data) { curl_easy_setopt(curl, CURLOPT_URL, url); if (method == OCIEROFS_HTTP_HEAD) { curl_easy_setopt(curl, CURLOPT_NOBODY, 1L); } else { curl_easy_setopt(curl, CURLOPT_NOBODY, 0L); curl_easy_setopt(curl, CURLOPT_HTTPGET, 1L); } if (write_func) { curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_func); curl_easy_setopt(curl, CURLOPT_WRITEDATA, write_data); } curl_easy_setopt(curl, CURLOPT_HEADERFUNCTION, header_func); curl_easy_setopt(curl, CURLOPT_HEADERDATA, header_data); if (headers) curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers); return 0; } static int ocierofs_curl_perform(struct CURL *curl, long *http_code_out) { CURLcode res; long http_code = 0; res = curl_easy_perform(curl); if (res != CURLE_OK) { erofs_err("curl request failed: %s", curl_easy_strerror(res)); return -EIO; } if (http_code_out) { res = curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &http_code); if (res != CURLE_OK) { erofs_err("failed to get HTTP response code: %s", curl_easy_strerror(res)); return -EIO; } *http_code_out = http_code; } return 0; } static int ocierofs_request_perform(struct ocierofs_ctx *ctx, struct ocierofs_request *req, struct ocierofs_response *resp) { int ret; ret = ocierofs_curl_setup_rq(ctx->curl, req->url, OCIEROFS_HTTP_GET, req->headers, ocierofs_write_callback, resp, NULL, NULL); if (ret) return ret; ret = ocierofs_curl_perform(ctx->curl, &resp->http_code); if (ret) return ret; if (resp->http_code < 200 || resp->http_code >= 300) return -EIO; return 0; } /** * ocierofs_parse_auth_header - Parse WWW-Authenticate header for Bearer auth * @auth_header: authentication header string * @realm_out: pointer to store realm value * @service_out: pointer to store service value * @scope_out: pointer to store scope value * * Parse Bearer authentication header and extract realm, service, and scope * parameters for subsequent token requests. * * Return: 0 on success, negative errno on failure */ static int ocierofs_parse_auth_header(const char *auth_header, char **realm_out, char **service_out, char **scope_out) { char *realm = NULL, *service = NULL, *scope = NULL; static const char * const param_names[] = {"realm=", "service=", "scope="}; char **param_values[] = {&realm, &service, &scope}; char *header_copy = NULL; const char *p; int i, ret = 0; // https://datatracker.ietf.org/doc/html/rfc6750#section-3 if (strncmp(auth_header, "Bearer ", strlen("Bearer "))) return -EINVAL; header_copy = strdup(auth_header); if (!header_copy) return -ENOMEM; /* Clean up header: replace newlines with spaces and remove double spaces */ for (char *q = header_copy; *q; q++) { if (*q == '\n' || *q == '\r') *q = ' '; } p = header_copy + strlen("Bearer "); for (i = 0; i < ARRAY_SIZE(param_names); i++) { const char *param_start; char *value; size_t len; param_start = strstr(p, param_names[i]); if (!param_start) continue; param_start += strlen(param_names[i]); if (*param_start != '"') continue; param_start++; const char *param_end = strchr(param_start, '"'); if (!param_end) continue; len = param_end - param_start; value = strndup(param_start, len); if (!value) { ret = -ENOMEM; goto out; } *param_values[i] = value; } free(header_copy); *realm_out = realm; *service_out = service; *scope_out = scope; return 0; out: free(header_copy); free(realm); free(service); free(scope); return ret; } /** * ocierofs_extract_www_auth_info - Extract WWW-Authenticate header information * @resp_data: HTTP response data containing headers * @realm_out: pointer to store realm value (optional) * @service_out: pointer to store service value (optional) * @scope_out: pointer to store scope value (optional) * * Extract realm, service, and scope from WWW-Authenticate header in HTTP response. * This function handles the common pattern of parsing WWW-Authenticate headers * that appears in multiple places in the OCI authentication flow. * * Return: 0 on success, negative errno on failure */ static int ocierofs_extract_www_auth_info(const char *resp_data, char **realm_out, char **service_out, char **scope_out) { char *www_auth; char *line_end; char *realm = NULL, *service = NULL, *scope = NULL; int ret; if (!resp_data) return -EINVAL; www_auth = strcasestr(resp_data, "www-authenticate:"); if (!www_auth) return -ENOENT; line_end = strchr(www_auth, '\n'); if (line_end) *line_end = '\0'; www_auth += strlen("www-authenticate:"); while (*www_auth == ' ') www_auth++; ret = ocierofs_parse_auth_header(www_auth, &realm, &service, &scope); if (ret == 0) { if (realm_out) { *realm_out = realm; realm = NULL; } if (service_out) { *service_out = service; service = NULL; } if (scope_out) { *scope_out = scope; scope = NULL; } } free(realm); free(service); free(scope); return ret; } /** * ocierofs_get_auth_token_with_url - Get authentication token from auth server * @ctx: OCI context structure * @auth_url: authentication server URL * @service: service name for authentication * @repository: repository name * @username: username for basic auth (optional) * @password: password for basic auth (optional) * * Request authentication token from the specified auth server URL using * basic authentication if credentials are provided. * * Return: authentication header string on success, ERR_PTR on failure */ static char *ocierofs_get_auth_token_with_url(struct ocierofs_ctx *ctx, const char *auth_url, const char *service, const char *repository, const char *username, const char *password) { struct ocierofs_request req = {}; struct ocierofs_response resp = {}; json_object *root, *token_obj, *access_token_obj; const char *token; char *auth_header = NULL; int ret; if (!auth_url || !service || !repository) return ERR_PTR(-EINVAL); if (asprintf(&req.url, "%s?service=%s&scope=repository:%s:pull", auth_url, service, repository) == -1) { return ERR_PTR(-ENOMEM); } if (username && password && *username) { ret = ocierofs_curl_setup_basic_auth(ctx->curl, username, password); if (ret) goto out_url; } ret = ocierofs_request_perform(ctx, &req, &resp); ocierofs_curl_clear_auth(ctx); if (ret) goto out_url; if (!resp.data) { erofs_err("empty response from auth server"); ret = -EINVAL; goto out_url; } root = json_tokener_parse(resp.data); if (!root) { erofs_err("failed to parse auth response"); ret = -EINVAL; goto out_json; } if (!json_object_object_get_ex(root, "token", &token_obj) && !json_object_object_get_ex(root, "access_token", &access_token_obj)) { erofs_err("no token found in auth response"); ret = -EINVAL; goto out_json; } token = json_object_get_string(token_obj ? token_obj : access_token_obj); if (!token) { erofs_err("invalid token in auth response"); ret = -EINVAL; goto out_json; } if (asprintf(&auth_header, "Authorization: Bearer %s", token) == -1) { ret = -ENOMEM; goto out_json; } out_json: json_object_put(root); out_url: ocierofs_response_cleanup(&resp); ocierofs_request_cleanup(&req); return ret ? ERR_PTR(ret) : auth_header; } static char *ocierofs_discover_auth_endpoint(struct ocierofs_ctx *ctx, const char *registry, const char *repository) { struct ocierofs_response resp = {}; char *realm = NULL; char *service = NULL; char *result = NULL; char *test_url; const char *api_registry; CURLcode res; long http_code; api_registry = ocierofs_get_api_registry(registry); if (asprintf(&test_url, "%s%s/v2/%s/manifests/nonexistent", ctx->schema, api_registry, repository) < 0) return NULL; curl_easy_reset(ctx->curl); ocierofs_curl_setup_common_options(ctx->curl); ocierofs_curl_setup_rq(ctx->curl, test_url, OCIEROFS_HTTP_HEAD, NULL, NULL, NULL, ocierofs_write_callback, &resp); res = curl_easy_perform(ctx->curl); curl_easy_getinfo(ctx->curl, CURLINFO_RESPONSE_CODE, &http_code); if (res == CURLE_OK && (http_code == 401 || http_code == 403 || http_code == 404) && resp.data) { if (ocierofs_extract_www_auth_info(resp.data, &realm, &service, NULL) == 0) { result = realm; realm = NULL; } } free(realm); free(service); ocierofs_response_cleanup(&resp); free(test_url); return result; } static char *ocierofs_get_auth_token(struct ocierofs_ctx *ctx, const char *registry, const char *repository, const char *username, const char *password) { static const char * const auth_patterns[] = { "%s%s/v2/auth", "%sauth.%s/token", "%s%s/token", NULL, }; char *auth_header = NULL; char *discovered_auth_url = NULL; char *discovered_service = NULL; const char *service = registry; bool docker_reg; int i; docker_reg = !strcmp(registry, DOCKER_API_REGISTRY) || !strcmp(registry, DOCKER_REGISTRY); if (docker_reg) { service = "registry.docker.io"; auth_header = ocierofs_get_auth_token_with_url(ctx, "https://auth.docker.io/token", service, repository, username, password); if (!IS_ERR(auth_header)) return auth_header; } discovered_auth_url = ocierofs_discover_auth_endpoint(ctx, registry, repository); if (discovered_auth_url) { const char *api_registry, *auth_service; struct ocierofs_response resp = {}; char *test_url; CURLcode res; long http_code; api_registry = ocierofs_get_api_registry(registry); if (asprintf(&test_url, "%s%s/v2/%s/manifests/nonexistent", ctx->schema, api_registry, repository) >= 0) { curl_easy_reset(ctx->curl); ocierofs_curl_setup_common_options(ctx->curl); ocierofs_curl_setup_rq(ctx->curl, test_url, OCIEROFS_HTTP_HEAD, NULL, NULL, NULL, ocierofs_write_callback, &resp); res = curl_easy_perform(ctx->curl); curl_easy_getinfo(ctx->curl, CURLINFO_RESPONSE_CODE, &http_code); if (res == CURLE_OK && (http_code == 401 || http_code == 403 || http_code == 404) && resp.data) { char *realm = NULL; ocierofs_extract_www_auth_info(resp.data, &realm, &discovered_service, NULL); free(realm); } ocierofs_response_cleanup(&resp); free(test_url); } auth_service = discovered_service ? discovered_service : service; auth_header = ocierofs_get_auth_token_with_url(ctx, discovered_auth_url, auth_service, repository, username, password); free(discovered_auth_url); free(discovered_service); if (!IS_ERR(auth_header)) return auth_header; } for (i = 0; auth_patterns[i]; i++) { char *auth_url; if (asprintf(&auth_url, auth_patterns[i], ctx->schema, registry) < 0) continue; auth_header = ocierofs_get_auth_token_with_url(ctx, auth_url, service, repository, username, password); free(auth_url); if (!IS_ERR(auth_header)) return auth_header; if (!docker_reg) return NULL; } return ERR_PTR(-ENOENT); } static char *ocierofs_get_manifest_digest(struct ocierofs_ctx *ctx, const char *registry, const char *repository, const char *tag, const char *platform, const char *auth_header) { struct ocierofs_request req = {}; struct ocierofs_response resp = {}; json_object *root, *manifests, *manifest, *platform_obj, *arch_obj; json_object *os_obj, *digest_obj, *schema_obj, *media_type_obj; char *digest = NULL; const char *api_registry; int ret = 0, len, i; api_registry = ocierofs_get_api_registry(registry); if (asprintf(&req.url, "%s%s/v2/%s/manifests/%s", ctx->schema, api_registry, repository, tag) < 0) return ERR_PTR(-ENOMEM); if (auth_header && strstr(auth_header, "Bearer")) req.headers = curl_slist_append(req.headers, auth_header); req.headers = curl_slist_append(req.headers, "Accept: " DOCKER_MEDIATYPE_MANIFEST_LIST "," OCI_MEDIATYPE_INDEX "," OCI_MEDIATYPE_MANIFEST "," DOCKER_MEDIATYPE_MANIFEST_V1 "," DOCKER_MEDIATYPE_MANIFEST_V2); ret = ocierofs_request_perform(ctx, &req, &resp); if (ret) goto out; if (!resp.data) { erofs_err("empty response from manifest request"); ret = -EINVAL; goto out; } root = json_tokener_parse(resp.data); if (!root) { erofs_err("failed to parse manifest JSON"); ret = -EINVAL; goto out; } if (json_object_object_get_ex(root, "schemaVersion", &schema_obj)) { if (json_object_get_int(schema_obj) < 0) { digest = strdup(tag); ret = 0; goto out_json; } } if (json_object_object_get_ex(root, "mediaType", &media_type_obj)) { const char *media_type = json_object_get_string(media_type_obj); if (ocierofs_is_manifest(media_type)) { digest = strdup(tag); ret = 0; goto out_json; } } if (!json_object_object_get_ex(root, "manifests", &manifests)) { erofs_err("no manifests found in manifest list"); ret = -EINVAL; goto out_json; } len = json_object_array_length(manifests); for (i = 0; i < len; i++) { manifest = json_object_array_get_idx(manifests, i); if (json_object_object_get_ex(manifest, "platform", &platform_obj) && json_object_object_get_ex(platform_obj, "architecture", &arch_obj) && json_object_object_get_ex(platform_obj, "os", &os_obj) && json_object_object_get_ex(manifest, "digest", &digest_obj)) { const char *arch = json_object_get_string(arch_obj); const char *os = json_object_get_string(os_obj); json_object *variant_obj; const char *variant = NULL; char manifest_platform[64]; if (json_object_object_get_ex(platform_obj, "variant", &variant_obj)) variant = json_object_get_string(variant_obj); if (variant) snprintf(manifest_platform, sizeof(manifest_platform), "%s/%s/%s", os, arch, variant); else snprintf(manifest_platform, sizeof(manifest_platform), "%s/%s", os, arch); if (!strcmp(manifest_platform, platform)) { digest = strdup(json_object_get_string(digest_obj)); break; } } } if (!digest) ret = -ENOENT; out_json: json_object_put(root); out: ocierofs_response_cleanup(&resp); ocierofs_request_cleanup(&req); return ret ? ERR_PTR(ret) : digest; } static void ocierofs_free_layers_info(struct ocierofs_layer_info **layers, int count) { int i; if (!layers) return; for (i = 0; i < count; i++) { if (layers[i]) { free(layers[i]->digest); free(layers[i]->media_type); free(layers[i]); } } free(layers); } static int ocierofs_fetch_layers_info(struct ocierofs_ctx *ctx) { const char *registry = ctx->registry; const char *repository = ctx->repository; const char *digest = ctx->manifest_digest; const char *auth_header = ctx->auth_header; struct ocierofs_request req = {}; struct ocierofs_response resp = {}; json_object *root, *layers, *layer, *digest_obj, *media_type_obj, *size_obj; struct ocierofs_layer_info **layers_info = NULL; const char *api_registry; int ret, len, i; ctx->layer_count = 0; api_registry = ocierofs_get_api_registry(registry); if (asprintf(&req.url, "%s%s/v2/%s/manifests/%s", ctx->schema, api_registry, repository, digest) < 0) return -ENOMEM; if (auth_header && strstr(auth_header, "Bearer")) req.headers = curl_slist_append(req.headers, auth_header); req.headers = curl_slist_append(req.headers, "Accept: " OCI_MEDIATYPE_MANIFEST "," DOCKER_MEDIATYPE_MANIFEST_V2); ret = ocierofs_request_perform(ctx, &req, &resp); if (ret) goto out; if (!resp.data) { erofs_err("empty response from layers request"); ret = -EINVAL; goto out; } root = json_tokener_parse(resp.data); if (!root) { erofs_err("failed to parse manifest JSON"); ret = -EINVAL; goto out; } if (!json_object_object_get_ex(root, "layers", &layers) || json_object_get_type(layers) != json_type_array) { erofs_err("no layers found in manifest"); ret = -EINVAL; goto out_json; } len = json_object_array_length(layers); if (!len) { ret = -EINVAL; goto out_json; } layers_info = calloc(len, sizeof(*layers_info)); if (!layers_info) { ret = -ENOMEM; goto out_json; } for (i = 0; i < len; i++) { layer = json_object_array_get_idx(layers, i); if (!json_object_object_get_ex(layer, "digest", &digest_obj)) { ret = -EINVAL; goto out_free; } layers_info[i] = calloc(1, sizeof(**layers_info)); if (!layers_info[i]) { ret = -ENOMEM; goto out_free; } layers_info[i]->digest = strdup(json_object_get_string(digest_obj)); if (!layers_info[i]->digest) { ret = -ENOMEM; goto out_free; } if (json_object_object_get_ex(layer, "mediaType", &media_type_obj)) layers_info[i]->media_type = strdup(json_object_get_string(media_type_obj)); else layers_info[i]->media_type = NULL; if (json_object_object_get_ex(layer, "size", &size_obj)) layers_info[i]->size = json_object_get_int64(size_obj); else layers_info[i]->size = 0; } ctx->layer_count = len; json_object_put(root); ocierofs_response_cleanup(&resp); ocierofs_request_cleanup(&req); ctx->layers = layers_info; return 0; out_free: ocierofs_free_layers_info(layers_info, i); out_json: json_object_put(root); out: ocierofs_response_cleanup(&resp); ocierofs_request_cleanup(&req); return ret; } static int ocierofs_process_tar_stream(struct erofs_importer *importer, int fd, const struct ocierofs_config *config, u64 *tar_offset_out) { struct erofs_tarfile tarfile = {}; int ret, decoder, zinfo_fd; struct erofs_vfile vf; init_list_head(&tarfile.global.xattrs); /* * Choose decoder based on config: * - tarindex + zinfo -> tar.gzip (GZRAN decoder) * - tarindex only -> tar (no decoder, raw) * - neither -> default gzip decoder */ if (config && config->tarindex_path) { tarfile.index_mode = true; if (config->zinfo_path) decoder = EROFS_IOS_DECODER_GZRAN; else decoder = EROFS_IOS_DECODER_NONE; } else { decoder = EROFS_IOS_DECODER_GZIP; } ret = erofs_iostream_open(&tarfile.ios, fd, decoder); if (ret) { erofs_err("failed to initialize tar stream: %s", erofs_strerror(ret)); return ret; } do { ret = tarerofs_parse_tar(importer, &tarfile); /* Continue parsing until end of archive */ } while (!ret); if (decoder == EROFS_IOS_DECODER_GZRAN) { zinfo_fd = open(config->zinfo_path, O_WRONLY | O_CREAT | O_TRUNC, 0644); if (zinfo_fd < 0) { ret = -errno; } else { vf = (struct erofs_vfile){ .fd = zinfo_fd }; ret = erofs_gzran_builder_export_zinfo(tarfile.ios.gb, &vf); close(zinfo_fd); if (ret < 0) { erofs_err("failed to export zinfo: %s", erofs_strerror(ret)); } } } if (tar_offset_out) *tar_offset_out = tarfile.offset; erofs_iostream_close(&tarfile.ios); if (ret < 0 && ret != -ENODATA) { erofs_err("failed to process tar stream: %s", erofs_strerror(ret)); return ret; } return 0; } static int ocierofs_prepare_auth(struct ocierofs_ctx *ctx, const char *username, const char *password) { char *auth_header = NULL; int ret = 0; ctx->using_basic = false; free(ctx->auth_header); ctx->auth_header = NULL; auth_header = ocierofs_get_auth_token(ctx, ctx->registry, ctx->repository, username, password); if (!IS_ERR(auth_header)) { ctx->auth_header = auth_header; return 0; } if (username && password && *username && *password) { ret = ocierofs_curl_setup_basic_auth(ctx->curl, username, password); if (ret) return ret; ctx->using_basic = true; } return 0; } static int ocierofs_find_layer_by_digest(struct ocierofs_ctx *ctx, const char *digest) { int i; for (i = 0; i < ctx->layer_count; i++) { DBG_BUGON(!ctx->layers[i]); DBG_BUGON(!ctx->layers[i]->digest); if (!strcmp(ctx->layers[i]->digest, digest)) return i; } return -1; } static int ocierofs_prepare_layers(struct ocierofs_ctx *ctx, const struct ocierofs_config *config) { struct erofs_docker_credential dcred = { NULL, NULL }; const char *username = config->username; const char *password = config->password; int ret; /* Fallback to Docker config.json if no CLI credentials provided */ if ((!username || !*username) && (!password || !*password)) { if (!erofs_docker_config_lookup(ctx->registry, &dcred)) { username = dcred.username; password = dcred.password; } } ret = ocierofs_prepare_auth(ctx, username, password); erofs_docker_credential_free(&dcred); if (ret) return ret; ctx->manifest_digest = ocierofs_get_manifest_digest(ctx, ctx->registry, ctx->repository, ctx->tag, ctx->platform, ctx->auth_header); if (IS_ERR(ctx->manifest_digest)) { ret = PTR_ERR(ctx->manifest_digest); erofs_err("failed to get manifest digest: %s", erofs_strerror(ret)); ctx->manifest_digest = NULL; goto out_auth; } ret = ocierofs_fetch_layers_info(ctx); if (ret) { erofs_err("failed to get image layers: %s", erofs_strerror(ret)); ctx->layers = NULL; goto out_manifest; } if (!ctx->blob_digest && config->layer_index >= 0) { if (config->layer_index >= ctx->layer_count) { erofs_err("layer index %d out of range (0..%d)", config->layer_index, ctx->layer_count - 1); ret = -EINVAL; goto out_layers; } DBG_BUGON(!ctx->layers[config->layer_index]); DBG_BUGON(!ctx->layers[config->layer_index]->digest); ctx->blob_digest = strdup(ctx->layers[config->layer_index]->digest); if (!ctx->blob_digest) { ret = -ENOMEM; goto out_layers; } } if (ctx->blob_digest) { if (ocierofs_find_layer_by_digest(ctx, ctx->blob_digest) < 0) { erofs_err("layer digest %s not found in image layers", ctx->blob_digest); ret = -ENOENT; goto out_layers; } } return 0; out_layers: ocierofs_free_layers_info(ctx->layers, ctx->layer_count); ctx->layers = NULL; out_manifest: free(ctx->manifest_digest); ctx->manifest_digest = NULL; out_auth: free(ctx->auth_header); ctx->auth_header = NULL; if (ctx->using_basic) ocierofs_curl_clear_auth(ctx); return ret; } /* * ocierofs_parse_ref - Parse OCI image reference string * @ctx: OCI context structure * @ref_str: OCI image reference string * * Return: 0 on success, negative errno on failure */ static int ocierofs_parse_ref(struct ocierofs_ctx *ctx, const char *ref_str) { const char *slash, *colon, *dot; const char *repo_part; size_t len; char *tmp; if (!ctx || !ref_str) return -EINVAL; slash = strchr(ref_str, '/'); if (slash) { dot = strchr(ref_str, '.'); colon = strchr(ref_str, ':'); /* a dot or colon before the slash indicating a registry */ if ((dot && dot < slash) || (colon && colon < slash)) { len = slash - ref_str; tmp = strndup(ref_str, len); if (!tmp) return -ENOMEM; free(ctx->registry); ctx->registry = tmp; repo_part = slash + 1; } else { repo_part = ref_str; } } else { repo_part = ref_str; } colon = strchr(repo_part, ':'); if (colon) { len = colon - repo_part; tmp = strndup(repo_part, len); } else { tmp = strdup(repo_part); } if (!tmp) return -ENOMEM; if (!strchr(tmp, '/') && (!strcmp(ctx->registry, DOCKER_API_REGISTRY) || !strcmp(ctx->registry, DOCKER_REGISTRY))) { char *full_repo; if (asprintf(&full_repo, "library/%s", tmp) == -1) { free(tmp); return -ENOMEM; } free(tmp); tmp = full_repo; } free(ctx->repository); ctx->repository = tmp; if (colon) { free(ctx->tag); ctx->tag = strdup(colon + 1); if (!ctx->tag) return -ENOMEM; } return 0; } const char *ocierofs_get_platform_spec(void) { #if defined(__linux__) #define EROFS_OCI_OS "linux" #elif defined(__APPLE__) #define EROFS_OCI_OS "darwin" #elif defined(_WIN32) #define EROFS_OCI_OS "windows" #elif defined(__FreeBSD__) #define EROFS_OCI_OS "freebsd" #endif #if defined(__x86_64__) || defined(__amd64__) return EROFS_OCI_OS "/amd64"; #elif defined(__aarch64__) || defined(__arm64__) return EROFS_OCI_OS "/arm64/v8"; #elif defined(__i386__) return EROFS_OCI_OS "/386"; #elif defined(__arm__) return EROFS_OCI_OS "/arm/v7"; #elif defined(__riscv) && (__riscv_xlen == 64) return EROFS_OCI_OS "/riscv64"; #elif defined(__ppc64__) && defined(__BYTE_ORDER__) && \ (__BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__) return EROFS_OCI_OS "/ppc64le"; #elif defined(__ppc64__) return EROFS_OCI_OS "/ppc64"; #elif defined(__s390x__) return EROFS_OCI_OS "/s390x"; #else return NULL; #endif } /** * ocierofs_init - Initialize OCI context * @ctx: OCI context structure to initialize * @config: OCI configuration * * Initialize OCI context structure, set up CURL handle, and configure * default parameters including platform (host platform), registry * (registry-1.docker.io), and tag (latest). * * Return: 0 on success, negative errno on failure */ static int ocierofs_init(struct ocierofs_ctx *ctx, const struct ocierofs_config *config) { int ret; ctx->curl = curl_easy_init(); if (!ctx->curl) return -EIO; if (ocierofs_curl_setup_common_options(ctx->curl)) return -EIO; if (config->blob_digest) ctx->blob_digest = strdup(config->blob_digest); else ctx->blob_digest = NULL; ctx->registry = strdup("registry-1.docker.io"); ctx->tag = strdup("latest"); ctx->platform = strdup(config->platform ?: ocierofs_get_platform_spec()); if (!ctx->registry || !ctx->tag || !ctx->platform) return -ENOMEM; ctx->schema = config->insecure ? "http://" : "https://"; ret = ocierofs_parse_ref(ctx, config->image_ref); if (ret) return ret; if (config->insecure && (!strcmp(ctx->registry, DOCKER_API_REGISTRY) || !strcmp(ctx->registry, DOCKER_REGISTRY))) { erofs_err("Insecure connection to Docker registry is not allowed"); return -EINVAL; } ret = ocierofs_prepare_layers(ctx, config); if (ret) return ret; return 0; } static int ocierofs_download_blob_to_fd(struct ocierofs_ctx *ctx, const char *digest, const char *auth_header, int outfd) { struct ocierofs_request req = {}; struct ocierofs_stream stream = {}; const char *api_registry; long http_code; int ret; stream = (struct ocierofs_stream) { .digest = digest, .blobfd = outfd, }; api_registry = ocierofs_get_api_registry(ctx->registry); if (asprintf(&req.url, "%s%s/v2/%s/blobs/%s", ctx->schema, api_registry, ctx->repository, digest) == -1) return -ENOMEM; if (auth_header && strstr(auth_header, "Bearer")) req.headers = curl_slist_append(req.headers, auth_header); curl_easy_reset(ctx->curl); ret = ocierofs_curl_setup_common_options(ctx->curl); if (ret) goto out; ret = ocierofs_curl_setup_rq(ctx->curl, req.url, OCIEROFS_HTTP_GET, req.headers, ocierofs_layer_write_callback, &stream, NULL, NULL); if (ret) goto out; ret = ocierofs_curl_perform(ctx->curl, &http_code); if (ret) goto out; if (http_code < 200 || http_code >= 300) { erofs_err("HTTP request failed with code %ld", http_code); ret = -EIO; goto out; } ret = 0; out: ocierofs_request_cleanup(&req); return ret; } static int ocierofs_extract_layer(struct ocierofs_ctx *ctx, const char *digest, const char *auth_header) { struct ocierofs_stream stream = {}; int ret; stream = (struct ocierofs_stream) { .digest = digest, .blobfd = erofs_tmpfile(), }; if (stream.blobfd < 0) { erofs_err("failed to create temporary file for %s", digest); return -errno; } ret = ocierofs_download_blob_to_fd(ctx, digest, auth_header, stream.blobfd); if (ret) goto out; if (lseek(stream.blobfd, 0, SEEK_SET) < 0) { erofs_err("failed to seek to beginning of temp file: %s", strerror(errno)); ret = -errno; goto out; } return stream.blobfd; out: if (stream.blobfd >= 0) close(stream.blobfd); return ret; } /** * ocierofs_ctx_cleanup - Clean up OCI context and free allocated resources * @ctx: OCI context structure to clean up * * Clean up CURL handle, free all allocated string parameters, and * reset the OCI context structure to a clean state. */ static void ocierofs_ctx_cleanup(struct ocierofs_ctx *ctx) { if (!ctx) return; if (ctx->curl) { curl_easy_cleanup(ctx->curl); ctx->curl = NULL; } free(ctx->auth_header); ctx->auth_header = NULL; ocierofs_free_layers_info(ctx->layers, ctx->layer_count); free(ctx->registry); free(ctx->repository); free(ctx->tag); free(ctx->platform); free(ctx->manifest_digest); free(ctx->blob_digest); } int ocierofs_build_trees(struct erofs_importer *importer, const struct ocierofs_config *config) { struct ocierofs_ctx ctx = {}; int ret, i, end, fd; u64 tar_offset = 0; ret = ocierofs_init(&ctx, config); if (ret) { ocierofs_ctx_cleanup(&ctx); return ret; } if (ctx.blob_digest) { i = ocierofs_find_layer_by_digest(&ctx, ctx.blob_digest); if (i < 0) { erofs_err("layer digest %s not found", ctx.blob_digest); ret = -ENOENT; goto out; } end = i + 1; } else { i = 0; end = ctx.layer_count; } if (config->tarindex_path && (end - i) != 1) { erofs_err("tarindex mode requires exactly one layer (use blob= or layer= option)"); ret = -EINVAL; goto out; } while (i < end) { char *trimmed = erofs_trim_for_progressinfo(ctx.layers[i]->digest, sizeof("Extracting layer ...") - 1); erofs_update_progressinfo("Extracting layer %s ...", trimmed); free(trimmed); fd = ocierofs_extract_layer(&ctx, ctx.layers[i]->digest, ctx.auth_header); if (fd < 0) { erofs_err("failed to extract layer %s: %s", ctx.layers[i]->digest, erofs_strerror(fd)); ret = fd; break; } ret = ocierofs_process_tar_stream(importer, fd, config, &tar_offset); close(fd); if (ret) { erofs_err("failed to process tar stream for layer %s: %s", ctx.layers[i]->digest, erofs_strerror(ret)); break; } i++; } out: if (config->tarindex_path && importer->sbi) importer->sbi->devs[0].blocks = BLK_ROUND_UP(importer->sbi, tar_offset); ocierofs_ctx_cleanup(&ctx); return ret; } static int ocierofs_download_blob_range(struct ocierofs_ctx *ctx, off_t offset, size_t length, void **out_buf, size_t *out_size) { struct ocierofs_request req = {}; struct ocierofs_response resp = {}; const char *api_registry; char rangehdr[64]; long http_code = 0; int ret, index; const char *digest; u64 blob_size; size_t available; size_t copy_size; index = ocierofs_find_layer_by_digest(ctx, ctx->blob_digest); if (index < 0) return -ENOENT; digest = ctx->blob_digest; blob_size = ctx->layers[index]->size; if (offset < 0) return -EINVAL; if (offset >= blob_size) { *out_size = 0; return 0; } if (length && offset + length > blob_size) length = (size_t)(blob_size - offset); api_registry = ocierofs_get_api_registry(ctx->registry); if (asprintf(&req.url, "%s%s/v2/%s/blobs/%s", ctx->schema, api_registry, ctx->repository, digest) == -1) return -ENOMEM; if (length) snprintf(rangehdr, sizeof(rangehdr), "Range: bytes=%lld-%lld", (long long)offset, (long long)(offset + (off_t)length - 1)); else snprintf(rangehdr, sizeof(rangehdr), "Range: bytes=%lld-", (long long)offset); if (ctx->auth_header && strstr(ctx->auth_header, "Bearer")) req.headers = curl_slist_append(req.headers, ctx->auth_header); req.headers = curl_slist_append(req.headers, rangehdr); curl_easy_reset(ctx->curl); ret = ocierofs_curl_setup_common_options(ctx->curl); if (ret) goto out; ret = ocierofs_curl_setup_rq(ctx->curl, req.url, OCIEROFS_HTTP_GET, req.headers, ocierofs_write_callback, &resp, NULL, NULL); if (ret) goto out; ret = ocierofs_curl_perform(ctx->curl, &http_code); if (ret) goto out; ret = 0; if (http_code == 206) { *out_buf = resp.data; *out_size = resp.size; resp.data = NULL; } else if (http_code == 200) { if (!offset) { *out_buf = resp.data; *out_size = resp.size; resp.data = NULL; } else if (offset < resp.size) { available = resp.size - offset; copy_size = length ? min_t(size_t, length, available) : available; *out_buf = malloc(copy_size); if (!*out_buf) { ret = -ENOMEM; goto out; } memcpy(*out_buf, resp.data + offset, copy_size); *out_size = copy_size; } } else { erofs_err("HTTP range request failed with code %ld", http_code); ret = -EIO; } out: if (req.headers) curl_slist_free_all(req.headers); free(req.url); free(resp.data); return ret; } static ssize_t ocierofs_io_pread(struct erofs_vfile *vf, void *buf, size_t len, u64 offset) { struct ocierofs_iostream *oci_iostream = *(struct ocierofs_iostream **)vf->payload; void *download_buf = NULL; size_t download_size = 0; ssize_t ret; ret = ocierofs_download_blob_range(oci_iostream->ctx, offset, len, &download_buf, &download_size); if (ret < 0) return ret; if (download_buf && download_size > 0) { memcpy(buf, download_buf, download_size); free(download_buf); return download_size; } return 0; } static ssize_t ocierofs_io_read(struct erofs_vfile *vf, void *buf, size_t len) { struct ocierofs_iostream *oci_iostream = *(struct ocierofs_iostream **)vf->payload; ssize_t ret; ret = ocierofs_io_pread(vf, buf, len, oci_iostream->offset); if (ret > 0) oci_iostream->offset += ret; return ret; } static void ocierofs_io_close(struct erofs_vfile *vfile) { struct ocierofs_iostream *oci_iostream = *(struct ocierofs_iostream **)vfile->payload; ocierofs_ctx_cleanup(oci_iostream->ctx); free(oci_iostream->ctx); free(oci_iostream); *(struct ocierofs_iostream **)vfile->payload = NULL; } static struct erofs_vfops ocierofs_io_vfops = { .pread = ocierofs_io_pread, .read = ocierofs_io_read, .close = ocierofs_io_close, }; int ocierofs_io_open(struct erofs_vfile *vfile, const struct ocierofs_config *cfg) { struct ocierofs_ctx *ctx; struct ocierofs_iostream *oci_iostream = NULL; int err; ctx = calloc(1, sizeof(*ctx)); if (!ctx) return -ENOMEM; err = ocierofs_init(ctx, cfg); if (err) goto out; if (!ctx->blob_digest) { err = -EINVAL; goto out; } oci_iostream = calloc(1, sizeof(*oci_iostream)); if (!oci_iostream) { err = -ENOMEM; goto out; } oci_iostream->ctx = ctx; oci_iostream->offset = 0; *vfile = (struct erofs_vfile){.ops = &ocierofs_io_vfops}; *(struct ocierofs_iostream **)vfile->payload = oci_iostream; return 0; out: ocierofs_ctx_cleanup(ctx); free(ctx); return err; } char *ocierofs_encode_userpass(const char *username, const char *password) { char *userpw, *out; size_t outlen; int ret; ret = asprintf(&userpw, "%s:%s", username ?: "", password ?: ""); if (ret < 0) return ERR_PTR(-ENOMEM); outlen = 4 * DIV_ROUND_UP(ret, 3); out = malloc(outlen + 1); if (!out) { ret = -ENOMEM; } else { ret = erofs_base64_encode((u8 *)userpw, ret, out); if (ret < 0) free(out); else out[ret] = '\0'; } free(userpw); return ret < 0 ? ERR_PTR(ret) : out; } int ocierofs_decode_userpass(const char *b64, char **out_user, char **out_pass) { size_t len; unsigned char *out; int ret; char *colon; if (!b64 || !out_user || !out_pass) return -EINVAL; *out_user = NULL; *out_pass = NULL; len = strlen(b64); out = malloc(len * 3 / 4 + 1); if (!out) return -ENOMEM; ret = erofs_base64_decode(b64, len, out); if (ret < 0) { free(out); return ret; } out[ret] = '\0'; colon = (char *)memchr(out, ':', ret); if (!colon) { free(out); return -EINVAL; } *colon = '\0'; *out_user = strdup((char *)out); *out_pass = strdup(colon + 1); free(out); if (!*out_user || !*out_pass) { free(*out_user); free(*out_pass); *out_user = *out_pass = NULL; return -ENOMEM; } return 0; } #else int ocierofs_io_open(struct erofs_vfile *vfile, const struct ocierofs_config *cfg) { return -EOPNOTSUPP; } #endif #if defined(OCIEROFS_ENABLED) && defined(TEST) struct ocierofs_parse_ref_testcase { const char *name; const char *ref_str; const char *expected_registry; const char *expected_repository; const char *expected_tag; }; static bool run_ocierofs_parse_ref_test(const struct ocierofs_parse_ref_testcase *tc) { struct ocierofs_ctx ctx = {}; int ret; printf("Running test: %s\n", tc->name); /* Initialize with default values */ ctx.registry = strdup(DOCKER_API_REGISTRY); ctx.tag = strdup("latest"); if (!ctx.registry || !ctx.tag) { printf(" FAILED: memory allocation error during setup\n"); free(ctx.registry); free(ctx.tag); return false; } ret = ocierofs_parse_ref(&ctx, tc->ref_str); if (ret < 0) { printf(" FAILED: ocierofs_parse_ref returned %d\n", ret); goto cleanup; } if (tc->expected_registry && strcmp(ctx.registry, tc->expected_registry) != 0) { printf(" FAILED: registry mismatch\n"); printf(" Expected: %s\n", tc->expected_registry); printf(" Got: %s\n", ctx.registry); ret = -EINVAL; goto cleanup; } if (tc->expected_repository && strcmp(ctx.repository, tc->expected_repository) != 0) { printf(" FAILED: repository mismatch\n"); printf(" Expected: %s\n", tc->expected_repository); printf(" Got: %s\n", ctx.repository); ret = -EINVAL; goto cleanup; } if (tc->expected_tag && strcmp(ctx.tag, tc->expected_tag) != 0) { printf(" FAILED: tag mismatch\n"); printf(" Expected: %s\n", tc->expected_tag); printf(" Got: %s\n", ctx.tag); ret = -EINVAL; goto cleanup; } printf(" PASSED\n"); printf(" Registry: %s\n", ctx.registry); printf(" Repository: %s\n", ctx.repository); printf(" Tag: %s\n", ctx.tag); cleanup: free(ctx.registry); free(ctx.repository); free(ctx.tag); return ret == 0; } static int test_ocierofs_parse_ref(void) { struct ocierofs_parse_ref_testcase tests[] = { { .name = "Simple image name (Docker Hub library)", .ref_str = "nginx", .expected_registry = DOCKER_API_REGISTRY, .expected_repository = "library/nginx", .expected_tag = "latest", }, { .name = "Image with tag (Docker Hub library)", .ref_str = "nginx:1.21", .expected_registry = DOCKER_API_REGISTRY, .expected_repository = "library/nginx", .expected_tag = "1.21", }, { .name = "User repository without tag", .ref_str = "user/myapp", .expected_registry = DOCKER_API_REGISTRY, .expected_repository = "user/myapp", .expected_tag = "latest", }, { .name = "User repository with tag", .ref_str = "user/myapp:v2.0", .expected_registry = DOCKER_API_REGISTRY, .expected_repository = "user/myapp", .expected_tag = "v2.0", }, { .name = "Custom registry without tag", .ref_str = "registry.example.com/myapp", .expected_registry = "registry.example.com", .expected_repository = "myapp", .expected_tag = "latest", }, { .name = "Custom registry with tag", .ref_str = "registry.example.com/myapp:v1.0", .expected_registry = "registry.example.com", .expected_repository = "myapp", .expected_tag = "v1.0", }, { .name = "Custom registry with port", .ref_str = "localhost:5000/myapp:latest", .expected_registry = "localhost:5000", .expected_repository = "myapp", .expected_tag = "latest", }, { .name = "Custom registry with ip & port", .ref_str = "127.0.0.1:5000/myapp:latest", .expected_registry = "127.0.0.1:5000", .expected_repository = "myapp", .expected_tag = "latest", }, { .name = "Custom registry with nested repository", .ref_str = "registry.example.com/org/project/app:dev", .expected_registry = "registry.example.com", .expected_repository = "org/project/app", .expected_tag = "dev", }, { .name = "Tag with digest-like format", .ref_str = "myapp:sha256-abc123", .expected_registry = DOCKER_API_REGISTRY, .expected_repository = "library/myapp", .expected_tag = "sha256-abc123", }, { .name = "Multi-level path without registry", .ref_str = "org/team/app:v1", .expected_registry = DOCKER_API_REGISTRY, .expected_repository = "org/team/app", .expected_tag = "v1", }, }; int i, pass = 0; for (i = 0; i < ARRAY_SIZE(tests); ++i) { pass += run_ocierofs_parse_ref_test(&tests[i]); putc('\n', stdout); } printf("Run all %d tests with %d PASSED\n", i, pass); return ARRAY_SIZE(tests) == pass; } int main(int argc, char *argv[]) { exit(test_ocierofs_parse_ref() ? EXIT_SUCCESS : EXIT_FAILURE); } #endif erofs-utils-1.9.1/lib/remotes/s3.c000066400000000000000000001176511515160260000167220ustar00rootroot00000000000000// SPDX-License-Identifier: GPL-2.0+ OR Apache-2.0 /* * Copyright (C) 2025 HUAWEI, Inc. * http://www.huawei.com/ * Created by Yifan Zhao */ #define _GNU_SOURCE #include #include #include #include #include #include #include #include "erofs/internal.h" #include "erofs/print.h" #include "erofs/inode.h" #include "erofs/blobchunk.h" #include "erofs/diskbuf.h" #include "erofs/importer.h" #include "liberofs_rebuild.h" #include "liberofs_s3.h" #define S3EROFS_PATH_MAX 1024 #define S3EROFS_MAX_QUERY_PARAMS 16 #define S3EROFS_URL_LEN 8192 #define S3EROFS_CANONICAL_URI_LEN 2048 #define S3EROFS_CANONICAL_QUERY_LEN S3EROFS_URL_LEN #define BASE64_ENCODE_LEN(len) (((len + 2) / 3) * 4) struct s3erofs_query_params { int num; const char *key[S3EROFS_MAX_QUERY_PARAMS]; const char *value[S3EROFS_MAX_QUERY_PARAMS]; }; struct s3erofs_curl_request { char url[S3EROFS_URL_LEN]; char canonical_uri[S3EROFS_CANONICAL_URI_LEN]; char canonical_query[S3EROFS_CANONICAL_QUERY_LEN]; }; static const char *s3erofs_parse_host(const char *endpoint, const char **schema) { const char *host, *split; split = strstr(endpoint, "://"); if (!split) { host = endpoint; if (schema) *schema = NULL; } else { host = split + sizeof("://") - 1; if (schema) { *schema = strndup(endpoint, host - endpoint); if (!*schema) return ERR_PTR(-ENOMEM); } } return host; } enum s3erofs_urlencode_mode { S3EROFS_URLENCODE_QUERY_PARAM, S3EROFS_URLENCODE_S3_KEY, }; static void *s3erofs_urlencode(const char *input, enum s3erofs_urlencode_mode mode) { static const char hex[] = "0123456789ABCDEF"; char *p, *url; int i, c; bool safe; url = malloc(strlen(input) * 3 + 1); if (!url) return ERR_PTR(-ENOMEM); p = url; for (i = 0; i < strlen(input); ++i) { c = (unsigned char)input[i]; if (mode == S3EROFS_URLENCODE_S3_KEY) /* * AWS S3 safe characters for object key names: * - Alphanumeric: 0-9 a-z A-Z * - Special: ! - _ . * ' ( ) * - Forward slash (/) for hierarchy * See: https://docs.aws.amazon.com/AmazonS3/latest/userguide/object-keys.html */ safe = isalpha(c) || isdigit(c) || c == '!' || c == '-' || c == '_' || c == '.' || c == '*' || c == '(' || c == ')' || c == '\'' || c == '/'; else /* * URL encode query parameters * See: https://docs.aws.amazon.com/AmazonS3/latest/API/sig-v4-header-based-auth.html#create-signature-presign-entire-payload */ safe = isalpha(c) || isdigit(c) || c == '-' || c == '.' || c == '_' || c == '~'; if (safe) { *p++ = c; } else { /* URL encode this character */ *p++ = '%'; *p++ = hex[c >> 4]; *p++ = hex[c & 0x0F]; } } *p = '\0'; return url; } struct s3erofs_qsort_kv { char *key; char *value; }; static int compare_kv_pair(const void *a, const void *b) { return strcmp(((const struct s3erofs_qsort_kv *)a)->key, ((const struct s3erofs_qsort_kv *)b)->key); } static int s3erofs_prepare_canonical_query(struct s3erofs_curl_request *req, struct s3erofs_query_params *params) { struct s3erofs_qsort_kv *pairs; int i, pos = 0, ret = 0; if (!params->num) return 0; pairs = calloc(1, sizeof(struct s3erofs_qsort_kv) * params->num); for (i = 0; i < params->num; i++) { pairs[i].key = s3erofs_urlencode(params->key[i], S3EROFS_URLENCODE_QUERY_PARAM); if (IS_ERR(pairs[i].key)) { ret = PTR_ERR(pairs[i].key); pairs[i].key = NULL; goto out; } pairs[i].value = s3erofs_urlencode(params->value[i], S3EROFS_URLENCODE_QUERY_PARAM); if (IS_ERR(pairs[i].value)) { ret = PTR_ERR(pairs[i].value); pairs[i].value = NULL; goto out; } } qsort(pairs, params->num, sizeof(struct s3erofs_qsort_kv), compare_kv_pair); for (i = 0; i < params->num; i++) pos += snprintf(req->canonical_query + pos, S3EROFS_CANONICAL_QUERY_LEN - pos, "%s=%s%s", pairs[i].key, pairs[i].value, (i == params->num - 1) ? "" : "&"); req->canonical_query[pos] = '\0'; out: for (i = 0; i < params->num; i++) { free(pairs[i].key); free(pairs[i].value); } free(pairs); return ret; } static int s3erofs_prepare_url(struct s3erofs_curl_request *req, const char *endpoint, const char *path, const char *key, struct s3erofs_query_params *params, enum s3erofs_url_style url_style, enum s3erofs_signature_version sig) { static const char https[] = "https://"; const char *schema, *host; /* an additional slash is added, which wasn't specified by user inputs */ bool slash = false; bool bucket_domain = false; char *url = req->url; char *encoded_key = NULL; int pos, canonical_uri_pos, i, ret = 0; if (!endpoint) return -EINVAL; host = s3erofs_parse_host(endpoint, &schema); if (IS_ERR(host)) return PTR_ERR(host); if (!schema) schema = https; if (__erofs_unlikely(!path)) path = "/"; if (__erofs_unlikely(path[0] == '/')) { path++; bucket_domain = true; if (url_style != S3EROFS_URL_STYLE_VIRTUAL_HOST) return -EINVAL; } if (url_style == S3EROFS_URL_STYLE_PATH) { pos = snprintf(url, S3EROFS_URL_LEN, "%s%s/%s", schema, host, path); canonical_uri_pos = pos - strlen(path) - 1; } else { const char *split = strchr(path, '/'); if (bucket_domain) { pos = snprintf(url, S3EROFS_URL_LEN, "%s%s/%s", schema, host, path); canonical_uri_pos = pos - 1; } else if (!split) { pos = snprintf(url, S3EROFS_URL_LEN, "%s%s.%s/", schema, path, host); canonical_uri_pos = pos - 1; slash = true; } else { pos = snprintf(url, S3EROFS_URL_LEN, "%s%.*s.%s%s", schema, (int)(split - path), path, host, split); canonical_uri_pos = pos - strlen(split); } } if (key) { encoded_key = s3erofs_urlencode(key, S3EROFS_URLENCODE_S3_KEY); if (IS_ERR(encoded_key)) { ret = PTR_ERR(encoded_key); encoded_key = NULL; goto err; } if (url[pos - 1] == '/') --pos; else slash = true; pos += snprintf(url + pos, S3EROFS_URL_LEN - pos, "/%s", encoded_key); } if (sig == S3EROFS_SIGNATURE_VERSION_2) { if (bucket_domain) { const char *bucket = strchr(host, '.'); if (!bucket) { ret = -EINVAL; goto err; } i = snprintf(req->canonical_uri, S3EROFS_CANONICAL_URI_LEN, "/%.*s/", (int)(bucket - host), host); } else { req->canonical_uri[0] = '/'; i = 1; } i += snprintf(req->canonical_uri + i, S3EROFS_CANONICAL_URI_LEN - i, "%s%s%s", path, slash ? "/" : "", encoded_key ? encoded_key : ""); } else { i = snprintf(req->canonical_uri, S3EROFS_CANONICAL_URI_LEN, "%s", url + canonical_uri_pos); } req->canonical_uri[i] = '\0'; if (params) { for (i = 0; i < params->num; i++) pos += snprintf(url + pos, S3EROFS_URL_LEN - pos, "%c%s=%s", (!i ? '?' : '&'), params->key[i], params->value[i]); ret = s3erofs_prepare_canonical_query(req, params); if (ret < 0) goto err; } erofs_dbg("Request URL %s", url); erofs_dbg("Request canonical_uri %s", req->canonical_uri); err: if (encoded_key) free(encoded_key); if (schema != https) free((void *)schema); return ret; } static char *get_canonical_headers(const struct curl_slist *list) { const struct curl_slist *current = list; char *result; size_t len = 0; while (current) { len += strlen(current->data) + 1; current = current->next; } result = (char *)malloc(len + 1); if (!result) return NULL; current = list; len = 0; while (current) { strcpy(result + len, current->data); len += strlen(current->data); result[len++] = '\n'; current = current->next; } result[len] = '\0'; return result; } enum s3erofs_date_format { S3EROFS_DATE_RFC1123, S3EROFS_DATE_ISO8601, S3EROFS_DATE_YYYYMMDD }; static void s3erofs_format_time(time_t t, char *buf, size_t maxlen, enum s3erofs_date_format fmt) { const char *format; struct tm *ptm = gmtime(&t); switch (fmt) { case S3EROFS_DATE_RFC1123: format = "%a, %d %b %Y %H:%M:%S GMT"; break; case S3EROFS_DATE_ISO8601: format = "%Y%m%dT%H%M%SZ"; break; case S3EROFS_DATE_YYYYMMDD: format = "%Y%m%d"; break; default: erofs_err("unknown date format %d", fmt); buf[0] = '\0'; return; } strftime(buf, maxlen, format, ptm); } static void s3erofs_to_hex(const u8 *data, size_t len, char *output) { static const char hex_chars[] = "0123456789abcdef"; size_t i; for (i = 0; i < len; i++) { output[i * 2] = hex_chars[data[i] >> 4]; output[i * 2 + 1] = hex_chars[data[i] & 0x0f]; } output[len * 2] = '\0'; } // See: https://docs.aws.amazon.com/AmazonS3/latest/API/RESTAuthentication.html#ConstructingTheAuthenticationHeader static char *s3erofs_sigv2_header(const struct curl_slist *headers, const char *content_md5, const char *content_type, const char *date, const char *canonical_uri, const char *ak, const char *sk) { u8 hmac_signature[EVP_MAX_MD_SIZE]; char *str, *output = NULL; unsigned int len, pos, output_len; const char *prefix = "Authorization: AWS "; if (!date || !ak || !sk) return ERR_PTR(-EINVAL); if (!content_md5) content_md5 = ""; if (!content_type) content_type = ""; if (!canonical_uri) canonical_uri = "/"; pos = asprintf(&str, "GET\n%s\n%s\n%s\n%s%s", content_md5, content_type, date, "", canonical_uri); if (pos < 0) return ERR_PTR(-ENOMEM); if (!HMAC(EVP_sha1(), sk, strlen(sk), (u8 *)str, strlen(str), hmac_signature, &len)) goto free_string; output_len = BASE64_ENCODE_LEN(len); output_len += strlen(prefix); output_len += strlen(ak); output_len += 1; /* for ':' between ak and signature */ output = (char *)malloc(output_len + 1); if (!output) goto free_string; pos = snprintf(output, output_len, "%s%s:", prefix, ak); if (pos < 0) goto free_string; EVP_EncodeBlock((u8 *)output + pos, hmac_signature, len); free_string: free(str); return output ?: ERR_PTR(-ENOMEM); } // See: https://docs.aws.amazon.com/AmazonS3/latest/API/sig-v4-header-based-auth.html static char *s3erofs_sigv4_header(const struct curl_slist *headers, time_t request_time, const char *canonical_uri, const char *canonical_query, const char *region, const char *ak, const char *sk) { u8 ping_buf[EVP_MAX_MD_SIZE], pong_buf[EVP_MAX_MD_SIZE]; char hex_buf[EVP_MAX_MD_SIZE * 2 + 1]; char date_str[16], timestamp[32]; char *canonical_request, *canonical_headers; char *string_to_sign, *scope, *aws4_secret; unsigned int len; char *output = NULL; int err = 0; if (!canonical_uri || !region || !ak || !sk) return ERR_PTR(-EINVAL); if (!canonical_query) canonical_query = ""; canonical_headers = get_canonical_headers(headers); if (!canonical_headers) return ERR_PTR(-ENOMEM); // Get current time in required formats s3erofs_format_time(request_time, date_str, sizeof(date_str), S3EROFS_DATE_YYYYMMDD); s3erofs_format_time(request_time, timestamp, sizeof(timestamp), S3EROFS_DATE_ISO8601); // Task 1: Create canonical request if (asprintf(&canonical_request, "GET\n" "%s\n" "%s\n" "%s\n" "host;x-amz-content-sha256;x-amz-date\n" "UNSIGNED-PAYLOAD", canonical_uri, canonical_query, canonical_headers) < 0) { err = -ENOMEM; goto err_canonical_headers; } // Hash the canonical request if (!EVP_Digest(canonical_request, strlen(canonical_request), ping_buf, &len, EVP_sha256(), NULL)) { err = -EIO; goto err_canonical_request; } s3erofs_to_hex(ping_buf, len, hex_buf); // Task 2: Create string to sign if (asprintf(&scope, "%s/%s/s3/aws4_request", date_str, region) < 0) { err = -ENOMEM; goto err_canonical_request; } if (asprintf(&string_to_sign, "AWS4-HMAC-SHA256\n" "%s\n" // timestamp (ISO8601, e.g., 20251115T123456Z) "%s\n" // credential scope (e.g., 20251115/us-east-1/s3/aws4_request) "%s", // canonical request hash (hex-encoded SHA-256) timestamp, scope, hex_buf) < 0) { err = -ENOMEM; goto err_scope; } // Task 3: Calculate signing key if (asprintf(&aws4_secret, "AWS4%s", sk) < 0) { err = -ENOMEM; goto err_string_to_sign; } if (!HMAC(EVP_sha256(), aws4_secret, strlen(aws4_secret), (u8 *)date_str, strlen(date_str), ping_buf, &len)) { err = -EIO; goto err_aws4_secret; } if (!HMAC(EVP_sha256(), ping_buf, len, (u8 *)region, strlen(region), pong_buf, &len)) { err = -EIO; goto err_aws4_secret; } if (!HMAC(EVP_sha256(), pong_buf, len, (u8 *)"s3", strlen("s3"), ping_buf, &len)) { err = -EIO; goto err_aws4_secret; } if (!HMAC(EVP_sha256(), ping_buf, len, (u8 *)"aws4_request", strlen("aws4_request"), pong_buf, &len)) { err = -EIO; goto err_aws4_secret; } // Calculate signature if (!HMAC(EVP_sha256(), pong_buf, len, (u8 *)string_to_sign, strlen(string_to_sign), ping_buf, &len)) { err = -EIO; goto err_aws4_secret; } s3erofs_to_hex(ping_buf, len, hex_buf); // Build Authorization header if (asprintf(&output, "Authorization: AWS4-HMAC-SHA256 " "Credential=%s/%s, " "SignedHeaders=host;x-amz-content-sha256;x-amz-date, " "Signature=%s", ak, scope, hex_buf) < 0) { err = -ENOMEM; goto err_aws4_secret; } err_aws4_secret: free(aws4_secret); err_string_to_sign: free(string_to_sign); err_scope: free(scope); err_canonical_request: free(canonical_request); err_canonical_headers: free(canonical_headers); return err ? ERR_PTR(err) : output; } static int s3erofs_request_insert_auth_v2(struct curl_slist **request_headers, struct s3erofs_curl_request *req, struct erofs_s3 *s3) { static const char date_prefix[] = "Date: "; char date[64], *sigv2; memcpy(date, date_prefix, sizeof(date_prefix) - 1); s3erofs_format_time(time(NULL), date + sizeof(date_prefix) - 1, sizeof(date) - sizeof(date_prefix) + 1, S3EROFS_DATE_RFC1123); sigv2 = s3erofs_sigv2_header(*request_headers, NULL, NULL, date + sizeof(date_prefix) - 1, req->canonical_uri, s3->access_key, s3->secret_key); if (IS_ERR(sigv2)) return PTR_ERR(sigv2); *request_headers = curl_slist_append(*request_headers, date); *request_headers = curl_slist_append(*request_headers, sigv2); free(sigv2); return 0; } static int s3erofs_request_insert_auth_v4(struct curl_slist **request_headers, struct s3erofs_curl_request *req, struct erofs_s3 *s3) { char timestamp[32], *sigv4, *tmp; const char *host, *host_end; time_t request_time = time(NULL); /* Add following headers for SigV4 in alphabetical order: */ /* 1. host */ host = s3erofs_parse_host(req->url, NULL); host_end = strchr(host, '/'); if (!host_end) return -EINVAL; if (asprintf(&tmp, "host:%.*s", (int)(host_end - host), host) < 0) return -ENOMEM; *request_headers = curl_slist_append(*request_headers, tmp); free(tmp); /* 2. x-amz-content-sha256 */ *request_headers = curl_slist_append( *request_headers, "x-amz-content-sha256:UNSIGNED-PAYLOAD"); /* 3. x-amz-date */ s3erofs_format_time(request_time, timestamp, sizeof(timestamp), S3EROFS_DATE_ISO8601); if (asprintf(&tmp, "x-amz-date:%s", timestamp) < 0) return -ENOMEM; *request_headers = curl_slist_append(*request_headers, tmp); free(tmp); sigv4 = s3erofs_sigv4_header(*request_headers, request_time, req->canonical_uri, req->canonical_query, s3->region, s3->access_key, s3->secret_key); if (IS_ERR(sigv4)) return PTR_ERR(sigv4); *request_headers = curl_slist_append(*request_headers, sigv4); free(sigv4); return 0; } struct s3erofs_curl_response { char *data; size_t size; }; static size_t s3erofs_request_write_memory_cb(void *contents, size_t size, size_t nmemb, void *userp) { size_t realsize = size * nmemb; struct s3erofs_curl_response *response = userp; void *tmp; tmp = realloc(response->data, response->size + realsize + 1); if (tmp == NULL) return 0; response->data = tmp; memcpy(response->data + response->size, contents, realsize); response->size += realsize; response->data[response->size] = '\0'; return realsize; } static int s3erofs_request_perform(struct erofs_s3 *s3, struct s3erofs_curl_request *req, void *resp) { struct curl_slist *request_headers = NULL; CURL *curl = s3->easy_curl; long http_code = 0; int ret; if (s3->access_key[0]) { if (s3->sig == S3EROFS_SIGNATURE_VERSION_4) ret = s3erofs_request_insert_auth_v4(&request_headers, req, s3); else ret = s3erofs_request_insert_auth_v2(&request_headers, req, s3); if (ret < 0) { erofs_err("failed to insert auth headers"); return ret; } } curl_easy_setopt(curl, CURLOPT_URL, req->url); curl_easy_setopt(curl, CURLOPT_WRITEDATA, resp); curl_easy_setopt(curl, CURLOPT_HTTPHEADER, request_headers); ret = curl_easy_perform(curl); if (ret != CURLE_OK) { erofs_err("curl_easy_perform() failed: %s", curl_easy_strerror(ret)); ret = -EIO; goto err_header; } ret = curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &http_code); if (ret != CURLE_OK) { erofs_err("curl_easy_getinfo() failed: %s", curl_easy_strerror(ret)); ret = -EIO; goto err_header; } if (!(http_code >= 200 && http_code < 300)) { erofs_err("request failed with HTTP code %ld", http_code); ret = -EIO; } err_header: curl_slist_free_all(request_headers); return ret; } struct s3erofs_object_info { char *key; u64 size; time_t mtime; u32 mtime_ns; }; struct s3erofs_object_iterator { struct erofs_s3 *s3; struct s3erofs_object_info *objects; int cur; char *bucket, *prefix; const char *delimiter; char *next_marker; bool is_truncated; }; static int s3erofs_parse_list_objects_one(xmlNodePtr node, struct s3erofs_object_info *info) { xmlNodePtr child; xmlChar *str; for (child = node->children; child; child = child->next) { if (child->type == XML_ELEMENT_NODE) { str = xmlNodeGetContent(child); if (!str) return -ENOMEM; if (xmlStrEqual(child->name, (const xmlChar *)"LastModified")) { struct tm tm; char *end; end = strptime((char *)str, "%Y-%m-%dT%H:%M:%S", &tm); if (!end || (*end != '.' && *end != 'Z' && *end != '\0')) { xmlFree(str); return -EIO; } if (*end == '.') { info->mtime_ns = strtoul(end + 1, &end, 10); if (*end != 'Z' && *end != '\0') { xmlFree(str); return -EIO; } } /* * Not set by strptime(); tells mktime() to determine * whether daylight saving time is in effect */ tm.tm_isdst = -1; info->mtime = mktime(&tm); } if (xmlStrEqual(child->name, (const xmlChar *)"Key")) info->key = strdup((char *)str); else if (xmlStrEqual(child->name, (const xmlChar *)"Size")) info->size = atoll((char *)str); xmlFree(str); } } return 0; } static int s3erofs_parse_list_objects_result(const char *data, int len, struct s3erofs_object_iterator *it) { xmlNodePtr root = NULL, node, next; int ret, i, contents_count; xmlDocPtr doc = NULL; xmlChar *str; void *tmp; doc = xmlReadMemory(data, len, NULL, NULL, 0); if (!doc) { erofs_err("failed to parse XML data"); return -EINVAL; } root = xmlDocGetRootElement(doc); if (!root) { erofs_err("failed to get root element"); ret = -EINVAL; goto out; } if (!xmlStrEqual(root->name, (const xmlChar *)"ListBucketResult")) { erofs_err("invalid root element: expected ListBucketResult, got %s", root->name); ret = -EINVAL; goto out; } // https://docs.aws.amazon.com/AmazonS3/latest/API/API_ListObjects.html#AmazonS3-ListObjects-response-NextMarker free(it->next_marker); it->next_marker = NULL; contents_count = 1; for (node = root->children; node; node = next) { next = node->next; if (node->type == XML_ELEMENT_NODE) { if (xmlStrEqual(node->name, (const xmlChar *)"Contents")) { ++contents_count; continue; } if (xmlStrEqual(node->name, (const xmlChar *)"IsTruncated")) { str = xmlNodeGetContent(node); if (str) { it->is_truncated = !!xmlStrEqual(str, (const xmlChar *)"true"); xmlFree(str); } } else if (xmlStrEqual(node->name, (const xmlChar *)"NextMarker")) { str = xmlNodeGetContent(node); if (str) { it->next_marker = strdup((char *)str); xmlFree(str); if (!it->next_marker) { ret = -ENOMEM; goto out; } } } xmlUnlinkNode(node); } xmlUnlinkNode(node); xmlFreeNode(node); } i = 0; if (it->objects) { for (; it->objects[i].key; ++i) { free(it->objects[i].key); it->objects[i].key = NULL; } } if (i + 1 < contents_count) { tmp = malloc(contents_count * sizeof(*it->objects)); if (!tmp) { ret = -ENOMEM; goto out; } free(it->objects); it->objects = tmp; it->objects[0].key = NULL; } it->cur = 0; ret = 0; for (i = 0, node = root->children; node; node = node->next) { if (__erofs_unlikely(i >= contents_count - 1)) { DBG_BUGON(1); continue; } ret = s3erofs_parse_list_objects_one(node, &it->objects[i]); if (ret < 0) { erofs_err("failed to parse contents node %s: %s", (const char *)node->name, erofs_strerror(ret)); break; } it->objects[++i].key = NULL; } /* * `NextMarker` is returned only if the `delimiter` request parameter * is specified. * * If the response is truncated and does not include `NextMarker`, use * the value of the last `Key` element in the response as the `marker` * parameter in the next request. */ if (!ret && i && it->is_truncated && !it->next_marker) { it->next_marker = strdup(it->objects[i - 1].key); if (!it->next_marker) ret = -ENOMEM; } if (!ret) ret = i; out: xmlFreeDoc(doc); return ret; } static int s3erofs_list_objects(struct s3erofs_object_iterator *it) { struct s3erofs_curl_request req = {}; struct s3erofs_curl_response resp = {}; struct s3erofs_query_params params; struct erofs_s3 *s3 = it->s3; int ret = 0; if (it->delimiter && strlen(it->delimiter) > S3EROFS_PATH_MAX) { erofs_err("delimiter is too long"); return -EINVAL; } params.num = 0; if (it->prefix) { params.key[params.num] = "prefix"; params.value[params.num] = it->prefix; ++params.num; } if (it->delimiter) { params.key[params.num] = "delimiter"; params.value[params.num] = it->delimiter; ++params.num; } if (it->next_marker) { params.key[params.num] = "marker"; params.value[params.num] = it->next_marker; ++params.num; } ret = s3erofs_prepare_url(&req, s3->endpoint, it->bucket, NULL, ¶ms, s3->url_style, s3->sig); if (ret < 0) return ret; if (curl_easy_setopt(s3->easy_curl, CURLOPT_WRITEFUNCTION, s3erofs_request_write_memory_cb) != CURLE_OK) return -EIO; ret = s3erofs_request_perform(s3, &req, &resp); if (ret >= 0) ret = s3erofs_parse_list_objects_result(resp.data, resp.size, it); free(resp.data); return ret; } static struct s3erofs_object_iterator * s3erofs_create_object_iterator(struct erofs_s3 *s3, const char *path, const char *delimiter) { struct s3erofs_object_iterator *iter; const char *prefix; iter = calloc(1, sizeof(struct s3erofs_object_iterator)); if (!iter) return ERR_PTR(-ENOMEM); iter->s3 = s3; prefix = strchr(path, '/'); if (!prefix) { iter->bucket = strdup(path); iter->prefix = NULL; } else if (prefix == path) { iter->bucket = NULL; iter->prefix = strdup(path + 1); } else { if (++prefix - path > S3EROFS_PATH_MAX) return ERR_PTR(-EINVAL); iter->bucket = strndup(path, prefix - path); iter->prefix = strdup(prefix); } iter->delimiter = delimiter; iter->is_truncated = true; return iter; } static void s3erofs_destroy_object_iterator(struct s3erofs_object_iterator *it) { int i; if (it->next_marker) free(it->next_marker); if (it->objects) { for (i = 0; it->objects[i].key; ++i) free(it->objects[i].key); free(it->objects); } free(it->prefix); free(it->bucket); free(it); } static struct s3erofs_object_info * s3erofs_get_next_object(struct s3erofs_object_iterator *it) { int ret; if (it->objects && it->objects[it->cur].key) return &it->objects[it->cur++]; if (it->is_truncated) { ret = s3erofs_list_objects(it); if (ret < 0) return ERR_PTR(ret); return &it->objects[it->cur++]; } return NULL; } static int s3erofs_curl_easy_init(struct erofs_s3 *s3) { CURL *curl; curl = curl_easy_init(); if (!curl) return -ENOMEM; if (curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L) != CURLE_OK) goto out_cleanup; if (curl_easy_setopt(curl, CURLOPT_CONNECTTIMEOUT, 30L) != CURLE_OK) goto out_cleanup; if (curl_easy_setopt(curl, CURLOPT_USERAGENT, "s3erofs/" PACKAGE_VERSION) != CURLE_OK) goto out_cleanup; s3->easy_curl = curl; return 0; out_cleanup: curl_easy_cleanup(curl); return -EFAULT; } static void s3erofs_curl_easy_exit(struct erofs_s3 *s3) { if (!s3->easy_curl) return; curl_easy_cleanup(s3->easy_curl); s3->easy_curl = NULL; } struct s3erofs_curl_getobject_resp { struct erofs_vfile *vf; erofs_off_t pos, end; }; static size_t s3erofs_remote_getobject_cb(void *contents, size_t size, size_t nmemb, void *userp) { struct s3erofs_curl_getobject_resp *resp = userp; size_t realsize = size * nmemb; if (resp->pos + realsize > resp->end || erofs_io_pwrite(resp->vf, contents, resp->pos, realsize) != realsize) return 0; resp->pos += realsize; return realsize; } static int s3erofs_remote_getobject(struct erofs_importer *im, struct erofs_s3 *s3, struct erofs_inode *inode, const char *bucket, const char *key) { struct erofs_sb_info *sbi = inode->sbi; struct s3erofs_curl_request req = {}; struct s3erofs_curl_getobject_resp resp; struct erofs_vfile vf; u64 diskbuf_off; int ret; ret = s3erofs_prepare_url(&req, s3->endpoint, bucket, key, NULL, s3->url_style, s3->sig); if (ret < 0) return ret; if (curl_easy_setopt(s3->easy_curl, CURLOPT_WRITEFUNCTION, s3erofs_remote_getobject_cb) != CURLE_OK) return -EIO; resp.pos = 0; if (!sbi->available_compr_algs && im->params->no_datainline) { inode->datalayout = EROFS_INODE_FLAT_PLAIN; inode->idata_size = 0; ret = erofs_allocate_inode_bh_data(inode, DIV_ROUND_UP(inode->i_size, 1U << sbi->blkszbits), false); if (ret) return ret; resp.vf = &sbi->bdev; resp.pos = erofs_pos(inode->sbi, inode->u.i_blkaddr); inode->datasource = EROFS_INODE_DATA_SOURCE_NONE; } else { 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); } vf = (struct erofs_vfile) {.fd = erofs_diskbuf_reserve(inode->i_diskbuf, 0, &diskbuf_off)}; if (vf.fd < 0) return -EBADF; resp.pos = diskbuf_off; resp.vf = &vf; inode->datasource = EROFS_INODE_DATA_SOURCE_DISKBUF; } resp.end = resp.pos + inode->i_size; ret = s3erofs_request_perform(s3, &req, &resp); if (resp.vf == &vf) { erofs_diskbuf_commit(inode->i_diskbuf, resp.pos - diskbuf_off); if (ret) { erofs_diskbuf_close(inode->i_diskbuf); inode->i_diskbuf = NULL; inode->datasource = EROFS_INODE_DATA_SOURCE_NONE; } } if (ret) return ret; return resp.pos != resp.end ? -EIO : 0; } int s3erofs_build_trees(struct erofs_importer *im, struct erofs_s3 *s3, const char *path, bool fillzero) { struct erofs_sb_info *sbi = im->sbi; struct erofs_inode *root = im->root; struct s3erofs_object_iterator *iter; struct s3erofs_object_info *obj; struct erofs_dentry *d; struct erofs_inode *inode; struct stat st; char *trimmed; bool dumb; int ret; st.st_uid = root->i_uid; st.st_gid = root->i_gid; ret = s3erofs_curl_easy_init(s3); if (ret) { erofs_err("failed to initialize s3erofs: %s", erofs_strerror(ret)); return ret; } iter = s3erofs_create_object_iterator(s3, path, NULL); if (IS_ERR(iter)) { erofs_err("failed to create object iterator"); ret = PTR_ERR(iter); goto err_global; } while (1) { obj = s3erofs_get_next_object(iter); if (!obj) { break; } else if (IS_ERR(obj)) { ret = PTR_ERR(obj); erofs_err("failed to get next object: %s", erofs_strerror(ret)); goto err_iter; } d = erofs_rebuild_get_dentry(root, obj->key, false, &dumb, &dumb, false); if (IS_ERR(d)) { ret = PTR_ERR(d); goto err_iter; } if (d->type == EROFS_FT_DIR) { inode = d->inode; inode->i_mode = S_IFDIR | 0755; } else { inode = erofs_new_inode(sbi); if (IS_ERR(inode)) { ret = PTR_ERR(inode); goto err_iter; } inode->i_mode = S_IFREG | 0644; inode->i_parent = d->inode; inode->i_nlink = 1; d->inode = inode; d->type = EROFS_FT_REG_FILE; } inode->i_srcpath = strdup(obj->key); if (!inode->i_srcpath) { ret = -ENOMEM; goto err_iter; } trimmed = erofs_trim_for_progressinfo(inode->i_srcpath, sizeof("Importing ...") - 1); erofs_update_progressinfo("Importing %s ...", trimmed); free(trimmed); st.st_mtime = obj->mtime; ST_MTIM_NSEC_SET(&st, obj->mtime_ns); ret = __erofs_fill_inode(im, inode, &st, obj->key); if (!ret && S_ISREG(inode->i_mode)) { inode->i_size = obj->size; if (fillzero) ret = erofs_write_zero_inode(inode); else ret = s3erofs_remote_getobject(im, s3, inode, iter->bucket, obj->key); } if (ret) goto err_iter; } err_iter: s3erofs_destroy_object_iterator(iter); err_global: s3erofs_curl_easy_exit(s3); return ret; } #ifdef TEST struct s3erofs_prepare_url_testcase { const char *name; const char *endpoint; const char *path; const char *key; enum s3erofs_url_style url_style; const char *expected_url; const char *expected_canonical_v2; const char *expected_canonical_v4; int expected_ret; }; static bool run_s3erofs_prepare_url_test(const struct s3erofs_prepare_url_testcase *tc, enum s3erofs_signature_version sig) { struct s3erofs_curl_request req = {}; int ret; const char *expected_canonical; printf("Running test: %s\n", tc->name); ret = s3erofs_prepare_url(&req, tc->endpoint, tc->path, tc->key, NULL, tc->url_style, sig); if (ret != tc->expected_ret) { printf(" FAILED: expected return %d, got %d\n", tc->expected_ret, ret); return false; } if (ret < 0) { printf(" PASSED (expected error)\n"); return true; } if (tc->expected_url && strcmp(req.url, tc->expected_url) != 0) { printf(" FAILED: URL mismatch\n"); printf(" Expected: %s\n", tc->expected_url); printf(" Got: %s\n", req.url); return false; } expected_canonical = (sig == S3EROFS_SIGNATURE_VERSION_2 ? tc->expected_canonical_v2 : tc->expected_canonical_v4); if (expected_canonical && strcmp(req.canonical_uri, expected_canonical) != 0) { printf(" FAILED: Canonical uri mismatch\n"); printf(" Expected: %s\n", expected_canonical); printf(" Got: %s\n", req.canonical_uri); return false; } printf(" PASSED\n"); printf(" URL: %s\n", req.url); printf(" Canonical: %s\n", req.canonical_uri); return true; } static bool test_s3erofs_prepare_url(void) { struct s3erofs_prepare_url_testcase tests[] = { { .name = "Virtual-hosted style with https", .endpoint = "s3.amazonaws.com", .path = "my-bucket", .key = "path/to/object.txt", .url_style = S3EROFS_URL_STYLE_VIRTUAL_HOST, .expected_url = "https://my-bucket.s3.amazonaws.com/path/to/object.txt", .expected_canonical_v2 = "/my-bucket/path/to/object.txt", .expected_canonical_v4 = "/path/to/object.txt", .expected_ret = 0, }, { .name = "Path style with https", .endpoint = "s3.amazonaws.com", .path = "my-bucket", .key = "path/to/object.txt", .url_style = S3EROFS_URL_STYLE_PATH, .expected_url = "https://s3.amazonaws.com/my-bucket/path/to/object.txt", .expected_canonical_v2 = "/my-bucket/path/to/object.txt", .expected_canonical_v4 = "/my-bucket/path/to/object.txt", .expected_ret = 0, }, { .name = "Virtual-hosted with explicit https://", .endpoint = "https://s3.us-west-2.amazonaws.com", .path = "test-bucket", .key = "file.bin", .url_style = S3EROFS_URL_STYLE_VIRTUAL_HOST, .expected_url = "https://test-bucket.s3.us-west-2.amazonaws.com/file.bin", .expected_canonical_v2 = "/test-bucket/file.bin", .expected_canonical_v4 = "/file.bin", .expected_ret = 0, }, { .name = "Path style with explicit http://", .endpoint = "http://localhost:9000", .path = "local-bucket", .key = "data/file.dat", .url_style = S3EROFS_URL_STYLE_PATH, .expected_url = "http://localhost:9000/local-bucket/data/file.dat", .expected_canonical_v2 = "/local-bucket/data/file.dat", .expected_canonical_v4 = "/local-bucket/data/file.dat", .expected_ret = 0, }, { .name = "Virtual-hosted style with key ends with slash", .endpoint = "http://localhost:9000", .path = "local-bucket", .key = "data/file.dat/", .url_style = S3EROFS_URL_STYLE_VIRTUAL_HOST, .expected_url = "http://local-bucket.localhost:9000/data/file.dat/", .expected_canonical_v2 = "/local-bucket/data/file.dat/", .expected_canonical_v4 = "/data/file.dat/", .expected_ret = 0, }, { .name = "Path style with key ends with slash", .endpoint = "http://localhost:9000", .path = "local-bucket", .key = "data/file.dat/", .url_style = S3EROFS_URL_STYLE_PATH, .expected_url = "http://localhost:9000/local-bucket/data/file.dat/", .expected_canonical_v2 = "/local-bucket/data/file.dat/", .expected_canonical_v4 = "/local-bucket/data/file.dat/", .expected_ret = 0, }, { .name = "Virtual-hosted without key", .endpoint = "s3.amazonaws.com", .path = "my-bucket", .key = NULL, .url_style = S3EROFS_URL_STYLE_VIRTUAL_HOST, .expected_url = "https://my-bucket.s3.amazonaws.com/", .expected_canonical_v2 = "/my-bucket/", .expected_canonical_v4 = "/", .expected_ret = 0, }, { .name = "Path style without key", .endpoint = "s3.amazonaws.com", .path = "my-bucket", .key = NULL, .url_style = S3EROFS_URL_STYLE_PATH, .expected_url = "https://s3.amazonaws.com/my-bucket", .expected_canonical_v2 = "/my-bucket", .expected_canonical_v4 = "/my-bucket", .expected_ret = 0, }, { .name = "Path style bucket ending with slash without key", .endpoint = "s3.amazonaws.com", .path = "bucket/", .key = NULL, .url_style = S3EROFS_URL_STYLE_PATH, .expected_url = "https://s3.amazonaws.com/bucket/", .expected_canonical_v2 = "/bucket/", .expected_canonical_v4 = "/bucket/", .expected_ret = 0, }, { .name = "Virtual-hosted bucket ending with slash without key", .endpoint = "s3.amazonaws.com", .path = "bucket/", .key = NULL, .url_style = S3EROFS_URL_STYLE_VIRTUAL_HOST, .expected_url = "https://bucket.s3.amazonaws.com/", .expected_canonical_v2 = "/bucket/", .expected_canonical_v4 = "/", .expected_ret = 0, }, { .name = "Path style bucket ending with slash", .endpoint = "s3.amazonaws.com", .path = "bucket/", .key = "object.txt", .url_style = S3EROFS_URL_STYLE_PATH, .expected_url = "https://s3.amazonaws.com/bucket/object.txt", .expected_canonical_v2 = "/bucket/object.txt", .expected_canonical_v4 = "/bucket/object.txt", .expected_ret = 0, }, { .name = "Virtual-hosted bucket ending with slash", .endpoint = "s3.amazonaws.com", .path = "bucket/", .key = "object.txt", .url_style = S3EROFS_URL_STYLE_VIRTUAL_HOST, .expected_url = "https://bucket.s3.amazonaws.com/object.txt", .expected_canonical_v2 = "/bucket/object.txt", .expected_canonical_v4 = "/object.txt", .expected_ret = 0, }, { .name = "Path style bucket ending with slash key with slash", .endpoint = "s3.amazonaws.com", .path = "bucket/", .key = "a/b/c/object.txt", .url_style = S3EROFS_URL_STYLE_PATH, .expected_url = "https://s3.amazonaws.com/bucket/a/b/c/object.txt", .expected_canonical_v2 = "/bucket/a/b/c/object.txt", .expected_canonical_v4 = "/bucket/a/b/c/object.txt", .expected_ret = 0, }, { .name = "Virtual-hosted bucket ending with slash key with slash", .endpoint = "s3.amazonaws.com", .path = "bucket/", .key = "a/b/c/object.txt", .url_style = S3EROFS_URL_STYLE_VIRTUAL_HOST, .expected_url = "https://bucket.s3.amazonaws.com/a/b/c/object.txt", .expected_canonical_v2 = "/bucket/a/b/c/object.txt", .expected_canonical_v4 = "/a/b/c/object.txt", .expected_ret = 0, }, { .name = "Error: NULL endpoint", .endpoint = NULL, .path = "my-bucket", .key = "file.txt", .url_style = S3EROFS_URL_STYLE_PATH, .expected_url = NULL, .expected_canonical_v2 = NULL, .expected_canonical_v4 = NULL, .expected_ret = -EINVAL, }, { .name = "Error: NULL bucket", .endpoint = "s3.amazonaws.com", .path = NULL, .key = "file.txt", .url_style = S3EROFS_URL_STYLE_PATH, .expected_url = NULL, .expected_canonical_v2 = NULL, .expected_canonical_v4 = NULL, .expected_ret = -EINVAL, }, { .name = "Key with special characters", .endpoint = "s3.amazonaws.com", .path = "bucket", .key = "path/to/file-name_v2.0.txt", .url_style = S3EROFS_URL_STYLE_VIRTUAL_HOST, .expected_url = "https://bucket.s3.amazonaws.com/path/to/file-name_v2.0.txt", .expected_canonical_v2 = "/bucket/path/to/file-name_v2.0.txt", .expected_canonical_v4 = "/path/to/file-name_v2.0.txt", .expected_ret = 0, }, { .name = "S3 Bucket domain name (1)", .endpoint = "bucket.s3.amazonaws.com", .path = "/", .key = "object.txt", .url_style = S3EROFS_URL_STYLE_VIRTUAL_HOST, .expected_url = "https://bucket.s3.amazonaws.com/object.txt", .expected_canonical_v2 = "/bucket/object.txt", .expected_canonical_v4 = "/object.txt", .expected_ret = 0, }, { .name = "S3 Bucket domain name (2)", .endpoint = "bucket.s3.amazonaws.com", .path = NULL, .key = "object.txt", .url_style = S3EROFS_URL_STYLE_VIRTUAL_HOST, .expected_url = "https://bucket.s3.amazonaws.com/object.txt", .expected_canonical_v2 = "/bucket/object.txt", .expected_canonical_v4 = "/object.txt", .expected_ret = 0, }, { .name = "Key with spaces", .endpoint = "s3.amazonaws.com", .path = "bucket", .key = "my folder/my file.txt", .url_style = S3EROFS_URL_STYLE_VIRTUAL_HOST, .expected_url = "https://bucket.s3.amazonaws.com/my%20folder/my%20file.txt", .expected_canonical_v2 = "/bucket/my%20folder/my%20file.txt", .expected_canonical_v4 = "/my%20folder/my%20file.txt", .expected_ret = 0, }, { .name = "Key with special characters (&, $, @, =)", .endpoint = "s3.amazonaws.com", .path = "bucket", .key = "file&name$test@sign=value.txt", .url_style = S3EROFS_URL_STYLE_PATH, .expected_url = "https://s3.amazonaws.com/bucket/file%26name%24test%40sign%3Dvalue.txt", .expected_canonical_v2 = "/bucket/file%26name%24test%40sign%3Dvalue.txt", .expected_canonical_v4 = "/bucket/file%26name%24test%40sign%3Dvalue.txt", .expected_ret = 0, }, { .name = "Key with semicolon, colon, and plus", .endpoint = "s3.amazonaws.com", .path = "bucket", .key = "file;name:test+data.txt", .url_style = S3EROFS_URL_STYLE_VIRTUAL_HOST, .expected_url = "https://bucket.s3.amazonaws.com/file%3Bname%3Atest%2Bdata.txt", .expected_canonical_v2 = "/bucket/file%3Bname%3Atest%2Bdata.txt", .expected_canonical_v4 = "/file%3Bname%3Atest%2Bdata.txt", .expected_ret = 0, }, { .name = "Key with comma and question mark", .endpoint = "s3.amazonaws.com", .path = "bucket", .key = "file,name?query.txt", .url_style = S3EROFS_URL_STYLE_PATH, .expected_url = "https://s3.amazonaws.com/bucket/file%2Cname%3Fquery.txt", .expected_canonical_v2 = "/bucket/file%2Cname%3Fquery.txt", .expected_canonical_v4 = "/bucket/file%2Cname%3Fquery.txt", .expected_ret = 0, }, { .name = "Key with multiple special characters", .endpoint = "s3.amazonaws.com", .path = "bucket", .key = "path/to/file name & data@2024.txt", .url_style = S3EROFS_URL_STYLE_VIRTUAL_HOST, .expected_url = "https://bucket.s3.amazonaws.com/path/to/file%20name%20%26%20data%402024.txt", .expected_canonical_v2 = "/bucket/path/to/file%20name%20%26%20data%402024.txt", .expected_canonical_v4 = "/path/to/file%20name%20%26%20data%402024.txt", .expected_ret = 0, } }; int i; int pass = 0; for (i = 0; i < ARRAY_SIZE(tests); ++i) { pass += run_s3erofs_prepare_url_test(&tests[i], S3EROFS_SIGNATURE_VERSION_2); putc('\n', stdout); pass += run_s3erofs_prepare_url_test(&tests[i], S3EROFS_SIGNATURE_VERSION_4); putc('\n', stdout); } printf("Run all %d tests with %d PASSED\n", 2 * i, pass); return 2 * ARRAY_SIZE(tests) == pass; } int main(int argc, char *argv[]) { exit(test_s3erofs_prepare_url() ? EXIT_SUCCESS : EXIT_FAILURE); } #endif erofs-utils-1.9.1/lib/rolling_hash.h000066400000000000000000000025441515160260000173670ustar00rootroot00000000000000/* 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.9.1/lib/sha256.c000066400000000000000000000203251515160260000157160ustar00rootroot00000000000000// SPDX-License-Identifier: Unlicense /* * sha256.c --- The sha256 algorithm * * (copied from LibTomCrypt with adaption.) */ #include "sha256.h" #include #ifdef __USE_OPENSSL_SHA256 void erofs_sha256_init(struct sha256_state *md) { int ret; md->ctx = EVP_MD_CTX_new(); if (!md->ctx) return; ret = EVP_DigestInit(md->ctx, EVP_sha256()); DBG_BUGON(ret != 1); } int erofs_sha256_process(struct sha256_state *md, const unsigned char *in, unsigned long inlen) { int ret; if (!md->ctx) return -1; ret = EVP_DigestUpdate(md->ctx, in, inlen); return (ret == 1) - 1; } int erofs_sha256_done(struct sha256_state *md, unsigned char *out) { int ret; unsigned int mdsize; if (!md->ctx) return -1; ret = EVP_DigestFinal_ex(md->ctx, out, &mdsize); if (ret != 1) return -1; EVP_MD_CTX_free(md->ctx); return 0; } #else /* 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; } #endif void erofs_sha256(const unsigned char *in, unsigned long in_size, unsigned char out[32]) { struct sha256_state md; erofs_sha256_init(&md); #ifdef __USE_OPENSSL_SHA256 EVP_MD_CTX_set_flags(md.ctx, EVP_MD_CTX_FLAG_ONESHOT); #endif 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.9.1/lib/sha256.h000066400000000000000000000012731515160260000157240ustar00rootroot00000000000000/* SPDX-License-Identifier: GPL-2.0+ OR Apache-2.0 */ #ifndef __EROFS_LIB_SHA256_H #define __EROFS_LIB_SHA256_H #include "erofs/defs.h" #if defined(HAVE_OPENSSL) && defined(HAVE_OPENSSL_EVP_H) #include struct sha256_state { EVP_MD_CTX *ctx; }; #define __USE_OPENSSL_SHA256 #else struct sha256_state { u64 length; u32 state[8], curlen; u8 buf[64]; }; #endif 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.9.1/lib/super.c000066400000000000000000000325011515160260000160430ustar00rootroot00000000000000// 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 "liberofs_cache.h" #include "liberofs_compress.h" #include "liberofs_metabox.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].uniaddr = le32_to_cpu(dis.uniaddr_lo); sbi->devs[i].blocks = le32_to_cpu(dis.blocks_lo); 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 read, ret; if (sbi->sb_valid) return 0; read = erofs_io_pread(&sbi->bdev, data, EROFS_MAX_BLOCK_SIZE, 0); if (read < EROFS_SUPER_OFFSET + sizeof(*dsb)) { ret = read < 0 ? read : -EIO; erofs_err("cannot read erofs superblock: %s", erofs_strerror(ret)); return ret; } 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 > read - EROFS_SUPER_OFFSET) { erofs_err("invalid sb_extslots %u", dsb->sb_extslots); return -EINVAL; } sbi->primarydevice_blocks = le32_to_cpu(dsb->blocks_lo); 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; if (erofs_sb_has_48bit(sbi) && dsb->rootnid_8b) { sbi->root_nid = le64_to_cpu(dsb->rootnid_8b); sbi->primarydevice_blocks = (sbi->primarydevice_blocks << 32) | le16_to_cpu(dsb->rb.blocks_hi); } else { sbi->root_nid = le16_to_cpu(dsb->rb.rootnid_2b); } sbi->packed_nid = le64_to_cpu(dsb->packed_nid); if (erofs_sb_has_metabox(sbi)) { if (sbi->sb_size <= offsetof(struct erofs_super_block, metabox_nid)) return -EFSCORRUPTED; sbi->metabox_nid = le64_to_cpu(dsb->metabox_nid); if (sbi->metabox_nid & BIT_ULL(EROFS_DIRENT_NID_METABOX_BIT)) return -EFSCORRUPTED; /* self-loop detection */ } else { sbi->metabox_nid = 0; } sbi->inos = le64_to_cpu(dsb->inos); sbi->checksum = le32_to_cpu(dsb->checksum); sbi->epoch = (s64)le64_to_cpu(dsb->epoch); sbi->fixed_nsec = le32_to_cpu(dsb->fixed_nsec); sbi->build_time = le32_to_cpu(dsb->build_time); memcpy(&sbi->uuid, dsb->uuid, sizeof(dsb->uuid)); if (erofs_sb_has_ishare_xattrs(sbi)) { if (dsb->ishare_xattr_prefix_id >= sbi->xattr_prefix_count) { erofs_err("invalid ishare xattr prefix id %d", dsb->ishare_xattr_prefix_id); return -EFSCORRUPTED; } sbi->ishare_xattr_prefix_id = dsb->ishare_xattr_prefix_id | EROFS_XATTR_LONG_PREFIX; } 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; } sbi->sb_valid = !ret; if (erofs_sb_has_48bit(sbi)) erofs_info("EXPERIMENTAL 48-bit layout support in use. Use at your own risk!"); if (erofs_sb_has_metabox(sbi)) { erofs_info("EXPERIMENTAL metadata compression support in use. Use at your own risk!"); erofs_info("No in-memory cache for metadata compression: userspace parser for metabox remains slow."); } return ret; } void erofs_put_super(struct erofs_sb_info *sbi) { if (sbi->devs) { int i; DBG_BUGON(!sbi->extra_devices); for (i = 0; i < sbi->extra_devices; ++i) free(sbi->devs[i].src_path); free(sbi->devs); sbi->devs = NULL; } if (sbi->bmgr) { erofs_buffer_exit(sbi->bmgr); sbi->bmgr = NULL; } z_erofs_compress_exit(sbi); erofs_xattr_exit(sbi); sbi->sb_valid = false; } int erofs_writesb(struct erofs_sb_info *sbi) { struct erofs_buffer_head *sb_bh = sbi->bh_sb; struct erofs_super_block sb = { .magic = cpu_to_le32(EROFS_SUPER_MAGIC_V1), .blkszbits = sbi->blkszbits, .rb.rootnid_2b = cpu_to_le16(sbi->root_nid), .inos = cpu_to_le64(sbi->inos), .epoch = cpu_to_le64(sbi->epoch), .build_time = cpu_to_le64(sbi->build_time), .fixed_nsec = cpu_to_le32(sbi->fixed_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), .ishare_xattr_prefix_id = sbi->ishare_xattr_prefix_id & EROFS_XATTR_LONG_PREFIX_MASK, }; char *buf; int ret; sb.blocks_lo = cpu_to_le32(sbi->primarydevice_blocks); if (sbi->primarydevice_blocks > UINT32_MAX || sbi->root_nid > UINT16_MAX) { sb.rb.blocks_hi = cpu_to_le16(sbi->primarydevice_blocks >> 32); sb.rootnid_8b = cpu_to_le64(sbi->root_nid); } 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); if (erofs_sb_has_metabox(sbi)) sb.metabox_nid = cpu_to_le64(sbi->metabox_nid); sb.sb_extslots = (sbi->sb_size - 128) >> 4; buf = calloc(round_up(EROFS_SUPER_OFFSET + sbi->sb_size, erofs_blksiz(sbi)), 1); if (!buf) { erofs_err("failed to allocate memory for sb: %s", erofs_strerror(-errno)); return -ENOMEM; } memcpy(buf + EROFS_SUPER_OFFSET, &sb, sbi->sb_size); ret = erofs_dev_write(sbi, buf, sb_bh ? erofs_btell(sb_bh, false) : 0, EROFS_SUPER_OFFSET + sbi->sb_size); 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_sb_info *sbi = bmgr->sbi; struct erofs_buffer_head *bh; unsigned int sb_size = 128; int err; if (erofs_sb_has_metabox(sbi) && sb_size <= offsetof(struct erofs_super_block, metabox_nid)) sb_size = offsetof(struct erofs_super_block, metabox_nid) + 8; sbi->sb_size = round_up(sb_size, 16); 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_OFFSET + sbi->sb_size); 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; /* * skip the first 1024 bytes, to allow for the installation * of x86 boot sectors and other oddities. */ if (erofs_blksiz(sbi) > EROFS_SUPER_OFFSET) len = erofs_blksiz(sbi) - EROFS_SUPER_OFFSET; else len = erofs_blksiz(sbi); ret = erofs_dev_read(sbi, 0, buf, EROFS_SUPER_OFFSET, len); if (ret) { erofs_err("failed to read superblock to set checksum: %s", erofs_strerror(ret)); return ret; } sb = (struct erofs_super_block *)buf; 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); *crc = erofs_crc32c(~0, (u8 *)sb, len); /* set up checksum field to erofs_super_block */ sb->checksum = cpu_to_le32(*crc); ret = erofs_dev_write(sbi, buf, EROFS_SUPER_OFFSET, len); if (ret) { erofs_err("failed to write checksummed superblock: %s", erofs_strerror(ret)); return ret; } return 0; } int erofs_superblock_csum_verify(struct erofs_sb_info *sbi) { u32 len = erofs_blksiz(sbi), crc; u8 buf[EROFS_MAX_BLOCK_SIZE]; struct erofs_super_block *sb; int ret; if (len > EROFS_SUPER_OFFSET) len -= EROFS_SUPER_OFFSET; ret = erofs_dev_read(sbi, 0, buf, EROFS_SUPER_OFFSET, len); if (ret) { erofs_err("failed to read superblock to calculate sbcsum: %d", ret); return -1; } sb = (struct erofs_super_block *)buf; sb->checksum = 0; crc = erofs_crc32c(~0, (u8 *)sb, len); if (crc == sbi->checksum) return 0; erofs_err("invalid checksum 0x%08x, 0x%08x expected", sbi->checksum, crc); return -EBADMSG; } int erofs_mkfs_init_devices(struct erofs_sb_info *sbi, unsigned int devices) { struct erofs_buffer_head *bh; if (!devices) return 0; sbi->devs = calloc(devices, sizeof(sbi->devs[0])); if (!sbi->devs) return -ENOMEM; bh = erofs_balloc(sbi->bmgr, DEVT, sizeof(struct erofs_deviceslot) * devices, 0); if (IS_ERR(bh)) { free(sbi->devs); sbi->devs = NULL; return PTR_ERR(bh); } erofs_mapbh(NULL, bh->block); bh->op = &erofs_skip_write_bhops; sbi->bh_devt = bh; sbi->devt_slotoff = erofs_btell(bh, false) / EROFS_DEVT_SLOT_SIZE; sbi->extra_devices = devices; erofs_sb_set_device_table(sbi); return 0; } int erofs_write_device_table(struct erofs_sb_info *sbi) { erofs_blk_t nblocks = sbi->primarydevice_blocks; struct erofs_buffer_head *bh = sbi->bh_devt; erofs_off_t pos; unsigned int i, ret; if (!sbi->extra_devices) goto out; if (!bh) { if (erofs_sb_has_device_table(sbi)) return 0; return -EINVAL; } pos = erofs_btell(bh, false); if (pos == EROFS_NULL_ADDR) { DBG_BUGON(1); return -EINVAL; } i = 0; do { struct erofs_deviceslot dis = { .uniaddr_lo = cpu_to_le32(nblocks), .blocks_lo = cpu_to_le32(sbi->devs[i].blocks), }; memcpy(dis.tag, sbi->devs[i].tag, sizeof(dis.tag)); ret = erofs_dev_write(sbi, &dis, pos, sizeof(dis)); if (ret) return ret; pos += sizeof(dis); nblocks += sbi->devs[i].blocks; } while (++i < sbi->extra_devices); bh->op = &erofs_drop_directly_bhops; erofs_bdrop(bh, false); sbi->bh_devt = NULL; out: sbi->total_blocks = nblocks; return 0; } int erofs_mkfs_format_fs(struct erofs_sb_info *sbi, unsigned int blkszbits, unsigned int dsunit, bool metazone) { struct erofs_buffer_head *bh; struct erofs_bufmgr *bmgr; sbi->blkszbits = blkszbits; bmgr = erofs_buffer_init(sbi, 0, NULL); if (!bmgr) return -ENOMEM; sbi->bmgr = bmgr; bmgr->dsunit = dsunit; if (metazone) sbi->metazone_startblk = EROFS_META_NEW_ADDR; else sbi->metazone_startblk = 0; bh = erofs_reserve_sb(bmgr); if (IS_ERR(bh)) return PTR_ERR(bh); sbi->bh_sb = bh; return 0; } int erofs_mkfs_load_fs(struct erofs_sb_info *sbi, unsigned int dsunit) { union { struct stat st; erofs_blk_t startblk; } u; struct erofs_bufmgr *bmgr; int err; sbi->bh_sb = NULL; erofs_warn("EXPERIMENTAL incremental build in use. Use at your own risk!"); err = erofs_read_superblock(sbi); if (err) { erofs_err("failed to read superblock of %s: %s", sbi->devname, erofs_strerror(err)); return err; } err = erofs_io_fstat(&sbi->bdev, &u.st); if (!err && S_ISREG(u.st.st_mode)) u.startblk = DIV_ROUND_UP(u.st.st_size, erofs_blksiz(sbi)); else u.startblk = sbi->primarydevice_blocks; bmgr = erofs_buffer_init(sbi, u.startblk, NULL); if (!bmgr) return -ENOMEM; sbi->bmgr = bmgr; bmgr->dsunit = dsunit; return 0; } erofs-utils-1.9.1/lib/tar.c000066400000000000000000000635331515160260000155040ustar00rootroot00000000000000// SPDX-License-Identifier: GPL-2.0+ OR Apache-2.0 #include #include #include #include #include "erofs/print.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/importer.h" #if defined(HAVE_ZLIB) #include #endif #include "liberofs_base64.h" #include "liberofs_cache.h" #include "liberofs_gzran.h" #include "liberofs_rebuild.h" /* 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; } else if (ios->decoder == EROFS_IOS_DECODER_GZRAN) { erofs_gzran_builder_final(ios->gb); return; } erofs_io_close(&ios->vf); } 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 if (decoder == EROFS_IOS_DECODER_GZRAN) { ios->vf.fd = fd; ios->feof = false; ios->sz = 0; ios->bufsize = EROFS_GZRAN_WINSIZE * 2; ios->gb = erofs_gzran_builder_init(&ios->vf, 4194304); if (IS_ERR(ios->gb)) return PTR_ERR(ios->gb); } 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 if (ios->decoder == EROFS_IOS_DECODER_GZRAN) { ret = erofs_gzran_builder_read(ios->gb, ios->buffer + rabytes); if (ret < 0) return ret; ios->tail += ret; DBG_BUGON(ios->tail > ios->bufsize); if (!ret) ios->feof = true; } 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 = strtoll(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) { errno = 0; /* * 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' || *(char *)ptr == '\377') { 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_vfs_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 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 = erofs_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, false); 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) { if (!ret) ret = -EIO; 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; if (ret < 0) return ret; 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; j = inode->i_size; DBG_BUGON(!j); do { nread = erofs_iostream_read(&tar->ios, &buf, j); if (nread <= 0) { if (!nread) nread = -EIO; break; } if (pwrite(fd, buf, nread, off) != nread) { nread = -EIO; break; } j -= nread; off += nread; } while (j); erofs_diskbuf_commit(inode->i_diskbuf, inode->i_size); inode->datasource = EROFS_INODE_DATA_SOURCE_DISKBUF; return nread < 0 ? nread : 0; } int tarerofs_parse_tar(struct erofs_importer *im, struct erofs_tarfile *tar) { struct erofs_pax_header eh = tar->global; struct erofs_importer_params *params = im->params; struct erofs_sb_info *sbi = im->sbi; struct erofs_inode *root = im->root; bool whout, opq, e = false; char _path[PATH_MAX + 1], *path = _path + 1; 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 csum, cksum; int ckksum, ret, rem, j; 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->ios.feof) { erofs_warn("unexpected end of file @ %llu (may be non-standard tar without end of archive zeros)", tar_offset); ret = 1; goto out; } 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 { *_path = '\0'; 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; } 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 (d == d2) { ret = 0; goto out; } if (S_ISDIR(d2->inode->i_mode)) { ret = -EISDIR; goto out; } inode = erofs_igrab(d2->inode); ++inode->i_nlink; if (d->type != EROFS_FT_UNKNOWN) { tarerofs_remove_inode(d->inode); erofs_iput(d->inode); } d->inode = inode; d->type = d2->type; 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(im, 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 && !sbi->available_compr_algs && params->no_datainline) { 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) { inode->i_nlink = 2; } 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.9.1/lib/uuid.c000066400000000000000000000050171515160260000156550ustar00rootroot00000000000000// 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; #if defined(HAVE_SYS_RANDOM_H) && defined(HAVE_GETRANDOM) r = getrandom(out, size, flags); #elif defined(__linux__) && 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.9.1/lib/uuid_unparse.c000066400000000000000000000010331515160260000174040ustar00rootroot00000000000000// 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.9.1/lib/vmdk.c000066400000000000000000000040171515160260000156470ustar00rootroot00000000000000// SPDX-License-Identifier: GPL-2.0+ OR Apache-2.0 #include "erofs/internal.h" static int erofs_vmdk_desc_add_extent(FILE *f, u64 sectors, const char *filename, u64 offset) { static const char extent_line_fmt[] = "RW %" PRIu64 " FLAT \"%s\" %" PRIu64 "\n"; while (sectors) { u64 count = min_t(u64, sectors, 0x80000000 >> 9); int ret; ret = fprintf(f, extent_line_fmt, count, filename, offset); if (ret < 0) return -errno; offset += count; sectors -= count; } return 0; } int erofs_dump_vmdk_desc(FILE *f, struct erofs_sb_info *sbi) { static const char desc_template_1[] = "# Disk DescriptorFile\n" "version=1\n" "CID=%" PRIx32 "\n" "parentCID=%" PRIx32 "\n" "createType=\"%s\"\n" "\n" "# Extent description\n"; static const char desc_template_2[] = "\n" "# The Disk Data Base\n" "#DDB\n" "\n" "ddb.virtualHWVersion = \"%s\"\n" "ddb.geometry.cylinders = \"%" PRIu64 "\"\n" "ddb.geometry.heads = \"%" PRIu32 "\"\n" "ddb.geometry.sectors = \"63\"\n" "ddb.adapterType = \"%s\"\n"; static const char subformat[] = "twoGbMaxExtentFlat"; static const char adapter_type[] = "ide"; u32 cid = ((u32 *)sbi->uuid)[0] ^ ((u32 *)sbi->uuid)[1] ^ ((u32 *)sbi->uuid)[2] ^ ((u32 *)sbi->uuid)[3]; u32 parent_cid = 0xffffffff; u32 number_heads = 16; char *hw_version = "4"; u64 total_sectors, sectors; int ret, i; fprintf(f, desc_template_1, cid, parent_cid, subformat); sectors = sbi->primarydevice_blocks << (sbi->blkszbits - 9); ret = erofs_vmdk_desc_add_extent(f, sectors, (char *)sbi->devname, 0); if (ret) return ret; total_sectors = sectors; for (i = 0; i < sbi->extra_devices; ++i) { const char *name = sbi->devs[i].src_path ?: (const char *)sbi->devs[i].tag; sectors = (u64)sbi->devs[i].blocks << (sbi->blkszbits - 9); ret = erofs_vmdk_desc_add_extent(f, sectors, name, 0); if (ret) return ret; total_sectors += sectors; } fprintf(f, desc_template_2, hw_version, (u64)DIV_ROUND_UP(total_sectors, 63ULL * number_heads), number_heads, adapter_type); return 0; } erofs-utils-1.9.1/lib/workqueue.c000066400000000000000000000050551515160260000167400ustar00rootroot00000000000000// 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.9.1/lib/xattr.c000066400000000000000000001145051515160260000160540ustar00rootroot00000000000000// SPDX-License-Identifier: GPL-2.0+ OR Apache-2.0 /* * Copyright (C) 2019 Li Guifu * Gao Xiang * Copyright (C) 2025 Alibaba Cloud */ #define _GNU_SOURCE #include #include #include #include "erofs/print.h" #include "erofs/list.h" #include "erofs/xattr.h" #include "erofs/importer.h" #include "liberofs_cache.h" #include "liberofs_fragments.h" #include "liberofs_metabox.h" #include "liberofs_xxhash.h" #include "liberofs_private.h" #ifdef HAVE_SYS_XATTR_H #include #endif #ifdef HAVE_LINUX_XATTR_H #include #endif #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 static ssize_t erofs_sys_llistxattr(const char *path, char *list, size_t size) { #ifdef HAVE_LLISTXATTR return llistxattr(path, list, size); #elif defined(__APPLE__) return listxattr(path, list, size, XATTR_NOFOLLOW); #endif return 0; } static ssize_t erofs_sys_lgetxattr(const char *path, const char *name, void *value, size_t size) { int ret = -ENODATA; #ifdef HAVE_LGETXATTR ret = lgetxattr(path, name, value, size); if (ret < 0) ret = -errno; #elif defined(__APPLE__) ret = getxattr(path, name, value, size, 0, XATTR_NOFOLLOW); if (ret < 0) { ret = -errno; if (ret == -ENOATTR) ret = -ENODATA; } #endif return ret; } ssize_t erofs_sys_lsetxattr(const char *path, const char *name, void *value, size_t size) { int ret; #ifdef HAVE_LSETXATTR ret = lsetxattr(path, name, value, size, 0); #elif defined(__APPLE__) ret = setxattr(path, name, value, size, 0, XATTR_NOFOLLOW); #else ret = -1; errno = ENODATA; #endif if (ret < 0) return errno; return ret; } /* 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 erofs_xattritem { struct list_head node; struct erofs_xattritem *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 erofs_inode_xattr_node { struct list_head list; struct erofs_xattritem *item; }; struct erofs_xattrmgr { struct list_head hash[1 << 14]; struct erofs_xattritem *shared_xattrs; }; int erofs_xattr_init(struct erofs_sb_info *sbi) { struct erofs_xattrmgr *xamgr = sbi->xamgr; unsigned int i; if (xamgr) return 0; xamgr = malloc(sizeof(struct erofs_xattrmgr)); if (!xamgr) return -ENOMEM; for (i = 0; i < ARRAY_SIZE(xamgr->hash); ++i) init_list_head(&xamgr->hash[i]); sbi->xamgr = xamgr; return 0; } static struct erofs_xattr_prefix { const char *prefix; unsigned int prefix_len; } xattr_types[] = { [0] = {""}, [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 erofs_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 erofs_xattr_prefix *p; *index = 0; *len = 0; for (p = xattr_types + 1; 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 erofs_xattritem *item) { if (item->count > 1) return --item->count; list_del(&item->node); free((void *)item->kvbuf); free(item); return 0; } static struct erofs_xattritem *get_xattritem(struct erofs_sb_info *sbi, char *kvbuf, unsigned int len[2]) { struct erofs_xattrmgr *xamgr = sbi->xamgr; struct erofs_xattritem *item; struct ea_type_node *tnode; struct list_head *head; 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]) & (ARRAY_SIZE(xamgr->hash) - 1); head = xamgr->hash + hkey; list_for_each_entry(item, head, node) { 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); (void)erofs_xattr_prefix_matches(kvbuf, &item->base_index, &item->prefix_len); DBG_BUGON(len[0] < item->prefix_len); init_list_head(&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; } } list_add(&item->node, head); return item; } static struct erofs_xattritem *parse_one_xattr(struct erofs_sb_info *sbi, const char *path, const char *key, unsigned int keylen) { ssize_t ret; struct erofs_xattritem *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 */ ret = erofs_sys_lgetxattr(path, key, NULL, 0); if (ret < 0) return ERR_PTR(ret); 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 */ ret = erofs_sys_lgetxattr(path, key, kvbuf + EROFS_XATTR_KSIZE(len), len[1]); if (ret < 0) 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(sbi, kvbuf, len); if (!IS_ERR(item)) return item; ret = PTR_ERR(item); out: free(kvbuf); return ERR_PTR(ret); } static struct erofs_xattritem *erofs_get_selabel_xattr(struct erofs_sb_info *sbi, const char *srcpath, mode_t mode) { #ifdef HAVE_LIBSELINUX if (cfg.sehnd) { char *secontext; int ret; unsigned int len[2]; char *kvbuf, *fspath; struct erofs_xattritem *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(sbi, kvbuf, len); if (IS_ERR(item)) free(kvbuf); return item; } #endif return NULL; } static int erofs_inode_xattr_add(struct list_head *hlist, struct erofs_xattritem *item) { struct erofs_inode_xattr_node *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 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(struct erofs_sb_info *sbi, const char *path, mode_t mode, struct list_head *ixattrs) { ssize_t kllen = erofs_sys_llistxattr(path, NULL, 0); char *keylst, *key, *klend; unsigned int keylen; struct erofs_xattritem *item; int ret; if (kllen < 0 && errno != ENODATA && errno != ENOTSUP) { erofs_err("failed to get the size of the xattr list for %s: %s", path, strerror(errno)); 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.*/ kllen = erofs_sys_llistxattr(path, keylst, kllen); if (kllen < 0) { erofs_err("llistxattr to get names for %s failed", path); ret = -errno; goto err; } /* * 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(sbi, path, key, keylen); /* skip inaccessible xattrs */ if (item == ERR_PTR(-ENODATA) || !item) { erofs_warn("skipped inaccessible xattr %s in %s", key, path); continue; } if (IS_ERR(item)) { ret = PTR_ERR(item); goto err; } if (ixattrs) { ret = erofs_inode_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(sbi, path, mode); if (IS_ERR(item)) return PTR_ERR(item); if (item && ixattrs) ret = erofs_inode_xattr_add(ixattrs, item); return ret; err: free(keylst); return ret; } int erofs_setxattr(struct erofs_inode *inode, int index, const char *name, const void *value, size_t size) { struct erofs_sb_info *sbi = inode->sbi; struct erofs_xattritem *item; const struct erofs_xattr_prefix *prefix = NULL; struct ea_type_node *tnode; unsigned int len[2]; char *kvbuf; if (index & EROFS_XATTR_LONG_PREFIX) { list_for_each_entry(tnode, &ea_name_prefixes, list) { if (index == tnode->index) { prefix = &tnode->type; break; } } } else if (index < ARRAY_SIZE(xattr_types)) { prefix = &xattr_types[index]; } if (!prefix) return -EINVAL; len[0] = prefix->prefix_len + strlen(name); len[1] = size; kvbuf = malloc(EROFS_XATTR_KVSIZE(len)); if (!kvbuf) return -ENOMEM; memcpy(kvbuf, prefix->prefix, prefix->prefix_len); memcpy(kvbuf + prefix->prefix_len, name, EROFS_XATTR_KSIZE(len) - prefix->prefix_len); memcpy(kvbuf + EROFS_XATTR_KSIZE(len), value, size); item = get_xattritem(sbi, kvbuf, len); if (IS_ERR(item)) { free(kvbuf); return PTR_ERR(item); } DBG_BUGON(!item); return erofs_inode_xattr_add(&inode->i_xattrs, item); } int erofs_vfs_setxattr(struct erofs_inode *inode, const char *name, const void *value, size_t size) { return erofs_setxattr(inode, 0, name, value, size); } static void erofs_removexattr(struct erofs_inode *inode, const char *key) { struct erofs_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_vfs_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_vfs_setxattr(inode, OVL_XATTR_ORIGIN, NULL, 0); } #ifdef WITH_ANDROID static int erofs_droid_xattr_set_caps(struct erofs_inode *inode) { struct erofs_sb_info *sbi = inode->sbi; const u64 capabilities = inode->capabilities; char *kvbuf; unsigned int len[2]; struct vfs_cap_data caps; struct erofs_xattritem *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(sbi, kvbuf, len); if (IS_ERR(item)) { free(kvbuf); return PTR_ERR(item); } DBG_BUGON(!item); return erofs_inode_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; ret = read_xattrs_from_file(inode->sbi, 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_vfs_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 erofs_xattritem *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 erofs_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 erofs_xattritem *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 0; } static int erofs_count_all_xattrs_from_path(struct erofs_sb_info *sbi, 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)) 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(sbi, buf, st.st_mode, NULL); if (ret) goto fail; if (!S_ISDIR(st.st_mode)) continue; ret = erofs_count_all_xattrs_from_path(sbi, buf); if (ret) goto fail; } if (errno) ret = -errno; fail: closedir(_dir); return ret; } static unsigned int erofs_cleanxattrs(struct erofs_xattrmgr *xamgr, unsigned int inlinexattr_tolerance) { struct erofs_xattritem *item, *n; unsigned int i, count; count = 0; for (i = 0; i < ARRAY_SIZE(xamgr->hash); ++i) { list_for_each_entry_safe(item, n, xamgr->hash + i, node) { if (item->count > inlinexattr_tolerance) { item->next_shared_xattr = xamgr->shared_xattrs; xamgr->shared_xattrs = item; ++count; continue; } list_del(&item->node); free((void *)item->kvbuf); free(item); } } return count; } static int comp_shared_xattritem(const void *a, const void *b) { const struct erofs_xattritem *ia, *ib; unsigned int la, lb; int ret; ia = *((const struct erofs_xattritem **)a); ib = *((const struct erofs_xattritem **)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_importer *im, bool plain) { const struct erofs_importer_params *params = im->params; struct erofs_sb_info *sbi = im->sbi; bool may_fragments = params->fragments || erofs_sb_has_fragments(sbi); struct erofs_vfile *vf = &sbi->bdev; struct erofs_bufmgr *bmgr = sbi->bmgr; struct erofs_buffer_head *bh = NULL; struct erofs_vfile _vf; struct ea_type_node *tnode; s64 start, offset = 0; int err; if (!ea_prefix_count) return 0; if (!plain) { if (erofs_sb_has_metabox(sbi)) { bmgr = erofs_metadata_bmgr(sbi, true); vf = bmgr->vf; } else if (may_fragments) { erofs_sb_set_fragments(sbi); _vf = (struct erofs_vfile){ .fd = erofs_packedfile(sbi) }; vf = &_vf; offset = lseek(vf->fd, 0, SEEK_CUR); if (offset < 0) return -errno; bmgr = NULL; } else { plain = true; } } if (plain) erofs_sb_set_plain_xattr_pfx(sbi); if (bmgr) { bh = erofs_balloc(bmgr, XATTR, 0, 0); if (IS_ERR(bh)) return PTR_ERR(bh); (void)erofs_mapbh(bmgr, bh->block); offset = erofs_btell(bh, false); } if ((offset >> 2) > UINT32_MAX) return -EOVERFLOW; start = offset; 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_pwrite(vf, &u.s, offset, sizeof(__le16) + len); if (err != sizeof(__le16) + len) { if (err < 0) return err; return -EIO; } offset = round_up(offset + sizeof(__le16) + len, 4); } if (bh) { bh->op = &erofs_drop_directly_bhops; err = erofs_bh_balloon(bh, offset - start); if (err < 0) return err; bh->op = &erofs_drop_directly_bhops; erofs_bdrop(bh, false); } else { DBG_BUGON(bmgr); if (lseek(vf->fd, offset, SEEK_CUR) < 0) return -errno; } erofs_sb_set_xattr_prefixes(sbi); return 0; } static void erofs_write_xattr_entry(char *buf, struct erofs_xattritem *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_load_shared_xattrs_from_path(struct erofs_sb_info *sbi, const char *path, long inlinexattr_tolerance) { struct erofs_xattrmgr *xamgr = sbi->xamgr; struct erofs_xattritem *item, *n, **sorted_n; unsigned int sharedxattr_count, p, i; struct erofs_buffer_head *bh; char *buf; int ret; erofs_off_t off; erofs_off_t shared_xattrs_size = 0; /* check if xattr or shared xattr is disabled */ if (inlinexattr_tolerance < 0 || inlinexattr_tolerance >= INT_MAX) return 0; ret = erofs_count_all_xattrs_from_path(sbi, path); if (ret) return ret; sharedxattr_count = erofs_cleanxattrs(xamgr, inlinexattr_tolerance); if (!sharedxattr_count) return 0; sorted_n = malloc((sharedxattr_count + 1) * sizeof(n)); if (!sorted_n) return -ENOMEM; i = 0; while (xamgr->shared_xattrs) { item = xamgr->shared_xattrs; sorted_n[i++] = item; xamgr->shared_xattrs = item->next_shared_xattr; shared_xattrs_size = erofs_next_xattr_align(shared_xattrs_size, item); } DBG_BUGON(i != sharedxattr_count); sorted_n[i] = NULL; qsort(sorted_n, sharedxattr_count, sizeof(n), comp_shared_xattritem); 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 < sharedxattr_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); } xamgr->shared_xattrs = 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); 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 erofs_inode_xattr_node *node, *n; struct erofs_xattritem *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 erofs_xattr_iter { struct erofs_sb_info *sbi; struct erofs_buf buf; erofs_off_t pos; void *kaddr; char *buffer; int buffer_size, buffer_ofs; /* getxattr */ int index, infix_len; const char *name; size_t len; }; static int erofs_init_inode_xattrs(struct erofs_inode *vi) { struct erofs_sb_info *sbi = vi->sbi; struct erofs_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 -ENODATA; } it.buf = __EROFS_BUF_INITIALIZER; erofs_init_metabuf(&it.buf, sbi, erofs_inode_in_metabox(vi)); it.pos = erofs_iloc(vi) + vi->inode_isize; /* read in shared xattr array (non-atomic, see kmalloc below) */ it.kaddr = erofs_bread(&it.buf, it.pos, true); if (IS_ERR(it.kaddr)) return PTR_ERR(it.kaddr); ih = it.kaddr; vi->xattr_shared_count = ih->h_shared_count; vi->xattr_shared_xattrs = malloc(vi->xattr_shared_count * sizeof(uint)); if (!vi->xattr_shared_xattrs) { erofs_put_metabuf(&it.buf); return -ENOMEM; } /* let's skip ibody header */ it.pos += sizeof(struct erofs_xattr_ibody_header); for (i = 0; i < vi->xattr_shared_count; ++i) { it.kaddr = erofs_bread(&it.buf, it.pos, true); if (IS_ERR(it.kaddr)) { free(vi->xattr_shared_xattrs); vi->xattr_shared_xattrs = NULL; return PTR_ERR(it.kaddr); } vi->xattr_shared_xattrs[i] = le32_to_cpu(*(__le32 *)it.kaddr); it.pos += sizeof(__le32); } erofs_put_metabuf(&it.buf); erofs_atomic_set_bit(EROFS_I_EA_INITED_BIT, &vi->flags); return ret; } static int erofs_xattr_copy_to_buffer(struct erofs_xattr_iter *it, unsigned int len) { unsigned int slice, processed; struct erofs_sb_info *sbi = it->sbi; void *src; for (processed = 0; processed < len; processed += slice) { it->kaddr = erofs_bread(&it->buf, it->pos, true); if (IS_ERR(it->kaddr)) return PTR_ERR(it->kaddr); src = it->kaddr; slice = min_t(unsigned int, erofs_blksiz(sbi) - erofs_blkoff(sbi, it->pos), len - processed); memcpy(it->buffer + it->buffer_ofs, src, slice); it->buffer_ofs += slice; it->pos += slice; } return 0; } static int erofs_listxattr_foreach(struct erofs_xattr_iter *it) { struct erofs_xattr_entry entry; unsigned int base_index, name_total, prefix_len, infix_len = 0; const char *prefix, *infix = NULL; int err; /* 1. handle xattr entry */ entry = *(struct erofs_xattr_entry *)it->kaddr; it->pos += sizeof(struct erofs_xattr_entry); base_index = entry.e_name_index; 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 0; 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 0; prefix = xattr_types[base_index].prefix; prefix_len = xattr_types[base_index].prefix_len; name_total = prefix_len + infix_len + entry.e_name_len + 1; if (!it->buffer) { it->buffer_ofs += name_total; return 0; } if (it->buffer_ofs + name_total > 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; /* 2. handle xattr name */ err = erofs_xattr_copy_to_buffer(it, entry.e_name_len); if (err) return err; it->buffer[it->buffer_ofs++] = '\0'; return 0; } static int erofs_getxattr_foreach(struct erofs_xattr_iter *it) { struct erofs_sb_info *sbi = it->sbi; struct erofs_xattr_entry entry; unsigned int slice, processed, value_sz; /* 1. handle xattr entry */ entry = *(struct erofs_xattr_entry *)it->kaddr; it->pos += sizeof(struct erofs_xattr_entry); value_sz = le16_to_cpu(entry.e_value_size); /* should also match the infix for long name prefixes */ if (entry.e_name_index & EROFS_XATTR_LONG_PREFIX) { 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 -ENODATA; if (it->index != pf->prefix->base_index || it->len != entry.e_name_len + pf->infix_len) return -ENODATA; if (memcmp(it->name, pf->prefix->infix, pf->infix_len)) return -ENODATA; it->infix_len = pf->infix_len; } else { if (it->index != entry.e_name_index || it->len != entry.e_name_len) return -ENODATA; it->infix_len = 0; } /* 2. handle xattr name */ for (processed = 0; processed < entry.e_name_len; processed += slice) { it->kaddr = erofs_bread(&it->buf, it->pos, true); if (IS_ERR(it->kaddr)) return PTR_ERR(it->kaddr); slice = min_t(unsigned int, erofs_blksiz(sbi) - erofs_blkoff(sbi, it->pos), entry.e_name_len - processed); if (memcmp(it->name + it->infix_len + processed, it->kaddr, slice)) return -ENODATA; it->pos += slice; } /* 3. handle xattr value */ if (!it->buffer) { it->buffer_ofs = value_sz; return 0; } if (it->buffer_size < value_sz) return -ERANGE; return erofs_xattr_copy_to_buffer(it, value_sz); } static int erofs_xattr_iter_inline(struct erofs_xattr_iter *it, struct erofs_inode *vi, bool getxattr) { unsigned int xattr_header_sz, remaining, entry_sz; erofs_off_t next_pos; int ret; xattr_header_sz = sizeof(struct erofs_xattr_ibody_header) + sizeof(u32) * vi->xattr_shared_count; if (xattr_header_sz >= vi->xattr_isize) { DBG_BUGON(xattr_header_sz > vi->xattr_isize); return -ENODATA; } erofs_init_metabuf(&it->buf, it->sbi, erofs_inode_in_metabox(vi)); remaining = vi->xattr_isize - xattr_header_sz; it->pos = erofs_iloc(vi) + vi->inode_isize + xattr_header_sz; do { it->kaddr = erofs_bread(&it->buf, it->pos, true); if (IS_ERR(it->kaddr)) return PTR_ERR(it->kaddr); entry_sz = erofs_xattr_entry_size(it->kaddr); /* xattr on-disk corruption: xattr entry beyond xattr_isize */ if (remaining < entry_sz) { DBG_BUGON(1); return -EFSCORRUPTED; } remaining -= entry_sz; next_pos = it->pos + entry_sz; if (getxattr) ret = erofs_getxattr_foreach(it); else ret = erofs_listxattr_foreach(it); if ((getxattr && ret != -ENODATA) || (!getxattr && ret)) break; it->pos = next_pos; } while (remaining); return ret; } static int erofs_xattr_iter_shared(struct erofs_xattr_iter *it, struct erofs_inode *vi, bool getxattr) { struct erofs_sb_info *sbi = vi->sbi; unsigned int i; int ret = -ENODATA; erofs_init_metabuf(&it->buf, sbi, erofs_sb_has_shared_ea_in_metabox(sbi)); for (i = 0; i < vi->xattr_shared_count; ++i) { it->pos = erofs_pos(sbi, sbi->xattr_blkaddr) + vi->xattr_shared_xattrs[i] * sizeof(__le32); it->kaddr = erofs_bread(&it->buf, it->pos, true); if (IS_ERR(it->kaddr)) return PTR_ERR(it->kaddr); if (getxattr) ret = erofs_getxattr_foreach(it); else ret = erofs_listxattr_foreach(it); if ((getxattr && ret != -ENODATA) || (!getxattr && ret)) break; } return ret; } int erofs_getxattr(struct erofs_inode *vi, const char *name, char *buffer, size_t buffer_size) { int ret; unsigned int prefix, prefixlen; struct erofs_xattr_iter it; if (!name) return -EINVAL; ret = erofs_init_inode_xattrs(vi); if (ret) return ret; if (!erofs_xattr_prefix_matches(name, &prefix, &prefixlen)) return -ENODATA; it.index = prefix; it.name = name + prefixlen; it.len = strlen(it.name); if (it.len > EROFS_NAME_LEN) return -ERANGE; it.sbi = vi->sbi; it.buf = __EROFS_BUF_INITIALIZER; it.buffer = buffer; it.buffer_size = buffer_size; it.buffer_ofs = 0; ret = erofs_xattr_iter_inline(&it, vi, true); if (ret == -ENODATA) ret = erofs_xattr_iter_shared(&it, vi, true); erofs_put_metabuf(&it.buf); return ret ? ret : it.buffer_ofs; } int erofs_listxattr(struct erofs_inode *vi, char *buffer, size_t buffer_size) { int ret; struct erofs_xattr_iter it; ret = erofs_init_inode_xattrs(vi); if (ret == -ENODATA) return 0; if (ret) return ret; it.sbi = vi->sbi; it.buf = __EROFS_BUF_INITIALIZER; it.buffer = buffer; it.buffer_size = buffer_size; it.buffer_ofs = 0; ret = erofs_xattr_iter_inline(&it, vi, false); if (!ret || ret == -ENODATA) ret = erofs_xattr_iter_shared(&it, vi, false); if (ret == -ENODATA) ret = 0; erofs_put_metabuf(&it.buf); return ret ? ret : it.buffer_ofs; } 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)) { /* Use internal hidden xattrs */ tnode->base_index = 0; tnode->base_len = 0; } 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; init_list_head(&tnode->list); list_add_tail(&tnode->list, &ea_name_prefixes); return ea_prefix_count++; } int erofs_xattr_set_ishare_prefix(struct erofs_sb_info *sbi, const char *prefix) { int err; err = erofs_xattr_insert_name_prefix(prefix); if (err < 0) return err; sbi->ishare_xattr_prefix_id = EROFS_XATTR_LONG_PREFIX | err; erofs_sb_set_ishare_xattrs(sbi); 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) { bool plain = erofs_sb_has_plain_xattr_pfx(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 (!plain) { if (sbi->metabox_nid) nid = sbi->metabox_nid; else 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; } void erofs_xattr_exit(struct erofs_sb_info *sbi) { struct erofs_xattrmgr *xamgr = sbi->xamgr; erofs_xattr_prefixes_cleanup(sbi); if (!xamgr) return; (void)erofs_cleanxattrs(xamgr, UINT_MAX); sbi->xamgr = NULL; free(xamgr); } erofs-utils-1.9.1/lib/xxhash.c000066400000000000000000000141531515160260000162130ustar00rootroot00000000000000// 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.9.1/lib/zmap.c000066400000000000000000000523761515160260000156700ustar00rootroot00000000000000// 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; 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_START(erofs_iloc(vi) + vi->inode_isize + vi->xattr_isize) + lcn * sizeof(struct z_erofs_lcluster_index); struct z_erofs_lcluster_index *di; unsigned int advise; di = erofs_read_metabuf(&m->map->buf, sbi, pos, erofs_inode_in_metabox(vi)); if (IS_ERR(di)) return PTR_ERR(di); 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_lclusterbits; 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_lclusterbits) { 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_lclusterbits; const unsigned int totalidx = BLK_ROUND_UP(sbi, vi->i_size); unsigned int compacted_4b_initial, compacted_2b, amortizedshift; unsigned int vcnt, lo, lobits, encodebits, nblk, bytes; bool big_pcluster = vi->z_advise & Z_EROFS_ADVISE_BIG_PCLUSTER_1; erofs_off_t pos; u8 *in, type; int i; 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; in = erofs_read_metabuf(&m->map->buf, sbi, pos, erofs_inode_in_metabox(vi)); if (IS_ERR(in)) return PTR_ERR(in); /* 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) >> ilog2(vcnt); bytes = pos & ((vcnt << amortizedshift) - 1); in -= bytes; i = bytes >> 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_lclusterbits; 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; if (m->type >= Z_EROFS_LCLUSTER_TYPE_MAX) { erofs_err("unknown type %u @ lcn %lu of nid %llu", m->type, lcn, vi->nid | 0ULL); DBG_BUGON(1); return -EOPNOTSUPP; } else if (m->type == Z_EROFS_LCLUSTER_TYPE_NONHEAD) { lookback_distance = m->delta[0]; if (!lookback_distance) break; continue; } else { m->headtype = m->type; m->map->m_la = (lcn << lclusterbits) | m->clusterofs; return 0; } } 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_lclusterbits) >= 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); if (m->type == Z_EROFS_LCLUSTER_TYPE_NONHEAD) { if (m->delta[0] != 1) { erofs_err("bogus CBLKCNT @ lcn %lu of nid %llu", lcn, vi->nid | 0ULL); DBG_BUGON(1); return -EFSCORRUPTED; } if (m->compressedblks) goto out; } else if (m->type < Z_EROFS_LCLUSTER_TYPE_MAX) { /* * if the 1st NONHEAD lcluster is actually PLAIN or HEAD type * rather than CBLKCNT, it's a 1 block-sized pcluster. */ m->compressedblks = 1; goto out; } 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; } 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_lclusterbits; 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_MAX) { 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_map_blocks_fo(struct erofs_inode *vi, struct erofs_map_blocks *map, int flags) { struct erofs_sb_info *sbi = vi->sbi; bool fragment = vi->z_advise & Z_EROFS_ADVISE_FRAGMENT_PCLUSTER; bool ztailpacking = vi->z_idata_size; unsigned int lclusterbits = vi->z_lclusterbits; struct z_erofs_maprecorder m = { .inode = vi, .map = map, }; int err = 0; unsigned int endoff, afmt; unsigned long initial_lcn; unsigned long long ofs, end; ofs = flags & EROFS_GET_BLOCKS_FINDTAIL ? vi->i_size - 1 : map->m_la; if (fragment && !(flags & EROFS_GET_BLOCKS_FINDTAIL) && !vi->z_tailextent_headlcn) { map->m_la = 0; map->m_llen = vi->i_size; map->m_flags = EROFS_MAP_FRAGMENT; return 0; } 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 ((flags & EROFS_GET_BLOCKS_FINDTAIL) && ztailpacking) vi->z_fragmentoff = 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; __erofs_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_fragmentoff; map->m_plen = vi->z_idata_size; if (erofs_blkoff(sbi, map->m_pa) + map->m_plen > erofs_blksiz(sbi)) { erofs_err("invalid tail-packing pclustersize %llu", map->m_plen | 0ULL); goto out; } } 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_map_blocks_ext(struct erofs_inode *vi, struct erofs_map_blocks *map, int flags) { struct erofs_sb_info *sbi = vi->sbi; bool interlaced = vi->z_advise & Z_EROFS_ADVISE_INTERLACED_PCLUSTER; unsigned int recsz = z_erofs_extent_recsize(vi->z_advise); erofs_off_t pos = round_up(Z_EROFS_MAP_HEADER_END(erofs_iloc(vi) + vi->inode_isize + vi->xattr_isize), recsz); unsigned int bmask = erofs_blksiz(sbi) - 1; bool in_mbox = erofs_inode_in_metabox(vi); erofs_off_t lend = vi->i_size; erofs_off_t l, r, mid, pa, la, lstart; struct z_erofs_extent *ext; unsigned int fmt; bool last; map->m_flags = 0; if (recsz <= offsetof(struct z_erofs_extent, pstart_hi)) { if (recsz <= offsetof(struct z_erofs_extent, pstart_lo)) { ext = erofs_read_metabuf(&map->buf, sbi, pos, in_mbox); if (IS_ERR(ext)) return PTR_ERR(ext); pa = le64_to_cpu(*(__le64 *)ext); pos += sizeof(__le64); lstart = 0; } else { lstart = round_down(map->m_la, 1 << vi->z_lclusterbits); pos += (lstart >> vi->z_lclusterbits) * recsz; pa = EROFS_NULL_ADDR; } for (; lstart <= map->m_la; lstart += 1 << vi->z_lclusterbits) { ext = erofs_read_metabuf(&map->buf, sbi, pos, in_mbox); if (IS_ERR(ext)) return PTR_ERR(ext); map->m_plen = le32_to_cpu(ext->plen); if (pa != EROFS_NULL_ADDR) { map->m_pa = pa; pa += map->m_plen & Z_EROFS_EXTENT_PLEN_MASK; } else { map->m_pa = le32_to_cpu(ext->pstart_lo); } pos += recsz; } last = (lstart >= round_up(lend, 1 << vi->z_lclusterbits)); lend = min(lstart, lend); lstart -= 1 << vi->z_lclusterbits; } else { lstart = lend; for (l = 0, r = vi->z_extents; l < r; ) { mid = l + (r - l) / 2; ext = erofs_read_metabuf(&map->buf, sbi, pos + mid * recsz, in_mbox); if (IS_ERR(ext)) return PTR_ERR(ext); la = le32_to_cpu(ext->lstart_lo); pa = le32_to_cpu(ext->pstart_lo) | (u64)le32_to_cpu(ext->pstart_hi) << 32; if (recsz > offsetof(struct z_erofs_extent, lstart_hi)) la |= (u64)le32_to_cpu(ext->lstart_hi) << 32; if (la > map->m_la) { r = mid; if (la > lend) { DBG_BUGON(1); return -EFSCORRUPTED; } lend = la; } else { l = mid + 1; if (map->m_la == la) r = min(l + 1, r); lstart = la; map->m_plen = le32_to_cpu(ext->plen); map->m_pa = pa; } } last = (l >= vi->z_extents); } if (lstart < lend) { map->m_la = lstart; if (last && (vi->z_advise & Z_EROFS_ADVISE_FRAGMENT_PCLUSTER)) { map->m_flags = EROFS_MAP_FRAGMENT; vi->z_fragmentoff = map->m_plen; if (recsz > offsetof(struct z_erofs_extent, pstart_lo)) vi->z_fragmentoff |= map->m_pa << 32; } else if (map->m_plen & Z_EROFS_EXTENT_PLEN_MASK) { map->m_flags |= EROFS_MAP_MAPPED | EROFS_MAP_FULL_MAPPED | EROFS_MAP_ENCODED; fmt = map->m_plen >> Z_EROFS_EXTENT_PLEN_FMT_BIT; if (map->m_plen & Z_EROFS_EXTENT_PLEN_PARTIAL) map->m_flags |= EROFS_MAP_PARTIAL_REF; map->m_plen &= Z_EROFS_EXTENT_PLEN_MASK; if (fmt) map->m_algorithmformat = fmt - 1; else if (interlaced && !((map->m_pa | map->m_plen) & bmask)) map->m_algorithmformat = Z_EROFS_COMPRESSION_INTERLACED; else map->m_algorithmformat = Z_EROFS_COMPRESSION_SHIFTED; } } map->m_llen = lend - map->m_la; return 0; } static int z_erofs_fill_inode_lazy(struct erofs_inode *vi) { struct erofs_sb_info *sbi = vi->sbi; int err = 0, headnr; erofs_off_t pos; struct erofs_buf buf = __EROFS_BUF_INITIALIZER; struct z_erofs_map_header *h; 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); h = erofs_read_metabuf(&buf, sbi, pos, erofs_inode_in_metabox(vi)); if (IS_ERR(h)) return PTR_ERR(h); /* * 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 done; } vi->z_advise = le16_to_cpu(h->h_advise); vi->z_lclusterbits = sbi->blkszbits + (h->h_clusterbits & 15); if (vi->datalayout == EROFS_INODE_COMPRESSED_FULL && (vi->z_advise & Z_EROFS_ADVISE_EXTENTS)) { vi->z_extents = le32_to_cpu(h->h_extents_lo) | ((u64)le16_to_cpu(h->h_extents_hi) << 32); goto done; } vi->z_algorithmtype[0] = h->h_algorithmtype & 15; vi->z_algorithmtype[1] = h->h_algorithmtype >> 4; if (vi->z_advise & Z_EROFS_ADVISE_FRAGMENT_PCLUSTER) vi->z_fragmentoff = le32_to_cpu(h->h_fragmentoff); else if (vi->z_advise & Z_EROFS_ADVISE_INLINE_PCLUSTER) vi->z_idata_size = le16_to_cpu(h->h_idata_size); 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); err = -EOPNOTSUPP; goto out_put_metabuf; } 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); err = -EFSCORRUPTED; goto out_put_metabuf; } if (vi->z_idata_size || (vi->z_advise & Z_EROFS_ADVISE_FRAGMENT_PCLUSTER)) { struct erofs_map_blocks map = { .buf = __EROFS_BUF_INITIALIZER }; err = z_erofs_map_blocks_fo(vi, &map, EROFS_GET_BLOCKS_FINDTAIL); if (err < 0) return err; } done: erofs_atomic_set_bit(EROFS_I_Z_INITED_BIT, &vi->flags); out_put_metabuf: erofs_put_metabuf(&buf); return err; } 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->datalayout == EROFS_INODE_COMPRESSED_FULL && (vi->z_advise & Z_EROFS_ADVISE_EXTENTS)) err = z_erofs_map_blocks_ext(vi, map, flags); else err = z_erofs_map_blocks_fo(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.9.1/man/000077500000000000000000000000001515160260000145455ustar00rootroot00000000000000erofs-utils-1.9.1/man/Makefile.am000066400000000000000000000003521515160260000166010ustar00rootroot00000000000000# SPDX-License-Identifier: GPL-2.0+ dist_man_MANS = mkfs.erofs.1 dump.erofs.1 fsck.erofs.1 EXTRA_DIST = erofsfuse.1 mount.erofs.8 man_MANS = if ENABLE_FUSE man_MANS += erofsfuse.1 endif if OS_LINUX man_MANS += mount.erofs.8 endif erofs-utils-1.9.1/man/dump.erofs.1000066400000000000000000000035251515160260000167160ustar00rootroot00000000000000.\" 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.9.1/man/erofsfuse.1000066400000000000000000000024301515160260000166270ustar00rootroot00000000000000.\" 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.9.1/man/fsck.erofs.1000066400000000000000000000037341515160260000167010ustar00rootroot00000000000000.\" 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 .B "--no-sbcrc" Bypass the on-disk superblock checksum verification. .TP .BI "\-\-nid=" # Specify the target inode by its NID for checking or extraction. The default is the root inode. .TP .BI "\-\-path=" path Specify the target inode by its path for checking or extraction. If both \fB\-\-nid\fR and \fB\-\-path\fR are specified, \fB\-\-path\fR takes precedence. .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.9.1/man/mkfs.erofs.1000066400000000000000000000433131515160260000167100ustar00rootroot00000000000000.\" 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 various \fISOURCE\fR, where \fISOURCE\fR can be: .RS .IP \(bu 2 a local directory .IP \(bu 2 a (compressed) tarball .IP \(bu 2 an S3 bucket or a prefix within it .IP \(bu 2 an OCI image reference .IP \(bu 2 other EROFS image(s), see \fIREBUILD MODE\fR below .RE .SH OPTIONS .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 5.13+). .TP .BI "\-m " "#\fR[:\fIcompression-algorithm\fR]" Enable metadata compression with #-byte clusters in a metabox inode (Linux 6.17+); optionally specify a compression algorithm (defaults to data compression algorithm if omitted). .TP .BI "\-\-MZ\fR[\fP=<0|[id]>\fR]\fP" Put inode metadata ('i') and/or directory data ('d') into the separate metadata zone. This improves spatial locality of metadata layout within the image, which is beneficial for metadata access if the storage has long I/O latencies. .TP .BI "\-x " # Limit how many xattrs will be inlined. The default is 2. .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 "\-\-zD\fR[\fP=<0|1>\fR]\fP" Used to enable directory compression: 0=disable, 1=enable. If the optional argument is omitted, directory compression is enabled by default. .TP .BI "\-d " # Specify the level of debugging messages. The default is 2, which shows basic warning messages. 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 48bit Enable 48-bit block addressing and encoded extents to support larger filesystem images and byte-oriented data compression mainly for Zstandard. (Linux 6.15+) .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 6.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 6.1+) .TP .BI dot-omitted Omit the "." (dot) directory entry in all directories to reduce metadata overhead (Linux 6.15+). It will also enable \fB48bit\fR on-disk layout. .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 6.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 .B legacy-compress " (obsolete; should not be used in daily use)" Disable "compacted indexes" on-disk layout. .TP .BI nosbcrc Disable filesystem superblock checksum explicitly. .TP .BI plain-xattr-prefixes Store long extended attribute name prefixes directly on disk rather than in special inodes. By default, long xattr name prefixes are placed in metabox_inode (if metabox is enabled) or packed_inode (if fragments is enabled). This option forces them to be stored as plain on-disk structures. .TP .B xattr-name-filter Enable a name filter for extended attributes to optimize negative lookups. (Linux 6.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 5.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 "\-\-async-queue-limit=" # Specify the maximum number of entries in the multi-threaded job queue. .TP .BI "\-\-aufs" Replace aufs special files with overlayfs metadata. .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 "\-\-clean=" MODE Run full clean build with the given \fIMODE\fR, which could be one of \fBdata\fR, \fBrvsp\fR, or \fB0\fR. If \fB\-\-clean\fR is specified without an explicit value, it is treated as \fB\-\-clean=data\fR. \fBdata\fR: Import complete file data from the source into the destination image, creating a fully self-contained EROFS image. This mode is useful when you need a standalone image that doesn't depend on external blob devices. \fBrvsp\fR: Reserve space for file data in the destination image without copying the actual content. The file data will need to be filled in later through other means. This is useful for creating sparse images or when the actual data will be populated separately. \fB0\fR:Fill all inode data with zeros. The current source-specific support for \fIMODE\fR: .RS 1.2i .TP .I Local directory source Only \fBdata\fR is supported. \fBrvsp\fR and \fB0\fR will be ignored. .TP .I Tar source (\fB\-\-tar\fR) \fBdata\fR and \fBrvsp\fR are supported. \fB0\fR will be ignored. Note that \fBrvsp\fR takes precedence over \fB--tar=i\fR or \fB--tar=headerball\fR. .TP .I Rebuild mode Only \fBrvsp\fR is supported. .TP .I S3 source (\fB\-\-s3\fR) \fBdata\fR and \fB0\fR are supported. .TP .I OCI source (\fB\-\-oci\fR) Only \fBdata\fR is supported. .RE .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 .IP .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 #. If \fI--dsunit\fR and \fI--chunksize\fR are both set, \fI--dsunit\fR will be ignored if it is larger than \fI--chunksize\fR. If \fI--dsunit\fR is larger, it spans multiple chunks, for example: \fI-b 4096\fR, \fI--dsunit 512\fR (2MiB), \fI--chunksize 4096\fR Once a chunk is deduplicated, all subsequent chunks will no longer be aligned. For optimal performance, it is recommended to set \fI--dsunit\fR to the same value as \fI--chunksize\fR: E.g. \fI-b\fR 4096, \fI--dsunit 512\fR (2MiB), \fI--chunksize $((4096*512))\fR .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 "\-\-fsalignblks=" # Specify the alignment of the primary device size (usually the filesystem size) in blocks. .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 .BI "\-\-gzinfo\fR[\fP=" file \fR]\fP (used together with \fI--tar\fR) Generate AWS SOCI-compatible zinfo to support random gzip access. Source file must be a gzip-compressed tarball. .TP .BI "\-\-hard-dereference" Dereference hardlinks and add links as separate inodes. .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 "\-\-incremental=" MODE Run an incremental build where DESTINATION is an existing EROFS image, and the data specified by SOURCE will be incrementally appended to the image. \fIMODE\fR has the same meaning as in \fB\-\-clean\fR above. Incremental build is unsupported for \fB\-\-s3\fR and \fB\-\-oci\fR sources. If \fB\-\-incremental\fR is specified without an explicit value, it is treated as \fB\-\-incremental=data\fR. The current source-specific support for \fIMODE\fR: .RS 1.2i .TP .I Local directory source Only \fBdata\fR is supported. \fBrvsp\fR and \fB0\fR will be ignored. .TP .I Tar source (\fB\-\-tar\fR) \fBdata\fR and \fBrvsp\fR are supported. \fB0\fR will be ignored. Note that \fBrvsp\fR takes precedence over \fB--tar=i\fR or \fB--tar=headerball\fR. .TP .I Rebuild mode Only \fBrvsp\fR is supported. .RE .TP .BI "\-\-max-extent-bytes=" # Specify maximum decompressed extent size in bytes. The default is unlimited. .TP .B \-\-mkfs-time (used together with \fB-T\fR) the given timestamp is only applied to the build time. .TP .BI "\-\-mount-point=" path Specify the prefix of target filesystem path (default: /). .TP .BI "\-\-oci\fR[\fP=\fR]\fP" Generate a full (f) or index-only (i) image from OCI remote source. Additional options can be specified: .RS 1.2i .TP .BI platform= platform Specify the platform (default: linux/amd64). .TP .BI layer= # Specify the layer index to extract (0-based; omit to extract all layers). .TP .BI blob= digest Specify the blob digest to extract (omit to extract all layers). .TP .BI username= username Username for authentication (optional). .TP .BI password= password Password for authentication (optional). .TP .B insecure Use HTTP instead of HTTPS (optional). .RE .TP .BI "\-\-offset=" # Skip # bytes at the beginning of the image. .TP .BI "\-\-ovlfs-strip\fR[\fP=<0|1>\fR]\fP" Strip overlayfs metadata in the target image (e.g. whiteouts). .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 .B "\-\-quiet" Quiet execution (do not write anything to standard output.) .TP .BI "\-\-root-xattr-isize=" # Ensure the inline xattr size of the root directory is # bytes at least. .TP .BI "\-\-s3=" endpoint Generate an image from S3-compatible object store. Additional options can be specified: .RS 1.2i .TP .BI passwd_file= file S3FS-compatible password file, with the format of "accessKey:secretKey" in the first line. .TP .BI urlstyle= style S3 API calling style (vhost or path) (default: vhost). .TP .BI sig= version S3 API signature version (2 or 4) (default: 2). .TP .BI region= code Region code in which endpoint belongs to (required for sig=4). .RE .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. .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 "\-\-vmdk-desc=" FILE Generate a VMDK descriptor file to merge sub-filesystems, which can be used for tar index or rebuild mode. .TP .BI "\-\-workers=" # Set the number of worker threads to # (default: number of CPUs). .TP .BI "\-\-xattr-inode-digest=" name Specify extended attribute name to record inode digests. .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 6.4+). .TP .BI "\-\-zfeature-bits=" # Toggle filesystem compression features according to given bits #. Each bit in the value corresponds to a specific compression feature: .RS 1.2i .nf .ft CW 7 6 5 4 3 2 1 0 (bit position) | | | | | | | | | | | | | | | +-- Bit 0 (1) : legacy-compress | | | | | | +---- Bit 1 (2) : ztailpacking | | | | | +------ Bit 2 (4) : fragments | | | | +-------- Bit 3 (8) : all-fragments | | | +---------- Bit 4 (16) : dedupe | | +------------ Bit 5 (32) : fragdedupe | +-------------- Bit 6 (64) : 48bit +---------------- Bit 7 (128) : dot-omitted .ft .fi .RE .IP For example, .B --zfeature-bits=6 (binary: 0000 0110) enables ztailpacking (bit 1) and fragments (bit 2). .RE .TP \fB\-h\fR, \fB\-\-help\fR Display help string and exit. .TP \fB\-V\fR, \fB\-\-version\fR Print the version number and exit. .SH NOTES .TP .B REBUILD MODE \fBRebuild mode\fR allows \fBmkfs.erofs\fR to generate a new EROFS image from one or more existing EROFS images passed as \fISOURCE\fR(s). This mode is particularly useful for merging multiple EROFS images or creating index-only metadata images that reference data in the source images. When SOURCE contains one or more EROFS image files, .B mkfs.erofs automatically enters rebuild mode. The behavior is controlled by the .B \-\-clean or .B \-\-incremental options, which determine how file data is handled: .RS 1.2i .TP .I Default mode (blob index) The generated image contains only metadata (inodes, dentries, and xattrs). File data is referenced through chunk-based indexes pointing to the original source images, which act as external blob devices. This creates a compact metadata layer suitable for layered filesystem scenarios, similar to container image layers. .TP .I rvsp mode \fB\-\-clean=rvsp\fR or \fB\-\-incremental=rvsp\fR: Reserve space for file data without copying actual content, useful for creating sparse images. .RE .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.9.1/man/mount.erofs.8000066400000000000000000000076331515160260000171260ustar00rootroot00000000000000.\" Copyright (c) 2025 Chengyu Zhu .\" .TH MOUNT.EROFS 8 .SH NAME mount.erofs \- manage EROFS filesystem .SH SYNOPSIS \fBmount.erofs\fR [\fIOPTIONS\fR] \fISOURCE\fR \fIMOUNTPOINT\fR .br \fBmount.erofs\fR \fB\-u\fR \fITARGET\fR .br \fBmount.erofs\fR \fB\-\-reattach\fR \fITARGET\fR .SH DESCRIPTION EROFS is an enhanced lightweight read-only filesystem with modern designs for scenarios which need high-performance read-only requirements. .PP \fBmount.erofs\fR is used to mount an EROFS filesystem from \fISOURCE\fR (which can be an image file or block device) to a \fIMOUNTPOINT\fR. It supports multiple backends including direct kernel mount, FUSE-based mount, and NBD (Network Block Device) for remote sources like OCI images. .SH OPTIONS .TP .B \-h, \-\-help Display help message and exit. .TP .B \-V, \-\-version Display version information and exit. .TP .BI "\-o " options Comma-separated list of mount options. See \fBMOUNT OPTIONS\fR below. .TP .BI "\-t " type[.subtype] Specify the filesystem type and optional subtype. The type should be \fBerofs\fR. Available subtypes are: .RS .TP .B fuse Use FUSE-based mount via \fBerofsfuse\fR. .TP .B local Force direct kernel mount (default if available). .TP .B nbd Use NBD backend for remote sources (e.g., OCI images). .RE .TP .B \-u Unmount the filesystem at the specified target. .TP .B \-\-reattach Reattach to an existing NBD device and restart the NBD service. .SH MOUNT OPTIONS Standard mount options: .TP .B ro Mount the filesystem read-only (default). .TP .B rw Mount the filesystem read-write (not supported for EROFS). .TP .B nosuid Do not honor set-user-ID and set-group-ID bits. .TP .B suid Honor set-user-ID and set-group-ID bits (default). .TP .B nodev Do not interpret character or block special devices. .TP .B dev Interpret character or block special devices (default). .TP .B noexec Do not allow direct execution of any binaries. .TP .B exec Allow execution of binaries (default). .TP .B noatime Do not update inode access times. .TP .B atime Update inode access times (default). .TP .B nodiratime Do not update directory inode access times. .TP .B diratime Update directory inode access times (default). .TP .B relatime Update inode access times relative to modify or change time. .TP .B norelatime Do not use relative atime updates. .SH OCI-SPECIFIC OPTIONS The following OCI-specific options are available: .TP .BI "oci.blob=" digest Specify the OCI blob digest to mount. The digest should be in the format \fBsha256:...\fR. Cannot be used together with \fBoci.layer\fR. .TP .BI "oci.layer=" index Specify the OCI layer index to mount (0-based). Cannot be used together with \fBoci.blob\fR. .TP .BI "oci.platform=" platform Specify the target platform (default: \fBlinux/amd64\fR). .TP .BI "oci.username=" username Username for OCI registry authentication. .TP .BI "oci.password=" password Password for OCI registry authentication. .TP .BI "oci.tarindex=" path Path to a tarball index file for hybrid tar+OCI mode. .TP .BI "oci.zinfo=" path Path to a gzip zinfo file for random access to gzip-compressed tar layers. .TP .BI "oci.insecure" Use HTTP instead of HTTPS to access the image registry. .SH NOTES .IP \(bu 2 EROFS filesystems are read-only by nature. The \fBrw\fR option will be ignored. .IP \(bu 2 When mounting OCI images via NBD, the mount process creates a background daemon to serve the NBD device. The daemon will automatically clean up when the filesystem is unmounted. .IP \(bu 2 The \fB\-\-reattach\fR option is useful for recovering NBD mounts after a system crash or when the NBD daemon was terminated unexpectedly. .IP \(bu 2 Kernel direct mount is used when mounting a regular file without specifying a backend type. If file-based mounts is unsupported, loop devices will be set up automatically. .SH SEE ALSO .BR mkfs.erofs (1), .BR erofsfuse (1), .BR dump.erofs (1), .BR fsck.erofs (1), .BR mount (8), .BR umount (8) .SH AVAILABILITY \fBmount.erofs\fR is part of erofs-utils. erofs-utils-1.9.1/mkfs/000077500000000000000000000000001515160260000147325ustar00rootroot00000000000000erofs-utils-1.9.1/mkfs/Makefile.am000066400000000000000000000004021515160260000167620ustar00rootroot00000000000000# 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 erofs-utils-1.9.1/mkfs/main.c000066400000000000000000001630101515160260000160230ustar00rootroot00000000000000// 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/importer.h" #include "erofs/diskbuf.h" #include "erofs/inode.h" #include "erofs/tar.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 "../lib/compressor.h" #include "../lib/liberofs_gzran.h" #include "../lib/liberofs_metabox.h" #include "../lib/liberofs_oci.h" #include "../lib/liberofs_private.h" #include "../lib/liberofs_rebuild.h" #include "../lib/liberofs_s3.h" #include "../lib/liberofs_uuid.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}, #ifdef WITH_ANDROID {"product-out", required_argument, NULL, 513}, {"fs-config-file", required_argument, NULL, 514}, #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}, {"gzinfo", optional_argument, NULL, 535}, #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}, #ifdef EROFS_MT_ENABLED {"async-queue-limit", required_argument, NULL, 530}, #endif {"fsalignblks", required_argument, NULL, 531}, {"vmdk-desc", required_argument, NULL, 532}, #ifdef S3EROFS_ENABLED {"s3", required_argument, NULL, 533}, #endif #ifdef OCIEROFS_ENABLED {"oci", optional_argument, NULL, 534}, #endif {"zD", optional_argument, NULL, 536}, {"MZ", optional_argument, NULL, 537}, {"xattr-prefix", required_argument, NULL, 538}, {"xattr-inode-digest", required_argument, NULL, 539}, {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); } if (!strcmp(s->name, "lzma")) { printf("\n%s LZMA advanced options (do not specify if unsure):\n", spaces); printf("%s [,lc=] n = number of literal context bits\n", spaces); printf("%s [,lp=] n = number of literal position bits\n", spaces); printf("%s [,pb=] n = number of position bits\n", spaces); } } 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" " -m#[:X] enable metadata compression (# = physical cluster size in bytes;\n" " X = another compression algorithm for metadata)\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" " --zD[=<0|1>] specify directory compression: 0=disable [default], 1=enable\n" " --MZ[=<0|[id]>] put inode metadata ('i') and/or directory data ('d') into the separate metadata zone.\n" " --all-root make all files owned by root\n" #ifdef EROFS_MT_ENABLED " --async-queue-limit=# specify the maximum number of entries in the multi-threaded job queue\n" #endif " --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|0 (data: full data, rvsp: space fallocated\n" " 0: inodes zeroed)\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" " --fsalignblks=# specify the alignment of the primary device size in blocks\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" #ifdef S3EROFS_ENABLED " --s3=X generate an image from S3-compatible object store\n" " [,passwd_file=Y] X=endpoint, Y=s3fs-compatible password file\n" " [,urlstyle=Z] S3 API calling style (Z = vhost|path) (default: vhost)\n" " [,sig=<2,4>] S3 API signature version (default: 2)\n" " [,region=W] W=region code in which endpoint belongs to (required for sig=4)\n" #endif #ifdef OCIEROFS_ENABLED " --oci=[f|i] generate a full (f) or index-only (i) image from OCI remote source\n" " [,platform=X] X=platform (default: linux/amd64)\n" " [,layer=#] #=layer index to extract (0-based; omit to extract all layers)\n" " [,blob=Y] Y=blob digest to extract (omit to extract all layers)\n" " [,username=Z] Z=username for authentication (optional)\n" " [,password=W] W=password for authentication (optional)\n" " [,insecure] use HTTP instead of HTTPS (optional)\n" #endif " --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 omitted 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 HAVE_ZLIB " --gzinfo[=X] generate AWS SOCI-compatible zinfo in order to support random gzip access\n" #endif " --vmdk-desc=X generate a VMDK descriptor file to merge sub-filesystems\n" #ifdef EROFS_MT_ENABLED " --workers=# set the number of worker threads to # (default: %u)\n" #endif " --xattr-inode-digest=X specify extended attribute name X to record inode digests\n" " --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" #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 struct erofsmkfs_cfg { struct z_erofs_paramset zcfgs[EROFS_MAX_COMPR_CFGS + 1]; /* < 0, xattr disabled and >= INT_MAX, always use inline xattrs */ long inlinexattr_tolerance; bool inode_metazone; u64 unix_timestamp; unsigned int total_zcfgs; } mkfscfg = { .inlinexattr_tolerance = 2, .unix_timestamp = -1, }; static unsigned int pclustersize_packed, pclustersize_max; static int pclustersize_metabox = -1; static struct erofs_tarfile erofstar = { .global.xattrs = LIST_HEAD_INIT(erofstar.global.xattrs) }; static bool incremental_mode; static u8 metabox_algorithmid; #ifdef S3EROFS_ENABLED static struct erofs_s3 s3cfg; #endif #ifdef OCIEROFS_ENABLED static struct ocierofs_config ocicfg; #endif static bool mkfs_oci_tarindex_mode; enum { EROFS_MKFS_DATA_IMPORT_DEFAULT, EROFS_MKFS_DATA_IMPORT_FULLDATA, EROFS_MKFS_DATA_IMPORT_RVSP, EROFS_MKFS_DATA_IMPORT_ZEROFILL, } dataimport_mode; static enum { EROFS_MKFS_SOURCE_LOCALDIR, EROFS_MKFS_SOURCE_TAR, EROFS_MKFS_SOURCE_S3, EROFS_MKFS_SOURCE_OCI, EROFS_MKFS_SOURCE_REBUILD, } source_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 tarerofs_decoder; static FILE *vmdk_dcf; static char *mkfs_aws_zinfo_file; static int erofs_mkfs_feat_set_legacy_compress(struct erofs_importer_params *params, bool en, const char *val, unsigned int vallen) { if (vallen) return -EINVAL; if (en) erofs_warn("ancient !lz4_0padding layout (< Linux 5.4) is no longer supported"); params->no_zcompact = en; return 0; } static int erofs_mkfs_feat_set_ztailpacking(struct erofs_importer_params *params, bool en, const char *val, unsigned int vallen) { if (vallen) return -EINVAL; params->ztailpacking = en; return 0; } static int erofs_mkfs_strtoull(const char *nptr, char **endptr, unsigned long long *res, int base) { char *end; unsigned long long number; errno = 0; number = strtoull(nptr, &end, base); if (errno) return -errno; if (*end == 'k' || *end == 'K') number <<= 10, ++end; else if (*end == 'm' || *end == 'M') number <<= 20, ++end; else if (*end == 'g' || *end == 'G') number <<= 30, ++end; *res = number; if (endptr) *endptr = end; return 0; } static int erofs_mkfs_feat_set_fragments(struct erofs_importer_params *params, bool en, const char *val, unsigned int vallen) { if (!en) { if (vallen) return -EINVAL; params->fragments = false; return 0; } if (vallen) { unsigned long long i; char *endptr; int err; err = erofs_mkfs_strtoull(val, &endptr, &i, 0); if (err || endptr - val != vallen) { erofs_err("invalid pcluster size %s for the packed file", val); return -EINVAL; } pclustersize_packed = i; } params->fragments = true; return 0; } static int erofs_mkfs_feat_set_all_fragments(struct erofs_importer_params *params, bool en, const char *val, unsigned int vallen) { params->all_fragments = en; return erofs_mkfs_feat_set_fragments(params, en, val, vallen); } static int erofs_mkfs_feat_set_dedupe(struct erofs_importer_params *params, bool en, const char *val, unsigned int vallen) { if (vallen) return -EINVAL; params->dedupe = en ? EROFS_DEDUPE_FORCE_ON : EROFS_DEDUPE_FORCE_OFF; return 0; } static int erofs_mkfs_feat_set_fragdedupe(struct erofs_importer_params *params, bool en, const char *val, unsigned int vallen) { if (!en) { if (vallen) return -EINVAL; params->fragdedupe = EROFS_FRAGDEDUPE_OFF; } else if (vallen == sizeof("inode") - 1 && !memcmp(val, "inode", vallen)) { params->fragdedupe = EROFS_FRAGDEDUPE_INODE; } else { params->fragdedupe = EROFS_FRAGDEDUPE_FULL; } return 0; } static int erofs_mkfs_feat_set_48bit(struct erofs_importer_params *params, bool en, const char *val, unsigned int vallen) { if (vallen) return -EINVAL; if (en) erofs_sb_set_48bit(&g_sbi); else erofs_sb_clear_48bit(&g_sbi); return 0; } static bool mkfs_dot_omitted; static unsigned char mkfs_blkszbits; static int erofs_mkfs_feat_set_dot_omitted(struct erofs_importer_params *params, bool en, const char *val, unsigned int vallen) { if (vallen) return -EINVAL; mkfs_dot_omitted = en; return 0; } static struct { char *feat; int (*set)(struct erofs_importer_params *params, 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}, {"48bit", erofs_mkfs_feat_set_48bit}, {"dot-omitted", erofs_mkfs_feat_set_dot_omitted}, {NULL, NULL}, }; static bool mkfs_no_datainline; static bool mkfs_plain_xattr_pfx; static int parse_extended_opts(struct erofs_importer_params *params, 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; params->force_inodeversion = EROFS_FORCE_INODE_COMPACT; params->ignore_mtime = true; } else if (MATCH_EXTENTED_OPT("force-inode-extended", token, keylen)) { if (vallen) return -EINVAL; params->force_inodeversion = EROFS_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; mkfs_no_datainline = true; } else if (MATCH_EXTENTED_OPT("inline_data", token, keylen)) { if (vallen) return -EINVAL; mkfs_no_datainline = !!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 if (MATCH_EXTENTED_OPT("plain-xattr-prefixes", token, keylen)) { if (vallen) return -EINVAL; mkfs_plain_xattr_pfx = true; } 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(params, !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(struct erofs_importer_params *params, 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(params, 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; source_mode = EROFS_MKFS_SOURCE_TAR; 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; } #ifdef S3EROFS_ENABLED static int mkfs_parse_s3_cfg_passwd(const char *filepath, char *ak, char *sk) { struct stat st; int fd, n, ret; char buf[S3_ACCESS_KEY_LEN + S3_SECRET_KEY_LEN + 3]; char *colon; fd = open(filepath, O_RDONLY); if (fd < 0) { erofs_err("failed to open passwd_file %s", filepath); return -errno; } ret = fstat(fd, &st); if (ret) { ret = -errno; goto err; } if (!S_ISREG(st.st_mode)) { erofs_err("%s is not a regular file", filepath); ret = -EINVAL; goto err; } if ((st.st_mode & 077) != 0) erofs_warn("passwd_file %s should not be accessible by group or others", filepath); if (st.st_size > S3_ACCESS_KEY_LEN + S3_SECRET_KEY_LEN + 3) { erofs_err("passwd_file %s is too large (size: %llu)", filepath, st.st_size | 0ULL); ret = -EINVAL; goto err; } n = read(fd, buf, st.st_size); if (n < 0) { ret = -errno; goto err; } buf[n] = '\0'; while (n > 0 && (buf[n - 1] == '\n' || buf[n - 1] == '\r')) buf[--n] = '\0'; colon = strchr(buf, ':'); if (!colon) { ret = -EINVAL; goto err; } *colon = '\0'; strcpy(ak, buf); strcpy(sk, colon + 1); err: close(fd); return ret; } static int mkfs_parse_s3_cfg(char *cfg_str) { char *p, *q, *opt; int ret = 0; if (source_mode != EROFS_MKFS_SOURCE_LOCALDIR) return -EINVAL; source_mode = EROFS_MKFS_SOURCE_S3; if (!cfg_str) { erofs_err("s3: missing parameter"); return -EINVAL; } s3cfg.url_style = S3EROFS_URL_STYLE_VIRTUAL_HOST; s3cfg.sig = S3EROFS_SIGNATURE_VERSION_2; p = strchr(cfg_str, ','); if (p) { s3cfg.endpoint = strndup(cfg_str, p - cfg_str); } else { s3cfg.endpoint = strdup(cfg_str); return 0; } opt = p + 1; while (opt) { q = strchr(opt, ','); if (q) *q = '\0'; if ((p = strstr(opt, "passwd_file="))) { p += sizeof("passwd_file=") - 1; ret = mkfs_parse_s3_cfg_passwd(p, s3cfg.access_key, s3cfg.secret_key); if (ret) return ret; } else if ((p = strstr(opt, "urlstyle="))) { p += sizeof("urlstyle=") - 1; if (strncmp(p, "vhost", 5) == 0) { s3cfg.url_style = S3EROFS_URL_STYLE_VIRTUAL_HOST; } else if (strncmp(p, "path", 4) == 0) { s3cfg.url_style = S3EROFS_URL_STYLE_PATH; } else { erofs_err("invalid S3 addressing style %s", p); return -EINVAL; } } else if ((p = strstr(opt, "sig="))) { p += strlen("sig="); if (strncmp(p, "4", 1) == 0) { s3cfg.sig = S3EROFS_SIGNATURE_VERSION_4; } else if (strncmp(p, "2", 1) == 0) { s3cfg.sig = S3EROFS_SIGNATURE_VERSION_2; } else { erofs_err("invalid AWS signature version %s", p); return -EINVAL; } } else if ((p = strstr(opt, "region="))) { p += strlen("region="); opt = strchr(cfg_str, ','); s3cfg.region = opt ? strndup(p, opt - p) : strdup(p); } else { erofs_err("invalid --s3 option %s", opt); return -EINVAL; } opt = q ? q + 1 : NULL; } if (s3cfg.sig == S3EROFS_SIGNATURE_VERSION_4 && !s3cfg.region) { erofs_err("invalid --s3: using sig=4 requires region provided"); return -EINVAL; } return 0; } #endif static int erofs_mkfs_strtoll(const char *nptr, char **endptr, long long *res, int base) { char *end; long long number; errno = 0; number = strtoll(nptr, &end, base); if (errno) return -errno; if (*end == 'k' || *end == 'K') number *= 1024, ++end; else if (*end == 'm' || *end == 'M') number *= 1048576, ++end; else if (*end == 'g' || *end == 'G') number *= 1073741824, ++end; *res = number; if (endptr) *endptr = end; return 0; } static int erofs_mkfs_strtol(const char *nptr, char **endptr, long *res, int base) { long long res_ll; int ret; ret = erofs_mkfs_strtoll(nptr, endptr, &res_ll, base); if (ret) return ret; if (res_ll > LONG_MAX || res_ll < LONG_MIN) return -ERANGE; *res = res_ll; return 0; } static int erofs_mkfs_strtou32(const char *nptr, char **endptr, u32 *res, int base) { unsigned long long res_ull; int ret; ret = erofs_mkfs_strtoull(nptr, endptr, &res_ull, base); if (ret) return ret; if (res_ull > UINT32_MAX) return -ERANGE; *res = res_ull; return 0; } #ifdef OCIEROFS_ENABLED /* * mkfs_parse_oci_options - Parse comma-separated OCI options string * @cfg: OCI configuration structure to update * @options_str: comma-separated options string * * Parse OCI options string containing comma-separated key=value pairs. * * Supported options include f|i, platform, blob|layer, username, password, * and insecure. * * Return: 0 on success, negative errno on failure */ static int mkfs_parse_oci_options(struct ocierofs_config *oci_cfg, char *options_str) { char *opt, *q, *p; long idx; int ret; if (!options_str) return 0; oci_cfg->layer_index = -1; opt = options_str; q = strchr(opt, ','); if (q) *q = '\0'; if (!strcmp(opt, "i") || !strcmp(opt, "f")) { mkfs_oci_tarindex_mode = (*opt == 'i'); opt = q ? q + 1 : NULL; } else if (q) { *q = ','; } while (opt) { q = strchr(opt, ','); if (q) *q = '\0'; if ((p = strstr(opt, "platform="))) { p += strlen("platform="); free(oci_cfg->platform); oci_cfg->platform = strdup(p); if (!oci_cfg->platform) return -ENOMEM; } else if ((p = strstr(opt, "blob="))) { p += strlen("blob="); free(oci_cfg->blob_digest); if (oci_cfg->layer_index >= 0) { erofs_err("invalid --oci: blob and layer cannot be set together"); return -EINVAL; } if (!strncmp(p, "sha256:", 7)) { oci_cfg->blob_digest = strdup(p); if (!oci_cfg->blob_digest) return -ENOMEM; } else if (asprintf(&oci_cfg->blob_digest, "sha256:%s", p) < 0) { return -ENOMEM; } } else if ((p = strstr(opt, "layer="))) { p += strlen("layer="); if (oci_cfg->blob_digest) { erofs_err("invalid --oci: layer and blob cannot be set together"); return -EINVAL; } ret = erofs_mkfs_strtol(p, NULL, &idx, 10); if (ret || idx < 0) return -EINVAL; oci_cfg->layer_index = (int)idx; } else if ((p = strstr(opt, "username="))) { p += strlen("username="); free(oci_cfg->username); oci_cfg->username = strdup(p); if (!oci_cfg->username) return -ENOMEM; } else if ((p = strstr(opt, "password="))) { p += strlen("password="); free(oci_cfg->password); oci_cfg->password = strdup(p); if (!oci_cfg->password) return -ENOMEM; } else if ((p = strstr(opt, "insecure"))) { oci_cfg->insecure = true; } else { erofs_err("mkfs: invalid --oci value %s", opt); return -EINVAL; } opt = q ? q + 1 : NULL; } return 0; } #endif struct z_erofs_paramset erofs_mkfs_zparams[EROFS_MAX_COMPR_CFGS + 1]; unsigned int erofs_mkfs_total_ccfgs; static int mkfs_parse_one_compress_alg(char *alg) { struct z_erofs_paramset *zset = mkfscfg.zcfgs + mkfscfg.total_zcfgs; char extraopts[48]; char *p, *q, *opt, *endptr; int i, j; if (zset >= erofs_mkfs_zparams + ARRAY_SIZE(erofs_mkfs_zparams)) { erofs_err("too many algorithm types"); return -EINVAL; } zset->clevel = -1; zset->dict_size = 0; i = 0; p = strchr(alg, ','); if (!p) { zset->alg = alg; } else { *p++ = '\0'; zset->alg = alg; if (isdigit(*p)) { /* support old '-zlzma,9' form */ zset->clevel = strtol(p, &endptr, 10); if (*endptr && *endptr != ',') { erofs_err("invalid compression level %s", p); return -EINVAL; } } else { for (opt = p; opt;) { q = strchr(opt, ','); if (q) *q = '\0'; if ((p = strstr(opt, "level="))) { p += strlen("level="); zset->clevel = 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="); j = erofs_mkfs_strtou32(p, &endptr, &zset->dict_size, 0); if (j < 0 || (*endptr != '\0' && endptr != q)) { erofs_err("invalid compression dictsize %s", p); return -EINVAL; } } else { if (i) j = snprintf(extraopts + i, sizeof(extraopts) - i, ",%s", opt); else j = snprintf(extraopts, sizeof(extraopts), "%s", opt); if (j < 0) return -ERANGE; i += j; } opt = q ? q + 1 : NULL; } } } if (i) zset->extraopts = strdup(extraopts); return mkfscfg.total_zcfgs++; } static int mkfs_parse_compress_algs(char *algs) { char *s; int ret; for (s = strtok(algs, ":"); s; s = strtok(NULL, ":")) { ret = mkfs_parse_one_compress_alg(s); if (ret < 0) 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_sources(int argc, char *argv[], int optind) { struct stat st; int err, fd; char *s; switch (source_mode) { case EROFS_MKFS_SOURCE_LOCALDIR: err = lstat((s = argv[optind++]), &st); if (err) { erofs_err("failed to stat %s: %s", s, erofs_strerror(-errno)); return -ENOENT; } if (S_ISDIR(st.st_mode)) { cfg.c_src_path = realpath(s, NULL); if (!cfg.c_src_path) { erofs_err("failed to parse source directory: %s", erofs_strerror(-errno)); return -ENOENT; } erofs_set_fs_root(cfg.c_src_path); } else { cfg.c_src_path = strdup(s); if (!cfg.c_src_path) return -ENOMEM; source_mode = EROFS_MKFS_SOURCE_REBUILD; } break; case EROFS_MKFS_SOURCE_TAR: cfg.c_src_path = strdup(argv[optind++]); if (!cfg.c_src_path) return -ENOMEM; fd = open(cfg.c_src_path, O_RDONLY); if (fd < 0) { erofs_err("failed to open tar 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; } break; #ifdef S3EROFS_ENABLED case EROFS_MKFS_SOURCE_S3: cfg.c_src_path = strdup(argv[optind++]); if (!cfg.c_src_path) return -ENOMEM; break; #endif #ifdef OCIEROFS_ENABLED case EROFS_MKFS_SOURCE_OCI: if (optind < argc) { cfg.c_src_path = strdup(argv[optind++]); if (!cfg.c_src_path) return -ENOMEM; } else { erofs_err("missing OCI source argument"); return -EINVAL; } break; #endif default: erofs_err("unexpected source_mode: %d", source_mode); return -EINVAL; } if (source_mode == EROFS_MKFS_SOURCE_REBUILD) { 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; } return 0; } static int mkfs_parse_options_cfg(struct erofs_importer_params *params, int argc, char *argv[]) { bool has_timestamp = false; bool quiet = false; char *endptr; int opt, err; long i; while ((opt = getopt_long(argc, argv, "C:E:L:T:U:b:d:m:x:z:Vh", long_options, NULL)) != -1) { switch (opt) { case 'z': i = mkfs_parse_compress_algs(optarg); if (i) return i; break; case 'b': err = erofs_mkfs_strtol(optarg, &endptr, &i, 0); if (err || *endptr != '\0' || i < 512 || i > EROFS_MAX_BLOCK_SIZE) { erofs_err("invalid block size %s", optarg); return -EINVAL; } mkfs_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; } mkfscfg.inlinexattr_tolerance = i; break; case 'E': opt = parse_extended_opts(params, 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': mkfscfg.unix_timestamp = strtoull(optarg, &endptr, 0); if (mkfscfg.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: params->fixed_uid = strtoul(optarg, &endptr, 0); if (params->fixed_uid == -1 || *endptr != '\0') { erofs_err("invalid uid %s", optarg); return -EINVAL; } break; case 6: params->fixed_gid = strtoul(optarg, &endptr, 0); if (params->fixed_gid == -1 || *endptr != '\0') { erofs_err("invalid gid %s", optarg); return -EINVAL; } break; case 7: params->fixed_uid = params->fixed_gid = 0; break; #ifndef NDEBUG case 8: cfg.c_random_pclusterblks = true; break; case 18: cfg.c_random_algorithms = true; break; #endif case 9: err = erofs_mkfs_strtol(optarg, &endptr, &i, 0); if (err || *endptr != '\0' || i > INT32_MAX || i < INT32_MIN) { erofs_err("invalid maximum compressed extent size %s", optarg); return -EINVAL; } params->max_compressed_extent_size = i; 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; #endif case 'C': err = erofs_mkfs_strtol(optarg, &endptr, &i, 0); if (err < 0 || *endptr != '\0' || i <= 0) { erofs_err("invalid physical clustersize %s", optarg); return -EINVAL; } pclustersize_max = i; break; case 'm': { char *algid = strchr(optarg, ':'); if (algid) { algid[0] = '\0'; metabox_algorithmid = strtoul(algid + 1, &endptr, 0); if (*endptr != '\0') { err = mkfs_parse_one_compress_alg(algid + 1); if (err < 0) return err; metabox_algorithmid = err; } } err = erofs_mkfs_strtol(optarg, &endptr, &i, 0); if (err < 0 || (*endptr != '\0' && algid != endptr) || i <= 0) { erofs_err("invalid metabox option %s", optarg); return -EINVAL; } pclustersize_metabox = 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: params->ignore_mtime = true; break; case 15: params->ignore_mtime = false; break; case 16: errno = 0; params->uid_offset = strtoul(optarg, &endptr, 0); if (errno || *endptr != '\0') { erofs_err("invalid uid offset %s", optarg); return -EINVAL; } break; case 17: errno = 0; params->gid_offset = strtoul(optarg, &endptr, 0); if (errno || *endptr != '\0') { erofs_err("invalid gid offset %s", optarg); return -EINVAL; } break; case 20: mkfs_parse_tar_cfg(optarg); break; case 21: erofstar.aufs = true; break; case 516: params->ovlfs_strip = !optarg || !strcmp(optarg, "1"); 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(params, 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 if (!strcmp(optarg, "0")) { dataimport_mode = EROFS_MKFS_DATA_IMPORT_ZEROFILL; } 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: params->hard_dereference = true; break; case 529: dsunit = strtoul(optarg, &endptr, 0); if (*endptr != '\0') { erofs_err("invalid dsunit %s", optarg); return -EINVAL; } break; #ifdef EROFS_MT_ENABLED case 530: params->mt_async_queue_limit = strtoul(optarg, &endptr, 0); if (*endptr != '\0') { erofs_err("invalid async-queue-limit %s", optarg); return -EINVAL; } break; #endif case 531: params->fsalignblks = strtoul(optarg, &endptr, 0); if (*endptr != '\0') { erofs_err("invalid fsalignblks %s", optarg); return -EINVAL; } break; case 532: vmdk_dcf = fopen(optarg, "wb"); if (!vmdk_dcf) { erofs_err("failed to open vmdk desc `%s`", optarg); return -EINVAL; } break; #ifdef S3EROFS_ENABLED case 533: err = mkfs_parse_s3_cfg(optarg); if (err) return err; break; #endif #ifdef OCIEROFS_ENABLED case 534: { source_mode = EROFS_MKFS_SOURCE_OCI; err = mkfs_parse_oci_options(&ocicfg, optarg); if (err) return err; break; } #endif case 535: if (optarg) mkfs_aws_zinfo_file = strdup(optarg); tarerofs_decoder = EROFS_IOS_DECODER_GZRAN; break; case 536: if (!optarg || strcmp(optarg, "1")) { params->compress_dir = true; params->grouped_dirdata = true; } else { params->compress_dir = false; } break; case 537: if (!optarg) { mkfscfg.inode_metazone = true; params->dirdata_in_metazone = true; } else if (!strcmp(optarg, "0")) { mkfscfg.inode_metazone = false; params->dirdata_in_metazone = false; } else { for (i = 0; optarg[i]; ++i) { if (optarg[i] == 'i') { mkfscfg.inode_metazone = true; } else if (optarg[i] == 'd') { params->dirdata_in_metazone = true; } else { erofs_err("invalid metazone flags `%s`", optarg); return -EINVAL; } } if (params->dirdata_in_metazone && !mkfscfg.inode_metazone) { erofs_err("inode metadata must be in the metadata zone if directory data is stored there"); return -EINVAL; } } break; case 538: errno = 0; opt = erofs_xattr_insert_name_prefix(optarg); if (opt < 0) { erofs_err("failed to parse xattr name prefix: %s", erofs_strerror(opt)); return opt; } cfg.c_extra_ea_name_prefixes = true; break; case 539: err = erofs_xattr_set_ishare_prefix(&g_sbi, optarg); if (err < 0) { erofs_err("failed to parse ishare name: %s", erofs_strerror(err)); return err; } break; case 'V': version(); exit(0); case 'h': usage(argc, argv); exit(0); default: /* '?' */ return -EINVAL; } } if (cfg.c_blobdev_path && cfg.c_chunkbits < mkfs_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) { err = mkfs_parse_sources(argc, argv, optind); if (err) return err; } else if (source_mode != EROFS_MKFS_SOURCE_TAR) { 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; } if (quiet) { cfg.c_dbg_lvl = EROFS_ERR; cfg.c_showprogress = false; } if (pclustersize_max) { if (pclustersize_max < (1U << mkfs_blkszbits) || pclustersize_max % (1U << mkfs_blkszbits)) { erofs_err("invalid physical clustersize %u", pclustersize_max); return -EINVAL; } params->pclusterblks_max = pclustersize_max >> mkfs_blkszbits; params->pclusterblks_def = params->pclusterblks_max; } if (cfg.c_chunkbits && cfg.c_chunkbits < mkfs_blkszbits) { erofs_err("chunksize %u must be larger than block size", 1u << cfg.c_chunkbits); return -EINVAL; } /* * chunksize must be greater than or equal to dsunit to keep * data alignment working. * * If chunksize is smaller than dsunit (e.g., chunksize=4K, dsunit=2M), * deduplicating a chunk will cause all subsequent data to become * unaligned. Therefore, let's issue a warning here and still skip * alignment for now. */ if (cfg.c_chunkbits && dsunit && (1u << (cfg.c_chunkbits - g_sbi.blkszbits)) < dsunit) { erofs_warn("chunksize %u bytes is smaller than dsunit %u blocks, ignore dsunit !", 1u << cfg.c_chunkbits, dsunit); } if (pclustersize_packed) { if (pclustersize_packed < (1U << mkfs_blkszbits) || pclustersize_packed % (1U << mkfs_blkszbits)) { erofs_err("invalid pcluster size for the packed file %u", pclustersize_packed); return -EINVAL; } params->pclusterblks_packed = pclustersize_packed >> mkfs_blkszbits; } if (pclustersize_metabox >= 0) { if (pclustersize_metabox && (pclustersize_metabox < (1U << mkfs_blkszbits) || pclustersize_metabox % (1U << mkfs_blkszbits))) { erofs_err("invalid pcluster size %u for the metabox inode", pclustersize_metabox); return -EINVAL; } params->pclusterblks_metabox = pclustersize_metabox >> mkfs_blkszbits; cfg.c_mkfs_metabox_algid = metabox_algorithmid; erofs_sb_set_metabox(&g_sbi); } if (has_timestamp && cfg.c_timeinherit == TIMESTAMP_UNSPECIFIED) cfg.c_timeinherit = TIMESTAMP_FIXED; return 0; } static void erofs_mkfs_default_options(struct erofs_importer_params *params) { cfg.c_showprogress = 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 mkfs_blkszbits = ilog2(min_t(u32, getpagesize(), EROFS_MAX_BLOCK_SIZE)); params->pclusterblks_max = 1U; params->pclusterblks_def = 1U; g_sbi.feature_incompat = 0; 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; } mkfscfg.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_device_info *devs; 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) { src->xamgr = g_sbi.xamgr; ret = erofs_rebuild_load_tree(root, src, datamode); src->xamgr = NULL; 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; devs = g_sbi.devs; list_for_each_entry(src, &rebuild_src_list, list) { u8 *tag = NULL; DBG_BUGON(src->dev < 1); idx = src->dev - 1; if (extra_devices) { nblocks = src->devs[0].blocks; tag = src->devs[0].tag; } else { nblocks = src->primarydevice_blocks; devs[idx].src_path = strdup(src->devname); } devs[idx].blocks = nblocks; if (tag && *tag) memcpy(devs[idx].tag, tag, sizeof(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(void) { 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: %llu (of %u-byte blocks)\n" "Filesystem total inodes: %llu\n" "Filesystem %s metadata blocks: %llu\n" "Filesystem %s deduplicated bytes (of source files): %llu\n", uuid_str, g_sbi.total_blocks | 0ULL, 1U << mkfs_blkszbits, g_sbi.inos | 0ULL, incr, erofs_total_metablocks(g_sbi.bmgr) | 0ULL, incr, g_sbi.saved_by_deduplication | 0ULL); } int main(int argc, char **argv) { struct erofs_importer_params importer_params; struct erofs_importer importer = { .params = &importer_params, .sbi = &g_sbi, }; struct erofs_inode *root = NULL; bool tar_index_512b = false; struct timeval t; FILE *blklst = NULL; int err; u32 crc; err = liberofs_global_init(); if (err) return 1; erofs_importer_preset(&importer_params); erofs_mkfs_default_options(&importer_params); err = mkfs_parse_options_cfg(&importer_params, argc, argv); erofs_show_progs(argc, argv); if (err) { if (err == -EINVAL) fprintf(stderr, "Try '%s --help' for more information.\n", argv[0]); goto exit; } err = parse_source_date_epoch(); if (err) { fprintf(stderr, "Try '%s --help' for more information.\n", argv[0]); goto exit; } g_sbi.fixed_nsec = 0; if (mkfscfg.unix_timestamp != -1) importer_params.build_time = mkfscfg.unix_timestamp; else if (!gettimeofday(&t, NULL)) importer_params.build_time = t.tv_sec; else importer_params.build_time = 0; 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]); goto exit; } #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); goto exit; } #endif erofs_show_config(); #ifndef NDEBUG if (cfg.c_random_pclusterblks) srand(time(NULL)); #endif if (source_mode == EROFS_MKFS_SOURCE_TAR) { 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 && !erofstar.headeronly_mode) { /* * If mapfile is unspecified for tarfs index mode, * 512-byte block size is enforced here. */ mkfs_blkszbits = 9; tar_index_512b = true; } } else if (source_mode == EROFS_MKFS_SOURCE_REBUILD) { 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; } mkfs_blkszbits = src->blkszbits; } else if (mkfs_oci_tarindex_mode) { mkfs_blkszbits = 9; tar_index_512b = true; } if (!incremental_mode) err = erofs_mkfs_format_fs(&g_sbi, mkfs_blkszbits, dsunit, mkfscfg.inode_metazone); else err = erofs_mkfs_load_fs(&g_sbi, dsunit); if (err) goto exit; /* 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 ((source_mode == EROFS_MKFS_SOURCE_TAR && !erofstar.index_mode) || (source_mode == EROFS_MKFS_SOURCE_S3) || (source_mode == EROFS_MKFS_SOURCE_OCI)) { err = erofs_diskbuf_init(1); if (err) { erofs_err("failed to initialize diskbuf: %s", strerror(-err)); goto exit; } } err = erofs_load_compress_hints(&importer, &g_sbi); if (err) { erofs_err("failed to load compress hints %s", cfg.c_compress_hints_file); goto exit; } if (mkfscfg.inlinexattr_tolerance < 0) importer_params.no_xattrs = true; importer_params.z_paramsets = mkfscfg.zcfgs; importer_params.source = cfg.c_src_path; importer_params.no_datainline = mkfs_no_datainline; importer_params.dot_omitted = mkfs_dot_omitted; err = erofs_importer_init(&importer); if (err) goto exit; if (importer_params.dedupe == EROFS_DEDUPE_FORCE_ON) { if (!g_sbi.available_compr_algs) { 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; } } } cfg.c_dedupe = importer_params.dedupe; if (cfg.c_chunkbits) { err = erofs_blob_init(cfg.c_blobdev_path, 1 << cfg.c_chunkbits); if (err) goto exit; } if (tar_index_512b || 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; } } if (source_mode == EROFS_MKFS_SOURCE_LOCALDIR) { err = erofs_load_shared_xattrs_from_path(&g_sbi, cfg.c_src_path, mkfscfg.inlinexattr_tolerance); if (err) { erofs_err("failed to load shared xattrs: %s", erofs_strerror(err)); goto exit; } err = erofs_xattr_flush_name_prefixes(&importer, mkfs_plain_xattr_pfx); if (err) { erofs_err("failed to flush long xattr prefixes: %s", erofs_strerror(err)); goto exit; } root = erofs_new_inode(&g_sbi); if (IS_ERR(root)) { err = PTR_ERR(root); goto exit; } } else { err = erofs_xattr_flush_name_prefixes(&importer, mkfs_plain_xattr_pfx); if (err) { erofs_err("failed to flush long xattr prefixes: %s", erofs_strerror(err)); goto exit; } root = erofs_make_empty_root_inode(&importer, &g_sbi); if (IS_ERR(root)) { err = PTR_ERR(root); goto exit; } } importer.root = root; if (source_mode == EROFS_MKFS_SOURCE_TAR) { while (!(err = tarerofs_parse_tar(&importer, &erofstar))) ; } else if (source_mode == EROFS_MKFS_SOURCE_REBUILD) { err = erofs_mkfs_rebuild_load_trees(root); #ifdef S3EROFS_ENABLED } else if (source_mode == EROFS_MKFS_SOURCE_S3) { if (!s3cfg.access_key[0] && getenv("AWS_ACCESS_KEY_ID")) { strncpy(s3cfg.access_key, getenv("AWS_ACCESS_KEY_ID"), sizeof(s3cfg.access_key)); s3cfg.access_key[S3_ACCESS_KEY_LEN] = '\0'; } if (!s3cfg.secret_key[0] && getenv("AWS_SECRET_ACCESS_KEY")) { strncpy(s3cfg.secret_key, getenv("AWS_SECRET_ACCESS_KEY"), sizeof(s3cfg.secret_key)); s3cfg.secret_key[S3_SECRET_KEY_LEN] = '\0'; } if (incremental_mode || dataimport_mode == EROFS_MKFS_DATA_IMPORT_RVSP) err = -EOPNOTSUPP; else err = s3erofs_build_trees(&importer, &s3cfg, cfg.c_src_path, dataimport_mode == EROFS_MKFS_DATA_IMPORT_ZEROFILL); #endif #ifdef OCIEROFS_ENABLED } else if (source_mode == EROFS_MKFS_SOURCE_OCI) { ocicfg.image_ref = cfg.c_src_path; if (mkfs_oci_tarindex_mode) ocicfg.tarindex_path = strdup(cfg.c_src_path); if (!ocicfg.zinfo_path) ocicfg.zinfo_path = mkfs_aws_zinfo_file; if (incremental_mode || dataimport_mode == EROFS_MKFS_DATA_IMPORT_RVSP || dataimport_mode == EROFS_MKFS_DATA_IMPORT_ZEROFILL) err = -EOPNOTSUPP; else err = ocierofs_build_trees(&importer, &ocicfg); if (err) goto exit; #endif } if (err < 0) goto exit; err = erofs_importer_load_tree(&importer, source_mode != EROFS_MKFS_SOURCE_LOCALDIR, incremental_mode); if (err) goto exit; if (tar_index_512b) { if (!g_sbi.extra_devices) { DBG_BUGON(1); } else { if (source_mode != EROFS_MKFS_SOURCE_OCI) { if (cfg.c_src_path) g_sbi.devs[0].src_path = strdup(cfg.c_src_path); g_sbi.devs[0].blocks = BLK_ROUND_UP(&g_sbi, erofstar.offset); } } } if (erofstar.index_mode || cfg.c_chunkbits || g_sbi.extra_devices) { err = erofs_mkfs_dump_blobs(&g_sbi); if (err) goto exit; } err = erofs_importer_flush_all(&importer); if (err) goto exit; erofs_iput(root); root = NULL; err = erofs_writesb(&g_sbi); if (err) goto exit; err = erofs_dev_resize(&g_sbi, g_sbi.primarydevice_blocks); 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); } if (!err && vmdk_dcf) { err = erofs_dump_vmdk_desc(vmdk_dcf, &g_sbi); fclose(vmdk_dcf); } exit: if (root) erofs_iput(root); z_erofs_dedupe_exit(); blklst = erofs_blocklist_close(); if (blklst) fclose(blklst); erofs_cleanup_compress_hints(); erofs_cleanup_exclude_rules(); if (cfg.c_chunkbits || source_mode == EROFS_MKFS_SOURCE_REBUILD) erofs_blob_exit(); erofs_xattr_cleanup_name_prefixes(); erofs_rebuild_cleanup(); erofs_diskbuf_exit(); if (!err && source_mode == EROFS_MKFS_SOURCE_TAR) { if (mkfs_aws_zinfo_file) { struct erofs_vfile vf; int fd; fd = open(mkfs_aws_zinfo_file, O_WRONLY | O_CREAT | O_TRUNC, 0644); if (fd < 0) { err = -errno; } else { vf = (struct erofs_vfile){ .fd = fd }; err = erofs_gzran_builder_export_zinfo(erofstar.ios.gb, &vf); } } erofs_iostream_close(&erofstar.ios); if (erofstar.ios.dumpfd >= 0) close(erofstar.ios.dumpfd); } erofs_importer_exit(&importer); if (err) { erofs_err("\tCould not format the device : %s\n", erofs_strerror(err)); err = 1; } else { erofs_update_progressinfo("Build completed.\n"); erofs_mkfs_showsummaries(); } erofs_put_super(&g_sbi); erofs_dev_close(&g_sbi); liberofs_global_exit(); return err; } erofs-utils-1.9.1/mount/000077500000000000000000000000001515160260000151345ustar00rootroot00000000000000erofs-utils-1.9.1/mount/Makefile.am000066400000000000000000000004431515160260000171710ustar00rootroot00000000000000# SPDX-License-Identifier: GPL-2.0+ # Makefile.am AUTOMAKE_OPTIONS = foreign if OS_LINUX sbin_PROGRAMS = mount.erofs AM_CPPFLAGS = ${libuuid_CFLAGS} mount_erofs_SOURCES = main.c mount_erofs_CFLAGS = -Wall -I$(top_srcdir)/include mount_erofs_LDADD = $(top_builddir)/lib/liberofs.la endif erofs-utils-1.9.1/mount/main.c000066400000000000000000001051731515160260000162330ustar00rootroot00000000000000// SPDX-License-Identifier: GPL-2.0+ #define _GNU_SOURCE #include #include #include #include #include #include #include #include #include #include #include #include "erofs/config.h" #include "erofs/print.h" #include "erofs/err.h" #include "erofs/io.h" #include "../lib/liberofs_nbd.h" #include "../lib/liberofs_oci.h" #include "../lib/liberofs_gzran.h" #ifdef HAVE_LINUX_LOOP_H #include #else #define LOOP_CTL_GET_FREE 0x4C82 #define LOOP_SET_FD 0x4C00 #define LOOP_SET_STATUS 0x4C02 enum { LO_FLAGS_AUTOCLEAR = 4, }; struct loop_info { char pad[44]; int lo_flags; char pad1[120]; }; #endif #ifdef HAVE_SYS_SYSMACROS_H #include #endif /* Device boundary probe */ #define EROFSMOUNT_NBD_DISK_SIZE (INT64_MAX >> 9) enum erofs_backend_drv { EROFSAUTO, EROFSLOCAL, EROFSFUSE, EROFSNBD, }; enum erofsmount_mode { EROFSMOUNT_MODE_MOUNT, EROFSMOUNT_MODE_UMOUNT, EROFSMOUNT_MODE_DISCONNECT, EROFSMOUNT_MODE_REATTACH, }; static struct erofsmount_cfg { char *device; char *target; char *options; char *full_options; /* used for erofsfuse */ char *fstype; long flags; enum erofs_backend_drv backend; enum erofsmount_mode mountmode; bool force_loopdev; } mountcfg = { .full_options = "ro", .flags = MS_RDONLY, /* default mountflags */ .fstype = "erofs", }; enum erofs_nbd_source_type { EROFSNBD_SOURCE_LOCAL, EROFSNBD_SOURCE_OCI, }; static struct erofs_nbd_source { enum erofs_nbd_source_type type; union { const char *device_path; struct ocierofs_config ocicfg; }; } nbdsrc; static void usage(int argc, char **argv) { printf("Usage: %s [OPTIONS] SOURCE [MOUNTPOINT]\n" "Manage EROFS filesystem.\n" "\n" "General options:\n" " -V, --version print the version number of mount.erofs and exit\n" " -h, --help display this help and exit\n" " -d <0-9> set output verbosity; 0=quiet, 9=verbose (default=%i)\n" " -o options comma-separated list of mount options\n" " -t type[.subtype] filesystem type (and optional subtype)\n" " subtypes: fuse, local, nbd\n" " -u unmount the filesystem\n" " --disconnect abort an existing NBD device forcibly\n" " --reattach reattach to an existing NBD device\n" #ifdef OCIEROFS_ENABLED "\n" "OCI-specific options (EXPERIMENTAL, with -o):\n" " oci.blob= specify OCI blob digest (sha256:...)\n" " oci.layer= specify OCI layer index\n" " oci.platform= specify platform (default: linux/amd64)\n" " oci.username= username for authentication (optional)\n" " oci.password= password for authentication (optional)\n" " oci.tarindex= path to tarball index file (optional)\n" " oci.zinfo= path to gzip zinfo file (optional)\n" " oci.insecure use HTTP instead of HTTPS (optional)\n" #endif , argv[0], EROFS_WARN); } static void version(void) { printf("mount.erofs (erofs-utils) %s\n", cfg.c_version); } #ifdef OCIEROFS_ENABLED static int erofsmount_parse_oci_option(const char *option) { struct ocierofs_config *oci_cfg = &nbdsrc.ocicfg; const char *p; long idx; if ((p = strstr(option, "oci.blob=")) != NULL) { p += strlen("oci.blob="); free(oci_cfg->blob_digest); if (oci_cfg->layer_index >= 0) { erofs_err("invalid options: oci.blob and oci.layer cannot be set together"); return -EINVAL; } if (!strncmp(p, "sha256:", 7)) { oci_cfg->blob_digest = strdup(p); if (!oci_cfg->blob_digest) return -ENOMEM; } else if (asprintf(&oci_cfg->blob_digest, "sha256:%s", p) < 0) { return -ENOMEM; } } else if ((p = strstr(option, "oci.layer=")) != NULL) { p += strlen("oci.layer="); if (oci_cfg->blob_digest) { erofs_err("invalid options: oci.layer and oci.blob cannot be set together"); return -EINVAL; } idx = strtol(p, NULL, 10); if (idx < 0) return -EINVAL; oci_cfg->layer_index = (int)idx; } else if ((p = strstr(option, "oci.platform=")) != NULL) { p += strlen("oci.platform="); free(oci_cfg->platform); oci_cfg->platform = strdup(p); if (!oci_cfg->platform) return -ENOMEM; } else if ((p = strstr(option, "oci.username=")) != NULL) { p += strlen("oci.username="); free(oci_cfg->username); oci_cfg->username = strdup(p); if (!oci_cfg->username) return -ENOMEM; } else if ((p = strstr(option, "oci.password=")) != NULL) { p += strlen("oci.password="); free(oci_cfg->password); oci_cfg->password = strdup(p); if (!oci_cfg->password) return -ENOMEM; } else if ((p = strstr(option, "oci.tarindex=")) != NULL) { p += strlen("oci.tarindex="); free(oci_cfg->tarindex_path); oci_cfg->tarindex_path = strdup(p); if (!oci_cfg->tarindex_path) return -ENOMEM; } else if ((p = strstr(option, "oci.zinfo=")) != NULL) { p += strlen("oci.zinfo="); free(oci_cfg->zinfo_path); oci_cfg->zinfo_path = strdup(p); if (!oci_cfg->zinfo_path) return -ENOMEM; } else if ((p = strstr(option, "oci.insecure")) != NULL) { oci_cfg->insecure = true; } else { return -EINVAL; } return 0; } #else static int erofsmount_parse_oci_option(const char *option) { return -EINVAL; } #endif static long erofsmount_parse_flagopts(char *s, long flags, char **more) { static const struct { char *name; long flags; } opts[] = { {"defaults", 0}, {"quiet", 0}, // NOPs {"user", 0}, {"nouser", 0}, // checked in fstab, ignored in -o {"ro", MS_RDONLY}, {"rw", ~(long)MS_RDONLY}, {"nosuid", MS_NOSUID}, {"suid", ~(long)MS_NOSUID}, {"nodev", MS_NODEV}, {"dev", ~(long)MS_NODEV}, {"noexec", MS_NOEXEC}, {"exec", ~(long)MS_NOEXEC}, {"sync", MS_SYNCHRONOUS}, {"async", ~(long)MS_SYNCHRONOUS}, {"noatime", MS_NOATIME}, {"atime", ~(long)MS_NOATIME}, {"norelatime", ~(long)MS_RELATIME}, {"relatime", MS_RELATIME}, {"nodiratime", MS_NODIRATIME}, {"diratime", ~(long)MS_NODIRATIME}, {"loud", ~(long)MS_SILENT}, {"remount", MS_REMOUNT}, {"move", MS_MOVE}, // mand dirsync rec iversion strictatime }; for (;;) { char *comma; int i; int err; comma = strchr(s, ','); if (comma) *comma = '\0'; if (!strcmp(s, "loop")) { mountcfg.force_loopdev = true; } else if (strncmp(s, "oci", 3) == 0) { /* Initialize ocicfg here iff != EROFSNBD_SOURCE_OCI */ if (nbdsrc.type != EROFSNBD_SOURCE_OCI) { erofs_warn("EXPERIMENTAL OCI mount support in use, use at your own risk."); erofs_warn("Note that runtime performance is still unoptimized."); nbdsrc.type = EROFSNBD_SOURCE_OCI; nbdsrc.ocicfg.layer_index = -1; } err = erofsmount_parse_oci_option(s); if (err < 0) return err; } else { for (i = 0; i < ARRAY_SIZE(opts); ++i) { if (!strcasecmp(s, opts[i].name)) { if (opts[i].flags < 0) flags &= opts[i].flags; else flags |= opts[i].flags; break; } } if (more && i >= ARRAY_SIZE(opts)) { int sl = strlen(s); char *new = *more; i = new ? strlen(new) : 0; new = realloc(new, i + strlen(s) + 2); if (!new) return -ENOMEM; if (i) new[i++] = ','; memcpy(new + i, s, sl); new[i + sl] = '\0'; *more = new; } } if (!comma) break; *comma = ','; s = comma + 1; } return flags; } static int erofsmount_parse_options(int argc, char **argv) { static const struct option long_options[] = { {"help", no_argument, 0, 'h'}, {"version", no_argument, 0, 'V'}, {"reattach", no_argument, 0, 512}, {"disconnect", no_argument, 0, 513}, {0, 0, 0, 0}, }; char *dot; long ret; int opt; int i; nbdsrc.ocicfg.layer_index = -1; while ((opt = getopt_long(argc, argv, "VNfhd:no:st:uv", long_options, NULL)) != -1) { switch (opt) { case 'h': usage(argc, argv); exit(0); case 'V': version(); exit(0); 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 'o': mountcfg.full_options = optarg; ret = erofsmount_parse_flagopts(optarg, mountcfg.flags, &mountcfg.options); if (ret < 0) return (int)ret; mountcfg.flags = ret; break; case 't': dot = strchr(optarg, '.'); if (dot) { if (!strcmp(dot + 1, "fuse")) { mountcfg.backend = EROFSFUSE; } else if (!strcmp(dot + 1, "local")) { mountcfg.backend = EROFSLOCAL; } else if (!strcmp(dot + 1, "nbd")) { mountcfg.backend = EROFSNBD; } else { erofs_err("invalid filesystem subtype `%s`", dot + 1); return -EINVAL; } *dot = '\0'; } mountcfg.fstype = optarg; break; case 'u': mountcfg.mountmode = EROFSMOUNT_MODE_UMOUNT; break; case 512: mountcfg.mountmode = EROFSMOUNT_MODE_REATTACH; break; case 513: mountcfg.mountmode = EROFSMOUNT_MODE_DISCONNECT; break; default: return -EINVAL; } } if (mountcfg.mountmode == EROFSMOUNT_MODE_MOUNT) { if (optind >= argc) { erofs_err("missing argument: DEVICE"); return -EINVAL; } mountcfg.device = strdup(argv[optind++]); if (!mountcfg.device) return -ENOMEM; } if (optind >= argc) { if (mountcfg.mountmode == EROFSMOUNT_MODE_MOUNT) erofs_err("missing argument: MOUNTPOINT"); else erofs_err("missing argument: TARGET"); return -EINVAL; } mountcfg.target = strdup(argv[optind++]); if (!mountcfg.target) return -ENOMEM; if (optind < argc) { erofs_err("unexpected argument: %s\n", argv[optind]); return -EINVAL; } return 0; } static int erofsmount_fuse(const char *source, const char *mountpoint, const char *fstype, const char *options) { char *command; int err; if (strcmp(fstype, "erofs")) { fprintf(stderr, "unsupported filesystem type `%s`\n", mountcfg.fstype); return -ENODEV; } err = asprintf(&command, "erofsfuse -o%s %s %s", options, source, mountpoint); if (err < 0) return -ENOMEM; /* execvp() doesn't work for external mount helpers here */ err = execl("/bin/sh", "/bin/sh", "-c", command, NULL); if (err < 0) { perror("failed to execute /bin/sh"); return -errno; } return 0; } struct erofsmount_tarindex_priv { struct erofs_vfile tarindex_vf; struct erofs_vfile *zinfo_vf; u64 tarindex_size; }; static ssize_t erofsmount_tarindex_pread(struct erofs_vfile *vf, void *buf, size_t count, u64 offset) { struct erofsmount_tarindex_priv *tp; ssize_t local_read = 0, remote_read = 0; u64 index_part, tardata_part, remote_offset; tp = *(struct erofsmount_tarindex_priv **)vf->payload; DBG_BUGON(!tp); /* Handle device boundary probe requests */ if (offset >= EROFSMOUNT_NBD_DISK_SIZE) return 0; if (offset > tp->tarindex_size) { remote_offset = offset - tp->tarindex_size; index_part = 0; } else { index_part = min_t(u64, count, tp->tarindex_size - offset); remote_offset = 0; } tardata_part = count - index_part; if (index_part) { local_read = erofs_io_pread(&tp->tarindex_vf, buf, index_part, offset); if (local_read < 0) return local_read; } if (tardata_part) { remote_read = erofs_io_pread(tp->zinfo_vf, buf + local_read, tardata_part, remote_offset); if (remote_read < 0) return remote_read; } return local_read + remote_read; } static void erofsmount_tarindex_close(struct erofs_vfile *vf) { struct erofsmount_tarindex_priv *tp; tp = *(struct erofsmount_tarindex_priv **)vf->payload; DBG_BUGON(!tp); if (tp->tarindex_size > 0) erofs_io_close(&tp->tarindex_vf); if (tp->zinfo_vf) erofs_io_close(tp->zinfo_vf); free(tp); } static struct erofs_vfops tarindex_vfile_ops = { .pread = erofsmount_tarindex_pread, .close = erofsmount_tarindex_close, }; static int load_file_to_buf(const char *path, void **out, unsigned int *out_len) { void *buf = NULL; FILE *fp; int ret = 0; long sz; size_t num; fp = fopen(path, "rb"); if (!fp) return -errno; if (fseek(fp, 0, SEEK_END) != 0) { ret = -errno; goto out; } sz = ftell(fp); if (sz < 0) { ret = -errno; goto out; } rewind(fp); if (!sz) { ret = -EINVAL; goto out; } buf = malloc((size_t)sz); if (!buf) { ret = -ENOMEM; goto out; } num = fread(buf, 1, (size_t)sz, fp); if (num != (size_t)sz) { ret = -EIO; goto out; } *out = buf; *out_len = (unsigned int)sz; buf = NULL; out: if (ret < 0 && buf) free(buf); fclose(fp); return ret; } static int erofsmount_init_gzran(struct erofs_vfile **zinfo_vf, const struct ocierofs_config *oci_cfg, const char *zinfo_path) { int err = 0; void *zinfo_data = NULL; unsigned int zinfo_len = 0; struct erofs_vfile *oci_vf = NULL; oci_vf = malloc(sizeof(*oci_vf)); if (!oci_vf) { err = -ENOMEM; goto cleanup; } err = ocierofs_io_open(oci_vf, oci_cfg); if (err) { free(oci_vf); goto cleanup; } /* If no zinfo_path, return oci_vf directly for tar format */ if (!zinfo_path) { *zinfo_vf = oci_vf; return 0; } err = load_file_to_buf(zinfo_path, &zinfo_data, &zinfo_len); if (err) { erofs_io_close(oci_vf); free(oci_vf); return err; } *zinfo_vf = erofs_gzran_zinfo_open(oci_vf, zinfo_data, zinfo_len); if (IS_ERR(*zinfo_vf)) { err = PTR_ERR(*zinfo_vf); *zinfo_vf = NULL; erofs_io_close(oci_vf); free(oci_vf); goto cleanup; } free(zinfo_data); return 0; cleanup: if (zinfo_data) free(zinfo_data); return err; } /* * Create tarindex source for gzran+oci hybrid mode with three scenarios: * 1. tarindex + zinfo: Remote data is tar.gzip format * 2. tarindex only: Remote data is tar format */ static int erofsmount_tarindex_open(struct erofs_vfile *out_vf, const struct ocierofs_config *oci_cfg, const char *tarindex_path, const char *zinfo_path) { struct erofsmount_tarindex_priv *tp; int err; struct stat st; struct erofs_vfile *vf; tp = calloc(1, sizeof(*tp)); if (!tp) return -ENOMEM; vf = &tp->tarindex_vf; vf->fd = -1; if (tarindex_path) { err = open(tarindex_path, O_RDONLY); if (err < 0) { err = -errno; goto err_out; } vf->fd = err; if (fstat(vf->fd, &st) < 0) { err = -errno; goto err_out; } tp->tarindex_size = st.st_size; } err = erofsmount_init_gzran(&tp->zinfo_vf, oci_cfg, zinfo_path); if (err) goto err_out; out_vf->ops = &tarindex_vfile_ops; out_vf->fd = 0; out_vf->offset = 0; *(struct erofsmount_tarindex_priv **)out_vf->payload = tp; return 0; err_out: if (vf->fd >= 0) close(vf->fd); free(tp); return err; } struct erofsmount_nbd_ctx { struct erofs_vfile vd; /* virtual device */ struct erofs_vfile sk; /* socket file */ }; static void *erofsmount_nbd_loopfn(void *arg) { struct erofsmount_nbd_ctx *ctx = arg; int err; while (1) { struct erofs_nbd_request rq; ssize_t written; off_t pos; err = erofs_nbd_get_request(ctx->sk.fd, &rq); if (err < 0) { if (err == -EPIPE) err = 0; break; } if (rq.type != EROFS_NBD_CMD_READ) { err = erofs_nbd_send_reply_header(ctx->sk.fd, rq.cookie, -EIO); if (err) break; } erofs_nbd_send_reply_header(ctx->sk.fd, rq.cookie, 0); pos = rq.from; do { written = erofs_io_sendfile(&ctx->sk, &ctx->vd, &pos, rq.len); if (written == -EINTR) { err = written; goto out; } } while (written < 0); err = __erofs_0write(ctx->sk.fd, rq.len - written); if (err) { if (err > 0) err = -EIO; break; } } out: erofs_io_close(&ctx->vd); erofs_io_close(&ctx->sk); return (void *)(uintptr_t)err; } static int erofsmount_startnbd(int nbdfd, struct erofs_nbd_source *source) { struct erofsmount_nbd_ctx ctx = {}; uintptr_t retcode; pthread_t th; int err, err2; if (source->type == EROFSNBD_SOURCE_OCI) { if (source->ocicfg.tarindex_path || source->ocicfg.zinfo_path) { err = erofsmount_tarindex_open(&ctx.vd, &source->ocicfg, source->ocicfg.tarindex_path, source->ocicfg.zinfo_path); if (err) goto out_closefd; } else { err = ocierofs_io_open(&ctx.vd, &source->ocicfg); if (err) goto out_closefd; } } else { err = open(source->device_path, O_RDONLY); if (err < 0) { err = -errno; goto out_closefd; } ctx.vd.fd = err; } err = erofs_nbd_connect(nbdfd, 9, EROFSMOUNT_NBD_DISK_SIZE); if (err < 0) { erofs_io_close(&ctx.vd); goto out_closefd; } ctx.sk.fd = err; err = -pthread_create(&th, NULL, erofsmount_nbd_loopfn, &ctx); if (err) { erofs_io_close(&ctx.vd); erofs_io_close(&ctx.sk); goto out_closefd; } err = erofs_nbd_do_it(nbdfd); err2 = -pthread_join(th, (void **)&retcode); if (!err2 && retcode) { erofs_err("NBD worker failed with %s", erofs_strerror(retcode)); err2 = retcode; } return err ?: err2; out_closefd: close(nbdfd); return err; } #ifdef OCIEROFS_ENABLED static int erofsmount_write_recovery_oci(FILE *f, struct erofs_nbd_source *source) { char *b64cred = NULL; const char *platform; int ret; if (source->ocicfg.username || source->ocicfg.password) { b64cred = ocierofs_encode_userpass(source->ocicfg.username, source->ocicfg.password); if (IS_ERR(b64cred)) return PTR_ERR(b64cred); } platform = source->ocicfg.platform; if (!platform || !*platform) platform = ocierofs_get_platform_spec(); if ((source->ocicfg.tarindex_path || source->ocicfg.zinfo_path) && source->ocicfg.blob_digest && *source->ocicfg.blob_digest) { ret = fprintf(f, "TARINDEX_OCI_BLOB %s %s %s %s %s %s\n", source->ocicfg.image_ref ?: "", platform ?: "", source->ocicfg.blob_digest, b64cred ?: "", source->ocicfg.tarindex_path ?: "", source->ocicfg.zinfo_path ?: ""); free(b64cred); return ret < 0 ? -ENOMEM : 0; } if (source->ocicfg.blob_digest && *source->ocicfg.blob_digest) { ret = fprintf(f, "OCI_NATIVE_BLOB %s %s %s %s\n", source->ocicfg.image_ref ?: "", platform ?: "", source->ocicfg.blob_digest, b64cred ?: ""); free(b64cred); return ret < 0 ? -ENOMEM : 0; } if (source->ocicfg.layer_index >= 0) { ret = fprintf(f, "OCI_LAYER %s %s %d %s\n", source->ocicfg.image_ref ?: "", platform ?: "", source->ocicfg.layer_index, b64cred ?: ""); free(b64cred); return ret < 0 ? -ENOMEM : 0; } free(b64cred); return -EINVAL; } #else static int erofsmount_write_recovery_oci(FILE *f, struct erofs_nbd_source *source) { return -EOPNOTSUPP; } #endif static int erofsmount_write_recovery_local(FILE *f, struct erofs_nbd_source *source) { char *realp; int err; realp = realpath(source->device_path, NULL); if (!realp) return -errno; /* TYPE \n(more..) */ err = fprintf(f, "LOCAL %s\n", realp) < 0; free(realp); return err ? -ENOMEM : 0; } static char *erofsmount_write_recovery_info(struct erofs_nbd_source *source) { char recp[] = "/var/run/erofs/mountnbd_XXXXXX"; int fd, err; FILE *f; fd = mkstemp(recp); if (fd < 0 && errno == ENOENT) { err = mkdir("/var/run/erofs", 0700); if (err) return ERR_PTR(-errno); fd = mkstemp(recp); } if (fd < 0) return ERR_PTR(-errno); f = fdopen(fd, "w+"); if (!f) { close(fd); return ERR_PTR(-errno); } if (source->type == EROFSNBD_SOURCE_OCI) err = erofsmount_write_recovery_oci(f, source); else err = erofsmount_write_recovery_local(f, source); fclose(f); if (err) return ERR_PTR(err); return strdup(recp) ?: ERR_PTR(-ENOMEM); } #ifdef OCIEROFS_ENABLED /* Parse input string in format: "image_ref platform layer [b64cred]" */ static int erofsmount_parse_recovery_ocilayer(struct ocierofs_config *oci_cfg, char *source) { char *tokens[4] = {0}; int token_count = 0; char *p = source; int err; char *endptr; unsigned long v; while (token_count < 4 && (p = strchr(p, ' ')) != NULL) { *p++ = '\0'; while (*p == ' ') p++; if (*p == '\0') break; tokens[token_count++] = p; } if (token_count < 2) return -EINVAL; oci_cfg->image_ref = source; oci_cfg->platform = tokens[0]; v = strtoul(tokens[1], &endptr, 10); if (endptr == tokens[1] || *endptr != '\0') return -EINVAL; oci_cfg->layer_index = (int)v; free(oci_cfg->blob_digest); oci_cfg->blob_digest = NULL; if (token_count > 2) { err = ocierofs_decode_userpass(tokens[2], &oci_cfg->username, &oci_cfg->password); if (err) return err; } return 0; } static int erofsmount_parse_recovery_ociblob(struct ocierofs_config *oci_cfg, char *source) { char *tokens[4] = {0}; int token_count = 0; char *p = source; int err; while (token_count < 4 && (p = strchr(p, ' ')) != NULL) { *p++ = '\0'; while (*p == ' ') p++; if (*p == '\0') break; tokens[token_count++] = p; } if (token_count < 2) return -EINVAL; oci_cfg->image_ref = source; oci_cfg->platform = tokens[0]; { const char *digest = tokens[1]; const char *hex; if (!digest || strncmp(digest, "sha256:", 7) != 0) return -EINVAL; hex = digest + 7; if (strlen(hex) != 64) return -EINVAL; free(oci_cfg->blob_digest); oci_cfg->blob_digest = strdup(digest); if (!oci_cfg->blob_digest) return -ENOMEM; } oci_cfg->layer_index = -1; if (token_count > 2) { err = ocierofs_decode_userpass(tokens[2], &oci_cfg->username, &oci_cfg->password); if (err) return err; } return 0; } static int erofsmount_reattach_oci(struct erofs_vfile *vf, const char *type, char *source) { struct ocierofs_config oci_cfg = {}; int err; if (!strcmp(type, "OCI_LAYER")) err = erofsmount_parse_recovery_ocilayer(&oci_cfg, source); else if (!strcmp(type, "OCI_NATIVE_BLOB")) err = erofsmount_parse_recovery_ociblob(&oci_cfg, source); else return -EOPNOTSUPP; if (err) return err; return ocierofs_io_open(vf, &oci_cfg); } #else static int erofsmount_reattach_oci(struct erofs_vfile *vf, const char *type, char *source) { return -EOPNOTSUPP; } #endif static int erofsmount_reattach_gzran_oci(struct erofsmount_nbd_ctx *ctx, char *source) { char *tokens[6] = {0}, *p = source, *space, *oci_source; char *meta_path = NULL, *zinfo_path = NULL; int token_count = 0, err; const char *b64cred; struct erofs_vfile temp_vd; struct ocierofs_config oci_cfg = {}; while (token_count < 5) { space = strchr(p, ' '); if (!space) break; *space = '\0'; p = space + 1; tokens[token_count++] = p; } if (token_count < 4) return -EINVAL; b64cred = (token_count > 2 && tokens[2]) ? tokens[2] : ""; err = asprintf(&oci_source, "%s %s %s %s", source, tokens[0], tokens[1], b64cred); if (err < 0) return -ENOMEM; err = erofsmount_reattach_oci(&ctx->vd, "OCI_NATIVE_BLOB", oci_source); free(oci_source); if (err) return err; temp_vd = ctx->vd; oci_cfg.image_ref = strdup(source); if (!oci_cfg.image_ref) { erofs_io_close(&temp_vd); return -ENOMEM; } if (token_count > 3 && tokens[3] && *tokens[3]) meta_path = tokens[3]; if (token_count > 4 && tokens[4] && *tokens[4]) zinfo_path = tokens[4]; err = erofsmount_tarindex_open(&ctx->vd, &oci_cfg, meta_path, zinfo_path); free(oci_cfg.image_ref); erofs_io_close(&temp_vd); return err; } static int erofsmount_nbd_fix_backend_linkage(int num, char **recp) { char *newrecp; int err; if (!*recp) return 0; newrecp = erofs_nbd_get_identifier(num); if (!IS_ERR(newrecp) && newrecp) { err = strcmp(newrecp, *recp) ? -EFAULT : 0; free(newrecp); return err; } if (asprintf(&newrecp, "/var/run/erofs/mountnbd_nbd%d", num) <= 0) return -ENOMEM; if (rename(*recp, newrecp) < 0) { err = -errno; free(newrecp); return err; } free(*recp); *recp = newrecp; return 0; } static int erofsmount_startnbd_nl(pid_t *pid, struct erofs_nbd_source *source) { int pipefd[2], err, num; err = pipe(pipefd); if (err < 0) return -errno; if ((*pid = fork()) == 0) { struct erofsmount_nbd_ctx ctx = {}; char *recp; /* Otherwise, NBD disconnect sends SIGPIPE, skipping cleanup */ if (signal(SIGPIPE, SIG_IGN) == SIG_ERR) exit(EXIT_FAILURE); if (source->type == EROFSNBD_SOURCE_OCI) { if (source->ocicfg.tarindex_path || source->ocicfg.zinfo_path) { err = erofsmount_tarindex_open(&ctx.vd, &source->ocicfg, source->ocicfg.tarindex_path, source->ocicfg.zinfo_path); if (err) exit(EXIT_FAILURE); } else { err = ocierofs_io_open(&ctx.vd, &source->ocicfg); if (err) exit(EXIT_FAILURE); } } else { err = open(source->device_path, O_RDONLY); if (err < 0) exit(EXIT_FAILURE); ctx.vd.fd = err; } recp = erofsmount_write_recovery_info(source); if (IS_ERR(recp)) { erofs_io_close(&ctx.vd); exit(EXIT_FAILURE); } num = -1; err = erofs_nbd_nl_connect(&num, 9, EROFSMOUNT_NBD_DISK_SIZE, recp); if (err >= 0) { ctx.sk.fd = err; err = erofsmount_nbd_fix_backend_linkage(num, &recp); if (err) { erofs_io_close(&ctx.sk); } else { err = write(pipefd[1], &num, sizeof(int)); if (err < 0) err = -errno; close(pipefd[1]); close(pipefd[0]); if (err >= sizeof(int)) { err = (int)(uintptr_t)erofsmount_nbd_loopfn(&ctx); goto out_fork; } } } erofs_io_close(&ctx.vd); out_fork: (void)unlink(recp); free(recp); exit(err ? EXIT_FAILURE : EXIT_SUCCESS); } close(pipefd[1]); err = read(pipefd[0], &num, sizeof(int)); close(pipefd[0]); if (err < sizeof(int)) return -EPIPE; return num; } static int erofsmount_reattach(const char *target) { char *identifier, *line, *source, *recp = NULL; struct erofsmount_nbd_ctx ctx = {}; int nbdnum, err; struct stat st; size_t n; FILE *f; err = lstat(target, &st); if (err < 0) return -errno; if (!S_ISBLK(st.st_mode) || major(st.st_rdev) != EROFS_NBD_MAJOR) return -ENOTBLK; nbdnum = erofs_nbd_get_index_from_minor(minor(st.st_rdev)); if (nbdnum < 0) return nbdnum; identifier = erofs_nbd_get_identifier(nbdnum); if (IS_ERR(identifier)) { identifier = NULL; } else if (identifier && *identifier == '\0') { free(identifier); identifier = NULL; } if (!identifier && (asprintf(&recp, "/var/run/erofs/mountnbd_nbd%d", nbdnum) <= 0)) { err = -ENOMEM; goto err_identifier; } f = fopen(identifier ?: recp, "r"); if (!f) { err = -errno; free(recp); goto err_identifier; } free(recp); line = NULL; if ((err = getline(&line, &n, f)) <= 0) { err = -errno; fclose(f); goto err_identifier; } fclose(f); if (err && line[err - 1] == '\n') line[err - 1] = '\0'; source = strchr(line, ' '); if (!source) { erofs_err("invalid source recorded in recovery file: %s", line); err = -EINVAL; goto err_line; } else { *(source++) = '\0'; } if (!strcmp(line, "LOCAL")) { err = open(source, O_RDONLY); if (err < 0) { err = -errno; goto err_line; } ctx.vd.fd = err; } else if (!strcmp(line, "TARINDEX_OCI_BLOB")) { err = erofsmount_reattach_gzran_oci(&ctx, source); if (err) goto err_line; } else if (!strcmp(line, "OCI_LAYER") || !strcmp(line, "OCI_NATIVE_BLOB")) { err = erofsmount_reattach_oci(&ctx.vd, line, source); if (err) goto err_line; } else { err = -EOPNOTSUPP; erofs_err("unsupported source type %s recorded in recovery file", line); goto err_line; } err = erofs_nbd_nl_reconnect(nbdnum, identifier); if (err >= 0) { ctx.sk.fd = err; if (fork() == 0) { free(line); free(identifier); if ((uintptr_t)erofsmount_nbd_loopfn(&ctx)) return EXIT_FAILURE; return EXIT_SUCCESS; } erofs_io_close(&ctx.sk); err = 0; } erofs_io_close(&ctx.vd); err_line: free(line); err_identifier: free(identifier); return err; } static int erofsmount_nbd(struct erofs_nbd_source *source, const char *mountpoint, const char *fstype, int flags, const char *options) { bool is_netlink = false; char nbdpath[32], *id; int num, nbdfd = -1; pid_t pid = 0; long err; if (strcmp(fstype, "erofs")) { fprintf(stderr, "unsupported filesystem type `%s`\n", mountcfg.fstype); return -ENODEV; } flags |= MS_RDONLY; err = erofsmount_startnbd_nl(&pid, source); if (err < 0) { erofs_info("Fall back to ioctl-based NBD; failover is unsupported"); num = erofs_nbd_devscan(); if (num < 0) return num; (void)snprintf(nbdpath, sizeof(nbdpath), "/dev/nbd%d", num); nbdfd = open(nbdpath, O_RDWR); if (nbdfd < 0) return -errno; if ((pid = fork()) == 0) return erofsmount_startnbd(nbdfd, source) ? EXIT_FAILURE : EXIT_SUCCESS; } else { num = err; (void)snprintf(nbdpath, sizeof(nbdpath), "/dev/nbd%d", num); is_netlink = true; } while (1) { err = erofs_nbd_in_service(num); if (err == -ENOENT || err == -ENOTCONN) { err = waitpid(pid, NULL, WNOHANG); if (err < 0) { err = -errno; break; } else if (err > 0) { /* child process exited unexpectedly */ err = -EIO; break; } usleep(50000); continue; } if (err >= 0) err = (err != pid ? -EBUSY : 0); break; } if (!err) { if (mount(nbdpath, mountpoint, fstype, flags, options) < 0) { err = -errno; if (is_netlink) erofs_nbd_nl_disconnect(num); else erofs_nbd_disconnect(nbdfd); } if (!err && is_netlink) { id = erofs_nbd_get_identifier(num); err = IS_ERR(id) ? PTR_ERR(id) : erofs_nbd_nl_reconfigure(num, id, true); if (err) erofs_warn("failed to turn on autoclear for nbd%d: %s", num, erofs_strerror(err)); if (!IS_ERR(id)) free(id); } } if (!is_netlink) { DBG_BUGON(nbdfd < 0); close(nbdfd); } return err; } #define EROFSMOUNT_LOOPDEV_RETRIES 3 static int erofsmount_loopmount(const char *source, const char *mountpoint, const char *fstype, int flags, const char *options) { int fd, dfd, num; struct loop_info li = {}; bool ro = flags & MS_RDONLY; char device[32]; fd = open("/dev/loop-control", O_RDWR | O_CLOEXEC); if (fd < 0) return -errno; num = ioctl(fd, LOOP_CTL_GET_FREE); if (num < 0) return -errno; close(fd); snprintf(device, sizeof(device), "/dev/loop%d", num); for (num = 0; num < EROFSMOUNT_LOOPDEV_RETRIES; ++num) { fd = open(device, (ro ? O_RDONLY : O_RDWR) | O_CLOEXEC); if (fd >= 0) break; usleep(50000); } if (fd < 0) return -errno; dfd = open(source, (ro ? O_RDONLY : O_RDWR)); if (dfd < 0) goto out_err; num = ioctl(fd, LOOP_SET_FD, dfd); if (num < 0) { close(dfd); goto out_err; } close(dfd); li.lo_flags = LO_FLAGS_AUTOCLEAR; num = ioctl(fd, LOOP_SET_STATUS, &li); if (num < 0) goto out_err; num = mount(device, mountpoint, fstype, flags, options); if (num < 0) goto out_err; close(fd); return 0; out_err: close(fd); return -errno; } int erofsmount_umount(char *target) { char *device = NULL, *mountpoint = NULL; int err, fd, nbdnum; struct stat st; FILE *mounts; size_t n; char *s; bool isblk; target = realpath(target, NULL); if (!target) return -errno; err = lstat(target, &st); if (err < 0) { err = -errno; goto err_out; } if (S_ISBLK(st.st_mode)) { isblk = true; } else if (S_ISDIR(st.st_mode)) { isblk = false; } else { err = -EINVAL; goto err_out; } mounts = fopen("/proc/mounts", "r"); if (!mounts) { err = -ENOENT; goto err_out; } for (s = NULL; (getline(&s, &n, mounts)) > 0;) { bool hit = false; char *f1, *f2, *end; f1 = s; end = strchr(f1, ' '); if (end) *end = '\0'; if (isblk && !strcmp(f1, target)) hit = true; if (end) { f2 = end + 1; end = strchr(f2, ' '); if (end) *end = '\0'; if (!isblk && !strcmp(f2, target)) hit = true; } if (hit) { if (isblk) { err = -EBUSY; free(s); fclose(mounts); goto err_out; } free(device); device = strdup(f1); if (!mountpoint) mountpoint = strdup(f2); } } free(s); fclose(mounts); if (!isblk && !device) { err = -ENOENT; goto err_out; } if (isblk && !mountpoint && S_ISBLK(st.st_mode) && major(st.st_rdev) == EROFS_NBD_MAJOR) { nbdnum = erofs_nbd_get_index_from_minor(minor(st.st_rdev)); err = erofs_nbd_nl_disconnect(nbdnum); if (err != -EOPNOTSUPP) return err; } /* Avoid TOCTOU issue with NBD_CFLAG_DISCONNECT_ON_CLOSE */ fd = open(isblk ? target : device, O_RDWR); if (fd < 0) { err = -errno; goto err_out; } if (mountpoint) { err = umount(mountpoint); if (err) { err = -errno; close(fd); goto err_out; } } err = fstat(fd, &st); if (err < 0) err = -errno; else if (S_ISBLK(st.st_mode) && major(st.st_rdev) == EROFS_NBD_MAJOR) { nbdnum = erofs_nbd_get_index_from_minor(minor(st.st_rdev)); err = erofs_nbd_nl_disconnect(nbdnum); if (err == -EOPNOTSUPP) err = erofs_nbd_disconnect(fd); } close(fd); err_out: free(device); free(mountpoint); free(target); return err < 0 ? err : 0; } static int erofsmount_disconnect(const char *target) { int nbdnum, err, fd; struct stat st; err = lstat(target, &st); if (err < 0) return -errno; if (!S_ISBLK(st.st_mode) || major(st.st_rdev) != EROFS_NBD_MAJOR) return -ENOTBLK; nbdnum = erofs_nbd_get_index_from_minor(minor(st.st_rdev)); err = erofs_nbd_nl_disconnect(nbdnum); if (err == -EOPNOTSUPP) { fd = open(target, O_RDWR); if (fd < 0) { err = -errno; goto err_out; } err = erofs_nbd_disconnect(fd); close(fd); } err_out: return err < 0 ? err : 0; } int main(int argc, char *argv[]) { int err; erofs_init_configure(); err = erofsmount_parse_options(argc, argv); if (err) { if (err == -EINVAL) fprintf(stderr, "Try '%s --help' for more information.\n", argv[0]); return EXIT_FAILURE; } if (mountcfg.mountmode == EROFSMOUNT_MODE_UMOUNT) { err = erofsmount_umount(mountcfg.target); if (err < 0) fprintf(stderr, "Failed to unmount %s: %s\n", mountcfg.target, erofs_strerror(err)); return err ? EXIT_FAILURE : EXIT_SUCCESS; } if (mountcfg.mountmode == EROFSMOUNT_MODE_REATTACH) { err = erofsmount_reattach(mountcfg.target); if (err < 0) fprintf(stderr, "Failed to reattach %s: %s\n", mountcfg.target, erofs_strerror(err)); return err ? EXIT_FAILURE : EXIT_SUCCESS; } if (mountcfg.mountmode == EROFSMOUNT_MODE_DISCONNECT) { err = erofsmount_disconnect(mountcfg.target); if (err < 0) fprintf(stderr, "Failed to disconnect %s: %s\n", mountcfg.target, erofs_strerror(err)); return err ? EXIT_FAILURE : EXIT_SUCCESS; } if (mountcfg.backend == EROFSFUSE) { err = erofsmount_fuse(mountcfg.device, mountcfg.target, mountcfg.fstype, mountcfg.full_options); goto exit; } if (mountcfg.backend == EROFSNBD) { if (nbdsrc.type == EROFSNBD_SOURCE_OCI) nbdsrc.ocicfg.image_ref = mountcfg.device; else nbdsrc.device_path = mountcfg.device; err = erofsmount_nbd(&nbdsrc, mountcfg.target, mountcfg.fstype, mountcfg.flags, mountcfg.options); goto exit; } if (mountcfg.force_loopdev) goto loopmount; err = mount(mountcfg.device, mountcfg.target, mountcfg.fstype, mountcfg.flags, mountcfg.options); if (err < 0) err = -errno; if ((err == -ENODEV || err == -EPERM) && mountcfg.backend == EROFSAUTO) err = erofsmount_fuse(mountcfg.device, mountcfg.target, mountcfg.fstype, mountcfg.full_options); else if (err == -ENOTBLK) loopmount: err = erofsmount_loopmount(mountcfg.device, mountcfg.target, mountcfg.fstype, mountcfg.flags, mountcfg.options); exit: if (err < 0) fprintf(stderr, "Failed to mount %s: %s\n", mountcfg.fstype, erofs_strerror(err)); return err ? EXIT_FAILURE : EXIT_SUCCESS; } erofs-utils-1.9.1/scripts/000077500000000000000000000000001515160260000154615ustar00rootroot00000000000000erofs-utils-1.9.1/scripts/get-version-number000077500000000000000000000022421515160260000211370ustar00rootroot00000000000000#!/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