From efc492d327ea6a9658674eb9e971aff3742818cd Mon Sep 17 00:00:00 2001 From: Manuel Bentele Date: Tue, 8 Sep 2020 15:07:31 +0200 Subject: Added patched losetup utility to configure xloop devices --- .gitignore | 1 + CMakeLists.txt | 8 + Kbuild.in | 9 - Kconfig | 93 -- Makefile | 12 - kernel/CMakeLists.txt | 4 + kernel/Kbuild.in | 9 + kernel/Kconfig | 93 ++ kernel/Makefile | 12 + kernel/loop_file_fmt.c | 347 +++++ kernel/loop_file_fmt.h | 380 ++++++ kernel/loop_file_fmt_qcow_cache.c | 218 ++++ kernel/loop_file_fmt_qcow_cache.h | 51 + kernel/loop_file_fmt_qcow_cluster.c | 270 ++++ kernel/loop_file_fmt_qcow_cluster.h | 23 + kernel/loop_file_fmt_qcow_main.c | 953 ++++++++++++++ kernel/loop_file_fmt_qcow_main.h | 419 +++++++ kernel/loop_file_fmt_raw.c | 465 +++++++ kernel/loop_main.c | 2221 +++++++++++++++++++++++++++++++++ kernel/loop_main.h | 105 ++ kernel/uapi/linux/loop.h | 125 ++ loop_file_fmt.c | 347 ----- loop_file_fmt.h | 380 ------ loop_file_fmt_qcow_cache.c | 218 ---- loop_file_fmt_qcow_cache.h | 51 - loop_file_fmt_qcow_cluster.c | 270 ---- loop_file_fmt_qcow_cluster.h | 23 - loop_file_fmt_qcow_main.c | 953 -------------- loop_file_fmt_qcow_main.h | 419 ------- loop_file_fmt_raw.c | 465 ------- loop_main.c | 2221 --------------------------------- loop_main.h | 105 -- uapi/linux/loop.h | 125 -- utils/CMakeLists.txt | 15 + utils/bash-completion/losetup | 85 ++ utils/config.h | 897 +++++++++++++ utils/include/all-io.h | 81 ++ utils/include/bitops.h | 150 +++ utils/include/blkdev.h | 151 +++ utils/include/c.h | 427 +++++++ utils/include/canonicalize.h | 32 + utils/include/caputils.h | 34 + utils/include/carefulputc.h | 155 +++ utils/include/cctype.h | 325 +++++ utils/include/closestream.h | 110 ++ utils/include/color-names.h | 44 + utils/include/colors.h | 73 ++ utils/include/cpuset.h | 99 ++ utils/include/crc32.h | 12 + utils/include/crc32c.h | 9 + utils/include/debug.h | 181 +++ utils/include/debugobj.h | 22 + utils/include/encode.h | 14 + utils/include/env.h | 35 + utils/include/exec_shell.h | 6 + utils/include/exitcodes.h | 25 + utils/include/fileutils.h | 77 ++ utils/include/fuzz.h | 9 + utils/include/idcache.h | 28 + utils/include/ismounted.h | 14 + utils/include/iso9660.h | 58 + utils/include/linux_version.h | 14 + utils/include/list.h | 361 ++++++ utils/include/loopdev.h | 222 ++++ utils/include/mangle.h | 28 + utils/include/match.h | 12 + utils/include/mbsalign.h | 66 + utils/include/mbsedit.h | 32 + utils/include/md5.h | 24 + utils/include/minix.h | 85 ++ utils/include/monotonic.h | 22 + utils/include/namespace.h | 56 + utils/include/nls.h | 153 +++ utils/include/optutils.h | 107 ++ utils/include/pager.h | 9 + utils/include/partx.h | 63 + utils/include/path.h | 135 ++ utils/include/pathnames.h | 210 ++++ utils/include/pidfd-utils.h | 28 + utils/include/plymouth-ctrl.h | 65 + utils/include/procutils.h | 34 + utils/include/pt-bsd.h | 156 +++ utils/include/pt-gpt-partnames.h | 160 +++ utils/include/pt-mbr-partnames.h | 112 ++ utils/include/pt-mbr.h | 187 +++ utils/include/pt-sgi.h | 115 ++ utils/include/pt-sun.h | 90 ++ utils/include/pty-session.h | 110 ++ utils/include/pwdutils.h | 14 + utils/include/randutils.h | 17 + utils/include/rpmatch.h | 13 + utils/include/setproctitle.h | 7 + utils/include/sha1.h | 27 + utils/include/signames.h | 8 + utils/include/statfs_magic.h | 100 ++ utils/include/strutils.h | 333 +++++ utils/include/strv.h | 55 + utils/include/swapheader.h | 23 + utils/include/swapprober.h | 9 + utils/include/sysfs.h | 113 ++ utils/include/timer.h | 22 + utils/include/timeutils.h | 92 ++ utils/include/ttyutils.h | 205 +++ utils/include/widechar.h | 47 + utils/include/xalloc.h | 139 +++ utils/lib/CMakeLists.txt | 45 + utils/lib/blkdev.c | 452 +++++++ utils/lib/canonicalize.c | 250 ++++ utils/lib/caputils.c | 45 + utils/lib/color-names.c | 64 + utils/lib/colors.c | 907 ++++++++++++++ utils/lib/cpuset.c | 413 ++++++ utils/lib/crc32.c | 142 +++ utils/lib/crc32c.c | 102 ++ utils/lib/encode.c | 79 ++ utils/lib/env.c | 238 ++++ utils/lib/exec_shell.c | 51 + utils/lib/fileutils.c | 246 ++++ utils/lib/idcache.c | 117 ++ utils/lib/ismounted.c | 396 ++++++ utils/lib/langinfo.c | 124 ++ utils/lib/linux_version.c | 71 ++ utils/lib/loopdev.c | 1914 ++++++++++++++++++++++++++++ utils/lib/mangle.c | 169 +++ utils/lib/match.c | 53 + utils/lib/mbsalign.c | 627 ++++++++++ utils/lib/mbsedit.c | 225 ++++ utils/lib/md5.c | 257 ++++ utils/lib/monotonic.c | 81 ++ utils/lib/pager.c | 317 +++++ utils/lib/path.c | 1248 ++++++++++++++++++ utils/lib/plymouth-ctrl.c | 144 +++ utils/lib/procutils.c | 308 +++++ utils/lib/pty-session.c | 725 +++++++++++ utils/lib/pwdutils.c | 156 +++ utils/lib/randutils.c | 238 ++++ utils/lib/setproctitle.c | 75 ++ utils/lib/sha1.c | 256 ++++ utils/lib/signames.c | 204 +++ utils/lib/strutils.c | 1135 +++++++++++++++++ utils/lib/strv.c | 403 ++++++ utils/lib/sysfs.c | 1127 +++++++++++++++++ utils/lib/timer.c | 95 ++ utils/lib/timeutils.c | 611 +++++++++ utils/lib/ttyutils.c | 152 +++ utils/libsmartcols/CMakeLists.txt | 22 + utils/libsmartcols/src/buffer.c | 152 +++ utils/libsmartcols/src/calculate.c | 454 +++++++ utils/libsmartcols/src/cell.c | 257 ++++ utils/libsmartcols/src/column.c | 564 +++++++++ utils/libsmartcols/src/fput.c | 97 ++ utils/libsmartcols/src/grouping.c | 575 +++++++++ utils/libsmartcols/src/init.c | 62 + utils/libsmartcols/src/iter.c | 74 ++ utils/libsmartcols/src/libsmartcols.h | 336 +++++ utils/libsmartcols/src/line.c | 540 ++++++++ utils/libsmartcols/src/print-api.c | 211 ++++ utils/libsmartcols/src/print.c | 1089 ++++++++++++++++ utils/libsmartcols/src/smartcolsP.h | 468 +++++++ utils/libsmartcols/src/symbols.c | 293 +++++ utils/libsmartcols/src/table.c | 1691 +++++++++++++++++++++++++ utils/libsmartcols/src/version.c | 62 + utils/libsmartcols/src/walk.c | 152 +++ utils/sys-utils/CMakeLists.txt | 9 + utils/sys-utils/losetup.8 | 215 ++++ utils/sys-utils/losetup.c | 955 ++++++++++++++ 166 files changed, 35292 insertions(+), 5691 deletions(-) create mode 100644 CMakeLists.txt delete mode 100644 Kbuild.in delete mode 100644 Kconfig delete mode 100644 Makefile create mode 100644 kernel/CMakeLists.txt create mode 100644 kernel/Kbuild.in create mode 100644 kernel/Kconfig create mode 100644 kernel/Makefile create mode 100644 kernel/loop_file_fmt.c create mode 100644 kernel/loop_file_fmt.h create mode 100644 kernel/loop_file_fmt_qcow_cache.c create mode 100644 kernel/loop_file_fmt_qcow_cache.h create mode 100644 kernel/loop_file_fmt_qcow_cluster.c create mode 100644 kernel/loop_file_fmt_qcow_cluster.h create mode 100644 kernel/loop_file_fmt_qcow_main.c create mode 100644 kernel/loop_file_fmt_qcow_main.h create mode 100644 kernel/loop_file_fmt_raw.c create mode 100644 kernel/loop_main.c create mode 100644 kernel/loop_main.h create mode 100644 kernel/uapi/linux/loop.h delete mode 100644 loop_file_fmt.c delete mode 100644 loop_file_fmt.h delete mode 100644 loop_file_fmt_qcow_cache.c delete mode 100644 loop_file_fmt_qcow_cache.h delete mode 100644 loop_file_fmt_qcow_cluster.c delete mode 100644 loop_file_fmt_qcow_cluster.h delete mode 100644 loop_file_fmt_qcow_main.c delete mode 100644 loop_file_fmt_qcow_main.h delete mode 100644 loop_file_fmt_raw.c delete mode 100644 loop_main.c delete mode 100644 loop_main.h delete mode 100644 uapi/linux/loop.h create mode 100644 utils/CMakeLists.txt create mode 100644 utils/bash-completion/losetup create mode 100644 utils/config.h create mode 100644 utils/include/all-io.h create mode 100644 utils/include/bitops.h create mode 100644 utils/include/blkdev.h create mode 100644 utils/include/c.h create mode 100644 utils/include/canonicalize.h create mode 100644 utils/include/caputils.h create mode 100644 utils/include/carefulputc.h create mode 100644 utils/include/cctype.h create mode 100644 utils/include/closestream.h create mode 100644 utils/include/color-names.h create mode 100644 utils/include/colors.h create mode 100644 utils/include/cpuset.h create mode 100644 utils/include/crc32.h create mode 100644 utils/include/crc32c.h create mode 100644 utils/include/debug.h create mode 100644 utils/include/debugobj.h create mode 100644 utils/include/encode.h create mode 100644 utils/include/env.h create mode 100644 utils/include/exec_shell.h create mode 100644 utils/include/exitcodes.h create mode 100644 utils/include/fileutils.h create mode 100644 utils/include/fuzz.h create mode 100644 utils/include/idcache.h create mode 100644 utils/include/ismounted.h create mode 100644 utils/include/iso9660.h create mode 100644 utils/include/linux_version.h create mode 100644 utils/include/list.h create mode 100644 utils/include/loopdev.h create mode 100644 utils/include/mangle.h create mode 100644 utils/include/match.h create mode 100644 utils/include/mbsalign.h create mode 100644 utils/include/mbsedit.h create mode 100644 utils/include/md5.h create mode 100644 utils/include/minix.h create mode 100644 utils/include/monotonic.h create mode 100644 utils/include/namespace.h create mode 100644 utils/include/nls.h create mode 100644 utils/include/optutils.h create mode 100644 utils/include/pager.h create mode 100644 utils/include/partx.h create mode 100644 utils/include/path.h create mode 100644 utils/include/pathnames.h create mode 100644 utils/include/pidfd-utils.h create mode 100644 utils/include/plymouth-ctrl.h create mode 100644 utils/include/procutils.h create mode 100644 utils/include/pt-bsd.h create mode 100644 utils/include/pt-gpt-partnames.h create mode 100644 utils/include/pt-mbr-partnames.h create mode 100644 utils/include/pt-mbr.h create mode 100644 utils/include/pt-sgi.h create mode 100644 utils/include/pt-sun.h create mode 100644 utils/include/pty-session.h create mode 100644 utils/include/pwdutils.h create mode 100644 utils/include/randutils.h create mode 100644 utils/include/rpmatch.h create mode 100644 utils/include/setproctitle.h create mode 100644 utils/include/sha1.h create mode 100644 utils/include/signames.h create mode 100644 utils/include/statfs_magic.h create mode 100644 utils/include/strutils.h create mode 100644 utils/include/strv.h create mode 100644 utils/include/swapheader.h create mode 100644 utils/include/swapprober.h create mode 100644 utils/include/sysfs.h create mode 100644 utils/include/timer.h create mode 100644 utils/include/timeutils.h create mode 100644 utils/include/ttyutils.h create mode 100644 utils/include/widechar.h create mode 100644 utils/include/xalloc.h create mode 100644 utils/lib/CMakeLists.txt create mode 100644 utils/lib/blkdev.c create mode 100644 utils/lib/canonicalize.c create mode 100644 utils/lib/caputils.c create mode 100644 utils/lib/color-names.c create mode 100644 utils/lib/colors.c create mode 100644 utils/lib/cpuset.c create mode 100644 utils/lib/crc32.c create mode 100644 utils/lib/crc32c.c create mode 100644 utils/lib/encode.c create mode 100644 utils/lib/env.c create mode 100644 utils/lib/exec_shell.c create mode 100644 utils/lib/fileutils.c create mode 100644 utils/lib/idcache.c create mode 100644 utils/lib/ismounted.c create mode 100644 utils/lib/langinfo.c create mode 100644 utils/lib/linux_version.c create mode 100644 utils/lib/loopdev.c create mode 100644 utils/lib/mangle.c create mode 100644 utils/lib/match.c create mode 100644 utils/lib/mbsalign.c create mode 100644 utils/lib/mbsedit.c create mode 100644 utils/lib/md5.c create mode 100644 utils/lib/monotonic.c create mode 100644 utils/lib/pager.c create mode 100644 utils/lib/path.c create mode 100644 utils/lib/plymouth-ctrl.c create mode 100644 utils/lib/procutils.c create mode 100644 utils/lib/pty-session.c create mode 100644 utils/lib/pwdutils.c create mode 100644 utils/lib/randutils.c create mode 100644 utils/lib/setproctitle.c create mode 100644 utils/lib/sha1.c create mode 100644 utils/lib/signames.c create mode 100644 utils/lib/strutils.c create mode 100644 utils/lib/strv.c create mode 100644 utils/lib/sysfs.c create mode 100644 utils/lib/timer.c create mode 100644 utils/lib/timeutils.c create mode 100644 utils/lib/ttyutils.c create mode 100644 utils/libsmartcols/CMakeLists.txt create mode 100644 utils/libsmartcols/src/buffer.c create mode 100644 utils/libsmartcols/src/calculate.c create mode 100644 utils/libsmartcols/src/cell.c create mode 100644 utils/libsmartcols/src/column.c create mode 100644 utils/libsmartcols/src/fput.c create mode 100644 utils/libsmartcols/src/grouping.c create mode 100644 utils/libsmartcols/src/init.c create mode 100644 utils/libsmartcols/src/iter.c create mode 100644 utils/libsmartcols/src/libsmartcols.h create mode 100644 utils/libsmartcols/src/line.c create mode 100644 utils/libsmartcols/src/print-api.c create mode 100644 utils/libsmartcols/src/print.c create mode 100644 utils/libsmartcols/src/smartcolsP.h create mode 100644 utils/libsmartcols/src/symbols.c create mode 100644 utils/libsmartcols/src/table.c create mode 100644 utils/libsmartcols/src/version.c create mode 100644 utils/libsmartcols/src/walk.c create mode 100644 utils/sys-utils/CMakeLists.txt create mode 100644 utils/sys-utils/losetup.8 create mode 100644 utils/sys-utils/losetup.c diff --git a/.gitignore b/.gitignore index b2d1740..4dafafe 100644 --- a/.gitignore +++ b/.gitignore @@ -6,3 +6,4 @@ modules.order *.o.cmd *.mod* *.ko.cmd +build diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..b136eca --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,8 @@ +cmake_minimum_required(VERSION 3.10) + +# set the project name +project(xloop) + +# add subprojects +add_subdirectory(kernel) +add_subdirectory(utils) diff --git a/Kbuild.in b/Kbuild.in deleted file mode 100644 index b61f7a0..0000000 --- a/Kbuild.in +++ /dev/null @@ -1,9 +0,0 @@ -# SPDX-License-Identifier: GPL-2.0 - -obj-$(CONFIG_BLK_DEV_XLOOP) += xloop.o -xloop-objs += loop_main.o loop_file_fmt.o - -obj-$(CONFIG_BLK_DEV_XLOOP_FILE_FMT_RAW) += loop_file_fmt_raw.o - -loop_file_fmt_qcow-y += loop_file_fmt_qcow_main.o loop_file_fmt_qcow_cluster.o loop_file_fmt_qcow_cache.o -obj-$(CONFIG_BLK_DEV_XLOOP_FILE_FMT_QCOW) += loop_file_fmt_qcow.o diff --git a/Kconfig b/Kconfig deleted file mode 100644 index 2fe8cb5..0000000 --- a/Kconfig +++ /dev/null @@ -1,93 +0,0 @@ -# SPDX-License-Identifier: GPL-2.0 -# -# Loop device driver configuration -# - -config BLK_DEV_XLOOP - tristate "Loopback device support" - ---help--- - Saying Y here will allow you to use a regular file as a block - device; you can then create a file system on that block device and - mount it just as you would mount other block devices such as hard - drive partitions, CD-ROM drives or floppy drives. The loop devices - are block special device files with major number 7 and typically - called /dev/loop0, /dev/loop1 etc. - - This is useful if you want to check an ISO 9660 file system before - burning the CD, or if you want to use floppy images without first - writing them to floppy. Furthermore, some Linux distributions avoid - the need for a dedicated Linux partition by keeping their complete - root file system inside a DOS FAT file using this loop device - driver. - - To use the loop device, you need the losetup utility, found in the - util-linux package, see - . - - The loop device driver can also be used to "hide" a file system in - a disk partition, floppy, or regular file, either using encryption - (scrambling the data) or steganography (hiding the data in the low - bits of, say, a sound file). This is also safe if the file resides - on a remote file server. - - There are several ways of encrypting disks. Some of these require - kernel patches. The vanilla kernel offers the cryptoloop option - and a Device Mapper target (which is superior, as it supports all - file systems). If you want to use the cryptoloop, say Y to both - LOOP and CRYPTOLOOP, and make sure you have a recent (version 2.12 - or later) version of util-linux. Additionally, be aware that - the cryptoloop is not safe for storing journaled filesystems. - - Note that this loop device has nothing to do with the loopback - device used for network connections from the machine to itself. - - To compile this driver as a module, choose M here: the - module will be called loop. - - Most users will answer N here. - -config BLK_DEV_XLOOP_MIN_COUNT - int "Number of loop devices to pre-create at init time" - depends on BLK_DEV_XLOOP - default 8 - help - Static number of loop devices to be unconditionally pre-created - at init time. - - This default value can be overwritten on the kernel command - line or with module-parameter loop.max_loop. - - The historic default is 8. If a late 2011 version of losetup(8) - is used, it can be set to 0, since needed loop devices can be - dynamically allocated with the /dev/loop-control interface. - -config BLK_DEV_CRYPTOLOOP - tristate "Cryptoloop Support" - select CRYPTO - select CRYPTO_CBC - depends on BLK_DEV_XLOOP - ---help--- - Say Y here if you want to be able to use the ciphers that are - provided by the CryptoAPI as loop transformation. This might be - used as hard disk encryption. - - WARNING: This device is not safe for journaled file systems like - ext3 or Reiserfs. Please use the Device Mapper crypto module - instead, which can be configured to be on-disk compatible with the - cryptoloop device. - -config BLK_DEV_XLOOP_FILE_FMT_RAW - tristate "Loop device binary file format support" - depends on BLK_DEV_XLOOP - ---help--- - Say Y or M here if you want to enable the binary (RAW) file format - support of the loop device module. - -config BLK_DEV_XLOOP_FILE_FMT_QCOW - tristate "Loop device QCOW file format support" - depends on BLK_DEV_XLOOP - select ZLIB_INFLATE - select ZLIB_DEFLATE - ---help--- - Say Y or M here if you want to enable the QEMU's copy on write (QCOW) - file format support of the loop device module. diff --git a/Makefile b/Makefile deleted file mode 100644 index b7f2191..0000000 --- a/Makefile +++ /dev/null @@ -1,12 +0,0 @@ -# SPDX-License-Identifier: GPL-2.0 -include $(PWD)/Kbuild.in - -ifndef KDIR - KDIR = /lib/modules/$(shell uname -r)/build -endif - -all: - make -C "$(KDIR)" "M=$(PWD)" modules - -clean: - make -C "$(KDIR)" "M=$(PWD)" clean diff --git a/kernel/CMakeLists.txt b/kernel/CMakeLists.txt new file mode 100644 index 0000000..3c91af4 --- /dev/null +++ b/kernel/CMakeLists.txt @@ -0,0 +1,4 @@ +cmake_minimum_required(VERSION 3.10) + +# set the project name +project(xloop-kernel) diff --git a/kernel/Kbuild.in b/kernel/Kbuild.in new file mode 100644 index 0000000..b61f7a0 --- /dev/null +++ b/kernel/Kbuild.in @@ -0,0 +1,9 @@ +# SPDX-License-Identifier: GPL-2.0 + +obj-$(CONFIG_BLK_DEV_XLOOP) += xloop.o +xloop-objs += loop_main.o loop_file_fmt.o + +obj-$(CONFIG_BLK_DEV_XLOOP_FILE_FMT_RAW) += loop_file_fmt_raw.o + +loop_file_fmt_qcow-y += loop_file_fmt_qcow_main.o loop_file_fmt_qcow_cluster.o loop_file_fmt_qcow_cache.o +obj-$(CONFIG_BLK_DEV_XLOOP_FILE_FMT_QCOW) += loop_file_fmt_qcow.o diff --git a/kernel/Kconfig b/kernel/Kconfig new file mode 100644 index 0000000..2fe8cb5 --- /dev/null +++ b/kernel/Kconfig @@ -0,0 +1,93 @@ +# SPDX-License-Identifier: GPL-2.0 +# +# Loop device driver configuration +# + +config BLK_DEV_XLOOP + tristate "Loopback device support" + ---help--- + Saying Y here will allow you to use a regular file as a block + device; you can then create a file system on that block device and + mount it just as you would mount other block devices such as hard + drive partitions, CD-ROM drives or floppy drives. The loop devices + are block special device files with major number 7 and typically + called /dev/loop0, /dev/loop1 etc. + + This is useful if you want to check an ISO 9660 file system before + burning the CD, or if you want to use floppy images without first + writing them to floppy. Furthermore, some Linux distributions avoid + the need for a dedicated Linux partition by keeping their complete + root file system inside a DOS FAT file using this loop device + driver. + + To use the loop device, you need the losetup utility, found in the + util-linux package, see + . + + The loop device driver can also be used to "hide" a file system in + a disk partition, floppy, or regular file, either using encryption + (scrambling the data) or steganography (hiding the data in the low + bits of, say, a sound file). This is also safe if the file resides + on a remote file server. + + There are several ways of encrypting disks. Some of these require + kernel patches. The vanilla kernel offers the cryptoloop option + and a Device Mapper target (which is superior, as it supports all + file systems). If you want to use the cryptoloop, say Y to both + LOOP and CRYPTOLOOP, and make sure you have a recent (version 2.12 + or later) version of util-linux. Additionally, be aware that + the cryptoloop is not safe for storing journaled filesystems. + + Note that this loop device has nothing to do with the loopback + device used for network connections from the machine to itself. + + To compile this driver as a module, choose M here: the + module will be called loop. + + Most users will answer N here. + +config BLK_DEV_XLOOP_MIN_COUNT + int "Number of loop devices to pre-create at init time" + depends on BLK_DEV_XLOOP + default 8 + help + Static number of loop devices to be unconditionally pre-created + at init time. + + This default value can be overwritten on the kernel command + line or with module-parameter loop.max_loop. + + The historic default is 8. If a late 2011 version of losetup(8) + is used, it can be set to 0, since needed loop devices can be + dynamically allocated with the /dev/loop-control interface. + +config BLK_DEV_CRYPTOLOOP + tristate "Cryptoloop Support" + select CRYPTO + select CRYPTO_CBC + depends on BLK_DEV_XLOOP + ---help--- + Say Y here if you want to be able to use the ciphers that are + provided by the CryptoAPI as loop transformation. This might be + used as hard disk encryption. + + WARNING: This device is not safe for journaled file systems like + ext3 or Reiserfs. Please use the Device Mapper crypto module + instead, which can be configured to be on-disk compatible with the + cryptoloop device. + +config BLK_DEV_XLOOP_FILE_FMT_RAW + tristate "Loop device binary file format support" + depends on BLK_DEV_XLOOP + ---help--- + Say Y or M here if you want to enable the binary (RAW) file format + support of the loop device module. + +config BLK_DEV_XLOOP_FILE_FMT_QCOW + tristate "Loop device QCOW file format support" + depends on BLK_DEV_XLOOP + select ZLIB_INFLATE + select ZLIB_DEFLATE + ---help--- + Say Y or M here if you want to enable the QEMU's copy on write (QCOW) + file format support of the loop device module. diff --git a/kernel/Makefile b/kernel/Makefile new file mode 100644 index 0000000..b7f2191 --- /dev/null +++ b/kernel/Makefile @@ -0,0 +1,12 @@ +# SPDX-License-Identifier: GPL-2.0 +include $(PWD)/Kbuild.in + +ifndef KDIR + KDIR = /lib/modules/$(shell uname -r)/build +endif + +all: + make -C "$(KDIR)" "M=$(PWD)" modules + +clean: + make -C "$(KDIR)" "M=$(PWD)" clean diff --git a/kernel/loop_file_fmt.c b/kernel/loop_file_fmt.c new file mode 100644 index 0000000..062ea0d --- /dev/null +++ b/kernel/loop_file_fmt.c @@ -0,0 +1,347 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * loop_file_fmt.c + * + * File format subsystem for the xloop device module. + * + * Copyright (C) 2019 Manuel Bentele + */ + +#include +#include + +#include "loop_file_fmt.h" + +/* storage for all registered file format drivers */ +static struct xloop_file_fmt_driver *xloop_file_fmt_drivers[MAX_XLO_FILE_FMT] = { + NULL +}; + +int xloop_file_fmt_register_driver(struct xloop_file_fmt_driver *drv) +{ + int ret = 0; + + if (drv == NULL) + return -EFAULT; + + if (drv->file_fmt_type > MAX_XLO_FILE_FMT) + return -EINVAL; + + if (xloop_file_fmt_drivers[drv->file_fmt_type] == NULL) { + xloop_file_fmt_drivers[drv->file_fmt_type] = drv; + printk(KERN_INFO "xloop_file_fmt: successfully registered file " + "format driver %s", drv->name); + } else { + printk(KERN_WARNING "xloop_file_fmt: driver for file format " + "already registered"); + ret = -EBUSY; + } + + return ret; +} +EXPORT_SYMBOL(xloop_file_fmt_register_driver); + +void xloop_file_fmt_unregister_driver(struct xloop_file_fmt_driver *drv) +{ + if (drv == NULL) + return; + + if (drv->file_fmt_type > MAX_XLO_FILE_FMT) + return; + + xloop_file_fmt_drivers[drv->file_fmt_type] = NULL; + printk(KERN_INFO "xloop_file_fmt: successfully unregistered file " + "format driver %s", drv->name); +} +EXPORT_SYMBOL(xloop_file_fmt_unregister_driver); + +struct xloop_file_fmt *xloop_file_fmt_alloc(void) +{ + return kzalloc(sizeof(struct xloop_file_fmt), GFP_KERNEL); +} + +void xloop_file_fmt_free(struct xloop_file_fmt *xlo_fmt) +{ + kfree(xlo_fmt); +} + +int xloop_file_fmt_set_xlo(struct xloop_file_fmt *xlo_fmt, struct xloop_device *xlo) +{ + if (xlo_fmt == NULL) + return -EINVAL; + + xlo_fmt->xlo = xlo; + + return 0; +} +EXPORT_SYMBOL(xloop_file_fmt_set_xlo); + +struct xloop_device *xloop_file_fmt_get_xlo(struct xloop_file_fmt *xlo_fmt) +{ + return xlo_fmt->xlo; +} +EXPORT_SYMBOL(xloop_file_fmt_get_xlo); + +int xloop_file_fmt_init(struct xloop_file_fmt *xlo_fmt, + u32 file_fmt_type) +{ + struct xloop_file_fmt_ops *ops; + struct module *drv; + int ret = 0; + + if (file_fmt_type > MAX_XLO_FILE_FMT) + return -EINVAL; + + xlo_fmt->file_fmt_type = file_fmt_type; + + if (xlo_fmt->file_fmt_state != file_fmt_uninitialized) { + printk(KERN_WARNING "xloop_file_fmt: file format is " + "initialized already"); + return -EINVAL; + } + + /* check if new file format driver is registered */ + if (xloop_file_fmt_drivers[xlo_fmt->file_fmt_type] == NULL) { + printk(KERN_ERR "xloop_file_fmt: file format driver is not " + "available"); + return -ENODEV; + } + + printk(KERN_INFO "xloop_file_fmt: use file format driver %s", + xloop_file_fmt_drivers[xlo_fmt->file_fmt_type]->name); + + drv = xloop_file_fmt_drivers[xlo_fmt->file_fmt_type]->owner; + if (!try_module_get(drv)) { + printk(KERN_ERR "xloop_file_fmt: file format driver %s can not " + "be accessed", + xloop_file_fmt_drivers[xlo_fmt->file_fmt_type]->name); + return -ENODEV; + } + + ops = xloop_file_fmt_drivers[xlo_fmt->file_fmt_type]->ops; + if (likely(ops->init)) { + ret = ops->init(xlo_fmt); + if (ret < 0) + goto free_drv; + } + + /* after increasing the refcount of file format driver module and + * the successful initialization, the file format is initialized */ + xlo_fmt->file_fmt_state = file_fmt_initialized; + + return ret; + +free_drv: + module_put(drv); + xlo_fmt->file_fmt_state = file_fmt_uninitialized; + return ret; +} + +void xloop_file_fmt_exit(struct xloop_file_fmt *xlo_fmt) +{ + struct xloop_file_fmt_ops *ops; + struct module *drv; + + if (xlo_fmt->file_fmt_state != file_fmt_initialized) { + printk(KERN_WARNING "xloop_file_fmt: file format is " + "uninitialized already"); + return; + } + + ops = xloop_file_fmt_drivers[xlo_fmt->file_fmt_type]->ops; + if (likely(ops->exit)) + ops->exit(xlo_fmt); + + drv = xloop_file_fmt_drivers[xlo_fmt->file_fmt_type]->owner; + module_put(drv); + + /* after decreasing the refcount of file format driver module, + * the file format is uninitialized */ + xlo_fmt->file_fmt_state = file_fmt_uninitialized; +} + +int xloop_file_fmt_read(struct xloop_file_fmt *xlo_fmt, + struct request *rq) +{ + struct xloop_file_fmt_ops *ops; + + if (unlikely(xlo_fmt->file_fmt_state != file_fmt_initialized)) { + printk(KERN_ERR "xloop_file_fmt: file format is " + "not initialized, can not read"); + return -EINVAL; + } + + ops = xloop_file_fmt_drivers[xlo_fmt->file_fmt_type]->ops; + if (likely(ops->read)) + return ops->read(xlo_fmt, rq); + else + return -EIO; +} + +int xloop_file_fmt_read_aio(struct xloop_file_fmt *xlo_fmt, + struct request *rq) +{ + struct xloop_file_fmt_ops *ops; + + if (unlikely(xlo_fmt->file_fmt_state != file_fmt_initialized)) { + printk(KERN_ERR "xloop_file_fmt: file format is " + "not initialized, can not read aio"); + return -EINVAL; + } + + ops = xloop_file_fmt_drivers[xlo_fmt->file_fmt_type]->ops; + if (likely(ops->read_aio)) + return ops->read_aio(xlo_fmt, rq); + else + return -EIO; +} + +int xloop_file_fmt_write(struct xloop_file_fmt *xlo_fmt, + struct request *rq) +{ + struct xloop_file_fmt_ops *ops; + + if (unlikely(xlo_fmt->file_fmt_state != file_fmt_initialized)) { + printk(KERN_ERR "xloop_file_fmt: file format is " + "not initialized, can not write"); + return -EINVAL; + } + + ops = xloop_file_fmt_drivers[xlo_fmt->file_fmt_type]->ops; + if (likely(ops->write)) + return ops->write(xlo_fmt, rq); + else + return -EIO; +} + +int xloop_file_fmt_write_aio(struct xloop_file_fmt *xlo_fmt, + struct request *rq) +{ + struct xloop_file_fmt_ops *ops; + + if (unlikely(xlo_fmt->file_fmt_state != file_fmt_initialized)) { + printk(KERN_ERR "xloop_file_fmt: file format is " + "not initialized, can not write aio"); + return -EINVAL; + } + + ops = xloop_file_fmt_drivers[xlo_fmt->file_fmt_type]->ops; + if (likely(ops->write_aio)) + return ops->write_aio(xlo_fmt, rq); + else + return -EIO; +} + +int xloop_file_fmt_write_zeros(struct xloop_file_fmt *xlo_fmt, + struct request *rq) +{ + struct xloop_file_fmt_ops *ops; + + if (unlikely(xlo_fmt->file_fmt_state != file_fmt_initialized)) { + printk(KERN_ERR "xloop_file_fmt: file format is " + "not initialized, can not write zeros"); + return -EINVAL; + } + + ops = xloop_file_fmt_drivers[xlo_fmt->file_fmt_type]->ops; + if (likely(ops->write_zeros)) + return ops->write_zeros(xlo_fmt, rq); + else + return -EIO; +} + +int xloop_file_fmt_discard(struct xloop_file_fmt *xlo_fmt, + struct request *rq) +{ + struct xloop_file_fmt_ops *ops; + + if (unlikely(xlo_fmt->file_fmt_state != file_fmt_initialized)) { + printk(KERN_ERR "xloop_file_fmt: file format is " + "not initialized, can not discard"); + return -EINVAL; + } + + ops = xloop_file_fmt_drivers[xlo_fmt->file_fmt_type]->ops; + if (likely(ops->discard)) + return ops->discard(xlo_fmt, rq); + else + return -EIO; +} + +int xloop_file_fmt_flush(struct xloop_file_fmt *xlo_fmt) +{ + struct xloop_file_fmt_ops *ops; + + if (unlikely(xlo_fmt->file_fmt_state != file_fmt_initialized)) { + printk(KERN_ERR "xloop_file_fmt: file format is " + "not initialized, can not flush"); + return -EINVAL; + } + + ops = xloop_file_fmt_drivers[xlo_fmt->file_fmt_type]->ops; + if (likely(ops->flush)) + return ops->flush(xlo_fmt); + + return 0; +} + +loff_t xloop_file_fmt_sector_size(struct xloop_file_fmt *xlo_fmt, + struct file *file, loff_t offset, loff_t sizelimit) +{ + struct xloop_file_fmt_ops *ops; + + if (unlikely(xlo_fmt->file_fmt_state != file_fmt_initialized)) { + printk(KERN_ERR "xloop_file_fmt: file format is " + "not initialized, can not read sector size"); + return 0; + } + + ops = xloop_file_fmt_drivers[xlo_fmt->file_fmt_type]->ops; + if (likely(ops->sector_size)) + return ops->sector_size(xlo_fmt, file, offset, sizelimit); + else + return 0; +} + +int xloop_file_fmt_change(struct xloop_file_fmt *xlo_fmt, + u32 file_fmt_type_new) +{ + if (file_fmt_type_new > MAX_XLO_FILE_FMT) + return -EINVAL; + + /* Unload the old file format driver if the file format is + * initialized */ + if (xlo_fmt->file_fmt_state == file_fmt_initialized) + xloop_file_fmt_exit(xlo_fmt); + + /* Load the new file format driver because the file format is + * uninitialized now */ + return xloop_file_fmt_init(xlo_fmt, file_fmt_type_new); +} + +ssize_t xloop_file_fmt_print_type(u32 file_fmt_type, char *file_fmt_name) +{ + ssize_t len = 0; + + switch (file_fmt_type) { + case XLO_FILE_FMT_RAW: + len = sprintf(file_fmt_name, "%s", "RAW"); + break; + case XLO_FILE_FMT_QCOW: + len = sprintf(file_fmt_name, "%s", "QCOW"); + break; + case XLO_FILE_FMT_VDI: + len = sprintf(file_fmt_name, "%s", "VDI"); + break; + case XLO_FILE_FMT_VMDK: + len = sprintf(file_fmt_name, "%s", "VMDK"); + break; + default: + len = sprintf(file_fmt_name, "%s", "ERROR: Unsupported xloop " + "file format!"); + break; + } + + return len; +} +EXPORT_SYMBOL(xloop_file_fmt_print_type); diff --git a/kernel/loop_file_fmt.h b/kernel/loop_file_fmt.h new file mode 100644 index 0000000..38d6a3b --- /dev/null +++ b/kernel/loop_file_fmt.h @@ -0,0 +1,380 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * loop_file_fmt.h + * + * File format subsystem for the xloop device module. + * + * Copyright (C) 2019 Manuel Bentele + */ + +#ifndef _LINUX_XLOOP_FILE_FMT_H +#define _LINUX_XLOOP_FILE_FMT_H + +#include "loop_main.h" + +struct xloop_file_fmt; + +#define XLO_FILE_FMT_RAW 0 +#define XLO_FILE_FMT_QCOW 1 +#define XLO_FILE_FMT_VDI 2 +#define XLO_FILE_FMT_VMDK 3 +#define MAX_XLO_FILE_FMT (XLO_FILE_FMT_VMDK + 1) + +/** + * struct xloop_file_fmt_ops - File format subsystem operations + * + * Data structure representing the file format subsystem interface. + */ +struct xloop_file_fmt_ops { + /** + * @init: Initialization callback function + */ + int (*init) (struct xloop_file_fmt *xlo_fmt); + + /** + * @exit: Release callback function + */ + void (*exit) (struct xloop_file_fmt *xlo_fmt); + + /** + * @read: Read IO callback function + */ + int (*read) (struct xloop_file_fmt *xlo_fmt, + struct request *rq); + + /** + * @write: Write IO callback function + */ + int (*write) (struct xloop_file_fmt *xlo_fmt, + struct request *rq); + + /** + * @read_aio: Asynchronous read IO callback function + */ + int (*read_aio) (struct xloop_file_fmt *xlo_fmt, + struct request *rq); + + /** + * @write_aio: Asynchronous write IO callback function + */ + int (*write_aio) (struct xloop_file_fmt *xlo_fmt, + struct request *rq); + + /** + * @zero: Zero (discard) IO callback function + */ + int (*write_zeros) (struct xloop_file_fmt *xlo_fmt, + struct request *rq); + + /** + * @discard: Discard IO callback function + */ + int (*discard) (struct xloop_file_fmt *xlo_fmt, + struct request *rq); + + /** + * @flush: Flush callback function + */ + int (*flush) (struct xloop_file_fmt *xlo_fmt); + + /** + * @sector_size: Get sector size callback function + */ + loff_t (*sector_size) (struct xloop_file_fmt *xlo_fmt, + struct file *file, loff_t offset, loff_t sizelimit); +}; + +/** + * struct xloop_file_fmt_driver - File format subsystem driver + * + * Data structure to implement file format drivers for the file format + * subsystem. + */ +struct xloop_file_fmt_driver { + /** + * @name: Name of the file format driver + */ + const char *name; + + /** + * @file_fmt_type: xloop file format type of the file format driver + */ + const u32 file_fmt_type; + + /** + * @ops: Driver's implemented file format operations + */ + struct xloop_file_fmt_ops *ops; + + /** + * @ops: Owner of the file format driver + */ + struct module *owner; +}; + +/* + * states of the file format + * + * transitions: + * xloop_file_fmt_init(...) + * ---> uninitialized ------------------------------> initialized + * xloop_file_fmt_exit(...) + * initialized ------------------------------> uninitialized + * xloop_file_fmt_read(...) + * initialized ------------------------------> initialized + * xloop_file_fmt_read_aio(...) + * initialized ------------------------------> initialized + * xloop_file_fmt_write(...) + * initialized ------------------------------> initialized + * xloop_file_fmt_write_aio(...) + * initialized ------------------------------> initialized + * xloop_file_fmt_discard(...) + * initialized ------------------------------> initialized + * xloop_file_fmt_flush(...) + * initialized ------------------------------> initialized + * xloop_file_fmt_sector_size(...) + * initialized ------------------------------> initialized + * + * xloop_file_fmt_change(...) + * +-----------------------------------------------------------+ + * | exit(...) init(...) | + * | initialized -------> uninitialized -------> initialized | + * +-----------------------------------------------------------+ + */ +enum { + file_fmt_uninitialized = 0, + file_fmt_initialized +}; + +/** + * struct xloop_file_fmt - xloop file format + * + * Data structure to use with the file format the xloop file format subsystem. + */ +struct xloop_file_fmt { + /** + * @file_fmt_type: Current type of the xloop file format + */ + u32 file_fmt_type; + + /** + * @file_fmt_state: Current state of the xloop file format + */ + int file_fmt_state; + + /** + * @xlo: Link to a file format's xloop device + */ + struct xloop_device *xlo; + + /** + * @private_data: Optional link to a file format's driver specific data + */ + void *private_data; +}; + + +/* subsystem functions for the driver implementation */ + +/** + * xloop_file_fmt_register_driver - Register a xloop file format driver + * @drv: File format driver + * + * Registers the specified xloop file format driver @drv by the xloop file format + * subsystem. + */ +extern int xloop_file_fmt_register_driver(struct xloop_file_fmt_driver *drv); + +/** + * xloop_file_fmt_unregister_driver - Unregister a xloop file format driver + * @drv: File format driver + * + * Unregisters the specified xloop file format driver @drv from the xloop file + * format subsystem. + */ +extern void xloop_file_fmt_unregister_driver(struct xloop_file_fmt_driver *drv); + + +/* subsystem functions for subsystem usage */ + +/** + * xloop_file_fmt_alloc - Allocate a xloop file format + * + * Dynamically allocates a xloop file format and returns a pointer to the + * created xloop file format. + */ +extern struct xloop_file_fmt *xloop_file_fmt_alloc(void); + +/** + * xloop_file_fmt_free - Free an allocated xloop file format + * @xlo_fmt: xloop file format + * + * Frees the already allocated xloop file format @xlo_fmt. + */ +extern void xloop_file_fmt_free(struct xloop_file_fmt *xlo_fmt); + +/** + * xloop_file_fmt_set_xlo - Set the xloop file format's xloop device + * @xlo_fmt: xloop file format + * @xlo: xloop device + * + * The link to the xloop device @xlo is set in the xloop file format @xlo_fmt. + */ +extern int xloop_file_fmt_set_xlo(struct xloop_file_fmt *xlo_fmt, + struct xloop_device *xlo); + +/** + * xloop_file_fmt_get_xlo - Get the xloop file format's xloop device + * @xlo_fmt: xloop file format + * + * Returns a pointer to the xloop device of the xloop file format @xlo_fmt. + */ +extern struct xloop_device *xloop_file_fmt_get_xlo(struct xloop_file_fmt *xlo_fmt); + +/** + * xloop_file_fmt_init - Initialize a xloop file format + * @xlo_fmt: xloop file format + * @file_fmt_type: Type of the file format + * + * Initializes the specified xloop file format @xlo_fmt and sets up the correct + * file format type @file_fmt_type. Depending on @file_fmt_type, the correct + * xloop file format driver is loaded in the subsystems backend. If no xloop file + * format driver for the specified file format is available an error is + * returned. + */ +extern int xloop_file_fmt_init(struct xloop_file_fmt *xlo_fmt, + u32 file_fmt_type); + +/** + * xloop_file_fmt_exit - Release a xloop file format + * @xlo_fmt: xloop file format + * + * Releases the specified xloop file format @xlo_fmt and all its resources. + */ +extern void xloop_file_fmt_exit(struct xloop_file_fmt *xlo_fmt); + +/** + * xloop_file_fmt_read - Read IO from a xloop file format + * @xlo_fmt: xloop file format + * @rq: IO Request + * + * Reads IO from the file format's xloop device by sending the IO read request + * @rq to the xloop file format subsystem. The subsystem calls the registered + * callback function of the suitable xloop file format driver. + */ +extern int xloop_file_fmt_read(struct xloop_file_fmt *xlo_fmt, + struct request *rq); + +/** + * xloop_file_fmt_read_aio - Read IO from a xloop file format asynchronously + * @xlo_fmt: xloop file format + * @rq: IO Request + * + * Reads IO from the file format's xloop device asynchronously by sending the + * IO read aio request @rq to the xloop file format subsystem. The subsystem + * calls the registered callback function of the suitable xloop file format + * driver. + */ +extern int xloop_file_fmt_read_aio(struct xloop_file_fmt *xlo_fmt, + struct request *rq); + +/** + * xloop_file_fmt_write - Write IO to a xloop file format + * @xlo_fmt: xloop file format + * @rq: IO Request + * + * Write IO to the file format's xloop device by sending the IO write request + * @rq to the xloop file format subsystem. The subsystem calls the registered + * callback function of the suitable xloop file format driver. + */ +extern int xloop_file_fmt_write(struct xloop_file_fmt *xlo_fmt, + struct request *rq); + +/** + * xloop_file_fmt_write_aio - Write IO to a xloop file format asynchronously + * @xlo_fmt: xloop file format + * @rq: IO Request + * + * Write IO to the file format's xloop device asynchronously by sending the + * IO write aio request @rq to the xloop file format subsystem. The subsystem + * calls the registered callback function of the suitable xloop file format + * driver. + */ +extern int xloop_file_fmt_write_aio(struct xloop_file_fmt *xlo_fmt, + struct request *rq); + +/** + * xloop_file_fmt_write_zeros - Zero (discard) IO on a xloop file format + * @xlo_fmt: xloop file format + * @rq: IO Request + * + * Zero (discard) IO on the file format's xloop device by sending the IO write + * zeros request @rq to the xloop file format subsystem. The subsystem calls the + * registered callback function of the suitable xloop file format driver. + */ +extern int xloop_file_fmt_write_zeros(struct xloop_file_fmt *xlo_fmt, + struct request *rq); + +/** + * xloop_file_fmt_discard - Discard IO on a xloop file format + * @xlo_fmt: xloop file format + * @rq: IO Request + * + * Discard IO on the file format's xloop device by sending the IO discard + * request @rq to the xloop file format subsystem. The subsystem calls the + * registered callback function of the suitable xloop file format driver. + */ +extern int xloop_file_fmt_discard(struct xloop_file_fmt *xlo_fmt, + struct request *rq); + +/** + * xloop_file_fmt_flush - Flush a xloop file format + * @xlo_fmt: xloop file format + * + * Flush the file format's xloop device by calling the registered callback + * function of the suitable xloop file format driver. + */ +extern int xloop_file_fmt_flush(struct xloop_file_fmt *xlo_fmt); + +/** + * xloop_file_fmt_sector_size - Get sector size of a xloop file format + * @xlo_fmt: xloop file format + * @file: xloop file formats file for sector size calculation + * @offset: Offset within the file for sector size calculation + * @sizelimit: Sizelimit of the file for sector size calculation + * + * Returns the physical sector size of the given xloop file format's file. + * If the xloop file format implements a sparse disk image format, then this + * function returns the virtual sector size. + */ +extern loff_t xloop_file_fmt_sector_size(struct xloop_file_fmt *xlo_fmt, + struct file *file, loff_t offset, loff_t sizelimit); + +/** + * xloop_file_fmt_change - Change the xloop file format's type + * @xlo_fmt: xloop file format + * @file_fmt_type_new: xloop file format type + * + * Changes the file format type of the already initialized xloop file format + * @xlo_fmt. Therefore, the function releases the old file format and frees all + * of its resources before the xloop file format @xlo_fmt is initialized and set + * up with the new file format @file_fmt_type_new. + */ +extern int xloop_file_fmt_change(struct xloop_file_fmt *xlo_fmt, + u32 file_fmt_type_new); + + +/* helper functions of the subsystem */ + +/** + * xloop_file_fmt_print_type - Convert file format type to string + * @file_fmt_type: xloop file format type + * @file_fmt_name: xloop file format type string + * + * Converts the specified numeric @file_fmt_type value into a human readable + * string stating the file format as string in @file_fmt_name. + */ +extern ssize_t xloop_file_fmt_print_type(u32 file_fmt_type, + char *file_fmt_name); + +#endif diff --git a/kernel/loop_file_fmt_qcow_cache.c b/kernel/loop_file_fmt_qcow_cache.c new file mode 100644 index 0000000..4ef772a --- /dev/null +++ b/kernel/loop_file_fmt_qcow_cache.c @@ -0,0 +1,218 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * loop_file_fmt_qcow_cache.c + * + * QCOW file format driver for the xloop device module. + * + * Ported QCOW2 implementation of the QEMU project (GPL-2.0): + * L2/refcount table cache for the QCOW2 format. + * + * The copyright (C) 2010 of the original code is owned by + * Kevin Wolf + * + * Copyright (C) 2019 Manuel Bentele + */ + +#include +#include +#include +#include +#include +#include + +#include "loop_file_fmt_qcow_main.h" +#include "loop_file_fmt_qcow_cache.h" + +static inline void *__xloop_file_fmt_qcow_cache_get_table_addr( + struct xloop_file_fmt_qcow_cache *c, int table) +{ + return (u8 *) c->table_array + (size_t) table * c->table_size; +} + +static inline int __xloop_file_fmt_qcow_cache_get_table_idx( + struct xloop_file_fmt_qcow_cache *c, void *table) +{ + ptrdiff_t table_offset = (u8 *) table - (u8 *) c->table_array; + int idx = table_offset / c->table_size; + ASSERT(idx >= 0 && idx < c->size && table_offset % c->table_size == 0); + return idx; +} + +static inline const char *__xloop_file_fmt_qcow_cache_get_name( + struct xloop_file_fmt *xlo_fmt, struct xloop_file_fmt_qcow_cache *c) +{ + struct xloop_file_fmt_qcow_data *qcow_data = xlo_fmt->private_data; + + if (c == qcow_data->refcount_block_cache) { + return "refcount block"; + } else if (c == qcow_data->l2_table_cache) { + return "L2 table"; + } else { + /* do not abort, because this is not critical */ + return "unknown"; + } +} + +struct xloop_file_fmt_qcow_cache *xloop_file_fmt_qcow_cache_create( + struct xloop_file_fmt *xlo_fmt, int num_tables, unsigned table_size) +{ +#ifdef CONFIG_DEBUG_DRIVER + struct xloop_file_fmt_qcow_data *qcow_data = xlo_fmt->private_data; +#endif + struct xloop_file_fmt_qcow_cache *c; + + ASSERT(num_tables > 0); + ASSERT(is_power_of_2(table_size)); + ASSERT(table_size >= (1 << QCOW_MIN_CLUSTER_BITS)); + ASSERT(table_size <= qcow_data->cluster_size); + + c = kzalloc(sizeof(*c), GFP_KERNEL); + if (!c) { + return NULL; + } + + c->size = num_tables; + c->table_size = table_size; + c->entries = vzalloc(sizeof(struct xloop_file_fmt_qcow_cache_table) * + num_tables); + c->table_array = vzalloc(num_tables * c->table_size); + + if (!c->entries || !c->table_array) { + vfree(c->table_array); + vfree(c->entries); + kfree(c); + c = NULL; + } + + return c; +} + +void xloop_file_fmt_qcow_cache_destroy(struct xloop_file_fmt *xlo_fmt) +{ + struct xloop_file_fmt_qcow_data *qcow_data = xlo_fmt->private_data; + struct xloop_file_fmt_qcow_cache *c = qcow_data->l2_table_cache; + int i; + + for (i = 0; i < c->size; i++) { + ASSERT(c->entries[i].ref == 0); + } + + vfree(c->table_array); + vfree(c->entries); + kfree(c); +} + +static int __xloop_file_fmt_qcow_cache_entry_flush( + struct xloop_file_fmt_qcow_cache *c, int i) +{ + if (!c->entries[i].dirty || !c->entries[i].offset) { + return 0; + } else { + printk(KERN_ERR "xloop_file_fmt_qcow: Flush dirty cache tables " + "is not supported yet\n"); + return -ENOSYS; + } +} + +static int __xloop_file_fmt_qcow_cache_do_get(struct xloop_file_fmt *xlo_fmt, + struct xloop_file_fmt_qcow_cache *c, u64 offset, void **table, + bool read_from_disk) +{ + struct xloop_device *xlo = xloop_file_fmt_get_xlo(xlo_fmt); + int i; + int ret; + int lookup_index; + u64 min_lru_counter = U64_MAX; + int min_lru_index = -1; + u64 read_offset; + size_t len; + + ASSERT(offset != 0); + + if (!IS_ALIGNED(offset, c->table_size)) { + printk_ratelimited(KERN_ERR "xloop_file_fmt_qcow: Cannot get " + "entry from %s cache: offset %llx is unaligned\n", + __xloop_file_fmt_qcow_cache_get_name(xlo_fmt, c), + offset); + return -EIO; + } + + /* Check if the table is already cached */ + i = lookup_index = (offset / c->table_size * 4) % c->size; + do { + const struct xloop_file_fmt_qcow_cache_table *t = + &c->entries[i]; + if (t->offset == offset) { + goto found; + } + if (t->ref == 0 && t->lru_counter < min_lru_counter) { + min_lru_counter = t->lru_counter; + min_lru_index = i; + } + if (++i == c->size) { + i = 0; + } + } while (i != lookup_index); + + if (min_lru_index == -1) { + BUG(); + panic("Oops: This can't happen in current synchronous code, " + "but leave the check here as a reminder for whoever " + "starts using AIO with the QCOW cache"); + } + + /* Cache miss: write a table back and replace it */ + i = min_lru_index; + + ret = __xloop_file_fmt_qcow_cache_entry_flush(c, i); + if (ret < 0) { + return ret; + } + + c->entries[i].offset = 0; + if (read_from_disk) { + read_offset = offset; + len = kernel_read(xlo->xlo_backing_file, + __xloop_file_fmt_qcow_cache_get_table_addr(c, i), + c->table_size, &read_offset); + if (len < 0) { + len = ret; + return ret; + } + } + + c->entries[i].offset = offset; + + /* And return the right table */ +found: + c->entries[i].ref++; + *table = __xloop_file_fmt_qcow_cache_get_table_addr(c, i); + + return 0; +} + +int xloop_file_fmt_qcow_cache_get(struct xloop_file_fmt *xlo_fmt, u64 offset, + void **table) +{ + struct xloop_file_fmt_qcow_data *qcow_data = xlo_fmt->private_data; + struct xloop_file_fmt_qcow_cache *c = qcow_data->l2_table_cache; + + return __xloop_file_fmt_qcow_cache_do_get(xlo_fmt, c, offset, table, + true); +} + +void xloop_file_fmt_qcow_cache_put(struct xloop_file_fmt *xlo_fmt, void **table) +{ + struct xloop_file_fmt_qcow_data *qcow_data = xlo_fmt->private_data; + struct xloop_file_fmt_qcow_cache *c = qcow_data->l2_table_cache; + int i = __xloop_file_fmt_qcow_cache_get_table_idx(c, *table); + + c->entries[i].ref--; + *table = NULL; + + if (c->entries[i].ref == 0) { + c->entries[i].lru_counter = ++c->lru_counter; + } + + ASSERT(c->entries[i].ref >= 0); +} diff --git a/kernel/loop_file_fmt_qcow_cache.h b/kernel/loop_file_fmt_qcow_cache.h new file mode 100644 index 0000000..d2f1010 --- /dev/null +++ b/kernel/loop_file_fmt_qcow_cache.h @@ -0,0 +1,51 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * xloop_file_fmt_qcow_cache.h + * + * Ported QCOW2 implementation of the QEMU project (GPL-2.0): + * L2/refcount table cache for the QCOW2 format. + * + * The copyright (C) 2010 of the original code is owned by + * Kevin Wolf + * + * Copyright (C) 2019 Manuel Bentele + */ + +#ifndef _LINUX_XLOOP_FILE_FMT_QCOW_CACHE_H +#define _LINUX_XLOOP_FILE_FMT_QCOW_CACHE_H + +#include "loop_file_fmt.h" + +struct xloop_file_fmt_qcow_cache_table { + s64 offset; + u64 lru_counter; + int ref; + bool dirty; +}; + +struct xloop_file_fmt_qcow_cache { + struct xloop_file_fmt_qcow_cache_table *entries; + struct xloop_file_fmt_qcow_cache *depends; + int size; + int table_size; + bool depends_on_flush; + void *table_array; + u64 lru_counter; + u64 cache_clean_lru_counter; +}; + +extern struct xloop_file_fmt_qcow_cache *xloop_file_fmt_qcow_cache_create( + struct xloop_file_fmt *xlo_fmt, + int num_tables, + unsigned table_size); + +extern void xloop_file_fmt_qcow_cache_destroy(struct xloop_file_fmt *xlo_fmt); + +extern int xloop_file_fmt_qcow_cache_get(struct xloop_file_fmt *xlo_fmt, + u64 offset, + void **table); + +extern void xloop_file_fmt_qcow_cache_put(struct xloop_file_fmt *xlo_fmt, + void **table); + +#endif diff --git a/kernel/loop_file_fmt_qcow_cluster.c b/kernel/loop_file_fmt_qcow_cluster.c new file mode 100644 index 0000000..593a173 --- /dev/null +++ b/kernel/loop_file_fmt_qcow_cluster.c @@ -0,0 +1,270 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * xloop_file_fmt_qcow_cluster.c + * + * Ported QCOW2 implementation of the QEMU project (GPL-2.0): + * Cluster calculation and lookup for the QCOW2 format. + * + * The copyright (C) 2004-2006 of the original code is owned by Fabrice Bellard. + * + * Copyright (C) 2019 Manuel Bentele + */ + +#include +#include + +#include "loop_file_fmt.h" +#include "loop_file_fmt_qcow_main.h" +#include "loop_file_fmt_qcow_cache.h" +#include "loop_file_fmt_qcow_cluster.h" + +/* + * Loads a L2 slice into memory (L2 slices are the parts of L2 tables + * that are loaded by the qcow2 cache). If the slice is in the cache, + * the cache is used; otherwise the L2 slice is loaded from the image + * file. + */ +static int __xloop_file_fmt_qcow_cluster_l2_load(struct xloop_file_fmt *xlo_fmt, + u64 offset, u64 l2_offset, u64 **l2_slice) +{ + struct xloop_file_fmt_qcow_data *qcow_data = xlo_fmt->private_data; + + int start_of_slice = sizeof(u64) * ( + xloop_file_fmt_qcow_offset_to_l2_index(qcow_data, offset) - + xloop_file_fmt_qcow_offset_to_l2_slice_index(qcow_data, offset) + ); + + ASSERT(qcow_data->l2_table_cache != NULL); + return xloop_file_fmt_qcow_cache_get(xlo_fmt, l2_offset + start_of_slice, + (void **) l2_slice); +} + +/* + * Checks how many clusters in a given L2 slice are contiguous in the image + * file. As soon as one of the flags in the bitmask stop_flags changes compared + * to the first cluster, the search is stopped and the cluster is not counted + * as contiguous. (This allows it, for example, to stop at the first compressed + * cluster which may require a different handling) + */ +static int __xloop_file_fmt_qcow_cluster_count_contiguous( + struct xloop_file_fmt *xlo_fmt, int nb_clusters, int cluster_size, + u64 *l2_slice, u64 stop_flags) +{ + int i; + enum xloop_file_fmt_qcow_cluster_type first_cluster_type; + u64 mask = stop_flags | L2E_OFFSET_MASK | QCOW_OFLAG_COMPRESSED; + u64 first_entry = be64_to_cpu(l2_slice[0]); + u64 offset = first_entry & mask; + + first_cluster_type = xloop_file_fmt_qcow_get_cluster_type(xlo_fmt, + first_entry); + if (first_cluster_type == QCOW_CLUSTER_UNALLOCATED) { + return 0; + } + + /* must be allocated */ + ASSERT(first_cluster_type == QCOW_CLUSTER_NORMAL || + first_cluster_type == QCOW_CLUSTER_ZERO_ALLOC); + + for (i = 0; i < nb_clusters; i++) { + u64 l2_entry = be64_to_cpu(l2_slice[i]) & mask; + if (offset + (u64) i * cluster_size != l2_entry) { + break; + } + } + + return i; +} + +/* + * Checks how many consecutive unallocated clusters in a given L2 + * slice have the same cluster type. + */ +static int __xloop_file_fmt_qcow_cluster_count_contiguous_unallocated( + struct xloop_file_fmt *xlo_fmt, int nb_clusters, u64 *l2_slice, + enum xloop_file_fmt_qcow_cluster_type wanted_type) +{ + int i; + + ASSERT(wanted_type == QCOW_CLUSTER_ZERO_PLAIN || + wanted_type == QCOW_CLUSTER_UNALLOCATED); + + for (i = 0; i < nb_clusters; i++) { + u64 entry = be64_to_cpu(l2_slice[i]); + enum xloop_file_fmt_qcow_cluster_type type = + xloop_file_fmt_qcow_get_cluster_type(xlo_fmt, entry); + + if (type != wanted_type) { + break; + } + } + + return i; +} + +/* + * For a given offset of the virtual disk, find the cluster type and offset in + * the qcow2 file. The offset is stored in *cluster_offset. + * + * On entry, *bytes is the maximum number of contiguous bytes starting at + * offset that we are interested in. + * + * On exit, *bytes is the number of bytes starting at offset that have the same + * cluster type and (if applicable) are stored contiguously in the image file. + * Compressed clusters are always returned one by one. + * + * Returns the cluster type (QCOW2_CLUSTER_*) on success, -errno in error + * cases. + */ +int xloop_file_fmt_qcow_cluster_get_offset(struct xloop_file_fmt *xlo_fmt, + u64 offset, unsigned int *bytes, u64 *cluster_offset) +{ + struct xloop_file_fmt_qcow_data *qcow_data = xlo_fmt->private_data; + unsigned int l2_index; + u64 l1_index, l2_offset, *l2_slice; + int c; + unsigned int offset_in_cluster; + u64 bytes_available, bytes_needed, nb_clusters; + enum xloop_file_fmt_qcow_cluster_type type; + int ret; + + offset_in_cluster = xloop_file_fmt_qcow_offset_into_cluster(qcow_data, + offset); + bytes_needed = (u64) *bytes + offset_in_cluster; + + /* compute how many bytes there are between the start of the cluster + * containing offset and the end of the l2 slice that contains + * the entry pointing to it */ + bytes_available = ((u64)( + qcow_data->l2_slice_size - + xloop_file_fmt_qcow_offset_to_l2_slice_index(qcow_data, offset)) + ) << qcow_data->cluster_bits; + + if (bytes_needed > bytes_available) { + bytes_needed = bytes_available; + } + + *cluster_offset = 0; + + /* seek to the l2 offset in the l1 table */ + l1_index = xloop_file_fmt_qcow_offset_to_l1_index(qcow_data, offset); + if (l1_index >= qcow_data->l1_size) { + type = QCOW_CLUSTER_UNALLOCATED; + goto out; + } + + l2_offset = qcow_data->l1_table[l1_index] & L1E_OFFSET_MASK; + if (!l2_offset) { + type = QCOW_CLUSTER_UNALLOCATED; + goto out; + } + + if (xloop_file_fmt_qcow_offset_into_cluster(qcow_data, l2_offset)) { + printk_ratelimited(KERN_ERR "xloop_file_fmt_qcow: L2 table " + "offset %llx unaligned (L1 index: %llx)", l2_offset, + l1_index); + return -EIO; + } + + /* load the l2 slice in memory */ + ret = __xloop_file_fmt_qcow_cluster_l2_load(xlo_fmt, offset, l2_offset, + &l2_slice); + if (ret < 0) { + return ret; + } + + /* find the cluster offset for the given disk offset */ + l2_index = xloop_file_fmt_qcow_offset_to_l2_slice_index(qcow_data, + offset); + *cluster_offset = be64_to_cpu(l2_slice[l2_index]); + + nb_clusters = xloop_file_fmt_qcow_size_to_clusters(qcow_data, + bytes_needed); + /* bytes_needed <= *bytes + offset_in_cluster, both of which are + * unsigned integers; the minimum cluster size is 512, so this + * assertion is always true */ + ASSERT(nb_clusters <= INT_MAX); + + type = xloop_file_fmt_qcow_get_cluster_type(xlo_fmt, *cluster_offset); + if (qcow_data->qcow_version < 3 && ( + type == QCOW_CLUSTER_ZERO_PLAIN || + type == QCOW_CLUSTER_ZERO_ALLOC)) { + printk_ratelimited(KERN_ERR "xloop_file_fmt_qcow: zero cluster " + "entry found in pre-v3 image (L2 offset: %llx, " + "L2 index: %x)\n", l2_offset, l2_index); + ret = -EIO; + goto fail; + } + switch (type) { + case QCOW_CLUSTER_COMPRESSED: + if (xloop_file_fmt_qcow_has_data_file(xlo_fmt)) { + printk_ratelimited(KERN_ERR "xloop_file_fmt_qcow: " + "compressed cluster entry found in image with " + "external data file (L2 offset: %llx, " + "L2 index: %x)", l2_offset, l2_index); + ret = -EIO; + goto fail; + } + /* Compressed clusters can only be processed one by one */ + c = 1; + *cluster_offset &= L2E_COMPRESSED_OFFSET_SIZE_MASK; + break; + case QCOW_CLUSTER_ZERO_PLAIN: + case QCOW_CLUSTER_UNALLOCATED: + /* how many empty clusters ? */ + c = __xloop_file_fmt_qcow_cluster_count_contiguous_unallocated( + xlo_fmt, nb_clusters, &l2_slice[l2_index], type); + *cluster_offset = 0; + break; + case QCOW_CLUSTER_ZERO_ALLOC: + case QCOW_CLUSTER_NORMAL: + /* how many allocated clusters ? */ + c = __xloop_file_fmt_qcow_cluster_count_contiguous(xlo_fmt, + nb_clusters, qcow_data->cluster_size, + &l2_slice[l2_index], QCOW_OFLAG_ZERO); + *cluster_offset &= L2E_OFFSET_MASK; + if (xloop_file_fmt_qcow_offset_into_cluster(qcow_data, + *cluster_offset)) { + printk_ratelimited(KERN_ERR "xloop_file_fmt_qcow: " + "cluster allocation offset %llx unaligned " + "(L2 offset: %llx, L2 index: %x)\n", + *cluster_offset, l2_offset, l2_index); + ret = -EIO; + goto fail; + } + if (xloop_file_fmt_qcow_has_data_file(xlo_fmt) && + *cluster_offset != offset - offset_in_cluster) { + printk_ratelimited(KERN_ERR "xloop_file_fmt_qcow: " + "external data file host cluster offset %llx " + "does not match guest cluster offset: %llx, " + "L2 index: %x)", *cluster_offset, + offset - offset_in_cluster, l2_index); + ret = -EIO; + goto fail; + } + break; + default: + BUG(); + } + + xloop_file_fmt_qcow_cache_put(xlo_fmt, (void **) &l2_slice); + + bytes_available = (s64) c * qcow_data->cluster_size; + +out: + if (bytes_available > bytes_needed) { + bytes_available = bytes_needed; + } + + /* bytes_available <= bytes_needed <= *bytes + offset_in_cluster; + * subtracting offset_in_cluster will therefore definitely yield + * something not exceeding UINT_MAX */ + ASSERT(bytes_available - offset_in_cluster <= UINT_MAX); + *bytes = bytes_available - offset_in_cluster; + + return type; + +fail: + xloop_file_fmt_qcow_cache_put(xlo_fmt, (void **) &l2_slice); + return ret; +} diff --git a/kernel/loop_file_fmt_qcow_cluster.h b/kernel/loop_file_fmt_qcow_cluster.h new file mode 100644 index 0000000..5078f29 --- /dev/null +++ b/kernel/loop_file_fmt_qcow_cluster.h @@ -0,0 +1,23 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * xloop_file_fmt_qcow_cluster.h + * + * Ported QCOW2 implementation of the QEMU project (GPL-2.0): + * Cluster calculation and lookup for the QCOW2 format. + * + * The copyright (C) 2004-2006 of the original code is owned by Fabrice Bellard. + * + * Copyright (C) 2019 Manuel Bentele + */ + +#ifndef _LINUX_XLOOP_FILE_FMT_QCOW_CLUSTER_H +#define _LINUX_XLOOP_FILE_FMT_QCOW_CLUSTER_H + +#include "loop_file_fmt.h" + +extern int xloop_file_fmt_qcow_cluster_get_offset(struct xloop_file_fmt *xlo_fmt, + u64 offset, + unsigned int *bytes, + u64 *cluster_offset); + +#endif diff --git a/kernel/loop_file_fmt_qcow_main.c b/kernel/loop_file_fmt_qcow_main.c new file mode 100644 index 0000000..7c3e360 --- /dev/null +++ b/kernel/loop_file_fmt_qcow_main.c @@ -0,0 +1,953 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * xloop_file_fmt_qcow.c + * + * QCOW file format driver for the xloop device module. + * + * Copyright (C) 2019 Manuel Bentele + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "loop_file_fmt.h" +#include "loop_file_fmt_qcow_main.h" +#include "loop_file_fmt_qcow_cache.h" +#include "loop_file_fmt_qcow_cluster.h" + +static int __qcow_file_fmt_header_read(struct file *file, + struct xloop_file_fmt_qcow_header *header) +{ + ssize_t len; + loff_t offset; + int ret = 0; + + /* read QCOW header */ + offset = 0; + len = kernel_read(file, header, sizeof(*header), &offset); + if (len < 0) { + printk(KERN_ERR "xloop_file_fmt_qcow: could not read QCOW " + "header"); + return len; + } + + header->magic = be32_to_cpu(header->magic); + header->version = be32_to_cpu(header->version); + header->backing_file_offset = be64_to_cpu(header->backing_file_offset); + header->backing_file_size = be32_to_cpu(header->backing_file_size); + header->cluster_bits = be32_to_cpu(header->cluster_bits); + header->size = be64_to_cpu(header->size); + header->crypt_method = be32_to_cpu(header->crypt_method); + header->l1_size = be32_to_cpu(header->l1_size); + header->l1_table_offset = be64_to_cpu(header->l1_table_offset); + header->refcount_table_offset = + be64_to_cpu(header->refcount_table_offset); + header->refcount_table_clusters = + be32_to_cpu(header->refcount_table_clusters); + header->nb_snapshots = be32_to_cpu(header->nb_snapshots); + header->snapshots_offset = be64_to_cpu(header->snapshots_offset); + + /* check QCOW file format and header version */ + if (header->magic != QCOW_MAGIC) { + printk(KERN_ERR "xloop_file_fmt_qcow: image is not in QCOW " + "format"); + return -EINVAL; + } + + if (header->version < 2 || header->version > 3) { + printk(KERN_ERR "xloop_file_fmt_qcow: unsupported QCOW version " + "%d", header->version); + return -ENOTSUPP; + } + + /* initialize version 3 header fields */ + if (header->version == 2) { + header->incompatible_features = 0; + header->compatible_features = 0; + header->autoclear_features = 0; + header->refcount_order = 4; + header->header_length = 72; + } else { + header->incompatible_features = + be64_to_cpu(header->incompatible_features); + header->compatible_features = + be64_to_cpu(header->compatible_features); + header->autoclear_features = + be64_to_cpu(header->autoclear_features); + header->refcount_order = be32_to_cpu(header->refcount_order); + header->header_length = be32_to_cpu(header->header_length); + + if (header->header_length < 104) { + printk(KERN_ERR "xloop_file_fmt_qcow: QCOW header too " + "short"); + return -EINVAL; + } + } + + return ret; +} + +static int __qcow_file_fmt_validate_table(struct xloop_file_fmt *xlo_fmt, + u64 offset, u64 entries, size_t entry_len, s64 max_size_bytes, + const char *table_name) +{ + struct xloop_file_fmt_qcow_data *qcow_data = xlo_fmt->private_data; + + if (entries > max_size_bytes / entry_len) { + printk(KERN_INFO "xloop_file_fmt_qcow: %s too large", + table_name); + return -EFBIG; + } + + /* Use signed S64_MAX as the maximum even for u64 header fields, + * because values will be passed to qemu functions taking s64. */ + if ((S64_MAX - entries * entry_len < offset) || ( + xloop_file_fmt_qcow_offset_into_cluster(qcow_data, offset) != 0) + ) { + printk(KERN_INFO "xloop_file_fmt_qcow: %s offset invalid", + table_name); + return -EINVAL; + } + + return 0; +} + +static inline loff_t __qcow_file_fmt_rq_get_pos(struct xloop_file_fmt *xlo_fmt, + struct request *rq) +{ + struct xloop_device *xlo = xloop_file_fmt_get_xlo(xlo_fmt); + return ((loff_t) blk_rq_pos(rq) << 9) + xlo->xlo_offset; +} + +static int __qcow_file_fmt_compression_init(struct xloop_file_fmt *xlo_fmt) +{ + struct xloop_file_fmt_qcow_data *qcow_data = xlo_fmt->private_data; + int ret = 0; + + qcow_data->strm = kzalloc(sizeof(*qcow_data->strm), GFP_KERNEL); + if (!qcow_data->strm) { + ret = -ENOMEM; + goto out; + } + + qcow_data->strm->workspace = vzalloc(zlib_inflate_workspacesize()); + if (!qcow_data->strm->workspace) { + ret = -ENOMEM; + goto out_free_strm; + } + + qcow_data->cmp_last_coffset = ULLONG_MAX; + qcow_data->cmp_out_buf = vmalloc(qcow_data->cluster_size); + if (!qcow_data->cmp_out_buf) { + ret = -ENOMEM; + goto out_free_workspace; + } + + return ret; + +out_free_workspace: + vfree(qcow_data->strm->workspace); +out_free_strm: + kfree(qcow_data->strm); +out: + return ret; +} + +static void __qcow_file_fmt_compression_exit(struct xloop_file_fmt *xlo_fmt) +{ + struct xloop_file_fmt_qcow_data *qcow_data = xlo_fmt->private_data; + + vfree(qcow_data->strm->workspace); + kfree(qcow_data->strm); + vfree(qcow_data->cmp_out_buf); +} + +#ifdef CONFIG_DEBUG_FS +static void __qcow_file_fmt_header_to_buf(struct xloop_file_fmt *xlo_fmt, + const struct xloop_file_fmt_qcow_header *header) +{ + struct xloop_file_fmt_qcow_data *qcow_data = xlo_fmt->private_data; + char *header_buf = qcow_data->dbgfs_file_qcow_header_buf; + ssize_t len = 0; + + len += sprintf(header_buf + len, "magic: %d\n", + header->magic); + len += sprintf(header_buf + len, "version: %d\n", + header->version); + len += sprintf(header_buf + len, "backing_file_offset: %lld\n", + header->backing_file_offset); + len += sprintf(header_buf + len, "backing_file_size: %d\n", + header->backing_file_size); + len += sprintf(header_buf + len, "cluster_bits: %d\n", + header->cluster_bits); + len += sprintf(header_buf + len, "size: %lld\n", + header->size); + len += sprintf(header_buf + len, "crypt_method: %d\n", + header->crypt_method); + len += sprintf(header_buf + len, "l1_size: %d\n", + header->l1_size); + len += sprintf(header_buf + len, "l1_table_offset: %lld\n", + header->l1_table_offset); + len += sprintf(header_buf + len, "refcount_table_offset: %lld\n", + header->refcount_table_offset); + len += sprintf(header_buf + len, "refcount_table_clusters: %d\n", + header->refcount_table_clusters); + len += sprintf(header_buf + len, "nb_snapshots: %d\n", + header->nb_snapshots); + len += sprintf(header_buf + len, "snapshots_offset: %lld\n", + header->snapshots_offset); + + if (header->version == 3) { + len += sprintf(header_buf + len, + "incompatible_features: %lld\n", + header->incompatible_features); + len += sprintf(header_buf + len, + "compatible_features: %lld\n", + header->compatible_features); + len += sprintf(header_buf + len, + "autoclear_features: %lld\n", + header->autoclear_features); + len += sprintf(header_buf + len, + "refcount_order: %d\n", + header->refcount_order); + len += sprintf(header_buf + len, + "header_length: %d\n", + header->header_length); + } + + ASSERT(len < QCOW_HEADER_BUF_LEN); +} + +static ssize_t __qcow_file_fmt_dbgfs_hdr_read(struct file *file, + char __user *buf, size_t size, loff_t *ppos) +{ + struct xloop_file_fmt *xlo_fmt = file->private_data; + struct xloop_file_fmt_qcow_data *qcow_data = xlo_fmt->private_data; + char *header_buf = qcow_data->dbgfs_file_qcow_header_buf; + + return simple_read_from_buffer(buf, size, ppos, header_buf, + strlen(header_buf)); +} + +static const struct file_operations qcow_file_fmt_dbgfs_hdr_fops = { + .open = simple_open, + .read = __qcow_file_fmt_dbgfs_hdr_read +}; + +static ssize_t __qcow_file_fmt_dbgfs_ofs_read(struct file *file, + char __user *buf, size_t size, loff_t *ppos) +{ + struct xloop_file_fmt *xlo_fmt = file->private_data; + struct xloop_file_fmt_qcow_data *qcow_data = xlo_fmt->private_data; + unsigned int cur_bytes = 1; + u64 offset = 0; + u64 cluster_offset = 0; + s64 offset_in_cluster = 0; + ssize_t len = 0; + int ret = 0; + + /* read the share debugfs offset */ + ret = mutex_lock_interruptible(&qcow_data->dbgfs_qcow_offset_mutex); + if (ret) + return ret; + + offset = qcow_data->dbgfs_qcow_offset; + mutex_unlock(&qcow_data->dbgfs_qcow_offset_mutex); + + /* calculate and print the cluster offset */ + ret = xloop_file_fmt_qcow_cluster_get_offset(xlo_fmt, + offset, &cur_bytes, &cluster_offset); + if (ret < 0) + return -EINVAL; + + offset_in_cluster = xloop_file_fmt_qcow_offset_into_cluster(qcow_data, + offset); + + len = sprintf(qcow_data->dbgfs_file_qcow_cluster_buf, + "offset: %lld\ncluster_offset: %lld\noffset_in_cluster: %lld\n", + offset, cluster_offset, offset_in_cluster); + + ASSERT(len < QCOW_CLUSTER_BUF_LEN); + + return simple_read_from_buffer(buf, size, ppos, + qcow_data->dbgfs_file_qcow_cluster_buf, len); +} + +static ssize_t __qcow_file_fmt_dbgfs_ofs_write(struct file *file, + const char __user *buf, size_t size, loff_t *ppos) +{ + struct xloop_file_fmt *xlo_fmt = file->private_data; + struct xloop_file_fmt_qcow_data *qcow_data = xlo_fmt->private_data; + ssize_t len = 0; + int ret = 0; + + if (*ppos > QCOW_OFFSET_BUF_LEN || size > QCOW_OFFSET_BUF_LEN) + return -EINVAL; + + len = simple_write_to_buffer(qcow_data->dbgfs_file_qcow_offset_buf, + QCOW_OFFSET_BUF_LEN, ppos, buf, size); + if (len < 0) + return len; + + qcow_data->dbgfs_file_qcow_offset_buf[len] = '\0'; + + ret = mutex_lock_interruptible(&qcow_data->dbgfs_qcow_offset_mutex); + if (ret) + return ret; + + ret = kstrtou64(qcow_data->dbgfs_file_qcow_offset_buf, 10, + &qcow_data->dbgfs_qcow_offset); + if (ret < 0) + goto out; + + ret = len; +out: + mutex_unlock(&qcow_data->dbgfs_qcow_offset_mutex); + return ret; +} + +static const struct file_operations qcow_file_fmt_dbgfs_ofs_fops = { + .open = simple_open, + .read = __qcow_file_fmt_dbgfs_ofs_read, + .write = __qcow_file_fmt_dbgfs_ofs_write +}; + +static int __qcow_file_fmt_dbgfs_init(struct xloop_file_fmt *xlo_fmt) +{ + struct xloop_file_fmt_qcow_data *qcow_data = xlo_fmt->private_data; + struct xloop_device *xlo = xloop_file_fmt_get_xlo(xlo_fmt); + int ret = 0; + + qcow_data->dbgfs_dir = debugfs_create_dir("QCOW", xlo->xlo_dbgfs_dir); + if (IS_ERR_OR_NULL(qcow_data->dbgfs_dir)) { + ret = -ENODEV; + goto out; + } + + qcow_data->dbgfs_file_qcow_header = debugfs_create_file("header", + S_IRUGO, qcow_data->dbgfs_dir, xlo_fmt, + &qcow_file_fmt_dbgfs_hdr_fops); + if (IS_ERR_OR_NULL(qcow_data->dbgfs_file_qcow_header)) { + ret = -ENODEV; + goto out_free_dbgfs_dir; + } + + qcow_data->dbgfs_file_qcow_offset = debugfs_create_file("offset", + S_IRUGO | S_IWUSR, qcow_data->dbgfs_dir, xlo_fmt, + &qcow_file_fmt_dbgfs_ofs_fops); + if (IS_ERR_OR_NULL(qcow_data->dbgfs_file_qcow_offset)) { + qcow_data->dbgfs_file_qcow_offset = NULL; + ret = -ENODEV; + goto out_free_dbgfs_hdr; + } + + qcow_data->dbgfs_qcow_offset = 0; + mutex_init(&qcow_data->dbgfs_qcow_offset_mutex); + + return ret; + +out_free_dbgfs_hdr: + debugfs_remove(qcow_data->dbgfs_file_qcow_header); + qcow_data->dbgfs_file_qcow_header = NULL; +out_free_dbgfs_dir: + debugfs_remove(qcow_data->dbgfs_dir); + qcow_data->dbgfs_dir = NULL; +out: + return ret; +} + +static void __qcow_file_fmt_dbgfs_exit(struct xloop_file_fmt *xlo_fmt) +{ + struct xloop_file_fmt_qcow_data *qcow_data = xlo_fmt->private_data; + + if (qcow_data->dbgfs_file_qcow_offset) + debugfs_remove(qcow_data->dbgfs_file_qcow_offset); + + mutex_destroy(&qcow_data->dbgfs_qcow_offset_mutex); + + if (qcow_data->dbgfs_file_qcow_header) + debugfs_remove(qcow_data->dbgfs_file_qcow_header); + + if (qcow_data->dbgfs_dir) + debugfs_remove(qcow_data->dbgfs_dir); +} +#endif + +static int qcow_file_fmt_init(struct xloop_file_fmt *xlo_fmt) +{ + struct xloop_file_fmt_qcow_data *qcow_data; + struct xloop_device *xlo = xloop_file_fmt_get_xlo(xlo_fmt); + struct xloop_file_fmt_qcow_header header; + u64 l1_vm_state_index; + u64 l2_cache_size; + u64 l2_cache_entry_size; + ssize_t len; + unsigned int i; + int ret = 0; + + /* allocate memory for saving QCOW file format data */ + qcow_data = kzalloc(sizeof(*qcow_data), GFP_KERNEL); + if (!qcow_data) + return -ENOMEM; + + xlo_fmt->private_data = qcow_data; + + /* read the QCOW file header */ + ret = __qcow_file_fmt_header_read(xlo->xlo_backing_file, &header); + if (ret) + goto free_qcow_data; + + /* save information of the header fields in human readable format in + * a file buffer to access it with debugfs */ +#ifdef CONFIG_DEBUG_FS + __qcow_file_fmt_header_to_buf(xlo_fmt, &header); +#endif + + qcow_data->qcow_version = header.version; + + /* Initialise cluster size */ + if (header.cluster_bits < QCOW_MIN_CLUSTER_BITS + || header.cluster_bits > QCOW_MAX_CLUSTER_BITS) { + printk(KERN_ERR "xloop_file_fmt_qcow: unsupported cluster " + "size: 2^%d", header.cluster_bits); + ret = -EINVAL; + goto free_qcow_data; + } + + qcow_data->cluster_bits = header.cluster_bits; + qcow_data->cluster_size = 1 << qcow_data->cluster_bits; + qcow_data->cluster_sectors = 1 << + (qcow_data->cluster_bits - SECTOR_SHIFT); + + if (header.header_length > qcow_data->cluster_size) { + printk(KERN_ERR "xloop_file_fmt_qcow: QCOW header exceeds " + "cluster size"); + ret = -EINVAL; + goto free_qcow_data; + } + + if (header.backing_file_offset > qcow_data->cluster_size) { + printk(KERN_ERR "xloop_file_fmt_qcow: invalid backing file " + "offset"); + ret = -EINVAL; + goto free_qcow_data; + } + + if (header.backing_file_offset) { + printk(KERN_ERR "xloop_file_fmt_qcow: backing file support not " + "available"); + ret = -ENOTSUPP; + goto free_qcow_data; + } + + /* handle feature bits */ + qcow_data->incompatible_features = header.incompatible_features; + qcow_data->compatible_features = header.compatible_features; + qcow_data->autoclear_features = header.autoclear_features; + + if (qcow_data->incompatible_features & QCOW_INCOMPAT_DIRTY) { + printk(KERN_ERR "xloop_file_fmt_qcow: image contains " + "inconsistent refcounts"); + ret = -EACCES; + goto free_qcow_data; + } + + if (qcow_data->incompatible_features & QCOW_INCOMPAT_CORRUPT) { + printk(KERN_ERR "xloop_file_fmt_qcow: image is corrupt; cannot " + "be opened read/write"); + ret = -EACCES; + goto free_qcow_data; + } + + if (qcow_data->incompatible_features & QCOW_INCOMPAT_DATA_FILE) { + printk(KERN_ERR "xloop_file_fmt_qcow: clusters in the external " + "data file are not refcounted"); + ret = -EACCES; + goto free_qcow_data; + } + + /* Check support for various header values */ + if (header.refcount_order > 6) { + printk(KERN_ERR "xloop_file_fmt_qcow: reference count entry " + "width too large; may not exceed 64 bits"); + ret = -EINVAL; + goto free_qcow_data; + } + qcow_data->refcount_order = header.refcount_order; + qcow_data->refcount_bits = 1 << qcow_data->refcount_order; + qcow_data->refcount_max = U64_C(1) << (qcow_data->refcount_bits - 1); + qcow_data->refcount_max += qcow_data->refcount_max - 1; + + qcow_data->crypt_method_header = header.crypt_method; + if (qcow_data->crypt_method_header) { + printk(KERN_ERR "xloop_file_fmt_qcow: encryption support not " + "available"); + ret = -ENOTSUPP; + goto free_qcow_data; + } + + /* L2 is always one cluster */ + qcow_data->l2_bits = qcow_data->cluster_bits - 3; + qcow_data->l2_size = 1 << qcow_data->l2_bits; + /* 2^(qcow_data->refcount_order - 3) is the refcount width in bytes */ + qcow_data->refcount_block_bits = qcow_data->cluster_bits - + (qcow_data->refcount_order - 3); + qcow_data->refcount_block_size = 1 << qcow_data->refcount_block_bits; + qcow_data->size = header.size; + qcow_data->csize_shift = (62 - (qcow_data->cluster_bits - 8)); + qcow_data->csize_mask = (1 << (qcow_data->cluster_bits - 8)) - 1; + qcow_data->cluster_offset_mask = (1LL << qcow_data->csize_shift) - 1; + + qcow_data->refcount_table_offset = header.refcount_table_offset; + qcow_data->refcount_table_size = header.refcount_table_clusters << + (qcow_data->cluster_bits - 3); + + if (header.refcount_table_clusters == 0) { + printk(KERN_ERR "xloop_file_fmt_qcow: image does not contain a " + "reference count table"); + ret = -EINVAL; + goto free_qcow_data; + } + + ret = __qcow_file_fmt_validate_table(xlo_fmt, + qcow_data->refcount_table_offset, + header.refcount_table_clusters, qcow_data->cluster_size, + QCOW_MAX_REFTABLE_SIZE, "Reference count table"); + if (ret < 0) { + goto free_qcow_data; + } + + /* The total size in bytes of the snapshot table is checked in + * qcow2_read_snapshots() because the size of each snapshot is + * variable and we don't know it yet. + * Here we only check the offset and number of snapshots. */ + ret = __qcow_file_fmt_validate_table(xlo_fmt, header.snapshots_offset, + header.nb_snapshots, + sizeof(struct xloop_file_fmt_qcow_snapshot_header), + sizeof(struct xloop_file_fmt_qcow_snapshot_header) * + QCOW_MAX_SNAPSHOTS, "Snapshot table"); + if (ret < 0) { + goto free_qcow_data; + } + + /* read the level 1 table */ + ret = __qcow_file_fmt_validate_table(xlo_fmt, header.l1_table_offset, + header.l1_size, sizeof(u64), QCOW_MAX_L1_SIZE, + "Active L1 table"); + if (ret < 0) { + goto free_qcow_data; + } + qcow_data->l1_size = header.l1_size; + qcow_data->l1_table_offset = header.l1_table_offset; + + l1_vm_state_index = xloop_file_fmt_qcow_size_to_l1(qcow_data, + header.size); + if (l1_vm_state_index > INT_MAX) { + printk(KERN_ERR "xloop_file_fmt_qcow: image is too big"); + ret = -EFBIG; + goto free_qcow_data; + } + qcow_data->l1_vm_state_index = l1_vm_state_index; + + /* the L1 table must contain at least enough entries to put header.size + * bytes */ + if (qcow_data->l1_size < qcow_data->l1_vm_state_index) { + printk(KERN_ERR "xloop_file_fmt_qcow: L1 table is too small"); + ret = -EINVAL; + goto free_qcow_data; + } + + if (qcow_data->l1_size > 0) { + qcow_data->l1_table = vzalloc(round_up(qcow_data->l1_size * + sizeof(u64), 512)); + if (qcow_data->l1_table == NULL) { + printk(KERN_ERR "xloop_file_fmt_qcow: could not " + "allocate L1 table"); + ret = -ENOMEM; + goto free_qcow_data; + } + len = kernel_read(xlo->xlo_backing_file, qcow_data->l1_table, + qcow_data->l1_size * sizeof(u64), + &qcow_data->l1_table_offset); + if (len < 0) { + printk(KERN_ERR "xloop_file_fmt_qcow: could not read L1 " + "table"); + ret = len; + goto free_l1_table; + } + for (i = 0; i < qcow_data->l1_size; i++) { + qcow_data->l1_table[i] = + be64_to_cpu(qcow_data->l1_table[i]); + } + } + + /* Internal snapshots */ + qcow_data->snapshots_offset = header.snapshots_offset; + qcow_data->nb_snapshots = header.nb_snapshots; + + if (qcow_data->nb_snapshots > 0) { + printk(KERN_ERR "xloop_file_fmt_qcow: snapshots support not " + "available"); + ret = -ENOTSUPP; + goto free_l1_table; + } + + + /* create cache for L2 */ + l2_cache_size = qcow_data->size / (qcow_data->cluster_size / 8); + l2_cache_entry_size = min(qcow_data->cluster_size, (int)4096); + + /* limit the L2 size to maximum QCOW_DEFAULT_L2_CACHE_MAX_SIZE */ + l2_cache_size = min(l2_cache_size, (u64)QCOW_DEFAULT_L2_CACHE_MAX_SIZE); + + /* calculate the number of cache tables */ + l2_cache_size /= l2_cache_entry_size; + if (l2_cache_size < QCOW_MIN_L2_CACHE_SIZE) { + l2_cache_size = QCOW_MIN_L2_CACHE_SIZE; + } + + if (l2_cache_size > INT_MAX) { + printk(KERN_ERR "xloop_file_fmt_qcow: L2 cache size too big"); + ret = -EINVAL; + goto free_l1_table; + } + + qcow_data->l2_slice_size = l2_cache_entry_size / sizeof(u64); + + qcow_data->l2_table_cache = xloop_file_fmt_qcow_cache_create(xlo_fmt, + l2_cache_size, l2_cache_entry_size); + if (!qcow_data->l2_table_cache) { + ret = -ENOMEM; + goto free_l1_table; + } + + /* initialize compression support */ + ret = __qcow_file_fmt_compression_init(xlo_fmt); + if (ret < 0) + goto free_l2_cache; + + /* initialize debugfs entries */ +#ifdef CONFIG_DEBUG_FS + ret = __qcow_file_fmt_dbgfs_init(xlo_fmt); + if (ret < 0) + goto free_l2_cache; +#endif + + return ret; + +free_l2_cache: + xloop_file_fmt_qcow_cache_destroy(xlo_fmt); +free_l1_table: + vfree(qcow_data->l1_table); +free_qcow_data: + kfree(qcow_data); + xlo_fmt->private_data = NULL; + return ret; +} + +static void qcow_file_fmt_exit(struct xloop_file_fmt *xlo_fmt) +{ + struct xloop_file_fmt_qcow_data *qcow_data = xlo_fmt->private_data; + +#ifdef CONFIG_DEBUG_FS + __qcow_file_fmt_dbgfs_exit(xlo_fmt); +#endif + + __qcow_file_fmt_compression_exit(xlo_fmt); + + if (qcow_data->l1_table) { + vfree(qcow_data->l1_table); + } + + if (qcow_data->l2_table_cache) { + xloop_file_fmt_qcow_cache_destroy(xlo_fmt); + } + + if (qcow_data) { + kfree(qcow_data); + xlo_fmt->private_data = NULL; + } +} + +static ssize_t __qcow_file_fmt_buffer_decompress(struct xloop_file_fmt *xlo_fmt, + void *dest, + size_t dest_size, + const void *src, + size_t src_size) +{ + struct xloop_file_fmt_qcow_data *qcow_data = xlo_fmt->private_data; + int ret = 0; + + qcow_data->strm->avail_in = src_size; + qcow_data->strm->next_in = (void *) src; + qcow_data->strm->avail_out = dest_size; + qcow_data->strm->next_out = dest; + + ret = zlib_inflateInit2(qcow_data->strm, -12); + if (ret != Z_OK) { + return -1; + } + + ret = zlib_inflate(qcow_data->strm, Z_FINISH); + if ((ret != Z_STREAM_END && ret != Z_BUF_ERROR) + || qcow_data->strm->avail_out != 0) { + /* We approve Z_BUF_ERROR because we need @dest buffer to be + * filled, but @src buffer may be processed partly (because in + * qcow2 we know size of compressed data with precision of one + * sector) */ + ret = -1; + } else { + ret = 0; + } + return ret; +} + +static int __qcow_file_fmt_read_compressed(struct xloop_file_fmt *xlo_fmt, + struct bio_vec *bvec, + u64 file_cluster_offset, + u64 offset, + u64 bytes, + u64 bytes_done) +{ + struct xloop_file_fmt_qcow_data *qcow_data = xlo_fmt->private_data; + struct xloop_device *xlo = xloop_file_fmt_get_xlo(xlo_fmt); + int ret = 0, csize, nb_csectors; + u64 coffset; + u8 *in_buf = NULL; + ssize_t len; + void *data; + unsigned long irq_flags; + int offset_in_cluster = xloop_file_fmt_qcow_offset_into_cluster( + qcow_data, offset); + + coffset = file_cluster_offset & qcow_data->cluster_offset_mask; + nb_csectors = ((file_cluster_offset >> qcow_data->csize_shift) & + qcow_data->csize_mask) + 1; + csize = nb_csectors * QCOW_COMPRESSED_SECTOR_SIZE - + (coffset & ~QCOW_COMPRESSED_SECTOR_MASK); + + + if (qcow_data->cmp_last_coffset != coffset) { + in_buf = vmalloc(csize); + if (!in_buf) { + qcow_data->cmp_last_coffset = ULLONG_MAX; + return -ENOMEM; + } + qcow_data->cmp_last_coffset = coffset; + len = kernel_read(xlo->xlo_backing_file, in_buf, csize, &coffset); + if (len < 0) { + qcow_data->cmp_last_coffset = ULLONG_MAX; + ret = len; + goto out_free_in_buf; + } + + if (__qcow_file_fmt_buffer_decompress(xlo_fmt, qcow_data->cmp_out_buf, + qcow_data->cluster_size, in_buf, csize) < 0) { + qcow_data->cmp_last_coffset = ULLONG_MAX; + ret = -EIO; + goto out_free_in_buf; + } + } + + ASSERT(bytes <= bvec->bv_len); + data = bvec_kmap_irq(bvec, &irq_flags) + bytes_done; + memcpy(data, qcow_data->cmp_out_buf + offset_in_cluster, bytes); + flush_dcache_page(bvec->bv_page); + bvec_kunmap_irq(data, &irq_flags); + +out_free_in_buf: + vfree(in_buf); + + return ret; +} + +static int __qcow_file_fmt_read_bvec(struct xloop_file_fmt *xlo_fmt, + struct bio_vec *bvec, + loff_t *ppos) +{ + struct xloop_file_fmt_qcow_data *qcow_data = xlo_fmt->private_data; + struct xloop_device *xlo = xloop_file_fmt_get_xlo(xlo_fmt); + int offset_in_cluster; + int ret; + unsigned int cur_bytes; /* number of bytes in current iteration */ + u64 bytes; + u64 cluster_offset = 0; + u64 bytes_done = 0; + void *data; + unsigned long irq_flags; + ssize_t len; + loff_t pos_read; + + bytes = bvec->bv_len; + + while (bytes != 0) { + + /* prepare next request */ + cur_bytes = bytes; + + ret = xloop_file_fmt_qcow_cluster_get_offset(xlo_fmt, *ppos, + &cur_bytes, &cluster_offset); + if (ret < 0) { + goto fail; + } + + offset_in_cluster = xloop_file_fmt_qcow_offset_into_cluster( + qcow_data, *ppos); + + switch (ret) { + case QCOW_CLUSTER_UNALLOCATED: + case QCOW_CLUSTER_ZERO_PLAIN: + case QCOW_CLUSTER_ZERO_ALLOC: + data = bvec_kmap_irq(bvec, &irq_flags) + bytes_done; + memset(data, 0, cur_bytes); + flush_dcache_page(bvec->bv_page); + bvec_kunmap_irq(data, &irq_flags); + break; + + case QCOW_CLUSTER_COMPRESSED: + ret = __qcow_file_fmt_read_compressed(xlo_fmt, bvec, + cluster_offset, *ppos, cur_bytes, bytes_done); + if (ret < 0) { + goto fail; + } + + break; + + case QCOW_CLUSTER_NORMAL: + if ((cluster_offset & 511) != 0) { + ret = -EIO; + goto fail; + } + + pos_read = cluster_offset + offset_in_cluster; + + data = bvec_kmap_irq(bvec, &irq_flags) + bytes_done; + len = kernel_read(xlo->xlo_backing_file, data, cur_bytes, + &pos_read); + flush_dcache_page(bvec->bv_page); + bvec_kunmap_irq(data, &irq_flags); + + if (len < 0) + return len; + + break; + + default: + ret = -EIO; + goto fail; + } + + bytes -= cur_bytes; + *ppos += cur_bytes; + bytes_done += cur_bytes; + } + + ret = 0; + +fail: + return ret; +} + +static int qcow_file_fmt_read(struct xloop_file_fmt *xlo_fmt, + struct request *rq) +{ + struct bio_vec bvec; + struct req_iterator iter; + loff_t pos; + int ret = 0; + + pos = __qcow_file_fmt_rq_get_pos(xlo_fmt, rq); + + rq_for_each_segment(bvec, rq, iter) { + ret = __qcow_file_fmt_read_bvec(xlo_fmt, &bvec, &pos); + if (ret) + return ret; + + cond_resched(); + } + + return ret; +} + +static loff_t qcow_file_fmt_sector_size(struct xloop_file_fmt *xlo_fmt, + struct file *file, loff_t offset, loff_t sizelimit) +{ + struct xloop_file_fmt_qcow_header header; + loff_t xloopsize; + int ret; + + /* temporary read the QCOW file header of other QCOW image file */ + ret = __qcow_file_fmt_header_read(file, &header); + if (ret) + return 0; + + /* compute xloopsize in bytes */ + xloopsize = header.size; + if (offset > 0) + xloopsize -= offset; + /* offset is beyond i_size, weird but possible */ + if (xloopsize < 0) + return 0; + + if (sizelimit > 0 && sizelimit < xloopsize) + xloopsize = sizelimit; + /* + * Unfortunately, if we want to do I/O on the device, + * the number of 512-byte sectors has to fit into a sector_t. + */ + return xloopsize >> 9; +} + +static struct xloop_file_fmt_ops qcow_file_fmt_ops = { + .init = qcow_file_fmt_init, + .exit = qcow_file_fmt_exit, + .read = qcow_file_fmt_read, + .write = NULL, + .read_aio = NULL, + .write_aio = NULL, + .write_zeros = NULL, + .discard = NULL, + .flush = NULL, + .sector_size = qcow_file_fmt_sector_size, +}; + +static struct xloop_file_fmt_driver qcow_file_fmt_driver = { + .name = "QCOW", + .file_fmt_type = XLO_FILE_FMT_QCOW, + .ops = &qcow_file_fmt_ops, + .owner = THIS_MODULE, +}; + +static int __init xloop_file_fmt_qcow_init(void) +{ + printk(KERN_INFO "xloop_file_fmt_qcow: init xloop device QCOW file " + "format driver"); + return xloop_file_fmt_register_driver(&qcow_file_fmt_driver); +} + +static void __exit xloop_file_fmt_qcow_exit(void) +{ + printk(KERN_INFO "xloop_file_fmt_qcow: exit xloop device QCOW file " + "format driver"); + xloop_file_fmt_unregister_driver(&qcow_file_fmt_driver); +} + +module_init(xloop_file_fmt_qcow_init); +module_exit(xloop_file_fmt_qcow_exit); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Manuel Bentele "); +MODULE_DESCRIPTION("xloop device QCOW file format driver"); +MODULE_SOFTDEP("pre: xloop"); diff --git a/kernel/loop_file_fmt_qcow_main.h b/kernel/loop_file_fmt_qcow_main.h new file mode 100644 index 0000000..54b94c3 --- /dev/null +++ b/kernel/loop_file_fmt_qcow_main.h @@ -0,0 +1,419 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * xloop_file_fmt_qcow.h + * + * QCOW file format driver for the xloop device module. + * + * Ported QCOW2 implementation of the QEMU project (GPL-2.0): + * Declarations for the QCOW2 file format. + * + * The copyright (C) 2004-2006 of the original code is owned by Fabrice Bellard. + * + * Copyright (C) 2019 Manuel Bentele + */ + +#ifndef _LINUX_XLOOP_FILE_FMT_QCOW_H +#define _LINUX_XLOOP_FILE_FMT_QCOW_H + +#include +#include +#include +#include + +#ifdef CONFIG_DEBUG_FS +#include +#endif + +#include "loop_file_fmt.h" + +#ifdef CONFIG_DEBUG_DRIVER +#define ASSERT(x) \ +do { \ + if (!(x)) { \ + printk(KERN_EMERG "assertion failed %s: %d: %s\n", \ + __FILE__, __LINE__, #x); \ + BUG(); \ + } \ +} while (0) +#else +#define ASSERT(x) do { } while (0) +#endif + +#define KiB (1024) +#define MiB (1024 * 1024) + +#define QCOW_MAGIC (('Q' << 24) | ('F' << 16) | ('I' << 8) | 0xfb) + +#define QCOW_CRYPT_NONE 0 +#define QCOW_CRYPT_AES 1 +#define QCOW_CRYPT_LUKS 2 + +#define QCOW_MAX_CRYPT_CLUSTERS 32 +#define QCOW_MAX_SNAPSHOTS 65536 + +/* Field widths in QCOW mean normal cluster offsets cannot reach + * 64PB; depending on cluster size, compressed clusters can have a + * smaller limit (64PB for up to 16k clusters, then ramps down to + * 512TB for 2M clusters). */ +#define QCOW_MAX_CLUSTER_OFFSET ((1ULL << 56) - 1) + +/* 8 MB refcount table is enough for 2 PB images at 64k cluster size + * (128 GB for 512 byte clusters, 2 EB for 2 MB clusters) */ +#define QCOW_MAX_REFTABLE_SIZE (8 * MiB) + +/* 32 MB L1 table is enough for 2 PB images at 64k cluster size + * (128 GB for 512 byte clusters, 2 EB for 2 MB clusters) */ +#define QCOW_MAX_L1_SIZE (32 * MiB) + +/* Allow for an average of 1k per snapshot table entry, should be plenty of + * space for snapshot names and IDs */ +#define QCOW_MAX_SNAPSHOTS_SIZE (1024 * QCOW_MAX_SNAPSHOTS) + +/* Bitmap header extension constraints */ +#define QCOW_MAX_BITMAPS 65535 +#define QCOW_MAX_BITMAP_DIRECTORY_SIZE (1024 * QCOW_MAX_BITMAPS) + +/* indicate that the refcount of the referenced cluster is exactly one. */ +#define QCOW_OFLAG_COPIED (1ULL << 63) +/* indicate that the cluster is compressed (they never have the copied flag) */ +#define QCOW_OFLAG_COMPRESSED (1ULL << 62) +/* The cluster reads as all zeros */ +#define QCOW_OFLAG_ZERO (1ULL << 0) + +#define QCOW_MIN_CLUSTER_BITS 9 +#define QCOW_MAX_CLUSTER_BITS 21 + +/* Defined in the qcow2 spec (compressed cluster descriptor) */ +#define QCOW_COMPRESSED_SECTOR_SIZE 512U +#define QCOW_COMPRESSED_SECTOR_MASK (~(QCOW_COMPRESSED_SECTOR_SIZE - 1)) + +/* Must be at least 2 to cover COW */ +#define QCOW_MIN_L2_CACHE_SIZE 2 /* cache entries */ + +/* Must be at least 4 to cover all cases of refcount table growth */ +#define QCOW_MIN_REFCOUNT_CACHE_SIZE 4 /* clusters */ + +#define QCOW_DEFAULT_L2_CACHE_MAX_SIZE (32 * MiB) +#define QCOW_DEFAULT_CACHE_CLEAN_INTERVAL 600 /* seconds */ + +#define QCOW_DEFAULT_CLUSTER_SIZE 65536 + +/* Buffer size for debugfs file buffer to display QCOW header information */ +#define QCOW_HEADER_BUF_LEN 1024 + +/* Buffer size for debugfs file buffer to receive and display offset and + * cluster offset information */ +#define QCOW_OFFSET_BUF_LEN 32 +#define QCOW_CLUSTER_BUF_LEN 128 + +struct xloop_file_fmt_qcow_header { + u32 magic; + u32 version; + u64 backing_file_offset; + u32 backing_file_size; + u32 cluster_bits; + u64 size; /* in bytes */ + u32 crypt_method; + u32 l1_size; + u64 l1_table_offset; + u64 refcount_table_offset; + u32 refcount_table_clusters; + u32 nb_snapshots; + u64 snapshots_offset; + + /* The following fields are only valid for version >= 3 */ + u64 incompatible_features; + u64 compatible_features; + u64 autoclear_features; + + u32 refcount_order; + u32 header_length; +} __attribute__((packed)); + +struct xloop_file_fmt_qcow_snapshot_header { + /* header is 8 byte aligned */ + u64 l1_table_offset; + + u32 l1_size; + u16 id_str_size; + u16 name_size; + + u32 date_sec; + u32 date_nsec; + + u64 vm_clock_nsec; + + u32 vm_state_size; + /* for extension */ + u32 extra_data_size; + /* extra data follows */ + /* id_str follows */ + /* name follows */ +} __attribute__((packed)); + +enum { + QCOW_FEAT_TYPE_INCOMPATIBLE = 0, + QCOW_FEAT_TYPE_COMPATIBLE = 1, + QCOW_FEAT_TYPE_AUTOCLEAR = 2, +}; + +/* incompatible feature bits */ +enum { + QCOW_INCOMPAT_DIRTY_BITNR = 0, + QCOW_INCOMPAT_CORRUPT_BITNR = 1, + QCOW_INCOMPAT_DATA_FILE_BITNR = 2, + QCOW_INCOMPAT_DIRTY = 1 << QCOW_INCOMPAT_DIRTY_BITNR, + QCOW_INCOMPAT_CORRUPT = 1 << QCOW_INCOMPAT_CORRUPT_BITNR, + QCOW_INCOMPAT_DATA_FILE = 1 << QCOW_INCOMPAT_DATA_FILE_BITNR, + + QCOW_INCOMPAT_MASK = QCOW_INCOMPAT_DIRTY + | QCOW_INCOMPAT_CORRUPT + | QCOW_INCOMPAT_DATA_FILE, +}; + +/* compatible feature bits */ +enum { + QCOW_COMPAT_LAZY_REFCOUNTS_BITNR = 0, + QCOW_COMPAT_LAZY_REFCOUNTS = 1 << QCOW_COMPAT_LAZY_REFCOUNTS_BITNR, + + QCOW_COMPAT_FEAT_MASK = QCOW_COMPAT_LAZY_REFCOUNTS, +}; + +/* autoclear feature bits */ +enum { + QCOW_AUTOCLEAR_BITMAPS_BITNR = 0, + QCOW_AUTOCLEAR_DATA_FILE_RAW_BITNR = 1, + QCOW_AUTOCLEAR_BITMAPS = 1 << QCOW_AUTOCLEAR_BITMAPS_BITNR, + QCOW_AUTOCLEAR_DATA_FILE_RAW = 1 << QCOW_AUTOCLEAR_DATA_FILE_RAW_BITNR, + + QCOW_AUTOCLEAR_MASK = QCOW_AUTOCLEAR_BITMAPS | + QCOW_AUTOCLEAR_DATA_FILE_RAW, +}; + +struct xloop_file_fmt_qcow_data { + u64 size; + int cluster_bits; + int cluster_size; + int cluster_sectors; + int l2_slice_size; + int l2_bits; + int l2_size; + int l1_size; + int l1_vm_state_index; + int refcount_block_bits; + int refcount_block_size; + int csize_shift; + int csize_mask; + u64 cluster_offset_mask; + u64 l1_table_offset; + u64 *l1_table; + + struct xloop_file_fmt_qcow_cache *l2_table_cache; + struct xloop_file_fmt_qcow_cache *refcount_block_cache; + + u64 *refcount_table; + u64 refcount_table_offset; + u32 refcount_table_size; + u32 max_refcount_table_index; /* Last used entry in refcount_table */ + u64 free_cluster_index; + u64 free_byte_offset; + + u32 crypt_method_header; + u64 snapshots_offset; + int snapshots_size; + unsigned int nb_snapshots; + + u32 nb_bitmaps; + u64 bitmap_directory_size; + u64 bitmap_directory_offset; + + int qcow_version; + bool use_lazy_refcounts; + int refcount_order; + int refcount_bits; + u64 refcount_max; + + u64 incompatible_features; + u64 compatible_features; + u64 autoclear_features; + + struct z_stream_s *strm; + u8 *cmp_out_buf; + u64 cmp_last_coffset; + + /* debugfs entries */ +#ifdef CONFIG_DEBUG_FS + struct dentry *dbgfs_dir; + struct dentry *dbgfs_file_qcow_header; + char dbgfs_file_qcow_header_buf[QCOW_HEADER_BUF_LEN]; + struct dentry *dbgfs_file_qcow_offset; + char dbgfs_file_qcow_offset_buf[QCOW_OFFSET_BUF_LEN]; + char dbgfs_file_qcow_cluster_buf[QCOW_CLUSTER_BUF_LEN]; + u64 dbgfs_qcow_offset; + struct mutex dbgfs_qcow_offset_mutex; +#endif +}; + +struct xloop_file_fmt_qcow_cow_region { + /** + * Offset of the COW region in bytes from the start of the first + * cluster touched by the request. + */ + unsigned offset; + + /** Number of bytes to copy */ + unsigned nb_bytes; +}; + +enum xloop_file_fmt_qcow_cluster_type { + QCOW_CLUSTER_UNALLOCATED, + QCOW_CLUSTER_ZERO_PLAIN, + QCOW_CLUSTER_ZERO_ALLOC, + QCOW_CLUSTER_NORMAL, + QCOW_CLUSTER_COMPRESSED, +}; + +enum xloop_file_fmt_qcow_metadata_overlap { + QCOW_OL_MAIN_HEADER_BITNR = 0, + QCOW_OL_ACTIVE_L1_BITNR = 1, + QCOW_OL_ACTIVE_L2_BITNR = 2, + QCOW_OL_REFCOUNT_TABLE_BITNR = 3, + QCOW_OL_REFCOUNT_BLOCK_BITNR = 4, + QCOW_OL_SNAPSHOT_TABLE_BITNR = 5, + QCOW_OL_INACTIVE_L1_BITNR = 6, + QCOW_OL_INACTIVE_L2_BITNR = 7, + QCOW_OL_BITMAP_DIRECTORY_BITNR = 8, + + QCOW_OL_MAX_BITNR = 9, + + QCOW_OL_NONE = 0, + QCOW_OL_MAIN_HEADER = (1 << QCOW_OL_MAIN_HEADER_BITNR), + QCOW_OL_ACTIVE_L1 = (1 << QCOW_OL_ACTIVE_L1_BITNR), + QCOW_OL_ACTIVE_L2 = (1 << QCOW_OL_ACTIVE_L2_BITNR), + QCOW_OL_REFCOUNT_TABLE = (1 << QCOW_OL_REFCOUNT_TABLE_BITNR), + QCOW_OL_REFCOUNT_BLOCK = (1 << QCOW_OL_REFCOUNT_BLOCK_BITNR), + QCOW_OL_SNAPSHOT_TABLE = (1 << QCOW_OL_SNAPSHOT_TABLE_BITNR), + QCOW_OL_INACTIVE_L1 = (1 << QCOW_OL_INACTIVE_L1_BITNR), + /* NOTE: Checking overlaps with inactive L2 tables will result in bdrv + * reads. */ + QCOW_OL_INACTIVE_L2 = (1 << QCOW_OL_INACTIVE_L2_BITNR), + QCOW_OL_BITMAP_DIRECTORY = (1 << QCOW_OL_BITMAP_DIRECTORY_BITNR), +}; + +/* Perform all overlap checks which can be done in constant time */ +#define QCOW_OL_CONSTANT \ + (QCOW_OL_MAIN_HEADER | QCOW_OL_ACTIVE_L1 | QCOW_OL_REFCOUNT_TABLE | \ + QCOW_OL_SNAPSHOT_TABLE | QCOW_OL_BITMAP_DIRECTORY) + +/* Perform all overlap checks which don't require disk access */ +#define QCOW_OL_CACHED \ + (QCOW_OL_CONSTANT | QCOW_OL_ACTIVE_L2 | QCOW_OL_REFCOUNT_BLOCK | \ + QCOW_OL_INACTIVE_L1) + +/* Perform all overlap checks */ +#define QCOW_OL_ALL \ + (QCOW_OL_CACHED | QCOW_OL_INACTIVE_L2) + +#define L1E_OFFSET_MASK 0x00fffffffffffe00ULL +#define L2E_OFFSET_MASK 0x00fffffffffffe00ULL +#define L2E_COMPRESSED_OFFSET_SIZE_MASK 0x3fffffffffffffffULL + +#define REFT_OFFSET_MASK 0xfffffffffffffe00ULL + +#define INV_OFFSET (-1ULL) + +static inline bool xloop_file_fmt_qcow_has_data_file( + struct xloop_file_fmt *xlo_fmt) +{ + /* At the moment, there is no support for copy on write! */ + return false; +} + +static inline bool xloop_file_fmt_qcow_data_file_is_raw( + struct xloop_file_fmt *xlo_fmt) +{ + struct xloop_file_fmt_qcow_data *qcow_data = xlo_fmt->private_data; + return !!(qcow_data->autoclear_features & + QCOW_AUTOCLEAR_DATA_FILE_RAW); +} + +static inline s64 xloop_file_fmt_qcow_start_of_cluster( + struct xloop_file_fmt_qcow_data *qcow_data, s64 offset) +{ + return offset & ~(qcow_data->cluster_size - 1); +} + +static inline s64 xloop_file_fmt_qcow_offset_into_cluster( + struct xloop_file_fmt_qcow_data *qcow_data, s64 offset) +{ + return offset & (qcow_data->cluster_size - 1); +} + +static inline s64 xloop_file_fmt_qcow_size_to_clusters( + struct xloop_file_fmt_qcow_data *qcow_data, u64 size) +{ + return (size + (qcow_data->cluster_size - 1)) >> + qcow_data->cluster_bits; +} + +static inline s64 xloop_file_fmt_qcow_size_to_l1( + struct xloop_file_fmt_qcow_data *qcow_data, s64 size) +{ + int shift = qcow_data->cluster_bits + qcow_data->l2_bits; + return (size + (1ULL << shift) - 1) >> shift; +} + +static inline int xloop_file_fmt_qcow_offset_to_l1_index( + struct xloop_file_fmt_qcow_data *qcow_data, u64 offset) +{ + return offset >> (qcow_data->l2_bits + qcow_data->cluster_bits); +} + +static inline int xloop_file_fmt_qcow_offset_to_l2_index( + struct xloop_file_fmt_qcow_data *qcow_data, s64 offset) +{ + return (offset >> qcow_data->cluster_bits) & (qcow_data->l2_size - 1); +} + +static inline int xloop_file_fmt_qcow_offset_to_l2_slice_index( + struct xloop_file_fmt_qcow_data *qcow_data, s64 offset) +{ + return (offset >> qcow_data->cluster_bits) & + (qcow_data->l2_slice_size - 1); +} + +static inline s64 xloop_file_fmt_qcow_vm_state_offset( + struct xloop_file_fmt_qcow_data *qcow_data) +{ + return (s64)qcow_data->l1_vm_state_index << + (qcow_data->cluster_bits + qcow_data->l2_bits); +} + +static inline enum xloop_file_fmt_qcow_cluster_type +xloop_file_fmt_qcow_get_cluster_type(struct xloop_file_fmt *xlo_fmt, u64 l2_entry) +{ + if (l2_entry & QCOW_OFLAG_COMPRESSED) { + return QCOW_CLUSTER_COMPRESSED; + } else if (l2_entry & QCOW_OFLAG_ZERO) { + if (l2_entry & L2E_OFFSET_MASK) { + return QCOW_CLUSTER_ZERO_ALLOC; + } + return QCOW_CLUSTER_ZERO_PLAIN; + } else if (!(l2_entry & L2E_OFFSET_MASK)) { + /* Offset 0 generally means unallocated, but it is ambiguous + * with external data files because 0 is a valid offset there. + * However, all clusters in external data files always have + * refcount 1, so we can rely on QCOW_OFLAG_COPIED to + * disambiguate. */ + if (xloop_file_fmt_qcow_has_data_file(xlo_fmt) && + (l2_entry & QCOW_OFLAG_COPIED)) { + return QCOW_CLUSTER_NORMAL; + } else { + return QCOW_CLUSTER_UNALLOCATED; + } + } else { + return QCOW_CLUSTER_NORMAL; + } +} + +#endif diff --git a/kernel/loop_file_fmt_raw.c b/kernel/loop_file_fmt_raw.c new file mode 100644 index 0000000..11cc8cd --- /dev/null +++ b/kernel/loop_file_fmt_raw.c @@ -0,0 +1,465 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * xloop_file_fmt_raw.c + * + * RAW file format driver for the xloop device module. + * + * Copyright (C) 2019 Manuel Bentele + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "loop_file_fmt.h" + +static inline loff_t __raw_file_fmt_rq_get_pos(struct xloop_file_fmt *xlo_fmt, + struct request *rq) +{ + struct xloop_device *xlo = xloop_file_fmt_get_xlo(xlo_fmt); + return ((loff_t) blk_rq_pos(rq) << 9) + xlo->xlo_offset; +} + +/* transfer function for DEPRECATED cryptoxloop support */ +static inline int __raw_file_fmt_do_transfer(struct xloop_device *xlo, int cmd, + struct page *rpage, unsigned roffs, + struct page *lpage, unsigned loffs, + int size, sector_t rblock) +{ + int ret; + + ret = xlo->transfer(xlo, cmd, rpage, roffs, lpage, loffs, size, rblock); + if (likely(!ret)) + return 0; + + printk_ratelimited(KERN_ERR + "xloop_file_fmt_raw: Transfer error at byte offset %llu, length %i.\n", + (unsigned long long)rblock << 9, size); + return ret; +} + +static int __raw_file_fmt_read_transfer(struct xloop_device *xlo, + struct request *rq, loff_t pos) +{ + struct bio_vec bvec, b; + struct req_iterator iter; + struct iov_iter i; + struct page *page; + ssize_t len; + int ret = 0; + + page = alloc_page(GFP_NOIO); + if (unlikely(!page)) + return -ENOMEM; + + rq_for_each_segment(bvec, rq, iter) { + loff_t offset = pos; + + b.bv_page = page; + b.bv_offset = 0; + b.bv_len = bvec.bv_len; + + iov_iter_bvec(&i, READ, &b, 1, b.bv_len); + len = vfs_iter_read(xlo->xlo_backing_file, &i, &pos, 0); + if (len < 0) { + ret = len; + goto out_free_page; + } + + ret = __raw_file_fmt_do_transfer(xlo, READ, page, 0, bvec.bv_page, + bvec.bv_offset, len, offset >> 9); + if (ret) + goto out_free_page; + + flush_dcache_page(bvec.bv_page); + + if (len != bvec.bv_len) { + struct bio *bio; + + __rq_for_each_bio(bio, rq) + zero_fill_bio(bio); + break; + } + } + + ret = 0; +out_free_page: + __free_page(page); + return ret; +} + +static int raw_file_fmt_read(struct xloop_file_fmt *xlo_fmt, + struct request *rq) +{ + struct bio_vec bvec; + struct req_iterator iter; + struct iov_iter i; + ssize_t len; + struct xloop_device *xlo; + loff_t pos; + + xlo = xloop_file_fmt_get_xlo(xlo_fmt); + pos = __raw_file_fmt_rq_get_pos(xlo_fmt, rq); + + if (xlo->transfer) + return __raw_file_fmt_read_transfer(xlo, rq, pos); + + rq_for_each_segment(bvec, rq, iter) { + iov_iter_bvec(&i, READ, &bvec, 1, bvec.bv_len); + len = vfs_iter_read(xlo->xlo_backing_file, &i, &pos, 0); + if (len < 0) + return len; + + flush_dcache_page(bvec.bv_page); + + if (len != bvec.bv_len) { + struct bio *bio; + + __rq_for_each_bio(bio, rq) + zero_fill_bio(bio); + break; + } + cond_resched(); + } + + return 0; +} + +static void __raw_file_fmt_rw_aio_do_completion(struct xloop_cmd *cmd) +{ + struct request *rq = blk_mq_rq_from_pdu(cmd); + + if (!atomic_dec_and_test(&cmd->ref)) + return; + kfree(cmd->bvec); + cmd->bvec = NULL; +#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 9, 0) + if (likely(!blk_should_fake_timeout(rq->q))) + blk_mq_complete_request(rq); +#else + blk_mq_complete_request(rq); +#endif +} + +static void __raw_file_fmt_rw_aio_complete(struct kiocb *iocb, long ret, long ret2) +{ + struct xloop_cmd *cmd = container_of(iocb, struct xloop_cmd, iocb); + + if (cmd->css) + css_put(cmd->css); + cmd->ret = ret; + __raw_file_fmt_rw_aio_do_completion(cmd); +} + +static int __raw_file_fmt_rw_aio(struct xloop_device *xlo, + struct xloop_cmd *cmd, loff_t pos, bool rw) +{ + struct iov_iter iter; + struct req_iterator rq_iter; + struct bio_vec *bvec; + struct request *rq = blk_mq_rq_from_pdu(cmd); + struct bio *bio = rq->bio; + struct file *file = xlo->xlo_backing_file; + struct bio_vec tmp; + unsigned int offset; + int nr_bvec = 0; + int ret; + + rq_for_each_bvec(tmp, rq, rq_iter) + nr_bvec++; + + if (rq->bio != rq->biotail) { + + bvec = kmalloc_array(nr_bvec, sizeof(struct bio_vec), + GFP_NOIO); + if (!bvec) + return -EIO; + cmd->bvec = bvec; + + /* + * The bios of the request may be started from the middle of + * the 'bvec' because of bio splitting, so we can't directly + * copy bio->bi_iov_vec to new bvec. The rq_for_each_bvec + * API will take care of all details for us. + */ + rq_for_each_bvec(tmp, rq, rq_iter) { + *bvec = tmp; + bvec++; + } + bvec = cmd->bvec; + offset = 0; + } else { + /* + * Same here, this bio may be started from the middle of the + * 'bvec' because of bio splitting, so offset from the bvec + * must be passed to iov iterator + */ + offset = bio->bi_iter.bi_bvec_done; + bvec = __bvec_iter_bvec(bio->bi_io_vec, bio->bi_iter); + } + atomic_set(&cmd->ref, 2); + + iov_iter_bvec(&iter, rw, bvec, nr_bvec, blk_rq_bytes(rq)); + iter.iov_offset = offset; + + cmd->iocb.ki_pos = pos; + cmd->iocb.ki_filp = file; + cmd->iocb.ki_complete = __raw_file_fmt_rw_aio_complete; + cmd->iocb.ki_flags = IOCB_DIRECT; + cmd->iocb.ki_ioprio = IOPRIO_PRIO_VALUE(IOPRIO_CLASS_NONE, 0); + if (cmd->css) + kthread_associate_blkcg(cmd->css); + + if (rw == WRITE) + ret = call_write_iter(file, &cmd->iocb, &iter); + else + ret = call_read_iter(file, &cmd->iocb, &iter); + + __raw_file_fmt_rw_aio_do_completion(cmd); + kthread_associate_blkcg(NULL); + + if (ret != -EIOCBQUEUED) + cmd->iocb.ki_complete(&cmd->iocb, ret, 0); + return 0; +} + +static int raw_file_fmt_read_aio(struct xloop_file_fmt *xlo_fmt, + struct request *rq) +{ + struct xloop_device *xlo = xloop_file_fmt_get_xlo(xlo_fmt); + struct xloop_cmd *cmd = blk_mq_rq_to_pdu(rq); + loff_t pos = __raw_file_fmt_rq_get_pos(xlo_fmt, rq); + + return __raw_file_fmt_rw_aio(xlo, cmd, pos, READ); +} + +static int __raw_file_fmt_write_bvec(struct file *file, + struct bio_vec *bvec, + loff_t *ppos) +{ + struct iov_iter i; + ssize_t bw; + + iov_iter_bvec(&i, WRITE, bvec, 1, bvec->bv_len); + + file_start_write(file); + bw = vfs_iter_write(file, &i, ppos, 0); + file_end_write(file); + + if (likely(bw == bvec->bv_len)) + return 0; + + printk_ratelimited(KERN_ERR + "xloop_file_fmt_raw: Write error at byte offset %llu, length " + "%i.\n", (unsigned long long)*ppos, bvec->bv_len); + if (bw >= 0) + bw = -EIO; + return bw; +} + +/* + * This is the slow, transforming version that needs to double buffer the + * data as it cannot do the transformations in place without having direct + * access to the destination pages of the backing file. + */ +static int __raw_file_fmt_write_transfer(struct xloop_device *xlo, + struct request *rq, loff_t pos) +{ +struct bio_vec bvec, b; + struct req_iterator iter; + struct page *page; + int ret = 0; + + page = alloc_page(GFP_NOIO); + if (unlikely(!page)) + return -ENOMEM; + + rq_for_each_segment(bvec, rq, iter) { + ret = __raw_file_fmt_do_transfer(xlo, WRITE, page, 0, bvec.bv_page, + bvec.bv_offset, bvec.bv_len, pos >> 9); + if (unlikely(ret)) + break; + + b.bv_page = page; + b.bv_offset = 0; + b.bv_len = bvec.bv_len; + ret = __raw_file_fmt_write_bvec(xlo->xlo_backing_file, &b, &pos); + if (ret < 0) + break; + } + + __free_page(page); + return ret; +} + +static int raw_file_fmt_write(struct xloop_file_fmt *xlo_fmt, + struct request *rq) +{ + struct bio_vec bvec; + struct req_iterator iter; + int ret = 0; + struct xloop_device *xlo; + loff_t pos; + + xlo = xloop_file_fmt_get_xlo(xlo_fmt); + pos = __raw_file_fmt_rq_get_pos(xlo_fmt, rq); + + if (xlo->transfer) + return __raw_file_fmt_write_transfer(xlo, rq, pos); + + rq_for_each_segment(bvec, rq, iter) { + ret = __raw_file_fmt_write_bvec(xlo->xlo_backing_file, &bvec, &pos); + if (ret < 0) + break; + cond_resched(); + } + + return ret; +} + +static int raw_file_fmt_write_aio(struct xloop_file_fmt *xlo_fmt, + struct request *rq) +{ + struct xloop_device *xlo = xloop_file_fmt_get_xlo(xlo_fmt); + struct xloop_cmd *cmd = blk_mq_rq_to_pdu(rq); + loff_t pos = __raw_file_fmt_rq_get_pos(xlo_fmt, rq); + + return __raw_file_fmt_rw_aio(xlo, cmd, pos, WRITE); +} + +static int __raw_file_fmt_fallocate(struct xloop_device *xlo, + struct request *rq, loff_t pos, int mode) +{ + /* + * We use fallocate to manipulate the space mappings used by the image + * a.k.a. discard/zerorange. However we do not support this if + * encryption is enabled, because it may give an attacker useful + * information. + */ + struct file *file = xlo->xlo_backing_file; + struct request_queue *q = xlo->xlo_queue; + int ret; + + mode |= FALLOC_FL_KEEP_SIZE; + + if (!blk_queue_discard(q)) { + ret = -EOPNOTSUPP; + goto out; + } + + ret = file->f_op->fallocate(file, mode, pos, blk_rq_bytes(rq)); + if (unlikely(ret && ret != -EINVAL && ret != -EOPNOTSUPP)) + ret = -EIO; +out: + return ret; +} + +static int raw_file_fmt_write_zeros(struct xloop_file_fmt *xlo_fmt, + struct request *rq) +{ + loff_t pos = __raw_file_fmt_rq_get_pos(xlo_fmt, rq); + struct xloop_device *xlo = xloop_file_fmt_get_xlo(xlo_fmt); + + /* + * If the caller doesn't want deallocation, call zeroout to + * write zeroes the range. Otherwise, punch them out. + */ + return __raw_file_fmt_fallocate(xlo, rq, pos, + (rq->cmd_flags & REQ_NOUNMAP) ? + FALLOC_FL_ZERO_RANGE : + FALLOC_FL_PUNCH_HOLE); +} + +static int raw_file_fmt_discard(struct xloop_file_fmt *xlo_fmt, + struct request *rq) +{ + loff_t pos = __raw_file_fmt_rq_get_pos(xlo_fmt, rq); + struct xloop_device *xlo = xloop_file_fmt_get_xlo(xlo_fmt); + + return __raw_file_fmt_fallocate(xlo, rq, pos, FALLOC_FL_PUNCH_HOLE); +} + +static int raw_file_fmt_flush(struct xloop_file_fmt *xlo_fmt) +{ + struct xloop_device *xlo = xloop_file_fmt_get_xlo(xlo_fmt); + struct file *file = xlo->xlo_backing_file; + int ret = vfs_fsync(file, 0); + if (unlikely(ret && ret != -EINVAL)) + ret = -EIO; + + return ret; +} + +static loff_t raw_file_fmt_sector_size(struct xloop_file_fmt *xlo_fmt, + struct file *file, loff_t offset, loff_t sizelimit) +{ + loff_t xloopsize; + + /* Compute xloopsize in bytes */ + xloopsize = i_size_read(file->f_mapping->host); + if (offset > 0) + xloopsize -= offset; + /* offset is beyond i_size, weird but possible */ + if (xloopsize < 0) + return 0; + + if (sizelimit > 0 && sizelimit < xloopsize) + xloopsize = sizelimit; + /* + * Unfortunately, if we want to do I/O on the device, + * the number of 512-byte sectors has to fit into a sector_t. + */ + return xloopsize >> 9; +} + +static struct xloop_file_fmt_ops raw_file_fmt_ops = { + .init = NULL, + .exit = NULL, + .read = raw_file_fmt_read, + .write = raw_file_fmt_write, + .read_aio = raw_file_fmt_read_aio, + .write_aio = raw_file_fmt_write_aio, + .write_zeros = raw_file_fmt_write_zeros, + .discard = raw_file_fmt_discard, + .flush = raw_file_fmt_flush, + .sector_size = raw_file_fmt_sector_size, +}; + +static struct xloop_file_fmt_driver raw_file_fmt_driver = { + .name = "RAW", + .file_fmt_type = XLO_FILE_FMT_RAW, + .ops = &raw_file_fmt_ops, + .owner = THIS_MODULE, +}; + +static int __init xloop_file_fmt_raw_init(void) +{ + printk(KERN_INFO "xloop_file_fmt_raw: init xloop device RAW file format " + "driver"); + return xloop_file_fmt_register_driver(&raw_file_fmt_driver); +} + +static void __exit xloop_file_fmt_raw_exit(void) +{ + printk(KERN_INFO "xloop_file_fmt_raw: exit xloop device RAW file format " + "driver"); + xloop_file_fmt_unregister_driver(&raw_file_fmt_driver); +} + +module_init(xloop_file_fmt_raw_init); +module_exit(xloop_file_fmt_raw_exit); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Manuel Bentele "); +MODULE_DESCRIPTION("xloop device RAW file format driver"); +MODULE_SOFTDEP("pre: xloop"); diff --git a/kernel/loop_main.c b/kernel/loop_main.c new file mode 100644 index 0000000..9cd9c99 --- /dev/null +++ b/kernel/loop_main.c @@ -0,0 +1,2221 @@ +/* + * loop_main.c + * + * Written by Theodore Ts'o, 3/29/93 + * + * Copyright 1993 by Theodore Ts'o. Redistribution of this file is + * permitted under the GNU General Public License. + * + * DES encryption plus some minor changes by Werner Almesberger, 30-MAY-1993 + * more DES encryption plus IDEA encryption by Nicholas J. Leon, June 20, 1996 + * + * Modularized and updated for 1.1.16 kernel - Mitch Dsouza 28th May 1994 + * Adapted for 1.3.59 kernel - Andries Brouwer, 1 Feb 1996 + * + * Fixed do_xloop_request() re-entrancy - Vincent.Renardias@waw.com Mar 20, 1997 + * + * Added devfs support - Richard Gooch 16-Jan-1998 + * + * Handle sparse backing files correctly - Kenn Humborg, Jun 28, 1998 + * + * Loadable modules and other fixes by AK, 1998 + * + * Make real block number available to downstream transfer functions, enables + * CBC (and relatives) mode encryption requiring unique IVs per data block. + * Reed H. Petty, rhp@draper.net + * + * Maximum number of xloop devices now dynamic via max_xloop module parameter. + * Russell Kroll 19990701 + * + * Maximum number of xloop devices when compiled-in now selectable by passing + * max_xloop=<1-255> to the kernel on boot. + * Erik I. Bolsø, , Oct 31, 1999 + * + * Completely rewrite request handling to be make_request_fn style and + * non blocking, pushing work to a helper thread. Lots of fixes from + * Al Viro too. + * Jens Axboe , Nov 2000 + * + * Support up to 256 xloop devices + * Heinz Mauelshagen , Feb 2002 + * + * Support for falling back on the write file operation when the address space + * operations write_begin is not available on the backing filesystem. + * Anton Altaparmakov, 16 Feb 2005 + * + * Support for using file formats. + * Manuel Bentele , 2019 + * + * Still To Fix: + * - Advisory locking is ignored here. + * - Should use an own CAP_* category instead of CAP_SYS_ADMIN + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#ifdef CONFIG_DEBUG_FS +#include +#endif + +#include "loop_file_fmt.h" +#include "loop_main.h" + +#include + +static DEFINE_IDR(xloop_index_idr); +static DEFINE_MUTEX(xloop_ctl_mutex); + +static int max_part; +static int part_shift; + +static int transfer_xor(struct xloop_device *xlo, int cmd, + struct page *raw_page, unsigned raw_off, + struct page *xloop_page, unsigned xloop_off, + int size, sector_t real_block) +{ + char *raw_buf = kmap_atomic(raw_page) + raw_off; + char *xloop_buf = kmap_atomic(xloop_page) + xloop_off; + char *in, *out, *key; + int i, keysize; + + if (cmd == READ) { + in = raw_buf; + out = xloop_buf; + } else { + in = xloop_buf; + out = raw_buf; + } + + key = xlo->xlo_encrypt_key; + keysize = xlo->xlo_encrypt_key_size; + for (i = 0; i < size; i++) + *out++ = *in++ ^ key[(i & 511) % keysize]; + + kunmap_atomic(xloop_buf); + kunmap_atomic(raw_buf); + cond_resched(); + return 0; +} + +static int xor_init(struct xloop_device *xlo, const struct xloop_info64 *info) +{ + if (unlikely(info->xlo_encrypt_key_size <= 0)) + return -EINVAL; + return 0; +} + +static struct xloop_func_table none_funcs = { + .number = XLO_CRYPT_NONE, +}; + +static struct xloop_func_table xor_funcs = { + .number = XLO_CRYPT_XOR, + .transfer = transfer_xor, + .init = xor_init +}; + +/* xfer_funcs[0] is special - its release function is never called */ +static struct xloop_func_table *xfer_funcs[MAX_XLO_CRYPT] = { + &none_funcs, + &xor_funcs +}; + +static loff_t get_xloop_size(struct xloop_device *xlo, struct file *file) +{ + return xloop_file_fmt_sector_size(xlo->xlo_fmt, file, xlo->xlo_offset, + xlo->xlo_sizelimit); +} + +static void __xloop_update_dio(struct xloop_device *xlo, bool dio) +{ + struct file *file = xlo->xlo_backing_file; + struct address_space *mapping = file->f_mapping; + struct inode *inode = mapping->host; + unsigned short sb_bsize = 0; + unsigned dio_align = 0; + bool use_dio; + + if (inode->i_sb->s_bdev) { + sb_bsize = bdev_logical_block_size(inode->i_sb->s_bdev); + dio_align = sb_bsize - 1; + } + + /* + * We support direct I/O only if xlo_offset is aligned with the + * logical I/O size of backing device, and the logical block + * size of xloop is bigger than the backing device's and the xloop + * needn't transform transfer. + * + * TODO: the above condition may be loosed in the future, and + * direct I/O may be switched runtime at that time because most + * of requests in sane applications should be PAGE_SIZE aligned + */ + if (dio) { + if (queue_logical_block_size(xlo->xlo_queue) >= sb_bsize && + !(xlo->xlo_offset & dio_align) && + mapping->a_ops->direct_IO && + !xlo->transfer) + use_dio = true; + else + use_dio = false; + } else { + use_dio = false; + } + + if (xlo->use_dio == use_dio) + return; + + /* flush dirty pages before changing direct IO */ + xloop_file_fmt_flush(xlo->xlo_fmt); + + /* + * The flag of XLO_FLAGS_DIRECT_IO is handled similarly with + * XLO_FLAGS_READ_ONLY, both are set from kernel, and losetup + * will get updated by ioctl(XLOOP_GET_STATUS) + */ + if (xlo->xlo_state == Xlo_bound) + blk_mq_freeze_queue(xlo->xlo_queue); + xlo->use_dio = use_dio; + if (use_dio) { + blk_queue_flag_clear(QUEUE_FLAG_NOMERGES, xlo->xlo_queue); + xlo->xlo_flags |= XLO_FLAGS_DIRECT_IO; + } else { + blk_queue_flag_set(QUEUE_FLAG_NOMERGES, xlo->xlo_queue); + xlo->xlo_flags &= ~XLO_FLAGS_DIRECT_IO; + } + if (xlo->xlo_state == Xlo_bound) + blk_mq_unfreeze_queue(xlo->xlo_queue); +} + +/** + * xloop_validate_block_size() - validates the passed in block size + * @bsize: size to validate + */ +static int +xloop_validate_block_size(unsigned short bsize) +{ + if (bsize < 512 || bsize > PAGE_SIZE || !is_power_of_2(bsize)) + return -EINVAL; + + return 0; +} + +/** + * xloop_set_size() - sets device size and notifies userspace + * @xlo: struct xloop_device to set the size for + * @size: new size of the xloop device + * + * Callers must validate that the size passed into this function fits into + * a sector_t, eg using xloop_validate_size() + */ +static void xloop_set_size(struct xloop_device *xlo, loff_t size) +{ + struct block_device *bdev = xlo->xlo_device; +#if LINUX_VERSION_CODE < KERNEL_VERSION(5, 7, 0) + sector_t capacity; +#endif + + bd_set_size(bdev, size << SECTOR_SHIFT); + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 7, 0) + set_capacity_revalidate_and_notify(xlo->xlo_disk, size, false); +#else + capacity = get_capacity(xlo->xlo_disk); + set_capacity(xlo->xlo_disk, size); + if (capacity != size && capacity != 0 && size != 0) { + char *envp[] = { "RESIZE=1", NULL }; + kobject_uevent_env(&disk_to_dev(xlo->xlo_disk)->kobj, KOBJ_CHANGE, + envp); + } +#endif +} + +static void xlo_complete_rq(struct request *rq) +{ + struct xloop_cmd *cmd = blk_mq_rq_to_pdu(rq); + blk_status_t ret = BLK_STS_OK; + + if (!cmd->use_aio || cmd->ret < 0 || cmd->ret == blk_rq_bytes(rq) || + req_op(rq) != REQ_OP_READ) { + if (cmd->ret < 0) + ret = errno_to_blk_status(cmd->ret); + goto end_io; + } + + /* + * Short READ - if we got some data, advance our request and + * retry it. If we got no data, end the rest with EIO. + */ + if (cmd->ret) { + blk_update_request(rq, BLK_STS_OK, cmd->ret); + cmd->ret = 0; + blk_mq_requeue_request(rq, true); + } else { + if (cmd->use_aio) { + struct bio *bio = rq->bio; + + while (bio) { + zero_fill_bio(bio); + bio = bio->bi_next; + } + } + ret = BLK_STS_IOERR; +end_io: + blk_mq_end_request(rq, ret); + } +} + +static int do_req_filebacked(struct xloop_device *xlo, struct request *rq) +{ + struct xloop_cmd *cmd = blk_mq_rq_to_pdu(rq); + + /* + * xlo_write_simple and xlo_read_simple should have been covered + * by io submit style function like xlo_rw_aio(), one blocker + * is that xlo_read_simple() need to call flush_dcache_page after + * the page is written from kernel, and it isn't easy to handle + * this in io submit style function which submits all segments + * of the req at one time. And direct read IO doesn't need to + * run flush_dcache_page(). + */ + switch (req_op(rq)) { + case REQ_OP_FLUSH: + return xloop_file_fmt_flush(xlo->xlo_fmt); + case REQ_OP_WRITE_ZEROES: + return xloop_file_fmt_write_zeros(xlo->xlo_fmt, rq); + case REQ_OP_DISCARD: + return xloop_file_fmt_discard(xlo->xlo_fmt, rq); + case REQ_OP_WRITE: + if (cmd->use_aio) + return xloop_file_fmt_write_aio(xlo->xlo_fmt, rq); + else + return xloop_file_fmt_write(xlo->xlo_fmt, rq); + case REQ_OP_READ: + if (cmd->use_aio) + return xloop_file_fmt_read_aio(xlo->xlo_fmt, rq); + else + return xloop_file_fmt_read(xlo->xlo_fmt, rq); + default: + WARN_ON_ONCE(1); + return -EIO; + } +} + +static inline void xloop_update_dio(struct xloop_device *xlo) +{ + __xloop_update_dio(xlo, (xlo->xlo_backing_file->f_flags & O_DIRECT) | + xlo->use_dio); +} + +static void xloop_reread_partitions(struct xloop_device *xlo, + struct block_device *bdev) +{ + int rc; + + mutex_lock(&bdev->bd_mutex); +#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 5, 0) + rc = bdev_disk_changed(bdev, false); +#else + rc = blkdev_reread_part(bdev); +#endif + mutex_unlock(&bdev->bd_mutex); + if (rc) + pr_warn("%s: partition scan of xloop%d (%s) failed (rc=%d)\n", + __func__, xlo->xlo_number, xlo->xlo_file_name, rc); +} + +static inline int is_xloop_device(struct file *file) +{ + struct inode *i = file->f_mapping->host; + + return i && S_ISBLK(i->i_mode) && MAJOR(i->i_rdev) == LOOP_MAJOR; +} + +static int xloop_validate_file(struct file *file, struct block_device *bdev) +{ + struct inode *inode = file->f_mapping->host; + struct file *f = file; + + /* Avoid recursion */ + while (is_xloop_device(f)) { + struct xloop_device *l; + + if (f->f_mapping->host->i_bdev == bdev) + return -EBADF; + + l = f->f_mapping->host->i_bdev->bd_disk->private_data; + if (l->xlo_state != Xlo_bound) { + return -EINVAL; + } + f = l->xlo_backing_file; + } + if (!S_ISREG(inode->i_mode) && !S_ISBLK(inode->i_mode)) + return -EINVAL; + return 0; +} + +/* + * xloop_change_fd switched the backing store of a xloopback device to + * a new file. This is useful for operating system installers to free up + * the original file and in High Availability environments to switch to + * an alternative location for the content in case of server meltdown. + * This can only work if the xloop device is used read-only, and if the + * new backing store is the same size and type as the old backing store. + */ +static int xloop_change_fd(struct xloop_device *xlo, struct block_device *bdev, + unsigned int arg) +{ + struct file *file = NULL, *old_file; + int error; + bool partscan; + + error = mutex_lock_killable(&xloop_ctl_mutex); + if (error) + return error; + error = -ENXIO; + if (xlo->xlo_state != Xlo_bound) + goto out_err; + + /* the xloop device has to be read-only */ + error = -EINVAL; + if (!(xlo->xlo_flags & XLO_FLAGS_READ_ONLY)) + goto out_err; + + error = -EBADF; + file = fget(arg); + if (!file) + goto out_err; + + error = xloop_validate_file(file, bdev); + if (error) + goto out_err; + + old_file = xlo->xlo_backing_file; + + error = -EINVAL; + + /* size of the new backing store needs to be the same */ + if (get_xloop_size(xlo, file) != get_xloop_size(xlo, old_file)) + goto out_err; + + /* and ... switch */ + blk_mq_freeze_queue(xlo->xlo_queue); + mapping_set_gfp_mask(old_file->f_mapping, xlo->old_gfp_mask); + xlo->xlo_backing_file = file; + xlo->old_gfp_mask = mapping_gfp_mask(file->f_mapping); + mapping_set_gfp_mask(file->f_mapping, + xlo->old_gfp_mask & ~(__GFP_IO|__GFP_FS)); + xloop_update_dio(xlo); + blk_mq_unfreeze_queue(xlo->xlo_queue); + partscan = xlo->xlo_flags & XLO_FLAGS_PARTSCAN; + mutex_unlock(&xloop_ctl_mutex); + /* + * We must drop file reference outside of xloop_ctl_mutex as dropping + * the file ref can take bd_mutex which creates circular locking + * dependency. + */ + fput(old_file); + if (partscan) + xloop_reread_partitions(xlo, bdev); + return 0; + +out_err: + mutex_unlock(&xloop_ctl_mutex); + if (file) + fput(file); + return error; +} + +/* xloop sysfs attributes */ + +static ssize_t xloop_attr_show(struct device *dev, char *page, + ssize_t (*callback)(struct xloop_device *, char *)) +{ + struct gendisk *disk = dev_to_disk(dev); + struct xloop_device *xlo = disk->private_data; + + return callback(xlo, page); +} + +#define XLOOP_ATTR_RO(_name) \ +static ssize_t xloop_attr_##_name##_show(struct xloop_device *, char *); \ +static ssize_t xloop_attr_do_show_##_name(struct device *d, \ + struct device_attribute *attr, char *b) \ +{ \ + return xloop_attr_show(d, b, xloop_attr_##_name##_show); \ +} \ +static struct device_attribute xloop_attr_##_name = \ + __ATTR(_name, 0444, xloop_attr_do_show_##_name, NULL); + +static ssize_t xloop_attr_backing_file_show(struct xloop_device *xlo, char *buf) +{ + ssize_t ret; + char *p = NULL; + + spin_lock_irq(&xlo->xlo_lock); + if (xlo->xlo_backing_file) + p = file_path(xlo->xlo_backing_file, buf, PAGE_SIZE - 1); + spin_unlock_irq(&xlo->xlo_lock); + + if (IS_ERR_OR_NULL(p)) + ret = PTR_ERR(p); + else { + ret = strlen(p); + memmove(buf, p, ret); + buf[ret++] = '\n'; + buf[ret] = 0; + } + + return ret; +} + +static ssize_t xloop_attr_file_fmt_type_show(struct xloop_device *xlo, + char *buf) +{ + ssize_t len = 0; + + len = xloop_file_fmt_print_type(xlo->xlo_fmt->file_fmt_type, buf); + len += sprintf(buf + len, "\n"); + + return len; +} + +static ssize_t xloop_attr_offset_show(struct xloop_device *xlo, char *buf) +{ + return sprintf(buf, "%llu\n", (unsigned long long)xlo->xlo_offset); +} + +static ssize_t xloop_attr_sizelimit_show(struct xloop_device *xlo, char *buf) +{ + return sprintf(buf, "%llu\n", (unsigned long long)xlo->xlo_sizelimit); +} + +static ssize_t xloop_attr_autoclear_show(struct xloop_device *xlo, char *buf) +{ + int autoclear = (xlo->xlo_flags & XLO_FLAGS_AUTOCLEAR); + + return sprintf(buf, "%s\n", autoclear ? "1" : "0"); +} + +static ssize_t xloop_attr_partscan_show(struct xloop_device *xlo, char *buf) +{ + int partscan = (xlo->xlo_flags & XLO_FLAGS_PARTSCAN); + + return sprintf(buf, "%s\n", partscan ? "1" : "0"); +} + +static ssize_t xloop_attr_dio_show(struct xloop_device *xlo, char *buf) +{ + int dio = (xlo->xlo_flags & XLO_FLAGS_DIRECT_IO); + + return sprintf(buf, "%s\n", dio ? "1" : "0"); +} + +XLOOP_ATTR_RO(backing_file); +XLOOP_ATTR_RO(file_fmt_type); +XLOOP_ATTR_RO(offset); +XLOOP_ATTR_RO(sizelimit); +XLOOP_ATTR_RO(autoclear); +XLOOP_ATTR_RO(partscan); +XLOOP_ATTR_RO(dio); + +static struct attribute *xloop_attrs[] = { + &xloop_attr_backing_file.attr, + &xloop_attr_file_fmt_type.attr, + &xloop_attr_offset.attr, + &xloop_attr_sizelimit.attr, + &xloop_attr_autoclear.attr, + &xloop_attr_partscan.attr, + &xloop_attr_dio.attr, + NULL, +}; + +static struct attribute_group xloop_attribute_group = { + .name = "xloop", + .attrs= xloop_attrs, +}; + +static void xloop_sysfs_init(struct xloop_device *xlo) +{ + xlo->sysfs_inited = !sysfs_create_group(&disk_to_dev(xlo->xlo_disk)->kobj, + &xloop_attribute_group); +} + +static void xloop_sysfs_exit(struct xloop_device *xlo) +{ + if (xlo->sysfs_inited) + sysfs_remove_group(&disk_to_dev(xlo->xlo_disk)->kobj, + &xloop_attribute_group); +} + +static void xloop_config_discard(struct xloop_device *xlo) +{ + struct file *file = xlo->xlo_backing_file; + struct inode *inode = file->f_mapping->host; + struct request_queue *q = xlo->xlo_queue; + u32 granularity, max_discard_sectors; + + /* + * If the backing device is a block device, mirror its zeroing + * capability. Set the discard sectors to the block device's zeroing + * capabilities because xloop discards result in blkdev_issue_zeroout(), + * not blkdev_issue_discard(). This maintains consistent behavior with + * file-backed xloop devices: discarded regions read back as zero. + */ + if (S_ISBLK(inode->i_mode) && !xlo->xlo_encrypt_key_size) { + struct request_queue *backingq; + + backingq = bdev_get_queue(inode->i_bdev); + + max_discard_sectors = backingq->limits.max_write_zeroes_sectors; + granularity = backingq->limits.discard_granularity ?: + queue_physical_block_size(backingq); + + /* + * We use punch hole to reclaim the free space used by the + * image a.k.a. discard. However we do not support discard if + * encryption is enabled, because it may give an attacker + * useful information. + */ + } else if (!file->f_op->fallocate || xlo->xlo_encrypt_key_size) { + max_discard_sectors = 0; + granularity = 0; + + } else { + max_discard_sectors = UINT_MAX >> 9; + granularity = inode->i_sb->s_blocksize; + } + + if (max_discard_sectors) { + q->limits.discard_granularity = granularity; + blk_queue_max_discard_sectors(q, max_discard_sectors); + blk_queue_max_write_zeroes_sectors(q, max_discard_sectors); + blk_queue_flag_set(QUEUE_FLAG_DISCARD, q); + } else { + q->limits.discard_granularity = 0; + blk_queue_max_discard_sectors(q, 0); + blk_queue_max_write_zeroes_sectors(q, 0); + blk_queue_flag_clear(QUEUE_FLAG_DISCARD, q); + } + q->limits.discard_alignment = 0; +} + +static void xloop_unprepare_queue(struct xloop_device *xlo) +{ + kthread_flush_worker(&xlo->worker); + kthread_stop(xlo->worker_task); +} + +static int xloop_kthread_worker_fn(void *worker_ptr) +{ +#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 8, 0) + current->flags |= PF_LOCAL_THROTTLE | PF_MEMALLOC_NOIO; +#else + current->flags |= PF_LESS_THROTTLE | PF_MEMALLOC_NOIO; +#endif + return kthread_worker_fn(worker_ptr); +} + +static int xloop_prepare_queue(struct xloop_device *xlo) +{ + kthread_init_worker(&xlo->worker); + xlo->worker_task = kthread_run(xloop_kthread_worker_fn, + &xlo->worker, "xloop%d", xlo->xlo_number); + if (IS_ERR(xlo->worker_task)) + return -ENOMEM; + set_user_nice(xlo->worker_task, MIN_NICE); + return 0; +} + +static void xloop_update_rotational(struct xloop_device *xlo) +{ + struct file *file = xlo->xlo_backing_file; + struct inode *file_inode = file->f_mapping->host; + struct block_device *file_bdev = file_inode->i_sb->s_bdev; + struct request_queue *q = xlo->xlo_queue; + bool nonrot = true; + + /* not all filesystems (e.g. tmpfs) have a sb->s_bdev */ + if (file_bdev) + nonrot = blk_queue_nonrot(bdev_get_queue(file_bdev)); + + if (nonrot) + blk_queue_flag_set(QUEUE_FLAG_NONROT, q); + else + blk_queue_flag_clear(QUEUE_FLAG_NONROT, q); +} + +static int +xloop_release_xfer(struct xloop_device *xlo) +{ + int err = 0; + struct xloop_func_table *xfer = xlo->xlo_encryption; + + if (xfer) { + if (xfer->release) + err = xfer->release(xlo); + xlo->transfer = NULL; + xlo->xlo_encryption = NULL; + module_put(xfer->owner); + } + return err; +} + +static int +xloop_init_xfer(struct xloop_device *xlo, struct xloop_func_table *xfer, + const struct xloop_info64 *i) +{ + int err = 0; + + if (xfer) { + struct module *owner = xfer->owner; + + if (!try_module_get(owner)) + return -EINVAL; + if (xfer->init) + err = xfer->init(xlo, i); + if (err) + module_put(owner); + else + xlo->xlo_encryption = xfer; + } + return err; +} + +/** + * xloop_set_status_from_info - configure device from xloop_info + * @xlo: struct xloop_device to configure + * @info: struct xloop_info64 to configure the device with + * + * Configures the xloop device parameters according to the passed + * in xloop_info64 configuration. + */ +static int +xloop_set_status_from_info(struct xloop_device *xlo, + const struct xloop_info64 *info) +{ + int err; + struct xloop_func_table *xfer; + kuid_t uid = current_uid(); + + if ((unsigned int) info->xlo_encrypt_key_size > XLO_KEY_SIZE) + return -EINVAL; + + err = xloop_release_xfer(xlo); + if (err) + return err; + + if (info->xlo_encrypt_type) { + unsigned int type = info->xlo_encrypt_type; + + if (type >= MAX_XLO_CRYPT) + return -EINVAL; + xfer = xfer_funcs[type]; + if (xfer == NULL) + return -EINVAL; + } else + xfer = NULL; + + err = xloop_init_xfer(xlo, xfer, info); + if (err) + return err; + + xlo->xlo_offset = info->xlo_offset; + xlo->xlo_sizelimit = info->xlo_sizelimit; + memcpy(xlo->xlo_file_name, info->xlo_file_name, XLO_NAME_SIZE); + memcpy(xlo->xlo_crypt_name, info->xlo_crypt_name, XLO_NAME_SIZE); + xlo->xlo_file_name[XLO_NAME_SIZE-1] = 0; + xlo->xlo_crypt_name[XLO_NAME_SIZE-1] = 0; + + if (!xfer) + xfer = &none_funcs; + xlo->transfer = xfer->transfer; + xlo->ioctl = xfer->ioctl; + + xlo->xlo_flags = info->xlo_flags; + + xlo->xlo_encrypt_key_size = info->xlo_encrypt_key_size; + xlo->xlo_init[0] = info->xlo_init[0]; + xlo->xlo_init[1] = info->xlo_init[1]; + if (info->xlo_encrypt_key_size) { + memcpy(xlo->xlo_encrypt_key, info->xlo_encrypt_key, + info->xlo_encrypt_key_size); + xlo->xlo_key_owner = uid; + } + + return 0; +} + +static int xloop_configure(struct xloop_device *xlo, fmode_t mode, + struct block_device *bdev, + const struct xloop_config *config) +{ + struct file *file; + struct inode *inode; + struct address_space *mapping; + struct block_device *claimed_bdev = NULL; + int error; + loff_t size; + bool partscan; + unsigned short bsize; + + /* This is safe, since we have a reference from open(). */ + __module_get(THIS_MODULE); + + error = -EBADF; + file = fget(config->fd); + if (!file) + goto out; + + /* + * If we don't hold exclusive handle for the device, upgrade to it + * here to avoid changing device under exclusive owner. + */ + if (!(mode & FMODE_EXCL)) { +#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 9, 0) + claimed_bdev = bdev->bd_contains; + error = bd_prepare_to_claim(bdev, claimed_bdev, xloop_configure); + if (error) + goto out_putf; +#else + claimed_bdev = bd_start_claiming(bdev, xloop_configure); + if (IS_ERR(claimed_bdev)) { + error = PTR_ERR(claimed_bdev); + goto out_putf; + } +#endif + } + + error = mutex_lock_killable(&xloop_ctl_mutex); + if (error) + goto out_bdev; + + error = -EBUSY; + if (xlo->xlo_state != Xlo_unbound) + goto out_unlock; + + error = xloop_validate_file(file, bdev); + if (error) + goto out_unlock; + + mapping = file->f_mapping; + inode = mapping->host; + + if ((config->info.xlo_flags & ~XLOOP_CONFIGURE_SETTABLE_FLAGS) != 0) { + error = -EINVAL; + goto out_unlock; + } + + if (config->block_size) { + error = xloop_validate_block_size(config->block_size); + if (error) + goto out_unlock; + } + + error = xloop_set_status_from_info(xlo, &config->info); + if (error) + goto out_unlock; + + if (!(file->f_mode & FMODE_WRITE) || !(mode & FMODE_WRITE) || + !file->f_op->write_iter) + xlo->xlo_flags |= XLO_FLAGS_READ_ONLY; + + error = xloop_prepare_queue(xlo); + if (error) + goto out_unlock; + + error = xloop_file_fmt_init(xlo->xlo_fmt, + config->info.xlo_file_fmt_type); + if (error) + goto out_unlock; + + set_device_ro(bdev, (xlo->xlo_flags & XLO_FLAGS_READ_ONLY) != 0); + + xlo->use_dio = xlo->xlo_flags & XLO_FLAGS_DIRECT_IO; + xlo->xlo_device = bdev; + xlo->xlo_backing_file = file; + xlo->old_gfp_mask = mapping_gfp_mask(mapping); + mapping_set_gfp_mask(mapping, xlo->old_gfp_mask & ~(__GFP_IO|__GFP_FS)); + + if (!(xlo->xlo_flags & XLO_FLAGS_READ_ONLY) && file->f_op->fsync) + blk_queue_write_cache(xlo->xlo_queue, true, false); + + if (config->block_size) + bsize = config->block_size; + else if ((xlo->xlo_backing_file->f_flags & O_DIRECT) && inode->i_sb->s_bdev) + /* In case of direct I/O, match underlying block size */ + bsize = bdev_logical_block_size(inode->i_sb->s_bdev); + else + bsize = 512; + + blk_queue_logical_block_size(xlo->xlo_queue, bsize); + blk_queue_physical_block_size(xlo->xlo_queue, bsize); + blk_queue_io_min(xlo->xlo_queue, bsize); + + xloop_update_rotational(xlo); + xloop_update_dio(xlo); + xloop_sysfs_init(xlo); + + size = get_xloop_size(xlo, file); + xloop_set_size(xlo, size); + + set_blocksize(bdev, S_ISBLK(inode->i_mode) ? + block_size(inode->i_bdev) : PAGE_SIZE); + + xlo->xlo_state = Xlo_bound; + if (part_shift) + xlo->xlo_flags |= XLO_FLAGS_PARTSCAN; + partscan = xlo->xlo_flags & XLO_FLAGS_PARTSCAN; + if (partscan) + xlo->xlo_disk->flags &= ~GENHD_FL_NO_PART_SCAN; + + /* Grab the block_device to prevent its destruction after we + * put /dev/xloopXX inode. Later in __xloop_clr_fd() we bdput(bdev). + */ + bdgrab(bdev); + mutex_unlock(&xloop_ctl_mutex); + if (partscan) + xloop_reread_partitions(xlo, bdev); + if (claimed_bdev) + bd_abort_claiming(bdev, claimed_bdev, xloop_configure); + return 0; + +out_unlock: + mutex_unlock(&xloop_ctl_mutex); +out_bdev: + if (claimed_bdev) + bd_abort_claiming(bdev, claimed_bdev, xloop_configure); +out_putf: + fput(file); +out: + /* This is safe: open() is still holding a reference. */ + module_put(THIS_MODULE); + return error; +} + +static int __xloop_clr_fd(struct xloop_device *xlo, bool release) +{ + struct file *filp = NULL; + gfp_t gfp = xlo->old_gfp_mask; + struct block_device *bdev = xlo->xlo_device; + int err = 0; + bool partscan = false; + int xlo_number; + + mutex_lock(&xloop_ctl_mutex); + if (WARN_ON_ONCE(xlo->xlo_state != Xlo_rundown)) { + err = -ENXIO; + goto out_unlock; + } + + filp = xlo->xlo_backing_file; + if (filp == NULL) { + err = -EINVAL; + goto out_unlock; + } + + /* freeze request queue during the transition */ + blk_mq_freeze_queue(xlo->xlo_queue); + + xloop_file_fmt_exit(xlo->xlo_fmt); + + spin_lock_irq(&xlo->xlo_lock); + xlo->xlo_backing_file = NULL; + spin_unlock_irq(&xlo->xlo_lock); + + xloop_release_xfer(xlo); + xlo->transfer = NULL; + xlo->ioctl = NULL; + xlo->xlo_device = NULL; + xlo->xlo_encryption = NULL; + xlo->xlo_offset = 0; + xlo->xlo_sizelimit = 0; + xlo->xlo_encrypt_key_size = 0; + memset(xlo->xlo_encrypt_key, 0, XLO_KEY_SIZE); + memset(xlo->xlo_crypt_name, 0, XLO_NAME_SIZE); + memset(xlo->xlo_file_name, 0, XLO_NAME_SIZE); + blk_queue_logical_block_size(xlo->xlo_queue, 512); + blk_queue_physical_block_size(xlo->xlo_queue, 512); + blk_queue_io_min(xlo->xlo_queue, 512); + if (bdev) { + bdput(bdev); + invalidate_bdev(bdev); + bdev->bd_inode->i_mapping->wb_err = 0; + } + set_capacity(xlo->xlo_disk, 0); + xloop_sysfs_exit(xlo); + if (bdev) { + bd_set_size(bdev, 0); + /* let user-space know about this change */ + kobject_uevent(&disk_to_dev(bdev->bd_disk)->kobj, KOBJ_CHANGE); + } + mapping_set_gfp_mask(filp->f_mapping, gfp); + /* This is safe: open() is still holding a reference. */ + module_put(THIS_MODULE); + blk_mq_unfreeze_queue(xlo->xlo_queue); + + partscan = xlo->xlo_flags & XLO_FLAGS_PARTSCAN && bdev; + xlo_number = xlo->xlo_number; + xloop_unprepare_queue(xlo); +out_unlock: + mutex_unlock(&xloop_ctl_mutex); + if (partscan) { + /* + * bd_mutex has been held already in release path, so don't + * acquire it if this function is called in such case. + * + * If the reread partition isn't from release path, xlo_refcnt + * must be at least one and it can only become zero when the + * current holder is released. + */ + if (!release) + mutex_lock(&bdev->bd_mutex); +#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 5, 0) + err = bdev_disk_changed(bdev, false); +#else + err = blkdev_reread_part(bdev); +#endif + if (!release) + mutex_unlock(&bdev->bd_mutex); + if (err) + pr_warn("%s: partition scan of xloop%d failed (rc=%d)\n", + __func__, xlo_number, err); + /* Device is gone, no point in returning error */ + err = 0; + } + + /* + * xlo->xlo_state is set to Xlo_unbound here after above partscan has + * finished. + * + * There cannot be anybody else entering __xloop_clr_fd() as + * xlo->xlo_backing_file is already cleared and Xlo_rundown state + * protects us from all the other places trying to change the 'xlo' + * device. + */ + mutex_lock(&xloop_ctl_mutex); + xlo->xlo_flags = 0; + if (!part_shift) + xlo->xlo_disk->flags |= GENHD_FL_NO_PART_SCAN; + xlo->xlo_state = Xlo_unbound; + mutex_unlock(&xloop_ctl_mutex); + + /* + * Need not hold xloop_ctl_mutex to fput backing file. + * Calling fput holding xloop_ctl_mutex triggers a circular + * lock dependency possibility warning as fput can take + * bd_mutex which is usually taken before xloop_ctl_mutex. + */ + if (filp) + fput(filp); + return err; +} + +static int xloop_clr_fd(struct xloop_device *xlo) +{ + int err; + + err = mutex_lock_killable(&xloop_ctl_mutex); + if (err) + return err; + if (xlo->xlo_state != Xlo_bound) { + mutex_unlock(&xloop_ctl_mutex); + return -ENXIO; + } + /* + * If we've explicitly asked to tear down the xloop device, + * and it has an elevated reference count, set it for auto-teardown when + * the last reference goes away. This stops $!~#$@ udev from + * preventing teardown because it decided that it needs to run blkid on + * the xloopback device whenever they appear. xfstests is notorious for + * failing tests because blkid via udev races with a losetup + * /do something like mkfs/losetup -d causing the losetup -d + * command to fail with EBUSY. + */ + if (atomic_read(&xlo->xlo_refcnt) > 1) { + xlo->xlo_flags |= XLO_FLAGS_AUTOCLEAR; + mutex_unlock(&xloop_ctl_mutex); + return 0; + } + xlo->xlo_state = Xlo_rundown; + mutex_unlock(&xloop_ctl_mutex); + + return __xloop_clr_fd(xlo, false); +} + +static int +xloop_set_status(struct xloop_device *xlo, const struct xloop_info64 *info) +{ + int err; + struct block_device *bdev; + kuid_t uid = current_uid(); + int prev_xlo_flags; + bool partscan = false; + bool size_changed = false; + + err = mutex_lock_killable(&xloop_ctl_mutex); + if (err) + return err; + if (xlo->xlo_encrypt_key_size && + !uid_eq(xlo->xlo_key_owner, uid) && + !capable(CAP_SYS_ADMIN)) { + err = -EPERM; + goto out_unlock; + } + if (xlo->xlo_state != Xlo_bound) { + err = -ENXIO; + goto out_unlock; + } + + if (xlo->xlo_offset != info->xlo_offset || + xlo->xlo_sizelimit != info->xlo_sizelimit) { + size_changed = true; + sync_blockdev(xlo->xlo_device); + invalidate_bdev(xlo->xlo_device); + } + + /* I/O need to be drained during transfer transition */ + blk_mq_freeze_queue(xlo->xlo_queue); + + if (size_changed && xlo->xlo_device->bd_inode->i_mapping->nrpages) { + /* If any pages were dirtied after invalidate_bdev(), try again */ + err = -EAGAIN; + pr_warn("%s: xloop%d (%s) has still dirty pages (nrpages=%lu)\n", + __func__, xlo->xlo_number, xlo->xlo_file_name, + xlo->xlo_device->bd_inode->i_mapping->nrpages); + goto out_unfreeze; + } + + prev_xlo_flags = xlo->xlo_flags; + + err = xloop_set_status_from_info(xlo, info); + if (err) + goto out_unfreeze; + + /* Mask out flags that can't be set using XLOOP_SET_STATUS. */ + xlo->xlo_flags &= XLOOP_SET_STATUS_SETTABLE_FLAGS; + /* For those flags, use the previous values instead */ + xlo->xlo_flags |= prev_xlo_flags & ~XLOOP_SET_STATUS_SETTABLE_FLAGS; + /* For flags that can't be cleared, use previous values too */ + xlo->xlo_flags |= prev_xlo_flags & ~XLOOP_SET_STATUS_CLEARABLE_FLAGS; + + if (xlo->xlo_fmt->file_fmt_type != info->xlo_file_fmt_type) { + /* xloop file format has changed, so change file format driver */ + err = xloop_file_fmt_change(xlo->xlo_fmt, info->xlo_file_fmt_type); + if (err) + goto out_unfreeze; + + /* After change of the file format, recalculate the capacity of + * the loop device. */ + size_changed = true; + } + + if (size_changed) { + loff_t new_size = get_xloop_size(xlo, xlo->xlo_backing_file); + xloop_set_size(xlo, new_size); + } + + xloop_config_discard(xlo); + + /* update dio if xlo_offset or transfer is changed */ + __xloop_update_dio(xlo, xlo->use_dio); + +out_unfreeze: + blk_mq_unfreeze_queue(xlo->xlo_queue); + + if (!err && (xlo->xlo_flags & XLO_FLAGS_PARTSCAN) && + !(prev_xlo_flags & XLO_FLAGS_PARTSCAN)) { + xlo->xlo_disk->flags &= ~GENHD_FL_NO_PART_SCAN; + bdev = xlo->xlo_device; + partscan = true; + } +out_unlock: + mutex_unlock(&xloop_ctl_mutex); + if (partscan) + xloop_reread_partitions(xlo, bdev); + + return err; +} + +static int +xloop_get_status(struct xloop_device *xlo, struct xloop_info64 *info) +{ + struct path path; + struct kstat stat; + int ret; + + ret = mutex_lock_killable(&xloop_ctl_mutex); + if (ret) + return ret; + if (xlo->xlo_state != Xlo_bound) { + mutex_unlock(&xloop_ctl_mutex); + return -ENXIO; + } + + memset(info, 0, sizeof(*info)); + info->xlo_number = xlo->xlo_number; + info->xlo_offset = xlo->xlo_offset; + info->xlo_sizelimit = xlo->xlo_sizelimit; + info->xlo_flags = xlo->xlo_flags; + memcpy(info->xlo_file_name, xlo->xlo_file_name, XLO_NAME_SIZE); + memcpy(info->xlo_crypt_name, xlo->xlo_crypt_name, XLO_NAME_SIZE); + info->xlo_encrypt_type = + xlo->xlo_encryption ? xlo->xlo_encryption->number : 0; + if (xlo->xlo_encrypt_key_size && capable(CAP_SYS_ADMIN)) { + info->xlo_encrypt_key_size = xlo->xlo_encrypt_key_size; + memcpy(info->xlo_encrypt_key, xlo->xlo_encrypt_key, + xlo->xlo_encrypt_key_size); + } + + /* Drop xloop_ctl_mutex while we call into the filesystem. */ + path = xlo->xlo_backing_file->f_path; + path_get(&path); + mutex_unlock(&xloop_ctl_mutex); + ret = vfs_getattr(&path, &stat, STATX_INO, AT_STATX_SYNC_AS_STAT); + if (!ret) { + info->xlo_device = huge_encode_dev(stat.dev); + info->xlo_inode = stat.ino; + info->xlo_rdevice = huge_encode_dev(stat.rdev); + } + path_put(&path); + return ret; +} + +static void +xloop_info64_from_old(const struct xloop_info *info, struct xloop_info64 *info64) +{ + memset(info64, 0, sizeof(*info64)); + info64->xlo_number = info->xlo_number; + info64->xlo_device = info->xlo_device; + info64->xlo_inode = info->xlo_inode; + info64->xlo_rdevice = info->xlo_rdevice; + info64->xlo_offset = info->xlo_offset; + info64->xlo_sizelimit = 0; + info64->xlo_encrypt_type = info->xlo_encrypt_type; + info64->xlo_encrypt_key_size = info->xlo_encrypt_key_size; + info64->xlo_flags = info->xlo_flags; + info64->xlo_init[0] = info->xlo_init[0]; + info64->xlo_init[1] = info->xlo_init[1]; + info64->xlo_file_fmt_type = info->xlo_file_fmt_type; + if (info->xlo_encrypt_type == XLO_CRYPT_CRYPTOAPI) + memcpy(info64->xlo_crypt_name, info->xlo_name, XLO_NAME_SIZE); + else + memcpy(info64->xlo_file_name, info->xlo_name, XLO_NAME_SIZE); + memcpy(info64->xlo_encrypt_key, info->xlo_encrypt_key, XLO_KEY_SIZE); +} + +static int +xloop_info64_to_old(const struct xloop_info64 *info64, struct xloop_info *info) +{ + memset(info, 0, sizeof(*info)); + info->xlo_number = info64->xlo_number; + info->xlo_device = info64->xlo_device; + info->xlo_inode = info64->xlo_inode; + info->xlo_rdevice = info64->xlo_rdevice; + info->xlo_offset = info64->xlo_offset; + info->xlo_encrypt_type = info64->xlo_encrypt_type; + info->xlo_encrypt_key_size = info64->xlo_encrypt_key_size; + info->xlo_flags = info64->xlo_flags; + info->xlo_init[0] = info64->xlo_init[0]; + info->xlo_init[1] = info64->xlo_init[1]; + info->xlo_file_fmt_type = info64->xlo_file_fmt_type; + if (info->xlo_encrypt_type == XLO_CRYPT_CRYPTOAPI) + memcpy(info->xlo_name, info64->xlo_crypt_name, XLO_NAME_SIZE); + else + memcpy(info->xlo_name, info64->xlo_file_name, XLO_NAME_SIZE); + memcpy(info->xlo_encrypt_key, info64->xlo_encrypt_key, XLO_KEY_SIZE); + + /* error in case values were truncated */ + if (info->xlo_device != info64->xlo_device || + info->xlo_rdevice != info64->xlo_rdevice || + info->xlo_inode != info64->xlo_inode || + info->xlo_offset != info64->xlo_offset) + return -EOVERFLOW; + + return 0; +} + +static int +xloop_set_status_old(struct xloop_device *xlo, const struct xloop_info __user *arg) +{ + struct xloop_info info; + struct xloop_info64 info64; + + if (copy_from_user(&info, arg, sizeof (struct xloop_info))) + return -EFAULT; + xloop_info64_from_old(&info, &info64); + return xloop_set_status(xlo, &info64); +} + +static int +xloop_set_status64(struct xloop_device *xlo, const struct xloop_info64 __user *arg) +{ + struct xloop_info64 info64; + + if (copy_from_user(&info64, arg, sizeof (struct xloop_info64))) + return -EFAULT; + return xloop_set_status(xlo, &info64); +} + +static int +xloop_get_status_old(struct xloop_device *xlo, struct xloop_info __user *arg) { + struct xloop_info info; + struct xloop_info64 info64; + int err; + + if (!arg) + return -EINVAL; + err = xloop_get_status(xlo, &info64); + if (!err) + err = xloop_info64_to_old(&info64, &info); + if (!err && copy_to_user(arg, &info, sizeof(info))) + err = -EFAULT; + + return err; +} + +static int +xloop_get_status64(struct xloop_device *xlo, struct xloop_info64 __user *arg) { + struct xloop_info64 info64; + int err; + + if (!arg) + return -EINVAL; + err = xloop_get_status(xlo, &info64); + if (!err && copy_to_user(arg, &info64, sizeof(info64))) + err = -EFAULT; + + return err; +} + +static int xloop_set_capacity(struct xloop_device *xlo) +{ + loff_t size; + + if (unlikely(xlo->xlo_state != Xlo_bound)) + return -ENXIO; + + size = get_xloop_size(xlo, xlo->xlo_backing_file); + xloop_set_size(xlo, size); + + return 0; +} + +static int xloop_set_dio(struct xloop_device *xlo, unsigned long arg) +{ + int error = -ENXIO; + if (xlo->xlo_state != Xlo_bound) + goto out; + + __xloop_update_dio(xlo, !!arg); + if (xlo->use_dio == !!arg) + return 0; + error = -EINVAL; + out: + return error; +} + +static int xloop_set_block_size(struct xloop_device *xlo, unsigned long arg) +{ + int err = 0; + + if (xlo->xlo_state != Xlo_bound) + return -ENXIO; + + err = xloop_validate_block_size(arg); + if (err) + return err; + + if (xlo->xlo_queue->limits.logical_block_size == arg) + return 0; + + sync_blockdev(xlo->xlo_device); + invalidate_bdev(xlo->xlo_device); + + blk_mq_freeze_queue(xlo->xlo_queue); + + /* invalidate_bdev should have truncated all the pages */ + if (xlo->xlo_device->bd_inode->i_mapping->nrpages) { + err = -EAGAIN; + pr_warn("%s: xloop%d (%s) has still dirty pages (nrpages=%lu)\n", + __func__, xlo->xlo_number, xlo->xlo_file_name, + xlo->xlo_device->bd_inode->i_mapping->nrpages); + goto out_unfreeze; + } + + blk_queue_logical_block_size(xlo->xlo_queue, arg); + blk_queue_physical_block_size(xlo->xlo_queue, arg); + blk_queue_io_min(xlo->xlo_queue, arg); + xloop_update_dio(xlo); +out_unfreeze: + blk_mq_unfreeze_queue(xlo->xlo_queue); + + return err; +} + +static int xlo_simple_ioctl(struct xloop_device *xlo, unsigned int cmd, + unsigned long arg) +{ + int err; + + err = mutex_lock_killable(&xloop_ctl_mutex); + if (err) + return err; + switch (cmd) { + case XLOOP_SET_CAPACITY: + err = xloop_set_capacity(xlo); + break; + case XLOOP_SET_DIRECT_IO: + err = xloop_set_dio(xlo, arg); + break; + case XLOOP_SET_BLOCK_SIZE: + err = xloop_set_block_size(xlo, arg); + break; + default: + err = xlo->ioctl ? xlo->ioctl(xlo, cmd, arg) : -EINVAL; + } + mutex_unlock(&xloop_ctl_mutex); + return err; +} + +static int xlo_ioctl(struct block_device *bdev, fmode_t mode, + unsigned int cmd, unsigned long arg) +{ + struct xloop_device *xlo = bdev->bd_disk->private_data; + void __user *argp = (void __user *) arg; + int err; + + switch (cmd) { + case XLOOP_SET_FD: { + /* + * Legacy case - pass in a zeroed out struct xloop_config with + * only the file descriptor set , which corresponds with the + * default parameters we'd have used otherwise. + */ + struct xloop_config config; + + memset(&config, 0, sizeof(config)); + config.fd = arg; + + return xloop_configure(xlo, mode, bdev, &config); + } + case XLOOP_CONFIGURE: { + struct xloop_config config; + + if (copy_from_user(&config, argp, sizeof(config))) + return -EFAULT; + + return xloop_configure(xlo, mode, bdev, &config); + } + case XLOOP_CHANGE_FD: + return xloop_change_fd(xlo, bdev, arg); + case XLOOP_CLR_FD: + return xloop_clr_fd(xlo); + case XLOOP_SET_STATUS: + err = -EPERM; + if ((mode & FMODE_WRITE) || capable(CAP_SYS_ADMIN)) { + err = xloop_set_status_old(xlo, argp); + } + break; + case XLOOP_GET_STATUS: + return xloop_get_status_old(xlo, argp); + case XLOOP_SET_STATUS64: + err = -EPERM; + if ((mode & FMODE_WRITE) || capable(CAP_SYS_ADMIN)) { + err = xloop_set_status64(xlo, argp); + } + break; + case XLOOP_GET_STATUS64: + return xloop_get_status64(xlo, argp); + case XLOOP_SET_CAPACITY: + case XLOOP_SET_DIRECT_IO: + case XLOOP_SET_BLOCK_SIZE: + if (!(mode & FMODE_WRITE) && !capable(CAP_SYS_ADMIN)) + return -EPERM; + fallthrough; + default: + err = xlo_simple_ioctl(xlo, cmd, arg); + break; + } + + return err; +} + +#ifdef CONFIG_COMPAT +struct compat_xloop_info { + compat_int_t xlo_number; /* ioctl r/o */ + compat_dev_t xlo_device; /* ioctl r/o */ + compat_ulong_t xlo_inode; /* ioctl r/o */ + compat_dev_t xlo_rdevice; /* ioctl r/o */ + compat_int_t xlo_offset; + compat_int_t xlo_encrypt_type; + compat_int_t xlo_encrypt_key_size; /* ioctl w/o */ + compat_int_t xlo_flags; /* ioctl r/o */ + char xlo_name[XLO_NAME_SIZE]; + unsigned char xlo_encrypt_key[XLO_KEY_SIZE]; /* ioctl w/o */ + compat_ulong_t xlo_init[2]; + char reserved[4]; + compat_int_t xlo_file_fmt_type; +}; + +/* + * Transfer 32-bit compatibility structure in userspace to 64-bit xloop info + * - noinlined to reduce stack space usage in main part of driver + */ +static noinline int +xloop_info64_from_compat(const struct compat_xloop_info __user *arg, + struct xloop_info64 *info64) +{ + struct compat_xloop_info info; + + if (copy_from_user(&info, arg, sizeof(info))) + return -EFAULT; + + memset(info64, 0, sizeof(*info64)); + info64->xlo_number = info.xlo_number; + info64->xlo_device = info.xlo_device; + info64->xlo_inode = info.xlo_inode; + info64->xlo_rdevice = info.xlo_rdevice; + info64->xlo_offset = info.xlo_offset; + info64->xlo_sizelimit = 0; + info64->xlo_encrypt_type = info.xlo_encrypt_type; + info64->xlo_encrypt_key_size = info.xlo_encrypt_key_size; + info64->xlo_flags = info.xlo_flags; + info64->xlo_init[0] = info.xlo_init[0]; + info64->xlo_init[1] = info.xlo_init[1]; + info64->xlo_file_fmt_type = info.xlo_file_fmt_type; + if (info.xlo_encrypt_type == XLO_CRYPT_CRYPTOAPI) + memcpy(info64->xlo_crypt_name, info.xlo_name, XLO_NAME_SIZE); + else + memcpy(info64->xlo_file_name, info.xlo_name, XLO_NAME_SIZE); + memcpy(info64->xlo_encrypt_key, info.xlo_encrypt_key, XLO_KEY_SIZE); + return 0; +} + +/* + * Transfer 64-bit xloop info to 32-bit compatibility structure in userspace + * - noinlined to reduce stack space usage in main part of driver + */ +static noinline int +xloop_info64_to_compat(const struct xloop_info64 *info64, + struct compat_xloop_info __user *arg) +{ + struct compat_xloop_info info; + + memset(&info, 0, sizeof(info)); + info.xlo_number = info64->xlo_number; + info.xlo_device = info64->xlo_device; + info.xlo_inode = info64->xlo_inode; + info.xlo_rdevice = info64->xlo_rdevice; + info.xlo_offset = info64->xlo_offset; + info.xlo_encrypt_type = info64->xlo_encrypt_type; + info.xlo_encrypt_key_size = info64->xlo_encrypt_key_size; + info.xlo_flags = info64->xlo_flags; + info.xlo_init[0] = info64->xlo_init[0]; + info.xlo_init[1] = info64->xlo_init[1]; + if (info.xlo_encrypt_type == XLO_CRYPT_CRYPTOAPI) + memcpy(info.xlo_name, info64->xlo_crypt_name, XLO_NAME_SIZE); + else + memcpy(info.xlo_name, info64->xlo_file_name, XLO_NAME_SIZE); + memcpy(info.xlo_encrypt_key, info64->xlo_encrypt_key, XLO_KEY_SIZE); + + /* error in case values were truncated */ + if (info.xlo_device != info64->xlo_device || + info.xlo_rdevice != info64->xlo_rdevice || + info.xlo_inode != info64->xlo_inode || + info.xlo_offset != info64->xlo_offset || + info.xlo_init[0] != info64->xlo_init[0] || + info.xlo_init[1] != info64->xlo_init[1]) + return -EOVERFLOW; + + if (copy_to_user(arg, &info, sizeof(info))) + return -EFAULT; + return 0; +} + +static int +xloop_set_status_compat(struct xloop_device *xlo, + const struct compat_xloop_info __user *arg) +{ + struct xloop_info64 info64; + int ret; + + ret = xloop_info64_from_compat(arg, &info64); + if (ret < 0) + return ret; + return xloop_set_status(xlo, &info64); +} + +static int +xloop_get_status_compat(struct xloop_device *xlo, + struct compat_xloop_info __user *arg) +{ + struct xloop_info64 info64; + int err; + + if (!arg) + return -EINVAL; + err = xloop_get_status(xlo, &info64); + if (!err) + err = xloop_info64_to_compat(&info64, arg); + return err; +} + +static int xlo_compat_ioctl(struct block_device *bdev, fmode_t mode, + unsigned int cmd, unsigned long arg) +{ + struct xloop_device *xlo = bdev->bd_disk->private_data; + int err; + + switch(cmd) { + case XLOOP_SET_STATUS: + err = xloop_set_status_compat(xlo, + (const struct compat_xloop_info __user *)arg); + break; + case XLOOP_GET_STATUS: + err = xloop_get_status_compat(xlo, + (struct compat_xloop_info __user *)arg); + break; + case XLOOP_SET_CAPACITY: + case XLOOP_CLR_FD: + case XLOOP_GET_STATUS64: + case XLOOP_SET_STATUS64: + case XLOOP_CONFIGURE: + arg = (unsigned long) compat_ptr(arg); + fallthrough; + case XLOOP_SET_FD: + case XLOOP_CHANGE_FD: + case XLOOP_SET_BLOCK_SIZE: + case XLOOP_SET_DIRECT_IO: + err = xlo_ioctl(bdev, mode, cmd, arg); + break; + default: + err = -ENOIOCTLCMD; + break; + } + return err; +} +#endif + +static int xlo_open(struct block_device *bdev, fmode_t mode) +{ + struct xloop_device *xlo; + int err; + + err = mutex_lock_killable(&xloop_ctl_mutex); + if (err) + return err; + xlo = bdev->bd_disk->private_data; + if (!xlo) { + err = -ENXIO; + goto out; + } + + atomic_inc(&xlo->xlo_refcnt); +out: + mutex_unlock(&xloop_ctl_mutex); + return err; +} + +static void xlo_release(struct gendisk *disk, fmode_t mode) +{ + struct xloop_device *xlo; + + mutex_lock(&xloop_ctl_mutex); + xlo = disk->private_data; + if (atomic_dec_return(&xlo->xlo_refcnt)) + goto out_unlock; + + if (xlo->xlo_flags & XLO_FLAGS_AUTOCLEAR) { + if (xlo->xlo_state != Xlo_bound) + goto out_unlock; + xlo->xlo_state = Xlo_rundown; + mutex_unlock(&xloop_ctl_mutex); + /* + * In autoclear mode, stop the xloop thread + * and remove configuration after last close. + */ + __xloop_clr_fd(xlo, true); + return; + } else if (xlo->xlo_state == Xlo_bound) { + /* + * Otherwise keep thread (if running) and config, + * but flush possible ongoing bios in thread. + */ + blk_mq_freeze_queue(xlo->xlo_queue); + blk_mq_unfreeze_queue(xlo->xlo_queue); + } + +out_unlock: + mutex_unlock(&xloop_ctl_mutex); +} + +static const struct block_device_operations xlo_fops = { + .owner = THIS_MODULE, + .open = xlo_open, + .release = xlo_release, + .ioctl = xlo_ioctl, +#ifdef CONFIG_COMPAT + .compat_ioctl = xlo_compat_ioctl, +#endif +}; + +/* + * And now the modules code and kernel interface. + */ +static int max_xloop; +module_param(max_xloop, int, 0444); +MODULE_PARM_DESC(max_xloop, "Maximum number of xloop devices"); +module_param(max_part, int, 0444); +MODULE_PARM_DESC(max_part, "Maximum number of partitions per xloop device"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS_BLOCKDEV_MAJOR(LOOP_MAJOR); + +int xloop_register_transfer(struct xloop_func_table *funcs) +{ + unsigned int n = funcs->number; + + if (n >= MAX_XLO_CRYPT || xfer_funcs[n]) + return -EINVAL; + xfer_funcs[n] = funcs; + return 0; +} + +static int unregister_transfer_cb(int id, void *ptr, void *data) +{ + struct xloop_device *xlo = ptr; + struct xloop_func_table *xfer = data; + + mutex_lock(&xloop_ctl_mutex); + if (xlo->xlo_encryption == xfer) + xloop_release_xfer(xlo); + mutex_unlock(&xloop_ctl_mutex); + return 0; +} + +int xloop_unregister_transfer(int number) +{ + unsigned int n = number; + struct xloop_func_table *xfer; + + if (n == 0 || n >= MAX_XLO_CRYPT || (xfer = xfer_funcs[n]) == NULL) + return -EINVAL; + + xfer_funcs[n] = NULL; + idr_for_each(&xloop_index_idr, &unregister_transfer_cb, xfer); + return 0; +} + +EXPORT_SYMBOL(xloop_register_transfer); +EXPORT_SYMBOL(xloop_unregister_transfer); + +static blk_status_t xloop_queue_rq(struct blk_mq_hw_ctx *hctx, + const struct blk_mq_queue_data *bd) +{ + struct request *rq = bd->rq; + struct xloop_cmd *cmd = blk_mq_rq_to_pdu(rq); + struct xloop_device *xlo = rq->q->queuedata; + + blk_mq_start_request(rq); + + if (xlo->xlo_state != Xlo_bound) + return BLK_STS_IOERR; + + switch (req_op(rq)) { + case REQ_OP_FLUSH: + case REQ_OP_DISCARD: + case REQ_OP_WRITE_ZEROES: + cmd->use_aio = false; + break; + default: + cmd->use_aio = xlo->use_dio; + break; + } + + /* always use the first bio's css */ +#ifdef CONFIG_BLK_CGROUP + if (cmd->use_aio && rq->bio && rq->bio->bi_blkg) { + cmd->css = &bio_blkcg(rq->bio)->css; + css_get(cmd->css); + } else +#endif + cmd->css = NULL; + kthread_queue_work(&xlo->worker, &cmd->work); + + return BLK_STS_OK; +} + +static void xloop_handle_cmd(struct xloop_cmd *cmd) +{ + struct request *rq = blk_mq_rq_from_pdu(cmd); + const bool write = op_is_write(req_op(rq)); + struct xloop_device *xlo = rq->q->queuedata; + int ret = 0; + + if (write && (xlo->xlo_flags & XLO_FLAGS_READ_ONLY)) { + ret = -EIO; + goto failed; + } + + ret = do_req_filebacked(xlo, rq); + failed: + /* complete non-aio request */ + if (!cmd->use_aio || ret) { + if (ret == -EOPNOTSUPP) + cmd->ret = ret; + else + cmd->ret = ret ? -EIO : 0; +#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 9, 0) + if (likely(!blk_should_fake_timeout(rq->q))) + blk_mq_complete_request(rq); +#else + blk_mq_complete_request(rq); +#endif + } +} + +static void xloop_queue_work(struct kthread_work *work) +{ + struct xloop_cmd *cmd = + container_of(work, struct xloop_cmd, work); + + xloop_handle_cmd(cmd); +} + +static int xloop_init_request(struct blk_mq_tag_set *set, struct request *rq, + unsigned int hctx_idx, unsigned int numa_node) +{ + struct xloop_cmd *cmd = blk_mq_rq_to_pdu(rq); + + kthread_init_work(&cmd->work, xloop_queue_work); + return 0; +} + +static const struct blk_mq_ops xloop_mq_ops = { + .queue_rq = xloop_queue_rq, + .init_request = xloop_init_request, + .complete = xlo_complete_rq, +}; + +static struct dentry *xloop_dbgfs_dir; + +static int xloop_add(struct xloop_device **l, int i) +{ + struct xloop_device *xlo; + struct gendisk *disk; + int err; + + err = -ENOMEM; + xlo = kzalloc(sizeof(*xlo), GFP_KERNEL); + if (!xlo) + goto out; + + xlo->xlo_state = Xlo_unbound; + + /* allocate id, if @id >= 0, we're requesting that specific id */ + if (i >= 0) { + err = idr_alloc(&xloop_index_idr, xlo, i, i + 1, GFP_KERNEL); + if (err == -ENOSPC) + err = -EEXIST; + } else { + err = idr_alloc(&xloop_index_idr, xlo, 0, 0, GFP_KERNEL); + } + if (err < 0) + goto out_free_dev; + i = err; + + err = -ENOMEM; + xlo->tag_set.ops = &xloop_mq_ops; + xlo->tag_set.nr_hw_queues = 1; + xlo->tag_set.queue_depth = 128; + xlo->tag_set.numa_node = NUMA_NO_NODE; + xlo->tag_set.cmd_size = sizeof(struct xloop_cmd); +#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 8, 0) + xlo->tag_set.flags = BLK_MQ_F_SHOULD_MERGE | BLK_MQ_F_STACKING; +#else + xlo->tag_set.flags = BLK_MQ_F_SHOULD_MERGE; +#endif + xlo->tag_set.driver_data = xlo; + + err = blk_mq_alloc_tag_set(&xlo->tag_set); + if (err) + goto out_free_idr; + + xlo->xlo_queue = blk_mq_init_queue(&xlo->tag_set); + if (IS_ERR(xlo->xlo_queue)) { + err = PTR_ERR(xlo->xlo_queue); + goto out_cleanup_tags; + } + xlo->xlo_queue->queuedata = xlo; + + blk_queue_max_hw_sectors(xlo->xlo_queue, BLK_DEF_MAX_SECTORS); + + /* + * By default, we do buffer IO, so it doesn't make sense to enable + * merge because the I/O submitted to backing file is handled page by + * page. For directio mode, merge does help to dispatch bigger request + * to underlayer disk. We will enable merge once directio is enabled. + */ + blk_queue_flag_set(QUEUE_FLAG_NOMERGES, xlo->xlo_queue); + + err = -ENOMEM; + xlo->xlo_fmt = xloop_file_fmt_alloc(); + if (!xlo->xlo_fmt) + goto out_free_queue; + + xloop_file_fmt_set_xlo(xlo->xlo_fmt, xlo); + + err = -ENOMEM; + disk = xlo->xlo_disk = alloc_disk(1 << part_shift); + if (!disk) + goto out_free_file_fmt; + + /* + * Disable partition scanning by default. The in-kernel partition + * scanning can be requested individually per-device during its + * setup. Userspace can always add and remove partitions from all + * devices. The needed partition minors are allocated from the + * extended minor space, the main xloop device numbers will continue + * to match the xloop minors, regardless of the number of partitions + * used. + * + * If max_part is given, partition scanning is globally enabled for + * all xloop devices. The minors for the main xloop devices will be + * multiples of max_part. + * + * Note: Global-for-all-devices, set-only-at-init, read-only module + * parameteters like 'max_xloop' and 'max_part' make things needlessly + * complicated, are too static, inflexible and may surprise + * userspace tools. Parameters like this in general should be avoided. + */ + if (!part_shift) + disk->flags |= GENHD_FL_NO_PART_SCAN; + disk->flags |= GENHD_FL_EXT_DEVT; + atomic_set(&xlo->xlo_refcnt, 0); + xlo->xlo_number = i; + spin_lock_init(&xlo->xlo_lock); + disk->major = LOOP_MAJOR; + disk->first_minor = i << part_shift; + disk->fops = &xlo_fops; + disk->private_data = xlo; + disk->queue = xlo->xlo_queue; + sprintf(disk->disk_name, "xloop%d", i); + add_disk(disk); + *l = xlo; + + /* initialize debugfs entries */ + /* create for each loop device a debugfs directory under 'loop' if + * the 'block' directory exists, otherwise create the loop directory in + * the root directory */ +#ifdef CONFIG_DEBUG_FS + xlo->xlo_dbgfs_dir = debugfs_create_dir(disk->disk_name, xloop_dbgfs_dir); + + if (IS_ERR_OR_NULL(xlo->xlo_dbgfs_dir)) { + err = -ENODEV; + xlo->xlo_dbgfs_dir = NULL; + goto out_free_file_fmt; + } +#endif + + return xlo->xlo_number; + +out_free_file_fmt: + xloop_file_fmt_free(xlo->xlo_fmt); +out_free_queue: + blk_cleanup_queue(xlo->xlo_queue); +out_cleanup_tags: + blk_mq_free_tag_set(&xlo->tag_set); +out_free_idr: + idr_remove(&xloop_index_idr, i); +out_free_dev: + kfree(xlo); +out: + return err; +} + +static void xloop_remove(struct xloop_device *xlo) +{ + xloop_file_fmt_free(xlo->xlo_fmt); + debugfs_remove(xlo->xlo_dbgfs_dir); + del_gendisk(xlo->xlo_disk); + blk_cleanup_queue(xlo->xlo_queue); + blk_mq_free_tag_set(&xlo->tag_set); + put_disk(xlo->xlo_disk); + kfree(xlo); +} + +static int find_free_cb(int id, void *ptr, void *data) +{ + struct xloop_device *xlo = ptr; + struct xloop_device **l = data; + + if (xlo->xlo_state == Xlo_unbound) { + *l = xlo; + return 1; + } + return 0; +} + +static int xloop_lookup(struct xloop_device **l, int i) +{ + struct xloop_device *xlo; + int ret = -ENODEV; + + if (i < 0) { + int err; + + err = idr_for_each(&xloop_index_idr, &find_free_cb, &xlo); + if (err == 1) { + *l = xlo; + ret = xlo->xlo_number; + } + goto out; + } + + /* lookup and return a specific i */ + xlo = idr_find(&xloop_index_idr, i); + if (xlo) { + *l = xlo; + ret = xlo->xlo_number; + } +out: + return ret; +} + +static struct kobject *xloop_probe(dev_t dev, int *part, void *data) +{ + struct xloop_device *xlo; + struct kobject *kobj; + int err; + + mutex_lock(&xloop_ctl_mutex); + err = xloop_lookup(&xlo, MINOR(dev) >> part_shift); + if (err < 0) + err = xloop_add(&xlo, MINOR(dev) >> part_shift); + if (err < 0) + kobj = NULL; + else + kobj = get_disk_and_module(xlo->xlo_disk); + mutex_unlock(&xloop_ctl_mutex); + + *part = 0; + return kobj; +} + +static long xloop_control_ioctl(struct file *file, unsigned int cmd, + unsigned long parm) +{ + struct xloop_device *xlo; + int ret; + + ret = mutex_lock_killable(&xloop_ctl_mutex); + if (ret) + return ret; + + ret = -ENOSYS; + switch (cmd) { + case XLOOP_CTL_ADD: + ret = xloop_lookup(&xlo, parm); + if (ret >= 0) { + ret = -EEXIST; + break; + } + ret = xloop_add(&xlo, parm); + break; + case XLOOP_CTL_REMOVE: + ret = xloop_lookup(&xlo, parm); + if (ret < 0) + break; + if (xlo->xlo_state != Xlo_unbound) { + ret = -EBUSY; + break; + } + if (atomic_read(&xlo->xlo_refcnt) > 0) { + ret = -EBUSY; + break; + } + xlo->xlo_disk->private_data = NULL; + idr_remove(&xloop_index_idr, xlo->xlo_number); + xloop_remove(xlo); + break; + case XLOOP_CTL_GET_FREE: + ret = xloop_lookup(&xlo, -1); + if (ret >= 0) + break; + ret = xloop_add(&xlo, -1); + } + mutex_unlock(&xloop_ctl_mutex); + + return ret; +} + +static const struct file_operations xloop_ctl_fops = { + .open = nonseekable_open, + .unlocked_ioctl = xloop_control_ioctl, + .compat_ioctl = xloop_control_ioctl, + .owner = THIS_MODULE, + .llseek = noop_llseek, +}; + +static struct miscdevice xloop_misc = { + .minor = LOOP_CTRL_MINOR, + .name = "xloop-control", + .fops = &xloop_ctl_fops, +}; + +MODULE_ALIAS_MISCDEV(LOOP_CTRL_MINOR); +MODULE_ALIAS("devname:xloop-control"); + +static int __init xloop_init(void) +{ + int i, nr; + unsigned long range; + struct xloop_device *xlo; + int err; + + part_shift = 0; + if (max_part > 0) { + part_shift = fls(max_part); + + /* + * Adjust max_part according to part_shift as it is exported + * to user space so that user can decide correct minor number + * if [s]he want to create more devices. + * + * Note that -1 is required because partition 0 is reserved + * for the whole disk. + */ + max_part = (1UL << part_shift) - 1; + } + + if ((1UL << part_shift) > DISK_MAX_PARTS) { + err = -EINVAL; + goto err_out; + } + + if (max_xloop > 1UL << (MINORBITS - part_shift)) { + err = -EINVAL; + goto err_out; + } + + /* + * If max_xloop is specified, create that many devices upfront. + * This also becomes a hard limit. If max_xloop is not specified, + * create CONFIG_BLK_DEV_XLOOP_MIN_COUNT xloop devices at module + * init time. xloop devices can be requested on-demand with the + * /dev/xloop-control interface, or be instantiated by accessing + * a 'dead' device node. + */ + if (max_xloop) { + nr = CONFIG_BLK_DEV_LOOP_MIN_COUNT; + range = 1UL << MINORBITS; + } + + err = misc_register(&xloop_misc); + if (err < 0) + goto err_out; + + + if (register_blkdev(LOOP_MAJOR, "xloop")) { + err = -EIO; + goto misc_out; + } + +#ifdef CONFIG_DEBUG_FS + xloop_dbgfs_dir = debugfs_create_dir("xloop", NULL); + if (IS_ERR_OR_NULL(xloop_dbgfs_dir)) { + err = -ENODEV; + goto misc_out; + } +#endif + + blk_register_region(MKDEV(LOOP_MAJOR, 0), range, + THIS_MODULE, xloop_probe, NULL, NULL); + + /* pre-create number of devices given by config or max_xloop */ + mutex_lock(&xloop_ctl_mutex); + for (i = 0; i < nr; i++) + xloop_add(&xlo, i); + mutex_unlock(&xloop_ctl_mutex); + + printk(KERN_INFO "xloop: module loaded\n"); + return 0; + +misc_out: + misc_deregister(&xloop_misc); +err_out: + return err; +} + +static int xloop_exit_cb(int id, void *ptr, void *data) +{ + struct xloop_device *xlo = ptr; + + xloop_remove(xlo); + return 0; +} + +static void __exit xloop_exit(void) +{ + unsigned long range; + + range = max_xloop ? max_xloop << part_shift : 1UL << MINORBITS; + + mutex_lock(&xloop_ctl_mutex); + + idr_for_each(&xloop_index_idr, &xloop_exit_cb, NULL); + idr_destroy(&xloop_index_idr); + + blk_unregister_region(MKDEV(LOOP_MAJOR, 0), range); + unregister_blkdev(LOOP_MAJOR, "xloop"); + +#ifdef CONFIG_DEBUG_FS + debugfs_remove(xloop_dbgfs_dir); +#endif + + misc_deregister(&xloop_misc); + + mutex_unlock(&xloop_ctl_mutex); +} + +module_init(xloop_init); +module_exit(xloop_exit); + +#ifndef MODULE +static int __init max_xloop_setup(char *str) +{ + max_xloop = simple_strtol(str, NULL, 0); + return 1; +} + +__setup("max_xloop=", max_xloop_setup); +#endif diff --git a/kernel/loop_main.h b/kernel/loop_main.h new file mode 100644 index 0000000..1b5851a --- /dev/null +++ b/kernel/loop_main.h @@ -0,0 +1,105 @@ +/* + * loop_main.h + * + * Written by Theodore Ts'o, 3/29/93. + * + * Copyright 1993 by Theodore Ts'o. Redistribution of this file is + * permitted under the GNU General Public License. + */ +#ifndef _LINUX_XLOOP_H +#define _LINUX_XLOOP_H + +#include +#include +#include +#include +#include +#include +#include "uapi/linux/loop.h" +#ifdef CONFIG_DEBUG_FS +#include +#endif + +#include "loop_file_fmt.h" + +/* Possible states of device */ +enum { + Xlo_unbound, + Xlo_bound, + Xlo_rundown, +}; + +struct xloop_func_table; + +struct xloop_device { + int xlo_number; + atomic_t xlo_refcnt; + loff_t xlo_offset; + loff_t xlo_sizelimit; + int xlo_flags; + int (*transfer)(struct xloop_device *, int cmd, + struct page *raw_page, unsigned raw_off, + struct page *xloop_page, unsigned xloop_off, + int size, sector_t real_block); + char xlo_file_name[XLO_NAME_SIZE]; + char xlo_crypt_name[XLO_NAME_SIZE]; + char xlo_encrypt_key[XLO_KEY_SIZE]; + int xlo_encrypt_key_size; + struct xloop_func_table *xlo_encryption; + __u32 xlo_init[2]; + kuid_t xlo_key_owner; /* Who set the key */ + int (*ioctl)(struct xloop_device *, int cmd, + unsigned long arg); + + struct xloop_file_fmt *xlo_fmt; + + struct file * xlo_backing_file; + struct block_device *xlo_device; + void *key_data; + + gfp_t old_gfp_mask; + + spinlock_t xlo_lock; + int xlo_state; + struct kthread_worker worker; + struct task_struct *worker_task; + bool use_dio; + bool sysfs_inited; + + struct request_queue *xlo_queue; + struct blk_mq_tag_set tag_set; + struct gendisk *xlo_disk; + +#ifdef CONFIG_DEBUG_FS + struct dentry *xlo_dbgfs_dir; +#endif +}; + +struct xloop_cmd { + struct kthread_work work; + bool use_aio; /* use AIO interface to handle I/O */ + atomic_t ref; /* only for aio */ + long ret; + struct kiocb iocb; + struct bio_vec *bvec; + struct cgroup_subsys_state *css; +}; + +/* Support for loadable transfer modules */ +struct xloop_func_table { + int number; /* filter type */ + int (*transfer)(struct xloop_device *xlo, int cmd, + struct page *raw_page, unsigned raw_off, + struct page *xloop_page, unsigned xloop_off, + int size, sector_t real_block); + int (*init)(struct xloop_device *, const struct xloop_info64 *); + /* release is called from xloop_unregister_transfer or clr_fd */ + int (*release)(struct xloop_device *); + int (*ioctl)(struct xloop_device *, int cmd, unsigned long arg); + struct module *owner; +}; + +int xloop_register_transfer(struct xloop_func_table *funcs); +int xloop_unregister_transfer(int number); + +#endif diff --git a/kernel/uapi/linux/loop.h b/kernel/uapi/linux/loop.h new file mode 100644 index 0000000..f93f6ad --- /dev/null +++ b/kernel/uapi/linux/loop.h @@ -0,0 +1,125 @@ +/* SPDX-License-Identifier: GPL-1.0+ WITH Linux-syscall-note */ +/* + * include/linux/loop.h + * + * Written by Theodore Ts'o, 3/29/93. + * + * Copyright 1993 by Theodore Ts'o. Redistribution of this file is + * permitted under the GNU General Public License. + */ +#ifndef _UAPI_LINUX_XLOOP_H +#define _UAPI_LINUX_XLOOP_H + + +#define XLO_NAME_SIZE 64 +#define XLO_KEY_SIZE 32 + + +/* + * xloop flags + */ +enum { + XLO_FLAGS_READ_ONLY = 1, + XLO_FLAGS_AUTOCLEAR = 4, + XLO_FLAGS_PARTSCAN = 8, + XLO_FLAGS_DIRECT_IO = 16, +}; + +/* XLO_FLAGS that can be set using XLOOP_SET_STATUS(64) */ +#define XLOOP_SET_STATUS_SETTABLE_FLAGS (XLO_FLAGS_AUTOCLEAR | XLO_FLAGS_PARTSCAN) + +/* XLO_FLAGS that can be cleared using XLOOP_SET_STATUS(64) */ +#define XLOOP_SET_STATUS_CLEARABLE_FLAGS (XLO_FLAGS_AUTOCLEAR) + +/* XLO_FLAGS that can be set using XLOOP_CONFIGURE */ +#define XLOOP_CONFIGURE_SETTABLE_FLAGS (XLO_FLAGS_READ_ONLY | XLO_FLAGS_AUTOCLEAR \ + | XLO_FLAGS_PARTSCAN | XLO_FLAGS_DIRECT_IO) + +#include /* for __kernel_old_dev_t */ +#include /* for __u64 */ + +/* Backwards compatibility version */ +struct xloop_info { + int xlo_number; /* ioctl r/o */ + __kernel_old_dev_t xlo_device; /* ioctl r/o */ + unsigned long xlo_inode; /* ioctl r/o */ + __kernel_old_dev_t xlo_rdevice; /* ioctl r/o */ + int xlo_offset; + int xlo_encrypt_type; + int xlo_encrypt_key_size; /* ioctl w/o */ + int xlo_flags; + char xlo_name[XLO_NAME_SIZE]; + unsigned char xlo_encrypt_key[XLO_KEY_SIZE]; /* ioctl w/o */ + unsigned long xlo_init[2]; + char reserved[4]; + int xlo_file_fmt_type; +}; + +struct xloop_info64 { + __u64 xlo_device; /* ioctl r/o */ + __u64 xlo_inode; /* ioctl r/o */ + __u64 xlo_rdevice; /* ioctl r/o */ + __u64 xlo_offset; + __u64 xlo_sizelimit; /* bytes, 0 == max available */ + __u32 xlo_number; /* ioctl r/o */ + __u32 xlo_encrypt_type; + __u32 xlo_encrypt_key_size; /* ioctl w/o */ + __u32 xlo_flags; + __u8 xlo_file_name[XLO_NAME_SIZE]; + __u8 xlo_crypt_name[XLO_NAME_SIZE]; + __u8 xlo_encrypt_key[XLO_KEY_SIZE]; /* ioctl w/o */ + __u64 xlo_init[2]; + __u32 xlo_file_fmt_type; +}; + +/** + * struct xloop_config - Complete configuration for a xloop device. + * @fd: fd of the file to be used as a backing file for the xloop device. + * @block_size: block size to use; ignored if 0. + * @info: struct xloop_info64 to configure the xloop device with. + * + * This structure is used with the XLOOP_CONFIGURE ioctl, and can be used to + * atomically setup and configure all xloop device parameters at once. + */ +struct xloop_config { + __u32 fd; + __u32 block_size; + struct xloop_info64 info; + __u64 __reserved[8]; +}; + +/* + * xloop filter types + */ +#define XLO_CRYPT_NONE 0 +#define XLO_CRYPT_XOR 1 +#define XLO_CRYPT_DES 2 +#define XLO_CRYPT_FISH2 3 /* Twofish encryption */ +#define XLO_CRYPT_BLOW 4 +#define XLO_CRYPT_CAST128 5 +#define XLO_CRYPT_IDEA 6 +#define XLO_CRYPT_DUMMY 9 +#define XLO_CRYPT_SKIPJACK 10 +#define XLO_CRYPT_CRYPTOAPI 18 +#define MAX_XLO_CRYPT 20 + +/* + * IOCTL commands --- we will commandeer 0x4C ('L') + */ +#define XLOOP_SET_FD 0x4C00 +#define XLOOP_CLR_FD 0x4C01 +#define XLOOP_SET_STATUS 0x4C02 +#define XLOOP_GET_STATUS 0x4C03 +#define XLOOP_SET_STATUS64 0x4C04 +#define XLOOP_GET_STATUS64 0x4C05 +#define XLOOP_CHANGE_FD 0x4C06 +#define XLOOP_SET_CAPACITY 0x4C07 +#define XLOOP_SET_DIRECT_IO 0x4C08 +#define XLOOP_SET_BLOCK_SIZE 0x4C09 +#define XLOOP_CONFIGURE 0x4C0A + +/* /dev/xloop-control interface */ +#define XLOOP_CTL_ADD 0x4C80 +#define XLOOP_CTL_REMOVE 0x4C81 +#define XLOOP_CTL_GET_FREE 0x4C82 +#endif /* _UAPI_LINUX_XLOOP_H */ diff --git a/loop_file_fmt.c b/loop_file_fmt.c deleted file mode 100644 index 062ea0d..0000000 --- a/loop_file_fmt.c +++ /dev/null @@ -1,347 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0 */ -/* - * loop_file_fmt.c - * - * File format subsystem for the xloop device module. - * - * Copyright (C) 2019 Manuel Bentele - */ - -#include -#include - -#include "loop_file_fmt.h" - -/* storage for all registered file format drivers */ -static struct xloop_file_fmt_driver *xloop_file_fmt_drivers[MAX_XLO_FILE_FMT] = { - NULL -}; - -int xloop_file_fmt_register_driver(struct xloop_file_fmt_driver *drv) -{ - int ret = 0; - - if (drv == NULL) - return -EFAULT; - - if (drv->file_fmt_type > MAX_XLO_FILE_FMT) - return -EINVAL; - - if (xloop_file_fmt_drivers[drv->file_fmt_type] == NULL) { - xloop_file_fmt_drivers[drv->file_fmt_type] = drv; - printk(KERN_INFO "xloop_file_fmt: successfully registered file " - "format driver %s", drv->name); - } else { - printk(KERN_WARNING "xloop_file_fmt: driver for file format " - "already registered"); - ret = -EBUSY; - } - - return ret; -} -EXPORT_SYMBOL(xloop_file_fmt_register_driver); - -void xloop_file_fmt_unregister_driver(struct xloop_file_fmt_driver *drv) -{ - if (drv == NULL) - return; - - if (drv->file_fmt_type > MAX_XLO_FILE_FMT) - return; - - xloop_file_fmt_drivers[drv->file_fmt_type] = NULL; - printk(KERN_INFO "xloop_file_fmt: successfully unregistered file " - "format driver %s", drv->name); -} -EXPORT_SYMBOL(xloop_file_fmt_unregister_driver); - -struct xloop_file_fmt *xloop_file_fmt_alloc(void) -{ - return kzalloc(sizeof(struct xloop_file_fmt), GFP_KERNEL); -} - -void xloop_file_fmt_free(struct xloop_file_fmt *xlo_fmt) -{ - kfree(xlo_fmt); -} - -int xloop_file_fmt_set_xlo(struct xloop_file_fmt *xlo_fmt, struct xloop_device *xlo) -{ - if (xlo_fmt == NULL) - return -EINVAL; - - xlo_fmt->xlo = xlo; - - return 0; -} -EXPORT_SYMBOL(xloop_file_fmt_set_xlo); - -struct xloop_device *xloop_file_fmt_get_xlo(struct xloop_file_fmt *xlo_fmt) -{ - return xlo_fmt->xlo; -} -EXPORT_SYMBOL(xloop_file_fmt_get_xlo); - -int xloop_file_fmt_init(struct xloop_file_fmt *xlo_fmt, - u32 file_fmt_type) -{ - struct xloop_file_fmt_ops *ops; - struct module *drv; - int ret = 0; - - if (file_fmt_type > MAX_XLO_FILE_FMT) - return -EINVAL; - - xlo_fmt->file_fmt_type = file_fmt_type; - - if (xlo_fmt->file_fmt_state != file_fmt_uninitialized) { - printk(KERN_WARNING "xloop_file_fmt: file format is " - "initialized already"); - return -EINVAL; - } - - /* check if new file format driver is registered */ - if (xloop_file_fmt_drivers[xlo_fmt->file_fmt_type] == NULL) { - printk(KERN_ERR "xloop_file_fmt: file format driver is not " - "available"); - return -ENODEV; - } - - printk(KERN_INFO "xloop_file_fmt: use file format driver %s", - xloop_file_fmt_drivers[xlo_fmt->file_fmt_type]->name); - - drv = xloop_file_fmt_drivers[xlo_fmt->file_fmt_type]->owner; - if (!try_module_get(drv)) { - printk(KERN_ERR "xloop_file_fmt: file format driver %s can not " - "be accessed", - xloop_file_fmt_drivers[xlo_fmt->file_fmt_type]->name); - return -ENODEV; - } - - ops = xloop_file_fmt_drivers[xlo_fmt->file_fmt_type]->ops; - if (likely(ops->init)) { - ret = ops->init(xlo_fmt); - if (ret < 0) - goto free_drv; - } - - /* after increasing the refcount of file format driver module and - * the successful initialization, the file format is initialized */ - xlo_fmt->file_fmt_state = file_fmt_initialized; - - return ret; - -free_drv: - module_put(drv); - xlo_fmt->file_fmt_state = file_fmt_uninitialized; - return ret; -} - -void xloop_file_fmt_exit(struct xloop_file_fmt *xlo_fmt) -{ - struct xloop_file_fmt_ops *ops; - struct module *drv; - - if (xlo_fmt->file_fmt_state != file_fmt_initialized) { - printk(KERN_WARNING "xloop_file_fmt: file format is " - "uninitialized already"); - return; - } - - ops = xloop_file_fmt_drivers[xlo_fmt->file_fmt_type]->ops; - if (likely(ops->exit)) - ops->exit(xlo_fmt); - - drv = xloop_file_fmt_drivers[xlo_fmt->file_fmt_type]->owner; - module_put(drv); - - /* after decreasing the refcount of file format driver module, - * the file format is uninitialized */ - xlo_fmt->file_fmt_state = file_fmt_uninitialized; -} - -int xloop_file_fmt_read(struct xloop_file_fmt *xlo_fmt, - struct request *rq) -{ - struct xloop_file_fmt_ops *ops; - - if (unlikely(xlo_fmt->file_fmt_state != file_fmt_initialized)) { - printk(KERN_ERR "xloop_file_fmt: file format is " - "not initialized, can not read"); - return -EINVAL; - } - - ops = xloop_file_fmt_drivers[xlo_fmt->file_fmt_type]->ops; - if (likely(ops->read)) - return ops->read(xlo_fmt, rq); - else - return -EIO; -} - -int xloop_file_fmt_read_aio(struct xloop_file_fmt *xlo_fmt, - struct request *rq) -{ - struct xloop_file_fmt_ops *ops; - - if (unlikely(xlo_fmt->file_fmt_state != file_fmt_initialized)) { - printk(KERN_ERR "xloop_file_fmt: file format is " - "not initialized, can not read aio"); - return -EINVAL; - } - - ops = xloop_file_fmt_drivers[xlo_fmt->file_fmt_type]->ops; - if (likely(ops->read_aio)) - return ops->read_aio(xlo_fmt, rq); - else - return -EIO; -} - -int xloop_file_fmt_write(struct xloop_file_fmt *xlo_fmt, - struct request *rq) -{ - struct xloop_file_fmt_ops *ops; - - if (unlikely(xlo_fmt->file_fmt_state != file_fmt_initialized)) { - printk(KERN_ERR "xloop_file_fmt: file format is " - "not initialized, can not write"); - return -EINVAL; - } - - ops = xloop_file_fmt_drivers[xlo_fmt->file_fmt_type]->ops; - if (likely(ops->write)) - return ops->write(xlo_fmt, rq); - else - return -EIO; -} - -int xloop_file_fmt_write_aio(struct xloop_file_fmt *xlo_fmt, - struct request *rq) -{ - struct xloop_file_fmt_ops *ops; - - if (unlikely(xlo_fmt->file_fmt_state != file_fmt_initialized)) { - printk(KERN_ERR "xloop_file_fmt: file format is " - "not initialized, can not write aio"); - return -EINVAL; - } - - ops = xloop_file_fmt_drivers[xlo_fmt->file_fmt_type]->ops; - if (likely(ops->write_aio)) - return ops->write_aio(xlo_fmt, rq); - else - return -EIO; -} - -int xloop_file_fmt_write_zeros(struct xloop_file_fmt *xlo_fmt, - struct request *rq) -{ - struct xloop_file_fmt_ops *ops; - - if (unlikely(xlo_fmt->file_fmt_state != file_fmt_initialized)) { - printk(KERN_ERR "xloop_file_fmt: file format is " - "not initialized, can not write zeros"); - return -EINVAL; - } - - ops = xloop_file_fmt_drivers[xlo_fmt->file_fmt_type]->ops; - if (likely(ops->write_zeros)) - return ops->write_zeros(xlo_fmt, rq); - else - return -EIO; -} - -int xloop_file_fmt_discard(struct xloop_file_fmt *xlo_fmt, - struct request *rq) -{ - struct xloop_file_fmt_ops *ops; - - if (unlikely(xlo_fmt->file_fmt_state != file_fmt_initialized)) { - printk(KERN_ERR "xloop_file_fmt: file format is " - "not initialized, can not discard"); - return -EINVAL; - } - - ops = xloop_file_fmt_drivers[xlo_fmt->file_fmt_type]->ops; - if (likely(ops->discard)) - return ops->discard(xlo_fmt, rq); - else - return -EIO; -} - -int xloop_file_fmt_flush(struct xloop_file_fmt *xlo_fmt) -{ - struct xloop_file_fmt_ops *ops; - - if (unlikely(xlo_fmt->file_fmt_state != file_fmt_initialized)) { - printk(KERN_ERR "xloop_file_fmt: file format is " - "not initialized, can not flush"); - return -EINVAL; - } - - ops = xloop_file_fmt_drivers[xlo_fmt->file_fmt_type]->ops; - if (likely(ops->flush)) - return ops->flush(xlo_fmt); - - return 0; -} - -loff_t xloop_file_fmt_sector_size(struct xloop_file_fmt *xlo_fmt, - struct file *file, loff_t offset, loff_t sizelimit) -{ - struct xloop_file_fmt_ops *ops; - - if (unlikely(xlo_fmt->file_fmt_state != file_fmt_initialized)) { - printk(KERN_ERR "xloop_file_fmt: file format is " - "not initialized, can not read sector size"); - return 0; - } - - ops = xloop_file_fmt_drivers[xlo_fmt->file_fmt_type]->ops; - if (likely(ops->sector_size)) - return ops->sector_size(xlo_fmt, file, offset, sizelimit); - else - return 0; -} - -int xloop_file_fmt_change(struct xloop_file_fmt *xlo_fmt, - u32 file_fmt_type_new) -{ - if (file_fmt_type_new > MAX_XLO_FILE_FMT) - return -EINVAL; - - /* Unload the old file format driver if the file format is - * initialized */ - if (xlo_fmt->file_fmt_state == file_fmt_initialized) - xloop_file_fmt_exit(xlo_fmt); - - /* Load the new file format driver because the file format is - * uninitialized now */ - return xloop_file_fmt_init(xlo_fmt, file_fmt_type_new); -} - -ssize_t xloop_file_fmt_print_type(u32 file_fmt_type, char *file_fmt_name) -{ - ssize_t len = 0; - - switch (file_fmt_type) { - case XLO_FILE_FMT_RAW: - len = sprintf(file_fmt_name, "%s", "RAW"); - break; - case XLO_FILE_FMT_QCOW: - len = sprintf(file_fmt_name, "%s", "QCOW"); - break; - case XLO_FILE_FMT_VDI: - len = sprintf(file_fmt_name, "%s", "VDI"); - break; - case XLO_FILE_FMT_VMDK: - len = sprintf(file_fmt_name, "%s", "VMDK"); - break; - default: - len = sprintf(file_fmt_name, "%s", "ERROR: Unsupported xloop " - "file format!"); - break; - } - - return len; -} -EXPORT_SYMBOL(xloop_file_fmt_print_type); diff --git a/loop_file_fmt.h b/loop_file_fmt.h deleted file mode 100644 index 38d6a3b..0000000 --- a/loop_file_fmt.h +++ /dev/null @@ -1,380 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0 */ -/* - * loop_file_fmt.h - * - * File format subsystem for the xloop device module. - * - * Copyright (C) 2019 Manuel Bentele - */ - -#ifndef _LINUX_XLOOP_FILE_FMT_H -#define _LINUX_XLOOP_FILE_FMT_H - -#include "loop_main.h" - -struct xloop_file_fmt; - -#define XLO_FILE_FMT_RAW 0 -#define XLO_FILE_FMT_QCOW 1 -#define XLO_FILE_FMT_VDI 2 -#define XLO_FILE_FMT_VMDK 3 -#define MAX_XLO_FILE_FMT (XLO_FILE_FMT_VMDK + 1) - -/** - * struct xloop_file_fmt_ops - File format subsystem operations - * - * Data structure representing the file format subsystem interface. - */ -struct xloop_file_fmt_ops { - /** - * @init: Initialization callback function - */ - int (*init) (struct xloop_file_fmt *xlo_fmt); - - /** - * @exit: Release callback function - */ - void (*exit) (struct xloop_file_fmt *xlo_fmt); - - /** - * @read: Read IO callback function - */ - int (*read) (struct xloop_file_fmt *xlo_fmt, - struct request *rq); - - /** - * @write: Write IO callback function - */ - int (*write) (struct xloop_file_fmt *xlo_fmt, - struct request *rq); - - /** - * @read_aio: Asynchronous read IO callback function - */ - int (*read_aio) (struct xloop_file_fmt *xlo_fmt, - struct request *rq); - - /** - * @write_aio: Asynchronous write IO callback function - */ - int (*write_aio) (struct xloop_file_fmt *xlo_fmt, - struct request *rq); - - /** - * @zero: Zero (discard) IO callback function - */ - int (*write_zeros) (struct xloop_file_fmt *xlo_fmt, - struct request *rq); - - /** - * @discard: Discard IO callback function - */ - int (*discard) (struct xloop_file_fmt *xlo_fmt, - struct request *rq); - - /** - * @flush: Flush callback function - */ - int (*flush) (struct xloop_file_fmt *xlo_fmt); - - /** - * @sector_size: Get sector size callback function - */ - loff_t (*sector_size) (struct xloop_file_fmt *xlo_fmt, - struct file *file, loff_t offset, loff_t sizelimit); -}; - -/** - * struct xloop_file_fmt_driver - File format subsystem driver - * - * Data structure to implement file format drivers for the file format - * subsystem. - */ -struct xloop_file_fmt_driver { - /** - * @name: Name of the file format driver - */ - const char *name; - - /** - * @file_fmt_type: xloop file format type of the file format driver - */ - const u32 file_fmt_type; - - /** - * @ops: Driver's implemented file format operations - */ - struct xloop_file_fmt_ops *ops; - - /** - * @ops: Owner of the file format driver - */ - struct module *owner; -}; - -/* - * states of the file format - * - * transitions: - * xloop_file_fmt_init(...) - * ---> uninitialized ------------------------------> initialized - * xloop_file_fmt_exit(...) - * initialized ------------------------------> uninitialized - * xloop_file_fmt_read(...) - * initialized ------------------------------> initialized - * xloop_file_fmt_read_aio(...) - * initialized ------------------------------> initialized - * xloop_file_fmt_write(...) - * initialized ------------------------------> initialized - * xloop_file_fmt_write_aio(...) - * initialized ------------------------------> initialized - * xloop_file_fmt_discard(...) - * initialized ------------------------------> initialized - * xloop_file_fmt_flush(...) - * initialized ------------------------------> initialized - * xloop_file_fmt_sector_size(...) - * initialized ------------------------------> initialized - * - * xloop_file_fmt_change(...) - * +-----------------------------------------------------------+ - * | exit(...) init(...) | - * | initialized -------> uninitialized -------> initialized | - * +-----------------------------------------------------------+ - */ -enum { - file_fmt_uninitialized = 0, - file_fmt_initialized -}; - -/** - * struct xloop_file_fmt - xloop file format - * - * Data structure to use with the file format the xloop file format subsystem. - */ -struct xloop_file_fmt { - /** - * @file_fmt_type: Current type of the xloop file format - */ - u32 file_fmt_type; - - /** - * @file_fmt_state: Current state of the xloop file format - */ - int file_fmt_state; - - /** - * @xlo: Link to a file format's xloop device - */ - struct xloop_device *xlo; - - /** - * @private_data: Optional link to a file format's driver specific data - */ - void *private_data; -}; - - -/* subsystem functions for the driver implementation */ - -/** - * xloop_file_fmt_register_driver - Register a xloop file format driver - * @drv: File format driver - * - * Registers the specified xloop file format driver @drv by the xloop file format - * subsystem. - */ -extern int xloop_file_fmt_register_driver(struct xloop_file_fmt_driver *drv); - -/** - * xloop_file_fmt_unregister_driver - Unregister a xloop file format driver - * @drv: File format driver - * - * Unregisters the specified xloop file format driver @drv from the xloop file - * format subsystem. - */ -extern void xloop_file_fmt_unregister_driver(struct xloop_file_fmt_driver *drv); - - -/* subsystem functions for subsystem usage */ - -/** - * xloop_file_fmt_alloc - Allocate a xloop file format - * - * Dynamically allocates a xloop file format and returns a pointer to the - * created xloop file format. - */ -extern struct xloop_file_fmt *xloop_file_fmt_alloc(void); - -/** - * xloop_file_fmt_free - Free an allocated xloop file format - * @xlo_fmt: xloop file format - * - * Frees the already allocated xloop file format @xlo_fmt. - */ -extern void xloop_file_fmt_free(struct xloop_file_fmt *xlo_fmt); - -/** - * xloop_file_fmt_set_xlo - Set the xloop file format's xloop device - * @xlo_fmt: xloop file format - * @xlo: xloop device - * - * The link to the xloop device @xlo is set in the xloop file format @xlo_fmt. - */ -extern int xloop_file_fmt_set_xlo(struct xloop_file_fmt *xlo_fmt, - struct xloop_device *xlo); - -/** - * xloop_file_fmt_get_xlo - Get the xloop file format's xloop device - * @xlo_fmt: xloop file format - * - * Returns a pointer to the xloop device of the xloop file format @xlo_fmt. - */ -extern struct xloop_device *xloop_file_fmt_get_xlo(struct xloop_file_fmt *xlo_fmt); - -/** - * xloop_file_fmt_init - Initialize a xloop file format - * @xlo_fmt: xloop file format - * @file_fmt_type: Type of the file format - * - * Initializes the specified xloop file format @xlo_fmt and sets up the correct - * file format type @file_fmt_type. Depending on @file_fmt_type, the correct - * xloop file format driver is loaded in the subsystems backend. If no xloop file - * format driver for the specified file format is available an error is - * returned. - */ -extern int xloop_file_fmt_init(struct xloop_file_fmt *xlo_fmt, - u32 file_fmt_type); - -/** - * xloop_file_fmt_exit - Release a xloop file format - * @xlo_fmt: xloop file format - * - * Releases the specified xloop file format @xlo_fmt and all its resources. - */ -extern void xloop_file_fmt_exit(struct xloop_file_fmt *xlo_fmt); - -/** - * xloop_file_fmt_read - Read IO from a xloop file format - * @xlo_fmt: xloop file format - * @rq: IO Request - * - * Reads IO from the file format's xloop device by sending the IO read request - * @rq to the xloop file format subsystem. The subsystem calls the registered - * callback function of the suitable xloop file format driver. - */ -extern int xloop_file_fmt_read(struct xloop_file_fmt *xlo_fmt, - struct request *rq); - -/** - * xloop_file_fmt_read_aio - Read IO from a xloop file format asynchronously - * @xlo_fmt: xloop file format - * @rq: IO Request - * - * Reads IO from the file format's xloop device asynchronously by sending the - * IO read aio request @rq to the xloop file format subsystem. The subsystem - * calls the registered callback function of the suitable xloop file format - * driver. - */ -extern int xloop_file_fmt_read_aio(struct xloop_file_fmt *xlo_fmt, - struct request *rq); - -/** - * xloop_file_fmt_write - Write IO to a xloop file format - * @xlo_fmt: xloop file format - * @rq: IO Request - * - * Write IO to the file format's xloop device by sending the IO write request - * @rq to the xloop file format subsystem. The subsystem calls the registered - * callback function of the suitable xloop file format driver. - */ -extern int xloop_file_fmt_write(struct xloop_file_fmt *xlo_fmt, - struct request *rq); - -/** - * xloop_file_fmt_write_aio - Write IO to a xloop file format asynchronously - * @xlo_fmt: xloop file format - * @rq: IO Request - * - * Write IO to the file format's xloop device asynchronously by sending the - * IO write aio request @rq to the xloop file format subsystem. The subsystem - * calls the registered callback function of the suitable xloop file format - * driver. - */ -extern int xloop_file_fmt_write_aio(struct xloop_file_fmt *xlo_fmt, - struct request *rq); - -/** - * xloop_file_fmt_write_zeros - Zero (discard) IO on a xloop file format - * @xlo_fmt: xloop file format - * @rq: IO Request - * - * Zero (discard) IO on the file format's xloop device by sending the IO write - * zeros request @rq to the xloop file format subsystem. The subsystem calls the - * registered callback function of the suitable xloop file format driver. - */ -extern int xloop_file_fmt_write_zeros(struct xloop_file_fmt *xlo_fmt, - struct request *rq); - -/** - * xloop_file_fmt_discard - Discard IO on a xloop file format - * @xlo_fmt: xloop file format - * @rq: IO Request - * - * Discard IO on the file format's xloop device by sending the IO discard - * request @rq to the xloop file format subsystem. The subsystem calls the - * registered callback function of the suitable xloop file format driver. - */ -extern int xloop_file_fmt_discard(struct xloop_file_fmt *xlo_fmt, - struct request *rq); - -/** - * xloop_file_fmt_flush - Flush a xloop file format - * @xlo_fmt: xloop file format - * - * Flush the file format's xloop device by calling the registered callback - * function of the suitable xloop file format driver. - */ -extern int xloop_file_fmt_flush(struct xloop_file_fmt *xlo_fmt); - -/** - * xloop_file_fmt_sector_size - Get sector size of a xloop file format - * @xlo_fmt: xloop file format - * @file: xloop file formats file for sector size calculation - * @offset: Offset within the file for sector size calculation - * @sizelimit: Sizelimit of the file for sector size calculation - * - * Returns the physical sector size of the given xloop file format's file. - * If the xloop file format implements a sparse disk image format, then this - * function returns the virtual sector size. - */ -extern loff_t xloop_file_fmt_sector_size(struct xloop_file_fmt *xlo_fmt, - struct file *file, loff_t offset, loff_t sizelimit); - -/** - * xloop_file_fmt_change - Change the xloop file format's type - * @xlo_fmt: xloop file format - * @file_fmt_type_new: xloop file format type - * - * Changes the file format type of the already initialized xloop file format - * @xlo_fmt. Therefore, the function releases the old file format and frees all - * of its resources before the xloop file format @xlo_fmt is initialized and set - * up with the new file format @file_fmt_type_new. - */ -extern int xloop_file_fmt_change(struct xloop_file_fmt *xlo_fmt, - u32 file_fmt_type_new); - - -/* helper functions of the subsystem */ - -/** - * xloop_file_fmt_print_type - Convert file format type to string - * @file_fmt_type: xloop file format type - * @file_fmt_name: xloop file format type string - * - * Converts the specified numeric @file_fmt_type value into a human readable - * string stating the file format as string in @file_fmt_name. - */ -extern ssize_t xloop_file_fmt_print_type(u32 file_fmt_type, - char *file_fmt_name); - -#endif diff --git a/loop_file_fmt_qcow_cache.c b/loop_file_fmt_qcow_cache.c deleted file mode 100644 index 4ef772a..0000000 --- a/loop_file_fmt_qcow_cache.c +++ /dev/null @@ -1,218 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0 */ -/* - * loop_file_fmt_qcow_cache.c - * - * QCOW file format driver for the xloop device module. - * - * Ported QCOW2 implementation of the QEMU project (GPL-2.0): - * L2/refcount table cache for the QCOW2 format. - * - * The copyright (C) 2010 of the original code is owned by - * Kevin Wolf - * - * Copyright (C) 2019 Manuel Bentele - */ - -#include -#include -#include -#include -#include -#include - -#include "loop_file_fmt_qcow_main.h" -#include "loop_file_fmt_qcow_cache.h" - -static inline void *__xloop_file_fmt_qcow_cache_get_table_addr( - struct xloop_file_fmt_qcow_cache *c, int table) -{ - return (u8 *) c->table_array + (size_t) table * c->table_size; -} - -static inline int __xloop_file_fmt_qcow_cache_get_table_idx( - struct xloop_file_fmt_qcow_cache *c, void *table) -{ - ptrdiff_t table_offset = (u8 *) table - (u8 *) c->table_array; - int idx = table_offset / c->table_size; - ASSERT(idx >= 0 && idx < c->size && table_offset % c->table_size == 0); - return idx; -} - -static inline const char *__xloop_file_fmt_qcow_cache_get_name( - struct xloop_file_fmt *xlo_fmt, struct xloop_file_fmt_qcow_cache *c) -{ - struct xloop_file_fmt_qcow_data *qcow_data = xlo_fmt->private_data; - - if (c == qcow_data->refcount_block_cache) { - return "refcount block"; - } else if (c == qcow_data->l2_table_cache) { - return "L2 table"; - } else { - /* do not abort, because this is not critical */ - return "unknown"; - } -} - -struct xloop_file_fmt_qcow_cache *xloop_file_fmt_qcow_cache_create( - struct xloop_file_fmt *xlo_fmt, int num_tables, unsigned table_size) -{ -#ifdef CONFIG_DEBUG_DRIVER - struct xloop_file_fmt_qcow_data *qcow_data = xlo_fmt->private_data; -#endif - struct xloop_file_fmt_qcow_cache *c; - - ASSERT(num_tables > 0); - ASSERT(is_power_of_2(table_size)); - ASSERT(table_size >= (1 << QCOW_MIN_CLUSTER_BITS)); - ASSERT(table_size <= qcow_data->cluster_size); - - c = kzalloc(sizeof(*c), GFP_KERNEL); - if (!c) { - return NULL; - } - - c->size = num_tables; - c->table_size = table_size; - c->entries = vzalloc(sizeof(struct xloop_file_fmt_qcow_cache_table) * - num_tables); - c->table_array = vzalloc(num_tables * c->table_size); - - if (!c->entries || !c->table_array) { - vfree(c->table_array); - vfree(c->entries); - kfree(c); - c = NULL; - } - - return c; -} - -void xloop_file_fmt_qcow_cache_destroy(struct xloop_file_fmt *xlo_fmt) -{ - struct xloop_file_fmt_qcow_data *qcow_data = xlo_fmt->private_data; - struct xloop_file_fmt_qcow_cache *c = qcow_data->l2_table_cache; - int i; - - for (i = 0; i < c->size; i++) { - ASSERT(c->entries[i].ref == 0); - } - - vfree(c->table_array); - vfree(c->entries); - kfree(c); -} - -static int __xloop_file_fmt_qcow_cache_entry_flush( - struct xloop_file_fmt_qcow_cache *c, int i) -{ - if (!c->entries[i].dirty || !c->entries[i].offset) { - return 0; - } else { - printk(KERN_ERR "xloop_file_fmt_qcow: Flush dirty cache tables " - "is not supported yet\n"); - return -ENOSYS; - } -} - -static int __xloop_file_fmt_qcow_cache_do_get(struct xloop_file_fmt *xlo_fmt, - struct xloop_file_fmt_qcow_cache *c, u64 offset, void **table, - bool read_from_disk) -{ - struct xloop_device *xlo = xloop_file_fmt_get_xlo(xlo_fmt); - int i; - int ret; - int lookup_index; - u64 min_lru_counter = U64_MAX; - int min_lru_index = -1; - u64 read_offset; - size_t len; - - ASSERT(offset != 0); - - if (!IS_ALIGNED(offset, c->table_size)) { - printk_ratelimited(KERN_ERR "xloop_file_fmt_qcow: Cannot get " - "entry from %s cache: offset %llx is unaligned\n", - __xloop_file_fmt_qcow_cache_get_name(xlo_fmt, c), - offset); - return -EIO; - } - - /* Check if the table is already cached */ - i = lookup_index = (offset / c->table_size * 4) % c->size; - do { - const struct xloop_file_fmt_qcow_cache_table *t = - &c->entries[i]; - if (t->offset == offset) { - goto found; - } - if (t->ref == 0 && t->lru_counter < min_lru_counter) { - min_lru_counter = t->lru_counter; - min_lru_index = i; - } - if (++i == c->size) { - i = 0; - } - } while (i != lookup_index); - - if (min_lru_index == -1) { - BUG(); - panic("Oops: This can't happen in current synchronous code, " - "but leave the check here as a reminder for whoever " - "starts using AIO with the QCOW cache"); - } - - /* Cache miss: write a table back and replace it */ - i = min_lru_index; - - ret = __xloop_file_fmt_qcow_cache_entry_flush(c, i); - if (ret < 0) { - return ret; - } - - c->entries[i].offset = 0; - if (read_from_disk) { - read_offset = offset; - len = kernel_read(xlo->xlo_backing_file, - __xloop_file_fmt_qcow_cache_get_table_addr(c, i), - c->table_size, &read_offset); - if (len < 0) { - len = ret; - return ret; - } - } - - c->entries[i].offset = offset; - - /* And return the right table */ -found: - c->entries[i].ref++; - *table = __xloop_file_fmt_qcow_cache_get_table_addr(c, i); - - return 0; -} - -int xloop_file_fmt_qcow_cache_get(struct xloop_file_fmt *xlo_fmt, u64 offset, - void **table) -{ - struct xloop_file_fmt_qcow_data *qcow_data = xlo_fmt->private_data; - struct xloop_file_fmt_qcow_cache *c = qcow_data->l2_table_cache; - - return __xloop_file_fmt_qcow_cache_do_get(xlo_fmt, c, offset, table, - true); -} - -void xloop_file_fmt_qcow_cache_put(struct xloop_file_fmt *xlo_fmt, void **table) -{ - struct xloop_file_fmt_qcow_data *qcow_data = xlo_fmt->private_data; - struct xloop_file_fmt_qcow_cache *c = qcow_data->l2_table_cache; - int i = __xloop_file_fmt_qcow_cache_get_table_idx(c, *table); - - c->entries[i].ref--; - *table = NULL; - - if (c->entries[i].ref == 0) { - c->entries[i].lru_counter = ++c->lru_counter; - } - - ASSERT(c->entries[i].ref >= 0); -} diff --git a/loop_file_fmt_qcow_cache.h b/loop_file_fmt_qcow_cache.h deleted file mode 100644 index d2f1010..0000000 --- a/loop_file_fmt_qcow_cache.h +++ /dev/null @@ -1,51 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0 */ -/* - * xloop_file_fmt_qcow_cache.h - * - * Ported QCOW2 implementation of the QEMU project (GPL-2.0): - * L2/refcount table cache for the QCOW2 format. - * - * The copyright (C) 2010 of the original code is owned by - * Kevin Wolf - * - * Copyright (C) 2019 Manuel Bentele - */ - -#ifndef _LINUX_XLOOP_FILE_FMT_QCOW_CACHE_H -#define _LINUX_XLOOP_FILE_FMT_QCOW_CACHE_H - -#include "loop_file_fmt.h" - -struct xloop_file_fmt_qcow_cache_table { - s64 offset; - u64 lru_counter; - int ref; - bool dirty; -}; - -struct xloop_file_fmt_qcow_cache { - struct xloop_file_fmt_qcow_cache_table *entries; - struct xloop_file_fmt_qcow_cache *depends; - int size; - int table_size; - bool depends_on_flush; - void *table_array; - u64 lru_counter; - u64 cache_clean_lru_counter; -}; - -extern struct xloop_file_fmt_qcow_cache *xloop_file_fmt_qcow_cache_create( - struct xloop_file_fmt *xlo_fmt, - int num_tables, - unsigned table_size); - -extern void xloop_file_fmt_qcow_cache_destroy(struct xloop_file_fmt *xlo_fmt); - -extern int xloop_file_fmt_qcow_cache_get(struct xloop_file_fmt *xlo_fmt, - u64 offset, - void **table); - -extern void xloop_file_fmt_qcow_cache_put(struct xloop_file_fmt *xlo_fmt, - void **table); - -#endif diff --git a/loop_file_fmt_qcow_cluster.c b/loop_file_fmt_qcow_cluster.c deleted file mode 100644 index 593a173..0000000 --- a/loop_file_fmt_qcow_cluster.c +++ /dev/null @@ -1,270 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0 */ -/* - * xloop_file_fmt_qcow_cluster.c - * - * Ported QCOW2 implementation of the QEMU project (GPL-2.0): - * Cluster calculation and lookup for the QCOW2 format. - * - * The copyright (C) 2004-2006 of the original code is owned by Fabrice Bellard. - * - * Copyright (C) 2019 Manuel Bentele - */ - -#include -#include - -#include "loop_file_fmt.h" -#include "loop_file_fmt_qcow_main.h" -#include "loop_file_fmt_qcow_cache.h" -#include "loop_file_fmt_qcow_cluster.h" - -/* - * Loads a L2 slice into memory (L2 slices are the parts of L2 tables - * that are loaded by the qcow2 cache). If the slice is in the cache, - * the cache is used; otherwise the L2 slice is loaded from the image - * file. - */ -static int __xloop_file_fmt_qcow_cluster_l2_load(struct xloop_file_fmt *xlo_fmt, - u64 offset, u64 l2_offset, u64 **l2_slice) -{ - struct xloop_file_fmt_qcow_data *qcow_data = xlo_fmt->private_data; - - int start_of_slice = sizeof(u64) * ( - xloop_file_fmt_qcow_offset_to_l2_index(qcow_data, offset) - - xloop_file_fmt_qcow_offset_to_l2_slice_index(qcow_data, offset) - ); - - ASSERT(qcow_data->l2_table_cache != NULL); - return xloop_file_fmt_qcow_cache_get(xlo_fmt, l2_offset + start_of_slice, - (void **) l2_slice); -} - -/* - * Checks how many clusters in a given L2 slice are contiguous in the image - * file. As soon as one of the flags in the bitmask stop_flags changes compared - * to the first cluster, the search is stopped and the cluster is not counted - * as contiguous. (This allows it, for example, to stop at the first compressed - * cluster which may require a different handling) - */ -static int __xloop_file_fmt_qcow_cluster_count_contiguous( - struct xloop_file_fmt *xlo_fmt, int nb_clusters, int cluster_size, - u64 *l2_slice, u64 stop_flags) -{ - int i; - enum xloop_file_fmt_qcow_cluster_type first_cluster_type; - u64 mask = stop_flags | L2E_OFFSET_MASK | QCOW_OFLAG_COMPRESSED; - u64 first_entry = be64_to_cpu(l2_slice[0]); - u64 offset = first_entry & mask; - - first_cluster_type = xloop_file_fmt_qcow_get_cluster_type(xlo_fmt, - first_entry); - if (first_cluster_type == QCOW_CLUSTER_UNALLOCATED) { - return 0; - } - - /* must be allocated */ - ASSERT(first_cluster_type == QCOW_CLUSTER_NORMAL || - first_cluster_type == QCOW_CLUSTER_ZERO_ALLOC); - - for (i = 0; i < nb_clusters; i++) { - u64 l2_entry = be64_to_cpu(l2_slice[i]) & mask; - if (offset + (u64) i * cluster_size != l2_entry) { - break; - } - } - - return i; -} - -/* - * Checks how many consecutive unallocated clusters in a given L2 - * slice have the same cluster type. - */ -static int __xloop_file_fmt_qcow_cluster_count_contiguous_unallocated( - struct xloop_file_fmt *xlo_fmt, int nb_clusters, u64 *l2_slice, - enum xloop_file_fmt_qcow_cluster_type wanted_type) -{ - int i; - - ASSERT(wanted_type == QCOW_CLUSTER_ZERO_PLAIN || - wanted_type == QCOW_CLUSTER_UNALLOCATED); - - for (i = 0; i < nb_clusters; i++) { - u64 entry = be64_to_cpu(l2_slice[i]); - enum xloop_file_fmt_qcow_cluster_type type = - xloop_file_fmt_qcow_get_cluster_type(xlo_fmt, entry); - - if (type != wanted_type) { - break; - } - } - - return i; -} - -/* - * For a given offset of the virtual disk, find the cluster type and offset in - * the qcow2 file. The offset is stored in *cluster_offset. - * - * On entry, *bytes is the maximum number of contiguous bytes starting at - * offset that we are interested in. - * - * On exit, *bytes is the number of bytes starting at offset that have the same - * cluster type and (if applicable) are stored contiguously in the image file. - * Compressed clusters are always returned one by one. - * - * Returns the cluster type (QCOW2_CLUSTER_*) on success, -errno in error - * cases. - */ -int xloop_file_fmt_qcow_cluster_get_offset(struct xloop_file_fmt *xlo_fmt, - u64 offset, unsigned int *bytes, u64 *cluster_offset) -{ - struct xloop_file_fmt_qcow_data *qcow_data = xlo_fmt->private_data; - unsigned int l2_index; - u64 l1_index, l2_offset, *l2_slice; - int c; - unsigned int offset_in_cluster; - u64 bytes_available, bytes_needed, nb_clusters; - enum xloop_file_fmt_qcow_cluster_type type; - int ret; - - offset_in_cluster = xloop_file_fmt_qcow_offset_into_cluster(qcow_data, - offset); - bytes_needed = (u64) *bytes + offset_in_cluster; - - /* compute how many bytes there are between the start of the cluster - * containing offset and the end of the l2 slice that contains - * the entry pointing to it */ - bytes_available = ((u64)( - qcow_data->l2_slice_size - - xloop_file_fmt_qcow_offset_to_l2_slice_index(qcow_data, offset)) - ) << qcow_data->cluster_bits; - - if (bytes_needed > bytes_available) { - bytes_needed = bytes_available; - } - - *cluster_offset = 0; - - /* seek to the l2 offset in the l1 table */ - l1_index = xloop_file_fmt_qcow_offset_to_l1_index(qcow_data, offset); - if (l1_index >= qcow_data->l1_size) { - type = QCOW_CLUSTER_UNALLOCATED; - goto out; - } - - l2_offset = qcow_data->l1_table[l1_index] & L1E_OFFSET_MASK; - if (!l2_offset) { - type = QCOW_CLUSTER_UNALLOCATED; - goto out; - } - - if (xloop_file_fmt_qcow_offset_into_cluster(qcow_data, l2_offset)) { - printk_ratelimited(KERN_ERR "xloop_file_fmt_qcow: L2 table " - "offset %llx unaligned (L1 index: %llx)", l2_offset, - l1_index); - return -EIO; - } - - /* load the l2 slice in memory */ - ret = __xloop_file_fmt_qcow_cluster_l2_load(xlo_fmt, offset, l2_offset, - &l2_slice); - if (ret < 0) { - return ret; - } - - /* find the cluster offset for the given disk offset */ - l2_index = xloop_file_fmt_qcow_offset_to_l2_slice_index(qcow_data, - offset); - *cluster_offset = be64_to_cpu(l2_slice[l2_index]); - - nb_clusters = xloop_file_fmt_qcow_size_to_clusters(qcow_data, - bytes_needed); - /* bytes_needed <= *bytes + offset_in_cluster, both of which are - * unsigned integers; the minimum cluster size is 512, so this - * assertion is always true */ - ASSERT(nb_clusters <= INT_MAX); - - type = xloop_file_fmt_qcow_get_cluster_type(xlo_fmt, *cluster_offset); - if (qcow_data->qcow_version < 3 && ( - type == QCOW_CLUSTER_ZERO_PLAIN || - type == QCOW_CLUSTER_ZERO_ALLOC)) { - printk_ratelimited(KERN_ERR "xloop_file_fmt_qcow: zero cluster " - "entry found in pre-v3 image (L2 offset: %llx, " - "L2 index: %x)\n", l2_offset, l2_index); - ret = -EIO; - goto fail; - } - switch (type) { - case QCOW_CLUSTER_COMPRESSED: - if (xloop_file_fmt_qcow_has_data_file(xlo_fmt)) { - printk_ratelimited(KERN_ERR "xloop_file_fmt_qcow: " - "compressed cluster entry found in image with " - "external data file (L2 offset: %llx, " - "L2 index: %x)", l2_offset, l2_index); - ret = -EIO; - goto fail; - } - /* Compressed clusters can only be processed one by one */ - c = 1; - *cluster_offset &= L2E_COMPRESSED_OFFSET_SIZE_MASK; - break; - case QCOW_CLUSTER_ZERO_PLAIN: - case QCOW_CLUSTER_UNALLOCATED: - /* how many empty clusters ? */ - c = __xloop_file_fmt_qcow_cluster_count_contiguous_unallocated( - xlo_fmt, nb_clusters, &l2_slice[l2_index], type); - *cluster_offset = 0; - break; - case QCOW_CLUSTER_ZERO_ALLOC: - case QCOW_CLUSTER_NORMAL: - /* how many allocated clusters ? */ - c = __xloop_file_fmt_qcow_cluster_count_contiguous(xlo_fmt, - nb_clusters, qcow_data->cluster_size, - &l2_slice[l2_index], QCOW_OFLAG_ZERO); - *cluster_offset &= L2E_OFFSET_MASK; - if (xloop_file_fmt_qcow_offset_into_cluster(qcow_data, - *cluster_offset)) { - printk_ratelimited(KERN_ERR "xloop_file_fmt_qcow: " - "cluster allocation offset %llx unaligned " - "(L2 offset: %llx, L2 index: %x)\n", - *cluster_offset, l2_offset, l2_index); - ret = -EIO; - goto fail; - } - if (xloop_file_fmt_qcow_has_data_file(xlo_fmt) && - *cluster_offset != offset - offset_in_cluster) { - printk_ratelimited(KERN_ERR "xloop_file_fmt_qcow: " - "external data file host cluster offset %llx " - "does not match guest cluster offset: %llx, " - "L2 index: %x)", *cluster_offset, - offset - offset_in_cluster, l2_index); - ret = -EIO; - goto fail; - } - break; - default: - BUG(); - } - - xloop_file_fmt_qcow_cache_put(xlo_fmt, (void **) &l2_slice); - - bytes_available = (s64) c * qcow_data->cluster_size; - -out: - if (bytes_available > bytes_needed) { - bytes_available = bytes_needed; - } - - /* bytes_available <= bytes_needed <= *bytes + offset_in_cluster; - * subtracting offset_in_cluster will therefore definitely yield - * something not exceeding UINT_MAX */ - ASSERT(bytes_available - offset_in_cluster <= UINT_MAX); - *bytes = bytes_available - offset_in_cluster; - - return type; - -fail: - xloop_file_fmt_qcow_cache_put(xlo_fmt, (void **) &l2_slice); - return ret; -} diff --git a/loop_file_fmt_qcow_cluster.h b/loop_file_fmt_qcow_cluster.h deleted file mode 100644 index 5078f29..0000000 --- a/loop_file_fmt_qcow_cluster.h +++ /dev/null @@ -1,23 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0 */ -/* - * xloop_file_fmt_qcow_cluster.h - * - * Ported QCOW2 implementation of the QEMU project (GPL-2.0): - * Cluster calculation and lookup for the QCOW2 format. - * - * The copyright (C) 2004-2006 of the original code is owned by Fabrice Bellard. - * - * Copyright (C) 2019 Manuel Bentele - */ - -#ifndef _LINUX_XLOOP_FILE_FMT_QCOW_CLUSTER_H -#define _LINUX_XLOOP_FILE_FMT_QCOW_CLUSTER_H - -#include "loop_file_fmt.h" - -extern int xloop_file_fmt_qcow_cluster_get_offset(struct xloop_file_fmt *xlo_fmt, - u64 offset, - unsigned int *bytes, - u64 *cluster_offset); - -#endif diff --git a/loop_file_fmt_qcow_main.c b/loop_file_fmt_qcow_main.c deleted file mode 100644 index 7c3e360..0000000 --- a/loop_file_fmt_qcow_main.c +++ /dev/null @@ -1,953 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0 */ -/* - * xloop_file_fmt_qcow.c - * - * QCOW file format driver for the xloop device module. - * - * Copyright (C) 2019 Manuel Bentele - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "loop_file_fmt.h" -#include "loop_file_fmt_qcow_main.h" -#include "loop_file_fmt_qcow_cache.h" -#include "loop_file_fmt_qcow_cluster.h" - -static int __qcow_file_fmt_header_read(struct file *file, - struct xloop_file_fmt_qcow_header *header) -{ - ssize_t len; - loff_t offset; - int ret = 0; - - /* read QCOW header */ - offset = 0; - len = kernel_read(file, header, sizeof(*header), &offset); - if (len < 0) { - printk(KERN_ERR "xloop_file_fmt_qcow: could not read QCOW " - "header"); - return len; - } - - header->magic = be32_to_cpu(header->magic); - header->version = be32_to_cpu(header->version); - header->backing_file_offset = be64_to_cpu(header->backing_file_offset); - header->backing_file_size = be32_to_cpu(header->backing_file_size); - header->cluster_bits = be32_to_cpu(header->cluster_bits); - header->size = be64_to_cpu(header->size); - header->crypt_method = be32_to_cpu(header->crypt_method); - header->l1_size = be32_to_cpu(header->l1_size); - header->l1_table_offset = be64_to_cpu(header->l1_table_offset); - header->refcount_table_offset = - be64_to_cpu(header->refcount_table_offset); - header->refcount_table_clusters = - be32_to_cpu(header->refcount_table_clusters); - header->nb_snapshots = be32_to_cpu(header->nb_snapshots); - header->snapshots_offset = be64_to_cpu(header->snapshots_offset); - - /* check QCOW file format and header version */ - if (header->magic != QCOW_MAGIC) { - printk(KERN_ERR "xloop_file_fmt_qcow: image is not in QCOW " - "format"); - return -EINVAL; - } - - if (header->version < 2 || header->version > 3) { - printk(KERN_ERR "xloop_file_fmt_qcow: unsupported QCOW version " - "%d", header->version); - return -ENOTSUPP; - } - - /* initialize version 3 header fields */ - if (header->version == 2) { - header->incompatible_features = 0; - header->compatible_features = 0; - header->autoclear_features = 0; - header->refcount_order = 4; - header->header_length = 72; - } else { - header->incompatible_features = - be64_to_cpu(header->incompatible_features); - header->compatible_features = - be64_to_cpu(header->compatible_features); - header->autoclear_features = - be64_to_cpu(header->autoclear_features); - header->refcount_order = be32_to_cpu(header->refcount_order); - header->header_length = be32_to_cpu(header->header_length); - - if (header->header_length < 104) { - printk(KERN_ERR "xloop_file_fmt_qcow: QCOW header too " - "short"); - return -EINVAL; - } - } - - return ret; -} - -static int __qcow_file_fmt_validate_table(struct xloop_file_fmt *xlo_fmt, - u64 offset, u64 entries, size_t entry_len, s64 max_size_bytes, - const char *table_name) -{ - struct xloop_file_fmt_qcow_data *qcow_data = xlo_fmt->private_data; - - if (entries > max_size_bytes / entry_len) { - printk(KERN_INFO "xloop_file_fmt_qcow: %s too large", - table_name); - return -EFBIG; - } - - /* Use signed S64_MAX as the maximum even for u64 header fields, - * because values will be passed to qemu functions taking s64. */ - if ((S64_MAX - entries * entry_len < offset) || ( - xloop_file_fmt_qcow_offset_into_cluster(qcow_data, offset) != 0) - ) { - printk(KERN_INFO "xloop_file_fmt_qcow: %s offset invalid", - table_name); - return -EINVAL; - } - - return 0; -} - -static inline loff_t __qcow_file_fmt_rq_get_pos(struct xloop_file_fmt *xlo_fmt, - struct request *rq) -{ - struct xloop_device *xlo = xloop_file_fmt_get_xlo(xlo_fmt); - return ((loff_t) blk_rq_pos(rq) << 9) + xlo->xlo_offset; -} - -static int __qcow_file_fmt_compression_init(struct xloop_file_fmt *xlo_fmt) -{ - struct xloop_file_fmt_qcow_data *qcow_data = xlo_fmt->private_data; - int ret = 0; - - qcow_data->strm = kzalloc(sizeof(*qcow_data->strm), GFP_KERNEL); - if (!qcow_data->strm) { - ret = -ENOMEM; - goto out; - } - - qcow_data->strm->workspace = vzalloc(zlib_inflate_workspacesize()); - if (!qcow_data->strm->workspace) { - ret = -ENOMEM; - goto out_free_strm; - } - - qcow_data->cmp_last_coffset = ULLONG_MAX; - qcow_data->cmp_out_buf = vmalloc(qcow_data->cluster_size); - if (!qcow_data->cmp_out_buf) { - ret = -ENOMEM; - goto out_free_workspace; - } - - return ret; - -out_free_workspace: - vfree(qcow_data->strm->workspace); -out_free_strm: - kfree(qcow_data->strm); -out: - return ret; -} - -static void __qcow_file_fmt_compression_exit(struct xloop_file_fmt *xlo_fmt) -{ - struct xloop_file_fmt_qcow_data *qcow_data = xlo_fmt->private_data; - - vfree(qcow_data->strm->workspace); - kfree(qcow_data->strm); - vfree(qcow_data->cmp_out_buf); -} - -#ifdef CONFIG_DEBUG_FS -static void __qcow_file_fmt_header_to_buf(struct xloop_file_fmt *xlo_fmt, - const struct xloop_file_fmt_qcow_header *header) -{ - struct xloop_file_fmt_qcow_data *qcow_data = xlo_fmt->private_data; - char *header_buf = qcow_data->dbgfs_file_qcow_header_buf; - ssize_t len = 0; - - len += sprintf(header_buf + len, "magic: %d\n", - header->magic); - len += sprintf(header_buf + len, "version: %d\n", - header->version); - len += sprintf(header_buf + len, "backing_file_offset: %lld\n", - header->backing_file_offset); - len += sprintf(header_buf + len, "backing_file_size: %d\n", - header->backing_file_size); - len += sprintf(header_buf + len, "cluster_bits: %d\n", - header->cluster_bits); - len += sprintf(header_buf + len, "size: %lld\n", - header->size); - len += sprintf(header_buf + len, "crypt_method: %d\n", - header->crypt_method); - len += sprintf(header_buf + len, "l1_size: %d\n", - header->l1_size); - len += sprintf(header_buf + len, "l1_table_offset: %lld\n", - header->l1_table_offset); - len += sprintf(header_buf + len, "refcount_table_offset: %lld\n", - header->refcount_table_offset); - len += sprintf(header_buf + len, "refcount_table_clusters: %d\n", - header->refcount_table_clusters); - len += sprintf(header_buf + len, "nb_snapshots: %d\n", - header->nb_snapshots); - len += sprintf(header_buf + len, "snapshots_offset: %lld\n", - header->snapshots_offset); - - if (header->version == 3) { - len += sprintf(header_buf + len, - "incompatible_features: %lld\n", - header->incompatible_features); - len += sprintf(header_buf + len, - "compatible_features: %lld\n", - header->compatible_features); - len += sprintf(header_buf + len, - "autoclear_features: %lld\n", - header->autoclear_features); - len += sprintf(header_buf + len, - "refcount_order: %d\n", - header->refcount_order); - len += sprintf(header_buf + len, - "header_length: %d\n", - header->header_length); - } - - ASSERT(len < QCOW_HEADER_BUF_LEN); -} - -static ssize_t __qcow_file_fmt_dbgfs_hdr_read(struct file *file, - char __user *buf, size_t size, loff_t *ppos) -{ - struct xloop_file_fmt *xlo_fmt = file->private_data; - struct xloop_file_fmt_qcow_data *qcow_data = xlo_fmt->private_data; - char *header_buf = qcow_data->dbgfs_file_qcow_header_buf; - - return simple_read_from_buffer(buf, size, ppos, header_buf, - strlen(header_buf)); -} - -static const struct file_operations qcow_file_fmt_dbgfs_hdr_fops = { - .open = simple_open, - .read = __qcow_file_fmt_dbgfs_hdr_read -}; - -static ssize_t __qcow_file_fmt_dbgfs_ofs_read(struct file *file, - char __user *buf, size_t size, loff_t *ppos) -{ - struct xloop_file_fmt *xlo_fmt = file->private_data; - struct xloop_file_fmt_qcow_data *qcow_data = xlo_fmt->private_data; - unsigned int cur_bytes = 1; - u64 offset = 0; - u64 cluster_offset = 0; - s64 offset_in_cluster = 0; - ssize_t len = 0; - int ret = 0; - - /* read the share debugfs offset */ - ret = mutex_lock_interruptible(&qcow_data->dbgfs_qcow_offset_mutex); - if (ret) - return ret; - - offset = qcow_data->dbgfs_qcow_offset; - mutex_unlock(&qcow_data->dbgfs_qcow_offset_mutex); - - /* calculate and print the cluster offset */ - ret = xloop_file_fmt_qcow_cluster_get_offset(xlo_fmt, - offset, &cur_bytes, &cluster_offset); - if (ret < 0) - return -EINVAL; - - offset_in_cluster = xloop_file_fmt_qcow_offset_into_cluster(qcow_data, - offset); - - len = sprintf(qcow_data->dbgfs_file_qcow_cluster_buf, - "offset: %lld\ncluster_offset: %lld\noffset_in_cluster: %lld\n", - offset, cluster_offset, offset_in_cluster); - - ASSERT(len < QCOW_CLUSTER_BUF_LEN); - - return simple_read_from_buffer(buf, size, ppos, - qcow_data->dbgfs_file_qcow_cluster_buf, len); -} - -static ssize_t __qcow_file_fmt_dbgfs_ofs_write(struct file *file, - const char __user *buf, size_t size, loff_t *ppos) -{ - struct xloop_file_fmt *xlo_fmt = file->private_data; - struct xloop_file_fmt_qcow_data *qcow_data = xlo_fmt->private_data; - ssize_t len = 0; - int ret = 0; - - if (*ppos > QCOW_OFFSET_BUF_LEN || size > QCOW_OFFSET_BUF_LEN) - return -EINVAL; - - len = simple_write_to_buffer(qcow_data->dbgfs_file_qcow_offset_buf, - QCOW_OFFSET_BUF_LEN, ppos, buf, size); - if (len < 0) - return len; - - qcow_data->dbgfs_file_qcow_offset_buf[len] = '\0'; - - ret = mutex_lock_interruptible(&qcow_data->dbgfs_qcow_offset_mutex); - if (ret) - return ret; - - ret = kstrtou64(qcow_data->dbgfs_file_qcow_offset_buf, 10, - &qcow_data->dbgfs_qcow_offset); - if (ret < 0) - goto out; - - ret = len; -out: - mutex_unlock(&qcow_data->dbgfs_qcow_offset_mutex); - return ret; -} - -static const struct file_operations qcow_file_fmt_dbgfs_ofs_fops = { - .open = simple_open, - .read = __qcow_file_fmt_dbgfs_ofs_read, - .write = __qcow_file_fmt_dbgfs_ofs_write -}; - -static int __qcow_file_fmt_dbgfs_init(struct xloop_file_fmt *xlo_fmt) -{ - struct xloop_file_fmt_qcow_data *qcow_data = xlo_fmt->private_data; - struct xloop_device *xlo = xloop_file_fmt_get_xlo(xlo_fmt); - int ret = 0; - - qcow_data->dbgfs_dir = debugfs_create_dir("QCOW", xlo->xlo_dbgfs_dir); - if (IS_ERR_OR_NULL(qcow_data->dbgfs_dir)) { - ret = -ENODEV; - goto out; - } - - qcow_data->dbgfs_file_qcow_header = debugfs_create_file("header", - S_IRUGO, qcow_data->dbgfs_dir, xlo_fmt, - &qcow_file_fmt_dbgfs_hdr_fops); - if (IS_ERR_OR_NULL(qcow_data->dbgfs_file_qcow_header)) { - ret = -ENODEV; - goto out_free_dbgfs_dir; - } - - qcow_data->dbgfs_file_qcow_offset = debugfs_create_file("offset", - S_IRUGO | S_IWUSR, qcow_data->dbgfs_dir, xlo_fmt, - &qcow_file_fmt_dbgfs_ofs_fops); - if (IS_ERR_OR_NULL(qcow_data->dbgfs_file_qcow_offset)) { - qcow_data->dbgfs_file_qcow_offset = NULL; - ret = -ENODEV; - goto out_free_dbgfs_hdr; - } - - qcow_data->dbgfs_qcow_offset = 0; - mutex_init(&qcow_data->dbgfs_qcow_offset_mutex); - - return ret; - -out_free_dbgfs_hdr: - debugfs_remove(qcow_data->dbgfs_file_qcow_header); - qcow_data->dbgfs_file_qcow_header = NULL; -out_free_dbgfs_dir: - debugfs_remove(qcow_data->dbgfs_dir); - qcow_data->dbgfs_dir = NULL; -out: - return ret; -} - -static void __qcow_file_fmt_dbgfs_exit(struct xloop_file_fmt *xlo_fmt) -{ - struct xloop_file_fmt_qcow_data *qcow_data = xlo_fmt->private_data; - - if (qcow_data->dbgfs_file_qcow_offset) - debugfs_remove(qcow_data->dbgfs_file_qcow_offset); - - mutex_destroy(&qcow_data->dbgfs_qcow_offset_mutex); - - if (qcow_data->dbgfs_file_qcow_header) - debugfs_remove(qcow_data->dbgfs_file_qcow_header); - - if (qcow_data->dbgfs_dir) - debugfs_remove(qcow_data->dbgfs_dir); -} -#endif - -static int qcow_file_fmt_init(struct xloop_file_fmt *xlo_fmt) -{ - struct xloop_file_fmt_qcow_data *qcow_data; - struct xloop_device *xlo = xloop_file_fmt_get_xlo(xlo_fmt); - struct xloop_file_fmt_qcow_header header; - u64 l1_vm_state_index; - u64 l2_cache_size; - u64 l2_cache_entry_size; - ssize_t len; - unsigned int i; - int ret = 0; - - /* allocate memory for saving QCOW file format data */ - qcow_data = kzalloc(sizeof(*qcow_data), GFP_KERNEL); - if (!qcow_data) - return -ENOMEM; - - xlo_fmt->private_data = qcow_data; - - /* read the QCOW file header */ - ret = __qcow_file_fmt_header_read(xlo->xlo_backing_file, &header); - if (ret) - goto free_qcow_data; - - /* save information of the header fields in human readable format in - * a file buffer to access it with debugfs */ -#ifdef CONFIG_DEBUG_FS - __qcow_file_fmt_header_to_buf(xlo_fmt, &header); -#endif - - qcow_data->qcow_version = header.version; - - /* Initialise cluster size */ - if (header.cluster_bits < QCOW_MIN_CLUSTER_BITS - || header.cluster_bits > QCOW_MAX_CLUSTER_BITS) { - printk(KERN_ERR "xloop_file_fmt_qcow: unsupported cluster " - "size: 2^%d", header.cluster_bits); - ret = -EINVAL; - goto free_qcow_data; - } - - qcow_data->cluster_bits = header.cluster_bits; - qcow_data->cluster_size = 1 << qcow_data->cluster_bits; - qcow_data->cluster_sectors = 1 << - (qcow_data->cluster_bits - SECTOR_SHIFT); - - if (header.header_length > qcow_data->cluster_size) { - printk(KERN_ERR "xloop_file_fmt_qcow: QCOW header exceeds " - "cluster size"); - ret = -EINVAL; - goto free_qcow_data; - } - - if (header.backing_file_offset > qcow_data->cluster_size) { - printk(KERN_ERR "xloop_file_fmt_qcow: invalid backing file " - "offset"); - ret = -EINVAL; - goto free_qcow_data; - } - - if (header.backing_file_offset) { - printk(KERN_ERR "xloop_file_fmt_qcow: backing file support not " - "available"); - ret = -ENOTSUPP; - goto free_qcow_data; - } - - /* handle feature bits */ - qcow_data->incompatible_features = header.incompatible_features; - qcow_data->compatible_features = header.compatible_features; - qcow_data->autoclear_features = header.autoclear_features; - - if (qcow_data->incompatible_features & QCOW_INCOMPAT_DIRTY) { - printk(KERN_ERR "xloop_file_fmt_qcow: image contains " - "inconsistent refcounts"); - ret = -EACCES; - goto free_qcow_data; - } - - if (qcow_data->incompatible_features & QCOW_INCOMPAT_CORRUPT) { - printk(KERN_ERR "xloop_file_fmt_qcow: image is corrupt; cannot " - "be opened read/write"); - ret = -EACCES; - goto free_qcow_data; - } - - if (qcow_data->incompatible_features & QCOW_INCOMPAT_DATA_FILE) { - printk(KERN_ERR "xloop_file_fmt_qcow: clusters in the external " - "data file are not refcounted"); - ret = -EACCES; - goto free_qcow_data; - } - - /* Check support for various header values */ - if (header.refcount_order > 6) { - printk(KERN_ERR "xloop_file_fmt_qcow: reference count entry " - "width too large; may not exceed 64 bits"); - ret = -EINVAL; - goto free_qcow_data; - } - qcow_data->refcount_order = header.refcount_order; - qcow_data->refcount_bits = 1 << qcow_data->refcount_order; - qcow_data->refcount_max = U64_C(1) << (qcow_data->refcount_bits - 1); - qcow_data->refcount_max += qcow_data->refcount_max - 1; - - qcow_data->crypt_method_header = header.crypt_method; - if (qcow_data->crypt_method_header) { - printk(KERN_ERR "xloop_file_fmt_qcow: encryption support not " - "available"); - ret = -ENOTSUPP; - goto free_qcow_data; - } - - /* L2 is always one cluster */ - qcow_data->l2_bits = qcow_data->cluster_bits - 3; - qcow_data->l2_size = 1 << qcow_data->l2_bits; - /* 2^(qcow_data->refcount_order - 3) is the refcount width in bytes */ - qcow_data->refcount_block_bits = qcow_data->cluster_bits - - (qcow_data->refcount_order - 3); - qcow_data->refcount_block_size = 1 << qcow_data->refcount_block_bits; - qcow_data->size = header.size; - qcow_data->csize_shift = (62 - (qcow_data->cluster_bits - 8)); - qcow_data->csize_mask = (1 << (qcow_data->cluster_bits - 8)) - 1; - qcow_data->cluster_offset_mask = (1LL << qcow_data->csize_shift) - 1; - - qcow_data->refcount_table_offset = header.refcount_table_offset; - qcow_data->refcount_table_size = header.refcount_table_clusters << - (qcow_data->cluster_bits - 3); - - if (header.refcount_table_clusters == 0) { - printk(KERN_ERR "xloop_file_fmt_qcow: image does not contain a " - "reference count table"); - ret = -EINVAL; - goto free_qcow_data; - } - - ret = __qcow_file_fmt_validate_table(xlo_fmt, - qcow_data->refcount_table_offset, - header.refcount_table_clusters, qcow_data->cluster_size, - QCOW_MAX_REFTABLE_SIZE, "Reference count table"); - if (ret < 0) { - goto free_qcow_data; - } - - /* The total size in bytes of the snapshot table is checked in - * qcow2_read_snapshots() because the size of each snapshot is - * variable and we don't know it yet. - * Here we only check the offset and number of snapshots. */ - ret = __qcow_file_fmt_validate_table(xlo_fmt, header.snapshots_offset, - header.nb_snapshots, - sizeof(struct xloop_file_fmt_qcow_snapshot_header), - sizeof(struct xloop_file_fmt_qcow_snapshot_header) * - QCOW_MAX_SNAPSHOTS, "Snapshot table"); - if (ret < 0) { - goto free_qcow_data; - } - - /* read the level 1 table */ - ret = __qcow_file_fmt_validate_table(xlo_fmt, header.l1_table_offset, - header.l1_size, sizeof(u64), QCOW_MAX_L1_SIZE, - "Active L1 table"); - if (ret < 0) { - goto free_qcow_data; - } - qcow_data->l1_size = header.l1_size; - qcow_data->l1_table_offset = header.l1_table_offset; - - l1_vm_state_index = xloop_file_fmt_qcow_size_to_l1(qcow_data, - header.size); - if (l1_vm_state_index > INT_MAX) { - printk(KERN_ERR "xloop_file_fmt_qcow: image is too big"); - ret = -EFBIG; - goto free_qcow_data; - } - qcow_data->l1_vm_state_index = l1_vm_state_index; - - /* the L1 table must contain at least enough entries to put header.size - * bytes */ - if (qcow_data->l1_size < qcow_data->l1_vm_state_index) { - printk(KERN_ERR "xloop_file_fmt_qcow: L1 table is too small"); - ret = -EINVAL; - goto free_qcow_data; - } - - if (qcow_data->l1_size > 0) { - qcow_data->l1_table = vzalloc(round_up(qcow_data->l1_size * - sizeof(u64), 512)); - if (qcow_data->l1_table == NULL) { - printk(KERN_ERR "xloop_file_fmt_qcow: could not " - "allocate L1 table"); - ret = -ENOMEM; - goto free_qcow_data; - } - len = kernel_read(xlo->xlo_backing_file, qcow_data->l1_table, - qcow_data->l1_size * sizeof(u64), - &qcow_data->l1_table_offset); - if (len < 0) { - printk(KERN_ERR "xloop_file_fmt_qcow: could not read L1 " - "table"); - ret = len; - goto free_l1_table; - } - for (i = 0; i < qcow_data->l1_size; i++) { - qcow_data->l1_table[i] = - be64_to_cpu(qcow_data->l1_table[i]); - } - } - - /* Internal snapshots */ - qcow_data->snapshots_offset = header.snapshots_offset; - qcow_data->nb_snapshots = header.nb_snapshots; - - if (qcow_data->nb_snapshots > 0) { - printk(KERN_ERR "xloop_file_fmt_qcow: snapshots support not " - "available"); - ret = -ENOTSUPP; - goto free_l1_table; - } - - - /* create cache for L2 */ - l2_cache_size = qcow_data->size / (qcow_data->cluster_size / 8); - l2_cache_entry_size = min(qcow_data->cluster_size, (int)4096); - - /* limit the L2 size to maximum QCOW_DEFAULT_L2_CACHE_MAX_SIZE */ - l2_cache_size = min(l2_cache_size, (u64)QCOW_DEFAULT_L2_CACHE_MAX_SIZE); - - /* calculate the number of cache tables */ - l2_cache_size /= l2_cache_entry_size; - if (l2_cache_size < QCOW_MIN_L2_CACHE_SIZE) { - l2_cache_size = QCOW_MIN_L2_CACHE_SIZE; - } - - if (l2_cache_size > INT_MAX) { - printk(KERN_ERR "xloop_file_fmt_qcow: L2 cache size too big"); - ret = -EINVAL; - goto free_l1_table; - } - - qcow_data->l2_slice_size = l2_cache_entry_size / sizeof(u64); - - qcow_data->l2_table_cache = xloop_file_fmt_qcow_cache_create(xlo_fmt, - l2_cache_size, l2_cache_entry_size); - if (!qcow_data->l2_table_cache) { - ret = -ENOMEM; - goto free_l1_table; - } - - /* initialize compression support */ - ret = __qcow_file_fmt_compression_init(xlo_fmt); - if (ret < 0) - goto free_l2_cache; - - /* initialize debugfs entries */ -#ifdef CONFIG_DEBUG_FS - ret = __qcow_file_fmt_dbgfs_init(xlo_fmt); - if (ret < 0) - goto free_l2_cache; -#endif - - return ret; - -free_l2_cache: - xloop_file_fmt_qcow_cache_destroy(xlo_fmt); -free_l1_table: - vfree(qcow_data->l1_table); -free_qcow_data: - kfree(qcow_data); - xlo_fmt->private_data = NULL; - return ret; -} - -static void qcow_file_fmt_exit(struct xloop_file_fmt *xlo_fmt) -{ - struct xloop_file_fmt_qcow_data *qcow_data = xlo_fmt->private_data; - -#ifdef CONFIG_DEBUG_FS - __qcow_file_fmt_dbgfs_exit(xlo_fmt); -#endif - - __qcow_file_fmt_compression_exit(xlo_fmt); - - if (qcow_data->l1_table) { - vfree(qcow_data->l1_table); - } - - if (qcow_data->l2_table_cache) { - xloop_file_fmt_qcow_cache_destroy(xlo_fmt); - } - - if (qcow_data) { - kfree(qcow_data); - xlo_fmt->private_data = NULL; - } -} - -static ssize_t __qcow_file_fmt_buffer_decompress(struct xloop_file_fmt *xlo_fmt, - void *dest, - size_t dest_size, - const void *src, - size_t src_size) -{ - struct xloop_file_fmt_qcow_data *qcow_data = xlo_fmt->private_data; - int ret = 0; - - qcow_data->strm->avail_in = src_size; - qcow_data->strm->next_in = (void *) src; - qcow_data->strm->avail_out = dest_size; - qcow_data->strm->next_out = dest; - - ret = zlib_inflateInit2(qcow_data->strm, -12); - if (ret != Z_OK) { - return -1; - } - - ret = zlib_inflate(qcow_data->strm, Z_FINISH); - if ((ret != Z_STREAM_END && ret != Z_BUF_ERROR) - || qcow_data->strm->avail_out != 0) { - /* We approve Z_BUF_ERROR because we need @dest buffer to be - * filled, but @src buffer may be processed partly (because in - * qcow2 we know size of compressed data with precision of one - * sector) */ - ret = -1; - } else { - ret = 0; - } - return ret; -} - -static int __qcow_file_fmt_read_compressed(struct xloop_file_fmt *xlo_fmt, - struct bio_vec *bvec, - u64 file_cluster_offset, - u64 offset, - u64 bytes, - u64 bytes_done) -{ - struct xloop_file_fmt_qcow_data *qcow_data = xlo_fmt->private_data; - struct xloop_device *xlo = xloop_file_fmt_get_xlo(xlo_fmt); - int ret = 0, csize, nb_csectors; - u64 coffset; - u8 *in_buf = NULL; - ssize_t len; - void *data; - unsigned long irq_flags; - int offset_in_cluster = xloop_file_fmt_qcow_offset_into_cluster( - qcow_data, offset); - - coffset = file_cluster_offset & qcow_data->cluster_offset_mask; - nb_csectors = ((file_cluster_offset >> qcow_data->csize_shift) & - qcow_data->csize_mask) + 1; - csize = nb_csectors * QCOW_COMPRESSED_SECTOR_SIZE - - (coffset & ~QCOW_COMPRESSED_SECTOR_MASK); - - - if (qcow_data->cmp_last_coffset != coffset) { - in_buf = vmalloc(csize); - if (!in_buf) { - qcow_data->cmp_last_coffset = ULLONG_MAX; - return -ENOMEM; - } - qcow_data->cmp_last_coffset = coffset; - len = kernel_read(xlo->xlo_backing_file, in_buf, csize, &coffset); - if (len < 0) { - qcow_data->cmp_last_coffset = ULLONG_MAX; - ret = len; - goto out_free_in_buf; - } - - if (__qcow_file_fmt_buffer_decompress(xlo_fmt, qcow_data->cmp_out_buf, - qcow_data->cluster_size, in_buf, csize) < 0) { - qcow_data->cmp_last_coffset = ULLONG_MAX; - ret = -EIO; - goto out_free_in_buf; - } - } - - ASSERT(bytes <= bvec->bv_len); - data = bvec_kmap_irq(bvec, &irq_flags) + bytes_done; - memcpy(data, qcow_data->cmp_out_buf + offset_in_cluster, bytes); - flush_dcache_page(bvec->bv_page); - bvec_kunmap_irq(data, &irq_flags); - -out_free_in_buf: - vfree(in_buf); - - return ret; -} - -static int __qcow_file_fmt_read_bvec(struct xloop_file_fmt *xlo_fmt, - struct bio_vec *bvec, - loff_t *ppos) -{ - struct xloop_file_fmt_qcow_data *qcow_data = xlo_fmt->private_data; - struct xloop_device *xlo = xloop_file_fmt_get_xlo(xlo_fmt); - int offset_in_cluster; - int ret; - unsigned int cur_bytes; /* number of bytes in current iteration */ - u64 bytes; - u64 cluster_offset = 0; - u64 bytes_done = 0; - void *data; - unsigned long irq_flags; - ssize_t len; - loff_t pos_read; - - bytes = bvec->bv_len; - - while (bytes != 0) { - - /* prepare next request */ - cur_bytes = bytes; - - ret = xloop_file_fmt_qcow_cluster_get_offset(xlo_fmt, *ppos, - &cur_bytes, &cluster_offset); - if (ret < 0) { - goto fail; - } - - offset_in_cluster = xloop_file_fmt_qcow_offset_into_cluster( - qcow_data, *ppos); - - switch (ret) { - case QCOW_CLUSTER_UNALLOCATED: - case QCOW_CLUSTER_ZERO_PLAIN: - case QCOW_CLUSTER_ZERO_ALLOC: - data = bvec_kmap_irq(bvec, &irq_flags) + bytes_done; - memset(data, 0, cur_bytes); - flush_dcache_page(bvec->bv_page); - bvec_kunmap_irq(data, &irq_flags); - break; - - case QCOW_CLUSTER_COMPRESSED: - ret = __qcow_file_fmt_read_compressed(xlo_fmt, bvec, - cluster_offset, *ppos, cur_bytes, bytes_done); - if (ret < 0) { - goto fail; - } - - break; - - case QCOW_CLUSTER_NORMAL: - if ((cluster_offset & 511) != 0) { - ret = -EIO; - goto fail; - } - - pos_read = cluster_offset + offset_in_cluster; - - data = bvec_kmap_irq(bvec, &irq_flags) + bytes_done; - len = kernel_read(xlo->xlo_backing_file, data, cur_bytes, - &pos_read); - flush_dcache_page(bvec->bv_page); - bvec_kunmap_irq(data, &irq_flags); - - if (len < 0) - return len; - - break; - - default: - ret = -EIO; - goto fail; - } - - bytes -= cur_bytes; - *ppos += cur_bytes; - bytes_done += cur_bytes; - } - - ret = 0; - -fail: - return ret; -} - -static int qcow_file_fmt_read(struct xloop_file_fmt *xlo_fmt, - struct request *rq) -{ - struct bio_vec bvec; - struct req_iterator iter; - loff_t pos; - int ret = 0; - - pos = __qcow_file_fmt_rq_get_pos(xlo_fmt, rq); - - rq_for_each_segment(bvec, rq, iter) { - ret = __qcow_file_fmt_read_bvec(xlo_fmt, &bvec, &pos); - if (ret) - return ret; - - cond_resched(); - } - - return ret; -} - -static loff_t qcow_file_fmt_sector_size(struct xloop_file_fmt *xlo_fmt, - struct file *file, loff_t offset, loff_t sizelimit) -{ - struct xloop_file_fmt_qcow_header header; - loff_t xloopsize; - int ret; - - /* temporary read the QCOW file header of other QCOW image file */ - ret = __qcow_file_fmt_header_read(file, &header); - if (ret) - return 0; - - /* compute xloopsize in bytes */ - xloopsize = header.size; - if (offset > 0) - xloopsize -= offset; - /* offset is beyond i_size, weird but possible */ - if (xloopsize < 0) - return 0; - - if (sizelimit > 0 && sizelimit < xloopsize) - xloopsize = sizelimit; - /* - * Unfortunately, if we want to do I/O on the device, - * the number of 512-byte sectors has to fit into a sector_t. - */ - return xloopsize >> 9; -} - -static struct xloop_file_fmt_ops qcow_file_fmt_ops = { - .init = qcow_file_fmt_init, - .exit = qcow_file_fmt_exit, - .read = qcow_file_fmt_read, - .write = NULL, - .read_aio = NULL, - .write_aio = NULL, - .write_zeros = NULL, - .discard = NULL, - .flush = NULL, - .sector_size = qcow_file_fmt_sector_size, -}; - -static struct xloop_file_fmt_driver qcow_file_fmt_driver = { - .name = "QCOW", - .file_fmt_type = XLO_FILE_FMT_QCOW, - .ops = &qcow_file_fmt_ops, - .owner = THIS_MODULE, -}; - -static int __init xloop_file_fmt_qcow_init(void) -{ - printk(KERN_INFO "xloop_file_fmt_qcow: init xloop device QCOW file " - "format driver"); - return xloop_file_fmt_register_driver(&qcow_file_fmt_driver); -} - -static void __exit xloop_file_fmt_qcow_exit(void) -{ - printk(KERN_INFO "xloop_file_fmt_qcow: exit xloop device QCOW file " - "format driver"); - xloop_file_fmt_unregister_driver(&qcow_file_fmt_driver); -} - -module_init(xloop_file_fmt_qcow_init); -module_exit(xloop_file_fmt_qcow_exit); - -MODULE_LICENSE("GPL"); -MODULE_AUTHOR("Manuel Bentele "); -MODULE_DESCRIPTION("xloop device QCOW file format driver"); -MODULE_SOFTDEP("pre: xloop"); diff --git a/loop_file_fmt_qcow_main.h b/loop_file_fmt_qcow_main.h deleted file mode 100644 index 54b94c3..0000000 --- a/loop_file_fmt_qcow_main.h +++ /dev/null @@ -1,419 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0 */ -/* - * xloop_file_fmt_qcow.h - * - * QCOW file format driver for the xloop device module. - * - * Ported QCOW2 implementation of the QEMU project (GPL-2.0): - * Declarations for the QCOW2 file format. - * - * The copyright (C) 2004-2006 of the original code is owned by Fabrice Bellard. - * - * Copyright (C) 2019 Manuel Bentele - */ - -#ifndef _LINUX_XLOOP_FILE_FMT_QCOW_H -#define _LINUX_XLOOP_FILE_FMT_QCOW_H - -#include -#include -#include -#include - -#ifdef CONFIG_DEBUG_FS -#include -#endif - -#include "loop_file_fmt.h" - -#ifdef CONFIG_DEBUG_DRIVER -#define ASSERT(x) \ -do { \ - if (!(x)) { \ - printk(KERN_EMERG "assertion failed %s: %d: %s\n", \ - __FILE__, __LINE__, #x); \ - BUG(); \ - } \ -} while (0) -#else -#define ASSERT(x) do { } while (0) -#endif - -#define KiB (1024) -#define MiB (1024 * 1024) - -#define QCOW_MAGIC (('Q' << 24) | ('F' << 16) | ('I' << 8) | 0xfb) - -#define QCOW_CRYPT_NONE 0 -#define QCOW_CRYPT_AES 1 -#define QCOW_CRYPT_LUKS 2 - -#define QCOW_MAX_CRYPT_CLUSTERS 32 -#define QCOW_MAX_SNAPSHOTS 65536 - -/* Field widths in QCOW mean normal cluster offsets cannot reach - * 64PB; depending on cluster size, compressed clusters can have a - * smaller limit (64PB for up to 16k clusters, then ramps down to - * 512TB for 2M clusters). */ -#define QCOW_MAX_CLUSTER_OFFSET ((1ULL << 56) - 1) - -/* 8 MB refcount table is enough for 2 PB images at 64k cluster size - * (128 GB for 512 byte clusters, 2 EB for 2 MB clusters) */ -#define QCOW_MAX_REFTABLE_SIZE (8 * MiB) - -/* 32 MB L1 table is enough for 2 PB images at 64k cluster size - * (128 GB for 512 byte clusters, 2 EB for 2 MB clusters) */ -#define QCOW_MAX_L1_SIZE (32 * MiB) - -/* Allow for an average of 1k per snapshot table entry, should be plenty of - * space for snapshot names and IDs */ -#define QCOW_MAX_SNAPSHOTS_SIZE (1024 * QCOW_MAX_SNAPSHOTS) - -/* Bitmap header extension constraints */ -#define QCOW_MAX_BITMAPS 65535 -#define QCOW_MAX_BITMAP_DIRECTORY_SIZE (1024 * QCOW_MAX_BITMAPS) - -/* indicate that the refcount of the referenced cluster is exactly one. */ -#define QCOW_OFLAG_COPIED (1ULL << 63) -/* indicate that the cluster is compressed (they never have the copied flag) */ -#define QCOW_OFLAG_COMPRESSED (1ULL << 62) -/* The cluster reads as all zeros */ -#define QCOW_OFLAG_ZERO (1ULL << 0) - -#define QCOW_MIN_CLUSTER_BITS 9 -#define QCOW_MAX_CLUSTER_BITS 21 - -/* Defined in the qcow2 spec (compressed cluster descriptor) */ -#define QCOW_COMPRESSED_SECTOR_SIZE 512U -#define QCOW_COMPRESSED_SECTOR_MASK (~(QCOW_COMPRESSED_SECTOR_SIZE - 1)) - -/* Must be at least 2 to cover COW */ -#define QCOW_MIN_L2_CACHE_SIZE 2 /* cache entries */ - -/* Must be at least 4 to cover all cases of refcount table growth */ -#define QCOW_MIN_REFCOUNT_CACHE_SIZE 4 /* clusters */ - -#define QCOW_DEFAULT_L2_CACHE_MAX_SIZE (32 * MiB) -#define QCOW_DEFAULT_CACHE_CLEAN_INTERVAL 600 /* seconds */ - -#define QCOW_DEFAULT_CLUSTER_SIZE 65536 - -/* Buffer size for debugfs file buffer to display QCOW header information */ -#define QCOW_HEADER_BUF_LEN 1024 - -/* Buffer size for debugfs file buffer to receive and display offset and - * cluster offset information */ -#define QCOW_OFFSET_BUF_LEN 32 -#define QCOW_CLUSTER_BUF_LEN 128 - -struct xloop_file_fmt_qcow_header { - u32 magic; - u32 version; - u64 backing_file_offset; - u32 backing_file_size; - u32 cluster_bits; - u64 size; /* in bytes */ - u32 crypt_method; - u32 l1_size; - u64 l1_table_offset; - u64 refcount_table_offset; - u32 refcount_table_clusters; - u32 nb_snapshots; - u64 snapshots_offset; - - /* The following fields are only valid for version >= 3 */ - u64 incompatible_features; - u64 compatible_features; - u64 autoclear_features; - - u32 refcount_order; - u32 header_length; -} __attribute__((packed)); - -struct xloop_file_fmt_qcow_snapshot_header { - /* header is 8 byte aligned */ - u64 l1_table_offset; - - u32 l1_size; - u16 id_str_size; - u16 name_size; - - u32 date_sec; - u32 date_nsec; - - u64 vm_clock_nsec; - - u32 vm_state_size; - /* for extension */ - u32 extra_data_size; - /* extra data follows */ - /* id_str follows */ - /* name follows */ -} __attribute__((packed)); - -enum { - QCOW_FEAT_TYPE_INCOMPATIBLE = 0, - QCOW_FEAT_TYPE_COMPATIBLE = 1, - QCOW_FEAT_TYPE_AUTOCLEAR = 2, -}; - -/* incompatible feature bits */ -enum { - QCOW_INCOMPAT_DIRTY_BITNR = 0, - QCOW_INCOMPAT_CORRUPT_BITNR = 1, - QCOW_INCOMPAT_DATA_FILE_BITNR = 2, - QCOW_INCOMPAT_DIRTY = 1 << QCOW_INCOMPAT_DIRTY_BITNR, - QCOW_INCOMPAT_CORRUPT = 1 << QCOW_INCOMPAT_CORRUPT_BITNR, - QCOW_INCOMPAT_DATA_FILE = 1 << QCOW_INCOMPAT_DATA_FILE_BITNR, - - QCOW_INCOMPAT_MASK = QCOW_INCOMPAT_DIRTY - | QCOW_INCOMPAT_CORRUPT - | QCOW_INCOMPAT_DATA_FILE, -}; - -/* compatible feature bits */ -enum { - QCOW_COMPAT_LAZY_REFCOUNTS_BITNR = 0, - QCOW_COMPAT_LAZY_REFCOUNTS = 1 << QCOW_COMPAT_LAZY_REFCOUNTS_BITNR, - - QCOW_COMPAT_FEAT_MASK = QCOW_COMPAT_LAZY_REFCOUNTS, -}; - -/* autoclear feature bits */ -enum { - QCOW_AUTOCLEAR_BITMAPS_BITNR = 0, - QCOW_AUTOCLEAR_DATA_FILE_RAW_BITNR = 1, - QCOW_AUTOCLEAR_BITMAPS = 1 << QCOW_AUTOCLEAR_BITMAPS_BITNR, - QCOW_AUTOCLEAR_DATA_FILE_RAW = 1 << QCOW_AUTOCLEAR_DATA_FILE_RAW_BITNR, - - QCOW_AUTOCLEAR_MASK = QCOW_AUTOCLEAR_BITMAPS | - QCOW_AUTOCLEAR_DATA_FILE_RAW, -}; - -struct xloop_file_fmt_qcow_data { - u64 size; - int cluster_bits; - int cluster_size; - int cluster_sectors; - int l2_slice_size; - int l2_bits; - int l2_size; - int l1_size; - int l1_vm_state_index; - int refcount_block_bits; - int refcount_block_size; - int csize_shift; - int csize_mask; - u64 cluster_offset_mask; - u64 l1_table_offset; - u64 *l1_table; - - struct xloop_file_fmt_qcow_cache *l2_table_cache; - struct xloop_file_fmt_qcow_cache *refcount_block_cache; - - u64 *refcount_table; - u64 refcount_table_offset; - u32 refcount_table_size; - u32 max_refcount_table_index; /* Last used entry in refcount_table */ - u64 free_cluster_index; - u64 free_byte_offset; - - u32 crypt_method_header; - u64 snapshots_offset; - int snapshots_size; - unsigned int nb_snapshots; - - u32 nb_bitmaps; - u64 bitmap_directory_size; - u64 bitmap_directory_offset; - - int qcow_version; - bool use_lazy_refcounts; - int refcount_order; - int refcount_bits; - u64 refcount_max; - - u64 incompatible_features; - u64 compatible_features; - u64 autoclear_features; - - struct z_stream_s *strm; - u8 *cmp_out_buf; - u64 cmp_last_coffset; - - /* debugfs entries */ -#ifdef CONFIG_DEBUG_FS - struct dentry *dbgfs_dir; - struct dentry *dbgfs_file_qcow_header; - char dbgfs_file_qcow_header_buf[QCOW_HEADER_BUF_LEN]; - struct dentry *dbgfs_file_qcow_offset; - char dbgfs_file_qcow_offset_buf[QCOW_OFFSET_BUF_LEN]; - char dbgfs_file_qcow_cluster_buf[QCOW_CLUSTER_BUF_LEN]; - u64 dbgfs_qcow_offset; - struct mutex dbgfs_qcow_offset_mutex; -#endif -}; - -struct xloop_file_fmt_qcow_cow_region { - /** - * Offset of the COW region in bytes from the start of the first - * cluster touched by the request. - */ - unsigned offset; - - /** Number of bytes to copy */ - unsigned nb_bytes; -}; - -enum xloop_file_fmt_qcow_cluster_type { - QCOW_CLUSTER_UNALLOCATED, - QCOW_CLUSTER_ZERO_PLAIN, - QCOW_CLUSTER_ZERO_ALLOC, - QCOW_CLUSTER_NORMAL, - QCOW_CLUSTER_COMPRESSED, -}; - -enum xloop_file_fmt_qcow_metadata_overlap { - QCOW_OL_MAIN_HEADER_BITNR = 0, - QCOW_OL_ACTIVE_L1_BITNR = 1, - QCOW_OL_ACTIVE_L2_BITNR = 2, - QCOW_OL_REFCOUNT_TABLE_BITNR = 3, - QCOW_OL_REFCOUNT_BLOCK_BITNR = 4, - QCOW_OL_SNAPSHOT_TABLE_BITNR = 5, - QCOW_OL_INACTIVE_L1_BITNR = 6, - QCOW_OL_INACTIVE_L2_BITNR = 7, - QCOW_OL_BITMAP_DIRECTORY_BITNR = 8, - - QCOW_OL_MAX_BITNR = 9, - - QCOW_OL_NONE = 0, - QCOW_OL_MAIN_HEADER = (1 << QCOW_OL_MAIN_HEADER_BITNR), - QCOW_OL_ACTIVE_L1 = (1 << QCOW_OL_ACTIVE_L1_BITNR), - QCOW_OL_ACTIVE_L2 = (1 << QCOW_OL_ACTIVE_L2_BITNR), - QCOW_OL_REFCOUNT_TABLE = (1 << QCOW_OL_REFCOUNT_TABLE_BITNR), - QCOW_OL_REFCOUNT_BLOCK = (1 << QCOW_OL_REFCOUNT_BLOCK_BITNR), - QCOW_OL_SNAPSHOT_TABLE = (1 << QCOW_OL_SNAPSHOT_TABLE_BITNR), - QCOW_OL_INACTIVE_L1 = (1 << QCOW_OL_INACTIVE_L1_BITNR), - /* NOTE: Checking overlaps with inactive L2 tables will result in bdrv - * reads. */ - QCOW_OL_INACTIVE_L2 = (1 << QCOW_OL_INACTIVE_L2_BITNR), - QCOW_OL_BITMAP_DIRECTORY = (1 << QCOW_OL_BITMAP_DIRECTORY_BITNR), -}; - -/* Perform all overlap checks which can be done in constant time */ -#define QCOW_OL_CONSTANT \ - (QCOW_OL_MAIN_HEADER | QCOW_OL_ACTIVE_L1 | QCOW_OL_REFCOUNT_TABLE | \ - QCOW_OL_SNAPSHOT_TABLE | QCOW_OL_BITMAP_DIRECTORY) - -/* Perform all overlap checks which don't require disk access */ -#define QCOW_OL_CACHED \ - (QCOW_OL_CONSTANT | QCOW_OL_ACTIVE_L2 | QCOW_OL_REFCOUNT_BLOCK | \ - QCOW_OL_INACTIVE_L1) - -/* Perform all overlap checks */ -#define QCOW_OL_ALL \ - (QCOW_OL_CACHED | QCOW_OL_INACTIVE_L2) - -#define L1E_OFFSET_MASK 0x00fffffffffffe00ULL -#define L2E_OFFSET_MASK 0x00fffffffffffe00ULL -#define L2E_COMPRESSED_OFFSET_SIZE_MASK 0x3fffffffffffffffULL - -#define REFT_OFFSET_MASK 0xfffffffffffffe00ULL - -#define INV_OFFSET (-1ULL) - -static inline bool xloop_file_fmt_qcow_has_data_file( - struct xloop_file_fmt *xlo_fmt) -{ - /* At the moment, there is no support for copy on write! */ - return false; -} - -static inline bool xloop_file_fmt_qcow_data_file_is_raw( - struct xloop_file_fmt *xlo_fmt) -{ - struct xloop_file_fmt_qcow_data *qcow_data = xlo_fmt->private_data; - return !!(qcow_data->autoclear_features & - QCOW_AUTOCLEAR_DATA_FILE_RAW); -} - -static inline s64 xloop_file_fmt_qcow_start_of_cluster( - struct xloop_file_fmt_qcow_data *qcow_data, s64 offset) -{ - return offset & ~(qcow_data->cluster_size - 1); -} - -static inline s64 xloop_file_fmt_qcow_offset_into_cluster( - struct xloop_file_fmt_qcow_data *qcow_data, s64 offset) -{ - return offset & (qcow_data->cluster_size - 1); -} - -static inline s64 xloop_file_fmt_qcow_size_to_clusters( - struct xloop_file_fmt_qcow_data *qcow_data, u64 size) -{ - return (size + (qcow_data->cluster_size - 1)) >> - qcow_data->cluster_bits; -} - -static inline s64 xloop_file_fmt_qcow_size_to_l1( - struct xloop_file_fmt_qcow_data *qcow_data, s64 size) -{ - int shift = qcow_data->cluster_bits + qcow_data->l2_bits; - return (size + (1ULL << shift) - 1) >> shift; -} - -static inline int xloop_file_fmt_qcow_offset_to_l1_index( - struct xloop_file_fmt_qcow_data *qcow_data, u64 offset) -{ - return offset >> (qcow_data->l2_bits + qcow_data->cluster_bits); -} - -static inline int xloop_file_fmt_qcow_offset_to_l2_index( - struct xloop_file_fmt_qcow_data *qcow_data, s64 offset) -{ - return (offset >> qcow_data->cluster_bits) & (qcow_data->l2_size - 1); -} - -static inline int xloop_file_fmt_qcow_offset_to_l2_slice_index( - struct xloop_file_fmt_qcow_data *qcow_data, s64 offset) -{ - return (offset >> qcow_data->cluster_bits) & - (qcow_data->l2_slice_size - 1); -} - -static inline s64 xloop_file_fmt_qcow_vm_state_offset( - struct xloop_file_fmt_qcow_data *qcow_data) -{ - return (s64)qcow_data->l1_vm_state_index << - (qcow_data->cluster_bits + qcow_data->l2_bits); -} - -static inline enum xloop_file_fmt_qcow_cluster_type -xloop_file_fmt_qcow_get_cluster_type(struct xloop_file_fmt *xlo_fmt, u64 l2_entry) -{ - if (l2_entry & QCOW_OFLAG_COMPRESSED) { - return QCOW_CLUSTER_COMPRESSED; - } else if (l2_entry & QCOW_OFLAG_ZERO) { - if (l2_entry & L2E_OFFSET_MASK) { - return QCOW_CLUSTER_ZERO_ALLOC; - } - return QCOW_CLUSTER_ZERO_PLAIN; - } else if (!(l2_entry & L2E_OFFSET_MASK)) { - /* Offset 0 generally means unallocated, but it is ambiguous - * with external data files because 0 is a valid offset there. - * However, all clusters in external data files always have - * refcount 1, so we can rely on QCOW_OFLAG_COPIED to - * disambiguate. */ - if (xloop_file_fmt_qcow_has_data_file(xlo_fmt) && - (l2_entry & QCOW_OFLAG_COPIED)) { - return QCOW_CLUSTER_NORMAL; - } else { - return QCOW_CLUSTER_UNALLOCATED; - } - } else { - return QCOW_CLUSTER_NORMAL; - } -} - -#endif diff --git a/loop_file_fmt_raw.c b/loop_file_fmt_raw.c deleted file mode 100644 index 11cc8cd..0000000 --- a/loop_file_fmt_raw.c +++ /dev/null @@ -1,465 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0 */ -/* - * xloop_file_fmt_raw.c - * - * RAW file format driver for the xloop device module. - * - * Copyright (C) 2019 Manuel Bentele - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "loop_file_fmt.h" - -static inline loff_t __raw_file_fmt_rq_get_pos(struct xloop_file_fmt *xlo_fmt, - struct request *rq) -{ - struct xloop_device *xlo = xloop_file_fmt_get_xlo(xlo_fmt); - return ((loff_t) blk_rq_pos(rq) << 9) + xlo->xlo_offset; -} - -/* transfer function for DEPRECATED cryptoxloop support */ -static inline int __raw_file_fmt_do_transfer(struct xloop_device *xlo, int cmd, - struct page *rpage, unsigned roffs, - struct page *lpage, unsigned loffs, - int size, sector_t rblock) -{ - int ret; - - ret = xlo->transfer(xlo, cmd, rpage, roffs, lpage, loffs, size, rblock); - if (likely(!ret)) - return 0; - - printk_ratelimited(KERN_ERR - "xloop_file_fmt_raw: Transfer error at byte offset %llu, length %i.\n", - (unsigned long long)rblock << 9, size); - return ret; -} - -static int __raw_file_fmt_read_transfer(struct xloop_device *xlo, - struct request *rq, loff_t pos) -{ - struct bio_vec bvec, b; - struct req_iterator iter; - struct iov_iter i; - struct page *page; - ssize_t len; - int ret = 0; - - page = alloc_page(GFP_NOIO); - if (unlikely(!page)) - return -ENOMEM; - - rq_for_each_segment(bvec, rq, iter) { - loff_t offset = pos; - - b.bv_page = page; - b.bv_offset = 0; - b.bv_len = bvec.bv_len; - - iov_iter_bvec(&i, READ, &b, 1, b.bv_len); - len = vfs_iter_read(xlo->xlo_backing_file, &i, &pos, 0); - if (len < 0) { - ret = len; - goto out_free_page; - } - - ret = __raw_file_fmt_do_transfer(xlo, READ, page, 0, bvec.bv_page, - bvec.bv_offset, len, offset >> 9); - if (ret) - goto out_free_page; - - flush_dcache_page(bvec.bv_page); - - if (len != bvec.bv_len) { - struct bio *bio; - - __rq_for_each_bio(bio, rq) - zero_fill_bio(bio); - break; - } - } - - ret = 0; -out_free_page: - __free_page(page); - return ret; -} - -static int raw_file_fmt_read(struct xloop_file_fmt *xlo_fmt, - struct request *rq) -{ - struct bio_vec bvec; - struct req_iterator iter; - struct iov_iter i; - ssize_t len; - struct xloop_device *xlo; - loff_t pos; - - xlo = xloop_file_fmt_get_xlo(xlo_fmt); - pos = __raw_file_fmt_rq_get_pos(xlo_fmt, rq); - - if (xlo->transfer) - return __raw_file_fmt_read_transfer(xlo, rq, pos); - - rq_for_each_segment(bvec, rq, iter) { - iov_iter_bvec(&i, READ, &bvec, 1, bvec.bv_len); - len = vfs_iter_read(xlo->xlo_backing_file, &i, &pos, 0); - if (len < 0) - return len; - - flush_dcache_page(bvec.bv_page); - - if (len != bvec.bv_len) { - struct bio *bio; - - __rq_for_each_bio(bio, rq) - zero_fill_bio(bio); - break; - } - cond_resched(); - } - - return 0; -} - -static void __raw_file_fmt_rw_aio_do_completion(struct xloop_cmd *cmd) -{ - struct request *rq = blk_mq_rq_from_pdu(cmd); - - if (!atomic_dec_and_test(&cmd->ref)) - return; - kfree(cmd->bvec); - cmd->bvec = NULL; -#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 9, 0) - if (likely(!blk_should_fake_timeout(rq->q))) - blk_mq_complete_request(rq); -#else - blk_mq_complete_request(rq); -#endif -} - -static void __raw_file_fmt_rw_aio_complete(struct kiocb *iocb, long ret, long ret2) -{ - struct xloop_cmd *cmd = container_of(iocb, struct xloop_cmd, iocb); - - if (cmd->css) - css_put(cmd->css); - cmd->ret = ret; - __raw_file_fmt_rw_aio_do_completion(cmd); -} - -static int __raw_file_fmt_rw_aio(struct xloop_device *xlo, - struct xloop_cmd *cmd, loff_t pos, bool rw) -{ - struct iov_iter iter; - struct req_iterator rq_iter; - struct bio_vec *bvec; - struct request *rq = blk_mq_rq_from_pdu(cmd); - struct bio *bio = rq->bio; - struct file *file = xlo->xlo_backing_file; - struct bio_vec tmp; - unsigned int offset; - int nr_bvec = 0; - int ret; - - rq_for_each_bvec(tmp, rq, rq_iter) - nr_bvec++; - - if (rq->bio != rq->biotail) { - - bvec = kmalloc_array(nr_bvec, sizeof(struct bio_vec), - GFP_NOIO); - if (!bvec) - return -EIO; - cmd->bvec = bvec; - - /* - * The bios of the request may be started from the middle of - * the 'bvec' because of bio splitting, so we can't directly - * copy bio->bi_iov_vec to new bvec. The rq_for_each_bvec - * API will take care of all details for us. - */ - rq_for_each_bvec(tmp, rq, rq_iter) { - *bvec = tmp; - bvec++; - } - bvec = cmd->bvec; - offset = 0; - } else { - /* - * Same here, this bio may be started from the middle of the - * 'bvec' because of bio splitting, so offset from the bvec - * must be passed to iov iterator - */ - offset = bio->bi_iter.bi_bvec_done; - bvec = __bvec_iter_bvec(bio->bi_io_vec, bio->bi_iter); - } - atomic_set(&cmd->ref, 2); - - iov_iter_bvec(&iter, rw, bvec, nr_bvec, blk_rq_bytes(rq)); - iter.iov_offset = offset; - - cmd->iocb.ki_pos = pos; - cmd->iocb.ki_filp = file; - cmd->iocb.ki_complete = __raw_file_fmt_rw_aio_complete; - cmd->iocb.ki_flags = IOCB_DIRECT; - cmd->iocb.ki_ioprio = IOPRIO_PRIO_VALUE(IOPRIO_CLASS_NONE, 0); - if (cmd->css) - kthread_associate_blkcg(cmd->css); - - if (rw == WRITE) - ret = call_write_iter(file, &cmd->iocb, &iter); - else - ret = call_read_iter(file, &cmd->iocb, &iter); - - __raw_file_fmt_rw_aio_do_completion(cmd); - kthread_associate_blkcg(NULL); - - if (ret != -EIOCBQUEUED) - cmd->iocb.ki_complete(&cmd->iocb, ret, 0); - return 0; -} - -static int raw_file_fmt_read_aio(struct xloop_file_fmt *xlo_fmt, - struct request *rq) -{ - struct xloop_device *xlo = xloop_file_fmt_get_xlo(xlo_fmt); - struct xloop_cmd *cmd = blk_mq_rq_to_pdu(rq); - loff_t pos = __raw_file_fmt_rq_get_pos(xlo_fmt, rq); - - return __raw_file_fmt_rw_aio(xlo, cmd, pos, READ); -} - -static int __raw_file_fmt_write_bvec(struct file *file, - struct bio_vec *bvec, - loff_t *ppos) -{ - struct iov_iter i; - ssize_t bw; - - iov_iter_bvec(&i, WRITE, bvec, 1, bvec->bv_len); - - file_start_write(file); - bw = vfs_iter_write(file, &i, ppos, 0); - file_end_write(file); - - if (likely(bw == bvec->bv_len)) - return 0; - - printk_ratelimited(KERN_ERR - "xloop_file_fmt_raw: Write error at byte offset %llu, length " - "%i.\n", (unsigned long long)*ppos, bvec->bv_len); - if (bw >= 0) - bw = -EIO; - return bw; -} - -/* - * This is the slow, transforming version that needs to double buffer the - * data as it cannot do the transformations in place without having direct - * access to the destination pages of the backing file. - */ -static int __raw_file_fmt_write_transfer(struct xloop_device *xlo, - struct request *rq, loff_t pos) -{ -struct bio_vec bvec, b; - struct req_iterator iter; - struct page *page; - int ret = 0; - - page = alloc_page(GFP_NOIO); - if (unlikely(!page)) - return -ENOMEM; - - rq_for_each_segment(bvec, rq, iter) { - ret = __raw_file_fmt_do_transfer(xlo, WRITE, page, 0, bvec.bv_page, - bvec.bv_offset, bvec.bv_len, pos >> 9); - if (unlikely(ret)) - break; - - b.bv_page = page; - b.bv_offset = 0; - b.bv_len = bvec.bv_len; - ret = __raw_file_fmt_write_bvec(xlo->xlo_backing_file, &b, &pos); - if (ret < 0) - break; - } - - __free_page(page); - return ret; -} - -static int raw_file_fmt_write(struct xloop_file_fmt *xlo_fmt, - struct request *rq) -{ - struct bio_vec bvec; - struct req_iterator iter; - int ret = 0; - struct xloop_device *xlo; - loff_t pos; - - xlo = xloop_file_fmt_get_xlo(xlo_fmt); - pos = __raw_file_fmt_rq_get_pos(xlo_fmt, rq); - - if (xlo->transfer) - return __raw_file_fmt_write_transfer(xlo, rq, pos); - - rq_for_each_segment(bvec, rq, iter) { - ret = __raw_file_fmt_write_bvec(xlo->xlo_backing_file, &bvec, &pos); - if (ret < 0) - break; - cond_resched(); - } - - return ret; -} - -static int raw_file_fmt_write_aio(struct xloop_file_fmt *xlo_fmt, - struct request *rq) -{ - struct xloop_device *xlo = xloop_file_fmt_get_xlo(xlo_fmt); - struct xloop_cmd *cmd = blk_mq_rq_to_pdu(rq); - loff_t pos = __raw_file_fmt_rq_get_pos(xlo_fmt, rq); - - return __raw_file_fmt_rw_aio(xlo, cmd, pos, WRITE); -} - -static int __raw_file_fmt_fallocate(struct xloop_device *xlo, - struct request *rq, loff_t pos, int mode) -{ - /* - * We use fallocate to manipulate the space mappings used by the image - * a.k.a. discard/zerorange. However we do not support this if - * encryption is enabled, because it may give an attacker useful - * information. - */ - struct file *file = xlo->xlo_backing_file; - struct request_queue *q = xlo->xlo_queue; - int ret; - - mode |= FALLOC_FL_KEEP_SIZE; - - if (!blk_queue_discard(q)) { - ret = -EOPNOTSUPP; - goto out; - } - - ret = file->f_op->fallocate(file, mode, pos, blk_rq_bytes(rq)); - if (unlikely(ret && ret != -EINVAL && ret != -EOPNOTSUPP)) - ret = -EIO; -out: - return ret; -} - -static int raw_file_fmt_write_zeros(struct xloop_file_fmt *xlo_fmt, - struct request *rq) -{ - loff_t pos = __raw_file_fmt_rq_get_pos(xlo_fmt, rq); - struct xloop_device *xlo = xloop_file_fmt_get_xlo(xlo_fmt); - - /* - * If the caller doesn't want deallocation, call zeroout to - * write zeroes the range. Otherwise, punch them out. - */ - return __raw_file_fmt_fallocate(xlo, rq, pos, - (rq->cmd_flags & REQ_NOUNMAP) ? - FALLOC_FL_ZERO_RANGE : - FALLOC_FL_PUNCH_HOLE); -} - -static int raw_file_fmt_discard(struct xloop_file_fmt *xlo_fmt, - struct request *rq) -{ - loff_t pos = __raw_file_fmt_rq_get_pos(xlo_fmt, rq); - struct xloop_device *xlo = xloop_file_fmt_get_xlo(xlo_fmt); - - return __raw_file_fmt_fallocate(xlo, rq, pos, FALLOC_FL_PUNCH_HOLE); -} - -static int raw_file_fmt_flush(struct xloop_file_fmt *xlo_fmt) -{ - struct xloop_device *xlo = xloop_file_fmt_get_xlo(xlo_fmt); - struct file *file = xlo->xlo_backing_file; - int ret = vfs_fsync(file, 0); - if (unlikely(ret && ret != -EINVAL)) - ret = -EIO; - - return ret; -} - -static loff_t raw_file_fmt_sector_size(struct xloop_file_fmt *xlo_fmt, - struct file *file, loff_t offset, loff_t sizelimit) -{ - loff_t xloopsize; - - /* Compute xloopsize in bytes */ - xloopsize = i_size_read(file->f_mapping->host); - if (offset > 0) - xloopsize -= offset; - /* offset is beyond i_size, weird but possible */ - if (xloopsize < 0) - return 0; - - if (sizelimit > 0 && sizelimit < xloopsize) - xloopsize = sizelimit; - /* - * Unfortunately, if we want to do I/O on the device, - * the number of 512-byte sectors has to fit into a sector_t. - */ - return xloopsize >> 9; -} - -static struct xloop_file_fmt_ops raw_file_fmt_ops = { - .init = NULL, - .exit = NULL, - .read = raw_file_fmt_read, - .write = raw_file_fmt_write, - .read_aio = raw_file_fmt_read_aio, - .write_aio = raw_file_fmt_write_aio, - .write_zeros = raw_file_fmt_write_zeros, - .discard = raw_file_fmt_discard, - .flush = raw_file_fmt_flush, - .sector_size = raw_file_fmt_sector_size, -}; - -static struct xloop_file_fmt_driver raw_file_fmt_driver = { - .name = "RAW", - .file_fmt_type = XLO_FILE_FMT_RAW, - .ops = &raw_file_fmt_ops, - .owner = THIS_MODULE, -}; - -static int __init xloop_file_fmt_raw_init(void) -{ - printk(KERN_INFO "xloop_file_fmt_raw: init xloop device RAW file format " - "driver"); - return xloop_file_fmt_register_driver(&raw_file_fmt_driver); -} - -static void __exit xloop_file_fmt_raw_exit(void) -{ - printk(KERN_INFO "xloop_file_fmt_raw: exit xloop device RAW file format " - "driver"); - xloop_file_fmt_unregister_driver(&raw_file_fmt_driver); -} - -module_init(xloop_file_fmt_raw_init); -module_exit(xloop_file_fmt_raw_exit); - -MODULE_LICENSE("GPL"); -MODULE_AUTHOR("Manuel Bentele "); -MODULE_DESCRIPTION("xloop device RAW file format driver"); -MODULE_SOFTDEP("pre: xloop"); diff --git a/loop_main.c b/loop_main.c deleted file mode 100644 index 9cd9c99..0000000 --- a/loop_main.c +++ /dev/null @@ -1,2221 +0,0 @@ -/* - * loop_main.c - * - * Written by Theodore Ts'o, 3/29/93 - * - * Copyright 1993 by Theodore Ts'o. Redistribution of this file is - * permitted under the GNU General Public License. - * - * DES encryption plus some minor changes by Werner Almesberger, 30-MAY-1993 - * more DES encryption plus IDEA encryption by Nicholas J. Leon, June 20, 1996 - * - * Modularized and updated for 1.1.16 kernel - Mitch Dsouza 28th May 1994 - * Adapted for 1.3.59 kernel - Andries Brouwer, 1 Feb 1996 - * - * Fixed do_xloop_request() re-entrancy - Vincent.Renardias@waw.com Mar 20, 1997 - * - * Added devfs support - Richard Gooch 16-Jan-1998 - * - * Handle sparse backing files correctly - Kenn Humborg, Jun 28, 1998 - * - * Loadable modules and other fixes by AK, 1998 - * - * Make real block number available to downstream transfer functions, enables - * CBC (and relatives) mode encryption requiring unique IVs per data block. - * Reed H. Petty, rhp@draper.net - * - * Maximum number of xloop devices now dynamic via max_xloop module parameter. - * Russell Kroll 19990701 - * - * Maximum number of xloop devices when compiled-in now selectable by passing - * max_xloop=<1-255> to the kernel on boot. - * Erik I. Bolsø, , Oct 31, 1999 - * - * Completely rewrite request handling to be make_request_fn style and - * non blocking, pushing work to a helper thread. Lots of fixes from - * Al Viro too. - * Jens Axboe , Nov 2000 - * - * Support up to 256 xloop devices - * Heinz Mauelshagen , Feb 2002 - * - * Support for falling back on the write file operation when the address space - * operations write_begin is not available on the backing filesystem. - * Anton Altaparmakov, 16 Feb 2005 - * - * Support for using file formats. - * Manuel Bentele , 2019 - * - * Still To Fix: - * - Advisory locking is ignored here. - * - Should use an own CAP_* category instead of CAP_SYS_ADMIN - * - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#ifdef CONFIG_DEBUG_FS -#include -#endif - -#include "loop_file_fmt.h" -#include "loop_main.h" - -#include - -static DEFINE_IDR(xloop_index_idr); -static DEFINE_MUTEX(xloop_ctl_mutex); - -static int max_part; -static int part_shift; - -static int transfer_xor(struct xloop_device *xlo, int cmd, - struct page *raw_page, unsigned raw_off, - struct page *xloop_page, unsigned xloop_off, - int size, sector_t real_block) -{ - char *raw_buf = kmap_atomic(raw_page) + raw_off; - char *xloop_buf = kmap_atomic(xloop_page) + xloop_off; - char *in, *out, *key; - int i, keysize; - - if (cmd == READ) { - in = raw_buf; - out = xloop_buf; - } else { - in = xloop_buf; - out = raw_buf; - } - - key = xlo->xlo_encrypt_key; - keysize = xlo->xlo_encrypt_key_size; - for (i = 0; i < size; i++) - *out++ = *in++ ^ key[(i & 511) % keysize]; - - kunmap_atomic(xloop_buf); - kunmap_atomic(raw_buf); - cond_resched(); - return 0; -} - -static int xor_init(struct xloop_device *xlo, const struct xloop_info64 *info) -{ - if (unlikely(info->xlo_encrypt_key_size <= 0)) - return -EINVAL; - return 0; -} - -static struct xloop_func_table none_funcs = { - .number = XLO_CRYPT_NONE, -}; - -static struct xloop_func_table xor_funcs = { - .number = XLO_CRYPT_XOR, - .transfer = transfer_xor, - .init = xor_init -}; - -/* xfer_funcs[0] is special - its release function is never called */ -static struct xloop_func_table *xfer_funcs[MAX_XLO_CRYPT] = { - &none_funcs, - &xor_funcs -}; - -static loff_t get_xloop_size(struct xloop_device *xlo, struct file *file) -{ - return xloop_file_fmt_sector_size(xlo->xlo_fmt, file, xlo->xlo_offset, - xlo->xlo_sizelimit); -} - -static void __xloop_update_dio(struct xloop_device *xlo, bool dio) -{ - struct file *file = xlo->xlo_backing_file; - struct address_space *mapping = file->f_mapping; - struct inode *inode = mapping->host; - unsigned short sb_bsize = 0; - unsigned dio_align = 0; - bool use_dio; - - if (inode->i_sb->s_bdev) { - sb_bsize = bdev_logical_block_size(inode->i_sb->s_bdev); - dio_align = sb_bsize - 1; - } - - /* - * We support direct I/O only if xlo_offset is aligned with the - * logical I/O size of backing device, and the logical block - * size of xloop is bigger than the backing device's and the xloop - * needn't transform transfer. - * - * TODO: the above condition may be loosed in the future, and - * direct I/O may be switched runtime at that time because most - * of requests in sane applications should be PAGE_SIZE aligned - */ - if (dio) { - if (queue_logical_block_size(xlo->xlo_queue) >= sb_bsize && - !(xlo->xlo_offset & dio_align) && - mapping->a_ops->direct_IO && - !xlo->transfer) - use_dio = true; - else - use_dio = false; - } else { - use_dio = false; - } - - if (xlo->use_dio == use_dio) - return; - - /* flush dirty pages before changing direct IO */ - xloop_file_fmt_flush(xlo->xlo_fmt); - - /* - * The flag of XLO_FLAGS_DIRECT_IO is handled similarly with - * XLO_FLAGS_READ_ONLY, both are set from kernel, and losetup - * will get updated by ioctl(XLOOP_GET_STATUS) - */ - if (xlo->xlo_state == Xlo_bound) - blk_mq_freeze_queue(xlo->xlo_queue); - xlo->use_dio = use_dio; - if (use_dio) { - blk_queue_flag_clear(QUEUE_FLAG_NOMERGES, xlo->xlo_queue); - xlo->xlo_flags |= XLO_FLAGS_DIRECT_IO; - } else { - blk_queue_flag_set(QUEUE_FLAG_NOMERGES, xlo->xlo_queue); - xlo->xlo_flags &= ~XLO_FLAGS_DIRECT_IO; - } - if (xlo->xlo_state == Xlo_bound) - blk_mq_unfreeze_queue(xlo->xlo_queue); -} - -/** - * xloop_validate_block_size() - validates the passed in block size - * @bsize: size to validate - */ -static int -xloop_validate_block_size(unsigned short bsize) -{ - if (bsize < 512 || bsize > PAGE_SIZE || !is_power_of_2(bsize)) - return -EINVAL; - - return 0; -} - -/** - * xloop_set_size() - sets device size and notifies userspace - * @xlo: struct xloop_device to set the size for - * @size: new size of the xloop device - * - * Callers must validate that the size passed into this function fits into - * a sector_t, eg using xloop_validate_size() - */ -static void xloop_set_size(struct xloop_device *xlo, loff_t size) -{ - struct block_device *bdev = xlo->xlo_device; -#if LINUX_VERSION_CODE < KERNEL_VERSION(5, 7, 0) - sector_t capacity; -#endif - - bd_set_size(bdev, size << SECTOR_SHIFT); - -#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 7, 0) - set_capacity_revalidate_and_notify(xlo->xlo_disk, size, false); -#else - capacity = get_capacity(xlo->xlo_disk); - set_capacity(xlo->xlo_disk, size); - if (capacity != size && capacity != 0 && size != 0) { - char *envp[] = { "RESIZE=1", NULL }; - kobject_uevent_env(&disk_to_dev(xlo->xlo_disk)->kobj, KOBJ_CHANGE, - envp); - } -#endif -} - -static void xlo_complete_rq(struct request *rq) -{ - struct xloop_cmd *cmd = blk_mq_rq_to_pdu(rq); - blk_status_t ret = BLK_STS_OK; - - if (!cmd->use_aio || cmd->ret < 0 || cmd->ret == blk_rq_bytes(rq) || - req_op(rq) != REQ_OP_READ) { - if (cmd->ret < 0) - ret = errno_to_blk_status(cmd->ret); - goto end_io; - } - - /* - * Short READ - if we got some data, advance our request and - * retry it. If we got no data, end the rest with EIO. - */ - if (cmd->ret) { - blk_update_request(rq, BLK_STS_OK, cmd->ret); - cmd->ret = 0; - blk_mq_requeue_request(rq, true); - } else { - if (cmd->use_aio) { - struct bio *bio = rq->bio; - - while (bio) { - zero_fill_bio(bio); - bio = bio->bi_next; - } - } - ret = BLK_STS_IOERR; -end_io: - blk_mq_end_request(rq, ret); - } -} - -static int do_req_filebacked(struct xloop_device *xlo, struct request *rq) -{ - struct xloop_cmd *cmd = blk_mq_rq_to_pdu(rq); - - /* - * xlo_write_simple and xlo_read_simple should have been covered - * by io submit style function like xlo_rw_aio(), one blocker - * is that xlo_read_simple() need to call flush_dcache_page after - * the page is written from kernel, and it isn't easy to handle - * this in io submit style function which submits all segments - * of the req at one time. And direct read IO doesn't need to - * run flush_dcache_page(). - */ - switch (req_op(rq)) { - case REQ_OP_FLUSH: - return xloop_file_fmt_flush(xlo->xlo_fmt); - case REQ_OP_WRITE_ZEROES: - return xloop_file_fmt_write_zeros(xlo->xlo_fmt, rq); - case REQ_OP_DISCARD: - return xloop_file_fmt_discard(xlo->xlo_fmt, rq); - case REQ_OP_WRITE: - if (cmd->use_aio) - return xloop_file_fmt_write_aio(xlo->xlo_fmt, rq); - else - return xloop_file_fmt_write(xlo->xlo_fmt, rq); - case REQ_OP_READ: - if (cmd->use_aio) - return xloop_file_fmt_read_aio(xlo->xlo_fmt, rq); - else - return xloop_file_fmt_read(xlo->xlo_fmt, rq); - default: - WARN_ON_ONCE(1); - return -EIO; - } -} - -static inline void xloop_update_dio(struct xloop_device *xlo) -{ - __xloop_update_dio(xlo, (xlo->xlo_backing_file->f_flags & O_DIRECT) | - xlo->use_dio); -} - -static void xloop_reread_partitions(struct xloop_device *xlo, - struct block_device *bdev) -{ - int rc; - - mutex_lock(&bdev->bd_mutex); -#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 5, 0) - rc = bdev_disk_changed(bdev, false); -#else - rc = blkdev_reread_part(bdev); -#endif - mutex_unlock(&bdev->bd_mutex); - if (rc) - pr_warn("%s: partition scan of xloop%d (%s) failed (rc=%d)\n", - __func__, xlo->xlo_number, xlo->xlo_file_name, rc); -} - -static inline int is_xloop_device(struct file *file) -{ - struct inode *i = file->f_mapping->host; - - return i && S_ISBLK(i->i_mode) && MAJOR(i->i_rdev) == LOOP_MAJOR; -} - -static int xloop_validate_file(struct file *file, struct block_device *bdev) -{ - struct inode *inode = file->f_mapping->host; - struct file *f = file; - - /* Avoid recursion */ - while (is_xloop_device(f)) { - struct xloop_device *l; - - if (f->f_mapping->host->i_bdev == bdev) - return -EBADF; - - l = f->f_mapping->host->i_bdev->bd_disk->private_data; - if (l->xlo_state != Xlo_bound) { - return -EINVAL; - } - f = l->xlo_backing_file; - } - if (!S_ISREG(inode->i_mode) && !S_ISBLK(inode->i_mode)) - return -EINVAL; - return 0; -} - -/* - * xloop_change_fd switched the backing store of a xloopback device to - * a new file. This is useful for operating system installers to free up - * the original file and in High Availability environments to switch to - * an alternative location for the content in case of server meltdown. - * This can only work if the xloop device is used read-only, and if the - * new backing store is the same size and type as the old backing store. - */ -static int xloop_change_fd(struct xloop_device *xlo, struct block_device *bdev, - unsigned int arg) -{ - struct file *file = NULL, *old_file; - int error; - bool partscan; - - error = mutex_lock_killable(&xloop_ctl_mutex); - if (error) - return error; - error = -ENXIO; - if (xlo->xlo_state != Xlo_bound) - goto out_err; - - /* the xloop device has to be read-only */ - error = -EINVAL; - if (!(xlo->xlo_flags & XLO_FLAGS_READ_ONLY)) - goto out_err; - - error = -EBADF; - file = fget(arg); - if (!file) - goto out_err; - - error = xloop_validate_file(file, bdev); - if (error) - goto out_err; - - old_file = xlo->xlo_backing_file; - - error = -EINVAL; - - /* size of the new backing store needs to be the same */ - if (get_xloop_size(xlo, file) != get_xloop_size(xlo, old_file)) - goto out_err; - - /* and ... switch */ - blk_mq_freeze_queue(xlo->xlo_queue); - mapping_set_gfp_mask(old_file->f_mapping, xlo->old_gfp_mask); - xlo->xlo_backing_file = file; - xlo->old_gfp_mask = mapping_gfp_mask(file->f_mapping); - mapping_set_gfp_mask(file->f_mapping, - xlo->old_gfp_mask & ~(__GFP_IO|__GFP_FS)); - xloop_update_dio(xlo); - blk_mq_unfreeze_queue(xlo->xlo_queue); - partscan = xlo->xlo_flags & XLO_FLAGS_PARTSCAN; - mutex_unlock(&xloop_ctl_mutex); - /* - * We must drop file reference outside of xloop_ctl_mutex as dropping - * the file ref can take bd_mutex which creates circular locking - * dependency. - */ - fput(old_file); - if (partscan) - xloop_reread_partitions(xlo, bdev); - return 0; - -out_err: - mutex_unlock(&xloop_ctl_mutex); - if (file) - fput(file); - return error; -} - -/* xloop sysfs attributes */ - -static ssize_t xloop_attr_show(struct device *dev, char *page, - ssize_t (*callback)(struct xloop_device *, char *)) -{ - struct gendisk *disk = dev_to_disk(dev); - struct xloop_device *xlo = disk->private_data; - - return callback(xlo, page); -} - -#define XLOOP_ATTR_RO(_name) \ -static ssize_t xloop_attr_##_name##_show(struct xloop_device *, char *); \ -static ssize_t xloop_attr_do_show_##_name(struct device *d, \ - struct device_attribute *attr, char *b) \ -{ \ - return xloop_attr_show(d, b, xloop_attr_##_name##_show); \ -} \ -static struct device_attribute xloop_attr_##_name = \ - __ATTR(_name, 0444, xloop_attr_do_show_##_name, NULL); - -static ssize_t xloop_attr_backing_file_show(struct xloop_device *xlo, char *buf) -{ - ssize_t ret; - char *p = NULL; - - spin_lock_irq(&xlo->xlo_lock); - if (xlo->xlo_backing_file) - p = file_path(xlo->xlo_backing_file, buf, PAGE_SIZE - 1); - spin_unlock_irq(&xlo->xlo_lock); - - if (IS_ERR_OR_NULL(p)) - ret = PTR_ERR(p); - else { - ret = strlen(p); - memmove(buf, p, ret); - buf[ret++] = '\n'; - buf[ret] = 0; - } - - return ret; -} - -static ssize_t xloop_attr_file_fmt_type_show(struct xloop_device *xlo, - char *buf) -{ - ssize_t len = 0; - - len = xloop_file_fmt_print_type(xlo->xlo_fmt->file_fmt_type, buf); - len += sprintf(buf + len, "\n"); - - return len; -} - -static ssize_t xloop_attr_offset_show(struct xloop_device *xlo, char *buf) -{ - return sprintf(buf, "%llu\n", (unsigned long long)xlo->xlo_offset); -} - -static ssize_t xloop_attr_sizelimit_show(struct xloop_device *xlo, char *buf) -{ - return sprintf(buf, "%llu\n", (unsigned long long)xlo->xlo_sizelimit); -} - -static ssize_t xloop_attr_autoclear_show(struct xloop_device *xlo, char *buf) -{ - int autoclear = (xlo->xlo_flags & XLO_FLAGS_AUTOCLEAR); - - return sprintf(buf, "%s\n", autoclear ? "1" : "0"); -} - -static ssize_t xloop_attr_partscan_show(struct xloop_device *xlo, char *buf) -{ - int partscan = (xlo->xlo_flags & XLO_FLAGS_PARTSCAN); - - return sprintf(buf, "%s\n", partscan ? "1" : "0"); -} - -static ssize_t xloop_attr_dio_show(struct xloop_device *xlo, char *buf) -{ - int dio = (xlo->xlo_flags & XLO_FLAGS_DIRECT_IO); - - return sprintf(buf, "%s\n", dio ? "1" : "0"); -} - -XLOOP_ATTR_RO(backing_file); -XLOOP_ATTR_RO(file_fmt_type); -XLOOP_ATTR_RO(offset); -XLOOP_ATTR_RO(sizelimit); -XLOOP_ATTR_RO(autoclear); -XLOOP_ATTR_RO(partscan); -XLOOP_ATTR_RO(dio); - -static struct attribute *xloop_attrs[] = { - &xloop_attr_backing_file.attr, - &xloop_attr_file_fmt_type.attr, - &xloop_attr_offset.attr, - &xloop_attr_sizelimit.attr, - &xloop_attr_autoclear.attr, - &xloop_attr_partscan.attr, - &xloop_attr_dio.attr, - NULL, -}; - -static struct attribute_group xloop_attribute_group = { - .name = "xloop", - .attrs= xloop_attrs, -}; - -static void xloop_sysfs_init(struct xloop_device *xlo) -{ - xlo->sysfs_inited = !sysfs_create_group(&disk_to_dev(xlo->xlo_disk)->kobj, - &xloop_attribute_group); -} - -static void xloop_sysfs_exit(struct xloop_device *xlo) -{ - if (xlo->sysfs_inited) - sysfs_remove_group(&disk_to_dev(xlo->xlo_disk)->kobj, - &xloop_attribute_group); -} - -static void xloop_config_discard(struct xloop_device *xlo) -{ - struct file *file = xlo->xlo_backing_file; - struct inode *inode = file->f_mapping->host; - struct request_queue *q = xlo->xlo_queue; - u32 granularity, max_discard_sectors; - - /* - * If the backing device is a block device, mirror its zeroing - * capability. Set the discard sectors to the block device's zeroing - * capabilities because xloop discards result in blkdev_issue_zeroout(), - * not blkdev_issue_discard(). This maintains consistent behavior with - * file-backed xloop devices: discarded regions read back as zero. - */ - if (S_ISBLK(inode->i_mode) && !xlo->xlo_encrypt_key_size) { - struct request_queue *backingq; - - backingq = bdev_get_queue(inode->i_bdev); - - max_discard_sectors = backingq->limits.max_write_zeroes_sectors; - granularity = backingq->limits.discard_granularity ?: - queue_physical_block_size(backingq); - - /* - * We use punch hole to reclaim the free space used by the - * image a.k.a. discard. However we do not support discard if - * encryption is enabled, because it may give an attacker - * useful information. - */ - } else if (!file->f_op->fallocate || xlo->xlo_encrypt_key_size) { - max_discard_sectors = 0; - granularity = 0; - - } else { - max_discard_sectors = UINT_MAX >> 9; - granularity = inode->i_sb->s_blocksize; - } - - if (max_discard_sectors) { - q->limits.discard_granularity = granularity; - blk_queue_max_discard_sectors(q, max_discard_sectors); - blk_queue_max_write_zeroes_sectors(q, max_discard_sectors); - blk_queue_flag_set(QUEUE_FLAG_DISCARD, q); - } else { - q->limits.discard_granularity = 0; - blk_queue_max_discard_sectors(q, 0); - blk_queue_max_write_zeroes_sectors(q, 0); - blk_queue_flag_clear(QUEUE_FLAG_DISCARD, q); - } - q->limits.discard_alignment = 0; -} - -static void xloop_unprepare_queue(struct xloop_device *xlo) -{ - kthread_flush_worker(&xlo->worker); - kthread_stop(xlo->worker_task); -} - -static int xloop_kthread_worker_fn(void *worker_ptr) -{ -#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 8, 0) - current->flags |= PF_LOCAL_THROTTLE | PF_MEMALLOC_NOIO; -#else - current->flags |= PF_LESS_THROTTLE | PF_MEMALLOC_NOIO; -#endif - return kthread_worker_fn(worker_ptr); -} - -static int xloop_prepare_queue(struct xloop_device *xlo) -{ - kthread_init_worker(&xlo->worker); - xlo->worker_task = kthread_run(xloop_kthread_worker_fn, - &xlo->worker, "xloop%d", xlo->xlo_number); - if (IS_ERR(xlo->worker_task)) - return -ENOMEM; - set_user_nice(xlo->worker_task, MIN_NICE); - return 0; -} - -static void xloop_update_rotational(struct xloop_device *xlo) -{ - struct file *file = xlo->xlo_backing_file; - struct inode *file_inode = file->f_mapping->host; - struct block_device *file_bdev = file_inode->i_sb->s_bdev; - struct request_queue *q = xlo->xlo_queue; - bool nonrot = true; - - /* not all filesystems (e.g. tmpfs) have a sb->s_bdev */ - if (file_bdev) - nonrot = blk_queue_nonrot(bdev_get_queue(file_bdev)); - - if (nonrot) - blk_queue_flag_set(QUEUE_FLAG_NONROT, q); - else - blk_queue_flag_clear(QUEUE_FLAG_NONROT, q); -} - -static int -xloop_release_xfer(struct xloop_device *xlo) -{ - int err = 0; - struct xloop_func_table *xfer = xlo->xlo_encryption; - - if (xfer) { - if (xfer->release) - err = xfer->release(xlo); - xlo->transfer = NULL; - xlo->xlo_encryption = NULL; - module_put(xfer->owner); - } - return err; -} - -static int -xloop_init_xfer(struct xloop_device *xlo, struct xloop_func_table *xfer, - const struct xloop_info64 *i) -{ - int err = 0; - - if (xfer) { - struct module *owner = xfer->owner; - - if (!try_module_get(owner)) - return -EINVAL; - if (xfer->init) - err = xfer->init(xlo, i); - if (err) - module_put(owner); - else - xlo->xlo_encryption = xfer; - } - return err; -} - -/** - * xloop_set_status_from_info - configure device from xloop_info - * @xlo: struct xloop_device to configure - * @info: struct xloop_info64 to configure the device with - * - * Configures the xloop device parameters according to the passed - * in xloop_info64 configuration. - */ -static int -xloop_set_status_from_info(struct xloop_device *xlo, - const struct xloop_info64 *info) -{ - int err; - struct xloop_func_table *xfer; - kuid_t uid = current_uid(); - - if ((unsigned int) info->xlo_encrypt_key_size > XLO_KEY_SIZE) - return -EINVAL; - - err = xloop_release_xfer(xlo); - if (err) - return err; - - if (info->xlo_encrypt_type) { - unsigned int type = info->xlo_encrypt_type; - - if (type >= MAX_XLO_CRYPT) - return -EINVAL; - xfer = xfer_funcs[type]; - if (xfer == NULL) - return -EINVAL; - } else - xfer = NULL; - - err = xloop_init_xfer(xlo, xfer, info); - if (err) - return err; - - xlo->xlo_offset = info->xlo_offset; - xlo->xlo_sizelimit = info->xlo_sizelimit; - memcpy(xlo->xlo_file_name, info->xlo_file_name, XLO_NAME_SIZE); - memcpy(xlo->xlo_crypt_name, info->xlo_crypt_name, XLO_NAME_SIZE); - xlo->xlo_file_name[XLO_NAME_SIZE-1] = 0; - xlo->xlo_crypt_name[XLO_NAME_SIZE-1] = 0; - - if (!xfer) - xfer = &none_funcs; - xlo->transfer = xfer->transfer; - xlo->ioctl = xfer->ioctl; - - xlo->xlo_flags = info->xlo_flags; - - xlo->xlo_encrypt_key_size = info->xlo_encrypt_key_size; - xlo->xlo_init[0] = info->xlo_init[0]; - xlo->xlo_init[1] = info->xlo_init[1]; - if (info->xlo_encrypt_key_size) { - memcpy(xlo->xlo_encrypt_key, info->xlo_encrypt_key, - info->xlo_encrypt_key_size); - xlo->xlo_key_owner = uid; - } - - return 0; -} - -static int xloop_configure(struct xloop_device *xlo, fmode_t mode, - struct block_device *bdev, - const struct xloop_config *config) -{ - struct file *file; - struct inode *inode; - struct address_space *mapping; - struct block_device *claimed_bdev = NULL; - int error; - loff_t size; - bool partscan; - unsigned short bsize; - - /* This is safe, since we have a reference from open(). */ - __module_get(THIS_MODULE); - - error = -EBADF; - file = fget(config->fd); - if (!file) - goto out; - - /* - * If we don't hold exclusive handle for the device, upgrade to it - * here to avoid changing device under exclusive owner. - */ - if (!(mode & FMODE_EXCL)) { -#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 9, 0) - claimed_bdev = bdev->bd_contains; - error = bd_prepare_to_claim(bdev, claimed_bdev, xloop_configure); - if (error) - goto out_putf; -#else - claimed_bdev = bd_start_claiming(bdev, xloop_configure); - if (IS_ERR(claimed_bdev)) { - error = PTR_ERR(claimed_bdev); - goto out_putf; - } -#endif - } - - error = mutex_lock_killable(&xloop_ctl_mutex); - if (error) - goto out_bdev; - - error = -EBUSY; - if (xlo->xlo_state != Xlo_unbound) - goto out_unlock; - - error = xloop_validate_file(file, bdev); - if (error) - goto out_unlock; - - mapping = file->f_mapping; - inode = mapping->host; - - if ((config->info.xlo_flags & ~XLOOP_CONFIGURE_SETTABLE_FLAGS) != 0) { - error = -EINVAL; - goto out_unlock; - } - - if (config->block_size) { - error = xloop_validate_block_size(config->block_size); - if (error) - goto out_unlock; - } - - error = xloop_set_status_from_info(xlo, &config->info); - if (error) - goto out_unlock; - - if (!(file->f_mode & FMODE_WRITE) || !(mode & FMODE_WRITE) || - !file->f_op->write_iter) - xlo->xlo_flags |= XLO_FLAGS_READ_ONLY; - - error = xloop_prepare_queue(xlo); - if (error) - goto out_unlock; - - error = xloop_file_fmt_init(xlo->xlo_fmt, - config->info.xlo_file_fmt_type); - if (error) - goto out_unlock; - - set_device_ro(bdev, (xlo->xlo_flags & XLO_FLAGS_READ_ONLY) != 0); - - xlo->use_dio = xlo->xlo_flags & XLO_FLAGS_DIRECT_IO; - xlo->xlo_device = bdev; - xlo->xlo_backing_file = file; - xlo->old_gfp_mask = mapping_gfp_mask(mapping); - mapping_set_gfp_mask(mapping, xlo->old_gfp_mask & ~(__GFP_IO|__GFP_FS)); - - if (!(xlo->xlo_flags & XLO_FLAGS_READ_ONLY) && file->f_op->fsync) - blk_queue_write_cache(xlo->xlo_queue, true, false); - - if (config->block_size) - bsize = config->block_size; - else if ((xlo->xlo_backing_file->f_flags & O_DIRECT) && inode->i_sb->s_bdev) - /* In case of direct I/O, match underlying block size */ - bsize = bdev_logical_block_size(inode->i_sb->s_bdev); - else - bsize = 512; - - blk_queue_logical_block_size(xlo->xlo_queue, bsize); - blk_queue_physical_block_size(xlo->xlo_queue, bsize); - blk_queue_io_min(xlo->xlo_queue, bsize); - - xloop_update_rotational(xlo); - xloop_update_dio(xlo); - xloop_sysfs_init(xlo); - - size = get_xloop_size(xlo, file); - xloop_set_size(xlo, size); - - set_blocksize(bdev, S_ISBLK(inode->i_mode) ? - block_size(inode->i_bdev) : PAGE_SIZE); - - xlo->xlo_state = Xlo_bound; - if (part_shift) - xlo->xlo_flags |= XLO_FLAGS_PARTSCAN; - partscan = xlo->xlo_flags & XLO_FLAGS_PARTSCAN; - if (partscan) - xlo->xlo_disk->flags &= ~GENHD_FL_NO_PART_SCAN; - - /* Grab the block_device to prevent its destruction after we - * put /dev/xloopXX inode. Later in __xloop_clr_fd() we bdput(bdev). - */ - bdgrab(bdev); - mutex_unlock(&xloop_ctl_mutex); - if (partscan) - xloop_reread_partitions(xlo, bdev); - if (claimed_bdev) - bd_abort_claiming(bdev, claimed_bdev, xloop_configure); - return 0; - -out_unlock: - mutex_unlock(&xloop_ctl_mutex); -out_bdev: - if (claimed_bdev) - bd_abort_claiming(bdev, claimed_bdev, xloop_configure); -out_putf: - fput(file); -out: - /* This is safe: open() is still holding a reference. */ - module_put(THIS_MODULE); - return error; -} - -static int __xloop_clr_fd(struct xloop_device *xlo, bool release) -{ - struct file *filp = NULL; - gfp_t gfp = xlo->old_gfp_mask; - struct block_device *bdev = xlo->xlo_device; - int err = 0; - bool partscan = false; - int xlo_number; - - mutex_lock(&xloop_ctl_mutex); - if (WARN_ON_ONCE(xlo->xlo_state != Xlo_rundown)) { - err = -ENXIO; - goto out_unlock; - } - - filp = xlo->xlo_backing_file; - if (filp == NULL) { - err = -EINVAL; - goto out_unlock; - } - - /* freeze request queue during the transition */ - blk_mq_freeze_queue(xlo->xlo_queue); - - xloop_file_fmt_exit(xlo->xlo_fmt); - - spin_lock_irq(&xlo->xlo_lock); - xlo->xlo_backing_file = NULL; - spin_unlock_irq(&xlo->xlo_lock); - - xloop_release_xfer(xlo); - xlo->transfer = NULL; - xlo->ioctl = NULL; - xlo->xlo_device = NULL; - xlo->xlo_encryption = NULL; - xlo->xlo_offset = 0; - xlo->xlo_sizelimit = 0; - xlo->xlo_encrypt_key_size = 0; - memset(xlo->xlo_encrypt_key, 0, XLO_KEY_SIZE); - memset(xlo->xlo_crypt_name, 0, XLO_NAME_SIZE); - memset(xlo->xlo_file_name, 0, XLO_NAME_SIZE); - blk_queue_logical_block_size(xlo->xlo_queue, 512); - blk_queue_physical_block_size(xlo->xlo_queue, 512); - blk_queue_io_min(xlo->xlo_queue, 512); - if (bdev) { - bdput(bdev); - invalidate_bdev(bdev); - bdev->bd_inode->i_mapping->wb_err = 0; - } - set_capacity(xlo->xlo_disk, 0); - xloop_sysfs_exit(xlo); - if (bdev) { - bd_set_size(bdev, 0); - /* let user-space know about this change */ - kobject_uevent(&disk_to_dev(bdev->bd_disk)->kobj, KOBJ_CHANGE); - } - mapping_set_gfp_mask(filp->f_mapping, gfp); - /* This is safe: open() is still holding a reference. */ - module_put(THIS_MODULE); - blk_mq_unfreeze_queue(xlo->xlo_queue); - - partscan = xlo->xlo_flags & XLO_FLAGS_PARTSCAN && bdev; - xlo_number = xlo->xlo_number; - xloop_unprepare_queue(xlo); -out_unlock: - mutex_unlock(&xloop_ctl_mutex); - if (partscan) { - /* - * bd_mutex has been held already in release path, so don't - * acquire it if this function is called in such case. - * - * If the reread partition isn't from release path, xlo_refcnt - * must be at least one and it can only become zero when the - * current holder is released. - */ - if (!release) - mutex_lock(&bdev->bd_mutex); -#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 5, 0) - err = bdev_disk_changed(bdev, false); -#else - err = blkdev_reread_part(bdev); -#endif - if (!release) - mutex_unlock(&bdev->bd_mutex); - if (err) - pr_warn("%s: partition scan of xloop%d failed (rc=%d)\n", - __func__, xlo_number, err); - /* Device is gone, no point in returning error */ - err = 0; - } - - /* - * xlo->xlo_state is set to Xlo_unbound here after above partscan has - * finished. - * - * There cannot be anybody else entering __xloop_clr_fd() as - * xlo->xlo_backing_file is already cleared and Xlo_rundown state - * protects us from all the other places trying to change the 'xlo' - * device. - */ - mutex_lock(&xloop_ctl_mutex); - xlo->xlo_flags = 0; - if (!part_shift) - xlo->xlo_disk->flags |= GENHD_FL_NO_PART_SCAN; - xlo->xlo_state = Xlo_unbound; - mutex_unlock(&xloop_ctl_mutex); - - /* - * Need not hold xloop_ctl_mutex to fput backing file. - * Calling fput holding xloop_ctl_mutex triggers a circular - * lock dependency possibility warning as fput can take - * bd_mutex which is usually taken before xloop_ctl_mutex. - */ - if (filp) - fput(filp); - return err; -} - -static int xloop_clr_fd(struct xloop_device *xlo) -{ - int err; - - err = mutex_lock_killable(&xloop_ctl_mutex); - if (err) - return err; - if (xlo->xlo_state != Xlo_bound) { - mutex_unlock(&xloop_ctl_mutex); - return -ENXIO; - } - /* - * If we've explicitly asked to tear down the xloop device, - * and it has an elevated reference count, set it for auto-teardown when - * the last reference goes away. This stops $!~#$@ udev from - * preventing teardown because it decided that it needs to run blkid on - * the xloopback device whenever they appear. xfstests is notorious for - * failing tests because blkid via udev races with a losetup - * /do something like mkfs/losetup -d causing the losetup -d - * command to fail with EBUSY. - */ - if (atomic_read(&xlo->xlo_refcnt) > 1) { - xlo->xlo_flags |= XLO_FLAGS_AUTOCLEAR; - mutex_unlock(&xloop_ctl_mutex); - return 0; - } - xlo->xlo_state = Xlo_rundown; - mutex_unlock(&xloop_ctl_mutex); - - return __xloop_clr_fd(xlo, false); -} - -static int -xloop_set_status(struct xloop_device *xlo, const struct xloop_info64 *info) -{ - int err; - struct block_device *bdev; - kuid_t uid = current_uid(); - int prev_xlo_flags; - bool partscan = false; - bool size_changed = false; - - err = mutex_lock_killable(&xloop_ctl_mutex); - if (err) - return err; - if (xlo->xlo_encrypt_key_size && - !uid_eq(xlo->xlo_key_owner, uid) && - !capable(CAP_SYS_ADMIN)) { - err = -EPERM; - goto out_unlock; - } - if (xlo->xlo_state != Xlo_bound) { - err = -ENXIO; - goto out_unlock; - } - - if (xlo->xlo_offset != info->xlo_offset || - xlo->xlo_sizelimit != info->xlo_sizelimit) { - size_changed = true; - sync_blockdev(xlo->xlo_device); - invalidate_bdev(xlo->xlo_device); - } - - /* I/O need to be drained during transfer transition */ - blk_mq_freeze_queue(xlo->xlo_queue); - - if (size_changed && xlo->xlo_device->bd_inode->i_mapping->nrpages) { - /* If any pages were dirtied after invalidate_bdev(), try again */ - err = -EAGAIN; - pr_warn("%s: xloop%d (%s) has still dirty pages (nrpages=%lu)\n", - __func__, xlo->xlo_number, xlo->xlo_file_name, - xlo->xlo_device->bd_inode->i_mapping->nrpages); - goto out_unfreeze; - } - - prev_xlo_flags = xlo->xlo_flags; - - err = xloop_set_status_from_info(xlo, info); - if (err) - goto out_unfreeze; - - /* Mask out flags that can't be set using XLOOP_SET_STATUS. */ - xlo->xlo_flags &= XLOOP_SET_STATUS_SETTABLE_FLAGS; - /* For those flags, use the previous values instead */ - xlo->xlo_flags |= prev_xlo_flags & ~XLOOP_SET_STATUS_SETTABLE_FLAGS; - /* For flags that can't be cleared, use previous values too */ - xlo->xlo_flags |= prev_xlo_flags & ~XLOOP_SET_STATUS_CLEARABLE_FLAGS; - - if (xlo->xlo_fmt->file_fmt_type != info->xlo_file_fmt_type) { - /* xloop file format has changed, so change file format driver */ - err = xloop_file_fmt_change(xlo->xlo_fmt, info->xlo_file_fmt_type); - if (err) - goto out_unfreeze; - - /* After change of the file format, recalculate the capacity of - * the loop device. */ - size_changed = true; - } - - if (size_changed) { - loff_t new_size = get_xloop_size(xlo, xlo->xlo_backing_file); - xloop_set_size(xlo, new_size); - } - - xloop_config_discard(xlo); - - /* update dio if xlo_offset or transfer is changed */ - __xloop_update_dio(xlo, xlo->use_dio); - -out_unfreeze: - blk_mq_unfreeze_queue(xlo->xlo_queue); - - if (!err && (xlo->xlo_flags & XLO_FLAGS_PARTSCAN) && - !(prev_xlo_flags & XLO_FLAGS_PARTSCAN)) { - xlo->xlo_disk->flags &= ~GENHD_FL_NO_PART_SCAN; - bdev = xlo->xlo_device; - partscan = true; - } -out_unlock: - mutex_unlock(&xloop_ctl_mutex); - if (partscan) - xloop_reread_partitions(xlo, bdev); - - return err; -} - -static int -xloop_get_status(struct xloop_device *xlo, struct xloop_info64 *info) -{ - struct path path; - struct kstat stat; - int ret; - - ret = mutex_lock_killable(&xloop_ctl_mutex); - if (ret) - return ret; - if (xlo->xlo_state != Xlo_bound) { - mutex_unlock(&xloop_ctl_mutex); - return -ENXIO; - } - - memset(info, 0, sizeof(*info)); - info->xlo_number = xlo->xlo_number; - info->xlo_offset = xlo->xlo_offset; - info->xlo_sizelimit = xlo->xlo_sizelimit; - info->xlo_flags = xlo->xlo_flags; - memcpy(info->xlo_file_name, xlo->xlo_file_name, XLO_NAME_SIZE); - memcpy(info->xlo_crypt_name, xlo->xlo_crypt_name, XLO_NAME_SIZE); - info->xlo_encrypt_type = - xlo->xlo_encryption ? xlo->xlo_encryption->number : 0; - if (xlo->xlo_encrypt_key_size && capable(CAP_SYS_ADMIN)) { - info->xlo_encrypt_key_size = xlo->xlo_encrypt_key_size; - memcpy(info->xlo_encrypt_key, xlo->xlo_encrypt_key, - xlo->xlo_encrypt_key_size); - } - - /* Drop xloop_ctl_mutex while we call into the filesystem. */ - path = xlo->xlo_backing_file->f_path; - path_get(&path); - mutex_unlock(&xloop_ctl_mutex); - ret = vfs_getattr(&path, &stat, STATX_INO, AT_STATX_SYNC_AS_STAT); - if (!ret) { - info->xlo_device = huge_encode_dev(stat.dev); - info->xlo_inode = stat.ino; - info->xlo_rdevice = huge_encode_dev(stat.rdev); - } - path_put(&path); - return ret; -} - -static void -xloop_info64_from_old(const struct xloop_info *info, struct xloop_info64 *info64) -{ - memset(info64, 0, sizeof(*info64)); - info64->xlo_number = info->xlo_number; - info64->xlo_device = info->xlo_device; - info64->xlo_inode = info->xlo_inode; - info64->xlo_rdevice = info->xlo_rdevice; - info64->xlo_offset = info->xlo_offset; - info64->xlo_sizelimit = 0; - info64->xlo_encrypt_type = info->xlo_encrypt_type; - info64->xlo_encrypt_key_size = info->xlo_encrypt_key_size; - info64->xlo_flags = info->xlo_flags; - info64->xlo_init[0] = info->xlo_init[0]; - info64->xlo_init[1] = info->xlo_init[1]; - info64->xlo_file_fmt_type = info->xlo_file_fmt_type; - if (info->xlo_encrypt_type == XLO_CRYPT_CRYPTOAPI) - memcpy(info64->xlo_crypt_name, info->xlo_name, XLO_NAME_SIZE); - else - memcpy(info64->xlo_file_name, info->xlo_name, XLO_NAME_SIZE); - memcpy(info64->xlo_encrypt_key, info->xlo_encrypt_key, XLO_KEY_SIZE); -} - -static int -xloop_info64_to_old(const struct xloop_info64 *info64, struct xloop_info *info) -{ - memset(info, 0, sizeof(*info)); - info->xlo_number = info64->xlo_number; - info->xlo_device = info64->xlo_device; - info->xlo_inode = info64->xlo_inode; - info->xlo_rdevice = info64->xlo_rdevice; - info->xlo_offset = info64->xlo_offset; - info->xlo_encrypt_type = info64->xlo_encrypt_type; - info->xlo_encrypt_key_size = info64->xlo_encrypt_key_size; - info->xlo_flags = info64->xlo_flags; - info->xlo_init[0] = info64->xlo_init[0]; - info->xlo_init[1] = info64->xlo_init[1]; - info->xlo_file_fmt_type = info64->xlo_file_fmt_type; - if (info->xlo_encrypt_type == XLO_CRYPT_CRYPTOAPI) - memcpy(info->xlo_name, info64->xlo_crypt_name, XLO_NAME_SIZE); - else - memcpy(info->xlo_name, info64->xlo_file_name, XLO_NAME_SIZE); - memcpy(info->xlo_encrypt_key, info64->xlo_encrypt_key, XLO_KEY_SIZE); - - /* error in case values were truncated */ - if (info->xlo_device != info64->xlo_device || - info->xlo_rdevice != info64->xlo_rdevice || - info->xlo_inode != info64->xlo_inode || - info->xlo_offset != info64->xlo_offset) - return -EOVERFLOW; - - return 0; -} - -static int -xloop_set_status_old(struct xloop_device *xlo, const struct xloop_info __user *arg) -{ - struct xloop_info info; - struct xloop_info64 info64; - - if (copy_from_user(&info, arg, sizeof (struct xloop_info))) - return -EFAULT; - xloop_info64_from_old(&info, &info64); - return xloop_set_status(xlo, &info64); -} - -static int -xloop_set_status64(struct xloop_device *xlo, const struct xloop_info64 __user *arg) -{ - struct xloop_info64 info64; - - if (copy_from_user(&info64, arg, sizeof (struct xloop_info64))) - return -EFAULT; - return xloop_set_status(xlo, &info64); -} - -static int -xloop_get_status_old(struct xloop_device *xlo, struct xloop_info __user *arg) { - struct xloop_info info; - struct xloop_info64 info64; - int err; - - if (!arg) - return -EINVAL; - err = xloop_get_status(xlo, &info64); - if (!err) - err = xloop_info64_to_old(&info64, &info); - if (!err && copy_to_user(arg, &info, sizeof(info))) - err = -EFAULT; - - return err; -} - -static int -xloop_get_status64(struct xloop_device *xlo, struct xloop_info64 __user *arg) { - struct xloop_info64 info64; - int err; - - if (!arg) - return -EINVAL; - err = xloop_get_status(xlo, &info64); - if (!err && copy_to_user(arg, &info64, sizeof(info64))) - err = -EFAULT; - - return err; -} - -static int xloop_set_capacity(struct xloop_device *xlo) -{ - loff_t size; - - if (unlikely(xlo->xlo_state != Xlo_bound)) - return -ENXIO; - - size = get_xloop_size(xlo, xlo->xlo_backing_file); - xloop_set_size(xlo, size); - - return 0; -} - -static int xloop_set_dio(struct xloop_device *xlo, unsigned long arg) -{ - int error = -ENXIO; - if (xlo->xlo_state != Xlo_bound) - goto out; - - __xloop_update_dio(xlo, !!arg); - if (xlo->use_dio == !!arg) - return 0; - error = -EINVAL; - out: - return error; -} - -static int xloop_set_block_size(struct xloop_device *xlo, unsigned long arg) -{ - int err = 0; - - if (xlo->xlo_state != Xlo_bound) - return -ENXIO; - - err = xloop_validate_block_size(arg); - if (err) - return err; - - if (xlo->xlo_queue->limits.logical_block_size == arg) - return 0; - - sync_blockdev(xlo->xlo_device); - invalidate_bdev(xlo->xlo_device); - - blk_mq_freeze_queue(xlo->xlo_queue); - - /* invalidate_bdev should have truncated all the pages */ - if (xlo->xlo_device->bd_inode->i_mapping->nrpages) { - err = -EAGAIN; - pr_warn("%s: xloop%d (%s) has still dirty pages (nrpages=%lu)\n", - __func__, xlo->xlo_number, xlo->xlo_file_name, - xlo->xlo_device->bd_inode->i_mapping->nrpages); - goto out_unfreeze; - } - - blk_queue_logical_block_size(xlo->xlo_queue, arg); - blk_queue_physical_block_size(xlo->xlo_queue, arg); - blk_queue_io_min(xlo->xlo_queue, arg); - xloop_update_dio(xlo); -out_unfreeze: - blk_mq_unfreeze_queue(xlo->xlo_queue); - - return err; -} - -static int xlo_simple_ioctl(struct xloop_device *xlo, unsigned int cmd, - unsigned long arg) -{ - int err; - - err = mutex_lock_killable(&xloop_ctl_mutex); - if (err) - return err; - switch (cmd) { - case XLOOP_SET_CAPACITY: - err = xloop_set_capacity(xlo); - break; - case XLOOP_SET_DIRECT_IO: - err = xloop_set_dio(xlo, arg); - break; - case XLOOP_SET_BLOCK_SIZE: - err = xloop_set_block_size(xlo, arg); - break; - default: - err = xlo->ioctl ? xlo->ioctl(xlo, cmd, arg) : -EINVAL; - } - mutex_unlock(&xloop_ctl_mutex); - return err; -} - -static int xlo_ioctl(struct block_device *bdev, fmode_t mode, - unsigned int cmd, unsigned long arg) -{ - struct xloop_device *xlo = bdev->bd_disk->private_data; - void __user *argp = (void __user *) arg; - int err; - - switch (cmd) { - case XLOOP_SET_FD: { - /* - * Legacy case - pass in a zeroed out struct xloop_config with - * only the file descriptor set , which corresponds with the - * default parameters we'd have used otherwise. - */ - struct xloop_config config; - - memset(&config, 0, sizeof(config)); - config.fd = arg; - - return xloop_configure(xlo, mode, bdev, &config); - } - case XLOOP_CONFIGURE: { - struct xloop_config config; - - if (copy_from_user(&config, argp, sizeof(config))) - return -EFAULT; - - return xloop_configure(xlo, mode, bdev, &config); - } - case XLOOP_CHANGE_FD: - return xloop_change_fd(xlo, bdev, arg); - case XLOOP_CLR_FD: - return xloop_clr_fd(xlo); - case XLOOP_SET_STATUS: - err = -EPERM; - if ((mode & FMODE_WRITE) || capable(CAP_SYS_ADMIN)) { - err = xloop_set_status_old(xlo, argp); - } - break; - case XLOOP_GET_STATUS: - return xloop_get_status_old(xlo, argp); - case XLOOP_SET_STATUS64: - err = -EPERM; - if ((mode & FMODE_WRITE) || capable(CAP_SYS_ADMIN)) { - err = xloop_set_status64(xlo, argp); - } - break; - case XLOOP_GET_STATUS64: - return xloop_get_status64(xlo, argp); - case XLOOP_SET_CAPACITY: - case XLOOP_SET_DIRECT_IO: - case XLOOP_SET_BLOCK_SIZE: - if (!(mode & FMODE_WRITE) && !capable(CAP_SYS_ADMIN)) - return -EPERM; - fallthrough; - default: - err = xlo_simple_ioctl(xlo, cmd, arg); - break; - } - - return err; -} - -#ifdef CONFIG_COMPAT -struct compat_xloop_info { - compat_int_t xlo_number; /* ioctl r/o */ - compat_dev_t xlo_device; /* ioctl r/o */ - compat_ulong_t xlo_inode; /* ioctl r/o */ - compat_dev_t xlo_rdevice; /* ioctl r/o */ - compat_int_t xlo_offset; - compat_int_t xlo_encrypt_type; - compat_int_t xlo_encrypt_key_size; /* ioctl w/o */ - compat_int_t xlo_flags; /* ioctl r/o */ - char xlo_name[XLO_NAME_SIZE]; - unsigned char xlo_encrypt_key[XLO_KEY_SIZE]; /* ioctl w/o */ - compat_ulong_t xlo_init[2]; - char reserved[4]; - compat_int_t xlo_file_fmt_type; -}; - -/* - * Transfer 32-bit compatibility structure in userspace to 64-bit xloop info - * - noinlined to reduce stack space usage in main part of driver - */ -static noinline int -xloop_info64_from_compat(const struct compat_xloop_info __user *arg, - struct xloop_info64 *info64) -{ - struct compat_xloop_info info; - - if (copy_from_user(&info, arg, sizeof(info))) - return -EFAULT; - - memset(info64, 0, sizeof(*info64)); - info64->xlo_number = info.xlo_number; - info64->xlo_device = info.xlo_device; - info64->xlo_inode = info.xlo_inode; - info64->xlo_rdevice = info.xlo_rdevice; - info64->xlo_offset = info.xlo_offset; - info64->xlo_sizelimit = 0; - info64->xlo_encrypt_type = info.xlo_encrypt_type; - info64->xlo_encrypt_key_size = info.xlo_encrypt_key_size; - info64->xlo_flags = info.xlo_flags; - info64->xlo_init[0] = info.xlo_init[0]; - info64->xlo_init[1] = info.xlo_init[1]; - info64->xlo_file_fmt_type = info.xlo_file_fmt_type; - if (info.xlo_encrypt_type == XLO_CRYPT_CRYPTOAPI) - memcpy(info64->xlo_crypt_name, info.xlo_name, XLO_NAME_SIZE); - else - memcpy(info64->xlo_file_name, info.xlo_name, XLO_NAME_SIZE); - memcpy(info64->xlo_encrypt_key, info.xlo_encrypt_key, XLO_KEY_SIZE); - return 0; -} - -/* - * Transfer 64-bit xloop info to 32-bit compatibility structure in userspace - * - noinlined to reduce stack space usage in main part of driver - */ -static noinline int -xloop_info64_to_compat(const struct xloop_info64 *info64, - struct compat_xloop_info __user *arg) -{ - struct compat_xloop_info info; - - memset(&info, 0, sizeof(info)); - info.xlo_number = info64->xlo_number; - info.xlo_device = info64->xlo_device; - info.xlo_inode = info64->xlo_inode; - info.xlo_rdevice = info64->xlo_rdevice; - info.xlo_offset = info64->xlo_offset; - info.xlo_encrypt_type = info64->xlo_encrypt_type; - info.xlo_encrypt_key_size = info64->xlo_encrypt_key_size; - info.xlo_flags = info64->xlo_flags; - info.xlo_init[0] = info64->xlo_init[0]; - info.xlo_init[1] = info64->xlo_init[1]; - if (info.xlo_encrypt_type == XLO_CRYPT_CRYPTOAPI) - memcpy(info.xlo_name, info64->xlo_crypt_name, XLO_NAME_SIZE); - else - memcpy(info.xlo_name, info64->xlo_file_name, XLO_NAME_SIZE); - memcpy(info.xlo_encrypt_key, info64->xlo_encrypt_key, XLO_KEY_SIZE); - - /* error in case values were truncated */ - if (info.xlo_device != info64->xlo_device || - info.xlo_rdevice != info64->xlo_rdevice || - info.xlo_inode != info64->xlo_inode || - info.xlo_offset != info64->xlo_offset || - info.xlo_init[0] != info64->xlo_init[0] || - info.xlo_init[1] != info64->xlo_init[1]) - return -EOVERFLOW; - - if (copy_to_user(arg, &info, sizeof(info))) - return -EFAULT; - return 0; -} - -static int -xloop_set_status_compat(struct xloop_device *xlo, - const struct compat_xloop_info __user *arg) -{ - struct xloop_info64 info64; - int ret; - - ret = xloop_info64_from_compat(arg, &info64); - if (ret < 0) - return ret; - return xloop_set_status(xlo, &info64); -} - -static int -xloop_get_status_compat(struct xloop_device *xlo, - struct compat_xloop_info __user *arg) -{ - struct xloop_info64 info64; - int err; - - if (!arg) - return -EINVAL; - err = xloop_get_status(xlo, &info64); - if (!err) - err = xloop_info64_to_compat(&info64, arg); - return err; -} - -static int xlo_compat_ioctl(struct block_device *bdev, fmode_t mode, - unsigned int cmd, unsigned long arg) -{ - struct xloop_device *xlo = bdev->bd_disk->private_data; - int err; - - switch(cmd) { - case XLOOP_SET_STATUS: - err = xloop_set_status_compat(xlo, - (const struct compat_xloop_info __user *)arg); - break; - case XLOOP_GET_STATUS: - err = xloop_get_status_compat(xlo, - (struct compat_xloop_info __user *)arg); - break; - case XLOOP_SET_CAPACITY: - case XLOOP_CLR_FD: - case XLOOP_GET_STATUS64: - case XLOOP_SET_STATUS64: - case XLOOP_CONFIGURE: - arg = (unsigned long) compat_ptr(arg); - fallthrough; - case XLOOP_SET_FD: - case XLOOP_CHANGE_FD: - case XLOOP_SET_BLOCK_SIZE: - case XLOOP_SET_DIRECT_IO: - err = xlo_ioctl(bdev, mode, cmd, arg); - break; - default: - err = -ENOIOCTLCMD; - break; - } - return err; -} -#endif - -static int xlo_open(struct block_device *bdev, fmode_t mode) -{ - struct xloop_device *xlo; - int err; - - err = mutex_lock_killable(&xloop_ctl_mutex); - if (err) - return err; - xlo = bdev->bd_disk->private_data; - if (!xlo) { - err = -ENXIO; - goto out; - } - - atomic_inc(&xlo->xlo_refcnt); -out: - mutex_unlock(&xloop_ctl_mutex); - return err; -} - -static void xlo_release(struct gendisk *disk, fmode_t mode) -{ - struct xloop_device *xlo; - - mutex_lock(&xloop_ctl_mutex); - xlo = disk->private_data; - if (atomic_dec_return(&xlo->xlo_refcnt)) - goto out_unlock; - - if (xlo->xlo_flags & XLO_FLAGS_AUTOCLEAR) { - if (xlo->xlo_state != Xlo_bound) - goto out_unlock; - xlo->xlo_state = Xlo_rundown; - mutex_unlock(&xloop_ctl_mutex); - /* - * In autoclear mode, stop the xloop thread - * and remove configuration after last close. - */ - __xloop_clr_fd(xlo, true); - return; - } else if (xlo->xlo_state == Xlo_bound) { - /* - * Otherwise keep thread (if running) and config, - * but flush possible ongoing bios in thread. - */ - blk_mq_freeze_queue(xlo->xlo_queue); - blk_mq_unfreeze_queue(xlo->xlo_queue); - } - -out_unlock: - mutex_unlock(&xloop_ctl_mutex); -} - -static const struct block_device_operations xlo_fops = { - .owner = THIS_MODULE, - .open = xlo_open, - .release = xlo_release, - .ioctl = xlo_ioctl, -#ifdef CONFIG_COMPAT - .compat_ioctl = xlo_compat_ioctl, -#endif -}; - -/* - * And now the modules code and kernel interface. - */ -static int max_xloop; -module_param(max_xloop, int, 0444); -MODULE_PARM_DESC(max_xloop, "Maximum number of xloop devices"); -module_param(max_part, int, 0444); -MODULE_PARM_DESC(max_part, "Maximum number of partitions per xloop device"); -MODULE_LICENSE("GPL"); -MODULE_ALIAS_BLOCKDEV_MAJOR(LOOP_MAJOR); - -int xloop_register_transfer(struct xloop_func_table *funcs) -{ - unsigned int n = funcs->number; - - if (n >= MAX_XLO_CRYPT || xfer_funcs[n]) - return -EINVAL; - xfer_funcs[n] = funcs; - return 0; -} - -static int unregister_transfer_cb(int id, void *ptr, void *data) -{ - struct xloop_device *xlo = ptr; - struct xloop_func_table *xfer = data; - - mutex_lock(&xloop_ctl_mutex); - if (xlo->xlo_encryption == xfer) - xloop_release_xfer(xlo); - mutex_unlock(&xloop_ctl_mutex); - return 0; -} - -int xloop_unregister_transfer(int number) -{ - unsigned int n = number; - struct xloop_func_table *xfer; - - if (n == 0 || n >= MAX_XLO_CRYPT || (xfer = xfer_funcs[n]) == NULL) - return -EINVAL; - - xfer_funcs[n] = NULL; - idr_for_each(&xloop_index_idr, &unregister_transfer_cb, xfer); - return 0; -} - -EXPORT_SYMBOL(xloop_register_transfer); -EXPORT_SYMBOL(xloop_unregister_transfer); - -static blk_status_t xloop_queue_rq(struct blk_mq_hw_ctx *hctx, - const struct blk_mq_queue_data *bd) -{ - struct request *rq = bd->rq; - struct xloop_cmd *cmd = blk_mq_rq_to_pdu(rq); - struct xloop_device *xlo = rq->q->queuedata; - - blk_mq_start_request(rq); - - if (xlo->xlo_state != Xlo_bound) - return BLK_STS_IOERR; - - switch (req_op(rq)) { - case REQ_OP_FLUSH: - case REQ_OP_DISCARD: - case REQ_OP_WRITE_ZEROES: - cmd->use_aio = false; - break; - default: - cmd->use_aio = xlo->use_dio; - break; - } - - /* always use the first bio's css */ -#ifdef CONFIG_BLK_CGROUP - if (cmd->use_aio && rq->bio && rq->bio->bi_blkg) { - cmd->css = &bio_blkcg(rq->bio)->css; - css_get(cmd->css); - } else -#endif - cmd->css = NULL; - kthread_queue_work(&xlo->worker, &cmd->work); - - return BLK_STS_OK; -} - -static void xloop_handle_cmd(struct xloop_cmd *cmd) -{ - struct request *rq = blk_mq_rq_from_pdu(cmd); - const bool write = op_is_write(req_op(rq)); - struct xloop_device *xlo = rq->q->queuedata; - int ret = 0; - - if (write && (xlo->xlo_flags & XLO_FLAGS_READ_ONLY)) { - ret = -EIO; - goto failed; - } - - ret = do_req_filebacked(xlo, rq); - failed: - /* complete non-aio request */ - if (!cmd->use_aio || ret) { - if (ret == -EOPNOTSUPP) - cmd->ret = ret; - else - cmd->ret = ret ? -EIO : 0; -#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 9, 0) - if (likely(!blk_should_fake_timeout(rq->q))) - blk_mq_complete_request(rq); -#else - blk_mq_complete_request(rq); -#endif - } -} - -static void xloop_queue_work(struct kthread_work *work) -{ - struct xloop_cmd *cmd = - container_of(work, struct xloop_cmd, work); - - xloop_handle_cmd(cmd); -} - -static int xloop_init_request(struct blk_mq_tag_set *set, struct request *rq, - unsigned int hctx_idx, unsigned int numa_node) -{ - struct xloop_cmd *cmd = blk_mq_rq_to_pdu(rq); - - kthread_init_work(&cmd->work, xloop_queue_work); - return 0; -} - -static const struct blk_mq_ops xloop_mq_ops = { - .queue_rq = xloop_queue_rq, - .init_request = xloop_init_request, - .complete = xlo_complete_rq, -}; - -static struct dentry *xloop_dbgfs_dir; - -static int xloop_add(struct xloop_device **l, int i) -{ - struct xloop_device *xlo; - struct gendisk *disk; - int err; - - err = -ENOMEM; - xlo = kzalloc(sizeof(*xlo), GFP_KERNEL); - if (!xlo) - goto out; - - xlo->xlo_state = Xlo_unbound; - - /* allocate id, if @id >= 0, we're requesting that specific id */ - if (i >= 0) { - err = idr_alloc(&xloop_index_idr, xlo, i, i + 1, GFP_KERNEL); - if (err == -ENOSPC) - err = -EEXIST; - } else { - err = idr_alloc(&xloop_index_idr, xlo, 0, 0, GFP_KERNEL); - } - if (err < 0) - goto out_free_dev; - i = err; - - err = -ENOMEM; - xlo->tag_set.ops = &xloop_mq_ops; - xlo->tag_set.nr_hw_queues = 1; - xlo->tag_set.queue_depth = 128; - xlo->tag_set.numa_node = NUMA_NO_NODE; - xlo->tag_set.cmd_size = sizeof(struct xloop_cmd); -#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 8, 0) - xlo->tag_set.flags = BLK_MQ_F_SHOULD_MERGE | BLK_MQ_F_STACKING; -#else - xlo->tag_set.flags = BLK_MQ_F_SHOULD_MERGE; -#endif - xlo->tag_set.driver_data = xlo; - - err = blk_mq_alloc_tag_set(&xlo->tag_set); - if (err) - goto out_free_idr; - - xlo->xlo_queue = blk_mq_init_queue(&xlo->tag_set); - if (IS_ERR(xlo->xlo_queue)) { - err = PTR_ERR(xlo->xlo_queue); - goto out_cleanup_tags; - } - xlo->xlo_queue->queuedata = xlo; - - blk_queue_max_hw_sectors(xlo->xlo_queue, BLK_DEF_MAX_SECTORS); - - /* - * By default, we do buffer IO, so it doesn't make sense to enable - * merge because the I/O submitted to backing file is handled page by - * page. For directio mode, merge does help to dispatch bigger request - * to underlayer disk. We will enable merge once directio is enabled. - */ - blk_queue_flag_set(QUEUE_FLAG_NOMERGES, xlo->xlo_queue); - - err = -ENOMEM; - xlo->xlo_fmt = xloop_file_fmt_alloc(); - if (!xlo->xlo_fmt) - goto out_free_queue; - - xloop_file_fmt_set_xlo(xlo->xlo_fmt, xlo); - - err = -ENOMEM; - disk = xlo->xlo_disk = alloc_disk(1 << part_shift); - if (!disk) - goto out_free_file_fmt; - - /* - * Disable partition scanning by default. The in-kernel partition - * scanning can be requested individually per-device during its - * setup. Userspace can always add and remove partitions from all - * devices. The needed partition minors are allocated from the - * extended minor space, the main xloop device numbers will continue - * to match the xloop minors, regardless of the number of partitions - * used. - * - * If max_part is given, partition scanning is globally enabled for - * all xloop devices. The minors for the main xloop devices will be - * multiples of max_part. - * - * Note: Global-for-all-devices, set-only-at-init, read-only module - * parameteters like 'max_xloop' and 'max_part' make things needlessly - * complicated, are too static, inflexible and may surprise - * userspace tools. Parameters like this in general should be avoided. - */ - if (!part_shift) - disk->flags |= GENHD_FL_NO_PART_SCAN; - disk->flags |= GENHD_FL_EXT_DEVT; - atomic_set(&xlo->xlo_refcnt, 0); - xlo->xlo_number = i; - spin_lock_init(&xlo->xlo_lock); - disk->major = LOOP_MAJOR; - disk->first_minor = i << part_shift; - disk->fops = &xlo_fops; - disk->private_data = xlo; - disk->queue = xlo->xlo_queue; - sprintf(disk->disk_name, "xloop%d", i); - add_disk(disk); - *l = xlo; - - /* initialize debugfs entries */ - /* create for each loop device a debugfs directory under 'loop' if - * the 'block' directory exists, otherwise create the loop directory in - * the root directory */ -#ifdef CONFIG_DEBUG_FS - xlo->xlo_dbgfs_dir = debugfs_create_dir(disk->disk_name, xloop_dbgfs_dir); - - if (IS_ERR_OR_NULL(xlo->xlo_dbgfs_dir)) { - err = -ENODEV; - xlo->xlo_dbgfs_dir = NULL; - goto out_free_file_fmt; - } -#endif - - return xlo->xlo_number; - -out_free_file_fmt: - xloop_file_fmt_free(xlo->xlo_fmt); -out_free_queue: - blk_cleanup_queue(xlo->xlo_queue); -out_cleanup_tags: - blk_mq_free_tag_set(&xlo->tag_set); -out_free_idr: - idr_remove(&xloop_index_idr, i); -out_free_dev: - kfree(xlo); -out: - return err; -} - -static void xloop_remove(struct xloop_device *xlo) -{ - xloop_file_fmt_free(xlo->xlo_fmt); - debugfs_remove(xlo->xlo_dbgfs_dir); - del_gendisk(xlo->xlo_disk); - blk_cleanup_queue(xlo->xlo_queue); - blk_mq_free_tag_set(&xlo->tag_set); - put_disk(xlo->xlo_disk); - kfree(xlo); -} - -static int find_free_cb(int id, void *ptr, void *data) -{ - struct xloop_device *xlo = ptr; - struct xloop_device **l = data; - - if (xlo->xlo_state == Xlo_unbound) { - *l = xlo; - return 1; - } - return 0; -} - -static int xloop_lookup(struct xloop_device **l, int i) -{ - struct xloop_device *xlo; - int ret = -ENODEV; - - if (i < 0) { - int err; - - err = idr_for_each(&xloop_index_idr, &find_free_cb, &xlo); - if (err == 1) { - *l = xlo; - ret = xlo->xlo_number; - } - goto out; - } - - /* lookup and return a specific i */ - xlo = idr_find(&xloop_index_idr, i); - if (xlo) { - *l = xlo; - ret = xlo->xlo_number; - } -out: - return ret; -} - -static struct kobject *xloop_probe(dev_t dev, int *part, void *data) -{ - struct xloop_device *xlo; - struct kobject *kobj; - int err; - - mutex_lock(&xloop_ctl_mutex); - err = xloop_lookup(&xlo, MINOR(dev) >> part_shift); - if (err < 0) - err = xloop_add(&xlo, MINOR(dev) >> part_shift); - if (err < 0) - kobj = NULL; - else - kobj = get_disk_and_module(xlo->xlo_disk); - mutex_unlock(&xloop_ctl_mutex); - - *part = 0; - return kobj; -} - -static long xloop_control_ioctl(struct file *file, unsigned int cmd, - unsigned long parm) -{ - struct xloop_device *xlo; - int ret; - - ret = mutex_lock_killable(&xloop_ctl_mutex); - if (ret) - return ret; - - ret = -ENOSYS; - switch (cmd) { - case XLOOP_CTL_ADD: - ret = xloop_lookup(&xlo, parm); - if (ret >= 0) { - ret = -EEXIST; - break; - } - ret = xloop_add(&xlo, parm); - break; - case XLOOP_CTL_REMOVE: - ret = xloop_lookup(&xlo, parm); - if (ret < 0) - break; - if (xlo->xlo_state != Xlo_unbound) { - ret = -EBUSY; - break; - } - if (atomic_read(&xlo->xlo_refcnt) > 0) { - ret = -EBUSY; - break; - } - xlo->xlo_disk->private_data = NULL; - idr_remove(&xloop_index_idr, xlo->xlo_number); - xloop_remove(xlo); - break; - case XLOOP_CTL_GET_FREE: - ret = xloop_lookup(&xlo, -1); - if (ret >= 0) - break; - ret = xloop_add(&xlo, -1); - } - mutex_unlock(&xloop_ctl_mutex); - - return ret; -} - -static const struct file_operations xloop_ctl_fops = { - .open = nonseekable_open, - .unlocked_ioctl = xloop_control_ioctl, - .compat_ioctl = xloop_control_ioctl, - .owner = THIS_MODULE, - .llseek = noop_llseek, -}; - -static struct miscdevice xloop_misc = { - .minor = LOOP_CTRL_MINOR, - .name = "xloop-control", - .fops = &xloop_ctl_fops, -}; - -MODULE_ALIAS_MISCDEV(LOOP_CTRL_MINOR); -MODULE_ALIAS("devname:xloop-control"); - -static int __init xloop_init(void) -{ - int i, nr; - unsigned long range; - struct xloop_device *xlo; - int err; - - part_shift = 0; - if (max_part > 0) { - part_shift = fls(max_part); - - /* - * Adjust max_part according to part_shift as it is exported - * to user space so that user can decide correct minor number - * if [s]he want to create more devices. - * - * Note that -1 is required because partition 0 is reserved - * for the whole disk. - */ - max_part = (1UL << part_shift) - 1; - } - - if ((1UL << part_shift) > DISK_MAX_PARTS) { - err = -EINVAL; - goto err_out; - } - - if (max_xloop > 1UL << (MINORBITS - part_shift)) { - err = -EINVAL; - goto err_out; - } - - /* - * If max_xloop is specified, create that many devices upfront. - * This also becomes a hard limit. If max_xloop is not specified, - * create CONFIG_BLK_DEV_XLOOP_MIN_COUNT xloop devices at module - * init time. xloop devices can be requested on-demand with the - * /dev/xloop-control interface, or be instantiated by accessing - * a 'dead' device node. - */ - if (max_xloop) { - nr = CONFIG_BLK_DEV_LOOP_MIN_COUNT; - range = 1UL << MINORBITS; - } - - err = misc_register(&xloop_misc); - if (err < 0) - goto err_out; - - - if (register_blkdev(LOOP_MAJOR, "xloop")) { - err = -EIO; - goto misc_out; - } - -#ifdef CONFIG_DEBUG_FS - xloop_dbgfs_dir = debugfs_create_dir("xloop", NULL); - if (IS_ERR_OR_NULL(xloop_dbgfs_dir)) { - err = -ENODEV; - goto misc_out; - } -#endif - - blk_register_region(MKDEV(LOOP_MAJOR, 0), range, - THIS_MODULE, xloop_probe, NULL, NULL); - - /* pre-create number of devices given by config or max_xloop */ - mutex_lock(&xloop_ctl_mutex); - for (i = 0; i < nr; i++) - xloop_add(&xlo, i); - mutex_unlock(&xloop_ctl_mutex); - - printk(KERN_INFO "xloop: module loaded\n"); - return 0; - -misc_out: - misc_deregister(&xloop_misc); -err_out: - return err; -} - -static int xloop_exit_cb(int id, void *ptr, void *data) -{ - struct xloop_device *xlo = ptr; - - xloop_remove(xlo); - return 0; -} - -static void __exit xloop_exit(void) -{ - unsigned long range; - - range = max_xloop ? max_xloop << part_shift : 1UL << MINORBITS; - - mutex_lock(&xloop_ctl_mutex); - - idr_for_each(&xloop_index_idr, &xloop_exit_cb, NULL); - idr_destroy(&xloop_index_idr); - - blk_unregister_region(MKDEV(LOOP_MAJOR, 0), range); - unregister_blkdev(LOOP_MAJOR, "xloop"); - -#ifdef CONFIG_DEBUG_FS - debugfs_remove(xloop_dbgfs_dir); -#endif - - misc_deregister(&xloop_misc); - - mutex_unlock(&xloop_ctl_mutex); -} - -module_init(xloop_init); -module_exit(xloop_exit); - -#ifndef MODULE -static int __init max_xloop_setup(char *str) -{ - max_xloop = simple_strtol(str, NULL, 0); - return 1; -} - -__setup("max_xloop=", max_xloop_setup); -#endif diff --git a/loop_main.h b/loop_main.h deleted file mode 100644 index 1b5851a..0000000 --- a/loop_main.h +++ /dev/null @@ -1,105 +0,0 @@ -/* - * loop_main.h - * - * Written by Theodore Ts'o, 3/29/93. - * - * Copyright 1993 by Theodore Ts'o. Redistribution of this file is - * permitted under the GNU General Public License. - */ -#ifndef _LINUX_XLOOP_H -#define _LINUX_XLOOP_H - -#include -#include -#include -#include -#include -#include -#include "uapi/linux/loop.h" -#ifdef CONFIG_DEBUG_FS -#include -#endif - -#include "loop_file_fmt.h" - -/* Possible states of device */ -enum { - Xlo_unbound, - Xlo_bound, - Xlo_rundown, -}; - -struct xloop_func_table; - -struct xloop_device { - int xlo_number; - atomic_t xlo_refcnt; - loff_t xlo_offset; - loff_t xlo_sizelimit; - int xlo_flags; - int (*transfer)(struct xloop_device *, int cmd, - struct page *raw_page, unsigned raw_off, - struct page *xloop_page, unsigned xloop_off, - int size, sector_t real_block); - char xlo_file_name[XLO_NAME_SIZE]; - char xlo_crypt_name[XLO_NAME_SIZE]; - char xlo_encrypt_key[XLO_KEY_SIZE]; - int xlo_encrypt_key_size; - struct xloop_func_table *xlo_encryption; - __u32 xlo_init[2]; - kuid_t xlo_key_owner; /* Who set the key */ - int (*ioctl)(struct xloop_device *, int cmd, - unsigned long arg); - - struct xloop_file_fmt *xlo_fmt; - - struct file * xlo_backing_file; - struct block_device *xlo_device; - void *key_data; - - gfp_t old_gfp_mask; - - spinlock_t xlo_lock; - int xlo_state; - struct kthread_worker worker; - struct task_struct *worker_task; - bool use_dio; - bool sysfs_inited; - - struct request_queue *xlo_queue; - struct blk_mq_tag_set tag_set; - struct gendisk *xlo_disk; - -#ifdef CONFIG_DEBUG_FS - struct dentry *xlo_dbgfs_dir; -#endif -}; - -struct xloop_cmd { - struct kthread_work work; - bool use_aio; /* use AIO interface to handle I/O */ - atomic_t ref; /* only for aio */ - long ret; - struct kiocb iocb; - struct bio_vec *bvec; - struct cgroup_subsys_state *css; -}; - -/* Support for loadable transfer modules */ -struct xloop_func_table { - int number; /* filter type */ - int (*transfer)(struct xloop_device *xlo, int cmd, - struct page *raw_page, unsigned raw_off, - struct page *xloop_page, unsigned xloop_off, - int size, sector_t real_block); - int (*init)(struct xloop_device *, const struct xloop_info64 *); - /* release is called from xloop_unregister_transfer or clr_fd */ - int (*release)(struct xloop_device *); - int (*ioctl)(struct xloop_device *, int cmd, unsigned long arg); - struct module *owner; -}; - -int xloop_register_transfer(struct xloop_func_table *funcs); -int xloop_unregister_transfer(int number); - -#endif diff --git a/uapi/linux/loop.h b/uapi/linux/loop.h deleted file mode 100644 index f93f6ad..0000000 --- a/uapi/linux/loop.h +++ /dev/null @@ -1,125 +0,0 @@ -/* SPDX-License-Identifier: GPL-1.0+ WITH Linux-syscall-note */ -/* - * include/linux/loop.h - * - * Written by Theodore Ts'o, 3/29/93. - * - * Copyright 1993 by Theodore Ts'o. Redistribution of this file is - * permitted under the GNU General Public License. - */ -#ifndef _UAPI_LINUX_XLOOP_H -#define _UAPI_LINUX_XLOOP_H - - -#define XLO_NAME_SIZE 64 -#define XLO_KEY_SIZE 32 - - -/* - * xloop flags - */ -enum { - XLO_FLAGS_READ_ONLY = 1, - XLO_FLAGS_AUTOCLEAR = 4, - XLO_FLAGS_PARTSCAN = 8, - XLO_FLAGS_DIRECT_IO = 16, -}; - -/* XLO_FLAGS that can be set using XLOOP_SET_STATUS(64) */ -#define XLOOP_SET_STATUS_SETTABLE_FLAGS (XLO_FLAGS_AUTOCLEAR | XLO_FLAGS_PARTSCAN) - -/* XLO_FLAGS that can be cleared using XLOOP_SET_STATUS(64) */ -#define XLOOP_SET_STATUS_CLEARABLE_FLAGS (XLO_FLAGS_AUTOCLEAR) - -/* XLO_FLAGS that can be set using XLOOP_CONFIGURE */ -#define XLOOP_CONFIGURE_SETTABLE_FLAGS (XLO_FLAGS_READ_ONLY | XLO_FLAGS_AUTOCLEAR \ - | XLO_FLAGS_PARTSCAN | XLO_FLAGS_DIRECT_IO) - -#include /* for __kernel_old_dev_t */ -#include /* for __u64 */ - -/* Backwards compatibility version */ -struct xloop_info { - int xlo_number; /* ioctl r/o */ - __kernel_old_dev_t xlo_device; /* ioctl r/o */ - unsigned long xlo_inode; /* ioctl r/o */ - __kernel_old_dev_t xlo_rdevice; /* ioctl r/o */ - int xlo_offset; - int xlo_encrypt_type; - int xlo_encrypt_key_size; /* ioctl w/o */ - int xlo_flags; - char xlo_name[XLO_NAME_SIZE]; - unsigned char xlo_encrypt_key[XLO_KEY_SIZE]; /* ioctl w/o */ - unsigned long xlo_init[2]; - char reserved[4]; - int xlo_file_fmt_type; -}; - -struct xloop_info64 { - __u64 xlo_device; /* ioctl r/o */ - __u64 xlo_inode; /* ioctl r/o */ - __u64 xlo_rdevice; /* ioctl r/o */ - __u64 xlo_offset; - __u64 xlo_sizelimit; /* bytes, 0 == max available */ - __u32 xlo_number; /* ioctl r/o */ - __u32 xlo_encrypt_type; - __u32 xlo_encrypt_key_size; /* ioctl w/o */ - __u32 xlo_flags; - __u8 xlo_file_name[XLO_NAME_SIZE]; - __u8 xlo_crypt_name[XLO_NAME_SIZE]; - __u8 xlo_encrypt_key[XLO_KEY_SIZE]; /* ioctl w/o */ - __u64 xlo_init[2]; - __u32 xlo_file_fmt_type; -}; - -/** - * struct xloop_config - Complete configuration for a xloop device. - * @fd: fd of the file to be used as a backing file for the xloop device. - * @block_size: block size to use; ignored if 0. - * @info: struct xloop_info64 to configure the xloop device with. - * - * This structure is used with the XLOOP_CONFIGURE ioctl, and can be used to - * atomically setup and configure all xloop device parameters at once. - */ -struct xloop_config { - __u32 fd; - __u32 block_size; - struct xloop_info64 info; - __u64 __reserved[8]; -}; - -/* - * xloop filter types - */ -#define XLO_CRYPT_NONE 0 -#define XLO_CRYPT_XOR 1 -#define XLO_CRYPT_DES 2 -#define XLO_CRYPT_FISH2 3 /* Twofish encryption */ -#define XLO_CRYPT_BLOW 4 -#define XLO_CRYPT_CAST128 5 -#define XLO_CRYPT_IDEA 6 -#define XLO_CRYPT_DUMMY 9 -#define XLO_CRYPT_SKIPJACK 10 -#define XLO_CRYPT_CRYPTOAPI 18 -#define MAX_XLO_CRYPT 20 - -/* - * IOCTL commands --- we will commandeer 0x4C ('L') - */ -#define XLOOP_SET_FD 0x4C00 -#define XLOOP_CLR_FD 0x4C01 -#define XLOOP_SET_STATUS 0x4C02 -#define XLOOP_GET_STATUS 0x4C03 -#define XLOOP_SET_STATUS64 0x4C04 -#define XLOOP_GET_STATUS64 0x4C05 -#define XLOOP_CHANGE_FD 0x4C06 -#define XLOOP_SET_CAPACITY 0x4C07 -#define XLOOP_SET_DIRECT_IO 0x4C08 -#define XLOOP_SET_BLOCK_SIZE 0x4C09 -#define XLOOP_CONFIGURE 0x4C0A - -/* /dev/xloop-control interface */ -#define XLOOP_CTL_ADD 0x4C80 -#define XLOOP_CTL_REMOVE 0x4C81 -#define XLOOP_CTL_GET_FREE 0x4C82 -#endif /* _UAPI_LINUX_XLOOP_H */ diff --git a/utils/CMakeLists.txt b/utils/CMakeLists.txt new file mode 100644 index 0000000..d1e359d --- /dev/null +++ b/utils/CMakeLists.txt @@ -0,0 +1,15 @@ +cmake_minimum_required(VERSION 3.10) + +# set the project name +project(xloop-utils) + +# include global headers +include_directories(${CMAKE_CURRENT_SOURCE_DIR}) +include_directories(${CMAKE_CURRENT_SOURCE_DIR}/include) + +# add include of config.h to each source file +add_compile_options(-include ${CMAKE_CURRENT_SOURCE_DIR}/config.h) + +add_subdirectory(lib) +add_subdirectory(libsmartcols) +add_subdirectory(sys-utils) diff --git a/utils/bash-completion/losetup b/utils/bash-completion/losetup new file mode 100644 index 0000000..e085abe --- /dev/null +++ b/utils/bash-completion/losetup @@ -0,0 +1,85 @@ +_losetup_module() +{ + local cur prev OPTS ARG + COMPREPLY=() + cur="${COMP_WORDS[COMP_CWORD]}" + prev="${COMP_WORDS[COMP_CWORD-1]}" + case $prev in + '-d'|'--detach') + ARG="$($1 --output NAME | awk '{if (1 < NR) {print}}')" + COMPREPLY=( $(compgen -W "$ARG" -- $cur) ) + return 0 + ;; + '-j'|'--associated') + ARG="$($1 --output BACK-FILE | awk '{if (1 < NR) {print}}')" + COMPREPLY=( $(compgen -W "$ARG" -- $cur) ) + return 0 + ;; + '-c'|'--set-capacity') + ARG="$(for I in /dev/loop[0-9]*; do if [ -e $I ]; then echo $I; fi; done)" + COMPREPLY=( $(compgen -W "$ARG" -- $cur) ) + return 0 + ;; + '-o'|'--offset'|'--sizelimit') + COMPREPLY=( $(compgen -W "number" -- $cur) ) + return 0 + ;; + '-t'|'--type') + ARG="RAW QCOW VDI VMDK" + COMPREPLY=( $(compgen -W "$ARG" -- $cur) ) + return 0 + ;; + '-O'|'--output') + local prefix realcur OUTPUT_ALL OUTPUT + realcur="${cur##*,}" + prefix="${cur%$realcur}" + OUTPUT_ALL="NAME AUTOCLEAR BACK-FILE BACK-INO + BACK-MAJ:MIN FILE-FORMAT MAJ:MIN OFFSET PARTSCAN RO + SIZELIMIT DIO" + for WORD in $OUTPUT_ALL; do + if ! [[ $prefix == *"$WORD"* ]]; then + OUTPUT="$WORD ${OUTPUT:-""}" + fi + done + compopt -o nospace + COMPREPLY=( $(compgen -P "$prefix" -W "$OUTPUT" -S ',' -- $realcur) ) + return 0 + ;; + '-h'|'--help'|'-V'|'--version') + return 0 + ;; + esac + case $cur in + -*) + OPTS="--all + --detach + --detach-all + --find + --set-capacity + --associated + --nooverlap + --offset + --sizelimit + --partscan + --read-only + --show + --type + --verbose + --json + --list + --noheadings + --output + --output-all + --raw + --help + --version" + COMPREPLY=( $(compgen -W "${OPTS[*]}" -- $cur) ) + return 0 + ;; + esac + local IFS=$'\n' + compopt -o filenames + COMPREPLY=( $(compgen -f -- $cur) ) + return 0 +} +complete -F _losetup_module losetup diff --git a/utils/config.h b/utils/config.h new file mode 100644 index 0000000..f74c162 --- /dev/null +++ b/utils/config.h @@ -0,0 +1,897 @@ +/* config.h. Generated from config.h.in by configure. */ +/* config.h.in. Generated from configure.ac by autoheader. */ + +/* Define if building universal (internal helper macro) */ +/* #undef AC_APPLE_UNIVERSAL_BUILD */ + +/* Enable agetty --reload feature */ +#define AGETTY_RELOAD 1 + +/* Should chfn and chsh require the user to enter the password? */ +#define CHFN_CHSH_PASSWORD 1 + +/* Path to hwclock adjtime file */ +#define CONFIG_ADJTIME_PATH "/etc/adjtime" + +/* Define if cryptsetup is to be loaded via dlopen */ +/* #undef CRYPTSETUP_VIA_DLOPEN */ + +/* Define to 1 if translation of program messages to the user's native + language is requested. */ +#define ENABLE_NLS 1 + +/* search path for fs helpers */ +#define FS_SEARCH_PATH "/sbin:/sbin/fs.d:/sbin/fs" + +/* Define to 1 if you have the header file. */ +/* #undef HAVE_ASM_IO_H */ + +/* Define if btrfs stuff is available */ +#define HAVE_BTRFS_SUPPORT 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_BYTESWAP_H 1 + +/* Define to 1 if you have the Mac OS X function CFLocaleCopyCurrent in the + CoreFoundation framework. */ +/* #undef HAVE_CFLOCALECOPYCURRENT */ + +/* Define to 1 if you have the Mac OS X function CFPreferencesCopyAppValue in + the CoreFoundation framework. */ +/* #undef HAVE_CFPREFERENCESCOPYAPPVALUE */ + +/* Define to 1 if you have the `clearenv' function. */ +#define HAVE_CLEARENV 1 + +/* Define to 1 if you have the `clock_gettime' function. */ +#define HAVE_CLOCK_GETTIME 1 + +/* Define to 1 if the system has the type `cpu_set_t'. */ +#define HAVE_CPU_SET_T 1 + +/* Define if cryptsetup is available */ +/* #undef HAVE_CRYPTSETUP */ + +/* Define if crypt_activate_by_signed_key exist in -lcryptsetup */ +/* #undef HAVE_CRYPT_ACTIVATE_BY_SIGNED_KEY */ + +/* Define to 1 if you have the header file. */ +#define HAVE_CRYPT_H 1 + +/* Define if the GNU dcgettext() function is already present or preinstalled. + */ +#define HAVE_DCGETTEXT 1 + +/* Define to 1 if you have the declaration of `BLK_ZONE_REP_CAPACITY', and to + 0 if you don't. */ +#define HAVE_DECL_BLK_ZONE_REP_CAPACITY 0 + +/* Define to 1 if you have the declaration of `CPU_ALLOC', and to 0 if you + don't. */ +#define HAVE_DECL_CPU_ALLOC 1 + +/* Define to 1 if you have the declaration of `dirfd', and to 0 if you don't. + */ +/* #undef HAVE_DECL_DIRFD */ + +/* Define to 1 if you have the declaration of `tzname', and to 0 if you don't. + */ +/* #undef HAVE_DECL_TZNAME */ + +/* Define to 1 if you have the declaration of `_NL_TIME_WEEK_1STDAY', and to 0 + if you don't. */ +#define HAVE_DECL__NL_TIME_WEEK_1STDAY 1 + +/* Define to 1 if you have the `dirfd' function. */ +#define HAVE_DIRFD 1 + +/* Define to 1 if `dd_fd' is a member of `DIR'. */ +/* #undef HAVE_DIR_DD_FD */ + +/* Define to 1 if you have the header file. */ +#define HAVE_DLFCN_H 1 + +/* Define to 1 if you have the `eaccess' function. */ +#define HAVE_EACCESS 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_ENDIAN_H 1 + +/* Define to 1 if have **environ prototype */ +#define HAVE_ENVIRON_DECL 1 + +/* Define to 1 if you have the `err' function. */ +#define HAVE_ERR 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_ERRNO_H 1 + +/* Define to 1 if you have the `errx' function. */ +#define HAVE_ERRX 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_ERR_H 1 + +/* Define to 1 if you have the `explicit_bzero' function. */ +#define HAVE_EXPLICIT_BZERO 1 + +/* Have valid fallocate() function */ +#define HAVE_FALLOCATE 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_FCNTL_H 1 + +/* Define to 1 if you have the `fpurge' function. */ +/* #undef HAVE_FPURGE */ + +/* Define to 1 if fseeko (and presumably ftello) exists and is declared. */ +#define HAVE_FSEEKO 1 + +/* Define to 1 if you have the `fstatat' function. */ +#define HAVE_FSTATAT 1 + +/* Define to 1 if you have the `fsync' function. */ +#define HAVE_FSYNC 1 + +/* Define to 1 if you have the `futimens' function. */ +#define HAVE_FUTIMENS 1 + +/* Define to 1 if you have the `getdomainname' function. */ +#define HAVE_GETDOMAINNAME 1 + +/* Define to 1 if you have the `getdtablesize' function. */ +#define HAVE_GETDTABLESIZE 1 + +/* Define to 1 if you have the `getexecname' function. */ +/* #undef HAVE_GETEXECNAME */ + +/* Define to 1 if you have the `getmntinfo' function. */ +/* #undef HAVE_GETMNTINFO */ + +/* Define to 1 if you have the header file. */ +#define HAVE_GETOPT_H 1 + +/* Define to 1 if you have the `getrandom' function. */ +#define HAVE_GETRANDOM 1 + +/* Define to 1 if you have the `getrlimit' function. */ +#define HAVE_GETRLIMIT 1 + +/* Define to 1 if you have the `getsgnam' function. */ +#define HAVE_GETSGNAM 1 + +/* Define if the GNU gettext() function is already present or preinstalled. */ +#define HAVE_GETTEXT 1 + +/* Define to 1 if you have the `getusershell' function. */ +#define HAVE_GETUSERSHELL 1 + +/* Define if you have the iconv() function and it works. */ +/* #undef HAVE_ICONV */ + +/* Define to 1 if you have the `inotify_init' function. */ +#define HAVE_INOTIFY_INIT 1 + +/* Define to 1 if you have the `inotify_init1' function. */ +#define HAVE_INOTIFY_INIT1 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_INTTYPES_H 1 + +/* Define to 1 if you have the `ioperm' function. */ +#define HAVE_IOPERM 1 + +/* Define to 1 if you have the `iopl' function. */ +#define HAVE_IOPL 1 + +/* Define to 1 if you have the `isnan' function. */ +#define HAVE_ISNAN 1 + +/* Define to 1 if you have the `jrand48' function. */ +#define HAVE_JRAND48 1 + +/* Define if langinfo.h defines ALTMON_x constants */ +#define HAVE_LANGINFO_ALTMON 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_LANGINFO_H 1 + +/* Define if langinfo.h defines _NL_ABALTMON_x constants */ +#define HAVE_LANGINFO_NL_ABALTMON 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_LASTLOG_H 1 + +/* Define to 1 if you have the `lchown' function. */ +#define HAVE_LCHOWN 1 + +/* Define to 1 if you have the `audit' library (-laudit). */ +/* #undef HAVE_LIBAUDIT */ + +/* Define to 1 if you have the -lblkid. */ +#define HAVE_LIBBLKID 1 + +/* Define to 1 if you have the `cap-ng' library (-lcap-ng). */ +#define HAVE_LIBCAP_NG 1 + +/* Do we need -lcrypt? */ +#define HAVE_LIBCRYPT 1 + +/* Define if libeconf is available */ +/* #undef HAVE_LIBECONF */ + +/* Define if libmount available. */ +#define HAVE_LIBMOUNT 1 + +/* Define if ncurses library available */ +/* #undef HAVE_LIBNCURSES */ + +/* Define if ncursesw library available */ +#define HAVE_LIBNCURSESW 1 + +/* Define to 1 if you have the `readline' library (-lreadline). */ +#define HAVE_LIBREADLINE 1 + +/* Define if librtas exists */ +/* #undef HAVE_LIBRTAS */ + +/* Define if SELinux is available */ +/* #undef HAVE_LIBSELINUX */ + +/* Define if libsystemd is available */ +#define HAVE_LIBSYSTEMD 1 + +/* Define if libtinfo or libtinfow available. */ +#define HAVE_LIBTINFO 1 + +/* Define to 1 if you have the `udev' library (-ludev). */ +#define HAVE_LIBUDEV 1 + +/* Define if libuser is available */ +/* #undef HAVE_LIBUSER */ + +/* Define to 1 if you have the `utempter' library (-lutempter). */ +/* #undef HAVE_LIBUTEMPTER */ + +/* Define to 1 if you have the `util' library (-lutil). */ +#define HAVE_LIBUTIL 1 + +/* Define to 1 if you have the header file. */ +/* #undef HAVE_LIBUTIL_H */ + +/* Define to 1 if you have the -luuid. */ +#define HAVE_LIBUUID 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_LINUX_BLKPG_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_LINUX_BLKZONED_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_LINUX_BTRFS_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_LINUX_CAPABILITY_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_LINUX_CDROM_H 1 + +/* Define to 1 if you have the header file. */ +/* #undef HAVE_LINUX_COMPILER_H */ + +/* Define to 1 if you have the header file. */ +#define HAVE_LINUX_FALLOC_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_LINUX_FD_H 1 + +/* Define to 1 if you have the header file. */ +/* #undef HAVE_LINUX_FS_H */ + +/* Define to 1 if you have the header file. */ +#define HAVE_LINUX_GSMMUX_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_LINUX_MAJOR_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_LINUX_NET_NAMESPACE_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_LINUX_RAW_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_LINUX_SECUREBITS_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_LINUX_TIOCL_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_LINUX_VERSION_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_LINUX_WATCHDOG_H 1 + +/* Define to 1 if you have the `llseek' function. */ +/* #undef HAVE_LLSEEK */ + +/* Define to 1 if you have the header file. */ +#define HAVE_LOCALE_H 1 + +/* Define to 1 if the system has the type `loff_t'. */ +#define HAVE_LOFF_T 1 + +/* Define to 1 if you have the libmagic present. */ +#define HAVE_MAGIC 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_MEMORY_H 1 + +/* Define to 1 if you have the `mempcpy' function. */ +#define HAVE_MEMPCPY 1 + +/* Define to 1 if you have the `mkostemp' function. */ +#define HAVE_MKOSTEMP 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_MNTENT_H 1 + +/* Define to 1 if you have the `nanosleep' function. */ +#define HAVE_NANOSLEEP 1 + +/* Define to 1 if you have the header file. */ +/* #undef HAVE_NCURSESW_NCURSES_H */ + +/* Define to 1 if you have the header file. */ +/* #undef HAVE_NCURSESW_TERM_H */ + +/* Define to 1 if you have the header file. */ +#define HAVE_NCURSES_H 1 + +/* Define to 1 if you have the header file. */ +/* #undef HAVE_NCURSES_NCURSES_H */ + +/* Define to 1 if you have the header file. */ +/* #undef HAVE_NCURSES_TERM_H */ + +/* Define to 1 if you have the header file. */ +#define HAVE_NETINET_IN_H 1 + +/* Define to 1 if you have the header file. */ +/* #undef HAVE_NET_IF_DL_H */ + +/* Define to 1 if you have the header file. */ +#define HAVE_NET_IF_H 1 + +/* Define to 1 if you have the `ntp_gettime' function. */ +#define HAVE_NTP_GETTIME 1 + +/* Define to 1 if you have the `openat' function. */ +#define HAVE_OPENAT 1 + +/* Define to 1 if you have the `open_memstream' function. */ +#define HAVE_OPEN_MEMSTREAM 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_PATHS_H 1 + +/* Define if libpcre2 is available */ +#define HAVE_PCRE 1 + +/* Define to 1 if you have the `personality' function. */ +#define HAVE_PERSONALITY 1 + +/* Define to 1 if you have the `pidfd_open' function. */ +/* #undef HAVE_PIDFD_OPEN */ + +/* Define to 1 if you have the `pidfd_send_signal' function. */ +/* #undef HAVE_PIDFD_SEND_SIGNAL */ + +/* Define to 1 if you have the `posix_fadvise' function. */ +#define HAVE_POSIX_FADVISE 1 + +/* Have valid posix_fallocate() function */ +#define HAVE_POSIX_FALLOCATE 1 + +/* Define to 1 if you have the `prctl' function. */ +#define HAVE_PRCTL 1 + +/* Define to 1 if you have the `prlimit' function. */ +#define HAVE_PRLIMIT 1 + +/* Define if program_invocation_short_name is defined */ +#define HAVE_PROGRAM_INVOCATION_SHORT_NAME 1 + +/* have PTY support */ +#define HAVE_PTY 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_PTY_H 1 + +/* Define to 1 if you have the `qsort_r' function. */ +#define HAVE_QSORT_R 1 + +/* Define to 1 if you have the `reboot' function. */ +#define HAVE_REBOOT 1 + +/* Define if curses library has the resizeterm(). */ +#define HAVE_RESIZETERM 1 + +/* Define to 1 if you have the `rpmatch' function. */ +#define HAVE_RPMATCH 1 + +/* Define if struct sockaddr contains sa_len */ +/* #undef HAVE_SA_LEN */ + +/* Define to 1 if you have the `scandirat' function. */ +#define HAVE_SCANDIRAT 1 + +/* Define to 1 if you have the `sched_setattr' function. */ +/* #undef HAVE_SCHED_SETATTR */ + +/* Define to 1 if you have the `sched_setscheduler' function. */ +#define HAVE_SCHED_SETSCHEDULER 1 + +/* Define to 1 if you have the `secure_getenv' function. */ +#define HAVE_SECURE_GETENV 1 + +/* Define to 1 if you have the `security_get_initial_context' function. */ +/* #undef HAVE_SECURITY_GET_INITIAL_CONTEXT */ + +/* Define to 1 if you have the header file. */ +/* #undef HAVE_SECURITY_OPENPAM_H */ + +/* Define to 1 if you have the header file. */ +#define HAVE_SECURITY_PAM_APPL_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_SECURITY_PAM_MISC_H 1 + +/* Define to 1 if you have the `setitimer' function. */ +/* #undef HAVE_SETITIMER */ + +/* Define to 1 if you have the `setns' function. */ +#define HAVE_SETNS 1 + +/* Define to 1 if you have the `setprogname' function. */ +/* #undef HAVE_SETPROGNAME */ + +/* Define to 1 if you have the `setresgid' function. */ +#define HAVE_SETRESGID 1 + +/* Define to 1 if you have the `setresuid' function. */ +#define HAVE_SETRESUID 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_SHADOW_H 1 + +/* Define to 1 if the system has the type `sighandler_t'. */ +#define HAVE_SIGHANDLER_T 1 + +/* Define to 1 if you have the `sigqueue' function. */ +#define HAVE_SIGQUEUE 1 + +/* Define to 1 if you have the header file. */ +/* #undef HAVE_SLANG_H */ + +/* Define to 1 if you have the header file. */ +/* #undef HAVE_SLANG_SLANG_H */ + +/* Define to 1 if you have the header file. */ +/* #undef HAVE_SLANG_SLCURSES_H */ + +/* Define to 1 if you have the header file. */ +/* #undef HAVE_SLCURSES_H */ + +/* Add SMACK support */ +/* #undef HAVE_SMACK */ + +/* Define to 1 if you have the `srandom' function. */ +#define HAVE_SRANDOM 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_STDINT_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_STDIO_EXT_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_STDLIB_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_STRINGS_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_STRING_H 1 + +/* Define to 1 if you have the `strnchr' function. */ +/* #undef HAVE_STRNCHR */ + +/* Define to 1 if you have the `strndup' function. */ +#define HAVE_STRNDUP 1 + +/* Define to 1 if you have the `strnlen' function. */ +#define HAVE_STRNLEN 1 + +/* Define to 1 if have strsignal function prototype */ +#define HAVE_STRSIGNAL_DECL 1 + +/* Define to 1 if `st_mtim.tv_nsec' is a member of `struct stat'. */ +#define HAVE_STRUCT_STAT_ST_MTIM_TV_NSEC 1 + +/* Define to 1 if `c_line' is a member of `struct termios'. */ +#define HAVE_STRUCT_TERMIOS_C_LINE 1 + +/* Define to 1 if `tm_zone' is a member of `struct tm'. */ +#define HAVE_STRUCT_TM_TM_ZONE 1 + +/* Define to 1 if you have the `swapoff' function. */ +#define HAVE_SWAPOFF 1 + +/* Define to 1 if you have the `swapon' function. */ +#define HAVE_SWAPON 1 + +/* Define to 1 if you have the `sysconf' function. */ +#define HAVE_SYSCONF 1 + +/* Define to 1 if you have the `sysinfo' function. */ +#define HAVE_SYSINFO 1 + +/* Define to 1 if you have the header file. */ +/* #undef HAVE_SYS_DISKLABEL_H */ + +/* Define to 1 if you have the header file. */ +/* #undef HAVE_SYS_DISK_H */ + +/* Define to 1 if you have the header file. */ +/* #undef HAVE_SYS_ENDIAN_H */ + +/* Define to 1 if you have the header file. */ +#define HAVE_SYS_FILE_H 1 + +/* Define to 1 if you have the header file. */ +/* #undef HAVE_SYS_IOCCOM_H */ + +/* Define to 1 if you have the header file. */ +#define HAVE_SYS_IOCTL_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_SYS_IO_H 1 + +/* Define to 1 if you have the header file. */ +/* #undef HAVE_SYS_MKDEV_H */ + +/* Define to 1 if you have the header file. */ +#define HAVE_SYS_MOUNT_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_SYS_PARAM_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_SYS_PRCTL_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_SYS_RESOURCE_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_SYS_SIGNALFD_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_SYS_SOCKET_H 1 + +/* Define to 1 if you have the header file. */ +/* #undef HAVE_SYS_SOCKIO_H */ + +/* Define to 1 if you have the header file. */ +#define HAVE_SYS_STAT_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_SYS_SWAP_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_SYS_SYSCALL_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_SYS_SYSMACROS_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_SYS_TIMEX_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_SYS_TIME_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_SYS_TTYDEFAULTS_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_SYS_TYPES_H 1 + +/* Define to 1 if you have the header file. */ +/* #undef HAVE_SYS_UCRED_H */ + +/* Define to 1 if you have the header file. */ +#define HAVE_SYS_UN_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_TERM_H 1 + +/* Define to 1 if you have the `timegm' function. */ +#define HAVE_TIMEGM 1 + +/* Define if timer_create exist in -lrt -lpthread */ +#define HAVE_TIMER_CREATE 1 + +/* Define to 1 if the target supports thread-local storage. */ +#define HAVE_TLS 1 + +/* Does struct tm have a field tm_gmtoff? */ +#define HAVE_TM_GMTOFF 1 + +/* Define to 1 if your `struct tm' has `tm_zone'. Deprecated, use + `HAVE_STRUCT_TM_TM_ZONE' instead. */ +#define HAVE_TM_ZONE 1 + +/* Define to 1 if you don't have `tm_zone' but do have the external array + `tzname'. */ +/* #undef HAVE_TZNAME */ + +/* Define to 1 if the system has the type `union semun'. */ +/* #undef HAVE_UNION_SEMUN */ + +/* Define to 1 if you have the header file. */ +#define HAVE_UNISTD_H 1 + +/* Define to 1 if you have the `unlinkat' function. */ +#define HAVE_UNLINKAT 1 + +/* Define to 1 if you have the `unshare' function. */ +#define HAVE_UNSHARE 1 + +/* Define to 1 if you have the `updwtmpx' function. */ +#define HAVE_UPDWTMPX 1 + +/* Define if curses library has the use_default_colors(). */ +#define HAVE_USE_DEFAULT_COLORS 1 + +/* Define to 1 if you have the `usleep' function. */ +#define HAVE_USLEEP 1 + +/* Define to 1 if you have the `utimensat' function. */ +#define HAVE_UTIMENSAT 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_UTMPX_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_UTMP_H 1 + +/* Define to 1 if you want to use uuid daemon. */ +#define HAVE_UUIDD 1 + +/* Define to 1 if you have the `vwarnx' function. */ +#define HAVE_VWARNX 1 + +/* Define to 1 if you have the `warn' function. */ +#define HAVE_WARN 1 + +/* Define to 1 if you have the `warnx' function. */ +#define HAVE_WARNX 1 + +/* Do we have wide character support? */ +#define HAVE_WIDECHAR 1 + +/* Define to 1 if you have the `__fpending' function. */ +#define HAVE___FPENDING 1 + +/* Define to 1 if you have the `__fpurge' function. */ +#define HAVE___FPURGE 1 + +/* Define if __progname is defined */ +#define HAVE___PROGNAME 1 + +/* Define to 1 if you have the `__secure_getenv' function. */ +/* #undef HAVE___SECURE_GETENV */ + +/* libblkid date string */ +#define LIBBLKID_DATE "23-Jul-2020" + +/* libblkid version string */ +#define LIBBLKID_VERSION "2.36.97" + +/* libfdisk version string */ +#define LIBFDISK_VERSION "2.36.97" + +/* libmount version string */ +#define LIBMOUNT_VERSION "2.36.97" + +/* libsmartcols version string */ +#define LIBSMARTCOLS_VERSION "2.36.97" + +/* Should login chown /dev/vcsN? */ +/* #undef LOGIN_CHOWN_VCS */ + +/* Should login stat() the mailbox? */ +/* #undef LOGIN_STAT_MAIL */ + +/* Define to the sub-directory where libtool stores uninstalled libraries. */ +#define LT_OBJDIR ".libs/" + +/* "Multi-arch triplet for whereis library search path" */ +/* #undef MULTIARCHTRIPLET */ + +/* Define to 1 if assertions should be disabled. */ +/* #undef NDEBUG */ + +/* Should chsh allow only shells in /etc/shells? */ +#define ONLY_LISTED_SHELLS 1 + +/* Name of package */ +#define PACKAGE "util-linux" + +/* Define to the address where bug reports for this package should be sent. */ +#define PACKAGE_BUGREPORT "kzak@redhat.com" + +/* Define to the full name of this package. */ +#define PACKAGE_NAME "util-linux" + +/* Define to the full name and version of this package. */ +#define PACKAGE_STRING "util-linux 2.36.97-87ac5-dirty" + +/* Define to the one symbol short name of this package. */ +#define PACKAGE_TARNAME "util-linux" + +/* Define to the home page for this package. */ +#define PACKAGE_URL "http://www.kernel.org/pub/linux/utils/util-linux/" + +/* Define to the version of this package. */ +#define PACKAGE_VERSION "2.36.97-87ac5" + +/* Should pg ring the bell on invalid keys? */ +#define PG_BELL 1 + +/* Define to 1 if you have the ANSI C header files. */ +#define STDC_HEADERS 1 + +/* Fallback syscall number for fallocate */ +/* #undef SYS_fallocate */ + +/* Fallback syscall number for ioprio_get */ +/* #undef SYS_ioprio_get */ + +/* Fallback syscall number for ioprio_set */ +/* #undef SYS_ioprio_set */ + +/* Fallback syscall number for pidfd_open */ +/* #undef SYS_pidfd_open */ + +/* Fallback syscall number for pidfd_send_signal */ +/* #undef SYS_pidfd_send_signal */ + +/* Fallback syscall number for pivot_root */ +/* #undef SYS_pivot_root */ + +/* Fallback syscall number for prlimit64 */ +/* #undef SYS_prlimit64 */ + +/* Fallback syscall number for sched_getaffinity */ +/* #undef SYS_sched_getaffinity */ + +/* Fallback syscall number for sched_setattr */ +/* #undef SYS_sched_setattr */ + +/* Fallback syscall number for setns */ +/* #undef SYS_setns */ + +/* Fallback syscall number for swapoff */ +/* #undef SYS_swapoff */ + +/* Fallback syscall number for swapon */ +/* #undef SYS_swapon */ + +/* Fallback syscall number for unshare */ +/* #undef SYS_unshare */ + +/* Define to 1 if your declares `struct tm'. */ +/* #undef TM_IN_SYS_TIME */ + +/* Enables colorized output from utils by default */ +#define USE_COLORS_BY_DEFAULT 1 + +/* Define to 1 if want to use CMOS clock. */ +#define USE_HWCLOCK_CMOS 1 + +/* use datetime parsing GPLv3 code to hwclock */ +#define USE_HWCLOCK_GPLv3_DATETIME 1 + +/* Define to 1 if want to support mtab. */ +/* #undef USE_LIBMOUNT_SUPPORT_MTAB */ + +/* Define to 1 if want to support namepaces. */ +#define USE_LIBMOUNT_SUPPORT_NAMESPACES 1 + +/* Enable plymouth support feature for sulogin and aggety */ +#define USE_PLYMOUTH_SUPPORT 1 + +/* Should sulogin use a emergency mount of /dev and /proc? */ +/* #undef USE_SULOGIN_EMERGENCY_MOUNT */ + +/* Enable extensions on AIX 3, Interix. */ +#ifndef _ALL_SOURCE +# define _ALL_SOURCE 1 +#endif +/* Enable GNU extensions on systems that have them. */ +#ifndef _GNU_SOURCE +# define _GNU_SOURCE 1 +#endif +/* Enable threading extensions on Solaris. */ +#ifndef _POSIX_PTHREAD_SEMANTICS +# define _POSIX_PTHREAD_SEMANTICS 1 +#endif +/* Enable extensions on HP NonStop. */ +#ifndef _TANDEM_SOURCE +# define _TANDEM_SOURCE 1 +#endif +/* Enable general extensions on Solaris. */ +#ifndef __EXTENSIONS__ +# define __EXTENSIONS__ 1 +#endif + + +/* Should wall and write be installed setgid tty? */ +#define USE_TTY_GROUP 1 + +/* Define to 1 to remove /bin and /sbin from PATH env.variable */ +/* #undef USE_USRDIR_PATHS_ONLY */ + +/* Define to 1 to use vendordir */ +/* #undef USE_VENDORDIR */ + +/* Version number of package */ +#define VERSION "2.36.97-87ac5" + +/* Define WORDS_BIGENDIAN to 1 if your processor stores words with the most + significant byte first (like Motorola and SPARC, unlike Intel). */ +#if defined AC_APPLE_UNIVERSAL_BUILD +# if defined __BIG_ENDIAN__ +# define WORDS_BIGENDIAN 1 +# endif +#else +# ifndef WORDS_BIGENDIAN +/* # undef WORDS_BIGENDIAN */ +# endif +#endif + +/* Enable MAP_ANON in sys/mman.h on Mac OS X */ +/* #undef _DARWIN_C_SOURCE */ + +/* Enable large inode numbers on Mac OS X 10.5. */ +#ifndef _DARWIN_USE_64_BIT_INODE +# define _DARWIN_USE_64_BIT_INODE 1 +#endif + +/* Number of bits in a file offset, on hosts where this is settable. */ +/* #undef _FILE_OFFSET_BITS */ + +/* Define to 1 to make fseeko visible on some hosts (e.g. glibc 2.2). */ +/* #undef _LARGEFILE_SOURCE */ + +/* Define for large files, on AIX-style hosts. */ +/* #undef _LARGE_FILES */ + +/* Define to 1 if on MINIX. */ +/* #undef _MINIX */ + +/* Define to 2 if the system does not provide POSIX.1 features except with + this defined. */ +/* #undef _POSIX_1_SOURCE */ + +/* Define to 1 if you need to in order for `stat' and other things to work. */ +/* #undef _POSIX_SOURCE */ + +/* Define to empty if `const' does not conform to ANSI C. */ +/* #undef const */ + +/* Define to empty if the keyword `volatile' does not work. Warning: valid + code using `volatile' can become incorrect without. Disable with care. */ +/* #undef volatile */ diff --git a/utils/include/all-io.h b/utils/include/all-io.h new file mode 100644 index 0000000..8ffa9cf --- /dev/null +++ b/utils/include/all-io.h @@ -0,0 +1,81 @@ +/* + * No copyright is claimed. This code is in the public domain; do with + * it what you wish. + * + * Written by Karel Zak + * Petr Uzel + */ + +#ifndef UTIL_LINUX_ALL_IO_H +#define UTIL_LINUX_ALL_IO_H + +#include +#include +#include + +#include "c.h" + +static inline int write_all(int fd, const void *buf, size_t count) +{ + while (count) { + ssize_t tmp; + + errno = 0; + tmp = write(fd, buf, count); + if (tmp > 0) { + count -= tmp; + if (count) + buf = (const void *) ((const char *) buf + tmp); + } else if (errno != EINTR && errno != EAGAIN) + return -1; + if (errno == EAGAIN) /* Try later, *sigh* */ + xusleep(250000); + } + return 0; +} + +static inline int fwrite_all(const void *ptr, size_t size, + size_t nmemb, FILE *stream) +{ + while (nmemb) { + size_t tmp; + + errno = 0; + tmp = fwrite(ptr, size, nmemb, stream); + if (tmp > 0) { + nmemb -= tmp; + if (nmemb) + ptr = (const void *) ((const char *) ptr + (tmp * size)); + } else if (errno != EINTR && errno != EAGAIN) + return -1; + if (errno == EAGAIN) /* Try later, *sigh* */ + xusleep(250000); + } + return 0; +} + +static inline ssize_t read_all(int fd, char *buf, size_t count) +{ + ssize_t ret; + ssize_t c = 0; + int tries = 0; + + memset(buf, 0, count); + while (count > 0) { + ret = read(fd, buf, count); + if (ret <= 0) { + if (ret < 0 && (errno == EAGAIN || errno == EINTR) && (tries++ < 5)) { + xusleep(250000); + continue; + } + return c ? c : -1; + } + tries = 0; + count -= ret; + buf += ret; + c += ret; + } + return c; +} + +#endif /* UTIL_LINUX_ALL_IO_H */ diff --git a/utils/include/bitops.h b/utils/include/bitops.h new file mode 100644 index 0000000..287d4af --- /dev/null +++ b/utils/include/bitops.h @@ -0,0 +1,150 @@ +/* + * No copyright is claimed. This code is in the public domain; do with + * it what you wish. + * + * Written by Karel Zak + */ +#ifndef BITOPS_H +#define BITOPS_H + +#include +#include + +#if defined(HAVE_BYTESWAP_H) +# include +#endif + +#if defined(HAVE_ENDIAN_H) +# include +#elif defined(HAVE_SYS_ENDIAN_H) /* BSDs have them here */ +# include +#endif + +#if defined(__OpenBSD__) +# include +# define be16toh(x) betoh16(x) +# define be32toh(x) betoh32(x) +# define be64toh(x) betoh64(x) +#elif defined(__NetBSD__) || defined(__FreeBSD__) || defined(__DragonFly__) +# define bswap_16(x) bswap16(x) +# define bswap_32(x) bswap32(x) +# define bswap_64(x) bswap64(x) +#elif defined(__APPLE__) +# include +# define htobe16(x) OSSwapHostToBigInt16(x) +# define htole16(x) OSSwapHostToLittleInt16(x) +# define be16toh(x) OSSwapBigToHostInt16(x) +# define le16toh(x) OSSwapLittleToHostInt16(x) +# define htobe32(x) OSSwapHostToBigInt32(x) +# define htole32(x) OSSwapHostToLittleInt32(x) +# define be32toh(x) OSSwapBigToHostInt32(x) +# define le32toh(x) OSSwapLittleToHostInt32(x) +# define htobe64(x) OSSwapHostToBigInt64(x) +# define htole64(x) OSSwapHostToLittleInt64(x) +# define be64toh(x) OSSwapBigToHostInt64(x) +# define le64toh(x) OSSwapLittleToHostInt64(x) +# define bswap_16(x) OSSwapInt16(x) +# define bswap_32(x) OSSwapInt32(x) +# define bswap_64(x) OSSwapInt64(x) +#endif + +/* + * Fallbacks + * casts are necessary for constants, because we never know how for sure + * how U/UL/ULL map to __u16, __u32, __u64. At least not in a portable way. + */ +#ifndef bswap_16 +# define bswap_16(x) ((uint16_t)( \ + (((uint16_t)(x) & 0x00FF) << 8) | \ + (((uint16_t)(x) & 0xFF00) >> 8))) +#endif + +#ifndef bswap_32 +# define bswap_32(x) ((uint32_t)( \ + (((uint32_t)(x) & 0x000000FF) << 24) | \ + (((uint32_t)(x) & 0x0000FF00) << 8) | \ + (((uint32_t)(x) & 0x00FF0000) >> 8) | \ + (((uint32_t)(x) & 0xFF000000) >> 24))) +#endif + +#ifndef bswap_64 +# define bswap_64(x) ((uint64_t)( \ + (((uint64_t)(x) & 0x00000000000000FFULL) << 56) | \ + (((uint64_t)(x) & 0x000000000000FF00ULL) << 40) | \ + (((uint64_t)(x) & 0x0000000000FF0000ULL) << 24) | \ + (((uint64_t)(x) & 0x00000000FF000000ULL) << 8) | \ + (((uint64_t)(x) & 0x000000FF00000000ULL) >> 8) | \ + (((uint64_t)(x) & 0x0000FF0000000000ULL) >> 24) | \ + (((uint64_t)(x) & 0x00FF000000000000ULL) >> 40) | \ + (((uint64_t)(x) & 0xFF00000000000000ULL) >> 56))) +#endif + +#ifndef htobe16 +# if !defined(WORDS_BIGENDIAN) +# define htobe16(x) bswap_16 (x) +# define htole16(x) (x) +# define be16toh(x) bswap_16 (x) +# define le16toh(x) (x) +# define htobe32(x) bswap_32 (x) +# define htole32(x) (x) +# define be32toh(x) bswap_32 (x) +# define le32toh(x) (x) +# define htobe64(x) bswap_64 (x) +# define htole64(x) (x) +# define be64toh(x) bswap_64 (x) +# define le64toh(x) (x) +# else +# define htobe16(x) (x) +# define htole16(x) bswap_16 (x) +# define be16toh(x) (x) +# define le16toh(x) bswap_16 (x) +# define htobe32(x) (x) +# define htole32(x) bswap_32 (x) +# define be32toh(x) (x) +# define le32toh(x) bswap_32 (x) +# define htobe64(x) (x) +# define htole64(x) bswap_64 (x) +# define be64toh(x) (x) +# define le64toh(x) bswap_64 (x) +# endif +#endif + +/* + * Byte swab macros (based on linux/byteorder/swab.h) + */ +#define swab16(x) bswap_16(x) +#define swab32(x) bswap_32(x) +#define swab64(x) bswap_64(x) + +#define cpu_to_le16(x) ((uint16_t) htole16(x)) +#define cpu_to_le32(x) ((uint32_t) htole32(x)) +#define cpu_to_le64(x) ((uint64_t) htole64(x)) + +#define cpu_to_be16(x) ((uint16_t) htobe16(x)) +#define cpu_to_be32(x) ((uint32_t) htobe32(x)) +#define cpu_to_be64(x) ((uint64_t) htobe64(x)) + +#define le16_to_cpu(x) ((uint16_t) le16toh(x)) +#define le32_to_cpu(x) ((uint32_t) le32toh(x)) +#define le64_to_cpu(x) ((uint64_t) le64toh(x)) + +#define be16_to_cpu(x) ((uint16_t) be16toh(x)) +#define be32_to_cpu(x) ((uint32_t) be32toh(x)) +#define be64_to_cpu(x) ((uint64_t) be64toh(x)) + +/* + * Bit map related macros. Usually provided by libc. + */ +#ifndef NBBY +# define NBBY CHAR_BIT +#endif + +#ifndef setbit +# define setbit(a,i) ((a)[(i)/NBBY] |= 1<<((i)%NBBY)) +# define clrbit(a,i) ((a)[(i)/NBBY] &= ~(1<<((i)%NBBY))) +# define isset(a,i) ((a)[(i)/NBBY] & (1<<((i)%NBBY))) +# define isclr(a,i) (((a)[(i)/NBBY] & (1<<((i)%NBBY))) == 0) +#endif + +#endif /* BITOPS_H */ + diff --git a/utils/include/blkdev.h b/utils/include/blkdev.h new file mode 100644 index 0000000..6cbecbb --- /dev/null +++ b/utils/include/blkdev.h @@ -0,0 +1,151 @@ +/* + * No copyright is claimed. This code is in the public domain; do with + * it what you wish. + * + * Written by Karel Zak + */ +#ifndef BLKDEV_H +#define BLKDEV_H + +#include +#include +#ifdef HAVE_SYS_IOCCOM_H +# include /* for _IO macro on e.g. Solaris */ +#endif +#include +#include +#include + +#ifdef HAVE_SYS_MKDEV_H +# include /* major and minor on Solaris */ +#endif + +#define DEFAULT_SECTOR_SIZE 512 + +#ifdef __linux__ +/* very basic ioctls, should be available everywhere */ +# ifndef BLKROSET +# define BLKROSET _IO(0x12,93) /* set device read-only (0 = read-write) */ +# define BLKROGET _IO(0x12,94) /* get read-only status (0 = read_write) */ +# define BLKRRPART _IO(0x12,95) /* re-read partition table */ +# define BLKGETSIZE _IO(0x12,96) /* return device size /512 (long *arg) */ +# define BLKFLSBUF _IO(0x12,97) /* flush buffer cache */ +# define BLKRASET _IO(0x12,98) /* set read ahead for block device */ +# define BLKRAGET _IO(0x12,99) /* get current read ahead setting */ +# define BLKFRASET _IO(0x12,100) /* set filesystem (mm/filemap.c) read-ahead */ +# define BLKFRAGET _IO(0x12,101) /* get filesystem (mm/filemap.c) read-ahead */ +# define BLKSECTSET _IO(0x12,102) /* set max sectors per request (ll_rw_blk.c) */ +# define BLKSECTGET _IO(0x12,103) /* get max sectors per request (ll_rw_blk.c) */ +# define BLKSSZGET _IO(0x12,104) /* get block device sector size */ + +/* ioctls introduced in 2.2.16, removed in 2.5.58 */ +# define BLKELVGET _IOR(0x12,106,size_t) /* elevator get */ +# define BLKELVSET _IOW(0x12,107,size_t) /* elevator set */ + +# define BLKBSZGET _IOR(0x12,112,size_t) +# define BLKBSZSET _IOW(0x12,113,size_t) +# endif /* !BLKROSET */ + +# ifndef BLKGETSIZE64 +# define BLKGETSIZE64 _IOR(0x12,114,size_t) /* return device size in bytes (u64 *arg) */ +# endif + +/* block device topology ioctls, introduced in 2.6.32 (commit ac481c20) */ +# ifndef BLKIOMIN +# define BLKIOMIN _IO(0x12,120) +# define BLKIOOPT _IO(0x12,121) +# define BLKALIGNOFF _IO(0x12,122) +# define BLKPBSZGET _IO(0x12,123) +# endif + +/* discard zeroes support, introduced in 2.6.33 (commit 98262f27) */ +# ifndef BLKDISCARDZEROES +# define BLKDISCARDZEROES _IO(0x12,124) +# endif + +/* filesystem freeze, introduced in 2.6.29 (commit fcccf502) */ +# ifndef FIFREEZE +# define FIFREEZE _IOWR('X', 119, int) /* Freeze */ +# define FITHAW _IOWR('X', 120, int) /* Thaw */ +# endif + +/* uniform CD-ROM information */ +# ifndef CDROM_GET_CAPABILITY +# define CDROM_GET_CAPABILITY 0x5331 +# endif + +#endif /* __linux */ + + +#ifdef APPLE_DARWIN +# define BLKGETSIZE DKIOCGETBLOCKCOUNT32 +#endif + +#ifndef HDIO_GETGEO +# ifdef __linux__ +# define HDIO_GETGEO 0x0301 +# endif + +struct hd_geometry { + unsigned char heads; + unsigned char sectors; + unsigned short cylinders; /* truncated */ + unsigned long start; +}; +#endif /* HDIO_GETGEO */ + + +/* are we working with block device? */ +int is_blkdev(int fd); + +/* open block device or file */ +int open_blkdev_or_file(const struct stat *st, const char *name, const int oflag); + +/* Determine size in bytes */ +off_t blkdev_find_size (int fd); + +/* get size in bytes */ +int blkdev_get_size(int fd, unsigned long long *bytes); + +/* get 512-byte sector count */ +int blkdev_get_sectors(int fd, unsigned long long *sectors); + +/* get hardware sector size */ +int blkdev_get_sector_size(int fd, int *sector_size); + +/* specifies whether or not the device is misaligned */ +int blkdev_is_misaligned(int fd); + +/* get physical block device size */ +int blkdev_get_physector_size(int fd, int *sector_size); + +/* is the device cdrom capable? */ +int blkdev_is_cdrom(int fd); + +/* get device's geometry - legacy */ +int blkdev_get_geometry(int fd, unsigned int *h, unsigned int *s); + +/* SCSI device types. Copied almost as-is from kernel header. + * http://git.kernel.org/cgit/linux/kernel/git/torvalds/linux.git/tree/include/scsi/scsi.h */ +#define SCSI_TYPE_DISK 0x00 +#define SCSI_TYPE_TAPE 0x01 +#define SCSI_TYPE_PRINTER 0x02 +#define SCSI_TYPE_PROCESSOR 0x03 /* HP scanners use this */ +#define SCSI_TYPE_WORM 0x04 /* Treated as ROM by our system */ +#define SCSI_TYPE_ROM 0x05 +#define SCSI_TYPE_SCANNER 0x06 +#define SCSI_TYPE_MOD 0x07 /* Magneto-optical disk - treated as SCSI_TYPE_DISK */ +#define SCSI_TYPE_MEDIUM_CHANGER 0x08 +#define SCSI_TYPE_COMM 0x09 /* Communications device */ +#define SCSI_TYPE_RAID 0x0c +#define SCSI_TYPE_ENCLOSURE 0x0d /* Enclosure Services Device */ +#define SCSI_TYPE_RBC 0x0e +#define SCSI_TYPE_OSD 0x11 +#define SCSI_TYPE_NO_LUN 0x7f + +/* convert scsi type code to name */ +const char *blkdev_scsi_type_to_name(int type); + +int blkdev_lock(int fd, const char *devname, const char *lockmode); + +#endif /* BLKDEV_H */ diff --git a/utils/include/c.h b/utils/include/c.h new file mode 100644 index 0000000..ae08131 --- /dev/null +++ b/utils/include/c.h @@ -0,0 +1,427 @@ +/* + * Fundamental C definitions. + * + * No copyright is claimed. This code is in the public domain; do with + * it what you wish. + */ +#ifndef UTIL_LINUX_C_H +#define UTIL_LINUX_C_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#ifdef HAVE_ERR_H +# include +#endif + +#ifdef HAVE_SYS_SYSMACROS_H +# include /* for major, minor */ +#endif + +#ifndef LOGIN_NAME_MAX +# define LOGIN_NAME_MAX 256 +#endif + +#ifndef NAME_MAX +# define NAME_MAX PATH_MAX +#endif + +/* + * __GNUC_PREREQ is deprecated in favour of __has_attribute() and + * __has_feature(). The __has macros are supported by clang and gcc>=5. + */ +#ifndef __GNUC_PREREQ +# if defined __GNUC__ && defined __GNUC_MINOR__ +# define __GNUC_PREREQ(maj, min) \ + ((__GNUC__ << 16) + __GNUC_MINOR__ >= ((maj) << 16) + (min)) +# else +# define __GNUC_PREREQ(maj, min) 0 +# endif +#endif + +#ifdef __GNUC__ + +/* &a[0] degrades to a pointer: a different type from an array */ +# define __must_be_array(a) \ + UL_BUILD_BUG_ON_ZERO(__builtin_types_compatible_p(__typeof__(a), __typeof__(&a[0]))) + +# define ignore_result(x) __extension__ ({ \ + __typeof__(x) __dummy __attribute__((__unused__)) = (x); (void) __dummy; \ +}) + +#else /* !__GNUC__ */ +# define __must_be_array(a) 0 +# define __attribute__(_arg_) +# define ignore_result(x) ((void) (x)) +#endif /* !__GNUC__ */ + +/* + * It evaluates to 1 if the attribute/feature is supported by the current + * compilation target. Fallback for old compilers. + */ +#ifndef __has_attribute + #define __has_attribute(x) 0 +#endif + +#ifndef __has_feature + #define __has_feature(x) 0 +#endif + +/* + * Function attributes + */ +#ifndef __ul_alloc_size +# if (__has_attribute(alloc_size) && __has_attribute(warn_unused_result)) || __GNUC_PREREQ (4, 3) +# define __ul_alloc_size(s) __attribute__((alloc_size(s), warn_unused_result)) +# else +# define __ul_alloc_size(s) +# endif +#endif + +#ifndef __ul_calloc_size +# if (__has_attribute(alloc_size) && __has_attribute(warn_unused_result)) || __GNUC_PREREQ (4, 3) +# define __ul_calloc_size(n, s) __attribute__((alloc_size(n, s), warn_unused_result)) +# else +# define __ul_calloc_size(n, s) +# endif +#endif + +#if __has_attribute(returns_nonnull) || __GNUC_PREREQ (4, 9) +# define __ul_returns_nonnull __attribute__((returns_nonnull)) +#else +# define __ul_returns_nonnull +#endif + +/* + * Force a compilation error if condition is true, but also produce a + * result (of value 0 and type size_t), so the expression can be used + * e.g. in a structure initializer (or wherever else comma expressions + * aren't permitted). + */ +#define UL_BUILD_BUG_ON_ZERO(e) __extension__ (sizeof(struct { int:-!!(e); })) +#define BUILD_BUG_ON_NULL(e) ((void *)sizeof(struct { int:-!!(e); })) + +#ifndef ARRAY_SIZE +# define ARRAY_SIZE(arr) (sizeof(arr) / sizeof((arr)[0]) + __must_be_array(arr)) +#endif + +#ifndef PATH_MAX +# define PATH_MAX 4096 +#endif + +#ifndef TRUE +# define TRUE 1 +#endif + +#ifndef FALSE +# define FALSE 0 +#endif + +#ifndef min +# define min(x, y) __extension__ ({ \ + __typeof__(x) _min1 = (x); \ + __typeof__(y) _min2 = (y); \ + (void) (&_min1 == &_min2); \ + _min1 < _min2 ? _min1 : _min2; }) +#endif + +#ifndef max +# define max(x, y) __extension__ ({ \ + __typeof__(x) _max1 = (x); \ + __typeof__(y) _max2 = (y); \ + (void) (&_max1 == &_max2); \ + _max1 > _max2 ? _max1 : _max2; }) +#endif + +#ifndef cmp_numbers +# define cmp_numbers(x, y) __extension__ ({ \ + __typeof__(x) _a = (x); \ + __typeof__(y) _b = (y); \ + (void) (&_a == &_b); \ + _a == _b ? 0 : _a > _b ? 1 : -1; }) +#endif + +#ifndef offsetof +#define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER) +#endif + +/* + * container_of - cast a member of a structure out to the containing structure + * @ptr: the pointer to the member. + * @type: the type of the container struct this is embedded in. + * @member: the name of the member within the struct. + */ +#ifndef container_of +#define container_of(ptr, type, member) __extension__ ({ \ + const __typeof__( ((type *)0)->member ) *__mptr = (ptr); \ + (type *)( (char *)__mptr - offsetof(type,member) );}) +#endif + +#ifndef HAVE_PROGRAM_INVOCATION_SHORT_NAME +# ifdef HAVE___PROGNAME +extern char *__progname; +# define program_invocation_short_name __progname +# else +# ifdef HAVE_GETEXECNAME +# define program_invocation_short_name \ + prog_inv_sh_nm_from_file(getexecname(), 0) +# else +# define program_invocation_short_name \ + prog_inv_sh_nm_from_file(__FILE__, 1) +# endif +static char prog_inv_sh_nm_buf[256]; +static inline char * +prog_inv_sh_nm_from_file(char *f, char stripext) +{ + char *t; + + if ((t = strrchr(f, '/')) != NULL) + t++; + else + t = f; + + strncpy(prog_inv_sh_nm_buf, t, sizeof(prog_inv_sh_nm_buf) - 1); + prog_inv_sh_nm_buf[sizeof(prog_inv_sh_nm_buf) - 1] = '\0'; + + if (stripext && (t = strrchr(prog_inv_sh_nm_buf, '.')) != NULL) + *t = '\0'; + + return prog_inv_sh_nm_buf; +} +# endif +#endif + + +#ifndef HAVE_ERR_H +static inline void +errmsg(char doexit, int excode, char adderr, const char *fmt, ...) +{ + fprintf(stderr, "%s: ", program_invocation_short_name); + if (fmt != NULL) { + va_list argp; + va_start(argp, fmt); + vfprintf(stderr, fmt, argp); + va_end(argp); + if (adderr) + fprintf(stderr, ": "); + } + if (adderr) + fprintf(stderr, "%m"); + fprintf(stderr, "\n"); + if (doexit) + exit(excode); +} + +#ifndef HAVE_ERR +# define err(E, FMT...) errmsg(1, E, 1, FMT) +#endif + +#ifndef HAVE_ERRX +# define errx(E, FMT...) errmsg(1, E, 0, FMT) +#endif + +#ifndef HAVE_WARN +# define warn(FMT...) errmsg(0, 0, 1, FMT) +#endif + +#ifndef HAVE_WARNX +# define warnx(FMT...) errmsg(0, 0, 0, FMT) +#endif +#endif /* !HAVE_ERR_H */ + + +/* Don't use inline function to avoid '#include "nls.h"' in c.h + */ +#define errtryhelp(eval) __extension__ ({ \ + fprintf(stderr, _("Try '%s --help' for more information.\n"), \ + program_invocation_short_name); \ + exit(eval); \ +}) + +/* After failed execvp() */ +#define EX_EXEC_FAILED 126 /* Program located, but not usable. */ +#define EX_EXEC_ENOENT 127 /* Could not find program to exec. */ +#define errexec(name) err(errno == ENOENT ? EX_EXEC_ENOENT : EX_EXEC_FAILED, \ + _("failed to execute %s"), name) + + +static inline __attribute__((const)) int is_power_of_2(unsigned long num) +{ + return (num != 0 && ((num & (num - 1)) == 0)); +} + +#ifndef HAVE_LOFF_T +typedef int64_t loff_t; +#endif + +#if !defined(HAVE_DIRFD) && (!defined(HAVE_DECL_DIRFD) || HAVE_DECL_DIRFD == 0) && defined(HAVE_DIR_DD_FD) +#include +#include +static inline int dirfd(DIR *d) +{ + return d->dd_fd; +} +#endif + +/* + * Fallback defines for old versions of glibc + */ +#include + +#ifdef O_CLOEXEC +#define UL_CLOEXECSTR "e" +#else +#define UL_CLOEXECSTR "" +#endif + +#ifndef O_CLOEXEC +#define O_CLOEXEC 0 +#endif + +#ifdef __FreeBSD_kernel__ +#ifndef F_DUPFD_CLOEXEC +#define F_DUPFD_CLOEXEC 17 /* Like F_DUPFD, but FD_CLOEXEC is set */ +#endif +#endif + + +#ifndef AI_ADDRCONFIG +#define AI_ADDRCONFIG 0x0020 +#endif + +#ifndef IUTF8 +#define IUTF8 0040000 +#endif + +/* + * MAXHOSTNAMELEN replacement + */ +static inline size_t get_hostname_max(void) +{ + long len = sysconf(_SC_HOST_NAME_MAX); + + if (0 < len) + return len; + +#ifdef MAXHOSTNAMELEN + return MAXHOSTNAMELEN; +#elif HOST_NAME_MAX + return HOST_NAME_MAX; +#endif + return 64; +} + +/* + * The usleep function was marked obsolete in POSIX.1-2001 and was removed + * in POSIX.1-2008. It was replaced with nanosleep() that provides more + * advantages (like no interaction with signals and other timer functions). + */ +#include + +static inline int xusleep(useconds_t usec) +{ +#ifdef HAVE_NANOSLEEP + struct timespec waittime = { + .tv_sec = usec / 1000000L, + .tv_nsec = (usec % 1000000L) * 1000 + }; + return nanosleep(&waittime, NULL); +#elif defined(HAVE_USLEEP) + return usleep(usec); +#else +# error "System with usleep() or nanosleep() required!" +#endif +} + +/* + * Constant strings for usage() functions. For more info see + * Documentation/{howto-usage-function.txt,boilerplate.c} + */ +#define USAGE_HEADER _("\nUsage:\n") +#define USAGE_OPTIONS _("\nOptions:\n") +#define USAGE_FUNCTIONS _("\nFunctions:\n") +#define USAGE_COMMANDS _("\nCommands:\n") +#define USAGE_ARGUMENTS _("\nArguments:\n") +#define USAGE_COLUMNS _("\nAvailable output columns:\n") +#define USAGE_SEPARATOR "\n" + +#define USAGE_OPTSTR_HELP _("display this help") +#define USAGE_OPTSTR_VERSION _("display version") + +#define USAGE_HELP_OPTIONS(marg_dsc) \ + "%-" #marg_dsc "s%s\n" \ + "%-" #marg_dsc "s%s\n" \ + , " -h, --help", USAGE_OPTSTR_HELP \ + , " -V, --version", USAGE_OPTSTR_VERSION + +#define USAGE_ARG_SEPARATOR "\n" +#define USAGE_ARG_SIZE(_name) \ + _(" %s arguments may be followed by the suffixes for\n" \ + " GiB, TiB, PiB, EiB, ZiB, and YiB (the \"iB\" is optional)\n"), _name + +#define USAGE_MAN_TAIL(_man) _("\nFor more details see %s.\n"), _man + +#define UTIL_LINUX_VERSION _("%s from %s\n"), program_invocation_short_name, PACKAGE_STRING + +#define print_version(eval) __extension__ ({ \ + printf(UTIL_LINUX_VERSION); \ + exit(eval); \ +}) + +/* + * seek stuff + */ +#ifndef SEEK_DATA +# define SEEK_DATA 3 +#endif +#ifndef SEEK_HOLE +# define SEEK_HOLE 4 +#endif + + +/* + * Macros to convert #define'itions to strings, for example + * #define XYXXY 42 + * printf ("%s=%s\n", stringify(XYXXY), stringify_value(XYXXY)); + */ +#define stringify_value(s) stringify(s) +#define stringify(s) #s + +/* + * UL_ASAN_BLACKLIST is a macro to tell AddressSanitizer (a compile-time + * instrumentation shipped with Clang and GCC) to not instrument the + * annotated function. Furthermore, it will prevent the compiler from + * inlining the function because inlining currently breaks the blacklisting + * mechanism of AddressSanitizer. + */ +#if __has_feature(address_sanitizer) && __has_attribute(no_sanitize_memory) && __has_attribute(no_sanitize_address) +# define UL_ASAN_BLACKLIST __attribute__((noinline)) __attribute__((no_sanitize_memory)) __attribute__((no_sanitize_address)) +#else +# define UL_ASAN_BLACKLIST /* nothing */ +#endif + +/* + * Note that sysconf(_SC_GETPW_R_SIZE_MAX) returns *initial* suggested size for + * pwd buffer and in some cases it is not large enough. See POSIX and + * getpwnam_r man page for more details. + */ +#define UL_GETPW_BUFSIZ (16 * 1024) + +/* + * Darwin or other BSDs may only have MAP_ANON. To get it on Darwin we must + * define _DARWIN_C_SOURCE before including sys/mman.h. We do this in config.h. + */ +#if !defined MAP_ANONYMOUS && defined MAP_ANON +# define MAP_ANONYMOUS (MAP_ANON) +#endif + +#endif /* UTIL_LINUX_C_H */ diff --git a/utils/include/canonicalize.h b/utils/include/canonicalize.h new file mode 100644 index 0000000..ff6ef0d --- /dev/null +++ b/utils/include/canonicalize.h @@ -0,0 +1,32 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Library Public License as published by + * the Free Software Foundation; either version 2, 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 Library Public License for more details. + */ +#ifndef CANONICALIZE_H +#define CANONICALIZE_H + +#include "c.h" /* for PATH_MAX */ +#include "strutils.h" + +extern char *canonicalize_path(const char *path); +extern char *canonicalize_path_restricted(const char *path); +extern char *canonicalize_dm_name(const char *ptname); +extern char *__canonicalize_dm_name(const char *prefix, const char *ptname); + +extern char *absolute_path(const char *path); + +static inline int is_relative_path(const char *path) +{ + if (!path || *path == '/') + return 0; + return 1; +} + +#endif /* CANONICALIZE_H */ diff --git a/utils/include/caputils.h b/utils/include/caputils.h new file mode 100644 index 0000000..852903a --- /dev/null +++ b/utils/include/caputils.h @@ -0,0 +1,34 @@ +/* + * 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, or (at your option) any + * later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef CAPUTILS_H +#define CAPUTILS_H + +#include + +#ifndef PR_CAP_AMBIENT +# define PR_CAP_AMBIENT 47 +# define PR_CAP_AMBIENT_IS_SET 1 +# define PR_CAP_AMBIENT_RAISE 2 +# define PR_CAP_AMBIENT_LOWER 3 +#endif + +extern int capset(cap_user_header_t header, cap_user_data_t data); +extern int capget(cap_user_header_t header, const cap_user_data_t data); + +extern int cap_last_cap(void); + +#endif /* CAPUTILS_H */ diff --git a/utils/include/carefulputc.h b/utils/include/carefulputc.h new file mode 100644 index 0000000..f1c0356 --- /dev/null +++ b/utils/include/carefulputc.h @@ -0,0 +1,155 @@ +#ifndef UTIL_LINUX_CAREFULPUTC_H +#define UTIL_LINUX_CAREFULPUTC_H + +/* + * A putc() for use in write and wall (that sometimes are sgid tty). + * It avoids control characters in our locale, and also ASCII control + * characters. Note that the locale of the recipient is unknown. +*/ +#include +#include +#include + +#include "cctype.h" + +static inline int fputc_careful(int c, FILE *fp, const char fail) +{ + int ret; + + if (isprint(c) || c == '\a' || c == '\t' || c == '\r' || c == '\n') + ret = putc(c, fp); + else if (!c_isascii(c)) + ret = fprintf(fp, "\\%3o", (unsigned char)c); + else { + ret = putc(fail, fp); + if (ret != EOF) + ret = putc(c ^ 0x40, fp); + } + return (ret < 0) ? EOF : 0; +} + +/* + * Requirements enumerated via testing (V8, Firefox, IE11): + * + * var charsToEscape = []; + * for (var i = 0; i < 65535; i += 1) { + * try { + * JSON.parse('{"sample": "' + String.fromCodePoint(i) + '"}'); + * } catch (e) { + * charsToEscape.push(i); + * } + * } + */ +static inline void fputs_quoted_case_json(const char *data, FILE *out, int dir) +{ + const char *p; + + fputc('"', out); + for (p = data; p && *p; p++) { + + const unsigned char c = (unsigned char) *p; + + /* From http://www.json.org + * + * The double-quote and backslashes would break out a string or + * init an escape sequence if not escaped. + * + * Note that single-quotes and forward slashes, while they're + * in the JSON spec, don't break double-quoted strings. + */ + if (c == '"' || c == '\\') { + fputc('\\', out); + fputc(c, out); + continue; + } + + /* All non-control characters OK; do the case swap as required. */ + if (c >= 0x20) { + fputc(dir == 1 ? toupper(c) : + dir == -1 ? tolower(c) : *p, out); + continue; + } + + /* In addition, all chars under ' ' break Node's/V8/Chrome's, and + * Firefox's JSON.parse function + */ + switch (c) { + /* Handle short-hand cases to reduce output size. C + * has most of the same stuff here, so if there's an + * "Escape for C" function somewhere in the STL, we + * should probably be using it. + */ + case '\b': + fputs("\\b", out); + break; + case '\t': + fputs("\\t", out); + break; + case '\n': + fputs("\\n", out); + break; + case '\f': + fputs("\\f", out); + break; + case '\r': + fputs("\\r", out); + break; + default: + /* Other assorted control characters */ + fprintf(out, "\\u00%02x", c); + break; + } + } + fputc('"', out); +} + + +static inline void fputs_quoted_case(const char *data, FILE *out, int dir) +{ + const char *p; + + fputc('"', out); + for (p = data; p && *p; p++) { + if ((unsigned char) *p == 0x22 || /* " */ + (unsigned char) *p == 0x5c || /* \ */ + (unsigned char) *p == 0x60 || /* ` */ + (unsigned char) *p == 0x24 || /* $ */ + !isprint((unsigned char) *p) || + iscntrl((unsigned char) *p)) { + + fprintf(out, "\\x%02x", (unsigned char) *p); + } else + fputc(dir == 1 ? toupper(*p) : + dir == -1 ? tolower(*p) : + *p, out); + } + fputc('"', out); +} + +#define fputs_quoted(_d, _o) fputs_quoted_case(_d, _o, 0) +#define fputs_quoted_upper(_d, _o) fputs_quoted_case(_d, _o, 1) +#define fputs_quoted_lower(_d, _o) fputs_quoted_case(_d, _o, -1) + +#define fputs_quoted_json(_d, _o) fputs_quoted_case_json(_d, _o, 0) +#define fputs_quoted_json_upper(_d, _o) fputs_quoted_case_json(_d, _o, 1) +#define fputs_quoted_json_lower(_d, _o) fputs_quoted_case_json(_d, _o, -1) + +static inline void fputs_nonblank(const char *data, FILE *out) +{ + const char *p; + + for (p = data; p && *p; p++) { + if (isblank((unsigned char) *p) || + (unsigned char) *p == 0x5c || /* \ */ + !isprint((unsigned char) *p) || + iscntrl((unsigned char) *p)) { + + fprintf(out, "\\x%02x", (unsigned char) *p); + + } else + fputc(*p, out); + } +} + + +#endif /* _CAREFULPUTC_H */ diff --git a/utils/include/cctype.h b/utils/include/cctype.h new file mode 100644 index 0000000..6ab644c --- /dev/null +++ b/utils/include/cctype.h @@ -0,0 +1,325 @@ +/** + * Character handling in C locale. + * + * This file is based on gnulib c-ctype.h-dd7a871 with the + * other gnulib dependencies removed for use in util-linux. + * + * These functions work like the corresponding functions in , + * except that they have the C (POSIX) locale hardwired, whereas the + * functions' behaviour depends on the current locale set via + * setlocale. + * + * Copyright (C) 2000-2003, 2006, 2008-2017 Free Software Foundation, Inc. + * + * 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, see . + */ + +#ifndef UTIL_LINUX_CCTYPE_H +#define UTIL_LINUX_CCTYPE_H + +/** + * The functions defined in this file assume the "C" locale and a character + * set without diacritics (ASCII-US or EBCDIC-US or something like that). + * Even if the "C" locale on a particular system is an extension of the ASCII + * character set (like on BeOS, where it is UTF-8, or on AmigaOS, where it + * is ISO-8859-1), the functions in this file recognize only the ASCII + * characters. + */ + +#if (' ' == 32) && ('!' == 33) && ('"' == 34) && ('#' == 35) \ + && ('%' == 37) && ('&' == 38) && ('\'' == 39) && ('(' == 40) \ + && (')' == 41) && ('*' == 42) && ('+' == 43) && (',' == 44) \ + && ('-' == 45) && ('.' == 46) && ('/' == 47) && ('0' == 48) \ + && ('1' == 49) && ('2' == 50) && ('3' == 51) && ('4' == 52) \ + && ('5' == 53) && ('6' == 54) && ('7' == 55) && ('8' == 56) \ + && ('9' == 57) && (':' == 58) && (';' == 59) && ('<' == 60) \ + && ('=' == 61) && ('>' == 62) && ('?' == 63) && ('A' == 65) \ + && ('B' == 66) && ('C' == 67) && ('D' == 68) && ('E' == 69) \ + && ('F' == 70) && ('G' == 71) && ('H' == 72) && ('I' == 73) \ + && ('J' == 74) && ('K' == 75) && ('L' == 76) && ('M' == 77) \ + && ('N' == 78) && ('O' == 79) && ('P' == 80) && ('Q' == 81) \ + && ('R' == 82) && ('S' == 83) && ('T' == 84) && ('U' == 85) \ + && ('V' == 86) && ('W' == 87) && ('X' == 88) && ('Y' == 89) \ + && ('Z' == 90) && ('[' == 91) && ('\\' == 92) && (']' == 93) \ + && ('^' == 94) && ('_' == 95) && ('a' == 97) && ('b' == 98) \ + && ('c' == 99) && ('d' == 100) && ('e' == 101) && ('f' == 102) \ + && ('g' == 103) && ('h' == 104) && ('i' == 105) && ('j' == 106) \ + && ('k' == 107) && ('l' == 108) && ('m' == 109) && ('n' == 110) \ + && ('o' == 111) && ('p' == 112) && ('q' == 113) && ('r' == 114) \ + && ('s' == 115) && ('t' == 116) && ('u' == 117) && ('v' == 118) \ + && ('w' == 119) && ('x' == 120) && ('y' == 121) && ('z' == 122) \ + && ('{' == 123) && ('|' == 124) && ('}' == 125) && ('~' == 126) + +/* + * The character set is ASCII or one of its variants or extensions, not EBCDIC. + * Testing the value of '\n' and '\r' is not relevant. + */ +# define C_CTYPE_ASCII 1 +#elif ! (' ' == '\x40' && '0' == '\xf0' \ + && 'A' == '\xc1' && 'J' == '\xd1' && 'S' == '\xe2' \ + && 'a' == '\x81' && 'j' == '\x91' && 's' == '\xa2') +# error "Only ASCII and EBCDIC are supported" +#endif + +#if 'A' < 0 +# error "EBCDIC and char is signed -- not supported" +#endif + +/* Cases for control characters. */ +#define _C_CTYPE_CNTRL \ + case '\a': case '\b': case '\f': case '\n': \ + case '\r': case '\t': case '\v': \ + _C_CTYPE_OTHER_CNTRL + +/* ASCII control characters other than those with \-letter escapes. */ +#if C_CTYPE_ASCII +# define _C_CTYPE_OTHER_CNTRL \ + case '\x00': case '\x01': case '\x02': case '\x03': \ + case '\x04': case '\x05': case '\x06': case '\x0e': \ + case '\x0f': case '\x10': case '\x11': case '\x12': \ + case '\x13': case '\x14': case '\x15': case '\x16': \ + case '\x17': case '\x18': case '\x19': case '\x1a': \ + case '\x1b': case '\x1c': case '\x1d': case '\x1e': \ + case '\x1f': case '\x7f' +#else + +/* + * Use EBCDIC code page 1047's assignments for ASCII control chars; + * assume all EBCDIC code pages agree about these assignments. + */ +# define _C_CTYPE_OTHER_CNTRL \ + case '\x00': case '\x01': case '\x02': case '\x03': \ + case '\x07': case '\x0e': case '\x0f': case '\x10': \ + case '\x11': case '\x12': case '\x13': case '\x18': \ + case '\x19': case '\x1c': case '\x1d': case '\x1e': \ + case '\x1f': case '\x26': case '\x27': case '\x2d': \ + case '\x2e': case '\x32': case '\x37': case '\x3c': \ + case '\x3d': case '\x3f' +#endif + +/* Cases for lowercase hex letters, and lowercase letters, all offset by N. */ +#define _C_CTYPE_LOWER_A_THRU_F_N(N) \ + case 'a' + (N): case 'b' + (N): case 'c' + (N): case 'd' + (N): \ + case 'e' + (N): case 'f' + (N) +#define _C_CTYPE_LOWER_N(N) \ + _C_CTYPE_LOWER_A_THRU_F_N(N): \ + case 'g' + (N): case 'h' + (N): case 'i' + (N): case 'j' + (N): \ + case 'k' + (N): case 'l' + (N): case 'm' + (N): case 'n' + (N): \ + case 'o' + (N): case 'p' + (N): case 'q' + (N): case 'r' + (N): \ + case 's' + (N): case 't' + (N): case 'u' + (N): case 'v' + (N): \ + case 'w' + (N): case 'x' + (N): case 'y' + (N): case 'z' + (N) + +/* Cases for hex letters, digits, lower, punct, and upper. */ +#define _C_CTYPE_A_THRU_F \ + _C_CTYPE_LOWER_A_THRU_F_N (0): \ + _C_CTYPE_LOWER_A_THRU_F_N ('A' - 'a') +#define _C_CTYPE_DIGIT \ + case '0': case '1': case '2': case '3': \ + case '4': case '5': case '6': case '7': \ + case '8': case '9' +#define _C_CTYPE_LOWER _C_CTYPE_LOWER_N (0) +#define _C_CTYPE_PUNCT \ + case '!': case '"': case '#': case '$': \ + case '%': case '&': case '\'': case '(': \ + case ')': case '*': case '+': case ',': \ + case '-': case '.': case '/': case ':': \ + case ';': case '<': case '=': case '>': \ + case '?': case '@': case '[': case '\\': \ + case ']': case '^': case '_': case '`': \ + case '{': case '|': case '}': case '~' +#define _C_CTYPE_UPPER _C_CTYPE_LOWER_N ('A' - 'a') + +/** + * Function definitions. + * + * Unlike the functions in , which require an argument in the range + * of the 'unsigned char' type, the functions here operate on values that are + * in the 'unsigned char' range or in the 'char' range. In other words, + * when you have a 'char' value, you need to cast it before using it as + * argument to a function: + * + * const char *s = ...; + * if (isalpha ((unsigned char) *s)) ... + * + * but you don't need to cast it for the functions defined in this file: + * + * const char *s = ...; + * if (c_isalpha (*s)) ... + */ + +static inline int c_isalnum (int c) +{ + switch (c) { + _C_CTYPE_DIGIT: + _C_CTYPE_LOWER: + _C_CTYPE_UPPER: + return 1; + default: + return 0; + } +} + +static inline int c_isalpha (int c) +{ + switch (c) { + _C_CTYPE_LOWER: + _C_CTYPE_UPPER: + return 1; + default: + return 0; + } +} + +/* The function isascii is not locale dependent. + * Its use in EBCDIC is questionable. + */ +static inline int c_isascii (int c) +{ + switch (c) { + case ' ': + _C_CTYPE_CNTRL: + _C_CTYPE_DIGIT: + _C_CTYPE_LOWER: + _C_CTYPE_PUNCT: + _C_CTYPE_UPPER: + return 1; + default: + return 0; + } +} + +static inline int c_isblank (int c) +{ + return c == ' ' || c == '\t'; +} + +static inline int c_iscntrl (int c) +{ + switch (c) { + _C_CTYPE_CNTRL: + return 1; + default: + return 0; + } +} + +static inline int c_isdigit (int c) +{ + switch (c) { + _C_CTYPE_DIGIT: + return 1; + default: + return 0; + } +} + +static inline int c_isgraph (int c) +{ + switch (c) { + _C_CTYPE_DIGIT: + _C_CTYPE_LOWER: + _C_CTYPE_PUNCT: + _C_CTYPE_UPPER: + return 1; + default: + return 0; + } +} + +static inline int c_islower (int c) +{ + switch (c) { + _C_CTYPE_LOWER: + return 1; + default: + return 0; + } +} + +static inline int c_isprint (int c) +{ + switch (c) { + case ' ': + _C_CTYPE_DIGIT: + _C_CTYPE_LOWER: + _C_CTYPE_PUNCT: + _C_CTYPE_UPPER: + return 1; + default: + return 0; + } +} + +static inline int c_ispunct (int c) +{ + switch (c) { + _C_CTYPE_PUNCT: + return 1; + default: + return 0; + } +} + +static inline int c_isspace (int c) +{ + switch (c) { + case ' ': case '\t': case '\n': case '\v': case '\f': case '\r': + return 1; + default: + return 0; + } +} + +static inline int c_isupper (int c) +{ + switch (c) { + _C_CTYPE_UPPER: + return 1; + default: + return 0; + } +} + +static inline int c_isxdigit (int c) +{ + switch (c) { + _C_CTYPE_DIGIT: + _C_CTYPE_A_THRU_F: + return 1; + default: + return 0; + } +} + +static inline int c_tolower (int c) +{ + switch (c) { + _C_CTYPE_UPPER: + return c - 'A' + 'a'; + default: + return c; + } +} + +static inline int c_toupper (int c) +{ + switch (c) { + _C_CTYPE_LOWER: + return c - 'a' + 'A'; + default: + return c; + } +} + +#endif /* UTIL_LINUX_CCTYPE_H */ diff --git a/utils/include/closestream.h b/utils/include/closestream.h new file mode 100644 index 0000000..41afbe2 --- /dev/null +++ b/utils/include/closestream.h @@ -0,0 +1,110 @@ +#ifndef UTIL_LINUX_CLOSESTREAM_H +#define UTIL_LINUX_CLOSESTREAM_H + +#include +#ifdef HAVE_STDIO_EXT_H +#include +#endif +#include + +#include "c.h" +#include "nls.h" + +#ifndef CLOSE_EXIT_CODE +# define CLOSE_EXIT_CODE EXIT_FAILURE +#endif + +static inline int +close_stream(FILE * stream) +{ +#ifdef HAVE___FPENDING + const int some_pending = (__fpending(stream) != 0); +#endif + const int prev_fail = (ferror(stream) != 0); + const int fclose_fail = (fclose(stream) != 0); + + if (prev_fail || (fclose_fail && ( +#ifdef HAVE___FPENDING + some_pending || +#endif + errno != EBADF))) { + if (!fclose_fail && !(errno == EPIPE)) + errno = 0; + return EOF; + } + return 0; +} + +static inline int +flush_standard_stream(FILE *stream) +{ + int fd; + + errno = 0; + + if (ferror(stream) != 0 || fflush(stream) != 0) + goto error; + + /* + * Calling fflush is not sufficient on some filesystems + * like e.g. NFS, which may defer the actual flush until + * close. Calling fsync would help solve this, but would + * probably result in a performance hit. Thus, we work + * around this issue by calling close on a dup'd file + * descriptor from the stream. + */ + if ((fd = fileno(stream)) < 0 || (fd = dup(fd)) < 0 || close(fd) != 0) + goto error; + + return 0; +error: + return (errno == EBADF) ? 0 : EOF; +} + +/* Meant to be used atexit(close_stdout); */ +static inline void +close_stdout(void) +{ + if (flush_standard_stream(stdout) != 0 && !(errno == EPIPE)) { + if (errno) + warn(_("write error")); + else + warnx(_("write error")); + _exit(CLOSE_EXIT_CODE); + } + + if (flush_standard_stream(stderr) != 0) + _exit(CLOSE_EXIT_CODE); +} + +static inline void +close_stdout_atexit(void) +{ + /* + * Note that close stdout at exit disables ASAN to report memory leaks + */ +#if !defined(__SANITIZE_ADDRESS__) + atexit(close_stdout); +#endif +} + +#ifndef HAVE_FSYNC +static inline int +fsync(int fd __attribute__((__unused__))) +{ + return 0; +} +#endif + +static inline int +close_fd(int fd) +{ + const int fsync_fail = (fsync(fd) != 0); + const int close_fail = (close(fd) != 0); + + if (fsync_fail || close_fail) + return EOF; + return 0; +} + +#endif /* UTIL_LINUX_CLOSESTREAM_H */ diff --git a/utils/include/color-names.h b/utils/include/color-names.h new file mode 100644 index 0000000..42f6f8f --- /dev/null +++ b/utils/include/color-names.h @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2012-2015 Karel Zak + * + * This file may be distributed under the terms of the + * GNU Lesser General Public License. + */ +#ifndef UTIL_LINUX_COLOR_NAMES_H +#define UTIL_LINUX_COLOR_NAMES_H + +#define UL_COLOR_RESET "\033[0m" +#define UL_COLOR_BOLD "\033[1m" +#define UL_COLOR_HALFBRIGHT "\033[2m" +#define UL_COLOR_UNDERSCORE "\033[4m" +#define UL_COLOR_BLINK "\033[5m" +#define UL_COLOR_REVERSE "\033[7m" + +/* Standard colors */ +#define UL_COLOR_BLACK "\033[30m" +#define UL_COLOR_RED "\033[31m" +#define UL_COLOR_GREEN "\033[32m" +#define UL_COLOR_BROWN "\033[33m" /* well, brown */ +#define UL_COLOR_BLUE "\033[34m" +#define UL_COLOR_MAGENTA "\033[35m" +#define UL_COLOR_CYAN "\033[36m" +#define UL_COLOR_GRAY "\033[37m" + +/* Bold variants */ +#define UL_COLOR_DARK_GRAY "\033[1;30m" +#define UL_COLOR_BOLD_RED "\033[1;31m" +#define UL_COLOR_BOLD_GREEN "\033[1;32m" +#define UL_COLOR_BOLD_YELLOW "\033[1;33m" +#define UL_COLOR_BOLD_BLUE "\033[1;34m" +#define UL_COLOR_BOLD_MAGENTA "\033[1;35m" +#define UL_COLOR_BOLD_CYAN "\033[1;36m" + +#define UL_COLOR_WHITE "\033[1;37m" + + +/* maximal length of human readable name of ESC seq. */ +#define UL_COLORNAME_MAXSZ 32 + +extern const char *color_sequence_from_colorname(const char *str); + +#endif /* UTIL_LINUX_COLOR_NAMES_H */ diff --git a/utils/include/colors.h b/utils/include/colors.h new file mode 100644 index 0000000..d4ae4e4 --- /dev/null +++ b/utils/include/colors.h @@ -0,0 +1,73 @@ +/* + * Copyright (C) 2012 Ondrej Oprala + * Copyright (C) 2012-2014 Karel Zak + * + * This file may be distributed under the terms of the + * GNU Lesser General Public License. + */ +#ifndef UTIL_LINUX_COLORS_H +#define UTIL_LINUX_COLORS_H + +#include +#include + +#include "color-names.h" + +/* --color[=WHEN] */ +enum colortmode { + UL_COLORMODE_AUTO = 0, + UL_COLORMODE_NEVER, + UL_COLORMODE_ALWAYS, + UL_COLORMODE_UNDEF, + + __UL_NCOLORMODES /* last */ +}; + +#ifdef USE_COLORS_BY_DEFAULT +# define USAGE_COLORS_DEFAULT _("colors are enabled by default") +#else +# define USAGE_COLORS_DEFAULT _("colors are disabled by default") +#endif + +extern int colormode_from_string(const char *str); +extern int colormode_or_err(const char *str, const char *errmsg); + +/* Initialize the global variable UL_COLOR_TERM_OK */ +extern int colors_init(int mode, const char *util_name); + +/* Returns 1 or 0 */ +extern int colors_wanted(void); + +/* Returns UL_COLORMODE_* */ +extern int colors_mode(void); + +/* temporary enable/disable colors */ +extern void colors_off(void); +extern void colors_on(void); + + +/* Set the color */ +extern void color_fenable(const char *seq, FILE *f); + +extern void color_scheme_fenable(const char *name, const char *dflt, FILE *f); +extern const char *color_scheme_get_sequence(const char *name, const char *dflt); + +static inline void color_enable(const char *seq) +{ + color_fenable(seq, stdout); +} + +static inline void color_scheme_enable(const char *name, const char *dflt) +{ + color_scheme_fenable(name, dflt, stdout); +} + +/* Reset colors to default */ +extern void color_fdisable(FILE *f); + +static inline void color_disable(void) +{ + color_fdisable(stdout); +} + +#endif /* UTIL_LINUX_COLORS_H */ diff --git a/utils/include/cpuset.h b/utils/include/cpuset.h new file mode 100644 index 0000000..5a531bf --- /dev/null +++ b/utils/include/cpuset.h @@ -0,0 +1,99 @@ +/* + * This file may be redistributed under the terms of the + * GNU Lesser General Public License. + */ +#ifndef UTIL_LINUX_CPUSET_H +#define UTIL_LINUX_CPUSET_H + +#include + +/* + * Fallback for old or obscure libcs without dynamically allocated cpusets + * + * The following macros are based on code from glibc. + * + * The GNU C Library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + */ +#if !HAVE_DECL_CPU_ALLOC + +# define CPU_ZERO_S(setsize, cpusetp) \ + do { \ + size_t __i; \ + size_t __imax = (setsize) / sizeof (__cpu_mask); \ + __cpu_mask *__bits = (cpusetp)->__bits; \ + for (__i = 0; __i < __imax; ++__i) \ + __bits[__i] = 0; \ + } while (0) + +# define CPU_SET_S(cpu, setsize, cpusetp) \ + ({ size_t __cpu = (cpu); \ + __cpu < 8 * (setsize) \ + ? (((__cpu_mask *) ((cpusetp)->__bits))[__CPUELT (__cpu)] \ + |= __CPUMASK (__cpu)) \ + : 0; }) + +# define CPU_ISSET_S(cpu, setsize, cpusetp) \ + ({ size_t __cpu = (cpu); \ + __cpu < 8 * (setsize) \ + ? ((((__cpu_mask *) ((cpusetp)->__bits))[__CPUELT (__cpu)] \ + & __CPUMASK (__cpu))) != 0 \ + : 0; }) + +# define CPU_EQUAL_S(setsize, cpusetp1, cpusetp2) \ + ({ __cpu_mask *__arr1 = (cpusetp1)->__bits; \ + __cpu_mask *__arr2 = (cpusetp2)->__bits; \ + size_t __imax = (setsize) / sizeof (__cpu_mask); \ + size_t __i; \ + for (__i = 0; __i < __imax; ++__i) \ + if (__arr1[__i] != __arr2[__i]) \ + break; \ + __i == __imax; }) + +extern int __cpuset_count_s(size_t setsize, const cpu_set_t *set); +# define CPU_COUNT_S(setsize, cpusetp) __cpuset_count_s(setsize, cpusetp) + +# define CPU_ALLOC_SIZE(count) \ + ((((count) + __NCPUBITS - 1) / __NCPUBITS) * sizeof (__cpu_mask)) +# define CPU_ALLOC(count) (malloc(CPU_ALLOC_SIZE(count))) +# define CPU_FREE(cpuset) (free(cpuset)) + +#endif /* !HAVE_DECL_CPU_ALLOC */ + + +#define cpuset_nbits(setsize) (8 * (setsize)) + +/* + * The @idx parameter returns an index of the first mask from @ary array where + * the @cpu is set. + * + * Returns: 0 if found, otherwise 1. + */ +static inline int cpuset_ary_isset(size_t cpu, cpu_set_t **ary, size_t nmemb, + size_t setsize, size_t *idx) +{ + size_t i; + + for (i = 0; i < nmemb; i++) { + if (CPU_ISSET_S(cpu, setsize, ary[i])) { + *idx = i; + return 0; + } + } + return 1; +} + +extern int get_max_number_of_cpus(void); + +extern cpu_set_t *cpuset_alloc(int ncpus, size_t *setsize, size_t *nbits); +extern void cpuset_free(cpu_set_t *set); + +extern char *cpulist_create(char *str, size_t len, cpu_set_t *set, size_t setsize); +extern int cpulist_parse(const char *str, cpu_set_t *set, size_t setsize, int fail); + +extern char *cpumask_create(char *str, size_t len, cpu_set_t *set, size_t setsize); +extern int cpumask_parse(const char *str, cpu_set_t *set, size_t setsize); + +#endif /* UTIL_LINUX_CPUSET_H */ diff --git a/utils/include/crc32.h b/utils/include/crc32.h new file mode 100644 index 0000000..2551f50 --- /dev/null +++ b/utils/include/crc32.h @@ -0,0 +1,12 @@ +#ifndef UL_NG_CRC32_H +#define UL_NG_CRC32_H + +#include +#include + +extern uint32_t ul_crc32(uint32_t seed, const unsigned char *buf, size_t len); +extern uint32_t ul_crc32_exclude_offset(uint32_t seed, const unsigned char *buf, size_t len, + size_t exclude_off, size_t exclude_len); + +#endif + diff --git a/utils/include/crc32c.h b/utils/include/crc32c.h new file mode 100644 index 0000000..1c50839 --- /dev/null +++ b/utils/include/crc32c.h @@ -0,0 +1,9 @@ +#ifndef UL_NG_CRC32C_H +#define UL_NG_CRC32C_H + +#include +#include + +extern uint32_t crc32c(uint32_t crc, const void *buf, size_t size); + +#endif /* UL_NG_CRC32C_H */ diff --git a/utils/include/debug.h b/utils/include/debug.h new file mode 100644 index 0000000..39c21d5 --- /dev/null +++ b/utils/include/debug.h @@ -0,0 +1,181 @@ +/* + * Copyright (C) 2014 Ondrej Oprala + * Copyright (C) 2014 Karel Zak + * + * This file may be distributed under the terms of the + * GNU Lesser General Public License. + */ +#ifndef UTIL_LINUX_DEBUG_H +#define UTIL_LINUX_DEBUG_H + + +/* + * util-linux debug macros + * + * The debug stuff is based on _debug_mask that controls what outputs is + * expected. The mask is usually initialized by _DEBUG= env.variable + * + * After successful initialization the flag _DEBUG_INIT is always set + * to the mask (this flag is required). The is usually library API + * prefix (e.g. MNT_) or program name (e.g. CFDISK_) + * + * In the code is possible to use + * + * DBG(FOO, ul_debug("this is output for foo")); + * + * where for the FOO has to be defined _DEBUG_FOO. + * + * It's possible to initialize the mask by comma delimited strings with + * subsystem names (e.g. "LIBMOUNT_DEBUG=options,tab"). In this case is + * necessary to define mask names array. This functionality is optional. + * + * It's strongly recommended to use UL_* macros to define/declare/use + * the debug stuff. + * + * See disk-utils/cfdisk.c: cfdisk_init_debug() for programs debug + * or libmount/src/init.c: mnt_init_debug() for library debug + * + */ + +#include +#include + +struct ul_debug_maskname { + const char *name; + int mask; + const char *help; +}; +#define UL_DEBUG_EMPTY_MASKNAMES {{ NULL, 0, NULL }} +#define UL_DEBUG_DEFINE_MASKNAMES(m) static const struct ul_debug_maskname m ## _masknames[] +#define UL_DEBUG_MASKNAMES(m) m ## _masknames + +#define UL_DEBUG_MASK(m) m ## _debug_mask +#define UL_DEBUG_DEFINE_MASK(m) int UL_DEBUG_MASK(m) +#define UL_DEBUG_DECLARE_MASK(m) extern UL_DEBUG_DEFINE_MASK(m) + +/* + * Internal mask flags (above 0xffffff) + */ +#define __UL_DEBUG_FL_NOADDR (1 << 24) /* Don't print object address */ + + +/* l - library name, p - flag prefix, m - flag postfix, x - function */ +#define __UL_DBG(l, p, m, x) \ + do { \ + if ((p ## m) & l ## _debug_mask) { \ + fprintf(stderr, "%d: %s: %8s: ", getpid(), # l, # m); \ + x; \ + } \ + } while (0) + +#define __UL_DBG_CALL(l, p, m, x) \ + do { \ + if ((p ## m) & l ## _debug_mask) { \ + x; \ + } \ + } while (0) + +#define __UL_DBG_FLUSH(l, p) \ + do { \ + if (l ## _debug_mask && \ + l ## _debug_mask != p ## INIT) { \ + fflush(stderr); \ + } \ + } while (0) + +#define __UL_INIT_DEBUG_FROM_STRING(lib, pref, mask, str) \ + do { \ + if (lib ## _debug_mask & pref ## INIT) \ + ; \ + else if (!mask && str) { \ + lib ## _debug_mask = ul_debug_parse_mask(lib ## _masknames, str); \ + } else \ + lib ## _debug_mask = mask; \ + if (lib ## _debug_mask) { \ + if (getuid() != geteuid() || getgid() != getegid()) { \ + lib ## _debug_mask |= __UL_DEBUG_FL_NOADDR; \ + fprintf(stderr, "%d: %s: don't print memory addresses (SUID executable).\n", getpid(), # lib); \ + } \ + } \ + lib ## _debug_mask |= pref ## INIT; \ + } while (0) + + +#define __UL_INIT_DEBUG_FROM_ENV(lib, pref, mask, env) \ + do { \ + const char *envstr = mask ? NULL : getenv(# env); \ + __UL_INIT_DEBUG_FROM_STRING(lib, pref, mask, envstr); \ + } while (0) + + + +static inline void __attribute__ ((__format__ (__printf__, 1, 2))) +ul_debug(const char *mesg, ...) +{ + va_list ap; + va_start(ap, mesg); + vfprintf(stderr, mesg, ap); + va_end(ap); + fputc('\n', stderr); +} + +static inline int ul_debug_parse_mask( + const struct ul_debug_maskname flagnames[], + const char *mask) +{ + int res; + char *ptr; + + /* let's check for a numeric mask first */ + res = strtoul(mask, &ptr, 0); + + /* perhaps it's a comma-separated string? */ + if (ptr && *ptr && flagnames && flagnames[0].name) { + char *msbuf, *ms, *name; + res = 0; + + ms = msbuf = strdup(mask); + if (!ms) + return res; + + while ((name = strtok_r(ms, ",", &ptr))) { + const struct ul_debug_maskname *d; + ms = ptr; + + for (d = flagnames; d && d->name; d++) { + if (strcmp(name, d->name) == 0) { + res |= d->mask; + break; + } + } + /* nothing else we can do by OR-ing the mask */ + if (res == 0xffff) + break; + } + free(msbuf); + } else if (ptr && strcmp(ptr, "all") == 0) + res = 0xffff; + + return res; +} + +static inline void ul_debug_print_masks( + const char *env, + const struct ul_debug_maskname flagnames[]) +{ + const struct ul_debug_maskname *d; + + if (!flagnames) + return; + + fprintf(stderr, "Available \"%s=[,...]|\" debug masks:\n", + env); + for (d = flagnames; d && d->name; d++) { + if (!d->help) + continue; + fprintf(stderr, " %-8s [0x%04x] : %s\n", + d->name, d->mask, d->help); + } +} + +#endif /* UTIL_LINUX_DEBUG_H */ diff --git a/utils/include/debugobj.h b/utils/include/debugobj.h new file mode 100644 index 0000000..73b70b8 --- /dev/null +++ b/utils/include/debugobj.h @@ -0,0 +1,22 @@ +#ifndef UTIL_LINUX_DEBUGOBJ_H +#define UTIL_LINUX_DEBUGOBJ_H + +/* + * Include *after* debug.h and after UL_DEBUG_CURRENT_MASK define. + */ + +static inline void __attribute__ ((__format__ (__printf__, 2, 3))) +ul_debugobj(const void *handler, const char *mesg, ...) +{ + va_list ap; + + if (handler && !(UL_DEBUG_CURRENT_MASK & __UL_DEBUG_FL_NOADDR)) + fprintf(stderr, "[%p]: ", handler); + + va_start(ap, mesg); + vfprintf(stderr, mesg, ap); + va_end(ap); + fputc('\n', stderr); +} + +#endif /* UTIL_LINUX_DEBUGOBJ_H */ diff --git a/utils/include/encode.h b/utils/include/encode.h new file mode 100644 index 0000000..b259ab5 --- /dev/null +++ b/utils/include/encode.h @@ -0,0 +1,14 @@ +#ifndef UTIL_LINUX_ENCODE_H +#define UTIL_LINUX_ENCODE_H + +extern size_t ul_encode_to_utf8(int enc, unsigned char *dest, size_t len, + const unsigned char *src, size_t count) + __attribute__((nonnull)); + +enum { + UL_ENCODE_UTF16BE = 0, + UL_ENCODE_UTF16LE, + UL_ENCODE_LATIN1 +}; + +#endif diff --git a/utils/include/env.h b/utils/include/env.h new file mode 100644 index 0000000..c2caecb --- /dev/null +++ b/utils/include/env.h @@ -0,0 +1,35 @@ +#ifndef UTIL_LINUX_ENV_H +#define UTIL_LINUX_ENV_H + +#include "c.h" +#include "nls.h" + +struct ul_env_list; + +extern void sanitize_env(void); +extern void __sanitize_env(struct ul_env_list **org); + +extern int env_list_setenv(struct ul_env_list *ls); +extern void env_list_free(struct ul_env_list *ls); + +extern char *safe_getenv(const char *arg); + + +#ifndef XSETENV_EXIT_CODE +# define XSETENV_EXIT_CODE EXIT_FAILURE +#endif + +static inline void xsetenv(char const *name, char const *val, int overwrite) +{ + if (setenv(name, val, overwrite) != 0) + err(XSETENV_EXIT_CODE, _("failed to set the %s environment variable"), name); +} + +static inline int remote_entry(char **argv, int remove, int last) +{ + memmove(argv + remove, argv + remove + 1, sizeof(char *) * (last - remove)); + return last - 1; +} + +#endif /* UTIL_LINUX_ENV_H */ + diff --git a/utils/include/exec_shell.h b/utils/include/exec_shell.h new file mode 100644 index 0000000..04aa089 --- /dev/null +++ b/utils/include/exec_shell.h @@ -0,0 +1,6 @@ +#ifndef UTIL_LINUX_EXEC_SHELL_H +#define UTIL_LINUX_EXEC_SHELL_H + +extern void exec_shell(void) __attribute__((__noreturn__)); + +#endif /* UTIL_LINUX_EXEC_SHELL_H */ diff --git a/utils/include/exitcodes.h b/utils/include/exitcodes.h new file mode 100644 index 0000000..f28f68e --- /dev/null +++ b/utils/include/exitcodes.h @@ -0,0 +1,25 @@ +#ifndef UTIL_LINUX_EXITCODES_H +#define UTIL_LINUX_EXITCODES_H +/* + * BE CAREFUL + * + * These exit codes are part of the official interface for mount, + * fsck, mkfs, etc. wrappers. + */ + +/* Exit codes used by mkfs-type programs */ +#define MKFS_EX_OK 0 /* No errors */ +#define MKFS_EX_ERROR 8 /* Operational error */ +#define MKFS_EX_USAGE 16 /* Usage or syntax error */ + +/* Exit codes used by fsck-type programs */ +#define FSCK_EX_OK 0 /* No errors */ +#define FSCK_EX_NONDESTRUCT 1 /* File system errors corrected */ +#define FSCK_EX_REBOOT 2 /* System should be rebooted */ +#define FSCK_EX_DESTRUCT FSCK_EX_REBOOT /* Alias */ +#define FSCK_EX_UNCORRECTED 4 /* File system errors left uncorrected */ +#define FSCK_EX_ERROR 8 /* Operational error */ +#define FSCK_EX_USAGE 16 /* Usage or syntax error */ +#define FSCK_EX_LIBRARY 128 /* Shared library error */ + +#endif /* UTIL_LINUX_EXITCODES_H */ diff --git a/utils/include/fileutils.h b/utils/include/fileutils.h new file mode 100644 index 0000000..479ad15 --- /dev/null +++ b/utils/include/fileutils.h @@ -0,0 +1,77 @@ +#ifndef UTIL_LINUX_FILEUTILS +#define UTIL_LINUX_FILEUTILS + +#include +#include +#include +#include +#include + +#include "c.h" + +extern int mkstemp_cloexec(char *template); + +extern int xmkstemp(char **tmpname, const char *dir, const char *prefix); + +static inline FILE *xfmkstemp(char **tmpname, const char *dir, const char *prefix) +{ + int fd; + FILE *ret; + + fd = xmkstemp(tmpname, dir, prefix); + if (fd == -1) + return NULL; + + if (!(ret = fdopen(fd, "w+" UL_CLOEXECSTR))) { + close(fd); + return NULL; + } + return ret; +} + +#ifdef HAVE_OPENAT +static inline FILE *fopen_at(int dir, const char *filename, + int flags, const char *mode) +{ + int fd = openat(dir, filename, flags); + if (fd < 0) + return NULL; + + return fdopen(fd, mode); +} +#endif + +static inline int is_same_inode(const int fd, const struct stat *st) +{ + struct stat f; + + if (fstat(fd, &f) < 0) + return 0; + else if (f.st_dev != st->st_dev || f.st_ino != st->st_ino) + return 0; + return 1; +} + +extern int dup_fd_cloexec(int oldfd, int lowfd); +extern int get_fd_tabsize(void); + +extern int mkdir_p(const char *path, mode_t mode); +extern char *stripoff_last_component(char *path); + +/* This is readdir()-like function, but skips "." and ".." directory entries */ +static inline struct dirent *xreaddir(DIR *dp) +{ + struct dirent *d; + + while ((d = readdir(dp))) { + if (!strcmp(d->d_name, ".") || + !strcmp(d->d_name, "..")) + continue; + break; + } + return d; +} + +extern void close_all_fds(const int exclude[], size_t exsz); + +#endif /* UTIL_LINUX_FILEUTILS */ diff --git a/utils/include/fuzz.h b/utils/include/fuzz.h new file mode 100644 index 0000000..1b0dbd2 --- /dev/null +++ b/utils/include/fuzz.h @@ -0,0 +1,9 @@ +#ifndef UTIL_LINUX_FUZZ_H +#define UTIL_LINUX_FUZZ_H + +#include +#include + +int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size); + +#endif /* UTIL_LINUX_FUZZ_H */ diff --git a/utils/include/idcache.h b/utils/include/idcache.h new file mode 100644 index 0000000..912edd5 --- /dev/null +++ b/utils/include/idcache.h @@ -0,0 +1,28 @@ +#ifndef UTIL_LINUX_IDCACHE_H +#define UTIL_LINUX_IDCACHE_H + +#include +#include + +#define IDCACHE_FLAGS_NAMELEN (1 << 1) + +struct identry { + unsigned long int id; + char *name; + struct identry *next; +}; + +struct idcache { + struct identry *ent; /* first entry */ + int width; /* name width */ +}; + + +extern struct idcache *new_idcache(void); +extern void add_gid(struct idcache *cache, unsigned long int id); +extern void add_uid(struct idcache *cache, unsigned long int id); + +extern void free_idcache(struct idcache *ic); +extern struct identry *get_id(struct idcache *ic, unsigned long int id); + +#endif /* UTIL_LINUX_IDCACHE_H */ diff --git a/utils/include/ismounted.h b/utils/include/ismounted.h new file mode 100644 index 0000000..57918cb --- /dev/null +++ b/utils/include/ismounted.h @@ -0,0 +1,14 @@ +#ifndef IS_MOUNTED_H +#define IS_MOUNTED_H + +#define MF_MOUNTED 1 +#define MF_ISROOT 2 +#define MF_READONLY 4 +#define MF_SWAP 8 +#define MF_BUSY 16 + +extern int is_mounted(const char *file); +extern int check_mount_point(const char *device, int *mount_flags, + char *mtpt, int mtlen); + +#endif /* IS_MOUNTED_H */ diff --git a/utils/include/iso9660.h b/utils/include/iso9660.h new file mode 100644 index 0000000..73decd9 --- /dev/null +++ b/utils/include/iso9660.h @@ -0,0 +1,58 @@ +#ifndef UTIL_LINUX_ISO_H +#define UTIL_LINUX_ISO_H + +#include + +#include "c.h" + +static inline int isonum_721(unsigned char *p) +{ + return ((p[0] & 0xff) + | ((p[1] & 0xff) << 8)); +} + +static inline int isonum_722(unsigned char *p) +{ + return ((p[1] & 0xff) + | ((p[0] & 0xff) << 8)); +} + +static inline int isonum_723(unsigned char *p, bool check_match) +{ + int le = isonum_721(p); + int be = isonum_722(p + 2); + + if (check_match && le != be) + /* translation is useless */ + warnx("723error: le=%d be=%d", le, be); + return (le); +} + +static inline int isonum_731(unsigned char *p) +{ + return ((p[0] & 0xff) + | ((p[1] & 0xff) << 8) + | ((p[2] & 0xff) << 16) + | ((p[3] & 0xff) << 24)); +} + +static inline int isonum_732(unsigned char *p) +{ + return ((p[3] & 0xff) + | ((p[2] & 0xff) << 8) + | ((p[1] & 0xff) << 16) + | ((p[0] & 0xff) << 24)); +} + +static inline int isonum_733(unsigned char *p, bool check_match) +{ + int le = isonum_731(p); + int be = isonum_732(p + 4); + + if (check_match && le != be) + /* translation is useless */ + warnx("733error: le=%d be=%d", le, be); + return(le); +} + +#endif diff --git a/utils/include/linux_version.h b/utils/include/linux_version.h new file mode 100644 index 0000000..a6a1e99 --- /dev/null +++ b/utils/include/linux_version.h @@ -0,0 +1,14 @@ +#ifndef LINUX_VERSION_H +#define LINUX_VERSION_H + +#ifdef HAVE_LINUX_VERSION_H +# include +#endif + +#ifndef KERNEL_VERSION +# define KERNEL_VERSION(a,b,c) (((a) << 16) + ((b) << 8) + (c)) +#endif + +int get_linux_version(void); + +#endif /* LINUX_VERSION_H */ diff --git a/utils/include/list.h b/utils/include/list.h new file mode 100644 index 0000000..96c84e5 --- /dev/null +++ b/utils/include/list.h @@ -0,0 +1,361 @@ +/* + * Copyright (C) 2008 Karel Zak + * Copyright (C) 1999-2008 by Theodore Ts'o + * + * This file may be redistributed under the terms of the + * GNU Lesser General Public License. + * + * (based on list.h from e2fsprogs) + * Merge sort based on kernel's implementation. + */ + +#ifndef UTIL_LINUX_LIST_H +#define UTIL_LINUX_LIST_H + +#include "c.h" + +/* TODO: use AC_C_INLINE */ +#ifdef __GNUC__ +#define _INLINE_ static __inline__ +#else /* For Watcom C */ +#define _INLINE_ static inline +#endif + +/* + * Simple doubly linked list implementation. + * + * Some of the internal functions ("__xxx") are useful when + * manipulating whole lists rather than single entries, as + * sometimes we already know the next/prev entries and we can + * generate better code by using them directly rather than + * using the generic single-entry routines. + */ + +struct list_head { + struct list_head *next, *prev; +}; + +#define INIT_LIST_HEAD(ptr) do { \ + (ptr)->next = (ptr); (ptr)->prev = (ptr); \ +} while (0) + +/* + * Insert a new entry between two known consecutive entries. + * + * This is only for internal list manipulation where we know + * the prev/next entries already! + */ +_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; +} + +/** + * list_add - add a new entry + * @add: new entry to be added + * @head: list head to add it after + * + * Insert a new entry after the specified head. + * This is good for implementing stacks. + */ +_INLINE_ void list_add(struct list_head *add, struct list_head *head) +{ + __list_add(add, head, head->next); +} + +/** + * list_add_tail - add a new entry + * @add: new entry to be added + * @head: list head to add it before + * + * Insert a new entry before the specified head. + * This is useful for implementing queues. + */ +_INLINE_ void list_add_tail(struct list_head *add, struct list_head *head) +{ + __list_add(add, head->prev, head); +} + +/* + * Delete a list entry by making the prev/next entries + * point to each other. + * + * This is only for internal list manipulation where we know + * the prev/next entries already! + */ +_INLINE_ void __list_del(struct list_head * prev, + struct list_head * next) +{ + next->prev = prev; + prev->next = next; +} + +/** + * list_del - deletes entry from list. + * @entry: the element to delete from the list. + * + * list_empty() on @entry does not return true after this, @entry is + * in an undefined state. + */ +_INLINE_ void list_del(struct list_head *entry) +{ + __list_del(entry->prev, entry->next); +} + +/** + * list_del_init - deletes entry from list and reinitialize it. + * @entry: the element to delete from the list. + */ +_INLINE_ void list_del_init(struct list_head *entry) +{ + __list_del(entry->prev, entry->next); + INIT_LIST_HEAD(entry); +} + +/** + * list_empty - tests whether a list is empty + * @head: the list to test. + */ +_INLINE_ int list_empty(struct list_head *head) +{ + return head->next == head; +} + +/** + * list_entry_is_last - tests whether is entry last in the list + * @entry: the entry to test. + * @head: the list to test. + */ +_INLINE_ int list_entry_is_last(struct list_head *entry, struct list_head *head) +{ + return head->prev == entry; +} + +/** + * list_entry_is_first - tests whether is entry first in the list + * @entry: the entry to test. + * @head: the list to test. + */ +_INLINE_ int list_entry_is_first(struct list_head *entry, struct list_head *head) +{ + return head->next == entry; +} + +/** + * list_splice - join two lists + * @list: the new list to add. + * @head: the place to add it in the first list. + */ +_INLINE_ void list_splice(struct list_head *list, struct list_head *head) +{ + struct list_head *first = list->next; + + if (first != list) { + struct list_head *last = list->prev; + struct list_head *at = head->next; + + first->prev = head; + head->next = first; + + last->next = at; + at->prev = last; + } +} + +/** + * list_entry - get the struct for this entry + * @ptr: the &struct list_head pointer. + * @type: the type of the struct this is embedded in. + * @member: the name of the list_struct within the struct. + */ +#define list_entry(ptr, type, member) container_of(ptr, type, member) + +#define list_first_entry(head, type, member) \ + ((head) && (head)->next != (head) ? list_entry((head)->next, type, member) : NULL) + +#define list_last_entry(head, type, member) \ + ((head) && (head)->prev != (head) ? list_entry((head)->prev, type, member) : NULL) + +/** + * list_for_each - iterate over elements in a list + * @pos: the &struct list_head to use as a loop counter. + * @head: the head for your list. + */ +#define list_for_each(pos, head) \ + for (pos = (head)->next; pos != (head); pos = pos->next) + +/** + * list_for_each_backwardly - iterate over elements in a list in reverse + * @pos: the &struct list_head to use as a loop counter. + * @head: the head for your list. + */ +#define list_for_each_backwardly(pos, head) \ + for (pos = (head)->prev; pos != (head); pos = pos->prev) + +/** + * list_for_each_safe - iterate over elements in a list, but don't dereference + * pos after the body is done (in case it is freed) + * @pos: the &struct list_head to use as a loop counter. + * @pnext: the &struct list_head to use as a pointer to the next item. + * @head: the head for your list (not included in iteration). + */ +#define list_for_each_safe(pos, pnext, head) \ + for (pos = (head)->next, pnext = pos->next; pos != (head); \ + pos = pnext, pnext = pos->next) + +_INLINE_ size_t list_count_entries(struct list_head *head) +{ + struct list_head *pos; + size_t ct = 0; + + list_for_each(pos, head) + ct++; + + return ct; +} + +#define MAX_LIST_LENGTH_BITS 20 + +/* + * 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. + */ +_INLINE_ struct list_head *merge(int (*cmp)(struct list_head *a, + struct list_head *b, + void *data), + void *data, + struct list_head *a, struct list_head *b) +{ + struct list_head head, *tail = &head; + + while (a && b) { + /* if equal, take 'a' -- important for sort stability */ + if ((*cmp)(a, b, data) <= 0) { + tail->next = a; + a = a->next; + } else { + tail->next = b; + b = b->next; + } + tail = tail->next; + } + tail->next = a ? a : b; + return head.next; +} + +/* + * 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. + */ +_INLINE_ void merge_and_restore_back_links(int (*cmp)(struct list_head *a, + struct list_head *b, + void *data), + void *data, + struct list_head *head, + struct list_head *a, struct list_head *b) +{ + struct list_head *tail = head; + + while (a && b) { + /* if equal, take 'a' -- important for sort stability */ + if ((*cmp)(a, b, data) <= 0) { + tail->next = a; + a->prev = tail; + a = a->next; + } else { + tail->next = b; + b->prev = tail; + b = b->next; + } + tail = tail->next; + } + tail->next = a ? a : b; + + do { + /* + * In worst cases 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. + */ + (*cmp)(tail->next, tail->next, data); + + tail->next->prev = tail; + tail = tail->next; + } while (tail->next); + + tail->next = head; + head->prev = tail; +} + + +/** + * list_sort - sort a list + * @head: the list to sort + * @cmp: the elements comparison function + * + * This function implements "merge sort", which has O(nlog(n)) + * complexity. + * + * The comparison function @cmp must return a negative value if @a + * should sort before @b, and a positive value if @a should sort after + * @b. If @a and @b are equivalent, and their original relative + * ordering is to be preserved, @cmp must return 0. + */ +_INLINE_ void list_sort(struct list_head *head, + int (*cmp)(struct list_head *a, + struct list_head *b, + void *data), + void *data) +{ + struct list_head *part[MAX_LIST_LENGTH_BITS+1]; /* sorted partial lists + -- last slot is a sentinel */ + size_t lev; /* index into part[] */ + size_t max_lev = 0; + struct list_head *list; + + if (list_empty(head)) + return; + + memset(part, 0, sizeof(part)); + + head->prev->next = NULL; + list = head->next; + + while (list) { + struct list_head *cur = list; + list = list->next; + cur->next = NULL; + + for (lev = 0; part[lev]; lev++) { + cur = merge(cmp, data, part[lev], cur); + part[lev] = NULL; + } + if (lev > max_lev) { + /* list passed to list_sort() too long for efficiency */ + if (lev >= ARRAY_SIZE(part) - 1) + lev--; + max_lev = lev; + } + part[lev] = cur; + } + + for (lev = 0; lev < max_lev; lev++) + if (part[lev]) + list = merge(cmp, data, part[lev], list); + + merge_and_restore_back_links(cmp, data, head, part[max_lev], list); +} + +#undef _INLINE_ + +#endif /* UTIL_LINUX_LIST_H */ diff --git a/utils/include/loopdev.h b/utils/include/loopdev.h new file mode 100644 index 0000000..bc68e52 --- /dev/null +++ b/utils/include/loopdev.h @@ -0,0 +1,222 @@ +#ifndef UTIL_LINUX_LOOPDEV_H +#define UTIL_LINUX_LOOPDEV_H + +#include "sysfs.h" + +/* + * loop_info.lo_encrypt_type + */ +#define LO_CRYPT_NONE 0 +#define LO_CRYPT_XOR 1 +#define LO_CRYPT_DES 2 +#define LO_CRYPT_CRYPTOAPI 18 + +/* + * loop_info.lo_file_fmt_type + */ +#define LO_FILE_FMT_RAW 0 +#define LO_FILE_FMT_QCOW 1 +#define LO_FILE_FMT_VDI 2 +#define LO_FILE_FMT_VMDK 3 + +#define LOOP_SET_FD 0x4C00 +#define LOOP_CLR_FD 0x4C01 +/* + * Obsolete (kernel < 2.6) + * + * #define LOOP_SET_STATUS 0x4C02 + * #define LOOP_GET_STATUS 0x4C03 + */ +#define LOOP_SET_STATUS64 0x4C04 +#define LOOP_GET_STATUS64 0x4C05 +/* #define LOOP_CHANGE_FD 0x4C06 */ +#define LOOP_SET_CAPACITY 0x4C07 +#define LOOP_SET_DIRECT_IO 0x4C08 +#define LOOP_SET_BLOCK_SIZE 0x4C09 + +/* /dev/loop-control interface */ +#ifndef LOOP_CTL_ADD +# define LOOP_CTL_ADD 0x4C80 +# define LOOP_CTL_REMOVE 0x4C81 +# define LOOP_CTL_GET_FREE 0x4C82 +#endif + +/* + * loop_info.lo_flags + */ +enum { + LO_FLAGS_READ_ONLY = 1, + LO_FLAGS_USE_AOPS = 2, + LO_FLAGS_AUTOCLEAR = 4, /* kernel >= 2.6.25 */ + LO_FLAGS_PARTSCAN = 8, /* kernel >= 3.2 */ + LO_FLAGS_DIRECT_IO = 16, /* kernel >= 4.2 */ +}; + +#define LO_NAME_SIZE 64 +#define LO_KEY_SIZE 32 + +/* + * Linux LOOP_{SET,GET}_STATUS64 ioctl struct + */ +struct loop_info64 { + uint64_t lo_device; + uint64_t lo_inode; + uint64_t lo_rdevice; + uint64_t lo_offset; + uint64_t lo_sizelimit; /* bytes, 0 == max available */ + uint32_t lo_number; + uint32_t lo_encrypt_type; + uint32_t lo_encrypt_key_size; + uint32_t lo_flags; + uint8_t lo_file_name[LO_NAME_SIZE]; + uint8_t lo_crypt_name[LO_NAME_SIZE]; + uint8_t lo_encrypt_key[LO_KEY_SIZE]; + uint64_t lo_init[2]; + uint32_t lo_file_fmt_type; +}; + +#define LOOPDEV_MAJOR 7 /* loop major number */ +#define LOOPDEV_DEFAULT_NNODES 8 /* default number of loop devices */ + +struct loopdev_iter { + FILE *proc; /* /proc/partitions */ + DIR *sysblock; /* /sys/block */ + int ncur; /* current position */ + int *minors; /* ary of minor numbers (when scan whole /dev) */ + int nminors; /* number of items in *minors */ + int ct_perm; /* count permission problems */ + int ct_succ; /* count number of detected devices */ + + unsigned int done:1; /* scanning done */ + unsigned int default_check:1;/* check first LOOPDEV_NLOOPS */ + int flags; /* LOOPITER_FL_* flags */ +}; + +enum { + LOOPITER_FL_FREE = (1 << 0), + LOOPITER_FL_USED = (1 << 1) +}; + +/* + * handler for work with loop devices + */ +struct loopdev_cxt { + char device[128]; /* device path (e.g. /dev/loop) */ + char *filename; /* backing file for loopcxt_set_... */ + int fd; /* open(/dev/looo) */ + int mode; /* fd mode O_{RDONLY,RDWR} */ + uint64_t blocksize; /* used by loopcxt_setup_device() */ + + int flags; /* LOOPDEV_FL_* flags */ + unsigned int has_info:1; /* .info contains data */ + unsigned int extra_check:1; /* unusual stuff for iterator */ + unsigned int info_failed:1; /* LOOP_GET_STATUS ioctl failed */ + unsigned int control_ok:1; /* /dev/loop-control success */ + + struct path_cxt *sysfs; /* pointer to /sys/dev/block// */ + struct loop_info64 info; /* for GET/SET ioctl */ + struct loopdev_iter iter; /* scans /sys or /dev for used/free devices */ +}; + +#define UL_LOOPDEVCXT_EMPTY { .fd = -1 } + +/* + * loopdev_cxt.flags + */ +enum { + LOOPDEV_FL_RDONLY = (1 << 0), /* open(/dev/loop) mode; default */ + LOOPDEV_FL_RDWR = (1 << 1), /* necessary for loop setup only */ + LOOPDEV_FL_OFFSET = (1 << 4), + LOOPDEV_FL_NOSYSFS = (1 << 5), + LOOPDEV_FL_NOIOCTL = (1 << 6), + LOOPDEV_FL_DEVSUBDIR = (1 << 7), + LOOPDEV_FL_CONTROL = (1 << 8), /* system with /dev/loop-control */ + LOOPDEV_FL_SIZELIMIT = (1 << 9) +}; + +/* + * High-level + */ +extern int loopmod_supports_partscan(void); + +extern int is_loopdev(const char *device); +extern int loopdev_is_autoclear(const char *device); + +extern char *loopdev_get_backing_file(const char *device); +extern int loopdev_is_used(const char *device, const char *filename, + uint64_t offset, uint64_t sizelimit, int flags); +extern char *loopdev_find_by_backing_file(const char *filename, + uint64_t offset, uint64_t sizelimit, int flags); +extern int loopcxt_find_unused(struct loopdev_cxt *lc); +extern int loopdev_delete(const char *device); +extern int loopdev_count_by_backing_file(const char *filename, char **loopdev); + +/* + * Low-level + */ +extern int loopcxt_init(struct loopdev_cxt *lc, int flags) + __attribute__ ((warn_unused_result)); +extern void loopcxt_deinit(struct loopdev_cxt *lc); + +extern int loopcxt_set_device(struct loopdev_cxt *lc, const char *device) + __attribute__ ((warn_unused_result)); +extern int loopcxt_has_device(struct loopdev_cxt *lc); +extern int loopcxt_add_device(struct loopdev_cxt *lc); +extern char *loopcxt_strdup_device(struct loopdev_cxt *lc); +extern const char *loopcxt_get_device(struct loopdev_cxt *lc); +extern struct loop_info64 *loopcxt_get_info(struct loopdev_cxt *lc); + +extern int loopcxt_get_fd(struct loopdev_cxt *lc); +extern int loopcxt_set_fd(struct loopdev_cxt *lc, int fd, int mode); + +extern int loopcxt_init_iterator(struct loopdev_cxt *lc, int flags); +extern int loopcxt_deinit_iterator(struct loopdev_cxt *lc); +extern int loopcxt_next(struct loopdev_cxt *lc); + +extern int loopcxt_setup_device(struct loopdev_cxt *lc); +extern int loopcxt_delete_device(struct loopdev_cxt *lc); + +extern int loopcxt_ioctl_status(struct loopdev_cxt *lc); +extern int loopcxt_ioctl_capacity(struct loopdev_cxt *lc); +extern int loopcxt_ioctl_dio(struct loopdev_cxt *lc, unsigned long use_dio); +extern int loopcxt_ioctl_blocksize(struct loopdev_cxt *lc, uint64_t blocksize); + +int loopcxt_set_offset(struct loopdev_cxt *lc, uint64_t offset); +int loopcxt_set_sizelimit(struct loopdev_cxt *lc, uint64_t sizelimit); +int loopcxt_set_blocksize(struct loopdev_cxt *lc, uint64_t blocksize); +int loopcxt_set_file_fmt_type(struct loopdev_cxt *lc, uint32_t file_fmt_type); +int loopcxt_set_flags(struct loopdev_cxt *lc, uint32_t flags); +int loopcxt_set_backing_file(struct loopdev_cxt *lc, const char *filename); + +extern char *loopcxt_get_backing_file(struct loopdev_cxt *lc); +extern int loopcxt_get_backing_devno(struct loopdev_cxt *lc, dev_t *devno); +extern int loopcxt_get_backing_inode(struct loopdev_cxt *lc, ino_t *ino); +extern int loopcxt_get_offset(struct loopdev_cxt *lc, uint64_t *offset); +extern int loopcxt_get_blocksize(struct loopdev_cxt *lc, uint64_t *blocksize); +extern int loopcxt_get_sizelimit(struct loopdev_cxt *lc, uint64_t *size); +extern int loopcxt_get_encrypt_type(struct loopdev_cxt *lc, uint32_t *type); +extern int loopcxt_get_file_fmt_type(struct loopdev_cxt *lc, uint32_t* file_fmt_type); +extern char *loopcxt_get_file_fmt_type_string(struct loopdev_cxt *lc); +extern const char *loopcxt_get_crypt_name(struct loopdev_cxt *lc); +extern int loopcxt_is_autoclear(struct loopdev_cxt *lc); +extern int loopcxt_is_readonly(struct loopdev_cxt *lc); +extern int loopcxt_is_dio(struct loopdev_cxt *lc); +extern int loopcxt_is_partscan(struct loopdev_cxt *lc); +extern int loopcxt_find_by_backing_file(struct loopdev_cxt *lc, + const char *filename, + uint64_t offset, uint64_t sizelimit, + int flags); +extern int loopcxt_find_overlap(struct loopdev_cxt *lc, + const char *filename, + uint64_t offset, uint64_t sizelimit); + +extern int loopcxt_is_used(struct loopdev_cxt *lc, + struct stat *st, + const char *backing_file, + uint64_t offset, + uint64_t sizelimit, + int flags); + +extern int parse_file_fmt_type(const char *file_fmt_type_str, uint32_t *file_fmt_type); + +#endif /* UTIL_LINUX_LOOPDEV_H */ diff --git a/utils/include/mangle.h b/utils/include/mangle.h new file mode 100644 index 0000000..08c66cb --- /dev/null +++ b/utils/include/mangle.h @@ -0,0 +1,28 @@ +#ifndef UTIL_LINUX_MANGLE_H +#define UTIL_LINUX_MANGLE_H + +/* + * Functions for \oct encoding used in mtab/fstab/swaps/etc. + */ + +extern char *mangle(const char *s); + +extern void unmangle_to_buffer(const char *s, char *buf, size_t len); +extern size_t unhexmangle_to_buffer(const char *s, char *buf, size_t len); + +extern char *unmangle(const char *s, const char **end); + +static inline void unmangle_string(char *s) +{ + if (s) + unmangle_to_buffer(s, s, strlen(s) + 1); +} + +static inline void unhexmangle_string(char *s) +{ + if (s) + unhexmangle_to_buffer(s, s, strlen(s) + 1); +} + +#endif /* UTIL_LINUX_MANGLE_H */ + diff --git a/utils/include/match.h b/utils/include/match.h new file mode 100644 index 0000000..94440c2 --- /dev/null +++ b/utils/include/match.h @@ -0,0 +1,12 @@ +/* + * Copyright (C) 2011 Karel Zak + * + * This file may be redistributed under the terms of the + * GNU Lesser General Public License. + */ +#ifndef UTIL_LINUX_MATCH_H +#define UTIL_LINUX_MATCH_H + +extern int match_fstype(const char *type, const char *pattern); + +#endif /* UTIL_LINUX_MATCH_H */ diff --git a/utils/include/mbsalign.h b/utils/include/mbsalign.h new file mode 100644 index 0000000..4f5add8 --- /dev/null +++ b/utils/include/mbsalign.h @@ -0,0 +1,66 @@ +/* Align/Truncate a string in a given screen width + Copyright (C) 2009-2010 Free Software Foundation, Inc. + Copyright (C) 2010-2013 Karel Zak + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation, either version 2.1 of the License, or + (at your option) any later version. + + This 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, see . */ +#ifndef UTIL_LINUX_MBSALIGN_H +# define UTIL_LINUX_MBSALIGN_H +# include + +typedef enum { MBS_ALIGN_LEFT, MBS_ALIGN_RIGHT, MBS_ALIGN_CENTER } mbs_align_t; + +enum { + /* Use unibyte mode for invalid multibyte strings or + or when heap memory is exhausted. */ + MBA_UNIBYTE_FALLBACK = 0x0001, + +#if 0 /* Other possible options. */ + /* Skip invalid multibyte chars rather than failing */ + MBA_IGNORE_INVALID = 0x0002, + + /* Align multibyte strings using "figure space" (\u2007) */ + MBA_USE_FIGURE_SPACE = 0x0004, + + /* Don't add any padding */ + MBA_TRUNCATE_ONLY = 0x0008, + + /* Don't truncate */ + MBA_PAD_ONLY = 0x0010, +#endif +}; + +extern size_t mbs_truncate(char *str, size_t *width); + +extern size_t mbsalign (const char *src, char *dest, + size_t dest_size, size_t *width, + mbs_align_t align, int flags); + +extern size_t mbsalign_with_padding (const char *src, char *dest, size_t dest_size, + size_t *width, mbs_align_t align, int flags, + int padchar); + +extern size_t mbs_safe_nwidth(const char *buf, size_t bufsz, size_t *sz); +extern size_t mbs_safe_width(const char *s); + +extern size_t mbs_nwidth(const char *buf, size_t bufsz); +extern size_t mbs_width(const char *s); + +extern char *mbs_safe_encode(const char *s, size_t *width); +extern char *mbs_safe_encode_to_buffer(const char *s, size_t *width, char *buf, const char *safechars); +extern size_t mbs_safe_encode_size(size_t bytes); + +extern char *mbs_invalid_encode(const char *s, size_t *width); +extern char *mbs_invalid_encode_to_buffer(const char *s, size_t *width, char *buf); + +#endif /* UTIL_LINUX_MBSALIGN_H */ diff --git a/utils/include/mbsedit.h b/utils/include/mbsedit.h new file mode 100644 index 0000000..8d1c6c2 --- /dev/null +++ b/utils/include/mbsedit.h @@ -0,0 +1,32 @@ +#ifndef UTIL_LINUX_MBSEDIT_H +# define UTIL_LINUX_MBSEDIT_H + +#include "mbsalign.h" +#include "widechar.h" + +struct mbs_editor { + char *buf; /* buffer */ + size_t max_bytes; /* size of the buffer */ + size_t max_cells; /* maximal allowed number of cells */ + size_t cur_cells; /* number of cells to print the buffer */ + size_t cur_bytes; /* number of chars in bytes */ + size_t cursor; /* cursor position in bytes */ + size_t cursor_cells; /* cursor position in cells */ +}; + +enum { + MBS_EDIT_LEFT, + MBS_EDIT_RIGHT, + MBS_EDIT_END, + MBS_EDIT_HOME +}; + +struct mbs_editor *mbs_new_edit(char *buf, size_t bufsz, size_t ncells); +char *mbs_free_edit(struct mbs_editor *edit); + +int mbs_edit_goto(struct mbs_editor *edit, int where); +int mbs_edit_delete(struct mbs_editor *edit); +int mbs_edit_backspace(struct mbs_editor *edit); +int mbs_edit_insert(struct mbs_editor *edit, wint_t c); + +#endif /* UTIL_LINUX_MBSEDIT_H */ diff --git a/utils/include/md5.h b/utils/include/md5.h new file mode 100644 index 0000000..02e627b --- /dev/null +++ b/utils/include/md5.h @@ -0,0 +1,24 @@ +#ifndef UTIL_LINUX_MD5_H +#define UTIL_LINUX_MD5_H + +#include + +#define UL_MD5LENGTH 16 + +struct UL_MD5Context { + uint32_t buf[4]; + uint32_t bits[2]; + unsigned char in[64]; +}; + +void ul_MD5Init(struct UL_MD5Context *ctx); +void ul_MD5Update(struct UL_MD5Context *ctx, unsigned char const *buf, unsigned len); +void ul_MD5Final(unsigned char digest[UL_MD5LENGTH], struct UL_MD5Context *ctx); +void ul_MD5Transform(uint32_t buf[4], uint32_t const in[16]); + +/* + * This is needed to make RSAREF happy on some MS-DOS compilers. + */ +typedef struct UL_MD5Context UL_MD5_CTX; + +#endif /* !UTIL_LINUX_MD5_H */ diff --git a/utils/include/minix.h b/utils/include/minix.h new file mode 100644 index 0000000..f28991c --- /dev/null +++ b/utils/include/minix.h @@ -0,0 +1,85 @@ +#ifndef UTIL_LINUX_MINIX_H +#define UTIL_LINUX_MINIX_H + +#include + +struct minix_inode { + uint16_t i_mode; + uint16_t i_uid; + uint32_t i_size; + uint32_t i_time; + uint8_t i_gid; + uint8_t i_nlinks; + uint16_t i_zone[9]; +}; + +struct minix2_inode { + uint16_t i_mode; + uint16_t i_nlinks; + uint16_t i_uid; + uint16_t i_gid; + uint32_t i_size; + uint32_t i_atime; + uint32_t i_mtime; + uint32_t i_ctime; + uint32_t i_zone[10]; +}; + +struct minix_super_block { + uint16_t s_ninodes; + uint16_t s_nzones; + uint16_t s_imap_blocks; + uint16_t s_zmap_blocks; + uint16_t s_firstdatazone; + uint16_t s_log_zone_size; + uint32_t s_max_size; + uint16_t s_magic; + uint16_t s_state; + uint32_t s_zones; +}; + +/* V3 minix super-block data on disk */ +struct minix3_super_block { + uint32_t s_ninodes; + uint16_t s_pad0; + uint16_t s_imap_blocks; + uint16_t s_zmap_blocks; + uint16_t s_firstdatazone; + uint16_t s_log_zone_size; + uint16_t s_pad1; + uint32_t s_max_size; + uint32_t s_zones; + uint16_t s_magic; + uint16_t s_pad2; + uint16_t s_blocksize; + uint8_t s_disk_version; +}; + +/* + * Minix subpartitions are always within primary dos partition. + */ +#define MINIX_MAXPARTITIONS 4 + +#define MINIX_BLOCK_SIZE_BITS 10 +#define MINIX_BLOCK_SIZE (1 << MINIX_BLOCK_SIZE_BITS) + +#define MINIX_NAME_MAX 255 /* # chars in a file name */ +#define MINIX_MAX_INODES 65535 + +#define MINIX_INODES_PER_BLOCK ((MINIX_BLOCK_SIZE)/(sizeof (struct minix_inode))) +#define MINIX2_INODES_PER_BLOCK ((MINIX_BLOCK_SIZE)/(sizeof (struct minix2_inode))) + +/* minix_super_block.s_state */ +#define MINIX_VALID_FS 0x0001 /* Clean fs. */ +#define MINIX_ERROR_FS 0x0002 /* fs has errors. */ + + +#define MINIX_SUPER_MAGIC 0x137F /* minix V1 fs, 14 char names */ +#define MINIX_SUPER_MAGIC2 0x138F /* minix V1 fs, 30 char names */ + +#define MINIX2_SUPER_MAGIC 0x2468 /* minix V2 fs, 14 char names */ +#define MINIX2_SUPER_MAGIC2 0x2478 /* minix V2 fs, 30 char names */ + +#define MINIX3_SUPER_MAGIC 0x4d5a /* minix V3 fs (60 char names) */ + +#endif /* UTIL_LINUX_MINIX_H */ diff --git a/utils/include/monotonic.h b/utils/include/monotonic.h new file mode 100644 index 0000000..380e59c --- /dev/null +++ b/utils/include/monotonic.h @@ -0,0 +1,22 @@ +/* + * No copyright is claimed. This code is in the public domain; do with + * it what you wish. + */ +#ifndef UTIL_LINUX_MONOTONIC_H +#define UTIL_LINUX_MONOTONIC_H + +# ifdef CLOCK_MONOTONIC_RAW +# define UL_CLOCK_MONOTONIC CLOCK_MONOTONIC_RAW +# else +# define UL_CLOCK_MONOTONIC CLOCK_MONOTONIC +# endif + +#include + +extern int get_boot_time(struct timeval *boot_time); + +extern time_t get_suspended_time(void); + +extern int gettime_monotonic(struct timeval *tv); + +#endif /* UTIL_LINUX_MONOTONIC_H */ diff --git a/utils/include/namespace.h b/utils/include/namespace.h new file mode 100644 index 0000000..2d0a56e --- /dev/null +++ b/utils/include/namespace.h @@ -0,0 +1,56 @@ + +/* + * No copyright is claimed. This code is in the public domain; do with + * it what you wish. + * + * Compat code so unshare and setns can be used with older libcs + */ +#ifndef UTIL_LINUX_NAMESPACE_H +# define UTIL_LINUX_NAMESPACE_H + +# include + +# ifndef CLONE_NEWNS +# define CLONE_NEWNS 0x00020000 +# endif +# ifndef CLONE_NEWCGROUP +# define CLONE_NEWCGROUP 0x02000000 +# endif +# ifndef CLONE_NEWUTS +# define CLONE_NEWUTS 0x04000000 +# endif +# ifndef CLONE_NEWIPC +# define CLONE_NEWIPC 0x08000000 +# endif +# ifndef CLONE_NEWNET +# define CLONE_NEWNET 0x40000000 +# endif +# ifndef CLONE_NEWUSER +# define CLONE_NEWUSER 0x10000000 +# endif +# ifndef CLONE_NEWPID +# define CLONE_NEWPID 0x20000000 +# endif +# ifndef CLONE_NEWTIME +# define CLONE_NEWTIME 0x00000080 +# endif + +# if !defined(HAVE_UNSHARE) || !defined(HAVE_SETNS) +# include +# endif + +# if !defined(HAVE_UNSHARE) && defined(SYS_unshare) +static inline int unshare(int flags) +{ + return syscall(SYS_unshare, flags); +} +# endif + +# if !defined(HAVE_SETNS) && defined(SYS_setns) +static inline int setns(int fd, int nstype) +{ + return syscall(SYS_setns, fd, nstype); +} +# endif + +#endif /* UTIL_LINUX_NAMESPACE_H */ diff --git a/utils/include/nls.h b/utils/include/nls.h new file mode 100644 index 0000000..5566908 --- /dev/null +++ b/utils/include/nls.h @@ -0,0 +1,153 @@ +#ifndef UTIL_LINUX_NLS_H +#define UTIL_LINUX_NLS_H + +#ifndef LOCALEDIR +#define LOCALEDIR "/usr/share/locale" +#endif + +#ifdef HAVE_LOCALE_H +# include +#else +# undef setlocale +# define setlocale(Category, Locale) /* empty */ +struct lconv +{ + char *decimal_point; +}; +# undef localeconv +# define localeconv() NULL +#endif + + +#ifdef ENABLE_NLS +# include +/* + * For NLS support in the public shared libraries we have to specify text + * domain name to be independent on the main program. For this purpose define + * UL_TEXTDOMAIN_EXPLICIT before you include nls.h to your shared library code. + */ +# ifdef UL_TEXTDOMAIN_EXPLICIT +# define _(Text) dgettext (UL_TEXTDOMAIN_EXPLICIT, Text) +# else +# define _(Text) gettext (Text) +# endif +# ifdef gettext_noop +# define N_(String) gettext_noop (String) +# else +# define N_(String) (String) +# endif +# define P_(Singular, Plural, n) ngettext (Singular, Plural, n) +#else +# undef bindtextdomain +# define bindtextdomain(Domain, Directory) /* empty */ +# undef textdomain +# define textdomain(Domain) /* empty */ +# define _(Text) (Text) +# define N_(Text) (Text) +# define P_(Singular, Plural, n) ((n) == 1 ? (Singular) : (Plural)) +#endif /* ENABLE_NLS */ + +#ifdef HAVE_LANGINFO_H +# include +#else + +typedef int nl_item; +extern char *langinfo_fallback(nl_item item); + +# define nl_langinfo langinfo_fallback + +enum { + CODESET = 1, + RADIXCHAR, + THOUSEP, + D_T_FMT, + D_FMT, + T_FMT, + T_FMT_AMPM, + AM_STR, + PM_STR, + + DAY_1, + DAY_2, + DAY_3, + DAY_4, + DAY_5, + DAY_6, + DAY_7, + + ABDAY_1, + ABDAY_2, + ABDAY_3, + ABDAY_4, + ABDAY_5, + ABDAY_6, + ABDAY_7, + + MON_1, + MON_2, + MON_3, + MON_4, + MON_5, + MON_6, + MON_7, + MON_8, + MON_9, + MON_10, + MON_11, + MON_12, + + ABMON_1, + ABMON_2, + ABMON_3, + ABMON_4, + ABMON_5, + ABMON_6, + ABMON_7, + ABMON_8, + ABMON_9, + ABMON_10, + ABMON_11, + ABMON_12, + + ERA_D_FMT, + ERA_D_T_FMT, + ERA_T_FMT, + ALT_DIGITS, + CRNCYSTR, + YESEXPR, + NOEXPR +}; + +#endif /* !HAVE_LANGINFO_H */ + +#ifndef HAVE_LANGINFO_ALTMON +# define ALTMON_1 MON_1 +# define ALTMON_2 MON_2 +# define ALTMON_3 MON_3 +# define ALTMON_4 MON_4 +# define ALTMON_5 MON_5 +# define ALTMON_6 MON_6 +# define ALTMON_7 MON_7 +# define ALTMON_8 MON_8 +# define ALTMON_9 MON_9 +# define ALTMON_10 MON_10 +# define ALTMON_11 MON_11 +# define ALTMON_12 MON_12 +#endif /* !HAVE_LANGINFO_ALTMON */ + +#ifndef HAVE_LANGINFO_NL_ABALTMON +# define _NL_ABALTMON_1 ABMON_1 +# define _NL_ABALTMON_2 ABMON_2 +# define _NL_ABALTMON_3 ABMON_3 +# define _NL_ABALTMON_4 ABMON_4 +# define _NL_ABALTMON_5 ABMON_5 +# define _NL_ABALTMON_6 ABMON_6 +# define _NL_ABALTMON_7 ABMON_7 +# define _NL_ABALTMON_8 ABMON_8 +# define _NL_ABALTMON_9 ABMON_9 +# define _NL_ABALTMON_10 ABMON_10 +# define _NL_ABALTMON_11 ABMON_11 +# define _NL_ABALTMON_12 ABMON_12 +#endif /* !HAVE_LANGINFO_NL_ABALTMON */ + +#endif /* UTIL_LINUX_NLS_H */ diff --git a/utils/include/optutils.h b/utils/include/optutils.h new file mode 100644 index 0000000..0dc127b --- /dev/null +++ b/utils/include/optutils.h @@ -0,0 +1,107 @@ +#ifndef UTIL_LINUX_OPTUTILS_H +#define UTIL_LINUX_OPTUTILS_H + +#include + +#include "c.h" +#include "nls.h" +#include "cctype.h" + +static inline const char *option_to_longopt(int c, const struct option *opts) +{ + const struct option *o; + + assert(!(opts == NULL)); + for (o = opts; o->name; o++) + if (o->val == c) + return o->name; + return NULL; +} + +#ifndef OPTUTILS_EXIT_CODE +# define OPTUTILS_EXIT_CODE EXIT_FAILURE +#endif + +/* + * Check collisions between options. + * + * The conflicts between options are described in ul_excl_t array. The + * array contains groups of mutually exclusive options. For example + * + * static const ul_excl_t excl[] = { + * { 'Z','b','c' }, // first group + * { 'b','x' }, // second group + * { 0 } + * }; + * + * int excl_st[ARRAY_SIZE(excl)] = UL_EXCL_STATUS_INIT; + * + * while ((c = getopt_long(argc, argv, "Zbcx", longopts, NULL)) != -1) { + * + * err_exclusive_options(c, longopts, excl, excl_st); + * + * switch (c) { + * case 'Z': + * .... + * } + * } + * + * The array excl[] defines two groups of the mutually exclusive options. The + * option '-b' is in the both groups. + * + * Note that the options in the group have to be in ASCII order (ABC..abc..) and + * groups have to be also in ASCII order. + * + * The maximal number of the options in the group is 15 (size of the array is + * 16, last is zero). + * + * The current status of options is stored in excl_st array. The size of the array + * must be the same as number of the groups in the ul_excl_t array. + * + * If you're unsure then see sys-utils/mount.c or misc-utils/findmnt.c. + */ +#define UL_EXCL_STATUS_INIT { 0 } +typedef int ul_excl_t[16]; + +static inline void err_exclusive_options( + int c, + const struct option *opts, + const ul_excl_t *excl, + int *status) +{ + int e; + + for (e = 0; excl[e][0] && excl[e][0] <= c; e++) { + const int *op = excl[e]; + + for (; *op && *op <= c; op++) { + if (*op != c) + continue; + if (status[e] == 0) + status[e] = c; + else if (status[e] != c) { + size_t ct = 0; + + fprintf(stderr, _("%s: mutually exclusive " + "arguments:"), + program_invocation_short_name); + + for (op = excl[e]; + ct + 1 < ARRAY_SIZE(excl[0]) && *op; + op++, ct++) { + const char *n = option_to_longopt(*op, opts); + if (n) + fprintf(stderr, " --%s", n); + else if (c_isgraph(*op)) + fprintf(stderr, " -%c", *op); + } + fputc('\n', stderr); + exit(OPTUTILS_EXIT_CODE); + } + break; + } + } +} + +#endif + diff --git a/utils/include/pager.h b/utils/include/pager.h new file mode 100644 index 0000000..0a7140d --- /dev/null +++ b/utils/include/pager.h @@ -0,0 +1,9 @@ +#ifndef UTIL_LINUX_PAGER +#define UTIL_LINUX_PAGER + +void pager_redirect(void); + +void pager_open(void); +void pager_close(void); + +#endif diff --git a/utils/include/partx.h b/utils/include/partx.h new file mode 100644 index 0000000..618a0a4 --- /dev/null +++ b/utils/include/partx.h @@ -0,0 +1,63 @@ +#ifndef UTIL_LINUX_PARTX_H +#define UTIL_LINUX_PARTX_H + +#include +#include +#include + +#ifndef BLKPG_ADD_PARTITION +# define BLKPG_ADD_PARTITION 1 +#endif + +#ifndef BLKPG_DEL_PARTITION +# define BLKPG_DEL_PARTITION 2 +#endif + +#ifndef BLKPG_RESIZE_PARTITION +# define BLKPG_RESIZE_PARTITION 3 /* since Linux 3.6 */ +#endif + + +#define INIT_BLKPG_PARTITION(_partno, _start, _size) { \ + .pno = (_partno), \ + .start = (_start) << 9, \ + .length = (_size) << 9, \ + .devname[0] = 0, \ + .volname[0] = 0 \ +} + +#define INIT_BLKPG_ARG(_action, _part) { \ + .op = (_action), \ + .flags = 0, \ + .datalen = sizeof(*(_part)), \ + .data = (_part) \ +} + + +static inline int partx_del_partition(int fd, unsigned int partno) +{ + struct blkpg_partition p = INIT_BLKPG_PARTITION(partno, 0, 0); + struct blkpg_ioctl_arg a = INIT_BLKPG_ARG(BLKPG_DEL_PARTITION, &p); + + return ioctl(fd, BLKPG, &a); +} + +static inline int partx_add_partition(int fd, int partno, + uint64_t start, uint64_t size) +{ + struct blkpg_partition p = INIT_BLKPG_PARTITION(partno, start, size); + struct blkpg_ioctl_arg a = INIT_BLKPG_ARG(BLKPG_ADD_PARTITION, &p); + + return ioctl(fd, BLKPG, &a); +} + +static inline int partx_resize_partition(int fd, int partno, + uint64_t start, uint64_t size) +{ + struct blkpg_partition p = INIT_BLKPG_PARTITION(partno, start, size); + struct blkpg_ioctl_arg a = INIT_BLKPG_ARG(BLKPG_RESIZE_PARTITION, &p); + + return ioctl(fd, BLKPG, &a); +} + +#endif /* UTIL_LINUX_PARTX_H */ diff --git a/utils/include/path.h b/utils/include/path.h new file mode 100644 index 0000000..2a4f80e --- /dev/null +++ b/utils/include/path.h @@ -0,0 +1,135 @@ +#ifndef UTIL_LINUX_PATH_H +#define UTIL_LINUX_PATH_H + +#include +#include +#include +#include +#include + +#include "c.h" + +struct path_cxt { + int dir_fd; + char *dir_path; + + int refcount; + + char *prefix; + char path_buffer[PATH_MAX]; + + void *dialect; + void (*free_dialect)(struct path_cxt *); + int (*redirect_on_enoent)(struct path_cxt *, const char *, int *); +}; + +struct path_cxt *ul_new_path(const char *dir, ...); +void ul_unref_path(struct path_cxt *pc); +void ul_ref_path(struct path_cxt *pc); + +void ul_path_init_debug(void); + +int ul_path_set_prefix(struct path_cxt *pc, const char *prefix); +const char *ul_path_get_prefix(struct path_cxt *pc); + +int ul_path_set_dir(struct path_cxt *pc, const char *dir); +const char *ul_path_get_dir(struct path_cxt *pc); + +int ul_path_set_dialect(struct path_cxt *pc, void *data, void free_data(struct path_cxt *)); +void *ul_path_get_dialect(struct path_cxt *pc); + +int ul_path_set_enoent_redirect(struct path_cxt *pc, int (*func)(struct path_cxt *, const char *, int *)); +int ul_path_get_dirfd(struct path_cxt *pc); +void ul_path_close_dirfd(struct path_cxt *pc); +int ul_path_isopen_dirfd(struct path_cxt *pc); +int ul_path_is_accessible(struct path_cxt *pc); + +char *ul_path_get_abspath(struct path_cxt *pc, char *buf, size_t bufsz, const char *path, ...) + __attribute__ ((__format__ (__printf__, 4, 5))); + +int ul_path_stat(struct path_cxt *pc, struct stat *sb, const char *path); +int ul_path_access(struct path_cxt *pc, int mode, const char *path); +int ul_path_accessf(struct path_cxt *pc, int mode, const char *path, ...) + __attribute__ ((__format__ (__printf__, 3, 4))); + +int ul_path_open(struct path_cxt *pc, int flags, const char *path); +int ul_path_openf(struct path_cxt *pc, int flags, const char *path, ...) + __attribute__ ((__format__ (__printf__, 3, 4))); +int ul_path_vopenf(struct path_cxt *pc, int flags, const char *path, va_list ap); + +FILE *ul_path_fopen(struct path_cxt *pc, const char *mode, const char *path); +FILE *ul_path_fopenf(struct path_cxt *pc, const char *mode, const char *path, ...) + __attribute__ ((__format__ (__printf__, 3, 4))); +FILE *ul_path_vfopenf(struct path_cxt *pc, const char *mode, const char *path, va_list ap); + +DIR *ul_path_opendir(struct path_cxt *pc, const char *path); +DIR *ul_path_vopendirf(struct path_cxt *pc, const char *path, va_list ap); +DIR *ul_path_opendirf(struct path_cxt *pc, const char *path, ...) + __attribute__ ((__format__ (__printf__, 2, 3))); + +ssize_t ul_path_readlink(struct path_cxt *pc, char *buf, size_t bufsiz, const char *path); +ssize_t ul_path_readlinkf(struct path_cxt *pc, char *buf, size_t bufsiz, const char *path, ...) + __attribute__ ((__format__ (__printf__, 4, 5))); + +int ul_path_read(struct path_cxt *pc, char *buf, size_t len, const char *path); +int ul_path_vreadf(struct path_cxt *pc, char *buf, size_t len, const char *path, va_list ap); +int ul_path_readf(struct path_cxt *pc, char *buf, size_t len, const char *path, ...) + __attribute__ ((__format__ (__printf__, 4, 5))); + +int ul_path_read_string(struct path_cxt *pc, char **str, const char *path); +int ul_path_readf_string(struct path_cxt *pc, char **str, const char *path, ...) + __attribute__ ((__format__ (__printf__, 3, 4))); + +int ul_path_read_buffer(struct path_cxt *pc, char *buf, size_t bufsz, const char *path); +int ul_path_readf_buffer(struct path_cxt *pc, char *buf, size_t bufsz, const char *path, ...) + __attribute__ ((__format__ (__printf__, 4, 5))); + +int ul_path_scanf(struct path_cxt *pc, const char *path, const char *fmt, ...); +int ul_path_scanff(struct path_cxt *pc, const char *path, va_list ap, const char *fmt, ...) + __attribute__ ((__format__ (__scanf__, 4, 5))); + +int ul_path_read_majmin(struct path_cxt *pc, dev_t *res, const char *path); +int ul_path_readf_majmin(struct path_cxt *pc, dev_t *res, const char *path, ...) + __attribute__ ((__format__ (__printf__, 3, 4))); + +int ul_path_read_u32(struct path_cxt *pc, uint32_t *res, const char *path); +int ul_path_readf_u32(struct path_cxt *pc, uint32_t *res, const char *path, ...) + __attribute__ ((__format__ (__printf__, 3, 4))); + +int ul_path_read_s32(struct path_cxt *pc, int32_t *res, const char *path); +int ul_path_readf_s32(struct path_cxt *pc, int32_t *res, const char *path, ...) + __attribute__ ((__format__ (__printf__, 3, 4))); + +int ul_path_read_u64(struct path_cxt *pc, uint64_t *res, const char *path); +int ul_path_readf_u64(struct path_cxt *pc, uint64_t *res, const char *path, ...) + __attribute__ ((__format__ (__printf__, 3, 4))); + +int ul_path_read_s64(struct path_cxt *pc, int64_t *res, const char *path); +int ul_path_readf_s64(struct path_cxt *pc, int64_t *res, const char *path, ...) + __attribute__ ((__format__ (__printf__, 3, 4))); + +int ul_path_write_string(struct path_cxt *pc, const char *str, const char *path); +int ul_path_writef_string(struct path_cxt *pc, const char *str, const char *path, ...) + __attribute__ ((__format__ (__printf__, 3, 4))); + +int ul_path_write_s64(struct path_cxt *pc, int64_t num, const char *path); +int ul_path_write_u64(struct path_cxt *pc, uint64_t num, const char *path); +int ul_path_writef_u64(struct path_cxt *pc, uint64_t num, const char *path, ...) + __attribute__ ((__format__ (__printf__, 3, 4))); + +int ul_path_count_dirents(struct path_cxt *pc, const char *path); +int ul_path_countf_dirents(struct path_cxt *pc, const char *path, ...) + __attribute__ ((__format__ (__printf__, 2, 3))); + +FILE *ul_prefix_fopen(const char *prefix, const char *path, const char *mode); + + +#ifdef HAVE_CPU_SET_T +# include "cpuset.h" +int ul_path_readf_cpuset(struct path_cxt *pc, cpu_set_t **set, int maxcpus, const char *path, ...) + __attribute__ ((__format__ (__printf__, 4, 5))); + +int ul_path_readf_cpulist(struct path_cxt *pc, cpu_set_t **set, int maxcpus, const char *path, ...) + __attribute__ ((__format__ (__printf__, 4, 5))); +#endif /* HAVE_CPU_SET_T */ +#endif /* UTIL_LINUX_PATH_H */ diff --git a/utils/include/pathnames.h b/utils/include/pathnames.h new file mode 100644 index 0000000..8f62337 --- /dev/null +++ b/utils/include/pathnames.h @@ -0,0 +1,210 @@ +/* + * No copyright is claimed. This code is in the public domain; do with + * it what you wish. + */ +#ifndef PATHNAMES_H +#define PATHNAMES_H + +#ifdef HAVE_PATHS_H +# include +#endif + +#ifndef __STDC__ +# error "we need an ANSI compiler" +#endif + +/* used by kernel in /proc (e.g. /proc/swaps) for deleted files */ +#define PATH_DELETED_SUFFIX " (deleted)" + +/* DEFPATHs from don't include /usr/local */ +#undef _PATH_DEFPATH + +#ifdef USE_USRDIR_PATHS_ONLY +# define _PATH_DEFPATH "/usr/local/bin:/usr/bin" +#else +# define _PATH_DEFPATH "/usr/local/bin:/bin:/usr/bin" +#endif + +#undef _PATH_DEFPATH_ROOT + +#ifdef USE_USRDIR_PATHS_ONLY +# define _PATH_DEFPATH_ROOT "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin" +#else +# define _PATH_DEFPATH_ROOT "/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin" +#endif + +#define _PATH_HUSHLOGIN ".hushlogin" +#define _PATH_HUSHLOGINS "/etc/hushlogins" + +#define _PATH_NOLOGIN_TXT "/etc/nologin.txt" + +#ifndef _PATH_MAILDIR +# define _PATH_MAILDIR "/var/spool/mail" +#endif +#define _PATH_MOTDFILE "/usr/share/misc/motd:/run/motd:/etc/motd" +#ifndef _PATH_NOLOGIN +# define _PATH_NOLOGIN "/etc/nologin" +#endif +#define _PATH_VAR_NOLOGIN "/var/run/nologin" + +#ifndef _PATH_LOGIN +# define _PATH_LOGIN "/bin/login" +#endif +#define _PATH_SHUTDOWN "/sbin/shutdown" +#define _PATH_POWEROFF "/sbin/poweroff" + +#define _PATH_TERMCOLORS_DIRNAME "terminal-colors.d" +#define _PATH_TERMCOLORS_DIR "/etc/" _PATH_TERMCOLORS_DIRNAME + +/* login paths */ +#define _PATH_PASSWD "/etc/passwd" +#define _PATH_GSHADOW "/etc/gshadow" +#define _PATH_GROUP "/etc/group" +#define _PATH_SHADOW_PASSWD "/etc/shadow" +#define _PATH_SHELLS "/etc/shells" + +#ifndef _PATH_TMP +# define _PATH_TMP "/tmp/" +#endif + +#ifndef _PATH_BTMP +# define _PATH_BTMP "/var/log/btmp" +#endif + +#define _PATH_ISSUE_FILENAME "issue" +#define _PATH_ISSUE_DIRNAME _PATH_ISSUE_FILENAME ".d" + +#define _PATH_ISSUE "/etc/" _PATH_ISSUE_FILENAME +#define _PATH_ISSUEDIR "/etc/" _PATH_ISSUE_DIRNAME + +#define _PATH_OS_RELEASE_ETC "/etc/os-release" +#define _PATH_OS_RELEASE_USR "/usr/lib/os-release" +#define _PATH_NUMLOCK_ON _PATH_RUNSTATEDIR "/numlock-on" +#define _PATH_LOGINDEFS "/etc/login.defs" + +/* misc paths */ +#define _PATH_WORDS "/usr/share/dict/words" +#define _PATH_WORDS_ALT "/usr/share/dict/web2" + +/* mount paths */ +#define _PATH_FILESYSTEMS "/etc/filesystems" +#define _PATH_PROC_SWAPS "/proc/swaps" +#define _PATH_PROC_FILESYSTEMS "/proc/filesystems" +#define _PATH_PROC_MOUNTS "/proc/mounts" +#define _PATH_PROC_PARTITIONS "/proc/partitions" +#define _PATH_PROC_DEVICES "/proc/devices" +#define _PATH_PROC_MOUNTINFO "/proc/self/mountinfo" +#define _PATH_PROC_LOCKS "/proc/locks" +#define _PATH_PROC_CDROMINFO "/proc/sys/dev/cdrom/info" + +#define _PATH_PROC_UIDMAP "/proc/self/uid_map" +#define _PATH_PROC_GIDMAP "/proc/self/gid_map" +#define _PATH_PROC_SETGROUPS "/proc/self/setgroups" + +#define _PATH_PROC_FDDIR "/proc/self/fd" + +#define _PATH_PROC_ATTR_CURRENT "/proc/self/attr/current" +#define _PATH_PROC_ATTR_EXEC "/proc/self/attr/exec" +#define _PATH_PROC_CAPLASTCAP "/proc/sys/kernel/cap_last_cap" + + +#define _PATH_SYS_BLOCK "/sys/block" +#define _PATH_SYS_DEVBLOCK "/sys/dev/block" +#define _PATH_SYS_DEVCHAR "/sys/dev/char" +#define _PATH_SYS_CLASS "/sys/class" +#define _PATH_SYS_SCSI "/sys/bus/scsi" + +#define _PATH_SYS_SELINUX "/sys/fs/selinux" +#define _PATH_SYS_APPARMOR "/sys/kernel/security/apparmor" + +#ifndef _PATH_MOUNTED +# ifdef MOUNTED /* deprecated */ +# define _PATH_MOUNTED MOUNTED +# else +# define _PATH_MOUNTED "/etc/mtab" +# endif +#endif + +#ifndef _PATH_MNTTAB +# ifdef MNTTAB /* deprecated */ +# define _PATH_MNTTAB MNTTAB +# else +# define _PATH_MNTTAB "/etc/fstab" +# endif +#endif + +#ifndef _PATH_DEV + /* + * The tailing '/' in _PATH_DEV is there for compatibility with libc. + */ +# define _PATH_DEV "/dev/" +#endif + +#define _PATH_DEV_MAPPER "/dev/mapper" + +#define _PATH_DEV_MEM "/dev/mem" + +#define _PATH_DEV_LOOP "/dev/loop" +#define _PATH_DEV_LOOPCTL "/dev/loop-control" + +/* udev paths */ +#define _PATH_DEV_BYLABEL "/dev/disk/by-label" +#define _PATH_DEV_BYUUID "/dev/disk/by-uuid" +#define _PATH_DEV_BYID "/dev/disk/by-id" +#define _PATH_DEV_BYPATH "/dev/disk/by-path" +#define _PATH_DEV_BYPARTLABEL "/dev/disk/by-partlabel" +#define _PATH_DEV_BYPARTUUID "/dev/disk/by-partuuid" + +/* hwclock paths */ +#ifdef CONFIG_ADJTIME_PATH +# define _PATH_ADJTIME CONFIG_ADJTIME_PATH +#else +# define _PATH_ADJTIME "/etc/adjtime" +#endif + +#ifdef __ia64__ +# define _PATH_RTC_DEV "/dev/efirtc" +#else +# define _PATH_RTC_DEV "/dev/rtc0" +#endif + +/* raw paths*/ +#define _PATH_RAWDEVDIR "/dev/raw/" +#define _PATH_RAWDEVCTL _PATH_RAWDEVDIR "rawctl" +/* deprecated */ +#define _PATH_RAWDEVCTL_OLD "/dev/rawctl" + +/* ipc paths */ +#define _PATH_PROC_SYSV_MSG "/proc/sysvipc/msg" +#define _PATH_PROC_SYSV_SEM "/proc/sysvipc/sem" +#define _PATH_PROC_SYSV_SHM "/proc/sysvipc/shm" +#define _PATH_PROC_IPC_MSGMAX "/proc/sys/kernel/msgmax" +#define _PATH_PROC_IPC_MSGMNB "/proc/sys/kernel/msgmnb" +#define _PATH_PROC_IPC_MSGMNI "/proc/sys/kernel/msgmni" +#define _PATH_PROC_IPC_SEM "/proc/sys/kernel/sem" +#define _PATH_PROC_IPC_SHMALL "/proc/sys/kernel/shmall" +#define _PATH_PROC_IPC_SHMMAX "/proc/sys/kernel/shmmax" +#define _PATH_PROC_IPC_SHMMNI "/proc/sys/kernel/shmmni" + +/* irqtop paths */ +#define _PATH_PROC_INTERRUPTS "/proc/interrupts" +#define _PATH_PROC_SOFTIRQS "/proc/softirqs" +#define _PATH_PROC_UPTIME "/proc/uptime" + +/* kernel command line */ +#define _PATH_PROC_CMDLINE "/proc/cmdline" + +/* logger paths */ +#define _PATH_DEVLOG "/dev/log" + +/* ctrlaltdel paths */ +#define _PATH_PROC_CTRL_ALT_DEL "/proc/sys/kernel/ctrl-alt-del" + +/* lscpu paths */ +#define _PATH_PROC_CPUINFO "/proc/cpuinfo" + +/* rfkill paths */ +#define _PATH_DEV_RFKILL "/dev/rfkill" +#define _PATH_SYS_RFKILL "/sys/class/rfkill" + +#endif /* PATHNAMES_H */ diff --git a/utils/include/pidfd-utils.h b/utils/include/pidfd-utils.h new file mode 100644 index 0000000..4a6c3a6 --- /dev/null +++ b/utils/include/pidfd-utils.h @@ -0,0 +1,28 @@ +#ifndef UTIL_LINUX_PIDFD_UTILS +#define UTIL_LINUX_PIDFD_UTILS + +#if defined(__linux__) +# include +# if defined(SYS_pidfd_send_signal) && defined(SYS_pidfd_open) +# include + +# ifndef HAVE_PIDFD_SEND_SIGNAL +static inline int pidfd_send_signal(int pidfd, int sig, siginfo_t *info, + unsigned int flags) +{ + return syscall(SYS_pidfd_send_signal, pidfd, sig, info, flags); +} +# endif + +# ifndef HAVE_PIDFD_OPEN +static inline int pidfd_open(pid_t pid, unsigned int flags) +{ + return syscall(SYS_pidfd_open, pid, flags); +} +# endif + +# define UL_HAVE_PIDFD 1 + +# endif /* SYS_pidfd_send_signal */ +#endif /* __linux__ */ +#endif /* UTIL_LINUX_PIDFD_UTILS */ diff --git a/utils/include/plymouth-ctrl.h b/utils/include/plymouth-ctrl.h new file mode 100644 index 0000000..b6f1299 --- /dev/null +++ b/utils/include/plymouth-ctrl.h @@ -0,0 +1,65 @@ +/* + * plymouth-ctrl.h Header file for communications with plymouthd + * + * Copyright (c) 2016 SUSE Linux GmbH, All rights reserved. + * Copyright (c) 2016 Werner Fink + * + * 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, 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 (see the file COPYING); if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, + * MA 02110-1301, USA. + * + * Author: Werner Fink + */ + +/* + * Taken from plymouth 0.9.0 src/ply-boot-protocol.h + */ + +#ifndef UTIL_LINUX_PLYMOUTH_CTRL_H +#define UTIL_LINUX_PLYMOUTH_CTRL_H + +#define PLYMOUTH_SOCKET_PATH "\0/org/freedesktop/plymouthd" +#define ANSWER_TYP '\x2' +#define ANSWER_ENQ '\x5' +#define ANSWER_ACK '\x6' +#define ANSWER_MLT '\t' +#define ANSWER_NCK '\x15' + +#define MAGIC_PRG_STOP 'A' +#define MAGIC_PRG_CONT 'a' +#define MAGIC_UPDATE 'U' +#define MAGIC_SYS_UPDATE 'u' +#define MAGIC_SYS_INIT 'S' +#define MAGIC_DEACTIVATE 'D' +#define MAGIC_REACTIVATE 'r' +#define MAGIC_SHOW_SPLASH '$' +#define MAGIC_HIDE_SPLASH 'H' +#define MAGIC_CHMOD 'C' +#define MAGIC_CHROOT 'R' +#define MAGIC_ACTIVE_VT 'V' +#define MAGIC_QUESTION 'W' +#define MAGIC_SHOW_MSG 'M' +#define MAGIC_HIDE_MSG 'm' +#define MAGIC_KEYSTROKE 'K' +#define MAGIC_KEYSTROKE_RM 'L' +#define MAGIC_PING 'P' +#define MAGIC_QUIT 'Q' +#define MAGIC_CACHED_PWD 'c' +#define MAGIC_ASK_PWD '*' +#define MAGIC_DETAILS '!' + +#define PLYMOUTH_TERMIOS_FLAGS_DELAY 30 +extern int plymouth_command(int cmd, ...); + +#endif /* UTIL_LINUX_PLYMOUTH_CTRL_H */ diff --git a/utils/include/procutils.h b/utils/include/procutils.h new file mode 100644 index 0000000..9f8dd76 --- /dev/null +++ b/utils/include/procutils.h @@ -0,0 +1,34 @@ +#ifndef UTIL_LINUX_PROCUTILS +#define UTIL_LINUX_PROCUTILS + +#include + +struct proc_tasks { + DIR *dir; +}; + +extern struct proc_tasks *proc_open_tasks(pid_t pid); +extern void proc_close_tasks(struct proc_tasks *tasks); +extern int proc_next_tid(struct proc_tasks *tasks, pid_t *tid); + +struct proc_processes { + DIR *dir; + + const char *fltr_name; + uid_t fltr_uid; + + unsigned int has_fltr_name : 1, + has_fltr_uid : 1; +}; + +extern struct proc_processes *proc_open_processes(void); +extern void proc_close_processes(struct proc_processes *ps); + +extern void proc_processes_filter_by_name(struct proc_processes *ps, const char *name); +extern void proc_processes_filter_by_uid(struct proc_processes *ps, uid_t uid); +extern int proc_next_pid(struct proc_processes *ps, pid_t *pid); + +extern char *proc_get_command(pid_t pid); +extern char *proc_get_command_name(pid_t pid); + +#endif /* UTIL_LINUX_PROCUTILS */ diff --git a/utils/include/pt-bsd.h b/utils/include/pt-bsd.h new file mode 100644 index 0000000..9bf47a5 --- /dev/null +++ b/utils/include/pt-bsd.h @@ -0,0 +1,156 @@ +#ifndef UTIL_LINUX_PT_BSD_H +#define UTIL_LINUX_PT_BSD_H + +#define BSD_MAXPARTITIONS 16 +#define BSD_FS_UNUSED 0 + +#ifndef BSD_DISKMAGIC +# define BSD_DISKMAGIC ((uint32_t) 0x82564557) +#endif + +#define BSD_LINUX_BOOTDIR "/usr/ucb/mdec" + +#if defined (__alpha__) || defined (__powerpc__) || \ + defined (__ia64__) || defined (__hppa__) +# define BSD_LABELSECTOR 0 +# define BSD_LABELOFFSET 64 +#else +# define BSD_LABELSECTOR 1 +# define BSD_LABELOFFSET 0 +#endif + +#define BSD_BBSIZE 8192 /* size of boot area, with label */ +#define BSD_SBSIZE 8192 /* max size of fs superblock */ + +struct bsd_disklabel { + uint32_t d_magic; /* the magic number */ + int16_t d_type; /* drive type */ + int16_t d_subtype; /* controller/d_type specific */ + char d_typename[16]; /* type name, e.g. "eagle" */ + char d_packname[16]; /* pack identifier */ + + /* disk geometry: */ + uint32_t d_secsize; /* # of bytes per sector */ + uint32_t d_nsectors; /* # of data sectors per track */ + uint32_t d_ntracks; /* # of tracks per cylinder */ + uint32_t d_ncylinders; /* # of data cylinders per unit */ + uint32_t d_secpercyl; /* # of data sectors per cylinder */ + uint32_t d_secperunit; /* # of data sectors per unit */ + + /* + * Spares (bad sector replacements) below + * are not counted in d_nsectors or d_secpercyl. + * Spare sectors are assumed to be physical sectors + * which occupy space at the end of each track and/or cylinder. + */ + uint16_t d_sparespertrack; /* # of spare sectors per track */ + uint16_t d_sparespercyl; /* # of spare sectors per cylinder */ + + /* + * Alternate cylinders include maintenance, replacement, + * configuration description areas, etc. + */ + uint32_t d_acylinders; /* # of alt. cylinders per unit */ + + /* hardware characteristics: */ + /* + * d_interleave, d_trackskew and d_cylskew describe perturbations + * in the media format used to compensate for a slow controller. + * Interleave is physical sector interleave, set up by the formatter + * or controller when formatting. When interleaving is in use, + * logically adjacent sectors are not physically contiguous, + * but instead are separated by some number of sectors. + * It is specified as the ratio of physical sectors traversed + * per logical sector. Thus an interleave of 1:1 implies contiguous + * layout, while 2:1 implies that logical sector 0 is separated + * by one sector from logical sector 1. + * d_trackskew is the offset of sector 0 on track N + * relative to sector 0 on track N-1 on the same cylinder. + * Finally, d_cylskew is the offset of sector 0 on cylinder N + * relative to sector 0 on cylinder N-1. + */ + uint16_t d_rpm; /* rotational speed */ + uint16_t d_interleave; /* hardware sector interleave */ + uint16_t d_trackskew; /* sector 0 skew, per track */ + uint16_t d_cylskew; /* sector 0 skew, per cylinder */ + uint32_t d_headswitch; /* head switch time, usec */ + uint32_t d_trkseek; /* track-to-track seek, usec */ + uint32_t d_flags; /* generic flags */ + uint32_t d_drivedata[5]; /* drive-type specific information */ + uint32_t d_spare[5]; /* reserved for future use */ + uint32_t d_magic2; /* the magic number (again) */ + uint16_t d_checksum; /* xor of data incl. partitions */ + + /* filesystem and partition information: */ + uint16_t d_npartitions; /* number of partitions in following */ + uint32_t d_bbsize; /* size of boot area at sn0, bytes */ + uint32_t d_sbsize; /* max size of fs superblock, bytes */ + + struct bsd_partition { /* the partition table */ + uint32_t p_size; /* number of sectors in partition */ + uint32_t p_offset; /* starting sector */ + uint32_t p_fsize; /* filesystem basic fragment size */ + uint8_t p_fstype; /* filesystem type, see below */ + uint8_t p_frag; /* filesystem fragments per block */ + uint16_t p_cpg; /* filesystem cylinders per group */ + } __attribute__((packed)) d_partitions[BSD_MAXPARTITIONS]; /* actually may be more */ +} __attribute__((packed)); + + +/* d_type values: */ +#define BSD_DTYPE_SMD 1 /* SMD, XSMD; VAX hp/up */ +#define BSD_DTYPE_MSCP 2 /* MSCP */ +#define BSD_DTYPE_DEC 3 /* other DEC (rk, rl) */ +#define BSD_DTYPE_SCSI 4 /* SCSI */ +#define BSD_DTYPE_ESDI 5 /* ESDI interface */ +#define BSD_DTYPE_ST506 6 /* ST506 etc. */ +#define BSD_DTYPE_HPIB 7 /* CS/80 on HP-IB */ +#define BSD_DTYPE_HPFL 8 /* HP Fiber-link */ +#define BSD_DTYPE_FLOPPY 10 /* floppy */ + +/* d_subtype values: */ +#define BSD_DSTYPE_INDOSPART 0x8 /* is inside dos partition */ +#define BSD_DSTYPE_DOSPART(s) ((s) & 3) /* dos partition number */ +#define BSD_DSTYPE_GEOMETRY 0x10 /* drive params in label */ + +/* + * Filesystem type and version. + * Used to interpret other filesystem-specific + * per-partition information. + */ +#define BSD_FS_UNUSED 0 /* unused */ +#define BSD_FS_SWAP 1 /* swap */ +#define BSD_FS_V6 2 /* Sixth Edition */ +#define BSD_FS_V7 3 /* Seventh Edition */ +#define BSD_FS_SYSV 4 /* System V */ +#define BSD_FS_V71K 5 /* V7 with 1K blocks (4.1, 2.9) */ +#define BSD_FS_V8 6 /* Eighth Edition, 4K blocks */ +#define BSD_FS_BSDFFS 7 /* 4.2BSD fast file system */ +#define BSD_FS_BSDLFS 9 /* 4.4BSD log-structured file system */ +#define BSD_FS_OTHER 10 /* in use, but unknown/unsupported */ +#define BSD_FS_HPFS 11 /* OS/2 high-performance file system */ +#define BSD_FS_ISO9660 12 /* ISO-9660 filesystem (cdrom) */ +#define BSD_FS_ISOFS BSD_FS_ISO9660 +#define BSD_FS_BOOT 13 /* partition contains bootstrap */ +#define BSD_FS_ADOS 14 /* AmigaDOS fast file system */ +#define BSD_FS_HFS 15 /* Macintosh HFS */ +#define BSD_FS_ADVFS 16 /* Digital Unix AdvFS */ + +/* this is annoying, but it's also the way it is :-( */ +#ifdef __alpha__ +#define BSD_FS_EXT2 8 /* ext2 file system */ +#else +#define BSD_FS_MSDOS 8 /* MS-DOS file system */ +#endif + +/* + * flags shared by various drives: + */ +#define BSD_D_REMOVABLE 0x01 /* removable media */ +#define BSD_D_ECC 0x02 /* supports ECC */ +#define BSD_D_BADSECT 0x04 /* supports bad sector forw. */ +#define BSD_D_RAMDISK 0x08 /* disk emulator */ +#define BSD_D_CHAIN 0x10 /* can do back-back transfers */ +#define BSD_D_DOSPART 0x20 /* within MSDOS partition */ + +#endif /* UTIL_LINUX_PT_BSD_H */ diff --git a/utils/include/pt-gpt-partnames.h b/utils/include/pt-gpt-partnames.h new file mode 100644 index 0000000..604f2c6 --- /dev/null +++ b/utils/include/pt-gpt-partnames.h @@ -0,0 +1,160 @@ + +/* + * No copyright is claimed. This code is in the public domain; do with + * it what you wish. + * + * Written by Karel Zak + * + * Probably the most complete list of the GUIDs are at: + * https://wikipedia.org/wiki/GUID_Partition_Table + * + * The macro DEF_GUID() has to be defined in your code according to array where + * you want to include this file. + */ + +/* Generic OS */ +DEF_GUID("C12A7328-F81F-11D2-BA4B-00A0C93EC93B", N_("EFI System")), + +DEF_GUID("024DEE41-33E7-11D3-9D69-0008C781F39F", N_("MBR partition scheme")), +DEF_GUID("D3BFE2DE-3DAF-11DF-BA40-E3A556D89593", N_("Intel Fast Flash")), + +/* Hah!IdontneedEFI */ +DEF_GUID("21686148-6449-6E6F-744E-656564454649", N_("BIOS boot")), + +/* NIH syndrome */ +DEF_GUID("F4019732-066E-4E12-8273-346C5641494F", N_("Sony boot partition")), +DEF_GUID("BFBFAFE7-A34F-448A-9A5B-6213EB736C22", N_("Lenovo boot partition")), + +/* PowerPC reference platform boot partition */ +DEF_GUID("9E1A2D38-C612-4316-AA26-8B49521E5A8B", N_("PowerPC PReP boot")), + +/* Open Network Install Environment */ +DEF_GUID("7412F7D5-A156-4B13-81DC-867174929325", N_("ONIE boot")), +DEF_GUID("D4E6E2CD-4469-46F3-B5CB-1BFF57AFC149", N_("ONIE config")), + +/* Windows */ +DEF_GUID("E3C9E316-0B5C-4DB8-817D-F92DF00215AE", N_("Microsoft reserved")), +DEF_GUID("EBD0A0A2-B9E5-4433-87C0-68B6B72699C7", N_("Microsoft basic data")), +DEF_GUID("5808C8AA-7E8F-42E0-85D2-E1E90434CFB3", N_("Microsoft LDM metadata")), +DEF_GUID("AF9B60A0-1431-4F62-BC68-3311714A69AD", N_("Microsoft LDM data")), +DEF_GUID("DE94BBA4-06D1-4D40-A16A-BFD50179D6AC", N_("Windows recovery environment")), +DEF_GUID("37AFFC90-EF7D-4E96-91C3-2D7AE055B174", N_("IBM General Parallel Fs")), +DEF_GUID("E75CAF8F-F680-4CEE-AFA3-B001E56EFC2D", N_("Microsoft Storage Spaces")), + +/* HP-UX */ +DEF_GUID("75894C1E-3AEB-11D3-B7C1-7B03A0000000", N_("HP-UX data")), +DEF_GUID("E2A1E728-32E3-11D6-A682-7B03A0000000", N_("HP-UX service")), + +/* Linux (https://systemd.io/DISCOVERABLE_PARTITIONS/) */ +DEF_GUID("0657FD6D-A4AB-43C4-84E5-0933C84B4F4F", N_("Linux swap")), +DEF_GUID("0FC63DAF-8483-4772-8E79-3D69D8477DE4", N_("Linux filesystem")), +DEF_GUID("3B8F8425-20E0-4F3B-907F-1A25A76F98E8", N_("Linux server data")), +DEF_GUID("44479540-F297-41B2-9AF7-D131D5F0458A", N_("Linux root (x86)")), +DEF_GUID("69DAD710-2CE4-4E3C-B16C-21A1D49ABED3", N_("Linux root (ARM)")), +DEF_GUID("4F68BCE3-E8CD-4DB1-96E7-FBCAF984B709", N_("Linux root (x86-64)")), +DEF_GUID("B921B045-1DF0-41C3-AF44-4C6F280D3FAE", N_("Linux root (ARM-64)")), +DEF_GUID("993D8D3D-F80E-4225-855A-9DAF8ED7EA97", N_("Linux root (IA-64)")), +DEF_GUID("8DA63339-0007-60C0-C436-083AC8230908", N_("Linux reserved")), +DEF_GUID("933AC7E1-2EB4-4F13-B844-0E14E2AEF915", N_("Linux home")), +DEF_GUID("A19D880F-05FC-4D3B-A006-743F0F84911E", N_("Linux RAID")), +DEF_GUID("E6D6D379-F507-44C2-A23C-238F2A3DF928", N_("Linux LVM")), +DEF_GUID("4D21B016-B534-45C2-A9FB-5C16E091FD2D", N_("Linux variable data")), +DEF_GUID("7EC6F557-3BC5-4ACA-B293-16EF5DF639D1", N_("Linux temporary data")), +DEF_GUID("D13C5D3B-B5D1-422A-B29F-9454FDC89D76", N_("Linux root verity (x86)")), +DEF_GUID("7386CDF2-203C-47A9-A498-F2ECCE45A2D6", N_("Linux root verity (ARM)")), +DEF_GUID("2C7357ED-EBD2-46D9-AEC1-23D437EC2BF5", N_("Linux root verity (x86-64)")), +DEF_GUID("DF3300CE-D69F-4C92-978C-9BFB0F38D820", N_("Linux root verity (ARM-64)")), +DEF_GUID("86ED10D5-B607-45BB-8957-D350F23D0571", N_("Linux root verity (IA-64)")), +/* ... too crazy, ignore for now: +DEF_GUID("7FFEC5C9-2D00-49B7-8941-3EA10A5586B7", N_("Linux plain dm-crypt")), +DEF_GUID("CA7D7CCB-63ED-4C53-861C-1742536059CC", N_("Linux LUKS")), +*/ +/* Linux https://systemd.io/BOOT_LOADER_SPECIFICATION/ */ +DEF_GUID("BC13C2FF-59E6-4262-A352-B275FD6F7172", N_("Linux extended boot")), + +/* Linux https://systemd.io/HOME_DIRECTORY/ */ +DEF_GUID("773f91ef-66d4-49b5-bd83-d683bf40ad16", N_("Linux user's home")), + +/* FreeBSD */ +DEF_GUID("516E7CB4-6ECF-11D6-8FF8-00022D09712B", N_("FreeBSD data")), +DEF_GUID("83BD6B9D-7F41-11DC-BE0B-001560B84F0F", N_("FreeBSD boot")), +DEF_GUID("516E7CB5-6ECF-11D6-8FF8-00022D09712B", N_("FreeBSD swap")), +DEF_GUID("516E7CB6-6ECF-11D6-8FF8-00022D09712B", N_("FreeBSD UFS")), +DEF_GUID("516E7CBA-6ECF-11D6-8FF8-00022D09712B", N_("FreeBSD ZFS")), +DEF_GUID("516E7CB8-6ECF-11D6-8FF8-00022D09712B", N_("FreeBSD Vinum")), + +/* Apple OSX */ +DEF_GUID("48465300-0000-11AA-AA11-00306543ECAC", N_("Apple HFS/HFS+")), +DEF_GUID("7C3457EF-0000-11AA-AA11-00306543ECAC", N_("Apple APFS")), +DEF_GUID("55465300-0000-11AA-AA11-00306543ECAC", N_("Apple UFS")), +DEF_GUID("52414944-0000-11AA-AA11-00306543ECAC", N_("Apple RAID")), +DEF_GUID("52414944-5F4F-11AA-AA11-00306543ECAC", N_("Apple RAID offline")), +DEF_GUID("426F6F74-0000-11AA-AA11-00306543ECAC", N_("Apple boot")), +DEF_GUID("4C616265-6C00-11AA-AA11-00306543ECAC", N_("Apple label")), +DEF_GUID("5265636F-7665-11AA-AA11-00306543ECAC", N_("Apple TV recovery")), +DEF_GUID("53746F72-6167-11AA-AA11-00306543ECAC", N_("Apple Core storage")), + +/* Solaris */ +DEF_GUID("6A82CB45-1DD2-11B2-99A6-080020736631", N_("Solaris boot")), +DEF_GUID("6A85CF4D-1DD2-11B2-99A6-080020736631", N_("Solaris root")), +/* same as Apple ZFS */ +DEF_GUID("6A898CC3-1DD2-11B2-99A6-080020736631", N_("Solaris /usr & Apple ZFS")), +DEF_GUID("6A87C46F-1DD2-11B2-99A6-080020736631", N_("Solaris swap")), +DEF_GUID("6A8B642B-1DD2-11B2-99A6-080020736631", N_("Solaris backup")), +DEF_GUID("6A8EF2E9-1DD2-11B2-99A6-080020736631", N_("Solaris /var")), +DEF_GUID("6A90BA39-1DD2-11B2-99A6-080020736631", N_("Solaris /home")), +DEF_GUID("6A9283A5-1DD2-11B2-99A6-080020736631", N_("Solaris alternate sector")), +DEF_GUID("6A945A3B-1DD2-11B2-99A6-080020736631", N_("Solaris reserved 1")), +DEF_GUID("6A9630D1-1DD2-11B2-99A6-080020736631", N_("Solaris reserved 2")), +DEF_GUID("6A980767-1DD2-11B2-99A6-080020736631", N_("Solaris reserved 3")), +DEF_GUID("6A96237F-1DD2-11B2-99A6-080020736631", N_("Solaris reserved 4")), +DEF_GUID("6A8D2AC7-1DD2-11B2-99A6-080020736631", N_("Solaris reserved 5")), + +/* NetBSD */ +DEF_GUID("49F48D32-B10E-11DC-B99B-0019D1879648", N_("NetBSD swap")), +DEF_GUID("49F48D5A-B10E-11DC-B99B-0019D1879648", N_("NetBSD FFS")), +DEF_GUID("49F48D82-B10E-11DC-B99B-0019D1879648", N_("NetBSD LFS")), +DEF_GUID("2DB519C4-B10E-11DC-B99B-0019D1879648", N_("NetBSD concatenated")), +DEF_GUID("2DB519EC-B10E-11DC-B99B-0019D1879648", N_("NetBSD encrypted")), +DEF_GUID("49F48DAA-B10E-11DC-B99B-0019D1879648", N_("NetBSD RAID")), + +/* ChromeOS */ +DEF_GUID("FE3A2A5D-4F32-41A7-B725-ACCC3285A309", N_("ChromeOS kernel")), +DEF_GUID("3CB8E202-3B7E-47DD-8A3C-7FF2A13CFCEC", N_("ChromeOS root fs")), +DEF_GUID("2E0A753D-9E48-43B0-8337-B15192CB1B5E", N_("ChromeOS reserved")), + +/* MidnightBSD */ +DEF_GUID("85D5E45A-237C-11E1-B4B3-E89A8F7FC3A7", N_("MidnightBSD data")), +DEF_GUID("85D5E45E-237C-11E1-B4B3-E89A8F7FC3A7", N_("MidnightBSD boot")), +DEF_GUID("85D5E45B-237C-11E1-B4B3-E89A8F7FC3A7", N_("MidnightBSD swap")), +DEF_GUID("0394EF8B-237E-11E1-B4B3-E89A8F7FC3A7", N_("MidnightBSD UFS")), +DEF_GUID("85D5E45D-237C-11E1-B4B3-E89A8F7FC3A7", N_("MidnightBSD ZFS")), +DEF_GUID("85D5E45C-237C-11E1-B4B3-E89A8F7FC3A7", N_("MidnightBSD Vinum")), + +/* Ceph */ +DEF_GUID("45B0969E-9B03-4F30-B4C6-B4B80CEFF106", N_("Ceph Journal")), +DEF_GUID("45B0969E-9B03-4F30-B4C6-5EC00CEFF106", N_("Ceph Encrypted Journal")), +DEF_GUID("4FBD7E29-9D25-41B8-AFD0-062C0CEFF05D", N_("Ceph OSD")), +DEF_GUID("4FBD7E29-9D25-41B8-AFD0-5EC00CEFF05D", N_("Ceph crypt OSD")), +DEF_GUID("89C57F98-2FE5-4DC0-89C1-F3AD0CEFF2BE", N_("Ceph disk in creation")), +DEF_GUID("89C57F98-2FE5-4DC0-89C1-5EC00CEFF2BE", N_("Ceph crypt disk in creation")), + +/* VMware */ +DEF_GUID("AA31E02A-400F-11DB-9590-000C2911D1B8", N_("VMware VMFS")), +DEF_GUID("9D275380-40AD-11DB-BF97-000C2911D1B8", N_("VMware Diagnostic")), +DEF_GUID("381CFCCC-7288-11E0-92EE-000C2911D0B2", N_("VMware Virtual SAN")), +DEF_GUID("77719A0C-A4A0-11E3-A47E-000C29745A24", N_("VMware Virsto")), +DEF_GUID("9198EFFC-31C0-11DB-8F78-000C2911D1B8", N_("VMware Reserved")), + +/* OpenBSD */ +DEF_GUID("824CC7A0-36A8-11E3-890A-952519AD3F61", N_("OpenBSD data")), + +/* QNX */ +DEF_GUID("CEF5A9AD-73BC-4601-89F3-CDEEEEE321A1", N_("QNX6 file system")), + +/* Plan 9 */ +DEF_GUID("C91818F9-8025-47AF-89D2-F030D7000C2C", N_("Plan 9 partition")), + +/* HiFive Unleased bootloaders */ +DEF_GUID("5B193300-FC78-40CD-8002-E86C45580B47", N_("HiFive Unleashed FSBL")), +DEF_GUID("2E54B353-1271-4842-806F-E436D6AF6985", N_("HiFive Unleashed BBL")), diff --git a/utils/include/pt-mbr-partnames.h b/utils/include/pt-mbr-partnames.h new file mode 100644 index 0000000..19a3450 --- /dev/null +++ b/utils/include/pt-mbr-partnames.h @@ -0,0 +1,112 @@ + {0x00, N_("Empty")}, + {0x01, N_("FAT12")}, + {0x02, N_("XENIX root")}, + {0x03, N_("XENIX usr")}, + {0x04, N_("FAT16 <32M")}, + {0x05, N_("Extended")}, /* DOS 3.3+ extended partition */ + {0x06, N_("FAT16")}, /* DOS 16-bit >=32M */ + {0x07, N_("HPFS/NTFS/exFAT")}, /* OS/2 IFS, eg, HPFS or NTFS or QNX or exFAT */ + {0x08, N_("AIX")}, /* AIX boot (AIX -- PS/2 port) or SplitDrive */ + {0x09, N_("AIX bootable")}, /* AIX data or Coherent */ + {0x0a, N_("OS/2 Boot Manager")},/* OS/2 Boot Manager */ + {0x0b, N_("W95 FAT32")}, + {0x0c, N_("W95 FAT32 (LBA)")},/* LBA really is `Extended Int 13h' */ + {0x0e, N_("W95 FAT16 (LBA)")}, + {0x0f, N_("W95 Ext'd (LBA)")}, + {0x10, N_("OPUS")}, + {0x11, N_("Hidden FAT12")}, + {0x12, N_("Compaq diagnostics")}, + {0x14, N_("Hidden FAT16 <32M")}, + {0x16, N_("Hidden FAT16")}, + {0x17, N_("Hidden HPFS/NTFS")}, + {0x18, N_("AST SmartSleep")}, + {0x1b, N_("Hidden W95 FAT32")}, + {0x1c, N_("Hidden W95 FAT32 (LBA)")}, + {0x1e, N_("Hidden W95 FAT16 (LBA)")}, + {0x24, N_("NEC DOS")}, + {0x27, N_("Hidden NTFS WinRE")}, + {0x39, N_("Plan 9")}, + {0x3c, N_("PartitionMagic recovery")}, + {0x40, N_("Venix 80286")}, + {0x41, N_("PPC PReP Boot")}, + {0x42, N_("SFS")}, + {0x4d, N_("QNX4.x")}, + {0x4e, N_("QNX4.x 2nd part")}, + {0x4f, N_("QNX4.x 3rd part")}, + {0x50, N_("OnTrack DM")}, + {0x51, N_("OnTrack DM6 Aux1")}, /* (or Novell) */ + {0x52, N_("CP/M")}, /* CP/M or Microport SysV/AT */ + {0x53, N_("OnTrack DM6 Aux3")}, + {0x54, N_("OnTrackDM6")}, + {0x55, N_("EZ-Drive")}, + {0x56, N_("Golden Bow")}, + {0x5c, N_("Priam Edisk")}, + {0x61, N_("SpeedStor")}, + {0x63, N_("GNU HURD or SysV")}, /* GNU HURD or Mach or Sys V/386 (such as ISC UNIX) */ + {0x64, N_("Novell Netware 286")}, + {0x65, N_("Novell Netware 386")}, + {0x70, N_("DiskSecure Multi-Boot")}, + {0x75, N_("PC/IX")}, + {0x80, N_("Old Minix")}, /* Minix 1.4a and earlier */ + {0x81, N_("Minix / old Linux")},/* Minix 1.4b and later */ + {0x82, N_("Linux swap / Solaris")}, + {0x83, N_("Linux")}, + {0x84, N_("OS/2 hidden or Intel hibernation")},/* OS/2 hidden C: drive, + hibernation type Microsoft APM + or hibernation Intel Rapid Start */ + {0x85, N_("Linux extended")}, + {0x86, N_("NTFS volume set")}, + {0x87, N_("NTFS volume set")}, + {0x88, N_("Linux plaintext")}, + {0x8e, N_("Linux LVM")}, + {0x93, N_("Amoeba")}, + {0x94, N_("Amoeba BBT")}, /* (bad block table) */ + {0x9f, N_("BSD/OS")}, /* BSDI */ + {0xa0, N_("IBM Thinkpad hibernation")}, + {0xa5, N_("FreeBSD")}, /* various BSD flavours */ + {0xa6, N_("OpenBSD")}, + {0xa7, N_("NeXTSTEP")}, + {0xa8, N_("Darwin UFS")}, + {0xa9, N_("NetBSD")}, + {0xab, N_("Darwin boot")}, + {0xaf, N_("HFS / HFS+")}, + {0xb7, N_("BSDI fs")}, + {0xb8, N_("BSDI swap")}, + {0xbb, N_("Boot Wizard hidden")}, + {0xbc, N_("Acronis FAT32 LBA")},/* hidden (+0xb0) Acronis Secure Zone (backup software) */ + {0xbe, N_("Solaris boot")}, + {0xbf, N_("Solaris")}, + {0xc1, N_("DRDOS/sec (FAT-12)")}, + {0xc4, N_("DRDOS/sec (FAT-16 < 32M)")}, + {0xc6, N_("DRDOS/sec (FAT-16)")}, + {0xc7, N_("Syrinx")}, + {0xda, N_("Non-FS data")}, + {0xdb, N_("CP/M / CTOS / ...")},/* CP/M or Concurrent CP/M or + Concurrent DOS or CTOS */ + {0xde, N_("Dell Utility")}, /* Dell PowerEdge Server utilities */ + {0xdf, N_("BootIt")}, /* BootIt EMBRM */ + {0xe1, N_("DOS access")}, /* DOS access or SpeedStor 12-bit FAT + extended partition */ + {0xe3, N_("DOS R/O")}, /* DOS R/O or SpeedStor */ + {0xe4, N_("SpeedStor")}, /* SpeedStor 16-bit FAT extended + partition < 1024 cyl. */ + + /* Linux https://www.freedesktop.org/wiki/Specifications/BootLoaderSpec/ */ + {0xea, N_("Linux extended boot")}, + + {0xeb, N_("BeOS fs")}, + {0xee, N_("GPT")}, /* Intel EFI GUID Partition Table */ + {0xef, N_("EFI (FAT-12/16/32)")},/* Intel EFI System Partition */ + {0xf0, N_("Linux/PA-RISC boot")},/* Linux/PA-RISC boot loader */ + {0xf1, N_("SpeedStor")}, + {0xf4, N_("SpeedStor")}, /* SpeedStor large partition */ + {0xf2, N_("DOS secondary")}, /* DOS 3.3+ secondary */ + {0xfb, N_("VMware VMFS")}, + {0xfc, N_("VMware VMKCORE")}, /* VMware kernel dump partition */ + {0xfd, N_("Linux raid autodetect")},/* Linux raid partition with + autodetect using persistent + superblock */ + {0xfe, N_("LANstep")}, /* SpeedStor >1024 cyl. or LANstep */ + {0xff, N_("BBT")}, /* Xenix Bad Block Table */ + + { 0, NULL } diff --git a/utils/include/pt-mbr.h b/utils/include/pt-mbr.h new file mode 100644 index 0000000..1a38246 --- /dev/null +++ b/utils/include/pt-mbr.h @@ -0,0 +1,187 @@ +#ifndef UTIL_LINUX_PT_MBR_H +#define UTIL_LINUX_PT_MBR_H + +#include + +struct dos_partition { + unsigned char boot_ind; /* 0x80 - active */ + unsigned char bh, bs, bc; /* begin CHS */ + unsigned char sys_ind; + unsigned char eh, es, ec; /* end CHS */ + unsigned char start_sect[4]; + unsigned char nr_sects[4]; +} __attribute__((packed)); + +#define MBR_PT_OFFSET 0x1be +#define MBR_PT_BOOTBITS_SIZE 440 + +static inline struct dos_partition *mbr_get_partition(unsigned char *mbr, int i) +{ + return (struct dos_partition *) + (mbr + MBR_PT_OFFSET + (i * sizeof(struct dos_partition))); +} + +/* assemble badly aligned little endian integer */ +static inline uint32_t __dos_assemble_4le(const unsigned char *p) +{ + uint32_t last_byte = p[3]; + + return p[0] | (p[1] << 8) | (p[2] << 16) | (last_byte << 24); +} + +static inline void __dos_store_4le(unsigned char *p, unsigned int val) +{ + assert(!(p == NULL)); + p[0] = (val & 0xff); + p[1] = ((val >> 8) & 0xff); + p[2] = ((val >> 16) & 0xff); + p[3] = ((val >> 24) & 0xff); +} + +static inline unsigned int dos_partition_get_start(struct dos_partition *p) +{ + return __dos_assemble_4le(&(p->start_sect[0])); +} + +static inline void dos_partition_set_start(struct dos_partition *p, unsigned int n) +{ + __dos_store_4le(p->start_sect, n); +} + +static inline unsigned int dos_partition_get_size(struct dos_partition *p) +{ + return __dos_assemble_4le(&(p->nr_sects[0])); +} + +static inline void dos_partition_set_size(struct dos_partition *p, unsigned int n) +{ + __dos_store_4le(p->nr_sects, n); +} + +static inline int mbr_is_valid_magic(const unsigned char *mbr) +{ + return mbr[510] == 0x55 && mbr[511] == 0xaa ? 1 : 0; +} + +static inline void mbr_set_magic(unsigned char *b) +{ + b[510] = 0x55; + b[511] = 0xaa; +} + +static inline unsigned int mbr_get_id(const unsigned char *mbr) +{ + return __dos_assemble_4le(&mbr[440]); +} + +static inline void mbr_set_id(unsigned char *b, unsigned int id) +{ + __dos_store_4le(&b[440], id); +} + +enum { + MBR_EMPTY_PARTITION = 0x00, + MBR_FAT12_PARTITION = 0x01, + MBR_XENIX_ROOT_PARTITION = 0x02, + MBR_XENIX_USR_PARTITION = 0x03, + MBR_FAT16_LESS32M_PARTITION = 0x04, + MBR_DOS_EXTENDED_PARTITION = 0x05, + MBR_FAT16_PARTITION = 0x06, /* DOS 16-bit >=32M */ + MBR_HPFS_NTFS_PARTITION = 0x07, /* OS/2 IFS, eg, HPFS or NTFS or QNX */ + MBR_AIX_PARTITION = 0x08, /* AIX boot (AIX -- PS/2 port) or SplitDrive */ + MBR_AIX_BOOTABLE_PARTITION = 0x09, /* AIX data or Coherent */ + MBR_OS2_BOOTMNGR_PARTITION = 0x0a, /* OS/2 Boot Manager */ + MBR_W95_FAT32_PARTITION = 0x0b, + MBR_W95_FAT32_LBA_PARTITION = 0x0c, /* LBA really is `Extended Int 13h' */ + MBR_W95_FAT16_LBA_PARTITION = 0x0e, + MBR_W95_EXTENDED_PARTITION = 0x0f, + MBR_OPUS_PARTITION = 0x10, + MBR_HIDDEN_FAT12_PARTITION = 0x11, + MBR_COMPAQ_DIAGNOSTICS_PARTITION = 0x12, + MBR_HIDDEN_FAT16_L32M_PARTITION = 0x14, + MBR_HIDDEN_FAT16_PARTITION = 0x16, + MBR_HIDDEN_HPFS_NTFS_PARTITION = 0x17, + MBR_AST_SMARTSLEEP_PARTITION = 0x18, + MBR_HIDDEN_W95_FAT32_PARTITION = 0x1b, + MBR_HIDDEN_W95_FAT32LBA_PARTITION = 0x1c, + MBR_HIDDEN_W95_FAT16LBA_PARTITION = 0x1e, + MBR_NEC_DOS_PARTITION = 0x24, + MBR_PLAN9_PARTITION = 0x39, + MBR_PARTITIONMAGIC_PARTITION = 0x3c, + MBR_VENIX80286_PARTITION = 0x40, + MBR_PPC_PREP_BOOT_PARTITION = 0x41, + MBR_SFS_PARTITION = 0x42, + MBR_QNX_4X_PARTITION = 0x4d, + MBR_QNX_4X_2ND_PARTITION = 0x4e, + MBR_QNX_4X_3RD_PARTITION = 0x4f, + MBR_DM_PARTITION = 0x50, + MBR_DM6_AUX1_PARTITION = 0x51, /* (or Novell) */ + MBR_CPM_PARTITION = 0x52, /* CP/M or Microport SysV/AT */ + MBR_DM6_AUX3_PARTITION = 0x53, + MBR_DM6_PARTITION = 0x54, + MBR_EZ_DRIVE_PARTITION = 0x55, + MBR_GOLDEN_BOW_PARTITION = 0x56, + MBR_PRIAM_EDISK_PARTITION = 0x5c, + MBR_SPEEDSTOR_PARTITION = 0x61, + MBR_GNU_HURD_PARTITION = 0x63, /* GNU HURD or Mach or Sys V/386 (such as ISC UNIX) */ + MBR_UNIXWARE_PARTITION = MBR_GNU_HURD_PARTITION, + MBR_NETWARE_286_PARTITION = 0x64, + MBR_NETWARE_386_PARTITION = 0x65, + MBR_DISKSECURE_MULTIBOOT_PARTITION = 0x70, + MBR_PC_IX_PARTITION = 0x75, + MBR_OLD_MINIX_PARTITION = 0x80, /* Minix 1.4a and earlier */ + MBR_MINIX_PARTITION = 0x81, /* Minix 1.4b and later */ + MBR_LINUX_SWAP_PARTITION = 0x82, + MBR_SOLARIS_X86_PARTITION = MBR_LINUX_SWAP_PARTITION, + MBR_LINUX_DATA_PARTITION = 0x83, + MBR_OS2_HIDDEN_DRIVE_PARTITION = 0x84, /* also hibernation MS APM, Intel Rapid Start */ + MBR_INTEL_HIBERNATION_PARTITION = MBR_OS2_HIDDEN_DRIVE_PARTITION, + MBR_LINUX_EXTENDED_PARTITION = 0x85, + MBR_NTFS_VOL_SET1_PARTITION = 0x86, + MBR_NTFS_VOL_SET2_PARTITION = 0x87, + MBR_LINUX_PLAINTEXT_PARTITION = 0x88, + MBR_LINUX_LVM_PARTITION = 0x8e, + MBR_AMOEBA_PARTITION = 0x93, + MBR_AMOEBA_BBT_PARTITION = 0x94, /* (bad block table) */ + MBR_BSD_OS_PARTITION = 0x9f, /* BSDI */ + MBR_THINKPAD_HIBERNATION_PARTITION = 0xa0, + MBR_FREEBSD_PARTITION = 0xa5, /* various BSD flavours */ + MBR_OPENBSD_PARTITION = 0xa6, + MBR_NEXTSTEP_PARTITION = 0xa7, + MBR_DARWIN_UFS_PARTITION = 0xa8, + MBR_NETBSD_PARTITION = 0xa9, + MBR_DARWIN_BOOT_PARTITION = 0xab, + MBR_HFS_HFS_PARTITION = 0xaf, + MBR_BSDI_FS_PARTITION = 0xb7, + MBR_BSDI_SWAP_PARTITION = 0xb8, + MBR_BOOTWIZARD_HIDDEN_PARTITION = 0xbb, + MBR_ACRONIS_FAT32LBA_PARTITION = 0xbc, /* Acronis Secure Zone with ipl for loader F11.SYS */ + MBR_SOLARIS_BOOT_PARTITION = 0xbe, + MBR_SOLARIS_PARTITION = 0xbf, + MBR_DRDOS_FAT12_PARTITION = 0xc1, + MBR_DRDOS_FAT16_L32M_PARTITION = 0xc4, + MBR_DRDOS_FAT16_PARTITION = 0xc6, + MBR_SYRINX_PARTITION = 0xc7, + MBR_NONFS_DATA_PARTITION = 0xda, + MBR_CPM_CTOS_PARTITION = 0xdb, /* CP/M or Concurrent CP/M or Concurrent DOS or CTOS */ + MBR_DELL_UTILITY_PARTITION = 0xde, /* Dell PowerEdge Server utilities */ + MBR_BOOTIT_PARTITION = 0xdf, /* BootIt EMBRM */ + MBR_DOS_ACCESS_PARTITION = 0xe1, /* DOS access or SpeedStor 12-bit FAT extended partition */ + MBR_DOS_RO_PARTITION = 0xe3, /* DOS R/O or SpeedStor */ + MBR_SPEEDSTOR_EXTENDED_PARTITION = 0xe4, /* SpeedStor 16-bit FAT extended partition < 1024 cyl. */ + MBR_RUFUS_EXTRA_PARTITION = 0xea, /* Rufus extra partition for alignment */ + MBR_BEOS_FS_PARTITION = 0xeb, + MBR_GPT_PARTITION = 0xee, /* Intel EFI GUID Partition Table */ + MBR_EFI_SYSTEM_PARTITION = 0xef, /* Intel EFI System Partition */ + MBR_LINUX_PARISC_BOOT_PARTITION = 0xf0, /* Linux/PA-RISC boot loader */ + MBR_SPEEDSTOR1_PARTITION = 0xf1, + MBR_SPEEDSTOR2_PARTITION = 0xf4, /* SpeedStor large partition */ + MBR_DOS_SECONDARY_PARTITION = 0xf2, /* DOS 3.3+ secondary */ + MBR_VMWARE_VMFS_PARTITION = 0xfb, + MBR_VMWARE_VMKCORE_PARTITION = 0xfc, /* VMware kernel dump partition */ + MBR_LINUX_RAID_PARTITION = 0xfd, /* Linux raid partition with autodetect using persistent superblock */ + MBR_LANSTEP_PARTITION = 0xfe, /* SpeedStor >1024 cyl. or LANstep */ + MBR_XENIX_BBT_PARTITION = 0xff, /* Xenix Bad Block Table */ +}; + +#endif /* UTIL_LINUX_PT_MBR_H */ diff --git a/utils/include/pt-sgi.h b/utils/include/pt-sgi.h new file mode 100644 index 0000000..6d512ee --- /dev/null +++ b/utils/include/pt-sgi.h @@ -0,0 +1,115 @@ +#ifndef UTIL_LINUX_PT_SGI_H +#define UTIL_LINUX_PT_SGI_H + +#include + +#define SGI_LABEL_MAGIC 0x0be5a941 + +#define SGI_MAXPARTITIONS 16 +#define SGI_MAXVOLUMES 15 + +/* partition types */ +enum { + SGI_TYPE_VOLHDR = 0x00, + SGI_TYPE_TRKREPL = 0x01, + SGI_TYPE_SECREPL = 0x02, + SGI_TYPE_SWAP = 0x03, + SGI_TYPE_BSD = 0x04, + SGI_TYPE_SYSV = 0x05, + SGI_TYPE_ENTIRE_DISK = 0x06, + SGI_TYPE_EFS = 0x07, + SGI_TYPE_LVOL = 0x08, + SGI_TYPE_RLVOL = 0x09, + SGI_TYPE_XFS = 0x0a, + SGI_TYPE_XFSLOG = 0x0b, + SGI_TYPE_XLV = 0x0c, + SGI_TYPE_XVM = 0x0d +}; + +struct sgi_device_parameter { + unsigned char skew; + unsigned char gap1; + unsigned char gap2; + unsigned char sparecyl; + + uint16_t pcylcount; + uint16_t head_vol0; + uint16_t ntrks; /* tracks in cyl 0 or vol 0 */ + + unsigned char cmd_tag_queue_depth; + unsigned char unused0; + + uint16_t unused1; + uint16_t nsect; /* sectors/tracks in cyl 0 or vol 0 */ + uint16_t bytes; + uint16_t ilfact; + uint32_t flags; /* SGI_DEVPARAM_* controller flags */ + uint32_t datarate; + uint32_t retries_on_error; + uint32_t ms_per_word; + uint16_t xylogics_gap1; + uint16_t xylogics_syncdelay; + uint16_t xylogics_readdelay; + uint16_t xylogics_gap2; + uint16_t xylogics_readgate; + uint16_t xylogics_writecont; +} __attribute__((packed)); + +enum { + SGI_DEVPARAM_SECTOR_SLIP = 0x01, + SGI_DEVPARAM_SECTOR_FWD = 0x02, + SGI_DEVPARAM_TRACK_FWD = 0x04, + SGI_DEVPARAM_TRACK_MULTIVOL = 0x08, + SGI_DEVPARAM_IGNORE_ERRORS = 0x10, + SGI_DEVPARAM_RESEEK = 0x20, + SGI_DEVPARAM_CMDTAGQ_ENABLE = 0x40 +}; + + +struct sgi_disklabel { + uint32_t magic; /* magic number */ + uint16_t root_part_num; /* # root partition */ + uint16_t swap_part_num; /* # swap partition */ + unsigned char boot_file[16]; /* name of boot file */ + + struct sgi_device_parameter devparam; /* not used now */ + + struct sgi_volume { + unsigned char name[8]; /* name of volume */ + uint32_t block_num; /* logical block number */ + uint32_t num_bytes; /* how big, in bytes */ + } __attribute__((packed)) volume[SGI_MAXVOLUMES]; + + struct sgi_partition { + uint32_t num_blocks; /* size in logical blocks */ + uint32_t first_block; /* first logical block */ + uint32_t type; /* type of this partition */ + } __attribute__((packed)) partitions[SGI_MAXPARTITIONS]; + + /* checksum is the 32bit 2's complement sum of the disklabel */ + uint32_t csum; /* disk label checksum */ + uint32_t padding; /* padding */ +} __attribute__((packed)); + +static inline uint32_t sgi_pt_checksum(struct sgi_disklabel *label) +{ + int count; + uint32_t sum = 0; + unsigned char *ptr = (unsigned char *) label; + + count = sizeof(*label) / sizeof(uint32_t); + ptr += sizeof(uint32_t) * (count - 1); + + while (count--) { + uint32_t val; + + memcpy(&val, ptr, sizeof(uint32_t)); + sum -= be32_to_cpu(val); + + ptr -= sizeof(uint32_t); + } + + return sum; +} + +#endif /* UTIL_LINUX_PT_SGI_H */ diff --git a/utils/include/pt-sun.h b/utils/include/pt-sun.h new file mode 100644 index 0000000..8bb5d95 --- /dev/null +++ b/utils/include/pt-sun.h @@ -0,0 +1,90 @@ +#ifndef UTIL_LINUX_PT_SUN_H +#define UTIL_LINUX_PT_SUN_H + +#include + +#define SUN_LABEL_MAGIC 0xDABE + +/* Supported VTOC setting */ +#define SUN_VTOC_SANITY 0x600DDEEE /* magic number */ +#define SUN_VTOC_VERSION 1 +#define SUN_MAXPARTITIONS 8 + +struct sun_disklabel { + unsigned char label_id[128]; /* Informative text string */ + + struct sun_vtoc { + uint32_t version; /* version */ + char volume_id[8];/* volume name */ + uint16_t nparts; /* num of partitions */ + + struct sun_info { /* partition information */ + uint16_t id; /* SUN_TAG_* */ + uint16_t flags; /* SUN_FLAG_* */ + } __attribute__ ((packed)) infos[8]; + + uint16_t padding; /* padding */ + uint32_t bootinfo[3]; /* info needed by mboot */ + uint32_t sanity; /* magic number */ + uint32_t reserved[10]; /* padding */ + uint32_t timestamp[8]; /* partition timestamp */ + } __attribute__ ((packed)) vtoc; + + uint32_t write_reinstruct; /* sectors to skip, writes */ + uint32_t read_reinstruct; /* sectors to skip, reads */ + unsigned char spare[148]; /* padding */ + uint16_t rpm; /* disk rotational speed */ + uint16_t pcyl; /* physical cylinder count */ + uint16_t apc; /* extra sects per cylinder */ + uint16_t obs1; + uint16_t obs2; + uint16_t intrlv; /* interleave factor */ + uint16_t ncyl; /* data cylinder count */ + uint16_t acyl; /* alt. cylinder count */ + uint16_t nhead; /* tracks per cylinder <---- */ + uint16_t nsect; /* sectors per track <---- */ + uint16_t obs3; + uint16_t obs4; + + struct sun_partition { /* partitions */ + uint32_t start_cylinder; + uint32_t num_sectors; + } __attribute__ ((packed)) partitions[8]; + + uint16_t magic; /* magic number */ + uint16_t csum; /* label xor'd checksum */ +} __attribute__ ((packed)); + + +#define SUN_TAG_UNASSIGNED 0x00 /* Unassigned partition */ +#define SUN_TAG_BOOT 0x01 /* Boot partition */ +#define SUN_TAG_ROOT 0x02 /* Root filesystem */ +#define SUN_TAG_SWAP 0x03 /* Swap partition */ +#define SUN_TAG_USR 0x04 /* /usr filesystem */ +#define SUN_TAG_WHOLEDISK 0x05 /* Full-disk slice */ +#define SUN_TAG_STAND 0x06 /* Stand partition */ +#define SUN_TAG_VAR 0x07 /* /var filesystem */ +#define SUN_TAG_HOME 0x08 /* /home filesystem */ +#define SUN_TAG_ALTSCTR 0x09 /* Alt sector partition */ +#define SUN_TAG_CACHE 0x0a /* Cachefs partition */ +#define SUN_TAG_RESERVED 0x0b /* SMI reserved data */ +#define SUN_TAG_LINUX_SWAP 0x82 /* Linux SWAP */ +#define SUN_TAG_LINUX_NATIVE 0x83 /* Linux filesystem */ +#define SUN_TAG_LINUX_LVM 0x8e /* Linux LVM */ +#define SUN_TAG_LINUX_RAID 0xfd /* LInux RAID */ + +#define SUN_FLAG_UNMNT 0x01 /* Unmountable partition*/ +#define SUN_FLAG_RONLY 0x10 /* Read only */ + +static inline uint16_t sun_pt_checksum(const struct sun_disklabel *label) +{ + const uint16_t *ptr = ((const uint16_t *) (label + 1)) - 1; + uint16_t sum; + + for (sum = 0; ptr >= ((const uint16_t *) label);) + sum ^= *ptr--; + + return sum; +} + +#endif /* UTIL_LINUX_PT_SUN_H */ diff --git a/utils/include/pty-session.h b/utils/include/pty-session.h new file mode 100644 index 0000000..0c9ccc6 --- /dev/null +++ b/utils/include/pty-session.h @@ -0,0 +1,110 @@ +/* + * This code is in the public domain; do with it what you wish. + * + * Written by Karel Zak in Jul 2019 + */ +#ifndef UTIL_LINUX_PTY_SESSION_H +#define UTIL_LINUX_PTY_SESSION_H + +#include +#include +#include +#include + +#include + +/* + * Callbacks -- the first argument is always callback data, see + * ul_pty_set_callback_data(). + */ +struct ul_pty_callbacks { + /* + * Optional. Executed on SIGCHLD when ssi_code is EXITED, KILLED or + * DUMPED; The callback has to call ul_pty_set_child(pty, (pid_t) -1) + * if child is no more alive. + */ + void (*child_wait)(void *, pid_t); + + /* + * Used when child_wait() undefined to informa about child status + */ + void (*child_die)(void *, pid_t, int); + + /* + * Executed on SIGCHLD when ssi_status is SIGSTOP + */ + void (*child_sigstop)(void *, pid_t); + + /* + * Executed in master loop before ul_pty enter poll() and in time set by + * ul_pty_set_mainloop_time(). The callback is no used when time is not set. + */ + int (*mainloop)(void *); + + /* + * Executed on master or stdin activity, arguments: + * 2nd - file descriptor + * 3rd - buffer with data + * 4th - size of the data + */ + int (*log_stream_activity)(void *, int, char *, size_t); + + /* + * Executed on signal, arguments: + * 2nd - signal info + * 3rd - NULL or signal specific data (e.g. struct winsize on SIGWINCH + */ + int (*log_signal)(void *, struct signalfd_siginfo *, void *); + + /* + * Executed on SIGUSR1 + */ + int (*flush_logs)(void *); +}; + +struct ul_pty { + struct termios stdin_attrs; /* stdin and slave terminal runtime attributes */ + int master; /* parent side */ + int slave; /* child side */ + int sigfd; /* signalfd() */ + int poll_timeout; + struct winsize win; /* terminal window size */ + sigset_t orgsig; /* original signal mask */ + + int delivered_signal; + + struct ul_pty_callbacks callbacks; + void *callback_data; + + pid_t child; + + struct timeval next_callback_time; + + unsigned int isterm:1, /* is stdin terminal? */ + slave_echo:1; /* keep ECHO on pty slave */ +}; + +void ul_pty_init_debug(int mask); +struct ul_pty *ul_new_pty(int is_stdin_tty); +void ul_free_pty(struct ul_pty *pty); + +void ul_pty_slave_echo(struct ul_pty *pty, int enable); +int ul_pty_get_delivered_signal(struct ul_pty *pty); + +void ul_pty_set_callback_data(struct ul_pty *pty, void *data); +void ul_pty_set_child(struct ul_pty *pty, pid_t child); + +struct ul_pty_callbacks *ul_pty_get_callbacks(struct ul_pty *pty); +int ul_pty_is_running(struct ul_pty *pty); +int ul_pty_setup(struct ul_pty *pty); +void ul_pty_cleanup(struct ul_pty *pty); +void ul_pty_init_slave(struct ul_pty *pty); +int ul_pty_proxy_master(struct ul_pty *pty); + +void ul_pty_set_mainloop_time(struct ul_pty *pty, struct timeval *tv); +int ul_pty_get_childfd(struct ul_pty *pty); +void ul_pty_wait_for_child(struct ul_pty *pty); +pid_t ul_pty_get_child(struct ul_pty *pty); +void ul_pty_write_eof_to_child(struct ul_pty *pty); + +#endif /* UTIL_LINUX_PTY_H */ diff --git a/utils/include/pwdutils.h b/utils/include/pwdutils.h new file mode 100644 index 0000000..b58268d --- /dev/null +++ b/utils/include/pwdutils.h @@ -0,0 +1,14 @@ +#ifndef UTIL_LINUX_PWDUTILS_H +#define UTIL_LINUX_PWDUTILS_H + +#include +#include +#include + +extern struct passwd *xgetpwnam(const char *username, char **pwdbuf); +extern struct group *xgetgrnam(const char *groupname, char **grpbuf); +extern struct passwd *xgetpwuid(uid_t uid, char **pwdbuf); +extern char *xgetlogin(void); + +#endif /* UTIL_LINUX_PWDUTILS_H */ + diff --git a/utils/include/randutils.h b/utils/include/randutils.h new file mode 100644 index 0000000..86e35f3 --- /dev/null +++ b/utils/include/randutils.h @@ -0,0 +1,17 @@ +#ifndef UTIL_LINUX_RANDUTILS +#define UTIL_LINUX_RANDUTILS + +#ifdef HAVE_SRANDOM +#define srand(x) srandom(x) +#define rand() random() +#endif + +/* rand() based */ +extern int rand_get_number(int low_n, int high_n); + +/* /dev/urandom based with fallback to rand() */ +extern int random_get_fd(void); +extern void random_get_bytes(void *buf, size_t nbytes); +extern const char *random_tell_source(void); + +#endif diff --git a/utils/include/rpmatch.h b/utils/include/rpmatch.h new file mode 100644 index 0000000..f64d52e --- /dev/null +++ b/utils/include/rpmatch.h @@ -0,0 +1,13 @@ +#ifndef UTIL_LINUX_RPMATCH_H +#define UTIL_LINUX_RPMATCH_H + +#ifndef HAVE_RPMATCH +#define rpmatch(r) \ + (*r == 'y' || *r == 'Y' ? 1 : *r == 'n' || *r == 'N' ? 0 : -1) +#endif + +#define RPMATCH_YES 1 +#define RPMATCH_NO 0 +#define RPMATCH_INVALID -1 + +#endif /* UTIL_LINUX_RPMATCH_H */ diff --git a/utils/include/setproctitle.h b/utils/include/setproctitle.h new file mode 100644 index 0000000..70a9efa --- /dev/null +++ b/utils/include/setproctitle.h @@ -0,0 +1,7 @@ +#ifndef UTIL_LINUX_SETPROCTITLE_H +#define UTIL_LINUX_SETPROCTITLE_H + +extern void initproctitle (int argc, char **argv); +extern void setproctitle (const char *prog, const char *txt); + +#endif diff --git a/utils/include/sha1.h b/utils/include/sha1.h new file mode 100644 index 0000000..62af1da --- /dev/null +++ b/utils/include/sha1.h @@ -0,0 +1,27 @@ +#ifndef UTIL_LINUX_SHA1_H +#define UTIL_LINUX_SHA1_H + +/* + SHA-1 in C + By Steve Reid + 100% Public Domain + */ + +#include "stdint.h" + +#define UL_SHA1LENGTH 20 + +typedef struct +{ + uint32_t state[5]; + uint32_t count[2]; + unsigned char buffer[64]; +} UL_SHA1_CTX; + +void ul_SHA1Transform(uint32_t state[5], const unsigned char buffer[64]); +void ul_SHA1Init(UL_SHA1_CTX *context); +void ul_SHA1Update(UL_SHA1_CTX *context, const unsigned char *data, uint32_t len); +void ul_SHA1Final(unsigned char digest[UL_SHA1LENGTH], UL_SHA1_CTX *context); +void ul_SHA1(char *hash_out, const char *str, unsigned len); + +#endif /* UTIL_LINUX_SHA1_H */ diff --git a/utils/include/signames.h b/utils/include/signames.h new file mode 100644 index 0000000..a4fd1bc --- /dev/null +++ b/utils/include/signames.h @@ -0,0 +1,8 @@ +#ifndef SIGNAMES_H +#define SIGNAMES_H + +int signame_to_signum(const char *sig); +const char *signum_to_signame(int signum); +int get_signame_by_idx(size_t idx, const char **signame, int *signum); + +#endif /* SIGNAMES_H */ diff --git a/utils/include/statfs_magic.h b/utils/include/statfs_magic.h new file mode 100644 index 0000000..b6b0225 --- /dev/null +++ b/utils/include/statfs_magic.h @@ -0,0 +1,100 @@ +#ifndef UTIL_LINUX_STATFS_MAGIC_H +#define UTIL_LINUX_STATFS_MAGIC_H + +#include + +/* + * If possible then don't depend on internal libc __SWORD_TYPE type. + */ +#ifdef __GNUC__ +#define F_TYPE_EQUAL(a, b) (a == (__typeof__(a)) b) +#else +#define F_TYPE_EQUAL(a, b) (a == (__SWORD_TYPE) b) +#endif + +/* + * Unfortunately, Linux kernel header file is incomplete + * mess and kernel returns by statfs f_type many numbers that are nowhere + * specified (in API). + * + * This is collection of the magic numbers. + */ +#define STATFS_ADFS_MAGIC 0xadf5 +#define STATFS_AFFS_MAGIC 0xadff +#define STATFS_AFS_MAGIC 0x5346414F +#define STATFS_AUTOFS_MAGIC 0x0187 +#define STATFS_BDEVFS_MAGIC 0x62646576 +#define STATFS_BEFS_MAGIC 0x42465331 +#define STATFS_BFS_MAGIC 0x1BADFACE +#define STATFS_BINFMTFS_MAGIC 0x42494e4d +#define STATFS_BTRFS_MAGIC 0x9123683E +#define STATFS_CEPH_MAGIC 0x00c36400 +#define STATFS_CGROUP_MAGIC 0x27e0eb +#define STATFS_CGROUP2_MAGIC 0x63677270 +#define STATFS_CIFS_MAGIC 0xff534d42 +#define STATFS_CODA_MAGIC 0x73757245 +#define STATFS_CONFIGFS_MAGIC 0x62656570 +#define STATFS_CRAMFS_MAGIC 0x28cd3d45 +#define STATFS_DEBUGFS_MAGIC 0x64626720 +#define STATFS_DEVPTS_MAGIC 0x1cd1 +#define STATFS_ECRYPTFS_MAGIC 0xf15f +#define STATFS_EFIVARFS_MAGIC 0xde5e81e4 +#define STATFS_EFS_MAGIC 0x414A53 +#define STATFS_EXOFS_MAGIC 0x5DF5 +#define STATFS_EXT2_MAGIC 0xEF53 +#define STATFS_EXT3_MAGIC 0xEF53 +#define STATFS_EXT4_MAGIC 0xEF53 +#define STATFS_F2FS_MAGIC 0xF2F52010 +#define STATFS_FUSE_MAGIC 0x65735546 +#define STATFS_FUTEXFS_MAGIC 0xBAD1DEA +#define STATFS_GFS2_MAGIC 0x01161970 +#define STATFS_HFSPLUS_MAGIC 0x482b +#define STATFS_HOSTFS_MAGIC 0x00c0ffee +#define STATFS_HPFS_MAGIC 0xf995e849 +#define STATFS_HPPFS_MAGIC 0xb00000ee +#define STATFS_HUGETLBFS_MAGIC 0x958458f6 +#define STATFS_ISOFS_MAGIC 0x9660 +#define STATFS_JFFS2_MAGIC 0x72b6 +#define STATFS_JFS_MAGIC 0x3153464a +#define STATFS_LOGFS_MAGIC 0xc97e8168 +#define STATFS_MINIX2_MAGIC 0x2468 +#define STATFS_MINIX2_MAGIC2 0x2478 +#define STATFS_MINIX3_MAGIC 0x4d5a +#define STATFS_MINIX_MAGIC 0x137F +#define STATFS_MINIX_MAGIC2 0x138F +#define STATFS_MQUEUE_MAGIC 0x19800202 +#define STATFS_MSDOS_MAGIC 0x4d44 +#define STATFS_NCP_MAGIC 0x564c +#define STATFS_NFS_MAGIC 0x6969 +#define STATFS_NILFS_MAGIC 0x3434 +#define STATFS_NTFS_MAGIC 0x5346544e +#define STATFS_OCFS2_MAGIC 0x7461636f +#define STATFS_OMFS_MAGIC 0xC2993D87 +#define STATFS_OPENPROMFS_MAGIC 0x9fa1 +#define STATFS_PIPEFS_MAGIC 0x50495045 +#define STATFS_PROC_MAGIC 0x9fa0 +#define STATFS_PSTOREFS_MAGIC 0x6165676C +#define STATFS_QNX4_MAGIC 0x002f +#define STATFS_QNX6_MAGIC 0x68191122 +#define STATFS_RAMFS_MAGIC 0x858458f6 +#define STATFS_REISERFS_MAGIC 0x52654973 +#define STATFS_ROMFS_MAGIC 0x7275 +#define STATFS_SECURITYFS_MAGIC 0x73636673 +#define STATFS_SELINUXFS_MAGIC 0xf97cff8c +#define STATFS_SMACKFS_MAGIC 0x43415d53 +#define STATFS_SMB_MAGIC 0x517B +#define STATFS_SOCKFS_MAGIC 0x534F434B +#define STATFS_SQUASHFS_MAGIC 0x73717368 +#define STATFS_SYSFS_MAGIC 0x62656572 +#define STATFS_TMPFS_MAGIC 0x01021994 +#define STATFS_UBIFS_MAGIC 0x24051905 +#define STATFS_UDF_MAGIC 0x15013346 +#define STATFS_UFS2_MAGIC 0x19540119 +#define STATFS_UFS_MAGIC 0x00011954 +#define STATFS_V9FS_MAGIC 0x01021997 +#define STATFS_VXFS_MAGIC 0xa501FCF5 +#define STATFS_XENFS_MAGIC 0xabba1974 +#define STATFS_XFS_MAGIC 0x58465342 + +#endif /* UTIL_LINUX_STATFS_MAGIC_H */ + diff --git a/utils/include/strutils.h b/utils/include/strutils.h new file mode 100644 index 0000000..4b3182f --- /dev/null +++ b/utils/include/strutils.h @@ -0,0 +1,333 @@ +#ifndef UTIL_LINUX_STRUTILS +#define UTIL_LINUX_STRUTILS + +#include +#include +#include +#include +#include +#include +#include + +/* initialize a custom exit code for all *_or_err functions */ +extern void strutils_set_exitcode(int exit_code); + +extern int parse_size(const char *str, uintmax_t *res, int *power); +extern int strtosize(const char *str, uintmax_t *res); +extern uintmax_t strtosize_or_err(const char *str, const char *errmesg); + +extern int16_t strtos16_or_err(const char *str, const char *errmesg); +extern uint16_t strtou16_or_err(const char *str, const char *errmesg); +extern uint16_t strtox16_or_err(const char *str, const char *errmesg); + +extern int32_t strtos32_or_err(const char *str, const char *errmesg); +extern uint32_t strtou32_or_err(const char *str, const char *errmesg); +extern uint32_t strtox32_or_err(const char *str, const char *errmesg); + +extern int64_t strtos64_or_err(const char *str, const char *errmesg); +extern uint64_t strtou64_or_err(const char *str, const char *errmesg); +extern uint64_t strtox64_or_err(const char *str, const char *errmesg); + +extern double strtod_or_err(const char *str, const char *errmesg); + +extern long strtol_or_err(const char *str, const char *errmesg); +extern unsigned long strtoul_or_err(const char *str, const char *errmesg); + +extern void strtotimeval_or_err(const char *str, struct timeval *tv, + const char *errmesg); + +extern int isdigit_strend(const char *str, const char **end); +#define isdigit_string(_s) isdigit_strend(_s, NULL) + +extern int isxdigit_strend(const char *str, const char **end); +#define isxdigit_string(_s) isxdigit_strend(_s, NULL) + + +extern int parse_switch(const char *arg, const char *errmesg, ...); + +#ifndef HAVE_MEMPCPY +extern void *mempcpy(void *restrict dest, const void *restrict src, size_t n); +#endif +#ifndef HAVE_STRNLEN +extern size_t strnlen(const char *s, size_t maxlen); +#endif +#ifndef HAVE_STRNDUP +extern char *strndup(const char *s, size_t n); +#endif +#ifndef HAVE_STRNCHR +extern char *strnchr(const char *s, size_t maxlen, int c); +#endif + +/* caller guarantees n > 0 */ +static inline void xstrncpy(char *dest, const char *src, size_t n) +{ + strncpy(dest, src, n-1); + dest[n-1] = 0; +} + +/* This is like strncpy(), but based on memcpy(), so compilers and static + * analyzers do not complain when sizeof(destination) is the same as 'n' and + * result is not terminated by zero. + * + * Use this function to copy string to logs with fixed sizes (wtmp/utmp. ...) + * where string terminator is optional. + */ +static inline void *str2memcpy(void *dest, const char *src, size_t n) +{ + size_t bytes = strlen(src) + 1; + + if (bytes > n) + bytes = n; + + memcpy(dest, src, bytes); + return dest; +} + +static inline char *mem2strcpy(char *dest, const void *src, size_t n, size_t nmax) +{ + if (n + 1 > nmax) + n = nmax - 1; + + memcpy(dest, src, n); + dest[nmax-1] = '\0'; + return dest; +} + +/* Reallocate @str according to @newstr and copy @newstr to @str; returns new @str. + * The @str is not modified if reallocation failed (like classic realloc()). + */ +static inline char * __attribute__((warn_unused_result)) +strrealloc(char *str, const char *newstr) +{ + size_t nsz, osz; + + if (!str) + return newstr ? strdup(newstr) : NULL; + if (!newstr) + return NULL; + + osz = strlen(str); + nsz = strlen(newstr); + + if (nsz > osz) + str = realloc(str, nsz + 1); + if (str) + memcpy(str, newstr, nsz + 1); + return str; +} + +/* Copy string @str to struct @stru to member addressed by @offset */ +static inline int strdup_to_offset(void *stru, size_t offset, const char *str) +{ + char **o; + char *p = NULL; + + if (!stru) + return -EINVAL; + + o = (char **) ((char *) stru + offset); + if (str) { + p = strdup(str); + if (!p) + return -ENOMEM; + } + + free(*o); + *o = p; + return 0; +} + +/* Copy string __str to struct member _m of the struct _s */ +#define strdup_to_struct_member(_s, _m, _str) \ + strdup_to_offset((void *) _s, offsetof(__typeof__(*(_s)), _m), _str) + +/* Copy string addressed by @offset between two structs */ +static inline int strdup_between_offsets(void *stru_dst, void *stru_src, size_t offset) +{ + char **src; + char **dst; + char *p = NULL; + + if (!stru_src || !stru_dst) + return -EINVAL; + + src = (char **) ((char *) stru_src + offset); + dst = (char **) ((char *) stru_dst + offset); + + if (*src) { + p = strdup(*src); + if (!p) + return -ENOMEM; + } + + free(*dst); + *dst = p; + return 0; +} + +/* Copy string addressed by struct member between two instances of the same + * struct type */ +#define strdup_between_structs(_dst, _src, _m) \ + strdup_between_offsets((void *)_dst, (void *)_src, offsetof(__typeof__(*(_src)), _m)) + + +extern char *xstrmode(mode_t mode, char *str); + +/* Options for size_to_human_string() */ +enum +{ + SIZE_SUFFIX_1LETTER = 0, + SIZE_SUFFIX_3LETTER = (1 << 0), + SIZE_SUFFIX_SPACE = (1 << 1), + SIZE_DECIMAL_2DIGITS = (1 << 2) +}; + +extern char *size_to_human_string(int options, uint64_t bytes); + +extern int string_to_idarray(const char *list, int ary[], size_t arysz, + int (name2id)(const char *, size_t)); +extern int string_add_to_idarray(const char *list, int ary[], + size_t arysz, size_t *ary_pos, + int (name2id)(const char *, size_t)); + +extern int string_to_bitarray(const char *list, char *ary, + int (*name2bit)(const char *, size_t)); + +extern int string_to_bitmask(const char *list, + unsigned long *mask, + long (*name2flag)(const char *, size_t)); +extern int parse_range(const char *str, int *lower, int *upper, int def); + +extern int streq_paths(const char *a, const char *b); + +/* + * Match string beginning. + */ +static inline const char *startswith(const char *s, const char *prefix) +{ + size_t sz = prefix ? strlen(prefix) : 0; + + if (s && sz && strncmp(s, prefix, sz) == 0) + return s + sz; + return NULL; +} + +/* + * Case insensitive match string beginning. + */ +static inline const char *startswith_no_case(const char *s, const char *prefix) +{ + size_t sz = prefix ? strlen(prefix) : 0; + + if (s && sz && strncasecmp(s, prefix, sz) == 0) + return s + sz; + return NULL; +} + +/* + * Match string ending. + */ +static inline const char *endswith(const char *s, const char *postfix) +{ + size_t sl = s ? strlen(s) : 0; + size_t pl = postfix ? strlen(postfix) : 0; + + if (pl == 0) + return s + sl; + if (sl < pl) + return NULL; + if (memcmp(s + sl - pl, postfix, pl) != 0) + return NULL; + return s + sl - pl; +} + +/* + * Skip leading white space. + */ +static inline const char *skip_space(const char *p) +{ + while (isspace(*p)) + ++p; + return p; +} + +static inline const char *skip_blank(const char *p) +{ + while (isblank(*p)) + ++p; + return p; +} + + +/* Removes whitespace from the right-hand side of a string (trailing + * whitespace). + * + * Returns size of the new string (without \0). + */ +static inline size_t rtrim_whitespace(unsigned char *str) +{ + size_t i; + + if (!str) + return 0; + i = strlen((char *) str); + while (i) { + i--; + if (!isspace(str[i])) { + i++; + break; + } + } + str[i] = '\0'; + return i; +} + +/* Removes whitespace from the left-hand side of a string. + * + * Returns size of the new string (without \0). + */ +static inline size_t ltrim_whitespace(unsigned char *str) +{ + size_t len; + unsigned char *p; + + if (!str) + return 0; + for (p = str; *p && isspace(*p); p++); + + len = strlen((char *) p); + + if (p > str) + memmove(str, p, len + 1); + + return len; +} + +static inline void strrep(char *s, int find, int replace) +{ + while (s && *s && (s = strchr(s, find)) != NULL) + *s++ = replace; +} + +static inline void strrem(char *s, int rem) +{ + char *p; + + if (!s) + return; + for (p = s; *s; s++) { + if (*s != rem) + *p++ = *s; + } + *p = '\0'; +} + +extern char *strnappend(const char *s, const char *suffix, size_t b); +extern char *strappend(const char *s, const char *suffix); +extern char *strfappend(const char *s, const char *format, ...) + __attribute__ ((__format__ (__printf__, 2, 0))); +extern const char *split(const char **state, size_t *l, const char *separator, int quoted); + +extern int skip_fline(FILE *fp); + +#endif diff --git a/utils/include/strv.h b/utils/include/strv.h new file mode 100644 index 0000000..260ad12 --- /dev/null +++ b/utils/include/strv.h @@ -0,0 +1,55 @@ +#ifndef UTIL_LINUX_STRV +#define UTIL_LINUX_STRV + +#include + +#include "c.h" + +char **strv_free(char **l); +void strv_clear(char **l); +char **strv_copy(char * const *l); +unsigned strv_length(char * const *l); + +int strv_extend_strv(char ***a, char **b); +int strv_extend_strv_concat(char ***a, char **b, const char *suffix); +int strv_extend(char ***l, const char *value); +int strv_extendv(char ***l, const char *format, va_list ap); +int strv_extendf(char ***l, const char *format, ...) + __attribute__ ((__format__ (__printf__, 2, 0))); +int strv_push(char ***l, char *value); +int strv_push_prepend(char ***l, char *value); +int strv_consume(char ***l, char *value); +int strv_consume_prepend(char ***l, char *value); + +char **strv_remove(char **l, const char *s); + +char **strv_new(const char *x, ...); +char **strv_new_ap(const char *x, va_list ap); + +static inline const char* STRV_IFNOTNULL(const char *x) { + return x ? x : (const char *) -1; +} + +static inline int strv_isempty(char * const *l) { + return !l || !*l; +} + +char **strv_split(const char *s, const char *separator); +char *strv_join(char **l, const char *separator); + +#define STRV_FOREACH(s, l) \ + for ((s) = (l); (s) && *(s); (s)++) + +#define STRV_FOREACH_BACKWARDS(s, l) \ + STRV_FOREACH(s, l) \ + ; \ + for ((s)--; (l) && ((s) >= (l)); (s)--) + + +#define STRV_MAKE_EMPTY ((char*[1]) { NULL }) + +char **strv_reverse(char **l); + +#endif /* UTIL_LINUX_STRV */ + + diff --git a/utils/include/swapheader.h b/utils/include/swapheader.h new file mode 100644 index 0000000..3fce0d0 --- /dev/null +++ b/utils/include/swapheader.h @@ -0,0 +1,23 @@ +#ifndef _SWAPHEADER_H +#define _SWAPHEADER_H + +#define SWAP_VERSION 1 +#define SWAP_UUID_LENGTH 16 +#define SWAP_LABEL_LENGTH 16 +#define SWAP_SIGNATURE "SWAPSPACE2" +#define SWAP_SIGNATURE_SZ (sizeof(SWAP_SIGNATURE) - 1) + +#include + +struct swap_header_v1_2 { + char bootbits[1024]; /* Space for disklabel etc. */ + uint32_t version; + uint32_t last_page; + uint32_t nr_badpages; + unsigned char uuid[SWAP_UUID_LENGTH]; + char volume_name[SWAP_LABEL_LENGTH]; + uint32_t padding[117]; + uint32_t badpages[1]; +}; + +#endif /* _SWAPHEADER_H */ diff --git a/utils/include/swapprober.h b/utils/include/swapprober.h new file mode 100644 index 0000000..5107700 --- /dev/null +++ b/utils/include/swapprober.h @@ -0,0 +1,9 @@ +#ifndef UTIL_LINUX_SWAP_PROBER_H +#define UTIL_LINUX_SWAP_PROBER_H + +#include + +blkid_probe get_swap_prober(const char *devname); + +#endif /* UTIL_LINUX_SWAP_PROBER_H */ + diff --git a/utils/include/sysfs.h b/utils/include/sysfs.h new file mode 100644 index 0000000..dcd2a14 --- /dev/null +++ b/utils/include/sysfs.h @@ -0,0 +1,113 @@ +/* + * Copyright (C) 2011 Karel Zak + */ +#ifndef UTIL_LINUX_SYSFS_H +#define UTIL_LINUX_SYSFS_H + + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "path.h" + +/** + * sysfs_devname_sys_to_dev: + * @name: devname to be converted in place + * + * Linux kernel linux/drivers/base/core.c: device_get_devnode() + * defines a replacement of '!' in the /sys device name by '/' in the + * /dev device name. This helper replaces all occurrences of '!' in + * @name by '/' to convert from /sys to /dev. + */ +static inline void sysfs_devname_sys_to_dev(char *name) +{ + char *c; + + if (name) + while ((c = strchr(name, '!'))) + c[0] = '/'; +} + +/** + * sysfs_devname_dev_to_sys: + * @name: devname to be converted in place + * + * See sysfs_devname_sys_to_dev(). + */ +static inline void sysfs_devname_dev_to_sys(char *name) +{ + char *c; + + if (name) + while ((c = strchr(name, '/'))) + c[0] = '!'; +} + +struct sysfs_blkdev { + dev_t devno; + struct path_cxt *parent; + + unsigned int scsi_host, + scsi_channel, + scsi_target, + scsi_lun; + + unsigned int has_hctl : 1, + hctl_error : 1 ; +}; + +void ul_sysfs_init_debug(void); + +struct path_cxt *ul_new_sysfs_path(dev_t devno, struct path_cxt *parent, const char *prefix); +int sysfs_blkdev_init_path(struct path_cxt *pc, dev_t devno, struct path_cxt *parent); + +int sysfs_blkdev_set_parent(struct path_cxt *pc, struct path_cxt *parent); +struct path_cxt *sysfs_blkdev_get_parent(struct path_cxt *pc); + +char *sysfs_blkdev_get_name(struct path_cxt *pc, char *buf, size_t bufsiz); +int sysfs_blkdev_is_partition_dirent(DIR *dir, struct dirent *d, const char *parent_name); +int sysfs_blkdev_count_partitions(struct path_cxt *pc, const char *devname); +dev_t sysfs_blkdev_partno_to_devno(struct path_cxt *pc, int partno); +char *sysfs_blkdev_get_slave(struct path_cxt *pc); +char *sysfs_blkdev_get_path(struct path_cxt *pc, char *buf, size_t bufsiz); +dev_t sysfs_blkdev_get_devno(struct path_cxt *pc); + +char *sysfs_blkdev_get_devchain(struct path_cxt *pc, char *buf, size_t bufsz); +int sysfs_blkdev_next_subsystem(struct path_cxt *pc __attribute__((unused)), char *devchain, char **subsys); + +int sysfs_blkdev_is_hotpluggable(struct path_cxt *pc); +int sysfs_blkdev_get_wholedisk( struct path_cxt *pc, + char *diskname, + size_t len, + dev_t *diskdevno); + +int sysfs_devno_to_wholedisk(dev_t dev, char *diskname, + size_t len, dev_t *diskdevno); +int sysfs_devno_is_dm_private(dev_t devno, char **uuid); +int sysfs_devno_is_wholedisk(dev_t devno); + +dev_t sysfs_devname_to_devno(const char *name); +dev_t __sysfs_devname_to_devno(const char *prefix, const char *name, const char *parent); +int sysfs_devname_is_hidden(const char *prefix, const char *name); + +char *sysfs_devno_to_devpath(dev_t devno, char *buf, size_t bufsiz); +char *sysfs_devno_to_devname(dev_t devno, char *buf, size_t bufsiz); +int sysfs_devno_count_partitions(dev_t devno); + +int sysfs_blkdev_scsi_get_hctl(struct path_cxt *pc, int *h, int *c, int *t, int *l); +char *sysfs_blkdev_scsi_host_strdup_attribute(struct path_cxt *pc, + const char *type, const char *attr); +int sysfs_blkdev_scsi_host_is(struct path_cxt *pc, const char *type); +int sysfs_blkdev_scsi_has_attribute(struct path_cxt *pc, const char *attr); +int sysfs_blkdev_scsi_path_contains(struct path_cxt *pc, const char *pattern); + + +#endif /* UTIL_LINUX_SYSFS_H */ diff --git a/utils/include/timer.h b/utils/include/timer.h new file mode 100644 index 0000000..70da1ba --- /dev/null +++ b/utils/include/timer.h @@ -0,0 +1,22 @@ +#ifndef UTIL_LINUX_TIMER_H +#define UTIL_LINUX_TIMER_H + +#include +#include + +#ifdef HAVE_TIMER_CREATE +struct ul_timer { + timer_t t_id; +}; +#else +struct ul_timer { + struct itimerval old_timer; + struct sigaction old_sa; +}; +#endif + +extern int setup_timer(struct ul_timer *timer, struct itimerval *timeout, + void (*timeout_handler)(int, siginfo_t *, void *)); +extern void cancel_timer(struct ul_timer *timer); + +#endif /* UTIL_LINUX_TIMER_H */ diff --git a/utils/include/timeutils.h b/utils/include/timeutils.h new file mode 100644 index 0000000..e452e55 --- /dev/null +++ b/utils/include/timeutils.h @@ -0,0 +1,92 @@ +/*** + First set of functions in this file are part of systemd, and were + copied to util-linux at August 2013. + + Copyright 2010 Lennart Poettering + Copyright (C) 2014 Karel Zak + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see . +***/ +#ifndef UTIL_LINUX_TIME_UTIL_H +#define UTIL_LINUX_TIME_UTIL_H + +#include +#include +#include + +typedef uint64_t usec_t; +typedef uint64_t nsec_t; + +#define MSEC_PER_SEC 1000ULL +#define USEC_PER_SEC 1000000ULL +#define USEC_PER_MSEC 1000ULL +#define NSEC_PER_SEC 1000000000ULL +#define NSEC_PER_MSEC 1000000ULL +#define NSEC_PER_USEC 1000ULL + +#define USEC_PER_MINUTE (60ULL*USEC_PER_SEC) +#define NSEC_PER_MINUTE (60ULL*NSEC_PER_SEC) +#define USEC_PER_HOUR (60ULL*USEC_PER_MINUTE) +#define NSEC_PER_HOUR (60ULL*NSEC_PER_MINUTE) +#define USEC_PER_DAY (24ULL*USEC_PER_HOUR) +#define NSEC_PER_DAY (24ULL*NSEC_PER_HOUR) +#define USEC_PER_WEEK (7ULL*USEC_PER_DAY) +#define NSEC_PER_WEEK (7ULL*NSEC_PER_DAY) +#define USEC_PER_MONTH (2629800ULL*USEC_PER_SEC) +#define NSEC_PER_MONTH (2629800ULL*NSEC_PER_SEC) +#define USEC_PER_YEAR (31557600ULL*USEC_PER_SEC) +#define NSEC_PER_YEAR (31557600ULL*NSEC_PER_SEC) + +#define FORMAT_TIMESTAMP_MAX ((4*4+1)+11+9+4+1) /* weekdays can be unicode */ +#define FORMAT_TIMESTAMP_RELATIVE_MAX 256 +#define FORMAT_TIMESPAN_MAX 64 + +int parse_timestamp(const char *t, usec_t *usec); +int get_gmtoff(const struct tm *tp); + +/* flags and masks for strxxx_iso() functions */ +enum { + ISO_DATE = (1 << 0), + ISO_TIME = (1 << 1), + ISO_TIMEZONE = (1 << 2), + ISO_DOTUSEC = (1 << 3), + ISO_COMMAUSEC = (1 << 4), + ISO_T = (1 << 5), + ISO_GMTIME = (1 << 6), + ISO_TIMESTAMP = ISO_DATE | ISO_TIME | ISO_TIMEZONE, + ISO_TIMESTAMP_T = ISO_TIMESTAMP | ISO_T, + ISO_TIMESTAMP_DOT = ISO_TIMESTAMP | ISO_DOTUSEC, + ISO_TIMESTAMP_DOT_T = ISO_TIMESTAMP_DOT | ISO_T, + ISO_TIMESTAMP_COMMA = ISO_TIMESTAMP | ISO_COMMAUSEC, + ISO_TIMESTAMP_COMMA_T = ISO_TIMESTAMP_COMMA | ISO_T, + ISO_TIMESTAMP_COMMA_G = ISO_TIMESTAMP_COMMA | ISO_GMTIME, + ISO_TIMESTAMP_COMMA_GT = ISO_TIMESTAMP_COMMA_G | ISO_T +}; + +#define CTIME_BUFSIZ 26 +#define ISO_BUFSIZ 42 + +int strtimeval_iso(struct timeval *tv, int flags, char *buf, size_t bufsz); +int strtm_iso(struct tm *tm, int flags, char *buf, size_t bufsz); +int strtime_iso(const time_t *t, int flags, char *buf, size_t bufsz); + +#define UL_SHORTTIME_THISYEAR_HHMM (1 << 1) + +int strtime_short(const time_t *t, struct timeval *now, int flags, char *buf, size_t bufsz); + +#ifndef HAVE_TIMEGM +extern time_t timegm(struct tm *tm); +#endif + +#endif /* UTIL_LINUX_TIME_UTIL_H */ diff --git a/utils/include/ttyutils.h b/utils/include/ttyutils.h new file mode 100644 index 0000000..f164a58 --- /dev/null +++ b/utils/include/ttyutils.h @@ -0,0 +1,205 @@ +/* + * No copyright is claimed. This code is in the public domain; do with + * it what you wish. + * + * Written by Karel Zak + */ +#ifndef UTIL_LINUX_TTYUTILS_H +#define UTIL_LINUX_TTYUTILS_H + +#include +#include +#include +#ifdef HAVE_SYS_IOCTL_H +#include +#endif +#ifdef HAVE_SYS_TTYDEFAULTS_H +#include +#endif + +/* Some shorthands for control characters. */ +#define CTL(x) ((x) ^ 0100) /* Assumes ASCII dialect */ +#define CR CTL('M') /* carriage return */ +#define NL CTL('J') /* line feed */ +#define BS CTL('H') /* back space */ +#define DEL CTL('?') /* delete */ + +/* Defaults for line-editing etc. characters; you may want to change these. */ +#define DEF_ERASE DEL /* default erase character */ +#define DEF_INTR CTL('C') /* default interrupt character */ +#define DEF_QUIT CTL('\\') /* default quit char */ +#define DEF_KILL CTL('U') /* default kill char */ +#define DEF_EOF CTL('D') /* default EOF char */ +#define DEF_EOL 0 +#define DEF_SWITCH 0 /* default switch char */ + +/* Fallback for termios->c_cc[] */ +#ifndef CREPRINT +# define CREPRINT ('r' & 037) +#endif +#ifndef CDISCARD +# define CDISCARD ('o' & 037) +#endif + +/* Default termios->iflag */ +#ifndef TTYDEF_IFLAG +# define TTYDEF_IFLAG (BRKINT | ICRNL | IMAXBEL | IXON | IXANY) +#endif + +/* Default termios->oflag */ +#ifndef TTYDEF_OFLAG +# define TTYDEF_OFLAG (OPOST | ONLCR /*| OXTABS*/) +#endif + +/* Default termios->lflag */ +#ifndef TTYDEF_LFLAG +# define TTYDEF_LFLAG (ECHO | ICANON | ISIG | IEXTEN | ECHOE|ECHOKE|ECHOCTL) +#endif + +/* Default termios->cflag */ +#ifndef TTYDEF_CFLAG +# define TTYDEF_CFLAG (CREAD | CS8 | HUPCL) +#endif + +/* Storage for things detected while the login name was read. */ +struct chardata { + int erase; /* erase character */ + int kill; /* kill character */ + int eol; /* end-of-line character */ + int parity; /* what parity did we see */ + int capslock; /* upper case without lower case */ +}; + +#define INIT_CHARDATA(ptr) do { \ + (ptr)->erase = DEF_ERASE; \ + (ptr)->kill = DEF_KILL; \ + (ptr)->eol = CTRL('r'); \ + (ptr)->parity = 0; \ + (ptr)->capslock = 0; \ + } while (0) + +extern int get_terminal_dimension(int *cols, int *lines); +extern int get_terminal_width(int default_width); +extern int get_terminal_type(const char **type); +extern int get_terminal_stdfd(void); +extern int get_terminal_name(const char **path, const char **name, + const char **number); + +#define UL_TTY_KEEPCFLAGS (1 << 1) +#define UL_TTY_UTF8 (1 << 2) + +static inline void reset_virtual_console(struct termios *tp, int flags) +{ + /* Use defaults of for base settings */ + tp->c_iflag |= TTYDEF_IFLAG; + tp->c_oflag |= TTYDEF_OFLAG; + tp->c_lflag |= TTYDEF_LFLAG; + + if ((flags & UL_TTY_KEEPCFLAGS) == 0) { +#ifdef CBAUD + tp->c_lflag &= ~CBAUD; +#endif + tp->c_cflag |= (B38400 | TTYDEF_CFLAG); + } + + /* Sane setting, allow eight bit characters, no carriage return delay + * the same result as `stty sane cr0 pass8' + */ +#ifndef IUCLC +# define IUCLC 0 +#endif +#ifndef NL0 +# define NL0 0 +#endif +#ifndef CR0 +# define CR0 0 +#endif +#ifndef BS0 +# define BS0 0 +#endif +#ifndef VT0 +# define VT0 0 +#endif +#ifndef FF0 +# define FF0 0 +#endif +#ifndef OLCUC +# define OLCUC 0 +#endif +#ifndef OFILL +# define OFILL 0 +#endif +#ifndef NLDLY +# define NLDLY 0 +#endif +#ifndef CRDLY +# define CRDLY 0 +#endif +#ifndef BSDLY +# define BSDLY 0 +#endif +#ifndef VTDLY +# define VTDLY 0 +#endif +#ifndef FFDLY +# define FFDLY 0 +#endif +#ifndef TAB0 +# define TAB0 0 +#endif +#ifndef TABDLY +# define TABDLY 0 +#endif + + tp->c_iflag |= (BRKINT | ICRNL | IMAXBEL); + tp->c_iflag &= ~(IGNBRK | INLCR | IGNCR | IXOFF | IUCLC | IXANY | ISTRIP); + tp->c_oflag |= (OPOST | ONLCR | NL0 | CR0 | TAB0 | BS0 | VT0 | FF0); + tp->c_oflag &= ~(OLCUC | OCRNL | ONOCR | ONLRET | OFILL | \ + NLDLY|CRDLY|TABDLY|BSDLY|VTDLY|FFDLY); + tp->c_lflag |= (ISIG | ICANON | IEXTEN | ECHO|ECHOE|ECHOK|ECHOKE|ECHOCTL); + tp->c_lflag &= ~(ECHONL|ECHOPRT | NOFLSH | TOSTOP); + + if ((flags & UL_TTY_KEEPCFLAGS) == 0) { + tp->c_cflag |= (CREAD | CS8 | HUPCL); + tp->c_cflag &= ~(PARODD | PARENB); + } +#ifdef OFDEL + tp->c_oflag &= ~OFDEL; +#endif +#ifdef XCASE + tp->c_lflag &= ~XCASE; +#endif +#ifdef IUTF8 + if (flags & UL_TTY_UTF8) + tp->c_iflag |= IUTF8; /* Set UTF-8 input flag */ + else + tp->c_iflag &= ~IUTF8; +#endif + /* VTIME and VMIN can overlap with VEOF and VEOL since they are + * only used for non-canonical mode. We just set the at the + * beginning, so nothing bad should happen. + */ + tp->c_cc[VTIME] = 0; + tp->c_cc[VMIN] = 1; + tp->c_cc[VINTR] = CINTR; + tp->c_cc[VQUIT] = CQUIT; + tp->c_cc[VERASE] = CERASE; /* ASCII DEL (0177) */ + tp->c_cc[VKILL] = CKILL; + tp->c_cc[VEOF] = CEOF; +#ifdef VSWTC + tp->c_cc[VSWTC] = _POSIX_VDISABLE; +#elif defined(VSWTCH) + tp->c_cc[VSWTCH] = _POSIX_VDISABLE; +#endif + tp->c_cc[VSTART] = CSTART; + tp->c_cc[VSTOP] = CSTOP; + tp->c_cc[VSUSP] = CSUSP; + tp->c_cc[VEOL] = _POSIX_VDISABLE; + tp->c_cc[VREPRINT] = CREPRINT; + tp->c_cc[VDISCARD] = CDISCARD; + tp->c_cc[VWERASE] = CWERASE; + tp->c_cc[VLNEXT] = CLNEXT; + tp->c_cc[VEOL2] = _POSIX_VDISABLE; +} + +#endif /* UTIL_LINUX_TTYUTILS_H */ diff --git a/utils/include/widechar.h b/utils/include/widechar.h new file mode 100644 index 0000000..c1f2cf2 --- /dev/null +++ b/utils/include/widechar.h @@ -0,0 +1,47 @@ +/* Declarations for wide characters */ +/* This file must be included last because the redefinition of wchar_t may + cause conflicts when system include files were included after it. */ + +#ifdef HAVE_WIDECHAR + +# include +# include + +#else /* !HAVE_WIDECHAR */ + +# include + /* Fallback for types */ +# define wchar_t char +# define wint_t int +# ifndef WEOF +# define WEOF EOF +# endif + + /* Fallback for input operations */ +# define fgetwc fgetc +# define getwc getc +# define getwchar getchar +# define fgetws fgets + + /* Fallback for output operations */ +# define fputwc fputc +# define putwc putc +# define putwchar putchar +# define fputws fputs + + /* Fallback for character classification */ +# define iswgraph isgraph +# define iswprint isprint +# define iswspace isspace + + /* Fallback for string functions */ +# define wcschr strchr +# define wcsdup strdup +# define wcslen strlen +# define wcspbrk strpbrk + +# define wcwidth(c) (1) +# define wmemset memset +# define ungetwc ungetc + +#endif /* HAVE_WIDECHAR */ diff --git a/utils/include/xalloc.h b/utils/include/xalloc.h new file mode 100644 index 0000000..c4124cb --- /dev/null +++ b/utils/include/xalloc.h @@ -0,0 +1,139 @@ +/* + * Copyright (C) 2010 Davidlohr Bueso + * + * This file may be redistributed under the terms of the + * GNU Lesser General Public License. + * + * General memory allocation wrappers for malloc, realloc, calloc and strdup + */ + +#ifndef UTIL_LINUX_XALLOC_H +#define UTIL_LINUX_XALLOC_H + +#include +#include + +#include "c.h" + +#ifndef XALLOC_EXIT_CODE +# define XALLOC_EXIT_CODE EXIT_FAILURE +#endif + +static inline +__attribute__((__noreturn__)) +void __err_oom(const char *file, unsigned int line) +{ + err(XALLOC_EXIT_CODE, "%s: %u: cannot allocate memory", file, line); +} + +#define err_oom() __err_oom(__FILE__, __LINE__) + +static inline +__ul_alloc_size(1) +__ul_returns_nonnull +void *xmalloc(const size_t size) +{ + void *ret = malloc(size); + + if (!ret && size) + err(XALLOC_EXIT_CODE, "cannot allocate %zu bytes", size); + return ret; +} + +static inline +__ul_alloc_size(2) +__ul_returns_nonnull +void *xrealloc(void *ptr, const size_t size) +{ + void *ret = realloc(ptr, size); + + if (!ret && size) + err(XALLOC_EXIT_CODE, "cannot allocate %zu bytes", size); + return ret; +} + +static inline +__ul_calloc_size(1, 2) +__ul_returns_nonnull +void *xcalloc(const size_t nelems, const size_t size) +{ + void *ret = calloc(nelems, size); + + if (!ret && size && nelems) + err(XALLOC_EXIT_CODE, "cannot allocate %zu bytes", size); + return ret; +} + +static inline +__attribute__((warn_unused_result)) +__ul_returns_nonnull +char *xstrdup(const char *str) +{ + char *ret; + + assert(str); + ret = strdup(str); + if (!ret) + err(XALLOC_EXIT_CODE, "cannot duplicate string"); + return ret; +} + +static inline +__attribute__((warn_unused_result)) +__ul_returns_nonnull +char *xstrndup(const char *str, size_t size) +{ + char *ret; + + assert(str); + ret = strndup(str, size); + if (!ret) + err(XALLOC_EXIT_CODE, "cannot duplicate string"); + return ret; +} + + +static inline +__attribute__((__format__(printf, 2, 3))) +int xasprintf(char **strp, const char *fmt, ...) +{ + int ret; + va_list args; + + va_start(args, fmt); + ret = vasprintf(&(*strp), fmt, args); + va_end(args); + if (ret < 0) + err(XALLOC_EXIT_CODE, "cannot allocate string"); + return ret; +} + +static inline +__attribute__((__format__(printf, 2, 0))) +int xvasprintf(char **strp, const char *fmt, va_list ap) +{ + int ret = vasprintf(&(*strp), fmt, ap); + + if (ret < 0) + err(XALLOC_EXIT_CODE, "cannot allocate string"); + return ret; +} + + +static inline +__attribute__((warn_unused_result)) +char *xgethostname(void) +{ + char *name; + size_t sz = get_hostname_max() + 1; + + name = xmalloc(sizeof(char) * sz); + if (gethostname(name, sz) != 0) { + free(name); + return NULL; + } + name[sz - 1] = '\0'; + return name; +} + +#endif diff --git a/utils/lib/CMakeLists.txt b/utils/lib/CMakeLists.txt new file mode 100644 index 0000000..b84dc3f --- /dev/null +++ b/utils/lib/CMakeLists.txt @@ -0,0 +1,45 @@ +cmake_minimum_required(VERSION 3.10) + +# set the project name +project(xloop-utils-lib) + +add_library(libcommon STATIC ${CMAKE_CURRENT_SOURCE_DIR}/blkdev.c + ${CMAKE_CURRENT_SOURCE_DIR}/canonicalize.c + ${CMAKE_CURRENT_SOURCE_DIR}/caputils.c + ${CMAKE_CURRENT_SOURCE_DIR}/color-names.c + ${CMAKE_CURRENT_SOURCE_DIR}/colors.c + ${CMAKE_CURRENT_SOURCE_DIR}/cpuset.c + ${CMAKE_CURRENT_SOURCE_DIR}/crc32.c + ${CMAKE_CURRENT_SOURCE_DIR}/crc32c.c + ${CMAKE_CURRENT_SOURCE_DIR}/encode.c + ${CMAKE_CURRENT_SOURCE_DIR}/env.c + ${CMAKE_CURRENT_SOURCE_DIR}/exec_shell.c + ${CMAKE_CURRENT_SOURCE_DIR}/fileutils.c + ${CMAKE_CURRENT_SOURCE_DIR}/idcache.c + ${CMAKE_CURRENT_SOURCE_DIR}/ismounted.c + ${CMAKE_CURRENT_SOURCE_DIR}/langinfo.c + ${CMAKE_CURRENT_SOURCE_DIR}/linux_version.c + ${CMAKE_CURRENT_SOURCE_DIR}/loopdev.c + ${CMAKE_CURRENT_SOURCE_DIR}/mangle.c + ${CMAKE_CURRENT_SOURCE_DIR}/match.c + ${CMAKE_CURRENT_SOURCE_DIR}/mbsalign.c + ${CMAKE_CURRENT_SOURCE_DIR}/mbsedit.c + ${CMAKE_CURRENT_SOURCE_DIR}/md5.c + ${CMAKE_CURRENT_SOURCE_DIR}/monotonic.c + ${CMAKE_CURRENT_SOURCE_DIR}/pager.c + ${CMAKE_CURRENT_SOURCE_DIR}/path.c + ${CMAKE_CURRENT_SOURCE_DIR}/plymouth-ctrl.c + ${CMAKE_CURRENT_SOURCE_DIR}/procutils.c + ${CMAKE_CURRENT_SOURCE_DIR}/pty-session.c + ${CMAKE_CURRENT_SOURCE_DIR}/pwdutils.c + ${CMAKE_CURRENT_SOURCE_DIR}/randutils.c + ${CMAKE_CURRENT_SOURCE_DIR}/setproctitle.c + ${CMAKE_CURRENT_SOURCE_DIR}/sha1.c + ${CMAKE_CURRENT_SOURCE_DIR}/signames.c + ${CMAKE_CURRENT_SOURCE_DIR}/strutils.c + ${CMAKE_CURRENT_SOURCE_DIR}/strv.c + ${CMAKE_CURRENT_SOURCE_DIR}/sysfs.c + ${CMAKE_CURRENT_SOURCE_DIR}/timer.c + ${CMAKE_CURRENT_SOURCE_DIR}/timeutils.c + ${CMAKE_CURRENT_SOURCE_DIR}/ttyutils.c) +target_include_directories(libcommon PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/src) diff --git a/utils/lib/blkdev.c b/utils/lib/blkdev.c new file mode 100644 index 0000000..c22853d --- /dev/null +++ b/utils/lib/blkdev.c @@ -0,0 +1,452 @@ +/* + * No copyright is claimed. This code is in the public domain; do with + * it what you wish. + * + * Written by Karel Zak + */ +#include +#include +#include +#include +#include +#include + +#ifdef HAVE_LINUX_FD_H +#include +#endif + +#ifdef HAVE_SYS_DISKLABEL_H +#include +#endif + +#ifdef HAVE_SYS_DISK_H +# include +#endif + +#ifndef EBADFD +# define EBADFD 77 /* File descriptor in bad state */ +#endif + +#include "blkdev.h" +#include "c.h" +#include "linux_version.h" +#include "fileutils.h" +#include "nls.h" + +static long +blkdev_valid_offset (int fd, off_t offset) { + char ch; + + if (lseek (fd, offset, 0) < 0) + return 0; + if (read (fd, &ch, 1) < 1) + return 0; + return 1; +} + +int is_blkdev(int fd) +{ + struct stat st; + return (fstat(fd, &st) == 0 && S_ISBLK(st.st_mode)); +} + +off_t +blkdev_find_size (int fd) { + uintmax_t high, low = 0; + + for (high = 1024; blkdev_valid_offset (fd, high); ) { + if (high == UINTMAX_MAX) + return -1; + + low = high; + + if (high >= UINTMAX_MAX/2) + high = UINTMAX_MAX; + else + high *= 2; + } + + while (low < high - 1) + { + uintmax_t mid = (low + high) / 2; + + if (blkdev_valid_offset (fd, mid)) + low = mid; + else + high = mid; + } + blkdev_valid_offset (fd, 0); + return (low + 1); +} + +/* get size in bytes */ +int +blkdev_get_size(int fd, unsigned long long *bytes) +{ +#ifdef DKIOCGETBLOCKCOUNT + /* Apple Darwin */ + if (ioctl(fd, DKIOCGETBLOCKCOUNT, bytes) >= 0) { + *bytes <<= 9; + return 0; + } +#endif + +#ifdef BLKGETSIZE64 + if (ioctl(fd, BLKGETSIZE64, bytes) >= 0) + return 0; +#endif + +#ifdef BLKGETSIZE + { + unsigned long size; + + if (ioctl(fd, BLKGETSIZE, &size) >= 0) { + *bytes = ((unsigned long long)size << 9); + return 0; + } + } + +#endif /* BLKGETSIZE */ + +#ifdef DIOCGMEDIASIZE + /* FreeBSD */ + if (ioctl(fd, DIOCGMEDIASIZE, bytes) >= 0) + return 0; +#endif + +#ifdef FDGETPRM + { + struct floppy_struct this_floppy; + + if (ioctl(fd, FDGETPRM, &this_floppy) >= 0) { + *bytes = ((unsigned long long) this_floppy.size) << 9; + return 0; + } + } +#endif /* FDGETPRM */ + +#if defined(HAVE_SYS_DISKLABEL_H) && defined(DIOCGDINFO) + { + /* + * This code works for FreeBSD 4.11 i386, except for the full device + * (such as /dev/ad0). It doesn't work properly for newer FreeBSD + * though. FreeBSD >= 5.0 should be covered by the DIOCGMEDIASIZE + * above however. + * + * Note that FreeBSD >= 4.0 has disk devices as unbuffered (raw, + * character) devices, so we need to check for S_ISCHR, too. + */ + int part = -1; + struct disklabel lab; + struct partition *pp; + struct stat st; + + if ((fstat(fd, &st) >= 0) && + (S_ISBLK(st.st_mode) || S_ISCHR(st.st_mode))) + part = st.st_rdev & 7; + + if (part >= 0 && (ioctl(fd, DIOCGDINFO, (char *)&lab) >= 0)) { + pp = &lab.d_partitions[part]; + if (pp->p_size) { + *bytes = pp->p_size << 9; + return 0; + } + } + } +#endif /* defined(HAVE_SYS_DISKLABEL_H) && defined(DIOCGDINFO) */ + + { + struct stat st; + + if (fstat(fd, &st) == 0 && S_ISREG(st.st_mode)) { + *bytes = st.st_size; + return 0; + } + if (!S_ISBLK(st.st_mode)) + return -1; + } + + *bytes = blkdev_find_size(fd); + return 0; +} + +/* get 512-byte sector count */ +int +blkdev_get_sectors(int fd, unsigned long long *sectors) +{ + unsigned long long bytes; + + if (blkdev_get_size(fd, &bytes) == 0) { + *sectors = (bytes >> 9); + return 0; + } + + return -1; +} + +/* + * Get logical sector size. + * + * This is the smallest unit the storage device can + * address. It is typically 512 bytes. + */ +#ifdef BLKSSZGET +int blkdev_get_sector_size(int fd, int *sector_size) +{ + if (ioctl(fd, BLKSSZGET, sector_size) >= 0) + return 0; + return -1; +} +#else +int blkdev_get_sector_size(int fd __attribute__((__unused__)), int *sector_size) +{ + *sector_size = DEFAULT_SECTOR_SIZE; + return 0; +} +#endif + +/* + * Get physical block device size. The BLKPBSZGET is supported since Linux + * 2.6.32. For old kernels is probably the best to assume that physical sector + * size is the same as logical sector size. + * + * Example: + * + * rc = blkdev_get_physector_size(fd, &physec); + * if (rc || physec == 0) { + * rc = blkdev_get_sector_size(fd, &physec); + * if (rc) + * physec = DEFAULT_SECTOR_SIZE; + * } + */ +#ifdef BLKPBSZGET +int blkdev_get_physector_size(int fd, int *sector_size) +{ + if (ioctl(fd, BLKPBSZGET, §or_size) >= 0) + return 0; + return -1; +} +#else +int blkdev_get_physector_size(int fd __attribute__((__unused__)), int *sector_size) +{ + *sector_size = DEFAULT_SECTOR_SIZE; + return 0; +} +#endif + +/* + * Return the alignment status of a device + */ +#ifdef BLKALIGNOFF +int blkdev_is_misaligned(int fd) +{ + int aligned; + + if (ioctl(fd, BLKALIGNOFF, &aligned) < 0) + return 0; /* probably kernel < 2.6.32 */ + /* + * Note that kernel returns -1 as alignment offset if no compatible + * sizes and alignments exist for stacked devices + */ + return aligned != 0 ? 1 : 0; +} +#else +int blkdev_is_misaligned(int fd __attribute__((__unused__))) +{ + return 0; +} +#endif + +int open_blkdev_or_file(const struct stat *st, const char *name, const int oflag) +{ + int fd; + + if (S_ISBLK(st->st_mode)) { + fd = open(name, oflag | O_EXCL); + } else + fd = open(name, oflag); + if (-1 < fd && !is_same_inode(fd, st)) { + close(fd); + errno = EBADFD; + return -1; + } + if (-1 < fd && S_ISBLK(st->st_mode) && blkdev_is_misaligned(fd)) + warnx(_("warning: %s is misaligned"), name); + return fd; +} + +#ifdef CDROM_GET_CAPABILITY +int blkdev_is_cdrom(int fd) +{ + int ret; + + if ((ret = ioctl(fd, CDROM_GET_CAPABILITY, NULL)) < 0) + return 0; + + return ret; +} +#else +int blkdev_is_cdrom(int fd __attribute__((__unused__))) +{ + return 0; +} +#endif + +/* + * Get kernel's interpretation of the device's geometry. + * + * Returns the heads and sectors - but not cylinders + * as it's truncated for disks with more than 65535 tracks. + * + * Note that this is deprecated in favor of LBA addressing. + */ +#ifdef HDIO_GETGEO +int blkdev_get_geometry(int fd, unsigned int *h, unsigned int *s) +{ + struct hd_geometry geometry; + + if (ioctl(fd, HDIO_GETGEO, &geometry) == 0) { + *h = geometry.heads; + *s = geometry.sectors; + return 0; + } +#else +int blkdev_get_geometry(int fd __attribute__((__unused__)), + unsigned int *h, unsigned int *s) +{ + *h = 0; + *s = 0; +#endif + return -1; +} + +/* + * Convert scsi type to human readable string. + */ +const char *blkdev_scsi_type_to_name(int type) +{ + switch (type) { + case SCSI_TYPE_DISK: + return "disk"; + case SCSI_TYPE_TAPE: + return "tape"; + case SCSI_TYPE_PRINTER: + return "printer"; + case SCSI_TYPE_PROCESSOR: + return "processor"; + case SCSI_TYPE_WORM: + return "worm"; + case SCSI_TYPE_ROM: + return "rom"; + case SCSI_TYPE_SCANNER: + return "scanner"; + case SCSI_TYPE_MOD: + return "mo-disk"; + case SCSI_TYPE_MEDIUM_CHANGER: + return "changer"; + case SCSI_TYPE_COMM: + return "comm"; + case SCSI_TYPE_RAID: + return "raid"; + case SCSI_TYPE_ENCLOSURE: + return "enclosure"; + case SCSI_TYPE_RBC: + return "rbc"; + case SCSI_TYPE_OSD: + return "osd"; + case SCSI_TYPE_NO_LUN: + return "no-lun"; + default: + break; + } + return NULL; +} + +/* return 0 on success */ +int blkdev_lock(int fd, const char *devname, const char *lockmode) +{ + int oper, rc, msg = 0; + + if (!lockmode) + lockmode = getenv("LOCK_BLOCK_DEVICE"); + if (!lockmode) + return 0; + + if (strcasecmp(lockmode, "yes") == 0 || + strcmp(lockmode, "1") == 0) + oper = LOCK_EX; + + else if (strcasecmp(lockmode, "nonblock") == 0) + oper = LOCK_EX | LOCK_NB; + + else if (strcasecmp(lockmode, "no") == 0 || + strcmp(lockmode, "0") == 0) + return 0; + else { + warnx(_("unsupported lock mode: %s"), lockmode); + return -EINVAL; + } + + if (!(oper & LOCK_NB)) { + /* Try non-block first to provide message */ + rc = flock(fd, oper | LOCK_NB); + if (rc == 0) + return 0; + if (rc != 0 && errno == EWOULDBLOCK) { + fprintf(stderr, _("%s: %s: device already locked, waiting to get lock ... "), + program_invocation_short_name, devname); + msg = 1; + } + } + rc = flock(fd, oper); + if (rc != 0) { + switch (errno) { + case EWOULDBLOCK: /* LOCK_NB */ + warnx(_("%s: device already locked"), devname); + break; + default: + warn(_("%s: failed to get lock"), devname); + } + } else if (msg) + fprintf(stderr, _("OK\n")); + return rc; +} + + +#ifdef TEST_PROGRAM_BLKDEV +#include +#include +#include +int +main(int argc, char **argv) +{ + unsigned long long bytes; + unsigned long long sectors; + int sector_size, phy_sector_size; + int fd; + + if (argc != 2) { + fprintf(stderr, "usage: %s device\n", argv[0]); + exit(EXIT_FAILURE); + } + + if ((fd = open(argv[1], O_RDONLY|O_CLOEXEC)) < 0) + err(EXIT_FAILURE, "open %s failed", argv[1]); + + if (blkdev_get_size(fd, &bytes) < 0) + err(EXIT_FAILURE, "blkdev_get_size() failed"); + if (blkdev_get_sectors(fd, §ors) < 0) + err(EXIT_FAILURE, "blkdev_get_sectors() failed"); + if (blkdev_get_sector_size(fd, §or_size) < 0) + err(EXIT_FAILURE, "blkdev_get_sector_size() failed"); + if (blkdev_get_physector_size(fd, &phy_sector_size) < 0) + err(EXIT_FAILURE, "blkdev_get_physector_size() failed"); + + printf(" bytes: %llu\n", bytes); + printf(" sectors: %llu\n", sectors); + printf(" sector size: %d\n", sector_size); + printf("phy-sector size: %d\n", phy_sector_size); + + return EXIT_SUCCESS; +} +#endif /* TEST_PROGRAM_BLKDEV */ diff --git a/utils/lib/canonicalize.c b/utils/lib/canonicalize.c new file mode 100644 index 0000000..e101c5b --- /dev/null +++ b/utils/lib/canonicalize.c @@ -0,0 +1,250 @@ +/* + * canonicalize.c -- canonicalize pathname by removing symlinks + * + * This file may be distributed under the terms of the + * GNU Lesser General Public License. + * + * Copyright (C) 2009-2013 Karel Zak + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "canonicalize.h" +#include "pathnames.h" +#include "all-io.h" + +/* + * Converts private "dm-N" names to "/dev/mapper/" + * + * Since 2.6.29 (patch 784aae735d9b0bba3f8b9faef4c8b30df3bf0128) kernel sysfs + * provides the real DM device names in /sys/block//dm/name + */ +char *__canonicalize_dm_name(const char *prefix, const char *ptname) +{ + FILE *f; + size_t sz; + char path[256], name[sizeof(path) - sizeof(_PATH_DEV_MAPPER)], *res = NULL; + + if (!ptname || !*ptname) + return NULL; + + if (!prefix) + prefix = ""; + + snprintf(path, sizeof(path), "%s/sys/block/%s/dm/name", prefix, ptname); + if (!(f = fopen(path, "r" UL_CLOEXECSTR))) + return NULL; + + /* read "\n" from sysfs */ + if (fgets(name, sizeof(name), f) && (sz = strlen(name)) > 1) { + name[sz - 1] = '\0'; + snprintf(path, sizeof(path), _PATH_DEV_MAPPER "/%s", name); + + if ((prefix && *prefix) || access(path, F_OK) == 0) + res = strdup(path); + } + fclose(f); + return res; +} + +char *canonicalize_dm_name(const char *ptname) +{ + return __canonicalize_dm_name(NULL, ptname); +} + +static int is_dm_devname(char *canonical, char **name) +{ + struct stat sb; + char *p = strrchr(canonical, '/'); + + *name = NULL; + + if (!p + || strncmp(p, "/dm-", 4) != 0 + || !isdigit(*(p + 4)) + || stat(canonical, &sb) != 0 + || !S_ISBLK(sb.st_mode)) + return 0; + + *name = p + 1; + return 1; +} + +/* + * This function does not canonicalize the path! It just prepends CWD before a + * relative path. If the path is no relative than returns NULL. The path does + * not have to exist. + */ +char *absolute_path(const char *path) +{ + char cwd[PATH_MAX], *res, *p; + size_t psz, csz; + + if (!is_relative_path(path)) { + errno = EINVAL; + return NULL; + } + if (!getcwd(cwd, sizeof(cwd))) + return NULL; + + /* simple clean up */ + if (startswith(path, "./")) + path += 2; + else if (strcmp(path, ".") == 0) + path = NULL; + + if (!path || !*path) + return strdup(cwd); + + csz = strlen(cwd); + psz = strlen(path); + + p = res = malloc(csz + 1 + psz + 1); + if (!res) + return NULL; + + memcpy(p, cwd, csz); + p += csz; + *p++ = '/'; + memcpy(p, path, psz + 1); + + return res; +} + +char *canonicalize_path(const char *path) +{ + char *canonical, *dmname; + + if (!path || !*path) + return NULL; + + canonical = realpath(path, NULL); + if (!canonical) + return strdup(path); + + if (is_dm_devname(canonical, &dmname)) { + char *dm = canonicalize_dm_name(dmname); + if (dm) { + free(canonical); + return dm; + } + } + + return canonical; +} + +char *canonicalize_path_restricted(const char *path) +{ + char *canonical = NULL; + int errsv = 0; + int pipes[2]; + ssize_t len; + pid_t pid; + + if (!path || !*path) + return NULL; + + if (pipe(pipes) != 0) + return NULL; + + /* + * To accurately assume identity of getuid() we must use setuid() + * but if we do that, we lose ability to reassume euid of 0, so + * we fork to do the check to keep euid intact. + */ + pid = fork(); + switch (pid) { + case -1: + close(pipes[0]); + close(pipes[1]); + return NULL; /* fork error */ + case 0: + close(pipes[0]); /* close unused end */ + pipes[0] = -1; + errno = 0; + + /* drop permissions */ + if (setgid(getgid()) < 0 || setuid(getuid()) < 0) + canonical = NULL; /* failed */ + else { + char *dmname = NULL; + + canonical = realpath(path, NULL); + if (canonical && is_dm_devname(canonical, &dmname)) { + char *dm = canonicalize_dm_name(dmname); + if (dm) { + free(canonical); + canonical = dm; + } + } + } + + len = canonical ? (ssize_t) strlen(canonical) : + errno ? -errno : -EINVAL; + + /* send length or errno */ + write_all(pipes[1], (char *) &len, sizeof(len)); + if (canonical) + write_all(pipes[1], canonical, len); + exit(0); + default: + break; + } + + close(pipes[1]); /* close unused end */ + pipes[1] = -1; + + /* read size or -errno */ + if (read_all(pipes[0], (char *) &len, sizeof(len)) != sizeof(len)) + goto done; + if (len < 0) { + errsv = -len; + goto done; + } + + canonical = malloc(len + 1); + if (!canonical) { + errsv = ENOMEM; + goto done; + } + /* read path */ + if (read_all(pipes[0], canonical, len) != len) { + errsv = errno; + goto done; + } + canonical[len] = '\0'; +done: + if (errsv) { + free(canonical); + canonical = NULL; + } + close(pipes[0]); + + /* We make a best effort to reap child */ + waitpid(pid, NULL, 0); + + errno = errsv; + return canonical; +} + + +#ifdef TEST_PROGRAM_CANONICALIZE +int main(int argc, char **argv) +{ + if (argc < 2) { + fprintf(stderr, "usage: %s \n", argv[0]); + exit(EXIT_FAILURE); + } + + fprintf(stdout, "orig: %s\n", argv[1]); + fprintf(stdout, "real: %s\n", canonicalize_path(argv[1])); + exit(EXIT_SUCCESS); +} +#endif diff --git a/utils/lib/caputils.c b/utils/lib/caputils.c new file mode 100644 index 0000000..17e9c01 --- /dev/null +++ b/utils/lib/caputils.c @@ -0,0 +1,45 @@ +/* + * 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, or (at your option) any + * later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include + +#include "caputils.h" +#include "pathnames.h" + +int cap_last_cap(void) +{ + /* CAP_LAST_CAP is untrustworthy. */ + static int ret = -1; + int matched; + FILE *f; + + if (ret != -1) + return ret; + + f = fopen(_PATH_PROC_CAPLASTCAP, "r"); + if (!f) { + ret = CAP_LAST_CAP; /* guess */ + return ret; + } + + matched = fscanf(f, "%d", &ret); + fclose(f); + + if (matched != 1) + ret = CAP_LAST_CAP; /* guess */ + + return ret; +} diff --git a/utils/lib/color-names.c b/utils/lib/color-names.c new file mode 100644 index 0000000..9b1505e --- /dev/null +++ b/utils/lib/color-names.c @@ -0,0 +1,64 @@ +/* + * No copyright is claimed. This code is in the public domain; do with + * it what you wish. + * + * Written by Karel Zak + */ +#include "c.h" +#include "color-names.h" + +struct ul_color_name { + const char *name; + const char *seq; +}; + +/* + * qsort/bsearch buddy + */ +static int cmp_color_name(const void *a0, const void *b0) +{ + const struct ul_color_name + *a = (const struct ul_color_name *) a0, + *b = (const struct ul_color_name *) b0; + return strcmp(a->name, b->name); +} + +/* + * Maintains human readable color names + */ +const char *color_sequence_from_colorname(const char *str) +{ + static const struct ul_color_name basic_schemes[] = { + { "black", UL_COLOR_BLACK }, + { "blink", UL_COLOR_BLINK }, + { "blue", UL_COLOR_BLUE }, + { "bold", UL_COLOR_BOLD }, + { "brown", UL_COLOR_BROWN }, + { "cyan", UL_COLOR_CYAN }, + { "darkgray", UL_COLOR_DARK_GRAY }, + { "gray", UL_COLOR_GRAY }, + { "green", UL_COLOR_GREEN }, + { "halfbright", UL_COLOR_HALFBRIGHT }, + { "lightblue", UL_COLOR_BOLD_BLUE }, + { "lightcyan", UL_COLOR_BOLD_CYAN }, + { "lightgray,", UL_COLOR_GRAY }, + { "lightgreen", UL_COLOR_BOLD_GREEN }, + { "lightmagenta", UL_COLOR_BOLD_MAGENTA }, + { "lightred", UL_COLOR_BOLD_RED }, + { "magenta", UL_COLOR_MAGENTA }, + { "red", UL_COLOR_RED }, + { "reset", UL_COLOR_RESET, }, + { "reverse", UL_COLOR_REVERSE }, + { "yellow", UL_COLOR_BOLD_YELLOW }, + { "white", UL_COLOR_WHITE } + }; + struct ul_color_name key = { .name = str }, *res; + + if (!str) + return NULL; + + res = bsearch(&key, basic_schemes, ARRAY_SIZE(basic_schemes), + sizeof(struct ul_color_name), + cmp_color_name); + return res ? res->seq : NULL; +} diff --git a/utils/lib/colors.c b/utils/lib/colors.c new file mode 100644 index 0000000..e317519 --- /dev/null +++ b/utils/lib/colors.c @@ -0,0 +1,907 @@ +/* + * Copyright (C) 2012 Ondrej Oprala + * Copyright (C) 2012-2014 Karel Zak + * + * This file may be distributed under the terms of the + * GNU Lesser General Public License. + */ +#include +#include +#include +#include +#include + +#if defined(HAVE_LIBNCURSES) || defined(HAVE_LIBNCURSESW) +# if defined(HAVE_NCURSESW_NCURSES_H) +# include +# elif defined(HAVE_NCURSES_NCURSES_H) +# include +# elif defined(HAVE_NCURSES_H) +# include +# endif +# if defined(HAVE_NCURSESW_TERM_H) +# include +# elif defined(HAVE_NCURSES_TERM_H) +# include +# elif defined(HAVE_TERM_H) +# include +# endif +#endif + +#include "c.h" +#include "colors.h" +#include "pathnames.h" +#include "strutils.h" + +#include "debug.h" + +/* + * Default behavior, may be overridden by terminal-colors.d/{enable,disable}. + */ +#ifdef USE_COLORS_BY_DEFAULT +# define UL_COLORMODE_DEFAULT UL_COLORMODE_AUTO /* check isatty() */ +#else +# define UL_COLORMODE_DEFAULT UL_COLORMODE_NEVER /* no colors by default */ +#endif + +/* + * terminal-colors.d debug stuff + */ +static UL_DEBUG_DEFINE_MASK(termcolors); +UL_DEBUG_DEFINE_MASKNAMES(termcolors) = UL_DEBUG_EMPTY_MASKNAMES; + +#define TERMCOLORS_DEBUG_INIT (1 << 1) +#define TERMCOLORS_DEBUG_CONF (1 << 2) +#define TERMCOLORS_DEBUG_SCHEME (1 << 3) +#define TERMCOLORS_DEBUG_ALL 0xFFFF + +#define DBG(m, x) __UL_DBG(termcolors, TERMCOLORS_DEBUG_, m, x) +#define ON_DBG(m, x) __UL_DBG_CALL(termcolors, TERMCOLORS_DEBUG_, m, x) + +/* + * terminal-colors.d file types + */ +enum { + UL_COLORFILE_DISABLE, /* .disable */ + UL_COLORFILE_ENABLE, /* .enable */ + UL_COLORFILE_SCHEME, /* .scheme */ + + __UL_COLORFILE_COUNT +}; + +struct ul_color_scheme { + char *name; + char *seq; +}; + +/* + * Global colors control struct + * + * The terminal-colors.d/ evaluation is based on "scores": + * + * filename score + * --------------------------------------- + * type 1 + * @termname.type 10 + 1 + * utilname.type 20 + 1 + * utilname@termname.type 20 + 10 + 1 + * + * the match with higher score wins. The score is per type. + */ +struct ul_color_ctl { + const char *utilname; /* util name */ + const char *termname; /* terminal name ($TERM) */ + + char *sfile; /* path to scheme */ + + struct ul_color_scheme *schemes; /* array with color schemes */ + size_t nschemes; /* number of the items */ + size_t schemes_sz; /* number of the allocated items */ + + int mode; /* UL_COLORMODE_* */ + unsigned int has_colors : 1, /* based on mode and scores[] */ + disabled : 1, /* disable colors */ + cs_configured : 1, /* color schemes read */ + configured : 1; /* terminal-colors.d parsed */ + + int scores[__UL_COLORFILE_COUNT]; /* the best match */ +}; + +/* + * Control struct, globally shared. + */ +static struct ul_color_ctl ul_colors; + +static void colors_free_schemes(struct ul_color_ctl *cc); +static int colors_read_schemes(struct ul_color_ctl *cc); + +/* + * qsort/bsearch buddy + */ +static int cmp_scheme_name(const void *a0, const void *b0) +{ + const struct ul_color_scheme *a = (const struct ul_color_scheme *) a0, + *b = (const struct ul_color_scheme *) b0; + return strcmp(a->name, b->name); +} + +/* + * Resets control struct (note that we don't allocate the struct) + */ +static void colors_reset(struct ul_color_ctl *cc) +{ + if (!cc) + return; + + colors_free_schemes(cc); + + free(cc->sfile); + + cc->sfile = NULL; + cc->utilname = NULL; + cc->termname = NULL; + cc->mode = UL_COLORMODE_UNDEF; + + memset(cc->scores, 0, sizeof(cc->scores)); +} + +static void colors_debug(struct ul_color_ctl *cc) +{ + size_t i; + + if (!cc) + return; + + printf("Colors:\n"); + printf("\tutilname = '%s'\n", cc->utilname); + printf("\ttermname = '%s'\n", cc->termname); + printf("\tscheme file = '%s'\n", cc->sfile); + printf("\tmode = %s\n", + cc->mode == UL_COLORMODE_UNDEF ? "undefined" : + cc->mode == UL_COLORMODE_AUTO ? "auto" : + cc->mode == UL_COLORMODE_NEVER ? "never" : + cc->mode == UL_COLORMODE_ALWAYS ? "always" : "???"); + printf("\thas_colors = %d\n", cc->has_colors); + printf("\tdisabled = %d\n", cc->disabled); + printf("\tconfigured = %d\n", cc->configured); + printf("\tcs configured = %d\n", cc->cs_configured); + + fputc('\n', stdout); + + for (i = 0; i < ARRAY_SIZE(cc->scores); i++) + printf("\tscore %s = %d\n", + i == UL_COLORFILE_DISABLE ? "disable" : + i == UL_COLORFILE_ENABLE ? "enable" : + i == UL_COLORFILE_SCHEME ? "scheme" : "???", + cc->scores[i]); + + fputc('\n', stdout); + + for (i = 0; i < cc->nschemes; i++) { + printf("\tscheme #%02zu ", i); + color_scheme_enable(cc->schemes[i].name, NULL); + fputs(cc->schemes[i].name, stdout); + color_disable(); + fputc('\n', stdout); + } + fputc('\n', stdout); +} + +/* + * Parses [[][@].] + */ +static int filename_to_tokens(const char *str, + const char **name, size_t *namesz, + const char **term, size_t *termsz, + int *filetype) +{ + const char *type_start, *term_start, *p; + + if (!str || !*str || *str == '.' || strlen(str) > PATH_MAX) + return -EINVAL; + + /* parse .type */ + p = strrchr(str, '.'); + type_start = p ? p + 1 : str; + + if (strcmp(type_start, "disable") == 0) + *filetype = UL_COLORFILE_DISABLE; + else if (strcmp(type_start, "enable") == 0) + *filetype = UL_COLORFILE_ENABLE; + else if (strcmp(type_start, "scheme") == 0) + *filetype = UL_COLORFILE_SCHEME; + else { + DBG(CONF, ul_debug("unknown type '%s'", type_start)); + return 1; /* unknown type */ + } + + if (type_start == str) + return 0; /* "type" only */ + + /* parse @termname */ + p = strchr(str, '@'); + term_start = p ? p + 1 : NULL; + if (term_start) { + *term = term_start; + *termsz = type_start - term_start - 1; + if (term_start - 1 == str) + return 0; /* "@termname.type" */ + } + + /* parse utilname */ + p = term_start ? term_start : type_start; + *name = str; + *namesz = p - str - 1; + + return 0; +} + +/* + * Scans @dirname and select the best matches for UL_COLORFILE_* types. + * The result is stored to cc->scores. The path to the best "scheme" + * file is stored to cc->scheme. + */ +static int colors_readdir(struct ul_color_ctl *cc, const char *dirname) +{ + DIR *dir; + int rc = 0; + struct dirent *d; + char sfile[PATH_MAX] = { '\0' }; + size_t namesz, termsz; + + if (!dirname || !cc || !cc->utilname || !*cc->utilname) + return -EINVAL; + + DBG(CONF, ul_debug("reading dir: '%s'", dirname)); + + dir = opendir(dirname); + if (!dir) + return -errno; + + namesz = strlen(cc->utilname); + termsz = cc->termname ? strlen(cc->termname) : 0; + + while ((d = readdir(dir))) { + int type, score = 1; + const char *tk_name = NULL, *tk_term = NULL; + size_t tk_namesz = 0, tk_termsz = 0; + + if (*d->d_name == '.') + continue; +#ifdef _DIRENT_HAVE_D_TYPE + if (d->d_type != DT_UNKNOWN && d->d_type != DT_LNK && + d->d_type != DT_REG) + continue; +#endif + if (filename_to_tokens(d->d_name, + &tk_name, &tk_namesz, + &tk_term, &tk_termsz, &type) != 0) + continue; + + /* count theoretical score before we check names to avoid + * unnecessary strcmp() */ + if (tk_name) + score += 20; + if (tk_term) + score += 10; + + DBG(CONF, ul_debug("item '%s': score=%d " + "[cur: %d, name(%zu): %s, term(%zu): %s]", + d->d_name, score, cc->scores[type], + tk_namesz, tk_name, + tk_termsz, tk_term)); + + + if (score < cc->scores[type]) + continue; + + /* filter out by names */ + if (tk_namesz && (tk_namesz != namesz || + strncmp(tk_name, cc->utilname, namesz) != 0)) + continue; + + if (tk_termsz && (termsz == 0 || tk_termsz != termsz || + strncmp(tk_term, cc->termname, termsz) != 0)) + continue; + + DBG(CONF, ul_debug("setting '%s' from %d -to-> %d", + type == UL_COLORFILE_SCHEME ? "scheme" : + type == UL_COLORFILE_DISABLE ? "disable" : + type == UL_COLORFILE_ENABLE ? "enable" : "???", + cc->scores[type], score)); + cc->scores[type] = score; + if (type == UL_COLORFILE_SCHEME) + strncpy(sfile, d->d_name, sizeof(sfile)); + } + + if (*sfile) { + sfile[sizeof(sfile) - 1] = '\0'; + if (asprintf(&cc->sfile, "%s/%s", dirname, sfile) <= 0) + rc = -ENOMEM; + } + + closedir(dir); + return rc; +} + +/* atexit() wrapper */ +static void colors_deinit(void) +{ + colors_reset(&ul_colors); +} + +/* + * Returns path to $XDG_CONFIG_HOME/terminal-colors.d + */ +static char *colors_get_homedir(char *buf, size_t bufsz) +{ + char *p = getenv("XDG_CONFIG_HOME"); + + if (p) { + snprintf(buf, bufsz, "%s/" _PATH_TERMCOLORS_DIRNAME, p); + return buf; + } + + p = getenv("HOME"); + if (p) { + snprintf(buf, bufsz, "%s/.config/" _PATH_TERMCOLORS_DIRNAME, p); + return buf; + } + + return NULL; +} + +/* canonicalize sequence */ +static int cn_sequence(const char *str, char **seq) +{ + char *in, *out; + int len; + + if (!str) + return -EINVAL; + + *seq = NULL; + + /* convert logical names like "red" to the real sequence */ + if (*str != '\\' && isalpha(*str)) { + const char *s = color_sequence_from_colorname(str); + *seq = strdup(s ? s : str); + + return *seq ? 0 : -ENOMEM; + } + + /* convert xx;yy sequences to "\033[xx;yy" */ + if ((len = asprintf(seq, "\033[%sm", str)) < 1) + return -ENOMEM; + + for (in = *seq, out = *seq; in && *in; in++) { + if (*in != '\\') { + *out++ = *in; + continue; + } + switch(*(in + 1)) { + case 'a': + *out++ = '\a'; /* Bell */ + break; + case 'b': + *out++ = '\b'; /* Backspace */ + break; + case 'e': + *out++ = '\033'; /* Escape */ + break; + case 'f': + *out++ = '\f'; /* Form Feed */ + break; + case 'n': + *out++ = '\n'; /* Newline */ + break; + case 'r': + *out++ = '\r'; /* Carriage Return */ + break; + case 't': + *out++ = '\t'; /* Tab */ + break; + case 'v': + *out++ = '\v'; /* Vertical Tab */ + break; + case '\\': + *out++ = '\\'; /* Backslash */ + break; + case '_': + *out++ = ' '; /* Space */ + break; + case '#': + *out++ = '#'; /* Hash mark */ + break; + case '?': + *out++ = '?'; /* Question mark */ + break; + default: + *out++ = *in; + *out++ = *(in + 1); + break; + } + in++; + } + + if (out) { + assert ((out - *seq) <= len); + *out = '\0'; + } + + return 0; +} + + +/* + * Adds one color sequence to array with color scheme. + * When returning success (0) this function takes ownership of + * @seq and @name, which have to be allocated strings. + */ +static int colors_add_scheme(struct ul_color_ctl *cc, + char *name, + char *seq0) +{ + struct ul_color_scheme *cs = NULL; + char *seq = NULL; + int rc; + + if (!cc || !name || !*name || !seq0 || !*seq0) + return -EINVAL; + + DBG(SCHEME, ul_debug("add '%s'", name)); + + rc = cn_sequence(seq0, &seq); + if (rc) + return rc; + + rc = -ENOMEM; + + /* convert logical name (e.g. "red") to real ESC code */ + if (isalpha(*seq)) { + const char *s = color_sequence_from_colorname(seq); + char *p; + + if (!s) { + DBG(SCHEME, ul_debug("unknown logical name: %s", seq)); + rc = -EINVAL; + goto err; + } + + p = strdup(s); + if (!p) + goto err; + free(seq); + seq = p; + } + + /* enlarge the array */ + if (cc->nschemes == cc->schemes_sz) { + void *tmp = realloc(cc->schemes, (cc->nschemes + 10) + * sizeof(struct ul_color_scheme)); + if (!tmp) + goto err; + cc->schemes = tmp; + cc->schemes_sz = cc->nschemes + 10; + } + + /* add a new item */ + cs = &cc->schemes[cc->nschemes]; + cs->seq = seq; + cs->name = strdup(name); + if (!cs->name) + goto err; + + cc->nschemes++; + return 0; +err: + if (cs) { + free(cs->seq); + free(cs->name); + cs->seq = cs->name = NULL; + } else + free(seq); + return rc; +} + +/* + * Deallocates all regards to color schemes + */ +static void colors_free_schemes(struct ul_color_ctl *cc) +{ + size_t i; + + DBG(SCHEME, ul_debug("free scheme")); + + for (i = 0; i < cc->nschemes; i++) { + free(cc->schemes[i].name); + free(cc->schemes[i].seq); + } + + free(cc->schemes); + cc->schemes = NULL; + cc->nschemes = 0; + cc->schemes_sz = 0; +} + +/* + * The scheme configuration has to be sorted for bsearch + */ +static void colors_sort_schemes(struct ul_color_ctl *cc) +{ + if (!cc->nschemes) + return; + + DBG(SCHEME, ul_debug("sort scheme")); + + qsort(cc->schemes, cc->nschemes, + sizeof(struct ul_color_scheme), cmp_scheme_name); +} + +/* + * Returns just one color scheme + */ +static struct ul_color_scheme *colors_get_scheme(struct ul_color_ctl *cc, + const char *name) +{ + struct ul_color_scheme key = { .name = (char *) name}, *res; + + if (!cc || !name || !*name) + return NULL; + + if (!cc->cs_configured) { + int rc = colors_read_schemes(cc); + if (rc) + return NULL; + } + if (!cc->nschemes) + return NULL; + + DBG(SCHEME, ul_debug("search '%s'", name)); + + res = bsearch(&key, cc->schemes, cc->nschemes, + sizeof(struct ul_color_scheme), + cmp_scheme_name); + + return res && res->seq ? res : NULL; +} + +/* + * Parses filenames in terminal-colors.d + */ +static int colors_read_configuration(struct ul_color_ctl *cc) +{ + int rc = -ENOENT; + char *dirname, buf[PATH_MAX]; + + cc->termname = getenv("TERM"); + + dirname = colors_get_homedir(buf, sizeof(buf)); + if (dirname) + rc = colors_readdir(cc, dirname); /* ~/.config */ + if (rc == -EPERM || rc == -EACCES || rc == -ENOENT) + rc = colors_readdir(cc, _PATH_TERMCOLORS_DIR); /* /etc */ + + cc->configured = 1; + return rc; +} + +/* + * Reads terminal-colors.d/ scheme file into array schemes + */ +static int colors_read_schemes(struct ul_color_ctl *cc) +{ + int rc = 0; + FILE *f = NULL; + char buf[BUFSIZ], + cn[129], seq[129]; + + if (!cc->configured) + rc = colors_read_configuration(cc); + + cc->cs_configured = 1; + + if (rc || !cc->sfile) + goto done; + + DBG(SCHEME, ul_debug("reading file '%s'", cc->sfile)); + + f = fopen(cc->sfile, "r"); + if (!f) { + rc = -errno; + goto done; + } + + while (fgets(buf, sizeof(buf), f)) { + char *p = strchr(buf, '\n'); + + if (!p) { + if (feof(f)) + p = strchr(buf, '\0'); + else { + rc = -errno; + goto done; + } + } + *p = '\0'; + p = (char *) skip_blank(buf); + if (*p == '\0' || *p == '#') + continue; + + rc = sscanf(p, "%128[^ ] %128[^\n ]", cn, seq); + if (rc == 2 && *cn && *seq) { + rc = colors_add_scheme(cc, cn, seq); /* set rc=0 on success */ + if (rc) + goto done; + } + } + rc = 0; + +done: + if (f) + fclose(f); + colors_sort_schemes(cc); + + return rc; +} + + +static void termcolors_init_debug(void) +{ + __UL_INIT_DEBUG_FROM_ENV(termcolors, TERMCOLORS_DEBUG_, 0, TERMINAL_COLORS_DEBUG); +} + +static int colors_terminal_is_ready(void) +{ + int ncolors = -1; + +#if defined(HAVE_LIBNCURSES) || defined(HAVE_LIBNCURSESW) + { + int ret; + + if (setupterm(NULL, STDOUT_FILENO, &ret) == 0 && ret == 1) + ncolors = tigetnum("colors"); + } +#endif + if (1 < ncolors) { + DBG(CONF, ul_debug("terminal is ready (supports %d colors)", ncolors)); + return 1; + } + + DBG(CONF, ul_debug("terminal is NOT ready (no colors)")); + return 0; +} + +/** + * colors_init: + * @mode: UL_COLORMODE_* + * @name: util argv[0] + * + * Initialize private color control struct and initialize the colors + * status. The color schemes are parsed on demand by colors_get_scheme(). + * + * Returns: >0 on success. + */ +int colors_init(int mode, const char *name) +{ + int ready = -1; + struct ul_color_ctl *cc = &ul_colors; + + cc->utilname = name; + + termcolors_init_debug(); + + if (mode != UL_COLORMODE_ALWAYS && !isatty(STDOUT_FILENO)) + cc->mode = UL_COLORMODE_NEVER; + else + cc->mode = mode; + + if (cc->mode == UL_COLORMODE_UNDEF + && (ready = colors_terminal_is_ready())) { + int rc = colors_read_configuration(cc); + if (rc) + cc->mode = UL_COLORMODE_DEFAULT; + else { + + /* evaluate scores */ + if (cc->scores[UL_COLORFILE_DISABLE] > + cc->scores[UL_COLORFILE_ENABLE]) + cc->mode = UL_COLORMODE_NEVER; + else + cc->mode = UL_COLORMODE_DEFAULT; + + atexit(colors_deinit); + } + } + + switch (cc->mode) { + case UL_COLORMODE_AUTO: + cc->has_colors = ready == -1 ? colors_terminal_is_ready() : ready; + break; + case UL_COLORMODE_ALWAYS: + cc->has_colors = 1; + break; + case UL_COLORMODE_NEVER: + default: + cc->has_colors = 0; + } + + ON_DBG(CONF, colors_debug(cc)); + + return cc->has_colors; +} + +/* + * Temporary disable colors (this setting is independent on terminal-colors.d/) + */ +void colors_off(void) +{ + ul_colors.disabled = 1; +} + +/* + * Enable colors + */ +void colors_on(void) +{ + ul_colors.disabled = 0; +} + +/* + * Is terminal-colors.d/ configured to use colors? + */ +int colors_wanted(void) +{ + return ul_colors.has_colors; +} + +/* + * Returns mode + */ +int colors_mode(void) +{ + return ul_colors.mode; +} + +/* + * Enable @seq color + */ +void color_fenable(const char *seq, FILE *f) +{ + if (!ul_colors.disabled && ul_colors.has_colors && seq) + fputs(seq, f); +} + +/* + * Returns escape sequence by logical @name, if undefined then returns @dflt. + */ +const char *color_scheme_get_sequence(const char *name, const char *dflt) +{ + struct ul_color_scheme *cs; + + if (ul_colors.disabled || !ul_colors.has_colors) + return NULL; + + cs = colors_get_scheme(&ul_colors, name); + return cs && cs->seq ? cs->seq : dflt; +} + +/* + * Enable color by logical @name, if undefined enable @dflt. + */ +void color_scheme_fenable(const char *name, const char *dflt, FILE *f) +{ + const char *seq = color_scheme_get_sequence(name, dflt); + + if (!seq) + return; + color_fenable(seq, f); +} + + +/* + * Disable previously enabled color + */ +void color_fdisable(FILE *f) +{ + if (!ul_colors.disabled && ul_colors.has_colors) + fputs(UL_COLOR_RESET, f); +} + +/* + * Parses @str to return UL_COLORMODE_* + */ +int colormode_from_string(const char *str) +{ + size_t i; + static const char *modes[] = { + [UL_COLORMODE_AUTO] = "auto", + [UL_COLORMODE_NEVER] = "never", + [UL_COLORMODE_ALWAYS] = "always", + [UL_COLORMODE_UNDEF] = "" + }; + + if (!str || !*str) + return -EINVAL; + + assert(ARRAY_SIZE(modes) == __UL_NCOLORMODES); + + for (i = 0; i < ARRAY_SIZE(modes); i++) { + if (strcasecmp(str, modes[i]) == 0) + return i; + } + + return -EINVAL; +} + +/* + * Parses @str and exit(EXIT_FAILURE) on error + */ +int colormode_or_err(const char *str, const char *errmsg) +{ + const char *p = str && *str == '=' ? str + 1 : str; + int colormode; + + colormode = colormode_from_string(p); + if (colormode < 0) + errx(EXIT_FAILURE, "%s: '%s'", errmsg, p); + + return colormode; +} + +#ifdef TEST_PROGRAM_COLORS +# include +int main(int argc, char *argv[]) +{ + static const struct option longopts[] = { + { "mode", required_argument, NULL, 'm' }, + { "color", required_argument, NULL, 'c' }, + { "color-scheme", required_argument, NULL, 'C' }, + { "name", required_argument, NULL, 'n' }, + { NULL, 0, NULL, 0 } + }; + int c, mode = UL_COLORMODE_UNDEF; /* default */ + const char *color = "red", *name = NULL, *color_scheme = NULL; + const char *seq = NULL; + + while ((c = getopt_long(argc, argv, "C:c:m:n:", longopts, NULL)) != -1) { + switch (c) { + case 'c': + color = optarg; + break; + case 'C': + color_scheme = optarg; + break; + case 'm': + mode = colormode_or_err(optarg, "unsupported color mode"); + break; + case 'n': + name = optarg; + break; + default: + fprintf(stderr, "usage: %s [options]\n" + " -m, --mode default is undefined\n" + " -c, --color color for the test message\n" + " -C, --color-scheme color for the test message\n" + " -n, --name util name\n", + program_invocation_short_name); + return EXIT_FAILURE; + } + } + + colors_init(mode, name ? name : program_invocation_short_name); + + seq = color_sequence_from_colorname(color); + + if (color_scheme) + color_scheme_enable(color_scheme, seq); + else + color_enable(seq); + printf("Hello World!"); + color_disable(); + fputc('\n', stdout); + + return EXIT_SUCCESS; +} +#endif /* TEST_PROGRAM_COLORS */ + diff --git a/utils/lib/cpuset.c b/utils/lib/cpuset.c new file mode 100644 index 0000000..2847db8 --- /dev/null +++ b/utils/lib/cpuset.c @@ -0,0 +1,413 @@ +/* + * Terminology: + * + * cpuset - (libc) cpu_set_t data structure represents set of CPUs + * cpumask - string with hex mask (e.g. "0x00000001") + * cpulist - string with CPU ranges (e.g. "0-3,5,7,8") + * + * Based on code from taskset.c and Linux kernel. + * + * This file may be redistributed under the terms of the + * GNU Lesser General Public License. + * + * Copyright (C) 2010 Karel Zak + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "cpuset.h" +#include "c.h" + +static inline int val_to_char(int v) +{ + if (v >= 0 && v < 10) + return '0' + v; + if (v >= 10 && v < 16) + return ('a' - 10) + v; + return -1; +} + +static inline int char_to_val(int c) +{ + int cl; + + if (c >= '0' && c <= '9') + return c - '0'; + cl = tolower(c); + if (cl >= 'a' && cl <= 'f') + return cl + (10 - 'a'); + return -1; +} + +static const char *nexttoken(const char *q, int sep) +{ + if (q) + q = strchr(q, sep); + if (q) + q++; + return q; +} + +/* + * Number of bits in a CPU bitmask on current system + */ +int get_max_number_of_cpus(void) +{ +#ifdef SYS_sched_getaffinity + int n, cpus = 2048; + size_t setsize; + cpu_set_t *set = cpuset_alloc(cpus, &setsize, NULL); + + if (!set) + return -1; /* error */ + + for (;;) { + CPU_ZERO_S(setsize, set); + + /* the library version does not return size of cpumask_t */ + n = syscall(SYS_sched_getaffinity, 0, setsize, set); + + if (n < 0 && errno == EINVAL && cpus < 1024 * 1024) { + cpuset_free(set); + cpus *= 2; + set = cpuset_alloc(cpus, &setsize, NULL); + if (!set) + return -1; /* error */ + continue; + } + cpuset_free(set); + return n * 8; + } +#endif + return -1; +} + +/* + * Allocates a new set for ncpus and returns size in bytes and size in bits + */ +cpu_set_t *cpuset_alloc(int ncpus, size_t *setsize, size_t *nbits) +{ + cpu_set_t *set = CPU_ALLOC(ncpus); + + if (!set) + return NULL; + if (setsize) + *setsize = CPU_ALLOC_SIZE(ncpus); + if (nbits) + *nbits = cpuset_nbits(CPU_ALLOC_SIZE(ncpus)); + return set; +} + +void cpuset_free(cpu_set_t *set) +{ + CPU_FREE(set); +} + +#if !HAVE_DECL_CPU_ALLOC +/* Please, use CPU_COUNT_S() macro. This is fallback */ +int __cpuset_count_s(size_t setsize, const cpu_set_t *set) +{ + int s = 0; + const __cpu_mask *p = set->__bits; + const __cpu_mask *end = &set->__bits[setsize / sizeof (__cpu_mask)]; + + while (p < end) { + __cpu_mask l = *p++; + + if (l == 0) + continue; +# if LONG_BIT > 32 + l = (l & 0x5555555555555555ul) + ((l >> 1) & 0x5555555555555555ul); + l = (l & 0x3333333333333333ul) + ((l >> 2) & 0x3333333333333333ul); + l = (l & 0x0f0f0f0f0f0f0f0ful) + ((l >> 4) & 0x0f0f0f0f0f0f0f0ful); + l = (l & 0x00ff00ff00ff00fful) + ((l >> 8) & 0x00ff00ff00ff00fful); + l = (l & 0x0000ffff0000fffful) + ((l >> 16) & 0x0000ffff0000fffful); + l = (l & 0x00000000fffffffful) + ((l >> 32) & 0x00000000fffffffful); +# else + l = (l & 0x55555555ul) + ((l >> 1) & 0x55555555ul); + l = (l & 0x33333333ul) + ((l >> 2) & 0x33333333ul); + l = (l & 0x0f0f0f0ful) + ((l >> 4) & 0x0f0f0f0ful); + l = (l & 0x00ff00fful) + ((l >> 8) & 0x00ff00fful); + l = (l & 0x0000fffful) + ((l >> 16) & 0x0000fffful); +# endif + s += l; + } + return s; +} +#endif + +/* + * Returns human readable representation of the cpuset. The output format is + * a list of CPUs with ranges (for example, "0,1,3-9"). + */ +char *cpulist_create(char *str, size_t len, + cpu_set_t *set, size_t setsize) +{ + size_t i; + char *ptr = str; + int entry_made = 0; + size_t max = cpuset_nbits(setsize); + + for (i = 0; i < max; i++) { + if (CPU_ISSET_S(i, setsize, set)) { + int rlen; + size_t j, run = 0; + entry_made = 1; + for (j = i + 1; j < max; j++) { + if (CPU_ISSET_S(j, setsize, set)) + run++; + else + break; + } + if (!run) + rlen = snprintf(ptr, len, "%zu,", i); + else if (run == 1) { + rlen = snprintf(ptr, len, "%zu,%zu,", i, i + 1); + i++; + } else { + rlen = snprintf(ptr, len, "%zu-%zu,", i, i + run); + i += run; + } + if (rlen < 0 || (size_t) rlen >= len) + return NULL; + ptr += rlen; + len -= rlen; + } + } + ptr -= entry_made; + *ptr = '\0'; + + return str; +} + +/* + * Returns string with CPU mask. + */ +char *cpumask_create(char *str, size_t len, + cpu_set_t *set, size_t setsize) +{ + char *ptr = str; + char *ret = NULL; + int cpu; + + for (cpu = cpuset_nbits(setsize) - 4; cpu >= 0; cpu -= 4) { + char val = 0; + + if (len == (size_t) (ptr - str)) + break; + + if (CPU_ISSET_S(cpu, setsize, set)) + val |= 1; + if (CPU_ISSET_S(cpu + 1, setsize, set)) + val |= 2; + if (CPU_ISSET_S(cpu + 2, setsize, set)) + val |= 4; + if (CPU_ISSET_S(cpu + 3, setsize, set)) + val |= 8; + + if (!ret && val) + ret = ptr; + *ptr++ = val_to_char(val); + } + *ptr = '\0'; + return ret ? ret : ptr - 1; +} + +/* + * Parses string with CPUs mask. + */ +int cpumask_parse(const char *str, cpu_set_t *set, size_t setsize) +{ + int len = strlen(str); + const char *ptr = str + len - 1; + int cpu = 0; + + /* skip 0x, it's all hex anyway */ + if (len > 1 && !memcmp(str, "0x", 2L)) + str += 2; + + CPU_ZERO_S(setsize, set); + + while (ptr >= str) { + char val; + + /* cpu masks in /sys uses comma as a separator */ + if (*ptr == ',') + ptr--; + + val = char_to_val(*ptr); + if (val == (char) -1) + return -1; + if (val & 1) + CPU_SET_S(cpu, setsize, set); + if (val & 2) + CPU_SET_S(cpu + 1, setsize, set); + if (val & 4) + CPU_SET_S(cpu + 2, setsize, set); + if (val & 8) + CPU_SET_S(cpu + 3, setsize, set); + ptr--; + cpu += 4; + } + + return 0; +} + +static int nextnumber(const char *str, char **end, unsigned int *result) +{ + errno = 0; + if (str == NULL || *str == '\0' || !isdigit(*str)) + return -EINVAL; + *result = (unsigned int) strtoul(str, end, 10); + if (errno) + return -errno; + if (str == *end) + return -EINVAL; + return 0; +} + +/* + * Parses string with list of CPU ranges. + * Returns 0 on success. + * Returns 1 on error. + * Returns 2 if fail is set and a cpu number passed in the list doesn't fit + * into the cpu_set. If fail is not set cpu numbers that do not fit are + * ignored and 0 is returned instead. + */ +int cpulist_parse(const char *str, cpu_set_t *set, size_t setsize, int fail) +{ + size_t max = cpuset_nbits(setsize); + const char *p, *q; + char *end = NULL; + + q = str; + CPU_ZERO_S(setsize, set); + + while (p = q, q = nexttoken(q, ','), p) { + unsigned int a; /* beginning of range */ + unsigned int b; /* end of range */ + unsigned int s; /* stride */ + const char *c1, *c2; + + if (nextnumber(p, &end, &a) != 0) + return 1; + b = a; + s = 1; + p = end; + + c1 = nexttoken(p, '-'); + c2 = nexttoken(p, ','); + + if (c1 != NULL && (c2 == NULL || c1 < c2)) { + if (nextnumber(c1, &end, &b) != 0) + return 1; + + c1 = end && *end ? nexttoken(end, ':') : NULL; + + if (c1 != NULL && (c2 == NULL || c1 < c2)) { + if (nextnumber(c1, &end, &s) != 0) + return 1; + if (s == 0) + return 1; + } + } + + if (!(a <= b)) + return 1; + while (a <= b) { + if (fail && (a >= max)) + return 2; + CPU_SET_S(a, setsize, set); + a += s; + } + } + + if (end && *end) + return 1; + return 0; +} + +#ifdef TEST_PROGRAM_CPUSET + +#include + +int main(int argc, char *argv[]) +{ + cpu_set_t *set; + size_t setsize, buflen, nbits; + char *buf, *mask = NULL, *range = NULL; + int ncpus = 2048, rc, c; + + static const struct option longopts[] = { + { "ncpus", 1, NULL, 'n' }, + { "mask", 1, NULL, 'm' }, + { "range", 1, NULL, 'r' }, + { NULL, 0, NULL, 0 } + }; + + while ((c = getopt_long(argc, argv, "n:m:r:", longopts, NULL)) != -1) { + switch(c) { + case 'n': + ncpus = atoi(optarg); + break; + case 'm': + mask = strdup(optarg); + break; + case 'r': + range = strdup(optarg); + break; + default: + goto usage_err; + } + } + + if (!mask && !range) + goto usage_err; + + set = cpuset_alloc(ncpus, &setsize, &nbits); + if (!set) + err(EXIT_FAILURE, "failed to allocate cpu set"); + + /* + fprintf(stderr, "ncpus: %d, cpuset bits: %zd, cpuset bytes: %zd\n", + ncpus, nbits, setsize); + */ + + buflen = 7 * nbits; + buf = malloc(buflen); + if (!buf) + err(EXIT_FAILURE, "failed to allocate cpu set buffer"); + + if (mask) + rc = cpumask_parse(mask, set, setsize); + else + rc = cpulist_parse(range, set, setsize, 0); + + if (rc) + errx(EXIT_FAILURE, "failed to parse string: %s", mask ? : range); + + printf("%-15s = %15s ", mask ? : range, + cpumask_create(buf, buflen, set, setsize)); + printf("[%s]\n", cpulist_create(buf, buflen, set, setsize)); + + free(buf); + free(mask); + free(range); + cpuset_free(set); + + return EXIT_SUCCESS; + +usage_err: + fprintf(stderr, + "usage: %s [--ncpus ] --mask | --range \n", + program_invocation_short_name); + exit(EXIT_FAILURE); +} +#endif diff --git a/utils/lib/crc32.c b/utils/lib/crc32.c new file mode 100644 index 0000000..824693d --- /dev/null +++ b/utils/lib/crc32.c @@ -0,0 +1,142 @@ +/* + * COPYRIGHT (C) 1986 Gary S. Brown. You may use this program, or + * code or tables extracted from it, as desired without restriction. + * + * First, the polynomial itself and its table of feedback terms. The + * polynomial is + * X^32+X^26+X^23+X^22+X^16+X^12+X^11+X^10+X^8+X^7+X^5+X^4+X^2+X^1+X^0 + * + * Note that we take it "backwards" and put the highest-order term in + * the lowest-order bit. The X^32 term is "implied"; the LSB is the + * X^31 term, etc. The X^0 term (usually shown as "+1") results in + * the MSB being 1. + * + * Note that the usual hardware shift register implementation, which + * is what we're using (we're merely optimizing it by doing eight-bit + * chunks at a time) shifts bits into the lowest-order term. In our + * implementation, that means shifting towards the right. Why do we + * do it this way? Because the calculated CRC must be transmitted in + * order from highest-order term to lowest-order term. UARTs transmit + * characters in order from LSB to MSB. By storing the CRC this way, + * we hand it to the UART in the order low-byte to high-byte; the UART + * sends each low-bit to high-bit; and the result is transmission bit + * by bit from highest- to lowest-order term without requiring any bit + * shuffling on our part. Reception works similarly. + * + * The feedback terms table consists of 256, 32-bit entries. Notes + * + * The table can be generated at runtime if desired; code to do so + * is shown later. It might not be obvious, but the feedback + * terms simply represent the results of eight shift/xor opera- + * tions for all combinations of data and CRC register values. + * + * The values must be right-shifted by eight bits by the "updcrc" + * logic; the shift must be unsigned (bring in zeroes). On some + * hardware you could probably optimize the shift in assembler by + * using byte-swap instructions. + * polynomial $edb88320 + * + */ + +#include + +#include "crc32.h" + + +static const uint32_t crc32_tab[] = { + 0x00000000L, 0x77073096L, 0xee0e612cL, 0x990951baL, 0x076dc419L, + 0x706af48fL, 0xe963a535L, 0x9e6495a3L, 0x0edb8832L, 0x79dcb8a4L, + 0xe0d5e91eL, 0x97d2d988L, 0x09b64c2bL, 0x7eb17cbdL, 0xe7b82d07L, + 0x90bf1d91L, 0x1db71064L, 0x6ab020f2L, 0xf3b97148L, 0x84be41deL, + 0x1adad47dL, 0x6ddde4ebL, 0xf4d4b551L, 0x83d385c7L, 0x136c9856L, + 0x646ba8c0L, 0xfd62f97aL, 0x8a65c9ecL, 0x14015c4fL, 0x63066cd9L, + 0xfa0f3d63L, 0x8d080df5L, 0x3b6e20c8L, 0x4c69105eL, 0xd56041e4L, + 0xa2677172L, 0x3c03e4d1L, 0x4b04d447L, 0xd20d85fdL, 0xa50ab56bL, + 0x35b5a8faL, 0x42b2986cL, 0xdbbbc9d6L, 0xacbcf940L, 0x32d86ce3L, + 0x45df5c75L, 0xdcd60dcfL, 0xabd13d59L, 0x26d930acL, 0x51de003aL, + 0xc8d75180L, 0xbfd06116L, 0x21b4f4b5L, 0x56b3c423L, 0xcfba9599L, + 0xb8bda50fL, 0x2802b89eL, 0x5f058808L, 0xc60cd9b2L, 0xb10be924L, + 0x2f6f7c87L, 0x58684c11L, 0xc1611dabL, 0xb6662d3dL, 0x76dc4190L, + 0x01db7106L, 0x98d220bcL, 0xefd5102aL, 0x71b18589L, 0x06b6b51fL, + 0x9fbfe4a5L, 0xe8b8d433L, 0x7807c9a2L, 0x0f00f934L, 0x9609a88eL, + 0xe10e9818L, 0x7f6a0dbbL, 0x086d3d2dL, 0x91646c97L, 0xe6635c01L, + 0x6b6b51f4L, 0x1c6c6162L, 0x856530d8L, 0xf262004eL, 0x6c0695edL, + 0x1b01a57bL, 0x8208f4c1L, 0xf50fc457L, 0x65b0d9c6L, 0x12b7e950L, + 0x8bbeb8eaL, 0xfcb9887cL, 0x62dd1ddfL, 0x15da2d49L, 0x8cd37cf3L, + 0xfbd44c65L, 0x4db26158L, 0x3ab551ceL, 0xa3bc0074L, 0xd4bb30e2L, + 0x4adfa541L, 0x3dd895d7L, 0xa4d1c46dL, 0xd3d6f4fbL, 0x4369e96aL, + 0x346ed9fcL, 0xad678846L, 0xda60b8d0L, 0x44042d73L, 0x33031de5L, + 0xaa0a4c5fL, 0xdd0d7cc9L, 0x5005713cL, 0x270241aaL, 0xbe0b1010L, + 0xc90c2086L, 0x5768b525L, 0x206f85b3L, 0xb966d409L, 0xce61e49fL, + 0x5edef90eL, 0x29d9c998L, 0xb0d09822L, 0xc7d7a8b4L, 0x59b33d17L, + 0x2eb40d81L, 0xb7bd5c3bL, 0xc0ba6cadL, 0xedb88320L, 0x9abfb3b6L, + 0x03b6e20cL, 0x74b1d29aL, 0xead54739L, 0x9dd277afL, 0x04db2615L, + 0x73dc1683L, 0xe3630b12L, 0x94643b84L, 0x0d6d6a3eL, 0x7a6a5aa8L, + 0xe40ecf0bL, 0x9309ff9dL, 0x0a00ae27L, 0x7d079eb1L, 0xf00f9344L, + 0x8708a3d2L, 0x1e01f268L, 0x6906c2feL, 0xf762575dL, 0x806567cbL, + 0x196c3671L, 0x6e6b06e7L, 0xfed41b76L, 0x89d32be0L, 0x10da7a5aL, + 0x67dd4accL, 0xf9b9df6fL, 0x8ebeeff9L, 0x17b7be43L, 0x60b08ed5L, + 0xd6d6a3e8L, 0xa1d1937eL, 0x38d8c2c4L, 0x4fdff252L, 0xd1bb67f1L, + 0xa6bc5767L, 0x3fb506ddL, 0x48b2364bL, 0xd80d2bdaL, 0xaf0a1b4cL, + 0x36034af6L, 0x41047a60L, 0xdf60efc3L, 0xa867df55L, 0x316e8eefL, + 0x4669be79L, 0xcb61b38cL, 0xbc66831aL, 0x256fd2a0L, 0x5268e236L, + 0xcc0c7795L, 0xbb0b4703L, 0x220216b9L, 0x5505262fL, 0xc5ba3bbeL, + 0xb2bd0b28L, 0x2bb45a92L, 0x5cb36a04L, 0xc2d7ffa7L, 0xb5d0cf31L, + 0x2cd99e8bL, 0x5bdeae1dL, 0x9b64c2b0L, 0xec63f226L, 0x756aa39cL, + 0x026d930aL, 0x9c0906a9L, 0xeb0e363fL, 0x72076785L, 0x05005713L, + 0x95bf4a82L, 0xe2b87a14L, 0x7bb12baeL, 0x0cb61b38L, 0x92d28e9bL, + 0xe5d5be0dL, 0x7cdcefb7L, 0x0bdbdf21L, 0x86d3d2d4L, 0xf1d4e242L, + 0x68ddb3f8L, 0x1fda836eL, 0x81be16cdL, 0xf6b9265bL, 0x6fb077e1L, + 0x18b74777L, 0x88085ae6L, 0xff0f6a70L, 0x66063bcaL, 0x11010b5cL, + 0x8f659effL, 0xf862ae69L, 0x616bffd3L, 0x166ccf45L, 0xa00ae278L, + 0xd70dd2eeL, 0x4e048354L, 0x3903b3c2L, 0xa7672661L, 0xd06016f7L, + 0x4969474dL, 0x3e6e77dbL, 0xaed16a4aL, 0xd9d65adcL, 0x40df0b66L, + 0x37d83bf0L, 0xa9bcae53L, 0xdebb9ec5L, 0x47b2cf7fL, 0x30b5ffe9L, + 0xbdbdf21cL, 0xcabac28aL, 0x53b39330L, 0x24b4a3a6L, 0xbad03605L, + 0xcdd70693L, 0x54de5729L, 0x23d967bfL, 0xb3667a2eL, 0xc4614ab8L, + 0x5d681b02L, 0x2a6f2b94L, 0xb40bbe37L, 0xc30c8ea1L, 0x5a05df1bL, + 0x2d02ef8dL +}; + +static inline uint32_t crc32_add_char(uint32_t crc, unsigned char c) +{ + return crc32_tab[(crc ^ c) & 0xff] ^ (crc >> 8); +} + +/* + * This a generic crc32() function, it takes seed as an argument, + * and does __not__ xor at the end. Then individual users can do + * whatever they need. + */ +uint32_t ul_crc32(uint32_t seed, const unsigned char *buf, size_t len) +{ + uint32_t crc = seed; + const unsigned char *p = buf; + + while (len) { + crc = crc32_add_char(crc, *p++); + len--; + } + + return crc; +} + +uint32_t ul_crc32_exclude_offset(uint32_t seed, const unsigned char *buf, size_t len, + size_t exclude_off, size_t exclude_len) +{ + uint32_t crc = seed; + const unsigned char *p = buf; + size_t i; + + for (i = 0; i < len; i++) { + unsigned char x = *p++; + + if (i >= exclude_off && i < exclude_off + exclude_len) + x = 0; + + crc = crc32_add_char(crc, x); + } + + return crc; +} + diff --git a/utils/lib/crc32c.c b/utils/lib/crc32c.c new file mode 100644 index 0000000..49e7543 --- /dev/null +++ b/utils/lib/crc32c.c @@ -0,0 +1,102 @@ +/* + * This code is from freebsd/sys/libkern/crc32.c + * + * Simplest table-based crc32c. Performance is not important + * for checking crcs on superblocks + */ + +/*- + * COPYRIGHT (C) 1986 Gary S. Brown. You may use this program, or + * code or tables extracted from it, as desired without restriction. + */ + +#include "crc32c.h" + +static const uint32_t crc32Table[256] = { + 0x00000000L, 0xF26B8303L, 0xE13B70F7L, 0x1350F3F4L, + 0xC79A971FL, 0x35F1141CL, 0x26A1E7E8L, 0xD4CA64EBL, + 0x8AD958CFL, 0x78B2DBCCL, 0x6BE22838L, 0x9989AB3BL, + 0x4D43CFD0L, 0xBF284CD3L, 0xAC78BF27L, 0x5E133C24L, + 0x105EC76FL, 0xE235446CL, 0xF165B798L, 0x030E349BL, + 0xD7C45070L, 0x25AFD373L, 0x36FF2087L, 0xC494A384L, + 0x9A879FA0L, 0x68EC1CA3L, 0x7BBCEF57L, 0x89D76C54L, + 0x5D1D08BFL, 0xAF768BBCL, 0xBC267848L, 0x4E4DFB4BL, + 0x20BD8EDEL, 0xD2D60DDDL, 0xC186FE29L, 0x33ED7D2AL, + 0xE72719C1L, 0x154C9AC2L, 0x061C6936L, 0xF477EA35L, + 0xAA64D611L, 0x580F5512L, 0x4B5FA6E6L, 0xB93425E5L, + 0x6DFE410EL, 0x9F95C20DL, 0x8CC531F9L, 0x7EAEB2FAL, + 0x30E349B1L, 0xC288CAB2L, 0xD1D83946L, 0x23B3BA45L, + 0xF779DEAEL, 0x05125DADL, 0x1642AE59L, 0xE4292D5AL, + 0xBA3A117EL, 0x4851927DL, 0x5B016189L, 0xA96AE28AL, + 0x7DA08661L, 0x8FCB0562L, 0x9C9BF696L, 0x6EF07595L, + 0x417B1DBCL, 0xB3109EBFL, 0xA0406D4BL, 0x522BEE48L, + 0x86E18AA3L, 0x748A09A0L, 0x67DAFA54L, 0x95B17957L, + 0xCBA24573L, 0x39C9C670L, 0x2A993584L, 0xD8F2B687L, + 0x0C38D26CL, 0xFE53516FL, 0xED03A29BL, 0x1F682198L, + 0x5125DAD3L, 0xA34E59D0L, 0xB01EAA24L, 0x42752927L, + 0x96BF4DCCL, 0x64D4CECFL, 0x77843D3BL, 0x85EFBE38L, + 0xDBFC821CL, 0x2997011FL, 0x3AC7F2EBL, 0xC8AC71E8L, + 0x1C661503L, 0xEE0D9600L, 0xFD5D65F4L, 0x0F36E6F7L, + 0x61C69362L, 0x93AD1061L, 0x80FDE395L, 0x72966096L, + 0xA65C047DL, 0x5437877EL, 0x4767748AL, 0xB50CF789L, + 0xEB1FCBADL, 0x197448AEL, 0x0A24BB5AL, 0xF84F3859L, + 0x2C855CB2L, 0xDEEEDFB1L, 0xCDBE2C45L, 0x3FD5AF46L, + 0x7198540DL, 0x83F3D70EL, 0x90A324FAL, 0x62C8A7F9L, + 0xB602C312L, 0x44694011L, 0x5739B3E5L, 0xA55230E6L, + 0xFB410CC2L, 0x092A8FC1L, 0x1A7A7C35L, 0xE811FF36L, + 0x3CDB9BDDL, 0xCEB018DEL, 0xDDE0EB2AL, 0x2F8B6829L, + 0x82F63B78L, 0x709DB87BL, 0x63CD4B8FL, 0x91A6C88CL, + 0x456CAC67L, 0xB7072F64L, 0xA457DC90L, 0x563C5F93L, + 0x082F63B7L, 0xFA44E0B4L, 0xE9141340L, 0x1B7F9043L, + 0xCFB5F4A8L, 0x3DDE77ABL, 0x2E8E845FL, 0xDCE5075CL, + 0x92A8FC17L, 0x60C37F14L, 0x73938CE0L, 0x81F80FE3L, + 0x55326B08L, 0xA759E80BL, 0xB4091BFFL, 0x466298FCL, + 0x1871A4D8L, 0xEA1A27DBL, 0xF94AD42FL, 0x0B21572CL, + 0xDFEB33C7L, 0x2D80B0C4L, 0x3ED04330L, 0xCCBBC033L, + 0xA24BB5A6L, 0x502036A5L, 0x4370C551L, 0xB11B4652L, + 0x65D122B9L, 0x97BAA1BAL, 0x84EA524EL, 0x7681D14DL, + 0x2892ED69L, 0xDAF96E6AL, 0xC9A99D9EL, 0x3BC21E9DL, + 0xEF087A76L, 0x1D63F975L, 0x0E330A81L, 0xFC588982L, + 0xB21572C9L, 0x407EF1CAL, 0x532E023EL, 0xA145813DL, + 0x758FE5D6L, 0x87E466D5L, 0x94B49521L, 0x66DF1622L, + 0x38CC2A06L, 0xCAA7A905L, 0xD9F75AF1L, 0x2B9CD9F2L, + 0xFF56BD19L, 0x0D3D3E1AL, 0x1E6DCDEEL, 0xEC064EEDL, + 0xC38D26C4L, 0x31E6A5C7L, 0x22B65633L, 0xD0DDD530L, + 0x0417B1DBL, 0xF67C32D8L, 0xE52CC12CL, 0x1747422FL, + 0x49547E0BL, 0xBB3FFD08L, 0xA86F0EFCL, 0x5A048DFFL, + 0x8ECEE914L, 0x7CA56A17L, 0x6FF599E3L, 0x9D9E1AE0L, + 0xD3D3E1ABL, 0x21B862A8L, 0x32E8915CL, 0xC083125FL, + 0x144976B4L, 0xE622F5B7L, 0xF5720643L, 0x07198540L, + 0x590AB964L, 0xAB613A67L, 0xB831C993L, 0x4A5A4A90L, + 0x9E902E7BL, 0x6CFBAD78L, 0x7FAB5E8CL, 0x8DC0DD8FL, + 0xE330A81AL, 0x115B2B19L, 0x020BD8EDL, 0xF0605BEEL, + 0x24AA3F05L, 0xD6C1BC06L, 0xC5914FF2L, 0x37FACCF1L, + 0x69E9F0D5L, 0x9B8273D6L, 0x88D28022L, 0x7AB90321L, + 0xAE7367CAL, 0x5C18E4C9L, 0x4F48173DL, 0xBD23943EL, + 0xF36E6F75L, 0x0105EC76L, 0x12551F82L, 0xE03E9C81L, + 0x34F4F86AL, 0xC69F7B69L, 0xD5CF889DL, 0x27A40B9EL, + 0x79B737BAL, 0x8BDCB4B9L, 0x988C474DL, 0x6AE7C44EL, + 0xBE2DA0A5L, 0x4C4623A6L, 0x5F16D052L, 0xAD7D5351L +}; + +/* + *This was singletable_crc32c() in bsd + * + * If you will not be passing crc back into this function to process more bytes, + * the answer is: + * + * crc = crc32c(~0L, buf, size); + * [ crc = crc32c(crc, buf, size); ] + * crc ^= ~0L + * + */ +uint32_t +crc32c(uint32_t crc, const void *buf, size_t size) +{ + const uint8_t *p = buf; + + while (size--) + crc = crc32Table[(crc ^ *p++) & 0xff] ^ (crc >> 8); + + return crc; +} diff --git a/utils/lib/encode.c b/utils/lib/encode.c new file mode 100644 index 0000000..10b5971 --- /dev/null +++ b/utils/lib/encode.c @@ -0,0 +1,79 @@ +/* + * Based on code from libblkid, + * + * Copyright (C) 2008 Kay Sievers + * Copyright (C) 2009 Karel Zak + * Copyright (C) 2020 Pali Rohár + * + * This file may be redistributed under the terms of the + * GNU Lesser General Public License. + */ +#include "c.h" +#include "encode.h" + +size_t ul_encode_to_utf8(int enc, unsigned char *dest, size_t len, + const unsigned char *src, size_t count) +{ + size_t i, j; + uint32_t c; + uint16_t c2; + + for (j = i = 0; i < count; i++) { + if (enc == UL_ENCODE_UTF16LE) { + if (i+2 > count) + break; + c = (src[i+1] << 8) | src[i]; + i++; + } else if (enc == UL_ENCODE_UTF16BE) { + if (i+2 > count) + break; + c = (src[i] << 8) | src[i+1]; + i++; + } else if (enc == UL_ENCODE_LATIN1) { + c = src[i]; + } else { + return 0; + } + if ((enc == UL_ENCODE_UTF16LE || enc == UL_ENCODE_UTF16BE) && + c >= 0xD800 && c <= 0xDBFF && i+2 < count) { + if (enc == UL_ENCODE_UTF16LE) + c2 = (src[i+2] << 8) | src[i+1]; + else + c2 = (src[i+1] << 8) | src[i+2]; + if (c2 >= 0xDC00 && c2 <= 0xDFFF) { + c = 0x10000 + ((c - 0xD800) << 10) + (c2 - 0xDC00); + i += 2; + } + } + if (c == 0) { + dest[j] = '\0'; + break; + } + + if (c < 0x80) { + if (j+1 >= len) + break; + dest[j++] = (uint8_t) c; + } else if (c < 0x800) { + if (j+2 >= len) + break; + dest[j++] = (uint8_t) (0xc0 | (c >> 6)); + dest[j++] = (uint8_t) (0x80 | (c & 0x3f)); + } else if (c < 0x10000) { + if (j+3 >= len) + break; + dest[j++] = (uint8_t) (0xe0 | (c >> 12)); + dest[j++] = (uint8_t) (0x80 | ((c >> 6) & 0x3f)); + dest[j++] = (uint8_t) (0x80 | (c & 0x3f)); + } else { + if (j+4 >= len) + break; + dest[j++] = (uint8_t) (0xf0 | (c >> 18)); + dest[j++] = (uint8_t) (0x80 | ((c >> 12) & 0x3f)); + dest[j++] = (uint8_t) (0x80 | ((c >> 6) & 0x3f)); + dest[j++] = (uint8_t) (0x80 | (c & 0x3f)); + } + } + dest[j] = '\0'; + return j; +} diff --git a/utils/lib/env.c b/utils/lib/env.c new file mode 100644 index 0000000..c26a5be --- /dev/null +++ b/utils/lib/env.c @@ -0,0 +1,238 @@ +/* + * Security checks of environment + * Added from shadow-utils package + * by Arkadiusz MiÅ›kiewicz + * + */ + +#include +#include +#include +#ifdef HAVE_SYS_PRCTL_H +#include +#else +#define PR_GET_DUMPABLE 3 +#endif +#if (!defined(HAVE_PRCTL) && defined(linux)) +#include +#endif +#include +#include + +#include "env.h" + +#ifndef HAVE_ENVIRON_DECL +extern char **environ; +#endif + +static char * const forbid[] = { + "BASH_ENV=", /* GNU creeping featurism strikes again... */ + "ENV=", + "HOME=", + "IFS=", + "KRB_CONF=", + "LD_", /* anything with the LD_ prefix */ + "LIBPATH=", + "MAIL=", + "NLSPATH=", + "PATH=", + "SHELL=", + "SHLIB_PATH=", + (char *) 0 +}; + +/* these are allowed, but with no slashes inside + (to work around security problems in GNU gettext) */ +static char * const noslash[] = { + "LANG=", + "LANGUAGE=", + "LC_", /* anything with the LC_ prefix */ + (char *) 0 +}; + + +struct ul_env_list { + char *env; + struct ul_env_list *next; +}; + +/* + * Saves @name env.varable to @ls, returns pointer to the new head of the list. + */ +static struct ul_env_list *env_list_add(struct ul_env_list *ls0, const char *str) +{ + struct ul_env_list *ls; + char *p; + size_t sz = 0; + + if (!str || !*str) + return ls0; + + sz = strlen(str) + 1; + p = malloc(sizeof(struct ul_env_list) + sz); + + ls = (struct ul_env_list *) p; + p += sizeof(struct ul_env_list); + memcpy(p, str, sz); + ls->env = p; + + ls->next = ls0; + return ls; +} + +/* + * Use setenv() for all stuff in @ls. + * + * It would be possible to use putenv(), but we want to keep @ls free()-able. + */ +int env_list_setenv(struct ul_env_list *ls) +{ + int rc = 0; + + while (ls && rc == 0) { + if (ls->env) { + char *val = strchr(ls->env, '='); + if (!val) + continue; + *val = '\0'; + rc = setenv(ls->env, val + 1, 0); + *val = '='; + } + ls = ls->next; + } + return rc; +} + +void env_list_free(struct ul_env_list *ls) +{ + while (ls) { + struct ul_env_list *x = ls; + ls = ls->next; + free(x); + } +} + +/* + * Removes unwanted variables from environ[]. If @ls is not NULL than stores + * unwnated variables to the list. + */ +void __sanitize_env(struct ul_env_list **org) +{ + char **envp = environ; + char * const *bad; + char **cur; + int last = 0; + + for (cur = envp; *cur; cur++) + last++; + + for (cur = envp; *cur; cur++) { + for (bad = forbid; *bad; bad++) { + if (strncmp(*cur, *bad, strlen(*bad)) == 0) { + if (org) + *org = env_list_add(*org, *cur); + last = remote_entry(envp, cur - envp, last); + cur--; + break; + } + } + } + + for (cur = envp; *cur; cur++) { + for (bad = noslash; *bad; bad++) { + if (strncmp(*cur, *bad, strlen(*bad)) != 0) + continue; + if (!strchr(*cur, '/')) + continue; /* OK */ + if (org) + *org = env_list_add(*org, *cur); + last = remote_entry(envp, cur - envp, last); + cur--; + break; + } + } +} + +void sanitize_env(void) +{ + __sanitize_env(NULL); +} + +char *safe_getenv(const char *arg) +{ + uid_t ruid = getuid(); + + if (ruid != 0 || (ruid != geteuid()) || (getgid() != getegid())) + return NULL; +#ifdef HAVE_PRCTL + if (prctl(PR_GET_DUMPABLE, 0, 0, 0, 0) == 0) + return NULL; +#else +#if (defined(linux) && defined(SYS_prctl)) + if (syscall(SYS_prctl, PR_GET_DUMPABLE, 0, 0, 0, 0) == 0) + return NULL; +#endif +#endif +#ifdef HAVE_SECURE_GETENV +return secure_getenv(arg); +#elif HAVE___SECURE_GETENV + return __secure_getenv(arg); +#else + return getenv(arg); +#endif +} + +#ifdef TEST_PROGRAM +int main(void) +{ + char *const *bad; + char copy[32]; + char *p; + int retval = EXIT_SUCCESS; + struct ul_env_list *removed = NULL; + + for (bad = forbid; *bad; bad++) { + strcpy(copy, *bad); + p = strchr(copy, '='); + if (p) + *p = '\0'; + setenv(copy, copy, 1); + } + + /* removed */ + __sanitize_env(&removed); + + /* check removal */ + for (bad = forbid; *bad; bad++) { + strcpy(copy, *bad); + p = strchr(copy, '='); + if (p) + *p = '\0'; + p = getenv(copy); + if (p) { + warnx("%s was not removed", copy); + retval = EXIT_FAILURE; + } + } + + /* restore removed */ + env_list_setenv(removed); + + /* check restore */ + for (bad = forbid; *bad; bad++) { + strcpy(copy, *bad); + p = strchr(copy, '='); + if (p) + *p = '\0'; + p = getenv(copy); + if (!p) { + warnx("%s was not restored", copy); + retval = EXIT_FAILURE; + } + } + + env_list_free(removed); + + return retval; +} +#endif diff --git a/utils/lib/exec_shell.c b/utils/lib/exec_shell.c new file mode 100644 index 0000000..18798eb --- /dev/null +++ b/utils/lib/exec_shell.c @@ -0,0 +1,51 @@ +/* + * exec_shell() - launch a shell, else exit! + * + * 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, or (at your option) any + * later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include +#include +#include +#include +#include + +#include "nls.h" +#include "c.h" +#include "xalloc.h" + +#include "exec_shell.h" + +#define DEFAULT_SHELL "/bin/sh" + +void __attribute__((__noreturn__)) exec_shell(void) +{ + const char *shell = getenv("SHELL"); + char *shellc; + const char *shell_basename; + char *arg0; + + if (!shell) + shell = DEFAULT_SHELL; + + shellc = xstrdup(shell); + shell_basename = basename(shellc); + arg0 = xmalloc(strlen(shell_basename) + 2); + arg0[0] = '-'; + strcpy(arg0 + 1, shell_basename); + + execl(shell, arg0, NULL); + errexec(shell); +} diff --git a/utils/lib/fileutils.c b/utils/lib/fileutils.c new file mode 100644 index 0000000..3ca43c1 --- /dev/null +++ b/utils/lib/fileutils.c @@ -0,0 +1,246 @@ +/* + * Copyright (C) 2012 Sami Kerola + */ + +#include +#include +#include +#include +#include +#include + +#include "c.h" +#include "fileutils.h" +#include "pathnames.h" + +int mkstemp_cloexec(char *template) +{ +#ifdef HAVE_MKOSTEMP + return mkostemp(template, O_RDWR|O_CREAT|O_EXCL|O_CLOEXEC); +#else + int fd, old_flags, errno_save; + + fd = mkstemp(template); + if (fd < 0) + return fd; + + old_flags = fcntl(fd, F_GETFD, 0); + if (old_flags < 0) + goto unwind; + if (fcntl(fd, F_SETFD, old_flags | O_CLOEXEC) < 0) + goto unwind; + + return fd; + +unwind: + errno_save = errno; + unlink(template); + close(fd); + errno = errno_save; + + return -1; +#endif +} + +/* Create open temporary file in safe way. Please notice that the + * file permissions are -rw------- by default. */ +int xmkstemp(char **tmpname, const char *dir, const char *prefix) +{ + char *localtmp; + const char *tmpenv; + mode_t old_mode; + int fd, rc; + + /* Some use cases must be capable of being moved atomically + * with rename(2), which is the reason why dir is here. */ + tmpenv = dir ? dir : getenv("TMPDIR"); + if (!tmpenv) + tmpenv = _PATH_TMP; + + rc = asprintf(&localtmp, "%s/%s.XXXXXX", tmpenv, prefix); + if (rc < 0) + return -1; + + old_mode = umask(077); + fd = mkstemp_cloexec(localtmp); + umask(old_mode); + if (fd == -1) { + free(localtmp); + localtmp = NULL; + } + *tmpname = localtmp; + return fd; +} + +int dup_fd_cloexec(int oldfd, int lowfd) +{ + int fd, flags, errno_save; + +#ifdef F_DUPFD_CLOEXEC + fd = fcntl(oldfd, F_DUPFD_CLOEXEC, lowfd); + if (fd >= 0) + return fd; +#endif + + fd = dup(oldfd); + if (fd < 0) + return fd; + + flags = fcntl(fd, F_GETFD); + if (flags < 0) + goto unwind; + if (fcntl(fd, F_SETFD, flags | FD_CLOEXEC) < 0) + goto unwind; + + return fd; + +unwind: + errno_save = errno; + close(fd); + errno = errno_save; + + return -1; +} + +/* + * portable getdtablesize() + */ +int get_fd_tabsize(void) +{ + int m; + +#if defined(HAVE_GETDTABLESIZE) + m = getdtablesize(); +#elif defined(HAVE_GETRLIMIT) && defined(RLIMIT_NOFILE) + struct rlimit rl; + + getrlimit(RLIMIT_NOFILE, &rl); + m = rl.rlim_cur; +#elif defined(HAVE_SYSCONF) && defined(_SC_OPEN_MAX) + m = sysconf(_SC_OPEN_MAX); +#else + m = OPEN_MAX; +#endif + return m; +} + +static inline int in_set(int x, const int set[], size_t setsz) +{ + size_t i; + + for (i = 0; i < setsz; i++) { + if (set[i] == x) + return 1; + } + return 0; +} + +void close_all_fds(const int exclude[], size_t exsz) +{ + struct dirent *d; + DIR *dir; + + dir = opendir(_PATH_PROC_FDDIR); + if (dir) { + while ((d = xreaddir(dir))) { + char *end; + int fd; + + errno = 0; + fd = strtol(d->d_name, &end, 10); + + if (errno || end == d->d_name || !end || *end) + continue; + if (dirfd(dir) == fd) + continue; + if (in_set(fd, exclude, exsz)) + continue; + close(fd); + } + closedir(dir); + } else { + int fd, tbsz = get_fd_tabsize(); + + for (fd = 0; fd < tbsz; fd++) { + if (!in_set(fd, exclude, exsz)) + close(fd); + } + } +} + +#ifdef TEST_PROGRAM_FILEUTILS +int main(int argc, char *argv[]) +{ + if (argc < 2) + errx(EXIT_FAILURE, "Usage %s --{mkstemp,close-fds}", argv[0]); + + if (strcmp(argv[1], "--mkstemp") == 0) { + FILE *f; + char *tmpname; + f = xfmkstemp(&tmpname, NULL, "test"); + unlink(tmpname); + free(tmpname); + fclose(f); + + } else if (strcmp(argv[1], "--close-fds") == 0) { + static const int wanted_fds[] = { + STDIN_FILENO, STDOUT_FILENO, STDERR_FILENO + }; + + ignore_result( dup(STDIN_FILENO) ); + ignore_result( dup(STDIN_FILENO) ); + ignore_result( dup(STDIN_FILENO) ); + + close_all_fds(wanted_fds, ARRAY_SIZE(wanted_fds)); + } + return EXIT_SUCCESS; +} +#endif + + +int mkdir_p(const char *path, mode_t mode) +{ + char *p, *dir; + int rc = 0; + + if (!path || !*path) + return -EINVAL; + + dir = p = strdup(path); + if (!dir) + return -ENOMEM; + + if (*p == '/') + p++; + + while (p && *p) { + char *e = strchr(p, '/'); + if (e) + *e = '\0'; + if (*p) { + rc = mkdir(dir, mode); + if (rc && errno != EEXIST) + break; + rc = 0; + } + if (!e) + break; + *e = '/'; + p = e + 1; + } + + free(dir); + return rc; +} + +/* returns basename and keeps dirname in the @path, if @path is "/" (root) + * then returns empty string */ +char *stripoff_last_component(char *path) +{ + char *p = path ? strrchr(path, '/') : NULL; + + if (!p) + return NULL; + *p = '\0'; + return p + 1; +} diff --git a/utils/lib/idcache.c b/utils/lib/idcache.c new file mode 100644 index 0000000..5550223 --- /dev/null +++ b/utils/lib/idcache.c @@ -0,0 +1,117 @@ +/* + * No copyright is claimed. This code is in the public domain; do with + * it what you wish. + * + * Written by Karel Zak + */ +#include +#include +#include +#include + +#include "c.h" +#include "idcache.h" + +struct identry *get_id(struct idcache *ic, unsigned long int id) +{ + struct identry *ent; + + if (!ic) + return NULL; + + for (ent = ic->ent; ent; ent = ent->next) { + if (ent->id == id) + return ent; + } + + return NULL; +} + +struct idcache *new_idcache(void) +{ + return calloc(1, sizeof(struct idcache)); +} + +void free_idcache(struct idcache *ic) +{ + struct identry *ent = ic->ent; + + while (ent) { + struct identry *next = ent->next; + free(ent->name); + free(ent); + ent = next; + } + + free(ic); +} + +static void add_id(struct idcache *ic, char *name, unsigned long int id) +{ + struct identry *ent, *x; + int w = 0; + + ent = calloc(1, sizeof(struct identry)); + if (!ent) + return; + ent->id = id; + + if (name) { +#ifdef HAVE_WIDECHAR + wchar_t wc[LOGIN_NAME_MAX + 1]; + + if (mbstowcs(wc, name, LOGIN_NAME_MAX) > 0) { + wc[LOGIN_NAME_MAX] = '\0'; + w = wcswidth(wc, LOGIN_NAME_MAX); + } + else +#endif + w = strlen(name); + } + + /* note, we ignore names with non-printable widechars */ + if (w > 0) { + ent->name = strdup(name); + if (!ent->name) { + free(ent); + return; + } + } else { + if (asprintf(&ent->name, "%lu", id) < 0) { + free(ent); + return; + } + } + + for (x = ic->ent; x && x->next; x = x->next); + + if (x) + x->next = ent; + else + ic->ent = ent; + + if (w <= 0) + w = ent->name ? strlen(ent->name) : 0; + ic->width = ic->width < w ? w : ic->width; +} + +void add_uid(struct idcache *cache, unsigned long int id) +{ + struct identry *ent= get_id(cache, id); + + if (!ent) { + struct passwd *pw = getpwuid((uid_t) id); + add_id(cache, pw ? pw->pw_name : NULL, id); + } +} + +void add_gid(struct idcache *cache, unsigned long int id) +{ + struct identry *ent = get_id(cache, id); + + if (!ent) { + struct group *gr = getgrgid((gid_t) id); + add_id(cache, gr ? gr->gr_name : NULL, id); + } +} + diff --git a/utils/lib/ismounted.c b/utils/lib/ismounted.c new file mode 100644 index 0000000..9a20b23 --- /dev/null +++ b/utils/lib/ismounted.c @@ -0,0 +1,396 @@ +/* + * ismounted.c --- Check to see if the filesystem was mounted + * + * Copyright (C) 1995,1996,1997,1998,1999,2000,2008 Theodore Ts'o. + * + * This file may be redistributed under the terms of the GNU Public + * License. + */ +#include +#include +#include +#include +#include +#ifdef HAVE_MNTENT_H +#include +#endif +#include +#include +#include +#include + +#ifndef __linux__ +# ifdef HAVE_SYS_UCRED_H +# include +# endif +# ifdef HAVE_SYS_MOUNT_H +# include +# endif +#endif + +#include "pathnames.h" +#include "strutils.h" +#include "ismounted.h" +#include "c.h" +#ifdef __linux__ +# include "loopdev.h" +#endif + + + +#ifdef HAVE_MNTENT_H +/* + * Helper function which checks a file in /etc/mtab format to see if a + * filesystem is mounted. Returns an error if the file doesn't exist + * or can't be opened. + */ +static int check_mntent_file(const char *mtab_file, const char *file, + int *mount_flags, char *mtpt, int mtlen) +{ + struct mntent *mnt; + struct stat st_buf; + int retval = 0; + dev_t file_dev=0, file_rdev=0; + ino_t file_ino=0; + FILE *f; + int fd; + + *mount_flags = 0; + if ((f = setmntent (mtab_file, "r")) == NULL) + return errno; + + if (stat(file, &st_buf) == 0) { + if (S_ISBLK(st_buf.st_mode)) { +#ifndef __GNU__ /* The GNU hurd is broken with respect to stat devices */ + file_rdev = st_buf.st_rdev; +#endif /* __GNU__ */ + } else { + file_dev = st_buf.st_dev; + file_ino = st_buf.st_ino; + } + } + + while ((mnt = getmntent (f)) != NULL) { + if (mnt->mnt_fsname[0] != '/') + continue; + if (strcmp(file, mnt->mnt_fsname) == 0) + break; + if (stat(mnt->mnt_fsname, &st_buf) != 0) + continue; + + if (S_ISBLK(st_buf.st_mode)) { +#ifndef __GNU__ + if (file_rdev && file_rdev == st_buf.st_rdev) + break; +#ifdef __linux__ + /* maybe the file is loopdev backing file */ + if (file_dev + && major(st_buf.st_rdev) == LOOPDEV_MAJOR + && loopdev_is_used(mnt->mnt_fsname, file, 0, 0, 0)) + break; +#endif /* __linux__ */ +#endif /* __GNU__ */ + } else { + if (file_dev && ((file_dev == st_buf.st_dev) && + (file_ino == st_buf.st_ino))) + break; + } + } + + if (mnt == NULL) { +#ifndef __GNU__ /* The GNU hurd is broken with respect to stat devices */ + /* + * Do an extra check to see if this is the root device. We + * can't trust /etc/mtab, and /proc/mounts will only list + * /dev/root for the root filesystem. Argh. Instead we + * check if the given device has the same major/minor number + * as the device that the root directory is on. + */ + if (file_rdev && stat("/", &st_buf) == 0 && + st_buf.st_dev == file_rdev) { + *mount_flags = MF_MOUNTED; + if (mtpt) + xstrncpy(mtpt, "/", mtlen); + goto is_root; + } +#endif /* __GNU__ */ + goto errout; + } +#ifndef __GNU__ /* The GNU hurd is deficient; what else is new? */ + /* Validate the entry in case /etc/mtab is out of date */ + /* + * We need to be paranoid, because some broken distributions + * (read: Slackware) don't initialize /etc/mtab before checking + * all of the non-root filesystems on the disk. + */ + if (stat(mnt->mnt_dir, &st_buf) < 0) { + retval = errno; + if (retval == ENOENT) { +#ifdef DEBUG + printf("Bogus entry in %s! (%s does not exist)\n", + mtab_file, mnt->mnt_dir); +#endif /* DEBUG */ + retval = 0; + } + goto errout; + } + if (file_rdev && (st_buf.st_dev != file_rdev)) { +#ifdef DEBUG + printf("Bogus entry in %s! (%s not mounted on %s)\n", + mtab_file, file, mnt->mnt_dir); +#endif /* DEBUG */ + goto errout; + } +#endif /* __GNU__ */ + *mount_flags = MF_MOUNTED; + +#ifdef MNTOPT_RO + /* Check to see if the ro option is set */ + if (hasmntopt(mnt, MNTOPT_RO)) + *mount_flags |= MF_READONLY; +#endif + + if (mtpt) + xstrncpy(mtpt, mnt->mnt_dir, mtlen); + /* + * Check to see if we're referring to the root filesystem. + * If so, do a manual check to see if we can open /etc/mtab + * read/write, since if the root is mounted read/only, the + * contents of /etc/mtab may not be accurate. + */ + if (!strcmp(mnt->mnt_dir, "/")) { +is_root: +#define TEST_FILE "/.ismount-test-file" + *mount_flags |= MF_ISROOT; + fd = open(TEST_FILE, O_RDWR|O_CREAT|O_CLOEXEC, 0600); + if (fd < 0) { + if (errno == EROFS) + *mount_flags |= MF_READONLY; + } else + close(fd); + (void) unlink(TEST_FILE); + } + retval = 0; +errout: + endmntent (f); + return retval; +} + +static int check_mntent(const char *file, int *mount_flags, + char *mtpt, int mtlen) +{ + int retval; + +#ifdef DEBUG + retval = check_mntent_file("/tmp/mtab", file, mount_flags, + mtpt, mtlen); + if (retval == 0) + return 0; +#endif /* DEBUG */ +#ifdef __linux__ + retval = check_mntent_file("/proc/mounts", file, mount_flags, + mtpt, mtlen); + if (retval == 0 && (*mount_flags != 0)) + return 0; + if (access("/proc/mounts", R_OK) == 0) { + *mount_flags = 0; + return retval; + } +#endif /* __linux__ */ +#if defined(MOUNTED) || defined(_PATH_MOUNTED) +#ifndef MOUNTED +#define MOUNTED _PATH_MOUNTED +#endif /* MOUNTED */ + retval = check_mntent_file(MOUNTED, file, mount_flags, mtpt, mtlen); + return retval; +#else + *mount_flags = 0; + return 0; +#endif /* defined(MOUNTED) || defined(_PATH_MOUNTED) */ +} + +#else +#if defined(HAVE_GETMNTINFO) + +static int check_getmntinfo(const char *file, int *mount_flags, + char *mtpt, int mtlen) +{ + struct statfs *mp; + int len, n; + const char *s1; + char *s2; + + n = getmntinfo(&mp, MNT_NOWAIT); + if (n == 0) + return errno; + + len = sizeof(_PATH_DEV) - 1; + s1 = file; + if (strncmp(_PATH_DEV, s1, len) == 0) + s1 += len; + + *mount_flags = 0; + while (--n >= 0) { + s2 = mp->f_mntfromname; + if (strncmp(_PATH_DEV, s2, len) == 0) { + s2 += len - 1; + *s2 = 'r'; + } + if (strcmp(s1, s2) == 0 || strcmp(s1, &s2[1]) == 0) { + *mount_flags = MF_MOUNTED; + break; + } + ++mp; + } + if (mtpt) + xstrncpy(mtpt, mp->f_mntonname, mtlen); + return 0; +} +#endif /* HAVE_GETMNTINFO */ +#endif /* HAVE_MNTENT_H */ + +/* + * Check to see if we're dealing with the swap device. + */ +static int is_swap_device(const char *file) +{ + FILE *f; + char buf[1024], *cp; + dev_t file_dev; + struct stat st_buf; + int ret = 0; + + file_dev = 0; +#ifndef __GNU__ /* The GNU hurd is broken with respect to stat devices */ + if ((stat(file, &st_buf) == 0) && + S_ISBLK(st_buf.st_mode)) + file_dev = st_buf.st_rdev; +#endif /* __GNU__ */ + + if (!(f = fopen("/proc/swaps", "r" UL_CLOEXECSTR))) + return 0; + /* Skip the first line */ + if (!fgets(buf, sizeof(buf), f)) + goto leave; + if (*buf && strncmp(buf, "Filename\t", 9) != 0) + /* Linux <=2.6.19 contained a bug in the /proc/swaps + * code where the header would not be displayed + */ + goto valid_first_line; + + while (fgets(buf, sizeof(buf), f)) { +valid_first_line: + if ((cp = strchr(buf, ' ')) != NULL) + *cp = 0; + if ((cp = strchr(buf, '\t')) != NULL) + *cp = 0; + if (strcmp(buf, file) == 0) { + ret++; + break; + } +#ifndef __GNU__ + if (file_dev && (stat(buf, &st_buf) == 0) && + S_ISBLK(st_buf.st_mode) && + file_dev == st_buf.st_rdev) { + ret++; + break; + } +#endif /* __GNU__ */ + } + +leave: + fclose(f); + return ret; +} + + +/* + * check_mount_point() fills determines if the device is mounted or otherwise + * busy, and fills in mount_flags with one or more of the following flags: + * MF_MOUNTED, MF_ISROOT, MF_READONLY, MF_SWAP, and MF_BUSY. If mtpt is + * non-NULL, the directory where the device is mounted is copied to where mtpt + * is pointing, up to mtlen characters. + */ +#ifdef __TURBOC__ + #pragma argsused +#endif +int check_mount_point(const char *device, int *mount_flags, + char *mtpt, int mtlen) +{ + int retval = 0; + + if (is_swap_device(device)) { + *mount_flags = MF_MOUNTED | MF_SWAP; + if (mtpt && mtlen) + xstrncpy(mtpt, "[SWAP]", mtlen); + } else { +#ifdef HAVE_MNTENT_H + retval = check_mntent(device, mount_flags, mtpt, mtlen); +#else +#ifdef HAVE_GETMNTINFO + retval = check_getmntinfo(device, mount_flags, mtpt, mtlen); +#else +#ifdef __GNUC__ + #warning "Can't use getmntent or getmntinfo to check for mounted filesystems!" +#endif + *mount_flags = 0; +#endif /* HAVE_GETMNTINFO */ +#endif /* HAVE_MNTENT_H */ + } + if (retval) + return retval; + +#ifdef __linux__ /* This only works on Linux 2.6+ systems */ + { + struct stat st_buf; + int fd; + if ((stat(device, &st_buf) != 0) || + !S_ISBLK(st_buf.st_mode)) + return 0; + fd = open(device, O_RDONLY|O_EXCL|O_CLOEXEC); + if (fd < 0) { + if (errno == EBUSY) + *mount_flags |= MF_BUSY; + } else + close(fd); + } +#endif + + return 0; +} + +int is_mounted(const char *file) +{ + int retval; + int mount_flags = 0; + + retval = check_mount_point(file, &mount_flags, NULL, 0); + if (retval) + return 0; + return mount_flags & MF_MOUNTED; +} + +#ifdef TEST_PROGRAM_ISMOUNTED +int main(int argc, char **argv) +{ + int flags = 0; + char devname[PATH_MAX]; + + if (argc < 2) { + fprintf(stderr, "Usage: %s device\n", argv[0]); + return EXIT_FAILURE; + } + + if (check_mount_point(argv[1], &flags, devname, sizeof(devname)) == 0 && + (flags & MF_MOUNTED)) { + if (flags & MF_SWAP) + printf("used swap device\n"); + else + printf("mounted on %s\n", devname); + return EXIT_SUCCESS; + } + + printf("not mounted\n"); + return EXIT_FAILURE; +} +#endif /* DEBUG */ diff --git a/utils/lib/langinfo.c b/utils/lib/langinfo.c new file mode 100644 index 0000000..a200085 --- /dev/null +++ b/utils/lib/langinfo.c @@ -0,0 +1,124 @@ +/* + * This is callback solution for systems without nl_langinfo(), this function + * returns hardcoded and on locale setting indepndent value. + * + * See langinfo.h man page for more details. + * + * No copyright is claimed. This code is in the public domain; do with + * it what you wish. + * + * Copyright (C) 2010 Karel Zak + */ +#include "nls.h" + +char *langinfo_fallback(nl_item item) +{ + switch (item) { + case CODESET: + return "ISO-8859-1"; + case THOUSEP: + return ","; + case D_T_FMT: + case ERA_D_T_FMT: + return "%a %b %e %H:%M:%S %Y"; + case D_FMT: + case ERA_D_FMT: + return "%m/%d/%y"; + case T_FMT: + case ERA_T_FMT: + return "%H:%M:%S"; + case T_FMT_AMPM: + return "%I:%M:%S %p"; + case AM_STR: + return "AM"; + case PM_STR: + return "PM"; + case DAY_1: + return "Sunday"; + case DAY_2: + return "Monday"; + case DAY_3: + return "Tuesday"; + case DAY_4: + return "Wednesday"; + case DAY_5: + return "Thursday"; + case DAY_6: + return "Friday"; + case DAY_7: + return "Saturday"; + case ABDAY_1: + return "Sun"; + case ABDAY_2: + return "Mon"; + case ABDAY_3: + return "Tue"; + case ABDAY_4: + return "Wed"; + case ABDAY_5: + return "Thu"; + case ABDAY_6: + return "Fri"; + case ABDAY_7: + return "Sat"; + case MON_1: + return "January"; + case MON_2: + return "February"; + case MON_3: + return "March"; + case MON_4: + return "April"; + case MON_5: + return "May"; + case MON_6: + return "June"; + case MON_7: + return "July"; + case MON_8: + return "August"; + case MON_9: + return "September"; + case MON_10: + return "October"; + case MON_11: + return "November"; + case MON_12: + return "December"; + case ABMON_1: + return "Jan"; + case ABMON_2: + return "Feb"; + case ABMON_3: + return "Mar"; + case ABMON_4: + return "Apr"; + case ABMON_5: + return "May"; + case ABMON_6: + return "Jun"; + case ABMON_7: + return "Jul"; + case ABMON_8: + return "Aug"; + case ABMON_9: + return "Sep"; + case ABMON_10: + return "Oct"; + case ABMON_11: + return "Nov"; + case ABMON_12: + return "Dec"; + case ALT_DIGITS: + return "\0\0\0\0\0\0\0\0\0\0"; + case CRNCYSTR: + return "-"; + case YESEXPR: + return "^[yY]"; + case NOEXPR: + return "^[nN]"; + default: + return ""; + } +} + diff --git a/utils/lib/linux_version.c b/utils/lib/linux_version.c new file mode 100644 index 0000000..137bbe7 --- /dev/null +++ b/utils/lib/linux_version.c @@ -0,0 +1,71 @@ +#include +#include + +#include "c.h" +#include "linux_version.h" + +int get_linux_version (void) +{ + static int kver = -1; + struct utsname uts; + int x = 0, y = 0, z = 0; + int n; + + if (kver != -1) + return kver; + if (uname(&uts)) + return kver = 0; + + n = sscanf(uts.release, "%d.%d.%d", &x, &y, &z); + if (n < 1 || n > 3) + return kver = 0; + + return kver = KERNEL_VERSION(x, y, z); +} + +#ifdef TEST_PROGRAM_LINUXVERSION +# include +int main(int argc, char *argv[]) +{ + int rc = EXIT_FAILURE; + + if (argc == 1) { + printf("Linux version: %d\n", get_linux_version()); + rc = EXIT_SUCCESS; + + } else if (argc == 5) { + const char *oper = argv[1]; + + int x = atoi(argv[2]), + y = atoi(argv[3]), + z = atoi(argv[4]); + int kver = get_linux_version(); + int uver = KERNEL_VERSION(x, y, z); + + if (strcmp(oper, "==") == 0) + rc = kver == uver; + else if (strcmp(oper, "<=") == 0) + rc = kver <= uver; + else if (strcmp(oper, ">=") == 0) + rc = kver >= uver; + else + errx(EXIT_FAILURE, "unsupported operator"); + + if (rc) + printf("match\n"); + else + printf("not-match [%d %s %d, x.y.z: %d.%d.%d]\n", + kver, oper, uver, x, y, z); + + rc = rc ? EXIT_SUCCESS : EXIT_FAILURE; + + } else + fprintf(stderr, "Usage:\n" + " %s [ ]\n" + "supported operators:\n" + " ==, <=, >=\n", + program_invocation_short_name); + + return rc; +} +#endif diff --git a/utils/lib/loopdev.c b/utils/lib/loopdev.c new file mode 100644 index 0000000..686be53 --- /dev/null +++ b/utils/lib/loopdev.c @@ -0,0 +1,1914 @@ + +/* + * No copyright is claimed. This code is in the public domain; do with + * it what you wish. + * + * Written by Karel Zak + * + * -- based on mount/losetup.c + * + * Simple library for work with loop devices. + * + * - requires kernel 2.6.x + * - reads info from /sys/block/loop/loop/ (new kernels) + * - reads info by ioctl + * - supports *unlimited* number of loop devices + * - supports /dev/loop as well as /dev/loop/ + * - minimize overhead (fd, loopinfo, ... are shared for all operations) + * - setup (associate device and backing file) + * - delete (dis-associate file) + * - old LOOP_{SET,GET}_STATUS (32bit) ioctls are unsupported + * - extendible + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "linux_version.h" +#include "c.h" +#include "sysfs.h" +#include "pathnames.h" +#include "loopdev.h" +#include "canonicalize.h" +#include "blkdev.h" +#include "debug.h" + +/* + * Debug stuff (based on include/debug.h) + */ +static UL_DEBUG_DEFINE_MASK(loopdev); +UL_DEBUG_DEFINE_MASKNAMES(loopdev) = UL_DEBUG_EMPTY_MASKNAMES; + +#define LOOPDEV_DEBUG_INIT (1 << 1) +#define LOOPDEV_DEBUG_CXT (1 << 2) +#define LOOPDEV_DEBUG_ITER (1 << 3) +#define LOOPDEV_DEBUG_SETUP (1 << 4) + +#define DBG(m, x) __UL_DBG(loopdev, LOOPDEV_DEBUG_, m, x) +#define ON_DBG(m, x) __UL_DBG_CALL(loopdev, LOOPDEV_DEBUG_, m, x) + +#define UL_DEBUG_CURRENT_MASK UL_DEBUG_MASK(loopdev) +#include "debugobj.h" + +static void loopdev_init_debug(void) +{ + if (loopdev_debug_mask) + return; + __UL_INIT_DEBUG_FROM_ENV(loopdev, LOOPDEV_DEBUG_, 0, LOOPDEV_DEBUG); +} + +/* + * see loopcxt_init() + */ +#define loopcxt_ioctl_enabled(_lc) (!((_lc)->flags & LOOPDEV_FL_NOIOCTL)) +#define loopcxt_sysfs_available(_lc) (!((_lc)->flags & LOOPDEV_FL_NOSYSFS)) \ + && !loopcxt_ioctl_enabled(_lc) + +/* + * @lc: context + * @device: device name, absolute device path or NULL to reset the current setting + * + * Sets device, absolute paths (e.g. "/dev/loop") are unchanged, device + * names ("loop") are converted to the path (/dev/loop or to + * /dev/loop/) + * + * This sets the device name, but does not check if the device exists! + * + * Returns: <0 on error, 0 on success + */ +int loopcxt_set_device(struct loopdev_cxt *lc, const char *device) +{ + if (!lc) + return -EINVAL; + + if (lc->fd >= 0) { + close(lc->fd); + DBG(CXT, ul_debugobj(lc, "closing old open fd")); + } + lc->fd = -1; + lc->mode = 0; + lc->blocksize = 0; + lc->has_info = 0; + lc->info_failed = 0; + *lc->device = '\0'; + memset(&lc->info, 0, sizeof(lc->info)); + + /* set new */ + if (device) { + if (*device != '/') { + const char *dir = _PATH_DEV; + + /* compose device name for /dev/loop or /dev/loop/ */ + if (lc->flags & LOOPDEV_FL_DEVSUBDIR) { + if (strlen(device) < 5) + return -1; + device += 4; + dir = _PATH_DEV_LOOP "/"; /* _PATH_DEV uses tailing slash */ + } + snprintf(lc->device, sizeof(lc->device), "%s%s", + dir, device); + } else + xstrncpy(lc->device, device, sizeof(lc->device)); + + DBG(CXT, ul_debugobj(lc, "%s name assigned", device)); + } + + ul_unref_path(lc->sysfs); + lc->sysfs = NULL; + return 0; +} + +int loopcxt_has_device(struct loopdev_cxt *lc) +{ + return lc && *lc->device; +} + +/* + * @lc: context + * @flags: LOOPDEV_FL_* flags + * + * Initialize loop handler. + * + * We have two sets of the flags: + * + * * LOOPDEV_FL_* flags control loopcxt_* API behavior + * + * * LO_FLAGS_* are kernel flags used for LOOP_{SET,GET}_STAT64 ioctls + * + * Note about LOOPDEV_FL_{RDONLY,RDWR} flags. These flags are used for open(2) + * syscall to open loop device. By default is the device open read-only. + * + * The exception is loopcxt_setup_device(), where the device is open read-write + * if LO_FLAGS_READ_ONLY flags is not set (see loopcxt_set_flags()). + * + * Returns: <0 on error, 0 on success. + */ +int loopcxt_init(struct loopdev_cxt *lc, int flags) +{ + int rc; + struct stat st; + struct loopdev_cxt dummy = UL_LOOPDEVCXT_EMPTY; + + if (!lc) + return -EINVAL; + + loopdev_init_debug(); + DBG(CXT, ul_debugobj(lc, "initialize context")); + + memcpy(lc, &dummy, sizeof(dummy)); + lc->flags = flags; + + rc = loopcxt_set_device(lc, NULL); + if (rc) + return rc; + + if (stat(_PATH_SYS_BLOCK, &st) || !S_ISDIR(st.st_mode)) { + lc->flags |= LOOPDEV_FL_NOSYSFS; + lc->flags &= ~LOOPDEV_FL_NOIOCTL; + DBG(CXT, ul_debugobj(lc, "init: disable /sys usage")); + } + + if (!(lc->flags & LOOPDEV_FL_NOSYSFS) && + get_linux_version() >= KERNEL_VERSION(2,6,37)) { + /* + * Use only sysfs for basic information about loop devices + */ + lc->flags |= LOOPDEV_FL_NOIOCTL; + DBG(CXT, ul_debugobj(lc, "init: ignore ioctls")); + } + + if (!(lc->flags & LOOPDEV_FL_CONTROL) && !stat(_PATH_DEV_LOOPCTL, &st)) { + lc->flags |= LOOPDEV_FL_CONTROL; + DBG(CXT, ul_debugobj(lc, "init: loop-control detected ")); + } + + return 0; +} + +/* + * @lc: context + * + * Deinitialize loop context + */ +void loopcxt_deinit(struct loopdev_cxt *lc) +{ + int errsv = errno; + + if (!lc) + return; + + DBG(CXT, ul_debugobj(lc, "de-initialize")); + + free(lc->filename); + lc->filename = NULL; + + ignore_result( loopcxt_set_device(lc, NULL) ); + loopcxt_deinit_iterator(lc); + + errno = errsv; +} + +/* + * @lc: context + * + * Returns newly allocated device path. + */ +char *loopcxt_strdup_device(struct loopdev_cxt *lc) +{ + if (!lc || !*lc->device) + return NULL; + return strdup(lc->device); +} + +/* + * @lc: context + * + * Returns pointer device name in the @lc struct. + */ +const char *loopcxt_get_device(struct loopdev_cxt *lc) +{ + return lc && *lc->device ? lc->device : NULL; +} + +/* + * @lc: context + * + * Returns pointer to the sysfs context (see lib/sysfs.c) + */ +static struct path_cxt *loopcxt_get_sysfs(struct loopdev_cxt *lc) +{ + if (!lc || !*lc->device || (lc->flags & LOOPDEV_FL_NOSYSFS)) + return NULL; + + if (!lc->sysfs) { + dev_t devno = sysfs_devname_to_devno(lc->device); + if (!devno) { + DBG(CXT, ul_debugobj(lc, "sysfs: failed devname to devno")); + return NULL; + } + + lc->sysfs = ul_new_sysfs_path(devno, NULL, NULL); + if (!lc->sysfs) + DBG(CXT, ul_debugobj(lc, "sysfs: init failed")); + } + + return lc->sysfs; +} + +/* + * @lc: context + * + * Returns: file descriptor to the open loop device or <0 on error. The mode + * depends on LOOPDEV_FL_{RDWR,RDONLY} context flags. Default is + * read-only. + */ +int loopcxt_get_fd(struct loopdev_cxt *lc) +{ + if (!lc || !*lc->device) + return -EINVAL; + + if (lc->fd < 0) { + lc->mode = lc->flags & LOOPDEV_FL_RDWR ? O_RDWR : O_RDONLY; + lc->fd = open(lc->device, lc->mode | O_CLOEXEC); + DBG(CXT, ul_debugobj(lc, "open %s [%s]: %m", lc->device, + lc->flags & LOOPDEV_FL_RDWR ? "rw" : "ro")); + } + return lc->fd; +} + +int loopcxt_set_fd(struct loopdev_cxt *lc, int fd, int mode) +{ + if (!lc) + return -EINVAL; + + lc->fd = fd; + lc->mode = mode; + return 0; +} + +/* + * @lc: context + * @flags: LOOPITER_FL_* flags + * + * Iterator can be used to scan list of the free or used loop devices. + * + * Returns: <0 on error, 0 on success + */ +int loopcxt_init_iterator(struct loopdev_cxt *lc, int flags) +{ + struct loopdev_iter *iter; + struct stat st; + + if (!lc) + return -EINVAL; + + + iter = &lc->iter; + DBG(ITER, ul_debugobj(iter, "initialize")); + + /* always zeroize + */ + memset(iter, 0, sizeof(*iter)); + iter->ncur = -1; + iter->flags = flags; + iter->default_check = 1; + + if (!lc->extra_check) { + /* + * Check for /dev/loop/ subdirectory + */ + if (!(lc->flags & LOOPDEV_FL_DEVSUBDIR) && + stat(_PATH_DEV_LOOP, &st) == 0 && S_ISDIR(st.st_mode)) + lc->flags |= LOOPDEV_FL_DEVSUBDIR; + + lc->extra_check = 1; + } + return 0; +} + +/* + * @lc: context + * + * Returns: <0 on error, 0 on success + */ +int loopcxt_deinit_iterator(struct loopdev_cxt *lc) +{ + struct loopdev_iter *iter; + + if (!lc) + return -EINVAL; + + iter = &lc->iter; + DBG(ITER, ul_debugobj(iter, "de-initialize")); + + free(iter->minors); + if (iter->proc) + fclose(iter->proc); + if (iter->sysblock) + closedir(iter->sysblock); + + memset(iter, 0, sizeof(*iter)); + return 0; +} + +/* + * Same as loopcxt_set_device, but also checks if the device is + * associated with any file. + * + * Returns: <0 on error, 0 on success, 1 device does not match with + * LOOPITER_FL_{USED,FREE} flags. + */ +static int loopiter_set_device(struct loopdev_cxt *lc, const char *device) +{ + int rc = loopcxt_set_device(lc, device); + int used; + + if (rc) + return rc; + + if (!(lc->iter.flags & LOOPITER_FL_USED) && + !(lc->iter.flags & LOOPITER_FL_FREE)) + return 0; /* caller does not care about device status */ + + if (!is_loopdev(lc->device)) { + DBG(ITER, ul_debugobj(&lc->iter, "%s does not exist", lc->device)); + return -errno; + } + + DBG(ITER, ul_debugobj(&lc->iter, "%s exist", lc->device)); + + used = loopcxt_get_offset(lc, NULL) == 0; + + if ((lc->iter.flags & LOOPITER_FL_USED) && used) + return 0; + + if ((lc->iter.flags & LOOPITER_FL_FREE) && !used) + return 0; + + DBG(ITER, ul_debugobj(&lc->iter, "failed to use %s device", lc->device)); + + ignore_result( loopcxt_set_device(lc, NULL) ); + return 1; +} + +static int cmpnum(const void *p1, const void *p2) +{ + return (((* (const int *) p1) > (* (const int *) p2)) - + ((* (const int *) p1) < (* (const int *) p2))); +} + +/* + * The classic scandir() is more expensive and less portable. + * We needn't full loop device names -- loop numbers (loop) + * are enough. + */ +static int loop_scandir(const char *dirname, int **ary, int hasprefix) +{ + DIR *dir; + struct dirent *d; + unsigned int n, count = 0, arylen = 0; + + if (!dirname || !ary) + return 0; + + DBG(ITER, ul_debug("scan dir: %s", dirname)); + + dir = opendir(dirname); + if (!dir) + return 0; + free(*ary); + *ary = NULL; + + while((d = readdir(dir))) { +#ifdef _DIRENT_HAVE_D_TYPE + if (d->d_type != DT_BLK && d->d_type != DT_UNKNOWN && + d->d_type != DT_LNK) + continue; +#endif + if (!strcmp(d->d_name, ".") || !strcmp(d->d_name, "..")) + continue; + + if (hasprefix) { + /* /dev/loop */ + if (sscanf(d->d_name, "loop%u", &n) != 1) + continue; + } else { + /* /dev/loop/ */ + char *end = NULL; + + errno = 0; + n = strtol(d->d_name, &end, 10); + if (d->d_name == end || (end && *end) || errno) + continue; + } + if (n < LOOPDEV_DEFAULT_NNODES) + continue; /* ignore loop<0..7> */ + + if (count + 1 > arylen) { + int *tmp; + + arylen += 1; + + tmp = realloc(*ary, arylen * sizeof(int)); + if (!tmp) { + free(*ary); + *ary = NULL; + closedir(dir); + return -1; + } + *ary = tmp; + } + if (*ary) + (*ary)[count++] = n; + } + if (count && *ary) + qsort(*ary, count, sizeof(int), cmpnum); + + closedir(dir); + return count; +} + +/* + * Set the next *used* loop device according to /proc/partitions. + * + * Loop devices smaller than 512 bytes are invisible for this function. + */ +static int loopcxt_next_from_proc(struct loopdev_cxt *lc) +{ + struct loopdev_iter *iter = &lc->iter; + char buf[BUFSIZ]; + + DBG(ITER, ul_debugobj(iter, "scan /proc/partitions")); + + if (!iter->proc) + iter->proc = fopen(_PATH_PROC_PARTITIONS, "r" UL_CLOEXECSTR); + if (!iter->proc) + return 1; + + while (fgets(buf, sizeof(buf), iter->proc)) { + unsigned int m; + char name[128 + 1]; + + + if (sscanf(buf, " %u %*s %*s %128[^\n ]", + &m, name) != 2 || m != LOOPDEV_MAJOR) + continue; + + DBG(ITER, ul_debugobj(iter, "checking %s", name)); + + if (loopiter_set_device(lc, name) == 0) + return 0; + } + + return 1; +} + +/* + * Set the next *used* loop device according to + * /sys/block/loopN/loop/backing_file (kernel >= 2.6.37 is required). + * + * This is preferred method. + */ +static int loopcxt_next_from_sysfs(struct loopdev_cxt *lc) +{ + struct loopdev_iter *iter = &lc->iter; + struct dirent *d; + int fd; + + DBG(ITER, ul_debugobj(iter, "scanning /sys/block")); + + if (!iter->sysblock) + iter->sysblock = opendir(_PATH_SYS_BLOCK); + + if (!iter->sysblock) + return 1; + + fd = dirfd(iter->sysblock); + + while ((d = readdir(iter->sysblock))) { + char name[NAME_MAX + 18 + 1]; + struct stat st; + + DBG(ITER, ul_debugobj(iter, "check %s", d->d_name)); + + if (strcmp(d->d_name, ".") == 0 + || strcmp(d->d_name, "..") == 0 + || strncmp(d->d_name, "loop", 4) != 0) + continue; + + snprintf(name, sizeof(name), "%s/loop/backing_file", d->d_name); + if (fstatat(fd, name, &st, 0) != 0) + continue; + + if (loopiter_set_device(lc, d->d_name) == 0) + return 0; + } + + return 1; +} + +/* + * @lc: context, has to initialized by loopcxt_init_iterator() + * + * Returns: 0 on success, -1 on error, 1 at the end of scanning. The details + * about the current loop device are available by + * loopcxt_get_{fd,backing_file,device,offset, ...} functions. + */ +int loopcxt_next(struct loopdev_cxt *lc) +{ + struct loopdev_iter *iter; + + if (!lc) + return -EINVAL; + + + iter = &lc->iter; + if (iter->done) + return 1; + + DBG(ITER, ul_debugobj(iter, "next")); + + /* A) Look for used loop devices in /proc/partitions ("losetup -a" only) + */ + if (iter->flags & LOOPITER_FL_USED) { + int rc; + + if (loopcxt_sysfs_available(lc)) + rc = loopcxt_next_from_sysfs(lc); + else + rc = loopcxt_next_from_proc(lc); + if (rc == 0) + return 0; + goto done; + } + + /* B) Classic way, try first eight loop devices (default number + * of loop devices). This is enough for 99% of all cases. + */ + if (iter->default_check) { + DBG(ITER, ul_debugobj(iter, "next: default check")); + for (++iter->ncur; iter->ncur < LOOPDEV_DEFAULT_NNODES; + iter->ncur++) { + char name[16]; + snprintf(name, sizeof(name), "loop%d", iter->ncur); + + if (loopiter_set_device(lc, name) == 0) + return 0; + } + iter->default_check = 0; + } + + /* C) the worst possibility, scan whole /dev or /dev/loop/ + */ + if (!iter->minors) { + DBG(ITER, ul_debugobj(iter, "next: scanning /dev")); + iter->nminors = (lc->flags & LOOPDEV_FL_DEVSUBDIR) ? + loop_scandir(_PATH_DEV_LOOP, &iter->minors, 0) : + loop_scandir(_PATH_DEV, &iter->minors, 1); + iter->ncur = -1; + } + for (++iter->ncur; iter->ncur < iter->nminors; iter->ncur++) { + char name[16]; + snprintf(name, sizeof(name), "loop%d", iter->minors[iter->ncur]); + + if (loopiter_set_device(lc, name) == 0) + return 0; + } +done: + loopcxt_deinit_iterator(lc); + return 1; +} + +/* + * @device: path to device + */ +int is_loopdev(const char *device) +{ + struct stat st; + + if (device && stat(device, &st) == 0 && + S_ISBLK(st.st_mode) && + major(st.st_rdev) == LOOPDEV_MAJOR) + return 1; + + errno = ENODEV; + return 0; +} + +/* + * @lc: context + * + * Returns result from LOOP_GET_STAT64 ioctl or NULL on error. + */ +struct loop_info64 *loopcxt_get_info(struct loopdev_cxt *lc) +{ + int fd; + + if (!lc || lc->info_failed) { + errno = EINVAL; + return NULL; + } + errno = 0; + if (lc->has_info) + return &lc->info; + + fd = loopcxt_get_fd(lc); + if (fd < 0) + return NULL; + + if (ioctl(fd, LOOP_GET_STATUS64, &lc->info) == 0) { + lc->has_info = 1; + lc->info_failed = 0; + DBG(CXT, ul_debugobj(lc, "reading loop_info64 OK")); + return &lc->info; + } + + lc->info_failed = 1; + DBG(CXT, ul_debugobj(lc, "reading loop_info64 FAILED")); + + return NULL; +} + +/* + * @lc: context + * + * Returns (allocated) string with path to the file associated + * with the current loop device. + */ +char *loopcxt_get_backing_file(struct loopdev_cxt *lc) +{ + struct path_cxt *sysfs = loopcxt_get_sysfs(lc); + char *res = NULL; + + if (sysfs) + /* + * This is always preferred, the loop_info64 + * has too small buffer for the filename. + */ + ul_path_read_string(sysfs, &res, "loop/backing_file"); + + if (!res && loopcxt_ioctl_enabled(lc)) { + struct loop_info64 *lo = loopcxt_get_info(lc); + + if (lo) { + lo->lo_file_name[LO_NAME_SIZE - 2] = '*'; + lo->lo_file_name[LO_NAME_SIZE - 1] = '\0'; + res = strdup((char *) lo->lo_file_name); + } + } + + DBG(CXT, ul_debugobj(lc, "get_backing_file [%s]", res)); + return res; +} + +/* + * @lc: context + * @offset: returns offset number for the given device + * + * Returns: <0 on error, 0 on success + */ +int loopcxt_get_offset(struct loopdev_cxt *lc, uint64_t *offset) +{ + struct path_cxt *sysfs = loopcxt_get_sysfs(lc); + int rc = -EINVAL; + + if (sysfs) + rc = ul_path_read_u64(sysfs, offset, "loop/offset"); + + if (rc && loopcxt_ioctl_enabled(lc)) { + struct loop_info64 *lo = loopcxt_get_info(lc); + if (lo) { + if (offset) + *offset = lo->lo_offset; + rc = 0; + } else + rc = -errno; + } + + DBG(CXT, ul_debugobj(lc, "get_offset [rc=%d]", rc)); + return rc; +} + +/* + * @lc: context + * @blocksize: returns logical blocksize for the given device + * + * Returns: <0 on error, 0 on success + */ +int loopcxt_get_blocksize(struct loopdev_cxt *lc, uint64_t *blocksize) +{ + struct path_cxt *sysfs = loopcxt_get_sysfs(lc); + int rc = -EINVAL; + + if (sysfs) + rc = ul_path_read_u64(sysfs, blocksize, "queue/logical_block_size"); + + /* Fallback based on BLKSSZGET ioctl */ + if (rc) { + int fd = loopcxt_get_fd(lc); + int sz = 0; + + if (fd < 0) + return -EINVAL; + rc = blkdev_get_sector_size(fd, &sz); + if (rc) + return rc; + + *blocksize = sz; + } + + DBG(CXT, ul_debugobj(lc, "get_blocksize [rc=%d]", rc)); + return rc; +} + +/* + * @lc: context + * @sizelimit: returns size limit for the given device + * + * Returns: <0 on error, 0 on success + */ +int loopcxt_get_sizelimit(struct loopdev_cxt *lc, uint64_t *size) +{ + struct path_cxt *sysfs = loopcxt_get_sysfs(lc); + int rc = -EINVAL; + + if (sysfs) + rc = ul_path_read_u64(sysfs, size, "loop/sizelimit"); + + if (rc && loopcxt_ioctl_enabled(lc)) { + struct loop_info64 *lo = loopcxt_get_info(lc); + if (lo) { + if (size) + *size = lo->lo_sizelimit; + rc = 0; + } else + rc = -errno; + } + + DBG(CXT, ul_debugobj(lc, "get_sizelimit [rc=%d]", rc)); + return rc; +} + +/* + * @lc: context + * @devno: returns encryption type + * + * Cryptoloop is DEPRECATED! + * + * Returns: <0 on error, 0 on success + */ +int loopcxt_get_encrypt_type(struct loopdev_cxt *lc, uint32_t *type) +{ + struct loop_info64 *lo = loopcxt_get_info(lc); + int rc; + + /* not provided by sysfs */ + if (lo) { + if (type) + *type = lo->lo_encrypt_type; + rc = 0; + } else + rc = -errno; + + DBG(CXT, ul_debugobj(lc, "get_encrypt_type [rc=%d]", rc)); + return rc; +} + +/* + * @file_fmt_type_str: file format type string. + * @file_fmt_type: returns file format type from the given file format string. + * + * Returns: <0 on error, 0 on success + */ +int parse_file_fmt_type(const char *file_fmt_type_str, uint32_t *file_fmt_type) +{ + int rc = 0; + + if (!strcmp(file_fmt_type_str, "RAW")) + *file_fmt_type = LO_FILE_FMT_RAW; + else if (!strcmp(file_fmt_type_str, "QCOW")) + *file_fmt_type = LO_FILE_FMT_QCOW; + else if (!strcmp(file_fmt_type_str, "VDI")) + *file_fmt_type = LO_FILE_FMT_VDI; + else if (!strcmp(file_fmt_type_str, "VMDK")) + *file_fmt_type = LO_FILE_FMT_VMDK; + else + rc = -EINVAL; + + return rc; +} + +/* + * @lc: context + * @file_fmt_type: returns file format type of the given device + * + * Returns: <0 on error, 0 on success + */ +int loopcxt_get_file_fmt_type(struct loopdev_cxt *lc, uint32_t* file_fmt_type) +{ + struct path_cxt *sysfs = loopcxt_get_sysfs(lc); + int rc = 0; + + if (sysfs) { + /* check if file_fmt_type is accessible and supported by the kernel module */ + char* file_fmt_str = NULL; + if (ul_path_read_string(sysfs, &file_fmt_str, "loop/file_fmt_type") == 0) + rc = parse_file_fmt_type(file_fmt_str, file_fmt_type); + } else + rc = -errno; + + if (rc != 0 && loopcxt_ioctl_enabled(lc)) { + struct loop_info64 *lo = loopcxt_get_info(lc); + if (lo) + *file_fmt_type = lo->lo_file_fmt_type; + } + + return 0; +} + +/* + * @lc: context + * + * Returns (allocated) string with file format type of the current loop device. + */ +char *loopcxt_get_file_fmt_type_string(struct loopdev_cxt *lc) +{ + struct path_cxt *sysfs = loopcxt_get_sysfs(lc); + char *res = NULL; + + if (sysfs) + ul_path_read_string(sysfs, &res, "loop/file_fmt_type"); + + DBG(CXT, ul_debugobj(lc, "loopcxt_get_file_fmt_type_string [%s]", res)); + return res; +} + +/* + * @lc: context + * @devno: returns crypt name + * + * Cryptoloop is DEPRECATED! + * + * Returns: <0 on error, 0 on success + */ +const char *loopcxt_get_crypt_name(struct loopdev_cxt *lc) +{ + struct loop_info64 *lo = loopcxt_get_info(lc); + + if (lo) + return (char *) lo->lo_crypt_name; + + DBG(CXT, ul_debugobj(lc, "get_crypt_name failed")); + return NULL; +} + +/* + * @lc: context + * @devno: returns backing file devno + * + * Returns: <0 on error, 0 on success + */ +int loopcxt_get_backing_devno(struct loopdev_cxt *lc, dev_t *devno) +{ + struct loop_info64 *lo = loopcxt_get_info(lc); + int rc; + + if (lo) { + if (devno) + *devno = lo->lo_device; + rc = 0; + } else + rc = -errno; + + DBG(CXT, ul_debugobj(lc, "get_backing_devno [rc=%d]", rc)); + return rc; +} + +/* + * @lc: context + * @ino: returns backing file inode + * + * Returns: <0 on error, 0 on success + */ +int loopcxt_get_backing_inode(struct loopdev_cxt *lc, ino_t *ino) +{ + struct loop_info64 *lo = loopcxt_get_info(lc); + int rc; + + if (lo) { + if (ino) + *ino = lo->lo_inode; + rc = 0; + } else + rc = -errno; + + DBG(CXT, ul_debugobj(lc, "get_backing_inode [rc=%d]", rc)); + return rc; +} + +/* + * Check if the kernel supports partitioned loop devices. + * + * Notes: + * - kernels < 3.2 support partitioned loop devices and PT scanning + * only if max_part= module parameter is non-zero + * + * - kernels >= 3.2 always support partitioned loop devices + * + * - kernels >= 3.2 always support BLKPG_{ADD,DEL}_PARTITION ioctls + * + * - kernels >= 3.2 enable PT scanner only if max_part= is non-zero or if the + * LO_FLAGS_PARTSCAN flag is set for the device. The PT scanner is disabled + * by default. + * + * See kernel commit e03c8dd14915fabc101aa495828d58598dc5af98. + */ +int loopmod_supports_partscan(void) +{ + int rc, ret = 0; + FILE *f; + + if (get_linux_version() >= KERNEL_VERSION(3,2,0)) + return 1; + + f = fopen("/sys/module/loop/parameters/max_part", "r" UL_CLOEXECSTR); + if (!f) + return 0; + rc = fscanf(f, "%d", &ret); + fclose(f); + return rc == 1 ? ret : 0; +} + +/* + * @lc: context + * + * Returns: 1 if the partscan flags is set *or* (for old kernels) partitions + * scanning is enabled for all loop devices. + */ +int loopcxt_is_partscan(struct loopdev_cxt *lc) +{ + struct path_cxt *sysfs = loopcxt_get_sysfs(lc); + + if (sysfs) { + /* kernel >= 3.2 */ + int fl; + if (ul_path_read_s32(sysfs, &fl, "loop/partscan") == 0) + return fl; + } + + /* old kernels (including kernels without loopN/loop/ directory */ + return loopmod_supports_partscan(); +} + +/* + * @lc: context + * + * Returns: 1 if the autoclear flags is set. + */ +int loopcxt_is_autoclear(struct loopdev_cxt *lc) +{ + struct path_cxt *sysfs = loopcxt_get_sysfs(lc); + + if (sysfs) { + int fl; + if (ul_path_read_s32(sysfs, &fl, "loop/autoclear") == 0) + return fl; + } + + if (loopcxt_ioctl_enabled(lc)) { + struct loop_info64 *lo = loopcxt_get_info(lc); + if (lo) + return lo->lo_flags & LO_FLAGS_AUTOCLEAR; + } + return 0; +} + +/* + * @lc: context + * + * Returns: 1 if the readonly flags is set. + */ +int loopcxt_is_readonly(struct loopdev_cxt *lc) +{ + struct path_cxt *sysfs = loopcxt_get_sysfs(lc); + + if (sysfs) { + int fl; + if (ul_path_read_s32(sysfs, &fl, "ro") == 0) + return fl; + } + + if (loopcxt_ioctl_enabled(lc)) { + struct loop_info64 *lo = loopcxt_get_info(lc); + if (lo) + return lo->lo_flags & LO_FLAGS_READ_ONLY; + } + return 0; +} + +/* + * @lc: context + * + * Returns: 1 if the dio flags is set. + */ +int loopcxt_is_dio(struct loopdev_cxt *lc) +{ + struct path_cxt *sysfs = loopcxt_get_sysfs(lc); + + if (sysfs) { + int fl; + if (ul_path_read_s32(sysfs, &fl, "loop/dio") == 0) + return fl; + } + if (loopcxt_ioctl_enabled(lc)) { + struct loop_info64 *lo = loopcxt_get_info(lc); + if (lo) + return lo->lo_flags & LO_FLAGS_DIRECT_IO; + } + return 0; +} + +/* + * @lc: context + * @st: backing file stat or NULL + * @backing_file: filename + * @offset: offset (use LOOPDEV_FL_OFFSET if specified) + * @sizelimit: size limit (use LOOPDEV_FL_SIZELIMIT if specified) + * @flags: LOOPDEV_FL_{OFFSET,SIZELIMIT} + * + * Returns 1 if the current @lc loopdev is associated with the given backing + * file. Note that the preferred way is to use devno and inode number rather + * than filename. The @backing_file filename is poor solution usable in case + * that you don't have rights to call stat(). + * + * LOOPDEV_FL_SIZELIMIT requires LOOPDEV_FL_OFFSET being set as well. + * + * Don't forget that old kernels provide very restricted (in size) backing + * filename by LOOP_GET_STAT64 ioctl only. + */ +int loopcxt_is_used(struct loopdev_cxt *lc, + struct stat *st, + const char *backing_file, + uint64_t offset, + uint64_t sizelimit, + int flags) +{ + ino_t ino = 0; + dev_t dev = 0; + + if (!lc) + return 0; + + DBG(CXT, ul_debugobj(lc, "checking %s vs. %s", + loopcxt_get_device(lc), + backing_file)); + + if (st && loopcxt_get_backing_inode(lc, &ino) == 0 && + loopcxt_get_backing_devno(lc, &dev) == 0) { + + if (ino == st->st_ino && dev == st->st_dev) + goto found; + + /* don't use filename if we have devno and inode */ + return 0; + } + + /* poor man's solution */ + if (backing_file) { + char *name = loopcxt_get_backing_file(lc); + int rc = name && strcmp(name, backing_file) == 0; + + free(name); + if (rc) + goto found; + } + + return 0; +found: + if (flags & LOOPDEV_FL_OFFSET) { + uint64_t off = 0; + + int rc = loopcxt_get_offset(lc, &off) == 0 && off == offset; + + if (rc && flags & LOOPDEV_FL_SIZELIMIT) { + uint64_t sz = 0; + + return loopcxt_get_sizelimit(lc, &sz) == 0 && sz == sizelimit; + } + return rc; + } + return 1; +} + +/* + * The setting is removed by loopcxt_set_device() loopcxt_next()! + */ +int loopcxt_set_offset(struct loopdev_cxt *lc, uint64_t offset) +{ + if (!lc) + return -EINVAL; + lc->info.lo_offset = offset; + + DBG(CXT, ul_debugobj(lc, "set offset=%jd", offset)); + return 0; +} + +/* + * The setting is removed by loopcxt_set_device() loopcxt_next()! + */ +int loopcxt_set_sizelimit(struct loopdev_cxt *lc, uint64_t sizelimit) +{ + if (!lc) + return -EINVAL; + lc->info.lo_sizelimit = sizelimit; + + DBG(CXT, ul_debugobj(lc, "set sizelimit=%jd", sizelimit)); + return 0; +} + +/* + * The blocksize will be used by loopcxt_set_device(). For already exiting + * devices use loopcxt_ioctl_blocksize(). + * + * The setting is removed by loopcxt_set_device() loopcxt_next()! + */ +int loopcxt_set_blocksize(struct loopdev_cxt *lc, uint64_t blocksize) +{ + if (!lc) + return -EINVAL; + lc->blocksize = blocksize; + + DBG(CXT, ul_debugobj(lc, "set blocksize=%jd", blocksize)); + return 0; +} + +/* + * @lc: context + * @file_fmt_type: kernel LO_FILE_FMT_{RAW,QCOW,VDI,VMDK} flags + * + * The setting is removed by loopcxt_set_device() loopcxt_next()! + * + * Returns: 0 on success, <0 on error. + */ +int loopcxt_set_file_fmt_type(struct loopdev_cxt *lc, uint32_t file_fmt_type) { + if (!lc) + return -EINVAL; + lc->info.lo_file_fmt_type = file_fmt_type; + + DBG(CXT, ul_debugobj(lc, "set file_fmt_type=%u", (unsigned) file_fmt_type)); + return 0; +} + +/* + * @lc: context + * @flags: kernel LO_FLAGS_{READ_ONLY,USE_AOPS,AUTOCLEAR} flags + * + * The setting is removed by loopcxt_set_device() loopcxt_next()! + * + * Returns: 0 on success, <0 on error. + */ +int loopcxt_set_flags(struct loopdev_cxt *lc, uint32_t flags) +{ + if (!lc) + return -EINVAL; + lc->info.lo_flags = flags; + + DBG(CXT, ul_debugobj(lc, "set flags=%u", (unsigned) flags)); + return 0; +} + +/* + * @lc: context + * @filename: backing file path (the path will be canonicalized) + * + * The setting is removed by loopcxt_set_device() loopcxt_next()! + * + * Returns: 0 on success, <0 on error. + */ +int loopcxt_set_backing_file(struct loopdev_cxt *lc, const char *filename) +{ + if (!lc) + return -EINVAL; + + lc->filename = canonicalize_path(filename); + if (!lc->filename) + return -errno; + + xstrncpy((char *)lc->info.lo_file_name, lc->filename, LO_NAME_SIZE); + + DBG(CXT, ul_debugobj(lc, "set backing file=%s", lc->info.lo_file_name)); + return 0; +} + +/* + * In kernels prior to v3.9, if the offset or sizelimit options + * are used, the block device's size won't be synced automatically. + * blockdev --getsize64 and filesystems will use the backing + * file size until the block device has been re-opened or the + * LOOP_SET_CAPACITY ioctl is called to sync the sizes. + * + * Since mount -oloop uses the LO_FLAGS_AUTOCLEAR option and passes + * the open file descriptor to the mount system call, we need to use + * the ioctl. Calling losetup directly doesn't have this problem since + * it closes the device when it exits and whatever consumes the device + * next will re-open it, causing the resync. + */ +static int loopcxt_check_size(struct loopdev_cxt *lc, int file_fd) +{ + uint64_t size, expected_size; + int dev_fd; + struct stat st; + + if (!lc->info.lo_offset && !lc->info.lo_sizelimit) + return 0; + + if (fstat(file_fd, &st)) { + DBG(CXT, ul_debugobj(lc, "failed to fstat backing file")); + return -errno; + } + if (S_ISBLK(st.st_mode)) { + if (blkdev_get_size(file_fd, + (unsigned long long *) &expected_size)) { + DBG(CXT, ul_debugobj(lc, "failed to determine device size")); + return -errno; + } + } else + expected_size = st.st_size; + + if (expected_size == 0 || expected_size <= lc->info.lo_offset) { + DBG(CXT, ul_debugobj(lc, "failed to determine expected size")); + return 0; /* ignore this error */ + } + + if (lc->info.lo_offset > 0) + expected_size -= lc->info.lo_offset; + + if (lc->info.lo_sizelimit > 0 && lc->info.lo_sizelimit < expected_size) + expected_size = lc->info.lo_sizelimit; + + dev_fd = loopcxt_get_fd(lc); + if (dev_fd < 0) { + DBG(CXT, ul_debugobj(lc, "failed to get loop FD")); + return -errno; + } + + if (blkdev_get_size(dev_fd, (unsigned long long *) &size)) { + DBG(CXT, ul_debugobj(lc, "failed to determine loopdev size")); + return -errno; + } + + /* It's block device, so, align to 512-byte sectors */ + if (expected_size % 512) { + DBG(CXT, ul_debugobj(lc, "expected size misaligned to 512-byte sectors")); + expected_size = (expected_size >> 9) << 9; + } + + if (expected_size != size) { + DBG(CXT, ul_debugobj(lc, "warning: loopdev and expected " + "size mismatch (%ju/%ju)", + size, expected_size)); + + if (loopcxt_ioctl_capacity(lc)) { + /* ioctl not available */ + if (errno == ENOTTY || errno == EINVAL) + errno = ERANGE; + return -errno; + } + + if (blkdev_get_size(dev_fd, (unsigned long long *) &size)) + return -errno; + + if (expected_size != size) { + errno = ERANGE; + DBG(CXT, ul_debugobj(lc, "failed to set loopdev size, " + "size: %ju, expected: %ju", + size, expected_size)); + return -errno; + } + } + + return 0; +} + +/* + * @lc: context + * + * Associate the current device (see loopcxt_{set,get}_device()) with + * a file (see loopcxt_set_backing_file()). + * + * The device is initialized read-write by default. If you want read-only + * device then set LO_FLAGS_READ_ONLY by loopcxt_set_flags(). The LOOPDEV_FL_* + * flags are ignored and modified according to LO_FLAGS_*. + * + * If the device is already open by loopcxt_get_fd() then this setup device + * function will re-open the device to fix read/write mode. + * + * The device is also initialized read-only if the backing file is not + * possible to open read-write (e.g. read-only FS). + * + * Returns: <0 on error, 0 on success. + */ +int loopcxt_setup_device(struct loopdev_cxt *lc) +{ + int file_fd, dev_fd, mode = O_RDWR, rc = -1, cnt = 0, err, again; + int errsv = 0; + + if (!lc || !*lc->device || !lc->filename) + return -EINVAL; + + DBG(SETUP, ul_debugobj(lc, "device setup requested")); + + /* + * Open backing file and device + */ + if (lc->info.lo_flags & LO_FLAGS_READ_ONLY) + mode = O_RDONLY; + + if ((file_fd = open(lc->filename, mode | O_CLOEXEC)) < 0) { + if (mode != O_RDONLY && (errno == EROFS || errno == EACCES)) + file_fd = open(lc->filename, mode = O_RDONLY); + + if (file_fd < 0) { + DBG(SETUP, ul_debugobj(lc, "open backing file failed: %m")); + return -errno; + } + } + DBG(SETUP, ul_debugobj(lc, "backing file open: OK")); + + if (lc->fd != -1 && lc->mode != mode) { + DBG(SETUP, ul_debugobj(lc, "closing already open device (mode mismatch)")); + close(lc->fd); + lc->fd = -1; + lc->mode = 0; + } + + if (mode == O_RDONLY) { + lc->flags |= LOOPDEV_FL_RDONLY; /* open() mode */ + lc->info.lo_flags |= LO_FLAGS_READ_ONLY; /* kernel loopdev mode */ + } else { + lc->flags |= LOOPDEV_FL_RDWR; /* open() mode */ + lc->info.lo_flags &= ~LO_FLAGS_READ_ONLY; + lc->flags &= ~LOOPDEV_FL_RDONLY; + } + + do { + errno = 0; + dev_fd = loopcxt_get_fd(lc); + if (dev_fd >= 0 || lc->control_ok == 0) + break; + if (errno != EACCES && errno != ENOENT) + break; + /* We have permissions to open /dev/loop-control, but open + * /dev/loopN failed with EACCES, it's probably because udevd + * does not applied chown yet. Let's wait a moment. */ + xusleep(25000); + } while (cnt++ < 16); + + if (dev_fd < 0) { + rc = -errno; + goto err; + } + + DBG(SETUP, ul_debugobj(lc, "device open: OK")); + + /* + * Set FD + */ + if (ioctl(dev_fd, LOOP_SET_FD, file_fd) < 0) { + rc = -errno; + errsv = errno; + DBG(SETUP, ul_debugobj(lc, "LOOP_SET_FD failed: %m")); + goto err; + } + + DBG(SETUP, ul_debugobj(lc, "LOOP_SET_FD: OK")); + + if (lc->blocksize > 0 + && (rc = loopcxt_ioctl_blocksize(lc, lc->blocksize)) < 0) { + errsv = -rc; + goto err; + } + + do { + err = ioctl(dev_fd, LOOP_SET_STATUS64, &lc->info); + again = err && errno == EAGAIN; + if (again) + xusleep(250000); + } while (again); + if (err) { + rc = -errno; + errsv = errno; + DBG(SETUP, ul_debugobj(lc, "LOOP_SET_STATUS64 failed: %m")); + goto err; + } + + DBG(SETUP, ul_debugobj(lc, "LOOP_SET_STATUS64: OK")); + + if ((rc = loopcxt_check_size(lc, file_fd))) + goto err; + + close(file_fd); + + memset(&lc->info, 0, sizeof(lc->info)); + lc->has_info = 0; + lc->info_failed = 0; + + DBG(SETUP, ul_debugobj(lc, "success [rc=0]")); + return 0; +err: + if (file_fd >= 0) + close(file_fd); + if (dev_fd >= 0 && rc != -EBUSY) + ioctl(dev_fd, LOOP_CLR_FD, 0); + if (errsv) + errno = errsv; + + DBG(SETUP, ul_debugobj(lc, "failed [rc=%d]", rc)); + return rc; +} + +/* + * @lc: context + * + * Update status of the current device (see loopcxt_{set,get}_device()). + * + * Note that once initialized, kernel accepts only selected changes: + * LO_FLAGS_AUTOCLEAR and LO_FLAGS_PARTSCAN + * For more see linux/drivers/block/loop.c:loop_set_status() + * + * Returns: <0 on error, 0 on success. + */ +int loopcxt_ioctl_status(struct loopdev_cxt *lc) +{ + int dev_fd, rc = -1, err, again; + + errno = 0; + dev_fd = loopcxt_get_fd(lc); + + if (dev_fd < 0) { + rc = -errno; + return rc; + } + DBG(SETUP, ul_debugobj(lc, "device open: OK")); + + do { + err = ioctl(dev_fd, LOOP_SET_STATUS64, &lc->info); + again = err && errno == EAGAIN; + if (again) + xusleep(250000); + } while (again); + if (err) { + rc = -errno; + DBG(SETUP, ul_debugobj(lc, "LOOP_SET_STATUS64 failed: %m")); + return rc; + } + + DBG(SETUP, ul_debugobj(lc, "LOOP_SET_STATUS64: OK")); + return 0; +} + +int loopcxt_ioctl_capacity(struct loopdev_cxt *lc) +{ + int fd = loopcxt_get_fd(lc); + + if (fd < 0) + return -EINVAL; + + /* Kernels prior to v2.6.30 don't support this ioctl */ + if (ioctl(fd, LOOP_SET_CAPACITY, 0) < 0) { + int rc = -errno; + DBG(CXT, ul_debugobj(lc, "LOOP_SET_CAPACITY failed: %m")); + return rc; + } + + DBG(CXT, ul_debugobj(lc, "capacity set")); + return 0; +} + +int loopcxt_ioctl_dio(struct loopdev_cxt *lc, unsigned long use_dio) +{ + int fd = loopcxt_get_fd(lc); + + if (fd < 0) + return -EINVAL; + + /* Kernels prior to v4.4 don't support this ioctl */ + if (ioctl(fd, LOOP_SET_DIRECT_IO, use_dio) < 0) { + int rc = -errno; + DBG(CXT, ul_debugobj(lc, "LOOP_SET_DIRECT_IO failed: %m")); + return rc; + } + + DBG(CXT, ul_debugobj(lc, "direct io set")); + return 0; +} + +/* + * Kernel uses "unsigned long" as ioctl arg, but we use u64 for all sizes to + * keep loopdev internal API simple. + */ +int loopcxt_ioctl_blocksize(struct loopdev_cxt *lc, uint64_t blocksize) +{ + int fd = loopcxt_get_fd(lc); + + if (fd < 0) + return -EINVAL; + + /* Kernels prior to v4.14 don't support this ioctl */ + if (ioctl(fd, LOOP_SET_BLOCK_SIZE, (unsigned long) blocksize) < 0) { + int rc = -errno; + DBG(CXT, ul_debugobj(lc, "LOOP_SET_BLOCK_SIZE failed: %m")); + return rc; + } + + DBG(CXT, ul_debugobj(lc, "logical block size set")); + return 0; +} + +int loopcxt_delete_device(struct loopdev_cxt *lc) +{ + int fd = loopcxt_get_fd(lc); + + if (fd < 0) + return -EINVAL; + + if (ioctl(fd, LOOP_CLR_FD, 0) < 0) { + DBG(CXT, ul_debugobj(lc, "LOOP_CLR_FD failed: %m")); + return -errno; + } + + DBG(CXT, ul_debugobj(lc, "device removed")); + return 0; +} + +int loopcxt_add_device(struct loopdev_cxt *lc) +{ + int rc = -EINVAL; + int ctl, nr = -1; + const char *p, *dev = loopcxt_get_device(lc); + + if (!dev) + goto done; + + if (!(lc->flags & LOOPDEV_FL_CONTROL)) { + rc = -ENOSYS; + goto done; + } + + p = strrchr(dev, '/'); + if (!p || (sscanf(p, "/loop%d", &nr) != 1 && sscanf(p, "/%d", &nr) != 1) + || nr < 0) + goto done; + + ctl = open(_PATH_DEV_LOOPCTL, O_RDWR|O_CLOEXEC); + if (ctl >= 0) { + DBG(CXT, ul_debugobj(lc, "add_device %d", nr)); + rc = ioctl(ctl, LOOP_CTL_ADD, nr); + close(ctl); + } + lc->control_ok = rc >= 0 ? 1 : 0; +done: + DBG(CXT, ul_debugobj(lc, "add_device done [rc=%d]", rc)); + return rc; +} + +/* + * Note that LOOP_CTL_GET_FREE ioctl is supported since kernel 3.1. In older + * kernels we have to check all loop devices to found unused one. + * + * See kernel commit 770fe30a46a12b6fb6b63fbe1737654d28e8484. + */ +int loopcxt_find_unused(struct loopdev_cxt *lc) +{ + int rc = -1; + + DBG(CXT, ul_debugobj(lc, "find_unused requested")); + + if (lc->flags & LOOPDEV_FL_CONTROL) { + int ctl; + + DBG(CXT, ul_debugobj(lc, "using loop-control")); + + ctl = open(_PATH_DEV_LOOPCTL, O_RDWR|O_CLOEXEC); + if (ctl >= 0) + rc = ioctl(ctl, LOOP_CTL_GET_FREE); + if (rc >= 0) { + char name[16]; + snprintf(name, sizeof(name), "loop%d", rc); + + rc = loopiter_set_device(lc, name); + } + lc->control_ok = ctl >= 0 && rc == 0 ? 1 : 0; + if (ctl >= 0) + close(ctl); + DBG(CXT, ul_debugobj(lc, "find_unused by loop-control [rc=%d]", rc)); + } + + if (rc < 0) { + DBG(CXT, ul_debugobj(lc, "using loop scan")); + rc = loopcxt_init_iterator(lc, LOOPITER_FL_FREE); + if (rc) + return rc; + + rc = loopcxt_next(lc); + loopcxt_deinit_iterator(lc); + DBG(CXT, ul_debugobj(lc, "find_unused by scan [rc=%d]", rc)); + } + return rc; +} + + + +/* + * Return: TRUE/FALSE + */ +int loopdev_is_autoclear(const char *device) +{ + struct loopdev_cxt lc; + int rc; + + if (!device) + return 0; + + rc = loopcxt_init(&lc, 0); + if (!rc) + rc = loopcxt_set_device(&lc, device); + if (!rc) + rc = loopcxt_is_autoclear(&lc); + + loopcxt_deinit(&lc); + return rc; +} + +char *loopdev_get_backing_file(const char *device) +{ + struct loopdev_cxt lc; + char *res = NULL; + + if (!device) + return NULL; + if (loopcxt_init(&lc, 0)) + return NULL; + if (loopcxt_set_device(&lc, device) == 0) + res = loopcxt_get_backing_file(&lc); + + loopcxt_deinit(&lc); + return res; +} + +/* + * Returns: TRUE/FALSE + */ +int loopdev_is_used(const char *device, const char *filename, + uint64_t offset, uint64_t sizelimit, int flags) +{ + struct loopdev_cxt lc; + struct stat st; + int rc = 0; + + if (!device || !filename) + return 0; + + rc = loopcxt_init(&lc, 0); + if (!rc) + rc = loopcxt_set_device(&lc, device); + if (rc) + return rc; + + rc = !stat(filename, &st); + rc = loopcxt_is_used(&lc, rc ? &st : NULL, filename, offset, sizelimit, flags); + + loopcxt_deinit(&lc); + return rc; +} + +int loopdev_delete(const char *device) +{ + struct loopdev_cxt lc; + int rc; + + if (!device) + return -EINVAL; + + rc = loopcxt_init(&lc, 0); + if (!rc) + rc = loopcxt_set_device(&lc, device); + if (!rc) + rc = loopcxt_delete_device(&lc); + loopcxt_deinit(&lc); + return rc; +} + +/* + * Returns: 0 = success, < 0 error, 1 not found + */ +int loopcxt_find_by_backing_file(struct loopdev_cxt *lc, const char *filename, + uint64_t offset, uint64_t sizelimit, int flags) +{ + int rc, hasst; + struct stat st; + + if (!filename) + return -EINVAL; + + hasst = !stat(filename, &st); + + rc = loopcxt_init_iterator(lc, LOOPITER_FL_USED); + if (rc) + return rc; + + while ((rc = loopcxt_next(lc)) == 0) { + + if (loopcxt_is_used(lc, hasst ? &st : NULL, + filename, offset, sizelimit, flags)) + break; + } + + loopcxt_deinit_iterator(lc); + return rc; +} + +/* + * Returns: 0 = not found, < 0 error, 1 found, 2 found full size and offset match + */ +int loopcxt_find_overlap(struct loopdev_cxt *lc, const char *filename, + uint64_t offset, uint64_t sizelimit) +{ + int rc, hasst; + struct stat st; + + if (!filename) + return -EINVAL; + + DBG(CXT, ul_debugobj(lc, "find_overlap requested")); + hasst = !stat(filename, &st); + + rc = loopcxt_init_iterator(lc, LOOPITER_FL_USED); + if (rc) + return rc; + + while ((rc = loopcxt_next(lc)) == 0) { + uint64_t lc_sizelimit, lc_offset; + + rc = loopcxt_is_used(lc, hasst ? &st : NULL, + filename, offset, sizelimit, 0); + if (!rc) + continue; /* unused */ + if (rc < 0) + break; /* error */ + + DBG(CXT, ul_debugobj(lc, "found %s backed by %s", + loopcxt_get_device(lc), filename)); + + rc = loopcxt_get_offset(lc, &lc_offset); + if (rc) { + DBG(CXT, ul_debugobj(lc, "failed to get offset for device %s", + loopcxt_get_device(lc))); + break; + } + rc = loopcxt_get_sizelimit(lc, &lc_sizelimit); + if (rc) { + DBG(CXT, ul_debugobj(lc, "failed to get sizelimit for device %s", + loopcxt_get_device(lc))); + break; + } + + /* full match */ + if (lc_sizelimit == sizelimit && lc_offset == offset) { + DBG(CXT, ul_debugobj(lc, "overlapping loop device %s (full match)", + loopcxt_get_device(lc))); + rc = 2; + goto found; + } + + /* overlap */ + if (lc_sizelimit != 0 && offset >= lc_offset + lc_sizelimit) + continue; + if (sizelimit != 0 && offset + sizelimit <= lc_offset) + continue; + + DBG(CXT, ul_debugobj(lc, "overlapping loop device %s", + loopcxt_get_device(lc))); + rc = 1; + goto found; + } + + if (rc == 1) + rc = 0; /* not found */ +found: + loopcxt_deinit_iterator(lc); + DBG(CXT, ul_debugobj(lc, "find_overlap done [rc=%d]", rc)); + return rc; +} + +/* + * Returns allocated string with device name + */ +char *loopdev_find_by_backing_file(const char *filename, uint64_t offset, uint64_t sizelimit, int flags) +{ + struct loopdev_cxt lc; + char *res = NULL; + + if (!filename) + return NULL; + + if (loopcxt_init(&lc, 0)) + return NULL; + if (loopcxt_find_by_backing_file(&lc, filename, offset, sizelimit, flags) == 0) + res = loopcxt_strdup_device(&lc); + loopcxt_deinit(&lc); + + return res; +} + +/* + * Returns number of loop devices associated with @file, if only one loop + * device is associated with the given @filename and @loopdev is not NULL then + * @loopdev returns name of the device. + */ +int loopdev_count_by_backing_file(const char *filename, char **loopdev) +{ + struct loopdev_cxt lc; + int count = 0, rc; + + if (!filename) + return -1; + + rc = loopcxt_init(&lc, 0); + if (rc) + return rc; + if (loopcxt_init_iterator(&lc, LOOPITER_FL_USED)) + return -1; + + while(loopcxt_next(&lc) == 0) { + char *backing = loopcxt_get_backing_file(&lc); + + if (!backing || strcmp(backing, filename) != 0) { + free(backing); + continue; + } + + free(backing); + if (loopdev && count == 0) + *loopdev = loopcxt_strdup_device(&lc); + count++; + } + + loopcxt_deinit(&lc); + + if (loopdev && count > 1) { + free(*loopdev); + *loopdev = NULL; + } + return count; +} + diff --git a/utils/lib/mangle.c b/utils/lib/mangle.c new file mode 100644 index 0000000..1a3b89a --- /dev/null +++ b/utils/lib/mangle.c @@ -0,0 +1,169 @@ +/* + * Functions for \oct encoding used in mtab/fstab/swaps/etc. + * + * No copyright is claimed. This code is in the public domain; do with + * it what you wish. + * + * Copyright (C) 2010 Karel Zak + */ +#include +#include +#include +#include + +#include "mangle.h" +#include "c.h" + +#define isoctal(a) (((a) & ~7) == '0') + +#define from_hex(c) (isdigit(c) ? c - '0' : tolower(c) - 'a' + 10) + +#define is_unwanted_char(x) (strchr(" \t\n\\", (unsigned int) x) != NULL) + + +char *mangle(const char *s) +{ + char *ss, *sp; + + if (!s) + return NULL; + + ss = sp = malloc(4 * strlen(s) + 1); + if (!sp) + return NULL; + while(1) { + if (!*s) { + *sp = '\0'; + break; + } + if (is_unwanted_char(*s)) { + *sp++ = '\\'; + *sp++ = '0' + ((*s & 0300) >> 6); + *sp++ = '0' + ((*s & 070) >> 3); + *sp++ = '0' + (*s & 07); + } else + *sp++ = *s; + s++; + } + return ss; +} + + +void unmangle_to_buffer(const char *s, char *buf, size_t len) +{ + size_t sz = 0; + + if (!s) + return; + + while(*s && sz < len - 1) { + if (*s == '\\' && sz + 3 < len - 1 && isoctal(s[1]) && + isoctal(s[2]) && isoctal(s[3])) { + + *buf++ = 64*(s[1] & 7) + 8*(s[2] & 7) + (s[3] & 7); + s += 4; + sz += 4; + } else { + *buf++ = *s++; + sz++; + } + } + *buf = '\0'; +} + +size_t unhexmangle_to_buffer(const char *s, char *buf, size_t len) +{ + size_t sz = 0; + const char *buf0 = buf; + + if (!s) + return 0; + + while(*s && sz < len - 1) { + if (*s == '\\' && sz + 3 < len - 1 && s[1] == 'x' && + isxdigit(s[2]) && isxdigit(s[3])) { + + *buf++ = from_hex(s[2]) << 4 | from_hex(s[3]); + s += 4; + sz += 4; + } else { + *buf++ = *s++; + sz++; + } + } + *buf = '\0'; + return buf - buf0 + 1; +} + +static inline const char *skip_nonspaces(const char *s) +{ + while (s && *s && !(*s == ' ' || *s == '\t')) + s++; + return s; +} + +/* + * Returns mallocated buffer or NULL in case of error. + */ +char *unmangle(const char *s, const char **end) +{ + char *buf; + const char *e; + size_t sz; + + if (!s) + return NULL; + + e = skip_nonspaces(s); + sz = e - s + 1; + + if (end) + *end = e; + if (e == s) + return NULL; /* empty string */ + + buf = malloc(sz); + if (!buf) + return NULL; + + unmangle_to_buffer(s, buf, sz); + return buf; +} + +#ifdef TEST_PROGRAM_MANGLE +#include +int main(int argc, char *argv[]) +{ + char *p = NULL; + if (argc < 3) { + fprintf(stderr, "usage: %s --mangle|unmangle \n", + program_invocation_short_name); + return EXIT_FAILURE; + } + + if (!strcmp(argv[1], "--mangle")) { + p = mangle(argv[2]); + printf("mangled: '%s'\n", p); + free(p); + } + + else if (!strcmp(argv[1], "--unmangle")) { + char *x = unmangle(argv[2], NULL); + + if (x) { + printf("unmangled: '%s'\n", x); + free(x); + } + + x = strdup(argv[2]); + if (x) { + unmangle_to_buffer(x, x, strlen(x) + 1); + + printf("self-unmangled: '%s'\n", x); + free(x); + } + } + + return EXIT_SUCCESS; +} +#endif /* TEST_PROGRAM_MANGLE */ diff --git a/utils/lib/match.c b/utils/lib/match.c new file mode 100644 index 0000000..a286a19 --- /dev/null +++ b/utils/lib/match.c @@ -0,0 +1,53 @@ +/* + * Copyright (C) 2011 Karel Zak + * + * This file may be redistributed under the terms of the + * GNU Lesser General Public License. + */ + +#include + +#include "match.h" + +/* + * match_fstype: + * @type: filesystem type + * @pattern: filesystem name or comma delimited list of names + * + * The @pattern list of filesystem can be prefixed with a global + * "no" prefix to invert matching of the whole list. The "no" could + * also be used for individual items in the @pattern list. So, + * "nofoo,bar" has the same meaning as "nofoo,nobar". + */ +int match_fstype(const char *type, const char *pattern) +{ + int no = 0; /* negated types list */ + int len; + const char *p; + + if (!pattern && !type) + return 1; + if (!pattern) + return 0; + + if (!strncmp(pattern, "no", 2)) { + no = 1; + pattern += 2; + } + + /* Does type occur in types, separated by commas? */ + len = strlen(type); + p = pattern; + while(1) { + if (!strncmp(p, "no", 2) && !strncasecmp(p+2, type, len) && + (p[len+2] == 0 || p[len+2] == ',')) + return 0; + if (strncasecmp(p, type, len) == 0 && (p[len] == 0 || p[len] == ',')) + return !no; + p = strchr(p,','); + if (!p) + break; + p++; + } + return no; +} diff --git a/utils/lib/mbsalign.c b/utils/lib/mbsalign.c new file mode 100644 index 0000000..e251202 --- /dev/null +++ b/utils/lib/mbsalign.c @@ -0,0 +1,627 @@ +/* Align/Truncate a string in a given screen width + Copyright (C) 2009-2010 Free Software Foundation, Inc. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation, either version 2.1 of the License, or + (at your option) any later version. + + This 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, see . */ + +/* Written by Pádraig Brady. */ + +#include +#include +#include +#include +#include +#include + +#include "c.h" +#include "mbsalign.h" +#include "strutils.h" +#include "widechar.h" + +/* + * Counts number of cells in multibyte string. All control and + * non-printable chars are ignored. + * + * Returns: number of cells. + */ +size_t mbs_nwidth(const char *buf, size_t bufsz) +{ + const char *p = buf, *last = buf; + size_t width = 0; + +#ifdef HAVE_WIDECHAR + mbstate_t st; + memset(&st, 0, sizeof(st)); +#endif + if (p && *p && bufsz) + last = p + (bufsz - 1); + + while (p && *p && p <= last) { + if (iscntrl((unsigned char) *p)) { + p++; + + /* try detect "\e[x;ym" and skip on success */ + if (*p && *p == '[') { + const char *e = p; + while (*e && e < last && *e != 'm') + e++; + if (*e == 'm') + p = e + 1; + } + continue; + } +#ifdef HAVE_WIDECHAR + wchar_t wc; + size_t len = mbrtowc(&wc, p, MB_CUR_MAX, &st); + + if (len == 0) + break; + if (len > 0 && iswprint(wc)) { + int x = wcwidth(wc); + if (x > 0) + width += x; + } else if (len == (size_t) -1 || len == (size_t) -2) + len = 1; + p += len; +#else + if (isprint((unsigned char) *p)) + width++; + p++; +#endif + } + + return width; +} + +size_t mbs_width(const char *s) +{ + if (!s || !*s) + return 0; + return mbs_nwidth(s, strlen(s)); +} + +/* + * Counts number of cells in multibyte string. For all control and + * non-printable chars is the result width enlarged to store \x?? hex + * sequence. See mbs_safe_encode(). + * + * Returns: number of cells, @sz returns number of bytes. + */ +size_t mbs_safe_nwidth(const char *buf, size_t bufsz, size_t *sz) +{ + const char *p = buf, *last = buf; + size_t width = 0, bytes = 0; + +#ifdef HAVE_WIDECHAR + mbstate_t st; + memset(&st, 0, sizeof(st)); +#endif + if (p && *p && bufsz) + last = p + (bufsz - 1); + + while (p && *p && p <= last) { + if ((p < last && *p == '\\' && *(p + 1) == 'x') + || iscntrl((unsigned char) *p)) { + width += 4, bytes += 4; /* *p encoded to \x?? */ + p++; + } +#ifdef HAVE_WIDECHAR + else { + wchar_t wc; + size_t len = mbrtowc(&wc, p, MB_CUR_MAX, &st); + + if (len == 0) + break; + + if (len == (size_t) -1 || len == (size_t) -2) { + len = 1; + if (isprint((unsigned char) *p)) + width += 1, bytes += 1; + else + width += 4, bytes += 4; + + } else if (!iswprint(wc)) { + width += len * 4; /* hex encode whole sequence */ + bytes += len * 4; + } else { + width += wcwidth(wc); /* number of cells */ + bytes += len; /* number of bytes */ + } + p += len; + } +#else + else if (!isprint((unsigned char) *p)) { + width += 4, bytes += 4; /* *p encoded to \x?? */ + p++; + } else { + width++, bytes++; + p++; + } +#endif + } + + if (sz) + *sz = bytes; + return width; +} + +size_t mbs_safe_width(const char *s) +{ + if (!s || !*s) + return 0; + return mbs_safe_nwidth(s, strlen(s), NULL); +} + +/* + * Copy @s to @buf and replace control and non-printable chars with + * \x?? hex sequence. The @width returns number of cells. The @safechars + * are not encoded. + * + * The @buf has to be big enough to store mbs_safe_encode_size(strlen(s))) + * bytes. + */ +char *mbs_safe_encode_to_buffer(const char *s, size_t *width, char *buf, const char *safechars) +{ + const char *p = s; + char *r; + size_t sz = s ? strlen(s) : 0; + +#ifdef HAVE_WIDECHAR + mbstate_t st; + memset(&st, 0, sizeof(st)); +#endif + if (!sz || !buf) + return NULL; + + r = buf; + *width = 0; + + while (p && *p) { + if (safechars && strchr(safechars, *p)) { + *r++ = *p++; + continue; + } + + if ((*p == '\\' && *(p + 1) == 'x') + || iscntrl((unsigned char) *p)) { + sprintf(r, "\\x%02x", (unsigned char) *p); + r += 4; + *width += 4; + p++; + } +#ifdef HAVE_WIDECHAR + else { + wchar_t wc; + size_t len = mbrtowc(&wc, p, MB_CUR_MAX, &st); + + if (len == 0) + break; /* end of string */ + + if (len == (size_t) -1 || len == (size_t) -2) { + len = 1; + /* + * Not valid multibyte sequence -- maybe it's + * printable char according to the current locales. + */ + if (!isprint((unsigned char) *p)) { + sprintf(r, "\\x%02x", (unsigned char) *p); + r += 4; + *width += 4; + } else { + (*width)++; + *r++ = *p; + } + } else if (!iswprint(wc)) { + size_t i; + for (i = 0; i < len; i++) { + sprintf(r, "\\x%02x", (unsigned char) p[i]); + r += 4; + *width += 4; + } + } else { + memcpy(r, p, len); + r += len; + *width += wcwidth(wc); + } + p += len; + } +#else + else if (!isprint((unsigned char) *p)) { + sprintf(r, "\\x%02x", (unsigned char) *p); + p++; + r += 4; + *width += 4; + } else { + *r++ = *p++; + (*width)++; + } +#endif + } + + *r = '\0'; + return buf; +} + +/* + * Copy @s to @buf and replace broken sequences to \x?? hex sequence. The + * @width returns number of cells. The @safechars are not encoded. + * + * The @buf has to be big enough to store mbs_safe_encode_size(strlen(s))) + * bytes. + */ +char *mbs_invalid_encode_to_buffer(const char *s, size_t *width, char *buf) +{ + const char *p = s; + char *r; + size_t sz = s ? strlen(s) : 0; + +#ifdef HAVE_WIDECHAR + mbstate_t st; + memset(&st, 0, sizeof(st)); +#endif + if (!sz || !buf) + return NULL; + + r = buf; + *width = 0; + + while (p && *p) { +#ifdef HAVE_WIDECHAR + wchar_t wc; + size_t len = mbrtowc(&wc, p, MB_CUR_MAX, &st); +#else + size_t len = 1; +#endif + + if (len == 0) + break; /* end of string */ + + if (len == (size_t) -1 || len == (size_t) -2) { + len = 1; + /* + * Not valid multibyte sequence -- maybe it's + * printable char according to the current locales. + */ + if (!isprint((unsigned char) *p)) { + sprintf(r, "\\x%02x", (unsigned char) *p); + r += 4; + *width += 4; + } else { + (*width)++; + *r++ = *p; + } + } else if (*p == '\\' && *(p + 1) == 'x') { + sprintf(r, "\\x%02x", (unsigned char) *p); + r += 4; + *width += 4; + } else { + memcpy(r, p, len); + r += len; + *width += wcwidth(wc); + } + p += len; + } + + *r = '\0'; + return buf; +} + +size_t mbs_safe_encode_size(size_t bytes) +{ + return (bytes * 4) + 1; +} + +/* + * Returns allocated string where all control and non-printable chars are + * replaced with \x?? hex sequence. + */ +char *mbs_safe_encode(const char *s, size_t *width) +{ + size_t sz = s ? strlen(s) : 0; + char *buf, *ret = NULL; + + if (!sz) + return NULL; + buf = malloc(mbs_safe_encode_size(sz)); + if (buf) + ret = mbs_safe_encode_to_buffer(s, width, buf, NULL); + if (!ret) + free(buf); + return ret; +} + +/* + * Returns allocated string where all broken widechars chars are + * replaced with \x?? hex sequence. + */ +char *mbs_invalid_encode(const char *s, size_t *width) +{ + size_t sz = s ? strlen(s) : 0; + char *buf, *ret = NULL; + + if (!sz) + return NULL; + buf = malloc(mbs_safe_encode_size(sz)); + if (buf) + ret = mbs_invalid_encode_to_buffer(s, width, buf); + if (!ret) + free(buf); + return ret; +} + +#ifdef HAVE_WIDECHAR + +static bool +wc_ensure_printable (wchar_t *wchars) +{ + bool replaced = false; + wchar_t *wc = wchars; + while (*wc) + { + if (!iswprint ((wint_t) *wc)) + { + *wc = 0xFFFD; /* L'\uFFFD' (replacement char) */ + replaced = true; + } + wc++; + } + return replaced; +} + +/* Truncate wchar string to width cells. + * Returns number of cells used. */ + +static size_t +wc_truncate (wchar_t *wc, size_t width) +{ + size_t cells = 0; + int next_cells = 0; + + while (*wc) + { + next_cells = wcwidth (*wc); + if (next_cells == -1) /* non printable */ + { + *wc = 0xFFFD; /* L'\uFFFD' (replacement char) */ + next_cells = 1; + } + if (cells + next_cells > width) + break; + + cells += next_cells; + wc++; + } + *wc = L'\0'; + return cells; +} + +static int +rpl_wcswidth (const wchar_t *s, size_t n) +{ + int ret = 0; + + while (n-- > 0 && *s != L'\0') + { + int nwidth = wcwidth (*s++); + if (nwidth == -1) /* non printable */ + return -1; + if (ret > (INT_MAX - nwidth)) /* overflow */ + return -1; + ret += nwidth; + } + + return ret; +} +#endif /* HAVE_WIDECHAR */ + +/* Truncate multi-byte string to @width and returns number of + * bytes of the new string @str, and in @width returns number + * of cells. + */ +size_t +mbs_truncate(char *str, size_t *width) +{ + ssize_t bytes = strlen(str); +#ifdef HAVE_WIDECHAR + ssize_t sz = mbstowcs(NULL, str, 0); + wchar_t *wcs = NULL; + + if (sz == (ssize_t) -1) + goto done; + + wcs = calloc(1, (sz + 1) * sizeof(wchar_t)); + if (!wcs) + goto done; + + if (!mbstowcs(wcs, str, sz)) + goto done; + *width = wc_truncate(wcs, *width); + bytes = wcstombs(str, wcs, bytes); +done: + free(wcs); +#else + if (bytes >= 0 && *width < (size_t) bytes) + bytes = *width; +#endif + if (bytes >= 0) + str[bytes] = '\0'; + return bytes; +} + +/* Write N_SPACES space characters to DEST while ensuring + nothing is written beyond DEST_END. A terminating NUL + is always added to DEST. + A pointer to the terminating NUL is returned. */ + +static char* +mbs_align_pad (char *dest, const char* dest_end, size_t n_spaces, int padchar) +{ + for (/* nothing */; n_spaces && (dest < dest_end); n_spaces--) + *dest++ = padchar; + *dest = '\0'; + return dest; +} + +size_t +mbsalign (const char *src, char *dest, size_t dest_size, + size_t *width, mbs_align_t align, int flags) +{ + return mbsalign_with_padding(src, dest, dest_size, width, align, flags, ' '); +} + +/* Align a string, SRC, in a field of *WIDTH columns, handling multi-byte + characters; write the result into the DEST_SIZE-byte buffer, DEST. + ALIGNMENT specifies whether to left- or right-justify or to center. + If SRC requires more than *WIDTH columns, truncate it to fit. + When centering, the number of trailing spaces may be one less than the + number of leading spaces. The FLAGS parameter is unused at present. + Return the length in bytes required for the final result, not counting + the trailing NUL. A return value of DEST_SIZE or larger means there + wasn't enough space. DEST will be NUL terminated in any case. + Return (size_t) -1 upon error (invalid multi-byte sequence in SRC, + or malloc failure), unless MBA_UNIBYTE_FALLBACK is specified. + Update *WIDTH to indicate how many columns were used before padding. */ + +size_t +mbsalign_with_padding (const char *src, char *dest, size_t dest_size, + size_t *width, mbs_align_t align, +#ifdef HAVE_WIDECHAR + int flags, +#else + int flags __attribute__((__unused__)), +#endif + int padchar) +{ + size_t ret = -1; + size_t src_size = strlen (src) + 1; + char *newstr = NULL; + wchar_t *str_wc = NULL; + const char *str_to_print = src; + size_t n_cols = src_size - 1; + size_t n_used_bytes = n_cols; /* Not including NUL */ + size_t n_spaces = 0, space_left; + +#ifdef HAVE_WIDECHAR + bool conversion = false; + bool wc_enabled = false; + + /* In multi-byte locales convert to wide characters + to allow easy truncation. Also determine number + of screen columns used. */ + if (MB_CUR_MAX > 1) + { + size_t src_chars = mbstowcs (NULL, src, 0); + if (src_chars == (size_t) -1) + { + if (flags & MBA_UNIBYTE_FALLBACK) + goto mbsalign_unibyte; + else + goto mbsalign_cleanup; + } + src_chars += 1; /* make space for NUL */ + str_wc = malloc (src_chars * sizeof (wchar_t)); + if (str_wc == NULL) + { + if (flags & MBA_UNIBYTE_FALLBACK) + goto mbsalign_unibyte; + else + goto mbsalign_cleanup; + } + if (mbstowcs (str_wc, src, src_chars) != 0) + { + str_wc[src_chars - 1] = L'\0'; + wc_enabled = true; + conversion = wc_ensure_printable (str_wc); + n_cols = rpl_wcswidth (str_wc, src_chars); + } + } + + /* If we transformed or need to truncate the source string + then create a modified copy of it. */ + if (wc_enabled && (conversion || (n_cols > *width))) + { + if (conversion) + { + /* May have increased the size by converting + \t to \uFFFD for example. */ + src_size = wcstombs(NULL, str_wc, 0) + 1; + } + newstr = malloc (src_size); + if (newstr == NULL) + { + if (flags & MBA_UNIBYTE_FALLBACK) + goto mbsalign_unibyte; + else + goto mbsalign_cleanup; + } + str_to_print = newstr; + n_cols = wc_truncate (str_wc, *width); + n_used_bytes = wcstombs (newstr, str_wc, src_size); + } + +mbsalign_unibyte: +#endif + + if (n_cols > *width) /* Unibyte truncation required. */ + { + n_cols = *width; + n_used_bytes = n_cols; + } + + if (*width > n_cols) /* Padding required. */ + n_spaces = *width - n_cols; + + /* indicate to caller how many cells needed (not including padding). */ + *width = n_cols; + + /* indicate to caller how many bytes needed (not including NUL). */ + ret = n_used_bytes + (n_spaces * 1); + + /* Write as much NUL terminated output to DEST as possible. */ + if (dest_size != 0) + { + char *dest_end = dest + dest_size - 1; + size_t start_spaces; + size_t end_spaces; + + switch (align) + { + case MBS_ALIGN_CENTER: + start_spaces = n_spaces / 2 + n_spaces % 2; + end_spaces = n_spaces / 2; + break; + case MBS_ALIGN_LEFT: + start_spaces = 0; + end_spaces = n_spaces; + break; + case MBS_ALIGN_RIGHT: + start_spaces = n_spaces; + end_spaces = 0; + break; + default: + abort(); + } + + dest = mbs_align_pad (dest, dest_end, start_spaces, padchar); + space_left = dest_end - dest; + dest = mempcpy (dest, str_to_print, min (n_used_bytes, space_left)); + mbs_align_pad (dest, dest_end, end_spaces, padchar); + } +#ifdef HAVE_WIDECHAR +mbsalign_cleanup: +#endif + free (str_wc); + free (newstr); + + return ret; +} diff --git a/utils/lib/mbsedit.c b/utils/lib/mbsedit.c new file mode 100644 index 0000000..8ce5901 --- /dev/null +++ b/utils/lib/mbsedit.c @@ -0,0 +1,225 @@ +/* + * Very simple multibyte buffer editor. Allows to maintaine the current + * position in the string, add and remove chars on the current position. + * + * This file may be distributed under the terms of the + * GNU Lesser General Public License. + * + * Copyright (C) 2017 Karel Zak + */ +#include +#include +#include +#include + +#include "mbsalign.h" +#include "mbsedit.h" + +struct mbs_editor *mbs_new_edit(char *buf, size_t bufsz, size_t ncells) +{ + struct mbs_editor *edit = calloc(1, sizeof(*edit)); + + if (edit) { + edit->buf = buf; + edit->max_bytes = bufsz; + edit->max_cells = ncells; + edit->cur_cells = mbs_safe_width(buf); + edit->cur_bytes = strlen(buf); + } + return edit; +} + +char *mbs_free_edit(struct mbs_editor *edit) +{ + char *ret = edit ? edit->buf : NULL; + + free(edit); + return ret; +} + +static size_t mbs_next(const char *str, size_t *ncells) +{ +#ifdef HAVE_WIDECHAR + wchar_t wc; + size_t n = 0; + + if (!str || !*str) + return 0; + + n = mbrtowc(&wc, str, MB_CUR_MAX, NULL); + *ncells = wcwidth(wc); + return n; +#else + if (!str || !*str) + return 0; + *ncells = 1; + return 1; +#endif +} + +static size_t mbs_prev(const char *start, const char *end, size_t *ncells) +{ +#ifdef HAVE_WIDECHAR + wchar_t wc = 0; + const char *p, *prev; + size_t n = 0; + + if (!start || !end || start == end || !*start) + return 0; + + prev = p = start; + while (p < end) { + n = mbrtowc(&wc, p, MB_CUR_MAX, NULL); + prev = p; + + if (n == (size_t) -1 || n == (size_t) -2) + p++; + else + p += n; + } + + if (prev == end) + return 0; + *ncells = wcwidth(wc); + return n; +#else + if (!start || !end || start == end || !*start) + return 0; + *ncells = 1; + return 1; +#endif +} + +int mbs_edit_goto(struct mbs_editor *edit, int where) +{ + switch (where) { + case MBS_EDIT_LEFT: + if (edit->cursor == 0) + return 1; + else { + size_t n, cells; + n = mbs_prev(edit->buf, edit->buf + edit->cursor, &cells); + if (n) { + edit->cursor -= n; + edit->cursor_cells -= cells; + } + } + break; + case MBS_EDIT_RIGHT: + if (edit->cursor_cells >= edit->cur_cells) + return 1; + else { + size_t n, cells; + n = mbs_next(edit->buf + edit->cursor, &cells); + if (n) { + edit->cursor += n; + edit->cursor_cells += cells; + } + } + break; + case MBS_EDIT_HOME: + edit->cursor = 0; + edit->cursor_cells = 0; + break; + case MBS_EDIT_END: + edit->cursor = edit->cur_bytes; + edit->cursor_cells = edit->cur_cells; + break; + default: + return -EINVAL; + } + + return 0; +} + +/* Remove next MB from @str, returns number of removed bytes */ +static size_t remove_next(char *str, size_t *ncells) +{ + /* all in bytes! */ + size_t bytes, move_bytes, n; + + n = mbs_next(str, ncells); + bytes = strlen(str); + move_bytes = bytes - n; + + memmove(str, str + n, move_bytes); + str[bytes - n] = '\0'; + return n; +} + +static size_t mbs_insert(char *str, wint_t c, size_t *ncells) +{ + /* all in bytes! */ + size_t n = 1, bytes; + char *in; + +#ifdef HAVE_WIDECHAR + wchar_t wc = (wchar_t) c; + char in_buf[MB_CUR_MAX]; + + n = wctomb(in_buf, wc); + if (n == (size_t) -1) + return n; + *ncells = wcwidth(wc); + in = in_buf; +#else + *ncells = 1; + in = (char *) &c; +#endif + bytes = strlen(str); + + memmove(str + n, str, bytes); + memcpy(str, in, n); + str[bytes + n] = '\0'; + return n; +} + +static int mbs_edit_remove(struct mbs_editor *edit) +{ + size_t n, ncells; + + if (edit->cur_cells == 0 || edit->cursor >= edit->cur_bytes) + return 1; + + n = remove_next(edit->buf + edit->cursor, &ncells); + if (n == (size_t)-1) + return 1; + + edit->cur_bytes -= n; + edit->cur_cells = mbs_safe_width(edit->buf); + return 0; +} + +int mbs_edit_delete(struct mbs_editor *edit) +{ + if (edit->cursor >= edit->cur_bytes + && mbs_edit_goto(edit, MBS_EDIT_LEFT) == 1) + return 1; + + return mbs_edit_remove(edit); +} + +int mbs_edit_backspace(struct mbs_editor *edit) +{ + if (mbs_edit_goto(edit, MBS_EDIT_LEFT) == 0) + return mbs_edit_remove(edit); + return 1; +} + +int mbs_edit_insert(struct mbs_editor *edit, wint_t c) +{ + size_t n, ncells; + + if (edit->cur_bytes + MB_CUR_MAX > edit->max_bytes) + return 1; + + n = mbs_insert(edit->buf + edit->cursor, c, &ncells); + if (n == (size_t)-1) + return 1; + + edit->cursor += n; + edit->cursor_cells += ncells; + edit->cur_bytes += n; + edit->cur_cells = mbs_safe_width(edit->buf); + return 0; +} diff --git a/utils/lib/md5.c b/utils/lib/md5.c new file mode 100644 index 0000000..3765ab9 --- /dev/null +++ b/utils/lib/md5.c @@ -0,0 +1,257 @@ +/* + * This code implements the MD5 message-digest algorithm. + * The algorithm is due to Ron Rivest. This code was + * written by Colin Plumb in 1993, no copyright is claimed. + * This code is in the public domain; do with it what you wish. + * + * Equivalent code is available from RSA Data Security, Inc. + * This code has been tested against that, and is equivalent, + * except that you don't need to include two pages of legalese + * with every copy. + * + * To compute the message digest of a chunk of bytes, declare an + * MD5Context structure, pass it to MD5Init, call MD5Update as + * needed on buffers full of bytes, and then call MD5Final, which + * will fill a supplied 16-byte array with the digest. + */ +#include /* for memcpy() */ + +#include "md5.h" + +#if !defined(WORDS_BIGENDIAN) +# define byteReverse(buf, len) /* Nothing */ +#else +static void byteReverse(unsigned char *buf, unsigned longs); + +#ifndef ASM_MD5 +/* + * Note: this code is harmless on little-endian machines. + */ +static void byteReverse(unsigned char *buf, unsigned longs) +{ + uint32_t t; + do { + t = (uint32_t) ((unsigned) buf[3] << 8 | buf[2]) << 16 | + ((unsigned) buf[1] << 8 | buf[0]); + *(uint32_t *) buf = t; + buf += 4; + } while (--longs); +} +#endif /* !ASM_MD5 */ +#endif /* !WORDS_BIGENDIAN */ + +/* + * Start MD5 accumulation. Set bit count to 0 and buffer to mysterious + * initialization constants. + */ +void ul_MD5Init(struct UL_MD5Context *ctx) +{ + ctx->buf[0] = 0x67452301; + ctx->buf[1] = 0xefcdab89; + ctx->buf[2] = 0x98badcfe; + ctx->buf[3] = 0x10325476; + + ctx->bits[0] = 0; + ctx->bits[1] = 0; +} + +/* + * Update context to reflect the concatenation of another buffer full + * of bytes. + */ +void ul_MD5Update(struct UL_MD5Context *ctx, unsigned char const *buf, unsigned len) +{ + uint32_t t; + + /* Update bitcount */ + + t = ctx->bits[0]; + if ((ctx->bits[0] = t + ((uint32_t) len << 3)) < t) + ctx->bits[1]++; /* Carry from low to high */ + ctx->bits[1] += len >> 29; + + t = (t >> 3) & 0x3f; /* Bytes already in shsInfo->data */ + + /* Handle any leading odd-sized chunks */ + + if (t) { + unsigned char *p = (unsigned char *) ctx->in + t; + + t = 64 - t; + if (len < t) { + memcpy(p, buf, len); + return; + } + memcpy(p, buf, t); + byteReverse(ctx->in, 16); + ul_MD5Transform(ctx->buf, (uint32_t *) ctx->in); + buf += t; + len -= t; + } + /* Process data in 64-byte chunks */ + + while (len >= 64) { + memcpy(ctx->in, buf, 64); + byteReverse(ctx->in, 16); + ul_MD5Transform(ctx->buf, (uint32_t *) ctx->in); + buf += 64; + len -= 64; + } + + /* Handle any remaining bytes of data. */ + + memcpy(ctx->in, buf, len); +} + +/* + * Final wrapup - pad to 64-byte boundary with the bit pattern + * 1 0* (64-bit count of bits processed, MSB-first) + */ +void ul_MD5Final(unsigned char digest[UL_MD5LENGTH], struct UL_MD5Context *ctx) +{ + unsigned count; + unsigned char *p; + + /* Compute number of bytes mod 64 */ + count = (ctx->bits[0] >> 3) & 0x3F; + + /* Set the first char of padding to 0x80. This is safe since there is + always at least one byte free */ + p = ctx->in + count; + *p++ = 0x80; + + /* Bytes of padding needed to make 64 bytes */ + count = 64 - 1 - count; + + /* Pad out to 56 mod 64 */ + if (count < 8) { + /* Two lots of padding: Pad the first block to 64 bytes */ + memset(p, 0, count); + byteReverse(ctx->in, 16); + ul_MD5Transform(ctx->buf, (uint32_t *) ctx->in); + + /* Now fill the next block with 56 bytes */ + memset(ctx->in, 0, 56); + } else { + /* Pad block to 56 bytes */ + memset(p, 0, count - 8); + } + byteReverse(ctx->in, 14); + + /* Append length in bits and transform. + * Use memcpy to avoid aliasing problems. On most systems, + * this will be optimized away to the same code. + */ + memcpy(&ctx->in[14 * sizeof(uint32_t)], &ctx->bits[0], 4); + memcpy(&ctx->in[15 * sizeof(uint32_t)], &ctx->bits[1], 4); + + ul_MD5Transform(ctx->buf, (uint32_t *) ctx->in); + byteReverse((unsigned char *) ctx->buf, 4); + memcpy(digest, ctx->buf, UL_MD5LENGTH); + memset(ctx, 0, sizeof(*ctx)); /* In case it's sensitive */ +} + +#ifndef ASM_MD5 + +/* The four core functions - F1 is optimized somewhat */ + +/* #define F1(x, y, z) (x & y | ~x & z) */ +#define F1(x, y, z) (z ^ (x & (y ^ z))) +#define F2(x, y, z) F1(z, x, y) +#define F3(x, y, z) (x ^ y ^ z) +#define F4(x, y, z) (y ^ (x | ~z)) + +/* This is the central step in the MD5 algorithm. */ +#define MD5STEP(f, w, x, y, z, data, s) \ + ( w += f(x, y, z) + data, w = w<>(32-s), w += x ) + +/* + * The core of the MD5 algorithm, this alters an existing MD5 hash to + * reflect the addition of 16 longwords of new data. MD5Update blocks + * the data and converts bytes into longwords for this routine. + */ +void ul_MD5Transform(uint32_t buf[4], uint32_t const in[16]) +{ + register uint32_t a, b, c, d; + + a = buf[0]; + b = buf[1]; + c = buf[2]; + d = buf[3]; + + MD5STEP(F1, a, b, c, d, in[0] + 0xd76aa478, 7); + MD5STEP(F1, d, a, b, c, in[1] + 0xe8c7b756, 12); + MD5STEP(F1, c, d, a, b, in[2] + 0x242070db, 17); + MD5STEP(F1, b, c, d, a, in[3] + 0xc1bdceee, 22); + MD5STEP(F1, a, b, c, d, in[4] + 0xf57c0faf, 7); + MD5STEP(F1, d, a, b, c, in[5] + 0x4787c62a, 12); + MD5STEP(F1, c, d, a, b, in[6] + 0xa8304613, 17); + MD5STEP(F1, b, c, d, a, in[7] + 0xfd469501, 22); + MD5STEP(F1, a, b, c, d, in[8] + 0x698098d8, 7); + MD5STEP(F1, d, a, b, c, in[9] + 0x8b44f7af, 12); + MD5STEP(F1, c, d, a, b, in[10] + 0xffff5bb1, 17); + MD5STEP(F1, b, c, d, a, in[11] + 0x895cd7be, 22); + MD5STEP(F1, a, b, c, d, in[12] + 0x6b901122, 7); + MD5STEP(F1, d, a, b, c, in[13] + 0xfd987193, 12); + MD5STEP(F1, c, d, a, b, in[14] + 0xa679438e, 17); + MD5STEP(F1, b, c, d, a, in[15] + 0x49b40821, 22); + + MD5STEP(F2, a, b, c, d, in[1] + 0xf61e2562, 5); + MD5STEP(F2, d, a, b, c, in[6] + 0xc040b340, 9); + MD5STEP(F2, c, d, a, b, in[11] + 0x265e5a51, 14); + MD5STEP(F2, b, c, d, a, in[0] + 0xe9b6c7aa, 20); + MD5STEP(F2, a, b, c, d, in[5] + 0xd62f105d, 5); + MD5STEP(F2, d, a, b, c, in[10] + 0x02441453, 9); + MD5STEP(F2, c, d, a, b, in[15] + 0xd8a1e681, 14); + MD5STEP(F2, b, c, d, a, in[4] + 0xe7d3fbc8, 20); + MD5STEP(F2, a, b, c, d, in[9] + 0x21e1cde6, 5); + MD5STEP(F2, d, a, b, c, in[14] + 0xc33707d6, 9); + MD5STEP(F2, c, d, a, b, in[3] + 0xf4d50d87, 14); + MD5STEP(F2, b, c, d, a, in[8] + 0x455a14ed, 20); + MD5STEP(F2, a, b, c, d, in[13] + 0xa9e3e905, 5); + MD5STEP(F2, d, a, b, c, in[2] + 0xfcefa3f8, 9); + MD5STEP(F2, c, d, a, b, in[7] + 0x676f02d9, 14); + MD5STEP(F2, b, c, d, a, in[12] + 0x8d2a4c8a, 20); + + MD5STEP(F3, a, b, c, d, in[5] + 0xfffa3942, 4); + MD5STEP(F3, d, a, b, c, in[8] + 0x8771f681, 11); + MD5STEP(F3, c, d, a, b, in[11] + 0x6d9d6122, 16); + MD5STEP(F3, b, c, d, a, in[14] + 0xfde5380c, 23); + MD5STEP(F3, a, b, c, d, in[1] + 0xa4beea44, 4); + MD5STEP(F3, d, a, b, c, in[4] + 0x4bdecfa9, 11); + MD5STEP(F3, c, d, a, b, in[7] + 0xf6bb4b60, 16); + MD5STEP(F3, b, c, d, a, in[10] + 0xbebfbc70, 23); + MD5STEP(F3, a, b, c, d, in[13] + 0x289b7ec6, 4); + MD5STEP(F3, d, a, b, c, in[0] + 0xeaa127fa, 11); + MD5STEP(F3, c, d, a, b, in[3] + 0xd4ef3085, 16); + MD5STEP(F3, b, c, d, a, in[6] + 0x04881d05, 23); + MD5STEP(F3, a, b, c, d, in[9] + 0xd9d4d039, 4); + MD5STEP(F3, d, a, b, c, in[12] + 0xe6db99e5, 11); + MD5STEP(F3, c, d, a, b, in[15] + 0x1fa27cf8, 16); + MD5STEP(F3, b, c, d, a, in[2] + 0xc4ac5665, 23); + + MD5STEP(F4, a, b, c, d, in[0] + 0xf4292244, 6); + MD5STEP(F4, d, a, b, c, in[7] + 0x432aff97, 10); + MD5STEP(F4, c, d, a, b, in[14] + 0xab9423a7, 15); + MD5STEP(F4, b, c, d, a, in[5] + 0xfc93a039, 21); + MD5STEP(F4, a, b, c, d, in[12] + 0x655b59c3, 6); + MD5STEP(F4, d, a, b, c, in[3] + 0x8f0ccc92, 10); + MD5STEP(F4, c, d, a, b, in[10] + 0xffeff47d, 15); + MD5STEP(F4, b, c, d, a, in[1] + 0x85845dd1, 21); + MD5STEP(F4, a, b, c, d, in[8] + 0x6fa87e4f, 6); + MD5STEP(F4, d, a, b, c, in[15] + 0xfe2ce6e0, 10); + MD5STEP(F4, c, d, a, b, in[6] + 0xa3014314, 15); + MD5STEP(F4, b, c, d, a, in[13] + 0x4e0811a1, 21); + MD5STEP(F4, a, b, c, d, in[4] + 0xf7537e82, 6); + MD5STEP(F4, d, a, b, c, in[11] + 0xbd3af235, 10); + MD5STEP(F4, c, d, a, b, in[2] + 0x2ad7d2bb, 15); + MD5STEP(F4, b, c, d, a, in[9] + 0xeb86d391, 21); + + buf[0] += a; + buf[1] += b; + buf[2] += c; + buf[3] += d; +} + +#endif + diff --git a/utils/lib/monotonic.c b/utils/lib/monotonic.c new file mode 100644 index 0000000..f0aeba6 --- /dev/null +++ b/utils/lib/monotonic.c @@ -0,0 +1,81 @@ +/* + * Please, don't add this file to libcommon because clock_gettime() requires + * -lrt on systems with old libc. + * + * No copyright is claimed. This code is in the public domain; do with + * it what you wish. + */ +#include +#include +#ifdef HAVE_SYSINFO +#include +#endif +#include + +#include "c.h" +#include "monotonic.h" + +int get_boot_time(struct timeval *boot_time) +{ +#ifdef CLOCK_BOOTTIME + struct timespec hires_uptime; + struct timeval lores_uptime; +#endif + struct timeval now; +#ifdef HAVE_SYSINFO + struct sysinfo info; +#endif + + if (gettimeofday(&now, NULL) != 0) + return -errno; +#ifdef CLOCK_BOOTTIME + if (clock_gettime(CLOCK_BOOTTIME, &hires_uptime) == 0) { + TIMESPEC_TO_TIMEVAL(&lores_uptime, &hires_uptime); + timersub(&now, &lores_uptime, boot_time); + return 0; + } +#endif +#ifdef HAVE_SYSINFO + /* fallback */ + if (sysinfo(&info) != 0) + return -errno; + + boot_time->tv_sec = now.tv_sec - info.uptime; + boot_time->tv_usec = 0; + return 0; +#else + return -ENOSYS; +#endif +} + +time_t get_suspended_time(void) +{ +#if defined(CLOCK_BOOTTIME) && defined(CLOCK_MONOTONIC) + struct timespec boot, mono; + + if (clock_gettime(CLOCK_BOOTTIME, &boot) == 0 && + clock_gettime(CLOCK_MONOTONIC, &mono) == 0) + return boot.tv_sec - mono.tv_sec; +#endif + return 0; +} + +int gettime_monotonic(struct timeval *tv) +{ +#ifdef CLOCK_MONOTONIC + /* Can slew only by ntp and adjtime */ + int ret; + struct timespec ts; + + /* Linux specific, can't slew */ + if (!(ret = clock_gettime(UL_CLOCK_MONOTONIC, &ts))) { + tv->tv_sec = ts.tv_sec; + tv->tv_usec = ts.tv_nsec / 1000; + } + return ret; +#else + return gettimeofday(tv, NULL); +#endif +} + + diff --git a/utils/lib/pager.c b/utils/lib/pager.c new file mode 100644 index 0000000..b3cf6ee --- /dev/null +++ b/utils/lib/pager.c @@ -0,0 +1,317 @@ +/* + * Based on linux-perf/git scm + * + * Some modifications and simplifications for util-linux + * by Davidlohr Bueso - March 2012. + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "c.h" +#include "xalloc.h" +#include "nls.h" +#include "ttyutils.h" +#include "pager.h" + +#define NULL_DEVICE "/dev/null" + +static const char *pager_argv[] = { "sh", "-c", NULL, NULL }; + +struct child_process { + const char **argv; + pid_t pid; + int in; + int out; + int err; + + int org_err; + int org_out; + struct sigaction orig_sigint; + struct sigaction orig_sighup; + struct sigaction orig_sigterm; + struct sigaction orig_sigquit; + struct sigaction orig_sigpipe; + + unsigned no_stdin:1; + void (*preexec_cb)(void); +}; +static struct child_process pager_process; + +static inline void close_pair(int fd[2]) +{ + close(fd[0]); + close(fd[1]); +} + +static int start_command(struct child_process *cmd) +{ + int need_in; + int fdin[2]; + + /* + * In case of errors we must keep the promise to close FDs + * that have been passed in via ->in and ->out. + */ + need_in = !cmd->no_stdin && cmd->in < 0; + if (need_in) { + if (pipe(fdin) < 0) { + if (cmd->out > 0) + close(cmd->out); + return -1; + } + cmd->in = fdin[1]; + } + + fflush(NULL); + cmd->pid = fork(); + if (!cmd->pid) { + if (need_in) { + dup2(fdin[0], STDIN_FILENO); + close_pair(fdin); + } else if (cmd->in > 0) { + dup2(cmd->in, STDIN_FILENO); + close(cmd->in); + } + + cmd->preexec_cb(); + execvp(cmd->argv[0], (char *const*) cmd->argv); + errexec(cmd->argv[0]); + } + + if (cmd->pid < 0) { + if (need_in) + close_pair(fdin); + else if (cmd->in) + close(cmd->in); + return -1; + } + + if (need_in) + close(fdin[0]); + else if (cmd->in) + close(cmd->in); + return 0; +} + +static int wait_or_whine(pid_t pid) +{ + for (;;) { + int status, code; + pid_t waiting = waitpid(pid, &status, 0); + + if (waiting < 0) { + if (errno == EINTR) + continue; + err(EXIT_FAILURE, _("waitpid failed (%s)"), strerror(errno)); + } + if (waiting != pid) + return -1; + if (WIFSIGNALED(status)) + return -1; + + if (!WIFEXITED(status)) + return -1; + code = WEXITSTATUS(status); + switch (code) { + case 127: + return -1; + case 0: + return 0; + default: + return -1; + } + } +} + +static int finish_command(struct child_process *cmd) +{ + return wait_or_whine(cmd->pid); +} + +static void pager_preexec(void) +{ + /* + * Work around bug in "less" by not starting it until we + * have real input + */ + fd_set in, ex; + + FD_ZERO(&in); + FD_SET(STDIN_FILENO, &in); + ex = in; + + select(STDIN_FILENO + 1, &in, NULL, &ex, NULL); + + if (setenv("LESS", "FRSX", 0) != 0) + warn(_("failed to set the %s environment variable"), "LESS"); +} + +static void wait_for_pager(void) +{ + if (pager_process.pid == 0) + return; + + fflush(stdout); + fflush(stderr); + /* signal EOF to pager */ + close(STDOUT_FILENO); + close(STDERR_FILENO); + finish_command(&pager_process); +} + +static void wait_for_pager_signal(int signo) +{ + wait_for_pager(); + raise(signo); +} + +static int has_command(const char *cmd) +{ + const char *path; + char *p, *s; + int rc = 0; + + if (!cmd) + goto done; + if (*cmd == '/') { + rc = access(cmd, X_OK) == 0; + goto done; + } + + path = getenv("PATH"); + if (!path) + goto done; + p = xstrdup(path); + if (!p) + goto done; + + for(s = strtok(p, ":"); s; s = strtok(NULL, ":")) { + int fd = open(s, O_RDONLY|O_CLOEXEC); + if (fd < 0) + continue; + rc = faccessat(fd, cmd, X_OK, 0) == 0; + close(fd); + if (rc) + break; + } + free(p); +done: + /*fprintf(stderr, "has PAGER %s rc=%d\n", cmd, rc);*/ + return rc; +} + +static void __setup_pager(void) +{ + const char *pager = getenv("PAGER"); + struct sigaction sa; + + if (!isatty(STDOUT_FILENO)) + return; + + if (!pager) + pager = "less"; + else if (!*pager || !strcmp(pager, "cat")) + return; + + if (!has_command(pager)) + return; + + /* spawn the pager */ + pager_argv[2] = pager; + pager_process.argv = pager_argv; + pager_process.in = -1; + pager_process.preexec_cb = pager_preexec; + + if (start_command(&pager_process)) + return; + + /* original process continues, but writes to the pipe */ + dup2(pager_process.in, STDOUT_FILENO); + if (isatty(STDERR_FILENO)) + dup2(pager_process.in, STDERR_FILENO); + close(pager_process.in); + + memset(&sa, 0, sizeof(sa)); + sa.sa_handler = wait_for_pager_signal; + + /* this makes sure that the parent terminates after the pager */ + sigaction(SIGINT, &sa, &pager_process.orig_sigint); + sigaction(SIGHUP, &sa, &pager_process.orig_sighup); + sigaction(SIGTERM, &sa, &pager_process.orig_sigterm); + sigaction(SIGQUIT, &sa, &pager_process.orig_sigquit); + sigaction(SIGPIPE, &sa, &pager_process.orig_sigpipe); +} + +/* Setup pager and redirects output to the $PAGER. The pager is closed at exit. + */ +void pager_redirect(void) +{ + if (pager_process.pid) + return; /* already running */ + + __setup_pager(); + + atexit(wait_for_pager); +} + +/* Setup pager and redirect output, the pager may be closed by pager_close(). + */ +void pager_open(void) +{ + if (pager_process.pid) + return; /* already running */ + + pager_process.org_out = dup(STDOUT_FILENO); + pager_process.org_err = dup(STDERR_FILENO); + + __setup_pager(); +} + +/* Close pager and restore original std{out,err}. + */ +void pager_close(void) +{ + if (pager_process.pid == 0) + return; + + wait_for_pager(); + + /* restore original output */ + dup2(pager_process.org_out, STDOUT_FILENO); + dup2(pager_process.org_err, STDERR_FILENO); + + close(pager_process.org_out); + close(pager_process.org_err); + + /* restore original segnals setting */ + sigaction(SIGINT, &pager_process.orig_sigint, NULL); + sigaction(SIGHUP, &pager_process.orig_sighup, NULL); + sigaction(SIGTERM, &pager_process.orig_sigterm, NULL); + sigaction(SIGQUIT, &pager_process.orig_sigquit, NULL); + sigaction(SIGPIPE, &pager_process.orig_sigpipe, NULL); + + memset(&pager_process, 0, sizeof(pager_process)); +} + +#ifdef TEST_PROGRAM_PAGER + +#define MAX 255 + +int main(int argc __attribute__ ((__unused__)), + char *argv[] __attribute__ ((__unused__))) +{ + int i; + + pager_redirect(); + for (i = 0; i < MAX; i++) + printf("%d\n", i); + return EXIT_SUCCESS; +} +#endif /* TEST_PROGRAM_PAGER */ diff --git a/utils/lib/path.c b/utils/lib/path.c new file mode 100644 index 0000000..75fa853 --- /dev/null +++ b/utils/lib/path.c @@ -0,0 +1,1248 @@ +/* + * Simple functions to access files. Paths can be globally prefixed to read + * data from an alternative source (e.g. a /proc dump for regression tests). + * + * The paths is possible to format by printf-like way for functions with "f" + * postfix in the name (e.g. readf, openf, ... ul_path_readf_u64()). + * + * The ul_path_read_* API is possible to use without path_cxt handler. In this + * case is not possible to use global prefix and printf-like formatting. + * + * No copyright is claimed. This code is in the public domain; do with + * it what you wish. + * + * Written by Karel Zak [February 2018] + */ +#include +#include +#include +#include +#include +#include + +#include "c.h" +#include "fileutils.h" +#include "all-io.h" +#include "path.h" +#include "debug.h" +#include "strutils.h" + +/* + * Debug stuff (based on include/debug.h) + */ +static UL_DEBUG_DEFINE_MASK(ulpath); +UL_DEBUG_DEFINE_MASKNAMES(ulpath) = UL_DEBUG_EMPTY_MASKNAMES; + +#define ULPATH_DEBUG_INIT (1 << 1) +#define ULPATH_DEBUG_CXT (1 << 2) + +#define DBG(m, x) __UL_DBG(ulpath, ULPATH_DEBUG_, m, x) +#define ON_DBG(m, x) __UL_DBG_CALL(ulpath, ULPATH_DEBUG_, m, x) + +#define UL_DEBUG_CURRENT_MASK UL_DEBUG_MASK(ulpath) +#include "debugobj.h" + +void ul_path_init_debug(void) +{ + if (ulpath_debug_mask) + return; + __UL_INIT_DEBUG_FROM_ENV(ulpath, ULPATH_DEBUG_, 0, ULPATH_DEBUG); +} + +struct path_cxt *ul_new_path(const char *dir, ...) +{ + struct path_cxt *pc = calloc(1, sizeof(*pc)); + + if (!pc) + return NULL; + + DBG(CXT, ul_debugobj(pc, "alloc")); + + pc->refcount = 1; + pc->dir_fd = -1; + + if (dir) { + int rc; + va_list ap; + + va_start(ap, dir); + rc = vasprintf(&pc->dir_path, dir, ap); + va_end(ap); + + if (rc < 0 || !pc->dir_path) + goto fail; + } + return pc; +fail: + ul_unref_path(pc); + return NULL; +} + +void ul_ref_path(struct path_cxt *pc) +{ + if (pc) + pc->refcount++; +} + +void ul_unref_path(struct path_cxt *pc) +{ + if (!pc) + return; + + pc->refcount--; + + if (pc->refcount <= 0) { + DBG(CXT, ul_debugobj(pc, "dealloc")); + if (pc->dialect) + pc->free_dialect(pc); + ul_path_close_dirfd(pc); + free(pc->dir_path); + free(pc->prefix); + free(pc); + } +} + +int ul_path_set_prefix(struct path_cxt *pc, const char *prefix) +{ + char *p = NULL; + + assert(pc->dir_fd < 0); + + if (prefix) { + p = strdup(prefix); + if (!p) + return -ENOMEM; + } + + free(pc->prefix); + pc->prefix = p; + DBG(CXT, ul_debugobj(pc, "new prefix: '%s'", p)); + return 0; +} + +const char *ul_path_get_prefix(struct path_cxt *pc) +{ + return pc ? pc->prefix : NULL; +} + +int ul_path_set_dir(struct path_cxt *pc, const char *dir) +{ + char *p = NULL; + + if (dir) { + p = strdup(dir); + if (!p) + return -ENOMEM; + } + + if (pc->dir_fd >= 0) { + close(pc->dir_fd); + pc->dir_fd = -1; + } + + free(pc->dir_path); + pc->dir_path = p; + DBG(CXT, ul_debugobj(pc, "new dir: '%s'", p)); + return 0; +} + +const char *ul_path_get_dir(struct path_cxt *pc) +{ + return pc ? pc->dir_path : NULL; +} + +int ul_path_set_dialect(struct path_cxt *pc, void *data, void free_data(struct path_cxt *)) +{ + pc->dialect = data; + pc->free_dialect = free_data; + DBG(CXT, ul_debugobj(pc, "(re)set dialect")); + return 0; +} + +void *ul_path_get_dialect(struct path_cxt *pc) +{ + return pc ? pc->dialect : NULL; +} + +int ul_path_set_enoent_redirect(struct path_cxt *pc, int (*func)(struct path_cxt *, const char *, int *)) +{ + pc->redirect_on_enoent = func; + return 0; +} + +static const char *get_absdir(struct path_cxt *pc) +{ + int rc; + const char *dirpath; + + if (!pc->prefix) + return pc->dir_path; + + dirpath = pc->dir_path; + if (!dirpath) + return pc->prefix; + if (*dirpath == '/') + dirpath++; + + rc = snprintf(pc->path_buffer, sizeof(pc->path_buffer), "%s/%s", pc->prefix, dirpath); + if (rc < 0) + return NULL; + if ((size_t)rc >= sizeof(pc->path_buffer)) { + errno = ENAMETOOLONG; + return NULL; + } + + return pc->path_buffer; +} + +int ul_path_is_accessible(struct path_cxt *pc) +{ + const char *path; + assert(pc); + + if (pc->dir_fd >= 0) + return 1; + + path = get_absdir(pc); + if (!path) + return 0; + return access(path, F_OK) == 0; +} + +int ul_path_get_dirfd(struct path_cxt *pc) +{ + assert(pc); + assert(pc->dir_path); + + if (pc->dir_fd < 0) { + const char *path = get_absdir(pc); + if (!path) + return -errno; + + DBG(CXT, ul_debugobj(pc, "opening dir: '%s'", path)); + pc->dir_fd = open(path, O_RDONLY|O_CLOEXEC); + } + + return pc->dir_fd; +} + +/* Note that next ul_path_get_dirfd() will reopen the directory */ +void ul_path_close_dirfd(struct path_cxt *pc) +{ + assert(pc); + + if (pc->dir_fd >= 0) { + DBG(CXT, ul_debugobj(pc, "closing dir")); + close(pc->dir_fd); + pc->dir_fd = -1; + } +} + +int ul_path_isopen_dirfd(struct path_cxt *pc) +{ + return pc && pc->dir_fd >= 0; +} + +static const char *ul_path_mkpath(struct path_cxt *pc, const char *path, va_list ap) +{ + int rc; + + errno = 0; + + rc = vsnprintf(pc->path_buffer, sizeof(pc->path_buffer), path, ap); + if (rc < 0) { + if (!errno) + errno = EINVAL; + return NULL; + } + + if ((size_t)rc >= sizeof(pc->path_buffer)) { + errno = ENAMETOOLONG; + return NULL; + } + + return pc->path_buffer; +} + +char *ul_path_get_abspath(struct path_cxt *pc, char *buf, size_t bufsz, const char *path, ...) +{ + if (path) { + int rc; + va_list ap; + const char *tail = NULL, *dirpath = pc->dir_path; + + va_start(ap, path); + tail = ul_path_mkpath(pc, path, ap); + va_end(ap); + + if (dirpath && *dirpath == '/') + dirpath++; + if (tail && *tail == '/') + tail++; + + rc = snprintf(buf, bufsz, "%s/%s/%s", + pc->prefix ? pc->prefix : "", + dirpath ? dirpath : "", + tail ? tail : ""); + + if ((size_t)rc >= bufsz) { + errno = ENAMETOOLONG; + return NULL; + } + } else { + const char *tmp = get_absdir(pc); + + if (!tmp) + return NULL; + xstrncpy(buf, tmp, bufsz); + } + + return buf; +} + + +int ul_path_access(struct path_cxt *pc, int mode, const char *path) +{ + int rc; + + if (!pc) { + rc = access(path, mode); + DBG(CXT, ul_debug("access '%s' [no context, rc=%d]", path, rc)); + } else { + int dir = ul_path_get_dirfd(pc); + if (dir < 0) + return dir; + if (*path == '/') + path++; + + rc = faccessat(dir, path, mode, 0); + + if (rc && errno == ENOENT + && pc->redirect_on_enoent + && pc->redirect_on_enoent(pc, path, &dir) == 0) + rc = faccessat(dir, path, mode, 0); + + DBG(CXT, ul_debugobj(pc, "access: '%s' [rc=%d]", path, rc)); + } + return rc; +} + +int ul_path_accessf(struct path_cxt *pc, int mode, const char *path, ...) +{ + va_list ap; + const char *p; + + va_start(ap, path); + p = ul_path_mkpath(pc, path, ap); + va_end(ap); + + return !p ? -errno : ul_path_access(pc, mode, p); +} + +int ul_path_stat(struct path_cxt *pc, struct stat *sb, const char *path) +{ + int rc; + + if (!pc) { + rc = stat(path, sb); + DBG(CXT, ul_debug("stat '%s' [no context, rc=%d]", path, rc)); + } else { + int dir = ul_path_get_dirfd(pc); + if (dir < 0) + return dir; + if (*path == '/') + path++; + + rc = fstatat(dir, path, sb, 0); + + if (rc && errno == ENOENT + && pc->redirect_on_enoent + && pc->redirect_on_enoent(pc, path, &dir) == 0) + rc = fstatat(dir, path, sb, 0); + + DBG(CXT, ul_debugobj(pc, "stat '%s' [rc=%d]", path, rc)); + } + return rc; +} + +int ul_path_open(struct path_cxt *pc, int flags, const char *path) +{ + int fd; + + if (!pc) { + fd = open(path, flags); + DBG(CXT, ul_debug("opening '%s' [no context]", path)); + } else { + int fdx; + int dir = ul_path_get_dirfd(pc); + if (dir < 0) + return dir; + + if (*path == '/') + path++; + + fdx = fd = openat(dir, path, flags); + + if (fd < 0 && errno == ENOENT + && pc->redirect_on_enoent + && pc->redirect_on_enoent(pc, path, &dir) == 0) + fd = openat(dir, path, flags); + + DBG(CXT, ul_debugobj(pc, "opening '%s'%s", path, fdx != fd ? " [redirected]" : "")); + } + return fd; +} + +int ul_path_vopenf(struct path_cxt *pc, int flags, const char *path, va_list ap) +{ + const char *p = ul_path_mkpath(pc, path, ap); + + return !p ? -errno : ul_path_open(pc, flags, p); +} + +int ul_path_openf(struct path_cxt *pc, int flags, const char *path, ...) +{ + va_list ap; + int rc; + + va_start(ap, path); + rc = ul_path_vopenf(pc, flags, path, ap); + va_end(ap); + + return rc; +} + +/* + * Maybe stupid, but good enough ;-) + */ +static int mode2flags(const char *mode) +{ + int flags = 0; + const char *p; + + for (p = mode; p && *p; p++) { + if (*p == 'r' && *(p + 1) == '+') + flags |= O_RDWR; + else if (*p == 'r') + flags |= O_RDONLY; + + else if (*p == 'w' && *(p + 1) == '+') + flags |= O_RDWR | O_TRUNC; + else if (*p == 'w') + flags |= O_WRONLY | O_TRUNC; + + else if (*p == 'a' && *(p + 1) == '+') + flags |= O_RDWR | O_APPEND; + else if (*p == 'a') + flags |= O_WRONLY | O_APPEND; +#ifdef O_CLOEXEC + else if (*p == *UL_CLOEXECSTR) + flags |= O_CLOEXEC; +#endif + } + + return flags; +} + +FILE *ul_path_fopen(struct path_cxt *pc, const char *mode, const char *path) +{ + int flags = mode2flags(mode); + int fd = ul_path_open(pc, flags, path); + + if (fd < 0) + return NULL; + + return fdopen(fd, mode); +} + + +FILE *ul_path_vfopenf(struct path_cxt *pc, const char *mode, const char *path, va_list ap) +{ + const char *p = ul_path_mkpath(pc, path, ap); + + return !p ? NULL : ul_path_fopen(pc, mode, p); +} + +FILE *ul_path_fopenf(struct path_cxt *pc, const char *mode, const char *path, ...) +{ + FILE *f; + va_list ap; + + va_start(ap, path); + f = ul_path_vfopenf(pc, mode, path, ap); + va_end(ap); + + return f; +} + +/* + * Open directory @path in read-onl mode. If the path is NULL then duplicate FD + * to the directory addressed by @pc. + */ +DIR *ul_path_opendir(struct path_cxt *pc, const char *path) +{ + DIR *dir; + int fd = -1; + + if (path) + fd = ul_path_open(pc, O_RDONLY|O_CLOEXEC, path); + else if (pc->dir_path) { + int dirfd; + + DBG(CXT, ul_debugobj(pc, "duplicate dir path")); + dirfd = ul_path_get_dirfd(pc); + if (dirfd >= 0) + fd = dup_fd_cloexec(dirfd, STDERR_FILENO + 1); + } + + if (fd < 0) + return NULL; + + dir = fdopendir(fd); + if (!dir) { + close(fd); + return NULL; + } + if (!path) + rewinddir(dir); + return dir; +} + + +/* + * Open directory @path in read-onl mode. If the path is NULL then duplicate FD + * to the directory addressed by @pc. + */ +DIR *ul_path_vopendirf(struct path_cxt *pc, const char *path, va_list ap) +{ + const char *p = ul_path_mkpath(pc, path, ap); + + return !p ? NULL : ul_path_opendir(pc, p); +} + +/* + * Open directory @path in read-onl mode. If the path is NULL then duplicate FD + * to the directory addressed by @pc. + */ +DIR *ul_path_opendirf(struct path_cxt *pc, const char *path, ...) +{ + va_list ap; + DIR *dir; + + va_start(ap, path); + dir = ul_path_vopendirf(pc, path, ap); + va_end(ap); + + return dir; +} + +/* + * If @path is NULL then readlink is called on @pc directory. + */ +ssize_t ul_path_readlink(struct path_cxt *pc, char *buf, size_t bufsiz, const char *path) +{ + int dirfd; + + if (!path) { + const char *p = get_absdir(pc); + if (!p) + return -errno; + return readlink(p, buf, bufsiz); + } + + dirfd = ul_path_get_dirfd(pc); + if (dirfd < 0) + return dirfd; + + if (*path == '/') + path++; + + return readlinkat(dirfd, path, buf, bufsiz); +} + +/* + * If @path is NULL then readlink is called on @pc directory. + */ +ssize_t ul_path_readlinkf(struct path_cxt *pc, char *buf, size_t bufsiz, const char *path, ...) +{ + const char *p; + va_list ap; + + va_start(ap, path); + p = ul_path_mkpath(pc, path, ap); + va_end(ap); + + return !p ? -errno : ul_path_readlink(pc, buf, bufsiz, p); +} + +int ul_path_read(struct path_cxt *pc, char *buf, size_t len, const char *path) +{ + int rc, errsv; + int fd; + + fd = ul_path_open(pc, O_RDONLY|O_CLOEXEC, path); + if (fd < 0) + return -errno; + + DBG(CXT, ul_debug(" reading '%s'", path)); + rc = read_all(fd, buf, len); + + errsv = errno; + close(fd); + errno = errsv; + return rc; +} + +int ul_path_vreadf(struct path_cxt *pc, char *buf, size_t len, const char *path, va_list ap) +{ + const char *p = ul_path_mkpath(pc, path, ap); + + return !p ? -errno : ul_path_read(pc, buf, len, p); +} + +int ul_path_readf(struct path_cxt *pc, char *buf, size_t len, const char *path, ...) +{ + va_list ap; + int rc; + + va_start(ap, path); + rc = ul_path_vreadf(pc, buf, len, path, ap); + va_end(ap); + + return rc; +} + + +/* + * Returns newly allocated buffer with data from file. Maximal size is BUFSIZ + * (send patch if you need something bigger;-) + * + * Returns size of the string! + */ +int ul_path_read_string(struct path_cxt *pc, char **str, const char *path) +{ + char buf[BUFSIZ]; + int rc; + + if (!str) + return -EINVAL; + + *str = NULL; + rc = ul_path_read(pc, buf, sizeof(buf) - 1, path); + if (rc < 0) + return rc; + + /* Remove tailing newline (usual in sysfs) */ + if (rc > 0 && *(buf + rc - 1) == '\n') + --rc; + + buf[rc] = '\0'; + *str = strdup(buf); + if (!*str) + rc = -ENOMEM; + + return rc; +} + +int ul_path_readf_string(struct path_cxt *pc, char **str, const char *path, ...) +{ + const char *p; + va_list ap; + + va_start(ap, path); + p = ul_path_mkpath(pc, path, ap); + va_end(ap); + + return !p ? -errno : ul_path_read_string(pc, str, p); +} + +int ul_path_read_buffer(struct path_cxt *pc, char *buf, size_t bufsz, const char *path) +{ + int rc = ul_path_read(pc, buf, bufsz - 1, path); + if (rc < 0) + return rc; + + /* Remove tailing newline (usual in sysfs) */ + if (rc > 0 && *(buf + rc - 1) == '\n') + buf[--rc] = '\0'; + else + buf[rc - 1] = '\0'; + + return rc; +} + +int ul_path_readf_buffer(struct path_cxt *pc, char *buf, size_t bufsz, const char *path, ...) +{ + const char *p; + va_list ap; + + va_start(ap, path); + p = ul_path_mkpath(pc, path, ap); + va_end(ap); + + return !p ? -errno : ul_path_read_buffer(pc, buf, bufsz, p); +} + +int ul_path_scanf(struct path_cxt *pc, const char *path, const char *fmt, ...) +{ + FILE *f; + va_list fmt_ap; + int rc; + + f = ul_path_fopen(pc, "r" UL_CLOEXECSTR, path); + if (!f) + return -EINVAL; + + DBG(CXT, ul_debug(" fscanf [%s] '%s'", fmt, path)); + + va_start(fmt_ap, fmt); + rc = vfscanf(f, fmt, fmt_ap); + va_end(fmt_ap); + + fclose(f); + return rc; +} + +int ul_path_scanff(struct path_cxt *pc, const char *path, va_list ap, const char *fmt, ...) +{ + FILE *f; + va_list fmt_ap; + int rc; + + f = ul_path_vfopenf(pc, "r" UL_CLOEXECSTR, path, ap); + if (!f) + return -EINVAL; + + va_start(fmt_ap, fmt); + rc = vfscanf(f, fmt, fmt_ap); + va_end(fmt_ap); + + fclose(f); + return rc; +} + + +int ul_path_read_s64(struct path_cxt *pc, int64_t *res, const char *path) +{ + int64_t x = 0; + int rc; + + rc = ul_path_scanf(pc, path, "%"SCNd64, &x); + if (rc != 1) + return -1; + if (res) + *res = x; + return 0; +} + +int ul_path_readf_s64(struct path_cxt *pc, int64_t *res, const char *path, ...) +{ + const char *p; + va_list ap; + + va_start(ap, path); + p = ul_path_mkpath(pc, path, ap); + va_end(ap); + + return !p ? -errno : ul_path_read_s64(pc, res, p); +} + +int ul_path_read_u64(struct path_cxt *pc, uint64_t *res, const char *path) +{ + uint64_t x = 0; + int rc; + + rc = ul_path_scanf(pc, path, "%"SCNu64, &x); + if (rc != 1) + return -1; + if (res) + *res = x; + return 0; +} + +int ul_path_readf_u64(struct path_cxt *pc, uint64_t *res, const char *path, ...) +{ + const char *p; + va_list ap; + + va_start(ap, path); + p = ul_path_mkpath(pc, path, ap); + va_end(ap); + + return !p ? -errno : ul_path_read_u64(pc, res, p); +} + +int ul_path_read_s32(struct path_cxt *pc, int *res, const char *path) +{ + int rc, x = 0; + + rc = ul_path_scanf(pc, path, "%d", &x); + if (rc != 1) + return -1; + if (res) + *res = x; + return 0; +} + +int ul_path_readf_s32(struct path_cxt *pc, int *res, const char *path, ...) +{ + const char *p; + va_list ap; + + va_start(ap, path); + p = ul_path_mkpath(pc, path, ap); + va_end(ap); + + return !p ? -errno : ul_path_read_s32(pc, res, p); +} + +int ul_path_read_u32(struct path_cxt *pc, unsigned int *res, const char *path) +{ + int rc; + unsigned int x; + + rc = ul_path_scanf(pc, path, "%u", &x); + if (rc != 1) + return -1; + if (res) + *res = x; + return 0; +} + +int ul_path_readf_u32(struct path_cxt *pc, unsigned int *res, const char *path, ...) +{ + const char *p; + va_list ap; + + va_start(ap, path); + p = ul_path_mkpath(pc, path, ap); + va_end(ap); + + return !p ? -errno : ul_path_read_u32(pc, res, p); +} + +int ul_path_read_majmin(struct path_cxt *pc, dev_t *res, const char *path) +{ + int rc, maj, min; + + rc = ul_path_scanf(pc, path, "%d:%d", &maj, &min); + if (rc != 2) + return -1; + if (res) + *res = makedev(maj, min); + return 0; +} + +int ul_path_readf_majmin(struct path_cxt *pc, dev_t *res, const char *path, ...) +{ + const char *p; + va_list ap; + + va_start(ap, path); + p = ul_path_mkpath(pc, path, ap); + va_end(ap); + + return !p ? -errno : ul_path_read_majmin(pc, res, p); +} + +int ul_path_write_string(struct path_cxt *pc, const char *str, const char *path) +{ + int rc, errsv; + int fd; + + fd = ul_path_open(pc, O_WRONLY|O_CLOEXEC, path); + if (fd < 0) + return -errno; + + rc = write_all(fd, str, strlen(str)); + + errsv = errno; + close(fd); + errno = errsv; + return rc; +} + +int ul_path_writef_string(struct path_cxt *pc, const char *str, const char *path, ...) +{ + const char *p; + va_list ap; + + va_start(ap, path); + p = ul_path_mkpath(pc, path, ap); + va_end(ap); + + return !p ? -errno : ul_path_write_string(pc, str, p); +} + +int ul_path_write_s64(struct path_cxt *pc, int64_t num, const char *path) +{ + char buf[sizeof(stringify_value(LLONG_MAX))]; + int rc, errsv; + int fd, len; + + fd = ul_path_open(pc, O_WRONLY|O_CLOEXEC, path); + if (fd < 0) + return -errno; + + len = snprintf(buf, sizeof(buf), "%" PRId64, num); + if (len < 0 || (size_t) len >= sizeof(buf)) + rc = len < 0 ? -errno : -E2BIG; + else + rc = write_all(fd, buf, len); + + errsv = errno; + close(fd); + errno = errsv; + return rc; +} + +int ul_path_write_u64(struct path_cxt *pc, uint64_t num, const char *path) +{ + char buf[sizeof(stringify_value(ULLONG_MAX))]; + int rc, errsv; + int fd, len; + + fd = ul_path_open(pc, O_WRONLY|O_CLOEXEC, path); + if (fd < 0) + return -errno; + + len = snprintf(buf, sizeof(buf), "%" PRIu64, num); + if (len < 0 || (size_t) len >= sizeof(buf)) + rc = len < 0 ? -errno : -E2BIG; + else + rc = write_all(fd, buf, len); + + errsv = errno; + close(fd); + errno = errsv; + return rc; +} + +int ul_path_writef_u64(struct path_cxt *pc, uint64_t num, const char *path, ...) +{ + const char *p; + va_list ap; + + va_start(ap, path); + p = ul_path_mkpath(pc, path, ap); + va_end(ap); + + return !p ? -errno : ul_path_write_u64(pc, num, p); + +} + +int ul_path_count_dirents(struct path_cxt *pc, const char *path) +{ + DIR *dir; + int r = 0; + + dir = ul_path_opendir(pc, path); + if (!dir) + return 0; + + while (xreaddir(dir)) r++; + + closedir(dir); + return r; +} + +int ul_path_countf_dirents(struct path_cxt *pc, const char *path, ...) +{ + const char *p; + va_list ap; + + va_start(ap, path); + p = ul_path_mkpath(pc, path, ap); + va_end(ap); + + return !p ? -errno : ul_path_count_dirents(pc, p); +} + +/* + * Like fopen() but, @path is always prefixed by @prefix. This function is + * useful in case when ul_path_* API is overkill. + */ +FILE *ul_prefix_fopen(const char *prefix, const char *path, const char *mode) +{ + char buf[PATH_MAX]; + + if (!path) + return NULL; + if (!prefix) + return fopen(path, mode); + if (*path == '/') + path++; + + snprintf(buf, sizeof(buf), "%s/%s", prefix, path); + return fopen(buf, mode); +} + +#ifdef HAVE_CPU_SET_T +static int ul_path_cpuparse(struct path_cxt *pc, cpu_set_t **set, int maxcpus, int islist, const char *path, va_list ap) +{ + FILE *f; + size_t setsize, len = maxcpus * 7; + char buf[len]; + int rc; + + *set = NULL; + + f = ul_path_vfopenf(pc, "r" UL_CLOEXECSTR, path, ap); + if (!f) + return -errno; + + rc = fgets(buf, len, f) == NULL ? -errno : 0; + fclose(f); + + if (rc) + return rc; + + len = strlen(buf); + if (buf[len - 1] == '\n') + buf[len - 1] = '\0'; + + *set = cpuset_alloc(maxcpus, &setsize, NULL); + if (!*set) + return -ENOMEM; + + if (islist) { + if (cpulist_parse(buf, *set, setsize, 0)) { + cpuset_free(*set); + return -EINVAL; + } + } else { + if (cpumask_parse(buf, *set, setsize)) { + cpuset_free(*set); + return -EINVAL; + } + } + return 0; +} + +int ul_path_readf_cpuset(struct path_cxt *pc, cpu_set_t **set, int maxcpus, const char *path, ...) +{ + va_list ap; + int rc = 0; + + va_start(ap, path); + rc = ul_path_cpuparse(pc, set, maxcpus, 0, path, ap); + va_end(ap); + + return rc; +} + +int ul_path_readf_cpulist(struct path_cxt *pc, cpu_set_t **set, int maxcpus, const char *path, ...) +{ + va_list ap; + int rc = 0; + + va_start(ap, path); + rc = ul_path_cpuparse(pc, set, maxcpus, 1, path, ap); + va_end(ap); + + return rc; +} + +#endif /* HAVE_CPU_SET_T */ + + +#ifdef TEST_PROGRAM_PATH +#include + +static void __attribute__((__noreturn__)) usage(void) +{ + fprintf(stdout, " %s [options] \n\n", program_invocation_short_name); + fputs(" -p, --prefix redirect hardcoded paths to \n", stdout); + + fputs(" Commands:\n", stdout); + fputs(" read-u64 read uint64_t from file\n", stdout); + fputs(" read-s64 read int64_t from file\n", stdout); + fputs(" read-u32 read uint32_t from file\n", stdout); + fputs(" read-s32 read int32_t from file\n", stdout); + fputs(" read-string read string from file\n", stdout); + fputs(" read-majmin read devno from file\n", stdout); + fputs(" read-link read symlink\n", stdout); + fputs(" write-string write string from file\n", stdout); + fputs(" write-u64 write uint64_t from file\n", stdout); + + exit(EXIT_SUCCESS); +} + +int main(int argc, char *argv[]) +{ + int c; + const char *prefix = NULL, *dir, *file, *command; + struct path_cxt *pc = NULL; + + static const struct option longopts[] = { + { "prefix", 1, NULL, 'p' }, + { "help", 0, NULL, 'h' }, + { NULL, 0, NULL, 0 }, + }; + + while((c = getopt_long(argc, argv, "p:h", longopts, NULL)) != -1) { + switch(c) { + case 'p': + prefix = optarg; + break; + case 'h': + usage(); + break; + default: + err(EXIT_FAILURE, "try --help"); + } + } + + if (optind == argc) + errx(EXIT_FAILURE, " not defined"); + dir = argv[optind++]; + + ul_path_init_debug(); + + pc = ul_new_path(dir); + if (!pc) + err(EXIT_FAILURE, "failed to initialize path context"); + if (prefix) + ul_path_set_prefix(pc, prefix); + + if (optind == argc) + errx(EXIT_FAILURE, " not defined"); + command = argv[optind++]; + + if (strcmp(command, "read-u32") == 0) { + uint32_t res; + + if (optind == argc) + errx(EXIT_FAILURE, " not defined"); + file = argv[optind++]; + + if (ul_path_read_u32(pc, &res, file) != 0) + err(EXIT_FAILURE, "read u64 failed"); + printf("read: %s: %u\n", file, res); + + if (ul_path_readf_u32(pc, &res, "%s", file) != 0) + err(EXIT_FAILURE, "readf u64 failed"); + printf("readf: %s: %u\n", file, res); + + } else if (strcmp(command, "read-s32") == 0) { + int32_t res; + + if (optind == argc) + errx(EXIT_FAILURE, " not defined"); + file = argv[optind++]; + + if (ul_path_read_s32(pc, &res, file) != 0) + err(EXIT_FAILURE, "read u64 failed"); + printf("read: %s: %d\n", file, res); + + if (ul_path_readf_s32(pc, &res, "%s", file) != 0) + err(EXIT_FAILURE, "readf u64 failed"); + printf("readf: %s: %d\n", file, res); + + } else if (strcmp(command, "read-u64") == 0) { + uint64_t res; + + if (optind == argc) + errx(EXIT_FAILURE, " not defined"); + file = argv[optind++]; + + if (ul_path_read_u64(pc, &res, file) != 0) + err(EXIT_FAILURE, "read u64 failed"); + printf("read: %s: %" PRIu64 "\n", file, res); + + if (ul_path_readf_u64(pc, &res, "%s", file) != 0) + err(EXIT_FAILURE, "readf u64 failed"); + printf("readf: %s: %" PRIu64 "\n", file, res); + + } else if (strcmp(command, "read-s64") == 0) { + int64_t res; + + if (optind == argc) + errx(EXIT_FAILURE, " not defined"); + file = argv[optind++]; + + if (ul_path_read_s64(pc, &res, file) != 0) + err(EXIT_FAILURE, "read u64 failed"); + printf("read: %s: %" PRIu64 "\n", file, res); + + if (ul_path_readf_s64(pc, &res, "%s", file) != 0) + err(EXIT_FAILURE, "readf u64 failed"); + printf("readf: %s: %" PRIu64 "\n", file, res); + + } else if (strcmp(command, "read-majmin") == 0) { + dev_t res; + + if (optind == argc) + errx(EXIT_FAILURE, " not defined"); + file = argv[optind++]; + + if (ul_path_read_majmin(pc, &res, file) != 0) + err(EXIT_FAILURE, "read maj:min failed"); + printf("read: %s: %d\n", file, (int) res); + + if (ul_path_readf_majmin(pc, &res, "%s", file) != 0) + err(EXIT_FAILURE, "readf maj:min failed"); + printf("readf: %s: %d\n", file, (int) res); + + } else if (strcmp(command, "read-string") == 0) { + char *res; + + if (optind == argc) + errx(EXIT_FAILURE, " not defined"); + file = argv[optind++]; + + if (ul_path_read_string(pc, &res, file) < 0) + err(EXIT_FAILURE, "read string failed"); + printf("read: %s: %s\n", file, res); + + if (ul_path_readf_string(pc, &res, "%s", file) < 0) + err(EXIT_FAILURE, "readf string failed"); + printf("readf: %s: %s\n", file, res); + + } else if (strcmp(command, "read-link") == 0) { + char res[PATH_MAX]; + + if (optind == argc) + errx(EXIT_FAILURE, " not defined"); + file = argv[optind++]; + + if (ul_path_readlink(pc, res, sizeof(res), file) < 0) + err(EXIT_FAILURE, "read symlink failed"); + printf("read: %s: %s\n", file, res); + + if (ul_path_readlinkf(pc, res, sizeof(res), "%s", file) < 0) + err(EXIT_FAILURE, "readf symlink failed"); + printf("readf: %s: %s\n", file, res); + + } else if (strcmp(command, "write-string") == 0) { + char *str; + + if (optind + 1 == argc) + errx(EXIT_FAILURE, " not defined"); + file = argv[optind++]; + str = argv[optind++]; + + if (ul_path_write_string(pc, str, file) != 0) + err(EXIT_FAILURE, "write string failed"); + if (ul_path_writef_string(pc, str, "%s", file) != 0) + err(EXIT_FAILURE, "writef string failed"); + + } else if (strcmp(command, "write-u64") == 0) { + uint64_t num; + + if (optind + 1 == argc) + errx(EXIT_FAILURE, " not defined"); + file = argv[optind++]; + num = strtoumax(argv[optind++], NULL, 0); + + if (ul_path_write_u64(pc, num, file) != 0) + err(EXIT_FAILURE, "write u64 failed"); + if (ul_path_writef_u64(pc, num, "%s", file) != 0) + err(EXIT_FAILURE, "writef u64 failed"); + } + + ul_unref_path(pc); + return EXIT_SUCCESS; +} +#endif /* TEST_PROGRAM_PATH */ + diff --git a/utils/lib/plymouth-ctrl.c b/utils/lib/plymouth-ctrl.c new file mode 100644 index 0000000..2d3deda --- /dev/null +++ b/utils/lib/plymouth-ctrl.c @@ -0,0 +1,144 @@ +/* + * plymouth-ctrl.c Simply communications with plymouthd + * to avoid forked sub processes and/or + * missed plymouth send commands tool + * due a plymouthd replacement. + * + * Copyright (c) 2016 SUSE Linux GmbH, All rights reserved. + * Copyright (c) 2016 Werner Fink + * + * 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, 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 (see the file COPYING); if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, + * MA 02110-1301, USA. + * + * Author: Werner Fink + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "all-io.h" +#include "c.h" +#include "nls.h" +#include "plymouth-ctrl.h" + +static int can_read(int fd, const long timeout) +{ + struct pollfd fds = { + .fd = fd, + .events = POLLIN|POLLPRI, + .revents = 0, + }; + int ret; + + do { + ret = poll(&fds, 1, timeout); + } while ((ret < 0) && (errno == EINTR)); + + return (ret == 1) && (fds.revents & (POLLIN|POLLPRI)); +} + +static int open_un_socket_and_connect(void) +{ + /* The abstract UNIX socket of plymouth */ + struct sockaddr_un su = { + .sun_family = AF_UNIX, + .sun_path = PLYMOUTH_SOCKET_PATH, + }; + const int one = 1; + int fd, ret; + + fd = socket(PF_UNIX, SOCK_STREAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0); + if (fd < 0) { + warnx(_("cannot open UNIX socket")); + goto err; + } + + ret = setsockopt(fd, SOL_SOCKET, SO_PASSCRED, &one, sizeof(one)); + if (ret < 0) { + warnx(_("cannot set option for UNIX socket")); + close(fd); + fd = -1; + goto err; + } + + /* Note, the abstract PLYMOUTH_SOCKET_PATH has a leading NULL byte */ + ret = connect(fd, (struct sockaddr *) &su, + offsetof(struct sockaddr_un, sun_path) + 1 + strlen(su.sun_path+1)); + if (ret < 0) { + if (errno != ECONNREFUSED) + warnx(_("cannot connect on UNIX socket")); + close(fd); + fd = -1; + goto err; + } +err: + return fd; +} + +int plymouth_command(int cmd, ...) +{ + uint8_t answer[2], command[2]; + struct sigaction sp, op; + int fdsock = -1, ret = 0; + + sigemptyset (&sp.sa_mask); + sp.sa_handler = SIG_IGN; + sp.sa_flags = SA_RESTART; + sigaction(SIGPIPE, &sp, &op); + + /* The plymouthd does read at least two bytes. */ + command[1] = '\0'; + switch (cmd) { + case MAGIC_PING: + fdsock = open_un_socket_and_connect(); + if (fdsock >= 0) { + command[0] = cmd; + write_all(fdsock, command, sizeof(command)); + } + break; + case MAGIC_QUIT: + fdsock = open_un_socket_and_connect(); + if (fdsock >= 0) { + command[0] = cmd; + write_all(fdsock, command, sizeof(command)); + } + break; + default: + warnx(_("the plymouth request %c is not implemented"), cmd); + case '?': + goto err; + } + + answer[0] = '\0'; + if (fdsock >= 0) { + if (can_read(fdsock, 1000)) + read_all(fdsock, (char *) &answer[0], sizeof(answer)); + close(fdsock); + } + sigaction(SIGPIPE, &op, NULL); + ret = (answer[0] == ANSWER_ACK) ? 1 : 0; +err: + return ret; +} + diff --git a/utils/lib/procutils.c b/utils/lib/procutils.c new file mode 100644 index 0000000..8fb5d5c --- /dev/null +++ b/utils/lib/procutils.c @@ -0,0 +1,308 @@ +/* + * Copyright (C) 2011 Davidlohr Bueso + * + * procutils.c: General purpose procfs parsing utilities + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Library Public License as published by + * the Free Software Foundation; either version 2, 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 Library Public License for more details. + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "procutils.h" +#include "fileutils.h" +#include "all-io.h" +#include "c.h" + +/* + * @pid: process ID for which we want to obtain the threads group + * + * Returns: newly allocated tasks structure + */ +struct proc_tasks *proc_open_tasks(pid_t pid) +{ + struct proc_tasks *tasks; + char path[PATH_MAX]; + + sprintf(path, "/proc/%d/task/", pid); + + tasks = malloc(sizeof(struct proc_tasks)); + if (tasks) { + tasks->dir = opendir(path); + if (tasks->dir) + return tasks; + } + + free(tasks); + return NULL; +} + +/* + * @tasks: allocated tasks structure + * + * Returns: nothing + */ +void proc_close_tasks(struct proc_tasks *tasks) +{ + if (tasks && tasks->dir) + closedir(tasks->dir); + free(tasks); +} + +/* + * @tasks: allocated task structure + * @tid: [output] one of the thread IDs belonging to the thread group + * If when an error occurs, it is set to 0. + * + * Returns: 0 on success, 1 on end, -1 on failure or no more threads + */ +int proc_next_tid(struct proc_tasks *tasks, pid_t *tid) +{ + struct dirent *d; + char *end; + + if (!tasks || !tid) + return -EINVAL; + + *tid = 0; + errno = 0; + + do { + d = readdir(tasks->dir); + if (!d) + return errno ? -1 : 1; /* error or end-of-dir */ + + if (!isdigit((unsigned char) *d->d_name)) + continue; + errno = 0; + *tid = (pid_t) strtol(d->d_name, &end, 10); + if (errno || d->d_name == end || (end && *end)) + return -1; + + } while (!*tid); + + return 0; +} + +/* returns process command path, use free() for result */ +static char *proc_file_strdup(pid_t pid, const char *name) +{ + char buf[BUFSIZ], *res = NULL; + ssize_t sz = 0; + size_t i; + int fd; + + snprintf(buf, sizeof(buf), "/proc/%d/%s", (int) pid, name); + fd = open(buf, O_RDONLY); + if (fd < 0) + goto done; + + sz = read_all(fd, buf, sizeof(buf)); + if (sz <= 0) + goto done; + + for (i = 0; i < (size_t) sz; i++) { + + if (buf[i] == '\0') + buf[i] = ' '; + } + buf[sz - 1] = '\0'; + res = strdup(buf); +done: + if (fd >= 0) + close(fd); + return res; +} + +/* returns process command path, use free() for result */ +char *proc_get_command(pid_t pid) +{ + return proc_file_strdup(pid, "cmdline"); +} + +/* returns process command name, use free() for result */ +char *proc_get_command_name(pid_t pid) +{ + return proc_file_strdup(pid, "comm"); +} + +struct proc_processes *proc_open_processes(void) +{ + struct proc_processes *ps; + + ps = calloc(1, sizeof(struct proc_processes)); + if (ps) { + ps->dir = opendir("/proc"); + if (ps->dir) + return ps; + } + + free(ps); + return NULL; +} + +void proc_close_processes(struct proc_processes *ps) +{ + if (ps && ps->dir) + closedir(ps->dir); + free(ps); +} + +void proc_processes_filter_by_name(struct proc_processes *ps, const char *name) +{ + ps->fltr_name = name; + ps->has_fltr_name = name ? 1 : 0; +} + +void proc_processes_filter_by_uid(struct proc_processes *ps, uid_t uid) +{ + ps->fltr_uid = uid; + ps->has_fltr_uid = 1; +} + +int proc_next_pid(struct proc_processes *ps, pid_t *pid) +{ + struct dirent *d; + + if (!ps || !pid) + return -EINVAL; + + *pid = 0; + errno = 0; + + do { + char buf[BUFSIZ], *p; + + errno = 0; + d = readdir(ps->dir); + if (!d) + return errno ? -1 : 1; /* error or end-of-dir */ + + + if (!isdigit((unsigned char) *d->d_name)) + continue; + + /* filter out by UID */ + if (ps->has_fltr_uid) { + struct stat st; + + if (fstatat(dirfd(ps->dir), d->d_name, &st, 0)) + continue; + if (ps->fltr_uid != st.st_uid) + continue; + } + + /* filter out by NAME */ + if (ps->has_fltr_name) { + char procname[256]; + FILE *f; + + snprintf(buf, sizeof(buf), "%s/stat", d->d_name); + f = fopen_at(dirfd(ps->dir), buf, O_CLOEXEC|O_RDONLY, "r"); + if (!f) + continue; + + p = fgets(buf, sizeof(buf), f); + fclose(f); + if (!p) + continue; + + if (sscanf(buf, "%*d (%255[^)])", procname) != 1) + continue; + + /* ok, we got the process name. */ + if (strcmp(procname, ps->fltr_name) != 0) + continue; + } + + p = NULL; + errno = 0; + *pid = (pid_t) strtol(d->d_name, &p, 10); + if (errno || d->d_name == p || (p && *p)) + return errno ? -errno : -1; + + return 0; + } while (1); + + return 0; +} + +#ifdef TEST_PROGRAM_PROCUTILS + +static int test_tasks(int argc, char *argv[]) +{ + pid_t tid, pid; + struct proc_tasks *ts; + + if (argc != 2) + return EXIT_FAILURE; + + pid = strtol(argv[1], (char **) NULL, 10); + printf("PID=%d, TIDs:", pid); + + ts = proc_open_tasks(pid); + if (!ts) + err(EXIT_FAILURE, "open list of tasks failed"); + + while (proc_next_tid(ts, &tid) == 0) + printf(" %d", tid); + + printf("\n"); + proc_close_tasks(ts); + return EXIT_SUCCESS; +} + +static int test_processes(int argc, char *argv[]) +{ + pid_t pid; + struct proc_processes *ps; + + ps = proc_open_processes(); + if (!ps) + err(EXIT_FAILURE, "open list of processes failed"); + + if (argc >= 3 && strcmp(argv[1], "--name") == 0) + proc_processes_filter_by_name(ps, argv[2]); + + if (argc >= 3 && strcmp(argv[1], "--uid") == 0) + proc_processes_filter_by_uid(ps, (uid_t) atol(argv[2])); + + while (proc_next_pid(ps, &pid) == 0) + printf(" %d", pid); + + printf("\n"); + proc_close_processes(ps); + return EXIT_SUCCESS; +} + +int main(int argc, char *argv[]) +{ + if (argc < 2) { + fprintf(stderr, "usage: %1$s --tasks \n" + " %1$s --processes [---name ] [--uid ]\n", + program_invocation_short_name); + return EXIT_FAILURE; + } + + if (strcmp(argv[1], "--tasks") == 0) + return test_tasks(argc - 1, argv + 1); + if (strcmp(argv[1], "--processes") == 0) + return test_processes(argc - 1, argv + 1); + + return EXIT_FAILURE; +} +#endif /* TEST_PROGRAM_PROCUTILS */ diff --git a/utils/lib/pty-session.c b/utils/lib/pty-session.c new file mode 100644 index 0000000..06b2a49 --- /dev/null +++ b/utils/lib/pty-session.c @@ -0,0 +1,725 @@ +/* + * This is pseudo-terminal container for child process where parent creates a + * proxy between the current std{in,out,etrr} and the child's pty. Advantages: + * + * - child has no access to parent's terminal (e.g. su --pty) + * - parent can log all traffic between user and child's terminal (e.g. script(1)) + * - it's possible to start commands on terminal although parent has no terminal + * + * This code is in the public domain; do with it what you wish. + * + * Written by Karel Zak in Jul 2019 + */ +#include +#include +#include +#include +#include +#include +#include +#include + +#include "c.h" +#include "all-io.h" +#include "ttyutils.h" +#include "pty-session.h" +#include "monotonic.h" +#include "debug.h" + +static UL_DEBUG_DEFINE_MASK(ulpty); +UL_DEBUG_DEFINE_MASKNAMES(ulpty) = UL_DEBUG_EMPTY_MASKNAMES; + +#define ULPTY_DEBUG_INIT (1 << 1) +#define ULPTY_DEBUG_SETUP (1 << 2) +#define ULPTY_DEBUG_SIG (1 << 3) +#define ULPTY_DEBUG_IO (1 << 4) +#define ULPTY_DEBUG_DONE (1 << 5) +#define ULPTY_DEBUG_ALL 0xFFFF + +#define DBG(m, x) __UL_DBG(ulpty, ULPTY_DEBUG_, m, x) +#define ON_DBG(m, x) __UL_DBG_CALL(ulpty, ULPTY_DEBUG_, m, x) + +#define UL_DEBUG_CURRENT_MASK UL_DEBUG_MASK(ulpty) +#include "debugobj.h" + +void ul_pty_init_debug(int mask) +{ + if (ulpty_debug_mask) + return; + __UL_INIT_DEBUG_FROM_ENV(ulpty, ULPTY_DEBUG_, mask, ULPTY_DEBUG); +} + +struct ul_pty *ul_new_pty(int is_stdin_tty) +{ + struct ul_pty *pty = calloc(1, sizeof(*pty)); + + if (!pty) + return NULL; + + DBG(SETUP, ul_debugobj(pty, "alloc handler")); + pty->isterm = is_stdin_tty; + pty->master = -1; + pty->slave = -1; + pty->sigfd = -1; + pty->child = (pid_t) -1; + + return pty; +} + +void ul_free_pty(struct ul_pty *pty) +{ + free(pty); +} + +void ul_pty_slave_echo(struct ul_pty *pty, int enable) +{ + assert(pty); + pty->slave_echo = enable ? 1 : 0; +} + +int ul_pty_get_delivered_signal(struct ul_pty *pty) +{ + assert(pty); + return pty->delivered_signal; +} + +struct ul_pty_callbacks *ul_pty_get_callbacks(struct ul_pty *pty) +{ + assert(pty); + return &pty->callbacks; +} + +void ul_pty_set_callback_data(struct ul_pty *pty, void *data) +{ + assert(pty); + pty->callback_data = data; +} + +void ul_pty_set_child(struct ul_pty *pty, pid_t child) +{ + assert(pty); + pty->child = child; +} + +int ul_pty_get_childfd(struct ul_pty *pty) +{ + assert(pty); + return pty->master; +} + +pid_t ul_pty_get_child(struct ul_pty *pty) +{ + assert(pty); + return pty->child; +} + +/* it's active when signals are redirected to sigfd */ +int ul_pty_is_running(struct ul_pty *pty) +{ + assert(pty); + return pty->sigfd >= 0; +} + +void ul_pty_set_mainloop_time(struct ul_pty *pty, struct timeval *tv) +{ + assert(pty); + if (!tv) { + DBG(IO, ul_debugobj(pty, "mainloop time: clear")); + timerclear(&pty->next_callback_time); + } else { + pty->next_callback_time.tv_sec = tv->tv_sec; + pty->next_callback_time.tv_usec = tv->tv_usec; + DBG(IO, ul_debugobj(pty, "mainloop time: %ld.%06ld", tv->tv_sec, tv->tv_usec)); + } +} + +static void pty_signals_cleanup(struct ul_pty *pty) +{ + if (pty->sigfd != -1) + close(pty->sigfd); + pty->sigfd = -1; + + /* restore original setting */ + sigprocmask(SIG_SETMASK, &pty->orgsig, NULL); +} + +/* call me before fork() */ +int ul_pty_setup(struct ul_pty *pty) +{ + struct termios attrs; + sigset_t ourset; + int rc = 0; + + assert(pty->sigfd == -1); + + /* save the current signals setting */ + sigprocmask(0, NULL, &pty->orgsig); + + if (pty->isterm) { + DBG(SETUP, ul_debugobj(pty, "create for terminal")); + + /* original setting of the current terminal */ + if (tcgetattr(STDIN_FILENO, &pty->stdin_attrs) != 0) { + rc = -errno; + goto done; + } + + attrs = pty->stdin_attrs; + if (pty->slave_echo) + attrs.c_lflag |= ECHO; + else + attrs.c_lflag &= ~ECHO; + + ioctl(STDIN_FILENO, TIOCGWINSZ, (char *)&pty->win); + /* create master+slave */ + rc = openpty(&pty->master, &pty->slave, NULL, &attrs, &pty->win); + if (rc) + goto done; + + /* set the current terminal to raw mode; pty_cleanup() reverses this change on exit */ + cfmakeraw(&attrs); + tcsetattr(STDIN_FILENO, TCSANOW, &attrs); + } else { + DBG(SETUP, ul_debugobj(pty, "create for non-terminal")); + + rc = openpty(&pty->master, &pty->slave, NULL, NULL, NULL); + if (rc) + goto done; + + tcgetattr(pty->slave, &attrs); + + if (pty->slave_echo) + attrs.c_lflag |= ECHO; + else + attrs.c_lflag &= ~ECHO; + + tcsetattr(pty->slave, TCSANOW, &attrs); + } + + sigfillset(&ourset); + if (sigprocmask(SIG_BLOCK, &ourset, NULL)) { + rc = -errno; + goto done; + } + + sigemptyset(&ourset); + sigaddset(&ourset, SIGCHLD); + sigaddset(&ourset, SIGWINCH); + sigaddset(&ourset, SIGALRM); + sigaddset(&ourset, SIGTERM); + sigaddset(&ourset, SIGINT); + sigaddset(&ourset, SIGQUIT); + + if (pty->callbacks.flush_logs) + sigaddset(&ourset, SIGUSR1); + + if ((pty->sigfd = signalfd(-1, &ourset, SFD_CLOEXEC)) < 0) + rc = -errno; +done: + if (rc) + ul_pty_cleanup(pty); + + DBG(SETUP, ul_debugobj(pty, "pty setup done [master=%d, slave=%d, rc=%d]", + pty->master, pty->slave, rc)); + return rc; +} + +/* cleanup in parent process */ +void ul_pty_cleanup(struct ul_pty *pty) +{ + struct termios rtt; + + pty_signals_cleanup(pty); + + if (pty->master == -1 || !pty->isterm) + return; + + DBG(DONE, ul_debugobj(pty, "cleanup")); + rtt = pty->stdin_attrs; + tcsetattr(STDIN_FILENO, TCSADRAIN, &rtt); +} + +/* call me in child process */ +void ul_pty_init_slave(struct ul_pty *pty) +{ + DBG(SETUP, ul_debugobj(pty, "initialize slave")); + + setsid(); + + ioctl(pty->slave, TIOCSCTTY, 1); + close(pty->master); + + dup2(pty->slave, STDIN_FILENO); + dup2(pty->slave, STDOUT_FILENO); + dup2(pty->slave, STDERR_FILENO); + + close(pty->slave); + + if (pty->sigfd >= 0) + close(pty->sigfd); + + pty->slave = -1; + pty->master = -1; + pty->sigfd = -1; + + sigprocmask(SIG_SETMASK, &pty->orgsig, NULL); + + DBG(SETUP, ul_debugobj(pty, "... initialize slave done")); +} + +static int write_output(char *obuf, ssize_t bytes) +{ + DBG(IO, ul_debug(" writing output")); + + if (write_all(STDOUT_FILENO, obuf, bytes)) { + DBG(IO, ul_debug(" writing output *failed*")); + return -errno; + } + + return 0; +} + +static int write_to_child(struct ul_pty *pty, char *buf, size_t bufsz) +{ + return write_all(pty->master, buf, bufsz); +} + +/* + * The pty is usually faster than shell, so it's a good idea to wait until + * the previous message has been already read by shell from slave before we + * write to master. This is necessary especially for EOF situation when we can + * send EOF to master before shell is fully initialized, to workaround this + * problem we wait until slave is empty. For example: + * + * echo "date" | su --pty + * + * Unfortunately, the child (usually shell) can ignore stdin at all, so we + * don't wait forever to avoid dead locks... + * + * Note that su --pty is primarily designed for interactive sessions as it + * maintains master+slave tty stuff within the session. Use pipe to write to + * pty and assume non-interactive (tee-like) behavior is NOT well supported. + */ +void ul_pty_write_eof_to_child(struct ul_pty *pty) +{ + unsigned int tries = 0; + struct pollfd fds[] = { + { .fd = pty->slave, .events = POLLIN } + }; + char c = DEF_EOF; + + DBG(IO, ul_debugobj(pty, " waiting for empty slave")); + while (poll(fds, 1, 10) == 1 && tries < 8) { + DBG(IO, ul_debugobj(pty, " slave is not empty")); + xusleep(250000); + tries++; + } + if (tries < 8) + DBG(IO, ul_debugobj(pty, " slave is empty now")); + + DBG(IO, ul_debugobj(pty, " sending EOF to master")); + write_to_child(pty, &c, sizeof(char)); +} + +static int mainloop_callback(struct ul_pty *pty) +{ + int rc; + + if (!pty->callbacks.mainloop) + return 0; + + DBG(IO, ul_debugobj(pty, "calling mainloop callback")); + rc = pty->callbacks.mainloop(pty->callback_data); + + DBG(IO, ul_debugobj(pty, " callback done [rc=%d]", rc)); + return rc; +} + +static int handle_io(struct ul_pty *pty, int fd, int *eof) +{ + char buf[BUFSIZ]; + ssize_t bytes; + int rc = 0; + + DBG(IO, ul_debugobj(pty, " handle I/O on fd=%d", fd)); + *eof = 0; + + /* read from active FD */ + bytes = read(fd, buf, sizeof(buf)); + if (bytes < 0) { + if (errno == EAGAIN || errno == EINTR) + return 0; + return -errno; + } + + if (bytes == 0) { + *eof = 1; + return 0; + } + + /* from stdin (user) to command */ + if (fd == STDIN_FILENO) { + DBG(IO, ul_debugobj(pty, " stdin --> master %zd bytes", bytes)); + + if (write_to_child(pty, buf, bytes)) + return -errno; + + /* without sync write_output() will write both input & + * shell output that looks like double echoing */ + fdatasync(pty->master); + + /* from command (master) to stdout */ + } else if (fd == pty->master) { + DBG(IO, ul_debugobj(pty, " master --> stdout %zd bytes", bytes)); + write_output(buf, bytes); + } + + if (pty->callbacks.log_stream_activity) + rc = pty->callbacks.log_stream_activity( + pty->callback_data, fd, buf, bytes); + + return rc; +} + +void ul_pty_wait_for_child(struct ul_pty *pty) +{ + int status; + pid_t pid; + int options = 0; + + if (pty->child == (pid_t) -1) + return; + + DBG(SIG, ul_debug("waiting for child [child=%d]", (int) pty->child)); + + if (ul_pty_is_running(pty)) { + /* wait for specific child */ + options = WNOHANG; + for (;;) { + pid = waitpid(pty->child, &status, options); + DBG(SIG, ul_debug(" waitpid done [rc=%d]", (int) pid)); + if (pid != (pid_t) - 1) { + if (pty->callbacks.child_die) + pty->callbacks.child_die( + pty->callback_data, + pty->child, status); + ul_pty_set_child(pty, (pid_t) -1); + } else + break; + } + } else { + /* final wait */ + while ((pid = wait3(&status, options, NULL)) > 0) { + DBG(SIG, ul_debug(" wait3 done [rc=%d]", (int) pid)); + if (pid == pty->child) { + if (pty->callbacks.child_die) + pty->callbacks.child_die( + pty->callback_data, + pty->child, status); + ul_pty_set_child(pty, (pid_t) -1); + } + } + } +} + +static int handle_signal(struct ul_pty *pty, int fd) +{ + struct signalfd_siginfo info; + ssize_t bytes; + int rc = 0; + + DBG(SIG, ul_debugobj(pty, " handle signal on fd=%d", fd)); + + bytes = read(fd, &info, sizeof(info)); + if (bytes != sizeof(info)) { + if (bytes < 0 && (errno == EAGAIN || errno == EINTR)) + return 0; + return -errno; + } + + switch (info.ssi_signo) { + case SIGCHLD: + DBG(SIG, ul_debugobj(pty, " get signal SIGCHLD")); + + if (info.ssi_code == CLD_EXITED + || info.ssi_code == CLD_KILLED + || info.ssi_code == CLD_DUMPED) { + + if (pty->callbacks.child_wait) + pty->callbacks.child_wait(pty->callback_data, + pty->child); + else + ul_pty_wait_for_child(pty); + + } else if (info.ssi_status == SIGSTOP && pty->child > 0) + pty->callbacks.child_sigstop(pty->callback_data, + pty->child); + + if (pty->child <= 0) { + DBG(SIG, ul_debugobj(pty, " no child, setting leaving timeout")); + pty->poll_timeout = 10; + timerclear(&pty->next_callback_time); + } + return 0; + case SIGWINCH: + DBG(SIG, ul_debugobj(pty, " get signal SIGWINCH")); + if (pty->isterm) { + ioctl(STDIN_FILENO, TIOCGWINSZ, (char *)&pty->win); + ioctl(pty->slave, TIOCSWINSZ, (char *)&pty->win); + + if (pty->callbacks.log_signal) + rc = pty->callbacks.log_signal(pty->callback_data, + &info, (void *) &pty->win); + } + break; + case SIGTERM: + /* fallthrough */ + case SIGINT: + /* fallthrough */ + case SIGQUIT: + DBG(SIG, ul_debugobj(pty, " get signal SIG{TERM,INT,QUIT}")); + pty->delivered_signal = info.ssi_signo; + /* Child termination is going to generate SIGCHLD (see above) */ + if (pty->child > 0) + kill(pty->child, SIGTERM); + + if (pty->callbacks.log_signal) + rc = pty->callbacks.log_signal(pty->callback_data, + &info, (void *) &pty->win); + break; + case SIGUSR1: + DBG(SIG, ul_debugobj(pty, " get signal SIGUSR1")); + if (pty->callbacks.flush_logs) + rc = pty->callbacks.flush_logs(pty->callback_data); + break; + default: + abort(); + } + + return rc; +} + +/* loop in parent */ +int ul_pty_proxy_master(struct ul_pty *pty) +{ + int rc = 0, ret, eof = 0; + enum { + POLLFD_SIGNAL = 0, + POLLFD_MASTER, + POLLFD_STDIN + + }; + struct pollfd pfd[] = { + [POLLFD_SIGNAL] = { .fd = -1, .events = POLLIN | POLLERR | POLLHUP }, + [POLLFD_MASTER] = { .fd = pty->master, .events = POLLIN | POLLERR | POLLHUP }, + [POLLFD_STDIN] = { .fd = STDIN_FILENO, .events = POLLIN | POLLERR | POLLHUP } + }; + + /* We use signalfd, and standard signals by handlers are completely blocked */ + assert(pty->sigfd >= 0); + + pfd[POLLFD_SIGNAL].fd = pty->sigfd; + pty->poll_timeout = -1; + + while (!pty->delivered_signal) { + size_t i; + int errsv, timeout; + + DBG(IO, ul_debugobj(pty, "--poll() loop--")); + + /* note, callback usually updates @next_callback_time */ + if (timerisset(&pty->next_callback_time)) { + struct timeval now; + + DBG(IO, ul_debugobj(pty, " callback requested")); + gettime_monotonic(&now); + if (timercmp(&now, &pty->next_callback_time, >)) { + rc = mainloop_callback(pty); + if (rc) + break; + } + } + + /* set timeout */ + if (timerisset(&pty->next_callback_time)) { + struct timeval now, rest; + + gettime_monotonic(&now); + timersub(&pty->next_callback_time, &now, &rest); + timeout = (rest.tv_sec * 1000) + (rest.tv_usec / 1000); + } else + timeout = pty->poll_timeout; + + /* wait for input, signal or timeout */ + DBG(IO, ul_debugobj(pty, "calling poll() [timeout=%dms]", timeout)); + ret = poll(pfd, ARRAY_SIZE(pfd), timeout); + + errsv = errno; + DBG(IO, ul_debugobj(pty, "poll() rc=%d", ret)); + + /* error */ + if (ret < 0) { + if (errsv == EAGAIN) + continue; + rc = -errno; + break; + } + + /* timeout */ + if (ret == 0) { + if (timerisset(&pty->next_callback_time)) { + rc = mainloop_callback(pty); + if (rc == 0) + continue; + } else + rc = 0; + + DBG(IO, ul_debugobj(pty, "leaving poll() loop [timeout=%d, rc=%d]", timeout, rc)); + break; + } + /* event */ + for (i = 0; i < ARRAY_SIZE(pfd); i++) { + rc = 0; + + if (pfd[i].revents == 0) + continue; + + DBG(IO, ul_debugobj(pty, " active pfd[%s].fd=%d %s %s %s %s", + i == POLLFD_STDIN ? "stdin" : + i == POLLFD_MASTER ? "master" : + i == POLLFD_SIGNAL ? "signal" : "???", + pfd[i].fd, + pfd[i].revents & POLLIN ? "POLLIN" : "", + pfd[i].revents & POLLHUP ? "POLLHUP" : "", + pfd[i].revents & POLLERR ? "POLLERR" : "", + pfd[i].revents & POLLNVAL ? "POLLNVAL" : "")); + + switch (i) { + case POLLFD_STDIN: + case POLLFD_MASTER: + /* data */ + if (pfd[i].revents & POLLIN) + rc = handle_io(pty, pfd[i].fd, &eof); + /* EOF maybe detected in two ways; they are as follows: + * A) poll() return POLLHUP event after close() + * B) read() returns 0 (no data) + * + * POLLNVAL means that fd is closed. + */ + if ((pfd[i].revents & POLLHUP) || (pfd[i].revents & POLLNVAL) || eof) { + DBG(IO, ul_debugobj(pty, " ignore FD")); + pfd[i].fd = -1; + if (i == POLLFD_STDIN) { + ul_pty_write_eof_to_child(pty); + DBG(IO, ul_debugobj(pty, " ignore STDIN")); + } + } + continue; + case POLLFD_SIGNAL: + rc = handle_signal(pty, pfd[i].fd); + break; + } + if (rc) + break; + } + } + + pty_signals_cleanup(pty); + + DBG(IO, ul_debug("poll() done [signal=%d, rc=%d]", pty->delivered_signal, rc)); + return rc; +} + +#ifdef TEST_PROGRAM_PTY +/* + * $ make test_pty + * $ ./test_pty + * + * ... and see for example tty(1) or "ps afu" + */ +static void child_sigstop(void *data __attribute__((__unused__)), pid_t child) +{ + kill(getpid(), SIGSTOP); + kill(child, SIGCONT); +} + +int main(int argc, char *argv[]) +{ + struct ul_pty_callbacks *cb; + const char *shell, *command = NULL, *shname = NULL; + int caught_signal = 0; + pid_t child; + struct ul_pty *pty; + + shell = getenv("SHELL"); + if (shell == NULL) + shell = _PATH_BSHELL; + if (argc == 2) + command = argv[1]; + + ul_pty_init_debug(0); + + pty = ul_new_pty(isatty(STDIN_FILENO)); + if (!pty) + err(EXIT_FAILURE, "failed to allocate PTY handler"); + + cb = ul_pty_get_callbacks(pty); + cb->child_sigstop = child_sigstop; + + if (ul_pty_setup(pty)) + err(EXIT_FAILURE, "failed to create pseudo-terminal"); + + fflush(stdout); /* ??? */ + + switch ((int) (child = fork())) { + case -1: /* error */ + ul_pty_cleanup(pty); + err(EXIT_FAILURE, "cannot create child process"); + break; + + case 0: /* child */ + ul_pty_init_slave(pty); + + signal(SIGTERM, SIG_DFL); /* because /etc/csh.login */ + + shname = strrchr(shell, '/'); + shname = shname ? shname + 1 : shell; + + if (command) + execl(shell, shname, "-c", command, NULL); + else + execl(shell, shname, "-i", NULL); + err(EXIT_FAILURE, "failed to execute %s", shell); + break; + + default: + break; + } + + /* parent */ + ul_pty_set_child(pty, child); + + /* this is the main loop */ + ul_pty_proxy_master(pty); + + /* all done; cleanup and kill */ + caught_signal = ul_pty_get_delivered_signal(pty); + + if (!caught_signal && ul_pty_get_child(pty) != (pid_t)-1) + ul_pty_wait_for_child(pty); /* final wait */ + + if (caught_signal && ul_pty_get_child(pty) != (pid_t)-1) { + fprintf(stderr, "\nSession terminated, killing shell..."); + kill(child, SIGTERM); + sleep(2); + kill(child, SIGKILL); + fprintf(stderr, " ...killed.\n"); + } + + ul_pty_cleanup(pty); + ul_free_pty(pty); + return EXIT_SUCCESS; +} + +#endif /* TEST_PROGRAM */ + diff --git a/utils/lib/pwdutils.c b/utils/lib/pwdutils.c new file mode 100644 index 0000000..d5f4d2e --- /dev/null +++ b/utils/lib/pwdutils.c @@ -0,0 +1,156 @@ +#include + +#include "c.h" +#include "pwdutils.h" +#include "xalloc.h" + +/* Returns allocated passwd and allocated pwdbuf to store passwd strings + * fields. In case of error returns NULL and set errno, for unknown user set + * errno to EINVAL + */ +struct passwd *xgetpwnam(const char *username, char **pwdbuf) +{ + struct passwd *pwd = NULL, *res = NULL; + int rc; + + if (!pwdbuf || !username) + return NULL; + + *pwdbuf = xmalloc(UL_GETPW_BUFSIZ); + pwd = xcalloc(1, sizeof(struct passwd)); + + errno = 0; + rc = getpwnam_r(username, pwd, *pwdbuf, UL_GETPW_BUFSIZ, &res); + if (rc != 0) { + errno = rc; + goto failed; + } + if (!res) { + errno = EINVAL; + goto failed; + } + return pwd; +failed: + free(pwd); + free(*pwdbuf); + return NULL; +} + +/* Returns allocated group and allocated grpbuf to store group strings + * fields. In case of error returns NULL and set errno, for unknown group set + * errno to EINVAL + */ +struct group *xgetgrnam(const char *groupname, char **grpbuf) +{ + struct group *grp = NULL, *res = NULL; + int rc; + + if (!grpbuf || !groupname) + return NULL; + + *grpbuf = xmalloc(UL_GETPW_BUFSIZ); + grp = xcalloc(1, sizeof(struct group)); + + errno = 0; + rc = getgrnam_r(groupname, grp, *grpbuf, UL_GETPW_BUFSIZ, &res); + if (rc != 0) { + errno = rc; + goto failed; + } + if (!res) { + errno = EINVAL; + goto failed; + } + return grp; +failed: + free(grp); + free(*grpbuf); + return NULL; +} + +struct passwd *xgetpwuid(uid_t uid, char **pwdbuf) +{ + struct passwd *pwd = NULL, *res = NULL; + int rc; + + if (!pwdbuf) + return NULL; + + *pwdbuf = xmalloc(UL_GETPW_BUFSIZ); + pwd = xcalloc(1, sizeof(struct passwd)); + + errno = 0; + rc = getpwuid_r(uid, pwd, *pwdbuf, UL_GETPW_BUFSIZ, &res); + if (rc != 0) { + errno = rc; + goto failed; + } + if (!res) { + errno = EINVAL; + goto failed; + } + return pwd; +failed: + free(pwd); + free(*pwdbuf); + return NULL; +} + +char *xgetlogin(void) +{ + struct passwd *pw = NULL; + uid_t ruid; + char *user; + + user = getlogin(); + if (user) + return xstrdup(user); + + /* GNU Hurd implementation has an extension where a process can exist in a + * non-conforming environment, and thus be outside the realms of POSIX + * process identifiers; on this platform, getuid() fails with a status of + * (uid_t)(-1) and sets errno if a program is run from a non-conforming + * environment. + * + * http://austingroupbugs.net/view.php?id=511 + */ + errno = 0; + ruid = getuid(); + + if (errno == 0) + pw = getpwuid(ruid); + if (pw && pw->pw_name && *pw->pw_name) + return xstrdup(pw->pw_name); + + return NULL; +} + +#ifdef TEST_PROGRAM +int main(int argc, char *argv[]) +{ + char *buf = NULL; + struct passwd *pwd = NULL; + + if (argc != 2) { + fprintf(stderr, "usage: %s \n", argv[0]); + return EXIT_FAILURE; + } + + pwd = xgetpwnam(argv[1], &buf); + if (!pwd) + err(EXIT_FAILURE, "failed to get %s pwd entry", argv[1]); + + printf("Username: %s\n", pwd->pw_name); + printf("UID: %d\n", pwd->pw_uid); + printf("HOME: %s\n", pwd->pw_dir); + printf("GECO: %s\n", pwd->pw_gecos); + + free(pwd); + free(buf); + + printf("Current: %s\n", (buf = xgetlogin())); + free(buf); + + return EXIT_SUCCESS; +} +#endif /* TEST_PROGRAM */ diff --git a/utils/lib/randutils.c b/utils/lib/randutils.c new file mode 100644 index 0000000..bd2a8f6 --- /dev/null +++ b/utils/lib/randutils.c @@ -0,0 +1,238 @@ +/* + * SPDX-License-Identifier: BSD-3-Clause + * + * General purpose random utilities. Based on libuuid code. + * + * This code is free software; you can redistribute it and/or modify it under + * the terms of the Modified BSD License. The complete text of the license is + * available in the Documentation/licenses/COPYING.BSD-3-Clause file. + */ +#include +#include +#include +#include +#include +#include + +#include + +#include "c.h" +#include "randutils.h" +#include "nls.h" + +#ifdef HAVE_TLS +#define THREAD_LOCAL static __thread +#else +#define THREAD_LOCAL static +#endif + +#ifdef HAVE_GETRANDOM +# include +#elif defined (__linux__) +# if !defined(SYS_getrandom) && defined(__NR_getrandom) + /* usable kernel-headers, but old glibc-headers */ +# define SYS_getrandom __NR_getrandom +# endif +#endif + +#if !defined(HAVE_GETRANDOM) && defined(SYS_getrandom) +/* libc without function, but we have syscal */ +#define GRND_NONBLOCK 0x01 +#define GRND_RANDOM 0x02 +static int getrandom(void *buf, size_t buflen, unsigned int flags) +{ + return (syscall(SYS_getrandom, buf, buflen, flags)); +} +# define HAVE_GETRANDOM +#endif + +#if defined(__linux__) && defined(__NR_gettid) && defined(HAVE_JRAND48) +#define DO_JRAND_MIX +THREAD_LOCAL unsigned short ul_jrand_seed[3]; +#endif + +int rand_get_number(int low_n, int high_n) +{ + return rand() % (high_n - low_n + 1) + low_n; +} + +static void crank_random(void) +{ + int i; + struct timeval tv; + unsigned int n_pid, n_uid; + + gettimeofday(&tv, NULL); + n_pid = getpid(); + n_uid = getuid(); + srand((n_pid << 16) ^ n_uid ^ tv.tv_sec ^ tv.tv_usec); + +#ifdef DO_JRAND_MIX + ul_jrand_seed[0] = getpid() ^ (tv.tv_sec & 0xFFFF); + ul_jrand_seed[1] = getppid() ^ (tv.tv_usec & 0xFFFF); + ul_jrand_seed[2] = (tv.tv_sec ^ tv.tv_usec) >> 16; +#endif + /* Crank the random number generator a few times */ + gettimeofday(&tv, NULL); + for (i = (tv.tv_sec ^ tv.tv_usec) & 0x1F; i > 0; i--) + rand(); +} + +int random_get_fd(void) +{ + int i, fd; + + fd = open("/dev/urandom", O_RDONLY | O_CLOEXEC); + if (fd == -1) + fd = open("/dev/random", O_RDONLY | O_NONBLOCK | O_CLOEXEC); + if (fd >= 0) { + i = fcntl(fd, F_GETFD); + if (i >= 0) + fcntl(fd, F_SETFD, i | FD_CLOEXEC); + } + crank_random(); + return fd; +} + +/* + * Generate a stream of random nbytes into buf. + * Use /dev/urandom if possible, and if not, + * use glibc pseudo-random functions. + */ +#define UL_RAND_READ_ATTEMPTS 8 +#define UL_RAND_READ_DELAY 125000 /* microseconds */ + +void random_get_bytes(void *buf, size_t nbytes) +{ + unsigned char *cp = (unsigned char *)buf; + size_t i, n = nbytes; + int lose_counter = 0; + +#ifdef HAVE_GETRANDOM + while (n > 0) { + int x; + + errno = 0; + x = getrandom(cp, n, GRND_NONBLOCK); + if (x > 0) { /* success */ + n -= x; + cp += x; + lose_counter = 0; + + } else if (errno == ENOSYS) { /* kernel without getrandom() */ + break; + + } else if (errno == EAGAIN && lose_counter < UL_RAND_READ_ATTEMPTS) { + xusleep(UL_RAND_READ_DELAY); /* no entropy, wait and try again */ + lose_counter++; + } else + break; + } + + if (errno == ENOSYS) +#endif + /* + * We've been built against headers that support getrandom, but the + * running kernel does not. Fallback to reading from /dev/{u,}random + * as before + */ + { + int fd = random_get_fd(); + + lose_counter = 0; + if (fd >= 0) { + while (n > 0) { + ssize_t x = read(fd, cp, n); + if (x <= 0) { + if (lose_counter++ > UL_RAND_READ_ATTEMPTS) + break; + xusleep(UL_RAND_READ_DELAY); + continue; + } + n -= x; + cp += x; + lose_counter = 0; + } + + close(fd); + } + } + /* + * We do this all the time, but this is the only source of + * randomness if /dev/random/urandom is out to lunch. + */ + crank_random(); + for (cp = buf, i = 0; i < nbytes; i++) + *cp++ ^= (rand() >> 7) & 0xFF; + +#ifdef DO_JRAND_MIX + { + unsigned short tmp_seed[3]; + + memcpy(tmp_seed, ul_jrand_seed, sizeof(tmp_seed)); + ul_jrand_seed[2] = ul_jrand_seed[2] ^ syscall(__NR_gettid); + for (cp = buf, i = 0; i < nbytes; i++) + *cp++ ^= (jrand48(tmp_seed) >> 7) & 0xFF; + memcpy(ul_jrand_seed, tmp_seed, + sizeof(ul_jrand_seed)-sizeof(unsigned short)); + } +#endif +} + + +/* + * Tell source of randomness. + */ +const char *random_tell_source(void) +{ +#ifdef HAVE_GETRANDOM + return _("getrandom() function"); +#else + size_t i; + static const char *random_sources[] = { + "/dev/urandom", + "/dev/random" + }; + + for (i = 0; i < ARRAY_SIZE(random_sources); i++) { + if (!access(random_sources[i], R_OK)) + return random_sources[i]; + } +#endif + return _("libc pseudo-random functions"); +} + +#ifdef TEST_PROGRAM_RANDUTILS +#include + +int main(int argc, char *argv[]) +{ + size_t i, n; + int64_t *vp, v; + char *buf; + size_t bufsz; + + n = argc == 1 ? 16 : atoi(argv[1]); + + printf("Multiple random calls:\n"); + for (i = 0; i < n; i++) { + random_get_bytes(&v, sizeof(v)); + printf("#%02zu: %25"PRIu64"\n", i, v); + } + + + printf("One random call:\n"); + bufsz = n * sizeof(*vp); + buf = malloc(bufsz); + if (!buf) + err(EXIT_FAILURE, "failed to allocate buffer"); + + random_get_bytes(buf, bufsz); + for (i = 0; i < n; i++) { + vp = (int64_t *) (buf + (i * sizeof(*vp))); + printf("#%02zu: %25"PRIu64"\n", i, *vp); + } + + return EXIT_SUCCESS; +} +#endif /* TEST_PROGRAM_RANDUTILS */ diff --git a/utils/lib/setproctitle.c b/utils/lib/setproctitle.c new file mode 100644 index 0000000..7168e46 --- /dev/null +++ b/utils/lib/setproctitle.c @@ -0,0 +1,75 @@ +/* + * set process title for ps (from sendmail) + * + * Clobbers argv of our main procedure so ps(1) will display the title. + */ +#include +#include +#include +#include + +#include "setproctitle.h" + +#ifndef SPT_BUFSIZE +# define SPT_BUFSIZE 2048 +#endif + +extern char **environ; + +static char **argv0; +static size_t argv_lth; + +void initproctitle (int argc, char **argv) +{ + int i; + char **envp = environ; + + /* + * Move the environment so we can reuse the memory. + * (Code borrowed from sendmail.) + * WARNING: ugly assumptions on memory layout here; + * if this ever causes problems, #undef DO_PS_FIDDLING + */ + for (i = 0; envp[i] != NULL; i++) + continue; + + environ = malloc(sizeof(char *) * (i + 1)); + if (environ == NULL) + return; + + for (i = 0; envp[i] != NULL; i++) + if ((environ[i] = strdup(envp[i])) == NULL) + return; + environ[i] = NULL; + + if (i > 0) + argv_lth = envp[i-1] + strlen(envp[i-1]) - argv[0]; + else + argv_lth = argv[argc-1] + strlen(argv[argc-1]) - argv[0]; + if (argv_lth > 1) + argv0 = argv; +} + +void setproctitle (const char *prog, const char *txt) +{ + size_t i; + char buf[SPT_BUFSIZE]; + + if (!argv0) + return; + + if (strlen(prog) + strlen(txt) + 5 > SPT_BUFSIZE) + return; + + sprintf(buf, "%s -- %s", prog, txt); + + i = strlen(buf); + if (i > argv_lth - 2) { + i = argv_lth - 2; + buf[i] = '\0'; + } + memset(argv0[0], '\0', argv_lth); /* clear the memory area */ + strcpy(argv0[0], buf); + + argv0[1] = NULL; +} diff --git a/utils/lib/sha1.c b/utils/lib/sha1.c new file mode 100644 index 0000000..22d33b3 --- /dev/null +++ b/utils/lib/sha1.c @@ -0,0 +1,256 @@ +/* + * SHA-1 in C by Steve Reid + * 100% Public Domain + * + * Test Vectors (from FIPS PUB 180-1) + * 1) "abc": A9993E36 4706816A BA3E2571 7850C26C 9CD0D89D + * 2) "abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq": 84983E44 1C3BD26E BAAE4AA1 F95129E5 E54670F1 + * 3) A million repetitions of "a": 34AA973C D4C4DAA4 F61EEB2B DBAD2731 6534016F + */ + +#define UL_SHA1HANDSOFF + +#include +#include +#include + +#include "sha1.h" + +#define rol(value, bits) (((value) << (bits)) | ((value) >> (32 - (bits)))) + +/* blk0() and blk() perform the initial expand. */ +#ifdef WORDS_BIGENDIAN +# define blk0(i) block->l[i] +#else +# define blk0(i) (block->l[i] = (rol(block->l[i],24)&0xFF00FF00) \ + |(rol(block->l[i],8)&0x00FF00FF)) +#endif + +#define blk(i) (block->l[i&15] = rol(block->l[(i+13)&15]^block->l[(i+8)&15] \ + ^block->l[(i+2)&15]^block->l[i&15],1)) + +/* (R0+R1), R2, R3, R4 are the different operations used in SHA1 */ +#define R0(v,w,x,y,z,i) z+=((w&(x^y))^y)+blk0(i)+0x5A827999+rol(v,5);w=rol(w,30); +#define R1(v,w,x,y,z,i) z+=((w&(x^y))^y)+blk(i)+0x5A827999+rol(v,5);w=rol(w,30); +#define R2(v,w,x,y,z,i) z+=(w^x^y)+blk(i)+0x6ED9EBA1+rol(v,5);w=rol(w,30); +#define R3(v,w,x,y,z,i) z+=(((w|x)&y)|(w&x))+blk(i)+0x8F1BBCDC+rol(v,5);w=rol(w,30); +#define R4(v,w,x,y,z,i) z+=(w^x^y)+blk(i)+0xCA62C1D6+rol(v,5);w=rol(w,30); + +/* Hash a single 512-bit block. This is the core of the algorithm. */ + +void ul_SHA1Transform(uint32_t state[5], const unsigned char buffer[64]) +{ + uint32_t a, b, c, d, e; + + typedef union { + unsigned char c[64]; + uint32_t l[16]; + } CHAR64LONG16; + +#ifdef UL_SHA1HANDSOFF + CHAR64LONG16 block[1]; /* use array to appear as a pointer */ + + memcpy(block, buffer, 64); +#else + /* The following had better never be used because it causes the + * pointer-to-const buffer to be cast into a pointer to non-const. + * And the result is written through. I threw a "const" in, hoping + * this will cause a diagnostic. + */ + CHAR64LONG16 *block = (const CHAR64LONG16 *)buffer; +#endif + /* Copy context->state[] to working vars */ + a = state[0]; + b = state[1]; + c = state[2]; + d = state[3]; + e = state[4]; + /* 4 rounds of 20 operations each. Loop unrolled. */ + R0(a, b, c, d, e, 0); + R0(e, a, b, c, d, 1); + R0(d, e, a, b, c, 2); + R0(c, d, e, a, b, 3); + R0(b, c, d, e, a, 4); + R0(a, b, c, d, e, 5); + R0(e, a, b, c, d, 6); + R0(d, e, a, b, c, 7); + R0(c, d, e, a, b, 8); + R0(b, c, d, e, a, 9); + R0(a, b, c, d, e, 10); + R0(e, a, b, c, d, 11); + R0(d, e, a, b, c, 12); + R0(c, d, e, a, b, 13); + R0(b, c, d, e, a, 14); + R0(a, b, c, d, e, 15); + R1(e, a, b, c, d, 16); + R1(d, e, a, b, c, 17); + R1(c, d, e, a, b, 18); + R1(b, c, d, e, a, 19); + R2(a, b, c, d, e, 20); + R2(e, a, b, c, d, 21); + R2(d, e, a, b, c, 22); + R2(c, d, e, a, b, 23); + R2(b, c, d, e, a, 24); + R2(a, b, c, d, e, 25); + R2(e, a, b, c, d, 26); + R2(d, e, a, b, c, 27); + R2(c, d, e, a, b, 28); + R2(b, c, d, e, a, 29); + R2(a, b, c, d, e, 30); + R2(e, a, b, c, d, 31); + R2(d, e, a, b, c, 32); + R2(c, d, e, a, b, 33); + R2(b, c, d, e, a, 34); + R2(a, b, c, d, e, 35); + R2(e, a, b, c, d, 36); + R2(d, e, a, b, c, 37); + R2(c, d, e, a, b, 38); + R2(b, c, d, e, a, 39); + R3(a, b, c, d, e, 40); + R3(e, a, b, c, d, 41); + R3(d, e, a, b, c, 42); + R3(c, d, e, a, b, 43); + R3(b, c, d, e, a, 44); + R3(a, b, c, d, e, 45); + R3(e, a, b, c, d, 46); + R3(d, e, a, b, c, 47); + R3(c, d, e, a, b, 48); + R3(b, c, d, e, a, 49); + R3(a, b, c, d, e, 50); + R3(e, a, b, c, d, 51); + R3(d, e, a, b, c, 52); + R3(c, d, e, a, b, 53); + R3(b, c, d, e, a, 54); + R3(a, b, c, d, e, 55); + R3(e, a, b, c, d, 56); + R3(d, e, a, b, c, 57); + R3(c, d, e, a, b, 58); + R3(b, c, d, e, a, 59); + R4(a, b, c, d, e, 60); + R4(e, a, b, c, d, 61); + R4(d, e, a, b, c, 62); + R4(c, d, e, a, b, 63); + R4(b, c, d, e, a, 64); + R4(a, b, c, d, e, 65); + R4(e, a, b, c, d, 66); + R4(d, e, a, b, c, 67); + R4(c, d, e, a, b, 68); + R4(b, c, d, e, a, 69); + R4(a, b, c, d, e, 70); + R4(e, a, b, c, d, 71); + R4(d, e, a, b, c, 72); + R4(c, d, e, a, b, 73); + R4(b, c, d, e, a, 74); + R4(a, b, c, d, e, 75); + R4(e, a, b, c, d, 76); + R4(d, e, a, b, c, 77); + R4(c, d, e, a, b, 78); + R4(b, c, d, e, a, 79); + /* Add the working vars back into context.state[] */ + state[0] += a; + state[1] += b; + state[2] += c; + state[3] += d; + state[4] += e; + /* Wipe variables */ + a = b = c = d = e = 0; +#ifdef UL_SHA1HANDSOFF + memset(block, '\0', sizeof(block)); +#endif +} + +/* SHA1Init - Initialize new context */ + +void ul_SHA1Init(UL_SHA1_CTX *context) +{ + /* SHA1 initialization constants */ + context->state[0] = 0x67452301; + context->state[1] = 0xEFCDAB89; + context->state[2] = 0x98BADCFE; + context->state[3] = 0x10325476; + context->state[4] = 0xC3D2E1F0; + context->count[0] = context->count[1] = 0; +} + +/* Run your data through this. */ + +void ul_SHA1Update(UL_SHA1_CTX *context, const unsigned char *data, uint32_t len) +{ + uint32_t i; + + uint32_t j; + + j = context->count[0]; + if ((context->count[0] += len << 3) < j) + context->count[1]++; + context->count[1] += (len >> 29); + j = (j >> 3) & 63; + if ((j + len) > 63) { + memcpy(&context->buffer[j], data, (i = 64 - j)); + ul_SHA1Transform(context->state, context->buffer); + for (; i + 63 < len; i += 64) { + ul_SHA1Transform(context->state, &data[i]); + } + j = 0; + } else + i = 0; + memcpy(&context->buffer[j], &data[i], len - i); +} + +/* Add padding and return the message digest. */ + +void ul_SHA1Final(unsigned char digest[20], UL_SHA1_CTX *context) +{ + unsigned i; + + unsigned char finalcount[8]; + + unsigned char c; + +#if 0 /* untested "improvement" by DHR */ + /* Convert context->count to a sequence of bytes + * in finalcount. Second element first, but + * big-endian order within element. + * But we do it all backwards. + */ + unsigned char *fcp = &finalcount[8]; + + for (i = 0; i < 2; i++) { + uint32_t t = context->count[i]; + + int j; + + for (j = 0; j < 4; t >>= 8, j++) + *--fcp = (unsigned char)t} +#else + for (i = 0; i < 8; i++) { + finalcount[i] = (unsigned char)((context->count[(i >= 4 ? 0 : 1)] >> ((3 - (i & 3)) * 8)) & 255); /* Endian independent */ + } +#endif + c = 0200; + ul_SHA1Update(context, &c, 1); + while ((context->count[0] & 504) != 448) { + c = 0000; + ul_SHA1Update(context, &c, 1); + } + ul_SHA1Update(context, finalcount, 8); /* Should cause a SHA1Transform() */ + for (i = 0; i < 20; i++) { + digest[i] = (unsigned char) + ((context->state[i >> 2] >> ((3 - (i & 3)) * 8)) & 255); + } + /* Wipe variables */ + memset(context, '\0', sizeof(*context)); + memset(&finalcount, '\0', sizeof(finalcount)); +} + +void ul_SHA1(char *hash_out, const char *str, unsigned len) +{ + UL_SHA1_CTX ctx; + unsigned int ii; + + ul_SHA1Init(&ctx); + for (ii = 0; ii < len; ii += 1) + ul_SHA1Update(&ctx, (const unsigned char *)str + ii, 1); + ul_SHA1Final((unsigned char *)hash_out, &ctx); + hash_out[20] = '\0'; +} diff --git a/utils/lib/signames.c b/utils/lib/signames.c new file mode 100644 index 0000000..316eec5 --- /dev/null +++ b/utils/lib/signames.c @@ -0,0 +1,204 @@ +/* + * Copyright (c) 1988, 1993, 1994, 2017 + * The Regents of the University of California. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by the University of + * California, Berkeley and its contributors. + * 4. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ +/* + * 2017-10-14 Niklas Hambüchen + * - Extracted signal names mapping from kill.c + * + * Copyright (C) 2014 Sami Kerola + * Copyright (C) 2014 Karel Zak + * Copyright (C) 2017 Niklas Hambüchen + */ + +#include /* for isdigit() */ +#include +#include +#include +#include + +#include "c.h" +#include "strutils.h" +#include "signames.h" + +static const struct ul_signal_name { + const char *name; + int val; +} ul_signames[] = { + /* POSIX signals */ + { "HUP", SIGHUP }, /* 1 */ + { "INT", SIGINT }, /* 2 */ + { "QUIT", SIGQUIT }, /* 3 */ + { "ILL", SIGILL }, /* 4 */ +#ifdef SIGTRAP + { "TRAP", SIGTRAP }, /* 5 */ +#endif + { "ABRT", SIGABRT }, /* 6 */ +#ifdef SIGIOT + { "IOT", SIGIOT }, /* 6, same as SIGABRT */ +#endif +#ifdef SIGEMT + { "EMT", SIGEMT }, /* 7 (mips,alpha,sparc*) */ +#endif +#ifdef SIGBUS + { "BUS", SIGBUS }, /* 7 (arm,i386,m68k,ppc), 10 (mips,alpha,sparc*) */ +#endif + { "FPE", SIGFPE }, /* 8 */ + { "KILL", SIGKILL }, /* 9 */ + { "USR1", SIGUSR1 }, /* 10 (arm,i386,m68k,ppc), 30 (alpha,sparc*), 16 (mips) */ + { "SEGV", SIGSEGV }, /* 11 */ + { "USR2", SIGUSR2 }, /* 12 (arm,i386,m68k,ppc), 31 (alpha,sparc*), 17 (mips) */ + { "PIPE", SIGPIPE }, /* 13 */ + { "ALRM", SIGALRM }, /* 14 */ + { "TERM", SIGTERM }, /* 15 */ +#ifdef SIGSTKFLT + { "STKFLT", SIGSTKFLT }, /* 16 (arm,i386,m68k,ppc) */ +#endif + { "CHLD", SIGCHLD }, /* 17 (arm,i386,m68k,ppc), 20 (alpha,sparc*), 18 (mips) */ +#ifdef SIGCLD + { "CLD", SIGCLD }, /* same as SIGCHLD (mips) */ +#endif + { "CONT", SIGCONT }, /* 18 (arm,i386,m68k,ppc), 19 (alpha,sparc*), 25 (mips) */ + { "STOP", SIGSTOP }, /* 19 (arm,i386,m68k,ppc), 17 (alpha,sparc*), 23 (mips) */ + { "TSTP", SIGTSTP }, /* 20 (arm,i386,m68k,ppc), 18 (alpha,sparc*), 24 (mips) */ + { "TTIN", SIGTTIN }, /* 21 (arm,i386,m68k,ppc,alpha,sparc*), 26 (mips) */ + { "TTOU", SIGTTOU }, /* 22 (arm,i386,m68k,ppc,alpha,sparc*), 27 (mips) */ +#ifdef SIGURG + { "URG", SIGURG }, /* 23 (arm,i386,m68k,ppc), 16 (alpha,sparc*), 21 (mips) */ +#endif +#ifdef SIGXCPU + { "XCPU", SIGXCPU }, /* 24 (arm,i386,m68k,ppc,alpha,sparc*), 30 (mips) */ +#endif +#ifdef SIGXFSZ + { "XFSZ", SIGXFSZ }, /* 25 (arm,i386,m68k,ppc,alpha,sparc*), 31 (mips) */ +#endif +#ifdef SIGVTALRM + { "VTALRM", SIGVTALRM }, /* 26 (arm,i386,m68k,ppc,alpha,sparc*), 28 (mips) */ +#endif +#ifdef SIGPROF + { "PROF", SIGPROF }, /* 27 (arm,i386,m68k,ppc,alpha,sparc*), 29 (mips) */ +#endif +#ifdef SIGWINCH + { "WINCH", SIGWINCH }, /* 28 (arm,i386,m68k,ppc,alpha,sparc*), 20 (mips) */ +#endif +#ifdef SIGIO + { "IO", SIGIO }, /* 29 (arm,i386,m68k,ppc), 23 (alpha,sparc*), 22 (mips) */ +#endif +#ifdef SIGPOLL + { "POLL", SIGPOLL }, /* same as SIGIO */ +#endif +#ifdef SIGINFO + { "INFO", SIGINFO }, /* 29 (alpha) */ +#endif +#ifdef SIGLOST + { "LOST", SIGLOST }, /* 29 (arm,i386,m68k,ppc,sparc*) */ +#endif +#ifdef SIGPWR + { "PWR", SIGPWR }, /* 30 (arm,i386,m68k,ppc), 29 (alpha,sparc*), 19 (mips) */ +#endif +#ifdef SIGUNUSED + { "UNUSED", SIGUNUSED }, /* 31 (arm,i386,m68k,ppc) */ +#endif +#ifdef SIGSYS + { "SYS", SIGSYS }, /* 31 (mips,alpha,sparc*) */ +#endif +}; + +#ifdef SIGRTMIN +static int rtsig_to_signum(const char *sig) +{ + int num, maxi = 0; + char *ep = NULL; + + if (strncasecmp(sig, "min+", 4) == 0) + sig += 4; + else if (strncasecmp(sig, "max-", 4) == 0) { + sig += 4; + maxi = 1; + } + if (!isdigit(*sig)) + return -1; + errno = 0; + num = strtol(sig, &ep, 10); + if (!ep || sig == ep || errno || num < 0) + return -1; + num = maxi ? SIGRTMAX - num : SIGRTMIN + num; + if (num < SIGRTMIN || SIGRTMAX < num) + return -1; + return num; +} +#endif + +int signame_to_signum(const char *sig) +{ + size_t n; + + if (!strncasecmp(sig, "sig", 3)) + sig += 3; +#ifdef SIGRTMIN + /* RT signals */ + if (!strncasecmp(sig, "rt", 2)) + return rtsig_to_signum(sig + 2); +#endif + /* Normal signals */ + for (n = 0; n < ARRAY_SIZE(ul_signames); n++) { + if (!strcasecmp(ul_signames[n].name, sig)) + return ul_signames[n].val; + } + return -1; +} + +const char *signum_to_signame(int signum) +{ + size_t n; + + for (n = 0; n < ARRAY_SIZE(ul_signames); n++) { + if (ul_signames[n].val == signum) { + return ul_signames[n].name; + } + } + + return NULL; +} + +int get_signame_by_idx(size_t idx, const char **signame, int *signum) +{ + if (idx >= ARRAY_SIZE(ul_signames)) + return -1; + + if (signame) + *signame = ul_signames[idx].name; + if (signum) + *signum = ul_signames[idx].val; + return 0; + +} + diff --git a/utils/lib/strutils.c b/utils/lib/strutils.c new file mode 100644 index 0000000..304f314 --- /dev/null +++ b/utils/lib/strutils.c @@ -0,0 +1,1135 @@ +/* + * Copyright (C) 2010 Karel Zak + * Copyright (C) 2010 Davidlohr Bueso + * + * No copyright is claimed. This code is in the public domain; do with + * it what you wish. + */ +#include +#include +#include +#include +#include +#include +#include +#include + +#include "c.h" +#include "nls.h" +#include "strutils.h" +#include "bitops.h" +#include "pathnames.h" + +static int STRTOXX_EXIT_CODE = EXIT_FAILURE; + +void strutils_set_exitcode(int ex) { + STRTOXX_EXIT_CODE = ex; +} + +static int do_scale_by_power (uintmax_t *x, int base, int power) +{ + while (power--) { + if (UINTMAX_MAX / base < *x) + return -ERANGE; + *x *= base; + } + return 0; +} + +/* + * strtosize() - convert string to size (uintmax_t). + * + * Supported suffixes: + * + * XiB or X for 2^N + * where X = {K,M,G,T,P,E,Z,Y} + * or X = {k,m,g,t,p,e} (undocumented for backward compatibility only) + * for example: + * 10KiB = 10240 + * 10K = 10240 + * + * XB for 10^N + * where X = {K,M,G,T,P,E,Z,Y} + * for example: + * 10KB = 10000 + * + * The optional 'power' variable returns number associated with used suffix + * {K,M,G,T,P,E,Z,Y} = {1,2,3,4,5,6,7,8}. + * + * The function also supports decimal point, for example: + * 0.5MB = 500000 + * 0.5MiB = 512000 + * + * Note that the function does not accept numbers with '-' (negative sign) + * prefix. + */ +int parse_size(const char *str, uintmax_t *res, int *power) +{ + const char *p; + char *end; + uintmax_t x, frac = 0; + int base = 1024, rc = 0, pwr = 0, frac_zeros = 0; + + static const char *suf = "KMGTPEZY"; + static const char *suf2 = "kmgtpezy"; + const char *sp; + + *res = 0; + + if (!str || !*str) { + rc = -EINVAL; + goto err; + } + + /* Only positive numbers are acceptable + * + * Note that this check is not perfect, it would be better to + * use lconv->negative_sign. But coreutils use the same solution, + * so it's probably good enough... + */ + p = str; + while (isspace((unsigned char) *p)) + p++; + if (*p == '-') { + rc = -EINVAL; + goto err; + } + + errno = 0, end = NULL; + x = strtoumax(str, &end, 0); + + if (end == str || + (errno != 0 && (x == UINTMAX_MAX || x == 0))) { + rc = errno ? -errno : -EINVAL; + goto err; + } + if (!end || !*end) + goto done; /* without suffix */ + p = end; + + /* + * Check size suffixes + */ +check_suffix: + if (*(p + 1) == 'i' && (*(p + 2) == 'B' || *(p + 2) == 'b') && !*(p + 3)) + base = 1024; /* XiB, 2^N */ + else if ((*(p + 1) == 'B' || *(p + 1) == 'b') && !*(p + 2)) + base = 1000; /* XB, 10^N */ + else if (*(p + 1)) { + struct lconv const *l = localeconv(); + const char *dp = l ? l->decimal_point : NULL; + size_t dpsz = dp ? strlen(dp) : 0; + + if (frac == 0 && *p && dp && strncmp(dp, p, dpsz) == 0) { + const char *fstr = p + dpsz; + + for (p = fstr; *p == '0'; p++) + frac_zeros++; + fstr = p; + if (isdigit(*fstr)) { + errno = 0, end = NULL; + frac = strtoumax(fstr, &end, 0); + if (end == fstr || + (errno != 0 && (frac == UINTMAX_MAX || frac == 0))) { + rc = errno ? -errno : -EINVAL; + goto err; + } + } else + end = (char *) p; + + if (frac && (!end || !*end)) { + rc = -EINVAL; + goto err; /* without suffix, but with frac */ + } + p = end; + goto check_suffix; + } + rc = -EINVAL; + goto err; /* unexpected suffix */ + } + + sp = strchr(suf, *p); + if (sp) + pwr = (sp - suf) + 1; + else { + sp = strchr(suf2, *p); + if (sp) + pwr = (sp - suf2) + 1; + else { + rc = -EINVAL; + goto err; + } + } + + rc = do_scale_by_power(&x, base, pwr); + if (power) + *power = pwr; + if (frac && pwr) { + int i; + uintmax_t frac_div = 10, frac_poz = 1, frac_base = 1; + + /* mega, giga, ... */ + do_scale_by_power(&frac_base, base, pwr); + + /* maximal divisor for last digit (e.g. for 0.05 is + * frac_div=100, for 0.054 is frac_div=1000, etc.) + * + * Reduce frac if too large. + */ + while (frac_div < frac) { + if (frac_div <= UINTMAX_MAX/10) + frac_div *= 10; + else + frac /= 10; + } + + /* 'frac' is without zeros (5 means 0.5 as well as 0.05) */ + for (i = 0; i < frac_zeros; i++) { + if (frac_div <= UINTMAX_MAX/10) + frac_div *= 10; + else + frac /= 10; + } + + /* + * Go backwardly from last digit and add to result what the + * digit represents in the frac_base. For example 0.25G + * + * 5 means 1GiB / (100/5) + * 2 means 1GiB / (10/2) + */ + do { + unsigned int seg = frac % 10; /* last digit of the frac */ + uintmax_t seg_div = frac_div / frac_poz; /* what represents the segment 1000, 100, .. */ + + frac /= 10; /* remove last digit from frac */ + frac_poz *= 10; + + if (seg && seg_div / seg) + x += frac_base / (seg_div / seg); + } while (frac); + } +done: + *res = x; +err: + if (rc < 0) + errno = -rc; + return rc; +} + +int strtosize(const char *str, uintmax_t *res) +{ + return parse_size(str, res, NULL); +} + +int isdigit_strend(const char *str, const char **end) +{ + const char *p; + + for (p = str; p && *p && isdigit((unsigned char) *p); p++); + + if (end) + *end = p; + return p && p > str && !*p; +} + +int isxdigit_strend(const char *str, const char **end) +{ + const char *p; + + for (p = str; p && *p && isxdigit((unsigned char) *p); p++); + + if (end) + *end = p; + + return p && p > str && !*p; +} + +/* + * parse_switch(argv[i], "on", "off", "yes", "no", NULL); + */ +int parse_switch(const char *arg, const char *errmesg, ...) +{ + const char *a, *b; + va_list ap; + + va_start(ap, errmesg); + do { + a = va_arg(ap, char *); + if (!a) + break; + b = va_arg(ap, char *); + if (!b) + break; + + if (strcmp(arg, a) == 0) { + va_end(ap); + return 1; + } + + if (strcmp(arg, b) == 0) { + va_end(ap); + return 0; + } + } while (1); + va_end(ap); + + errx(STRTOXX_EXIT_CODE, "%s: '%s'", errmesg, arg); +} + +#ifndef HAVE_MEMPCPY +void *mempcpy(void *restrict dest, const void *restrict src, size_t n) +{ + return ((char *)memcpy(dest, src, n)) + n; +} +#endif + +#ifndef HAVE_STRNLEN +size_t strnlen(const char *s, size_t maxlen) +{ + size_t i; + + for (i = 0; i < maxlen; i++) { + if (s[i] == '\0') + return i; + } + return maxlen; +} +#endif + +#ifndef HAVE_STRNCHR +char *strnchr(const char *s, size_t maxlen, int c) +{ + for (; maxlen-- && *s != '\0'; ++s) + if (*s == (char)c) + return (char *)s; + return NULL; +} +#endif + +#ifndef HAVE_STRNDUP +char *strndup(const char *s, size_t n) +{ + size_t len = strnlen(s, n); + char *new = malloc((len + 1) * sizeof(char)); + if (!new) + return NULL; + new[len] = '\0'; + return (char *) memcpy(new, s, len); +} +#endif + +static uint32_t _strtou32_or_err(const char *str, const char *errmesg, int base); +static uint64_t _strtou64_or_err(const char *str, const char *errmesg, int base); + +int16_t strtos16_or_err(const char *str, const char *errmesg) +{ + int32_t num = strtos32_or_err(str, errmesg); + + if (num < INT16_MIN || num > INT16_MAX) { + errno = ERANGE; + err(STRTOXX_EXIT_CODE, "%s: '%s'", errmesg, str); + } + return num; +} + +static uint16_t _strtou16_or_err(const char *str, const char *errmesg, int base) +{ + uint32_t num = _strtou32_or_err(str, errmesg, base); + + if (num > UINT16_MAX) { + errno = ERANGE; + err(STRTOXX_EXIT_CODE, "%s: '%s'", errmesg, str); + } + return num; +} + +uint16_t strtou16_or_err(const char *str, const char *errmesg) +{ + return _strtou16_or_err(str, errmesg, 10); +} + +uint16_t strtox16_or_err(const char *str, const char *errmesg) +{ + return _strtou16_or_err(str, errmesg, 16); +} + +int32_t strtos32_or_err(const char *str, const char *errmesg) +{ + int64_t num = strtos64_or_err(str, errmesg); + + if (num < INT32_MIN || num > INT32_MAX) { + errno = ERANGE; + err(STRTOXX_EXIT_CODE, "%s: '%s'", errmesg, str); + } + return num; +} + +static uint32_t _strtou32_or_err(const char *str, const char *errmesg, int base) +{ + uint64_t num = _strtou64_or_err(str, errmesg, base); + + if (num > UINT32_MAX) { + errno = ERANGE; + err(STRTOXX_EXIT_CODE, "%s: '%s'", errmesg, str); + } + return num; +} + +uint32_t strtou32_or_err(const char *str, const char *errmesg) +{ + return _strtou32_or_err(str, errmesg, 10); +} + +uint32_t strtox32_or_err(const char *str, const char *errmesg) +{ + return _strtou32_or_err(str, errmesg, 16); +} + +int64_t strtos64_or_err(const char *str, const char *errmesg) +{ + int64_t num; + char *end = NULL; + + errno = 0; + if (str == NULL || *str == '\0') + goto err; + num = strtoimax(str, &end, 10); + + if (errno || str == end || (end && *end)) + goto err; + + return num; +err: + if (errno == ERANGE) + err(STRTOXX_EXIT_CODE, "%s: '%s'", errmesg, str); + + errx(STRTOXX_EXIT_CODE, "%s: '%s'", errmesg, str); +} + +static uint64_t _strtou64_or_err(const char *str, const char *errmesg, int base) +{ + uintmax_t num; + char *end = NULL; + + errno = 0; + if (str == NULL || *str == '\0') + goto err; + num = strtoumax(str, &end, base); + + if (errno || str == end || (end && *end)) + goto err; + + return num; +err: + if (errno == ERANGE) + err(STRTOXX_EXIT_CODE, "%s: '%s'", errmesg, str); + + errx(STRTOXX_EXIT_CODE, "%s: '%s'", errmesg, str); +} + +uint64_t strtou64_or_err(const char *str, const char *errmesg) +{ + return _strtou64_or_err(str, errmesg, 10); +} + +uint64_t strtox64_or_err(const char *str, const char *errmesg) +{ + return _strtou64_or_err(str, errmesg, 16); +} + +double strtod_or_err(const char *str, const char *errmesg) +{ + double num; + char *end = NULL; + + errno = 0; + if (str == NULL || *str == '\0') + goto err; + num = strtod(str, &end); + + if (errno || str == end || (end && *end)) + goto err; + + return num; +err: + if (errno == ERANGE) + err(STRTOXX_EXIT_CODE, "%s: '%s'", errmesg, str); + + errx(STRTOXX_EXIT_CODE, "%s: '%s'", errmesg, str); +} + +long strtol_or_err(const char *str, const char *errmesg) +{ + long num; + char *end = NULL; + + errno = 0; + if (str == NULL || *str == '\0') + goto err; + num = strtol(str, &end, 10); + + if (errno || str == end || (end && *end)) + goto err; + + return num; +err: + if (errno == ERANGE) + err(STRTOXX_EXIT_CODE, "%s: '%s'", errmesg, str); + + errx(STRTOXX_EXIT_CODE, "%s: '%s'", errmesg, str); +} + +unsigned long strtoul_or_err(const char *str, const char *errmesg) +{ + unsigned long num; + char *end = NULL; + + errno = 0; + if (str == NULL || *str == '\0') + goto err; + num = strtoul(str, &end, 10); + + if (errno || str == end || (end && *end)) + goto err; + + return num; +err: + if (errno == ERANGE) + err(STRTOXX_EXIT_CODE, "%s: '%s'", errmesg, str); + + errx(STRTOXX_EXIT_CODE, "%s: '%s'", errmesg, str); +} + +uintmax_t strtosize_or_err(const char *str, const char *errmesg) +{ + uintmax_t num; + + if (strtosize(str, &num) == 0) + return num; + + if (errno) + err(STRTOXX_EXIT_CODE, "%s: '%s'", errmesg, str); + + errx(STRTOXX_EXIT_CODE, "%s: '%s'", errmesg, str); +} + + +void strtotimeval_or_err(const char *str, struct timeval *tv, const char *errmesg) +{ + double user_input; + + user_input = strtod_or_err(str, errmesg); + tv->tv_sec = (time_t) user_input; + tv->tv_usec = (long)((user_input - tv->tv_sec) * 1000000); +} + +/* + * Converts stat->st_mode to ls(1)-like mode string. The size of "str" must + * be 11 bytes. + */ +char *xstrmode(mode_t mode, char *str) +{ + unsigned short i = 0; + + if (S_ISDIR(mode)) + str[i++] = 'd'; + else if (S_ISLNK(mode)) + str[i++] = 'l'; + else if (S_ISCHR(mode)) + str[i++] = 'c'; + else if (S_ISBLK(mode)) + str[i++] = 'b'; + else if (S_ISSOCK(mode)) + str[i++] = 's'; + else if (S_ISFIFO(mode)) + str[i++] = 'p'; + else if (S_ISREG(mode)) + str[i++] = '-'; + + str[i++] = mode & S_IRUSR ? 'r' : '-'; + str[i++] = mode & S_IWUSR ? 'w' : '-'; + str[i++] = (mode & S_ISUID + ? (mode & S_IXUSR ? 's' : 'S') + : (mode & S_IXUSR ? 'x' : '-')); + str[i++] = mode & S_IRGRP ? 'r' : '-'; + str[i++] = mode & S_IWGRP ? 'w' : '-'; + str[i++] = (mode & S_ISGID + ? (mode & S_IXGRP ? 's' : 'S') + : (mode & S_IXGRP ? 'x' : '-')); + str[i++] = mode & S_IROTH ? 'r' : '-'; + str[i++] = mode & S_IWOTH ? 'w' : '-'; + str[i++] = (mode & S_ISVTX + ? (mode & S_IXOTH ? 't' : 'T') + : (mode & S_IXOTH ? 'x' : '-')); + str[i] = '\0'; + + return str; +} + +/* + * returns exponent (2^x=n) in range KiB..EiB (2^10..2^60) + */ +static int get_exp(uint64_t n) +{ + int shft; + + for (shft = 10; shft <= 60; shft += 10) { + if (n < (1ULL << shft)) + break; + } + return shft - 10; +} + +char *size_to_human_string(int options, uint64_t bytes) +{ + char buf[32]; + int dec, exp; + uint64_t frac; + const char *letters = "BKMGTPE"; + char suffix[sizeof(" KiB")], *psuf = suffix; + char c; + + if (options & SIZE_SUFFIX_SPACE) + *psuf++ = ' '; + + + exp = get_exp(bytes); + c = *(letters + (exp ? exp / 10 : 0)); + dec = exp ? bytes / (1ULL << exp) : bytes; + frac = exp ? bytes % (1ULL << exp) : 0; + + *psuf++ = c; + + if ((options & SIZE_SUFFIX_3LETTER) && (c != 'B')) { + *psuf++ = 'i'; + *psuf++ = 'B'; + } + + *psuf = '\0'; + + /* fprintf(stderr, "exp: %d, unit: %c, dec: %d, frac: %jd\n", + * exp, suffix[0], dec, frac); + */ + + /* round */ + if (frac) { + /* get 3 digits after decimal point */ + if (frac >= UINT64_MAX / 1000) + frac = ((frac / 1024) * 1000) / (1ULL << (exp - 10)) ; + else + frac = (frac * 1000) / (1ULL << (exp)) ; + + if (options & SIZE_DECIMAL_2DIGITS) { + /* round 4/5 and keep 2 digits after decimal point */ + frac = (frac + 5) / 10 ; + } else { + /* round 4/5 and keep 1 digit after decimal point */ + frac = ((frac + 50) / 100) * 10 ; + } + + /* rounding could have overflowed */ + if (frac == 100) { + dec++; + frac = 0; + } + } + + if (frac) { + struct lconv const *l = localeconv(); + char *dp = l ? l->decimal_point : NULL; + int len; + + if (!dp || !*dp) + dp = "."; + + len = snprintf(buf, sizeof(buf), "%d%s%02" PRIu64, dec, dp, frac); + if (len > 0 && (size_t) len < sizeof(buf)) { + /* remove potential extraneous zero */ + if (buf[len - 1] == '0') + buf[len--] = '\0'; + /* append suffix */ + xstrncpy(buf+len, suffix, sizeof(buf) - len); + } else + *buf = '\0'; /* snprintf error */ + } else + snprintf(buf, sizeof(buf), "%d%s", dec, suffix); + + return strdup(buf); +} + +/* + * Parses comma delimited list to array with IDs, for example: + * + * "aaa,bbb,ccc" --> ary[0] = FOO_AAA; + * ary[1] = FOO_BBB; + * ary[3] = FOO_CCC; + * + * The function name2id() provides conversion from string to ID. + * + * Returns: >= 0 : number of items added to ary[] + * -1 : parse error or unknown item + * -2 : arysz reached + */ +int string_to_idarray(const char *list, int ary[], size_t arysz, + int (name2id)(const char *, size_t)) +{ + const char *begin = NULL, *p; + size_t n = 0; + + if (!list || !*list || !ary || !arysz || !name2id) + return -1; + + for (p = list; p && *p; p++) { + const char *end = NULL; + int id; + + if (n >= arysz) + return -2; + if (!begin) + begin = p; /* begin of the column name */ + if (*p == ',') + end = p; /* terminate the name */ + if (*(p + 1) == '\0') + end = p + 1; /* end of string */ + if (!begin || !end) + continue; + if (end <= begin) + return -1; + + id = name2id(begin, end - begin); + if (id == -1) + return -1; + ary[ n++ ] = id; + begin = NULL; + if (end && !*end) + break; + } + return n; +} + +/* + * Parses the array like string_to_idarray but if format is "+aaa,bbb" + * it adds fields to array instead of replacing them. + */ +int string_add_to_idarray(const char *list, int ary[], size_t arysz, + size_t *ary_pos, int (name2id)(const char *, size_t)) +{ + const char *list_add; + int r; + + if (!list || !*list || !ary_pos || *ary_pos > arysz) + return -1; + + if (list[0] == '+') + list_add = &list[1]; + else { + list_add = list; + *ary_pos = 0; + } + + r = string_to_idarray(list_add, &ary[*ary_pos], arysz - *ary_pos, name2id); + if (r > 0) + *ary_pos += r; + return r; +} + +/* + * LIST ::= [, ] + * + * The is translated to 'id' by name2id() function and the 'id' is used + * as a position in the 'ary' bit array. It means that the 'id' has to be in + * range <0..N> where N < sizeof(ary) * NBBY. + * + * Returns: 0 on success, <0 on error. + */ +int string_to_bitarray(const char *list, + char *ary, + int (*name2bit)(const char *, size_t)) +{ + const char *begin = NULL, *p; + + if (!list || !name2bit || !ary) + return -EINVAL; + + for (p = list; p && *p; p++) { + const char *end = NULL; + int bit; + + if (!begin) + begin = p; /* begin of the level name */ + if (*p == ',') + end = p; /* terminate the name */ + if (*(p + 1) == '\0') + end = p + 1; /* end of string */ + if (!begin || !end) + continue; + if (end <= begin) + return -1; + + bit = name2bit(begin, end - begin); + if (bit < 0) + return bit; + setbit(ary, bit); + begin = NULL; + if (end && !*end) + break; + } + return 0; +} + +/* + * LIST ::= [, ] + * + * The is translated to 'id' by name2flag() function and the flags is + * set to the 'mask' +* + * Returns: 0 on success, <0 on error. + */ +int string_to_bitmask(const char *list, + unsigned long *mask, + long (*name2flag)(const char *, size_t)) +{ + const char *begin = NULL, *p; + + if (!list || !name2flag || !mask) + return -EINVAL; + + for (p = list; p && *p; p++) { + const char *end = NULL; + long flag; + + if (!begin) + begin = p; /* begin of the level name */ + if (*p == ',') + end = p; /* terminate the name */ + if (*(p + 1) == '\0') + end = p + 1; /* end of string */ + if (!begin || !end) + continue; + if (end <= begin) + return -1; + + flag = name2flag(begin, end - begin); + if (flag < 0) + return flag; /* error */ + *mask |= flag; + begin = NULL; + if (end && !*end) + break; + } + return 0; +} + +/* + * Parse the lower and higher values in a string containing + * "lower:higher" or "lower-higher" format. Note that either + * the lower or the higher values may be missing, and the def + * value will be assigned to it by default. + * + * Returns: 0 on success, <0 on error. + */ +int parse_range(const char *str, int *lower, int *upper, int def) +{ + char *end = NULL; + + if (!str) + return 0; + + *upper = *lower = def; + errno = 0; + + if (*str == ':') { /* <:N> */ + str++; + *upper = strtol(str, &end, 10); + if (errno || !end || *end || end == str) + return -1; + } else { + *upper = *lower = strtol(str, &end, 10); + if (errno || !end || end == str) + return -1; + + if (*end == ':' && !*(end + 1)) /* */ + *upper = def; + else if (*end == '-' || *end == ':') { /* */ + str = end + 1; + end = NULL; + errno = 0; + *upper = strtol(str, &end, 10); + + if (errno || !end || *end || end == str) + return -1; + } + } + return 0; +} + +static const char *next_path_segment(const char *str, size_t *sz) +{ + const char *start, *p; + + start = str; + *sz = 0; + while (start && *start == '/' && *(start + 1) == '/') + start++; + + if (!start || !*start) + return NULL; + + for (*sz = 1, p = start + 1; *p && *p != '/'; p++) { + (*sz)++; + } + + return start; +} + +int streq_paths(const char *a, const char *b) +{ + while (a && b) { + size_t a_sz, b_sz; + const char *a_seg = next_path_segment(a, &a_sz); + const char *b_seg = next_path_segment(b, &b_sz); + + /* + fprintf(stderr, "A>>>(%zu) '%s'\n", a_sz, a_seg); + fprintf(stderr, "B>>>(%zu) '%s'\n", b_sz, b_seg); + */ + + /* end of the path */ + if (a_sz + b_sz == 0) + return 1; + + /* ignore tailing slash */ + if (a_sz + b_sz == 1 && + ((a_seg && *a_seg == '/') || (b_seg && *b_seg == '/'))) + return 1; + + if (!a_seg || !b_seg) + break; + if (a_sz != b_sz || strncmp(a_seg, b_seg, a_sz) != 0) + break; + + a = a_seg + a_sz; + b = b_seg + b_sz; + }; + + return 0; +} + +char *strnappend(const char *s, const char *suffix, size_t b) +{ + size_t a; + char *r; + + if (!s && !suffix) + return strdup(""); + if (!s) + return strndup(suffix, b); + if (!suffix) + return strdup(s); + + assert(s); + assert(suffix); + + a = strlen(s); + if (b > ((size_t) -1) - a) + return NULL; + + r = malloc(a + b + 1); + if (!r) + return NULL; + + memcpy(r, s, a); + memcpy(r + a, suffix, b); + r[a+b] = 0; + + return r; +} + +char *strappend(const char *s, const char *suffix) +{ + return strnappend(s, suffix, suffix ? strlen(suffix) : 0); +} + +char *strfappend(const char *s, const char *format, ...) +{ + va_list ap; + char *val, *res; + int sz; + + va_start(ap, format); + sz = vasprintf(&val, format, ap); + va_end(ap); + + if (sz < 0) + return NULL; + + res = strnappend(s, val, sz); + free(val); + return res; +} + +static size_t strcspn_escaped(const char *s, const char *reject) +{ + int escaped = 0; + int n; + + for (n=0; s[n]; n++) { + if (escaped) + escaped = 0; + else if (s[n] == '\\') + escaped = 1; + else if (strchr(reject, s[n])) + break; + } + + /* if s ends in \, return index of previous char */ + return n - escaped; +} + +/* Split a string into words. */ +const char *split(const char **state, size_t *l, const char *separator, int quoted) +{ + const char *current; + + current = *state; + + if (!*current) { + assert(**state == '\0'); + return NULL; + } + + current += strspn(current, separator); + if (!*current) { + *state = current; + return NULL; + } + + if (quoted && strchr("\'\"", *current)) { + char quotechars[2] = {*current, '\0'}; + + *l = strcspn_escaped(current + 1, quotechars); + if (current[*l + 1] == '\0' || current[*l + 1] != quotechars[0] || + (current[*l + 2] && !strchr(separator, current[*l + 2]))) { + /* right quote missing or garbage at the end */ + *state = current; + return NULL; + } + *state = current++ + *l + 2; + } else if (quoted) { + *l = strcspn_escaped(current, separator); + if (current[*l] && !strchr(separator, current[*l])) { + /* unfinished escape */ + *state = current; + return NULL; + } + *state = current + *l; + } else { + *l = strcspn(current, separator); + *state = current + *l; + } + + return current; +} + +/* Rewind file pointer forward to new line. */ +int skip_fline(FILE *fp) +{ + int ch; + + do { + if ((ch = fgetc(fp)) == EOF) + return 1; + if (ch == '\n') + return 0; + } while (1); +} + +#ifdef TEST_PROGRAM_STRUTILS +struct testS { + char *name; + char *value; +}; + +static int test_strdup_to_member(int argc, char *argv[]) +{ + struct testS *xx; + + if (argc < 3) + return EXIT_FAILURE; + + xx = calloc(1, sizeof(*xx)); + if (!xx) + err(EXIT_FAILURE, "calloc() failed"); + + strdup_to_struct_member(xx, name, argv[1]); + strdup_to_struct_member(xx, value, argv[2]); + + if (strcmp(xx->name, argv[1]) != 0 && + strcmp(xx->value, argv[2]) != 0) + errx(EXIT_FAILURE, "strdup_to_struct_member() failed"); + + printf("1: '%s', 2: '%s'\n", xx->name, xx->value); + + free(xx->name); + free(xx->value); + free(xx); + return EXIT_SUCCESS; +} + +static int test_strutils_sizes(int argc, char *argv[]) +{ + uintmax_t size = 0; + char *hum1, *hum2, *hum3; + + if (argc < 2) + return EXIT_FAILURE; + + if (strtosize(argv[1], &size)) + errx(EXIT_FAILURE, "invalid size '%s' value", argv[1]); + + hum1 = size_to_human_string(SIZE_SUFFIX_1LETTER, size); + hum2 = size_to_human_string(SIZE_SUFFIX_3LETTER | + SIZE_SUFFIX_SPACE, size); + hum3 = size_to_human_string(SIZE_SUFFIX_3LETTER | + SIZE_SUFFIX_SPACE | + SIZE_DECIMAL_2DIGITS, size); + + printf("%25s : %20ju : %8s : %12s : %13s\n", argv[1], size, hum1, hum2, hum3); + free(hum1); + free(hum2); + free(hum3); + + return EXIT_SUCCESS; +} + +static int test_strutils_cmp_paths(int argc, char *argv[]) +{ + int rc = streq_paths(argv[1], argv[2]); + + if (argc < 3) + return EXIT_FAILURE; + + printf("%s: '%s' '%s'\n", rc == 1 ? "YES" : "NOT", argv[1], argv[2]); + return EXIT_SUCCESS; +} + +int main(int argc, char *argv[]) +{ + if (argc == 3 && strcmp(argv[1], "--size") == 0) + return test_strutils_sizes(argc - 1, argv + 1); + + if (argc == 4 && strcmp(argv[1], "--cmp-paths") == 0) + return test_strutils_cmp_paths(argc - 1, argv + 1); + + if (argc == 4 && strcmp(argv[1], "--strdup-member") == 0) + return test_strdup_to_member(argc - 1, argv + 1); + + fprintf(stderr, "usage: %1$s --size [suffix]\n" + " %1$s --cmp-paths \n" + " %1$s --strdup-member \n", + argv[0]); + + return EXIT_FAILURE; +} +#endif /* TEST_PROGRAM_STRUTILS */ diff --git a/utils/lib/strv.c b/utils/lib/strv.c new file mode 100644 index 0000000..ddc2a0c --- /dev/null +++ b/utils/lib/strv.c @@ -0,0 +1,403 @@ +/* + * + * Copyright 2010 Lennart Poettering + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * + * Copyright (C) 2015 Karel Zak + * Modified the original version from systemd project for util-linux. + */ + +#include +#include +#include +#include +#include +#include + +#include "strutils.h" +#include "strv.h" + +void strv_clear(char **l) { + char **k; + + if (!l) + return; + + for (k = l; *k; k++) + free(*k); + + *l = NULL; +} + +char **strv_free(char **l) { + strv_clear(l); + free(l); + return NULL; +} + +char **strv_copy(char * const *l) { + char **r, **k; + + k = r = malloc(sizeof(char *) * (strv_length(l) + 1)); + if (!r) + return NULL; + + if (l) + for (; *l; k++, l++) { + *k = strdup(*l); + if (!*k) { + strv_free(r); + return NULL; + } + } + + *k = NULL; + return r; +} + +unsigned strv_length(char * const *l) { + unsigned n = 0; + + if (!l) + return 0; + + for (; *l; l++) + n++; + + return n; +} + +char **strv_new_ap(const char *x, va_list ap) { + const char *s; + char **a; + unsigned n = 0, i = 0; + va_list aq; + + /* As a special trick we ignore all listed strings that equal + * (const char*) -1. This is supposed to be used with the + * STRV_IFNOTNULL() macro to include possibly NULL strings in + * the string list. */ + + if (x) { + n = x == (const char*) -1 ? 0 : 1; + + va_copy(aq, ap); + while ((s = va_arg(aq, const char*))) { + if (s == (const char*) -1) + continue; + + n++; + } + + va_end(aq); + } + + a = malloc(sizeof(char *) * (n + 1)); + if (!a) + return NULL; + + if (x) { + if (x != (const char*) -1) { + a[i] = strdup(x); + if (!a[i]) + goto fail; + i++; + } + + while ((s = va_arg(ap, const char*))) { + + if (s == (const char*) -1) + continue; + + a[i] = strdup(s); + if (!a[i]) + goto fail; + + i++; + } + } + + a[i] = NULL; + + return a; + +fail: + strv_free(a); + return NULL; +} + +char **strv_new(const char *x, ...) { + char **r; + va_list ap; + + va_start(ap, x); + r = strv_new_ap(x, ap); + va_end(ap); + + return r; +} + +int strv_extend_strv(char ***a, char **b) { + int r; + char **s; + + STRV_FOREACH(s, b) { + r = strv_extend(a, *s); + if (r < 0) + return r; + } + + return 0; +} + +int strv_extend_strv_concat(char ***a, char **b, const char *suffix) { + int r; + char **s; + + STRV_FOREACH(s, b) { + char *v; + + v = strappend(*s, suffix); + if (!v) + return -ENOMEM; + + r = strv_push(a, v); + if (r < 0) { + free(v); + return r; + } + } + + return 0; +} + + +#define _FOREACH_WORD(word, length, s, separator, quoted, state) \ + for ((state) = (s), (word) = split(&(state), &(length), (separator), (quoted)); (word); (word) = split(&(state), &(length), (separator), (quoted))) + +#define FOREACH_WORD_SEPARATOR(word, length, s, separator, state) \ + _FOREACH_WORD(word, length, s, separator, false, state) + + +char **strv_split(const char *s, const char *separator) { + const char *word, *state; + size_t l; + unsigned n, i; + char **r; + + assert(s); + + n = 0; + FOREACH_WORD_SEPARATOR(word, l, s, separator, state) + n++; + + r = malloc(sizeof(char *) * (n + 1)); + if (!r) + return NULL; + + i = 0; + FOREACH_WORD_SEPARATOR(word, l, s, separator, state) { + r[i] = strndup(word, l); + if (!r[i]) { + strv_free(r); + return NULL; + } + + i++; + } + + r[i] = NULL; + return r; +} + +char *strv_join(char **l, const char *separator) { + char *r, *e; + char **s; + size_t n, k; + + if (!separator) + separator = " "; + + k = strlen(separator); + + n = 0; + STRV_FOREACH(s, l) { + if (n != 0) + n += k; + n += strlen(*s); + } + + r = malloc(n + 1); + if (!r) + return NULL; + + e = r; + STRV_FOREACH(s, l) { + if (e != r) + e = stpcpy(e, separator); + + e = stpcpy(e, *s); + } + + *e = 0; + + return r; +} + +int strv_push(char ***l, char *value) { + char **c; + unsigned n, m; + + if (!value) + return 0; + + n = strv_length(*l); + + /* Increase and check for overflow */ + m = n + 2; + if (m < n) + return -ENOMEM; + + c = realloc(*l, sizeof(char *) * m); + if (!c) + return -ENOMEM; + + c[n] = value; + c[n+1] = NULL; + + *l = c; + return 0; +} + +int strv_push_prepend(char ***l, char *value) { + char **c; + unsigned n, m, i; + + if (!value) + return 0; + + n = strv_length(*l); + + /* increase and check for overflow */ + m = n + 2; + if (m < n) + return -ENOMEM; + + c = malloc(sizeof(char *) * m); + if (!c) + return -ENOMEM; + + for (i = 0; i < n; i++) + c[i+1] = (*l)[i]; + + c[0] = value; + c[n+1] = NULL; + + free(*l); + *l = c; + + return 0; +} + +int strv_consume(char ***l, char *value) { + int r; + + r = strv_push(l, value); + if (r < 0) + free(value); + + return r; +} + +int strv_consume_prepend(char ***l, char *value) { + int r; + + r = strv_push_prepend(l, value); + if (r < 0) + free(value); + + return r; +} + +int strv_extend(char ***l, const char *value) { + char *v; + + if (!value) + return 0; + + v = strdup(value); + if (!v) + return -ENOMEM; + + return strv_consume(l, v); +} + +char **strv_remove(char **l, const char *s) { + char **f, **t; + + if (!l) + return NULL; + + assert(s); + + /* Drops every occurrence of s in the string list, edits + * in-place. */ + + for (f = t = l; *f; f++) + if (strcmp(*f, s) == 0) + free(*f); + else + *(t++) = *f; + + *t = NULL; + return l; +} + +int strv_extendf(char ***l, const char *format, ...) { + va_list ap; + char *x; + int r; + + va_start(ap, format); + r = vasprintf(&x, format, ap); + va_end(ap); + + if (r < 0) + return -ENOMEM; + + return strv_consume(l, x); +} + +int strv_extendv(char ***l, const char *format, va_list ap) { + char *x; + int r; + + r = vasprintf(&x, format, ap); + if (r < 0) + return -ENOMEM; + + return strv_consume(l, x); +} + +char **strv_reverse(char **l) { + unsigned n, i; + + n = strv_length(l); + if (n <= 1) + return l; + + for (i = 0; i < n / 2; i++) { + char *t; + + t = l[i]; + l[i] = l[n-1-i]; + l[n-1-i] = t; + } + + return l; +} diff --git a/utils/lib/sysfs.c b/utils/lib/sysfs.c new file mode 100644 index 0000000..5b4de2c --- /dev/null +++ b/utils/lib/sysfs.c @@ -0,0 +1,1127 @@ +/* + * No copyright is claimed. This code is in the public domain; do with + * it what you wish. + * + * Written by Karel Zak + */ +#include +#include +#include +#include +#include + +#include "c.h" +#include "pathnames.h" +#include "sysfs.h" +#include "fileutils.h" +#include "all-io.h" +#include "debug.h" +#include "strutils.h" + +static void sysfs_blkdev_deinit_path(struct path_cxt *pc); +static int sysfs_blkdev_enoent_redirect(struct path_cxt *pc, const char *path, int *dirfd); + +/* + * Debug stuff (based on include/debug.h) + */ +static UL_DEBUG_DEFINE_MASK(ulsysfs); +UL_DEBUG_DEFINE_MASKNAMES(ulsysfs) = UL_DEBUG_EMPTY_MASKNAMES; + +#define ULSYSFS_DEBUG_INIT (1 << 1) +#define ULSYSFS_DEBUG_CXT (1 << 2) + +#define DBG(m, x) __UL_DBG(ulsysfs, ULSYSFS_DEBUG_, m, x) +#define ON_DBG(m, x) __UL_DBG_CALL(ulsysfs, ULSYSFS_DEBUG_, m, x) + +#define UL_DEBUG_CURRENT_MASK UL_DEBUG_MASK(ulsysfs) +#include "debugobj.h" + +void ul_sysfs_init_debug(void) +{ + if (ulsysfs_debug_mask) + return; + __UL_INIT_DEBUG_FROM_ENV(ulsysfs, ULSYSFS_DEBUG_, 0, ULSYSFS_DEBUG); +} + +struct path_cxt *ul_new_sysfs_path(dev_t devno, struct path_cxt *parent, const char *prefix) +{ + struct path_cxt *pc = ul_new_path(NULL); + + if (!pc) + return NULL; + if (prefix) + ul_path_set_prefix(pc, prefix); + + if (sysfs_blkdev_init_path(pc, devno, parent) != 0) { + ul_unref_path(pc); + return NULL; + } + + DBG(CXT, ul_debugobj(pc, "alloc")); + return pc; +} + +/* + * sysfs_blkdev_* is sysfs extension to ul_path_* API for block devices. + * + * The function is possible to call in loop and without sysfs_blkdev_deinit_path(). + * The sysfs_blkdev_deinit_path() is automatically called by ul_unref_path(). + * + */ +int sysfs_blkdev_init_path(struct path_cxt *pc, dev_t devno, struct path_cxt *parent) +{ + struct sysfs_blkdev *blk; + int rc; + char buf[sizeof(_PATH_SYS_DEVBLOCK) + + sizeof(stringify_value(UINT32_MAX)) * 2 + + 3]; + + /* define path to devno stuff */ + snprintf(buf, sizeof(buf), _PATH_SYS_DEVBLOCK "/%d:%d", major(devno), minor(devno)); + rc = ul_path_set_dir(pc, buf); + if (rc) + return rc; + + /* make sure path exists */ + rc = ul_path_get_dirfd(pc); + if (rc < 0) + return rc; + + /* initialize sysfs blkdev specific stuff */ + blk = ul_path_get_dialect(pc); + if (!blk) { + DBG(CXT, ul_debugobj(pc, "alloc new sysfs handler")); + blk = calloc(1, sizeof(struct sysfs_blkdev)); + if (!blk) + return -ENOMEM; + + ul_path_set_dialect(pc, blk, sysfs_blkdev_deinit_path); + ul_path_set_enoent_redirect(pc, sysfs_blkdev_enoent_redirect); + } + + DBG(CXT, ul_debugobj(pc, "init sysfs stuff")); + + blk->devno = devno; + sysfs_blkdev_set_parent(pc, parent); + + return 0; +} + +static void sysfs_blkdev_deinit_path(struct path_cxt *pc) +{ + struct sysfs_blkdev *blk; + + if (!pc) + return; + + DBG(CXT, ul_debugobj(pc, "deinit")); + + blk = ul_path_get_dialect(pc); + if (!blk) + return; + + ul_unref_path(blk->parent); + free(blk); + + ul_path_set_dialect(pc, NULL, NULL); +} + +int sysfs_blkdev_set_parent(struct path_cxt *pc, struct path_cxt *parent) +{ + struct sysfs_blkdev *blk = ul_path_get_dialect(pc); + + if (!pc || !blk) + return -EINVAL; + + if (blk->parent) { + ul_unref_path(blk->parent); + blk->parent = NULL; + } + + if (parent) { + ul_ref_path(parent); + blk->parent = parent; + } else + blk->parent = NULL; + + DBG(CXT, ul_debugobj(pc, "new parent")); + return 0; +} + +struct path_cxt *sysfs_blkdev_get_parent(struct path_cxt *pc) +{ + struct sysfs_blkdev *blk = ul_path_get_dialect(pc); + return blk ? blk->parent : NULL; +} + +/* + * Redirects ENOENT errors to the parent, if the path is to the queue/ + * sysfs directory. For example + * + * /sys/dev/block/8:1/queue/logical_block_size redirects to + * /sys/dev/block/8:0/queue/logical_block_size + */ +static int sysfs_blkdev_enoent_redirect(struct path_cxt *pc, const char *path, int *dirfd) +{ + struct sysfs_blkdev *blk = ul_path_get_dialect(pc); + + if (blk && blk->parent && strncmp(path, "queue/", 6) == 0) { + *dirfd = ul_path_get_dirfd(blk->parent); + if (*dirfd >= 0) { + DBG(CXT, ul_debugobj(pc, "%s redirected to parent", path)); + return 0; + } + } + return 1; /* no redirect */ +} + +char *sysfs_blkdev_get_name(struct path_cxt *pc, char *buf, size_t bufsiz) +{ + char link[PATH_MAX]; + char *name; + ssize_t sz; + + /* read /sys/dev/block/ link */ + sz = ul_path_readlink(pc, link, sizeof(link) - 1, NULL); + if (sz < 0) + return NULL; + link[sz] = '\0'; + + name = strrchr(link, '/'); + if (!name) + return NULL; + + name++; + sz = strlen(name); + if ((size_t) sz + 1 > bufsiz) + return NULL; + + memcpy(buf, name, sz + 1); + sysfs_devname_sys_to_dev(buf); + return buf; +} + +int sysfs_blkdev_is_partition_dirent(DIR *dir, struct dirent *d, const char *parent_name) +{ + char path[NAME_MAX + 6 + 1]; + +#ifdef _DIRENT_HAVE_D_TYPE + if (d->d_type != DT_DIR && + d->d_type != DT_LNK && + d->d_type != DT_UNKNOWN) + return 0; +#endif + if (parent_name) { + const char *p = parent_name; + size_t len; + + /* /dev/sda --> "sda" */ + if (*parent_name == '/') { + p = strrchr(parent_name, '/'); + if (!p) + return 0; + p++; + } + + len = strlen(p); + if (strlen(d->d_name) <= len) + return 0; + + /* partitions subdir name is + * "[:digit:]" or "p[:digit:]" + */ + return strncmp(p, d->d_name, len) == 0 && + ((*(d->d_name + len) == 'p' && isdigit(*(d->d_name + len + 1))) + || isdigit(*(d->d_name + len))); + } + + /* Cannot use /partition file, not supported on old sysfs */ + snprintf(path, sizeof(path), "%s/start", d->d_name); + + return faccessat(dirfd(dir), path, R_OK, 0) == 0; +} + +int sysfs_blkdev_count_partitions(struct path_cxt *pc, const char *devname) +{ + DIR *dir; + struct dirent *d; + int r = 0; + + dir = ul_path_opendir(pc, NULL); + if (!dir) + return 0; + + while ((d = xreaddir(dir))) { + if (sysfs_blkdev_is_partition_dirent(dir, d, devname)) + r++; + } + + closedir(dir); + return r; +} + +/* + * Converts @partno (partition number) to devno of the partition. + * The @pc handles wholedisk device. + * + * Note that this code does not expect any special format of the + * partitions devnames. + */ +dev_t sysfs_blkdev_partno_to_devno(struct path_cxt *pc, int partno) +{ + DIR *dir; + struct dirent *d; + dev_t devno = 0; + + dir = ul_path_opendir(pc, NULL); + if (!dir) + return 0; + + while ((d = xreaddir(dir))) { + int n; + + if (!sysfs_blkdev_is_partition_dirent(dir, d, NULL)) + continue; + + if (ul_path_readf_s32(pc, &n, "%s/partition", d->d_name)) + continue; + + if (n == partno) { + if (ul_path_readf_majmin(pc, &devno, "%s/dev", d->d_name) == 0) + break; + } + } + + closedir(dir); + DBG(CXT, ul_debugobj(pc, "partno (%d) -> devno (%d)", (int) partno, (int) devno)); + return devno; +} + + +/* + * Returns slave name if there is only one slave, otherwise returns NULL. + * The result should be deallocated by free(). + */ +char *sysfs_blkdev_get_slave(struct path_cxt *pc) +{ + DIR *dir; + struct dirent *d; + char *name = NULL; + + dir = ul_path_opendir(pc, "slaves"); + if (!dir) + return NULL; + + while ((d = xreaddir(dir))) { + if (name) + goto err; /* more slaves */ + name = strdup(d->d_name); + } + + closedir(dir); + return name; +err: + free(name); + closedir(dir); + return NULL; +} + + +#define SUBSYSTEM_LINKNAME "/subsystem" + +/* + * For example: + * + * chain: /sys/dev/block/../../devices/pci0000:00/0000:00:1a.0/usb1/1-1/1-1.2/ \ + * 1-1.2:1.0/host65/target65:0:0/65:0:0:0/block/sdb + * + * The function check if /subsystem symlink exists, if yes then returns + * basename of the readlink result, and remove the last subdirectory from the + * path. + */ +static char *get_subsystem(char *chain, char *buf, size_t bufsz) +{ + size_t len; + char *p; + + if (!chain || !*chain) + return NULL; + + len = strlen(chain); + if (len + sizeof(SUBSYSTEM_LINKNAME) > PATH_MAX) + return NULL; + + do { + ssize_t sz; + + /* append "/subsystem" to the path */ + memcpy(chain + len, SUBSYSTEM_LINKNAME, sizeof(SUBSYSTEM_LINKNAME)); + + /* try if subsystem symlink exists */ + sz = readlink(chain, buf, bufsz - 1); + + /* remove last subsystem from chain */ + chain[len] = '\0'; + p = strrchr(chain, '/'); + if (p) { + *p = '\0'; + len = p - chain; + } + + if (sz > 0) { + /* we found symlink to subsystem, return basename */ + buf[sz] = '\0'; + return basename(buf); + } + + } while (p); + + return NULL; +} + +/* + * Returns complete path to the device, the patch contains all subsystems + * used for the device. + */ +char *sysfs_blkdev_get_devchain(struct path_cxt *pc, char *buf, size_t bufsz) +{ + /* read /sys/dev/block/: symlink */ + ssize_t sz = ul_path_readlink(pc, buf, bufsz, NULL); + const char *prefix; + size_t psz = 0; + + if (sz <= 0 || sz + sizeof(_PATH_SYS_DEVBLOCK "/") > bufsz) + return NULL; + + buf[sz++] = '\0'; + prefix = ul_path_get_prefix(pc); + if (prefix) + psz = strlen(prefix); + + /* create absolute patch from the link */ + memmove(buf + psz + sizeof(_PATH_SYS_DEVBLOCK "/") - 1, buf, sz); + if (prefix) + memcpy(buf, prefix, psz); + + memcpy(buf + psz, _PATH_SYS_DEVBLOCK "/", sizeof(_PATH_SYS_DEVBLOCK "/") - 1); + return buf; +} + +/* + * The @subsys returns the next subsystem in the chain. Function modifies + * @devchain string. + * + * Returns: 0 in success, <0 on error, 1 on end of chain + */ +int sysfs_blkdev_next_subsystem(struct path_cxt *pc __attribute__((unused)), + char *devchain, char **subsys) +{ + char subbuf[PATH_MAX]; + char *sub; + + if (!subsys || !devchain) + return -EINVAL; + + *subsys = NULL; + + while ((sub = get_subsystem(devchain, subbuf, sizeof(subbuf)))) { + *subsys = strdup(sub); + if (!*subsys) + return -ENOMEM; + return 0; + } + + return 1; +} + + +static int is_hotpluggable_subsystem(const char *name) +{ + static const char * const hotplug_subsystems[] = { + "usb", + "ieee1394", + "pcmcia", + "mmc", + "ccw" + }; + size_t i; + + for (i = 0; i < ARRAY_SIZE(hotplug_subsystems); i++) + if (strcmp(name, hotplug_subsystems[i]) == 0) + return 1; + + return 0; +} + +int sysfs_blkdev_is_hotpluggable(struct path_cxt *pc) +{ + char buf[PATH_MAX], *chain, *sub; + int rc = 0; + + + /* check /sys/dev/block/:/removable attribute */ + if (ul_path_read_s32(pc, &rc, "removable") == 0 && rc == 1) + return 1; + + chain = sysfs_blkdev_get_devchain(pc, buf, sizeof(buf)); + + while (chain && sysfs_blkdev_next_subsystem(pc, chain, &sub) == 0) { + rc = is_hotpluggable_subsystem(sub); + if (rc) { + free(sub); + break; + } + free(sub); + } + + return rc; +} + +static int get_dm_wholedisk(struct path_cxt *pc, char *diskname, + size_t len, dev_t *diskdevno) +{ + int rc = 0; + char *name; + + /* Note, sysfs_blkdev_get_slave() returns the first slave only, + * if there is more slaves, then return NULL + */ + name = sysfs_blkdev_get_slave(pc); + if (!name) + return -1; + + if (diskname && len) + xstrncpy(diskname, name, len); + + if (diskdevno) { + *diskdevno = __sysfs_devname_to_devno(ul_path_get_prefix(pc), name, NULL); + if (!*diskdevno) + rc = -1; + } + + free(name); + return rc; +} + +/* + * Returns by @diskdevno whole disk device devno and (optionally) by + * @diskname the whole disk device name. + */ +int sysfs_blkdev_get_wholedisk( struct path_cxt *pc, + char *diskname, + size_t len, + dev_t *diskdevno) +{ + int is_part = 0; + + if (!pc) + return -1; + + is_part = ul_path_access(pc, F_OK, "partition") == 0; + if (!is_part) { + /* + * Extra case for partitions mapped by device-mapper. + * + * All regular partitions (added by BLKPG ioctl or kernel PT + * parser) have the /sys/.../partition file. The partitions + * mapped by DM don't have such file, but they have "part" + * prefix in DM UUID. + */ + char *uuid = NULL, *tmp, *prefix; + + ul_path_read_string(pc, &uuid, "dm/uuid"); + tmp = uuid; + prefix = uuid ? strsep(&tmp, "-") : NULL; + + if (prefix && strncasecmp(prefix, "part", 4) == 0) + is_part = 1; + free(uuid); + + if (is_part && + get_dm_wholedisk(pc, diskname, len, diskdevno) == 0) + /* + * partitioned device, mapped by DM + */ + goto done; + + is_part = 0; + } + + if (!is_part) { + /* + * unpartitioned device + */ + if (diskname && !sysfs_blkdev_get_name(pc, diskname, len)) + goto err; + if (diskdevno) + *diskdevno = sysfs_blkdev_get_devno(pc); + + } else { + /* + * partitioned device + * - readlink /sys/dev/block/8:1 = ../../block/sda/sda1 + * - dirname ../../block/sda/sda1 = ../../block/sda + * - basename ../../block/sda = sda + */ + char linkpath[PATH_MAX]; + char *name; + ssize_t linklen; + + linklen = ul_path_readlink(pc, linkpath, sizeof(linkpath) - 1, NULL); + if (linklen < 0) + goto err; + linkpath[linklen] = '\0'; + + stripoff_last_component(linkpath); /* dirname */ + name = stripoff_last_component(linkpath); /* basename */ + if (!name) + goto err; + + sysfs_devname_sys_to_dev(name); + if (diskname && len) + xstrncpy(diskname, name, len); + + if (diskdevno) { + *diskdevno = __sysfs_devname_to_devno(ul_path_get_prefix(pc), name, NULL); + if (!*diskdevno) + goto err; + } + } + +done: + return 0; +err: + return -1; +} + +int sysfs_devno_to_wholedisk(dev_t devno, char *diskname, + size_t len, dev_t *diskdevno) +{ + struct path_cxt *pc; + int rc = 0; + + if (!devno) + return -EINVAL; + pc = ul_new_sysfs_path(devno, NULL, NULL); + if (!pc) + return -ENOMEM; + + rc = sysfs_blkdev_get_wholedisk(pc, diskname, len, diskdevno); + ul_unref_path(pc); + return rc; +} + +/* + * Returns 1 if the device is private device mapper device. The @uuid + * (if not NULL) returns DM device UUID, use free() to deallocate. + */ +int sysfs_devno_is_dm_private(dev_t devno, char **uuid) +{ + struct path_cxt *pc = NULL; + char *id = NULL; + int rc = 0; + + pc = ul_new_sysfs_path(devno, NULL, NULL); + if (!pc) + goto done; + if (ul_path_read_string(pc, &id, "dm/uuid") <= 0 || !id) + goto done; + + /* Private LVM devices use "LVM--" uuid format (important + * is the "LVM" prefix and "-" postfix). + */ + if (strncmp(id, "LVM-", 4) == 0) { + char *p = strrchr(id + 4, '-'); + + if (p && *(p + 1)) + rc = 1; + + /* Private Stratis devices prefix the UUID with "stratis-1-private" + */ + } else if (strncmp(id, "stratis-1-private", 17) == 0) { + rc = 1; + } +done: + ul_unref_path(pc); + if (uuid) + *uuid = id; + else + free(id); + return rc; +} + +/* + * Return 0 or 1, or < 0 in case of error + */ +int sysfs_devno_is_wholedisk(dev_t devno) +{ + dev_t disk; + + if (sysfs_devno_to_wholedisk(devno, NULL, 0, &disk) != 0) + return -1; + + return devno == disk; +} + + +int sysfs_blkdev_scsi_get_hctl(struct path_cxt *pc, int *h, int *c, int *t, int *l) +{ + char buf[PATH_MAX], *hctl; + struct sysfs_blkdev *blk; + ssize_t len; + + blk = ul_path_get_dialect(pc); + + if (!blk || blk->hctl_error) + return -EINVAL; + if (blk->has_hctl) + goto done; + + blk->hctl_error = 1; + len = ul_path_readlink(pc, buf, sizeof(buf) - 1, "device"); + if (len < 0) + return len; + + buf[len] = '\0'; + hctl = strrchr(buf, '/'); + if (!hctl) + return -1; + hctl++; + + if (sscanf(hctl, "%u:%u:%u:%u", &blk->scsi_host, &blk->scsi_channel, + &blk->scsi_target, &blk->scsi_lun) != 4) + return -1; + + blk->has_hctl = 1; +done: + if (h) + *h = blk->scsi_host; + if (c) + *c = blk->scsi_channel; + if (t) + *t = blk->scsi_target; + if (l) + *l = blk->scsi_lun; + + blk->hctl_error = 0; + return 0; +} + + +static char *scsi_host_attribute_path( + struct path_cxt *pc, + const char *type, + char *buf, + size_t bufsz, + const char *attr) +{ + int len; + int host; + const char *prefix; + + if (sysfs_blkdev_scsi_get_hctl(pc, &host, NULL, NULL, NULL)) + return NULL; + + prefix = ul_path_get_prefix(pc); + if (!prefix) + prefix = ""; + + if (attr) + len = snprintf(buf, bufsz, "%s%s/%s_host/host%d/%s", + prefix, _PATH_SYS_CLASS, type, host, attr); + else + len = snprintf(buf, bufsz, "%s%s/%s_host/host%d", + prefix, _PATH_SYS_CLASS, type, host); + + return (len < 0 || (size_t) len >= bufsz) ? NULL : buf; +} + +char *sysfs_blkdev_scsi_host_strdup_attribute(struct path_cxt *pc, + const char *type, const char *attr) +{ + char buf[1024]; + int rc; + FILE *f; + + if (!attr || !type || + !scsi_host_attribute_path(pc, type, buf, sizeof(buf), attr)) + return NULL; + + if (!(f = fopen(buf, "r" UL_CLOEXECSTR))) + return NULL; + + rc = fscanf(f, "%1023[^\n]", buf); + fclose(f); + + return rc == 1 ? strdup(buf) : NULL; +} + +int sysfs_blkdev_scsi_host_is(struct path_cxt *pc, const char *type) +{ + char buf[PATH_MAX]; + struct stat st; + + if (!type || !scsi_host_attribute_path(pc, type, + buf, sizeof(buf), NULL)) + return 0; + + return stat(buf, &st) == 0 && S_ISDIR(st.st_mode); +} + +static char *scsi_attribute_path(struct path_cxt *pc, + char *buf, size_t bufsz, const char *attr) +{ + int len, h, c, t, l; + const char *prefix; + + if (sysfs_blkdev_scsi_get_hctl(pc, &h, &c, &t, &l) != 0) + return NULL; + + prefix = ul_path_get_prefix(pc); + if (!prefix) + prefix = ""; + + if (attr) + len = snprintf(buf, bufsz, "%s%s/devices/%d:%d:%d:%d/%s", + prefix, _PATH_SYS_SCSI, + h,c,t,l, attr); + else + len = snprintf(buf, bufsz, "%s%s/devices/%d:%d:%d:%d", + prefix, _PATH_SYS_SCSI, + h,c,t,l); + return (len < 0 || (size_t) len >= bufsz) ? NULL : buf; +} + +int sysfs_blkdev_scsi_has_attribute(struct path_cxt *pc, const char *attr) +{ + char path[PATH_MAX]; + struct stat st; + + if (!scsi_attribute_path(pc, path, sizeof(path), attr)) + return 0; + + return stat(path, &st) == 0; +} + +int sysfs_blkdev_scsi_path_contains(struct path_cxt *pc, const char *pattern) +{ + char path[PATH_MAX], linkc[PATH_MAX]; + struct stat st; + ssize_t len; + + if (!scsi_attribute_path(pc, path, sizeof(path), NULL)) + return 0; + + if (stat(path, &st) != 0) + return 0; + + len = readlink(path, linkc, sizeof(linkc) - 1); + if (len < 0) + return 0; + + linkc[len] = '\0'; + return strstr(linkc, pattern) != NULL; +} + +static dev_t read_devno(const char *path) +{ + FILE *f; + int maj = 0, min = 0; + dev_t dev = 0; + + f = fopen(path, "r" UL_CLOEXECSTR); + if (!f) + return 0; + + if (fscanf(f, "%d:%d", &maj, &min) == 2) + dev = makedev(maj, min); + fclose(f); + return dev; +} + +int sysfs_devname_is_hidden(const char *prefix, const char *name) +{ + char buf[PATH_MAX]; + int rc = 0, hidden = 0, len; + FILE *f; + + if (strncmp("/dev/", name, 5) == 0) + return 0; + + if (!prefix) + prefix = ""; + /* + * Create path to /sys/block//hidden + */ + len = snprintf(buf, sizeof(buf), + "%s" _PATH_SYS_BLOCK "/%s/hidden", + prefix, name); + + if (len < 0 || (size_t) len + 1 > sizeof(buf)) + return 0; + + f = fopen(buf, "r" UL_CLOEXECSTR); + if (!f) + return 0; + + rc = fscanf(f, "%d", &hidden); + fclose(f); + + return rc == 1 ? hidden : 0; +} + + +dev_t __sysfs_devname_to_devno(const char *prefix, const char *name, const char *parent) +{ + char buf[PATH_MAX]; + char *_name = NULL; /* name as encoded in sysfs */ + dev_t dev = 0; + int len; + + if (!prefix) + prefix = ""; + + assert(name); + + if (strncmp("/dev/", name, 5) == 0) { + /* + * Read from /dev + */ + struct stat st; + + if (stat(name, &st) == 0) { + dev = st.st_rdev; + goto done; + } + name += 5; /* unaccessible, or not node in /dev */ + } + + _name = strdup(name); + if (!_name) + goto done; + sysfs_devname_dev_to_sys(_name); + + if (parent && strncmp("dm-", name, 3) != 0) { + /* + * Create path to /sys/block///dev + */ + char *_parent = strdup(parent); + + if (!_parent) { + free(_parent); + goto done; + } + sysfs_devname_dev_to_sys(_parent); + len = snprintf(buf, sizeof(buf), + "%s" _PATH_SYS_BLOCK "/%s/%s/dev", + prefix, _parent, _name); + free(_parent); + if (len < 0 || (size_t) len >= sizeof(buf)) + goto done; + + /* don't try anything else for dm-* */ + dev = read_devno(buf); + goto done; + } + + /* + * Read from /sys/block//dev + */ + len = snprintf(buf, sizeof(buf), + "%s" _PATH_SYS_BLOCK "/%s/dev", + prefix, _name); + if (len < 0 || (size_t) len >= sizeof(buf)) + goto done; + dev = read_devno(buf); + + if (!dev) { + /* + * Read from /sys/block//device/dev + */ + len = snprintf(buf, sizeof(buf), + "%s" _PATH_SYS_BLOCK "/%s/device/dev", + prefix, _name); + if (len < 0 || (size_t) len >= sizeof(buf)) + goto done; + dev = read_devno(buf); + } +done: + free(_name); + return dev; +} + +dev_t sysfs_devname_to_devno(const char *name) +{ + return __sysfs_devname_to_devno(NULL, name, NULL); +} + +char *sysfs_blkdev_get_path(struct path_cxt *pc, char *buf, size_t bufsiz) +{ + const char *name = sysfs_blkdev_get_name(pc, buf, bufsiz); + char *res = NULL; + size_t sz; + struct stat st; + + if (!name) + goto done; + + sz = strlen(name); + if (sz + sizeof("/dev/") > bufsiz) + goto done; + + /* create the final "/dev/" string */ + memmove(buf + 5, name, sz + 1); + memcpy(buf, "/dev/", 5); + + if (!stat(buf, &st) && S_ISBLK(st.st_mode) && st.st_rdev == sysfs_blkdev_get_devno(pc)) + res = buf; +done: + return res; +} + +dev_t sysfs_blkdev_get_devno(struct path_cxt *pc) +{ + return ((struct sysfs_blkdev *) ul_path_get_dialect(pc))->devno; +} + +/* + * Returns devname (e.g. "/dev/sda1") for the given devno. + * + * Please, use more robust blkid_devno_to_devname() in your applications. + */ +char *sysfs_devno_to_devpath(dev_t devno, char *buf, size_t bufsiz) +{ + struct path_cxt *pc = ul_new_sysfs_path(devno, NULL, NULL); + char *res = NULL; + + if (pc) { + res = sysfs_blkdev_get_path(pc, buf, bufsiz); + ul_unref_path(pc); + } + return res; +} + +char *sysfs_devno_to_devname(dev_t devno, char *buf, size_t bufsiz) +{ + struct path_cxt *pc = ul_new_sysfs_path(devno, NULL, NULL); + char *res = NULL; + + if (pc) { + res = sysfs_blkdev_get_name(pc, buf, bufsiz); + ul_unref_path(pc); + } + return res; +} + +int sysfs_devno_count_partitions(dev_t devno) +{ + struct path_cxt *pc = ul_new_sysfs_path(devno, NULL, NULL); + int n = 0; + + if (pc) { + char buf[PATH_MAX + 1]; + char *name = sysfs_blkdev_get_name(pc, buf, sizeof(buf)); + + n = sysfs_blkdev_count_partitions(pc, name); + ul_unref_path(pc); + } + return n; +} + + +#ifdef TEST_PROGRAM_SYSFS +#include +#include +#include + +int main(int argc, char *argv[]) +{ + struct path_cxt *pc; + char *devname; + dev_t devno, disk_devno; + char path[PATH_MAX], *sub, *chain; + char diskname[32]; + int i, is_part, rc = EXIT_SUCCESS; + uint64_t u64; + + if (argc != 2) + errx(EXIT_FAILURE, "usage: %s ", argv[0]); + + ul_sysfs_init_debug(); + + devname = argv[1]; + devno = sysfs_devname_to_devno(devname); + + if (!devno) + err(EXIT_FAILURE, "failed to read devno"); + + printf("non-context:\n"); + printf(" DEVNO: %u (%d:%d)\n", (unsigned int) devno, major(devno), minor(devno)); + printf(" DEVNAME: %s\n", sysfs_devno_to_devname(devno, path, sizeof(path))); + printf(" DEVPATH: %s\n", sysfs_devno_to_devpath(devno, path, sizeof(path))); + + sysfs_devno_to_wholedisk(devno, diskname, sizeof(diskname), &disk_devno); + printf(" WHOLEDISK-DEVNO: %u (%d:%d)\n", (unsigned int) disk_devno, major(disk_devno), minor(disk_devno)); + printf(" WHOLEDISK-DEVNAME: %s\n", diskname); + + pc = ul_new_sysfs_path(devno, NULL, NULL); + if (!pc) + goto done; + + printf("context based:\n"); + devno = sysfs_blkdev_get_devno(pc); + printf(" DEVNO: %u (%d:%d)\n", (unsigned int) devno, major(devno), minor(devno)); + printf(" DEVNAME: %s\n", sysfs_blkdev_get_name(pc, path, sizeof(path))); + printf(" DEVPATH: %s\n", sysfs_blkdev_get_path(pc, path, sizeof(path))); + + sysfs_devno_to_wholedisk(devno, diskname, sizeof(diskname), &disk_devno); + printf(" WHOLEDISK-DEVNO: %u (%d:%d)\n", (unsigned int) disk_devno, major(disk_devno), minor(disk_devno)); + printf(" WHOLEDISK-DEVNAME: %s\n", diskname); + + is_part = ul_path_access(pc, F_OK, "partition") == 0; + printf(" PARTITION: %s\n", is_part ? "YES" : "NOT"); + + if (is_part && disk_devno) { + struct path_cxt *disk_pc = ul_new_sysfs_path(disk_devno, NULL, NULL); + sysfs_blkdev_set_parent(pc, disk_pc); + + ul_unref_path(disk_pc); + } + + printf(" HOTPLUG: %s\n", sysfs_blkdev_is_hotpluggable(pc) ? "yes" : "no"); + printf(" SLAVES: %d\n", ul_path_count_dirents(pc, "slaves")); + + if (!is_part) { + printf("First 5 partitions:\n"); + for (i = 1; i <= 5; i++) { + dev_t dev = sysfs_blkdev_partno_to_devno(pc, i); + if (dev) + printf("\t#%d %d:%d\n", i, major(dev), minor(dev)); + } + } + + if (ul_path_read_u64(pc, &u64, "size") != 0) + printf(" (!) read SIZE failed\n"); + else + printf(" SIZE: %jd\n", u64); + + if (ul_path_read_s32(pc, &i, "queue/hw_sector_size")) + printf(" (!) read SECTOR failed\n"); + else + printf(" SECTOR: %d\n", i); + + + chain = sysfs_blkdev_get_devchain(pc, path, sizeof(path)); + printf(" SUBSUSTEMS:\n"); + + while (chain && sysfs_blkdev_next_subsystem(pc, chain, &sub) == 0) { + printf("\t%s\n", sub); + free(sub); + } + + rc = EXIT_SUCCESS; +done: + ul_unref_path(pc); + return rc; +} +#endif /* TEST_PROGRAM_SYSFS */ diff --git a/utils/lib/timer.c b/utils/lib/timer.c new file mode 100644 index 0000000..c1ea54e --- /dev/null +++ b/utils/lib/timer.c @@ -0,0 +1,95 @@ +/* + * Please, don't add this file to libcommon because timers requires + * -lrt on systems with old libc (and probably also -lpthread for static + * build). + */ +#include +#include +#include + +#include "c.h" +#include "timer.h" + +/* + * Note the timeout is used for the first signal, then the signal is send + * repeatedly in interval ~1% of the original timeout to avoid race in signal + * handling -- for example you want to use timer to define timeout for a + * syscall: + * + * setup_timer() + * syscall() + * cancel_timer() + * + * if the timeout is too short than it's possible that the signal is delivered + * before application enter the syscall function. For this reason timer send + * the signal repeatedly. + * + * The applications need to ensure that they can tolerate multiple signal + * deliveries. + */ +#ifdef HAVE_TIMER_CREATE +int setup_timer(struct ul_timer *timer, + struct itimerval *timeout, + void (*timeout_handler)(int, siginfo_t *, void *)) +{ + time_t sec = timeout->it_value.tv_sec; + long usec = timeout->it_value.tv_usec; + struct sigaction sig_a; + static struct sigevent sig_e = { + .sigev_notify = SIGEV_SIGNAL, + .sigev_signo = SIGALRM + }; + struct itimerspec val = { + .it_value.tv_sec = sec, + .it_value.tv_nsec = usec * 1000, + .it_interval.tv_sec = sec / 100, + .it_interval.tv_nsec = (sec ? sec % 100 : 1) * 10*1000*1000 + }; + + if (sigemptyset(&sig_a.sa_mask)) + return 1; + + sig_a.sa_flags = SA_SIGINFO; + sig_a.sa_sigaction = timeout_handler; + + if (sigaction(SIGALRM, &sig_a, NULL)) + return 1; + if (timer_create(CLOCK_MONOTONIC, &sig_e, &timer->t_id)) + return 1; + if (timer_settime(timer->t_id, 0, &val, NULL)) + return 1; + return 0; +} +void cancel_timer(struct ul_timer *timer) +{ + timer_delete(timer->t_id); +} + +#else /* !HAVE_TIMER_CREATE */ + +int setup_timer(struct ul_timer *timer, + struct itimerval *timeout, + void (*timeout_handler)(int, siginfo_t *, void *)) +{ + struct sigaction sa; + + memset(&sa, 0, sizeof sa); + memset(timer, 0, sizeof(*timer)); + + sa.sa_flags = SA_SIGINFO | SA_RESETHAND; + sa.sa_sigaction = timeout_handler; + + if (sigaction(SIGALRM, &sa, &timer->old_sa)) + return 1; + if (setitimer(ITIMER_REAL, timeout, &timer->old_timer) != 0) + return 1; + return 0; +} + +void cancel_timer(struct ul_timer *timer) +{ + setitimer(ITIMER_REAL, &timer->old_timer, NULL); + sigaction(SIGALRM, &timer->old_sa, NULL); + +} +#endif /* !HAVE_TIMER_CREATE */ diff --git a/utils/lib/timeutils.c b/utils/lib/timeutils.c new file mode 100644 index 0000000..8b443cd --- /dev/null +++ b/utils/lib/timeutils.c @@ -0,0 +1,611 @@ +/*** + First set of functions in this file are part of systemd, and were + copied to util-linux at August 2013. + + Copyright 2010 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with util-linux; If not, see . +***/ + +#include +#include +#include +#include +#include +#include + +#include "c.h" +#include "nls.h" +#include "strutils.h" +#include "timeutils.h" + +#define WHITESPACE " \t\n\r" + +#define streq(a,b) (strcmp((a),(b)) == 0) + +static int parse_sec(const char *t, usec_t *usec) +{ + static const struct { + const char *suffix; + usec_t usec; + } table[] = { + { "seconds", USEC_PER_SEC }, + { "second", USEC_PER_SEC }, + { "sec", USEC_PER_SEC }, + { "s", USEC_PER_SEC }, + { "minutes", USEC_PER_MINUTE }, + { "minute", USEC_PER_MINUTE }, + { "min", USEC_PER_MINUTE }, + { "months", USEC_PER_MONTH }, + { "month", USEC_PER_MONTH }, + { "msec", USEC_PER_MSEC }, + { "ms", USEC_PER_MSEC }, + { "m", USEC_PER_MINUTE }, + { "hours", USEC_PER_HOUR }, + { "hour", USEC_PER_HOUR }, + { "hr", USEC_PER_HOUR }, + { "h", USEC_PER_HOUR }, + { "days", USEC_PER_DAY }, + { "day", USEC_PER_DAY }, + { "d", USEC_PER_DAY }, + { "weeks", USEC_PER_WEEK }, + { "week", USEC_PER_WEEK }, + { "w", USEC_PER_WEEK }, + { "years", USEC_PER_YEAR }, + { "year", USEC_PER_YEAR }, + { "y", USEC_PER_YEAR }, + { "usec", 1ULL }, + { "us", 1ULL }, + { "", USEC_PER_SEC }, /* default is sec */ + }; + + const char *p; + usec_t r = 0; + int something = FALSE; + + assert(t); + assert(usec); + + p = t; + for (;;) { + long long l, z = 0; + char *e; + unsigned i, n = 0; + + p += strspn(p, WHITESPACE); + + if (*p == 0) { + if (!something) + return -EINVAL; + + break; + } + + errno = 0; + l = strtoll(p, &e, 10); + + if (errno > 0) + return -errno; + + if (l < 0) + return -ERANGE; + + if (*e == '.') { + char *b = e + 1; + + errno = 0; + z = strtoll(b, &e, 10); + if (errno > 0) + return -errno; + + if (z < 0) + return -ERANGE; + + if (e == b) + return -EINVAL; + + n = e - b; + + } else if (e == p) + return -EINVAL; + + e += strspn(e, WHITESPACE); + + for (i = 0; i < ARRAY_SIZE(table); i++) + if (startswith(e, table[i].suffix)) { + usec_t k = (usec_t) z * table[i].usec; + + for (; n > 0; n--) + k /= 10; + + r += (usec_t) l *table[i].usec + k; + p = e + strlen(table[i].suffix); + + something = TRUE; + break; + } + + if (i >= ARRAY_SIZE(table)) + return -EINVAL; + + } + + *usec = r; + + return 0; +} + +int parse_timestamp(const char *t, usec_t *usec) +{ + static const struct { + const char *name; + const int nr; + } day_nr[] = { + { "Sunday", 0 }, + { "Sun", 0 }, + { "Monday", 1 }, + { "Mon", 1 }, + { "Tuesday", 2 }, + { "Tue", 2 }, + { "Wednesday", 3 }, + { "Wed", 3 }, + { "Thursday", 4 }, + { "Thu", 4 }, + { "Friday", 5 }, + { "Fri", 5 }, + { "Saturday", 6 }, + { "Sat", 6 }, + }; + + const char *k; + struct tm tm, copy; + time_t x; + usec_t plus = 0, minus = 0, ret; + int r, weekday = -1; + unsigned i; + + /* + * Allowed syntaxes: + * + * 2012-09-22 16:34:22 + * 2012-09-22T16:34:22 + * 2012-09-22 16:34 (seconds will be set to 0) + * 2012-09-22 (time will be set to 00:00:00) + * 16:34:22 (date will be set to today) + * 16:34 (date will be set to today, seconds to 0) + * now + * yesterday (time is set to 00:00:00) + * today (time is set to 00:00:00) + * tomorrow (time is set to 00:00:00) + * +5min + * -5days + * + */ + + assert(t); + assert(usec); + + x = time(NULL); + localtime_r(&x, &tm); + tm.tm_isdst = -1; + + if (streq(t, "now")) + goto finish; + + else if (streq(t, "today")) { + tm.tm_sec = tm.tm_min = tm.tm_hour = 0; + goto finish; + + } else if (streq(t, "yesterday")) { + tm.tm_mday--; + tm.tm_sec = tm.tm_min = tm.tm_hour = 0; + goto finish; + + } else if (streq(t, "tomorrow")) { + tm.tm_mday++; + tm.tm_sec = tm.tm_min = tm.tm_hour = 0; + goto finish; + + } else if (t[0] == '+') { + + r = parse_sec(t + 1, &plus); + if (r < 0) + return r; + + goto finish; + } else if (t[0] == '-') { + + r = parse_sec(t + 1, &minus); + if (r < 0) + return r; + + goto finish; + + } else if (endswith(t, " ago")) { + char *z; + + z = strndup(t, strlen(t) - 4); + if (!z) + return -ENOMEM; + + r = parse_sec(z, &minus); + free(z); + if (r < 0) + return r; + + goto finish; + } + + for (i = 0; i < ARRAY_SIZE(day_nr); i++) { + size_t skip; + + if (!startswith_no_case(t, day_nr[i].name)) + continue; + + skip = strlen(day_nr[i].name); + if (t[skip] != ' ') + continue; + + weekday = day_nr[i].nr; + t += skip + 1; + break; + } + + copy = tm; + k = strptime(t, "%y-%m-%d %H:%M:%S", &tm); + if (k && *k == 0) + goto finish; + + tm = copy; + k = strptime(t, "%Y-%m-%d %H:%M:%S", &tm); + if (k && *k == 0) + goto finish; + + tm = copy; + k = strptime(t, "%Y-%m-%dT%H:%M:%S", &tm); + if (k && *k == 0) + goto finish; + + tm = copy; + k = strptime(t, "%y-%m-%d %H:%M", &tm); + if (k && *k == 0) { + tm.tm_sec = 0; + goto finish; + } + + tm = copy; + k = strptime(t, "%Y-%m-%d %H:%M", &tm); + if (k && *k == 0) { + tm.tm_sec = 0; + goto finish; + } + + tm = copy; + k = strptime(t, "%y-%m-%d", &tm); + if (k && *k == 0) { + tm.tm_sec = tm.tm_min = tm.tm_hour = 0; + goto finish; + } + + tm = copy; + k = strptime(t, "%Y-%m-%d", &tm); + if (k && *k == 0) { + tm.tm_sec = tm.tm_min = tm.tm_hour = 0; + goto finish; + } + + tm = copy; + k = strptime(t, "%H:%M:%S", &tm); + if (k && *k == 0) + goto finish; + + tm = copy; + k = strptime(t, "%H:%M", &tm); + if (k && *k == 0) { + tm.tm_sec = 0; + goto finish; + } + + tm = copy; + k = strptime(t, "%Y%m%d%H%M%S", &tm); + if (k && *k == 0) { + tm.tm_sec = 0; + goto finish; + } + + return -EINVAL; + + finish: + x = mktime(&tm); + if (x == (time_t)-1) + return -EINVAL; + + if (weekday >= 0 && tm.tm_wday != weekday) + return -EINVAL; + + ret = (usec_t) x *USEC_PER_SEC; + + ret += plus; + if (ret > minus) + ret -= minus; + else + ret = 0; + + *usec = ret; + + return 0; +} + +/* Returns the difference in seconds between its argument and GMT. If if TP is + * invalid or no DST information is available default to UTC, that is, zero. + * tzset is called so, for example, 'TZ="UTC" hwclock' will work as expected. + * Derived from glibc/time/strftime_l.c + */ +int get_gmtoff(const struct tm *tp) +{ + if (tp->tm_isdst < 0) + return 0; + +#if HAVE_TM_GMTOFF + return tp->tm_gmtoff; +#else + struct tm tm; + struct tm gtm; + struct tm ltm = *tp; + time_t lt; + + tzset(); + lt = mktime(<m); + /* Check if mktime returning -1 is an error or a valid time_t */ + if (lt == (time_t) -1) { + if (! localtime_r(<, &tm) + || ((ltm.tm_sec ^ tm.tm_sec) + | (ltm.tm_min ^ tm.tm_min) + | (ltm.tm_hour ^ tm.tm_hour) + | (ltm.tm_mday ^ tm.tm_mday) + | (ltm.tm_mon ^ tm.tm_mon) + | (ltm.tm_year ^ tm.tm_year))) + return 0; + } + + if (! gmtime_r(<, >m)) + return 0; + + /* Calculate the GMT offset, that is, the difference between the + * TP argument (ltm) and GMT (gtm). + * + * Compute intervening leap days correctly even if year is negative. + * Take care to avoid int overflow in leap day calculations, but it's OK + * to assume that A and B are close to each other. + */ + int a4 = (ltm.tm_year >> 2) + (1900 >> 2) - ! (ltm.tm_year & 3); + int b4 = (gtm.tm_year >> 2) + (1900 >> 2) - ! (gtm.tm_year & 3); + int a100 = a4 / 25 - (a4 % 25 < 0); + int b100 = b4 / 25 - (b4 % 25 < 0); + int a400 = a100 >> 2; + int b400 = b100 >> 2; + int intervening_leap_days = (a4 - b4) - (a100 - b100) + (a400 - b400); + + int years = ltm.tm_year - gtm.tm_year; + int days = (365 * years + intervening_leap_days + + (ltm.tm_yday - gtm.tm_yday)); + + return (60 * (60 * (24 * days + (ltm.tm_hour - gtm.tm_hour)) + + (ltm.tm_min - gtm.tm_min)) + (ltm.tm_sec - gtm.tm_sec)); +#endif +} + +static int format_iso_time(struct tm *tm, suseconds_t usec, int flags, char *buf, size_t bufsz) +{ + char *p = buf; + int len; + + if (flags & ISO_DATE) { + len = snprintf(p, bufsz, "%4ld-%.2d-%.2d", + tm->tm_year + (long) 1900, + tm->tm_mon + 1, tm->tm_mday); + if (len < 0 || (size_t) len > bufsz) + goto err; + bufsz -= len; + p += len; + } + + if ((flags & ISO_DATE) && (flags & ISO_TIME)) { + if (bufsz < 1) + goto err; + *p++ = (flags & ISO_T) ? 'T' : ' '; + bufsz--; + } + + if (flags & ISO_TIME) { + len = snprintf(p, bufsz, "%02d:%02d:%02d", tm->tm_hour, + tm->tm_min, tm->tm_sec); + if (len < 0 || (size_t) len > bufsz) + goto err; + bufsz -= len; + p += len; + } + + if (flags & ISO_DOTUSEC) { + len = snprintf(p, bufsz, ".%06ld", (long) usec); + if (len < 0 || (size_t) len > bufsz) + goto err; + bufsz -= len; + p += len; + + } else if (flags & ISO_COMMAUSEC) { + len = snprintf(p, bufsz, ",%06ld", (long) usec); + if (len < 0 || (size_t) len > bufsz) + goto err; + bufsz -= len; + p += len; + } + + if (flags & ISO_TIMEZONE) { + int tmin = get_gmtoff(tm) / 60; + int zhour = tmin / 60; + int zmin = abs(tmin % 60); + len = snprintf(p, bufsz, "%+03d:%02d", zhour,zmin); + if (len < 0 || (size_t) len > bufsz) + goto err; + } + return 0; + err: + warnx(_("format_iso_time: buffer overflow.")); + return -1; +} + +/* timeval to ISO 8601 */ +int strtimeval_iso(struct timeval *tv, int flags, char *buf, size_t bufsz) +{ + struct tm tm; + struct tm *rc; + + if (flags & ISO_GMTIME) + rc = gmtime_r(&tv->tv_sec, &tm); + else + rc = localtime_r(&tv->tv_sec, &tm); + + if (rc) + return format_iso_time(&tm, tv->tv_usec, flags, buf, bufsz); + + warnx(_("time %ld is out of range."), tv->tv_sec); + return -1; +} + +/* struct tm to ISO 8601 */ +int strtm_iso(struct tm *tm, int flags, char *buf, size_t bufsz) +{ + return format_iso_time(tm, 0, flags, buf, bufsz); +} + +/* time_t to ISO 8601 */ +int strtime_iso(const time_t *t, int flags, char *buf, size_t bufsz) +{ + struct tm tm; + struct tm *rc; + + if (flags & ISO_GMTIME) + rc = gmtime_r(t, &tm); + else + rc = localtime_r(t, &tm); + + if (rc) + return format_iso_time(&tm, 0, flags, buf, bufsz); + + warnx(_("time %ld is out of range."), (long)t); + return -1; +} + +/* relative time functions */ +static inline int time_is_thisyear(struct tm const *const tm, + struct tm const *const tmnow) +{ + return tm->tm_year == tmnow->tm_year; +} + +static inline int time_is_today(struct tm const *const tm, + struct tm const *const tmnow) +{ + return (tm->tm_yday == tmnow->tm_yday && + time_is_thisyear(tm, tmnow)); +} + +int strtime_short(const time_t *t, struct timeval *now, int flags, char *buf, size_t bufsz) +{ + struct tm tm, tmnow; + int rc = 0; + + if (now->tv_sec == 0) + gettimeofday(now, NULL); + + localtime_r(t, &tm); + localtime_r(&now->tv_sec, &tmnow); + + if (time_is_today(&tm, &tmnow)) { + rc = snprintf(buf, bufsz, "%02d:%02d", tm.tm_hour, tm.tm_min); + if (rc < 0 || (size_t) rc > bufsz) + return -1; + rc = 1; + + } else if (time_is_thisyear(&tm, &tmnow)) { + if (flags & UL_SHORTTIME_THISYEAR_HHMM) + rc = strftime(buf, bufsz, "%b%d/%H:%M", &tm); + else + rc = strftime(buf, bufsz, "%b%d", &tm); + } else + rc = strftime(buf, bufsz, "%Y-%b%d", &tm); + + return rc <= 0 ? -1 : 0; +} + +#ifndef HAVE_TIMEGM +time_t timegm(struct tm *tm) +{ + const char *zone = getenv("TZ"); + time_t ret; + + setenv("TZ", "", 1); + tzset(); + ret = mktime(tm); + if (zone) + setenv("TZ", zone, 1); + else + unsetenv("TZ"); + tzset(); + return ret; +} +#endif /* HAVE_TIMEGM */ + +#ifdef TEST_PROGRAM_TIMEUTILS + +int main(int argc, char *argv[]) +{ + struct timeval tv = { 0 }; + char buf[ISO_BUFSIZ]; + + if (argc < 2) { + fprintf(stderr, "usage: %s [