diff options
231 files changed, 40746 insertions, 3804 deletions
@@ -20,14 +20,17 @@ # doc_loadbios docfdisk +fectest flash_erase flash_lock flash_otp_dump +flash_otp_erase flash_otp_info flash_otp_lock flash_otp_write flash_unlock flashcp +fsck.ubifs ftl_check ftl_format jffs2dump @@ -84,6 +87,7 @@ flash_stress flash_speed flash_readtest nandbiterrs +nandflipbits nandpagetest nandsubpagetest ubiattach @@ -97,6 +101,7 @@ ubinize ubirename ubirmvol ubirsvol +ubiscan ubiupdatevol volrefcnt mtdlib_test @@ -108,6 +113,16 @@ tests/fs-tests/stress/fs_stress00.sh tests/fs-tests/stress/fs_stress01.sh tests/ubi-tests/runubitests.sh tests/ubi-tests/ubi-stress-test.sh +tests/ubifs_tools-tests/lib/common.sh +tests/ubifs_tools-tests/ubifs_tools_run_all.sh +tests/ubifs_tools-tests/fsck_tests/authentication_refuse.sh +tests/ubifs_tools-tests/fsck_tests/cycle_mount_fsck_check.sh +tests/ubifs_tools-tests/fsck_tests/powercut_fsck_mount.sh +tests/ubifs_tools-tests/fsck_tests/cycle_corrupted_fsck_fault_inject.sh +tests/ubifs_tools-tests/fsck_tests/cycle_powercut_mount_fsck.sh +tests/ubifs_tools-tests/fsck_tests/random_corrupted_fsck.sh +tests/ubifs_tools-tests/fsck_tests/fsck_bad_image.sh +tests/ubifs_tools-tests/mkfs_tests/build_fs_from_dir.sh # # Files generated by autotools diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..204ee0f --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,1038 @@ +# Changelog + +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/). + +## [2.3.0] - 2025-02-15 +### Added + - fsck.ubifs: Add fsck utility for ubifs + - ubifs-utils: Support Address-Sanitizier debug + - nand-utils: nanddump: Add support for testing continuous reads + - mtd-tests: nandbiterrs: Add support for testing continuous reads + - mtd-tests: flash_speed: Benchmark continuous reads + +### Fixed + - Various integer handling errors (potential overflows, divide by zero) + - mkfs.jffs2: fix segfault when parsing dev table + - nand-utils: nanddump: Explicitely use the page size when relevant + - misc-utils: add missing error handling for 'bam' allocation in ftl_check.c + - mtdinfo: type mis-match in printf format string + - nanddump: const cast warning + - flashcp: uninitialized variable + - jffs2reader: potential null pointer dereference + - tests: checkfs: Add previous prototype for do_pwr_dn() + +### Changed + - mkfs.ubifs: re-sync with the kernel code + - mkfs.ubifs: move most of the code into a libubifs library + - Import a more recent version of libiniparser + - mtd-tests: flash_speed: cleanup/refactor + +## [2.2.1] - 2024-09-25 +### Fixed + - fectest: Fix time formatting with _TIME_BITS=64 on 32-bit system + - mkfs.ubifs: Clear direct_write property when closing target + - mkfs.ubifs: Initialize 'cipher_name' as NULL + - mkfs.ubifs: Fix wrong xattr entry type + - mkfs.ubifs: Fix incorrect dir size calculation in encryption scenario + - mkfs.ubifs: Close libubi in error handling paths + - mkfs.ubifs: Fix missed closing fd + - mkfs.ubifs: Fix memleak for in error paths + - also check for static libuuid + +### Changed + - refactor: integck: split out common remount logic + - refactor: move rbtree & list code from jffsX-utils to common code + +## [2.2.0] - 2024-03-29 +### Added + - flashcp: Add write last option + - flash_erase: Add an option for JFFS2 cleanmarker size + - ubiattach: Add disable fastmap option + - ubiattach: Add option to reserve peb pool for fastmap + - support building without zlib + - CHANGELOG & README files + +### Fixed + - jffs2dump: check return value of lseek + - mkfs.ubifs: fix xattr scanning for builds with selinux support + +### Changed + - overhaul dependency handling in the build system + +## [2.1.6] - 2023-08-30 +### Added + - flash_speed: Measure read while write latency + - Support `mtd:<num>` syntax for several tools + +### Fixed + - flashcp: check for lseek errors + - flashcp: fix buffer overflow + - flashcp: verify data in `--partition` + - flashcp: abort on `--partition` and `--erase-all` + - flashcp: correct casting for percent display + - mtdpart: document partition of size 0 + - mkfs.ubifs: Non-terminated string related failure with option selinux + - nandtest: handle nand devices larger than 4G + - Fix printf format specifiers for 64 bit integer types + +### Changed + - flashcp: merge duplicate write code paths + - flashcp: merge duplicate MEMERASE code paths + - flashcp: simplify logging + +## [2.1.5] - 2022-10-07 +### Fixed + - mkfs.jffs2: spelling of `--compression-mode` parameter in help text + - ubinfo: `--vol_id` return code for absent volume id + - nandflipbits: fix corrupted oob + - libmtd: do not ignore non-zero eraseblock size when `MTD_NO_ERASE` is set + - jffs2reader: warning about unaligned pointer + - tests: Remove unused linux/fs.h header from includes + - fix test bashism + - nanddump: fix writing big images on 32bit machines + - nor-utils: fix memory leak + +### Changed + - flash_otp_dump: make offset optional + - nandwrite: warn about writing 0xff blocks + +## [2.1.4] - 2022-10-07 +### Added + - ubiscan debugging and statistics utility + +### Fixed + - Some mtd-tests erroneously using sub-pages instead of the full page size + - Buffer overrun in fectest + - Build failures due to missing jffs2 kernel header + +## [2.1.3] - 2021-07-25 +### Added + - flashcp: Add new function that copy only different blocks + - flash_erase: Add flash erase chip + - Add flash_otp_erase + - Add an ubifs mount helper + - Add nandflipbits tool + +### Fixed + - mkfs.ubifs: Fix runtime assertions when running without crypto + - Use AC_SYS_LARGEFILE + - Fix test binary installation + - libmtd: avoid divide by zero + - ubihealthd: fix UBIFS build dependency + - mkfs.ubifs: remove `OPENSSL_no_config()` + - misc-utils: Add fectest to build system + - mkfs.ubifs: Fix build with SELinux + - Fix typos found by Debian's lintian tool + - Fix jffs2 build if zlib or lzo headers are not in default paths + +## [2.1.2] - 2020-07-13 +### Added + - flashcp: Add option `-A`, `--erase-all` + - mtd-utils: add optional offset parameter to `flash_otp_dump` + - ubi-utils: Implement a ubihealthd + - mkfs.ubifs: Add authentication support + +### Fixed + - mtd-utils: Fix return value of ubiformat + - ubiupdatevol: Prevent null pointer dereference + - libubigen: remove unnecessary include + - libubi: remove private kernel header from includes + - mkfs.ubifs: fscrypt: bail from encrypt_block if gen_essiv_salt fails + - mkfs.ubifs: abort add_directory if readdir fails + - mkfs.ubifs: close file descriptor in add_file error path + - mkfs.ubifs: don't leak copied command line arguments + - mkfs.ubifs: free derived fscrypt context in add_directory error paths + - mkfs.ubifs: don't leak hastable iterators + - mkfs.ubifs: don't leak temporary buffers + - mkfs.ubifs: propperly cleanup in ALL interpret_table_entry error paths + - mkfs.jffs2: don't leak temporary buffer if readlink fails + - libmtd: don't leak temporary buffers + - ftl_check: don't leak temporary buffers + - ftl_format: don't leak temporary buffers + - ubiformat: don't leak file descriptors + - nanddump: don't leak copied command line arguments + - mtd_debug: cleanup error handling in flash_to_file + - jittertest: fix error check for open system call + - fs-tests: don't leak temporary buffers + - mtd-utils: Fix printf format specifiers with the wrong type + - mtd-utils: Fix potential negative arguments passed to close(2) + - mtd-utils: Fix various TOCTOU issues + - mtd-utils: Fix some simple cases of uninitialized value reads + - mtd-utils: Fix wrong argument to sizeof in nanddump + - mtd-utils: Fix "are we really at EOF" test logic in libubi read_data + - mtd-utils: Fix potentially unterminated strings + - mtd-utils: Add checks to code that copies strings into fixed sized buffers + - mkfs.ubifs: fix broken build if fscrtyp is disabled + +### Changed + - ubifs-media: Update to Linux-5.3-rc3 + +## [2.1.1] - 2019-07-21 +### Added + - mkfs.ubifs: Add ZSTD compression + +### Fixed + - ubiformat: Dont ignore sequence number CLI option + - mkfs.ubifs: fix build without openssl + - mkfs.ubifs: fix regression when trying to store device special files + - mkfs.ubifs: fix description of favor_lzo + - unittests/test_lib: Include proper header for `_IOC_SIZE` + - unittests/libmtd_test: Include fcntl header + - unittests: Define the use of `_GNU_SOURCE` + - ubinize: Exit with non-zero exit code on error. + - mtd-tests: nandbiterrs: Fix issue that just insert error at bit 7 + - ubi-tests: ubi_mkvol_request: Fully initialize `struct ubi_mkvol_request req` + - ubi-tests: io_read: Filter invalid offset before `lseek` in `io_read` test + - ubi-tests: mkvol test: Checks return value `ENOSPC` for `ubi_mkvol` + - ubi-tests: fm_param: Replace `fm_auto` with `fm_autoconvert` + +## [2.1.0] - 2019-03-19 +### Added + - mkfs.ubifs: Implement support for file system encryption + - mkfs.ubifs: Implement selinux labelling support + - ubinize: add support for skipping CRC check of a static volume when opening + - ubimkvol: add support for skipping CRC check of a static volume when opening + - Add lsmtd program + +### Fixed + - update various kernel headers + - Instead of doing preprocessor magic, just output off_t as long long + - fix verification percent display in flashcp + - mkfs.ubifs: fix double free + - mkfs.ubifs: Fix xattr nlink value + - ubinize: avoid to create two `UBI_LAYOUT_VOLUME_ID` volume + - common.h: fix prompt function + - libmtd: don't print an error message for devices without ecc support + - io_paral: Fix error handling of update_volume() + - ubimkvol: Initialize req to zero to make sure no flags are set by default + - libubi: add volume flags to `ubi_mkvol_request` + - mkfs.ubifs: add_xattr is not depending on host XATTR support + - Revert "Return correct error number in ubi_get_vol_info1" which + introduced a regression. + - make sure pkg-config is installed in configure script + - ubiformat: process command line arguments before handling file arguments + +### Changed + - ubiformat: remove no-volume-table option + +## [2.0.2] - 2018-04-16 +### Added + - libmtd: Add support to access OOB available size + - mkfs.ubifs: Allow root entry in device table + +### Fixed + - Fix unit-test header and file paths for out of tree builds + - Fix unit test mockup for oobavail sysfs file + - misc-utils: flash_erase: Fix Jffs2 type flash erase problem + - libmtd_legacy: Fix some function description mismatches + - mtd-utils: ubifs: fix typo in without_lzo definition + - mtd: tests: check erase block count in page test + - mtd: unittests: Stop testing stat() calls + - mtd: unittests: Decode arg size from ioctl request + - mtd: unittests: Use proper unsigned long type for ioctl requests + - mtd: tests: Fix check on ebcnt in nandpagetest + - ubi-utils: ubicrc32: process command line arguments first + - nandbiterrs: Fix erroneous counter increment in for loop body + - jittertest: Use the appropriate versions of abs() + - Mark or fix switch cases that fall through + - mkfs.ubifs: ignore EOPNOTSUPP when listing extended attributes + - misc-utils: initialize "ip" in docfdisk to NULL + - mkfs.ubifs: Apply squash-uids to the root node + +### Changed + - ubi-utils: ubiformat.c: convert to integer arithmetic + - mtd-utils: common.c: convert to integer arithmetic + - Run unit test programs through "make check" + - Enable more compiler warning flags, fix warnings + - Add no-return attribute to usage() style functions + - Remove self-assignments of unused paramters + - tests: checkfs: Remove unused source file from makefiles + - ubi-tests: io_update: fix missleading indentation + - Add ctags files to .gitignore + - libscan: fix a comment typo in libscan.h + - libmtd: fix a comment typo in dev_node2num + +## [2.0.1] - 2017-08-24 +### Added + - nandbiterrs: Add Erased Pages Bit Flip Test + - mkfs.ubifs: Add support for symlinks in device table + - nanddump: Add `--skip-bad-blocks-to-start` option + - nandwrite: Add `--skip-bad-blocks-to-start` option + +### Fixed + - common: Always terminate with failure status if command line options + are unknown or missing + - common: Fix format specifier definitions for `off_t` and `loff_t` + - common: More consistent exit codes + - libmtd: Fix error status if MTD is not present on the system + - libubi: Add klibc specific fixes for `ioctl` + - libubi: Fix error status in `ubi_get_vol_info1` for non-existing volumes + - misc-utils: Support jffs2 flash-erase for large OOB (>32b) + - mkfs.jffs2: Add missing header inclusions required for build with musl + - mkfs.ubifs: Fix alignment trap triggered by NEON instructions + - mkfs.ubifs: Fix uuid.h path + - mkfs.ubifs: Replace broken ubifs_assert with libc assert + - nandbiterrs: Actually get the new ECC bit flip count before comparing stats + - nandpagetest: Improved argument sanity checking + - nandwrite: Fix bad block skipping + - nandwrite: Improved argument sanity checking + - ubinfo: Improved argument sanity checking + - ubi-tests: Replace variable-length array with `malloc` + - ubi-tests: Support up to 64k NAND page size + +### Changed + - build-system: Enable compiler warnings + - build-system: Restructure autoconf dependency checking + - common: Add const modifier to read only strings and string constants + - common: Eliminate warnings about missing prototypes + - common: Get rid of rpmatch usage + - common: Remove README.udev from ubi-tests extra dist + - common: Remove unused variables and functions + - common: Silence warnings about unused arguments + - flashcp: Drop custom defines for `EXIT_FAILURE` and `EXIT_SUCCESS` + - libiniparser: remove unused function needing float + - libmissing: Use autoconf header detection directly + - libubi: Remove `UDEV_SETTLE_HACK` + - misc-utils: Move libfec to common public header & library directory + - nandwrite: replace erase loop with mtd_erase_multi + - serve_image: Use PRIdoff_t as format specifier. + - ubi-tests: Speedup io_paral by using rand_r() + - ubirename: Fix spelling + +## [2.0.0] - 2016-12-22 +### Added + - libmissing with stubs for functions not present in libraries like musl + - unittests for libmtd and libubi + - port most kernel space mtd test modules to userspace + - mkfs.ubifs: extended attribute support + - ubinize: Move lengthy help text to a man page + - nandwrite: Add skip-all-ff-pages option + - flash_{un,}lock: support for MEMISLOCKED + - nandtest: support hex/dec/oct for `--offset` and `--length` + +### Fixed + - common: Fix 'unchecked return code' warnings + - common: Fix PRI{x,d}off definitions for x86_64 platform + - common: include sys/sysmacros.h for major/minor/makedev + - common: fix wrong format specifiers on mips32 + - libmtd: Fix uninitialized buffers + - libmtd: Eliminate warnings about implicit non-const casting + - libmtd: Fix return status in mtd_torture test function + - libmtd: mtd_read: Take the buffer offset into account when reading + - mkfs.ubifs: use gid from table instead 2x uid + - mkfs.ubifs: fix compiler warning for WITHOUT_LZO + - mkfs.ubifs: fix build when WITHOUT_LZO is set + - mkfs.ubifs: correct the size of nnode in memset + - mkfs.jffs2: initialize lzo decompression buffer size + - mkfs.jffs2: Fix scanf() formatstring for modern C version + - nanddump: check write function result for errors + - nanddump: write requested length only + - flash_{un,}lock: don't allow "last byte + 1" + - flash_{un,}lock: improve strtol() error handling + - ubinize: Always return error code (at least -1) in case of an error + - recv_image: fix build warnings w/newer glibc & _BSD_SOURCE + - serve_image: use proper POSIX_C_SOURCE value + - flashcp: Use %llu to print filestat.st_size + - mtd_debug: check amount of data read. + - fs-tests: integrity: don't include header <bits/stdio_lim.h> + - tests: Fix endian issue with CRC generation algorithm + - make_a_release.sh: fix MTD spelling + - Fix packaging of unit test files + - Correct casting for final status report in flashcp + +### Changed + - autotools based build system + - complete restructuring of the source tree + - cleanup of some utilities + - removal of some very old, unused or duplicated files from the source tree + - libmtd: removal of very old, completely unused and broken functions + - nandwrite: Factor out buffer checking code + +## [1.5.2] - 2015-07-24 +### Added + - mtdpart utility to add/delete partition + - jffs2dump: XATTR and XREF support for content dump & endianess conversion + +### Fixed + - ubifs-media.h: include "byteorder.h" + - ubiformat: fix the subpage size hint on the error path + - include/common.h: fix build against current uClibc + - include/common.h: fix build against recent 0.9.33 uClibc + - libmtd: fix comment typo + - mkfs.ubifs: Fix build with gcc 5.1 + - libmtd: don't ignore "region index" parameter in `mtd_regioninfo()` + - .gitignore: add new mtdpart utility + - ubi-tests: fix a some overflows + - formating issues (trailing whitespaces, space-before-tab, blank line at EOF) + - fs-tests: pass `TEST_DIR` to integck in run_all.sh + - nandtest: fix `--reads` argument + - libmtd: fix `mtd_dev_present` return value on legacy systems + - libfec: use standard C type instead of `u_long` + - serve_image: do not include error.h + - recv_image: do not include error.h + - include/common.h: fix build against musl + - integck: Use `$(CC)` and `$(AR)` instead of `gcc` and `ar` consistently + +### Changed + - mkfs.ubifs: start using common code, move some macros to common header + - Move mkfs.ubifs/ubifs-media.h to include/mtd + - make_a_release.sh: suggest announcement e-mail + - nandtest: Remove redundant check + - nandtest: Move the "read and compare" code to a function + - nandtest: Introduce multiple reads & check iterations + +## [1.5.1] - 2014-04-07 +### Added + - tests: ubi: add stress-test.sh + - ubi-utils: Add ubiblock tool + - flash_erase, flash_otp_write: check the nand type + - ubiupdatevol: add a `--skip` option + - nandwrite: add `--input-{skip,size}` options + - mkfs.ubifs: allow reformatting of devices + - new prompt() helper for talking to the user + +### Fixed + - Makefile: add LDFLAGS_mkfs.ubifs, fix libuuid issue + - runtests.sh: print more consistent messages + - integck: fix identation + - nandwrite: apply corrent version of `--input-*` patch series + - ubi-tests: argument number mismatch for `ubi_leb_change_start` + - ubi-tests: fix pthreads linking + - ubiformat: correct "non-ubifs" warning message + - mkfs.ubifs: correct and improve LEB size error prints + - ubi-utils: Fix file descriptor leaks in libubi + - integck.c: Fix buffer overflow in save_file + - integck.c: Only verify the operation after datastructures have been updated + - mkfs.ubifs: Improve error handling of is_contained() + - nandwrite: clean up length types + - nand{dump, test, write}: clean up `--help` handling + - fix build errors w/newer kernel headers & glibc + - ubinize: fix usage text + - consistency between `u_int32_t` / `off_t` / `off64_t` + - tests: io_update correct lseek parameters order + - ubiformat: fix failure on big partitions (>4Gio) + - Check mtdoffset is not larger than mtd.size in case of a bad block. + - flash_otp_write: fix writing to NAND in presence of partial reads + - flash_otp_write: fix a buffer overflow on NAND with write size > 2048 + - ubiformat: really skip some messages when quiet + - ubiformat: fix error path + +### Changed + - nandwrite: minor cleanups + - tests: ubi: clean-up the runtests.sh script + - libubi: Remove `ubi_attach_mtd` + - load_nandsim.sh: switch to sh, remove bashisms, cleanup code + - load_nandsim.sh: introduce a usage function + - load_nandsim.sh: intruduce fatal function + - load_nandsim.sh: use dochere for help output + - mkfs.ubifs: rewrite path checking + - ubi-tests: switch to using common.h from the top level + - ubi-tests: rename common.[ch] to helpers.[ch] + - integck.c: immediately dump the buffer containing the errors + - use xstrdup in a few more places + - move `_GNU_SOURCE` to the main makefile + - ftl_check/ftl_format/nftldump: use existing mtd_swab.h header + - flash_erase: use `pwrite()` rather than `lseek()` and `write()` + - UBI: sync ubi-user.h with kernel v3.6-rc1 + - introduce `PRIxoff_t` and `PRIdoff_t` printf helpers + - ubiattach: use `max_beb_per1024` in `UBI_IOCATT` ioctl + - ubiattach: fail if kernel ignores max_beb_per1024 + - libubi: factorize `ubi_attach` and `ubi_attach_mtd` code + - Makefile: Build and install `flash_otp_lock`, `flash_otp_write` + - ubiformat: clean up synopsis of command-line parameters + +## [1.5.0] - 2012-05-07 +### Added + - limbtd: implement `mtd_dev_present` for older kernels + - libubi: make `ubi_dev_present()` a library function + - libmtd: add `mtd_dev_present()` library function + +### Fixed + - mkfs.ubifs: do not ignore `--max-leb-cnt` when formatting an UBI volume + - ubinfo: fix `--all` for non-consecutive device numbers + - mtdinfo: fix `--all` for non-consecutive device numbers + - libmtd: fix segmentation fault on lib->mtd + - mtdinfo: correct grammar on error message + - libmtd: fix `mtd_write()` issues for large data-only writes + - libmtd: check if the device is present before accessing it + - libmtd_legacy: don't open device in R/W + - Makefile: fix "make clean" for old GNU find + - libmtd: Variable name same as function name causing compile to fail (Android) + +### Changed + - make_a_release.sh: remind about pushing the master branch + - docfsdisk: minor cosmetic cleanup + - mkfs.jffs2: improve documentation of `--pagesize` parameter + +## [1.4.9] - 2011-12-17 +### Added + - Add a script to make releases + - nandtest: add seed argument + +### Fixed + - nandtest: seed random generator with time + - libmtd: allow write operations when MEMWRITE is not supported + - nandtest: set oldstats.failed + - Makefile: fix install target with out-of-tree builds + +### Changed + - Makefile: separate man page install and compression steps + +## [1.4.8] - 2011-11-19 +### Fixed + - Makefile: Version number accedientally left at 1.4.6 in last release + +## [1.4.7] - 2011-11-18 +### Added + - libmtd: support MEMWRITE ioctl + +### Fixed + - jffs2reader: get rid of linker error + - jffs2reader: eliminate compiler errors + - ubiformat: handle write errors correctly + - mtd-utils: add jffs2reader to .gitignore + +### Changed + - jffs2reader: use major() and minor() helpers + - jffs2reader: print ctime only by user's request + - jffs2reader: use const char * for path variables + - jffs2reader: introduce ADD_BYTES macro + - jffs2reader: update the header inclusion block + - nandtest: print number of bits corrected during test + - mkfs.ubifs/ubinize: increase PEB size limit to 2MiB + - mtdinfo: provide info when used without arguments + - nandwrite: use common.h "errmsg" functions + - nandwrite: re-implement `--autoplace` option + - move OOB auto-layout into libmtd's mtd_write + - nandwrite: merge `mtd_write_oob` and `mtd_write` calls + - libmtd: modify `mtd_write` to cover OOB writes + - update mtd-abi.h + - nandwrite: consolidate buffer usage + - nanddump: kill usages of MEMSETOOBSEL ioctl + - nandwrite: refactor "old_oobinfo" code + - nandwrite: cleanup "oobinfochanged" leftovers + - nandwrite: remove C99 comment style + - use `__func__` instead of `__FUNCTION__` + - mtd_debug: replace #defines with enum + - mtd_debug: fixup style + +### Removed + - tests: checkfs: remove unused code + - nandwrite: kill `--raw` option + - nandwrite: kill -j, -y, and -f options + - nandwrite: remove `autoplace` features + +## [1.4.6] - 2011-08-20 +### Fixed + - mkfs.ubifs: fix a gcc warning + - integck: fix build error (MS_DIRSYNC, MS_RELATIME) + - mtd-tests: checkfs: fix size_t related warning + - mtd-tests: io_paral: build error, "variable length strings" + - flash_erase: fix incorrect help message + - Makefile: fix "version.h" build for cross-compiling + - Makefile: fix "make clean" for cross-compile + - mtdinfo: don't open NULL pointer when getting region_info with `-a' + - mtdinfo: refactor code to remove "args.all" dependency + - mtdinfo: fixup "example usage" help section + - nandwrite: invalid erase after page write failure + +### Changed + - nanddump: change default to `--bb=skipbad` + - nanddump: change `-o` to mean `--oob`, not `--omitoob` + - nanddump: default to NOT dumping OOB data + - mkfs.ubifs: use common.h + - switch more utils to unified versioning + - add common version printing function + - mtdinfo: restructure help message + - mtdinfo: consolidate help as display_help() + - rewrite build system to avoid recursion + - autogenerate version.h from build system + +### Removed + - mtdinfo: remove -m leftovers + - mkfs.ubifs: remove root inode squash feature + - nanddump: kill `--omitbad`, `--noskipbad` + - kill flash_info + - mtdinfo: kill `-m` option + - build: remove old SYMLINKS variable + +## [1.4.5] - 2011-07-25 +### Added + - Makefile: introduce new target tests in Makefile + - Makefile: introduce cscope target + - nanddump: add `--oob` option + - nanddump: add `--bb=METHOD` option + - mkfs.ubifs: add `-F` option for `free-space fixup` + - libmtd: add helper funcs for getting regioninfo and locked info + - mtdinfo: add regioninfo/eraseblock map display + - flash_info: allow people to get info on multiple devices + +### Fixed + - serve_image: adjust classifier and type for printf + - libmtd: use PRIu64 classifier for uint64_t printf arguments + - mkfs.jffs2: fix casting of __off64_t + - tests: ubi-tests: clean libubi.a and *.o + - tests: fs-tests: ssize_t return type in read() wrapper + - mkfs.jffs2: fix casting of printf argument + - tests: jittertest: fix set of compiler warnings + - tests: ubi-tests: seed_random_generator() was used w/o prototype + - tests: fs-tests: check return value of functions + - tests: checkfs: fix linker warnings + - tests: checkfs: fix compiler warnings + - tests: make jittertest buildable + - mkfs.ubifs: check output first + - flash_{lock, unlock}: fix off-by-one error for "entire device" length + - fs-tests: use independent random generators for ops and data + - ubi-utils: ubimkvol: fix parameters parsing regression + +### Changed + - tests: checkfs: integerate with common Makefile + - fs-tests: integck: major cleanup/rewrite + - mtdinfo: deprecate the -m option + - libmtd: improve mtd_islocked interface + - switch ubi and ubifs tools to use common strtoX funcs + - mtdinfo: separate out ubi information printing + - fs_tests: make the test-suite finish faster + - nanddump: document, warn about future default `--omitoob` + - feature-removal-schedule: describe nanddump changes + - nanddump: warn about new default BB handling + - nanddump: update help message for BB method changes + - nanddump: sort options in help message alphabetically by shortname + - jffs2: make lzo optional at build time + - flash_info: deprecate + - ubi-utils: send help/version info to stdout + - libmtd: use O_CLOEXEC + - include/mtd: sync with kernel + - flash_info: convert to common.h + - ignore (FLAT) gdb files + - flash_{lock,unlock}: convert to common code + - flash_{lock,unlock}: merge into one util + - flash_{lock,unlock}: merge functionality + - flash_lock/flash_unlock/flash_info: clean up style + +### Removed + - punt redundant libcrc32 + - mtdinfo: remove now unused ubigen info + +## [1.4.4] - 2011-04-01 +### Added + - libmtd: fix OOB read and write interface + +### Fixed + - fs-tests: test_1: fix compilation warnings + - fs-tests: perf: fix compilation warning + - fs-tests: integck: improve re-mounting test coverage + +### Changed + - mkfs.ubifs: deprecate `squash-rino-perm` options + - nanddump: fail if `-s` parameter is unaligned + +## [1.4.3] - 2011-03-18 +### Added + - nandwrite: add only write oob option + +### Fixed + - common.h: simple_strtoll type + +### Changed + - flash_erase: start position should be in bytes + +## [1.4.2] - 2010-12-31 +### Added + - nandwrite: Large page+oob support + - nanddump/nandwrite: 64-bit support utilizing libmtd + - introduce xzalloc() helper + - add xasprintf() helper + - new memory wrappers + - new strtoX helpers + - new bareverbose() helper + - libmtd: add lock/unlock helpers + - nandwrite: add `--skipbad` to write bad blocks + +### Fixed + - nanddump: fix initialization of bad blocks oob data buffer + - nanddump: always check the first erase block + - flash_erase: Fix output of offsets + - nanddump/nandwrite: Style fixups + - nanddump/nandwrite: style, signed-ness, printing fixups + - nandwrite: type consistency + - nandwrite: prevent 32-bit overflow + - nandwrite: avoid NULL buffer pointers + - libmtd: fix OOB size initialization in legacy code + - Makefile: Use $(CURDIR) in place of $(PWD) + - mkfs.ubifs: Fix heap corruption on LEB overrun + - mkfs.ubifs: Fix --squash-rino-perm / --nosquash-rino-perm + - Fix make install errors + - libmtd: fix "fount" typo + - sys_errmsg: fix indentation + - recv_image: fix `__USE_GNU` hack + - ubi-utils: tweak const strings decls + - compr_rtime: fix unused warning + - libfec: fix up pointer warnings in fec magic computation + - mkfs.jffs2: fix devtable count as mkfs.ubifs does + - mkfs.jffs2: fix repeated dev nodes + - mkfs.ubifs: Fix typo in short options of mkfs.ubifs + +### Changed + - mkfs.ubifs: do not override root inode permissions + - Makefile: remove old ubi-utils + - Add --squash-rino-perm removal plan + - nanddump: warn when the start address is not page aligned + - nanddump: Dynamic buffer, increase pagesize/oobsize + - nandwrite: Use libmtd to get correct mtd parameters + - nandwrite: switch "oobsize" for "writesize" + - nandwrite: Clarify usage of aligned "erasesize" + - nandwrite: add check for negative blockalign + - nandwrite: use common.h "errmsg_die" + - nanddump: choose correct "printf" format-specifier + - nanddump: check for negative inputs + - nanddump: change "unsigned" to "signed" + - nanddump/nandwrite: use "simple_" str functions + - common.h: Add MAX() macro, fix MIN() + - nanddump: Refactor pretty print code into an sprintf() + - jffs2reader: convert to common.h helpers + - libmtd: make malloc failures fatal + - sumtool/libfec: convert "()" to "(void)" in func defs + - sumtool: convert to common.h helpers + - mkfs.jffs2: convert to common.h and xalloc.h helpers + - libmtd: unify some error messages + - xalloc: simplify/unify error messages + - enable garbage collection of unused function/data sections + - mkfs.jffs2: use new xasprintf() helper + - unify flash_erase and flash_eraseall + - common.h: clean up PROGRAM_NAME usage + - standardize PROGRAM_NAME + - mkfs.ubifs: use common ARRAY_SIZE + - punt duplicate normsg_cont define + - convert to common.h/min + - libmtd: unify erase block argument checking + - compr: drop unused model argument + - compr_zlib: mark local functions as static + - rbtree: avoid redefining offsetof + +### Removed + - ftl_check: drop unused verbose flag + - nandwrite: Remove redundant 'autoplace' check + +## [1.4.1] - 2010-10-19 +### Changed + - Hide zlib's crc32 in compr_zlib.c and mkfs.ubifs/compr.c + +## [1.4.0] - 2010-09-13 +### Added + - libmtd: add OOB read and write interfaces + - libmtd: support MEMERASE64 + - lib: Add forgotten Makefile + - libmtd: add mtd_write_img + - ubi-tests: add normsg + - ubinfo: document the new -N option + - nanddump: add canonical (hex+ascii) flag + - nanddump: add "forcebinary" flag + - nanddump: add `--nobad` to read bad blocks + - libubi: add support to attach/detach by MTD device path + - ubiattach/ubidetach: add support to attach/detach by path + - nanddump: Support 4096+218 and 4096+224 page sizes + - ubinfo: add -N option to get info by name of ubi volume + - support 4096+64 page sizes + +### Fixed + - mkfs.jffs2: fix `--enable-compressor` + - lib: fix libcrc32 generation + - rename crc32 to mtd_crc32 + - mkfs.ubifs: fix compilation warning + - mkfs.jffs2: fixed warnings + - nandtest: Fixed indentation + - fix spelling error + - fix compiler warnings + - nandwrite: fix the bug of writing a yaffs2 image to NAND + - nandwrite: check if the start address is page-aligned + - nandtest: fix `--keep` argument + - fix parallel build between ubi-utils and mkfs.ubifs + - clean up compile warnings + - ubi-utils: reformat help text to fit in 80 columns + - nanddump: Fix hexdump nybble ordering + - mtd_debug: fix creation mode parameter + - mkfs.jffs2: fix integer underflow in jffs2_rtime_compress() + +### Changed + - libs: remove ubiutils-specific stuff from common.h + - libs: make crc32 and fec to be libraries + - libscan: rename hdr to ech + - ubi-test: seed the random genrator in tests + - ubi-tests: use rand instead of random in io_paral + - ubi-utils: harmonize mtd device node variables + - ubi-utils: harmonize libmtd interface a bit + - ubi-tests: remove some junk from the integ test + - libubigen: make init_vid_hdr externally visible + - libubigen: move header comments + - libubigen: do not create huge arrays on stack + - ubi-tests: teach errmsg and failed return error code + - ubi-tests: rename err_msg to errmsg + - libubi: provide mtd number in UBI device information + - libubi: remove few fields from volume info + - nanddump: increase max OOB size + - nanddump: rename `--nobad` to `--noskipbad` + - build: add option to not force largefile support + - ubi-utils: drop -Werror in old utils + - flash_eraseall: tweaks to make binary size smaller + - mtd: change flash_eraseall to use libmtd-wrapped ioctls + - mtd-utils: update to latest mtd-abi.h from kernel.org + - move libmtd source files to lib/ subdirectory + - ubi-utils: provide default value for /dev/ubi_ctrl + +### Removed + - nanddump: drop unused --ignoreerrors option + +## [1.3.1] - 2010-01-15 +### Fixed + - ubiformat: always initialize seq number + - ubiformat: be consistent with sequence numbers + +## [1.3.0] - 2009-12-09 +### Added + - Makefile: Add Optional ZLIB and LZO CPPFLAGS and LDFLAGS + - Add mkfs.ubifs + - Add support for 4k pages + - ubirmvol: remove volume by name + - nandwrite: Support reading from standard input + - nanddump: Add Support for Quiet Option + - ubinize: allow an absent 'image' in the ubinize configuration + - handle non-power-of-2 erase size + - add ubirename utility + - ubiformat: mark faulty blocks as bad + - ubi-utils: add sysfs interface support and new tool + - libubi: add `ubi_set_property`, `ubi_leb_unmap` interfaces + - libubi: add `ubi_is_mapped()` function + - ubinize: add sequence number support + - ubiformat: add image sequence support + - ubi-utils: add ubirsvol tool to resize UBI volumes + - flash_unlock: enhancing for unlocking of specified number of blocks + +### Fixed + - ubi-tests: fix makefile + - ubi-tests: fix run script + - ubinize: validate number of sections + - ubinize: add more ini-file validation + - ubinize: fix static volumes generation + - fs-tests: fix symlink bug in integrity test + - fs-tests: fix max file name length in integrity test + - fs-tests: allow for symlink name too long in integrity test + - ubi-utils: fixed and enhanced `--flash-image` option + - ubimkvol: check for free LEBs + - ubiupdatevol: remove non-existing option from help message + - fs-tests: also preserve mount options when mounting again + - nandwrite/nanddump: Pass Real Names as Arguments to perror + - ubinize: correct subpage_size print and initialise vol_info to zero + - flash_eraseall reports incorrect percentage + - ubi-utils: fix CFLAGS handling wrt cross compilation + - Makefile: Separate '-m' and the mode with a space when invoking 'install' + - ubi-utils: various fixes + - ubiformat.c: fix printf(%d, size_t) warning + - ubi-utils: fix up build system + - nandwrite: correct data reading + - mkfs.jffs2: fix dir creation in / + - ubi-utils: fix warning in fprintf() code + - common.mk: tweak rules to workaround make-3.80 bugs + - fs-tests: fix remounting in integck + - ubi-utils: minor printing fix + - ubi-utils: fix compilation warnings + - ubi-tests: fix build and some warnings + - ubiupdatevol: fix -t parameter + - mkfs.jffs2: fix lzo usage on 64bit systems + - make sure compiler supports warning flags + - libmtd: fix mtd_is_bad return code + - libubigen: don't define large array on stack + - ubiformat: fix segfault and messages + - Prevent git-clean from removing cscope files + - ubinfo: handle -d correctly + - ubiformat: fix build error + - ubi-tests: make tests compile again + - libubigen: add missing include + - libubi: fix multiple memory corruptions + - ubi-utils: fix memory corruptions + - libmtd: recognize pre-MTD-sysfs kernels better + - nandwrite: fix loop condition + - nandwrite: return error if failure when reading from standard input + - nandwrite: fix error handling + - libubi: fix wrong size calculation with sizes > 4GiB + - ubiformat: allow zero erase counter + - ubi-utils: fix compilation errors when using CPPFLAGS + - nandwrite: fix incorrect use of errno + - ubiformat: fix typo in the help output + - ubiformat: fix `--erase-counter` handling + - flash_lock: fix length being passed + - ubiformat: check that min IO size is power of 2 + - ubiformat: fix error message + +### Changed + - enable parallel build process + - ubi-utils: use 'stat(2)' instead of 'lstat(2)' + - ubi-utils: allow ubiformat to read from stdin + - ubi-utils: minor rename + - nandwrite/nanddump: cleanup qualifiers, exit mnemonics, booleans, usage + - fec.c: replace bzero, bcopy, bcmp wiuth memset, memcpy, memcmp + - libubi: be more verbose about errors + - libubi: remove some too verbose messages + - Unify all common build system parts + - ubiformat: nicify error messages + - libmtd: Add `_FILE_OFFSET_BITS=64`, fix `lseek` overflow + - update ubi-user.h, ubi-header.h, ubi-media.h + - Add generated binaries to gitignore + - ubi-utils: re-arrange directory layout + - ubi-utils: tweak vpath handling + - libmtd: move comments to headers + - libubi: amend included header files + - libubi: remove unnecessary header files + - libmtd: amend interface + - ubi-utils: rename `ubi_node_type`, `mtd_get_info`, `mtd_info` + - libubi: improve `libubi_open` interface, error handling + - libubi: do not use udevsettle + - libmtd: rename `allows_bb`, `rdonly`, `num` fields + - libmtd: make type_str to be an array + - ubi-tests: improve io_paral test + - ubiformat: torture eraseblocks on write errors + - ubiformat: minor printing clean-up + - ubiformat: nicify error output + - ubiformat: make badblocks output less confusing + - common: remove depricated KB,MB,GB support + - ubi-utils: update ubi-media.h + - ubiformat: clean up help output a little + - jffs2dump: rewrite `--help` output to be more suitable for `help2man` + - nandwrite: unified reading from standard input and from file + +### Removed + - ubi-utils: remove debugging leftovers + - ubi-utils: remove depricated -d option + +## [1.2.0] - 2008-06-27 +### Added + - nandwrite: add 'markbad' option + - Add nand integrity testing utility + - Add utilities for multicast send/receive of MTD images + - ubi-utils: add -m option to ubimkvol + - ubi-utils: add -S option to ubimkvol + - ubi-utils: Add rmvol test + - Add test program orph.c + - mkfs.jffs2.c: detect hardlinks + - ubi-utils: add ubinfo, ubiupdate, ubiattach, ubidetach + - ubi-utils: bin2nand, nand2bin support for different ecc layouts + - ubi-utils: ubinize, ubiformat + - Add libmtd, libscan + - libubi: support atomic LEB change ioctl + - mtd_debug: support new flags + - Add load_nandsim.sh script + - fs-tests: add simple performance test + - mkfs.jffs2: Add Support for Symlinks to Device Table + +### Fixed + - Fix repeated warning about ECC correction, in nandtest + - fs-tests: fix bug in simple test_2 + - ubi-utils: Fixup oob data generation + - ubi-utils: various fixes in unubi + - ubi-utils: nand2bin had ECC calculation problems + - libubi: fix use of negative `errno` values + - libubi: fail gracefully if ubi is not supported on the current system + - ubi-utils: fix readdir error checking + - ubi-utils: fix bytes output formating + - libubi: fix sysfs direntries scanning + - ubi-utils: pddcustomize fixup update volume + - ubi-utils: fix warnings for gcc 4.2.3 + - ubi-utils: ubimkvol: fix wrong variable printout + - Use LSB locations for manpages + - compr_lzo.c: allocate enough memory for lzo compressor. + - mkfs.jffs2.c: fix issue with crashing when using lzo compression + - fs-tests: preserve mount options when mounting again + - mkfs.jffs2: Remove Incorrect Find Optimization + +### Changed + - nandtest: Mark blocks bad on failed write; don't abort + - Makefile: Make optflags easier for distros to override + - Makefile: build ubi-utils from top dir, add to install target + - Improve option handling in nandtest, add markbad and offset/length options + - ubi-utils: rename `__unused` to `ubi_unused` + - ubi-utils: remove useless build information + - ubi-utils: Nand2bin add more information when bad blocks occur + - ubi-utils: migrate to new libubi + - ubi-utils: Update and bugfix unubi + - ubi-utils: Test-case for unubi + - ubi-utils: unubi: add physical erase block number for analysis + - ubi-utils: overhaul README, tests + - ubi-utils: move UBI tests to tests/ubi-tests/ + - ubi-utils: move jffs2_test.sh to ubi-tests + - mkfs.jffs2: use separate ino field in struct filesystem_entry for jffs2 ino# + - mkfs.jffs2: Set mkfs.jffs2 page size runtime instead of fixed + - ubi-tools: major cleanups in ubimkvol, ubirmvol + - libubi: consistently rename `eb` to `leb` + - ubi-utils: cleanup ubicrc32 + - ubi-utils: more sanity checks + - Move perl scripts to scritps directory + - tests: move checkfs test, jittertest to tests directory + - ubi-utils: update headers + - ubi-utils: CLI handling for ubigen consistent with other tools + - ubi-utils: scripts/mkdevs.pl: create control device node as well + - ubi-utils: major overhaul/rewrite for pfi2ubi + - ubi-tools: use uint32_t in userspace + - ubi-utils: use macros not hardcoded constants + - ubi-utils: use mtd_swab.h + - ubi-tools: improve printing macros + - ubi-tools: consistent option handling for ubimkvol, ubirmvol, ubiupdatevol + - ubi-utils: don't use argp.h + - Teach libubi_open not to prirnt error message + - ubi-utils: use common.h in libubi + - fs-tests: allow for ENOSPC in test fwrite00 + - Makefile: respect CFLAGS/CPPFLAGS from build environment + +### Removed + - Remove mkfs.jffs utility + - ubi-utils: remove unused directory, testcases.txt + +## [1.1.0] - 2007-08-03 +### Added + - Add GPL license text, .gitignore file + - mkfs.jffs2: Add XATTR support + - nanddump: Add `-n` argument (no-ecc) + - Support for 1KB page & 32 bytes spare NAND + - Add feature removal schedule file + - Add fs-tests from Adrian Hunter + - Add UBI utilities + - Add lzo helper functions + +### Fixed + - nanddump.c: add missing `--file` long options + - fix handling of ioctl return value in nand-utils + - Correct integrity test calculations of free space + +### Changed + - nandwrite: Allow `-s` argument to accept hex and octal values + - Add usage message for flash_erase.c + - Consolidate the swab macros into one location + - Switch to using standard types + - Update mtd-abi.h from upstream kernel. + - Update user headers from latest kernel tree + - Update mtd-abi.h and use new NAND ECC functionality + - Makefile: Support out-of-tree builds + - mkfs.jffs2: Add xseqno into `jffs2_raw_xref` + - Report compressed file sizes (including node headers) in mkfs.jffs2 stats + - Consistently use getopt based option parsing + - Amend tests not to insist that file system type is JFFS2 + - Remove CVS $Id:$ tags + +### Removed + - mkfs.ffs2 + - Remaining Automake files + +## [1.0.0] - 2006-04-30 +### Added + - Import source from CVS + - Add user space headers + +### Fixed + - memory leak in `write_regular_file` + - Fix cbuf free properly + - Fix mtd_debug after removal of some MTD types and flags + - Fix `make install` target + +### Changed + - Build using in-tree user space headers diff --git a/Makefile.am b/Makefile.am index 4fdcd70..c756127 100644 --- a/Makefile.am +++ b/Makefile.am @@ -1,17 +1,22 @@ ACLOCAL_AMFLAGS = -I m4 -AM_CPPFLAGS = $(WARN_CFLAGS) -D_GNU_SOURCE -std=gnu99 -I$(top_srcdir)/include +AM_CPPFLAGS = $(WARN_CFLAGS) -D_GNU_SOURCE -std=gnu99 -I$(top_srcdir)/include \ + -include $(top_builddir)/include/config.h -if WITHOUT_XATTR -AM_CPPFLAGS += -DWITHOUT_XATTR +if WITH_ZLIB +AM_CPPFLAGS += -DWITH_ZLIB endif -if WITHOUT_LZO -AM_CPPFLAGS += -DWITHOUT_LZO +if WITH_XATTR +AM_CPPFLAGS += -DWITH_XATTR endif -if WITHOUT_ZSTD -AM_CPPFLAGS += -DWITHOUT_ZSTD +if WITH_LZO +AM_CPPFLAGS += -DWITH_LZO +endif + +if WITH_ZSTD +AM_CPPFLAGS += -DWITH_ZSTD endif if WITH_SELINUX @@ -22,31 +27,22 @@ if WITH_CRYPTO AM_CPPFLAGS += -DWITH_CRYPTO endif -if WITH_GETRANDOM -AM_CPPFLAGS += -DWITH_GETRANDOM -endif - sbin_PROGRAMS = sbin_SCRIPTS = check_PROGRAMS = noinst_LIBRARIES = noinst_PROGRAMS = noinst_SCRIPTS = -pkglibexec_PROGRAMS = -pkglibexec_SCRIPTS = dist_man1_MANS = dist_man8_MANS = -TESTS = -EXTRA_DIST = - -GLOBAL_HEADER = \ - include +testdir = @TESTBINDIR@ +test_PROGRAMS = +test_SCRIPTS = +test_DATA = -GLOBAL_EXTRA = \ - COPYING - -EXTRA_DIST += $(GLOBAL_HEADER) $(GLOBAL_EXTRA) +TESTS = +EXTRA_DIST = COPYING CHANGELOG.md README.txt include lib/Makemodule.am include ubi-utils/Makemodule.am @@ -68,6 +64,7 @@ include tests/jittertest/Makemodule.am include tests/checkfs/Makemodule.am include tests/fs-tests/Makemodule.am include tests/mtd-tests/Makemodule.am +include tests/ubifs_tools-tests/Makemodule.am endif if UNIT_TESTS diff --git a/README.txt b/README.txt new file mode 100644 index 0000000..4311698 --- /dev/null +++ b/README.txt @@ -0,0 +1,142 @@ + + mtd-utils + ========= + + This package provides userspace utilities for working with Linux MTD and UBI + infrastructure, as well as related filesystems (JFFS2, UBIFS). + + MTD stands for "Memory Technology Devices" and means flash, RAM and similar + chips, used for solid state storage on embedded devices. Think of e.g. a raw + nand-flash chip attached directly to an SoC via parallel or SPI bus, but + without _any_ special management like an eMMC has. The MTD subsystem exposes + those devices to userspace as character devices. It provides a unified + interface, but does not attempt to hide the page vs erase-block device + geometry or issues involved in dealing with the underlying storage, like wear + or bad blocks. + + For this purpose, UBI provides a layer on top of MTD that hides away some + of those complexities. UBI also implements LVM-like logical volume partitioning + and some other useful capabilities. + + UBI does not emulate block device like storage. UBI volumes are exposed as + character devices with page vs erase-block access geometry, much like MTD + partitions. But they behave more like a flash chip with "idealized" properties + and transparent wear-leveling. + + JFFS2 is a flash friendly filesystem. It predates UBI and is designed to run + on top of raw MTD, implementing its own wear leveling. + + The more recent UBIFS (formerly JFFS3) can be used on top of UBI volumes and + offers a number of improvements over JFFS2, offers newer compression schemes + and advanced features like file-level encryption and authentication. + + + Installing + ---------- + + mtd-utils are packaged by a number of Linux distributions[0] and can be + installed via the package management system. A Yocto recipe is available + as well[1] as part of the openembedded-core layer. + + Should the tools be too big for on-device use, the BusyBox project provides + compatible clone versions. + + Before making this choice however, please note that some "heavy weight" tools + like mkfs.ubifs are typically not needed on the device itself. The kernel + module can format a volume on first mount. Other, heavy weight tools like lsmtd + can be disabled via a configure flag. + + [0] https://repology.org/project/mtd-utils/versions + [1] https://layers.openembedded.org/layerindex/recipe/262/ + + + How to get the source + --------------------- + + The official git repository and release tarballs are hosted on infradead.org: + + Relase tarballs: ftp://ftp.infradead.org/pub/mtd-utils + git repository: git://git.infradead.org/mtd-utils.git + + This is also where the website and documentation are hosted: + + http://www.linux-mtd.infradead.org/ + + A http mirror is also provided on infraroot.at: + + Relase tarballs: https://infraroot.at/pub/mtd + git repository: https://git.infraroot.at/mtd-utils.git + + + There are a number of mirrored repositories on GitHub, but this is not where + official development takes place. If you want to contribute upstream, please + do not open any issues or pull requests there. Upstream development is done + via mailing list (see below). + + + How to build + ------------ + + mtd-utils uses an autotools based build system. If you are building the git + tree, you first need to run `./autogen.sh`. This sets up the build system + and requires autoconf, automake and possibly other autotools to be installed. + + After unpacking a tarball (or running autogen.sh) simply run: + + $ ./configure + $ make + + to build the package. This does not require autotools to be installed, but + does need pkg-config, make, etc... + + There are a number of configure switches to tune the build. + Run `./configure --help` to get an overview. + + + For compiling mtd-utils, you need development packages for the following + dependency libraries: + + zlib (optional dependency for mkfs.ubifs, mkfs.jffs2) + lzo2 (optional dependency for mkfs.ubifs, mkfs.jffs2) + zstd (optional dependency for mkfs.ubifs) + libuuid* (required by mkfs.ubifs) + libselinux (optional dependency for mkfs.ubifs) + openssl (optional dependency for mkfs.ubifs) + + * this library is part of util-linux, aka util-linux-ng and some distributions + package it under that name. + + For xattr & acl support in mkfs.ubifs and mkfs.jffs2, the build system looks + for the "sys/xattr.h" and "sys/acl.h" header files. Depending on your + distributions, those may be packaged as part of libattr and libacl + respectively. + + + Please note that the mkfs tools are optional and can be disabled via a + configure flag. This should also remove any library dependencies. + + + How to contribute + ----------------- + + Development of mtd-utils takes place on the linux-mtd mailing list: + + mailto: linux-mtd@lists.infradead.org + archive: https://lists.infradead.org/pipermail/linux-mtd/ + + You can subscribe here: http://lists.infradead.org/mailman/listinfo/linux-mtd/ + or simply send "subscribe" to linux-mtd-request@lists.infradead.org + + + Contributions are submitted in the form of plain text patches, + using `git send-email` or similar. + + Please prefix your subject with "mtd-utils: " to make them easier to spot. + + It may take a few days for a patch to be picked up. This is in part done + intentionally to allow other people to comment on it. If it's been more + than a week and you feel your patch might have been overlooked, please send + a friendly ping, or re-submit the patch series with "[RESEND]" in the subject. + + + There is also a #mtd IRC channel on irc.oftc.net diff --git a/configure.ac b/configure.ac index bcea080..2a79ba8 100644 --- a/configure.ac +++ b/configure.ac @@ -1,29 +1,25 @@ AC_PREREQ([2.60]) -m4_define([RELEASE], 2.1.2) +m4_define([RELEASE], 2.3.0) AC_INIT([mtd-utils], [RELEASE], [linux-mtd@lists.infradead.org], mtd-utils) AC_ARG_ENABLE([unit-tests], [AS_HELP_STRING([--enable-unit-tests], [Compile unit test programs])], - [case "${enableval}" in - yes) AM_CONDITIONAL([UNIT_TESTS], [true]) ;; - no) AM_CONDITIONAL([UNIT_TESTS], [false]) ;; - *) AC_MSG_ERROR([bad value ${enableval} for --enable-unit-tests]) ;; - esac], - [AM_CONDITIONAL([UNIT_TESTS], [false])]) + [], [enable_unit_tests="no"]) -AM_COND_IF([UNIT_TESTS], [: ${CFLAGS=""}], []) +AS_IF([test "x$enable_unit_tests" = "xyes"], [: ${CFLAGS=""}], []) AC_CONFIG_MACRO_DIR([m4]) AM_INIT_AUTOMAKE([foreign subdir-objects dist-bzip2]) AM_SILENT_RULES([yes]) -AC_PROG_LIBTOOL -AC_DISABLE_STATIC AC_PROG_CC AC_PROG_INSTALL - +# autoconf <= 2.63 doesn't have AC_PROG_AR +AC_CHECK_TOOL(AR, ar, :) +AC_PROG_RANLIB +AC_SYS_LARGEFILE m4_ifndef([PKG_PROG_PKG_CONFIG], [m4_fatal([Could not locate the pkg-config autoconf @@ -61,163 +57,110 @@ AC_SUBST([WARN_CFLAGS]) ###### handle configure switches, select dependencies ###### -need_clock_gettime="no" -need_pthread="no" -need_uuid="no" -need_zlib="no" -need_lzo="no" -need_zstd="no" -need_xattr="no" -need_cmocka="no" -need_selinux="no" -need_openssl="no" -need_getrandom="no" +AC_ARG_WITH([tests], + [AS_HELP_STRING([--without-tests], [Compile test programs])], + [], [with_tests="yes"]) -AM_COND_IF([UNIT_TESTS], [ - need_cmocka="yes" -]) +AC_DEFINE_DIR(TESTBINDIR, libexecdir/mtd-utils, + [Path where test and debug programs will be installed]) +AC_ARG_ENABLE([ubihealthd], + [AS_HELP_STRING([--enable-ubihealthd], [Build the ubihealthd program])], + [], [enable_ubihealthd="yes"]) -AC_ARG_ENABLE([tests], - [AS_HELP_STRING([--disable-tests], [Compile test programs])], - [case "${enableval}" in - yes) AM_CONDITIONAL([BUILD_TESTS], [true]) ;; - no) AM_CONDITIONAL([BUILD_TESTS], [false]) ;; - *) AC_MSG_ERROR([bad value ${enableval} for --disable-tests]) ;; - esac], - [AM_CONDITIONAL([BUILD_TESTS], [true])]) - -AM_COND_IF([BUILD_TESTS], [ - need_clock_gettime="yes" - need_pthread="yes" -]) - - -AC_ARG_ENABLE([install-tests], - [AS_HELP_STRING([--enable-install-tests], [Install test programs])], - [case "${enableval}" in - yes) AM_CONDITIONAL([INSTALL_TESTS], [true]) ;; - no) AM_CONDITIONAL([INSTALL_TESTS], [false]) ;; - *) AC_MSG_ERROR([bad value ${enableval} for --enable-install-tests]) ;; - esac], - [AM_CONDITIONAL([INSTALL_TESTS], [false])]) - - -AM_COND_IF([INSTALL_TESTS], - [AC_SUBST(testbindir, ["\$libexecpath"])], - [AC_SUBST(testbindir, ["\".\""])]) - - -AC_ARG_ENABLE([lsmtd], - [AS_HELP_STRING([--disable-lsmtd], [Do not build the lsmtd program])], - [case "${enableval}" in - yes) AM_CONDITIONAL([BUILD_LSMTD], [true]) ;; - no) AM_CONDITIONAL([BUILD_LSMTD], [false]) ;; - *) AC_MSG_ERROR([bad value ${enableval} for --disable-lsmtd]) ;; - esac], - [AM_CONDITIONAL([BUILD_LSMTD], [true])]) +AC_ARG_WITH([lsmtd], + [AS_HELP_STRING([--without-lsmtd], [Do not build the lsmtd program])], + [], [with_lsmtd="yes"]) AC_ARG_WITH([jffs], [AS_HELP_STRING([--without-jffs], [Disable jffsX utilities])], - [case "${withval}" in - yes) AM_CONDITIONAL([BUILD_JFFSX], [true]) ;; - no) AM_CONDITIONAL([BUILD_JFFSX], [false]) ;; - *) AC_MSG_ERROR([bad value ${withval} for --without-jffs]) ;; - esac], - [AM_CONDITIONAL([BUILD_JFFSX], [true])]) + [], [with_jffs="yes"]) AC_ARG_WITH([ubifs], [AS_HELP_STRING([--without-ubifs], [Disable ubifs utilities])], - [case "${withval}" in - yes) AM_CONDITIONAL([BUILD_UBIFS], [true]) ;; - no) AM_CONDITIONAL([BUILD_UBIFS], [false]) ;; - *) AC_MSG_ERROR([bad value ${withval} for --without-ubifs]) ;; - esac], - [AM_CONDITIONAL([BUILD_UBIFS], [true])]) - -AM_COND_IF([BUILD_UBIFS], [ - need_uuid="yes" - need_xattr="yes" - need_zlib="yes" - need_lzo="yes" - need_zstd="yes" - need_openssl="yes" - need_getrandom="yes" -]) + [], [with_ubifs="yes"]) -AM_COND_IF([BUILD_JFFSX], [ - need_xattr="yes" - need_zlib="yes" - need_lzo="yes" -]) +AC_ARG_WITH([zlib], + [AS_HELP_STRING([--with-zlib], [Support zlib deflate compression])], + [], [with_zlib="check"]) AC_ARG_WITH([xattr], - [AS_HELP_STRING([--without-xattr], - [Disable support forextended file attributes])], - [case "${withval}" in - yes) ;; - no) need_xattr="no" ;; - *) AC_MSG_ERROR([bad value ${withval} for --without-xattr]) ;; - esac]) + [AS_HELP_STRING([--with-xattr], [Support extended file attributes])], + [], [with_xattr="check"]) AC_ARG_WITH([lzo], - [AS_HELP_STRING([--without-lzo], [Disable support for LZO compression])], - [case "${withval}" in - yes) ;; - no) need_lzo="no" ;; - *) AC_MSG_ERROR([bad value ${withval} for --without-lzo]) ;; - esac]) + [AS_HELP_STRING([--with-lzo], [Support LZO compression])], + [], [with_lzo="check"]) AC_ARG_WITH([zstd], - [AS_HELP_STRING([--without-zstd], [Disable support for ZSTD compression])], - [case "${withval}" in - yes) ;; - no) need_zstd="no" ;; - *) AC_MSG_ERROR([bad value ${withval} for --without-zstd]) ;; - esac]) + [AS_HELP_STRING([--with-zstd], [Support for ZSTD compression])], + [], [with_zstd="check"]) AC_ARG_WITH([selinux], [AS_HELP_STRING([--with-selinux], - [Enable support for selinux extended attributes])], - [case "${withval}" in - yes) need_selinux="yes";; - no) ;; - *) AC_MSG_ERROR([bad value ${withval} for --with-selinux]) ;; - esac]) + [Support for selinux extended attributes])], + [], [with_selinux="check"]) AC_ARG_WITH([crypto], - [AS_HELP_STRING([--without-crypto], - [Disable support for UBIFS crypto features])], - [case "${withval}" in - yes) ;; - no) need_openssl="no";; - *) AC_MSG_ERROR([bad value ${withval} for --without-crypto]) ;; - esac]) + [AS_HELP_STRING([--with-crypto], [Support for UBIFS crypto features])], + [], [with_crypto="check"]) + +AC_ARG_ENABLE([asan], + [AS_HELP_STRING([--enable-asan], [Support AddressSanitizer debug])], + [], [enable_asan="no"]) ##### search for dependencies ##### +need_clock_gettime="no" +need_pthread="no" +need_uuid="no" +need_cmocka="no" +need_getrandom="no" + clock_gettime_missing="no" pthread_missing="no" uuid_missing="no" -zlib_missing="no" -lzo_missing="no" -zstd_missing="no" -xattr_missing="no" cmocka_missing="no" -selinux_missing="no" -openssl_missing="no" getrandom_missing="no" -if test "x$need_zlib" = "xyes"; then - PKG_CHECK_MODULES(ZLIB, [zlib], [], [zlib_missing="yes"]) -fi +AS_IF([test "x$enable_unit_tests" = "xyes"], [ + need_cmocka="yes" +]) -if test "x$need_selinux" = "xyes"; then - PKG_CHECK_MODULES(LIBSELINUX, [libselinux], [], [selinux_missing="yes"]) -fi +AS_IF([test "x$with_tests" = "xyes"], [ + need_clock_gettime="yes" + need_pthread="yes" +]) + +AS_IF([test "x$enable_ubihealthd" = "xyes"], [ + need_getrandom="yes" +]) + +AC_ARG_VAR([DUMP_STACK_LD], [linker flags for rdynamic]) +AS_IF([test "x$with_ubifs" = "xyes"], [ + need_uuid="yes" + need_getrandom="yes" + DUMP_STACK_LD="-rdynamic" +]) + +AS_IF([test "x$with_zlib" != "xno"], [ + PKG_CHECK_MODULES(ZLIB, [zlib], [with_zlib="yes"], + [AS_IF([test "x$with_zlib" != "xcheck"], + [AC_MSG_ERROR([cannot find zlib])], + [with_zlib="no"])]) +], []) + +AC_ARG_VAR([ASAN_LIBS], [linker flags for lasan]) +AS_IF([test "x$enable_asan" = "xyes"], [ + AC_CHECK_LIB([asan], [_init], [ + ASAN_LIBS="-lasan -fsanitize=address -fsanitize-recover=address -g"], [ + AC_MSG_ERROR([cannot find libasan]) + ]) +]) if test "x$need_uuid" = "xyes"; then - PKG_CHECK_MODULES(UUID, [uuid], [], [uuid_missing="yes"]) + PKG_CHECK_MODULES(UUID, [uuid], [], + [PKG_CHECK_MODULES_STATIC(UUID, [uuid], [], [uuid_missing="yes"])]) fi if test "x$need_clock_gettime" = "xyes"; then @@ -229,34 +172,63 @@ if test "x$need_pthread" = "xyes"; then AX_PTHREAD([], [pthread_missing="yes"]) fi -if test "x$need_lzo" = "xyes"; then - AC_ARG_VAR([LZO_CFLAGS], [C compiler flags for lzo]) - AC_ARG_VAR([LZO_LIBS], [linker flags for lzo]) - AC_CHECK_LIB([lzo2], [lzo1x_1_15_compress], [LZO_LIBS="-llzo2"], - [AC_CHECK_LIB([lzo],[lzo1x_1_15_compress],[LZO_LIBS="-llzo"], - [lzo_missing="yes"] - )] - ) -fi - -if test "x$need_zstd" = "xyes"; then - PKG_CHECK_MODULES([ZSTD], [libzstd],, zstd_missing="yes") -fi - -if test "x$need_xattr" = "xyes"; then - AC_CHECK_HEADERS([sys/xattr.h], [], [xattr_missing="yes"]) - AC_CHECK_HEADERS([sys/acl.h], [], [xattr_missing="yes"]) -fi - -if test "x$need_selinux" = "xyes"; then - AC_CHECK_HEADERS([selinux/selinux.h], [], [selinux_missing="yes"]) - AC_CHECK_HEADERS([selinux/label.h], [], [selinux_missing="yes"]) -fi +AC_ARG_VAR([LZO_CFLAGS], [C compiler flags for lzo]) +AC_ARG_VAR([LZO_LIBS], [linker flags for lzo]) -if test "x$need_openssl" = "xyes"; then - AC_CHECK_HEADER(openssl/rand.h) - PKG_CHECK_MODULES(OPENSSL, [openssl], [], [openssl_missing="yes"]) -fi +AS_IF([test -z "$LZO_LIBS" -a "x$with_lzo" != "xno"], [ + AC_CHECK_LIB([lzo2], [lzo1x_1_15_compress], [LZO_LIBS="-llzo2"], + [AC_CHECK_LIB([lzo],[lzo1x_1_15_compress], + [LZO_LIBS="-llzo"], + [] + )] + ) +], []) + +AS_IF([test -z "$LZO_LIBS"], [AS_IF([test "x$with_lzo" = "xyes"], + [AC_MSG_ERROR([cannot find liblzo])], + [with_lzo="no"])], + [with_lzo="yes"]) + +AS_IF([test "x$with_zstd" != "xno"], [ + PKG_CHECK_MODULES(ZSTD, [libzstd], [with_zstd="yes"], + [AS_IF([test "x$with_zstd" != "xcheck"], + [AC_MSG_ERROR([cannot find zstd])], + [with_zstd="no"])]) +], []) + +AS_IF([test "x$with_xattr" != "xno"], [ + have_xattr="yes" + + AC_CHECK_HEADERS([sys/xattr.h], [], [have_xattr="no"]) + AC_CHECK_HEADERS([sys/acl.h], [], [have_xattr="no"]) + + AS_IF([test "x$with_xattr" != "xcheck" -a "x$have_xattr" = "xno"], + [AC_MSG_ERROR([cannot find xattr/acl headers])], + [with_xattr="$have_xattr"]) +], []) + +AS_IF([test "x$with_selinux" != "xno"], [ + have_selinux="yes" + + PKG_CHECK_MODULES(LIBSELINUX, [libselinux], [], [have_selinux="no"]) + AC_CHECK_HEADERS([selinux/selinux.h], [], [have_selinux="no"]) + AC_CHECK_HEADERS([selinux/label.h], [], [have_selinux="no"]) + + AS_IF([test "x$with_selinux" != "xcheck" -a "x$have_selinux" = "xno"], + [AC_MSG_ERROR([cannot find SELinux libraries])], + [with_selinux="$have_selinux"]) +], []) + +AS_IF([test "x$with_crypto" != "xno"], [ + have_openssl="yes" + + AC_CHECK_HEADERS([openssl/rand.h], [], [have_openssl="no"]) + PKG_CHECK_MODULES(OPENSSL, [openssl], [], [have_openssl="no"]) + + AS_IF([test "x$with_crypto" != "xcheck" -a "x$have_openssl" = "xno"], + [AC_MSG_ERROR([cannot find OpenSSL libraries])], + [with_crypto="$have_openssl"]) +], []) if test "x$need_getrandom" = "xyes"; then AC_CHECK_HEADERS([sys/random.h], [], [getrandom_missing="yes"]) @@ -290,49 +262,9 @@ if test "x$uuid_missing" = "xyes"; then dep_missing="yes" fi -if test "x$zlib_missing" = "xyes"; then - AC_MSG_WARN([cannot find ZLIB library required for mkfs programs]) - AC_MSG_NOTICE([mtd-utils can optionally be built without mkfs.ubifs]) - AC_MSG_NOTICE([mtd-utils can optionally be built without mkfs.jffs2]) - dep_missing="yes" -fi - -if test "x$lzo_missing" = "xyes"; then - AC_MSG_WARN([cannot find LZO library required for mkfs programs]) - AC_MSG_NOTICE([mtd-utils can optionally be built without mkfs.ubifs]) - AC_MSG_NOTICE([mtd-utils can optionally be built without mkfs.jffs2]) - AC_MSG_NOTICE([mtd-utils can optionally be built without LZO support]) - dep_missing="yes" -fi - -if test "x$zstd_missing" = "xyes"; then - AC_MSG_WARN([cannot find ZSTD library required for mkfs program]) - AC_MSG_NOTICE([mtd-utils can optionally be built without mkfs.ubifs]) - AC_MSG_NOTICE([mtd-utils can optionally be built without ZSTD support]) - dep_missing="yes" -fi - -if test "x$xattr_missing" = "xyes"; then - AC_MSG_WARN([cannot find headers for extended attributes]) - AC_MSG_WARN([disabling XATTR support]) - need_xattr="no" -fi - -if test "x$selinux_missing" = "xyes"; then - AC_MSG_WARN([cannot find headers for selinux library]) - AC_MSG_WARN([disabling SELINUX support]) - need_selinux="no" -fi - -if test "x$openssl_missing" = "xyes"; then - AC_MSG_WARN([cannot find headers for OpenSSL library]) - AC_MSG_WARN([disabling OpenSSL support]) - need_openssl="no" -fi - if test "x$getrandom_missing" = "xyes"; then AC_MSG_WARN([cannot find headers for getrandom() function]) - AC_MSG_WARN([disabling UBIFS ubihealthd support]) + AC_MSG_NOTICE([mkfs.ubifs, ubihealthd can optionally be disabled]) need_getrandom="no" fi @@ -348,12 +280,20 @@ fi ##### generate output ##### -AM_CONDITIONAL([WITHOUT_LZO], [test "x$need_lzo" != "xyes"]) -AM_CONDITIONAL([WITHOUT_ZSTD], [test "x$need_zstd" != "xyes"]) -AM_CONDITIONAL([WITHOUT_XATTR], [test "x$need_xattr" != "xyes"]) -AM_CONDITIONAL([WITH_SELINUX], [test "x$need_selinux" == "xyes"]) -AM_CONDITIONAL([WITH_CRYPTO], [test "x$need_openssl" == "xyes"]) -AM_CONDITIONAL([WITH_GETRANDOM], [test "x$need_getrandom" == "xyes"]) +AM_CONDITIONAL([WITH_LZO], [test "x$with_lzo" = "xyes"]) +AM_CONDITIONAL([WITH_ZLIB], [test "x$with_zlib" = "xyes"]) +AM_CONDITIONAL([WITH_ZSTD], [test "x$with_zstd" = "xyes"]) +AM_CONDITIONAL([WITH_XATTR], [test "x$with_xattr" = "xyes"]) +AM_CONDITIONAL([WITH_SELINUX], [test "x$with_selinux" = "xyes"]) +AM_CONDITIONAL([WITH_CRYPTO], [test "x$with_crypto" = "xyes"]) +AM_CONDITIONAL([WITH_UBIHEALTHD], [test "x$enable_ubihealthd" = "xyes"]) +AM_CONDITIONAL([WITH_ASAN], [test "x$enable_asan" = "xyes"]) + +AM_CONDITIONAL([BUILD_UBIFS], [test "x$with_ubifs" = "xyes"]) +AM_CONDITIONAL([BUILD_JFFSX], [test "x$with_jffs" = "xyes"]) +AM_CONDITIONAL([BUILD_LSMTD], [test "x$with_lsmtd" = "xyes"]) +AM_CONDITIONAL([BUILD_TESTS], [test "x$with_tests" = "xyes"]) +AM_CONDITIONAL([UNIT_TESTS], [test "x$enable_unit_tests" = "xyes"]) AC_CHECK_SIZEOF([off_t]) AC_CHECK_SIZEOF([loff_t]) @@ -365,6 +305,54 @@ AC_CONFIG_FILES([tests/fs-tests/fs_help_all.sh tests/fs-tests/stress/fs_stress00.sh tests/fs-tests/stress/fs_stress01.sh tests/ubi-tests/runubitests.sh - tests/ubi-tests/ubi-stress-test.sh]) + tests/ubi-tests/ubi-stress-test.sh + tests/ubifs_tools-tests/lib/common.sh + tests/ubifs_tools-tests/ubifs_tools_run_all.sh + tests/ubifs_tools-tests/fsck_tests/authentication_refuse.sh + tests/ubifs_tools-tests/fsck_tests/cycle_mount_fsck_check.sh + tests/ubifs_tools-tests/fsck_tests/powercut_fsck_mount.sh + tests/ubifs_tools-tests/fsck_tests/cycle_corrupted_fsck_fault_inject.sh + tests/ubifs_tools-tests/fsck_tests/cycle_powercut_mount_fsck.sh + tests/ubifs_tools-tests/fsck_tests/random_corrupted_fsck.sh + tests/ubifs_tools-tests/fsck_tests/fsck_bad_image.sh + tests/ubifs_tools-tests/mkfs_tests/build_fs_from_dir.sh]) AC_OUTPUT([Makefile]) + +AC_MSG_RESULT([ + ${PACKAGE} ${VERSION} + + prefix: ${prefix} + exec prefix: ${exec_prefix} + + runstatedir: ${runstatedir} + bindir: ${bindir} + sbindir: ${sbindir} + libdir: ${libdir} + includedir: ${includedir} + + compiler: ${CC} + cflags: ${CFLAGS} + ldflags: ${LDFLAGS} + asan debug: ${enable_asan} + + lzo support: ${with_lzo} + zlib support: ${with_zlib} + zstd support: ${with_zstd} + xattr/acl support: ${with_xattr} + SELinux support: ${with_selinux} + fscrypt support: ${with_crypto} + + Test programs: ${with_tests} + Unit tests: ${enable_unit_tests} + ubihealthd: ${enable_ubihealthd} + lsmtd: ${with_lsmtd} + jffs2 utils: ${with_jffs} + ubifs utils: ${with_ubifs} + + warnings: + +${WARN_CFLAGS} + + Type 'make' or 'make <utilname>' to compile. +]) diff --git a/include/common.h b/include/common.h index 7270769..303d30d 100644 --- a/include/common.h +++ b/include/common.h @@ -31,8 +31,6 @@ #include <unistd.h> #include <sys/sysmacros.h> -#include "config.h" - #ifndef PROGRAM_NAME # error "You must define PROGRAM_NAME before including this header" #endif @@ -238,6 +236,7 @@ do { \ long long util_get_bytes(const char *str); void util_print_bytes(long long bytes, int bracket); int util_srand(void); +char *mtd_find_dev_node(const char *id); /* * The following helpers are here to avoid compiler complaints about unchecked diff --git a/include/crc32.h b/include/crc32.h index 9c1f742..f5271f3 100644 --- a/include/crc32.h +++ b/include/crc32.h @@ -10,4 +10,9 @@ /* Return a 32-bit CRC of the contents of the buffer */ extern uint32_t mtd_crc32(uint32_t val, const void *ss, int len); +static inline uint32_t crc32(uint32_t val, const void *ss, int len) +{ + return mtd_crc32(val, ss, len); +} + #endif /* __CRC32_H__ */ diff --git a/include/dictionary.h b/include/dictionary.h index c7d1790..f459cfe 100644 --- a/include/dictionary.h +++ b/include/dictionary.h @@ -3,8 +3,6 @@ /** @file dictionary.h @author N. Devillard - @date Sep 2007 - @version $Revision: 1.12 $ @brief Implements a dictionary for string variables. This module implements a simple dictionary object, i.e. a list @@ -13,33 +11,27 @@ */ /*--------------------------------------------------------------------------*/ -/* - $Id: dictionary.h,v 1.12 2007-11-23 21:37:00 ndevilla Exp $ - $Author: ndevilla $ - $Date: 2007-11-23 21:37:00 $ - $Revision: 1.12 $ -*/ - #ifndef _DICTIONARY_H_ #define _DICTIONARY_H_ /*--------------------------------------------------------------------------- - Includes + Includes ---------------------------------------------------------------------------*/ #include <stdio.h> -#include <stdlib.h> -#include <string.h> -#include <unistd.h> + +#ifdef __cplusplus +extern "C" { +#endif /*--------------------------------------------------------------------------- - New types + New types ---------------------------------------------------------------------------*/ /*-------------------------------------------------------------------------*/ /** - @brief Dictionary object + @brief Dictionary object This object contains a list of string/string associations. Each association is identified by a unique string key. Looking up values @@ -48,16 +40,16 @@ */ /*-------------------------------------------------------------------------*/ typedef struct _dictionary_ { - int n ; /** Number of entries in dictionary */ - int size ; /** Storage size */ - char ** val ; /** List of string values */ - char ** key ; /** List of string keys */ - unsigned * hash ; /** List of hash values for keys */ + unsigned n ; /** Number of entries in dictionary */ + size_t size ; /** Storage size */ + char ** val ; /** List of string values */ + char ** key ; /** List of string keys */ + unsigned * hash ; /** List of hash values for keys */ } dictionary ; /*--------------------------------------------------------------------------- - Function prototypes + Function prototypes ---------------------------------------------------------------------------*/ /*-------------------------------------------------------------------------*/ @@ -72,20 +64,20 @@ typedef struct _dictionary_ { by comparing the key itself in last resort. */ /*--------------------------------------------------------------------------*/ -unsigned dictionary_hash(char * key); +unsigned dictionary_hash(const char * key); /*-------------------------------------------------------------------------*/ /** @brief Create a new dictionary object. @param size Optional initial size of the dictionary. - @return 1 newly allocated dictionary objet. + @return 1 newly allocated dictionary object. This function allocates a new dictionary object of given size and returns it. If you do not know in advance (roughly) the number of entries in the dictionary, give size=0. */ /*--------------------------------------------------------------------------*/ -dictionary * dictionary_new(int size); +dictionary * dictionary_new(size_t size); /*-------------------------------------------------------------------------*/ /** @@ -112,7 +104,7 @@ void dictionary_del(dictionary * vd); dictionary object, you should not try to free it or modify it. */ /*--------------------------------------------------------------------------*/ -char * dictionary_get(dictionary * d, char * key, char * def); +const char * dictionary_get(const dictionary * d, const char * key, const char * def); /*-------------------------------------------------------------------------*/ @@ -141,7 +133,7 @@ char * dictionary_get(dictionary * d, char * key, char * def); This function returns non-zero in case of failure. */ /*--------------------------------------------------------------------------*/ -int dictionary_set(dictionary * vd, char * key, char * val); +int dictionary_set(dictionary * vd, const char * key, const char * val); /*-------------------------------------------------------------------------*/ /** @@ -154,7 +146,7 @@ int dictionary_set(dictionary * vd, char * key, char * val); key cannot be found. */ /*--------------------------------------------------------------------------*/ -void dictionary_unset(dictionary * d, char * key); +void dictionary_unset(dictionary * d, const char * key); /*-------------------------------------------------------------------------*/ @@ -169,6 +161,10 @@ void dictionary_unset(dictionary * d, char * key); output file pointers. */ /*--------------------------------------------------------------------------*/ -void dictionary_dump(dictionary * d, FILE * out); +void dictionary_dump(const dictionary * d, FILE * out); + +#ifdef __cplusplus +} +#endif #endif diff --git a/include/libiniparser.h b/include/libiniparser.h index abd77aa..2fe0a3d 100644 --- a/include/libiniparser.h +++ b/include/libiniparser.h @@ -3,43 +3,23 @@ /** @file iniparser.h @author N. Devillard - @date Sep 2007 - @version 3.0 @brief Parser for ini files. */ /*--------------------------------------------------------------------------*/ -/* - $Id: iniparser.h,v 1.24 2007-11-23 21:38:19 ndevilla Exp $ - $Revision: 1.24 $ -*/ - #ifndef _INIPARSER_H_ #define _INIPARSER_H_ /*--------------------------------------------------------------------------- - Includes + Includes ---------------------------------------------------------------------------*/ -#include <stdio.h> -#include <stdlib.h> -#include <string.h> - -/* - * The following #include is necessary on many Unixes but not Linux. - * It is not needed for Windows platforms. - * Uncomment it if needed. - */ -/* #include <unistd.h> */ - #include "dictionary.h" +#include <stdint.h> -/*--------------------------------------------------------------------------- - Macros - ---------------------------------------------------------------------------*/ -/** For backwards compatibility only */ -#define iniparser_getstr(d, k) iniparser_getstring(d, k, NULL) -#define iniparser_setstr iniparser_setstring +#ifdef __cplusplus +extern "C" { +#endif /*-------------------------------------------------------------------------*/ /** @@ -60,7 +40,7 @@ */ /*--------------------------------------------------------------------------*/ -int iniparser_getnsec(dictionary * d); +int iniparser_getnsec(const dictionary * d); /*-------------------------------------------------------------------------*/ @@ -78,7 +58,7 @@ int iniparser_getnsec(dictionary * d); */ /*--------------------------------------------------------------------------*/ -char * iniparser_getsecname(dictionary * d, int n); +const char * iniparser_getsecname(const dictionary * d, int n); /*-------------------------------------------------------------------------*/ @@ -86,21 +66,39 @@ char * iniparser_getsecname(dictionary * d, int n); @brief Save a dictionary to a loadable ini file @param d Dictionary to dump @param f Opened file pointer to dump to - @return void This function dumps a given dictionary into a loadable ini file. It is Ok to specify @c stderr or @c stdout as output files. + + All values are quoted, these charecters are escaped: + + - ' : the quote character (e.g. "String with \"Quotes\"") + - \ : the backslash character (e.g. "C:\\tmp") + */ /*--------------------------------------------------------------------------*/ -void iniparser_dump_ini(dictionary * d, FILE * f); +void iniparser_dump_ini(const dictionary * d, FILE * f); + +/*-------------------------------------------------------------------------*/ +/** + @brief Save a dictionary section to a loadable ini file + @param d Dictionary to dump + @param s Section name of dictionary to dump + @param f Opened file pointer to dump to + + This function dumps a given section of a given dictionary into a loadable ini + file. It is Ok to specify @c stderr or @c stdout as output files. + */ +/*--------------------------------------------------------------------------*/ + +void iniparser_dumpsection_ini(const dictionary * d, const char * s, FILE * f); /*-------------------------------------------------------------------------*/ /** @brief Dump a dictionary to an opened file pointer. @param d Dictionary to dump. @param f Opened file pointer to dump to. - @return void This function prints out the contents of a dictionary, one element by line, onto the provided file pointer. It is OK to specify @c stderr @@ -108,7 +106,7 @@ void iniparser_dump_ini(dictionary * d, FILE * f); purposes mostly. */ /*--------------------------------------------------------------------------*/ -void iniparser_dump(dictionary * d, FILE * f); +void iniparser_dump(const dictionary * d, FILE * f); /*-------------------------------------------------------------------------*/ /** @@ -125,7 +123,7 @@ void iniparser_dump(dictionary * d, FILE * f); the dictionary, do not free or modify it. */ /*--------------------------------------------------------------------------*/ -char * iniparser_getstring(dictionary * d, const char * key, char * def); +const char * iniparser_getstring(const dictionary * d, const char * key, const char * def); /*-------------------------------------------------------------------------*/ /** @@ -154,7 +152,94 @@ char * iniparser_getstring(dictionary * d, const char * key, char * def); Credits: Thanks to A. Becker for suggesting strtol() */ /*--------------------------------------------------------------------------*/ -int iniparser_getint(dictionary * d, const char * key, int notfound); +int iniparser_getint(const dictionary * d, const char * key, int notfound); + +/*-------------------------------------------------------------------------*/ +/** + @brief Get the string associated to a key, convert to an long int + @param d Dictionary to search + @param key Key string to look for + @param notfound Value to return in case of error + @return integer + + This function queries a dictionary for a key. A key as read from an + ini file is given as "section:key". If the key cannot be found, + the notfound value is returned. + + Supported values for integers include the usual C notation + so decimal, octal (starting with 0) and hexadecimal (starting with 0x) + are supported. Examples: + + - "42" -> 42 + - "042" -> 34 (octal -> decimal) + - "0x42" -> 66 (hexa -> decimal) + + Warning: the conversion may overflow in various ways. Conversion is + totally outsourced to strtol(), see the associated man page for overflow + handling. + */ +/*--------------------------------------------------------------------------*/ +long int iniparser_getlongint(const dictionary * d, const char * key, long int notfound); + +/*-------------------------------------------------------------------------*/ +/** + @brief Get the string associated to a key, convert to an int64_t + @param d Dictionary to search + @param key Key string to look for + @param notfound Value to return in case of error + @return integer + + This function queries a dictionary for a key. A key as read from an + ini file is given as "section:key". If the key cannot be found, + the notfound value is returned. + + Supported values for integers include the usual C notation + so decimal, octal (starting with 0) and hexadecimal (starting with 0x) + are supported. Examples: + + - "42" -> 42 + - "042" -> 34 (octal -> decimal) + - "0x42" -> 66 (hexa -> decimal) + + Warning: the conversion may overflow in various ways. Conversion is + totally outsourced to strtoimax(), see the associated man page for overflow + handling. + + This function is usefull on 32bit architectures where `long int` is only + 32bit. + */ +/*--------------------------------------------------------------------------*/ +int64_t iniparser_getint64(const dictionary * d, const char * key, int64_t notfound); + +/*-------------------------------------------------------------------------*/ +/** + @brief Get the string associated to a key, convert to an uint64_t + @param d Dictionary to search + @param key Key string to look for + @param notfound Value to return in case of error + @return integer + + This function queries a dictionary for a key. A key as read from an + ini file is given as "section:key". If the key cannot be found, + the notfound value is returned. + + Supported values for integers include the usual C notation + so decimal, octal (starting with 0) and hexadecimal (starting with 0x) + are supported. Examples: + + - "42" -> 42 + - "042" -> 34 (octal -> decimal) + - "0x42" -> 66 (hexa -> decimal) + + Warning: the conversion may overflow in various ways. Conversion is + totally outsourced to strtoumax(), see the associated man page for overflow + handling. + + This function is usefull on 32bit architectures where `long int` is only + 32bit. + */ +/*--------------------------------------------------------------------------*/ +uint64_t iniparser_getuint64(const dictionary * d, const char * key, uint64_t notfound); /*-------------------------------------------------------------------------*/ /** @@ -188,36 +273,18 @@ int iniparser_getint(dictionary * d, const char * key, int notfound); necessarily have to be 0 or 1. */ /*--------------------------------------------------------------------------*/ -int iniparser_getboolean(dictionary * d, const char * key, int notfound); - - -/*-------------------------------------------------------------------------*/ -/** - @brief Set an entry in a dictionary. - @param ini Dictionary to modify. - @param entry Entry to modify (entry name) - @param val New value to associate to the entry. - @return int 0 if Ok, -1 otherwise. - - If the given entry can be found in the dictionary, it is modified to - contain the provided value. If it cannot be found, -1 is returned. - It is Ok to set val to NULL. - */ -/*--------------------------------------------------------------------------*/ -int iniparser_setstring(dictionary * ini, char * entry, char * val); - +int iniparser_getboolean(const dictionary * d, const char * key, int notfound); /*-------------------------------------------------------------------------*/ /** @brief Delete an entry in a dictionary @param ini Dictionary to modify @param entry Entry to delete (entry name) - @return void If the given entry can be found, it is deleted from the dictionary. */ /*--------------------------------------------------------------------------*/ -void iniparser_unset(dictionary * ini, char * entry); +void iniparser_unset(dictionary * ini, const char * entry); /*-------------------------------------------------------------------------*/ /** @@ -231,7 +298,7 @@ void iniparser_unset(dictionary * ini, char * entry); of querying for the presence of sections in a dictionary. */ /*--------------------------------------------------------------------------*/ -int iniparser_find_entry(dictionary * ini, char * entry) ; +int iniparser_find_entry(const dictionary * ini, const char * entry) ; /*-------------------------------------------------------------------------*/ /** @@ -244,6 +311,17 @@ int iniparser_find_entry(dictionary * ini, char * entry) ; should not be accessed directly, but through accessor functions instead. + Iff the value is a quoted string it supports some escape sequences: + + - \" or ' : the quote character + (e.g. 'String with "Quotes"' or "String with 'Quotes'") + - \ : the backslash character (e.g. "C:\tmp") + + Escape sequences always start with a backslash. Additional escape sequences + might be added in the future. Backslash characters must be escaped. Any other + sequence then those outlined above is invalid and may lead to unpredictable + results. + The returned dictionary must be freed using iniparser_freedict(). */ /*--------------------------------------------------------------------------*/ @@ -251,9 +329,35 @@ dictionary * iniparser_load(const char * ininame); /*-------------------------------------------------------------------------*/ /** + @brief Parse an ini file and return an allocated dictionary object + @param in File to read. + @param ininame Name of the ini file to read (only used for nicer error messages) + @return Pointer to newly allocated dictionary + + This is the parser for ini files. This function is called, providing + the file to be read. It returns a dictionary object that should not + be accessed directly, but through accessor functions instead. + + Iff the value is a quoted string it supports some escape sequences: + + - \" or ' : the quote character + (e.g. 'String with "Quotes"' or "String with 'Quotes'") + - \ : the backslash character (e.g. "C:\tmp") + + Escape sequences always start with a backslash. Additional escape sequences + might be added in the future. Backslash characters must be escaped. Any other + sequence then those outlined above is invalid and may lead to unpredictable + results. + + The returned dictionary must be freed using iniparser_freedict(). + */ +/*--------------------------------------------------------------------------*/ +dictionary * iniparser_load_file(FILE * in, const char * ininame); + +/*-------------------------------------------------------------------------*/ +/** @brief Free all memory associated to an ini dictionary @param d Dictionary to free - @return void Free all memory associated to an ini dictionary. It is mandatory to call this function before the dictionary object @@ -262,4 +366,8 @@ dictionary * iniparser_load(const char * ininame); /*--------------------------------------------------------------------------*/ void iniparser_freedict(dictionary * d); +#ifdef __cplusplus +} +#endif + #endif diff --git a/include/libmissing.h b/include/libmissing.h index 0196033..0fb0cf4 100644 --- a/include/libmissing.h +++ b/include/libmissing.h @@ -1,8 +1,6 @@ #ifndef LIBMISSING_H #define LIBMISSING_H -#include "config.h" - #ifdef HAVE_EXECINFO_H #include <execinfo.h> #endif diff --git a/include/libmtd.h b/include/libmtd.h index cc24af8..f909a16 100644 --- a/include/libmtd.h +++ b/include/libmtd.h @@ -1,5 +1,6 @@ /* * Copyright (C) 2008, 2009 Nokia Corporation + * Copyright 2021 NXP * * 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 @@ -129,7 +130,7 @@ int mtd_get_info(libmtd_t desc, struct mtd_info *info); /** * mtd_get_dev_info - get information about an MTD device. * @desc: MTD library descriptor - * @node: name of the MTD device node + * @node: path of the MTD device node * @mtd: the MTD device information is returned here * * This function gets information about MTD device defined by the @node device @@ -151,6 +152,17 @@ int mtd_get_dev_info(libmtd_t desc, const char *node, struct mtd_dev_info *mtd); int mtd_get_dev_info1(libmtd_t desc, int mtd_num, struct mtd_dev_info *mtd); /** + * mtd_get_dev_info2 - get information about an MTD device. + * @desc: MTD library descriptor + * @name: name of the MTD device + * @mtd: the MTD device information is returned here + * + * This function is identical to 'mtd_get_dev_info()' except that it accepts + * MTD device's name, not MTD character device. + */ +int mtd_get_dev_info2(libmtd_t desc, const char *name, struct mtd_dev_info *mtd); + +/** * mtd_lock - lock eraseblocks. * @desc: MTD library descriptor * @mtd: MTD device description object @@ -175,6 +187,20 @@ int mtd_lock(const struct mtd_dev_info *mtd, int fd, int eb); int mtd_unlock(const struct mtd_dev_info *mtd, int fd, int eb); /** + * mtd_unlock_multi - unlock eraseblocks. + * @desc: MTD library descriptor + * @mtd: MTD device description object + * @fd: MTD device node file descriptor + * @eb: index of first eraseblock to unlock + * @blocks: the number of eraseblocks to unlock + * + * This function unlocks @blocks starting at eraseblock @eb. + * Returns %0 in case of success and %-1 in case of failure. + */ +int mtd_unlock_multi(const struct mtd_dev_info *mtd, int fd, int eb, + int blocks); + +/** * mtd_erase - erase multiple eraseblocks. * @desc: MTD library descriptor * @mtd: MTD device description object diff --git a/include/libubi.h b/include/libubi.h index 46c732a..b5b3d8f 100644 --- a/include/libubi.h +++ b/include/libubi.h @@ -25,6 +25,7 @@ #include <ctype.h> #include <stdint.h> +#include <stdbool.h> #include <mtd/ubi-user.h> #ifdef __cplusplus @@ -53,6 +54,8 @@ typedef void * libubi_t; * @vid_hdr_offset: VID header offset (%0 means default offset and this is what * most of the users want) * @max_beb_per1024: Maximum expected bad eraseblocks per 1024 eraseblocks + * @disable_fm: whether disable fastmap + * @need_resv_pool: whether reserve free pebs for filling pool/wl_pool */ struct ubi_attach_request { @@ -61,6 +64,8 @@ struct ubi_attach_request const char *mtd_dev_node; int vid_hdr_offset; int max_beb_per1024; + bool disable_fm; + bool need_resv_pool; }; /** @@ -426,8 +431,8 @@ int ubi_vol_block_remove(int fd); * @bytes: how many bytes will be written to the volume * * This function initiates UBI volume update and returns %0 in case of success - * and %-1 in case of error. The caller is assumed to write @bytes data to the - * volume @fd afterward. + * and %-1 in case of error (errno is set). The caller is assumed to write + * @bytes data to the volume @fd afterward. */ int ubi_update_start(libubi_t desc, int fd, long long bytes); @@ -482,6 +487,21 @@ int ubi_leb_unmap(int fd, int lnum); */ int ubi_is_mapped(int fd, int lnum); +/** + * ubi_leb_map - map logical eraseblock to a physical eraseblock. + * @fd: volume character device file descriptor + * @lnum: logical eraseblock number + * + * This function maps an un-mapped logical eraseblock @lnum to a physical + * eraseblock. This means, that after a successful invocation of this + * function the logical eraseblock @lnum will be empty (contain only %0xFF + * bytes) and be mapped to a physical eraseblock, even if an unclean reboot + * happens. + * + * This function returns zero in case of success, %-1 in case of failures. + */ +int ubi_leb_map(int fd, int lnum); + #ifdef __cplusplus } #endif diff --git a/include/list.h b/include/list.h new file mode 100644 index 0000000..d26c9d1 --- /dev/null +++ b/include/list.h @@ -0,0 +1,263 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (c) 2006 Silicon Graphics, Inc. + * All Rights Reserved. + */ +#ifndef __LIST_H__ +#define __LIST_H__ + +#include <stddef.h> + +/* + * This undef is here because BSD 4.4 added some LIST_ macros into system + * header file sys/queue.h. This header is included in many other system + * headers and thus causes "macro redefined" warnings. + * + * As OS X is kind of a derivate of BSD, this affects OS X too. + * + * To use our own LIST_ macros (copied from kernel code), we have to + * at first undefine the conflicting system macros. + * + */ +#undef LIST_HEAD +#undef LIST_HEAD_INIT + +/* + * Simple, generic doubly-linked list implementation. + */ + +struct list_head { + struct list_head *next; + struct list_head *prev; +}; + +#define LIST_HEAD_INIT(name) { &(name), &(name) } + +#define LIST_HEAD(name) \ + struct list_head name = LIST_HEAD_INIT(name) + +#define INIT_LIST_HEAD(list) list_head_init(list) +static inline void list_head_init(struct list_head *list) +{ + list->next = list->prev = list; +} + +static inline void list_head_destroy(struct list_head *list) +{ + list->next = list->prev = NULL; +} + +static inline void __list_add(struct list_head *add, + struct list_head *prev, struct list_head *next) +{ + next->prev = add; + add->next = next; + add->prev = prev; + prev->next = add; +} + +static inline void list_add(struct list_head *add, struct list_head *head) +{ + __list_add(add, head, head->next); +} + +static inline void list_add_tail(struct list_head *add, struct list_head *head) +{ + __list_add(add, head->prev, head); +} + +static inline void __list_del(struct list_head *prev, struct list_head *next) +{ + next->prev = prev; + prev->next = next; +} + +static inline void list_del_init(struct list_head *entry) +{ + __list_del(entry->prev, entry->next); + list_head_init(entry); +} + +static inline void list_del(struct list_head *entry) +{ + __list_del(entry->prev, entry->next); +} + +static inline void list_move(struct list_head *list, struct list_head *head) +{ + __list_del(list->prev, list->next); + list_add(list, head); +} + +static inline void list_move_tail(struct list_head *list, struct list_head *head) +{ + __list_del(list->prev, list->next); + list_add_tail(list, head); +} + +/** + * list_is_last - tests whether @list is the last entry in list @head + * @list: the entry to test + * @head: the head of the list + */ +static inline int list_is_last(const struct list_head *list, const struct list_head *head) +{ + return list->next == head; +} + +static inline int list_empty(const 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(struct list_head *list, struct list_head *head) +{ + if (!list_empty(list)) + __list_splice(list, head, head->next); +} + +static inline void list_splice_tail(struct list_head *list, + struct list_head *head) +{ + if (!list_empty(list)) + __list_splice(list, head->prev, head); +} + +static inline void list_splice_init(struct list_head *list, + struct list_head *head) +{ + if (!list_empty(list)) { + __list_splice(list, head, head->next); + list_head_init(list); + } +} + +#define list_entry(ptr, type, member) ({ \ + const typeof( ((type *)0)->member ) *__mptr = (ptr); \ + (type *)( (char *)__mptr - offsetof(type,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_entry((head)->next, typeof(*pos), member); \ + &pos->member != (head); \ + pos = list_entry(pos->member.next, typeof(*pos), member)) + +#define list_for_each_entry_safe(pos, n, head, member) \ + for (pos = list_entry((head)->next, typeof(*pos), member), \ + n = list_entry(pos->member.next, typeof(*pos), member); \ + &pos->member != (head); \ + pos = n, n = list_entry(n->member.next, typeof(*n), member)) + +#define list_first_entry(ptr, type, member) \ + list_entry((ptr)->next, type, member) + +#define container_of(ptr, type, member) ({ \ + const typeof( ((type *)0)->member ) *__mptr = (ptr); \ + (type *)( (char *)__mptr - offsetof(type,member) );}) + +/** + * list_entry_is_head - test if the entry points to the head of the list + * @pos: the type * to cursor + * @head: the head for your list. + * @member: the name of the list_head within the struct. + */ +#define list_entry_is_head(pos, head, member) \ + (&pos->member == (head)) + +typedef int __attribute__((nonnull(2,3))) (*list_cmp_func_t)(void *, + const struct list_head *, const struct list_head *); +__attribute__((nonnull(2,3))) +void list_sort(void *priv, struct list_head *head, list_cmp_func_t cmp); + +/** + * list_splice_tail_init - join two lists and reinitialise the emptied list + * @list: the new list to add. + * @head: the place to add it in the first list. + * + * Each of the lists is a queue. + * The list at @list is reinitialised + */ +static inline void list_splice_tail_init(struct list_head *list, + struct list_head *head) +{ + if (!list_empty(list)) { + __list_splice(list, head->prev, head); + INIT_LIST_HEAD(list); + } +} + +/** + * list_replace - replace old entry by new one + * @old : the element to be replaced + * @new : the new element to insert + * + * If @old was empty, it will be overwritten. + */ +static inline void list_replace(struct list_head *old, + struct list_head *new) +{ + new->next = old->next; + new->next->prev = new; + new->prev = old->prev; + new->prev->next = new; +} + +/** + * list_last_entry - get the last element from a list + * @ptr: the list head to take the element from. + * @type: the type of the struct this is embedded in. + * @member: the name of the list_head within the struct. + * + * Note, that list is expected to be not empty. + */ +#define list_last_entry(ptr, type, member) \ + list_entry((ptr)->prev, type, member) + +/** + * list_prev_entry - get the prev element in list + * @pos: the type * to cursor + * @member: the name of the list_head within the struct. + */ +#define list_prev_entry(pos, member) \ + list_entry((pos)->member.prev, typeof(*(pos)), member) + +/** + * list_next_entry - get the next element in list + * @pos: the type * to cursor + * @member: the name of the list_head within the struct. + */ +#define list_next_entry(pos, member) \ + list_entry((pos)->member.next, typeof(*(pos)), member) + +/** + * list_for_each_entry_reverse - iterate backwards over list of given type. + * @pos: the type * to use as a loop cursor. + * @head: the head for your list. + * @member: the name of the list_head within the struct. + */ +#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)) + +#endif /* __LIST_H__ */ diff --git a/include/mtd/mtd-abi.h b/include/mtd/mtd-abi.h index bcd7496..a54c386 100644 --- a/include/mtd/mtd-abi.h +++ b/include/mtd/mtd-abi.h @@ -201,6 +201,8 @@ struct otp_info { * modes (see "struct mtd_write_req") */ #define MEMWRITE _IOWR('M', 24, struct mtd_write_req) +/* Erase a given range of user data (must be in mode %MTD_FILE_MODE_OTP_USER) */ +#define OTPERASE _IOW('M', 25, struct otp_info) /* * Obsolete legacy interface. Keep it in order not to break userspace diff --git a/include/mtd/ubi-user.h b/include/mtd/ubi-user.h index 707c4f2..bb5c0f9 100644 --- a/include/mtd/ubi-user.h +++ b/include/mtd/ubi-user.h @@ -235,6 +235,8 @@ enum { * @mtd_num: MTD device number to attach * @vid_hdr_offset: VID header offset (use defaults if %0) * @max_beb_per1024: maximum expected number of bad PEB per 1024 PEBs + * @disable_fm: whether disable fastmap + * @need_resv_pool: whether reserve free pebs for filling pool/wl_pool * @padding: reserved for future, not used, has to be zeroed * * This data structure is used to specify MTD device UBI has to attach and the @@ -270,13 +272,19 @@ enum { * eraseblocks for new bad eraseblocks, but attempts to use available * eraseblocks (if any). The accepted range is 0-768. If 0 is given, the * default kernel value of %CONFIG_MTD_UBI_BEB_LIMIT will be used. + * + * If @disable_fm is not zero, ubi doesn't create new fastmap even the module + * param 'fm_autoconvert' is set, and existed old fastmap will be destroyed + * after doing full scanning. */ struct ubi_attach_req { int32_t ubi_num; int32_t mtd_num; int32_t vid_hdr_offset; int16_t max_beb_per1024; - int8_t padding[10]; + int8_t disable_fm; + int8_t need_resv_pool; + int8_t padding[8]; }; /* diff --git a/jffsX-utils/rbtree.h b/include/rbtree.h index 0d77b65..89926e7 100644 --- a/jffsX-utils/rbtree.h +++ b/include/rbtree.h @@ -155,6 +155,38 @@ extern struct rb_node *rb_prev(struct rb_node *); extern struct rb_node *rb_first(struct rb_root *); extern struct rb_node *rb_last(struct rb_root *); +/* Postorder iteration - always visit the parent after its children */ +extern struct rb_node *rb_first_postorder(const struct rb_root *); +extern struct rb_node *rb_next_postorder(const struct rb_node *); + +#define rb_entry_safe(ptr, type, member) \ + ({ typeof(ptr) ____ptr = (ptr); \ + ____ptr ? rb_entry(____ptr, type, member) : NULL; \ + }) + +/** + * rbtree_postorder_for_each_entry_safe - iterate in post-order over rb_root of + * given type allowing the backing memory of @pos to be invalidated + * + * @pos: the 'type *' to use as a loop cursor. + * @n: another 'type *' to use as temporary storage + * @root: 'rb_root *' of the rbtree. + * @field: the name of the rb_node field within 'type'. + * + * rbtree_postorder_for_each_entry_safe() provides a similar guarantee as + * list_for_each_entry_safe() and allows the iteration to continue independent + * of changes to @pos by the body of the loop. + * + * Note, however, that it cannot handle other modifications that re-order the + * rbtree it is iterating over. This includes calling rb_erase() on @pos, as + * rb_erase() may rebalance the tree, causing us to miss some nodes. + */ +#define rbtree_postorder_for_each_entry_safe(pos, n, root, field) \ + for (pos = rb_entry_safe(rb_first_postorder(root), typeof(*pos), field); \ + pos && ({ n = rb_entry_safe(rb_next_postorder(&pos->field), \ + typeof(*pos), field); 1; }); \ + pos = n) + /* Fast replacement of a single node without remove/rebalance/add/rebalance */ extern void rb_replace_node(struct rb_node *victim, struct rb_node *new, struct rb_root *root); diff --git a/jffsX-utils/Makemodule.am b/jffsX-utils/Makemodule.am index fb181de..4ae4c57 100644 --- a/jffsX-utils/Makemodule.am +++ b/jffsX-utils/Makemodule.am @@ -1,37 +1,38 @@ mkfs_jffs2_SOURCES = \ jffsX-utils/mkfs.jffs2.c \ - jffsX-utils/rbtree.h \ - jffsX-utils/compr_zlib.c \ jffsX-utils/compr.h \ - jffsX-utils/rbtree.c \ - jffsX-utils/compr_lzo.c \ jffsX-utils/compr.c \ - jffsX-utils/compr_rtime.c + jffsX-utils/compr_rtime.c \ + jffsX-utils/compr.h \ + jffsX-utils/summary.h \ + include/linux/jffs2.h \ + include/mtd/jffs2-user.h \ + include/list.h \ + include/rbtree.h mkfs_jffs2_LDADD = libmtd.a $(ZLIB_LIBS) $(LZO_LIBS) mkfs_jffs2_CPPFLAGS = $(AM_CPPFLAGS) $(ZLIB_CFLAGS) $(LZO_CFLAGS) -jffs2reader_SOURCES = jffsX-utils/jffs2reader.c +jffs2reader_SOURCES = jffsX-utils/jffs2reader.c include/mtd/jffs2-user.h jffs2reader_LDADD = libmtd.a $(ZLIB_LIBS) $(LZO_LIBS) +jffs2reader_CPPFLAGS = $(AM_CPPFLAGS) $(ZLIB_CFLAGS) $(LZO_CFLAGS) -jffs2dump_SOURCES = jffsX-utils/jffs2dump.c +jffs2dump_SOURCES = jffsX-utils/jffs2dump.c include/mtd/jffs2-user.h +jffs2dump_SOURCES += jffsX-utils/summary.h jffs2dump_LDADD = libmtd.a $(ZLIB_LIBS) $(LZO_LIBS) +jffs2dump_CPPFLAGS = $(AM_CPPFLAGS) $(ZLIB_CFLAGS) $(LZO_CFLAGS) -sumtool_SOURCES = jffsX-utils/sumtool.c +sumtool_SOURCES = jffsX-utils/sumtool.c jffsX-utils/summary.h sumtool_LDADD = libmtd.a -JFFSX_BINS = \ - mkfs.jffs2 jffs2dump jffs2reader sumtool - -JFFSX_MAN = \ - jffsX-utils/mkfs.jffs2.1 - -JFFSX_EXTRA = \ - jffsX-utils/device_table.txt jffsX-utils/mkfs.jffs2.1 +if WITH_LZO +mkfs_jffs2_SOURCES += jffsX-utils/compr_lzo.c +endif -JFFSX_HEADER = \ - jffsX-utils/compr.h jffsX-utils/rbtree.h jffsX-utils/summary.h +if WITH_ZLIB +mkfs_jffs2_SOURCES += jffsX-utils/compr_zlib.c +endif -EXTRA_DIST += $(JFFSX_HEADER) $(JFFSX_EXTRA) +EXTRA_DIST += jffsX-utils/device_table.txt jffsX-utils/mkfs.jffs2.1 -dist_man1_MANS += $(JFFSX_MAN) -sbin_PROGRAMS += $(JFFSX_BINS) +dist_man1_MANS += jffsX-utils/mkfs.jffs2.1 +sbin_PROGRAMS += mkfs.jffs2 jffs2dump jffs2reader sumtool diff --git a/jffsX-utils/compr.c b/jffsX-utils/compr.c index cb4432e..d408ef8 100644 --- a/jffsX-utils/compr.c +++ b/jffsX-utils/compr.c @@ -17,55 +17,6 @@ extern int page_size; -/* LIST IMPLEMENTATION (from linux/list.h) */ - -#define LIST_HEAD_INIT(name) { &(name), &(name) } - -#define LIST_HEAD(name) \ - struct list_head name = LIST_HEAD_INIT(name) - -static inline void __list_add(struct list_head *new, - struct list_head *prev, - struct list_head *next) -{ - next->prev = new; - new->next = next; - new->prev = prev; - prev->next = new; -} - -static inline void list_add(struct list_head *new, struct list_head *head) -{ - __list_add(new, head, head->next); -} - -static inline void list_add_tail(struct list_head *new, struct list_head *head) -{ - __list_add(new, head->prev, head); -} - -static inline void __list_del(struct list_head *prev, struct list_head *next) -{ - next->prev = prev; - prev->next = next; -} - -static inline void list_del(struct list_head *entry) -{ - __list_del(entry->prev, entry->next); - entry->next = (void *) 0; - entry->prev = (void *) 0; -} - -#define list_entry(ptr, type, member) \ - ((type *)((char *)(ptr)-(unsigned long)(&((type *)0)->member))) - -#define list_for_each_entry(pos, head, member) \ - for (pos = list_entry((head)->next, typeof(*pos), member); \ - &pos->member != (head); \ - pos = list_entry(pos->member.next, typeof(*pos), member)) - - /* Available compressors are on this list */ static LIST_HEAD(jffs2_compressor_list); @@ -511,13 +462,13 @@ reinsert: int jffs2_compressors_init(void) { -#ifdef CONFIG_JFFS2_ZLIB +#ifdef WITH_ZLIB jffs2_zlib_init(); #endif #ifdef CONFIG_JFFS2_RTIME jffs2_rtime_init(); #endif -#ifdef CONFIG_JFFS2_LZO +#ifdef WITH_LZO jffs2_lzo_init(); #endif return 0; @@ -528,10 +479,10 @@ int jffs2_compressors_exit(void) #ifdef CONFIG_JFFS2_RTIME jffs2_rtime_exit(); #endif -#ifdef CONFIG_JFFS2_ZLIB +#ifdef WITH_ZLIB jffs2_zlib_exit(); #endif -#ifdef CONFIG_JFFS2_LZO +#ifdef WITH_LZO jffs2_lzo_exit(); #endif return 0; diff --git a/jffsX-utils/compr.h b/jffsX-utils/compr.h index a21e935..6969952 100644 --- a/jffsX-utils/compr.h +++ b/jffsX-utils/compr.h @@ -15,10 +15,9 @@ #include <stdlib.h> #include <stdint.h> #include "linux/jffs2.h" +#include "list.h" -#define CONFIG_JFFS2_ZLIB #define CONFIG_JFFS2_RTIME -#define CONFIG_JFFS2_LZO #define JFFS2_RUBINMIPS_PRIORITY 10 #define JFFS2_DYNRUBIN_PRIORITY 20 @@ -51,10 +50,6 @@ #define KERN_INFO #define KERN_DEBUG -struct list_head { - struct list_head *next, *prev; -}; - void jffs2_set_compression_mode(int mode); int jffs2_get_compression_mode(void); int jffs2_set_compression_mode_name(const char *mode_name); @@ -103,7 +98,7 @@ char *jffs2_stats(void); /* Compressor modules */ /* These functions will be called by jffs2_compressors_init/exit */ -#ifdef CONFIG_JFFS2_ZLIB +#ifdef WITH_ZLIB int jffs2_zlib_init(void); void jffs2_zlib_exit(void); #endif @@ -111,7 +106,7 @@ void jffs2_zlib_exit(void); int jffs2_rtime_init(void); void jffs2_rtime_exit(void); #endif -#ifdef CONFIG_JFFS2_LZO +#ifdef WITH_LZO int jffs2_lzo_init(void); void jffs2_lzo_exit(void); #endif diff --git a/jffsX-utils/compr_lzo.c b/jffsX-utils/compr_lzo.c index 56aa1b4..ddd8d55 100644 --- a/jffsX-utils/compr_lzo.c +++ b/jffsX-utils/compr_lzo.c @@ -24,8 +24,6 @@ #include <stdint.h> #include <stdio.h> #include <string.h> - -#ifndef WITHOUT_LZO #include <asm/types.h> #include <linux/jffs2.h> #include <lzo/lzo1x.h> @@ -121,16 +119,3 @@ void jffs2_lzo_exit(void) free(lzo_compress_buf); free(lzo_mem); } - -#else - -int jffs2_lzo_init(void) -{ - return 0; -} - -void jffs2_lzo_exit(void) -{ -} - -#endif diff --git a/jffsX-utils/jffs2dump.c b/jffsX-utils/jffs2dump.c index d30b59f..b757ebe 100644 --- a/jffsX-utils/jffs2dump.c +++ b/jffsX-utils/jffs2dump.c @@ -757,6 +757,12 @@ int main(int argc, char **argv) // get image length imglen = lseek(fd, 0, SEEK_END); + if (imglen < 0) { + perror(img); + close(fd); + exit(EXIT_FAILURE); + } + lseek (fd, 0, SEEK_SET); data = malloc (imglen); @@ -766,6 +772,13 @@ int main(int argc, char **argv) exit(EXIT_FAILURE); } + if (datsize < 0 || oobsize < 0 || datsize > imglen || (long)datsize + oobsize < 0) { + fprintf(stderr, "Error: invalid datsize/oobsize.\n"); + free(data); + close (fd); + exit(EXIT_FAILURE); + } + if (datsize && oobsize) { int idx = 0; long len = imglen; @@ -777,7 +790,7 @@ int main(int argc, char **argv) read_nocheck (fd, oob, oobsize); idx += datsize; imglen -= oobsize; - len -= datsize + oobsize; + len -= (long)datsize + oobsize; } } else { diff --git a/jffsX-utils/jffs2reader.c b/jffsX-utils/jffs2reader.c index 083500e..548fc8d 100644 --- a/jffsX-utils/jffs2reader.c +++ b/jffsX-utils/jffs2reader.c @@ -75,7 +75,11 @@ BUGS: #include <sys/types.h> #include <sys/stat.h> #include <dirent.h> +#ifdef WITH_ZLIB #include <zlib.h> +#else +typedef unsigned long uLongf; +#endif #include "mtd/jffs2-user.h" #include "common.h" @@ -132,12 +136,13 @@ static void putblock(char *b, size_t bsize, size_t * rsize, bzero(b + *rsize, je32_to_cpu(n->isize) - *rsize); switch (n->compr) { +#ifdef WITH_ZLIB case JFFS2_COMPR_ZLIB: uncompress((Bytef *) b + je32_to_cpu(n->offset), &dlen, (Bytef *) ((char *) n) + sizeof(struct jffs2_raw_inode), (uLongf) je32_to_cpu(n->csize)); break; - +#endif case JFFS2_COMPR_NONE: memcpy(b + je32_to_cpu(n->offset), ((char *) n) + sizeof(struct jffs2_raw_inode), dlen); @@ -336,8 +341,9 @@ static void printdir(char *o, size_t size, struct dir *d, const char *path, d = d->next; continue; } - - filetime = ctime((const time_t *) &(ri->ctime)); + time_t _ctime; + memcpy(&_ctime, &(ri->ctime), sizeof(time_t)); + filetime = ctime(&_ctime); age = time(NULL) - je32_to_cpu(ri->ctime); mode.v32 = ri->mode.m; printf("%s %-4d %-8d %-8d ", mode_string(je32_to_cpu(mode)), @@ -688,6 +694,9 @@ static struct jffs2_raw_dirent *resolvepath0(char *o, size_t size, uint32_t ino, pp = path = xstrdup(p); + if (path == NULL) + return NULL; + if (*path == '/') { path++; ino = 1; diff --git a/jffsX-utils/mkfs.jffs2.1 b/jffsX-utils/mkfs.jffs2.1 index 7c57ddc..75f96f5 100644 --- a/jffsX-utils/mkfs.jffs2.1 +++ b/jffsX-utils/mkfs.jffs2.1 @@ -119,13 +119,13 @@ the kernel can be changed with a #define to accept images of the non-native endianness. Full bi-endian support in the kernel is not planned. -It is unlikely that JFFS2 images are useful except in conjuction +It is unlikely that JFFS2 images are useful except in conjunction with the MTD (Memory Technology Device) drivers in the Linux kernel, since the JFFS2 file system driver in the kernel requires MTD devices. .SH OPTIONS Options that take SIZE arguments can be specified as either -decimal (e.g., 65536), octal (0200000), or hexidecimal (0x1000). +decimal (e.g., 65536), octal (0200000), or hexadecimal (0x1000). .TP .B -p, --pad[=SIZE] Pad output to SIZE bytes with 0xFF. If SIZE is not specified, diff --git a/jffsX-utils/mkfs.jffs2.c b/jffsX-utils/mkfs.jffs2.c index 9cc5eaf..da07b69 100644 --- a/jffsX-utils/mkfs.jffs2.c +++ b/jffsX-utils/mkfs.jffs2.c @@ -65,7 +65,7 @@ #include <ctype.h> #include <time.h> #include <getopt.h> -#ifndef WITHOUT_XATTR +#ifdef WITH_XATTR #include <sys/xattr.h> #include <sys/acl.h> #endif @@ -428,7 +428,7 @@ static int interpret_table_entry(struct filesystem_entry *root, char *line) if (sscanf (line, "%" SCANF_PREFIX "s %c %lo %lu %lu %lu %lu %lu %lu %lu", SCANF_STRING(name), &type, &mode, &uid, &gid, &major, &minor, - &start, &increment, &count) < 0) + &start, &increment, &count) < 2) { return 1; } @@ -978,7 +978,7 @@ static void write_special_file(struct filesystem_entry *e) padword(); } -#ifndef WITHOUT_XATTR +#ifdef WITH_XATTR typedef struct xattr_entry { struct xattr_entry *next; uint32_t xid; @@ -1209,7 +1209,7 @@ static void write_xattr_entry(struct filesystem_entry *e) } } -#else /* WITHOUT_XATTR */ +#else /* WITH_XATTR */ #define write_xattr_entry(x) #endif @@ -1380,7 +1380,7 @@ static struct option long_options[] = { {"test-compression", 0, NULL, 't'}, {"compressor-priority", 1, NULL, 'y'}, {"incremental", 1, NULL, 'i'}, -#ifndef WITHOUT_XATTR +#ifdef WITH_XATTR {"with-xattr", 0, NULL, 1000 }, {"with-selinux", 0, NULL, 1001 }, {"with-posix-acl", 0, NULL, 1002 }, @@ -1401,7 +1401,8 @@ static const char helptext[] = " page size (default: 4KiB)\n" " -e, --eraseblock=SIZE Use erase block size SIZE (default: 64KiB)\n" " -c, --cleanmarker=SIZE Size of cleanmarker (default 12)\n" -" -m, --compr-mode=MODE Select compression mode (default: priority)\n" +" -m, --compression-mode=MODE\n" +" Select compression mode (default: priority)\n" " -x, --disable-compressor=COMPRESSOR_NAME\n" " Disable a compressor\n" " -X, --enable-compressor=COMPRESSOR_NAME\n" @@ -1419,7 +1420,7 @@ static const char helptext[] = " -q, --squash Squash permissions and owners making all files be owned by root\n" " -U, --squash-uids Squash owners making all files be owned by root\n" " -P, --squash-perms Squash permissions on all files\n" -#ifndef WITHOUT_XATTR +#ifdef WITH_XATTR " --with-xattr stuff all xattr entries into image\n" " --with-selinux stuff only SELinux Labels into jffs2 image\n" " --with-posix-acl stuff only POSIX ACL entries into jffs2 image\n" @@ -1744,7 +1745,7 @@ int main(int argc, char **argv) sys_errmsg_die("cannot open (incremental) file"); } break; -#ifndef WITHOUT_XATTR +#ifdef WITH_XATTR case 1000: /* --with-xattr */ enable_xattr |= (1 << JFFS2_XPREFIX_USER) | (1 << JFFS2_XPREFIX_SECURITY) diff --git a/lib/Makemodule.am b/lib/Makemodule.am index 5bee5b6..441532d 100644 --- a/lib/Makemodule.am +++ b/lib/Makemodule.am @@ -1,27 +1,49 @@ libmtd_a_SOURCES = \ lib/libmtd.c \ + include/libmtd.h \ lib/libfec.c \ + include/libfec.h \ lib/common.c \ + include/common.h \ + lib/list_sort.c \ + include/list.h \ + lib/rbtree.c \ + include/rbtree.h \ lib/libcrc32.c \ + include/crc32.h \ lib/libmtd_legacy.c \ - lib/libmtd_int.h + lib/libmtd_int.h \ + include/xalloc.h \ + include/mtd/mtd-user.h \ + include/mtd/mtd-abi.h libmissing_a_SOURCES = \ - lib/execinfo.c + lib/execinfo.c \ + include/libmissing.h libubi_a_SOURCES = \ lib/libubi.c \ - lib/libubi_int.h + lib/libubi_int.h \ + include/libubi.h \ + include/mtd/ubi-user.h libubigen_a_SOURCES = \ - lib/libubigen.c + lib/libubigen.c \ + include/libubigen.h \ + include/mtd_swab.h \ + include/mtd/ubi-media.h libscan_a_SOURCES = \ - lib/libscan.c + lib/libscan.c \ + include/libscan.h \ + include/mtd_swab.h \ + include/mtd/ubi-media.h libiniparser_a_SOURCES = \ lib/libiniparser.c \ - lib/dictionary.c + include/libiniparser.h \ + lib/dictionary.c \ + include/dictionary.h EXTRA_DIST += lib/LICENSE.libiniparser diff --git a/lib/common.c b/lib/common.c index 8041878..e278593 100644 --- a/lib/common.c +++ b/lib/common.c @@ -33,6 +33,9 @@ #include <stdlib.h> #include <unistd.h> #include "common.h" +#include "libmtd.h" + +#define MTD_DEV_PATT "/dev/mtd%d" /** * get_multiplier - convert size specifier to an integer multiplier. @@ -162,3 +165,46 @@ int util_srand(void) srand(seed); return 0; } + +/** + * mtd_find_dev_node - Find the device node for an MTD + * @id: Identifier for the MTD. this can be the device node itself, or + * "mtd:<name>" to look up MTD by name + * + * This is a helper function to convert MTD device identifiers into their + * device node. + * + * Returns a pointer to a string containing the device node that must be + * free'd, or NULL on failure. + */ +char *mtd_find_dev_node(const char *id) +{ + struct mtd_dev_info info; + struct libmtd_t *lib_mtd; + char *node; + int ret; + + if (strncmp(id, "mtd:", 4)) { + /* Assume @id is the device node */ + return strdup(id); + } + + /* Search for MTD matching name */ + id += 4; + + lib_mtd = libmtd_open(); + if (!lib_mtd) + return NULL; + + ret = mtd_get_dev_info2(lib_mtd, id, &info); + libmtd_close(lib_mtd); + if (ret < 0) + return NULL; + + node = malloc(strlen(MTD_DEV_PATT) + 20); + if (!node) + return NULL; + + sprintf(node, MTD_DEV_PATT, info.mtd_num); + return node; +} diff --git a/lib/dictionary.c b/lib/dictionary.c index f4b7468..85dfdc0 100644 --- a/lib/dictionary.c +++ b/lib/dictionary.c @@ -1,10 +1,8 @@ /*-------------------------------------------------------------------------*/ /** - @file dictionary.c - @author N. Devillard - @date Sep 2007 - @version $Revision: 1.27 $ - @brief Implements a dictionary for string variables. + @file dictionary.c + @author N. Devillard + @brief Implements a dictionary for string variables. This module implements a simple dictionary object, i.e. a list of string/string associations. This object is useful to store e.g. @@ -12,48 +10,22 @@ */ /*--------------------------------------------------------------------------*/ -/* - $Id: dictionary.c,v 1.27 2007-11-23 21:39:18 ndevilla Exp $ - $Revision: 1.27 $ -*/ /*--------------------------------------------------------------------------- - Includes + Includes ---------------------------------------------------------------------------*/ #include "dictionary.h" #include <stdio.h> #include <stdlib.h> #include <string.h> -#include <unistd.h> - -/** Maximum value size for integers and doubles. */ -#define MAXVALSZ 1024 /** Minimal allocated number of entries in a dictionary */ -#define DICTMINSZ 128 - -/** Invalid key token */ -#define DICT_INVALID_KEY ((char*)-1) +#define DICTMINSZ 128 /*--------------------------------------------------------------------------- - Private functions + Private functions ---------------------------------------------------------------------------*/ -/* Doubles the allocated size associated to a pointer */ -/* 'size' is the current allocated size. */ -static void * mem_double(void * ptr, int size) -{ - void * newptr ; - - newptr = calloc(2*size, 1); - if (newptr==NULL) { - return NULL ; - } - memcpy(newptr, ptr, size); - free(ptr); - return newptr ; -} - /*-------------------------------------------------------------------------*/ /** @brief Duplicate a string @@ -64,26 +36,71 @@ static void * mem_double(void * ptr, int size) for systems that do not have it. */ /*--------------------------------------------------------------------------*/ -static char * xstrdup(char * s) +static char * xstrdup(const char * s) { char * t ; + size_t len ; if (!s) return NULL ; - t = malloc(strlen(s)+1) ; + + len = strlen(s) + 1 ; + t = (char*) malloc(len) ; if (t) { - strcpy(t,s); + memcpy(t, s, len) ; } return t ; } +/*-------------------------------------------------------------------------*/ +/** + @brief Double the size of the dictionary + @param d Dictionary to grow + @return This function returns non-zero in case of failure + */ +/*--------------------------------------------------------------------------*/ +static int dictionary_grow(dictionary * d) +{ + char ** new_val ; + char ** new_key ; + unsigned * new_hash ; + + new_val = (char**) calloc(d->size * 2, sizeof *d->val); + new_key = (char**) calloc(d->size * 2, sizeof *d->key); + new_hash = (unsigned*) calloc(d->size * 2, sizeof *d->hash); + if (!new_val || !new_key || !new_hash) { + /* An allocation failed, leave the dictionary unchanged */ + if (new_val) + free(new_val); + if (new_key) + free(new_key); + if (new_hash) + free(new_hash); + return -1 ; + } + /* Initialize the newly allocated space */ + memcpy(new_val, d->val, d->size * sizeof(char *)); + memcpy(new_key, d->key, d->size * sizeof(char *)); + memcpy(new_hash, d->hash, d->size * sizeof(unsigned)); + /* Delete previous data */ + free(d->val); + free(d->key); + free(d->hash); + /* Actually update the dictionary */ + d->size *= 2 ; + d->val = new_val; + d->key = new_key; + d->hash = new_hash; + return 0 ; +} + /*--------------------------------------------------------------------------- - Function codes + Function codes ---------------------------------------------------------------------------*/ /*-------------------------------------------------------------------------*/ /** - @brief Compute the hash key for a string. - @param key Character string to use for key. - @return 1 unsigned int on at least 32 bits. + @brief Compute the hash key for a string. + @param key Character string to use for key. + @return 1 unsigned int on at least 32 bits. This hash function has been taken from an Article in Dr Dobbs Journal. This is normally a collision-free function, distributing keys evenly. @@ -91,86 +108,97 @@ static char * xstrdup(char * s) by comparing the key itself in last resort. */ /*--------------------------------------------------------------------------*/ -unsigned dictionary_hash(char * key) +unsigned dictionary_hash(const char * key) { - int len ; - unsigned hash ; - int i ; - - len = strlen(key); - for (hash=0, i=0 ; i<len ; i++) { - hash += (unsigned)key[i] ; - hash += (hash<<10); - hash ^= (hash>>6) ; - } - hash += (hash <<3); - hash ^= (hash >>11); - hash += (hash <<15); - return hash ; + size_t len ; + unsigned hash ; + size_t i ; + + if (!key) + return 0 ; + + len = strlen(key); + for (hash=0, i=0 ; i<len ; i++) { + hash += (unsigned)key[i] ; + hash += (hash<<10); + hash ^= (hash>>6) ; + } + hash += (hash <<3); + hash ^= (hash >>11); + hash += (hash <<15); + return hash ; } /*-------------------------------------------------------------------------*/ /** - @brief Create a new dictionary object. - @param size Optional initial size of the dictionary. - @return 1 newly allocated dictionary objet. + @brief Create a new dictionary object. + @param size Optional initial size of the dictionary. + @return 1 newly allocated dictionary object. This function allocates a new dictionary object of given size and returns it. If you do not know in advance (roughly) the number of entries in the dictionary, give size=0. */ -/*--------------------------------------------------------------------------*/ -dictionary * dictionary_new(int size) +/*-------------------------------------------------------------------------*/ +dictionary * dictionary_new(size_t size) { - dictionary * d ; - - /* If no size was specified, allocate space for DICTMINSZ */ - if (size<DICTMINSZ) size=DICTMINSZ ; - - if (!(d = (dictionary *)calloc(1, sizeof(dictionary)))) { - return NULL; - } - d->size = size ; - d->val = (char **)calloc(size, sizeof(char*)); - d->key = (char **)calloc(size, sizeof(char*)); - d->hash = (unsigned int *)calloc(size, sizeof(unsigned)); - return d ; + dictionary * d ; + + /* If no size was specified, allocate space for DICTMINSZ */ + if (size<DICTMINSZ) size=DICTMINSZ ; + + d = (dictionary*) calloc(1, sizeof *d) ; + + if (d) { + d->size = size ; + d->val = (char**) calloc(size, sizeof *d->val); + d->key = (char**) calloc(size, sizeof *d->key); + d->hash = (unsigned*) calloc(size, sizeof *d->hash); + if (!d->size || !d->val || !d->hash) { + free((void *) d->size); + free((void *) d->val); + free((void *) d->hash); + free(d); + d = NULL; + } + } + return d ; } /*-------------------------------------------------------------------------*/ /** - @brief Delete a dictionary object - @param d dictionary object to deallocate. - @return void + @brief Delete a dictionary object + @param d dictionary object to deallocate. + @return void Deallocate a dictionary object and all memory associated to it. */ /*--------------------------------------------------------------------------*/ void dictionary_del(dictionary * d) { - int i ; - - if (d==NULL) return ; - for (i=0 ; i<d->size ; i++) { - if (d->key[i]!=NULL) - free(d->key[i]); - if (d->val[i]!=NULL) - free(d->val[i]); - } - free(d->val); - free(d->key); - free(d->hash); - free(d); - return ; + size_t i ; + + if (d==NULL) return ; + for (i=0 ; i<d->size ; i++) { + if (d->key[i]!=NULL) + free(d->key[i]); + if (d->val[i]!=NULL) + free(d->val[i]); + } + free(d->val); + free(d->key); + free(d->hash); + free(d); + return ; } /*-------------------------------------------------------------------------*/ /** - @brief Get a value from a dictionary. - @param d dictionary object to search. - @param key Key to look for in the dictionary. + @brief Get a value from a dictionary. + @param d dictionary object to search. + @param key Key to look for in the dictionary. @param def Default value to return if key not found. - @return 1 pointer to internally allocated character string. + @return 1 pointer to internally allocated character string. This function locates a key in a dictionary and returns a pointer to its value, or the passed 'def' pointer if no such key can be found in @@ -178,24 +206,27 @@ void dictionary_del(dictionary * d) dictionary object, you should not try to free it or modify it. */ /*--------------------------------------------------------------------------*/ -char * dictionary_get(dictionary * d, char * key, char * def) +const char * dictionary_get(const dictionary * d, const char * key, const char * def) { - unsigned hash ; - int i ; + unsigned hash ; + size_t i ; - hash = dictionary_hash(key); - for (i=0 ; i<d->size ; i++) { + if(d == NULL || key == NULL) + return def ; + + hash = dictionary_hash(key); + for (i=0 ; i<d->size ; i++) { if (d->key[i]==NULL) continue ; /* Compare hash */ - if (hash==d->hash[i]) { + if (hash==d->hash[i]) { /* Compare string, to avoid hash collisions */ if (!strcmp(key, d->key[i])) { - return d->val[i] ; - } - } - } - return def ; + return d->val[i] ; + } + } + } + return def ; } /*-------------------------------------------------------------------------*/ @@ -224,96 +255,87 @@ char * dictionary_get(dictionary * d, char * key, char * def) This function returns non-zero in case of failure. */ /*--------------------------------------------------------------------------*/ -int dictionary_set(dictionary * d, char * key, char * val) +int dictionary_set(dictionary * d, const char * key, const char * val) { - int i ; - unsigned hash ; + size_t i ; + unsigned hash ; - if (d==NULL || key==NULL) return -1 ; + if (d==NULL || key==NULL) return -1 ; - /* Compute hash for this key */ - hash = dictionary_hash(key) ; - /* Find if value is already in dictionary */ - if (d->n>0) { - for (i=0 ; i<d->size ; i++) { + /* Compute hash for this key */ + hash = dictionary_hash(key) ; + /* Find if value is already in dictionary */ + if (d->n>0) { + for (i=0 ; i<d->size ; i++) { if (d->key[i]==NULL) continue ; - if (hash==d->hash[i]) { /* Same hash value */ - if (!strcmp(key, d->key[i])) { /* Same key */ - /* Found a value: modify and return */ - if (d->val[i]!=NULL) - free(d->val[i]); - d->val[i] = val ? xstrdup(val) : NULL ; + if (hash==d->hash[i]) { /* Same hash value */ + if (!strcmp(key, d->key[i])) { /* Same key */ + /* Found a value: modify and return */ + if (d->val[i]!=NULL) + free(d->val[i]); + d->val[i] = (val ? xstrdup(val) : NULL); /* Value has been modified: return */ - return 0 ; - } - } - } - } - /* Add a new value */ - /* See if dictionary needs to grow */ - if (d->n==d->size) { - - /* Reached maximum size: reallocate dictionary */ - d->val = (char **)mem_double(d->val, d->size * sizeof(char*)) ; - d->key = (char **)mem_double(d->key, d->size * sizeof(char*)) ; - d->hash = (unsigned int *)mem_double(d->hash, d->size * sizeof(unsigned)) ; - if ((d->val==NULL) || (d->key==NULL) || (d->hash==NULL)) { - /* Cannot grow dictionary */ - return -1 ; + return 0 ; + } + } } - /* Double size */ - d->size *= 2 ; - } + } + /* Add a new value */ + /* See if dictionary needs to grow */ + if (d->n==d->size) { + /* Reached maximum size: reallocate dictionary */ + if (dictionary_grow(d) != 0) + return -1; + } - /* Insert key in the first empty slot */ - for (i=0 ; i<d->size ; i++) { - if (d->key[i]==NULL) { - /* Add key here */ - break ; - } + /* Insert key in the first empty slot. Start at d->n and wrap at + d->size. Because d->n < d->size this will necessarily + terminate. */ + for (i=d->n ; d->key[i] ; ) { + if(++i == d->size) i = 0; } - /* Copy key */ - d->key[i] = xstrdup(key); - d->val[i] = val ? xstrdup(val) : NULL ; - d->hash[i] = hash; - d->n ++ ; - return 0 ; + /* Copy key */ + d->key[i] = xstrdup(key); + d->val[i] = (val ? xstrdup(val) : NULL) ; + d->hash[i] = hash; + d->n ++ ; + return 0 ; } /*-------------------------------------------------------------------------*/ /** - @brief Delete a key in a dictionary - @param d dictionary object to modify. - @param key Key to remove. + @brief Delete a key in a dictionary + @param d dictionary object to modify. + @param key Key to remove. @return void This function deletes a key in a dictionary. Nothing is done if the key cannot be found. */ /*--------------------------------------------------------------------------*/ -void dictionary_unset(dictionary * d, char * key) +void dictionary_unset(dictionary * d, const char * key) { - unsigned hash ; - int i ; + unsigned hash ; + size_t i ; - if (key == NULL) { - return; - } + if (key == NULL || d == NULL) { + return; + } - hash = dictionary_hash(key); - for (i=0 ; i<d->size ; i++) { + hash = dictionary_hash(key); + for (i=0 ; i<d->size ; i++) { if (d->key[i]==NULL) continue ; /* Compare hash */ - if (hash==d->hash[i]) { + if (hash==d->hash[i]) { /* Compare string, to avoid hash collisions */ if (!strcmp(key, d->key[i])) { /* Found key */ break ; - } - } - } + } + } + } if (i>=d->size) /* Key not found */ return ; @@ -331,75 +353,31 @@ void dictionary_unset(dictionary * d, char * key) /*-------------------------------------------------------------------------*/ /** - @brief Dump a dictionary to an opened file pointer. - @param d Dictionary to dump - @param f Opened file pointer. - @return void + @brief Dump a dictionary to an opened file pointer. + @param d Dictionary to dump + @param f Opened file pointer. + @return void Dumps a dictionary onto an opened file pointer. Key pairs are printed out as @c [Key]=[Value], one per line. It is Ok to provide stdout or stderr as output file pointers. */ /*--------------------------------------------------------------------------*/ -void dictionary_dump(dictionary * d, FILE * out) +void dictionary_dump(const dictionary * d, FILE * out) { - int i ; - - if (d==NULL || out==NULL) return ; - if (d->n<1) { - fprintf(out, "empty dictionary\n"); - return ; - } - for (i=0 ; i<d->size ; i++) { + size_t i ; + + if (d==NULL || out==NULL) return ; + if (d->n<1) { + fprintf(out, "empty dictionary\n"); + return ; + } + for (i=0 ; i<d->size ; i++) { if (d->key[i]) { fprintf(out, "%20s\t[%s]\n", d->key[i], d->val[i] ? d->val[i] : "UNDEF"); } - } - return ; -} - - -/* Test code */ -#ifdef TESTDIC -#define NVALS 20000 -int main(int argc, char *argv[]) -{ - dictionary * d ; - char * val ; - int i ; - char cval[90] ; - - /* Allocate dictionary */ - printf("allocating...\n"); - d = dictionary_new(0); - - /* Set values in dictionary */ - printf("setting %d values...\n", NVALS); - for (i=0 ; i<NVALS ; i++) { - sprintf(cval, "%04d", i); - dictionary_set(d, cval, "salut"); - } - printf("getting %d values...\n", NVALS); - for (i=0 ; i<NVALS ; i++) { - sprintf(cval, "%04d", i); - val = dictionary_get(d, cval, DICT_INVALID_KEY); - if (val==DICT_INVALID_KEY) { - printf("cannot get value for key [%s]\n", cval); - } - } - printf("unsetting %d values...\n", NVALS); - for (i=0 ; i<NVALS ; i++) { - sprintf(cval, "%04d", i); - dictionary_unset(d, cval); - } - if (d->n != 0) { - printf("error deleting values\n"); } - printf("deallocating...\n"); - dictionary_del(d); - return 0 ; + return ; } -#endif -/* vim: set ts=4 et sw=4 tw=75 */ diff --git a/lib/libiniparser.c b/lib/libiniparser.c index a6ddcc7..4b21b34 100644 --- a/lib/libiniparser.c +++ b/lib/libiniparser.c @@ -3,19 +3,16 @@ /** @file iniparser.c @author N. Devillard - @date Sep 2007 - @version 3.0 @brief Parser for ini files. */ /*--------------------------------------------------------------------------*/ -/* - $Id: iniparser.c,v 2.18 2008-01-03 18:35:39 ndevilla Exp $ - $Revision: 2.18 $ - $Date: 2008-01-03 18:35:39 $ -*/ /*---------------------------- Includes ------------------------------------*/ #include <ctype.h> -#include <libiniparser.h> +#include <stdarg.h> +#include <stdlib.h> +#include <string.h> +#include <inttypes.h> +#include "libiniparser.h" /*---------------------------- Defines -------------------------------------*/ #define ASCIILINESZ (1024) @@ -38,68 +35,101 @@ typedef enum _line_status_ { /*-------------------------------------------------------------------------*/ /** - @brief Convert a string to lowercase. - @param s String to convert. - @return ptr to statically allocated string. - - This function returns a pointer to a statically allocated string - containing a lowercased version of the input string. Do not free - or modify the returned string! Since the returned string is statically - allocated, it will be modified at each function call (not re-entrant). + @brief Convert a string to lowercase. + @param in String to convert. + @param out Output buffer. + @param len Size of the out buffer. + @return ptr to the out buffer or NULL if an error occured. + + This function convert a string into lowercase. + At most len - 1 elements of the input string will be converted. */ /*--------------------------------------------------------------------------*/ -static char * strlwc(const char * s) +static const char * strlwc(const char * in, char *out, unsigned len) { - static char l[ASCIILINESZ+1]; - int i ; + unsigned i ; - if (s==NULL) return NULL ; - memset(l, 0, ASCIILINESZ+1); + if (in==NULL || out == NULL || len==0) return NULL ; i=0 ; - while (s[i] && i<ASCIILINESZ) { - l[i] = (char)tolower((int)s[i]); + while (in[i] != '\0' && i < len-1) { + out[i] = (char)tolower((int)in[i]); i++ ; } - l[ASCIILINESZ]=(char)0; - return l ; + out[i] = '\0'; + return out ; +} + +/*-------------------------------------------------------------------------*/ +/** + @brief Duplicate a string + @param s String to duplicate + @return Pointer to a newly allocated string, to be freed with free() + + This is a replacement for strdup(). This implementation is provided + for systems that do not have it. + */ +/*--------------------------------------------------------------------------*/ +static char * xstrdup(const char * s) +{ + char * t ; + size_t len ; + if (!s) + return NULL ; + + len = strlen(s) + 1 ; + t = (char*) malloc(len) ; + if (t) { + memcpy(t, s, len) ; + } + return t ; } /*-------------------------------------------------------------------------*/ /** - @brief Remove blanks at the beginning and the end of a string. - @param s String to parse. - @return ptr to statically allocated string. - - This function returns a pointer to a statically allocated string, - which is identical to the input string, except that all blank - characters at the end and the beg. of the string have been removed. - Do not free or modify the returned string! Since the returned string - is statically allocated, it will be modified at each function call - (not re-entrant). + @brief Remove blanks at the beginning and the end of a string. + @param str String to parse and alter. + @return unsigned New size of the string. */ /*--------------------------------------------------------------------------*/ -static char * strstrip(char * s) +static unsigned strstrip(char * s) { - static char l[ASCIILINESZ+1]; - char * last ; - - if (s==NULL) return NULL ; - - while (isspace((int)*s) && *s) s++; - memset(l, 0, ASCIILINESZ+1); - strcpy(l, s); - last = l + strlen(l); - while (last > l) { - if (!isspace((int)*(last-1))) - break ; - last -- ; - } - *last = (char)0; - return (char*)l ; + char *last = NULL ; + char *dest = s; + + if (s==NULL) return 0; + + last = s + strlen(s); + while (isspace((int)*s) && *s) s++; + while (last > s) { + if (!isspace((int)*(last-1))) + break ; + last -- ; + } + *last = (char)0; + + memmove(dest,s,last - s + 1); + return last - s; } /*-------------------------------------------------------------------------*/ /** + @brief Default error callback for iniparser: wraps `fprintf(stderr, ...)`. + */ +/*--------------------------------------------------------------------------*/ +static int default_error_callback(const char *format, ...) +{ + int ret; + va_list argptr; + va_start(argptr, format); + ret = vfprintf(stderr, format, argptr); + va_end(argptr); + return ret; +} + +static int (*iniparser_error_callback)(const char*, ...) = default_error_callback; + +/*-------------------------------------------------------------------------*/ +/** @brief Get number of sections in a dictionary @param d Dictionary to examine @return int Number of sections found in dictionary @@ -116,9 +146,9 @@ static char * strstrip(char * s) This function returns -1 in case of error. */ /*--------------------------------------------------------------------------*/ -int iniparser_getnsec(dictionary * d) +int iniparser_getnsec(const dictionary * d) { - int i ; + size_t i ; int nsec ; if (d==NULL) return -1 ; @@ -147,9 +177,9 @@ int iniparser_getnsec(dictionary * d) This function returns NULL in case of error. */ /*--------------------------------------------------------------------------*/ -char * iniparser_getsecname(dictionary * d, int n) +const char * iniparser_getsecname(const dictionary * d, int n) { - int i ; + size_t i ; int foundsec ; if (d==NULL || n<0) return NULL ; @@ -182,9 +212,9 @@ char * iniparser_getsecname(dictionary * d, int n) purposes mostly. */ /*--------------------------------------------------------------------------*/ -void iniparser_dump(dictionary * d, FILE * f) +void iniparser_dump(const dictionary * d, FILE * f) { - int i ; + size_t i ; if (d==NULL || f==NULL) return ; for (i=0 ; i<d->size ; i++) { @@ -199,6 +229,26 @@ void iniparser_dump(dictionary * d, FILE * f) return ; } +static void escape_value(char *escaped, char *value) { + char c; + int v = 0; + int e = 0; + + if(!escaped || !value) + return; + + while((c = value[v]) != '\0') { + if(c == '\\' || c == '"') { + escaped[e] = '\\'; + e++; + } + escaped[e] = c; + v++; + e++; + } + escaped[e] = '\0'; +} + /*-------------------------------------------------------------------------*/ /** @brief Save a dictionary to a loadable ini file @@ -210,13 +260,12 @@ void iniparser_dump(dictionary * d, FILE * f) It is Ok to specify @c stderr or @c stdout as output files. */ /*--------------------------------------------------------------------------*/ -void iniparser_dump_ini(dictionary * d, FILE * f) +void iniparser_dump_ini(const dictionary * d, FILE * f) { - int i, j ; - char keym[ASCIILINESZ+1]; - int nsec ; - char * secname ; - int seclen ; + size_t i ; + size_t nsec ; + const char * secname ; + char escaped[ASCIILINESZ+1] = ""; if (d==NULL || f==NULL) return ; @@ -226,24 +275,50 @@ void iniparser_dump_ini(dictionary * d, FILE * f) for (i=0 ; i<d->size ; i++) { if (d->key[i]==NULL) continue ; - fprintf(f, "%s = %s\n", d->key[i], d->val[i]); + escape_value(escaped, d->val[i]); + fprintf(f, "%s = \"%s\"\n", d->key[i], escaped); } return ; } for (i=0 ; i<nsec ; i++) { secname = iniparser_getsecname(d, i) ; - seclen = (int)strlen(secname); - fprintf(f, "\n[%s]\n", secname); - sprintf(keym, "%s:", secname); - for (j=0 ; j<d->size ; j++) { - if (d->key[j]==NULL) - continue ; - if (!strncmp(d->key[j], keym, seclen+1)) { - fprintf(f, - "%-30s = %s\n", - d->key[j]+seclen+1, - d->val[j] ? d->val[j] : ""); - } + iniparser_dumpsection_ini(d, secname, f); + } + fprintf(f, "\n"); + return ; +} + +/*-------------------------------------------------------------------------*/ +/** + @brief Save a dictionary section to a loadable ini file + @param d Dictionary to dump + @param s Section name of dictionary to dump + @param f Opened file pointer to dump to + @return void + + This function dumps a given section of a given dictionary into a loadable ini + file. It is Ok to specify @c stderr or @c stdout as output files. + */ +/*--------------------------------------------------------------------------*/ +void iniparser_dumpsection_ini(const dictionary * d, const char * s, FILE * f) +{ + size_t j ; + char keym[ASCIILINESZ+1]; + int seclen ; + char escaped[ASCIILINESZ+1] = ""; + + if (d==NULL || f==NULL) return ; + if (! iniparser_find_entry(d, s)) return ; + + seclen = (int)strlen(s); + fprintf(f, "\n[%s]\n", s); + sprintf(keym, "%s:", s); + for (j=0 ; j<d->size ; j++) { + if (d->key[j]==NULL) + continue ; + if (!strncmp(d->key[j], keym, seclen+1)) { + escape_value(escaped, d->val[j]); + fprintf(f, "%-30s = \"%s\"\n", d->key[j]+seclen+1, escaped); } } fprintf(f, "\n"); @@ -265,26 +340,27 @@ void iniparser_dump_ini(dictionary * d, FILE * f) the dictionary, do not free or modify it. */ /*--------------------------------------------------------------------------*/ -char * iniparser_getstring(dictionary * d, const char * key, char * def) +const char * iniparser_getstring(const dictionary * d, const char * key, const char * def) { - char * lc_key ; - char * sval ; + const char * lc_key ; + const char * sval ; + char tmp_str[ASCIILINESZ+1]; if (d==NULL || key==NULL) return def ; - lc_key = strlwc(key); + lc_key = strlwc(key, tmp_str, sizeof(tmp_str)); sval = dictionary_get(d, lc_key, def); return sval ; } /*-------------------------------------------------------------------------*/ /** - @brief Get the string associated to a key, convert to an int + @brief Get the string associated to a key, convert to an long int @param d Dictionary to search @param key Key string to look for @param notfound Value to return in case of error - @return integer + @return long integer This function queries a dictionary for a key. A key as read from an ini file is given as "section:key". If the key cannot be found, @@ -305,13 +381,65 @@ char * iniparser_getstring(dictionary * d, const char * key, char * def) Credits: Thanks to A. Becker for suggesting strtol() */ /*--------------------------------------------------------------------------*/ -int iniparser_getint(dictionary * d, const char * key, int notfound) +long int iniparser_getlongint(const dictionary * d, const char * key, long int notfound) { - char * str ; + const char * str ; str = iniparser_getstring(d, key, INI_INVALID_KEY); - if (str==INI_INVALID_KEY) return notfound ; - return (int)strtol(str, NULL, 0); + if (str==NULL || str==INI_INVALID_KEY) return notfound ; + return strtol(str, NULL, 0); +} + +int64_t iniparser_getint64(const dictionary * d, const char * key, int64_t notfound) +{ + const char * str ; + + str = iniparser_getstring(d, key, INI_INVALID_KEY); + if (str==NULL || str==INI_INVALID_KEY) return notfound ; + return strtoimax(str, NULL, 0); +} + +uint64_t iniparser_getuint64(const dictionary * d, const char * key, uint64_t notfound) +{ + const char * str ; + + str = iniparser_getstring(d, key, INI_INVALID_KEY); + if (str==NULL || str==INI_INVALID_KEY) return notfound ; + return strtoumax(str, NULL, 0); +} + + + +/*-------------------------------------------------------------------------*/ +/** + @brief Get the string associated to a key, convert to an int + @param d Dictionary to search + @param key Key string to look for + @param notfound Value to return in case of error + @return integer + + This function queries a dictionary for a key. A key as read from an + ini file is given as "section:key". If the key cannot be found, + the notfound value is returned. + + Supported values for integers include the usual C notation + so decimal, octal (starting with 0) and hexadecimal (starting with 0x) + are supported. Examples: + + "42" -> 42 + "042" -> 34 (octal -> decimal) + "0x42" -> 66 (hexa -> decimal) + + Warning: the conversion may overflow in various ways. Conversion is + totally outsourced to strtol(), see the associated man page for overflow + handling. + + Credits: Thanks to A. Becker for suggesting strtol() + */ +/*--------------------------------------------------------------------------*/ +int iniparser_getint(const dictionary * d, const char * key, int notfound) +{ + return (int)iniparser_getlongint(d, key, notfound); } /*-------------------------------------------------------------------------*/ @@ -346,13 +474,13 @@ int iniparser_getint(dictionary * d, const char * key, int notfound) necessarily have to be 0 or 1. */ /*--------------------------------------------------------------------------*/ -int iniparser_getboolean(dictionary * d, const char * key, int notfound) +int iniparser_getboolean(const dictionary * d, const char * key, int notfound) { - char * c ; - int ret ; + int ret ; + const char * c ; c = iniparser_getstring(d, key, INI_INVALID_KEY); - if (c==INI_INVALID_KEY) return notfound ; + if (c==NULL || c==INI_INVALID_KEY) return notfound ; if (c[0]=='y' || c[0]=='Y' || c[0]=='1' || c[0]=='t' || c[0]=='T') { ret = 1 ; } else if (c[0]=='n' || c[0]=='N' || c[0]=='0' || c[0]=='f' || c[0]=='F') { @@ -375,10 +503,7 @@ int iniparser_getboolean(dictionary * d, const char * key, int notfound) of querying for the presence of sections in a dictionary. */ /*--------------------------------------------------------------------------*/ -int iniparser_find_entry( - dictionary * ini, - char * entry -) +int iniparser_find_entry(const dictionary * ini, const char * entry) { int found=0 ; if (iniparser_getstring(ini, entry, INI_INVALID_KEY)!=INI_INVALID_KEY) { @@ -397,14 +522,53 @@ int iniparser_find_entry( If the given entry can be found, it is deleted from the dictionary. */ /*--------------------------------------------------------------------------*/ -void iniparser_unset(dictionary * ini, char * entry) +void iniparser_unset(dictionary * ini, const char * entry) { - dictionary_unset(ini, strlwc(entry)); + char tmp_str[ASCIILINESZ+1]; + dictionary_unset(ini, strlwc(entry, tmp_str, sizeof(tmp_str))); +} + +static void parse_quoted_value(char *value, char quote) { + char c; + char *quoted; + int q = 0, v = 0; + int esc = 0; + + if(!value) + return; + + quoted = xstrdup(value); + + if(!quoted) { + iniparser_error_callback("iniparser: memory allocation failure\n"); + goto end_of_value; + } + + while((c = quoted[q]) != '\0') { + if(!esc) { + if(c == '\\') { + esc = 1; + q++; + continue; + } + + if(c == quote) { + goto end_of_value; + } + } + esc = 0; + value[v] = c; + v++; + q++; + } +end_of_value: + value[v] = '\0'; + free(quoted); } /*-------------------------------------------------------------------------*/ /** - @brief Load a single line from an INI file + @brief Load a single line from an INI file @param input_line Input line, may be concatenated multi-line input @param section Output space to store section @param key Output space to store key @@ -413,38 +577,54 @@ void iniparser_unset(dictionary * ini, char * entry) */ /*--------------------------------------------------------------------------*/ static line_status iniparser_line( - char * input_line, + const char * input_line, char * section, char * key, char * value) { line_status sta ; - char line[ASCIILINESZ+1]; - int len ; + char * line = NULL; + size_t len ; + int d_quote; - strcpy(line, strstrip(input_line)); - len = (int)strlen(line); + line = xstrdup(input_line); + len = strstrip(line); sta = LINE_UNPROCESSED ; if (len<1) { /* Empty line */ sta = LINE_EMPTY ; - } else if (line[0]=='#') { + } else if (line[0]=='#' || line[0]==';') { /* Comment line */ sta = LINE_COMMENT ; } else if (line[0]=='[' && line[len-1]==']') { - /* Section name */ - sscanf(line, "[%[^]]", section); - strcpy(section, strstrip(section)); - strcpy(section, strlwc(section)); + /* Section name without opening square bracket */ + sscanf(line, "[%[^\n]", section); + len = strlen(section); + /* Section name without closing square bracket */ + if(section[len-1] == ']') + { + section[len-1] = '\0'; + } + strstrip(section); + strlwc(section, section, len); sta = LINE_SECTION ; - } else if (sscanf (line, "%[^=] = \"%[^\"]\"", key, value) == 2 - || sscanf (line, "%[^=] = '%[^\']'", key, value) == 2 - || sscanf (line, "%[^=] = %[^;#]", key, value) == 2) { - /* Usual key=value, with or without comments */ - strcpy(key, strstrip(key)); - strcpy(key, strlwc(key)); - strcpy(value, strstrip(value)); + } else if ((d_quote = sscanf (line, "%[^=] = \"%[^\n]\"", key, value)) == 2 + || sscanf (line, "%[^=] = '%[^\n]'", key, value) == 2) { + /* Usual key=value with quotes, with or without comments */ + strstrip(key); + strlwc(key, key, len); + if(d_quote == 2) + parse_quoted_value(value, '"'); + else + parse_quoted_value(value, '\''); + /* Don't strip spaces from values surrounded with quotes */ + sta = LINE_VALUE ; + } else if (sscanf (line, "%[^=] = %[^;#]", key, value) == 2) { + /* Usual key=value without quotes, with or without comments */ + strstrip(key); + strlwc(key, key, len); + strstrip(value); /* * sscanf cannot handle '' or "" as empty values * this is done here @@ -461,56 +641,51 @@ static line_status iniparser_line( * key=; * key=# */ - strcpy(key, strstrip(key)); - strcpy(key, strlwc(key)); + strstrip(key); + strlwc(key, key, len); value[0]=0 ; sta = LINE_VALUE ; } else { /* Generate syntax error */ sta = LINE_ERROR ; } + + free(line); return sta ; } /*-------------------------------------------------------------------------*/ /** @brief Parse an ini file and return an allocated dictionary object - @param ininame Name of the ini file to read. + @param in File to read. + @param ininame Name of the ini file to read (only used for nicer error messages) @return Pointer to newly allocated dictionary This is the parser for ini files. This function is called, providing - the name of the file to be read. It returns a dictionary object that - should not be accessed directly, but through accessor functions - instead. + the file to be read. It returns a dictionary object that should not + be accessed directly, but through accessor functions instead. The returned dictionary must be freed using iniparser_freedict(). */ /*--------------------------------------------------------------------------*/ -dictionary * iniparser_load(const char * ininame) +dictionary * iniparser_load_file(FILE * in, const char * ininame) { - FILE * in ; - char line [ASCIILINESZ+1] ; char section [ASCIILINESZ+1] ; char key [ASCIILINESZ+1] ; - char tmp [ASCIILINESZ+1] ; + char tmp [(ASCIILINESZ * 2) + 2] ; char val [ASCIILINESZ+1] ; int last=0 ; int len ; int lineno=0 ; int errs=0; + int mem_err=0; dictionary * dict ; - if ((in=fopen(ininame, "r"))==NULL) { - fprintf(stderr, "iniparser: cannot open %s\n", ininame); - return NULL ; - } - dict = dictionary_new(0) ; if (!dict) { - fclose(in); return NULL ; } @@ -523,14 +698,15 @@ dictionary * iniparser_load(const char * ininame) while (fgets(line+last, ASCIILINESZ-last, in)!=NULL) { lineno++ ; len = (int)strlen(line)-1; + if (len<=0) + continue; /* Safety check against buffer overflows */ - if (line[len]!='\n') { - fprintf(stderr, - "iniparser: input line too long in %s (%d)\n", - ininame, - lineno); + if (line[len]!='\n' && !feof(in)) { + iniparser_error_callback( + "iniparser: input line too long in %s (%d)\n", + ininame, + lineno); dictionary_del(dict); - fclose(in); return NULL ; } /* Get rid of \n and spaces at end of line */ @@ -539,6 +715,9 @@ dictionary * iniparser_load(const char * ininame) line[len]=0 ; len-- ; } + if (len < 0) { /* Line was entirely \n and/or spaces */ + len = 0; + } /* Detect multi-line */ if (line[len]=='\\') { /* Multi-line value */ @@ -553,19 +732,20 @@ dictionary * iniparser_load(const char * ininame) break ; case LINE_SECTION: - errs = dictionary_set(dict, section, NULL); + mem_err = dictionary_set(dict, section, NULL); break ; case LINE_VALUE: sprintf(tmp, "%s:%s", section, key); - errs = dictionary_set(dict, tmp, val) ; + mem_err = dictionary_set(dict, tmp, val); break ; case LINE_ERROR: - fprintf(stderr, "iniparser: syntax error in %s (%d):\n", - ininame, - lineno); - fprintf(stderr, "-> %s\n", line); + iniparser_error_callback( + "iniparser: syntax error in %s (%d):\n-> %s\n", + ininame, + lineno, + line); errs++ ; break; @@ -574,8 +754,8 @@ dictionary * iniparser_load(const char * ininame) } memset(line, 0, ASCIILINESZ); last=0; - if (errs<0) { - fprintf(stderr, "iniparser: memory allocation failure\n"); + if (mem_err<0) { + iniparser_error_callback("iniparser: memory allocation failure\n"); break ; } } @@ -583,10 +763,40 @@ dictionary * iniparser_load(const char * ininame) dictionary_del(dict); dict = NULL ; } + return dict ; +} + +/*-------------------------------------------------------------------------*/ +/** + @brief Parse an ini file and return an allocated dictionary object + @param ininame Name of the ini file to read. + @return Pointer to newly allocated dictionary + + This is the parser for ini files. This function is called, providing + the name of the file to be read. It returns a dictionary object that + should not be accessed directly, but through accessor functions + instead. + + The returned dictionary must be freed using iniparser_freedict(). + */ +/*--------------------------------------------------------------------------*/ +dictionary * iniparser_load(const char * ininame) +{ + FILE * in ; + dictionary * dict ; + + if ((in=fopen(ininame, "r"))==NULL) { + iniparser_error_callback("iniparser: cannot open %s\n", ininame); + return NULL ; + } + + dict = iniparser_load_file(in, ininame); fclose(in); + return dict ; } + /*-------------------------------------------------------------------------*/ /** @brief Free all memory associated to an ini dictionary @@ -602,5 +812,3 @@ void iniparser_freedict(dictionary * d) { dictionary_del(d); } - -/* vim: set ts=4 et sw=4 tw=75 */ diff --git a/lib/libmtd.c b/lib/libmtd.c index 9d8d0e8..f588e09 100644 --- a/lib/libmtd.c +++ b/lib/libmtd.c @@ -1,6 +1,7 @@ /* * Copyright (c) International Business Machines Corp., 2006 * Copyright (C) 2009 Nokia Corporation + * Copyright 2021 NXP * * 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 @@ -426,7 +427,7 @@ static int type_str2int(const char *str) /** * dev_node2num - find MTD device number by its character device node. * @lib: MTD library descriptor - * @node: name of the MTD device node + * @node: path of the MTD device node * @mtd_num: MTD device number is returned here * * This function returns %0 in case of success and %-1 in case of failure. @@ -476,6 +477,58 @@ static int dev_node2num(struct libmtd *lib, const char *node, int *mtd_num) } /** + * dev_name2num - find MTD device number by its MTD name + * @lib: MTD library descriptor + * @name: name of the MTD device + * @mtd_num: MTD device number is returned here + * + * This function returns %0 in case of success and %-1 in case of failure. + */ +static int dev_name2num(struct libmtd *lib, const char *name, int *mtd_num) +{ + struct mtd_info info; + char name2[MTD_NAME_MAX + 1]; + int i, mtd_num_tmp = -1; + + if (mtd_get_info((libmtd_t *)lib, &info)) + return -1; + + for (i = info.lowest_mtd_num; i <= info.highest_mtd_num; i++) { + int ret; + + ret = dev_read_data(lib->mtd_name, i, name2, + MTD_NAME_MAX + 1); + if (ret < 0) { + if (errno == ENOENT) + continue; + if (!errno) + break; + return -1; + } + name2[ret - 1] = '\0'; + + if (!strcmp(name, name2)) { + // Device name collision + if (mtd_num_tmp >= 0) { + errmsg("Multiple MTD's found matching name %s", name); + errno = ENODEV; + return -1; + } + + mtd_num_tmp = i; + } + } + + if (mtd_num_tmp < 0) { + errno = ENODEV; + return -1; + } + + *mtd_num = mtd_num_tmp; + return 0; +} + +/** * sysfs_is_supported - check whether the MTD sub-system supports MTD. * @lib: MTD library descriptor * @@ -791,7 +844,10 @@ int mtd_get_dev_info1(libmtd_t desc, int mtd_num, struct mtd_dev_info *mtd) return -1; mtd->writable = !!(ret & MTD_WRITEABLE); - mtd->eb_cnt = mtd->size / mtd->eb_size; + if ((ret & MTD_NO_ERASE) && (mtd->eb_size == 0)) + mtd->eb_cnt = 1; + else + mtd->eb_cnt = mtd->size / mtd->eb_size; mtd->type = type_str2int(mtd->type_str); mtd->bb_allowed = !!(mtd->type == MTD_NANDFLASH || mtd->type == MTD_MLCNANDFLASH); @@ -813,6 +869,20 @@ int mtd_get_dev_info(libmtd_t desc, const char *node, struct mtd_dev_info *mtd) return mtd_get_dev_info1(desc, mtd_num, mtd); } +int mtd_get_dev_info2(libmtd_t desc, const char *name, struct mtd_dev_info *mtd) +{ + int mtd_num; + struct libmtd *lib = (struct libmtd *)desc; + + if (!lib->sysfs_supported) + return legacy_get_dev_info2(name, mtd); + + if (dev_name2num(lib, name, &mtd_num)) + return -1; + + return mtd_get_dev_info1(desc, mtd_num, mtd); +} + static inline int mtd_ioctl_error(const struct mtd_dev_info *mtd, int eb, const char *sreq) { @@ -831,8 +901,8 @@ static int mtd_valid_erase_block(const struct mtd_dev_info *mtd, int eb) return 0; } -static int mtd_xlock(const struct mtd_dev_info *mtd, int fd, int eb, int req, - const char *sreq) +static int mtd_xlock(const struct mtd_dev_info *mtd, int fd, int eb, + int blocks, int req, const char *sreq) { int ret; struct erase_info_user ei; @@ -841,8 +911,14 @@ static int mtd_xlock(const struct mtd_dev_info *mtd, int fd, int eb, int req, if (ret) return ret; + if (blocks > 1) { + ret = mtd_valid_erase_block(mtd, eb + blocks - 1); + if (ret) + return ret; + } + ei.start = eb * mtd->eb_size; - ei.length = mtd->eb_size; + ei.length = mtd->eb_size * blocks; ret = ioctl(fd, req, &ei); if (ret < 0) @@ -850,16 +926,23 @@ static int mtd_xlock(const struct mtd_dev_info *mtd, int fd, int eb, int req, return 0; } -#define mtd_xlock(mtd, fd, eb, req) mtd_xlock(mtd, fd, eb, req, #req) +#define mtd_xlock(mtd, fd, eb, blocks, req) \ + mtd_xlock(mtd, fd, eb, blocks, req, #req) int mtd_lock(const struct mtd_dev_info *mtd, int fd, int eb) { - return mtd_xlock(mtd, fd, eb, MEMLOCK); + return mtd_xlock(mtd, fd, eb, 1, MEMLOCK); } int mtd_unlock(const struct mtd_dev_info *mtd, int fd, int eb) { - return mtd_xlock(mtd, fd, eb, MEMUNLOCK); + return mtd_xlock(mtd, fd, eb, 1, MEMUNLOCK); +} + +int mtd_unlock_multi(const struct mtd_dev_info *mtd, int fd, int eb, + int blocks) +{ + return mtd_xlock(mtd, fd, eb, blocks, MEMUNLOCK); } int mtd_erase_multi(libmtd_t desc, const struct mtd_dev_info *mtd, diff --git a/lib/libmtd_int.h b/lib/libmtd_int.h index c0514d2..79200f7 100644 --- a/lib/libmtd_int.h +++ b/lib/libmtd_int.h @@ -106,6 +106,7 @@ int legacy_dev_present(int mtd_num); int legacy_mtd_get_info(struct mtd_info *info); int legacy_get_dev_info(const char *node, struct mtd_dev_info *mtd); int legacy_get_dev_info1(int dev_num, struct mtd_dev_info *mtd); +int legacy_get_dev_info2(const char *name, struct mtd_dev_info *mtd); int legacy_get_mtd_oobavail(const char *node); int legacy_get_mtd_oobavail1(int mtd_num); diff --git a/lib/libmtd_legacy.c b/lib/libmtd_legacy.c index 4eb4a70..e0ecf49 100644 --- a/lib/libmtd_legacy.c +++ b/lib/libmtd_legacy.c @@ -428,3 +428,41 @@ int legacy_get_dev_info1(int mtd_num, struct mtd_dev_info *mtd) sprintf(node, MTD_DEV_PATT, mtd_num); return legacy_get_dev_info(node, mtd); } + +/** + * legacy_get_dev_info2 - legacy version of `mtd_get_dev_info2()` + * @name: name of the MTD device + * @mtd: the MTD device information is returned here + * + * This function is similar to 'mtd_get_dev_info2()' and has the same + * conventions. + */ +int legacy_get_dev_info2(const char *name, struct mtd_dev_info *mtd) +{ + struct proc_parse_info pi; + int ret, mtd_num = -1; + + ret = proc_parse_start(&pi); + if (ret) + return -1; + + while (proc_parse_next(&pi)) { + if (!strcmp(name, pi.name)) { + // Device name collision + if (mtd_num >= 0) { + errmsg("Multiple MTD's found matching name %s", name); + errno = ENODEV; + return -1; + } + + mtd_num = pi.mtd_num; + } + } + + if (mtd_num < 0) { + errno = ENODEV; + return -1; + } + + return legacy_get_dev_info1(mtd_num, mtd); +} diff --git a/lib/libubi.c b/lib/libubi.c index baaca2f..86736dd 100644 --- a/lib/libubi.c +++ b/lib/libubi.c @@ -767,6 +767,8 @@ int ubi_attach(libubi_t desc, const char *node, struct ubi_attach_request *req) r.ubi_num = req->dev_num; r.mtd_num = req->mtd_num; r.vid_hdr_offset = req->vid_hdr_offset; + r.disable_fm = req->disable_fm ? 1 : 0; + r.need_resv_pool = req->need_resv_pool ? 1 : 0; if (req->max_beb_per1024) { /* @@ -1362,3 +1364,13 @@ int ubi_is_mapped(int fd, int lnum) { return ioctl(fd, UBI_IOCEBISMAP, &lnum); } + +int ubi_leb_map(int fd, int lnum) +{ + struct ubi_map_req r; + + memset(&r, 0, sizeof(struct ubi_map_req)); + r.lnum = lnum; + + return ioctl(fd, UBI_IOCEBMAP, &r); +} diff --git a/lib/list_sort.c b/lib/list_sort.c new file mode 100644 index 0000000..d873438 --- /dev/null +++ b/lib/list_sort.c @@ -0,0 +1,246 @@ +// SPDX-License-Identifier: GPL-2.0 +#include "list.h" + +/* + * Returns a list organized in an intermediate format suited + * to chaining of merge() calls: null-terminated, no reserved or + * sentinel head node, "prev" links not maintained. + */ +__attribute__((nonnull(2,3,4))) +static struct list_head *merge(void *priv, list_cmp_func_t cmp, + struct list_head *a, struct list_head *b) +{ + struct list_head *head, **tail = &head; + + for (;;) { + /* if equal, take 'a' -- important for sort stability */ + if (cmp(priv, a, b) <= 0) { + *tail = a; + tail = &a->next; + a = a->next; + if (!a) { + *tail = b; + break; + } + } else { + *tail = b; + tail = &b->next; + b = b->next; + if (!b) { + *tail = a; + break; + } + } + } + return head; +} + +/* + * Combine final list merge with restoration of standard doubly-linked + * list structure. This approach duplicates code from merge(), but + * runs faster than the tidier alternatives of either a separate final + * prev-link restoration pass, or maintaining the prev links + * throughout. + */ +__attribute__((nonnull(2,3,4,5))) +static void merge_final(void *priv, list_cmp_func_t cmp, struct list_head *head, + struct list_head *a, struct list_head *b) +{ + struct list_head *tail = head; + unsigned int count = 0; + + for (;;) { + /* if equal, take 'a' -- important for sort stability */ + if (cmp(priv, a, b) <= 0) { + tail->next = a; + a->prev = tail; + tail = a; + a = a->next; + if (!a) + break; + } else { + tail->next = b; + b->prev = tail; + tail = b; + b = b->next; + if (!b) { + b = a; + break; + } + } + } + + /* Finish linking remainder of list b on to tail */ + tail->next = b; + do { + /* + * If the merge is highly unbalanced (e.g. the input is + * already sorted), this loop may run many iterations. + * Continue callbacks to the client even though no + * element comparison is needed, so the client's cmp() + * routine can invoke cond_resched() periodically. + */ + if (!++count) + cmp(priv, b, b); + b->prev = tail; + tail = b; + b = b->next; + } while (b); + + /* And the final links to make a circular doubly-linked list */ + tail->next = head; + head->prev = tail; +} + +/** + * list_sort - sort a list + * @priv: private data, opaque to list_sort(), passed to @cmp + * @head: the list to sort + * @cmp: the elements comparison function + * + * The comparison function @cmp must return > 0 if @a should sort after + * @b ("@a > @b" if you want an ascending sort), and <= 0 if @a should + * sort before @b *or* their original order should be preserved. It is + * always called with the element that came first in the input in @a, + * and list_sort is a stable sort, so it is not necessary to distinguish + * the @a < @b and @a == @b cases. + * + * This is compatible with two styles of @cmp function: + * - The traditional style which returns <0 / =0 / >0, or + * - Returning a boolean 0/1. + * The latter offers a chance to save a few cycles in the comparison + * (which is used by e.g. plug_ctx_cmp() in block/blk-mq.c). + * + * A good way to write a multi-word comparison is:: + * + * if (a->high != b->high) + * return a->high > b->high; + * if (a->middle != b->middle) + * return a->middle > b->middle; + * return a->low > b->low; + * + * + * This mergesort is as eager as possible while always performing at least + * 2:1 balanced merges. Given two pending sublists of size 2^k, they are + * merged to a size-2^(k+1) list as soon as we have 2^k following elements. + * + * Thus, it will avoid cache thrashing as long as 3*2^k elements can + * fit into the cache. Not quite as good as a fully-eager bottom-up + * mergesort, but it does use 0.2*n fewer comparisons, so is faster in + * the common case that everything fits into L1. + * + * + * The merging is controlled by "count", the number of elements in the + * pending lists. This is beautifully simple code, but rather subtle. + * + * Each time we increment "count", we set one bit (bit k) and clear + * bits k-1 .. 0. Each time this happens (except the very first time + * for each bit, when count increments to 2^k), we merge two lists of + * size 2^k into one list of size 2^(k+1). + * + * This merge happens exactly when the count reaches an odd multiple of + * 2^k, which is when we have 2^k elements pending in smaller lists, + * so it's safe to merge away two lists of size 2^k. + * + * After this happens twice, we have created two lists of size 2^(k+1), + * which will be merged into a list of size 2^(k+2) before we create + * a third list of size 2^(k+1), so there are never more than two pending. + * + * The number of pending lists of size 2^k is determined by the + * state of bit k of "count" plus two extra pieces of information: + * + * - The state of bit k-1 (when k == 0, consider bit -1 always set), and + * - Whether the higher-order bits are zero or non-zero (i.e. + * is count >= 2^(k+1)). + * + * There are six states we distinguish. "x" represents some arbitrary + * bits, and "y" represents some arbitrary non-zero bits: + * 0: 00x: 0 pending of size 2^k; x pending of sizes < 2^k + * 1: 01x: 0 pending of size 2^k; 2^(k-1) + x pending of sizes < 2^k + * 2: x10x: 0 pending of size 2^k; 2^k + x pending of sizes < 2^k + * 3: x11x: 1 pending of size 2^k; 2^(k-1) + x pending of sizes < 2^k + * 4: y00x: 1 pending of size 2^k; 2^k + x pending of sizes < 2^k + * 5: y01x: 2 pending of size 2^k; 2^(k-1) + x pending of sizes < 2^k + * (merge and loop back to state 2) + * + * We gain lists of size 2^k in the 2->3 and 4->5 transitions (because + * bit k-1 is set while the more significant bits are non-zero) and + * merge them away in the 5->2 transition. Note in particular that just + * before the 5->2 transition, all lower-order bits are 11 (state 3), + * so there is one list of each smaller size. + * + * When we reach the end of the input, we merge all the pending + * lists, from smallest to largest. If you work through cases 2 to + * 5 above, you can see that the number of elements we merge with a list + * of size 2^k varies from 2^(k-1) (cases 3 and 5 when x == 0) to + * 2^(k+1) - 1 (second merge of case 5 when x == 2^(k-1) - 1). + */ +__attribute__((nonnull(2,3))) +void list_sort(void *priv, struct list_head *head, list_cmp_func_t cmp) +{ + struct list_head *list = head->next, *pending = NULL; + size_t count = 0; /* Count of pending */ + + if (list == head->prev) /* Zero or one elements */ + return; + + /* Convert to a null-terminated singly-linked list. */ + head->prev->next = NULL; + + /* + * Data structure invariants: + * - All lists are singly linked and null-terminated; prev + * pointers are not maintained. + * - pending is a prev-linked "list of lists" of sorted + * sublists awaiting further merging. + * - Each of the sorted sublists is power-of-two in size. + * - Sublists are sorted by size and age, smallest & newest at front. + * - There are zero to two sublists of each size. + * - A pair of pending sublists are merged as soon as the number + * of following pending elements equals their size (i.e. + * each time count reaches an odd multiple of that size). + * That ensures each later final merge will be at worst 2:1. + * - Each round consists of: + * - Merging the two sublists selected by the highest bit + * which flips when count is incremented, and + * - Adding an element from the input as a size-1 sublist. + */ + do { + size_t bits; + struct list_head **tail = &pending; + + /* Find the least-significant clear bit in count */ + for (bits = count; bits & 1; bits >>= 1) + tail = &(*tail)->prev; + /* Do the indicated merge */ + if (bits) { + struct list_head *a = *tail, *b = a->prev; + + a = merge(priv, cmp, b, a); + /* Install the merged result in place of the inputs */ + a->prev = b->prev; + *tail = a; + } + + /* Move one element from input list to pending */ + list->prev = pending; + pending = list; + list = list->next; + pending->next = NULL; + count++; + } while (list); + + /* End of input; merge together all the pending lists. */ + list = pending; + pending = pending->prev; + for (;;) { + struct list_head *next = pending->prev; + + if (!next) + break; + list = merge(priv, cmp, pending, list); + pending = next; + } + /* The final merge, rebuilding prev links */ + merge_final(priv, cmp, head, pending, list); +} diff --git a/jffsX-utils/rbtree.c b/lib/rbtree.c index 329e098..32c8755 100644 --- a/jffsX-utils/rbtree.c +++ b/lib/rbtree.c @@ -388,3 +388,41 @@ void rb_replace_node(struct rb_node *victim, struct rb_node *new, /* Copy the pointers/colour from the victim to the replacement */ *new = *victim; } + +static struct rb_node *rb_left_deepest_node(const struct rb_node *node) +{ + for (;;) { + if (node->rb_left) + node = node->rb_left; + else if (node->rb_right) + node = node->rb_right; + else + return (struct rb_node *)node; + } +} + +struct rb_node *rb_next_postorder(const struct rb_node *node) +{ + const struct rb_node *parent; + if (!node) + return NULL; + parent = rb_parent(node); + + /* If we're sitting on node, we've already seen our children */ + if (parent && node == parent->rb_left && parent->rb_right) { + /* If we are the parent's left node, go to the parent's right + * node then all the way down to the left */ + return rb_left_deepest_node(parent->rb_right); + } else + /* Otherwise we are the parent's right node, and the parent + * should be next */ + return (struct rb_node *)parent; +} + +struct rb_node *rb_first_postorder(const struct rb_root *root) +{ + if (!root->rb_node) + return NULL; + + return rb_left_deepest_node(root->rb_node); +} diff --git a/m4/ac_define_dir.m4 b/m4/ac_define_dir.m4 new file mode 100644 index 0000000..3b48c8b --- /dev/null +++ b/m4/ac_define_dir.m4 @@ -0,0 +1,35 @@ +dnl @synopsis AC_DEFINE_DIR(VARNAME, DIR [, DESCRIPTION]) +dnl +dnl This macro sets VARNAME to the expansion of the DIR variable, +dnl taking care of fixing up ${prefix} and such. +dnl +dnl VARNAME is then offered as both an output variable and a C +dnl preprocessor symbol. +dnl +dnl Example: +dnl +dnl AC_DEFINE_DIR([DATADIR], [datadir], [Where data are placed to.]) +dnl +dnl @category Misc +dnl @author Stepan Kasal <kasal@ucw.cz> +dnl @author Andreas Schwab <schwab@suse.de> +dnl @author Guido U. Draheim <guidod@gmx.de> +dnl @author Alexandre Oliva +dnl @version 2006-10-13 +dnl @license AllPermissive + +AC_DEFUN([AC_DEFINE_DIR], [ + prefix_NONE= + exec_prefix_NONE= + test "x$prefix" = xNONE && prefix_NONE=yes && prefix=$ac_default_prefix + test "x$exec_prefix" = xNONE && exec_prefix_NONE=yes && exec_prefix=$prefix +dnl In Autoconf 2.60, ${datadir} refers to ${datarootdir}, which in turn +dnl refers to ${prefix}. Thus we have to use `eval' twice. + eval ac_define_dir="\"[$]$2\"" + eval ac_define_dir="\"$ac_define_dir\"" + AC_SUBST($1, "$ac_define_dir") + AC_DEFINE_UNQUOTED($1, "$ac_define_dir", [$3]) + test "$prefix_NONE" && prefix=NONE + test "$exec_prefix_NONE" && exec_prefix=NONE +]) + diff --git a/m4/ax_pthread.m4 b/m4/ax_pthread.m4 index 82ce2b9..9f35d13 100644 --- a/m4/ax_pthread.m4 +++ b/m4/ax_pthread.m4 @@ -1,5 +1,5 @@ # =========================================================================== -# http://www.gnu.org/software/autoconf-archive/ax_pthread.html +# https://www.gnu.org/software/autoconf-archive/ax_pthread.html # =========================================================================== # # SYNOPSIS @@ -14,20 +14,24 @@ # flags that are needed. (The user can also force certain compiler # flags/libs to be tested by setting these environment variables.) # -# Also sets PTHREAD_CC to any special C compiler that is needed for -# multi-threaded programs (defaults to the value of CC otherwise). (This -# is necessary on AIX to use the special cc_r compiler alias.) +# Also sets PTHREAD_CC and PTHREAD_CXX to any special C compiler that is +# needed for multi-threaded programs (defaults to the value of CC +# respectively CXX otherwise). (This is necessary on e.g. AIX to use the +# special cc_r/CC_r compiler alias.) # # NOTE: You are assumed to not only compile your program with these flags, # but also to link with them as well. For example, you might link with # $PTHREAD_CC $CFLAGS $PTHREAD_CFLAGS $LDFLAGS ... $PTHREAD_LIBS $LIBS +# $PTHREAD_CXX $CXXFLAGS $PTHREAD_CFLAGS $LDFLAGS ... $PTHREAD_LIBS $LIBS # # If you are only building threaded programs, you may wish to use these # variables in your default LIBS, CFLAGS, and CC: # # LIBS="$PTHREAD_LIBS $LIBS" # CFLAGS="$CFLAGS $PTHREAD_CFLAGS" +# CXXFLAGS="$CXXFLAGS $PTHREAD_CFLAGS" # CC="$PTHREAD_CC" +# CXX="$PTHREAD_CXX" # # In addition, if the PTHREAD_CREATE_JOINABLE thread-attribute constant # has a nonstandard name, this macro defines PTHREAD_CREATE_JOINABLE to @@ -55,6 +59,7 @@ # # Copyright (c) 2008 Steven G. Johnson <stevenj@alum.mit.edu> # Copyright (c) 2011 Daniel Richard G. <skunk@iSKUNK.ORG> +# Copyright (c) 2019 Marc Stevens <marc.stevens@cwi.nl> # # 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 @@ -67,7 +72,7 @@ # Public License for more details. # # You should have received a copy of the GNU General Public License along -# with this program. If not, see <http://www.gnu.org/licenses/>. +# with this program. If not, see <https://www.gnu.org/licenses/>. # # As a special exception, the respective Autoconf Macro's copyright owner # gives unlimited permission to copy, distribute and modify the configure @@ -82,7 +87,7 @@ # modified version of the Autoconf Macro, you may extend this special # exception to the GPL to apply to your modified version as well. -#serial 23 +#serial 31 AU_ALIAS([ACX_PTHREAD], [AX_PTHREAD]) AC_DEFUN([AX_PTHREAD], [ @@ -104,6 +109,7 @@ if test "x$PTHREAD_CFLAGS$PTHREAD_LIBS" != "x"; then ax_pthread_save_CFLAGS="$CFLAGS" ax_pthread_save_LIBS="$LIBS" AS_IF([test "x$PTHREAD_CC" != "x"], [CC="$PTHREAD_CC"]) + AS_IF([test "x$PTHREAD_CXX" != "x"], [CXX="$PTHREAD_CXX"]) CFLAGS="$CFLAGS $PTHREAD_CFLAGS" LIBS="$PTHREAD_LIBS $LIBS" AC_MSG_CHECKING([for pthread_join using $CC $PTHREAD_CFLAGS $PTHREAD_LIBS]) @@ -123,10 +129,12 @@ fi # (e.g. DEC) have both -lpthread and -lpthreads, where one of the # libraries is broken (non-POSIX). -# Create a list of thread flags to try. Items starting with a "-" are -# C compiler flags, and other items are library names, except for "none" -# which indicates that we try without any flags at all, and "pthread-config" -# which is a program returning the flags for the Pth emulation library. +# Create a list of thread flags to try. Items with a "," contain both +# C compiler flags (before ",") and linker flags (after ","). Other items +# starting with a "-" are C compiler flags, and remaining items are +# library names, except for "none" which indicates that we try without +# any flags at all, and "pthread-config" which is a program returning +# the flags for the Pth emulation library. ax_pthread_flags="pthreads none -Kthread -pthread -pthreads -mthreads pthread --thread-safe -mt pthread-config" @@ -194,14 +202,47 @@ case $host_os in # that too in a future libc.) So we'll check first for the # standard Solaris way of linking pthreads (-mt -lpthread). - ax_pthread_flags="-mt,pthread pthread $ax_pthread_flags" + ax_pthread_flags="-mt,-lpthread pthread $ax_pthread_flags" ;; esac +# Are we compiling with Clang? + +AC_CACHE_CHECK([whether $CC is Clang], + [ax_cv_PTHREAD_CLANG], + [ax_cv_PTHREAD_CLANG=no + # Note that Autoconf sets GCC=yes for Clang as well as GCC + if test "x$GCC" = "xyes"; then + AC_EGREP_CPP([AX_PTHREAD_CC_IS_CLANG], + [/* Note: Clang 2.7 lacks __clang_[a-z]+__ */ +# if defined(__clang__) && defined(__llvm__) + AX_PTHREAD_CC_IS_CLANG +# endif + ], + [ax_cv_PTHREAD_CLANG=yes]) + fi + ]) +ax_pthread_clang="$ax_cv_PTHREAD_CLANG" + + # GCC generally uses -pthread, or -pthreads on some platforms (e.g. SPARC) +# Note that for GCC and Clang -pthread generally implies -lpthread, +# except when -nostdlib is passed. +# This is problematic using libtool to build C++ shared libraries with pthread: +# [1] https://gcc.gnu.org/bugzilla/show_bug.cgi?id=25460 +# [2] https://bugzilla.redhat.com/show_bug.cgi?id=661333 +# [3] https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=468555 +# To solve this, first try -pthread together with -lpthread for GCC + AS_IF([test "x$GCC" = "xyes"], - [ax_pthread_flags="-pthread -pthreads $ax_pthread_flags"]) + [ax_pthread_flags="-pthread,-lpthread -pthread -pthreads $ax_pthread_flags"]) + +# Clang takes -pthread (never supported any other flag), but we'll try with -lpthread first + +AS_IF([test "x$ax_pthread_clang" = "xyes"], + [ax_pthread_flags="-pthread,-lpthread -pthread"]) + # The presence of a feature test macro requesting re-entrant function # definitions is, on some systems, a strong hint that pthreads support is @@ -224,25 +265,86 @@ AS_IF([test "x$ax_pthread_check_macro" = "x--"], [ax_pthread_check_cond=0], [ax_pthread_check_cond="!defined($ax_pthread_check_macro)"]) -# Are we compiling with Clang? -AC_CACHE_CHECK([whether $CC is Clang], - [ax_cv_PTHREAD_CLANG], - [ax_cv_PTHREAD_CLANG=no - # Note that Autoconf sets GCC=yes for Clang as well as GCC - if test "x$GCC" = "xyes"; then - AC_EGREP_CPP([AX_PTHREAD_CC_IS_CLANG], - [/* Note: Clang 2.7 lacks __clang_[a-z]+__ */ -# if defined(__clang__) && defined(__llvm__) - AX_PTHREAD_CC_IS_CLANG -# endif - ], - [ax_cv_PTHREAD_CLANG=yes]) - fi - ]) -ax_pthread_clang="$ax_cv_PTHREAD_CLANG" +if test "x$ax_pthread_ok" = "xno"; then +for ax_pthread_try_flag in $ax_pthread_flags; do + + case $ax_pthread_try_flag in + none) + AC_MSG_CHECKING([whether pthreads work without any flags]) + ;; + + *,*) + PTHREAD_CFLAGS=`echo $ax_pthread_try_flag | sed "s/^\(.*\),\(.*\)$/\1/"` + PTHREAD_LIBS=`echo $ax_pthread_try_flag | sed "s/^\(.*\),\(.*\)$/\2/"` + AC_MSG_CHECKING([whether pthreads work with "$PTHREAD_CFLAGS" and "$PTHREAD_LIBS"]) + ;; + + -*) + AC_MSG_CHECKING([whether pthreads work with $ax_pthread_try_flag]) + PTHREAD_CFLAGS="$ax_pthread_try_flag" + ;; + + pthread-config) + AC_CHECK_PROG([ax_pthread_config], [pthread-config], [yes], [no]) + AS_IF([test "x$ax_pthread_config" = "xno"], [continue]) + PTHREAD_CFLAGS="`pthread-config --cflags`" + PTHREAD_LIBS="`pthread-config --ldflags` `pthread-config --libs`" + ;; + + *) + AC_MSG_CHECKING([for the pthreads library -l$ax_pthread_try_flag]) + PTHREAD_LIBS="-l$ax_pthread_try_flag" + ;; + esac + + ax_pthread_save_CFLAGS="$CFLAGS" + ax_pthread_save_LIBS="$LIBS" + CFLAGS="$CFLAGS $PTHREAD_CFLAGS" + LIBS="$PTHREAD_LIBS $LIBS" + + # Check for various functions. We must include pthread.h, + # since some functions may be macros. (On the Sequent, we + # need a special flag -Kthread to make this header compile.) + # We check for pthread_join because it is in -lpthread on IRIX + # while pthread_create is in libc. We check for pthread_attr_init + # due to DEC craziness with -lpthreads. We check for + # pthread_cleanup_push because it is one of the few pthread + # functions on Solaris that doesn't have a non-functional libc stub. + # We try pthread_create on general principles. + + AC_LINK_IFELSE([AC_LANG_PROGRAM([#include <pthread.h> +# if $ax_pthread_check_cond +# error "$ax_pthread_check_macro must be defined" +# endif + static void *some_global = NULL; + static void routine(void *a) + { + /* To avoid any unused-parameter or + unused-but-set-parameter warning. */ + some_global = a; + } + static void *start_routine(void *a) { return a; }], + [pthread_t th; pthread_attr_t attr; + pthread_create(&th, 0, start_routine, 0); + pthread_join(th, 0); + pthread_attr_init(&attr); + pthread_cleanup_push(routine, 0); + pthread_cleanup_pop(0) /* ; */])], + [ax_pthread_ok=yes], + []) + + CFLAGS="$ax_pthread_save_CFLAGS" + LIBS="$ax_pthread_save_LIBS" + + AC_MSG_RESULT([$ax_pthread_ok]) + AS_IF([test "x$ax_pthread_ok" = "xyes"], [break]) + + PTHREAD_LIBS="" + PTHREAD_CFLAGS="" +done +fi -ax_pthread_clang_warning=no # Clang needs special handling, because older versions handle the -pthread # option in a rather... idiosyncratic way @@ -261,11 +363,6 @@ if test "x$ax_pthread_clang" = "xyes"; then # -pthread does define _REENTRANT, and while the Darwin headers # ignore this macro, third-party headers might not.) - PTHREAD_CFLAGS="-pthread" - PTHREAD_LIBS= - - ax_pthread_ok=yes - # However, older versions of Clang make a point of warning the user # that, in an invocation where only linking and no compilation is # taking place, the -pthread option has no effect ("argument unused @@ -294,7 +391,7 @@ if test "x$ax_pthread_clang" = "xyes"; then # step ax_pthread_save_ac_link="$ac_link" ax_pthread_sed='s/conftest\.\$ac_ext/conftest.$ac_objext/g' - ax_pthread_link_step=`$as_echo "$ac_link" | sed "$ax_pthread_sed"` + ax_pthread_link_step=`AS_ECHO(["$ac_link"]) | sed "$ax_pthread_sed"` ax_pthread_2step_ac_link="($ac_compile) && (echo ==== >&5) && ($ax_pthread_link_step)" ax_pthread_save_CFLAGS="$CFLAGS" for ax_pthread_try in '' -Qunused-arguments -Wno-unused-command-line-argument unknown; do @@ -320,78 +417,7 @@ if test "x$ax_pthread_clang" = "xyes"; then fi # $ax_pthread_clang = yes -if test "x$ax_pthread_ok" = "xno"; then -for ax_pthread_try_flag in $ax_pthread_flags; do - - case $ax_pthread_try_flag in - none) - AC_MSG_CHECKING([whether pthreads work without any flags]) - ;; - - -mt,pthread) - AC_MSG_CHECKING([whether pthreads work with -mt -lpthread]) - PTHREAD_CFLAGS="-mt" - PTHREAD_LIBS="-lpthread" - ;; - - -*) - AC_MSG_CHECKING([whether pthreads work with $ax_pthread_try_flag]) - PTHREAD_CFLAGS="$ax_pthread_try_flag" - ;; - - pthread-config) - AC_CHECK_PROG([ax_pthread_config], [pthread-config], [yes], [no]) - AS_IF([test "x$ax_pthread_config" = "xno"], [continue]) - PTHREAD_CFLAGS="`pthread-config --cflags`" - PTHREAD_LIBS="`pthread-config --ldflags` `pthread-config --libs`" - ;; - - *) - AC_MSG_CHECKING([for the pthreads library -l$ax_pthread_try_flag]) - PTHREAD_LIBS="-l$ax_pthread_try_flag" - ;; - esac - - ax_pthread_save_CFLAGS="$CFLAGS" - ax_pthread_save_LIBS="$LIBS" - CFLAGS="$CFLAGS $PTHREAD_CFLAGS" - LIBS="$PTHREAD_LIBS $LIBS" - - # Check for various functions. We must include pthread.h, - # since some functions may be macros. (On the Sequent, we - # need a special flag -Kthread to make this header compile.) - # We check for pthread_join because it is in -lpthread on IRIX - # while pthread_create is in libc. We check for pthread_attr_init - # due to DEC craziness with -lpthreads. We check for - # pthread_cleanup_push because it is one of the few pthread - # functions on Solaris that doesn't have a non-functional libc stub. - # We try pthread_create on general principles. - - AC_LINK_IFELSE([AC_LANG_PROGRAM([#include <pthread.h> -# if $ax_pthread_check_cond -# error "$ax_pthread_check_macro must be defined" -# endif - static void routine(void *a) { a = 0; } - static void *start_routine(void *a) { return a; }], - [pthread_t th; pthread_attr_t attr; - pthread_create(&th, 0, start_routine, 0); - pthread_join(th, 0); - pthread_attr_init(&attr); - pthread_cleanup_push(routine, 0); - pthread_cleanup_pop(0) /* ; */])], - [ax_pthread_ok=yes], - []) - - CFLAGS="$ax_pthread_save_CFLAGS" - LIBS="$ax_pthread_save_LIBS" - AC_MSG_RESULT([$ax_pthread_ok]) - AS_IF([test "x$ax_pthread_ok" = "xyes"], [break]) - - PTHREAD_LIBS="" - PTHREAD_CFLAGS="" -done -fi # Various other checks: if test "x$ax_pthread_ok" = "xyes"; then @@ -438,7 +464,8 @@ if test "x$ax_pthread_ok" = "xyes"; then AC_CACHE_CHECK([for PTHREAD_PRIO_INHERIT], [ax_cv_PTHREAD_PRIO_INHERIT], [AC_LINK_IFELSE([AC_LANG_PROGRAM([[#include <pthread.h>]], - [[int i = PTHREAD_PRIO_INHERIT;]])], + [[int i = PTHREAD_PRIO_INHERIT; + return i;]])], [ax_cv_PTHREAD_PRIO_INHERIT=yes], [ax_cv_PTHREAD_PRIO_INHERIT=no]) ]) @@ -460,18 +487,28 @@ if test "x$ax_pthread_ok" = "xyes"; then [#handle absolute path differently from PATH based program lookup AS_CASE(["x$CC"], [x/*], - [AS_IF([AS_EXECUTABLE_P([${CC}_r])],[PTHREAD_CC="${CC}_r"])], - [AC_CHECK_PROGS([PTHREAD_CC],[${CC}_r],[$CC])])]) + [ + AS_IF([AS_EXECUTABLE_P([${CC}_r])],[PTHREAD_CC="${CC}_r"]) + AS_IF([test "x${CXX}" != "x"], [AS_IF([AS_EXECUTABLE_P([${CXX}_r])],[PTHREAD_CXX="${CXX}_r"])]) + ], + [ + AC_CHECK_PROGS([PTHREAD_CC],[${CC}_r],[$CC]) + AS_IF([test "x${CXX}" != "x"], [AC_CHECK_PROGS([PTHREAD_CXX],[${CXX}_r],[$CXX])]) + ] + ) + ]) ;; esac fi fi test -n "$PTHREAD_CC" || PTHREAD_CC="$CC" +test -n "$PTHREAD_CXX" || PTHREAD_CXX="$CXX" AC_SUBST([PTHREAD_LIBS]) AC_SUBST([PTHREAD_CFLAGS]) AC_SUBST([PTHREAD_CC]) +AC_SUBST([PTHREAD_CXX]) # Finally, execute ACTION-IF-FOUND/ACTION-IF-NOT-FOUND: if test "x$ax_pthread_ok" = "xyes"; then @@ -483,4 +520,3 @@ else fi AC_LANG_POP ])dnl AX_PTHREAD - diff --git a/misc-utils/Makemodule.am b/misc-utils/Makemodule.am index 2289252..1ce1a68 100644 --- a/misc-utils/Makemodule.am +++ b/misc-utils/Makemodule.am @@ -1,24 +1,34 @@ -ftl_format_SOURCES = misc-utils/ftl_format.c +ftl_format_SOURCES = misc-utils/ftl_format.c include/mtd_swab.h +ftl_format_SOURCES += include/mtd/ftl-user.h doc_loadbios_SOURCES = misc-utils/doc_loadbios.c -ftl_check_SOURCES = misc-utils/ftl_check.c +ftl_check_SOURCES = misc-utils/ftl_check.c include/mtd_swab.h +ftl_check_SOURCES += include/mtd/ftl-user.h mtd_debug_SOURCES = misc-utils/mtd_debug.c +mtd_debug_LDADD = libmtd.a mtdpart_SOURCES = misc-utils/mtdpart.c +mtdpart_LDADD = libmtd.a -docfdisk_SOURCES = misc-utils/docfdisk.c +docfdisk_SOURCES = misc-utils/docfdisk.c include/mtd_swab.h +docfdisk_SOURCES += include/mtd/inftl-user.h include/mtd/ftl-user.h -serve_image_SOURCES = misc-utils/serve_image.c +serve_image_SOURCES = misc-utils/serve_image.c misc-utils/mcast_image.h serve_image_LDADD = libmtd.a -recv_image_SOURCES = misc-utils/recv_image.c +recv_image_SOURCES = misc-utils/recv_image.c misc-utils/mcast_image.h recv_image_LDADD = libmtd.a +fectest_SOURCES = misc-utils/fectest.c misc-utils/mcast_image.h +fectest_LDADD = libmtd.a + flash_lock_SOURCES = misc-utils/flash_lock.c +flash_lock_LDADD = libmtd.a flash_unlock_SOURCES = misc-utils/flash_unlock.c +flash_unlock_LDADD = libmtd.a flash_otp_info_SOURCES = misc-utils/flash_otp_info.c @@ -26,42 +36,34 @@ flash_otp_dump_SOURCES = misc-utils/flash_otp_dump.c flash_otp_lock_SOURCES = misc-utils/flash_otp_lock.c +flash_otp_erase_SOURCES = misc-utils/flash_otp_erase.c + flash_otp_write_SOURCES = misc-utils/flash_otp_write.c flashcp_SOURCES = misc-utils/flashcp.c +flashcp_LDADD = libmtd.a flash_erase_SOURCES = misc-utils/flash_erase.c flash_erase_LDADD = libmtd.a -MISC_BINS = \ +sbin_PROGRAMS += \ ftl_format doc_loadbios ftl_check mtd_debug docfdisk \ - serve_image recv_image flash_erase flash_lock \ + serve_image recv_image fectest flash_erase flash_lock \ flash_unlock flash_otp_info flash_otp_dump flash_otp_lock \ - flash_otp_write flashcp mtdpart + flash_otp_erase flash_otp_write flashcp mtdpart MISC_SH = \ misc-utils/flash_eraseall -MISC_EXTRA = \ - misc-utils/MAKEDEV - -MISC_HEADER = \ - misc-utils/mcast_image.h \ - misc-utils/lsmtd.h - -MISC_MAN = - if BUILD_LSMTD -lsmtd_SOURCES = misc-utils/lsmtd.c misc-utils/lsmtd_scan.c +lsmtd_SOURCES = misc-utils/lsmtd.c misc-utils/lsmtd_scan.c misc-utils/lsmtd.h lsmtd_LDADD = libmtd.a libubi.a lsmtd_CPPFLAGS = $(AM_CPPFLAGS) -I$(top_srcdir)/misc-utils -MISC_BINS += lsmtd -MISC_MAN += misc-utils/lsmtd.8 +sbin_PROGRAMS += lsmtd +dist_man8_MANS += misc-utils/lsmtd.8 endif -EXTRA_DIST += $(MISC_HEADER) $(MISC_EXTRA) $(MISC_SH) +EXTRA_DIST += misc-utils/MAKEDEV $(MISC_SH) -dist_man8_MANS += $(MISC_MAN) -sbin_PROGRAMS += $(MISC_BINS) sbin_SCRIPTS += $(MISC_SH) diff --git a/misc-utils/fectest.c b/misc-utils/fectest.c index fcba513..f560f2b 100644 --- a/misc-utils/fectest.c +++ b/misc-utils/fectest.c @@ -21,7 +21,7 @@ int main(void) unsigned char buf[NR_PKTS * PKT_SIZE]; unsigned char pktbuf[(NR_PKTS + DROPS) * PKT_SIZE]; struct fec_parms *fec; - unsigned char *srcs[NR_PKTS]; + unsigned char *srcs[NR_PKTS + DROPS]; unsigned char *pkt[NR_PKTS + DROPS]; int pktnr[NR_PKTS + DROPS]; struct timeval then, now; @@ -87,6 +87,6 @@ int main(void) exit(1); } - printf("Decoded in %ld.%06lds\n", now.tv_sec, now.tv_usec); + printf("Decoded in %ld.%06lds\n", (long)now.tv_sec, (long)now.tv_usec); return 0; } diff --git a/misc-utils/flash_erase.c b/misc-utils/flash_erase.c index a7fc6a6..36f8d57 100644 --- a/misc-utils/flash_erase.c +++ b/misc-utils/flash_erase.c @@ -2,6 +2,7 @@ Copyright (C) 2000 Arcom Control System Ltd Copyright (C) 2010 Mike Frysinger <vapier@gentoo.org> + Copyright 2021 NXP 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 @@ -50,11 +51,10 @@ static int unlock; /* unlock sectors before erasing */ static struct jffs2_unknown_node cleanmarker; int target_endian = __BYTE_ORDER; -static void show_progress(struct mtd_dev_info *mtd, off_t start, int eb, - int eb_start, int eb_cnt) +static void show_progress(off_t start, int eb, int eb_start, int eb_cnt, int step) { bareverbose(!quiet, "\rErasing %d Kibyte @ %llx -- %2i %% complete ", - mtd->eb_size / 1024, (unsigned long long)start, ((eb - eb_start) * 100) / eb_cnt); + step / 1024, (unsigned long long)start, ((eb - eb_start) * 100) / eb_cnt); fflush(stdout); } @@ -64,13 +64,16 @@ static void display_help (void) "Erase blocks of the specified MTD device.\n" "Specify a count of 0 to erase to end of device.\n" "\n" - " -j, --jffs2 format the device for jffs2\n" - " -N, --noskipbad don't skip bad blocks\n" - " -u, --unlock unlock sectors before erasing\n" - " -q, --quiet do not display progress messages\n" - " --silent same as --quiet\n" - " --help display this help and exit\n" - " --version output version information and exit\n", + " -j, --jffs2 format the device for jffs2\n" + " -c, --cleanmarker=SIZE size of jffs2 cleanmarker (default 12)\n" + " -N, --noskipbad don't skip bad blocks\n" + " -u, --unlock unlock sectors before erasing\n" + " -q, --quiet do not display progress messages\n" + " --silent same as --quiet\n" + " --help display this help and exit\n" + " --version output version information and exit\n", + "\n" + " MTD_DEVICE MTD device node or 'mtd:<name>'\n" PROGRAM_NAME); } @@ -88,14 +91,35 @@ static void display_version (void) PROGRAM_NAME); } +static void clear_marker(libmtd_t mtd_desc, struct mtd_dev_info *mtd, int fd, + unsigned int eb, int cmlen, bool isNAND) +{ + off_t offset = (off_t)eb * mtd->eb_size; + + /* write cleanmarker */ + if (isNAND) { + if (mtd_write(mtd_desc, mtd, fd, eb, 0, NULL, 0, &cleanmarker, cmlen, + MTD_OPS_AUTO_OOB) != 0) { + sys_errmsg("%s: MTD writeoob failure", mtd_device); + return; + } + } else { + if (pwrite(fd, &cleanmarker, sizeof(cleanmarker), (loff_t)offset) != sizeof(cleanmarker)) { + sys_errmsg("%s: MTD write failure", mtd_device); + return; + } + } + verbose(!quiet, "%llx : Cleanmarker Updated.", (unsigned long long)offset); +} + int main(int argc, char *argv[]) { libmtd_t mtd_desc; struct mtd_dev_info mtd; - int fd, cmlen = 8; + int fd, cmlen = 8, cmsize = sizeof(cleanmarker); unsigned long long start; unsigned int eb, eb_start, eb_cnt; - bool isNAND; + bool isNAND, erase_chip = false; int error = 0; off_t offset = 0; @@ -104,11 +128,12 @@ int main(int argc, char *argv[]) */ for (;;) { int option_index = 0; - static const char *short_options = "jNquVh"; + static const char *short_options = "jc:NquVh"; static const struct option long_options[] = { {"help", no_argument, 0, 'h'}, {"version", no_argument, 0, 'V'}, {"jffs2", no_argument, 0, 'j'}, + {"cleanmarker", required_argument, 0, 'c'}, {"noskipbad", no_argument, 0, 'N'}, {"quiet", no_argument, 0, 'q'}, {"silent", no_argument, 0, 'q'}, @@ -132,6 +157,9 @@ int main(int argc, char *argv[]) case 'j': jffs2 = 1; break; + case 'c': + cmsize = atoi(optarg); + break; case 'N': noskipbad = 1; break; @@ -148,7 +176,9 @@ int main(int argc, char *argv[]) } switch (argc - optind) { case 3: - mtd_device = argv[optind]; + mtd_device = mtd_find_dev_node(argv[optind]); + if (!mtd_device) + return errmsg("Can't find MTD device %s", argv[optind]); start = simple_strtoull(argv[optind + 1], &error); eb_cnt = simple_strtoul(argv[optind + 2], &error); break; @@ -182,6 +212,10 @@ int main(int argc, char *argv[]) if (jffs2 && mtd.type == MTD_MLCNANDFLASH) return errmsg("JFFS2 cannot support MLC NAND."); + if (jffs2 && cmsize < sizeof(cleanmarker)) + return errmsg("cleanmarker size must be >= 12"); + if (jffs2 && cmsize >= mtd.eb_size) + return errmsg("cleanmarker size must be < eraseblock size"); eb_start = start / mtd.eb_size; @@ -191,7 +225,7 @@ int main(int argc, char *argv[]) cleanmarker.magic = cpu_to_je16 (JFFS2_MAGIC_BITMASK); cleanmarker.nodetype = cpu_to_je16 (JFFS2_NODETYPE_CLEANMARKER); if (!isNAND) { - cleanmarker.totlen = cpu_to_je32(sizeof(cleanmarker)); + cleanmarker.totlen = cpu_to_je32(cmsize); } else { cleanmarker.totlen = cpu_to_je32(8); cmlen = min(mtd.oobavail, 8); @@ -205,6 +239,47 @@ int main(int argc, char *argv[]) if (eb_cnt == 0) eb_cnt = (mtd.size / mtd.eb_size) - eb_start; + if (eb_start == 0 && mtd.size == (long long)eb_cnt * mtd.eb_size) + erase_chip = true; + + /* If MTD device may have bad eraseblocks, + * erase one by one each sector + */ + if (noskipbad && mtd.bb_allowed) + erase_chip = false; + + if (erase_chip) { + show_progress(0, eb_start, eb_start, eb_cnt, mtd.size); + + if (unlock) { + if (mtd_unlock_multi(&mtd, fd, eb_start, eb_cnt) != 0) { + sys_errmsg("%s: MTD unlock entire chip failure." \ + "Trying one by one each sector.", + mtd_device); + goto erase_each_sector; + } + } + + if (mtd_erase_multi(mtd_desc, &mtd, fd, eb_start, eb_cnt) != 0) { + sys_errmsg("%s: MTD Erase entire chip failure" \ + "Trying one by one each sector.", + mtd_device); + goto erase_each_sector; + } + + show_progress(0, eb_start + eb_cnt, eb_start, + eb_cnt, mtd.size); + + if (!jffs2) + goto out; + + /* write cleanmarker */ + for (eb = eb_start; eb < eb_start + eb_cnt; eb++) + clear_marker(mtd_desc, &mtd, fd, eb, cmlen, isNAND); + goto out; + } + +erase_each_sector: for (eb = eb_start; eb < eb_start + eb_cnt; eb++) { offset = (off_t)eb * mtd.eb_size; @@ -223,7 +298,7 @@ int main(int argc, char *argv[]) } } - show_progress(&mtd, offset, eb, eb_start, eb_cnt); + show_progress(offset, eb, eb_start, eb_cnt, mtd.eb_size); if (unlock) { if (mtd_unlock(&mtd, fd, eb) != 0) { @@ -237,26 +312,11 @@ int main(int argc, char *argv[]) continue; } - /* format for JFFS2 ? */ - if (!jffs2) - continue; - - /* write cleanmarker */ - if (isNAND) { - if (mtd_write(mtd_desc, &mtd, fd, eb, 0, NULL, 0, &cleanmarker, cmlen, - MTD_OPS_AUTO_OOB) != 0) { - sys_errmsg("%s: MTD writeoob failure", mtd_device); - continue; - } - } else { - if (pwrite(fd, &cleanmarker, sizeof(cleanmarker), (loff_t)offset) != sizeof(cleanmarker)) { - sys_errmsg("%s: MTD write failure", mtd_device); - continue; - } - } - verbose(!quiet, " Cleanmarker Updated."); + if (jffs2) + clear_marker(mtd_desc, &mtd, fd, eb, cmlen, isNAND); } - show_progress(&mtd, offset, eb, eb_start, eb_cnt); + show_progress(offset, eb, eb_start, eb_cnt, mtd.eb_size); +out: bareverbose(!quiet, "\n"); return 0; diff --git a/misc-utils/flash_otp_dump.c b/misc-utils/flash_otp_dump.c index f1e1782..324d856 100644 --- a/misc-utils/flash_otp_dump.c +++ b/misc-utils/flash_otp_dump.c @@ -20,7 +20,7 @@ int main(int argc,char *argv[]) int offset = 0; unsigned char buf[16]; - if (argc <= 3 || (strcmp(argv[1], "-f") && strcmp(argv[1], "-u"))) { + if (argc < 3 || (strcmp(argv[1], "-f") && strcmp(argv[1], "-u"))) { fprintf(stderr,"Usage: %s [ -f | -u ] <device> [<offset>]\n", PROGRAM_NAME); return EINVAL; } diff --git a/misc-utils/flash_otp_erase.c b/misc-utils/flash_otp_erase.c new file mode 100644 index 0000000..771e230 --- /dev/null +++ b/misc-utils/flash_otp_erase.c @@ -0,0 +1,64 @@ +/* + * flash_otp_erase.c -- erase area of One-Time-Program data + */ + +#define PROGRAM_NAME "flash_otp_erase" + +#include <stdio.h> +#include <unistd.h> +#include <fcntl.h> +#include <string.h> +#include <stdlib.h> +#include <errno.h> +#include <sys/ioctl.h> + +#include <mtd/mtd-user.h> +#include "common.h" + +int main(int argc,char *argv[]) +{ + int fd, val, ret, offset, size; + struct otp_info info; + char *p; + + if (argc != 5 || strcmp(argv[1], "-u")) { + fprintf(stderr, "Usage: %s -u <device> <offset> <size>\n", PROGRAM_NAME); + fprintf(stderr, "offset and size must match on OTP region boundaries\n"); + return EINVAL; + } + + fd = open(argv[2], O_WRONLY); + if (fd < 0) { + perror(argv[2]); + return errno; + } + + val = MTD_OTP_USER; + ret = ioctl(fd, OTPSELECT, &val); + if (ret < 0) { + perror("OTPSELECT"); + return errno; + } + + offset = strtoul(argv[3], &p, 0); + if (argv[3][0] == 0 || *p != 0) { + fprintf(stderr, "%s: bad offset value\n", PROGRAM_NAME); + return ERANGE; + } + + size = strtoul(argv[4], &p, 0); + if (argv[4][0] == 0 || *p != 0) { + fprintf(stderr, "%s: bad size value\n", PROGRAM_NAME); + return ERANGE; + } + + info.start = offset; + info.length = size; + ret = ioctl(fd, OTPERASE, &info); + if (ret < 0) { + perror("OTPERASE"); + return errno; + } + + return 0; +} diff --git a/misc-utils/flash_unlock.c b/misc-utils/flash_unlock.c index fbbfa51..fa5decb 100644 --- a/misc-utils/flash_unlock.c +++ b/misc-utils/flash_unlock.c @@ -51,6 +51,8 @@ static NORETURN void usage(int status) " -l --lock Lock a region of flash\n" " -u --unlock Unlock a region of flash\n" "\n" + " <mtd device> MTD device node or 'mtd:<name>'\n" + "\n" "If offset is not specified, it defaults to 0.\n" "If block count is not specified, it defaults to all blocks.\n" "A block count of -1 means all blocks.\n", @@ -125,7 +127,12 @@ static void process_args(int argc, char *argv[]) } /* First non-option argument */ - dev = argv[arg_idx++]; + dev = mtd_find_dev_node(argv[arg_idx]); + if (!dev) { + errmsg("MTD device not found %s", argv[arg_idx]); + usage(EXIT_FAILURE); + } + arg_idx++; /* Second non-option argument */ if (arg_idx < argc) diff --git a/misc-utils/flashcp.c b/misc-utils/flashcp.c index d7b0a59..6065a8c 100644 --- a/misc-utils/flashcp.c +++ b/misc-utils/flashcp.c @@ -48,7 +48,7 @@ /* for debugging purposes only */ #ifdef DEBUG #undef DEBUG -#define DEBUG(fmt,args...) { log_printf (LOG_ERROR,"%d: ",__LINE__); log_printf (LOG_ERROR,fmt,## args); } +#define DEBUG(fmt,args...) { fprintf (stderr,"%d: ",__LINE__); fprintf (stderr,fmt,## args); } #else #undef DEBUG #define DEBUG(fmt,args...) @@ -57,36 +57,47 @@ #define KB(x) ((x) / 1024) #define PERCENTAGE(x,total) (((x) * 100) / (total)) -/* size of read/write buffer */ -#define BUFSIZE (10 * 1024) - /* cmd-line flags */ #define FLAG_NONE 0x00 -#define FLAG_VERBOSE 0x01 #define FLAG_HELP 0x02 #define FLAG_FILENAME 0x04 #define FLAG_DEVICE 0x08 #define FLAG_ERASE_ALL 0x10 +#define FLAG_PARTITION 0x20 +#define FLAG_WR_LAST 0x40 /* error levels */ #define LOG_NORMAL 1 #define LOG_ERROR 2 -static void log_printf (int level,const char *fmt, ...) +static NORETURN void log_failure (const char *fmt, ...) { - FILE *fp = level == LOG_NORMAL ? stdout : stderr; va_list ap; va_start (ap,fmt); - vfprintf (fp,fmt,ap); + vfprintf (stderr,fmt,ap); va_end (ap); - fflush (fp); + fflush (stderr); + + exit (EXIT_FAILURE); } -static NORETURN void showusage(bool error) +static int verbose = 0; +static void log_verbose (const char *fmt, ...) { - int level = error ? LOG_ERROR : LOG_NORMAL; + va_list ap; + + if (!verbose) + return; + + va_start (ap,fmt); + vfprintf (stdout,fmt,ap); + va_end (ap); + fflush (stdout); +} - log_printf (level, +static NORETURN void showusage(bool error) +{ + fprintf (error ? stderr : stdout, "\n" "Flash Copy - Written by Abraham van der Merwe <abraham@2d3d.co.za>\n" "\n" @@ -94,12 +105,14 @@ static NORETURN void showusage(bool error) " %1$s -h | --help\n" " %1$s -V | --version\n" "\n" - " -h | --help Show this help message\n" - " -v | --verbose Show progress reports\n" - " -A | --erase-all Erases the whole device regardless of the image size\n" - " -V | --version Show version information and exit\n" - " <filename> File which you want to copy to flash\n" - " <device> Flash device to write to (e.g. /dev/mtd0, /dev/mtd1, etc.)\n" + " -h | --help Show this help message\n" + " -v | --verbose Show progress reports\n" + " -p | --partition Only copy different block from file to device\n" + " -A | --erase-all Erases the whole device regardless of the image size\n" + " -l | --wr-last=bytes Write the first [bytes] last\n" + " -V | --version Show version information and exit\n" + " <filename> File which you want to copy to flash\n" + " <device> Flash device node or 'mtd:<name>' to write to (e.g. /dev/mtd0, /dev/mtd1, mtd:data, etc.)\n" "\n", PROGRAM_NAME); @@ -108,49 +121,85 @@ static NORETURN void showusage(bool error) static int safe_open (const char *pathname,int flags) { + const char *access = "unknown"; int fd; fd = open (pathname,flags); if (fd < 0) { - log_printf (LOG_ERROR,"While trying to open %s",pathname); if (flags & O_RDWR) - log_printf (LOG_ERROR," for read/write access"); + access = "read/write"; else if (flags & O_RDONLY) - log_printf (LOG_ERROR," for read access"); + access = "read"; else if (flags & O_WRONLY) - log_printf (LOG_ERROR," for write access"); - log_printf (LOG_ERROR,": %m\n"); - exit (EXIT_FAILURE); + access = "write"; + + log_failure ("While trying to open %s for %s access: %m\n",pathname,access); } return (fd); } -static void safe_read (int fd,const char *filename,void *buf,size_t count,bool verbose) +static void safe_read (int fd,const char *filename,void *buf,size_t count) { ssize_t result; result = read (fd,buf,count); if (count != result) { - if (verbose) log_printf (LOG_NORMAL,"\n"); + log_verbose ("\n"); + if (result < 0) + { + log_failure("While reading data from %s: %m\n",filename); + } + log_failure("Short read count returned while reading from %s\n",filename); + } +} + +static void safe_write (int fd,const void *buf,size_t count,size_t written,unsigned long long to_write,const char *device) +{ + ssize_t result; + + /* write to device */ + result = write (fd,buf,count); + if (count != result) + { + log_verbose ("\n"); if (result < 0) { - log_printf (LOG_ERROR,"While reading data from %s: %m\n",filename); - exit (EXIT_FAILURE); + log_failure("While writing data to 0x%.8lx-0x%.8lx on %s: %m\n", + written,written + count,device); } - log_printf (LOG_ERROR,"Short read count returned while reading from %s\n",filename); - exit (EXIT_FAILURE); + log_failure("Short write count returned while writing to x%.8zx-0x%.8zx on %s: %zu/%llu bytes written to flash\n", + written,written + count,device,written + result,to_write); } } +static off_t safe_lseek (int fd,off_t offset,int whence,const char *filename) +{ + off_t off; + + off = lseek (fd,offset,whence); + if (off < 0) + { + log_failure("While seeking on %s: %m\n",filename); + } + + return off; +} + static void safe_rewind (int fd,const char *filename) { - if (lseek (fd,0L,SEEK_SET) < 0) + safe_lseek(fd,0L,SEEK_SET,filename); +} + +static void safe_memerase (int fd,const char *device,struct erase_info_user *erase) +{ + if (ioctl (fd,MEMERASE,erase) < 0) { - log_printf (LOG_ERROR,"While seeking to start of %s: %m\n",filename); - exit (EXIT_FAILURE); + log_verbose ("\n"); + log_failure("While erasing blocks 0x%.8x-0x%.8x on %s: %m\n", + (unsigned int) erase->start,(unsigned int) (erase->start + erase->length),device); } } @@ -168,12 +217,13 @@ int main (int argc,char *argv[]) { const char *filename = NULL,*device = NULL; int i,flags = FLAG_NONE; - ssize_t result; size_t size,written; struct mtd_info_user mtd; struct erase_info_user erase; struct stat filestat; - unsigned char src[BUFSIZE],dest[BUFSIZE]; + unsigned char *src, *dest, *wrlast_buf = NULL; + unsigned long long wrlast_len = 0; + int error = 0; /********************* * parse cmd-line @@ -181,11 +231,13 @@ int main (int argc,char *argv[]) for (;;) { int option_index = 0; - static const char *short_options = "hvAV"; + static const char *short_options = "hvpAl:V"; static const struct option long_options[] = { {"help", no_argument, 0, 'h'}, {"verbose", no_argument, 0, 'v'}, + {"partition", no_argument, 0, 'p'}, {"erase-all", no_argument, 0, 'A'}, + {"wr-last", required_argument, 0, 'l'}, {"version", no_argument, 0, 'V'}, {0, 0, 0, 0}, }; @@ -202,13 +254,21 @@ int main (int argc,char *argv[]) DEBUG("Got FLAG_HELP\n"); break; case 'v': - flags |= FLAG_VERBOSE; + verbose = 1; DEBUG("Got FLAG_VERBOSE\n"); break; + case 'p': + flags |= FLAG_PARTITION; + DEBUG("Got FLAG_PARTITION"); + break; case 'A': flags |= FLAG_ERASE_ALL; DEBUG("Got FLAG_ERASE_ALL\n"); break; + case 'l': + flags |= FLAG_WR_LAST; + wrlast_len = simple_strtoll(optarg, &error); + break; case 'V': common_print_version(); exit(EXIT_SUCCESS); @@ -224,13 +284,23 @@ int main (int argc,char *argv[]) DEBUG("Got filename: %s\n",filename); flags |= FLAG_DEVICE; - device = argv[optind+1]; + device = mtd_find_dev_node(argv[optind+1]); + if (!device) + log_failure("Failed to find device %s\n", argv[optind+1]); + DEBUG("Got device: %s\n",device); } if (flags & FLAG_HELP || device == NULL) showusage(flags != FLAG_HELP); + if (flags & FLAG_PARTITION && flags & FLAG_ERASE_ALL) + log_failure("Option --partition does not support --erase-all\n"); + + if (flags & FLAG_PARTITION && flags & FLAG_WR_LAST) { + log_failure("Option --partition does not support --wr-last\n"); + } + atexit (cleanup); /* get some info about the flash device */ @@ -238,23 +308,30 @@ int main (int argc,char *argv[]) if (ioctl (dev_fd,MEMGETINFO,&mtd) < 0) { DEBUG("ioctl(): %m\n"); - log_printf (LOG_ERROR,"This doesn't seem to be a valid MTD flash device!\n"); - exit (EXIT_FAILURE); + log_failure("This doesn't seem to be a valid MTD flash device!\n"); } /* get some info about the file we want to copy */ fil_fd = safe_open (filename,O_RDONLY); if (fstat (fil_fd,&filestat) < 0) - { - log_printf (LOG_ERROR,"While trying to get the file status of %s: %m\n",filename); - exit (EXIT_FAILURE); - } + log_failure("While trying to get the file status of %s: %m\n",filename); /* does it fit into the device/partition? */ if (filestat.st_size > mtd.size) + log_failure("%s won't fit into %s!\n",filename,device); + + src = malloc(mtd.erasesize); + if (!src) + log_failure("Malloc failed"); + + dest = malloc(mtd.erasesize); + if (!dest) + log_failure("Malloc failed"); + + /* diff block flashcp */ + if (flags & FLAG_PARTITION) { - log_printf (LOG_ERROR,"%s won't fit into %s!\n",filename,device); - exit (EXIT_FAILURE); + goto DIFF_BLOCKS; } /***************************************************** @@ -275,37 +352,24 @@ int main (int argc,char *argv[]) erase.length *= mtd.erasesize; } - if (flags & FLAG_VERBOSE) + if (verbose) { /* if the user wants verbose output, erase 1 block at a time and show him/her what's going on */ int blocks = erase.length / mtd.erasesize; erase.length = mtd.erasesize; - log_printf (LOG_NORMAL,"Erasing blocks: 0/%d (0%%)",blocks); + log_verbose ("Erasing blocks: 0/%d (0%%)",blocks); for (i = 1; i <= blocks; i++) { - log_printf (LOG_NORMAL,"\rErasing blocks: %d/%d (%d%%)",i,blocks,PERCENTAGE (i,blocks)); - if (ioctl (dev_fd,MEMERASE,&erase) < 0) - { - log_printf (LOG_NORMAL,"\n"); - log_printf (LOG_ERROR, - "While erasing blocks 0x%.8x-0x%.8x on %s: %m\n", - (unsigned int) erase.start,(unsigned int) (erase.start + erase.length),device); - exit (EXIT_FAILURE); - } + log_verbose ("\rErasing blocks: %d/%d (%d%%)",i,blocks,PERCENTAGE (i,blocks)); + safe_memerase(dev_fd,device,&erase); erase.start += mtd.erasesize; } - log_printf (LOG_NORMAL,"\rErasing blocks: %d/%d (100%%)\n",blocks,blocks); + log_verbose ("\rErasing blocks: %d/%d (100%%)\n",blocks,blocks); } else { /* if not, erase the whole chunk in one shot */ - if (ioctl (dev_fd,MEMERASE,&erase) < 0) - { - log_printf (LOG_ERROR, - "While erasing blocks from 0x%.8x-0x%.8x on %s: %m\n", - (unsigned int) erase.start,(unsigned int) (erase.start + erase.length),device); - exit (EXIT_FAILURE); - } + safe_memerase(dev_fd,device,&erase); } DEBUG("Erased %u / %luk bytes\n",erase.length,filestat.st_size); @@ -313,50 +377,68 @@ int main (int argc,char *argv[]) * write the entire file to flash * **********************************/ - if (flags & FLAG_VERBOSE) log_printf (LOG_NORMAL,"Writing data: 0k/%lluk (0%%)",KB ((unsigned long long)filestat.st_size)); size = filestat.st_size; - i = BUFSIZE; + i = mtd.erasesize; written = 0; + + if ((flags & FLAG_WR_LAST) && (filestat.st_size > wrlast_len)) { + if (wrlast_len > mtd.erasesize) + log_failure("The wrlast (%lluk) is larger than erasesize (%lluk)\n", KB (wrlast_len), KB ((unsigned long long)mtd.erasesize)); + + if (size < mtd.erasesize) i = size; + + log_verbose ("Reading %lluk of data to write last.\n", KB ((unsigned long long)wrlast_len)); + wrlast_buf = malloc(wrlast_len); + if (!wrlast_buf) + log_failure("Malloc failed"); + safe_read (fil_fd, filename, wrlast_buf, wrlast_len); + safe_lseek(dev_fd, wrlast_len, SEEK_SET, device); + written += wrlast_len; + size -= wrlast_len; + i -= wrlast_len; + + log_verbose ("Writing remaining erase block data: %dk/%lluk (%llu%%)\n", + KB (written + i), + KB ((unsigned long long)filestat.st_size), + PERCENTAGE ((unsigned long long)written + i, (unsigned long long)filestat.st_size)); + safe_read (fil_fd, filename, src, i); + safe_write(dev_fd, src, i, written, (unsigned long long)filestat.st_size, device); + + written += i; + size -= i; + i = mtd.erasesize; + } else { + log_verbose ("Writing data: 0k/%lluk (0%%)",KB ((unsigned long long)filestat.st_size)); + } + while (size) { - if (size < BUFSIZE) i = size; - if (flags & FLAG_VERBOSE) - log_printf (LOG_NORMAL,"\rWriting data: %dk/%lluk (%llu%%)", - KB (written + i), - KB ((unsigned long long)filestat.st_size), - PERCENTAGE (written + i,(unsigned long long)filestat.st_size)); + if (size < mtd.erasesize) i = size; + log_verbose ("\rWriting data: %dk/%lluk (%llu%%)", + KB (written + i), + KB ((unsigned long long)filestat.st_size), + PERCENTAGE ((unsigned long long)written + i,(unsigned long long)filestat.st_size)); /* read from filename */ - safe_read (fil_fd,filename,src,i,flags & FLAG_VERBOSE); + safe_read (fil_fd,filename,src,i); /* write to device */ - result = write (dev_fd,src,i); - if (i != result) - { - if (flags & FLAG_VERBOSE) log_printf (LOG_NORMAL,"\n"); - if (result < 0) - { - log_printf (LOG_ERROR, - "While writing data to 0x%.8lx-0x%.8lx on %s: %m\n", - written,written + i,device); - exit (EXIT_FAILURE); - } - log_printf (LOG_ERROR, - "Short write count returned while writing to x%.8zx-0x%.8zx on %s: %zu/%llu bytes written to flash\n", - written,written + i,device,written + result,(unsigned long long)filestat.st_size); - exit (EXIT_FAILURE); - } + safe_write(dev_fd,src,i,written,(unsigned long long)filestat.st_size,device); written += i; size -= i; } - if (flags & FLAG_VERBOSE) - log_printf (LOG_NORMAL, - "\rWriting data: %lluk/%lluk (100%%)\n", - KB ((unsigned long long)filestat.st_size), - KB ((unsigned long long)filestat.st_size)); + log_verbose ("\rWriting data: %lluk/%lluk (100%%)\n", + KB ((unsigned long long)filestat.st_size), + KB ((unsigned long long)filestat.st_size)); DEBUG("Wrote %d / %lluk bytes\n",written,(unsigned long long)filestat.st_size); + if ((flags & FLAG_WR_LAST) && (filestat.st_size > wrlast_len)) { + log_verbose ("Writing %lluk of the write last data.\n", KB ((unsigned long long)wrlast_len)); + safe_rewind (dev_fd, device); + safe_write(dev_fd, wrlast_buf, wrlast_len, 0, wrlast_len, device); + } + /********************************** * verify that flash == file data * **********************************/ @@ -364,43 +446,96 @@ int main (int argc,char *argv[]) safe_rewind (fil_fd,filename); safe_rewind (dev_fd,device); size = filestat.st_size; - i = BUFSIZE; + i = mtd.erasesize; written = 0; - if (flags & FLAG_VERBOSE) log_printf (LOG_NORMAL,"Verifying data: 0k/%lluk (0%%)",KB ((unsigned long long)filestat.st_size)); + log_verbose ("Verifying data: 0k/%lluk (0%%)",KB ((unsigned long long)filestat.st_size)); while (size) { - if (size < BUFSIZE) i = size; - if (flags & FLAG_VERBOSE) - log_printf (LOG_NORMAL, - "\rVerifying data: %luk/%lluk (%llu%%)", - KB (written + i), - KB ((unsigned long long)filestat.st_size), - PERCENTAGE (written + i,(unsigned long long)filestat.st_size)); + if (size < mtd.erasesize) i = size; + log_verbose ("\rVerifying data: %luk/%lluk (%llu%%)", + KB (written + i), + KB ((unsigned long long)filestat.st_size), + PERCENTAGE ((unsigned long long)written + i,(unsigned long long)filestat.st_size)); /* read from filename */ - safe_read (fil_fd,filename,src,i,flags & FLAG_VERBOSE); + safe_read (fil_fd,filename,src,i); /* read from device */ - safe_read (dev_fd,device,dest,i,flags & FLAG_VERBOSE); + safe_read (dev_fd,device,dest,i); /* compare buffers */ if (memcmp (src,dest,i)) - { - log_printf (LOG_ERROR, - "File does not seem to match flash data. First mismatch at 0x%.8zx-0x%.8zx\n", + log_failure("File does not seem to match flash data. First mismatch at 0x%.8zx-0x%.8zx\n", written,written + i); - exit (EXIT_FAILURE); - } written += i; size -= i; } - if (flags & FLAG_VERBOSE) - log_printf (LOG_NORMAL, - "\rVerifying data: %lluk/%lluk (100%%)\n", - KB ((unsigned long long)filestat.st_size), - KB ((unsigned long long)filestat.st_size)); + log_verbose ("\rVerifying data: %lluk/%lluk (100%%)\n", + KB ((unsigned long long)filestat.st_size), + KB ((unsigned long long)filestat.st_size)); DEBUG("Verified %d / %lluk bytes\n",written,(unsigned long long)filestat.st_size); exit (EXIT_SUCCESS); + + /********************************************* + * Copy different blocks from file to device * + ********************************************/ +DIFF_BLOCKS: + safe_rewind (fil_fd,filename); + safe_rewind (dev_fd,device); + size = filestat.st_size; + i = mtd.erasesize; + erase.start = 0; + erase.length = (filestat.st_size + mtd.erasesize - 1) / mtd.erasesize; + erase.length *= mtd.erasesize; + written = 0; + unsigned long current_dev_block = 0; + int diffBlock = 0; + int blocks = erase.length / mtd.erasesize; + erase.length = mtd.erasesize; + + log_verbose ("\rProcessing blocks: 0/%d (%d%%)", blocks, PERCENTAGE (0,blocks)); + for (int s = 1; s <= blocks; s++) + { + if (size < mtd.erasesize) i = size; + log_verbose ("\rProcessing blocks: %d/%d (%d%%)", s, blocks, PERCENTAGE (s,blocks)); + + /* read from filename */ + safe_read (fil_fd,filename,src,i); + + /* read from device */ + current_dev_block = safe_lseek(dev_fd, 0, SEEK_CUR, device); + safe_read (dev_fd,device,dest,i); + + /* compare buffers, if not the same, erase and write the block */ + if (memcmp (src,dest,i)) + { + diffBlock++; + /* erase block */ + safe_lseek(dev_fd, current_dev_block, SEEK_SET, device); + safe_memerase(dev_fd,device,&erase); + + /* write to device */ + safe_lseek(dev_fd, current_dev_block, SEEK_SET, device); + safe_write(dev_fd,src,i,written,(unsigned long long)filestat.st_size,device); + + /* read from device */ + safe_lseek(dev_fd, current_dev_block, SEEK_SET, device); + safe_read (dev_fd,device,dest,i); + + /* compare buffers for write success */ + if (memcmp (src,dest,i)) + log_failure("File does not seem to match flash data. First mismatch at 0x%.8zx-0x%.8zx\n", + written,written + i); + } + + erase.start += i; + written += i; + size -= i; + } + + log_verbose ("\ndiff blocks: %d\n", diffBlock); + + exit (EXIT_SUCCESS); } diff --git a/misc-utils/ftl_check.c b/misc-utils/ftl_check.c index 5b2dae5..fe6a91a 100644 --- a/misc-utils/ftl_check.c +++ b/misc-utils/ftl_check.c @@ -121,6 +121,10 @@ static void check_partition(int fd) /* Create basic block allocation table for control blocks */ nbam = (mtd.erasesize >> hdr.BlockSize); bam = malloc(nbam * sizeof(u_int)); + if (!bam) { + perror("malloc failed"); + return; + } for (i = 0; i < le16_to_cpu(hdr.NumEraseUnits); i++) { if (lseek(fd, (i << hdr.EraseUnitSize), SEEK_SET) == -1) { diff --git a/misc-utils/lsmtd.8 b/misc-utils/lsmtd.8 index 3061c40..198c8a7 100644 --- a/misc-utils/lsmtd.8 +++ b/misc-utils/lsmtd.8 @@ -14,7 +14,7 @@ command reads the .B sysfs filesystem to gather information. Alternatively, the .B procfs -filesystem and ioctl interfaces are used, should the sysfs filesytem not +filesystem and ioctl interfaces are used, should the sysfs filesystem not be available. .PP The command prints all MTD and UBI devices in a pretty-printed list format by diff --git a/misc-utils/lsmtd.c b/misc-utils/lsmtd.c index 24a30ac..b62b69f 100644 --- a/misc-utils/lsmtd.c +++ b/misc-utils/lsmtd.c @@ -72,11 +72,11 @@ static struct column cols[] = { { "SUB-SIZE", "subpage size", COL_SUBSIZE, COL_DT_SIZE, 0 }, { "OOB-SIZE", "out of band data size", COL_OOBSIZE, COL_DT_SIZE, 0 }, { "RO", "read-only device", COL_RO, COL_DT_BOOL, 0 }, - { "CORRUPTED", "wheather an UBI volume is corrupted", + { "CORRUPTED", "whether an UBI volume is corrupted", COL_CORRUPTED, COL_DT_BOOL, 0 }, { "REGIONS", "number of additional erase regions", COL_REGION, COL_DT_NUMBER, 0 }, - { "BB", "wheather the MTD device may have bad eraseblocks", + { "BB", "whether the MTD device may have bad eraseblocks", COL_BB, COL_DT_BOOL, 0 }, { "MAX-EC", "current highest erase counter value on UBI devices", COL_MAXEC, COL_DT_NUMBER, 0 }, @@ -107,7 +107,7 @@ static NORETURN void usage(int status) " -r, --raw Use raw output format\n" " -P, --pairs Use key=\"value\" output format\n" " -J, --json Use JSON output format\n" -" -o, --output <list> Comma seperated list of columns to print\n" +" -o, --output <list> Comma separated list of columns to print\n" " -O, --output-all Print all columns\n" " -x, --sort <column> Sort output by <column>\n" " -m, --no-ubi Do not display information about UBI devices/volumes\n" diff --git a/misc-utils/lsmtd_scan.c b/misc-utils/lsmtd_scan.c index cec7b5c..9331c5b 100644 --- a/misc-utils/lsmtd_scan.c +++ b/misc-utils/lsmtd_scan.c @@ -162,7 +162,7 @@ int scan_ubi(libubi_t lib_ubi) } if (j == num_mtd_devices) { - fprintf(stderr, "Cannot find mtd device %d refered to " + fprintf(stderr, "Cannot find mtd device %d referred to " "by ubi device %d\n", dev_info.mtd_num, dev_info.dev_num); return -1; diff --git a/misc-utils/mtd_debug.c b/misc-utils/mtd_debug.c index c0b7109..abee5e3 100644 --- a/misc-utils/mtd_debug.c +++ b/misc-utils/mtd_debug.c @@ -348,6 +348,7 @@ int main(int argc, char *argv[]) { int err = 0, fd; int open_flag; + char *dev; enum { OPT_INFO, @@ -369,8 +370,12 @@ int main(int argc, char *argv[]) showusage(); /* open device */ + dev = mtd_find_dev_node(argv[2]); + if (!dev) + errmsg_die("Failed to find MTD device %s", argv[2]); + open_flag = (option == OPT_INFO || option == OPT_READ) ? O_RDONLY : O_RDWR; - if ((fd = open(argv[2], O_SYNC | open_flag)) < 0) + if ((fd = open(dev, O_SYNC | open_flag)) < 0) errmsg_die("open()"); switch (option) { diff --git a/misc-utils/mtdpart.c b/misc-utils/mtdpart.c index ba35d87..a341148 100644 --- a/misc-utils/mtdpart.c +++ b/misc-utils/mtdpart.c @@ -36,8 +36,10 @@ static void display_help(int status) " -h, --help Display this help and exit\n" " -V, --version Output version information and exit\n" "\n" +" <MTD_DEVICE> MTD device node or 'mtd:<name>'\n" +"\n" "START location and SIZE of the partition are in bytes. They should align on\n" -"eraseblock size.\n", +"eraseblock size. If SIZE is 0 the partition will go to end of MTD device.\n", PROGRAM_NAME ); exit(status); @@ -106,7 +108,10 @@ static void process_options(int argc, char * const argv[]) display_help(EXIT_FAILURE); const char *s_command = argv[optind++]; - mtddev = argv[optind++]; + mtddev = mtd_find_dev_node(argv[optind]); + if (!mtddev) + errmsg_die("MTD device not found %s", argv[optind]); + optind++; if (strcmp(s_command, "del") == 0 && (argc - optind) == 1) { const char *s_part_no = argv[optind++]; diff --git a/nand-utils/Makemodule.am b/nand-utils/Makemodule.am index d75b0cb..cee6777 100644 --- a/nand-utils/Makemodule.am +++ b/nand-utils/Makemodule.am @@ -7,26 +7,24 @@ nandwrite_LDADD = libmtd.a nandtest_SOURCES = nand-utils/nandtest.c nandtest_LDADD = libmtd.a -nftldump_SOURCES = nand-utils/nftldump.c +nftldump_SOURCES = nand-utils/nftldump.c include/mtd_swab.h +nftldump_SOURCES += include/mtd/nftl-user.h include/mtd/ftl-user.h nftldump_LDADD = libmtd.a -nftl_format_SOURCES = nand-utils/nftl_format.c +nftl_format_SOURCES = nand-utils/nftl_format.c include/mtd_swab.h +nftl_format_SOURCES += include/mtd/nftl-user.h include/mtd/ftl-user.h nftl_format_LDADD = libmtd.a -NAND_BINS = \ - nanddump nandwrite nandtest nftldump nftl_format +nandflipbits_SOURCES = nand-utils/nandflipbits.c +nandflipbits_LDADD = libmtd.a NAND_SH = \ nand-utils/load_nandsim.sh EXTRA_DIST += $(NAND_SH) -sbin_PROGRAMS += $(NAND_BINS) +sbin_PROGRAMS += nanddump nandwrite nandtest nftldump nftl_format nandflipbits if BUILD_TESTS -if INSTALL_TESTS -pkglibexec_SCRIPTS += $(NAND_SH) -else -noinst_SCRIPTS += $(NAND_SH) -endif +test_SCRIPTS += $(NAND_SH) endif diff --git a/nand-utils/nanddump.c b/nand-utils/nanddump.c index d7fc320..b4de05e 100644 --- a/nand-utils/nanddump.c +++ b/nand-utils/nanddump.c @@ -54,6 +54,7 @@ static void display_help(int status) "-s addr --startaddress=addr Start address\n" " --skip-bad-blocks-to-start\n" " Skip bad blocks when seeking to the start address\n" +"-C --continuous Continuous read up to a block of data at a time\n" "\n" "--bb=METHOD, where METHOD can be `padbad', `dumpbad', or `skipbad':\n" " padbad: dump flash data, substituting 0xFF for any bad blocks\n" @@ -89,6 +90,7 @@ static bool quiet = false; // suppress diagnostic output static bool canonical = false; // print nice + ascii static bool forcebinary = false; // force printing binary to tty static bool skip_bad_blocks_to_start = false; +static bool continuous = false; // leverage continuous reads static enum { padbad, // dump flash data, substituting 0xFF for any bad blocks @@ -100,10 +102,11 @@ static void process_options(int argc, char * const argv[]) { int error = 0; bool oob_default = true; + char *dumpfile_tmp = NULL; for (;;) { int option_index = 0; - static const char short_options[] = "hs:f:l:opqncaV"; + static const char short_options[] = "hs:f:l:opqncaVC"; static const struct option long_options[] = { {"version", no_argument, 0, 'V'}, {"bb", required_argument, 0, 0}, @@ -119,6 +122,7 @@ static void process_options(int argc, char * const argv[]) {"length", required_argument, 0, 'l'}, {"noecc", no_argument, 0, 'n'}, {"quiet", no_argument, 0, 'q'}, + {"continuous", no_argument, 0, 'C'}, {0, 0, 0, 0}, }; @@ -162,8 +166,8 @@ static void process_options(int argc, char * const argv[]) start_addr = simple_strtoll(optarg, &error); break; case 'f': - free(dumpfile); - dumpfile = xstrdup(optarg); + free(dumpfile_tmp); + dumpfile = dumpfile_tmp = xstrdup(optarg); break; case 'l': length = simple_strtoll(optarg, &error); @@ -191,6 +195,9 @@ static void process_options(int argc, char * const argv[]) case 'n': noecc = true; break; + case 'C': + continuous = true; + break; case 'h': display_help(EXIT_SUCCESS); break; @@ -220,6 +227,13 @@ static void process_options(int argc, char * const argv[]) exit(EXIT_FAILURE); } + if (continuous && !omitoob) { + fprintf(stderr, "Sequential/continuous reads (when available) will\n" + "always skip OOB data, so it is not possible to \n" + "request both at the same time.\n"); + exit(EXIT_FAILURE); + } + if ((argc - optind) != 1 || error) display_help(EXIT_FAILURE); @@ -332,7 +346,7 @@ static int ofd_write(int ofd, const void *buf, size_t nbyte) */ int main(int argc, char * const argv[]) { - long long ofs, end_addr = 0; + long long ofs, end_addr = 0, readbuf_sz; long long blockstart = 1; int i, fd, ofd = 0, bs, badblock = 0; struct mtd_dev_info mtd; @@ -362,8 +376,9 @@ int main(int argc, char * const argv[]) return errmsg("mtd_get_dev_info failed"); /* Allocate buffers */ + readbuf_sz = mtd.eb_size; oobbuf = xmalloc(mtd.oob_size); - readbuf = xmalloc(mtd.min_io_size); + readbuf = xmalloc(readbuf_sz); if (noecc) { if (ioctl(fd, MTDFILEMODE, MTD_FILE_MODE_RAW) != 0) { @@ -427,8 +442,6 @@ int main(int argc, char * const argv[]) if (!length || end_addr > mtd.size) end_addr = mtd.size; - bs = mtd.min_io_size; - /* Print informative message */ if (!quiet) { fprintf(stderr, "Block size %d, page size %d, OOB size %d\n", @@ -440,6 +453,8 @@ int main(int argc, char * const argv[]) /* Dump the flash contents */ for (ofs = start_addr; ofs < end_addr; ofs += bs) { + long long size_left = end_addr - ofs; + /* Check for bad block */ if (bb_method == dumpbad) { badblock = 0; @@ -453,16 +468,21 @@ int main(int argc, char * const argv[]) } } + if (continuous) + bs = MIN(size_left, mtd.eb_size); + else + bs = mtd.min_io_size; + if (badblock) { /* skip bad block, increase end_addr */ if (bb_method == skipbad) { end_addr += mtd.eb_size; - ofs += mtd.eb_size - bs; + ofs += mtd.eb_size - mtd.min_io_size; if (end_addr > mtd.size) end_addr = mtd.size; continue; } - memset(readbuf, 0xff, bs); + memset(readbuf, 0xff, readbuf_sz); } else { /* Read page data and exit on failure */ if (mtd_read(&mtd, fd, ofs / mtd.eb_size, ofs % mtd.eb_size, readbuf, bs)) { @@ -499,7 +519,6 @@ int main(int argc, char * const argv[]) } } else { /* Write requested length if oob is omitted */ - size_t size_left = end_addr - ofs; if (omitoob && (size_left < bs)) err = ofd_write(ofd, readbuf, size_left); else diff --git a/nand-utils/nandflipbits.c b/nand-utils/nandflipbits.c new file mode 100644 index 0000000..a417189 --- /dev/null +++ b/nand-utils/nandflipbits.c @@ -0,0 +1,326 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2014 Bootlin + * + * Authors: Boris Brezillon <boris.brezillon@collabora.com> + * Miquel Raynal <miquel.raynal@bootlin.com> + * + * Overview: + * This utility manually flips specified bits in a NAND flash. + */ + +#define PROGRAM_NAME "nandflipbits" + +#include <mtd/mtd-user.h> +#include <sys/ioctl.h> +#include <unistd.h> +#include <string.h> +#include <stdlib.h> +#include <libmtd.h> +#include <getopt.h> +#include <stdio.h> +#include <fcntl.h> + +#include "common.h" + +struct bit_flip { + uint32_t block; + uint64_t offset; + int bit; + bool done; +}; + +static void usage(int status) +{ + fprintf(status == EXIT_SUCCESS ? stdout : stderr, +"Usage: "PROGRAM_NAME" [OPTIONS] <device> <bit>@<address> [<bit>@<address>...]\n" +"\n" +" Test ECC engines, see if they match the specified correction strength:\n" +" * Reads in raw mode the data from an MTD device\n" +" * Flips the indicated bit(s)\n" +" * Write it back in raw mode.\n" +"\n" +" -h, --help Display this help and exit\n" +" -o, --oob Provided addresses take OOB area into account\n" +" -q, --quiet Don't display progress messages\n" +"\n" + ); + exit(status); +} + +static const char *mtd_device; +static bool quiet = false; +static bool oob_mode = false; +static struct bit_flip *bits_to_flip; +static int nbits_to_flip = 0; + +static void process_options(int argc, char * const argv[]) +{ + int error = 0; + int i; + + for (;;) { + int option_index = 0; + static const char short_options[] = "hoq"; + static const struct option long_options[] = { + {"help", no_argument, 0, 'h'}, + {"oob", no_argument, 0, 'o'}, + {"quiet", no_argument, 0, 'q'}, + {0, 0, 0, 0}, + }; + + int c = getopt_long(argc, argv, short_options, + long_options, &option_index); + if (c == EOF) + break; + + switch (c) { + case 'q': + quiet = true; + break; + case 'o': + oob_mode = true; + break; + case 'h': + usage(EXIT_SUCCESS); + break; + case '?': + default: + error++; + break; + } + } + + argc -= optind; + argv += optind; + + /* + * There must be at least the MTD device node path argument remaining + * and a list of minimum one 'bits-to-flip'. + */ + + if (argc < 2 || error) + usage(EXIT_FAILURE); + + /* MTD device */ + mtd_device = argv[0]; + argc--; + argv++; + + /* Parse the bits to flip */ + nbits_to_flip = argc; + bits_to_flip = malloc(sizeof(*bits_to_flip) * nbits_to_flip); + if (!bits_to_flip) + exit(EXIT_FAILURE); + + for (i = 0; i < nbits_to_flip; i++) { + struct bit_flip *bit_to_flip = &bits_to_flip[i]; + char *desc = argv[i]; + + bit_to_flip->bit = strtol(desc, &desc, 0); + if (errno || bit_to_flip->bit > 7) + goto free_bits; + + if (!desc || *desc++ != '@') + goto free_bits; + + bit_to_flip->offset = strtol(desc, &desc, 0); + if (errno) + goto free_bits; + } + + return; + +free_bits: + free(bits_to_flip); + + fprintf(stderr, "Invalid bit description\n"); + + exit(EXIT_FAILURE); +} + +int main(int argc, char **argv) +{ + struct mtd_dev_info mtd; + libmtd_t mtd_desc; + uint64_t mtdlen; + uint32_t pagelen, pages_per_blk, blklen; + uint8_t *buffer; + int fd, ret, i; + + process_options(argc, argv); + + /* Open the libmtd */ + mtd_desc = libmtd_open(); + if (!mtd_desc) { + fprintf(stderr, "Cannot initialize libmtd\n"); + ret = EXIT_FAILURE; + goto free_bits; + } + + /* Fill in MTD device capability structure */ + ret = mtd_get_dev_info(mtd_desc, mtd_device, &mtd); + if (ret < 0) { + fprintf(stderr, "Cannot retrieve MTD device information\n"); + ret = EXIT_FAILURE; + goto close_lib; + } + + /* Verify we are using a NAND device */ + if (mtd.type != MTD_NANDFLASH && mtd.type != MTD_MLCNANDFLASH) { + fprintf(stderr, "%s is not a NAND flash\n", mtd_device); + ret = EXIT_FAILURE; + goto close_lib; + } + + /* Open the MTD device */ + fd = open(mtd_device, O_RDWR); + if (fd < 0) { + fprintf(stderr, "Cannot open %s\n", mtd_device); + ret = EXIT_FAILURE; + goto close_lib; + } + + /* Select raw mode */ + ret = ioctl(fd, MTDFILEMODE, MTD_FILE_MODE_RAW); + if (ret) { + fprintf(stderr, "Unavailable raw mode ioctl\n"); + ret = EXIT_FAILURE; + goto close_fd; + } + + pagelen = mtd.min_io_size + (oob_mode ? mtd.oob_size : 0); + pages_per_blk = mtd.eb_size / mtd.min_io_size; + blklen = pages_per_blk * pagelen; + mtdlen = (uint64_t)blklen * (uint64_t)mtd.eb_cnt; + buffer = malloc((mtd.min_io_size + mtd.oob_size) * pages_per_blk); + if (!buffer) { + ret = EXIT_FAILURE; + goto close_fd; + } + + for (i = 0; i < nbits_to_flip; i++) { + int page; + + if (bits_to_flip[i].offset >= mtdlen) { + fprintf(stderr, "Invalid byte offset %" PRId64 + " (max %" PRId64 ")\n", + bits_to_flip[i].offset, mtdlen); + ret = EXIT_FAILURE; + goto free_buf; + } + + bits_to_flip[i].block = bits_to_flip[i].offset / blklen; + bits_to_flip[i].offset %= blklen; + page = bits_to_flip[i].offset / pagelen; + bits_to_flip[i].offset = (page * + (mtd.min_io_size + mtd.oob_size)) + + (bits_to_flip[i].offset % pagelen); + } + + while (1) { + struct bit_flip *bit_to_flip = NULL; + int blkoffs; + int bufoffs; + + /* Look for the next bitflip to insert */ + for (i = 0; i < nbits_to_flip; i++) { + if (bits_to_flip[i].done == false) { + bit_to_flip = &bits_to_flip[i]; + break; + } + } + + if (!bit_to_flip) { + ret = EXIT_SUCCESS; + break; + } + + /* Read the content of all the pages of a block */ + blkoffs = 0; + bufoffs = 0; + for (i = 0; i < pages_per_blk; i++) { + ret = mtd_read(&mtd, fd, bit_to_flip->block, blkoffs, + buffer + bufoffs, mtd.min_io_size); + if (ret) { + fprintf(stderr, "MTD read failure\n"); + ret = EXIT_FAILURE; + goto free_buf; + } + + bufoffs += mtd.min_io_size; + + ret = mtd_read_oob(mtd_desc, &mtd, fd, + (unsigned long long)bit_to_flip->block * mtd.eb_size + + blkoffs, + mtd.oob_size, buffer + bufoffs); + if (ret) { + fprintf(stderr, "MTD OOB read failure\n"); + ret = EXIT_FAILURE; + goto free_buf; + } + + bufoffs += mtd.oob_size; + blkoffs += mtd.min_io_size; + } + + /* Flip all bits that are located in this particular block */ + for (i = 0; i < nbits_to_flip; i++) { + unsigned char val, mask; + + if (bits_to_flip[i].block != bit_to_flip->block) + continue; + + mask = 1 << bits_to_flip[i].bit; + val = buffer[bits_to_flip[i].offset] & mask; + if (val) + buffer[bits_to_flip[i].offset] &= ~mask; + else + buffer[bits_to_flip[i].offset] |= mask; + } + + /* Erase the block */ + ret = mtd_erase(mtd_desc, &mtd, fd, bit_to_flip->block); + if (ret) { + fprintf(stderr, "MTD erase failure\n"); + ret = EXIT_FAILURE; + goto free_buf; + } + + /* Rewrite the pages, still in raw mode, with the bitflips */ + blkoffs = 0; + bufoffs = 0; + for (i = 0; i < pages_per_blk; i++) { + ret = mtd_write(mtd_desc, &mtd, fd, bit_to_flip->block, + blkoffs, buffer + bufoffs, mtd.min_io_size, + buffer + bufoffs + mtd.min_io_size, + mtd.oob_size, + MTD_OPS_RAW); + if (ret) { + fprintf(stderr, "MTD write failure\n"); + ret = EXIT_FAILURE; + goto free_buf; + } + + blkoffs += mtd.min_io_size; + bufoffs += mtd.min_io_size + mtd.oob_size; + } + + /* Mark the added bitflips as done */ + for (i = 0; i < nbits_to_flip; i++) { + if (bits_to_flip[i].block == bit_to_flip->block) + bits_to_flip[i].done = true; + } + } + +free_buf: + free(buffer); +close_fd: + close(fd); +close_lib: + libmtd_close(mtd_desc); +free_bits: + free(bits_to_flip); + + exit(ret); +} diff --git a/nand-utils/nandtest.c b/nand-utils/nandtest.c index 06dec25..cac0dde 100644 --- a/nand-utils/nandtest.c +++ b/nand-utils/nandtest.c @@ -8,6 +8,7 @@ #include <string.h> #include <time.h> #include <unistd.h> +#include <limits.h> #include <sys/stat.h> #include <sys/ioctl.h> #include <sys/types.h> @@ -144,6 +145,26 @@ static int erase_and_write(loff_t ofs, unsigned char *data, unsigned char *rbuf, return 0; } +static uint64_t get_mem_size(const char* device) +{ + const char* p = strrchr(device, '/'); + char path[PATH_MAX]; + int fd; + + snprintf(path, sizeof(path), "/sys/class/mtd/%s/size", p); + fd = open(path, O_RDONLY); + if (fd >= 0) { + char buffer[32]; + ssize_t n = read(fd, buffer, sizeof(buffer)); + close(fd); + if (n > 0) { + return strtoull(buffer, NULL, 0); + } + } + + fprintf(stderr, "Can't read size from %s\n", path); + exit(1); +} /* * Main program @@ -156,8 +177,9 @@ int main(int argc, char **argv) int nr_passes = 1; int nr_reads = 4; int keep_contents = 0; - uint32_t offset = 0; - uint32_t length = -1; + uint64_t offset = 0; + uint64_t length = -1; + uint64_t mem_size = 0; int error = 0; seed = time(NULL); @@ -212,11 +234,11 @@ int main(int argc, char **argv) break; case 'o': - offset = simple_strtoul(optarg, &error); + offset = simple_strtoull(optarg, &error); break; case 'l': - length = simple_strtoul(optarg, &error); + length = simple_strtoull(optarg, &error); break; } @@ -238,29 +260,34 @@ int main(int argc, char **argv) exit(1); } + mem_size = get_mem_size(argv[optind]); + if (length == -1) - length = meminfo.size; + length = mem_size; if (offset % meminfo.erasesize) { - fprintf(stderr, "Offset %x not multiple of erase size %x\n", + fprintf(stderr, "Offset %" PRIx64 + " not multiple of erase size %x\n", offset, meminfo.erasesize); exit(1); } if (length % meminfo.erasesize) { - fprintf(stderr, "Length %x not multiple of erase size %x\n", + fprintf(stderr, "Length %" PRIx64 + " not multiple of erase size %x\n", length, meminfo.erasesize); exit(1); } - if (length + offset > meminfo.size) { - fprintf(stderr, "Length %x + offset %x exceeds device size %x\n", - length, offset, meminfo.size); + if (length + offset > mem_size) { + fprintf(stderr, "Length %" PRIx64 " + offset %" PRIx64 + " exceeds device size %" PRIx64 "\n", + length, offset, mem_size); exit(1); } wbuf = malloc(meminfo.erasesize * 3); if (!wbuf) { fprintf(stderr, "Could not allocate %d bytes for buffer\n", - meminfo.erasesize * 2); + meminfo.erasesize * 3); exit(1); } rbuf = wbuf + meminfo.erasesize; diff --git a/nand-utils/nandwrite.c b/nand-utils/nandwrite.c index e8a210c..cd53a17 100644 --- a/nand-utils/nandwrite.c +++ b/nand-utils/nandwrite.c @@ -280,6 +280,7 @@ int main(int argc, char * const argv[]) libmtd_t mtd_desc; int ebsize_aligned; uint8_t write_mode; + size_t all_ffs_cnt = 0; process_options(argc, argv); @@ -417,6 +418,8 @@ int main(int argc, char * const argv[]) */ while ((imglen > 0 || writebuf < filebuf + filebuf_len) && mtdoffset < mtd.size) { + bool allffs; + /* * New eraseblock, check for bad block(s) * Stay in the loop to be sure that, if mtdoffset changes because @@ -555,7 +558,8 @@ int main(int argc, char * const argv[]) } ret = 0; - if (!skipallffs || !buffer_check_pattern(writebuf, mtd.min_io_size, 0xff)) { + allffs = buffer_check_pattern(writebuf, mtd.min_io_size, 0xff); + if (!allffs || !skipallffs) { /* Write out data */ ret = mtd_write(mtd_desc, &mtd, fd, mtdoffset / mtd.eb_size, mtdoffset % mtd.eb_size, @@ -564,6 +568,8 @@ int main(int argc, char * const argv[]) writeoob ? oobbuf : NULL, writeoob ? mtd.oob_size : 0, write_mode); + if (!ret && allffs) + all_ffs_cnt++; } if (ret) { @@ -615,6 +621,11 @@ closeall: || (writebuf < filebuf + filebuf_len)) sys_errmsg_die("Data was only partially written due to error"); + if (all_ffs_cnt) { + fprintf(stderr, "Written %zu blocks containing only 0xff bytes\n", all_ffs_cnt); + fprintf(stderr, "Those block may be incorrectly treated as empty!\n"); + } + /* Return happy */ return EXIT_SUCCESS; } diff --git a/nor-utils/Makemodule.am b/nor-utils/Makemodule.am index a5a0d4d..441a72e 100644 --- a/nor-utils/Makemodule.am +++ b/nor-utils/Makemodule.am @@ -1,10 +1,7 @@ -rfddump_SOURCES = nor-utils/rfddump.c +rfddump_SOURCES = nor-utils/rfddump.c include/mtd_swab.h rfddump_LDADD = libmtd.a rfdformat_SOURCES = nor-utils/rfdformat.c rfdformat_LDADD = libmtd.a -NOR_BINS = \ - rfddump rfdformat - -sbin_PROGRAMS += $(NOR_BINS) +sbin_PROGRAMS += rfddump rfdformat diff --git a/nor-utils/rfddump.c b/nor-utils/rfddump.c index 01ab4c2..d6d7f68 100644 --- a/nor-utils/rfddump.c +++ b/nor-utils/rfddump.c @@ -256,12 +256,10 @@ int main(int argc, char *argv[]) if (!rfd.sector_map) { perror(PROGRAM_NAME); close(fd); - free(rfd.sector_map); + free(rfd.header); return 2; } - rfd.mtd_filename = rfd.mtd_filename; - for (i=0; i<rfd.sector_count; i++) rfd.sector_map[i] = -1; diff --git a/tests/checkfs/Makemodule.am b/tests/checkfs/Makemodule.am index 6e1314f..631ab92 100644 --- a/tests/checkfs/Makemodule.am +++ b/tests/checkfs/Makemodule.am @@ -1,22 +1,10 @@ checkfs_SOURCES = tests/checkfs/checkfs.c tests/checkfs/comm.c +checkfs_SOURCES += tests/checkfs/common.h checkfs_CPPFLAGS = $(AM_CPPFLAGS) makefiles_SOURCES = tests/checkfs/makefiles.c makefiles_CPPFLAGS = $(AM_CPPFLAGS) -CHECKFS_BINS = \ - makefiles checkfs +EXTRA_DIST += tests/checkfs/README -CHECKFS_HEADER = \ - tests/checkfs/common.h - -CHECKFS_EXTRA = \ - tests/checkfs/README - -EXTRA_DIST += $(CHECKFS_EXTRA) $(CHECKFS_HEADER) - -if INSTALL_TESTS -pkglibexec_PROGRAMS += $(CHECKFS_BINS) -else -noinst_PROGRAMS += $(CHECKFS_BINS) -endif +test_PROGRAMS += makefiles checkfs diff --git a/tests/checkfs/comm.c b/tests/checkfs/comm.c index c8c457e..9f4e20d 100644 --- a/tests/checkfs/comm.c +++ b/tests/checkfs/comm.c @@ -28,6 +28,8 @@ #include <unistd.h> #include <string.h> +int do_pwr_dn(int fd, int cycleCnt); + /* This is the routine that forms and sends the "ok to pwr me down" message diff --git a/tests/fs-tests/Makemodule.am b/tests/fs-tests/Makemodule.am index 2eae860..0c58c9c 100644 --- a/tests/fs-tests/Makemodule.am +++ b/tests/fs-tests/Makemodule.am @@ -3,84 +3,76 @@ integck_LDADD = libubi.a libmissing.a integck_CPPFLAGS = $(AM_CPPFLAGS) -I$(top_srcdir)/ubi-utils/include test_1_SOURCES = tests/fs-tests/simple/test_1.c tests/fs-tests/lib/tests.c +test_1_SOURCES += tests/fs-tests/lib/tests.h test_1_CPPFLAGS = $(AM_CPPFLAGS) -I$(top_srcdir)/tests/fs-tests/lib test_2_SOURCES = tests/fs-tests/simple/test_2.c tests/fs-tests/lib/tests.c +test_2_SOURCES += tests/fs-tests/lib/tests.h test_2_CPPFLAGS = $(AM_CPPFLAGS) -I$(top_srcdir)/tests/fs-tests/lib ftrunc_SOURCES = tests/fs-tests/simple/ftrunc.c tests/fs-tests/lib/tests.c +ftrunc_SOURCES += tests/fs-tests/lib/tests.h ftrunc_CPPFLAGS = $(AM_CPPFLAGS) -I$(top_srcdir)/tests/fs-tests/lib perf_SOURCES = tests/fs-tests/simple/perf.c tests/fs-tests/lib/tests.c +perf_SOURCES += tests/fs-tests/lib/tests.h perf_CPPFLAGS = $(AM_CPPFLAGS) -I$(top_srcdir)/tests/fs-tests/lib orph_SOURCES = tests/fs-tests/simple/orph.c tests/fs-tests/lib/tests.c +orph_SOURCES += tests/fs-tests/lib/tests.h orph_CPPFLAGS = $(AM_CPPFLAGS) -I$(top_srcdir)/tests/fs-tests/lib stress_1_SOURCES = tests/fs-tests/stress/atoms/stress_1.c -stress_1_SOURCES += tests/fs-tests/lib/tests.c +stress_1_SOURCES += tests/fs-tests/lib/tests.c tests/fs-tests/lib/tests.h stress_1_CPPFLAGS = $(AM_CPPFLAGS) -I$(top_srcdir)/tests/fs-tests/lib stress_2_SOURCES = tests/fs-tests/stress/atoms/stress_2.c -stress_2_SOURCES += tests/fs-tests/lib/tests.c +stress_2_SOURCES += tests/fs-tests/lib/tests.c tests/fs-tests/lib/tests.h stress_2_CPPFLAGS = $(AM_CPPFLAGS) -I$(top_srcdir)/tests/fs-tests/lib stress_3_SOURCES = tests/fs-tests/stress/atoms/stress_3.c -stress_3_SOURCES += tests/fs-tests/lib/tests.c +stress_3_SOURCES += tests/fs-tests/lib/tests.c tests/fs-tests/lib/tests.h stress_3_CPPFLAGS = $(AM_CPPFLAGS) -I$(top_srcdir)/tests/fs-tests/lib pdfrun_SOURCES = tests/fs-tests/stress/atoms/pdfrun.c -pdfrun_SOURCES += tests/fs-tests/lib/tests.c +pdfrun_SOURCES += tests/fs-tests/lib/tests.c tests/fs-tests/lib/tests.h pdfrun_CPPFLAGS = $(AM_CPPFLAGS) -I$(top_srcdir)/tests/fs-tests/lib rndwrite00_SOURCES = tests/fs-tests/stress/atoms/rndwrite00.c -rndwrite00_SOURCES += tests/fs-tests/lib/tests.c +rndwrite00_SOURCES += tests/fs-tests/lib/tests.c tests/fs-tests/lib/tests.h rndwrite00_CPPFLAGS = $(AM_CPPFLAGS) -I$(top_srcdir)/tests/fs-tests/lib fwrite00_SOURCES = tests/fs-tests/stress/atoms/fwrite00.c -fwrite00_SOURCES += tests/fs-tests/lib/tests.c +fwrite00_SOURCES += tests/fs-tests/lib/tests.c tests/fs-tests/lib/tests.h fwrite00_CPPFLAGS = $(AM_CPPFLAGS) -I$(top_srcdir)/tests/fs-tests/lib rmdir00_SOURCES = tests/fs-tests/stress/atoms/rmdir00.c -rmdir00_SOURCES += tests/fs-tests/lib/tests.c +rmdir00_SOURCES += tests/fs-tests/lib/tests.c tests/fs-tests/lib/tests.h rmdir00_CPPFLAGS = $(AM_CPPFLAGS) -I$(top_srcdir)/tests/fs-tests/lib rndrm00_SOURCES = tests/fs-tests/stress/atoms/rndrm00.c -rndrm00_SOURCES += tests/fs-tests/lib/tests.c +rndrm00_SOURCES += tests/fs-tests/lib/tests.c tests/fs-tests/lib/tests.h rndrm00_CPPFLAGS = $(AM_CPPFLAGS) -I$(top_srcdir)/tests/fs-tests/lib rndrm99_SOURCES = tests/fs-tests/stress/atoms/rndrm99.c -rndrm99_SOURCES += tests/fs-tests/lib/tests.c +rndrm99_SOURCES += tests/fs-tests/lib/tests.c tests/fs-tests/lib/tests.h rndrm99_CPPFLAGS = $(AM_CPPFLAGS) -I$(top_srcdir)/tests/fs-tests/lib gcd_hupper_SOURCES = tests/fs-tests/stress/atoms/gcd_hupper.c -gcd_hupper_SOURCES += tests/fs-tests/lib/tests.c +gcd_hupper_SOURCES += tests/fs-tests/lib/tests.c tests/fs-tests/lib/tests.h gcd_hupper_CPPFLAGS = $(AM_CPPFLAGS) -I$(top_srcdir)/tests/fs-tests/lib fstest_monitor_SOURCES = tests/fs-tests/utils/fstest_monitor.c free_space_SOURCES = tests/fs-tests/utils/free_space.c -FSTEST_BINS = \ +test_PROGRAMS += \ integck test_1 test_2 ftrunc perf orph \ stress_1 stress_2 stress_3 pdfrun gcd_hupper \ rndwrite00 fwrite00 rmdir00 rndrm00 rndrm99 \ fstest_monitor free_space -FSTEST_SH = \ +test_SCRIPTS += \ tests/fs-tests/fs_help_all.sh tests/fs-tests/fs_run_all.sh \ tests/fs-tests/stress/fs_stress00.sh \ tests/fs-tests/stress/fs_stress01.sh - -FSTEST_HEADER = \ - tests/fs-tests/lib/tests.h - -EXTRA_DIST += $(FSTEST_HEADER) - -if INSTALL_TESTS -pkglibexec_SCRIPTS += $(FSTEST_SH) -pkglibexec_PROGRAMS += $(FSTEST_BINS) -else -noinst_SCRIPTS += $(FSTEST_SH) -noinst_PROGRAMS += $(FSTEST_BINS) -endif diff --git a/tests/fs-tests/fs_help_all.sh.in b/tests/fs-tests/fs_help_all.sh.in index 39219b2..3898fbd 100755 --- a/tests/fs-tests/fs_help_all.sh.in +++ b/tests/fs-tests/fs_help_all.sh.in @@ -1,10 +1,6 @@ #!/bin/sh -prefix=@prefix@ -exec_prefix=@exec_prefix@ -bindir=@bindir@ -libexecpath=@libexecdir@/mtd-utils -TESTBINDIR=@testbindir@ +TESTBINDIR=@TESTBINDIR@ echo ------------------------------------------------------------------------------- $TESTBINDIR/test_1 -h diff --git a/tests/fs-tests/fs_run_all.sh.in b/tests/fs-tests/fs_run_all.sh.in index a3676f9..f15a63b 100755 --- a/tests/fs-tests/fs_run_all.sh.in +++ b/tests/fs-tests/fs_run_all.sh.in @@ -1,10 +1,6 @@ #!/bin/sh -prefix=@prefix@ -exec_prefix=@exec_prefix@ -bindir=@bindir@ -libexecpath=@libexecdir@/mtd-utils -TESTBINDIR=@testbindir@ +TESTBINDIR=@TESTBINDIR@ TEST_DIR=$TEST_FILE_SYSTEM_MOUNT_DIR if test -z "$TEST_DIR"; diff --git a/tests/fs-tests/integrity/integck.c b/tests/fs-tests/integrity/integck.c index 0a7f142..4a6ef16 100644 --- a/tests/fs-tests/integrity/integck.c +++ b/tests/fs-tests/integrity/integck.c @@ -2577,6 +2577,122 @@ static int rm_minus_rf_dir(const char *dir_name) return 0; } +/* + * Detach the MTD device from UBI and attach it back. This function is used + * whed performing emulated power cut testing andthe power cuts are amulated by + * UBI, not by UBIFS. In this case, to recover from the emulated power cut we + * have to unmount UBIFS and re-attach the MTD device. + */ +static int reattach(void) +{ + int err = 0; + libubi_t libubi; + struct ubi_attach_request req; + + libubi = libubi_open(); + if (!libubi) { + if (errno == 0) + return errmsg("UBI is not present in the system"); + return sys_errmsg("cannot open libubi"); + } + + err = ubi_detach_mtd(libubi, "/dev/ubi_ctrl", args.mtdn); + if (err) { + sys_errmsg("cannot detach mtd%d", args.mtdn); + goto out; + } + + req.dev_num = UBI_DEV_NUM_AUTO; + req.mtd_num = args.mtdn; + req.vid_hdr_offset = 0; + req.mtd_dev_node = NULL; + req.max_beb_per1024 = 0; + + err = ubi_attach(libubi, "/dev/ubi_ctrl", &req); + if (err) + sys_errmsg("cannot attach mtd%d", args.mtdn); + +out: + libubi_close(libubi); + return err; +} + +/** + * Unmount and mount back the test file-system. + */ +static int umount_and_remount(int mounted, int reatt, int um_rorw) +{ + int ret = 0; + unsigned long flags; + + if (mounted) + CHECK(umount(fsinfo.mount_point) != -1); + + if (reatt) + CHECK(reattach() == 0); + + if (!um_rorw) { + ret = mount(fsinfo.fsdev, fsinfo.mount_point, + fsinfo.fstype, fsinfo.mount_flags, + fsinfo.mount_opts); + if (ret) { + pcv("unmounted %s, but cannot mount it back R/W", + fsinfo.mount_point); + return -1; + } + } else { + ret = mount(fsinfo.fsdev, fsinfo.mount_point, + fsinfo.fstype, fsinfo.mount_flags | MS_RDONLY, + fsinfo.mount_opts); + if (ret) { + pcv("unmounted %s, but cannot mount it back R/O", + fsinfo.mount_point); + return -1; + } + + flags = fsinfo.mount_flags | MS_REMOUNT; + flags &= ~((unsigned long)MS_RDONLY); + ret = mount(fsinfo.fsdev, fsinfo.mount_point, + fsinfo.fstype, flags, fsinfo.mount_opts); + if (ret) { + pcv("unmounted %s, mounted R/O, but cannot re-mount it R/W", + fsinfo.mount_point); + return -1; + } + } + + return 0; +} + +/** + * Remount the test file-system RO first, then RW. + */ +static int remount_ro_rw(const char *tries) +{ + int ret; + unsigned long flags; + + flags = fsinfo.mount_flags | MS_RDONLY | MS_REMOUNT; + ret = mount(fsinfo.fsdev, fsinfo.mount_point, fsinfo.fstype, + flags, fsinfo.mount_opts); + if (ret) { + pcv("cannot remount %s R/O%s", tries, fsinfo.mount_point); + return -1; + } + + flags = fsinfo.mount_flags | MS_REMOUNT; + flags &= ~((unsigned long)MS_RDONLY); + ret = mount(fsinfo.fsdev, fsinfo.mount_point, fsinfo.fstype, + flags, fsinfo.mount_opts); + if (ret) { + pcv("remounted %s R/O%s, but cannot re-mount it R/W", + tries, fsinfo.mount_point); + return -1; + } + + return 0; +} + /** * Re-mount the test file-system. This function randomly select how to * re-mount. @@ -2601,24 +2717,9 @@ static int remount_tested_fs(void) um = 1; if (rorw1) { - flags = fsinfo.mount_flags | MS_RDONLY | MS_REMOUNT; - ret = mount(fsinfo.fsdev, fsinfo.mount_point, fsinfo.fstype, - flags, fsinfo.mount_opts); - if (ret) { - pcv("cannot remount %s R/O (1)", - fsinfo.mount_point); - return -1; - } - - flags = fsinfo.mount_flags | MS_REMOUNT; - flags &= ~((unsigned long)MS_RDONLY); - ret = mount(fsinfo.fsdev, fsinfo.mount_point, fsinfo.fstype, - flags, fsinfo.mount_opts); - if (ret) { - pcv("remounted %s R/O (1), but cannot re-mount it R/W", - fsinfo.mount_point); + ret = remount_ro_rw(" (1)"); + if (ret) return -1; - } } if (um) { @@ -2633,61 +2734,15 @@ static int remount_tested_fs(void) } } - ret = umount(fsinfo.mount_point); - if (ret) { - pcv("cannot unmount %s", fsinfo.mount_point); + ret = umount_and_remount(1, 0, um_rorw); + if (ret) return -1; - } - - if (!um_rorw) { - ret = mount(fsinfo.fsdev, fsinfo.mount_point, - fsinfo.fstype, fsinfo.mount_flags, - fsinfo.mount_opts); - if (ret) { - pcv("unmounted %s, but cannot mount it back R/W", - fsinfo.mount_point); - return -1; - } - } else { - ret = mount(fsinfo.fsdev, fsinfo.mount_point, - fsinfo.fstype, fsinfo.mount_flags | MS_RDONLY, - fsinfo.mount_opts); - if (ret) { - pcv("unmounted %s, but cannot mount it back R/O", - fsinfo.mount_point); - return -1; - } - - flags = fsinfo.mount_flags | MS_REMOUNT; - flags &= ~((unsigned long)MS_RDONLY); - ret = mount(fsinfo.fsdev, fsinfo.mount_point, - fsinfo.fstype, flags, fsinfo.mount_opts); - if (ret) { - pcv("unmounted %s, mounted R/O, but cannot re-mount it R/W", - fsinfo.mount_point); - return -1; - } - } } if (rorw2) { - flags = fsinfo.mount_flags | MS_RDONLY | MS_REMOUNT; - ret = mount(fsinfo.fsdev, fsinfo.mount_point, fsinfo.fstype, - flags, fsinfo.mount_opts); - if (ret) { - pcv("cannot re-mount %s R/O (3)", fsinfo.mount_point); - return -1; - } - - flags = fsinfo.mount_flags | MS_REMOUNT; - flags &= ~((unsigned long)MS_RDONLY); - ret = mount(fsinfo.fsdev, fsinfo.mount_point, fsinfo.fstype, - flags, fsinfo.mount_opts); - if (ret) { - pcv("remounted %s R/O (3), but cannot re-mount it back R/W", - fsinfo.mount_point); + ret = remount_ro_rw(" (3)"); + if (ret) return -1; - } } CHECK(chdir(fsinfo.mount_point) == 0); @@ -3144,53 +3199,12 @@ static void free_fs_info(struct dir_info *dir) } /* - * Detach the MTD device from UBI and attach it back. This function is used - * whed performing emulated power cut testing andthe power cuts are amulated by - * UBI, not by UBIFS. In this case, to recover from the emulated power cut we - * have to unmount UBIFS and re-attach the MTD device. - */ -static int reattach(void) -{ - int err = 0; - libubi_t libubi; - struct ubi_attach_request req; - - libubi = libubi_open(); - if (!libubi) { - if (errno == 0) - return errmsg("UBI is not present in the system"); - return sys_errmsg("cannot open libubi"); - } - - err = ubi_detach_mtd(libubi, "/dev/ubi_ctrl", args.mtdn); - if (err) { - sys_errmsg("cannot detach mtd%d", args.mtdn); - goto out; - } - - req.dev_num = UBI_DEV_NUM_AUTO; - req.mtd_num = args.mtdn; - req.vid_hdr_offset = 0; - req.mtd_dev_node = NULL; - req.max_beb_per1024 = 0; - - err = ubi_attach(libubi, "/dev/ubi_ctrl", &req); - if (err) - sys_errmsg("cannot attach mtd%d", args.mtdn); - -out: - libubi_close(libubi); - return err; -} - -/* * Recover the tested file-system from an emulated power cut failure by * unmounting it and mounting it again. */ static int recover_tested_fs(void) { int ret; - unsigned long flags; unsigned int um_rorw, rorw2; struct mntent *mntent; @@ -3206,60 +3220,15 @@ static int recover_tested_fs(void) * while mounting in 'remount_tested_fs()'. */ mntent = get_tested_fs_mntent(); - if (mntent) - CHECK(umount(fsinfo.mount_point) != -1); - if (args.reattach) - CHECK(reattach() == 0); - - if (!um_rorw) { - ret = mount(fsinfo.fsdev, fsinfo.mount_point, - fsinfo.fstype, fsinfo.mount_flags, - fsinfo.mount_opts); - if (ret) { - pcv("unmounted %s, but cannot mount it back R/W", - fsinfo.mount_point); - return -1; - } - } else { - ret = mount(fsinfo.fsdev, fsinfo.mount_point, - fsinfo.fstype, fsinfo.mount_flags | MS_RDONLY, - fsinfo.mount_opts); - if (ret) { - pcv("unmounted %s, but cannot mount it back R/O", - fsinfo.mount_point); - return -1; - } - - flags = fsinfo.mount_flags | MS_REMOUNT; - flags &= ~((unsigned long)MS_RDONLY); - ret = mount(fsinfo.fsdev, fsinfo.mount_point, - fsinfo.fstype, flags, fsinfo.mount_opts); - if (ret) { - pcv("unmounted %s, mounted R/O, but cannot re-mount it R/W", - fsinfo.mount_point); - return -1; - } - } + ret = umount_and_remount(!!mntent, args.reattach, um_rorw); + if (ret) + return -1; if (rorw2) { - flags = fsinfo.mount_flags | MS_RDONLY | MS_REMOUNT; - ret = mount(fsinfo.fsdev, fsinfo.mount_point, fsinfo.fstype, - flags, fsinfo.mount_opts); - if (ret) { - pcv("cannot re-mount %s R/O", fsinfo.mount_point); - return -1; - } - - flags = fsinfo.mount_flags | MS_REMOUNT; - flags &= ~((unsigned long)MS_RDONLY); - ret = mount(fsinfo.fsdev, fsinfo.mount_point, fsinfo.fstype, - flags, fsinfo.mount_opts); - if (ret) { - pcv("remounted %s R/O, but cannot re-mount it back R/W", - fsinfo.mount_point); + ret = remount_ro_rw(""); + if (ret) return -1; - } } return 0; diff --git a/tests/fs-tests/lib/tests.c b/tests/fs-tests/lib/tests.c index d1a2e0c..3db0426 100644 --- a/tests/fs-tests/lib/tests.c +++ b/tests/fs-tests/lib/tests.c @@ -35,7 +35,6 @@ #include <sys/vfs.h> #include <sys/mount.h> #include <sys/statvfs.h> -#include <linux/fs.h> #include <linux/jffs2.h> #include "tests.h" diff --git a/tests/fs-tests/stress/fs_stress00.sh.in b/tests/fs-tests/stress/fs_stress00.sh.in index 85ec7a2..f7d317c 100755 --- a/tests/fs-tests/stress/fs_stress00.sh.in +++ b/tests/fs-tests/stress/fs_stress00.sh.in @@ -1,10 +1,6 @@ #!/bin/sh -prefix=@prefix@ -exec_prefix=@exec_prefix@ -bindir=@bindir@ -libexecpath=@libexecdir@/mtd-utils -TESTBINDIR=@testbindir@ +TESTBINDIR=@TESTBINDIR@ TEST_DIR=$TEST_FILE_SYSTEM_MOUNT_DIR if test -z "$TEST_DIR"; diff --git a/tests/fs-tests/stress/fs_stress01.sh.in b/tests/fs-tests/stress/fs_stress01.sh.in index d0ea8f9..da6babf 100755 --- a/tests/fs-tests/stress/fs_stress01.sh.in +++ b/tests/fs-tests/stress/fs_stress01.sh.in @@ -1,10 +1,6 @@ #!/bin/sh -prefix=@prefix@ -exec_prefix=@exec_prefix@ -bindir=@bindir@ -libexecpath=@libexecdir@/mtd-utils -TESTBINDIR=@testbindir@ +TESTBINDIR=@TESTBINDIR@ TEST_DIR=$TEST_FILE_SYSTEM_MOUNT_DIR if test -z "$TEST_DIR"; diff --git a/tests/jittertest/Makemodule.am b/tests/jittertest/Makemodule.am index d4cc121..d280192 100644 --- a/tests/jittertest/Makemodule.am +++ b/tests/jittertest/Makemodule.am @@ -4,21 +4,8 @@ JitterTest_CPPFLAGS = $(AM_CPPFLAGS) plotJittervsFill_SOURCES = tests/jittertest/plotJittervsFill.c plotJittervsFill_CPPFLAGS = $(AM_CPPFLAGS) -JITTEREST_BINS = \ - JitterTest plotJittervsFill +test_PROGRAMS += JitterTest plotJittervsFill -JITTERTEST_SH = \ - tests/jittertest/filljffs2.sh +test_SCRIPTS += tests/jittertest/filljffs2.sh -JITTERTEST_EXTRA = \ - tests/jittertest/README - -EXTRA_DIST += $(JITTERTEST_EXTRA) $(JITTERTEST_SH) - -if INSTALL_TESTS -pkglibexec_SCRIPTS += $(JITTERTEST_SH) -pkglibexec_PROGRAMS += $(JITTEREST_BINS) -else -noinst_SCRIPTS += $(JITTERTEST_SH) -noinst_PROGRAMS += $(JITTEREST_BINS) -endif +EXTRA_DIST += tests/jittertest/README tests/jittertest/filljffs2.sh diff --git a/tests/mtd-tests/Makemodule.am b/tests/mtd-tests/Makemodule.am index 5925fd9..d02e9e4 100644 --- a/tests/mtd-tests/Makemodule.am +++ b/tests/mtd-tests/Makemodule.am @@ -7,9 +7,12 @@ flash_stress_LDADD = libmtd.a flash_stress_CPPFLAGS = $(AM_CPPFLAGS) flash_speed_SOURCES = tests/mtd-tests/flash_speed.c -flash_speed_LDADD = libmtd.a +flash_speed_LDADD = libmtd.a $(PTHREAD_LIBS) flash_speed_CPPFLAGS = $(AM_CPPFLAGS) +flash_speed_LDADD += $(PTHREAD_CFLAGS) +flash_speed_CPPFLAGS += $(PTHREAD_CFLAGS) + nandbiterrs_SOURCES = tests/mtd-tests/nandbiterrs.c nandbiterrs_LDADD = libmtd.a nandbiterrs_CPPFLAGS = $(AM_CPPFLAGS) @@ -26,12 +29,6 @@ nandsubpagetest_SOURCES = tests/mtd-tests/nandsubpagetest.c nandsubpagetest_LDADD = libmtd.a nandsubpagetest_CPPFLAGS = $(AM_CPPFLAGS) -MTDTEST_BINS = \ +test_PROGRAMS += \ flash_torture flash_stress flash_speed nandbiterrs flash_readtest \ nandpagetest nandsubpagetest - -if INSTALL_TESTS -pkglibexec_PROGRAMS += $(MTDTEST_BINS) -else -noinst_PROGRAMS += $(MTDTEST_BINS) -endif diff --git a/tests/mtd-tests/flash_readtest.c b/tests/mtd-tests/flash_readtest.c index c5fabc9..519ff89 100644 --- a/tests/mtd-tests/flash_readtest.c +++ b/tests/mtd-tests/flash_readtest.c @@ -125,10 +125,14 @@ static void process_options(int argc, char **argv) } } - if (optind < argc) - mtddev = argv[optind++]; - else + if (optind < argc) { + mtddev = mtd_find_dev_node(argv[optind]); + if (!mtddev) + errmsg_die("Can't find MTD device %s", argv[optind]); + optind++; + } else { errmsg_die("No device specified!\n"); + } if (optind < argc) usage(EXIT_FAILURE); @@ -227,7 +231,7 @@ int main(int argc, char **argv) puts("not NAND flash, assume page size is 512 bytes."); pgsize = 512; } else { - pgsize = mtd.subpage_size; + pgsize = mtd.min_io_size; } pgcnt = mtd.eb_size / pgsize; diff --git a/tests/mtd-tests/flash_speed.c b/tests/mtd-tests/flash_speed.c index 0058269..11f396c 100644 --- a/tests/mtd-tests/flash_speed.c +++ b/tests/mtd-tests/flash_speed.c @@ -23,6 +23,7 @@ * Author: Adrian Hunter <adrian.hunter@nokia.com> */ #define DESTRUCTIVE 0x01 +#define CONTINOUS 0x02 #define PROGRAM_NAME "flash_speed" @@ -33,6 +34,7 @@ #include <stdlib.h> #include <libmtd.h> #include <getopt.h> +#include <pthread.h> #include <stdio.h> #include <fcntl.h> #include <time.h> @@ -46,7 +48,9 @@ static const char *mtddev; static libmtd_t mtd_desc; static int fd; -static int peb=-1, count=-1, skip=-1, flags=0; +static int npages = 1; +static int peb=-1, count=-1, skip=-1, flags=0, speb=-1; +static bool continuous = false; static struct timespec start, finish; static int pgsize, pgcnt; static int goodebcnt; @@ -57,6 +61,8 @@ static const struct option options[] = { { "peb", required_argument, NULL, 'b' }, { "count", required_argument, NULL, 'c' }, { "skip", required_argument, NULL, 's' }, + { "sec-peb", required_argument, NULL, 'k' }, + { "continuous", no_argument, NULL, 'C' }, { NULL, 0, NULL, 0 }, }; @@ -69,7 +75,9 @@ static NORETURN void usage(int status) " -b, --peb <num> Start from this physical erase block\n" " -c, --count <num> Number of erase blocks to use (default: all)\n" " -s, --skip <num> Number of blocks to skip\n" - " -d, --destructive Run destructive (erase and write speed) tests\n", + " -d, --destructive Run destructive (erase and write speed) tests\n" + " -k, --sec-peb <num> Start of secondary block to measure RWW latency (requires -d)\n" + " -C, --continuous Increase the number of consecutive pages gradually\n", status==EXIT_SUCCESS ? stdout : stderr); exit(status); } @@ -93,7 +101,7 @@ static void process_options(int argc, char **argv) int c; while (1) { - c = getopt_long(argc, argv, "hb:c:s:d", options, NULL); + c = getopt_long(argc, argv, "hb:c:s:dk:C", options, NULL); if (c == -1) break; @@ -126,15 +134,29 @@ static void process_options(int argc, char **argv) goto failmulti; flags |= DESTRUCTIVE; break; + case 'k': + if (speb >= 0) + goto failmulti; + speb = read_num(c, optarg); + if (speb < 0) + goto failarg; + break; + case 'C': + continuous = true; + break; default: exit(EXIT_FAILURE); } } - if (optind < argc) - mtddev = argv[optind++]; - else + if (optind < argc) { + mtddev = mtd_find_dev_node(argv[optind]); + if (!mtddev) + errmsg_die("Can't find MTD device %s", argv[optind]); + optind++; + } else { errmsg_die("No device specified!\n"); + } if (optind < argc) usage(EXIT_FAILURE); @@ -144,11 +166,15 @@ static void process_options(int argc, char **argv) skip = 0; if (count < 0) count = 1; + if (speb >= 0 && !(flags & DESTRUCTIVE)) + goto faildestr; return; failmulti: errmsg_die("'-%c' specified more than once!\n", c); failarg: errmsg_die("Invalid argument for '-%c'!\n", c); +faildestr: + errmsg_die("'-k' specified, -d is missing!\n"); } static int write_eraseblock(int ebnum) @@ -214,71 +240,56 @@ static int write_eraseblock_by_2pages(int ebnum) return err; } -static int read_eraseblock_by_page(int ebnum) -{ - void *buf = iobuf; - int i, err = 0; - - for (i = 0; i < pgcnt; ++i) { - err = mtd_read(&mtd, fd, ebnum, i * pgsize, iobuf, pgsize); - if (err) { - fprintf(stderr, "Error reading block %d, page %d!\n", - ebnum, i); - break; - } - buf += pgsize; - } - - return err; -} - -static int read_eraseblock_by_2pages(int ebnum) +static int read_eraseblock_by_npages(int ebnum) { - int i, n = pgcnt / 2, err = 0; - size_t sz = pgsize * 2; + int i, n = pgcnt / npages, err = 0; + size_t sz = pgsize * npages; void *buf = iobuf; for (i = 0; i < n; ++i) { err = mtd_read(&mtd, fd, ebnum, i * sz, iobuf, sz); if (err) { - fprintf(stderr, "Error reading block %d, page %d + %d!\n", - ebnum, i*2, i*2+1); + fprintf(stderr, "Error reading block %d, page [%d-%d]!\n", + ebnum, i*npages, (i*npages) + npages- 1); return err; } buf += sz; } - if (pgcnt % 2) { - err = mtd_read(&mtd, fd, ebnum, i * sz, iobuf, pgsize); - if (err) { - fprintf(stderr, "Error reading block %d, page %d!\n", - ebnum, i*2); - } - } return err; } -static void start_timing(void) +static void start_timing(struct timespec *start) { - clock_gettime(CLOCK_MONOTONIC_RAW, &start); + clock_gettime(CLOCK_MONOTONIC_RAW, start); } -static void stop_timing(void) +static void stop_timing(struct timespec *finish) { - clock_gettime(CLOCK_MONOTONIC_RAW, &finish); + clock_gettime(CLOCK_MONOTONIC_RAW, finish); } -static long calc_speed(void) +static long calc_duration(struct timespec *start, struct timespec *finish) { long ms; - ms = (finish.tv_sec - start.tv_sec) * 1000L; - ms += (finish.tv_nsec - start.tv_nsec) / 1000000L; + ms = (finish->tv_sec - start->tv_sec) * 1000L; + ms += (finish->tv_nsec - start->tv_nsec) / 1000000L; + + return ms; +} + +static long calc_speed(struct timespec *start, struct timespec *finish, + int pages_per_set) +{ + long ms = calc_duration(start, finish); + int sets_in_eb = pgcnt / pages_per_set; + size_t sz = pgsize * pages_per_set * sets_in_eb; if (ms <= 0) return 0; - return ((long)goodebcnt * (mtd.eb_size / 1024L) * 1000L) / ms; + return ((long)goodebcnt * (sz / 1024L) * 1000L) / ms; } static void scan_for_bad_eraseblocks(unsigned int eb, int ebcnt, int ebskip) @@ -313,8 +324,34 @@ static int erase_good_eraseblocks(unsigned int eb, int ebcnt, int ebskip) return err; } -#define TIME_OP_PER_PEB( op )\ - start_timing();\ +struct thread_arg { + int (*op)(int peb); + int peb; + struct timespec start; + struct timespec finish; +}; + +static void *op_thread(void *ptr) +{ + struct thread_arg *args = ptr; + unsigned long err = 0; + int i; + + start_timing(&args->start); + for (i = 0; i < count; ++i) { + if (bbt[i]) + continue; + err = args->op(args->peb + i * (skip + 1)); + if (err) + break; + } + stop_timing(&args->finish); + + return (void *)err; +} + +#define TIME_OP_PER_PEB( op, npages ) \ + start_timing(&start);\ for (i = 0; i < count; ++i) {\ if (bbt[i])\ continue;\ @@ -322,8 +359,8 @@ static int erase_good_eraseblocks(unsigned int eb, int ebcnt, int ebskip) if (err)\ goto out;\ }\ - stop_timing();\ - speed = calc_speed() + stop_timing(&finish);\ + speed = calc_speed(&start, &finish, npages) int main(int argc, char **argv) { @@ -343,7 +380,7 @@ int main(int argc, char **argv) puts("not NAND flash, assume page size is 512 bytes."); pgsize = 512; } else { - pgsize = mtd.subpage_size; + pgsize = mtd.min_io_size; } pgcnt = mtd.eb_size / pgsize; @@ -384,13 +421,13 @@ int main(int argc, char **argv) goto out; puts("testing eraseblock write speed"); - TIME_OP_PER_PEB(write_eraseblock); + TIME_OP_PER_PEB(write_eraseblock, 1); printf("eraseblock write speed is %ld KiB/s\n", speed); } /* Read all eraseblocks, 1 eraseblock at a time */ puts("testing eraseblock read speed"); - TIME_OP_PER_PEB(read_eraseblock); + TIME_OP_PER_PEB(read_eraseblock, 1); printf("eraseblock read speed is %ld KiB/s\n", speed); /* Write all eraseblocks, 1 page at a time */ @@ -400,40 +437,55 @@ int main(int argc, char **argv) goto out; puts("testing page write speed"); - TIME_OP_PER_PEB(write_eraseblock_by_page); + TIME_OP_PER_PEB(write_eraseblock_by_page, 1); printf("page write speed is %ld KiB/s\n", speed); } /* Read all eraseblocks, 1 page at a time */ puts("testing page read speed"); - TIME_OP_PER_PEB(read_eraseblock_by_page); + npages = 1; + TIME_OP_PER_PEB(read_eraseblock_by_npages, npages); printf("page read speed is %ld KiB/s\n", speed); - /* Write all eraseblocks, 2 pages at a time */ - if (flags & DESTRUCTIVE) { - err = erase_good_eraseblocks(peb, count, skip); - if (err) - goto out; + if (continuous) { + /* Write all eraseblocks, 2 pages at a time */ + if (flags & DESTRUCTIVE) { + err = erase_good_eraseblocks(peb, count, skip); + if (err) + goto out; - puts("testing 2 page write speed"); - TIME_OP_PER_PEB(write_eraseblock_by_2pages); - printf("2 page write speed is %ld KiB/s\n", speed); - } + puts("testing 2 page write speed"); + TIME_OP_PER_PEB(write_eraseblock_by_2pages, 2); + printf("2 page write speed is %ld KiB/s\n", speed); + } - /* Read all eraseblocks, 2 pages at a time */ - puts("testing 2 page read speed"); - TIME_OP_PER_PEB(read_eraseblock_by_2pages); - printf("2 page read speed is %ld KiB/s\n", speed); + /* Read all eraseblocks, N pages at a time */ + puts("testing multiple pages read speed"); + for (npages = 2; npages <= 16 && npages <= pgcnt; npages++) { + TIME_OP_PER_PEB(read_eraseblock_by_npages, npages); + printf("%d page read speed is %ld KiB/s\n", npages, speed); + } + if (pgcnt >= 32) { + npages = 32; + TIME_OP_PER_PEB(read_eraseblock_by_npages, npages); + printf("%d page read speed is %ld KiB/s\n", npages, speed); + } + if (pgcnt >= 64) { + npages = 64; + TIME_OP_PER_PEB(read_eraseblock_by_npages, npages); + printf("%d page read speed is %ld KiB/s\n", npages, speed); + } + } /* Erase all eraseblocks */ if (flags & DESTRUCTIVE) { puts("Testing erase speed"); - start_timing(); + start_timing(&start); err = erase_good_eraseblocks(peb, count, skip); if (err) goto out; - stop_timing(); - speed = calc_speed(); + stop_timing(&finish); + speed = calc_speed(&start, &finish, 1); printf("erase speed is %ld KiB/s\n", speed); } @@ -442,7 +494,7 @@ int main(int argc, char **argv) for (k = 1; k < 7; ++k) { blocks = 1 << k; printf("Testing %dx multi-block erase speed\n", blocks); - start_timing(); + start_timing(&start); for (i = 0; i < count; ) { for (j = 0; j < blocks && (i + j) < count; ++j) if (bbt[i + j]) @@ -456,13 +508,93 @@ int main(int argc, char **argv) goto out; i += j; } - stop_timing(); - speed = calc_speed(); + stop_timing(&finish); + speed = calc_speed(&start, &finish, 1); printf("%dx multi-block erase speed is %ld KiB/s\n", blocks, speed); } } + /* Write a page and immediately after try to read another page. Report + * the latency difference when performed on different banks (NOR only). + */ + if (speb >= 0 && mtd.subpage_size == 1) { + long rww_duration_w, rww_latency_end; + long rww_duration_rnw, rww_duration_r_end; + bool rww_r_end_first; + struct thread_arg write_args_peb = { + .op = write_eraseblock, + .peb = peb, + }; + struct thread_arg read_args_speb = { + .op = read_eraseblock, + .peb = speb, + }; + struct sched_param param_write, param_read; + pthread_attr_t attr_write, attr_read; + pthread_t write_thread, read_thread; + void *retval; + + puts("testing read while write latency"); + + /* Change scheduling priorities so that the write thread gets + *scheduled more aggressively than the read thread. + */ + pthread_attr_init(&attr_write); + pthread_attr_setinheritsched(&attr_write, PTHREAD_EXPLICIT_SCHED); + pthread_attr_setschedpolicy(&attr_write, SCHED_FIFO); + param_write.sched_priority = 42; + pthread_attr_setschedparam(&attr_write, ¶m_write); + + pthread_attr_init(&attr_read); + pthread_attr_setinheritsched(&attr_read, PTHREAD_EXPLICIT_SCHED); + pthread_attr_setschedpolicy(&attr_read, SCHED_FIFO); + param_read.sched_priority = 41; + pthread_attr_setschedparam(&attr_read, ¶m_read); + + err = pthread_create(&write_thread, &attr_write, + (void *)op_thread, &write_args_peb); + if (err) { + errmsg("parallel write pthread create failed"); + goto out; + } + + err = pthread_create(&read_thread, &attr_read, + (void *)op_thread, &read_args_speb); + if (err) { + errmsg("parallel read pthread create failed"); + goto out; + } + + pthread_join(read_thread, &retval); + if ((long)retval) { + errmsg("parallel read pthread failed"); + goto out; + } + + pthread_join(write_thread, &retval); + if ((long)retval) { + errmsg("parallel write pthread failed"); + goto out; + } + + rww_duration_w = calc_duration(&write_args_peb.start, + &write_args_peb.finish); + rww_latency_end = calc_duration(&write_args_peb.finish, + &read_args_speb.finish); + rww_r_end_first = rww_latency_end < 0; + if (rww_r_end_first) + rww_duration_rnw = rww_duration_w; + else + rww_duration_rnw = calc_duration(&write_args_peb.start, + &read_args_speb.finish); + + rww_duration_r_end = calc_duration(&write_args_peb.start, + &read_args_speb.finish); + printf("read while write took %ldms, read ended after %ldms\n", + rww_duration_rnw, rww_duration_r_end); + } + puts("finished"); status = EXIT_SUCCESS; out: diff --git a/tests/mtd-tests/flash_stress.c b/tests/mtd-tests/flash_stress.c index 249d8cb..da39e14 100644 --- a/tests/mtd-tests/flash_stress.c +++ b/tests/mtd-tests/flash_stress.c @@ -126,10 +126,14 @@ static void process_options(int argc, char **argv) } } - if (optind < argc) - mtddev = argv[optind++]; - else + if (optind < argc) { + mtddev = mtd_find_dev_node(argv[optind]); + if (!mtddev) + errmsg_die("Can't find MTD device %s", argv[optind]); + optind++; + } else { errmsg_die("No device specified!\n"); + } if (optind < argc) usage(EXIT_FAILURE); @@ -242,7 +246,7 @@ int main(int argc, char **argv) puts("not NAND flash, assume page size is 512 bytes."); pgsize = 512; } else { - pgsize = mtd.subpage_size; + pgsize = mtd.min_io_size; } pgcnt = mtd.eb_size / pgsize; diff --git a/tests/mtd-tests/flash_torture.c b/tests/mtd-tests/flash_torture.c index 5aad8e0..6363f9e 100644 --- a/tests/mtd-tests/flash_torture.c +++ b/tests/mtd-tests/flash_torture.c @@ -144,10 +144,14 @@ static void process_options(int argc, char **argv) } } - if (optind < argc) - mtddev = argv[optind++]; - else + if (optind < argc) { + mtddev = mtd_find_dev_node(argv[optind]); + if (!mtddev) + errmsg_die("Can't find MTD device %s", argv[optind]); + optind++; + } else { errmsg_die("No device specified!\n"); + } if (optind < argc) usage(EXIT_FAILURE); diff --git a/tests/mtd-tests/nandbiterrs.c b/tests/mtd-tests/nandbiterrs.c index f583c14..424a95f 100644 --- a/tests/mtd-tests/nandbiterrs.c +++ b/tests/mtd-tests/nandbiterrs.c @@ -63,12 +63,13 @@ #define MODE_INCREMENTAL 0x02 #define MODE_OVERWRITE 0x04 #define PAGE_ERASED 0x08 +#define CONTINUOUS_READ 0x10 static int peb = -1, page = -1, max_overwrite = -1, seed = -1; static const char *mtddev; static unsigned char *wbuffer, *rbuffer, *old_data; -static int fd, pagesize, pagecount, flags; +static int fd, pagesize, bs, pagecount, flags; static struct mtd_dev_info mtd; static libmtd_t mtd_desc; @@ -81,6 +82,7 @@ static const struct option options[] = { { "erased", no_argument, NULL, 'e' }, { "writes", required_argument, NULL, 'w' }, { "incremental", no_argument, NULL, 'i' }, + { "continuous", no_argument, NULL, 'c' }, { "overwrite", no_argument, NULL, 'o' }, { NULL, 0, NULL, 0 }, }; @@ -95,7 +97,8 @@ static NORETURN void usage(int status) " -b, --peb <num> Use this physical erase block\n" " -p, --page <num> Use this page within the erase block\n" " -s, --seed <num> Specify seed for PRNG\n" - " -e, --erased Test erased pages instead of written pages\n\n" + " -e, --erased Test erased pages instead of written pages\n" + " -c, --continuous Use two consecutive pages (incremental test only)\n\n" "Options controling test mode:\n" " -i, --incremental Manually insert bit errors until ECC fails\n" " -o, --overwrite Rewrite page until bits flip and ECC fails\n\n" @@ -124,7 +127,7 @@ static void process_options(int argc, char **argv) int c; while (1) { - c = getopt_long(argc, argv, "hkb:p:s:eiow:", options, NULL); + c = getopt_long(argc, argv, "hkb:p:s:eiow:c", options, NULL); if (c == -1) break; @@ -175,6 +178,9 @@ static void process_options(int argc, char **argv) case 'e': flags |= PAGE_ERASED; break; + case 'c': + flags |= CONTINUOUS_READ; + break; case 'h': usage(EXIT_SUCCESS); default: @@ -193,6 +199,9 @@ static void process_options(int argc, char **argv) if (!(flags & (MODE_OVERWRITE|MODE_INCREMENTAL))) errmsg_die("No test mode specified!"); + if (flags & CONTINUOUS_READ && !(flags & MODE_INCREMENTAL)) + errmsg_die("Use --continuous with --incremental only!"); + if ((max_overwrite > 0) && !(flags & MODE_OVERWRITE)) errmsg_die("Write count specified but mode is not --overwrite!"); @@ -235,9 +244,9 @@ static void init_buffer(void) unsigned int i; if (flags & PAGE_ERASED) { - memset(wbuffer, 0xff, pagesize); + memset(wbuffer, 0xff, bs); } else { - for (i = 0; i < pagesize; ++i) + for (i = 0; i < bs; ++i) wbuffer[i] = hash(i+seed); } } @@ -251,7 +260,7 @@ static int write_page(void) goto fail_mode; err = mtd_write(mtd_desc, &mtd, fd, peb, page*pagesize, - wbuffer, pagesize, NULL, 0, 0); + wbuffer, bs, NULL, 0, 0); if (err) fprintf(stderr, "Failed to write page %d in block %d\n", peb, page); @@ -290,7 +299,7 @@ static int read_page(void) if (ioctl(fd, ECCGETSTATS, &old) != 0) goto failstats; - err = mtd_read(&mtd, fd, peb, page*pagesize, rbuffer, pagesize); + err = mtd_read(&mtd, fd, peb, page*pagesize, rbuffer, bs); if (err) { fputs("Read failed!\n", stderr); return -1; @@ -316,7 +325,7 @@ static int verify_page(void) int erased = flags & PAGE_ERASED; unsigned int i, errs = 0; - for (i = 0; i < pagesize; ++i) { + for (i = 0; i < bs; ++i) { if (rbuffer[i] != (erased ? 0xff : hash(i+seed))) ++errs; } @@ -332,7 +341,7 @@ static int insert_biterror(void) { int bit, mask, byte; - for (byte = 0; byte < pagesize; ++byte) { + for (byte = 0; byte < bs; ++byte) { for (bit = 7, mask = 0x80; bit >= 0; bit--, mask >>= 1) { if (wbuffer[byte] & mask) { wbuffer[byte] &= ~mask; @@ -461,6 +470,10 @@ int main(int argc, char **argv) pagesize = mtd.subpage_size; pagecount = mtd.eb_size / pagesize; + if (!(flags & CONTINUOUS_READ)) + bs = pagesize; + else + bs = 2 * pagesize; if (peb >= mtd.eb_cnt) return errmsg("Physical erase block %d is out of range!", peb); @@ -483,13 +496,13 @@ int main(int argc, char **argv) } } - wbuffer = malloc(pagesize); + wbuffer = malloc(bs); if (!wbuffer) { perror(NULL); goto fail_dev; } - rbuffer = malloc(pagesize); + rbuffer = malloc(bs); if (!rbuffer) { perror(NULL); goto fail_rbuffer; diff --git a/tests/ubi-tests/Makemodule.am b/tests/ubi-tests/Makemodule.am index d1fbab7..13d7b08 100644 --- a/tests/ubi-tests/Makemodule.am +++ b/tests/ubi-tests/Makemodule.am @@ -1,12 +1,15 @@ io_basic_SOURCES = tests/ubi-tests/io_basic.c tests/ubi-tests/helpers.c +io_basic_SOURCES += tests/ubi-tests/helpers.h io_basic_LDADD = libubi.a io_basic_CPPFLAGS = $(AM_CPPFLAGS) -I$(top_srcdir)/ubi-utils/include io_update_SOURCES = tests/ubi-tests/io_update.c tests/ubi-tests/helpers.c +io_update_SOURCES += tests/ubi-tests/helpers.h io_update_LDADD = libubi.a io_update_CPPFLAGS = $(AM_CPPFLAGS) -I$(top_srcdir)/ubi-utils/include io_paral_SOURCES = tests/ubi-tests/io_paral.c tests/ubi-tests/helpers.c +io_paral_SOURCES += tests/ubi-tests/helpers.h io_paral_LDADD = libubi.a $(PTHREAD_LIBS) io_paral_CPPFLAGS = $(AM_CPPFLAGS) -I$(top_srcdir)/ubi-utils/include @@ -14,26 +17,32 @@ io_paral_LDADD += $(PTHREAD_CFLAGS) io_paral_CPPFLAGS += $(PTHREAD_CFLAGS) io_read_SOURCES = tests/ubi-tests/io_read.c tests/ubi-tests/helpers.c +io_read_SOURCES += tests/ubi-tests/helpers.h io_read_LDADD = libubi.a io_read_CPPFLAGS = $(AM_CPPFLAGS) -I$(top_srcdir)/ubi-utils/include volrefcnt_SOURCES = tests/ubi-tests/volrefcnt.c tests/ubi-tests/helpers.c +volrefcnt_SOURCES += tests/ubi-tests/helpers.h volrefcnt_LDADD = libubi.a volrefcnt_CPPFLAGS = $(AM_CPPFLAGS) -I$(top_srcdir)/ubi-utils/include integ_SOURCES = tests/ubi-tests/integ.c tests/ubi-tests/helpers.c +integ_SOURCES += tests/ubi-tests/helpers.h integ_LDADD = libubi.a integ_CPPFLAGS = $(AM_CPPFLAGS) -I$(top_srcdir)/ubi-utils/include mkvol_basic_SOURCES = tests/ubi-tests/mkvol_basic.c tests/ubi-tests/helpers.c +mkvol_basic_SOURCES += tests/ubi-tests/helpers.h mkvol_basic_LDADD = libubi.a mkvol_basic_CPPFLAGS = $(AM_CPPFLAGS) -I$(top_srcdir)/ubi-utils/include mkvol_bad_SOURCES = tests/ubi-tests/mkvol_bad.c tests/ubi-tests/helpers.c +mkvol_bad_SOURCES += tests/ubi-tests/helpers.h mkvol_bad_LDADD = libubi.a mkvol_bad_CPPFLAGS = $(AM_CPPFLAGS) -I$(top_srcdir)/ubi-utils/include mkvol_paral_SOURCES = tests/ubi-tests/mkvol_paral.c tests/ubi-tests/helpers.c +mkvol_paral_SOURCES += tests/ubi-tests/helpers.h mkvol_paral_LDADD = libubi.a $(PTHREAD_LIBS) mkvol_paral_CPPFLAGS = $(AM_CPPFLAGS) -I$(top_srcdir)/ubi-utils/include @@ -41,25 +50,13 @@ mkvol_paral_LDADD += $(PTHREAD_CFLAGS) mkvol_paral_CPPFLAGS += $(PTHREAD_CFLAGS) rsvol_SOURCES = tests/ubi-tests/rsvol.c tests/ubi-tests/helpers.c +rsvol_SOURCES += tests/ubi-tests/helpers.h rsvol_LDADD = libubi.a rsvol_CPPFLAGS = $(AM_CPPFLAGS) -I$(top_srcdir)/ubi-utils/include -UBITEST_BINS = \ +test_PROGRAMS += \ io_basic io_update io_paral io_read volrefcnt integ \ mkvol_basic mkvol_bad mkvol_paral rsvol -UBITEST_SH = \ +test_SCRIPTS += \ tests/ubi-tests/runubitests.sh tests/ubi-tests/ubi-stress-test.sh - -UBITEST_HEADER = \ - tests/ubi-tests/helpers.h - -EXTRA_DIST += $(UBITEST_HEADER) - -if INSTALL_TESTS -pkglibexec_SCRIPTS += $(UBITEST_SH) -pkglibexec_PROGRAMS += $(UBITEST_BINS) -else -noinst_SCRIPTS += $(UBITEST_SH) -noinst_PROGRAMS += $(UBITEST_BINS) -endif diff --git a/tests/ubi-tests/runubitests.sh.in b/tests/ubi-tests/runubitests.sh.in index a1b23c4..1675308 100755 --- a/tests/ubi-tests/runubitests.sh.in +++ b/tests/ubi-tests/runubitests.sh.in @@ -1,10 +1,6 @@ #!/bin/sh -euf -prefix=@prefix@ -exec_prefix=@exec_prefix@ -bindir=@bindir@ -libexecpath=@libexecdir@/mtd-utils -TESTBINDIR=@testbindir@ +TESTBINDIR=@TESTBINDIR@ tests="mkvol_basic mkvol_bad mkvol_paral rsvol io_basic io_read io_update io_paral volrefcnt" diff --git a/tests/ubi-tests/ubi-stress-test.sh.in b/tests/ubi-tests/ubi-stress-test.sh.in index 657ef4b..e178076 100755 --- a/tests/ubi-tests/ubi-stress-test.sh.in +++ b/tests/ubi-tests/ubi-stress-test.sh.in @@ -1,10 +1,6 @@ #!/bin/sh -euf -prefix=@prefix@ -exec_prefix=@exec_prefix@ -bindir=@bindir@ -libexecpath=@libexecdir@/mtd-utils -TESTBINDIR=@testbindir@ +TESTBINDIR=@TESTBINDIR@ fatal() { diff --git a/tests/ubifs_tools-tests/Makemodule.am b/tests/ubifs_tools-tests/Makemodule.am new file mode 100644 index 0000000..1715757 --- /dev/null +++ b/tests/ubifs_tools-tests/Makemodule.am @@ -0,0 +1,66 @@ +test_SCRIPTS += \ + tests/ubifs_tools-tests/lib/common.sh \ + tests/ubifs_tools-tests/ubifs_tools_run_all.sh \ + tests/ubifs_tools-tests/fsck_tests/authentication_refuse.sh \ + tests/ubifs_tools-tests/fsck_tests/cycle_mount_fsck_check.sh \ + tests/ubifs_tools-tests/fsck_tests/powercut_fsck_mount.sh \ + tests/ubifs_tools-tests/fsck_tests/cycle_corrupted_fsck_fault_inject.sh \ + tests/ubifs_tools-tests/fsck_tests/cycle_powercut_mount_fsck.sh \ + tests/ubifs_tools-tests/fsck_tests/random_corrupted_fsck.sh \ + tests/ubifs_tools-tests/fsck_tests/fsck_bad_image.sh \ + tests/ubifs_tools-tests/mkfs_tests/build_fs_from_dir.sh + +test_DATA += \ + tests/ubifs_tools-tests/images/good.gz \ + tests/ubifs_tools-tests/images/sb_fanout.gz \ + tests/ubifs_tools-tests/images/sb_fmt_version.gz \ + tests/ubifs_tools-tests/images/sb_leb_size.gz \ + tests/ubifs_tools-tests/images/sb_log_lebs.gz \ + tests/ubifs_tools-tests/images/sb_min_io_size.gz \ + tests/ubifs_tools-tests/images/master_highest_inum.gz \ + tests/ubifs_tools-tests/images/master_lpt.gz \ + tests/ubifs_tools-tests/images/master_tnc.gz \ + tests/ubifs_tools-tests/images/master_total_dead.gz \ + tests/ubifs_tools-tests/images/master_total_dirty.gz \ + tests/ubifs_tools-tests/images/master_total_free.gz \ + tests/ubifs_tools-tests/images/journal_log.gz \ + tests/ubifs_tools-tests/images/journal_bud.gz \ + tests/ubifs_tools-tests/images/orphan_node.gz \ + tests/ubifs_tools-tests/images/lpt_dirty.gz \ + tests/ubifs_tools-tests/images/lpt_flags.gz \ + tests/ubifs_tools-tests/images/lpt_free.gz \ + tests/ubifs_tools-tests/images/lpt_pos.gz \ + tests/ubifs_tools-tests/images/ltab_dirty.gz \ + tests/ubifs_tools-tests/images/ltab_free.gz \ + tests/ubifs_tools-tests/images/index_size.gz \ + tests/ubifs_tools-tests/images/tnc_lv0_key.gz \ + tests/ubifs_tools-tests/images/tnc_lv0_len.gz \ + tests/ubifs_tools-tests/images/tnc_lv0_pos.gz \ + tests/ubifs_tools-tests/images/tnc_noleaf_key.gz \ + tests/ubifs_tools-tests/images/tnc_noleaf_len.gz \ + tests/ubifs_tools-tests/images/tnc_noleaf_pos.gz \ + tests/ubifs_tools-tests/images/corrupted_data_leb.gz \ + tests/ubifs_tools-tests/images/corrupted_idx_leb.gz \ + tests/ubifs_tools-tests/images/inode_data.gz \ + tests/ubifs_tools-tests/images/inode_mode.gz \ + tests/ubifs_tools-tests/images/inode_nlink.gz \ + tests/ubifs_tools-tests/images/inode_size.gz \ + tests/ubifs_tools-tests/images/inode_xcnt.gz \ + tests/ubifs_tools-tests/images/soft_link_inode_mode.gz \ + tests/ubifs_tools-tests/images/soft_link_data_len.gz \ + tests/ubifs_tools-tests/images/dentry_key.gz \ + tests/ubifs_tools-tests/images/dentry_nlen.gz \ + tests/ubifs_tools-tests/images/dentry_type.gz \ + tests/ubifs_tools-tests/images/xinode_flags.gz \ + tests/ubifs_tools-tests/images/xinode_key.gz \ + tests/ubifs_tools-tests/images/xinode_mode.gz \ + tests/ubifs_tools-tests/images/xentry_key.gz \ + tests/ubifs_tools-tests/images/xentry_nlen.gz \ + tests/ubifs_tools-tests/images/xentry_type.gz \ + tests/ubifs_tools-tests/images/xent_host.gz \ + tests/ubifs_tools-tests/images/dir_many_dentry.gz \ + tests/ubifs_tools-tests/images/dir_lost.gz \ + tests/ubifs_tools-tests/images/dir_lost_duplicated.gz \ + tests/ubifs_tools-tests/images/dir_lost_not_recover.gz \ + tests/ubifs_tools-tests/images/root_dir.gz \ + tests/ubifs_tools-tests/images/empty_tnc.gz diff --git a/tests/ubifs_tools-tests/README.txt b/tests/ubifs_tools-tests/README.txt new file mode 100644 index 0000000..9e94c22 --- /dev/null +++ b/tests/ubifs_tools-tests/README.txt @@ -0,0 +1,303 @@ + + ubifs_tools tests + ================== + + There are seven testcases for fsck.ubifs on encryption/non-encryption + situations: + 1) authentication_refuse: Currently authenticated UBIFS image is not + supported for fsck.ubifs, check whether fsck.ubifs can refuse the + authenticated UBIFS image. + 2) random_corrupted_fsck: Inject random corruption on UBIFS image + by writing random data on kinds of mtd devices (eg. nand, nor), + check the consistency of UBIFS after fsck. + This testcase simulate random bad UBIFS image caused by hardware + exceptions(eg. ecc uncorrectable, unwritten), and makes sure that + fsck.ubifs could make UBIFS be consistent after repairing UBIFS + image. + 3) cycle_corrupted_fsck_fault_inject: Inject memory/io fault while + doing fsck for corrupted UBIFS images. + This testcase mainly checks whether fsck.ubifs has problems (eg. + UAF, null-ptr-def, etc.) in random error paths. Besides, it + provides a similar way to simulate powercut during fsck, and + checks whether the fsck.ubifs can fix an UBIFS image after many + rounds interrupted by kinds of errors. + 4) cycle_powercut_mount_fsck: Inject powercut while doing fsstress + on mounted UBIFS, check the consistency of UBIFS after fsck. + This testscase mainly makes sure that fsck.ubifs can make UBIFS + image be consistent in common stress cases and powercut cases. + 5) powercut_fsck_mount: Inject powercut while doing fsstress on + mounted UBIFS for kinds of flashes (eg. nand, nor). + This testcase mainly makes sure that fsck.ubifs can make UBIFS + image be consistent on different flashes (eg. nand, nor). Because + the min_io_size of nor flash is 1, the UBIFS image on nor flash + will be different from nand flash after doing powercut, so we need + make sure fsck.ubifs can handle these two types of flash. + 6) cycle_mount_fsck_check: Do fsstress and fsck ubifs image, make + sure all files(and their data) are not lost after fsck. + This testcase mainly checks whether fsck.ubifs could corrupt the + filesystem content in common case. + 7) fsck_bad_image: For kinds of inconsistent UBIFS images(which + can simulate corruptions caused by some potentional UBIFS bug), check + the result of fsck. + This testcase mainly checks whether the behavior is in expected after + repairing specific inconsistent UBIFS image. There is no debugfs tools + (for example: debugfs[ext4], xfs_db) for UBIFS, so no way to inject + precise corruption into UBIFS image, we have to prepare inconsistent + UBIFS images in advance like e2fsprogs[1] does. (Goto [2] to see how to + generate inconsistent UBIFS images). + Original UBIFS image content: + / + ├── corrupt_file (xattr - user.corrupt:123, 2K data) + ├── dir + │ ├── block_dev + │ ├── char_dev + │ ├── dir + │ └── file (content: '123') + ├── hardl_corrupt_file => corrupt_file + └── softl_corrupt_file -> corrupt_file + Here's a descriptons of the various test images: + ========================================================================= + image | Description | expectancy + ------------------------------------------------------------------------- + good | good image contains | fsck success, fs content is + | kinds of files. | not changed. + ------------------------------------------------------------------------- + sb_fanout | invalid fanout in | fsck failed. + | superblock. | + ------------------------------------------------------------------------- + sb_fmt_version | invalid fmt_version | fsck failed. + | in superblock. | + ------------------------------------------------------------------------- + sb_leb_size | invalid leb_size in | fsck failed. + | superblock. | + ------------------------------------------------------------------------- + sb_log_lebs | invalid log lebs in | fsck failed. + | superblock. | + ------------------------------------------------------------------------- + sb_min_io_size | invalid min_io_size | fsck failed. + | in superblock. | + ------------------------------------------------------------------------- + master_highest_inum | invalid highest_inum| fsck success, fs content is + | in master nodes. | not changed. + ------------------------------------------------------------------------- + master_lpt | bad lpt pos in | fsck success, fs content is + | master nodes. | not changed. + ------------------------------------------------------------------------- + master_tnc | bad tnc pos in | fsck success, fs content is + | master nodes. | not changed. + ------------------------------------------------------------------------- + master_total_dead | bad total_dead in | fsck success, fs content is + | master nodes. | not changed. + ------------------------------------------------------------------------- + master_total_dirty | bad total_dirty in | fsck success, fs content is + | master nodes. | not changed. + ------------------------------------------------------------------------- + master_total_free | bad total_free in | fsck success, fs content is + | master nodes. | not changed. + ------------------------------------------------------------------------- + journal_log | corrupted log area. | fsck success, fs content is + | | not changed. + ------------------------------------------------------------------------- + journal_bud | corrupted bud area. | fsck success, file data is + | | lost. + ------------------------------------------------------------------------- + orphan_node | bad orphan node. | fsck success, file is + | | deleted as expected. + ------------------------------------------------------------------------- + lpt_dirty | bad dirty in pnode. | fsck success, fs content is + | | not changed. + ------------------------------------------------------------------------- + lpt_flags | bad flags in pnode | fsck success, fs content is + | (eg. index). | not changed. + ------------------------------------------------------------------------- + lpt_free | bad free in pnode. | fsck success, fs content is + | | not changed. + ------------------------------------------------------------------------- + lpt_pos | bad pos in nnode. | fsck success, fs content is + | | not changed. + ------------------------------------------------------------------------- + ltab_dirty | bad dirty in lprops | fsck success, fs content is + | table. | not changed. + ------------------------------------------------------------------------- + ltab_free | bad free in lprops | fsck success, fs content is + | table. | not changed. + ------------------------------------------------------------------------- + index_size | bad index size in | fsck success, fs content is + | master nodes. | not changed. + ------------------------------------------------------------------------- + tnc_lv0_key | bad key in lv0 | fsck success, fs content is + | znode. | not changed. + ------------------------------------------------------------------------- + tnc_lv0_len | bad len in lv0 | fsck success, fs content is + | znode. | not changed. + ------------------------------------------------------------------------- + tnc_lv0_pos | bad pos in lv0 | fsck success, fs content is + | znode. | not changed. + ------------------------------------------------------------------------- + tnc_noleaf_key | bad key in non-leaf | fsck success, fs content is + | znode. | not changed. + ------------------------------------------------------------------------- + tnc_noleaf_len | bad len in non-leaf | fsck success, fs content is + | znode. | not changed. + ------------------------------------------------------------------------- + tnc_noleaf_pos | bad pos in non-leaf | fsck success, fs content is + | znode. | not changed. + ------------------------------------------------------------------------- + corrupted_data_leb | corrupted data leb. | fsck success, partial data of + | | file is lost. + ------------------------------------------------------------------------- + corrupted_idx_leb | corrupted index leb.| fsck success, fs content is + | | not changed. + ------------------------------------------------------------------------- + inode_data | bad data node. | fsck success, file content + | | is changed, other files are + | | not changed. + ------------------------------------------------------------------------- + inode_mode | bad inode mode for | fsck success, file is + | file. | dropped, other files are not + | | changed. + ------------------------------------------------------------------------- + inode_nlink | wrong nlink for | fsck success, nlink is + | file. | corrected, fs content is not + | | changed. + ------------------------------------------------------------------------- + inode_size | wrong inode size | fsck success, inode size is + | for file. | corrected, fs content is not + | | changed. + ------------------------------------------------------------------------- + inode_xcnt | wrong inode | fsck success, xattr_cnt is + | xattr_cnt for file. | corrected, fs content is not + | | changed. + ------------------------------------------------------------------------- + soft_link_inode_mode| bad inode mode for | fsck success, soft link + | solf link file. | file is dropped, other files + | | are not changed. + ------------------------------------------------------------------------- + soft_link_data_len | bad inode data_len | fsck success, soft link + | for solt link file. | file is dropped, other files + | | are not changed. + ------------------------------------------------------------------------- + dentry_key | bad dentry key for | fsck success, dentry is + | file. | removed, other files are + | | not changed. + ------------------------------------------------------------------------- + dentry_nlen | inconsistent nlen | fsck success, dentry is + | and name in dentry | removed, other files are + | for file. | not changed. + ------------------------------------------------------------------------- + dentry_type | inconsistent type | fsck success, dentry is + | between dentry and | removed, other files are + | inode for file. | not changed. + ------------------------------------------------------------------------- + xinode_flags | lost UBIFS_XATTR_FL | fsck success, xattr is + | in xattr inode | removed, other files are + | flags for file. | not changed. + ------------------------------------------------------------------------- + xinode_key | bad xattr inode key | fsck success, xattr is + | for file. | removed, other files are + | | not changed. + ------------------------------------------------------------------------- + xinode_mode | bad xattr inode | fsck success, xattr is + | mode for file. | removed, other files are + | | not changed. + ------------------------------------------------------------------------- + xentry_key | bad xattr entry key | fsck success, xattr is + | for file. | removed, other files are + | | not changed. + ------------------------------------------------------------------------- + xentry_nlen | inconsistent nlen | fsck success, xattr is + | and name in xattr | removed, other files are + | entry for file. | not changed. + ------------------------------------------------------------------------- + xentry_type | inconsistent type | fsck success, xattr is + | between xattr entry | removed, other files are + | and xattr inode for | not changed. + | file. | + ------------------------------------------------------------------------- + xent_host | the xattr's host | fsck success, file, hard + | is a xattr too, the | link and soft link are + | flag of corrupt_file| dropped, other files are + | inode is modified. | not changed. + ------------------------------------------------------------------------- + dir_many_dentry | dir has too many | fsck success, hard link is + | dentries, the dentry| dropped, other files are not + | of hard link is | changed. + | modified. | + ------------------------------------------------------------------------- + dir_lost | bad dentry for dir. | fsck success, the 'file' is + | | recovered under lost+found, + | | left files under dir are + | | removed, other files are not + | | changed. + ------------------------------------------------------------------------- + dir_lost_duplicated | bad inode for dir, | fsck success, the 'file' is + | there is a file | recovered with INO_<inum>_1 + | under lost+found, | under lost+found, left files + | which named with the| under dir are removed, other + | inum of the 'file'. | files are not changed. + ------------------------------------------------------------------------- + dir_lost_not_recover| bad inode for dir, | fsck success, all files + | lost+found is a | under dir are removed, + | regular file and | other files are not changed. + | exists under root | + | dir. | + ------------------------------------------------------------------------- + root_dir | bad '/'. | fsck success, create new + | | root dir('/'). All regular + | | files are reocovered under + | | lost+found, other files are + | | removed. + ------------------------------------------------------------------------- + empty_tnc | all files have bad | fsck success, fs content + | inode. | becomes empty. + ========================================================================= + + There is one testcase for mkfs.ubifs on encryption/non-encryption + situations: + 1) build_fs_from_dir: Initialize UBIFS image from a given directory, then + check whether the fs content in mounted UBIFS is consistent with the + original directory. Both UBI volume and file are chosen as storage + mediums to test. This testcase mainly ensures that mkfs.ubifs can + format an UBIFS image as user expected. + + Dependence + ---------- + kernel configs: + CONFIG_MTD_NAND_NANDSIM=m + CONFIG_MTD_MTDRAM=m + CONFIG_MTD_UBI=m + CONFIG_UBIFS_FS=m + CONFIG_UBIFS_FS_XATTR=y + CONFIG_UBIFS_FS_AUTHENTICATION=y + CONFIG_FS_ENCRYPTION=y + CONFIG_FAILSLAB=y + CONFIG_FAIL_PAGE_ALLOC=y + + tools: + fsstress [3][4] + keyctl [5] + fscryptctl [6] + setfattr/getfattr [7] + + Running + ------- + + Please build and install mtd-utils first. + Run single case: + cd $INSTALL_DIR/libexec/mtd-utils + ./powercut_fsck_mount.sh + ./random_corrupted_fsck.sh + ./cycle_mount_fsck_check.sh + ./build_fs_from_dir.sh + Run all cases: sh $INSTALL_DIR/libexec/mtd-utils/ubifs_tools_run_all.sh + + References + ---------- + + [1] https://git.kernel.org/pub/scm/fs/ext2/e2fsprogs.git/tree/tests/README + [2] https://bugzilla.kernel.org/show_bug.cgi?id=218924 + [3] https://git.kernel.org/pub/scm/fs/xfs/xfstests-dev.git/tree/ltp/fsstress.c + [4] https://github.com/linux-test-project/ltp/blob/master/testcases/kernel/fs/fsstress/fsstress.c + [5] https://github.com/torvalds/linux/blob/master/security/keys/keyctl.c + [6] https://github.com/google/fscryptctl + [7] https://github.com/philips/attr/tree/master diff --git a/tests/ubifs_tools-tests/fsck_tests/authentication_refuse.sh.in b/tests/ubifs_tools-tests/fsck_tests/authentication_refuse.sh.in new file mode 100755 index 0000000..268a7de --- /dev/null +++ b/tests/ubifs_tools-tests/fsck_tests/authentication_refuse.sh.in @@ -0,0 +1,66 @@ +#!/bin/sh +# Copyright (c), 2024, Huawei Technologies Co, Ltd. +# Author: Zhihao Cheng <chengzhihao1@huawei.com> +# +# Test Description: +# Refuse checking authenticated UBIFS image +# Running time: 10s + +TESTBINDIR=@TESTBINDIR@ +source $TESTBINDIR/common.sh + +ID="0xec,0xa1,0x00,0x15" # 128M 128KB 2KB 512-sub-page + +function run_test() +{ + echo "Do authentication_refused test" + + modprobe nandsim id_bytes=$ID + mtdnum="$(find_mtd_device "$nandsim_patt")" + flash_eraseall /dev/mtd$mtdnum + + modprobe ubi mtd="$mtdnum,2048" || fatal "modprobe ubi fail" + ubimkvol -N vol_test -m -n 0 /dev/ubi$UBI_NUM || fatal "mkvol fail" + modprobe ubifs || fatal "modprobe ubifs fail" + + mount_ubifs $DEV $MNT "authentication" || fatal "mount ubifs failed" + fsstress -d $MNT/fsstress -l0 -p4 -n10000 & + sleep $((RANDOM % 5)) + + ps -e | grep -w fsstress > /dev/null 2>&1 + while [ $? -eq 0 ] + do + killall -9 fsstress > /dev/null 2>&1 + sleep 1 + ps -e | grep -w fsstress > /dev/null 2>&1 + done + + while true + do + res=`mount | grep "$MNT"` + if [[ "$res" == "" ]] + then + break; + fi + umount $MNT + sleep 0.1 + done + + fsck.ubifs -a $DEV # 'fsck.ubifs $DEV' is fine too. + res=$? + if [[ $res == $FSCK_OK ]] + then + fatal "fsck should not be success!" + fi + + modprobe -r ubifs + modprobe -r ubi + modprobe -r nandsim +} + +start_t=$(date +%s) +run_test +end_t=$(date +%s) +time_cost=$(( end_t - start_t )) +echo "Success, cost $time_cost seconds" +exit 0 diff --git a/tests/ubifs_tools-tests/fsck_tests/cycle_corrupted_fsck_fault_inject.sh.in b/tests/ubifs_tools-tests/fsck_tests/cycle_corrupted_fsck_fault_inject.sh.in new file mode 100755 index 0000000..2073fec --- /dev/null +++ b/tests/ubifs_tools-tests/fsck_tests/cycle_corrupted_fsck_fault_inject.sh.in @@ -0,0 +1,225 @@ +#!/bin/sh +# Copyright (c), 2024, Huawei Technologies Co, Ltd. +# Author: Zhihao Cheng <chengzhihao1@huawei.com> +# +# Test Description: +# For many kinds of flash, do following things +# 1. mount UBIFS +# 2. fsstress && unmount +# 3. inject corruption into UBIFS image randomly +# 3. fsck UBIFS && inject kinds of errors(memory, io) +# 4. check UBIFS mounting result +# Running time: 15min + +TESTBINDIR=@TESTBINDIR@ +source $TESTBINDIR/common.sh + +function run_test() +{ + local simulator="$1"; + local size="$2"; + local peb_size="$3"; + local page_size="$4"; + local encryption=$5; + + echo "======================================================================" + printf "%s" "$simulator: ${size}MiB PEB size ${peb_size}KiB" + if [ "$simulator" = "nandsim" ]; then + printf " %s" "page size ${page_size}Bytes" + fi + printf " $encryption\n" + + if [ "$simulator" = "nandsim" ]; then + $TESTBINDIR/load_nandsim.sh "$size" "$peb_size" "$page_size" || echo "cannot load nandsim"; + mtdnum="$(find_mtd_device "$nandsim_patt")" + elif [ "$simulator" = "mtdram" ]; then + load_mtdram "$size" "$peb_size" || echo "cannot load mtdram" + mtdnum="$(find_mtd_device "$mtdram_patt")" + else + fatal "$simulator is not supported" + fi + + flash_eraseall /dev/mtd$mtdnum + modprobe ubi mtd="$mtdnum,$page_size" || fatal "modprobe ubi fail" + ubimkvol -N vol_test -m -n 0 /dev/ubi$UBI_NUM || fatal "mkvol fail" + modprobe ubifs || fatal "modprobe ubifs fail" + mount_ubifs $DEV $MNT || fatal "mount ubifs fail" + if [[ "$encryption" == "encrypted" ]]; then + encryption_gen_key + encryption_set_key $MNT + fi + + fsstress -d $MNT -l0 -p4 -n10000 & + + sleep $((RANDOM % 20)) + + ps -e | grep -w fsstress > /dev/null 2>&1 + while [ $? -eq 0 ] + do + killall -9 fsstress > /dev/null 2>&1 + sleep 1 + ps -e | grep -w fsstress > /dev/null 2>&1 + done + + while true + do + res=`mount | grep "$MNT"` + if [[ "$res" == "" ]] + then + break; + fi + umount $MNT + sleep 0.1 + done + + # inject corruption + times=$((RANDOM % 10)) + let times=$times+10 + i=0 + tot_peb=`cat /sys/class/ubi/ubi$UBI_NUM/total_eraseblocks`; + + modprobe -r ubifs + modprobe -r ubi # Stop wear-leveling & erasing worker + while [[ $i -lt $times ]] + do + let i=$i+1; + peb=$((RANDOM % $tot_peb)); + pg=`expr $peb_size \* 1024`; + peb_off=`expr $pg \* $peb` + pages=`expr $pg / $page_size`; + pg=`expr $pages - 2`; + pg=$((RANDOM % $pg)); + pg_off=`expr $pg + 2`; + pg_start=`expr $pages \* $peb`; + pg=`expr $pg_start + $pg_off`; + vid_pg=`expr $pg_start + 1`; + dd if=/dev/mtd$mtdnum of=$TMP_FILE bs=$page_size skip=$vid_pg count=1 2>/dev/null; + content=`cat $TMP_FILE | grep UBI!`; # vid header magic + if [[ "$content" == "" ]]; then + # Skip free PEB, otherwise LEB data could be overwritten in UBIFS + continue; + fi + if [[ $((RANDOM % 2)) == 0 ]]; then + # Corrupts 1 page + dd if=/dev/urandom of=/dev/mtd$mtdnum bs=$page_size seek=$pg count=1; + else + # Erase 1 LEB, TNC points to an unmapped area + flash_erase /dev/mtd$mtdnum $peb_off 1 + fi + done + rm -f $TMP_FILE 2>/dev/null + sync + + skip=0 + modprobe ubi mtd="$mtdnum,$page_size" + ret=$? + if [[ $ret != 0 ]] + then + skip=1 + echo "UBI layout volume is corrupted, skip" + fi + + if [[ $skip == 0 ]]; then + modprobe ubifs || fatal "modprobe ubifs2 fail" + dmesg -c > /dev/null + + round=0 + while [[ $round -lt 50 ]]; + do + let round=$round+1 + inject_mem=0 + + fsck.ubifs -yb $DEV 2>&1 > $LOG_FILE & + pid=$! + if [[ $((RANDOM % 2)) == 0 ]]; then + inject_mem_err $pid + inject_mem=1 + fi + inject_io_err + wait $pid + cat $LOG_FILE + if [[ $inject_mem == 1 ]]; then + cancel_mem_err + fi + cancel_io_err + + # UBI could become ro-mode, reload it + modprobe -r ubifs + modprobe -r ubi + modprobe ubi mtd="$mtdnum,$page_size" || fatal "modprobe ubi2 fail" + modprobe ubifs || fatal "modprobe ubifs3 fail" + done + + fsck.ubifs -yb $DEV 2>&1 > $LOG_FILE + res=$? + cat $LOG_FILE + let "ret=$res&~$FSCK_NONDESTRUCT" + if [[ $ret != $FSCK_OK ]] + then + # Skip superblock error + log=`cat $LOG_FILE | grep "bad node at LEB 0:"` + if [[ "$log" != "" ]] + then + skip=1 + echo "SB is corrupted, skip fsck & mounting" + else + fatal "fsck fail $res" + fi + fi + + if [[ $skip == 0 ]]; then + enable_chkfs + + mount_ubifs $DEV $MNT "noauthentication" "noatime" + res=$? + if [[ $res != 0 ]] + then + fatal "mount fail $res" + fi + + if [[ "$encryption" == "encrypted" ]]; then + # Ignore the encrypting error, root dir could be + # corrupted, the new root dir cannot be + # encrypted because it is not empty. + encryption_set_key $MNT 1 + fi + + du -sh $MNT > /dev/null # Make sure all files are accessible + ret=$? + if [[ $ret != 0 ]]; then + fatal "Cannot access all files" + fi + # check_err_msg is not suggested in this testcase, because + # ubi_io_read(triggered by wear_leveling_worker -> ubi_eba_copy_leb) + # could print stack if ecc uncorrectable errors are detected. + + umount $MNT + res=$? + if [[ $res != 0 ]] + then + fatal "unmount fail $res" + fi + fi + + modprobe -r ubifs + modprobe -r ubi + fi + modprobe -r $simulator + + echo "----------------------------------------------------------------------" +} + +check_fsstress +start_t=$(date +%s) +echo "Do corrruption+cycle_fsck_fault_injection test in kinds of flashes" +for simulator in "mtdram" "nandsim"; do + for encryption in "encrypted" "noencrypted"; do + run_test "$simulator" "16" "16" "512" $encryption + run_test "$simulator" "256" "128" "2048" $encryption + run_test "$simulator" "1024" "512" "2048" $encryption + done +done +end_t=$(date +%s) +time_cost=$(( end_t - start_t )) +echo "Success, cost $time_cost seconds" +exit 0 diff --git a/tests/ubifs_tools-tests/fsck_tests/cycle_mount_fsck_check.sh.in b/tests/ubifs_tools-tests/fsck_tests/cycle_mount_fsck_check.sh.in new file mode 100755 index 0000000..c9972e2 --- /dev/null +++ b/tests/ubifs_tools-tests/fsck_tests/cycle_mount_fsck_check.sh.in @@ -0,0 +1,154 @@ +#!/bin/sh +# Copyright (c), 2024, Huawei Technologies Co, Ltd. +# Author: Zhihao Cheng <chengzhihao1@huawei.com> +# +# Test Description: +# Do many cycles of mount/fsstress/umount/fsck/mount, check whether the +# filesystem content before fsck and after fsck are consistent. +# Running time: 10h + +TESTBINDIR=@TESTBINDIR@ +source $TESTBINDIR/common.sh + +ID="0xec,0xa1,0x00,0x15" # 128M 128KB 2KB 512-sub-page + +function run_test() +{ + encryption=$1 + modprobe nandsim id_bytes=$ID + mtdnum="$(find_mtd_device "$nandsim_patt")" + flash_eraseall /dev/mtd$mtdnum + + dmesg -c > /dev/null + + modprobe ubi mtd="$mtdnum,2048" || fatal "modprobe ubi fail" + ubimkvol -N vol_test -m -n 0 /dev/ubi$UBI_NUM || fatal "mkvol fail" + modprobe ubifs || fatal "modprobe ubifs fail" + + echo "Do cycle mount+umount+fsck+check_fs_content test ($encryption)" + + if [[ "$encryption" == "encrypted" ]]; then + encryption_gen_key + fi + + round=0 + while [[ $round -lt 20 ]] + do + echo "---------------------- ROUND $round ----------------------" + let round=$round+1 + + mount_ubifs $DEV $MNT "noauthentication" "noatime" || fatal "mount ubifs fail" + if [[ "$encryption" == "encrypted" ]]; then + encryption_set_key $MNT + fi + + per=`df -Th | grep ubifs | awk '{print $6}'`; + if [[ ${per%?} -gt 95 ]]; then + # Used > 95% + echo "Clean files" + rm -rf $MNT/* + check_err_msg + fi + + fsstress -d $MNT -l0 -p4 -n10000 & + + sleep $((RANDOM % 30)) + + ps -e | grep -w fsstress > /dev/null 2>&1 + while [ $? -eq 0 ] + do + killall -9 fsstress > /dev/null 2>&1 + sleep 1 + ps -e | grep -w fsstress > /dev/null 2>&1 + done + + per=`df -Th | grep ubifs | awk '{print $6}'`; + if [[ ${per%?} -gt 95 ]]; then + dmesg -c > /dev/null # The ENOSPC error messages may exist + else + check_err_msg # Make sure new operations are okay after fsck + fi + sync + + # Record filesystem information + rm -f $TMP_FILE 2>/dev/null + read_dir $MNT "md5sum" + + while true + do + res=`mount | grep "$MNT"` + if [[ "$res" == "" ]] + then + break; + fi + umount $MNT + sleep 0.1 + done + + fsck.ubifs -a $DEV 2>&1 > $LOG_FILE + res=$? + cat $LOG_FILE + if [[ $res != $FSCK_OK ]] + then + # The lpt nodes could be parsed incorrectly because the lpt disk + # layout is too simple. See details in + # https://lore.kernel.org/linux-mtd/97ca7fe4-4ad4-edd1-e97a-1d540aeabe2d@huawei.com/ + log=`cat $LOG_FILE | grep "dbg_check_ltab_lnum: invalid empty space in LEB"` + if [[ "$log" == "" ]]; then + fatal "fsck fail $res" + fi + if [[ $res != $FSCK_NONDESTRUCT ]]; then + fatal "fsck fail $res" + fi + fi + + enable_chkfs + + mount_ubifs $DEV $MNT "noauthentication" "noatime" + res=$? + if [[ $res != 0 ]] + then + fatal "mount fail $res" + fi + + if [[ "$encryption" == "encrypted" ]]; then + encryption_set_key $MNT + fi + + du -sh $MNT > /dev/null # Ensure that all files are accessible + ret=$? + if [[ $ret != 0 ]]; then + fatal "Cannot access all files" + fi + check_err_msg + + # Check filesystem information + parse_dir "md5sum" + rm -f $TMP_FILE 2>/dev/null + + umount $MNT + res=$? + if [[ $res != 0 ]] + then + fatal "unmount fail $res" + fi + + check_err_msg + + disable_chkfs + done + + modprobe -r ubifs + modprobe -r ubi + modprobe -r nandsim +} + +check_fsstress +start_t=$(date +%s) +for encryption in "encrypted" "noencrypted"; do + run_test $encryption +done +end_t=$(date +%s) +time_cost=$(( end_t - start_t )) +echo "Success, cost $time_cost seconds" +exit 0 diff --git a/tests/ubifs_tools-tests/fsck_tests/cycle_powercut_mount_fsck.sh.in b/tests/ubifs_tools-tests/fsck_tests/cycle_powercut_mount_fsck.sh.in new file mode 100755 index 0000000..6af1b54 --- /dev/null +++ b/tests/ubifs_tools-tests/fsck_tests/cycle_powercut_mount_fsck.sh.in @@ -0,0 +1,148 @@ +#!/bin/sh +# Copyright (c), 2024, Huawei Technologies Co, Ltd. +# Author: Zhihao Cheng <chengzhihao1@huawei.com> +# +# Test Description: +# Do many cycles of mount/fsstress/powercut/umount/fsck/mount, check whether +# mount is successful. +# Running time: 9h + +TESTBINDIR=@TESTBINDIR@ +source $TESTBINDIR/common.sh + +ID="0x20,0xa7,0x00,0x26" # 4G 256KB 4KB 2KB-sub-page + +function run_test() +{ + local encryption=$1 + + echo "Do cycle mount+powercut+fsck+umount($encryption) test" + modprobe nandsim id_bytes=$ID + mtdnum="$(find_mtd_device "$nandsim_patt")" + flash_eraseall /dev/mtd$mtdnum + + dmesg -c > /dev/null + + modprobe ubi mtd="$mtdnum,4096" || fatal "modprobe ubi fail" + ubimkvol -N vol_test -m -n 0 /dev/ubi$UBI_NUM || fatal "mkvol fail" + modprobe ubifs || fatal "modprobe ubifs fail" + + if [[ "$encryption" == "encrypted" ]]; then + encryption_gen_key + fi + + round=0 + while [[ $round -lt 60 ]] + do + echo "---------------------- ROUND $round ----------------------" + let round=$round+1 + + mount_ubifs $DEV $MNT || fatal "mount ubifs fail" + if [[ "$encryption" == "encrypted" ]]; then + encryption_set_key $MNT + fi + + if [[ $(($round % 30)) == 0 ]] + then + echo "Clean files" + rm -rf $MNT/* + check_err_msg + fi + + fsstress -d $MNT -l0 -p4 -n10000 & + sleep $((RANDOM % 30)) + + per=`df -Th | grep ubifs | awk '{print $6}'`; + if [[ ${per%?} -gt 95 ]]; then + dmesg -c > /dev/null # The ENOSPC error messages may exist + else + check_err_msg # Make sure new operations are okay after fsck + fi + powercut + + ps -e | grep -w fsstress > /dev/null 2>&1 + while [ $? -eq 0 ] + do + killall -9 fsstress > /dev/null 2>&1 + sleep 1 + ps -e | grep -w fsstress > /dev/null 2>&1 + done + + while true + do + res=`mount | grep "$MNT"` + if [[ "$res" == "" ]] + then + break; + fi + umount $MNT + sleep 0.1 + done + + fsck.ubifs -a $DEV 2>&1 > $LOG_FILE + res=$? + cat $LOG_FILE + if [[ $res != $FSCK_OK ]] + then + # Powercut during layout_leb_in_gaps may change index + # LEBs without updating LPT. + idx_log=`cat $LOG_FILE | grep "Inconsistent properties" | grep "is_idx 1"` + # The lpt nodes could be parsed incorrectly because the lpt disk + # layout is too simple. See details in + # https://lore.kernel.org/linux-mtd/97ca7fe4-4ad4-edd1-e97a-1d540aeabe2d@huawei.com/ + lpt_log=`cat $LOG_FILE | grep "dbg_check_ltab_lnum: invalid empty space in LEB"` + if [[ "$idx_log" == "" ]] && [[ "$lpt_log" == "" ]]; then + fatal "fsck fail $res" + fi + if [[ $res != $FSCK_NONDESTRUCT ]]; then + fatal "fsck fail $res" + fi + fi + + dmesg -c > /dev/null # powercut could reproduce error messages + + enable_chkfs + + mount_ubifs $DEV $MNT "noauthentication" "noatime" + res=$? + if [[ $res != 0 ]] + then + fatal "mount fail $res" + fi + + if [[ "$encryption" == "encrypted" ]]; then + encryption_set_key $MNT + fi + + du -sh $MNT > /dev/null # Make sure all files are accessible + ret=$? + if [[ $ret != 0 ]]; then + fatal "Cannot access all files" + fi + check_err_msg + + umount $MNT + res=$? + if [[ $res != 0 ]] + then + fatal "unmount fail $res" + fi + + check_err_msg + + disable_chkfs + done + + modprobe -r ubifs + modprobe -r ubi + modprobe -r nandsim +} + +check_fsstress +start_t=$(date +%s) +run_test "encrypted" +run_test "noencrypted" +end_t=$(date +%s) +time_cost=$(( end_t - start_t )) +echo "Success, cost $time_cost seconds" +exit 0 diff --git a/tests/ubifs_tools-tests/fsck_tests/fsck_bad_image.sh.in b/tests/ubifs_tools-tests/fsck_tests/fsck_bad_image.sh.in new file mode 100755 index 0000000..2ba9f78 --- /dev/null +++ b/tests/ubifs_tools-tests/fsck_tests/fsck_bad_image.sh.in @@ -0,0 +1,355 @@ +#!/bin/sh +# Copyright (c), 2024, Huawei Technologies Co, Ltd. +# Author: Zhihao Cheng <chengzhihao1@huawei.com> +# +# Test Description: Tests whether all inconsistent UBIFS images can be fixed +# as expected. +# Origin UBIFS image content: +# / +# ├── corrupt_file (xattr - user.corrupt:123, 2K data) +# ├── dir +# │ ├── block_dev +# │ ├── char_dev +# │ ├── dir +# │ └── file (content: '123') +# ├── hardl_corrupt_file => corrupt_file +# └── softl_corrupt_file -> corrupt_file +# +# Running time: 2min + +TESTBINDIR=@TESTBINDIR@ +source $TESTBINDIR/common.sh + +CORRUPT_FILE=corrupt_file +CORRUPT_FILE_INUM=INO_65 +XATTR_NAME="user.corrupt" +XATTR_VAL=123 +CORRUPT_FILE_MD5=7d2f953e91033c743ab6a801d5ee6e15 +SOFT_LINK_FILE=softl_corrupt_file +HARD_LINK_FILE=hardl_corrupt_file +DIR=dir +BLOCK_DEV=block_dev +CHAR_DEV=char_dev +FILE=file +FILE_INUM=INO_72 +FILE_MD5=ba1f2511fc30423bdbb183fe33f3dd0f +LOST_FOUND="lost+found" + +function fsck_image() +{ + local img_type=$1; + local img=$2; + local fsck_mode=$3; + local file_exist=$4; + local file_nochange=$5 + local file_xattr_exist=$6; + local hard_link_exist=$7; + local hard_link_no_change=$8; + local hard_link_xattr_exist=$9; + local soft_link_exist=${10}; + local dir_exist=${11}; + local dir_file_no_change=${12}; + local lost_found=${13}; + + echo "======================================================================" + echo "fsck $img_type, success_fsck_mode:$fsck_mode file_exist:$file_exist file_nochange:$file_nochange file_xattr_exist:$file_xattr_exist hard_link_exist:$hard_link_exist hard_link_no_change:$hard_link_no_change:hard_link_xattr_exist $hard_link_xattr_exist:soft_link_exist:$soft_link_exist dir_exist:$dir_exist $lost_found" + + load_mtdram 1 16 || echo "cannot load mtdram" + mtdnum="$(find_mtd_device "$mtdram_patt")" + + dmesg -c > /dev/null + gzip -f -k -d $TESTBINDIR/${img}.gz || fatal "gzip failed" + flash_eraseall /dev/mtd$mtdnum + dd if=$TESTBINDIR/$img of=/dev/mtd$mtdnum bs=1M + rm -f $TESTBINDIR/$img + modprobe ubi mtd="$mtdnum,0" || fatal "modprobe ubi fail" + modprobe ubifs || fatal "modprobe ubifs fail" + + fsck.ubifs -a $DEV + let "ret=$?&~$FSCK_NONDESTRUCT" + if [[ $ret != $FSCK_OK ]]; then + if [[ "$fsck_mode" == "safe" ]]; then + fatal "The image should be fixed in $fsck_mode mode, but it fails" + fi + + fsck.ubifs -y $DEV + let "ret=$?&~$FSCK_NONDESTRUCT" + if [[ $ret != $FSCK_OK ]]; then + if [[ "$fsck_mode" == "danger_default" ]]; then + fatal "The image should be fixed in $fsck_mode mode, but it fails" + fi + + fsck.ubifs -yb $DEV + let "ret=$?&~$FSCK_NONDESTRUCT" + if [[ $ret != $FSCK_OK ]]; then + if [[ "$fsck_mode" == "danger_rebuild" ]]; then + fatal "The image should be fixed in $fsck_mode mode, but it fails" + fi + + echo "fsck failed is expected, skip" + + modprobe -r ubifs + modprobe -r ubi + modprobe -r mtdram + echo "----------------------------------------------------------------------" + + return; + elif [[ "$fsck_mode" != "danger_rebuild" ]]; then + fatal "The image should not be fixed in $fsck_mode mode, but it succeeds" + fi + elif [[ "$fsck_mode" != "danger_default" ]]; then + fatal "The image should not be fixed in $fsck_mode mode, but it succeeds" + fi + elif [[ "$fsck_mode" != "safe" ]]; then + fatal "The image should not be fixed in $fsck_mode mode, but it succeeds" + fi + + enable_chkfs + + mount_ubifs $DEV $MNT + ret=$? + if [[ $ret != 0 ]]; then + fatal "mount failed $ret" + fi + + du -sh $MNT > /dev/null # Make sure all files are accessible + ret=$? + if [[ $ret != 0 ]]; then + fatal "cannot access all files $ret" + fi + + if [[ $file_exist == 1 ]]; then + if ! [ -f $MNT/$CORRUPT_FILE ]; then + fatal "$MNT/$CORRUPT_FILE is lost" + fi + else + if [ -f $MNT/$CORRUPT_FILE ]; then + fatal "$MNT/$CORRUPT_FILE should not exist" + fi + fi + + md5_after=`md5sum $MNT/$CORRUPT_FILE 2>/dev/null | awk '{print $1}'` + if [[ $file_nochange == 1 ]]; then + if [[ $CORRUPT_FILE_MD5 != $md5_after ]]; then + fatal "content changed for $MNT/$CORRUPT_FILE" + fi + else + if [[ $CORRUPT_FILE_MD5 == $md5_after ]]; then + fatal "content not changed for $MNT/$CORRUPT_FILE" + fi + fi + + xattr=`getfattr -n $XATTR_NAME $MNT/$CORRUPT_FILE 2>/dev/null | grep $XATTR_NAME | awk -F '=' '{ print $2 }'` + if [[ $file_xattr_exist == 1 ]]; then + if ! [[ "$xattr" =~ "$XATTR_VAL" ]]; then + fatal "wrong xattr $xattr for $MNT/$CORRUPT_DENT_NAME" + fi + else + if [[ "$xattr" =~ "$XATTR_VAL" ]]; then + fatal "xattr $xattr for $MNT/$CORRUPT_DENT_NAME should not exist" + fi + fi + + if [[ $hard_link_exist == 1 ]]; then + if ! [ -f $MNT/$HARD_LINK_FILE ]; then + fatal "$MNT/$HARD_LINK_FILE should is lost" + fi + else + if [ -f $MNT/$HARD_LINK_FILE ]; then + fatal "$MNT/$HARD_LINK_FILE should not exist" + fi + fi + + md5_after=`md5sum $MNT/$HARD_LINK_FILE 2>/dev/null | awk '{print $1}'` + if [[ $hard_link_no_change == 1 ]]; then + if [[ $CORRUPT_FILE_MD5 != $md5_after ]]; then + fatal "content changed for $MNT/$HARD_LINK_FILE" + fi + else + if [[ $CORRUPT_FILE_MD5 == $md5_after ]]; then + fatal "content not changed for $MNT/$HARD_LINK_FILE" + fi + fi + + xattr=`getfattr -n $XATTR_NAME $MNT/$HARD_LINK_FILE 2>/dev/null | grep $XATTR_NAME | awk -F '=' '{ print $2 }'` + if [[ $hard_link_xattr_exist == 1 ]]; then + if ! [[ "$xattr" =~ "$XATTR_VAL" ]]; then + fatal "wrong xattr $xattr for $MNT/$HARD_LINK_FILE" + fi + else + if [[ "$xattr" =~ "$XATTR_VAL" ]]; then + fatal "xattr $xattr for $MNT/$HARD_LINK_FILE should not exist" + fi + fi + + link=`stat -c %N $MNT/$SOFT_LINK_FILE 2>/dev/null | grep $SOFT_LINK_FILE | grep $CORRUPT_FILE` + if [[ $soft_link_exist == 1 ]]; then + if [[ "$link" == "" ]]; then + fatal "$MNT/$SOFT_LINK_FILE is lost" + fi + else + if [[ "$link" != "" ]]; then + fatal "$MNT/$SOFT_LINK_FILE should not exist" + fi + fi + + if [[ $dir_exist == 1 ]]; then + if ! [ -d $MNT/$DIR ]; then + fatal "$MNT/$DIR is lost" + fi + if ! [ -d $MNT/$DIR/$DIR ]; then + fatal "$MNT/$DIR/$DIR is lost" + fi + if ! [ -f $MNT/$DIR/$FILE ]; then + fatal "$MNT/$DIR/$FILE is lost" + fi + f_md5=`md5sum $MNT/$DIR/$FILE 2>/dev/null | awk '{print $1}'` + if [[ $dir_file_no_change == 1 ]]; then + if [[ $FILE_MD5 != $f_md5 ]]; then + fatal "content changed for $MNT/$DIR/$FILE" + fi + else + if [[ $FILE_MD5 == $f_md5 ]]; then + fatal "content not changed for $MNT/$DIR/$FILE" + fi + fi + if ! [ -b $MNT/$DIR/$BLOCK_DEV ]; then + fatal "$MNT/$DIR/$BLOCK_DEV is lost" + fi + major=`stat -c %t $MNT/$DIR/$BLOCK_DEV` + minor=`stat -c %T $MNT/$DIR/$BLOCK_DEV` + if [[ $major != 1 ]] || [[ $minor != 2 ]]; then + echo "major/minor changed for $MNT/$DIR/$BLOCK_DEV" + fi + if ! [ -c $MNT/$DIR/$CHAR_DEV ]; then + fatal "$MNT/$DIR/$CHAR_DEV is lost" + fi + major=`stat -c %t $MNT/$DIR/$CHAR_DEV` + minor=`stat -c %T $MNT/$DIR/$CHAR_DEV` + if [[ $major != 0 ]] || [[ $minor != 1 ]]; then + echo "major/minor changed for $MNT/$DIR/$CHAR_DEV" + fi + else + if [ -d $MNT/$DIR ]; then + fatal "$MNT/$DIR should not exist" + fi + fi + + if [[ "$lost_found" == "no lost+found" ]]; then + if [ -d $MNT/$LOST_FOUND ]; then + fatal "$MNT/$LOST_FOUND should not exist" + fi + elif [[ "$lost_found" == "lost+found is regular" ]]; then + if ! [ -f $MNT/$LOST_FOUND ]; then + fatal "$MNT/$LOST_FOUND is not regular file" + fi + else + if ! [ -d $MNT/$LOST_FOUND ]; then + fatal "$MNT/$LOST_FOUND is lost" + fi + + if ! [ -f $MNT/$LOST_FOUND/${FILE_INUM}_0 ]; then + fatal "$MNT/$LOST_FOUND/${FILE_INUM}_0 is lost" + fi + if [[ "$lost_found" == "lost+found has one" ]]; then + f_md5=`md5sum $MNT/$LOST_FOUND/${FILE_INUM}_0 2>/dev/null | awk '{print $1}'` + if [[ $FILE_MD5 != $f_md5 ]]; then + fatal "content changed for $MNT/$LOST_FOUND/${FILE_INUM}_0" + fi + elif [[ "$lost_found" == "lost+found has two" ]]; then + f_md5=`md5sum $MNT/$LOST_FOUND/${CORRUPT_FILE_INUM}_0 2>/dev/null | awk '{print $1}'` + if [[ $CORRUPT_FILE_MD5 != $f_md5 ]]; then + fatal "content changed for $MNT/$LOST_FOUND/${CORRUPT_FILE_INUM}_0" + fi + f_md5=`md5sum $MNT/$LOST_FOUND/${FILE_INUM}_0 2>/dev/null | awk '{print $1}'` + if [[ $FILE_MD5 != $f_md5 ]]; then + fatal "content changed for $MNT/$LOST_FOUND/${FILE_INUM}_0" + fi + else + if ! [ -f $MNT/$LOST_FOUND/${FILE_INUM}_1 ]; then + fatal "$MNT/$LOST_FOUND/${FILE_INUM}_1 is lost" + fi + f_md5=`md5sum $MNT/$LOST_FOUND/${FILE_INUM}_1 2>/dev/null | awk '{print $1}'` + if [[ $FILE_MD5 != $f_md5 ]]; then + fatal "content changed for $MNT/$LOST_FOUND/${FILE_INUM}_1" + fi + fi + fi + + umount $MNT + res=$? + if [[ $res != 0 ]] + then + fatal "unmount fail $res" + fi + + disable_chkfs + + check_err_msg + + modprobe -r ubifs + modprobe -r ubi + modprobe -r mtdram + + echo "----------------------------------------------------------------------" +} + +start_t=$(date +%s) +echo "Do inconsistent UBIFS images fscking test" +fsck_image "good image" good "safe" 1 1 1 1 1 1 1 1 1 "no lost+found" +fsck_image "bad sb fanout image" sb_fanout "none" 1 1 1 1 1 1 1 1 1 "no lost+found" +fsck_image "bad sb fmt_version image" sb_fmt_version "none" 1 1 1 1 1 1 1 1 1 "no lost+found" +fsck_image "bad sb leb_size image" sb_leb_size "none" 1 1 1 1 1 1 1 1 1 "no lost+found" +fsck_image "bad sb log_lebs image" sb_log_lebs "none" 1 1 1 1 1 1 1 1 1 "no lost+found" +fsck_image "bad sb min_io_size image" sb_min_io_size "none" 1 1 1 1 1 1 1 1 1 "no lost+found" +fsck_image "bad master highest_inum image" master_highest_inum "danger_rebuild" 1 1 1 1 1 1 1 1 1 "no lost+found" +fsck_image "bad master lpt image" master_lpt "danger_rebuild" 1 1 1 1 1 1 1 1 1 "no lost+found" +fsck_image "bad master tnc image" master_tnc "danger_rebuild" 1 1 1 1 1 1 1 1 1 "no lost+found" +fsck_image "bad master total_dead image" master_total_dead "safe" 1 1 1 1 1 1 1 1 1 "no lost+found" +fsck_image "bad master total_dirty image" master_total_dirty "safe" 1 1 1 1 1 1 1 1 1 "no lost+found" +fsck_image "bad master total_free image" master_total_free "safe" 1 1 1 1 1 1 1 1 1 "no lost+found" +fsck_image "corrupted journal log area image" journal_log "danger_rebuild" 1 1 1 1 1 1 1 1 1 "no lost+found" +fsck_image "corrupted journal bud area image" journal_bud "danger_default" 1 0 1 1 0 1 1 1 0 "no lost+found" +fsck_image "bad orphan node image" orphan_node "danger_default" 0 0 0 0 0 0 1 1 1 "no lost+found" +fsck_image "bad lpt dirty image" lpt_dirty "safe" 1 1 1 1 1 1 1 1 1 "no lost+found" +fsck_image "bad lpt lpt_flags image" lpt_flags "safe" 1 1 1 1 1 1 1 1 1 "no lost+found" +fsck_image "bad lpt free image" lpt_free "safe" 1 1 1 1 1 1 1 1 1 "no lost+found" +fsck_image "bad lpt pos image" lpt_pos "safe" 1 1 1 1 1 1 1 1 1 "no lost+found" +fsck_image "bad lprops table dirty image" ltab_dirty "safe" 1 1 1 1 1 1 1 1 1 "no lost+found" +fsck_image "bad lprops table free image" ltab_free "safe" 1 1 1 1 1 1 1 1 1 "no lost+found" +fsck_image "bad index size image" index_size "safe" 1 1 1 1 1 1 1 1 1 "no lost+found" +fsck_image "bad tnc lv0 key image" tnc_lv0_key "danger_rebuild" 1 1 1 1 1 1 1 1 1 "no lost+found" +fsck_image "bad tnc lv0 len image" tnc_lv0_len "danger_rebuild" 1 1 1 1 1 1 1 1 1 "no lost+found" +fsck_image "bad tnc lv0 pos image" tnc_lv0_pos "danger_rebuild" 1 1 1 1 1 1 1 1 1 "no lost+found" +fsck_image "bad tnc non-leaf key image" tnc_noleaf_key "danger_rebuild" 1 1 1 1 1 1 1 1 1 "no lost+found" +fsck_image "bad tnc non-leaf len image" tnc_noleaf_len "danger_rebuild" 1 1 1 1 1 1 1 1 1 "no lost+found" +fsck_image "bad tnc non-leaf pos image" tnc_noleaf_pos "danger_rebuild" 1 1 1 1 1 1 1 1 1 "no lost+found" +fsck_image "corrupted leb for file data image" corrupted_data_leb "danger_default" 1 0 1 1 0 1 1 1 1 "no lost+found" +fsck_image "corrupted leb for TNC image" corrupted_idx_leb "danger_rebuild" 1 1 1 1 1 1 1 1 1 "no lost+found" +fsck_image "bad inode data image" inode_data "danger_default" 1 0 1 1 0 1 1 1 1 "no lost+found" +fsck_image "bad inode mode image" inode_mode "danger_default" 0 0 0 0 0 0 1 1 1 "no lost+found" +fsck_image "bad inode nlink image" inode_nlink "safe" 1 1 1 1 1 1 1 1 1 "no lost+found" +fsck_image "bad inode size image" inode_size "safe" 1 1 1 1 1 1 1 1 1 "no lost+found" +fsck_image "bad inode xattr_cnt image" inode_xcnt "safe" 1 1 1 1 1 1 1 1 1 "no lost+found" +fsck_image "bad softlink inode mode image" soft_link_inode_mode "danger_default" 1 1 1 1 1 1 0 1 1 "no lost+found" +fsck_image "bad softlink inode data_len image" soft_link_data_len "danger_default" 1 1 1 1 1 1 0 1 1 "no lost+found" +fsck_image "bad dentry key image" dentry_key "danger_default" 0 0 0 1 1 1 1 1 1 "no lost+found" +fsck_image "bad dentry nlen image" dentry_nlen "danger_default" 0 0 0 1 1 1 1 1 1 "no lost+found" +fsck_image "bad dentry type image" dentry_type "danger_default" 0 0 0 1 1 1 1 1 1 "no lost+found" +fsck_image "bad xattr inode flags image" xinode_flags "danger_default" 1 1 0 1 1 0 1 1 1 "no lost+found" +fsck_image "bad xattr inode key image" xinode_key "danger_default" 1 1 0 1 1 0 1 1 1 "no lost+found" +fsck_image "bad xattr inode mode image" xinode_mode "danger_default" 1 1 0 1 1 0 1 1 1 "no lost+found" +fsck_image "bad xattr dentry key image" xentry_key "danger_default" 1 1 0 1 1 0 1 1 1 "no lost+found" +fsck_image "bad xattr dentry nlen image" xentry_nlen "danger_default" 1 1 0 1 1 0 1 1 1 "no lost+found" +fsck_image "bad xattr dentry type image" xentry_type "danger_default" 1 1 0 1 1 0 1 1 1 "no lost+found" +fsck_image "bad xattr host image" xent_host "danger_default" 0 0 0 0 0 0 1 1 1 "no lost+found" +fsck_image "dir has too many dentry image" dir_many_dentry "danger_default" 1 1 1 0 0 0 1 1 1 "no lost+found" +fsck_image "bad dir image" dir_lost "danger_default" 1 1 1 1 1 1 1 0 1 "lost+found has one" +fsck_image "bad dir and duplicated file name in lost+found image" dir_lost_duplicated "danger_default" 1 1 1 1 1 1 1 0 1 "lost+found has duplicated files" +fsck_image "bad dir and lost+found image" dir_lost_not_recover "danger_default" 1 1 1 1 1 1 1 0 1 "lost+found is regular" +fsck_image "bad root dir image" root_dir "danger_default" 0 0 0 0 0 0 0 0 1 "lost+found has two" +fsck_image "empty TNC image" empty_tnc "danger_rebuild" 0 0 0 0 0 0 0 0 1 "no lost+found" +end_t=$(date +%s) +time_cost=$(( end_t - start_t )) +echo "Success, cost $time_cost seconds" +exit 0 diff --git a/tests/ubifs_tools-tests/fsck_tests/powercut_fsck_mount.sh.in b/tests/ubifs_tools-tests/fsck_tests/powercut_fsck_mount.sh.in new file mode 100755 index 0000000..ac7128b --- /dev/null +++ b/tests/ubifs_tools-tests/fsck_tests/powercut_fsck_mount.sh.in @@ -0,0 +1,148 @@ +#!/bin/sh +# Copyright (c), 2024, Huawei Technologies Co, Ltd. +# Author: Zhihao Cheng <chengzhihao1@huawei.com> +# +# Test Description: +# For many kinds of flash, do following things +# 1. mount UBIFS +# 2. fsstress & powercut & unmount +# 3. fsck UBIFS +# 4. check UBIFS mounting result +# Running time: 1h + +TESTBINDIR=@TESTBINDIR@ +source $TESTBINDIR/common.sh + +function run_test() +{ + local simulator="$1"; + local size="$2"; + local peb_size="$3"; + local page_size="$4"; + local encryption=$5; + + echo "======================================================================" + printf "%s" "$simulator: ${size}MiB PEB size ${peb_size}KiB" + if [ "$simulator" = "nandsim" ]; then + printf " %s" "page size ${page_size}Bytes" + fi + printf " $encryption\n" + + if [ "$simulator" = "nandsim" ]; then + $TESTBINDIR/load_nandsim.sh "$size" "$peb_size" "$page_size" || echo "cannot load nandsim"; + mtdnum="$(find_mtd_device "$nandsim_patt")" + elif [ "$simulator" = "mtdram" ]; then + load_mtdram "$size" "$peb_size" || echo "cannot load mtdram" + mtdnum="$(find_mtd_device "$mtdram_patt")" + else + fatal "$simulator is not supported" + fi + + flash_eraseall /dev/mtd$mtdnum + modprobe ubi mtd="$mtdnum,$page_size" || fatal "modprobe ubi fail" + ubimkvol -N vol_test -m -n 0 /dev/ubi$UBI_NUM || fatal "mkvol fail" + modprobe ubifs || fatal "modprobe ubifs fail" + mount_ubifs $DEV $MNT || fatal "mount ubifs fail" + if [[ "$encryption" == "encrypted" ]]; then + encryption_gen_key + encryption_set_key $MNT + fi + + fsstress -d $MNT -l0 -p4 -n10000 & + sleep $((RANDOM % 120)) + powercut + + ps -e | grep -w fsstress > /dev/null 2>&1 + while [ $? -eq 0 ] + do + killall -9 fsstress > /dev/null 2>&1 + sleep 1 + ps -e | grep -w fsstress > /dev/null 2>&1 + done + + while true + do + res=`mount | grep "$MNT"` + if [[ "$res" == "" ]] + then + break; + fi + umount $MNT + sleep 0.1 + done + + fsck.ubifs -a $DEV 2>&1 > $LOG_FILE + res=$? + cat $LOG_FILE + if [[ $res != $FSCK_OK ]] + then + # Powercut during layout_leb_in_gaps may change index LEBs + # without updating LPT. + idx_log=`cat $LOG_FILE | grep "Inconsistent properties" | grep "is_idx 1"` + # The lpt nodes could be parsed incorrectly because the lpt disk + # layout is too simple. See details in + # https://lore.kernel.org/linux-mtd/97ca7fe4-4ad4-edd1-e97a-1d540aeabe2d@huawei.com/ + lpt_log=`cat $LOG_FILE | grep "dbg_check_ltab_lnum: invalid empty space in LEB"` + if [[ "$idx_log" == "" ]] && [[ "$lpt_log" == "" ]]; then + fatal "fsck fail $res" + fi + if [[ $res != $FSCK_NONDESTRUCT ]]; then + fatal "fsck fail $res" + fi + fi + + dmesg -c > /dev/null # powercut could reproduce error messages + + enable_chkfs + + mount_ubifs $DEV $MNT "noauthentication" "noatime" + res=$? + if [[ $res != 0 ]] + then + fatal "mount fail $res" + fi + + if [[ "$encryption" == "encrypted" ]]; then + encryption_set_key $MNT + fi + + du -sh $MNT > /dev/null # Make sure all files are accessible + ret=$? + if [[ $ret != 0 ]]; then + fatal "Cannot access all files" + fi + check_err_msg + + umount $MNT + res=$? + if [[ $res != 0 ]] + then + fatal "unmount fail $res" + fi + + check_err_msg + + modprobe -r ubifs + modprobe -r ubi + modprobe -r $simulator + + echo "----------------------------------------------------------------------" +} + +check_fsstress +start_t=$(date +%s) +echo "Do powercut+fsck+mount test in kinds of flashes" +for simulator in "mtdram" "nandsim"; do + for encryption in "encrypted" "noencrypted"; do + run_test "$simulator" "16" "16" "512" $encryption + run_test "$simulator" "64" "16" "512" $encryption + run_test "$simulator" "128" "64" "2048" $encryption + run_test "$simulator" "256" "128" "2048" $encryption + run_test "$simulator" "512" "128" "2048" $encryption + run_test "$simulator" "1024" "512" "2048" $encryption + done +done +end_t=$(date +%s) +time_cost=$(( end_t - start_t )) +echo "Success, cost $time_cost seconds" +exit 0 diff --git a/tests/ubifs_tools-tests/fsck_tests/random_corrupted_fsck.sh.in b/tests/ubifs_tools-tests/fsck_tests/random_corrupted_fsck.sh.in new file mode 100755 index 0000000..64760c9 --- /dev/null +++ b/tests/ubifs_tools-tests/fsck_tests/random_corrupted_fsck.sh.in @@ -0,0 +1,206 @@ +#!/bin/sh +# Copyright (c), 2024, Huawei Technologies Co, Ltd. +# Author: Zhihao Cheng <chengzhihao1@huawei.com> +# +# Test Description: +# For many kinds of flash, do following things +# 1. mount UBIFS +# 2. fsstress && unmount +# 3. inject corruption into UBIFS image randomly +# 3. fsck UBIFS +# 4. check UBIFS mounting result +# Running time: 1h + +TESTBINDIR=@TESTBINDIR@ +source $TESTBINDIR/common.sh + +function run_test() +{ + local simulator="$1"; + local size="$2"; + local peb_size="$3"; + local page_size="$4"; + local encryption=$5; + + echo "======================================================================" + printf "%s" "$simulator: ${size}MiB PEB size ${peb_size}KiB" + if [ "$simulator" = "nandsim" ]; then + printf " %s" "page size ${page_size}Bytes" + fi + printf " $encryption\n" + + if [ "$simulator" = "nandsim" ]; then + $TESTBINDIR/load_nandsim.sh "$size" "$peb_size" "$page_size" || echo "cannot load nandsim"; + mtdnum="$(find_mtd_device "$nandsim_patt")" + elif [ "$simulator" = "mtdram" ]; then + load_mtdram "$size" "$peb_size" || echo "cannot load mtdram" + mtdnum="$(find_mtd_device "$mtdram_patt")" + else + fatal "$simulator is not supported" + fi + + flash_eraseall /dev/mtd$mtdnum + modprobe ubi mtd="$mtdnum,$page_size" || fatal "modprobe ubi fail" + ubimkvol -N vol_test -m -n 0 /dev/ubi$UBI_NUM || fatal "mkvol fail" + modprobe ubifs || fatal "modprobe ubifs fail" + mount_ubifs $DEV $MNT || fatal "mount ubifs fail" + if [[ "$encryption" == "encrypted" ]]; then + encryption_gen_key + encryption_set_key $MNT + fi + + fsstress -d $MNT -l0 -p4 -n10000 & + + while true; + do + per=`df -Th | grep ubifs | awk '{print $6}'`; + if [[ ${per%?} -gt 95 ]]; then + # Used > 95% + break; + fi + done + + ps -e | grep -w fsstress > /dev/null 2>&1 + while [ $? -eq 0 ] + do + killall -9 fsstress > /dev/null 2>&1 + sleep 1 + ps -e | grep -w fsstress > /dev/null 2>&1 + done + + while true + do + res=`mount | grep "$MNT"` + if [[ "$res" == "" ]] + then + break; + fi + umount $MNT + sleep 0.1 + done + + # injection + times=$((RANDOM % 10)) + let times=$times+10 + i=0 + tot_peb=`cat /sys/class/ubi/ubi$UBI_NUM/total_eraseblocks`; + + modprobe -r ubifs + modprobe -r ubi # Stop wear-leveling & erasing worker + while [[ $i -lt $times ]] + do + let i=$i+1; + peb=$((RANDOM % $tot_peb)); + pg=`expr $peb_size \* 1024`; + peb_off=`expr $pg \* $peb` + pages=`expr $pg / $page_size`; + pg=`expr $pages - 2`; + pg=$((RANDOM % $pg)); + pg_off=`expr $pg + 2`; + pg_start=`expr $pages \* $peb`; + pg=`expr $pg_start + $pg_off`; + vid_pg=`expr $pg_start + 1`; + dd if=/dev/mtd$mtdnum of=$TMP_FILE bs=$page_size skip=$vid_pg count=1 2>/dev/null; + content=`cat $TMP_FILE | grep UBI!`; # vid header magic + if [[ "$content" == "" ]]; then + # Skip free PEB, otherwise data could be overwritten in UBIFS + continue; + fi + if [[ $((RANDOM % 2)) == 0 ]]; then + # Corrupts 1 page + dd if=/dev/urandom of=/dev/mtd$mtdnum bs=$page_size seek=$pg count=1; + else + # Erase 1 LEB, TNC points to an unmapped area + flash_erase /dev/mtd$mtdnum $peb_off 1 + fi + done + rm -f $TMP_FILE 2>/dev/null + sync + + skip=0 + modprobe ubi mtd="$mtdnum,$page_size" + ret=$? + if [[ $ret != 0 ]] + then + skip=1 + echo "UBI layout volume is corrupted, skip" + fi + + if [[ $skip == 0 ]]; then + modprobe ubifs || fatal "modprobe ubifs2 fail" + dmesg -c > /dev/null + fsck.ubifs -yb $DEV 2>&1 > $LOG_FILE + res=$? + cat $LOG_FILE + let "ret=$res&~$FSCK_NONDESTRUCT" + if [[ $ret != $FSCK_OK ]] + then + # Skip superblock error + log=`cat $LOG_FILE | grep "bad node at LEB 0:"` + if [[ "$log" != "" ]] + then + skip=1 + echo "SB is corrupted, skip fsck & mounting" + else + fatal "fsck fail $res" + fi + fi + + if [[ $skip == 0 ]]; then + enable_chkfs + + mount_ubifs $DEV $MNT "noauthentication" "noatime" + res=$? + if [[ $res != 0 ]] + then + fatal "mount fail $res" + fi + + if [[ "$encryption" == "encrypted" ]]; then + # Ignore the encrypting error, root dir could be + # corrupted, the new root dir cannot be + # encrypted because it is not empty. + encryption_set_key $MNT 1 + fi + + du -sh $MNT > /dev/null # Make sure all files are accessible + ret=$? + if [[ $ret != 0 ]]; then + fatal "Cannot access all files" + fi + # check_err_msg is not suggested in this testcase, because + # ubi_io_read(triggered by wear_leveling_worker -> ubi_eba_copy_leb) + # could print stack if ecc uncorrectable errors are detected. + + umount $MNT + res=$? + if [[ $res != 0 ]] + then + fatal "unmount fail $res" + fi + fi + modprobe -r ubifs + modprobe -r ubi + fi + modprobe -r $simulator + + echo "----------------------------------------------------------------------" +} + +check_fsstress +start_t=$(date +%s) +echo "Do random_corrruption+fsck+mount test in kinds of flashes" +for simulator in "mtdram" "nandsim"; do + for encryption in "encrypted" "noencrypted"; do + run_test "$simulator" "16" "16" "512" $encryption + run_test "$simulator" "64" "16" "512" $encryption + run_test "$simulator" "128" "64" "2048" $encryption + run_test "$simulator" "256" "128" "2048" $encryption + run_test "$simulator" "512" "128" "2048" $encryption + run_test "$simulator" "1024" "512" "2048" $encryption + done +done +end_t=$(date +%s) +time_cost=$(( end_t - start_t )) +echo "Success, cost $time_cost seconds" +exit 0 diff --git a/tests/ubifs_tools-tests/images/corrupted_data_leb.gz b/tests/ubifs_tools-tests/images/corrupted_data_leb.gz Binary files differnew file mode 100644 index 0000000..ebb98a4 --- /dev/null +++ b/tests/ubifs_tools-tests/images/corrupted_data_leb.gz diff --git a/tests/ubifs_tools-tests/images/corrupted_idx_leb.gz b/tests/ubifs_tools-tests/images/corrupted_idx_leb.gz Binary files differnew file mode 100644 index 0000000..446f811 --- /dev/null +++ b/tests/ubifs_tools-tests/images/corrupted_idx_leb.gz diff --git a/tests/ubifs_tools-tests/images/dentry_key.gz b/tests/ubifs_tools-tests/images/dentry_key.gz Binary files differnew file mode 100644 index 0000000..bc313b5 --- /dev/null +++ b/tests/ubifs_tools-tests/images/dentry_key.gz diff --git a/tests/ubifs_tools-tests/images/dentry_nlen.gz b/tests/ubifs_tools-tests/images/dentry_nlen.gz Binary files differnew file mode 100644 index 0000000..4e5c826 --- /dev/null +++ b/tests/ubifs_tools-tests/images/dentry_nlen.gz diff --git a/tests/ubifs_tools-tests/images/dentry_type.gz b/tests/ubifs_tools-tests/images/dentry_type.gz Binary files differnew file mode 100644 index 0000000..e67348d --- /dev/null +++ b/tests/ubifs_tools-tests/images/dentry_type.gz diff --git a/tests/ubifs_tools-tests/images/dir_lost.gz b/tests/ubifs_tools-tests/images/dir_lost.gz Binary files differnew file mode 100644 index 0000000..fd42040 --- /dev/null +++ b/tests/ubifs_tools-tests/images/dir_lost.gz diff --git a/tests/ubifs_tools-tests/images/dir_lost_duplicated.gz b/tests/ubifs_tools-tests/images/dir_lost_duplicated.gz Binary files differnew file mode 100644 index 0000000..09def92 --- /dev/null +++ b/tests/ubifs_tools-tests/images/dir_lost_duplicated.gz diff --git a/tests/ubifs_tools-tests/images/dir_lost_not_recover.gz b/tests/ubifs_tools-tests/images/dir_lost_not_recover.gz Binary files differnew file mode 100644 index 0000000..6fe82bb --- /dev/null +++ b/tests/ubifs_tools-tests/images/dir_lost_not_recover.gz diff --git a/tests/ubifs_tools-tests/images/dir_many_dentry.gz b/tests/ubifs_tools-tests/images/dir_many_dentry.gz Binary files differnew file mode 100644 index 0000000..f027bfc --- /dev/null +++ b/tests/ubifs_tools-tests/images/dir_many_dentry.gz diff --git a/tests/ubifs_tools-tests/images/empty_tnc.gz b/tests/ubifs_tools-tests/images/empty_tnc.gz Binary files differnew file mode 100644 index 0000000..7e85a8c --- /dev/null +++ b/tests/ubifs_tools-tests/images/empty_tnc.gz diff --git a/tests/ubifs_tools-tests/images/good.gz b/tests/ubifs_tools-tests/images/good.gz Binary files differnew file mode 100644 index 0000000..686e949 --- /dev/null +++ b/tests/ubifs_tools-tests/images/good.gz diff --git a/tests/ubifs_tools-tests/images/index_size.gz b/tests/ubifs_tools-tests/images/index_size.gz Binary files differnew file mode 100644 index 0000000..0ebcf59 --- /dev/null +++ b/tests/ubifs_tools-tests/images/index_size.gz diff --git a/tests/ubifs_tools-tests/images/inode_data.gz b/tests/ubifs_tools-tests/images/inode_data.gz Binary files differnew file mode 100644 index 0000000..f8135dc --- /dev/null +++ b/tests/ubifs_tools-tests/images/inode_data.gz diff --git a/tests/ubifs_tools-tests/images/inode_mode.gz b/tests/ubifs_tools-tests/images/inode_mode.gz Binary files differnew file mode 100644 index 0000000..1bdbb26 --- /dev/null +++ b/tests/ubifs_tools-tests/images/inode_mode.gz diff --git a/tests/ubifs_tools-tests/images/inode_nlink.gz b/tests/ubifs_tools-tests/images/inode_nlink.gz Binary files differnew file mode 100644 index 0000000..19461d9 --- /dev/null +++ b/tests/ubifs_tools-tests/images/inode_nlink.gz diff --git a/tests/ubifs_tools-tests/images/inode_size.gz b/tests/ubifs_tools-tests/images/inode_size.gz Binary files differnew file mode 100644 index 0000000..aa7574c --- /dev/null +++ b/tests/ubifs_tools-tests/images/inode_size.gz diff --git a/tests/ubifs_tools-tests/images/inode_xcnt.gz b/tests/ubifs_tools-tests/images/inode_xcnt.gz Binary files differnew file mode 100644 index 0000000..4bc5ac2 --- /dev/null +++ b/tests/ubifs_tools-tests/images/inode_xcnt.gz diff --git a/tests/ubifs_tools-tests/images/journal_bud.gz b/tests/ubifs_tools-tests/images/journal_bud.gz Binary files differnew file mode 100644 index 0000000..37cf453 --- /dev/null +++ b/tests/ubifs_tools-tests/images/journal_bud.gz diff --git a/tests/ubifs_tools-tests/images/journal_log.gz b/tests/ubifs_tools-tests/images/journal_log.gz Binary files differnew file mode 100644 index 0000000..e1ddc88 --- /dev/null +++ b/tests/ubifs_tools-tests/images/journal_log.gz diff --git a/tests/ubifs_tools-tests/images/lpt_dirty.gz b/tests/ubifs_tools-tests/images/lpt_dirty.gz Binary files differnew file mode 100644 index 0000000..b7381c3 --- /dev/null +++ b/tests/ubifs_tools-tests/images/lpt_dirty.gz diff --git a/tests/ubifs_tools-tests/images/lpt_flags.gz b/tests/ubifs_tools-tests/images/lpt_flags.gz Binary files differnew file mode 100644 index 0000000..baf6097 --- /dev/null +++ b/tests/ubifs_tools-tests/images/lpt_flags.gz diff --git a/tests/ubifs_tools-tests/images/lpt_free.gz b/tests/ubifs_tools-tests/images/lpt_free.gz Binary files differnew file mode 100644 index 0000000..abc46fd --- /dev/null +++ b/tests/ubifs_tools-tests/images/lpt_free.gz diff --git a/tests/ubifs_tools-tests/images/lpt_pos.gz b/tests/ubifs_tools-tests/images/lpt_pos.gz Binary files differnew file mode 100644 index 0000000..d050f18 --- /dev/null +++ b/tests/ubifs_tools-tests/images/lpt_pos.gz diff --git a/tests/ubifs_tools-tests/images/ltab_dirty.gz b/tests/ubifs_tools-tests/images/ltab_dirty.gz Binary files differnew file mode 100644 index 0000000..56dabee --- /dev/null +++ b/tests/ubifs_tools-tests/images/ltab_dirty.gz diff --git a/tests/ubifs_tools-tests/images/ltab_free.gz b/tests/ubifs_tools-tests/images/ltab_free.gz Binary files differnew file mode 100644 index 0000000..985e275 --- /dev/null +++ b/tests/ubifs_tools-tests/images/ltab_free.gz diff --git a/tests/ubifs_tools-tests/images/master_highest_inum.gz b/tests/ubifs_tools-tests/images/master_highest_inum.gz Binary files differnew file mode 100644 index 0000000..08ed044 --- /dev/null +++ b/tests/ubifs_tools-tests/images/master_highest_inum.gz diff --git a/tests/ubifs_tools-tests/images/master_lpt.gz b/tests/ubifs_tools-tests/images/master_lpt.gz Binary files differnew file mode 100644 index 0000000..4b205a2 --- /dev/null +++ b/tests/ubifs_tools-tests/images/master_lpt.gz diff --git a/tests/ubifs_tools-tests/images/master_tnc.gz b/tests/ubifs_tools-tests/images/master_tnc.gz Binary files differnew file mode 100644 index 0000000..4219a5e --- /dev/null +++ b/tests/ubifs_tools-tests/images/master_tnc.gz diff --git a/tests/ubifs_tools-tests/images/master_total_dead.gz b/tests/ubifs_tools-tests/images/master_total_dead.gz Binary files differnew file mode 100644 index 0000000..165d787 --- /dev/null +++ b/tests/ubifs_tools-tests/images/master_total_dead.gz diff --git a/tests/ubifs_tools-tests/images/master_total_dirty.gz b/tests/ubifs_tools-tests/images/master_total_dirty.gz Binary files differnew file mode 100644 index 0000000..9416bbd --- /dev/null +++ b/tests/ubifs_tools-tests/images/master_total_dirty.gz diff --git a/tests/ubifs_tools-tests/images/master_total_free.gz b/tests/ubifs_tools-tests/images/master_total_free.gz Binary files differnew file mode 100644 index 0000000..e6e942f --- /dev/null +++ b/tests/ubifs_tools-tests/images/master_total_free.gz diff --git a/tests/ubifs_tools-tests/images/orphan_node.gz b/tests/ubifs_tools-tests/images/orphan_node.gz Binary files differnew file mode 100644 index 0000000..901d75d --- /dev/null +++ b/tests/ubifs_tools-tests/images/orphan_node.gz diff --git a/tests/ubifs_tools-tests/images/root_dir.gz b/tests/ubifs_tools-tests/images/root_dir.gz Binary files differnew file mode 100644 index 0000000..d45e4dc --- /dev/null +++ b/tests/ubifs_tools-tests/images/root_dir.gz diff --git a/tests/ubifs_tools-tests/images/sb_fanout.gz b/tests/ubifs_tools-tests/images/sb_fanout.gz Binary files differnew file mode 100644 index 0000000..520fd09 --- /dev/null +++ b/tests/ubifs_tools-tests/images/sb_fanout.gz diff --git a/tests/ubifs_tools-tests/images/sb_fmt_version.gz b/tests/ubifs_tools-tests/images/sb_fmt_version.gz Binary files differnew file mode 100644 index 0000000..c6309e1 --- /dev/null +++ b/tests/ubifs_tools-tests/images/sb_fmt_version.gz diff --git a/tests/ubifs_tools-tests/images/sb_leb_size.gz b/tests/ubifs_tools-tests/images/sb_leb_size.gz Binary files differnew file mode 100644 index 0000000..a04ed7b --- /dev/null +++ b/tests/ubifs_tools-tests/images/sb_leb_size.gz diff --git a/tests/ubifs_tools-tests/images/sb_log_lebs.gz b/tests/ubifs_tools-tests/images/sb_log_lebs.gz Binary files differnew file mode 100644 index 0000000..9113fe4 --- /dev/null +++ b/tests/ubifs_tools-tests/images/sb_log_lebs.gz diff --git a/tests/ubifs_tools-tests/images/sb_min_io_size.gz b/tests/ubifs_tools-tests/images/sb_min_io_size.gz Binary files differnew file mode 100644 index 0000000..788096d --- /dev/null +++ b/tests/ubifs_tools-tests/images/sb_min_io_size.gz diff --git a/tests/ubifs_tools-tests/images/soft_link_data_len.gz b/tests/ubifs_tools-tests/images/soft_link_data_len.gz Binary files differnew file mode 100644 index 0000000..10414a7 --- /dev/null +++ b/tests/ubifs_tools-tests/images/soft_link_data_len.gz diff --git a/tests/ubifs_tools-tests/images/soft_link_inode_mode.gz b/tests/ubifs_tools-tests/images/soft_link_inode_mode.gz Binary files differnew file mode 100644 index 0000000..e333611 --- /dev/null +++ b/tests/ubifs_tools-tests/images/soft_link_inode_mode.gz diff --git a/tests/ubifs_tools-tests/images/tnc_lv0_key.gz b/tests/ubifs_tools-tests/images/tnc_lv0_key.gz Binary files differnew file mode 100644 index 0000000..bcc8ca2 --- /dev/null +++ b/tests/ubifs_tools-tests/images/tnc_lv0_key.gz diff --git a/tests/ubifs_tools-tests/images/tnc_lv0_len.gz b/tests/ubifs_tools-tests/images/tnc_lv0_len.gz Binary files differnew file mode 100644 index 0000000..1c64d79 --- /dev/null +++ b/tests/ubifs_tools-tests/images/tnc_lv0_len.gz diff --git a/tests/ubifs_tools-tests/images/tnc_lv0_pos.gz b/tests/ubifs_tools-tests/images/tnc_lv0_pos.gz Binary files differnew file mode 100644 index 0000000..5816123 --- /dev/null +++ b/tests/ubifs_tools-tests/images/tnc_lv0_pos.gz diff --git a/tests/ubifs_tools-tests/images/tnc_noleaf_key.gz b/tests/ubifs_tools-tests/images/tnc_noleaf_key.gz Binary files differnew file mode 100644 index 0000000..fc7c0ed --- /dev/null +++ b/tests/ubifs_tools-tests/images/tnc_noleaf_key.gz diff --git a/tests/ubifs_tools-tests/images/tnc_noleaf_len.gz b/tests/ubifs_tools-tests/images/tnc_noleaf_len.gz Binary files differnew file mode 100644 index 0000000..2d0f80d --- /dev/null +++ b/tests/ubifs_tools-tests/images/tnc_noleaf_len.gz diff --git a/tests/ubifs_tools-tests/images/tnc_noleaf_pos.gz b/tests/ubifs_tools-tests/images/tnc_noleaf_pos.gz Binary files differnew file mode 100644 index 0000000..1a05959 --- /dev/null +++ b/tests/ubifs_tools-tests/images/tnc_noleaf_pos.gz diff --git a/tests/ubifs_tools-tests/images/xent_host.gz b/tests/ubifs_tools-tests/images/xent_host.gz Binary files differnew file mode 100644 index 0000000..b2aef46 --- /dev/null +++ b/tests/ubifs_tools-tests/images/xent_host.gz diff --git a/tests/ubifs_tools-tests/images/xentry_key.gz b/tests/ubifs_tools-tests/images/xentry_key.gz Binary files differnew file mode 100644 index 0000000..b4fa6d5 --- /dev/null +++ b/tests/ubifs_tools-tests/images/xentry_key.gz diff --git a/tests/ubifs_tools-tests/images/xentry_nlen.gz b/tests/ubifs_tools-tests/images/xentry_nlen.gz Binary files differnew file mode 100644 index 0000000..e351280 --- /dev/null +++ b/tests/ubifs_tools-tests/images/xentry_nlen.gz diff --git a/tests/ubifs_tools-tests/images/xentry_type.gz b/tests/ubifs_tools-tests/images/xentry_type.gz Binary files differnew file mode 100644 index 0000000..e0c5763 --- /dev/null +++ b/tests/ubifs_tools-tests/images/xentry_type.gz diff --git a/tests/ubifs_tools-tests/images/xinode_flags.gz b/tests/ubifs_tools-tests/images/xinode_flags.gz Binary files differnew file mode 100644 index 0000000..34d3ff7 --- /dev/null +++ b/tests/ubifs_tools-tests/images/xinode_flags.gz diff --git a/tests/ubifs_tools-tests/images/xinode_key.gz b/tests/ubifs_tools-tests/images/xinode_key.gz Binary files differnew file mode 100644 index 0000000..c92956c --- /dev/null +++ b/tests/ubifs_tools-tests/images/xinode_key.gz diff --git a/tests/ubifs_tools-tests/images/xinode_mode.gz b/tests/ubifs_tools-tests/images/xinode_mode.gz Binary files differnew file mode 100644 index 0000000..0e52648 --- /dev/null +++ b/tests/ubifs_tools-tests/images/xinode_mode.gz diff --git a/tests/ubifs_tools-tests/lib/common.sh.in b/tests/ubifs_tools-tests/lib/common.sh.in new file mode 100755 index 0000000..8d1b3c0 --- /dev/null +++ b/tests/ubifs_tools-tests/lib/common.sh.in @@ -0,0 +1,359 @@ +#!/bin/sh +# Copyright (c), 2024, Huawei Technologies Co, Ltd. +# Author: Zhihao Cheng <chengzhihao1@huawei.com> +# +# Provide basic functions. + +UBI_NUM=0 +DEV=/dev/ubi0_0 +MNT=/mnt/test_file_system +TMP_FILE=/tmp/ubifs_test_file +TMP_MNT=/tmp/ubifs_tmp_mnt +LOG_FILE=/tmp/ubifs_log +KEY_FILE=/tmp/key +IMG_FILE=ubifs.img +nandsim_patt="NAND simulator" +mtdram_patt="mtdram test device" + +# fsck returning code +FSCK_OK=0 # No errors +FSCK_NONDESTRUCT=1 # File system errors corrected +FSCK_REBOOT=2 # System should be rebooted +FSCK_UNCORRECTED=4 # File system errors left uncorrected +FSCK_ERROR=8 # Operational error +FSCK_USAGE=16 # Usage or syntax error +FSCK_CANCELED=32 # Aborted with a signal or ^ +FSCK_LIBRARY=128 # Shared library error + +function fatal() +{ + echo "Error: $1" 1>&2 + exit 1 +} + +# All loaded modules(mtdram/nandsim/ubi/ubifs) won't be removed if errors +# happen, it is useful to debug based on the UBIFS image. +function cleanup_handler() +{ + local ret="$1" + + umount $TMP_MNT >/dev/null 2>&1 ||: + rm -rf $TMP_MNT >/dev/null 2>&1 ||: + if [ "$ret" == "0" ]; then + umount $MNT >/dev/null 2>&1 ||: + modprobe -r ubifs >/dev/null 2>&1 ||: + modprobe -r ubi >/dev/null 2>&1 ||: + modprobe -r nandsim >/dev/null 2>&1 ||: + modprobe -r mtdram >/dev/null 2>&1 ||: + rm -rf $MNT >/dev/null 2>&1 ||: + rm -f $TMP_FILE >/dev/null 2>&1 ||: + rm -f $KEY_FILE >/dev/null 2>&1 ||: + rm -f $LOG_FILE >/dev/null 2>&1 ||: + rm -f $IMG_FILE >/dev/null 2>&1 ||: + exit 0 + else + exit 1 + fi +} +trap 'cleanup_handler $?' EXIT +trap 'cleanup_handler 1' HUP PIPE INT QUIT TERM + +function find_mtd_device() +{ + printf "%s" "$(grep "$1" /proc/mtd | sed -e "s/^mtd\([0-9]\+\):.*$/\1/")" +} + +function powercut() +{ + dmesg -c > /dev/null + echo 1 > /sys/kernel/debug/ubifs/tst_recovery; + while true; + do + msg=`dmesg -c | grep "Power cut emulated"`; + if [[ "$msg" != "" ]]; + then + break; + fi + ro_error=`cat /sys/kernel/debug/ubifs/ubi${UBI_NUM}_0/ro_error` + if [[ $ro_error != 0 ]]; then + break; + fi + done + echo 0 > /sys/kernel/debug/ubifs/tst_recovery +} + +# Load mtdram with specified size and PEB size +# Usage: load_mtdram <flash size> <PEB size> +# 1. Flash size is specified in MiB +# 2. PEB size is specified in KiB +function load_mtdram() +{ + local size="$1"; shift + local peb_size="$1"; shift + + size="$(($size * 1024))" + modprobe mtdram total_size="$size" erase_size="$peb_size" +} + +function check_fsstress() +{ + cmd=`fsstress | grep "op_name"` + if ! [[ "$cmd" =~ "op_name" ]]; then + fatal "fsstress is not found" + fi +} + +# Check error messages +function check_err_msg() +{ + msg=`dmesg | grep -E "dump_stack|UBIFS error|switched to read-only mode"`; + if [[ "$msg" != "" ]] + then + dmesg + fatal "error message detected!" + fi + dmesg -c > /dev/null +} + +# Iterate all files under certain dir +# $1: dir +# $2: "md5sum" means that need record md5 for regular file, otherwise don't record md5 for regular file +function read_dir() { + for file in `ls -a $1` + do + cur_f=$1"/"$file + if [ -b $cur_f ] + then + major=`stat -c %t $cur_f` + minor=`stat -c %T $cur_f` + echo "block $cur_f $major $minor" >> $TMP_FILE + elif [ -c $cur_f ] + then + major=`stat -c %t $cur_f` + minor=`stat -c %T $cur_f` + echo "char $cur_f $major $minor" >> $TMP_FILE + elif [ -L $cur_f ] + then + link=`stat -c %N $cur_f` + echo "symlink $cur_f $link" >> $TMP_FILE + elif [ -S $cur_f ] + then + echo "sock $cur_f" >> $TMP_FILE + elif [ -p $cur_f ] + then + echo "fifo $cur_f" >> $TMP_FILE + elif [ -f $cur_f ] + then + sz=`stat -c %s $cur_f` + if [[ "$2" != "md5sum" ]]; then + echo "reg $cur_f $sz" >> $TMP_FILE + else + md5=`md5sum $cur_f | awk '{print $1}'` + echo "reg $cur_f $md5 $sz" >> $TMP_FILE + fi + elif [ -d $cur_f ] + then + if [[ $file != '.' && $file != '..' ]] + then + echo "dir $cur_f" >> $TMP_FILE + read_dir $1"/"$file $2 + fi + else + fatal "record unknown file type $cur_f" + fi + done +} + +# Check whether there are files lost after fsck/mkfs +# $1: "md5sum" means need record md5 for regular file, otherwise don't check md5 for regular file +function parse_dir() +{ + while read line + do + array=(${line//\ / }); + f_type=${array[0]}; + cur_f=${array[1]}; + cur_info="" + if [[ "$f_type" =~ "block" ]] + then + major=`stat -c %t $cur_f` + minor=`stat -c %T $cur_f` + cur_info="block $cur_f $major $minor" + elif [[ "$f_type" =~ "char" ]] + then + major=`stat -c %t $cur_f` + minor=`stat -c %T $cur_f` + cur_info="char $cur_f $major $minor" + elif [[ "$f_type" =~ "symlink" ]] + then + link=`stat -c %N $cur_f` + cur_info="symlink $cur_f $link" + elif [[ "$f_type" =~ "sock" ]] + then + cur_info="sock $cur_f" + elif [[ "$f_type" =~ "fifo" ]] + then + cur_info="fifo $cur_f" + elif [[ "$f_type" =~ "reg" ]] + then + sz=`stat -c %s $cur_f` + if [[ "$1" != "md5sum" ]]; then + cur_info="reg $cur_f $sz" + else + md5=`md5sum $cur_f | awk '{print $1}'` + cur_info="reg $cur_f $md5 $sz" + fi + elif [[ "$f_type" =~ "dir" ]] + then + cur_info="dir $cur_f" + else + fatal "parse unknown file type $cur_f" + fi + if [[ "$cur_info" != "$line" ]] + then + fatal "current info $cur_info, but expect $line" + fi + done < $TMP_FILE +} + +function authentication() +{ + keyctl clear @s + res=$? + if [[ $res != 0 ]]; then + fatal "keyctl is not found" + fi + keyctl add logon ubifs:foo 12345678901234567890123456789012 @s +} + +function encryption_gen_key() +{ + # CONFIG_FS_ENCRYPTION=y + head -c 64 /dev/urandom > $KEY_FILE + cmd=`fscryptctl -h | grep "set_policy"` + if ! [[ "$cmd" =~ "set_policy" ]]; then + fatal "fscryptctl is not found" + fi +} + +function encryption_set_key() +{ + mnt=$1 + ignore_err=$2 + # https://github.com/google/fscryptctl + key=$(fscryptctl add_key $mnt < $KEY_FILE) + fscryptctl set_policy $key $mnt + #fscryptctl get_policy $mnt + ret=$? + if [[ $ret != 0 && $ignore_err != 1 ]]; then + fatal "set encryption policy failed" + fi +} + +function mount_ubifs() +{ + local dev=$1; + local mnt=$2; + local auth=$3; + local noatime=$4; + local option=""; + if [[ "$noatime" == "noatime" ]]; then + option="-o noatime" + fi + if [[ "$auth" == "authentication" ]]; then + authentication + if [[ "$option" == "" ]]; then + option="-o auth_key=ubifs:foo,auth_hash_name=sha256" + else + option="$option,auth_key=ubifs:foo,auth_hash_name=sha256" + fi + fi + mount -t ubifs $option $dev $mnt +} + +function enable_chkfs() +{ + echo 1 > /sys/kernel/debug/ubifs/chk_fs + echo 1 > /sys/kernel/debug/ubifs/chk_general + echo 1 > /sys/kernel/debug/ubifs/chk_index + echo 1 > /sys/kernel/debug/ubifs/chk_lprops + echo 1 > /sys/kernel/debug/ubifs/chk_orphans +} + +function disable_chkfs() +{ + echo 0 > /sys/kernel/debug/ubifs/chk_fs + echo 0 > /sys/kernel/debug/ubifs/chk_general + echo 0 > /sys/kernel/debug/ubifs/chk_index + echo 0 > /sys/kernel/debug/ubifs/chk_lprops + echo 0 > /sys/kernel/debug/ubifs/chk_orphans +} + +function inject_mem_err() +{ + # CONFIG_FAILSLAB=y + # CONFIG_FAIL_PAGE_ALLOC=y + local pid=$1; + + if ! [ -f /sys/kernel/debug/failslab/probability ]; then + fatal "failslab is not enabled, injection failed" + fi + if ! [ -f /sys/kernel/debug/fail_page_alloc/probability ]; then + fatal "fail_page_alloc is not enabled, injection failed" + fi + + echo 1 > /proc/$pid/make-it-fail + + echo Y > /sys/kernel/debug/failslab/task-filter + echo 1 > /sys/kernel/debug/failslab/probability # 1% failure + echo 10000 > /sys/kernel/debug/failslab/times + echo 1 > /sys/kernel/debug/failslab/verbose + echo N > /sys/kernel/debug/failslab/ignore-gfp-wait + + echo Y > /sys/kernel/debug/fail_page_alloc/task-filter + echo 1 > /sys/kernel/debug/fail_page_alloc/probability + echo 10000 > /sys/kernel/debug/fail_page_alloc/times + echo 0 > /sys/kernel/debug/fail_page_alloc/verbose + echo N > /sys/kernel/debug/fail_page_alloc/ignore-gfp-wait +} + +function cancel_mem_err() +{ + echo 0 > /sys/kernel/debug/failslab/probability + echo 0 > /sys/kernel/debug/failslab/times + echo 0 > /sys/kernel/debug/failslab/verbose + echo N > /sys/kernel/debug/failslab/task-filter + echo Y > /sys/kernel/debug/failslab/ignore-gfp-wait + + echo 0 > /sys/kernel/debug/fail_page_alloc/probability + echo 0 > /sys/kernel/debug/fail_page_alloc/times + echo 1 > /sys/kernel/debug/fail_page_alloc/verbose + echo N > /sys/kernel/debug/fail_page_alloc/task-filter + echo Y > /sys/kernel/debug/fail_page_alloc/ignore-gfp-wait +} + +function inject_io_err() +{ + if ! [ -f /sys/kernel/debug/ubi/ubi$UBI_NUM/tst_emulate_io_failures ]; then + fatal "tst_emulate_io_failures is not enabled, skip injection" + fi + + echo 1 > /sys/kernel/debug/ubi/ubi$UBI_NUM/tst_emulate_io_failures +} + +function cancel_io_err() +{ + echo 0 > /sys/kernel/debug/ubi/ubi$UBI_NUM/tst_emulate_io_failures +} + +if ! [ -d $MNT ]; then + mkdir -p $MNT +fi +if ! [ -d $TMP_MNT ]; then + mkdir -p $TMP_MNT +fi + +modprobe ubi || fatal "common.sh: cannot load ubi" +modprobe ubifs || fatal "common.sh: cannot load ubifs" +modprobe -r ubifs +modprobe -r ubi diff --git a/tests/ubifs_tools-tests/mkfs_tests/build_fs_from_dir.sh.in b/tests/ubifs_tools-tests/mkfs_tests/build_fs_from_dir.sh.in new file mode 100755 index 0000000..0b22856 --- /dev/null +++ b/tests/ubifs_tools-tests/mkfs_tests/build_fs_from_dir.sh.in @@ -0,0 +1,174 @@ +#!/bin/sh +# Copyright (c), 2024, Huawei Technologies Co, Ltd. +# Author: Zhihao Cheng <chengzhihao1@huawei.com> +# +# Test Description: +# Initialize UBIFS image from a given directory, then check whether the +# fs content in mounted UBIFS is consistent with the original directory. +# Both UBI volume and file are chosen as storage mediums to test. +# Running time: 10min + +TESTBINDIR=@TESTBINDIR@ +source $TESTBINDIR/common.sh + +function run_test() +{ + local simulator="$1"; + local size="$2"; + local peb_size="$3"; + local page_size="$4"; + local vid_offs=$page_size; + local encryption=$5; + local test_medium=$6; + local space_fix=$7; + local need_fsck=$8; + local double_mkfs=$9; + local leb_size=$(($(($peb_size*1024))-$page_size)); + VIDHDR_SZ=64; + + option="" + if [[ "$space_fix" == "fix_space" ]]; then + option="-F" + fi + echo "======================================================================" + printf "%s" "$simulator: ${size}MiB PEB size ${peb_size}KiB" + if [ "$simulator" = "mtdram" ]; then + page_size=8 + leb_size=$(($leb_size-$VIDHDR_SZ)) + else + leb_size=$(($leb_size-$page_size)) + fi + printf " %s" "page size ${page_size}Bytes" + printf " $encryption $test_medium $space_fix $need_fsck $double_mkfs\n" + + if [ "$simulator" = "nandsim" ]; then + $TESTBINDIR/load_nandsim.sh "$size" "$peb_size" "$page_size" || echo "cannot load nandsim"; + mtdnum="$(find_mtd_device "$nandsim_patt")" + elif [ "$simulator" = "mtdram" ]; then + load_mtdram "$size" "$peb_size" || echo "cannot load mtdram" + mtdnum="$(find_mtd_device "$mtdram_patt")" + else + fatal "$simulator is not supported" + fi + + dmesg -c > /dev/null + flash_eraseall /dev/mtd$mtdnum + modprobe ubi mtd="$mtdnum,$vid_offs" || fatal "modprobe ubi fail" + ubimkvol -N vol_test -m -n 0 /dev/ubi$UBI_NUM || fatal "mkvol fail" + modprobe ubifs || fatal "modprobe ubifs fail" + if [[ "$encryption" == "encrypted" ]]; then + encryption_gen_key + option="$option --cipher AES-256-XTS --key $KEY_FILE" + fi + + if [[ "$test_medium" == "volume" ]]; then + mkfs.ubifs $option -m${page_size} -c 1024 -e $leb_size -f 4 -r $TMP_MNT $DEV + if [[ $? != 0 ]]; then + fatal "mkfs failed" + fi + if [[ "$double_mkfs" == "double_format" ]]; then + mkfs.ubifs -y $option -m${page_size} -c 1024 -e $leb_size -f 4 -r $TMP_MNT $DEV + if [[ $? != 0 ]]; then + fatal "mkfs failed" + fi + fi + else + mkfs.ubifs $option -m${page_size} -c 1024 -e $leb_size -f 4 -r $TMP_MNT -o $IMG_FILE + if [[ $? != 0 ]]; then + fatal "mkfs failed" + fi + ubiupdatevol $DEV $IMG_FILE + if [[ $? != 0 ]]; then + fatal "ubiupdatevol failed" + fi + fi + + if [[ "$need_fsck" == "do_fsck" ]]; then + fsck.ubifs -a $DEV # 'fsck.ubifs $DEV' is fine too. + res=$? + if [[ $res != $FSCK_OK ]] + then + fatal "fsck expects result $FSCK_OK, but $res is returned" + fi + fi + + enable_chkfs + + mount_ubifs $DEV $MNT "noauthentication" "noatime" + res=$? + if [[ $res != 0 ]] + then + fatal "mount fail $res" + fi + + if [[ "$encryption" != "encrypted" ]]; then + # Check filesystem information, skip encrypted image. + # fscryptctl is not compatible with fscryptctl in mtd-utils. + # See https://github.com/google/fscryptctl/issues/33 + du -sh $MNT > /dev/null # Make sure all files are accessible + ret=$? + if [[ $ret != 0 ]]; then + fatal "Cannot access all files" + fi + + parse_dir "md5sum" + fi + + check_err_msg + + umount $MNT + res=$? + if [[ $res != 0 ]] + then + fatal "unmount fail $res" + fi + + check_err_msg + disable_chkfs + + if [[ "$test_medium" != "volume" ]]; then + rm -f $IMG_FILE + fi + modprobe -r ubifs + modprobe -r ubi + modprobe -r $simulator + + echo "----------------------------------------------------------------------" +} + +check_fsstress +start_t=$(date +%s) +echo "Do mkfs+fsck+mount test in kinds of flashes" +mount -t tmpfs -osize=50m none $TMP_MNT || fatal "cannot mount tmpfs" +echo 123 > $TMP_MNT/file +setfattr -n user.xyz -v 123abc $TMP_MNT/file +fsstress -d $TMP_MNT -l30 -n10 -p4 +# Record filesystem information +rm -f $TMP_FILE 2>/dev/null +read_dir $TMP_MNT "md5sum" + +# No authentication tests, which needs a specific key from certs directory corresponding to linux source code.. +# See https://patchwork.ozlabs.org/project/linux-mtd/cover/20190806104928.1224-1-s.hauer@pengutronix.de/ +for simulator in "mtdram" "nandsim"; do + for encryption in "encrypted" "noencrypted"; do + for test_medium in "volume" "file"; do + for space_fix in "fix_space" "nofix_space"; do + for need_fsck in "do_fsck" "no_fsck"; do + for double_mkfs in "double_format" "format_once"; do + run_test "$simulator" "64" "64" "2048" $encryption $test_medium $space_fix $need_fsck $double_mkfs + run_test "$simulator" "128" "128" "2048" $encryption $test_medium $space_fix $need_fsck $double_mkfs + run_test "$simulator" "512" "512" "2048" $encryption $test_medium $space_fix $need_fsck $double_mkfs + run_test "$simulator" "1024" "512" "2048" $encryption $test_medium $space_fix $need_fsck $double_mkfs + done + done + done + done + done +done + +umount $TMP_MNT +rm -f $TMP_FILE 2>/dev/null +end_t=$(date +%s) +time_cost=$(( end_t - start_t )) +echo "Success, cost $time_cost seconds" +exit 0 diff --git a/tests/ubifs_tools-tests/ubifs_tools_run_all.sh.in b/tests/ubifs_tools-tests/ubifs_tools_run_all.sh.in new file mode 100755 index 0000000..a7caad0 --- /dev/null +++ b/tests/ubifs_tools-tests/ubifs_tools_run_all.sh.in @@ -0,0 +1,65 @@ +#!/bin/sh +# Copyright (c), 2024, Huawei Technologies Co, Ltd. +# Author: Zhihao Cheng <chengzhihao1@huawei.com> +# +# Test Description: +# Run all testcases under 'tests' directory + +function print_line() +{ + echo ">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>" + echo ">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>" +} + +TESTBINDIR=@TESTBINDIR@ + +print_line +$TESTBINDIR/authentication_refuse.sh +if [[ $? != 0 ]]; then + echo "authentication_refuse failed" + exit 1 +fi +print_line +$TESTBINDIR/powercut_fsck_mount.sh +if [[ $? != 0 ]]; then + echo "powercut_fsck_mount failed" + exit 1 +fi +print_line +$TESTBINDIR/cycle_corrupted_fsck_fault_inject.sh +if [[ $? != 0 ]]; then + echo "cycle_corrupted_fsck_fault_inject failed" + exit 1 +fi +print_line +$TESTBINDIR/fsck_bad_image.sh +if [[ $? != 0 ]]; then + echo "fsck_bad_image failed" + exit 1 +fi +print_line +$TESTBINDIR/random_corrupted_fsck.sh +if [[ $? != 0 ]]; then + echo "random_corrupted_fsck failed" + exit 1 +fi +print_line +$TESTBINDIR/cycle_powercut_mount_fsck.sh +if [[ $? != 0 ]]; then + echo "cycle_powercut_mount_fsck failed" + exit 1 +fi +print_line +$TESTBINDIR/cycle_mount_fsck_check.sh +if [[ $? != 0 ]]; then + echo "cycle_mount_fsck_check failed" + exit 1 +fi +print_line +$TESTBINDIR/build_fs_from_dir.sh +if [[ $? != 0 ]]; then + echo "build_fs_from_dir failed" + exit 1 +fi + +exit 0 diff --git a/tests/unittests/Makemodule.am b/tests/unittests/Makemodule.am index df1041d..277cac2 100644 --- a/tests/unittests/Makemodule.am +++ b/tests/unittests/Makemodule.am @@ -1,9 +1,11 @@ ubilib_test_SOURCES = tests/unittests/libubi_test.c lib/libubi.c +ubilib_test_SOURCES += tests/unittests/test_lib.h ubilib_test_LDADD = $(CMOCKA_LIBS) ubilib_test_LDFLAGS = -Wl,--wrap=open -Wl,--wrap=close -Wl,--wrap=ioctl -Wl,--wrap=read -Wl,--wrap=lseek ubilib_test_CPPFLAGS = -O0 --std=gnu99 $(CMOCKA_CFLAGS) -I$(top_srcdir)/include -DSYSFS_ROOT='"$(top_srcdir)/tests/unittests/sysfs_mock"' mtdlib_test_SOURCES = tests/unittests/libmtd_test.c lib/libmtd.c lib/libmtd_legacy.c +mtdlib_test_SOURCES += tests/unittests/test_lib.h mtdlib_test_LDADD = $(CMOCKA_LIBS) mtdlib_test_LDFLAGS = -Wl,--wrap=open -Wl,--wrap=close -Wl,--wrap=ioctl -Wl,--wrap=read -Wl,--wrap=lseek -Wl,--wrap=write mtdlib_test_CPPFLAGS = -O0 -D_GNU_SOURCE --std=gnu99 $(CMOCKA_CFLAGS) -I$(top_srcdir)/lib/ -I$(top_srcdir)/include -DSYSFS_ROOT='"$(top_srcdir)/tests/unittests/sysfs_mock"' @@ -12,13 +14,7 @@ TEST_BINS = \ ubilib_test \ mtdlib_test -UNITTEST_HEADER = \ - tests/unittests/test_lib.h - -UNITTEST_EXTRA = \ - tests/unittests/sysfs_mock - -EXTRA_DIST += $(UNITTEST_HEADER) $(UNITTEST_EXTRA) +EXTRA_DIST += tests/unittests/sysfs_mock TESTS += $(TEST_BINS) check_PROGRAMS += $(TEST_BINS) diff --git a/ubi-utils/Makemodule.am b/ubi-utils/Makemodule.am index 2835702..66c0238 100644 --- a/ubi-utils/Makemodule.am +++ b/ubi-utils/Makemodule.am @@ -22,9 +22,12 @@ ubidetach_LDADD = libmtd.a libubi.a ubinize_SOURCES = ubi-utils/ubinize.c ubinize_LDADD = libubi.a libubigen.a libmtd.a libiniparser.a -ubiformat_SOURCES = ubi-utils/ubiformat.c +ubiformat_SOURCES = ubi-utils/ubiformat.c include/mtd_swab.h ubiformat_LDADD = libubi.a libubigen.a libmtd.a libscan.a +ubiscan_SOURCES = ubi-utils/ubiscan.c include/mtd_swab.h +ubiscan_LDADD = libubi.a libubigen.a libscan.a libmtd.a + ubirename_SOURCES = ubi-utils/ubirename.c ubirename_LDADD = libmtd.a libubi.a @@ -37,22 +40,16 @@ ubirsvol_LDADD = libmtd.a libubi.a ubiblock_SOURCES = ubi-utils/ubiblock.c ubiblock_LDADD = libmtd.a libubi.a -if WITH_GETRANDOM ubihealthd_SOURCES = ubi-utils/ubihealthd.c ubihealthd_LDADD = libmtd.a libubi.a -endif -UBI_BINS = \ +sbin_PROGRAMS += \ ubiupdatevol ubimkvol ubirmvol ubicrc32 ubinfo ubiattach \ - ubidetach ubinize ubiformat ubirename mtdinfo ubirsvol ubiblock + ubidetach ubinize ubiformat ubirename mtdinfo ubirsvol ubiblock ubiscan -if WITH_GETRANDOM -UBI_BINS += ubihealthd +if WITH_UBIHEALTHD +sbin_PROGRAMS += ubihealthd endif -UBI_MAN = \ - ubi-utils/ubinize.8 - -dist_man8_MANS += $(UBI_MAN) +dist_man8_MANS += ubi-utils/ubinize.8 -sbin_PROGRAMS += $(UBI_BINS) diff --git a/ubi-utils/mtdinfo.c b/ubi-utils/mtdinfo.c index 0606ab0..61ce7bc 100644 --- a/ubi-utils/mtdinfo.c +++ b/ubi-utils/mtdinfo.c @@ -30,6 +30,7 @@ #include <string.h> #include <unistd.h> #include <mtd/mtd-user.h> +#include <mtd/ubi-media.h> #include <libubigen.h> #include <libmtd.h> @@ -54,7 +55,7 @@ static void display_help(void) printf( "%1$s version %2$s - a tool to print MTD information.\n" "\n" - "Usage: %1$s <MTD node file path> [--map | -M] [--ubi-info | -u]\n" + "Usage: %1$s <mtd device> [--map | -M] [--ubi-info | -u]\n" " %1$s --all [--ubi-info | -u]\n" " %1$s [--help | --version]\n" "\n" @@ -68,6 +69,8 @@ static void display_help(void) "-h, --help print help message\n" "-V, --version print program version\n" "\n" + "<mtd device> MTD device node or 'mtd:<name>'\n" + "\n" "Examples:\n" " %1$s /dev/mtd0 print information MTD device /dev/mtd0\n" " %1$s /dev/mtd0 -u print information MTD device /dev/mtd0\n" @@ -124,10 +127,13 @@ static int parse_opt(int argc, char * const argv[]) } } - if (optind == argc - 1) - args.node = argv[optind]; - else if (optind < argc) + if (optind == argc - 1) { + args.node = mtd_find_dev_node(argv[optind]); + if (!args.node) + return errmsg("Failed to find MTD device %s", argv[optind]); + } else if (optind < argc) { return errmsg("more then one MTD device specified (use -h for help)"); + } if (args.all && args.node) args.node = NULL; @@ -161,7 +167,7 @@ static void print_ubi_info(const struct mtd_info *mtd_info, struct ubigen_info ui; if (!mtd_info->sysfs_supported) { - errmsg("cannot provide UBI info, becasue sub-page size is " + errmsg("cannot provide UBI info, because sub-page size is " "not known"); return; } @@ -179,7 +185,7 @@ static void print_ubi_info(const struct mtd_info *mtd_info, static void print_region_map(const struct mtd_dev_info *mtd, int fd, const region_info_t *reginfo) { - unsigned long start; + unsigned long long start; int i, width; int ret_locked, errno_locked, ret_bad, errno_bad; @@ -197,8 +203,8 @@ static void print_region_map(const struct mtd_dev_info *mtd, int fd, ret_locked = ret_bad = errno_locked = errno_bad = 0; for (i = 0; i < reginfo->numblocks; ++i) { - start = reginfo->offset + i * reginfo->erasesize; - printf(" %*i: %08lx ", width, i, start); + start = reginfo->offset + (unsigned long long)i * reginfo->erasesize; + printf(" %*i: %08llx ", width, i, start); if (ret_locked != -1) { ret_locked = mtd_is_locked(mtd, fd, i); diff --git a/ubi-utils/ubiattach.c b/ubi-utils/ubiattach.c index 09f85af..e758dab 100644 --- a/ubi-utils/ubiattach.c +++ b/ubi-utils/ubiattach.c @@ -42,6 +42,8 @@ struct args { const char *node; const char *dev; int max_beb_per1024; + bool disable_fm; + bool need_resv_pool; }; static struct args args = { @@ -51,6 +53,8 @@ static struct args args = { .node = NULL, .dev = NULL, .max_beb_per1024 = 0, + .disable_fm = false, + .need_resv_pool = false, }; static const char doc[] = PROGRAM_NAME " version " VERSION @@ -67,6 +71,11 @@ static const char optionsstr[] = "-b, --max-beb-per1024 maximum expected bad block number per 1024 eraseblock.\n" " The default value is correct for most NAND devices.\n" " Allowed range is 0-768, 0 means the default kernel value.\n" +"-f, --disable-fastmap don't create new fastmap and do full scanning (existed\n" +" fastmap will be destroyed) for the given ubi device.\n" +"-r, --reserve-pool Slow down the frequency of updating fastmap by reserving\n" +" pebs for filling pool/wl_pool, which can prolong flash\n" +" service life.\n" "-h, --help print help message\n" "-V, --version print program version"; @@ -74,7 +83,7 @@ static const char usage[] = "Usage: " PROGRAM_NAME " [<UBI control device node file name>]\n" "\t[-m <MTD device number>] [-d <UBI device number>] [-p <path to device>]\n" "\t[--mtdn=<MTD device number>] [--devn=<UBI device number>]\n" -"\t[--dev-path=<path to device>]\n" +"\t[--dev-path=<path to device>] [-f] [--disable-fastmap] [-r] [--reserve-pool]\n" "\t[--max-beb-per1024=<maximum bad block number per 1024 blocks>]\n" "UBI control device defaults to " DEFAULT_CTRL_DEV " if not supplied.\n" "Example 1: " PROGRAM_NAME " -p /dev/mtd0 - attach /dev/mtd0 to UBI\n" @@ -93,6 +102,8 @@ static const struct option long_options[] = { { .name = "mtdn", .has_arg = 1, .flag = NULL, .val = 'm' }, { .name = "vid-hdr-offset", .has_arg = 1, .flag = NULL, .val = 'O' }, { .name = "max-beb-per1024", .has_arg = 1, .flag = NULL, .val = 'b' }, + { .name = "disable-fastmap", .has_arg = 0, .flag = NULL, .val = 'f' }, + { .name = "reserve-pool", .has_arg = 0, .flag = NULL, .val = 'r' }, { .name = "help", .has_arg = 0, .flag = NULL, .val = 'h' }, { .name = "version", .has_arg = 0, .flag = NULL, .val = 'V' }, { NULL, 0, NULL, 0}, @@ -103,7 +114,7 @@ static int parse_opt(int argc, char * const argv[]) while (1) { int key, error = 0; - key = getopt_long(argc, argv, "p:m:d:O:b:hV", long_options, NULL); + key = getopt_long(argc, argv, "p:m:d:O:b:frhV", long_options, NULL); if (key == -1) break; @@ -143,6 +154,14 @@ static int parse_opt(int argc, char * const argv[]) break; + case 'f': + args.disable_fm = true; + break; + + case 'r': + args.need_resv_pool = true; + break; + case 'h': printf("%s\n\n", doc); printf("%s\n\n", usage); @@ -213,6 +232,8 @@ int main(int argc, char * const argv[]) req.vid_hdr_offset = args.vidoffs; req.mtd_dev_node = args.dev; req.max_beb_per1024 = args.max_beb_per1024; + req.disable_fm = args.disable_fm; + req.need_resv_pool = args.need_resv_pool; err = ubi_attach(libubi, args.node, &req); if (err < 0) { diff --git a/ubi-utils/ubinfo.c b/ubi-utils/ubinfo.c index 29530be..6c5b19e 100644 --- a/ubi-utils/ubinfo.c +++ b/ubi-utils/ubinfo.c @@ -417,12 +417,9 @@ int main(int argc, char * const argv[]) goto out_libubi; } - if (args.devn != -1 && args.vol_id != -1) { - print_vol_info(libubi, args.devn, args.vol_id); - goto out; - } - - if (args.devn == -1 && args.vol_id == -1) + if (args.devn != -1 && args.vol_id != -1) + err = print_vol_info(libubi, args.devn, args.vol_id); + else if (args.devn == -1 && args.vol_id == -1) err = print_general_info(libubi, args.all); else if (args.devn != -1 && args.vol_id == -1) err = print_dev_info(libubi, args.devn, args.all); @@ -430,7 +427,6 @@ int main(int argc, char * const argv[]) if (err) goto out_libubi; -out: libubi_close(libubi); return 0; diff --git a/ubi-utils/ubinize.c b/ubi-utils/ubinize.c index 967b65c..9c950b1 100644 --- a/ubi-utils/ubinize.c +++ b/ubi-utils/ubinize.c @@ -375,8 +375,8 @@ static int read_section(const struct ubigen_info *ui, const char *sname, vi->alignment = iniparser_getint(args.dict, buf, -1); if (vi->alignment == -1) vi->alignment = 1; - else if (vi->id < 0) - return errmsg("negative volume alignement %d in section \"%s\"", + else if (vi->alignment <= 0) + return errmsg("not positive volume alignment %d in section \"%s\"", vi->alignment, sname); verbose(args.verbose, "volume alignment: %d", vi->alignment); diff --git a/ubi-utils/ubirsvol.c b/ubi-utils/ubirsvol.c index 0854abc..55f6794 100644 --- a/ubi-utils/ubirsvol.c +++ b/ubi-utils/ubirsvol.c @@ -57,8 +57,10 @@ static const char optionsstr[] = "-N, --name=<volume name> volume name to resize\n" "-s, --size=<bytes> volume size volume size in bytes, kilobytes (KiB)\n" " or megabytes (MiB)\n" +" zero size means use all available free bytes\n" "-S, --lebs=<LEBs count> alternative way to give volume size in logical\n" " eraseblocks\n" +" zero size means use all available free LEBs\n" "-h, -?, --help print help message\n" "-V, --version print program version"; @@ -114,13 +116,13 @@ static int parse_opt(int argc, char * const argv[]) switch (key) { case 's': args.bytes = util_get_bytes(optarg); - if (args.bytes <= 0) + if (args.bytes < 0) return errmsg("bad volume size: \"%s\"", optarg); break; case 'S': args.lebs = simple_strtoull(optarg, &error); - if (error || args.lebs <= 0) + if (error || args.lebs < 0) return errmsg("bad LEB count: \"%s\"", optarg); break; @@ -231,7 +233,10 @@ int main(int argc, char * const argv[]) } if (args.lebs != -1) - args.bytes = vol_info.leb_size * args.lebs; + args.bytes = (long long)vol_info.leb_size * args.lebs; + + if (args.lebs == 0 || args.bytes == 0) + args.bytes = vol_info.rsvd_bytes + dev_info.avail_bytes; err = ubi_rsvol(libubi, args.node, args.vol_id, args.bytes); if (err) { diff --git a/ubi-utils/ubiscan.c b/ubi-utils/ubiscan.c new file mode 100644 index 0000000..66af12a --- /dev/null +++ b/ubi-utils/ubiscan.c @@ -0,0 +1,318 @@ +/* + * Copyright (C) 2021 Diego Ismirlian + * + * 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +/* + * An utility to scan MTD devices. + * + * Author: Diego Ismirlian dismirlian (at) google's mail + */ + +#define PROGRAM_NAME "ubiscan" + +#include <sys/stat.h> +#include <unistd.h> +#include <stdint.h> +#include <stdlib.h> +#include <getopt.h> +#include <fcntl.h> +#include <limits.h> + +#include <mtd/ubi-media.h> +#include <libubi.h> +#include <libmtd.h> +#include <libscan.h> +#include "common.h" + +#define MAX_BINS 50 + +/* The variables below are set by command line arguments */ +struct args { + int verbose; + const char *node; + int node_fd; + int bin_thresholds[MAX_BINS - 1]; + int nbins; +}; + +static struct args args = { + .verbose = 0, + .nbins = 6, + .bin_thresholds = { + 10, + 100, + 1000, + 10000, + 100000, + }, +}; + +static const char doc[] = PROGRAM_NAME " version " VERSION + " - a tool to scan MTD devices"; + +static const char optionsstr[] = +"-h, -?, --help print help message\n" +"-H, --histrogram=<list> comma-separated list of bin thresholds\n" +"-v, --verbose be verbose\n" +"-V, --version print program version\n"; + +static const char usage[] = +"Usage: " PROGRAM_NAME " <MTD device node file name> " +"\t\t\t[--help] [--version] [--verbose] [--histogram=<list>]"; + +static const struct option long_options[] = { + { .name = "help", .has_arg = 0, .flag = NULL, .val = 'h' }, + { .name = "histogram", .has_arg = 1, .flag = NULL, .val = 'H' }, + { .name = "verbose", .has_arg = 0, .flag = NULL, .val = 'v' }, + { .name = "version", .has_arg = 0, .flag = NULL, .val = 'V' }, + { NULL, 0, NULL, 0}, +}; + +static int parse_opt(int argc, char * const argv[]) +{ + int last_bin = 0; + while (1) { + int key; + + key = getopt_long(argc, argv, "h?VvH:", long_options, NULL); + if (key == -1) + break; + + switch (key) { + case 'v': + args.verbose = 1; + break; + case 'H': { + args.nbins = 1; + char *token = strtok(optarg, ","); + while (token) { + if (args.nbins == MAX_BINS) + return errmsg("too many bins"); + int th = atoi(token); + if (th <= last_bin) + return errmsg("bad bin threshold list"); + args.bin_thresholds[args.nbins - 1] = th; + last_bin = th; + args.nbins++; + token = strtok(NULL, ","); + } + } break; + case 'V': + common_print_version(); + exit(EXIT_SUCCESS); + case 'h': + printf("%s\n\n", doc); + printf("%s\n\n", usage); + printf("%s\n", optionsstr); + exit(EXIT_SUCCESS); + case '?': + printf("%s\n\n", doc); + printf("%s\n\n", usage); + printf("%s\n", optionsstr); + return -1; + + case ':': + return errmsg("parameter is missing"); + + default: + fprintf(stderr, "Use -h for help\n"); + return -1; + } + } + + if (optind == argc) + return errmsg("MTD device name was not specified (use -h for help)"); + else if (optind != argc - 1) + return errmsg("more then one MTD device specified (use -h for help)"); + + args.node = argv[optind]; + return 0; +} + +int main(int argc, char * const argv[]) +{ + int err; + libmtd_t libmtd; + struct mtd_info mtd_info; + struct mtd_dev_info mtd; + struct ubi_scan_info *si; + int max, min; + + struct { + int min; + int max; + int cnt; + uint64_t mean; + } bins[MAX_BINS]; + + err = parse_opt(argc, argv); + if (err) + return -1; + + libmtd = libmtd_open(); + if (!libmtd) + return errmsg("MTD subsystem is not present"); + + err = mtd_get_info(libmtd, &mtd_info); + if (err) { + sys_errmsg("cannot get MTD information"); + goto out_close_mtd; + } + + err = mtd_get_dev_info(libmtd, args.node, &mtd); + if (err) { + sys_errmsg("cannot get information about \"%s\"", args.node); + goto out_close_mtd; + } + + args.node_fd = open(args.node, O_RDONLY); + if (args.node_fd == -1) { + sys_errmsg("cannot open \"%s\"", args.node); + goto out_close_mtd; + } + + printf("Summary\n"); + printf("=========================================================\n"); + printf("mtd : %d\n", mtd.mtd_num); + printf("type : %s\n", mtd.type_str); + printf("size : "); + util_print_bytes(mtd.size, 1); + printf("\n"); + printf("PEBs : %d\n", mtd.eb_cnt); + printf("min I/O: %d bytes\n", mtd.min_io_size); + + printf("\n"); + printf("PEB erase counters\n"); + printf("=========================================================\n"); + err = ubi_scan(&mtd, args.node_fd, &si, 0); + if (err) { + errmsg("failed to scan mtd%d (%s)", mtd.mtd_num, args.node); + goto out_close; + } + + memset(bins, 0, sizeof(bins)); + + for (int j = 0; j < args.nbins; j++) + bins[j].min = INT_MAX; + + min = INT_MAX; + max = 0; + + for (int eb = 0; eb < mtd.eb_cnt; eb++) { + uint32_t ec = si->ec[eb]; + switch (ec) { + case EB_EMPTY: + case EB_CORRUPTED: + case EB_ALIEN: + case EB_BAD: + case EC_MAX: + break; + default: { + int bin = 0; + + if (ec > max) + max = ec; + if (ec < min) + min = ec; + + for (int j = 0; j < args.nbins - 1 && ec >= args.bin_thresholds[j]; j++, bin++); + + bins[bin].cnt++; + bins[bin].mean += ec; + if (ec < bins[bin].min) + bins[bin].min = ec; + if (ec > bins[bin].max) + bins[bin].max = ec; + + } break; + } + } + + printf("valid : %d\n", si->ok_cnt); + printf("empty : %d\n", si->empty_cnt); + printf("corrupted: %d\n", si->corrupted_cnt); + printf("alien : %d\n", si->alien_cnt); + printf("bad : %d\n", si->bad_cnt); + + if (si->ok_cnt == 0) + min = 0; + + printf("\n"); + printf("Histogram\n"); + printf("=========================================================\n"); + printf("from to count min avg max\n"); + printf("---------------------------------------------------------\n"); + for (int j = 0; j < args.nbins; j++) { + if (bins[j].cnt) + bins[j].mean /= bins[j].cnt; + else + bins[j].min = 0; + + int from = (j == 0) ? 0 : args.bin_thresholds[j - 1]; + if (j == args.nbins - 1) + printf("%-8d .. inf: %8d %8d %8" PRIu64 " %8d\n", + from, bins[j].cnt, bins[j].min, bins[j].mean, bins[j].max); + else + printf("%-8d .. %8d: %8d %8d %8" PRIu64 " %8d\n", + from, args.bin_thresholds[j] - 1, + bins[j].cnt, bins[j].min, bins[j].mean, bins[j].max); + } + printf("---------------------------------------------------------\n"); + printf("Total : %8d %8d %8llu %8d\n", si->ok_cnt, min, si->mean_ec, max); + + if (args.verbose) { + printf("\n"); + printf("Details\n"); + printf("=========================================================\n"); + for (int eb = 0; eb < mtd.eb_cnt; eb++) { + printf("PEB %8d: ", eb); + uint32_t ec = si->ec[eb]; + switch (ec) { + case EB_EMPTY: + printf("EB_EMPTY\n"); + break; + case EB_CORRUPTED: + printf("EB_CORRUPTED\n"); + break; + case EB_ALIEN: + printf("EB_ALIEN\n"); + break; + case EB_BAD: + printf("EB_BAD\n"); + break; + case EC_MAX: + printf("EC_MAX\n"); + break; + default: + printf("%u\n", ec); + break; + } + } + } + + ubi_scan_free(si); + close(args.node_fd); + libmtd_close(libmtd); + return 0; + +out_close: + close(args.node_fd); +out_close_mtd: + libmtd_close(libmtd); + return -1; +} + diff --git a/ubifs-utils/Makemodule.am b/ubifs-utils/Makemodule.am index 59109cc..f84569a 100644 --- a/ubifs-utils/Makemodule.am +++ b/ubifs-utils/Makemodule.am @@ -1,50 +1,103 @@ -mkfs_ubifs_SOURCES = \ - ubifs-utils/mkfs.ubifs/mkfs.ubifs.c \ - ubifs-utils/mkfs.ubifs/defs.h \ - ubifs-utils/mkfs.ubifs/lpt.h \ - ubifs-utils/mkfs.ubifs/mkfs.ubifs.h \ - ubifs-utils/mkfs.ubifs/crc16.h \ - ubifs-utils/mkfs.ubifs/key.h \ - ubifs-utils/mkfs.ubifs/compr.h \ - ubifs-utils/mkfs.ubifs/ubifs.h \ - ubifs-utils/mkfs.ubifs/sign.h \ - ubifs-utils/mkfs.ubifs/crc16.c \ - ubifs-utils/mkfs.ubifs/lpt.c \ - ubifs-utils/mkfs.ubifs/compr.c \ - ubifs-utils/mkfs.ubifs/hashtable/hashtable.h \ - ubifs-utils/mkfs.ubifs/hashtable/hashtable_itr.h \ - ubifs-utils/mkfs.ubifs/hashtable/hashtable_private.h \ - ubifs-utils/mkfs.ubifs/hashtable/hashtable.c \ - ubifs-utils/mkfs.ubifs/hashtable/hashtable_itr.c \ - ubifs-utils/mkfs.ubifs/devtable.c +common_SOURCES = \ + ubifs-utils/common/compiler_attributes.h \ + ubifs-utils/common/linux_types.h \ + ubifs-utils/common/linux_err.h \ + ubifs-utils/common/atomic.h \ + ubifs-utils/common/bitops.h \ + ubifs-utils/common/bitops.c \ + ubifs-utils/common/spinlock.h \ + ubifs-utils/common/mutex.h \ + ubifs-utils/common/rwsem.h \ + ubifs-utils/common/kmem.h \ + ubifs-utils/common/kmem.c \ + ubifs-utils/common/sort.h \ + ubifs-utils/common/sort.c \ + ubifs-utils/common/defs.h \ + ubifs-utils/common/crc16.h \ + ubifs-utils/common/crc16.c \ + ubifs-utils/common/compr.h \ + ubifs-utils/common/compr.c \ + ubifs-utils/common/hashtable/hashtable.h \ + ubifs-utils/common/hashtable/hashtable_itr.h \ + ubifs-utils/common/hashtable/hashtable_private.h \ + ubifs-utils/common/hashtable/hashtable.c \ + ubifs-utils/common/hashtable/hashtable_itr.c \ + ubifs-utils/common/devtable.h \ + ubifs-utils/common/devtable.c \ + ubifs-utils/common/hexdump.c + +libubifs_SOURCES = \ + ubifs-utils/libubifs/ubifs-media.h \ + ubifs-utils/libubifs/ubifs.h \ + ubifs-utils/libubifs/key.h \ + ubifs-utils/libubifs/misc.h \ + ubifs-utils/libubifs/io.c \ + ubifs-utils/libubifs/sb.c \ + ubifs-utils/libubifs/super.c \ + ubifs-utils/libubifs/master.c \ + ubifs-utils/libubifs/debug.h \ + ubifs-utils/libubifs/debug.c \ + ubifs-utils/libubifs/scan.c \ + ubifs-utils/libubifs/find.c \ + ubifs-utils/libubifs/dir.c \ + ubifs-utils/libubifs/budget.c \ + ubifs-utils/libubifs/journal.c \ + ubifs-utils/libubifs/gc.c \ + ubifs-utils/libubifs/lpt.c \ + ubifs-utils/libubifs/lpt_commit.c \ + ubifs-utils/libubifs/lprops.c \ + ubifs-utils/libubifs/tnc_misc.c \ + ubifs-utils/libubifs/tnc.c \ + ubifs-utils/libubifs/tnc_commit.c \ + ubifs-utils/libubifs/commit.c \ + ubifs-utils/libubifs/orphan.c \ + ubifs-utils/libubifs/log.c \ + ubifs-utils/libubifs/replay.c \ + ubifs-utils/libubifs/recovery.c if WITH_CRYPTO -mkfs_ubifs_SOURCES += ubifs-utils/mkfs.ubifs/crypto.c \ - ubifs-utils/mkfs.ubifs/fscrypt.c \ - ubifs-utils/mkfs.ubifs/sign.c +common_SOURCES += ubifs-utils/common/crypto.c \ + ubifs-utils/common/crypto.h \ + ubifs-utils/common/fscrypt.c \ + ubifs-utils/common/fscrypt.h \ + ubifs-utils/common/sign.h \ + ubifs-utils/common/sign.c + +libubifs_SOURCES += ubifs-utils/libubifs/auth.c endif -mkfs_ubifs_LDADD = libmtd.a libubi.a $(ZLIB_LIBS) $(LZO_LIBS) $(ZSTD_LIBS) $(UUID_LIBS) $(LIBSELINUX_LIBS) $(OPENSSL_LIBS) -lm -mkfs_ubifs_CPPFLAGS = $(AM_CPPFLAGS) $(ZLIB_CFLAGS) $(LZO_CFLAGS) $(ZSTD_CFLAGS) $(UUID_CFLAGS) $(LIBSELINUX_CFLAGS)\ - -I$(top_srcdir)/ubi-utils/include -I$(top_srcdir)/ubifs-utils/mkfs.ubifs/ +mkfs_ubifs_SOURCES = \ + $(common_SOURCES) \ + $(libubifs_SOURCES) \ + ubifs-utils/mkfs.ubifs/mkfs.ubifs.c + +mkfs_ubifs_LDADD = libmtd.a libubi.a $(ZLIB_LIBS) $(LZO_LIBS) $(ZSTD_LIBS) $(UUID_LIBS) $(LIBSELINUX_LIBS) $(OPENSSL_LIBS) \ + $(DUMP_STACK_LD) $(ASAN_LIBS) -lm -lpthread libmissing.a +mkfs_ubifs_CPPFLAGS = $(AM_CPPFLAGS) $(ZLIB_CFLAGS) $(LZO_CFLAGS) $(ZSTD_CFLAGS) $(UUID_CFLAGS) $(LIBSELINUX_CFLAGS) \ + -I$(top_srcdir)/ubi-utils/include -I$(top_srcdir)/ubifs-utils/common -I $(top_srcdir)/ubifs-utils/libubifs -UBIFS_BINS = \ - mkfs.ubifs +fsck_ubifs_SOURCES = \ + $(common_SOURCES) \ + $(libubifs_SOURCES) \ + ubifs-utils/fsck.ubifs/fsck.ubifs.h \ + ubifs-utils/fsck.ubifs/fsck.ubifs.c \ + ubifs-utils/fsck.ubifs/problem.c \ + ubifs-utils/fsck.ubifs/load_fs.c \ + ubifs-utils/fsck.ubifs/extract_files.c \ + ubifs-utils/fsck.ubifs/rebuild_fs.c \ + ubifs-utils/fsck.ubifs/check_files.c \ + ubifs-utils/fsck.ubifs/check_space.c \ + ubifs-utils/fsck.ubifs/handle_disconnected.c -UBIFS_HEADER = \ - ubifs-utils/mkfs.ubifs/compr.h ubifs-utils/mkfs.ubifs/crc16.h \ - ubifs-utils/mkfs.ubifs/defs.h ubifs-utils/mkfs.ubifs/key.h \ - ubifs-utils/mkfs.ubifs/lpt.h ubifs-utils/mkfs.ubifs/mkfs.ubifs.h \ - ubifs-utils/mkfs.ubifs/ubifs.h \ - ubifs-utils/mkfs.ubifs/crypto.h \ - ubifs-utils/mkfs.ubifs/fscrypt.h \ - ubifs-utils/mkfs.ubifs/hashtable/hashtable.h \ - ubifs-utils/mkfs.ubifs/hashtable/hashtable_itr.h \ - ubifs-utils/mkfs.ubifs/hashtable/hashtable_private.h +fsck_ubifs_LDADD = libmtd.a libubi.a $(ZLIB_LIBS) $(LZO_LIBS) $(ZSTD_LIBS) $(UUID_LIBS) $(LIBSELINUX_LIBS) $(OPENSSL_LIBS) \ + $(DUMP_STACK_LD) $(ASAN_LIBS) -lm -lpthread libmissing.a +fsck_ubifs_CPPFLAGS = $(AM_CPPFLAGS) $(ZLIB_CFLAGS) $(LZO_CFLAGS) $(ZSTD_CFLAGS) $(UUID_CFLAGS) $(LIBSELINUX_CFLAGS) \ + -I$(top_srcdir)/ubi-utils/include -I$(top_srcdir)/ubifs-utils/common -I $(top_srcdir)/ubifs-utils/libubifs \ + -I$(top_srcdir)/ubifs-utils/fsck.ubifs -UBIFS_EXTRA = \ - ubifs-utils/mkfs.ubifs/README +EXTRA_DIST += ubifs-utils/common/README ubifs-utils/libubifs/README -EXTRA_DIST += $(UBIFS_HEADER) $(UBIFS_EXTRA) +dist_sbin_SCRIPTS = ubifs-utils/mount.ubifs -sbin_PROGRAMS += $(UBIFS_BINS) +sbin_PROGRAMS += mkfs.ubifs +sbin_PROGRAMS += fsck.ubifs diff --git a/ubifs-utils/common/README b/ubifs-utils/common/README new file mode 100644 index 0000000..8fe716e --- /dev/null +++ b/ubifs-utils/common/README @@ -0,0 +1,13 @@ +Common Library + +* crc16.h and crc16.c were copied from the linux kernel(https://elixir.bootlin.com/linux/v5.10.232/source/lib/crc16.c). +* defs.h is a bunch of definitions to smooth things over. +* hashtable/* was downloaded from http://www.cl.cam.ac.uk/~cwc22/hashtable/ +* atomic.h was downloaded from https://the-linux-channel.the-toffee-project.org/index.php?page=6-tutorials-linux-user-space-atomic-operations +* bitops.h and bitops.c were copied from the linux kernel(https://elixir.bootlin.com/linux/v5.10.232/source/lib/find_bit.c). +* compiler_attributes.h was copied from the linux kernel(https://elixir.bootlin.com/linux/v5.10.232/source/include/linux/compiler_attributes.h). +* linux_types.h was copied from the linux kernel(https://elixir.bootlin.com/linux/v5.10.232/source/include/linux/types.h overflow.h fscrypt.h). +* linux_err.h was copied from the linux kernel(https://elixir.bootlin.com/linux/v5.10.232/source/include/linux/err.h). +* hexdump.c was copied from the linux kernel(https://elixir.bootlin.com/linux/v5.10.232/source/lib/hexdump.c). +* kmem.h and kmem.c were partial copied from xfsprogs-dev(https://git.kernel.org/pub/scm/fs/xfs/xfsprogs-dev.git/). +* sort.h and sort.c were copied from the linux kernel(https://elixir.bootlin.com/linux/v5.10.232/source/lib/sort.c). diff --git a/ubifs-utils/common/atomic.h b/ubifs-utils/common/atomic.h new file mode 100644 index 0000000..95754b2 --- /dev/null +++ b/ubifs-utils/common/atomic.h @@ -0,0 +1,137 @@ +//Source: http://golubenco.org/atomic-operations.html +#ifndef __ATOMIC_H__ +#define __ATOMIC_H__ + +#define GCC_VERSION (__GNUC__ * 10000 \ + + __GNUC_MINOR__ * 100 \ + + __GNUC_PATCHLEVEL__) + +/* Check GCC version, just to be safe */ +#if GCC_VERSION < 40100 +# error atomic.h works only with GCC newer than version 4.1 +#endif /* GNUC >= 4.1 */ + +/** + * Atomic type. + */ +typedef struct { + volatile long counter; +} atomic_long_t; + +#define ATOMIC_INIT(i) { (i) } + +/** + * Read atomic variable + * @param v pointer of type atomic_long_t + * + * Atomically reads the value of @v. + */ +#define atomic_long_read(v) ((v)->counter) + +/** + * Set atomic variable + * @param v pointer of type atomic_long_t + * @param i required value + */ +#define atomic_long_set(v,i) (((v)->counter) = (i)) + +/** + * Add to the atomic variable + * @param i integer value to add + * @param v pointer of type atomic_long_t + */ +static inline void atomic_long_add( int i, atomic_long_t *v ) +{ + (void)__sync_add_and_fetch(&v->counter, i); +} + +/** + * Subtract the atomic variable + * @param i integer value to subtract + * @param v pointer of type atomic_long_t + * + * Atomically subtracts @i from @v. + */ +static inline void atomic_long_sub( int i, atomic_long_t *v ) +{ + (void)__sync_sub_and_fetch(&v->counter, i); +} + +/** + * Subtract value from variable and test result + * @param i integer value to subtract + * @param v pointer of type atomic_long_t + * + * Atomically subtracts @i from @v and returns + * true if the result is zero, or false for all + * other cases. + */ +static inline int atomic_long_sub_and_test( int i, atomic_long_t *v ) +{ + return !(__sync_sub_and_fetch(&v->counter, i)); +} + +/** + * Increment atomic variable + * @param v pointer of type atomic_long_t + * + * Atomically increments @v by 1. + */ +static inline void atomic_long_inc( atomic_long_t *v ) +{ + (void)__sync_fetch_and_add(&v->counter, 1); +} + +/** + * @brief decrement atomic variable + * @param v: pointer of type atomic_long_t + * + * Atomically decrements @v by 1. Note that the guaranteed + * useful range of an atomic_long_t is only 24 bits. + */ +static inline void atomic_long_dec( atomic_long_t *v ) +{ + (void)__sync_fetch_and_sub(&v->counter, 1); +} + +/** + * @brief Decrement and test + * @param v pointer of type atomic_long_t + * + * Atomically decrements @v by 1 and + * returns true if the result is 0, or false for all other + * cases. + */ +static inline int atomic_long_dec_and_test( atomic_long_t *v ) +{ + return !(__sync_sub_and_fetch(&v->counter, 1)); +} + +/** + * @brief Increment and test + * @param v pointer of type atomic_long_t + * + * Atomically increments @v by 1 + * and returns true if the result is zero, or false for all + * other cases. + */ +static inline int atomic_long_inc_and_test( atomic_long_t *v ) +{ + return !(__sync_add_and_fetch(&v->counter, 1)); +} + +/** + * @brief add and test if negative + * @param v pointer of type atomic_long_t + * @param i integer value to add + * + * Atomically adds @i to @v and returns true + * if the result is negative, or false when + * result is greater than or equal to zero. + */ +static inline int atomic_long_add_negative( int i, atomic_long_t *v ) +{ + return (__sync_add_and_fetch(&v->counter, i) < 0); +} + +#endif diff --git a/ubifs-utils/common/bitops.c b/ubifs-utils/common/bitops.c new file mode 100644 index 0000000..c82f1fa --- /dev/null +++ b/ubifs-utils/common/bitops.c @@ -0,0 +1,37 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Realizations of bit operations. + */ + +#include "bitops.h" +#include "defs.h" + +/* + * This is a common helper function for find_next_bit and + * find_next_zero_bit. The difference is the "invert" argument, which + * is XORed with each fetched word before searching it for one bits. + */ +unsigned long _find_next_bit(const unsigned long *addr, + unsigned long nbits, unsigned long start, unsigned long invert) +{ + unsigned long tmp; + + if (!nbits || start >= nbits) + return nbits; + + tmp = addr[start / BITS_PER_LONG] ^ invert; + + /* Handle 1st word. */ + tmp &= BITMAP_FIRST_WORD_MASK(start); + start = round_down(start, BITS_PER_LONG); + + while (!tmp) { + start += BITS_PER_LONG; + if (start >= nbits) + return nbits; + + tmp = addr[start / BITS_PER_LONG] ^ invert; + } + + return min(start + __ffs(tmp), nbits); +} diff --git a/ubifs-utils/common/bitops.h b/ubifs-utils/common/bitops.h new file mode 100644 index 0000000..3a2d3f8 --- /dev/null +++ b/ubifs-utils/common/bitops.h @@ -0,0 +1,152 @@ +#ifndef __BITOPS_H__ +#define __BITOPS_H__ + +/* + * Non-atomic bitops. + */ + +#include <stdbool.h> + +#define BITS_PER_LONG __LONG_WIDTH__ +#define DIV_ROUND_UP(n, d) (((n) + (d) - 1) / (d)) +#define BITS_TO_LONGS(nr) DIV_ROUND_UP(nr, BITS_PER_LONG) + +#define BIT_MASK(nr) (1UL << ((nr) % BITS_PER_LONG)) +#define BIT_WORD(nr) ((nr) / BITS_PER_LONG) +#define BITMAP_FIRST_WORD_MASK(start) (~0UL << ((start) & (BITS_PER_LONG - 1))) + +static inline void __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 set_bit(int nr, volatile unsigned long *addr) +{ + __set_bit(nr, addr); +} + +static inline void __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 void clear_bit(int nr, volatile unsigned long *addr) +{ + __clear_bit(nr, addr); +} + +static inline bool test_bit(int nr, const volatile unsigned long *addr) +{ + unsigned long mask = BIT_MASK(nr); + unsigned long *p = ((unsigned long *)addr) + BIT_WORD(nr); + + return (*p & mask) != 0; +} + +/* Sets and returns original value of the bit */ +static inline int test_and_set_bit(int nr, volatile unsigned long *addr) +{ + if (test_bit(nr, addr)) + return 1; + set_bit(nr, addr); + return 0; +} + +/** + * fls - find last (most-significant) bit set + * @x: the word to search + * + * This is defined the same way as ffs. + * Note fls(0) = 0, fls(1) = 1, fls(0x80000000) = 32. + */ +static inline int fls(int x) +{ + int r = 32; + + if (!x) + return 0; + if (!(x & 0xffff0000u)) { + x <<= 16; + r -= 16; + } + if (!(x & 0xff000000u)) { + x <<= 8; + r -= 8; + } + if (!(x & 0xf0000000u)) { + x <<= 4; + r -= 4; + } + if (!(x & 0xc0000000u)) { + x <<= 2; + r -= 2; + } + if (!(x & 0x80000000u)) { + x <<= 1; + r -= 1; + } + return r; +} + +/** + * __ffs - find first bit in word. + * @word: The word to search + * + * Undefined if no bit exists, so code should check against 0 first. + */ +static inline unsigned long __ffs(unsigned long word) +{ + int num = 0; + +#if BITS_PER_LONG == 64 + if ((word & 0xffffffff) == 0) { + num += 32; + word >>= 32; + } +#endif + if ((word & 0xffff) == 0) { + num += 16; + word >>= 16; + } + if ((word & 0xff) == 0) { + num += 8; + word >>= 8; + } + if ((word & 0xf) == 0) { + num += 4; + word >>= 4; + } + if ((word & 0x3) == 0) { + num += 2; + word >>= 2; + } + if ((word & 0x1) == 0) + num += 1; + return num; +} + +unsigned long _find_next_bit(const unsigned long *addr, + unsigned long nbits, unsigned long start, unsigned long invert); + +/* + * Find the next set bit in a memory region. + */ +static inline unsigned long find_next_bit(const unsigned long *addr, + unsigned long size, unsigned long offset) +{ + return _find_next_bit(addr, size, offset, 0UL); +} + +static inline unsigned long find_next_zero_bit(const unsigned long *addr, + unsigned long size, unsigned long offset) +{ + return _find_next_bit(addr, size, offset, ~0UL); +} + +#endif diff --git a/ubifs-utils/common/compiler_attributes.h b/ubifs-utils/common/compiler_attributes.h new file mode 100644 index 0000000..bb65d3a --- /dev/null +++ b/ubifs-utils/common/compiler_attributes.h @@ -0,0 +1,79 @@ +#ifndef __COMPILER_ATTRIBUTES_H__ +#define __COMPILER_ATTRIBUTES_H__ + +#if __has_attribute(__fallthrough__) +#define fallthrough __attribute__((__fallthrough__)) +#else +#define fallthrough do {} while (0) +#endif + +#define __packed __attribute__((__packed__)) +#define __unused __attribute__((__unused__)) +#define __const __attribute__((__const__)) +#define __must_check __attribute__((__warn_unused_result__)) +#ifndef __force +#define __force +#endif + +/* + * Optional: only supported since clang >= 14.0 + * + * gcc: https://gcc.gnu.org/onlinedocs/gcc/Common-Function-Attributes.html#index-error-function-attribute + */ +#if __has_attribute(__error__) +# define __compiletime_error(msg) __attribute__((__error__(msg))) +#else +# define __compiletime_error(msg) +#endif + +#ifndef __compiletime_error +# define __compiletime_error(message) +#endif + +#ifdef __OPTIMIZE__ +# define __compiletime_assert(condition, msg, prefix, suffix) \ + do { \ + extern void prefix ## suffix(void) __compiletime_error(msg); \ + if (!(condition)) \ + prefix ## suffix(); \ + } while (0) +#else +# define __compiletime_assert(condition, msg, prefix, suffix) do { } while (0) +#endif + +#define _compiletime_assert(condition, msg, prefix, suffix) \ + __compiletime_assert(condition, msg, prefix, suffix) + +/** + * compiletime_assert - break build and emit msg if condition is false + * @condition: a compile-time constant condition to check + * @msg: a message to emit if condition is false + * + * In tradition of POSIX assert, this macro will break the build if the + * supplied condition is *false*, emitting the supplied error message if the + * compiler has support to do so. + */ +#define compiletime_assert(condition, msg) \ + _compiletime_assert(condition, msg, __compiletime_assert_, __COUNTER__) + +/** + * BUILD_BUG_ON_MSG - break compile if a condition is true & emit supplied + * error message. + * @condition: the condition which the compiler should know is false. + * + * See BUILD_BUG_ON for description. + */ +#define BUILD_BUG_ON_MSG(cond, msg) compiletime_assert(!(cond), msg) + +/** + * BUILD_BUG_ON - break compile if a condition is true. + * @condition: the condition which the compiler should know is false. + * + * If you have some code which relies on certain constants being equal, or + * some other compile-time-evaluated condition, you should use BUILD_BUG_ON to + * detect if someone changes it. + */ +#define BUILD_BUG_ON(condition) \ + BUILD_BUG_ON_MSG(condition, "BUILD_BUG_ON failed: " #condition) + +#endif diff --git a/ubifs-utils/mkfs.ubifs/compr.c b/ubifs-utils/common/compr.c index 06c35ca..6f90151 100644 --- a/ubifs-utils/mkfs.ubifs/compr.c +++ b/ubifs-utils/common/compr.c @@ -24,27 +24,31 @@ #include <stdio.h> #include <stdint.h> #include <string.h> -#ifndef WITHOUT_LZO +#ifdef WITH_LZO #include <lzo/lzo1x.h> #endif #include <linux/types.h> -#ifndef WITHOUT_ZSTD +#ifdef WITH_ZSTD #include <zstd.h> #endif +#ifdef WITH_ZLIB #define crc32 __zlib_crc32 #include <zlib.h> #undef crc32 +#endif #include "compr.h" -#include "mkfs.ubifs.h" +#include "ubifs.h" static void *lzo_mem; static unsigned long long errcnt = 0; -#ifndef WITHOUT_LZO +#ifdef WITH_LZO +extern struct ubifs_info info_; static struct ubifs_info *c = &info_; #endif +#ifdef WITH_ZLIB #define DEFLATE_DEF_LEVEL Z_DEFAULT_COMPRESSION #define DEFLATE_DEF_WINBITS 11 #define DEFLATE_DEF_MEMLEVEL 8 @@ -91,8 +95,9 @@ static int zlib_deflate(void *in_buf, size_t in_len, void *out_buf, return 0; } +#endif -#ifndef WITHOUT_LZO +#ifdef WITH_LZO static int lzo_compress(void *in_buf, size_t in_len, void *out_buf, size_t *out_len) { @@ -112,7 +117,7 @@ static int lzo_compress(void *in_buf, size_t in_len, void *out_buf, } #endif -#ifndef WITHOUT_ZSTD +#ifdef WITH_ZSTD static ZSTD_CCtx *zctx; static int zstd_compress(void *in_buf, size_t in_len, void *out_buf, @@ -140,7 +145,7 @@ static int no_compress(void *in_buf, size_t in_len, void *out_buf, static char *zlib_buf; -#ifndef WITHOUT_LZO +#if defined(WITH_LZO) && defined(WITH_ZLIB) static int favor_lzo_compress(void *in_buf, size_t in_len, void *out_buf, size_t *out_len, int *type) { @@ -177,12 +182,12 @@ static int favor_lzo_compress(void *in_buf, size_t in_len, void *out_buf, select_lzo: *out_len = lzo_len; - *type = MKFS_UBIFS_COMPR_LZO; + *type = UBIFS_COMPR_LZO; return 0; select_zlib: *out_len = zlib_len; - *type = MKFS_UBIFS_COMPR_ZLIB; + *type = UBIFS_COMPR_ZLIB; memcpy(out_buf, zlib_buf, zlib_len); return 0; } @@ -195,30 +200,33 @@ int compress_data(void *in_buf, size_t in_len, void *out_buf, size_t *out_len, if (in_len < UBIFS_MIN_COMPR_LEN) { no_compress(in_buf, in_len, out_buf, out_len); - return MKFS_UBIFS_COMPR_NONE; + return UBIFS_COMPR_NONE; } -#ifdef WITHOUT_LZO - { - switch (type) { -#else +#if defined(WITH_LZO) && defined(WITH_ZLIB) if (c->favor_lzo) ret = favor_lzo_compress(in_buf, in_len, out_buf, out_len, &type); else { +#else + { +#endif switch (type) { - case MKFS_UBIFS_COMPR_LZO: +#ifdef WITH_LZO + case UBIFS_COMPR_LZO: ret = lzo_compress(in_buf, in_len, out_buf, out_len); break; #endif - case MKFS_UBIFS_COMPR_ZLIB: +#ifdef WITH_ZLIB + case UBIFS_COMPR_ZLIB: ret = zlib_deflate(in_buf, in_len, out_buf, out_len); break; -#ifndef WITHOUT_ZSTD - case MKFS_UBIFS_COMPR_ZSTD: +#endif +#ifdef WITH_ZSTD + case UBIFS_COMPR_ZSTD: ret = zstd_compress(in_buf, in_len, out_buf, out_len); break; #endif - case MKFS_UBIFS_COMPR_NONE: + case UBIFS_COMPR_NONE: ret = 1; break; default: @@ -229,14 +237,14 @@ int compress_data(void *in_buf, size_t in_len, void *out_buf, size_t *out_len, } if (ret || *out_len >= in_len) { no_compress(in_buf, in_len, out_buf, out_len); - return MKFS_UBIFS_COMPR_NONE; + return UBIFS_COMPR_NONE; } return type; } int init_compression(void) { -#ifdef WITHOUT_LZO +#ifndef WITH_LZO lzo_mem = NULL; #else lzo_mem = malloc(LZO1X_999_MEM_COMPRESS); @@ -244,11 +252,15 @@ int init_compression(void) return -1; #endif +#ifndef WITH_ZLIB + zlib_buf = NULL; +#else zlib_buf = malloc(UBIFS_BLOCK_SIZE * WORST_COMPR_FACTOR); if (!zlib_buf) goto err; +#endif -#ifndef WITHOUT_ZSTD +#ifdef WITH_ZSTD zctx = ZSTD_createCCtx(); if (!zctx) goto err; @@ -265,7 +277,7 @@ void destroy_compression(void) { free(zlib_buf); free(lzo_mem); -#ifndef WITHOUT_ZSTD +#ifdef WITH_ZSTD ZSTD_freeCCtx(zctx); #endif if (errcnt) diff --git a/ubifs-utils/mkfs.ubifs/compr.h b/ubifs-utils/common/compr.h index d58c7c7..3e8e9b6 100644 --- a/ubifs-utils/mkfs.ubifs/compr.h +++ b/ubifs-utils/common/compr.h @@ -31,14 +31,6 @@ */ #define WORST_COMPR_FACTOR 4 -enum compression_type -{ - MKFS_UBIFS_COMPR_NONE, - MKFS_UBIFS_COMPR_LZO, - MKFS_UBIFS_COMPR_ZLIB, - MKFS_UBIFS_COMPR_ZSTD, -}; - int compress_data(void *in_buf, size_t in_len, void *out_buf, size_t *out_len, int type); int init_compression(void); diff --git a/ubifs-utils/mkfs.ubifs/crc16.c b/ubifs-utils/common/crc16.c index a19512e..a19512e 100644 --- a/ubifs-utils/mkfs.ubifs/crc16.c +++ b/ubifs-utils/common/crc16.c diff --git a/ubifs-utils/mkfs.ubifs/crc16.h b/ubifs-utils/common/crc16.h index 539d21a..539d21a 100644 --- a/ubifs-utils/mkfs.ubifs/crc16.h +++ b/ubifs-utils/common/crc16.h diff --git a/ubifs-utils/mkfs.ubifs/crypto.c b/ubifs-utils/common/crypto.c index 19c445e..e4ef349 100644 --- a/ubifs-utils/mkfs.ubifs/crypto.c +++ b/ubifs-utils/common/crypto.c @@ -17,14 +17,16 @@ * Authors: David Oberhollenzer <david.oberhollenzer@sigma-star.at> */ -#define PROGRAM_NAME "mkfs.ubifs" #include <openssl/evp.h> #include <openssl/err.h> +#include <openssl/rand.h> #include <string.h> #include <assert.h> +#include "linux_types.h" #include "fscrypt.h" -#include "common.h" +#include "defs.h" +#include "ubifs.h" static int do_hash(const EVP_MD *md, const unsigned char *in, size_t len, unsigned char *out) { diff --git a/ubifs-utils/mkfs.ubifs/crypto.h b/ubifs-utils/common/crypto.h index b6ffad1..b6ffad1 100644 --- a/ubifs-utils/mkfs.ubifs/crypto.h +++ b/ubifs-utils/common/crypto.h diff --git a/ubifs-utils/common/defs.h b/ubifs-utils/common/defs.h new file mode 100644 index 0000000..d5edbf6 --- /dev/null +++ b/ubifs-utils/common/defs.h @@ -0,0 +1,126 @@ +/* + * Greate deal of the code was taken from the kernel UBIFS implementation, and + * this file contains some "glue" definitions. + */ + +#ifndef __UBIFS_DEFS_H__ +#define __UBIFS_DEFS_H__ + +#include <stdlib.h> +#include <stdio.h> +#include <unistd.h> +#include <limits.h> +#include <errno.h> +#include <time.h> +#include <assert.h> +#if HAVE_EXECINFO_H +#include <execinfo.h> +#else +#include "libmissing.h" +#endif +#include "ubifs.h" + +/* common.h requires the PROGRAM_NAME macro */ +extern struct ubifs_info info_; +#define PROGRAM_NAME (info_.program_name) +#include "common.h" + +#define MKFS_PROGRAM_NAME "mkfs.ubifs" +#define FSCK_PROGRAM_NAME "fsck.ubifs" + +enum { MKFS_PROGRAM_TYPE = 0, FSCK_PROGRAM_TYPE }; + +enum { + DUMP_PREFIX_NONE, + DUMP_PREFIX_ADDRESS, + DUMP_PREFIX_OFFSET +}; + +#define pr_debug(fmt, ...) do { if (info_.debug_level >= DEBUG_LEVEL) \ + printf("<DEBUG> %s[%d] (%s): %s: " fmt, PROGRAM_NAME, getpid(), \ + info_.dev_name, __FUNCTION__, ##__VA_ARGS__); \ +} while(0) + +#define pr_notice(fmt, ...) do { if (info_.debug_level >= INFO_LEVEL) \ + printf("<INFO> %s[%d] (%s): %s: " fmt, PROGRAM_NAME, getpid(), \ + info_.dev_name, __FUNCTION__, ##__VA_ARGS__); \ +} while(0) + +#define pr_warn(fmt, ...) do { if (info_.debug_level >= WARN_LEVEL) \ + printf("<WARN> %s[%d] (%s): %s: " fmt, PROGRAM_NAME, getpid(), \ + info_.dev_name, __FUNCTION__, ##__VA_ARGS__); \ +} while(0) + +#define pr_err(fmt, ...) do { if (info_.debug_level >= ERR_LEVEL) \ + printf("<ERROR> %s[%d] (%s): %s: " fmt, PROGRAM_NAME, getpid(), \ + info_.dev_name, __FUNCTION__, ##__VA_ARGS__); \ +} while(0) + +#define pr_cont(fmt, ...) do { if (info_.debug_level >= ERR_LEVEL) \ + printf(fmt, ##__VA_ARGS__); \ +} while(0) + +static inline void dump_stack(void) +{ +#define STACK_SIZE 512 + int j, nptrs; + void *buffer[STACK_SIZE]; + char **strings; + + if (info_.debug_level < ERR_LEVEL) + return; + + nptrs = backtrace(buffer, STACK_SIZE); + strings = backtrace_symbols(buffer, nptrs); + + printf("dump_stack:\n"); + for (j = 0; j < nptrs; j++) + printf("%s\n", strings[j]); + + free(strings); +} + +static inline u32 get_random_u32(void) +{ + srand(time(NULL)); + return rand(); +} + +static inline time_t ktime_get_seconds(void) +{ + return time(NULL); +} + +#define likely(x) (x) +#define unlikely(x) (x) + +#define cond_resched() do {} while(0) + +#define BUG() do { \ + assert(0); \ +} while(0) +#define BUG_ON(cond) do { \ + assert(!cond); \ +} while(0) + +#define smp_wmb() do {} while(0) +#define smp_rmb() do {} while(0) +#define smp_mb__before_atomic() do {} while(0) +#define smp_mb__after_atomic() do {} while(0) + +#define min3(x, y, z) min((typeof(x))min(x, y), z) + +static inline u64 div_u64(u64 dividend, u32 divisor) +{ + return dividend / divisor; +} + +#if INT_MAX != 0x7fffffff +#error : sizeof(int) must be 4 for this program +#endif + +#if (~0ULL) != 0xffffffffffffffffULL +#error : sizeof(long long) must be 8 for this program +#endif + +#endif diff --git a/ubifs-utils/mkfs.ubifs/devtable.c b/ubifs-utils/common/devtable.c index aa815fb..2e581ff 100644 --- a/ubifs-utils/mkfs.ubifs/devtable.c +++ b/ubifs-utils/common/devtable.c @@ -45,7 +45,15 @@ * for more information about what the device table is. */ -#include "mkfs.ubifs.h" +#include <string.h> +#include <ctype.h> +#include <sys/stat.h> +#include <sys/types.h> +#include <sys/sysmacros.h> + +#include "devtable.h" +#include "ubifs.h" +#include "defs.h" #include "hashtable/hashtable.h" #include "hashtable/hashtable_itr.h" @@ -110,7 +118,7 @@ static int separate_last(const char *buf, int len, char **path, char **name) *path = malloc(path_len + 1); if (!*path) - return err_msg("cannot allocate %d bytes of memory", + return errmsg("cannot allocate %d bytes of memory", path_len + 1); memcpy(*path, buf, path_len); (*path)[path_len] = '\0'; @@ -118,7 +126,7 @@ static int separate_last(const char *buf, int len, char **path, char **name) *name = malloc(name_len + 1); if (!*name) { free(*path); - return err_msg("cannot allocate %d bytes of memory", + return errmsg("cannot allocate %d bytes of memory", name_len + 1); } memcpy(*name, n, name_len + 1); @@ -138,29 +146,29 @@ static int interpret_table_entry(const char *line) if (sscanf(line, "%1023s %c %o %u %u %u %u %u %u %u", buf, &type, &mode, &uid, &gid, &major, &minor, &start, &increment, &count) < 0) - return sys_err_msg("sscanf failed"); + return sys_errmsg("sscanf failed"); - dbg_msg(3, "name %s, type %c, mode %o, uid %u, gid %u, major %u, " - "minor %u, start %u, inc %u, cnt %u", - buf, type, mode, uid, gid, major, minor, start, - increment, count); + pr_debug("name %s, type %c, mode %o, uid %u, gid %u, major %u, " + "minor %u, start %u, inc %u, cnt %u\n", + buf, type, mode, uid, gid, major, minor, start, + increment, count); len = strnlen(buf, 1024); if (len == 0) - return err_msg("empty path"); + return errmsg("empty path"); if (len == 1024) - return err_msg("too long path"); + return errmsg("too long path"); if (buf[0] != '/') - return err_msg("device table entries require absolute paths"); + return errmsg("device table entries require absolute paths"); if (strstr(buf, "//")) - return err_msg("'//' cannot be used in the path"); + return errmsg("'//' cannot be used in the path"); if (len > 1 && buf[len - 1] == '/') - return err_msg("do not put '/' at the end"); + return errmsg("do not put '/' at the end"); if (strstr(buf, "/./") || strstr(buf, "/../") || !strcmp(buf + len - 2, "/.") || !strcmp(buf + len - 3, "/..")) - return err_msg("'.' and '..' cannot be used in the path"); + return errmsg("'.' and '..' cannot be used in the path"); switch (type) { case 'd': @@ -181,10 +189,10 @@ static int interpret_table_entry(const char *line) case 'l': mode |= S_IFLNK; if ((mode & 0777) != 0777) - return err_msg("link permission must be 0777"); + return errmsg("link permission must be 0777"); break; default: - return err_msg("unsupported file type '%c'", type); + return errmsg("unsupported file type '%c'", type); } if (separate_last(buf, len, &path, &name)) @@ -196,16 +204,16 @@ static int interpret_table_entry(const char *line) */ ph_elt = hashtable_search(path_htbl, path); if (!ph_elt) { - dbg_msg(3, "inserting '%s' into path hash table", path); + pr_debug("inserting '%s' into path hash table\n", path); ph_elt = malloc(sizeof(struct path_htbl_element)); if (!ph_elt) { - err_msg("cannot allocate %zd bytes of memory", + errmsg("cannot allocate %zd bytes of memory", sizeof(struct path_htbl_element)); goto out_free; } if (!hashtable_insert(path_htbl, path, ph_elt)) { - err_msg("cannot insert into path hash table"); + errmsg("cannot insert into path hash table"); goto out_free; } @@ -214,13 +222,13 @@ static int interpret_table_entry(const char *line) ph_elt->name_htbl = create_hashtable(128, &r5_hash, &is_equivalent); if (!ph_elt->name_htbl) { - err_msg("cannot create name hash table"); + errmsg("cannot create name hash table"); goto out_free; } } if (increment != 0 && count == 0) { - err_msg("count cannot be zero if increment is non-zero"); + errmsg("count cannot be zero if increment is non-zero"); goto out_free; } @@ -234,7 +242,7 @@ static int interpret_table_entry(const char *line) /* This entry does not require any iterating */ nh_elt = malloc(sizeof(struct name_htbl_element)); if (!nh_elt) { - err_msg("cannot allocate %zd bytes of memory", + errmsg("cannot allocate %zd bytes of memory", sizeof(struct name_htbl_element)); goto out_free; } @@ -244,17 +252,17 @@ static int interpret_table_entry(const char *line) nh_elt->gid = gid; nh_elt->dev = makedev(major, minor); - dbg_msg(3, "inserting '%s' into name hash table (major %d, minor %d)", - name, major(nh_elt->dev), minor(nh_elt->dev)); + pr_debug("inserting '%s' into name hash table (major %d, minor %d)\n", + name, major(nh_elt->dev), minor(nh_elt->dev)); if (hashtable_search(ph_elt->name_htbl, name)) { - err_msg("'%s' is referred twice", buf); + errmsg("'%s' is referred twice", buf); goto out_free; } nh_elt->name = name; if (!hashtable_insert(ph_elt->name_htbl, name, nh_elt)) { - err_msg("cannot insert into name hash table"); + errmsg("cannot insert into name hash table"); goto out_free; } } else { @@ -264,7 +272,7 @@ static int interpret_table_entry(const char *line) for (i = start; i < num; i++) { nh_elt = malloc(sizeof(struct name_htbl_element)); if (!nh_elt) { - err_msg("cannot allocate %zd bytes of memory", + errmsg("cannot allocate %zd bytes of memory", sizeof(struct name_htbl_element)); goto out_free; } @@ -276,24 +284,24 @@ static int interpret_table_entry(const char *line) nm = malloc(len); if (!nm) { - err_msg("cannot allocate %d bytes of memory", len); + errmsg("cannot allocate %d bytes of memory", len); goto out_free; } sprintf(nm, "%s%d", name, i); nh_elt->name = nm; - dbg_msg(3, "inserting '%s' into name hash table (major %d, minor %d)", - nm, major(nh_elt->dev), minor(nh_elt->dev)); + pr_debug("inserting '%s' into name hash table (major %d, minor %d)\n", + nm, major(nh_elt->dev), minor(nh_elt->dev)); if (hashtable_search(ph_elt->name_htbl, nm)) { - err_msg("'%s' is referred twice", buf); + errmsg("'%s' is referred twice", buf); free (nm); goto out_free; } if (!hashtable_insert(ph_elt->name_htbl, nm, nh_elt)) { - err_msg("cannot insert into name hash table"); + errmsg("cannot insert into name hash table"); free (nm); goto out_free; } @@ -328,23 +336,23 @@ int parse_devtable(const char *tbl_file) struct stat st; size_t len; - dbg_msg(1, "parsing device table file '%s'", tbl_file); + pr_debug("parsing device table file '%s'\n", tbl_file); path_htbl = create_hashtable(128, &r5_hash, &is_equivalent); if (!path_htbl) - return err_msg("cannot create path hash table"); + return errmsg("cannot create path hash table"); f = fopen(tbl_file, "r"); if (!f) - return sys_err_msg("cannot open '%s'", tbl_file); + return sys_errmsg("cannot open '%s'", tbl_file); if (fstat(fileno(f), &st) < 0) { - sys_err_msg("cannot stat '%s'", tbl_file); + sys_errmsg("cannot stat '%s'", tbl_file); goto out_close; } if (st.st_size < 10) { - sys_err_msg("'%s' is too short", tbl_file); + sys_errmsg("'%s' is too short", tbl_file); goto out_close; } @@ -369,7 +377,7 @@ int parse_devtable(const char *tbl_file) /* If this is not a comment line, try to interpret it */ if (len && *line != '#') { if (interpret_table_entry(line)) { - err_msg("cannot parse '%s'", line); + errmsg("cannot parse '%s'", line); goto out_close; } } @@ -378,12 +386,13 @@ int parse_devtable(const char *tbl_file) line = NULL; } - dbg_msg(1, "finished parsing"); + pr_debug("finished parsing\n"); fclose(f); return 0; out_close: fclose(f); + free(line); free_devtable_info(); return -1; } @@ -441,19 +450,19 @@ int override_attributes(struct stat *st, struct path_htbl_element *ph_elt, if (S_ISCHR(st->st_mode) || S_ISBLK(st->st_mode) || S_ISFIFO(st->st_mode)) - return err_msg("%s/%s both exists at UBIFS root at host, " + return errmsg("%s/%s both exists at UBIFS root at host, " "and is referred from the device table", strcmp(ph_elt->path, "/") ? ph_elt->path : "", nh_elt->name); if ((st->st_mode & S_IFMT) != (nh_elt->mode & S_IFMT)) - return err_msg("%s/%s is referred from the device table also exists in " + return errmsg("%s/%s is referred from the device table also exists in " "the UBIFS root directory at host, but the file type is " "different", strcmp(ph_elt->path, "/") ? ph_elt->path : "", nh_elt->name); - dbg_msg(3, "set UID %d, GID %d, mode %o for %s/%s as device table says", - nh_elt->uid, nh_elt->gid, nh_elt->mode, ph_elt->path, nh_elt->name); + pr_debug("set UID %d, GID %d, mode %o for %s/%s as device table says\n", + nh_elt->uid, nh_elt->gid, nh_elt->mode, ph_elt->path, nh_elt->name); st->st_uid = nh_elt->uid; st->st_gid = nh_elt->gid; diff --git a/ubifs-utils/mkfs.ubifs/mkfs.ubifs.h b/ubifs-utils/common/devtable.h index 5690984..97585f2 100644 --- a/ubifs-utils/mkfs.ubifs/mkfs.ubifs.h +++ b/ubifs-utils/common/devtable.h @@ -20,86 +20,8 @@ * Zoltan Sogor */ -#ifndef __MKFS_UBIFS_H__ -#define __MKFS_UBIFS_H__ - -#include <unistd.h> -#include <stdlib.h> -#include <stdio.h> -#include <limits.h> -#include <string.h> -#include <stdint.h> -#include <endian.h> -#include <byteswap.h> -#include <linux/types.h> -#include <linux/fs.h> - -#include <getopt.h> -#include <sys/types.h> -#include <sys/stat.h> -#include <sys/ioctl.h> -#include <fcntl.h> -#include <dirent.h> -#include <errno.h> -#include <libgen.h> -#include <ctype.h> -#include <uuid.h> -#include <sys/file.h> - -#ifdef WITH_CRYPTO -#include <openssl/rand.h> -#endif - -#include <mtd/ubifs-media.h> - -/* common.h requires the PROGRAM_NAME macro */ -#define PROGRAM_NAME "mkfs.ubifs" -#include "common.h" - -#include "libubi.h" -#include "defs.h" -#include "crc16.h" -#include "ubifs.h" -#include "key.h" -#include "lpt.h" -#include "compr.h" -#include "sign.h" - -/* - * Compression flags are duplicated so that compr.c can compile without ubifs.h. - * Here we make sure they are the same. - */ -#if MKFS_UBIFS_COMPR_NONE != UBIFS_COMPR_NONE -#error MKFS_UBIFS_COMPR_NONE != UBIFS_COMPR_NONE -#endif -#if MKFS_UBIFS_COMPR_LZO != UBIFS_COMPR_LZO -#error MKFS_UBIFS_COMPR_LZO != UBIFS_COMPR_LZO -#endif -#if MKFS_UBIFS_COMPR_ZLIB != UBIFS_COMPR_ZLIB -#error MKFS_UBIFS_COMPR_ZLIB != UBIFS_COMPR_ZLIB -#endif -#if MKFS_UBIFS_COMPR_ZSTD != UBIFS_COMPR_ZSTD -#error MKFS_UBIFS_COMPR_ZSTD != UBIFS_COMPR_ZSTD -#endif - -extern int verbose; -extern int debug_level; - -#define dbg_msg(lvl, fmt, ...) do {if (debug_level >= lvl) \ - printf("mkfs.ubifs: %s: " fmt "\n", __FUNCTION__, ##__VA_ARGS__); \ -} while(0) - -#define err_msg(fmt, ...) ({ \ - fprintf(stderr, "Error: " fmt "\n", ##__VA_ARGS__); \ - -1; \ -}) - -#define sys_err_msg(fmt, ...) ({ \ - int err_ = errno; \ - fprintf(stderr, "Error: " fmt "\n", ##__VA_ARGS__); \ - fprintf(stderr, " %s (error %d)\n", strerror(err_), err_); \ - -1; \ -}) +#ifndef __DEVTABLE_H__ +#define __DEVTABLE_H__ /** * struct path_htbl_element - an element of the path hash table. @@ -136,11 +58,8 @@ struct name_htbl_element { dev_t dev; }; -extern struct ubifs_info info_; - struct hashtable_itr; -int write_leb(int lnum, int len, void *buf); int parse_devtable(const char *tbl_file); struct path_htbl_element *devtbl_find_path(const char *path); struct name_htbl_element *devtbl_find_name(struct path_htbl_element *ph_elt, diff --git a/ubifs-utils/mkfs.ubifs/fscrypt.c b/ubifs-utils/common/fscrypt.c index b75bdf7..f39faa7 100644 --- a/ubifs-utils/mkfs.ubifs/fscrypt.c +++ b/ubifs-utils/common/fscrypt.c @@ -18,9 +18,12 @@ * David Oberhollenzer <david.oberhollenzer@sigma-star.at> */ -#define PROGRAM_NAME "mkfs.ubifs" -#include "fscrypt.h" +#include <endian.h> +#include "linux_types.h" +#include "fscrypt.h" +#include "defs.h" +#include "ubifs.h" static __u8 fscrypt_masterkey[FS_MAX_KEY_SIZE]; static struct cipher *fscrypt_cipher; @@ -33,7 +36,7 @@ unsigned char *calc_fscrypt_subkey(struct fscrypt_context *fctx) ret = derive_key_aes(fctx->nonce, fscrypt_masterkey, fscrypt_cipher->key_length, new_key); if (ret < 0) { - err_msg("derive_key_aes failed: %i\n", ret); + errmsg("derive_key_aes failed: %i\n", ret); free(new_key); new_key = NULL; @@ -86,7 +89,7 @@ unsigned int fscrypt_fname_encrypted_size(struct fscrypt_context *fctx, return round_up(ilen, padding); } -int encrypt_path(void **outbuf, void *data, unsigned int data_len, +int encrypt_path(void **outbuf, const void *data, unsigned int data_len, unsigned int max_namelen, struct fscrypt_context *fctx) { void *inbuf, *crypt_key; @@ -109,7 +112,7 @@ int encrypt_path(void **outbuf, void *data, unsigned int data_len, if (!crypt_key) { free(inbuf); free(*outbuf); - return err_msg("could not compute subkey"); + return errmsg("could not compute subkey"); } ret = fscrypt_cipher->encrypt_fname(inbuf, cryptlen, @@ -117,7 +120,7 @@ int encrypt_path(void **outbuf, void *data, unsigned int data_len, if (ret < 0) { free(inbuf); free(*outbuf); - return err_msg("could not encrypt filename"); + return errmsg("could not encrypt filename"); } free(crypt_key); @@ -142,7 +145,7 @@ int encrypt_data_node(struct fscrypt_context *fctx, unsigned int block_no, if (!crypt_key) { free(inbuf); free(outbuf); - return err_msg("could not compute subkey"); + return errmsg("could not compute subkey"); } ret = fscrypt_cipher->encrypt_block(inbuf, pad_len, @@ -152,7 +155,7 @@ int encrypt_data_node(struct fscrypt_context *fctx, unsigned int block_no, free(inbuf); free(outbuf); free(crypt_key); - return err_msg("encrypt_block returned %zi " + return errmsg("encrypt_block returned %zi " "instead of %zi", ret, pad_len); } @@ -182,11 +185,11 @@ static int parse_key_descriptor(const char *desc, __u8 *dst) for (i = 0; i < FS_KEY_DESCRIPTOR_SIZE; ++i) { if (!desc[i * 2] || !desc[i * 2 + 1]) { - err_msg("key descriptor '%s' is too short", desc); + errmsg("key descriptor '%s' is too short", desc); return -1; } if (!isxdigit(desc[i * 2]) || !isxdigit(desc[i * 2 + 1])) { - err_msg("invalid key descriptor '%s'", desc); + errmsg("invalid key descriptor '%s'", desc); return -1; } @@ -197,7 +200,7 @@ static int parse_key_descriptor(const char *desc, __u8 *dst) } if (desc[i * 2]) { - err_msg("key descriptor '%s' is too long", desc); + errmsg("key descriptor '%s' is too long", desc); return -1; } return 0; @@ -220,11 +223,11 @@ static int load_master_key(const char *key_file, struct cipher *fsc) goto fail; } if (keysize == 0) { - err_msg("loading key from '%s': file is empty", key_file); + errmsg("loading key from '%s': file is empty", key_file); goto fail; } if (keysize < fsc->key_length) { - err_msg("key '%s' is too short (at least %u bytes required)", + errmsg("key '%s' is too short (at least %u bytes required)", key_file, fsc->key_length); goto fail; } diff --git a/ubifs-utils/mkfs.ubifs/fscrypt.h b/ubifs-utils/common/fscrypt.h index 34b799c..4a073e9 100644 --- a/ubifs-utils/mkfs.ubifs/fscrypt.h +++ b/ubifs-utils/common/fscrypt.h @@ -21,9 +21,13 @@ #ifndef FSCRYPT_H #define FSCRYPT_H +#ifdef WITH_CRYPTO +#include <openssl/rand.h> +#endif +#include <assert.h> -#include "mkfs.ubifs.h" -#include <sys/types.h> +#include "compiler_attributes.h" +#include "ubifs.h" #include "crypto.h" #ifndef FS_KEY_DESCRIPTOR_SIZE @@ -77,7 +81,7 @@ struct fscrypt_context { __u8 flags; __u8 master_key_descriptor[FS_KEY_DESCRIPTOR_SIZE]; __u8 nonce[FS_KEY_DERIVATION_NONCE_SIZE]; -} __attribute__((packed)); +} __packed; /** * For encrypted symlinks, the ciphertext length is stored at the beginning @@ -86,7 +90,7 @@ struct fscrypt_context { struct fscrypt_symlink_data { __le16 len; char encrypted_path[1]; -} __attribute__((packed)); +} __packed; #ifndef FS_MAX_KEY_SIZE @@ -103,7 +107,7 @@ struct fscrypt_context *inherit_fscrypt_context(struct fscrypt_context *fctx); void free_fscrypt_context(struct fscrypt_context *fctx); unsigned int fscrypt_fname_encrypted_size(struct fscrypt_context *fctx, unsigned int ilen); -int encrypt_path(void **outbuf, void *data, unsigned int data_len, +int encrypt_path(void **outbuf, const void *data, unsigned int data_len, unsigned int max_namelen, struct fscrypt_context *fctx); int encrypt_data_node(struct fscrypt_context *fctx, unsigned int block_no, struct ubifs_data_node *dn, size_t length); @@ -131,11 +135,12 @@ static inline void free_fscrypt_context(struct fscrypt_context *fctx) { (void)fctx; - assert(0); + assert(!fctx); } -static inline int encrypt_path(void **outbuf, void *data, unsigned int data_len, - unsigned int max_namelen, struct fscrypt_context *fctx) +static inline int encrypt_path(void **outbuf, const void *data, + unsigned int data_len, unsigned int max_namelen, + struct fscrypt_context *fctx) { (void)outbuf; (void)data; @@ -163,7 +168,7 @@ static inline struct fscrypt_context *inherit_fscrypt_context(struct fscrypt_con { (void)fctx; - assert(0); + assert(!fctx); return NULL; } #endif /* WITH_CRYPTO */ diff --git a/ubifs-utils/mkfs.ubifs/hashtable/hashtable.c b/ubifs-utils/common/hashtable/hashtable.c index c1f99ed..af7fed9 100644 --- a/ubifs-utils/mkfs.ubifs/hashtable/hashtable.c +++ b/ubifs-utils/common/hashtable/hashtable.c @@ -1,15 +1,15 @@ /* Copyright (C) 2004 Christopher Clark <firstname.lastname@cl.cam.ac.uk> */ -#define PROGRAM_NAME "hashtable" - -#include "common.h" -#include "hashtable.h" -#include "hashtable_private.h" #include <stdlib.h> #include <stdio.h> #include <string.h> #include <math.h> +#include "ubifs.h" +#include "defs.h" +#include "hashtable.h" +#include "hashtable_private.h" + /* Credit for primes table: Aaron Krowne http://br.endernet.org/~akrowne/ diff --git a/ubifs-utils/mkfs.ubifs/hashtable/hashtable.h b/ubifs-utils/common/hashtable/hashtable.h index c0b0acd..c0b0acd 100644 --- a/ubifs-utils/mkfs.ubifs/hashtable/hashtable.h +++ b/ubifs-utils/common/hashtable/hashtable.h diff --git a/ubifs-utils/mkfs.ubifs/hashtable/hashtable_itr.c b/ubifs-utils/common/hashtable/hashtable_itr.c index d102453..d102453 100644 --- a/ubifs-utils/mkfs.ubifs/hashtable/hashtable_itr.c +++ b/ubifs-utils/common/hashtable/hashtable_itr.c diff --git a/ubifs-utils/mkfs.ubifs/hashtable/hashtable_itr.h b/ubifs-utils/common/hashtable/hashtable_itr.h index 5c94a04..5c94a04 100644 --- a/ubifs-utils/mkfs.ubifs/hashtable/hashtable_itr.h +++ b/ubifs-utils/common/hashtable/hashtable_itr.h diff --git a/ubifs-utils/mkfs.ubifs/hashtable/hashtable_private.h b/ubifs-utils/common/hashtable/hashtable_private.h index 3a558e6..3a558e6 100644 --- a/ubifs-utils/mkfs.ubifs/hashtable/hashtable_private.h +++ b/ubifs-utils/common/hashtable/hashtable_private.h diff --git a/ubifs-utils/common/hexdump.c b/ubifs-utils/common/hexdump.c new file mode 100644 index 0000000..7ac4694 --- /dev/null +++ b/ubifs-utils/common/hexdump.c @@ -0,0 +1,218 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * lib/hexdump.c + */ + +#include <stdio.h> + +#include "linux_types.h" +#include "defs.h" + +#define __get_unaligned_t(type, ptr) ({ \ + const struct { type x; } __packed *__pptr = (typeof(__pptr))(ptr); \ + __pptr->x; \ +}) + +#define get_unaligned(ptr) __get_unaligned_t(typeof(*(ptr)), (ptr)) + +const char hex_asc[] = "0123456789abcdef"; + +#define hex_asc_lo(x) hex_asc[((x) & 0x0f)] +#define hex_asc_hi(x) hex_asc[((x) & 0xf0) >> 4] + +void print_hex_dump(const char *prefix_str, int prefix_type, + int rowsize, int groupsize, + const void *buf, size_t len, bool ascii); +/** + * hex_dump_to_buffer - convert a blob of data to "hex ASCII" in memory + * @buf: data blob to dump + * @len: number of bytes in the @buf + * @rowsize: number of bytes to print per line; must be 16 or 32 + * @groupsize: number of bytes to print at a time (1, 2, 4, 8; default = 1) + * @linebuf: where to put the converted data + * @linebuflen: total size of @linebuf, including space for terminating NUL + * @ascii: include ASCII after the hex output + * + * hex_dump_to_buffer() works on one "line" of output at a time, i.e., + * 16 or 32 bytes of input data converted to hex + ASCII output. + * + * Given a buffer of u8 data, hex_dump_to_buffer() converts the input data + * to a hex + ASCII dump at the supplied memory location. + * The converted output is always NUL-terminated. + * + * E.g.: + * hex_dump_to_buffer(frame->data, frame->len, 16, 1, + * linebuf, sizeof(linebuf), true); + * + * example output buffer: + * 40 41 42 43 44 45 46 47 48 49 4a 4b 4c 4d 4e 4f @ABCDEFGHIJKLMNO + * + * Return: + * The amount of bytes placed in the buffer without terminating NUL. If the + * output was truncated, then the return value is the number of bytes + * (excluding the terminating NUL) which would have been written to the final + * string if enough space had been available. + */ +static int hex_dump_to_buffer(const void *buf, size_t len, int rowsize, + int groupsize, char *linebuf, size_t linebuflen, + bool ascii) +{ + const u8 *ptr = buf; + int ngroups; + u8 ch; + int j, lx = 0; + int ascii_column; + int ret; + + if (rowsize != 16 && rowsize != 32) + rowsize = 16; + + if (len > rowsize) /* limit to one line at a time */ + len = rowsize; + if (!is_power_of_2(groupsize) || groupsize > 8) + groupsize = 1; + if ((len % groupsize) != 0) /* no mixed size output */ + groupsize = 1; + + ngroups = len / groupsize; + ascii_column = rowsize * 2 + rowsize / groupsize + 1; + + if (!linebuflen) + goto overflow1; + + if (!len) + goto nil; + + if (groupsize == 8) { + const u64 *ptr8 = buf; + + for (j = 0; j < ngroups; j++) { + ret = snprintf(linebuf + lx, linebuflen - lx, + "%s%16.16llx", j ? " " : "", + get_unaligned(ptr8 + j)); + if (ret >= linebuflen - lx) + goto overflow1; + lx += ret; + } + } else if (groupsize == 4) { + const u32 *ptr4 = buf; + + for (j = 0; j < ngroups; j++) { + ret = snprintf(linebuf + lx, linebuflen - lx, + "%s%8.8x", j ? " " : "", + get_unaligned(ptr4 + j)); + if (ret >= linebuflen - lx) + goto overflow1; + lx += ret; + } + } else if (groupsize == 2) { + const u16 *ptr2 = buf; + + for (j = 0; j < ngroups; j++) { + ret = snprintf(linebuf + lx, linebuflen - lx, + "%s%4.4x", j ? " " : "", + get_unaligned(ptr2 + j)); + if (ret >= linebuflen - lx) + goto overflow1; + lx += ret; + } + } else { + for (j = 0; j < len; j++) { + if (linebuflen < lx + 2) + goto overflow2; + ch = ptr[j]; + linebuf[lx++] = hex_asc_hi(ch); + if (linebuflen < lx + 2) + goto overflow2; + linebuf[lx++] = hex_asc_lo(ch); + if (linebuflen < lx + 2) + goto overflow2; + linebuf[lx++] = ' '; + } + if (j) + lx--; + } + if (!ascii) + goto nil; + + while (lx < ascii_column) { + if (linebuflen < lx + 2) + goto overflow2; + linebuf[lx++] = ' '; + } + for (j = 0; j < len; j++) { + if (linebuflen < lx + 2) + goto overflow2; + ch = ptr[j]; + linebuf[lx++] = (isascii(ch) && isprint(ch)) ? ch : '.'; + } +nil: + linebuf[lx] = '\0'; + return lx; +overflow2: + linebuf[lx++] = '\0'; +overflow1: + return ascii ? ascii_column + len : (groupsize * 2 + 1) * ngroups - 1; +} + +/** + * print_hex_dump - print a text hex dump to syslog for a binary blob of data + * @prefix_str: string to prefix each line with; + * caller supplies trailing spaces for alignment if desired + * @prefix_type: controls whether prefix of an offset, address, or none + * is printed (%DUMP_PREFIX_OFFSET, %DUMP_PREFIX_ADDRESS, %DUMP_PREFIX_NONE) + * @rowsize: number of bytes to print per line; must be 16 or 32 + * @groupsize: number of bytes to print at a time (1, 2, 4, 8; default = 1) + * @buf: data blob to dump + * @len: number of bytes in the @buf + * @ascii: include ASCII after the hex output + * + * Given a buffer of u8 data, print_hex_dump() prints a hex + ASCII dump + * to the kernel log at the specified kernel log level, with an optional + * leading prefix. + * + * print_hex_dump() works on one "line" of output at a time, i.e., + * 16 or 32 bytes of input data converted to hex + ASCII output. + * print_hex_dump() iterates over the entire input @buf, breaking it into + * "line size" chunks to format and print. + * + * E.g.: + * print_hex_dump(KERN_DEBUG, "raw data: ", DUMP_PREFIX_ADDRESS, + * 16, 1, frame->data, frame->len, true); + * + * Example output using %DUMP_PREFIX_OFFSET and 1-byte mode: + * 0009ab42: 40 41 42 43 44 45 46 47 48 49 4a 4b 4c 4d 4e 4f @ABCDEFGHIJKLMNO + * Example output using %DUMP_PREFIX_ADDRESS and 4-byte mode: + * ffffffff88089af0: 73727170 77767574 7b7a7978 7f7e7d7c pqrstuvwxyz{|}~. + */ +void print_hex_dump(const char *prefix_str, int prefix_type, + int rowsize, int groupsize, + const void *buf, size_t len, bool ascii) +{ + const u8 *ptr = buf; + int i, linelen, remaining = len; + char linebuf[32 * 3 + 2 + 32 + 1]; + + if (rowsize != 16 && rowsize != 32) + rowsize = 16; + + for (i = 0; i < len; i += rowsize) { + linelen = min(remaining, rowsize); + remaining -= rowsize; + + hex_dump_to_buffer(ptr + i, linelen, rowsize, groupsize, + linebuf, sizeof(linebuf), ascii); + + switch (prefix_type) { + case DUMP_PREFIX_ADDRESS: + printf("%s%p: %s\n", prefix_str, ptr + i, linebuf); + break; + case DUMP_PREFIX_OFFSET: + printf("%s%.8x: %s\n", prefix_str, i, linebuf); + break; + default: + printf("%s%s\n", prefix_str, linebuf); + break; + } + } +} diff --git a/ubifs-utils/common/kmem.c b/ubifs-utils/common/kmem.c new file mode 100644 index 0000000..e926a13 --- /dev/null +++ b/ubifs-utils/common/kmem.c @@ -0,0 +1,64 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Simple memory interface + */ + +#include "compiler_attributes.h" +#include "linux_types.h" +#include "kmem.h" +#include "defs.h" + +static void *kmem_alloc(size_t size) +{ + void *ptr = malloc(size); + + if (ptr == NULL) + sys_errmsg("malloc failed (%d bytes)", (int)size); + return ptr; +} + +static void *kmem_zalloc(size_t size) +{ + void *ptr = kmem_alloc(size); + + if (!ptr) + return ptr; + + memset(ptr, 0, size); + return ptr; +} + +void *kmalloc(size_t size, gfp_t flags) +{ + if (flags & __GFP_ZERO) + return kmem_zalloc(size); + return kmem_alloc(size); +} + +void *krealloc(void *ptr, size_t new_size, __unused gfp_t flags) +{ + ptr = realloc(ptr, new_size); + if (ptr == NULL) + sys_errmsg("realloc failed (%d bytes)", (int)new_size); + return ptr; +} + +void *kmalloc_array(size_t n, size_t size, gfp_t flags) +{ + size_t bytes; + + if (unlikely(check_mul_overflow(n, size, &bytes))) + return NULL; + return kmalloc(bytes, flags); +} + +void *kmemdup(const void *src, size_t len, gfp_t gfp) +{ + void *p; + + p = kmalloc(len, gfp); + if (p) + memcpy(p, src, len); + + return p; +} diff --git a/ubifs-utils/common/kmem.h b/ubifs-utils/common/kmem.h new file mode 100644 index 0000000..9fe2a36 --- /dev/null +++ b/ubifs-utils/common/kmem.h @@ -0,0 +1,56 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (c) 2008 Silicon Graphics, Inc. + * All Rights Reserved. + */ +#ifndef __KMEM_H__ +#define __KMEM_H__ + +#include <stdlib.h> + +typedef unsigned int gfp_t; + +#define GFP_KERNEL 0 +#define GFP_NOFS 0 +#define __GFP_NOWARN 0 +#define __GFP_ZERO 1 + +#define vmalloc(size) malloc(size) +#define vfree(ptr) free(ptr) + +extern void *kmalloc(size_t, gfp_t); +extern void *krealloc(void *, size_t, __attribute__((unused)) gfp_t); +extern void *kmalloc_array(size_t, size_t, gfp_t); +extern void *kmemdup(const void *src, size_t len, gfp_t gfp); + +static inline void kfree(const void *ptr) +{ + free((void *)ptr); +} + +static inline void kvfree(const void *ptr) +{ + kfree(ptr); +} + +static inline void *kvmalloc(size_t size, gfp_t flags) +{ + return kmalloc(size, flags); +} + +static inline void *kzalloc(size_t size, gfp_t flags) +{ + return kmalloc(size, flags | __GFP_ZERO); +} + +static inline void *__vmalloc(unsigned long size, gfp_t gfp_mask) +{ + return kmalloc(size, gfp_mask); +} + +static inline void *kcalloc(size_t n, size_t size, gfp_t flags) +{ + return kmalloc_array(n, size, flags | __GFP_ZERO); +} + +#endif diff --git a/ubifs-utils/common/linux_err.h b/ubifs-utils/common/linux_err.h new file mode 100644 index 0000000..5c6ddc3 --- /dev/null +++ b/ubifs-utils/common/linux_err.h @@ -0,0 +1,62 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef _LINUX_ERR_H +#define _LINUX_ERR_H + +/* Adapted from include/linux/err.h */ + +#include <stdbool.h> + +/* + * Kernel pointers have redundant information, so we can use a + * scheme where we can return either an error code or a normal + * pointer with the same return value. + * + * This should be a per-architecture thing, to allow different + * error and pointer decisions. + */ +#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 long PTR_ERR(const void *ptr) +{ + return (long) ptr; +} + +static inline bool IS_ERR(const void *ptr) +{ + return IS_ERR_VALUE((unsigned long)ptr); +} + +static inline bool IS_ERR_OR_NULL(const void *ptr) +{ + return !ptr || IS_ERR_VALUE((unsigned long)ptr); +} + +/** + * ERR_CAST - Explicitly cast an error-valued pointer to another pointer type + * @ptr: The pointer to cast. + * + * Explicitly cast an error-valued pointer to another pointer type in such a + * way as to make it clear that's what's going on. + */ +static inline void * ERR_CAST(const void *ptr) +{ + /* cast away the const */ + return (void *) ptr; +} + +static inline int PTR_ERR_OR_ZERO(const void *ptr) +{ + if (IS_ERR(ptr)) + return PTR_ERR(ptr); + else + return 0; +} + +#endif /* _LINUX_ERR_H */ diff --git a/ubifs-utils/common/linux_types.h b/ubifs-utils/common/linux_types.h new file mode 100644 index 0000000..ebf9ecd --- /dev/null +++ b/ubifs-utils/common/linux_types.h @@ -0,0 +1,92 @@ +#ifndef __LINUX_TYPES_H__ +#define __LINUX_TYPES_H__ + +#include <linux/types.h> +#include <sys/types.h> +#include <byteswap.h> +#include <stdint.h> +#include <unistd.h> + +#include "compiler_attributes.h" + +typedef __u8 u8; +typedef __u16 u16; +typedef __u32 u32; +typedef __u64 u64; + +typedef __s64 time64_t; + +struct qstr { + const char *name; + size_t len; +}; + +struct fscrypt_name { + struct qstr disk_name; +}; + +#define fname_name(p) ((p)->disk_name.name) +#define fname_len(p) ((p)->disk_name.len) + +#define S_IRUGO (S_IRUSR|S_IRGRP|S_IROTH) +#define S_IXUGO (S_IXUSR|S_IXGRP|S_IXOTH) + +#define t16(x) ({ \ + uint16_t __b = (x); \ + (__LITTLE_ENDIAN==__BYTE_ORDER) ? __b : bswap_16(__b); \ +}) + +#define t32(x) ({ \ + uint32_t __b = (x); \ + (__LITTLE_ENDIAN==__BYTE_ORDER) ? __b : bswap_32(__b); \ +}) + +#define t64(x) ({ \ + uint64_t __b = (x); \ + (__LITTLE_ENDIAN==__BYTE_ORDER) ? __b : bswap_64(__b); \ +}) + +#define cpu_to_le16(x) ((__le16){t16(x)}) +#define cpu_to_le32(x) ((__le32){t32(x)}) +#define cpu_to_le64(x) ((__le64){t64(x)}) + +#define le16_to_cpu(x) (t16((x))) +#define le32_to_cpu(x) (t32((x))) +#define le64_to_cpu(x) (t64((x))) + +#define check_mul_overflow(a, b, d) ({ \ + typeof(a) __a = (a); \ + typeof(b) __b = (b); \ + typeof(d) __d = (d); \ + (void) (&__a == &__b); \ + (void) (&__a == __d); \ + __builtin_mul_overflow(__a, __b, __d); \ +}) + +static inline __must_check size_t array_size(size_t a, size_t b) +{ + size_t bytes; + if (check_mul_overflow(a, b, &bytes)) + return SIZE_MAX; + + return bytes; +} + +static inline int int_log2(unsigned int arg) +{ + int l = 0; + + arg >>= 1; + while (arg) { + l++; + arg >>= 1; + } + return l; +} + +#undef PAGE_SIZE +#define PAGE_SIZE (getpagesize()) +#undef PAGE_SHIFT +#define PAGE_SHIFT (int_log2(PAGE_SIZE)) + +#endif diff --git a/ubifs-utils/common/mutex.h b/ubifs-utils/common/mutex.h new file mode 100644 index 0000000..4bf018b --- /dev/null +++ b/ubifs-utils/common/mutex.h @@ -0,0 +1,18 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef __LINUX_MUTEX_H_ +#define __LINUX_MUTEX_H_ + +#include <pthread.h> + +struct mutex { + pthread_mutex_t lock; +}; + +#define mutex_init(x) pthread_mutex_init(&(x)->lock, NULL) + +#define mutex_lock(x) pthread_mutex_lock(&(x)->lock) +#define mutex_lock_nested(x, c) pthread_mutex_lock(&(x)->lock) +#define mutex_unlock(x) pthread_mutex_unlock(&(x)->lock) +#define mutex_is_locked(x) (pthread_mutex_trylock(&(x)->lock) == EBUSY) + +#endif diff --git a/ubifs-utils/common/rwsem.h b/ubifs-utils/common/rwsem.h new file mode 100644 index 0000000..3761724 --- /dev/null +++ b/ubifs-utils/common/rwsem.h @@ -0,0 +1,19 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef __LINUX_RWSEM_H_ +#define __LINUX_RWSEM_H_ + +#include <pthread.h> + +struct rw_semaphore { + pthread_mutex_t lock; +}; + +#define init_rwsem(x) pthread_mutex_init(&(x)->lock, NULL) + +#define down_read(x) pthread_mutex_lock(&(x)->lock) +#define down_write(x) pthread_mutex_lock(&(x)->lock) +#define up_read(x) pthread_mutex_unlock(&(x)->lock) +#define up_write(x) pthread_mutex_unlock(&(x)->lock) +#define down_write_trylock(x) (pthread_mutex_trylock(&(x)->lock) == 0) + +#endif diff --git a/ubifs-utils/mkfs.ubifs/sign.c b/ubifs-utils/common/sign.c index b7ad7ef..032a6ac 100644 --- a/ubifs-utils/mkfs.ubifs/sign.c +++ b/ubifs-utils/common/sign.c @@ -17,9 +17,7 @@ * Author: Sascha Hauer */ -#include "mkfs.ubifs.h" -#include "common.h" - +#include <string.h> #include <openssl/evp.h> #include <openssl/opensslv.h> #include <openssl/bio.h> @@ -30,16 +28,17 @@ #include <openssl/conf.h> #include <err.h> +#include "linux_types.h" +#include "sign.h" +#include "ubifs.h" +#include "defs.h" + +extern struct ubifs_info info_; static struct ubifs_info *c = &info_; EVP_MD_CTX *hash_md; const EVP_MD *md; -int authenticated(void) -{ - return c->hash_algo_name != NULL; -} - static int match_string(const char * const *array, size_t n, const char *string) { int index; @@ -107,14 +106,13 @@ static void drain_openssl_errors(void) #define ssl_err_msg(fmt, ...) ({ \ display_openssl_errors(__LINE__); \ - err_msg(fmt, ## __VA_ARGS__); \ + errmsg(fmt, ## __VA_ARGS__); \ -1; \ }) static const char *key_pass; -static int pem_pw_cb(char *buf, int len, __attribute__((unused)) int w, - __attribute__((unused)) void *v) +static int pem_pw_cb(char *buf, int len, __unused int w, __unused void *v) { int pwlen; @@ -212,9 +210,9 @@ static X509 *read_x509(const char *x509_name) n = BIO_read(b, buf, 2); if (n != 2) { if (BIO_should_retry(b)) - err_msg("%s: Read wanted retry", x509_name); + errmsg("%s: Read wanted retry", x509_name); if (n >= 0) - err_msg("%s: Short read", x509_name); + errmsg("%s: Short read", x509_name); goto out; } @@ -239,36 +237,32 @@ out: return x509; } -int sign_superblock_node(void *node) +int hash_sign_node(const char *auth_key_filename, const char *auth_cert_filename, + void *buf, int *len, void *outbuf) { EVP_PKEY *private_key; CMS_ContentInfo *cms = NULL; X509 *cert = NULL; BIO *bd, *bm; void *obuf; - long len; int ret; void *pret; - struct ubifs_sig_node *sig = node + UBIFS_SB_NODE_SZ; - - if (!authenticated()) - return 0; ERR_load_crypto_strings(); ERR_clear_error(); key_pass = getenv("MKFS_UBIFS_SIGN_PIN"); - bm = BIO_new_mem_buf(node, UBIFS_SB_NODE_SZ); + bm = BIO_new_mem_buf(buf, UBIFS_SB_NODE_SZ); - private_key = read_private_key(c->auth_key_filename, &cert); + private_key = read_private_key(auth_key_filename, &cert); if (!private_key) return -1; if (!cert) { - if (!c->auth_cert_filename) - return err_msg("authentication certificate not provided (--auth-cert)"); - cert = read_x509(c->auth_cert_filename); + if (!auth_cert_filename) + return errmsg("authentication certificate not provided (--auth-cert)"); + cert = read_x509(auth_cert_filename); } if (!cert) @@ -281,31 +275,27 @@ int sign_superblock_node(void *node) CMS_NOCERTS | CMS_PARTIAL | CMS_BINARY | CMS_DETACHED | CMS_STREAM); if (!cms) - return err_msg("CMS_sign failed"); + return errmsg("CMS_sign failed"); pret = CMS_add1_signer(cms, cert, private_key, md, CMS_NOCERTS | CMS_BINARY | CMS_NOSMIMECAP | CMS_NOATTR); if (!pret) - return err_msg("CMS_add1_signer failed"); + return errmsg("CMS_add1_signer failed"); ret = CMS_final(cms, bm, NULL, CMS_NOCERTS | CMS_BINARY); if (!ret) - return err_msg("CMS_final failed"); + return errmsg("CMS_final failed"); bd = BIO_new(BIO_s_mem()); ret = i2d_CMS_bio_stream(bd, cms, NULL, 0); if (!ret) - return err_msg("i2d_CMS_bio_stream failed"); - - len = BIO_get_mem_data(bd, &obuf); + return errmsg("i2d_CMS_bio_stream failed"); - sig->type = UBIFS_SIGNATURE_TYPE_PKCS7; - sig->len = cpu_to_le32(len); - sig->ch.node_type = UBIFS_SIG_NODE; + *len = BIO_get_mem_data(bd, &obuf); - memcpy(sig + 1, obuf, len); + memcpy(outbuf, obuf, *len); BIO_free(bd); BIO_free(bm); @@ -313,97 +303,93 @@ int sign_superblock_node(void *node) return 0; } -/** - * ubifs_node_calc_hash - calculate the hash of a UBIFS node - * @c: UBIFS file-system description object - * @node: the node to calculate a hash for - * @hash: the returned hash - */ -void ubifs_node_calc_hash(const void *node, uint8_t *hash) +int hash_digest(const void *buf, unsigned int len, uint8_t *hash) { - const struct ubifs_ch *ch = node; + int err; unsigned int md_len; - if (!authenticated()) - return; + err = EVP_DigestInit_ex(hash_md, md, NULL); + if (!err) + return errmsg("Init hash digest failed"); + err = EVP_DigestUpdate(hash_md, buf, len); + if (!err) + return errmsg("Update hash digest failed"); + err = EVP_DigestFinal_ex(hash_md, hash, &md_len); + if (!err) + return errmsg("Finalize hash digest failed"); - EVP_DigestInit_ex(hash_md, md, NULL); - EVP_DigestUpdate(hash_md, node, le32_to_cpu(ch->len)); - EVP_DigestFinal_ex(hash_md, hash, &md_len); + return 0; } -/** - * mst_node_calc_hash - calculate the hash of a UBIFS master node - * @c: UBIFS file-system description object - * @node: the node to calculate a hash for - * @hash: the returned hash - */ -void mst_node_calc_hash(const void *node, uint8_t *hash) +int hash_digest_init(void) { - unsigned int md_len; + int err; - if (!authenticated()) - return; + err = EVP_DigestInit_ex(hash_md, md, NULL); + if (!err) + return errmsg("Init hash digest failed"); - EVP_DigestInit_ex(hash_md, md, NULL); - EVP_DigestUpdate(hash_md, node + sizeof(struct ubifs_ch), - UBIFS_MST_NODE_SZ - sizeof(struct ubifs_ch)); - EVP_DigestFinal_ex(hash_md, hash, &md_len); + return 0; } -void hash_digest_init(void) +int hash_digest_update(const void *buf, int len) { - if (!authenticated()) - return; + int err; - EVP_DigestInit_ex(hash_md, md, NULL); -} + err = EVP_DigestUpdate(hash_md, buf, len); + if (!err) + return errmsg("Update hash digest failed"); -void hash_digest_update(const void *buf, int len) -{ - if (!authenticated()) - return; - - EVP_DigestUpdate(hash_md, buf, len); + return 0; } -void hash_digest_final(void *hash, unsigned int *len) +int hash_digest_final(void *hash) { - if (!authenticated()) - return; + int err; + unsigned int md_len; + + err = EVP_DigestFinal_ex(hash_md, hash, &md_len); + if (!err) + return errmsg("Finalize hash digest failed"); - EVP_DigestFinal_ex(hash_md, hash, len); + return 0; } -int init_authentication(void) +int init_authentication(const char *algo_name, int *hash_len, int *hash_algo) { - int hash_algo; - - if (!c->auth_key_filename && !c->auth_cert_filename && !c->hash_algo_name) - return 0; - - if (!c->auth_key_filename) - return err_msg("authentication key not given (--auth-key)"); - - if (!c->hash_algo_name) - return err_msg("Hash algorithm not given (--hash-algo)"); - - OPENSSL_no_config(); + OPENSSL_config(NULL); OpenSSL_add_all_algorithms(); ERR_load_crypto_strings(); md = EVP_get_digestbyname(c->hash_algo_name); if (!md) - return err_msg("Unknown message digest %s", c->hash_algo_name); + return errmsg("Unknown message digest %s", c->hash_algo_name); hash_md = EVP_MD_CTX_create(); - c->hash_len = EVP_MD_size(md); - - hash_algo = match_string(hash_algo_name, HASH_ALGO__LAST, c->hash_algo_name); - if (hash_algo < 0) - return err_msg("Unsupported message digest %s", c->hash_algo_name); + if (!hash_md) + return errmsg("Cannot create md ctx"); + + *hash_len = EVP_MD_size(md); + if (*hash_len < 0) { + EVP_MD_CTX_destroy(hash_md); + hash_md = NULL; + return errmsg("Cannot init hash len"); + } - c->hash_algo = hash_algo; + *hash_algo = match_string(hash_algo_name, HASH_ALGO__LAST, algo_name); + if (*hash_algo < 0) { + EVP_MD_CTX_destroy(hash_md); + hash_md = NULL; + return errmsg("Unsupported message digest %s", algo_name); + } return 0; } + +void exit_authentication(void) +{ + if (hash_md) { + EVP_MD_CTX_destroy(hash_md); + hash_md = NULL; + } +} diff --git a/ubifs-utils/common/sign.h b/ubifs-utils/common/sign.h new file mode 100644 index 0000000..f49c76a --- /dev/null +++ b/ubifs-utils/common/sign.h @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2018 Pengutronix + * + * 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 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 + * + * Author: Sascha Hauer + */ + +#ifndef __UBIFS_SIGN_H__ +#define __UBIFS_SIGN_H__ + +#include <openssl/evp.h> + +struct shash_desc { + void *ctx; +}; + +int hash_digest(const void *buf, unsigned int len, uint8_t *hash); +int hash_digest_init(void); +int hash_digest_update(const void *buf, int len); +int hash_digest_final(void *hash); +int init_authentication(const char *algo_name, int *hash_len, int *hash_algo); +void exit_authentication(void); +void mst_node_calc_hash(const void *node, uint8_t *hash); +int hash_sign_node(const char *auth_key_filename, const char *auth_cert_filename, + void *buf, int *len, void *outbuf); + +#endif /* __UBIFS_SIGN_H__ */ diff --git a/ubifs-utils/common/sort.c b/ubifs-utils/common/sort.c new file mode 100644 index 0000000..d585836 --- /dev/null +++ b/ubifs-utils/common/sort.c @@ -0,0 +1,274 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * A fast, small, non-recursive O(n log n) sort for the Linux kernel + * + * This performs n*log2(n) + 0.37*n + o(n) comparisons on average, + * and 1.5*n*log2(n) + O(n) in the (very contrived) worst case. + * + * Glibc qsort() manages n*log2(n) - 1.26*n for random inputs (1.63*n + * better) at the expense of stack usage and much larger code to avoid + * quicksort's O(n^2) worst case. + */ + +#include <stdio.h> +#include <stdbool.h> +#include <linux/types.h> + +#include "sort.h" +#include "linux_types.h" + +/** + * is_aligned - is this pointer & size okay for word-wide copying? + * @base: pointer to data + * @size: size of each element + * @align: required alignment (typically 4 or 8) + * + * Returns true if elements can be copied using word loads and stores. + * The size must be a multiple of the alignment. + * + * For some reason, gcc doesn't know to optimize "if (a & mask || b & mask)" + * to "if ((a | b) & mask)", so we do that by hand. + */ +__const __always_inline +static bool is_aligned(const void *base, size_t size, unsigned char align) +{ + unsigned char lsbits = (unsigned char)size; + + (void)base; + return (lsbits & (align - 1)) == 0; +} + +/** + * swap_words_32 - swap two elements in 32-bit chunks + * @a: pointer to the first element to swap + * @b: pointer to the second element to swap + * @n: element size (must be a multiple of 4) + * + * Exchange the two objects in memory. This exploits base+index addressing, + * which basically all CPUs have, to minimize loop overhead computations. + * + * For some reason, on x86 gcc 7.3.0 adds a redundant test of n at the + * bottom of the loop, even though the zero flag is still valid from the + * subtract (since the intervening mov instructions don't alter the flags). + * Gcc 8.1.0 doesn't have that problem. + */ +static void swap_words_32(void *a, void *b, size_t n) +{ + do { + u32 t = *(u32 *)(a + (n -= 4)); + *(u32 *)(a + n) = *(u32 *)(b + n); + *(u32 *)(b + n) = t; + } while (n); +} + +/** + * swap_words_64 - swap two elements in 64-bit chunks + * @a: pointer to the first element to swap + * @b: pointer to the second element to swap + * @n: element size (must be a multiple of 8) + * + * Exchange the two objects in memory. This exploits base+index + * addressing, which basically all CPUs have, to minimize loop overhead + * computations. + * + * We'd like to use 64-bit loads if possible. If they're not, emulating + * one requires base+index+4 addressing which x86 has but most other + * processors do not. + */ +static void swap_words_64(void *a, void *b, size_t n) +{ + do { + u64 t = *(u64 *)(a + (n -= 8)); + *(u64 *)(a + n) = *(u64 *)(b + n); + *(u64 *)(b + n) = t; + } while (n); +} + +/** + * swap_bytes - swap two elements a byte at a time + * @a: pointer to the first element to swap + * @b: pointer to the second element to swap + * @n: element size + * + * This is the fallback if alignment doesn't allow using larger chunks. + */ +static void swap_bytes(void *a, void *b, size_t n) +{ + do { + char t = ((char *)a)[--n]; + ((char *)a)[n] = ((char *)b)[n]; + ((char *)b)[n] = t; + } while (n); +} + +/* + * The values are arbitrary as long as they can't be confused with + * a pointer, but small integers make for the smallest compare + * instructions. + */ +#define SWAP_WORDS_64 (swap_r_func_t)0 +#define SWAP_WORDS_32 (swap_r_func_t)1 +#define SWAP_BYTES (swap_r_func_t)2 +#define SWAP_WRAPPER (swap_r_func_t)3 + +struct wrapper { + cmp_func_t cmp; + swap_func_t swap; +}; + +/* + * The function pointer is last to make tail calls most efficient if the + * compiler decides not to inline this function. + */ +static void do_swap(void *a, void *b, size_t size, swap_r_func_t swap_func, const void *priv) +{ + if (swap_func == SWAP_WRAPPER) { + ((const struct wrapper *)priv)->swap(a, b, (int)size); + return; + } + + if (swap_func == SWAP_WORDS_64) + swap_words_64(a, b, size); + else if (swap_func == SWAP_WORDS_32) + swap_words_32(a, b, size); + else if (swap_func == SWAP_BYTES) + swap_bytes(a, b, size); + else + swap_func(a, b, (int)size, priv); +} + +#define _CMP_WRAPPER ((cmp_r_func_t)0L) + +static int do_cmp(const void *a, const void *b, cmp_r_func_t cmp, const void *priv) +{ + if (cmp == _CMP_WRAPPER) + return ((const struct wrapper *)priv)->cmp(a, b); + return cmp(a, b, priv); +} + +/** + * parent - given the offset of the child, find the offset of the parent. + * @i: the offset of the heap element whose parent is sought. Non-zero. + * @lsbit: a precomputed 1-bit mask, equal to "size & -size" + * @size: size of each element + * + * In terms of array indexes, the parent of element j = @i/@size is simply + * (j-1)/2. But when working in byte offsets, we can't use implicit + * truncation of integer divides. + * + * Fortunately, we only need one bit of the quotient, not the full divide. + * @size has a least significant bit. That bit will be clear if @i is + * an even multiple of @size, and set if it's an odd multiple. + * + * Logically, we're doing "if (i & lsbit) i -= size;", but since the + * branch is unpredictable, it's done with a bit of clever branch-free + * code instead. + */ +__const __always_inline +static size_t parent(size_t i, unsigned int lsbit, size_t size) +{ + i -= size; + i -= size & -(i & lsbit); + return i / 2; +} + +/** + * sort_r - sort an array of elements + * @base: pointer to data to sort + * @num: number of elements + * @size: size of each element + * @cmp_func: pointer to comparison function + * @swap_func: pointer to swap function or NULL + * @priv: third argument passed to comparison function + * + * This function does a heapsort on the given array. You may provide + * a swap_func function if you need to do something more than a memory + * copy (e.g. fix up pointers or auxiliary data), but the built-in swap + * avoids a slow retpoline and so is significantly faster. + * + * Sorting time is O(n log n) both on average and worst-case. While + * quicksort is slightly faster on average, it suffers from exploitable + * O(n*n) worst-case behavior and extra memory requirements that make + * it less suitable for kernel use. + */ +void sort_r(void *base, size_t num, size_t size, + cmp_r_func_t cmp_func, + swap_r_func_t swap_func, + const void *priv) +{ + /* pre-scale counters for performance */ + size_t n = num * size, a = (num/2) * size; + const unsigned int lsbit = size & -size; /* Used to find parent */ + + if (!a) /* num < 2 || size == 0 */ + return; + + /* called from 'sort' without swap function, let's pick the default */ + if (swap_func == SWAP_WRAPPER && !((struct wrapper *)priv)->swap) + swap_func = NULL; + + if (!swap_func) { + if (is_aligned(base, size, 8)) + swap_func = SWAP_WORDS_64; + else if (is_aligned(base, size, 4)) + swap_func = SWAP_WORDS_32; + else + swap_func = SWAP_BYTES; + } + + /* + * Loop invariants: + * 1. elements [a,n) satisfy the heap property (compare greater than + * all of their children), + * 2. elements [n,num*size) are sorted, and + * 3. a <= b <= c <= d <= n (whenever they are valid). + */ + for (;;) { + size_t b, c, d; + + if (a) /* Building heap: sift down --a */ + a -= size; + else if (n -= size) /* Sorting: Extract root to --n */ + do_swap(base, base + n, size, swap_func, priv); + else /* Sort complete */ + break; + + /* + * Sift element at "a" down into heap. This is the + * "bottom-up" variant, which significantly reduces + * calls to cmp_func(): we find the sift-down path all + * the way to the leaves (one compare per level), then + * backtrack to find where to insert the target element. + * + * Because elements tend to sift down close to the leaves, + * this uses fewer compares than doing two per level + * on the way down. (A bit more than half as many on + * average, 3/4 worst-case.) + */ + for (b = a; c = 2*b + size, (d = c + size) < n;) + b = do_cmp(base + c, base + d, cmp_func, priv) >= 0 ? c : d; + if (d == n) /* Special case last leaf with no sibling */ + b = c; + + /* Now backtrack from "b" to the correct location for "a" */ + while (b != a && do_cmp(base + a, base + b, cmp_func, priv) >= 0) + b = parent(b, lsbit, size); + c = b; /* Where "a" belongs */ + while (b != a) { /* Shift it into place */ + b = parent(b, lsbit, size); + do_swap(base + b, base + c, size, swap_func, priv); + } + } +} + +void sort(void *base, size_t num, size_t size, + cmp_func_t cmp_func, + swap_func_t swap_func) +{ + struct wrapper w = { + .cmp = cmp_func, + .swap = swap_func, + }; + + return sort_r(base, num, size, _CMP_WRAPPER, SWAP_WRAPPER, &w); +} diff --git a/ubifs-utils/common/sort.h b/ubifs-utils/common/sort.h new file mode 100644 index 0000000..8982942 --- /dev/null +++ b/ubifs-utils/common/sort.h @@ -0,0 +1,20 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef _LINUX_SORT_H +#define _LINUX_SORT_H + +typedef void (*swap_r_func_t)(void *a, void *b, int size, const void *priv); +typedef void (*swap_func_t)(void *a, void *b, int size); + +typedef int (*cmp_r_func_t)(const void *a, const void *b, const void *priv); +typedef int (*cmp_func_t)(const void *a, const void *b); + +void sort_r(void *base, size_t num, size_t size, + cmp_r_func_t cmp_func, + swap_r_func_t swap_func, + const void *priv); + +void sort(void *base, size_t num, size_t size, + cmp_func_t cmp_func, + swap_func_t swap_func); + +#endif diff --git a/ubifs-utils/common/spinlock.h b/ubifs-utils/common/spinlock.h new file mode 100644 index 0000000..b9ed393 --- /dev/null +++ b/ubifs-utils/common/spinlock.h @@ -0,0 +1,14 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef __LINUX_SPINLOCK_H_ +#define __LINUX_SPINLOCK_H_ + +#include <pthread.h> + +#define spinlock_t pthread_mutex_t +#define DEFINE_SPINLOCK(x) pthread_mutex_t x = PTHREAD_MUTEX_INITIALIZER +#define spin_lock_init(x) pthread_mutex_init(x, NULL) + +#define spin_lock(x) pthread_mutex_lock(x) +#define spin_unlock(x) pthread_mutex_unlock(x) + +#endif diff --git a/ubifs-utils/fsck.ubifs/.gitignore b/ubifs-utils/fsck.ubifs/.gitignore new file mode 100644 index 0000000..09d664a --- /dev/null +++ b/ubifs-utils/fsck.ubifs/.gitignore @@ -0,0 +1 @@ +/fsck.ubifs diff --git a/ubifs-utils/fsck.ubifs/README.txt b/ubifs-utils/fsck.ubifs/README.txt new file mode 100644 index 0000000..a4daae7 --- /dev/null +++ b/ubifs-utils/fsck.ubifs/README.txt @@ -0,0 +1,388 @@ + fsck.ubifs + ========== + The fsck.ubifs can check and repair the UBIFS image on a given UBI volume, it + could fix inconsistent UBIFS image(which is corrupted by hardware exceptions + or UBIFS realization bugs) and makes filesystem become consistent. + + + Manuals + ------- + There are four modes for fsck.ubifs: + 1. normal mode(no options): Check the filesystem, ask user whether to fix the + problem as long as inconsistent data is found during fs checking. + 2. safe mode(-a option): Check and automatic safely repair the filesystem, if + there are any data dropping operations needed by fixing, fsck will fail. + 3. danger mode(-y option): Answer 'yes' to all questions. There are two sub + modes: + a) Default submode(no options): Check and automatic repair the filesystem + according to TNC, data dropping will be reported. If TNC/master/log is + corrupted, fsck will fail. + b) rebuild submode(-b option): Check and automatic forcibly repair the + filesystem, turns to rebuild filesystem if TNC/master/log is corrupted. + Always make fsck successful. + 4. check mode(-n option): Make no changes to the filesystem, only check the + filesystem. This mode doesn't check space, because unclean LEBs cannot be + rewritten in read-only mode. + + The exit code returned by fsck.ubifs is compatible with FSCK(8), which is the + sum of the following conditions: + 0 - No errors + 1 - File system errors corrected + 2 - System should be rebooted + 4 - File system errors left uncorrected + 8 - Operational error + 16 - Usage or syntax error + 32 - Fsck canceled by user request + 128 - Shared library error + + + Designment + ---------- + There are 2 working modes for fsck: rebuild mode and non-rebuild mode. The main + idea is that construct all files by scanning the entire filesystem, then check + the consistency of metadata(file meta information, space statistics, etc.) + according to the files. The file(xattr is treated as a file) is organized as: + file tree(rbtree, inum indexed) + / \ + file1 file2 + / \ + file3 file4 + file { + inode node // each file has 1 inode node + dentry (sub rb_tree, sqnum indexed) + // '/' has no dentries, otherwise at least 1 dentry is required. + trun node // the newest one truncation node + data (sub rb_tree, block number indexed) + // Each file may have 0 or many data nodes + xattrs (sub rb_tree, inum indexed) + // Each file may have 0 or many xattr files + } + + Step 0. Both two modes need to read the superblock firstly, fsck fails if + superblock is corrupted, because fsck has no idea about the location + of each area(master, log, main, etc.) when the layout is lost. + + A. Rebuild mode: + Step 1. Scan nodes(inode node/dentry node/data node/truncation node) from all + LEBs. + a) Corrupted LEBs(eg. garbage data, corrupted empty space) are dropped + during scanning. + b) Corrupted nodes(eg. incorrect crc, bad inode size, bad dentry name + length, etc.) are dropped during scanning. + c) Valid inode nodes(nlink > 0) and dentry nodes(inum != 0) are put + into two valid trees(valid_inos & valid_dents) separately. + d) Deleted inode nodes (nlink is 0) and deleted dentry nodes(inum is 0) + are put into two deleted trees(del_inos & del_dents) separately. + e) Other nodes(data nodes/truncation node) are put into corresponding + file, if the file doesn't exist, insert a new file into the file + tree. + Step 2. Traverse nodes from deleted trees, remove inode nodes and dentry nodes + with smaller sqnum from valid trees. valid_inos - del_inos = left_inos, + valid_dents - del_dents = left_dents. + This step handles the deleting case, for example, file A is deleted, + deleted inode node and deleted dentry node are written, if we ignore + the deleted nodes, file A can be recovered after rebuilding because + undeleted inode node and undeleted dentry node can be scanned. There's + an exception, if deleted inode node and deleted dentry node are + reclaimed(by gc) after deletion, file A is recovered. So deleted data + or files could be recovered by rebuild mode. + Step 3. Traverse left_inos and left_dents, insert inode node and dentry nodes + into the corresponding file. + Step 4. Traverse all files, drop invalid files, move xattr files into the + corresponding host file's subtree. Invalid files such as: + a) File has no inode node or inode nlink is zero + b) Non-consistent file types between inode node and dentry nodes + c) File has no dentry nodes(excepts '/') + d) Encrypted file has no xattr information + e) Non regular file has data nodes + f) Directory/xattr file has more than one dentries + g) Xattr file has no host inode, or the host inode is a xattr + h) Non-xattr file's parent is not a directory + i) etc. + Step 5. Extract reachable directory entries tree. Make sure that all files can + be searched from '/', unreachable file is deleted. Since all xattr + files are attached to the corresponding host file, only non-xattr + files should be checked. Luckily, directory file only has one dentry, + the reachable checking of a dentry becomes easy. Traverse all + dentries for each file, check whether the dentry is reachable, if not, + remove dentry from the file. If the file has no dentries, the file is + unreachable. + Step 6. Correct the file information. Traverse all files and calculate + information(nlink, size, xattr_cnt, etc.) for each file just like + check_leaf(in linux kernel) does, correct the inode node based on the + calculated information. + Step 7. Record used LEBs. Traverse all files'(including effective nodes from + deletion trees in step 2) position, after this step fsck knows which + LEB is empty. + Step 8. Re-write data. Read data from LEB and write back data, make sure that + all LEB is ended with empty data(0xFF). It will prevent failed gc + scanning in the next mounting. + Step 9. Build TNC. Construct TNC according to all files' nodes, just like mkfs + does(refer to add_to_index in mkfs), then write TNC(refer to + write_index in mkfs) on flash. (If there are no files, create a new + root dir file.) + Step 10.Build LPT. Construct LPT according to all nodes' position and length, + just like mkfs does, then write LPT(refer to write_lpt) on flash. + Step 11.Clean up log area and orphan area. Log area and orphan area can be + erased. + Step 12.Write master node. Since all meta areas are ready, master node can be + updated. + + B. Non-rebuild mode: + Step 1. Read master & init lpt. + a) Scan master nodes failed or master node is invalid (which is not + caused by invalid space statistics), danger mode with rebuild_fs and + normal mode with 'yes' answer will turn to rebuild mode, other modes + will exit. Fsck cannot find the right TNC/LPT if the master node is + invalid, which affects subsequent steps, so this problem must be + fixed. + b) Invalid space statistics in master node, set %FR_LPT_INCORRECT for + for lpt status and ignore the error. + c) LPT node is corrupted, set %FR_LPT_CORRUPTED for lpt status and + ignore the error. + Step 2. Replay journal. + I. Scan log LEBs to get all buds. + a) Nodes in log LEBs are invalid/corrupted, danger mode with + rebuild_fs and normal mode with 'yes' answer will turn to rebuild + mode, other modes will exit. Corrupted log LEB could fail + ubifs_consolidate_log, which may lead to commit failure by out of + space in the log area, so this problem must be fixed. + II. Scan bud LEBs to get all nodes. + a) Nodes in bud LEBs are invalid/corrupted, danger mode and normal + mode with 'yes' answer will drop bud LEB and set + %FR_LPT_INCORRECT for lpt status, other modes will exit. + Corrupted LEB will make gc failed, so this problem must be + fixed. + III. Record isize into size tree according to data/truncation/inode + nodes. + IV. Apply nodes to TNC & LPT, update property for bud LEBs. + a) Corrupted/Invalid node searched from TNC, skip node and set + %FR_LPT_INCORRECT in lpt status for danger mode and normal mode + with 'yes' answer, other modes will exit. The space statistics + depend on a valid TNC, so this problem must be fixed. + b) Corrupted/Invalid index node read from TNC, danger mode with + rebuild_fs and normal mode with 'yes' answer will turn to + rebuild filesystem, other modes will exit. The space statistics + depend on a valid TNC, so this problem must be fixed. + c) Corrupted/Invalid lpt node, Set %FR_LPT_CORRUPTED for lpt status + and ignore the error. + d) Incorrect LEB property: Set %FR_LPT_INCORRECT for lpt status and + ignore the error. + e) If lpt status is not empty, skip updating lpt, because incorrect + LEB property could trigger assertion failure in ubifs_change_lp. + Step 3. Handle orphan nodes. + I. Scan orphan LEB to get all orphan nodes. + a) Corrupted/Invalid orphan node: danger mode and normal mode with + 'yes' answer will drop orphan LEB, other modes will exit. + Corrupted orphan area could lead to mounting/committing failure, + so this problem must be fixed. + II. Parse orphan node, find the original inode for each inum. + a) Corrupted/Invalid node searched from TNC, skip node for danger + mode and normal mode with 'yes' answer, other modes will exit. + b) Corrupted/Invalid index node read from TNC, danger mode with + rebuild_fs and normal mode with 'yes' answer will turn to + rebuild filesystem, other modes will exit. The space statistics + depend on a valid TNC, so this problem must be fixed. + III. Remove inode for each inum, update TNC & LPT. + a) Corrupted/Invalid node searched from TNC, skip node for danger + mode and normal mode with 'yes' answer, other modes will exit. + b) Corrupted/Invalid index node read from TNC, danger mode with + rebuild_fs and normal mode with 'yes' answer will turn to + rebuild filesystem, other modes will exit. The space statistics + depend on a valid TNC, so this problem must be fixed. + c) Corrupted/Invalid lpt node, Set %FR_LPT_CORRUPTED for lpt + status and ignore the error. + d) Incorrect LEB property: Set %FR_LPT_INCORRECT for lpt status + and ignore the error. + e) If lpt status is not empty, skip updating lpt, because + incorrect LEB property could trigger assertion failure in + ubifs_change_lp. + Step 4. Consolidate log area. + a) Corrupted data in log LEBs, danger mode with rebuild_fs and normal + mode with 'yes' answer will turn to rebuild filesystem, other modes + will exit. It could make commit failed by out of space in log area, + so this problem must be fixed. + Step 5. Recover isize. + I. Traverse size tree, lookup corresponding inode from TNC. + a) Corrupted/Invalid node searched from TNC, skip node for danger + mode and normal mode with 'yes' answer, other modes will exit. + b) Corrupted/Invalid index node read from TNC, danger mode with + rebuild_fs and normal mode with 'yes' answer will turn to + rebuild filesystem, other modes will exit. The space statistics + depend on a valid TNC, so this problem must be fixed. + II. Update isize for inode. Keep <inum, isize> in size tree for check + mode, remove <inum, isize> from the size tree and update inode + node in place for other modes. + Step 6. Traverse TNC and construct files. + I. Traverse TNC, check whether the leaf node is valid, remove invalid + nodes, construct file for valid node and insert the file into the + file tree. + a) Corrupted/Invalid node searched from TNC, remove corresponding + TNC branch for danger mode and normal mode with 'yes' answer, + other modes will exit. The space statistics depend on a valid + TNC, so this problem must be fixed. + b) Corrupted/Invalid index node read from TNC, danger mode with + rebuild_fs and normal mode with 'yes' answer will turn to + rebuild filesystem, other modes will exit. The space statistics + depend on a valid TNC, so this problem must be fixed. + II. Scan all LEBs(contain TNC) for non check mode(unclean LEBs cannot + be fixed in read-only mode, so scanning may fail in check mode, + then space statistics won't be checked in check mode), remove TNC + branch which points to corrupted LEB. + a) Corrupted data is found by scanning. If the current node is + index node, danger mode with rebuild_fs and normal mode with + 'yes' answer will turn to rebuild filesystem, other modes will + exit; If the current node is non-index node, danger mode and + normal mode with 'yes' answer will remove all TNC branches which + point to the corrupted LEB, other modes will exit. The space + statistics depend on valid LEB scanning, so this problem must + be fixed. + b) LEB contains both index and non-index nodes, danger mode with + rebuild_fs and normal mode with 'yes' answer will turn to + rebuild filesystem, other modes will exit. Invalid LEB will make + gc failed, so this problem must be fixed. + Step 7. Update files' size for check mode. Update files' size according to the + size tree for check mode. + Step 8. Check and handle invalid files. Similar to rebuild mode, but the + methods of handling are different: + a) Move unattached(file has no dentries) regular file into disconnected + list for safe mode, danger mode and normal mode with 'yes' answer, + let subsequent steps to handle them with lost+found. Other modes + will exit. Disconnected file affects the result of calculated + information(which will be used in subsequent steps) for its' parent + file(eg. nlink, size), so this problem must be fixed. + b) Make file type be consistent between inode, detries and data nodes + by deleting dentries or data nodes, for danger mode and normal mode + with 'yes' answer, other modes will exit. + c) Delete file for other invalid cases(eg. file has no inode) in + danger mode and normal mode with 'yes' answer, other modes will + exit. + Step 9. Extract reachable directory entries tree. Similar to rebuild mode, but + the methods of handling are different: + a) Remove unreachable dentry for danger mode and normal mode with 'yes' + answer, other modes will exit. Unreachable dentry affects the + calculated information(which will be used in subsequent steps) for + its' file(eg. nlink), so this problem must be fixed. + b) Delete unreachable non-regular file for danger mode and normal mode + with 'yes' answer, other modes will exit. Unreachable file affects + the calculated information(which will be used in subsequent steps) + for its' parent file(eg. nlink, size), so this problem must be + fixed. + c) Move unreachable regular file into disconnected list for safe mode, + danger mode and normal mode with 'yes' answer, let subsequent steps + to handle them with lost+found. Other modes will exit. Disconnected + file affects the calculated information(which will be used in + subsequent steps) for its' parent file(eg. nlink, size), so this + problem must be fixed. + Step 10.Correct the file information. Similar to rebuild mode, but the methods + of handling are different: + a) Correct the file information for safe mode, danger mode and normal + mode with 'yes' answer, other modes will exit. Incorrect file + information affects the new creations(which will be used in handling + lost+found), so this problem must be fixed. + Step 11.Check whether the TNC is empty. Empty TNC is equal to corrupted TNC, + which means that zero child count for root znode. If TNC is empty(All + nodes are invalid and are deleted from TNC), turn to rebuild mode for + danger mode with rebuild_fs and normal mode with 'yes' answer, other + modes will exit. + Step 12.Check and correct the space statistics. + I. Exit for check mode, if %FR_LPT_CORRUPTED or %FR_LPT_INCORRECT is + set in lpt status, the exit code should have %FSCK_UNCORRECTED. + II. Check lpt status, if %FR_LPT_CORRUPTED is set in lpt status, normal + mode with 'no' answer will exit, other modes will rebuild lpt. New + creations could be done in subsequent steps, which depends on + correct space statistics, so this problem must be fixed. + III. Traverse LPT nodes, check the correctness of nnode and pnode, + compare LEB scanning result with LEB properties. + a) LPT node is corrupted, normal mode with 'no' answer will exit, + rebuild lpt for other modes. New creations could be done in + subsequent steps, which depends on the correct space + statistics, so this problem must be fixed. + b) Incorrect nnode/pnode, normal mode with 'no' answer will exit, + other modes will correct the nnode/pnode. New creations could + be done in subsequent steps, which depends on correct space + statistics, so this problem must be fixed. + c) Inconsistent comparing result, normal mode with 'no' answer + will exit, other modes will correct the space statistics. New + creations could be done in subsequent steps, which depends on + correct space statistics, so this problem must be fixed. + IV. Compare LPT area scanning result with lprops table information. + a) LPT area is corrupted, normal mode with 'no' answer will exit, + rebuild lpt for other modes. Commit could fail in doing LPT gc + caused by scanning corrupted data, so this problem must be + fixed. + b) Inconsistent comparing result, normal mode with 'no' answer + will exit, other modes will correct the lprops table + information. Commit could fail in writing LPT with %ENOSPC + return code caused by incorrect space statistics in the LPT + area, so this problem must be fixed. + Step 13.Do commit, commit problem fixing modifications to disk. The index size + checking depends on this step. + Step 14.Check and correct the index size. Check and correct the index size by + traversing TNC just like dbg_check_idx_size does. This step should be + executed after first committing, because 'c->calc_idx_sz' can be + changed in 'ubifs_tnc_start_commit' and the initial value of + 'c->calc_idx_sz' read from the disk is untrusted. Correct the index + size for safe mode, danger mode and normal mode with 'yes' answer, + other modes will exit. New creations could be done in subsequent steps, + which depends on the correct index size, so this problem must be fixed. + Step 15.Check and create the root dir. Check whether the root dir exists, + create a new one if it is not found, for safe mode, danger mode and + normal mode with 'yes' answer, other modes will exit. Mounting depends + on the root dir, so this problem must be fixed. + Step 16.Check and create the lost+found. + I. If the root dir is encrypted, set lost+found as invalid. Because it + is impossible to check whether the lost+found exists in an encrypted + directory. + II. Search the lost+found under root dir. + a) Found a lost+found, lost+found is a non-encrypted directory, set + lost+found as valid, otherwise set lost+found as invalid. + b) Not found the lost+found, create a new one. If creation is + failed by %ENOSPC, set lost+found as invalid. + Step 17.Handle each file from the disconnected list. + I. If lost+found is invalid, delete file for danger mode and normal + mode with 'yes' answer, other modes will skip and set the exit code + with %FSCK_UNCORRECTED. + II. If lost+found is valid, link disconnected file under lost+found + directory with the name of the corresponding inode number + (INO_<inum>_<index>, index(starts from 0) is used to handle the + conflicted names). + a) Fails in handling conflicted file names, delete file for danger + mode and normal mode with 'yes' answer, other modes will skip + and set the exit code with %FSCK_UNCORRECTED. + b) Fails in linking caused by %ENOSPC, delete file for danger mode + and normal mode with 'yes' answer, other modes will skip and set + the exit code with %FSCK_UNCORRECTED. + Step 18.Do final commit, commit problem fixing modifications to disk and clear + %UBIFS_MST_DIRTY flag for master node. + + + Advantage + --------- + 1. Can be used for any UBIFS image, fsck has nothing to do with kernel version. + 2. Fsck is tolerant with power-cut, fsck will always succeed in a certain mode + without changing mode even power-cut happens in checking and repairing. In + other words, fsck won't let UBIFS image become worse in abnormal situations. + 3. It is compatible with FSCK(8), the exit code returned by fsck.ubifs is same + as FSCK, the command options used by fsck are supported in fsck.ubifs too. + 4. The UBIFS image can be fixed as long as the super block is not corrupted. + 5. Encrypted UBIFS image is supported, because dentry name and data content of + file are not necessary for fsck. + + + Limitations + ----------- + 1. UBIFS image file is not supported(Not like ext4). The UBIFS image file is + not equal to UBI volume, empty LEBs are not included in image file, so UBIFS + cannot allocate empty space when file recovering is needed. Another reason + is that atomic LEB changing is not supported by image file. + 2. Authenticated UBIFS image is not supported, UBIFS metadata(TNC/LPT) parsing + depends on the authentication key which is not supported in fsck options. + + + Authors + ------- + Zhihao Cheng <chengzhihao1@huawei.com> + Zhang Yi <yi.zhang@huawei.com> + Xiang Yang <xiangyang3@huawei.com> + Huang Xiaojia <huangxiaojia2@huawei.com> diff --git a/ubifs-utils/fsck.ubifs/check_files.c b/ubifs-utils/fsck.ubifs/check_files.c new file mode 100644 index 0000000..1e1a77b --- /dev/null +++ b/ubifs-utils/fsck.ubifs/check_files.c @@ -0,0 +1,555 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2024, Huawei Technologies Co, Ltd. + * + * Authors: Zhihao Cheng <chengzhihao1@huawei.com> + */ + +#include <stdio.h> +#include <stdlib.h> +#include <sys/stat.h> + +#include "linux_err.h" +#include "bitops.h" +#include "kmem.h" +#include "ubifs.h" +#include "defs.h" +#include "debug.h" +#include "key.h" +#include "fsck.ubifs.h" + +struct invalid_node { + union ubifs_key key; + int lnum; + int offs; + struct list_head list; +}; + +struct iteration_info { + struct list_head invalid_nodes; + unsigned long *corrupted_lebs; +}; + +static int add_invalid_node(struct ubifs_info *c, union ubifs_key *key, + int lnum, int offs, struct iteration_info *iter) +{ + struct invalid_node *in; + + in = kmalloc(sizeof(struct invalid_node), GFP_KERNEL); + if (!in) { + log_err(c, errno, "can not allocate invalid node"); + return -ENOMEM; + } + + key_copy(c, key, &in->key); + in->lnum = lnum; + in->offs = offs; + list_add(&in->list, &iter->invalid_nodes); + + return 0; +} + +static int construct_file(struct ubifs_info *c, union ubifs_key *key, + int lnum, int offs, void *node, + struct iteration_info *iter) +{ + ino_t inum = 0; + struct rb_root *tree = &FSCK(c)->scanned_files; + struct scanned_node *sn = NULL; + struct ubifs_ch *ch = (struct ubifs_ch *)node; + + switch (ch->node_type) { + case UBIFS_INO_NODE: + { + struct scanned_ino_node ino_node; + + if (!parse_ino_node(c, lnum, offs, node, key, &ino_node)) { + if (fix_problem(c, INVALID_INO_NODE, NULL)) + return add_invalid_node(c, key, lnum, offs, iter); + } + inum = key_inum(c, key); + sn = (struct scanned_node *)&ino_node; + break; + } + case UBIFS_DENT_NODE: + case UBIFS_XENT_NODE: + { + struct scanned_dent_node dent_node; + + if (!parse_dent_node(c, lnum, offs, node, key, &dent_node)) { + if (fix_problem(c, INVALID_DENT_NODE, NULL)) + return add_invalid_node(c, key, lnum, offs, iter); + } + inum = dent_node.inum; + sn = (struct scanned_node *)&dent_node; + break; + } + case UBIFS_DATA_NODE: + { + struct scanned_data_node data_node; + + if (!parse_data_node(c, lnum, offs, node, key, &data_node)) { + if (fix_problem(c, INVALID_DATA_NODE, NULL)) + return add_invalid_node(c, key, lnum, offs, iter); + } + inum = key_inum(c, key); + sn = (struct scanned_node *)&data_node; + break; + } + default: + ubifs_assert(c, 0); + } + + dbg_fsck("construct file(%lu) for %s node, TNC location %d:%d, in %s", + inum, ubifs_get_key_name(key_type(c, key)), sn->lnum, sn->offs, + c->dev_name); + return insert_or_update_file(c, tree, sn, key_type(c, key), inum); +} + +static int scan_check_leb(struct ubifs_info *c, int lnum, bool is_idx) +{ + int err = 0; + struct ubifs_scan_leb *sleb; + struct ubifs_scan_node *snod; + + if (FSCK(c)->mode == CHECK_MODE) + /* Skip check mode. */ + return 0; + + ubifs_assert(c, lnum >= c->main_first); + if (test_bit(lnum - c->main_first, FSCK(c)->used_lebs)) + return 0; + + sleb = ubifs_scan(c, lnum, 0, c->sbuf, 0); + if (IS_ERR(sleb)) { + err = PTR_ERR(sleb); + if (test_and_clear_failure_reason_callback(c, FR_DATA_CORRUPTED)) + err = 1; + return err; + } + + list_for_each_entry(snod, &sleb->nodes, list) { + if (is_idx) { + if (snod->type != UBIFS_IDX_NODE) { + err = 1; + goto out; + } + } else { + if (snod->type == UBIFS_IDX_NODE) { + err = 1; + goto out; + } + } + } + + set_bit(lnum - c->main_first, FSCK(c)->used_lebs); + +out: + ubifs_scan_destroy(sleb); + return err; +} + +static int check_leaf(struct ubifs_info *c, struct ubifs_zbranch *zbr, + void *priv) +{ + void *node; + struct iteration_info *iter = (struct iteration_info *)priv; + union ubifs_key *key = &zbr->key; + int lnum = zbr->lnum, offs = zbr->offs, len = zbr->len, err = 0; + + if (len < UBIFS_CH_SZ) { + ubifs_err(c, "bad leaf length %d (LEB %d:%d)", + len, lnum, offs); + set_failure_reason_callback(c, FR_TNC_CORRUPTED); + return -EINVAL; + } + if (key_type(c, key) != UBIFS_INO_KEY && + key_type(c, key) != UBIFS_DATA_KEY && + key_type(c, key) != UBIFS_DENT_KEY && + key_type(c, key) != UBIFS_XENT_KEY) { + ubifs_err(c, "bad key type %d (LEB %d:%d)", + key_type(c, key), lnum, offs); + set_failure_reason_callback(c, FR_TNC_CORRUPTED); + return -EINVAL; + } + + if (test_bit(lnum - c->main_first, iter->corrupted_lebs)) { + if (fix_problem(c, SCAN_CORRUPTED, zbr)) + /* All nodes in corrupted LEB should be removed. */ + return add_invalid_node(c, key, lnum, offs, iter); + return 0; + } + + err = scan_check_leb(c, lnum, false); + if (err < 0) { + return err; + } else if (err) { + set_bit(lnum - c->main_first, iter->corrupted_lebs); + if (fix_problem(c, SCAN_CORRUPTED, zbr)) + return add_invalid_node(c, key, lnum, offs, iter); + return 0; + } + + node = kmalloc(len, GFP_NOFS); + if (!node) + return -ENOMEM; + + err = ubifs_tnc_read_node(c, zbr, node); + if (err) { + if (test_and_clear_failure_reason_callback(c, FR_DATA_CORRUPTED)) { + if (fix_problem(c, TNC_DATA_CORRUPTED, NULL)) + err = add_invalid_node(c, key, lnum, offs, iter); + } + goto out; + } + + err = construct_file(c, key, lnum, offs, node, iter); + +out: + kfree(node); + return err; +} + +static int check_znode(struct ubifs_info *c, struct ubifs_znode *znode, + __unused void *priv) +{ + int err; + const struct ubifs_zbranch *zbr; + + if (znode->parent) + zbr = &znode->parent->zbranch[znode->iip]; + else + zbr = &c->zroot; + + if (zbr->lnum == 0) { + /* The znode has been split up. */ + ubifs_assert(c, zbr->offs == 0 && zbr->len == 0); + return 0; + } + + err = scan_check_leb(c, zbr->lnum, true); + if (err < 0) { + return err; + } else if (err) { + set_failure_reason_callback(c, FR_TNC_CORRUPTED); + return -EINVAL; + } + + return 0; +} + +static int remove_invalid_nodes(struct ubifs_info *c, + struct list_head *invalid_nodes, int error) +{ + int ret = 0;; + struct invalid_node *in; + + while (!list_empty(invalid_nodes)) { + in = list_entry(invalid_nodes->next, struct invalid_node, list); + + if (!error) { + error = ubifs_tnc_remove_node(c, &in->key, in->lnum, in->offs); + if (error) { + /* TNC traversing is finished, any TNC path is accessible */ + ubifs_assert(c, !get_failure_reason_callback(c)); + ret = error; + } + } + + list_del(&in->list); + kfree(in); + } + + return ret; +} + +/** + * traverse_tnc_and_construct_files - traverse TNC and construct all files. + * @c: UBIFS file-system description object + * + * This function does two things by traversing TNC: + * 1. Check all index nodes and non-index nodes, then construct file according + * to scanned non-index nodes and insert file into file tree. + * 2. Make sure that LEB(contains any nodes from TNC) can be scanned by + * ubifs_scan, and the LEB only contains index nodes or non-index nodes. + * Returns zero in case of success, a negative error code in case of failure. + */ +int traverse_tnc_and_construct_files(struct ubifs_info *c) +{ + int err, ret; + struct iteration_info iter; + + FSCK(c)->scanned_files = RB_ROOT; + FSCK(c)->used_lebs = kcalloc(BITS_TO_LONGS(c->main_lebs), + sizeof(unsigned long), GFP_KERNEL); + if (!FSCK(c)->used_lebs) { + err = -ENOMEM; + log_err(c, errno, "can not allocate bitmap of used lebs"); + return err; + } + INIT_LIST_HEAD(&iter.invalid_nodes); + iter.corrupted_lebs = kcalloc(BITS_TO_LONGS(c->main_lebs), + sizeof(unsigned long), GFP_KERNEL); + if (!iter.corrupted_lebs) { + err = -ENOMEM; + log_err(c, errno, "can not allocate bitmap of corrupted lebs"); + goto out; + } + + err = dbg_walk_index(c, check_leaf, check_znode, &iter); + + ret = remove_invalid_nodes(c, &iter.invalid_nodes, err); + if (!err) + err = ret; + + kfree(iter.corrupted_lebs); +out: + if (err) { + kfree(FSCK(c)->used_lebs); + destroy_file_tree(c, &FSCK(c)->scanned_files); + } + return err; +} + +/** + * update_files_size - Update files' size. + * @c: UBIFS file-system description object + * + * This function updates files' size according to @c->size_tree for check mode. + */ +void update_files_size(struct ubifs_info *c) +{ + struct rb_node *this; + + if (FSCK(c)->mode != CHECK_MODE) { + /* Other modes(rw) have updated inode size in place. */ + dbg_fsck("skip updating files' size%s, in %s", + mode_name(c), c->dev_name); + return; + } + + log_out(c, "Update files' size"); + + this = rb_first(&c->size_tree); + while (this) { + struct size_entry *e; + + e = rb_entry(this, struct size_entry, rb); + this = rb_next(this); + + if (e->exists && e->i_size < e->d_size) { + struct scanned_file *file; + + file = lookup_file(&FSCK(c)->scanned_files, e->inum); + if (file && file->ino.header.exist && + file->ino.size < e->d_size) { + dbg_fsck("update file(%lu) size %llu->%llu, in %s", + e->inum, file->ino.size, + (unsigned long long)e->d_size, + c->dev_name); + file->ino.size = e->d_size; + } + } + + rb_erase(&e->rb, &c->size_tree); + kfree(e); + } +} + +/** + * handle_invalid_files - Handle invalid files. + * @c: UBIFS file-system description object + * + * This function checks and handles invalid files, there are three situations: + * 1. Move unattached(file has no dentries, or file's parent file has invalid + * type) regular file into disconnected list, let subsequent steps to handle + * them with lost+found. + * 2. Make file type be consistent between inode, detries and data nodes by + * deleting dentries or data blocks. + * 3. Delete file for other invalid cases(eg. file has no inode). + * + * Returns zero in case of success, a negative error code in case of failure. + */ +int handle_invalid_files(struct ubifs_info *c) +{ + int err; + struct rb_node *node; + struct scanned_file *file; + struct rb_root *tree = &FSCK(c)->scanned_files; + LIST_HEAD(tmp_list); + + /* Add all xattr files into a list. */ + for (node = rb_first(tree); node; node = rb_next(node)) { + file = rb_entry(node, struct scanned_file, rb); + + if (file->ino.is_xattr) + list_add(&file->list, &tmp_list); + } + + /* + * Round 1: Traverse xattr files, check whether the xattr file is + * valid, move valid xattr file into corresponding host file's subtree. + */ + while (!list_empty(&tmp_list)) { + file = list_entry(tmp_list.next, struct scanned_file, list); + + list_del(&file->list); + rb_erase(&file->rb, tree); + err = file_is_valid(c, file, tree, NULL); + if (err < 0) { + destroy_file_content(c, file); + kfree(file); + return err; + } else if (!err) { + err = delete_file(c, file); + kfree(file); + if (err) + return err; + } + } + + /* Round 2: Traverse non-xattr files. */ + for (node = rb_first(tree); node; node = rb_next(node)) { + int is_diconnected = 0; + + file = rb_entry(node, struct scanned_file, rb); + err = file_is_valid(c, file, tree, &is_diconnected); + if (err < 0) { + return err; + } else if (!err) { + if (is_diconnected) + list_add(&file->list, &FSCK(c)->disconnected_files); + else + list_add(&file->list, &tmp_list); + } + } + + /* Delete & remove invalid files. */ + while (!list_empty(&tmp_list)) { + file = list_entry(tmp_list.next, struct scanned_file, list); + + list_del(&file->list); + err = delete_file(c, file); + if (err) + return err; + rb_erase(&file->rb, tree); + kfree(file); + } + + /* Remove disconnected file from the file tree. */ + list_for_each_entry(file, &FSCK(c)->disconnected_files, list) { + rb_erase(&file->rb, tree); + } + + return 0; +} + +/** + * handle_dentry_tree - Handle unreachable dentries and files. + * @c: UBIFS file-system description object + * + * This function iterates all directory entries and remove those unreachable + * ones. If file has no directory entries, it becomes unreachable: + * 1. If the unreachable file has non-regular type, delete it; + * 2. If the unreachable file has regular type, move it into the + * @FSCK(c)->disconnected_files. + * 'Unreachable' means that a directory entry can not be searched from '/'. + * + * Returns zero in case of success, a negative error code in case of failure. + */ +int handle_dentry_tree(struct ubifs_info *c) +{ + struct rb_node *node; + struct scanned_file *file; + struct rb_root *tree = &FSCK(c)->scanned_files; + LIST_HEAD(unreachable); + + for (node = rb_first(tree); node; node = rb_next(node)) { + file = rb_entry(node, struct scanned_file, rb); + + /* + * Since all xattr files are already attached to corresponding + * host file, there are only non-xattr files in the file tree. + */ + ubifs_assert(c, !file->ino.is_xattr); + if (!file_is_reachable(c, file, tree)) + list_add(&file->list, &unreachable); + } + + while (!list_empty(&unreachable)) { + file = list_entry(unreachable.next, struct scanned_file, list); + + list_del(&file->list); + if (S_ISREG(file->ino.mode)) { + /* + * Move regular type unreachable file into the + * @FSCK(c)->disconnected_files. + */ + list_add(&file->list, &FSCK(c)->disconnected_files); + rb_erase(&file->rb, tree); + } else { + /* Delete non-regular type unreachable file. */ + int err = delete_file(c, file); + if (err) + return err; + rb_erase(&file->rb, tree); + kfree(file); + } + } + + return 0; +} + +/** + * tnc_is_empty - Check whether the TNC is empty. + * @c: UBIFS file-system description object + * + * Returns %true if the TNC is empty, otherwise %false is returned. + */ +bool tnc_is_empty(struct ubifs_info *c) +{ + /* + * Check whether the TNC is empty, turn to rebuild_fs if it is empty. + * Can we recreate a new root dir to avoid empty TNC? The answer is no, + * lpt fixing should be done before creating new entry, but lpt fixing + * needs a committing before new dirty data generated to ensure that + * bud data won't be overwritten(bud LEB could become freeable after + * replaying journal, corrected lpt may treat it as a free one to hold + * new data, see details in space checking & correcting step). Then we + * have to create the new root dir after fixing lpt and a committing, + * znode without children(empty TNC) maybe written on disk at the + * moment of committing, which corrupts the UBIFS image. So we choose + * to rebuild the filesystem if the TNC is empty, this case is + * equivalent to corrupted TNC. + */ + return c->zroot.znode->child_cnt == 0; +} + +/** + * check_and_create_root - Check and create root dir. + * @c: UBIFS file-system description object + * + * This function checks whether the root dir is existed, create a new root + * dir if it doesn't exist. Returns zero in case of success, a negative error + * code in case of failure. + */ +int check_and_create_root(struct ubifs_info *c) +{ + int err; + struct ubifs_inode *ui = ubifs_lookup_by_inum(c, UBIFS_ROOT_INO); + + if (!IS_ERR(ui)) { + /* The root dir is found. */ + dbg_fsck("root dir is found, in %s", c->dev_name); + kfree(ui); + return 0; + } + + err = PTR_ERR(ui); + if (err != -ENOENT) + return err; + + fix_problem(c, ROOT_DIR_NOT_FOUND, NULL); + dbg_fsck("root dir is lost, create a new one, in %s", c->dev_name); + return ubifs_create_root(c); +} diff --git a/ubifs-utils/fsck.ubifs/check_space.c b/ubifs-utils/fsck.ubifs/check_space.c new file mode 100644 index 0000000..cff8a7c --- /dev/null +++ b/ubifs-utils/fsck.ubifs/check_space.c @@ -0,0 +1,690 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2024, Huawei Technologies Co, Ltd. + * + * Authors: Zhihao Cheng <chengzhihao1@huawei.com> + */ + +#include <stdio.h> +#include <stdlib.h> + +#include "linux_err.h" +#include "bitops.h" +#include "kmem.h" +#include "ubifs.h" +#include "defs.h" +#include "debug.h" +#include "key.h" +#include "misc.h" +#include "fsck.ubifs.h" + +/** + * get_free_leb - get a free LEB according to @FSCK(c)->used_lebs. + * @c: UBIFS file-system description object + * + * This function tries to find a free LEB, lnum is returned if found, otherwise + * %-ENOSPC is returned. + */ +int get_free_leb(struct ubifs_info *c) +{ + int lnum; + + lnum = find_next_zero_bit(FSCK(c)->used_lebs, c->main_lebs, 0); + if (lnum >= c->main_lebs) { + ubifs_err(c, "No space left."); + return -ENOSPC; + } + set_bit(lnum, FSCK(c)->used_lebs); + lnum += c->main_first; + + return lnum; +} + +/** + * build_lpt - construct LPT and write it into flash. + * @c: UBIFS file-system description object + * @calculate_lp_cb: callback function to calculate the properties for given LEB + * @free_ltab: %true means to release c->ltab after creating lpt + * + * This function builds LPT according to the calculated results by + * @calculate_lp_cb and writes LPT into flash. Returns zero in case of success, + * a negative error code in case of failure. + */ +int build_lpt(struct ubifs_info *c, calculate_lp_callback calculate_lp_cb, + bool free_ltab) +{ + int i, err, lnum, free, dirty; + u8 hash_lpt[UBIFS_HASH_ARR_SZ]; + + memset(&c->lst, 0, sizeof(struct ubifs_lp_stats)); + /* Set gc lnum, equivalent to ubifs_rcvry_gc_commit/take_gc_lnum. */ + lnum = get_free_leb(c); + if (lnum < 0) + return lnum; + c->gc_lnum = lnum; + + /* Update LPT. */ + for (i = 0; i < c->main_lebs; i++) { + err = calculate_lp_cb(c, i, &free, &dirty, NULL); + if (err) + return err; + + FSCK(c)->lpts[i].free = free; + FSCK(c)->lpts[i].dirty = dirty; + c->lst.total_free += free; + c->lst.total_dirty += dirty; + + if (free == c->leb_size) + c->lst.empty_lebs++; + + if (FSCK(c)->lpts[i].flags & LPROPS_INDEX) { + c->lst.idx_lebs += 1; + } else { + int spc; + + spc = free + dirty; + if (spc < c->dead_wm) + c->lst.total_dead += spc; + else + c->lst.total_dark += ubifs_calc_dark(c, spc); + c->lst.total_used += c->leb_size - spc; + } + + dbg_fsck("build properties for LEB %d, free %d dirty %d is_idx %d, in %s", + i + c->main_first, free, dirty, + FSCK(c)->lpts[i].flags & LPROPS_INDEX ? 1 : 0, + c->dev_name); + } + + /* Write LPT. */ + return ubifs_create_lpt(c, FSCK(c)->lpts, c->main_lebs, hash_lpt, free_ltab); +} + +static int scan_get_lp(struct ubifs_info *c, int index, int *free, int *dirty, + int *is_idx) +{ + struct ubifs_scan_leb *sleb; + struct ubifs_scan_node *snod; + int used, idx_leb, lnum = index + c->main_first, err = 0; + bool is_build_lpt = FSCK(c)->lpt_status & FR_LPT_CORRUPTED; + + if (is_build_lpt) { + if (!test_bit(index, FSCK(c)->used_lebs) || c->gc_lnum == lnum) { + *free = c->leb_size; + *dirty = 0; + return 0; + } + } else { + if (!test_bit(index, FSCK(c)->used_lebs)) { + *free = c->leb_size; + *dirty = 0; + return 0; + } + } + + sleb = ubifs_scan(c, lnum, 0, c->sbuf, 0); + if (IS_ERR(sleb)) { + /* All TNC LEBs have passed ubifs_scan in previous steps. */ + ubifs_assert(c, !get_failure_reason_callback(c)); + return PTR_ERR(sleb); + } + + idx_leb = -1; + used = 0; + list_for_each_entry(snod, &sleb->nodes, list) { + int found, level = 0; + + if (idx_leb == -1) + idx_leb = (snod->type == UBIFS_IDX_NODE) ? 1 : 0; + + if (idx_leb) + /* + * Previous steps have ensured that every TNC LEB + * contains only index nodes or non-index nodes. + */ + ubifs_assert(c, snod->type == UBIFS_IDX_NODE); + + if (snod->type == UBIFS_IDX_NODE) { + struct ubifs_idx_node *idx = snod->node; + + key_read(c, ubifs_idx_key(c, idx), &snod->key); + level = le16_to_cpu(idx->level); + } + + found = ubifs_tnc_has_node(c, &snod->key, level, lnum, + snod->offs, idx_leb); + if (found) { + if (found < 0) { + err = found; + /* + * TNC traversing is finished in previous steps, + * any TNC path is accessible. + */ + ubifs_assert(c, !get_failure_reason_callback(c)); + goto out; + } + used += ALIGN(snod->len, 8); + } + } + + if (is_build_lpt && !used) { + *free = c->leb_size; + *dirty = 0; + } else { + *free = c->leb_size - sleb->endpt; + *dirty = sleb->endpt - used; + if (idx_leb == 1) { + if (is_build_lpt) + FSCK(c)->lpts[index].flags = LPROPS_INDEX; + else + *is_idx = 1; + } + } + +out: + ubifs_scan_destroy(sleb); + return err; +} + +static void clear_buds(struct ubifs_info *c) +{ + int i; + + /* + * Since lpt is invalid, space statistics cannot be trusted, the buds + * were used to trace taken LEBs(LPT related), and fsck makes sure that + * there will be no new journal writings(no space allocations) before + * committing, so we should clear buds to prevent wrong lpt updating in + * committing stage(eg. ubifs_return_leb operation for @c->old_buds). + */ + free_buds(c, true); + for (i = 0; i < c->jhead_cnt; i++) { + c->jheads[i].wbuf.lnum = -1; + c->jheads[i].wbuf.offs = -1; + } +} + +static void clear_lp_lists_and_heaps(struct ubifs_info *c) +{ + int i; + + /* + * Since lpt is invalid, clear in-memory fast accessing paths (lp + * lists & heaps). + */ + c->freeable_cnt = 0; + c->in_a_category_cnt = 0; + for (i = 0; i < LPROPS_HEAP_CNT; i++) { + memset(c->lpt_heap[i].arr, 0, LPT_HEAP_SZ * sizeof(void *)); + c->lpt_heap[i].cnt = 0; + c->lpt_heap[i].max_cnt = LPT_HEAP_SZ; + } + memset(c->dirty_idx.arr, 0, LPT_HEAP_SZ * sizeof(void *)); + c->dirty_idx.cnt = 0; + c->dirty_idx.max_cnt = LPT_HEAP_SZ; + INIT_LIST_HEAD(&c->uncat_list); + INIT_LIST_HEAD(&c->empty_list); + INIT_LIST_HEAD(&c->freeable_list); + INIT_LIST_HEAD(&c->frdi_idx_list); +} + +static int retake_ihead(struct ubifs_info *c) +{ + int err = take_ihead(c); + + if (err < 0) { + /* All LPT nodes must be accessible. */ + ubifs_assert(c, !get_failure_reason_callback(c)); + ubifs_assert(c, FSCK(c)->lpt_status == 0); + } else + err = 0; + + return err; +} + +static int rebuild_lpt(struct ubifs_info *c) +{ + int err; + + /* Clear buds. */ + clear_buds(c); + /* Clear stale in-memory lpt data. */ + c->lpt_drty_flgs = 0; + c->dirty_nn_cnt = 0; + c->dirty_pn_cnt = 0; + clear_lp_lists_and_heaps(c); + ubifs_free_lpt_nodes(c); + kfree(c->ltab); + c->ltab = NULL; + + FSCK(c)->lpts = kzalloc(sizeof(struct ubifs_lprops) * c->main_lebs, + GFP_KERNEL); + if (!FSCK(c)->lpts) { + log_err(c, errno, "can not allocate lpts"); + return -ENOMEM; + } + + err = build_lpt(c, scan_get_lp, false); + if (err) + goto out; + + err = retake_ihead(c); + if (err) + goto out; + + FSCK(c)->lpt_status = 0; + +out: + kfree(FSCK(c)->lpts); + return err; +} + +static void check_and_correct_nnode(struct ubifs_info *c, + struct ubifs_nnode *nnode, + struct ubifs_nnode *parent_nnode, + int row, int col, int *corrected) +{ + int num = ubifs_calc_nnode_num(row, col); + + if (nnode->num != num) { + struct nnode_problem nnp = { + .nnode = nnode, + .parent_nnode = parent_nnode, + .num = num, + }; + + /* + * The nnode number is read from disk in big lpt mode, which + * could lead to the wrong nnode number, otherwise, ther nnode + * number cannot be wrong. + */ + ubifs_assert(c, c->big_lpt); + FSCK(c)->lpt_status |= FR_LPT_INCORRECT; + if (fix_problem(c, NNODE_INCORRECT, &nnp)) { + nnode->num = num; + *corrected = 1; + } + } +} + +static int check_and_correct_pnode(struct ubifs_info *c, + struct ubifs_pnode *pnode, int col, + struct ubifs_lp_stats *lst, + int *freeable_cnt, int *corrected) +{ + int i, index, lnum; + const int lp_cnt = UBIFS_LPT_FANOUT; + + if (pnode->num != col) { + struct pnode_problem pnp = { + .pnode = pnode, + .num = col, + }; + + /* + * The pnode number is read from disk in big lpt mode, which + * could lead to the wrong pnode number, otherwise, ther pnode + * number cannot be wrong. + */ + ubifs_assert(c, c->big_lpt); + FSCK(c)->lpt_status |= FR_LPT_INCORRECT; + if (fix_problem(c, PNODE_INCORRECT, &pnp)) { + pnode->num = col; + *corrected = 1; + } + } + + index = pnode->num << UBIFS_LPT_FANOUT_SHIFT; + lnum = index + c->main_first; + for (i = 0; i < lp_cnt && lnum < c->leb_cnt; i++, index++, lnum++) { + int err, cat, free, dirty, is_idx = 0; + struct ubifs_lprops *lp = &pnode->lprops[i]; + + err = scan_get_lp(c, index, &free, &dirty, &is_idx); + if (err) + return err; + + dbg_fsck("calculate properties for LEB %d, free %d dirty %d is_idx %d, in %s", + lnum, free, dirty, is_idx, c->dev_name); + + if (!FSCK(c)->lpt_status && lp->free + lp->dirty == c->leb_size + && !test_bit(index, FSCK(c)->used_lebs)) { + /* + * Some LEBs may become freeable in the following cases: + * a. LEBs become freeable after replaying the journal. + * b. Unclean reboot while doing gc for a freeable + * non-index LEB + * c. Freeable index LEBs in an uncompleted commit due + * to an unclean unmount. + * , which makes that these LEBs won't be accounted into + * the FSCK(c)->used_lebs, but they actually have + * free/dirty space statistics. So we should skip + * checking space for these LEBs. + */ + free = lp->free; + dirty = lp->dirty; + is_idx = (lp->flags & LPROPS_INDEX) ? 1 : 0; + } + if (lnum != lp->lnum || + free != lp->free || dirty != lp->dirty || + (is_idx && !(lp->flags & LPROPS_INDEX)) || + (!is_idx && (lp->flags & LPROPS_INDEX))) { + struct lp_problem lpp = { + .lnum = lnum, + .lp = lp, + .free = free, + .dirty = dirty, + .is_idx = is_idx, + }; + + FSCK(c)->lpt_status |= FR_LPT_INCORRECT; + if (fix_problem(c, LP_INCORRECT, &lpp)) { + lp->lnum = lnum; + lp->free = free; + lp->dirty = dirty; + lp->flags = is_idx ? LPROPS_INDEX : 0; + *corrected = 1; + } + } + + cat = ubifs_categorize_lprops(c, lp); + if (cat != (lp->flags & LPROPS_CAT_MASK)) { + if (FSCK(c)->lpt_status & FR_LPT_INCORRECT) { + lp->flags &= ~LPROPS_CAT_MASK; + lp->flags |= cat; + } else { + /* lp could be in the heap or un-categorized(add heap failed). */ + ubifs_assert(c, (lp->flags & LPROPS_CAT_MASK) == LPROPS_UNCAT); + } + } + if (cat == LPROPS_FREEABLE) + *freeable_cnt = *freeable_cnt + 1; + if ((lp->flags & LPROPS_TAKEN) && free == c->leb_size) + lst->taken_empty_lebs += 1; + + lst->total_free += free; + lst->total_dirty += dirty; + + if (free == c->leb_size) + lst->empty_lebs++; + + if (is_idx) { + lst->idx_lebs += 1; + } else { + int spc; + + spc = free + dirty; + if (spc < c->dead_wm) + lst->total_dead += spc; + else + lst->total_dark += ubifs_calc_dark(c, spc); + lst->total_used += c->leb_size - spc; + } + } + + return 0; +} + +static int check_and_correct_lpt(struct ubifs_info *c, int *lpt_corrected) +{ + int err, i, cnt, iip, row, col, corrected, lnum, max_num, freeable_cnt; + struct ubifs_cnode *cn, *cnode; + struct ubifs_nnode *nnode, *nn; + struct ubifs_pnode *pnode; + struct ubifs_lp_stats lst; + + max_num = 0; + freeable_cnt = 0; + memset(&lst, 0, sizeof(struct ubifs_lp_stats)); + + /* Load the entire LPT tree, check whether there are corrupted nodes. */ + cnt = DIV_ROUND_UP(c->main_lebs, UBIFS_LPT_FANOUT); + for (i = 0; i < cnt; i++) { + pnode = ubifs_pnode_lookup(c, i); + if (IS_ERR(pnode)) + return PTR_ERR(pnode); + if (pnode->num > max_num) + max_num = pnode->num; + } + + /* Check whether there are pnodes exceeding the 'c->main_lebs'. */ + pnode = ubifs_pnode_lookup(c, 0); + if (IS_ERR(pnode)) + return PTR_ERR(pnode); + while (pnode) { + if (pnode->num > max_num) { + ubifs_err(c, "pnode(%d) exceeds max number(%d)", + pnode->num, max_num); + set_failure_reason_callback(c, FR_LPT_CORRUPTED); + return -EINVAL; + } + pnode = ubifs_find_next_pnode(c, pnode); + if (IS_ERR(pnode)) + return PTR_ERR(pnode); + } + + /* Check & correct nnodes and pnodes(including LEB properties). */ + row = col = iip = 0; + cnode = (struct ubifs_cnode *)c->nroot; + while (cnode) { + ubifs_assert(c, row >= 0); + nnode = cnode->parent; + if (cnode->level) { + corrected = 0; + /* cnode is a nnode */ + nn = (struct ubifs_nnode *)cnode; + check_and_correct_nnode(c, nn, nnode, row, col, + &corrected); + if (corrected) + ubifs_make_nnode_dirty(c, nn); + while (iip < UBIFS_LPT_FANOUT) { + cn = nn->nbranch[iip].cnode; + if (cn) { + /* Go down */ + row += 1; + col <<= UBIFS_LPT_FANOUT_SHIFT; + col += iip; + iip = 0; + cnode = cn; + break; + } + /* Go right */ + iip += 1; + } + if (iip < UBIFS_LPT_FANOUT) + continue; + } else { + corrected = 0; + /* cnode is a pnode */ + pnode = (struct ubifs_pnode *)cnode; + err = check_and_correct_pnode(c, pnode, col, &lst, + &freeable_cnt, &corrected); + if (err) + return err; + if (corrected) + ubifs_make_pnode_dirty(c, pnode); + } + /* Go up and to the right */ + row -= 1; + col >>= UBIFS_LPT_FANOUT_SHIFT; + iip = cnode->iip + 1; + cnode = (struct ubifs_cnode *)nnode; + } + + dbg_fsck("empty_lebs %d, idx_lebs %d, total_free %lld, total_dirty %lld," + " total_used %lld, total_dead %lld, total_dark %lld," + " taken_empty_lebs %d, freeable_cnt %d, in %s", + lst.empty_lebs, lst.idx_lebs, lst.total_free, lst.total_dirty, + lst.total_used, lst.total_dead, lst.total_dark, + lst.taken_empty_lebs, freeable_cnt, c->dev_name); + + /* Check & correct the global space statistics. */ + if (lst.empty_lebs != c->lst.empty_lebs || + lst.idx_lebs != c->lst.idx_lebs || + lst.total_free != c->lst.total_free || + lst.total_dirty != c->lst.total_dirty || + lst.total_used != c->lst.total_used || + lst.total_dead != c->lst.total_dead || + lst.total_dark != c->lst.total_dark) { + struct space_stat_problem ssp = { + .lst = &c->lst, + .calc_lst = &lst, + }; + + FSCK(c)->lpt_status |= FR_LPT_INCORRECT; + if (fix_problem(c, SPACE_STAT_INCORRECT, &ssp)) { + c->lst.empty_lebs = lst.empty_lebs; + c->lst.idx_lebs = lst.idx_lebs; + c->lst.total_free = lst.total_free; + c->lst.total_dirty = lst.total_dirty; + c->lst.total_used = lst.total_used; + c->lst.total_dead = lst.total_dead; + c->lst.total_dark = lst.total_dark; + } + } + + /* Check & correct the lprops table information. */ + for (lnum = c->lpt_first; lnum <= c->lpt_last; lnum++) { + err = dbg_check_ltab_lnum(c, lnum); + if (err) + return err; + } + + if (FSCK(c)->lpt_status & FR_LPT_INCORRECT) { + /* Reset the taken_empty_lebs. */ + c->lst.taken_empty_lebs = 0; + /* Clear buds. */ + clear_buds(c); + /* Clear lp lists & heaps. */ + clear_lp_lists_and_heaps(c); + /* + * Build lp lists & heaps, subsequent steps could recover + * disconnected files by allocating free space. + */ + for (lnum = c->main_first; lnum < c->leb_cnt; lnum++) { + int cat; + struct ubifs_lprops *lp = ubifs_lpt_lookup(c, lnum); + if (IS_ERR(lp)) + return PTR_ERR(lp); + + /* Clear %LPROPS_TAKEN flag for all LEBs. */ + lp->flags &= ~LPROPS_TAKEN; + cat = lp->flags & LPROPS_CAT_MASK; + ubifs_add_to_cat(c, lp, cat); + } + /* + * The %LPROPS_TAKEN flag is cleared in LEB properties, just + * remark it for c->ihead_lnum LEB. + */ + err = retake_ihead(c); + if (err) + return err; + + *lpt_corrected = 1; + FSCK(c)->lpt_status &= ~FR_LPT_INCORRECT; + } else { + ubifs_assert(c, c->freeable_cnt == freeable_cnt); + ubifs_assert(c, c->lst.taken_empty_lebs == lst.taken_empty_lebs); + ubifs_assert(c, c->in_a_category_cnt == c->main_lebs); + } + + return 0; +} + +/** + * check_and_correct_space - check & correct the space statistics. + * @c: UBIFS file-system description object + * + * This function does following things: + * 1. Check fsck mode, exit program if current mode is check mode. + * 2. Check space statistics by comparing lpt records with scanning results + * for all main LEBs. There could be following problems: + * a) comparison result is inconsistent: correct the lpt records by LEB + * scanning results. + * b) lpt is corrupted: rebuild lpt. + * 3. Set the gc lnum. + * Returns zero in case of success, a negative error code in case of failure. + */ +int check_and_correct_space(struct ubifs_info *c) +{ + int err, lpt_corrected = 0; + + if (FSCK(c)->mode == CHECK_MODE) { + /* + * The check mode will exit, because unclean LEBs are not + * rewritten for readonly mode in previous steps. + */ + if (FSCK(c)->lpt_status) + exit_code |= FSCK_UNCORRECTED; + dbg_fsck("skip checking & correcting space%s, in %s", + mode_name(c), c->dev_name); + exit(exit_code); + } + + log_out(c, "Check and correct the space statistics"); + + if (FSCK(c)->lpt_status & FR_LPT_CORRUPTED) { +rebuild: + if (fix_problem(c, LPT_CORRUPTED, NULL)) + return rebuild_lpt(c); + } + + err = check_and_correct_lpt(c, &lpt_corrected); + if (err) { + if (test_and_clear_failure_reason_callback(c, FR_LPT_CORRUPTED)) + goto rebuild; + return err; + } + + /* Set gc lnum. */ + if (c->need_recovery || lpt_corrected) { + err = ubifs_rcvry_gc_commit(c); + if (err) { + /* All LPT nodes must be accessible. */ + ubifs_assert(c, !get_failure_reason_callback(c)); + ubifs_assert(c, FSCK(c)->lpt_status == 0); + return err; + } + } else { + err = take_gc_lnum(c); + if (err) { + /* All LPT nodes must be accessible. */ + ubifs_assert(c, !get_failure_reason_callback(c)); + ubifs_assert(c, FSCK(c)->lpt_status == 0); + return err; + } + err = ubifs_leb_unmap(c, c->gc_lnum); + if (err) + return err; + } + + return err; +} + +/** + * check_and_correct_index_size - check & correct the index size. + * @c: UBIFS file-system description object + * + * This function checks and corrects the index size by traversing TNC: Returns + * zero in case of success, a negative error code in case of failure. + */ +int check_and_correct_index_size(struct ubifs_info *c) +{ + int err; + unsigned long long index_size = 0; + + ubifs_assert(c, c->bi.old_idx_sz == c->calc_idx_sz); + err = dbg_walk_index(c, NULL, add_size, &index_size); + if (err) { + /* All TNC nodes must be accessible. */ + ubifs_assert(c, !get_failure_reason_callback(c)); + return err; + } + + dbg_fsck("total index size %llu, in %s", index_size, c->dev_name); + if (index_size != c->calc_idx_sz && + fix_problem(c, INCORRECT_IDX_SZ, &index_size)) + c->bi.old_idx_sz = c->calc_idx_sz = index_size; + + return 0; +} diff --git a/ubifs-utils/fsck.ubifs/extract_files.c b/ubifs-utils/fsck.ubifs/extract_files.c new file mode 100644 index 0000000..000ef5d --- /dev/null +++ b/ubifs-utils/fsck.ubifs/extract_files.c @@ -0,0 +1,1576 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2024, Huawei Technologies Co, Ltd. + * + * Authors: Zhihao Cheng <chengzhihao1@huawei.com> + */ + +#include <stdio.h> +#include <stdlib.h> +#include <getopt.h> +#include <sys/stat.h> + +#include <linux/limits.h> + +#include "linux_err.h" +#include "bitops.h" +#include "kmem.h" +#include "crc32.h" +#include "ubifs.h" +#include "defs.h" +#include "debug.h" +#include "key.h" +#include "misc.h" +#include "fsck.ubifs.h" + +static void parse_node_header(int lnum, int offs, int len, + unsigned long long sqnum, + struct scanned_node *header) +{ + header->exist = true; + header->lnum = lnum; + header->offs = offs; + header->len = len; + header->sqnum = sqnum; +} + +static inline bool inode_can_be_encrypted(struct ubifs_info *c, + struct scanned_ino_node *ino_node) +{ + if (!c->encrypted) + return false; + + if (ino_node->is_xattr) + return false; + + /* Only regular files, directories, and symlinks can be encrypted. */ + if (S_ISREG(ino_node->mode) || S_ISDIR(ino_node->mode) || + S_ISLNK(ino_node->mode)) + return true; + + return false; +} + +/** + * parse_ino_node - parse inode node and check it's validity. + * @c: UBIFS file-system description object + * @lnum: logical eraseblock number + * @offs: the offset in LEB of the raw inode node + * @node: raw node + * @key: key of node scanned (if it has one) + * @ino_node: node used to store raw inode information + * + * This function checks the raw inode information, and stores inode + * information into @ino_node. Returns %true if the inode is valid, + * otherwise %false is returned. + */ +bool parse_ino_node(struct ubifs_info *c, int lnum, int offs, void *node, + union ubifs_key *key, struct scanned_ino_node *ino_node) +{ + bool valid = false; + int data_len, node_len; + unsigned int flags; + unsigned long long sqnum; + struct ubifs_ch *ch = (struct ubifs_ch *)node; + struct ubifs_ino_node *ino = (struct ubifs_ino_node *)node; + ino_t inum = key_inum(c, key); + + if (!inum || inum > INUM_WATERMARK) { + if (FSCK(c)->mode == REBUILD_MODE) + dbg_fsck("bad inode node(bad inum %lu) at %d:%d, in %s", + inum, lnum, offs, c->dev_name); + else + log_out(c, "bad inode node(bad inum %lu) at %d:%d", + inum, lnum, offs); + goto out; + } + + if (ch->node_type != key_type(c, key)) { + if (FSCK(c)->mode == REBUILD_MODE) + dbg_fsck("bad inode node %lu(inconsistent node type %d vs key_type %d) at %d:%d, in %s", + inum, ch->node_type, key_type(c, key), + lnum, offs, c->dev_name); + else + log_out(c, "bad inode node %lu(inconsistent node type %d vs key_type %d) at %d:%d", + inum, ch->node_type, key_type(c, key), + lnum, offs); + goto out; + } + + node_len = le32_to_cpu(ch->len); + sqnum = le64_to_cpu(ch->sqnum); + key_copy(c, key, &ino_node->key); + flags = le32_to_cpu(ino->flags); + data_len = le32_to_cpu(ino->data_len); + ino_node->is_xattr = !!(flags & UBIFS_XATTR_FL) ? 1 : 0; + ino_node->is_encrypted = !!(flags & UBIFS_CRYPT_FL) ? 1 : 0; + ino_node->mode = le32_to_cpu(ino->mode); + ino_node->nlink = le32_to_cpu(ino->nlink); + ino_node->xcnt = le32_to_cpu(ino->xattr_cnt); + ino_node->xsz = le32_to_cpu(ino->xattr_size); + ino_node->xnms = le32_to_cpu(ino->xattr_names); + ino_node->size = le64_to_cpu(ino->size); + + if (inum == UBIFS_ROOT_INO && !S_ISDIR(ino_node->mode)) { + if (FSCK(c)->mode == REBUILD_MODE) + dbg_fsck("bad inode node %lu(root inode is not dir, tyoe %u) at %d:%d, in %s", + inum, ino_node->mode & S_IFMT, lnum, offs, + c->dev_name); + else + log_out(c, "bad inode node %lu(root inode is not dir, tyoe %u) at %d:%d", + inum, ino_node->mode & S_IFMT, lnum, offs); + goto out; + } + + if (ino_node->size > c->max_inode_sz) { + if (FSCK(c)->mode == REBUILD_MODE) + dbg_fsck("bad inode node %lu(size %llu is too large) at %d:%d, in %s", + inum, ino_node->size, lnum, offs, c->dev_name); + else + log_out(c, "bad inode node %lu(size %llu is too large) at %d:%d", + inum, ino_node->size, lnum, offs); + goto out; + } + + if (le16_to_cpu(ino->compr_type) >= UBIFS_COMPR_TYPES_CNT) { + if (FSCK(c)->mode == REBUILD_MODE) + dbg_fsck("bad inode node %lu(unknown compression type %d) at %d:%d, in %s", + inum, le16_to_cpu(ino->compr_type), lnum, offs, + c->dev_name); + else + log_out(c, "bad inode node %lu(unknown compression type %d) at %d:%d", + inum, le16_to_cpu(ino->compr_type), lnum, offs); + goto out; + } + + if (ino_node->xnms + ino_node->xcnt > XATTR_LIST_MAX) { + if (FSCK(c)->mode == REBUILD_MODE) + dbg_fsck("bad inode node %lu(too big xnames %u xcount %u) at %d:%d, in %s", + inum, ino_node->xnms, ino_node->xcnt, + lnum, offs, c->dev_name); + else + log_out(c, "bad inode node %lu(too big xnames %u xcount %u) at %d:%d", + inum, ino_node->xnms, ino_node->xcnt, + lnum, offs); + goto out; + } + + if (data_len < 0 || data_len > UBIFS_MAX_INO_DATA) { + if (FSCK(c)->mode == REBUILD_MODE) + dbg_fsck("bad inode node %lu(invalid data len %d) at %d:%d, in %s", + inum, data_len, lnum, offs, c->dev_name); + else + log_out(c, "bad inode node %lu(invalid data len %d) at %d:%d", + inum, data_len, lnum, offs); + goto out; + } + + if (UBIFS_INO_NODE_SZ + data_len != node_len) { + if (FSCK(c)->mode == REBUILD_MODE) + dbg_fsck("bad inode node %lu(inconsistent data len %d vs node len %d) at %d:%d, in %s", + inum, data_len, node_len, lnum, offs, c->dev_name); + else + log_out(c, "bad inode node %lu(inconsistent data len %d vs node len %d) at %d:%d", + inum, data_len, node_len, lnum, offs); + goto out; + } + + if (ino_node->is_xattr) { + if (!S_ISREG(ino_node->mode)) { + if (FSCK(c)->mode == REBUILD_MODE) + dbg_fsck("bad inode node %lu(bad type %u for xattr) at %d:%d, in %s", + inum, ino_node->mode & S_IFMT, + lnum, offs, c->dev_name); + else + log_out(c, "bad inode node %lu(bad type %u for xattr) at %d:%d", + inum, ino_node->mode & S_IFMT, + lnum, offs); + goto out; + } + if (data_len != ino_node->size) { + if (FSCK(c)->mode == REBUILD_MODE) + dbg_fsck("bad inode node %lu(inconsistent data_len %d vs size %llu for xattr) at %d:%d, in %s", + inum, data_len, ino_node->size, + lnum, offs, c->dev_name); + else + log_out(c, "bad inode node %lu(inconsistent data_len %d vs size %llu for xattr) at %d:%d", + inum, data_len, ino_node->size, + lnum, offs); + goto out; + } + if (ino_node->xcnt || ino_node->xsz || ino_node->xnms) { + if (FSCK(c)->mode == REBUILD_MODE) + dbg_fsck("bad inode node %lu(non zero xattr count %u xattr size %u xattr names %u for xattr) at %d:%d, in %s", + inum, ino_node->xcnt, ino_node->xsz, + ino_node->xnms, lnum, offs, c->dev_name); + else + log_out(c, "bad inode node %lu(non zero xattr count %u xattr size %u xattr names %u for xattr) at %d:%d", + inum, ino_node->xcnt, ino_node->xsz, + ino_node->xnms, lnum, offs); + goto out; + } + } + + switch (ino_node->mode & S_IFMT) { + case S_IFREG: + if (!ino_node->is_xattr && data_len != 0) { + if (FSCK(c)->mode == REBUILD_MODE) + dbg_fsck("bad inode node %lu(bad data len %d for reg file) at %d:%d, in %s", + inum, data_len, lnum, offs, c->dev_name); + else + log_out(c, "bad inode node %lu(bad data len %d for reg file) at %d:%d", + inum, data_len, lnum, offs); + goto out; + } + break; + case S_IFDIR: + if (data_len != 0) { + if (FSCK(c)->mode == REBUILD_MODE) + dbg_fsck("bad inode node %lu(bad data len %d for dir file) at %d:%d, in %s", + inum, data_len, lnum, offs, c->dev_name); + else + log_out(c, "bad inode node %lu(bad data len %d for dir file) at %d:%d", + inum, data_len, lnum, offs); + goto out; + } + break; + case S_IFLNK: + if (data_len == 0) { + /* + * For encryption enabled or selinux enabled situation, + * uninitialized inode with xattrs could be written + * before ubifs_jnl_update(). If the dent node is + * written successfully but the initialized inode is + * not written, ubifs_iget() will get bad symlink inode + * with 'ui->data_len = 0'. Similar phenomenon can also + * occur for block/char dev creation. + * Just drop the inode node when above class of + * exceptions are found. + */ + if (FSCK(c)->mode == REBUILD_MODE) + dbg_fsck("bad symlink inode node %lu(bad data len %d) at %d:%d, in %s", + inum, data_len, lnum, offs, c->dev_name); + else + log_out(c, "bad symlink inode node %lu(bad data len %d) at %d:%d", + inum, data_len, lnum, offs); + goto out; + } + break; + case S_IFBLK: + fallthrough; + case S_IFCHR: + { + union ubifs_dev_desc *dev = (union ubifs_dev_desc *)ino->data; + int sz_new = sizeof(dev->new), sz_huge = sizeof(dev->huge); + + if (data_len != sz_new && data_len != sz_huge) { + if (FSCK(c)->mode == REBUILD_MODE) + dbg_fsck("bad inode node %lu(bad data len %d for char/block file, expect %d or %d) at %d:%d, in %s", + inum, data_len, sz_new, sz_huge, lnum, + offs, c->dev_name); + else + log_out(c, "bad inode node %lu(bad data len %d for char/block file, expect %d or %d) at %d:%d", + inum, data_len, sz_new, sz_huge, lnum, + offs); + goto out; + } + break; + } + case S_IFSOCK: + fallthrough; + case S_IFIFO: + if (data_len != 0) { + if (FSCK(c)->mode == REBUILD_MODE) + dbg_fsck("bad inode node %lu(bad data len %d for fifo/sock file) at %d:%d, in %s", + inum, data_len, lnum, offs, c->dev_name); + else + log_out(c, "bad inode node %lu(bad data len %d for fifo/sock file) at %d:%d", + inum, data_len, lnum, offs); + goto out; + } + break; + default: + /* invalid file type. */ + if (FSCK(c)->mode == REBUILD_MODE) + dbg_fsck("bad inode node %lu(unknown type %u) at %d:%d, in %s", + inum, ino_node->mode & S_IFMT, lnum, offs, c->dev_name); + else + log_out(c, "bad inode node %lu(unknown type %u) at %d:%d", + inum, ino_node->mode & S_IFMT, lnum, offs); + goto out; + } + + if (ino_node->is_encrypted && !inode_can_be_encrypted(c, ino_node)) { + if (FSCK(c)->mode == REBUILD_MODE) + dbg_fsck("bad inode node %lu(encrypted but cannot be encrypted, type %u, is_xattr %d, fs_encrypted %d) at %d:%d, in %s", + inum, ino_node->mode & S_IFMT, ino_node->is_xattr, + c->encrypted, lnum, offs, c->dev_name); + else + log_out(c, "bad inode node %lu(encrypted but cannot be encrypted, type %u, is_xattr %d, fs_encrypted %d) at %d:%d", + inum, ino_node->mode & S_IFMT, ino_node->is_xattr, + c->encrypted, lnum, offs); + goto out; + } + + valid = true; + parse_node_header(lnum, offs, node_len, sqnum, &ino_node->header); + +out: + return valid; +} + +/** + * parse_dent_node - parse dentry node and check it's validity. + * @c: UBIFS file-system description object + * @lnum: logical eraseblock number + * @offs: the offset in LEB of the raw inode node + * @node: raw node + * @key: key of node scanned (if it has one) + * @dent_node: node used to store raw dentry information + * + * This function checks the raw dentry/(xattr entry) information, and + * stores dentry/(xattr entry) information into @dent_node. Returns + * %true if the entry is valid, otherwise %false is returned. + */ +bool parse_dent_node(struct ubifs_info *c, int lnum, int offs, void *node, + union ubifs_key *key, struct scanned_dent_node *dent_node) +{ + bool valid = false; + int node_len, nlen; + unsigned long long sqnum; + struct ubifs_ch *ch = (struct ubifs_ch *)node; + struct ubifs_dent_node *dent = (struct ubifs_dent_node *)node; + int key_type = key_type_flash(c, dent->key); + ino_t inum; + + nlen = le16_to_cpu(dent->nlen); + node_len = le32_to_cpu(ch->len); + sqnum = le64_to_cpu(ch->sqnum); + inum = le64_to_cpu(dent->inum); + + if (node_len != nlen + UBIFS_DENT_NODE_SZ + 1 || + dent->type >= UBIFS_ITYPES_CNT || + nlen > UBIFS_MAX_NLEN || dent->name[nlen] != 0 || + (key_type == UBIFS_XENT_KEY && + strnlen((const char *)dent->name, nlen) != nlen) || + inum > INUM_WATERMARK || key_type != ch->node_type) { + if (FSCK(c)->mode == REBUILD_MODE) + dbg_fsck("bad %s node(len %d nlen %d type %d inum %lu key_type %d node_type %d) at %d:%d, in %s", + ch->node_type == UBIFS_XENT_NODE ? "xattr entry" : "directory entry", + node_len, nlen, dent->type, inum, key_type, + ch->node_type, lnum, offs, c->dev_name); + else + log_out(c, "bad %s node(len %d nlen %d type %d inum %lu key_type %d node_type %d) at %d:%d", + ch->node_type == UBIFS_XENT_NODE ? "xattr entry" : "directory entry", + node_len, nlen, dent->type, inum, key_type, + ch->node_type, lnum, offs); + goto out; + } + + key_copy(c, key, &dent_node->key); + dent_node->can_be_found = false; + dent_node->type = dent->type; + dent_node->nlen = nlen; + memcpy(dent_node->name, dent->name, nlen); + dent_node->name[nlen] = '\0'; + dent_node->inum = inum; + + valid = true; + parse_node_header(lnum, offs, node_len, sqnum, &dent_node->header); + +out: + return valid; +} + +/** + * parse_data_node - parse data node and check it's validity. + * @c: UBIFS file-system description object + * @lnum: logical eraseblock number + * @offs: the offset in LEB of the raw data node + * @node: raw node + * @key: key of node scanned (if it has one) + * @ino_node: node used to store raw data information + * + * This function checks the raw data node information, and stores + * data node information into @data_node. Returns %true if the data + * node is valid, otherwise %false is returned. + */ +bool parse_data_node(struct ubifs_info *c, int lnum, int offs, void *node, + union ubifs_key *key, struct scanned_data_node *data_node) +{ + bool valid = false; + int node_len; + unsigned long long sqnum; + struct ubifs_ch *ch = (struct ubifs_ch *)node; + struct ubifs_data_node *dn = (struct ubifs_data_node *)node; + ino_t inum = key_inum(c, key); + + if (ch->node_type != key_type(c, key)) { + if (FSCK(c)->mode == REBUILD_MODE) + dbg_fsck("bad data node(inconsistent node type %d vs key_type %d) at %d:%d, in %s", + ch->node_type, key_type(c, key), + lnum, offs, c->dev_name); + else + log_out(c, "bad data node(inconsistent node type %d vs key_type %d) at %d:%d", + ch->node_type, key_type(c, key), lnum, offs); + goto out; + } + + if (!inum || inum > INUM_WATERMARK) { + if (FSCK(c)->mode == REBUILD_MODE) + dbg_fsck("bad data node(bad inum %lu) at %d:%d, in %s", + inum, lnum, offs, c->dev_name); + else + log_out(c, "bad data node(bad inum %lu) at %d:%d", + inum, lnum, offs); + goto out; + } + + node_len = le32_to_cpu(ch->len); + sqnum = le64_to_cpu(ch->sqnum); + key_copy(c, key, &data_node->key); + data_node->size = le32_to_cpu(dn->size); + + if (!data_node->size || data_node->size > UBIFS_BLOCK_SIZE) { + if (FSCK(c)->mode == REBUILD_MODE) + dbg_fsck("bad data node(invalid size %u) at %d:%d, in %s", + data_node->size, lnum, offs, c->dev_name); + else + log_out(c, "bad data node(invalid size %u) at %d:%d", + data_node->size, lnum, offs); + goto out; + } + + if (le16_to_cpu(dn->compr_type) >= UBIFS_COMPR_TYPES_CNT) { + if (FSCK(c)->mode == REBUILD_MODE) + dbg_fsck("bad data node(invalid compression type %d) at %d:%d, in %s", + le16_to_cpu(dn->compr_type), lnum, offs, c->dev_name); + else + log_out(c, "bad data node(invalid compression type %d) at %d:%d", + le16_to_cpu(dn->compr_type), lnum, offs); + goto out; + } + + valid = true; + parse_node_header(lnum, offs, node_len, sqnum, &data_node->header); + +out: + return valid; +} + +/** + * parse_trun_node - parse truncation node and check it's validity. + * @c: UBIFS file-system description object + * @lnum: logical eraseblock number + * @offs: the offset in LEB of the raw truncation node + * @node: raw node + * @key: key of node scanned (if it has one) + * @trun_node: node used to store raw truncation information + * + * This function checks the raw truncation information, and stores + * truncation information into @trun_node. Returns %true if the + * truncation is valid, otherwise %false is returned. + */ +bool parse_trun_node(struct ubifs_info *c, int lnum, int offs, void *node, + union ubifs_key *key, struct scanned_trun_node *trun_node) +{ + bool valid = false; + int node_len; + unsigned long long sqnum; + struct ubifs_ch *ch = (struct ubifs_ch *)node; + struct ubifs_trun_node *trun = (struct ubifs_trun_node *)node; + loff_t old_size = le64_to_cpu(trun->old_size); + loff_t new_size = le64_to_cpu(trun->new_size); + ino_t inum = le32_to_cpu(trun->inum); + + if (!inum || inum > INUM_WATERMARK) { + dbg_fsck("bad truncation node(bad inum %lu) at %d:%d, in %s", + inum, lnum, offs, c->dev_name); + goto out; + } + + node_len = le32_to_cpu(ch->len); + sqnum = le64_to_cpu(ch->sqnum); + trun_node->new_size = new_size; + + if (old_size < 0 || old_size > c->max_inode_sz || + new_size < 0 || new_size > c->max_inode_sz || + old_size <= new_size) { + dbg_fsck("bad truncation node(new size %ld old size %ld inum %lu) at %d:%d, in %s", + new_size, old_size, inum, lnum, offs, c->dev_name); + goto out; + } + + trun_key_init(c, key, inum); + valid = true; + parse_node_header(lnum, offs, node_len, sqnum, &trun_node->header); + +out: + return valid; +} + +/** + * insert_file_dentry - insert dentry according to scanned dent node. + * @file: file object + * @n_dent: scanned dent node + * + * Insert file dentry information. Returns zero in case of success, a + * negative error code in case of failure. + */ +static int insert_file_dentry(struct scanned_file *file, + struct scanned_dent_node *n_dent) +{ + struct scanned_dent_node *dent; + struct rb_node **p, *parent = NULL; + + p = &file->dent_nodes.rb_node; + while (*p) { + parent = *p; + dent = rb_entry(parent, struct scanned_dent_node, rb); + if (n_dent->header.sqnum < dent->header.sqnum) + p = &(*p)->rb_left; + else + p = &(*p)->rb_right; + } + + dent = kmalloc(sizeof(struct scanned_dent_node), GFP_KERNEL); + if (!dent) + return -ENOMEM; + + *dent = *n_dent; + rb_link_node(&dent->rb, parent, p); + rb_insert_color(&dent->rb, &file->dent_nodes); + + return 0; +} + +/** + * update_file_data - insert/update data according to scanned data node. + * @c: UBIFS file-system description object + * @file: file object + * @n_dn: scanned data node + * + * Insert or update file data information. Returns zero in case of success, + * a negative error code in case of failure. + */ +static int update_file_data(struct ubifs_info *c, struct scanned_file *file, + struct scanned_data_node *n_dn) +{ + int cmp; + struct scanned_data_node *dn, *o_dn = NULL; + struct rb_node **p, *parent = NULL; + + p = &file->data_nodes.rb_node; + while (*p) { + parent = *p; + dn = rb_entry(parent, struct scanned_data_node, rb); + cmp = keys_cmp(c, &n_dn->key, &dn->key); + if (cmp < 0) { + p = &(*p)->rb_left; + } else if (cmp > 0) { + p = &(*p)->rb_right; + } else { + o_dn = dn; + break; + } + } + + if (o_dn) { + /* found data node with same block no. */ + if (o_dn->header.sqnum < n_dn->header.sqnum) { + o_dn->header = n_dn->header; + o_dn->size = n_dn->size; + } + + return 0; + } + + dn = kmalloc(sizeof(struct scanned_data_node), GFP_KERNEL); + if (!dn) + return -ENOMEM; + + *dn = *n_dn; + INIT_LIST_HEAD(&dn->list); + rb_link_node(&dn->rb, parent, p); + rb_insert_color(&dn->rb, &file->data_nodes); + + return 0; +} + +/** + * update_file - update file information. + * @c: UBIFS file-system description object + * @file: file object + * @sn: scanned node + * @key_type: type of @sn + * + * Update inode/dent/truncation/data node information of @file. Returns + * zero in case of success, a negative error code in case of failure. + */ +static int update_file(struct ubifs_info *c, struct scanned_file *file, + struct scanned_node *sn, int key_type) +{ + int err = 0; + + switch (key_type) { + case UBIFS_INO_KEY: + { + struct scanned_ino_node *o_ino, *n_ino; + + o_ino = &file->ino; + n_ino = (struct scanned_ino_node *)sn; + if (o_ino->header.exist && o_ino->header.sqnum > sn->sqnum) + goto out; + + *o_ino = *n_ino; + break; + } + case UBIFS_DENT_KEY: + case UBIFS_XENT_KEY: + { + struct scanned_dent_node *dent = (struct scanned_dent_node *)sn; + + dent->file = file; + err = insert_file_dentry(file, dent); + break; + } + case UBIFS_DATA_KEY: + { + struct scanned_data_node *dn = (struct scanned_data_node *)sn; + + err = update_file_data(c, file, dn); + break; + } + case UBIFS_TRUN_KEY: + { + struct scanned_trun_node *o_trun, *n_trun; + + o_trun = &file->trun; + n_trun = (struct scanned_trun_node *)sn; + if (o_trun->header.exist && o_trun->header.sqnum > sn->sqnum) + goto out; + + *o_trun = *n_trun; + break; + } + default: + err = -EINVAL; + log_err(c, 0, "unknown key type %d", key_type); + } + +out: + return err; +} + +/** + * insert_or_update_file - insert or update file according to scanned node. + * @c: UBIFS file-system description object + * @file_tree: tree of all scanned files + * @sn: scanned node + * @key_type: key type of @sn + * @inum: inode number + * + * According to @sn, this function inserts file into the tree, or updates + * file information if it already exists in the tree. Returns zero in case + * of success, a negative error code in case of failure. + */ +int insert_or_update_file(struct ubifs_info *c, struct rb_root *file_tree, + struct scanned_node *sn, int key_type, ino_t inum) +{ + int err; + struct scanned_file *file, *old_file = NULL; + struct rb_node **p, *parent = NULL; + + p = &file_tree->rb_node; + while (*p) { + parent = *p; + file = rb_entry(parent, struct scanned_file, rb); + if (inum < file->inum) { + p = &(*p)->rb_left; + } else if (inum > file->inum) { + p = &(*p)->rb_right; + } else { + old_file = file; + break; + } + } + if (old_file) + return update_file(c, old_file, sn, key_type); + + file = kzalloc(sizeof(struct scanned_file), GFP_KERNEL); + if (!file) + return -ENOMEM; + + file->inum = inum; + file->dent_nodes = RB_ROOT; + file->data_nodes = RB_ROOT; + file->xattr_files = RB_ROOT; + INIT_LIST_HEAD(&file->list); + err = update_file(c, file, sn, key_type); + if (err) { + kfree(file); + return err; + } + rb_link_node(&file->rb, parent, p); + rb_insert_color(&file->rb, file_tree); + + return 0; +} + +/** + * destroy_file_content - destroy scanned data/dentry nodes in give file. + * @c: UBIFS file-system description object + * @file: file object + * + * Destroy all data/dentry nodes and xattrs attached to @file. + */ +void destroy_file_content(struct ubifs_info *c, struct scanned_file *file) +{ + struct scanned_data_node *data_node; + struct scanned_dent_node *dent_node; + struct scanned_file *xattr_file; + struct rb_node *this; + + this = rb_first(&file->data_nodes); + while (this) { + data_node = rb_entry(this, struct scanned_data_node, rb); + this = rb_next(this); + + rb_erase(&data_node->rb, &file->data_nodes); + kfree(data_node); + } + + this = rb_first(&file->dent_nodes); + while (this) { + dent_node = rb_entry(this, struct scanned_dent_node, rb); + this = rb_next(this); + + rb_erase(&dent_node->rb, &file->dent_nodes); + kfree(dent_node); + } + + this = rb_first(&file->xattr_files); + while (this) { + xattr_file = rb_entry(this, struct scanned_file, rb); + this = rb_next(this); + + ubifs_assert(c, !rb_first(&xattr_file->xattr_files)); + destroy_file_content(c, xattr_file); + rb_erase(&xattr_file->rb, &file->xattr_files); + kfree(xattr_file); + } +} + +/** + * destroy_file_tree - destroy files from a given tree. + * @c: UBIFS file-system description object + * @file_tree: tree of all scanned files + * + * Destroy scanned files from a given tree. + */ +void destroy_file_tree(struct ubifs_info *c, struct rb_root *file_tree) +{ + struct scanned_file *file; + struct rb_node *this; + + this = rb_first(file_tree); + while (this) { + file = rb_entry(this, struct scanned_file, rb); + this = rb_next(this); + + destroy_file_content(c, file); + + rb_erase(&file->rb, file_tree); + kfree(file); + } +} + +/** + * destroy_file_list - destroy files from a given list head. + * @c: UBIFS file-system description object + * @file_list: list of the scanned files + * + * Destroy scanned files from a given list. + */ +void destroy_file_list(struct ubifs_info *c, struct list_head *file_list) +{ + struct scanned_file *file; + + while (!list_empty(file_list)) { + file = list_entry(file_list->next, struct scanned_file, list); + + destroy_file_content(c, file); + list_del(&file->list); + kfree(file); + } +} + +/** + * lookup_file - lookup file according to inode number. + * @file_tree: tree of all scanned files + * @inum: inode number + * + * This function lookups target file from @file_tree according to @inum. + */ +struct scanned_file *lookup_file(struct rb_root *file_tree, ino_t inum) +{ + struct scanned_file *file; + struct rb_node *p; + + p = file_tree->rb_node; + while (p) { + file = rb_entry(p, struct scanned_file, rb); + + if (inum < file->inum) + p = p->rb_left; + else if (inum > file->inum) + p = p->rb_right; + else + return file; + } + + return NULL; +} + +static void handle_invalid_file(struct ubifs_info *c, int problem_type, + struct scanned_file *file, void *priv) +{ + struct invalid_file_problem ifp = { + .file = file, + .priv = priv, + }; + + if (FSCK(c)->mode == REBUILD_MODE) + return; + + fix_problem(c, problem_type, &ifp); +} + +static int delete_node(struct ubifs_info *c, const union ubifs_key *key, + int lnum, int offs) +{ + int err; + + err = ubifs_tnc_remove_node(c, key, lnum, offs); + if (err) { + /* TNC traversing is finished, any TNC path is accessible */ + ubifs_assert(c, !get_failure_reason_callback(c)); + } + + return err; +} + +static int delete_dent_nodes(struct ubifs_info *c, struct scanned_file *file, + int err) +{ + int ret = 0; + struct rb_node *this = rb_first(&file->dent_nodes); + struct scanned_dent_node *dent_node; + + while (this) { + dent_node = rb_entry(this, struct scanned_dent_node, rb); + this = rb_next(this); + + if (!err) { + err = delete_node(c, &dent_node->key, + dent_node->header.lnum, dent_node->header.offs); + if (err) + ret = ret ? ret : err; + } + + rb_erase(&dent_node->rb, &file->dent_nodes); + kfree(dent_node); + } + + return ret; +} + +int delete_file(struct ubifs_info *c, struct scanned_file *file) +{ + int err = 0, ret = 0; + struct rb_node *this; + struct scanned_file *xattr_file; + struct scanned_data_node *data_node; + + if (file->ino.header.exist) { + err = delete_node(c, &file->ino.key, file->ino.header.lnum, + file->ino.header.offs); + if (err) + ret = ret ? ret : err; + } + + this = rb_first(&file->data_nodes); + while (this) { + data_node = rb_entry(this, struct scanned_data_node, rb); + this = rb_next(this); + + if (!err) { + err = delete_node(c, &data_node->key, + data_node->header.lnum, data_node->header.offs); + if (err) + ret = ret ? ret : err; + } + + rb_erase(&data_node->rb, &file->data_nodes); + kfree(data_node); + } + + err = delete_dent_nodes(c, file, err); + if (err) + ret = ret ? : err; + + this = rb_first(&file->xattr_files); + while (this) { + xattr_file = rb_entry(this, struct scanned_file, rb); + this = rb_next(this); + + ubifs_assert(c, !rb_first(&xattr_file->xattr_files)); + err = delete_file(c, xattr_file); + if (err) + ret = ret ? ret : err; + rb_erase(&xattr_file->rb, &file->xattr_files); + kfree(xattr_file); + } + + return ret; +} + +/** + * insert_xattr_file - insert xattr file into file's subtree. + * @c: UBIFS file-system description object + * @xattr_file: xattr file + * @host_file: host file + * + * This inserts xattr file into its' host file's subtree. + */ +static void insert_xattr_file(struct ubifs_info *c, + struct scanned_file *xattr_file, + struct scanned_file *host_file) +{ + struct scanned_file *tmp_xattr_file; + struct rb_node **p, *parent = NULL; + + p = &host_file->xattr_files.rb_node; + while (*p) { + parent = *p; + tmp_xattr_file = rb_entry(parent, struct scanned_file, rb); + if (xattr_file->inum < tmp_xattr_file->inum) { + p = &(*p)->rb_left; + } else if (xattr_file->inum > tmp_xattr_file->inum) { + p = &(*p)->rb_right; + } else { + /* Impossible: Same xattr file is inserted twice. */ + ubifs_assert(c, 0); + } + } + + rb_link_node(&xattr_file->rb, parent, p); + rb_insert_color(&xattr_file->rb, &host_file->xattr_files); +} + +/** + * file_is_valid - check whether the file is valid. + * @c: UBIFS file-system description object + * @file: file object + * @file_tree: tree of all scanned files + * @is_diconnected: reason of invalid file, whether the @file is disconnected + * + * This function checks whether given @file is valid, following checks will + * be performed: + * 1. All files have none-zero nlink inode, otherwise they are invalid. + * 2. The file type comes from inode and dentries should be consistent, + * inconsistent dentries will be deleted. + * 3. Directory type or xattr type files only have one dentry. Superfluous + * dentries with lower sequence number will be deleted. + * 4. Non-regular file doesn't have data nodes. Data nodes are deleted for + * non-regular file. + * 5. All files must have at least one dentries, except '/', '/' doesn't + * have dentries. Non '/' file is invalid if it doesn't have dentries. + * 6. Xattr files should have host inode, and host inode cannot be a xattr, + * otherwise they are invalid. + * 7. Encrypted files should have corresponding xattrs, otherwise they are + * invalid. + * Xattr file will be inserted into corresponding host file's subtree. + * + * Returns %1 is @file is valid, %0 if @file is invalid, otherwise a negative + * error code in case of failure. + * Notice: All xattr files should be traversed before non-xattr files, because + * checking item 7 depends on it. + */ +int file_is_valid(struct ubifs_info *c, struct scanned_file *file, + struct rb_root *file_tree, int *is_diconnected) +{ + int type; + struct rb_node *node; + struct scanned_file *parent_file = NULL; + struct scanned_dent_node *dent_node; + struct scanned_data_node *data_node; + LIST_HEAD(drop_list); + + dbg_fsck("check validation of file %lu, in %s", file->inum, c->dev_name); + + if (!file->ino.header.exist) { + handle_invalid_file(c, FILE_HAS_NO_INODE, file, NULL); + return 0; + } + + if (!file->ino.nlink) { + handle_invalid_file(c, FILE_HAS_0_NLINK_INODE, file, NULL); + return 0; + } + + type = ubifs_get_dent_type(file->ino.mode); + + /* Drop dentry nodes with inconsistent type. */ + for (node = rb_first(&file->dent_nodes); node; node = rb_next(node)) { + int is_xattr = 0; + + dent_node = rb_entry(node, struct scanned_dent_node, rb); + + if (key_type(c, &dent_node->key) == UBIFS_XENT_KEY) + is_xattr = 1; + if (is_xattr != file->ino.is_xattr || type != dent_node->type) + list_add(&dent_node->list, &drop_list); + } + + while (!list_empty(&drop_list)) { + dent_node = list_entry(drop_list.next, struct scanned_dent_node, + list); + + handle_invalid_file(c, FILE_HAS_INCONSIST_TYPE, file, dent_node); + if (FSCK(c)->mode != REBUILD_MODE) { + int err = delete_node(c, &dent_node->key, + dent_node->header.lnum, dent_node->header.offs); + if (err) + return err; + } + + list_del(&dent_node->list); + rb_erase(&dent_node->rb, &file->dent_nodes); + kfree(dent_node); + } + + if (type != UBIFS_ITYPE_DIR && !file->ino.is_xattr) + goto check_data_nodes; + + /* Make sure that directory/xattr type files only have one dentry. */ + node = rb_first(&file->dent_nodes); + while (node) { + dent_node = rb_entry(node, struct scanned_dent_node, rb); + node = rb_next(node); + if (!node) + break; + + handle_invalid_file(c, FILE_HAS_TOO_MANY_DENT, file, dent_node); + if (FSCK(c)->mode != REBUILD_MODE) { + int err = delete_node(c, &dent_node->key, + dent_node->header.lnum, dent_node->header.offs); + if (err) + return err; + } + + rb_erase(&dent_node->rb, &file->dent_nodes); + kfree(dent_node); + } + +check_data_nodes: + if (type == UBIFS_ITYPE_REG && !file->ino.is_xattr) + goto check_dent_node; + + /* Make sure that non regular type files not have data/trun nodes. */ + file->trun.header.exist = 0; + node = rb_first(&file->data_nodes); + while (node) { + data_node = rb_entry(node, struct scanned_data_node, rb); + node = rb_next(node); + + handle_invalid_file(c, FILE_SHOULDNT_HAVE_DATA, file, data_node); + if (FSCK(c)->mode != REBUILD_MODE) { + int err = delete_node(c, &data_node->key, + data_node->header.lnum, data_node->header.offs); + if (err) + return err; + } + + rb_erase(&data_node->rb, &file->data_nodes); + kfree(data_node); + } + +check_dent_node: + if (rb_first(&file->dent_nodes)) { + if (file->inum == UBIFS_ROOT_INO) { + /* '/' has no dentries. */ + handle_invalid_file(c, FILE_ROOT_HAS_DENT, file, + rb_entry(rb_first(&file->dent_nodes), + struct scanned_dent_node, rb)); + return 0; + } + + node = rb_first(&file->dent_nodes); + dent_node = rb_entry(node, struct scanned_dent_node, rb); + parent_file = lookup_file(file_tree, key_inum(c, &dent_node->key)); + } else { + /* Non-root files must have dentries. */ + if (file->inum != UBIFS_ROOT_INO) { + if (type == UBIFS_ITYPE_REG && !file->ino.is_xattr) { + handle_invalid_file(c, FILE_IS_DISCONNECTED, + file, NULL); + if (is_diconnected) + *is_diconnected = 1; + } else { + handle_invalid_file(c, FILE_HAS_NO_DENT, + file, NULL); + } + return 0; + } + } + + if (file->ino.is_xattr) { + if (!parent_file) { + /* Host inode is not found. */ + handle_invalid_file(c, XATTR_HAS_NO_HOST, file, NULL); + return 0; + } + if (parent_file->ino.is_xattr) { + /* Host cannot be a xattr file. */ + handle_invalid_file(c, XATTR_HAS_WRONG_HOST, file, parent_file); + return 0; + } + + insert_xattr_file(c, file, parent_file); + if (parent_file->ino.is_encrypted) { + int nlen = min(dent_node->nlen, + strlen(UBIFS_XATTR_NAME_ENCRYPTION_CONTEXT)); + + if (!strncmp(dent_node->name, + UBIFS_XATTR_NAME_ENCRYPTION_CONTEXT, nlen)) + parent_file->has_encrypted_info = true; + } + } else { + if (parent_file && !S_ISDIR(parent_file->ino.mode)) { + /* Parent file should be directory. */ + if (type == UBIFS_ITYPE_REG) { + handle_invalid_file(c, FILE_IS_DISCONNECTED, + file, NULL); + if (FSCK(c)->mode != REBUILD_MODE) { + /* Delete dentries for the disconnected file. */ + int err = delete_dent_nodes(c, file, 0); + if (err) + return err; + } + if (is_diconnected) + *is_diconnected = 1; + } + return 0; + } + + /* + * Since xattr files are checked in first round, so all + * non-xattr files's @has_encrypted_info fields have been + * initialized. + */ + if (file->ino.is_encrypted && !file->has_encrypted_info) { + handle_invalid_file(c, FILE_HAS_NO_ENCRYPT, file, NULL); + return 0; + } + } + + return 1; +} + +static bool dentry_is_reachable(struct ubifs_info *c, + struct scanned_dent_node *dent_node, + struct list_head *path_list, + struct rb_root *file_tree) +{ + struct scanned_file *parent_file = NULL; + struct scanned_dent_node *dn, *parent_dent; + struct rb_node *p; + + /* Check whether the path is cyclical. */ + list_for_each_entry(dn, path_list, list) { + if (dn == dent_node) + return false; + } + + /* Quick path, dentry has already been checked as reachable. */ + if (dent_node->can_be_found) + return true; + + dent_node->can_be_found = true; + list_add(&dent_node->list, path_list); + + parent_file = lookup_file(file_tree, key_inum(c, &dent_node->key)); + /* Parent dentry is not found, unreachable. */ + if (!parent_file) + return false; + + /* Parent dentry is '/', reachable. */ + if (parent_file->inum == UBIFS_ROOT_INO) + return true; + + p = rb_first(&parent_file->dent_nodes); + if (!p) + return false; + parent_dent = rb_entry(p, struct scanned_dent_node, rb); + + return dentry_is_reachable(c, parent_dent, path_list, file_tree); +} + +/** + * file_is_reachable - whether the file can be found from '/'. + * @c: UBIFS file-system description object + * @file: file object + * @file_tree: tree of all scanned files + * + * This function iterates all directory entries in given @file and checks + * whether each dentry is reachable. All unreachable directory entries will + * be removed. + */ +bool file_is_reachable(struct ubifs_info *c, struct scanned_file *file, + struct rb_root *file_tree) +{ + struct rb_node *node; + struct scanned_dent_node *dent_node; + + if (file->inum == UBIFS_ROOT_INO) + goto reachable; + +retry: + for (node = rb_first(&file->dent_nodes); node; node = rb_next(node)) { + LIST_HEAD(path_list); + + dent_node = rb_entry(node, struct scanned_dent_node, rb); + + if (dentry_is_reachable(c, dent_node, &path_list, file_tree)) + continue; + + while (!list_empty(&path_list)) { + dent_node = list_entry(path_list.next, + struct scanned_dent_node, list); + + handle_invalid_file(c, DENTRY_IS_UNREACHABLE, + dent_node->file, dent_node); + if (FSCK(c)->mode != REBUILD_MODE) { + int err = delete_node(c, &dent_node->key, + dent_node->header.lnum, + dent_node->header.offs); + if (err) + return err; + } + dbg_fsck("remove unreachable dentry %s, in %s", + c->encrypted && !file->ino.is_xattr ? + "<encrypted>" : dent_node->name, c->dev_name); + list_del(&dent_node->list); + rb_erase(&dent_node->rb, &dent_node->file->dent_nodes); + kfree(dent_node); + } + + /* Since dentry node is removed from rb-tree, rescan rb-tree. */ + goto retry; + } + + if (!rb_first(&file->dent_nodes)) { + if (S_ISREG(file->ino.mode)) + handle_invalid_file(c, FILE_IS_DISCONNECTED, file, NULL); + else + handle_invalid_file(c, FILE_HAS_NO_DENT, file, NULL); + dbg_fsck("file %lu is unreachable, in %s", file->inum, c->dev_name); + return false; + } + +reachable: + dbg_fsck("file %lu is reachable, in %s", file->inum, c->dev_name); + return true; +} + +/** + * calculate_file_info - calculate the information of file + * @c: UBIFS file-system description object + * @file: file object + * @file_tree: tree of all scanned files + * + * This function calculates file information according to dentry nodes, + * data nodes and truncation node. The calculated informaion will be used + * to correct inode node. + */ +static int calculate_file_info(struct ubifs_info *c, struct scanned_file *file, + struct rb_root *file_tree) +{ + int nlink = 0; + bool corrupted_truncation = false; + unsigned long long ino_sqnum, trun_size = 0, new_size = 0, trun_sqnum = 0; + struct rb_node *node; + struct scanned_file *parent_file, *xattr_file; + struct scanned_dent_node *dent_node; + struct scanned_data_node *data_node; + LIST_HEAD(drop_list); + + for (node = rb_first(&file->xattr_files); node; node = rb_next(node)) { + xattr_file = rb_entry(node, struct scanned_file, rb); + dent_node = rb_entry(rb_first(&xattr_file->dent_nodes), + struct scanned_dent_node, rb); + + ubifs_assert(c, xattr_file->ino.is_xattr); + ubifs_assert(c, !rb_first(&xattr_file->xattr_files)); + xattr_file->calc_nlink = 1; + xattr_file->calc_size = xattr_file->ino.size; + + file->calc_xcnt += 1; + file->calc_xsz += CALC_DENT_SIZE(dent_node->nlen); + file->calc_xsz += CALC_XATTR_BYTES(xattr_file->ino.size); + file->calc_xnms += dent_node->nlen; + } + + if (file->inum == UBIFS_ROOT_INO) { + file->calc_nlink += 2; + file->calc_size += UBIFS_INO_NODE_SZ; + return 0; + } + + if (S_ISDIR(file->ino.mode)) { + file->calc_nlink += 2; + file->calc_size += UBIFS_INO_NODE_SZ; + + dent_node = rb_entry(rb_first(&file->dent_nodes), + struct scanned_dent_node, rb); + parent_file = lookup_file(file_tree, key_inum(c, &dent_node->key)); + if (!parent_file) { + ubifs_assert(c, 0); + return 0; + } + parent_file->calc_nlink += 1; + parent_file->calc_size += CALC_DENT_SIZE(dent_node->nlen); + return 0; + } + + for (node = rb_first(&file->dent_nodes); node; node = rb_next(node)) { + nlink++; + + dent_node = rb_entry(node, struct scanned_dent_node, rb); + + parent_file = lookup_file(file_tree, key_inum(c, &dent_node->key)); + if (!parent_file) { + ubifs_assert(c, 0); + return 0; + } + parent_file->calc_size += CALC_DENT_SIZE(dent_node->nlen); + } + file->calc_nlink = nlink; + + if (!S_ISREG(file->ino.mode)) { + /* No need to verify i_size for symlink/sock/block/char/fifo. */ + file->calc_size = file->ino.size; + return 0; + } + + /* + * Process i_size and data content, following situations should + * be considered: + * 1. Sequential writing or overwriting, i_size should be + * max(i_size, data node size), pick larger sqnum one from + * data nodes with same block index. + * 2. Mixed truncation and writing, i_size depends on the latest + * truncation node or inode node or last data node, pick data + * nodes which are not truncated. + * 3. Setting bigger i_size attr, pick inode size or biggest + * i_size calculated by data nodes. + */ + if (file->trun.header.exist) { + trun_size = file->trun.new_size; + trun_sqnum = file->trun.header.sqnum; + } + ino_sqnum = file->ino.header.sqnum; + for (node = rb_first(&file->data_nodes); node; node = rb_next(node)) { + unsigned long long d_sz, d_sqnum; + unsigned int block_no; + + data_node = rb_entry(node, struct scanned_data_node, rb); + + d_sqnum = data_node->header.sqnum; + block_no = key_block(c, &data_node->key); + d_sz = data_node->size + block_no * UBIFS_BLOCK_SIZE; + if ((trun_sqnum > d_sqnum && trun_size < d_sz) || + (ino_sqnum > d_sqnum && file->ino.size < d_sz)) { + /* + * The truncated data nodes are not gced after + * truncating, just remove them. + */ + list_add(&data_node->list, &drop_list); + } else { + new_size = max_t(unsigned long long, new_size, d_sz); + } + } + /* + * Truncation node is written successful, but inode node is not. It + * won't happen because inode node is written before truncation node + * according to ubifs_jnl_truncate(), unless only inode is corrupted. + * In this case, data nodes could have been removed in history mounting + * recovery, so i_size needs to be updated. + */ + if (trun_sqnum > ino_sqnum && trun_size < file->ino.size) { + if (trun_size < new_size) { + corrupted_truncation = true; + /* + * Appendant writing after truncation and newest inode + * is not fell on disk. + */ + goto update_isize; + } + + /* + * Overwriting happens after truncation and newest inode is + * not fell on disk. + */ + file->calc_size = trun_size; + goto drop_data; + } +update_isize: + /* + * The file cannot use 'new_size' directly when the file may have ever + * been set i_size. For example: + * 1. echo 123 > file # i_size = 4 + * 2. truncate -s 100 file # i_size = 100 + * After scanning, new_size is 4. Apperantly the size of 'file' should + * be 100. So, the calculated new_size according to data nodes should + * only be used for extending i_size, like ubifs_recover_size() does. + */ + if (new_size > file->ino.size || corrupted_truncation) + file->calc_size = new_size; + else + file->calc_size = file->ino.size; + +drop_data: + while (!list_empty(&drop_list)) { + data_node = list_entry(drop_list.next, struct scanned_data_node, + list); + + if (FSCK(c)->mode != REBUILD_MODE) { + /* + * Don't ask, inconsistent file correcting will be + * asked in function correct_file_info(). + */ + int err = delete_node(c, &data_node->key, + data_node->header.lnum, data_node->header.offs); + if (err) + return err; + } + list_del(&data_node->list); + rb_erase(&data_node->rb, &file->data_nodes); + kfree(data_node); + } + + return 0; +} + +/** + * correct_file_info - correct the information of file + * @c: UBIFS file-system description object + * @file: file object + * + * This function corrects file information according to calculated fields, + * eg. 'calc_nlink', 'calc_xcnt', 'calc_xsz', 'calc_xnms' and 'calc_size'. + * Corrected inode node will be re-written. + */ +static int correct_file_info(struct ubifs_info *c, struct scanned_file *file) +{ + uint32_t crc; + int err, lnum, len; + struct rb_node *node; + struct ubifs_ino_node *ino; + struct scanned_file *xattr_file; + + for (node = rb_first(&file->xattr_files); node; node = rb_next(node)) { + xattr_file = rb_entry(node, struct scanned_file, rb); + + err = correct_file_info(c, xattr_file); + if (err) + return err; + } + + if (file->calc_nlink == file->ino.nlink && + file->calc_xcnt == file->ino.xcnt && + file->calc_xsz == file->ino.xsz && + file->calc_xnms == file->ino.xnms && + file->calc_size == file->ino.size) + return 0; + + handle_invalid_file(c, FILE_IS_INCONSISTENT, file, NULL); + lnum = file->ino.header.lnum; + dbg_fsck("correct file(inum:%lu type:%s), nlink %u->%u, xattr cnt %u->%u, xattr size %u->%u, xattr names %u->%u, size %llu->%llu, at %d:%d, in %s", + file->inum, file->ino.is_xattr ? "xattr" : + ubifs_get_type_name(ubifs_get_dent_type(file->ino.mode)), + file->ino.nlink, file->calc_nlink, + file->ino.xcnt, file->calc_xcnt, + file->ino.xsz, file->calc_xsz, + file->ino.xnms, file->calc_xnms, + file->ino.size, file->calc_size, + lnum, file->ino.header.offs, c->dev_name); + + err = ubifs_leb_read(c, lnum, c->sbuf, 0, c->leb_size, 0); + if (err && err != -EBADMSG) + return err; + + ino = c->sbuf + file->ino.header.offs; + ino->nlink = cpu_to_le32(file->calc_nlink); + ino->xattr_cnt = cpu_to_le32(file->calc_xcnt); + ino->xattr_size = cpu_to_le32(file->calc_xsz); + ino->xattr_names = cpu_to_le32(file->calc_xnms); + ino->size = cpu_to_le64(file->calc_size); + len = le32_to_cpu(ino->ch.len); + crc = crc32(UBIFS_CRC32_INIT, (void *)ino + 8, len - 8); + ino->ch.crc = cpu_to_le32(crc); + + /* Atomically write the fixed LEB back again */ + return ubifs_leb_change(c, lnum, c->sbuf, c->leb_size); +} + +/** + * check_and_correct_files - check and correct information of files. + * @c: UBIFS file-system description object + * + * This function does similar things with dbg_check_filesystem(), besides, + * it also corrects file information if the calculated information is not + * consistent with information from flash. + */ +int check_and_correct_files(struct ubifs_info *c) +{ + int err; + struct rb_node *node; + struct scanned_file *file; + struct rb_root *tree = &FSCK(c)->scanned_files; + + for (node = rb_first(tree); node; node = rb_next(node)) { + file = rb_entry(node, struct scanned_file, rb); + + err = calculate_file_info(c, file, tree); + if (err) + return err; + } + + for (node = rb_first(tree); node; node = rb_next(node)) { + file = rb_entry(node, struct scanned_file, rb); + + err = correct_file_info(c, file); + if (err) + return err; + } + + if (list_empty(&FSCK(c)->disconnected_files)) + return 0; + + ubifs_assert(c, FSCK(c)->mode != REBUILD_MODE); + list_for_each_entry(file, &FSCK(c)->disconnected_files, list) { + err = calculate_file_info(c, file, tree); + if (err) + return err; + + /* Reset disconnected file's nlink as one. */ + file->calc_nlink = 1; + err = correct_file_info(c, file); + if (err) + return err; + } + + return 0; +} diff --git a/ubifs-utils/fsck.ubifs/fsck.ubifs.c b/ubifs-utils/fsck.ubifs/fsck.ubifs.c new file mode 100644 index 0000000..6ca0b57 --- /dev/null +++ b/ubifs-utils/fsck.ubifs/fsck.ubifs.c @@ -0,0 +1,636 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2024, Huawei Technologies Co, Ltd. + * + * Authors: Zhihao Cheng <chengzhihao1@huawei.com> + */ + +#include <stdio.h> +#include <stdlib.h> +#include <getopt.h> +#include <signal.h> + +#include "bitops.h" +#include "kmem.h" +#include "ubifs.h" +#include "defs.h" +#include "debug.h" +#include "key.h" +#include "misc.h" +#include "fsck.ubifs.h" + +/* + * Because we copy functions from the kernel, we use a subset of the UBIFS + * file-system description object struct ubifs_info. + */ +struct ubifs_info info_; +static struct ubifs_info *c = &info_; + +int exit_code = FSCK_OK; + +static const char *optstring = "Vrg:abyn"; + +static const struct option longopts[] = { + {"version", 0, NULL, 'V'}, + {"reserve", 1, NULL, 'r'}, + {"debug", 1, NULL, 'g'}, + {"auto", 1, NULL, 'a'}, + {"rebuild", 1, NULL, 'b'}, + {"yes", 1, NULL, 'y'}, + {"nochange", 1, NULL, 'n'}, + {NULL, 0, NULL, 0} +}; + +static const char *helptext = +"Usage: fsck.ubifs [OPTIONS] ubi_volume\n" +"Check & repair UBIFS filesystem on a given UBI volume\n\n" +"Options:\n" +"-V, --version Display version information\n" +"-g, --debug=LEVEL Display debug information (0 - none, 1 - error message,\n" +" 2 - warning message[default], 3 - notice message, 4 - debug message)\n" +"-a, --auto Automatic safely repair without droping data (No questions).\n" +" Can not be specified at the same time as the -y or -n options\n" +"-y, --yes Assume \"yes\" to all questions. Automatic repair and report dropping data (No questions).\n" +" There are two submodes for this working mode:\n" +" a. default - Fail if TNC/master/log is corrupted. Only -y option is specified\n" +" b. rebuild fs - Turn to rebuild fs if TNC/master/log is corrupted. Specify -b option to make effect\n" +" Can not be specified at the same time as the -a or -n options\n" +"-b, --rebuild Forcedly repair the filesystem even by rebuilding filesystem.\n" +" Depends on -y option\n" +"-n, --nochange Make no changes to the filesystem, only check filesystem.\n" +" This mode don't check space, because unclean LEBs are not rewritten in readonly mode.\n" +" Can not be specified at the same time as the -a or -y options\n" +"Examples:\n" +"\t1. Check and repair filesystem from UBI volume /dev/ubi0_0\n" +"\t fsck.ubifs /dev/ubi0_0\n" +"\t2. Only check without modifying filesystem from UBI volume /dev/ubi0_0\n" +"\t fsck.ubifs -n /dev/ubi0_0\n" +"\t3. Check and safely repair filesystem from UBI volume /dev/ubi0_0\n" +"\t fsck.ubifs -a /dev/ubi0_0\n" +"\t4. Check and forcedly repair filesystem from UBI volume /dev/ubi0_0\n" +"\t fsck.ubifs -y -b /dev/ubi0_0\n\n"; + +static inline void usage(void) +{ + printf("%s", helptext); + exit_code |= FSCK_USAGE; + exit(exit_code); +} + +static void get_options(int argc, char *argv[], int *mode) +{ + int opt, i, submode = 0; + char *endp; + + while (1) { + opt = getopt_long(argc, argv, optstring, longopts, &i); + if (opt == -1) + break; + switch (opt) { + case 'V': + common_print_version(); + exit(FSCK_OK); + case 'g': + c->debug_level = strtol(optarg, &endp, 0); + if (*endp != '\0' || endp == optarg || + c->debug_level < 0 || c->debug_level > DEBUG_LEVEL) { + log_err(c, 0, "bad debugging level '%s'", optarg); + usage(); + } + break; + case 'a': + if (*mode != NORMAL_MODE) { +conflict_opt: + log_err(c, 0, "Only one of the options -a, -n or -y may be specified"); + usage(); + } + *mode = SAFE_MODE; + break; + case 'y': + if (*mode != NORMAL_MODE) + goto conflict_opt; + *mode = DANGER_MODE0; + break; + case 'b': + submode = 1; + break; + case 'n': + if (*mode != NORMAL_MODE) + goto conflict_opt; + *mode = CHECK_MODE; + c->ro_mount = 1; + break; + case 'r': + /* Compatible with FSCK(8). */ + break; + default: + usage(); + } + } + + if (submode) { + if (*mode != DANGER_MODE0) { + log_err(c, 0, "Option -y is not specified when -b is used"); + usage(); + } else + *mode = DANGER_MODE1; + } + + if (optind != argc) { + c->dev_name = strdup(argv[optind]); + if (!c->dev_name) { + log_err(c, errno, "can not allocate dev_name"); + exit_code |= FSCK_ERROR; + exit(exit_code); + } + } + + if (!c->dev_name) { + log_err(c, 0, "no ubi_volume specified"); + usage(); + } +} + +static void exit_callback(void) +{ + if (exit_code & FSCK_NONDESTRUCT) + log_out(c, "********** Filesystem was modified **********"); + if (exit_code & FSCK_UNCORRECTED) + log_out(c, "********** WARNING: Filesystem still has errors **********"); + if (exit_code & ~(FSCK_OK|FSCK_NONDESTRUCT)) + log_out(c, "FSCK failed, exit code %d", exit_code); + else + log_out(c, "FSCK success!"); +} + +static void fsck_assert_failed(__unused const struct ubifs_info *c) +{ + exit_code |= FSCK_ERROR; + exit(exit_code); +} + +static void fsck_set_failure_reason(const struct ubifs_info *c, + unsigned int reason) +{ + if (FSCK(c)->mode == REBUILD_MODE) + return; + + FSCK(c)->failure_reason = reason; + if (reason & FR_LPT_CORRUPTED) { + log_out(c, "Found corrupted pnode/nnode, set lpt corrupted"); + FSCK(c)->lpt_status |= FR_LPT_CORRUPTED; + } + if (reason & FR_LPT_INCORRECT) { + log_out(c, "Bad space statistics, set lpt incorrect"); + FSCK(c)->lpt_status |= FR_LPT_INCORRECT; + } +} + +static unsigned int fsck_get_failure_reason(const struct ubifs_info *c) +{ + ubifs_assert(c, FSCK(c)->mode != REBUILD_MODE); + + return FSCK(c)->failure_reason; +} + +static void fsck_clear_failure_reason(const struct ubifs_info *c) +{ + ubifs_assert(c, FSCK(c)->mode != REBUILD_MODE); + + FSCK(c)->failure_reason = 0; +} + +static bool fsck_test_and_clear_failure_reason(const struct ubifs_info *c, + unsigned int reason) +{ + bool res = (FSCK(c)->failure_reason & reason) != 0; + + ubifs_assert(c, FSCK(c)->mode != REBUILD_MODE); + ubifs_assert(c, !(FSCK(c)->failure_reason & (~reason))); + + FSCK(c)->failure_reason = 0; + + return res; +} + +static void fsck_set_lpt_invalid(const struct ubifs_info *c, + unsigned int reason) +{ + ubifs_assert(c, FSCK(c)->mode != REBUILD_MODE); + + if (reason & FR_LPT_CORRUPTED) { + log_out(c, "Found corrupted pnode/nnode, set lpt corrupted"); + FSCK(c)->lpt_status |= FR_LPT_CORRUPTED; + } + if (reason & FR_LPT_INCORRECT) { + log_out(c, "Bad space statistics, set lpt incorrect"); + FSCK(c)->lpt_status |= FR_LPT_INCORRECT; + } +} + +static bool fsck_test_lpt_valid(const struct ubifs_info *c, int lnum, + int old_free, int old_dirty, + int free, int dirty) +{ + ubifs_assert(c, FSCK(c)->mode != REBUILD_MODE); + + if (c->cmt_state != COMMIT_RESTING) + /* Don't skip updating lpt when do commit. */ + goto out; + + if (FSCK(c)->lpt_status) + return false; + + if (c->lst.empty_lebs < 0 || c->lst.empty_lebs > c->main_lebs) { + log_out(c, "Bad empty_lebs %d(main_lebs %d), set lpt incorrect", + c->lst.empty_lebs, c->main_lebs); + goto out_invalid; + } + if (c->freeable_cnt < 0 || c->freeable_cnt > c->main_lebs) { + log_out(c, "Bad freeable_cnt %d(main_lebs %d), set lpt incorrect", + c->freeable_cnt, c->main_lebs); + goto out_invalid; + } + if (c->lst.taken_empty_lebs < 0 || + c->lst.taken_empty_lebs > c->lst.empty_lebs) { + log_out(c, "Bad taken_empty_lebs %d(empty_lebs %d), set lpt incorrect", + c->lst.taken_empty_lebs, c->lst.empty_lebs); + goto out_invalid; + } + if (c->lst.total_free & 7) { + log_out(c, "total_free(%lld) is not 8 bytes aligned, set lpt incorrect", + c->lst.total_free); + goto out_invalid; + } + if (c->lst.total_dirty & 7) { + log_out(c, "total_dirty(%lld) is not 8 bytes aligned, set lpt incorrect", + c->lst.total_dirty); + goto out_invalid; + } + if (c->lst.total_dead & 7) { + log_out(c, "total_dead(%lld) is not 8 bytes aligned, set lpt incorrect", + c->lst.total_dead); + goto out_invalid; + } + if (c->lst.total_dark & 7) { + log_out(c, "total_dark(%lld) is not 8 bytes aligned, set lpt incorrect", + c->lst.total_dark); + goto out_invalid; + } + if (c->lst.total_used & 7) { + log_out(c, "total_used(%lld) is not 8 bytes aligned, set lpt incorrect", + c->lst.total_used); + goto out_invalid; + } + if (old_free != LPROPS_NC && (old_free & 7)) { + log_out(c, "LEB %d old_free(%d) is not 8 bytes aligned, set lpt incorrect", + lnum, old_free); + goto out_invalid; + } + if (old_dirty != LPROPS_NC && (old_dirty & 7)) { + log_out(c, "LEB %d old_dirty(%d) is not 8 bytes aligned, set lpt incorrect", + lnum, old_dirty); + goto out_invalid; + } + if (free != LPROPS_NC && (free < 0 || free > c->leb_size)) { + log_out(c, "LEB %d bad free %d(leb_size %d), set lpt incorrect", + lnum, free, c->leb_size); + goto out_invalid; + } + if (dirty != LPROPS_NC && dirty < 0) { + /* Dirty may be more than c->leb_size before set_bud_lprops. */ + log_out(c, "LEB %d bad dirty %d(leb_size %d), set lpt incorrect", + lnum, dirty, c->leb_size); + goto out_invalid; + } + +out: + return true; + +out_invalid: + FSCK(c)->lpt_status |= FR_LPT_INCORRECT; + return false; +} + +static bool fsck_can_ignore_failure(const struct ubifs_info *c, + unsigned int reason) +{ + ubifs_assert(c, FSCK(c)->mode != REBUILD_MODE); + + if (c->cmt_state != COMMIT_RESTING) + /* Don't ignore failure when do commit. */ + return false; + if (reason & (FR_LPT_CORRUPTED | FR_LPT_INCORRECT)) + return true; + + return false; +} + +static const unsigned int reason_mapping_table[] = { + BUD_CORRUPTED, /* FR_H_BUD_CORRUPTED */ + TNC_DATA_CORRUPTED, /* FR_H_TNC_DATA_CORRUPTED */ + ORPHAN_CORRUPTED, /* FR_H_ORPHAN_CORRUPTED */ + LTAB_INCORRECT, /* FR_H_LTAB_INCORRECT */ +}; + +static bool fsck_handle_failure(const struct ubifs_info *c, unsigned int reason, + void *priv) +{ + ubifs_assert(c, FSCK(c)->mode != REBUILD_MODE); + + return fix_problem(c, reason_mapping_table[reason], priv); +} + +static void signal_cancel(int sig) +{ + ubifs_warn(c, "killed by signo %d", sig); + exit_code |= FSCK_CANCELED; + exit(exit_code); +} + +static int init_fsck_info(struct ubifs_info *c, int argc, char *argv[]) +{ + int err = 0, mode = NORMAL_MODE; + struct sigaction sa; + struct ubifs_fsck_info *fsck = NULL; + + if (atexit(exit_callback)) { + log_err(c, errno, "can not set exit callback"); + return -errno; + } + + init_ubifs_info(c, FSCK_PROGRAM_TYPE); + get_options(argc, argv, &mode); + + fsck = calloc(1, sizeof(struct ubifs_fsck_info)); + if (!fsck) { + err = -errno; + log_err(c, errno, "can not allocate fsck info"); + goto out_err; + } + + c->private = fsck; + FSCK(c)->mode = mode; + INIT_LIST_HEAD(&FSCK(c)->disconnected_files); + c->assert_failed_cb = fsck_assert_failed; + c->set_failure_reason_cb = fsck_set_failure_reason; + c->get_failure_reason_cb = fsck_get_failure_reason; + c->clear_failure_reason_cb = fsck_clear_failure_reason; + c->test_and_clear_failure_reason_cb = fsck_test_and_clear_failure_reason; + c->set_lpt_invalid_cb = fsck_set_lpt_invalid; + c->test_lpt_valid_cb = fsck_test_lpt_valid; + c->can_ignore_failure_cb = fsck_can_ignore_failure; + c->handle_failure_cb = fsck_handle_failure; + + memset(&sa, 0, sizeof(struct sigaction)); + sa.sa_handler = signal_cancel; + if (sigaction(SIGINT, &sa, NULL) || sigaction(SIGTERM, &sa, NULL)) { + err = -errno; + log_err(c, errno, "can not set signal handler"); + goto out_err; + } + + return 0; + +out_err: + free(fsck); + free(c->dev_name); + c->dev_name = NULL; + return err; +} + +static void destroy_fsck_info(struct ubifs_info *c) +{ + free(c->private); + c->private = NULL; + free(c->dev_name); + c->dev_name = NULL; +} + +void handle_error(const struct ubifs_info *c, int reason_set) +{ + bool handled = false; + unsigned int reason = get_failure_reason_callback(c); + + clear_failure_reason_callback(c); + if ((reason_set & HAS_DATA_CORRUPTED) && (reason & FR_DATA_CORRUPTED)) { + handled = true; + reason &= ~FR_DATA_CORRUPTED; + if (fix_problem(c, LOG_CORRUPTED, NULL)) + FSCK(c)->try_rebuild = true; + } + if ((reason_set & HAS_TNC_CORRUPTED) && (reason & FR_TNC_CORRUPTED)) { + ubifs_assert(c, !handled); + handled = true; + reason &= ~FR_TNC_CORRUPTED; + if (fix_problem(c, TNC_CORRUPTED, NULL)) + FSCK(c)->try_rebuild = true; + } + + ubifs_assert(c, reason == 0); + if (!handled) + exit_code |= FSCK_ERROR; +} + +static int commit_fix_modifications(struct ubifs_info *c, bool final_commit) +{ + int err; + + if (final_commit) { + log_out(c, "Final committing"); + c->mst_node->flags &= ~cpu_to_le32(UBIFS_MST_DIRTY); + c->mst_node->gc_lnum = cpu_to_le32(c->gc_lnum); + /* Force UBIFS to do commit by setting @c->mounting. */ + c->mounting = 1; + } else if (exit_code & FSCK_NONDESTRUCT) { + log_out(c, "Commit problem fixing modifications"); + /* Force UBIFS to do commit by setting @c->mounting. */ + c->mounting = 1; + } + + err = ubifs_run_commit(c); + + if (c->mounting) + c->mounting = 0; + + return err; +} + +/* + * do_fsck - Check & repair the filesystem. + */ +static int do_fsck(void) +{ + int err; + + log_out(c, "Traverse TNC and construct files"); + err = traverse_tnc_and_construct_files(c); + if (err) { + handle_error(c, HAS_TNC_CORRUPTED); + return err; + } + + update_files_size(c); + + log_out(c, "Check and handle invalid files"); + err = handle_invalid_files(c); + if (err) { + exit_code |= FSCK_ERROR; + goto free_used_lebs; + } + + log_out(c, "Check and handle unreachable files"); + err = handle_dentry_tree(c); + if (err) { + exit_code |= FSCK_ERROR; + goto free_disconnected_files; + } + + log_out(c, "Check and correct files"); + err = check_and_correct_files(c); + if (err) { + exit_code |= FSCK_ERROR; + goto free_disconnected_files; + } + + log_out(c, "Check whether the TNC is empty"); + if (tnc_is_empty(c) && fix_problem(c, EMPTY_TNC, NULL)) { + err = -EINVAL; + FSCK(c)->try_rebuild = true; + goto free_disconnected_files; + } + + err = check_and_correct_space(c); + kfree(FSCK(c)->used_lebs); + destroy_file_tree(c, &FSCK(c)->scanned_files); + if (err) { + exit_code |= FSCK_ERROR; + goto free_disconnected_files_2; + } + + /* + * Committing is required once before allocating new space(subsequent + * steps may need), because building lpt could mark LEB(which holds + * stale data nodes) as unused, if the LEB is overwritten by new data, + * old data won't be found in the next fsck run(assume that first fsck + * run is interrupted by the powercut), which could affect the + * correctness of LEB properties after replaying journal in the second + * fsck run. + */ + err = commit_fix_modifications(c, false); + if (err) { + exit_code |= FSCK_ERROR; + goto free_disconnected_files_2; + } + + log_out(c, "Check and correct the index size"); + err = check_and_correct_index_size(c); + if (err) { + exit_code |= FSCK_ERROR; + goto free_disconnected_files_2; + } + + log_out(c, "Check and create root dir"); + err = check_and_create_root(c); + if (err) { + exit_code |= FSCK_ERROR; + goto free_disconnected_files_2; + } + + if (list_empty(&FSCK(c)->disconnected_files)) + goto final_commit; + + log_out(c, "Check and create lost+found"); + err = check_and_create_lost_found(c); + if (err) { + exit_code |= FSCK_ERROR; + goto free_disconnected_files_2; + } + + log_out(c, "Handle disconnected files"); + err = handle_disonnected_files(c); + if (err) { + exit_code |= FSCK_ERROR; + goto free_disconnected_files_2; + } + +final_commit: + err = commit_fix_modifications(c, true); + if (err) + exit_code |= FSCK_ERROR; + + return err; + +free_disconnected_files_2: + destroy_file_list(c, &FSCK(c)->disconnected_files); + return err; + +free_disconnected_files: + destroy_file_list(c, &FSCK(c)->disconnected_files); +free_used_lebs: + kfree(FSCK(c)->used_lebs); + destroy_file_tree(c, &FSCK(c)->scanned_files); + return err; +} + +int main(int argc, char *argv[]) +{ + int err; + + err = init_fsck_info(c, argc, argv); + if (err) { + exit_code |= FSCK_ERROR; + goto out_exit; + } + + err = ubifs_open_volume(c, c->dev_name); + if (err) { + exit_code |= FSCK_ERROR; + goto out_destroy_fsck; + } + + /* + * Init: Read superblock + * Step 1: Read master & init lpt + * Step 2: Replay journal + * Step 3: Handle orphan nodes + * Step 4: Consolidate log + * Step 5: Recover isize + */ + err = ubifs_load_filesystem(c); + if (err) { + if (FSCK(c)->try_rebuild) + ubifs_rebuild_filesystem(c); + goto out_close; + } + + /* + * Step 6: Traverse tnc and construct files + * Step 7: Update files' size + * Step 8: Check and handle invalid files + * Step 9: Check and handle unreachable files + * Step 10: Check and correct files + * Step 11: Check whether the TNC is empty + * Step 12: Check and correct the space statistics + * Step 13: Commit problem fixing modifications + * Step 14: Check and correct the index size + * Step 15: Check and create root dir + * Step 16: Check and create lost+found + * Step 17: Handle disconnected files + * Step 18: Do final committing + */ + err = do_fsck(); + if (err && FSCK(c)->try_rebuild) { + ubifs_destroy_filesystem(c); + ubifs_rebuild_filesystem(c); + } else { + ubifs_destroy_filesystem(c); + } + +out_close: + ubifs_close_volume(c); +out_destroy_fsck: + destroy_fsck_info(c); +out_exit: + return exit_code; +} diff --git a/ubifs-utils/fsck.ubifs/fsck.ubifs.h b/ubifs-utils/fsck.ubifs/fsck.ubifs.h new file mode 100644 index 0000000..6529932 --- /dev/null +++ b/ubifs-utils/fsck.ubifs/fsck.ubifs.h @@ -0,0 +1,392 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2024, Huawei Technologies Co, Ltd. + * + * Authors: Zhihao Cheng <chengzhihao1@huawei.com> + */ + +#ifndef __FSCK_UBIFS_H__ +#define __FSCK_UBIFS_H__ + +/* Exit codes used by fsck-type programs */ +#define FSCK_OK 0 /* No errors */ +#define FSCK_NONDESTRUCT 1 /* File system errors corrected */ +#define FSCK_REBOOT 2 /* System should be rebooted */ +#define FSCK_UNCORRECTED 4 /* File system errors left uncorrected */ +#define FSCK_ERROR 8 /* Operational error */ +#define FSCK_USAGE 16 /* Usage or syntax error */ +#define FSCK_CANCELED 32 /* Aborted with a signal or ^C */ +#define FSCK_LIBRARY 128 /* Shared library error */ + +/* + * There are 6 working modes for fsck: + * NORMAL_MODE: Check the filesystem, ask user whether or not to fix the + * problem as long as inconsistent data is found during checking. + * SAFE_MODE: Check and safely repair the filesystem, if there are any + * data dropping operations needed by fixing, fsck will fail. + * DANGER_MODE0:Check and repair the filesystem according to TNC, data dropping + * will be reported. If TNC/master/log is corrupted, fsck will fail. + * DANGER_MODE1:Check and forcedly repair the filesystem according to TNC, + * turns to @REBUILD_MODE mode automatically if TNC/master/log is + * corrupted. + * REBUILD_MODE:Scan entire UBI volume to find all nodes, and rebuild the + * filesystem, always make fsck success. + * CHECK_MODE: Make no changes to the filesystem, only check the filesystem. + */ +enum { NORMAL_MODE = 0, SAFE_MODE, DANGER_MODE0, + DANGER_MODE1, REBUILD_MODE, CHECK_MODE }; + +/* Types of inconsistent problems */ +enum { SB_CORRUPTED = 0, MST_CORRUPTED, LOG_CORRUPTED, BUD_CORRUPTED, + TNC_CORRUPTED, TNC_DATA_CORRUPTED, ORPHAN_CORRUPTED, INVALID_INO_NODE, + INVALID_DENT_NODE, INVALID_DATA_NODE, SCAN_CORRUPTED, FILE_HAS_NO_INODE, + FILE_HAS_0_NLINK_INODE, FILE_HAS_INCONSIST_TYPE, FILE_HAS_TOO_MANY_DENT, + FILE_SHOULDNT_HAVE_DATA, FILE_HAS_NO_DENT, XATTR_HAS_NO_HOST, + XATTR_HAS_WRONG_HOST, FILE_HAS_NO_ENCRYPT, FILE_IS_DISCONNECTED, + FILE_ROOT_HAS_DENT, DENTRY_IS_UNREACHABLE, FILE_IS_INCONSISTENT, + EMPTY_TNC, LPT_CORRUPTED, NNODE_INCORRECT, PNODE_INCORRECT, + LP_INCORRECT, SPACE_STAT_INCORRECT, LTAB_INCORRECT, INCORRECT_IDX_SZ, + ROOT_DIR_NOT_FOUND, DISCONNECTED_FILE_CANNOT_BE_RECOVERED }; + +enum { HAS_DATA_CORRUPTED = 1, HAS_TNC_CORRUPTED = 2 }; + +typedef int (*calculate_lp_callback)(struct ubifs_info *c, + int index, int *free, int *dirty, + int *is_idx); + +struct scanned_file; + +/** + * scanned_node - common header node. + * @exist: whether the node is found by scanning + * @lnum: LEB number of the scanned node + * @offs: scanned node's offset within @lnum + * @len: length of scanned node + * @sqnum: sequence number + */ +struct scanned_node { + bool exist; + int lnum; + int offs; + int len; + unsigned long long sqnum; +}; + +/** + * scanned_ino_node - scanned inode node. + * @header: common header of scanned node + * @key: the key of inode node + * @is_xattr: %1 for xattr inode, otherwise %0 + * @is_encrypted: %1 for encrypted inode, otherwise %0 + * @mode: file mode + * @nlink: number of hard links + * @xcnt: count of extended attributes this inode has + * @xsz: summarized size of all extended attributes in bytes + * @xnms: sum of lengths of all extended attribute names + * @size: inode size in bytes + * @rb: link in the tree of valid inode nodes or deleted inode nodes + */ +struct scanned_ino_node { + struct scanned_node header; + union ubifs_key key; + unsigned int is_xattr:1; + unsigned int is_encrypted:1; + unsigned int mode; + unsigned int nlink; + unsigned int xcnt; + unsigned int xsz; + unsigned int xnms; + unsigned long long size; + struct rb_node rb; +}; + +/** + * scanned_dent_node - scanned dentry node. + * @header: common header of scanned node + * @key: the key of dentry node + * @can_be_found: whether this dentry can be found from '/' + * @type: file type, reg/dir/symlink/block/char/fifo/sock + * @nlen: name length + * @name: dentry name + * @inum: target inode number + * @file: corresponding file + * @rb: link in the trees of: + * 1) valid dentry nodes or deleted dentry node + * 2) all scanned dentry nodes from same file + * @list: link in the list dentries for looking up/deleting + */ +struct scanned_dent_node { + struct scanned_node header; + union ubifs_key key; + bool can_be_found; + unsigned int type; + unsigned int nlen; + char name[UBIFS_MAX_NLEN + 1]; + ino_t inum; + struct scanned_file *file; + struct rb_node rb; + struct list_head list; +}; + +/** + * scanned_data_node - scanned data node. + * @header: common header of scanned node + * @key: the key of data node + * @size: uncompressed data size in bytes + * @rb: link in the tree of all scanned data nodes from same file + * @list: link in the list for deleting + */ +struct scanned_data_node { + struct scanned_node header; + union ubifs_key key; + unsigned int size; + struct rb_node rb; + struct list_head list; +}; + +/** + * scanned_trun_node - scanned truncation node. + * @header: common header of scanned node + * @new_size: size after truncation + */ +struct scanned_trun_node { + struct scanned_node header; + unsigned long long new_size; +}; + +/** + * scanned_file - file info scanned from UBIFS volume. + * + * @calc_nlink: calculated count of directory entries refer this inode + * @calc_xcnt: calculated count of extended attributes + * @calc_xsz: calculated summary size of all extended attributes + * @calc_xnms: calculated sum of lengths of all extended attribute names + * @calc_size: calculated file size + * @has_encrypted_info: whether the file has encryption related xattrs + * + * @inum: inode number + * @ino: inode node + * @trun: truncation node + * + * @rb: link in the tree of all scanned files + * @list: link in the list files for kinds of processing + * @dent_nodes: tree of all scanned dentry nodes + * @data_nodes: tree of all scanned data nodes + * @xattr_files: tree of all scanned xattr files + */ +struct scanned_file { + unsigned int calc_nlink; + unsigned int calc_xcnt; + unsigned int calc_xsz; + unsigned int calc_xnms; + unsigned long long calc_size; + bool has_encrypted_info; + + ino_t inum; + struct scanned_ino_node ino; + struct scanned_trun_node trun; + + struct rb_node rb; + struct list_head list; + struct rb_root dent_nodes; + struct rb_root data_nodes; + struct rb_root xattr_files; +}; + +/** + * invalid_file_problem - problem instance for invalid file. + * @file: invalid file instance + * @priv: invalid instance in @file, could be a dent_node or data_node + */ +struct invalid_file_problem { + struct scanned_file *file; + void *priv; +}; + +/** + * nnode_problem - problem instance for incorrect nnode + * @nnode: incorrect nnode + * @parent_nnode: the parent nnode of @nnode, could be NULL if @nnode is root + * @num: calculated num + */ +struct nnode_problem { + struct ubifs_nnode *nnode; + struct ubifs_nnode *parent_nnode; + int num; +}; + +/** + * pnode_problem - problem instance for incorrect pnode + * @pnode: incorrect pnode + * @num: calculated num + */ +struct pnode_problem { + struct ubifs_pnode *pnode; + int num; +}; + +/** + * lp_problem - problem instance for incorrect LEB proerties + * @lp: incorrect LEB properties + * @lnum: LEB number + * @free: calculated free space in LEB + * @dirty: calculated dirty bytes in LEB + * @is_idx: %true means that the LEB is an index LEB + */ +struct lp_problem { + struct ubifs_lprops *lp; + int lnum; + int free; + int dirty; + int is_idx; +}; + +/** + * space_stat_problem - problem instance for incorrect space statistics + * @lst: current space statistics + * @calc_lst: calculated space statistics + */ +struct space_stat_problem { + struct ubifs_lp_stats *lst; + struct ubifs_lp_stats *calc_lst; +}; + +/** + * ubifs_rebuild_info - UBIFS rebuilding information. + * @write_buf: write buffer for LEB @head_lnum + * @head_lnum: current writing LEB number + * @head_offs: current writing position in LEB @head_lnum + * @need_update_lpt: whether to update lpt while writing index nodes + */ +struct ubifs_rebuild_info { + void *write_buf; + int head_lnum; + int head_offs; + bool need_update_lpt; +}; + +/** + * struct ubifs_fsck_info - UBIFS fsck information. + * @mode: working mode + * @failure_reason: reasons for failed operations + * @lpt_status: the status of lpt, could be: %0(OK), %FR_LPT_CORRUPTED or + * %FR_LPT_INCORRECT + * @scanned_files: tree of all scanned files + * @used_lebs: a bitmap used for recording used lebs + * @disconnected_files: regular files without dentries + * @lpts: lprops table + * @try_rebuild: %true means that try to rebuild fs when fsck failed + * @rebuild: rebuilding-related information + * @lost_and_found: inode number of the lost+found directory, %0 means invalid + */ +struct ubifs_fsck_info { + int mode; + unsigned int failure_reason; + unsigned int lpt_status; + struct rb_root scanned_files; + unsigned long *used_lebs; + struct list_head disconnected_files; + struct ubifs_lprops *lpts; + bool try_rebuild; + struct ubifs_rebuild_info *rebuild; + ino_t lost_and_found; +}; + +#define FSCK(c) ((struct ubifs_fsck_info*)c->private) + +static inline const char *mode_name(const struct ubifs_info *c) +{ + if (!c->private) + return ""; + + switch (FSCK(c)->mode) { + case NORMAL_MODE: + return ",normal mode"; + case SAFE_MODE: + return ",safe mode"; + case DANGER_MODE0: + return ",danger mode"; + case DANGER_MODE1: + return ",danger + rebuild mode"; + case REBUILD_MODE: + return ",rebuild mode"; + case CHECK_MODE: + return ",check mode"; + default: + return ""; + } +} + +#define log_out(c, fmt, ...) \ + printf("%s[%d] (%s%s): " fmt "\n", c->program_name ? : "noprog",\ + getpid(), c->dev_name ? : "-", mode_name(c), \ + ##__VA_ARGS__) + +#define log_err(c, err, fmt, ...) do { \ + printf("%s[%d][ERROR] (%s%s): %s: " fmt, \ + c->program_name ? : "noprog", getpid(), \ + c->dev_name ? : "-", mode_name(c), \ + __FUNCTION__, ##__VA_ARGS__); \ + if (err) \ + printf(" - %s", strerror(err)); \ + printf("\n"); \ +} while (0) + +/* Exit code for fsck program. */ +extern int exit_code; + +/* fsck.ubifs.c */ +void handle_error(const struct ubifs_info *c, int reason_set); + +/* problem.c */ +bool fix_problem(const struct ubifs_info *c, int problem_type, const void *priv); + +/* load_fs.c */ +int ubifs_load_filesystem(struct ubifs_info *c); +void ubifs_destroy_filesystem(struct ubifs_info *c); + +/* extract_files.c */ +bool parse_ino_node(struct ubifs_info *c, int lnum, int offs, void *node, + union ubifs_key *key, struct scanned_ino_node *ino_node); +bool parse_dent_node(struct ubifs_info *c, int lnum, int offs, void *node, + union ubifs_key *key, struct scanned_dent_node *dent_node); +bool parse_data_node(struct ubifs_info *c, int lnum, int offs, void *node, + union ubifs_key *key, struct scanned_data_node *data_node); +bool parse_trun_node(struct ubifs_info *c, int lnum, int offs, void *node, + union ubifs_key *key, struct scanned_trun_node *trun_node); +int insert_or_update_file(struct ubifs_info *c, struct rb_root *file_tree, + struct scanned_node *sn, int key_type, ino_t inum); +void destroy_file_content(struct ubifs_info *c, struct scanned_file *file); +void destroy_file_tree(struct ubifs_info *c, struct rb_root *file_tree); +void destroy_file_list(struct ubifs_info *c, struct list_head *file_list); +struct scanned_file *lookup_file(struct rb_root *file_tree, ino_t inum); +int delete_file(struct ubifs_info *c, struct scanned_file *file); +int file_is_valid(struct ubifs_info *c, struct scanned_file *file, + struct rb_root *file_tree, int *is_diconnected); +bool file_is_reachable(struct ubifs_info *c, struct scanned_file *file, + struct rb_root *file_tree); +int check_and_correct_files(struct ubifs_info *c); + +/* rebuild_fs.c */ +int ubifs_rebuild_filesystem(struct ubifs_info *c); + +/* check_files.c */ +int traverse_tnc_and_construct_files(struct ubifs_info *c); +void update_files_size(struct ubifs_info *c); +int handle_invalid_files(struct ubifs_info *c); +int handle_dentry_tree(struct ubifs_info *c); +bool tnc_is_empty(struct ubifs_info *c); +int check_and_create_root(struct ubifs_info *c); + +/* check_space.c */ +int get_free_leb(struct ubifs_info *c); +int build_lpt(struct ubifs_info *c, calculate_lp_callback calculate_lp_cb, + bool free_ltab); +int check_and_correct_space(struct ubifs_info *c); +int check_and_correct_index_size(struct ubifs_info *c); + +/* handle_disconnected.c */ +int check_and_create_lost_found(struct ubifs_info *c); +int handle_disonnected_files(struct ubifs_info *c); + +#endif diff --git a/ubifs-utils/fsck.ubifs/handle_disconnected.c b/ubifs-utils/fsck.ubifs/handle_disconnected.c new file mode 100644 index 0000000..be62522 --- /dev/null +++ b/ubifs-utils/fsck.ubifs/handle_disconnected.c @@ -0,0 +1,197 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2024, Huawei Technologies Co, Ltd. + * + * Authors: Huang Xiaojia <huangxiaojia2@huawei.com> + * Zhihao Cheng <chengzhihao1@huawei.com> + */ + +#include <stdio.h> +#include <stdlib.h> +#include <sys/stat.h> + +#include "linux_err.h" +#include "kmem.h" +#include "ubifs.h" +#include "defs.h" +#include "debug.h" +#include "key.h" +#include "fsck.ubifs.h" + +#define LOST_FOUND_DIR_NAME "lost+found" +#define MAX_REPEAT_NAME_RETRY_TIMES 10000000 + +/** + * check_and_create_lost_found - Check and create the lost+found directory. + * @c: UBIFS file-system description object + * + * This function checks whether the lost+found directory exists and creates a + * new one if no valid lost+found existing. If there is a valid lost+found + * directory, inode number is stored in @FSCK(c)->lost_and_found. Returns zero + * in case of success, a negative error code in case of failure. + */ +int check_and_create_lost_found(struct ubifs_info *c) +{ + struct ubifs_inode *root_ui, *lost_found_ui; + struct fscrypt_name nm; + int err = 0; + + root_ui = ubifs_lookup_by_inum(c, UBIFS_ROOT_INO); + if (IS_ERR(root_ui)) { + err = PTR_ERR(root_ui); + /* Previous step ensures that the root dir is valid. */ + ubifs_assert(c, err != -ENOENT); + return err; + } + + if (root_ui->flags & UBIFS_CRYPT_FL) { + ubifs_msg(c, "The root dir is encrypted, skip checking lost+found"); + goto free_root; + } + + fname_name(&nm) = LOST_FOUND_DIR_NAME; + fname_len(&nm) = strlen(LOST_FOUND_DIR_NAME); + lost_found_ui = ubifs_lookup(c, root_ui, &nm); + if (IS_ERR(lost_found_ui)) { + err = PTR_ERR(lost_found_ui); + if (err != -ENOENT) + goto free_root; + + /* Not found. Create a new lost+found. */ + err = ubifs_mkdir(c, root_ui, &nm, S_IRUGO | S_IWUSR | S_IXUSR); + if (err < 0) { + if (err == -ENOSPC) { + ubifs_msg(c, "No free space to create lost+found"); + err = 0; + } + goto free_root; + } + lost_found_ui = ubifs_lookup(c, root_ui, &nm); + if (IS_ERR(lost_found_ui)) { + err = PTR_ERR(lost_found_ui); + ubifs_assert(c, err != -ENOENT); + goto free_root; + } + FSCK(c)->lost_and_found = lost_found_ui->vfs_inode.inum; + ubifs_msg(c, "Create the lost+found"); + } else if (!(lost_found_ui->flags & UBIFS_CRYPT_FL) && + S_ISDIR(lost_found_ui->vfs_inode.mode)) { + FSCK(c)->lost_and_found = lost_found_ui->vfs_inode.inum; + } else { + ubifs_msg(c, "The type of lost+found is %s%s", + ubifs_get_type_name(ubifs_get_dent_type(lost_found_ui->vfs_inode.mode)), + lost_found_ui->flags & UBIFS_CRYPT_FL ? ", encrypted" : ""); + } + + kfree(lost_found_ui); +free_root: + kfree(root_ui); + return err; +} + +static int handle_disonnected_file(struct ubifs_info *c, + struct scanned_file *file) +{ + int err = 0; + + if (FSCK(c)->lost_and_found) { + unsigned int index = 0; + char file_name[UBIFS_MAX_NLEN + 1]; + struct fscrypt_name nm; + struct ubifs_inode *ui = NULL, *lost_found_ui = NULL; + + lost_found_ui = ubifs_lookup_by_inum(c, FSCK(c)->lost_and_found); + if (IS_ERR(lost_found_ui)) { + err = PTR_ERR(lost_found_ui); + ubifs_assert(c, err != -ENOENT); + return err; + } + ui = ubifs_lookup_by_inum(c, file->inum); + if (IS_ERR(ui)) { + err = PTR_ERR(ui); + ubifs_assert(c, err != -ENOENT); + goto free_lost_found_ui; + } + + while (index < MAX_REPEAT_NAME_RETRY_TIMES) { + struct ubifs_inode *target_ui; + + err = snprintf(file_name, sizeof(file_name), + "INO_%lu_%u", file->inum, index); + if (err < 0) + goto free_ui; + fname_name(&nm) = file_name; + fname_len(&nm) = strlen(file_name); + target_ui = ubifs_lookup(c, lost_found_ui, &nm); + if (IS_ERR(target_ui)) { + err = PTR_ERR(target_ui); + if (err == -ENOENT) + break; + goto free_ui; + } + kfree(target_ui); + index++; + } + + if (err != -ENOENT) { + err = 0; + kfree(ui); + kfree(lost_found_ui); + log_out(c, "Too many duplicated names(%u) in lost+found for inum %lu", + index, file->inum); + goto delete_file; + } + + /* Try to recover disconnected file into lost+found. */ + err = ubifs_link_recovery(c, lost_found_ui, ui, &nm); + if (err && err == -ENOSPC) { + err = 0; + log_out(c, "No free space to recover disconnected file"); + goto delete_file; + } + dbg_fsck("recover disconnected file %lu, in %s", + file->inum, c->dev_name); + +free_ui: + kfree(ui); +free_lost_found_ui: + kfree(lost_found_ui); + return err; + } + + log_out(c, "No valid lost+found"); + +delete_file: + if (fix_problem(c, DISCONNECTED_FILE_CANNOT_BE_RECOVERED, file)) + err = delete_file(c, file); + return err; +} + +/** + * handle_disonnected_files - Handle disconnected files. + * @c: UBIFS file-system description object + * + * This function tries to recover disonnected files into lost+found directory. + * If there is no free space left to recover the disconnected files, fsck may + * delete the files to make filesystem be consistent. Returns zero in case of + * success, a negative error code in case of failure. + */ +int handle_disonnected_files(struct ubifs_info *c) +{ + int err, ret = 0; + struct scanned_file *file; + + while (!list_empty(&FSCK(c)->disconnected_files)) { + file = list_entry(FSCK(c)->disconnected_files.next, + struct scanned_file, list); + + list_del(&file->list); + err = handle_disonnected_file(c, file); + if (err) + ret = ret ? ret : err; + destroy_file_content(c, file); + kfree(file); + } + + return ret; +} diff --git a/ubifs-utils/fsck.ubifs/load_fs.c b/ubifs-utils/fsck.ubifs/load_fs.c new file mode 100644 index 0000000..04208a1 --- /dev/null +++ b/ubifs-utils/fsck.ubifs/load_fs.c @@ -0,0 +1,261 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2024, Huawei Technologies Co, Ltd. + * + * Authors: Zhihao Cheng <chengzhihao1@huawei.com> + */ + +#include <stdio.h> +#include <stdlib.h> + +#include "bitops.h" +#include "kmem.h" +#include "ubifs.h" +#include "defs.h" +#include "debug.h" +#include "key.h" +#include "misc.h" +#include "fsck.ubifs.h" + +int ubifs_load_filesystem(struct ubifs_info *c) +{ + int err; + size_t sz; + + err = init_constants_early(c); + if (err) { + exit_code |= FSCK_ERROR; + return err; + } + + err = check_volume_empty(c); + if (err <= 0) { + exit_code |= FSCK_ERROR; + log_err(c, 0, "%s UBI volume!", err < 0 ? "bad" : "empty"); + return -EINVAL; + } + + if (c->ro_media && !c->ro_mount) { + exit_code |= FSCK_ERROR; + log_err(c, 0, "cannot read-write on read-only media"); + return -EROFS; + } + + err = -ENOMEM; + c->bottom_up_buf = kmalloc_array(BOTTOM_UP_HEIGHT, sizeof(int), + GFP_KERNEL); + if (!c->bottom_up_buf) { + exit_code |= FSCK_ERROR; + log_err(c, errno, "cannot allocate bottom_up_buf"); + goto out_free; + } + + c->sbuf = vmalloc(c->leb_size); + if (!c->sbuf) { + exit_code |= FSCK_ERROR; + log_err(c, errno, "cannot allocate sbuf"); + goto out_free; + } + + if (!c->ro_mount) { + c->ileb_buf = vmalloc(c->leb_size); + if (!c->ileb_buf) { + exit_code |= FSCK_ERROR; + log_err(c, errno, "cannot allocate ileb_buf"); + goto out_free; + } + } + + c->mounting = 1; + + log_out(c, "Read superblock"); + err = ubifs_read_superblock(c); + if (err) { + if (test_and_clear_failure_reason_callback(c, FR_DATA_CORRUPTED)) + fix_problem(c, SB_CORRUPTED, NULL); + exit_code |= FSCK_ERROR; + goto out_mounting; + } + + err = init_constants_sb(c); + if (err) { + exit_code |= FSCK_ERROR; + goto out_mounting; + } + + sz = ALIGN(c->max_idx_node_sz, c->min_io_size) * 2; + c->cbuf = kmalloc(sz, GFP_NOFS); + if (!c->cbuf) { + err = -ENOMEM; + exit_code |= FSCK_ERROR; + log_err(c, errno, "cannot allocate cbuf"); + goto out_mounting; + } + + err = alloc_wbufs(c); + if (err) { + exit_code |= FSCK_ERROR; + log_err(c, 0, "cannot allocate wbuf"); + goto out_mounting; + } + + log_out(c, "Read master & init lpt"); + err = ubifs_read_master(c); + if (err) { + if (test_and_clear_failure_reason_callback(c, FR_DATA_CORRUPTED)) { + if (fix_problem(c, MST_CORRUPTED, NULL)) + FSCK(c)->try_rebuild = true; + } else + exit_code |= FSCK_ERROR; + goto out_master; + } + + init_constants_master(c); + + if ((c->mst_node->flags & cpu_to_le32(UBIFS_MST_DIRTY)) != 0) { + ubifs_msg(c, "recovery needed"); + c->need_recovery = 1; + } + + if (c->need_recovery && !c->ro_mount) { + err = ubifs_recover_inl_heads(c, c->sbuf); + if (err) { + exit_code |= FSCK_ERROR; + goto out_master; + } + } + + err = ubifs_lpt_init(c, 1, !c->ro_mount); + if (err) { + exit_code |= FSCK_ERROR; + goto out_master; + } + + if (!c->ro_mount && c->space_fixup) { + err = ubifs_fixup_free_space(c); + if (err) { + exit_code |= FSCK_ERROR; + goto out_lpt; + } + } + + if (!c->ro_mount && !c->need_recovery) { + /* + * Set the "dirty" flag so that if we reboot uncleanly we + * will notice this immediately on the next mount. + */ + c->mst_node->flags |= cpu_to_le32(UBIFS_MST_DIRTY); + err = ubifs_write_master(c); + if (err) { + exit_code |= FSCK_ERROR; + goto out_lpt; + } + } + + if (!c->ro_mount && c->superblock_need_write) { + err = ubifs_write_sb_node(c, c->sup_node); + if (err) { + exit_code |= FSCK_ERROR; + goto out_lpt; + } + c->superblock_need_write = 0; + } + + log_out(c, "Replay journal"); + err = ubifs_replay_journal(c); + if (err) { + handle_error(c, HAS_DATA_CORRUPTED | HAS_TNC_CORRUPTED); + goto out_journal; + } + + /* Calculate 'min_idx_lebs' after journal replay */ + c->bi.min_idx_lebs = ubifs_calc_min_idx_lebs(c); + + log_out(c, "Handle orphan nodes"); + err = ubifs_mount_orphans(c, c->need_recovery, c->ro_mount); + if (err) { + handle_error(c, HAS_TNC_CORRUPTED); + goto out_orphans; + } + + if (!c->ro_mount) { + int lnum; + + /* Check for enough log space */ + lnum = c->lhead_lnum + 1; + if (lnum >= UBIFS_LOG_LNUM + c->log_lebs) + lnum = UBIFS_LOG_LNUM; + if (lnum == c->ltail_lnum) { + log_out(c, "Consolidate log"); + err = ubifs_consolidate_log(c); + if (err) { + handle_error(c, HAS_DATA_CORRUPTED); + goto out_orphans; + } + } + + if (c->need_recovery) { + log_out(c, "Recover isize"); + err = ubifs_recover_size(c, true); + if (err) { + handle_error(c, HAS_TNC_CORRUPTED); + goto out_orphans; + } + } + } else if (c->need_recovery) { + log_out(c, "Recover isize"); + err = ubifs_recover_size(c, false); + if (err) { + handle_error(c, HAS_TNC_CORRUPTED); + goto out_orphans; + } + } + + c->mounting = 0; + + return 0; + +out_orphans: + free_orphans(c); +out_journal: + destroy_journal(c); +out_lpt: + ubifs_lpt_free(c, 0); +out_master: + c->max_sqnum = 0; + c->highest_inum = 0; + c->calc_idx_sz = 0; + kfree(c->mst_node); + kfree(c->rcvrd_mst_node); + free_wbufs(c); +out_mounting: + c->mounting = 0; +out_free: + kfree(c->cbuf); + kfree(c->ileb_buf); + kfree(c->sbuf); + kfree(c->bottom_up_buf); + kfree(c->sup_node); + + return err; +} + +void ubifs_destroy_filesystem(struct ubifs_info *c) +{ + destroy_journal(c); + free_wbufs(c); + free_orphans(c); + ubifs_lpt_free(c, 0); + + c->max_sqnum = 0; + c->highest_inum = 0; + c->calc_idx_sz = 0; + + kfree(c->cbuf); + kfree(c->rcvrd_mst_node); + kfree(c->mst_node); + kfree(c->ileb_buf); + kfree(c->sbuf); + kfree(c->bottom_up_buf); + kfree(c->sup_node); +} diff --git a/ubifs-utils/fsck.ubifs/problem.c b/ubifs-utils/fsck.ubifs/problem.c new file mode 100644 index 0000000..916c976 --- /dev/null +++ b/ubifs-utils/fsck.ubifs/problem.c @@ -0,0 +1,377 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2024, Huawei Technologies Co, Ltd. + * + * Authors: Zhihao Cheng <chengzhihao1@huawei.com> + */ + +#include <stdio.h> +#include <stdlib.h> +#include <getopt.h> + +#include "ubifs.h" +#include "defs.h" +#include "debug.h" +#include "key.h" +#include "fsck.ubifs.h" + +/* + * problem flags. + * + * PROBLEM_FIXABLE: problem is fixable, unsolvable problem such as corrupted + * super block will abort the fsck program + * PROBLEM_MUST_FIX: problem must be fixed because it will affect the subsequent + * fsck process, otherwise aborting the fsck program + * PROBLEM_DROP_DATA: user data could be dropped after fixing the problem + * PROBLEM_NEED_REBUILD: rebuilding filesystem is needed to fix the problem + */ +#define PROBLEM_FIXABLE (1<<0) +#define PROBLEM_MUST_FIX (1<<1) +#define PROBLEM_DROP_DATA (1<<2) +#define PROBLEM_NEED_REBUILD (1<<3) + +struct fsck_problem { + unsigned int flags; + const char *desc; +}; + +static const struct fsck_problem problem_table[] = { + {0, "Corrupted superblock"}, // SB_CORRUPTED + {PROBLEM_FIXABLE | PROBLEM_MUST_FIX | PROBLEM_DROP_DATA | PROBLEM_NEED_REBUILD, "Corrupted master node"}, // MST_CORRUPTED + {PROBLEM_FIXABLE | PROBLEM_MUST_FIX | PROBLEM_DROP_DATA | PROBLEM_NEED_REBUILD, "Corrupted log area"}, // LOG_CORRUPTED + {PROBLEM_FIXABLE | PROBLEM_MUST_FIX | PROBLEM_DROP_DATA, "Corrupted bud LEB"}, // BUD_CORRUPTED + {PROBLEM_FIXABLE | PROBLEM_MUST_FIX | PROBLEM_DROP_DATA | PROBLEM_NEED_REBUILD, "Corrupted index node"}, // TNC_CORRUPTED + {PROBLEM_FIXABLE | PROBLEM_MUST_FIX | PROBLEM_DROP_DATA, "Corrupted data searched from TNC"}, // TNC_DATA_CORRUPTED + {PROBLEM_FIXABLE | PROBLEM_MUST_FIX | PROBLEM_DROP_DATA, "Corrupted orphan LEB"}, // ORPHAN_CORRUPTED + {PROBLEM_FIXABLE | PROBLEM_MUST_FIX | PROBLEM_DROP_DATA, "Invalid inode node"}, // INVALID_INO_NODE + {PROBLEM_FIXABLE | PROBLEM_MUST_FIX | PROBLEM_DROP_DATA, "Invalid dentry node"}, // INVALID_DENT_NODE + {PROBLEM_FIXABLE | PROBLEM_MUST_FIX | PROBLEM_DROP_DATA, "Invalid data node"}, // INVALID_DATA_NODE + {PROBLEM_FIXABLE | PROBLEM_MUST_FIX | PROBLEM_DROP_DATA, "Corrupted data is scanned"}, // SCAN_CORRUPTED + {PROBLEM_FIXABLE | PROBLEM_MUST_FIX | PROBLEM_DROP_DATA, "File has no inode"}, // FILE_HAS_NO_INODE + {PROBLEM_FIXABLE | PROBLEM_MUST_FIX | PROBLEM_DROP_DATA, "File has zero-nlink inode"}, // FILE_HAS_0_NLINK_INODE + {PROBLEM_FIXABLE | PROBLEM_MUST_FIX | PROBLEM_DROP_DATA, "File has inconsistent type"}, // FILE_HAS_INCONSIST_TYPE + {PROBLEM_FIXABLE | PROBLEM_MUST_FIX | PROBLEM_DROP_DATA, "File has too many dentries"}, // FILE_HAS_TOO_MANY_DENT + {PROBLEM_FIXABLE | PROBLEM_MUST_FIX | PROBLEM_DROP_DATA, "File should not have data"}, // FILE_SHOULDNT_HAVE_DATA + {PROBLEM_FIXABLE | PROBLEM_MUST_FIX | PROBLEM_DROP_DATA, "File has no dentries"}, // FILE_HAS_NO_DENT + {PROBLEM_FIXABLE | PROBLEM_MUST_FIX | PROBLEM_DROP_DATA, "Xattr file has no host"}, // XATTR_HAS_NO_HOST + {PROBLEM_FIXABLE | PROBLEM_MUST_FIX | PROBLEM_DROP_DATA, "Xattr file has wrong host"}, // XATTR_HAS_WRONG_HOST + {PROBLEM_FIXABLE | PROBLEM_MUST_FIX | PROBLEM_DROP_DATA, "Encrypted file has no encryption information"}, // FILE_HAS_NO_ENCRYPT + {PROBLEM_FIXABLE | PROBLEM_MUST_FIX, "File is disconnected(regular file without dentries)"}, // FILE_IS_DISCONNECTED + {PROBLEM_FIXABLE | PROBLEM_MUST_FIX | PROBLEM_DROP_DATA, "Root dir should not have a dentry"}, // FILE_ROOT_HAS_DENT + {PROBLEM_FIXABLE | PROBLEM_MUST_FIX | PROBLEM_DROP_DATA, "Dentry is unreachable"}, // DENTRY_IS_UNREACHABLE + {PROBLEM_FIXABLE | PROBLEM_MUST_FIX, "File is inconsistent"}, // FILE_IS_INCONSISTENT + {PROBLEM_FIXABLE | PROBLEM_MUST_FIX | PROBLEM_DROP_DATA | PROBLEM_NEED_REBUILD, "TNC is empty"}, // EMPTY_TNC + {PROBLEM_FIXABLE | PROBLEM_MUST_FIX, "Corrupted pnode/nnode"}, // LPT_CORRUPTED + {PROBLEM_FIXABLE | PROBLEM_MUST_FIX, "Inconsistent properties for nnode"}, // NNODE_INCORRECT + {PROBLEM_FIXABLE | PROBLEM_MUST_FIX, "Inconsistent properties for pnode"}, // PNODE_INCORRECT + {PROBLEM_FIXABLE | PROBLEM_MUST_FIX, "Inconsistent properties for LEB"}, // LP_INCORRECT + {PROBLEM_FIXABLE | PROBLEM_MUST_FIX, "Incorrect space statistics"}, // SPACE_STAT_INCORRECT + {PROBLEM_FIXABLE | PROBLEM_MUST_FIX, "Inconsistent properties for lprops table"}, // LTAB_INCORRECT + {PROBLEM_FIXABLE | PROBLEM_MUST_FIX, "Incorrect index size"}, // INCORRECT_IDX_SZ + {PROBLEM_FIXABLE | PROBLEM_MUST_FIX, "Root dir is lost"}, // ROOT_DIR_NOT_FOUND + {PROBLEM_FIXABLE | PROBLEM_DROP_DATA, "Disconnected file cannot be recovered"}, // DISCONNECTED_FILE_CANNOT_BE_RECOVERED +}; + +static const char *get_question(const struct fsck_problem *problem, + int problem_type) +{ + if (problem->flags & PROBLEM_NEED_REBUILD) + return "Rebuild filesystem?"; + + switch (problem_type) { + case BUD_CORRUPTED: + return "Drop bud?"; + case TNC_DATA_CORRUPTED: + case INVALID_INO_NODE: + case INVALID_DENT_NODE: + case INVALID_DATA_NODE: + case SCAN_CORRUPTED: + return "Drop it?"; + case ORPHAN_CORRUPTED: + return "Drop orphans on the LEB?"; + case FILE_HAS_NO_INODE: + case FILE_HAS_0_NLINK_INODE: + case FILE_HAS_NO_DENT: + case XATTR_HAS_NO_HOST: + case XATTR_HAS_WRONG_HOST: + case FILE_HAS_NO_ENCRYPT: + case FILE_ROOT_HAS_DENT: + case DENTRY_IS_UNREACHABLE: + case DISCONNECTED_FILE_CANNOT_BE_RECOVERED: + return "Delete it?"; + case FILE_HAS_INCONSIST_TYPE: + case FILE_HAS_TOO_MANY_DENT: + return "Remove dentry?"; + case FILE_SHOULDNT_HAVE_DATA: + return "Remove data block?"; + case FILE_IS_DISCONNECTED: + return "Put it into disconnected list?"; + case LPT_CORRUPTED: + return "Rebuild LPT?"; + case ROOT_DIR_NOT_FOUND: + return "Create a new one?"; + } + + return "Fix it?"; +} + +static void print_problem(const struct ubifs_info *c, + const struct fsck_problem *problem, int problem_type, + const void *priv) +{ + switch (problem_type) { + case BUD_CORRUPTED: + { + const struct ubifs_bud *bud = (const struct ubifs_bud *)priv; + + log_out(c, "problem: %s %d:%d %s", problem->desc, bud->lnum, + bud->start, dbg_jhead(bud->jhead)); + break; + } + case ORPHAN_CORRUPTED: + { + const int *lnum = (const int *)priv; + + log_out(c, "problem: %s %d", problem->desc, *lnum); + break; + } + case SCAN_CORRUPTED: + { + const struct ubifs_zbranch *zbr = (const struct ubifs_zbranch *)priv; + + log_out(c, "problem: %s in LEB %d, node in %d:%d becomes invalid", + problem->desc, zbr->lnum, zbr->lnum, zbr->offs); + break; + } + case FILE_HAS_NO_INODE: + { + const struct invalid_file_problem *ifp = (const struct invalid_file_problem *)priv; + + log_out(c, "problem: %s, ino %lu", problem->desc, ifp->file->inum); + break; + } + case FILE_HAS_INCONSIST_TYPE: + { + const struct invalid_file_problem *ifp = (const struct invalid_file_problem *)priv; + const struct scanned_dent_node *dent_node = (const struct scanned_dent_node *)ifp->priv; + + log_out(c, "problem: %s, ino %lu, inode type %s%s, dentry %s has type %s%s", + problem->desc, ifp->file->inum, + ubifs_get_type_name(ubifs_get_dent_type(ifp->file->ino.mode)), + ifp->file->ino.is_xattr ? "(xattr)" : "", + c->encrypted && !ifp->file->ino.is_xattr ? "<encrypted>" : dent_node->name, + ubifs_get_type_name(dent_node->type), + key_type(c, &dent_node->key) == UBIFS_XENT_KEY ? "(xattr)" : ""); + break; + } + case FILE_HAS_TOO_MANY_DENT: + case FILE_ROOT_HAS_DENT: + { + const struct invalid_file_problem *ifp = (const struct invalid_file_problem *)priv; + const struct scanned_dent_node *dent_node = (const struct scanned_dent_node *)ifp->priv; + + log_out(c, "problem: %s, ino %lu, type %s%s, dentry %s", + problem->desc, ifp->file->inum, + ubifs_get_type_name(ubifs_get_dent_type(ifp->file->ino.mode)), + ifp->file->ino.is_xattr ? "(xattr)" : "", + c->encrypted && !ifp->file->ino.is_xattr ? "<encrypted>" : dent_node->name); + break; + } + case FILE_SHOULDNT_HAVE_DATA: + { + const struct invalid_file_problem *ifp = (const struct invalid_file_problem *)priv; + const struct scanned_data_node *data_node = (const struct scanned_data_node *)ifp->priv; + + log_out(c, "problem: %s, ino %lu, type %s%s, data block %u", + problem->desc, ifp->file->inum, + ubifs_get_type_name(ubifs_get_dent_type(ifp->file->ino.mode)), + ifp->file->ino.is_xattr ? "(xattr)" : "", + key_block(c, &data_node->key)); + break; + } + case FILE_HAS_0_NLINK_INODE: + case FILE_HAS_NO_DENT: + case XATTR_HAS_NO_HOST: + case FILE_HAS_NO_ENCRYPT: + case FILE_IS_DISCONNECTED: + { + const struct invalid_file_problem *ifp = (const struct invalid_file_problem *)priv; + + log_out(c, "problem: %s, ino %lu type %s%s", problem->desc, + ifp->file->inum, + ubifs_get_type_name(ubifs_get_dent_type(ifp->file->ino.mode)), + ifp->file->ino.is_xattr ? "(xattr)" : ""); + break; + } + case XATTR_HAS_WRONG_HOST: + { + const struct invalid_file_problem *ifp = (const struct invalid_file_problem *)priv; + const struct scanned_file *host = (const struct scanned_file *)ifp->priv; + + log_out(c, "problem: %s, ino %lu type %s%s, host ino %lu type %s%s", + problem->desc, ifp->file->inum, + ubifs_get_type_name(ubifs_get_dent_type(ifp->file->ino.mode)), + ifp->file->ino.is_xattr ? "(xattr)" : "", host->inum, + ubifs_get_type_name(ubifs_get_dent_type(host->ino.mode)), + host->ino.is_xattr ? "(xattr)" : ""); + break; + } + case DENTRY_IS_UNREACHABLE: + { + const struct invalid_file_problem *ifp = (const struct invalid_file_problem *)priv; + const struct scanned_dent_node *dent_node = (const struct scanned_dent_node *)ifp->priv; + + log_out(c, "problem: %s, ino %lu, unreachable dentry %s, type %s%s", + problem->desc, ifp->file->inum, + c->encrypted && !ifp->file->ino.is_xattr ? "<encrypted>" : dent_node->name, + ubifs_get_type_name(dent_node->type), + key_type(c, &dent_node->key) == UBIFS_XENT_KEY ? "(xattr)" : ""); + break; + } + case FILE_IS_INCONSISTENT: + { + const struct invalid_file_problem *ifp = (const struct invalid_file_problem *)priv; + const struct scanned_file *file = ifp->file; + + log_out(c, "problem: %s, ino %lu type %s, nlink %u xcnt %u xsz %u xnms %u size %llu, " + "should be nlink %u xcnt %u xsz %u xnms %u size %llu", + problem->desc, file->inum, + file->ino.is_xattr ? "xattr" : ubifs_get_type_name(ubifs_get_dent_type(file->ino.mode)), + file->ino.nlink, file->ino.xcnt, file->ino.xsz, + file->ino.xnms, file->ino.size, + file->calc_nlink, file->calc_xcnt, file->calc_xsz, + file->calc_xnms, file->calc_size); + break; + } + case NNODE_INCORRECT: + { + const struct nnode_problem *nnp = (const struct nnode_problem *)priv; + + log_out(c, "problem: %s, nnode num %d expected %d parent num %d iip %d", + problem->desc, nnp->nnode->num, nnp->num, + nnp->parent_nnode ? nnp->parent_nnode->num : 0, + nnp->nnode->iip); + break; + } + case PNODE_INCORRECT: + { + const struct pnode_problem *pnp = (const struct pnode_problem *)priv; + + log_out(c, "problem: %s, pnode num %d expected %d parent num %d iip %d", + problem->desc, pnp->pnode->num, pnp->num, + pnp->pnode->parent->num, pnp->pnode->iip); + break; + } + case LP_INCORRECT: + { + const struct lp_problem *lpp = (const struct lp_problem *)priv; + + log_out(c, "problem: %s %d, free %d dirty %d is_idx %d, should be lnum %d free %d dirty %d is_idx %d", + problem->desc, lpp->lp->lnum, lpp->lp->free, + lpp->lp->dirty, lpp->lp->flags & LPROPS_INDEX ? 1 : 0, + lpp->lnum, lpp->free, lpp->dirty, lpp->is_idx); + break; + } + case SPACE_STAT_INCORRECT: + { + const struct space_stat_problem *ssp = (const struct space_stat_problem *)priv; + + log_out(c, "problem: %s, empty_lebs %d idx_lebs %d total_free %lld total_dirty %lld total_used %lld total_dead %lld total_dark %lld, should be empty_lebs %d idx_lebs %d total_free %lld total_dirty %lld total_used %lld total_dead %lld total_dark %lld", + problem->desc, ssp->lst->empty_lebs, ssp->lst->idx_lebs, + ssp->lst->total_free, ssp->lst->total_dirty, + ssp->lst->total_used, ssp->lst->total_dead, + ssp->lst->total_dark, ssp->calc_lst->empty_lebs, + ssp->calc_lst->idx_lebs, ssp->calc_lst->total_free, + ssp->calc_lst->total_dirty, ssp->calc_lst->total_used, + ssp->calc_lst->total_dead, ssp->calc_lst->total_dark); + break; + } + case INCORRECT_IDX_SZ: + { + const unsigned long long *calc_sz = (const unsigned long long *)priv; + + log_out(c, "problem: %s, index size is %llu, should be %llu", + problem->desc, c->calc_idx_sz, *calc_sz); + break; + } + case DISCONNECTED_FILE_CANNOT_BE_RECOVERED: + { + const struct scanned_file *file = (const struct scanned_file *)priv; + + log_out(c, "problem: %s, ino %lu, size %llu", problem->desc, + file->inum, file->ino.size); + break; + } + default: + log_out(c, "problem: %s", problem->desc); + break; + } +} + +static void fatal_error(const struct ubifs_info *c, + const struct fsck_problem *problem) +{ + if (!(problem->flags & PROBLEM_FIXABLE)) + log_out(c, "inconsistent problem cannot be fixed"); + else + log_out(c, "inconsistent problem must be fixed"); + exit(exit_code); +} + +/** + * fix_problem - whether fixing the inconsistent problem + * @c: UBIFS file-system description object + * @problem_type: the type of inconsistent problem + * @priv: private data for problem instance + * + * This function decides to fix/skip the inconsistent problem or abort the + * program according to @problem_type, returns %true if the problem should + * be fixed, returns %false if the problem will be skipped. + */ +bool fix_problem(const struct ubifs_info *c, int problem_type, const void *priv) +{ + bool ans, ask = true, def_y = true; + const struct fsck_problem *problem = &problem_table[problem_type]; + const char *question = get_question(problem, problem_type); + + ubifs_assert(c, FSCK(c)->mode != REBUILD_MODE); + + if (!(problem->flags & PROBLEM_FIXABLE)) { + exit_code |= FSCK_UNCORRECTED; + fatal_error(c, problem); + } + + if (FSCK(c)->mode == CHECK_MODE || + ((problem->flags & PROBLEM_DROP_DATA) && FSCK(c)->mode == SAFE_MODE) || + ((problem->flags & PROBLEM_NEED_REBUILD) && + (FSCK(c)->mode == SAFE_MODE || FSCK(c)->mode == DANGER_MODE0))) + def_y = false; + + if ((problem->flags & PROBLEM_NEED_REBUILD) && + (FSCK(c)->mode == DANGER_MODE0 || FSCK(c)->mode == DANGER_MODE1)) + ask = false; + + print_problem(c, problem, problem_type, priv); + ans = def_y; + if (FSCK(c)->mode == NORMAL_MODE) { + printf("%s[%d] (%s%s)", c->program_name, getpid(), + c->dev_name ? : "-", mode_name(c)); + if (prompt(question, def_y)) + ans = true; + else + ans = false; + } else { + if (ask) + log_out(c, "%s %c\n", question, def_y ? 'y' : 'n'); + } + + if (!ans) { + exit_code |= FSCK_UNCORRECTED; + if (problem->flags & PROBLEM_MUST_FIX) + fatal_error(c, problem); + } else { + exit_code |= FSCK_NONDESTRUCT; + } + + return ans; +} diff --git a/ubifs-utils/fsck.ubifs/rebuild_fs.c b/ubifs-utils/fsck.ubifs/rebuild_fs.c new file mode 100644 index 0000000..b82d728 --- /dev/null +++ b/ubifs-utils/fsck.ubifs/rebuild_fs.c @@ -0,0 +1,1453 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2024, Huawei Technologies Co, Ltd. + * + * Authors: Zhihao Cheng <chengzhihao1@huawei.com> + */ + +#include <stdio.h> +#include <stdlib.h> +#include <getopt.h> +#include <sys/stat.h> + +#include "linux_err.h" +#include "bitops.h" +#include "kmem.h" +#include "ubifs.h" +#include "defs.h" +#include "debug.h" +#include "key.h" +#include "misc.h" +#include "fsck.ubifs.h" + +/** + * scanned_info - nodes and files information from scanning. + * @valid_inos: the tree of scanned inode nodes with 'nlink > 0' + * @del_inos: the tree of scanned inode nodes with 'nlink = 0' + * @valid_dents: the tree of scanned dentry nodes with 'inum > 0' + * @del_dents: the tree of scanned dentry nodes with 'inum = 0' + */ +struct scanned_info { + struct rb_root valid_inos; + struct rb_root del_inos; + struct rb_root valid_dents; + struct rb_root del_dents; +}; + +/** + * struct idx_entry - index entry. + * @list: link in the list index entries for building index tree + * @key: key + * @name: directory entry name used for sorting colliding keys by name + * @lnum: LEB number + * @offs: offset + * @len: length + * + * The index is recorded as a linked list which is sorted and used to create + * the bottom level of the on-flash index tree. The remaining levels of the + * index tree are each built from the level below. + */ +struct idx_entry { + struct list_head list; + union ubifs_key key; + char *name; + int name_len; + int lnum; + int offs; + int len; +}; + +static int init_rebuild_info(struct ubifs_info *c) +{ + int err; + + c->sbuf = vmalloc(c->leb_size); + if (!c->sbuf) { + log_err(c, errno, "can not allocate sbuf"); + return -ENOMEM; + } + FSCK(c)->rebuild = kzalloc(sizeof(struct ubifs_rebuild_info), + GFP_KERNEL); + if (!FSCK(c)->rebuild) { + err = -ENOMEM; + log_err(c, errno, "can not allocate rebuild info"); + goto free_sbuf; + } + FSCK(c)->scanned_files = RB_ROOT; + FSCK(c)->used_lebs = kcalloc(BITS_TO_LONGS(c->main_lebs), + sizeof(unsigned long), GFP_KERNEL); + if (!FSCK(c)->used_lebs) { + err = -ENOMEM; + log_err(c, errno, "can not allocate bitmap of used lebs"); + goto free_rebuild; + } + FSCK(c)->lpts = kzalloc(sizeof(struct ubifs_lprops) * c->main_lebs, + GFP_KERNEL); + if (!FSCK(c)->lpts) { + err = -ENOMEM; + log_err(c, errno, "can not allocate lpts"); + goto free_used_lebs; + } + FSCK(c)->rebuild->write_buf = vmalloc(c->leb_size); + if (!FSCK(c)->rebuild->write_buf) { + err = -ENOMEM; + goto free_lpts; + } + FSCK(c)->rebuild->head_lnum = -1; + + return 0; + +free_lpts: + kfree(FSCK(c)->lpts); +free_used_lebs: + kfree(FSCK(c)->used_lebs); +free_rebuild: + kfree(FSCK(c)->rebuild); +free_sbuf: + vfree(c->sbuf); + return err; +} + +static void destroy_rebuild_info(struct ubifs_info *c) +{ + vfree(FSCK(c)->rebuild->write_buf); + kfree(FSCK(c)->lpts); + kfree(FSCK(c)->used_lebs); + kfree(FSCK(c)->rebuild); + vfree(c->sbuf); +} + +/** + * insert_or_update_ino_node - insert or update inode node. + * @c: UBIFS file-system description object + * @new_ino: new inode node + * @tree: a tree to record valid/deleted inode node info + * + * This function inserts @new_ino into the @tree, or updates inode node + * if it already exists in the tree. Returns zero in case of success, a + * negative error code in case of failure. + */ +static int insert_or_update_ino_node(struct ubifs_info *c, + struct scanned_ino_node *new_ino, + struct rb_root *tree) +{ + int cmp; + struct scanned_ino_node *ino_node, *old_ino_node = NULL; + struct rb_node **p, *parent = NULL; + + p = &tree->rb_node; + while (*p) { + parent = *p; + ino_node = rb_entry(parent, struct scanned_ino_node, rb); + cmp = keys_cmp(c, &new_ino->key, &ino_node->key); + if (cmp < 0) { + p = &(*p)->rb_left; + } else if (cmp > 0) { + p = &(*p)->rb_right; + } else { + old_ino_node = ino_node; + break; + } + } + if (old_ino_node) { + if (old_ino_node->header.sqnum < new_ino->header.sqnum) { + size_t len = offsetof(struct scanned_ino_node, rb); + + memcpy(old_ino_node, new_ino, len); + } + return 0; + } + + ino_node = kmalloc(sizeof(struct scanned_ino_node), GFP_KERNEL); + if (!ino_node) + return -ENOMEM; + + *ino_node = *new_ino; + rb_link_node(&ino_node->rb, parent, p); + rb_insert_color(&ino_node->rb, tree); + + return 0; +} + +static int namecmp(const char *a, int la, const char *b, int lb) +{ + int cmp, len = min(la, lb); + + cmp = memcmp(a, b, len); + if (cmp) + return cmp; + + return la - lb; +} + +/** + * insert_or_update_dent_node - insert or update dentry node. + * @c: UBIFS file-system description object + * @new_dent: new dentry node + * @tree: a tree to record valid/deleted dentry node info + * + * This function inserts @new_dent into the @tree, or updates dent node + * if it already exists in the tree. Returns zero in case of success, a + * negative error code in case of failure. + */ +static int insert_or_update_dent_node(struct ubifs_info *c, + struct scanned_dent_node *new_dent, + struct rb_root *tree) +{ + int cmp; + struct scanned_dent_node *dent_node, *old_dent_node = NULL; + struct rb_node **p, *parent = NULL; + + p = &tree->rb_node; + while (*p) { + parent = *p; + dent_node = rb_entry(parent, struct scanned_dent_node, rb); + cmp = keys_cmp(c, &new_dent->key, &dent_node->key); + if (cmp < 0) { + p = &(*p)->rb_left; + } else if (cmp > 0) { + p = &(*p)->rb_right; + } else { + cmp = namecmp(new_dent->name, new_dent->nlen, + dent_node->name, dent_node->nlen); + if (cmp < 0) { + p = &(*p)->rb_left; + } else if (cmp > 0) { + p = &(*p)->rb_right; + } else { + old_dent_node = dent_node; + break; + } + } + } + if (old_dent_node) { + if (old_dent_node->header.sqnum < new_dent->header.sqnum) { + size_t len = offsetof(struct scanned_dent_node, rb); + + memcpy(old_dent_node, new_dent, len); + } + return 0; + } + + dent_node = kmalloc(sizeof(struct scanned_dent_node), GFP_KERNEL); + if (!dent_node) + return -ENOMEM; + + *dent_node = *new_dent; + rb_link_node(&dent_node->rb, parent, p); + rb_insert_color(&dent_node->rb, tree); + + return 0; +} + +/** + * process_scanned_node - process scanned node. + * @c: UBIFS file-system description object + * @lnum: logical eraseblock number + * @snod: scanned node + * @si: records nodes and files information during scanning + * + * This function parses, checks and records scanned node information. + * Returns zero in case of success, 1% if the scanned LEB doesn't hold file + * data and should be ignored(eg. index LEB), a negative error code in case + * of failure. + */ +static int process_scanned_node(struct ubifs_info *c, int lnum, + struct ubifs_scan_node *snod, + struct scanned_info *si) +{ + ino_t inum; + int offs = snod->offs; + void *node = snod->node; + union ubifs_key *key = &snod->key; + struct rb_root *tree; + struct scanned_node *sn; + struct scanned_ino_node ino_node; + struct scanned_dent_node dent_node; + struct scanned_data_node data_node; + struct scanned_trun_node trun_node; + + switch (snod->type) { + case UBIFS_INO_NODE: + { + if (!parse_ino_node(c, lnum, offs, node, key, &ino_node)) + return 0; + + tree = &si->del_inos; + if (ino_node.nlink) + tree = &si->valid_inos; + return insert_or_update_ino_node(c, &ino_node, tree); + } + case UBIFS_DENT_NODE: + case UBIFS_XENT_NODE: + { + if (!parse_dent_node(c, lnum, offs, node, key, &dent_node)) + return 0; + + tree = &si->del_dents; + if (dent_node.inum) + tree = &si->valid_dents; + return insert_or_update_dent_node(c, &dent_node, tree); + } + case UBIFS_DATA_NODE: + { + if (!parse_data_node(c, lnum, offs, node, key, &data_node)) + return 0; + + inum = key_inum(c, key); + sn = (struct scanned_node *)&data_node; + break; + } + case UBIFS_TRUN_NODE: + { + if (!parse_trun_node(c, lnum, offs, node, key, &trun_node)) + return 0; + + inum = le32_to_cpu(((struct ubifs_trun_node *)node)->inum); + sn = (struct scanned_node *)&trun_node; + break; + } + default: + dbg_fsck("skip node type %d, at %d:%d, in %s", + snod->type, lnum, offs, c->dev_name); + return 1; + } + + tree = &FSCK(c)->scanned_files; + return insert_or_update_file(c, tree, sn, key_type(c, key), inum); +} + +/** + * destroy_scanned_info - destroy scanned nodes. + * @c: UBIFS file-system description object + * @si: records nodes and files information during scanning + * + * Destroy scanned files and all data/dentry nodes attached to file, destroy + * valid/deleted inode/dentry info. + */ +static void destroy_scanned_info(struct ubifs_info *c, struct scanned_info *si) +{ + struct scanned_ino_node *ino_node; + struct scanned_dent_node *dent_node; + struct rb_node *this; + + destroy_file_tree(c, &FSCK(c)->scanned_files); + + this = rb_first(&si->valid_inos); + while (this) { + ino_node = rb_entry(this, struct scanned_ino_node, rb); + this = rb_next(this); + + rb_erase(&ino_node->rb, &si->valid_inos); + kfree(ino_node); + } + + this = rb_first(&si->del_inos); + while (this) { + ino_node = rb_entry(this, struct scanned_ino_node, rb); + this = rb_next(this); + + rb_erase(&ino_node->rb, &si->del_inos); + kfree(ino_node); + } + + this = rb_first(&si->valid_dents); + while (this) { + dent_node = rb_entry(this, struct scanned_dent_node, rb); + this = rb_next(this); + + rb_erase(&dent_node->rb, &si->valid_dents); + kfree(dent_node); + } + + this = rb_first(&si->del_dents); + while (this) { + dent_node = rb_entry(this, struct scanned_dent_node, rb); + this = rb_next(this); + + rb_erase(&dent_node->rb, &si->del_dents); + kfree(dent_node); + } +} + +/** + * scan_nodes - scan node information from flash. + * @c: UBIFS file-system description object + * @si: records nodes and files information during scanning + * + * This function scans nodes from flash, all ino/dent nodes are split + * into valid tree and deleted tree, all trun/data nodes are collected + * into file, the file is inserted into @FSCK(c)->scanned_files. + */ +static int scan_nodes(struct ubifs_info *c, struct scanned_info *si) +{ + int lnum, err = 0; + struct ubifs_scan_leb *sleb; + struct ubifs_scan_node *snod; + + for (lnum = c->main_first; lnum < c->leb_cnt; ++lnum) { + dbg_fsck("scan nodes at LEB %d, in %s", lnum, c->dev_name); + + sleb = ubifs_scan(c, lnum, 0, c->sbuf, 1); + if (IS_ERR(sleb)) { + if (PTR_ERR(sleb) != -EUCLEAN) + return PTR_ERR(sleb); + + sleb = ubifs_recover_leb(c, lnum, 0, c->sbuf, -1); + if (IS_ERR(sleb)) { + if (PTR_ERR(sleb) != -EUCLEAN) + return PTR_ERR(sleb); + + /* This LEB holds corrupted data, abandon it. */ + continue; + } + } + + list_for_each_entry(snod, &sleb->nodes, list) { + if (snod->sqnum > c->max_sqnum) + c->max_sqnum = snod->sqnum; + + err = process_scanned_node(c, lnum, snod, si); + if (err < 0) { + log_err(c, 0, "process node failed at LEB %d, err %d", + lnum, err); + ubifs_scan_destroy(sleb); + goto out; + } else if (err == 1) { + err = 0; + break; + } + } + + ubifs_scan_destroy(sleb); + } + +out: + return err; +} + +static struct scanned_ino_node * +lookup_valid_ino_node(struct ubifs_info *c, struct scanned_info *si, + struct scanned_ino_node *target) +{ + int cmp; + struct scanned_ino_node *ino_node; + struct rb_node *p; + + p = si->valid_inos.rb_node; + while (p) { + ino_node = rb_entry(p, struct scanned_ino_node, rb); + cmp = keys_cmp(c, &target->key, &ino_node->key); + if (cmp < 0) { + p = p->rb_left; + } else if (cmp > 0) { + p = p->rb_right; + } else { + if (target->header.sqnum > ino_node->header.sqnum) + return ino_node; + else + return NULL; + } + } + + return NULL; +} + +static struct scanned_dent_node * +lookup_valid_dent_node(struct ubifs_info *c, struct scanned_info *si, + struct scanned_dent_node *target) +{ + int cmp; + struct scanned_dent_node *dent_node; + struct rb_node *p; + + p = si->valid_dents.rb_node; + while (p) { + dent_node = rb_entry(p, struct scanned_dent_node, rb); + cmp = keys_cmp(c, &target->key, &dent_node->key); + if (cmp < 0) { + p = p->rb_left; + } else if (cmp > 0) { + p = p->rb_right; + } else { + cmp = namecmp(target->name, target->nlen, + dent_node->name, dent_node->nlen); + if (cmp < 0) { + p = p->rb_left; + } else if (cmp > 0) { + p = p->rb_right; + } else { + if (target->header.sqnum > + dent_node->header.sqnum) + return dent_node; + else + return NULL; + } + } + } + + return NULL; +} + +static void update_lpt(struct ubifs_info *c, struct scanned_node *sn, + bool deleted) +{ + int index = sn->lnum - c->main_first; + int pos = sn->offs + ALIGN(sn->len, 8); + + set_bit(index, FSCK(c)->used_lebs); + FSCK(c)->lpts[index].end = max_t(int, FSCK(c)->lpts[index].end, pos); + + if (deleted) + return; + + FSCK(c)->lpts[index].used += ALIGN(sn->len, 8); +} + +/** + * remove_del_nodes - remove deleted nodes from valid node tree. + * @c: UBIFS file-system description object + * @si: records nodes and files information during scanning + * + * This function compares sqnum between deleted node and corresponding valid + * node, removes valid node from tree if the sqnum of deleted node is bigger. + * Deleted ino/dent nodes will be removed from @si->del_inos/@si->del_dents + * after this function finished. + */ +static void remove_del_nodes(struct ubifs_info *c, struct scanned_info *si) +{ + struct scanned_ino_node *del_ino_node, *valid_ino_node; + struct scanned_dent_node *del_dent_node, *valid_dent_node; + struct rb_node *this; + + this = rb_first(&si->del_inos); + while (this) { + del_ino_node = rb_entry(this, struct scanned_ino_node, rb); + this = rb_next(this); + + valid_ino_node = lookup_valid_ino_node(c, si, del_ino_node); + if (valid_ino_node) { + update_lpt(c, &del_ino_node->header, true); + rb_erase(&valid_ino_node->rb, &si->valid_inos); + kfree(valid_ino_node); + } + + rb_erase(&del_ino_node->rb, &si->del_inos); + kfree(del_ino_node); + } + + this = rb_first(&si->del_dents); + while (this) { + del_dent_node = rb_entry(this, struct scanned_dent_node, rb); + this = rb_next(this); + + valid_dent_node = lookup_valid_dent_node(c, si, del_dent_node); + if (valid_dent_node) { + update_lpt(c, &del_dent_node->header, true); + rb_erase(&valid_dent_node->rb, &si->valid_dents); + kfree(valid_dent_node); + } + + rb_erase(&del_dent_node->rb, &si->del_dents); + kfree(del_dent_node); + } +} + +/** + * add_valid_nodes_into_file - add valid nodes into file. + * @c: UBIFS file-system description object + * @si: records nodes and files information during scanning + * + * This function adds valid nodes into corresponding file, all valid ino/dent + * nodes will be removed from @si->valid_inos/@si->valid_dents if the function + * is executed successfully. + */ +static int add_valid_nodes_into_file(struct ubifs_info *c, + struct scanned_info *si) +{ + int err, type; + ino_t inum; + struct scanned_node *sn; + struct scanned_ino_node *ino_node; + struct scanned_dent_node *dent_node; + struct rb_node *this; + struct rb_root *tree = &FSCK(c)->scanned_files; + + this = rb_first(&si->valid_inos); + while (this) { + ino_node = rb_entry(this, struct scanned_ino_node, rb); + this = rb_next(this); + + sn = (struct scanned_node *)ino_node; + type = key_type(c, &ino_node->key); + inum = key_inum(c, &ino_node->key); + err = insert_or_update_file(c, tree, sn, type, inum); + if (err) + return err; + + rb_erase(&ino_node->rb, &si->valid_inos); + kfree(ino_node); + } + + this = rb_first(&si->valid_dents); + while (this) { + dent_node = rb_entry(this, struct scanned_dent_node, rb); + this = rb_next(this); + + sn = (struct scanned_node *)dent_node; + inum = dent_node->inum; + type = key_type(c, &dent_node->key); + err = insert_or_update_file(c, tree, sn, type, inum); + if (err) + return err; + + rb_erase(&dent_node->rb, &si->valid_dents); + kfree(dent_node); + } + + return 0; +} + +/** + * filter_invalid_files - filter out invalid files. + * @c: UBIFS file-system description object + * + * This function filters out invalid files(eg. inconsistent types between + * inode and dentry node, or missing inode/dentry node, or encrypted inode + * has no encryption related xattrs, etc.). + */ +static void filter_invalid_files(struct ubifs_info *c) +{ + struct rb_node *node; + struct scanned_file *file; + struct rb_root *tree = &FSCK(c)->scanned_files; + LIST_HEAD(tmp_list); + + /* Add all xattr files into a list. */ + for (node = rb_first(tree); node; node = rb_next(node)) { + file = rb_entry(node, struct scanned_file, rb); + + if (file->ino.is_xattr) + list_add(&file->list, &tmp_list); + } + + /* + * Round 1: Traverse xattr files, check whether the xattr file is + * valid, move valid xattr file into corresponding host file's subtree. + */ + while (!list_empty(&tmp_list)) { + file = list_entry(tmp_list.next, struct scanned_file, list); + + list_del(&file->list); + rb_erase(&file->rb, tree); + if (!file_is_valid(c, file, tree, NULL)) { + destroy_file_content(c, file); + kfree(file); + } + } + + /* Round 2: Traverse non-xattr files. */ + for (node = rb_first(tree); node; node = rb_next(node)) { + file = rb_entry(node, struct scanned_file, rb); + + if (!file_is_valid(c, file, tree, NULL)) + list_add(&file->list, &tmp_list); + } + + /* Remove invalid files. */ + while (!list_empty(&tmp_list)) { + file = list_entry(tmp_list.next, struct scanned_file, list); + + list_del(&file->list); + destroy_file_content(c, file); + rb_erase(&file->rb, tree); + kfree(file); + } +} + +/** + * extract_dentry_tree - extract reachable directory entries. + * @c: UBIFS file-system description object + * + * This function iterates all directory entries and remove those + * unreachable ones. 'Unreachable' means that a directory entry can + * not be searched from '/'. + */ +static void extract_dentry_tree(struct ubifs_info *c) +{ + struct rb_node *node; + struct scanned_file *file; + struct rb_root *tree = &FSCK(c)->scanned_files; + LIST_HEAD(unreachable); + + for (node = rb_first(tree); node; node = rb_next(node)) { + file = rb_entry(node, struct scanned_file, rb); + + /* + * Since all xattr files are already attached to corresponding + * host file, there are only non-xattr files in the file tree. + */ + ubifs_assert(c, !file->ino.is_xattr); + if (!file_is_reachable(c, file, tree)) + list_add(&file->list, &unreachable); + } + + /* Remove unreachable files. */ + while (!list_empty(&unreachable)) { + file = list_entry(unreachable.next, struct scanned_file, list); + + dbg_fsck("remove unreachable file %lu, in %s", + file->inum, c->dev_name); + list_del(&file->list); + destroy_file_content(c, file); + rb_erase(&file->rb, tree); + kfree(file); + } +} + +static void init_root_ino(struct ubifs_info *c, struct ubifs_ino_node *ino) +{ + __le64 tmp_le64; + + /* Create default root inode */ + ino_key_init_flash(c, &ino->key, UBIFS_ROOT_INO); + ino->ch.node_type = UBIFS_INO_NODE; + ino->creat_sqnum = cpu_to_le64(++c->max_sqnum); + ino->nlink = cpu_to_le32(2); + tmp_le64 = cpu_to_le64(time(NULL)); + ino->atime_sec = tmp_le64; + ino->ctime_sec = tmp_le64; + ino->mtime_sec = tmp_le64; + ino->atime_nsec = 0; + ino->ctime_nsec = 0; + ino->mtime_nsec = 0; + ino->mode = cpu_to_le32(S_IFDIR | S_IRUGO | S_IWUSR | S_IXUGO); + ino->size = cpu_to_le64(UBIFS_INO_NODE_SZ); + /* Set compression enabled by default */ + ino->flags = cpu_to_le32(UBIFS_COMPR_FL); +} + +/** + * flush_write_buf - flush write buffer. + * @c: UBIFS file-system description object + * + * This function flush write buffer to LEB @FSCK(c)->rebuild->head_lnum, then + * set @FSCK(c)->rebuild->head_lnum to '-1'. + */ +static int flush_write_buf(struct ubifs_info *c) +{ + int len, pad, err; + + if (!FSCK(c)->rebuild->head_offs) + return 0; + + len = ALIGN(FSCK(c)->rebuild->head_offs, c->min_io_size); + pad = len - FSCK(c)->rebuild->head_offs; + if (pad) + ubifs_pad(c, FSCK(c)->rebuild->write_buf + + FSCK(c)->rebuild->head_offs, pad); + + err = ubifs_leb_write(c, FSCK(c)->rebuild->head_lnum, + FSCK(c)->rebuild->write_buf, 0, len); + if (err) + return err; + + if (FSCK(c)->rebuild->need_update_lpt) { + int index = FSCK(c)->rebuild->head_lnum - c->main_first; + + FSCK(c)->lpts[index].free = c->leb_size - len; + FSCK(c)->lpts[index].dirty = pad; + FSCK(c)->lpts[index].flags = LPROPS_INDEX; + } + + FSCK(c)->rebuild->head_lnum = -1; + + return 0; +} + +/** + * reserve_space - reserve enough space to write data. + * @c: UBIFS file-system description object + * @len: the length of written data + * @lnum: the write LEB number is returned here + * @offs: the write pos in LEB is returned here + * + * This function finds target position <@lnum, @offs> to write data with + * length of @len. + */ +static int reserve_space(struct ubifs_info *c, int len, int *lnum, int *offs) +{ + int err, new_lnum; + + if (FSCK(c)->rebuild->head_lnum == -1) { +get_new: + new_lnum = get_free_leb(c); + if (new_lnum < 0) + return new_lnum; + + err = ubifs_leb_unmap(c, new_lnum); + if (err) + return err; + + FSCK(c)->rebuild->head_lnum = new_lnum; + FSCK(c)->rebuild->head_offs = 0; + } + + if (len > c->leb_size - FSCK(c)->rebuild->head_offs) { + err = flush_write_buf(c); + if (err) + return err; + + goto get_new; + } + + *lnum = FSCK(c)->rebuild->head_lnum; + *offs = FSCK(c)->rebuild->head_offs; + FSCK(c)->rebuild->head_offs += ALIGN(len, 8); + + return 0; +} + +static void copy_node_data(struct ubifs_info *c, void *node, int offs, int len) +{ + memcpy(FSCK(c)->rebuild->write_buf + offs, node, len); + memset(FSCK(c)->rebuild->write_buf + offs + len, 0xff, ALIGN(len, 8) - len); +} + +/** + * create_root - create root dir. + * @c: UBIFS file-system description object + * + * This function creates root dir. + */ +static int create_root(struct ubifs_info *c) +{ + int err, lnum, offs; + struct ubifs_ino_node *ino; + struct scanned_file *file; + + ino = kzalloc(ALIGN(UBIFS_INO_NODE_SZ, c->min_io_size), GFP_KERNEL); + if (!ino) + return -ENOMEM; + + c->max_sqnum = 0; + c->highest_inum = UBIFS_FIRST_INO; + init_root_ino(c, ino); + err = ubifs_prepare_node_hmac(c, ino, UBIFS_INO_NODE_SZ, -1, 1); + if (err) + goto out; + + err = reserve_space(c, UBIFS_INO_NODE_SZ, &lnum, &offs); + if (err) + goto out; + + copy_node_data(c, ino, offs, UBIFS_INO_NODE_SZ); + + err = flush_write_buf(c); + if (err) + goto out; + + file = kzalloc(sizeof(struct scanned_file), GFP_KERNEL); + if (!file) { + err = -ENOMEM; + goto out; + } + + file->inum = UBIFS_ROOT_INO; + file->dent_nodes = RB_ROOT; + file->data_nodes = RB_ROOT; + INIT_LIST_HEAD(&file->list); + + file->ino.header.exist = true; + file->ino.header.lnum = lnum; + file->ino.header.offs = offs; + file->ino.header.len = UBIFS_INO_NODE_SZ; + file->ino.header.sqnum = le64_to_cpu(ino->ch.sqnum); + ino_key_init(c, &file->ino.key, UBIFS_ROOT_INO); + file->ino.is_xattr = le32_to_cpu(ino->flags) & UBIFS_XATTR_FL; + file->ino.mode = le32_to_cpu(ino->mode); + file->calc_nlink = file->ino.nlink = le32_to_cpu(ino->nlink); + file->calc_xcnt = file->ino.xcnt = le32_to_cpu(ino->xattr_cnt); + file->calc_xsz = file->ino.xsz = le32_to_cpu(ino->xattr_size); + file->calc_xnms = file->ino.xnms = le32_to_cpu(ino->xattr_names); + file->calc_size = file->ino.size = le64_to_cpu(ino->size); + + rb_link_node(&file->rb, NULL, &FSCK(c)->scanned_files.rb_node); + rb_insert_color(&file->rb, &FSCK(c)->scanned_files); + +out: + kfree(ino); + return err; +} + +static const char *get_file_name(struct ubifs_info *c, struct scanned_file *file) +{ + static char name[UBIFS_MAX_NLEN + 1]; + struct rb_node *node; + struct scanned_dent_node *dent_node; + + node = rb_first(&file->dent_nodes); + if (!node) { + ubifs_assert(c, file->inum == UBIFS_ROOT_INO); + return "/"; + } + + if (c->encrypted && !file->ino.is_xattr) + /* Encrypted file name. */ + return "<encrypted>"; + + /* Get name from any one dentry. */ + dent_node = rb_entry(node, struct scanned_dent_node, rb); + memcpy(name, dent_node->name, dent_node->nlen); + /* @dent->name could be non '\0' terminated. */ + name[dent_node->nlen] = '\0'; + return name; +} + +static int parse_node_info(struct ubifs_info *c, struct scanned_node *sn, + union ubifs_key *key, char *name, int name_len, + struct list_head *idx_list, int *idx_cnt) +{ + struct idx_entry *e; + + update_lpt(c, sn, idx_cnt == NULL); + + if (idx_cnt == NULL) + /* Skip truncation node. */ + return 0; + + e = kmalloc(sizeof(struct idx_entry), GFP_KERNEL); + if (!e) + return -ENOMEM; + + key_copy(c, key, &e->key); + e->name = name; + e->name_len = name_len; + e->lnum = sn->lnum; + e->offs = sn->offs; + e->len = sn->len; + list_add_tail(&e->list, idx_list); + *idx_cnt = *idx_cnt + 1; + + return 0; +} + +static int add_idx_node(struct ubifs_info *c, struct ubifs_idx_node *idx, + union ubifs_key *key, int child_cnt, + struct idx_entry *e) +{ + int err, lnum, offs, len; + + len = ubifs_idx_node_sz(c, child_cnt); + ubifs_prepare_node(c, idx, len, 0); + + err = reserve_space(c, len, &lnum, &offs); + if (err) + return err; + + copy_node_data(c, idx, offs, len); + + c->calc_idx_sz += ALIGN(len, 8); + + /* The last index node written will be the root */ + c->zroot.lnum = lnum; + c->zroot.offs = offs; + c->zroot.len = len; + + key_copy(c, key, &e->key); + e->lnum = lnum; + e->offs = offs; + e->len = len; + + return err; +} + +static int cmp_idx(void *priv, const struct list_head *a, + const struct list_head *b) +{ + int cmp; + struct ubifs_info *c = priv; + struct idx_entry *ia, *ib; + + if (a == b) + return 0; + + ia = list_entry(a, struct idx_entry, list); + ib = list_entry(b, struct idx_entry, list); + + cmp = keys_cmp(c, &ia->key, &ib->key); + if (cmp) + return cmp; + + return namecmp(ia->name, ia->name_len, ib->name, ib->name_len); +} + +/** + * build_tnc - construct TNC and write it into flash. + * @c: UBIFS file-system description object + * @lower_idxs: leaf entries of TNC + * @lower_cnt: the count of @lower_idxs + * + * This function builds TNC according to @lower_idxs and writes all index + * nodes into flash. + */ +static int build_tnc(struct ubifs_info *c, struct list_head *lower_idxs, + int lower_cnt) +{ + int i, j, err, upper_cnt, child_cnt, idx_sz, level = 0; + struct idx_entry *pe, *tmp_e, *e = NULL; + struct ubifs_idx_node *idx; + struct ubifs_branch *br; + union ubifs_key key; + LIST_HEAD(upper_idxs); + + idx_sz = ubifs_idx_node_sz(c, c->fanout); + idx = kmalloc(idx_sz, GFP_KERNEL); + if (!idx) + return -ENOMEM; + + list_sort(c, lower_idxs, cmp_idx); + FSCK(c)->rebuild->need_update_lpt = true; + + ubifs_assert(c, lower_cnt != 0); + + do { + upper_cnt = lower_cnt / c->fanout; + if (lower_cnt % c->fanout) + upper_cnt += 1; + e = list_first_entry(lower_idxs, struct idx_entry, list); + + for (i = 0; i < upper_cnt; i++) { + if (i == upper_cnt - 1) { + child_cnt = lower_cnt % c->fanout; + if (child_cnt == 0) + child_cnt = c->fanout; + } else + child_cnt = c->fanout; + + key_copy(c, &e->key, &key); + memset(idx, 0, idx_sz); + idx->ch.node_type = UBIFS_IDX_NODE; + idx->child_cnt = cpu_to_le16(child_cnt); + idx->level = cpu_to_le16(level); + for (j = 0; j < child_cnt; j++) { + ubifs_assert(c, + !list_entry_is_head(e, lower_idxs, list)); + br = ubifs_idx_branch(c, idx, j); + key_write_idx(c, &e->key, &br->key); + br->lnum = cpu_to_le32(e->lnum); + br->offs = cpu_to_le32(e->offs); + br->len = cpu_to_le32(e->len); + e = list_next_entry(e, list); + } + + pe = kmalloc(sizeof(struct idx_entry), GFP_KERNEL); + if (!pe) { + err = -ENOMEM; + goto out; + } + + err = add_idx_node(c, idx, &key, child_cnt, pe); + if (err) { + kfree(pe); + goto out; + } + + list_add_tail(&pe->list, &upper_idxs); + } + + level++; + list_for_each_entry_safe(e, tmp_e, lower_idxs, list) { + list_del(&e->list); + kfree(e); + } + list_splice_init(&upper_idxs, lower_idxs); + lower_cnt = upper_cnt; + } while (lower_cnt > 1); + + /* Set the index head */ + c->ihead_lnum = FSCK(c)->rebuild->head_lnum; + c->ihead_offs = ALIGN(FSCK(c)->rebuild->head_offs, c->min_io_size); + + /* Flush the last index LEB */ + err = flush_write_buf(c); + FSCK(c)->rebuild->need_update_lpt = false; + +out: + list_for_each_entry_safe(e, tmp_e, lower_idxs, list) { + list_del(&e->list); + kfree(e); + } + list_for_each_entry_safe(e, tmp_e, &upper_idxs, list) { + list_del(&e->list); + kfree(e); + } + kfree(idx); + return err; +} + +static int record_file_used_lebs(struct ubifs_info *c, + struct scanned_file *file, + struct list_head *idx_list, int *idx_cnt) +{ + int err; + struct rb_node *node; + struct scanned_file *xattr_file; + struct scanned_dent_node *dent_node; + struct scanned_data_node *data_node; + + dbg_fsck("recovered file(inum:%lu name:%s type:%s), in %s", + file->inum, get_file_name(c, file), + file->ino.is_xattr ? "xattr" : + ubifs_get_type_name(ubifs_get_dent_type(file->ino.mode)), + c->dev_name); + c->highest_inum = max_t(ino_t, c->highest_inum, file->inum); + + err = parse_node_info(c, &file->ino.header, &file->ino.key, + NULL, 0, idx_list, idx_cnt); + if (err) + return err; + + if (file->trun.header.exist) { + err = parse_node_info(c, &file->trun.header, NULL, NULL, + 0, idx_list, NULL); + if (err) + return err; + } + + for (node = rb_first(&file->data_nodes); node; node = rb_next(node)) { + data_node = rb_entry(node, struct scanned_data_node, rb); + + err = parse_node_info(c, &data_node->header, &data_node->key, + NULL, 0, idx_list, idx_cnt); + if (err) + return err; + } + + for (node = rb_first(&file->dent_nodes); node; node = rb_next(node)) { + dent_node = rb_entry(node, struct scanned_dent_node, rb); + + err = parse_node_info(c, &dent_node->header, &dent_node->key, + dent_node->name, dent_node->nlen, + idx_list, idx_cnt); + if (err) + return err; + } + + for (node = rb_first(&file->xattr_files); node; node = rb_next(node)) { + xattr_file = rb_entry(node, struct scanned_file, rb); + + err = record_file_used_lebs(c, xattr_file, idx_list, idx_cnt); + if (err) + return err; + } + + return err; +} + +/** + * traverse_files_and_nodes - traverse all nodes from valid files. + * @c: UBIFS file-system description object + * + * This function traverses all nodes from valid files and does following + * things: + * 1. If there are no scanned files, create default empty filesystem. + * 2. Record all used LEBs which may hold useful nodes, then left unused + * LEBs could be taken for storing new index tree. + * 3. Re-write data to prevent failed gc scanning in the subsequent mounting + * process caused by corrupted data. + * 4. Build TNC. + */ +static int traverse_files_and_nodes(struct ubifs_info *c) +{ + int i, err = 0, idx_cnt = 0; + struct rb_node *node; + struct scanned_file *file; + struct rb_root *tree = &FSCK(c)->scanned_files; + struct idx_entry *ie, *tmp_ie; + LIST_HEAD(idx_list); + + if (rb_first(tree) == NULL) { + /* No scanned files. Create root dir. */ + log_out(c, "No files found, create empty filesystem"); + err = create_root(c); + if (err) + return err; + } + + log_out(c, "Record used LEBs"); + for (node = rb_first(tree); node; node = rb_next(node)) { + file = rb_entry(node, struct scanned_file, rb); + + err = record_file_used_lebs(c, file, &idx_list, &idx_cnt); + if (err) + goto out_idx_list; + } + + /* Re-write data. */ + log_out(c, "Re-write data"); + for (i = 0; i < c->main_lebs; ++i) { + int lnum, len, end; + + if (!test_bit(i, FSCK(c)->used_lebs)) + continue; + + lnum = i + c->main_first; + dbg_fsck("re-write LEB %d, in %s", lnum, c->dev_name); + + end = FSCK(c)->lpts[i].end; + len = ALIGN(end, c->min_io_size); + + err = ubifs_leb_read(c, lnum, c->sbuf, 0, len, 0); + if (err && err != -EBADMSG) + goto out_idx_list; + + if (len > end) + ubifs_pad(c, c->sbuf + end, len - end); + + err = ubifs_leb_change(c, lnum, c->sbuf, len); + if (err) + goto out_idx_list; + } + + /* Build TNC. */ + log_out(c, "Build TNC"); + err = build_tnc(c, &idx_list, idx_cnt); + +out_idx_list: + list_for_each_entry_safe(ie, tmp_ie, &idx_list, list) { + list_del(&ie->list); + kfree(ie); + } + return err; +} + +static int calculate_lp(struct ubifs_info *c, int index, int *free, int *dirty, + __unused int *is_idx) +{ + if (!test_bit(index, FSCK(c)->used_lebs) || + c->gc_lnum == index + c->main_first) { + *free = c->leb_size; + *dirty = 0; + } else if (FSCK(c)->lpts[index].flags & LPROPS_INDEX) { + *free = FSCK(c)->lpts[index].free; + *dirty = FSCK(c)->lpts[index].dirty; + } else { + int len = ALIGN(FSCK(c)->lpts[index].end, c->min_io_size); + + *free = c->leb_size - len; + *dirty = len - FSCK(c)->lpts[index].used; + + if (*dirty == c->leb_size) { + *free = c->leb_size; + *dirty = 0; + } + } + + return 0; +} + +/** + * clean_log - clean up log area. + * @c: UBIFS file-system description object + * + * This function cleans up log area, since there is no need to do recovery + * in next mounting. + */ +static int clean_log(struct ubifs_info *c) +{ + int lnum, err; + struct ubifs_cs_node *cs; + + for (lnum = UBIFS_LOG_LNUM; lnum <= c->log_last; lnum++) { + err = ubifs_leb_unmap(c, lnum); + if (err) + return err; + } + + cs = kzalloc(ALIGN(UBIFS_CS_NODE_SZ, c->min_io_size), GFP_KERNEL); + if (!cs) + return -ENOMEM; + + cs->ch.node_type = UBIFS_CS_NODE; + cs->cmt_no = cpu_to_le64(0); + + err = ubifs_write_node(c, cs, UBIFS_CS_NODE_SZ, UBIFS_LOG_LNUM, 0); + kfree(cs); + if (err) + return err; + + return 0; +} + +/** + * write_master - write master nodes. + * @c: UBIFS file-system description object + * + * This function updates meta information into master node and writes master + * node into master area. + */ +static int write_master(struct ubifs_info *c) +{ + int err, lnum; + struct ubifs_mst_node *mst; + + mst = kzalloc(c->mst_node_alsz, GFP_KERNEL); + if (!mst) + return -ENOMEM; + + mst->ch.node_type = UBIFS_MST_NODE; + mst->log_lnum = cpu_to_le32(UBIFS_LOG_LNUM); + mst->highest_inum = cpu_to_le64(c->highest_inum); + mst->cmt_no = 0; + mst->root_lnum = cpu_to_le32(c->zroot.lnum); + mst->root_offs = cpu_to_le32(c->zroot.offs); + mst->root_len = cpu_to_le32(c->zroot.len); + mst->gc_lnum = cpu_to_le32(c->gc_lnum); + mst->ihead_lnum = cpu_to_le32(c->ihead_lnum); + mst->ihead_offs = cpu_to_le32(c->ihead_offs); + mst->index_size = cpu_to_le64(c->calc_idx_sz); + mst->lpt_lnum = cpu_to_le32(c->lpt_lnum); + mst->lpt_offs = cpu_to_le32(c->lpt_offs); + mst->nhead_lnum = cpu_to_le32(c->nhead_lnum); + mst->nhead_offs = cpu_to_le32(c->nhead_offs); + mst->ltab_lnum = cpu_to_le32(c->ltab_lnum); + mst->ltab_offs = cpu_to_le32(c->ltab_offs); + mst->lsave_lnum = cpu_to_le32(c->lsave_lnum); + mst->lsave_offs = cpu_to_le32(c->lsave_offs); + mst->lscan_lnum = cpu_to_le32(c->main_first); + mst->empty_lebs = cpu_to_le32(c->lst.empty_lebs); + mst->idx_lebs = cpu_to_le32(c->lst.idx_lebs); + mst->leb_cnt = cpu_to_le32(c->leb_cnt); + mst->total_free = cpu_to_le64(c->lst.total_free); + mst->total_dirty = cpu_to_le64(c->lst.total_dirty); + mst->total_used = cpu_to_le64(c->lst.total_used); + mst->total_dead = cpu_to_le64(c->lst.total_dead); + mst->total_dark = cpu_to_le64(c->lst.total_dark); + mst->flags |= cpu_to_le32(UBIFS_MST_NO_ORPHS); + + lnum = UBIFS_MST_LNUM; + err = ubifs_leb_unmap(c, lnum); + if (err) + goto out; + err = ubifs_write_node_hmac(c, mst, UBIFS_MST_NODE_SZ, lnum, 0, + offsetof(struct ubifs_mst_node, hmac)); + if (err) + goto out; + lnum++; + err = ubifs_leb_unmap(c, lnum); + if (err) + goto out; + err = ubifs_write_node_hmac(c, mst, UBIFS_MST_NODE_SZ, lnum, 0, + offsetof(struct ubifs_mst_node, hmac)); + if (err) + goto out; + +out: + kfree(mst); + + return err; +} + +/** + * ubifs_rebuild_filesystem - Rebuild filesystem. + * @c: UBIFS file-system description object + * + * Scanning nodes from UBI volume and rebuild filesystem. Any inconsistent + * problems or corrupted data will be fixed. + */ +int ubifs_rebuild_filesystem(struct ubifs_info *c) +{ + int err = 0; + struct scanned_info si; + + si.valid_inos = si.del_inos = si.valid_dents = si.del_dents = RB_ROOT; + log_out(c, "Start rebuilding filesystem (Notice: dropping data/recovering deleted data can't be awared)"); + FSCK(c)->mode = REBUILD_MODE; + + err = init_rebuild_info(c); + if (err) { + exit_code |= FSCK_ERROR; + return err; + } + + /* Step 1: Scan valid/deleted nodes from volume. */ + log_out(c, "Scan nodes"); + err = scan_nodes(c, &si); + if (err) { + exit_code |= FSCK_ERROR; + goto out; + } + + /* Step 2: Remove deleted nodes from valid node tree. */ + log_out(c, "Remove deleted nodes"); + remove_del_nodes(c, &si); + + /* Step 3: Add valid nodes into file. */ + log_out(c, "Add valid nodes into file"); + err = add_valid_nodes_into_file(c, &si); + if (err) { + exit_code |= FSCK_ERROR; + goto out; + } + + /* Step 4: Drop invalid files. */ + log_out(c, "Filter invalid files"); + filter_invalid_files(c); + + /* Step 5: Extract reachable directory entries. */ + log_out(c, "Extract reachable files"); + extract_dentry_tree(c); + + /* Step 6: Check & correct files' information. */ + log_out(c, "Check & correct file information"); + err = check_and_correct_files(c); + if (err) { + exit_code |= FSCK_ERROR; + goto out; + } + + /* + * Step 7: Record used LEBs. + * Step 8: Re-write data to clean corrupted data. + * Step 9: Build TNC. + */ + err = traverse_files_and_nodes(c); + if (err) { + exit_code |= FSCK_ERROR; + goto out; + } + + /* Step 10. Build LPT. */ + log_out(c, "Build LPT"); + err = build_lpt(c, calculate_lp, true); + if (err) { + exit_code |= FSCK_ERROR; + goto out; + } + + /* Step 11. Clean up log & orphan. */ + log_out(c, "Clean up log & orphan"); + err = clean_log(c); + if (err) { + exit_code |= FSCK_ERROR; + goto out; + } + err = ubifs_clear_orphans(c); + if (err) { + exit_code |= FSCK_ERROR; + goto out; + } + + /* Step 12. Write master node. */ + log_out(c, "Write master"); + err = write_master(c); + if (err) + exit_code |= FSCK_ERROR; + +out: + destroy_scanned_info(c, &si); + destroy_rebuild_info(c); + + return err; +} diff --git a/ubifs-utils/libubifs/README b/ubifs-utils/libubifs/README new file mode 100644 index 0000000..dd9322a --- /dev/null +++ b/ubifs-utils/libubifs/README @@ -0,0 +1,30 @@ +UBIFS Library (Imported from linux kernel 6.13-rc7 aa22f4da2a46) + +* ubifs.h is a selection of definitions from fs/ubifs/ubifs.h from the linux kernel. +* key.h is copied from fs/ubifs/key.h from the linux kernel. +* ubifs-media.h is copied from fs/ubifs/ubifs-media.h from the linux kernel. +* find.c is copied from fs/ubifs/find.c from the linux kernel. +* scan.c is copied from fs/ubifs/scan.c from the linux kernel. +* gc.c is copied from fs/ubifs/gc.c from the linux kernel. +* log.c is copied from fs/ubifs/log.c from the linux kernel, and amended. +* tnc_commit.c is copied from fs/ubifs/tnc_commit.c from the linux kernel, and amended. +* master.c is copied from fs/ubifs/master.c from the linux kernel, and amended. +* recovery.c is copied from fs/ubifs/recovery.c from the linux kernel, and amended. +* lpt.c is a selection of functions copied from fs/ubifs/lpt.c from the linux kernel, and amended. +* auth.c is a selection of functions copied from fs/ubifs/auth.c from the linux kernel, and amended. +* budget.c is a selection of functions copied from fs/ubifs/budget.c from the linux kernel, and amended. +* commit.c is a selection of functions copied from fs/ubifs/commit.c from the linux kernel, and amended. +* debug.c is a selection of functions copied from fs/ubifs/debug.c from the linux kernel, and amended. +* debug.h is a selection of functions copied from fs/ubifs/debug.h from the linux kernel, and amended. +* io.c is a selection of functions copied from fs/ubifs/io.c from the linux kernel, and amended. +* lprops.c is a selection of functions copied from fs/ubifs/lprops.c from the linux kernel, and amended. +* lpt_commit.c is a selection of functions copied from fs/ubifs/lpt_commit.c from the linux kernel, and amended. +* misc.h is a selection of functions copied from fs/ubifs/misc.h from the linux kernel, and amended. +* orphan.c is a selection of functions copied from fs/ubifs/orphan.c from the linux kernel, and amended. +* replay.c is a selection of functions copied from fs/ubifs/replay.c from the linux kernel, and amended. +* sb.c is a selection of functions copied from fs/ubifs/sb.c from the linux kernel, and amended. +* super.c is a selection of functions copied from fs/ubifs/super.c from the linux kernel, and amended. +* tnc.c is a selection of functions copied from fs/ubifs/tnc.c from the linux kernel, and amended. +* tnc_misc.c is a selection of functions copied from fs/ubifs/tnc_misc.c from the linux kernel, and amended. +* journal.c is a selection of functions copied from fs/ubifs/journal.c from the linux kernel, and amended. +* dir.c is a selection of functions copied from fs/ubifs/dir.c from the linux kernel, and amended. diff --git a/ubifs-utils/libubifs/auth.c b/ubifs-utils/libubifs/auth.c new file mode 100644 index 0000000..fab1dba --- /dev/null +++ b/ubifs-utils/libubifs/auth.c @@ -0,0 +1,175 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * This file is part of UBIFS. + * + * Copyright (C) 2018 Pengutronix, Sascha Hauer <s.hauer@pengutronix.de> + */ + +/* + * This file implements various helper functions for UBIFS authentication support + */ + +#include "linux_err.h" +#include "ubifs.h" +#include "sign.h" +#include "defs.h" + +int ubifs_shash_init(const struct ubifs_info *c, + __unused struct shash_desc *desc) +{ + if (ubifs_authenticated(c)) + return hash_digest_init(); + else + return 0; +} + +int ubifs_shash_update(const struct ubifs_info *c, + __unused struct shash_desc *desc, + const void *buf, unsigned int len) +{ + int err = 0; + + if (ubifs_authenticated(c)) { + err = hash_digest_update(buf, len); + if (err < 0) + return err; + } + + return 0; +} + +int ubifs_shash_final(const struct ubifs_info *c, + __unused struct shash_desc *desc, u8 *out) +{ + return ubifs_authenticated(c) ? hash_digest_final(out) : 0; +} + +struct shash_desc *ubifs_hash_get_desc(const struct ubifs_info *c) +{ + int err; + + err = ubifs_shash_init(c, NULL); + if (err) + return ERR_PTR(err); + + return NULL; +} + +/** + * ubifs_node_calc_hash - calculate the hash of a UBIFS node + * @c: UBIFS file-system description object + * @node: the node to calculate a hash for + * @hash: the returned hash + * + * Returns 0 for success or a negative error code otherwise. + */ +int __ubifs_node_calc_hash(__unused const struct ubifs_info *c, + const void *node, u8 *hash) +{ + const struct ubifs_ch *ch = node; + + return hash_digest(node, le32_to_cpu(ch->len), hash); +} + +/** + * ubifs_master_node_calc_hash - calculate the hash of a UBIFS master node + * @node: the node to calculate a hash for + * @hash: the returned hash + */ +int ubifs_master_node_calc_hash(const struct ubifs_info *c, const void *node, + uint8_t *hash) +{ + if (!ubifs_authenticated(c)) + return 0; + + return hash_digest(node + sizeof(struct ubifs_ch), + UBIFS_MST_NODE_SZ - sizeof(struct ubifs_ch), hash); +} + +int ubifs_sign_superblock_node(struct ubifs_info *c, void *node) +{ + int err, len; + struct ubifs_sig_node *sig = node + UBIFS_SB_NODE_SZ; + + if (!ubifs_authenticated(c)) + return 0; + + err = hash_sign_node(c->auth_key_filename, c->auth_cert_filename, node, + &len, sig + 1); + if (err) + return err; + + sig->type = UBIFS_SIGNATURE_TYPE_PKCS7; + sig->len = cpu_to_le32(len); + sig->ch.node_type = UBIFS_SIG_NODE; + + return 0; +} + +/** + * ubifs_bad_hash - Report hash mismatches + * @c: UBIFS file-system description object + * @node: the node + * @hash: the expected hash + * @lnum: the LEB @node was read from + * @offs: offset in LEB @node was read from + * + * This function reports a hash mismatch when a node has a different hash than + * expected. + */ +void ubifs_bad_hash(const struct ubifs_info *c, const void *node, const u8 *hash, + int lnum, int offs) +{ + int len = min(c->hash_len, 20); + int cropped = len != c->hash_len; + const char *cont = cropped ? "..." : ""; + + u8 calc[UBIFS_HASH_ARR_SZ]; + + __ubifs_node_calc_hash(c, node, calc); + + ubifs_err(c, "hash mismatch on node at LEB %d:%d", lnum, offs); + ubifs_err(c, "hash expected: %*ph%s", len, hash, cont); + ubifs_err(c, "hash calculated: %*ph%s", len, calc, cont); +} + +/** + * ubifs_init_authentication - initialize UBIFS authentication support + * @c: UBIFS file-system description object + * + * This function returns 0 for success or a negative error code otherwise. + */ +int ubifs_init_authentication(struct ubifs_info *c) +{ + int err, hash_len, hash_algo; + + if (!c->auth_key_filename && !c->auth_cert_filename && !c->hash_algo_name) + return 0; + + if (!c->auth_key_filename) { + ubifs_err(c, "authentication key not given (--auth-key)"); + return -EINVAL; + } + + if (!c->hash_algo_name) { + ubifs_err(c, "Hash algorithm not given (--hash-algo)"); + return -EINVAL; + } + + err = init_authentication(c->hash_algo_name, &hash_len, &hash_algo); + if (err) { + ubifs_err(c, "Init authentication failed"); + return err; + } + + c->hash_len = hash_len; + c->hash_algo = hash_algo; + c->authenticated = 1; + + return 0; +} + +void __ubifs_exit_authentication(__unused struct ubifs_info *c) +{ + exit_authentication(); +} diff --git a/ubifs-utils/libubifs/budget.c b/ubifs-utils/libubifs/budget.c new file mode 100644 index 0000000..5550c9a --- /dev/null +++ b/ubifs-utils/libubifs/budget.c @@ -0,0 +1,595 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * This file is part of UBIFS. + * + * Copyright (C) 2006-2008 Nokia Corporation. + * + * Authors: Adrian Hunter + * Artem Bityutskiy (Битюцкий Артём) + */ + +/* + * This file implements the budgeting sub-system which is responsible for UBIFS + * space management. + * + * Factors such as compression, wasted space at the ends of LEBs, space in other + * journal heads, the effect of updates on the index, and so on, make it + * impossible to accurately predict the amount of space needed. Consequently + * approximations are used. + */ + +#include "bitops.h" +#include "ubifs.h" +#include "defs.h" +#include "debug.h" +#include "misc.h" + +/* + * When pessimistic budget calculations say that there is no enough space, + * UBIFS starts writing back dirty inodes and pages, doing garbage collection, + * or committing. The below constant defines maximum number of times UBIFS + * repeats the operations. + */ +#define MAX_MKSPC_RETRIES 3 + +/** + * run_gc - run garbage collector. + * @c: UBIFS file-system description object + * + * This function runs garbage collector to make some more free space. Returns + * zero if a free LEB has been produced, %-EAGAIN if commit is required, and a + * negative error code in case of failure. + */ +static int run_gc(struct ubifs_info *c) +{ + int lnum; + + /* Make some free space by garbage-collecting dirty space */ + down_read(&c->commit_sem); + lnum = ubifs_garbage_collect(c, 1); + up_read(&c->commit_sem); + if (lnum < 0) + return lnum; + + /* GC freed one LEB, return it to lprops */ + dbg_budg("GC freed LEB %d", lnum); + return ubifs_return_leb(c, lnum); +} + +/** + * make_free_space - make more free space on the file-system. + * @c: UBIFS file-system description object + * + * This function is called when an operation cannot be budgeted because there + * is supposedly no free space. But in most cases there is some free space: + * o budgeting is pessimistic, so it always budgets more than it is actually + * needed, so shrinking the liability is one way to make free space - the + * cached data will take less space then it was budgeted for; + * o GC may turn some dark space into free space (budgeting treats dark space + * as not available); + * o commit may free some LEB, i.e., turn freeable LEBs into free LEBs. + * + * So this function tries to do the above. Returns %-EAGAIN if some free space + * was presumably made and the caller has to re-try budgeting the operation. + * Returns %-ENOSPC if it couldn't do more free space, and other negative error + * codes on failures. + */ +static int make_free_space(struct ubifs_info *c) +{ + int err, retries = 0; + + do { + dbg_budg("Run GC"); + err = run_gc(c); + if (!err) + return -EAGAIN; + + if (err != -EAGAIN && err != -ENOSPC) + /* Some real error happened */ + return err; + + dbg_budg("Run commit (retries %d)", retries); + err = ubifs_run_commit(c); + if (err) + return err; + } while (retries++ < MAX_MKSPC_RETRIES); + + return -ENOSPC; +} + +/** + * ubifs_calc_min_idx_lebs - calculate amount of LEBs for the index. + * @c: UBIFS file-system description object + * + * This function calculates and returns the number of LEBs which should be kept + * for index usage. + */ +int ubifs_calc_min_idx_lebs(struct ubifs_info *c) +{ + int idx_lebs; + long long idx_size; + + idx_size = c->bi.old_idx_sz + c->bi.idx_growth + c->bi.uncommitted_idx; + /* And make sure we have thrice the index size of space reserved */ + idx_size += idx_size << 1; + /* + * We do not maintain 'old_idx_size' as 'old_idx_lebs'/'old_idx_bytes' + * pair, nor similarly the two variables for the new index size, so we + * have to do this costly 64-bit division on fast-path. + */ + idx_lebs = div_u64(idx_size + c->idx_leb_size - 1, c->idx_leb_size); + /* + * The index head is not available for the in-the-gaps method, so add an + * extra LEB to compensate. + */ + idx_lebs += 1; + if (idx_lebs < MIN_INDEX_LEBS) + idx_lebs = MIN_INDEX_LEBS; + return idx_lebs; +} + +/** + * ubifs_calc_available - calculate available FS space. + * @c: UBIFS file-system description object + * @min_idx_lebs: minimum number of LEBs reserved for the index + * + * This function calculates and returns amount of FS space available for use. + */ +long long ubifs_calc_available(const struct ubifs_info *c, int min_idx_lebs) +{ + int subtract_lebs; + long long available; + + available = c->main_bytes - c->lst.total_used; + + /* + * Now 'available' contains theoretically available flash space + * assuming there is no index, so we have to subtract the space which + * is reserved for the index. + */ + subtract_lebs = min_idx_lebs; + + /* Take into account that GC reserves one LEB for its own needs */ + subtract_lebs += 1; + + /* + * Since different write types go to different heads, we should + * reserve one leb for each head. + */ + subtract_lebs += c->jhead_cnt; + + /* We also reserve one LEB for deletions, which bypass budgeting */ + subtract_lebs += 1; + + available -= (long long)subtract_lebs * c->leb_size; + + /* Subtract the dead space which is not available for use */ + available -= c->lst.total_dead; + + /* + * Subtract dark space, which might or might not be usable - it depends + * on the data which we have on the media and which will be written. If + * this is a lot of uncompressed or not-compressible data, the dark + * space cannot be used. + */ + available -= c->lst.total_dark; + + /* + * However, there is more dark space. The index may be bigger than + * @min_idx_lebs. Those extra LEBs are assumed to be available, but + * their dark space is not included in total_dark, so it is subtracted + * here. + */ + if (c->lst.idx_lebs > min_idx_lebs) { + subtract_lebs = c->lst.idx_lebs - min_idx_lebs; + available -= subtract_lebs * c->dark_wm; + } + + /* The calculations are rough and may end up with a negative number */ + return available > 0 ? available : 0; +} + +/** + * can_use_rp - check whether the user is allowed to use reserved pool. + * @c: UBIFS file-system description object + * + * UBIFS has so-called "reserved pool" which is flash space reserved + * for the superuser and for uses whose UID/GID is recorded in UBIFS superblock. + * This function checks whether current user is allowed to use reserved pool. + * Returns %1 current user is allowed to use reserved pool and %0 otherwise. + */ +static int can_use_rp(__unused struct ubifs_info *c) +{ + /* Fsck can always use reserved pool. */ + return c->program_type == FSCK_PROGRAM_TYPE; +} + +/** + * do_budget_space - reserve flash space for index and data growth. + * @c: UBIFS file-system description object + * + * This function makes sure UBIFS has enough free LEBs for index growth and + * data. + * + * When budgeting index space, UBIFS reserves thrice as many LEBs as the index + * would take if it was consolidated and written to the flash. This guarantees + * that the "in-the-gaps" commit method always succeeds and UBIFS will always + * be able to commit dirty index. So this function basically adds amount of + * budgeted index space to the size of the current index, multiplies this by 3, + * and makes sure this does not exceed the amount of free LEBs. + * + * Notes about @c->bi.min_idx_lebs and @c->lst.idx_lebs variables: + * o @c->lst.idx_lebs is the number of LEBs the index currently uses. It might + * be large, because UBIFS does not do any index consolidation as long as + * there is free space. IOW, the index may take a lot of LEBs, but the LEBs + * will contain a lot of dirt. + * o @c->bi.min_idx_lebs is the number of LEBS the index presumably takes. IOW, + * the index may be consolidated to take up to @c->bi.min_idx_lebs LEBs. + * + * This function returns zero in case of success, and %-ENOSPC in case of + * failure. + */ +static int do_budget_space(struct ubifs_info *c) +{ + long long outstanding, available; + int lebs, rsvd_idx_lebs, min_idx_lebs; + + /* First budget index space */ + min_idx_lebs = ubifs_calc_min_idx_lebs(c); + + /* Now 'min_idx_lebs' contains number of LEBs to reserve */ + if (min_idx_lebs > c->lst.idx_lebs) + rsvd_idx_lebs = min_idx_lebs - c->lst.idx_lebs; + else + rsvd_idx_lebs = 0; + + /* + * The number of LEBs that are available to be used by the index is: + * + * @c->lst.empty_lebs + @c->freeable_cnt + @c->idx_gc_cnt - + * @c->lst.taken_empty_lebs + * + * @c->lst.empty_lebs are available because they are empty. + * @c->freeable_cnt are available because they contain only free and + * dirty space, @c->idx_gc_cnt are available because they are index + * LEBs that have been garbage collected and are awaiting the commit + * before they can be used. And the in-the-gaps method will grab these + * if it needs them. @c->lst.taken_empty_lebs are empty LEBs that have + * already been allocated for some purpose. + * + * Note, @c->idx_gc_cnt is included to both @c->lst.empty_lebs (because + * these LEBs are empty) and to @c->lst.taken_empty_lebs (because they + * are taken until after the commit). + * + * Note, @c->lst.taken_empty_lebs may temporarily be higher by one + * because of the way we serialize LEB allocations and budgeting. See a + * comment in 'ubifs_find_free_space()'. + */ + lebs = c->lst.empty_lebs + c->freeable_cnt + c->idx_gc_cnt - + c->lst.taken_empty_lebs; + if (unlikely(rsvd_idx_lebs > lebs)) { + dbg_budg("out of indexing space: min_idx_lebs %d (old %d), rsvd_idx_lebs %d", + min_idx_lebs, c->bi.min_idx_lebs, rsvd_idx_lebs); + return -ENOSPC; + } + + available = ubifs_calc_available(c, min_idx_lebs); + outstanding = c->bi.data_growth + c->bi.dd_growth; + + if (unlikely(available < outstanding)) { + dbg_budg("out of data space: available %lld, outstanding %lld", + available, outstanding); + return -ENOSPC; + } + + if (available - outstanding <= c->rp_size && !can_use_rp(c)) + return -ENOSPC; + + c->bi.min_idx_lebs = min_idx_lebs; + return 0; +} + +/** + * calc_idx_growth - calculate approximate index growth from budgeting request. + * @c: UBIFS file-system description object + * @req: budgeting request + * + * For now we assume each new node adds one znode. But this is rather poor + * approximation, though. + */ +static int calc_idx_growth(const struct ubifs_info *c, + const struct ubifs_budget_req *req) +{ + int znodes; + + znodes = req->new_ino + (req->new_page << UBIFS_BLOCKS_PER_PAGE_SHIFT) + + req->new_dent; + return znodes * c->max_idx_node_sz; +} + +/** + * calc_data_growth - calculate approximate amount of new data from budgeting + * request. + * @c: UBIFS file-system description object + * @req: budgeting request + */ +static int calc_data_growth(const struct ubifs_info *c, + const struct ubifs_budget_req *req) +{ + int data_growth; + + data_growth = req->new_ino ? c->bi.inode_budget : 0; + if (req->new_page) + data_growth += c->bi.page_budget; + if (req->new_dent) + data_growth += c->bi.dent_budget; + data_growth += req->new_ino_d; + return data_growth; +} + +/** + * calc_dd_growth - calculate approximate amount of data which makes other data + * dirty from budgeting request. + * @c: UBIFS file-system description object + * @req: budgeting request + */ +static int calc_dd_growth(const struct ubifs_info *c, + const struct ubifs_budget_req *req) +{ + int dd_growth; + + dd_growth = req->dirtied_page ? c->bi.page_budget : 0; + + if (req->dirtied_ino) + dd_growth += c->bi.inode_budget * req->dirtied_ino; + if (req->mod_dent) + dd_growth += c->bi.dent_budget; + dd_growth += req->dirtied_ino_d; + return dd_growth; +} + +/** + * ubifs_budget_space - ensure there is enough space to complete an operation. + * @c: UBIFS file-system description object + * @req: budget request + * + * This function allocates budget for an operation. It uses pessimistic + * approximation of how much flash space the operation needs. The goal of this + * function is to make sure UBIFS always has flash space to flush all dirty + * pages, dirty inodes, and dirty znodes (liability). This function may force + * commit, garbage-collection or write-back. Returns zero in case of success, + * %-ENOSPC if there is no free space and other negative error codes in case of + * failures. + */ +int ubifs_budget_space(struct ubifs_info *c, struct ubifs_budget_req *req) +{ + int err, idx_growth, data_growth, dd_growth, retried = 0; + + ubifs_assert(c, req->new_page <= 1); + ubifs_assert(c, req->dirtied_page <= 1); + ubifs_assert(c, req->new_dent <= 1); + ubifs_assert(c, req->mod_dent <= 1); + ubifs_assert(c, req->new_ino <= 1); + ubifs_assert(c, req->new_ino_d <= UBIFS_MAX_INO_DATA); + ubifs_assert(c, req->dirtied_ino <= 4); + ubifs_assert(c, req->dirtied_ino_d <= UBIFS_MAX_INO_DATA * 4); + ubifs_assert(c, !(req->new_ino_d & 7)); + ubifs_assert(c, !(req->dirtied_ino_d & 7)); + + data_growth = calc_data_growth(c, req); + dd_growth = calc_dd_growth(c, req); + if (!data_growth && !dd_growth) + return 0; + idx_growth = calc_idx_growth(c, req); + +again: + spin_lock(&c->space_lock); + ubifs_assert(c, c->bi.idx_growth >= 0); + ubifs_assert(c, c->bi.data_growth >= 0); + ubifs_assert(c, c->bi.dd_growth >= 0); + + if (unlikely(c->bi.nospace) && (c->bi.nospace_rp || !can_use_rp(c))) { + dbg_budg("no space"); + spin_unlock(&c->space_lock); + return -ENOSPC; + } + + c->bi.idx_growth += idx_growth; + c->bi.data_growth += data_growth; + c->bi.dd_growth += dd_growth; + + err = do_budget_space(c); + if (likely(!err)) { + req->idx_growth = idx_growth; + req->data_growth = data_growth; + req->dd_growth = dd_growth; + spin_unlock(&c->space_lock); + return 0; + } + + /* Restore the old values */ + c->bi.idx_growth -= idx_growth; + c->bi.data_growth -= data_growth; + c->bi.dd_growth -= dd_growth; + spin_unlock(&c->space_lock); + + if (req->fast) { + dbg_budg("no space for fast budgeting"); + return err; + } + + err = make_free_space(c); + cond_resched(); + if (err == -EAGAIN) { + dbg_budg("try again"); + goto again; + } else if (err == -ENOSPC) { + if (!retried) { + retried = 1; + dbg_budg("-ENOSPC, but anyway try once again"); + goto again; + } + dbg_budg("FS is full, -ENOSPC"); + c->bi.nospace = 1; + if (can_use_rp(c) || c->rp_size == 0) + c->bi.nospace_rp = 1; + smp_wmb(); + } else + ubifs_err(c, "cannot budget space, error %d", err); + return err; +} + +/** + * ubifs_release_budget - release budgeted free space. + * @c: UBIFS file-system description object + * @req: budget request + * + * This function releases the space budgeted by 'ubifs_budget_space()'. Note, + * since the index changes (which were budgeted for in @req->idx_growth) will + * only be written to the media on commit, this function moves the index budget + * from @c->bi.idx_growth to @c->bi.uncommitted_idx. The latter will be zeroed + * by the commit operation. + */ +void ubifs_release_budget(struct ubifs_info *c, struct ubifs_budget_req *req) +{ + ubifs_assert(c, req->new_page <= 1); + ubifs_assert(c, req->dirtied_page <= 1); + ubifs_assert(c, req->new_dent <= 1); + ubifs_assert(c, req->mod_dent <= 1); + ubifs_assert(c, req->new_ino <= 1); + ubifs_assert(c, req->new_ino_d <= UBIFS_MAX_INO_DATA); + ubifs_assert(c, req->dirtied_ino <= 4); + ubifs_assert(c, req->dirtied_ino_d <= UBIFS_MAX_INO_DATA * 4); + ubifs_assert(c, !(req->new_ino_d & 7)); + ubifs_assert(c, !(req->dirtied_ino_d & 7)); + if (!req->recalculate) { + ubifs_assert(c, req->idx_growth >= 0); + ubifs_assert(c, req->data_growth >= 0); + ubifs_assert(c, req->dd_growth >= 0); + } + + if (req->recalculate) { + req->data_growth = calc_data_growth(c, req); + req->dd_growth = calc_dd_growth(c, req); + req->idx_growth = calc_idx_growth(c, req); + } + + if (!req->data_growth && !req->dd_growth) + return; + + c->bi.nospace = c->bi.nospace_rp = 0; + smp_wmb(); + + spin_lock(&c->space_lock); + c->bi.idx_growth -= req->idx_growth; + c->bi.uncommitted_idx += req->idx_growth; + c->bi.data_growth -= req->data_growth; + c->bi.dd_growth -= req->dd_growth; + c->bi.min_idx_lebs = ubifs_calc_min_idx_lebs(c); + + ubifs_assert(c, c->bi.idx_growth >= 0); + ubifs_assert(c, c->bi.data_growth >= 0); + ubifs_assert(c, c->bi.dd_growth >= 0); + ubifs_assert(c, c->bi.min_idx_lebs < c->main_lebs); + ubifs_assert(c, !(c->bi.idx_growth & 7)); + ubifs_assert(c, !(c->bi.data_growth & 7)); + ubifs_assert(c, !(c->bi.dd_growth & 7)); + spin_unlock(&c->space_lock); +} + +/** + * ubifs_reported_space - calculate reported free space. + * @c: the UBIFS file-system description object + * @free: amount of free space + * + * This function calculates amount of free space which will be reported to + * user-space. User-space application tend to expect that if the file-system + * (e.g., via the 'statfs()' call) reports that it has N bytes available, they + * are able to write a file of size N. UBIFS attaches node headers to each data + * node and it has to write indexing nodes as well. This introduces additional + * overhead, and UBIFS has to report slightly less free space to meet the above + * expectations. + * + * This function assumes free space is made up of uncompressed data nodes and + * full index nodes (one per data node, tripled because we always allow enough + * space to write the index thrice). + * + * Note, the calculation is pessimistic, which means that most of the time + * UBIFS reports less space than it actually has. + */ +long long ubifs_reported_space(const struct ubifs_info *c, long long free) +{ + int divisor, factor, f; + + /* + * Reported space size is @free * X, where X is UBIFS block size + * divided by UBIFS block size + all overhead one data block + * introduces. The overhead is the node header + indexing overhead. + * + * Indexing overhead calculations are based on the following formula: + * I = N/(f - 1) + 1, where I - number of indexing nodes, N - number + * of data nodes, f - fanout. Because effective UBIFS fanout is twice + * as less than maximum fanout, we assume that each data node + * introduces 3 * @c->max_idx_node_sz / (@c->fanout/2 - 1) bytes. + * Note, the multiplier 3 is because UBIFS reserves thrice as more space + * for the index. + */ + f = c->fanout > 3 ? c->fanout >> 1 : 2; + factor = UBIFS_BLOCK_SIZE; + divisor = UBIFS_MAX_DATA_NODE_SZ; + divisor += (c->max_idx_node_sz * 3) / (f - 1); + free *= factor; + return div_u64(free, divisor); +} + +/** + * ubifs_get_free_space_nolock - return amount of free space. + * @c: UBIFS file-system description object + * + * This function calculates amount of free space to report to user-space. + * + * Because UBIFS may introduce substantial overhead (the index, node headers, + * alignment, wastage at the end of LEBs, etc), it cannot report real amount of + * free flash space it has (well, because not all dirty space is reclaimable, + * UBIFS does not actually know the real amount). If UBIFS did so, it would + * bread user expectations about what free space is. Users seem to accustomed + * to assume that if the file-system reports N bytes of free space, they would + * be able to fit a file of N bytes to the FS. This almost works for + * traditional file-systems, because they have way less overhead than UBIFS. + * So, to keep users happy, UBIFS tries to take the overhead into account. + */ +long long ubifs_get_free_space_nolock(struct ubifs_info *c) +{ + int rsvd_idx_lebs, lebs; + long long available, outstanding, free; + + ubifs_assert(c, c->bi.min_idx_lebs == ubifs_calc_min_idx_lebs(c)); + outstanding = c->bi.data_growth + c->bi.dd_growth; + available = ubifs_calc_available(c, c->bi.min_idx_lebs); + + /* + * When reporting free space to user-space, UBIFS guarantees that it is + * possible to write a file of free space size. This means that for + * empty LEBs we may use more precise calculations than + * 'ubifs_calc_available()' is using. Namely, we know that in empty + * LEBs we would waste only @c->leb_overhead bytes, not @c->dark_wm. + * Thus, amend the available space. + * + * Note, the calculations below are similar to what we have in + * 'do_budget_space()', so refer there for comments. + */ + if (c->bi.min_idx_lebs > c->lst.idx_lebs) + rsvd_idx_lebs = c->bi.min_idx_lebs - c->lst.idx_lebs; + else + rsvd_idx_lebs = 0; + lebs = c->lst.empty_lebs + c->freeable_cnt + c->idx_gc_cnt - + c->lst.taken_empty_lebs; + lebs -= rsvd_idx_lebs; + available += lebs * (c->dark_wm - c->leb_overhead); + + if (available > outstanding) + free = ubifs_reported_space(c, available - outstanding); + else + free = 0; + return free; +} diff --git a/ubifs-utils/libubifs/commit.c b/ubifs-utils/libubifs/commit.c new file mode 100644 index 0000000..f3b6113 --- /dev/null +++ b/ubifs-utils/libubifs/commit.c @@ -0,0 +1,383 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * This file is part of UBIFS. + * + * Copyright (C) 2006-2008 Nokia Corporation. + * + * Authors: Adrian Hunter + * Artem Bityutskiy (Битюцкий Артём) + */ + +/* + * This file implements functions that manage the running of the commit process. + * Each affected module has its own functions to accomplish their part in the + * commit and those functions are called here. + * + * The commit is the process whereby all updates to the index and LEB properties + * are written out together and the journal becomes empty. This keeps the + * file system consistent - at all times the state can be recreated by reading + * the index and LEB properties and then replaying the journal. + * + * The commit is split into two parts named "commit start" and "commit end". + * During commit start, the commit process has exclusive access to the journal + * by holding the commit semaphore down for writing. As few I/O operations as + * possible are performed during commit start, instead the nodes that are to be + * written are merely identified. During commit end, the commit semaphore is no + * longer held and the journal is again in operation, allowing users to continue + * to use the file system while the bulk of the commit I/O is performed. The + * purpose of this two-step approach is to prevent the commit from causing any + * latency blips. Note that in any case, the commit does not prevent lookups + * (as permitted by the TNC mutex), or access to VFS data structures e.g. page + * cache. + */ + +#include "linux_err.h" +#include "bitops.h" +#include "kmem.h" +#include "ubifs.h" +#include "debug.h" +#include "defs.h" +#include "misc.h" + +/* + * nothing_to_commit - check if there is nothing to commit. + * @c: UBIFS file-system description object + * + * This is a helper function which checks if there is anything to commit. It is + * used as an optimization to avoid starting the commit if it is not really + * necessary. Indeed, the commit operation always assumes flash I/O (e.g., + * writing the commit start node to the log), and it is better to avoid doing + * this unnecessarily. E.g., 'ubifs_sync_fs()' runs the commit, but if there is + * nothing to commit, it is more optimal to avoid any flash I/O. + * + * This function has to be called with @c->commit_sem locked for writing - + * this function does not take LPT/TNC locks because the @c->commit_sem + * guarantees that we have exclusive access to the TNC and LPT data structures. + * + * This function returns %1 if there is nothing to commit and %0 otherwise. + */ +static int nothing_to_commit(struct ubifs_info *c) +{ + /* + * During mounting or remounting from R/O mode to R/W mode we may + * commit for various recovery-related reasons. + */ + if (c->mounting || c->remounting_rw) + return 0; + + /* + * If the root TNC node is dirty, we definitely have something to + * commit. + */ + if (c->zroot.znode && ubifs_zn_dirty(c->zroot.znode)) + return 0; + + /* + * Increasing @c->dirty_pn_cnt/@c->dirty_nn_cnt and marking + * nnodes/pnodes as dirty in run_gc() could race with following + * checking, which leads inconsistent states between @c->nroot + * and @c->dirty_pn_cnt/@c->dirty_nn_cnt, holding @c->lp_mutex + * to avoid that. + */ + mutex_lock(&c->lp_mutex); + /* + * Even though the TNC is clean, the LPT tree may have dirty nodes. For + * example, this may happen if the budgeting subsystem invoked GC to + * make some free space, and the GC found an LEB with only dirty and + * free space. In this case GC would just change the lprops of this + * LEB (by turning all space into free space) and unmap it. + */ + if (c->nroot && test_bit(DIRTY_CNODE, &c->nroot->flags)) { + mutex_unlock(&c->lp_mutex); + return 0; + } + + ubifs_assert(c, atomic_long_read(&c->dirty_zn_cnt) == 0); + ubifs_assert(c, c->dirty_pn_cnt == 0); + ubifs_assert(c, c->dirty_nn_cnt == 0); + mutex_unlock(&c->lp_mutex); + + return 1; +} + +/** + * do_commit - commit the journal. + * @c: UBIFS file-system description object + * + * This function implements UBIFS commit. It has to be called with commit lock + * locked. Returns zero in case of success and a negative error code in case of + * failure. + */ +static int do_commit(struct ubifs_info *c) +{ + int err, new_ltail_lnum, old_ltail_lnum, i; + struct ubifs_zbranch zroot; + struct ubifs_lp_stats lst; + + dbg_cmt("start"); + ubifs_assert(c, !c->ro_media && !c->ro_mount); + + if (c->ro_error) { + err = -EROFS; + goto out_up; + } + + if (nothing_to_commit(c)) { + up_write(&c->commit_sem); + err = 0; + goto out_cancel; + } + + /* Sync all write buffers (necessary for recovery) */ + for (i = 0; i < c->jhead_cnt; i++) { + err = ubifs_wbuf_sync(&c->jheads[i].wbuf); + if (err) + goto out_up; + } + + c->cmt_no += 1; + err = ubifs_gc_start_commit(c); + if (err) + goto out_up; + err = dbg_check_lprops(c); + if (err) + goto out_up; + err = ubifs_log_start_commit(c, &new_ltail_lnum); + if (err) + goto out_up; + err = ubifs_tnc_start_commit(c, &zroot); + if (err) + goto out_up; + err = ubifs_lpt_start_commit(c); + if (err) + goto out_up; + err = ubifs_orphan_start_commit(c); + if (err) + goto out_up; + + ubifs_get_lp_stats(c, &lst); + + up_write(&c->commit_sem); + + err = ubifs_tnc_end_commit(c); + if (err) + goto out; + err = ubifs_lpt_end_commit(c); + if (err) + goto out; + err = ubifs_orphan_end_commit(c); + if (err) + goto out; + err = dbg_check_old_index(c, &zroot); + if (err) + goto out; + + c->mst_node->cmt_no = cpu_to_le64(c->cmt_no); + c->mst_node->log_lnum = cpu_to_le32(new_ltail_lnum); + c->mst_node->root_lnum = cpu_to_le32(zroot.lnum); + c->mst_node->root_offs = cpu_to_le32(zroot.offs); + c->mst_node->root_len = cpu_to_le32(zroot.len); + c->mst_node->ihead_lnum = cpu_to_le32(c->ihead_lnum); + c->mst_node->ihead_offs = cpu_to_le32(c->ihead_offs); + c->mst_node->index_size = cpu_to_le64(c->bi.old_idx_sz); + c->mst_node->lpt_lnum = cpu_to_le32(c->lpt_lnum); + c->mst_node->lpt_offs = cpu_to_le32(c->lpt_offs); + c->mst_node->nhead_lnum = cpu_to_le32(c->nhead_lnum); + c->mst_node->nhead_offs = cpu_to_le32(c->nhead_offs); + c->mst_node->ltab_lnum = cpu_to_le32(c->ltab_lnum); + c->mst_node->ltab_offs = cpu_to_le32(c->ltab_offs); + c->mst_node->lsave_lnum = cpu_to_le32(c->lsave_lnum); + c->mst_node->lsave_offs = cpu_to_le32(c->lsave_offs); + c->mst_node->lscan_lnum = cpu_to_le32(c->lscan_lnum); + c->mst_node->empty_lebs = cpu_to_le32(lst.empty_lebs); + c->mst_node->idx_lebs = cpu_to_le32(lst.idx_lebs); + c->mst_node->total_free = cpu_to_le64(lst.total_free); + c->mst_node->total_dirty = cpu_to_le64(lst.total_dirty); + c->mst_node->total_used = cpu_to_le64(lst.total_used); + c->mst_node->total_dead = cpu_to_le64(lst.total_dead); + c->mst_node->total_dark = cpu_to_le64(lst.total_dark); + if (c->no_orphs) + c->mst_node->flags |= cpu_to_le32(UBIFS_MST_NO_ORPHS); + else + c->mst_node->flags &= ~cpu_to_le32(UBIFS_MST_NO_ORPHS); + + old_ltail_lnum = c->ltail_lnum; + err = ubifs_log_end_commit(c, new_ltail_lnum); + if (err) + goto out; + + err = ubifs_log_post_commit(c, old_ltail_lnum); + if (err) + goto out; + err = ubifs_gc_end_commit(c); + if (err) + goto out; + err = ubifs_lpt_post_commit(c); + if (err) + goto out; + +out_cancel: + spin_lock(&c->cs_lock); + c->cmt_state = COMMIT_RESTING; + dbg_cmt("commit end"); + spin_unlock(&c->cs_lock); + return 0; + +out_up: + up_write(&c->commit_sem); +out: + ubifs_err(c, "commit failed, error %d", err); + spin_lock(&c->cs_lock); + c->cmt_state = COMMIT_BROKEN; + spin_unlock(&c->cs_lock); + ubifs_ro_mode(c, err); + return err; +} + +/** + * ubifs_commit_required - set commit state to "required". + * @c: UBIFS file-system description object + * + * This function is called if a commit is required but cannot be done from the + * calling function, so it is just flagged instead. + */ +void ubifs_commit_required(struct ubifs_info *c) +{ + spin_lock(&c->cs_lock); + switch (c->cmt_state) { + case COMMIT_RESTING: + case COMMIT_BACKGROUND: + dbg_cmt("old: %s, new: %s", dbg_cstate(c->cmt_state), + dbg_cstate(COMMIT_REQUIRED)); + c->cmt_state = COMMIT_REQUIRED; + break; + case COMMIT_RUNNING_BACKGROUND: + dbg_cmt("old: %s, new: %s", dbg_cstate(c->cmt_state), + dbg_cstate(COMMIT_RUNNING_REQUIRED)); + c->cmt_state = COMMIT_RUNNING_REQUIRED; + break; + case COMMIT_REQUIRED: + case COMMIT_RUNNING_REQUIRED: + case COMMIT_BROKEN: + break; + } + spin_unlock(&c->cs_lock); +} + +/** + * ubifs_request_bg_commit - notify the background thread to do a commit. + * @c: UBIFS file-system description object + * + * This function is called if the journal is full enough to make a commit + * worthwhile, so background thread is kicked to start it. + */ +void ubifs_request_bg_commit(__unused struct ubifs_info *c) +{ +} + +/** + * wait_for_commit - wait for commit. + * @c: UBIFS file-system description object + * + * This function sleeps until the commit operation is no longer running. + */ +static int wait_for_commit(struct ubifs_info *c) +{ + /* + * All commit operations are executed in synchronization context, + * so it is impossible that more than one threads doing commit. + */ + ubifs_assert(c, 0); + return 0; +} + +/** + * ubifs_run_commit - run or wait for commit. + * @c: UBIFS file-system description object + * + * This function runs commit and returns zero in case of success and a negative + * error code in case of failure. + */ +int ubifs_run_commit(struct ubifs_info *c) +{ + int err = 0; + + spin_lock(&c->cs_lock); + if (c->cmt_state == COMMIT_BROKEN) { + err = -EROFS; + goto out; + } + + if (c->cmt_state == COMMIT_RUNNING_BACKGROUND) + /* + * We set the commit state to 'running required' to indicate + * that we want it to complete as quickly as possible. + */ + c->cmt_state = COMMIT_RUNNING_REQUIRED; + + if (c->cmt_state == COMMIT_RUNNING_REQUIRED) { + spin_unlock(&c->cs_lock); + return wait_for_commit(c); + } + spin_unlock(&c->cs_lock); + + /* Ok, the commit is indeed needed */ + + down_write(&c->commit_sem); + spin_lock(&c->cs_lock); + /* + * Since we unlocked 'c->cs_lock', the state may have changed, so + * re-check it. + */ + if (c->cmt_state == COMMIT_BROKEN) { + err = -EROFS; + goto out_cmt_unlock; + } + + if (c->cmt_state == COMMIT_RUNNING_BACKGROUND) + c->cmt_state = COMMIT_RUNNING_REQUIRED; + + if (c->cmt_state == COMMIT_RUNNING_REQUIRED) { + up_write(&c->commit_sem); + spin_unlock(&c->cs_lock); + return wait_for_commit(c); + } + c->cmt_state = COMMIT_RUNNING_REQUIRED; + spin_unlock(&c->cs_lock); + + err = do_commit(c); + return err; + +out_cmt_unlock: + up_write(&c->commit_sem); +out: + spin_unlock(&c->cs_lock); + return err; +} + +/** + * ubifs_gc_should_commit - determine if it is time for GC to run commit. + * @c: UBIFS file-system description object + * + * This function is called by garbage collection to determine if commit should + * be run. If commit state is @COMMIT_BACKGROUND, which means that the journal + * is full enough to start commit, this function returns true. It is not + * absolutely necessary to commit yet, but it feels like this should be better + * then to keep doing GC. This function returns %1 if GC has to initiate commit + * and %0 if not. + */ +int ubifs_gc_should_commit(struct ubifs_info *c) +{ + int ret = 0; + + spin_lock(&c->cs_lock); + if (c->cmt_state == COMMIT_BACKGROUND) { + dbg_cmt("commit required now"); + c->cmt_state = COMMIT_REQUIRED; + } else + dbg_cmt("commit not requested"); + if (c->cmt_state == COMMIT_REQUIRED) + ret = 1; + spin_unlock(&c->cs_lock); + return ret; +} diff --git a/ubifs-utils/libubifs/debug.c b/ubifs-utils/libubifs/debug.c new file mode 100644 index 0000000..836cbc7 --- /dev/null +++ b/ubifs-utils/libubifs/debug.c @@ -0,0 +1,1033 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * This file is part of UBIFS. + * + * Copyright (C) 2006-2008 Nokia Corporation + * + * Authors: Artem Bityutskiy (Битюцкий Артём) + * Adrian Hunter + */ + +/* + * This file implements most of the debugging stuff which is compiled in only + * when it is enabled. But some debugging check functions are implemented in + * corresponding subsystem, just because they are closely related and utilize + * various local functions of those subsystems. + */ + +#include <stdio.h> +#include <unistd.h> + +#include "linux_err.h" +#include "bitops.h" +#include "kmem.h" +#include "ubifs.h" +#include "defs.h" +#include "debug.h" +#include "key.h" +#include "misc.h" + +static DEFINE_SPINLOCK(dbg_lock); + +static const char *get_key_fmt(int fmt) +{ + switch (fmt) { + case UBIFS_SIMPLE_KEY_FMT: + return "simple"; + default: + return "unknown/invalid format"; + } +} + +static const char *get_key_hash(int hash) +{ + switch (hash) { + case UBIFS_KEY_HASH_R5: + return "R5"; + case UBIFS_KEY_HASH_TEST: + return "test"; + default: + return "unknown/invalid name hash"; + } +} + +const char *ubifs_get_key_name(int type) +{ + switch (type) { + case UBIFS_INO_KEY: + return "inode"; + case UBIFS_DENT_KEY: + return "direntry"; + case UBIFS_XENT_KEY: + return "xentry"; + case UBIFS_DATA_KEY: + return "data"; + case UBIFS_TRUN_KEY: + return "truncate"; + default: + return "unknown/invalid key"; + } +} + +const char *ubifs_get_type_name(int type) +{ + switch (type) { + case UBIFS_ITYPE_REG: + return "file"; + case UBIFS_ITYPE_DIR: + return "dir"; + case UBIFS_ITYPE_LNK: + return "symlink"; + case UBIFS_ITYPE_BLK: + return "blkdev"; + case UBIFS_ITYPE_CHR: + return "char dev"; + case UBIFS_ITYPE_FIFO: + return "fifo"; + case UBIFS_ITYPE_SOCK: + return "socket"; + default: + return "unknown/invalid type"; + } +} + +const char *dbg_snprintf_key(const struct ubifs_info *c, + const union ubifs_key *key, char *buffer, int len) +{ + char *p = buffer; + int type = key_type(c, key); + + if (c->key_fmt == UBIFS_SIMPLE_KEY_FMT) { + switch (type) { + case UBIFS_INO_KEY: + len -= snprintf(p, len, "(%lu, %s)", + (unsigned long)key_inum(c, key), + ubifs_get_key_name(type)); + break; + case UBIFS_DENT_KEY: + case UBIFS_XENT_KEY: + len -= snprintf(p, len, "(%lu, %s, %#08x)", + (unsigned long)key_inum(c, key), + ubifs_get_key_name(type), + key_hash(c, key)); + break; + case UBIFS_DATA_KEY: + len -= snprintf(p, len, "(%lu, %s, %u)", + (unsigned long)key_inum(c, key), + ubifs_get_key_name(type), + key_block(c, key)); + break; + case UBIFS_TRUN_KEY: + len -= snprintf(p, len, "(%lu, %s)", + (unsigned long)key_inum(c, key), + ubifs_get_key_name(type)); + break; + default: + len -= snprintf(p, len, "(bad key type: %#08x, %#08x)", + key->u32[0], key->u32[1]); + } + } else + len -= snprintf(p, len, "bad key format %d", c->key_fmt); + ubifs_assert(c, len > 0); + return p; +} + +const char *dbg_ntype(int type) +{ + switch (type) { + case UBIFS_PAD_NODE: + return "padding node"; + case UBIFS_SB_NODE: + return "superblock node"; + case UBIFS_MST_NODE: + return "master node"; + case UBIFS_REF_NODE: + return "reference node"; + case UBIFS_INO_NODE: + return "inode node"; + case UBIFS_DENT_NODE: + return "direntry node"; + case UBIFS_XENT_NODE: + return "xentry node"; + case UBIFS_DATA_NODE: + return "data node"; + case UBIFS_TRUN_NODE: + return "truncate node"; + case UBIFS_IDX_NODE: + return "indexing node"; + case UBIFS_CS_NODE: + return "commit start node"; + case UBIFS_ORPH_NODE: + return "orphan node"; + case UBIFS_AUTH_NODE: + return "auth node"; + default: + return "unknown node"; + } +} + +static const char *dbg_gtype(int type) +{ + switch (type) { + case UBIFS_NO_NODE_GROUP: + return "no node group"; + case UBIFS_IN_NODE_GROUP: + return "in node group"; + case UBIFS_LAST_OF_NODE_GROUP: + return "last of node group"; + default: + return "unknown"; + } +} + +const char *dbg_cstate(int cmt_state) +{ + switch (cmt_state) { + case COMMIT_RESTING: + return "commit resting"; + case COMMIT_BACKGROUND: + return "background commit requested"; + case COMMIT_REQUIRED: + return "commit required"; + case COMMIT_RUNNING_BACKGROUND: + return "BACKGROUND commit running"; + case COMMIT_RUNNING_REQUIRED: + return "commit running and required"; + case COMMIT_BROKEN: + return "broken commit"; + default: + return "unknown commit state"; + } +} + +const char *dbg_jhead(int jhead) +{ + switch (jhead) { + case GCHD: + return "0 (GC)"; + case BASEHD: + return "1 (base)"; + case DATAHD: + return "2 (data)"; + default: + return "unknown journal head"; + } +} + +static void dump_ch(const struct ubifs_ch *ch) +{ + pr_err("\tmagic %#x\n", le32_to_cpu(ch->magic)); + pr_err("\tcrc %#x\n", le32_to_cpu(ch->crc)); + pr_err("\tnode_type %d (%s)\n", ch->node_type, + dbg_ntype(ch->node_type)); + pr_err("\tgroup_type %d (%s)\n", ch->group_type, + dbg_gtype(ch->group_type)); + pr_err("\tsqnum %llu\n", + (unsigned long long)le64_to_cpu(ch->sqnum)); + pr_err("\tlen %u\n", le32_to_cpu(ch->len)); +} + +void ubifs_dump_node(const struct ubifs_info *c, const void *node, int node_len) +{ + int i, n, type, safe_len, max_node_len, min_node_len; + union ubifs_key key; + const struct ubifs_ch *ch = node; + char key_buf[DBG_KEY_BUF_LEN]; + + /* If the magic is incorrect, just hexdump the first bytes */ + if (le32_to_cpu(ch->magic) != UBIFS_NODE_MAGIC) { + pr_err("Not a node, first %zu bytes:", UBIFS_CH_SZ); + print_hex_dump("", DUMP_PREFIX_OFFSET, 32, 1, + (void *)node, UBIFS_CH_SZ, 1); + return; + } + + /* Skip dumping unknown type node */ + type = ch->node_type; + if (type < 0 || type >= UBIFS_NODE_TYPES_CNT) { + pr_err("node type %d was not recognized\n", type); + return; + } + + spin_lock(&dbg_lock); + dump_ch(node); + + if (c->ranges[type].max_len == 0) { + max_node_len = min_node_len = c->ranges[type].len; + } else { + max_node_len = c->ranges[type].max_len; + min_node_len = c->ranges[type].min_len; + } + safe_len = le32_to_cpu(ch->len); + safe_len = safe_len > 0 ? safe_len : 0; + safe_len = min3(safe_len, max_node_len, node_len); + if (safe_len < min_node_len) { + pr_err("node len(%d) is too short for %s, left %d bytes:\n", + safe_len, dbg_ntype(type), + safe_len > UBIFS_CH_SZ ? + safe_len - (int)UBIFS_CH_SZ : 0); + if (safe_len > UBIFS_CH_SZ) + print_hex_dump("", DUMP_PREFIX_OFFSET, 32, 1, + (void *)node + UBIFS_CH_SZ, + safe_len - UBIFS_CH_SZ, 0); + goto out_unlock; + } + if (safe_len != le32_to_cpu(ch->len)) + pr_err("\ttruncated node length %d\n", safe_len); + + switch (type) { + case UBIFS_PAD_NODE: + { + const struct ubifs_pad_node *pad = node; + + pr_err("\tpad_len %u\n", le32_to_cpu(pad->pad_len)); + break; + } + case UBIFS_SB_NODE: + { + const struct ubifs_sb_node *sup = node; + unsigned int sup_flags = le32_to_cpu(sup->flags); + + pr_err("\tkey_hash %d (%s)\n", + (int)sup->key_hash, get_key_hash(sup->key_hash)); + pr_err("\tkey_fmt %d (%s)\n", + (int)sup->key_fmt, get_key_fmt(sup->key_fmt)); + pr_err("\tflags %#x\n", sup_flags); + pr_err("\tbig_lpt %u\n", + !!(sup_flags & UBIFS_FLG_BIGLPT)); + pr_err("\tspace_fixup %u\n", + !!(sup_flags & UBIFS_FLG_SPACE_FIXUP)); + pr_err("\tmin_io_size %u\n", le32_to_cpu(sup->min_io_size)); + pr_err("\tleb_size %u\n", le32_to_cpu(sup->leb_size)); + pr_err("\tleb_cnt %u\n", le32_to_cpu(sup->leb_cnt)); + pr_err("\tmax_leb_cnt %u\n", le32_to_cpu(sup->max_leb_cnt)); + pr_err("\tmax_bud_bytes %llu\n", + (unsigned long long)le64_to_cpu(sup->max_bud_bytes)); + pr_err("\tlog_lebs %u\n", le32_to_cpu(sup->log_lebs)); + pr_err("\tlpt_lebs %u\n", le32_to_cpu(sup->lpt_lebs)); + pr_err("\torph_lebs %u\n", le32_to_cpu(sup->orph_lebs)); + pr_err("\tjhead_cnt %u\n", le32_to_cpu(sup->jhead_cnt)); + pr_err("\tfanout %u\n", le32_to_cpu(sup->fanout)); + pr_err("\tlsave_cnt %u\n", le32_to_cpu(sup->lsave_cnt)); + pr_err("\tdefault_compr %u\n", + (int)le16_to_cpu(sup->default_compr)); + pr_err("\trp_size %llu\n", + (unsigned long long)le64_to_cpu(sup->rp_size)); + pr_err("\trp_uid %u\n", le32_to_cpu(sup->rp_uid)); + pr_err("\trp_gid %u\n", le32_to_cpu(sup->rp_gid)); + pr_err("\tfmt_version %u\n", le32_to_cpu(sup->fmt_version)); + pr_err("\ttime_gran %u\n", le32_to_cpu(sup->time_gran)); + pr_err("\tUUID %pUB\n", sup->uuid); + break; + } + case UBIFS_MST_NODE: + { + const struct ubifs_mst_node *mst = node; + + pr_err("\thighest_inum %llu\n", + (unsigned long long)le64_to_cpu(mst->highest_inum)); + pr_err("\tcommit number %llu\n", + (unsigned long long)le64_to_cpu(mst->cmt_no)); + pr_err("\tflags %#x\n", le32_to_cpu(mst->flags)); + pr_err("\tlog_lnum %u\n", le32_to_cpu(mst->log_lnum)); + pr_err("\troot_lnum %u\n", le32_to_cpu(mst->root_lnum)); + pr_err("\troot_offs %u\n", le32_to_cpu(mst->root_offs)); + pr_err("\troot_len %u\n", le32_to_cpu(mst->root_len)); + pr_err("\tgc_lnum %u\n", le32_to_cpu(mst->gc_lnum)); + pr_err("\tihead_lnum %u\n", le32_to_cpu(mst->ihead_lnum)); + pr_err("\tihead_offs %u\n", le32_to_cpu(mst->ihead_offs)); + pr_err("\tindex_size %llu\n", + (unsigned long long)le64_to_cpu(mst->index_size)); + pr_err("\tlpt_lnum %u\n", le32_to_cpu(mst->lpt_lnum)); + pr_err("\tlpt_offs %u\n", le32_to_cpu(mst->lpt_offs)); + pr_err("\tnhead_lnum %u\n", le32_to_cpu(mst->nhead_lnum)); + pr_err("\tnhead_offs %u\n", le32_to_cpu(mst->nhead_offs)); + pr_err("\tltab_lnum %u\n", le32_to_cpu(mst->ltab_lnum)); + pr_err("\tltab_offs %u\n", le32_to_cpu(mst->ltab_offs)); + pr_err("\tlsave_lnum %u\n", le32_to_cpu(mst->lsave_lnum)); + pr_err("\tlsave_offs %u\n", le32_to_cpu(mst->lsave_offs)); + pr_err("\tlscan_lnum %u\n", le32_to_cpu(mst->lscan_lnum)); + pr_err("\tleb_cnt %u\n", le32_to_cpu(mst->leb_cnt)); + pr_err("\tempty_lebs %u\n", le32_to_cpu(mst->empty_lebs)); + pr_err("\tidx_lebs %u\n", le32_to_cpu(mst->idx_lebs)); + pr_err("\ttotal_free %llu\n", + (unsigned long long)le64_to_cpu(mst->total_free)); + pr_err("\ttotal_dirty %llu\n", + (unsigned long long)le64_to_cpu(mst->total_dirty)); + pr_err("\ttotal_used %llu\n", + (unsigned long long)le64_to_cpu(mst->total_used)); + pr_err("\ttotal_dead %llu\n", + (unsigned long long)le64_to_cpu(mst->total_dead)); + pr_err("\ttotal_dark %llu\n", + (unsigned long long)le64_to_cpu(mst->total_dark)); + break; + } + case UBIFS_REF_NODE: + { + const struct ubifs_ref_node *ref = node; + + pr_err("\tlnum %u\n", le32_to_cpu(ref->lnum)); + pr_err("\toffs %u\n", le32_to_cpu(ref->offs)); + pr_err("\tjhead %u\n", le32_to_cpu(ref->jhead)); + break; + } + case UBIFS_INO_NODE: + { + const struct ubifs_ino_node *ino = node; + + key_read(c, &ino->key, &key); + pr_err("\tkey %s\n", + dbg_snprintf_key(c, &key, key_buf, DBG_KEY_BUF_LEN)); + pr_err("\tcreat_sqnum %llu\n", + (unsigned long long)le64_to_cpu(ino->creat_sqnum)); + pr_err("\tsize %llu\n", + (unsigned long long)le64_to_cpu(ino->size)); + pr_err("\tnlink %u\n", le32_to_cpu(ino->nlink)); + pr_err("\tatime %lld.%u\n", + (long long)le64_to_cpu(ino->atime_sec), + le32_to_cpu(ino->atime_nsec)); + pr_err("\tmtime %lld.%u\n", + (long long)le64_to_cpu(ino->mtime_sec), + le32_to_cpu(ino->mtime_nsec)); + pr_err("\tctime %lld.%u\n", + (long long)le64_to_cpu(ino->ctime_sec), + le32_to_cpu(ino->ctime_nsec)); + pr_err("\tuid %u\n", le32_to_cpu(ino->uid)); + pr_err("\tgid %u\n", le32_to_cpu(ino->gid)); + pr_err("\tmode %u\n", le32_to_cpu(ino->mode)); + pr_err("\tflags %#x\n", le32_to_cpu(ino->flags)); + pr_err("\txattr_cnt %u\n", le32_to_cpu(ino->xattr_cnt)); + pr_err("\txattr_size %u\n", le32_to_cpu(ino->xattr_size)); + pr_err("\txattr_names %u\n", le32_to_cpu(ino->xattr_names)); + pr_err("\tcompr_type %#x\n", + (int)le16_to_cpu(ino->compr_type)); + pr_err("\tdata len %u\n", le32_to_cpu(ino->data_len)); + break; + } + case UBIFS_DENT_NODE: + case UBIFS_XENT_NODE: + { + const struct ubifs_dent_node *dent = node; + int nlen = le16_to_cpu(dent->nlen); + + key_read(c, &dent->key, &key); + pr_err("\tkey %s\n", + dbg_snprintf_key(c, &key, key_buf, DBG_KEY_BUF_LEN)); + pr_err("\tinum %llu\n", + (unsigned long long)le64_to_cpu(dent->inum)); + pr_err("\ttype %d\n", (int)dent->type); + pr_err("\tnlen %d\n", nlen); + pr_err("\tname "); + + if (nlen > UBIFS_MAX_NLEN || + nlen > safe_len - UBIFS_DENT_NODE_SZ) + pr_err("(bad name length, not printing, bad or corrupted node)"); + else { + for (i = 0; i < nlen && dent->name[i]; i++) + pr_cont("%c", isprint(dent->name[i]) ? + dent->name[i] : '?'); + } + pr_cont("\n"); + + break; + } + case UBIFS_DATA_NODE: + { + const struct ubifs_data_node *dn = node; + + key_read(c, &dn->key, &key); + pr_err("\tkey %s\n", + dbg_snprintf_key(c, &key, key_buf, DBG_KEY_BUF_LEN)); + pr_err("\tsize %u\n", le32_to_cpu(dn->size)); + pr_err("\tcompr_typ %d\n", + (int)le16_to_cpu(dn->compr_type)); + pr_err("\tdata size %u\n", + le32_to_cpu(ch->len) - (unsigned int)UBIFS_DATA_NODE_SZ); + pr_err("\tdata (length = %d):\n", + safe_len - (int)UBIFS_DATA_NODE_SZ); + print_hex_dump("\t", DUMP_PREFIX_OFFSET, 32, 1, + (void *)&dn->data, + safe_len - (int)UBIFS_DATA_NODE_SZ, 0); + break; + } + case UBIFS_TRUN_NODE: + { + const struct ubifs_trun_node *trun = node; + + pr_err("\tinum %u\n", le32_to_cpu(trun->inum)); + pr_err("\told_size %llu\n", + (unsigned long long)le64_to_cpu(trun->old_size)); + pr_err("\tnew_size %llu\n", + (unsigned long long)le64_to_cpu(trun->new_size)); + break; + } + case UBIFS_IDX_NODE: + { + const struct ubifs_idx_node *idx = node; + int max_child_cnt = (safe_len - UBIFS_IDX_NODE_SZ) / + (ubifs_idx_node_sz(c, 1) - + UBIFS_IDX_NODE_SZ); + + n = min_t(int, le16_to_cpu(idx->child_cnt), max_child_cnt); + pr_err("\tchild_cnt %d\n", (int)le16_to_cpu(idx->child_cnt)); + pr_err("\tlevel %d\n", (int)le16_to_cpu(idx->level)); + pr_err("\tBranches:\n"); + + for (i = 0; i < n && i < c->fanout; i++) { + const struct ubifs_branch *br; + + br = ubifs_idx_branch(c, idx, i); + key_read(c, &br->key, &key); + pr_err("\t%d: LEB %d:%d len %d key %s\n", + i, le32_to_cpu(br->lnum), le32_to_cpu(br->offs), + le32_to_cpu(br->len), + dbg_snprintf_key(c, &key, key_buf, + DBG_KEY_BUF_LEN)); + } + break; + } + case UBIFS_CS_NODE: + break; + case UBIFS_ORPH_NODE: + { + const struct ubifs_orph_node *orph = node; + + pr_err("\tcommit number %llu\n", + (unsigned long long) + le64_to_cpu(orph->cmt_no) & LLONG_MAX); + pr_err("\tlast node flag %llu\n", + (unsigned long long)(le64_to_cpu(orph->cmt_no)) >> 63); + n = (safe_len - UBIFS_ORPH_NODE_SZ) >> 3; + pr_err("\t%d orphan inode numbers:\n", n); + for (i = 0; i < n; i++) + pr_err("\t ino %llu\n", + (unsigned long long)le64_to_cpu(orph->inos[i])); + break; + } + case UBIFS_AUTH_NODE: + { + break; + } + default: + pr_err("node type %d was not recognized\n", type); + } + +out_unlock: + spin_unlock(&dbg_lock); +} + +void ubifs_dump_lstats(const struct ubifs_lp_stats *lst) +{ + spin_lock(&dbg_lock); + pr_err("(pid %d) Lprops statistics: empty_lebs %d, idx_lebs %d\n", + getpid(), lst->empty_lebs, lst->idx_lebs); + pr_err("\ttaken_empty_lebs %d, total_free %lld, total_dirty %lld\n", + lst->taken_empty_lebs, lst->total_free, lst->total_dirty); + pr_err("\ttotal_used %lld, total_dark %lld, total_dead %lld\n", + lst->total_used, lst->total_dark, lst->total_dead); + spin_unlock(&dbg_lock); +} + +void ubifs_dump_budg(struct ubifs_info *c, const struct ubifs_budg_info *bi) +{ + int i; + struct rb_node *rb; + struct ubifs_bud *bud; + struct ubifs_gced_idx_leb *idx_gc; + long long available, outstanding, free; + + spin_lock(&c->space_lock); + spin_lock(&dbg_lock); + pr_err("(pid %d) Budgeting info: data budget sum %lld, total budget sum %lld\n", + getpid(), bi->data_growth + bi->dd_growth, + bi->data_growth + bi->dd_growth + bi->idx_growth); + pr_err("\tbudg_data_growth %lld, budg_dd_growth %lld, budg_idx_growth %lld\n", + bi->data_growth, bi->dd_growth, bi->idx_growth); + pr_err("\tmin_idx_lebs %d, old_idx_sz %llu, uncommitted_idx %lld\n", + bi->min_idx_lebs, bi->old_idx_sz, bi->uncommitted_idx); + pr_err("\tpage_budget %d, inode_budget %d, dent_budget %d\n", + bi->page_budget, bi->inode_budget, bi->dent_budget); + pr_err("\tnospace %u, nospace_rp %u\n", bi->nospace, bi->nospace_rp); + pr_err("\tdark_wm %d, dead_wm %d, max_idx_node_sz %d\n", + c->dark_wm, c->dead_wm, c->max_idx_node_sz); + + if (bi != &c->bi) + /* + * If we are dumping saved budgeting data, do not print + * additional information which is about the current state, not + * the old one which corresponded to the saved budgeting data. + */ + goto out_unlock; + + pr_err("\tfreeable_cnt %d, calc_idx_sz %lld, idx_gc_cnt %d\n", + c->freeable_cnt, c->calc_idx_sz, c->idx_gc_cnt); + pr_err("\tdirty_pg_cnt %ld, dirty_zn_cnt %ld, clean_zn_cnt %ld\n", + atomic_long_read(&c->dirty_pg_cnt), + atomic_long_read(&c->dirty_zn_cnt), + atomic_long_read(&c->clean_zn_cnt)); + pr_err("\tgc_lnum %d, ihead_lnum %d\n", c->gc_lnum, c->ihead_lnum); + + /* If we are in R/O mode, journal heads do not exist */ + if (c->jheads) + for (i = 0; i < c->jhead_cnt; i++) + pr_err("\tjhead %s\t LEB %d\n", + dbg_jhead(c->jheads[i].wbuf.jhead), + c->jheads[i].wbuf.lnum); + for (rb = rb_first(&c->buds); rb; rb = rb_next(rb)) { + bud = rb_entry(rb, struct ubifs_bud, rb); + pr_err("\tbud LEB %d\n", bud->lnum); + } + list_for_each_entry(bud, &c->old_buds, list) + pr_err("\told bud LEB %d\n", bud->lnum); + list_for_each_entry(idx_gc, &c->idx_gc, list) + pr_err("\tGC'ed idx LEB %d unmap %d\n", + idx_gc->lnum, idx_gc->unmap); + pr_err("\tcommit state %d\n", c->cmt_state); + + /* Print budgeting predictions */ + available = ubifs_calc_available(c, c->bi.min_idx_lebs); + outstanding = c->bi.data_growth + c->bi.dd_growth; + free = ubifs_get_free_space_nolock(c); + pr_err("Budgeting predictions:\n"); + pr_err("\tavailable: %lld, outstanding %lld, free %lld\n", + available, outstanding, free); +out_unlock: + spin_unlock(&dbg_lock); + spin_unlock(&c->space_lock); +} + +void ubifs_dump_lprop(const struct ubifs_info *c, const struct ubifs_lprops *lp) +{ + int i, spc, dark = 0, dead = 0; + struct rb_node *rb; + struct ubifs_bud *bud; + + spc = lp->free + lp->dirty; + if (spc < c->dead_wm) + dead = spc; + else + dark = ubifs_calc_dark(c, spc); + + if (lp->flags & LPROPS_INDEX) + pr_err("LEB %-7d free %-8d dirty %-8d used %-8d free + dirty %-8d flags %#x (", + lp->lnum, lp->free, lp->dirty, c->leb_size - spc, spc, + lp->flags); + else + pr_err("LEB %-7d free %-8d dirty %-8d used %-8d free + dirty %-8d dark %-4d dead %-4d nodes fit %-3d flags %#-4x (", + lp->lnum, lp->free, lp->dirty, c->leb_size - spc, spc, + dark, dead, (int)(spc / UBIFS_MAX_NODE_SZ), lp->flags); + + if (lp->flags & LPROPS_TAKEN) { + if (lp->flags & LPROPS_INDEX) + pr_cont("index, taken"); + else + pr_cont("taken"); + } else { + const char *s; + + if (lp->flags & LPROPS_INDEX) { + switch (lp->flags & LPROPS_CAT_MASK) { + case LPROPS_DIRTY_IDX: + s = "dirty index"; + break; + case LPROPS_FRDI_IDX: + s = "freeable index"; + break; + default: + s = "index"; + } + } else { + switch (lp->flags & LPROPS_CAT_MASK) { + case LPROPS_UNCAT: + s = "not categorized"; + break; + case LPROPS_DIRTY: + s = "dirty"; + break; + case LPROPS_FREE: + s = "free"; + break; + case LPROPS_EMPTY: + s = "empty"; + break; + case LPROPS_FREEABLE: + s = "freeable"; + break; + default: + s = NULL; + break; + } + } + pr_cont("%s", s); + } + + for (rb = rb_first((struct rb_root *)&c->buds); rb; rb = rb_next(rb)) { + bud = rb_entry(rb, struct ubifs_bud, rb); + if (bud->lnum == lp->lnum) { + int head = 0; + for (i = 0; i < c->jhead_cnt; i++) { + /* + * Note, if we are in R/O mode or in the middle + * of mounting/re-mounting, the write-buffers do + * not exist. + */ + if (c->jheads && + lp->lnum == c->jheads[i].wbuf.lnum) { + pr_cont(", jhead %s", dbg_jhead(i)); + head = 1; + } + } + if (!head) + pr_cont(", bud of jhead %s", + dbg_jhead(bud->jhead)); + } + } + if (lp->lnum == c->gc_lnum) + pr_cont(", GC LEB"); + pr_cont(")\n"); +} + +void ubifs_dump_lprops(struct ubifs_info *c) +{ + int lnum, err; + struct ubifs_lprops lp; + struct ubifs_lp_stats lst; + + pr_err("(pid %d) start dumping LEB properties\n", getpid()); + ubifs_get_lp_stats(c, &lst); + ubifs_dump_lstats(&lst); + + for (lnum = c->main_first; lnum < c->leb_cnt; lnum++) { + err = ubifs_read_one_lp(c, lnum, &lp); + if (err) { + ubifs_err(c, "cannot read lprops for LEB %d", lnum); + continue; + } + + ubifs_dump_lprop(c, &lp); + } + pr_err("(pid %d) finish dumping LEB properties\n", getpid()); +} + +void ubifs_dump_lpt_info(struct ubifs_info *c) +{ + int i; + + spin_lock(&dbg_lock); + pr_err("(pid %d) dumping LPT information\n", getpid()); + pr_err("\tlpt_sz: %lld\n", c->lpt_sz); + pr_err("\tpnode_sz: %d\n", c->pnode_sz); + pr_err("\tnnode_sz: %d\n", c->nnode_sz); + pr_err("\tltab_sz: %d\n", c->ltab_sz); + pr_err("\tlsave_sz: %d\n", c->lsave_sz); + pr_err("\tbig_lpt: %u\n", c->big_lpt); + pr_err("\tlpt_hght: %d\n", c->lpt_hght); + pr_err("\tpnode_cnt: %d\n", c->pnode_cnt); + pr_err("\tnnode_cnt: %d\n", c->nnode_cnt); + pr_err("\tdirty_pn_cnt: %d\n", c->dirty_pn_cnt); + pr_err("\tdirty_nn_cnt: %d\n", c->dirty_nn_cnt); + pr_err("\tlsave_cnt: %d\n", c->lsave_cnt); + pr_err("\tspace_bits: %d\n", c->space_bits); + pr_err("\tlpt_lnum_bits: %d\n", c->lpt_lnum_bits); + pr_err("\tlpt_offs_bits: %d\n", c->lpt_offs_bits); + pr_err("\tlpt_spc_bits: %d\n", c->lpt_spc_bits); + pr_err("\tpcnt_bits: %d\n", c->pcnt_bits); + pr_err("\tlnum_bits: %d\n", c->lnum_bits); + pr_err("\tLPT root is at %d:%d\n", c->lpt_lnum, c->lpt_offs); + pr_err("\tLPT head is at %d:%d\n", + c->nhead_lnum, c->nhead_offs); + pr_err("\tLPT ltab is at %d:%d\n", c->ltab_lnum, c->ltab_offs); + if (c->big_lpt) + pr_err("\tLPT lsave is at %d:%d\n", + c->lsave_lnum, c->lsave_offs); + for (i = 0; i < c->lpt_lebs; i++) + pr_err("\tLPT LEB %d free %d dirty %d tgc %d cmt %d\n", + i + c->lpt_first, c->ltab[i].free, c->ltab[i].dirty, + c->ltab[i].tgc, c->ltab[i].cmt); + spin_unlock(&dbg_lock); +} + +void ubifs_dump_leb(const struct ubifs_info *c, int lnum) +{ + struct ubifs_scan_leb *sleb; + struct ubifs_scan_node *snod; + void *buf; + + pr_err("(pid %d) start dumping LEB %d\n", getpid(), lnum); + + buf = __vmalloc(c->leb_size, GFP_NOFS); + if (!buf) { + ubifs_err(c, "cannot allocate memory for dumping LEB %d", lnum); + return; + } + + sleb = ubifs_scan(c, lnum, 0, buf, 0); + if (IS_ERR(sleb)) { + ubifs_err(c, "scan error %d", (int)PTR_ERR(sleb)); + goto out; + } + + pr_err("LEB %d has %d nodes ending at %d\n", lnum, + sleb->nodes_cnt, sleb->endpt); + + list_for_each_entry(snod, &sleb->nodes, list) { + cond_resched(); + pr_err("Dumping node at LEB %d:%d len %d\n", lnum, + snod->offs, snod->len); + ubifs_dump_node(c, snod->node, c->leb_size - snod->offs); + } + + pr_err("(pid %d) finish dumping LEB %d\n", getpid(), lnum); + ubifs_scan_destroy(sleb); + +out: + vfree(buf); + return; +} + +void ubifs_dump_znode(const struct ubifs_info *c, + const struct ubifs_znode *znode) +{ + int n; + const struct ubifs_zbranch *zbr; + char key_buf[DBG_KEY_BUF_LEN]; + + spin_lock(&dbg_lock); + if (znode->parent) + zbr = &znode->parent->zbranch[znode->iip]; + else + zbr = &c->zroot; + + pr_err("znode %p, LEB %d:%d len %d parent %p iip %d level %d child_cnt %d flags %lx\n", + znode, zbr->lnum, zbr->offs, zbr->len, znode->parent, znode->iip, + znode->level, znode->child_cnt, znode->flags); + + if (znode->child_cnt <= 0 || znode->child_cnt > c->fanout) { + spin_unlock(&dbg_lock); + return; + } + + pr_err("zbranches:\n"); + for (n = 0; n < znode->child_cnt; n++) { + zbr = &znode->zbranch[n]; + if (znode->level > 0) + pr_err("\t%d: znode %p LEB %d:%d len %d key %s\n", + n, zbr->znode, zbr->lnum, zbr->offs, zbr->len, + dbg_snprintf_key(c, &zbr->key, key_buf, + DBG_KEY_BUF_LEN)); + else + pr_err("\t%d: LNC %p LEB %d:%d len %d key %s\n", + n, zbr->znode, zbr->lnum, zbr->offs, zbr->len, + dbg_snprintf_key(c, &zbr->key, key_buf, + DBG_KEY_BUF_LEN)); + } + spin_unlock(&dbg_lock); +} + +void ubifs_dump_heap(__unused struct ubifs_info *c, struct ubifs_lpt_heap *heap, + int cat) +{ + int i; + + pr_err("(pid %d) start dumping heap cat %d (%d elements)\n", + getpid(), cat, heap->cnt); + for (i = 0; i < heap->cnt; i++) { + struct ubifs_lprops *lprops = heap->arr[i]; + + pr_err("\t%d. LEB %d hpos %d free %d dirty %d flags %d\n", + i, lprops->lnum, lprops->hpos, lprops->free, + lprops->dirty, lprops->flags); + } + pr_err("(pid %d) finish dumping heap\n", getpid()); +} + +void ubifs_dump_pnode(__unused struct ubifs_info *c, struct ubifs_pnode *pnode, + struct ubifs_nnode *parent, int iip) +{ + int i; + + pr_err("(pid %d) dumping pnode:\n", getpid()); + pr_err("\taddress %zx parent %zx cnext %zx\n", + (size_t)pnode, (size_t)parent, (size_t)pnode->cnext); + pr_err("\tflags %lu iip %d level %d num %d\n", + pnode->flags, iip, pnode->level, pnode->num); + for (i = 0; i < UBIFS_LPT_FANOUT; i++) { + struct ubifs_lprops *lp = &pnode->lprops[i]; + + pr_err("\t%d: free %d dirty %d flags %d lnum %d\n", + i, lp->free, lp->dirty, lp->flags, lp->lnum); + } +} + +/** + * dbg_walk_index - walk the on-flash index. + * @c: UBIFS file-system description object + * @leaf_cb: called for each leaf node + * @znode_cb: called for each indexing node + * @priv: private data which is passed to callbacks + * + * This function walks the UBIFS index and calls the @leaf_cb for each leaf + * node and @znode_cb for each indexing node. Returns zero in case of success + * and a negative error code in case of failure. + * + * It would be better if this function removed every znode it pulled to into + * the TNC, so that the behavior more closely matched the non-debugging + * behavior. + */ +int dbg_walk_index(struct ubifs_info *c, dbg_leaf_callback leaf_cb, + dbg_znode_callback znode_cb, void *priv) +{ + int err; + struct ubifs_zbranch *zbr; + struct ubifs_znode *znode, *child; + + mutex_lock(&c->tnc_mutex); + /* If the root indexing node is not in TNC - pull it */ + if (!c->zroot.znode) { + c->zroot.znode = ubifs_load_znode(c, &c->zroot, NULL, 0); + if (IS_ERR(c->zroot.znode)) { + err = PTR_ERR(c->zroot.znode); + c->zroot.znode = NULL; + goto out_unlock; + } + } + + /* + * We are going to traverse the indexing tree in the postorder manner. + * Go down and find the leftmost indexing node where we are going to + * start from. + */ + znode = c->zroot.znode; + while (znode->level > 0) { + zbr = &znode->zbranch[0]; + child = zbr->znode; + if (!child) { + child = ubifs_load_znode(c, zbr, znode, 0); + if (IS_ERR(child)) { + err = PTR_ERR(child); + goto out_unlock; + } + } + + znode = child; + } + + /* Iterate over all indexing nodes */ + while (1) { + int idx; + + cond_resched(); + + if (znode_cb) { + err = znode_cb(c, znode, priv); + if (err) { + ubifs_err(c, "znode checking function returned error %d", + err); + ubifs_dump_znode(c, znode); + goto out_dump; + } + } + if (leaf_cb && znode->level == 0) { + for (idx = 0; idx < znode->child_cnt; idx++) { + zbr = &znode->zbranch[idx]; + err = leaf_cb(c, zbr, priv); + if (err) { + ubifs_err(c, "leaf checking function returned error %d, for leaf at LEB %d:%d", + err, zbr->lnum, zbr->offs); + goto out_dump; + } + } + } + + if (!znode->parent) + break; + + idx = znode->iip + 1; + znode = znode->parent; + if (idx < znode->child_cnt) { + /* Switch to the next index in the parent */ + zbr = &znode->zbranch[idx]; + child = zbr->znode; + if (!child) { + child = ubifs_load_znode(c, zbr, znode, idx); + if (IS_ERR(child)) { + err = PTR_ERR(child); + goto out_unlock; + } + zbr->znode = child; + } + znode = child; + } else + /* + * This is the last child, switch to the parent and + * continue. + */ + continue; + + /* Go to the lowest leftmost znode in the new sub-tree */ + while (znode->level > 0) { + zbr = &znode->zbranch[0]; + child = zbr->znode; + if (!child) { + child = ubifs_load_znode(c, zbr, znode, 0); + if (IS_ERR(child)) { + err = PTR_ERR(child); + goto out_unlock; + } + zbr->znode = child; + } + znode = child; + } + } + + mutex_unlock(&c->tnc_mutex); + return 0; + +out_dump: + if (znode->parent) + zbr = &znode->parent->zbranch[znode->iip]; + else + zbr = &c->zroot; + ubifs_msg(c, "dump of znode at LEB %d:%d", zbr->lnum, zbr->offs); + ubifs_dump_znode(c, znode); +out_unlock: + mutex_unlock(&c->tnc_mutex); + return err; +} + +/** + * add_size - add znode size to partially calculated index size. + * @c: UBIFS file-system description object + * @znode: znode to add size for + * @priv: partially calculated index size + * + * This is a helper function for 'dbg_check_idx_size()' which is called for + * every indexing node and adds its size to the 'long long' variable pointed to + * by @priv. + */ +int add_size(struct ubifs_info *c, struct ubifs_znode *znode, void *priv) +{ + long long *idx_size = priv; + int add; + + add = ubifs_idx_node_sz(c, znode->child_cnt); + add = ALIGN(add, 8); + *idx_size += add; + return 0; +} + +void ubifs_assert_failed(struct ubifs_info *c, const char *expr, + const char *file, int line) +{ + ubifs_err(c, "UBIFS assert failed: %s, in %s:%u", expr, file, line); + + /* + * Different from linux kernel. + * Invoke callback function if there is one, otherwise make filesystem + * readonly when assertion is failed. + */ + if (c->assert_failed_cb) + c->assert_failed_cb(c); + else + ubifs_ro_mode(c, -EINVAL); +} diff --git a/ubifs-utils/libubifs/debug.h b/ubifs-utils/libubifs/debug.h new file mode 100644 index 0000000..c4aa59d --- /dev/null +++ b/ubifs-utils/libubifs/debug.h @@ -0,0 +1,172 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * This file is part of UBIFS. + * + * Copyright (C) 2006-2008 Nokia Corporation. + * + * Authors: Artem Bityutskiy (Битюцкий Артём) + * Adrian Hunter + */ + +#ifndef __UBIFS_DEBUG_H__ +#define __UBIFS_DEBUG_H__ + +/* Checking helper functions */ +typedef int (*dbg_leaf_callback)(struct ubifs_info *c, + struct ubifs_zbranch *zbr, void *priv); +typedef int (*dbg_znode_callback)(struct ubifs_info *c, + struct ubifs_znode *znode, void *priv); + +void ubifs_assert_failed(struct ubifs_info *c, const char *expr, + const char *file, int line); + +#define ubifs_assert(c, expr) do { \ + if (unlikely(!(expr))) { \ + ubifs_assert_failed((struct ubifs_info *)c, #expr, __FILE__, \ + __LINE__); \ + } \ +} while (0) + +#define ubifs_assert_cmt_locked(c) do { \ + if (unlikely(down_write_trylock(&(c)->commit_sem))) { \ + up_write(&(c)->commit_sem); \ + ubifs_err(c, "commit lock is not locked!\n"); \ + ubifs_assert(c, 0); \ + } \ +} while (0) + +#define ubifs_dbg_msg(type, fmt, ...) \ + pr_debug("UBIFS DBG " type " " fmt "\n", ##__VA_ARGS__) + +#define DBG_KEY_BUF_LEN 48 +#define ubifs_dbg_msg_key(type, key, fmt, ...) do { \ + char __tmp_key_buf[DBG_KEY_BUF_LEN]; \ + pr_debug("UBIFS DBG " type " " fmt "%s\n", ##__VA_ARGS__, \ + dbg_snprintf_key(c, key, __tmp_key_buf, DBG_KEY_BUF_LEN)); \ +} while (0) + +/* General messages */ +#define dbg_gen(fmt, ...) ubifs_dbg_msg("gen", fmt, ##__VA_ARGS__) +/* Additional journal messages */ +#define dbg_jnl(fmt, ...) ubifs_dbg_msg("jnl", fmt, ##__VA_ARGS__) +#define dbg_jnlk(key, fmt, ...) \ + ubifs_dbg_msg_key("jnl", key, fmt, ##__VA_ARGS__) +/* Additional TNC messages */ +#define dbg_tnc(fmt, ...) ubifs_dbg_msg("tnc", fmt, ##__VA_ARGS__) +#define dbg_tnck(key, fmt, ...) \ + ubifs_dbg_msg_key("tnc", key, fmt, ##__VA_ARGS__) +/* Additional lprops messages */ +#define dbg_lp(fmt, ...) ubifs_dbg_msg("lp", fmt, ##__VA_ARGS__) +/* Additional LEB find messages */ +#define dbg_find(fmt, ...) ubifs_dbg_msg("find", fmt, ##__VA_ARGS__) +/* Additional mount messages */ +#define dbg_mnt(fmt, ...) ubifs_dbg_msg("mnt", fmt, ##__VA_ARGS__) +#define dbg_mntk(key, fmt, ...) \ + ubifs_dbg_msg_key("mnt", key, fmt, ##__VA_ARGS__) +/* Additional I/O messages */ +#define dbg_io(fmt, ...) ubifs_dbg_msg("io", fmt, ##__VA_ARGS__) +/* Additional commit messages */ +#define dbg_cmt(fmt, ...) ubifs_dbg_msg("cmt", fmt, ##__VA_ARGS__) +/* Additional budgeting messages */ +#define dbg_budg(fmt, ...) ubifs_dbg_msg("budg", fmt, ##__VA_ARGS__) +/* Additional log messages */ +#define dbg_log(fmt, ...) ubifs_dbg_msg("log", fmt, ##__VA_ARGS__) +/* Additional gc messages */ +#define dbg_gc(fmt, ...) ubifs_dbg_msg("gc", fmt, ##__VA_ARGS__) +/* Additional scan messages */ +#define dbg_scan(fmt, ...) ubifs_dbg_msg("scan", fmt, ##__VA_ARGS__) +/* Additional recovery messages */ +#define dbg_rcvry(fmt, ...) ubifs_dbg_msg("rcvry", fmt, ##__VA_ARGS__) +/* Additional fsck messages */ +#define dbg_fsck(fmt, ...) ubifs_dbg_msg("fsck", fmt, ##__VA_ARGS__) + +static inline int dbg_is_chk_index(__unused const struct ubifs_info *c) +{ return 0; } + +/* Dump functions */ +const char *ubifs_get_key_name(int type); +const char *ubifs_get_type_name(int type); +const char *dbg_ntype(int type); +const char *dbg_cstate(int cmt_state); +const char *dbg_jhead(int jhead); +const char *dbg_get_key_dump(const struct ubifs_info *c, + const union ubifs_key *key); +const char *dbg_snprintf_key(const struct ubifs_info *c, + const union ubifs_key *key, char *buffer, int len); +void ubifs_dump_node(const struct ubifs_info *c, const void *node, + int node_len); +void ubifs_dump_lstats(const struct ubifs_lp_stats *lst); +void ubifs_dump_budg(struct ubifs_info *c, const struct ubifs_budg_info *bi); +void ubifs_dump_lprop(const struct ubifs_info *c, + const struct ubifs_lprops *lp); +void ubifs_dump_lprops(struct ubifs_info *c); +void ubifs_dump_lpt_info(struct ubifs_info *c); +void ubifs_dump_leb(const struct ubifs_info *c, int lnum); +void ubifs_dump_znode(const struct ubifs_info *c, + const struct ubifs_znode *znode); +void ubifs_dump_heap(struct ubifs_info *c, struct ubifs_lpt_heap *heap, + int cat); +void ubifs_dump_pnode(struct ubifs_info *c, struct ubifs_pnode *pnode, + struct ubifs_nnode *parent, int iip); +void ubifs_dump_index(struct ubifs_info *c); +void ubifs_dump_lpt_lebs(const struct ubifs_info *c); + +int dbg_walk_index(struct ubifs_info *c, dbg_leaf_callback leaf_cb, + dbg_znode_callback znode_cb, void *priv); +int add_size(struct ubifs_info *c, struct ubifs_znode *znode, void *priv); + +/* Checking functions */ +static inline void dbg_save_space_info(__unused struct ubifs_info *c) {} +static inline int dbg_check_space_info(__unused struct ubifs_info *c) +{ return 0; } +static inline int dbg_check_lprops(__unused struct ubifs_info *c) { return 0; } +static inline int dbg_old_index_check_init(__unused struct ubifs_info *c, + __unused struct ubifs_zbranch *zroot) +{ return 0; } +static inline int dbg_check_old_index(__unused struct ubifs_info *c, + __unused struct ubifs_zbranch *zroot) +{ return 0; } +static inline int dbg_check_cats(__unused struct ubifs_info *c) { return 0; } +static inline int dbg_check_ltab(__unused struct ubifs_info *c) { return 0; } +static inline int dbg_chk_lpt_free_spc(__unused struct ubifs_info *c) +{ return 0; } +static inline int dbg_chk_lpt_sz(__unused struct ubifs_info *c, + __unused int action, __unused int len) +{ return 0; } +static inline int dbg_check_tnc(__unused struct ubifs_info *c, + __unused int extra) { return 0; } +static inline int dbg_check_idx_size(__unused struct ubifs_info *c, + __unused long long idx_size) { return 0; } +static inline int dbg_check_filesystem(__unused struct ubifs_info *c) +{ return 0; } +static inline void dbg_check_heap(__unused struct ubifs_info *c, + __unused struct ubifs_lpt_heap *heap, + __unused int cat, + __unused int add_pos) {} +static inline int dbg_check_lpt_nodes(__unused struct ubifs_info *c, + __unused struct ubifs_cnode *cnode, + __unused int row, + __unused int col) { return 0; } +static inline int dbg_check_data_nodes_order(__unused struct ubifs_info *c, + __unused struct list_head *head) +{ return 0; } +static inline int dbg_check_nondata_nodes_order(__unused struct ubifs_info *c, + __unused struct list_head *head) +{ return 0; } +static inline int dbg_leb_write(__unused struct ubifs_info *c, + __unused int lnum, __unused const void *buf, + __unused int offs, __unused int len) +{ return 0; } +static inline int dbg_leb_change(__unused struct ubifs_info *c, + __unused int lnum, __unused const void *buf, + __unused int len) { return 0; } +static inline int dbg_leb_unmap(__unused struct ubifs_info *c, + __unused int lnum) { return 0; } +static inline int dbg_leb_map(__unused struct ubifs_info *c, __unused int lnum) +{ return 0; } + +extern void print_hex_dump(const char *prefix_str, + int prefix_type, int rowsize, int groupsize, + const void *buf, size_t len, bool ascii); + +#endif /* !__UBIFS_DEBUG_H__ */ diff --git a/ubifs-utils/libubifs/dir.c b/ubifs-utils/libubifs/dir.c new file mode 100644 index 0000000..89f77eb --- /dev/null +++ b/ubifs-utils/libubifs/dir.c @@ -0,0 +1,390 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* * This file is part of UBIFS. + * + * Copyright (C) 2006-2008 Nokia Corporation. + * Copyright (C) 2006, 2007 University of Szeged, Hungary + * + * Authors: Artem Bityutskiy (Битюцкий Артём) + * Adrian Hunter + * Zoltan Sogor + */ + +/* + * This file implements directory operations. + * + * All FS operations in this file allocate budget before writing anything to the + * media. If they fail to allocate it, the error is returned. The only + * exceptions are 'ubifs_unlink()' and 'ubifs_rmdir()' which keep working even + * if they unable to allocate the budget, because deletion %-ENOSPC failure is + * not what users are usually ready to get. UBIFS budgeting subsystem has some + * space reserved for these purposes. + * + * All operations in this file write all inodes which they change straight + * away, instead of marking them dirty. For example, 'ubifs_link()' changes + * @i_size of the parent inode and writes the parent inode together with the + * target inode. This was done to simplify file-system recovery which would + * otherwise be very difficult to do. The only exception is rename which marks + * the re-named inode dirty (because its @i_ctime is updated) but does not + * write it, but just marks it as dirty. + */ + +#include <sys/stat.h> + +#include "linux_err.h" +#include "bitops.h" +#include "kmem.h" +#include "ubifs.h" +#include "defs.h" +#include "debug.h" +#include "key.h" +#include "misc.h" + +/** + * inherit_flags - inherit flags of the parent inode. + * @c: UBIFS file-system description object + * @dir: parent inode + * @mode: new inode mode flags + * + * This is a helper function for 'ubifs_new_inode()' which inherits flag of the + * parent directory inode @dir. UBIFS inodes inherit the following flags: + * o %UBIFS_COMPR_FL, which is useful to switch compression on/of on + * sub-directory basis; + * o %UBIFS_SYNC_FL - useful for the same reasons; + * o %UBIFS_DIRSYNC_FL - similar, but relevant only to directories. + * + * This function returns the inherited flags. + */ +static int inherit_flags(struct ubifs_info *c, const struct inode *dir, + unsigned int mode) +{ + int flags; + const struct ubifs_inode *ui = ubifs_inode(dir); + + ubifs_assert(c, S_ISDIR(dir->mode)); + + flags = ui->flags & (UBIFS_COMPR_FL | UBIFS_SYNC_FL | UBIFS_DIRSYNC_FL); + if (!S_ISDIR(mode)) + /* The "DIRSYNC" flag only applies to directories */ + flags &= ~UBIFS_DIRSYNC_FL; + return flags; +} + +/** + * ubifs_new_inode - allocate new UBIFS inode object. + * @c: UBIFS file-system description object + * @dir: parent inode + * @mode: inode mode flags + * + * This function finds an unused inode number, allocates new ubifs inode and + * initializes it. Returns new ubifs inode in case of success and an error code + * in case of failure. + */ +static struct ubifs_inode *ubifs_new_inode(struct ubifs_info *c, + const struct inode *dir, + unsigned int mode) +{ + int err; + time_t now = time(NULL); + struct ubifs_inode *ui; + struct inode *inode; + + ui = kzalloc(sizeof(struct ubifs_inode), GFP_KERNEL); + if (!ui) + return ERR_PTR(-ENOMEM); + + inode = &ui->vfs_inode; + inode->atime_sec = inode->ctime_sec = inode->mtime_sec = now; + inode->nlink = 1; + inode->mode = mode; + if (dir) { + /* Create non root dir. */ + inode->uid = dir->uid; + inode->gid = dir->gid; + if ((dir->mode & S_ISGID) && S_ISDIR(mode)) + inode->mode |= S_ISGID; + ui->flags = inherit_flags(c, dir, mode); + } + if (S_ISDIR(mode)) + ui->ui_size = UBIFS_INO_NODE_SZ; + if (S_ISREG(mode)) + ui->compr_type = c->default_compr; + else + ui->compr_type = UBIFS_COMPR_NONE; + + if (dir) { + spin_lock(&c->cnt_lock); + /* Inode number overflow is currently not supported */ + if (c->highest_inum >= INUM_WARN_WATERMARK) { + if (c->highest_inum >= INUM_WATERMARK) { + spin_unlock(&c->cnt_lock); + ubifs_err(c, "out of inode numbers"); + err = -EINVAL; + goto out; + } + ubifs_warn(c, "running out of inode numbers (current %lu, max %u)", + (unsigned long)c->highest_inum, INUM_WATERMARK); + } + inode->inum = ++c->highest_inum; + } else { + /* Create root dir. */ + inode->inum = UBIFS_ROOT_INO; + } + /* + * The creation sequence number remains with this inode for its + * lifetime. All nodes for this inode have a greater sequence number, + * and so it is possible to distinguish obsolete nodes belonging to a + * previous incarnation of the same inode number - for example, for the + * purpose of rebuilding the index. + */ + ui->creat_sqnum = ++c->max_sqnum; + spin_unlock(&c->cnt_lock); + + return ui; + +out: + kfree(ui); + return ERR_PTR(err); +} + +/** + * ubifs_lookup_by_inum - look up the UBIFS inode according to inode number. + * @c: UBIFS file-system description object + * @inum: inode number + * + * This function looks up the UBIFS inode according to a given inode number. + * Returns zero in case of success and an error code in case of failure. + */ +struct ubifs_inode *ubifs_lookup_by_inum(struct ubifs_info *c, ino_t inum) +{ + int err; + union ubifs_key key; + struct inode *inode; + struct ubifs_inode *ui; + struct ubifs_ino_node *ino = NULL; + + ino = kmalloc(UBIFS_MAX_INO_NODE_SZ, GFP_NOFS); + if (!ino) + return ERR_PTR(-ENOMEM); + + ui = kzalloc(sizeof(struct ubifs_inode), GFP_KERNEL); + if (!ui) { + err = -ENOMEM; + goto out; + } + + inode = &ui->vfs_inode; + ino_key_init(c, &key, inum); + err = ubifs_tnc_lookup(c, &key, ino); + if (err) { + kfree(ui); + ubifs_assert(c, !get_failure_reason_callback(c)); + goto out; + } + + inode = &ui->vfs_inode; + inode->inum = inum; + inode->uid = le32_to_cpu(ino->uid); + inode->gid = le32_to_cpu(ino->gid); + inode->mode = le32_to_cpu(ino->mode); + inode->nlink = le32_to_cpu(ino->nlink); + inode->atime_sec = le64_to_cpu(ino->atime_sec); + inode->ctime_sec = le64_to_cpu(ino->ctime_sec); + inode->mtime_sec = le64_to_cpu(ino->mtime_sec); + inode->atime_nsec = le32_to_cpu(ino->atime_nsec); + inode->ctime_nsec = le32_to_cpu(ino->ctime_nsec); + inode->mtime_nsec = le32_to_cpu(ino->mtime_nsec); + ui->creat_sqnum = le64_to_cpu(ino->creat_sqnum); + ui->xattr_size = le32_to_cpu(ino->xattr_size); + ui->xattr_cnt = le32_to_cpu(ino->xattr_cnt); + ui->xattr_names = le32_to_cpu(ino->xattr_names); + ui->compr_type = le16_to_cpu(ino->compr_type); + ui->ui_size = le64_to_cpu(ino->size); + ui->flags = le32_to_cpu(ino->flags); + ui->data_len = le32_to_cpu(ino->data_len); + +out: + kfree(ino); + return err ? ERR_PTR(err) : ui; +} + +struct ubifs_inode *ubifs_lookup(struct ubifs_info *c, + struct ubifs_inode *dir_ui, + const struct fscrypt_name *nm) +{ + int err; + ino_t inum; + union ubifs_key key; + struct ubifs_dent_node *dent; + + if (fname_len(nm) > UBIFS_MAX_NLEN) + return ERR_PTR(-ENAMETOOLONG); + + dent = kmalloc(UBIFS_MAX_DENT_NODE_SZ, GFP_NOFS); + if (!dent) + return ERR_PTR(-ENOMEM); + + dent_key_init(c, &key, dir_ui->vfs_inode.inum, nm); + err = ubifs_tnc_lookup_nm(c, &key, dent, nm); + if (err) { + kfree(dent); + ubifs_assert(c, !get_failure_reason_callback(c)); + return ERR_PTR(err); + } + inum = le64_to_cpu(dent->inum); + kfree(dent); + + return ubifs_lookup_by_inum(c, inum); +} + +int ubifs_mkdir(struct ubifs_info *c, struct ubifs_inode *dir_ui, + const struct fscrypt_name *nm, unsigned int mode) +{ + struct ubifs_inode *ui; + struct inode *inode, *dir = &dir_ui->vfs_inode; + int err, sz_change; + struct ubifs_budget_req req = { .new_ino = 1, .new_dent = 1, + .dirtied_ino = 1}; + /* + * Budget request settings: new inode, new direntry and changing parent + * directory inode. + */ + dbg_gen("dent '%s', mode %#hx in dir ino %lu", + fname_name(nm), mode, dir->inum); + + /* New dir is not allowed to be created under an encrypted directory. */ + ubifs_assert(c, !(dir_ui->flags & UBIFS_CRYPT_FL)); + + err = ubifs_budget_space(c, &req); + if (err) + return err; + + sz_change = CALC_DENT_SIZE(fname_len(nm)); + + ui = ubifs_new_inode(c, dir, S_IFDIR | mode); + if (IS_ERR(ui)) { + err = PTR_ERR(ui); + goto out_budg; + } + + inode = &ui->vfs_inode; + inode->nlink++; + dir->nlink++; + dir_ui->ui_size += sz_change; + dir->ctime_sec = dir->mtime_sec = inode->ctime_sec; + err = ubifs_jnl_update_file(c, dir_ui, nm, ui); + if (err) { + ubifs_err(c, "cannot create directory, error %d", err); + goto out_cancel; + } + + kfree(ui); + ubifs_release_budget(c, &req); + return 0; + +out_cancel: + dir_ui->ui_size -= sz_change; + dir->nlink--; + kfree(ui); +out_budg: + ubifs_release_budget(c, &req); + return err; +} + +/** + * ubifs_link_recovery - link a disconnected file into the target directory. + * @c: UBIFS file-system description object + * @dir_ui: target directory + * @ui: the UBIFS inode of disconnected file + * @nm: directory entry name + * + * This function links the inode of disconnected file to a directory entry name + * under the target directory. Returns zero in case of success and an error code + * in case of failure. + */ +int ubifs_link_recovery(struct ubifs_info *c, struct ubifs_inode *dir_ui, + struct ubifs_inode *ui, const struct fscrypt_name *nm) +{ + struct inode *inode = &ui->vfs_inode, *dir = &dir_ui->vfs_inode; + int err, sz_change; + struct ubifs_budget_req req = { .new_dent = 1, .dirtied_ino = 2, + .dirtied_ino_d = ALIGN(ui->data_len, 8) }; + time_t now = time(NULL); + + /* + * Budget request settings: new direntry, changing the target inode, + * changing the parent inode. + */ + dbg_gen("dent '%s' to ino %lu (nlink %d) in dir ino %lu", + fname_name(nm), inode->inum, inode->nlink, dir->inum); + + /* New dir is not allowed to be created under an encrypted directory. */ + ubifs_assert(c, !(dir_ui->flags & UBIFS_CRYPT_FL)); + + sz_change = CALC_DENT_SIZE(fname_len(nm)); + + err = ubifs_budget_space(c, &req); + if (err) + return err; + + inode->ctime_sec = now; + dir_ui->ui_size += sz_change; + dir->ctime_sec = dir->mtime_sec = now; + err = ubifs_jnl_update_file(c, dir_ui, nm, ui); + if (err) + goto out_cancel; + + ubifs_release_budget(c, &req); + return 0; + +out_cancel: + dir_ui->ui_size -= sz_change; + ubifs_release_budget(c, &req); + return err; +} + +/** + * ubifs_create_root - create the root inode. + * @c: UBIFS file-system description object + * + * This function creates a new inode for the root directory. Returns zero in + * case of success and an error code in case of failure. + */ +int ubifs_create_root(struct ubifs_info *c) +{ + int err; + struct inode *inode; + struct ubifs_budget_req req = { .new_ino = 1 }; + struct ubifs_inode *ui; + + /* Budget request settings: new inode. */ + dbg_gen("create root dir"); + + err = ubifs_budget_space(c, &req); + if (err) + return err; + + ui = ubifs_new_inode(c, NULL, S_IFDIR | S_IRUGO | S_IWUSR | S_IXUGO); + if (IS_ERR(ui)) { + err = PTR_ERR(ui); + goto out_budg; + } + + inode = &ui->vfs_inode; + inode->nlink = 2; + ui->ui_size = UBIFS_INO_NODE_SZ; + ui->flags = UBIFS_COMPR_FL; + err = ubifs_jnl_update_file(c, NULL, NULL, ui); + if (err) + goto out_ui; + + kfree(ui); + ubifs_release_budget(c, &req); + return 0; + +out_ui: + kfree(ui); +out_budg: + ubifs_release_budget(c, &req); + ubifs_err(c, "cannot create root dir, error %d", err); + return err; +} diff --git a/ubifs-utils/libubifs/find.c b/ubifs-utils/libubifs/find.c new file mode 100644 index 0000000..364252e --- /dev/null +++ b/ubifs-utils/libubifs/find.c @@ -0,0 +1,970 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * This file is part of UBIFS. + * + * Copyright (C) 2006-2008 Nokia Corporation. + * + * Authors: Artem Bityutskiy (Битюцкий Артём) + * Adrian Hunter + */ + +/* + * This file contains functions for finding LEBs for various purposes e.g. + * garbage collection. In general, lprops category heaps and lists are used + * for fast access, falling back on scanning the LPT as a last resort. + */ + +#include <sys/types.h> + +#include "linux_err.h" +#include "bitops.h" +#include "sort.h" +#include "ubifs.h" +#include "defs.h" +#include "debug.h" +#include "misc.h" + +/** + * struct scan_data - data provided to scan callback functions + * @min_space: minimum number of bytes for which to scan + * @pick_free: whether it is OK to scan for empty LEBs + * @lnum: LEB number found is returned here + * @exclude_index: whether to exclude index LEBs + */ +struct scan_data { + int min_space; + int pick_free; + int lnum; + int exclude_index; +}; + +/** + * valuable - determine whether LEB properties are valuable. + * @c: the UBIFS file-system description object + * @lprops: LEB properties + * + * This function return %1 if the LEB properties should be added to the LEB + * properties tree in memory. Otherwise %0 is returned. + */ +static int valuable(struct ubifs_info *c, const struct ubifs_lprops *lprops) +{ + int n, cat = lprops->flags & LPROPS_CAT_MASK; + struct ubifs_lpt_heap *heap; + + switch (cat) { + case LPROPS_DIRTY: + case LPROPS_DIRTY_IDX: + case LPROPS_FREE: + heap = &c->lpt_heap[cat - 1]; + if (heap->cnt < heap->max_cnt) + return 1; + if (lprops->free + lprops->dirty >= c->dark_wm) + return 1; + return 0; + case LPROPS_EMPTY: + n = c->lst.empty_lebs + c->freeable_cnt - + c->lst.taken_empty_lebs; + if (n < c->lsave_cnt) + return 1; + return 0; + case LPROPS_FREEABLE: + return 1; + case LPROPS_FRDI_IDX: + return 1; + } + return 0; +} + +/** + * scan_for_dirty_cb - dirty space scan callback. + * @c: the UBIFS file-system description object + * @lprops: LEB properties to scan + * @in_tree: whether the LEB properties are in main memory + * @arg: information passed to and from the caller of the scan + * + * This function returns a code that indicates whether the scan should continue + * (%LPT_SCAN_CONTINUE), whether the LEB properties should be added to the tree + * in main memory (%LPT_SCAN_ADD), or whether the scan should stop + * (%LPT_SCAN_STOP). + */ +static int scan_for_dirty_cb(struct ubifs_info *c, + const struct ubifs_lprops *lprops, int in_tree, + void *arg) +{ + struct scan_data *data = arg; + int ret = LPT_SCAN_CONTINUE; + + /* Exclude LEBs that are currently in use */ + if (lprops->flags & LPROPS_TAKEN) + return LPT_SCAN_CONTINUE; + /* Determine whether to add these LEB properties to the tree */ + if (!in_tree && valuable(c, lprops)) + ret |= LPT_SCAN_ADD; + /* Exclude LEBs with too little space */ + if (lprops->free + lprops->dirty < data->min_space) + return ret; + /* If specified, exclude index LEBs */ + if (data->exclude_index && lprops->flags & LPROPS_INDEX) + return ret; + /* If specified, exclude empty or freeable LEBs */ + if (lprops->free + lprops->dirty == c->leb_size) { + if (!data->pick_free) + return ret; + /* Exclude LEBs with too little dirty space (unless it is empty) */ + } else if (lprops->dirty < c->dead_wm) + return ret; + /* Finally we found space */ + data->lnum = lprops->lnum; + return LPT_SCAN_ADD | LPT_SCAN_STOP; +} + +/** + * scan_for_dirty - find a data LEB with free space. + * @c: the UBIFS file-system description object + * @min_space: minimum amount free plus dirty space the returned LEB has to + * have + * @pick_free: if it is OK to return a free or freeable LEB + * @exclude_index: whether to exclude index LEBs + * + * This function returns a pointer to the LEB properties found or a negative + * error code. + */ +static const struct ubifs_lprops *scan_for_dirty(struct ubifs_info *c, + int min_space, int pick_free, + int exclude_index) +{ + const struct ubifs_lprops *lprops; + struct ubifs_lpt_heap *heap; + struct scan_data data; + int err, i; + + /* There may be an LEB with enough dirty space on the free heap */ + heap = &c->lpt_heap[LPROPS_FREE - 1]; + for (i = 0; i < heap->cnt; i++) { + lprops = heap->arr[i]; + if (lprops->free + lprops->dirty < min_space) + continue; + if (lprops->dirty < c->dead_wm) + continue; + return lprops; + } + /* + * A LEB may have fallen off of the bottom of the dirty heap, and ended + * up as uncategorized even though it has enough dirty space for us now, + * so check the uncategorized list. N.B. neither empty nor freeable LEBs + * can end up as uncategorized because they are kept on lists not + * finite-sized heaps. + */ + list_for_each_entry(lprops, &c->uncat_list, list) { + if (lprops->flags & LPROPS_TAKEN) + continue; + if (lprops->free + lprops->dirty < min_space) + continue; + if (exclude_index && (lprops->flags & LPROPS_INDEX)) + continue; + if (lprops->dirty < c->dead_wm) + continue; + return lprops; + } + /* We have looked everywhere in main memory, now scan the flash */ + if (c->pnodes_have >= c->pnode_cnt) + /* All pnodes are in memory, so skip scan */ + return ERR_PTR(-ENOSPC); + data.min_space = min_space; + data.pick_free = pick_free; + data.lnum = -1; + data.exclude_index = exclude_index; + err = ubifs_lpt_scan_nolock(c, -1, c->lscan_lnum, scan_for_dirty_cb, + &data); + if (err) + return ERR_PTR(err); + ubifs_assert(c, data.lnum >= c->main_first && data.lnum < c->leb_cnt); + c->lscan_lnum = data.lnum; + lprops = ubifs_lpt_lookup_dirty(c, data.lnum); + if (IS_ERR(lprops)) + return lprops; + ubifs_assert(c, lprops->lnum == data.lnum); + ubifs_assert(c, lprops->free + lprops->dirty >= min_space); + ubifs_assert(c, lprops->dirty >= c->dead_wm || + (pick_free && + lprops->free + lprops->dirty == c->leb_size)); + ubifs_assert(c, !(lprops->flags & LPROPS_TAKEN)); + ubifs_assert(c, !exclude_index || !(lprops->flags & LPROPS_INDEX)); + return lprops; +} + +/** + * ubifs_find_dirty_leb - find a dirty LEB for the Garbage Collector. + * @c: the UBIFS file-system description object + * @ret_lp: LEB properties are returned here on exit + * @min_space: minimum amount free plus dirty space the returned LEB has to + * have + * @pick_free: controls whether it is OK to pick empty or index LEBs + * + * This function tries to find a dirty logical eraseblock which has at least + * @min_space free and dirty space. It prefers to take an LEB from the dirty or + * dirty index heap, and it falls-back to LPT scanning if the heaps are empty + * or do not have an LEB which satisfies the @min_space criteria. + * + * Note, LEBs which have less than dead watermark of free + dirty space are + * never picked by this function. + * + * The additional @pick_free argument controls if this function has to return a + * free or freeable LEB if one is present. For example, GC must to set it to %1, + * when called from the journal space reservation function, because the + * appearance of free space may coincide with the loss of enough dirty space + * for GC to succeed anyway. + * + * In contrast, if the Garbage Collector is called from budgeting, it should + * just make free space, not return LEBs which are already free or freeable. + * + * In addition @pick_free is set to %2 by the recovery process in order to + * recover gc_lnum in which case an index LEB must not be returned. + * + * This function returns zero and the LEB properties of found dirty LEB in case + * of success, %-ENOSPC if no dirty LEB was found and a negative error code in + * case of other failures. The returned LEB is marked as "taken". + */ +int ubifs_find_dirty_leb(struct ubifs_info *c, struct ubifs_lprops *ret_lp, + int min_space, int pick_free) +{ + int err = 0, sum, exclude_index = pick_free == 2 ? 1 : 0; + const struct ubifs_lprops *lp = NULL, *idx_lp = NULL; + struct ubifs_lpt_heap *heap, *idx_heap; + + ubifs_get_lprops(c); + + if (pick_free) { + int lebs, rsvd_idx_lebs = 0; + + spin_lock(&c->space_lock); + lebs = c->lst.empty_lebs + c->idx_gc_cnt; + lebs += c->freeable_cnt - c->lst.taken_empty_lebs; + + /* + * Note, the index may consume more LEBs than have been reserved + * for it. It is OK because it might be consolidated by GC. + * But if the index takes fewer LEBs than it is reserved for it, + * this function must avoid picking those reserved LEBs. + */ + if (c->bi.min_idx_lebs >= c->lst.idx_lebs) { + rsvd_idx_lebs = c->bi.min_idx_lebs - c->lst.idx_lebs; + exclude_index = 1; + } + spin_unlock(&c->space_lock); + + /* Check if there are enough free LEBs for the index */ + if (rsvd_idx_lebs < lebs) { + /* OK, try to find an empty LEB */ + lp = ubifs_fast_find_empty(c); + if (lp) + goto found; + + /* Or a freeable LEB */ + lp = ubifs_fast_find_freeable(c); + if (lp) + goto found; + } else + /* + * We cannot pick free/freeable LEBs in the below code. + */ + pick_free = 0; + } else { + spin_lock(&c->space_lock); + exclude_index = (c->bi.min_idx_lebs >= c->lst.idx_lebs); + spin_unlock(&c->space_lock); + } + + /* Look on the dirty and dirty index heaps */ + heap = &c->lpt_heap[LPROPS_DIRTY - 1]; + idx_heap = &c->lpt_heap[LPROPS_DIRTY_IDX - 1]; + + if (idx_heap->cnt && !exclude_index) { + idx_lp = idx_heap->arr[0]; + sum = idx_lp->free + idx_lp->dirty; + /* + * Since we reserve thrice as much space for the index than it + * actually takes, it does not make sense to pick indexing LEBs + * with less than, say, half LEB of dirty space. May be half is + * not the optimal boundary - this should be tested and + * checked. This boundary should determine how much we use + * in-the-gaps to consolidate the index comparing to how much + * we use garbage collector to consolidate it. The "half" + * criteria just feels to be fine. + */ + if (sum < min_space || sum < c->half_leb_size) + idx_lp = NULL; + } + + if (heap->cnt) { + lp = heap->arr[0]; + if (lp->dirty + lp->free < min_space) + lp = NULL; + } + + /* Pick the LEB with most space */ + if (idx_lp && lp) { + if (idx_lp->free + idx_lp->dirty >= lp->free + lp->dirty) + lp = idx_lp; + } else if (idx_lp && !lp) + lp = idx_lp; + + if (lp) { + ubifs_assert(c, lp->free + lp->dirty >= c->dead_wm); + goto found; + } + + /* Did not find a dirty LEB on the dirty heaps, have to scan */ + dbg_find("scanning LPT for a dirty LEB"); + lp = scan_for_dirty(c, min_space, pick_free, exclude_index); + if (IS_ERR(lp)) { + err = PTR_ERR(lp); + goto out; + } + ubifs_assert(c, lp->dirty >= c->dead_wm || + (pick_free && lp->free + lp->dirty == c->leb_size)); + +found: + dbg_find("found LEB %d, free %d, dirty %d, flags %#x", + lp->lnum, lp->free, lp->dirty, lp->flags); + + lp = ubifs_change_lp(c, lp, LPROPS_NC, LPROPS_NC, + lp->flags | LPROPS_TAKEN, 0); + if (IS_ERR(lp)) { + err = PTR_ERR(lp); + goto out; + } + + memcpy(ret_lp, lp, sizeof(struct ubifs_lprops)); + +out: + ubifs_release_lprops(c); + return err; +} + +/** + * scan_for_free_cb - free space scan callback. + * @c: the UBIFS file-system description object + * @lprops: LEB properties to scan + * @in_tree: whether the LEB properties are in main memory + * @arg: information passed to and from the caller of the scan + * + * This function returns a code that indicates whether the scan should continue + * (%LPT_SCAN_CONTINUE), whether the LEB properties should be added to the tree + * in main memory (%LPT_SCAN_ADD), or whether the scan should stop + * (%LPT_SCAN_STOP). + */ +static int scan_for_free_cb(struct ubifs_info *c, + const struct ubifs_lprops *lprops, int in_tree, + void *arg) +{ + struct scan_data *data = arg; + int ret = LPT_SCAN_CONTINUE; + + /* Exclude LEBs that are currently in use */ + if (lprops->flags & LPROPS_TAKEN) + return LPT_SCAN_CONTINUE; + /* Determine whether to add these LEB properties to the tree */ + if (!in_tree && valuable(c, lprops)) + ret |= LPT_SCAN_ADD; + /* Exclude index LEBs */ + if (lprops->flags & LPROPS_INDEX) + return ret; + /* Exclude LEBs with too little space */ + if (lprops->free < data->min_space) + return ret; + /* If specified, exclude empty LEBs */ + if (!data->pick_free && lprops->free == c->leb_size) + return ret; + /* + * LEBs that have only free and dirty space must not be allocated + * because they may have been unmapped already or they may have data + * that is obsolete only because of nodes that are still sitting in a + * wbuf. + */ + if (lprops->free + lprops->dirty == c->leb_size && lprops->dirty > 0) + return ret; + /* Finally we found space */ + data->lnum = lprops->lnum; + return LPT_SCAN_ADD | LPT_SCAN_STOP; +} + +/** + * do_find_free_space - find a data LEB with free space. + * @c: the UBIFS file-system description object + * @min_space: minimum amount of free space required + * @pick_free: whether it is OK to scan for empty LEBs + * @squeeze: whether to try to find space in a non-empty LEB first + * + * This function returns a pointer to the LEB properties found or a negative + * error code. + */ +static +const struct ubifs_lprops *do_find_free_space(struct ubifs_info *c, + int min_space, int pick_free, + int squeeze) +{ + const struct ubifs_lprops *lprops; + struct ubifs_lpt_heap *heap; + struct scan_data data; + int err, i; + + if (squeeze) { + lprops = ubifs_fast_find_free(c); + if (lprops && lprops->free >= min_space) + return lprops; + } + if (pick_free) { + lprops = ubifs_fast_find_empty(c); + if (lprops) + return lprops; + } + if (!squeeze) { + lprops = ubifs_fast_find_free(c); + if (lprops && lprops->free >= min_space) + return lprops; + } + /* There may be an LEB with enough free space on the dirty heap */ + heap = &c->lpt_heap[LPROPS_DIRTY - 1]; + for (i = 0; i < heap->cnt; i++) { + lprops = heap->arr[i]; + if (lprops->free >= min_space) + return lprops; + } + /* + * A LEB may have fallen off of the bottom of the free heap, and ended + * up as uncategorized even though it has enough free space for us now, + * so check the uncategorized list. N.B. neither empty nor freeable LEBs + * can end up as uncategorized because they are kept on lists not + * finite-sized heaps. + */ + list_for_each_entry(lprops, &c->uncat_list, list) { + if (lprops->flags & LPROPS_TAKEN) + continue; + if (lprops->flags & LPROPS_INDEX) + continue; + if (lprops->free >= min_space) + return lprops; + } + /* We have looked everywhere in main memory, now scan the flash */ + if (c->pnodes_have >= c->pnode_cnt) + /* All pnodes are in memory, so skip scan */ + return ERR_PTR(-ENOSPC); + data.min_space = min_space; + data.pick_free = pick_free; + data.lnum = -1; + err = ubifs_lpt_scan_nolock(c, -1, c->lscan_lnum, + scan_for_free_cb, + &data); + if (err) + return ERR_PTR(err); + ubifs_assert(c, data.lnum >= c->main_first && data.lnum < c->leb_cnt); + c->lscan_lnum = data.lnum; + lprops = ubifs_lpt_lookup_dirty(c, data.lnum); + if (IS_ERR(lprops)) + return lprops; + ubifs_assert(c, lprops->lnum == data.lnum); + ubifs_assert(c, lprops->free >= min_space); + ubifs_assert(c, !(lprops->flags & LPROPS_TAKEN)); + ubifs_assert(c, !(lprops->flags & LPROPS_INDEX)); + return lprops; +} + +/** + * ubifs_find_free_space - find a data LEB with free space. + * @c: the UBIFS file-system description object + * @min_space: minimum amount of required free space + * @offs: contains offset of where free space starts on exit + * @squeeze: whether to try to find space in a non-empty LEB first + * + * This function looks for an LEB with at least @min_space bytes of free space. + * It tries to find an empty LEB if possible. If no empty LEBs are available, + * this function searches for a non-empty data LEB. The returned LEB is marked + * as "taken". + * + * This function returns found LEB number in case of success, %-ENOSPC if it + * failed to find a LEB with @min_space bytes of free space and other a negative + * error codes in case of failure. + */ +int ubifs_find_free_space(struct ubifs_info *c, int min_space, int *offs, + int squeeze) +{ + const struct ubifs_lprops *lprops; + int lebs, rsvd_idx_lebs, pick_free = 0, err, lnum, flags; + + dbg_find("min_space %d", min_space); + ubifs_get_lprops(c); + + /* Check if there are enough empty LEBs for commit */ + spin_lock(&c->space_lock); + if (c->bi.min_idx_lebs > c->lst.idx_lebs) + rsvd_idx_lebs = c->bi.min_idx_lebs - c->lst.idx_lebs; + else + rsvd_idx_lebs = 0; + lebs = c->lst.empty_lebs + c->freeable_cnt + c->idx_gc_cnt - + c->lst.taken_empty_lebs; + if (rsvd_idx_lebs < lebs) + /* + * OK to allocate an empty LEB, but we still don't want to go + * looking for one if there aren't any. + */ + if (c->lst.empty_lebs - c->lst.taken_empty_lebs > 0) { + pick_free = 1; + /* + * Because we release the space lock, we must account + * for this allocation here. After the LEB properties + * flags have been updated, we subtract one. Note, the + * result of this is that lprops also decreases + * @taken_empty_lebs in 'ubifs_change_lp()', so it is + * off by one for a short period of time which may + * introduce a small disturbance to budgeting + * calculations, but this is harmless because at the + * worst case this would make the budgeting subsystem + * be more pessimistic than needed. + * + * Fundamentally, this is about serialization of the + * budgeting and lprops subsystems. We could make the + * @space_lock a mutex and avoid dropping it before + * calling 'ubifs_change_lp()', but mutex is more + * heavy-weight, and we want budgeting to be as fast as + * possible. + */ + c->lst.taken_empty_lebs += 1; + } + spin_unlock(&c->space_lock); + + lprops = do_find_free_space(c, min_space, pick_free, squeeze); + if (IS_ERR(lprops)) { + err = PTR_ERR(lprops); + goto out; + } + + lnum = lprops->lnum; + flags = lprops->flags | LPROPS_TAKEN; + + lprops = ubifs_change_lp(c, lprops, LPROPS_NC, LPROPS_NC, flags, 0); + if (IS_ERR(lprops)) { + err = PTR_ERR(lprops); + goto out; + } + + if (pick_free) { + spin_lock(&c->space_lock); + c->lst.taken_empty_lebs -= 1; + spin_unlock(&c->space_lock); + } + + *offs = c->leb_size - lprops->free; + ubifs_release_lprops(c); + + if (*offs == 0) { + /* + * Ensure that empty LEBs have been unmapped. They may not have + * been, for example, because of an unclean unmount. Also + * LEBs that were freeable LEBs (free + dirty == leb_size) will + * not have been unmapped. + */ + err = ubifs_leb_unmap(c, lnum); + if (err) + return err; + } + + dbg_find("found LEB %d, free %d", lnum, c->leb_size - *offs); + ubifs_assert(c, *offs <= c->leb_size - min_space); + return lnum; + +out: + if (pick_free) { + spin_lock(&c->space_lock); + c->lst.taken_empty_lebs -= 1; + spin_unlock(&c->space_lock); + } + ubifs_release_lprops(c); + return err; +} + +/** + * scan_for_idx_cb - callback used by the scan for a free LEB for the index. + * @c: the UBIFS file-system description object + * @lprops: LEB properties to scan + * @in_tree: whether the LEB properties are in main memory + * @arg: information passed to and from the caller of the scan + * + * This function returns a code that indicates whether the scan should continue + * (%LPT_SCAN_CONTINUE), whether the LEB properties should be added to the tree + * in main memory (%LPT_SCAN_ADD), or whether the scan should stop + * (%LPT_SCAN_STOP). + */ +static int scan_for_idx_cb(struct ubifs_info *c, + const struct ubifs_lprops *lprops, int in_tree, + void *arg) +{ + struct scan_data *data = arg; + int ret = LPT_SCAN_CONTINUE; + + /* Exclude LEBs that are currently in use */ + if (lprops->flags & LPROPS_TAKEN) + return LPT_SCAN_CONTINUE; + /* Determine whether to add these LEB properties to the tree */ + if (!in_tree && valuable(c, lprops)) + ret |= LPT_SCAN_ADD; + /* Exclude index LEBS */ + if (lprops->flags & LPROPS_INDEX) + return ret; + /* Exclude LEBs that cannot be made empty */ + if (lprops->free + lprops->dirty != c->leb_size) + return ret; + /* + * We are allocating for the index so it is safe to allocate LEBs with + * only free and dirty space, because write buffers are sync'd at commit + * start. + */ + data->lnum = lprops->lnum; + return LPT_SCAN_ADD | LPT_SCAN_STOP; +} + +/** + * scan_for_leb_for_idx - scan for a free LEB for the index. + * @c: the UBIFS file-system description object + */ +static const struct ubifs_lprops *scan_for_leb_for_idx(struct ubifs_info *c) +{ + const struct ubifs_lprops *lprops; + struct scan_data data; + int err; + + data.lnum = -1; + err = ubifs_lpt_scan_nolock(c, -1, c->lscan_lnum, scan_for_idx_cb, + &data); + if (err) + return ERR_PTR(err); + ubifs_assert(c, data.lnum >= c->main_first && data.lnum < c->leb_cnt); + c->lscan_lnum = data.lnum; + lprops = ubifs_lpt_lookup_dirty(c, data.lnum); + if (IS_ERR(lprops)) + return lprops; + ubifs_assert(c, lprops->lnum == data.lnum); + ubifs_assert(c, lprops->free + lprops->dirty == c->leb_size); + ubifs_assert(c, !(lprops->flags & LPROPS_TAKEN)); + ubifs_assert(c, !(lprops->flags & LPROPS_INDEX)); + return lprops; +} + +/** + * ubifs_find_free_leb_for_idx - find a free LEB for the index. + * @c: the UBIFS file-system description object + * + * This function looks for a free LEB and returns that LEB number. The returned + * LEB is marked as "taken", "index". + * + * Only empty LEBs are allocated. This is for two reasons. First, the commit + * calculates the number of LEBs to allocate based on the assumption that they + * will be empty. Secondly, free space at the end of an index LEB is not + * guaranteed to be empty because it may have been used by the in-the-gaps + * method prior to an unclean unmount. + * + * If no LEB is found %-ENOSPC is returned. For other failures another negative + * error code is returned. + */ +int ubifs_find_free_leb_for_idx(struct ubifs_info *c) +{ + const struct ubifs_lprops *lprops; + int lnum = -1, err, flags; + + ubifs_get_lprops(c); + + lprops = ubifs_fast_find_empty(c); + if (!lprops) { + lprops = ubifs_fast_find_freeable(c); + if (!lprops) { + /* + * The first condition means the following: go scan the + * LPT if there are uncategorized lprops, which means + * there may be freeable LEBs there (UBIFS does not + * store the information about freeable LEBs in the + * master node). + */ + if (c->in_a_category_cnt != c->main_lebs || + c->lst.empty_lebs - c->lst.taken_empty_lebs > 0) { + ubifs_assert(c, c->freeable_cnt == 0); + lprops = scan_for_leb_for_idx(c); + if (IS_ERR(lprops)) { + err = PTR_ERR(lprops); + goto out; + } + } + } + } + + if (!lprops) { + err = -ENOSPC; + goto out; + } + + lnum = lprops->lnum; + + dbg_find("found LEB %d, free %d, dirty %d, flags %#x", + lnum, lprops->free, lprops->dirty, lprops->flags); + + flags = lprops->flags | LPROPS_TAKEN | LPROPS_INDEX; + lprops = ubifs_change_lp(c, lprops, c->leb_size, 0, flags, 0); + if (IS_ERR(lprops)) { + err = PTR_ERR(lprops); + goto out; + } + + ubifs_release_lprops(c); + + /* + * Ensure that empty LEBs have been unmapped. They may not have been, + * for example, because of an unclean unmount. Also LEBs that were + * freeable LEBs (free + dirty == leb_size) will not have been unmapped. + */ + err = ubifs_leb_unmap(c, lnum); + if (err) { + ubifs_change_one_lp(c, lnum, LPROPS_NC, LPROPS_NC, 0, + LPROPS_TAKEN | LPROPS_INDEX, 0); + return err; + } + + return lnum; + +out: + ubifs_release_lprops(c); + return err; +} + +static int cmp_dirty_idx(const void *a, const void *b) +{ + const struct ubifs_lprops *lpa = *(const struct ubifs_lprops **)a; + const struct ubifs_lprops *lpb = *(const struct ubifs_lprops **)b; + + return lpa->dirty + lpa->free - lpb->dirty - lpb->free; +} + +/** + * ubifs_save_dirty_idx_lnums - save an array of the most dirty index LEB nos. + * @c: the UBIFS file-system description object + * + * This function is called each commit to create an array of LEB numbers of + * dirty index LEBs sorted in order of dirty and free space. This is used by + * the in-the-gaps method of TNC commit. + */ +int ubifs_save_dirty_idx_lnums(struct ubifs_info *c) +{ + int i; + + ubifs_get_lprops(c); + /* Copy the LPROPS_DIRTY_IDX heap */ + c->dirty_idx.cnt = c->lpt_heap[LPROPS_DIRTY_IDX - 1].cnt; + memcpy(c->dirty_idx.arr, c->lpt_heap[LPROPS_DIRTY_IDX - 1].arr, + sizeof(void *) * c->dirty_idx.cnt); + /* Sort it so that the dirtiest is now at the end */ + sort(c->dirty_idx.arr, c->dirty_idx.cnt, sizeof(void *), + cmp_dirty_idx, NULL); + dbg_find("found %d dirty index LEBs", c->dirty_idx.cnt); + if (c->dirty_idx.cnt) + dbg_find("dirtiest index LEB is %d with dirty %d and free %d", + c->dirty_idx.arr[c->dirty_idx.cnt - 1]->lnum, + c->dirty_idx.arr[c->dirty_idx.cnt - 1]->dirty, + c->dirty_idx.arr[c->dirty_idx.cnt - 1]->free); + /* Replace the lprops pointers with LEB numbers */ + for (i = 0; i < c->dirty_idx.cnt; i++) + c->dirty_idx.arr[i] = (void *)(size_t)c->dirty_idx.arr[i]->lnum; + ubifs_release_lprops(c); + return 0; +} + +/** + * scan_dirty_idx_cb - callback used by the scan for a dirty index LEB. + * @c: the UBIFS file-system description object + * @lprops: LEB properties to scan + * @in_tree: whether the LEB properties are in main memory + * @arg: information passed to and from the caller of the scan + * + * This function returns a code that indicates whether the scan should continue + * (%LPT_SCAN_CONTINUE), whether the LEB properties should be added to the tree + * in main memory (%LPT_SCAN_ADD), or whether the scan should stop + * (%LPT_SCAN_STOP). + */ +static int scan_dirty_idx_cb(struct ubifs_info *c, + const struct ubifs_lprops *lprops, int in_tree, + void *arg) +{ + struct scan_data *data = arg; + int ret = LPT_SCAN_CONTINUE; + + /* Exclude LEBs that are currently in use */ + if (lprops->flags & LPROPS_TAKEN) + return LPT_SCAN_CONTINUE; + /* Determine whether to add these LEB properties to the tree */ + if (!in_tree && valuable(c, lprops)) + ret |= LPT_SCAN_ADD; + /* Exclude non-index LEBs */ + if (!(lprops->flags & LPROPS_INDEX)) + return ret; + /* Exclude LEBs with too little space */ + if (lprops->free + lprops->dirty < c->min_idx_node_sz) + return ret; + /* Finally we found space */ + data->lnum = lprops->lnum; + return LPT_SCAN_ADD | LPT_SCAN_STOP; +} + +/** + * find_dirty_idx_leb - find a dirty index LEB. + * @c: the UBIFS file-system description object + * + * This function returns LEB number upon success and a negative error code upon + * failure. In particular, -ENOSPC is returned if a dirty index LEB is not + * found. + * + * Note that this function scans the entire LPT but it is called very rarely. + */ +static int find_dirty_idx_leb(struct ubifs_info *c) +{ + const struct ubifs_lprops *lprops; + struct ubifs_lpt_heap *heap; + struct scan_data data; + int err, i, ret; + + /* Check all structures in memory first */ + data.lnum = -1; + heap = &c->lpt_heap[LPROPS_DIRTY_IDX - 1]; + for (i = 0; i < heap->cnt; i++) { + lprops = heap->arr[i]; + ret = scan_dirty_idx_cb(c, lprops, 1, &data); + if (ret & LPT_SCAN_STOP) + goto found; + } + list_for_each_entry(lprops, &c->frdi_idx_list, list) { + ret = scan_dirty_idx_cb(c, lprops, 1, &data); + if (ret & LPT_SCAN_STOP) + goto found; + } + list_for_each_entry(lprops, &c->uncat_list, list) { + ret = scan_dirty_idx_cb(c, lprops, 1, &data); + if (ret & LPT_SCAN_STOP) + goto found; + } + if (c->pnodes_have >= c->pnode_cnt) + /* All pnodes are in memory, so skip scan */ + return -ENOSPC; + err = ubifs_lpt_scan_nolock(c, -1, c->lscan_lnum, scan_dirty_idx_cb, + &data); + if (err) + return err; +found: + ubifs_assert(c, data.lnum >= c->main_first && data.lnum < c->leb_cnt); + c->lscan_lnum = data.lnum; + lprops = ubifs_lpt_lookup_dirty(c, data.lnum); + if (IS_ERR(lprops)) + return PTR_ERR(lprops); + ubifs_assert(c, lprops->lnum == data.lnum); + ubifs_assert(c, lprops->free + lprops->dirty >= c->min_idx_node_sz); + ubifs_assert(c, !(lprops->flags & LPROPS_TAKEN)); + ubifs_assert(c, (lprops->flags & LPROPS_INDEX)); + + dbg_find("found dirty LEB %d, free %d, dirty %d, flags %#x", + lprops->lnum, lprops->free, lprops->dirty, lprops->flags); + + lprops = ubifs_change_lp(c, lprops, LPROPS_NC, LPROPS_NC, + lprops->flags | LPROPS_TAKEN, 0); + if (IS_ERR(lprops)) + return PTR_ERR(lprops); + + return lprops->lnum; +} + +/** + * get_idx_gc_leb - try to get a LEB number from trivial GC. + * @c: the UBIFS file-system description object + */ +static int get_idx_gc_leb(struct ubifs_info *c) +{ + const struct ubifs_lprops *lp; + int err, lnum; + + err = ubifs_get_idx_gc_leb(c); + if (err < 0) + return err; + lnum = err; + /* + * The LEB was due to be unmapped after the commit but + * it is needed now for this commit. + */ + lp = ubifs_lpt_lookup_dirty(c, lnum); + if (IS_ERR(lp)) + return PTR_ERR(lp); + lp = ubifs_change_lp(c, lp, LPROPS_NC, LPROPS_NC, + lp->flags | LPROPS_INDEX, -1); + if (IS_ERR(lp)) + return PTR_ERR(lp); + dbg_find("LEB %d, dirty %d and free %d flags %#x", + lp->lnum, lp->dirty, lp->free, lp->flags); + return lnum; +} + +/** + * find_dirtiest_idx_leb - find dirtiest index LEB from dirtiest array. + * @c: the UBIFS file-system description object + */ +static int find_dirtiest_idx_leb(struct ubifs_info *c) +{ + const struct ubifs_lprops *lp; + int lnum; + + while (1) { + if (!c->dirty_idx.cnt) + return -ENOSPC; + /* The lprops pointers were replaced by LEB numbers */ + lnum = (size_t)c->dirty_idx.arr[--c->dirty_idx.cnt]; + lp = ubifs_lpt_lookup(c, lnum); + if (IS_ERR(lp)) + return PTR_ERR(lp); + if ((lp->flags & LPROPS_TAKEN) || !(lp->flags & LPROPS_INDEX)) + continue; + lp = ubifs_change_lp(c, lp, LPROPS_NC, LPROPS_NC, + lp->flags | LPROPS_TAKEN, 0); + if (IS_ERR(lp)) + return PTR_ERR(lp); + break; + } + dbg_find("LEB %d, dirty %d and free %d flags %#x", lp->lnum, lp->dirty, + lp->free, lp->flags); + ubifs_assert(c, lp->flags & LPROPS_TAKEN); + ubifs_assert(c, lp->flags & LPROPS_INDEX); + return lnum; +} + +/** + * ubifs_find_dirty_idx_leb - try to find dirtiest index LEB as at last commit. + * @c: the UBIFS file-system description object + * + * This function attempts to find an untaken index LEB with the most free and + * dirty space that can be used without overwriting index nodes that were in the + * last index committed. + */ +int ubifs_find_dirty_idx_leb(struct ubifs_info *c) +{ + int err; + + ubifs_get_lprops(c); + + /* + * We made an array of the dirtiest index LEB numbers as at the start of + * last commit. Try that array first. + */ + err = find_dirtiest_idx_leb(c); + + /* Next try scanning the entire LPT */ + if (err == -ENOSPC) + err = find_dirty_idx_leb(c); + + /* Finally take any index LEBs awaiting trivial GC */ + if (err == -ENOSPC) + err = get_idx_gc_leb(c); + + ubifs_release_lprops(c); + return err; +} diff --git a/ubifs-utils/libubifs/gc.c b/ubifs-utils/libubifs/gc.c new file mode 100644 index 0000000..c359535 --- /dev/null +++ b/ubifs-utils/libubifs/gc.c @@ -0,0 +1,1021 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * This file is part of UBIFS. + * + * Copyright (C) 2006-2008 Nokia Corporation. + * + * Authors: Adrian Hunter + * Artem Bityutskiy (Битюцкий Артём) + */ + +/* + * This file implements garbage collection. The procedure for garbage collection + * is different depending on whether a LEB as an index LEB (contains index + * nodes) or not. For non-index LEBs, garbage collection finds a LEB which + * contains a lot of dirty space (obsolete nodes), and copies the non-obsolete + * nodes to the journal, at which point the garbage-collected LEB is free to be + * reused. For index LEBs, garbage collection marks the non-obsolete index nodes + * dirty in the TNC, and after the next commit, the garbage-collected LEB is + * to be reused. Garbage collection will cause the number of dirty index nodes + * to grow, however sufficient space is reserved for the index to ensure the + * commit will never run out of space. + * + * Notes about dead watermark. At current UBIFS implementation we assume that + * LEBs which have less than @c->dead_wm bytes of free + dirty space are full + * and not worth garbage-collecting. The dead watermark is one min. I/O unit + * size, or min. UBIFS node size, depending on what is greater. Indeed, UBIFS + * Garbage Collector has to synchronize the GC head's write buffer before + * returning, so this is about wasting one min. I/O unit. However, UBIFS GC can + * actually reclaim even very small pieces of dirty space by garbage collecting + * enough dirty LEBs, but we do not bother doing this at this implementation. + * + * Notes about dark watermark. The results of GC work depends on how big are + * the UBIFS nodes GC deals with. Large nodes make GC waste more space. Indeed, + * if GC move data from LEB A to LEB B and nodes in LEB A are large, GC would + * have to waste large pieces of free space at the end of LEB B, because nodes + * from LEB A would not fit. And the worst situation is when all nodes are of + * maximum size. So dark watermark is the amount of free + dirty space in LEB + * which are guaranteed to be reclaimable. If LEB has less space, the GC might + * be unable to reclaim it. So, LEBs with free + dirty greater than dark + * watermark are "good" LEBs from GC's point of view. The other LEBs are not so + * good, and GC takes extra care when moving them. + */ + +#include "linux_err.h" +#include "bitops.h" +#include "kmem.h" +#include "ubifs.h" +#include "defs.h" +#include "debug.h" +#include "key.h" +#include "misc.h" + +/* + * GC may need to move more than one LEB to make progress. The below constants + * define "soft" and "hard" limits on the number of LEBs the garbage collector + * may move. + */ +#define SOFT_LEBS_LIMIT 4 +#define HARD_LEBS_LIMIT 32 + +/** + * switch_gc_head - switch the garbage collection journal head. + * @c: UBIFS file-system description object + * + * This function switch the GC head to the next LEB which is reserved in + * @c->gc_lnum. Returns %0 in case of success, %-EAGAIN if commit is required, + * and other negative error code in case of failures. + */ +static int switch_gc_head(struct ubifs_info *c) +{ + int err, gc_lnum = c->gc_lnum; + struct ubifs_wbuf *wbuf = &c->jheads[GCHD].wbuf; + + ubifs_assert(c, gc_lnum != -1); + dbg_gc("switch GC head from LEB %d:%d to LEB %d (waste %d bytes)", + wbuf->lnum, wbuf->offs + wbuf->used, gc_lnum, + c->leb_size - wbuf->offs - wbuf->used); + + err = ubifs_wbuf_sync_nolock(wbuf); + if (err) + return err; + + /* + * The GC write-buffer was synchronized, we may safely unmap + * 'c->gc_lnum'. + */ + err = ubifs_leb_unmap(c, gc_lnum); + if (err) + return err; + + err = ubifs_add_bud_to_log(c, GCHD, gc_lnum, 0); + if (err) + return err; + + c->gc_lnum = -1; + err = ubifs_wbuf_seek_nolock(wbuf, gc_lnum, 0); + return err; +} + +/** + * data_nodes_cmp - compare 2 data nodes. + * @priv: UBIFS file-system description object + * @a: first data node + * @b: second data node + * + * This function compares data nodes @a and @b. Returns %1 if @a has greater + * inode or block number, and %-1 otherwise. + */ +static int data_nodes_cmp(void *priv, const struct list_head *a, + const struct list_head *b) +{ + ino_t inuma, inumb; + struct ubifs_info *c = priv; + struct ubifs_scan_node *sa, *sb; + + cond_resched(); + if (a == b) + return 0; + + sa = list_entry(a, struct ubifs_scan_node, list); + sb = list_entry(b, struct ubifs_scan_node, list); + + ubifs_assert(c, key_type(c, &sa->key) == UBIFS_DATA_KEY); + ubifs_assert(c, key_type(c, &sb->key) == UBIFS_DATA_KEY); + ubifs_assert(c, sa->type == UBIFS_DATA_NODE); + ubifs_assert(c, sb->type == UBIFS_DATA_NODE); + + inuma = key_inum(c, &sa->key); + inumb = key_inum(c, &sb->key); + + if (inuma == inumb) { + unsigned int blka = key_block(c, &sa->key); + unsigned int blkb = key_block(c, &sb->key); + + if (blka <= blkb) + return -1; + } else if (inuma <= inumb) + return -1; + + return 1; +} + +/* + * nondata_nodes_cmp - compare 2 non-data nodes. + * @priv: UBIFS file-system description object + * @a: first node + * @a: second node + * + * This function compares nodes @a and @b. It makes sure that inode nodes go + * first and sorted by length in descending order. Directory entry nodes go + * after inode nodes and are sorted in ascending hash valuer order. + */ +static int nondata_nodes_cmp(void *priv, const struct list_head *a, + const struct list_head *b) +{ + ino_t inuma, inumb; + struct ubifs_info *c = priv; + struct ubifs_scan_node *sa, *sb; + + cond_resched(); + if (a == b) + return 0; + + sa = list_entry(a, struct ubifs_scan_node, list); + sb = list_entry(b, struct ubifs_scan_node, list); + + ubifs_assert(c, key_type(c, &sa->key) != UBIFS_DATA_KEY && + key_type(c, &sb->key) != UBIFS_DATA_KEY); + ubifs_assert(c, sa->type != UBIFS_DATA_NODE && + sb->type != UBIFS_DATA_NODE); + + /* Inodes go before directory entries */ + if (sa->type == UBIFS_INO_NODE) { + if (sb->type == UBIFS_INO_NODE) + return sb->len - sa->len; + return -1; + } + if (sb->type == UBIFS_INO_NODE) + return 1; + + ubifs_assert(c, key_type(c, &sa->key) == UBIFS_DENT_KEY || + key_type(c, &sa->key) == UBIFS_XENT_KEY); + ubifs_assert(c, key_type(c, &sb->key) == UBIFS_DENT_KEY || + key_type(c, &sb->key) == UBIFS_XENT_KEY); + ubifs_assert(c, sa->type == UBIFS_DENT_NODE || + sa->type == UBIFS_XENT_NODE); + ubifs_assert(c, sb->type == UBIFS_DENT_NODE || + sb->type == UBIFS_XENT_NODE); + + inuma = key_inum(c, &sa->key); + inumb = key_inum(c, &sb->key); + + if (inuma == inumb) { + uint32_t hasha = key_hash(c, &sa->key); + uint32_t hashb = key_hash(c, &sb->key); + + if (hasha <= hashb) + return -1; + } else if (inuma <= inumb) + return -1; + + return 1; +} + +/** + * sort_nodes - sort nodes for GC. + * @c: UBIFS file-system description object + * @sleb: describes nodes to sort and contains the result on exit + * @nondata: contains non-data nodes on exit + * @min: minimum node size is returned here + * + * This function sorts the list of inodes to garbage collect. First of all, it + * kills obsolete nodes and separates data and non-data nodes to the + * @sleb->nodes and @nondata lists correspondingly. + * + * Data nodes are then sorted in block number order - this is important for + * bulk-read; data nodes with lower inode number go before data nodes with + * higher inode number, and data nodes with lower block number go before data + * nodes with higher block number; + * + * Non-data nodes are sorted as follows. + * o First go inode nodes - they are sorted in descending length order. + * o Then go directory entry nodes - they are sorted in hash order, which + * should supposedly optimize 'readdir()'. Direntry nodes with lower parent + * inode number go before direntry nodes with higher parent inode number, + * and direntry nodes with lower name hash values go before direntry nodes + * with higher name hash values. + * + * This function returns zero in case of success and a negative error code in + * case of failure. + */ +static int sort_nodes(struct ubifs_info *c, struct ubifs_scan_leb *sleb, + struct list_head *nondata, int *min) +{ + int err; + struct ubifs_scan_node *snod, *tmp; + + *min = INT_MAX; + + /* Separate data nodes and non-data nodes */ + list_for_each_entry_safe(snod, tmp, &sleb->nodes, list) { + ubifs_assert(c, snod->type == UBIFS_INO_NODE || + snod->type == UBIFS_DATA_NODE || + snod->type == UBIFS_DENT_NODE || + snod->type == UBIFS_XENT_NODE || + snod->type == UBIFS_TRUN_NODE || + snod->type == UBIFS_AUTH_NODE); + + if (snod->type != UBIFS_INO_NODE && + snod->type != UBIFS_DATA_NODE && + snod->type != UBIFS_DENT_NODE && + snod->type != UBIFS_XENT_NODE) { + /* Probably truncation node, zap it */ + list_del(&snod->list); + kfree(snod); + continue; + } + + ubifs_assert(c, key_type(c, &snod->key) == UBIFS_DATA_KEY || + key_type(c, &snod->key) == UBIFS_INO_KEY || + key_type(c, &snod->key) == UBIFS_DENT_KEY || + key_type(c, &snod->key) == UBIFS_XENT_KEY); + + err = ubifs_tnc_has_node(c, &snod->key, 0, sleb->lnum, + snod->offs, 0); + if (err < 0) + return err; + + if (!err) { + /* The node is obsolete, remove it from the list */ + list_del(&snod->list); + kfree(snod); + continue; + } + + if (snod->len < *min) + *min = snod->len; + + if (key_type(c, &snod->key) != UBIFS_DATA_KEY) + list_move_tail(&snod->list, nondata); + } + + /* Sort data and non-data nodes */ + list_sort(c, &sleb->nodes, &data_nodes_cmp); + list_sort(c, nondata, &nondata_nodes_cmp); + + err = dbg_check_data_nodes_order(c, &sleb->nodes); + if (err) + return err; + err = dbg_check_nondata_nodes_order(c, nondata); + if (err) + return err; + return 0; +} + +/** + * move_node - move a node. + * @c: UBIFS file-system description object + * @sleb: describes the LEB to move nodes from + * @snod: the mode to move + * @wbuf: write-buffer to move node to + * + * This function moves node @snod to @wbuf, changes TNC correspondingly, and + * destroys @snod. Returns zero in case of success and a negative error code in + * case of failure. + */ +static int move_node(struct ubifs_info *c, struct ubifs_scan_leb *sleb, + struct ubifs_scan_node *snod, struct ubifs_wbuf *wbuf) +{ + int err, new_lnum = wbuf->lnum, new_offs = wbuf->offs + wbuf->used; + + cond_resched(); + err = ubifs_wbuf_write_nolock(wbuf, snod->node, snod->len); + if (err) + return err; + + err = ubifs_tnc_replace(c, &snod->key, sleb->lnum, + snod->offs, new_lnum, new_offs, + snod->len); + list_del(&snod->list); + kfree(snod); + return err; +} + +/** + * move_nodes - move nodes. + * @c: UBIFS file-system description object + * @sleb: describes the LEB to move nodes from + * + * This function moves valid nodes from data LEB described by @sleb to the GC + * journal head. This function returns zero in case of success, %-EAGAIN if + * commit is required, and other negative error codes in case of other + * failures. + */ +static int move_nodes(struct ubifs_info *c, struct ubifs_scan_leb *sleb) +{ + int err, min; + LIST_HEAD(nondata); + struct ubifs_wbuf *wbuf = &c->jheads[GCHD].wbuf; + + if (wbuf->lnum == -1) { + /* + * The GC journal head is not set, because it is the first GC + * invocation since mount. + */ + err = switch_gc_head(c); + if (err) + return err; + } + + err = sort_nodes(c, sleb, &nondata, &min); + if (err) + goto out; + + /* Write nodes to their new location. Use the first-fit strategy */ + while (1) { + int avail, moved = 0; + struct ubifs_scan_node *snod, *tmp; + + /* Move data nodes */ + list_for_each_entry_safe(snod, tmp, &sleb->nodes, list) { + avail = c->leb_size - wbuf->offs - wbuf->used - + ubifs_auth_node_sz(c); + if (snod->len > avail) + /* + * Do not skip data nodes in order to optimize + * bulk-read. + */ + break; + + err = ubifs_shash_update(c, c->jheads[GCHD].log_hash, + snod->node, snod->len); + if (err) + goto out; + + err = move_node(c, sleb, snod, wbuf); + if (err) + goto out; + moved = 1; + } + + /* Move non-data nodes */ + list_for_each_entry_safe(snod, tmp, &nondata, list) { + avail = c->leb_size - wbuf->offs - wbuf->used - + ubifs_auth_node_sz(c); + if (avail < min) + break; + + if (snod->len > avail) { + /* + * Keep going only if this is an inode with + * some data. Otherwise stop and switch the GC + * head. IOW, we assume that data-less inode + * nodes and direntry nodes are roughly of the + * same size. + */ + if (key_type(c, &snod->key) == UBIFS_DENT_KEY || + snod->len == UBIFS_INO_NODE_SZ) + break; + continue; + } + + err = ubifs_shash_update(c, c->jheads[GCHD].log_hash, + snod->node, snod->len); + if (err) + goto out; + + err = move_node(c, sleb, snod, wbuf); + if (err) + goto out; + moved = 1; + } + + if (ubifs_authenticated(c) && moved) { + struct ubifs_auth_node *auth; + + auth = kmalloc(ubifs_auth_node_sz(c), GFP_NOFS); + if (!auth) { + err = -ENOMEM; + goto out; + } + + err = ubifs_prepare_auth_node(c, auth, + c->jheads[GCHD].log_hash); + if (err) { + kfree(auth); + goto out; + } + + err = ubifs_wbuf_write_nolock(wbuf, auth, + ubifs_auth_node_sz(c)); + if (err) { + kfree(auth); + goto out; + } + + ubifs_add_dirt(c, wbuf->lnum, ubifs_auth_node_sz(c)); + } + + if (list_empty(&sleb->nodes) && list_empty(&nondata)) + break; + + /* + * Waste the rest of the space in the LEB and switch to the + * next LEB. + */ + err = switch_gc_head(c); + if (err) + goto out; + } + + return 0; + +out: + list_splice_tail(&nondata, &sleb->nodes); + return err; +} + +/** + * gc_sync_wbufs - sync write-buffers for GC. + * @c: UBIFS file-system description object + * + * We must guarantee that obsoleting nodes are on flash. Unfortunately they may + * be in a write-buffer instead. That is, a node could be written to a + * write-buffer, obsoleting another node in a LEB that is GC'd. If that LEB is + * erased before the write-buffer is sync'd and then there is an unclean + * unmount, then an existing node is lost. To avoid this, we sync all + * write-buffers. + * + * This function returns %0 on success or a negative error code on failure. + */ +static int gc_sync_wbufs(struct ubifs_info *c) +{ + int err, i; + + for (i = 0; i < c->jhead_cnt; i++) { + if (i == GCHD) + continue; + err = ubifs_wbuf_sync(&c->jheads[i].wbuf); + if (err) + return err; + } + return 0; +} + +/** + * ubifs_garbage_collect_leb - garbage-collect a logical eraseblock. + * @c: UBIFS file-system description object + * @lp: describes the LEB to garbage collect + * + * This function garbage-collects an LEB and returns one of the @LEB_FREED, + * @LEB_RETAINED, etc positive codes in case of success, %-EAGAIN if commit is + * required, and other negative error codes in case of failures. + */ +int ubifs_garbage_collect_leb(struct ubifs_info *c, struct ubifs_lprops *lp) +{ + struct ubifs_scan_leb *sleb; + struct ubifs_scan_node *snod; + struct ubifs_wbuf *wbuf = &c->jheads[GCHD].wbuf; + int err = 0, lnum = lp->lnum; + + ubifs_assert(c, c->gc_lnum != -1 || wbuf->offs + wbuf->used == 0 || + c->need_recovery); + ubifs_assert(c, c->gc_lnum != lnum); + ubifs_assert(c, wbuf->lnum != lnum); + + if (lp->free + lp->dirty == c->leb_size) { + /* Special case - a free LEB */ + dbg_gc("LEB %d is free, return it", lp->lnum); + ubifs_assert(c, !(lp->flags & LPROPS_INDEX)); + + if (lp->free != c->leb_size) { + /* + * Write buffers must be sync'd before unmapping + * freeable LEBs, because one of them may contain data + * which obsoletes something in 'lp->lnum'. + */ + err = gc_sync_wbufs(c); + if (err) + return err; + err = ubifs_change_one_lp(c, lp->lnum, c->leb_size, + 0, 0, 0, 0); + if (err) + return err; + } + err = ubifs_leb_unmap(c, lp->lnum); + if (err) + return err; + + if (c->gc_lnum == -1) { + c->gc_lnum = lnum; + return LEB_RETAINED; + } + + return LEB_FREED; + } + + /* + * We scan the entire LEB even though we only really need to scan up to + * (c->leb_size - lp->free). + */ + sleb = ubifs_scan(c, lnum, 0, c->sbuf, 0); + if (IS_ERR(sleb)) + return PTR_ERR(sleb); + + ubifs_assert(c, !list_empty(&sleb->nodes)); + snod = list_entry(sleb->nodes.next, struct ubifs_scan_node, list); + + if (snod->type == UBIFS_IDX_NODE) { + struct ubifs_gced_idx_leb *idx_gc; + + dbg_gc("indexing LEB %d (free %d, dirty %d)", + lnum, lp->free, lp->dirty); + list_for_each_entry(snod, &sleb->nodes, list) { + struct ubifs_idx_node *idx = snod->node; + int level = le16_to_cpu(idx->level); + + ubifs_assert(c, snod->type == UBIFS_IDX_NODE); + key_read(c, ubifs_idx_key(c, idx), &snod->key); + err = ubifs_dirty_idx_node(c, &snod->key, level, lnum, + snod->offs); + if (err) + goto out; + } + + idx_gc = kmalloc(sizeof(struct ubifs_gced_idx_leb), GFP_NOFS); + if (!idx_gc) { + err = -ENOMEM; + goto out; + } + + idx_gc->lnum = lnum; + idx_gc->unmap = 0; + list_add(&idx_gc->list, &c->idx_gc); + + /* + * Don't release the LEB until after the next commit, because + * it may contain data which is needed for recovery. So + * although we freed this LEB, it will become usable only after + * the commit. + */ + err = ubifs_change_one_lp(c, lnum, c->leb_size, 0, 0, + LPROPS_INDEX, 1); + if (err) + goto out; + err = LEB_FREED_IDX; + } else { + dbg_gc("data LEB %d (free %d, dirty %d)", + lnum, lp->free, lp->dirty); + + err = move_nodes(c, sleb); + if (err) + goto out_inc_seq; + + err = gc_sync_wbufs(c); + if (err) + goto out_inc_seq; + + err = ubifs_change_one_lp(c, lnum, c->leb_size, 0, 0, 0, 0); + if (err) + goto out_inc_seq; + + /* Allow for races with TNC */ + c->gced_lnum = lnum; + smp_wmb(); + c->gc_seq += 1; + smp_wmb(); + + if (c->gc_lnum == -1) { + c->gc_lnum = lnum; + err = LEB_RETAINED; + } else { + err = ubifs_wbuf_sync_nolock(wbuf); + if (err) + goto out; + + err = ubifs_leb_unmap(c, lnum); + if (err) + goto out; + + err = LEB_FREED; + } + } + +out: + ubifs_scan_destroy(sleb); + return err; + +out_inc_seq: + /* We may have moved at least some nodes so allow for races with TNC */ + c->gced_lnum = lnum; + smp_wmb(); + c->gc_seq += 1; + smp_wmb(); + goto out; +} + +/** + * ubifs_garbage_collect - UBIFS garbage collector. + * @c: UBIFS file-system description object + * @anyway: do GC even if there are free LEBs + * + * This function does out-of-place garbage collection. The return codes are: + * o positive LEB number if the LEB has been freed and may be used; + * o %-EAGAIN if the caller has to run commit; + * o %-ENOSPC if GC failed to make any progress; + * o other negative error codes in case of other errors. + * + * Garbage collector writes data to the journal when GC'ing data LEBs, and just + * marking indexing nodes dirty when GC'ing indexing LEBs. Thus, at some point + * commit may be required. But commit cannot be run from inside GC, because the + * caller might be holding the commit lock, so %-EAGAIN is returned instead; + * And this error code means that the caller has to run commit, and re-run GC + * if there is still no free space. + * + * There are many reasons why this function may return %-EAGAIN: + * o the log is full and there is no space to write an LEB reference for + * @c->gc_lnum; + * o the journal is too large and exceeds size limitations; + * o GC moved indexing LEBs, but they can be used only after the commit; + * o the shrinker fails to find clean znodes to free and requests the commit; + * o etc. + * + * Note, if the file-system is close to be full, this function may return + * %-EAGAIN infinitely, so the caller has to limit amount of re-invocations of + * the function. E.g., this happens if the limits on the journal size are too + * tough and GC writes too much to the journal before an LEB is freed. This + * might also mean that the journal is too large, and the TNC becomes to big, + * so that the shrinker is constantly called, finds not clean znodes to free, + * and requests commit. Well, this may also happen if the journal is all right, + * but another kernel process consumes too much memory. Anyway, infinite + * %-EAGAIN may happen, but in some extreme/misconfiguration cases. + */ +int ubifs_garbage_collect(struct ubifs_info *c, int anyway) +{ + int i, err, ret, min_space = c->dead_wm; + struct ubifs_lprops lp; + struct ubifs_wbuf *wbuf = &c->jheads[GCHD].wbuf; + + ubifs_assert_cmt_locked(c); + ubifs_assert(c, !c->ro_media && !c->ro_mount); + + if (ubifs_gc_should_commit(c)) + return -EAGAIN; + + mutex_lock_nested(&wbuf->io_mutex, wbuf->jhead); + + if (c->ro_error) { + ret = -EROFS; + goto out_unlock; + } + + /* We expect the write-buffer to be empty on entry */ + ubifs_assert(c, !wbuf->used); + + for (i = 0; ; i++) { + int space_before, space_after; + + /* Maybe continue after find and break before find */ + lp.lnum = -1; + + cond_resched(); + + /* Give the commit an opportunity to run */ + if (ubifs_gc_should_commit(c)) { + ret = -EAGAIN; + break; + } + + if (i > SOFT_LEBS_LIMIT && !list_empty(&c->idx_gc)) { + /* + * We've done enough iterations. Indexing LEBs were + * moved and will be available after the commit. + */ + dbg_gc("soft limit, some index LEBs GC'ed, -EAGAIN"); + ubifs_commit_required(c); + ret = -EAGAIN; + break; + } + + if (i > HARD_LEBS_LIMIT) { + /* + * We've moved too many LEBs and have not made + * progress, give up. + */ + dbg_gc("hard limit, -ENOSPC"); + ret = -ENOSPC; + break; + } + + /* + * Empty and freeable LEBs can turn up while we waited for + * the wbuf lock, or while we have been running GC. In that + * case, we should just return one of those instead of + * continuing to GC dirty LEBs. Hence we request + * 'ubifs_find_dirty_leb()' to return an empty LEB if it can. + */ + ret = ubifs_find_dirty_leb(c, &lp, min_space, anyway ? 0 : 1); + if (ret) { + if (ret == -ENOSPC) + dbg_gc("no more dirty LEBs"); + break; + } + + dbg_gc("found LEB %d: free %d, dirty %d, sum %d (min. space %d)", + lp.lnum, lp.free, lp.dirty, lp.free + lp.dirty, + min_space); + + space_before = c->leb_size - wbuf->offs - wbuf->used; + if (wbuf->lnum == -1) + space_before = 0; + + ret = ubifs_garbage_collect_leb(c, &lp); + if (ret < 0) { + if (ret == -EAGAIN) { + /* + * This is not error, so we have to return the + * LEB to lprops. But if 'ubifs_return_leb()' + * fails, its failure code is propagated to the + * caller instead of the original '-EAGAIN'. + */ + err = ubifs_return_leb(c, lp.lnum); + if (err) { + ret = err; + /* + * An LEB may always be "taken", + * so setting ubifs to read-only, + * and then executing sync wbuf will + * return -EROFS and enter the "out" + * error branch. + */ + ubifs_ro_mode(c, ret); + } + /* Maybe double return LEB if goto out */ + lp.lnum = -1; + break; + } + goto out; + } + + if (ret == LEB_FREED) { + /* An LEB has been freed and is ready for use */ + dbg_gc("LEB %d freed, return", lp.lnum); + ret = lp.lnum; + break; + } + + if (ret == LEB_FREED_IDX) { + /* + * This was an indexing LEB and it cannot be + * immediately used. And instead of requesting the + * commit straight away, we try to garbage collect some + * more. + */ + dbg_gc("indexing LEB %d freed, continue", lp.lnum); + continue; + } + + ubifs_assert(c, ret == LEB_RETAINED); + space_after = c->leb_size - wbuf->offs - wbuf->used; + dbg_gc("LEB %d retained, freed %d bytes", lp.lnum, + space_after - space_before); + + if (space_after > space_before) { + /* GC makes progress, keep working */ + min_space >>= 1; + if (min_space < c->dead_wm) + min_space = c->dead_wm; + continue; + } + + dbg_gc("did not make progress"); + + /* + * GC moved an LEB bud have not done any progress. This means + * that the previous GC head LEB contained too few free space + * and the LEB which was GC'ed contained only large nodes which + * did not fit that space. + * + * We can do 2 things: + * 1. pick another LEB in a hope it'll contain a small node + * which will fit the space we have at the end of current GC + * head LEB, but there is no guarantee, so we try this out + * unless we have already been working for too long; + * 2. request an LEB with more dirty space, which will force + * 'ubifs_find_dirty_leb()' to start scanning the lprops + * table, instead of just picking one from the heap + * (previously it already picked the dirtiest LEB). + */ + if (i < SOFT_LEBS_LIMIT) { + dbg_gc("try again"); + continue; + } + + min_space <<= 1; + if (min_space > c->dark_wm) + min_space = c->dark_wm; + dbg_gc("set min. space to %d", min_space); + } + + if (ret == -ENOSPC && !list_empty(&c->idx_gc)) { + dbg_gc("no space, some index LEBs GC'ed, -EAGAIN"); + ubifs_commit_required(c); + ret = -EAGAIN; + } + + err = ubifs_wbuf_sync_nolock(wbuf); + if (!err) + err = ubifs_leb_unmap(c, c->gc_lnum); + if (err) { + ret = err; + goto out; + } +out_unlock: + mutex_unlock(&wbuf->io_mutex); + return ret; + +out: + ubifs_assert(c, ret < 0); + ubifs_assert(c, ret != -ENOSPC && ret != -EAGAIN); + ubifs_wbuf_sync_nolock(wbuf); + ubifs_ro_mode(c, ret); + mutex_unlock(&wbuf->io_mutex); + if (lp.lnum != -1) + ubifs_return_leb(c, lp.lnum); + return ret; +} + +/** + * ubifs_gc_start_commit - garbage collection at start of commit. + * @c: UBIFS file-system description object + * + * If a LEB has only dirty and free space, then we may safely unmap it and make + * it free. Note, we cannot do this with indexing LEBs because dirty space may + * correspond index nodes that are required for recovery. In that case, the + * LEB cannot be unmapped until after the next commit. + * + * This function returns %0 upon success and a negative error code upon failure. + */ +int ubifs_gc_start_commit(struct ubifs_info *c) +{ + struct ubifs_gced_idx_leb *idx_gc; + const struct ubifs_lprops *lp; + int err = 0, flags; + + ubifs_get_lprops(c); + + /* + * Unmap (non-index) freeable LEBs. Note that recovery requires that all + * wbufs are sync'd before this, which is done in 'do_commit()'. + */ + while (1) { + lp = ubifs_fast_find_freeable(c); + if (!lp) + break; + ubifs_assert(c, !(lp->flags & LPROPS_TAKEN)); + ubifs_assert(c, !(lp->flags & LPROPS_INDEX)); + err = ubifs_leb_unmap(c, lp->lnum); + if (err) + goto out; + lp = ubifs_change_lp(c, lp, c->leb_size, 0, lp->flags, 0); + if (IS_ERR(lp)) { + err = PTR_ERR(lp); + goto out; + } + ubifs_assert(c, !(lp->flags & LPROPS_TAKEN)); + ubifs_assert(c, !(lp->flags & LPROPS_INDEX)); + } + + /* Mark GC'd index LEBs OK to unmap after this commit finishes */ + list_for_each_entry(idx_gc, &c->idx_gc, list) + idx_gc->unmap = 1; + + /* Record index freeable LEBs for unmapping after commit */ + while (1) { + lp = ubifs_fast_find_frdi_idx(c); + if (IS_ERR(lp)) { + err = PTR_ERR(lp); + goto out; + } + if (!lp) + break; + idx_gc = kmalloc(sizeof(struct ubifs_gced_idx_leb), GFP_NOFS); + if (!idx_gc) { + err = -ENOMEM; + goto out; + } + ubifs_assert(c, !(lp->flags & LPROPS_TAKEN)); + ubifs_assert(c, lp->flags & LPROPS_INDEX); + /* Don't release the LEB until after the next commit */ + flags = (lp->flags | LPROPS_TAKEN) ^ LPROPS_INDEX; + lp = ubifs_change_lp(c, lp, c->leb_size, 0, flags, 1); + if (IS_ERR(lp)) { + err = PTR_ERR(lp); + kfree(idx_gc); + goto out; + } + ubifs_assert(c, lp->flags & LPROPS_TAKEN); + ubifs_assert(c, !(lp->flags & LPROPS_INDEX)); + idx_gc->lnum = lp->lnum; + idx_gc->unmap = 1; + list_add(&idx_gc->list, &c->idx_gc); + } +out: + ubifs_release_lprops(c); + return err; +} + +/** + * ubifs_gc_end_commit - garbage collection at end of commit. + * @c: UBIFS file-system description object + * + * This function completes out-of-place garbage collection of index LEBs. + */ +int ubifs_gc_end_commit(struct ubifs_info *c) +{ + struct ubifs_gced_idx_leb *idx_gc, *tmp; + struct ubifs_wbuf *wbuf; + int err = 0; + + wbuf = &c->jheads[GCHD].wbuf; + mutex_lock_nested(&wbuf->io_mutex, wbuf->jhead); + list_for_each_entry_safe(idx_gc, tmp, &c->idx_gc, list) + if (idx_gc->unmap) { + dbg_gc("LEB %d", idx_gc->lnum); + err = ubifs_leb_unmap(c, idx_gc->lnum); + if (err) + goto out; + err = ubifs_change_one_lp(c, idx_gc->lnum, LPROPS_NC, + LPROPS_NC, 0, LPROPS_TAKEN, -1); + if (err) + goto out; + list_del(&idx_gc->list); + kfree(idx_gc); + } +out: + mutex_unlock(&wbuf->io_mutex); + return err; +} + +/** + * ubifs_destroy_idx_gc - destroy idx_gc list. + * @c: UBIFS file-system description object + * + * This function destroys the @c->idx_gc list. It is called when unmounting + * so locks are not needed. Returns zero in case of success and a negative + * error code in case of failure. + */ +void ubifs_destroy_idx_gc(struct ubifs_info *c) +{ + while (!list_empty(&c->idx_gc)) { + struct ubifs_gced_idx_leb *idx_gc; + + idx_gc = list_entry(c->idx_gc.next, struct ubifs_gced_idx_leb, + list); + c->idx_gc_cnt -= 1; + list_del(&idx_gc->list); + kfree(idx_gc); + } +} + +/** + * ubifs_get_idx_gc_leb - get a LEB from GC'd index LEB list. + * @c: UBIFS file-system description object + * + * Called during start commit so locks are not needed. + */ +int ubifs_get_idx_gc_leb(struct ubifs_info *c) +{ + struct ubifs_gced_idx_leb *idx_gc; + int lnum; + + if (list_empty(&c->idx_gc)) + return -ENOSPC; + idx_gc = list_entry(c->idx_gc.next, struct ubifs_gced_idx_leb, list); + lnum = idx_gc->lnum; + /* c->idx_gc_cnt is updated by the caller when lprops are updated */ + list_del(&idx_gc->list); + kfree(idx_gc); + return lnum; +} diff --git a/ubifs-utils/libubifs/io.c b/ubifs-utils/libubifs/io.c new file mode 100644 index 0000000..b6fb331 --- /dev/null +++ b/ubifs-utils/libubifs/io.c @@ -0,0 +1,1088 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * This file is part of UBIFS. + * + * Copyright (C) 2006-2008 Nokia Corporation. + * Copyright (C) 2006, 2007 University of Szeged, Hungary + * + * Authors: Artem Bityutskiy (Битюцкий Артём) + * Adrian Hunter + * Zoltan Sogor + */ + +/* + * This file implements UBIFS I/O subsystem which provides various I/O-related + * helper functions (reading/writing/checking/validating nodes) and implements + * write-buffering support. Write buffers help to save space which otherwise + * would have been wasted for padding to the nearest minimal I/O unit boundary. + * Instead, data first goes to the write-buffer and is flushed when the + * buffer is full or when it is not used for some time (by timer). This is + * similar to the mechanism is used by JFFS2. + * + * UBIFS distinguishes between minimum write size (@c->min_io_size) and maximum + * write size (@c->max_write_size). The latter is the maximum amount of bytes + * the underlying flash is able to program at a time, and writing in + * @c->max_write_size units should presumably be faster. Obviously, + * @c->min_io_size <= @c->max_write_size. Write-buffers are of + * @c->max_write_size bytes in size for maximum performance. However, when a + * write-buffer is flushed, only the portion of it (aligned to @c->min_io_size + * boundary) which contains data is written, not the whole write-buffer, + * because this is more space-efficient. + * + * This optimization adds few complications to the code. Indeed, on the one + * hand, we want to write in optimal @c->max_write_size bytes chunks, which + * also means aligning writes at the @c->max_write_size bytes offsets. On the + * other hand, we do not want to waste space when synchronizing the write + * buffer, so during synchronization we writes in smaller chunks. And this makes + * the next write offset to be not aligned to @c->max_write_size bytes. So the + * have to make sure that the write-buffer offset (@wbuf->offs) becomes aligned + * to @c->max_write_size bytes again. We do this by temporarily shrinking + * write-buffer size (@wbuf->size). + * + * Write-buffers are defined by 'struct ubifs_wbuf' objects and protected by + * mutexes defined inside these objects. Since sometimes upper-level code + * has to lock the write-buffer (e.g. journal space reservation code), many + * functions related to write-buffers have "nolock" suffix which means that the + * caller has to lock the write-buffer before calling this function. + * + * UBIFS stores nodes at 64 bit-aligned addresses. If the node length is not + * aligned, UBIFS starts the next node from the aligned address, and the padded + * bytes may contain any rubbish. In other words, UBIFS does not put padding + * bytes in those small gaps. Common headers of nodes store real node lengths, + * not aligned lengths. Indexing nodes also store real lengths in branches. + * + * UBIFS uses padding when it pads to the next min. I/O unit. In this case it + * uses padding nodes or padding bytes, if the padding node does not fit. + * + * All UBIFS nodes are protected by CRC checksums and UBIFS checks CRC when + * they are read from the flash media. + */ + +#include "kmem.h" +#include "crc32.h" +#include "ubifs.h" +#include "defs.h" +#include "debug.h" + +/** + * ubifs_ro_mode - switch UBIFS to read read-only mode. + * @c: UBIFS file-system description object + * @err: error code which is the reason of switching to R/O mode + */ +void ubifs_ro_mode(struct ubifs_info *c, int err) +{ + if (!c->ro_error) { + c->ro_error = 1; + c->no_chk_data_crc = 0; + ubifs_warn(c, "switched to read-only mode, error %d", err); + dump_stack(); + } +} + +/* + * Below are simple wrappers over UBI I/O functions which include some + * additional checks and UBIFS debugging stuff. See corresponding UBI function + * for more information. + */ + +int ubifs_leb_read(const struct ubifs_info *c, int lnum, void *buf, int offs, + int len, int even_ebadmsg) +{ + int err = 0; + off_t pos = (off_t)lnum * c->leb_size + offs; + + if (!len) + return 0; + + /* + * The %-EBADMSG may be ignored in some case, the buf may not be filled + * with data in some buggy mtd drivers. So we'd better to reset the buf + * content before reading. + */ + memset(buf, 0, len); + if (lseek(c->dev_fd, pos, SEEK_SET) != pos) { + err = -errno; + goto out; + } + + if (read(c->dev_fd, buf, len) != len) + err = -errno; +out: + /* + * In case of %-EBADMSG print the error message only if the + * @even_ebadmsg is true. + */ + if (err && (err != -EBADMSG || even_ebadmsg)) { + ubifs_err(c, "reading %d bytes from LEB %d:%d failed, error %d", + len, lnum, offs, err); + dump_stack(); + } + return err; +} + +int ubifs_leb_write(struct ubifs_info *c, int lnum, const void *buf, int offs, + int len) +{ + int err = 0; + off_t pos = (off_t)lnum * c->leb_size + offs; + + ubifs_assert(c, !c->ro_media && !c->ro_mount); + if (c->ro_error) + return -EROFS; + if (!c->libubi) { + err = -ENODEV; + goto out; + } + + if (!len) + return 0; + + if (lseek(c->dev_fd, pos, SEEK_SET) != pos) { + err = -errno; + goto out; + } + if (write(c->dev_fd, buf, len) != len) + err = -errno; +out: + if (err) { + ubifs_err(c, "writing %d bytes to LEB %d:%d failed, error %d", + len, lnum, offs, err); + ubifs_ro_mode(c, err); + dump_stack(); + } + return err; +} + +int ubifs_leb_change(struct ubifs_info *c, int lnum, const void *buf, int len) +{ + int err = 0; + off_t pos = (off_t)lnum * c->leb_size; + + ubifs_assert(c, !c->ro_media && !c->ro_mount); + if (c->ro_error) + return -EROFS; + if (c->libubi) { + err = ubi_leb_change_start(c->libubi, c->dev_fd, lnum, len); + if (err) { + ubifs_err(c, "ubi_leb_change_start failed"); + err = -errno; + goto out; + } + } + + if (!len) + return 0; + + if (lseek(c->dev_fd, pos, SEEK_SET) != pos) { + err = -errno; + goto out; + } + if (write(c->dev_fd, buf, len) != len) + err = -errno; +out: + if (err) { + ubifs_err(c, "changing %d bytes in LEB %d failed, error %d", + len, lnum, err); + ubifs_ro_mode(c, err); + dump_stack(); + } + return err; +} + +int ubifs_leb_unmap(struct ubifs_info *c, int lnum) +{ + int err = 0; + + ubifs_assert(c, !c->ro_media && !c->ro_mount); + if (c->ro_error) + return -EROFS; + if (!c->libubi) + return -ENODEV; + if (ubi_leb_unmap(c->dev_fd, lnum)) + err = -errno; + if (err) { + ubifs_err(c, "unmap LEB %d failed, error %d", lnum, err); + ubifs_ro_mode(c, err); + dump_stack(); + } + return err; +} + +int ubifs_leb_map(struct ubifs_info *c, int lnum) +{ + int err = 0; + + ubifs_assert(c, !c->ro_media && !c->ro_mount); + if (c->ro_error) + return -EROFS; + if (!c->libubi) + return -ENODEV; + if (ubi_leb_map(c->dev_fd, lnum)) + err = -errno; + if (err) { + ubifs_err(c, "mapping LEB %d failed, error %d", lnum, err); + ubifs_ro_mode(c, err); + dump_stack(); + } + return err; +} + +int ubifs_is_mapped(const struct ubifs_info *c, int lnum) +{ + int err = 0; + + if (!c->libubi) + return -ENODEV; + if (ubi_is_mapped(c->dev_fd, lnum)) + err = -errno; + if (err < 0) { + ubifs_err(c, "ubi_is_mapped failed for LEB %d, error %d", + lnum, err); + dump_stack(); + } + return err; +} + +/** + * ubifs_check_node - check node. + * @c: UBIFS file-system description object + * @buf: node to check + * @len: node length + * @lnum: logical eraseblock number + * @offs: offset within the logical eraseblock + * @quiet: print no messages + * @must_chk_crc: indicates whether to always check the CRC + * + * This function checks node magic number and CRC checksum. This function also + * validates node length to prevent UBIFS from becoming crazy when an attacker + * feeds it a file-system image with incorrect nodes. For example, too large + * node length in the common header could cause UBIFS to read memory outside of + * allocated buffer when checking the CRC checksum. + * + * This function may skip data nodes CRC checking if @c->no_chk_data_crc is + * true, which is controlled by corresponding UBIFS mount option. However, if + * @must_chk_crc is true, then @c->no_chk_data_crc is ignored and CRC is + * checked. Similarly, if @c->mounting or @c->remounting_rw is true (we are + * mounting or re-mounting to R/W mode), @c->no_chk_data_crc is ignored and CRC + * is checked. This is because during mounting or re-mounting from R/O mode to + * R/W mode we may read journal nodes (when replying the journal or doing the + * recovery) and the journal nodes may potentially be corrupted, so checking is + * required. + * + * This function returns zero in case of success and %-EUCLEAN in case of bad + * CRC or magic. + */ +int ubifs_check_node(const struct ubifs_info *c, const void *buf, int len, + int lnum, int offs, int quiet, int must_chk_crc) +{ + int err = -EINVAL, type, node_len; + uint32_t crc, node_crc, magic; + const struct ubifs_ch *ch = buf; + + ubifs_assert(c, lnum >= 0 && lnum < c->leb_cnt && offs >= 0); + ubifs_assert(c, !(offs & 7) && offs < c->leb_size); + + magic = le32_to_cpu(ch->magic); + if (magic != UBIFS_NODE_MAGIC) { + if (!quiet) + ubifs_err(c, "bad magic %#08x, expected %#08x", + magic, UBIFS_NODE_MAGIC); + err = -EUCLEAN; + goto out; + } + + type = ch->node_type; + if (type < 0 || type >= UBIFS_NODE_TYPES_CNT) { + if (!quiet) + ubifs_err(c, "bad node type %d", type); + goto out; + } + + node_len = le32_to_cpu(ch->len); + if (node_len + offs > c->leb_size) + goto out_len; + + if (c->ranges[type].max_len == 0) { + if (node_len != c->ranges[type].len) + goto out_len; + } else if (node_len < c->ranges[type].min_len || + node_len > c->ranges[type].max_len) + goto out_len; + + if (!must_chk_crc && type == UBIFS_DATA_NODE && !c->mounting && + !c->remounting_rw && c->no_chk_data_crc) + return 0; + + crc = crc32(UBIFS_CRC32_INIT, buf + 8, node_len - 8); + node_crc = le32_to_cpu(ch->crc); + if (crc != node_crc) { + if (!quiet) + ubifs_err(c, "bad CRC: calculated %#08x, read %#08x", + crc, node_crc); + err = -EUCLEAN; + goto out; + } + + return 0; + +out_len: + if (!quiet) + ubifs_err(c, "bad node length %d", node_len); +out: + if (!quiet) { + ubifs_err(c, "bad node at LEB %d:%d", lnum, offs); + ubifs_dump_node(c, buf, len); + dump_stack(); + } + return err; +} + +/** + * ubifs_pad - pad flash space. + * @c: UBIFS file-system description object + * @buf: buffer to put padding to + * @pad: how many bytes to pad + * + * The flash media obliges us to write only in chunks of %c->min_io_size and + * when we have to write less data we add padding node to the write-buffer and + * pad it to the next minimal I/O unit's boundary. Padding nodes help when the + * media is being scanned. If the amount of wasted space is not enough to fit a + * padding node which takes %UBIFS_PAD_NODE_SZ bytes, we write padding bytes + * pattern (%UBIFS_PADDING_BYTE). + * + * Padding nodes are also used to fill gaps when the "commit-in-gaps" method is + * used. + */ +void ubifs_pad(const struct ubifs_info *c, void *buf, int pad) +{ + uint32_t crc; + + ubifs_assert(c, pad >= 0); + + if (pad >= UBIFS_PAD_NODE_SZ) { + struct ubifs_ch *ch = buf; + struct ubifs_pad_node *pad_node = buf; + + ch->magic = cpu_to_le32(UBIFS_NODE_MAGIC); + ch->node_type = UBIFS_PAD_NODE; + ch->group_type = UBIFS_NO_NODE_GROUP; + ch->padding[0] = ch->padding[1] = 0; + ch->sqnum = 0; + ch->len = cpu_to_le32(UBIFS_PAD_NODE_SZ); + pad -= UBIFS_PAD_NODE_SZ; + pad_node->pad_len = cpu_to_le32(pad); + crc = crc32(UBIFS_CRC32_INIT, buf + 8, UBIFS_PAD_NODE_SZ - 8); + ch->crc = cpu_to_le32(crc); + memset(buf + UBIFS_PAD_NODE_SZ, 0, pad); + } else if (pad > 0) + /* Too little space, padding node won't fit */ + memset(buf, UBIFS_PADDING_BYTE, pad); +} + +/** + * next_sqnum - get next sequence number. + * @c: UBIFS file-system description object + */ +static unsigned long long next_sqnum(struct ubifs_info *c) +{ + unsigned long long sqnum; + + spin_lock(&c->cnt_lock); + sqnum = ++c->max_sqnum; + spin_unlock(&c->cnt_lock); + + if (unlikely(sqnum >= SQNUM_WARN_WATERMARK)) { + if (sqnum >= SQNUM_WATERMARK) { + ubifs_err(c, "sequence number overflow %llu, end of life", + sqnum); + ubifs_ro_mode(c, -EINVAL); + } + ubifs_warn(c, "running out of sequence numbers, end of life soon"); + } + + return sqnum; +} + +void ubifs_init_node(struct ubifs_info *c, void *node, int len, int pad) +{ + struct ubifs_ch *ch = node; + unsigned long long sqnum = next_sqnum(c); + + ubifs_assert(c, len >= UBIFS_CH_SZ); + + ch->magic = cpu_to_le32(UBIFS_NODE_MAGIC); + ch->len = cpu_to_le32(len); + ch->group_type = UBIFS_NO_NODE_GROUP; + ch->sqnum = cpu_to_le64(sqnum); + ch->padding[0] = ch->padding[1] = 0; + + if (pad) { + len = ALIGN(len, 8); + pad = ALIGN(len, c->min_io_size) - len; + ubifs_pad(c, node + len, pad); + } +} + +void ubifs_crc_node(__unused struct ubifs_info *c, void *node, int len) +{ + struct ubifs_ch *ch = node; + uint32_t crc; + + crc = crc32(UBIFS_CRC32_INIT, node + 8, len - 8); + ch->crc = cpu_to_le32(crc); +} + +/** + * ubifs_prepare_node_hmac - prepare node to be written to flash. + * @c: UBIFS file-system description object + * @node: the node to pad + * @len: node length + * @hmac_offs: offset of the HMAC in the node + * @pad: if the buffer has to be padded + * + * This function prepares node at @node to be written to the media - it + * calculates node CRC, fills the common header, and adds proper padding up to + * the next minimum I/O unit if @pad is not zero. if @hmac_offs is positive then + * a HMAC is inserted into the node at the given offset. + * + * This function returns 0 for success or a negative error code otherwise. + */ +int ubifs_prepare_node_hmac(struct ubifs_info *c, void *node, int len, + int hmac_offs, int pad) +{ + int err; + + ubifs_init_node(c, node, len, pad); + + if (hmac_offs > 0) { + err = ubifs_node_insert_hmac(c, node, len, hmac_offs); + if (err) + return err; + } + + ubifs_crc_node(c, node, len); + + return 0; +} + +/** + * ubifs_prepare_node - prepare node to be written to flash. + * @c: UBIFS file-system description object + * @node: the node to pad + * @len: node length + * @pad: if the buffer has to be padded + * + * This function prepares node at @node to be written to the media - it + * calculates node CRC, fills the common header, and adds proper padding up to + * the next minimum I/O unit if @pad is not zero. + */ +void ubifs_prepare_node(struct ubifs_info *c, void *node, int len, int pad) +{ + /* + * Deliberately ignore return value since this function can only fail + * when a hmac offset is given. + */ + ubifs_prepare_node_hmac(c, node, len, 0, pad); +} + +/** + * ubifs_prep_grp_node - prepare node of a group to be written to flash. + * @c: UBIFS file-system description object + * @node: the node to pad + * @len: node length + * @last: indicates the last node of the group + * + * This function prepares node at @node to be written to the media - it + * calculates node CRC and fills the common header. + */ +void ubifs_prep_grp_node(struct ubifs_info *c, void *node, int len, int last) +{ + uint32_t crc; + struct ubifs_ch *ch = node; + unsigned long long sqnum = next_sqnum(c); + + ubifs_assert(c, len >= UBIFS_CH_SZ); + + ch->magic = cpu_to_le32(UBIFS_NODE_MAGIC); + ch->len = cpu_to_le32(len); + if (last) + ch->group_type = UBIFS_LAST_OF_NODE_GROUP; + else + ch->group_type = UBIFS_IN_NODE_GROUP; + ch->sqnum = cpu_to_le64(sqnum); + ch->padding[0] = ch->padding[1] = 0; + crc = crc32(UBIFS_CRC32_INIT, node + 8, len - 8); + ch->crc = cpu_to_le32(crc); +} + +/** + * ubifs_wbuf_sync_nolock - synchronize write-buffer. + * @wbuf: write-buffer to synchronize + * + * This function synchronizes write-buffer @buf and returns zero in case of + * success or a negative error code in case of failure. + * + * Note, although write-buffers are of @c->max_write_size, this function does + * not necessarily writes all @c->max_write_size bytes to the flash. Instead, + * if the write-buffer is only partially filled with data, only the used part + * of the write-buffer (aligned on @c->min_io_size boundary) is synchronized. + * This way we waste less space. + */ +int ubifs_wbuf_sync_nolock(struct ubifs_wbuf *wbuf) +{ + struct ubifs_info *c = wbuf->c; + int err, dirt, sync_len; + + if (!wbuf->used || wbuf->lnum == -1) + /* Write-buffer is empty or not seeked */ + return 0; + + dbg_io("LEB %d:%d, %d bytes, jhead %s", + wbuf->lnum, wbuf->offs, wbuf->used, dbg_jhead(wbuf->jhead)); + ubifs_assert(c, !(wbuf->avail & 7)); + ubifs_assert(c, wbuf->offs + wbuf->size <= c->leb_size); + ubifs_assert(c, wbuf->size >= c->min_io_size); + ubifs_assert(c, wbuf->size <= c->max_write_size); + ubifs_assert(c, wbuf->size % c->min_io_size == 0); + ubifs_assert(c, !c->ro_media && !c->ro_mount); + if (c->leb_size - wbuf->offs >= c->max_write_size) + ubifs_assert(c, !((wbuf->offs + wbuf->size) % c->max_write_size)); + + if (c->ro_error) + return -EROFS; + + /* + * Do not write whole write buffer but write only the minimum necessary + * amount of min. I/O units. + */ + sync_len = ALIGN(wbuf->used, c->min_io_size); + dirt = sync_len - wbuf->used; + if (dirt) + ubifs_pad(c, wbuf->buf + wbuf->used, dirt); + err = ubifs_leb_write(c, wbuf->lnum, wbuf->buf, wbuf->offs, sync_len); + if (err) + return err; + + spin_lock(&wbuf->lock); + wbuf->offs += sync_len; + /* + * Now @wbuf->offs is not necessarily aligned to @c->max_write_size. + * But our goal is to optimize writes and make sure we write in + * @c->max_write_size chunks and to @c->max_write_size-aligned offset. + * Thus, if @wbuf->offs is not aligned to @c->max_write_size now, make + * sure that @wbuf->offs + @wbuf->size is aligned to + * @c->max_write_size. This way we make sure that after next + * write-buffer flush we are again at the optimal offset (aligned to + * @c->max_write_size). + */ + if (c->leb_size - wbuf->offs < c->max_write_size) + wbuf->size = c->leb_size - wbuf->offs; + else if (wbuf->offs & (c->max_write_size - 1)) + wbuf->size = ALIGN(wbuf->offs, c->max_write_size) - wbuf->offs; + else + wbuf->size = c->max_write_size; + wbuf->avail = wbuf->size; + wbuf->used = 0; + wbuf->next_ino = 0; + spin_unlock(&wbuf->lock); + + if (wbuf->sync_callback) + err = wbuf->sync_callback(c, wbuf->lnum, + c->leb_size - wbuf->offs, dirt); + return err; +} + +/** + * ubifs_wbuf_seek_nolock - seek write-buffer. + * @wbuf: write-buffer + * @lnum: logical eraseblock number to seek to + * @offs: logical eraseblock offset to seek to + * + * This function targets the write-buffer to logical eraseblock @lnum:@offs. + * The write-buffer has to be empty. Returns zero in case of success and a + * negative error code in case of failure. + */ +int ubifs_wbuf_seek_nolock(struct ubifs_wbuf *wbuf, int lnum, int offs) +{ + const struct ubifs_info *c = wbuf->c; + + dbg_io("LEB %d:%d, jhead %s", lnum, offs, dbg_jhead(wbuf->jhead)); + ubifs_assert(c, lnum >= 0 && lnum < c->leb_cnt); + ubifs_assert(c, offs >= 0 && offs <= c->leb_size); + ubifs_assert(c, offs % c->min_io_size == 0 && !(offs & 7)); + ubifs_assert(c, lnum != wbuf->lnum); + ubifs_assert(c, wbuf->used == 0); + + spin_lock(&wbuf->lock); + wbuf->lnum = lnum; + wbuf->offs = offs; + if (c->leb_size - wbuf->offs < c->max_write_size) + wbuf->size = c->leb_size - wbuf->offs; + else if (wbuf->offs & (c->max_write_size - 1)) + wbuf->size = ALIGN(wbuf->offs, c->max_write_size) - wbuf->offs; + else + wbuf->size = c->max_write_size; + wbuf->avail = wbuf->size; + wbuf->used = 0; + spin_unlock(&wbuf->lock); + + return 0; +} + +/** + * ubifs_wbuf_write_nolock - write data to flash via write-buffer. + * @wbuf: write-buffer + * @buf: node to write + * @len: node length + * + * This function writes data to flash via write-buffer @wbuf. This means that + * the last piece of the node won't reach the flash media immediately if it + * does not take whole max. write unit (@c->max_write_size). Instead, the node + * will sit in RAM until the write-buffer is synchronized (e.g., by timer, or + * because more data are appended to the write-buffer). + * + * This function returns zero in case of success and a negative error code in + * case of failure. If the node cannot be written because there is no more + * space in this logical eraseblock, %-ENOSPC is returned. + */ +int ubifs_wbuf_write_nolock(struct ubifs_wbuf *wbuf, void *buf, int len) +{ + struct ubifs_info *c = wbuf->c; + int err, n, written = 0, aligned_len = ALIGN(len, 8); + + dbg_io("%d bytes (%s) to jhead %s wbuf at LEB %d:%d", len, + dbg_ntype(((struct ubifs_ch *)buf)->node_type), + dbg_jhead(wbuf->jhead), wbuf->lnum, wbuf->offs + wbuf->used); + ubifs_assert(c, len > 0 && wbuf->lnum >= 0 && wbuf->lnum < c->leb_cnt); + ubifs_assert(c, wbuf->offs >= 0 && wbuf->offs % c->min_io_size == 0); + ubifs_assert(c, !(wbuf->offs & 7) && wbuf->offs <= c->leb_size); + ubifs_assert(c, wbuf->avail > 0 && wbuf->avail <= wbuf->size); + ubifs_assert(c, wbuf->size >= c->min_io_size); + ubifs_assert(c, wbuf->size <= c->max_write_size); + ubifs_assert(c, wbuf->size % c->min_io_size == 0); + ubifs_assert(c, mutex_is_locked(&wbuf->io_mutex)); + ubifs_assert(c, !c->ro_media && !c->ro_mount); + ubifs_assert(c, !c->space_fixup); + if (c->leb_size - wbuf->offs >= c->max_write_size) + ubifs_assert(c, !((wbuf->offs + wbuf->size) % c->max_write_size)); + + if (c->leb_size - wbuf->offs - wbuf->used < aligned_len) { + err = -ENOSPC; + goto out; + } + + if (c->ro_error) + return -EROFS; + + if (aligned_len <= wbuf->avail) { + /* + * The node is not very large and fits entirely within + * write-buffer. + */ + memcpy(wbuf->buf + wbuf->used, buf, len); + if (aligned_len > len) { + ubifs_assert(c, aligned_len - len < 8); + ubifs_pad(c, wbuf->buf + wbuf->used + len, aligned_len - len); + } + + if (aligned_len == wbuf->avail) { + dbg_io("flush jhead %s wbuf to LEB %d:%d", + dbg_jhead(wbuf->jhead), wbuf->lnum, wbuf->offs); + err = ubifs_leb_write(c, wbuf->lnum, wbuf->buf, + wbuf->offs, wbuf->size); + if (err) + goto out; + + spin_lock(&wbuf->lock); + wbuf->offs += wbuf->size; + if (c->leb_size - wbuf->offs >= c->max_write_size) + wbuf->size = c->max_write_size; + else + wbuf->size = c->leb_size - wbuf->offs; + wbuf->avail = wbuf->size; + wbuf->used = 0; + wbuf->next_ino = 0; + spin_unlock(&wbuf->lock); + } else { + spin_lock(&wbuf->lock); + wbuf->avail -= aligned_len; + wbuf->used += aligned_len; + spin_unlock(&wbuf->lock); + } + + goto exit; + } + + if (wbuf->used) { + /* + * The node is large enough and does not fit entirely within + * current available space. We have to fill and flush + * write-buffer and switch to the next max. write unit. + */ + dbg_io("flush jhead %s wbuf to LEB %d:%d", + dbg_jhead(wbuf->jhead), wbuf->lnum, wbuf->offs); + memcpy(wbuf->buf + wbuf->used, buf, wbuf->avail); + err = ubifs_leb_write(c, wbuf->lnum, wbuf->buf, wbuf->offs, + wbuf->size); + if (err) + goto out; + + wbuf->offs += wbuf->size; + len -= wbuf->avail; + aligned_len -= wbuf->avail; + written += wbuf->avail; + } else if (wbuf->offs & (c->max_write_size - 1)) { + /* + * The write-buffer offset is not aligned to + * @c->max_write_size and @wbuf->size is less than + * @c->max_write_size. Write @wbuf->size bytes to make sure the + * following writes are done in optimal @c->max_write_size + * chunks. + */ + dbg_io("write %d bytes to LEB %d:%d", + wbuf->size, wbuf->lnum, wbuf->offs); + err = ubifs_leb_write(c, wbuf->lnum, buf, wbuf->offs, + wbuf->size); + if (err) + goto out; + + wbuf->offs += wbuf->size; + len -= wbuf->size; + aligned_len -= wbuf->size; + written += wbuf->size; + } + + /* + * The remaining data may take more whole max. write units, so write the + * remains multiple to max. write unit size directly to the flash media. + * We align node length to 8-byte boundary because we anyway flash wbuf + * if the remaining space is less than 8 bytes. + */ + n = aligned_len >> c->max_write_shift; + if (n) { + int m = n - 1; + + dbg_io("write %d bytes to LEB %d:%d", n, wbuf->lnum, + wbuf->offs); + + if (m) { + /* '(n-1)<<c->max_write_shift < len' is always true. */ + m <<= c->max_write_shift; + err = ubifs_leb_write(c, wbuf->lnum, buf + written, + wbuf->offs, m); + if (err) + goto out; + wbuf->offs += m; + aligned_len -= m; + len -= m; + written += m; + } + + /* + * The non-written len of buf may be less than 'n' because + * parameter 'len' is not 8 bytes aligned, so here we read + * min(len, n) bytes from buf. + */ + n = 1 << c->max_write_shift; + memcpy(wbuf->buf, buf + written, min(len, n)); + if (n > len) { + ubifs_assert(c, n - len < 8); + ubifs_pad(c, wbuf->buf + len, n - len); + } + + err = ubifs_leb_write(c, wbuf->lnum, wbuf->buf, wbuf->offs, n); + if (err) + goto out; + wbuf->offs += n; + aligned_len -= n; + len -= min(len, n); + written += n; + } + + spin_lock(&wbuf->lock); + if (aligned_len) { + /* + * And now we have what's left and what does not take whole + * max. write unit, so write it to the write-buffer and we are + * done. + */ + memcpy(wbuf->buf, buf + written, len); + if (aligned_len > len) { + ubifs_assert(c, aligned_len - len < 8); + ubifs_pad(c, wbuf->buf + len, aligned_len - len); + } + } + + if (c->leb_size - wbuf->offs >= c->max_write_size) + wbuf->size = c->max_write_size; + else + wbuf->size = c->leb_size - wbuf->offs; + wbuf->avail = wbuf->size - aligned_len; + wbuf->used = aligned_len; + wbuf->next_ino = 0; + spin_unlock(&wbuf->lock); + +exit: + if (wbuf->sync_callback) { + int free = c->leb_size - wbuf->offs - wbuf->used; + + err = wbuf->sync_callback(c, wbuf->lnum, free, 0); + if (err) + goto out; + } + + return 0; + +out: + ubifs_err(c, "cannot write %d bytes to LEB %d:%d, error %d", + len, wbuf->lnum, wbuf->offs, err); + ubifs_dump_node(c, buf, written + len); + dump_stack(); + ubifs_dump_leb(c, wbuf->lnum); + return err; +} + +/** + * ubifs_write_node_hmac - write node to the media. + * @c: UBIFS file-system description object + * @buf: the node to write + * @len: node length + * @lnum: logical eraseblock number + * @offs: offset within the logical eraseblock + * @hmac_offs: offset of the HMAC within the node + * + * This function automatically fills node magic number, assigns sequence + * number, and calculates node CRC checksum. The length of the @buf buffer has + * to be aligned to the minimal I/O unit size. This function automatically + * appends padding node and padding bytes if needed. Returns zero in case of + * success and a negative error code in case of failure. + */ +int ubifs_write_node_hmac(struct ubifs_info *c, void *buf, int len, int lnum, + int offs, int hmac_offs) +{ + int err, buf_len = ALIGN(len, c->min_io_size); + + dbg_io("LEB %d:%d, %s, length %d (aligned %d)", + lnum, offs, dbg_ntype(((struct ubifs_ch *)buf)->node_type), len, + buf_len); + ubifs_assert(c, lnum >= 0 && lnum < c->leb_cnt && offs >= 0); + ubifs_assert(c, offs % c->min_io_size == 0 && offs < c->leb_size); + ubifs_assert(c, !c->ro_media && !c->ro_mount); + ubifs_assert(c, !c->space_fixup); + + if (c->ro_error) + return -EROFS; + + err = ubifs_prepare_node_hmac(c, buf, len, hmac_offs, 1); + if (err) + return err; + + err = ubifs_leb_write(c, lnum, buf, offs, buf_len); + if (err) + ubifs_dump_node(c, buf, len); + + return err; +} + +/** + * ubifs_write_node - write node to the media. + * @c: UBIFS file-system description object + * @buf: the node to write + * @len: node length + * @lnum: logical eraseblock number + * @offs: offset within the logical eraseblock + * + * This function automatically fills node magic number, assigns sequence + * number, and calculates node CRC checksum. The length of the @buf buffer has + * to be aligned to the minimal I/O unit size. This function automatically + * appends padding node and padding bytes if needed. Returns zero in case of + * success and a negative error code in case of failure. + */ +int ubifs_write_node(struct ubifs_info *c, void *buf, int len, int lnum, + int offs) +{ + return ubifs_write_node_hmac(c, buf, len, lnum, offs, -1); +} + +/** + * ubifs_read_node_wbuf - read node from the media or write-buffer. + * @wbuf: wbuf to check for un-written data + * @buf: buffer to read to + * @type: node type + * @len: node length + * @lnum: logical eraseblock number + * @offs: offset within the logical eraseblock + * + * This function reads a node of known type and length, checks it and stores + * in @buf. If the node partially or fully sits in the write-buffer, this + * function takes data from the buffer, otherwise it reads the flash media. + * Returns zero in case of success, %-EUCLEAN if CRC mismatched and a negative + * error code in case of failure. + */ +int ubifs_read_node_wbuf(struct ubifs_wbuf *wbuf, void *buf, int type, int len, + int lnum, int offs) +{ + const struct ubifs_info *c = wbuf->c; + int err, rlen, overlap; + struct ubifs_ch *ch = buf; + + dbg_io("LEB %d:%d, %s, length %d, jhead %s", lnum, offs, + dbg_ntype(type), len, dbg_jhead(wbuf->jhead)); + ubifs_assert(c, wbuf && lnum >= 0 && lnum < c->leb_cnt && offs >= 0); + ubifs_assert(c, !(offs & 7) && offs < c->leb_size); + ubifs_assert(c, type >= 0 && type < UBIFS_NODE_TYPES_CNT); + + spin_lock(&wbuf->lock); + overlap = (lnum == wbuf->lnum && offs + len > wbuf->offs); + if (!overlap) { + /* We may safely unlock the write-buffer and read the data */ + spin_unlock(&wbuf->lock); + return ubifs_read_node(c, buf, type, len, lnum, offs); + } + + /* Don't read under wbuf */ + rlen = wbuf->offs - offs; + if (rlen < 0) + rlen = 0; + + /* Copy the rest from the write-buffer */ + memcpy(buf + rlen, wbuf->buf + offs + rlen - wbuf->offs, len - rlen); + spin_unlock(&wbuf->lock); + + if (rlen > 0) { + /* Read everything that goes before write-buffer */ + err = ubifs_leb_read(c, lnum, buf, offs, rlen, 0); + if (err && err != -EBADMSG) + return err; + } + + if (type != ch->node_type) { + ubifs_err(c, "bad node type (%d but expected %d)", + ch->node_type, type); + goto out; + } + + err = ubifs_check_node(c, buf, len, lnum, offs, 0, 0); + if (err) { + set_failure_reason_callback(c, FR_DATA_CORRUPTED); + ubifs_err(c, "expected node type %d", type); + return err; + } + + rlen = le32_to_cpu(ch->len); + if (rlen != len) { + ubifs_err(c, "bad node length %d, expected %d", rlen, len); + goto out; + } + + return 0; + +out: + set_failure_reason_callback(c, FR_DATA_CORRUPTED); + ubifs_err(c, "bad node at LEB %d:%d", lnum, offs); + ubifs_dump_node(c, buf, len); + dump_stack(); + return -EINVAL; +} + +/** + * ubifs_read_node - read node. + * @c: UBIFS file-system description object + * @buf: buffer to read to + * @type: node type + * @len: node length (not aligned) + * @lnum: logical eraseblock number + * @offs: offset within the logical eraseblock + * + * This function reads a node of known type and length, checks it and + * stores in @buf. Returns zero in case of success, %-EUCLEAN if CRC mismatched + * and a negative error code in case of failure. + */ +int ubifs_read_node(const struct ubifs_info *c, void *buf, int type, int len, + int lnum, int offs) +{ + int err, l; + struct ubifs_ch *ch = buf; + + dbg_io("LEB %d:%d, %s, length %d", lnum, offs, dbg_ntype(type), len); + ubifs_assert(c, lnum >= 0 && lnum < c->leb_cnt && offs >= 0); + ubifs_assert(c, len >= UBIFS_CH_SZ && offs + len <= c->leb_size); + ubifs_assert(c, !(offs & 7) && offs < c->leb_size); + ubifs_assert(c, type >= 0 && type < UBIFS_NODE_TYPES_CNT); + + err = ubifs_leb_read(c, lnum, buf, offs, len, 0); + if (err && err != -EBADMSG) + return err; + + if (type != ch->node_type) { + ubifs_err(c, "bad node type (%d but expected %d)", + ch->node_type, type); + goto out; + } + + err = ubifs_check_node(c, buf, len, lnum, offs, 0, 0); + if (err) { + set_failure_reason_callback(c, FR_DATA_CORRUPTED); + ubifs_err(c, "expected node type %d", type); + return err; + } + + l = le32_to_cpu(ch->len); + if (l != len) { + ubifs_err(c, "bad node length %d, expected %d", l, len); + goto out; + } + + return 0; + +out: + set_failure_reason_callback(c, FR_DATA_CORRUPTED); + ubifs_err(c, "bad node at LEB %d:%d, LEB mapping status %d", lnum, + offs, ubi_is_mapped(c->dev_fd, lnum)); + ubifs_dump_node(c, buf, len); + dump_stack(); + return -EINVAL; +} + +/** + * ubifs_wbuf_init - initialize write-buffer. + * @c: UBIFS file-system description object + * @wbuf: write-buffer to initialize + * + * This function initializes write-buffer. Returns zero in case of success + * %-ENOMEM in case of failure. + */ +int ubifs_wbuf_init(struct ubifs_info *c, struct ubifs_wbuf *wbuf) +{ + size_t size; + + wbuf->buf = kmalloc(c->max_write_size, GFP_KERNEL); + if (!wbuf->buf) + return -ENOMEM; + + size = (c->max_write_size / UBIFS_CH_SZ + 1) * sizeof(ino_t); + wbuf->inodes = kmalloc(size, GFP_KERNEL); + if (!wbuf->inodes) { + kfree(wbuf->buf); + wbuf->buf = NULL; + return -ENOMEM; + } + + wbuf->used = 0; + wbuf->lnum = wbuf->offs = -1; + /* + * Different from linux kernel, there is no way to get leb_start in + * userspace, set write-buffer size as @c->max_write_size directly. + * Since wbuf->lnum is initialized as -1, wbuf->size will always be + * reset in ubifs_wbuf_seek_nolock, it won't be any problems. + */ + size = c->max_write_size; + wbuf->avail = wbuf->size = size; + wbuf->sync_callback = NULL; + mutex_init(&wbuf->io_mutex); + spin_lock_init(&wbuf->lock); + wbuf->c = c; + wbuf->next_ino = 0; + + return 0; +} diff --git a/ubifs-utils/libubifs/journal.c b/ubifs-utils/libubifs/journal.c new file mode 100644 index 0000000..45d82fd --- /dev/null +++ b/ubifs-utils/libubifs/journal.c @@ -0,0 +1,634 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * This file is part of UBIFS. + * + * Copyright (C) 2006-2008 Nokia Corporation. + * + * Authors: Artem Bityutskiy (Битюцкий Артём) + * Adrian Hunter + */ + +/* + * This file implements UBIFS journal. + * + * The journal consists of 2 parts - the log and bud LEBs. The log has fixed + * length and position, while a bud logical eraseblock is any LEB in the main + * area. Buds contain file system data - data nodes, inode nodes, etc. The log + * contains only references to buds and some other stuff like commit + * start node. The idea is that when we commit the journal, we do + * not copy the data, the buds just become indexed. Since after the commit the + * nodes in bud eraseblocks become leaf nodes of the file system index tree, we + * use term "bud". Analogy is obvious, bud eraseblocks contain nodes which will + * become leafs in the future. + * + * The journal is multi-headed because we want to write data to the journal as + * optimally as possible. It is nice to have nodes belonging to the same inode + * in one LEB, so we may write data owned by different inodes to different + * journal heads, although at present only one data head is used. + * + * For recovery reasons, the base head contains all inode nodes, all directory + * entry nodes and all truncate nodes. This means that the other heads contain + * only data nodes. + * + * Bud LEBs may be half-indexed. For example, if the bud was not full at the + * time of commit, the bud is retained to continue to be used in the journal, + * even though the "front" of the LEB is now indexed. In that case, the log + * reference contains the offset where the bud starts for the purposes of the + * journal. + * + * The journal size has to be limited, because the larger is the journal, the + * longer it takes to mount UBIFS (scanning the journal) and the more memory it + * takes (indexing in the TNC). + * + * All the journal write operations like 'ubifs_jnl_update()' here, which write + * multiple UBIFS nodes to the journal at one go, are atomic with respect to + * unclean reboots. Should the unclean reboot happen, the recovery code drops + * all the nodes. + */ + +#include <sys/stat.h> +#include "bitops.h" +#include "kmem.h" +#include "ubifs.h" +#include "defs.h" +#include "debug.h" +#include "key.h" +#include "misc.h" + +/** + * zero_ino_node_unused - zero out unused fields of an on-flash inode node. + * @ino: the inode to zero out + */ +static inline void zero_ino_node_unused(struct ubifs_ino_node *ino) +{ + memset(ino->padding1, 0, 4); + memset(ino->padding2, 0, 26); +} + +/** + * zero_dent_node_unused - zero out unused fields of an on-flash directory + * entry node. + * @dent: the directory entry to zero out + */ +static inline void zero_dent_node_unused(struct ubifs_dent_node *dent) +{ + dent->padding1 = 0; +} + +static void ubifs_add_auth_dirt(struct ubifs_info *c, int lnum) +{ + if (ubifs_authenticated(c)) + ubifs_add_dirt(c, lnum, ubifs_auth_node_sz(c)); +} + +/** + * reserve_space - reserve space in the journal. + * @c: UBIFS file-system description object + * @jhead: journal head number + * @len: node length + * + * This function reserves space in journal head @head. If the reservation + * succeeded, the journal head stays locked and later has to be unlocked using + * 'release_head()'. Returns zero in case of success, %-EAGAIN if commit has to + * be done, and other negative error codes in case of other failures. + */ +static int reserve_space(struct ubifs_info *c, int jhead, int len) +{ + int err = 0, err1, retries = 0, avail, lnum, offs, squeeze; + struct ubifs_wbuf *wbuf = &c->jheads[jhead].wbuf; + + /* + * Typically, the base head has smaller nodes written to it, so it is + * better to try to allocate space at the ends of eraseblocks. This is + * what the squeeze parameter does. + */ + ubifs_assert(c, !c->ro_media && !c->ro_mount); + squeeze = (jhead == BASEHD); +again: + mutex_lock_nested(&wbuf->io_mutex, wbuf->jhead); + + if (c->ro_error) { + err = -EROFS; + goto out_unlock; + } + + avail = c->leb_size - wbuf->offs - wbuf->used; + if (wbuf->lnum != -1 && avail >= len) + return 0; + + /* + * Write buffer wasn't seek'ed or there is no enough space - look for an + * LEB with some empty space. + */ + lnum = ubifs_find_free_space(c, len, &offs, squeeze); + if (lnum >= 0) + goto out; + + err = lnum; + if (err != -ENOSPC) + goto out_unlock; + + /* + * No free space, we have to run garbage collector to make + * some. But the write-buffer mutex has to be unlocked because + * GC also takes it. + */ + dbg_jnl("no free space in jhead %s, run GC", dbg_jhead(jhead)); + mutex_unlock(&wbuf->io_mutex); + + lnum = ubifs_garbage_collect(c, 0); + if (lnum < 0) { + err = lnum; + if (err != -ENOSPC) + return err; + + /* + * GC could not make a free LEB. But someone else may + * have allocated new bud for this journal head, + * because we dropped @wbuf->io_mutex, so try once + * again. + */ + dbg_jnl("GC couldn't make a free LEB for jhead %s", + dbg_jhead(jhead)); + if (retries++ < 2) { + dbg_jnl("retry (%d)", retries); + goto again; + } + + dbg_jnl("return -ENOSPC"); + return err; + } + + mutex_lock_nested(&wbuf->io_mutex, wbuf->jhead); + dbg_jnl("got LEB %d for jhead %s", lnum, dbg_jhead(jhead)); + avail = c->leb_size - wbuf->offs - wbuf->used; + + if (wbuf->lnum != -1 && avail >= len) { + /* + * Someone else has switched the journal head and we have + * enough space now. This happens when more than one process is + * trying to write to the same journal head at the same time. + */ + dbg_jnl("return LEB %d back, already have LEB %d:%d", + lnum, wbuf->lnum, wbuf->offs + wbuf->used); + err = ubifs_return_leb(c, lnum); + if (err) + goto out_unlock; + return 0; + } + + offs = 0; + +out: + /* + * Make sure we synchronize the write-buffer before we add the new bud + * to the log. Otherwise we may have a power cut after the log + * reference node for the last bud (@lnum) is written but before the + * write-buffer data are written to the next-to-last bud + * (@wbuf->lnum). And the effect would be that the recovery would see + * that there is corruption in the next-to-last bud. + */ + err = ubifs_wbuf_sync_nolock(wbuf); + if (err) + goto out_return; + err = ubifs_add_bud_to_log(c, jhead, lnum, offs); + if (err) + goto out_return; + err = ubifs_wbuf_seek_nolock(wbuf, lnum, offs); + if (err) + goto out_unlock; + + return 0; + +out_unlock: + mutex_unlock(&wbuf->io_mutex); + return err; + +out_return: + /* An error occurred and the LEB has to be returned to lprops */ + ubifs_assert(c, err < 0); + err1 = ubifs_return_leb(c, lnum); + if (err1 && err == -EAGAIN) + /* + * Return original error code only if it is not %-EAGAIN, + * which is not really an error. Otherwise, return the error + * code of 'ubifs_return_leb()'. + */ + err = err1; + mutex_unlock(&wbuf->io_mutex); + return err; +} + +static int ubifs_hash_nodes(struct ubifs_info *c, void *node, + int len, struct shash_desc *hash) +{ + int auth_node_size = ubifs_auth_node_sz(c); + int err; + + while (1) { + const struct ubifs_ch *ch = node; + int nodelen = le32_to_cpu(ch->len); + + ubifs_assert(c, len >= auth_node_size); + + if (len == auth_node_size) + break; + + ubifs_assert(c, len > nodelen); + ubifs_assert(c, ch->magic == cpu_to_le32(UBIFS_NODE_MAGIC)); + + err = ubifs_shash_update(c, hash, (void *)node, nodelen); + if (err) + return err; + + node += ALIGN(nodelen, 8); + len -= ALIGN(nodelen, 8); + } + + return ubifs_prepare_auth_node(c, node, hash); +} + +/** + * write_head - write data to a journal head. + * @c: UBIFS file-system description object + * @jhead: journal head + * @buf: buffer to write + * @len: length to write + * @lnum: LEB number written is returned here + * @offs: offset written is returned here + * @sync: non-zero if the write-buffer has to by synchronized + * + * This function writes data to the reserved space of journal head @jhead. + * Returns zero in case of success and a negative error code in case of + * failure. + */ +static int write_head(struct ubifs_info *c, int jhead, void *buf, int len, + int *lnum, int *offs, int sync) +{ + int err; + struct ubifs_wbuf *wbuf = &c->jheads[jhead].wbuf; + + ubifs_assert(c, jhead != GCHD); + + *lnum = c->jheads[jhead].wbuf.lnum; + *offs = c->jheads[jhead].wbuf.offs + c->jheads[jhead].wbuf.used; + dbg_jnl("jhead %s, LEB %d:%d, len %d", + dbg_jhead(jhead), *lnum, *offs, len); + + if (ubifs_authenticated(c)) { + err = ubifs_hash_nodes(c, buf, len, c->jheads[jhead].log_hash); + if (err) + return err; + } + + err = ubifs_wbuf_write_nolock(wbuf, buf, len); + if (err) + return err; + if (sync) + err = ubifs_wbuf_sync_nolock(wbuf); + return err; +} + +/** + * make_reservation - reserve journal space. + * @c: UBIFS file-system description object + * @jhead: journal head + * @len: how many bytes to reserve + * + * This function makes space reservation in journal head @jhead. The function + * takes the commit lock and locks the journal head, and the caller has to + * unlock the head and finish the reservation with 'finish_reservation()'. + * Returns zero in case of success and a negative error code in case of + * failure. + * + * Note, the journal head may be unlocked as soon as the data is written, while + * the commit lock has to be released after the data has been added to the + * TNC. + */ +static int make_reservation(struct ubifs_info *c, int jhead, int len) +{ + int err, cmt_retries = 0, nospc_retries = 0; + +again: + down_read(&c->commit_sem); + err = reserve_space(c, jhead, len); + if (!err) + /* c->commit_sem will get released via finish_reservation(). */ + return 0; + up_read(&c->commit_sem); + + if (err == -ENOSPC) { + /* + * GC could not make any progress. We should try to commit + * once because it could make some dirty space and GC would + * make progress, so make the error -EAGAIN so that the below + * will commit and re-try. + */ + if (nospc_retries++ < 2) { + dbg_jnl("no space, retry"); + err = -EAGAIN; + } + + /* + * This means that the budgeting is incorrect. We always have + * to be able to write to the media, because all operations are + * budgeted. Deletions are not budgeted, though, but we reserve + * an extra LEB for them. + */ + } + + if (err != -EAGAIN) + goto out; + + /* + * -EAGAIN means that the journal is full or too large, or the above + * code wants to do one commit. Do this and re-try. + */ + if (cmt_retries > 128) { + /* + * This should not happen unless the journal size limitations + * are too tough. + */ + ubifs_err(c, "stuck in space allocation"); + err = -ENOSPC; + goto out; + } else if (cmt_retries > 32) + ubifs_warn(c, "too many space allocation re-tries (%d)", + cmt_retries); + + dbg_jnl("-EAGAIN, commit and retry (retried %d times)", + cmt_retries); + cmt_retries += 1; + + err = ubifs_run_commit(c); + if (err) + return err; + goto again; + +out: + ubifs_err(c, "cannot reserve %d bytes in jhead %d, error %d", + len, jhead, err); + if (err == -ENOSPC) { + /* This are some budgeting problems, print useful information */ + down_write(&c->commit_sem); + dump_stack(); + ubifs_dump_budg(c, &c->bi); + ubifs_dump_lprops(c); + cmt_retries = dbg_check_lprops(c); + up_write(&c->commit_sem); + } + return err; +} + +/** + * release_head - release a journal head. + * @c: UBIFS file-system description object + * @jhead: journal head + * + * This function releases journal head @jhead which was locked by + * the 'make_reservation()' function. It has to be called after each successful + * 'make_reservation()' invocation. + */ +static inline void release_head(struct ubifs_info *c, int jhead) +{ + mutex_unlock(&c->jheads[jhead].wbuf.io_mutex); +} + +/** + * finish_reservation - finish a reservation. + * @c: UBIFS file-system description object + * + * This function finishes journal space reservation. It must be called after + * 'make_reservation()'. + */ +static void finish_reservation(struct ubifs_info *c) +{ + up_read(&c->commit_sem); +} + +/** + * ubifs_get_dent_type - translate VFS inode mode to UBIFS directory entry type. + * @mode: inode mode + */ +int ubifs_get_dent_type(int mode) +{ + switch (mode & S_IFMT) { + case S_IFREG: + return UBIFS_ITYPE_REG; + case S_IFDIR: + return UBIFS_ITYPE_DIR; + case S_IFLNK: + return UBIFS_ITYPE_LNK; + case S_IFBLK: + return UBIFS_ITYPE_BLK; + case S_IFCHR: + return UBIFS_ITYPE_CHR; + case S_IFIFO: + return UBIFS_ITYPE_FIFO; + case S_IFSOCK: + return UBIFS_ITYPE_SOCK; + default: + BUG(); + } + return 0; +} + +static void set_dent_cookie(struct ubifs_info *c, struct ubifs_dent_node *dent) +{ + if (c->double_hash) + dent->cookie = (__force __le32) get_random_u32(); + else + dent->cookie = 0; +} + +/** + * pack_inode - pack an ubifs inode node. + * @c: UBIFS file-system description object + * @ino: buffer in which to pack inode node + * @ui: ubifs inode to pack + * @last: indicates the last node of the group + */ +static void pack_inode(struct ubifs_info *c, struct ubifs_ino_node *ino, + const struct ubifs_inode *ui, int last) +{ + const struct inode *inode = &ui->vfs_inode; + int data_len = 0, last_reference = !inode->nlink; + + ino->ch.node_type = UBIFS_INO_NODE; + ino_key_init_flash(c, &ino->key, inode->inum); + ino->creat_sqnum = cpu_to_le64(ui->creat_sqnum); + ino->atime_sec = cpu_to_le64(inode->atime_sec); + ino->atime_nsec = cpu_to_le32(inode->atime_nsec); + ino->ctime_sec = cpu_to_le64(inode->ctime_sec); + ino->ctime_nsec = cpu_to_le32(inode->ctime_nsec); + ino->mtime_sec = cpu_to_le64(inode->mtime_sec); + ino->mtime_nsec = cpu_to_le32(inode->mtime_nsec); + ino->uid = cpu_to_le32(inode->uid); + ino->gid = cpu_to_le32(inode->gid); + ino->mode = cpu_to_le32(inode->mode); + ino->flags = cpu_to_le32(ui->flags); + ino->size = cpu_to_le64(ui->ui_size); + ino->nlink = cpu_to_le32(inode->nlink); + ino->compr_type = cpu_to_le16(ui->compr_type); + ino->data_len = cpu_to_le32(ui->data_len); + ino->xattr_cnt = cpu_to_le32(ui->xattr_cnt); + ino->xattr_size = cpu_to_le32(ui->xattr_size); + ino->xattr_names = cpu_to_le32(ui->xattr_names); + zero_ino_node_unused(ino); + + /* + * Drop the attached data if this is a deletion inode, the data is not + * needed anymore. + */ + if (!last_reference) { + memcpy(ino->data, ui->data, ui->data_len); + data_len = ui->data_len; + } + + ubifs_prep_grp_node(c, ino, UBIFS_INO_NODE_SZ + data_len, last); +} + +/** + * ubifs_jnl_update_file - update file. + * @c: UBIFS file-system description object + * @dir_ui: parent ubifs inode + * @nm: directory entry name + * @ui: ubifs inode to update + * + * This function updates an file by writing a directory entry node, the inode + * node itself, and the parent directory inode node to the journal. If the + * @dir_ui and @nm are NULL, only update @ui. + * + * Returns zero on success. In case of failure, a negative error code is + * returned. + */ +int ubifs_jnl_update_file(struct ubifs_info *c, + const struct ubifs_inode *dir_ui, + const struct fscrypt_name *nm, + const struct ubifs_inode *ui) +{ + const struct inode *dir = NULL, *inode = &ui->vfs_inode; + int err, dlen, ilen, len, lnum, ino_offs, dent_offs, dir_ilen; + int aligned_dlen, aligned_ilen; + struct ubifs_dent_node *dent; + struct ubifs_ino_node *ino; + union ubifs_key dent_key, ino_key; + u8 hash_dent[UBIFS_HASH_ARR_SZ]; + u8 hash_ino[UBIFS_HASH_ARR_SZ]; + u8 hash_ino_dir[UBIFS_HASH_ARR_SZ]; + + ubifs_assert(c, (!nm && !dir_ui) || (nm && dir_ui)); + ubifs_assert(c, inode->nlink != 0); + + ilen = UBIFS_INO_NODE_SZ + ui->data_len; + + if (nm) + dlen = UBIFS_DENT_NODE_SZ + fname_len(nm) + 1; + else + dlen = 0; + + if (dir_ui) { + dir = &dir_ui->vfs_inode; + ubifs_assert(c, dir->nlink != 0); + dir_ilen = UBIFS_INO_NODE_SZ + dir_ui->data_len; + } else + dir_ilen = 0; + + aligned_dlen = ALIGN(dlen, 8); + aligned_ilen = ALIGN(ilen, 8); + len = aligned_dlen + aligned_ilen + dir_ilen; + if (ubifs_authenticated(c)) + len += ALIGN(dir_ilen, 8) + ubifs_auth_node_sz(c); + + dent = kzalloc(len, GFP_NOFS); + if (!dent) + return -ENOMEM; + + /* Make reservation before allocating sequence numbers */ + err = make_reservation(c, BASEHD, len); + if (err) + goto out_free; + + if (nm) { + dent->ch.node_type = UBIFS_DENT_NODE; + dent_key_init(c, &dent_key, dir->inum, nm); + + key_write(c, &dent_key, dent->key); + dent->inum = cpu_to_le64(inode->inum); + dent->type = ubifs_get_dent_type(inode->mode); + dent->nlen = cpu_to_le16(fname_len(nm)); + memcpy(dent->name, fname_name(nm), fname_len(nm)); + dent->name[fname_len(nm)] = '\0'; + set_dent_cookie(c, dent); + + zero_dent_node_unused(dent); + ubifs_prep_grp_node(c, dent, dlen, 0); + err = ubifs_node_calc_hash(c, dent, hash_dent); + if (err) + goto out_release; + } + + ino = (void *)dent + aligned_dlen; + pack_inode(c, ino, ui, dir_ui == NULL ? 1 : 0); + err = ubifs_node_calc_hash(c, ino, hash_ino); + if (err) + goto out_release; + + if (dir_ui) { + ino = (void *)ino + aligned_ilen; + pack_inode(c, ino, dir_ui, 1); + err = ubifs_node_calc_hash(c, ino, hash_ino_dir); + if (err) + goto out_release; + } + + err = write_head(c, BASEHD, dent, len, &lnum, &dent_offs, 0); + if (err) + goto out_release; + release_head(c, BASEHD); + kfree(dent); + ubifs_add_auth_dirt(c, lnum); + + if (nm) { + err = ubifs_tnc_add_nm(c, &dent_key, lnum, dent_offs, dlen, + hash_dent, nm); + if (err) { + ubifs_assert(c, !get_failure_reason_callback(c)); + goto out_ro; + } + } + + ino_key_init(c, &ino_key, inode->inum); + ino_offs = dent_offs + aligned_dlen; + err = ubifs_tnc_add(c, &ino_key, lnum, ino_offs, ilen, hash_ino); + if (err) { + ubifs_assert(c, !get_failure_reason_callback(c)); + goto out_ro; + } + + if (dir_ui) { + ino_key_init(c, &ino_key, dir->inum); + ino_offs += aligned_ilen; + err = ubifs_tnc_add(c, &ino_key, lnum, ino_offs, dir_ilen, + hash_ino_dir); + if (err) { + ubifs_assert(c, !get_failure_reason_callback(c)); + goto out_ro; + } + } + + finish_reservation(c); + return 0; + +out_free: + kfree(dent); + return err; + +out_release: + release_head(c, BASEHD); + kfree(dent); +out_ro: + ubifs_ro_mode(c, err); + finish_reservation(c); + return err; +} diff --git a/ubifs-utils/libubifs/key.h b/ubifs-utils/libubifs/key.h new file mode 100644 index 0000000..0a35c6b --- /dev/null +++ b/ubifs-utils/libubifs/key.h @@ -0,0 +1,492 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * This file is part of UBIFS. + * + * Copyright (C) 2006-2008 Nokia Corporation. + * + * Authors: Artem Bityutskiy (Битюцкий Артём) + * Adrian Hunter + */ + +/* + * This header contains various key-related definitions and helper function. + * UBIFS allows several key schemes, so we access key fields only via these + * helpers. At the moment only one key scheme is supported. + * + * Simple key scheme + * ~~~~~~~~~~~~~~~~~ + * + * Keys are 64-bits long. First 32-bits are inode number (parent inode number + * in case of direntry key). Next 3 bits are node type. The last 29 bits are + * 4KiB offset in case of inode node, and direntry hash in case of a direntry + * node. We use "r5" hash borrowed from reiserfs. + */ + +/* + * Lot's of the key helpers require a struct ubifs_info *c as the first parameter. + * But we are not using it at all currently. That's designed for future extensions of + * different c->key_format. But right now, there is only one key type, UBIFS_SIMPLE_KEY_FMT. + */ + +#ifndef __UBIFS_KEY_H__ +#define __UBIFS_KEY_H__ + +/** + * key_mask_hash - mask a valid hash value. + * @val: value to be masked + * + * We use hash values as offset in directories, so values %0 and %1 are + * reserved for "." and "..". %2 is reserved for "end of readdir" marker. This + * function makes sure the reserved values are not used. + */ +static inline uint32_t key_mask_hash(uint32_t hash) +{ + hash &= UBIFS_S_KEY_HASH_MASK; + if (unlikely(hash <= 2)) + hash += 3; + return hash; +} + +/** + * key_r5_hash - R5 hash function (borrowed from reiserfs). + * @s: direntry name + * @len: name length + */ +static inline uint32_t key_r5_hash(const char *s, int len) +{ + uint32_t a = 0; + const signed char *str = (const signed char *)s; + + while (len--) { + a += *str << 4; + a += *str >> 4; + a *= 11; + str++; + } + + return key_mask_hash(a); +} + +/** + * key_test_hash - testing hash function. + * @str: direntry name + * @len: name length + */ +static inline uint32_t key_test_hash(const char *str, int len) +{ + uint32_t a = 0; + + len = min_t(uint32_t, len, 4); + memcpy(&a, str, len); + return key_mask_hash(a); +} + +/** + * ino_key_init - initialize inode key. + * @c: UBIFS file-system description object + * @key: key to initialize + * @inum: inode number + */ +static inline void ino_key_init(__unused const struct ubifs_info *c, + union ubifs_key *key, ino_t inum) +{ + key->u32[0] = inum; + key->u32[1] = UBIFS_INO_KEY << UBIFS_S_KEY_BLOCK_BITS; +} + +/** + * ino_key_init_flash - initialize on-flash inode key. + * @c: UBIFS file-system description object + * @k: key to initialize + * @inum: inode number + */ +static inline void ino_key_init_flash(__unused const struct ubifs_info *c, + void *k, ino_t inum) +{ + union ubifs_key *key = k; + + key->j32[0] = cpu_to_le32(inum); + key->j32[1] = cpu_to_le32(UBIFS_INO_KEY << UBIFS_S_KEY_BLOCK_BITS); + memset(k + 8, 0, UBIFS_MAX_KEY_LEN - 8); +} + +/** + * lowest_ino_key - get the lowest possible inode key. + * @c: UBIFS file-system description object + * @key: key to initialize + * @inum: inode number + */ +static inline void lowest_ino_key(__unused const struct ubifs_info *c, + union ubifs_key *key, ino_t inum) +{ + key->u32[0] = inum; + key->u32[1] = 0; +} + +/** + * highest_ino_key - get the highest possible inode key. + * @c: UBIFS file-system description object + * @key: key to initialize + * @inum: inode number + */ +static inline void highest_ino_key(__unused const struct ubifs_info *c, + union ubifs_key *key, ino_t inum) +{ + key->u32[0] = inum; + key->u32[1] = 0xffffffff; +} + +/** + * dent_key_init - initialize directory entry key. + * @c: UBIFS file-system description object + * @key: key to initialize + * @inum: parent inode number + * @nm: direntry name and length. Not a string when encrypted! + */ +static inline void dent_key_init(const struct ubifs_info *c, + union ubifs_key *key, ino_t inum, + const struct fscrypt_name *nm) +{ + uint32_t hash = c->key_hash(fname_name(nm), fname_len(nm)); + + ubifs_assert(c, !(hash & ~UBIFS_S_KEY_HASH_MASK)); + key->u32[0] = inum; + key->u32[1] = hash | (UBIFS_DENT_KEY << UBIFS_S_KEY_HASH_BITS); +} + +/** + * dent_key_init_hash - initialize directory entry key without re-calculating + * hash function. + * @c: UBIFS file-system description object + * @key: key to initialize + * @inum: parent inode number + * @hash: direntry name hash + */ +static inline void dent_key_init_hash(const struct ubifs_info *c, + union ubifs_key *key, ino_t inum, + uint32_t hash) +{ + ubifs_assert(c, !(hash & ~UBIFS_S_KEY_HASH_MASK)); + key->u32[0] = inum; + key->u32[1] = hash | (UBIFS_DENT_KEY << UBIFS_S_KEY_HASH_BITS); +} + +/** + * xent_key_init - initialize extended attribute entry key. + * @c: UBIFS file-system description object + * @key: key to initialize + * @inum: host inode number + * @nm: extended attribute entry name and length + */ +static inline void xent_key_init(const struct ubifs_info *c, + union ubifs_key *key, ino_t inum, + const struct fscrypt_name *nm) +{ + uint32_t hash = c->key_hash(fname_name(nm), fname_len(nm)); + + ubifs_assert(c, !(hash & ~UBIFS_S_KEY_HASH_MASK)); + key->u32[0] = inum; + key->u32[1] = hash | (UBIFS_XENT_KEY << UBIFS_S_KEY_HASH_BITS); +} + +/** + * lowest_xent_key - get the lowest possible extended attribute entry key. + * @c: UBIFS file-system description object + * @key: where to store the lowest key + * @inum: host inode number + */ +static inline void lowest_xent_key(__unused const struct ubifs_info *c, + union ubifs_key *key, ino_t inum) +{ + key->u32[0] = inum; + key->u32[1] = UBIFS_XENT_KEY << UBIFS_S_KEY_HASH_BITS; +} + +/** + * data_key_init - initialize data key. + * @c: UBIFS file-system description object + * @key: key to initialize + * @inum: inode number + * @block: block number + */ +static inline void data_key_init(const struct ubifs_info *c, + union ubifs_key *key, ino_t inum, + unsigned int block) +{ + ubifs_assert(c, !(block & ~UBIFS_S_KEY_BLOCK_MASK)); + key->u32[0] = inum; + key->u32[1] = block | (UBIFS_DATA_KEY << UBIFS_S_KEY_BLOCK_BITS); +} + +/** + * highest_data_key - get the highest possible data key for an inode. + * @c: UBIFS file-system description object + * @key: key to initialize + * @inum: inode number + */ +static inline void highest_data_key(const struct ubifs_info *c, + union ubifs_key *key, ino_t inum) +{ + data_key_init(c, key, inum, UBIFS_S_KEY_BLOCK_MASK); +} + +/** + * trun_key_init - initialize truncation node key. + * @c: UBIFS file-system description object + * @key: key to initialize + * @inum: inode number + * + * Note, UBIFS does not have truncation keys on the media and this function is + * only used for purposes of replay. + */ +static inline void trun_key_init(__unused const struct ubifs_info *c, + union ubifs_key *key, ino_t inum) +{ + key->u32[0] = inum; + key->u32[1] = UBIFS_TRUN_KEY << UBIFS_S_KEY_BLOCK_BITS; +} + +/** + * invalid_key_init - initialize invalid node key. + * @c: UBIFS file-system description object + * @key: key to initialize + * + * This is a helper function which marks a @key object as invalid. + */ +static inline void invalid_key_init(__unused const struct ubifs_info *c, + union ubifs_key *key) +{ + key->u32[0] = 0xDEADBEAF; + key->u32[1] = UBIFS_INVALID_KEY; +} + +/** + * key_type - get key type. + * @c: UBIFS file-system description object + * @key: key to get type of + */ +static inline int key_type(__unused const struct ubifs_info *c, + const union ubifs_key *key) +{ + return key->u32[1] >> UBIFS_S_KEY_BLOCK_BITS; +} + +/** + * key_type_flash - get type of a on-flash formatted key. + * @c: UBIFS file-system description object + * @k: key to get type of + */ +static inline int key_type_flash(__unused const struct ubifs_info *c, + const void *k) +{ + const union ubifs_key *key = k; + + return le32_to_cpu(key->j32[1]) >> UBIFS_S_KEY_BLOCK_BITS; +} + +/** + * key_inum - fetch inode number from key. + * @c: UBIFS file-system description object + * @k: key to fetch inode number from + */ +static inline ino_t key_inum(__unused const struct ubifs_info *c, const void *k) +{ + const union ubifs_key *key = k; + + return key->u32[0]; +} + +/** + * key_inum_flash - fetch inode number from an on-flash formatted key. + * @c: UBIFS file-system description object + * @k: key to fetch inode number from + */ +static inline ino_t key_inum_flash(__unused const struct ubifs_info *c, + const void *k) +{ + const union ubifs_key *key = k; + + return le32_to_cpu(key->j32[0]); +} + +/** + * key_hash - get directory entry hash. + * @c: UBIFS file-system description object + * @key: the key to get hash from + */ +static inline uint32_t key_hash(__unused const struct ubifs_info *c, + const union ubifs_key *key) +{ + return key->u32[1] & UBIFS_S_KEY_HASH_MASK; +} + +/** + * key_hash_flash - get directory entry hash from an on-flash formatted key. + * @c: UBIFS file-system description object + * @k: the key to get hash from + */ +static inline uint32_t key_hash_flash(__unused const struct ubifs_info *c, + const void *k) +{ + const union ubifs_key *key = k; + + return le32_to_cpu(key->j32[1]) & UBIFS_S_KEY_HASH_MASK; +} + +/** + * key_block - get data block number. + * @c: UBIFS file-system description object + * @key: the key to get the block number from + */ +static inline unsigned int key_block(__unused const struct ubifs_info *c, + const union ubifs_key *key) +{ + return key->u32[1] & UBIFS_S_KEY_BLOCK_MASK; +} + +/** + * key_block_flash - get data block number from an on-flash formatted key. + * @c: UBIFS file-system description object + * @k: the key to get the block number from + */ +static inline unsigned int key_block_flash(__unused const struct ubifs_info *c, + const void *k) +{ + const union ubifs_key *key = k; + + return le32_to_cpu(key->j32[1]) & UBIFS_S_KEY_BLOCK_MASK; +} + +/** + * key_read - transform a key to in-memory format. + * @c: UBIFS file-system description object + * @from: the key to transform + * @to: the key to store the result + */ +static inline void key_read(__unused const struct ubifs_info *c, + const void *from, union ubifs_key *to) +{ + const union ubifs_key *f = from; + + to->u32[0] = le32_to_cpu(f->j32[0]); + to->u32[1] = le32_to_cpu(f->j32[1]); +} + +/** + * key_write - transform a key from in-memory format. + * @c: UBIFS file-system description object + * @from: the key to transform + * @to: the key to store the result + */ +static inline void key_write(__unused const struct ubifs_info *c, + const union ubifs_key *from, void *to) +{ + union ubifs_key *t = to; + + t->j32[0] = cpu_to_le32(from->u32[0]); + t->j32[1] = cpu_to_le32(from->u32[1]); + memset(to + 8, 0, UBIFS_MAX_KEY_LEN - 8); +} + +/** + * key_write_idx - transform a key from in-memory format for the index. + * @c: UBIFS file-system description object + * @from: the key to transform + * @to: the key to store the result + */ +static inline void key_write_idx(__unused const struct ubifs_info *c, + const union ubifs_key *from, void *to) +{ + union ubifs_key *t = to; + + t->j32[0] = cpu_to_le32(from->u32[0]); + t->j32[1] = cpu_to_le32(from->u32[1]); +} + +/** + * key_copy - copy a key. + * @c: UBIFS file-system description object + * @from: the key to copy from + * @to: the key to copy to + */ +static inline void key_copy(__unused const struct ubifs_info *c, + const union ubifs_key *from, union ubifs_key *to) +{ + to->u64[0] = from->u64[0]; +} + +/** + * keys_cmp - compare keys. + * @c: UBIFS file-system description object + * @key1: the first key to compare + * @key2: the second key to compare + * + * This function compares 2 keys and returns %-1 if @key1 is less than + * @key2, %0 if the keys are equivalent and %1 if @key1 is greater than @key2. + */ +static inline int keys_cmp(__unused const struct ubifs_info *c, + const union ubifs_key *key1, + const union ubifs_key *key2) +{ + if (key1->u32[0] < key2->u32[0]) + return -1; + if (key1->u32[0] > key2->u32[0]) + return 1; + if (key1->u32[1] < key2->u32[1]) + return -1; + if (key1->u32[1] > key2->u32[1]) + return 1; + + return 0; +} + +/** + * keys_eq - determine if keys are equivalent. + * @c: UBIFS file-system description object + * @key1: the first key to compare + * @key2: the second key to compare + * + * This function compares 2 keys and returns %1 if @key1 is equal to @key2 and + * %0 if not. + */ +static inline int keys_eq(__unused const struct ubifs_info *c, + const union ubifs_key *key1, + const union ubifs_key *key2) +{ + if (key1->u32[0] != key2->u32[0]) + return 0; + if (key1->u32[1] != key2->u32[1]) + return 0; + return 1; +} + +/** + * is_hash_key - is a key vulnerable to hash collisions. + * @c: UBIFS file-system description object + * @key: key + * + * This function returns %1 if @key is a hashed key or %0 otherwise. + */ +static inline int is_hash_key(const struct ubifs_info *c, + const union ubifs_key *key) +{ + int type = key_type(c, key); + + return type == UBIFS_DENT_KEY || type == UBIFS_XENT_KEY; +} + +/** + * key_max_inode_size - get maximum file size allowed by current key format. + * @c: UBIFS file-system description object + */ +static inline unsigned long long key_max_inode_size(const struct ubifs_info *c) +{ + switch (c->key_fmt) { + case UBIFS_SIMPLE_KEY_FMT: + return (1ULL << UBIFS_S_KEY_BLOCK_BITS) * UBIFS_BLOCK_SIZE; + default: + return 0; + } +} + +#endif /* !__UBIFS_KEY_H__ */ diff --git a/ubifs-utils/libubifs/log.c b/ubifs-utils/libubifs/log.c new file mode 100644 index 0000000..c3dfd98 --- /dev/null +++ b/ubifs-utils/libubifs/log.c @@ -0,0 +1,750 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * This file is part of UBIFS. + * + * Copyright (C) 2006-2008 Nokia Corporation. + * + * Authors: Artem Bityutskiy (Битюцкий Артём) + * Adrian Hunter + */ + +/* + * This file is a part of UBIFS journal implementation and contains various + * functions which manipulate the log. The log is a fixed area on the flash + * which does not contain any data but refers to buds. The log is a part of the + * journal. + */ + +#include "linux_err.h" +#include "bitops.h" +#include "kmem.h" +#include "ubifs.h" +#include "defs.h" +#include "debug.h" +#include "misc.h" + +static int dbg_check_bud_bytes(struct ubifs_info *c); + +/** + * ubifs_search_bud - search bud LEB. + * @c: UBIFS file-system description object + * @lnum: logical eraseblock number to search + * + * This function searches bud LEB @lnum. Returns bud description object in case + * of success and %NULL if there is no bud with this LEB number. + */ +struct ubifs_bud *ubifs_search_bud(struct ubifs_info *c, int lnum) +{ + struct rb_node *p; + struct ubifs_bud *bud; + + spin_lock(&c->buds_lock); + p = c->buds.rb_node; + while (p) { + bud = rb_entry(p, struct ubifs_bud, rb); + if (lnum < bud->lnum) + p = p->rb_left; + else if (lnum > bud->lnum) + p = p->rb_right; + else { + spin_unlock(&c->buds_lock); + return bud; + } + } + spin_unlock(&c->buds_lock); + return NULL; +} + +/** + * ubifs_get_wbuf - get the wbuf associated with a LEB, if there is one. + * @c: UBIFS file-system description object + * @lnum: logical eraseblock number to search + * + * This functions returns the wbuf for @lnum or %NULL if there is not one. + */ +struct ubifs_wbuf *ubifs_get_wbuf(struct ubifs_info *c, int lnum) +{ + struct rb_node *p; + struct ubifs_bud *bud; + int jhead; + + if (!c->jheads) + return NULL; + + spin_lock(&c->buds_lock); + p = c->buds.rb_node; + while (p) { + bud = rb_entry(p, struct ubifs_bud, rb); + if (lnum < bud->lnum) + p = p->rb_left; + else if (lnum > bud->lnum) + p = p->rb_right; + else { + jhead = bud->jhead; + spin_unlock(&c->buds_lock); + return &c->jheads[jhead].wbuf; + } + } + spin_unlock(&c->buds_lock); + return NULL; +} + +/** + * empty_log_bytes - calculate amount of empty space in the log. + * @c: UBIFS file-system description object + */ +static inline long long empty_log_bytes(const struct ubifs_info *c) +{ + long long h, t; + + h = (long long)c->lhead_lnum * c->leb_size + c->lhead_offs; + t = (long long)c->ltail_lnum * c->leb_size; + + if (h > t) + return c->log_bytes - h + t; + else if (h != t) + return t - h; + else if (c->lhead_lnum != c->ltail_lnum) + return 0; + else + return c->log_bytes; +} + +/** + * ubifs_add_bud - add bud LEB to the tree of buds and its journal head list. + * @c: UBIFS file-system description object + * @bud: the bud to add + */ +void ubifs_add_bud(struct ubifs_info *c, struct ubifs_bud *bud) +{ + struct rb_node **p, *parent = NULL; + struct ubifs_bud *b; + struct ubifs_jhead *jhead; + + spin_lock(&c->buds_lock); + p = &c->buds.rb_node; + while (*p) { + parent = *p; + b = rb_entry(parent, struct ubifs_bud, rb); + ubifs_assert(c, bud->lnum != b->lnum); + if (bud->lnum < b->lnum) + p = &(*p)->rb_left; + else + p = &(*p)->rb_right; + } + + rb_link_node(&bud->rb, parent, p); + rb_insert_color(&bud->rb, &c->buds); + if (c->jheads) { + jhead = &c->jheads[bud->jhead]; + list_add_tail(&bud->list, &jhead->buds_list); + } else + ubifs_assert(c, c->replaying && c->ro_mount); + + /* + * Note, although this is a new bud, we anyway account this space now, + * before any data has been written to it, because this is about to + * guarantee fixed mount time, and this bud will anyway be read and + * scanned. + */ + c->bud_bytes += c->leb_size - bud->start; + + dbg_log("LEB %d:%d, jhead %s, bud_bytes %lld", bud->lnum, + bud->start, dbg_jhead(bud->jhead), c->bud_bytes); + spin_unlock(&c->buds_lock); +} + +/** + * ubifs_add_bud_to_log - add a new bud to the log. + * @c: UBIFS file-system description object + * @jhead: journal head the bud belongs to + * @lnum: LEB number of the bud + * @offs: starting offset of the bud + * + * This function writes a reference node for the new bud LEB @lnum to the log, + * and adds it to the buds trees. It also makes sure that log size does not + * exceed the 'c->max_bud_bytes' limit. Returns zero in case of success, + * %-EAGAIN if commit is required, and a negative error code in case of + * failure. + */ +int ubifs_add_bud_to_log(struct ubifs_info *c, int jhead, int lnum, int offs) +{ + int err; + struct ubifs_bud *bud; + struct ubifs_ref_node *ref; + + bud = kmalloc(sizeof(struct ubifs_bud), GFP_NOFS); + if (!bud) + return -ENOMEM; + ref = kzalloc(c->ref_node_alsz, GFP_NOFS); + if (!ref) { + kfree(bud); + return -ENOMEM; + } + + mutex_lock(&c->log_mutex); + ubifs_assert(c, !c->ro_media && !c->ro_mount); + if (c->ro_error) { + err = -EROFS; + goto out_unlock; + } + + /* Make sure we have enough space in the log */ + if (empty_log_bytes(c) - c->ref_node_alsz < c->min_log_bytes) { + dbg_log("not enough log space - %lld, required %d", + empty_log_bytes(c), c->min_log_bytes); + ubifs_commit_required(c); + err = -EAGAIN; + goto out_unlock; + } + + /* + * Make sure the amount of space in buds will not exceed the + * 'c->max_bud_bytes' limit, because we want to guarantee mount time + * limits. + * + * It is not necessary to hold @c->buds_lock when reading @c->bud_bytes + * because we are holding @c->log_mutex. All @c->bud_bytes take place + * when both @c->log_mutex and @c->bud_bytes are locked. + */ + if (c->bud_bytes + c->leb_size - offs > c->max_bud_bytes) { + dbg_log("bud bytes %lld (%lld max), require commit", + c->bud_bytes, c->max_bud_bytes); + ubifs_commit_required(c); + err = -EAGAIN; + goto out_unlock; + } + + /* + * If the journal is full enough - start background commit. Note, it is + * OK to read 'c->cmt_state' without spinlock because integer reads + * are atomic in the kernel. + */ + if (c->bud_bytes >= c->bg_bud_bytes && + c->cmt_state == COMMIT_RESTING) { + dbg_log("bud bytes %lld (%lld max), initiate BG commit", + c->bud_bytes, c->max_bud_bytes); + ubifs_request_bg_commit(c); + } + + bud->lnum = lnum; + bud->start = offs; + bud->jhead = jhead; + bud->log_hash = NULL; + + ref->ch.node_type = UBIFS_REF_NODE; + ref->lnum = cpu_to_le32(bud->lnum); + ref->offs = cpu_to_le32(bud->start); + ref->jhead = cpu_to_le32(jhead); + + if (c->lhead_offs > c->leb_size - c->ref_node_alsz) { + c->lhead_lnum = ubifs_next_log_lnum(c, c->lhead_lnum); + ubifs_assert(c, c->lhead_lnum != c->ltail_lnum); + c->lhead_offs = 0; + } + + if (c->lhead_offs == 0) { + /* Must ensure next log LEB has been unmapped */ + err = ubifs_leb_unmap(c, c->lhead_lnum); + if (err) + goto out_unlock; + } + + if (bud->start == 0) { + /* + * Before writing the LEB reference which refers an empty LEB + * to the log, we have to make sure it is mapped, because + * otherwise we'd risk to refer an LEB with garbage in case of + * an unclean reboot, because the target LEB might have been + * unmapped, but not yet physically erased. + */ + err = ubifs_leb_map(c, bud->lnum); + if (err) + goto out_unlock; + } + + dbg_log("write ref LEB %d:%d", + c->lhead_lnum, c->lhead_offs); + err = ubifs_write_node(c, ref, UBIFS_REF_NODE_SZ, c->lhead_lnum, + c->lhead_offs); + if (err) + goto out_unlock; + + err = ubifs_shash_update(c, c->log_hash, ref, UBIFS_REF_NODE_SZ); + if (err) + goto out_unlock; + + err = ubifs_shash_copy_state(c, c->log_hash, c->jheads[jhead].log_hash); + if (err) + goto out_unlock; + + c->lhead_offs += c->ref_node_alsz; + + ubifs_add_bud(c, bud); + + mutex_unlock(&c->log_mutex); + kfree(ref); + return 0; + +out_unlock: + mutex_unlock(&c->log_mutex); + kfree(ref); + kfree(bud); + return err; +} + +/** + * remove_buds - remove used buds. + * @c: UBIFS file-system description object + * + * This function removes use buds from the buds tree. It does not remove the + * buds which are pointed to by journal heads. + */ +static void remove_buds(struct ubifs_info *c) +{ + struct rb_node *p; + + ubifs_assert(c, list_empty(&c->old_buds)); + c->cmt_bud_bytes = 0; + spin_lock(&c->buds_lock); + p = rb_first(&c->buds); + while (p) { + struct rb_node *p1 = p; + struct ubifs_bud *bud; + struct ubifs_wbuf *wbuf; + + p = rb_next(p); + bud = rb_entry(p1, struct ubifs_bud, rb); + wbuf = &c->jheads[bud->jhead].wbuf; + + if (wbuf->lnum == bud->lnum) { + /* + * Do not remove buds which are pointed to by journal + * heads (non-closed buds). + */ + c->cmt_bud_bytes += wbuf->offs - bud->start; + dbg_log("preserve %d:%d, jhead %s, bud bytes %d, cmt_bud_bytes %lld", + bud->lnum, bud->start, dbg_jhead(bud->jhead), + wbuf->offs - bud->start, c->cmt_bud_bytes); + bud->start = wbuf->offs; + } else { + c->cmt_bud_bytes += c->leb_size - bud->start; + dbg_log("remove %d:%d, jhead %s, bud bytes %d, cmt_bud_bytes %lld", + bud->lnum, bud->start, dbg_jhead(bud->jhead), + c->leb_size - bud->start, c->cmt_bud_bytes); + rb_erase(p1, &c->buds); + /* + * If the commit does not finish, the recovery will need + * to replay the journal, in which case the old buds + * must be unchanged. Do not release them until post + * commit i.e. do not allow them to be garbage + * collected. + */ + list_move(&bud->list, &c->old_buds); + } + } + spin_unlock(&c->buds_lock); +} + +/** + * ubifs_log_start_commit - start commit. + * @c: UBIFS file-system description object + * @ltail_lnum: return new log tail LEB number + * + * The commit operation starts with writing "commit start" node to the log and + * reference nodes for all journal heads which will define new journal after + * the commit has been finished. The commit start and reference nodes are + * written in one go to the nearest empty log LEB (hence, when commit is + * finished UBIFS may safely unmap all the previous log LEBs). This function + * returns zero in case of success and a negative error code in case of + * failure. + */ +int ubifs_log_start_commit(struct ubifs_info *c, int *ltail_lnum) +{ + void *buf; + struct ubifs_cs_node *cs; + struct ubifs_ref_node *ref; + int err, i, max_len, len; + + err = dbg_check_bud_bytes(c); + if (err) + return err; + + max_len = UBIFS_CS_NODE_SZ + c->jhead_cnt * UBIFS_REF_NODE_SZ; + max_len = ALIGN(max_len, c->min_io_size); + buf = cs = kmalloc(max_len, GFP_NOFS); + if (!buf) + return -ENOMEM; + + cs->ch.node_type = UBIFS_CS_NODE; + cs->cmt_no = cpu_to_le64(c->cmt_no); + ubifs_prepare_node(c, cs, UBIFS_CS_NODE_SZ, 0); + + err = ubifs_shash_init(c, c->log_hash); + if (err) + goto out; + + err = ubifs_shash_update(c, c->log_hash, cs, UBIFS_CS_NODE_SZ); + if (err < 0) + goto out; + + /* + * Note, we do not lock 'c->log_mutex' because this is the commit start + * phase and we are exclusively using the log. And we do not lock + * write-buffer because nobody can write to the file-system at this + * phase. + */ + + len = UBIFS_CS_NODE_SZ; + for (i = 0; i < c->jhead_cnt; i++) { + int lnum = c->jheads[i].wbuf.lnum; + int offs = c->jheads[i].wbuf.offs; + + if (lnum == -1 || offs == c->leb_size) + continue; + + dbg_log("add ref to LEB %d:%d for jhead %s", + lnum, offs, dbg_jhead(i)); + ref = buf + len; + ref->ch.node_type = UBIFS_REF_NODE; + ref->lnum = cpu_to_le32(lnum); + ref->offs = cpu_to_le32(offs); + ref->jhead = cpu_to_le32(i); + + ubifs_prepare_node(c, ref, UBIFS_REF_NODE_SZ, 0); + len += UBIFS_REF_NODE_SZ; + + err = ubifs_shash_update(c, c->log_hash, ref, + UBIFS_REF_NODE_SZ); + if (err) + goto out; + ubifs_shash_copy_state(c, c->log_hash, c->jheads[i].log_hash); + } + + ubifs_pad(c, buf + len, ALIGN(len, c->min_io_size) - len); + + /* Switch to the next log LEB */ + if (c->lhead_offs) { + c->lhead_lnum = ubifs_next_log_lnum(c, c->lhead_lnum); + ubifs_assert(c, c->lhead_lnum != c->ltail_lnum); + c->lhead_offs = 0; + } + + /* Must ensure next LEB has been unmapped */ + err = ubifs_leb_unmap(c, c->lhead_lnum); + if (err) + goto out; + + len = ALIGN(len, c->min_io_size); + dbg_log("writing commit start at LEB %d:0, len %d", c->lhead_lnum, len); + err = ubifs_leb_write(c, c->lhead_lnum, cs, 0, len); + if (err) + goto out; + + *ltail_lnum = c->lhead_lnum; + + c->lhead_offs += len; + ubifs_assert(c, c->lhead_offs < c->leb_size); + + remove_buds(c); + + /* + * We have started the commit and now users may use the rest of the log + * for new writes. + */ + c->min_log_bytes = 0; + +out: + kfree(buf); + return err; +} + +/** + * ubifs_log_end_commit - end commit. + * @c: UBIFS file-system description object + * @ltail_lnum: new log tail LEB number + * + * This function is called on when the commit operation was finished. It + * moves log tail to new position and updates the master node so that it stores + * the new log tail LEB number. Returns zero in case of success and a negative + * error code in case of failure. + */ +int ubifs_log_end_commit(struct ubifs_info *c, int ltail_lnum) +{ + int err; + + /* + * At this phase we have to lock 'c->log_mutex' because UBIFS allows FS + * writes during commit. Its only short "commit" start phase when + * writers are blocked. + */ + mutex_lock(&c->log_mutex); + + dbg_log("old tail was LEB %d:0, new tail is LEB %d:0", + c->ltail_lnum, ltail_lnum); + + c->ltail_lnum = ltail_lnum; + /* + * The commit is finished and from now on it must be guaranteed that + * there is always enough space for the next commit. + */ + c->min_log_bytes = c->leb_size; + + spin_lock(&c->buds_lock); + c->bud_bytes -= c->cmt_bud_bytes; + spin_unlock(&c->buds_lock); + + err = dbg_check_bud_bytes(c); + if (err) + goto out; + + err = ubifs_write_master(c); + +out: + mutex_unlock(&c->log_mutex); + return err; +} + +/** + * ubifs_log_post_commit - things to do after commit is completed. + * @c: UBIFS file-system description object + * @old_ltail_lnum: old log tail LEB number + * + * Release buds only after commit is completed, because they must be unchanged + * if recovery is needed. + * + * Unmap log LEBs only after commit is completed, because they may be needed for + * recovery. + * + * This function returns %0 on success and a negative error code on failure. + */ +int ubifs_log_post_commit(struct ubifs_info *c, int old_ltail_lnum) +{ + int lnum, err = 0; + + while (!list_empty(&c->old_buds)) { + struct ubifs_bud *bud; + + bud = list_entry(c->old_buds.next, struct ubifs_bud, list); + err = ubifs_return_leb(c, bud->lnum); + if (err) + return err; + list_del(&bud->list); + kfree(bud->log_hash); + kfree(bud); + } + mutex_lock(&c->log_mutex); + for (lnum = old_ltail_lnum; lnum != c->ltail_lnum; + lnum = ubifs_next_log_lnum(c, lnum)) { + dbg_log("unmap log LEB %d", lnum); + err = ubifs_leb_unmap(c, lnum); + if (err) + goto out; + } +out: + mutex_unlock(&c->log_mutex); + return err; +} + +/** + * struct done_ref - references that have been done. + * @rb: rb-tree node + * @lnum: LEB number + */ +struct done_ref { + struct rb_node rb; + int lnum; +}; + +/** + * done_already - determine if a reference has been done already. + * @done_tree: rb-tree to store references that have been done + * @lnum: LEB number of reference + * + * This function returns %1 if the reference has been done, %0 if not, otherwise + * a negative error code is returned. + */ +static int done_already(struct rb_root *done_tree, int lnum) +{ + struct rb_node **p = &done_tree->rb_node, *parent = NULL; + struct done_ref *dr; + + while (*p) { + parent = *p; + dr = rb_entry(parent, struct done_ref, rb); + if (lnum < dr->lnum) + p = &(*p)->rb_left; + else if (lnum > dr->lnum) + p = &(*p)->rb_right; + else + return 1; + } + + dr = kzalloc(sizeof(struct done_ref), GFP_NOFS); + if (!dr) + return -ENOMEM; + + dr->lnum = lnum; + + rb_link_node(&dr->rb, parent, p); + rb_insert_color(&dr->rb, done_tree); + + return 0; +} + +/** + * destroy_done_tree - destroy the done tree. + * @done_tree: done tree to destroy + */ +static void destroy_done_tree(struct rb_root *done_tree) +{ + struct done_ref *dr, *n; + + rbtree_postorder_for_each_entry_safe(dr, n, done_tree, rb) + kfree(dr); +} + +/** + * add_node - add a node to the consolidated log. + * @c: UBIFS file-system description object + * @buf: buffer to which to add + * @lnum: LEB number to which to write is passed and returned here + * @offs: offset to where to write is passed and returned here + * @node: node to add + * + * This function returns %0 on success and a negative error code on failure. + */ +static int add_node(struct ubifs_info *c, void *buf, int *lnum, int *offs, + void *node) +{ + struct ubifs_ch *ch = node; + int len = le32_to_cpu(ch->len), remains = c->leb_size - *offs; + + if (len > remains) { + int sz = ALIGN(*offs, c->min_io_size), err; + + ubifs_pad(c, buf + *offs, sz - *offs); + err = ubifs_leb_change(c, *lnum, buf, sz); + if (err) + return err; + *lnum = ubifs_next_log_lnum(c, *lnum); + *offs = 0; + } + memcpy(buf + *offs, node, len); + *offs += ALIGN(len, 8); + return 0; +} + +/** + * ubifs_consolidate_log - consolidate the log. + * @c: UBIFS file-system description object + * + * Repeated failed commits could cause the log to be full, but at least 1 LEB is + * needed for commit. This function rewrites the reference nodes in the log + * omitting duplicates, and failed CS nodes, and leaving no gaps. + * + * This function returns %0 on success and a negative error code on failure. + */ +int ubifs_consolidate_log(struct ubifs_info *c) +{ + struct ubifs_scan_leb *sleb; + struct ubifs_scan_node *snod; + struct rb_root done_tree = RB_ROOT; + int lnum, err, first = 1, write_lnum, offs = 0; + void *buf; + + dbg_rcvry("log tail LEB %d, log head LEB %d", c->ltail_lnum, + c->lhead_lnum); + buf = vmalloc(c->leb_size); + if (!buf) + return -ENOMEM; + lnum = c->ltail_lnum; + write_lnum = lnum; + while (1) { + sleb = ubifs_scan(c, lnum, 0, c->sbuf, 0); + if (IS_ERR(sleb)) { + err = PTR_ERR(sleb); + goto out_free; + } + list_for_each_entry(snod, &sleb->nodes, list) { + switch (snod->type) { + case UBIFS_REF_NODE: { + struct ubifs_ref_node *ref = snod->node; + int ref_lnum = le32_to_cpu(ref->lnum); + + err = done_already(&done_tree, ref_lnum); + if (err < 0) + goto out_scan; + if (err != 1) { + err = add_node(c, buf, &write_lnum, + &offs, snod->node); + if (err) + goto out_scan; + } + break; + } + case UBIFS_CS_NODE: + if (!first) + break; + err = add_node(c, buf, &write_lnum, &offs, + snod->node); + if (err) + goto out_scan; + first = 0; + break; + } + } + ubifs_scan_destroy(sleb); + if (lnum == c->lhead_lnum) + break; + lnum = ubifs_next_log_lnum(c, lnum); + } + if (offs) { + int sz = ALIGN(offs, c->min_io_size); + + ubifs_pad(c, buf + offs, sz - offs); + err = ubifs_leb_change(c, write_lnum, buf, sz); + if (err) + goto out_free; + offs = ALIGN(offs, c->min_io_size); + } + destroy_done_tree(&done_tree); + vfree(buf); + if (write_lnum == c->lhead_lnum) { + set_failure_reason_callback(c, FR_DATA_CORRUPTED); + ubifs_err(c, "log is too full"); + return -EINVAL; + } + /* Unmap remaining LEBs */ + lnum = write_lnum; + do { + lnum = ubifs_next_log_lnum(c, lnum); + err = ubifs_leb_unmap(c, lnum); + if (err) + return err; + } while (lnum != c->lhead_lnum); + c->lhead_lnum = write_lnum; + c->lhead_offs = offs; + dbg_rcvry("new log head at %d:%d", c->lhead_lnum, c->lhead_offs); + return 0; + +out_scan: + ubifs_scan_destroy(sleb); +out_free: + destroy_done_tree(&done_tree); + vfree(buf); + return err; +} + +/** + * dbg_check_bud_bytes - make sure bud bytes calculation are all right. + * @c: UBIFS file-system description object + * + * This function makes sure the amount of flash space used by closed buds + * ('c->bud_bytes' is correct). Returns zero in case of success and %-EINVAL in + * case of failure. + */ +static int dbg_check_bud_bytes(__unused struct ubifs_info *c) +{ + return 0; +} diff --git a/ubifs-utils/libubifs/lprops.c b/ubifs-utils/libubifs/lprops.c new file mode 100644 index 0000000..a7a2305 --- /dev/null +++ b/ubifs-utils/libubifs/lprops.c @@ -0,0 +1,864 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * This file is part of UBIFS. + * + * Copyright (C) 2006-2008 Nokia Corporation. + * + * Authors: Adrian Hunter + * Artem Bityutskiy (Битюцкий Артём) + */ + +/* + * This file implements the functions that access LEB properties and their + * categories. LEBs are categorized based on the needs of UBIFS, and the + * categories are stored as either heaps or lists to provide a fast way of + * finding a LEB in a particular category. For example, UBIFS may need to find + * an empty LEB for the journal, or a very dirty LEB for garbage collection. + */ + +#include "linux_err.h" +#include "bitops.h" +#include "kmem.h" +#include "ubifs.h" +#include "defs.h" +#include "debug.h" +#include "misc.h" + +/** + * get_heap_comp_val - get the LEB properties value for heap comparisons. + * @lprops: LEB properties + * @cat: LEB category + */ +static int get_heap_comp_val(struct ubifs_lprops *lprops, int cat) +{ + switch (cat) { + case LPROPS_FREE: + return lprops->free; + case LPROPS_DIRTY_IDX: + return lprops->free + lprops->dirty; + default: + return lprops->dirty; + } +} + +/** + * move_up_lpt_heap - move a new heap entry up as far as possible. + * @c: UBIFS file-system description object + * @heap: LEB category heap + * @lprops: LEB properties to move + * @cat: LEB category + * + * New entries to a heap are added at the bottom and then moved up until the + * parent's value is greater. In the case of LPT's category heaps, the value + * is either the amount of free space or the amount of dirty space, depending + * on the category. + */ +static void move_up_lpt_heap(__unused struct ubifs_info *c, + struct ubifs_lpt_heap *heap, + struct ubifs_lprops *lprops, int cat) +{ + int val1, val2, hpos; + + hpos = lprops->hpos; + if (!hpos) + return; /* Already top of the heap */ + val1 = get_heap_comp_val(lprops, cat); + /* Compare to parent and, if greater, move up the heap */ + do { + int ppos = (hpos - 1) / 2; + + val2 = get_heap_comp_val(heap->arr[ppos], cat); + if (val2 >= val1) + return; + /* Greater than parent so move up */ + heap->arr[ppos]->hpos = hpos; + heap->arr[hpos] = heap->arr[ppos]; + heap->arr[ppos] = lprops; + lprops->hpos = ppos; + hpos = ppos; + } while (hpos); +} + +/** + * adjust_lpt_heap - move a changed heap entry up or down the heap. + * @c: UBIFS file-system description object + * @heap: LEB category heap + * @lprops: LEB properties to move + * @hpos: heap position of @lprops + * @cat: LEB category + * + * Changed entries in a heap are moved up or down until the parent's value is + * greater. In the case of LPT's category heaps, the value is either the amount + * of free space or the amount of dirty space, depending on the category. + */ +static void adjust_lpt_heap(__unused struct ubifs_info *c, + struct ubifs_lpt_heap *heap, + struct ubifs_lprops *lprops, int hpos, int cat) +{ + int val1, val2, val3, cpos; + + val1 = get_heap_comp_val(lprops, cat); + /* Compare to parent and, if greater than parent, move up the heap */ + if (hpos) { + int ppos = (hpos - 1) / 2; + + val2 = get_heap_comp_val(heap->arr[ppos], cat); + if (val1 > val2) { + /* Greater than parent so move up */ + while (1) { + heap->arr[ppos]->hpos = hpos; + heap->arr[hpos] = heap->arr[ppos]; + heap->arr[ppos] = lprops; + lprops->hpos = ppos; + hpos = ppos; + if (!hpos) + return; + ppos = (hpos - 1) / 2; + val2 = get_heap_comp_val(heap->arr[ppos], cat); + if (val1 <= val2) + return; + /* Still greater than parent so keep going */ + } + } + } + + /* Not greater than parent, so compare to children */ + while (1) { + /* Compare to left child */ + cpos = hpos * 2 + 1; + if (cpos >= heap->cnt) + return; + val2 = get_heap_comp_val(heap->arr[cpos], cat); + if (val1 < val2) { + /* Less than left child, so promote biggest child */ + if (cpos + 1 < heap->cnt) { + val3 = get_heap_comp_val(heap->arr[cpos + 1], + cat); + if (val3 > val2) + cpos += 1; /* Right child is bigger */ + } + heap->arr[cpos]->hpos = hpos; + heap->arr[hpos] = heap->arr[cpos]; + heap->arr[cpos] = lprops; + lprops->hpos = cpos; + hpos = cpos; + continue; + } + /* Compare to right child */ + cpos += 1; + if (cpos >= heap->cnt) + return; + val3 = get_heap_comp_val(heap->arr[cpos], cat); + if (val1 < val3) { + /* Less than right child, so promote right child */ + heap->arr[cpos]->hpos = hpos; + heap->arr[hpos] = heap->arr[cpos]; + heap->arr[cpos] = lprops; + lprops->hpos = cpos; + hpos = cpos; + continue; + } + return; + } +} + +/** + * add_to_lpt_heap - add LEB properties to a LEB category heap. + * @c: UBIFS file-system description object + * @lprops: LEB properties to add + * @cat: LEB category + * + * This function returns %1 if @lprops is added to the heap for LEB category + * @cat, otherwise %0 is returned because the heap is full. + */ +static int add_to_lpt_heap(struct ubifs_info *c, struct ubifs_lprops *lprops, + int cat) +{ + struct ubifs_lpt_heap *heap = &c->lpt_heap[cat - 1]; + + if (heap->cnt >= heap->max_cnt) { + const int b = LPT_HEAP_SZ / 2 - 1; + int cpos, val1, val2; + + /* Compare to some other LEB on the bottom of heap */ + /* Pick a position kind of randomly */ + cpos = (((size_t)lprops >> 4) & b) + b; + ubifs_assert(c, cpos >= b); + ubifs_assert(c, cpos < LPT_HEAP_SZ); + ubifs_assert(c, cpos < heap->cnt); + + val1 = get_heap_comp_val(lprops, cat); + val2 = get_heap_comp_val(heap->arr[cpos], cat); + if (val1 > val2) { + struct ubifs_lprops *lp; + + lp = heap->arr[cpos]; + lp->flags &= ~LPROPS_CAT_MASK; + lp->flags |= LPROPS_UNCAT; + list_add(&lp->list, &c->uncat_list); + lprops->hpos = cpos; + heap->arr[cpos] = lprops; + move_up_lpt_heap(c, heap, lprops, cat); + return 1; /* Added to heap */ + } + return 0; /* Not added to heap */ + } else { + lprops->hpos = heap->cnt++; + heap->arr[lprops->hpos] = lprops; + move_up_lpt_heap(c, heap, lprops, cat); + return 1; /* Added to heap */ + } +} + +/** + * remove_from_lpt_heap - remove LEB properties from a LEB category heap. + * @c: UBIFS file-system description object + * @lprops: LEB properties to remove + * @cat: LEB category + */ +static void remove_from_lpt_heap(struct ubifs_info *c, + struct ubifs_lprops *lprops, int cat) +{ + struct ubifs_lpt_heap *heap; + int hpos = lprops->hpos; + + heap = &c->lpt_heap[cat - 1]; + ubifs_assert(c, hpos >= 0 && hpos < heap->cnt); + ubifs_assert(c, heap->arr[hpos] == lprops); + heap->cnt -= 1; + if (hpos < heap->cnt) { + heap->arr[hpos] = heap->arr[heap->cnt]; + heap->arr[hpos]->hpos = hpos; + adjust_lpt_heap(c, heap, heap->arr[hpos], hpos, cat); + } +} + +/** + * lpt_heap_replace - replace lprops in a category heap. + * @c: UBIFS file-system description object + * @new_lprops: LEB properties with which to replace + * @cat: LEB category + * + * During commit it is sometimes necessary to copy a pnode (see dirty_cow_pnode) + * and the lprops that the pnode contains. When that happens, references in + * the category heaps to those lprops must be updated to point to the new + * lprops. This function does that. + */ +static void lpt_heap_replace(struct ubifs_info *c, + struct ubifs_lprops *new_lprops, int cat) +{ + struct ubifs_lpt_heap *heap; + int hpos = new_lprops->hpos; + + heap = &c->lpt_heap[cat - 1]; + heap->arr[hpos] = new_lprops; +} + +/** + * ubifs_add_to_cat - add LEB properties to a category list or heap. + * @c: UBIFS file-system description object + * @lprops: LEB properties to add + * @cat: LEB category to which to add + * + * LEB properties are categorized to enable fast find operations. + */ +void ubifs_add_to_cat(struct ubifs_info *c, struct ubifs_lprops *lprops, + int cat) +{ + switch (cat) { + case LPROPS_DIRTY: + case LPROPS_DIRTY_IDX: + case LPROPS_FREE: + if (add_to_lpt_heap(c, lprops, cat)) + break; + /* No more room on heap so make it un-categorized */ + cat = LPROPS_UNCAT; + fallthrough; + case LPROPS_UNCAT: + list_add(&lprops->list, &c->uncat_list); + break; + case LPROPS_EMPTY: + list_add(&lprops->list, &c->empty_list); + break; + case LPROPS_FREEABLE: + list_add(&lprops->list, &c->freeable_list); + c->freeable_cnt += 1; + break; + case LPROPS_FRDI_IDX: + list_add(&lprops->list, &c->frdi_idx_list); + break; + default: + ubifs_assert(c, 0); + } + + lprops->flags &= ~LPROPS_CAT_MASK; + lprops->flags |= cat; + c->in_a_category_cnt += 1; + ubifs_assert(c, c->in_a_category_cnt <= c->main_lebs); +} + +/** + * ubifs_remove_from_cat - remove LEB properties from a category list or heap. + * @c: UBIFS file-system description object + * @lprops: LEB properties to remove + * @cat: LEB category from which to remove + * + * LEB properties are categorized to enable fast find operations. + */ +static void ubifs_remove_from_cat(struct ubifs_info *c, + struct ubifs_lprops *lprops, int cat) +{ + switch (cat) { + case LPROPS_DIRTY: + case LPROPS_DIRTY_IDX: + case LPROPS_FREE: + remove_from_lpt_heap(c, lprops, cat); + break; + case LPROPS_FREEABLE: + c->freeable_cnt -= 1; + ubifs_assert(c, c->freeable_cnt >= 0); + fallthrough; + case LPROPS_UNCAT: + case LPROPS_EMPTY: + case LPROPS_FRDI_IDX: + ubifs_assert(c, !list_empty(&lprops->list)); + list_del(&lprops->list); + break; + default: + ubifs_assert(c, 0); + } + + c->in_a_category_cnt -= 1; + ubifs_assert(c, c->in_a_category_cnt >= 0); +} + +/** + * ubifs_replace_cat - replace lprops in a category list or heap. + * @c: UBIFS file-system description object + * @old_lprops: LEB properties to replace + * @new_lprops: LEB properties with which to replace + * + * During commit it is sometimes necessary to copy a pnode (see dirty_cow_pnode) + * and the lprops that the pnode contains. When that happens, references in + * category lists and heaps must be replaced. This function does that. + */ +void ubifs_replace_cat(struct ubifs_info *c, struct ubifs_lprops *old_lprops, + struct ubifs_lprops *new_lprops) +{ + int cat; + + cat = new_lprops->flags & LPROPS_CAT_MASK; + switch (cat) { + case LPROPS_DIRTY: + case LPROPS_DIRTY_IDX: + case LPROPS_FREE: + lpt_heap_replace(c, new_lprops, cat); + break; + case LPROPS_UNCAT: + case LPROPS_EMPTY: + case LPROPS_FREEABLE: + case LPROPS_FRDI_IDX: + list_replace(&old_lprops->list, &new_lprops->list); + break; + default: + ubifs_assert(c, 0); + } +} + +/** + * ubifs_ensure_cat - ensure LEB properties are categorized. + * @c: UBIFS file-system description object + * @lprops: LEB properties + * + * A LEB may have fallen off of the bottom of a heap, and ended up as + * un-categorized even though it has enough space for us now. If that is the + * case this function will put the LEB back onto a heap. + */ +void ubifs_ensure_cat(struct ubifs_info *c, struct ubifs_lprops *lprops) +{ + int cat = lprops->flags & LPROPS_CAT_MASK; + + if (cat != LPROPS_UNCAT) + return; + cat = ubifs_categorize_lprops(c, lprops); + if (cat == LPROPS_UNCAT) + return; + ubifs_remove_from_cat(c, lprops, LPROPS_UNCAT); + ubifs_add_to_cat(c, lprops, cat); +} + +/** + * ubifs_categorize_lprops - categorize LEB properties. + * @c: UBIFS file-system description object + * @lprops: LEB properties to categorize + * + * LEB properties are categorized to enable fast find operations. This function + * returns the LEB category to which the LEB properties belong. Note however + * that if the LEB category is stored as a heap and the heap is full, the + * LEB properties may have their category changed to %LPROPS_UNCAT. + */ +int ubifs_categorize_lprops(const struct ubifs_info *c, + const struct ubifs_lprops *lprops) +{ + if (lprops->flags & LPROPS_TAKEN) + return LPROPS_UNCAT; + + if (lprops->free == c->leb_size) { + ubifs_assert(c, !(lprops->flags & LPROPS_INDEX)); + return LPROPS_EMPTY; + } + + if (lprops->free + lprops->dirty == c->leb_size) { + if (lprops->flags & LPROPS_INDEX) + return LPROPS_FRDI_IDX; + else + return LPROPS_FREEABLE; + } + + if (lprops->flags & LPROPS_INDEX) { + if (lprops->dirty + lprops->free >= c->min_idx_node_sz) + return LPROPS_DIRTY_IDX; + } else { + if (lprops->dirty >= c->dead_wm && + lprops->dirty > lprops->free) + return LPROPS_DIRTY; + if (lprops->free > 0) + return LPROPS_FREE; + } + + return LPROPS_UNCAT; +} + +/** + * change_category - change LEB properties category. + * @c: UBIFS file-system description object + * @lprops: LEB properties to re-categorize + * + * LEB properties are categorized to enable fast find operations. When the LEB + * properties change they must be re-categorized. + */ +static void change_category(struct ubifs_info *c, struct ubifs_lprops *lprops) +{ + int old_cat = lprops->flags & LPROPS_CAT_MASK; + int new_cat = ubifs_categorize_lprops(c, lprops); + + if (old_cat == new_cat) { + struct ubifs_lpt_heap *heap; + + /* lprops on a heap now must be moved up or down */ + if (new_cat < 1 || new_cat > LPROPS_HEAP_CNT) + return; /* Not on a heap */ + heap = &c->lpt_heap[new_cat - 1]; + adjust_lpt_heap(c, heap, lprops, lprops->hpos, new_cat); + } else { + ubifs_remove_from_cat(c, lprops, old_cat); + ubifs_add_to_cat(c, lprops, new_cat); + } +} + +/** + * ubifs_calc_dark - calculate LEB dark space size. + * @c: the UBIFS file-system description object + * @spc: amount of free and dirty space in the LEB + * + * This function calculates and returns amount of dark space in an LEB which + * has @spc bytes of free and dirty space. + * + * UBIFS is trying to account the space which might not be usable, and this + * space is called "dark space". For example, if an LEB has only %512 free + * bytes, it is dark space, because it cannot fit a large data node. + */ +int ubifs_calc_dark(const struct ubifs_info *c, int spc) +{ + ubifs_assert(c, !(spc & 7)); + + if (spc < c->dark_wm) + return spc; + + /* + * If we have slightly more space then the dark space watermark, we can + * anyway safely assume it we'll be able to write a node of the + * smallest size there. + */ + if (spc - c->dark_wm < MIN_WRITE_SZ) + return spc - MIN_WRITE_SZ; + + return c->dark_wm; +} + +/** + * is_lprops_dirty - determine if LEB properties are dirty. + * @c: the UBIFS file-system description object + * @lprops: LEB properties to test + */ +static int is_lprops_dirty(struct ubifs_info *c, struct ubifs_lprops *lprops) +{ + struct ubifs_pnode *pnode; + int pos; + + pos = (lprops->lnum - c->main_first) & (UBIFS_LPT_FANOUT - 1); + pnode = (struct ubifs_pnode *)container_of(lprops - pos, + struct ubifs_pnode, + lprops[0]); + return !test_bit(COW_CNODE, &pnode->flags) && + test_bit(DIRTY_CNODE, &pnode->flags); +} + +/** + * ubifs_change_lp - change LEB properties. + * @c: the UBIFS file-system description object + * @lp: LEB properties to change + * @free: new free space amount + * @dirty: new dirty space amount + * @flags: new flags + * @idx_gc_cnt: change to the count of @idx_gc list + * + * This function changes LEB properties (@free, @dirty or @flag). However, the + * property which has the %LPROPS_NC value is not changed. Returns a pointer to + * the updated LEB properties on success and a negative error code on failure. + * + * Note, the LEB properties may have had to be copied (due to COW) and + * consequently the pointer returned may not be the same as the pointer + * passed. + */ +const struct ubifs_lprops *ubifs_change_lp(struct ubifs_info *c, + const struct ubifs_lprops *lp, + int free, int dirty, int flags, + int idx_gc_cnt) +{ + /* + * This is the only function that is allowed to change lprops, so we + * discard the "const" qualifier. + */ + struct ubifs_lprops *lprops = (struct ubifs_lprops *)lp; + + dbg_lp("LEB %d, free %d, dirty %d, flags %d", + lprops->lnum, free, dirty, flags); + + ubifs_assert(c, mutex_is_locked(&c->lp_mutex)); + ubifs_assert(c, c->lst.empty_lebs >= 0 && + c->lst.empty_lebs <= c->main_lebs); + ubifs_assert(c, c->freeable_cnt >= 0); + ubifs_assert(c, c->freeable_cnt <= c->main_lebs); + ubifs_assert(c, c->lst.taken_empty_lebs >= 0); + ubifs_assert(c, c->lst.taken_empty_lebs <= c->lst.empty_lebs); + ubifs_assert(c, !(c->lst.total_free & 7) && !(c->lst.total_dirty & 7)); + ubifs_assert(c, !(c->lst.total_dead & 7) && !(c->lst.total_dark & 7)); + ubifs_assert(c, !(c->lst.total_used & 7)); + ubifs_assert(c, free == LPROPS_NC || free >= 0); + ubifs_assert(c, dirty == LPROPS_NC || dirty >= 0); + + if (!is_lprops_dirty(c, lprops)) { + lprops = ubifs_lpt_lookup_dirty(c, lprops->lnum); + if (IS_ERR(lprops)) + return lprops; + } else + ubifs_assert(c, lprops == ubifs_lpt_lookup_dirty(c, lprops->lnum)); + + ubifs_assert(c, !(lprops->free & 7) && !(lprops->dirty & 7)); + + spin_lock(&c->space_lock); + if ((lprops->flags & LPROPS_TAKEN) && lprops->free == c->leb_size) + c->lst.taken_empty_lebs -= 1; + + if (!(lprops->flags & LPROPS_INDEX)) { + int old_spc; + + old_spc = lprops->free + lprops->dirty; + if (old_spc < c->dead_wm) + c->lst.total_dead -= old_spc; + else + c->lst.total_dark -= ubifs_calc_dark(c, old_spc); + + c->lst.total_used -= c->leb_size - old_spc; + } + + if (free != LPROPS_NC) { + free = ALIGN(free, 8); + c->lst.total_free += free - lprops->free; + + /* Increase or decrease empty LEBs counter if needed */ + if (free == c->leb_size) { + if (lprops->free != c->leb_size) + c->lst.empty_lebs += 1; + } else if (lprops->free == c->leb_size) + c->lst.empty_lebs -= 1; + lprops->free = free; + } + + if (dirty != LPROPS_NC) { + dirty = ALIGN(dirty, 8); + c->lst.total_dirty += dirty - lprops->dirty; + lprops->dirty = dirty; + } + + if (flags != LPROPS_NC) { + /* Take care about indexing LEBs counter if needed */ + if ((lprops->flags & LPROPS_INDEX)) { + if (!(flags & LPROPS_INDEX)) + c->lst.idx_lebs -= 1; + } else if (flags & LPROPS_INDEX) + c->lst.idx_lebs += 1; + lprops->flags = flags; + } + + if (!(lprops->flags & LPROPS_INDEX)) { + int new_spc; + + new_spc = lprops->free + lprops->dirty; + if (new_spc < c->dead_wm) + c->lst.total_dead += new_spc; + else + c->lst.total_dark += ubifs_calc_dark(c, new_spc); + + c->lst.total_used += c->leb_size - new_spc; + } + + if ((lprops->flags & LPROPS_TAKEN) && lprops->free == c->leb_size) + c->lst.taken_empty_lebs += 1; + + change_category(c, lprops); + c->idx_gc_cnt += idx_gc_cnt; + spin_unlock(&c->space_lock); + return lprops; +} + +/** + * ubifs_get_lp_stats - get lprops statistics. + * @c: UBIFS file-system description object + * @lst: return statistics + */ +void ubifs_get_lp_stats(struct ubifs_info *c, struct ubifs_lp_stats *lst) +{ + spin_lock(&c->space_lock); + memcpy(lst, &c->lst, sizeof(struct ubifs_lp_stats)); + spin_unlock(&c->space_lock); +} + +/** + * ubifs_change_one_lp - change LEB properties. + * @c: the UBIFS file-system description object + * @lnum: LEB to change properties for + * @free: amount of free space + * @dirty: amount of dirty space + * @flags_set: flags to set + * @flags_clean: flags to clean + * @idx_gc_cnt: change to the count of idx_gc list + * + * This function changes properties of LEB @lnum. It is a helper wrapper over + * 'ubifs_change_lp()' which hides lprops get/release. The arguments are the + * same as in case of 'ubifs_change_lp()'. Returns zero in case of success and + * a negative error code in case of failure. + */ +int ubifs_change_one_lp(struct ubifs_info *c, int lnum, int free, int dirty, + int flags_set, int flags_clean, int idx_gc_cnt) +{ + int err = 0, flags; + const struct ubifs_lprops *lp; + + if (!test_lpt_valid_callback(c, lnum, LPROPS_NC, LPROPS_NC, LPROPS_NC, + LPROPS_NC)) + return 0; + + ubifs_get_lprops(c); + + lp = ubifs_lpt_lookup_dirty(c, lnum); + if (IS_ERR(lp)) { + err = PTR_ERR(lp); + if (test_and_clear_failure_reason_callback(c, FR_LPT_CORRUPTED) && + can_ignore_failure_callback(c, FR_LPT_CORRUPTED)) + err = 0; + goto out; + } + + if (!test_lpt_valid_callback(c, lnum, lp->free, lp->dirty, free, dirty)) + goto out; + + flags = (lp->flags | flags_set) & ~flags_clean; + lp = ubifs_change_lp(c, lp, free, dirty, flags, idx_gc_cnt); + if (IS_ERR(lp)) + err = PTR_ERR(lp); + +out: + ubifs_release_lprops(c); + if (err) + ubifs_err(c, "cannot change properties of LEB %d, error %d", + lnum, err); + return err; +} + +/** + * ubifs_update_one_lp - update LEB properties. + * @c: the UBIFS file-system description object + * @lnum: LEB to change properties for + * @free: amount of free space + * @dirty: amount of dirty space to add + * @flags_set: flags to set + * @flags_clean: flags to clean + * + * This function is the same as 'ubifs_change_one_lp()' but @dirty is added to + * current dirty space, not substitutes it. + */ +int ubifs_update_one_lp(struct ubifs_info *c, int lnum, int free, int dirty, + int flags_set, int flags_clean) +{ + int err = 0, flags; + const struct ubifs_lprops *lp; + + if (!test_lpt_valid_callback(c, lnum, LPROPS_NC, LPROPS_NC, LPROPS_NC, + LPROPS_NC)) + return 0; + + ubifs_get_lprops(c); + + lp = ubifs_lpt_lookup_dirty(c, lnum); + if (IS_ERR(lp)) { + err = PTR_ERR(lp); + if (test_and_clear_failure_reason_callback(c, FR_LPT_CORRUPTED) && + can_ignore_failure_callback(c, FR_LPT_CORRUPTED)) + err = 0; + goto out; + } + + if (!test_lpt_valid_callback(c, lnum, lp->free, lp->dirty, free, + lp->dirty + dirty)) + goto out; + + flags = (lp->flags | flags_set) & ~flags_clean; + lp = ubifs_change_lp(c, lp, free, lp->dirty + dirty, flags, 0); + if (IS_ERR(lp)) + err = PTR_ERR(lp); + +out: + ubifs_release_lprops(c); + if (err) + ubifs_err(c, "cannot update properties of LEB %d, error %d", + lnum, err); + return err; +} + +/** + * ubifs_read_one_lp - read LEB properties. + * @c: the UBIFS file-system description object + * @lnum: LEB to read properties for + * @lp: where to store read properties + * + * This helper function reads properties of a LEB @lnum and stores them in @lp. + * Returns zero in case of success and a negative error code in case of + * failure. + */ +int ubifs_read_one_lp(struct ubifs_info *c, int lnum, struct ubifs_lprops *lp) +{ + int err = 0; + const struct ubifs_lprops *lpp; + + ubifs_get_lprops(c); + + lpp = ubifs_lpt_lookup(c, lnum); + if (IS_ERR(lpp)) { + err = PTR_ERR(lpp); + ubifs_err(c, "cannot read properties of LEB %d, error %d", + lnum, err); + goto out; + } + + memcpy(lp, lpp, sizeof(struct ubifs_lprops)); + +out: + ubifs_release_lprops(c); + return err; +} + +/** + * ubifs_fast_find_free - try to find a LEB with free space quickly. + * @c: the UBIFS file-system description object + * + * This function returns LEB properties for a LEB with free space or %NULL if + * the function is unable to find a LEB quickly. + */ +const struct ubifs_lprops *ubifs_fast_find_free(struct ubifs_info *c) +{ + struct ubifs_lprops *lprops; + struct ubifs_lpt_heap *heap; + + ubifs_assert(c, mutex_is_locked(&c->lp_mutex)); + + heap = &c->lpt_heap[LPROPS_FREE - 1]; + if (heap->cnt == 0) + return NULL; + + lprops = heap->arr[0]; + ubifs_assert(c, !(lprops->flags & LPROPS_TAKEN)); + ubifs_assert(c, !(lprops->flags & LPROPS_INDEX)); + return lprops; +} + +/** + * ubifs_fast_find_empty - try to find an empty LEB quickly. + * @c: the UBIFS file-system description object + * + * This function returns LEB properties for an empty LEB or %NULL if the + * function is unable to find an empty LEB quickly. + */ +const struct ubifs_lprops *ubifs_fast_find_empty(struct ubifs_info *c) +{ + struct ubifs_lprops *lprops; + + ubifs_assert(c, mutex_is_locked(&c->lp_mutex)); + + if (list_empty(&c->empty_list)) + return NULL; + + lprops = list_entry(c->empty_list.next, struct ubifs_lprops, list); + ubifs_assert(c, !(lprops->flags & LPROPS_TAKEN)); + ubifs_assert(c, !(lprops->flags & LPROPS_INDEX)); + ubifs_assert(c, lprops->free == c->leb_size); + return lprops; +} + +/** + * ubifs_fast_find_freeable - try to find a freeable LEB quickly. + * @c: the UBIFS file-system description object + * + * This function returns LEB properties for a freeable LEB or %NULL if the + * function is unable to find a freeable LEB quickly. + */ +const struct ubifs_lprops *ubifs_fast_find_freeable(struct ubifs_info *c) +{ + struct ubifs_lprops *lprops; + + ubifs_assert(c, mutex_is_locked(&c->lp_mutex)); + + if (list_empty(&c->freeable_list)) + return NULL; + + lprops = list_entry(c->freeable_list.next, struct ubifs_lprops, list); + ubifs_assert(c, !(lprops->flags & LPROPS_TAKEN)); + ubifs_assert(c, !(lprops->flags & LPROPS_INDEX)); + ubifs_assert(c, lprops->free + lprops->dirty == c->leb_size); + ubifs_assert(c, c->freeable_cnt > 0); + return lprops; +} + +/** + * ubifs_fast_find_frdi_idx - try to find a freeable index LEB quickly. + * @c: the UBIFS file-system description object + * + * This function returns LEB properties for a freeable index LEB or %NULL if the + * function is unable to find a freeable index LEB quickly. + */ +const struct ubifs_lprops *ubifs_fast_find_frdi_idx(struct ubifs_info *c) +{ + struct ubifs_lprops *lprops; + + ubifs_assert(c, mutex_is_locked(&c->lp_mutex)); + + if (list_empty(&c->frdi_idx_list)) + return NULL; + + lprops = list_entry(c->frdi_idx_list.next, struct ubifs_lprops, list); + ubifs_assert(c, !(lprops->flags & LPROPS_TAKEN)); + ubifs_assert(c, (lprops->flags & LPROPS_INDEX)); + ubifs_assert(c, lprops->free + lprops->dirty == c->leb_size); + return lprops; +} diff --git a/ubifs-utils/libubifs/lpt.c b/ubifs-utils/libubifs/lpt.c new file mode 100644 index 0000000..f2f2727 --- /dev/null +++ b/ubifs-utils/libubifs/lpt.c @@ -0,0 +1,2338 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * This file is part of UBIFS. + * + * Copyright (C) 2006-2008 Nokia Corporation. + * + * Authors: Adrian Hunter + * Artem Bityutskiy (Битюцкий Артём) + */ + +/* + * This file implements the LEB properties tree (LPT) area. The LPT area + * contains the LEB properties tree, a table of LPT area eraseblocks (ltab), and + * (for the "big" model) a table of saved LEB numbers (lsave). The LPT area sits + * between the log and the orphan area. + * + * The LPT area is like a miniature self-contained file system. It is required + * that it never runs out of space, is fast to access and update, and scales + * logarithmically. The LEB properties tree is implemented as a wandering tree + * much like the TNC, and the LPT area has its own garbage collection. + * + * The LPT has two slightly different forms called the "small model" and the + * "big model". The small model is used when the entire LEB properties table + * can be written into a single eraseblock. In that case, garbage collection + * consists of just writing the whole table, which therefore makes all other + * eraseblocks reusable. In the case of the big model, dirty eraseblocks are + * selected for garbage collection, which consists of marking the clean nodes in + * that LEB as dirty, and then only the dirty nodes are written out. Also, in + * the case of the big model, a table of LEB numbers is saved so that the entire + * LPT does not to be scanned looking for empty eraseblocks when UBIFS is first + * mounted. + */ + +#include "linux_err.h" +#include "bitops.h" +#include "kmem.h" +#include "crc16.h" +#include "ubifs.h" +#include "defs.h" +#include "debug.h" + +/** + * do_calc_lpt_geom - calculate sizes for the LPT area. + * @c: the UBIFS file-system description object + * + * Calculate the sizes of LPT bit fields, nodes, and tree, based on the + * properties of the flash and whether LPT is "big" (c->big_lpt). + */ +static void do_calc_lpt_geom(struct ubifs_info *c) +{ + int i, n, bits, per_leb_wastage, max_pnode_cnt; + long long sz, tot_wastage; + + if (c->program_type != MKFS_PROGRAM_TYPE) { + n = c->main_lebs + c->max_leb_cnt - c->leb_cnt; + max_pnode_cnt = DIV_ROUND_UP(n, UBIFS_LPT_FANOUT); + } else { + /* + * Different from linux kernel. + * + * We change it, because 'c->leb_cnt' is not initialized in + * mkfs.ubifs when do_calc_lpt_geom() is invoked, 'c->main_lebs' + * is calculated by 'c->max_leb_cnt', so the 'c->lpt_hght' + * should be calculated by 'c->main_lebs'. + */ + max_pnode_cnt = DIV_ROUND_UP(c->main_lebs, UBIFS_LPT_FANOUT); + } + + c->lpt_hght = 1; + n = UBIFS_LPT_FANOUT; + while (n < max_pnode_cnt) { + c->lpt_hght += 1; + n <<= UBIFS_LPT_FANOUT_SHIFT; + } + + c->pnode_cnt = DIV_ROUND_UP(c->main_lebs, UBIFS_LPT_FANOUT); + + n = DIV_ROUND_UP(c->pnode_cnt, UBIFS_LPT_FANOUT); + c->nnode_cnt = n; + for (i = 1; i < c->lpt_hght; i++) { + n = DIV_ROUND_UP(n, UBIFS_LPT_FANOUT); + c->nnode_cnt += n; + } + + c->space_bits = fls(c->leb_size) - 3; + c->lpt_lnum_bits = fls(c->lpt_lebs); + c->lpt_offs_bits = fls(c->leb_size - 1); + c->lpt_spc_bits = fls(c->leb_size); + + n = DIV_ROUND_UP(c->max_leb_cnt, UBIFS_LPT_FANOUT); + c->pcnt_bits = fls(n - 1); + + c->lnum_bits = fls(c->max_leb_cnt - 1); + + bits = UBIFS_LPT_CRC_BITS + UBIFS_LPT_TYPE_BITS + + (c->big_lpt ? c->pcnt_bits : 0) + + (c->space_bits * 2 + 1) * UBIFS_LPT_FANOUT; + c->pnode_sz = (bits + 7) / 8; + + bits = UBIFS_LPT_CRC_BITS + UBIFS_LPT_TYPE_BITS + + (c->big_lpt ? c->pcnt_bits : 0) + + (c->lpt_lnum_bits + c->lpt_offs_bits) * UBIFS_LPT_FANOUT; + c->nnode_sz = (bits + 7) / 8; + + bits = UBIFS_LPT_CRC_BITS + UBIFS_LPT_TYPE_BITS + + c->lpt_lebs * c->lpt_spc_bits * 2; + c->ltab_sz = (bits + 7) / 8; + + bits = UBIFS_LPT_CRC_BITS + UBIFS_LPT_TYPE_BITS + + c->lnum_bits * c->lsave_cnt; + c->lsave_sz = (bits + 7) / 8; + + /* Calculate the minimum LPT size */ + c->lpt_sz = (long long)c->pnode_cnt * c->pnode_sz; + c->lpt_sz += (long long)c->nnode_cnt * c->nnode_sz; + c->lpt_sz += c->ltab_sz; + if (c->big_lpt) + c->lpt_sz += c->lsave_sz; + + /* Add wastage */ + sz = c->lpt_sz; + per_leb_wastage = max_t(int, c->pnode_sz, c->nnode_sz); + sz += per_leb_wastage; + tot_wastage = per_leb_wastage; + while (sz > c->leb_size) { + sz += per_leb_wastage; + sz -= c->leb_size; + tot_wastage += per_leb_wastage; + } + tot_wastage += ALIGN(sz, c->min_io_size) - sz; + c->lpt_sz += tot_wastage; +} + +/** + * ubifs_calc_lpt_geom - calculate and check sizes for the LPT area. + * @c: the UBIFS file-system description object + * + * This function returns %0 on success and a negative error code on failure. + */ +int ubifs_calc_lpt_geom(struct ubifs_info *c) +{ + int lebs_needed; + long long sz; + + do_calc_lpt_geom(c); + + /* Verify that lpt_lebs is big enough */ + sz = c->lpt_sz * 2; /* Must have at least 2 times the size */ + lebs_needed = div_u64(sz + c->leb_size - 1, c->leb_size); + if (lebs_needed > c->lpt_lebs) { + ubifs_err(c, "too few LPT LEBs"); + return -EINVAL; + } + + /* Verify that ltab fits in a single LEB (since ltab is a single node */ + if (c->ltab_sz > c->leb_size) { + ubifs_err(c, "LPT ltab too big"); + return -EINVAL; + } + + c->check_lpt_free = c->big_lpt; + return 0; +} + +/** + * ubifs_calc_dflt_lpt_geom - calculate default LPT geometry. + * @c: the UBIFS file-system description object + * @main_lebs: number of main area LEBs is passed and returned here + * @big_lpt: whether the LPT area is "big" is returned here + * + * The size of the LPT area depends on parameters that themselves are dependent + * on the size of the LPT area. This function, successively recalculates the LPT + * area geometry until the parameters and resultant geometry are consistent. + * + * This function returns %0 on success and a negative error code on failure. + */ +int ubifs_calc_dflt_lpt_geom(struct ubifs_info *c, int *main_lebs, int *big_lpt) +{ + int i, lebs_needed; + long long sz; + + /* Start by assuming the minimum number of LPT LEBs */ + c->lpt_lebs = UBIFS_MIN_LPT_LEBS; + c->main_lebs = *main_lebs - c->lpt_lebs; + if (c->main_lebs <= 0) + return -EINVAL; + + /* And assume we will use the small LPT model */ + c->big_lpt = 0; + + /* + * Calculate the geometry based on assumptions above and then see if it + * makes sense + */ + do_calc_lpt_geom(c); + + /* Small LPT model must have lpt_sz < leb_size */ + if (c->lpt_sz > c->leb_size) { + /* Nope, so try again using big LPT model */ + c->big_lpt = 1; + do_calc_lpt_geom(c); + } + + /* Now check there are enough LPT LEBs */ + for (i = 0; i < 64 ; i++) { + sz = c->lpt_sz * 4; /* Allow 4 times the size */ + lebs_needed = div_u64(sz + c->leb_size - 1, c->leb_size); + if (lebs_needed > c->lpt_lebs) { + /* Not enough LPT LEBs so try again with more */ + c->lpt_lebs = lebs_needed; + c->main_lebs = *main_lebs - c->lpt_lebs; + if (c->main_lebs <= 0) + return -EINVAL; + do_calc_lpt_geom(c); + continue; + } + if (c->ltab_sz > c->leb_size) { + ubifs_err(c, "LPT ltab too big"); + return -EINVAL; + } + *main_lebs = c->main_lebs; + *big_lpt = c->big_lpt; + return 0; + } + return -EINVAL; +} + +/** + * pack_bits - pack bit fields end-to-end. + * @c: UBIFS file-system description object + * @addr: address at which to pack (passed and next address returned) + * @pos: bit position at which to pack (passed and next position returned) + * @val: value to pack + * @nrbits: number of bits of value to pack (1-32) + */ +static void pack_bits(const struct ubifs_info *c, uint8_t **addr, int *pos, uint32_t val, int nrbits) +{ + uint8_t *p = *addr; + int b = *pos; + + ubifs_assert(c, nrbits > 0); + ubifs_assert(c, nrbits <= 32); + ubifs_assert(c, *pos >= 0); + ubifs_assert(c, *pos < 8); + ubifs_assert(c, (val >> nrbits) == 0 || nrbits == 32); + if (b) { + *p |= ((uint8_t)val) << b; + nrbits += b; + if (nrbits > 8) { + *++p = (uint8_t)(val >>= (8 - b)); + if (nrbits > 16) { + *++p = (uint8_t)(val >>= 8); + if (nrbits > 24) { + *++p = (uint8_t)(val >>= 8); + if (nrbits > 32) + *++p = (uint8_t)(val >>= 8); + } + } + } + } else { + *p = (uint8_t)val; + if (nrbits > 8) { + *++p = (uint8_t)(val >>= 8); + if (nrbits > 16) { + *++p = (uint8_t)(val >>= 8); + if (nrbits > 24) + *++p = (uint8_t)(val >>= 8); + } + } + } + b = nrbits & 7; + if (b == 0) + p++; + *addr = p; + *pos = b; +} + +/** + * ubifs_unpack_bits - unpack bit fields. + * @c: UBIFS file-system description object + * @addr: address at which to unpack (passed and next address returned) + * @pos: bit position at which to unpack (passed and next position returned) + * @nrbits: number of bits of value to unpack (1-32) + * + * This functions returns the value unpacked. + */ +uint32_t ubifs_unpack_bits(const struct ubifs_info *c, uint8_t **addr, int *pos, int nrbits) +{ + const int k = 32 - nrbits; + uint8_t *p = *addr; + int b = *pos; + uint32_t val = 0; + const int bytes = (nrbits + b + 7) >> 3; + + ubifs_assert(c, nrbits > 0); + ubifs_assert(c, nrbits <= 32); + ubifs_assert(c, *pos >= 0); + ubifs_assert(c, *pos < 8); + if (b) { + switch (bytes) { + case 2: + val = p[1]; + break; + case 3: + val = p[1] | ((uint32_t)p[2] << 8); + break; + case 4: + val = p[1] | ((uint32_t)p[2] << 8) | + ((uint32_t)p[3] << 16); + break; + case 5: + val = p[1] | ((uint32_t)p[2] << 8) | + ((uint32_t)p[3] << 16) | + ((uint32_t)p[4] << 24); + } + val <<= (8 - b); + val |= *p >> b; + nrbits += b; + } else { + switch (bytes) { + case 1: + val = p[0]; + break; + case 2: + val = p[0] | ((uint32_t)p[1] << 8); + break; + case 3: + val = p[0] | ((uint32_t)p[1] << 8) | + ((uint32_t)p[2] << 16); + break; + case 4: + val = p[0] | ((uint32_t)p[1] << 8) | + ((uint32_t)p[2] << 16) | + ((uint32_t)p[3] << 24); + break; + } + } + val <<= k; + val >>= k; + b = nrbits & 7; + p += nrbits >> 3; + *addr = p; + *pos = b; + ubifs_assert(c, (val >> nrbits) == 0 || nrbits - b == 32); + return val; +} + +/** + * ubifs_pack_pnode - pack all the bit fields of a pnode. + * @c: UBIFS file-system description object + * @buf: buffer into which to pack + * @pnode: pnode to pack + */ +void ubifs_pack_pnode(struct ubifs_info *c, void *buf, + struct ubifs_pnode *pnode) +{ + uint8_t *addr = buf + UBIFS_LPT_CRC_BYTES; + int i, pos = 0; + uint16_t crc; + + pack_bits(c, &addr, &pos, UBIFS_LPT_PNODE, UBIFS_LPT_TYPE_BITS); + if (c->big_lpt) + pack_bits(c, &addr, &pos, pnode->num, c->pcnt_bits); + for (i = 0; i < UBIFS_LPT_FANOUT; i++) { + pack_bits(c, &addr, &pos, pnode->lprops[i].free >> 3, + c->space_bits); + pack_bits(c, &addr, &pos, pnode->lprops[i].dirty >> 3, + c->space_bits); + if (pnode->lprops[i].flags & LPROPS_INDEX) + pack_bits(c, &addr, &pos, 1, 1); + else + pack_bits(c, &addr, &pos, 0, 1); + } + crc = crc16(-1, buf + UBIFS_LPT_CRC_BYTES, + c->pnode_sz - UBIFS_LPT_CRC_BYTES); + addr = buf; + pos = 0; + pack_bits(c, &addr, &pos, crc, UBIFS_LPT_CRC_BITS); +} + +/** + * ubifs_pack_nnode - pack all the bit fields of a nnode. + * @c: UBIFS file-system description object + * @buf: buffer into which to pack + * @nnode: nnode to pack + */ +void ubifs_pack_nnode(struct ubifs_info *c, void *buf, + struct ubifs_nnode *nnode) +{ + uint8_t *addr = buf + UBIFS_LPT_CRC_BYTES; + int i, pos = 0; + uint16_t crc; + + pack_bits(c, &addr, &pos, UBIFS_LPT_NNODE, UBIFS_LPT_TYPE_BITS); + if (c->big_lpt) + pack_bits(c, &addr, &pos, nnode->num, c->pcnt_bits); + for (i = 0; i < UBIFS_LPT_FANOUT; i++) { + int lnum = nnode->nbranch[i].lnum; + + if (lnum == 0) + lnum = c->lpt_last + 1; + pack_bits(c, &addr, &pos, lnum - c->lpt_first, c->lpt_lnum_bits); + pack_bits(c, &addr, &pos, nnode->nbranch[i].offs, + c->lpt_offs_bits); + } + crc = crc16(-1, buf + UBIFS_LPT_CRC_BYTES, + c->nnode_sz - UBIFS_LPT_CRC_BYTES); + addr = buf; + pos = 0; + pack_bits(c, &addr, &pos, crc, UBIFS_LPT_CRC_BITS); +} + +/** + * ubifs_pack_ltab - pack the LPT's own lprops table. + * @c: UBIFS file-system description object + * @buf: buffer into which to pack + * @ltab: LPT's own lprops table to pack + */ +void ubifs_pack_ltab(struct ubifs_info *c, void *buf, + struct ubifs_lpt_lprops *ltab) +{ + uint8_t *addr = buf + UBIFS_LPT_CRC_BYTES; + int i, pos = 0; + uint16_t crc; + + pack_bits(c, &addr, &pos, UBIFS_LPT_LTAB, UBIFS_LPT_TYPE_BITS); + for (i = 0; i < c->lpt_lebs; i++) { + pack_bits(c, &addr, &pos, ltab[i].free, c->lpt_spc_bits); + pack_bits(c, &addr, &pos, ltab[i].dirty, c->lpt_spc_bits); + } + crc = crc16(-1, buf + UBIFS_LPT_CRC_BYTES, + c->ltab_sz - UBIFS_LPT_CRC_BYTES); + addr = buf; + pos = 0; + pack_bits(c, &addr, &pos, crc, UBIFS_LPT_CRC_BITS); +} + +/** + * ubifs_pack_lsave - pack the LPT's save table. + * @c: UBIFS file-system description object + * @buf: buffer into which to pack + * @lsave: LPT's save table to pack + */ +void ubifs_pack_lsave(struct ubifs_info *c, void *buf, int *lsave) +{ + uint8_t *addr = buf + UBIFS_LPT_CRC_BYTES; + int i, pos = 0; + uint16_t crc; + + pack_bits(c, &addr, &pos, UBIFS_LPT_LSAVE, UBIFS_LPT_TYPE_BITS); + for (i = 0; i < c->lsave_cnt; i++) + pack_bits(c, &addr, &pos, lsave[i], c->lnum_bits); + crc = crc16(-1, buf + UBIFS_LPT_CRC_BYTES, + c->lsave_sz - UBIFS_LPT_CRC_BYTES); + addr = buf; + pos = 0; + pack_bits(c, &addr, &pos, crc, UBIFS_LPT_CRC_BITS); +} + +/** + * ubifs_add_lpt_dirt - add dirty space to LPT LEB properties. + * @c: UBIFS file-system description object + * @lnum: LEB number to which to add dirty space + * @dirty: amount of dirty space to add + */ +void ubifs_add_lpt_dirt(struct ubifs_info *c, int lnum, int dirty) +{ + if (!dirty || !lnum) + return; + dbg_lp("LEB %d add %d to %d", + lnum, dirty, c->ltab[lnum - c->lpt_first].dirty); + ubifs_assert(c, lnum >= c->lpt_first && lnum <= c->lpt_last); + c->ltab[lnum - c->lpt_first].dirty += dirty; +} + +/** + * set_ltab - set LPT LEB properties. + * @c: UBIFS file-system description object + * @lnum: LEB number + * @free: amount of free space + * @dirty: amount of dirty space + */ +static void set_ltab(struct ubifs_info *c, int lnum, int free, int dirty) +{ + dbg_lp("LEB %d free %d dirty %d to %d %d", + lnum, c->ltab[lnum - c->lpt_first].free, + c->ltab[lnum - c->lpt_first].dirty, free, dirty); + ubifs_assert(c, lnum >= c->lpt_first && lnum <= c->lpt_last); + c->ltab[lnum - c->lpt_first].free = free; + c->ltab[lnum - c->lpt_first].dirty = dirty; +} + +/** + * ubifs_add_nnode_dirt - add dirty space to LPT LEB properties. + * @c: UBIFS file-system description object + * @nnode: nnode for which to add dirt + */ +void ubifs_add_nnode_dirt(struct ubifs_info *c, struct ubifs_nnode *nnode) +{ + struct ubifs_nnode *np = nnode->parent; + + if (np) + ubifs_add_lpt_dirt(c, np->nbranch[nnode->iip].lnum, + c->nnode_sz); + else { + ubifs_add_lpt_dirt(c, c->lpt_lnum, c->nnode_sz); + if (!(c->lpt_drty_flgs & LTAB_DIRTY)) { + c->lpt_drty_flgs |= LTAB_DIRTY; + ubifs_add_lpt_dirt(c, c->ltab_lnum, c->ltab_sz); + } + } +} + +/** + * add_pnode_dirt - add dirty space to LPT LEB properties. + * @c: UBIFS file-system description object + * @pnode: pnode for which to add dirt + */ +static void add_pnode_dirt(struct ubifs_info *c, struct ubifs_pnode *pnode) +{ + ubifs_add_lpt_dirt(c, pnode->parent->nbranch[pnode->iip].lnum, + c->pnode_sz); +} + +/** + * ubifs_calc_nnode_num - calculate nnode number. + * @row: the row in the tree (root is zero) + * @col: the column in the row (leftmost is zero) + * + * The nnode number is a number that uniquely identifies a nnode and can be used + * easily to traverse the tree from the root to that nnode. + * + * This function calculates and returns the nnode number for the nnode at @row + * and @col. + */ +int ubifs_calc_nnode_num(int row, int col) +{ + int num, bits; + + num = 1; + while (row--) { + bits = (col & (UBIFS_LPT_FANOUT - 1)); + col >>= UBIFS_LPT_FANOUT_SHIFT; + num <<= UBIFS_LPT_FANOUT_SHIFT; + num |= bits; + } + return num; +} + +/** + * calc_nnode_num_from_parent - calculate nnode number. + * @c: UBIFS file-system description object + * @parent: parent nnode + * @iip: index in parent + * + * The nnode number is a number that uniquely identifies a nnode and can be used + * easily to traverse the tree from the root to that nnode. + * + * This function calculates and returns the nnode number based on the parent's + * nnode number and the index in parent. + */ +static int calc_nnode_num_from_parent(const struct ubifs_info *c, + struct ubifs_nnode *parent, int iip) +{ + int num, shft; + + if (!parent) + return 1; + shft = (c->lpt_hght - parent->level) * UBIFS_LPT_FANOUT_SHIFT; + num = parent->num ^ (1 << shft); + num |= (UBIFS_LPT_FANOUT + iip) << shft; + return num; +} + +/** + * calc_pnode_num_from_parent - calculate pnode number. + * @c: UBIFS file-system description object + * @parent: parent nnode + * @iip: index in parent + * + * The pnode number is a number that uniquely identifies a pnode and can be used + * easily to traverse the tree from the root to that pnode. + * + * This function calculates and returns the pnode number based on the parent's + * nnode number and the index in parent. + */ +static int calc_pnode_num_from_parent(const struct ubifs_info *c, + struct ubifs_nnode *parent, int iip) +{ + int i, n = c->lpt_hght - 1, pnum = parent->num, num = 0; + + for (i = 0; i < n; i++) { + num <<= UBIFS_LPT_FANOUT_SHIFT; + num |= pnum & (UBIFS_LPT_FANOUT - 1); + pnum >>= UBIFS_LPT_FANOUT_SHIFT; + } + num <<= UBIFS_LPT_FANOUT_SHIFT; + num |= iip; + return num; +} + +/** + * ubifs_create_lpt - create lpt acccording to lprops array. + * @c: UBIFS file-system description object + * @lps: array of logical eraseblock properties + * @lp_cnt: the length of @lps + * @hash: hash of the LPT is returned here + * @free_ltab: %true means to release c->ltab after creating lpt + * + * This function creates lpt, the pnode will be initialized based on + * corresponding elements in @lps. If there are no corresponding lprops + * (eg. @lp_cnt is smaller than @c->main_lebs), the LEB property is set + * as free state. + */ +int ubifs_create_lpt(struct ubifs_info *c, struct ubifs_lprops *lps, int lp_cnt, + u8 *hash, bool free_ltab) +{ + int lnum, err = 0, i, j, cnt, len, alen, row; + int blnum, boffs, bsz, bcnt; + struct ubifs_pnode *pnode = NULL; + struct ubifs_nnode *nnode = NULL; + void *buf = NULL, *p; + struct ubifs_lpt_lprops *ltab = NULL; + int *lsave = NULL; + struct shash_desc *desc; + + desc = ubifs_hash_get_desc(c); + if (IS_ERR(desc)) + return PTR_ERR(desc); + + lsave = kmalloc_array(c->lsave_cnt, sizeof(int), GFP_KERNEL); + pnode = kzalloc(sizeof(struct ubifs_pnode), GFP_KERNEL); + nnode = kzalloc(sizeof(struct ubifs_nnode), GFP_KERNEL); + buf = vmalloc(c->leb_size); + ltab = vmalloc(array_size(sizeof(struct ubifs_lpt_lprops), + c->lpt_lebs)); + if (!pnode || !nnode || !buf || !ltab || !lsave) { + err = -ENOMEM; + goto out; + } + + ubifs_assert(c, !c->ltab); + c->ltab = ltab; /* Needed by set_ltab */ + + /* Initialize LPT's own lprops */ + for (i = 0; i < c->lpt_lebs; i++) { + ltab[i].free = c->leb_size; + ltab[i].dirty = 0; + ltab[i].tgc = 0; + ltab[i].cmt = 0; + } + + lnum = c->lpt_first; + p = buf; + len = 0; + /* + * Different from linux kernel. The number of leaf nodes (pnodes) should + * be calculated by the number of current main LEBs. The 'c->pnode_cnt' + * may not be equal to 'DIV_ROUND_UP(c->main_lebs, UBIFS_LPT_FANOUT)' in + * mkfs when 'c->leb_cnt != c->max_leb_cnt' is true. + */ + cnt = DIV_ROUND_UP(c->main_lebs, UBIFS_LPT_FANOUT); + + /* + * To calculate the internal node branches, we keep information about + * the level below. + */ + blnum = lnum; /* LEB number of level below */ + boffs = 0; /* Offset of level below */ + bcnt = cnt; /* Number of nodes in level below */ + bsz = c->pnode_sz; /* Size of nodes in level below */ + + /* Add all pnodes */ + for (i = 0; i < cnt; i++) { + if (len + c->pnode_sz > c->leb_size) { + alen = ALIGN(len, c->min_io_size); + set_ltab(c, lnum, c->leb_size - alen, alen - len); + /* + * Different from linux kernel. + * The mkfs may partially write data into a certain LEB + * of file image, the left unwritten area in the LEB + * should be filled with '0xFF'. + */ + if (c->libubi) { + memset(p, 0xff, alen - len); + err = ubifs_leb_change(c, lnum++, buf, alen); + } else { + memset(p, 0xff, c->leb_size - len); + err = ubifs_leb_change(c, lnum++, buf, c->leb_size); + } + if (err) + goto out; + p = buf; + len = 0; + } + /* Fill in the pnode */ + for (j = 0; j < UBIFS_LPT_FANOUT; j++) { + int k = (i << UBIFS_LPT_FANOUT_SHIFT) + j; + + if (k < lp_cnt) { + pnode->lprops[j].free = lps[k].free; + pnode->lprops[j].dirty = lps[k].dirty; + pnode->lprops[j].flags = lps[k].flags; + } else { + pnode->lprops[j].free = c->leb_size; + pnode->lprops[j].dirty = 0; + pnode->lprops[j].flags = 0; + } + } + ubifs_pack_pnode(c, p, pnode); + err = ubifs_shash_update(c, desc, p, c->pnode_sz); + if (err) + goto out; + + p += c->pnode_sz; + len += c->pnode_sz; + /* + * pnodes are simply numbered left to right starting at zero, + * which means the pnode number can be used easily to traverse + * down the tree to the corresponding pnode. + */ + pnode->num += 1; + } + + /* + * Different from linux kernel. The 'c->lpt_hght' is calculated by the + * 'c->max_leb_cnt', according to the implementation of function + * ubifs_pnode_lookup(), there are at least 'c->lpt_hght' cnodes should + * be created, otherwise the LPT looking up could be failed after + * mouting. + */ + row = c->lpt_hght - 1; + /* Add all nnodes, one level at a time */ + while (1) { + /* Number of internal nodes (nnodes) at next level */ + cnt = DIV_ROUND_UP(cnt, UBIFS_LPT_FANOUT); + if (cnt == 0) + cnt = 1; + for (i = 0; i < cnt; i++) { + if (len + c->nnode_sz > c->leb_size) { + alen = ALIGN(len, c->min_io_size); + set_ltab(c, lnum, c->leb_size - alen, + alen - len); + /* + * Different from linux kernel. + * The mkfs may partially write data into a certain LEB + * of file image, the left unwritten area in the LEB + * should be filled with '0xFF'. + */ + if (c->libubi) { + memset(p, 0xff, alen - len); + err = ubifs_leb_change(c, lnum++, buf, alen); + } else { + memset(p, 0xff, c->leb_size - len); + err = ubifs_leb_change(c, lnum++, buf, c->leb_size); + } + if (err) + goto out; + p = buf; + len = 0; + } + /* Only 1 nnode at this level, so it is the root */ + if (row == 0) { + c->lpt_lnum = lnum; + c->lpt_offs = len; + } + /* Set branches to the level below */ + for (j = 0; j < UBIFS_LPT_FANOUT; j++) { + if (bcnt) { + if (boffs + bsz > c->leb_size) { + blnum += 1; + boffs = 0; + } + nnode->nbranch[j].lnum = blnum; + nnode->nbranch[j].offs = boffs; + boffs += bsz; + bcnt--; + } else { + nnode->nbranch[j].lnum = 0; + nnode->nbranch[j].offs = 0; + } + } + nnode->num = ubifs_calc_nnode_num(row, i); + ubifs_pack_nnode(c, p, nnode); + p += c->nnode_sz; + len += c->nnode_sz; + } + /* Row zero is the top row */ + if (row == 0) + break; + /* Update the information about the level below */ + bcnt = cnt; + bsz = c->nnode_sz; + row -= 1; + } + + if (c->big_lpt) { + /* Need to add LPT's save table */ + if (len + c->lsave_sz > c->leb_size) { + alen = ALIGN(len, c->min_io_size); + set_ltab(c, lnum, c->leb_size - alen, alen - len); + /* + * Different from linux kernel. + * The mkfs may partially write data into a certain LEB + * of file image, the left unwritten area in the LEB + * should be filled with '0xFF'. + */ + if (c->libubi) { + memset(p, 0xff, alen - len); + err = ubifs_leb_change(c, lnum++, buf, alen); + } else { + memset(p, 0xff, c->leb_size - len); + err = ubifs_leb_change(c, lnum++, buf, c->leb_size); + } + if (err) + goto out; + p = buf; + len = 0; + } + + c->lsave_lnum = lnum; + c->lsave_offs = len; + + for (i = 0; i < c->lsave_cnt && i < c->main_lebs; i++) + lsave[i] = c->main_first + i; + for (; i < c->lsave_cnt; i++) + lsave[i] = c->main_first; + + ubifs_pack_lsave(c, p, lsave); + p += c->lsave_sz; + len += c->lsave_sz; + } + + /* Need to add LPT's own LEB properties table */ + if (len + c->ltab_sz > c->leb_size) { + alen = ALIGN(len, c->min_io_size); + set_ltab(c, lnum, c->leb_size - alen, alen - len); + /* + * Different from linux kernel. + * The mkfs may partially write data into a certain LEB + * of file image, the left unwritten area in the LEB + * should be filled with '0xFF'. + */ + if (c->libubi) { + memset(p, 0xff, alen - len); + err = ubifs_leb_change(c, lnum++, buf, alen); + } else { + memset(p, 0xff, c->leb_size - len); + err = ubifs_leb_change(c, lnum++, buf, c->leb_size); + } + if (err) + goto out; + p = buf; + len = 0; + } + + c->ltab_lnum = lnum; + c->ltab_offs = len; + + /* Update ltab before packing it */ + len += c->ltab_sz; + alen = ALIGN(len, c->min_io_size); + set_ltab(c, lnum, c->leb_size - alen, alen - len); + + ubifs_pack_ltab(c, p, ltab); + p += c->ltab_sz; + + /* Write remaining buffer */ + /* + * Different from linux kernel. + * The mkfs may partially write data into a certain LEB + * of file image, the left unwritten area in the LEB + * should be filled with '0xFF'. + */ + if (c->libubi) { + memset(p, 0xff, alen - len); + err = ubifs_leb_change(c, lnum, buf, alen); + } else { + memset(p, 0xff, c->leb_size - len); + err = ubifs_leb_change(c, lnum, buf, c->leb_size); + } + if (err) + goto out; + + if (c->big_lpt && c->lsave) + memcpy(c->lsave, lsave, c->lsave_cnt * sizeof(int)); + + err = ubifs_shash_final(c, desc, hash); + if (err) + goto out; + + c->nhead_lnum = lnum; + c->nhead_offs = ALIGN(len, c->min_io_size); + + dbg_lp("space_bits %d", c->space_bits); + dbg_lp("lpt_lnum_bits %d", c->lpt_lnum_bits); + dbg_lp("lpt_offs_bits %d", c->lpt_offs_bits); + dbg_lp("lpt_spc_bits %d", c->lpt_spc_bits); + dbg_lp("pcnt_bits %d", c->pcnt_bits); + dbg_lp("lnum_bits %d", c->lnum_bits); + dbg_lp("pnode_sz %d", c->pnode_sz); + dbg_lp("nnode_sz %d", c->nnode_sz); + dbg_lp("ltab_sz %d", c->ltab_sz); + dbg_lp("lsave_sz %d", c->lsave_sz); + dbg_lp("lsave_cnt %d", c->lsave_cnt); + dbg_lp("lpt_hght %d", c->lpt_hght); + dbg_lp("big_lpt %u", c->big_lpt); + dbg_lp("LPT root is at %d:%d", c->lpt_lnum, c->lpt_offs); + dbg_lp("LPT head is at %d:%d", c->nhead_lnum, c->nhead_offs); + dbg_lp("LPT ltab is at %d:%d", c->ltab_lnum, c->ltab_offs); + if (c->big_lpt) + dbg_lp("LPT lsave is at %d:%d", c->lsave_lnum, c->lsave_offs); +out: + if (free_ltab || err) { + c->ltab = NULL; + vfree(ltab); + } + kfree(desc); + kfree(lsave); + vfree(buf); + kfree(nnode); + kfree(pnode); + return err; +} + +/** + * update_cats - add LEB properties of a pnode to LEB category lists and heaps. + * @c: UBIFS file-system description object + * @pnode: pnode + * + * When a pnode is loaded into memory, the LEB properties it contains are added, + * by this function, to the LEB category lists and heaps. + */ +static void update_cats(struct ubifs_info *c, struct ubifs_pnode *pnode) +{ + int i; + + for (i = 0; i < UBIFS_LPT_FANOUT; i++) { + int cat = pnode->lprops[i].flags & LPROPS_CAT_MASK; + int lnum = pnode->lprops[i].lnum; + + if (!lnum) + return; + ubifs_add_to_cat(c, &pnode->lprops[i], cat); + } +} + +/** + * replace_cats - add LEB properties of a pnode to LEB category lists and heaps. + * @c: UBIFS file-system description object + * @old_pnode: pnode copied + * @new_pnode: pnode copy + * + * During commit it is sometimes necessary to copy a pnode + * (see dirty_cow_pnode). When that happens, references in + * category lists and heaps must be replaced. This function does that. + */ +static void replace_cats(struct ubifs_info *c, struct ubifs_pnode *old_pnode, + struct ubifs_pnode *new_pnode) +{ + int i; + + for (i = 0; i < UBIFS_LPT_FANOUT; i++) { + if (!new_pnode->lprops[i].lnum) + return; + ubifs_replace_cat(c, &old_pnode->lprops[i], + &new_pnode->lprops[i]); + } +} + +/** + * check_lpt_crc - check LPT node crc is correct. + * @c: UBIFS file-system description object + * @buf: buffer containing node + * @len: length of node + * + * This function returns %0 on success and a negative error code on failure. + */ +static int check_lpt_crc(const struct ubifs_info *c, void *buf, int len) +{ + int pos = 0; + uint8_t *addr = buf; + uint16_t crc, calc_crc; + + crc = ubifs_unpack_bits(c, &addr, &pos, UBIFS_LPT_CRC_BITS); + calc_crc = crc16(-1, buf + UBIFS_LPT_CRC_BYTES, + len - UBIFS_LPT_CRC_BYTES); + if (crc != calc_crc) { + set_failure_reason_callback(c, FR_LPT_CORRUPTED); + ubifs_err(c, "invalid crc in LPT node: crc %hx calc %hx", + crc, calc_crc); + dump_stack(); + return -EINVAL; + } + return 0; +} + +/** + * check_lpt_type - check LPT node type is correct. + * @c: UBIFS file-system description object + * @addr: address of type bit field is passed and returned updated here + * @pos: position of type bit field is passed and returned updated here + * @type: expected type + * + * This function returns %0 on success and a negative error code on failure. + */ +static int check_lpt_type(const struct ubifs_info *c, uint8_t **addr, + int *pos, int type) +{ + int node_type; + + node_type = ubifs_unpack_bits(c, addr, pos, UBIFS_LPT_TYPE_BITS); + if (node_type != type) { + set_failure_reason_callback(c, FR_LPT_CORRUPTED); + ubifs_err(c, "invalid type (%d) in LPT node type %d", + node_type, type); + dump_stack(); + return -EINVAL; + } + return 0; +} + +/** + * unpack_pnode - unpack a pnode. + * @c: UBIFS file-system description object + * @buf: buffer containing packed pnode to unpack + * @pnode: pnode structure to fill + * + * This function returns %0 on success and a negative error code on failure. + */ +static int unpack_pnode(const struct ubifs_info *c, void *buf, + struct ubifs_pnode *pnode) +{ + uint8_t *addr = buf + UBIFS_LPT_CRC_BYTES; + int i, pos = 0, err; + + err = check_lpt_type(c, &addr, &pos, UBIFS_LPT_PNODE); + if (err) + return err; + if (c->big_lpt) + pnode->num = ubifs_unpack_bits(c, &addr, &pos, c->pcnt_bits); + for (i = 0; i < UBIFS_LPT_FANOUT; i++) { + struct ubifs_lprops * const lprops = &pnode->lprops[i]; + + lprops->free = ubifs_unpack_bits(c, &addr, &pos, c->space_bits); + lprops->free <<= 3; + lprops->dirty = ubifs_unpack_bits(c, &addr, &pos, c->space_bits); + lprops->dirty <<= 3; + + if (ubifs_unpack_bits(c, &addr, &pos, 1)) + lprops->flags = LPROPS_INDEX; + else + lprops->flags = 0; + lprops->flags |= ubifs_categorize_lprops(c, lprops); + } + err = check_lpt_crc(c, buf, c->pnode_sz); + return err; +} + +/** + * ubifs_unpack_nnode - unpack a nnode. + * @c: UBIFS file-system description object + * @buf: buffer containing packed nnode to unpack + * @nnode: nnode structure to fill + * + * This function returns %0 on success and a negative error code on failure. + */ +int ubifs_unpack_nnode(const struct ubifs_info *c, void *buf, + struct ubifs_nnode *nnode) +{ + uint8_t *addr = buf + UBIFS_LPT_CRC_BYTES; + int i, pos = 0, err; + + err = check_lpt_type(c, &addr, &pos, UBIFS_LPT_NNODE); + if (err) + return err; + if (c->big_lpt) + nnode->num = ubifs_unpack_bits(c, &addr, &pos, c->pcnt_bits); + for (i = 0; i < UBIFS_LPT_FANOUT; i++) { + int lnum; + + lnum = ubifs_unpack_bits(c, &addr, &pos, c->lpt_lnum_bits) + + c->lpt_first; + if (lnum == c->lpt_last + 1) + lnum = 0; + nnode->nbranch[i].lnum = lnum; + nnode->nbranch[i].offs = ubifs_unpack_bits(c, &addr, &pos, + c->lpt_offs_bits); + } + err = check_lpt_crc(c, buf, c->nnode_sz); + return err; +} + +/** + * unpack_ltab - unpack the LPT's own lprops table. + * @c: UBIFS file-system description object + * @buf: buffer from which to unpack + * + * This function returns %0 on success and a negative error code on failure. + */ +static int unpack_ltab(const struct ubifs_info *c, void *buf) +{ + uint8_t *addr = buf + UBIFS_LPT_CRC_BYTES; + int i, pos = 0, err; + + err = check_lpt_type(c, &addr, &pos, UBIFS_LPT_LTAB); + if (err) + return err; + for (i = 0; i < c->lpt_lebs; i++) { + int free = ubifs_unpack_bits(c, &addr, &pos, c->lpt_spc_bits); + int dirty = ubifs_unpack_bits(c, &addr, &pos, c->lpt_spc_bits); + + if (free < 0 || free > c->leb_size || dirty < 0 || + dirty > c->leb_size || free + dirty > c->leb_size) { + set_failure_reason_callback(c, FR_LPT_CORRUPTED); + return -EINVAL; + } + + c->ltab[i].free = free; + c->ltab[i].dirty = dirty; + c->ltab[i].tgc = 0; + c->ltab[i].cmt = 0; + } + err = check_lpt_crc(c, buf, c->ltab_sz); + return err; +} + +/** + * unpack_lsave - unpack the LPT's save table. + * @c: UBIFS file-system description object + * @buf: buffer from which to unpack + * + * This function returns %0 on success and a negative error code on failure. + */ +static int unpack_lsave(const struct ubifs_info *c, void *buf) +{ + uint8_t *addr = buf + UBIFS_LPT_CRC_BYTES; + int i, pos = 0, err; + + err = check_lpt_type(c, &addr, &pos, UBIFS_LPT_LSAVE); + if (err) + return err; + for (i = 0; i < c->lsave_cnt; i++) { + int lnum = ubifs_unpack_bits(c, &addr, &pos, c->lnum_bits); + + if (lnum < c->main_first || lnum >= c->leb_cnt) { + set_failure_reason_callback(c, FR_LPT_CORRUPTED); + return -EINVAL; + } + c->lsave[i] = lnum; + } + err = check_lpt_crc(c, buf, c->lsave_sz); + return err; +} + +/** + * validate_nnode - validate a nnode. + * @c: UBIFS file-system description object + * @nnode: nnode to validate + * @parent: parent nnode (or NULL for the root nnode) + * @iip: index in parent + * + * This function returns %0 on success and a negative error code on failure. + */ +static int validate_nnode(const struct ubifs_info *c, struct ubifs_nnode *nnode, + struct ubifs_nnode *parent, int iip) +{ + int i, lvl, max_offs; + + if (c->big_lpt) { + int num = calc_nnode_num_from_parent(c, parent, iip); + + if (nnode->num != num) + goto out_invalid; + } + lvl = parent ? parent->level - 1 : c->lpt_hght; + if (lvl < 1) + goto out_invalid; + if (lvl == 1) + max_offs = c->leb_size - c->pnode_sz; + else + max_offs = c->leb_size - c->nnode_sz; + for (i = 0; i < UBIFS_LPT_FANOUT; i++) { + int lnum = nnode->nbranch[i].lnum; + int offs = nnode->nbranch[i].offs; + + if (lnum == 0) { + if (offs != 0) + goto out_invalid; + continue; + } + if (lnum < c->lpt_first || lnum > c->lpt_last) + goto out_invalid; + if (offs < 0 || offs > max_offs) + goto out_invalid; + } + return 0; + +out_invalid: + set_failure_reason_callback(c, FR_LPT_CORRUPTED); + return -EINVAL; +} + +/** + * validate_pnode - validate a pnode. + * @c: UBIFS file-system description object + * @pnode: pnode to validate + * @parent: parent nnode + * @iip: index in parent + * + * This function returns %0 on success and a negative error code on failure. + */ +static int validate_pnode(const struct ubifs_info *c, struct ubifs_pnode *pnode, + struct ubifs_nnode *parent, int iip) +{ + int i; + + if (c->big_lpt) { + int num = calc_pnode_num_from_parent(c, parent, iip); + + if (pnode->num != num) + goto out_invalid; + } + for (i = 0; i < UBIFS_LPT_FANOUT; i++) { + int free = pnode->lprops[i].free; + int dirty = pnode->lprops[i].dirty; + + if (free < 0 || free > c->leb_size || free % c->min_io_size || + (free & 7)) + goto out_invalid; + if (dirty < 0 || dirty > c->leb_size || (dirty & 7)) + goto out_invalid; + if (dirty + free > c->leb_size) + goto out_invalid; + } + return 0; + +out_invalid: + set_failure_reason_callback(c, FR_LPT_CORRUPTED); + return -EINVAL; +} + +/** + * set_pnode_lnum - set LEB numbers on a pnode. + * @c: UBIFS file-system description object + * @pnode: pnode to update + * + * This function calculates the LEB numbers for the LEB properties it contains + * based on the pnode number. + */ +static void set_pnode_lnum(const struct ubifs_info *c, + struct ubifs_pnode *pnode) +{ + int i, lnum; + + lnum = (pnode->num << UBIFS_LPT_FANOUT_SHIFT) + c->main_first; + for (i = 0; i < UBIFS_LPT_FANOUT; i++) { + if (lnum >= c->leb_cnt) + return; + pnode->lprops[i].lnum = lnum++; + } +} + +/** + * ubifs_read_nnode - read a nnode from flash and link it to the tree in memory. + * @c: UBIFS file-system description object + * @parent: parent nnode (or NULL for the root) + * @iip: index in parent + * + * This function returns %0 on success and a negative error code on failure. + */ +int ubifs_read_nnode(struct ubifs_info *c, struct ubifs_nnode *parent, int iip) +{ + struct ubifs_nbranch *branch = NULL; + struct ubifs_nnode *nnode = NULL; + void *buf = c->lpt_nod_buf; + int err, lnum, offs; + + if (parent) { + branch = &parent->nbranch[iip]; + lnum = branch->lnum; + offs = branch->offs; + } else { + lnum = c->lpt_lnum; + offs = c->lpt_offs; + } + nnode = kzalloc(sizeof(struct ubifs_nnode), GFP_NOFS); + if (!nnode) { + err = -ENOMEM; + goto out; + } + if (lnum == 0) { + /* + * This nnode was not written which just means that the LEB + * properties in the subtree below it describe empty LEBs. We + * make the nnode as though we had read it, which in fact means + * doing almost nothing. + */ + if (c->big_lpt) + nnode->num = calc_nnode_num_from_parent(c, parent, iip); + } else { + err = ubifs_leb_read(c, lnum, buf, offs, c->nnode_sz, 1); + if (err) { + if (err == -EBADMSG) + set_failure_reason_callback(c, FR_LPT_CORRUPTED); + goto out; + } + err = ubifs_unpack_nnode(c, buf, nnode); + if (err) + goto out; + } + err = validate_nnode(c, nnode, parent, iip); + if (err) + goto out; + if (!c->big_lpt) + nnode->num = calc_nnode_num_from_parent(c, parent, iip); + if (parent) { + branch->nnode = nnode; + nnode->level = parent->level - 1; + } else { + c->nroot = nnode; + nnode->level = c->lpt_hght; + } + nnode->parent = parent; + nnode->iip = iip; + return 0; + +out: + ubifs_err(c, "error %d reading nnode at %d:%d", err, lnum, offs); + dump_stack(); + kfree(nnode); + return err; +} + +/** + * read_pnode - read a pnode from flash and link it to the tree in memory. + * @c: UBIFS file-system description object + * @parent: parent nnode + * @iip: index in parent + * + * This function returns %0 on success and a negative error code on failure. + */ +static int read_pnode(struct ubifs_info *c, struct ubifs_nnode *parent, int iip) +{ + struct ubifs_nbranch *branch; + struct ubifs_pnode *pnode = NULL; + void *buf = c->lpt_nod_buf; + int err, lnum, offs; + + branch = &parent->nbranch[iip]; + lnum = branch->lnum; + offs = branch->offs; + pnode = kzalloc(sizeof(struct ubifs_pnode), GFP_NOFS); + if (!pnode) + return -ENOMEM; + + if (lnum == 0) { + /* + * This pnode was not written which just means that the LEB + * properties in it describe empty LEBs. We make the pnode as + * though we had read it. + */ + int i; + + if (c->big_lpt) + pnode->num = calc_pnode_num_from_parent(c, parent, iip); + for (i = 0; i < UBIFS_LPT_FANOUT; i++) { + struct ubifs_lprops * const lprops = &pnode->lprops[i]; + + lprops->free = c->leb_size; + lprops->flags = ubifs_categorize_lprops(c, lprops); + } + } else { + err = ubifs_leb_read(c, lnum, buf, offs, c->pnode_sz, 1); + if (err) { + if (err == -EBADMSG) + set_failure_reason_callback(c, FR_LPT_CORRUPTED); + goto out; + } + err = unpack_pnode(c, buf, pnode); + if (err) + goto out; + } + err = validate_pnode(c, pnode, parent, iip); + if (err) + goto out; + if (!c->big_lpt) + pnode->num = calc_pnode_num_from_parent(c, parent, iip); + branch->pnode = pnode; + pnode->parent = parent; + pnode->iip = iip; + set_pnode_lnum(c, pnode); + c->pnodes_have += 1; + return 0; + +out: + ubifs_err(c, "error %d reading pnode at %d:%d", err, lnum, offs); + ubifs_dump_pnode(c, pnode, parent, iip); + dump_stack(); + ubifs_err(c, "calc num: %d", calc_pnode_num_from_parent(c, parent, iip)); + kfree(pnode); + return err; +} + +/** + * read_ltab - read LPT's own lprops table. + * @c: UBIFS file-system description object + * + * This function returns %0 on success and a negative error code on failure. + */ +static int read_ltab(struct ubifs_info *c) +{ + int err; + void *buf; + + buf = vmalloc(c->ltab_sz); + if (!buf) + return -ENOMEM; + err = ubifs_leb_read(c, c->ltab_lnum, buf, c->ltab_offs, c->ltab_sz, 1); + if (err) { + if (err == -EBADMSG) + set_failure_reason_callback(c, FR_LPT_CORRUPTED); + goto out; + } + err = unpack_ltab(c, buf); +out: + vfree(buf); + return err; +} + +/** + * read_lsave - read LPT's save table. + * @c: UBIFS file-system description object + * + * This function returns %0 on success and a negative error code on failure. + */ +static int read_lsave(struct ubifs_info *c) +{ + int err, i; + void *buf; + + buf = vmalloc(c->lsave_sz); + if (!buf) + return -ENOMEM; + err = ubifs_leb_read(c, c->lsave_lnum, buf, c->lsave_offs, + c->lsave_sz, 1); + if (err) { + if (err == -EBADMSG) + set_failure_reason_callback(c, FR_LPT_CORRUPTED); + goto out; + } + err = unpack_lsave(c, buf); + if (err) + goto out; + for (i = 0; i < c->lsave_cnt; i++) { + int lnum = c->lsave[i]; + struct ubifs_lprops *lprops; + + /* + * Due to automatic resizing, the values in the lsave table + * could be beyond the volume size - just ignore them. + */ + if (lnum >= c->leb_cnt) + continue; + lprops = ubifs_lpt_lookup(c, lnum); + if (IS_ERR(lprops)) { + err = PTR_ERR(lprops); + goto out; + } + } +out: + vfree(buf); + return err; +} + +/** + * ubifs_get_nnode - get a nnode. + * @c: UBIFS file-system description object + * @parent: parent nnode (or NULL for the root) + * @iip: index in parent + * + * This function returns a pointer to the nnode on success or a negative error + * code on failure. + */ +struct ubifs_nnode *ubifs_get_nnode(struct ubifs_info *c, + struct ubifs_nnode *parent, int iip) +{ + struct ubifs_nbranch *branch; + struct ubifs_nnode *nnode; + int err; + + branch = &parent->nbranch[iip]; + nnode = branch->nnode; + if (nnode) + return nnode; + err = ubifs_read_nnode(c, parent, iip); + if (err) + return ERR_PTR(err); + return branch->nnode; +} + +/** + * ubifs_get_pnode - get a pnode. + * @c: UBIFS file-system description object + * @parent: parent nnode + * @iip: index in parent + * + * This function returns a pointer to the pnode on success or a negative error + * code on failure. + */ +struct ubifs_pnode *ubifs_get_pnode(struct ubifs_info *c, + struct ubifs_nnode *parent, int iip) +{ + struct ubifs_nbranch *branch; + struct ubifs_pnode *pnode; + int err; + + branch = &parent->nbranch[iip]; + pnode = branch->pnode; + if (pnode) + return pnode; + err = read_pnode(c, parent, iip); + if (err) + return ERR_PTR(err); + update_cats(c, branch->pnode); + return branch->pnode; +} + +/** + * ubifs_pnode_lookup - lookup a pnode in the LPT. + * @c: UBIFS file-system description object + * @i: pnode number (0 to (main_lebs - 1) / UBIFS_LPT_FANOUT) + * + * This function returns a pointer to the pnode on success or a negative + * error code on failure. + */ +struct ubifs_pnode *ubifs_pnode_lookup(struct ubifs_info *c, int i) +{ + int err, h, iip, shft; + struct ubifs_nnode *nnode; + + if (!c->nroot) { + err = ubifs_read_nnode(c, NULL, 0); + if (err) + return ERR_PTR(err); + } + i <<= UBIFS_LPT_FANOUT_SHIFT; + nnode = c->nroot; + shft = c->lpt_hght * UBIFS_LPT_FANOUT_SHIFT; + for (h = 1; h < c->lpt_hght; h++) { + iip = ((i >> shft) & (UBIFS_LPT_FANOUT - 1)); + shft -= UBIFS_LPT_FANOUT_SHIFT; + nnode = ubifs_get_nnode(c, nnode, iip); + if (IS_ERR(nnode)) + return ERR_CAST(nnode); + } + iip = ((i >> shft) & (UBIFS_LPT_FANOUT - 1)); + return ubifs_get_pnode(c, nnode, iip); +} + +/** + * ubifs_lpt_lookup - lookup LEB properties in the LPT. + * @c: UBIFS file-system description object + * @lnum: LEB number to lookup + * + * This function returns a pointer to the LEB properties on success or a + * negative error code on failure. + */ +struct ubifs_lprops *ubifs_lpt_lookup(struct ubifs_info *c, int lnum) +{ + int i, iip; + struct ubifs_pnode *pnode; + + i = lnum - c->main_first; + pnode = ubifs_pnode_lookup(c, i >> UBIFS_LPT_FANOUT_SHIFT); + if (IS_ERR(pnode)) + return ERR_CAST(pnode); + iip = (i & (UBIFS_LPT_FANOUT - 1)); + dbg_lp("LEB %d, free %d, dirty %d, flags %d", lnum, + pnode->lprops[iip].free, pnode->lprops[iip].dirty, + pnode->lprops[iip].flags); + return &pnode->lprops[iip]; +} + +/** + * dirty_cow_nnode - ensure a nnode is not being committed. + * @c: UBIFS file-system description object + * @nnode: nnode to check + * + * Returns dirtied nnode on success or negative error code on failure. + */ +static struct ubifs_nnode *dirty_cow_nnode(struct ubifs_info *c, + struct ubifs_nnode *nnode) +{ + struct ubifs_nnode *n; + int i; + + if (!test_bit(COW_CNODE, &nnode->flags)) { + /* nnode is not being committed */ + if (!test_and_set_bit(DIRTY_CNODE, &nnode->flags)) { + c->dirty_nn_cnt += 1; + ubifs_add_nnode_dirt(c, nnode); + } + return nnode; + } + + /* nnode is being committed, so copy it */ + n = kmemdup(nnode, sizeof(struct ubifs_nnode), GFP_NOFS); + if (unlikely(!n)) + return ERR_PTR(-ENOMEM); + + n->cnext = NULL; + __set_bit(DIRTY_CNODE, &n->flags); + __clear_bit(COW_CNODE, &n->flags); + + /* The children now have new parent */ + for (i = 0; i < UBIFS_LPT_FANOUT; i++) { + struct ubifs_nbranch *branch = &n->nbranch[i]; + + if (branch->cnode) + branch->cnode->parent = n; + } + + ubifs_assert(c, !test_bit(OBSOLETE_CNODE, &nnode->flags)); + __set_bit(OBSOLETE_CNODE, &nnode->flags); + + c->dirty_nn_cnt += 1; + ubifs_add_nnode_dirt(c, nnode); + if (nnode->parent) + nnode->parent->nbranch[n->iip].nnode = n; + else + c->nroot = n; + return n; +} + +/** + * dirty_cow_pnode - ensure a pnode is not being committed. + * @c: UBIFS file-system description object + * @pnode: pnode to check + * + * Returns dirtied pnode on success or negative error code on failure. + */ +static struct ubifs_pnode *dirty_cow_pnode(struct ubifs_info *c, + struct ubifs_pnode *pnode) +{ + struct ubifs_pnode *p; + + if (!test_bit(COW_CNODE, &pnode->flags)) { + /* pnode is not being committed */ + if (!test_and_set_bit(DIRTY_CNODE, &pnode->flags)) { + c->dirty_pn_cnt += 1; + add_pnode_dirt(c, pnode); + } + return pnode; + } + + /* pnode is being committed, so copy it */ + p = kmemdup(pnode, sizeof(struct ubifs_pnode), GFP_NOFS); + if (unlikely(!p)) + return ERR_PTR(-ENOMEM); + + p->cnext = NULL; + __set_bit(DIRTY_CNODE, &p->flags); + __clear_bit(COW_CNODE, &p->flags); + replace_cats(c, pnode, p); + + ubifs_assert(c, !test_bit(OBSOLETE_CNODE, &pnode->flags)); + __set_bit(OBSOLETE_CNODE, &pnode->flags); + + c->dirty_pn_cnt += 1; + add_pnode_dirt(c, pnode); + pnode->parent->nbranch[p->iip].pnode = p; + return p; +} + +/** + * ubifs_lpt_lookup_dirty - lookup LEB properties in the LPT. + * @c: UBIFS file-system description object + * @lnum: LEB number to lookup + * + * This function returns a pointer to the LEB properties on success or a + * negative error code on failure. + */ +struct ubifs_lprops *ubifs_lpt_lookup_dirty(struct ubifs_info *c, int lnum) +{ + int err, i, h, iip, shft; + struct ubifs_nnode *nnode; + struct ubifs_pnode *pnode; + + if (!c->nroot) { + err = ubifs_read_nnode(c, NULL, 0); + if (err) + return ERR_PTR(err); + } + nnode = c->nroot; + nnode = dirty_cow_nnode(c, nnode); + if (IS_ERR(nnode)) + return ERR_CAST(nnode); + i = lnum - c->main_first; + shft = c->lpt_hght * UBIFS_LPT_FANOUT_SHIFT; + for (h = 1; h < c->lpt_hght; h++) { + iip = ((i >> shft) & (UBIFS_LPT_FANOUT - 1)); + shft -= UBIFS_LPT_FANOUT_SHIFT; + nnode = ubifs_get_nnode(c, nnode, iip); + if (IS_ERR(nnode)) + return ERR_CAST(nnode); + nnode = dirty_cow_nnode(c, nnode); + if (IS_ERR(nnode)) + return ERR_CAST(nnode); + } + iip = ((i >> shft) & (UBIFS_LPT_FANOUT - 1)); + pnode = ubifs_get_pnode(c, nnode, iip); + if (IS_ERR(pnode)) + return ERR_CAST(pnode); + pnode = dirty_cow_pnode(c, pnode); + if (IS_ERR(pnode)) + return ERR_CAST(pnode); + iip = (i & (UBIFS_LPT_FANOUT - 1)); + dbg_lp("LEB %d, free %d, dirty %d, flags %d", lnum, + pnode->lprops[iip].free, pnode->lprops[iip].dirty, + pnode->lprops[iip].flags); + ubifs_assert(c, test_bit(DIRTY_CNODE, &pnode->flags)); + return &pnode->lprops[iip]; +} + +/** + * ubifs_lpt_calc_hash - Calculate hash of the LPT pnodes + * @c: UBIFS file-system description object + * @hash: the returned hash of the LPT pnodes + * + * This function iterates over the LPT pnodes and creates a hash over them. + * Returns 0 for success or a negative error code otherwise. + */ +int ubifs_lpt_calc_hash(struct ubifs_info *c, u8 *hash) +{ + struct ubifs_nnode *nnode, *nn; + struct ubifs_cnode *cnode; + struct shash_desc *desc; + int iip = 0, i; + int bufsiz = max_t(int, c->nnode_sz, c->pnode_sz); + void *buf; + int err; + + if (!ubifs_authenticated(c)) + return 0; + + if (!c->nroot) { + err = ubifs_read_nnode(c, NULL, 0); + if (err) + return err; + } + + desc = ubifs_hash_get_desc(c); + if (IS_ERR(desc)) + return PTR_ERR(desc); + + buf = kmalloc(bufsiz, GFP_NOFS); + if (!buf) { + err = -ENOMEM; + goto out; + } + + cnode = (struct ubifs_cnode *)c->nroot; + + while (cnode) { + nnode = cnode->parent; + nn = (struct ubifs_nnode *)cnode; + if (cnode->level > 1) { + while (iip < UBIFS_LPT_FANOUT) { + if (nn->nbranch[iip].lnum == 0) { + /* Go right */ + iip++; + continue; + } + + nnode = ubifs_get_nnode(c, nn, iip); + if (IS_ERR(nnode)) { + err = PTR_ERR(nnode); + goto out; + } + + /* Go down */ + iip = 0; + cnode = (struct ubifs_cnode *)nnode; + break; + } + if (iip < UBIFS_LPT_FANOUT) + continue; + } else { + struct ubifs_pnode *pnode; + + for (i = 0; i < UBIFS_LPT_FANOUT; i++) { + if (nn->nbranch[i].lnum == 0) + continue; + pnode = ubifs_get_pnode(c, nn, i); + if (IS_ERR(pnode)) { + err = PTR_ERR(pnode); + goto out; + } + + ubifs_pack_pnode(c, buf, pnode); + err = ubifs_shash_update(c, desc, buf, + c->pnode_sz); + if (err) + goto out; + } + } + /* Go up and to the right */ + iip = cnode->iip + 1; + cnode = (struct ubifs_cnode *)nnode; + } + + err = ubifs_shash_final(c, desc, hash); +out: + kfree(desc); + kfree(buf); + + return err; +} + +/** + * lpt_check_hash - check the hash of the LPT. + * @c: UBIFS file-system description object + * + * This function calculates a hash over all pnodes in the LPT and compares it with + * the hash stored in the master node. Returns %0 on success and a negative error + * code on failure. + */ +static int lpt_check_hash(struct ubifs_info *c) +{ + int err; + u8 hash[UBIFS_HASH_ARR_SZ]; + + if (!ubifs_authenticated(c)) + return 0; + + err = ubifs_lpt_calc_hash(c, hash); + if (err) + return err; + + if (ubifs_check_hash(c, c->mst_node->hash_lpt, hash)) { + err = -EPERM; + ubifs_err(c, "Failed to authenticate LPT"); + } else { + err = 0; + } + + return err; +} + +/** + * lpt_init_rd - initialize the LPT for reading. + * @c: UBIFS file-system description object + * + * This function returns %0 on success and a negative error code on failure. + */ +static int lpt_init_rd(struct ubifs_info *c) +{ + int err, i; + + c->ltab = vmalloc(array_size(sizeof(struct ubifs_lpt_lprops), + c->lpt_lebs)); + if (!c->ltab) + return -ENOMEM; + + i = max_t(int, c->nnode_sz, c->pnode_sz); + c->lpt_nod_buf = kmalloc(i, GFP_KERNEL); + if (!c->lpt_nod_buf) + return -ENOMEM; + + for (i = 0; i < LPROPS_HEAP_CNT; i++) { + c->lpt_heap[i].arr = kmalloc_array(LPT_HEAP_SZ, + sizeof(void *), + GFP_KERNEL); + if (!c->lpt_heap[i].arr) + return -ENOMEM; + c->lpt_heap[i].cnt = 0; + c->lpt_heap[i].max_cnt = LPT_HEAP_SZ; + } + + c->dirty_idx.arr = kmalloc_array(LPT_HEAP_SZ, sizeof(void *), + GFP_KERNEL); + if (!c->dirty_idx.arr) + return -ENOMEM; + c->dirty_idx.cnt = 0; + c->dirty_idx.max_cnt = LPT_HEAP_SZ; + + err = read_ltab(c); + if (err) { + if (test_and_clear_failure_reason_callback(c, FR_LPT_CORRUPTED) && + can_ignore_failure_callback(c, FR_LPT_CORRUPTED)) + err = 0; + else + return err; + } + + err = lpt_check_hash(c); + if (err) + return err; + + dbg_lp("space_bits %d", c->space_bits); + dbg_lp("lpt_lnum_bits %d", c->lpt_lnum_bits); + dbg_lp("lpt_offs_bits %d", c->lpt_offs_bits); + dbg_lp("lpt_spc_bits %d", c->lpt_spc_bits); + dbg_lp("pcnt_bits %d", c->pcnt_bits); + dbg_lp("lnum_bits %d", c->lnum_bits); + dbg_lp("pnode_sz %d", c->pnode_sz); + dbg_lp("nnode_sz %d", c->nnode_sz); + dbg_lp("ltab_sz %d", c->ltab_sz); + dbg_lp("lsave_sz %d", c->lsave_sz); + dbg_lp("lsave_cnt %d", c->lsave_cnt); + dbg_lp("lpt_hght %d", c->lpt_hght); + dbg_lp("big_lpt %u", c->big_lpt); + dbg_lp("LPT root is at %d:%d", c->lpt_lnum, c->lpt_offs); + dbg_lp("LPT head is at %d:%d", c->nhead_lnum, c->nhead_offs); + dbg_lp("LPT ltab is at %d:%d", c->ltab_lnum, c->ltab_offs); + if (c->big_lpt) + dbg_lp("LPT lsave is at %d:%d", c->lsave_lnum, c->lsave_offs); + + return 0; +} + +/** + * lpt_init_wr - initialize the LPT for writing. + * @c: UBIFS file-system description object + * + * 'lpt_init_rd()' must have been called already. + * + * This function returns %0 on success and a negative error code on failure. + */ +static int lpt_init_wr(struct ubifs_info *c) +{ + int err, i; + + c->ltab_cmt = vmalloc(array_size(sizeof(struct ubifs_lpt_lprops), + c->lpt_lebs)); + if (!c->ltab_cmt) + return -ENOMEM; + + c->lpt_buf = vmalloc(c->leb_size); + if (!c->lpt_buf) + return -ENOMEM; + + if (c->big_lpt) { + c->lsave = kmalloc_array(c->lsave_cnt, sizeof(int), GFP_NOFS); + if (!c->lsave) + return -ENOMEM; + err = read_lsave(c); + if (err) { + if (test_and_clear_failure_reason_callback(c, FR_LPT_CORRUPTED) && + can_ignore_failure_callback(c, FR_LPT_CORRUPTED)) + err = 0; + else + return err; + } + } + + for (i = 0; i < c->lpt_lebs; i++) + if (c->ltab[i].free == c->leb_size) { + err = ubifs_leb_unmap(c, i + c->lpt_first); + if (err) + return err; + } + + return 0; +} + +/** + * ubifs_lpt_init - initialize the LPT. + * @c: UBIFS file-system description object + * @rd: whether to initialize lpt for reading + * @wr: whether to initialize lpt for writing + * + * For mounting 'rw', @rd and @wr are both true. For mounting 'ro', @rd is true + * and @wr is false. For mounting from 'ro' to 'rw', @rd is false and @wr is + * true. + * + * This function returns %0 on success and a negative error code on failure. + */ +int ubifs_lpt_init(struct ubifs_info *c, int rd, int wr) +{ + int err; + + if (rd) { + err = lpt_init_rd(c); + if (err) + goto out_err; + } + + if (wr) { + err = lpt_init_wr(c); + if (err) + goto out_err; + } + + return 0; + +out_err: + if (wr) + ubifs_lpt_free(c, 1); + if (rd) + ubifs_lpt_free(c, 0); + return err; +} + +/** + * struct lpt_scan_node - somewhere to put nodes while we scan LPT. + * @nnode: where to keep a nnode + * @pnode: where to keep a pnode + * @cnode: where to keep a cnode + * @in_tree: is the node in the tree in memory + * @ptr: union of node pointers + * @ptr.nnode: pointer to the nnode (if it is an nnode) which may be here or in + * the tree + * @ptr.pnode: ditto for pnode + * @ptr.cnode: ditto for cnode + */ +struct lpt_scan_node { + union { + struct ubifs_nnode nnode; + struct ubifs_pnode pnode; + struct ubifs_cnode cnode; + }; + int in_tree; + union { + struct ubifs_nnode *nnode; + struct ubifs_pnode *pnode; + struct ubifs_cnode *cnode; + } ptr; +}; + +/** + * scan_get_nnode - for the scan, get a nnode from either the tree or flash. + * @c: the UBIFS file-system description object + * @path: where to put the nnode + * @parent: parent of the nnode + * @iip: index in parent of the nnode + * + * This function returns a pointer to the nnode on success or a negative error + * code on failure. + */ +static struct ubifs_nnode *scan_get_nnode(struct ubifs_info *c, + struct lpt_scan_node *path, + struct ubifs_nnode *parent, int iip) +{ + struct ubifs_nbranch *branch; + struct ubifs_nnode *nnode; + void *buf = c->lpt_nod_buf; + int err; + + branch = &parent->nbranch[iip]; + nnode = branch->nnode; + if (nnode) { + path->in_tree = 1; + path->ptr.nnode = nnode; + return nnode; + } + nnode = &path->nnode; + path->in_tree = 0; + path->ptr.nnode = nnode; + memset(nnode, 0, sizeof(struct ubifs_nnode)); + if (branch->lnum == 0) { + /* + * This nnode was not written which just means that the LEB + * properties in the subtree below it describe empty LEBs. We + * make the nnode as though we had read it, which in fact means + * doing almost nothing. + */ + if (c->big_lpt) + nnode->num = calc_nnode_num_from_parent(c, parent, iip); + } else { + err = ubifs_leb_read(c, branch->lnum, buf, branch->offs, + c->nnode_sz, 1); + if (err) { + if (err == -EBADMSG) + set_failure_reason_callback(c, FR_LPT_CORRUPTED); + return ERR_PTR(err); + } + err = ubifs_unpack_nnode(c, buf, nnode); + if (err) + return ERR_PTR(err); + } + err = validate_nnode(c, nnode, parent, iip); + if (err) + return ERR_PTR(err); + if (!c->big_lpt) + nnode->num = calc_nnode_num_from_parent(c, parent, iip); + nnode->level = parent->level - 1; + nnode->parent = parent; + nnode->iip = iip; + return nnode; +} + +/** + * scan_get_pnode - for the scan, get a pnode from either the tree or flash. + * @c: the UBIFS file-system description object + * @path: where to put the pnode + * @parent: parent of the pnode + * @iip: index in parent of the pnode + * + * This function returns a pointer to the pnode on success or a negative error + * code on failure. + */ +static struct ubifs_pnode *scan_get_pnode(struct ubifs_info *c, + struct lpt_scan_node *path, + struct ubifs_nnode *parent, int iip) +{ + struct ubifs_nbranch *branch; + struct ubifs_pnode *pnode; + void *buf = c->lpt_nod_buf; + int err; + + branch = &parent->nbranch[iip]; + pnode = branch->pnode; + if (pnode) { + path->in_tree = 1; + path->ptr.pnode = pnode; + return pnode; + } + pnode = &path->pnode; + path->in_tree = 0; + path->ptr.pnode = pnode; + memset(pnode, 0, sizeof(struct ubifs_pnode)); + if (branch->lnum == 0) { + /* + * This pnode was not written which just means that the LEB + * properties in it describe empty LEBs. We make the pnode as + * though we had read it. + */ + int i; + + if (c->big_lpt) + pnode->num = calc_pnode_num_from_parent(c, parent, iip); + for (i = 0; i < UBIFS_LPT_FANOUT; i++) { + struct ubifs_lprops * const lprops = &pnode->lprops[i]; + + lprops->free = c->leb_size; + lprops->flags = ubifs_categorize_lprops(c, lprops); + } + } else { + ubifs_assert(c, branch->lnum >= c->lpt_first && + branch->lnum <= c->lpt_last); + ubifs_assert(c, branch->offs >= 0 && branch->offs < c->leb_size); + err = ubifs_leb_read(c, branch->lnum, buf, branch->offs, + c->pnode_sz, 1); + if (err) { + if (err == -EBADMSG) + set_failure_reason_callback(c, FR_LPT_CORRUPTED); + return ERR_PTR(err); + } + err = unpack_pnode(c, buf, pnode); + if (err) + return ERR_PTR(err); + } + err = validate_pnode(c, pnode, parent, iip); + if (err) + return ERR_PTR(err); + if (!c->big_lpt) + pnode->num = calc_pnode_num_from_parent(c, parent, iip); + pnode->parent = parent; + pnode->iip = iip; + set_pnode_lnum(c, pnode); + return pnode; +} + +/** + * ubifs_lpt_scan_nolock - scan the LPT. + * @c: the UBIFS file-system description object + * @start_lnum: LEB number from which to start scanning + * @end_lnum: LEB number at which to stop scanning + * @scan_cb: callback function called for each lprops + * @data: data to be passed to the callback function + * + * This function returns %0 on success and a negative error code on failure. + */ +int ubifs_lpt_scan_nolock(struct ubifs_info *c, int start_lnum, int end_lnum, + ubifs_lpt_scan_callback scan_cb, void *data) +{ + int err = 0, i, h, iip, shft; + struct ubifs_nnode *nnode; + struct ubifs_pnode *pnode; + struct lpt_scan_node *path; + + if (start_lnum == -1) { + start_lnum = end_lnum + 1; + if (start_lnum >= c->leb_cnt) + start_lnum = c->main_first; + } + + ubifs_assert(c, start_lnum >= c->main_first && start_lnum < c->leb_cnt); + ubifs_assert(c, end_lnum >= c->main_first && end_lnum < c->leb_cnt); + + if (!c->nroot) { + err = ubifs_read_nnode(c, NULL, 0); + if (err) + return err; + } + + path = kmalloc_array(c->lpt_hght + 1, sizeof(struct lpt_scan_node), + GFP_NOFS); + if (!path) + return -ENOMEM; + + path[0].ptr.nnode = c->nroot; + path[0].in_tree = 1; +again: + /* Descend to the pnode containing start_lnum */ + nnode = c->nroot; + i = start_lnum - c->main_first; + shft = c->lpt_hght * UBIFS_LPT_FANOUT_SHIFT; + for (h = 1; h < c->lpt_hght; h++) { + iip = ((i >> shft) & (UBIFS_LPT_FANOUT - 1)); + shft -= UBIFS_LPT_FANOUT_SHIFT; + nnode = scan_get_nnode(c, path + h, nnode, iip); + if (IS_ERR(nnode)) { + err = PTR_ERR(nnode); + goto out; + } + } + iip = ((i >> shft) & (UBIFS_LPT_FANOUT - 1)); + pnode = scan_get_pnode(c, path + h, nnode, iip); + if (IS_ERR(pnode)) { + err = PTR_ERR(pnode); + goto out; + } + iip = (i & (UBIFS_LPT_FANOUT - 1)); + + /* Loop for each lprops */ + while (1) { + struct ubifs_lprops *lprops = &pnode->lprops[iip]; + int ret, lnum = lprops->lnum; + + ret = scan_cb(c, lprops, path[h].in_tree, data); + if (ret < 0) { + err = ret; + goto out; + } + if (ret & LPT_SCAN_ADD) { + /* Add all the nodes in path to the tree in memory */ + for (h = 1; h < c->lpt_hght; h++) { + const size_t sz = sizeof(struct ubifs_nnode); + struct ubifs_nnode *parent; + + if (path[h].in_tree) + continue; + nnode = kmemdup(&path[h].nnode, sz, GFP_NOFS); + if (!nnode) { + err = -ENOMEM; + goto out; + } + parent = nnode->parent; + parent->nbranch[nnode->iip].nnode = nnode; + path[h].ptr.nnode = nnode; + path[h].in_tree = 1; + path[h + 1].cnode.parent = nnode; + } + if (path[h].in_tree) + ubifs_ensure_cat(c, lprops); + else { + const size_t sz = sizeof(struct ubifs_pnode); + struct ubifs_nnode *parent; + + pnode = kmemdup(&path[h].pnode, sz, GFP_NOFS); + if (!pnode) { + err = -ENOMEM; + goto out; + } + parent = pnode->parent; + parent->nbranch[pnode->iip].pnode = pnode; + path[h].ptr.pnode = pnode; + path[h].in_tree = 1; + update_cats(c, pnode); + c->pnodes_have += 1; + } + err = dbg_check_lpt_nodes(c, (struct ubifs_cnode *) + c->nroot, 0, 0); + if (err) + goto out; + err = dbg_check_cats(c); + if (err) + goto out; + } + if (ret & LPT_SCAN_STOP) { + err = 0; + break; + } + /* Get the next lprops */ + if (lnum == end_lnum) { + /* + * We got to the end without finding what we were + * looking for + */ + err = -ENOSPC; + goto out; + } + if (lnum + 1 >= c->leb_cnt) { + /* Wrap-around to the beginning */ + start_lnum = c->main_first; + goto again; + } + if (iip + 1 < UBIFS_LPT_FANOUT) { + /* Next lprops is in the same pnode */ + iip += 1; + continue; + } + /* We need to get the next pnode. Go up until we can go right */ + iip = pnode->iip; + while (1) { + h -= 1; + ubifs_assert(c, h >= 0); + nnode = path[h].ptr.nnode; + if (iip + 1 < UBIFS_LPT_FANOUT) + break; + iip = nnode->iip; + } + /* Go right */ + iip += 1; + /* Descend to the pnode */ + h += 1; + for (; h < c->lpt_hght; h++) { + nnode = scan_get_nnode(c, path + h, nnode, iip); + if (IS_ERR(nnode)) { + err = PTR_ERR(nnode); + goto out; + } + iip = 0; + } + pnode = scan_get_pnode(c, path + h, nnode, iip); + if (IS_ERR(pnode)) { + err = PTR_ERR(pnode); + goto out; + } + iip = 0; + } +out: + kfree(path); + return err; +} diff --git a/ubifs-utils/libubifs/lpt_commit.c b/ubifs-utils/libubifs/lpt_commit.c new file mode 100644 index 0000000..79f7b14 --- /dev/null +++ b/ubifs-utils/libubifs/lpt_commit.c @@ -0,0 +1,1812 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * This file is part of UBIFS. + * + * Copyright (C) 2006-2008 Nokia Corporation. + * + * Authors: Adrian Hunter + * Artem Bityutskiy (Битюцкий Артём) + */ + +/* + * This file implements commit-related functionality of the LEB properties + * subsystem. + */ + +#include "linux_err.h" +#include "bitops.h" +#include "kmem.h" +#include "crc16.h" +#include "ubifs.h" +#include "defs.h" +#include "debug.h" +#include "misc.h" + +static int dbg_populate_lsave(struct ubifs_info *c); + +/** + * first_dirty_cnode - find first dirty cnode. + * @c: UBIFS file-system description object + * @nnode: nnode at which to start + * + * This function returns the first dirty cnode or %NULL if there is not one. + */ +static struct ubifs_cnode *first_dirty_cnode(const struct ubifs_info *c, struct ubifs_nnode *nnode) +{ + ubifs_assert(c, nnode); + while (1) { + int i, cont = 0; + + for (i = 0; i < UBIFS_LPT_FANOUT; i++) { + struct ubifs_cnode *cnode; + + cnode = nnode->nbranch[i].cnode; + if (cnode && + test_bit(DIRTY_CNODE, &cnode->flags)) { + if (cnode->level == 0) + return cnode; + nnode = (struct ubifs_nnode *)cnode; + cont = 1; + break; + } + } + if (!cont) + return (struct ubifs_cnode *)nnode; + } +} + +/** + * next_dirty_cnode - find next dirty cnode. + * @c: UBIFS file-system description object + * @cnode: cnode from which to begin searching + * + * This function returns the next dirty cnode or %NULL if there is not one. + */ +static struct ubifs_cnode *next_dirty_cnode(const struct ubifs_info *c, struct ubifs_cnode *cnode) +{ + struct ubifs_nnode *nnode; + int i; + + ubifs_assert(c, cnode); + nnode = cnode->parent; + if (!nnode) + return NULL; + for (i = cnode->iip + 1; i < UBIFS_LPT_FANOUT; i++) { + cnode = nnode->nbranch[i].cnode; + if (cnode && test_bit(DIRTY_CNODE, &cnode->flags)) { + if (cnode->level == 0) + return cnode; /* cnode is a pnode */ + /* cnode is a nnode */ + return first_dirty_cnode(c, (struct ubifs_nnode *)cnode); + } + } + return (struct ubifs_cnode *)nnode; +} + +/** + * get_cnodes_to_commit - create list of dirty cnodes to commit. + * @c: UBIFS file-system description object + * + * This function returns the number of cnodes to commit. + */ +static int get_cnodes_to_commit(struct ubifs_info *c) +{ + struct ubifs_cnode *cnode, *cnext; + int cnt = 0; + + if (!c->nroot) + return 0; + + if (!test_bit(DIRTY_CNODE, &c->nroot->flags)) + return 0; + + c->lpt_cnext = first_dirty_cnode(c, c->nroot); + cnode = c->lpt_cnext; + if (!cnode) + return 0; + cnt += 1; + while (1) { + ubifs_assert(c, !test_bit(COW_CNODE, &cnode->flags)); + __set_bit(COW_CNODE, &cnode->flags); + cnext = next_dirty_cnode(c, cnode); + if (!cnext) { + cnode->cnext = c->lpt_cnext; + break; + } + cnode->cnext = cnext; + cnode = cnext; + cnt += 1; + } + dbg_cmt("committing %d cnodes", cnt); + dbg_lp("committing %d cnodes", cnt); + ubifs_assert(c, cnt == c->dirty_nn_cnt + c->dirty_pn_cnt); + return cnt; +} + +/** + * upd_ltab - update LPT LEB properties. + * @c: UBIFS file-system description object + * @lnum: LEB number + * @free: amount of free space + * @dirty: amount of dirty space to add + */ +static void upd_ltab(struct ubifs_info *c, int lnum, int free, int dirty) +{ + dbg_lp("LEB %d free %d dirty %d to %d +%d", + lnum, c->ltab[lnum - c->lpt_first].free, + c->ltab[lnum - c->lpt_first].dirty, free, dirty); + ubifs_assert(c, lnum >= c->lpt_first && lnum <= c->lpt_last); + c->ltab[lnum - c->lpt_first].free = free; + c->ltab[lnum - c->lpt_first].dirty += dirty; +} + +/** + * alloc_lpt_leb - allocate an LPT LEB that is empty. + * @c: UBIFS file-system description object + * @lnum: LEB number is passed and returned here + * + * This function finds the next empty LEB in the ltab starting from @lnum. If a + * an empty LEB is found it is returned in @lnum and the function returns %0. + * Otherwise the function returns -ENOSPC. Note however, that LPT is designed + * never to run out of space. + */ +static int alloc_lpt_leb(struct ubifs_info *c, int *lnum) +{ + int i, n; + + n = *lnum - c->lpt_first + 1; + for (i = n; i < c->lpt_lebs; i++) { + if (c->ltab[i].tgc || c->ltab[i].cmt) + continue; + if (c->ltab[i].free == c->leb_size) { + c->ltab[i].cmt = 1; + *lnum = i + c->lpt_first; + return 0; + } + } + + for (i = 0; i < n; i++) { + if (c->ltab[i].tgc || c->ltab[i].cmt) + continue; + if (c->ltab[i].free == c->leb_size) { + c->ltab[i].cmt = 1; + *lnum = i + c->lpt_first; + return 0; + } + } + return -ENOSPC; +} + +/** + * layout_cnodes - layout cnodes for commit. + * @c: UBIFS file-system description object + * + * This function returns %0 on success and a negative error code on failure. + */ +static int layout_cnodes(struct ubifs_info *c) +{ + int lnum, offs, len, alen, done_lsave, done_ltab, err; + struct ubifs_cnode *cnode; + + err = dbg_chk_lpt_sz(c, 0, 0); + if (err) + return err; + cnode = c->lpt_cnext; + if (!cnode) + return 0; + lnum = c->nhead_lnum; + offs = c->nhead_offs; + /* Try to place lsave and ltab nicely */ + done_lsave = !c->big_lpt; + done_ltab = 0; + if (!done_lsave && offs + c->lsave_sz <= c->leb_size) { + done_lsave = 1; + c->lsave_lnum = lnum; + c->lsave_offs = offs; + offs += c->lsave_sz; + dbg_chk_lpt_sz(c, 1, c->lsave_sz); + } + + if (offs + c->ltab_sz <= c->leb_size) { + done_ltab = 1; + c->ltab_lnum = lnum; + c->ltab_offs = offs; + offs += c->ltab_sz; + dbg_chk_lpt_sz(c, 1, c->ltab_sz); + } + + do { + if (cnode->level) { + len = c->nnode_sz; + c->dirty_nn_cnt -= 1; + } else { + len = c->pnode_sz; + c->dirty_pn_cnt -= 1; + } + while (offs + len > c->leb_size) { + alen = ALIGN(offs, c->min_io_size); + upd_ltab(c, lnum, c->leb_size - alen, alen - offs); + dbg_chk_lpt_sz(c, 2, c->leb_size - offs); + err = alloc_lpt_leb(c, &lnum); + if (err) + goto no_space; + offs = 0; + ubifs_assert(c, lnum >= c->lpt_first && + lnum <= c->lpt_last); + /* Try to place lsave and ltab nicely */ + if (!done_lsave) { + done_lsave = 1; + c->lsave_lnum = lnum; + c->lsave_offs = offs; + offs += c->lsave_sz; + dbg_chk_lpt_sz(c, 1, c->lsave_sz); + continue; + } + if (!done_ltab) { + done_ltab = 1; + c->ltab_lnum = lnum; + c->ltab_offs = offs; + offs += c->ltab_sz; + dbg_chk_lpt_sz(c, 1, c->ltab_sz); + continue; + } + break; + } + if (cnode->parent) { + cnode->parent->nbranch[cnode->iip].lnum = lnum; + cnode->parent->nbranch[cnode->iip].offs = offs; + } else { + c->lpt_lnum = lnum; + c->lpt_offs = offs; + } + offs += len; + dbg_chk_lpt_sz(c, 1, len); + cnode = cnode->cnext; + } while (cnode && cnode != c->lpt_cnext); + + /* Make sure to place LPT's save table */ + if (!done_lsave) { + if (offs + c->lsave_sz > c->leb_size) { + alen = ALIGN(offs, c->min_io_size); + upd_ltab(c, lnum, c->leb_size - alen, alen - offs); + dbg_chk_lpt_sz(c, 2, c->leb_size - offs); + err = alloc_lpt_leb(c, &lnum); + if (err) + goto no_space; + offs = 0; + ubifs_assert(c, lnum >= c->lpt_first && + lnum <= c->lpt_last); + } + done_lsave = 1; + c->lsave_lnum = lnum; + c->lsave_offs = offs; + offs += c->lsave_sz; + dbg_chk_lpt_sz(c, 1, c->lsave_sz); + } + + /* Make sure to place LPT's own lprops table */ + if (!done_ltab) { + if (offs + c->ltab_sz > c->leb_size) { + alen = ALIGN(offs, c->min_io_size); + upd_ltab(c, lnum, c->leb_size - alen, alen - offs); + dbg_chk_lpt_sz(c, 2, c->leb_size - offs); + err = alloc_lpt_leb(c, &lnum); + if (err) + goto no_space; + offs = 0; + ubifs_assert(c, lnum >= c->lpt_first && + lnum <= c->lpt_last); + } + c->ltab_lnum = lnum; + c->ltab_offs = offs; + offs += c->ltab_sz; + dbg_chk_lpt_sz(c, 1, c->ltab_sz); + } + + alen = ALIGN(offs, c->min_io_size); + upd_ltab(c, lnum, c->leb_size - alen, alen - offs); + dbg_chk_lpt_sz(c, 4, alen - offs); + err = dbg_chk_lpt_sz(c, 3, alen); + if (err) + return err; + return 0; + +no_space: + ubifs_err(c, "LPT out of space at LEB %d:%d needing %d, done_ltab %d, done_lsave %d", + lnum, offs, len, done_ltab, done_lsave); + ubifs_dump_lpt_info(c); + ubifs_dump_lpt_lebs(c); + dump_stack(); + return err; +} + +/** + * realloc_lpt_leb - allocate an LPT LEB that is empty. + * @c: UBIFS file-system description object + * @lnum: LEB number is passed and returned here + * + * This function duplicates exactly the results of the function alloc_lpt_leb. + * It is used during end commit to reallocate the same LEB numbers that were + * allocated by alloc_lpt_leb during start commit. + * + * This function finds the next LEB that was allocated by the alloc_lpt_leb + * function starting from @lnum. If a LEB is found it is returned in @lnum and + * the function returns %0. Otherwise the function returns -ENOSPC. + * Note however, that LPT is designed never to run out of space. + */ +static int realloc_lpt_leb(struct ubifs_info *c, int *lnum) +{ + int i, n; + + n = *lnum - c->lpt_first + 1; + for (i = n; i < c->lpt_lebs; i++) + if (c->ltab[i].cmt) { + c->ltab[i].cmt = 0; + *lnum = i + c->lpt_first; + return 0; + } + + for (i = 0; i < n; i++) + if (c->ltab[i].cmt) { + c->ltab[i].cmt = 0; + *lnum = i + c->lpt_first; + return 0; + } + return -ENOSPC; +} + +/** + * write_cnodes - write cnodes for commit. + * @c: UBIFS file-system description object + * + * This function returns %0 on success and a negative error code on failure. + */ +static int write_cnodes(struct ubifs_info *c) +{ + int lnum, offs, len, from, err, wlen, alen, done_ltab, done_lsave; + struct ubifs_cnode *cnode; + void *buf = c->lpt_buf; + + cnode = c->lpt_cnext; + if (!cnode) + return 0; + lnum = c->nhead_lnum; + offs = c->nhead_offs; + from = offs; + /* Ensure empty LEB is unmapped */ + if (offs == 0) { + err = ubifs_leb_unmap(c, lnum); + if (err) + return err; + } + /* Try to place lsave and ltab nicely */ + done_lsave = !c->big_lpt; + done_ltab = 0; + if (!done_lsave && offs + c->lsave_sz <= c->leb_size) { + done_lsave = 1; + ubifs_pack_lsave(c, buf + offs, c->lsave); + offs += c->lsave_sz; + dbg_chk_lpt_sz(c, 1, c->lsave_sz); + } + + if (offs + c->ltab_sz <= c->leb_size) { + done_ltab = 1; + ubifs_pack_ltab(c, buf + offs, c->ltab_cmt); + offs += c->ltab_sz; + dbg_chk_lpt_sz(c, 1, c->ltab_sz); + } + + /* Loop for each cnode */ + do { + if (cnode->level) + len = c->nnode_sz; + else + len = c->pnode_sz; + while (offs + len > c->leb_size) { + wlen = offs - from; + if (wlen) { + alen = ALIGN(wlen, c->min_io_size); + memset(buf + offs, 0xff, alen - wlen); + err = ubifs_leb_write(c, lnum, buf + from, from, + alen); + if (err) + return err; + } + dbg_chk_lpt_sz(c, 2, c->leb_size - offs); + err = realloc_lpt_leb(c, &lnum); + if (err) + goto no_space; + offs = from = 0; + ubifs_assert(c, lnum >= c->lpt_first && + lnum <= c->lpt_last); + err = ubifs_leb_unmap(c, lnum); + if (err) + return err; + /* Try to place lsave and ltab nicely */ + if (!done_lsave) { + done_lsave = 1; + ubifs_pack_lsave(c, buf + offs, c->lsave); + offs += c->lsave_sz; + dbg_chk_lpt_sz(c, 1, c->lsave_sz); + continue; + } + if (!done_ltab) { + done_ltab = 1; + ubifs_pack_ltab(c, buf + offs, c->ltab_cmt); + offs += c->ltab_sz; + dbg_chk_lpt_sz(c, 1, c->ltab_sz); + continue; + } + break; + } + if (cnode->level) + ubifs_pack_nnode(c, buf + offs, + (struct ubifs_nnode *)cnode); + else + ubifs_pack_pnode(c, buf + offs, + (struct ubifs_pnode *)cnode); + /* + * The reason for the barriers is the same as in case of TNC. + * See comment in 'write_index()'. 'dirty_cow_nnode()' and + * 'dirty_cow_pnode()' are the functions for which this is + * important. + */ + clear_bit(DIRTY_CNODE, &cnode->flags); + smp_mb__before_atomic(); + clear_bit(COW_CNODE, &cnode->flags); + smp_mb__after_atomic(); + offs += len; + dbg_chk_lpt_sz(c, 1, len); + cnode = cnode->cnext; + } while (cnode && cnode != c->lpt_cnext); + + /* Make sure to place LPT's save table */ + if (!done_lsave) { + if (offs + c->lsave_sz > c->leb_size) { + wlen = offs - from; + alen = ALIGN(wlen, c->min_io_size); + memset(buf + offs, 0xff, alen - wlen); + err = ubifs_leb_write(c, lnum, buf + from, from, alen); + if (err) + return err; + dbg_chk_lpt_sz(c, 2, c->leb_size - offs); + err = realloc_lpt_leb(c, &lnum); + if (err) + goto no_space; + offs = from = 0; + ubifs_assert(c, lnum >= c->lpt_first && + lnum <= c->lpt_last); + err = ubifs_leb_unmap(c, lnum); + if (err) + return err; + } + done_lsave = 1; + ubifs_pack_lsave(c, buf + offs, c->lsave); + offs += c->lsave_sz; + dbg_chk_lpt_sz(c, 1, c->lsave_sz); + } + + /* Make sure to place LPT's own lprops table */ + if (!done_ltab) { + if (offs + c->ltab_sz > c->leb_size) { + wlen = offs - from; + alen = ALIGN(wlen, c->min_io_size); + memset(buf + offs, 0xff, alen - wlen); + err = ubifs_leb_write(c, lnum, buf + from, from, alen); + if (err) + return err; + dbg_chk_lpt_sz(c, 2, c->leb_size - offs); + err = realloc_lpt_leb(c, &lnum); + if (err) + goto no_space; + offs = from = 0; + ubifs_assert(c, lnum >= c->lpt_first && + lnum <= c->lpt_last); + err = ubifs_leb_unmap(c, lnum); + if (err) + return err; + } + ubifs_pack_ltab(c, buf + offs, c->ltab_cmt); + offs += c->ltab_sz; + dbg_chk_lpt_sz(c, 1, c->ltab_sz); + } + + /* Write remaining data in buffer */ + wlen = offs - from; + alen = ALIGN(wlen, c->min_io_size); + memset(buf + offs, 0xff, alen - wlen); + err = ubifs_leb_write(c, lnum, buf + from, from, alen); + if (err) + return err; + + dbg_chk_lpt_sz(c, 4, alen - wlen); + err = dbg_chk_lpt_sz(c, 3, ALIGN(offs, c->min_io_size)); + if (err) + return err; + + c->nhead_lnum = lnum; + c->nhead_offs = ALIGN(offs, c->min_io_size); + + dbg_lp("LPT root is at %d:%d", c->lpt_lnum, c->lpt_offs); + dbg_lp("LPT head is at %d:%d", c->nhead_lnum, c->nhead_offs); + dbg_lp("LPT ltab is at %d:%d", c->ltab_lnum, c->ltab_offs); + if (c->big_lpt) + dbg_lp("LPT lsave is at %d:%d", c->lsave_lnum, c->lsave_offs); + + return 0; + +no_space: + ubifs_err(c, "LPT out of space mismatch at LEB %d:%d needing %d, done_ltab %d, done_lsave %d", + lnum, offs, len, done_ltab, done_lsave); + ubifs_dump_lpt_info(c); + ubifs_dump_lpt_lebs(c); + dump_stack(); + return err; +} + +/** + * ubifs_find_next_pnode - find next pnode. + * @c: UBIFS file-system description object + * @pnode: pnode + * + * This function returns the next pnode or %NULL if there are no more pnodes. + * Note that pnodes that have never been written (lnum == 0) are skipped. + */ +struct ubifs_pnode *ubifs_find_next_pnode(struct ubifs_info *c, + struct ubifs_pnode *pnode) +{ + struct ubifs_nnode *nnode; + int iip; + + /* Try to go right */ + nnode = pnode->parent; + for (iip = pnode->iip + 1; iip < UBIFS_LPT_FANOUT; iip++) { + if (nnode->nbranch[iip].lnum) + return ubifs_get_pnode(c, nnode, iip); + } + + /* Go up while can't go right */ + do { + iip = nnode->iip + 1; + nnode = nnode->parent; + if (!nnode) + return NULL; + for (; iip < UBIFS_LPT_FANOUT; iip++) { + if (nnode->nbranch[iip].lnum) + break; + } + } while (iip >= UBIFS_LPT_FANOUT); + + /* Go right */ + nnode = ubifs_get_nnode(c, nnode, iip); + if (IS_ERR(nnode)) + return ERR_CAST(nnode); + + /* Go down to level 1 */ + while (nnode->level > 1) { + for (iip = 0; iip < UBIFS_LPT_FANOUT; iip++) { + if (nnode->nbranch[iip].lnum) + break; + } + if (iip >= UBIFS_LPT_FANOUT) { + /* + * Should not happen, but we need to keep going + * if it does. + */ + iip = 0; + } + nnode = ubifs_get_nnode(c, nnode, iip); + if (IS_ERR(nnode)) + return ERR_CAST(nnode); + } + + for (iip = 0; iip < UBIFS_LPT_FANOUT; iip++) + if (nnode->nbranch[iip].lnum) + break; + if (iip >= UBIFS_LPT_FANOUT) + /* Should not happen, but we need to keep going if it does */ + iip = 0; + return ubifs_get_pnode(c, nnode, iip); +} + +/** + * add_pnode_dirt - add dirty space to LPT LEB properties. + * @c: UBIFS file-system description object + * @pnode: pnode for which to add dirt + */ +static void add_pnode_dirt(struct ubifs_info *c, struct ubifs_pnode *pnode) +{ + ubifs_add_lpt_dirt(c, pnode->parent->nbranch[pnode->iip].lnum, + c->pnode_sz); +} + +/** + * ubifs_make_nnode_dirty - mark a nnode dirty. + * @c: UBIFS file-system description object + * @nnode: nnode to mark dirty + */ +void ubifs_make_nnode_dirty(struct ubifs_info *c, struct ubifs_nnode *nnode) +{ + while (nnode) { + if (!test_and_set_bit(DIRTY_CNODE, &nnode->flags)) { + c->dirty_nn_cnt += 1; + ubifs_add_nnode_dirt(c, nnode); + nnode = nnode->parent; + } else + break; + } +} + +/** + * ubifs_make_pnode_dirty - mark a pnode dirty. + * @c: UBIFS file-system description object + * @pnode: pnode to mark dirty + */ +void ubifs_make_pnode_dirty(struct ubifs_info *c, struct ubifs_pnode *pnode) +{ + /* Assumes cnext list is empty i.e. not called during commit */ + if (!test_and_set_bit(DIRTY_CNODE, &pnode->flags)) { + c->dirty_pn_cnt += 1; + add_pnode_dirt(c, pnode); + /* Mark parent and ancestors dirty too */ + ubifs_make_nnode_dirty(c, pnode->parent); + } +} + +/** + * make_tree_dirty - mark the entire LEB properties tree dirty. + * @c: UBIFS file-system description object + * + * This function is used by the "small" LPT model to cause the entire LEB + * properties tree to be written. The "small" LPT model does not use LPT + * garbage collection because it is more efficient to write the entire tree + * (because it is small). + * + * This function returns %0 on success and a negative error code on failure. + */ +static int make_tree_dirty(struct ubifs_info *c) +{ + struct ubifs_pnode *pnode; + + pnode = ubifs_pnode_lookup(c, 0); + if (IS_ERR(pnode)) + return PTR_ERR(pnode); + + while (pnode) { + ubifs_make_pnode_dirty(c, pnode); + pnode = ubifs_find_next_pnode(c, pnode); + if (IS_ERR(pnode)) + return PTR_ERR(pnode); + } + return 0; +} + +/** + * need_write_all - determine if the LPT area is running out of free space. + * @c: UBIFS file-system description object + * + * This function returns %1 if the LPT area is running out of free space and %0 + * if it is not. + */ +static int need_write_all(struct ubifs_info *c) +{ + long long free = 0; + int i; + + for (i = 0; i < c->lpt_lebs; i++) { + if (i + c->lpt_first == c->nhead_lnum) + free += c->leb_size - c->nhead_offs; + else if (c->ltab[i].free == c->leb_size) + free += c->leb_size; + else if (c->ltab[i].free + c->ltab[i].dirty == c->leb_size) + free += c->leb_size; + } + /* Less than twice the size left */ + if (free <= c->lpt_sz * 2) + return 1; + return 0; +} + +/** + * lpt_tgc_start - start trivial garbage collection of LPT LEBs. + * @c: UBIFS file-system description object + * + * LPT trivial garbage collection is where a LPT LEB contains only dirty and + * free space and so may be reused as soon as the next commit is completed. + * This function is called during start commit to mark LPT LEBs for trivial GC. + */ +static void lpt_tgc_start(struct ubifs_info *c) +{ + int i; + + for (i = 0; i < c->lpt_lebs; i++) { + if (i + c->lpt_first == c->nhead_lnum) + continue; + if (c->ltab[i].dirty > 0 && + c->ltab[i].free + c->ltab[i].dirty == c->leb_size) { + c->ltab[i].tgc = 1; + c->ltab[i].free = c->leb_size; + c->ltab[i].dirty = 0; + dbg_lp("LEB %d", i + c->lpt_first); + } + } +} + +/** + * lpt_tgc_end - end trivial garbage collection of LPT LEBs. + * @c: UBIFS file-system description object + * + * LPT trivial garbage collection is where a LPT LEB contains only dirty and + * free space and so may be reused as soon as the next commit is completed. + * This function is called after the commit is completed (master node has been + * written) and un-maps LPT LEBs that were marked for trivial GC. + */ +static int lpt_tgc_end(struct ubifs_info *c) +{ + int i, err; + + for (i = 0; i < c->lpt_lebs; i++) + if (c->ltab[i].tgc) { + err = ubifs_leb_unmap(c, i + c->lpt_first); + if (err) + return err; + c->ltab[i].tgc = 0; + dbg_lp("LEB %d", i + c->lpt_first); + } + return 0; +} + +/** + * populate_lsave - fill the lsave array with important LEB numbers. + * @c: the UBIFS file-system description object + * + * This function is only called for the "big" model. It records a small number + * of LEB numbers of important LEBs. Important LEBs are ones that are (from + * most important to least important): empty, freeable, freeable index, dirty + * index, dirty or free. Upon mount, we read this list of LEB numbers and bring + * their pnodes into memory. That will stop us from having to scan the LPT + * straight away. For the "small" model we assume that scanning the LPT is no + * big deal. + */ +static void populate_lsave(struct ubifs_info *c) +{ + struct ubifs_lprops *lprops; + struct ubifs_lpt_heap *heap; + int i, cnt = 0; + + ubifs_assert(c, c->big_lpt); + if (!(c->lpt_drty_flgs & LSAVE_DIRTY)) { + c->lpt_drty_flgs |= LSAVE_DIRTY; + ubifs_add_lpt_dirt(c, c->lsave_lnum, c->lsave_sz); + } + + if (dbg_populate_lsave(c)) + return; + + list_for_each_entry(lprops, &c->empty_list, list) { + c->lsave[cnt++] = lprops->lnum; + if (cnt >= c->lsave_cnt) + return; + } + list_for_each_entry(lprops, &c->freeable_list, list) { + c->lsave[cnt++] = lprops->lnum; + if (cnt >= c->lsave_cnt) + return; + } + list_for_each_entry(lprops, &c->frdi_idx_list, list) { + c->lsave[cnt++] = lprops->lnum; + if (cnt >= c->lsave_cnt) + return; + } + heap = &c->lpt_heap[LPROPS_DIRTY_IDX - 1]; + for (i = 0; i < heap->cnt; i++) { + c->lsave[cnt++] = heap->arr[i]->lnum; + if (cnt >= c->lsave_cnt) + return; + } + heap = &c->lpt_heap[LPROPS_DIRTY - 1]; + for (i = 0; i < heap->cnt; i++) { + c->lsave[cnt++] = heap->arr[i]->lnum; + if (cnt >= c->lsave_cnt) + return; + } + heap = &c->lpt_heap[LPROPS_FREE - 1]; + for (i = 0; i < heap->cnt; i++) { + c->lsave[cnt++] = heap->arr[i]->lnum; + if (cnt >= c->lsave_cnt) + return; + } + /* Fill it up completely */ + while (cnt < c->lsave_cnt) + c->lsave[cnt++] = c->main_first; +} + +/** + * nnode_lookup - lookup a nnode in the LPT. + * @c: UBIFS file-system description object + * @i: nnode number + * + * This function returns a pointer to the nnode on success or a negative + * error code on failure. + */ +static struct ubifs_nnode *nnode_lookup(struct ubifs_info *c, int i) +{ + int err, iip; + struct ubifs_nnode *nnode; + + if (!c->nroot) { + err = ubifs_read_nnode(c, NULL, 0); + if (err) + return ERR_PTR(err); + } + nnode = c->nroot; + while (1) { + iip = i & (UBIFS_LPT_FANOUT - 1); + i >>= UBIFS_LPT_FANOUT_SHIFT; + if (!i) + break; + nnode = ubifs_get_nnode(c, nnode, iip); + if (IS_ERR(nnode)) + return nnode; + } + return nnode; +} + +/** + * make_nnode_dirty - find a nnode and, if found, make it dirty. + * @c: UBIFS file-system description object + * @node_num: nnode number of nnode to make dirty + * @lnum: LEB number where nnode was written + * @offs: offset where nnode was written + * + * This function is used by LPT garbage collection. LPT garbage collection is + * used only for the "big" LPT model (c->big_lpt == 1). Garbage collection + * simply involves marking all the nodes in the LEB being garbage-collected as + * dirty. The dirty nodes are written next commit, after which the LEB is free + * to be reused. + * + * This function returns %0 on success and a negative error code on failure. + */ +static int make_nnode_dirty(struct ubifs_info *c, int node_num, int lnum, + int offs) +{ + struct ubifs_nnode *nnode; + + nnode = nnode_lookup(c, node_num); + if (IS_ERR(nnode)) + return PTR_ERR(nnode); + if (nnode->parent) { + struct ubifs_nbranch *branch; + + branch = &nnode->parent->nbranch[nnode->iip]; + if (branch->lnum != lnum || branch->offs != offs) + return 0; /* nnode is obsolete */ + } else if (c->lpt_lnum != lnum || c->lpt_offs != offs) + return 0; /* nnode is obsolete */ + /* Assumes cnext list is empty i.e. not called during commit */ + ubifs_make_nnode_dirty(c, nnode); + return 0; +} + +/** + * make_pnode_dirty - find a pnode and, if found, make it dirty. + * @c: UBIFS file-system description object + * @node_num: pnode number of pnode to make dirty + * @lnum: LEB number where pnode was written + * @offs: offset where pnode was written + * + * This function is used by LPT garbage collection. LPT garbage collection is + * used only for the "big" LPT model (c->big_lpt == 1). Garbage collection + * simply involves marking all the nodes in the LEB being garbage-collected as + * dirty. The dirty nodes are written next commit, after which the LEB is free + * to be reused. + * + * This function returns %0 on success and a negative error code on failure. + */ +static int make_pnode_dirty(struct ubifs_info *c, int node_num, int lnum, + int offs) +{ + struct ubifs_pnode *pnode; + struct ubifs_nbranch *branch; + + pnode = ubifs_pnode_lookup(c, node_num); + if (IS_ERR(pnode)) + return PTR_ERR(pnode); + branch = &pnode->parent->nbranch[pnode->iip]; + if (branch->lnum != lnum || branch->offs != offs) + return 0; + ubifs_make_pnode_dirty(c, pnode); + return 0; +} + +/** + * make_ltab_dirty - make ltab node dirty. + * @c: UBIFS file-system description object + * @lnum: LEB number where ltab was written + * @offs: offset where ltab was written + * + * This function is used by LPT garbage collection. LPT garbage collection is + * used only for the "big" LPT model (c->big_lpt == 1). Garbage collection + * simply involves marking all the nodes in the LEB being garbage-collected as + * dirty. The dirty nodes are written next commit, after which the LEB is free + * to be reused. + * + * This function returns %0 on success and a negative error code on failure. + */ +static int make_ltab_dirty(struct ubifs_info *c, int lnum, int offs) +{ + if (lnum != c->ltab_lnum || offs != c->ltab_offs) + return 0; /* This ltab node is obsolete */ + if (!(c->lpt_drty_flgs & LTAB_DIRTY)) { + c->lpt_drty_flgs |= LTAB_DIRTY; + ubifs_add_lpt_dirt(c, c->ltab_lnum, c->ltab_sz); + } + return 0; +} + +/** + * make_lsave_dirty - make lsave node dirty. + * @c: UBIFS file-system description object + * @lnum: LEB number where lsave was written + * @offs: offset where lsave was written + * + * This function is used by LPT garbage collection. LPT garbage collection is + * used only for the "big" LPT model (c->big_lpt == 1). Garbage collection + * simply involves marking all the nodes in the LEB being garbage-collected as + * dirty. The dirty nodes are written next commit, after which the LEB is free + * to be reused. + * + * This function returns %0 on success and a negative error code on failure. + */ +static int make_lsave_dirty(struct ubifs_info *c, int lnum, int offs) +{ + if (lnum != c->lsave_lnum || offs != c->lsave_offs) + return 0; /* This lsave node is obsolete */ + if (!(c->lpt_drty_flgs & LSAVE_DIRTY)) { + c->lpt_drty_flgs |= LSAVE_DIRTY; + ubifs_add_lpt_dirt(c, c->lsave_lnum, c->lsave_sz); + } + return 0; +} + +/** + * make_node_dirty - make node dirty. + * @c: UBIFS file-system description object + * @node_type: LPT node type + * @node_num: node number + * @lnum: LEB number where node was written + * @offs: offset where node was written + * + * This function is used by LPT garbage collection. LPT garbage collection is + * used only for the "big" LPT model (c->big_lpt == 1). Garbage collection + * simply involves marking all the nodes in the LEB being garbage-collected as + * dirty. The dirty nodes are written next commit, after which the LEB is free + * to be reused. + * + * This function returns %0 on success and a negative error code on failure. + */ +static int make_node_dirty(struct ubifs_info *c, int node_type, int node_num, + int lnum, int offs) +{ + switch (node_type) { + case UBIFS_LPT_NNODE: + return make_nnode_dirty(c, node_num, lnum, offs); + case UBIFS_LPT_PNODE: + return make_pnode_dirty(c, node_num, lnum, offs); + case UBIFS_LPT_LTAB: + return make_ltab_dirty(c, lnum, offs); + case UBIFS_LPT_LSAVE: + return make_lsave_dirty(c, lnum, offs); + } + return -EINVAL; +} + +/** + * get_lpt_node_len - return the length of a node based on its type. + * @c: UBIFS file-system description object + * @node_type: LPT node type + */ +static int get_lpt_node_len(const struct ubifs_info *c, int node_type) +{ + switch (node_type) { + case UBIFS_LPT_NNODE: + return c->nnode_sz; + case UBIFS_LPT_PNODE: + return c->pnode_sz; + case UBIFS_LPT_LTAB: + return c->ltab_sz; + case UBIFS_LPT_LSAVE: + return c->lsave_sz; + } + return 0; +} + +/** + * get_pad_len - return the length of padding in a buffer. + * @c: UBIFS file-system description object + * @buf: buffer + * @len: length of buffer + */ +static int get_pad_len(const struct ubifs_info *c, __unused uint8_t *buf, + int len) +{ + int offs, pad_len; + + if (c->min_io_size == 1) + return 0; + offs = c->leb_size - len; + pad_len = ALIGN(offs, c->min_io_size) - offs; + return pad_len; +} + +/** + * get_lpt_node_type - return type (and node number) of a node in a buffer. + * @c: UBIFS file-system description object + * @buf: buffer + * @node_num: node number is returned here + */ +static int get_lpt_node_type(const struct ubifs_info *c, uint8_t *buf, + int *node_num) +{ + uint8_t *addr = buf + UBIFS_LPT_CRC_BYTES; + int pos = 0, node_type; + + node_type = ubifs_unpack_bits(c, &addr, &pos, UBIFS_LPT_TYPE_BITS); + *node_num = ubifs_unpack_bits(c, &addr, &pos, c->pcnt_bits); + return node_type; +} + +/** + * is_a_node - determine if a buffer contains a node. + * @c: UBIFS file-system description object + * @buf: buffer + * @len: length of buffer + * + * This function returns %1 if the buffer contains a node or %0 if it does not. + */ +static int is_a_node(const struct ubifs_info *c, uint8_t *buf, int len) +{ + uint8_t *addr = buf + UBIFS_LPT_CRC_BYTES; + int pos = 0, node_type, node_len; + uint16_t crc, calc_crc; + + if (len < UBIFS_LPT_CRC_BYTES + (UBIFS_LPT_TYPE_BITS + 7) / 8) + return 0; + node_type = ubifs_unpack_bits(c, &addr, &pos, UBIFS_LPT_TYPE_BITS); + if (node_type == UBIFS_LPT_NOT_A_NODE) + return 0; + node_len = get_lpt_node_len(c, node_type); + if (!node_len || node_len > len) + return 0; + pos = 0; + addr = buf; + crc = ubifs_unpack_bits(c, &addr, &pos, UBIFS_LPT_CRC_BITS); + calc_crc = crc16(-1, buf + UBIFS_LPT_CRC_BYTES, + node_len - UBIFS_LPT_CRC_BYTES); + if (crc != calc_crc) + return 0; + return 1; +} + +/** + * lpt_gc_lnum - garbage collect a LPT LEB. + * @c: UBIFS file-system description object + * @lnum: LEB number to garbage collect + * + * LPT garbage collection is used only for the "big" LPT model + * (c->big_lpt == 1). Garbage collection simply involves marking all the nodes + * in the LEB being garbage-collected as dirty. The dirty nodes are written + * next commit, after which the LEB is free to be reused. + * + * This function returns %0 on success and a negative error code on failure. + */ +static int lpt_gc_lnum(struct ubifs_info *c, int lnum) +{ + int err, len = c->leb_size, node_type, node_num, node_len, offs; + void *buf = c->lpt_buf; + + dbg_lp("LEB %d", lnum); + + err = ubifs_leb_read(c, lnum, buf, 0, c->leb_size, 1); + if (err) + return err; + + while (1) { + if (!is_a_node(c, buf, len)) { + int pad_len; + + pad_len = get_pad_len(c, buf, len); + if (pad_len) { + buf += pad_len; + len -= pad_len; + continue; + } + return 0; + } + node_type = get_lpt_node_type(c, buf, &node_num); + node_len = get_lpt_node_len(c, node_type); + offs = c->leb_size - len; + ubifs_assert(c, node_len != 0); + mutex_lock(&c->lp_mutex); + err = make_node_dirty(c, node_type, node_num, lnum, offs); + mutex_unlock(&c->lp_mutex); + if (err) + return err; + buf += node_len; + len -= node_len; + } + return 0; +} + +/** + * lpt_gc - LPT garbage collection. + * @c: UBIFS file-system description object + * + * Select a LPT LEB for LPT garbage collection and call 'lpt_gc_lnum()'. + * Returns %0 on success and a negative error code on failure. + */ +static int lpt_gc(struct ubifs_info *c) +{ + int i, lnum = -1, dirty = 0; + + mutex_lock(&c->lp_mutex); + for (i = 0; i < c->lpt_lebs; i++) { + ubifs_assert(c, !c->ltab[i].tgc); + if (i + c->lpt_first == c->nhead_lnum || + c->ltab[i].free + c->ltab[i].dirty == c->leb_size) + continue; + if (c->ltab[i].dirty > dirty) { + dirty = c->ltab[i].dirty; + lnum = i + c->lpt_first; + } + } + mutex_unlock(&c->lp_mutex); + if (lnum == -1) + return -ENOSPC; + return lpt_gc_lnum(c, lnum); +} + +/** + * ubifs_lpt_start_commit - UBIFS commit starts. + * @c: the UBIFS file-system description object + * + * This function has to be called when UBIFS starts the commit operation. + * This function "freezes" all currently dirty LEB properties and does not + * change them anymore. Further changes are saved and tracked separately + * because they are not part of this commit. This function returns zero in case + * of success and a negative error code in case of failure. + */ +int ubifs_lpt_start_commit(struct ubifs_info *c) +{ + int err, cnt; + + dbg_lp(""); + + mutex_lock(&c->lp_mutex); + err = dbg_chk_lpt_free_spc(c); + if (err) + goto out; + err = dbg_check_ltab(c); + if (err) + goto out; + + if (c->check_lpt_free) { + /* + * We ensure there is enough free space in + * ubifs_lpt_post_commit() by marking nodes dirty. That + * information is lost when we unmount, so we also need + * to check free space once after mounting also. + */ + c->check_lpt_free = 0; + while (need_write_all(c)) { + mutex_unlock(&c->lp_mutex); + err = lpt_gc(c); + if (err) + return err; + mutex_lock(&c->lp_mutex); + } + } + + lpt_tgc_start(c); + + if (!c->dirty_pn_cnt) { + dbg_cmt("no cnodes to commit"); + err = 0; + goto out; + } + + if (!c->big_lpt && need_write_all(c)) { + /* If needed, write everything */ + err = make_tree_dirty(c); + if (err) + goto out; + lpt_tgc_start(c); + } + + if (c->big_lpt) + populate_lsave(c); + + cnt = get_cnodes_to_commit(c); + ubifs_assert(c, cnt != 0); + + err = layout_cnodes(c); + if (err) + goto out; + + err = ubifs_lpt_calc_hash(c, c->mst_node->hash_lpt); + if (err) + goto out; + + /* Copy the LPT's own lprops for end commit to write */ + memcpy(c->ltab_cmt, c->ltab, + sizeof(struct ubifs_lpt_lprops) * c->lpt_lebs); + c->lpt_drty_flgs &= ~(LTAB_DIRTY | LSAVE_DIRTY); + +out: + mutex_unlock(&c->lp_mutex); + return err; +} + +/** + * free_obsolete_cnodes - free obsolete cnodes for commit end. + * @c: UBIFS file-system description object + */ +static void free_obsolete_cnodes(struct ubifs_info *c) +{ + struct ubifs_cnode *cnode, *cnext; + + cnext = c->lpt_cnext; + if (!cnext) + return; + do { + cnode = cnext; + cnext = cnode->cnext; + if (test_bit(OBSOLETE_CNODE, &cnode->flags)) + kfree(cnode); + else + cnode->cnext = NULL; + } while (cnext != c->lpt_cnext); + c->lpt_cnext = NULL; +} + +/** + * ubifs_lpt_end_commit - finish the commit operation. + * @c: the UBIFS file-system description object + * + * This function has to be called when the commit operation finishes. It + * flushes the changes which were "frozen" by 'ubifs_lprops_start_commit()' to + * the media. Returns zero in case of success and a negative error code in case + * of failure. + */ +int ubifs_lpt_end_commit(struct ubifs_info *c) +{ + int err; + + dbg_lp(""); + + if (!c->lpt_cnext) + return 0; + + err = write_cnodes(c); + if (err) + return err; + + mutex_lock(&c->lp_mutex); + free_obsolete_cnodes(c); + mutex_unlock(&c->lp_mutex); + + return 0; +} + +/** + * ubifs_lpt_post_commit - post commit LPT trivial GC and LPT GC. + * @c: UBIFS file-system description object + * + * LPT trivial GC is completed after a commit. Also LPT GC is done after a + * commit for the "big" LPT model. + */ +int ubifs_lpt_post_commit(struct ubifs_info *c) +{ + int err; + + mutex_lock(&c->lp_mutex); + err = lpt_tgc_end(c); + if (err) + goto out; + if (c->big_lpt) + while (need_write_all(c)) { + mutex_unlock(&c->lp_mutex); + err = lpt_gc(c); + if (err) + return err; + mutex_lock(&c->lp_mutex); + } +out: + mutex_unlock(&c->lp_mutex); + return err; +} + +/** + * first_nnode - find the first nnode in memory. + * @c: UBIFS file-system description object + * @hght: height of tree where nnode found is returned here + * + * This function returns a pointer to the nnode found or %NULL if no nnode is + * found. This function is a helper to 'ubifs_lpt_free()'. + */ +static struct ubifs_nnode *first_nnode(struct ubifs_info *c, int *hght) +{ + struct ubifs_nnode *nnode; + int h, i, found; + + nnode = c->nroot; + *hght = 0; + if (!nnode) + return NULL; + for (h = 1; h < c->lpt_hght; h++) { + found = 0; + for (i = 0; i < UBIFS_LPT_FANOUT; i++) { + if (nnode->nbranch[i].nnode) { + found = 1; + nnode = nnode->nbranch[i].nnode; + *hght = h; + break; + } + } + if (!found) + break; + } + return nnode; +} + +/** + * next_nnode - find the next nnode in memory. + * @c: UBIFS file-system description object + * @nnode: nnode from which to start. + * @hght: height of tree where nnode is, is passed and returned here + * + * This function returns a pointer to the nnode found or %NULL if no nnode is + * found. This function is a helper to 'ubifs_lpt_free()'. + */ +static struct ubifs_nnode *next_nnode(struct ubifs_info *c, + struct ubifs_nnode *nnode, int *hght) +{ + struct ubifs_nnode *parent; + int iip, h, i, found; + + parent = nnode->parent; + if (!parent) + return NULL; + if (nnode->iip == UBIFS_LPT_FANOUT - 1) { + *hght -= 1; + return parent; + } + for (iip = nnode->iip + 1; iip < UBIFS_LPT_FANOUT; iip++) { + nnode = parent->nbranch[iip].nnode; + if (nnode) + break; + } + if (!nnode) { + *hght -= 1; + return parent; + } + for (h = *hght + 1; h < c->lpt_hght; h++) { + found = 0; + for (i = 0; i < UBIFS_LPT_FANOUT; i++) { + if (nnode->nbranch[i].nnode) { + found = 1; + nnode = nnode->nbranch[i].nnode; + *hght = h; + break; + } + } + if (!found) + break; + } + return nnode; +} + +/** + * ubifs_free_lpt_nodes - free pnodes/nnodes in LPT. + * @c: UBIFS file-system description object + */ +void ubifs_free_lpt_nodes(struct ubifs_info *c) +{ + int i, hght; + struct ubifs_nnode *nnode; + + nnode = first_nnode(c, &hght); + while (nnode) { + for (i = 0; i < UBIFS_LPT_FANOUT; i++) + kfree(nnode->nbranch[i].nnode); + nnode = next_nnode(c, nnode, &hght); + } + + kfree(c->nroot); + c->nroot = NULL; +} + +/** + * ubifs_lpt_free - free resources owned by the LPT. + * @c: UBIFS file-system description object + * @wr_only: free only resources used for writing + */ +void ubifs_lpt_free(struct ubifs_info *c, int wr_only) +{ + int i; + + /* Free write-only things first */ + + free_obsolete_cnodes(c); /* Leftover from a failed commit */ + + vfree(c->ltab_cmt); + c->ltab_cmt = NULL; + vfree(c->lpt_buf); + c->lpt_buf = NULL; + kfree(c->lsave); + c->lsave = NULL; + + if (wr_only) + return; + + /* Now free the rest */ + + ubifs_free_lpt_nodes(c); + for (i = 0; i < LPROPS_HEAP_CNT; i++) + kfree(c->lpt_heap[i].arr); + kfree(c->dirty_idx.arr); + vfree(c->ltab); + c->ltab = NULL; + kfree(c->lpt_nod_buf); +} + +/* + * Everything below is related to debugging. + */ + +/** + * dbg_is_all_ff - determine if a buffer contains only 0xFF bytes. + * @buf: buffer + * @len: buffer length + */ +static int dbg_is_all_ff(uint8_t *buf, int len) +{ + int i; + + for (i = 0; i < len; i++) + if (buf[i] != 0xff) + return 0; + return 1; +} + +/** + * dbg_is_nnode_dirty - determine if a nnode is dirty. + * @c: the UBIFS file-system description object + * @lnum: LEB number where nnode was written + * @offs: offset where nnode was written + */ +static int dbg_is_nnode_dirty(struct ubifs_info *c, int lnum, int offs) +{ + struct ubifs_nnode *nnode; + int hght; + + /* Entire tree is in memory so first_nnode / next_nnode are OK */ + nnode = first_nnode(c, &hght); + for (; nnode; nnode = next_nnode(c, nnode, &hght)) { + struct ubifs_nbranch *branch; + + cond_resched(); + if (nnode->parent) { + branch = &nnode->parent->nbranch[nnode->iip]; + if (branch->lnum != lnum || branch->offs != offs) + continue; + if (test_bit(DIRTY_CNODE, &nnode->flags)) + return 1; + return 0; + } else { + if (c->lpt_lnum != lnum || c->lpt_offs != offs) + continue; + if (test_bit(DIRTY_CNODE, &nnode->flags)) + return 1; + return 0; + } + } + return 1; +} + +/** + * dbg_is_pnode_dirty - determine if a pnode is dirty. + * @c: the UBIFS file-system description object + * @lnum: LEB number where pnode was written + * @offs: offset where pnode was written + */ +static int dbg_is_pnode_dirty(struct ubifs_info *c, int lnum, int offs) +{ + int i, cnt; + + cnt = DIV_ROUND_UP(c->main_lebs, UBIFS_LPT_FANOUT); + for (i = 0; i < cnt; i++) { + struct ubifs_pnode *pnode; + struct ubifs_nbranch *branch; + + cond_resched(); + pnode = ubifs_pnode_lookup(c, i); + if (IS_ERR(pnode)) + return PTR_ERR(pnode); + branch = &pnode->parent->nbranch[pnode->iip]; + if (branch->lnum != lnum || branch->offs != offs) + continue; + if (test_bit(DIRTY_CNODE, &pnode->flags)) + return 1; + return 0; + } + return 1; +} + +/** + * dbg_is_ltab_dirty - determine if a ltab node is dirty. + * @c: the UBIFS file-system description object + * @lnum: LEB number where ltab node was written + * @offs: offset where ltab node was written + */ +static int dbg_is_ltab_dirty(struct ubifs_info *c, int lnum, int offs) +{ + if (lnum != c->ltab_lnum || offs != c->ltab_offs) + return 1; + return (c->lpt_drty_flgs & LTAB_DIRTY) != 0; +} + +/** + * dbg_is_lsave_dirty - determine if a lsave node is dirty. + * @c: the UBIFS file-system description object + * @lnum: LEB number where lsave node was written + * @offs: offset where lsave node was written + */ +static int dbg_is_lsave_dirty(struct ubifs_info *c, int lnum, int offs) +{ + if (lnum != c->lsave_lnum || offs != c->lsave_offs) + return 1; + return (c->lpt_drty_flgs & LSAVE_DIRTY) != 0; +} + +/** + * dbg_is_node_dirty - determine if a node is dirty. + * @c: the UBIFS file-system description object + * @node_type: node type + * @lnum: LEB number where node was written + * @offs: offset where node was written + */ +static int dbg_is_node_dirty(struct ubifs_info *c, int node_type, int lnum, + int offs) +{ + switch (node_type) { + case UBIFS_LPT_NNODE: + return dbg_is_nnode_dirty(c, lnum, offs); + case UBIFS_LPT_PNODE: + return dbg_is_pnode_dirty(c, lnum, offs); + case UBIFS_LPT_LTAB: + return dbg_is_ltab_dirty(c, lnum, offs); + case UBIFS_LPT_LSAVE: + return dbg_is_lsave_dirty(c, lnum, offs); + } + return 1; +} + +/** + * dbg_check_ltab_lnum - check the ltab for a LPT LEB number. + * @c: the UBIFS file-system description object + * @lnum: LEB number where node was written + * + * This function returns %0 on success and a negative error code on failure. + */ +int dbg_check_ltab_lnum(struct ubifs_info *c, int lnum) +{ + int err, len = c->leb_size, dirty = 0, node_type, node_num, node_len; + int ret; + void *buf, *p; + + buf = p = __vmalloc(c->leb_size, GFP_NOFS); + if (!buf) { + ubifs_err(c, "cannot allocate memory for ltab checking"); + return -ENOMEM; + } + + dbg_lp("LEB %d", lnum); + + err = ubifs_leb_read(c, lnum, buf, 0, c->leb_size, 1); + if (err) { + if (err == -EBADMSG) + set_failure_reason_callback(c, FR_LPT_CORRUPTED); + goto out; + } + + while (1) { + if (!is_a_node(c, p, len)) { + int i, pad_len; + + pad_len = get_pad_len(c, p, len); + if (pad_len) { + p += pad_len; + len -= pad_len; + dirty += pad_len; + continue; + } + if (!dbg_is_all_ff(p, len)) { + set_failure_reason_callback(c, FR_LPT_CORRUPTED); + ubifs_err(c, "invalid empty space in LEB %d at %d", + lnum, c->leb_size - len); + err = -EINVAL; + goto out; + } + i = lnum - c->lpt_first; + if (len != c->ltab[i].free) { + ubifs_err(c, "invalid free space in LEB %d (free %d, expected %d)", + lnum, c->ltab[i].free, len); + err = -EINVAL; + if (handle_failure_callback(c, FR_H_LTAB_INCORRECT, NULL)) { + c->ltab[i].free = len; + err = 0; + } + } + if (dirty != c->ltab[i].dirty) { + ubifs_err(c, "invalid dirty space in LEB %d (dirty %d, expected %d)", + lnum, c->ltab[i].dirty, dirty); + err = -EINVAL; + if (handle_failure_callback(c, FR_H_LTAB_INCORRECT, NULL)) { + c->ltab[i].dirty = dirty; + err = 0; + } + } + goto out; + } + node_type = get_lpt_node_type(c, p, &node_num); + node_len = get_lpt_node_len(c, node_type); + ret = dbg_is_node_dirty(c, node_type, lnum, c->leb_size - len); + if (ret == 1) + dirty += node_len; + p += node_len; + len -= node_len; + } + +out: + vfree(buf); + return err; +} + +/** + * dump_lpt_leb - dump an LPT LEB. + * @c: UBIFS file-system description object + * @lnum: LEB number to dump + * + * This function dumps an LEB from LPT area. Nodes in this area are very + * different to nodes in the main area (e.g., they do not have common headers, + * they do not have 8-byte alignments, etc), so we have a separate function to + * dump LPT area LEBs. Note, LPT has to be locked by the caller. + */ +static void dump_lpt_leb(const struct ubifs_info *c, int lnum) +{ + int err, len = c->leb_size, node_type, node_num, node_len, offs; + void *buf, *p; + + pr_err("(pid %d) start dumping LEB %d\n", getpid(), lnum); + buf = p = __vmalloc(c->leb_size, GFP_NOFS); + if (!buf) { + ubifs_err(c, "cannot allocate memory to dump LPT"); + return; + } + + err = ubifs_leb_read(c, lnum, buf, 0, c->leb_size, 1); + if (err) + goto out; + + while (1) { + offs = c->leb_size - len; + if (!is_a_node(c, p, len)) { + int pad_len; + + pad_len = get_pad_len(c, p, len); + if (pad_len) { + pr_err("LEB %d:%d, pad %d bytes\n", + lnum, offs, pad_len); + p += pad_len; + len -= pad_len; + continue; + } + if (len) + pr_err("LEB %d:%d, free %d bytes\n", + lnum, offs, len); + break; + } + + node_type = get_lpt_node_type(c, p, &node_num); + switch (node_type) { + case UBIFS_LPT_PNODE: + { + node_len = c->pnode_sz; + if (c->big_lpt) + pr_err("LEB %d:%d, pnode num %d\n", + lnum, offs, node_num); + else + pr_err("LEB %d:%d, pnode\n", lnum, offs); + break; + } + case UBIFS_LPT_NNODE: + { + int i; + struct ubifs_nnode nnode; + + node_len = c->nnode_sz; + if (c->big_lpt) + pr_err("LEB %d:%d, nnode num %d, ", + lnum, offs, node_num); + else + pr_err("LEB %d:%d, nnode, ", + lnum, offs); + err = ubifs_unpack_nnode(c, p, &nnode); + if (err) { + pr_err("failed to unpack_node, error %d\n", + err); + break; + } + for (i = 0; i < UBIFS_LPT_FANOUT; i++) { + pr_cont("%d:%d", nnode.nbranch[i].lnum, + nnode.nbranch[i].offs); + if (i != UBIFS_LPT_FANOUT - 1) + pr_cont(", "); + } + pr_cont("\n"); + break; + } + case UBIFS_LPT_LTAB: + node_len = c->ltab_sz; + pr_err("LEB %d:%d, ltab\n", lnum, offs); + break; + case UBIFS_LPT_LSAVE: + node_len = c->lsave_sz; + pr_err("LEB %d:%d, lsave len\n", lnum, offs); + break; + default: + ubifs_err(c, "LPT node type %d not recognized", node_type); + goto out; + } + + p += node_len; + len -= node_len; + } + + pr_err("(pid %d) finish dumping LEB %d\n", getpid(), lnum); +out: + vfree(buf); + return; +} + +/** + * ubifs_dump_lpt_lebs - dump LPT lebs. + * @c: UBIFS file-system description object + * + * This function dumps all LPT LEBs. The caller has to make sure the LPT is + * locked. + */ +void ubifs_dump_lpt_lebs(const struct ubifs_info *c) +{ + int i; + + pr_err("(pid %d) start dumping all LPT LEBs\n", getpid()); + for (i = 0; i < c->lpt_lebs; i++) + dump_lpt_leb(c, i + c->lpt_first); + pr_err("(pid %d) finish dumping all LPT LEBs\n", getpid()); +} + +/** + * dbg_populate_lsave - debugging version of 'populate_lsave()' + * @c: UBIFS file-system description object + * + * This is a debugging version for 'populate_lsave()' which populates lsave + * with random LEBs instead of useful LEBs, which is good for test coverage. + * Returns zero if lsave has not been populated (this debugging feature is + * disabled) an non-zero if lsave has been populated. + */ +static int dbg_populate_lsave(__unused struct ubifs_info *c) +{ + return 0; +} diff --git a/ubifs-utils/libubifs/master.c b/ubifs-utils/libubifs/master.c new file mode 100644 index 0000000..54d2a78 --- /dev/null +++ b/ubifs-utils/libubifs/master.c @@ -0,0 +1,489 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * This file is part of UBIFS. + * + * Copyright (C) 2006-2008 Nokia Corporation. + * + * Authors: Artem Bityutskiy (Битюцкий Артём) + * Adrian Hunter + */ + +/* This file implements reading and writing the master node */ + +#include "linux_err.h" +#include "kmem.h" +#include "ubifs.h" +#include "defs.h" +#include "debug.h" + +/** + * ubifs_compare_master_node - compare two UBIFS master nodes + * @c: UBIFS file-system description object + * @m1: the first node + * @m2: the second node + * + * This function compares two UBIFS master nodes. Returns 0 if they are equal + * and nonzero if not. + */ +int ubifs_compare_master_node(__unused struct ubifs_info *c, void *m1, void *m2) +{ + int ret; + int behind; + int hmac_offs = offsetof(struct ubifs_mst_node, hmac); + + /* + * Do not compare the common node header since the sequence number and + * hence the CRC are different. + */ + ret = memcmp(m1 + UBIFS_CH_SZ, m2 + UBIFS_CH_SZ, + hmac_offs - UBIFS_CH_SZ); + if (ret) + return ret; + + /* + * Do not compare the embedded HMAC as well which also must be different + * due to the different common node header. + */ + behind = hmac_offs + UBIFS_MAX_HMAC_LEN; + + if (UBIFS_MST_NODE_SZ > behind) + return memcmp(m1 + behind, m2 + behind, UBIFS_MST_NODE_SZ - behind); + + return 0; +} + +/* mst_node_check_hash - Check hash of a master node + * @c: UBIFS file-system description object + * @mst: The master node + * @expected: The expected hash of the master node + * + * This checks the hash of a master node against a given expected hash. + * Note that we have two master nodes on a UBIFS image which have different + * sequence numbers and consequently different CRCs. To be able to match + * both master nodes we exclude the common node header containing the sequence + * number and CRC from the hash. + * + * Returns 0 if the hashes are equal, a negative error code otherwise. + */ +static int mst_node_check_hash(__unused const struct ubifs_info *c, + __unused const struct ubifs_mst_node *mst, + __unused const u8 *expected) +{ + // To be implemented + return 0; +} + +/** + * scan_for_master - search the valid master node. + * @c: UBIFS file-system description object + * + * This function scans the master node LEBs and search for the latest master + * node. Returns zero in case of success, %-EUCLEAN if there master area is + * corrupted and requires recovery, and a negative error code in case of + * failure. + */ +static int scan_for_master(struct ubifs_info *c) +{ + struct ubifs_scan_leb *sleb; + struct ubifs_scan_node *snod; + int lnum, offs = 0, nodes_cnt, err; + + lnum = UBIFS_MST_LNUM; + + sleb = ubifs_scan(c, lnum, 0, c->sbuf, 1); + if (IS_ERR(sleb)) + return PTR_ERR(sleb); + nodes_cnt = sleb->nodes_cnt; + if (nodes_cnt > 0) { + snod = list_entry(sleb->nodes.prev, struct ubifs_scan_node, + list); + if (snod->type != UBIFS_MST_NODE) + goto out_dump; + memcpy(c->mst_node, snod->node, snod->len); + offs = snod->offs; + } + ubifs_scan_destroy(sleb); + + lnum += 1; + + sleb = ubifs_scan(c, lnum, 0, c->sbuf, 1); + if (IS_ERR(sleb)) + return PTR_ERR(sleb); + if (sleb->nodes_cnt != nodes_cnt) + goto out; + if (!sleb->nodes_cnt) + goto out; + snod = list_entry(sleb->nodes.prev, struct ubifs_scan_node, list); + if (snod->type != UBIFS_MST_NODE) + goto out_dump; + if (snod->offs != offs) + goto out; + if (ubifs_compare_master_node(c, c->mst_node, snod->node)) + goto out; + + c->mst_offs = offs; + ubifs_scan_destroy(sleb); + + if (!ubifs_authenticated(c)) + return 0; + + if (ubifs_hmac_zero(c, c->mst_node->hmac)) { + err = mst_node_check_hash(c, c->mst_node, + c->sup_node->hash_mst); + if (err) + ubifs_err(c, "Failed to verify master node hash"); + } else { + err = ubifs_node_verify_hmac(c, c->mst_node, + sizeof(struct ubifs_mst_node), + offsetof(struct ubifs_mst_node, hmac)); + if (err) + ubifs_err(c, "Failed to verify master node HMAC"); + } + + if (err) + return -EPERM; + + return 0; + +out: + set_failure_reason_callback(c, FR_DATA_CORRUPTED); + ubifs_scan_destroy(sleb); + return -EUCLEAN; + +out_dump: + set_failure_reason_callback(c, FR_DATA_CORRUPTED); + ubifs_err(c, "unexpected node type %d master LEB %d:%d", + snod->type, lnum, snod->offs); + ubifs_scan_destroy(sleb); + return -EINVAL; +} + +/** + * validate_master - validate master node. + * @c: UBIFS file-system description object + * + * This function validates data which was read from master node. Returns zero + * if the data is all right and %-EINVAL if not. + */ +static int validate_master(const struct ubifs_info *c) +{ + unsigned int reason = FR_DATA_CORRUPTED; + long long main_sz; + int err; + + if (c->max_sqnum >= SQNUM_WATERMARK) { + err = 1; + goto out; + } + + if (c->cmt_no >= c->max_sqnum) { + err = 2; + goto out; + } + + if (c->highest_inum >= INUM_WATERMARK) { + err = 3; + goto out; + } + + if (c->lhead_lnum < UBIFS_LOG_LNUM || + c->lhead_lnum >= UBIFS_LOG_LNUM + c->log_lebs || + c->lhead_offs < 0 || c->lhead_offs >= c->leb_size || + c->lhead_offs & (c->min_io_size - 1)) { + err = 4; + goto out; + } + + if (c->zroot.lnum >= c->leb_cnt || c->zroot.lnum < c->main_first || + c->zroot.offs >= c->leb_size || c->zroot.offs & 7) { + err = 5; + goto out; + } + + if (c->zroot.len < c->ranges[UBIFS_IDX_NODE].min_len || + c->zroot.len > c->ranges[UBIFS_IDX_NODE].max_len) { + err = 6; + goto out; + } + + if (c->gc_lnum >= c->leb_cnt || c->gc_lnum < c->main_first) { + err = 7; + goto out; + } + + if (c->ihead_lnum >= c->leb_cnt || c->ihead_lnum < c->main_first || + c->ihead_offs % c->min_io_size || c->ihead_offs < 0 || + c->ihead_offs > c->leb_size || c->ihead_offs & 7) { + err = 8; + goto out; + } + + main_sz = (long long)c->main_lebs * c->leb_size; + if (c->bi.old_idx_sz & 7 || c->bi.old_idx_sz >= main_sz) { + err = 9; + goto out; + } + + if (c->lpt_lnum < c->lpt_first || c->lpt_lnum > c->lpt_last || + c->lpt_offs < 0 || c->lpt_offs + c->nnode_sz > c->leb_size) { + err = 10; + goto out; + } + + if (c->nhead_lnum < c->lpt_first || c->nhead_lnum > c->lpt_last || + c->nhead_offs < 0 || c->nhead_offs % c->min_io_size || + c->nhead_offs > c->leb_size) { + err = 11; + goto out; + } + + if (c->ltab_lnum < c->lpt_first || c->ltab_lnum > c->lpt_last || + c->ltab_offs < 0 || + c->ltab_offs + c->ltab_sz > c->leb_size) { + err = 12; + goto out; + } + + if (c->big_lpt && (c->lsave_lnum < c->lpt_first || + c->lsave_lnum > c->lpt_last || c->lsave_offs < 0 || + c->lsave_offs + c->lsave_sz > c->leb_size)) { + err = 13; + goto out; + } + + if (c->lscan_lnum < c->main_first || c->lscan_lnum >= c->leb_cnt) { + err = 14; + goto out; + } + + if (c->lst.empty_lebs < 0 || c->lst.empty_lebs > c->main_lebs - 2) { + reason = FR_LPT_INCORRECT; + err = 15; + goto out; + } + + if (c->lst.idx_lebs < 0 || c->lst.idx_lebs > c->main_lebs - 1) { + reason = FR_LPT_INCORRECT; + err = 16; + goto out; + } + + if (c->lst.total_free < 0 || c->lst.total_free > main_sz || + c->lst.total_free & 7) { + reason = FR_LPT_INCORRECT; + err = 17; + goto out; + } + + if (c->lst.total_dirty < 0 || (c->lst.total_dirty & 7)) { + reason = FR_LPT_INCORRECT; + err = 18; + goto out; + } + + if (c->lst.total_used < 0 || (c->lst.total_used & 7)) { + reason = FR_LPT_INCORRECT; + err = 19; + goto out; + } + + if (c->lst.total_free + c->lst.total_dirty + + c->lst.total_used > main_sz) { + reason = FR_LPT_INCORRECT; + err = 20; + goto out; + } + + if (c->lst.total_dead + c->lst.total_dark + + c->lst.total_used + c->bi.old_idx_sz > main_sz) { + reason = FR_LPT_INCORRECT; + err = 21; + goto out; + } + + if (c->lst.total_dead < 0 || + c->lst.total_dead > c->lst.total_free + c->lst.total_dirty || + c->lst.total_dead & 7) { + reason = FR_LPT_INCORRECT; + err = 22; + goto out; + } + + if (c->lst.total_dark < 0 || + c->lst.total_dark > c->lst.total_free + c->lst.total_dirty || + c->lst.total_dark & 7) { + reason = FR_LPT_INCORRECT; + err = 23; + goto out; + } + + return 0; + +out: + set_failure_reason_callback(c, reason); + ubifs_err(c, "bad master node at offset %d error %d", c->mst_offs, err); + ubifs_dump_node(c, c->mst_node, c->mst_node_alsz); + err = -EINVAL; + if (can_ignore_failure_callback(c, reason)) { + clear_failure_reason_callback(c); + err = 0; + } + return err; +} + +/** + * ubifs_read_master - read master node. + * @c: UBIFS file-system description object + * + * This function finds and reads the master node during file-system mount. If + * the flash is empty, it creates default master node as well. Returns zero in + * case of success and a negative error code in case of failure. + */ +int ubifs_read_master(struct ubifs_info *c) +{ + int err, old_leb_cnt; + + c->mst_node = kzalloc(c->mst_node_alsz, GFP_KERNEL); + if (!c->mst_node) + return -ENOMEM; + + err = scan_for_master(c); + if (err) { + if (err == -EUCLEAN) { + clear_failure_reason_callback(c); + err = ubifs_recover_master_node(c); + } + if (err) + /* + * Note, we do not free 'c->mst_node' here because the + * unmount routine will take care of this. + */ + return err; + } + + /* Make sure that the recovery flag is clear */ + c->mst_node->flags &= cpu_to_le32(~UBIFS_MST_RCVRY); + + c->max_sqnum = le64_to_cpu(c->mst_node->ch.sqnum); + c->highest_inum = le64_to_cpu(c->mst_node->highest_inum); + c->cmt_no = le64_to_cpu(c->mst_node->cmt_no); + c->zroot.lnum = le32_to_cpu(c->mst_node->root_lnum); + c->zroot.offs = le32_to_cpu(c->mst_node->root_offs); + c->zroot.len = le32_to_cpu(c->mst_node->root_len); + c->lhead_lnum = le32_to_cpu(c->mst_node->log_lnum); + c->gc_lnum = le32_to_cpu(c->mst_node->gc_lnum); + c->ihead_lnum = le32_to_cpu(c->mst_node->ihead_lnum); + c->ihead_offs = le32_to_cpu(c->mst_node->ihead_offs); + c->bi.old_idx_sz = le64_to_cpu(c->mst_node->index_size); + c->lpt_lnum = le32_to_cpu(c->mst_node->lpt_lnum); + c->lpt_offs = le32_to_cpu(c->mst_node->lpt_offs); + c->nhead_lnum = le32_to_cpu(c->mst_node->nhead_lnum); + c->nhead_offs = le32_to_cpu(c->mst_node->nhead_offs); + c->ltab_lnum = le32_to_cpu(c->mst_node->ltab_lnum); + c->ltab_offs = le32_to_cpu(c->mst_node->ltab_offs); + c->lsave_lnum = le32_to_cpu(c->mst_node->lsave_lnum); + c->lsave_offs = le32_to_cpu(c->mst_node->lsave_offs); + c->lscan_lnum = le32_to_cpu(c->mst_node->lscan_lnum); + c->lst.empty_lebs = le32_to_cpu(c->mst_node->empty_lebs); + c->lst.idx_lebs = le32_to_cpu(c->mst_node->idx_lebs); + old_leb_cnt = le32_to_cpu(c->mst_node->leb_cnt); + c->lst.total_free = le64_to_cpu(c->mst_node->total_free); + c->lst.total_dirty = le64_to_cpu(c->mst_node->total_dirty); + c->lst.total_used = le64_to_cpu(c->mst_node->total_used); + c->lst.total_dead = le64_to_cpu(c->mst_node->total_dead); + c->lst.total_dark = le64_to_cpu(c->mst_node->total_dark); + + ubifs_copy_hash(c, c->mst_node->hash_root_idx, c->zroot.hash); + + c->calc_idx_sz = c->bi.old_idx_sz; + + if (c->mst_node->flags & cpu_to_le32(UBIFS_MST_NO_ORPHS)) + c->no_orphs = 1; + + if (old_leb_cnt != c->leb_cnt) { + /* The file system has been resized */ + int growth = c->leb_cnt - old_leb_cnt; + + if (c->leb_cnt < old_leb_cnt || + c->leb_cnt < UBIFS_MIN_LEB_CNT) { + set_failure_reason_callback(c, FR_DATA_CORRUPTED); + ubifs_err(c, "bad leb_cnt on master node"); + ubifs_dump_node(c, c->mst_node, c->mst_node_alsz); + return -EINVAL; + } + + dbg_mnt("Auto resizing (master) from %d LEBs to %d LEBs", + old_leb_cnt, c->leb_cnt); + c->lst.empty_lebs += growth; + c->lst.total_free += growth * (long long)c->leb_size; + c->lst.total_dark += growth * (long long)c->dark_wm; + + /* + * Reflect changes back onto the master node. N.B. the master + * node gets written immediately whenever mounting (or + * remounting) in read-write mode, so we do not need to write it + * here. + */ + c->mst_node->leb_cnt = cpu_to_le32(c->leb_cnt); + c->mst_node->empty_lebs = cpu_to_le32(c->lst.empty_lebs); + c->mst_node->total_free = cpu_to_le64(c->lst.total_free); + c->mst_node->total_dark = cpu_to_le64(c->lst.total_dark); + } + + err = validate_master(c); + if (err) + return err; + + err = dbg_old_index_check_init(c, &c->zroot); + + return err; +} + +/** + * ubifs_write_master - write master node. + * @c: UBIFS file-system description object + * + * This function writes the master node. Returns zero in case of success and a + * negative error code in case of failure. The master node is written twice to + * enable recovery. + */ +int ubifs_write_master(struct ubifs_info *c) +{ + int err, lnum, offs, len; + + ubifs_assert(c, !c->ro_media && !c->ro_mount); + if (c->ro_error) + return -EROFS; + + lnum = UBIFS_MST_LNUM; + offs = c->mst_offs + c->mst_node_alsz; + len = UBIFS_MST_NODE_SZ; + + if (offs + UBIFS_MST_NODE_SZ > c->leb_size) { + err = ubifs_leb_unmap(c, lnum); + if (err) + return err; + offs = 0; + } + + c->mst_offs = offs; + c->mst_node->highest_inum = cpu_to_le64(c->highest_inum); + + ubifs_copy_hash(c, c->zroot.hash, c->mst_node->hash_root_idx); + err = ubifs_write_node_hmac(c, c->mst_node, len, lnum, offs, + offsetof(struct ubifs_mst_node, hmac)); + if (err) + return err; + + lnum += 1; + + if (offs == 0) { + err = ubifs_leb_unmap(c, lnum); + if (err) + return err; + } + err = ubifs_write_node_hmac(c, c->mst_node, len, lnum, offs, + offsetof(struct ubifs_mst_node, hmac)); + + return err; +} diff --git a/ubifs-utils/libubifs/misc.h b/ubifs-utils/libubifs/misc.h new file mode 100644 index 0000000..1b4404f --- /dev/null +++ b/ubifs-utils/libubifs/misc.h @@ -0,0 +1,225 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * This file is part of UBIFS. + * + * Copyright (C) 2006-2008 Nokia Corporation + * + * Authors: Artem Bityutskiy (Битюцкий Артём) + * Adrian Hunter + */ + +/* + * This file contains miscellaneous helper functions. + */ + +#ifndef __UBIFS_MISC_H__ +#define __UBIFS_MISC_H__ + +/** + * ubifs_zn_dirty - check if znode is dirty. + * @znode: znode to check + * + * This helper function returns %1 if @znode is dirty and %0 otherwise. + */ +static inline int ubifs_zn_dirty(const struct ubifs_znode *znode) +{ + return !!test_bit(DIRTY_ZNODE, &znode->flags); +} + +/** + * ubifs_zn_obsolete - check if znode is obsolete. + * @znode: znode to check + * + * This helper function returns %1 if @znode is obsolete and %0 otherwise. + */ +static inline int ubifs_zn_obsolete(const struct ubifs_znode *znode) +{ + return !!test_bit(OBSOLETE_ZNODE, &znode->flags); +} + +/** + * ubifs_zn_cow - check if znode has to be copied on write. + * @znode: znode to check + * + * This helper function returns %1 if @znode is has COW flag set and %0 + * otherwise. + */ +static inline int ubifs_zn_cow(const struct ubifs_znode *znode) +{ + return !!test_bit(COW_ZNODE, &znode->flags); +} + +/** + * ubifs_tnc_find_child - find next child in znode. + * @znode: znode to search at + * @start: the zbranch index to start at + * + * This helper function looks for znode child starting at index @start. Returns + * the child or %NULL if no children were found. + */ +static inline struct ubifs_znode * +ubifs_tnc_find_child(struct ubifs_znode *znode, int start) +{ + while (start < znode->child_cnt) { + if (znode->zbranch[start].znode) + return znode->zbranch[start].znode; + start += 1; + } + + return NULL; +} + +/** + * ubifs_inode - get UBIFS inode information by VFS 'struct inode' object. + * @inode: the VFS 'struct inode' pointer + */ +static inline struct ubifs_inode *ubifs_inode(const struct inode *inode) +{ + return container_of(inode, struct ubifs_inode, vfs_inode); +} + +/** + * ubifs_wbuf_sync - synchronize write-buffer. + * @wbuf: write-buffer to synchronize + * + * This is the same as 'ubifs_wbuf_sync_nolock()' but it does not assume + * that the write-buffer is already locked. + */ +static inline int ubifs_wbuf_sync(struct ubifs_wbuf *wbuf) +{ + int err; + + mutex_lock_nested(&wbuf->io_mutex, wbuf->jhead); + err = ubifs_wbuf_sync_nolock(wbuf); + mutex_unlock(&wbuf->io_mutex); + return err; +} + +/** + * ubifs_add_dirt - add dirty space to LEB properties. + * @c: the UBIFS file-system description object + * @lnum: LEB to add dirty space for + * @dirty: dirty space to add + * + * This is a helper function which increased amount of dirty LEB space. Returns + * zero in case of success and a negative error code in case of failure. + */ +static inline int ubifs_add_dirt(struct ubifs_info *c, int lnum, int dirty) +{ + return ubifs_update_one_lp(c, lnum, LPROPS_NC, dirty, 0, 0); +} + +/** + * ubifs_return_leb - return LEB to lprops. + * @c: the UBIFS file-system description object + * @lnum: LEB to return + * + * This helper function cleans the "taken" flag of a logical eraseblock in the + * lprops. Returns zero in case of success and a negative error code in case of + * failure. + */ +static inline int ubifs_return_leb(struct ubifs_info *c, int lnum) +{ + return ubifs_change_one_lp(c, lnum, LPROPS_NC, LPROPS_NC, 0, + LPROPS_TAKEN, 0); +} + +/** + * ubifs_idx_node_sz - return index node size. + * @c: the UBIFS file-system description object + * @child_cnt: number of children of this index node + */ +static inline int ubifs_idx_node_sz(const struct ubifs_info *c, int child_cnt) +{ + return UBIFS_IDX_NODE_SZ + (UBIFS_BRANCH_SZ + c->key_len + c->hash_len) + * child_cnt; +} + +/** + * ubifs_idx_branch - return pointer to an index branch. + * @c: the UBIFS file-system description object + * @idx: index node + * @bnum: branch number + */ +static inline +struct ubifs_branch *ubifs_idx_branch(const struct ubifs_info *c, + const struct ubifs_idx_node *idx, + int bnum) +{ + return (struct ubifs_branch *)((void *)idx->branches + + (UBIFS_BRANCH_SZ + c->key_len + c->hash_len) * bnum); +} + +/** + * ubifs_idx_key - return pointer to an index key. + * @c: the UBIFS file-system description object + * @idx: index node + */ +static inline void *ubifs_idx_key(__unused const struct ubifs_info *c, + const struct ubifs_idx_node *idx) +{ + return (void *)((struct ubifs_branch *)idx->branches)->key; +} + +/** + * ubifs_tnc_lookup - look up a file-system node. + * @c: UBIFS file-system description object + * @key: node key to lookup + * @node: the node is returned here + * + * This function look up and reads node with key @key. The caller has to make + * sure the @node buffer is large enough to fit the node. Returns zero in case + * of success, %-ENOENT if the node was not found, and a negative error code in + * case of failure. + */ +static inline int ubifs_tnc_lookup(struct ubifs_info *c, + const union ubifs_key *key, void *node) +{ + return ubifs_tnc_locate(c, key, node, NULL, NULL); +} + +/** + * ubifs_get_lprops - get reference to LEB properties. + * @c: the UBIFS file-system description object + * + * This function locks lprops. Lprops have to be unlocked by + * 'ubifs_release_lprops()'. + */ +static inline void ubifs_get_lprops(struct ubifs_info *c) +{ + mutex_lock(&c->lp_mutex); +} + +/** + * ubifs_release_lprops - release lprops lock. + * @c: the UBIFS file-system description object + * + * This function has to be called after each 'ubifs_get_lprops()' call to + * unlock lprops. + */ +static inline void ubifs_release_lprops(struct ubifs_info *c) +{ + ubifs_assert(c, mutex_is_locked(&c->lp_mutex)); + ubifs_assert(c, c->lst.empty_lebs >= 0 && + c->lst.empty_lebs <= c->main_lebs); + mutex_unlock(&c->lp_mutex); +} + +/** + * ubifs_next_log_lnum - switch to the next log LEB. + * @c: UBIFS file-system description object + * @lnum: current log LEB + * + * This helper function returns the log LEB number which goes next after LEB + * 'lnum'. + */ +static inline int ubifs_next_log_lnum(const struct ubifs_info *c, int lnum) +{ + lnum += 1; + if (lnum > c->log_last) + lnum = UBIFS_LOG_LNUM; + + return lnum; +} + +#endif /* __UBIFS_MISC_H__ */ diff --git a/ubifs-utils/libubifs/orphan.c b/ubifs-utils/libubifs/orphan.c new file mode 100644 index 0000000..baa4db7 --- /dev/null +++ b/ubifs-utils/libubifs/orphan.c @@ -0,0 +1,644 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * This file is part of UBIFS. + * + * Copyright (C) 2006-2008 Nokia Corporation. + * + * Author: Adrian Hunter + */ + +#include "linux_err.h" +#include "bitops.h" +#include "kmem.h" +#include "ubifs.h" +#include "defs.h" +#include "debug.h" +#include "key.h" +#include "misc.h" + +/* + * An orphan is an inode number whose inode node has been committed to the index + * with a link count of zero. That happens when an open file is deleted + * (unlinked) and then a commit is run. In the normal course of events the inode + * would be deleted when the file is closed. However in the case of an unclean + * unmount, orphans need to be accounted for. After an unclean unmount, the + * orphans' inodes must be deleted which means either scanning the entire index + * looking for them, or keeping a list on flash somewhere. This unit implements + * the latter approach. + * + * The orphan area is a fixed number of LEBs situated between the LPT area and + * the main area. The number of orphan area LEBs is specified when the file + * system is created. The minimum number is 1. The size of the orphan area + * should be so that it can hold the maximum number of orphans that are expected + * to ever exist at one time. + * + * The number of orphans that can fit in a LEB is: + * + * (c->leb_size - UBIFS_ORPH_NODE_SZ) / sizeof(__le64) + * + * For example: a 15872 byte LEB can fit 1980 orphans so 1 LEB may be enough. + * + * Orphans are accumulated in a rb-tree. When an inode's link count drops to + * zero, the inode number is added to the rb-tree. It is removed from the tree + * when the inode is deleted. Any new orphans that are in the orphan tree when + * the commit is run, are written to the orphan area in 1 or more orphan nodes. + * If the orphan area is full, it is consolidated to make space. There is + * always enough space because validation prevents the user from creating more + * than the maximum number of orphans allowed. + */ + +static int dbg_check_orphans(struct ubifs_info *c); + +/** + * ubifs_orphan_start_commit - start commit of orphans. + * @c: UBIFS file-system description object + * + * Start commit of orphans. + */ +int ubifs_orphan_start_commit(struct ubifs_info *c) +{ + struct ubifs_orphan *orphan, **last; + + spin_lock(&c->orphan_lock); + last = &c->orph_cnext; + list_for_each_entry(orphan, &c->orph_new, new_list) { + ubifs_assert(c, orphan->new); + ubifs_assert(c, !orphan->cmt); + orphan->new = 0; + orphan->cmt = 1; + *last = orphan; + last = &orphan->cnext; + } + *last = NULL; + c->cmt_orphans = c->new_orphans; + c->new_orphans = 0; + dbg_cmt("%d orphans to commit", c->cmt_orphans); + INIT_LIST_HEAD(&c->orph_new); + if (c->tot_orphans == 0) + c->no_orphs = 1; + else + c->no_orphs = 0; + spin_unlock(&c->orphan_lock); + return 0; +} + +/** + * avail_orphs - calculate available space. + * @c: UBIFS file-system description object + * + * This function returns the number of orphans that can be written in the + * available space. + */ +static int avail_orphs(struct ubifs_info *c) +{ + int avail_lebs, avail, gap; + + avail_lebs = c->orph_lebs - (c->ohead_lnum - c->orph_first) - 1; + avail = avail_lebs * + ((c->leb_size - UBIFS_ORPH_NODE_SZ) / sizeof(__le64)); + gap = c->leb_size - c->ohead_offs; + if (gap >= UBIFS_ORPH_NODE_SZ + sizeof(__le64)) + avail += (gap - UBIFS_ORPH_NODE_SZ) / sizeof(__le64); + return avail; +} + +/** + * tot_avail_orphs - calculate total space. + * @c: UBIFS file-system description object + * + * This function returns the number of orphans that can be written in half + * the total space. That leaves half the space for adding new orphans. + */ +static int tot_avail_orphs(struct ubifs_info *c) +{ + int avail_lebs, avail; + + avail_lebs = c->orph_lebs; + avail = avail_lebs * + ((c->leb_size - UBIFS_ORPH_NODE_SZ) / sizeof(__le64)); + return avail / 2; +} + +/** + * do_write_orph_node - write a node to the orphan head. + * @c: UBIFS file-system description object + * @len: length of node + * @atomic: write atomically + * + * This function writes a node to the orphan head from the orphan buffer. If + * %atomic is not zero, then the write is done atomically. On success, %0 is + * returned, otherwise a negative error code is returned. + */ +static int do_write_orph_node(struct ubifs_info *c, int len, int atomic) +{ + int err = 0; + + if (atomic) { + ubifs_assert(c, c->ohead_offs == 0); + ubifs_prepare_node(c, c->orph_buf, len, 1); + len = ALIGN(len, c->min_io_size); + err = ubifs_leb_change(c, c->ohead_lnum, c->orph_buf, len); + } else { + if (c->ohead_offs == 0) { + /* Ensure LEB has been unmapped */ + err = ubifs_leb_unmap(c, c->ohead_lnum); + if (err) + return err; + } + err = ubifs_write_node(c, c->orph_buf, len, c->ohead_lnum, + c->ohead_offs); + } + return err; +} + +/** + * write_orph_node - write an orphan node. + * @c: UBIFS file-system description object + * @atomic: write atomically + * + * This function builds an orphan node from the cnext list and writes it to the + * orphan head. On success, %0 is returned, otherwise a negative error code + * is returned. + */ +static int write_orph_node(struct ubifs_info *c, int atomic) +{ + struct ubifs_orphan *orphan, *cnext; + struct ubifs_orph_node *orph; + int gap, err, len, cnt, i; + + ubifs_assert(c, c->cmt_orphans > 0); + gap = c->leb_size - c->ohead_offs; + if (gap < UBIFS_ORPH_NODE_SZ + sizeof(__le64)) { + c->ohead_lnum += 1; + c->ohead_offs = 0; + gap = c->leb_size; + if (c->ohead_lnum > c->orph_last) { + /* + * We limit the number of orphans so that this should + * never happen. + */ + ubifs_err(c, "out of space in orphan area"); + return -EINVAL; + } + } + cnt = (gap - UBIFS_ORPH_NODE_SZ) / sizeof(__le64); + if (cnt > c->cmt_orphans) + cnt = c->cmt_orphans; + len = UBIFS_ORPH_NODE_SZ + cnt * sizeof(__le64); + ubifs_assert(c, c->orph_buf); + orph = c->orph_buf; + orph->ch.node_type = UBIFS_ORPH_NODE; + spin_lock(&c->orphan_lock); + cnext = c->orph_cnext; + for (i = 0; i < cnt; i++) { + orphan = cnext; + ubifs_assert(c, orphan->cmt); + orph->inos[i] = cpu_to_le64(orphan->inum); + orphan->cmt = 0; + cnext = orphan->cnext; + orphan->cnext = NULL; + } + c->orph_cnext = cnext; + c->cmt_orphans -= cnt; + spin_unlock(&c->orphan_lock); + if (c->cmt_orphans) + orph->cmt_no = cpu_to_le64(c->cmt_no); + else + /* Mark the last node of the commit */ + orph->cmt_no = cpu_to_le64((c->cmt_no) | (1ULL << 63)); + ubifs_assert(c, c->ohead_offs + len <= c->leb_size); + ubifs_assert(c, c->ohead_lnum >= c->orph_first); + ubifs_assert(c, c->ohead_lnum <= c->orph_last); + err = do_write_orph_node(c, len, atomic); + c->ohead_offs += ALIGN(len, c->min_io_size); + c->ohead_offs = ALIGN(c->ohead_offs, 8); + return err; +} + +/** + * write_orph_nodes - write orphan nodes until there are no more to commit. + * @c: UBIFS file-system description object + * @atomic: write atomically + * + * This function writes orphan nodes for all the orphans to commit. On success, + * %0 is returned, otherwise a negative error code is returned. + */ +static int write_orph_nodes(struct ubifs_info *c, int atomic) +{ + int err; + + while (c->cmt_orphans > 0) { + err = write_orph_node(c, atomic); + if (err) + return err; + } + if (atomic) { + int lnum; + + /* Unmap any unused LEBs after consolidation */ + for (lnum = c->ohead_lnum + 1; lnum <= c->orph_last; lnum++) { + err = ubifs_leb_unmap(c, lnum); + if (err) + return err; + } + } + return 0; +} + +/** + * consolidate - consolidate the orphan area. + * @c: UBIFS file-system description object + * + * This function enables consolidation by putting all the orphans into the list + * to commit. The list is in the order that the orphans were added, and the + * LEBs are written atomically in order, so at no time can orphans be lost by + * an unclean unmount. + * + * This function returns %0 on success and a negative error code on failure. + */ +static int consolidate(struct ubifs_info *c) +{ + int tot_avail = tot_avail_orphs(c), err = 0; + + spin_lock(&c->orphan_lock); + dbg_cmt("there is space for %d orphans and there are %d", + tot_avail, c->tot_orphans); + if (c->tot_orphans - c->new_orphans <= tot_avail) { + struct ubifs_orphan *orphan, **last; + int cnt = 0; + + /* Change the cnext list to include all non-new orphans */ + last = &c->orph_cnext; + list_for_each_entry(orphan, &c->orph_list, list) { + if (orphan->new) + continue; + orphan->cmt = 1; + *last = orphan; + last = &orphan->cnext; + cnt += 1; + } + *last = NULL; + ubifs_assert(c, cnt == c->tot_orphans - c->new_orphans); + c->cmt_orphans = cnt; + c->ohead_lnum = c->orph_first; + c->ohead_offs = 0; + } else { + /* + * We limit the number of orphans so that this should + * never happen. + */ + ubifs_err(c, "out of space in orphan area"); + err = -EINVAL; + } + spin_unlock(&c->orphan_lock); + return err; +} + +/** + * commit_orphans - commit orphans. + * @c: UBIFS file-system description object + * + * This function commits orphans to flash. On success, %0 is returned, + * otherwise a negative error code is returned. + */ +static int commit_orphans(struct ubifs_info *c) +{ + int avail, atomic = 0, err; + + ubifs_assert(c, c->cmt_orphans > 0); + avail = avail_orphs(c); + if (avail < c->cmt_orphans) { + /* Not enough space to write new orphans, so consolidate */ + err = consolidate(c); + if (err) + return err; + atomic = 1; + } + err = write_orph_nodes(c, atomic); + return err; +} + +/** + * erase_deleted - erase the orphans marked for deletion. + * @c: UBIFS file-system description object + * + * During commit, the orphans being committed cannot be deleted, so they are + * marked for deletion and deleted by this function. Also, the recovery + * adds killed orphans to the deletion list, and therefore they are deleted + * here too. + */ +static void erase_deleted(struct ubifs_info *c) +{ + struct ubifs_orphan *orphan, *dnext; + + spin_lock(&c->orphan_lock); + dnext = c->orph_dnext; + while (dnext) { + orphan = dnext; + dnext = orphan->dnext; + ubifs_assert(c, !orphan->new); + ubifs_assert(c, orphan->del); + list_del(&orphan->list); + c->tot_orphans -= 1; + dbg_gen("deleting orphan ino %lu", (unsigned long)orphan->inum); + kfree(orphan); + } + c->orph_dnext = NULL; + spin_unlock(&c->orphan_lock); +} + +/** + * ubifs_orphan_end_commit - end commit of orphans. + * @c: UBIFS file-system description object + * + * End commit of orphans. + */ +int ubifs_orphan_end_commit(struct ubifs_info *c) +{ + int err; + + if (c->cmt_orphans != 0) { + err = commit_orphans(c); + if (err) + return err; + } + erase_deleted(c); + err = dbg_check_orphans(c); + return err; +} + +/** + * ubifs_clear_orphans - erase all LEBs used for orphans. + * @c: UBIFS file-system description object + * + * If recovery is not required, then the orphans from the previous session + * are not needed. This function locates the LEBs used to record + * orphans, and un-maps them. + */ +int ubifs_clear_orphans(struct ubifs_info *c) +{ + int lnum, err; + + for (lnum = c->orph_first; lnum <= c->orph_last; lnum++) { + err = ubifs_leb_unmap(c, lnum); + if (err) + return err; + } + c->ohead_lnum = c->orph_first; + c->ohead_offs = 0; + return 0; +} + +/** + * do_kill_orphans - remove orphan inodes from the index. + * @c: UBIFS file-system description object + * @sleb: scanned LEB + * @last_cmt_no: cmt_no of last orphan node read is passed and returned here + * @outofdate: whether the LEB is out of date is returned here + * @last_flagged: whether the end orphan node is encountered + * + * This function is a helper to the 'kill_orphans()' function. It goes through + * every orphan node in a LEB and for every inode number recorded, removes + * all keys for that inode from the TNC. + */ +static int do_kill_orphans(struct ubifs_info *c, struct ubifs_scan_leb *sleb, + unsigned long long *last_cmt_no, int *outofdate, + int *last_flagged) +{ + struct ubifs_scan_node *snod; + struct ubifs_orph_node *orph; + struct ubifs_ino_node *ino = NULL; + unsigned long long cmt_no; + ino_t inum; + int i, n, err, first = 1; + + ino = kmalloc(UBIFS_MAX_INO_NODE_SZ, GFP_NOFS); + if (!ino) + return -ENOMEM; + + list_for_each_entry(snod, &sleb->nodes, list) { + if (snod->type != UBIFS_ORPH_NODE) { + ubifs_err(c, "invalid node type %d in orphan area at %d:%d", + snod->type, sleb->lnum, snod->offs); + ubifs_dump_node(c, snod->node, + c->leb_size - snod->offs); + err = -EINVAL; + set_failure_reason_callback(c, FR_DATA_CORRUPTED); + goto out_free; + } + + orph = snod->node; + + /* Check commit number */ + cmt_no = le64_to_cpu(orph->cmt_no) & LLONG_MAX; + /* + * The commit number on the master node may be less, because + * of a failed commit. If there are several failed commits in a + * row, the commit number written on orphan nodes will continue + * to increase (because the commit number is adjusted here) even + * though the commit number on the master node stays the same + * because the master node has not been re-written. + */ + if (cmt_no > c->cmt_no) + c->cmt_no = cmt_no; + if (cmt_no < *last_cmt_no && *last_flagged) { + /* + * The last orphan node had a higher commit number and + * was flagged as the last written for that commit + * number. That makes this orphan node, out of date. + */ + if (!first) { + ubifs_err(c, "out of order commit number %llu in orphan node at %d:%d", + cmt_no, sleb->lnum, snod->offs); + ubifs_dump_node(c, snod->node, + c->leb_size - snod->offs); + err = -EINVAL; + set_failure_reason_callback(c, FR_DATA_CORRUPTED); + goto out_free; + } + dbg_rcvry("out of date LEB %d", sleb->lnum); + *outofdate = 1; + err = 0; + goto out_free; + } + + if (first) + first = 0; + + n = (le32_to_cpu(orph->ch.len) - UBIFS_ORPH_NODE_SZ) >> 3; + for (i = 0; i < n; i++) { + union ubifs_key key; + + inum = le64_to_cpu(orph->inos[i]); + + ino_key_init(c, &key, inum); + err = ubifs_tnc_lookup(c, &key, ino); + if (err && err != -ENOENT) { + unsigned int reason; + + reason = get_failure_reason_callback(c); + if (reason & FR_DATA_CORRUPTED) { + test_and_clear_failure_reason_callback(c, FR_DATA_CORRUPTED); + if (handle_failure_callback(c, FR_H_TNC_DATA_CORRUPTED, NULL)) { + /* Leave the inode to be deleted by subsequent steps */ + continue; + } + } + goto out_free; + } + + /* + * Check whether an inode can really get deleted. + * linkat() with O_TMPFILE allows rebirth of an inode. + */ + if (err == 0 && ino->nlink == 0) { + dbg_rcvry("deleting orphaned inode %lu", + (unsigned long)inum); + + err = ubifs_tnc_remove_ino(c, inum); + if (err) { + if (c->program_type == FSCK_PROGRAM_TYPE) + goto out_free; + goto out_ro; + } + } + } + + *last_cmt_no = cmt_no; + if (le64_to_cpu(orph->cmt_no) & (1ULL << 63)) { + dbg_rcvry("last orph node for commit %llu at %d:%d", + cmt_no, sleb->lnum, snod->offs); + *last_flagged = 1; + } else + *last_flagged = 0; + } + + err = 0; +out_free: + kfree(ino); + return err; + +out_ro: + ubifs_ro_mode(c, err); + kfree(ino); + return err; +} + +/** + * kill_orphans - remove all orphan inodes from the index. + * @c: UBIFS file-system description object + * + * If recovery is required, then orphan inodes recorded during the previous + * session (which ended with an unclean unmount) must be deleted from the index. + * This is done by updating the TNC, but since the index is not updated until + * the next commit, the LEBs where the orphan information is recorded are not + * erased until the next commit. + */ +static int kill_orphans(struct ubifs_info *c) +{ + unsigned long long last_cmt_no = 0; + int lnum, err = 0, outofdate = 0, last_flagged = 0; + + c->ohead_lnum = c->orph_first; + c->ohead_offs = 0; + /* Check no-orphans flag and skip this if no orphans */ + if (c->no_orphs) { + dbg_rcvry("no orphans"); + return 0; + } + /* + * Orph nodes always start at c->orph_first and are written to each + * successive LEB in turn. Generally unused LEBs will have been unmapped + * but may contain out of date orphan nodes if the unmap didn't go + * through. In addition, the last orphan node written for each commit is + * marked (top bit of orph->cmt_no is set to 1). It is possible that + * there are orphan nodes from the next commit (i.e. the commit did not + * complete successfully). In that case, no orphans will have been lost + * due to the way that orphans are written, and any orphans added will + * be valid orphans anyway and so can be deleted. + */ + for (lnum = c->orph_first; lnum <= c->orph_last; lnum++) { + struct ubifs_scan_leb *sleb; + + dbg_rcvry("LEB %d", lnum); + sleb = ubifs_scan(c, lnum, 0, c->sbuf, 1); + if (IS_ERR(sleb)) { + if (PTR_ERR(sleb) == -EUCLEAN) { + clear_failure_reason_callback(c); + sleb = ubifs_recover_leb(c, lnum, 0, + c->sbuf, -1); + } + if (IS_ERR(sleb)) { + if (test_and_clear_failure_reason_callback(c, FR_DATA_CORRUPTED) && + handle_failure_callback(c, FR_H_ORPHAN_CORRUPTED, &lnum)) { + /* Skip the orphan LEB. */ + continue; + } + err = PTR_ERR(sleb); + break; + } + } + err = do_kill_orphans(c, sleb, &last_cmt_no, &outofdate, + &last_flagged); + if (err) { + unsigned int reason = get_failure_reason_callback(c); + + if (reason & FR_DATA_CORRUPTED) { + test_and_clear_failure_reason_callback(c, FR_DATA_CORRUPTED); + if (handle_failure_callback(c, FR_H_ORPHAN_CORRUPTED, &lnum)) { + err = 0; + /* Skip the orphan LEB. */ + ubifs_scan_destroy(sleb); + continue; + } + } + ubifs_scan_destroy(sleb); + break; + } + if (outofdate) { + ubifs_scan_destroy(sleb); + break; + } + if (sleb->endpt) { + c->ohead_lnum = lnum; + c->ohead_offs = sleb->endpt; + } + ubifs_scan_destroy(sleb); + } + return err; +} + +/** + * ubifs_mount_orphans - delete orphan inodes and erase LEBs that recorded them. + * @c: UBIFS file-system description object + * @unclean: indicates recovery from unclean unmount + * @read_only: indicates read only mount + * + * This function is called when mounting to erase orphans from the previous + * session. If UBIFS was not unmounted cleanly, then the inodes recorded as + * orphans are deleted. + */ +int ubifs_mount_orphans(struct ubifs_info *c, int unclean, int read_only) +{ + int err = 0; + + c->max_orphans = tot_avail_orphs(c); + + if (!read_only) { + c->orph_buf = vmalloc(c->leb_size); + if (!c->orph_buf) + return -ENOMEM; + } + + if (unclean) + err = kill_orphans(c); + else if (!read_only) + err = ubifs_clear_orphans(c); + + return err; +} + +static int dbg_check_orphans(__unused struct ubifs_info *c) +{ + return 0; +} diff --git a/ubifs-utils/libubifs/recovery.c b/ubifs-utils/libubifs/recovery.c new file mode 100644 index 0000000..905e164 --- /dev/null +++ b/ubifs-utils/libubifs/recovery.c @@ -0,0 +1,1404 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * This file is part of UBIFS. + * + * Copyright (C) 2006-2008 Nokia Corporation + * + * Authors: Adrian Hunter + * Artem Bityutskiy (Битюцкий Артём) + */ + +/* + * This file implements functions needed to recover from unclean un-mounts. + * When UBIFS is mounted, it checks a flag on the master node to determine if + * an un-mount was completed successfully. If not, the process of mounting + * incorporates additional checking and fixing of on-flash data structures. + * UBIFS always cleans away all remnants of an unclean un-mount, so that + * errors do not accumulate. However UBIFS defers recovery if it is mounted + * read-only, and the flash is not modified in that case. + * + * The general UBIFS approach to the recovery is that it recovers from + * corruptions which could be caused by power cuts, but it refuses to recover + * from corruption caused by other reasons. And UBIFS tries to distinguish + * between these 2 reasons of corruptions and silently recover in the former + * case and loudly complain in the latter case. + * + * UBIFS writes only to erased LEBs, so it writes only to the flash space + * containing only 0xFFs. UBIFS also always writes strictly from the beginning + * of the LEB to the end. And UBIFS assumes that the underlying flash media + * writes in @c->max_write_size bytes at a time. + * + * Hence, if UBIFS finds a corrupted node at offset X, it expects only the min. + * I/O unit corresponding to offset X to contain corrupted data, all the + * following min. I/O units have to contain empty space (all 0xFFs). If this is + * not true, the corruption cannot be the result of a power cut, and UBIFS + * refuses to mount. + */ + +#include <sys/types.h> + +#include "linux_err.h" +#include "bitops.h" +#include "kmem.h" +#include "crc32.h" +#include "ubifs.h" +#include "defs.h" +#include "debug.h" +#include "key.h" +#include "misc.h" + +/** + * is_empty - determine whether a buffer is empty (contains all 0xff). + * @buf: buffer to clean + * @len: length of buffer + * + * This function returns %1 if the buffer is empty (contains all 0xff) otherwise + * %0 is returned. + */ +static int is_empty(void *buf, int len) +{ + uint8_t *p = buf; + int i; + + for (i = 0; i < len; i++) + if (*p++ != 0xff) + return 0; + return 1; +} + +/** + * first_non_ff - find offset of the first non-0xff byte. + * @buf: buffer to search in + * @len: length of buffer + * + * This function returns offset of the first non-0xff byte in @buf or %-1 if + * the buffer contains only 0xff bytes. + */ +static int first_non_ff(void *buf, int len) +{ + uint8_t *p = buf; + int i; + + for (i = 0; i < len; i++) + if (*p++ != 0xff) + return i; + return -1; +} + +/** + * get_master_node - get the last valid master node allowing for corruption. + * @c: UBIFS file-system description object + * @lnum: LEB number + * @pbuf: buffer containing the LEB read, is returned here + * @mst: master node, if found, is returned here + * @cor: corruption, if found, is returned here + * + * This function allocates a buffer, reads the LEB into it, and finds and + * returns the last valid master node allowing for one area of corruption. + * The corrupt area, if there is one, must be consistent with the assumption + * that it is the result of an unclean unmount while the master node was being + * written. Under those circumstances, it is valid to use the previously written + * master node. + * + * This function returns %0 on success and a negative error code on failure. + */ +static int get_master_node(const struct ubifs_info *c, int lnum, void **pbuf, + struct ubifs_mst_node **mst, void **cor) +{ + const int sz = c->mst_node_alsz; + int err, offs, len; + void *sbuf, *buf; + + sbuf = vmalloc(c->leb_size); + if (!sbuf) + return -ENOMEM; + + err = ubifs_leb_read(c, lnum, sbuf, 0, c->leb_size, 0); + if (err && err != -EBADMSG) + goto out_free; + + /* Find the first position that is definitely not a node */ + offs = 0; + buf = sbuf; + len = c->leb_size; + while (offs + UBIFS_MST_NODE_SZ <= c->leb_size) { + struct ubifs_ch *ch = buf; + + if (le32_to_cpu(ch->magic) != UBIFS_NODE_MAGIC) + break; + offs += sz; + buf += sz; + len -= sz; + } + /* See if there was a valid master node before that */ + if (offs) { + int ret; + + offs -= sz; + buf -= sz; + len += sz; + ret = ubifs_scan_a_node(c, buf, len, lnum, offs, 1); + if (ret != SCANNED_A_NODE && offs) { + /* Could have been corruption so check one place back */ + offs -= sz; + buf -= sz; + len += sz; + ret = ubifs_scan_a_node(c, buf, len, lnum, offs, 1); + if (ret != SCANNED_A_NODE) + /* + * We accept only one area of corruption because + * we are assuming that it was caused while + * trying to write a master node. + */ + goto out_err; + } + if (ret == SCANNED_A_NODE) { + struct ubifs_ch *ch = buf; + + if (ch->node_type != UBIFS_MST_NODE) + goto out_err; + dbg_rcvry("found a master node at %d:%d", lnum, offs); + *mst = buf; + offs += sz; + buf += sz; + len -= sz; + } + } + /* Check for corruption */ + if (offs < c->leb_size) { + if (!is_empty(buf, min_t(int, len, sz))) { + *cor = buf; + dbg_rcvry("found corruption at %d:%d", lnum, offs); + } + offs += sz; + buf += sz; + len -= sz; + } + /* Check remaining empty space */ + if (offs < c->leb_size) + if (!is_empty(buf, len)) + goto out_err; + *pbuf = sbuf; + return 0; + +out_err: + set_failure_reason_callback(c, FR_DATA_CORRUPTED); + err = -EINVAL; +out_free: + vfree(sbuf); + *mst = NULL; + *cor = NULL; + return err; +} + +/** + * write_rcvrd_mst_node - write recovered master node. + * @c: UBIFS file-system description object + * @mst: master node + * + * This function returns %0 on success and a negative error code on failure. + */ +static int write_rcvrd_mst_node(struct ubifs_info *c, + struct ubifs_mst_node *mst) +{ + int err = 0, lnum = UBIFS_MST_LNUM, sz = c->mst_node_alsz; + __le32 save_flags; + + dbg_rcvry("recovery"); + + save_flags = mst->flags; + mst->flags |= cpu_to_le32(UBIFS_MST_RCVRY); + + err = ubifs_prepare_node_hmac(c, mst, UBIFS_MST_NODE_SZ, + offsetof(struct ubifs_mst_node, hmac), 1); + if (err) + goto out; + err = ubifs_leb_change(c, lnum, mst, sz); + if (err) + goto out; + err = ubifs_leb_change(c, lnum + 1, mst, sz); + if (err) + goto out; +out: + mst->flags = save_flags; + return err; +} + +/** + * ubifs_recover_master_node - recover the master node. + * @c: UBIFS file-system description object + * + * This function recovers the master node from corruption that may occur due to + * an unclean unmount. + * + * This function returns %0 on success and a negative error code on failure. + */ +int ubifs_recover_master_node(struct ubifs_info *c) +{ + void *buf1 = NULL, *buf2 = NULL, *cor1 = NULL, *cor2 = NULL; + struct ubifs_mst_node *mst1 = NULL, *mst2 = NULL, *mst; + const int sz = c->mst_node_alsz; + int err, offs1, offs2; + + dbg_rcvry("recovery"); + + err = get_master_node(c, UBIFS_MST_LNUM, &buf1, &mst1, &cor1); + if (err) + goto out_free; + + err = get_master_node(c, UBIFS_MST_LNUM + 1, &buf2, &mst2, &cor2); + if (err) + goto out_free; + + if (mst1) { + offs1 = (void *)mst1 - buf1; + if ((le32_to_cpu(mst1->flags) & UBIFS_MST_RCVRY) && + (offs1 == 0 && !cor1)) { + /* + * mst1 was written by recovery at offset 0 with no + * corruption. + */ + dbg_rcvry("recovery recovery"); + mst = mst1; + } else if (mst2) { + offs2 = (void *)mst2 - buf2; + if (offs1 == offs2) { + /* Same offset, so must be the same */ + if (ubifs_compare_master_node(c, mst1, mst2)) + goto out_err; + mst = mst1; + } else if (offs2 + sz == offs1) { + /* 1st LEB was written, 2nd was not */ + if (cor1) + goto out_err; + mst = mst1; + } else if (offs1 == 0 && + c->leb_size - offs2 - sz < sz) { + /* 1st LEB was unmapped and written, 2nd not */ + if (cor1) + goto out_err; + mst = mst1; + } else + goto out_err; + } else { + /* + * 2nd LEB was unmapped and about to be written, so + * there must be only one master node in the first LEB + * and no corruption. + */ + if (offs1 != 0 || cor1) + goto out_err; + mst = mst1; + } + } else { + if (!mst2) + goto out_err; + /* + * 1st LEB was unmapped and about to be written, so there must + * be no room left in 2nd LEB. + */ + offs2 = (void *)mst2 - buf2; + if (offs2 + sz + sz <= c->leb_size) + goto out_err; + mst = mst2; + } + + ubifs_msg(c, "recovered master node from LEB %d", + (mst == mst1 ? UBIFS_MST_LNUM : UBIFS_MST_LNUM + 1)); + + memcpy(c->mst_node, mst, UBIFS_MST_NODE_SZ); + + if (c->ro_mount) { + /* Read-only mode. Keep a copy for switching to rw mode */ + c->rcvrd_mst_node = kmalloc(sz, GFP_KERNEL); + if (!c->rcvrd_mst_node) { + err = -ENOMEM; + goto out_free; + } + memcpy(c->rcvrd_mst_node, c->mst_node, UBIFS_MST_NODE_SZ); + + /* + * We had to recover the master node, which means there was an + * unclean reboot. However, it is possible that the master node + * is clean at this point, i.e., %UBIFS_MST_DIRTY is not set. + * E.g., consider the following chain of events: + * + * 1. UBIFS was cleanly unmounted, so the master node is clean + * 2. UBIFS is being mounted R/W and starts changing the master + * node in the first (%UBIFS_MST_LNUM). A power cut happens, + * so this LEB ends up with some amount of garbage at the + * end. + * 3. UBIFS is being mounted R/O. We reach this place and + * recover the master node from the second LEB + * (%UBIFS_MST_LNUM + 1). But we cannot update the media + * because we are being mounted R/O. We have to defer the + * operation. + * 4. However, this master node (@c->mst_node) is marked as + * clean (since the step 1). And if we just return, the + * mount code will be confused and won't recover the master + * node when it is re-mounter R/W later. + * + * Thus, to force the recovery by marking the master node as + * dirty. + */ + c->mst_node->flags |= cpu_to_le32(UBIFS_MST_DIRTY); + } else { + /* Write the recovered master node */ + c->max_sqnum = le64_to_cpu(mst->ch.sqnum) - 1; + err = write_rcvrd_mst_node(c, c->mst_node); + if (err) + goto out_free; + } + + vfree(buf2); + vfree(buf1); + + return 0; + +out_err: + set_failure_reason_callback(c, FR_DATA_CORRUPTED); + err = -EINVAL; +out_free: + ubifs_err(c, "failed to recover master node"); + if (mst1) { + ubifs_err(c, "dumping first master node"); + ubifs_dump_node(c, mst1, c->leb_size - ((void *)mst1 - buf1)); + } + if (mst2) { + ubifs_err(c, "dumping second master node"); + ubifs_dump_node(c, mst2, c->leb_size - ((void *)mst2 - buf2)); + } + vfree(buf2); + vfree(buf1); + return err; +} + +/** + * is_last_write - determine if an offset was in the last write to a LEB. + * @c: UBIFS file-system description object + * @buf: buffer to check + * @offs: offset to check + * + * This function returns %1 if @offs was in the last write to the LEB whose data + * is in @buf, otherwise %0 is returned. The determination is made by checking + * for subsequent empty space starting from the next @c->max_write_size + * boundary. + */ +static int is_last_write(const struct ubifs_info *c, void *buf, int offs) +{ + int empty_offs, check_len; + uint8_t *p; + + /* + * Round up to the next @c->max_write_size boundary i.e. @offs is in + * the last wbuf written. After that should be empty space. + */ + empty_offs = ALIGN(offs + 1, c->max_write_size); + check_len = c->leb_size - empty_offs; + p = buf + empty_offs - offs; + return is_empty(p, check_len); +} + +/** + * clean_buf - clean the data from an LEB sitting in a buffer. + * @c: UBIFS file-system description object + * @buf: buffer to clean + * @lnum: LEB number to clean + * @offs: offset from which to clean + * @len: length of buffer + * + * This function pads up to the next min_io_size boundary (if there is one) and + * sets empty space to all 0xff. @buf, @offs and @len are updated to the next + * @c->min_io_size boundary. + */ +static void clean_buf(const struct ubifs_info *c, void **buf, int lnum, + int *offs, int *len) +{ + int empty_offs, pad_len; + + dbg_rcvry("cleaning corruption at %d:%d", lnum, *offs); + + ubifs_assert(c, !(*offs & 7)); + empty_offs = ALIGN(*offs, c->min_io_size); + pad_len = empty_offs - *offs; + ubifs_pad(c, *buf, pad_len); + *offs += pad_len; + *buf += pad_len; + *len -= pad_len; + memset(*buf, 0xff, c->leb_size - empty_offs); +} + +/** + * no_more_nodes - determine if there are no more nodes in a buffer. + * @c: UBIFS file-system description object + * @buf: buffer to check + * @len: length of buffer + * @lnum: LEB number of the LEB from which @buf was read + * @offs: offset from which @buf was read + * + * This function ensures that the corrupted node at @offs is the last thing + * written to a LEB. This function returns %1 if more data is not found and + * %0 if more data is found. + */ +static int no_more_nodes(const struct ubifs_info *c, void *buf, int len, + int lnum, int offs) +{ + struct ubifs_ch *ch = buf; + int skip, dlen = le32_to_cpu(ch->len); + + /* Check for empty space after the corrupt node's common header */ + skip = ALIGN(offs + UBIFS_CH_SZ, c->max_write_size) - offs; + if (is_empty(buf + skip, len - skip)) + return 1; + /* + * The area after the common header size is not empty, so the common + * header must be intact. Check it. + */ + if (ubifs_check_node(c, buf, len, lnum, offs, 1, 0) != -EUCLEAN) { + dbg_rcvry("unexpected bad common header at %d:%d", lnum, offs); + return 0; + } + /* Now we know the corrupt node's length we can skip over it */ + skip = ALIGN(offs + dlen, c->max_write_size) - offs; + /* After which there should be empty space */ + if (is_empty(buf + skip, len - skip)) + return 1; + dbg_rcvry("unexpected data at %d:%d", lnum, offs + skip); + return 0; +} + +/** + * fix_unclean_leb - fix an unclean LEB. + * @c: UBIFS file-system description object + * @sleb: scanned LEB information + * @start: offset where scan started + */ +static int fix_unclean_leb(struct ubifs_info *c, struct ubifs_scan_leb *sleb, + int start) +{ + int lnum = sleb->lnum, endpt = start; + + /* Get the end offset of the last node we are keeping */ + if (!list_empty(&sleb->nodes)) { + struct ubifs_scan_node *snod; + + snod = list_entry(sleb->nodes.prev, + struct ubifs_scan_node, list); + endpt = snod->offs + snod->len; + } + + if (c->ro_mount && !c->remounting_rw) { + /* Add to recovery list */ + struct ubifs_unclean_leb *ucleb; + + dbg_rcvry("need to fix LEB %d start %d endpt %d", + lnum, start, sleb->endpt); + ucleb = kzalloc(sizeof(struct ubifs_unclean_leb), GFP_NOFS); + if (!ucleb) + return -ENOMEM; + ucleb->lnum = lnum; + ucleb->endpt = endpt; + list_add_tail(&ucleb->list, &c->unclean_leb_list); + } else { + /* Write the fixed LEB back to flash */ + int err; + + dbg_rcvry("fixing LEB %d start %d endpt %d", + lnum, start, sleb->endpt); + if (endpt == 0) { + err = ubifs_leb_unmap(c, lnum); + if (err) + return err; + } else { + int len = ALIGN(endpt, c->min_io_size); + + if (start) { + err = ubifs_leb_read(c, lnum, sleb->buf, 0, + start, 1); + if (err && err != -EBADMSG) + return err; + } + /* Pad to min_io_size */ + if (len > endpt) { + int pad_len = len - ALIGN(endpt, 8); + + if (pad_len > 0) { + void *buf = sleb->buf + len - pad_len; + + ubifs_pad(c, buf, pad_len); + } + } + err = ubifs_leb_change(c, lnum, sleb->buf, len); + if (err) + return err; + } + } + return 0; +} + +/** + * drop_last_group - drop the last group of nodes. + * @sleb: scanned LEB information + * @offs: offset of dropped nodes is returned here + * + * This is a helper function for 'ubifs_recover_leb()' which drops the last + * group of nodes of the scanned LEB. + */ +static void drop_last_group(struct ubifs_scan_leb *sleb, int *offs) +{ + while (!list_empty(&sleb->nodes)) { + struct ubifs_scan_node *snod; + struct ubifs_ch *ch; + + snod = list_entry(sleb->nodes.prev, struct ubifs_scan_node, + list); + ch = snod->node; + if (ch->group_type != UBIFS_IN_NODE_GROUP) + break; + + dbg_rcvry("dropping grouped node at %d:%d", + sleb->lnum, snod->offs); + *offs = snod->offs; + list_del(&snod->list); + kfree(snod); + sleb->nodes_cnt -= 1; + } +} + +/** + * drop_last_node - drop the last node. + * @sleb: scanned LEB information + * @offs: offset of dropped nodes is returned here + * + * This is a helper function for 'ubifs_recover_leb()' which drops the last + * node of the scanned LEB. + */ +static void drop_last_node(struct ubifs_scan_leb *sleb, int *offs) +{ + struct ubifs_scan_node *snod; + + if (!list_empty(&sleb->nodes)) { + snod = list_entry(sleb->nodes.prev, struct ubifs_scan_node, + list); + + dbg_rcvry("dropping last node at %d:%d", + sleb->lnum, snod->offs); + *offs = snod->offs; + list_del(&snod->list); + kfree(snod); + sleb->nodes_cnt -= 1; + } +} + +/** + * ubifs_recover_leb - scan and recover a LEB. + * @c: UBIFS file-system description object + * @lnum: LEB number + * @offs: offset + * @sbuf: LEB-sized buffer to use + * @jhead: journal head number this LEB belongs to (%-1 if the LEB does not + * belong to any journal head) + * + * This function does a scan of a LEB, but caters for errors that might have + * been caused by the unclean unmount from which we are attempting to recover. + * Returns the scanned information on success and a negative error code on + * failure. + */ +struct ubifs_scan_leb *ubifs_recover_leb(struct ubifs_info *c, int lnum, + int offs, void *sbuf, int jhead) +{ + int ret = 0, err, len = c->leb_size - offs, start = offs, min_io_unit; + int grouped = jhead == -1 ? 0 : c->jheads[jhead].grouped; + struct ubifs_scan_leb *sleb; + void *buf = sbuf + offs; + + dbg_rcvry("%d:%d, jhead %d, grouped %d", lnum, offs, jhead, grouped); + + sleb = ubifs_start_scan(c, lnum, offs, sbuf); + if (IS_ERR(sleb)) + return sleb; + + ubifs_assert(c, len >= 8); + while (len >= 8) { + dbg_scan("look at LEB %d:%d (%d bytes left)", + lnum, offs, len); + + cond_resched(); + + /* + * Scan quietly until there is an error from which we cannot + * recover + */ + ret = ubifs_scan_a_node(c, buf, len, lnum, offs, 1); + if (ret == SCANNED_A_NODE) { + /* A valid node, and not a padding node */ + struct ubifs_ch *ch = buf; + int node_len; + + err = ubifs_add_snod(c, sleb, buf, offs); + if (err) + goto error; + node_len = ALIGN(le32_to_cpu(ch->len), 8); + offs += node_len; + buf += node_len; + len -= node_len; + } else if (ret > 0) { + /* Padding bytes or a valid padding node */ + offs += ret; + buf += ret; + len -= ret; + } else if (ret == SCANNED_EMPTY_SPACE || + ret == SCANNED_GARBAGE || + ret == SCANNED_A_BAD_PAD_NODE || + ret == SCANNED_A_CORRUPT_NODE) { + dbg_rcvry("found corruption (%d) at %d:%d", + ret, lnum, offs); + break; + } else { + ubifs_err(c, "unexpected return value %d", ret); + err = -EINVAL; + goto error; + } + } + + if (ret == SCANNED_GARBAGE || ret == SCANNED_A_BAD_PAD_NODE) { + if (!is_last_write(c, buf, offs)) + goto corrupted_rescan; + } else if (ret == SCANNED_A_CORRUPT_NODE) { + if (!no_more_nodes(c, buf, len, lnum, offs)) + goto corrupted_rescan; + } else if (!is_empty(buf, len)) { + if (!is_last_write(c, buf, offs)) { + int corruption = first_non_ff(buf, len); + + /* + * See header comment for this file for more + * explanations about the reasons we have this check. + */ + ubifs_err(c, "corrupt empty space LEB %d:%d, corruption starts at %d", + lnum, offs, corruption); + /* Make sure we dump interesting non-0xFF data */ + offs += corruption; + buf += corruption; + goto corrupted; + } + } + + min_io_unit = round_down(offs, c->min_io_size); + if (grouped) + /* + * If nodes are grouped, always drop the incomplete group at + * the end. + */ + drop_last_group(sleb, &offs); + + if (jhead == GCHD) { + /* + * If this LEB belongs to the GC head then while we are in the + * middle of the same min. I/O unit keep dropping nodes. So + * basically, what we want is to make sure that the last min. + * I/O unit where we saw the corruption is dropped completely + * with all the uncorrupted nodes which may possibly sit there. + * + * In other words, let's name the min. I/O unit where the + * corruption starts B, and the previous min. I/O unit A. The + * below code tries to deal with a situation when half of B + * contains valid nodes or the end of a valid node, and the + * second half of B contains corrupted data or garbage. This + * means that UBIFS had been writing to B just before the power + * cut happened. I do not know how realistic is this scenario + * that half of the min. I/O unit had been written successfully + * and the other half not, but this is possible in our 'failure + * mode emulation' infrastructure at least. + * + * So what is the problem, why we need to drop those nodes? Why + * can't we just clean-up the second half of B by putting a + * padding node there? We can, and this works fine with one + * exception which was reproduced with power cut emulation + * testing and happens extremely rarely. + * + * Imagine the file-system is full, we run GC which starts + * moving valid nodes from LEB X to LEB Y (obviously, LEB Y is + * the current GC head LEB). The @c->gc_lnum is -1, which means + * that GC will retain LEB X and will try to continue. Imagine + * that LEB X is currently the dirtiest LEB, and the amount of + * used space in LEB Y is exactly the same as amount of free + * space in LEB X. + * + * And a power cut happens when nodes are moved from LEB X to + * LEB Y. We are here trying to recover LEB Y which is the GC + * head LEB. We find the min. I/O unit B as described above. + * Then we clean-up LEB Y by padding min. I/O unit. And later + * 'ubifs_rcvry_gc_commit()' function fails, because it cannot + * find a dirty LEB which could be GC'd into LEB Y! Even LEB X + * does not match because the amount of valid nodes there does + * not fit the free space in LEB Y any more! And this is + * because of the padding node which we added to LEB Y. The + * user-visible effect of this which I once observed and + * analysed is that we cannot mount the file-system with + * -ENOSPC error. + * + * So obviously, to make sure that situation does not happen we + * should free min. I/O unit B in LEB Y completely and the last + * used min. I/O unit in LEB Y should be A. This is basically + * what the below code tries to do. + */ + while (offs > min_io_unit) + drop_last_node(sleb, &offs); + } + + buf = sbuf + offs; + len = c->leb_size - offs; + + clean_buf(c, &buf, lnum, &offs, &len); + ubifs_end_scan(c, sleb, lnum, offs); + + err = fix_unclean_leb(c, sleb, start); + if (err) + goto error; + + return sleb; + +corrupted_rescan: + /* Re-scan the corrupted data with verbose messages */ + ubifs_err(c, "corruption %d", ret); + ubifs_scan_a_node(c, buf, len, lnum, offs, 0); +corrupted: + set_failure_reason_callback(c, FR_DATA_CORRUPTED); + ubifs_scanned_corruption(c, lnum, offs, buf); + err = -EUCLEAN; +error: + ubifs_err(c, "LEB %d scanning failed", lnum); + ubifs_scan_destroy(sleb); + return ERR_PTR(err); +} + +/** + * get_cs_sqnum - get commit start sequence number. + * @c: UBIFS file-system description object + * @lnum: LEB number of commit start node + * @offs: offset of commit start node + * @cs_sqnum: commit start sequence number is returned here + * + * This function returns %0 on success and a negative error code on failure. + */ +static int get_cs_sqnum(struct ubifs_info *c, int lnum, int offs, + unsigned long long *cs_sqnum) +{ + struct ubifs_cs_node *cs_node = NULL; + int err, ret; + + dbg_rcvry("at %d:%d", lnum, offs); + cs_node = kmalloc(UBIFS_CS_NODE_SZ, GFP_KERNEL); + if (!cs_node) + return -ENOMEM; + if (c->leb_size - offs < UBIFS_CS_NODE_SZ) + goto out_err; + err = ubifs_leb_read(c, lnum, (void *)cs_node, offs, + UBIFS_CS_NODE_SZ, 0); + if (err && err != -EBADMSG) + goto out_free; + ret = ubifs_scan_a_node(c, cs_node, UBIFS_CS_NODE_SZ, lnum, offs, 0); + if (ret != SCANNED_A_NODE) { + ubifs_err(c, "Not a valid node"); + goto out_err; + } + if (cs_node->ch.node_type != UBIFS_CS_NODE) { + ubifs_err(c, "Not a CS node, type is %d", cs_node->ch.node_type); + goto out_err; + } + if (le64_to_cpu(cs_node->cmt_no) != c->cmt_no) { + ubifs_err(c, "CS node cmt_no %llu != current cmt_no %llu", + (unsigned long long)le64_to_cpu(cs_node->cmt_no), + c->cmt_no); + goto out_err; + } + *cs_sqnum = le64_to_cpu(cs_node->ch.sqnum); + dbg_rcvry("commit start sqnum %llu", *cs_sqnum); + kfree(cs_node); + return 0; + +out_err: + err = -EINVAL; + set_failure_reason_callback(c, FR_DATA_CORRUPTED); +out_free: + ubifs_err(c, "failed to get CS sqnum"); + kfree(cs_node); + return err; +} + +/** + * ubifs_recover_log_leb - scan and recover a log LEB. + * @c: UBIFS file-system description object + * @lnum: LEB number + * @offs: offset + * @sbuf: LEB-sized buffer to use + * + * This function does a scan of a LEB, but caters for errors that might have + * been caused by unclean reboots from which we are attempting to recover + * (assume that only the last log LEB can be corrupted by an unclean reboot). + * + * This function returns %0 on success and a negative error code on failure. + */ +struct ubifs_scan_leb *ubifs_recover_log_leb(struct ubifs_info *c, int lnum, + int offs, void *sbuf) +{ + struct ubifs_scan_leb *sleb; + int next_lnum; + + dbg_rcvry("LEB %d", lnum); + next_lnum = lnum + 1; + if (next_lnum >= UBIFS_LOG_LNUM + c->log_lebs) + next_lnum = UBIFS_LOG_LNUM; + if (next_lnum != c->ltail_lnum) { + /* + * We can only recover at the end of the log, so check that the + * next log LEB is empty or out of date. + */ + sleb = ubifs_scan(c, next_lnum, 0, sbuf, 0); + if (IS_ERR(sleb)) + return sleb; + if (sleb->nodes_cnt) { + struct ubifs_scan_node *snod; + unsigned long long cs_sqnum = c->cs_sqnum; + + snod = list_entry(sleb->nodes.next, + struct ubifs_scan_node, list); + if (cs_sqnum == 0) { + int err; + + err = get_cs_sqnum(c, lnum, offs, &cs_sqnum); + if (err) { + ubifs_scan_destroy(sleb); + return ERR_PTR(err); + } + } + if (snod->sqnum > cs_sqnum) { + ubifs_err(c, "unrecoverable log corruption in LEB %d", + lnum); + ubifs_scan_destroy(sleb); + return ERR_PTR(-EUCLEAN); + } + } + ubifs_scan_destroy(sleb); + } + return ubifs_recover_leb(c, lnum, offs, sbuf, -1); +} + +/** + * recover_head - recover a head. + * @c: UBIFS file-system description object + * @lnum: LEB number of head to recover + * @offs: offset of head to recover + * @sbuf: LEB-sized buffer to use + * + * This function ensures that there is no data on the flash at a head location. + * + * This function returns %0 on success and a negative error code on failure. + */ +static int recover_head(struct ubifs_info *c, int lnum, int offs, void *sbuf) +{ + int len = c->max_write_size, err; + + if (offs + len > c->leb_size) + len = c->leb_size - offs; + + if (!len) + return 0; + + /* Read at the head location and check it is empty flash */ + err = ubifs_leb_read(c, lnum, sbuf, offs, len, 1); + if (err || !is_empty(sbuf, len)) { + dbg_rcvry("cleaning head at %d:%d", lnum, offs); + if (offs == 0) + return ubifs_leb_unmap(c, lnum); + err = ubifs_leb_read(c, lnum, sbuf, 0, offs, 1); + if (err && err != -EBADMSG) + return err; + return ubifs_leb_change(c, lnum, sbuf, offs); + } + + return 0; +} + +/** + * ubifs_recover_inl_heads - recover index and LPT heads. + * @c: UBIFS file-system description object + * @sbuf: LEB-sized buffer to use + * + * This function ensures that there is no data on the flash at the index and + * LPT head locations. + * + * This deals with the recovery of a half-completed journal commit. UBIFS is + * careful never to overwrite the last version of the index or the LPT. Because + * the index and LPT are wandering trees, data from a half-completed commit will + * not be referenced anywhere in UBIFS. The data will be either in LEBs that are + * assumed to be empty and will be unmapped anyway before use, or in the index + * and LPT heads. + * + * This function returns %0 on success and a negative error code on failure. + */ +int ubifs_recover_inl_heads(struct ubifs_info *c, void *sbuf) +{ + int err; + + ubifs_assert(c, !c->ro_mount || c->remounting_rw); + + dbg_rcvry("checking index head at %d:%d", c->ihead_lnum, c->ihead_offs); + err = recover_head(c, c->ihead_lnum, c->ihead_offs, sbuf); + if (err) + return err; + + dbg_rcvry("checking LPT head at %d:%d", c->nhead_lnum, c->nhead_offs); + + return recover_head(c, c->nhead_lnum, c->nhead_offs, sbuf); +} + +/** + * grab_empty_leb - grab an empty LEB to use as GC LEB and run commit. + * @c: UBIFS file-system description object + * + * This is a helper function for 'ubifs_rcvry_gc_commit()' which grabs an empty + * LEB to be used as GC LEB (@c->gc_lnum), and then runs the commit. Returns + * zero in case of success and a negative error code in case of failure. + */ +static int grab_empty_leb(struct ubifs_info *c) +{ + int lnum, err; + + /* + * Note, it is very important to first search for an empty LEB and then + * run the commit, not vice-versa. The reason is that there might be + * only one empty LEB at the moment, the one which has been the + * @c->gc_lnum just before the power cut happened. During the regular + * UBIFS operation (not now) @c->gc_lnum is marked as "taken", so no + * one but GC can grab it. But at this moment this single empty LEB is + * not marked as taken, so if we run commit - what happens? Right, the + * commit will grab it and write the index there. Remember that the + * index always expands as long as there is free space, and it only + * starts consolidating when we run out of space. + * + * IOW, if we run commit now, we might not be able to find a free LEB + * after this. + */ + lnum = ubifs_find_free_leb_for_idx(c); + if (lnum < 0) { + ubifs_err(c, "could not find an empty LEB"); + ubifs_dump_lprops(c); + ubifs_dump_budg(c, &c->bi); + return lnum; + } + + /* Reset the index flag */ + err = ubifs_change_one_lp(c, lnum, LPROPS_NC, LPROPS_NC, 0, + LPROPS_INDEX, 0); + if (err) + return err; + + c->gc_lnum = lnum; + dbg_rcvry("found empty LEB %d, run commit", lnum); + + return ubifs_run_commit(c); +} + +/** + * ubifs_rcvry_gc_commit - recover the GC LEB number and run the commit. + * @c: UBIFS file-system description object + * + * Out-of-place garbage collection requires always one empty LEB with which to + * start garbage collection. The LEB number is recorded in c->gc_lnum and is + * written to the master node on unmounting. In the case of an unclean unmount + * the value of gc_lnum recorded in the master node is out of date and cannot + * be used. Instead, recovery must allocate an empty LEB for this purpose. + * However, there may not be enough empty space, in which case it must be + * possible to GC the dirtiest LEB into the GC head LEB. + * + * This function also runs the commit which causes the TNC updates from + * size-recovery and orphans to be written to the flash. That is important to + * ensure correct replay order for subsequent mounts. + * + * This function returns %0 on success and a negative error code on failure. + */ +int ubifs_rcvry_gc_commit(struct ubifs_info *c) +{ + struct ubifs_wbuf *wbuf = &c->jheads[GCHD].wbuf; + struct ubifs_lprops lp; + int err; + + dbg_rcvry("GC head LEB %d, offs %d", wbuf->lnum, wbuf->offs); + + c->gc_lnum = -1; + if (wbuf->lnum == -1 || wbuf->offs == c->leb_size) + return grab_empty_leb(c); + + err = ubifs_find_dirty_leb(c, &lp, wbuf->offs, 2); + if (err) { + if (err != -ENOSPC) + return err; + + dbg_rcvry("could not find a dirty LEB"); + return grab_empty_leb(c); + } + + ubifs_assert(c, !(lp.flags & LPROPS_INDEX)); + ubifs_assert(c, lp.free + lp.dirty >= wbuf->offs); + + /* + * We run the commit before garbage collection otherwise subsequent + * mounts will see the GC and orphan deletion in a different order. + */ + dbg_rcvry("committing"); + err = ubifs_run_commit(c); + if (err) + return err; + + dbg_rcvry("GC'ing LEB %d", lp.lnum); + mutex_lock_nested(&wbuf->io_mutex, wbuf->jhead); + err = ubifs_garbage_collect_leb(c, &lp); + if (err >= 0) { + int err2 = ubifs_wbuf_sync_nolock(wbuf); + + if (err2) + err = err2; + } + mutex_unlock(&wbuf->io_mutex); + if (err < 0) { + ubifs_err(c, "GC failed, error %d", err); + if (err == -EAGAIN) + err = -EINVAL; + return err; + } + + ubifs_assert(c, err == LEB_RETAINED); + if (err != LEB_RETAINED) + return -EINVAL; + + err = ubifs_leb_unmap(c, c->gc_lnum); + if (err) + return err; + + dbg_rcvry("allocated LEB %d for GC", lp.lnum); + return 0; +} + +/** + * add_ino - add an entry to the size tree. + * @c: UBIFS file-system description object + * @inum: inode number + * @i_size: size on inode + * @d_size: maximum size based on data nodes + * @exists: indicates whether the inode exists + */ +static int add_ino(struct ubifs_info *c, ino_t inum, loff_t i_size, + loff_t d_size, int exists) +{ + struct rb_node **p = &c->size_tree.rb_node, *parent = NULL; + struct size_entry *e; + + while (*p) { + parent = *p; + e = rb_entry(parent, struct size_entry, rb); + if (inum < e->inum) + p = &(*p)->rb_left; + else + p = &(*p)->rb_right; + } + + e = kzalloc(sizeof(struct size_entry), GFP_KERNEL); + if (!e) + return -ENOMEM; + + e->inum = inum; + e->i_size = i_size; + e->d_size = d_size; + e->exists = exists; + + rb_link_node(&e->rb, parent, p); + rb_insert_color(&e->rb, &c->size_tree); + + return 0; +} + +/** + * find_ino - find an entry on the size tree. + * @c: UBIFS file-system description object + * @inum: inode number + */ +static struct size_entry *find_ino(struct ubifs_info *c, ino_t inum) +{ + struct rb_node *p = c->size_tree.rb_node; + struct size_entry *e; + + while (p) { + e = rb_entry(p, struct size_entry, rb); + if (inum < e->inum) + p = p->rb_left; + else if (inum > e->inum) + p = p->rb_right; + else + return e; + } + return NULL; +} + +/** + * remove_ino - remove an entry from the size tree. + * @c: UBIFS file-system description object + * @inum: inode number + */ +static void remove_ino(struct ubifs_info *c, ino_t inum) +{ + struct size_entry *e = find_ino(c, inum); + + if (!e) + return; + rb_erase(&e->rb, &c->size_tree); + kfree(e); +} + +/** + * ubifs_destroy_size_tree - free resources related to the size tree. + * @c: UBIFS file-system description object + */ +void ubifs_destroy_size_tree(struct ubifs_info *c) +{ + struct size_entry *e, *n; + + rbtree_postorder_for_each_entry_safe(e, n, &c->size_tree, rb) { + kfree(e); + } + + c->size_tree = RB_ROOT; +} + +/** + * ubifs_recover_size_accum - accumulate inode sizes for recovery. + * @c: UBIFS file-system description object + * @key: node key + * @deletion: node is for a deletion + * @new_size: inode size + * + * This function has two purposes: + * 1) to ensure there are no data nodes that fall outside the inode size + * 2) to ensure there are no data nodes for inodes that do not exist + * To accomplish those purposes, a rb-tree is constructed containing an entry + * for each inode number in the journal that has not been deleted, and recording + * the size from the inode node, the maximum size of any data node (also altered + * by truncations) and a flag indicating a inode number for which no inode node + * was present in the journal. + * + * Note that there is still the possibility that there are data nodes that have + * been committed that are beyond the inode size, however the only way to find + * them would be to scan the entire index. Alternatively, some provision could + * be made to record the size of inodes at the start of commit, which would seem + * very cumbersome for a scenario that is quite unlikely and the only negative + * consequence of which is wasted space. + * + * This functions returns %0 on success and a negative error code on failure. + */ +int ubifs_recover_size_accum(struct ubifs_info *c, union ubifs_key *key, + int deletion, loff_t new_size) +{ + ino_t inum = key_inum(c, key); + struct size_entry *e; + int err; + + switch (key_type(c, key)) { + case UBIFS_INO_KEY: + if (deletion) + remove_ino(c, inum); + else { + e = find_ino(c, inum); + if (e) { + e->i_size = new_size; + e->exists = 1; + } else { + err = add_ino(c, inum, new_size, 0, 1); + if (err) + return err; + } + } + break; + case UBIFS_DATA_KEY: + e = find_ino(c, inum); + if (e) { + if (new_size > e->d_size) + e->d_size = new_size; + } else { + err = add_ino(c, inum, 0, new_size, 0); + if (err) + return err; + } + break; + case UBIFS_TRUN_KEY: + e = find_ino(c, inum); + if (e) + e->d_size = new_size; + break; + } + return 0; +} + +/** + * fix_size_in_place - fix inode size in place on flash. + * @c: UBIFS file-system description object + * @e: inode size information for recovery + */ +static int fix_size_in_place(struct ubifs_info *c, struct size_entry *e) +{ + struct ubifs_ino_node *ino = c->sbuf; + unsigned char *p; + union ubifs_key key; + int err, lnum, offs, len; + loff_t i_size; + uint32_t crc; + + /* Locate the inode node LEB number and offset */ + ino_key_init(c, &key, e->inum); + err = ubifs_tnc_locate(c, &key, ino, &lnum, &offs); + if (err) { + unsigned int reason = get_failure_reason_callback(c); + + if (reason & FR_DATA_CORRUPTED) { + test_and_clear_failure_reason_callback(c, FR_DATA_CORRUPTED); + if (handle_failure_callback(c, FR_H_TNC_DATA_CORRUPTED, NULL)) { + /* Leave the inode to be deleted by subsequent steps */ + return 0; + } + } + goto out; + } + /* + * If the size recorded on the inode node is greater than the size that + * was calculated from nodes in the journal then don't change the inode. + */ + i_size = le64_to_cpu(ino->size); + if (i_size >= e->d_size) + return 0; + /* Read the LEB */ + err = ubifs_leb_read(c, lnum, c->sbuf, 0, c->leb_size, 1); + if (err && err != -EBADMSG) + goto out; + /* Change the size field and recalculate the CRC */ + ino = c->sbuf + offs; + ino->size = cpu_to_le64(e->d_size); + len = le32_to_cpu(ino->ch.len); + crc = crc32(UBIFS_CRC32_INIT, (void *)ino + 8, len - 8); + ino->ch.crc = cpu_to_le32(crc); + /* Work out where data in the LEB ends and free space begins */ + p = c->sbuf; + len = c->leb_size - 1; + while (p[len] == 0xff) + len -= 1; + len = ALIGN(len + 1, c->min_io_size); + /* Atomically write the fixed LEB back again */ + err = ubifs_leb_change(c, lnum, c->sbuf, len); + if (err) + goto out; + dbg_rcvry("inode %lu at %d:%d size %lld -> %lld", + (unsigned long)e->inum, lnum, offs, (long long)i_size, + (long long)e->d_size); + return 0; + +out: + ubifs_warn(c, "inode %lu failed to fix size %lld -> %lld error %d", + (unsigned long)e->inum, (long long)e->i_size, + (long long)e->d_size, err); + return err; +} + +/** + * inode_fix_size - fix inode size + * @c: UBIFS file-system description object + * @e: inode size information for recovery + */ +static int inode_fix_size(struct ubifs_info *c, __unused struct size_entry *e) +{ + /* Don't remove entry, keep it in the size tree. */ + /* Remove this assertion after supporting authentication. */ + ubifs_assert(c, c->ro_mount); + return 0; +} + +/** + * ubifs_recover_size - recover inode size. + * @c: UBIFS file-system description object + * @in_place: If true, do a in-place size fixup + * + * This function attempts to fix inode size discrepancies identified by the + * 'ubifs_recover_size_accum()' function. + * + * This functions returns %0 on success and a negative error code on failure. + */ +int ubifs_recover_size(struct ubifs_info *c, bool in_place) +{ + struct rb_node *this = rb_first(&c->size_tree); + + while (this) { + struct size_entry *e; + int err; + + e = rb_entry(this, struct size_entry, rb); + + this = rb_next(this); + + if (!e->exists) { + union ubifs_key key; + + ino_key_init(c, &key, e->inum); + err = ubifs_tnc_lookup(c, &key, c->sbuf); + if (err && err != -ENOENT) { + unsigned int reason; + + reason = get_failure_reason_callback(c); + if (reason & FR_DATA_CORRUPTED) { + test_and_clear_failure_reason_callback(c, FR_DATA_CORRUPTED); + if (handle_failure_callback(c, FR_H_TNC_DATA_CORRUPTED, NULL)) { + /* Leave the inode to be deleted by subsequent steps */ + goto delete_entry; + } + } + return err; + } + if (err == -ENOENT) { + /* Remove data nodes that have no inode */ + dbg_rcvry("removing ino %lu", + (unsigned long)e->inum); + err = ubifs_tnc_remove_ino(c, e->inum); + if (err) + return err; + } else { + struct ubifs_ino_node *ino = c->sbuf; + + e->exists = 1; + e->i_size = le64_to_cpu(ino->size); + } + } + + if (e->exists && e->i_size < e->d_size) { + ubifs_assert(c, !(c->ro_mount && in_place)); + + /* + * We found data that is outside the found inode size, + * fixup the inode size + */ + + if (in_place) { + err = fix_size_in_place(c, e); + if (err) + return err; + } else { + err = inode_fix_size(c, e); + if (err) + return err; + continue; + } + } + +delete_entry: + rb_erase(&e->rb, &c->size_tree); + kfree(e); + } + + return 0; +} diff --git a/ubifs-utils/libubifs/replay.c b/ubifs-utils/libubifs/replay.c new file mode 100644 index 0000000..9d61133 --- /dev/null +++ b/ubifs-utils/libubifs/replay.c @@ -0,0 +1,1230 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * This file is part of UBIFS. + * + * Copyright (C) 2006-2008 Nokia Corporation. + * + * Authors: Adrian Hunter + * Artem Bityutskiy (Битюцкий Артём) + */ + +/* + * This file contains journal replay code. It runs when the file-system is being + * mounted and requires no locking. + * + * The larger is the journal, the longer it takes to scan it, so the longer it + * takes to mount UBIFS. This is why the journal has limited size which may be + * changed depending on the system requirements. But a larger journal gives + * faster I/O speed because it writes the index less frequently. So this is a + * trade-off. Also, the journal is indexed by the in-memory index (TNC), so the + * larger is the journal, the more memory its index may consume. + */ + +#include "linux_err.h" +#include "bitops.h" +#include "kmem.h" +#include "ubifs.h" +#include "defs.h" +#include "debug.h" +#include "key.h" +#include "misc.h" + +/** + * struct replay_entry - replay list entry. + * @lnum: logical eraseblock number of the node + * @offs: node offset + * @len: node length + * @hash: node hash + * @deletion: non-zero if this entry corresponds to a node deletion + * @sqnum: node sequence number + * @list: links the replay list + * @key: node key + * @nm: directory entry name + * @old_size: truncation old size + * @new_size: truncation new size + * + * The replay process first scans all buds and builds the replay list, then + * sorts the replay list in nodes sequence number order, and then inserts all + * the replay entries to the TNC. + */ +struct replay_entry { + int lnum; + int offs; + int len; + u8 hash[UBIFS_HASH_ARR_SZ]; + unsigned int deletion:1; + unsigned long long sqnum; + struct list_head list; + union ubifs_key key; + union { + struct fscrypt_name nm; + struct { + loff_t old_size; + loff_t new_size; + }; + }; +}; + +/** + * struct bud_entry - entry in the list of buds to replay. + * @list: next bud in the list + * @bud: bud description object + * @sqnum: reference node sequence number + * @free: free bytes in the bud + * @dirty: dirty bytes in the bud + */ +struct bud_entry { + struct list_head list; + struct ubifs_bud *bud; + unsigned long long sqnum; + int free; + int dirty; +}; + +/** + * set_bud_lprops - set free and dirty space used by a bud. + * @c: UBIFS file-system description object + * @b: bud entry which describes the bud + * + * This function makes sure the LEB properties of bud @b are set correctly + * after the replay. Returns zero in case of success and a negative error code + * in case of failure. + */ +static int set_bud_lprops(struct ubifs_info *c, struct bud_entry *b) +{ + const struct ubifs_lprops *lp; + int err = 0, dirty; + + if (!test_lpt_valid_callback(c, b->bud->lnum, LPROPS_NC, LPROPS_NC, + LPROPS_NC, LPROPS_NC)) + return 0; + + ubifs_get_lprops(c); + + lp = ubifs_lpt_lookup_dirty(c, b->bud->lnum); + if (IS_ERR(lp)) { + err = PTR_ERR(lp); + if (test_and_clear_failure_reason_callback(c, FR_LPT_CORRUPTED) && + can_ignore_failure_callback(c, FR_LPT_CORRUPTED)) + err = 0; + goto out; + } + + dirty = lp->dirty; + if (b->bud->start == 0 && (lp->free != c->leb_size || lp->dirty != 0)) { + /* + * The LEB was added to the journal with a starting offset of + * zero which means the LEB must have been empty. The LEB + * property values should be @lp->free == @c->leb_size and + * @lp->dirty == 0, but that is not the case. The reason is that + * the LEB had been garbage collected before it became the bud, + * and there was no commit in between. The garbage collector + * resets the free and dirty space without recording it + * anywhere except lprops, so if there was no commit then + * lprops does not have that information. + * + * We do not need to adjust free space because the scan has told + * us the exact value which is recorded in the replay entry as + * @b->free. + * + * However we do need to subtract from the dirty space the + * amount of space that the garbage collector reclaimed, which + * is the whole LEB minus the amount of space that was free. + */ + dbg_mnt("bud LEB %d was GC'd (%d free, %d dirty)", b->bud->lnum, + lp->free, lp->dirty); + dbg_gc("bud LEB %d was GC'd (%d free, %d dirty)", b->bud->lnum, + lp->free, lp->dirty); + dirty -= c->leb_size - lp->free; + /* + * If the replay order was perfect the dirty space would now be + * zero. The order is not perfect because the journal heads + * race with each other. This is not a problem but is does mean + * that the dirty space may temporarily exceed c->leb_size + * during the replay. + */ + if (dirty != 0) + dbg_mnt("LEB %d lp: %d free %d dirty replay: %d free %d dirty", + b->bud->lnum, lp->free, lp->dirty, b->free, + b->dirty); + } + if (!test_lpt_valid_callback(c, b->bud->lnum, lp->free, lp->dirty, + b->free, dirty + b->dirty)) + goto out; + + lp = ubifs_change_lp(c, lp, b->free, dirty + b->dirty, + lp->flags | LPROPS_TAKEN, 0); + if (IS_ERR(lp)) { + err = PTR_ERR(lp); + goto out; + } + + /* Make sure the journal head points to the latest bud */ + err = ubifs_wbuf_seek_nolock(&c->jheads[b->bud->jhead].wbuf, + b->bud->lnum, c->leb_size - b->free); + +out: + ubifs_release_lprops(c); + return err; +} + +/** + * set_buds_lprops - set free and dirty space for all replayed buds. + * @c: UBIFS file-system description object + * + * This function sets LEB properties for all replayed buds. Returns zero in + * case of success and a negative error code in case of failure. + */ +static int set_buds_lprops(struct ubifs_info *c) +{ + struct bud_entry *b; + int err; + + list_for_each_entry(b, &c->replay_buds, list) { + err = set_bud_lprops(c, b); + if (err) + return err; + } + + return 0; +} + +/** + * trun_remove_range - apply a replay entry for a truncation to the TNC. + * @c: UBIFS file-system description object + * @r: replay entry of truncation + */ +static int trun_remove_range(struct ubifs_info *c, struct replay_entry *r) +{ + unsigned min_blk, max_blk; + union ubifs_key min_key, max_key; + ino_t ino; + + min_blk = r->new_size / UBIFS_BLOCK_SIZE; + if (r->new_size & (UBIFS_BLOCK_SIZE - 1)) + min_blk += 1; + + max_blk = r->old_size / UBIFS_BLOCK_SIZE; + if ((r->old_size & (UBIFS_BLOCK_SIZE - 1)) == 0) + max_blk -= 1; + + ino = key_inum(c, &r->key); + + data_key_init(c, &min_key, ino, min_blk); + data_key_init(c, &max_key, ino, max_blk); + + return ubifs_tnc_remove_range(c, &min_key, &max_key); +} + +/** + * inode_still_linked - check whether inode in question will be re-linked. + * @c: UBIFS file-system description object + * @rino: replay entry to test + * + * O_TMPFILE files can be re-linked, this means link count goes from 0 to 1. + * This case needs special care, otherwise all references to the inode will + * be removed upon the first replay entry of an inode with link count 0 + * is found. + */ +static bool inode_still_linked(struct ubifs_info *c, struct replay_entry *rino) +{ + struct replay_entry *r; + + ubifs_assert(c, rino->deletion); + ubifs_assert(c, key_type(c, &rino->key) == UBIFS_INO_KEY); + + /* + * Find the most recent entry for the inode behind @rino and check + * whether it is a deletion. + */ + list_for_each_entry_reverse(r, &c->replay_list, list) { + ubifs_assert(c, r->sqnum >= rino->sqnum); + if (key_inum(c, &r->key) == key_inum(c, &rino->key) && + key_type(c, &r->key) == UBIFS_INO_KEY) + return r->deletion == 0; + + } + + ubifs_assert(c, 0); + return false; +} + +/** + * apply_replay_entry - apply a replay entry to the TNC. + * @c: UBIFS file-system description object + * @r: replay entry to apply + * + * Apply a replay entry to the TNC. + */ +static int apply_replay_entry(struct ubifs_info *c, struct replay_entry *r) +{ + int err; + + dbg_mntk(&r->key, "LEB %d:%d len %d deletion %d sqnum %llu key ", + r->lnum, r->offs, r->len, r->deletion, r->sqnum); + + if (is_hash_key(c, &r->key)) { + if (r->deletion) + err = ubifs_tnc_remove_nm(c, &r->key, &r->nm); + else + err = ubifs_tnc_add_nm(c, &r->key, r->lnum, r->offs, + r->len, r->hash, &r->nm); + } else { + if (r->deletion) + switch (key_type(c, &r->key)) { + case UBIFS_INO_KEY: + { + ino_t inum = key_inum(c, &r->key); + + if (inode_still_linked(c, r)) { + err = 0; + break; + } + + err = ubifs_tnc_remove_ino(c, inum); + break; + } + case UBIFS_TRUN_KEY: + err = trun_remove_range(c, r); + break; + default: + err = ubifs_tnc_remove(c, &r->key); + break; + } + else + err = ubifs_tnc_add(c, &r->key, r->lnum, r->offs, + r->len, r->hash); + if (err) + return err; + + if (c->need_recovery) + err = ubifs_recover_size_accum(c, &r->key, r->deletion, + r->new_size); + } + + return err; +} + +/** + * replay_entries_cmp - compare 2 replay entries. + * @priv: UBIFS file-system description object + * @a: first replay entry + * @b: second replay entry + * + * This is a comparios function for 'list_sort()' which compares 2 replay + * entries @a and @b by comparing their sequence number. Returns %1 if @a has + * greater sequence number and %-1 otherwise. + */ +static int replay_entries_cmp(void *priv, const struct list_head *a, + const struct list_head *b) +{ + struct ubifs_info *c = priv; + struct replay_entry *ra, *rb; + + cond_resched(); + if (a == b) + return 0; + + ra = list_entry(a, struct replay_entry, list); + rb = list_entry(b, struct replay_entry, list); + ubifs_assert(c, ra->sqnum != rb->sqnum); + if (ra->sqnum > rb->sqnum) + return 1; + return -1; +} + +/** + * apply_replay_list - apply the replay list to the TNC. + * @c: UBIFS file-system description object + * + * Apply all entries in the replay list to the TNC. Returns zero in case of + * success and a negative error code in case of failure. + */ +static int apply_replay_list(struct ubifs_info *c) +{ + struct replay_entry *r; + int err; + + list_sort(c, &c->replay_list, &replay_entries_cmp); + + list_for_each_entry(r, &c->replay_list, list) { + cond_resched(); + + err = apply_replay_entry(c, r); + if (err) + return err; + } + + return 0; +} + +/** + * destroy_replay_list - destroy the replay. + * @c: UBIFS file-system description object + * + * Destroy the replay list. + */ +static void destroy_replay_list(struct ubifs_info *c) +{ + struct replay_entry *r, *tmp; + + list_for_each_entry_safe(r, tmp, &c->replay_list, list) { + if (is_hash_key(c, &r->key)) + kfree(fname_name(&r->nm)); + list_del(&r->list); + kfree(r); + } +} + +/** + * insert_node - insert a node to the replay list + * @c: UBIFS file-system description object + * @lnum: node logical eraseblock number + * @offs: node offset + * @len: node length + * @key: node key + * @sqnum: sequence number + * @deletion: non-zero if this is a deletion + * @used: number of bytes in use in a LEB + * @old_size: truncation old size + * @new_size: truncation new size + * + * This function inserts a scanned non-direntry node to the replay list. The + * replay list contains @struct replay_entry elements, and we sort this list in + * sequence number order before applying it. The replay list is applied at the + * very end of the replay process. Since the list is sorted in sequence number + * order, the older modifications are applied first. This function returns zero + * in case of success and a negative error code in case of failure. + */ +static int insert_node(struct ubifs_info *c, int lnum, int offs, int len, + const u8 *hash, union ubifs_key *key, + unsigned long long sqnum, int deletion, int *used, + loff_t old_size, loff_t new_size) +{ + struct replay_entry *r; + + dbg_mntk(key, "add LEB %d:%d, key ", lnum, offs); + + if (key_inum(c, key) >= c->highest_inum) + c->highest_inum = key_inum(c, key); + + r = kzalloc(sizeof(struct replay_entry), GFP_KERNEL); + if (!r) + return -ENOMEM; + + if (!deletion) + *used += ALIGN(len, 8); + r->lnum = lnum; + r->offs = offs; + r->len = len; + ubifs_copy_hash(c, hash, r->hash); + r->deletion = !!deletion; + r->sqnum = sqnum; + key_copy(c, key, &r->key); + r->old_size = old_size; + r->new_size = new_size; + + list_add_tail(&r->list, &c->replay_list); + return 0; +} + +/** + * insert_dent - insert a directory entry node into the replay list. + * @c: UBIFS file-system description object + * @lnum: node logical eraseblock number + * @offs: node offset + * @len: node length + * @key: node key + * @name: directory entry name + * @nlen: directory entry name length + * @sqnum: sequence number + * @deletion: non-zero if this is a deletion + * @used: number of bytes in use in a LEB + * + * This function inserts a scanned directory entry node or an extended + * attribute entry to the replay list. Returns zero in case of success and a + * negative error code in case of failure. + */ +static int insert_dent(struct ubifs_info *c, int lnum, int offs, int len, + const u8 *hash, union ubifs_key *key, + const char *name, int nlen, unsigned long long sqnum, + int deletion, int *used) +{ + struct replay_entry *r; + char *nbuf; + + dbg_mntk(key, "add LEB %d:%d, key ", lnum, offs); + if (key_inum(c, key) >= c->highest_inum) + c->highest_inum = key_inum(c, key); + + r = kzalloc(sizeof(struct replay_entry), GFP_KERNEL); + if (!r) + return -ENOMEM; + + nbuf = kmalloc(nlen + 1, GFP_KERNEL); + if (!nbuf) { + kfree(r); + return -ENOMEM; + } + + if (!deletion) + *used += ALIGN(len, 8); + r->lnum = lnum; + r->offs = offs; + r->len = len; + ubifs_copy_hash(c, hash, r->hash); + r->deletion = !!deletion; + r->sqnum = sqnum; + key_copy(c, key, &r->key); + fname_len(&r->nm) = nlen; + memcpy(nbuf, name, nlen); + nbuf[nlen] = '\0'; + fname_name(&r->nm) = nbuf; + + list_add_tail(&r->list, &c->replay_list); + return 0; +} + +/** + * ubifs_validate_entry - validate directory or extended attribute entry node. + * @c: UBIFS file-system description object + * @dent: the node to validate + * + * This function validates directory or extended attribute entry node @dent. + * Returns zero if the node is all right and a %-EINVAL if not. + */ +int ubifs_validate_entry(struct ubifs_info *c, + const struct ubifs_dent_node *dent) +{ + int key_type = key_type_flash(c, dent->key); + int nlen = le16_to_cpu(dent->nlen); + + if (le32_to_cpu(dent->ch.len) != nlen + UBIFS_DENT_NODE_SZ + 1 || + dent->type >= UBIFS_ITYPES_CNT || + nlen > UBIFS_MAX_NLEN || dent->name[nlen] != 0 || + (key_type == UBIFS_XENT_KEY && + strnlen((const char *)dent->name, nlen) != nlen) || + le64_to_cpu(dent->inum) > MAX_INUM) { + ubifs_err(c, "bad %s node", key_type == UBIFS_DENT_KEY ? + "directory entry" : "extended attribute entry"); + return -EINVAL; + } + + if (key_type != UBIFS_DENT_KEY && key_type != UBIFS_XENT_KEY) { + ubifs_err(c, "bad key type %d", key_type); + return -EINVAL; + } + + return 0; +} + +/** + * is_last_bud - check if the bud is the last in the journal head. + * @c: UBIFS file-system description object + * @bud: bud description object + * + * This function checks if bud @bud is the last bud in its journal head. This + * information is then used by 'replay_bud()' to decide whether the bud can + * have corruptions or not. Indeed, only last buds can be corrupted by power + * cuts. Returns %1 if this is the last bud, and %0 if not. + */ +static int is_last_bud(struct ubifs_info *c, struct ubifs_bud *bud) +{ + struct ubifs_jhead *jh = &c->jheads[bud->jhead]; + struct ubifs_bud *next; + uint32_t data; + int err; + + if (list_is_last(&bud->list, &jh->buds_list)) + return 1; + + /* + * The following is a quirk to make sure we work correctly with UBIFS + * images used with older UBIFS. + * + * Normally, the last bud will be the last in the journal head's list + * of bud. However, there is one exception if the UBIFS image belongs + * to older UBIFS. This is fairly unlikely: one would need to use old + * UBIFS, then have a power cut exactly at the right point, and then + * try to mount this image with new UBIFS. + * + * The exception is: it is possible to have 2 buds A and B, A goes + * before B, and B is the last, bud B is contains no data, and bud A is + * corrupted at the end. The reason is that in older versions when the + * journal code switched the next bud (from A to B), it first added a + * log reference node for the new bud (B), and only after this it + * synchronized the write-buffer of current bud (A). But later this was + * changed and UBIFS started to always synchronize the write-buffer of + * the bud (A) before writing the log reference for the new bud (B). + * + * But because older UBIFS always synchronized A's write-buffer before + * writing to B, we can recognize this exceptional situation but + * checking the contents of bud B - if it is empty, then A can be + * treated as the last and we can recover it. + * + * TODO: remove this piece of code in a couple of years (today it is + * 16.05.2011). + */ + next = list_entry(bud->list.next, struct ubifs_bud, list); + if (!list_is_last(&next->list, &jh->buds_list)) + return 0; + + err = ubifs_leb_read(c, next->lnum, (char *)&data, next->start, 4, 1); + if (err) + return 0; + + return data == 0xFFFFFFFF; +} + +/** + * authenticate_sleb - authenticate one scan LEB + * @c: UBIFS file-system description object + * @sleb: the scan LEB to authenticate + * @log_hash: + * @is_last: if true, this is the last LEB + * + * This function iterates over the buds of a single LEB authenticating all buds + * with the authentication nodes on this LEB. Authentication nodes are written + * after some buds and contain a HMAC covering the authentication node itself + * and the buds between the last authentication node and the current + * authentication node. It can happen that the last buds cannot be authenticated + * because a powercut happened when some nodes were written but not the + * corresponding authentication node. This function returns the number of nodes + * that could be authenticated or a negative error code. + */ +static int authenticate_sleb(struct ubifs_info *c, struct ubifs_scan_leb *sleb, + __unused struct shash_desc *log_hash, + __unused int is_last) +{ + if (!ubifs_authenticated(c)) + return sleb->nodes_cnt; + + // To be implemented + return -EINVAL; +} + +/** + * replay_bud - replay a bud logical eraseblock. + * @c: UBIFS file-system description object + * @b: bud entry which describes the bud + * + * This function replays bud @bud, recovers it if needed, and adds all nodes + * from this bud to the replay list. Returns zero in case of success and a + * negative error code in case of failure. + */ +static int replay_bud(struct ubifs_info *c, struct bud_entry *b) +{ + int is_last = is_last_bud(c, b->bud); + int err = 0, used = 0, lnum = b->bud->lnum, offs = b->bud->start; + int n_nodes, n = 0; + struct ubifs_scan_leb *sleb; + struct ubifs_scan_node *snod; + + dbg_mnt("replay bud LEB %d, head %d, offs %d, is_last %d", + lnum, b->bud->jhead, offs, is_last); + + if (c->need_recovery && is_last) + /* + * Recover only last LEBs in the journal heads, because power + * cuts may cause corruptions only in these LEBs, because only + * these LEBs could possibly be written to at the power cut + * time. + */ + sleb = ubifs_recover_leb(c, lnum, offs, c->sbuf, b->bud->jhead); + else + sleb = ubifs_scan(c, lnum, offs, c->sbuf, 0); + if (IS_ERR(sleb)) + return PTR_ERR(sleb); + + n_nodes = authenticate_sleb(c, sleb, b->bud->log_hash, is_last); + if (n_nodes < 0) { + err = n_nodes; + goto out; + } + + ubifs_shash_copy_state(c, b->bud->log_hash, + c->jheads[b->bud->jhead].log_hash); + + /* + * The bud does not have to start from offset zero - the beginning of + * the 'lnum' LEB may contain previously committed data. One of the + * things we have to do in replay is to correctly update lprops with + * newer information about this LEB. + * + * At this point lprops thinks that this LEB has 'c->leb_size - offs' + * bytes of free space because it only contain information about + * committed data. + * + * But we know that real amount of free space is 'c->leb_size - + * sleb->endpt', and the space in the 'lnum' LEB between 'offs' and + * 'sleb->endpt' is used by bud data. We have to correctly calculate + * how much of these data are dirty and update lprops with this + * information. + * + * The dirt in that LEB region is comprised of padding nodes, deletion + * nodes, truncation nodes and nodes which are obsoleted by subsequent + * nodes in this LEB. So instead of calculating clean space, we + * calculate used space ('used' variable). + */ + + list_for_each_entry(snod, &sleb->nodes, list) { + u8 hash[UBIFS_HASH_ARR_SZ]; + int deletion = 0; + + cond_resched(); + + if (snod->sqnum >= SQNUM_WATERMARK) { + ubifs_err(c, "file system's life ended"); + goto out_dump; + } + + ubifs_node_calc_hash(c, snod->node, hash); + + if (snod->sqnum > c->max_sqnum) + c->max_sqnum = snod->sqnum; + + switch (snod->type) { + case UBIFS_INO_NODE: + { + struct ubifs_ino_node *ino = snod->node; + loff_t new_size = le64_to_cpu(ino->size); + + if (le32_to_cpu(ino->nlink) == 0) + deletion = 1; + err = insert_node(c, lnum, snod->offs, snod->len, hash, + &snod->key, snod->sqnum, deletion, + &used, 0, new_size); + break; + } + case UBIFS_DATA_NODE: + { + struct ubifs_data_node *dn = snod->node; + loff_t new_size = le32_to_cpu(dn->size) + + key_block(c, &snod->key) * + UBIFS_BLOCK_SIZE; + + err = insert_node(c, lnum, snod->offs, snod->len, hash, + &snod->key, snod->sqnum, deletion, + &used, 0, new_size); + break; + } + case UBIFS_DENT_NODE: + case UBIFS_XENT_NODE: + { + struct ubifs_dent_node *dent = snod->node; + + err = ubifs_validate_entry(c, dent); + if (err) + goto out_dump; + + err = insert_dent(c, lnum, snod->offs, snod->len, hash, + &snod->key, (const char *)dent->name, + le16_to_cpu(dent->nlen), snod->sqnum, + !le64_to_cpu(dent->inum), &used); + break; + } + case UBIFS_TRUN_NODE: + { + struct ubifs_trun_node *trun = snod->node; + loff_t old_size = le64_to_cpu(trun->old_size); + loff_t new_size = le64_to_cpu(trun->new_size); + union ubifs_key key; + + /* Validate truncation node */ + if (old_size < 0 || old_size > c->max_inode_sz || + new_size < 0 || new_size > c->max_inode_sz || + old_size <= new_size) { + ubifs_err(c, "bad truncation node"); + goto out_dump; + } + + /* + * Create a fake truncation key just to use the same + * functions which expect nodes to have keys. + */ + trun_key_init(c, &key, le32_to_cpu(trun->inum)); + err = insert_node(c, lnum, snod->offs, snod->len, hash, + &key, snod->sqnum, 1, &used, + old_size, new_size); + break; + } + case UBIFS_AUTH_NODE: + break; + default: + ubifs_err(c, "unexpected node type %d in bud LEB %d:%d", + snod->type, lnum, snod->offs); + err = -EINVAL; + goto out_dump; + } + if (err) + goto out; + + n++; + if (n == n_nodes) + break; + } + + ubifs_assert(c, ubifs_search_bud(c, lnum)); + ubifs_assert(c, sleb->endpt - offs >= used); + ubifs_assert(c, sleb->endpt % c->min_io_size == 0); + + b->dirty = sleb->endpt - offs - used; + b->free = c->leb_size - sleb->endpt; + dbg_mnt("bud LEB %d replied: dirty %d, free %d", + lnum, b->dirty, b->free); + +out: + ubifs_scan_destroy(sleb); + return err; + +out_dump: + set_failure_reason_callback(c, FR_DATA_CORRUPTED); + ubifs_err(c, "bad node is at LEB %d:%d", lnum, snod->offs); + ubifs_dump_node(c, snod->node, c->leb_size - snod->offs); + ubifs_scan_destroy(sleb); + return -EINVAL; +} + +/** + * replay_buds - replay all buds. + * @c: UBIFS file-system description object + * + * This function returns zero in case of success and a negative error code in + * case of failure. + */ +static int replay_buds(struct ubifs_info *c) +{ + struct bud_entry *b, *tmp_b; + int err; + unsigned long long prev_sqnum = 0; + + list_for_each_entry_safe(b, tmp_b, &c->replay_buds, list) { + err = replay_bud(c, b); + if (err) { + if (test_and_clear_failure_reason_callback(c, FR_DATA_CORRUPTED) && + handle_failure_callback(c, FR_H_BUD_CORRUPTED, b->bud)) { + /* Set %FR_LPT_INCORRECT for lpt status. */ + set_lpt_invalid_callback(c, FR_LPT_INCORRECT); + /* Skip replaying the bud LEB. */ + list_del(&b->list); + kfree(b); + continue; + } + return err; + } + + ubifs_assert(c, b->sqnum > prev_sqnum); + prev_sqnum = b->sqnum; + } + + return 0; +} + +/** + * destroy_bud_list - destroy the list of buds to replay. + * @c: UBIFS file-system description object + */ +static void destroy_bud_list(struct ubifs_info *c) +{ + struct bud_entry *b; + + while (!list_empty(&c->replay_buds)) { + b = list_entry(c->replay_buds.next, struct bud_entry, list); + list_del(&b->list); + kfree(b); + } +} + +/** + * add_replay_bud - add a bud to the list of buds to replay. + * @c: UBIFS file-system description object + * @lnum: bud logical eraseblock number to replay + * @offs: bud start offset + * @jhead: journal head to which this bud belongs + * @sqnum: reference node sequence number + * + * This function returns zero in case of success and a negative error code in + * case of failure. + */ +static int add_replay_bud(struct ubifs_info *c, int lnum, int offs, int jhead, + unsigned long long sqnum) +{ + struct ubifs_bud *bud; + struct bud_entry *b; + int err; + + dbg_mnt("add replay bud LEB %d:%d, head %d", lnum, offs, jhead); + + bud = kmalloc(sizeof(struct ubifs_bud), GFP_KERNEL); + if (!bud) + return -ENOMEM; + + b = kmalloc(sizeof(struct bud_entry), GFP_KERNEL); + if (!b) { + err = -ENOMEM; + goto out; + } + + bud->lnum = lnum; + bud->start = offs; + bud->jhead = jhead; + bud->log_hash = ubifs_hash_get_desc(c); + if (IS_ERR(bud->log_hash)) { + err = PTR_ERR(bud->log_hash); + goto out; + } + + ubifs_shash_copy_state(c, c->log_hash, bud->log_hash); + + ubifs_add_bud(c, bud); + + b->bud = bud; + b->sqnum = sqnum; + list_add_tail(&b->list, &c->replay_buds); + + return 0; +out: + kfree(bud); + kfree(b); + + return err; +} + +/** + * validate_ref - validate a reference node. + * @c: UBIFS file-system description object + * @ref: the reference node to validate + * + * This function returns %1 if a bud reference already exists for the LEB. %0 is + * returned if the reference node is new, otherwise %-EINVAL is returned if + * validation failed. + */ +static int validate_ref(struct ubifs_info *c, const struct ubifs_ref_node *ref) +{ + struct ubifs_bud *bud; + int lnum = le32_to_cpu(ref->lnum); + unsigned int offs = le32_to_cpu(ref->offs); + unsigned int jhead = le32_to_cpu(ref->jhead); + + /* + * ref->offs may point to the end of LEB when the journal head points + * to the end of LEB and we write reference node for it during commit. + * So this is why we require 'offs > c->leb_size'. + */ + if (jhead >= c->jhead_cnt || lnum >= c->leb_cnt || + lnum < c->main_first || offs > c->leb_size || + offs & (c->min_io_size - 1)) + return -EINVAL; + + /* Make sure we have not already looked at this bud */ + bud = ubifs_search_bud(c, lnum); + if (bud) { + if (bud->jhead == jhead && bud->start <= offs) + return 1; + ubifs_err(c, "bud at LEB %d:%d was already referred", lnum, offs); + return -EINVAL; + } + + return 0; +} + +/** + * replay_log_leb - replay a log logical eraseblock. + * @c: UBIFS file-system description object + * @lnum: log logical eraseblock to replay + * @offs: offset to start replaying from + * @sbuf: scan buffer + * + * This function replays a log LEB and returns zero in case of success, %1 if + * this is the last LEB in the log, and a negative error code in case of + * failure. + */ +static int replay_log_leb(struct ubifs_info *c, int lnum, int offs, void *sbuf) +{ + int err; + struct ubifs_scan_leb *sleb; + struct ubifs_scan_node *snod; + const struct ubifs_cs_node *node; + + dbg_mnt("replay log LEB %d:%d", lnum, offs); + sleb = ubifs_scan(c, lnum, offs, sbuf, c->need_recovery); + if (IS_ERR(sleb)) { + if (PTR_ERR(sleb) != -EUCLEAN || !c->need_recovery) + return PTR_ERR(sleb); + clear_failure_reason_callback(c); + /* + * Note, the below function will recover this log LEB only if + * it is the last, because unclean reboots can possibly corrupt + * only the tail of the log. + */ + sleb = ubifs_recover_log_leb(c, lnum, offs, sbuf); + if (IS_ERR(sleb)) + return PTR_ERR(sleb); + } + + if (sleb->nodes_cnt == 0) { + err = 1; + goto out; + } + + node = sleb->buf; + snod = list_entry(sleb->nodes.next, struct ubifs_scan_node, list); + if (c->cs_sqnum == 0) { + /* + * This is the first log LEB we are looking at, make sure that + * the first node is a commit start node. Also record its + * sequence number so that UBIFS can determine where the log + * ends, because all nodes which were have higher sequence + * numbers. + */ + if (snod->type != UBIFS_CS_NODE) { + ubifs_err(c, "first log node at LEB %d:%d is not CS node", + lnum, offs); + goto out_dump; + } + if (le64_to_cpu(node->cmt_no) != c->cmt_no) { + ubifs_err(c, "first CS node at LEB %d:%d has wrong commit number %llu expected %llu", + lnum, offs, + (unsigned long long)le64_to_cpu(node->cmt_no), + c->cmt_no); + goto out_dump; + } + + c->cs_sqnum = le64_to_cpu(node->ch.sqnum); + dbg_mnt("commit start sqnum %llu", c->cs_sqnum); + + err = ubifs_shash_init(c, c->log_hash); + if (err) + goto out; + + err = ubifs_shash_update(c, c->log_hash, node, UBIFS_CS_NODE_SZ); + if (err < 0) + goto out; + } + + if (snod->sqnum < c->cs_sqnum) { + /* + * This means that we reached end of log and now + * look to the older log data, which was already + * committed but the eraseblock was not erased (UBIFS + * only un-maps it). So this basically means we have to + * exit with "end of log" code. + */ + err = 1; + goto out; + } + + /* Make sure the first node sits at offset zero of the LEB */ + if (snod->offs != 0) { + ubifs_err(c, "first node is not at zero offset"); + goto out_dump; + } + + list_for_each_entry(snod, &sleb->nodes, list) { + cond_resched(); + + if (snod->sqnum >= SQNUM_WATERMARK) { + ubifs_err(c, "file system's life ended"); + goto out_dump; + } + + if (snod->sqnum < c->cs_sqnum) { + ubifs_err(c, "bad sqnum %llu, commit sqnum %llu", + snod->sqnum, c->cs_sqnum); + goto out_dump; + } + + if (snod->sqnum > c->max_sqnum) + c->max_sqnum = snod->sqnum; + + switch (snod->type) { + case UBIFS_REF_NODE: { + const struct ubifs_ref_node *ref = snod->node; + + err = validate_ref(c, ref); + if (err == 1) + break; /* Already have this bud */ + if (err) + goto out_dump; + + err = ubifs_shash_update(c, c->log_hash, ref, + UBIFS_REF_NODE_SZ); + if (err) + goto out; + + err = add_replay_bud(c, le32_to_cpu(ref->lnum), + le32_to_cpu(ref->offs), + le32_to_cpu(ref->jhead), + snod->sqnum); + if (err) + goto out; + + break; + } + case UBIFS_CS_NODE: + /* Make sure it sits at the beginning of LEB */ + if (snod->offs != 0) { + ubifs_err(c, "unexpected node in log"); + goto out_dump; + } + break; + default: + ubifs_err(c, "unexpected node in log"); + goto out_dump; + } + } + + if (sleb->endpt || c->lhead_offs >= c->leb_size) { + c->lhead_lnum = lnum; + c->lhead_offs = sleb->endpt; + } + + err = !sleb->endpt; +out: + ubifs_scan_destroy(sleb); + return err; + +out_dump: + set_failure_reason_callback(c, FR_DATA_CORRUPTED); + ubifs_err(c, "log error detected while replaying the log at LEB %d:%d", + lnum, offs + snod->offs); + ubifs_dump_node(c, snod->node, c->leb_size - snod->offs); + ubifs_scan_destroy(sleb); + return -EINVAL; +} + +/** + * take_ihead - update the status of the index head in lprops to 'taken'. + * @c: UBIFS file-system description object + * + * This function returns the amount of free space in the index head LEB or a + * negative error code. + */ +int take_ihead(struct ubifs_info *c) +{ + const struct ubifs_lprops *lp; + int err, free; + + ubifs_get_lprops(c); + + lp = ubifs_lpt_lookup_dirty(c, c->ihead_lnum); + if (IS_ERR(lp)) { + err = PTR_ERR(lp); + if (test_and_clear_failure_reason_callback(c, FR_LPT_CORRUPTED) && + can_ignore_failure_callback(c, FR_LPT_CORRUPTED)) + err = 0; + goto out; + } + + free = lp->free; + + if (!test_lpt_valid_callback(c, c->ihead_lnum, LPROPS_NC, LPROPS_NC, + LPROPS_NC, LPROPS_NC)) { + err = free; + goto out; + } + + lp = ubifs_change_lp(c, lp, LPROPS_NC, LPROPS_NC, + lp->flags | LPROPS_TAKEN, 0); + if (IS_ERR(lp)) { + err = PTR_ERR(lp); + goto out; + } + + err = free; +out: + ubifs_release_lprops(c); + return err; +} + +/** + * ubifs_replay_journal - replay journal. + * @c: UBIFS file-system description object + * + * This function scans the journal, replays and cleans it up. It makes sure all + * memory data structures related to uncommitted journal are built (dirty TNC + * tree, tree of buds, modified lprops, etc). + */ +int ubifs_replay_journal(struct ubifs_info *c) +{ + int err, lnum, free; + + BUILD_BUG_ON(UBIFS_TRUN_KEY > 5); + + /* Update the status of the index head in lprops to 'taken' */ + free = take_ihead(c); + if (free < 0) + return free; /* Error code */ + + if (c->program_type != FSCK_PROGRAM_TYPE) { + /* + * Skip index head checking for fsck, it is hard to check it + * caused by possible corrupted/incorrect lpt, tnc updating + * will report error code if index tree is really corrupted. + */ + if (c->ihead_offs != c->leb_size - free) { + ubifs_err(c, "bad index head LEB %d:%d", c->ihead_lnum, + c->ihead_offs); + return -EINVAL; + } + } + + dbg_mnt("start replaying the journal"); + c->replaying = 1; + lnum = c->ltail_lnum = c->lhead_lnum; + + do { + err = replay_log_leb(c, lnum, 0, c->sbuf); + if (err == 1) { + if (lnum != c->lhead_lnum) + /* We hit the end of the log */ + break; + + /* + * The head of the log must always start with the + * "commit start" node on a properly formatted UBIFS. + * But we found no nodes at all, which means that + * something went wrong and we cannot proceed mounting + * the file-system. + */ + set_failure_reason_callback(c, FR_DATA_CORRUPTED); + ubifs_err(c, "no UBIFS nodes found at the log head LEB %d:%d, possibly corrupted", + lnum, 0); + err = -EINVAL; + } + if (err) + goto out; + lnum = ubifs_next_log_lnum(c, lnum); + } while (lnum != c->ltail_lnum); + + err = replay_buds(c); + if (err) + goto out; + + err = apply_replay_list(c); + if (err) + goto out; + + err = set_buds_lprops(c); + if (err) + goto out; + + /* + * UBIFS budgeting calculations use @c->bi.uncommitted_idx variable + * to roughly estimate index growth. Things like @c->bi.min_idx_lebs + * depend on it. This means we have to initialize it to make sure + * budgeting works properly. + */ + c->bi.uncommitted_idx = atomic_long_read(&c->dirty_zn_cnt); + c->bi.uncommitted_idx *= c->max_idx_node_sz; + + ubifs_assert(c, c->bud_bytes <= c->max_bud_bytes || c->need_recovery); + dbg_mnt("finished, log head LEB %d:%d, max_sqnum %llu, highest_inum %lu", + c->lhead_lnum, c->lhead_offs, c->max_sqnum, + (unsigned long)c->highest_inum); +out: + destroy_replay_list(c); + destroy_bud_list(c); + c->replaying = 0; + return err; +} diff --git a/ubifs-utils/libubifs/sb.c b/ubifs-utils/libubifs/sb.c new file mode 100644 index 0000000..2147280 --- /dev/null +++ b/ubifs-utils/libubifs/sb.c @@ -0,0 +1,512 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * This file is part of UBIFS. + * + * Copyright (C) 2006-2008 Nokia Corporation. + * + * Authors: Artem Bityutskiy (Битюцкий Артём) + * Adrian Hunter + */ + +/* + * This file implements UBIFS superblock. The superblock is stored at the first + * LEB of the volume and is never changed by UBIFS. Only user-space tools may + * change it. The superblock node mostly contains geometry information. + */ + +#include "linux_err.h" +#include "bitops.h" +#include "kmem.h" +#include "ubifs.h" +#include "defs.h" +#include "debug.h" +#include "key.h" +#include "misc.h" + +/* Default number of LEB numbers in LPT's save table */ +#define DEFAULT_LSAVE_CNT 256 + +/** + * validate_sb - validate superblock node. + * @c: UBIFS file-system description object + * @sup: superblock node + * + * This function validates superblock node @sup. Since most of data was read + * from the superblock and stored in @c, the function validates fields in @c + * instead. Returns zero in case of success and %-EINVAL in case of validation + * failure. + */ +static int validate_sb(struct ubifs_info *c, struct ubifs_sb_node *sup) +{ + long long max_bytes; + int err = 1, min_leb_cnt; + + if (!c->key_hash) { + err = 2; + goto failed; + } + + if (sup->key_fmt != UBIFS_SIMPLE_KEY_FMT) { + err = 3; + goto failed; + } + + if (le32_to_cpu(sup->min_io_size) != c->min_io_size) { + ubifs_err(c, "min. I/O unit mismatch: %d in superblock, %d real", + le32_to_cpu(sup->min_io_size), c->min_io_size); + goto failed; + } + + if (le32_to_cpu(sup->leb_size) != c->leb_size) { + ubifs_err(c, "LEB size mismatch: %d in superblock, %d real", + le32_to_cpu(sup->leb_size), c->leb_size); + goto failed; + } + + if (c->log_lebs < UBIFS_MIN_LOG_LEBS || + c->lpt_lebs < UBIFS_MIN_LPT_LEBS || + c->orph_lebs < UBIFS_MIN_ORPH_LEBS || + c->main_lebs < UBIFS_MIN_MAIN_LEBS) { + err = 4; + goto failed; + } + + /* + * Calculate minimum allowed amount of main area LEBs. This is very + * similar to %UBIFS_MIN_LEB_CNT, but we take into account real what we + * have just read from the superblock. + */ + min_leb_cnt = UBIFS_SB_LEBS + UBIFS_MST_LEBS + c->log_lebs; + min_leb_cnt += c->lpt_lebs + c->orph_lebs + c->jhead_cnt + 6; + + if (c->leb_cnt < min_leb_cnt || c->leb_cnt > c->vi.rsvd_lebs) { + ubifs_err(c, "bad LEB count: %d in superblock, %d on UBI volume, %d minimum required", + c->leb_cnt, c->vi.rsvd_lebs, min_leb_cnt); + goto failed; + } + + if (c->max_leb_cnt < c->leb_cnt) { + ubifs_err(c, "max. LEB count %d less than LEB count %d", + c->max_leb_cnt, c->leb_cnt); + goto failed; + } + + if (c->main_lebs < UBIFS_MIN_MAIN_LEBS) { + ubifs_err(c, "too few main LEBs count %d, must be at least %d", + c->main_lebs, UBIFS_MIN_MAIN_LEBS); + goto failed; + } + + max_bytes = (long long)c->leb_size * UBIFS_MIN_BUD_LEBS; + if (c->max_bud_bytes < max_bytes) { + ubifs_err(c, "too small journal (%lld bytes), must be at least %lld bytes", + c->max_bud_bytes, max_bytes); + goto failed; + } + + max_bytes = (long long)c->leb_size * c->main_lebs; + if (c->max_bud_bytes > max_bytes) { + ubifs_err(c, "too large journal size (%lld bytes), only %lld bytes available in the main area", + c->max_bud_bytes, max_bytes); + goto failed; + } + + if (c->jhead_cnt < NONDATA_JHEADS_CNT + 1 || + c->jhead_cnt > NONDATA_JHEADS_CNT + UBIFS_MAX_JHEADS) { + err = 9; + goto failed; + } + + if (c->fanout < UBIFS_MIN_FANOUT || + ubifs_idx_node_sz(c, c->fanout) > c->leb_size) { + err = 10; + goto failed; + } + + if (c->lsave_cnt < 0 || (c->lsave_cnt > DEFAULT_LSAVE_CNT && + c->lsave_cnt > c->max_leb_cnt - UBIFS_SB_LEBS - UBIFS_MST_LEBS - + c->log_lebs - c->lpt_lebs - c->orph_lebs)) { + err = 11; + goto failed; + } + + if (UBIFS_SB_LEBS + UBIFS_MST_LEBS + c->log_lebs + c->lpt_lebs + + c->orph_lebs + c->main_lebs != c->leb_cnt) { + err = 12; + goto failed; + } + + if (c->default_compr >= UBIFS_COMPR_TYPES_CNT) { + err = 13; + goto failed; + } + + if (c->rp_size < 0 || max_bytes < c->rp_size) { + err = 14; + goto failed; + } + + if (le32_to_cpu(sup->time_gran) > 1000000000 || + le32_to_cpu(sup->time_gran) < 1) { + err = 15; + goto failed; + } + + if (!c->double_hash && c->fmt_version >= 5) { + err = 16; + goto failed; + } + + if (c->encrypted && c->fmt_version < 5) { + err = 17; + goto failed; + } + + return 0; + +failed: + ubifs_err(c, "bad superblock, error %d", err); + ubifs_dump_node(c, sup, ALIGN(UBIFS_SB_NODE_SZ, c->min_io_size)); + return -EINVAL; +} + +/** + * ubifs_read_sb_node - read superblock node. + * @c: UBIFS file-system description object + * + * This function returns a pointer to the superblock node or a negative error + * code. Note, the user of this function is responsible of kfree()'ing the + * returned superblock buffer. + */ +static struct ubifs_sb_node *ubifs_read_sb_node(struct ubifs_info *c) +{ + struct ubifs_sb_node *sup; + int err; + + sup = kmalloc(ALIGN(UBIFS_SB_NODE_SZ, c->min_io_size), GFP_NOFS); + if (!sup) + return ERR_PTR(-ENOMEM); + + err = ubifs_read_node(c, sup, UBIFS_SB_NODE, UBIFS_SB_NODE_SZ, + UBIFS_SB_LNUM, 0); + if (err) { + kfree(sup); + return ERR_PTR(err); + } + + return sup; +} + +static int authenticate_sb_node(__unused struct ubifs_info *c, + const struct ubifs_sb_node *sup) +{ + unsigned int sup_flags = le32_to_cpu(sup->flags); + int authenticated = !!(sup_flags & UBIFS_FLG_AUTHENTICATION); + + if (authenticated) { + // To be implemented + ubifs_err(c, "not support authentication"); + return -EOPNOTSUPP; + } + + return 0; +} + +/** + * ubifs_write_sb_node - write superblock node. + * @c: UBIFS file-system description object + * @sup: superblock node read with 'ubifs_read_sb_node()' + * + * This function returns %0 on success and a negative error code on failure. + */ +int ubifs_write_sb_node(struct ubifs_info *c, struct ubifs_sb_node *sup) +{ + int len = ALIGN(UBIFS_SB_NODE_SZ, c->min_io_size); + int err; + + err = ubifs_prepare_node_hmac(c, sup, UBIFS_SB_NODE_SZ, + offsetof(struct ubifs_sb_node, hmac), 1); + if (err) + return err; + + return ubifs_leb_change(c, UBIFS_SB_LNUM, sup, len); +} + +/** + * ubifs_read_superblock - read superblock. + * @c: UBIFS file-system description object + * + * This function finds, reads and checks the superblock. If an empty UBI volume + * is being mounted, this function creates default superblock. Returns zero in + * case of success, and a negative error code in case of failure. + */ +int ubifs_read_superblock(struct ubifs_info *c) +{ + int err, sup_flags; + struct ubifs_sb_node *sup; + + sup = ubifs_read_sb_node(c); + if (IS_ERR(sup)) + return PTR_ERR(sup); + + c->sup_node = sup; + + c->fmt_version = le32_to_cpu(sup->fmt_version); + c->ro_compat_version = le32_to_cpu(sup->ro_compat_version); + + /* + * The software supports all previous versions but not future versions, + * due to the unavailability of time-travelling equipment. + */ + if (c->fmt_version > UBIFS_FORMAT_VERSION) { + ubifs_assert(c, !c->ro_media || c->ro_mount); + if (!c->ro_mount || + c->ro_compat_version > UBIFS_RO_COMPAT_VERSION) { + ubifs_err(c, "on-flash format version is w%d/r%d, but software only supports up to version w%d/r%d", + c->fmt_version, c->ro_compat_version, + UBIFS_FORMAT_VERSION, + UBIFS_RO_COMPAT_VERSION); + if (c->ro_compat_version <= UBIFS_RO_COMPAT_VERSION) { + ubifs_msg(c, "only R/O mounting is possible"); + err = -EROFS; + } else + err = -EINVAL; + goto out; + } + } + + if (c->fmt_version < 3) { + ubifs_err(c, "on-flash format version %d is not supported", + c->fmt_version); + err = -EINVAL; + goto out; + } + + switch (sup->key_hash) { + case UBIFS_KEY_HASH_R5: + c->key_hash = key_r5_hash; + c->key_hash_type = UBIFS_KEY_HASH_R5; + break; + + case UBIFS_KEY_HASH_TEST: + c->key_hash = key_test_hash; + c->key_hash_type = UBIFS_KEY_HASH_TEST; + break; + } + + c->key_fmt = sup->key_fmt; + + switch (c->key_fmt) { + case UBIFS_SIMPLE_KEY_FMT: + c->key_len = UBIFS_SK_LEN; + break; + default: + ubifs_err(c, "unsupported key format"); + err = -EINVAL; + goto out; + } + + c->leb_cnt = le32_to_cpu(sup->leb_cnt); + c->max_leb_cnt = le32_to_cpu(sup->max_leb_cnt); + c->max_bud_bytes = le64_to_cpu(sup->max_bud_bytes); + c->log_lebs = le32_to_cpu(sup->log_lebs); + c->lpt_lebs = le32_to_cpu(sup->lpt_lebs); + c->orph_lebs = le32_to_cpu(sup->orph_lebs); + c->jhead_cnt = le32_to_cpu(sup->jhead_cnt) + NONDATA_JHEADS_CNT; + c->fanout = le32_to_cpu(sup->fanout); + c->lsave_cnt = le32_to_cpu(sup->lsave_cnt); + c->rp_size = le64_to_cpu(sup->rp_size); + sup_flags = le32_to_cpu(sup->flags); + c->default_compr = le16_to_cpu(sup->default_compr); + + c->big_lpt = !!(sup_flags & UBIFS_FLG_BIGLPT); + c->space_fixup = !!(sup_flags & UBIFS_FLG_SPACE_FIXUP); + c->double_hash = !!(sup_flags & UBIFS_FLG_DOUBLE_HASH); + c->encrypted = !!(sup_flags & UBIFS_FLG_ENCRYPTION); + + err = authenticate_sb_node(c, sup); + if (err) + goto out; + + if ((sup_flags & ~UBIFS_FLG_MASK) != 0) { + ubifs_err(c, "Unknown feature flags found: %#x", + sup_flags & ~UBIFS_FLG_MASK); + err = -EINVAL; + goto out; + } + + /* Automatically increase file system size to the maximum size */ + if (c->leb_cnt < c->vi.rsvd_lebs && c->leb_cnt < c->max_leb_cnt) { + int old_leb_cnt = c->leb_cnt; + + c->leb_cnt = min_t(int, c->max_leb_cnt, c->vi.rsvd_lebs); + sup->leb_cnt = cpu_to_le32(c->leb_cnt); + + c->superblock_need_write = 1; + + dbg_mnt("Auto resizing from %d LEBs to %d LEBs", + old_leb_cnt, c->leb_cnt); + } + + c->log_bytes = (long long)c->log_lebs * c->leb_size; + c->log_last = UBIFS_LOG_LNUM + c->log_lebs - 1; + c->lpt_first = UBIFS_LOG_LNUM + c->log_lebs; + c->lpt_last = c->lpt_first + c->lpt_lebs - 1; + c->orph_first = c->lpt_last + 1; + c->orph_last = c->orph_first + c->orph_lebs - 1; + c->main_lebs = c->leb_cnt - UBIFS_SB_LEBS - UBIFS_MST_LEBS; + c->main_lebs -= c->log_lebs + c->lpt_lebs + c->orph_lebs; + c->main_first = c->leb_cnt - c->main_lebs; + + err = validate_sb(c, sup); +out: + if (err) + set_failure_reason_callback(c, FR_DATA_CORRUPTED); + return err; +} + +/** + * fixup_leb - fixup/unmap an LEB containing free space. + * @c: UBIFS file-system description object + * @lnum: the LEB number to fix up + * @len: number of used bytes in LEB (starting at offset 0) + * + * This function reads the contents of the given LEB number @lnum, then fixes + * it up, so that empty min. I/O units in the end of LEB are actually erased on + * flash (rather than being just all-0xff real data). If the LEB is completely + * empty, it is simply unmapped. + */ +static int fixup_leb(struct ubifs_info *c, int lnum, int len) +{ + int err; + + ubifs_assert(c, len >= 0); + ubifs_assert(c, len % c->min_io_size == 0); + ubifs_assert(c, len < c->leb_size); + + if (len == 0) { + dbg_mnt("unmap empty LEB %d", lnum); + return ubifs_leb_unmap(c, lnum); + } + + dbg_mnt("fixup LEB %d, data len %d", lnum, len); + err = ubifs_leb_read(c, lnum, c->sbuf, 0, len, 1); + if (err && err != -EBADMSG) + return err; + + return ubifs_leb_change(c, lnum, c->sbuf, len); +} + +/** + * fixup_free_space - find & remap all LEBs containing free space. + * @c: UBIFS file-system description object + * + * This function walks through all LEBs in the filesystem and fiexes up those + * containing free/empty space. + */ +static int fixup_free_space(struct ubifs_info *c) +{ + int lnum, err = 0; + struct ubifs_lprops *lprops; + + ubifs_get_lprops(c); + + /* Fixup LEBs in the master area */ + for (lnum = UBIFS_MST_LNUM; lnum < UBIFS_LOG_LNUM; lnum++) { + err = fixup_leb(c, lnum, c->mst_offs + c->mst_node_alsz); + if (err) + goto out; + } + + /* Unmap unused log LEBs */ + lnum = ubifs_next_log_lnum(c, c->lhead_lnum); + while (lnum != c->ltail_lnum) { + err = fixup_leb(c, lnum, 0); + if (err) + goto out; + lnum = ubifs_next_log_lnum(c, lnum); + } + + /* + * Fixup the log head which contains the only a CS node at the + * beginning. + */ + err = fixup_leb(c, c->lhead_lnum, + ALIGN(UBIFS_CS_NODE_SZ, c->min_io_size)); + if (err) + goto out; + + /* Fixup LEBs in the LPT area */ + for (lnum = c->lpt_first; lnum <= c->lpt_last; lnum++) { + int free = c->ltab[lnum - c->lpt_first].free; + + if (free > 0) { + err = fixup_leb(c, lnum, c->leb_size - free); + if (err) + goto out; + } + } + + /* Unmap LEBs in the orphans area */ + for (lnum = c->orph_first; lnum <= c->orph_last; lnum++) { + err = fixup_leb(c, lnum, 0); + if (err) + goto out; + } + + /* Fixup LEBs in the main area */ + for (lnum = c->main_first; lnum < c->leb_cnt; lnum++) { + lprops = ubifs_lpt_lookup(c, lnum); + if (IS_ERR(lprops)) { + err = PTR_ERR(lprops); + goto out; + } + + if (lprops->free > 0) { + err = fixup_leb(c, lnum, c->leb_size - lprops->free); + if (err) + goto out; + } + } + +out: + ubifs_release_lprops(c); + return err; +} + +/** + * ubifs_fixup_free_space - find & fix all LEBs with free space. + * @c: UBIFS file-system description object + * + * This function fixes up LEBs containing free space on first mount, if the + * appropriate flag was set when the FS was created. Each LEB with one or more + * empty min. I/O unit (i.e. free-space-count > 0) is re-written, to make sure + * the free space is actually erased. E.g., this is necessary for some NAND + * chips, since the free space may have been programmed like real "0xff" data + * (generating a non-0xff ECC), causing future writes to the not-really-erased + * NAND pages to behave badly. After the space is fixed up, the superblock flag + * is cleared, so that this is skipped for all future mounts. + */ +int ubifs_fixup_free_space(struct ubifs_info *c) +{ + int err; + struct ubifs_sb_node *sup = c->sup_node; + + ubifs_assert(c, c->space_fixup); + ubifs_assert(c, !c->ro_mount); + + ubifs_msg(c, "start fixing up free space"); + + err = fixup_free_space(c); + if (err) + return err; + + /* Free-space fixup is no longer required */ + c->space_fixup = 0; + sup->flags &= cpu_to_le32(~UBIFS_FLG_SPACE_FIXUP); + + c->superblock_need_write = 1; + + ubifs_msg(c, "free space fixup complete"); + return err; +} diff --git a/ubifs-utils/libubifs/scan.c b/ubifs-utils/libubifs/scan.c new file mode 100644 index 0000000..e9581a6 --- /dev/null +++ b/ubifs-utils/libubifs/scan.c @@ -0,0 +1,372 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * This file is part of UBIFS. + * + * Copyright (C) 2006-2008 Nokia Corporation + * + * Authors: Adrian Hunter + * Artem Bityutskiy (Битюцкий Артём) + */ + +/* + * This file implements the scan which is a general-purpose function for + * determining what nodes are in an eraseblock. The scan is used to replay the + * journal, to do garbage collection. for the TNC in-the-gaps method, and by + * debugging functions. + */ + +#include "linux_err.h" +#include "kmem.h" +#include "ubifs.h" +#include "defs.h" +#include "debug.h" +#include "key.h" + +/** + * scan_padding_bytes - scan for padding bytes. + * @buf: buffer to scan + * @len: length of buffer + * + * This function returns the number of padding bytes on success and + * %SCANNED_GARBAGE on failure. + */ +static int scan_padding_bytes(void *buf, int len) +{ + int pad_len = 0, max_pad_len = min_t(int, UBIFS_PAD_NODE_SZ, len); + uint8_t *p = buf; + + dbg_scan("not a node"); + + while (pad_len < max_pad_len && *p++ == UBIFS_PADDING_BYTE) + pad_len += 1; + + if (!pad_len || (pad_len & 7)) + return SCANNED_GARBAGE; + + dbg_scan("%d padding bytes", pad_len); + + return pad_len; +} + +/** + * ubifs_scan_a_node - scan for a node or padding. + * @c: UBIFS file-system description object + * @buf: buffer to scan + * @len: length of buffer + * @lnum: logical eraseblock number + * @offs: offset within the logical eraseblock + * @quiet: print no messages + * + * This function returns a scanning code to indicate what was scanned. + */ +int ubifs_scan_a_node(const struct ubifs_info *c, void *buf, int len, int lnum, + int offs, int quiet) +{ + struct ubifs_ch *ch = buf; + uint32_t magic; + + magic = le32_to_cpu(ch->magic); + + if (magic == 0xFFFFFFFF) { + dbg_scan("hit empty space at LEB %d:%d", lnum, offs); + return SCANNED_EMPTY_SPACE; + } + + if (magic != UBIFS_NODE_MAGIC) + return scan_padding_bytes(buf, len); + + if (len < UBIFS_CH_SZ) + return SCANNED_GARBAGE; + + dbg_scan("scanning %s at LEB %d:%d", + dbg_ntype(ch->node_type), lnum, offs); + + if (ubifs_check_node(c, buf, len, lnum, offs, quiet, 1)) + return SCANNED_A_CORRUPT_NODE; + + if (ch->node_type == UBIFS_PAD_NODE) { + struct ubifs_pad_node *pad = buf; + int pad_len = le32_to_cpu(pad->pad_len); + int node_len = le32_to_cpu(ch->len); + + /* Validate the padding node */ + if (pad_len < 0 || + offs + node_len + pad_len > c->leb_size) { + if (!quiet) { + ubifs_err(c, "bad pad node at LEB %d:%d", + lnum, offs); + ubifs_dump_node(c, pad, len); + } + return SCANNED_A_BAD_PAD_NODE; + } + + /* Make the node pads to 8-byte boundary */ + if ((node_len + pad_len) & 7) { + if (!quiet) + ubifs_err(c, "bad padding length %d - %d", + offs, offs + node_len + pad_len); + return SCANNED_A_BAD_PAD_NODE; + } + + dbg_scan("%d bytes padded at LEB %d:%d, offset now %d", pad_len, + lnum, offs, ALIGN(offs + node_len + pad_len, 8)); + + return node_len + pad_len; + } + + return SCANNED_A_NODE; +} + +/** + * ubifs_start_scan - create LEB scanning information at start of scan. + * @c: UBIFS file-system description object + * @lnum: logical eraseblock number + * @offs: offset to start at (usually zero) + * @sbuf: scan buffer (must be c->leb_size) + * + * This function returns the scanned information on success and a negative error + * code on failure. + */ +struct ubifs_scan_leb *ubifs_start_scan(const struct ubifs_info *c, int lnum, + int offs, void *sbuf) +{ + struct ubifs_scan_leb *sleb; + int err; + + dbg_scan("scan LEB %d:%d", lnum, offs); + + sleb = kzalloc(sizeof(struct ubifs_scan_leb), GFP_NOFS); + if (!sleb) + return ERR_PTR(-ENOMEM); + + sleb->lnum = lnum; + INIT_LIST_HEAD(&sleb->nodes); + sleb->buf = sbuf; + + err = ubifs_leb_read(c, lnum, sbuf + offs, offs, c->leb_size - offs, 0); + if (err && err != -EBADMSG) { + ubifs_err(c, "cannot read %d bytes from LEB %d:%d, error %d", + c->leb_size - offs, lnum, offs, err); + kfree(sleb); + return ERR_PTR(err); + } + + /* + * Note, we ignore integrity errors (EBASMSG) because all the nodes are + * protected by CRC checksums. + */ + return sleb; +} + +/** + * ubifs_end_scan - update LEB scanning information at end of scan. + * @c: UBIFS file-system description object + * @sleb: scanning information + * @lnum: logical eraseblock number + * @offs: offset to start at (usually zero) + */ +void ubifs_end_scan(const struct ubifs_info *c, struct ubifs_scan_leb *sleb, + int lnum, int offs) +{ + dbg_scan("stop scanning LEB %d at offset %d", lnum, offs); + ubifs_assert(c, offs % c->min_io_size == 0); + + sleb->endpt = ALIGN(offs, c->min_io_size); +} + +/** + * ubifs_add_snod - add a scanned node to LEB scanning information. + * @c: UBIFS file-system description object + * @sleb: scanning information + * @buf: buffer containing node + * @offs: offset of node on flash + * + * This function returns %0 on success and a negative error code on failure. + */ +int ubifs_add_snod(const struct ubifs_info *c, struct ubifs_scan_leb *sleb, + void *buf, int offs) +{ + struct ubifs_ch *ch = buf; + struct ubifs_ino_node *ino = buf; + struct ubifs_scan_node *snod; + + snod = kmalloc(sizeof(struct ubifs_scan_node), GFP_NOFS); + if (!snod) + return -ENOMEM; + + snod->sqnum = le64_to_cpu(ch->sqnum); + snod->type = ch->node_type; + snod->offs = offs; + snod->len = le32_to_cpu(ch->len); + snod->node = buf; + + switch (ch->node_type) { + case UBIFS_INO_NODE: + case UBIFS_DENT_NODE: + case UBIFS_XENT_NODE: + case UBIFS_DATA_NODE: + /* + * The key is in the same place in all keyed + * nodes. + */ + key_read(c, &ino->key, &snod->key); + break; + default: + invalid_key_init(c, &snod->key); + break; + } + list_add_tail(&snod->list, &sleb->nodes); + sleb->nodes_cnt += 1; + return 0; +} + +/** + * ubifs_scanned_corruption - print information after UBIFS scanned corruption. + * @c: UBIFS file-system description object + * @lnum: LEB number of corruption + * @offs: offset of corruption + * @buf: buffer containing corruption + */ +void ubifs_scanned_corruption(const struct ubifs_info *c, int lnum, int offs, + void *buf) +{ + int len; + + ubifs_err(c, "corruption at LEB %d:%d", lnum, offs); + len = c->leb_size - offs; + if (len > 8192) + len = 8192; + ubifs_err(c, "first %d bytes from LEB %d:%d", len, lnum, offs); + print_hex_dump("", DUMP_PREFIX_OFFSET, 32, 4, buf, len, 1); +} + +/** + * ubifs_scan - scan a logical eraseblock. + * @c: UBIFS file-system description object + * @lnum: logical eraseblock number + * @offs: offset to start at (usually zero) + * @sbuf: scan buffer (must be of @c->leb_size bytes in size) + * @quiet: print no messages + * + * This function scans LEB number @lnum and returns complete information about + * its contents. Returns the scanned information in case of success and, + * %-EUCLEAN if the LEB neads recovery, and other negative error codes in case + * of failure. + * + * If @quiet is non-zero, this function does not print large and scary + * error messages and flash dumps in case of errors. + */ +struct ubifs_scan_leb *ubifs_scan(const struct ubifs_info *c, int lnum, + int offs, void *sbuf, int quiet) +{ + void *buf = sbuf + offs; + int err, len = c->leb_size - offs; + struct ubifs_scan_leb *sleb; + + sleb = ubifs_start_scan(c, lnum, offs, sbuf); + if (IS_ERR(sleb)) + return sleb; + + while (len >= 8) { + struct ubifs_ch *ch = buf; + int node_len, ret; + + dbg_scan("look at LEB %d:%d (%d bytes left)", + lnum, offs, len); + + cond_resched(); + + ret = ubifs_scan_a_node(c, buf, len, lnum, offs, quiet); + if (ret > 0) { + /* Padding bytes or a valid padding node */ + offs += ret; + buf += ret; + len -= ret; + continue; + } + + if (ret == SCANNED_EMPTY_SPACE) + /* Empty space is checked later */ + break; + + switch (ret) { + case SCANNED_GARBAGE: + ubifs_err(c, "garbage"); + goto corrupted; + case SCANNED_A_NODE: + break; + case SCANNED_A_CORRUPT_NODE: + case SCANNED_A_BAD_PAD_NODE: + ubifs_err(c, "bad node"); + goto corrupted; + default: + ubifs_err(c, "unknown"); + err = -EINVAL; + goto error; + } + + err = ubifs_add_snod(c, sleb, buf, offs); + if (err) + goto error; + + node_len = ALIGN(le32_to_cpu(ch->len), 8); + offs += node_len; + buf += node_len; + len -= node_len; + } + + if (offs % c->min_io_size) { + if (!quiet) + ubifs_err(c, "empty space starts at non-aligned offset %d", + offs); + goto corrupted; + } + + ubifs_end_scan(c, sleb, lnum, offs); + + for (; len > 4; offs += 4, buf = buf + 4, len -= 4) + if (*(uint32_t *)buf != 0xffffffff) + break; + for (; len; offs++, buf++, len--) + if (*(uint8_t *)buf != 0xff) { + if (!quiet) + ubifs_err(c, "corrupt empty space at LEB %d:%d", + lnum, offs); + goto corrupted; + } + + return sleb; + +corrupted: + if (!quiet) { + ubifs_scanned_corruption(c, lnum, offs, buf); + ubifs_err(c, "LEB %d scanning failed", lnum); + } + err = -EUCLEAN; + ubifs_scan_destroy(sleb); + set_failure_reason_callback(c, FR_DATA_CORRUPTED); + return ERR_PTR(err); + +error: + ubifs_err(c, "LEB %d scanning failed, error %d", lnum, err); + ubifs_scan_destroy(sleb); + return ERR_PTR(err); +} + +/** + * ubifs_scan_destroy - destroy LEB scanning information. + * @sleb: scanning information to free + */ +void ubifs_scan_destroy(struct ubifs_scan_leb *sleb) +{ + struct ubifs_scan_node *node; + struct list_head *head; + + head = &sleb->nodes; + while (!list_empty(head)) { + node = list_entry(head->next, struct ubifs_scan_node, list); + list_del(&node->list); + kfree(node); + } + kfree(sleb); +} diff --git a/ubifs-utils/libubifs/super.c b/ubifs-utils/libubifs/super.c new file mode 100644 index 0000000..559623f --- /dev/null +++ b/ubifs-utils/libubifs/super.c @@ -0,0 +1,702 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * This file is part of UBIFS. + * + * Copyright (C) 2006-2008 Nokia Corporation. + * + * Authors: Artem Bityutskiy (Битюцкий Артём) + * Adrian Hunter + */ + +/* + * This file implements UBIFS initialization and VFS superblock operations. Some + * initialization stuff which is rather large and complex is placed at + * corresponding subsystems, but most of it is here. + */ + +#include <stdio.h> +#include <unistd.h> +#include <sys/stat.h> +#include <sys/types.h> + +#include "linux_err.h" +#include "bitops.h" +#include "kmem.h" +#include "ubifs.h" +#include "defs.h" +#include "debug.h" +#include "key.h" +#include "misc.h" + +atomic_long_t ubifs_clean_zn_cnt; +static const int default_debug_level = WARN_LEVEL; + +/** + * open_ubi - open the libubi. + * @c: the UBIFS file-system description object + * @node: name of the UBI volume character device to fetch information about + * + * This function opens libubi, and initialize device & volume information + * according to @node. Returns %0 in case of success and %-1 in case of failure. + */ +int open_ubi(struct ubifs_info *c, const char *node) +{ + struct stat st; + + if (stat(node, &st)) + return -1; + + if (!S_ISCHR(st.st_mode)) { + errno = ENODEV; + return -1; + } + + c->libubi = libubi_open(); + if (!c->libubi) + return -1; + if (ubi_get_vol_info(c->libubi, node, &c->vi)) + goto out_err; + if (ubi_get_dev_info1(c->libubi, c->vi.dev_num, &c->di)) + goto out_err; + + return 0; + +out_err: + close_ubi(c); + return -1; +} + +void close_ubi(struct ubifs_info *c) +{ + if (c->libubi) { + libubi_close(c->libubi); + c->libubi = NULL; + } +} + +/** + * open_target - open the output target. + * @c: the UBIFS file-system description object + * + * Open the output target. The target can be an UBI volume + * or a file. + * + * Returns %0 in case of success and a negative error code in case of failure. + */ +int open_target(struct ubifs_info *c) +{ + if (c->libubi) { + c->dev_fd = open(c->dev_name, O_RDWR | O_EXCL); + + if (c->dev_fd == -1) { + ubifs_err(c, "cannot open the UBI volume. %s", + strerror(errno)); + return -errno; + } + if (ubi_set_property(c->dev_fd, UBI_VOL_PROP_DIRECT_WRITE, 1)) { + close(c->dev_fd); + ubifs_err(c, "ubi_set_property(set direct_write) failed. %s", + strerror(errno)); + return -errno; + } + } else { + c->dev_fd = open(c->dev_name, O_CREAT | O_RDWR | O_TRUNC, + S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH); + if (c->dev_fd == -1) { + ubifs_err(c, "cannot create output file. %s", + strerror(errno)); + return -errno; + } + } + return 0; +} + +/** + * close_target - close the output target. + * @c: the UBIFS file-system description object + * + * Close the output target. If the target was an UBI + * volume, also close libubi. + * + * Returns %0 in case of success and a negative error code in case of failure. + */ +int close_target(struct ubifs_info *c) +{ + if (c->dev_fd >= 0) { + if (c->libubi && ubi_set_property(c->dev_fd, UBI_VOL_PROP_DIRECT_WRITE, 0)) { + ubifs_err(c, "ubi_set_property(clear direct_write) failed. %s", + strerror(errno)); + return -errno; + } + if (close(c->dev_fd) == -1) { + ubifs_err(c, "cannot close the target. %s", + strerror(errno)); + return -errno; + } + } + return 0; +} + +/** + * ubifs_open_volume - open UBI volume. + * @c: the UBIFS file-system description object + * @volume_name: the UBI volume name + * + * Open ubi volume. This function is implemented by open_ubi + open_target. + * + * Returns %0 in case of success and a negative error code in case of failure. + */ +int ubifs_open_volume(struct ubifs_info *c, const char *volume_name) +{ + int err; + + err = open_ubi(c, volume_name); + if (err) { + ubifs_err(c, "cannot open libubi. %s", strerror(errno)); + return err; + } + + err = open_target(c); + if (err) + close_ubi(c); + + return err; +} + +/** + * ubifs_close_volume - close UBI volume. + * @c: the UBIFS file-system description object + * + * Close ubi volume. This function is implemented by close_target + close_ubi. + * + * Returns %0 in case of success and a negative error code in case of failure. + */ +int ubifs_close_volume(struct ubifs_info *c) +{ + int err; + + err = close_target(c); + if (err) + return err; + + close_ubi(c); + + return 0; +} + +/** + * check_volume_empty - check if the UBI volume is empty. + * @c: the UBIFS file-system description object + * + * This function checks if the UBI volume is empty by looking if its LEBs are + * mapped or not. + * + * Returns %0 in case of success, %1 is the volume is not empty, + * and a negative error code in case of failure. + */ +int check_volume_empty(struct ubifs_info *c) +{ + int lnum, err; + + for (lnum = 0; lnum < c->vi.rsvd_lebs; lnum++) { + err = ubi_is_mapped(c->dev_fd, lnum); + if (err < 0) + return err; + if (err == 1) + return 1; + } + return 0; +} + +void init_ubifs_info(struct ubifs_info *c, int program_type) +{ + spin_lock_init(&c->cnt_lock); + spin_lock_init(&c->cs_lock); + spin_lock_init(&c->buds_lock); + spin_lock_init(&c->space_lock); + spin_lock_init(&c->orphan_lock); + init_rwsem(&c->commit_sem); + mutex_init(&c->lp_mutex); + mutex_init(&c->tnc_mutex); + mutex_init(&c->log_mutex); + c->buds = RB_ROOT; + c->old_idx = RB_ROOT; + c->size_tree = RB_ROOT; + c->orph_tree = RB_ROOT; + INIT_LIST_HEAD(&c->idx_gc); + INIT_LIST_HEAD(&c->replay_list); + INIT_LIST_HEAD(&c->replay_buds); + INIT_LIST_HEAD(&c->uncat_list); + INIT_LIST_HEAD(&c->empty_list); + INIT_LIST_HEAD(&c->freeable_list); + INIT_LIST_HEAD(&c->frdi_idx_list); + INIT_LIST_HEAD(&c->unclean_leb_list); + INIT_LIST_HEAD(&c->old_buds); + INIT_LIST_HEAD(&c->orph_list); + INIT_LIST_HEAD(&c->orph_new); + c->no_chk_data_crc = 1; + + c->highest_inum = UBIFS_FIRST_INO; + c->lhead_lnum = c->ltail_lnum = UBIFS_LOG_LNUM; + + c->program_type = program_type; + switch (c->program_type) { + case MKFS_PROGRAM_TYPE: + c->program_name = MKFS_PROGRAM_NAME; + break; + case FSCK_PROGRAM_TYPE: + c->program_name = FSCK_PROGRAM_NAME; + /* Always check crc for data node. */ + c->no_chk_data_crc = 0; + break; + default: + assert(0); + break; + } + c->dev_fd = -1; + c->debug_level = default_debug_level; +} + +/** + * init_constants_early - initialize UBIFS constants. + * @c: UBIFS file-system description object + * + * This function initialize UBIFS constants which do not need the superblock to + * be read. It also checks that the UBI volume satisfies basic UBIFS + * requirements. Returns zero in case of success and a negative error code in + * case of failure. + */ +int init_constants_early(struct ubifs_info *c) +{ +#define NOR_MAX_WRITESZ 64 + if (c->vi.corrupted) { + ubifs_warn(c, "UBI volume is corrupted - read-only mode"); + c->ro_media = 1; + } + + if (c->vi.type == UBI_STATIC_VOLUME) { + ubifs_msg(c, "static UBI volume - read-only mode"); + c->ro_media = 1; + } + + c->max_inode_sz = key_max_inode_size(c); + c->leb_cnt = c->vi.rsvd_lebs; + c->leb_size = c->vi.leb_size; + c->half_leb_size = c->leb_size / 2; + c->min_io_size = c->di.min_io_size; + c->min_io_shift = fls(c->min_io_size) - 1; + if (c->min_io_size == 1) + /* + * Different from linux kernel, the max write size of nor flash + * is not exposed in sysfs, just reset @c->max_write_size. + */ + c->max_write_size = NOR_MAX_WRITESZ; + else + c->max_write_size = c->di.min_io_size; + c->max_write_shift = fls(c->max_write_size) - 1; + + if (c->leb_size < UBIFS_MIN_LEB_SZ) { + ubifs_err(c, "too small LEBs (%d bytes), min. is %d bytes", + c->leb_size, UBIFS_MIN_LEB_SZ); + return -EINVAL; + } + + if (c->leb_cnt < UBIFS_MIN_LEB_CNT) { + ubifs_err(c, "too few LEBs (%d), min. is %d", + c->leb_cnt, UBIFS_MIN_LEB_CNT); + return -EINVAL; + } + + if (!is_power_of_2(c->min_io_size)) { + ubifs_err(c, "bad min. I/O size %d", c->min_io_size); + return -EINVAL; + } + + /* + * Maximum write size has to be greater or equivalent to min. I/O + * size, and be multiple of min. I/O size. + */ + if (c->max_write_size < c->min_io_size || + c->max_write_size % c->min_io_size || + !is_power_of_2(c->max_write_size)) { + ubifs_err(c, "bad write buffer size %d for %d min. I/O unit", + c->max_write_size, c->min_io_size); + return -EINVAL; + } + + /* + * UBIFS aligns all node to 8-byte boundary, so to make function in + * io.c simpler, assume minimum I/O unit size to be 8 bytes if it is + * less than 8. + */ + if (c->min_io_size < 8) { + c->min_io_size = 8; + c->min_io_shift = 3; + if (c->max_write_size < c->min_io_size) { + c->max_write_size = c->min_io_size; + c->max_write_shift = c->min_io_shift; + } + } + + c->ref_node_alsz = ALIGN(UBIFS_REF_NODE_SZ, c->min_io_size); + c->mst_node_alsz = ALIGN(UBIFS_MST_NODE_SZ, c->min_io_size); + + /* + * Initialize node length ranges which are mostly needed for node + * length validation. + */ + c->ranges[UBIFS_PAD_NODE].len = UBIFS_PAD_NODE_SZ; + c->ranges[UBIFS_SB_NODE].len = UBIFS_SB_NODE_SZ; + c->ranges[UBIFS_MST_NODE].len = UBIFS_MST_NODE_SZ; + c->ranges[UBIFS_REF_NODE].len = UBIFS_REF_NODE_SZ; + c->ranges[UBIFS_TRUN_NODE].len = UBIFS_TRUN_NODE_SZ; + c->ranges[UBIFS_CS_NODE].len = UBIFS_CS_NODE_SZ; + c->ranges[UBIFS_AUTH_NODE].min_len = UBIFS_AUTH_NODE_SZ; + c->ranges[UBIFS_AUTH_NODE].max_len = UBIFS_AUTH_NODE_SZ + + UBIFS_MAX_HMAC_LEN; + c->ranges[UBIFS_SIG_NODE].min_len = UBIFS_SIG_NODE_SZ; + c->ranges[UBIFS_SIG_NODE].max_len = c->leb_size - UBIFS_SB_NODE_SZ; + + c->ranges[UBIFS_INO_NODE].min_len = UBIFS_INO_NODE_SZ; + c->ranges[UBIFS_INO_NODE].max_len = UBIFS_MAX_INO_NODE_SZ; + c->ranges[UBIFS_ORPH_NODE].min_len = + UBIFS_ORPH_NODE_SZ + sizeof(__le64); + c->ranges[UBIFS_ORPH_NODE].max_len = c->leb_size; + c->ranges[UBIFS_DENT_NODE].min_len = UBIFS_DENT_NODE_SZ; + c->ranges[UBIFS_DENT_NODE].max_len = UBIFS_MAX_DENT_NODE_SZ; + c->ranges[UBIFS_XENT_NODE].min_len = UBIFS_XENT_NODE_SZ; + c->ranges[UBIFS_XENT_NODE].max_len = UBIFS_MAX_XENT_NODE_SZ; + c->ranges[UBIFS_DATA_NODE].min_len = UBIFS_DATA_NODE_SZ; + c->ranges[UBIFS_DATA_NODE].max_len = UBIFS_MAX_DATA_NODE_SZ; + /* + * Minimum indexing node size is amended later when superblock is + * read and the key length is known. + */ + c->ranges[UBIFS_IDX_NODE].min_len = UBIFS_IDX_NODE_SZ + UBIFS_BRANCH_SZ; + /* + * Maximum indexing node size is amended later when superblock is + * read and the fanout is known. + */ + c->ranges[UBIFS_IDX_NODE].max_len = INT_MAX; + + /* + * Initialize dead and dark LEB space watermarks. See gc.c for comments + * about these values. + */ + c->dead_wm = ALIGN(MIN_WRITE_SZ, c->min_io_size); + c->dark_wm = ALIGN(UBIFS_MAX_NODE_SZ, c->min_io_size); + + /* + * Calculate how many bytes would be wasted at the end of LEB if it was + * fully filled with data nodes of maximum size. This is used in + * calculations when reporting free space. + */ + c->leb_overhead = c->leb_size % UBIFS_MAX_DATA_NODE_SZ; + + /* Log is ready, preserve one LEB for commits. */ + c->min_log_bytes = c->leb_size; + + return 0; +} + +/** + * bud_wbuf_callback - bud LEB write-buffer synchronization call-back. + * @c: UBIFS file-system description object + * @lnum: LEB the write-buffer was synchronized to + * @free: how many free bytes left in this LEB + * @pad: how many bytes were padded + * + * This is a callback function which is called by the I/O unit when the + * write-buffer is synchronized. We need this to correctly maintain space + * accounting in bud logical eraseblocks. This function returns zero in case of + * success and a negative error code in case of failure. + * + * This function actually belongs to the journal, but we keep it here because + * we want to keep it static. + */ +static int bud_wbuf_callback(struct ubifs_info *c, int lnum, int free, int pad) +{ + return ubifs_update_one_lp(c, lnum, free, pad, 0, 0); +} + +/* + * init_constants_sb - initialize UBIFS constants. + * @c: UBIFS file-system description object + * + * This is a helper function which initializes various UBIFS constants after + * the superblock has been read. It also checks various UBIFS parameters and + * makes sure they are all right. Returns zero in case of success and a + * negative error code in case of failure. + */ +int init_constants_sb(struct ubifs_info *c) +{ + int tmp, err; + long long tmp64; + + c->main_bytes = (long long)c->main_lebs * c->leb_size; + c->max_znode_sz = sizeof(struct ubifs_znode) + + c->fanout * sizeof(struct ubifs_zbranch); + + tmp = ubifs_idx_node_sz(c, 1); + c->ranges[UBIFS_IDX_NODE].min_len = tmp; + c->min_idx_node_sz = ALIGN(tmp, 8); + + tmp = ubifs_idx_node_sz(c, c->fanout); + c->ranges[UBIFS_IDX_NODE].max_len = tmp; + c->max_idx_node_sz = ALIGN(tmp, 8); + + /* Make sure LEB size is large enough to fit full commit */ + tmp = UBIFS_CS_NODE_SZ + UBIFS_REF_NODE_SZ * c->jhead_cnt; + tmp = ALIGN(tmp, c->min_io_size); + if (tmp > c->leb_size) { + ubifs_err(c, "too small LEB size %d, at least %d needed", + c->leb_size, tmp); + return -EINVAL; + } + + /* + * Make sure that the log is large enough to fit reference nodes for + * all buds plus one reserved LEB. + */ + tmp64 = c->max_bud_bytes + c->leb_size - 1; + c->max_bud_cnt = div_u64(tmp64, c->leb_size); + tmp = (c->ref_node_alsz * c->max_bud_cnt + c->leb_size - 1); + tmp /= c->leb_size; + tmp += 1; + if (c->log_lebs < tmp) { + ubifs_err(c, "too small log %d LEBs, required min. %d LEBs", + c->log_lebs, tmp); + return -EINVAL; + } + + /* + * When budgeting we assume worst-case scenarios when the pages are not + * be compressed and direntries are of the maximum size. + * + * Note, data, which may be stored in inodes is budgeted separately, so + * it is not included into 'c->bi.inode_budget'. + */ + c->bi.page_budget = UBIFS_MAX_DATA_NODE_SZ * UBIFS_BLOCKS_PER_PAGE; + c->bi.inode_budget = UBIFS_INO_NODE_SZ; + c->bi.dent_budget = UBIFS_MAX_DENT_NODE_SZ; + + /* + * When the amount of flash space used by buds becomes + * 'c->max_bud_bytes', UBIFS just blocks all writers and starts commit. + * The writers are unblocked when the commit is finished. To avoid + * writers to be blocked UBIFS initiates background commit in advance, + * when number of bud bytes becomes above the limit defined below. + */ + c->bg_bud_bytes = (c->max_bud_bytes * 13) >> 4; + + /* + * Ensure minimum journal size. All the bytes in the journal heads are + * considered to be used, when calculating the current journal usage. + * Consequently, if the journal is too small, UBIFS will treat it as + * always full. + */ + tmp64 = (long long)(c->jhead_cnt + 1) * c->leb_size + 1; + if (c->bg_bud_bytes < tmp64) + c->bg_bud_bytes = tmp64; + if (c->max_bud_bytes < tmp64 + c->leb_size) + c->max_bud_bytes = tmp64 + c->leb_size; + + err = ubifs_calc_lpt_geom(c); + if (err) + return err; + + /* Initialize effective LEB size used in budgeting calculations */ + c->idx_leb_size = c->leb_size - c->max_idx_node_sz; + return 0; +} + +/* + * init_constants_master - initialize UBIFS constants. + * @c: UBIFS file-system description object + * + * This is a helper function which initializes various UBIFS constants after + * the master node has been read. It also checks various UBIFS parameters and + * makes sure they are all right. + */ +void init_constants_master(struct ubifs_info *c) +{ + c->bi.min_idx_lebs = ubifs_calc_min_idx_lebs(c); +} + +/** + * take_gc_lnum - reserve GC LEB. + * @c: UBIFS file-system description object + * + * This function ensures that the LEB reserved for garbage collection is marked + * as "taken" in lprops. We also have to set free space to LEB size and dirty + * space to zero, because lprops may contain out-of-date information if the + * file-system was un-mounted before it has been committed. This function + * returns zero in case of success and a negative error code in case of + * failure. + */ +int take_gc_lnum(struct ubifs_info *c) +{ + int err; + + if (c->gc_lnum == -1) { + ubifs_err(c, "no LEB for GC"); + return -EINVAL; + } + + /* And we have to tell lprops that this LEB is taken */ + err = ubifs_change_one_lp(c, c->gc_lnum, c->leb_size, 0, + LPROPS_TAKEN, 0, 0); + return err; +} + +/** + * alloc_wbufs - allocate write-buffers. + * @c: UBIFS file-system description object + * + * This helper function allocates and initializes UBIFS write-buffers. Returns + * zero in case of success and %-ENOMEM in case of failure. + */ +int alloc_wbufs(struct ubifs_info *c) +{ + int i, err; + + c->jheads = kcalloc(c->jhead_cnt, sizeof(struct ubifs_jhead), + GFP_KERNEL); + if (!c->jheads) + return -ENOMEM; + + /* Initialize journal heads */ + for (i = 0; i < c->jhead_cnt; i++) { + INIT_LIST_HEAD(&c->jheads[i].buds_list); + err = ubifs_wbuf_init(c, &c->jheads[i].wbuf); + if (err) + goto out_wbuf; + + c->jheads[i].wbuf.sync_callback = &bud_wbuf_callback; + c->jheads[i].wbuf.jhead = i; + c->jheads[i].grouped = 1; + c->jheads[i].log_hash = ubifs_hash_get_desc(c); + if (IS_ERR(c->jheads[i].log_hash)) { + err = PTR_ERR(c->jheads[i].log_hash); + goto out_log_hash; + } + } + + /* + * Garbage Collector head does not need to be synchronized by timer. + * Also GC head nodes are not grouped. + */ + c->jheads[GCHD].grouped = 0; + + return 0; + +out_log_hash: + kfree(c->jheads[i].wbuf.buf); + kfree(c->jheads[i].wbuf.inodes); + +out_wbuf: + while (i--) { + kfree(c->jheads[i].wbuf.buf); + kfree(c->jheads[i].wbuf.inodes); + kfree(c->jheads[i].log_hash); + } + kfree(c->jheads); + c->jheads = NULL; + + return err; +} + +/** + * free_wbufs - free write-buffers. + * @c: UBIFS file-system description object + */ +void free_wbufs(struct ubifs_info *c) +{ + int i; + + if (c->jheads) { + for (i = 0; i < c->jhead_cnt; i++) { + kfree(c->jheads[i].wbuf.buf); + kfree(c->jheads[i].wbuf.inodes); + kfree(c->jheads[i].log_hash); + } + kfree(c->jheads); + c->jheads = NULL; + } +} + +/** + * free_orphans - free orphans. + * @c: UBIFS file-system description object + */ +void free_orphans(struct ubifs_info *c) +{ + struct ubifs_orphan *orph; + + while (c->orph_dnext) { + orph = c->orph_dnext; + c->orph_dnext = orph->dnext; + list_del(&orph->list); + kfree(orph); + } + + while (!list_empty(&c->orph_list)) { + orph = list_entry(c->orph_list.next, struct ubifs_orphan, list); + list_del(&orph->list); + kfree(orph); + ubifs_err(c, "orphan list not empty at unmount"); + } + + vfree(c->orph_buf); + c->orph_buf = NULL; +} + +/** + * free_buds - free per-bud objects. + * @c: UBIFS file-system description object + * @delete_from_list: whether to delete the bud from list + */ +void free_buds(struct ubifs_info *c, bool delete_from_list) +{ + struct ubifs_bud *bud, *n; + + rbtree_postorder_for_each_entry_safe(bud, n, &c->buds, rb) { + if (delete_from_list) + list_del(&bud->list); + kfree(bud->log_hash); + kfree(bud); + } + + c->buds = RB_ROOT; +} + +/** + * destroy_journal - destroy journal data structures. + * @c: UBIFS file-system description object + * + * This function destroys journal data structures including those that may have + * been created by recovery functions. + */ +void destroy_journal(struct ubifs_info *c) +{ + while (!list_empty(&c->unclean_leb_list)) { + struct ubifs_unclean_leb *ucleb; + + ucleb = list_entry(c->unclean_leb_list.next, + struct ubifs_unclean_leb, list); + list_del(&ucleb->list); + kfree(ucleb); + } + while (!list_empty(&c->old_buds)) { + struct ubifs_bud *bud; + + bud = list_entry(c->old_buds.next, struct ubifs_bud, list); + list_del(&bud->list); + kfree(bud->log_hash); + kfree(bud); + } + ubifs_destroy_idx_gc(c); + ubifs_destroy_size_tree(c); + ubifs_tnc_close(c); + free_buds(c, false); +} diff --git a/ubifs-utils/libubifs/tnc.c b/ubifs-utils/libubifs/tnc.c new file mode 100644 index 0000000..9277062 --- /dev/null +++ b/ubifs-utils/libubifs/tnc.c @@ -0,0 +1,3070 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * This file is part of UBIFS. + * + * Copyright (C) 2006-2008 Nokia Corporation. + * + * Authors: Adrian Hunter + * Artem Bityutskiy (Битюцкий Артём) + */ + +/* + * This file implements TNC (Tree Node Cache) which caches indexing nodes of + * the UBIFS B-tree. + * + * At the moment the locking rules of the TNC tree are quite simple and + * straightforward. We just have a mutex and lock it when we traverse the + * tree. If a znode is not in memory, we read it from flash while still having + * the mutex locked. + */ + +#include "linux_err.h" +#include "bitops.h" +#include "kmem.h" +#include "crc32.h" +#include "ubifs.h" +#include "defs.h" +#include "debug.h" +#include "key.h" +#include "misc.h" + +static int try_read_node(const struct ubifs_info *c, void *buf, int type, + struct ubifs_zbranch *zbr); +static int fallible_read_node(struct ubifs_info *c, const union ubifs_key *key, + struct ubifs_zbranch *zbr, void *node); + +/* + * Returned codes of 'matches_name()' and 'fallible_matches_name()' functions. + * @NAME_LESS: name corresponding to the first argument is less than second + * @NAME_MATCHES: names match + * @NAME_GREATER: name corresponding to the second argument is greater than + * first + * @NOT_ON_MEDIA: node referred by zbranch does not exist on the media + * + * These constants were introduce to improve readability. + */ +enum { + NAME_LESS = 0, + NAME_MATCHES = 1, + NAME_GREATER = 2, + NOT_ON_MEDIA = 3, +}; + +static void do_insert_old_idx(struct ubifs_info *c, + struct ubifs_old_idx *old_idx) +{ + struct ubifs_old_idx *o; + struct rb_node **p, *parent = NULL; + + p = &c->old_idx.rb_node; + while (*p) { + parent = *p; + o = rb_entry(parent, struct ubifs_old_idx, rb); + if (old_idx->lnum < o->lnum) + p = &(*p)->rb_left; + else if (old_idx->lnum > o->lnum) + p = &(*p)->rb_right; + else if (old_idx->offs < o->offs) + p = &(*p)->rb_left; + else if (old_idx->offs > o->offs) + p = &(*p)->rb_right; + else { + ubifs_err(c, "old idx added twice!"); + kfree(old_idx); + return; + } + } + rb_link_node(&old_idx->rb, parent, p); + rb_insert_color(&old_idx->rb, &c->old_idx); +} + +/** + * insert_old_idx - record an index node obsoleted since the last commit start. + * @c: UBIFS file-system description object + * @lnum: LEB number of obsoleted index node + * @offs: offset of obsoleted index node + * + * Returns %0 on success, and a negative error code on failure. + * + * For recovery, there must always be a complete intact version of the index on + * flash at all times. That is called the "old index". It is the index as at the + * time of the last successful commit. Many of the index nodes in the old index + * may be dirty, but they must not be erased until the next successful commit + * (at which point that index becomes the old index). + * + * That means that the garbage collection and the in-the-gaps method of + * committing must be able to determine if an index node is in the old index. + * Most of the old index nodes can be found by looking up the TNC using the + * 'lookup_znode()' function. However, some of the old index nodes may have + * been deleted from the current index or may have been changed so much that + * they cannot be easily found. In those cases, an entry is added to an RB-tree. + * That is what this function does. The RB-tree is ordered by LEB number and + * offset because they uniquely identify the old index node. + */ +static int insert_old_idx(struct ubifs_info *c, int lnum, int offs) +{ + struct ubifs_old_idx *old_idx; + + old_idx = kmalloc(sizeof(struct ubifs_old_idx), GFP_NOFS); + if (unlikely(!old_idx)) + return -ENOMEM; + old_idx->lnum = lnum; + old_idx->offs = offs; + do_insert_old_idx(c, old_idx); + + return 0; +} + +/** + * insert_old_idx_znode - record a znode obsoleted since last commit start. + * @c: UBIFS file-system description object + * @znode: znode of obsoleted index node + * + * Returns %0 on success, and a negative error code on failure. + */ +int insert_old_idx_znode(struct ubifs_info *c, struct ubifs_znode *znode) +{ + if (znode->parent) { + struct ubifs_zbranch *zbr; + + zbr = &znode->parent->zbranch[znode->iip]; + if (zbr->len) + return insert_old_idx(c, zbr->lnum, zbr->offs); + } else + if (c->zroot.len) + return insert_old_idx(c, c->zroot.lnum, + c->zroot.offs); + return 0; +} + +/** + * ins_clr_old_idx_znode - record a znode obsoleted since last commit start. + * @c: UBIFS file-system description object + * @znode: znode of obsoleted index node + * + * Returns %0 on success, and a negative error code on failure. + */ +static int ins_clr_old_idx_znode(struct ubifs_info *c, + struct ubifs_znode *znode) +{ + int err; + + if (znode->parent) { + struct ubifs_zbranch *zbr; + + zbr = &znode->parent->zbranch[znode->iip]; + if (zbr->len) { + err = insert_old_idx(c, zbr->lnum, zbr->offs); + if (err) + return err; + zbr->lnum = 0; + zbr->offs = 0; + zbr->len = 0; + } + } else + if (c->zroot.len) { + err = insert_old_idx(c, c->zroot.lnum, c->zroot.offs); + if (err) + return err; + c->zroot.lnum = 0; + c->zroot.offs = 0; + c->zroot.len = 0; + } + return 0; +} + +/** + * destroy_old_idx - destroy the old_idx RB-tree. + * @c: UBIFS file-system description object + * + * During start commit, the old_idx RB-tree is used to avoid overwriting index + * nodes that were in the index last commit but have since been deleted. This + * is necessary for recovery i.e. the old index must be kept intact until the + * new index is successfully written. The old-idx RB-tree is used for the + * in-the-gaps method of writing index nodes and is destroyed every commit. + */ +void destroy_old_idx(struct ubifs_info *c) +{ + struct ubifs_old_idx *old_idx, *n; + + rbtree_postorder_for_each_entry_safe(old_idx, n, &c->old_idx, rb) + kfree(old_idx); + + c->old_idx = RB_ROOT; +} + +/** + * copy_znode - copy a dirty znode. + * @c: UBIFS file-system description object + * @znode: znode to copy + * + * A dirty znode being committed may not be changed, so it is copied. + */ +static struct ubifs_znode *copy_znode(struct ubifs_info *c, + struct ubifs_znode *znode) +{ + struct ubifs_znode *zn; + + zn = kmemdup(znode, c->max_znode_sz, GFP_NOFS); + if (unlikely(!zn)) + return ERR_PTR(-ENOMEM); + + zn->cnext = NULL; + __set_bit(DIRTY_ZNODE, &zn->flags); + __clear_bit(COW_ZNODE, &zn->flags); + + return zn; +} + +/** + * add_idx_dirt - add dirt due to a dirty znode. + * @c: UBIFS file-system description object + * @lnum: LEB number of index node + * @dirt: size of index node + * + * This function updates lprops dirty space and the new size of the index. + */ +static int add_idx_dirt(struct ubifs_info *c, int lnum, int dirt) +{ + c->calc_idx_sz -= ALIGN(dirt, 8); + return ubifs_add_dirt(c, lnum, dirt); +} + +/** + * replace_znode - replace old znode with new znode. + * @c: UBIFS file-system description object + * @new_zn: new znode + * @old_zn: old znode + * @zbr: the branch of parent znode + * + * Replace old znode with new znode in TNC. + */ +static void replace_znode(struct ubifs_info *c, struct ubifs_znode *new_zn, + struct ubifs_znode *old_zn, struct ubifs_zbranch *zbr) +{ + ubifs_assert(c, !ubifs_zn_obsolete(old_zn)); + __set_bit(OBSOLETE_ZNODE, &old_zn->flags); + + if (old_zn->level != 0) { + int i; + const int n = new_zn->child_cnt; + + /* The children now have new parent */ + for (i = 0; i < n; i++) { + struct ubifs_zbranch *child = &new_zn->zbranch[i]; + + if (child->znode) + child->znode->parent = new_zn; + } + } + + zbr->znode = new_zn; + zbr->lnum = 0; + zbr->offs = 0; + zbr->len = 0; + + atomic_long_inc(&c->dirty_zn_cnt); +} + +/** + * dirty_cow_znode - ensure a znode is not being committed. + * @c: UBIFS file-system description object + * @zbr: branch of znode to check + * + * Returns dirtied znode on success or negative error code on failure. + */ +static struct ubifs_znode *dirty_cow_znode(struct ubifs_info *c, + struct ubifs_zbranch *zbr) +{ + struct ubifs_znode *znode = zbr->znode; + struct ubifs_znode *zn; + int err; + + if (!ubifs_zn_cow(znode)) { + /* znode is not being committed */ + if (!test_and_set_bit(DIRTY_ZNODE, &znode->flags)) { + atomic_long_inc(&c->dirty_zn_cnt); + atomic_long_dec(&c->clean_zn_cnt); + atomic_long_dec(&ubifs_clean_zn_cnt); + err = add_idx_dirt(c, zbr->lnum, zbr->len); + if (unlikely(err)) + return ERR_PTR(err); + } + return znode; + } + + zn = copy_znode(c, znode); + if (IS_ERR(zn)) + return zn; + + if (zbr->len) { + struct ubifs_old_idx *old_idx; + + old_idx = kmalloc(sizeof(struct ubifs_old_idx), GFP_NOFS); + if (unlikely(!old_idx)) { + err = -ENOMEM; + goto out; + } + old_idx->lnum = zbr->lnum; + old_idx->offs = zbr->offs; + + err = add_idx_dirt(c, zbr->lnum, zbr->len); + if (err) { + kfree(old_idx); + goto out; + } + + do_insert_old_idx(c, old_idx); + } + + replace_znode(c, zn, znode, zbr); + + return zn; + +out: + kfree(zn); + return ERR_PTR(err); +} + +/** + * lnc_add - add a leaf node to the leaf node cache. + * @c: UBIFS file-system description object + * @zbr: zbranch of leaf node + * @node: leaf node + * + * Leaf nodes are non-index nodes directory entry nodes or data nodes. The + * purpose of the leaf node cache is to save re-reading the same leaf node over + * and over again. Most things are cached by VFS, however the file system must + * cache directory entries for readdir and for resolving hash collisions. The + * present implementation of the leaf node cache is extremely simple, and + * allows for error returns that are not used but that may be needed if a more + * complex implementation is created. + * + * Note, this function does not add the @node object to LNC directly, but + * allocates a copy of the object and adds the copy to LNC. The reason for this + * is that @node has been allocated outside of the TNC subsystem and will be + * used with @c->tnc_mutex unlock upon return from the TNC subsystem. But LNC + * may be changed at any time, e.g. freed by the shrinker. + */ +static int lnc_add(struct ubifs_info *c, struct ubifs_zbranch *zbr, + const void *node) +{ + int err; + void *lnc_node; + const struct ubifs_dent_node *dent = node; + + ubifs_assert(c, !zbr->leaf); + ubifs_assert(c, zbr->len != 0); + ubifs_assert(c, is_hash_key(c, &zbr->key)); + + err = ubifs_validate_entry(c, dent); + if (err) { + dump_stack(); + ubifs_dump_node(c, dent, zbr->len); + return err; + } + + lnc_node = kmemdup(node, zbr->len, GFP_NOFS); + if (!lnc_node) + /* We don't have to have the cache, so no error */ + return 0; + + zbr->leaf = lnc_node; + return 0; +} + + /** + * lnc_add_directly - add a leaf node to the leaf-node-cache. + * @c: UBIFS file-system description object + * @zbr: zbranch of leaf node + * @node: leaf node + * + * This function is similar to 'lnc_add()', but it does not create a copy of + * @node but inserts @node to TNC directly. + */ +static int lnc_add_directly(struct ubifs_info *c, struct ubifs_zbranch *zbr, + void *node) +{ + int err; + + ubifs_assert(c, !zbr->leaf); + ubifs_assert(c, zbr->len != 0); + + err = ubifs_validate_entry(c, node); + if (err) { + dump_stack(); + ubifs_dump_node(c, node, zbr->len); + return err; + } + + zbr->leaf = node; + return 0; +} + +/** + * lnc_free - remove a leaf node from the leaf node cache. + * @zbr: zbranch of leaf node + */ +static void lnc_free(struct ubifs_zbranch *zbr) +{ + if (!zbr->leaf) + return; + kfree(zbr->leaf); + zbr->leaf = NULL; +} + +/** + * tnc_read_hashed_node - read a "hashed" leaf node. + * @c: UBIFS file-system description object + * @zbr: key and position of the node + * @node: node is returned here + * + * This function reads a "hashed" node defined by @zbr from the leaf node cache + * (in it is there) or from the hash media, in which case the node is also + * added to LNC. Returns zero in case of success or a negative error + * code in case of failure. + */ +static int tnc_read_hashed_node(struct ubifs_info *c, struct ubifs_zbranch *zbr, + void *node) +{ + int err; + + ubifs_assert(c, is_hash_key(c, &zbr->key)); + + if (zbr->leaf) { + /* Read from the leaf node cache */ + ubifs_assert(c, zbr->len != 0); + memcpy(node, zbr->leaf, zbr->len); + return 0; + } + + if (c->replaying) { + err = fallible_read_node(c, &zbr->key, zbr, node); + /* + * When the node was not found, return -ENOENT, 0 otherwise. + * Negative return codes stay as-is. + */ + if (err == 0) + err = -ENOENT; + else if (err == 1) + err = 0; + } else { + err = ubifs_tnc_read_node(c, zbr, node); + } + if (err) + return err; + + /* Add the node to the leaf node cache */ + err = lnc_add(c, zbr, node); + return err; +} + +/** + * try_read_node - read a node if it is a node. + * @c: UBIFS file-system description object + * @buf: buffer to read to + * @type: node type + * @zbr: the zbranch describing the node to read + * + * This function tries to read a node of known type and length, checks it and + * stores it in @buf. This function returns %1 if a node is present and %0 if + * a node is not present. A negative error code is returned for I/O errors. + * This function performs that same function as ubifs_read_node except that + * it does not require that there is actually a node present and instead + * the return code indicates if a node was read. + * + * Note, this function does not check CRC of data nodes if @c->no_chk_data_crc + * is true (it is controlled by corresponding mount option). However, if + * @c->mounting or @c->remounting_rw is true (we are mounting or re-mounting to + * R/W mode), @c->no_chk_data_crc is ignored and CRC is checked. This is + * because during mounting or re-mounting from R/O mode to R/W mode we may read + * journal nodes (when replying the journal or doing the recovery) and the + * journal nodes may potentially be corrupted, so checking is required. + */ +static int try_read_node(const struct ubifs_info *c, void *buf, int type, + struct ubifs_zbranch *zbr) +{ + int len = zbr->len; + int lnum = zbr->lnum; + int offs = zbr->offs; + int err, node_len; + struct ubifs_ch *ch = buf; + uint32_t crc, node_crc; + + dbg_io("LEB %d:%d, %s, length %d", lnum, offs, dbg_ntype(type), len); + + err = ubifs_leb_read(c, lnum, buf, offs, len, 1); + if (err && err != -EBADMSG) { + ubifs_err(c, "cannot read node type %d from LEB %d:%d, error %d", + type, lnum, offs, err); + return err; + } + + if (le32_to_cpu(ch->magic) != UBIFS_NODE_MAGIC) + return 0; + + if (ch->node_type != type) + return 0; + + node_len = le32_to_cpu(ch->len); + if (node_len != len) + return 0; + + if (type != UBIFS_DATA_NODE || !c->no_chk_data_crc || c->mounting || + c->remounting_rw) { + crc = crc32(UBIFS_CRC32_INIT, buf + 8, node_len - 8); + node_crc = le32_to_cpu(ch->crc); + if (crc != node_crc) + return 0; + } + + err = ubifs_node_check_hash(c, buf, zbr->hash); + if (err) { + ubifs_bad_hash(c, buf, zbr->hash, lnum, offs); + return 0; + } + + return 1; +} + +/** + * fallible_read_node - try to read a leaf node. + * @c: UBIFS file-system description object + * @key: key of node to read + * @zbr: position of node + * @node: node returned + * + * This function tries to read a node and returns %1 if the node is read, %0 + * if the node is not present, and a negative error code in the case of error. + */ +static int fallible_read_node(struct ubifs_info *c, const union ubifs_key *key, + struct ubifs_zbranch *zbr, void *node) +{ + int ret; + + dbg_tnck(key, "LEB %d:%d, key ", zbr->lnum, zbr->offs); + + ret = try_read_node(c, node, key_type(c, key), zbr); + if (ret == 1) { + union ubifs_key node_key; + struct ubifs_dent_node *dent = node; + + /* All nodes have key in the same place */ + key_read(c, &dent->key, &node_key); + if (keys_cmp(c, key, &node_key) != 0) + ret = 0; + } + if (ret == 0 && c->replaying) + dbg_mntk(key, "dangling branch LEB %d:%d len %d, key ", + zbr->lnum, zbr->offs, zbr->len); + return ret; +} + +/** + * matches_name - determine if a direntry or xattr entry matches a given name. + * @c: UBIFS file-system description object + * @zbr: zbranch of dent + * @nm: name to match + * + * This function checks if xentry/direntry referred by zbranch @zbr matches name + * @nm. Returns %NAME_MATCHES if it does, %NAME_LESS if the name referred by + * @zbr is less than @nm, and %NAME_GREATER if it is greater than @nm. In case + * of failure, a negative error code is returned. + */ +static int matches_name(struct ubifs_info *c, struct ubifs_zbranch *zbr, + const struct fscrypt_name *nm) +{ + struct ubifs_dent_node *dent; + int nlen, err; + + /* If possible, match against the dent in the leaf node cache */ + if (!zbr->leaf) { + dent = kmalloc(zbr->len, GFP_NOFS); + if (!dent) + return -ENOMEM; + + err = ubifs_tnc_read_node(c, zbr, dent); + if (err) + goto out_free; + + /* Add the node to the leaf node cache */ + err = lnc_add_directly(c, zbr, dent); + if (err) + goto out_free; + } else + dent = zbr->leaf; + + nlen = le16_to_cpu(dent->nlen); + err = memcmp(dent->name, fname_name(nm), min_t(int, nlen, fname_len(nm))); + if (err == 0) { + if (nlen == fname_len(nm)) + return NAME_MATCHES; + else if (nlen < fname_len(nm)) + return NAME_LESS; + else + return NAME_GREATER; + } else if (err < 0) + return NAME_LESS; + else + return NAME_GREATER; + +out_free: + kfree(dent); + return err; +} + +/** + * get_znode - get a TNC znode that may not be loaded yet. + * @c: UBIFS file-system description object + * @znode: parent znode + * @n: znode branch slot number + * + * This function returns the znode or a negative error code. + */ +static struct ubifs_znode *get_znode(struct ubifs_info *c, + struct ubifs_znode *znode, int n) +{ + struct ubifs_zbranch *zbr; + + zbr = &znode->zbranch[n]; + if (zbr->znode) + znode = zbr->znode; + else + znode = ubifs_load_znode(c, zbr, znode, n); + return znode; +} + +/** + * tnc_next - find next TNC entry. + * @c: UBIFS file-system description object + * @zn: znode is passed and returned here + * @n: znode branch slot number is passed and returned here + * + * This function returns %0 if the next TNC entry is found, %-ENOENT if there is + * no next entry, or a negative error code otherwise. + */ +static int tnc_next(struct ubifs_info *c, struct ubifs_znode **zn, int *n) +{ + struct ubifs_znode *znode = *zn; + int nn = *n; + + nn += 1; + if (nn < znode->child_cnt) { + *n = nn; + return 0; + } + while (1) { + struct ubifs_znode *zp; + + zp = znode->parent; + if (!zp) + return -ENOENT; + nn = znode->iip + 1; + znode = zp; + if (nn < znode->child_cnt) { + znode = get_znode(c, znode, nn); + if (IS_ERR(znode)) + return PTR_ERR(znode); + while (znode->level != 0) { + znode = get_znode(c, znode, 0); + if (IS_ERR(znode)) + return PTR_ERR(znode); + } + nn = 0; + break; + } + } + *zn = znode; + *n = nn; + return 0; +} + +/** + * tnc_prev - find previous TNC entry. + * @c: UBIFS file-system description object + * @zn: znode is returned here + * @n: znode branch slot number is passed and returned here + * + * This function returns %0 if the previous TNC entry is found, %-ENOENT if + * there is no next entry, or a negative error code otherwise. + */ +static int tnc_prev(struct ubifs_info *c, struct ubifs_znode **zn, int *n) +{ + struct ubifs_znode *znode = *zn; + int nn = *n; + + if (nn > 0) { + *n = nn - 1; + return 0; + } + while (1) { + struct ubifs_znode *zp; + + zp = znode->parent; + if (!zp) + return -ENOENT; + nn = znode->iip - 1; + znode = zp; + if (nn >= 0) { + znode = get_znode(c, znode, nn); + if (IS_ERR(znode)) + return PTR_ERR(znode); + while (znode->level != 0) { + nn = znode->child_cnt - 1; + znode = get_znode(c, znode, nn); + if (IS_ERR(znode)) + return PTR_ERR(znode); + } + nn = znode->child_cnt - 1; + break; + } + } + *zn = znode; + *n = nn; + return 0; +} + +/** + * resolve_collision - resolve a collision. + * @c: UBIFS file-system description object + * @key: key of a directory or extended attribute entry + * @zn: znode is returned here + * @n: zbranch number is passed and returned here + * @nm: name of the entry + * + * This function is called for "hashed" keys to make sure that the found key + * really corresponds to the looked up node (directory or extended attribute + * entry). It returns %1 and sets @zn and @n if the collision is resolved. + * %0 is returned if @nm is not found and @zn and @n are set to the previous + * entry, i.e. to the entry after which @nm could follow if it were in TNC. + * This means that @n may be set to %-1 if the leftmost key in @zn is the + * previous one. A negative error code is returned on failures. + */ +static int resolve_collision(struct ubifs_info *c, const union ubifs_key *key, + struct ubifs_znode **zn, int *n, + const struct fscrypt_name *nm) +{ + int err; + + err = matches_name(c, &(*zn)->zbranch[*n], nm); + if (unlikely(err < 0)) + return err; + if (err == NAME_MATCHES) + return 1; + + if (err == NAME_GREATER) { + /* Look left */ + while (1) { + err = tnc_prev(c, zn, n); + if (err == -ENOENT) { + ubifs_assert(c, *n == 0); + *n = -1; + return 0; + } + if (err < 0) + return err; + if (keys_cmp(c, &(*zn)->zbranch[*n].key, key)) { + /* + * We have found the branch after which we would + * like to insert, but inserting in this znode + * may still be wrong. Consider the following 3 + * znodes, in the case where we are resolving a + * collision with Key2. + * + * znode zp + * ---------------------- + * level 1 | Key0 | Key1 | + * ----------------------- + * | | + * znode za | | znode zb + * ------------ ------------ + * level 0 | Key0 | | Key2 | + * ------------ ------------ + * + * The lookup finds Key2 in znode zb. Lets say + * there is no match and the name is greater so + * we look left. When we find Key0, we end up + * here. If we return now, we will insert into + * znode za at slot n = 1. But that is invalid + * according to the parent's keys. Key2 must + * be inserted into znode zb. + * + * Note, this problem is not relevant for the + * case when we go right, because + * 'tnc_insert()' would correct the parent key. + */ + if (*n == (*zn)->child_cnt - 1) { + err = tnc_next(c, zn, n); + if (err) { + /* Should be impossible */ + ubifs_assert(c, 0); + if (err == -ENOENT) + err = -EINVAL; + return err; + } + ubifs_assert(c, *n == 0); + *n = -1; + } + return 0; + } + err = matches_name(c, &(*zn)->zbranch[*n], nm); + if (err < 0) + return err; + if (err == NAME_LESS) + return 0; + if (err == NAME_MATCHES) + return 1; + ubifs_assert(c, err == NAME_GREATER); + } + } else { + int nn = *n; + struct ubifs_znode *znode = *zn; + + /* Look right */ + while (1) { + err = tnc_next(c, &znode, &nn); + if (err == -ENOENT) + return 0; + if (err < 0) + return err; + if (keys_cmp(c, &znode->zbranch[nn].key, key)) + return 0; + err = matches_name(c, &znode->zbranch[nn], nm); + if (err < 0) + return err; + if (err == NAME_GREATER) + return 0; + *zn = znode; + *n = nn; + if (err == NAME_MATCHES) + return 1; + ubifs_assert(c, err == NAME_LESS); + } + } +} + +/** + * fallible_matches_name - determine if a dent matches a given name. + * @c: UBIFS file-system description object + * @zbr: zbranch of dent + * @nm: name to match + * + * This is a "fallible" version of 'matches_name()' function which does not + * panic if the direntry/xentry referred by @zbr does not exist on the media. + * + * This function checks if xentry/direntry referred by zbranch @zbr matches name + * @nm. Returns %NAME_MATCHES it does, %NAME_LESS if the name referred by @zbr + * is less than @nm, %NAME_GREATER if it is greater than @nm, and @NOT_ON_MEDIA + * if xentry/direntry referred by @zbr does not exist on the media. A negative + * error code is returned in case of failure. + */ +static int fallible_matches_name(struct ubifs_info *c, + struct ubifs_zbranch *zbr, + const struct fscrypt_name *nm) +{ + struct ubifs_dent_node *dent; + int nlen, err; + + /* If possible, match against the dent in the leaf node cache */ + if (!zbr->leaf) { + dent = kmalloc(zbr->len, GFP_NOFS); + if (!dent) + return -ENOMEM; + + err = fallible_read_node(c, &zbr->key, zbr, dent); + if (err < 0) + goto out_free; + if (err == 0) { + /* The node was not present */ + err = NOT_ON_MEDIA; + goto out_free; + } + ubifs_assert(c, err == 1); + + err = lnc_add_directly(c, zbr, dent); + if (err) + goto out_free; + } else + dent = zbr->leaf; + + nlen = le16_to_cpu(dent->nlen); + err = memcmp(dent->name, fname_name(nm), min_t(int, nlen, fname_len(nm))); + if (err == 0) { + if (nlen == fname_len(nm)) + return NAME_MATCHES; + else if (nlen < fname_len(nm)) + return NAME_LESS; + else + return NAME_GREATER; + } else if (err < 0) + return NAME_LESS; + else + return NAME_GREATER; + +out_free: + kfree(dent); + return err; +} + +/** + * fallible_resolve_collision - resolve a collision even if nodes are missing. + * @c: UBIFS file-system description object + * @key: key + * @zn: znode is returned here + * @n: branch number is passed and returned here + * @nm: name of directory entry + * @adding: indicates caller is adding a key to the TNC + * + * This is a "fallible" version of the 'resolve_collision()' function which + * does not panic if one of the nodes referred to by TNC does not exist on the + * media. This may happen when replaying the journal if a deleted node was + * Garbage-collected and the commit was not done. A branch that refers to a node + * that is not present is called a dangling branch. The following are the return + * codes for this function: + * o if @nm was found, %1 is returned and @zn and @n are set to the found + * branch; + * o if we are @adding and @nm was not found, %0 is returned; + * o if we are not @adding and @nm was not found, but a dangling branch was + * found, then %1 is returned and @zn and @n are set to the dangling branch; + * o a negative error code is returned in case of failure. + */ +static int fallible_resolve_collision(struct ubifs_info *c, + const union ubifs_key *key, + struct ubifs_znode **zn, int *n, + const struct fscrypt_name *nm, + int adding) +{ + struct ubifs_znode *o_znode = NULL, *znode = *zn; + int err, cmp, o_n = 0, unsure = 0, nn = *n; + + cmp = fallible_matches_name(c, &znode->zbranch[nn], nm); + if (unlikely(cmp < 0)) + return cmp; + if (cmp == NAME_MATCHES) + return 1; + if (cmp == NOT_ON_MEDIA) { + o_znode = znode; + o_n = nn; + /* + * We are unlucky and hit a dangling branch straight away. + * Now we do not really know where to go to find the needed + * branch - to the left or to the right. Well, let's try left. + */ + unsure = 1; + } else if (!adding) + unsure = 1; /* Remove a dangling branch wherever it is */ + + if (cmp == NAME_GREATER || unsure) { + /* Look left */ + while (1) { + err = tnc_prev(c, zn, n); + if (err == -ENOENT) { + ubifs_assert(c, *n == 0); + *n = -1; + break; + } + if (err < 0) + return err; + if (keys_cmp(c, &(*zn)->zbranch[*n].key, key)) { + /* See comments in 'resolve_collision()' */ + if (*n == (*zn)->child_cnt - 1) { + err = tnc_next(c, zn, n); + if (err) { + /* Should be impossible */ + ubifs_assert(c, 0); + if (err == -ENOENT) + err = -EINVAL; + return err; + } + ubifs_assert(c, *n == 0); + *n = -1; + } + break; + } + err = fallible_matches_name(c, &(*zn)->zbranch[*n], nm); + if (err < 0) + return err; + if (err == NAME_MATCHES) + return 1; + if (err == NOT_ON_MEDIA) { + o_znode = *zn; + o_n = *n; + continue; + } + if (!adding) + continue; + if (err == NAME_LESS) + break; + else + unsure = 0; + } + } + + if (cmp == NAME_LESS || unsure) { + /* Look right */ + *zn = znode; + *n = nn; + while (1) { + err = tnc_next(c, &znode, &nn); + if (err == -ENOENT) + break; + if (err < 0) + return err; + if (keys_cmp(c, &znode->zbranch[nn].key, key)) + break; + err = fallible_matches_name(c, &znode->zbranch[nn], nm); + if (err < 0) + return err; + if (err == NAME_GREATER) + break; + *zn = znode; + *n = nn; + if (err == NAME_MATCHES) + return 1; + if (err == NOT_ON_MEDIA) { + o_znode = znode; + o_n = nn; + } + } + } + + /* Never match a dangling branch when adding */ + if (adding || !o_znode) + return 0; + + dbg_mntk(key, "dangling match LEB %d:%d len %d key ", + o_znode->zbranch[o_n].lnum, o_znode->zbranch[o_n].offs, + o_znode->zbranch[o_n].len); + *zn = o_znode; + *n = o_n; + return 1; +} + +/** + * matches_position - determine if a zbranch matches a given position. + * @zbr: zbranch of dent + * @lnum: LEB number of dent to match + * @offs: offset of dent to match + * + * This function returns %1 if @lnum:@offs matches, and %0 otherwise. + */ +static int matches_position(struct ubifs_zbranch *zbr, int lnum, int offs) +{ + if (zbr->lnum == lnum && zbr->offs == offs) + return 1; + else + return 0; +} + +/** + * resolve_collision_directly - resolve a collision directly. + * @c: UBIFS file-system description object + * @key: key of directory entry + * @zn: znode is passed and returned here + * @n: zbranch number is passed and returned here + * @lnum: LEB number of dent node to match + * @offs: offset of dent node to match + * + * This function is used for "hashed" keys to make sure the found directory or + * extended attribute entry node is what was looked for. It is used when the + * flash address of the right node is known (@lnum:@offs) which makes it much + * easier to resolve collisions (no need to read entries and match full + * names). This function returns %1 and sets @zn and @n if the collision is + * resolved, %0 if @lnum:@offs is not found and @zn and @n are set to the + * previous directory entry. Otherwise a negative error code is returned. + */ +static int resolve_collision_directly(struct ubifs_info *c, + const union ubifs_key *key, + struct ubifs_znode **zn, int *n, + int lnum, int offs) +{ + struct ubifs_znode *znode; + int nn, err; + + znode = *zn; + nn = *n; + if (matches_position(&znode->zbranch[nn], lnum, offs)) + return 1; + + /* Look left */ + while (1) { + err = tnc_prev(c, &znode, &nn); + if (err == -ENOENT) + break; + if (err < 0) + return err; + if (keys_cmp(c, &znode->zbranch[nn].key, key)) + break; + if (matches_position(&znode->zbranch[nn], lnum, offs)) { + *zn = znode; + *n = nn; + return 1; + } + } + + /* Look right */ + znode = *zn; + nn = *n; + while (1) { + err = tnc_next(c, &znode, &nn); + if (err == -ENOENT) + return 0; + if (err < 0) + return err; + if (keys_cmp(c, &znode->zbranch[nn].key, key)) + return 0; + *zn = znode; + *n = nn; + if (matches_position(&znode->zbranch[nn], lnum, offs)) + return 1; + } +} + +/** + * dirty_cow_bottom_up - dirty a znode and its ancestors. + * @c: UBIFS file-system description object + * @znode: znode to dirty + * + * If we do not have a unique key that resides in a znode, then we cannot + * dirty that znode from the top down (i.e. by using lookup_level0_dirty) + * This function records the path back to the last dirty ancestor, and then + * dirties the znodes on that path. + */ +static struct ubifs_znode *dirty_cow_bottom_up(struct ubifs_info *c, + struct ubifs_znode *znode) +{ + struct ubifs_znode *zp; + int *path = c->bottom_up_buf, p = 0; + + ubifs_assert(c, c->zroot.znode); + ubifs_assert(c, znode); + if (c->zroot.znode->level > BOTTOM_UP_HEIGHT) { + kfree(c->bottom_up_buf); + c->bottom_up_buf = kmalloc_array(c->zroot.znode->level, + sizeof(int), + GFP_NOFS); + if (!c->bottom_up_buf) + return ERR_PTR(-ENOMEM); + path = c->bottom_up_buf; + } + if (c->zroot.znode->level) { + /* Go up until parent is dirty */ + while (1) { + int n; + + zp = znode->parent; + if (!zp) + break; + n = znode->iip; + ubifs_assert(c, p < c->zroot.znode->level); + path[p++] = n; + if (!zp->cnext && ubifs_zn_dirty(znode)) + break; + znode = zp; + } + } + + /* Come back down, dirtying as we go */ + while (1) { + struct ubifs_zbranch *zbr; + + zp = znode->parent; + if (zp) { + ubifs_assert(c, path[p - 1] >= 0); + ubifs_assert(c, path[p - 1] < zp->child_cnt); + zbr = &zp->zbranch[path[--p]]; + znode = dirty_cow_znode(c, zbr); + } else { + ubifs_assert(c, znode == c->zroot.znode); + znode = dirty_cow_znode(c, &c->zroot); + } + if (IS_ERR(znode) || !p) + break; + ubifs_assert(c, path[p - 1] >= 0); + ubifs_assert(c, path[p - 1] < znode->child_cnt); + znode = znode->zbranch[path[p - 1]].znode; + } + + return znode; +} + +/** + * ubifs_lookup_level0 - search for zero-level znode. + * @c: UBIFS file-system description object + * @key: key to lookup + * @zn: znode is returned here + * @n: znode branch slot number is returned here + * + * This function looks up the TNC tree and search for zero-level znode which + * refers key @key. The found zero-level znode is returned in @zn. There are 3 + * cases: + * o exact match, i.e. the found zero-level znode contains key @key, then %1 + * is returned and slot number of the matched branch is stored in @n; + * o not exact match, which means that zero-level znode does not contain + * @key, then %0 is returned and slot number of the closest branch or %-1 + * is stored in @n; In this case calling tnc_next() is mandatory. + * o @key is so small that it is even less than the lowest key of the + * leftmost zero-level node, then %0 is returned and %0 is stored in @n. + * + * Note, when the TNC tree is traversed, some znodes may be absent, then this + * function reads corresponding indexing nodes and inserts them to TNC. In + * case of failure, a negative error code is returned. + */ +int ubifs_lookup_level0(struct ubifs_info *c, const union ubifs_key *key, + struct ubifs_znode **zn, int *n) +{ + int err, exact; + struct ubifs_znode *znode; + time64_t time = ktime_get_seconds(); + + dbg_tnck(key, "search key "); + ubifs_assert(c, key_type(c, key) < UBIFS_INVALID_KEY); + + znode = c->zroot.znode; + if (unlikely(!znode)) { + znode = ubifs_load_znode(c, &c->zroot, NULL, 0); + if (IS_ERR(znode)) + return PTR_ERR(znode); + } + + znode->time = time; + + while (1) { + struct ubifs_zbranch *zbr; + + exact = ubifs_search_zbranch(c, znode, key, n); + + if (znode->level == 0) + break; + + if (*n < 0) + *n = 0; + zbr = &znode->zbranch[*n]; + + if (zbr->znode) { + znode->time = time; + znode = zbr->znode; + continue; + } + + /* znode is not in TNC cache, load it from the media */ + znode = ubifs_load_znode(c, zbr, znode, *n); + if (IS_ERR(znode)) + return PTR_ERR(znode); + } + + *zn = znode; + if (exact || !is_hash_key(c, key) || *n != -1) { + dbg_tnc("found %d, lvl %d, n %d", exact, znode->level, *n); + return exact; + } + + /* + * Here is a tricky place. We have not found the key and this is a + * "hashed" key, which may collide. The rest of the code deals with + * situations like this: + * + * | 3 | 5 | + * / \ + * | 3 | 5 | | 6 | 7 | (x) + * + * Or more a complex example: + * + * | 1 | 5 | + * / \ + * | 1 | 3 | | 5 | 8 | + * \ / + * | 5 | 5 | | 6 | 7 | (x) + * + * In the examples, if we are looking for key "5", we may reach nodes + * marked with "(x)". In this case what we have do is to look at the + * left and see if there is "5" key there. If there is, we have to + * return it. + * + * Note, this whole situation is possible because we allow to have + * elements which are equivalent to the next key in the parent in the + * children of current znode. For example, this happens if we split a + * znode like this: | 3 | 5 | 5 | 6 | 7 |, which results in something + * like this: + * | 3 | 5 | + * / \ + * | 3 | 5 | | 5 | 6 | 7 | + * ^ + * And this becomes what is at the first "picture" after key "5" marked + * with "^" is removed. What could be done is we could prohibit + * splitting in the middle of the colliding sequence. Also, when + * removing the leftmost key, we would have to correct the key of the + * parent node, which would introduce additional complications. Namely, + * if we changed the leftmost key of the parent znode, the garbage + * collector would be unable to find it (GC is doing this when GC'ing + * indexing LEBs). Although we already have an additional RB-tree where + * we save such changed znodes (see 'ins_clr_old_idx_znode()') until + * after the commit. But anyway, this does not look easy to implement + * so we did not try this. + */ + err = tnc_prev(c, &znode, n); + if (err == -ENOENT) { + dbg_tnc("found 0, lvl %d, n -1", znode->level); + *n = -1; + return 0; + } + if (unlikely(err < 0)) + return err; + if (keys_cmp(c, key, &znode->zbranch[*n].key)) { + dbg_tnc("found 0, lvl %d, n -1", znode->level); + *n = -1; + return 0; + } + + dbg_tnc("found 1, lvl %d, n %d", znode->level, *n); + *zn = znode; + return 1; +} + +/** + * lookup_level0_dirty - search for zero-level znode dirtying. + * @c: UBIFS file-system description object + * @key: key to lookup + * @zn: znode is returned here + * @n: znode branch slot number is returned here + * + * This function looks up the TNC tree and search for zero-level znode which + * refers key @key. The found zero-level znode is returned in @zn. There are 3 + * cases: + * o exact match, i.e. the found zero-level znode contains key @key, then %1 + * is returned and slot number of the matched branch is stored in @n; + * o not exact match, which means that zero-level znode does not contain @key + * then %0 is returned and slot number of the closed branch is stored in + * @n; + * o @key is so small that it is even less than the lowest key of the + * leftmost zero-level node, then %0 is returned and %-1 is stored in @n. + * + * Additionally all znodes in the path from the root to the located zero-level + * znode are marked as dirty. + * + * Note, when the TNC tree is traversed, some znodes may be absent, then this + * function reads corresponding indexing nodes and inserts them to TNC. In + * case of failure, a negative error code is returned. + */ +static int lookup_level0_dirty(struct ubifs_info *c, const union ubifs_key *key, + struct ubifs_znode **zn, int *n) +{ + int err, exact; + struct ubifs_znode *znode; + time64_t time = ktime_get_seconds(); + + dbg_tnck(key, "search and dirty key "); + + znode = c->zroot.znode; + if (unlikely(!znode)) { + znode = ubifs_load_znode(c, &c->zroot, NULL, 0); + if (IS_ERR(znode)) + return PTR_ERR(znode); + } + + znode = dirty_cow_znode(c, &c->zroot); + if (IS_ERR(znode)) + return PTR_ERR(znode); + + znode->time = time; + + while (1) { + struct ubifs_zbranch *zbr; + + exact = ubifs_search_zbranch(c, znode, key, n); + + if (znode->level == 0) + break; + + if (*n < 0) + *n = 0; + zbr = &znode->zbranch[*n]; + + if (zbr->znode) { + znode->time = time; + znode = dirty_cow_znode(c, zbr); + if (IS_ERR(znode)) + return PTR_ERR(znode); + continue; + } + + /* znode is not in TNC cache, load it from the media */ + znode = ubifs_load_znode(c, zbr, znode, *n); + if (IS_ERR(znode)) + return PTR_ERR(znode); + znode = dirty_cow_znode(c, zbr); + if (IS_ERR(znode)) + return PTR_ERR(znode); + } + + *zn = znode; + if (exact || !is_hash_key(c, key) || *n != -1) { + dbg_tnc("found %d, lvl %d, n %d", exact, znode->level, *n); + return exact; + } + + /* + * See huge comment at 'lookup_level0_dirty()' what is the rest of the + * code. + */ + err = tnc_prev(c, &znode, n); + if (err == -ENOENT) { + *n = -1; + dbg_tnc("found 0, lvl %d, n -1", znode->level); + return 0; + } + if (unlikely(err < 0)) + return err; + if (keys_cmp(c, key, &znode->zbranch[*n].key)) { + *n = -1; + dbg_tnc("found 0, lvl %d, n -1", znode->level); + return 0; + } + + if (znode->cnext || !ubifs_zn_dirty(znode)) { + znode = dirty_cow_bottom_up(c, znode); + if (IS_ERR(znode)) + return PTR_ERR(znode); + } + + dbg_tnc("found 1, lvl %d, n %d", znode->level, *n); + *zn = znode; + return 1; +} + +/** + * ubifs_tnc_locate - look up a file-system node and return it and its location. + * @c: UBIFS file-system description object + * @key: node key to lookup + * @node: the node is returned here + * @lnum: LEB number is returned here + * @offs: offset is returned here + * + * This function looks up and reads node with key @key. The caller has to make + * sure the @node buffer is large enough to fit the node. Returns zero in case + * of success, %-ENOENT if the node was not found, and a negative error code in + * case of failure. The node location can be returned in @lnum and @offs. + */ +int ubifs_tnc_locate(struct ubifs_info *c, const union ubifs_key *key, + void *node, int *lnum, int *offs) +{ + int found, n, err; + struct ubifs_znode *znode; + struct ubifs_zbranch *zt; + + mutex_lock(&c->tnc_mutex); + found = ubifs_lookup_level0(c, key, &znode, &n); + if (!found) { + err = -ENOENT; + goto out; + } else if (found < 0) { + err = found; + goto out; + } + zt = &znode->zbranch[n]; + if (lnum) { + *lnum = zt->lnum; + *offs = zt->offs; + } + if (is_hash_key(c, key)) { + /* + * In this case the leaf node cache gets used, so we pass the + * address of the zbranch and keep the mutex locked + */ + err = tnc_read_hashed_node(c, zt, node); + goto out; + } + err = ubifs_tnc_read_node(c, zt, node); + +out: + mutex_unlock(&c->tnc_mutex); + return err; +} + +/** + * do_lookup_nm- look up a "hashed" node. + * @c: UBIFS file-system description object + * @key: node key to lookup + * @node: the node is returned here + * @nm: node name + * + * This function looks up and reads a node which contains name hash in the key. + * Since the hash may have collisions, there may be many nodes with the same + * key, so we have to sequentially look to all of them until the needed one is + * found. This function returns zero in case of success, %-ENOENT if the node + * was not found, and a negative error code in case of failure. + */ +static int do_lookup_nm(struct ubifs_info *c, const union ubifs_key *key, + void *node, const struct fscrypt_name *nm) +{ + int found, n, err; + struct ubifs_znode *znode; + + dbg_tnck(key, "key "); + mutex_lock(&c->tnc_mutex); + found = ubifs_lookup_level0(c, key, &znode, &n); + if (!found) { + err = -ENOENT; + goto out_unlock; + } else if (found < 0) { + err = found; + goto out_unlock; + } + + ubifs_assert(c, n >= 0); + + err = resolve_collision(c, key, &znode, &n, nm); + dbg_tnc("rc returned %d, znode %p, n %d", err, znode, n); + if (unlikely(err < 0)) + goto out_unlock; + if (err == 0) { + err = -ENOENT; + goto out_unlock; + } + + err = tnc_read_hashed_node(c, &znode->zbranch[n], node); + +out_unlock: + mutex_unlock(&c->tnc_mutex); + return err; +} + +/** + * ubifs_tnc_lookup_nm - look up a "hashed" node. + * @c: UBIFS file-system description object + * @key: node key to lookup + * @node: the node is returned here + * @nm: node name + * + * This function looks up and reads a node which contains name hash in the key. + * Since the hash may have collisions, there may be many nodes with the same + * key, so we have to sequentially look to all of them until the needed one is + * found. This function returns zero in case of success, %-ENOENT if the node + * was not found, and a negative error code in case of failure. + */ +int ubifs_tnc_lookup_nm(struct ubifs_info *c, const union ubifs_key *key, + void *node, const struct fscrypt_name *nm) +{ + int err, len; + const struct ubifs_dent_node *dent = node; + + /* + * We assume that in most of the cases there are no name collisions and + * 'ubifs_tnc_lookup()' returns us the right direntry. + */ + err = ubifs_tnc_lookup(c, key, node); + if (err) + return err; + + len = le16_to_cpu(dent->nlen); + if (fname_len(nm) == len && !memcmp(dent->name, fname_name(nm), len)) + return 0; + + /* + * Unluckily, there are hash collisions and we have to iterate over + * them look at each direntry with colliding name hash sequentially. + */ + + return do_lookup_nm(c, key, node, nm); +} + +/** + * correct_parent_keys - correct parent znodes' keys. + * @c: UBIFS file-system description object + * @znode: znode to correct parent znodes for + * + * This is a helper function for 'tnc_insert()'. When the key of the leftmost + * zbranch changes, keys of parent znodes have to be corrected. This helper + * function is called in such situations and corrects the keys if needed. + */ +static void correct_parent_keys(const struct ubifs_info *c, + struct ubifs_znode *znode) +{ + union ubifs_key *key, *key1; + + ubifs_assert(c, znode->parent); + ubifs_assert(c, znode->iip == 0); + + key = &znode->zbranch[0].key; + key1 = &znode->parent->zbranch[0].key; + + while (keys_cmp(c, key, key1) < 0) { + key_copy(c, key, key1); + znode = znode->parent; + znode->alt = 1; + if (!znode->parent || znode->iip) + break; + key1 = &znode->parent->zbranch[0].key; + } +} + +/** + * insert_zbranch - insert a zbranch into a znode. + * @c: UBIFS file-system description object + * @znode: znode into which to insert + * @zbr: zbranch to insert + * @n: slot number to insert to + * + * This is a helper function for 'tnc_insert()'. UBIFS does not allow "gaps" in + * znode's array of zbranches and keeps zbranches consolidated, so when a new + * zbranch has to be inserted to the @znode->zbranches[]' array at the @n-th + * slot, zbranches starting from @n have to be moved right. + */ +static void insert_zbranch(struct ubifs_info *c, struct ubifs_znode *znode, + const struct ubifs_zbranch *zbr, int n) +{ + int i; + + ubifs_assert(c, ubifs_zn_dirty(znode)); + + if (znode->level) { + for (i = znode->child_cnt; i > n; i--) { + znode->zbranch[i] = znode->zbranch[i - 1]; + if (znode->zbranch[i].znode) + znode->zbranch[i].znode->iip = i; + } + if (zbr->znode) + zbr->znode->iip = n; + } else + for (i = znode->child_cnt; i > n; i--) + znode->zbranch[i] = znode->zbranch[i - 1]; + + znode->zbranch[n] = *zbr; + znode->child_cnt += 1; + + /* + * After inserting at slot zero, the lower bound of the key range of + * this znode may have changed. If this znode is subsequently split + * then the upper bound of the key range may change, and furthermore + * it could change to be lower than the original lower bound. If that + * happens, then it will no longer be possible to find this znode in the + * TNC using the key from the index node on flash. That is bad because + * if it is not found, we will assume it is obsolete and may overwrite + * it. Then if there is an unclean unmount, we will start using the + * old index which will be broken. + * + * So we first mark znodes that have insertions at slot zero, and then + * if they are split we add their lnum/offs to the old_idx tree. + */ + if (n == 0) + znode->alt = 1; +} + +/** + * tnc_insert - insert a node into TNC. + * @c: UBIFS file-system description object + * @znode: znode to insert into + * @zbr: branch to insert + * @n: slot number to insert new zbranch to + * + * This function inserts a new node described by @zbr into znode @znode. If + * znode does not have a free slot for new zbranch, it is split. Parent znodes + * are splat as well if needed. Returns zero in case of success or a negative + * error code in case of failure. + */ +static int tnc_insert(struct ubifs_info *c, struct ubifs_znode *znode, + struct ubifs_zbranch *zbr, int n) +{ + struct ubifs_znode *zn, *zi, *zp; + int i, keep, move, appending = 0; + union ubifs_key *key = &zbr->key, *key1; + + ubifs_assert(c, n >= 0 && n <= c->fanout); + + /* Implement naive insert for now */ +again: + zp = znode->parent; + if (znode->child_cnt < c->fanout) { + ubifs_assert(c, n != c->fanout); + dbg_tnck(key, "inserted at %d level %d, key ", n, znode->level); + + insert_zbranch(c, znode, zbr, n); + + /* Ensure parent's key is correct */ + if (n == 0 && zp && znode->iip == 0) + correct_parent_keys(c, znode); + + return 0; + } + + /* + * Unfortunately, @znode does not have more empty slots and we have to + * split it. + */ + dbg_tnck(key, "splitting level %d, key ", znode->level); + + if (znode->alt) + /* + * We can no longer be sure of finding this znode by key, so we + * record it in the old_idx tree. + */ + ins_clr_old_idx_znode(c, znode); + + zn = kzalloc(c->max_znode_sz, GFP_NOFS); + if (!zn) + return -ENOMEM; + zn->parent = zp; + zn->level = znode->level; + + /* Decide where to split */ + if (znode->level == 0 && key_type(c, key) == UBIFS_DATA_KEY) { + /* Try not to split consecutive data keys */ + if (n == c->fanout) { + key1 = &znode->zbranch[n - 1].key; + if (key_inum(c, key1) == key_inum(c, key) && + key_type(c, key1) == UBIFS_DATA_KEY) + appending = 1; + } else + goto check_split; + } else if (appending && n != c->fanout) { + /* Try not to split consecutive data keys */ + appending = 0; +check_split: + if (n >= (c->fanout + 1) / 2) { + key1 = &znode->zbranch[0].key; + if (key_inum(c, key1) == key_inum(c, key) && + key_type(c, key1) == UBIFS_DATA_KEY) { + key1 = &znode->zbranch[n].key; + if (key_inum(c, key1) != key_inum(c, key) || + key_type(c, key1) != UBIFS_DATA_KEY) { + keep = n; + move = c->fanout - keep; + zi = znode; + goto do_split; + } + } + } + } + + if (appending) { + keep = c->fanout; + move = 0; + } else { + keep = (c->fanout + 1) / 2; + move = c->fanout - keep; + } + + /* + * Although we don't at present, we could look at the neighbors and see + * if we can move some zbranches there. + */ + + if (n < keep) { + /* Insert into existing znode */ + zi = znode; + move += 1; + keep -= 1; + } else { + /* Insert into new znode */ + zi = zn; + n -= keep; + /* Re-parent */ + if (zn->level != 0) + zbr->znode->parent = zn; + } + +do_split: + + __set_bit(DIRTY_ZNODE, &zn->flags); + atomic_long_inc(&c->dirty_zn_cnt); + + zn->child_cnt = move; + znode->child_cnt = keep; + + dbg_tnc("moving %d, keeping %d", move, keep); + + /* Move zbranch */ + for (i = 0; i < move; i++) { + zn->zbranch[i] = znode->zbranch[keep + i]; + /* Re-parent */ + if (zn->level != 0) + if (zn->zbranch[i].znode) { + zn->zbranch[i].znode->parent = zn; + zn->zbranch[i].znode->iip = i; + } + } + + /* Insert new key and branch */ + dbg_tnck(key, "inserting at %d level %d, key ", n, zn->level); + + insert_zbranch(c, zi, zbr, n); + + /* Insert new znode (produced by spitting) into the parent */ + if (zp) { + if (n == 0 && zi == znode && znode->iip == 0) + correct_parent_keys(c, znode); + + /* Locate insertion point */ + n = znode->iip + 1; + + /* Tail recursion */ + zbr->key = zn->zbranch[0].key; + zbr->znode = zn; + zbr->lnum = 0; + zbr->offs = 0; + zbr->len = 0; + znode = zp; + + goto again; + } + + /* We have to split root znode */ + dbg_tnc("creating new zroot at level %d", znode->level + 1); + + zi = kzalloc(c->max_znode_sz, GFP_NOFS); + if (!zi) + return -ENOMEM; + + zi->child_cnt = 2; + zi->level = znode->level + 1; + + __set_bit(DIRTY_ZNODE, &zi->flags); + atomic_long_inc(&c->dirty_zn_cnt); + + zi->zbranch[0].key = znode->zbranch[0].key; + zi->zbranch[0].znode = znode; + zi->zbranch[0].lnum = c->zroot.lnum; + zi->zbranch[0].offs = c->zroot.offs; + zi->zbranch[0].len = c->zroot.len; + zi->zbranch[1].key = zn->zbranch[0].key; + zi->zbranch[1].znode = zn; + + c->zroot.lnum = 0; + c->zroot.offs = 0; + c->zroot.len = 0; + c->zroot.znode = zi; + + zn->parent = zi; + zn->iip = 1; + znode->parent = zi; + znode->iip = 0; + + return 0; +} + +/** + * ubifs_tnc_add - add a node to TNC. + * @c: UBIFS file-system description object + * @key: key to add + * @lnum: LEB number of node + * @offs: node offset + * @len: node length + * @hash: The hash over the node + * + * This function adds a node with key @key to TNC. The node may be new or it may + * obsolete some existing one. Returns %0 on success or negative error code on + * failure. + */ +int ubifs_tnc_add(struct ubifs_info *c, const union ubifs_key *key, int lnum, + int offs, int len, const u8 *hash) +{ + int found, n, err = 0; + struct ubifs_znode *znode; + + mutex_lock(&c->tnc_mutex); + dbg_tnck(key, "%d:%d, len %d, key ", lnum, offs, len); + found = lookup_level0_dirty(c, key, &znode, &n); + if (!found) { + struct ubifs_zbranch zbr; + + zbr.znode = NULL; + zbr.lnum = lnum; + zbr.offs = offs; + zbr.len = len; + ubifs_copy_hash(c, hash, zbr.hash); + key_copy(c, key, &zbr.key); + err = tnc_insert(c, znode, &zbr, n + 1); + } else if (found == 1) { + struct ubifs_zbranch *zbr = &znode->zbranch[n]; + + lnc_free(zbr); + err = ubifs_add_dirt(c, zbr->lnum, zbr->len); + zbr->lnum = lnum; + zbr->offs = offs; + zbr->len = len; + ubifs_copy_hash(c, hash, zbr->hash); + } else + err = found; + if (!err) + err = dbg_check_tnc(c, 0); + mutex_unlock(&c->tnc_mutex); + + return err; +} + +/** + * ubifs_tnc_replace - replace a node in the TNC only if the old node is found. + * @c: UBIFS file-system description object + * @key: key to add + * @old_lnum: LEB number of old node + * @old_offs: old node offset + * @lnum: LEB number of node + * @offs: node offset + * @len: node length + * + * This function replaces a node with key @key in the TNC only if the old node + * is found. This function is called by garbage collection when node are moved. + * Returns %0 on success or negative error code on failure. + */ +int ubifs_tnc_replace(struct ubifs_info *c, const union ubifs_key *key, + int old_lnum, int old_offs, int lnum, int offs, int len) +{ + int found, n, err = 0; + struct ubifs_znode *znode; + + mutex_lock(&c->tnc_mutex); + dbg_tnck(key, "old LEB %d:%d, new LEB %d:%d, len %d, key ", old_lnum, + old_offs, lnum, offs, len); + found = lookup_level0_dirty(c, key, &znode, &n); + if (found < 0) { + err = found; + goto out_unlock; + } + + if (found == 1) { + struct ubifs_zbranch *zbr = &znode->zbranch[n]; + + found = 0; + if (zbr->lnum == old_lnum && zbr->offs == old_offs) { + lnc_free(zbr); + err = ubifs_add_dirt(c, zbr->lnum, zbr->len); + if (err) + goto out_unlock; + zbr->lnum = lnum; + zbr->offs = offs; + zbr->len = len; + found = 1; + } else if (is_hash_key(c, key)) { + found = resolve_collision_directly(c, key, &znode, &n, + old_lnum, old_offs); + dbg_tnc("rc returned %d, znode %p, n %d, LEB %d:%d", + found, znode, n, old_lnum, old_offs); + if (found < 0) { + err = found; + goto out_unlock; + } + + if (found) { + /* Ensure the znode is dirtied */ + if (znode->cnext || !ubifs_zn_dirty(znode)) { + znode = dirty_cow_bottom_up(c, znode); + if (IS_ERR(znode)) { + err = PTR_ERR(znode); + goto out_unlock; + } + } + zbr = &znode->zbranch[n]; + lnc_free(zbr); + err = ubifs_add_dirt(c, zbr->lnum, + zbr->len); + if (err) + goto out_unlock; + zbr->lnum = lnum; + zbr->offs = offs; + zbr->len = len; + } + } + } + + if (!found) + err = ubifs_add_dirt(c, lnum, len); + + if (!err) + err = dbg_check_tnc(c, 0); + +out_unlock: + mutex_unlock(&c->tnc_mutex); + return err; +} + +/** + * ubifs_tnc_add_nm - add a "hashed" node to TNC. + * @c: UBIFS file-system description object + * @key: key to add + * @lnum: LEB number of node + * @offs: node offset + * @len: node length + * @hash: The hash over the node + * @nm: node name + * + * This is the same as 'ubifs_tnc_add()' but it should be used with keys which + * may have collisions, like directory entry keys. + */ +int ubifs_tnc_add_nm(struct ubifs_info *c, const union ubifs_key *key, + int lnum, int offs, int len, const u8 *hash, + const struct fscrypt_name *nm) +{ + int found, n, err = 0; + struct ubifs_znode *znode; + + mutex_lock(&c->tnc_mutex); + dbg_tnck(key, "LEB %d:%d, key ", lnum, offs); + found = lookup_level0_dirty(c, key, &znode, &n); + if (found < 0) { + err = found; + goto out_unlock; + } + + if (found == 1) { + if (c->replaying) + found = fallible_resolve_collision(c, key, &znode, &n, + nm, 1); + else + found = resolve_collision(c, key, &znode, &n, nm); + dbg_tnc("rc returned %d, znode %p, n %d", found, znode, n); + if (found < 0) { + err = found; + goto out_unlock; + } + + /* Ensure the znode is dirtied */ + if (znode->cnext || !ubifs_zn_dirty(znode)) { + znode = dirty_cow_bottom_up(c, znode); + if (IS_ERR(znode)) { + err = PTR_ERR(znode); + goto out_unlock; + } + } + + if (found == 1) { + struct ubifs_zbranch *zbr = &znode->zbranch[n]; + + lnc_free(zbr); + err = ubifs_add_dirt(c, zbr->lnum, zbr->len); + zbr->lnum = lnum; + zbr->offs = offs; + zbr->len = len; + ubifs_copy_hash(c, hash, zbr->hash); + goto out_unlock; + } + } + + if (!found) { + struct ubifs_zbranch zbr; + + zbr.znode = NULL; + zbr.lnum = lnum; + zbr.offs = offs; + zbr.len = len; + ubifs_copy_hash(c, hash, zbr.hash); + key_copy(c, key, &zbr.key); + err = tnc_insert(c, znode, &zbr, n + 1); + if (err) + goto out_unlock; + if (c->replaying) { + /* + * We did not find it in the index so there may be a + * dangling branch still in the index. So we remove it + * by passing 'ubifs_tnc_remove_nm()' the same key but + * an unmatchable name. + */ + struct fscrypt_name noname = { .disk_name = { .name = "", .len = 1 } }; + + err = dbg_check_tnc(c, 0); + mutex_unlock(&c->tnc_mutex); + if (err) + return err; + return ubifs_tnc_remove_nm(c, key, &noname); + } + } + +out_unlock: + if (!err) + err = dbg_check_tnc(c, 0); + mutex_unlock(&c->tnc_mutex); + return err; +} + +/** + * tnc_delete - delete a znode form TNC. + * @c: UBIFS file-system description object + * @znode: znode to delete from + * @n: zbranch slot number to delete + * + * This function deletes a leaf node from @n-th slot of @znode. Returns zero in + * case of success and a negative error code in case of failure. + */ +static int tnc_delete(struct ubifs_info *c, struct ubifs_znode *znode, int n) +{ + struct ubifs_zbranch *zbr; + struct ubifs_znode *zp; + int i, err; + + /* Delete without merge for now */ + ubifs_assert(c, znode->level == 0); + ubifs_assert(c, n >= 0 && n < c->fanout); + dbg_tnck(&znode->zbranch[n].key, "deleting key "); + + zbr = &znode->zbranch[n]; + lnc_free(zbr); + + err = ubifs_add_dirt(c, zbr->lnum, zbr->len); + if (err) { + ubifs_dump_znode(c, znode); + return err; + } + + /* We do not "gap" zbranch slots */ + for (i = n; i < znode->child_cnt - 1; i++) + znode->zbranch[i] = znode->zbranch[i + 1]; + znode->child_cnt -= 1; + + if (znode->child_cnt > 0) + return 0; + + /* Different with linux kernel, TNC could become empty. */ + if (!znode->parent) + return 0; + + /* + * This was the last zbranch, we have to delete this znode from the + * parent. + */ + + do { + ubifs_assert(c, !ubifs_zn_obsolete(znode)); + ubifs_assert(c, ubifs_zn_dirty(znode)); + + zp = znode->parent; + n = znode->iip; + + atomic_long_dec(&c->dirty_zn_cnt); + + err = insert_old_idx_znode(c, znode); + if (err) + return err; + + if (znode->cnext) { + __set_bit(OBSOLETE_ZNODE, &znode->flags); + atomic_long_inc(&c->clean_zn_cnt); + atomic_long_inc(&ubifs_clean_zn_cnt); + } else + kfree(znode); + znode = zp; + } while (znode->child_cnt == 1); /* while removing last child */ + + /* Remove from znode, entry n - 1 */ + znode->child_cnt -= 1; + ubifs_assert(c, znode->level != 0); + for (i = n; i < znode->child_cnt; i++) { + znode->zbranch[i] = znode->zbranch[i + 1]; + if (znode->zbranch[i].znode) + znode->zbranch[i].znode->iip = i; + } + + /* + * If this is the root and it has only 1 child then + * collapse the tree. + */ + if (!znode->parent) { + while (znode->child_cnt == 1 && znode->level != 0) { + zp = znode; + zbr = &znode->zbranch[0]; + znode = get_znode(c, znode, 0); + if (IS_ERR(znode)) + return PTR_ERR(znode); + znode = dirty_cow_znode(c, zbr); + if (IS_ERR(znode)) + return PTR_ERR(znode); + znode->parent = NULL; + znode->iip = 0; + if (c->zroot.len) { + err = insert_old_idx(c, c->zroot.lnum, + c->zroot.offs); + if (err) + return err; + } + c->zroot.lnum = zbr->lnum; + c->zroot.offs = zbr->offs; + c->zroot.len = zbr->len; + c->zroot.znode = znode; + ubifs_assert(c, !ubifs_zn_obsolete(zp)); + ubifs_assert(c, ubifs_zn_dirty(zp)); + atomic_long_dec(&c->dirty_zn_cnt); + + if (zp->cnext) { + __set_bit(OBSOLETE_ZNODE, &zp->flags); + atomic_long_inc(&c->clean_zn_cnt); + atomic_long_inc(&ubifs_clean_zn_cnt); + } else + kfree(zp); + } + } + + return 0; +} + +/** + * ubifs_tnc_remove - remove an index entry of a node. + * @c: UBIFS file-system description object + * @key: key of node + * + * Returns %0 on success or negative error code on failure. + */ +int ubifs_tnc_remove(struct ubifs_info *c, const union ubifs_key *key) +{ + int found, n, err = 0; + struct ubifs_znode *znode; + + mutex_lock(&c->tnc_mutex); + dbg_tnck(key, "key "); + found = lookup_level0_dirty(c, key, &znode, &n); + if (found < 0) { + err = found; + goto out_unlock; + } + if (found == 1) + err = tnc_delete(c, znode, n); + if (!err) + err = dbg_check_tnc(c, 0); + +out_unlock: + mutex_unlock(&c->tnc_mutex); + return err; +} + +/** + * ubifs_tnc_remove_nm - remove an index entry for a "hashed" node. + * @c: UBIFS file-system description object + * @key: key of node + * @nm: directory entry name + * + * Returns %0 on success or negative error code on failure. + */ +int ubifs_tnc_remove_nm(struct ubifs_info *c, const union ubifs_key *key, + const struct fscrypt_name *nm) +{ + int n, err; + struct ubifs_znode *znode; + + mutex_lock(&c->tnc_mutex); + dbg_tnck(key, "key "); + err = lookup_level0_dirty(c, key, &znode, &n); + if (err < 0) + goto out_unlock; + + if (err) { + if (c->replaying) + err = fallible_resolve_collision(c, key, &znode, &n, + nm, 0); + else + err = resolve_collision(c, key, &znode, &n, nm); + dbg_tnc("rc returned %d, znode %p, n %d", err, znode, n); + if (err < 0) + goto out_unlock; + if (err) { + /* Ensure the znode is dirtied */ + if (znode->cnext || !ubifs_zn_dirty(znode)) { + znode = dirty_cow_bottom_up(c, znode); + if (IS_ERR(znode)) { + err = PTR_ERR(znode); + goto out_unlock; + } + } + err = tnc_delete(c, znode, n); + } + } + +out_unlock: + if (!err) + err = dbg_check_tnc(c, 0); + mutex_unlock(&c->tnc_mutex); + return err; +} + +/** + * key_in_range - determine if a key falls within a range of keys. + * @c: UBIFS file-system description object + * @key: key to check + * @from_key: lowest key in range + * @to_key: highest key in range + * + * This function returns %1 if the key is in range and %0 otherwise. + */ +static int key_in_range(struct ubifs_info *c, union ubifs_key *key, + union ubifs_key *from_key, union ubifs_key *to_key) +{ + if (keys_cmp(c, key, from_key) < 0) + return 0; + if (keys_cmp(c, key, to_key) > 0) + return 0; + return 1; +} + +/** + * ubifs_tnc_remove_range - remove index entries in range. + * @c: UBIFS file-system description object + * @from_key: lowest key to remove + * @to_key: highest key to remove + * + * This function removes index entries starting at @from_key and ending at + * @to_key. This function returns zero in case of success and a negative error + * code in case of failure. + */ +int ubifs_tnc_remove_range(struct ubifs_info *c, union ubifs_key *from_key, + union ubifs_key *to_key) +{ + int i, n, k, err = 0; + struct ubifs_znode *znode; + union ubifs_key *key; + + mutex_lock(&c->tnc_mutex); + while (1) { + /* Find first level 0 znode that contains keys to remove */ + err = ubifs_lookup_level0(c, from_key, &znode, &n); + if (err < 0) + goto out_unlock; + + if (err) + key = from_key; + else { + err = tnc_next(c, &znode, &n); + if (err == -ENOENT) { + err = 0; + goto out_unlock; + } + if (err < 0) + goto out_unlock; + key = &znode->zbranch[n].key; + if (!key_in_range(c, key, from_key, to_key)) { + err = 0; + goto out_unlock; + } + } + + /* Ensure the znode is dirtied */ + if (znode->cnext || !ubifs_zn_dirty(znode)) { + znode = dirty_cow_bottom_up(c, znode); + if (IS_ERR(znode)) { + err = PTR_ERR(znode); + goto out_unlock; + } + } + + /* Remove all keys in range except the first */ + for (i = n + 1, k = 0; i < znode->child_cnt; i++, k++) { + key = &znode->zbranch[i].key; + if (!key_in_range(c, key, from_key, to_key)) + break; + lnc_free(&znode->zbranch[i]); + err = ubifs_add_dirt(c, znode->zbranch[i].lnum, + znode->zbranch[i].len); + if (err) { + ubifs_dump_znode(c, znode); + goto out_unlock; + } + dbg_tnck(key, "removing key "); + } + if (k) { + for (i = n + 1 + k; i < znode->child_cnt; i++) + znode->zbranch[i - k] = znode->zbranch[i]; + znode->child_cnt -= k; + } + + /* Now delete the first */ + err = tnc_delete(c, znode, n); + if (err) + goto out_unlock; + } + +out_unlock: + if (!err) + err = dbg_check_tnc(c, 0); + mutex_unlock(&c->tnc_mutex); + return err; +} + +/** + * ubifs_tnc_remove_ino - remove an inode from TNC. + * @c: UBIFS file-system description object + * @inum: inode number to remove + * + * This function remove inode @inum and all the extended attributes associated + * with the anode from TNC and returns zero in case of success or a negative + * error code in case of failure. + */ +int ubifs_tnc_remove_ino(struct ubifs_info *c, ino_t inum) +{ + union ubifs_key key1, key2; + struct ubifs_dent_node *xent, *pxent = NULL; + struct fscrypt_name nm = {0}; + + dbg_tnc("ino %lu", (unsigned long)inum); + + /* + * Walk all extended attribute entries and remove them together with + * corresponding extended attribute inodes. + */ + lowest_xent_key(c, &key1, inum); + while (1) { + ino_t xattr_inum; + int err; + + xent = ubifs_tnc_next_ent(c, &key1, &nm); + if (IS_ERR(xent)) { + unsigned int reason; + + err = PTR_ERR(xent); + if (err == -ENOENT) + break; + + reason = get_failure_reason_callback(c); + if (reason & FR_DATA_CORRUPTED) { + test_and_clear_failure_reason_callback(c, FR_DATA_CORRUPTED); + if (handle_failure_callback(c, FR_H_TNC_DATA_CORRUPTED, NULL)) { + /* Set %FR_LPT_INCORRECT for lpt status. */ + set_lpt_invalid_callback(c, FR_LPT_INCORRECT); + /* Leave xattrs to be deleted by subsequent steps */ + break; + } + } + kfree(pxent); + return err; + } + + xattr_inum = le64_to_cpu(xent->inum); + dbg_tnc("xent '%s', ino %lu", xent->name, + (unsigned long)xattr_inum); + + fname_name(&nm) = (const char *)xent->name; + fname_len(&nm) = le16_to_cpu(xent->nlen); + err = ubifs_tnc_remove_nm(c, &key1, &nm); + if (err) { + kfree(pxent); + kfree(xent); + return err; + } + + lowest_ino_key(c, &key1, xattr_inum); + highest_ino_key(c, &key2, xattr_inum); + err = ubifs_tnc_remove_range(c, &key1, &key2); + if (err) { + kfree(pxent); + kfree(xent); + return err; + } + + kfree(pxent); + pxent = xent; + key_read(c, &xent->key, &key1); + } + + kfree(pxent); + lowest_ino_key(c, &key1, inum); + highest_ino_key(c, &key2, inum); + + return ubifs_tnc_remove_range(c, &key1, &key2); +} + +/** + * ubifs_tnc_remove_node - remove an index entry of a node by given position. + * @c: UBIFS file-system description object + * @key: key of node + * @lnum: LEB number of node + * @offs: node offset + * + * Returns %0 on success or negative error code on failure. + */ +int ubifs_tnc_remove_node(struct ubifs_info *c, const union ubifs_key *key, + int lnum, int offs) +{ + int found, n, err = 0; + struct ubifs_znode *znode; + + mutex_lock(&c->tnc_mutex); + dbg_tnck(key, "pos %d:%d, key ", lnum, offs); + found = lookup_level0_dirty(c, key, &znode, &n); + if (found < 0) { + err = found; + goto out_unlock; + } + if (found == 1) { + struct ubifs_zbranch *zbr = &znode->zbranch[n]; + + if (zbr->lnum == lnum && zbr->offs == offs) { + err = tnc_delete(c, znode, n); + } else if (is_hash_key(c, key)) { + found = resolve_collision_directly(c, key, &znode, &n, + lnum, offs); + if (found < 0) { + err = found; + goto out_unlock; + } + + if (found) { + /* Ensure the znode is dirtied */ + if (znode->cnext || !ubifs_zn_dirty(znode)) { + znode = dirty_cow_bottom_up(c, znode); + if (IS_ERR(znode)) { + err = PTR_ERR(znode); + goto out_unlock; + } + } + err = tnc_delete(c, znode, n); + } else { + goto not_found; + } + } else { + goto not_found; + } + } else { +not_found: + /* Impossible, the node has been found before being deleted. */ + ubifs_assert(c, 0); + } + if (!err) + err = dbg_check_tnc(c, 0); + +out_unlock: + mutex_unlock(&c->tnc_mutex); + return err; +} + +/** + * ubifs_tnc_next_ent - walk directory or extended attribute entries. + * @c: UBIFS file-system description object + * @key: key of last entry + * @nm: name of last entry found or %NULL + * + * This function finds and reads the next directory or extended attribute entry + * after the given key (@key) if there is one. @nm is used to resolve + * collisions. + * + * If the name of the current entry is not known and only the key is known, + * @nm->name has to be %NULL. In this case the semantics of this function is a + * little bit different and it returns the entry corresponding to this key, not + * the next one. If the key was not found, the closest "right" entry is + * returned. + * + * If the fist entry has to be found, @key has to contain the lowest possible + * key value for this inode and @name has to be %NULL. + * + * This function returns the found directory or extended attribute entry node + * in case of success, %-ENOENT is returned if no entry was found, and a + * negative error code is returned in case of failure. + */ +struct ubifs_dent_node *ubifs_tnc_next_ent(struct ubifs_info *c, + union ubifs_key *key, + const struct fscrypt_name *nm) +{ + int n, err, type = key_type(c, key); + struct ubifs_znode *znode; + struct ubifs_dent_node *dent; + struct ubifs_zbranch *zbr; + union ubifs_key *dkey; + + dbg_tnck(key, "key "); + ubifs_assert(c, is_hash_key(c, key)); + + mutex_lock(&c->tnc_mutex); + err = ubifs_lookup_level0(c, key, &znode, &n); + if (unlikely(err < 0)) + goto out_unlock; + + if (fname_len(nm) > 0) { + if (err) { + /* Handle collisions */ + if (c->replaying) + err = fallible_resolve_collision(c, key, &znode, &n, + nm, 0); + else + err = resolve_collision(c, key, &znode, &n, nm); + dbg_tnc("rc returned %d, znode %p, n %d", + err, znode, n); + if (unlikely(err < 0)) + goto out_unlock; + } + + /* Now find next entry */ + err = tnc_next(c, &znode, &n); + if (unlikely(err)) + goto out_unlock; + } else { + /* + * The full name of the entry was not given, in which case the + * behavior of this function is a little different and it + * returns current entry, not the next one. + */ + if (!err) { + /* + * However, the given key does not exist in the TNC + * tree and @znode/@n variables contain the closest + * "preceding" element. Switch to the next one. + */ + err = tnc_next(c, &znode, &n); + if (err) + goto out_unlock; + } + } + + zbr = &znode->zbranch[n]; + dent = kmalloc(zbr->len, GFP_NOFS); + if (unlikely(!dent)) { + err = -ENOMEM; + goto out_unlock; + } + + /* + * The above 'tnc_next()' call could lead us to the next inode, check + * this. + */ + dkey = &zbr->key; + if (key_inum(c, dkey) != key_inum(c, key) || + key_type(c, dkey) != type) { + err = -ENOENT; + goto out_free; + } + + err = tnc_read_hashed_node(c, zbr, dent); + if (unlikely(err)) + goto out_free; + + mutex_unlock(&c->tnc_mutex); + return dent; + +out_free: + kfree(dent); +out_unlock: + mutex_unlock(&c->tnc_mutex); + return ERR_PTR(err); +} + +/** + * tnc_destroy_cnext - destroy left-over obsolete znodes from a failed commit. + * @c: UBIFS file-system description object + * + * Destroy left-over obsolete znodes from a failed commit. + */ +static void tnc_destroy_cnext(struct ubifs_info *c) +{ + struct ubifs_znode *cnext; + + if (!c->cnext) + return; + ubifs_assert(c, c->cmt_state == COMMIT_BROKEN); + cnext = c->cnext; + do { + struct ubifs_znode *znode = cnext; + + cnext = cnext->cnext; + if (ubifs_zn_obsolete(znode)) + kfree(znode); + else if (!ubifs_zn_cow(znode)) { + /* + * Don't forget to update clean znode count after + * committing failed, because ubifs will check this + * count while closing tnc. Non-obsolete znode could + * be re-dirtied during committing process, so dirty + * flag is untrustable. The flag 'COW_ZNODE' is set + * for each dirty znode before committing, and it is + * cleared as long as the znode become clean, so we + * can statistic clean znode count according to this + * flag. + */ + atomic_long_inc(&c->clean_zn_cnt); + atomic_long_inc(&ubifs_clean_zn_cnt); + } + } while (cnext && cnext != c->cnext); +} + +/** + * ubifs_tnc_close - close TNC subsystem and free all related resources. + * @c: UBIFS file-system description object + */ +void ubifs_tnc_close(struct ubifs_info *c) +{ + tnc_destroy_cnext(c); + ubifs_destroy_tnc_tree(c); + kfree(c->gap_lebs); + kfree(c->ilebs); + destroy_old_idx(c); +} + +/** + * left_znode - get the znode to the left. + * @c: UBIFS file-system description object + * @znode: znode + * + * This function returns a pointer to the znode to the left of @znode or NULL if + * there is not one. A negative error code is returned on failure. + */ +static struct ubifs_znode *left_znode(struct ubifs_info *c, + struct ubifs_znode *znode) +{ + int level = znode->level; + + while (1) { + int n = znode->iip - 1; + + /* Go up until we can go left */ + znode = znode->parent; + if (!znode) + return NULL; + if (n >= 0) { + /* Now go down the rightmost branch to 'level' */ + znode = get_znode(c, znode, n); + if (IS_ERR(znode)) + return znode; + while (znode->level != level) { + n = znode->child_cnt - 1; + znode = get_znode(c, znode, n); + if (IS_ERR(znode)) + return znode; + } + break; + } + } + return znode; +} + +/** + * right_znode - get the znode to the right. + * @c: UBIFS file-system description object + * @znode: znode + * + * This function returns a pointer to the znode to the right of @znode or NULL + * if there is not one. A negative error code is returned on failure. + */ +static struct ubifs_znode *right_znode(struct ubifs_info *c, + struct ubifs_znode *znode) +{ + int level = znode->level; + + while (1) { + int n = znode->iip + 1; + + /* Go up until we can go right */ + znode = znode->parent; + if (!znode) + return NULL; + if (n < znode->child_cnt) { + /* Now go down the leftmost branch to 'level' */ + znode = get_znode(c, znode, n); + if (IS_ERR(znode)) + return znode; + while (znode->level != level) { + znode = get_znode(c, znode, 0); + if (IS_ERR(znode)) + return znode; + } + break; + } + } + return znode; +} + +/** + * lookup_znode - find a particular indexing node from TNC. + * @c: UBIFS file-system description object + * @key: index node key to lookup + * @level: index node level + * @lnum: index node LEB number + * @offs: index node offset + * + * This function searches an indexing node by its first key @key and its + * address @lnum:@offs. It looks up the indexing tree by pulling all indexing + * nodes it traverses to TNC. This function is called for indexing nodes which + * were found on the media by scanning, for example when garbage-collecting or + * when doing in-the-gaps commit. This means that the indexing node which is + * looked for does not have to have exactly the same leftmost key @key, because + * the leftmost key may have been changed, in which case TNC will contain a + * dirty znode which still refers the same @lnum:@offs. This function is clever + * enough to recognize such indexing nodes. + * + * Note, if a znode was deleted or changed too much, then this function will + * not find it. For situations like this UBIFS has the old index RB-tree + * (indexed by @lnum:@offs). + * + * This function returns a pointer to the znode found or %NULL if it is not + * found. A negative error code is returned on failure. + */ +static struct ubifs_znode *lookup_znode(struct ubifs_info *c, + union ubifs_key *key, int level, + int lnum, int offs) +{ + struct ubifs_znode *znode, *zn; + int n, nn; + + ubifs_assert(c, key_type(c, key) < UBIFS_INVALID_KEY); + + /* + * The arguments have probably been read off flash, so don't assume + * they are valid. + */ + if (level < 0) + return ERR_PTR(-EINVAL); + + /* Get the root znode */ + znode = c->zroot.znode; + if (!znode) { + znode = ubifs_load_znode(c, &c->zroot, NULL, 0); + if (IS_ERR(znode)) + return znode; + } + /* Check if it is the one we are looking for */ + if (c->zroot.lnum == lnum && c->zroot.offs == offs) + return znode; + /* Descend to the parent level i.e. (level + 1) */ + if (level >= znode->level) + return NULL; + while (1) { + ubifs_search_zbranch(c, znode, key, &n); + if (n < 0) { + /* + * We reached a znode where the leftmost key is greater + * than the key we are searching for. This is the same + * situation as the one described in a huge comment at + * the end of the 'ubifs_lookup_level0()' function. And + * for exactly the same reasons we have to try to look + * left before giving up. + */ + znode = left_znode(c, znode); + if (!znode) + return NULL; + if (IS_ERR(znode)) + return znode; + ubifs_search_zbranch(c, znode, key, &n); + ubifs_assert(c, n >= 0); + } + if (znode->level == level + 1) + break; + znode = get_znode(c, znode, n); + if (IS_ERR(znode)) + return znode; + } + /* Check if the child is the one we are looking for */ + if (znode->zbranch[n].lnum == lnum && znode->zbranch[n].offs == offs) + return get_znode(c, znode, n); + /* If the key is unique, there is nowhere else to look */ + if (!is_hash_key(c, key)) + return NULL; + /* + * The key is not unique and so may be also in the znodes to either + * side. + */ + zn = znode; + nn = n; + /* Look left */ + while (1) { + /* Move one branch to the left */ + if (n) + n -= 1; + else { + znode = left_znode(c, znode); + if (!znode) + break; + if (IS_ERR(znode)) + return znode; + n = znode->child_cnt - 1; + } + /* Check it */ + if (znode->zbranch[n].lnum == lnum && + znode->zbranch[n].offs == offs) + return get_znode(c, znode, n); + /* Stop if the key is less than the one we are looking for */ + if (keys_cmp(c, &znode->zbranch[n].key, key) < 0) + break; + } + /* Back to the middle */ + znode = zn; + n = nn; + /* Look right */ + while (1) { + /* Move one branch to the right */ + if (++n >= znode->child_cnt) { + znode = right_znode(c, znode); + if (!znode) + break; + if (IS_ERR(znode)) + return znode; + n = 0; + } + /* Check it */ + if (znode->zbranch[n].lnum == lnum && + znode->zbranch[n].offs == offs) + return get_znode(c, znode, n); + /* Stop if the key is greater than the one we are looking for */ + if (keys_cmp(c, &znode->zbranch[n].key, key) > 0) + break; + } + return NULL; +} + +/** + * is_idx_node_in_tnc - determine if an index node is in the TNC. + * @c: UBIFS file-system description object + * @key: key of index node + * @level: index node level + * @lnum: LEB number of index node + * @offs: offset of index node + * + * This function returns %0 if the index node is not referred to in the TNC, %1 + * if the index node is referred to in the TNC and the corresponding znode is + * dirty, %2 if an index node is referred to in the TNC and the corresponding + * znode is clean, and a negative error code in case of failure. + * + * Note, the @key argument has to be the key of the first child. Also note, + * this function relies on the fact that 0:0 is never a valid LEB number and + * offset for a main-area node. + */ +int is_idx_node_in_tnc(struct ubifs_info *c, union ubifs_key *key, int level, + int lnum, int offs) +{ + struct ubifs_znode *znode; + + znode = lookup_znode(c, key, level, lnum, offs); + if (!znode) + return 0; + if (IS_ERR(znode)) + return PTR_ERR(znode); + + return ubifs_zn_dirty(znode) ? 1 : 2; +} + +/** + * is_leaf_node_in_tnc - determine if a non-indexing not is in the TNC. + * @c: UBIFS file-system description object + * @key: node key + * @lnum: node LEB number + * @offs: node offset + * + * This function returns %1 if the node is referred to in the TNC, %0 if it is + * not, and a negative error code in case of failure. + * + * Note, this function relies on the fact that 0:0 is never a valid LEB number + * and offset for a main-area node. + */ +static int is_leaf_node_in_tnc(struct ubifs_info *c, union ubifs_key *key, + int lnum, int offs) +{ + struct ubifs_zbranch *zbr; + struct ubifs_znode *znode, *zn; + int n, found, err, nn; + const int unique = !is_hash_key(c, key); + + found = ubifs_lookup_level0(c, key, &znode, &n); + if (found < 0) + return found; /* Error code */ + if (!found) + return 0; + zbr = &znode->zbranch[n]; + if (lnum == zbr->lnum && offs == zbr->offs) + return 1; /* Found it */ + if (unique) + return 0; + /* + * Because the key is not unique, we have to look left + * and right as well + */ + zn = znode; + nn = n; + /* Look left */ + while (1) { + err = tnc_prev(c, &znode, &n); + if (err == -ENOENT) + break; + if (err) + return err; + if (keys_cmp(c, key, &znode->zbranch[n].key)) + break; + zbr = &znode->zbranch[n]; + if (lnum == zbr->lnum && offs == zbr->offs) + return 1; /* Found it */ + } + /* Look right */ + znode = zn; + n = nn; + while (1) { + err = tnc_next(c, &znode, &n); + if (err) { + if (err == -ENOENT) + return 0; + return err; + } + if (keys_cmp(c, key, &znode->zbranch[n].key)) + break; + zbr = &znode->zbranch[n]; + if (lnum == zbr->lnum && offs == zbr->offs) + return 1; /* Found it */ + } + return 0; +} + +/** + * ubifs_tnc_has_node - determine whether a node is in the TNC. + * @c: UBIFS file-system description object + * @key: node key + * @level: index node level (if it is an index node) + * @lnum: node LEB number + * @offs: node offset + * @is_idx: non-zero if the node is an index node + * + * This function returns %1 if the node is in the TNC, %0 if it is not, and a + * negative error code in case of failure. For index nodes, @key has to be the + * key of the first child. An index node is considered to be in the TNC only if + * the corresponding znode is clean or has not been loaded. + */ +int ubifs_tnc_has_node(struct ubifs_info *c, union ubifs_key *key, int level, + int lnum, int offs, int is_idx) +{ + int err; + + mutex_lock(&c->tnc_mutex); + if (is_idx) { + err = is_idx_node_in_tnc(c, key, level, lnum, offs); + if (err < 0) + goto out_unlock; + if (err == 1) + /* The index node was found but it was dirty */ + err = 0; + else if (err == 2) + /* The index node was found and it was clean */ + err = 1; + else + BUG_ON(err != 0); + } else + err = is_leaf_node_in_tnc(c, key, lnum, offs); + +out_unlock: + mutex_unlock(&c->tnc_mutex); + return err; +} + +/** + * ubifs_dirty_idx_node - dirty an index node. + * @c: UBIFS file-system description object + * @key: index node key + * @level: index node level + * @lnum: index node LEB number + * @offs: index node offset + * + * This function loads and dirties an index node so that it can be garbage + * collected. The @key argument has to be the key of the first child. This + * function relies on the fact that 0:0 is never a valid LEB number and offset + * for a main-area node. Returns %0 on success and a negative error code on + * failure. + */ +int ubifs_dirty_idx_node(struct ubifs_info *c, union ubifs_key *key, int level, + int lnum, int offs) +{ + struct ubifs_znode *znode; + int err = 0; + + mutex_lock(&c->tnc_mutex); + znode = lookup_znode(c, key, level, lnum, offs); + if (!znode) + goto out_unlock; + if (IS_ERR(znode)) { + err = PTR_ERR(znode); + goto out_unlock; + } + znode = dirty_cow_bottom_up(c, znode); + if (IS_ERR(znode)) { + err = PTR_ERR(znode); + goto out_unlock; + } + +out_unlock: + mutex_unlock(&c->tnc_mutex); + return err; +} diff --git a/ubifs-utils/libubifs/tnc_commit.c b/ubifs-utils/libubifs/tnc_commit.c new file mode 100644 index 0000000..66922d4 --- /dev/null +++ b/ubifs-utils/libubifs/tnc_commit.c @@ -0,0 +1,1119 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * This file is part of UBIFS. + * + * Copyright (C) 2006-2008 Nokia Corporation. + * + * Authors: Adrian Hunter + * Artem Bityutskiy (Битюцкий Артём) + */ + +/* This file implements TNC functions for committing */ + +#include "linux_err.h" +#include "bitops.h" +#include "kmem.h" +#include "ubifs.h" +#include "defs.h" +#include "debug.h" +#include "key.h" +#include "misc.h" + +/** + * make_idx_node - make an index node for fill-the-gaps method of TNC commit. + * @c: UBIFS file-system description object + * @idx: buffer in which to place new index node + * @znode: znode from which to make new index node + * @lnum: LEB number where new index node will be written + * @offs: offset where new index node will be written + * @len: length of new index node + */ +static int make_idx_node(struct ubifs_info *c, struct ubifs_idx_node *idx, + struct ubifs_znode *znode, int lnum, int offs, int len) +{ + struct ubifs_znode *zp; + u8 hash[UBIFS_HASH_ARR_SZ]; + int i, err; + + /* Make index node */ + idx->ch.node_type = UBIFS_IDX_NODE; + idx->child_cnt = cpu_to_le16(znode->child_cnt); + idx->level = cpu_to_le16(znode->level); + for (i = 0; i < znode->child_cnt; i++) { + struct ubifs_branch *br = ubifs_idx_branch(c, idx, i); + struct ubifs_zbranch *zbr = &znode->zbranch[i]; + + key_write_idx(c, &zbr->key, &br->key); + br->lnum = cpu_to_le32(zbr->lnum); + br->offs = cpu_to_le32(zbr->offs); + br->len = cpu_to_le32(zbr->len); + ubifs_copy_hash(c, zbr->hash, ubifs_branch_hash(c, br)); + if (!zbr->lnum || !zbr->len) { + ubifs_err(c, "bad ref in znode"); + ubifs_dump_znode(c, znode); + if (zbr->znode) + ubifs_dump_znode(c, zbr->znode); + + return -EINVAL; + } + } + ubifs_prepare_node(c, idx, len, 0); + ubifs_node_calc_hash(c, idx, hash); + + znode->lnum = lnum; + znode->offs = offs; + znode->len = len; + + err = insert_old_idx_znode(c, znode); + + /* Update the parent */ + zp = znode->parent; + if (zp) { + struct ubifs_zbranch *zbr; + + zbr = &zp->zbranch[znode->iip]; + zbr->lnum = lnum; + zbr->offs = offs; + zbr->len = len; + ubifs_copy_hash(c, hash, zbr->hash); + } else { + c->zroot.lnum = lnum; + c->zroot.offs = offs; + c->zroot.len = len; + ubifs_copy_hash(c, hash, c->zroot.hash); + } + c->calc_idx_sz += ALIGN(len, 8); + + atomic_long_dec(&c->dirty_zn_cnt); + + ubifs_assert(c, ubifs_zn_dirty(znode)); + ubifs_assert(c, ubifs_zn_cow(znode)); + + /* + * Note, unlike 'write_index()' we do not add memory barriers here + * because this function is called with @c->tnc_mutex locked. + */ + __clear_bit(DIRTY_ZNODE, &znode->flags); + __clear_bit(COW_ZNODE, &znode->flags); + + return err; +} + +/** + * fill_gap - make index nodes in gaps in dirty index LEBs. + * @c: UBIFS file-system description object + * @lnum: LEB number that gap appears in + * @gap_start: offset of start of gap + * @gap_end: offset of end of gap + * @dirt: adds dirty space to this + * + * This function returns the number of index nodes written into the gap. + */ +static int fill_gap(struct ubifs_info *c, int lnum, int gap_start, int gap_end, + int *dirt) +{ + int len, gap_remains, gap_pos, written, pad_len; + + ubifs_assert(c, (gap_start & 7) == 0); + ubifs_assert(c, (gap_end & 7) == 0); + ubifs_assert(c, gap_end >= gap_start); + + gap_remains = gap_end - gap_start; + if (!gap_remains) + return 0; + gap_pos = gap_start; + written = 0; + while (c->enext) { + len = ubifs_idx_node_sz(c, c->enext->child_cnt); + if (len < gap_remains) { + struct ubifs_znode *znode = c->enext; + const int alen = ALIGN(len, 8); + int err; + + ubifs_assert(c, alen <= gap_remains); + err = make_idx_node(c, c->ileb_buf + gap_pos, znode, + lnum, gap_pos, len); + if (err) + return err; + gap_remains -= alen; + gap_pos += alen; + c->enext = znode->cnext; + if (c->enext == c->cnext) + c->enext = NULL; + written += 1; + } else + break; + } + if (gap_end == c->leb_size) { + c->ileb_len = ALIGN(gap_pos, c->min_io_size); + /* Pad to end of min_io_size */ + pad_len = c->ileb_len - gap_pos; + } else + /* Pad to end of gap */ + pad_len = gap_remains; + dbg_gc("LEB %d:%d to %d len %d nodes written %d wasted bytes %d", + lnum, gap_start, gap_end, gap_end - gap_start, written, pad_len); + ubifs_pad(c, c->ileb_buf + gap_pos, pad_len); + *dirt += pad_len; + return written; +} + +/** + * find_old_idx - find an index node obsoleted since the last commit start. + * @c: UBIFS file-system description object + * @lnum: LEB number of obsoleted index node + * @offs: offset of obsoleted index node + * + * Returns %1 if found and %0 otherwise. + */ +static int find_old_idx(struct ubifs_info *c, int lnum, int offs) +{ + struct ubifs_old_idx *o; + struct rb_node *p; + + p = c->old_idx.rb_node; + while (p) { + o = rb_entry(p, struct ubifs_old_idx, rb); + if (lnum < o->lnum) + p = p->rb_left; + else if (lnum > o->lnum) + p = p->rb_right; + else if (offs < o->offs) + p = p->rb_left; + else if (offs > o->offs) + p = p->rb_right; + else + return 1; + } + return 0; +} + +/** + * is_idx_node_in_use - determine if an index node can be overwritten. + * @c: UBIFS file-system description object + * @key: key of index node + * @level: index node level + * @lnum: LEB number of index node + * @offs: offset of index node + * + * If @key / @lnum / @offs identify an index node that was not part of the old + * index, then this function returns %0 (obsolete). Else if the index node was + * part of the old index but is now dirty %1 is returned, else if it is clean %2 + * is returned. A negative error code is returned on failure. + */ +static int is_idx_node_in_use(struct ubifs_info *c, union ubifs_key *key, + int level, int lnum, int offs) +{ + int ret; + + ret = is_idx_node_in_tnc(c, key, level, lnum, offs); + if (ret < 0) + return ret; /* Error code */ + if (ret == 0) + if (find_old_idx(c, lnum, offs)) + return 1; + return ret; +} + +/** + * layout_leb_in_gaps - layout index nodes using in-the-gaps method. + * @c: UBIFS file-system description object + * @p: return LEB number in @c->gap_lebs[p] + * + * This function lays out new index nodes for dirty znodes using in-the-gaps + * method of TNC commit. + * This function merely puts the next znode into the next gap, making no attempt + * to try to maximise the number of znodes that fit. + * This function returns the number of index nodes written into the gaps, or a + * negative error code on failure. + */ +static int layout_leb_in_gaps(struct ubifs_info *c, int p) +{ + struct ubifs_scan_leb *sleb; + struct ubifs_scan_node *snod; + int lnum, dirt = 0, gap_start, gap_end, err, written, tot_written; + + tot_written = 0; + /* Get an index LEB with lots of obsolete index nodes */ + lnum = ubifs_find_dirty_idx_leb(c); + if (lnum < 0) + /* + * There also may be dirt in the index head that could be + * filled, however we do not check there at present. + */ + return lnum; /* Error code */ + c->gap_lebs[p] = lnum; + dbg_gc("LEB %d", lnum); + /* + * Scan the index LEB. We use the generic scan for this even though + * it is more comprehensive and less efficient than is needed for this + * purpose. + */ + sleb = ubifs_scan(c, lnum, 0, c->ileb_buf, 0); + c->ileb_len = 0; + if (IS_ERR(sleb)) + return PTR_ERR(sleb); + gap_start = 0; + list_for_each_entry(snod, &sleb->nodes, list) { + struct ubifs_idx_node *idx; + int in_use, level; + + ubifs_assert(c, snod->type == UBIFS_IDX_NODE); + idx = snod->node; + key_read(c, ubifs_idx_key(c, idx), &snod->key); + level = le16_to_cpu(idx->level); + /* Determine if the index node is in use (not obsolete) */ + in_use = is_idx_node_in_use(c, &snod->key, level, lnum, + snod->offs); + if (in_use < 0) { + ubifs_scan_destroy(sleb); + return in_use; /* Error code */ + } + if (in_use) { + if (in_use == 1) + dirt += ALIGN(snod->len, 8); + /* + * The obsolete index nodes form gaps that can be + * overwritten. This gap has ended because we have + * found an index node that is still in use + * i.e. not obsolete + */ + gap_end = snod->offs; + /* Try to fill gap */ + written = fill_gap(c, lnum, gap_start, gap_end, &dirt); + if (written < 0) { + ubifs_scan_destroy(sleb); + return written; /* Error code */ + } + tot_written += written; + gap_start = ALIGN(snod->offs + snod->len, 8); + } + } + ubifs_scan_destroy(sleb); + c->ileb_len = c->leb_size; + gap_end = c->leb_size; + /* Try to fill gap */ + written = fill_gap(c, lnum, gap_start, gap_end, &dirt); + if (written < 0) + return written; /* Error code */ + tot_written += written; + if (tot_written == 0) { + struct ubifs_lprops lp; + + dbg_gc("LEB %d wrote %d index nodes", lnum, tot_written); + err = ubifs_read_one_lp(c, lnum, &lp); + if (err) + return err; + if (lp.free == c->leb_size) { + /* + * We must have snatched this LEB from the idx_gc list + * so we need to correct the free and dirty space. + */ + err = ubifs_change_one_lp(c, lnum, + c->leb_size - c->ileb_len, + dirt, 0, 0, 0); + if (err) + return err; + } + return 0; + } + err = ubifs_change_one_lp(c, lnum, c->leb_size - c->ileb_len, dirt, + 0, 0, 0); + if (err) + return err; + err = ubifs_leb_change(c, lnum, c->ileb_buf, c->ileb_len); + if (err) + return err; + dbg_gc("LEB %d wrote %d index nodes", lnum, tot_written); + return tot_written; +} + +/** + * get_leb_cnt - calculate the number of empty LEBs needed to commit. + * @c: UBIFS file-system description object + * @cnt: number of znodes to commit + * + * This function returns the number of empty LEBs needed to commit @cnt znodes + * to the current index head. The number is not exact and may be more than + * needed. + */ +static int get_leb_cnt(struct ubifs_info *c, int cnt) +{ + int d; + + /* Assume maximum index node size (i.e. overestimate space needed) */ + cnt -= (c->leb_size - c->ihead_offs) / c->max_idx_node_sz; + if (cnt < 0) + cnt = 0; + d = c->leb_size / c->max_idx_node_sz; + return DIV_ROUND_UP(cnt, d); +} + +/** + * layout_in_gaps - in-the-gaps method of committing TNC. + * @c: UBIFS file-system description object + * @cnt: number of dirty znodes to commit. + * + * This function lays out new index nodes for dirty znodes using in-the-gaps + * method of TNC commit. + * + * This function returns %0 on success and a negative error code on failure. + */ +static int layout_in_gaps(struct ubifs_info *c, int cnt) +{ + int err, leb_needed_cnt, written, p = 0, old_idx_lebs, *gap_lebs; + + dbg_gc("%d znodes to write", cnt); + + c->gap_lebs = kmalloc_array(c->lst.idx_lebs + 1, sizeof(int), + GFP_NOFS); + if (!c->gap_lebs) + return -ENOMEM; + + old_idx_lebs = c->lst.idx_lebs; + do { + ubifs_assert(c, p < c->lst.idx_lebs); + written = layout_leb_in_gaps(c, p); + if (written < 0) { + err = written; + if (err != -ENOSPC) { + kfree(c->gap_lebs); + c->gap_lebs = NULL; + return err; + } + if (!dbg_is_chk_index(c)) { + /* + * Do not print scary warnings if the debugging + * option which forces in-the-gaps is enabled. + */ + ubifs_warn(c, "out of space"); + ubifs_dump_budg(c, &c->bi); + ubifs_dump_lprops(c); + } + /* Try to commit anyway */ + break; + } + p++; + cnt -= written; + leb_needed_cnt = get_leb_cnt(c, cnt); + dbg_gc("%d znodes remaining, need %d LEBs, have %d", cnt, + leb_needed_cnt, c->ileb_cnt); + /* + * Dynamically change the size of @c->gap_lebs to prevent + * oob, because @c->lst.idx_lebs could be increased by + * function @get_idx_gc_leb (called by layout_leb_in_gaps-> + * ubifs_find_dirty_idx_leb) during loop. Only enlarge + * @c->gap_lebs when needed. + * + */ + if (leb_needed_cnt > c->ileb_cnt && p >= old_idx_lebs && + old_idx_lebs < c->lst.idx_lebs) { + old_idx_lebs = c->lst.idx_lebs; + gap_lebs = krealloc(c->gap_lebs, sizeof(int) * + (old_idx_lebs + 1), GFP_NOFS); + if (!gap_lebs) { + kfree(c->gap_lebs); + c->gap_lebs = NULL; + return -ENOMEM; + } + c->gap_lebs = gap_lebs; + } + } while (leb_needed_cnt > c->ileb_cnt); + + c->gap_lebs[p] = -1; + return 0; +} + +/** + * layout_in_empty_space - layout index nodes in empty space. + * @c: UBIFS file-system description object + * + * This function lays out new index nodes for dirty znodes using empty LEBs. + * + * This function returns %0 on success and a negative error code on failure. + */ +static int layout_in_empty_space(struct ubifs_info *c) +{ + struct ubifs_znode *znode, *cnext, *zp; + int lnum, offs, len, next_len, buf_len, buf_offs, used, avail; + int wlen, blen, err; + + cnext = c->enext; + if (!cnext) + return 0; + + lnum = c->ihead_lnum; + buf_offs = c->ihead_offs; + + buf_len = ubifs_idx_node_sz(c, c->fanout); + buf_len = ALIGN(buf_len, c->min_io_size); + used = 0; + avail = buf_len; + + /* Ensure there is enough room for first write */ + next_len = ubifs_idx_node_sz(c, cnext->child_cnt); + if (buf_offs + next_len > c->leb_size) + lnum = -1; + + while (1) { + znode = cnext; + + len = ubifs_idx_node_sz(c, znode->child_cnt); + + /* Determine the index node position */ + if (lnum == -1) { + if (c->ileb_nxt >= c->ileb_cnt) { + ubifs_err(c, "out of space"); + return -ENOSPC; + } + lnum = c->ilebs[c->ileb_nxt++]; + buf_offs = 0; + used = 0; + avail = buf_len; + } + + offs = buf_offs + used; + + znode->lnum = lnum; + znode->offs = offs; + znode->len = len; + + /* Update the parent */ + zp = znode->parent; + if (zp) { + struct ubifs_zbranch *zbr; + int i; + + i = znode->iip; + zbr = &zp->zbranch[i]; + zbr->lnum = lnum; + zbr->offs = offs; + zbr->len = len; + } else { + c->zroot.lnum = lnum; + c->zroot.offs = offs; + c->zroot.len = len; + } + c->calc_idx_sz += ALIGN(len, 8); + + /* + * Once lprops is updated, we can decrease the dirty znode count + * but it is easier to just do it here. + */ + atomic_long_dec(&c->dirty_zn_cnt); + + /* + * Calculate the next index node length to see if there is + * enough room for it + */ + cnext = znode->cnext; + if (cnext == c->cnext) + next_len = 0; + else + next_len = ubifs_idx_node_sz(c, cnext->child_cnt); + + /* Update buffer positions */ + wlen = used + len; + used += ALIGN(len, 8); + avail -= ALIGN(len, 8); + + if (next_len != 0 && + buf_offs + used + next_len <= c->leb_size && + avail > 0) + continue; + + if (avail <= 0 && next_len && + buf_offs + used + next_len <= c->leb_size) + blen = buf_len; + else + blen = ALIGN(wlen, c->min_io_size); + + /* The buffer is full or there are no more znodes to do */ + buf_offs += blen; + if (next_len) { + if (buf_offs + next_len > c->leb_size) { + err = ubifs_update_one_lp(c, lnum, + c->leb_size - buf_offs, blen - used, + 0, 0); + if (err) + return err; + lnum = -1; + } + used -= blen; + if (used < 0) + used = 0; + avail = buf_len - used; + continue; + } + err = ubifs_update_one_lp(c, lnum, c->leb_size - buf_offs, + blen - used, 0, 0); + if (err) + return err; + break; + } + + c->new_ihead_lnum = lnum; + c->new_ihead_offs = buf_offs; + + return 0; +} + +/** + * layout_commit - determine positions of index nodes to commit. + * @c: UBIFS file-system description object + * @no_space: indicates that insufficient empty LEBs were allocated + * @cnt: number of znodes to commit + * + * Calculate and update the positions of index nodes to commit. If there were + * an insufficient number of empty LEBs allocated, then index nodes are placed + * into the gaps created by obsolete index nodes in non-empty index LEBs. For + * this purpose, an obsolete index node is one that was not in the index as at + * the end of the last commit. To write "in-the-gaps" requires that those index + * LEBs are updated atomically in-place. + */ +static int layout_commit(struct ubifs_info *c, int no_space, int cnt) +{ + int err; + + if (no_space) { + err = layout_in_gaps(c, cnt); + if (err) + return err; + } + err = layout_in_empty_space(c); + return err; +} + +/** + * find_first_dirty - find first dirty znode. + * @znode: znode to begin searching from + */ +static struct ubifs_znode *find_first_dirty(struct ubifs_znode *znode) +{ + int i, cont; + + if (!znode) + return NULL; + + while (1) { + if (znode->level == 0) { + if (ubifs_zn_dirty(znode)) + return znode; + return NULL; + } + cont = 0; + for (i = 0; i < znode->child_cnt; i++) { + struct ubifs_zbranch *zbr = &znode->zbranch[i]; + + if (zbr->znode && ubifs_zn_dirty(zbr->znode)) { + znode = zbr->znode; + cont = 1; + break; + } + } + if (!cont) { + if (ubifs_zn_dirty(znode)) + return znode; + return NULL; + } + } +} + +/** + * find_next_dirty - find next dirty znode. + * @znode: znode to begin searching from + */ +static struct ubifs_znode *find_next_dirty(struct ubifs_znode *znode) +{ + int n = znode->iip + 1; + + znode = znode->parent; + if (!znode) + return NULL; + for (; n < znode->child_cnt; n++) { + struct ubifs_zbranch *zbr = &znode->zbranch[n]; + + if (zbr->znode && ubifs_zn_dirty(zbr->znode)) + return find_first_dirty(zbr->znode); + } + return znode; +} + +/** + * get_znodes_to_commit - create list of dirty znodes to commit. + * @c: UBIFS file-system description object + * + * This function returns the number of znodes to commit. + */ +static int get_znodes_to_commit(struct ubifs_info *c) +{ + struct ubifs_znode *znode, *cnext; + int cnt = 0; + + c->cnext = find_first_dirty(c->zroot.znode); + znode = c->enext = c->cnext; + if (!znode) { + dbg_cmt("no znodes to commit"); + return 0; + } + cnt += 1; + while (1) { + ubifs_assert(c, !ubifs_zn_cow(znode)); + __set_bit(COW_ZNODE, &znode->flags); + znode->alt = 0; + cnext = find_next_dirty(znode); + if (!cnext) { + ubifs_assert(c, !znode->parent); + znode->cparent = NULL; + znode->cnext = c->cnext; + break; + } + znode->cparent = znode->parent; + znode->ciip = znode->iip; + znode->cnext = cnext; + znode = cnext; + cnt += 1; + } + dbg_cmt("committing %d znodes", cnt); + ubifs_assert(c, cnt == atomic_long_read(&c->dirty_zn_cnt)); + return cnt; +} + +/** + * alloc_idx_lebs - allocate empty LEBs to be used to commit. + * @c: UBIFS file-system description object + * @cnt: number of znodes to commit + * + * This function returns %-ENOSPC if it cannot allocate a sufficient number of + * empty LEBs. %0 is returned on success, otherwise a negative error code + * is returned. + */ +static int alloc_idx_lebs(struct ubifs_info *c, int cnt) +{ + int i, leb_cnt, lnum; + + c->ileb_cnt = 0; + c->ileb_nxt = 0; + leb_cnt = get_leb_cnt(c, cnt); + dbg_cmt("need about %d empty LEBS for TNC commit", leb_cnt); + if (!leb_cnt) + return 0; + c->ilebs = kmalloc_array(leb_cnt, sizeof(int), GFP_NOFS); + if (!c->ilebs) + return -ENOMEM; + for (i = 0; i < leb_cnt; i++) { + lnum = ubifs_find_free_leb_for_idx(c); + if (lnum < 0) + return lnum; + c->ilebs[c->ileb_cnt++] = lnum; + dbg_cmt("LEB %d", lnum); + } + if (dbg_is_chk_index(c)) + return -ENOSPC; + return 0; +} + +/** + * free_unused_idx_lebs - free unused LEBs that were allocated for the commit. + * @c: UBIFS file-system description object + * + * It is possible that we allocate more empty LEBs for the commit than we need. + * This functions frees the surplus. + * + * This function returns %0 on success and a negative error code on failure. + */ +static int free_unused_idx_lebs(struct ubifs_info *c) +{ + int i, err = 0, lnum, er; + + for (i = c->ileb_nxt; i < c->ileb_cnt; i++) { + lnum = c->ilebs[i]; + dbg_cmt("LEB %d", lnum); + er = ubifs_change_one_lp(c, lnum, LPROPS_NC, LPROPS_NC, 0, + LPROPS_INDEX | LPROPS_TAKEN, 0); + if (!err) + err = er; + } + return err; +} + +/** + * free_idx_lebs - free unused LEBs after commit end. + * @c: UBIFS file-system description object + * + * This function returns %0 on success and a negative error code on failure. + */ +static int free_idx_lebs(struct ubifs_info *c) +{ + int err; + + err = free_unused_idx_lebs(c); + kfree(c->ilebs); + c->ilebs = NULL; + return err; +} + +/** + * ubifs_tnc_start_commit - start TNC commit. + * @c: UBIFS file-system description object + * @zroot: new index root position is returned here + * + * This function prepares the list of indexing nodes to commit and lays out + * their positions on flash. If there is not enough free space it uses the + * in-gap commit method. Returns zero in case of success and a negative error + * code in case of failure. + */ +int ubifs_tnc_start_commit(struct ubifs_info *c, struct ubifs_zbranch *zroot) +{ + int err = 0, cnt; + + mutex_lock(&c->tnc_mutex); + err = dbg_check_tnc(c, 1); + if (err) + goto out; + cnt = get_znodes_to_commit(c); + if (cnt != 0) { + int no_space = 0; + + err = alloc_idx_lebs(c, cnt); + if (err == -ENOSPC) + no_space = 1; + else if (err) + goto out_free; + err = layout_commit(c, no_space, cnt); + if (err) + goto out_free; + ubifs_assert(c, atomic_long_read(&c->dirty_zn_cnt) == 0); + err = free_unused_idx_lebs(c); + if (err) + goto out; + } + destroy_old_idx(c); + memcpy(zroot, &c->zroot, sizeof(struct ubifs_zbranch)); + + err = ubifs_save_dirty_idx_lnums(c); + if (err) + goto out; + + spin_lock(&c->space_lock); + /* + * Although we have not finished committing yet, update size of the + * committed index ('c->bi.old_idx_sz') and zero out the index growth + * budget. It is OK to do this now, because we've reserved all the + * space which is needed to commit the index, and it is save for the + * budgeting subsystem to assume the index is already committed, + * even though it is not. + */ + ubifs_assert(c, c->bi.min_idx_lebs == ubifs_calc_min_idx_lebs(c)); + c->bi.old_idx_sz = c->calc_idx_sz; + c->bi.uncommitted_idx = 0; + c->bi.min_idx_lebs = ubifs_calc_min_idx_lebs(c); + spin_unlock(&c->space_lock); + mutex_unlock(&c->tnc_mutex); + + dbg_cmt("number of index LEBs %d", c->lst.idx_lebs); + dbg_cmt("size of index %llu", c->calc_idx_sz); + return err; + +out_free: + free_idx_lebs(c); +out: + mutex_unlock(&c->tnc_mutex); + return err; +} + +/** + * write_index - write index nodes. + * @c: UBIFS file-system description object + * + * This function writes the index nodes whose positions were laid out in the + * layout_in_empty_space function. + */ +static int write_index(struct ubifs_info *c) +{ + struct ubifs_idx_node *idx; + struct ubifs_znode *znode, *cnext; + int i, lnum, offs, len, next_len, buf_len, buf_offs, used; + int avail, wlen, err, lnum_pos = 0, blen, nxt_offs; + + cnext = c->enext; + if (!cnext) + return 0; + + /* + * Always write index nodes to the index head so that index nodes and + * other types of nodes are never mixed in the same erase block. + */ + lnum = c->ihead_lnum; + buf_offs = c->ihead_offs; + + /* Allocate commit buffer */ + buf_len = ALIGN(c->max_idx_node_sz, c->min_io_size); + used = 0; + avail = buf_len; + + /* Ensure there is enough room for first write */ + next_len = ubifs_idx_node_sz(c, cnext->child_cnt); + if (buf_offs + next_len > c->leb_size) { + err = ubifs_update_one_lp(c, lnum, LPROPS_NC, 0, 0, + LPROPS_TAKEN); + if (err) + return err; + lnum = -1; + } + + while (1) { + u8 hash[UBIFS_HASH_ARR_SZ]; + + cond_resched(); + + znode = cnext; + idx = c->cbuf + used; + + /* Make index node */ + idx->ch.node_type = UBIFS_IDX_NODE; + idx->child_cnt = cpu_to_le16(znode->child_cnt); + idx->level = cpu_to_le16(znode->level); + for (i = 0; i < znode->child_cnt; i++) { + struct ubifs_branch *br = ubifs_idx_branch(c, idx, i); + struct ubifs_zbranch *zbr = &znode->zbranch[i]; + + key_write_idx(c, &zbr->key, &br->key); + br->lnum = cpu_to_le32(zbr->lnum); + br->offs = cpu_to_le32(zbr->offs); + br->len = cpu_to_le32(zbr->len); + ubifs_copy_hash(c, zbr->hash, ubifs_branch_hash(c, br)); + if (!zbr->lnum || !zbr->len) { + ubifs_err(c, "bad ref in znode"); + ubifs_dump_znode(c, znode); + if (zbr->znode) + ubifs_dump_znode(c, zbr->znode); + + return -EINVAL; + } + } + len = ubifs_idx_node_sz(c, znode->child_cnt); + ubifs_prepare_node(c, idx, len, 0); + ubifs_node_calc_hash(c, idx, hash); + + mutex_lock(&c->tnc_mutex); + + if (znode->cparent) + ubifs_copy_hash(c, hash, + znode->cparent->zbranch[znode->ciip].hash); + + if (znode->parent) { + if (!ubifs_zn_obsolete(znode)) + ubifs_copy_hash(c, hash, + znode->parent->zbranch[znode->iip].hash); + } else { + ubifs_copy_hash(c, hash, c->zroot.hash); + } + + mutex_unlock(&c->tnc_mutex); + + /* Determine the index node position */ + if (lnum == -1) { + lnum = c->ilebs[lnum_pos++]; + buf_offs = 0; + used = 0; + avail = buf_len; + } + offs = buf_offs + used; + + if (lnum != znode->lnum || offs != znode->offs || + len != znode->len) { + ubifs_err(c, "inconsistent znode posn"); + return -EINVAL; + } + + /* Grab some stuff from znode while we still can */ + cnext = znode->cnext; + + ubifs_assert(c, ubifs_zn_dirty(znode)); + ubifs_assert(c, ubifs_zn_cow(znode)); + + /* + * It is important that other threads should see %DIRTY_ZNODE + * flag cleared before %COW_ZNODE. Specifically, it matters in + * the 'dirty_cow_znode()' function. This is the reason for the + * first barrier. Also, we want the bit changes to be seen to + * other threads ASAP, to avoid unnecessary copying, which is + * the reason for the second barrier. + */ + clear_bit(DIRTY_ZNODE, &znode->flags); + smp_mb__before_atomic(); + clear_bit(COW_ZNODE, &znode->flags); + smp_mb__after_atomic(); + + /* + * We have marked the znode as clean but have not updated the + * @c->clean_zn_cnt counter. If this znode becomes dirty again + * before 'free_obsolete_znodes()' is called, then + * @c->clean_zn_cnt will be decremented before it gets + * incremented (resulting in 2 decrements for the same znode). + * This means that @c->clean_zn_cnt may become negative for a + * while. + * + * Q: why we cannot increment @c->clean_zn_cnt? + * A: because we do not have the @c->tnc_mutex locked, and the + * following code would be racy and buggy: + * + * if (!ubifs_zn_obsolete(znode)) { + * atomic_long_inc(&c->clean_zn_cnt); + * atomic_long_inc(&ubifs_clean_zn_cnt); + * } + * + * Thus, we just delay the @c->clean_zn_cnt update until we + * have the mutex locked. + */ + + /* Do not access znode from this point on */ + + /* Update buffer positions */ + wlen = used + len; + used += ALIGN(len, 8); + avail -= ALIGN(len, 8); + + /* + * Calculate the next index node length to see if there is + * enough room for it + */ + if (cnext == c->cnext) + next_len = 0; + else + next_len = ubifs_idx_node_sz(c, cnext->child_cnt); + + nxt_offs = buf_offs + used + next_len; + if (next_len && nxt_offs <= c->leb_size) { + if (avail > 0) + continue; + else + blen = buf_len; + } else { + wlen = ALIGN(wlen, 8); + blen = ALIGN(wlen, c->min_io_size); + ubifs_pad(c, c->cbuf + wlen, blen - wlen); + } + + /* The buffer is full or there are no more znodes to do */ + err = ubifs_leb_write(c, lnum, c->cbuf, buf_offs, blen); + if (err) + return err; + buf_offs += blen; + if (next_len) { + if (nxt_offs > c->leb_size) { + err = ubifs_update_one_lp(c, lnum, LPROPS_NC, 0, + 0, LPROPS_TAKEN); + if (err) + return err; + lnum = -1; + } + used -= blen; + if (used < 0) + used = 0; + avail = buf_len - used; + memmove(c->cbuf, c->cbuf + blen, used); + continue; + } + break; + } + + if (lnum != c->new_ihead_lnum || + buf_offs != c->new_ihead_offs) { + ubifs_err(c, "inconsistent ihead"); + return -EINVAL; + } + + c->ihead_lnum = lnum; + c->ihead_offs = buf_offs; + + return 0; +} + +/** + * free_obsolete_znodes - free obsolete znodes. + * @c: UBIFS file-system description object + * + * At the end of commit end, obsolete znodes are freed. + */ +static void free_obsolete_znodes(struct ubifs_info *c) +{ + struct ubifs_znode *znode, *cnext; + + cnext = c->cnext; + do { + znode = cnext; + cnext = znode->cnext; + if (ubifs_zn_obsolete(znode)) + kfree(znode); + else { + znode->cnext = NULL; + atomic_long_inc(&c->clean_zn_cnt); + atomic_long_inc(&ubifs_clean_zn_cnt); + } + } while (cnext != c->cnext); +} + +/** + * return_gap_lebs - return LEBs used by the in-gap commit method. + * @c: UBIFS file-system description object + * + * This function clears the "taken" flag for the LEBs which were used by the + * "commit in-the-gaps" method. + */ +static int return_gap_lebs(struct ubifs_info *c) +{ + int *p, err; + + if (!c->gap_lebs) + return 0; + + dbg_cmt(""); + for (p = c->gap_lebs; *p != -1; p++) { + err = ubifs_change_one_lp(c, *p, LPROPS_NC, LPROPS_NC, 0, + LPROPS_TAKEN, 0); + if (err) + return err; + } + + kfree(c->gap_lebs); + c->gap_lebs = NULL; + return 0; +} + +/** + * ubifs_tnc_end_commit - update the TNC for commit end. + * @c: UBIFS file-system description object + * + * Write the dirty znodes. + */ +int ubifs_tnc_end_commit(struct ubifs_info *c) +{ + int err; + + if (!c->cnext) + return 0; + + err = return_gap_lebs(c); + if (err) + return err; + + err = write_index(c); + if (err) + return err; + + mutex_lock(&c->tnc_mutex); + + dbg_cmt("TNC height is %d", c->zroot.znode->level + 1); + + free_obsolete_znodes(c); + + c->cnext = NULL; + kfree(c->ilebs); + c->ilebs = NULL; + + mutex_unlock(&c->tnc_mutex); + + return 0; +} diff --git a/ubifs-utils/libubifs/tnc_misc.c b/ubifs-utils/libubifs/tnc_misc.c new file mode 100644 index 0000000..0ffb434 --- /dev/null +++ b/ubifs-utils/libubifs/tnc_misc.c @@ -0,0 +1,452 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * This file is part of UBIFS. + * + * Copyright (C) 2006-2008 Nokia Corporation. + * + * Authors: Adrian Hunter + * Artem Bityutskiy (Битюцкий Артём) + */ + +/* + * This file contains miscelanious TNC-related functions shared betweend + * different files. This file does not form any logically separate TNC + * sub-system. The file was created because there is a lot of TNC code and + * putting it all in one file would make that file too big and unreadable. + */ + +#include "linux_err.h" +#include "bitops.h" +#include "kmem.h" +#include "ubifs.h" +#include "defs.h" +#include "debug.h" +#include "key.h" +#include "misc.h" + +/** + * ubifs_search_zbranch - search znode branch. + * @c: UBIFS file-system description object + * @znode: znode to search in + * @key: key to search for + * @n: znode branch slot number is returned here + * + * This is a helper function which search branch with key @key in @znode using + * binary search. The result of the search may be: + * o exact match, then %1 is returned, and the slot number of the branch is + * stored in @n; + * o no exact match, then %0 is returned and the slot number of the left + * closest branch is returned in @n; the slot if all keys in this znode are + * greater than @key, then %-1 is returned in @n. + */ +int ubifs_search_zbranch(const struct ubifs_info *c, + const struct ubifs_znode *znode, + const union ubifs_key *key, int *n) +{ + int beg = 0, end = znode->child_cnt, mid; + int cmp; + const struct ubifs_zbranch *zbr = &znode->zbranch[0]; + + if (!end) { + /* Different with linux kernel, TNC could become empty. */ + *n = -1; + return 0; + } + + ubifs_assert(c, end > beg); + + while (end > beg) { + mid = (beg + end) >> 1; + cmp = keys_cmp(c, key, &zbr[mid].key); + if (cmp > 0) + beg = mid + 1; + else if (cmp < 0) + end = mid; + else { + *n = mid; + return 1; + } + } + + *n = end - 1; + + /* The insert point is after *n */ + ubifs_assert(c, *n >= -1 && *n < znode->child_cnt); + if (*n == -1) + ubifs_assert(c, keys_cmp(c, key, &zbr[0].key) < 0); + else + ubifs_assert(c, keys_cmp(c, key, &zbr[*n].key) > 0); + if (*n + 1 < znode->child_cnt) + ubifs_assert(c, keys_cmp(c, key, &zbr[*n + 1].key) < 0); + + return 0; +} + +/** + * ubifs_tnc_postorder_first - find first znode to do postorder tree traversal. + * @znode: znode to start at (root of the sub-tree to traverse) + * + * Find the lowest leftmost znode in a subtree of the TNC tree. The LNC is + * ignored. + */ +struct ubifs_znode *ubifs_tnc_postorder_first(struct ubifs_znode *znode) +{ + if (unlikely(!znode)) + return NULL; + + while (znode->level > 0) { + struct ubifs_znode *child; + + child = ubifs_tnc_find_child(znode, 0); + if (!child) + return znode; + znode = child; + } + + return znode; +} + +/** + * ubifs_tnc_postorder_next - next TNC tree element in postorder traversal. + * @c: UBIFS file-system description object + * @znode: previous znode + * + * This function implements postorder TNC traversal. The LNC is ignored. + * Returns the next element or %NULL if @znode is already the last one. + */ +struct ubifs_znode *ubifs_tnc_postorder_next(const struct ubifs_info *c, + struct ubifs_znode *znode) +{ + struct ubifs_znode *zn; + + ubifs_assert(c, znode); + if (unlikely(!znode->parent)) + return NULL; + + /* Switch to the next index in the parent */ + zn = ubifs_tnc_find_child(znode->parent, znode->iip + 1); + if (!zn) + /* This is in fact the last child, return parent */ + return znode->parent; + + /* Go to the first znode in this new subtree */ + return ubifs_tnc_postorder_first(zn); +} + +/** + * ubifs_destroy_tnc_subtree - destroy all znodes connected to a subtree. + * @c: UBIFS file-system description object + * @znode: znode defining subtree to destroy + * + * This function destroys subtree of the TNC tree. Returns number of clean + * znodes in the subtree. + */ +long ubifs_destroy_tnc_subtree(const struct ubifs_info *c, + struct ubifs_znode *znode) +{ + struct ubifs_znode *zn = ubifs_tnc_postorder_first(znode); + long clean_freed = 0; + int n; + + ubifs_assert(c, zn); + while (1) { + for (n = 0; n < zn->child_cnt; n++) { + if (!zn->zbranch[n].znode) + continue; + + if (zn->level > 0 && + !ubifs_zn_dirty(zn->zbranch[n].znode)) + clean_freed += 1; + + cond_resched(); + kfree(zn->zbranch[n].znode); + } + + if (zn == znode) { + if (!ubifs_zn_dirty(zn)) + clean_freed += 1; + kfree(zn); + return clean_freed; + } + + zn = ubifs_tnc_postorder_next(c, zn); + } +} + +/** + * ubifs_destroy_tnc_tree - destroy all znodes connected to the TNC tree. + * @c: UBIFS file-system description object + * + * This function destroys the whole TNC tree and updates clean global znode + * count. + */ +void ubifs_destroy_tnc_tree(struct ubifs_info *c) +{ + long n, freed; + + if (!c->zroot.znode) + return; + + n = atomic_long_read(&c->clean_zn_cnt); + freed = ubifs_destroy_tnc_subtree(c, c->zroot.znode); + ubifs_assert(c, freed == n); + atomic_long_sub(n, &ubifs_clean_zn_cnt); + + c->zroot.znode = NULL; +} + +/** + * read_znode - read an indexing node from flash and fill znode. + * @c: UBIFS file-system description object + * @zzbr: the zbranch describing the node to read + * @znode: znode to read to + * + * This function reads an indexing node from the flash media and fills znode + * with the read data. Returns zero in case of success and a negative error + * code in case of failure. The read indexing node is validated and if anything + * is wrong with it, this function prints complaint messages and returns + * %-EINVAL. + */ +static int read_znode(struct ubifs_info *c, struct ubifs_zbranch *zzbr, + struct ubifs_znode *znode) +{ + int lnum = zzbr->lnum; + int offs = zzbr->offs; + int len = zzbr->len; + int i, err, type, cmp; + struct ubifs_idx_node *idx; + + idx = kmalloc(c->max_idx_node_sz, GFP_NOFS); + if (!idx) + return -ENOMEM; + + err = ubifs_read_node(c, idx, UBIFS_IDX_NODE, len, lnum, offs); + if (err < 0) { + if (test_and_clear_failure_reason_callback(c, FR_DATA_CORRUPTED)) + set_failure_reason_callback(c, FR_TNC_CORRUPTED); + kfree(idx); + return err; + } + + err = ubifs_node_check_hash(c, idx, zzbr->hash); + if (err) { + ubifs_bad_hash(c, idx, zzbr->hash, lnum, offs); + kfree(idx); + return err; + } + + znode->child_cnt = le16_to_cpu(idx->child_cnt); + znode->level = le16_to_cpu(idx->level); + + dbg_tnc("LEB %d:%d, level %d, %d branch", + lnum, offs, znode->level, znode->child_cnt); + + if (znode->child_cnt > c->fanout || znode->level > UBIFS_MAX_LEVELS) { + ubifs_err(c, "current fanout %d, branch count %d", + c->fanout, znode->child_cnt); + ubifs_err(c, "max levels %d, znode level %d", + UBIFS_MAX_LEVELS, znode->level); + err = 1; + goto out_dump; + } + + for (i = 0; i < znode->child_cnt; i++) { + struct ubifs_branch *br = ubifs_idx_branch(c, idx, i); + struct ubifs_zbranch *zbr = &znode->zbranch[i]; + + key_read(c, &br->key, &zbr->key); + zbr->lnum = le32_to_cpu(br->lnum); + zbr->offs = le32_to_cpu(br->offs); + zbr->len = le32_to_cpu(br->len); + ubifs_copy_hash(c, ubifs_branch_hash(c, br), zbr->hash); + zbr->znode = NULL; + + /* Validate branch */ + + if (zbr->lnum < c->main_first || + zbr->lnum >= c->leb_cnt || zbr->offs < 0 || + zbr->offs + zbr->len > c->leb_size || zbr->offs & 7) { + ubifs_err(c, "bad branch %d", i); + err = 2; + goto out_dump; + } + + switch (key_type(c, &zbr->key)) { + case UBIFS_INO_KEY: + case UBIFS_DATA_KEY: + case UBIFS_DENT_KEY: + case UBIFS_XENT_KEY: + break; + default: + ubifs_err(c, "bad key type at slot %d: %d", + i, key_type(c, &zbr->key)); + err = 3; + goto out_dump; + } + + if (znode->level) + type = UBIFS_IDX_NODE; + else + type = key_type(c, &zbr->key); + + if (c->ranges[type].max_len == 0) { + if (zbr->len != c->ranges[type].len) { + ubifs_err(c, "bad target node (type %d) length (%d)", + type, zbr->len); + ubifs_err(c, "have to be %d", c->ranges[type].len); + err = 4; + goto out_dump; + } + } else if (zbr->len < c->ranges[type].min_len || + zbr->len > c->ranges[type].max_len) { + ubifs_err(c, "bad target node (type %d) length (%d)", + type, zbr->len); + ubifs_err(c, "have to be in range of %d-%d", + c->ranges[type].min_len, + c->ranges[type].max_len); + err = 5; + goto out_dump; + } + } + + /* + * Ensure that the next key is greater or equivalent to the + * previous one. + */ + for (i = 0; i < znode->child_cnt - 1; i++) { + const union ubifs_key *key1, *key2; + + key1 = &znode->zbranch[i].key; + key2 = &znode->zbranch[i + 1].key; + + cmp = keys_cmp(c, key1, key2); + if (cmp > 0) { + ubifs_err(c, "bad key order (keys %d and %d)", i, i + 1); + err = 6; + goto out_dump; + } else if (cmp == 0 && !is_hash_key(c, key1)) { + /* These can only be keys with colliding hash */ + ubifs_err(c, "keys %d and %d are not hashed but equivalent", + i, i + 1); + err = 7; + goto out_dump; + } + } + + kfree(idx); + return 0; + +out_dump: + set_failure_reason_callback(c, FR_TNC_CORRUPTED); + ubifs_err(c, "bad indexing node at LEB %d:%d, error %d", lnum, offs, err); + ubifs_dump_node(c, idx, c->max_idx_node_sz); + kfree(idx); + return -EINVAL; +} + +/** + * ubifs_load_znode - load znode to TNC cache. + * @c: UBIFS file-system description object + * @zbr: znode branch + * @parent: znode's parent + * @iip: index in parent + * + * This function loads znode pointed to by @zbr into the TNC cache and + * returns pointer to it in case of success and a negative error code in case + * of failure. + */ +struct ubifs_znode *ubifs_load_znode(struct ubifs_info *c, + struct ubifs_zbranch *zbr, + struct ubifs_znode *parent, int iip) +{ + int err; + struct ubifs_znode *znode; + + ubifs_assert(c, !zbr->znode); + /* + * A slab cache is not presently used for znodes because the znode size + * depends on the fanout which is stored in the superblock. + */ + znode = kzalloc(c->max_znode_sz, GFP_NOFS); + if (!znode) + return ERR_PTR(-ENOMEM); + + err = read_znode(c, zbr, znode); + if (err) + goto out; + + atomic_long_inc(&c->clean_zn_cnt); + + /* + * Increment the global clean znode counter as well. It is OK that + * global and per-FS clean znode counters may be inconsistent for some + * short time (because we might be preempted at this point), the global + * one is only used in shrinker. + */ + atomic_long_inc(&ubifs_clean_zn_cnt); + + zbr->znode = znode; + znode->parent = parent; + znode->time = ktime_get_seconds(); + znode->iip = iip; + + return znode; + +out: + kfree(znode); + return ERR_PTR(err); +} + +/** + * ubifs_tnc_read_node - read a leaf node from the flash media. + * @c: UBIFS file-system description object + * @zbr: key and position of the node + * @node: node is returned here + * + * This function reads a node defined by @zbr from the flash media. Returns + * zero in case of success or a negative error code in case of failure. + */ +int ubifs_tnc_read_node(struct ubifs_info *c, struct ubifs_zbranch *zbr, + void *node) +{ + union ubifs_key key1, *key = &zbr->key; + int err, type = key_type(c, key); + struct ubifs_wbuf *wbuf; + + /* + * 'zbr' has to point to on-flash node. The node may sit in a bud and + * may even be in a write buffer, so we have to take care about this. + */ + wbuf = ubifs_get_wbuf(c, zbr->lnum); + if (wbuf) + err = ubifs_read_node_wbuf(wbuf, node, type, zbr->len, + zbr->lnum, zbr->offs); + else + err = ubifs_read_node(c, node, type, zbr->len, zbr->lnum, + zbr->offs); + + if (err) { + dbg_tnck(key, "key "); + return err; + } + + /* Make sure the key of the read node is correct */ + key_read(c, node + UBIFS_KEY_OFFSET, &key1); + if (!keys_eq(c, key, &key1)) { + set_failure_reason_callback(c, FR_DATA_CORRUPTED); + ubifs_err(c, "bad key in node at LEB %d:%d", + zbr->lnum, zbr->offs); + dbg_tnck(key, "looked for key "); + dbg_tnck(&key1, "but found node's key "); + ubifs_dump_node(c, node, zbr->len); + return -EINVAL; + } + + err = ubifs_node_check_hash(c, node, zbr->hash); + if (err) { + ubifs_bad_hash(c, node, zbr->hash, zbr->lnum, zbr->offs); + return err; + } + + return 0; +} diff --git a/include/mtd/ubifs-media.h b/ubifs-utils/libubifs/ubifs-media.h index f1e3a14..f1e3a14 100644 --- a/include/mtd/ubifs-media.h +++ b/ubifs-utils/libubifs/ubifs-media.h diff --git a/ubifs-utils/libubifs/ubifs.h b/ubifs-utils/libubifs/ubifs.h new file mode 100644 index 0000000..1c7bc7b --- /dev/null +++ b/ubifs-utils/libubifs/ubifs.h @@ -0,0 +1,1925 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * This file is part of UBIFS. + * + * Copyright (C) 2006-2008 Nokia Corporation + * + * Authors: Artem Bityutskiy (Битюцкий Артём) + * Adrian Hunter + */ + +#ifndef __UBIFS_H__ +#define __UBIFS_H__ + +#include <fcntl.h> +#include <string.h> + +#include "linux_types.h" +#include "list.h" +#include "rbtree.h" +#include "spinlock.h" +#include "mutex.h" +#include "rwsem.h" +#include "atomic.h" +#include "libubi.h" +#include "ubifs-media.h" + +/* Number of UBIFS blocks per VFS page */ +#define UBIFS_BLOCKS_PER_PAGE (PAGE_SIZE / UBIFS_BLOCK_SIZE) +#define UBIFS_BLOCKS_PER_PAGE_SHIFT (PAGE_SHIFT - UBIFS_BLOCK_SHIFT) + +/* "File system end of life" sequence number watermark */ +#define SQNUM_WARN_WATERMARK 0xFFFFFFFF00000000ULL +#define SQNUM_WATERMARK 0xFFFFFFFFFF000000ULL + +/* + * Minimum amount of LEBs reserved for the index. At present the index needs at + * least 2 LEBs: one for the index head and one for in-the-gaps method (which + * currently does not cater for the index head and so excludes it from + * consideration). + */ +#define MIN_INDEX_LEBS 2 + +/* Maximum logical eraseblock size in bytes */ +#define UBIFS_MAX_LEB_SZ (2*1024*1024) + +/* Minimum amount of data UBIFS writes to the flash */ +#define MIN_WRITE_SZ (UBIFS_DATA_NODE_SZ + 8) + +/* + * Currently we do not support inode number overlapping and re-using, so this + * watermark defines dangerous inode number level. This should be fixed later, + * although it is difficult to exceed current limit. Another option is to use + * 64-bit inode numbers, but this means more overhead. + */ +#define INUM_WARN_WATERMARK 0xFFF00000 +#define INUM_WATERMARK 0xFFFFFF00 + +/* Maximum number of entries in each LPT (LEB category) heap */ +#define LPT_HEAP_SZ 256 + +/* Maximum possible inode number (only 32-bit inodes are supported now) */ +#define MAX_INUM 0xFFFFFFFF + +/* Number of non-data journal heads */ +#define NONDATA_JHEADS_CNT 2 + +/* Shorter names for journal head numbers for internal usage */ +#define GCHD UBIFS_GC_HEAD +#define BASEHD UBIFS_BASE_HEAD +#define DATAHD UBIFS_DATA_HEAD + +/* 'No change' value for 'ubifs_change_lp()' */ +#define LPROPS_NC 0x80000001 + +/* + * There is no notion of truncation key because truncation nodes do not exist + * in TNC. However, when replaying, it is handy to introduce fake "truncation" + * keys for truncation nodes because the code becomes simpler. So we define + * %UBIFS_TRUN_KEY type. + * + * But otherwise, out of the journal reply scope, the truncation keys are + * invalid. + */ +#define UBIFS_TRUN_KEY UBIFS_KEY_TYPES_CNT +#define UBIFS_INVALID_KEY UBIFS_KEY_TYPES_CNT + +/* + * How much a directory entry/extended attribute entry adds to the parent/host + * inode. + */ +#define CALC_DENT_SIZE(name_len) ALIGN(UBIFS_DENT_NODE_SZ + (name_len) + 1, 8) + +/* How much an extended attribute adds to the host inode */ +#define CALC_XATTR_BYTES(data_len) ALIGN(UBIFS_INO_NODE_SZ + (data_len) + 1, 8) + +/* Maximum expected tree height for use by bottom_up_buf */ +#define BOTTOM_UP_HEIGHT 64 + +#define UBIFS_HASH_ARR_SZ UBIFS_MAX_HASH_LEN +#define UBIFS_HMAC_ARR_SZ UBIFS_MAX_HMAC_LEN + +/* + * Znode flags (actually, bit numbers which store the flags). + * + * DIRTY_ZNODE: znode is dirty + * COW_ZNODE: znode is being committed and a new instance of this znode has to + * be created before changing this znode + * OBSOLETE_ZNODE: znode is obsolete, which means it was deleted, but it is + * still in the commit list and the ongoing commit operation + * will commit it, and delete this znode after it is done + */ +enum { + DIRTY_ZNODE = 0, + COW_ZNODE = 1, + OBSOLETE_ZNODE = 2, +}; + +/* + * Commit states. + * + * COMMIT_RESTING: commit is not wanted + * COMMIT_BACKGROUND: background commit has been requested + * COMMIT_REQUIRED: commit is required + * COMMIT_RUNNING_BACKGROUND: background commit is running + * COMMIT_RUNNING_REQUIRED: commit is running and it is required + * COMMIT_BROKEN: commit failed + */ +enum { + COMMIT_RESTING = 0, + COMMIT_BACKGROUND, + COMMIT_REQUIRED, + COMMIT_RUNNING_BACKGROUND, + COMMIT_RUNNING_REQUIRED, + COMMIT_BROKEN, +}; + +/* + * 'ubifs_scan_a_node()' return values. + * + * SCANNED_GARBAGE: scanned garbage + * SCANNED_EMPTY_SPACE: scanned empty space + * SCANNED_A_NODE: scanned a valid node + * SCANNED_A_CORRUPT_NODE: scanned a corrupted node + * SCANNED_A_BAD_PAD_NODE: scanned a padding node with invalid pad length + * + * Greater than zero means: 'scanned that number of padding bytes' + */ +enum { + SCANNED_GARBAGE = 0, + SCANNED_EMPTY_SPACE = -1, + SCANNED_A_NODE = -2, + SCANNED_A_CORRUPT_NODE = -3, + SCANNED_A_BAD_PAD_NODE = -4, +}; + +/* + * LPT cnode flag bits. + * + * DIRTY_CNODE: cnode is dirty + * OBSOLETE_CNODE: cnode is being committed and has been copied (or deleted), + * so it can (and must) be freed when the commit is finished + * COW_CNODE: cnode is being committed and must be copied before writing + */ +enum { + DIRTY_CNODE = 0, + OBSOLETE_CNODE = 1, + COW_CNODE = 2, +}; + +/* + * Dirty flag bits (lpt_drty_flgs) for LPT special nodes. + * + * LTAB_DIRTY: ltab node is dirty + * LSAVE_DIRTY: lsave node is dirty + */ +enum { + LTAB_DIRTY = 1, + LSAVE_DIRTY = 2, +}; + +/* + * Return codes used by the garbage collector. + * @LEB_FREED: the logical eraseblock was freed and is ready to use + * @LEB_FREED_IDX: indexing LEB was freed and can be used only after the commit + * @LEB_RETAINED: the logical eraseblock was freed and retained for GC purposes + */ +enum { + LEB_FREED, + LEB_FREED_IDX, + LEB_RETAINED, +}; + +/** + * struct ubifs_old_idx - index node obsoleted since last commit start. + * @rb: rb-tree node + * @lnum: LEB number of obsoleted index node + * @offs: offset of obsoleted index node + */ +struct ubifs_old_idx { + struct rb_node rb; + int lnum; + int offs; +}; + +/* The below union makes it easier to deal with keys */ +union ubifs_key { + uint8_t u8[UBIFS_SK_LEN]; + uint32_t u32[UBIFS_SK_LEN/4]; + uint64_t u64[UBIFS_SK_LEN/8]; + __le32 j32[UBIFS_SK_LEN/4]; +}; + +/** + * struct ubifs_scan_node - UBIFS scanned node information. + * @list: list of scanned nodes + * @key: key of node scanned (if it has one) + * @sqnum: sequence number + * @type: type of node scanned + * @offs: offset with LEB of node scanned + * @len: length of node scanned + * @node: raw node + */ +struct ubifs_scan_node { + struct list_head list; + union ubifs_key key; + unsigned long long sqnum; + int type; + int offs; + int len; + void *node; +}; + +/** + * struct ubifs_scan_leb - UBIFS scanned LEB information. + * @lnum: logical eraseblock number + * @nodes_cnt: number of nodes scanned + * @nodes: list of struct ubifs_scan_node + * @endpt: end point (and therefore the start of empty space) + * @buf: buffer containing entire LEB scanned + */ +struct ubifs_scan_leb { + int lnum; + int nodes_cnt; + struct list_head nodes; + int endpt; + void *buf; +}; + +/** + * struct ubifs_gced_idx_leb - garbage-collected indexing LEB. + * @list: list + * @lnum: LEB number + * @unmap: OK to unmap this LEB + * + * This data structure is used to temporary store garbage-collected indexing + * LEBs - they are not released immediately, but only after the next commit. + * This is needed to guarantee recoverability. + */ +struct ubifs_gced_idx_leb { + struct list_head list; + int lnum; + int unmap; +}; + +/** + * struct inode - inode description. + * @uid: owner ID + * @gid: group ID + * @mode: access flags + * @nlink: number of hard links + * @inum: inode number + * @atime_sec: access time seconds + * @ctime_sec: creation time seconds + * @mtime_sec: modification time seconds + * @atime_nsec: access time nanoseconds + * @ctime_nsec: creation time nanoseconds + * @mtime_nsec: modification time nanoseconds + */ +struct inode { + unsigned int uid; + unsigned int gid; + unsigned int mode; + unsigned int nlink; + ino_t inum; + unsigned long long atime_sec; + unsigned long long ctime_sec; + unsigned long long mtime_sec; + unsigned int atime_nsec; + unsigned int ctime_nsec; + unsigned int mtime_nsec; +}; + +/** + * struct ubifs_inode - UBIFS in-memory inode description. + * @vfs_inode: VFS inode description object + * @creat_sqnum: sequence number at time of creation + * @xattr_size: summarized size of all extended attributes in bytes + * @xattr_cnt: count of extended attributes this inode has + * @xattr_names: sum of lengths of all extended attribute names belonging to + * this inode + * @ui_size: inode size used by UBIFS when writing to flash + * @flags: inode flags (@UBIFS_COMPR_FL, etc) + * @compr_type: default compression type used for this inode + * @data_len: length of the data attached to the inode + * @data: inode's data + */ +struct ubifs_inode { + struct inode vfs_inode; + unsigned long long creat_sqnum; + unsigned int xattr_size; + unsigned int xattr_cnt; + unsigned int xattr_names; + unsigned int compr_type:2; + loff_t ui_size; + int flags; + int data_len; + void *data; +}; + +/** + * struct ubifs_unclean_leb - records a LEB recovered under read-only mode. + * @list: list + * @lnum: LEB number of recovered LEB + * @endpt: offset where recovery ended + * + * This structure records a LEB identified during recovery that needs to be + * cleaned but was not because UBIFS was mounted read-only. The information + * is used to clean the LEB when remounting to read-write mode. + */ +struct ubifs_unclean_leb { + struct list_head list; + int lnum; + int endpt; +}; + +/* + * LEB properties flags. + * + * LPROPS_UNCAT: not categorized + * LPROPS_DIRTY: dirty > free, dirty >= @c->dead_wm, not index + * LPROPS_DIRTY_IDX: dirty + free > @c->min_idx_node_sze and index + * LPROPS_FREE: free > 0, dirty < @c->dead_wm, not empty, not index + * LPROPS_HEAP_CNT: number of heaps used for storing categorized LEBs + * LPROPS_EMPTY: LEB is empty, not taken + * LPROPS_FREEABLE: free + dirty == leb_size, not index, not taken + * LPROPS_FRDI_IDX: free + dirty == leb_size and index, may be taken + * LPROPS_CAT_MASK: mask for the LEB categories above + * LPROPS_TAKEN: LEB was taken (this flag is not saved on the media) + * LPROPS_INDEX: LEB contains indexing nodes (this flag also exists on flash) + */ +enum { + LPROPS_UNCAT = 0, + LPROPS_DIRTY = 1, + LPROPS_DIRTY_IDX = 2, + LPROPS_FREE = 3, + LPROPS_HEAP_CNT = 3, + LPROPS_EMPTY = 4, + LPROPS_FREEABLE = 5, + LPROPS_FRDI_IDX = 6, + LPROPS_CAT_MASK = 15, + LPROPS_TAKEN = 16, + LPROPS_INDEX = 32, +}; + +/** + * struct ubifs_lprops - logical eraseblock properties. + * @free: amount of free space in bytes + * @dirty: amount of dirty space in bytes + * @flags: LEB properties flags (see above) + * @lnum: LEB number + * @end: the end postition of LEB calculated by the last node + * @used: amount of used space in bytes + * @list: list of same-category lprops (for LPROPS_EMPTY and LPROPS_FREEABLE) + * @hpos: heap position in heap of same-category lprops (other categories) + */ +struct ubifs_lprops { + int free; + int dirty; + int flags; + int lnum; + int end; + int used; + union { + struct list_head list; + int hpos; + }; +}; + +/** + * struct ubifs_lpt_lprops - LPT logical eraseblock properties. + * @free: amount of free space in bytes + * @dirty: amount of dirty space in bytes + * @tgc: trivial GC flag (1 => unmap after commit end) + * @cmt: commit flag (1 => reserved for commit) + */ +struct ubifs_lpt_lprops { + int free; + int dirty; + unsigned tgc:1; + unsigned cmt:1; +}; + +/** + * struct ubifs_lp_stats - statistics of eraseblocks in the main area. + * @empty_lebs: number of empty LEBs + * @taken_empty_lebs: number of taken LEBs + * @idx_lebs: number of indexing LEBs + * @total_free: total free space in bytes (includes all LEBs) + * @total_dirty: total dirty space in bytes (includes all LEBs) + * @total_used: total used space in bytes (does not include index LEBs) + * @total_dead: total dead space in bytes (does not include index LEBs) + * @total_dark: total dark space in bytes (does not include index LEBs) + * + * The @taken_empty_lebs field counts the LEBs that are in the transient state + * of having been "taken" for use but not yet written to. @taken_empty_lebs is + * needed to account correctly for @gc_lnum, otherwise @empty_lebs could be + * used by itself (in which case 'unused_lebs' would be a better name). In the + * case of @gc_lnum, it is "taken" at mount time or whenever a LEB is retained + * by GC, but unlike other empty LEBs that are "taken", it may not be written + * straight away (i.e. before the next commit start or unmount), so either + * @gc_lnum must be specially accounted for, or the current approach followed + * i.e. count it under @taken_empty_lebs. + * + * @empty_lebs includes @taken_empty_lebs. + * + * @total_used, @total_dead and @total_dark fields do not account indexing + * LEBs. + */ +struct ubifs_lp_stats { + int empty_lebs; + int taken_empty_lebs; + int idx_lebs; + long long total_free; + long long total_dirty; + long long total_used; + long long total_dead; + long long total_dark; +}; + +struct ubifs_nnode; + +/** + * struct ubifs_cnode - LEB Properties Tree common node. + * @parent: parent nnode + * @cnext: next cnode to commit + * @flags: flags (%DIRTY_LPT_NODE or %OBSOLETE_LPT_NODE) + * @iip: index in parent + * @level: level in the tree (zero for pnodes, greater than zero for nnodes) + * @num: node number + */ +struct ubifs_cnode { + struct ubifs_nnode *parent; + struct ubifs_cnode *cnext; + unsigned long flags; + int iip; + int level; + int num; +}; + +/** + * struct ubifs_pnode - LEB Properties Tree leaf node. + * @parent: parent nnode + * @cnext: next cnode to commit + * @flags: flags (%DIRTY_LPT_NODE or %OBSOLETE_LPT_NODE) + * @iip: index in parent + * @level: level in the tree (always zero for pnodes) + * @num: node number + * @lprops: LEB properties array + */ +struct ubifs_pnode { + struct ubifs_nnode *parent; + struct ubifs_cnode *cnext; + unsigned long flags; + int iip; + int level; + int num; + struct ubifs_lprops lprops[UBIFS_LPT_FANOUT]; +}; + +/** + * struct ubifs_nbranch - LEB Properties Tree internal node branch. + * @lnum: LEB number of child + * @offs: offset of child + * @nnode: nnode child + * @pnode: pnode child + * @cnode: cnode child + */ +struct ubifs_nbranch { + int lnum; + int offs; + union { + struct ubifs_nnode *nnode; + struct ubifs_pnode *pnode; + struct ubifs_cnode *cnode; + }; +}; + +/** + * struct ubifs_nnode - LEB Properties Tree internal node. + * @parent: parent nnode + * @cnext: next cnode to commit + * @flags: flags (%DIRTY_LPT_NODE or %OBSOLETE_LPT_NODE) + * @iip: index in parent + * @level: level in the tree (always greater than zero for nnodes) + * @num: node number + * @nbranch: branches to child nodes + */ +struct ubifs_nnode { + struct ubifs_nnode *parent; + struct ubifs_cnode *cnext; + unsigned long flags; + int iip; + int level; + int num; + struct ubifs_nbranch nbranch[UBIFS_LPT_FANOUT]; +}; + +/** + * struct ubifs_lpt_heap - heap of categorized lprops. + * @arr: heap array + * @cnt: number in heap + * @max_cnt: maximum number allowed in heap + * + * There are %LPROPS_HEAP_CNT heaps. + */ +struct ubifs_lpt_heap { + struct ubifs_lprops **arr; + int cnt; + int max_cnt; +}; + +/* + * Return codes for LPT scan callback function. + * + * LPT_SCAN_CONTINUE: continue scanning + * LPT_SCAN_ADD: add the LEB properties scanned to the tree in memory + * LPT_SCAN_STOP: stop scanning + */ +enum { + LPT_SCAN_CONTINUE = 0, + LPT_SCAN_ADD = 1, + LPT_SCAN_STOP = 2, +}; + +struct ubifs_info; + +/* Callback used by the 'ubifs_lpt_scan_nolock()' function */ +typedef int (*ubifs_lpt_scan_callback)(struct ubifs_info *c, + const struct ubifs_lprops *lprops, + int in_tree, void *data); + +/** + * struct ubifs_wbuf - UBIFS write-buffer. + * @c: UBIFS file-system description object + * @buf: write-buffer (of min. flash I/O unit size) + * @lnum: logical eraseblock number the write-buffer points to + * @offs: write-buffer offset in this logical eraseblock + * @avail: number of bytes available in the write-buffer + * @used: number of used bytes in the write-buffer + * @size: write-buffer size (in [@c->min_io_size, @c->max_write_size] range) + * @jhead: journal head the mutex belongs to (note, needed only to shut lockdep + * up by 'mutex_lock_nested()). + * @sync_callback: write-buffer synchronization callback + * @io_mutex: serializes write-buffer I/O + * @lock: serializes @buf, @lnum, @offs, @avail, @used, @next_ino and @inodes + * fields + * @next_ino: points to the next position of the following inode number + * @inodes: stores the inode numbers of the nodes which are in wbuf + * + * The write-buffer synchronization callback is called when the write-buffer is + * synchronized in order to notify how much space was wasted due to + * write-buffer padding and how much free space is left in the LEB. + * + * Note: the fields @buf, @lnum, @offs, @avail and @used can be read under + * spin-lock or mutex because they are written under both mutex and spin-lock. + * @buf is appended to under mutex but overwritten under both mutex and + * spin-lock. Thus the data between @buf and @buf + @used can be read under + * spinlock. + */ +struct ubifs_wbuf { + struct ubifs_info *c; + void *buf; + int lnum; + int offs; + int avail; + int used; + int size; + int jhead; + int (*sync_callback)(struct ubifs_info *c, int lnum, int free, int pad); + struct mutex io_mutex; + spinlock_t lock; + int next_ino; + ino_t *inodes; +}; + +/** + * struct ubifs_bud - bud logical eraseblock. + * @lnum: logical eraseblock number + * @start: where the (uncommitted) bud data starts + * @jhead: journal head number this bud belongs to + * @list: link in the list buds belonging to the same journal head + * @rb: link in the tree of all buds + * @log_hash: the log hash from the commit start node up to this bud + */ +struct ubifs_bud { + int lnum; + int start; + int jhead; + struct list_head list; + struct rb_node rb; + struct shash_desc *log_hash; +}; + +/** + * struct ubifs_jhead - journal head. + * @wbuf: head's write-buffer + * @buds_list: list of bud LEBs belonging to this journal head + * @grouped: non-zero if UBIFS groups nodes when writing to this journal head + * @log_hash: the log hash from the commit start node up to this journal head + * + * Note, the @buds list is protected by the @c->buds_lock. + */ +struct ubifs_jhead { + struct ubifs_wbuf wbuf; + struct list_head buds_list; + unsigned int grouped:1; + struct shash_desc *log_hash; +}; + +/** + * struct ubifs_zbranch - key/coordinate/length branch stored in znodes. + * @key: key + * @znode: znode address in memory + * @lnum: LEB number of the target node (indexing node or data node) + * @offs: target node offset within @lnum + * @len: target node length + * @hash: the hash of the target node + */ +struct ubifs_zbranch { + union ubifs_key key; + union { + struct ubifs_znode *znode; + void *leaf; + }; + int lnum; + int offs; + int len; + u8 hash[UBIFS_HASH_ARR_SZ]; +}; + +/** + * struct ubifs_znode - in-memory representation of an indexing node. + * @parent: parent znode or NULL if it is the root + * @cnext: next znode to commit + * @cparent: parent node for this commit + * @ciip: index in cparent's zbranch array + * @flags: znode flags (%DIRTY_ZNODE, %COW_ZNODE or %OBSOLETE_ZNODE) + * @time: last access time (seconds) + * @level: level of the entry in the TNC tree + * @child_cnt: count of child znodes + * @iip: index in parent's zbranch array + * @alt: lower bound of key range has altered i.e. child inserted at slot 0 + * @lnum: LEB number of the corresponding indexing node + * @offs: offset of the corresponding indexing node + * @len: length of the corresponding indexing node + * @zbranch: array of znode branches (@c->fanout elements) + * + * Note! The @lnum, @offs, and @len fields are not really needed - we have them + * only for internal consistency check. They could be removed to save some RAM. + */ +struct ubifs_znode { + struct ubifs_znode *parent; + struct ubifs_znode *cnext; + struct ubifs_znode *cparent; + int ciip; + unsigned long flags; + time64_t time; + int level; + int child_cnt; + int iip; + int alt; + int lnum; + int offs; + int len; + struct ubifs_zbranch zbranch[]; +}; + +/** + * struct ubifs_node_range - node length range description data structure. + * @len: fixed node length + * @min_len: minimum possible node length + * @max_len: maximum possible node length + * + * If @max_len is %0, the node has fixed length @len. + */ +struct ubifs_node_range { + union { + int len; + int min_len; + }; + int max_len; +}; + +/** + * struct ubifs_budget_req - budget requirements of an operation. + * + * @fast: non-zero if the budgeting should try to acquire budget quickly and + * should not try to call write-back + * @recalculate: non-zero if @idx_growth, @data_growth, and @dd_growth fields + * have to be re-calculated + * @new_page: non-zero if the operation adds a new page + * @dirtied_page: non-zero if the operation makes a page dirty + * @new_dent: non-zero if the operation adds a new directory entry + * @mod_dent: non-zero if the operation removes or modifies an existing + * directory entry + * @new_ino: non-zero if the operation adds a new inode + * @new_ino_d: how much data newly created inode contains + * @dirtied_ino: how many inodes the operation makes dirty + * @dirtied_ino_d: how much data dirtied inode contains + * @idx_growth: how much the index will supposedly grow + * @data_growth: how much new data the operation will supposedly add + * @dd_growth: how much data that makes other data dirty the operation will + * supposedly add + * + * @idx_growth, @data_growth and @dd_growth are not used in budget request. The + * budgeting subsystem caches index and data growth values there to avoid + * re-calculating them when the budget is released. However, if @idx_growth is + * %-1, it is calculated by the release function using other fields. + * + * An inode may contain 4KiB of data at max., thus the widths of @new_ino_d + * is 13 bits, and @dirtied_ino_d - 15, because up to 4 inodes may be made + * dirty by the re-name operation. + * + * Note, UBIFS aligns node lengths to 8-bytes boundary, so the requester has to + * make sure the amount of inode data which contribute to @new_ino_d and + * @dirtied_ino_d fields are aligned. + */ +struct ubifs_budget_req { + unsigned int fast:1; + unsigned int recalculate:1; +#ifndef UBIFS_DEBUG + unsigned int new_page:1; + unsigned int dirtied_page:1; + unsigned int new_dent:1; + unsigned int mod_dent:1; + unsigned int new_ino:1; + unsigned int new_ino_d:13; + unsigned int dirtied_ino:4; + unsigned int dirtied_ino_d:15; +#else + /* Not bit-fields to check for overflows */ + unsigned int new_page; + unsigned int dirtied_page; + unsigned int new_dent; + unsigned int mod_dent; + unsigned int new_ino; + unsigned int new_ino_d; + unsigned int dirtied_ino; + unsigned int dirtied_ino_d; +#endif + int idx_growth; + int data_growth; + int dd_growth; +}; + +/** + * struct ubifs_orphan - stores the inode number of an orphan. + * @rb: rb-tree node of rb-tree of orphans sorted by inode number + * @list: list head of list of orphans in order added + * @new_list: list head of list of orphans added since the last commit + * @cnext: next orphan to commit + * @dnext: next orphan to delete + * @inum: inode number + * @new: %1 => added since the last commit, otherwise %0 + * @cmt: %1 => commit pending, otherwise %0 + * @del: %1 => delete pending, otherwise %0 + */ +struct ubifs_orphan { + struct rb_node rb; + struct list_head list; + struct list_head new_list; + struct ubifs_orphan *cnext; + struct ubifs_orphan *dnext; + ino_t inum; + unsigned new:1; + unsigned cmt:1; + unsigned del:1; +}; + +/** + * struct ubifs_budg_info - UBIFS budgeting information. + * @idx_growth: amount of bytes budgeted for index growth + * @data_growth: amount of bytes budgeted for cached data + * @dd_growth: amount of bytes budgeted for cached data that will make + * other data dirty + * @uncommitted_idx: amount of bytes were budgeted for growth of the index, but + * which still have to be taken into account because the index + * has not been committed so far + * @old_idx_sz: size of index on flash + * @min_idx_lebs: minimum number of LEBs required for the index + * @nospace: non-zero if the file-system does not have flash space (used as + * optimization) + * @nospace_rp: the same as @nospace, but additionally means that even reserved + * pool is full + * @page_budget: budget for a page (constant, never changed after mount) + * @inode_budget: budget for an inode (constant, never changed after mount) + * @dent_budget: budget for a directory entry (constant, never changed after + * mount) + */ +struct ubifs_budg_info { + long long idx_growth; + long long data_growth; + long long dd_growth; + long long uncommitted_idx; + unsigned long long old_idx_sz; + int min_idx_lebs; + unsigned int nospace:1; + unsigned int nospace_rp:1; + int page_budget; + int inode_budget; + int dent_budget; +}; + +/** + * struct ubifs_info - UBIFS file-system description data structure + * (per-superblock). + * + * @sup_node: The super block node as read from the device + * + * @highest_inum: highest used inode number + * @max_sqnum: current global sequence number + * @cmt_no: commit number of the last successfully completed commit, protected + * by @commit_sem + * @cnt_lock: protects @highest_inum and @max_sqnum counters + * @fmt_version: UBIFS on-flash format version + * @ro_compat_version: R/O compatibility version + * + * @debug_level: level of debug messages, 0 - none, 1 - error message, + * 2 - warning message, 3 - notice message, 4 - debug message + * @program_type: used to identify the type of current program + * @program_name: program name + * @dev_name: device name + * @dev_fd: opening handler for an UBI volume or an image file + * @libubi: opening handler for libubi + * + * @lhead_lnum: log head logical eraseblock number + * @lhead_offs: log head offset + * @ltail_lnum: log tail logical eraseblock number (offset is always 0) + * @log_mutex: protects the log, @lhead_lnum, @lhead_offs, @ltail_lnum, and + * @bud_bytes + * @min_log_bytes: minimum required number of bytes in the log + * @cmt_bud_bytes: used during commit to temporarily amount of bytes in + * committed buds + * + * @buds: tree of all buds indexed by bud LEB number + * @bud_bytes: how many bytes of flash is used by buds + * @buds_lock: protects the @buds tree, @bud_bytes, and per-journal head bud + * lists + * @jhead_cnt: count of journal heads + * @jheads: journal heads (head zero is base head) + * @max_bud_bytes: maximum number of bytes allowed in buds + * @bg_bud_bytes: number of bud bytes when background commit is initiated + * @old_buds: buds to be released after commit ends + * @max_bud_cnt: maximum number of buds + * + * @commit_sem: synchronizes committer with other processes + * @cmt_state: commit state + * @cs_lock: commit state lock + * + * @big_lpt: flag that LPT is too big to write whole during commit + * @space_fixup: flag indicating that free space in LEBs needs to be cleaned up + * @double_hash: flag indicating that we can do lookups by hash + * @encrypted: flag indicating that this file system contains encrypted files + * @no_chk_data_crc: do not check CRCs when reading data nodes (except during + * recovery) + * @authenticated: flag indigating the FS is mounted in authenticated mode + * @superblock_need_write: flag indicating that we need to write superblock node + * + * @tnc_mutex: protects the Tree Node Cache (TNC), @zroot, @cnext, @enext, and + * @calc_idx_sz + * @zroot: zbranch which points to the root index node and znode + * @cnext: next znode to commit + * @enext: next znode to commit to empty space + * @gap_lebs: array of LEBs used by the in-gaps commit method + * @cbuf: commit buffer + * @ileb_buf: buffer for commit in-the-gaps method + * @ileb_len: length of data in ileb_buf + * @ihead_lnum: LEB number of index head + * @ihead_offs: offset of index head + * @ilebs: pre-allocated index LEBs + * @ileb_cnt: number of pre-allocated index LEBs + * @ileb_nxt: next pre-allocated index LEBs + * @old_idx: tree of index nodes obsoleted since the last commit start + * @bottom_up_buf: a buffer which is used by 'dirty_cow_bottom_up()' in tnc.c + * + * @mst_node: master node + * @mst_offs: offset of valid master node + * + * @log_lebs: number of logical eraseblocks in the log + * @log_bytes: log size in bytes + * @log_last: last LEB of the log + * @lpt_lebs: number of LEBs used for lprops table + * @lpt_first: first LEB of the lprops table area + * @lpt_last: last LEB of the lprops table area + * @orph_lebs: number of LEBs used for the orphan area + * @orph_first: first LEB of the orphan area + * @orph_last: last LEB of the orphan area + * @main_lebs: count of LEBs in the main area + * @main_first: first LEB of the main area + * @main_bytes: main area size in bytes + * @default_compr: default compression type + * @favor_lzo: favor LZO compression method + * @favor_percent: lzo vs. zlib threshold used in case favor LZO + * + * @key_hash_type: type of the key hash + * @key_hash: direntry key hash function + * @key_fmt: key format + * @key_len: key length + * @fanout: fanout of the index tree (number of links per indexing node) + * + * @min_io_size: minimal input/output unit size + * @min_io_shift: number of bits in @min_io_size minus one + * @max_write_size: maximum amount of bytes the underlying flash can write at a + * time (MTD write buffer size) + * @max_write_shift: number of bits in @max_write_size minus one + * @leb_size: logical eraseblock size in bytes + * @half_leb_size: half LEB size + * @idx_leb_size: how many bytes of an LEB are effectively available when it is + * used to store indexing nodes (@leb_size - @max_idx_node_sz) + * @leb_cnt: count of logical eraseblocks + * @max_leb_cnt: maximum count of logical eraseblocks + * @ro_media: the underlying UBI volume is read-only + * @ro_mount: the file-system was mounted as read-only + * @ro_error: UBIFS switched to R/O mode because an error happened + * + * @dirty_pg_cnt: number of dirty pages (not used) + * @dirty_zn_cnt: number of dirty znodes + * @clean_zn_cnt: number of clean znodes + * + * @space_lock: protects @bi and @lst + * @lst: lprops statistics + * @bi: budgeting information + * @calc_idx_sz: temporary variable which is used to calculate new index size + * (contains accurate new index size at end of TNC commit start) + * + * @ref_node_alsz: size of the LEB reference node aligned to the min. flash + * I/O unit + * @mst_node_alsz: master node aligned size + * @min_idx_node_sz: minimum indexing node aligned on 8-bytes boundary + * @max_idx_node_sz: maximum indexing node aligned on 8-bytes boundary + * @max_inode_sz: maximum possible inode size in bytes + * @max_znode_sz: size of znode in bytes + * + * @leb_overhead: how many bytes are wasted in an LEB when it is filled with + * data nodes of maximum size - used in free space reporting + * @dead_wm: LEB dead space watermark + * @dark_wm: LEB dark space watermark + * + * @ranges: UBIFS node length ranges + * @di: UBI device information + * @vi: UBI volume information + * + * @orph_tree: rb-tree of orphan inode numbers + * @orph_list: list of orphan inode numbers in order added + * @orph_new: list of orphan inode numbers added since last commit + * @orph_cnext: next orphan to commit + * @orph_dnext: next orphan to delete + * @orphan_lock: lock for orph_tree and orph_new + * @orph_buf: buffer for orphan nodes + * @new_orphans: number of orphans since last commit + * @cmt_orphans: number of orphans being committed + * @tot_orphans: number of orphans in the rb_tree + * @max_orphans: maximum number of orphans allowed + * @ohead_lnum: orphan head LEB number + * @ohead_offs: orphan head offset + * @no_orphs: non-zero if there are no orphans + * + * @gc_lnum: LEB number used for garbage collection + * @sbuf: a buffer of LEB size used by GC and replay for scanning + * @idx_gc: list of index LEBs that have been garbage collected + * @idx_gc_cnt: number of elements on the idx_gc list + * @gc_seq: incremented for every non-index LEB garbage collected + * @gced_lnum: last non-index LEB that was garbage collected + * + * @space_bits: number of bits needed to record free or dirty space + * @lpt_lnum_bits: number of bits needed to record a LEB number in the LPT + * @lpt_offs_bits: number of bits needed to record an offset in the LPT + * @lpt_spc_bits: number of bits needed to space in the LPT + * @pcnt_bits: number of bits needed to record pnode or nnode number + * @lnum_bits: number of bits needed to record LEB number + * @nnode_sz: size of on-flash nnode + * @pnode_sz: size of on-flash pnode + * @ltab_sz: size of on-flash LPT lprops table + * @lsave_sz: size of on-flash LPT save table + * @pnode_cnt: number of pnodes + * @nnode_cnt: number of nnodes + * @lpt_hght: height of the LPT + * @pnodes_have: number of pnodes in memory + * + * @lp_mutex: protects lprops table and all the other lprops-related fields + * @lpt_lnum: LEB number of the root nnode of the LPT + * @lpt_offs: offset of the root nnode of the LPT + * @nhead_lnum: LEB number of LPT head + * @nhead_offs: offset of LPT head + * @lpt_drty_flgs: dirty flags for LPT special nodes e.g. ltab + * @dirty_nn_cnt: number of dirty nnodes + * @dirty_pn_cnt: number of dirty pnodes + * @check_lpt_free: flag that indicates LPT GC may be needed + * @lpt_sz: LPT size + * @lpt_nod_buf: buffer for an on-flash nnode or pnode + * @lpt_buf: buffer of LEB size used by LPT + * @nroot: address in memory of the root nnode of the LPT + * @lpt_cnext: next LPT node to commit + * @lpt_heap: array of heaps of categorized lprops + * @dirty_idx: a (reverse sorted) copy of the LPROPS_DIRTY_IDX heap as at + * previous commit start + * @uncat_list: list of un-categorized LEBs + * @empty_list: list of empty LEBs + * @freeable_list: list of freeable non-index LEBs (free + dirty == @leb_size) + * @frdi_idx_list: list of freeable index LEBs (free + dirty == @leb_size) + * @freeable_cnt: number of freeable LEBs in @freeable_list + * @in_a_category_cnt: count of lprops which are in a certain category, which + * basically meants that they were loaded from the flash + * + * @ltab_lnum: LEB number of LPT's own lprops table + * @ltab_offs: offset of LPT's own lprops table + * @lpt: lprops table + * @ltab: LPT's own lprops table + * @ltab_cmt: LPT's own lprops table (commit copy) + * @lsave_cnt: number of LEB numbers in LPT's save table + * @lsave_lnum: LEB number of LPT's save table + * @lsave_offs: offset of LPT's save table + * @lsave: LPT's save table + * @lscan_lnum: LEB number of last LPT scan + * + * @rp_size: reserved pool size + * + * @hash_algo_name: the name of the hashing algorithm to use + * @hash_algo: The hash algo number (from include/linux/hash_info.h) + * @auth_key_filename: authentication key file name + * @x509_filename: x509 certificate file name for authentication + * @hash_len: the length of the hash + * @root_idx_hash: The hash of the root index node + * @lpt_hash: The hash of the LPT + * @mst_hash: The hash of the master node + * @log_hash: the log hash from the commit start node up to the latest reference + * node. + * + * @need_recovery: %1 if the file-system needs recovery + * @replaying: %1 during journal replay + * @mounting: %1 while mounting + * @remounting_rw: %1 while re-mounting from R/O mode to R/W mode + * @replay_list: temporary list used during journal replay + * @replay_buds: list of buds to replay + * @cs_sqnum: sequence number of first node in the log (commit start node) + * @unclean_leb_list: LEBs to recover when re-mounting R/O mounted FS to R/W + * mode + * @rcvrd_mst_node: recovered master node to write when re-mounting R/O mounted + * FS to R/W mode + * @size_tree: inode size information for recovery + * + * @new_ihead_lnum: used by debugging to check @c->ihead_lnum + * @new_ihead_offs: used by debugging to check @c->ihead_offs + * + * @private: private information related to specific situation, eg. fsck. + * @assert_failed_cb: callback function to handle assertion failure + * @set_failure_reason_cb: record reasons while certain failure happens + * @get_failure_reason_cb: get failure reasons + * @clear_failure_reason_cb: callback function to clear the error which is + * caused by reading corrupted data or invalid lpt + * @test_and_clear_failure_reason_cb: callback function to check and clear the + * error which is caused by reading corrupted + * data or invalid lpt + * @set_lpt_invalid_cb: callback function to set the invalid lpt status + * @test_lpt_valid_cb: callback function to check whether lpt is corrupted or + * incorrect, should be called before updating lpt + * @can_ignore_failure_cb: callback function to decide whether the failure + * can be ignored + * @handle_failure_cb: callback function to decide whether the failure can be + * handled + */ +struct ubifs_info { + struct ubifs_sb_node *sup_node; + + ino_t highest_inum; + unsigned long long max_sqnum; + unsigned long long cmt_no; + spinlock_t cnt_lock; + int fmt_version; + int ro_compat_version; + + int debug_level; + int program_type; + const char *program_name; + char *dev_name; + int dev_fd; + libubi_t libubi; + + int lhead_lnum; + int lhead_offs; + int ltail_lnum; + struct mutex log_mutex; + int min_log_bytes; + long long cmt_bud_bytes; + + struct rb_root buds; + long long bud_bytes; + spinlock_t buds_lock; + int jhead_cnt; + struct ubifs_jhead *jheads; + long long max_bud_bytes; + long long bg_bud_bytes; + struct list_head old_buds; + int max_bud_cnt; + + struct rw_semaphore commit_sem; + int cmt_state; + spinlock_t cs_lock; + + unsigned int big_lpt:1; + unsigned int space_fixup:1; + unsigned int double_hash:1; + unsigned int encrypted:1; + unsigned int no_chk_data_crc:1; + unsigned int authenticated:1; + unsigned int superblock_need_write:1; + + struct mutex tnc_mutex; + struct ubifs_zbranch zroot; + struct ubifs_znode *cnext; + struct ubifs_znode *enext; + int *gap_lebs; + void *cbuf; + void *ileb_buf; + int ileb_len; + int ihead_lnum; + int ihead_offs; + int *ilebs; + int ileb_cnt; + int ileb_nxt; + struct rb_root old_idx; + int *bottom_up_buf; + + struct ubifs_mst_node *mst_node; + int mst_offs; + + int log_lebs; + long long log_bytes; + int log_last; + int lpt_lebs; + int lpt_first; + int lpt_last; + int orph_lebs; + int orph_first; + int orph_last; + int main_lebs; + int main_first; + long long main_bytes; + int default_compr; + int favor_lzo; + int favor_percent; + + uint8_t key_hash_type; + uint32_t (*key_hash)(const char *str, int len); + int key_fmt; + int key_len; + int fanout; + + int min_io_size; + int min_io_shift; + int max_write_size; + int max_write_shift; + int leb_size; + int half_leb_size; + int idx_leb_size; + int leb_cnt; + int max_leb_cnt; + unsigned int ro_media:1; + unsigned int ro_mount:1; + unsigned int ro_error:1; + + atomic_long_t dirty_pg_cnt; + atomic_long_t dirty_zn_cnt; + atomic_long_t clean_zn_cnt; + + spinlock_t space_lock; + struct ubifs_lp_stats lst; + struct ubifs_budg_info bi; + unsigned long long calc_idx_sz; + + int ref_node_alsz; + int mst_node_alsz; + int min_idx_node_sz; + int max_idx_node_sz; + long long max_inode_sz; + int max_znode_sz; + + int leb_overhead; + int dead_wm; + int dark_wm; + + struct ubifs_node_range ranges[UBIFS_NODE_TYPES_CNT]; + struct ubi_dev_info di; + struct ubi_vol_info vi; + + struct rb_root orph_tree; + struct list_head orph_list; + struct list_head orph_new; + struct ubifs_orphan *orph_cnext; + struct ubifs_orphan *orph_dnext; + spinlock_t orphan_lock; + void *orph_buf; + int new_orphans; + int cmt_orphans; + int tot_orphans; + int max_orphans; + int ohead_lnum; + int ohead_offs; + int no_orphs; + + int gc_lnum; + void *sbuf; + struct list_head idx_gc; + int idx_gc_cnt; + int gc_seq; + int gced_lnum; + + int space_bits; + int lpt_lnum_bits; + int lpt_offs_bits; + int lpt_spc_bits; + int pcnt_bits; + int lnum_bits; + int nnode_sz; + int pnode_sz; + int ltab_sz; + int lsave_sz; + int pnode_cnt; + int nnode_cnt; + int lpt_hght; + int pnodes_have; + + struct mutex lp_mutex; + int lpt_lnum; + int lpt_offs; + int nhead_lnum; + int nhead_offs; + int lpt_drty_flgs; + int dirty_nn_cnt; + int dirty_pn_cnt; + int check_lpt_free; + long long lpt_sz; + void *lpt_nod_buf; + void *lpt_buf; + struct ubifs_nnode *nroot; + struct ubifs_cnode *lpt_cnext; + struct ubifs_lpt_heap lpt_heap[LPROPS_HEAP_CNT]; + struct ubifs_lpt_heap dirty_idx; + struct list_head uncat_list; + struct list_head empty_list; + struct list_head freeable_list; + struct list_head frdi_idx_list; + int freeable_cnt; + int in_a_category_cnt; + + int ltab_lnum; + int ltab_offs; + struct ubifs_lprops *lpt; + struct ubifs_lpt_lprops *ltab; + struct ubifs_lpt_lprops *ltab_cmt; + int lsave_cnt; + int lsave_lnum; + int lsave_offs; + int *lsave; + int lscan_lnum; + + long long rp_size; + + char *hash_algo_name; + int hash_algo; + char *auth_key_filename; + char *auth_cert_filename; + int hash_len; + uint8_t root_idx_hash[UBIFS_MAX_HASH_LEN]; + uint8_t lpt_hash[UBIFS_MAX_HASH_LEN]; + uint8_t mst_hash[UBIFS_MAX_HASH_LEN]; + + struct shash_desc *log_hash; + + unsigned int need_recovery:1; + unsigned int replaying:1; + unsigned int mounting:1; + unsigned int remounting_rw:1; + struct list_head replay_list; + struct list_head replay_buds; + unsigned long long cs_sqnum; + struct list_head unclean_leb_list; + struct ubifs_mst_node *rcvrd_mst_node; + struct rb_root size_tree; + + int new_ihead_lnum; + int new_ihead_offs; + + void *private; + void (*assert_failed_cb)(const struct ubifs_info *c); + void (*set_failure_reason_cb)(const struct ubifs_info *c, + unsigned int reason); + unsigned int (*get_failure_reason_cb)(const struct ubifs_info *c); + void (*clear_failure_reason_cb)(const struct ubifs_info *c); + bool (*test_and_clear_failure_reason_cb)(const struct ubifs_info *c, + unsigned int reason); + void (*set_lpt_invalid_cb)(const struct ubifs_info *c, + unsigned int reason); + bool (*test_lpt_valid_cb)(const struct ubifs_info *c, int lnum, + int old_free, int old_dirty, + int free, int dirty); + bool (*can_ignore_failure_cb)(const struct ubifs_info *c, + unsigned int reason); + bool (*handle_failure_cb)(const struct ubifs_info *c, + unsigned int reason, void *priv); +}; + +extern atomic_long_t ubifs_clean_zn_cnt; + +/* auth.c */ +static inline int ubifs_authenticated(const struct ubifs_info *c) +{ + return c->authenticated; +} + +/** + * struct size_entry - inode size information for recovery. + * @rb: link in the RB-tree of sizes + * @inum: inode number + * @i_size: size on inode + * @d_size: maximum size based on data nodes + * @exists: indicates whether the inode exists + */ +struct size_entry { + struct rb_node rb; + ino_t inum; + loff_t i_size; + loff_t d_size; + int exists; +}; + +#ifdef WITH_CRYPTO +int ubifs_init_authentication(struct ubifs_info *c); +int ubifs_shash_init(const struct ubifs_info *c, struct shash_desc *desc); +int ubifs_shash_update(const struct ubifs_info *c, struct shash_desc *desc, + const void *buf, unsigned int len); +int ubifs_shash_final(const struct ubifs_info *c, struct shash_desc *desc, + u8 *out); +struct shash_desc *ubifs_hash_get_desc(const struct ubifs_info *c); +int __ubifs_node_calc_hash(const struct ubifs_info *c, const void *buf, + u8 *hash); +int ubifs_master_node_calc_hash(const struct ubifs_info *c, const void *node, + uint8_t *hash); +int ubifs_sign_superblock_node(struct ubifs_info *c, void *node); +void ubifs_bad_hash(const struct ubifs_info *c, const void *node, + const u8 *hash, int lnum, int offs); +void __ubifs_exit_authentication(struct ubifs_info *c); +#else +static inline int ubifs_init_authentication(__unused struct ubifs_info *c) +{ return 0; } +static inline int ubifs_shash_init(__unused const struct ubifs_info *c, + __unused struct shash_desc *desc) +{ return 0; } +static inline int ubifs_shash_update(__unused const struct ubifs_info *c, + __unused struct shash_desc *desc, + __unused const void *buf, + __unused unsigned int len) { return 0; } +static inline int ubifs_shash_final(__unused const struct ubifs_info *c, + __unused struct shash_desc *desc, + __unused u8 *out) { return 0; } +static inline struct shash_desc * +ubifs_hash_get_desc(__unused const struct ubifs_info *c) { return NULL; } +static inline int __ubifs_node_calc_hash(__unused const struct ubifs_info *c, + __unused const void *buf, + __unused u8 *hash) { return 0; } +static inline int +ubifs_master_node_calc_hash(__unused const struct ubifs_info *c, + __unused const void *node, __unused uint8_t *hash) +{ return 0; } +static inline int ubifs_sign_superblock_node(__unused struct ubifs_info *c, + __unused void *node) +{ return 0; } +static inline void ubifs_bad_hash(__unused const struct ubifs_info *c, + __unused const void *node, + __unused const u8 *hash, __unused int lnum, + __unused int offs) {} +static inline void __ubifs_exit_authentication(__unused struct ubifs_info *c) {} +#endif + +static inline int ubifs_prepare_auth_node(__unused struct ubifs_info *c, + __unused void *node, + __unused struct shash_desc *inhash) +{ + // To be implemented + return 0; +} + +static inline int +ubifs_node_calc_hash(const struct ubifs_info *c, const void *buf, u8 *hash) +{ + if (ubifs_authenticated(c)) + return __ubifs_node_calc_hash(c, buf, hash); + else + return 0; +} + +static inline int +ubifs_node_check_hash(__unused const struct ubifs_info *c, + __unused const void *buf, __unused const u8 *expected) +{ + // To be implemented + return 0; +} + +/** + * ubifs_check_hash - compare two hashes + * @c: UBIFS file-system description object + * @expected: first hash + * @got: second hash + * + * Compare two hashes @expected and @got. Returns 0 when they are equal, a + * negative error code otherwise. + */ +static inline int +ubifs_check_hash(__unused const struct ubifs_info *c, + __unused const u8 *expected, __unused const u8 *got) +{ + // To be implemented + return 0; +} + +/** + * ubifs_check_hmac - compare two HMACs + * @c: UBIFS file-system description object + * @expected: first HMAC + * @got: second HMAC + * + * Compare two hashes @expected and @got. Returns 0 when they are equal, a + * negative error code otherwise. + */ +static inline int +ubifs_check_hmac(__unused const struct ubifs_info *c, + __unused const u8 *expected, __unused const u8 *got) +{ + // To be implemented + return 0; +} + +/** + * ubifs_branch_hash - returns a pointer to the hash of a branch + * @c: UBIFS file-system description object + * @br: branch to get the hash from + * + * This returns a pointer to the hash of a branch. Since the key already is a + * dynamically sized object we cannot use a struct member here. + */ +static inline u8 * +ubifs_branch_hash(struct ubifs_info *c, struct ubifs_branch *br) +{ + return (void *)br + sizeof(*br) + c->key_len; +} + +/** + * ubifs_copy_hash - copy a hash + * @c: UBIFS file-system description object + * @from: source hash + * @to: destination hash + * + * With authentication this copies a hash, otherwise does nothing. + */ +static inline void +ubifs_copy_hash(const struct ubifs_info *c, const u8 *from, u8 *to) +{ + if (ubifs_authenticated(c)) + memcpy(to, from, c->hash_len); +} + +static inline int +ubifs_node_insert_hmac(__unused const struct ubifs_info *c, __unused void *buf, + __unused int len, __unused int ofs_hmac) +{ + // To be implemented + return 0; +} + +static inline int +ubifs_node_verify_hmac(__unused const struct ubifs_info *c, + __unused const void *buf, __unused int len, + __unused int ofs_hmac) +{ + // To be implemented + return 0; +} + +/** + * ubifs_auth_node_sz - returns the size of an authentication node + * @c: UBIFS file-system description object + * + * This function returns the size of an authentication node which can + * be 0 for unauthenticated filesystems or the real size of an auth node + * authentication is enabled. + */ +static inline int +ubifs_auth_node_sz(__unused const struct ubifs_info *c) +{ + // To be implemented + return 0; +} + +static inline bool +ubifs_hmac_zero(__unused struct ubifs_info *c, __unused const u8 *hmac) +{ + // To be implemented + return true; +} + +static inline int +ubifs_shash_copy_state(__unused const struct ubifs_info *c, + __unused struct shash_desc *src, + __unused struct shash_desc *target) +{ + // To be implemented + return 0; +} + +static inline void ubifs_exit_authentication(struct ubifs_info *c) +{ + if (ubifs_authenticated(c)) + __ubifs_exit_authentication(c); +} + +/* io.c */ +void ubifs_ro_mode(struct ubifs_info *c, int err); +int ubifs_leb_read(const struct ubifs_info *c, int lnum, void *buf, int offs, + int len, int even_ebadmsg); +int ubifs_leb_write(struct ubifs_info *c, int lnum, const void *buf, int offs, + int len); +int ubifs_leb_change(struct ubifs_info *c, int lnum, const void *buf, int len); +int ubifs_leb_unmap(struct ubifs_info *c, int lnum); +int ubifs_leb_map(struct ubifs_info *c, int lnum); +int ubifs_is_mapped(const struct ubifs_info *c, int lnum); +int ubifs_wbuf_write_nolock(struct ubifs_wbuf *wbuf, void *buf, int len); +int ubifs_wbuf_seek_nolock(struct ubifs_wbuf *wbuf, int lnum, int offs); +int ubifs_wbuf_init(struct ubifs_info *c, struct ubifs_wbuf *wbuf); +int ubifs_read_node(const struct ubifs_info *c, void *buf, int type, int len, + int lnum, int offs); +int ubifs_read_node_wbuf(struct ubifs_wbuf *wbuf, void *buf, int type, int len, + int lnum, int offs); +int ubifs_write_node(struct ubifs_info *c, void *node, int len, int lnum, + int offs); +int ubifs_write_node_hmac(struct ubifs_info *c, void *buf, int len, int lnum, + int offs, int hmac_offs); +int ubifs_check_node(const struct ubifs_info *c, const void *buf, int len, + int lnum, int offs, int quiet, int must_chk_crc); +void ubifs_init_node(struct ubifs_info *c, void *buf, int len, int pad); +void ubifs_crc_node(struct ubifs_info *c, void *buf, int len); +void ubifs_prepare_node(struct ubifs_info *c, void *buf, int len, int pad); +int ubifs_prepare_node_hmac(struct ubifs_info *c, void *node, int len, + int hmac_offs, int pad); +void ubifs_prep_grp_node(struct ubifs_info *c, void *node, int len, int last); +int ubifs_io_init(struct ubifs_info *c); +void ubifs_pad(const struct ubifs_info *c, void *buf, int pad); +int ubifs_wbuf_sync_nolock(struct ubifs_wbuf *wbuf); + +/* scan.c */ +struct ubifs_scan_leb *ubifs_scan(const struct ubifs_info *c, int lnum, + int offs, void *sbuf, int quiet); +void ubifs_scan_destroy(struct ubifs_scan_leb *sleb); +int ubifs_scan_a_node(const struct ubifs_info *c, void *buf, int len, int lnum, + int offs, int quiet); +struct ubifs_scan_leb *ubifs_start_scan(const struct ubifs_info *c, int lnum, + int offs, void *sbuf); +void ubifs_end_scan(const struct ubifs_info *c, struct ubifs_scan_leb *sleb, + int lnum, int offs); +int ubifs_add_snod(const struct ubifs_info *c, struct ubifs_scan_leb *sleb, + void *buf, int offs); +void ubifs_scanned_corruption(const struct ubifs_info *c, int lnum, int offs, + void *buf); + +/* Failure reasons which are checked by fsck. */ +enum { + FR_DATA_CORRUPTED = 1, /* Data is corrupted(master/log/orphan/main) */ + FR_TNC_CORRUPTED = 2, /* TNC is corrupted */ + FR_LPT_CORRUPTED = 4, /* LPT is corrupted */ + FR_LPT_INCORRECT = 8 /* Space statistics are wrong */ +}; +/* Partial failure reasons in common libs, which are handled by fsck. */ +enum { + FR_H_BUD_CORRUPTED = 0, /* Bud LEB is corrupted */ + FR_H_TNC_DATA_CORRUPTED, /* Data searched from TNC is corrupted */ + FR_H_ORPHAN_CORRUPTED, /* Orphan LEB is corrupted */ + FR_H_LTAB_INCORRECT, /* Lprops table is incorrect */ +}; +/* Callback functions for failure(which can be handled by fsck) happens. */ +static inline void set_failure_reason_callback(const struct ubifs_info *c, + unsigned int reason) +{ + if (c->set_failure_reason_cb) + c->set_failure_reason_cb(c, reason); +} +static inline unsigned int get_failure_reason_callback( + const struct ubifs_info *c) +{ + if (c->get_failure_reason_cb) + return c->get_failure_reason_cb(c); + + return 0; +} +static inline void clear_failure_reason_callback(const struct ubifs_info *c) +{ + if (c->clear_failure_reason_cb) + c->clear_failure_reason_cb(c); +} +static inline bool test_and_clear_failure_reason_callback( + const struct ubifs_info *c, + unsigned int reason) +{ + if (c->test_and_clear_failure_reason_cb) + return c->test_and_clear_failure_reason_cb(c, reason); + + return false; +} +static inline void set_lpt_invalid_callback(const struct ubifs_info *c, + unsigned int reason) +{ + if (c->set_lpt_invalid_cb) + c->set_lpt_invalid_cb(c, reason); +} +static inline bool test_lpt_valid_callback(const struct ubifs_info *c, int lnum, + int old_free, int old_dirty, + int free, int dirty) +{ + if (c->test_lpt_valid_cb) + return c->test_lpt_valid_cb(c, lnum, + old_free, old_dirty, free, dirty); + + return false; +} +static inline bool can_ignore_failure_callback(const struct ubifs_info *c, + unsigned int reason) +{ + if (c->can_ignore_failure_cb) + return c->can_ignore_failure_cb(c, reason); + + return false; +} +static inline bool handle_failure_callback(const struct ubifs_info *c, + unsigned int reason, void *priv) +{ + if (c->handle_failure_cb) + return c->handle_failure_cb(c, reason, priv); + + return false; +} + +/* log.c */ +void ubifs_add_bud(struct ubifs_info *c, struct ubifs_bud *bud); +void ubifs_create_buds_lists(struct ubifs_info *c); +int ubifs_add_bud_to_log(struct ubifs_info *c, int jhead, int lnum, int offs); +struct ubifs_bud *ubifs_search_bud(struct ubifs_info *c, int lnum); +struct ubifs_wbuf *ubifs_get_wbuf(struct ubifs_info *c, int lnum); +int ubifs_log_start_commit(struct ubifs_info *c, int *ltail_lnum); +int ubifs_log_end_commit(struct ubifs_info *c, int new_ltail_lnum); +int ubifs_log_post_commit(struct ubifs_info *c, int old_ltail_lnum); +int ubifs_consolidate_log(struct ubifs_info *c); + +/* journal.c */ +int ubifs_get_dent_type(int mode); +int ubifs_jnl_update_file(struct ubifs_info *c, + const struct ubifs_inode *dir_ui, + const struct fscrypt_name *nm, + const struct ubifs_inode *ui); + +/* budget.c */ +int ubifs_budget_space(struct ubifs_info *c, struct ubifs_budget_req *req); +void ubifs_release_budget(struct ubifs_info *c, struct ubifs_budget_req *req); +long long ubifs_get_free_space_nolock(struct ubifs_info *c); +int ubifs_calc_min_idx_lebs(struct ubifs_info *c); +long long ubifs_reported_space(const struct ubifs_info *c, long long free); +long long ubifs_calc_available(const struct ubifs_info *c, int min_idx_lebs); + +/* find.c */ +int ubifs_find_free_space(struct ubifs_info *c, int min_space, int *offs, + int squeeze); +int ubifs_find_free_leb_for_idx(struct ubifs_info *c); +int ubifs_find_dirty_leb(struct ubifs_info *c, struct ubifs_lprops *ret_lp, + int min_space, int pick_free); +int ubifs_find_dirty_idx_leb(struct ubifs_info *c); +int ubifs_save_dirty_idx_lnums(struct ubifs_info *c); + +/* tnc.c */ +int ubifs_lookup_level0(struct ubifs_info *c, const union ubifs_key *key, + struct ubifs_znode **zn, int *n); +int ubifs_tnc_lookup_nm(struct ubifs_info *c, const union ubifs_key *key, + void *node, const struct fscrypt_name *nm); +int ubifs_tnc_locate(struct ubifs_info *c, const union ubifs_key *key, + void *node, int *lnum, int *offs); +int ubifs_tnc_add(struct ubifs_info *c, const union ubifs_key *key, int lnum, + int offs, int len, const u8 *hash); +int ubifs_tnc_replace(struct ubifs_info *c, const union ubifs_key *key, + int old_lnum, int old_offs, int lnum, int offs, int len); +int ubifs_tnc_add_nm(struct ubifs_info *c, const union ubifs_key *key, + int lnum, int offs, int len, const u8 *hash, + const struct fscrypt_name *nm); +int ubifs_tnc_remove(struct ubifs_info *c, const union ubifs_key *key); +int ubifs_tnc_remove_nm(struct ubifs_info *c, const union ubifs_key *key, + const struct fscrypt_name *nm); +int ubifs_tnc_remove_range(struct ubifs_info *c, union ubifs_key *from_key, + union ubifs_key *to_key); +int ubifs_tnc_remove_ino(struct ubifs_info *c, ino_t inum); +int ubifs_tnc_remove_node(struct ubifs_info *c, const union ubifs_key *key, + int lnum, int offs); +struct ubifs_dent_node *ubifs_tnc_next_ent(struct ubifs_info *c, + union ubifs_key *key, + const struct fscrypt_name *nm); +void ubifs_tnc_close(struct ubifs_info *c); +int ubifs_tnc_has_node(struct ubifs_info *c, union ubifs_key *key, int level, + int lnum, int offs, int is_idx); +int ubifs_dirty_idx_node(struct ubifs_info *c, union ubifs_key *key, int level, + int lnum, int offs); +/* Shared by tnc.c for tnc_commit.c */ +void destroy_old_idx(struct ubifs_info *c); +int is_idx_node_in_tnc(struct ubifs_info *c, union ubifs_key *key, int level, + int lnum, int offs); +int insert_old_idx_znode(struct ubifs_info *c, struct ubifs_znode *znode); + +/* tnc_misc.c */ +int ubifs_search_zbranch(const struct ubifs_info *c, + const struct ubifs_znode *znode, + const union ubifs_key *key, int *n); +struct ubifs_znode *ubifs_tnc_postorder_first(struct ubifs_znode *znode); +struct ubifs_znode *ubifs_tnc_postorder_next(const struct ubifs_info *c, + struct ubifs_znode *znode); +long ubifs_destroy_tnc_subtree(const struct ubifs_info *c, + struct ubifs_znode *zr); +void ubifs_destroy_tnc_tree(struct ubifs_info *c); +struct ubifs_znode *ubifs_load_znode(struct ubifs_info *c, + struct ubifs_zbranch *zbr, + struct ubifs_znode *parent, int iip); +int ubifs_tnc_read_node(struct ubifs_info *c, struct ubifs_zbranch *zbr, + void *node); + +/* tnc_commit.c */ +int ubifs_tnc_start_commit(struct ubifs_info *c, struct ubifs_zbranch *zroot); +int ubifs_tnc_end_commit(struct ubifs_info *c); + +/* commit.c */ +void ubifs_commit_required(struct ubifs_info *c); +void ubifs_request_bg_commit(struct ubifs_info *c); +int ubifs_run_commit(struct ubifs_info *c); +void ubifs_recovery_commit(struct ubifs_info *c); +int ubifs_gc_should_commit(struct ubifs_info *c); +void ubifs_wait_for_commit(struct ubifs_info *c); + +/* master.c */ +int ubifs_compare_master_node(struct ubifs_info *c, void *m1, void *m2); +int ubifs_read_master(struct ubifs_info *c); +int ubifs_write_master(struct ubifs_info *c); + +/* sb.c */ +int ubifs_read_superblock(struct ubifs_info *c); +int ubifs_write_sb_node(struct ubifs_info *c, struct ubifs_sb_node *sup); +int ubifs_fixup_free_space(struct ubifs_info *c); + +/* replay.c */ +int ubifs_validate_entry(struct ubifs_info *c, + const struct ubifs_dent_node *dent); +int take_ihead(struct ubifs_info *c); +int ubifs_replay_journal(struct ubifs_info *c); + +/* gc.c */ +int ubifs_garbage_collect(struct ubifs_info *c, int anyway); +int ubifs_gc_start_commit(struct ubifs_info *c); +int ubifs_gc_end_commit(struct ubifs_info *c); +void ubifs_destroy_idx_gc(struct ubifs_info *c); +int ubifs_get_idx_gc_leb(struct ubifs_info *c); +int ubifs_garbage_collect_leb(struct ubifs_info *c, struct ubifs_lprops *lp); + +/* orphan.c */ +int ubifs_orphan_start_commit(struct ubifs_info *c); +int ubifs_orphan_end_commit(struct ubifs_info *c); +int ubifs_mount_orphans(struct ubifs_info *c, int unclean, int read_only); +int ubifs_clear_orphans(struct ubifs_info *c); + +/* lpt.c */ +int ubifs_calc_dflt_lpt_geom(struct ubifs_info *c, int *main_lebs, int *big_lpt); +int ubifs_calc_lpt_geom(struct ubifs_info *c); +int ubifs_create_lpt(struct ubifs_info *c, struct ubifs_lprops *lps, int lp_cnt, + u8 *hash, bool free_ltab); +int ubifs_lpt_init(struct ubifs_info *c, int rd, int wr); +struct ubifs_lprops *ubifs_lpt_lookup(struct ubifs_info *c, int lnum); +struct ubifs_lprops *ubifs_lpt_lookup_dirty(struct ubifs_info *c, int lnum); +int ubifs_lpt_scan_nolock(struct ubifs_info *c, int start_lnum, int end_lnum, + ubifs_lpt_scan_callback scan_cb, void *data); + +/* Shared by lpt.c for lpt_commit.c */ +void ubifs_pack_lsave(struct ubifs_info *c, void *buf, int *lsave); +void ubifs_pack_ltab(struct ubifs_info *c, void *buf, + struct ubifs_lpt_lprops *ltab); +void ubifs_pack_pnode(struct ubifs_info *c, void *buf, + struct ubifs_pnode *pnode); +void ubifs_pack_nnode(struct ubifs_info *c, void *buf, + struct ubifs_nnode *nnode); +struct ubifs_pnode *ubifs_get_pnode(struct ubifs_info *c, + struct ubifs_nnode *parent, int iip); +struct ubifs_nnode *ubifs_get_nnode(struct ubifs_info *c, + struct ubifs_nnode *parent, int iip); +struct ubifs_pnode *ubifs_pnode_lookup(struct ubifs_info *c, int i); +int ubifs_read_nnode(struct ubifs_info *c, struct ubifs_nnode *parent, int iip); +void ubifs_add_lpt_dirt(struct ubifs_info *c, int lnum, int dirty); +void ubifs_add_nnode_dirt(struct ubifs_info *c, struct ubifs_nnode *nnode); +uint32_t ubifs_unpack_bits(const struct ubifs_info *c, uint8_t **addr, int *pos, int nrbits); +int ubifs_calc_nnode_num(int row, int col); +struct ubifs_nnode *ubifs_first_nnode(struct ubifs_info *c, int *hght); +/* Needed only in debugging code in lpt_commit.c */ +int ubifs_unpack_nnode(const struct ubifs_info *c, void *buf, + struct ubifs_nnode *nnode); +int ubifs_lpt_calc_hash(struct ubifs_info *c, u8 *hash); + +/* lpt_commit.c */ +struct ubifs_pnode *ubifs_find_next_pnode(struct ubifs_info *c, + struct ubifs_pnode *pnode); +void ubifs_make_nnode_dirty(struct ubifs_info *c, struct ubifs_nnode *nnode); +void ubifs_make_pnode_dirty(struct ubifs_info *c, struct ubifs_pnode *pnode); +int ubifs_lpt_start_commit(struct ubifs_info *c); +int ubifs_lpt_end_commit(struct ubifs_info *c); +int ubifs_lpt_post_commit(struct ubifs_info *c); +void ubifs_free_lpt_nodes(struct ubifs_info *c); +void ubifs_lpt_free(struct ubifs_info *c, int wr_only); +int dbg_check_ltab_lnum(struct ubifs_info *c, int lnum); + +/* lprops.c */ +const struct ubifs_lprops *ubifs_change_lp(struct ubifs_info *c, + const struct ubifs_lprops *lp, + int free, int dirty, int flags, + int idx_gc_cnt); +void ubifs_get_lp_stats(struct ubifs_info *c, struct ubifs_lp_stats *lst); +void ubifs_add_to_cat(struct ubifs_info *c, struct ubifs_lprops *lprops, + int cat); +void ubifs_replace_cat(struct ubifs_info *c, struct ubifs_lprops *old_lprops, + struct ubifs_lprops *new_lprops); +void ubifs_ensure_cat(struct ubifs_info *c, struct ubifs_lprops *lprops); +int ubifs_categorize_lprops(const struct ubifs_info *c, + const struct ubifs_lprops *lprops); +int ubifs_change_one_lp(struct ubifs_info *c, int lnum, int free, int dirty, + int flags_set, int flags_clean, int idx_gc_cnt); +int ubifs_update_one_lp(struct ubifs_info *c, int lnum, int free, int dirty, + int flags_set, int flags_clean); +int ubifs_read_one_lp(struct ubifs_info *c, int lnum, struct ubifs_lprops *lp); +const struct ubifs_lprops *ubifs_fast_find_free(struct ubifs_info *c); +const struct ubifs_lprops *ubifs_fast_find_empty(struct ubifs_info *c); +const struct ubifs_lprops *ubifs_fast_find_freeable(struct ubifs_info *c); +const struct ubifs_lprops *ubifs_fast_find_frdi_idx(struct ubifs_info *c); +int ubifs_calc_dark(const struct ubifs_info *c, int spc); + +/* dir.c */ +struct ubifs_inode *ubifs_lookup_by_inum(struct ubifs_info *c, ino_t inum); +struct ubifs_inode *ubifs_lookup(struct ubifs_info *c, + struct ubifs_inode *dir_ui, + const struct fscrypt_name *nm); +int ubifs_mkdir(struct ubifs_info *c, struct ubifs_inode *dir_ui, + const struct fscrypt_name *nm, unsigned int mode); +int ubifs_link_recovery(struct ubifs_info *c, struct ubifs_inode *dir_ui, + struct ubifs_inode *ui, const struct fscrypt_name *nm); +int ubifs_create_root(struct ubifs_info *c); + +/* super.c */ +int open_ubi(struct ubifs_info *c, const char *node); +void close_ubi(struct ubifs_info *c); +int open_target(struct ubifs_info *c); +int close_target(struct ubifs_info *c); +int ubifs_open_volume(struct ubifs_info *c, const char *volume_name); +int ubifs_close_volume(struct ubifs_info *c); +int check_volume_empty(struct ubifs_info *c); +void init_ubifs_info(struct ubifs_info *c, int program_type); +int init_constants_early(struct ubifs_info *c); +int init_constants_sb(struct ubifs_info *c); +void init_constants_master(struct ubifs_info *c); +int take_gc_lnum(struct ubifs_info *c); +int alloc_wbufs(struct ubifs_info *c); +void free_wbufs(struct ubifs_info *c); +void free_orphans(struct ubifs_info *c); +void free_buds(struct ubifs_info *c, bool delete_from_list); +void destroy_journal(struct ubifs_info *c); + +/* recovery.c */ +int ubifs_recover_master_node(struct ubifs_info *c); +struct ubifs_scan_leb *ubifs_recover_leb(struct ubifs_info *c, int lnum, + int offs, void *sbuf, int jhead); +struct ubifs_scan_leb *ubifs_recover_log_leb(struct ubifs_info *c, int lnum, + int offs, void *sbuf); +int ubifs_recover_inl_heads(struct ubifs_info *c, void *sbuf); +int ubifs_rcvry_gc_commit(struct ubifs_info *c); +int ubifs_recover_size_accum(struct ubifs_info *c, union ubifs_key *key, + int deletion, loff_t new_size); +int ubifs_recover_size(struct ubifs_info *c, bool in_place); +void ubifs_destroy_size_tree(struct ubifs_info *c); + +/* Normal UBIFS messages */ +enum { ERR_LEVEL = 1, WARN_LEVEL, INFO_LEVEL, DEBUG_LEVEL }; +#define ubifs_msg(c, fmt, ...) do { \ + if (c->debug_level >= INFO_LEVEL) \ + printf("<INFO> %s[%d] (%s): %s: " fmt "\n", \ + c->program_name, getpid(), \ + c->dev_name, __FUNCTION__, ##__VA_ARGS__); \ +} while (0) +#define ubifs_warn(c, fmt, ...) do { \ + if (c->debug_level >= WARN_LEVEL) \ + printf("<WARN> %s[%d] (%s): %s: " fmt "\n", \ + c->program_name, getpid(), \ + c->dev_name, __FUNCTION__, ##__VA_ARGS__); \ +} while (0) +#define ubifs_err(c, fmt, ...) do { \ + if (c->debug_level >= ERR_LEVEL) \ + printf("<ERROR> %s[%d] (%s): %s: " fmt "\n", \ + c->program_name, getpid(), \ + c->dev_name, __FUNCTION__, ##__VA_ARGS__); \ +} while (0) + +#endif /* !__UBIFS_H__ */ diff --git a/ubifs-utils/mkfs.ubifs/README b/ubifs-utils/mkfs.ubifs/README deleted file mode 100644 index 7e19939..0000000 --- a/ubifs-utils/mkfs.ubifs/README +++ /dev/null @@ -1,9 +0,0 @@ -UBIFS File System - Make File System program - -* crc16.h and crc16.c were copied from the linux kernel. -* crc32.h and crc32.c were copied from mtd-utils and amended. -* ubifs.h is a selection of definitions from fs/ubifs/ubifs.h from the linux kernel. -* key.h is copied from fs/ubifs/key.h from the linux kernel. -* defs.h is a bunch of definitions to smooth things over. -* lpt.c is a selection of functions copied from fs/ubifs/lpt.c from the linux kernel, and amended. -* hashtable/* was downloaded from http://www.cl.cam.ac.uk/~cwc22/hashtable/ diff --git a/ubifs-utils/mkfs.ubifs/defs.h b/ubifs-utils/mkfs.ubifs/defs.h deleted file mode 100644 index 8db5277..0000000 --- a/ubifs-utils/mkfs.ubifs/defs.h +++ /dev/null @@ -1,90 +0,0 @@ -/* - * Greate deal of the code was taken from the kernel UBIFS implementation, and - * this file contains some "glue" definitions. - */ - -#ifndef __UBIFS_DEFS_H__ -#define __UBIFS_DEFS_H__ - -#define t16(x) ({ \ - uint16_t __b = (x); \ - (__LITTLE_ENDIAN==__BYTE_ORDER) ? __b : bswap_16(__b); \ -}) - -#define t32(x) ({ \ - uint32_t __b = (x); \ - (__LITTLE_ENDIAN==__BYTE_ORDER) ? __b : bswap_32(__b); \ -}) - -#define t64(x) ({ \ - uint64_t __b = (x); \ - (__LITTLE_ENDIAN==__BYTE_ORDER) ? __b : bswap_64(__b); \ -}) - -#define cpu_to_le16(x) ((__le16){t16(x)}) -#define cpu_to_le32(x) ((__le32){t32(x)}) -#define cpu_to_le64(x) ((__le64){t64(x)}) - -#define le16_to_cpu(x) (t16((x))) -#define le32_to_cpu(x) (t32((x))) -#define le64_to_cpu(x) (t64((x))) - -#define unlikely(x) (x) - -struct qstr -{ - char *name; - size_t len; -}; - -/** - * fls - find last (most-significant) bit set - * @x: the word to search - * - * This is defined the same way as ffs. - * Note fls(0) = 0, fls(1) = 1, fls(0x80000000) = 32. - */ -static inline int fls(int x) -{ - int r = 32; - - if (!x) - return 0; - if (!(x & 0xffff0000u)) { - x <<= 16; - r -= 16; - } - if (!(x & 0xff000000u)) { - x <<= 8; - r -= 8; - } - if (!(x & 0xf0000000u)) { - x <<= 4; - r -= 4; - } - if (!(x & 0xc0000000u)) { - x <<= 2; - r -= 2; - } - if (!(x & 0x80000000u)) { - x <<= 1; - r -= 1; - } - return r; -} - -#define do_div(n,base) ({ \ -int __res; \ -__res = ((unsigned long) n) % (unsigned) base; \ -n = ((unsigned long) n) / (unsigned) base; \ -__res; }) - -#if INT_MAX != 0x7fffffff -#error : sizeof(int) must be 4 for this program -#endif - -#if (~0ULL) != 0xffffffffffffffffULL -#error : sizeof(long long) must be 8 for this program -#endif - -#endif diff --git a/ubifs-utils/mkfs.ubifs/key.h b/ubifs-utils/mkfs.ubifs/key.h deleted file mode 100644 index 2de530b..0000000 --- a/ubifs-utils/mkfs.ubifs/key.h +++ /dev/null @@ -1,222 +0,0 @@ -/* - * This file is part of UBIFS. - * - * Copyright (C) 2006-2008 Nokia Corporation. - * - * 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 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 - * - * Authors: Artem Bityutskiy (Битюцкий Артём) - * Adrian Hunter - */ - -/* - * This header contains various key-related definitions and helper function. - * UBIFS allows several key schemes, so we access key fields only via these - * helpers. At the moment only one key scheme is supported. - * - * Simple key scheme - * ~~~~~~~~~~~~~~~~~ - * - * Keys are 64-bits long. First 32-bits are inode number (parent inode number - * in case of direntry key). Next 3 bits are node type. The last 29 bits are - * 4KiB offset in case of inode node, and direntry hash in case of a direntry - * node. We use "r5" hash borrowed from reiserfs. - */ - -#ifndef __UBIFS_KEY_H__ -#define __UBIFS_KEY_H__ - -#include <assert.h> - -/** - * key_mask_hash - mask a valid hash value. - * @val: value to be masked - * - * We use hash values as offset in directories, so values %0 and %1 are - * reserved for "." and "..". %2 is reserved for "end of readdir" marker. This - * function makes sure the reserved values are not used. - */ -static inline uint32_t key_mask_hash(uint32_t hash) -{ - hash &= UBIFS_S_KEY_HASH_MASK; - if (unlikely(hash <= 2)) - hash += 3; - return hash; -} - -/** - * key_r5_hash - R5 hash function (borrowed from reiserfs). - * @s: direntry name - * @len: name length - */ -static inline uint32_t key_r5_hash(const char *s, int len) -{ - uint32_t a = 0; - const signed char *str = (const signed char *)s; - - while (len--) { - a += *str << 4; - a += *str >> 4; - a *= 11; - str++; - } - - return key_mask_hash(a); -} - -/** - * key_test_hash - testing hash function. - * @str: direntry name - * @len: name length - */ -static inline uint32_t key_test_hash(const char *str, int len) -{ - uint32_t a = 0; - - len = min_t(uint32_t, len, 4); - memcpy(&a, str, len); - return key_mask_hash(a); -} - -/** - * ino_key_init - initialize inode key. - * @c: UBIFS file-system description object - * @key: key to initialize - * @inum: inode number - */ -static inline void ino_key_init(union ubifs_key *key, ino_t inum) -{ - key->u32[0] = inum; - key->u32[1] = UBIFS_INO_KEY << UBIFS_S_KEY_BLOCK_BITS; -} - -/** - * dent_key_init - initialize directory entry key. - * @c: UBIFS file-system description object - * @key: key to initialize - * @inum: parent inode number - * @nm: direntry name and length - */ -static inline void dent_key_init(const struct ubifs_info *c, - union ubifs_key *key, ino_t inum, - const void *name, int name_len) -{ - uint32_t hash = c->key_hash(name, name_len); - - assert(!(hash & ~UBIFS_S_KEY_HASH_MASK)); - key->u32[0] = inum; - key->u32[1] = hash | (UBIFS_DENT_KEY << UBIFS_S_KEY_HASH_BITS); -} - -/** - * xent_key_init - initialize extended attribute entry key. - * @c: UBIFS file-system description object - * @key: key to initialize - * @inum: host inode number - * @nm: extended attribute entry name and length - */ -static inline void xent_key_init(const struct ubifs_info *c, - union ubifs_key *key, ino_t inum, - const struct qstr *nm) -{ - uint32_t hash = c->key_hash(nm->name, nm->len); - - assert(!(hash & ~UBIFS_S_KEY_HASH_MASK)); - key->u32[0] = inum; - key->u32[1] = hash | (UBIFS_XENT_KEY << UBIFS_S_KEY_HASH_BITS); -} - -/** - * data_key_init - initialize data key. - * @c: UBIFS file-system description object - * @key: key to initialize - * @inum: inode number - * @block: block number - */ -static inline void data_key_init(union ubifs_key *key, ino_t inum, - unsigned int block) -{ - assert(!(block & ~UBIFS_S_KEY_BLOCK_MASK)); - key->u32[0] = inum; - key->u32[1] = block | (UBIFS_DATA_KEY << UBIFS_S_KEY_BLOCK_BITS); -} - -/** - * key_write - transform a key from in-memory format. - * @c: UBIFS file-system description object - * @from: the key to transform - * @to: the key to store the result - */ -static inline void key_write(const union ubifs_key *from, void *to) -{ - __le32 x[2]; - - x[0] = cpu_to_le32(from->u32[0]); - x[1] = cpu_to_le32(from->u32[1]); - - memcpy(to, &x, 8); - memset(to + 8, 0, UBIFS_MAX_KEY_LEN - 8); -} - -/** - * key_write_idx - transform a key from in-memory format for the index. - * @c: UBIFS file-system description object - * @from: the key to transform - * @to: the key to store the result - */ -static inline void key_write_idx(const union ubifs_key *from, void *to) -{ - __le32 x[2]; - - x[0] = cpu_to_le32(from->u32[0]); - x[1] = cpu_to_le32(from->u32[1]); - - memcpy(to, &x, 8); -} - -/** - * keys_cmp - compare keys. - * @c: UBIFS file-system description object - * @key1: the first key to compare - * @key2: the second key to compare - * - * This function compares 2 keys and returns %-1 if @key1 is less than - * @key2, 0 if the keys are equivalent and %1 if @key1 is greater than @key2. - */ -static inline int keys_cmp(const union ubifs_key *key1, - const union ubifs_key *key2) -{ - if (key1->u32[0] < key2->u32[0]) - return -1; - if (key1->u32[0] > key2->u32[0]) - return 1; - if (key1->u32[1] < key2->u32[1]) - return -1; - if (key1->u32[1] > key2->u32[1]) - return 1; - - return 0; -} - -/** - * key_type - get key type. - * @c: UBIFS file-system description object - * @key: key to get type of - */ -static inline int key_type(const union ubifs_key *key) -{ - return key->u32[1] >> UBIFS_S_KEY_BLOCK_BITS; -} - -#endif /* !__UBIFS_KEY_H__ */ diff --git a/ubifs-utils/mkfs.ubifs/lpt.c b/ubifs-utils/mkfs.ubifs/lpt.c deleted file mode 100644 index 7ee739a..0000000 --- a/ubifs-utils/mkfs.ubifs/lpt.c +++ /dev/null @@ -1,590 +0,0 @@ -/* - * This file is part of UBIFS. - * - * Copyright (C) 2006, 2007 Nokia Corporation. - * - * 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 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 - * - * Authors: Adrian Hunter - * Artem Bityutskiy - */ - -#include "mkfs.ubifs.h" - -#ifdef WITH_CRYPTO -#include <openssl/evp.h> -#endif - -/** - * do_calc_lpt_geom - calculate sizes for the LPT area. - * @c: the UBIFS file-system description object - * - * Calculate the sizes of LPT bit fields, nodes, and tree, based on the - * properties of the flash and whether LPT is "big" (c->big_lpt). - */ -static void do_calc_lpt_geom(struct ubifs_info *c) -{ - int n, bits, per_leb_wastage; - long long sz, tot_wastage; - - c->pnode_cnt = (c->main_lebs + UBIFS_LPT_FANOUT - 1) / UBIFS_LPT_FANOUT; - - n = (c->pnode_cnt + UBIFS_LPT_FANOUT - 1) / UBIFS_LPT_FANOUT; - c->nnode_cnt = n; - while (n > 1) { - n = (n + UBIFS_LPT_FANOUT - 1) / UBIFS_LPT_FANOUT; - c->nnode_cnt += n; - } - - c->lpt_hght = 1; - n = UBIFS_LPT_FANOUT; - while (n < c->pnode_cnt) { - c->lpt_hght += 1; - n <<= UBIFS_LPT_FANOUT_SHIFT; - } - - c->space_bits = fls(c->leb_size) - 3; - c->lpt_lnum_bits = fls(c->lpt_lebs); - c->lpt_offs_bits = fls(c->leb_size - 1); - c->lpt_spc_bits = fls(c->leb_size); - - n = (c->max_leb_cnt + UBIFS_LPT_FANOUT - 1) / UBIFS_LPT_FANOUT; - c->pcnt_bits = fls(n - 1); - - c->lnum_bits = fls(c->max_leb_cnt - 1); - - bits = UBIFS_LPT_CRC_BITS + UBIFS_LPT_TYPE_BITS + - (c->big_lpt ? c->pcnt_bits : 0) + - (c->space_bits * 2 + 1) * UBIFS_LPT_FANOUT; - c->pnode_sz = (bits + 7) / 8; - - bits = UBIFS_LPT_CRC_BITS + UBIFS_LPT_TYPE_BITS + - (c->big_lpt ? c->pcnt_bits : 0) + - (c->lpt_lnum_bits + c->lpt_offs_bits) * UBIFS_LPT_FANOUT; - c->nnode_sz = (bits + 7) / 8; - - bits = UBIFS_LPT_CRC_BITS + UBIFS_LPT_TYPE_BITS + - c->lpt_lebs * c->lpt_spc_bits * 2; - c->ltab_sz = (bits + 7) / 8; - - bits = UBIFS_LPT_CRC_BITS + UBIFS_LPT_TYPE_BITS + - c->lnum_bits * c->lsave_cnt; - c->lsave_sz = (bits + 7) / 8; - - /* Calculate the minimum LPT size */ - c->lpt_sz = (long long)c->pnode_cnt * c->pnode_sz; - c->lpt_sz += (long long)c->nnode_cnt * c->nnode_sz; - c->lpt_sz += c->ltab_sz; - c->lpt_sz += c->lsave_sz; - - /* Add wastage */ - sz = c->lpt_sz; - per_leb_wastage = max_t(int, c->pnode_sz, c->nnode_sz); - sz += per_leb_wastage; - tot_wastage = per_leb_wastage; - while (sz > c->leb_size) { - sz += per_leb_wastage; - sz -= c->leb_size; - tot_wastage += per_leb_wastage; - } - tot_wastage += ALIGN(sz, c->min_io_size) - sz; - c->lpt_sz += tot_wastage; -} - -/** - * calc_dflt_lpt_geom - calculate default LPT geometry. - * @c: the UBIFS file-system description object - * @main_lebs: number of main area LEBs is passed and returned here - * @big_lpt: whether the LPT area is "big" is returned here - * - * The size of the LPT area depends on parameters that themselves are dependent - * on the size of the LPT area. This function, successively recalculates the LPT - * area geometry until the parameters and resultant geometry are consistent. - * - * This function returns %0 on success and a negative error code on failure. - */ -int calc_dflt_lpt_geom(struct ubifs_info *c, int *main_lebs, int *big_lpt) -{ - int i, lebs_needed; - long long sz; - - /* Start by assuming the minimum number of LPT LEBs */ - c->lpt_lebs = UBIFS_MIN_LPT_LEBS; - c->main_lebs = *main_lebs - c->lpt_lebs; - if (c->main_lebs <= 0) - return -EINVAL; - - /* And assume we will use the small LPT model */ - c->big_lpt = 0; - - /* - * Calculate the geometry based on assumptions above and then see if it - * makes sense - */ - do_calc_lpt_geom(c); - - /* Small LPT model must have lpt_sz < leb_size */ - if (c->lpt_sz > c->leb_size) { - /* Nope, so try again using big LPT model */ - c->big_lpt = 1; - do_calc_lpt_geom(c); - } - - /* Now check there are enough LPT LEBs */ - for (i = 0; i < 64 ; i++) { - sz = c->lpt_sz * 4; /* Allow 4 times the size */ - sz += c->leb_size - 1; - do_div(sz, c->leb_size); - lebs_needed = sz; - if (lebs_needed > c->lpt_lebs) { - /* Not enough LPT LEBs so try again with more */ - c->lpt_lebs = lebs_needed; - c->main_lebs = *main_lebs - c->lpt_lebs; - if (c->main_lebs <= 0) - return -EINVAL; - do_calc_lpt_geom(c); - continue; - } - if (c->ltab_sz > c->leb_size) { - err_msg("LPT ltab too big"); - return -EINVAL; - } - *main_lebs = c->main_lebs; - *big_lpt = c->big_lpt; - return 0; - } - return -EINVAL; -} - -/** - * pack_bits - pack bit fields end-to-end. - * @addr: address at which to pack (passed and next address returned) - * @pos: bit position at which to pack (passed and next position returned) - * @val: value to pack - * @nrbits: number of bits of value to pack (1-32) - */ -static void pack_bits(uint8_t **addr, int *pos, uint32_t val, int nrbits) -{ - uint8_t *p = *addr; - int b = *pos; - - if (b) { - *p |= ((uint8_t)val) << b; - nrbits += b; - if (nrbits > 8) { - *++p = (uint8_t)(val >>= (8 - b)); - if (nrbits > 16) { - *++p = (uint8_t)(val >>= 8); - if (nrbits > 24) { - *++p = (uint8_t)(val >>= 8); - if (nrbits > 32) - *++p = (uint8_t)(val >>= 8); - } - } - } - } else { - *p = (uint8_t)val; - if (nrbits > 8) { - *++p = (uint8_t)(val >>= 8); - if (nrbits > 16) { - *++p = (uint8_t)(val >>= 8); - if (nrbits > 24) - *++p = (uint8_t)(val >>= 8); - } - } - } - b = nrbits & 7; - if (b == 0) - p++; - *addr = p; - *pos = b; -} - -/** - * pack_pnode - pack all the bit fields of a pnode. - * @c: UBIFS file-system description object - * @buf: buffer into which to pack - * @pnode: pnode to pack - */ -static void pack_pnode(struct ubifs_info *c, void *buf, - struct ubifs_pnode *pnode) -{ - uint8_t *addr = buf + UBIFS_LPT_CRC_BYTES; - int i, pos = 0; - uint16_t crc; - - pack_bits(&addr, &pos, UBIFS_LPT_PNODE, UBIFS_LPT_TYPE_BITS); - if (c->big_lpt) - pack_bits(&addr, &pos, pnode->num, c->pcnt_bits); - for (i = 0; i < UBIFS_LPT_FANOUT; i++) { - pack_bits(&addr, &pos, pnode->lprops[i].free >> 3, - c->space_bits); - pack_bits(&addr, &pos, pnode->lprops[i].dirty >> 3, - c->space_bits); - if (pnode->lprops[i].flags & LPROPS_INDEX) - pack_bits(&addr, &pos, 1, 1); - else - pack_bits(&addr, &pos, 0, 1); - } - crc = crc16(-1, buf + UBIFS_LPT_CRC_BYTES, - c->pnode_sz - UBIFS_LPT_CRC_BYTES); - addr = buf; - pos = 0; - pack_bits(&addr, &pos, crc, UBIFS_LPT_CRC_BITS); -} - -/** - * pack_nnode - pack all the bit fields of a nnode. - * @c: UBIFS file-system description object - * @buf: buffer into which to pack - * @nnode: nnode to pack - */ -static void pack_nnode(struct ubifs_info *c, void *buf, - struct ubifs_nnode *nnode) -{ - uint8_t *addr = buf + UBIFS_LPT_CRC_BYTES; - int i, pos = 0; - uint16_t crc; - - pack_bits(&addr, &pos, UBIFS_LPT_NNODE, UBIFS_LPT_TYPE_BITS); - if (c->big_lpt) - pack_bits(&addr, &pos, nnode->num, c->pcnt_bits); - for (i = 0; i < UBIFS_LPT_FANOUT; i++) { - int lnum = nnode->nbranch[i].lnum; - - if (lnum == 0) - lnum = c->lpt_last + 1; - pack_bits(&addr, &pos, lnum - c->lpt_first, c->lpt_lnum_bits); - pack_bits(&addr, &pos, nnode->nbranch[i].offs, - c->lpt_offs_bits); - } - crc = crc16(-1, buf + UBIFS_LPT_CRC_BYTES, - c->nnode_sz - UBIFS_LPT_CRC_BYTES); - addr = buf; - pos = 0; - pack_bits(&addr, &pos, crc, UBIFS_LPT_CRC_BITS); -} - -/** - * pack_ltab - pack the LPT's own lprops table. - * @c: UBIFS file-system description object - * @buf: buffer into which to pack - * @ltab: LPT's own lprops table to pack - */ -static void pack_ltab(struct ubifs_info *c, void *buf, - struct ubifs_lpt_lprops *ltab) -{ - uint8_t *addr = buf + UBIFS_LPT_CRC_BYTES; - int i, pos = 0; - uint16_t crc; - - pack_bits(&addr, &pos, UBIFS_LPT_LTAB, UBIFS_LPT_TYPE_BITS); - for (i = 0; i < c->lpt_lebs; i++) { - pack_bits(&addr, &pos, ltab[i].free, c->lpt_spc_bits); - pack_bits(&addr, &pos, ltab[i].dirty, c->lpt_spc_bits); - } - crc = crc16(-1, buf + UBIFS_LPT_CRC_BYTES, - c->ltab_sz - UBIFS_LPT_CRC_BYTES); - addr = buf; - pos = 0; - pack_bits(&addr, &pos, crc, UBIFS_LPT_CRC_BITS); -} - -/** - * pack_lsave - pack the LPT's save table. - * @c: UBIFS file-system description object - * @buf: buffer into which to pack - * @lsave: LPT's save table to pack - */ -static void pack_lsave(struct ubifs_info *c, void *buf, int *lsave) -{ - uint8_t *addr = buf + UBIFS_LPT_CRC_BYTES; - int i, pos = 0; - uint16_t crc; - - pack_bits(&addr, &pos, UBIFS_LPT_LSAVE, UBIFS_LPT_TYPE_BITS); - for (i = 0; i < c->lsave_cnt; i++) - pack_bits(&addr, &pos, lsave[i], c->lnum_bits); - crc = crc16(-1, buf + UBIFS_LPT_CRC_BYTES, - c->lsave_sz - UBIFS_LPT_CRC_BYTES); - addr = buf; - pos = 0; - pack_bits(&addr, &pos, crc, UBIFS_LPT_CRC_BITS); -} - -/** - * set_ltab - set LPT LEB properties. - * @c: UBIFS file-system description object - * @lnum: LEB number - * @free: amount of free space - * @dirty: amount of dirty space - */ -static void set_ltab(struct ubifs_info *c, int lnum, int free, int dirty) -{ - dbg_msg(3, "LEB %d free %d dirty %d to %d %d", - lnum, c->ltab[lnum - c->lpt_first].free, - c->ltab[lnum - c->lpt_first].dirty, free, dirty); - c->ltab[lnum - c->lpt_first].free = free; - c->ltab[lnum - c->lpt_first].dirty = dirty; -} - -/** - * calc_nnode_num - calculate nnode number. - * @row: the row in the tree (root is zero) - * @col: the column in the row (leftmost is zero) - * - * The nnode number is a number that uniquely identifies a nnode and can be used - * easily to traverse the tree from the root to that nnode. - * - * This function calculates and returns the nnode number for the nnode at @row - * and @col. - */ -static int calc_nnode_num(int row, int col) -{ - int num, bits; - - num = 1; - while (row--) { - bits = (col & (UBIFS_LPT_FANOUT - 1)); - col >>= UBIFS_LPT_FANOUT_SHIFT; - num <<= UBIFS_LPT_FANOUT_SHIFT; - num |= bits; - } - return num; -} - -/** - * create_lpt - create LPT. - * @c: UBIFS file-system description object - * - * This function returns %0 on success and a negative error code on failure. - */ -int create_lpt(struct ubifs_info *c) -{ - int lnum, err = 0, i, j, cnt, len, alen, row; - int blnum, boffs, bsz, bcnt; - struct ubifs_pnode *pnode = NULL; - struct ubifs_nnode *nnode = NULL; - void *buf = NULL, *p; - int *lsave = NULL; - unsigned int md_len; - - pnode = malloc(sizeof(struct ubifs_pnode)); - nnode = malloc(sizeof(struct ubifs_nnode)); - buf = malloc(c->leb_size); - lsave = malloc(sizeof(int) * c->lsave_cnt); - if (!pnode || !nnode || !buf || !lsave) { - err = -ENOMEM; - goto out; - } - memset(pnode, 0 , sizeof(struct ubifs_pnode)); - memset(nnode, 0 , sizeof(struct ubifs_nnode)); - - hash_digest_init(); - - c->lscan_lnum = c->main_first; - - lnum = c->lpt_first; - p = buf; - len = 0; - /* Number of leaf nodes (pnodes) */ - cnt = (c->main_lebs + UBIFS_LPT_FANOUT - 1) >> UBIFS_LPT_FANOUT_SHIFT; - //printf("pnode_cnt=%d\n",cnt); - - /* - * To calculate the internal node branches, we keep information about - * the level below. - */ - blnum = lnum; /* LEB number of level below */ - boffs = 0; /* Offset of level below */ - bcnt = cnt; /* Number of nodes in level below */ - bsz = c->pnode_sz; /* Size of nodes in level below */ - - /* Add pnodes */ - for (i = 0; i < cnt; i++) { - if (len + c->pnode_sz > c->leb_size) { - alen = ALIGN(len, c->min_io_size); - set_ltab(c, lnum, c->leb_size - alen, alen - len); - memset(p, 0xff, alen - len); - err = write_leb(lnum++, alen, buf); - if (err) - goto out; - p = buf; - len = 0; - } - /* Fill in the pnode */ - for (j = 0; j < UBIFS_LPT_FANOUT; j++) { - int k = (i << UBIFS_LPT_FANOUT_SHIFT) + j; - - if (k < c->main_lebs) - pnode->lprops[j] = c->lpt[k]; - else { - pnode->lprops[j].free = c->leb_size; - pnode->lprops[j].dirty = 0; - pnode->lprops[j].flags = 0; - } - } - pack_pnode(c, p, pnode); - - hash_digest_update(p, c->pnode_sz); - - p += c->pnode_sz; - len += c->pnode_sz; - /* - * pnodes are simply numbered left to right starting at zero, - * which means the pnode number can be used easily to traverse - * down the tree to the corresponding pnode. - */ - pnode->num += 1; - } - - hash_digest_final(c->lpt_hash, &md_len); - - row = c->lpt_hght - 1; - /* Add all nnodes, one level at a time */ - while (1) { - /* Number of internal nodes (nnodes) at next level */ - cnt = (cnt + UBIFS_LPT_FANOUT - 1) / UBIFS_LPT_FANOUT; - if (cnt == 0) - cnt = 1; - for (i = 0; i < cnt; i++) { - if (len + c->nnode_sz > c->leb_size) { - alen = ALIGN(len, c->min_io_size); - set_ltab(c, lnum, c->leb_size - alen, - alen - len); - memset(p, 0xff, alen - len); - err = write_leb(lnum++, alen, buf); - if (err) - goto out; - p = buf; - len = 0; - } - /* The root is on row zero */ - if (row == 0) { - c->lpt_lnum = lnum; - c->lpt_offs = len; - } - /* Set branches to the level below */ - for (j = 0; j < UBIFS_LPT_FANOUT; j++) { - if (bcnt) { - if (boffs + bsz > c->leb_size) { - blnum += 1; - boffs = 0; - } - nnode->nbranch[j].lnum = blnum; - nnode->nbranch[j].offs = boffs; - boffs += bsz; - bcnt--; - } else { - nnode->nbranch[j].lnum = 0; - nnode->nbranch[j].offs = 0; - } - } - nnode->num = calc_nnode_num(row, i); - pack_nnode(c, p, nnode); - p += c->nnode_sz; - len += c->nnode_sz; - } - /* Row zero is the top row */ - if (row == 0) - break; - /* Update the information about the level below */ - bcnt = cnt; - bsz = c->nnode_sz; - row -= 1; - } - - if (c->big_lpt) { - /* Need to add LPT's save table */ - if (len + c->lsave_sz > c->leb_size) { - alen = ALIGN(len, c->min_io_size); - set_ltab(c, lnum, c->leb_size - alen, alen - len); - memset(p, 0xff, alen - len); - err = write_leb(lnum++, alen, buf); - if (err) - goto out; - p = buf; - len = 0; - } - - c->lsave_lnum = lnum; - c->lsave_offs = len; - - for (i = 0; i < c->lsave_cnt; i++) - lsave[i] = c->main_first + i; - - pack_lsave(c, p, lsave); - p += c->lsave_sz; - len += c->lsave_sz; - } - - /* Need to add LPT's own LEB properties table */ - if (len + c->ltab_sz > c->leb_size) { - alen = ALIGN(len, c->min_io_size); - set_ltab(c, lnum, c->leb_size - alen, alen - len); - memset(p, 0xff, alen - len); - err = write_leb(lnum++, alen, buf); - if (err) - goto out; - p = buf; - len = 0; - } - - c->ltab_lnum = lnum; - c->ltab_offs = len; - - /* Update ltab before packing it */ - len += c->ltab_sz; - alen = ALIGN(len, c->min_io_size); - set_ltab(c, lnum, c->leb_size - alen, alen - len); - - pack_ltab(c, p, c->ltab); - p += c->ltab_sz; - - /* Write remaining buffer */ - memset(p, 0xff, alen - len); - err = write_leb(lnum, alen, buf); - if (err) - goto out; - - c->nhead_lnum = lnum; - c->nhead_offs = ALIGN(len, c->min_io_size); - - dbg_msg(1, "lpt_sz: %lld", c->lpt_sz); - dbg_msg(1, "space_bits: %d", c->space_bits); - dbg_msg(1, "lpt_lnum_bits: %d", c->lpt_lnum_bits); - dbg_msg(1, "lpt_offs_bits: %d", c->lpt_offs_bits); - dbg_msg(1, "lpt_spc_bits: %d", c->lpt_spc_bits); - dbg_msg(1, "pcnt_bits: %d", c->pcnt_bits); - dbg_msg(1, "lnum_bits: %d", c->lnum_bits); - dbg_msg(1, "pnode_sz: %d", c->pnode_sz); - dbg_msg(1, "nnode_sz: %d", c->nnode_sz); - dbg_msg(1, "ltab_sz: %d", c->ltab_sz); - dbg_msg(1, "lsave_sz: %d", c->lsave_sz); - dbg_msg(1, "lsave_cnt: %d", c->lsave_cnt); - dbg_msg(1, "lpt_hght: %d", c->lpt_hght); - dbg_msg(1, "big_lpt: %d", c->big_lpt); - dbg_msg(1, "LPT root is at %d:%d", c->lpt_lnum, c->lpt_offs); - dbg_msg(1, "LPT head is at %d:%d", c->nhead_lnum, c->nhead_offs); - dbg_msg(1, "LPT ltab is at %d:%d", c->ltab_lnum, c->ltab_offs); - if (c->big_lpt) - dbg_msg(1, "LPT lsave is at %d:%d", - c->lsave_lnum, c->lsave_offs); -out: - free(lsave); - free(buf); - free(nnode); - free(pnode); - return err; -} diff --git a/ubifs-utils/mkfs.ubifs/lpt.h b/ubifs-utils/mkfs.ubifs/lpt.h deleted file mode 100644 index 4cde59d..0000000 --- a/ubifs-utils/mkfs.ubifs/lpt.h +++ /dev/null @@ -1,28 +0,0 @@ -/* - * Copyright (C) 2008 Nokia Corporation. - * Copyright (C) 2008 University of Szeged, Hungary - * - * 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 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 - * - * Authors: Artem Bityutskiy - * Adrian Hunter - */ - -#ifndef __UBIFS_LPT_H__ -#define __UBIFS_LPT_H__ - -int calc_dflt_lpt_geom(struct ubifs_info *c, int *main_lebs, int *big_lpt); -int create_lpt(struct ubifs_info *c); - -#endif diff --git a/ubifs-utils/mkfs.ubifs/mkfs.ubifs.c b/ubifs-utils/mkfs.ubifs/mkfs.ubifs.c index d4be610..b5f3892 100644 --- a/ubifs-utils/mkfs.ubifs/mkfs.ubifs.c +++ b/ubifs-utils/mkfs.ubifs/mkfs.ubifs.c @@ -22,11 +22,18 @@ #define _XOPEN_SOURCE 500 /* For realpath() */ -#include "mkfs.ubifs.h" +#include <stdio.h> +#include <stdlib.h> +#include <libgen.h> +#include <getopt.h> +#include <dirent.h> #include <crc32.h> -#include "common.h" +#include <uuid.h> +#include <linux/fs.h> #include <sys/types.h> -#ifndef WITHOUT_XATTR +#include <sys/stat.h> +#include <sys/ioctl.h> +#ifdef WITH_XATTR #include <sys/xattr.h> #endif @@ -35,12 +42,20 @@ #include <selinux/label.h> #endif -#ifndef WITHOUT_ZSTD +#ifdef WITH_ZSTD #include <zstd.h> #endif +#include "bitops.h" #include "crypto.h" #include "fscrypt.h" +#include "ubifs.h" +#include "defs.h" +#include "debug.h" +#include "key.h" +#include "compr.h" +#include "misc.h" +#include "devtable.h" /* Size (prime number) of hash table for link counting */ #define HASH_TABLE_SIZE 10099 @@ -56,7 +71,6 @@ #ifdef WITH_SELINUX #define XATTR_NAME_SELINUX "security.selinux" static struct selabel_handle *sehnd; -static char *secontext; #endif /** @@ -123,10 +137,7 @@ struct inum_mapping { */ struct ubifs_info info_; static struct ubifs_info *c = &info_; -static libubi_t ubi; -/* Debug levels are: 0 (none), 1 (statistics), 2 (files) ,3 (more details) */ -int debug_level; int verbose; int yes; @@ -134,9 +145,6 @@ static char *root; static int root_len; static struct fscrypt_context *root_fctx; static struct stat root_st; -static char *output; -static int out_fd; -static int out_ubi; static int squash_owner; static int do_create_inum_attr; static char *context; @@ -210,9 +218,9 @@ static const char *helptext = "Usage: mkfs.ubifs [OPTIONS] target\n" "Make a UBIFS file system image from an existing directory tree\n\n" "Examples:\n" -"Build file system from directory /opt/img, writting the result in the ubifs.img file\n" +"Build file system from directory /opt/img, writing the result in the ubifs.img file\n" "\tmkfs.ubifs -m 512 -e 128KiB -c 100 -r /opt/img ubifs.img\n" -"The same, but writting directly to an UBI volume\n" +"The same, but writing directly to an UBI volume\n" "\tmkfs.ubifs -r /opt/img /dev/ubi0_0\n" "Creating an empty UBIFS filesystem on an UBI volume\n" "\tmkfs.ubifs /dev/ubi0_0\n\n" @@ -241,8 +249,8 @@ static const char *helptext = "-y, --yes assume the answer is \"yes\" for all questions\n" "-v, --verbose verbose operation\n" "-V, --version display version information\n" -"-g, --debug=LEVEL display debug information (0 - none, 1 - statistics,\n" -" 2 - files, 3 - more details)\n" +"-g, --debug=LEVEL display printing information (0 - none, 1 - error message, \n" +" 2 - warning message[default], 3 - notice message, 4 - debug message)\n" "-a, --set-inum-attr create user.image-inode-number extended attribute on files\n" " added to the image. The attribute will contain the inode\n" " number the file has in the generated image.\n" @@ -283,11 +291,6 @@ static const char *helptext = "mkfs.ubifs supports building signed images. For this the \"--hash-algo\",\n" "\"--auth-key\" and \"--auth-cert\" options have to be specified.\n"; -static inline uint8_t *ubifs_branch_hash(struct ubifs_branch *br) -{ - return (void *)br + sizeof(*br) + c->key_len; -} - /** * make_path - make a path name from a directory and a name. * @dir: directory path name @@ -396,62 +399,62 @@ static int validate_options(void) { int tmp; - if (!output) - return err_msg("no output file or UBI volume specified"); + if (!c->dev_name) + return errmsg("no output file or UBI volume specified"); if (root) { - tmp = is_contained(output, root); + tmp = is_contained(c->dev_name, root); if (tmp < 0) - return err_msg("failed to perform output file root check"); + return errmsg("failed to perform output file root check"); else if (tmp) - return err_msg("output file cannot be in the UBIFS root " + return errmsg("output file cannot be in the UBIFS root " "directory"); } if (!is_power_of_2(c->min_io_size)) - return err_msg("min. I/O unit size should be power of 2"); + return errmsg("min. I/O unit size should be power of 2"); if (c->leb_size < c->min_io_size) - return err_msg("min. I/O unit cannot be larger than LEB size"); + return errmsg("min. I/O unit cannot be larger than LEB size"); if (c->leb_size < UBIFS_MIN_LEB_SZ) - return err_msg("too small LEB size %d, minimum is %d", + return errmsg("too small LEB size %d, minimum is %d", c->leb_size, UBIFS_MIN_LEB_SZ); if (c->leb_size % c->min_io_size) - return err_msg("LEB should be multiple of min. I/O units"); + return errmsg("LEB should be multiple of min. I/O units"); if (c->leb_size % 8) - return err_msg("LEB size has to be multiple of 8"); + return errmsg("LEB size has to be multiple of 8"); if (c->leb_size > UBIFS_MAX_LEB_SZ) - return err_msg("too large LEB size %d, maximum is %d", + return errmsg("too large LEB size %d, maximum is %d", c->leb_size, UBIFS_MAX_LEB_SZ); if (c->max_leb_cnt < UBIFS_MIN_LEB_CNT) - return err_msg("too low max. count of LEBs, minimum is %d", + return errmsg("too low max. count of LEBs, minimum is %d", UBIFS_MIN_LEB_CNT); if (c->fanout < UBIFS_MIN_FANOUT) - return err_msg("too low fanout, minimum is %d", + return errmsg("too low fanout, minimum is %d", UBIFS_MIN_FANOUT); tmp = c->leb_size - UBIFS_IDX_NODE_SZ; tmp /= UBIFS_BRANCH_SZ + UBIFS_MAX_KEY_LEN; if (c->fanout > tmp) - return err_msg("too high fanout, maximum is %d", tmp); + return errmsg("too high fanout, maximum is %d", tmp); if (c->log_lebs < UBIFS_MIN_LOG_LEBS) - return err_msg("too few log LEBs, minimum is %d", + return errmsg("too few log LEBs, minimum is %d", UBIFS_MIN_LOG_LEBS); if (c->log_lebs >= c->max_leb_cnt - UBIFS_MIN_LEB_CNT) - return err_msg("too many log LEBs, maximum is %d", + return errmsg("too many log LEBs, maximum is %d", c->max_leb_cnt - UBIFS_MIN_LEB_CNT); if (c->orph_lebs < UBIFS_MIN_ORPH_LEBS) - return err_msg("too few orphan LEBs, minimum is %d", + return errmsg("too few orphan LEBs, minimum is %d", UBIFS_MIN_ORPH_LEBS); if (c->orph_lebs >= c->max_leb_cnt - UBIFS_MIN_LEB_CNT) - return err_msg("too many orphan LEBs, maximum is %d", + return errmsg("too many orphan LEBs, maximum is %d", c->max_leb_cnt - UBIFS_MIN_LEB_CNT); tmp = UBIFS_SB_LEBS + UBIFS_MST_LEBS + c->log_lebs + c->lpt_lebs; tmp += c->orph_lebs + 4; if (tmp > c->max_leb_cnt) - return err_msg("too low max. count of LEBs, expected at " + return errmsg("too low max. count of LEBs, expected at " "least %d", tmp); tmp = calc_min_log_lebs(c->max_bud_bytes); if (c->log_lebs < calc_min_log_lebs(c->max_bud_bytes)) - return err_msg("too few log LEBs, expected at least %d", tmp); + return errmsg("too few log LEBs, expected at least %d", tmp); if (c->rp_size >= ((long long)c->leb_size * c->max_leb_cnt) / 2) - return err_msg("too much reserved space %lld", c->rp_size); + return errmsg("too much reserved space %lld", c->rp_size); return 0; } @@ -497,41 +500,19 @@ static long long get_bytes(const char *str) long long bytes = strtoull(str, &endp, 0); if (endp == str || bytes < 0) - return err_msg("incorrect amount of bytes: \"%s\"", str); + return errmsg("incorrect amount of bytes: \"%s\"", str); if (*endp != '\0') { int mult = get_multiplier(endp); if (mult == -1) - return err_msg("bad size specifier: \"%s\" - " + return errmsg("bad size specifier: \"%s\" - " "should be 'KiB', 'MiB' or 'GiB'", endp); bytes *= mult; } return bytes; } -/** - * open_ubi - open the UBI volume. - * @node: name of the UBI volume character device to fetch information about - * - * Returns %0 in case of success and %-1 in case of failure - */ -static int open_ubi(const char *node) -{ - struct stat st; - - if (stat(node, &st) || !S_ISCHR(st.st_mode)) - return -1; - - ubi = libubi_open(); - if (!ubi) - return -1; - if (ubi_get_vol_info(ubi, node, &c->vi)) - return -1; - if (ubi_get_dev_info1(ubi, c->vi.dev_num, &c->di)) - return -1; - return 0; -} static void select_default_compr(void) { @@ -540,10 +521,12 @@ static void select_default_compr(void) return; } -#ifdef WITHOUT_LZO +#ifdef WITH_LZO + c->default_compr = UBIFS_COMPR_LZO; +#elif defined(WITH_ZLIB) c->default_compr = UBIFS_COMPR_ZLIB; #else - c->default_compr = UBIFS_COMPR_LZO; + c->default_compr = UBIFS_COMPR_NONE; #endif } @@ -555,7 +538,7 @@ static int get_options(int argc, char**argv) struct stat st; char *endp; #ifdef WITH_CRYPTO - const char *cipher_name; + const char *cipher_name = NULL; #endif c->fanout = 8; @@ -595,31 +578,31 @@ static int get_options(int argc, char**argv) /* Make sure the root directory exists */ if (stat(root, &st)) - return sys_err_msg("bad root directory '%s'", + return sys_errmsg("bad root directory '%s'", root); break; case 'm': c->min_io_size = get_bytes(optarg); if (c->min_io_size <= 0) - return err_msg("bad min. I/O size"); + return errmsg("bad min. I/O size"); break; case 'e': c->leb_size = get_bytes(optarg); if (c->leb_size <= 0) - return err_msg("bad LEB size"); + return errmsg("bad LEB size"); break; case 'c': c->max_leb_cnt = get_bytes(optarg); if (c->max_leb_cnt <= 0) - return err_msg("bad maximum LEB count"); + return errmsg("bad maximum LEB count"); break; case 'o': - output = xstrdup(optarg); + c->dev_name = xstrdup(optarg); break; case 'D': tbl_file = optarg; if (stat(tbl_file, &st) < 0) - return sys_err_msg("bad device table file '%s'", + return sys_errmsg("bad device table file '%s'", tbl_file); break; case 'y': @@ -642,16 +625,16 @@ static int get_options(int argc, char**argv) common_print_version(); exit(EXIT_SUCCESS); case 'g': - debug_level = strtol(optarg, &endp, 0); + c->debug_level = strtol(optarg, &endp, 0); if (*endp != '\0' || endp == optarg || - debug_level < 0 || debug_level > 3) - return err_msg("bad debugging level '%s'", + c->debug_level < 0 || c->debug_level > DEBUG_LEVEL) + return errmsg("bad debugging level '%s'", optarg); break; case 'f': c->fanout = strtol(optarg, &endp, 0); if (*endp != '\0' || endp == optarg || c->fanout <= 0) - return err_msg("bad fanout %s", optarg); + return errmsg("bad fanout %s", optarg); break; case 'F': c->space_fixup = 1; @@ -659,14 +642,14 @@ static int get_options(int argc, char**argv) case 'l': c->log_lebs = strtol(optarg, &endp, 0); if (*endp != '\0' || endp == optarg || c->log_lebs <= 0) - return err_msg("bad count of log LEBs '%s'", + return errmsg("bad count of log LEBs '%s'", optarg); break; case 'p': c->orph_lebs = strtol(optarg, &endp, 0); if (*endp != '\0' || endp == optarg || c->orph_lebs <= 0) - return err_msg("bad orphan LEB count '%s'", + return errmsg("bad orphan LEB count '%s'", optarg); break; case 'k': @@ -677,48 +660,52 @@ static int get_options(int argc, char**argv) c->key_hash = key_test_hash; c->key_hash_type = UBIFS_KEY_HASH_TEST; } else - return err_msg("bad key hash"); + return errmsg("bad key hash"); break; case 'x': if (strcmp(optarg, "none") == 0) c->default_compr = UBIFS_COMPR_NONE; +#ifdef WITH_ZLIB else if (strcmp(optarg, "zlib") == 0) c->default_compr = UBIFS_COMPR_ZLIB; -#ifndef WITHOUT_ZSTD +#endif +#ifdef WITH_ZSTD else if (strcmp(optarg, "zstd") == 0) c->default_compr = UBIFS_COMPR_ZSTD; #endif -#ifndef WITHOUT_LZO +#ifdef WITH_LZO + else if (strcmp(optarg, "lzo") == 0) + c->default_compr = UBIFS_COMPR_LZO; +#endif +#if defined(WITH_LZO) && defined(WITH_ZLIB) else if (strcmp(optarg, "favor_lzo") == 0) { c->default_compr = UBIFS_COMPR_LZO; c->favor_lzo = 1; - } else if (strcmp(optarg, "lzo") == 0) { - c->default_compr = UBIFS_COMPR_LZO; } #endif else - return err_msg("bad compressor name"); + return errmsg("bad compressor name"); break; case 'X': -#ifdef WITHOUT_LZO - return err_msg("built without LZO support"); +#if !defined(WITH_LZO) && !defined(WITH_ZLIB) + return errmsg("built without LZO or ZLIB support"); #else c->favor_percent = strtol(optarg, &endp, 0); if (*endp != '\0' || endp == optarg || c->favor_percent <= 0 || c->favor_percent >= 100) - return err_msg("bad favor LZO percent '%s'", + return errmsg("bad favor LZO percent '%s'", optarg); #endif break; case 'j': c->max_bud_bytes = get_bytes(optarg); if (c->max_bud_bytes <= 0) - return err_msg("bad maximum amount of buds"); + return errmsg("bad maximum amount of buds"); break; case 'R': c->rp_size = get_bytes(optarg); if (c->rp_size < 0) - return err_msg("bad reserved bytes count"); + return errmsg("bad reserved bytes count"); break; case 'U': squash_owner = 1; @@ -731,23 +718,24 @@ static int get_options(int argc, char**argv) context_len = strlen(optarg); context = (char *) xmalloc(context_len + 1); if (!context) - return err_msg("xmalloc failed\n"); + return errmsg("xmalloc failed\n"); memcpy(context, optarg, context_len); + context[context_len] = '\0'; /* Make sure root directory exists */ if (stat(context, &context_st)) - return sys_err_msg("bad file context %s\n", + return sys_errmsg("bad file context %s\n", context); break; case 'K': if (key_file) { - return err_msg("key file specified more than once"); + return errmsg("key file specified more than once"); } key_file = optarg; break; case 'b': if (key_desc) { - return err_msg("key descriptor specified more than once"); + return errmsg("key descriptor specified more than once"); } key_desc = optarg; break; @@ -798,20 +786,20 @@ static int get_options(int argc, char**argv) case HASH_ALGO_OPTION: case AUTH_KEY_OPTION: case AUTH_CERT_OPTION: - return err_msg("mkfs.ubifs was built without crypto support."); + return errmsg("mkfs.ubifs was built without crypto support."); #endif } } - if (optind != argc && !output) - output = xstrdup(argv[optind]); + if (optind != argc && !c->dev_name) + c->dev_name = xstrdup(argv[optind]); - if (!output) - return err_msg("not output device or file specified"); + if (!c->dev_name) + return errmsg("not output device or file specified"); - out_ubi = !open_ubi(output); + open_ubi(c, c->dev_name); - if (out_ubi) { + if (c->libubi) { c->min_io_size = c->di.min_io_size; c->leb_size = c->vi.leb_size; if (c->max_leb_cnt == -1) @@ -820,7 +808,7 @@ static int get_options(int argc, char**argv) if (key_file || key_desc) { #ifdef WITH_CRYPTO if (!key_file) - return err_msg("no key file specified"); + return errmsg("no key file specified"); c->double_hash = 1; c->encrypted = 1; @@ -833,7 +821,7 @@ static int get_options(int argc, char**argv) if (!root_fctx) return -1; #else - return err_msg("mkfs.ubifs was built without crypto support."); + return errmsg("mkfs.ubifs was built without crypto support."); #endif } @@ -841,14 +829,14 @@ static int get_options(int argc, char**argv) select_default_compr(); if (c->min_io_size == -1) - return err_msg("min. I/O unit was not specified " + return errmsg("min. I/O unit was not specified " "(use -h for help)"); if (c->leb_size == -1) - return err_msg("LEB size was not specified (use -h for help)"); + return errmsg("LEB size was not specified (use -h for help)"); if (c->max_leb_cnt == -1) - return err_msg("Maximum count of LEBs was not specified " + return errmsg("Maximum count of LEBs was not specified " "(use -h for help)"); if (c->max_bud_bytes == -1) { @@ -889,7 +877,7 @@ static int get_options(int argc, char**argv) printf("\tmin_io_size: %d\n", c->min_io_size); printf("\tleb_size: %d\n", c->leb_size); printf("\tmax_leb_cnt: %d\n", c->max_leb_cnt); - printf("\toutput: %s\n", output); + printf("\toutput: %s\n", c->dev_name); printf("\tjrn_size: %llu\n", c->max_bud_bytes); printf("\treserved: %llu\n", c->rp_size); switch (c->default_compr) { @@ -915,52 +903,7 @@ static int get_options(int argc, char**argv) return -1; if (tbl_file && parse_devtable(tbl_file)) - return err_msg("cannot parse device table file '%s'", tbl_file); - - return 0; -} - -/** - * prepare_node - fill in the common header. - * @node: node - * @len: node length - */ -static void prepare_node(void *node, int len) -{ - uint32_t crc; - struct ubifs_ch *ch = node; - - ch->magic = cpu_to_le32(UBIFS_NODE_MAGIC); - ch->len = cpu_to_le32(len); - ch->group_type = UBIFS_NO_NODE_GROUP; - ch->sqnum = cpu_to_le64(++c->max_sqnum); - ch->padding[0] = ch->padding[1] = 0; - crc = mtd_crc32(UBIFS_CRC32_INIT, node + 8, len - 8); - ch->crc = cpu_to_le32(crc); -} - -/** - * write_leb - copy the image of a LEB to the output target. - * @lnum: LEB number - * @len: length of data in the buffer - * @buf: buffer (must be at least c->leb_size bytes) - */ -int write_leb(int lnum, int len, void *buf) -{ - off_t pos = (off_t)lnum * c->leb_size; - - dbg_msg(3, "LEB %d len %d", lnum, len); - memset(buf + len, 0xff, c->leb_size - len); - if (out_ubi) - if (ubi_leb_change_start(ubi, out_fd, lnum, c->leb_size)) - return sys_err_msg("ubi_leb_change_start failed"); - - if (lseek(out_fd, pos, SEEK_SET) != pos) - return sys_err_msg("lseek failed seeking %lld", (long long)pos); - - if (write(out_fd, buf, c->leb_size) != c->leb_size) - return sys_err_msg("write failed writing %d bytes at pos %lld", - c->leb_size, (long long)pos); + return errmsg("cannot parse device table file '%s'", tbl_file); return 0; } @@ -971,46 +914,8 @@ int write_leb(int lnum, int len, void *buf) */ static int write_empty_leb(int lnum) { - return write_leb(lnum, 0, leb_buf); -} - -/** - * do_pad - pad a buffer to the minimum I/O size. - * @buf: buffer - * @len: buffer length - */ -static int do_pad(void *buf, int len) -{ - int pad_len, alen = ALIGN(len, 8), wlen = ALIGN(alen, c->min_io_size); - uint32_t crc; - - memset(buf + len, 0xff, alen - len); - pad_len = wlen - alen; - dbg_msg(3, "len %d pad_len %d", len, pad_len); - buf += alen; - if (pad_len >= (int)UBIFS_PAD_NODE_SZ) { - struct ubifs_ch *ch = buf; - struct ubifs_pad_node *pad_node = buf; - - ch->magic = cpu_to_le32(UBIFS_NODE_MAGIC); - ch->node_type = UBIFS_PAD_NODE; - ch->group_type = UBIFS_NO_NODE_GROUP; - ch->padding[0] = ch->padding[1] = 0; - ch->sqnum = cpu_to_le64(0); - ch->len = cpu_to_le32(UBIFS_PAD_NODE_SZ); - - pad_len -= UBIFS_PAD_NODE_SZ; - pad_node->pad_len = cpu_to_le32(pad_len); - - crc = mtd_crc32(UBIFS_CRC32_INIT, buf + 8, - UBIFS_PAD_NODE_SZ - 8); - ch->crc = cpu_to_le32(crc); - - memset(buf + UBIFS_PAD_NODE_SZ, 0, pad_len); - } else if (pad_len > 0) - memset(buf, UBIFS_PADDING_BYTE, pad_len); - - return wlen; + memset(leb_buf, 0xff, c->leb_size); + return ubifs_leb_change(c, lnum, leb_buf, c->leb_size); } /** @@ -1021,13 +926,16 @@ static int do_pad(void *buf, int len) */ static int write_node(void *node, int len, int lnum) { - prepare_node(node, len); + int alen = ALIGN(len, 8), wlen = ALIGN(len, c->min_io_size); + ubifs_prepare_node(c, node, len, 0); memcpy(leb_buf, node, len); + memset(leb_buf + len, 0xff, alen - len); + ubifs_pad(c, leb_buf + alen, wlen - alen); - len = do_pad(leb_buf, len); + memset(leb_buf + wlen, 0xff, c->leb_size - wlen); - return write_leb(lnum, len, leb_buf); + return ubifs_leb_change(c, lnum, leb_buf, c->leb_size); } /** @@ -1074,8 +982,7 @@ static void set_lprops(int lnum, int offs, int flags) free = c->leb_size - ALIGN(offs, a); dirty = c->leb_size - free - ALIGN(offs, 8); - dbg_msg(3, "LEB %d free %d dirty %d flags %d", lnum, free, dirty, - flags); + pr_debug("LEB %d free %d dirty %d flags %d\n", lnum, free, dirty, flags); if (i < c->main_lebs) { c->lpt[i].free = free; c->lpt[i].dirty = dirty; @@ -1110,7 +1017,7 @@ static int add_to_index(union ubifs_key *key, char *name, int name_len, { struct idx_entry *e; - dbg_msg(3, "LEB %d offs %d len %d", lnum, offs, len); + pr_debug("LEB %d offs %d len %d\n", lnum, offs, len); e = xmalloc(sizeof(struct idx_entry)); e->next = NULL; e->prev = idx_list_last; @@ -1140,8 +1047,10 @@ static int flush_nodes(void) if (!head_offs) return 0; - len = do_pad(leb_buf, head_offs); - err = write_leb(head_lnum, len, leb_buf); + len = ALIGN(head_offs, c->min_io_size); + ubifs_pad(c, leb_buf + head_offs, len - head_offs); + memset(leb_buf + len, 0xff, c->leb_size - len); + err = ubifs_leb_change(c, head_lnum, leb_buf, c->leb_size); if (err) return err; set_lprops(head_lnum, head_offs, head_flags); @@ -1179,19 +1088,19 @@ static int reserve_space(int len, int *lnum, int *offs) */ static int add_node(union ubifs_key *key, char *name, int name_len, void *node, int len) { - int err, lnum, offs, type = key_type(key); + int err, lnum, offs, type = key_type(c, key); uint8_t hash[UBIFS_MAX_HASH_LEN]; if (type == UBIFS_DENT_KEY || type == UBIFS_XENT_KEY) { if (!name) - return err_msg("Directory entry or xattr " + return errmsg("Directory entry or xattr " "without name!"); } else { if (name) - return err_msg("Name given for non dir/xattr node!"); + return errmsg("Name given for non dir/xattr node!"); } - prepare_node(node, len); + ubifs_prepare_node(c, node, len, 0); err = reserve_space(len, &lnum, &offs); if (err) @@ -1200,7 +1109,7 @@ static int add_node(union ubifs_key *key, char *name, int name_len, void *node, memcpy(leb_buf + offs, node, len); memset(leb_buf + offs + len, 0xff, ALIGN(len, 8) - len); - ubifs_node_calc_hash(node, hash); + ubifs_node_calc_hash(c, node, hash); add_to_index(key, name, name_len, lnum, offs, len, hash); @@ -1213,41 +1122,43 @@ static int add_xattr(struct ubifs_ino_node *host_ino, struct stat *st, { struct ubifs_ino_node *ino; struct ubifs_dent_node *xent; - struct qstr nm; + struct fscrypt_name nm; + char *tmp_name; union ubifs_key xkey, nkey; int len, ret; - nm.len = strlen(name); - nm.name = xmalloc(nm.len + 1); - memcpy(nm.name, name, nm.len + 1); + fname_len(&nm) = strlen(name); + tmp_name = xmalloc(fname_len(&nm) + 1); + memcpy(tmp_name, name, fname_len(&nm) + 1); + fname_name(&nm) = tmp_name; host_ino->xattr_cnt++; - host_ino->xattr_size += CALC_DENT_SIZE(nm.len); + host_ino->xattr_size += CALC_DENT_SIZE(fname_len(&nm)); host_ino->xattr_size += CALC_XATTR_BYTES(data_len); - host_ino->xattr_names += nm.len; + host_ino->xattr_names += fname_len(&nm); - xent = xzalloc(sizeof(*xent) + nm.len + 1); + xent = xzalloc(sizeof(*xent) + fname_len(&nm) + 1); ino = xzalloc(sizeof(*ino) + data_len); xent_key_init(c, &xkey, inum, &nm); xent->ch.node_type = UBIFS_XENT_NODE; - key_write(&xkey, &xent->key); + key_write(c, &xkey, &xent->key); - len = UBIFS_XENT_NODE_SZ + nm.len + 1; + len = UBIFS_XENT_NODE_SZ + fname_len(&nm) + 1; xent->ch.len = len; xent->padding1 = 0; - xent->type = UBIFS_ITYPE_DIR; - xent->nlen = cpu_to_le16(nm.len); + xent->type = UBIFS_ITYPE_REG; + xent->nlen = cpu_to_le16(fname_len(&nm)); - memcpy(xent->name, nm.name, nm.len + 1); + memcpy(xent->name, fname_name(&nm), fname_len(&nm) + 1); inum = ++c->highest_inum; creat_sqnum = ++c->max_sqnum; xent->inum = cpu_to_le64(inum); - ret = add_node(&xkey, nm.name, nm.len, xent, len); + ret = add_node(&xkey, tmp_name, fname_len(&nm), xent, len); if (ret) goto out; @@ -1268,8 +1179,8 @@ static int add_xattr(struct ubifs_ino_node *host_ino, struct stat *st, ino->compr_type = cpu_to_le16(c->default_compr); ino->ch.node_type = UBIFS_INO_NODE; - ino_key_init(&nkey, inum); - key_write(&nkey, &ino->key); + ino_key_init(c, &nkey, inum); + key_write(c, &nkey, &ino->key); ino->size = cpu_to_le64(data_len); ino->mode = cpu_to_le32(S_IFREG); @@ -1288,7 +1199,7 @@ out: return ret; } -#ifdef WITHOUT_XATTR +#ifndef WITH_XATTR static inline int create_inum_attr(ino_t inum, const char *name) { (void)inum; @@ -1341,7 +1252,7 @@ static int inode_add_xattr(struct ubifs_ino_node *host_ino, if (errno == ENOENT || errno == EOPNOTSUPP) return 0; - sys_err_msg("llistxattr failed on %s", path_name); + sys_errmsg("llistxattr failed on %s", path_name); return len; } @@ -1353,7 +1264,7 @@ static int inode_add_xattr(struct ubifs_ino_node *host_ino, len = llistxattr(path_name, buf, len); if (len < 0) { - sys_err_msg("llistxattr failed on %s", path_name); + sys_errmsg("llistxattr failed on %s", path_name); goto out_free; } @@ -1367,7 +1278,7 @@ static int inode_add_xattr(struct ubifs_ino_node *host_ino, attrsize = lgetxattr(path_name, name, attrbuf, sizeof(attrbuf) - 1); if (attrsize < 0) { - sys_err_msg("lgetxattr failed on %s", path_name); + sys_errmsg("lgetxattr failed on %s", path_name); goto out_free; } @@ -1377,7 +1288,7 @@ static int inode_add_xattr(struct ubifs_ino_node *host_ino, inum_from_xattr = strtoull(attrbuf, NULL, 10); if (inum != inum_from_xattr) { errno = EINVAL; - sys_err_msg("calculated inum (%llu) doesn't match inum from xattr (%llu) size (%zd) on %s", + sys_errmsg("calculated inum (%llu) doesn't match inum from xattr (%llu) size (%zd) on %s", (unsigned long long)inum, (unsigned long long)inum_from_xattr, attrsize, @@ -1388,6 +1299,15 @@ static int inode_add_xattr(struct ubifs_ino_node *host_ino, continue; } +#ifdef WITH_SELINUX + /* + Ignore selinux attributes if we have a label file, they are + instead provided by inode_add_selinux_xattr. + */ + if (!strcmp(name, XATTR_NAME_SELINUX) && context && sehnd) + continue; +#endif + ret = add_xattr(host_ino, st, inum, name, attrbuf, attrsize); if (ret < 0) goto out_free; @@ -1411,14 +1331,11 @@ static int inode_add_selinux_xattr(struct ubifs_ino_node *host_ino, int ret; char *sepath = NULL; char *name; - struct qstr nm; unsigned int con_size; + char *secontext; - if (!context || !sehnd) { - secontext = NULL; - con_size = 0; + if (!context || !sehnd) return 0; - } if (path_name[strlen(root)] == '/') sepath = strdup(&path_name[strlen(root)]); @@ -1427,31 +1344,24 @@ static int inode_add_selinux_xattr(struct ubifs_ino_node *host_ino, sepath = NULL; if (!sepath) - return sys_err_msg("could not get sepath\n"); + return sys_errmsg("could not get sepath\n"); if (selabel_lookup(sehnd, &secontext, sepath, st->st_mode) < 0) { /* Failed to lookup context, assume unlabeled */ secontext = strdup("system_u:object_r:unlabeled_t:s0"); - dbg_msg(2, "missing context: %s\t%s\t%d\n", secontext, sepath, - st->st_mode); + pr_debug("missing context: %s\t%s\t%d\n", secontext, sepath, + st->st_mode); } - dbg_msg(2, "appling selinux context on sepath=%s, secontext=%s\n", - sepath, secontext); + pr_debug("appling selinux context on sepath=%s, secontext=%s\n", + sepath, secontext); free(sepath); con_size = strlen(secontext) + 1; name = strdup(XATTR_NAME_SELINUX); - nm.name = name; - nm.len = strlen(name); - host_ino->xattr_cnt++; - host_ino->xattr_size += CALC_DENT_SIZE(nm.len); - host_ino->xattr_size += CALC_XATTR_BYTES(con_size); - host_ino->xattr_names += nm.len; - - ret = add_xattr(st, inum, secontext, con_size, &nm); + ret = add_xattr(host_ino, st, inum, name, secontext, con_size); if (ret < 0) - dbg_msg(2, "add_xattr failed %d\n", ret); + pr_debug("add_xattr failed %d\n", ret); return ret; } @@ -1563,9 +1473,9 @@ static int add_inode(struct stat *st, ino_t inum, void *data, use_flags |= UBIFS_CRYPT_FL; memset(ino, 0, UBIFS_INO_NODE_SZ); - ino_key_init(&key, inum); + ino_key_init(c, &key, inum); ino->ch.node_type = UBIFS_INO_NODE; - key_write(&key, &ino->key); + key_write(c, &key, &ino->key); ino->creat_sqnum = cpu_to_le64(creat_sqnum); ino->size = cpu_to_le64(st->st_size); ino->nlink = cpu_to_le32(st->st_nlink); @@ -1590,7 +1500,7 @@ static int add_inode(struct stat *st, ino_t inum, void *data, } else { /* TODO: what about device files? */ if (!S_ISLNK(st->st_mode)) - return err_msg("Expected symlink"); + return errmsg("Expected symlink"); ret = encrypt_symlink(&ino->data, data, data_len, fctx); if (ret < 0) @@ -1602,11 +1512,11 @@ static int add_inode(struct stat *st, ino_t inum, void *data, len = UBIFS_INO_NODE_SZ + data_len; if (xattr_path) { -#ifdef WITH_SELINUX ret = inode_add_selinux_xattr(ino, xattr_path, st, inum); -#else + if (ret < 0) + return ret; + ret = inode_add_xattr(ino, xattr_path, st, inum); -#endif if (ret < 0) return ret; } @@ -1645,7 +1555,7 @@ static int add_dir_inode(const char *path_name, DIR *dir, ino_t inum, loff_t siz if (dir) { fd = dirfd(dir); if (fd == -1) - return sys_err_msg("dirfd failed"); + return sys_errmsg("dirfd failed"); if (ioctl(fd, FS_IOC_GETFLAGS, &flags) == -1) flags = 0; } @@ -1683,9 +1593,9 @@ static int add_symlink_inode(const char *path_name, struct stat *st, ino_t inum, /* Take the symlink as is */ len = readlink(path_name, buf, UBIFS_MAX_INO_DATA + 1); if (len <= 0) - return sys_err_msg("readlink failed for %s", path_name); + return sys_errmsg("readlink failed for %s", path_name); if (len > UBIFS_MAX_INO_DATA) - return err_msg("symlink too long for %s", path_name); + return errmsg("symlink too long for %s", path_name); return add_inode(st, inum, buf, len, flags, path_name, fctx); } @@ -1706,19 +1616,22 @@ static void set_dent_cookie(struct ubifs_dent_node *dent) * @name: directory entry name * @inum: target inode number of the directory entry * @type: type of the target inode + * @kname_len: the length of name stored in the directory entry node is + * returned here */ static int add_dent_node(ino_t dir_inum, const char *name, ino_t inum, - unsigned char type, struct fscrypt_context *fctx) + unsigned char type, struct fscrypt_context *fctx, + int *kname_len) { struct ubifs_dent_node *dent = node_buf; union ubifs_key key; struct qstr dname; + struct fscrypt_name nm; char *kname; - int kname_len; int len; - dbg_msg(3, "%s ino %lu type %u dir ino %lu", name, (unsigned long)inum, - (unsigned int)type, (unsigned long)dir_inum); + pr_debug("%s ino %lu type %u dir ino %lu\n", name, (unsigned long)inum, + (unsigned int)type, (unsigned long)dir_inum); memset(dent, 0, UBIFS_DENT_NODE_SZ); dname.name = (void *)name; @@ -1732,10 +1645,10 @@ static int add_dent_node(ino_t dir_inum, const char *name, ino_t inum, set_dent_cookie(dent); if (!fctx) { - kname_len = dname.len; + *kname_len = dname.len; kname = strdup(name); if (!kname) - return err_msg("cannot allocate memory"); + return errmsg("cannot allocate memory"); } else { unsigned int max_namelen = UBIFS_MAX_NLEN; int ret; @@ -1748,18 +1661,20 @@ static int add_dent_node(ino_t dir_inum, const char *name, ino_t inum, if (ret < 0) return ret; - kname_len = ret; + *kname_len = ret; } - dent_key_init(c, &key, dir_inum, kname, kname_len); - dent->nlen = cpu_to_le16(kname_len); - memcpy(dent->name, kname, kname_len); - dent->name[kname_len] = '\0'; - len = UBIFS_DENT_NODE_SZ + kname_len + 1; + fname_name(&nm) = kname; + fname_len(&nm) = *kname_len; + dent_key_init(c, &key, dir_inum, &nm); + dent->nlen = cpu_to_le16(*kname_len); + memcpy(dent->name, kname, *kname_len); + dent->name[*kname_len] = '\0'; + len = UBIFS_DENT_NODE_SZ + *kname_len + 1; - key_write(&key, dent->key); + key_write(c, &key, dent->key); - return add_node(&key, kname, kname_len, dent, len); + return add_node(&key, kname, *kname_len, dent, len); } /** @@ -1828,7 +1743,7 @@ static int add_file(const char *path_name, struct stat *st, ino_t inum, fd = open(path_name, O_RDONLY | O_LARGEFILE); if (fd == -1) - return sys_err_msg("failed to open file '%s'", path_name); + return sys_errmsg("failed to open file '%s'", path_name); do { /* Read next block */ bytes_read = 0; @@ -1836,7 +1751,7 @@ static int add_file(const char *path_name, struct stat *st, ino_t inum, ret = read(fd, buf + bytes_read, UBIFS_BLOCK_SIZE - bytes_read); if (ret == -1) { - sys_err_msg("failed to read file '%s'", + sys_errmsg("failed to read file '%s'", path_name); close(fd); return 1; @@ -1853,16 +1768,18 @@ static int add_file(const char *path_name, struct stat *st, ino_t inum, } /* Make data node */ memset(dn, 0, UBIFS_DATA_NODE_SZ); - data_key_init(&key, inum, block_no); + data_key_init(c, &key, inum, block_no); dn->ch.node_type = UBIFS_DATA_NODE; - key_write(&key, &dn->key); + key_write(c, &key, &dn->key); out_len = NODE_BUFFER_SIZE - UBIFS_DATA_NODE_SZ; if (c->default_compr == UBIFS_COMPR_NONE && !c->encrypted && (flags & FS_COMPR_FL)) -#ifdef WITHOUT_LZO +#ifdef WITH_LZO + use_compr = UBIFS_COMPR_LZO; +#elif defined(WITH_ZLIB) use_compr = UBIFS_COMPR_ZLIB; #else - use_compr = UBIFS_COMPR_LZO; + use_compr = UBIFS_COMPR_NONE; #endif else use_compr = c->default_compr; @@ -1894,9 +1811,9 @@ static int add_file(const char *path_name, struct stat *st, ino_t inum, } while (ret != 0); if (close(fd) == -1) - return sys_err_msg("failed to close file '%s'", path_name); + return sys_errmsg("failed to close file '%s'", path_name); if (file_size != st->st_size) - return err_msg("file size changed during writing file '%s'", + return errmsg("file size changed during writing file '%s'", path_name); return add_inode(st, inum, NULL, 0, flags, path_name, fctx); @@ -1917,17 +1834,17 @@ static int add_non_dir(const char *path_name, ino_t *inum, unsigned int nlink, { int fd, flags = 0; - dbg_msg(2, "%s", path_name); + pr_debug("%s\n", path_name); if (S_ISREG(st->st_mode)) { fd = open(path_name, O_RDONLY); if (fd == -1) - return sys_err_msg("failed to open file '%s'", + return sys_errmsg("failed to open file '%s'", path_name); if (ioctl(fd, FS_IOC_GETFLAGS, &flags) == -1) flags = 0; if (close(fd) == -1) - return sys_err_msg("failed to close file '%s'", + return sys_errmsg("failed to close file '%s'", path_name); *type = UBIFS_ITYPE_REG; } else if (S_ISCHR(st->st_mode)) @@ -1941,7 +1858,7 @@ static int add_non_dir(const char *path_name, ino_t *inum, unsigned int nlink, else if (S_ISFIFO(st->st_mode)) *type = UBIFS_ITYPE_FIFO; else - return err_msg("file '%s' has unknown inode type", path_name); + return errmsg("file '%s' has unknown inode type", path_name); if (nlink) st->st_nlink = nlink; @@ -1955,7 +1872,7 @@ static int add_non_dir(const char *path_name, ino_t *inum, unsigned int nlink, im = lookup_inum_mapping(st->st_dev, st->st_ino); if (!im) - return err_msg("out of memory"); + return errmsg("out of memory"); if (im->use_nlink == 0) { /* New entry */ im->use_inum = *inum; @@ -1990,7 +1907,7 @@ static int add_non_dir(const char *path_name, ino_t *inum, unsigned int nlink, if (S_ISFIFO(st->st_mode)) return add_inode(st, *inum, NULL, 0, flags, NULL, NULL); - return err_msg("file '%s' has unknown inode type", path_name); + return errmsg("file '%s' has unknown inode type", path_name); } /** @@ -2007,7 +1924,7 @@ static int add_directory(const char *dir_name, ino_t dir_inum, struct stat *st, { struct dirent *entry; DIR *dir = NULL; - int err = 0; + int kname_len, err = 0; loff_t size = UBIFS_INO_NODE_SZ; char *name = NULL; unsigned int nlink = 2; @@ -2018,11 +1935,11 @@ static int add_directory(const char *dir_name, ino_t dir_inum, struct stat *st, unsigned char type; unsigned long long dir_creat_sqnum = ++c->max_sqnum; - dbg_msg(2, "%s", dir_name); + pr_debug("%s\n", dir_name); if (existing) { dir = opendir(dir_name); if (dir == NULL) - return sys_err_msg("cannot open directory '%s'", + return sys_errmsg("cannot open directory '%s'", dir_name); } @@ -2046,7 +1963,7 @@ static int add_directory(const char *dir_name, ino_t dir_inum, struct stat *st, if (!entry) { if (errno == 0) break; - sys_err_msg("error reading directory '%s'", dir_name); + sys_errmsg("error reading directory '%s'", dir_name); goto out_free; } @@ -2073,7 +1990,7 @@ static int add_directory(const char *dir_name, ino_t dir_inum, struct stat *st, free(name); name = make_path(dir_name, entry->d_name); if (lstat(name, &dent_st) == -1) { - sys_err_msg("lstat failed for file '%s'", name); + sys_errmsg("lstat failed for file '%s'", name); goto out_free; } @@ -2120,13 +2037,13 @@ static int add_directory(const char *dir_name, ino_t dir_inum, struct stat *st, goto out_free; } - err = add_dent_node(dir_inum, entry->d_name, inum, type, fctx); + err = add_dent_node(dir_inum, entry->d_name, inum, type, fctx, + &kname_len); if (err) { free_fscrypt_context(new_fctx); goto out_free; } - size += ALIGN(UBIFS_DENT_NODE_SZ + strlen(entry->d_name) + 1, - 8); + size += ALIGN(UBIFS_DENT_NODE_SZ + kname_len + 1, 8); if (new_fctx) free_fscrypt_context(new_fctx); @@ -2147,7 +2064,7 @@ static int add_directory(const char *dir_name, ino_t dir_inum, struct stat *st, * files. */ if (S_ISREG(nh_elt->mode)) { - err_msg("Bad device table entry %s/%s - it is " + errmsg("Bad device table entry %s/%s - it is " "prohibited to create regular files " "via device table", strcmp(ph_elt->path, "/") ? ph_elt->path : "", @@ -2191,13 +2108,14 @@ static int add_directory(const char *dir_name, ino_t dir_inum, struct stat *st, goto out_free; } - err = add_dent_node(dir_inum, nh_elt->name, inum, type, fctx); + err = add_dent_node(dir_inum, nh_elt->name, inum, type, fctx, + &kname_len); if (err) { free_fscrypt_context(new_fctx); goto out_free; } - size += ALIGN(UBIFS_DENT_NODE_SZ + strlen(nh_elt->name) + 1, 8); + size += ALIGN(UBIFS_DENT_NODE_SZ + kname_len + 1, 8); nh_elt = next_name_htbl_element(ph_elt, &itr); if (new_fctx) @@ -2213,7 +2131,7 @@ static int add_directory(const char *dir_name, ino_t dir_inum, struct stat *st, free(name); if (existing && closedir(dir) == -1) - return sys_err_msg("error closing directory '%s'", dir_name); + return sys_errmsg("error closing directory '%s'", dir_name); return 0; @@ -2237,7 +2155,7 @@ static int add_multi_linked_files(void) unsigned char type = 0; for (im = hash_table[i]; im; im = im->next) { - dbg_msg(2, "%s", im->path_name); + pr_debug("%s\n", im->path_name); err = add_non_dir(im->path_name, &im->use_inum, im->use_nlink, &type, &im->st, NULL); if (err) @@ -2260,7 +2178,7 @@ static int write_data(void) if (root) { err = stat(root, &root_st); if (err) - return sys_err_msg("bad root file-system directory '%s'", + return sys_errmsg("bad root file-system directory '%s'", root); if (squash_owner) root_st.st_uid = root_st.st_gid = 0; @@ -2314,7 +2232,7 @@ static int cmp_idx(const void *a, const void *b) const struct idx_entry *e2 = *(const struct idx_entry **)b; int cmp; - cmp = keys_cmp(&e1->key, &e2->key); + cmp = keys_cmp(c, &e1->key, &e2->key); if (cmp) return cmp; return namecmp(e1, e2); @@ -2331,7 +2249,7 @@ static int add_idx_node(void *node, int child_cnt) len = ubifs_idx_node_sz(c, child_cnt); - prepare_node(node, len); + ubifs_prepare_node(c, node, len, 0); err = reserve_space(len, &lnum, &offs); if (err) @@ -2340,10 +2258,10 @@ static int add_idx_node(void *node, int child_cnt) memcpy(leb_buf + offs, node, len); memset(leb_buf + offs + len, 0xff, ALIGN(len, 8) - len); - c->old_idx_sz += ALIGN(len, 8); + c->bi.old_idx_sz += ALIGN(len, 8); - dbg_msg(3, "at %d:%d len %d index size %llu", lnum, offs, len, - c->old_idx_sz); + pr_debug("at %d:%d len %d index size %llu\n", lnum, offs, len, + c->bi.old_idx_sz); /* The last index node written will be the root */ c->zroot.lnum = lnum; @@ -2365,7 +2283,7 @@ static int write_index(void) int child_cnt = 0, j, level, blnum, boffs, blen, blast_len, err; uint8_t *hashes; - dbg_msg(1, "leaf node count: %zd", idx_cnt); + pr_debug("leaf node count: %zd\n", idx_cnt); /* Reset the head for the index */ head_flags = LPROPS_INDEX; @@ -2376,7 +2294,7 @@ static int write_index(void) sz = idx_cnt * sizeof(struct idx_entry *); if (sz / sizeof(struct idx_entry *) != idx_cnt) { free(idx); - return err_msg("index is too big (%zu entries)", idx_cnt); + return errmsg("index is too big (%zu entries)", idx_cnt); } idx_ptr = xmalloc(sz); idx_ptr[0] = idx_list_first; @@ -2410,15 +2328,15 @@ static int write_index(void) idx->level = cpu_to_le16(0); for (j = 0; j < child_cnt; j++, p++) { br = ubifs_idx_branch(c, idx, j); - key_write_idx(&(*p)->key, &br->key); + key_write_idx(c, &(*p)->key, &br->key); br->lnum = cpu_to_le32((*p)->lnum); br->offs = cpu_to_le32((*p)->offs); br->len = cpu_to_le32((*p)->len); - memcpy(ubifs_branch_hash(br), (*p)->hash, c->hash_len); + memcpy(ubifs_branch_hash(c, br), (*p)->hash, c->hash_len); } add_idx_node(idx, child_cnt); - ubifs_node_calc_hash(idx, hashes + i * c->hash_len); + ubifs_node_calc_hash(c, idx, hashes + i * c->hash_len); } /* Write level 1 index nodes and above */ level = 0; @@ -2485,7 +2403,7 @@ static int write_index(void) * of the index node from the level below. */ br = ubifs_idx_branch(c, idx, j); - key_write_idx(&(*p)->key, &br->key); + key_write_idx(c, &(*p)->key, &br->key); br->lnum = cpu_to_le32(blnum); br->offs = cpu_to_le32(boffs); br->len = cpu_to_le32(blen); @@ -2496,12 +2414,12 @@ static int write_index(void) boffs += ALIGN(blen, 8); p += pstep; - memcpy(ubifs_branch_hash(br), + memcpy(ubifs_branch_hash(c, br), hashes + bn * c->hash_len, c->hash_len); } add_idx_node(idx, child_cnt); - ubifs_node_calc_hash(idx, hashes + i * c->hash_len); + ubifs_node_calc_hash(c, idx, hashes + i * c->hash_len); } } @@ -2515,13 +2433,13 @@ static int write_index(void) free(idx_ptr); free(idx); - dbg_msg(1, "zroot is at %d:%d len %d", c->zroot.lnum, c->zroot.offs, - c->zroot.len); + pr_debug("zroot is at %d:%d len %d\n", c->zroot.lnum, c->zroot.offs, + c->zroot.len); /* Set the index head */ c->ihead_lnum = head_lnum; c->ihead_offs = ALIGN(head_offs, c->min_io_size); - dbg_msg(1, "ihead is at %d:%d", c->ihead_lnum, c->ihead_offs); + pr_debug("ihead is at %d:%d\n", c->ihead_lnum, c->ihead_offs); /* Flush the last index LEB */ err = flush_nodes(); @@ -2554,7 +2472,7 @@ static int finalize_leb_cnt(void) { c->leb_cnt = head_lnum; if (c->leb_cnt > c->max_leb_cnt) - return err_msg("max_leb_cnt too low (%d needed)", c->leb_cnt); + return errmsg("max_leb_cnt too low (%d needed)", c->leb_cnt); c->main_lebs = c->leb_cnt - c->main_first; if (verbose) { printf("\tsuper lebs: %d\n", UBIFS_SB_LEBS); @@ -2567,13 +2485,13 @@ static int finalize_leb_cnt(void) printf("\tindex lebs: %d\n", c->lst.idx_lebs); printf("\tleb_cnt: %d\n", c->leb_cnt); } - dbg_msg(1, "total_free: %llu", c->lst.total_free); - dbg_msg(1, "total_dirty: %llu", c->lst.total_dirty); - dbg_msg(1, "total_used: %llu", c->lst.total_used); - dbg_msg(1, "total_dead: %llu", c->lst.total_dead); - dbg_msg(1, "total_dark: %llu", c->lst.total_dark); - dbg_msg(1, "index size: %llu", c->old_idx_sz); - dbg_msg(1, "empty_lebs: %d", c->lst.empty_lebs); + pr_debug("total_free: %llu\n", c->lst.total_free); + pr_debug("total_dirty: %llu\n", c->lst.total_dirty); + pr_debug("total_used: %llu\n", c->lst.total_used); + pr_debug("total_dead: %llu\n", c->lst.total_dead); + pr_debug("total_dark: %llu\n", c->lst.total_dark); + pr_debug("index size: %llu\n", c->bi.old_idx_sz); + pr_debug("empty_lebs: %d\n", c->lst.empty_lebs); return 0; } @@ -2599,7 +2517,6 @@ static int write_super(void) buf = xzalloc(c->leb_size); sup = buf; - sig = buf + UBIFS_SB_NODE_SZ; sup->ch.node_type = UBIFS_SB_NODE; sup->key_hash = c->key_hash_type; @@ -2635,27 +2552,27 @@ static int write_super(void) sup->flags |= cpu_to_le32(UBIFS_FLG_DOUBLE_HASH); if (c->encrypted) sup->flags |= cpu_to_le32(UBIFS_FLG_ENCRYPTION); - if (authenticated()) { + if (ubifs_authenticated(c)) { sup->flags |= cpu_to_le32(UBIFS_FLG_AUTHENTICATION); memcpy(sup->hash_mst, c->mst_hash, c->hash_len); } - prepare_node(sup, UBIFS_SB_NODE_SZ); + ubifs_prepare_node(c, sup, UBIFS_SB_NODE_SZ, 0); - err = sign_superblock_node(sup); + err = ubifs_sign_superblock_node(c, sup); if (err) goto out; sig = (void *)(sup + 1); - prepare_node(sig, UBIFS_SIG_NODE_SZ + le32_to_cpu(sig->len)); + ubifs_prepare_node(c, sig, UBIFS_SIG_NODE_SZ + le32_to_cpu(sig->len), 1); - len = do_pad(sig, UBIFS_SIG_NODE_SZ + le32_to_cpu(sig->len)); + len = ALIGN(ALIGN(UBIFS_SIG_NODE_SZ + le32_to_cpu(sig->len), 8), c->min_io_size); + memset(buf + UBIFS_SB_NODE_SZ + len, 0xff, c->leb_size - (UBIFS_SB_NODE_SZ + len)); - err = write_leb(UBIFS_SB_LNUM, UBIFS_SB_NODE_SZ + len, sup); + err = ubifs_leb_change(c, UBIFS_SB_LNUM, buf, c->leb_size); if (err) goto out; - err = 0; out: free(buf); @@ -2683,7 +2600,7 @@ static int write_master(void) mst.gc_lnum = cpu_to_le32(c->gc_lnum); mst.ihead_lnum = cpu_to_le32(c->ihead_lnum); mst.ihead_offs = cpu_to_le32(c->ihead_offs); - mst.index_size = cpu_to_le64(c->old_idx_sz); + mst.index_size = cpu_to_le64(c->bi.old_idx_sz); mst.lpt_lnum = cpu_to_le32(c->lpt_lnum); mst.lpt_offs = cpu_to_le32(c->lpt_offs); mst.nhead_lnum = cpu_to_le32(c->nhead_lnum); @@ -2702,7 +2619,7 @@ static int write_master(void) mst.total_dark = cpu_to_le64(c->lst.total_dark); mst.leb_cnt = cpu_to_le32(c->leb_cnt); - if (authenticated()) { + if (ubifs_authenticated(c)) { memcpy(mst.hash_root_idx, c->root_idx_hash, c->hash_len); memcpy(mst.hash_lpt, c->lpt_hash, c->hash_len); } @@ -2715,7 +2632,9 @@ static int write_master(void) if (err) return err; - mst_node_calc_hash(&mst, c->mst_hash); + err = ubifs_master_node_calc_hash(c, &mst, c->mst_hash); + if (err) + return err; return 0; } @@ -2755,7 +2674,8 @@ static int write_lpt(void) { int err, lnum; - err = create_lpt(c); + c->lscan_lnum = c->main_first; + err = ubifs_create_lpt(c, c->lpt, c->main_lebs, c->lpt_hash, true); if (err) return err; @@ -2786,87 +2706,11 @@ static int write_orphan_area(void) } /** - * check_volume_empty - check if the UBI volume is empty. - * - * This function checks if the UBI volume is empty by looking if its LEBs are - * mapped or not. - * - * Returns %0 in case of success, %1 is the volume is not empty, - * and a negative error code in case of failure. - */ -static int check_volume_empty(void) -{ - int lnum, err; - - for (lnum = 0; lnum < c->vi.rsvd_lebs; lnum++) { - err = ubi_is_mapped(out_fd, lnum); - if (err < 0) - return err; - if (err == 1) - return 1; - } - return 0; -} - -/** - * open_target - open the output target. - * - * Open the output target. The target can be an UBI volume - * or a file. - * - * Returns %0 in case of success and %-1 in case of failure. - */ -static int open_target(void) -{ - if (out_ubi) { - out_fd = open(output, O_RDWR | O_EXCL); - - if (out_fd == -1) - return sys_err_msg("cannot open the UBI volume '%s'", - output); - if (ubi_set_property(out_fd, UBI_VOL_PROP_DIRECT_WRITE, 1)) - return sys_err_msg("ubi_set_property failed"); - - if (!yes && check_volume_empty()) { - if (!prompt("UBI volume is not empty. Format anyways?", false)) - return err_msg("UBI volume is not empty"); - } - } else { - out_fd = open(output, O_CREAT | O_RDWR | O_TRUNC, - S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH); - if (out_fd == -1) - return sys_err_msg("cannot create output file '%s'", - output); - } - return 0; -} - - -/** - * close_target - close the output target. - * - * Close the output target. If the target was an UBI - * volume, also close libubi. - * - * Returns %0 in case of success and %-1 in case of failure. - */ -static int close_target(void) -{ - if (ubi) - libubi_close(ubi); - if (out_fd >= 0 && close(out_fd) == -1) - return sys_err_msg("cannot close the target '%s'", output); - if (output) - free(output); - return 0; -} - -/** * init - initialize things. */ static int init(void) { - int err, i, main_lebs, big_lpt = 0, sz; + int err, main_lebs, big_lpt = 0, sz; c->highest_inum = UBIFS_FIRST_INO; @@ -2875,7 +2719,7 @@ static int init(void) main_lebs = c->max_leb_cnt - UBIFS_SB_LEBS - UBIFS_MST_LEBS; main_lebs -= c->log_lebs + c->orph_lebs; - err = calc_dflt_lpt_geom(c, &main_lebs, &big_lpt); + err = ubifs_calc_dflt_lpt_geom(c, &main_lebs, &big_lpt); if (err) return err; @@ -2888,17 +2732,10 @@ static int init(void) c->lpt_last = c->lpt_first + c->lpt_lebs - 1; c->lpt = xmalloc(c->main_lebs * sizeof(struct ubifs_lprops)); - c->ltab = xmalloc(c->lpt_lebs * sizeof(struct ubifs_lprops)); - - /* Initialize LPT's own lprops */ - for (i = 0; i < c->lpt_lebs; i++) { - c->ltab[i].free = c->leb_size; - c->ltab[i].dirty = 0; - } c->dead_wm = ALIGN(MIN_WRITE_SZ, c->min_io_size); c->dark_wm = ALIGN(UBIFS_MAX_NODE_SZ, c->min_io_size); - dbg_msg(1, "dead_wm %d dark_wm %d", c->dead_wm, c->dark_wm); + pr_debug("dead_wm %d dark_wm %d\n", c->dead_wm, c->dark_wm); leb_buf = xmalloc(c->leb_size); node_buf = xmalloc(NODE_BUFFER_SIZE); @@ -2919,7 +2756,7 @@ static int init(void) sehnd = selabel_open(SELABEL_CTX_FILE, seopts, 1); if (!sehnd) - return err_msg("could not open selinux context\n"); + return errmsg("could not open selinux context\n"); } #endif @@ -2954,7 +2791,6 @@ static void deinit(void) #endif free(c->lpt); - free(c->ltab); free(leb_buf); free(node_buf); free(block_buf); @@ -2962,6 +2798,7 @@ static void deinit(void) free(hash_table); destroy_compression(); free_devtable_info(); + ubifs_exit_authentication(c); } /** @@ -2981,7 +2818,7 @@ static int mkfs(void) if (err) goto out; - err = init_authentication(); + err = ubifs_init_authentication(c); if (err) goto out; @@ -3028,30 +2865,41 @@ int main(int argc, char *argv[]) { int err; + init_ubifs_info(c, MKFS_PROGRAM_TYPE); + if (crypto_init()) return -1; err = get_options(argc, argv); if (err) - return err; + goto out; - err = open_target(); + err = open_target(c); if (err) - return err; + goto out; + + if (!yes && check_volume_empty(c)) { + if (!prompt("UBI volume is not empty. Format anyways?", false)) { + close_target(c); + err = errmsg("UBI volume is not empty"); + goto out; + } + } err = mkfs(); if (err) { - close_target(); - return err; + close_target(c); + goto out; } - err = close_target(); - if (err) - return err; + err = close_target(c); - if (verbose) + if (verbose && !err) printf("Success!\n"); +out: + free(c->dev_name); + close_ubi(c); crypto_cleanup(); - return 0; + return err; } diff --git a/ubifs-utils/mkfs.ubifs/sign.h b/ubifs-utils/mkfs.ubifs/sign.h deleted file mode 100644 index fe9fdd8..0000000 --- a/ubifs-utils/mkfs.ubifs/sign.h +++ /dev/null @@ -1,80 +0,0 @@ -/* - * Copyright (C) 2018 Pengutronix - * - * 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 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 - * - * Author: Sascha Hauer - */ - -#ifndef __UBIFS_SIGN_H__ -#define __UBIFS_SIGN_H__ - -#ifdef WITH_CRYPTO -#include <openssl/evp.h> - -void ubifs_node_calc_hash(const void *node, uint8_t *hash); -void mst_node_calc_hash(const void *node, uint8_t *hash); -void hash_digest_init(void); -void hash_digest_update(const void *buf, int len); -void hash_digest_final(void *hash, unsigned int *len); -int init_authentication(void); -int sign_superblock_node(void *node); -int authenticated(void); - -extern EVP_MD_CTX *hash_md; -extern const EVP_MD *md; - -#else -static inline void ubifs_node_calc_hash(__attribute__((unused)) const void *node, - __attribute__((unused)) uint8_t *hash) -{ -} - -static inline void mst_node_calc_hash(__attribute__((unused)) const void *node, - __attribute__((unused)) uint8_t *hash) -{ -} - -static inline void hash_digest_init(void) -{ -} - -static inline void hash_digest_update(__attribute__((unused)) const void *buf, - __attribute__((unused)) int len) -{ -} - -static inline void hash_digest_final(__attribute__((unused)) void *hash, - __attribute__((unused)) unsigned int *len) -{ -} - -static inline int init_authentication(void) -{ - return 0; -} - -static inline int sign_superblock_node(__attribute__((unused)) void *node) -{ - return 0; -} - -static inline int authenticated(void) -{ - return 0; -} - -#endif - -#endif /* __UBIFS_SIGN_H__ */ diff --git a/ubifs-utils/mkfs.ubifs/ubifs.h b/ubifs-utils/mkfs.ubifs/ubifs.h deleted file mode 100644 index 55937ce..0000000 --- a/ubifs-utils/mkfs.ubifs/ubifs.h +++ /dev/null @@ -1,471 +0,0 @@ -/* - * This file is part of UBIFS. - * - * Copyright (C) 2008 Nokia Corporation. - * Copyright (C) 2008 University of Szeged, Hungary - * - * 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 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 - * - * Authors: Artem Bityutskiy - * Adrian Hunter - * Zoltan Sogor - */ - -#ifndef __UBIFS_H__ -#define __UBIFS_H__ - -/* Maximum logical eraseblock size in bytes */ -#define UBIFS_MAX_LEB_SZ (2*1024*1024) - -/* Minimum amount of data UBIFS writes to the flash */ -#define MIN_WRITE_SZ (UBIFS_DATA_NODE_SZ + 8) - -/* Largest key size supported in this implementation */ -#define CUR_MAX_KEY_LEN UBIFS_SK_LEN - -/* - * There is no notion of truncation key because truncation nodes do not exist - * in TNC. However, when replaying, it is handy to introduce fake "truncation" - * keys for truncation nodes because the code becomes simpler. So we define - * %UBIFS_TRUN_KEY type. - */ -#define UBIFS_TRUN_KEY UBIFS_KEY_TYPES_CNT - -/* - * How much a directory entry/extended attribute entry adds to the parent/host - * inode. - */ -#define CALC_DENT_SIZE(name_len) ALIGN(UBIFS_DENT_NODE_SZ + (name_len) + 1, 8) - -/* How much an extended attribute adds to the host inode */ -#define CALC_XATTR_BYTES(data_len) ALIGN(UBIFS_INO_NODE_SZ + (data_len) + 1, 8) - -/* The below union makes it easier to deal with keys */ -union ubifs_key -{ - uint8_t u8[CUR_MAX_KEY_LEN]; - uint32_t u32[CUR_MAX_KEY_LEN/4]; - uint64_t u64[CUR_MAX_KEY_LEN/8]; - __le32 j32[CUR_MAX_KEY_LEN/4]; -}; - -/* - * LEB properties flags. - * - * LPROPS_UNCAT: not categorized - * LPROPS_DIRTY: dirty > 0, not index - * LPROPS_DIRTY_IDX: dirty + free > UBIFS_CH_SZ and index - * LPROPS_FREE: free > 0, not empty, not index - * LPROPS_HEAP_CNT: number of heaps used for storing categorized LEBs - * LPROPS_EMPTY: LEB is empty, not taken - * LPROPS_FREEABLE: free + dirty == leb_size, not index, not taken - * LPROPS_FRDI_IDX: free + dirty == leb_size and index, may be taken - * LPROPS_CAT_MASK: mask for the LEB categories above - * LPROPS_TAKEN: LEB was taken (this flag is not saved on the media) - * LPROPS_INDEX: LEB contains indexing nodes (this flag also exists on flash) - */ -enum { - LPROPS_UNCAT = 0, - LPROPS_DIRTY = 1, - LPROPS_DIRTY_IDX = 2, - LPROPS_FREE = 3, - LPROPS_HEAP_CNT = 3, - LPROPS_EMPTY = 4, - LPROPS_FREEABLE = 5, - LPROPS_FRDI_IDX = 6, - LPROPS_CAT_MASK = 15, - LPROPS_TAKEN = 16, - LPROPS_INDEX = 32, -}; - -/** - * struct ubifs_lprops - logical eraseblock properties. - * @free: amount of free space in bytes - * @dirty: amount of dirty space in bytes - * @flags: LEB properties flags (see above) - */ -struct ubifs_lprops -{ - int free; - int dirty; - int flags; -}; - -/** - * struct ubifs_lpt_lprops - LPT logical eraseblock properties. - * @free: amount of free space in bytes - * @dirty: amount of dirty space in bytes - */ -struct ubifs_lpt_lprops -{ - int free; - int dirty; -}; - -struct ubifs_nnode; - -/** - * struct ubifs_cnode - LEB Properties Tree common node. - * @parent: parent nnode - * @cnext: next cnode to commit - * @flags: flags (%DIRTY_LPT_NODE or %OBSOLETE_LPT_NODE) - * @iip: index in parent - * @level: level in the tree (zero for pnodes, greater than zero for nnodes) - * @num: node number - */ -struct ubifs_cnode -{ - struct ubifs_nnode *parent; - struct ubifs_cnode *cnext; - unsigned long flags; - int iip; - int level; - int num; -}; - -/** - * struct ubifs_pnode - LEB Properties Tree leaf node. - * @parent: parent nnode - * @cnext: next cnode to commit - * @flags: flags (%DIRTY_LPT_NODE or %OBSOLETE_LPT_NODE) - * @iip: index in parent - * @level: level in the tree (always zero for pnodes) - * @num: node number - * @lprops: LEB properties array - */ -struct ubifs_pnode -{ - struct ubifs_nnode *parent; - struct ubifs_cnode *cnext; - unsigned long flags; - int iip; - int level; - int num; - struct ubifs_lprops lprops[UBIFS_LPT_FANOUT]; -}; - -/** - * struct ubifs_nbranch - LEB Properties Tree internal node branch. - * @lnum: LEB number of child - * @offs: offset of child - * @nnode: nnode child - * @pnode: pnode child - * @cnode: cnode child - */ -struct ubifs_nbranch -{ - int lnum; - int offs; - union - { - struct ubifs_nnode *nnode; - struct ubifs_pnode *pnode; - struct ubifs_cnode *cnode; - }; -}; - -/** - * struct ubifs_nnode - LEB Properties Tree internal node. - * @parent: parent nnode - * @cnext: next cnode to commit - * @flags: flags (%DIRTY_LPT_NODE or %OBSOLETE_LPT_NODE) - * @iip: index in parent - * @level: level in the tree (always greater than zero for nnodes) - * @num: node number - * @nbranch: branches to child nodes - */ -struct ubifs_nnode -{ - struct ubifs_nnode *parent; - struct ubifs_cnode *cnext; - unsigned long flags; - int iip; - int level; - int num; - struct ubifs_nbranch nbranch[UBIFS_LPT_FANOUT]; -}; - -/** - * struct ubifs_lp_stats - statistics of eraseblocks in the main area. - * @empty_lebs: number of empty LEBs - * @taken_empty_lebs: number of taken LEBs - * @idx_lebs: number of indexing LEBs - * @total_free: total free space in bytes - * @total_dirty: total dirty space in bytes - * @total_used: total used space in bytes (includes only data LEBs) - * @total_dead: total dead space in bytes (includes only data LEBs) - * @total_dark: total dark space in bytes (includes only data LEBs) - */ -struct ubifs_lp_stats { - int empty_lebs; - int taken_empty_lebs; - int idx_lebs; - long long total_free; - long long total_dirty; - long long total_used; - long long total_dead; - long long total_dark; -}; - -/** - * struct ubifs_zbranch - key/coordinate/length branch stored in znodes. - * @key: key - * @znode: znode address in memory - * @lnum: LEB number of the indexing node - * @offs: offset of the indexing node within @lnum - * @len: target node length - */ -struct ubifs_zbranch -{ - union ubifs_key key; - struct ubifs_znode *znode; - int lnum; - int offs; - int len; -}; - -/** - * struct ubifs_znode - in-memory representation of an indexing node. - * @parent: parent znode or NULL if it is the root - * @cnext: next znode to commit - * @flags: flags - * @time: last access time (seconds) - * @level: level of the entry in the TNC tree - * @child_cnt: count of child znodes - * @iip: index in parent's zbranch array - * @alt: lower bound of key range has altered i.e. child inserted at slot 0 - * @zbranch: array of znode branches (@c->fanout elements) - */ -struct ubifs_znode -{ - struct ubifs_znode *parent; - struct ubifs_znode *cnext; - unsigned long flags; - unsigned long time; - int level; - int child_cnt; - int iip; - int alt; -#ifdef CONFIG_UBIFS_FS_DEBUG - int lnum, offs, len; -#endif - struct ubifs_zbranch zbranch[]; -}; - -/** - * struct ubifs_info - UBIFS file-system description data structure - * (per-superblock). - * - * @highest_inum: highest used inode number - * @max_sqnum: current global sequence number - * - * @jhead_cnt: count of journal heads - * @max_bud_bytes: maximum number of bytes allowed in buds - * - * @zroot: zbranch which points to the root index node and znode - * @ihead_lnum: LEB number of index head - * @ihead_offs: offset of index head - * - * @log_lebs: number of logical eraseblocks in the log - * @lpt_lebs: number of LEBs used for lprops table - * @lpt_first: first LEB of the lprops table area - * @lpt_last: last LEB of the lprops table area - * @main_lebs: count of LEBs in the main area - * @main_first: first LEB of the main area - * @default_compr: default compression type - * @favor_lzo: favor LZO compression method - * @favor_percent: lzo vs. zlib threshold used in case favor LZO - * - * @key_hash_type: type of the key hash - * @key_hash: direntry key hash function - * @key_fmt: key format - * @key_len: key length - * @fanout: fanout of the index tree (number of links per indexing node) - * - * @min_io_size: minimal input/output unit size - * @leb_size: logical eraseblock size in bytes - * @leb_cnt: count of logical eraseblocks - * @max_leb_cnt: maximum count of logical eraseblocks - * - * @old_idx_sz: size of index on flash - * @lst: lprops statistics - * - * @dead_wm: LEB dead space watermark - * @dark_wm: LEB dark space watermark - * - * @di: UBI device information - * @vi: UBI volume information - * - * @gc_lnum: LEB number used for garbage collection - * @rp_size: reserved pool size - * - * @space_bits: number of bits needed to record free or dirty space - * @lpt_lnum_bits: number of bits needed to record a LEB number in the LPT - * @lpt_offs_bits: number of bits needed to record an offset in the LPT - * @lpt_spc_bits: number of bits needed to space in the LPT - * @pcnt_bits: number of bits needed to record pnode or nnode number - * @lnum_bits: number of bits needed to record LEB number - * @nnode_sz: size of on-flash nnode - * @pnode_sz: size of on-flash pnode - * @ltab_sz: size of on-flash LPT lprops table - * @lsave_sz: size of on-flash LPT save table - * @pnode_cnt: number of pnodes - * @nnode_cnt: number of nnodes - * @lpt_hght: height of the LPT - * - * @lpt_lnum: LEB number of the root nnode of the LPT - * @lpt_offs: offset of the root nnode of the LPT - * @nhead_lnum: LEB number of LPT head - * @nhead_offs: offset of LPT head - * @big_lpt: flag that LPT is too big to write whole during commit - * @space_fixup: flag indicating that free space in LEBs needs to be cleaned up - * @double_hash: flag indicating that we can do lookups by hash - * @lpt_sz: LPT size - * - * @ltab_lnum: LEB number of LPT's own lprops table - * @ltab_offs: offset of LPT's own lprops table - * @lpt: lprops table - * @ltab: LPT's own lprops table - * @lsave_cnt: number of LEB numbers in LPT's save table - * @lsave_lnum: LEB number of LPT's save table - * @lsave_offs: offset of LPT's save table - * @lsave: LPT's save table - * @lscan_lnum: LEB number of last LPT scan - * - * @hash_algo_name: the name of the hashing algorithm to use - * @hash_algo: The hash algo number (from include/linux/hash_info.h) - * @auth_key_filename: authentication key file name - * @x509_filename: x509 certificate file name for authentication - * @hash_len: the length of the hash - * @root_idx_hash: The hash of the root index node - * @lpt_hash: The hash of the LPT - * @mst_hash: The hash of the master node - */ -struct ubifs_info -{ - ino_t highest_inum; - unsigned long long max_sqnum; - - int jhead_cnt; - long long max_bud_bytes; - - struct ubifs_zbranch zroot; - int ihead_lnum; - int ihead_offs; - - int log_lebs; - int lpt_lebs; - int lpt_first; - int lpt_last; - int orph_lebs; - int main_lebs; - int main_first; - int default_compr; - int favor_lzo; - int favor_percent; - - uint8_t key_hash_type; - uint32_t (*key_hash)(const char *str, int len); - int key_fmt; - int key_len; - int fanout; - - int min_io_size; - int leb_size; - int leb_cnt; - int max_leb_cnt; - - unsigned long long old_idx_sz; - struct ubifs_lp_stats lst; - - int dead_wm; - int dark_wm; - - struct ubi_dev_info di; - struct ubi_vol_info vi; - - int gc_lnum; - long long rp_size; - - int space_bits; - int lpt_lnum_bits; - int lpt_offs_bits; - int lpt_spc_bits; - int pcnt_bits; - int lnum_bits; - int nnode_sz; - int pnode_sz; - int ltab_sz; - int lsave_sz; - int pnode_cnt; - int nnode_cnt; - int lpt_hght; - - int lpt_lnum; - int lpt_offs; - int nhead_lnum; - int nhead_offs; - int big_lpt; - int space_fixup; - int double_hash; - int encrypted; - long long lpt_sz; - - int ltab_lnum; - int ltab_offs; - struct ubifs_lprops *lpt; - struct ubifs_lpt_lprops *ltab; - int lsave_cnt; - int lsave_lnum; - int lsave_offs; - int *lsave; - int lscan_lnum; - - char *hash_algo_name; - int hash_algo; - char *auth_key_filename; - char *auth_cert_filename; - int hash_len; - uint8_t root_idx_hash[UBIFS_MAX_HASH_LEN]; - uint8_t lpt_hash[UBIFS_MAX_HASH_LEN]; - uint8_t mst_hash[UBIFS_MAX_HASH_LEN]; -}; - -/** - * ubifs_idx_node_sz - return index node size. - * @c: the UBIFS file-system description object - * @child_cnt: number of children of this index node - */ -static inline int ubifs_idx_node_sz(const struct ubifs_info *c, int child_cnt) -{ - return UBIFS_IDX_NODE_SZ + (UBIFS_BRANCH_SZ + c->key_len + c->hash_len) - * child_cnt; -} - -/** - * ubifs_idx_branch - return pointer to an index branch. - * @c: the UBIFS file-system description object - * @idx: index node - * @bnum: branch number - */ -static inline -struct ubifs_branch *ubifs_idx_branch(const struct ubifs_info *c, - const struct ubifs_idx_node *idx, - int bnum) -{ - return (struct ubifs_branch *)((void *)idx->branches + - (UBIFS_BRANCH_SZ + c->key_len + c->hash_len) * bnum); -} - -#endif /* __UBIFS_H__ */ diff --git a/ubifs-utils/mount.ubifs b/ubifs-utils/mount.ubifs new file mode 100755 index 0000000..b94ddc5 --- /dev/null +++ b/ubifs-utils/mount.ubifs @@ -0,0 +1,101 @@ +#!/bin/sh + +# This script should be installed as /sbin/mount.ubifs. The benefit is that an +# fstab entry like: +# +# mtd=mtddev:home /home ubifs defaults 0 0 +# +# results in the ubi contained in the mtd named "mtddev" to be attached (if not +# already done) and then the volume named "home" being mounted to /home. + +# This is called by mount with the following options: +# /sbin/mount.ubifs spec dir [-sfnv] [-N namespace] [-o options] [-t type.subtype] + +spec="$1" +shift + +mtdname2num() { + local name + + name="$1" + + for d in $(find /sys/class/mtd/ -regex '.*/mtd[0-9]*'); do + case "$d" in + *ro) + continue + ;; + esac + + if test "$name" = "$(cat "$d/name")"; then + local dev mtdnum + + dev="$(basename "$d")" + mtdnum="${dev#mtd}" + echo "$mtdnum" + return + fi + done + + return 1 +} + +mtdnum2ubi() { + local mtdnum + + mtdnum="$1" + + for d in $(find /sys/class/ubi/ -regex '.*/ubi[0-9]*'); do + case "$d" in + *_[0-9]*) + continue + ;; + esac + + if test "$mtdnum" = "$(cat "$d/mtd_num")"; then + local ubi + + ubi="$(basename "$d")" + echo "$ubi" + return; + fi + done + + return 1 +} + +mtdnum2ubi_autoattach() { + local mtdnum ubi + + mtdnum="$1" + + ubi="$(mtdnum2ubi "$mtdnum")" && { echo "$ubi"; return; } + + # ubiattach might fail with "mtdX is already attached to ubiY" if there + # is more than one mount to do in the same mtd partition. So ignore errors. + ubiattach -m "$mtdnum" >&2 || true + + mtdnum2ubi "$mtdnum" +} + +case "$spec" in + mtd=*:*) + spec="${spec#mtd=}" + mtd="${spec%:*}" + rspec="${spec#*:}" + + mtdnum="$(mtdname2num "$mtd")" || { + echo "Failed to find mtdnum for mtd \"$mtd\"" + exit 1 + } + + ubi="$(mtdnum2ubi_autoattach "$mtdnum")" || { + echo "Failed to find ubi for mtd \"$mtd\"" + exit 1 + } + + spec="$ubi:$rspec" + + ;; +esac + +/bin/mount -i -t ubifs "$spec" "$@" |