mirror of
https://github.com/freebsd/freebsd-src.git
synced 2026-10-29 11:54:38 +01:00
zfs: merge openzfs/zfs@84aa7e7e0
Notable upstream pull request merges: #16761eb1738bcbFreeBSD: Enable Direct IO by default #16747 -multiple On-demand log-spacemap flush #184740d0eae2abimplement thorough scrub support (zpool scrub -t) #185626721ab981Calling thread IO #186577ed1268d3Fix race between device removal completion and pool export #187070d1f3b1c6RAIDZ: Fix parity regeneration/check condition #18713 -multiple libzfs: fix key unload failure when unmounting an encryption root #18714d902eec64Disconnect metaslab tracing from default builds #18716e78fa488azstream: multithreading #187186acb99cb1Do not return ESTALE for open-unlinked files #18720f607ef7e7Fix insufficient locking in dedup verify #18722 -multiple snapdir: misc cleanups #187249bf75b4b1Fix reads for blocks freed after being cloned #187259b7642df9Harden recv record validation #1873237f066e3cFreeBSD: Wire sha512 offload to the build #18736 -multiple Remove idmap/userns concept from core & FreeBSD platform code #18742d63f14057libzutil: keep valid spare and l2cache paths on import #18749f217627d4Fix receive of split large blocks with a short trailing chunk #1875483bf40784Use a single creation time for a recursive snapshot #18755ebd9e2765zstream: add "drop records" chain module #187637d565cdfazfs bookmark: add recursive (-r) bookmark creation #1876550557cc80zpool initialize: add -z to write zeroes #187660467ba01cAdd SECURITY.md policy filE #18773ffde77051Fix deduce_nblkptr for large dnodes on receive #18784 -multiple libzfs: clarify the raw incremental "IV set guid mismatch" error #187939a02e53adlibzfs: don't abort receiving a raw encrypted send that carries holds #1879530426217bRate limit Direct I/O verify zevents #1879873b202451Bound explicit user prefetch to a fraction of the ARC #188012a5331f8eFix dmu_zfetch_prime() assuming a stream was created #18802fb5fdefd8libzfs: fallback VDEV_UPATH to VDEV_PATH for non-DM devices #18809bd2d87399zdb: output refcounts from verify_spacemap_refcounts() #188115d530b8beABD: Validate borrowed buffer length #188125d687a975dbuf: use dirty record size for overridden writes #18819 -multiple ddt: fix refcount bypass and gang member leak for dedup gang blocks #1882187e2e4047BRT: Implement partial bv_entcount writing #18822 -multiple dnode_sync: Relax constraint on indirect freeing - #18822 #18827d98fa72caL2ARC: bound the rebuild by the write hand on a first sweep #1883190cdd9aefBatch object reallocation syncs in zfs receive #18833466d90e15zed: let autoexpand see capacity changes on partitioned disks #188351ac3f2786mmp: skip non-writeable vdevs during activity check #18838a04c40138DDT: Fix several bugs in pruning #188410bb41751fzpool export: return EBUSY when zvol minors are in use #188483020c18cadmu_recv: Avoid potential null deref #188558cdd9b2b7mmp: do not require writes to mirror legs the config marks absent #18858596c7a0ecReduce dp_lock scope for MOS writes #188593ebf3ffc4Predict and throttle buffers dirtied by the sync context #18860 -multiple Further parallelize block cloning #18865412b17a29Fix DMU bonus hold leak on I/O error #18867b5bb1f816Add missing checks to zfs_clone_range_replay() #188683df5b2e7flibzfs: String trimming should not operate out of bounds #18869be62c5385libzfs: Do not call munmap() when mmap() fails #1887174e76f2d8libzfs: don't truncate a resolved vdev path in zpool_vdev_name() #188747312322b5nvpair: Fix operator precedence #188833bd8cefdclibzfs: don't read a dataset handle after closing it in resume send #18886b4f70cb9eFix negative time overflows in DDT pruning #18888674b1ae5cDDT: Make ddt_zap_walk() use cursor _by_dnode functions #18889fd1ae59c7DDT: Skip DDT log lookups in ddt_prune_walk() #18892 -multiple zhack: add "mmp reclaim" to recover a pool stranded by MMP #18897216de07d8arc: fix race between arc_release() and arc_read_done() #1889928afe8b08CodeQL: Flag implicit compare-then-assign in branch conditions #189170a79c039farc: save on buf_hash_find() call at arc_read_done() #189207026d3335Fix race condition in raidz expansion startup #18927be55e01bcFreeBSD: Do not leak TSD zfs_geom_probe_vdev_key on errors #189285bd7d4466scan: count skipped blocks as examined #18937a5d678864Allow pool import with corrupted spare/l2arc configs #18960 -multiple vdev_open: use calling credential to check for device access #18961acbcdd92cdmu_recv_begin_check: dsl_dataset_rele() should be called on ds #18962df3ff37fcdmu_redact_snap: Do proper cleanup on ENAMETOOLONG #1896484aa7e7e0libspl: consult ZFS_HOSTID on FreeBSD as well Obtained from: OpenZFS OpenZFS commit:84aa7e7e09
This commit is contained in:
commit
22649d4dba
3388 files changed
+40555
-47588
No files matched your search
@@ -8,6 +8,7 @@ MLINKS= zstream.8 zstreamdump.8
|
||||
INCS= zstream.h
|
||||
SRCS= \
|
||||
zstream.c \
|
||||
zstream_backtrace.c \
|
||||
zstream_byteswap.c \
|
||||
zstream_chain.c \
|
||||
zstream_decompress.c \
|
||||
@@ -15,8 +16,12 @@ SRCS= \
|
||||
zstream_dump.c \
|
||||
zstream_fletcher4.c \
|
||||
zstream_io.c \
|
||||
zstream_queue.c \
|
||||
zstream_raw.c \
|
||||
zstream_recompress.c \
|
||||
zstream_redup.c \
|
||||
zstream_selftest.c \
|
||||
zstream_selftest_queue.c \
|
||||
zstream_token.c \
|
||||
zstream_util.c \
|
||||
zstream_validate.c
|
||||
|
||||
+3
-3
@@ -16,7 +16,7 @@
|
||||
<!--- If your change is a performance enhancement, please provide benchmarks here. -->
|
||||
<!--- Please think about using the draft PR feature if appropriate -->
|
||||
|
||||
### Types of changes
|
||||
### Types of Changes
|
||||
<!--- What types of changes does your code introduce? Put an `x` in all the boxes that apply: -->
|
||||
- [ ] Bug fix (non-breaking change which fixes an issue)
|
||||
- [ ] New feature (non-breaking change which adds functionality)
|
||||
@@ -24,10 +24,10 @@
|
||||
- [ ] Code cleanup (non-breaking change which makes code smaller or more readable)
|
||||
- [ ] Quality assurance (non-breaking change which makes the code more robust against bugs)
|
||||
- [ ] Breaking change (fix or feature that would cause existing functionality to change)
|
||||
- [ ] Library ABI change (libzfs, libzfs\_core, libnvpair, libuutil and libzfsbootenv)
|
||||
- [ ] Library ABI change (libzfs, libzfs\_core, libnvpair and libzfsbootenv)
|
||||
- [ ] Documentation (a change to man pages or other documentation)
|
||||
|
||||
### Checklist:
|
||||
### Checklist
|
||||
<!--- Go over all the following points, and put an `x` in all the boxes that apply. -->
|
||||
<!--- If you're unsure about any of these, don't hesitate to ask. We're here to help! -->
|
||||
- [ ] My code follows the OpenZFS [code style requirements](https://github.com/openzfs/zfs/blob/master/.github/CONTRIBUTING.md#coding-conventions).
|
||||
|
||||
+1
@@ -3,3 +3,4 @@ name: "Custom CodeQL Analysis"
|
||||
queries:
|
||||
- uses: ./.github/codeql/custom-queries/cpp/deprecatedFunctionUsage.ql
|
||||
- uses: ./.github/codeql/custom-queries/cpp/dslDatasetHoldReleMismatch.ql
|
||||
- uses: ./.github/codeql/custom-queries/cpp/AssignmentOfComparisonAsCondition.ql
|
||||
+96
@@ -0,0 +1,96 @@
|
||||
/**
|
||||
* @name Ambiguous assignment-of-comparison in a condition
|
||||
* @description Finds any assignment (`=`, `+=`, `-=`, `|=`, …) that appears in
|
||||
* a branching condition and whose rvalue is an unparenthesized
|
||||
* comparison.
|
||||
*
|
||||
* Comparison binds tighter than assignment, so
|
||||
* `if ((x = foo() < 0))` / `if ((x += foo() < 0))`
|
||||
* assigns the boolean result of the comparison. Programmers often
|
||||
* meant
|
||||
* `if ((x = foo()) < 0)` / `if ((x += foo()) < 0)`
|
||||
* (assign/update then compare). GCC `-Wparentheses` is silenced by
|
||||
* the outer parentheses in both forms, so the compiler does not
|
||||
* catch the mistake (see openzfs/zfs#18874).
|
||||
*
|
||||
* Rule (intentionally simple):
|
||||
* assignment in a branch condition
|
||||
* AND rvalue is a comparison
|
||||
* AND that comparison is not parenthesized.
|
||||
*
|
||||
* That is enough:
|
||||
* - Explicit assign-then-compare `((x = foo()) < 0)` has no
|
||||
* comparison as the assignment's rvalue.
|
||||
* - Explicit compare-then-assign `((x = (foo() < 0)))` has a
|
||||
* parenthesized comparison as the rvalue.
|
||||
* - Parentheses that only wrap a subexpression of the comparison
|
||||
* (e.g. `(foo())`) do not count as parenthesizing the
|
||||
* comparison itself.
|
||||
* @kind problem
|
||||
* @problem.severity error
|
||||
* @precision high
|
||||
* @id cpp/ambiguous-assignment-of-comparison-in-condition
|
||||
* @tags reliability
|
||||
* correctness
|
||||
* external/cwe/cwe-783
|
||||
* external/cwe/cwe-480
|
||||
*/
|
||||
|
||||
import cpp
|
||||
|
||||
/**
|
||||
* The condition expression of a branching construct
|
||||
* (if / while / do-while / for / ternary `?:`).
|
||||
*/
|
||||
Expr branchCondition() {
|
||||
result = any(IfStmt s).getCondition()
|
||||
or
|
||||
result = any(WhileStmt s).getCondition()
|
||||
or
|
||||
result = any(DoStmt s).getCondition()
|
||||
or
|
||||
result = any(ForStmt s).getCondition()
|
||||
or
|
||||
result = any(ConditionalExpr c).getCondition()
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `e` is the branch condition, or appears anywhere under it
|
||||
* (including under `&&` / `||` / `!`, and through parentheses/conversions).
|
||||
*/
|
||||
predicate inBranchCondition(Expr e) {
|
||||
e = branchCondition()
|
||||
or
|
||||
// Structural child of something already in a branch condition
|
||||
exists(Expr parent |
|
||||
parent.getAChild() = e and
|
||||
inBranchCondition(parent)
|
||||
)
|
||||
or
|
||||
// ParenthesisExpr and other conversions hang off getConversion(), not getAChild()
|
||||
exists(Expr base |
|
||||
base.getConversion+() = e and
|
||||
inBranchCondition(base)
|
||||
)
|
||||
or
|
||||
exists(Expr conv |
|
||||
e.getConversion+() = conv and
|
||||
inBranchCondition(conv)
|
||||
)
|
||||
}
|
||||
|
||||
from Assignment a, ComparisonOperation cmp
|
||||
where
|
||||
// Core rule: assignment in a branch condition whose rvalue is an
|
||||
// unparenthesized comparison. Covers `=` and all compound forms (`+=`, …)
|
||||
// because Assignment is the common base class.
|
||||
a.getRValue() = cmp and
|
||||
not cmp.isParenthesised() and
|
||||
inBranchCondition(a) and
|
||||
// Skip AST from templates that were never instantiated (incomplete / non-code).
|
||||
not a.isFromUninstantiatedTemplate(_)
|
||||
select a,
|
||||
"Assignment (`" + a.getOperator() +
|
||||
"`) in a branch condition has an unparenthesized comparison as its rvalue; " +
|
||||
"likely meant `((x " + a.getOperator() + " expr) op val)` rather than " +
|
||||
"`(x " + a.getOperator() + " expr op val)`."
|
||||
@@ -44,11 +44,11 @@ case "$OS" in
|
||||
OSv="almalinux9"
|
||||
URL="https://repo.almalinux.org/almalinux/10/cloud/x86_64/images/AlmaLinux-10-GenericCloud-latest.x86_64.qcow2"
|
||||
;;
|
||||
alpine3-23)
|
||||
OSNAME="Alpine Linux 3.23.2"
|
||||
alpine3-24)
|
||||
OSNAME="Alpine Linux 3.24.1"
|
||||
# Alpine Linux v3.22 and v3.23 are unknown to osinfo as of 2025-12-26.
|
||||
OSv="alpinelinux3.21"
|
||||
URL="https://dl-cdn.alpinelinux.org/alpine/v3.23/releases/cloud/generic_alpine-3.23.2-x86_64-bios-cloudinit-r0.qcow2"
|
||||
URL="https://dl-cdn.alpinelinux.org/alpine/v3.24/releases/cloud/generic_alpine-3.24.1-x86_64-bios-cloudinit-r0.qcow2"
|
||||
;;
|
||||
archlinux)
|
||||
OSNAME="Archlinux"
|
||||
@@ -349,7 +349,7 @@ else
|
||||
while pidof /usr/bin/qemu-system-x86_64 >/dev/null; do
|
||||
ssh 2>/dev/null root@vm0 "uname -a" && break
|
||||
done
|
||||
ssh root@vm0 "env IGNORE_OSVERSION=yes pkg install -y bash ca_root_nss git qemu-guest-agent python3 py311-cloud-init"
|
||||
ssh root@vm0 "env IGNORE_OSVERSION=yes pkg install -y bash ca_root_nss git qemu-guest-agent python3 net/cloud-init"
|
||||
ssh root@vm0 "chsh -s $BASH root"
|
||||
ssh root@vm0 'sysrc qemu_guest_agent_enable="YES"'
|
||||
ssh root@vm0 'sysrc cloudinit_enable="YES"'
|
||||
|
||||
@@ -16,7 +16,7 @@ set -eu
|
||||
function alpine() {
|
||||
echo "##[group]Install Development Tools"
|
||||
sudo apk add \
|
||||
acl alpine-sdk attr autoconf automake bash build-base clang21 coreutils \
|
||||
acl alpine-sdk attr autoconf automake bash build-base clang22 coreutils \
|
||||
cpio cryptsetup curl curl-dev dhcpcd eudev eudev-dev eudev-libs findutils \
|
||||
fio gawk gdb gettext-dev git grep jq libaio libaio-dev libcurl \
|
||||
libtirpc-dev libtool libunwind libunwind-dev linux-headers linux-tools \
|
||||
|
||||
+31
-10
@@ -23,8 +23,14 @@ function prefix() {
|
||||
M=$((DIFF/60))
|
||||
S=$((DIFF-(M*60)))
|
||||
|
||||
CTR=$(cat /tmp/ctr)
|
||||
echo $LINE| grep -q '^\[.*] Test[: ]' && CTR=$((CTR+1)) && echo $CTR > /tmp/ctr
|
||||
CTR=$(cat /tmp/ctr-vm${ID})
|
||||
# Publish the counter with a rename so it is never momentarily empty: the
|
||||
# other VM's reader reads this file, and a plain redirect truncates before
|
||||
# it writes. A read landing in that window fails, which under set -e ends
|
||||
# the reader and silently stops prefixing that VM's output.
|
||||
echo $LINE| grep -q '^\[.*] Test[: ]' && CTR=$((CTR+1)) \
|
||||
&& echo $CTR > "/tmp/ctr-vm${ID}.new" \
|
||||
&& mv "/tmp/ctr-vm${ID}.new" "/tmp/ctr-vm${ID}"
|
||||
|
||||
BASE="$HOME/work/zfs/zfs"
|
||||
COLOR="$BASE/scripts/zfs-tests-color.sh"
|
||||
@@ -35,9 +41,17 @@ function prefix() {
|
||||
if [ -z "$CLINE" ]; then
|
||||
printf "vm${ID}: %s\n" "$LINE"
|
||||
else
|
||||
# [vm2: 00:15:54 256] Test: functional/checksum/setup (run as root) [00:00] [PASS]
|
||||
printf "[vm${ID}: %02d:%02d:%02d %4d] %s\n" \
|
||||
"$H" "$M" "$S" "$CTR" "$CLINE"
|
||||
TOTAL=0
|
||||
BREAKDOWN=""
|
||||
TOTAL_VMS="$3"
|
||||
for ((v=1; v<=TOTAL_VMS; v++)); do
|
||||
read -r val < "/tmp/ctr-vm${v}"
|
||||
TOTAL=$((TOTAL + val))
|
||||
BREAKDOWN="${BREAKDOWN}${BREAKDOWN:+|}${val}"
|
||||
done
|
||||
# [vm2: 00:15:54 256(200|56)] Test: functional/checksum/setup (run as root) [00:00] [PASS]
|
||||
printf "[vm${ID}: %02d:%02d:%02d %4d(%s)] %s\n" \
|
||||
"$H" "$M" "$S" "$TOTAL" "$BREAKDOWN" "$CLINE"
|
||||
fi
|
||||
}
|
||||
|
||||
@@ -96,10 +110,10 @@ if [ -z ${1:-} ]; then
|
||||
source env.txt
|
||||
SSH=$(which ssh)
|
||||
TESTS='$HOME/zfs/.github/workflows/scripts/qemu-6-tests.sh'
|
||||
echo 0 > /tmp/ctr
|
||||
date "+%s" > /tmp/tsstart
|
||||
|
||||
for ((i=1; i<=VMs; i++)); do
|
||||
echo 0 > /tmp/ctr-vm${i}
|
||||
IP="192.168.122.1$i"
|
||||
|
||||
# We do an additional test build of Lustre against ZFS if we're vm2
|
||||
@@ -121,7 +135,7 @@ if [ -z ${1:-} ]; then
|
||||
$SSH zfs@$IP $TESTS $OS $i $VMs $extra $CI_TYPE
|
||||
# handly line by line and add info prefix
|
||||
stdbuf -oL tail -fq vm${i}log.txt \
|
||||
| while read -r line; do prefix "$i" "$line"; done &
|
||||
| while read -r line; do prefix "$i" "$line" "$VMs"; done &
|
||||
echo $! > vm${i}log.pid
|
||||
# don't mix up the initial --- Configuration --- part
|
||||
sleep 0.13
|
||||
@@ -132,7 +146,10 @@ if [ -z ${1:-} ]; then
|
||||
tail --pid=$(cat vm${i}.pid) -f /dev/null
|
||||
pid=$(cat vm${i}log.pid)
|
||||
rm -f vm${i}log.pid
|
||||
kill $pid
|
||||
# Tolerate a reader that has already exited. The ESRCH would otherwise
|
||||
# end this script under set -e and fail a job whose tests all passed.
|
||||
# It stays on stderr: a dead reader means output was lost.
|
||||
kill $pid || true
|
||||
done
|
||||
|
||||
exit 0
|
||||
@@ -187,12 +204,16 @@ case "$OS" in
|
||||
sudo chmod 1777 /var/tmp
|
||||
sudo mv -f /tmp/*.txt /var/tmp
|
||||
|
||||
# Allow for longer RCU timeouts due to the heavily virtualized and
|
||||
# potentially oversubscribed nature of the CI environment.
|
||||
# Allow for longer RCU and watchdog timeouts due to the heavily virtualized
|
||||
# and potentially over-subscribed nature of the CI environment.
|
||||
rcu_cpu_stall_timeout="/sys/module/rcupdate/parameters/rcu_cpu_stall_timeout"
|
||||
if test -f $rcu_cpu_stall_timeout; then
|
||||
echo 120 | sudo sh -c "cat > '$rcu_cpu_stall_timeout'"
|
||||
fi
|
||||
|
||||
if test -c /dev/watchdog; then
|
||||
sudo wdctl --settimeout 120 >/dev/null
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
|
||||
|
||||
+1
-1
@@ -14,7 +14,7 @@ on:
|
||||
type: string
|
||||
required: false
|
||||
default: ""
|
||||
description: "(optional) Only run on this specific OS (like 'fedora44' or 'alpine3-23')"
|
||||
description: "(optional) Only run on this specific OS (like 'fedora44' or 'alpine3-24')"
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }}
|
||||
|
||||
+1
-2
@@ -49,10 +49,9 @@ jobs:
|
||||
sudo modprobe zfs
|
||||
- name: Tests
|
||||
run: |
|
||||
[ -r /etc/hostid ] && [ -s /etc/hostid ] || sudo zgenhostid -f
|
||||
sudo truncate -s 256G /mnt/vdev
|
||||
sudo zpool create cipool -m $WORK_DIR -O compression=on -o autotrim=on /mnt/vdev
|
||||
sudo /usr/share/zfs/zloop.sh -t 600 -I 6 -l -m 1 -c $CORE_DIR -f $WORK_DIR -- -T 120 -P 60
|
||||
sudo /usr/share/zfs/zloop.sh -t 600 -I 6 -l -m 1 -M -c $CORE_DIR -f $WORK_DIR -- -T 120 -P 60
|
||||
- name: Prepare artifacts
|
||||
if: failure()
|
||||
run: |
|
||||
|
||||
@@ -59,6 +59,7 @@
|
||||
!NOTICE
|
||||
!README.md
|
||||
!RELEASES.md
|
||||
!SECURITY.md
|
||||
!TEST
|
||||
!zfs.release.in
|
||||
|
||||
|
||||
@@ -102,6 +102,7 @@ Phil Sutter <[email protected]> <[email protected]>
|
||||
poscat <[email protected]> <[email protected]>
|
||||
Qiuhao Chen <[email protected]> <[email protected]>
|
||||
Ryan <[email protected]> <[email protected]>
|
||||
Saju Palayur <[email protected]> <[email protected]>
|
||||
Sietse <[email protected]> <[email protected]>
|
||||
Yuxin Wang <[email protected]> <[email protected]>
|
||||
Zhenlei Huang <[email protected]> <[email protected]>
|
||||
@@ -141,6 +142,7 @@ buzzingwires <[email protected]> <[email protected]
|
||||
Cedric Maunoury <[email protected]> <[email protected]>
|
||||
Charles Suh <[email protected]> <[email protected]>
|
||||
Chris Peredun <[email protected]> <[email protected]>
|
||||
Christos Longros <[email protected]> <[email protected]>
|
||||
classabbyamp <[email protected]> <[email protected]>
|
||||
Dacian Reece-Stremtan <[email protected]> <[email protected]>
|
||||
Damian Szuberski <[email protected]> <[email protected]>
|
||||
|
||||
@@ -304,6 +304,7 @@ CONTRIBUTORS:
|
||||
hedong zhang <[email protected]>
|
||||
Heitor Alves de Siqueira <[email protected]>
|
||||
Henrik Riomar <[email protected]>
|
||||
HeonJe Lee <[email protected]>
|
||||
Herb Wartens <[email protected]>
|
||||
Hiếu Lê <[email protected]>
|
||||
hoshinomori <[email protected]>
|
||||
@@ -506,6 +507,7 @@ CONTRIBUTORS:
|
||||
mischivus <[email protected]>
|
||||
mnrx <[email protected]>
|
||||
Mohamed Tawfik <[email protected]>
|
||||
MorganaFuture <[email protected]>
|
||||
Morgan Jones <[email protected]>
|
||||
Moritz Maxeiner <[email protected]>
|
||||
Mo Zhou <[email protected]>
|
||||
@@ -523,6 +525,7 @@ CONTRIBUTORS:
|
||||
Nick Black <[email protected]>
|
||||
Nick Garvey <[email protected]>
|
||||
Nick Mattis <[email protected]>
|
||||
Nick Price <[email protected]>
|
||||
Nick Terrell <[email protected]>
|
||||
Niklas Haas <[email protected]>
|
||||
Nikolay Borisov <[email protected]>
|
||||
@@ -609,6 +612,7 @@ CONTRIBUTORS:
|
||||
Ryan Lahfa <[email protected]>
|
||||
Ryan Libby <[email protected]>
|
||||
Ryan Moeller <[email protected]>
|
||||
Saju Palayur <[email protected]>
|
||||
Sam Atkinson <[email protected]>
|
||||
Sam Hathaway <[email protected]>
|
||||
Sam James <[email protected]>
|
||||
@@ -677,6 +681,7 @@ CONTRIBUTORS:
|
||||
Thomas Bertschinger <[email protected]>
|
||||
Thomas Geppert <[email protected]>
|
||||
Thomas Lamprecht <[email protected]>
|
||||
tiehexue <[email protected]>
|
||||
Till Maas <[email protected]>
|
||||
Tim Chase <[email protected]>
|
||||
Tim Connors <[email protected]>
|
||||
@@ -746,6 +751,7 @@ CONTRIBUTORS:
|
||||
Yuxin Wang <[email protected]>
|
||||
Yuxuan Shui <[email protected]>
|
||||
Zachary Bedell <[email protected]>
|
||||
Zachary Sandberg <[email protected]>
|
||||
Zach Dykstra <[email protected]>
|
||||
zgock <[email protected]>
|
||||
Zhao Yongming <[email protected]>
|
||||
|
||||
@@ -6,5 +6,5 @@ Release: 1
|
||||
Release-Tags: relext
|
||||
License: CDDL
|
||||
Author: OpenZFS
|
||||
Linux-Maximum: 7.1
|
||||
Linux-Maximum: 7.2
|
||||
Linux-Minimum: 4.18
|
||||
@@ -31,10 +31,11 @@ if BUILD_LINUX
|
||||
include $(srcdir)/%D%/udev/Makefile.am
|
||||
endif
|
||||
endif
|
||||
CPPCHECKDIRS += module
|
||||
if CONFIG_KERNEL
|
||||
SUBDIRS += module
|
||||
|
||||
CPPCHECKDIRS += module
|
||||
|
||||
extradir = $(prefix)/src/zfs-$(VERSION)
|
||||
extra_HEADERS = zfs.release.in zfs_config.h.in
|
||||
endif
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
# Security Policy
|
||||
|
||||
## Reporting a Vulnerability
|
||||
|
||||
We encourage responsible disclosure of security vulnerabilities. If you
|
||||
find something suspicious, we encourage and appreciate your report.
|
||||
|
||||
The preferred way to report a vulnerability is to use the
|
||||
**"Report a vulnerability"** button under the **Security** tab of the
|
||||
OpenZFS GitHub repository. This creates a private communication channel
|
||||
between you and the maintainers, allowing us to review the report
|
||||
confidentially and respond as quickly as possible.
|
||||
|
||||
Please include, if possible:
|
||||
|
||||
- A clear description of the issue
|
||||
- Steps to reproduce the problem
|
||||
- Affected versions or branches
|
||||
- Any proof of concept, logs, or screenshots
|
||||
- Your assessment of the potential impact
|
||||
|
||||
## What to Expect
|
||||
|
||||
- We will review security reports as soon as practical.
|
||||
- We may ask follow-up questions to better understand the issue.
|
||||
- Please allow time for investigation and coordination before public
|
||||
disclosure.
|
||||
- If the issue is confirmed, we will work on a fix and release a security
|
||||
update as needed for supported OpenZFS versions.
|
||||
@@ -5,25 +5,14 @@
|
||||
# is available through the dbufs kstat and may be post-processed as
|
||||
# needed by the script.
|
||||
#
|
||||
# CDDL HEADER START
|
||||
# This file and its contents are supplied under the terms of the
|
||||
# Common Development and Distribution License ("CDDL"), version 1.0.
|
||||
# You may only use this file in accordance with the terms of version
|
||||
# 1.0 of the CDDL.
|
||||
#
|
||||
# The contents of this file are subject to the terms of the
|
||||
# Common Development and Distribution License, Version 1.0 only
|
||||
# (the "License"). You may not use this file except in compliance
|
||||
# with the License.
|
||||
#
|
||||
# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
|
||||
# or https://opensource.org/licenses/CDDL-1.0.
|
||||
# See the License for the specific language governing permissions
|
||||
# and limitations under the License.
|
||||
#
|
||||
# When distributing Covered Code, include this CDDL HEADER in each
|
||||
# file and include the License file at usr/src/OPENSOLARIS.LICENSE.
|
||||
# If applicable, add the following below this CDDL HEADER, with the
|
||||
# fields enclosed by brackets "[]" replaced with your own identifying
|
||||
# information: Portions Copyright [yyyy] [name of copyright owner]
|
||||
#
|
||||
# CDDL HEADER END
|
||||
# A full copy of the text of the CDDL should have accompanied this
|
||||
# source. A copy of the CDDL is also available via the Internet at
|
||||
# https://opensource.org/license/CDDL-1.0.
|
||||
#
|
||||
# Copyright (C) 2013 Lawrence Livermore National Security, LLC.
|
||||
# Produced at Lawrence Livermore National Laboratory (cf, DISCLAIMER).
|
||||
|
||||
@@ -1,23 +1,13 @@
|
||||
// SPDX-License-Identifier: CDDL-1.0
|
||||
/*
|
||||
* CDDL HEADER START
|
||||
* This file and its contents are supplied under the terms of the
|
||||
* Common Development and Distribution License ("CDDL"), version 1.0.
|
||||
* You may only use this file in accordance with the terms of version
|
||||
* 1.0 of the CDDL.
|
||||
*
|
||||
* The contents of this file are subject to the terms of the
|
||||
* Common Development and Distribution License (the "License").
|
||||
* You may not use this file except in compliance with the License.
|
||||
*
|
||||
* You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
|
||||
* or https://opensource.org/licenses/CDDL-1.0.
|
||||
* See the License for the specific language governing permissions
|
||||
* and limitations under the License.
|
||||
*
|
||||
* When distributing Covered Code, include this CDDL HEADER in each
|
||||
* file and include the License file at usr/src/OPENSOLARIS.LICENSE.
|
||||
* If applicable, add the following below this CDDL HEADER, with the
|
||||
* fields enclosed by brackets "[]" replaced with your own identifying
|
||||
* information: Portions Copyright [yyyy] [name of copyright owner]
|
||||
*
|
||||
* CDDL HEADER END
|
||||
* A full copy of the text of the CDDL should have accompanied this
|
||||
* source. A copy of the CDDL is also available via the Internet at
|
||||
* https://opensource.org/license/CDDL-1.0.
|
||||
*/
|
||||
|
||||
/*
|
||||
|
||||
@@ -1,23 +1,13 @@
|
||||
// SPDX-License-Identifier: CDDL-1.0
|
||||
/*
|
||||
* CDDL HEADER START
|
||||
* This file and its contents are supplied under the terms of the
|
||||
* Common Development and Distribution License ("CDDL"), version 1.0.
|
||||
* You may only use this file in accordance with the terms of version
|
||||
* 1.0 of the CDDL.
|
||||
*
|
||||
* The contents of this file are subject to the terms of the
|
||||
* Common Development and Distribution License (the "License").
|
||||
* You may not use this file except in compliance with the License.
|
||||
*
|
||||
* You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
|
||||
* or https://opensource.org/licenses/CDDL-1.0.
|
||||
* See the License for the specific language governing permissions
|
||||
* and limitations under the License.
|
||||
*
|
||||
* When distributing Covered Code, include this CDDL HEADER in each
|
||||
* file and include the License file at usr/src/OPENSOLARIS.LICENSE.
|
||||
* If applicable, add the following below this CDDL HEADER, with the
|
||||
* fields enclosed by brackets "[]" replaced with your own identifying
|
||||
* information: Portions Copyright [yyyy] [name of copyright owner]
|
||||
*
|
||||
* CDDL HEADER END
|
||||
* A full copy of the text of the CDDL should have accompanied this
|
||||
* source. A copy of the CDDL is also available via the Internet at
|
||||
* https://opensource.org/license/CDDL-1.0.
|
||||
*/
|
||||
|
||||
/*
|
||||
|
||||
@@ -1,23 +1,13 @@
|
||||
// SPDX-License-Identifier: CDDL-1.0
|
||||
/*
|
||||
* CDDL HEADER START
|
||||
* This file and its contents are supplied under the terms of the
|
||||
* Common Development and Distribution License ("CDDL"), version 1.0.
|
||||
* You may only use this file in accordance with the terms of version
|
||||
* 1.0 of the CDDL.
|
||||
*
|
||||
* The contents of this file are subject to the terms of the
|
||||
* Common Development and Distribution License (the "License").
|
||||
* You may not use this file except in compliance with the License.
|
||||
*
|
||||
* You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
|
||||
* or https://opensource.org/licenses/CDDL-1.0.
|
||||
* See the License for the specific language governing permissions
|
||||
* and limitations under the License.
|
||||
*
|
||||
* When distributing Covered Code, include this CDDL HEADER in each
|
||||
* file and include the License file at usr/src/OPENSOLARIS.LICENSE.
|
||||
* If applicable, add the following below this CDDL HEADER, with the
|
||||
* fields enclosed by brackets "[]" replaced with your own identifying
|
||||
* information: Portions Copyright [yyyy] [name of copyright owner]
|
||||
*
|
||||
* CDDL HEADER END
|
||||
* A full copy of the text of the CDDL should have accompanied this
|
||||
* source. A copy of the CDDL is also available via the Internet at
|
||||
* https://opensource.org/license/CDDL-1.0.
|
||||
*/
|
||||
|
||||
/*
|
||||
|
||||
@@ -1,23 +1,13 @@
|
||||
// SPDX-License-Identifier: CDDL-1.0
|
||||
/*
|
||||
* CDDL HEADER START
|
||||
* This file and its contents are supplied under the terms of the
|
||||
* Common Development and Distribution License ("CDDL"), version 1.0.
|
||||
* You may only use this file in accordance with the terms of version
|
||||
* 1.0 of the CDDL.
|
||||
*
|
||||
* The contents of this file are subject to the terms of the
|
||||
* Common Development and Distribution License (the "License").
|
||||
* You may not use this file except in compliance with the License.
|
||||
*
|
||||
* You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
|
||||
* or https://opensource.org/licenses/CDDL-1.0.
|
||||
* See the License for the specific language governing permissions
|
||||
* and limitations under the License.
|
||||
*
|
||||
* When distributing Covered Code, include this CDDL HEADER in each
|
||||
* file and include the License file at usr/src/OPENSOLARIS.LICENSE.
|
||||
* If applicable, add the following below this CDDL HEADER, with the
|
||||
* fields enclosed by brackets "[]" replaced with your own identifying
|
||||
* information: Portions Copyright [yyyy] [name of copyright owner]
|
||||
*
|
||||
* CDDL HEADER END
|
||||
* A full copy of the text of the CDDL should have accompanied this
|
||||
* source. A copy of the CDDL is also available via the Internet at
|
||||
* https://opensource.org/license/CDDL-1.0.
|
||||
*/
|
||||
|
||||
/*
|
||||
|
||||
@@ -22,25 +22,14 @@
|
||||
#
|
||||
# and has been improved by many people since.
|
||||
#
|
||||
# CDDL HEADER START
|
||||
# This file and its contents are supplied under the terms of the
|
||||
# Common Development and Distribution License ("CDDL"), version 1.0.
|
||||
# You may only use this file in accordance with the terms of version
|
||||
# 1.0 of the CDDL.
|
||||
#
|
||||
# The contents of this file are subject to the terms of the
|
||||
# Common Development and Distribution License, Version 1.0 only
|
||||
# (the "License"). You may not use this file except in compliance
|
||||
# with the License.
|
||||
#
|
||||
# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
|
||||
# or https://opensource.org/licenses/CDDL-1.0.
|
||||
# See the License for the specific language governing permissions
|
||||
# and limitations under the License.
|
||||
#
|
||||
# When distributing Covered Code, include this CDDL HEADER in each
|
||||
# file and include the License file at usr/src/OPENSOLARIS.LICENSE.
|
||||
# If applicable, add the following below this CDDL HEADER, with the
|
||||
# fields enclosed by brackets "[]" replaced with your own identifying
|
||||
# information: Portions Copyright [yyyy] [name of copyright owner]
|
||||
#
|
||||
# CDDL HEADER END
|
||||
# A full copy of the text of the CDDL should have accompanied this
|
||||
# source. A copy of the CDDL is also available via the Internet at
|
||||
# https://opensource.org/license/CDDL-1.0.
|
||||
#
|
||||
#
|
||||
# Fields have a fixed width. Every interval, we fill the "v"
|
||||
|
||||
@@ -1,23 +1,13 @@
|
||||
// SPDX-License-Identifier: CDDL-1.0
|
||||
/*
|
||||
* CDDL HEADER START
|
||||
* This file and its contents are supplied under the terms of the
|
||||
* Common Development and Distribution License ("CDDL"), version 1.0.
|
||||
* You may only use this file in accordance with the terms of version
|
||||
* 1.0 of the CDDL.
|
||||
*
|
||||
* The contents of this file are subject to the terms of the
|
||||
* Common Development and Distribution License (the "License").
|
||||
* You may not use this file except in compliance with the License.
|
||||
*
|
||||
* You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
|
||||
* or https://opensource.org/licenses/CDDL-1.0.
|
||||
* See the License for the specific language governing permissions
|
||||
* and limitations under the License.
|
||||
*
|
||||
* When distributing Covered Code, include this CDDL HEADER in each
|
||||
* file and include the License file at usr/src/OPENSOLARIS.LICENSE.
|
||||
* If applicable, add the following below this CDDL HEADER, with the
|
||||
* fields enclosed by brackets "[]" replaced with your own identifying
|
||||
* information: Portions Copyright [yyyy] [name of copyright owner]
|
||||
*
|
||||
* CDDL HEADER END
|
||||
* A full copy of the text of the CDDL should have accompanied this
|
||||
* source. A copy of the CDDL is also available via the Internet at
|
||||
* https://opensource.org/license/CDDL-1.0.
|
||||
*/
|
||||
|
||||
/*
|
||||
@@ -37,6 +27,7 @@
|
||||
* Copyright (c) 2023, 2024, Klara Inc.
|
||||
* Copyright (c) 2023, Rob Norris <robn@despairlabs.com>
|
||||
* Copyright (c) 2026, TrueNAS.
|
||||
* Copyright 2026 Edgecast Cloud LLC.
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
@@ -937,13 +928,24 @@ dump_packed_nvlist(objset_t *os, uint64_t object, void *data, size_t size)
|
||||
nvlist_t *nv;
|
||||
size_t nvsize = *(uint64_t *)data;
|
||||
char *packed = umem_alloc(nvsize, UMEM_NOFAIL);
|
||||
int err;
|
||||
|
||||
VERIFY0(dmu_read(os, object, 0, nvsize, packed, DMU_READ_PREFETCH));
|
||||
err = dmu_read(os, object, 0, nvsize, packed, DMU_READ_PREFETCH);
|
||||
if (err != 0) {
|
||||
(void) printf("got error %u from dmu_read\n", err);
|
||||
umem_free(packed, nvsize);
|
||||
return;
|
||||
}
|
||||
|
||||
VERIFY0(nvlist_unpack(packed, nvsize, &nv, 0));
|
||||
err = nvlist_unpack(packed, nvsize, &nv, 0);
|
||||
|
||||
umem_free(packed, nvsize);
|
||||
|
||||
if (err != 0) {
|
||||
(void) printf("got error %u from nvlist_unpack\n", err);
|
||||
return;
|
||||
}
|
||||
|
||||
dump_nvlist(nv, 8);
|
||||
|
||||
nvlist_free(nv);
|
||||
@@ -1447,10 +1449,10 @@ dump_zpldir(objset_t *os, uint64_t object, void *data, size_t size)
|
||||
zap_attribute_free(attrp);
|
||||
}
|
||||
|
||||
static int
|
||||
static uint64_t
|
||||
get_dtl_refcount(vdev_t *vd)
|
||||
{
|
||||
int refcount = 0;
|
||||
uint64_t refcount = 0;
|
||||
|
||||
if (vd->vdev_ops->vdev_op_leaf) {
|
||||
space_map_t *sm = vd->vdev_dtl_sm;
|
||||
@@ -1466,10 +1468,10 @@ get_dtl_refcount(vdev_t *vd)
|
||||
return (refcount);
|
||||
}
|
||||
|
||||
static int
|
||||
static uint64_t
|
||||
get_metaslab_refcount(vdev_t *vd)
|
||||
{
|
||||
int refcount = 0;
|
||||
uint64_t refcount = 0;
|
||||
|
||||
if (vd->vdev_top == vd) {
|
||||
for (uint64_t m = 0; m < vd->vdev_ms_count; m++) {
|
||||
@@ -1486,11 +1488,11 @@ get_metaslab_refcount(vdev_t *vd)
|
||||
return (refcount);
|
||||
}
|
||||
|
||||
static int
|
||||
static uint64_t
|
||||
get_obsolete_refcount(vdev_t *vd)
|
||||
{
|
||||
uint64_t obsolete_sm_object;
|
||||
int refcount = 0;
|
||||
uint64_t refcount = 0;
|
||||
|
||||
VERIFY0(vdev_obsolete_sm_object(vd, &obsolete_sm_object));
|
||||
if (vd->vdev_top == vd && obsolete_sm_object != 0) {
|
||||
@@ -1511,7 +1513,7 @@ get_obsolete_refcount(vdev_t *vd)
|
||||
return (refcount);
|
||||
}
|
||||
|
||||
static int
|
||||
static uint64_t
|
||||
get_prev_obsolete_spacemap_refcount(spa_t *spa)
|
||||
{
|
||||
uint64_t prev_obj =
|
||||
@@ -1526,10 +1528,10 @@ get_prev_obsolete_spacemap_refcount(spa_t *spa)
|
||||
return (0);
|
||||
}
|
||||
|
||||
static int
|
||||
static uint64_t
|
||||
get_checkpoint_refcount(vdev_t *vd)
|
||||
{
|
||||
int refcount = 0;
|
||||
uint64_t refcount = 0;
|
||||
|
||||
if (vd->vdev_top == vd && vd->vdev_top_zap != 0 &&
|
||||
zap_contains(spa_meta_objset(vd->vdev_spa),
|
||||
@@ -1542,7 +1544,7 @@ get_checkpoint_refcount(vdev_t *vd)
|
||||
return (refcount);
|
||||
}
|
||||
|
||||
static int
|
||||
static uint64_t
|
||||
get_log_spacemap_refcount(spa_t *spa)
|
||||
{
|
||||
return (avl_numnodes(&spa->spa_sm_logs_by_txg));
|
||||
@@ -1552,23 +1554,37 @@ static int
|
||||
verify_spacemap_refcounts(spa_t *spa)
|
||||
{
|
||||
uint64_t expected_refcount = 0;
|
||||
uint64_t actual_refcount;
|
||||
uint64_t actual_refcount = 0;
|
||||
uint64_t dtl_refcount, metaslab_refcount, obsolete_refcount,
|
||||
prev_obsolete_refcount, checkpoint_refcount, log_spacemap_refcount;
|
||||
|
||||
(void) feature_get_refcount(spa,
|
||||
&spa_feature_table[SPA_FEATURE_SPACEMAP_HISTOGRAM],
|
||||
&expected_refcount);
|
||||
actual_refcount = get_dtl_refcount(spa->spa_root_vdev);
|
||||
actual_refcount += get_metaslab_refcount(spa->spa_root_vdev);
|
||||
actual_refcount += get_obsolete_refcount(spa->spa_root_vdev);
|
||||
actual_refcount += get_prev_obsolete_spacemap_refcount(spa);
|
||||
actual_refcount += get_checkpoint_refcount(spa->spa_root_vdev);
|
||||
actual_refcount += get_log_spacemap_refcount(spa);
|
||||
dtl_refcount = get_dtl_refcount(spa->spa_root_vdev);
|
||||
metaslab_refcount = get_metaslab_refcount(spa->spa_root_vdev);
|
||||
obsolete_refcount = get_obsolete_refcount(spa->spa_root_vdev);
|
||||
prev_obsolete_refcount = get_prev_obsolete_spacemap_refcount(spa);
|
||||
checkpoint_refcount = get_checkpoint_refcount(spa->spa_root_vdev);
|
||||
log_spacemap_refcount = get_log_spacemap_refcount(spa);
|
||||
actual_refcount = dtl_refcount + metaslab_refcount +
|
||||
obsolete_refcount + prev_obsolete_refcount +
|
||||
checkpoint_refcount + log_spacemap_refcount;
|
||||
|
||||
if (expected_refcount != actual_refcount) {
|
||||
(void) printf("space map refcount mismatch: expected %lld != "
|
||||
"actual %lld\n",
|
||||
(longlong_t)expected_refcount,
|
||||
(longlong_t)actual_refcount);
|
||||
(void) printf("space map refcount mismatch: expected %llu != "
|
||||
"actual %llu\n",
|
||||
(u_longlong_t)expected_refcount,
|
||||
(u_longlong_t)actual_refcount);
|
||||
(void) printf("\tDTL: %llu, metaslab: %llu, obsolete: %llu, "
|
||||
"prev obsolete: %llu, checkpoint: %llu, "
|
||||
"log spacemap: %llu\n",
|
||||
(u_longlong_t)dtl_refcount,
|
||||
(u_longlong_t)metaslab_refcount,
|
||||
(u_longlong_t)obsolete_refcount,
|
||||
(u_longlong_t)prev_obsolete_refcount,
|
||||
(u_longlong_t)checkpoint_refcount,
|
||||
(u_longlong_t)log_spacemap_refcount);
|
||||
return (2);
|
||||
}
|
||||
return (0);
|
||||
|
||||
@@ -1,23 +1,13 @@
|
||||
// SPDX-License-Identifier: CDDL-1.0
|
||||
/*
|
||||
* CDDL HEADER START
|
||||
* This file and its contents are supplied under the terms of the
|
||||
* Common Development and Distribution License ("CDDL"), version 1.0.
|
||||
* You may only use this file in accordance with the terms of version
|
||||
* 1.0 of the CDDL.
|
||||
*
|
||||
* The contents of this file are subject to the terms of the
|
||||
* Common Development and Distribution License (the "License").
|
||||
* You may not use this file except in compliance with the License.
|
||||
*
|
||||
* You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
|
||||
* or https://opensource.org/licenses/CDDL-1.0.
|
||||
* See the License for the specific language governing permissions
|
||||
* and limitations under the License.
|
||||
*
|
||||
* When distributing Covered Code, include this CDDL HEADER in each
|
||||
* file and include the License file at usr/src/OPENSOLARIS.LICENSE.
|
||||
* If applicable, add the following below this CDDL HEADER, with the
|
||||
* fields enclosed by brackets "[]" replaced with your own identifying
|
||||
* information: Portions Copyright [yyyy] [name of copyright owner]
|
||||
*
|
||||
* CDDL HEADER END
|
||||
* A full copy of the text of the CDDL should have accompanied this
|
||||
* source. A copy of the CDDL is also available via the Internet at
|
||||
* https://opensource.org/license/CDDL-1.0.
|
||||
*/
|
||||
/*
|
||||
* Copyright 2017 Spectra Logic Corp Inc. All rights reserved.
|
||||
|
||||
@@ -1,23 +1,13 @@
|
||||
// SPDX-License-Identifier: CDDL-1.0
|
||||
/*
|
||||
* CDDL HEADER START
|
||||
* This file and its contents are supplied under the terms of the
|
||||
* Common Development and Distribution License ("CDDL"), version 1.0.
|
||||
* You may only use this file in accordance with the terms of version
|
||||
* 1.0 of the CDDL.
|
||||
*
|
||||
* The contents of this file are subject to the terms of the
|
||||
* Common Development and Distribution License (the "License").
|
||||
* You may not use this file except in compliance with the License.
|
||||
*
|
||||
* You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
|
||||
* or https://opensource.org/licenses/CDDL-1.0.
|
||||
* See the License for the specific language governing permissions
|
||||
* and limitations under the License.
|
||||
*
|
||||
* When distributing Covered Code, include this CDDL HEADER in each
|
||||
* file and include the License file at usr/src/OPENSOLARIS.LICENSE.
|
||||
* If applicable, add the following below this CDDL HEADER, with the
|
||||
* fields enclosed by brackets "[]" replaced with your own identifying
|
||||
* information: Portions Copyright [yyyy] [name of copyright owner]
|
||||
*
|
||||
* CDDL HEADER END
|
||||
* A full copy of the text of the CDDL should have accompanied this
|
||||
* source. A copy of the CDDL is also available via the Internet at
|
||||
* https://opensource.org/license/CDDL-1.0.
|
||||
*/
|
||||
/*
|
||||
* Copyright 2009 Sun Microsystems, Inc. All rights reserved.
|
||||
|
||||
@@ -1,23 +1,13 @@
|
||||
// SPDX-License-Identifier: CDDL-1.0
|
||||
/*
|
||||
* CDDL HEADER START
|
||||
* This file and its contents are supplied under the terms of the
|
||||
* Common Development and Distribution License ("CDDL"), version 1.0.
|
||||
* You may only use this file in accordance with the terms of version
|
||||
* 1.0 of the CDDL.
|
||||
*
|
||||
* The contents of this file are subject to the terms of the
|
||||
* Common Development and Distribution License (the "License").
|
||||
* You may not use this file except in compliance with the License.
|
||||
*
|
||||
* You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
|
||||
* or https://opensource.org/licenses/CDDL-1.0.
|
||||
* See the License for the specific language governing permissions
|
||||
* and limitations under the License.
|
||||
*
|
||||
* When distributing Covered Code, include this CDDL HEADER in each
|
||||
* file and include the License file at usr/src/OPENSOLARIS.LICENSE.
|
||||
* If applicable, add the following below this CDDL HEADER, with the
|
||||
* fields enclosed by brackets "[]" replaced with your own identifying
|
||||
* information: Portions Copyright [yyyy] [name of copyright owner]
|
||||
*
|
||||
* CDDL HEADER END
|
||||
* A full copy of the text of the CDDL should have accompanied this
|
||||
* source. A copy of the CDDL is also available via the Internet at
|
||||
* https://opensource.org/license/CDDL-1.0.
|
||||
*/
|
||||
/*
|
||||
* Copyright (c) 2004, 2010, Oracle and/or its affiliates. All rights reserved.
|
||||
|
||||
@@ -1,23 +1,13 @@
|
||||
// SPDX-License-Identifier: CDDL-1.0
|
||||
/*
|
||||
* CDDL HEADER START
|
||||
* This file and its contents are supplied under the terms of the
|
||||
* Common Development and Distribution License ("CDDL"), version 1.0.
|
||||
* You may only use this file in accordance with the terms of version
|
||||
* 1.0 of the CDDL.
|
||||
*
|
||||
* The contents of this file are subject to the terms of the
|
||||
* Common Development and Distribution License (the "License").
|
||||
* You may not use this file except in compliance with the License.
|
||||
*
|
||||
* You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
|
||||
* or https://opensource.org/licenses/CDDL-1.0.
|
||||
* See the License for the specific language governing permissions
|
||||
* and limitations under the License.
|
||||
*
|
||||
* When distributing Covered Code, include this CDDL HEADER in each
|
||||
* file and include the License file at usr/src/OPENSOLARIS.LICENSE.
|
||||
* If applicable, add the following below this CDDL HEADER, with the
|
||||
* fields enclosed by brackets "[]" replaced with your own identifying
|
||||
* information: Portions Copyright [yyyy] [name of copyright owner]
|
||||
*
|
||||
* CDDL HEADER END
|
||||
* A full copy of the text of the CDDL should have accompanied this
|
||||
* source. A copy of the CDDL is also available via the Internet at
|
||||
* https://opensource.org/license/CDDL-1.0.
|
||||
*/
|
||||
|
||||
/*
|
||||
|
||||
@@ -1,24 +1,13 @@
|
||||
// SPDX-License-Identifier: CDDL-1.0
|
||||
/*
|
||||
* CDDL HEADER START
|
||||
* This file and its contents are supplied under the terms of the
|
||||
* Common Development and Distribution License ("CDDL"), version 1.0.
|
||||
* You may only use this file in accordance with the terms of version
|
||||
* 1.0 of the CDDL.
|
||||
*
|
||||
* The contents of this file are subject to the terms of the
|
||||
* Common Development and Distribution License, Version 1.0 only
|
||||
* (the "License"). You may not use this file except in compliance
|
||||
* with the License.
|
||||
*
|
||||
* You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
|
||||
* or https://opensource.org/licenses/CDDL-1.0.
|
||||
* See the License for the specific language governing permissions
|
||||
* and limitations under the License.
|
||||
*
|
||||
* When distributing Covered Code, include this CDDL HEADER in each
|
||||
* file and include the License file at usr/src/OPENSOLARIS.LICENSE.
|
||||
* If applicable, add the following below this CDDL HEADER, with the
|
||||
* fields enclosed by brackets "[]" replaced with your own identifying
|
||||
* information: Portions Copyright [yyyy] [name of copyright owner]
|
||||
*
|
||||
* CDDL HEADER END
|
||||
* A full copy of the text of the CDDL should have accompanied this
|
||||
* source. A copy of the CDDL is also available via the Internet at
|
||||
* https://opensource.org/license/CDDL-1.0.
|
||||
*/
|
||||
/*
|
||||
* Copyright 2004 Sun Microsystems, Inc. All rights reserved.
|
||||
|
||||
@@ -1,24 +1,13 @@
|
||||
// SPDX-License-Identifier: CDDL-1.0
|
||||
/*
|
||||
* CDDL HEADER START
|
||||
* This file and its contents are supplied under the terms of the
|
||||
* Common Development and Distribution License ("CDDL"), version 1.0.
|
||||
* You may only use this file in accordance with the terms of version
|
||||
* 1.0 of the CDDL.
|
||||
*
|
||||
* The contents of this file are subject to the terms of the
|
||||
* Common Development and Distribution License, Version 1.0 only
|
||||
* (the "License"). You may not use this file except in compliance
|
||||
* with the License.
|
||||
*
|
||||
* You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
|
||||
* or https://opensource.org/licenses/CDDL-1.0.
|
||||
* See the License for the specific language governing permissions
|
||||
* and limitations under the License.
|
||||
*
|
||||
* When distributing Covered Code, include this CDDL HEADER in each
|
||||
* file and include the License file at usr/src/OPENSOLARIS.LICENSE.
|
||||
* If applicable, add the following below this CDDL HEADER, with the
|
||||
* fields enclosed by brackets "[]" replaced with your own identifying
|
||||
* information: Portions Copyright [yyyy] [name of copyright owner]
|
||||
*
|
||||
* CDDL HEADER END
|
||||
* A full copy of the text of the CDDL should have accompanied this
|
||||
* source. A copy of the CDDL is also available via the Internet at
|
||||
* https://opensource.org/license/CDDL-1.0.
|
||||
*/
|
||||
/*
|
||||
* Copyright 2004 Sun Microsystems, Inc. All rights reserved.
|
||||
|
||||
@@ -1,14 +1,13 @@
|
||||
// SPDX-License-Identifier: CDDL-1.0
|
||||
/*
|
||||
* CDDL HEADER START
|
||||
* This file and its contents are supplied under the terms of the
|
||||
* Common Development and Distribution License ("CDDL"), version 1.0.
|
||||
* You may only use this file in accordance with the terms of version
|
||||
* 1.0 of the CDDL.
|
||||
*
|
||||
* The contents of this file are subject to the terms of the
|
||||
* Common Development and Distribution License Version 1.0 (CDDL-1.0).
|
||||
* You can obtain a copy of the license from the top-level file
|
||||
* "OPENSOLARIS.LICENSE" or at <http://opensource.org/licenses/CDDL-1.0>.
|
||||
* You may not use this file except in compliance with the license.
|
||||
*
|
||||
* CDDL HEADER END
|
||||
* A full copy of the text of the CDDL should have accompanied this
|
||||
* source. A copy of the CDDL is also available via the Internet at
|
||||
* https://opensource.org/license/CDDL-1.0.
|
||||
*/
|
||||
|
||||
/*
|
||||
|
||||
@@ -1,14 +1,13 @@
|
||||
// SPDX-License-Identifier: CDDL-1.0
|
||||
/*
|
||||
* CDDL HEADER START
|
||||
* This file and its contents are supplied under the terms of the
|
||||
* Common Development and Distribution License ("CDDL"), version 1.0.
|
||||
* You may only use this file in accordance with the terms of version
|
||||
* 1.0 of the CDDL.
|
||||
*
|
||||
* The contents of this file are subject to the terms of the
|
||||
* Common Development and Distribution License Version 1.0 (CDDL-1.0).
|
||||
* You can obtain a copy of the license from the top-level file
|
||||
* "OPENSOLARIS.LICENSE" or at <http://opensource.org/licenses/CDDL-1.0>.
|
||||
* You may not use this file except in compliance with the license.
|
||||
*
|
||||
* CDDL HEADER END
|
||||
* A full copy of the text of the CDDL should have accompanied this
|
||||
* source. A copy of the CDDL is also available via the Internet at
|
||||
* https://opensource.org/license/CDDL-1.0.
|
||||
*/
|
||||
|
||||
/*
|
||||
|
||||
@@ -1,23 +1,13 @@
|
||||
// SPDX-License-Identifier: CDDL-1.0
|
||||
/*
|
||||
* CDDL HEADER START
|
||||
* This file and its contents are supplied under the terms of the
|
||||
* Common Development and Distribution License ("CDDL"), version 1.0.
|
||||
* You may only use this file in accordance with the terms of version
|
||||
* 1.0 of the CDDL.
|
||||
*
|
||||
* The contents of this file are subject to the terms of the
|
||||
* Common Development and Distribution License (the "License").
|
||||
* You may not use this file except in compliance with the License.
|
||||
*
|
||||
* You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
|
||||
* or https://opensource.org/licenses/CDDL-1.0.
|
||||
* See the License for the specific language governing permissions
|
||||
* and limitations under the License.
|
||||
*
|
||||
* When distributing Covered Code, include this CDDL HEADER in each
|
||||
* file and include the License file at usr/src/OPENSOLARIS.LICENSE.
|
||||
* If applicable, add the following below this CDDL HEADER, with the
|
||||
* fields enclosed by brackets "[]" replaced with your own identifying
|
||||
* information: Portions Copyright [yyyy] [name of copyright owner]
|
||||
*
|
||||
* CDDL HEADER END
|
||||
* A full copy of the text of the CDDL should have accompanied this
|
||||
* source. A copy of the CDDL is also available via the Internet at
|
||||
* https://opensource.org/license/CDDL-1.0.
|
||||
*/
|
||||
|
||||
/*
|
||||
@@ -559,7 +549,7 @@ zfs_fm_recv(fmd_hdl_t *hdl, fmd_event_t *ep, nvlist_t *nvl, const char *class)
|
||||
uint64_t ena, pool_guid, vdev_guid, parent_guid;
|
||||
uint64_t checksum_n, checksum_t;
|
||||
uint64_t io_n, io_t;
|
||||
er_timeval_t pool_load;
|
||||
er_timeval_t pool_load = {0};
|
||||
er_timeval_t er_when;
|
||||
nvlist_t *detector;
|
||||
boolean_t pool_found = B_FALSE;
|
||||
|
||||
@@ -1,23 +1,13 @@
|
||||
// SPDX-License-Identifier: CDDL-1.0
|
||||
/*
|
||||
* CDDL HEADER START
|
||||
* This file and its contents are supplied under the terms of the
|
||||
* Common Development and Distribution License ("CDDL"), version 1.0.
|
||||
* You may only use this file in accordance with the terms of version
|
||||
* 1.0 of the CDDL.
|
||||
*
|
||||
* The contents of this file are subject to the terms of the
|
||||
* Common Development and Distribution License (the "License").
|
||||
* You may not use this file except in compliance with the License.
|
||||
*
|
||||
* You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
|
||||
* or https://opensource.org/licenses/CDDL-1.0.
|
||||
* See the License for the specific language governing permissions
|
||||
* and limitations under the License.
|
||||
*
|
||||
* When distributing Covered Code, include this CDDL HEADER in each
|
||||
* file and include the License file at usr/src/OPENSOLARIS.LICENSE.
|
||||
* If applicable, add the following below this CDDL HEADER, with the
|
||||
* fields enclosed by brackets "[]" replaced with your own identifying
|
||||
* information: Portions Copyright [yyyy] [name of copyright owner]
|
||||
*
|
||||
* CDDL HEADER END
|
||||
* A full copy of the text of the CDDL should have accompanied this
|
||||
* source. A copy of the CDDL is also available via the Internet at
|
||||
* https://opensource.org/license/CDDL-1.0.
|
||||
*/
|
||||
/*
|
||||
* Copyright (c) 2007, 2010, Oracle and/or its affiliates. All rights reserved.
|
||||
@@ -1080,8 +1070,63 @@ zfsdle_vdev_online(zpool_handle_t *zhp, void *data)
|
||||
zed_log_msg(LOG_INFO, "zfsdle_vdev_online: searching for '%s' in '%s'",
|
||||
devname, zpool_get_name(zhp));
|
||||
|
||||
if ((tgt = zpool_find_vdev_by_physpath(zhp, devname,
|
||||
&avail_spare, &l2cache, NULL)) != NULL) {
|
||||
tgt = zpool_find_vdev_by_physpath(zhp, devname,
|
||||
&avail_spare, &l2cache, NULL);
|
||||
|
||||
/*
|
||||
* A whole-disk event carries no vdev guid (the label lives on
|
||||
* the partition), and udev provides no ID_PATH on some buses,
|
||||
* so neither lookup above can match. Identify the vdev by
|
||||
* reading the label off the whole-disk partition instead: the
|
||||
* pool and vdev guids stored there don't depend on which name
|
||||
* the pool was imported with (by-id, by-path or a bare device
|
||||
* node), and they can't be forged by a stale config path left
|
||||
* behind in an unrelated pool.
|
||||
*/
|
||||
if (tgt == NULL && nvlist_lookup_uint64(udev_nvl, ZFS_EV_VDEV_GUID,
|
||||
&guid) != 0 && nvlist_lookup_string(udev_nvl, DEV_NAME,
|
||||
&tmp_devname) == 0 && !nvlist_exists(udev_nvl, DEV_IS_PART)) {
|
||||
nvlist_t *label = NULL;
|
||||
uint64_t label_pool_guid = 0, label_vdev_guid = 0;
|
||||
int fd;
|
||||
|
||||
strlcpy(devname, tmp_devname, MAXPATHLEN);
|
||||
zfs_append_partition(devname, MAXPATHLEN);
|
||||
|
||||
if ((fd = open(devname, O_RDONLY | O_CLOEXEC)) >= 0) {
|
||||
if (zpool_read_label(fd, &label, NULL) == 0 &&
|
||||
label != NULL &&
|
||||
nvlist_lookup_uint64(label,
|
||||
ZPOOL_CONFIG_POOL_GUID, &label_pool_guid) == 0 &&
|
||||
nvlist_lookup_uint64(label, ZPOOL_CONFIG_GUID,
|
||||
&label_vdev_guid) == 0 &&
|
||||
label_pool_guid == zpool_get_prop_int(zhp,
|
||||
ZPOOL_PROP_GUID, NULL)) {
|
||||
zed_log_msg(LOG_INFO, "zfsdle_vdev_online: "
|
||||
"matched vdev %llu by the label on '%s'",
|
||||
(u_longlong_t)label_vdev_guid, devname);
|
||||
|
||||
(void) snprintf(devname, MAXPATHLEN, "%llu",
|
||||
(u_longlong_t)label_vdev_guid);
|
||||
tgt = zpool_find_vdev(zhp, devname,
|
||||
&avail_spare, &l2cache, NULL);
|
||||
if (tgt != NULL && (avail_spare || l2cache))
|
||||
tgt = NULL;
|
||||
if (tgt != NULL) {
|
||||
uint64_t wd = 0;
|
||||
|
||||
(void) nvlist_lookup_uint64(tgt,
|
||||
ZPOOL_CONFIG_WHOLE_DISK, &wd);
|
||||
if (!wd)
|
||||
tgt = NULL;
|
||||
}
|
||||
}
|
||||
nvlist_free(label);
|
||||
(void) close(fd);
|
||||
}
|
||||
}
|
||||
|
||||
if (tgt != NULL) {
|
||||
const char *path;
|
||||
char fullpath[MAXPATHLEN];
|
||||
uint64_t wholedisk = 0;
|
||||
|
||||
@@ -1,23 +1,13 @@
|
||||
// SPDX-License-Identifier: CDDL-1.0
|
||||
/*
|
||||
* CDDL HEADER START
|
||||
* This file and its contents are supplied under the terms of the
|
||||
* Common Development and Distribution License ("CDDL"), version 1.0.
|
||||
* You may only use this file in accordance with the terms of version
|
||||
* 1.0 of the CDDL.
|
||||
*
|
||||
* The contents of this file are subject to the terms of the
|
||||
* Common Development and Distribution License (the "License").
|
||||
* You may not use this file except in compliance with the License.
|
||||
*
|
||||
* You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
|
||||
* or https://opensource.org/licenses/CDDL-1.0.
|
||||
* See the License for the specific language governing permissions
|
||||
* and limitations under the License.
|
||||
*
|
||||
* When distributing Covered Code, include this CDDL HEADER in each
|
||||
* file and include the License file at usr/src/OPENSOLARIS.LICENSE.
|
||||
* If applicable, add the following below this CDDL HEADER, with the
|
||||
* fields enclosed by brackets "[]" replaced with your own identifying
|
||||
* information: Portions Copyright [yyyy] [name of copyright owner]
|
||||
*
|
||||
* CDDL HEADER END
|
||||
* A full copy of the text of the CDDL should have accompanied this
|
||||
* source. A copy of the CDDL is also available via the Internet at
|
||||
* https://opensource.org/license/CDDL-1.0.
|
||||
*/
|
||||
/*
|
||||
* Copyright (c) 2006, 2010, Oracle and/or its affiliates. All rights reserved.
|
||||
|
||||
@@ -1,16 +1,19 @@
|
||||
// SPDX-License-Identifier: CDDL-1.0
|
||||
/*
|
||||
* This file and its contents are supplied under the terms of the
|
||||
* Common Development and Distribution License ("CDDL"), version 1.0.
|
||||
* You may only use this file in accordance with the terms of version
|
||||
* 1.0 of the CDDL.
|
||||
*
|
||||
* A full copy of the text of the CDDL should have accompanied this
|
||||
* source. A copy of the CDDL is also available via the Internet at
|
||||
* https://opensource.org/license/CDDL-1.0.
|
||||
*/
|
||||
/*
|
||||
* This file is part of the ZFS Event Daemon (ZED).
|
||||
*
|
||||
* Developed at Lawrence Livermore National Laboratory (LLNL-CODE-403049).
|
||||
* Copyright (C) 2013-2014 Lawrence Livermore National Security, LLC.
|
||||
* Refer to the OpenZFS git commit log for authoritative copyright attribution.
|
||||
*
|
||||
* The contents of this file are subject to the terms of the
|
||||
* Common Development and Distribution License Version 1.0 (CDDL-1.0).
|
||||
* You can obtain a copy of the license from the top-level file
|
||||
* "OPENSOLARIS.LICENSE" or at <http://opensource.org/licenses/CDDL-1.0>.
|
||||
* You may not use this file except in compliance with the license.
|
||||
*/
|
||||
|
||||
#include <errno.h>
|
||||
|
||||
@@ -2,15 +2,14 @@
|
||||
# SPDX-License-Identifier: CDDL-1.0
|
||||
# shellcheck disable=SC2154
|
||||
#
|
||||
# CDDL HEADER START
|
||||
# This file and its contents are supplied under the terms of the
|
||||
# Common Development and Distribution License ("CDDL"), version 1.0.
|
||||
# You may only use this file in accordance with the terms of version
|
||||
# 1.0 of the CDDL.
|
||||
#
|
||||
# The contents of this file are subject to the terms of the
|
||||
# Common Development and Distribution License Version 1.0 (CDDL-1.0).
|
||||
# You can obtain a copy of the license from the top-level file
|
||||
# "OPENSOLARIS.LICENSE" or at <http://opensource.org/licenses/CDDL-1.0>.
|
||||
# You may not use this file except in compliance with the license.
|
||||
#
|
||||
# CDDL HEADER END
|
||||
# A full copy of the text of the CDDL should have accompanied this
|
||||
# source. A copy of the CDDL is also available via the Internet at
|
||||
# https://opensource.org/license/CDDL-1.0.
|
||||
#
|
||||
|
||||
#
|
||||
|
||||
@@ -1,16 +1,19 @@
|
||||
// SPDX-License-Identifier: CDDL-1.0
|
||||
/*
|
||||
* This file and its contents are supplied under the terms of the
|
||||
* Common Development and Distribution License ("CDDL"), version 1.0.
|
||||
* You may only use this file in accordance with the terms of version
|
||||
* 1.0 of the CDDL.
|
||||
*
|
||||
* A full copy of the text of the CDDL should have accompanied this
|
||||
* source. A copy of the CDDL is also available via the Internet at
|
||||
* https://opensource.org/license/CDDL-1.0.
|
||||
*/
|
||||
/*
|
||||
* This file is part of the ZFS Event Daemon (ZED).
|
||||
*
|
||||
* Developed at Lawrence Livermore National Laboratory (LLNL-CODE-403049).
|
||||
* Copyright (C) 2013-2014 Lawrence Livermore National Security, LLC.
|
||||
* Refer to the OpenZFS git commit log for authoritative copyright attribution.
|
||||
*
|
||||
* The contents of this file are subject to the terms of the
|
||||
* Common Development and Distribution License Version 1.0 (CDDL-1.0).
|
||||
* You can obtain a copy of the license from the top-level file
|
||||
* "OPENSOLARIS.LICENSE" or at <http://opensource.org/licenses/CDDL-1.0>.
|
||||
* You may not use this file except in compliance with the license.
|
||||
*/
|
||||
|
||||
#ifndef ZED_H
|
||||
|
||||
@@ -1,16 +1,19 @@
|
||||
// SPDX-License-Identifier: CDDL-1.0
|
||||
/*
|
||||
* This file and its contents are supplied under the terms of the
|
||||
* Common Development and Distribution License ("CDDL"), version 1.0.
|
||||
* You may only use this file in accordance with the terms of version
|
||||
* 1.0 of the CDDL.
|
||||
*
|
||||
* A full copy of the text of the CDDL should have accompanied this
|
||||
* source. A copy of the CDDL is also available via the Internet at
|
||||
* https://opensource.org/license/CDDL-1.0.
|
||||
*/
|
||||
/*
|
||||
* This file is part of the ZFS Event Daemon (ZED).
|
||||
*
|
||||
* Developed at Lawrence Livermore National Laboratory (LLNL-CODE-403049).
|
||||
* Copyright (C) 2013-2014 Lawrence Livermore National Security, LLC.
|
||||
* Refer to the OpenZFS git commit log for authoritative copyright attribution.
|
||||
*
|
||||
* The contents of this file are subject to the terms of the
|
||||
* Common Development and Distribution License Version 1.0 (CDDL-1.0).
|
||||
* You can obtain a copy of the license from the top-level file
|
||||
* "OPENSOLARIS.LICENSE" or at <http://opensource.org/licenses/CDDL-1.0>.
|
||||
* You may not use this file except in compliance with the license.
|
||||
*/
|
||||
|
||||
#include <assert.h>
|
||||
|
||||
@@ -1,16 +1,19 @@
|
||||
// SPDX-License-Identifier: CDDL-1.0
|
||||
/*
|
||||
* This file and its contents are supplied under the terms of the
|
||||
* Common Development and Distribution License ("CDDL"), version 1.0.
|
||||
* You may only use this file in accordance with the terms of version
|
||||
* 1.0 of the CDDL.
|
||||
*
|
||||
* A full copy of the text of the CDDL should have accompanied this
|
||||
* source. A copy of the CDDL is also available via the Internet at
|
||||
* https://opensource.org/license/CDDL-1.0.
|
||||
*/
|
||||
/*
|
||||
* This file is part of the ZFS Event Daemon (ZED).
|
||||
*
|
||||
* Developed at Lawrence Livermore National Laboratory (LLNL-CODE-403049).
|
||||
* Copyright (C) 2013-2014 Lawrence Livermore National Security, LLC.
|
||||
* Refer to the OpenZFS git commit log for authoritative copyright attribution.
|
||||
*
|
||||
* The contents of this file are subject to the terms of the
|
||||
* Common Development and Distribution License Version 1.0 (CDDL-1.0).
|
||||
* You can obtain a copy of the license from the top-level file
|
||||
* "OPENSOLARIS.LICENSE" or at <http://opensource.org/licenses/CDDL-1.0>.
|
||||
* You may not use this file except in compliance with the license.
|
||||
*/
|
||||
|
||||
#ifndef ZED_CONF_H
|
||||
|
||||
@@ -1,14 +1,13 @@
|
||||
// SPDX-License-Identifier: CDDL-1.0
|
||||
/*
|
||||
* CDDL HEADER START
|
||||
* This file and its contents are supplied under the terms of the
|
||||
* Common Development and Distribution License ("CDDL"), version 1.0.
|
||||
* You may only use this file in accordance with the terms of version
|
||||
* 1.0 of the CDDL.
|
||||
*
|
||||
* The contents of this file are subject to the terms of the
|
||||
* Common Development and Distribution License Version 1.0 (CDDL-1.0).
|
||||
* You can obtain a copy of the license from the top-level file
|
||||
* "OPENSOLARIS.LICENSE" or at <http://opensource.org/licenses/CDDL-1.0>.
|
||||
* You may not use this file except in compliance with the license.
|
||||
*
|
||||
* CDDL HEADER END
|
||||
* A full copy of the text of the CDDL should have accompanied this
|
||||
* source. A copy of the CDDL is also available via the Internet at
|
||||
* https://opensource.org/license/CDDL-1.0.
|
||||
*/
|
||||
|
||||
/*
|
||||
@@ -242,6 +241,9 @@ zed_udev_monitor(void *arg)
|
||||
part != NULL && part[0] != '\0') {
|
||||
const char *devname =
|
||||
udev_device_get_property_value(dev, "DEVNAME");
|
||||
const char *resize =
|
||||
udev_device_get_property_value(dev, "RESIZE");
|
||||
const char *dev_action = udev_device_get_action(dev);
|
||||
|
||||
if (strcmp(part, "atari") == 0) {
|
||||
zed_log_msg(LOG_INFO,
|
||||
@@ -249,6 +251,19 @@ zed_udev_monitor(void *arg)
|
||||
"but we're going to assume it's a false "
|
||||
"positive and still use it (issue #13497)",
|
||||
__func__, devname);
|
||||
} else if (dev_action != NULL &&
|
||||
strcmp(dev_action, "change") == 0 &&
|
||||
resize != NULL && strcmp(resize, "1") == 0) {
|
||||
/*
|
||||
* A capacity change is reported on the disk
|
||||
* itself, not on its partitions, so this is
|
||||
* the only chance to notice it. Pass the
|
||||
* event on for a possible autoexpand of a
|
||||
* whole-disk vdev (issue #12505).
|
||||
*/
|
||||
zed_log_msg(LOG_INFO,
|
||||
"%s: %s capacity change on partitioned "
|
||||
"disk", __func__, devname);
|
||||
} else {
|
||||
zed_log_msg(LOG_INFO,
|
||||
"%s: skip %s since it has a %s partition "
|
||||
|
||||
@@ -1,14 +1,13 @@
|
||||
// SPDX-License-Identifier: CDDL-1.0
|
||||
/*
|
||||
* CDDL HEADER START
|
||||
* This file and its contents are supplied under the terms of the
|
||||
* Common Development and Distribution License ("CDDL"), version 1.0.
|
||||
* You may only use this file in accordance with the terms of version
|
||||
* 1.0 of the CDDL.
|
||||
*
|
||||
* The contents of this file are subject to the terms of the
|
||||
* Common Development and Distribution License Version 1.0 (CDDL-1.0).
|
||||
* You can obtain a copy of the license from the top-level file
|
||||
* "OPENSOLARIS.LICENSE" or at <http://opensource.org/licenses/CDDL-1.0>.
|
||||
* You may not use this file except in compliance with the license.
|
||||
*
|
||||
* CDDL HEADER END
|
||||
* A full copy of the text of the CDDL should have accompanied this
|
||||
* source. A copy of the CDDL is also available via the Internet at
|
||||
* https://opensource.org/license/CDDL-1.0.
|
||||
*/
|
||||
|
||||
/*
|
||||
|
||||
@@ -1,16 +1,19 @@
|
||||
// SPDX-License-Identifier: CDDL-1.0
|
||||
/*
|
||||
* This file and its contents are supplied under the terms of the
|
||||
* Common Development and Distribution License ("CDDL"), version 1.0.
|
||||
* You may only use this file in accordance with the terms of version
|
||||
* 1.0 of the CDDL.
|
||||
*
|
||||
* A full copy of the text of the CDDL should have accompanied this
|
||||
* source. A copy of the CDDL is also available via the Internet at
|
||||
* https://opensource.org/license/CDDL-1.0.
|
||||
*/
|
||||
/*
|
||||
* This file is part of the ZFS Event Daemon (ZED).
|
||||
*
|
||||
* Developed at Lawrence Livermore National Laboratory (LLNL-CODE-403049).
|
||||
* Copyright (C) 2013-2014 Lawrence Livermore National Security, LLC.
|
||||
* Refer to the OpenZFS git commit log for authoritative copyright attribution.
|
||||
*
|
||||
* The contents of this file are subject to the terms of the
|
||||
* Common Development and Distribution License Version 1.0 (CDDL-1.0).
|
||||
* You can obtain a copy of the license from the top-level file
|
||||
* "OPENSOLARIS.LICENSE" or at <http://opensource.org/licenses/CDDL-1.0>.
|
||||
* You may not use this file except in compliance with the license.
|
||||
*/
|
||||
|
||||
#include <ctype.h>
|
||||
|
||||
@@ -1,16 +1,19 @@
|
||||
// SPDX-License-Identifier: CDDL-1.0
|
||||
/*
|
||||
* This file and its contents are supplied under the terms of the
|
||||
* Common Development and Distribution License ("CDDL"), version 1.0.
|
||||
* You may only use this file in accordance with the terms of version
|
||||
* 1.0 of the CDDL.
|
||||
*
|
||||
* A full copy of the text of the CDDL should have accompanied this
|
||||
* source. A copy of the CDDL is also available via the Internet at
|
||||
* https://opensource.org/license/CDDL-1.0.
|
||||
*/
|
||||
/*
|
||||
* This file is part of the ZFS Event Daemon (ZED).
|
||||
*
|
||||
* Developed at Lawrence Livermore National Laboratory (LLNL-CODE-403049).
|
||||
* Copyright (C) 2013-2014 Lawrence Livermore National Security, LLC.
|
||||
* Refer to the OpenZFS git commit log for authoritative copyright attribution.
|
||||
*
|
||||
* The contents of this file are subject to the terms of the
|
||||
* Common Development and Distribution License Version 1.0 (CDDL-1.0).
|
||||
* You can obtain a copy of the license from the top-level file
|
||||
* "OPENSOLARIS.LICENSE" or at <http://opensource.org/licenses/CDDL-1.0>.
|
||||
* You may not use this file except in compliance with the license.
|
||||
*/
|
||||
|
||||
#ifndef ZED_EVENT_H
|
||||
|
||||
@@ -1,16 +1,19 @@
|
||||
// SPDX-License-Identifier: CDDL-1.0
|
||||
/*
|
||||
* This file and its contents are supplied under the terms of the
|
||||
* Common Development and Distribution License ("CDDL"), version 1.0.
|
||||
* You may only use this file in accordance with the terms of version
|
||||
* 1.0 of the CDDL.
|
||||
*
|
||||
* A full copy of the text of the CDDL should have accompanied this
|
||||
* source. A copy of the CDDL is also available via the Internet at
|
||||
* https://opensource.org/license/CDDL-1.0.
|
||||
*/
|
||||
/*
|
||||
* This file is part of the ZFS Event Daemon (ZED).
|
||||
*
|
||||
* Developed at Lawrence Livermore National Laboratory (LLNL-CODE-403049).
|
||||
* Copyright (C) 2013-2014 Lawrence Livermore National Security, LLC.
|
||||
* Refer to the OpenZFS git commit log for authoritative copyright attribution.
|
||||
*
|
||||
* The contents of this file are subject to the terms of the
|
||||
* Common Development and Distribution License Version 1.0 (CDDL-1.0).
|
||||
* You can obtain a copy of the license from the top-level file
|
||||
* "OPENSOLARIS.LICENSE" or at <http://opensource.org/licenses/CDDL-1.0>.
|
||||
* You may not use this file except in compliance with the license.
|
||||
*/
|
||||
|
||||
#include <assert.h>
|
||||
|
||||
@@ -1,16 +1,19 @@
|
||||
// SPDX-License-Identifier: CDDL-1.0
|
||||
/*
|
||||
* This file and its contents are supplied under the terms of the
|
||||
* Common Development and Distribution License ("CDDL"), version 1.0.
|
||||
* You may only use this file in accordance with the terms of version
|
||||
* 1.0 of the CDDL.
|
||||
*
|
||||
* A full copy of the text of the CDDL should have accompanied this
|
||||
* source. A copy of the CDDL is also available via the Internet at
|
||||
* https://opensource.org/license/CDDL-1.0.
|
||||
*/
|
||||
/*
|
||||
* This file is part of the ZFS Event Daemon (ZED).
|
||||
*
|
||||
* Developed at Lawrence Livermore National Laboratory (LLNL-CODE-403049).
|
||||
* Copyright (C) 2013-2014 Lawrence Livermore National Security, LLC.
|
||||
* Refer to the OpenZFS git commit log for authoritative copyright attribution.
|
||||
*
|
||||
* The contents of this file are subject to the terms of the
|
||||
* Common Development and Distribution License Version 1.0 (CDDL-1.0).
|
||||
* You can obtain a copy of the license from the top-level file
|
||||
* "OPENSOLARIS.LICENSE" or at <http://opensource.org/licenses/CDDL-1.0>.
|
||||
* You may not use this file except in compliance with the license.
|
||||
*/
|
||||
|
||||
#ifndef ZED_EXEC_H
|
||||
|
||||
@@ -1,16 +1,19 @@
|
||||
// SPDX-License-Identifier: CDDL-1.0
|
||||
/*
|
||||
* This file and its contents are supplied under the terms of the
|
||||
* Common Development and Distribution License ("CDDL"), version 1.0.
|
||||
* You may only use this file in accordance with the terms of version
|
||||
* 1.0 of the CDDL.
|
||||
*
|
||||
* A full copy of the text of the CDDL should have accompanied this
|
||||
* source. A copy of the CDDL is also available via the Internet at
|
||||
* https://opensource.org/license/CDDL-1.0.
|
||||
*/
|
||||
/*
|
||||
* This file is part of the ZFS Event Daemon (ZED).
|
||||
*
|
||||
* Developed at Lawrence Livermore National Laboratory (LLNL-CODE-403049).
|
||||
* Copyright (C) 2013-2014 Lawrence Livermore National Security, LLC.
|
||||
* Refer to the OpenZFS git commit log for authoritative copyright attribution.
|
||||
*
|
||||
* The contents of this file are subject to the terms of the
|
||||
* Common Development and Distribution License Version 1.0 (CDDL-1.0).
|
||||
* You can obtain a copy of the license from the top-level file
|
||||
* "OPENSOLARIS.LICENSE" or at <http://opensource.org/licenses/CDDL-1.0>.
|
||||
* You may not use this file except in compliance with the license.
|
||||
*/
|
||||
|
||||
#include <dirent.h>
|
||||
|
||||
@@ -1,16 +1,19 @@
|
||||
// SPDX-License-Identifier: CDDL-1.0
|
||||
/*
|
||||
* This file and its contents are supplied under the terms of the
|
||||
* Common Development and Distribution License ("CDDL"), version 1.0.
|
||||
* You may only use this file in accordance with the terms of version
|
||||
* 1.0 of the CDDL.
|
||||
*
|
||||
* A full copy of the text of the CDDL should have accompanied this
|
||||
* source. A copy of the CDDL is also available via the Internet at
|
||||
* https://opensource.org/license/CDDL-1.0.
|
||||
*/
|
||||
/*
|
||||
* This file is part of the ZFS Event Daemon (ZED).
|
||||
*
|
||||
* Developed at Lawrence Livermore National Laboratory (LLNL-CODE-403049).
|
||||
* Copyright (C) 2013-2014 Lawrence Livermore National Security, LLC.
|
||||
* Refer to the OpenZFS git commit log for authoritative copyright attribution.
|
||||
*
|
||||
* The contents of this file are subject to the terms of the
|
||||
* Common Development and Distribution License Version 1.0 (CDDL-1.0).
|
||||
* You can obtain a copy of the license from the top-level file
|
||||
* "OPENSOLARIS.LICENSE" or at <http://opensource.org/licenses/CDDL-1.0>.
|
||||
* You may not use this file except in compliance with the license.
|
||||
*/
|
||||
|
||||
#ifndef ZED_FILE_H
|
||||
|
||||
@@ -1,16 +1,19 @@
|
||||
// SPDX-License-Identifier: CDDL-1.0
|
||||
/*
|
||||
* This file and its contents are supplied under the terms of the
|
||||
* Common Development and Distribution License ("CDDL"), version 1.0.
|
||||
* You may only use this file in accordance with the terms of version
|
||||
* 1.0 of the CDDL.
|
||||
*
|
||||
* A full copy of the text of the CDDL should have accompanied this
|
||||
* source. A copy of the CDDL is also available via the Internet at
|
||||
* https://opensource.org/license/CDDL-1.0.
|
||||
*/
|
||||
/*
|
||||
* This file is part of the ZFS Event Daemon (ZED).
|
||||
*
|
||||
* Developed at Lawrence Livermore National Laboratory (LLNL-CODE-403049).
|
||||
* Copyright (C) 2013-2014 Lawrence Livermore National Security, LLC.
|
||||
* Refer to the OpenZFS git commit log for authoritative copyright attribution.
|
||||
*
|
||||
* The contents of this file are subject to the terms of the
|
||||
* Common Development and Distribution License Version 1.0 (CDDL-1.0).
|
||||
* You can obtain a copy of the license from the top-level file
|
||||
* "OPENSOLARIS.LICENSE" or at <http://opensource.org/licenses/CDDL-1.0>.
|
||||
* You may not use this file except in compliance with the license.
|
||||
*/
|
||||
|
||||
#include <assert.h>
|
||||
|
||||
@@ -1,16 +1,19 @@
|
||||
// SPDX-License-Identifier: CDDL-1.0
|
||||
/*
|
||||
* This file and its contents are supplied under the terms of the
|
||||
* Common Development and Distribution License ("CDDL"), version 1.0.
|
||||
* You may only use this file in accordance with the terms of version
|
||||
* 1.0 of the CDDL.
|
||||
*
|
||||
* A full copy of the text of the CDDL should have accompanied this
|
||||
* source. A copy of the CDDL is also available via the Internet at
|
||||
* https://opensource.org/license/CDDL-1.0.
|
||||
*/
|
||||
/*
|
||||
* This file is part of the ZFS Event Daemon (ZED).
|
||||
*
|
||||
* Developed at Lawrence Livermore National Laboratory (LLNL-CODE-403049).
|
||||
* Copyright (C) 2013-2014 Lawrence Livermore National Security, LLC.
|
||||
* Refer to the OpenZFS git commit log for authoritative copyright attribution.
|
||||
*
|
||||
* The contents of this file are subject to the terms of the
|
||||
* Common Development and Distribution License Version 1.0 (CDDL-1.0).
|
||||
* You can obtain a copy of the license from the top-level file
|
||||
* "OPENSOLARIS.LICENSE" or at <http://opensource.org/licenses/CDDL-1.0>.
|
||||
* You may not use this file except in compliance with the license.
|
||||
*/
|
||||
|
||||
#ifndef ZED_LOG_H
|
||||
|
||||
@@ -1,16 +1,19 @@
|
||||
// SPDX-License-Identifier: CDDL-1.0
|
||||
/*
|
||||
* This file and its contents are supplied under the terms of the
|
||||
* Common Development and Distribution License ("CDDL"), version 1.0.
|
||||
* You may only use this file in accordance with the terms of version
|
||||
* 1.0 of the CDDL.
|
||||
*
|
||||
* A full copy of the text of the CDDL should have accompanied this
|
||||
* source. A copy of the CDDL is also available via the Internet at
|
||||
* https://opensource.org/license/CDDL-1.0.
|
||||
*/
|
||||
/*
|
||||
* This file is part of the ZFS Event Daemon (ZED).
|
||||
*
|
||||
* Developed at Lawrence Livermore National Laboratory (LLNL-CODE-403049).
|
||||
* Copyright (C) 2013-2014 Lawrence Livermore National Security, LLC.
|
||||
* Refer to the OpenZFS git commit log for authoritative copyright attribution.
|
||||
*
|
||||
* The contents of this file are subject to the terms of the
|
||||
* Common Development and Distribution License Version 1.0 (CDDL-1.0).
|
||||
* You can obtain a copy of the license from the top-level file
|
||||
* "OPENSOLARIS.LICENSE" or at <http://opensource.org/licenses/CDDL-1.0>.
|
||||
* You may not use this file except in compliance with the license.
|
||||
*/
|
||||
|
||||
#include <assert.h>
|
||||
@@ -115,6 +118,7 @@ _zed_strings_node_create(const char *key, const char *val)
|
||||
} else {
|
||||
np->key = np->val;
|
||||
}
|
||||
/* cppcheck-suppress memleak */
|
||||
return (np);
|
||||
|
||||
nomem:
|
||||
|
||||
@@ -1,16 +1,19 @@
|
||||
// SPDX-License-Identifier: CDDL-1.0
|
||||
/*
|
||||
* This file and its contents are supplied under the terms of the
|
||||
* Common Development and Distribution License ("CDDL"), version 1.0.
|
||||
* You may only use this file in accordance with the terms of version
|
||||
* 1.0 of the CDDL.
|
||||
*
|
||||
* A full copy of the text of the CDDL should have accompanied this
|
||||
* source. A copy of the CDDL is also available via the Internet at
|
||||
* https://opensource.org/license/CDDL-1.0.
|
||||
*/
|
||||
/*
|
||||
* This file is part of the ZFS Event Daemon (ZED).
|
||||
*
|
||||
* Developed at Lawrence Livermore National Laboratory (LLNL-CODE-403049).
|
||||
* Copyright (C) 2013-2014 Lawrence Livermore National Security, LLC.
|
||||
* Refer to the OpenZFS git commit log for authoritative copyright attribution.
|
||||
*
|
||||
* The contents of this file are subject to the terms of the
|
||||
* Common Development and Distribution License Version 1.0 (CDDL-1.0).
|
||||
* You can obtain a copy of the license from the top-level file
|
||||
* "OPENSOLARIS.LICENSE" or at <http://opensource.org/licenses/CDDL-1.0>.
|
||||
* You may not use this file except in compliance with the license.
|
||||
*/
|
||||
|
||||
#ifndef ZED_STRINGS_H
|
||||
|
||||
@@ -1,23 +1,13 @@
|
||||
// SPDX-License-Identifier: CDDL-1.0
|
||||
/*
|
||||
* CDDL HEADER START
|
||||
* This file and its contents are supplied under the terms of the
|
||||
* Common Development and Distribution License ("CDDL"), version 1.0.
|
||||
* You may only use this file in accordance with the terms of version
|
||||
* 1.0 of the CDDL.
|
||||
*
|
||||
* The contents of this file are subject to the terms of the
|
||||
* Common Development and Distribution License (the "License").
|
||||
* You may not use this file except in compliance with the License.
|
||||
*
|
||||
* You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
|
||||
* or https://opensource.org/licenses/CDDL-1.0.
|
||||
* See the License for the specific language governing permissions
|
||||
* and limitations under the License.
|
||||
*
|
||||
* When distributing Covered Code, include this CDDL HEADER in each
|
||||
* file and include the License file at usr/src/OPENSOLARIS.LICENSE.
|
||||
* If applicable, add the following below this CDDL HEADER, with the
|
||||
* fields enclosed by brackets "[]" replaced with your own identifying
|
||||
* information: Portions Copyright [yyyy] [name of copyright owner]
|
||||
*
|
||||
* CDDL HEADER END
|
||||
* A full copy of the text of the CDDL should have accompanied this
|
||||
* source. A copy of the CDDL is also available via the Internet at
|
||||
* https://opensource.org/license/CDDL-1.0.
|
||||
*/
|
||||
|
||||
/*
|
||||
|
||||
@@ -1,23 +1,13 @@
|
||||
// SPDX-License-Identifier: CDDL-1.0
|
||||
/*
|
||||
* CDDL HEADER START
|
||||
* This file and its contents are supplied under the terms of the
|
||||
* Common Development and Distribution License ("CDDL"), version 1.0.
|
||||
* You may only use this file in accordance with the terms of version
|
||||
* 1.0 of the CDDL.
|
||||
*
|
||||
* The contents of this file are subject to the terms of the
|
||||
* Common Development and Distribution License (the "License").
|
||||
* You may not use this file except in compliance with the License.
|
||||
*
|
||||
* You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
|
||||
* or https://opensource.org/licenses/CDDL-1.0.
|
||||
* See the License for the specific language governing permissions
|
||||
* and limitations under the License.
|
||||
*
|
||||
* When distributing Covered Code, include this CDDL HEADER in each
|
||||
* file and include the License file at usr/src/OPENSOLARIS.LICENSE.
|
||||
* If applicable, add the following below this CDDL HEADER, with the
|
||||
* fields enclosed by brackets "[]" replaced with your own identifying
|
||||
* information: Portions Copyright [yyyy] [name of copyright owner]
|
||||
*
|
||||
* CDDL HEADER END
|
||||
* A full copy of the text of the CDDL should have accompanied this
|
||||
* source. A copy of the CDDL is also available via the Internet at
|
||||
* https://opensource.org/license/CDDL-1.0.
|
||||
*/
|
||||
|
||||
/*
|
||||
|
||||
@@ -1,23 +1,13 @@
|
||||
// SPDX-License-Identifier: CDDL-1.0
|
||||
/*
|
||||
* CDDL HEADER START
|
||||
* This file and its contents are supplied under the terms of the
|
||||
* Common Development and Distribution License ("CDDL"), version 1.0.
|
||||
* You may only use this file in accordance with the terms of version
|
||||
* 1.0 of the CDDL.
|
||||
*
|
||||
* The contents of this file are subject to the terms of the
|
||||
* Common Development and Distribution License (the "License").
|
||||
* You may not use this file except in compliance with the License.
|
||||
*
|
||||
* You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
|
||||
* or https://opensource.org/licenses/CDDL-1.0.
|
||||
* See the License for the specific language governing permissions
|
||||
* and limitations under the License.
|
||||
*
|
||||
* When distributing Covered Code, include this CDDL HEADER in each
|
||||
* file and include the License file at usr/src/OPENSOLARIS.LICENSE.
|
||||
* If applicable, add the following below this CDDL HEADER, with the
|
||||
* fields enclosed by brackets "[]" replaced with your own identifying
|
||||
* information: Portions Copyright [yyyy] [name of copyright owner]
|
||||
*
|
||||
* CDDL HEADER END
|
||||
* A full copy of the text of the CDDL should have accompanied this
|
||||
* source. A copy of the CDDL is also available via the Internet at
|
||||
* https://opensource.org/license/CDDL-1.0.
|
||||
*/
|
||||
|
||||
/*
|
||||
@@ -420,7 +410,7 @@ get_usage(zfs_help_t idx)
|
||||
return (gettext("\tdiff [-FHth] <snapshot> "
|
||||
"[snapshot|filesystem]\n"));
|
||||
case HELP_BOOKMARK:
|
||||
return (gettext("\tbookmark <snapshot|bookmark> "
|
||||
return (gettext("\tbookmark [-r] <snapshot|bookmark> "
|
||||
"<newbookmark>\n"));
|
||||
case HELP_CHANNEL_PROGRAM:
|
||||
return (gettext("\tprogram [-jn] [-t <instruction limit>] "
|
||||
@@ -7715,6 +7705,8 @@ unshare_unmount_path(int op, char *path, int flags, boolean_t is_manual)
|
||||
}
|
||||
(void) fprintf(stderr, gettext("warning: %s not in"
|
||||
"/proc/self/mounts\n"), path);
|
||||
/* libzfs-internal flags; umount2(2) rejects them */
|
||||
flags &= ~(MS_CRYPT | MS_OVERLAY);
|
||||
if ((ret = umount2(path, flags)) != 0)
|
||||
(void) fprintf(stderr, gettext("%s: %s\n"), path,
|
||||
strerror(errno));
|
||||
@@ -8212,11 +8204,92 @@ out:
|
||||
return (err != 0);
|
||||
}
|
||||
|
||||
typedef struct bookmark_cbdata {
|
||||
nvlist_t *cb_nvl;
|
||||
const char *cb_snapname; /* source snapshot name (after '@') */
|
||||
const char *cb_bookname; /* new bookmark name (after '#') */
|
||||
} bookmark_cbdata_t;
|
||||
|
||||
/*
|
||||
* zfs bookmark <fs@source>|<fs#source> <fs#bookmark>
|
||||
* Recursively gather "<dataset>#bookname" -> "<dataset>@snapname" pairs for
|
||||
* every descendant that actually has the source snapshot, mirroring the way
|
||||
* "zfs snapshot -r" collects its targets. Descendants that lack the snapshot
|
||||
* (or whose name is too long to form the pair) are skipped rather than failing
|
||||
* the whole request. Unlike snapshotting, bookmarking only needs the snapshot
|
||||
* to exist, so an inconsistent (e.g. mid-receive) dataset is not skipped.
|
||||
*/
|
||||
static int
|
||||
zfs_bookmark_cb(zfs_handle_t *zhp, void *arg)
|
||||
{
|
||||
bookmark_cbdata_t *cb = arg;
|
||||
char snap[ZFS_MAX_DATASET_NAME_LEN];
|
||||
char book[ZFS_MAX_DATASET_NAME_LEN];
|
||||
int rv = 0;
|
||||
int n;
|
||||
|
||||
n = snprintf(snap, sizeof (snap), "%s@%s", zfs_get_name(zhp),
|
||||
cb->cb_snapname);
|
||||
if (n >= 0 && (size_t)n < sizeof (snap) && lzc_exists(snap)) {
|
||||
n = snprintf(book, sizeof (book), "%s#%s",
|
||||
zfs_get_name(zhp), cb->cb_bookname);
|
||||
if (n >= 0 && (size_t)n < sizeof (book))
|
||||
fnvlist_add_string(cb->cb_nvl, book, snap);
|
||||
}
|
||||
|
||||
rv = zfs_iter_filesystems_v2(zhp, 0, zfs_bookmark_cb, cb);
|
||||
zfs_close(zhp);
|
||||
return (rv);
|
||||
}
|
||||
|
||||
static void
|
||||
zfs_bookmark_perror(const char *bookname, int err)
|
||||
{
|
||||
const char *err_msg = NULL;
|
||||
char errbuf[1024];
|
||||
|
||||
(void) snprintf(errbuf, sizeof (errbuf),
|
||||
dgettext(TEXT_DOMAIN, "cannot create bookmark '%s'"), bookname);
|
||||
|
||||
switch (err) {
|
||||
case EXDEV:
|
||||
err_msg = "bookmark is in a different pool";
|
||||
break;
|
||||
case ZFS_ERR_BOOKMARK_SOURCE_NOT_ANCESTOR:
|
||||
err_msg = "source is not an ancestor of the "
|
||||
"new bookmark's dataset";
|
||||
break;
|
||||
case EEXIST:
|
||||
err_msg = "bookmark exists";
|
||||
break;
|
||||
case EINVAL:
|
||||
err_msg = "invalid argument";
|
||||
break;
|
||||
case ENOTSUP:
|
||||
err_msg = "bookmark feature not enabled";
|
||||
break;
|
||||
case ENOSPC:
|
||||
err_msg = "out of space";
|
||||
break;
|
||||
case ENOENT:
|
||||
err_msg = "dataset does not exist";
|
||||
break;
|
||||
default:
|
||||
(void) zfs_standard_error(g_zfs, err, errbuf);
|
||||
break;
|
||||
}
|
||||
if (err_msg != NULL) {
|
||||
(void) fprintf(stderr, "%s: %s\n", errbuf,
|
||||
dgettext(TEXT_DOMAIN, err_msg));
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* zfs bookmark [-r] <fs@source>|<fs#source> <fs#bookmark>
|
||||
*
|
||||
* Creates a bookmark with the given name from the source snapshot
|
||||
* or creates a copy of an existing source bookmark.
|
||||
* or creates a copy of an existing source bookmark. With -r, a bookmark
|
||||
* is created for the source snapshot of every descendant dataset that has
|
||||
* one.
|
||||
*/
|
||||
static int
|
||||
zfs_do_bookmark(int argc, char **argv)
|
||||
@@ -8225,12 +8298,17 @@ zfs_do_bookmark(int argc, char **argv)
|
||||
char expbuf[ZFS_MAX_DATASET_NAME_LEN];
|
||||
int source_type;
|
||||
nvlist_t *nvl;
|
||||
nvlist_t *errlist = NULL;
|
||||
boolean_t recursive = B_FALSE;
|
||||
int ret = 0;
|
||||
int c;
|
||||
|
||||
/* check options */
|
||||
while ((c = getopt(argc, argv, "")) != -1) {
|
||||
while ((c = getopt(argc, argv, "r")) != -1) {
|
||||
switch (c) {
|
||||
case 'r':
|
||||
recursive = B_TRUE;
|
||||
break;
|
||||
case '?':
|
||||
(void) fprintf(stderr,
|
||||
gettext("invalid option '%c'\n"), optopt);
|
||||
@@ -8308,6 +8386,12 @@ zfs_do_bookmark(int argc, char **argv)
|
||||
default: abort();
|
||||
}
|
||||
|
||||
if (recursive && source_type != ZFS_TYPE_SNAPSHOT) {
|
||||
(void) fprintf(stderr, gettext("recursive bookmarks (-r) can "
|
||||
"only be created from a snapshot source\n"));
|
||||
goto usage;
|
||||
}
|
||||
|
||||
/* test the source exists */
|
||||
zfs_handle_t *zhp;
|
||||
zhp = zfs_open(g_zfs, source, source_type);
|
||||
@@ -8316,51 +8400,53 @@ zfs_do_bookmark(int argc, char **argv)
|
||||
zfs_close(zhp);
|
||||
|
||||
nvl = fnvlist_alloc();
|
||||
fnvlist_add_string(nvl, bookname, source);
|
||||
ret = lzc_bookmark(nvl, NULL);
|
||||
fnvlist_free(nvl);
|
||||
|
||||
if (recursive) {
|
||||
bookmark_cbdata_t cb = { 0 };
|
||||
char dsname[ZFS_MAX_DATASET_NAME_LEN];
|
||||
|
||||
/* recurse from the dataset the source snapshot belongs to */
|
||||
(void) strlcpy(dsname, source, sizeof (dsname));
|
||||
*strchr(dsname, '@') = '\0';
|
||||
|
||||
cb.cb_nvl = nvl;
|
||||
cb.cb_snapname = strchr(source, '@') + 1;
|
||||
cb.cb_bookname = strchr(bookname, '#') + 1;
|
||||
|
||||
zhp = zfs_open(g_zfs, dsname,
|
||||
ZFS_TYPE_FILESYSTEM | ZFS_TYPE_VOLUME);
|
||||
if (zhp == NULL) {
|
||||
fnvlist_free(nvl);
|
||||
goto usage;
|
||||
}
|
||||
/* zfs_bookmark_cb() closes zhp */
|
||||
if (zfs_bookmark_cb(zhp, &cb) != 0) {
|
||||
fnvlist_free(nvl);
|
||||
return (1);
|
||||
}
|
||||
} else {
|
||||
fnvlist_add_string(nvl, bookname, source);
|
||||
}
|
||||
|
||||
ret = lzc_bookmark(nvl, &errlist);
|
||||
|
||||
if (ret != 0) {
|
||||
const char *err_msg = NULL;
|
||||
char errbuf[1024];
|
||||
boolean_t reported = B_FALSE;
|
||||
|
||||
(void) snprintf(errbuf, sizeof (errbuf),
|
||||
dgettext(TEXT_DOMAIN,
|
||||
"cannot create bookmark '%s'"), bookname);
|
||||
|
||||
switch (ret) {
|
||||
case EXDEV:
|
||||
err_msg = "bookmark is in a different pool";
|
||||
break;
|
||||
case ZFS_ERR_BOOKMARK_SOURCE_NOT_ANCESTOR:
|
||||
err_msg = "source is not an ancestor of the "
|
||||
"new bookmark's dataset";
|
||||
break;
|
||||
case EEXIST:
|
||||
err_msg = "bookmark exists";
|
||||
break;
|
||||
case EINVAL:
|
||||
err_msg = "invalid argument";
|
||||
break;
|
||||
case ENOTSUP:
|
||||
err_msg = "bookmark feature not enabled";
|
||||
break;
|
||||
case ENOSPC:
|
||||
err_msg = "out of space";
|
||||
break;
|
||||
case ENOENT:
|
||||
err_msg = "dataset does not exist";
|
||||
break;
|
||||
default:
|
||||
(void) zfs_standard_error(g_zfs, ret, errbuf);
|
||||
break;
|
||||
}
|
||||
if (err_msg != NULL) {
|
||||
(void) fprintf(stderr, "%s: %s\n", errbuf,
|
||||
dgettext(TEXT_DOMAIN, err_msg));
|
||||
for (nvpair_t *pair = nvlist_next_nvpair(errlist, NULL);
|
||||
pair != NULL; pair = nvlist_next_nvpair(errlist, pair)) {
|
||||
zfs_bookmark_perror(nvpair_name(pair),
|
||||
fnvpair_value_int32(pair));
|
||||
reported = B_TRUE;
|
||||
}
|
||||
/* fall back to the overall error if none was itemized */
|
||||
if (!reported)
|
||||
zfs_bookmark_perror(bookname, ret);
|
||||
}
|
||||
|
||||
fnvlist_free(nvl);
|
||||
nvlist_free(errlist);
|
||||
|
||||
return (ret != 0);
|
||||
|
||||
usage:
|
||||
|
||||
@@ -1,23 +1,13 @@
|
||||
// SPDX-License-Identifier: CDDL-1.0
|
||||
/*
|
||||
* CDDL HEADER START
|
||||
* This file and its contents are supplied under the terms of the
|
||||
* Common Development and Distribution License ("CDDL"), version 1.0.
|
||||
* You may only use this file in accordance with the terms of version
|
||||
* 1.0 of the CDDL.
|
||||
*
|
||||
* The contents of this file are subject to the terms of the
|
||||
* Common Development and Distribution License (the "License").
|
||||
* You may not use this file except in compliance with the License.
|
||||
*
|
||||
* You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
|
||||
* or https://opensource.org/licenses/CDDL-1.0.
|
||||
* See the License for the specific language governing permissions
|
||||
* and limitations under the License.
|
||||
*
|
||||
* When distributing Covered Code, include this CDDL HEADER in each
|
||||
* file and include the License file at usr/src/OPENSOLARIS.LICENSE.
|
||||
* If applicable, add the following below this CDDL HEADER, with the
|
||||
* fields enclosed by brackets "[]" replaced with your own identifying
|
||||
* information: Portions Copyright [yyyy] [name of copyright owner]
|
||||
*
|
||||
* CDDL HEADER END
|
||||
* A full copy of the text of the CDDL should have accompanied this
|
||||
* source. A copy of the CDDL is also available via the Internet at
|
||||
* https://opensource.org/license/CDDL-1.0.
|
||||
*/
|
||||
|
||||
/*
|
||||
|
||||
@@ -1,23 +1,13 @@
|
||||
// SPDX-License-Identifier: CDDL-1.0
|
||||
/*
|
||||
* CDDL HEADER START
|
||||
* This file and its contents are supplied under the terms of the
|
||||
* Common Development and Distribution License ("CDDL"), version 1.0.
|
||||
* You may only use this file in accordance with the terms of version
|
||||
* 1.0 of the CDDL.
|
||||
*
|
||||
* The contents of this file are subject to the terms of the
|
||||
* Common Development and Distribution License (the "License").
|
||||
* You may not use this file except in compliance with the License.
|
||||
*
|
||||
* You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
|
||||
* or https://opensource.org/licenses/CDDL-1.0.
|
||||
* See the License for the specific language governing permissions
|
||||
* and limitations under the License.
|
||||
*
|
||||
* When distributing Covered Code, include this CDDL HEADER in each
|
||||
* file and include the License file at usr/src/OPENSOLARIS.LICENSE.
|
||||
* If applicable, add the following below this CDDL HEADER, with the
|
||||
* fields enclosed by brackets "[]" replaced with your own identifying
|
||||
* information: Portions Copyright [yyyy] [name of copyright owner]
|
||||
*
|
||||
* CDDL HEADER END
|
||||
* A full copy of the text of the CDDL should have accompanied this
|
||||
* source. A copy of the CDDL is also available via the Internet at
|
||||
* https://opensource.org/license/CDDL-1.0.
|
||||
*/
|
||||
/*
|
||||
* Copyright (c) 2017, Intel Corporation. All rights reserved.
|
||||
|
||||
@@ -1,23 +1,13 @@
|
||||
// SPDX-License-Identifier: CDDL-1.0
|
||||
/*
|
||||
* CDDL HEADER START
|
||||
* This file and its contents are supplied under the terms of the
|
||||
* Common Development and Distribution License ("CDDL"), version 1.0.
|
||||
* You may only use this file in accordance with the terms of version
|
||||
* 1.0 of the CDDL.
|
||||
*
|
||||
* The contents of this file are subject to the terms of the
|
||||
* Common Development and Distribution License (the "License").
|
||||
* You may not use this file except in compliance with the License.
|
||||
*
|
||||
* You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
|
||||
* or https://opensource.org/licenses/CDDL-1.0.
|
||||
* See the License for the specific language governing permissions
|
||||
* and limitations under the License.
|
||||
*
|
||||
* When distributing Covered Code, include this CDDL HEADER in each
|
||||
* file and include the License file at usr/src/OPENSOLARIS.LICENSE.
|
||||
* If applicable, add the following below this CDDL HEADER, with the
|
||||
* fields enclosed by brackets "[]" replaced with your own identifying
|
||||
* information: Portions Copyright [yyyy] [name of copyright owner]
|
||||
*
|
||||
* CDDL HEADER END
|
||||
* A full copy of the text of the CDDL should have accompanied this
|
||||
* source. A copy of the CDDL is also available via the Internet at
|
||||
* https://opensource.org/license/CDDL-1.0.
|
||||
*/
|
||||
/*
|
||||
* Copyright (c) 2005, 2010, Oracle and/or its affiliates. All rights reserved.
|
||||
|
||||
@@ -1,23 +1,13 @@
|
||||
// SPDX-License-Identifier: CDDL-1.0
|
||||
/*
|
||||
* CDDL HEADER START
|
||||
* This file and its contents are supplied under the terms of the
|
||||
* Common Development and Distribution License ("CDDL"), version 1.0.
|
||||
* You may only use this file in accordance with the terms of version
|
||||
* 1.0 of the CDDL.
|
||||
*
|
||||
* The contents of this file are subject to the terms of the
|
||||
* Common Development and Distribution License (the "License").
|
||||
* You may not use this file except in compliance with the License.
|
||||
*
|
||||
* You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
|
||||
* or https://opensource.org/licenses/CDDL-1.0.
|
||||
* See the License for the specific language governing permissions
|
||||
* and limitations under the License.
|
||||
*
|
||||
* When distributing Covered Code, include this CDDL HEADER in each
|
||||
* file and include the License file at usr/src/OPENSOLARIS.LICENSE.
|
||||
* If applicable, add the following below this CDDL HEADER, with the
|
||||
* fields enclosed by brackets "[]" replaced with your own identifying
|
||||
* information: Portions Copyright [yyyy] [name of copyright owner]
|
||||
*
|
||||
* CDDL HEADER END
|
||||
* A full copy of the text of the CDDL should have accompanied this
|
||||
* source. A copy of the CDDL is also available via the Internet at
|
||||
* https://opensource.org/license/CDDL-1.0.
|
||||
*/
|
||||
/*
|
||||
* Copyright (c) 2019 by Delphix. All rights reserved.
|
||||
|
||||
@@ -1,23 +1,13 @@
|
||||
// SPDX-License-Identifier: CDDL-1.0
|
||||
/*
|
||||
* CDDL HEADER START
|
||||
* This file and its contents are supplied under the terms of the
|
||||
* Common Development and Distribution License ("CDDL"), version 1.0.
|
||||
* You may only use this file in accordance with the terms of version
|
||||
* 1.0 of the CDDL.
|
||||
*
|
||||
* The contents of this file are subject to the terms of the
|
||||
* Common Development and Distribution License (the "License").
|
||||
* You may not use this file except in compliance with the License.
|
||||
*
|
||||
* You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
|
||||
* or https://opensource.org/licenses/CDDL-1.0.
|
||||
* See the License for the specific language governing permissions
|
||||
* and limitations under the License.
|
||||
*
|
||||
* When distributing Covered Code, include this CDDL HEADER in each
|
||||
* file and include the License file at usr/src/OPENSOLARIS.LICENSE.
|
||||
* If applicable, add the following below this CDDL HEADER, with the
|
||||
* fields enclosed by brackets "[]" replaced with your own identifying
|
||||
* information: Portions Copyright [yyyy] [name of copyright owner]
|
||||
*
|
||||
* CDDL HEADER END
|
||||
* A full copy of the text of the CDDL should have accompanied this
|
||||
* source. A copy of the CDDL is also available via the Internet at
|
||||
* https://opensource.org/license/CDDL-1.0.
|
||||
*/
|
||||
|
||||
/*
|
||||
@@ -34,9 +24,39 @@
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <sys/stat.h>
|
||||
#include <time.h>
|
||||
#include <unistd.h>
|
||||
|
||||
/*
|
||||
* Fill buf with len bytes from the system CSPRNG. Returns 0 on success
|
||||
* and -1 on failure, with errno set.
|
||||
*/
|
||||
static int
|
||||
get_random_bytes(void *buf, size_t len)
|
||||
{
|
||||
int fd = open("/dev/urandom", O_RDONLY | O_CLOEXEC);
|
||||
if (fd < 0)
|
||||
return (-1);
|
||||
|
||||
size_t got = 0;
|
||||
while (got < len) {
|
||||
ssize_t n = read(fd, (char *)buf + got, len - got);
|
||||
if (n <= 0) {
|
||||
if (n < 0 && errno == EINTR)
|
||||
continue;
|
||||
if (n == 0)
|
||||
errno = EIO;
|
||||
int saved = errno;
|
||||
(void) close(fd);
|
||||
errno = saved;
|
||||
return (-1);
|
||||
}
|
||||
got += (size_t)n;
|
||||
}
|
||||
|
||||
(void) close(fd);
|
||||
return (0);
|
||||
}
|
||||
|
||||
static __attribute__((noreturn)) void
|
||||
usage(void)
|
||||
{
|
||||
@@ -59,7 +79,7 @@ main(int argc, char **argv)
|
||||
{
|
||||
/* default file path, can be optionally set by user */
|
||||
const char *path = "/etc/hostid";
|
||||
/* holds converted user input or lrand48() generated value */
|
||||
/* holds converted user input or generated value */
|
||||
unsigned long input_i = 0;
|
||||
|
||||
int opt;
|
||||
@@ -112,12 +132,24 @@ main(int argc, char **argv)
|
||||
}
|
||||
|
||||
/*
|
||||
* generate if not provided by user
|
||||
* also handle unlikely zero return from lrand48()
|
||||
* Generate if not provided by user. The hostid identifies
|
||||
* this machine to ZFS multihost protection, so it must be
|
||||
* unique. Obtain a value from the system CSPRNG to ensure
|
||||
* that even on systems that share a common image and boot
|
||||
* deterministically different hostids will be generated.
|
||||
* The loop handles the unlikely zero return case.
|
||||
*/
|
||||
while (input_i == 0) {
|
||||
srand48(getpid() ^ time(NULL));
|
||||
input_i = lrand48();
|
||||
uint32_t rnd;
|
||||
|
||||
if (get_random_bytes(&rnd, sizeof (rnd)) != 0) {
|
||||
(void) fprintf(stderr,
|
||||
"zgenhostid: failed to read /dev/urandom: %s\n",
|
||||
strerror(errno));
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
input_i = rnd;
|
||||
}
|
||||
|
||||
FILE *fp = fopen(path, "wb");
|
||||
|
||||
+185
-17
@@ -1,23 +1,13 @@
|
||||
// SPDX-License-Identifier: CDDL-1.0
|
||||
/*
|
||||
* CDDL HEADER START
|
||||
* This file and its contents are supplied under the terms of the
|
||||
* Common Development and Distribution License ("CDDL"), version 1.0.
|
||||
* You may only use this file in accordance with the terms of version
|
||||
* 1.0 of the CDDL.
|
||||
*
|
||||
* The contents of this file are subject to the terms of the
|
||||
* Common Development and Distribution License (the "License").
|
||||
* You may not use this file except in compliance with the License.
|
||||
*
|
||||
* You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
|
||||
* or https://opensource.org/licenses/CDDL-1.0.
|
||||
* See the License for the specific language governing permissions
|
||||
* and limitations under the License.
|
||||
*
|
||||
* When distributing Covered Code, include this CDDL HEADER in each
|
||||
* file and include the License file at usr/src/OPENSOLARIS.LICENSE.
|
||||
* If applicable, add the following below this CDDL HEADER, with the
|
||||
* fields enclosed by brackets "[]" replaced with your own identifying
|
||||
* information: Portions Copyright [yyyy] [name of copyright owner]
|
||||
*
|
||||
* CDDL HEADER END
|
||||
* A full copy of the text of the CDDL should have accompanied this
|
||||
* source. A copy of the CDDL is also available via the Internet at
|
||||
* https://opensource.org/license/CDDL-1.0.
|
||||
*/
|
||||
|
||||
/*
|
||||
@@ -45,6 +35,7 @@
|
||||
#include <sys/dsl_synctask.h>
|
||||
#include <sys/vdev.h>
|
||||
#include <sys/vdev_impl.h>
|
||||
#include <sys/mmp.h>
|
||||
#include <sys/fs/zfs.h>
|
||||
#include <sys/dmu_objset.h>
|
||||
#include <sys/dsl_pool.h>
|
||||
@@ -111,6 +102,13 @@ usage(void)
|
||||
"\n"
|
||||
" <device> : path to vdev\n"
|
||||
"\n"
|
||||
" mmp reclaim <pool>\n"
|
||||
" import a pool whose MMP claim cannot reach every mirror\n"
|
||||
" leg the config still expects, then mark the unreachable\n"
|
||||
" leaves offline so ordinary imports succeed. Manual\n"
|
||||
" recovery: fence the peer first, this cannot see a\n"
|
||||
" live host whose legs are all invisible from here\n"
|
||||
"\n"
|
||||
" metaslab leak <pool>\n"
|
||||
" apply allocation map from zdb to specified pool\n");
|
||||
exit(1);
|
||||
@@ -601,6 +599,174 @@ zhack_do_action_idle(int argc, char **argv)
|
||||
spa_close(spa, FTAG);
|
||||
}
|
||||
|
||||
/*
|
||||
* Collect the mirror legs this host could not open. vdev_not_present is set
|
||||
* during import for any leaf whose open failed (vdev.c), and a leg in that
|
||||
* state is what the relaxed claim forgives, so it is also what has to be
|
||||
* marked offline for the ordinary imports which follow to succeed.
|
||||
*/
|
||||
static void
|
||||
zhack_collect_absent(vdev_t *vd, uint64_t *guids, uint_t *n, uint_t max)
|
||||
{
|
||||
/*
|
||||
* Skip the same subtrees mmp_claim_uberblock_sync() skips. The claim
|
||||
* never counts these, so offlining them buys nothing, and offlining a
|
||||
* log leg would drag in spa_reset_logs(). Pruned at the interior node
|
||||
* because the log flag lives on the top-level vdev.
|
||||
*/
|
||||
if (vd->vdev_islog || vd->vdev_isspare || vd->vdev_isl2cache ||
|
||||
vd->vdev_ishole || vd->vdev_ops == &vdev_indirect_ops)
|
||||
return;
|
||||
|
||||
for (uint64_t c = 0; c < vd->vdev_children; c++)
|
||||
zhack_collect_absent(vd->vdev_child[c], guids, n, max);
|
||||
|
||||
if (!vd->vdev_ops->vdev_op_leaf || !vd->vdev_not_present)
|
||||
return;
|
||||
|
||||
/*
|
||||
* Take only the legs the relaxed claim can forgive, which are the
|
||||
* direct children of a top-level mirror: the relaxation lives in the
|
||||
* nparity == 0 branch and walks that vdev's children. A raidz or
|
||||
* draid member is required as parity+1 in aggregate and never demanded
|
||||
* individually, so an absent one does not raise the requirement and
|
||||
* offlining it would be a persistent change that buys nothing.
|
||||
*/
|
||||
if (vd->vdev_parent != vd->vdev_top ||
|
||||
vdev_get_nparity(vd->vdev_top) != 0)
|
||||
return;
|
||||
|
||||
VERIFY3U(*n, <, max);
|
||||
guids[(*n)++] = vd->vdev_guid;
|
||||
}
|
||||
|
||||
static int
|
||||
zhack_do_mmp_reclaim(int argc, char **argv)
|
||||
{
|
||||
spa_t *spa;
|
||||
char *target;
|
||||
uint64_t *guids;
|
||||
uint_t nguids = 0, max;
|
||||
int c, failed = 0;
|
||||
|
||||
optind = 1;
|
||||
while ((c = getopt(argc, argv, "+")) != -1) {
|
||||
switch (c) {
|
||||
default:
|
||||
usage();
|
||||
break;
|
||||
}
|
||||
}
|
||||
argc -= optind;
|
||||
argv += optind;
|
||||
|
||||
if (argc < 1) {
|
||||
(void) fprintf(stderr, "error: missing pool name\n");
|
||||
usage();
|
||||
}
|
||||
target = argv[0];
|
||||
|
||||
/*
|
||||
* Relax the claim for this import only. The write, the wait and the
|
||||
* re-read are untouched, so a competing importer which shares any
|
||||
* visibility with us is still refused.
|
||||
*/
|
||||
mmp_claim_relaxed = B_TRUE;
|
||||
zhack_spa_open(target, B_FALSE, FTAG, &spa);
|
||||
mmp_claim_relaxed = B_FALSE;
|
||||
|
||||
/*
|
||||
* Nothing to recover on a pool without multihost: no claim runs, so an
|
||||
* ordinary import already succeeds with the absent leaves simply
|
||||
* missing. Offlining them here would be a permanent change to a pool
|
||||
* that never needed this tool.
|
||||
*/
|
||||
if (!spa_multihost(spa)) {
|
||||
(void) fprintf(stderr, "%s: multihost is off, so no uberblock "
|
||||
"claim runs and an ordinary import will succeed; refusing "
|
||||
"to offline anything\n", target);
|
||||
spa_close(spa, FTAG);
|
||||
return (1);
|
||||
}
|
||||
|
||||
/* Takes SCL_VDEV itself, so size the array before we hold it. */
|
||||
max = MAX(vdev_count_leaves(spa), 1);
|
||||
guids = umem_zalloc(max * sizeof (uint64_t), UMEM_NOFAIL);
|
||||
|
||||
spa_config_enter(spa, SCL_VDEV, FTAG, RW_READER);
|
||||
zhack_collect_absent(spa->spa_root_vdev, guids, &nguids, max);
|
||||
spa_config_exit(spa, SCL_VDEV, FTAG);
|
||||
|
||||
if (nguids == 0) {
|
||||
(void) fprintf(stdout, "%s: imported, no absent leaves to "
|
||||
"mark offline\n", target);
|
||||
}
|
||||
|
||||
for (uint_t i = 0; i < nguids; i++) {
|
||||
int error = vdev_offline(spa, guids[i], 0);
|
||||
|
||||
if (error == 0) {
|
||||
(void) fprintf(stdout, "%s: marked absent leaf %llu "
|
||||
"offline\n", target, (u_longlong_t)guids[i]);
|
||||
continue;
|
||||
}
|
||||
|
||||
failed++;
|
||||
if (error == EBUSY) {
|
||||
(void) fprintf(stderr, "%s: leaf %llu holds data no "
|
||||
"other leaf has, left online\n", target,
|
||||
(u_longlong_t)guids[i]);
|
||||
} else {
|
||||
(void) fprintf(stderr, "%s: could not offline leaf "
|
||||
"%llu: %s\n", target, (u_longlong_t)guids[i],
|
||||
strerror(error));
|
||||
}
|
||||
}
|
||||
|
||||
if (failed != 0) {
|
||||
/*
|
||||
* Not "the pool still needs zhack": this run exports cleanly
|
||||
* under our own hostid, so the next import here skips the
|
||||
* activity check entirely. The cost lands on the next host
|
||||
* to take the pool, whose claim will count the leaves left
|
||||
* online and refuse.
|
||||
*/
|
||||
(void) fprintf(stderr, "%s: %d absent leaves are still "
|
||||
"online; a later import from another host will count "
|
||||
"them and be refused\n", target, failed);
|
||||
}
|
||||
|
||||
umem_free(guids, max * sizeof (uint64_t));
|
||||
spa_close(spa, FTAG);
|
||||
|
||||
return (failed == 0 ? 0 : 1);
|
||||
}
|
||||
|
||||
static int
|
||||
zhack_do_mmp(int argc, char **argv)
|
||||
{
|
||||
char *subcommand;
|
||||
|
||||
argc--;
|
||||
argv++;
|
||||
if (argc == 0) {
|
||||
(void) fprintf(stderr,
|
||||
"error: no mmp operation specified\n");
|
||||
usage();
|
||||
}
|
||||
|
||||
subcommand = argv[0];
|
||||
if (strcmp(subcommand, "reclaim") == 0) {
|
||||
return (zhack_do_mmp_reclaim(argc, argv));
|
||||
} else {
|
||||
(void) fprintf(stderr, "error: unknown subcommand: %s\n",
|
||||
subcommand);
|
||||
usage();
|
||||
}
|
||||
|
||||
return (0);
|
||||
}
|
||||
|
||||
static int
|
||||
zhack_do_action(int argc, char **argv)
|
||||
{
|
||||
@@ -1372,6 +1538,8 @@ main(int argc, char **argv)
|
||||
rv = zhack_do_action(argc, argv);
|
||||
} else if (strcmp(subcommand, "feature") == 0) {
|
||||
rv = zhack_do_feature(argc, argv);
|
||||
} else if (strcmp(subcommand, "mmp") == 0) {
|
||||
rv = zhack_do_mmp(argc, argv);
|
||||
} else if (strcmp(subcommand, "label") == 0) {
|
||||
return (zhack_do_label(argc, argv));
|
||||
} else if (strcmp(subcommand, "metaslab") == 0) {
|
||||
|
||||
@@ -4,23 +4,14 @@
|
||||
# Print out statistics for all zil stats. This information is
|
||||
# available through the zil kstat.
|
||||
#
|
||||
# CDDL HEADER START
|
||||
# This file and its contents are supplied under the terms of the
|
||||
# Common Development and Distribution License ("CDDL"), version 1.0.
|
||||
# You may only use this file in accordance with the terms of version
|
||||
# 1.0 of the CDDL.
|
||||
#
|
||||
# The contents of this file are subject to the terms of the
|
||||
# Common Development and Distribution License, Version 1.0 only
|
||||
# (the "License"). You may not use this file except in compliance
|
||||
# with the License.
|
||||
#
|
||||
# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
|
||||
# or https://opensource.org/licenses/CDDL-1.0.
|
||||
# See the License for the specific language governing permissions
|
||||
# and limitations under the License.
|
||||
#
|
||||
# When distributing Covered Code, include this CDDL HEADER in each
|
||||
# file and include the License file at usr/src/OPENSOLARIS.LICENSE.
|
||||
# If applicable, add the following below this CDDL HEADER, with the
|
||||
# fields enclosed by brackets "[]" replaced with your own identifying
|
||||
# information: Portions Copyright [yyyy] [name of copyright owner]
|
||||
# A full copy of the text of the CDDL should have accompanied this
|
||||
# source. A copy of the CDDL is also available via the Internet at
|
||||
# https://opensource.org/license/CDDL-1.0.
|
||||
#
|
||||
# This script must remain compatible with Python 3.6+.
|
||||
#
|
||||
|
||||
@@ -1,23 +1,13 @@
|
||||
// SPDX-License-Identifier: CDDL-1.0
|
||||
/*
|
||||
* CDDL HEADER START
|
||||
* This file and its contents are supplied under the terms of the
|
||||
* Common Development and Distribution License ("CDDL"), version 1.0.
|
||||
* You may only use this file in accordance with the terms of version
|
||||
* 1.0 of the CDDL.
|
||||
*
|
||||
* The contents of this file are subject to the terms of the
|
||||
* Common Development and Distribution License (the "License").
|
||||
* You may not use this file except in compliance with the License.
|
||||
*
|
||||
* You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
|
||||
* or https://opensource.org/licenses/CDDL-1.0.
|
||||
* See the License for the specific language governing permissions
|
||||
* and limitations under the License.
|
||||
*
|
||||
* When distributing Covered Code, include this CDDL HEADER in each
|
||||
* file and include the License file at usr/src/OPENSOLARIS.LICENSE.
|
||||
* If applicable, add the following below this CDDL HEADER, with the
|
||||
* fields enclosed by brackets "[]" replaced with your own identifying
|
||||
* information: Portions Copyright [yyyy] [name of copyright owner]
|
||||
*
|
||||
* CDDL HEADER END
|
||||
* A full copy of the text of the CDDL should have accompanied this
|
||||
* source. A copy of the CDDL is also available via the Internet at
|
||||
* https://opensource.org/license/CDDL-1.0.
|
||||
*/
|
||||
/*
|
||||
* Copyright (c) 2006, 2010, Oracle and/or its affiliates. All rights reserved.
|
||||
|
||||
@@ -1,23 +1,13 @@
|
||||
// SPDX-License-Identifier: CDDL-1.0
|
||||
/*
|
||||
* CDDL HEADER START
|
||||
* This file and its contents are supplied under the terms of the
|
||||
* Common Development and Distribution License ("CDDL"), version 1.0.
|
||||
* You may only use this file in accordance with the terms of version
|
||||
* 1.0 of the CDDL.
|
||||
*
|
||||
* The contents of this file are subject to the terms of the
|
||||
* Common Development and Distribution License (the "License").
|
||||
* You may not use this file except in compliance with the License.
|
||||
*
|
||||
* You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
|
||||
* or https://opensource.org/licenses/CDDL-1.0.
|
||||
* See the License for the specific language governing permissions
|
||||
* and limitations under the License.
|
||||
*
|
||||
* When distributing Covered Code, include this CDDL HEADER in each
|
||||
* file and include the License file at usr/src/OPENSOLARIS.LICENSE.
|
||||
* If applicable, add the following below this CDDL HEADER, with the
|
||||
* fields enclosed by brackets "[]" replaced with your own identifying
|
||||
* information: Portions Copyright [yyyy] [name of copyright owner]
|
||||
*
|
||||
* CDDL HEADER END
|
||||
* A full copy of the text of the CDDL should have accompanied this
|
||||
* source. A copy of the CDDL is also available via the Internet at
|
||||
* https://opensource.org/license/CDDL-1.0.
|
||||
*/
|
||||
/*
|
||||
* Copyright (c) 2005, 2010, Oracle and/or its affiliates. All rights reserved.
|
||||
|
||||
@@ -1,23 +1,13 @@
|
||||
// SPDX-License-Identifier: CDDL-1.0
|
||||
/*
|
||||
* CDDL HEADER START
|
||||
* This file and its contents are supplied under the terms of the
|
||||
* Common Development and Distribution License ("CDDL"), version 1.0.
|
||||
* You may only use this file in accordance with the terms of version
|
||||
* 1.0 of the CDDL.
|
||||
*
|
||||
* The contents of this file are subject to the terms of the
|
||||
* Common Development and Distribution License (the "License").
|
||||
* You may not use this file except in compliance with the License.
|
||||
*
|
||||
* You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
|
||||
* or https://opensource.org/licenses/CDDL-1.0.
|
||||
* See the License for the specific language governing permissions
|
||||
* and limitations under the License.
|
||||
*
|
||||
* When distributing Covered Code, include this CDDL HEADER in each
|
||||
* file and include the License file at usr/src/OPENSOLARIS.LICENSE.
|
||||
* If applicable, add the following below this CDDL HEADER, with the
|
||||
* fields enclosed by brackets "[]" replaced with your own identifying
|
||||
* information: Portions Copyright [yyyy] [name of copyright owner]
|
||||
*
|
||||
* CDDL HEADER END
|
||||
* A full copy of the text of the CDDL should have accompanied this
|
||||
* source. A copy of the CDDL is also available via the Internet at
|
||||
* https://opensource.org/license/CDDL-1.0.
|
||||
*/
|
||||
/*
|
||||
* Copyright (c) 2005, 2010, Oracle and/or its affiliates. All rights reserved.
|
||||
|
||||
@@ -1,23 +1,13 @@
|
||||
// SPDX-License-Identifier: CDDL-1.0
|
||||
/*
|
||||
* CDDL HEADER START
|
||||
* This file and its contents are supplied under the terms of the
|
||||
* Common Development and Distribution License ("CDDL"), version 1.0.
|
||||
* You may only use this file in accordance with the terms of version
|
||||
* 1.0 of the CDDL.
|
||||
*
|
||||
* The contents of this file are subject to the terms of the
|
||||
* Common Development and Distribution License (the "License").
|
||||
* You may not use this file except in compliance with the License.
|
||||
*
|
||||
* You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
|
||||
* or https://opensource.org/licenses/CDDL-1.0.
|
||||
* See the License for the specific language governing permissions
|
||||
* and limitations under the License.
|
||||
*
|
||||
* When distributing Covered Code, include this CDDL HEADER in each
|
||||
* file and include the License file at usr/src/OPENSOLARIS.LICENSE.
|
||||
* If applicable, add the following below this CDDL HEADER, with the
|
||||
* fields enclosed by brackets "[]" replaced with your own identifying
|
||||
* information: Portions Copyright [yyyy] [name of copyright owner]
|
||||
*
|
||||
* CDDL HEADER END
|
||||
* A full copy of the text of the CDDL should have accompanied this
|
||||
* source. A copy of the CDDL is also available via the Internet at
|
||||
* https://opensource.org/license/CDDL-1.0.
|
||||
*/
|
||||
|
||||
/*
|
||||
|
||||
@@ -1,23 +1,13 @@
|
||||
// SPDX-License-Identifier: CDDL-1.0
|
||||
/*
|
||||
* CDDL HEADER START
|
||||
* This file and its contents are supplied under the terms of the
|
||||
* Common Development and Distribution License ("CDDL"), version 1.0.
|
||||
* You may only use this file in accordance with the terms of version
|
||||
* 1.0 of the CDDL.
|
||||
*
|
||||
* The contents of this file are subject to the terms of the
|
||||
* Common Development and Distribution License (the "License").
|
||||
* You may not use this file except in compliance with the License.
|
||||
*
|
||||
* You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
|
||||
* or https://opensource.org/licenses/CDDL-1.0.
|
||||
* See the License for the specific language governing permissions
|
||||
* and limitations under the License.
|
||||
*
|
||||
* When distributing Covered Code, include this CDDL HEADER in each
|
||||
* file and include the License file at usr/src/OPENSOLARIS.LICENSE.
|
||||
* If applicable, add the following below this CDDL HEADER, with the
|
||||
* fields enclosed by brackets "[]" replaced with your own identifying
|
||||
* information: Portions Copyright [yyyy] [name of copyright owner]
|
||||
*
|
||||
* CDDL HEADER END
|
||||
* A full copy of the text of the CDDL should have accompanied this
|
||||
* source. A copy of the CDDL is also available via the Internet at
|
||||
* https://opensource.org/license/CDDL-1.0.
|
||||
*/
|
||||
|
||||
/*
|
||||
|
||||
@@ -1,23 +1,13 @@
|
||||
// SPDX-License-Identifier: CDDL-1.0
|
||||
/*
|
||||
* CDDL HEADER START
|
||||
* This file and its contents are supplied under the terms of the
|
||||
* Common Development and Distribution License ("CDDL"), version 1.0.
|
||||
* You may only use this file in accordance with the terms of version
|
||||
* 1.0 of the CDDL.
|
||||
*
|
||||
* The contents of this file are subject to the terms of the
|
||||
* Common Development and Distribution License (the "License").
|
||||
* You may not use this file except in compliance with the License.
|
||||
*
|
||||
* You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
|
||||
* or https://opensource.org/licenses/CDDL-1.0.
|
||||
* See the License for the specific language governing permissions
|
||||
* and limitations under the License.
|
||||
*
|
||||
* When distributing Covered Code, include this CDDL HEADER in each
|
||||
* file and include the License file at usr/src/OPENSOLARIS.LICENSE.
|
||||
* If applicable, add the following below this CDDL HEADER, with the
|
||||
* fields enclosed by brackets "[]" replaced with your own identifying
|
||||
* information: Portions Copyright [yyyy] [name of copyright owner]
|
||||
*
|
||||
* CDDL HEADER END
|
||||
* A full copy of the text of the CDDL should have accompanied this
|
||||
* source. A copy of the CDDL is also available via the Internet at
|
||||
* https://opensource.org/license/CDDL-1.0.
|
||||
*/
|
||||
/*
|
||||
* Copyright 2007 Sun Microsystems, Inc. All rights reserved.
|
||||
|
||||
@@ -1,23 +1,13 @@
|
||||
// SPDX-License-Identifier: CDDL-1.0
|
||||
/*
|
||||
* CDDL HEADER START
|
||||
* This file and its contents are supplied under the terms of the
|
||||
* Common Development and Distribution License ("CDDL"), version 1.0.
|
||||
* You may only use this file in accordance with the terms of version
|
||||
* 1.0 of the CDDL.
|
||||
*
|
||||
* The contents of this file are subject to the terms of the
|
||||
* Common Development and Distribution License (the "License").
|
||||
* You may not use this file except in compliance with the License.
|
||||
*
|
||||
* You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
|
||||
* or https://opensource.org/licenses/CDDL-1.0.
|
||||
* See the License for the specific language governing permissions
|
||||
* and limitations under the License.
|
||||
*
|
||||
* When distributing Covered Code, include this CDDL HEADER in each
|
||||
* file and include the License file at usr/src/OPENSOLARIS.LICENSE.
|
||||
* If applicable, add the following below this CDDL HEADER, with the
|
||||
* fields enclosed by brackets "[]" replaced with your own identifying
|
||||
* information: Portions Copyright [yyyy] [name of copyright owner]
|
||||
*
|
||||
* CDDL HEADER END
|
||||
* A full copy of the text of the CDDL should have accompanied this
|
||||
* source. A copy of the CDDL is also available via the Internet at
|
||||
* https://opensource.org/license/CDDL-1.0.
|
||||
*/
|
||||
|
||||
/*
|
||||
@@ -33,8 +23,9 @@
|
||||
* Copyright (c) 2017, Intel Corporation.
|
||||
* Copyright (c) 2019, loli10K <ezomori.nozomu@gmail.com>
|
||||
* Copyright (c) 2021, Colm Buckley <colm@tuatha.org>
|
||||
* Copyright (c) 2021, 2023, 2025, Klara, Inc.
|
||||
* Copyright (c) 2021, 2023-2026, Klara, Inc.
|
||||
* Copyright (c) 2021, 2025 Hewlett Packard Enterprise Development LP.
|
||||
* Copyright (c) 2026, TrueNAS.
|
||||
*/
|
||||
|
||||
#include <assert.h>
|
||||
@@ -127,6 +118,7 @@ static int zpool_do_get(int, char **);
|
||||
static int zpool_do_set(int, char **);
|
||||
|
||||
static int zpool_do_sync(int, char **);
|
||||
static int zpool_do_condense(int, char **);
|
||||
|
||||
static int zpool_do_version(int, char **);
|
||||
|
||||
@@ -146,7 +138,8 @@ enum zpool_options {
|
||||
ZPOOL_OPTION_ALLOW_ASHIFT_MISMATCH,
|
||||
ZPOOL_OPTION_POOL_KEY_GUID,
|
||||
ZPOOL_OPTION_JSON_NUMS_AS_INT,
|
||||
ZPOOL_OPTION_JSON_FLAT_VDEVS
|
||||
ZPOOL_OPTION_JSON_FLAT_VDEVS,
|
||||
ZPOOL_OPTION_CONDENSE_LIST_TYPES
|
||||
};
|
||||
|
||||
/*
|
||||
@@ -174,6 +167,7 @@ typedef enum {
|
||||
HELP_CLEAR,
|
||||
HELP_CREATE,
|
||||
HELP_CHECKPOINT,
|
||||
HELP_CONDENSE,
|
||||
HELP_DDT_PRUNE,
|
||||
HELP_DESTROY,
|
||||
HELP_DETACH,
|
||||
@@ -361,6 +355,16 @@ static const char *vdev_trim_state_str[] = {
|
||||
"COMPLETE"
|
||||
};
|
||||
|
||||
static const char *condense_unit(const char *type) {
|
||||
if (strcmp(type, POOL_CONDENSE_LOG_SPACEMAP) == 0)
|
||||
return ("metaslabs");
|
||||
#ifdef ZFS_DEBUG
|
||||
if (strcmp(type, "debug") == 0)
|
||||
return ("nits"); /* fundamental unit of debugging? ;) */
|
||||
#endif
|
||||
return ("items");
|
||||
}
|
||||
|
||||
#define ZFS_NICE_TIMESTAMP 100
|
||||
|
||||
/*
|
||||
@@ -417,6 +421,7 @@ static zpool_command_t command_table[] = {
|
||||
{ "resilver", zpool_do_resilver, HELP_RESILVER },
|
||||
{ "scrub", zpool_do_scrub, HELP_SCRUB },
|
||||
{ "trim", zpool_do_trim, HELP_TRIM },
|
||||
{ "condense", zpool_do_condense, HELP_CONDENSE },
|
||||
{ NULL },
|
||||
{ "import", zpool_do_import, HELP_IMPORT },
|
||||
{ "export", zpool_do_export, HELP_EXPORT },
|
||||
@@ -428,6 +433,7 @@ static zpool_command_t command_table[] = {
|
||||
{ NULL },
|
||||
{ "get", zpool_do_get, HELP_GET },
|
||||
{ "set", zpool_do_set, HELP_SET },
|
||||
{ NULL },
|
||||
{ "sync", zpool_do_sync, HELP_SYNC },
|
||||
{ NULL },
|
||||
{ "wait", zpool_do_wait, HELP_WAIT },
|
||||
@@ -509,11 +515,12 @@ get_usage(zpool_help_t idx)
|
||||
case HELP_REOPEN:
|
||||
return (gettext("\treopen [-n] <pool>\n"));
|
||||
case HELP_INITIALIZE:
|
||||
return (gettext("\tinitialize [-c | -s | -u] [-w] <-a | <pool> "
|
||||
"[<device> ...]>\n"));
|
||||
return (gettext("\tinitialize [-c | -s | -u] [-w] [-z] "
|
||||
"<-a | <pool> [<device> ...]>\n"));
|
||||
case HELP_SCRUB:
|
||||
return (gettext("\tscrub [-e | -s | -p | -C | -E | -S] [-w] "
|
||||
"<-a | <pool> [<pool> ...]>\n"));
|
||||
return (gettext("\tscrub [-e | -s | -p | -t | -C [-t] | "
|
||||
"[-S date] [-E date] [-t]] [-w]\n"
|
||||
"\t <-a | <pool> [<pool> ...]>\n"));
|
||||
case HELP_RESILVER:
|
||||
return (gettext("\tresilver <pool> ...\n"));
|
||||
case HELP_TRIM:
|
||||
@@ -547,6 +554,10 @@ get_usage(zpool_help_t idx)
|
||||
return (gettext("\treguid [-g guid] <pool>\n"));
|
||||
case HELP_SYNC:
|
||||
return (gettext("\tsync [pool] ...\n"));
|
||||
case HELP_CONDENSE:
|
||||
return (gettext("\tcondense [-t <type>] [-c | -w] "
|
||||
"[-a | <pool> ...]\n"
|
||||
"\tcondense --types\n"));
|
||||
case HELP_VERSION:
|
||||
return (gettext("\tversion [-j]\n"));
|
||||
case HELP_WAIT:
|
||||
@@ -751,7 +762,7 @@ usage(boolean_t requested)
|
||||
}
|
||||
|
||||
/*
|
||||
* zpool initialize [-c | -s | -u] [-w] <-a | pool> [<vdev> ...]
|
||||
* zpool initialize [-c | -s | -u] [-w] [-z] <-a | pool> [<vdev> ...]
|
||||
* Initialize all unused blocks in the specified vdevs, or all vdevs in the pool
|
||||
* if none specified.
|
||||
*
|
||||
@@ -770,6 +781,7 @@ zpool_do_initialize(int argc, char **argv)
|
||||
int err = 0;
|
||||
boolean_t wait = B_FALSE;
|
||||
boolean_t initialize_all = B_FALSE;
|
||||
boolean_t zero = B_FALSE;
|
||||
|
||||
struct option long_options[] = {
|
||||
{"cancel", no_argument, NULL, 'c'},
|
||||
@@ -777,16 +789,20 @@ zpool_do_initialize(int argc, char **argv)
|
||||
{"uninit", no_argument, NULL, 'u'},
|
||||
{"wait", no_argument, NULL, 'w'},
|
||||
{"all", no_argument, NULL, 'a'},
|
||||
{"zero", no_argument, NULL, 'z'},
|
||||
{0, 0, 0, 0}
|
||||
};
|
||||
|
||||
pool_initialize_func_t cmd_type = POOL_INITIALIZE_START;
|
||||
while ((c = getopt_long(argc, argv, "acsuw", long_options,
|
||||
while ((c = getopt_long(argc, argv, "acsuwz", long_options,
|
||||
NULL)) != -1) {
|
||||
switch (c) {
|
||||
case 'a':
|
||||
initialize_all = B_TRUE;
|
||||
break;
|
||||
case 'z':
|
||||
zero = B_TRUE;
|
||||
break;
|
||||
case 'c':
|
||||
if (cmd_type != POOL_INITIALIZE_START &&
|
||||
cmd_type != POOL_INITIALIZE_CANCEL) {
|
||||
@@ -835,7 +851,9 @@ zpool_do_initialize(int argc, char **argv)
|
||||
|
||||
initialize_cbdata_t cbdata = {
|
||||
.wait = wait,
|
||||
.cmd_type = cmd_type
|
||||
.cmd_type = cmd_type,
|
||||
.value = 0,
|
||||
.value_provided = zero
|
||||
};
|
||||
|
||||
if (initialize_all && argc > 0) {
|
||||
@@ -855,6 +873,12 @@ zpool_do_initialize(int argc, char **argv)
|
||||
usage(B_FALSE);
|
||||
}
|
||||
|
||||
if (zero && (cmd_type != POOL_INITIALIZE_START)) {
|
||||
(void) fprintf(stderr, gettext("-z cannot be used with -c, -s "
|
||||
"or -u\n"));
|
||||
usage(B_FALSE);
|
||||
}
|
||||
|
||||
if (argc == 0 && initialize_all) {
|
||||
/* Initilize each pool recursively */
|
||||
err = for_each_pool(argc, argv, B_TRUE, NULL, ZFS_TYPE_POOL,
|
||||
@@ -878,9 +902,11 @@ zpool_do_initialize(int argc, char **argv)
|
||||
fnvlist_add_boolean(vdevs, argv[i]);
|
||||
}
|
||||
if (wait)
|
||||
err = zpool_initialize_wait(zhp, cmd_type, vdevs);
|
||||
err = zpool_initialize_wait(zhp, cmd_type, vdevs,
|
||||
cbdata.value, cbdata.value_provided);
|
||||
else
|
||||
err = zpool_initialize(zhp, cmd_type, vdevs);
|
||||
err = zpool_initialize(zhp, cmd_type, vdevs,
|
||||
cbdata.value, cbdata.value_provided);
|
||||
fnvlist_free(vdevs);
|
||||
}
|
||||
|
||||
@@ -8431,6 +8457,7 @@ zpool_do_reopen(int argc, char **argv)
|
||||
typedef struct scrub_cbdata {
|
||||
int cb_type;
|
||||
pool_scrub_cmd_t cb_scrub_cmd;
|
||||
pool_scrub_flags_t cb_scrub_flags;
|
||||
time_t cb_date_start;
|
||||
time_t cb_date_end;
|
||||
} scrub_cbdata_t;
|
||||
@@ -8477,7 +8504,7 @@ scrub_callback(zpool_handle_t *zhp, void *data)
|
||||
}
|
||||
|
||||
err = zpool_scan_range(zhp, cb->cb_type, cb->cb_scrub_cmd,
|
||||
cb->cb_date_start, cb->cb_date_end);
|
||||
cb->cb_scrub_flags, cb->cb_date_start, cb->cb_date_end);
|
||||
if (err == 0 && zpool_has_checkpoint(zhp) &&
|
||||
cb->cb_type == POOL_SCAN_SCRUB) {
|
||||
(void) printf(gettext("warning: will not scrub state that "
|
||||
@@ -8518,7 +8545,7 @@ date_string_to_sec(const char *timestr, boolean_t rounding)
|
||||
}
|
||||
|
||||
/*
|
||||
* zpool scrub [-e | -s | -p | -C | -E | -S] [-w] [-a | <pool> ...]
|
||||
* zpool scrub [-e | -s | -p | -C | -E | -S | -t] [-w] [-a | <pool> ...]
|
||||
*
|
||||
* -a Scrub all pools.
|
||||
* -e Only scrub blocks in the error log.
|
||||
@@ -8527,6 +8554,7 @@ date_string_to_sec(const char *timestr, boolean_t rounding)
|
||||
* -s Stop. Stops any in-progress scrub.
|
||||
* -p Pause. Pause in-progress scrub.
|
||||
* -w Wait. Blocks until scrub has completed.
|
||||
* -t Decompress and decrypt (if key is loaded) scrubbed blocks.
|
||||
* -C Scrub from last saved txg.
|
||||
*/
|
||||
int
|
||||
@@ -8538,17 +8566,17 @@ zpool_do_scrub(int argc, char **argv)
|
||||
int error;
|
||||
|
||||
cb.cb_type = POOL_SCAN_SCRUB;
|
||||
cb.cb_scrub_cmd = POOL_SCRUB_NORMAL;
|
||||
cb.cb_scrub_cmd = 0;
|
||||
cb.cb_scrub_flags = 0;
|
||||
cb.cb_date_start = cb.cb_date_end = 0;
|
||||
|
||||
boolean_t is_error_scrub = B_FALSE;
|
||||
boolean_t is_pause = B_FALSE;
|
||||
boolean_t is_stop = B_FALSE;
|
||||
boolean_t is_txg_continue = B_FALSE;
|
||||
boolean_t scrub_all = B_FALSE;
|
||||
|
||||
/* check options */
|
||||
while ((c = getopt(argc, argv, "aspweCE:S:")) != -1) {
|
||||
while ((c = getopt(argc, argv, "aspweCE:S:t")) != -1) {
|
||||
switch (c) {
|
||||
case 'a':
|
||||
scrub_all = B_TRUE;
|
||||
@@ -8556,6 +8584,9 @@ zpool_do_scrub(int argc, char **argv)
|
||||
case 'e':
|
||||
is_error_scrub = B_TRUE;
|
||||
break;
|
||||
case 't':
|
||||
cb.cb_scrub_flags |= POOL_SCRUB_THOROUGH;
|
||||
break;
|
||||
case 'E':
|
||||
/*
|
||||
* Round the date. It's better to scrub more data than
|
||||
@@ -8576,7 +8607,7 @@ zpool_do_scrub(int argc, char **argv)
|
||||
wait = B_TRUE;
|
||||
break;
|
||||
case 'C':
|
||||
is_txg_continue = B_TRUE;
|
||||
cb.cb_scrub_cmd |= POOL_SCRUB_FROM_LAST_TXG;
|
||||
break;
|
||||
case '?':
|
||||
(void) fprintf(stderr, gettext("invalid option '%c'\n"),
|
||||
@@ -8589,17 +8620,39 @@ zpool_do_scrub(int argc, char **argv)
|
||||
(void) fprintf(stderr, gettext("invalid option "
|
||||
"combination: -s and -p are mutually exclusive\n"));
|
||||
usage(B_FALSE);
|
||||
} else if (is_pause && is_txg_continue) {
|
||||
} else if (is_error_scrub && is_pause) {
|
||||
(void) fprintf(stderr, gettext("invalid option "
|
||||
"combination: -e and -p are mutually exclusive\n"));
|
||||
usage(B_FALSE);
|
||||
} else if (is_error_scrub && is_stop) {
|
||||
(void) fprintf(stderr, gettext("invalid option "
|
||||
"combination: -e and -s are mutually exclusive\n"));
|
||||
usage(B_FALSE);
|
||||
} else if (is_error_scrub &&
|
||||
(cb.cb_scrub_cmd & POOL_SCRUB_FROM_LAST_TXG)) {
|
||||
(void) fprintf(stderr, gettext("invalid option "
|
||||
"combination: -e and -C are mutually exclusive\n"));
|
||||
usage(B_FALSE);
|
||||
} else if (is_error_scrub &&
|
||||
(cb.cb_scrub_flags & POOL_SCRUB_THOROUGH)) {
|
||||
(void) fprintf(stderr, gettext("invalid option "
|
||||
"combination: -e and -t are mutually exclusive\n"));
|
||||
usage(B_FALSE);
|
||||
} else if (is_pause && (cb.cb_scrub_cmd & POOL_SCRUB_FROM_LAST_TXG)) {
|
||||
(void) fprintf(stderr, gettext("invalid option "
|
||||
"combination: -p and -C are mutually exclusive\n"));
|
||||
usage(B_FALSE);
|
||||
} else if (is_stop && is_txg_continue) {
|
||||
} else if (is_pause && (cb.cb_scrub_flags & POOL_SCRUB_THOROUGH)) {
|
||||
(void) fprintf(stderr, gettext("invalid option "
|
||||
"combination: -p and -t are mutually exclusive\n"));
|
||||
usage(B_FALSE);
|
||||
} else if (is_stop && (cb.cb_scrub_cmd & POOL_SCRUB_FROM_LAST_TXG)) {
|
||||
(void) fprintf(stderr, gettext("invalid option "
|
||||
"combination: -s and -C are mutually exclusive\n"));
|
||||
usage(B_FALSE);
|
||||
} else if (is_error_scrub && is_txg_continue) {
|
||||
} else if (is_stop && (cb.cb_scrub_flags & POOL_SCRUB_THOROUGH)) {
|
||||
(void) fprintf(stderr, gettext("invalid option "
|
||||
"combination: -e and -C are mutually exclusive\n"));
|
||||
"combination: -s and -t are mutually exclusive\n"));
|
||||
usage(B_FALSE);
|
||||
} else {
|
||||
if (is_error_scrub)
|
||||
@@ -8609,19 +8662,26 @@ zpool_do_scrub(int argc, char **argv)
|
||||
cb.cb_scrub_cmd = POOL_SCRUB_PAUSE;
|
||||
} else if (is_stop) {
|
||||
cb.cb_type = POOL_SCAN_NONE;
|
||||
} else if (is_txg_continue) {
|
||||
cb.cb_scrub_cmd = POOL_SCRUB_FROM_LAST_TXG;
|
||||
} else {
|
||||
cb.cb_scrub_cmd = POOL_SCRUB_NORMAL;
|
||||
}
|
||||
}
|
||||
|
||||
boolean_t is_thorough =
|
||||
(cb.cb_scrub_flags & POOL_SCRUB_THOROUGH) != 0;
|
||||
if ((cb.cb_date_start != 0 || cb.cb_date_end != 0) &&
|
||||
cb.cb_scrub_cmd != POOL_SCRUB_NORMAL) {
|
||||
(void) fprintf(stderr, gettext("invalid option combination: "
|
||||
"start/end date is available only with normal scrub\n"));
|
||||
(cb.cb_scrub_cmd & POOL_SCRUB_FROM_LAST_TXG)) {
|
||||
(void) fprintf(stderr, gettext("invalid option "
|
||||
"combination: -C and -S/-E date are mutually "
|
||||
"exclusive\n"));
|
||||
usage(B_FALSE);
|
||||
} else if ((cb.cb_date_start != 0 || cb.cb_date_end != 0) &&
|
||||
(is_error_scrub || is_stop || is_pause ||
|
||||
(!is_thorough && cb.cb_scrub_cmd != POOL_SCRUB_NORMAL))) {
|
||||
(void) fprintf(stderr, gettext("invalid option "
|
||||
"combination: start/end date is available only "
|
||||
"with normal or thorough scrub\n"));
|
||||
usage(B_FALSE);
|
||||
}
|
||||
|
||||
if (cb.cb_date_start != 0 && cb.cb_date_end != 0 &&
|
||||
cb.cb_date_start > cb.cb_date_end) {
|
||||
(void) fprintf(stderr, gettext("invalid arguments: "
|
||||
@@ -8669,6 +8729,7 @@ zpool_do_resilver(int argc, char **argv)
|
||||
|
||||
cb.cb_type = POOL_SCAN_RESILVER;
|
||||
cb.cb_scrub_cmd = POOL_SCRUB_NORMAL;
|
||||
cb.cb_scrub_flags = 0;
|
||||
cb.cb_date_start = cb.cb_date_end = 0;
|
||||
|
||||
/* check options */
|
||||
@@ -8850,6 +8911,156 @@ zpool_do_trim(int argc, char **argv)
|
||||
return (error);
|
||||
}
|
||||
|
||||
static const char *condense_types[] = {
|
||||
POOL_CONDENSE_LOG_SPACEMAP,
|
||||
#ifdef ZFS_DEBUG
|
||||
"debug",
|
||||
#endif
|
||||
NULL,
|
||||
};
|
||||
|
||||
typedef struct {
|
||||
const char *cmd;
|
||||
const char *type;
|
||||
} condense_cb_t;
|
||||
|
||||
static int
|
||||
condense_cb(zpool_handle_t *zhp, void *data)
|
||||
{
|
||||
condense_cb_t *cb = data;
|
||||
if (cb->type != NULL)
|
||||
return (zpool_condense(zhp, cb->cmd, cb->type));
|
||||
|
||||
int err = 0;
|
||||
for (uint_t i = 0; condense_types[i] != NULL; i++) {
|
||||
#ifdef ZFS_DEBUG
|
||||
/*
|
||||
* Skip the debug type when no type is specificed. This is
|
||||
* the "condense everything" mode, and people _do_ run debug
|
||||
* builds, and we don't want them to be accidentally running
|
||||
* the debug condense op.
|
||||
*/
|
||||
if (strcmp(condense_types[i], "debug") == 0)
|
||||
continue;
|
||||
#endif
|
||||
err = zpool_condense(zhp, cb->cmd, condense_types[i]);
|
||||
|
||||
/*
|
||||
* Quietly ignore ENOTSUP, so we don't fail all condense types
|
||||
* just because one isn't supporte.d
|
||||
*/
|
||||
if (err != 0 && err != ENOTSUP)
|
||||
break;
|
||||
}
|
||||
|
||||
return (err);
|
||||
}
|
||||
|
||||
/*
|
||||
* zpool condense [-t <type>] [-c | -w] [-a | <pool> ...]
|
||||
*
|
||||
* -t <type> What to condense.
|
||||
* -a Condense all pools.
|
||||
* -c Cancel. Ends any in-progress condense.
|
||||
* -w Wait. Blocks until condense has completed.
|
||||
*
|
||||
* Condense (flush, garbage-collect) the pool metadata on the specfied pools.
|
||||
*/
|
||||
static int
|
||||
zpool_do_condense(int argc, char **argv)
|
||||
{
|
||||
struct option long_options[] = {
|
||||
{"type", required_argument, NULL, 't'},
|
||||
{"all", no_argument, NULL, 'a'},
|
||||
{"cancel", no_argument, NULL, 'c'},
|
||||
{"wait", no_argument, NULL, 'w'},
|
||||
{"types", no_argument, NULL,
|
||||
ZPOOL_OPTION_CONDENSE_LIST_TYPES},
|
||||
{0, 0, 0, 0}
|
||||
};
|
||||
|
||||
condense_cb_t cb = {
|
||||
.cmd = "start",
|
||||
.type = NULL,
|
||||
};
|
||||
|
||||
boolean_t cancel = B_FALSE;
|
||||
boolean_t all_pools = B_FALSE;
|
||||
boolean_t wait = B_FALSE;
|
||||
|
||||
int c;
|
||||
while ((c = getopt_long(argc, argv, "at:cw", long_options, NULL))
|
||||
!= -1) {
|
||||
switch (c) {
|
||||
case 'a':
|
||||
all_pools = B_TRUE;
|
||||
break;
|
||||
case 't': {
|
||||
for (uint_t i = 0; condense_types[i] != NULL; i++) {
|
||||
if (strcmp(condense_types[i], optarg) == 0) {
|
||||
cb.type = condense_types[i];
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (cb.type == NULL) {
|
||||
(void) fprintf(stderr,
|
||||
gettext("invalid condense type '%s'\n"),
|
||||
optarg);
|
||||
usage(B_FALSE);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 'c':
|
||||
cb.cmd = "cancel";
|
||||
cancel = B_TRUE;
|
||||
break;
|
||||
case 'w':
|
||||
wait = B_TRUE;
|
||||
break;
|
||||
case ZPOOL_OPTION_CONDENSE_LIST_TYPES:
|
||||
for (uint_t i = 0; condense_types[i] != NULL; i++)
|
||||
printf("%s\n", condense_types[i]);
|
||||
return (0);
|
||||
case '?':
|
||||
if (optopt != 0) {
|
||||
(void) fprintf(stderr,
|
||||
gettext("invalid option '%c'\n"), optopt);
|
||||
} else {
|
||||
(void) fprintf(stderr,
|
||||
gettext("invalid option '%s'\n"),
|
||||
argv[optind - 1]);
|
||||
}
|
||||
usage(B_FALSE);
|
||||
}
|
||||
}
|
||||
|
||||
argc -= optind;
|
||||
argv += optind;
|
||||
|
||||
if (argc < 1 && !all_pools) {
|
||||
(void) fprintf(stderr, gettext("missing pool name argument\n"));
|
||||
usage(B_FALSE);
|
||||
return (-1);
|
||||
}
|
||||
|
||||
if (wait && cancel) {
|
||||
(void) fprintf(stderr, gettext("-w cannot be used with -c\n"));
|
||||
usage(B_FALSE);
|
||||
}
|
||||
|
||||
int error = for_each_pool(argc, argv, B_FALSE, NULL, ZFS_TYPE_POOL,
|
||||
B_FALSE, condense_cb, &cb);
|
||||
|
||||
if (wait && !error) {
|
||||
zpool_wait_activity_t act = ZPOOL_WAIT_CONDENSE;
|
||||
error = for_each_pool(argc, argv, B_FALSE, NULL, ZFS_TYPE_POOL,
|
||||
B_FALSE, wait_callback, &act);
|
||||
}
|
||||
|
||||
return (error);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Converts a total number of seconds to a human readable string broken
|
||||
* down in to days/hours/minutes/seconds.
|
||||
@@ -8941,7 +9152,7 @@ print_err_scrub_status(pool_scan_stat_t *ps)
|
||||
* Print out detailed scrub status.
|
||||
*/
|
||||
static void
|
||||
print_scan_scrub_resilver_status(pool_scan_stat_t *ps)
|
||||
print_scan_scrub_resilver_status(pool_scan_stat_t *ps, boolean_t is_thorough)
|
||||
{
|
||||
time_t start, end, pause;
|
||||
uint64_t pass_scanned, scanned, pass_issued, issued, total_s, total_i;
|
||||
@@ -8976,10 +9187,17 @@ print_scan_scrub_resilver_status(pool_scan_stat_t *ps)
|
||||
secs_to_dhms(end - start, time_buf);
|
||||
|
||||
if (is_scrub) {
|
||||
(void) printf(gettext("scrub repaired %s "
|
||||
"in %s with %llu errors on %s"), processed_buf,
|
||||
time_buf, (u_longlong_t)ps->pss_errors,
|
||||
ctime(&end));
|
||||
if (is_thorough) {
|
||||
(void) printf(gettext("thorough scrub "
|
||||
"repaired %s in %s with %llu errors on %s"),
|
||||
processed_buf, time_buf,
|
||||
(u_longlong_t)ps->pss_errors, ctime(&end));
|
||||
} else {
|
||||
(void) printf(gettext("scrub repaired %s "
|
||||
"in %s with %llu errors on %s"),
|
||||
processed_buf, time_buf,
|
||||
(u_longlong_t)ps->pss_errors, ctime(&end));
|
||||
}
|
||||
} else if (is_resilver) {
|
||||
(void) printf(gettext("resilvered %s "
|
||||
"in %s with %llu errors on %s"), processed_buf,
|
||||
@@ -8989,8 +9207,13 @@ print_scan_scrub_resilver_status(pool_scan_stat_t *ps)
|
||||
return;
|
||||
} else if (ps->pss_state == DSS_CANCELED) {
|
||||
if (is_scrub) {
|
||||
(void) printf(gettext("scrub canceled on %s"),
|
||||
ctime(&end));
|
||||
if (is_thorough) {
|
||||
(void) printf(gettext("thorough scrub canceled "
|
||||
"on %s"), ctime(&end));
|
||||
} else {
|
||||
(void) printf(gettext("scrub canceled on %s"),
|
||||
ctime(&end));
|
||||
}
|
||||
} else if (is_resilver) {
|
||||
(void) printf(gettext("resilver canceled on %s"),
|
||||
ctime(&end));
|
||||
@@ -9003,13 +9226,25 @@ print_scan_scrub_resilver_status(pool_scan_stat_t *ps)
|
||||
/* Scan is in progress. Resilvers can't be paused. */
|
||||
if (is_scrub) {
|
||||
if (pause == 0) {
|
||||
(void) printf(gettext("scrub in progress since %s"),
|
||||
ctime(&start));
|
||||
if (is_thorough) {
|
||||
(void) printf(gettext("thorough scrub "
|
||||
"in progress since %s"), ctime(&start));
|
||||
} else {
|
||||
(void) printf(gettext("scrub in progress "
|
||||
"since %s"), ctime(&start));
|
||||
}
|
||||
} else {
|
||||
(void) printf(gettext("scrub paused since %s"),
|
||||
ctime(&pause));
|
||||
(void) printf(gettext("\tscrub started on %s"),
|
||||
ctime(&start));
|
||||
if (is_thorough) {
|
||||
(void) printf(gettext("thorough scrub paused "
|
||||
"since %s"), ctime(&pause));
|
||||
(void) printf(gettext("\tthorough scrub "
|
||||
"started on %s"), ctime(&start));
|
||||
} else {
|
||||
(void) printf(gettext("scrub paused since %s"),
|
||||
ctime(&pause));
|
||||
(void) printf(gettext("\tscrub started on %s"),
|
||||
ctime(&start));
|
||||
}
|
||||
}
|
||||
} else if (is_resilver) {
|
||||
(void) printf(gettext("resilver in progress since %s"),
|
||||
@@ -9929,6 +10164,53 @@ removal_status_nvlist(zpool_handle_t *zhp, status_cbdata_t *cb,
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
condense_status_nvlist(nvlist_t *nvroot, status_cbdata_t *cb, nvlist_t *item)
|
||||
{
|
||||
nvlist_t *cnv = NULL;
|
||||
nvlist_lookup_nvlist(nvroot, ZPOOL_CONFIG_CONDENSE_STATS, &cnv);
|
||||
if (cnv == NULL)
|
||||
return;
|
||||
|
||||
nvlist_t *onv = fnvlist_alloc();
|
||||
for (nvpair_t *nvp = nvlist_next_nvpair(cnv, NULL);
|
||||
nvp != NULL; nvp = nvlist_next_nvpair(cnv, nvp)) {
|
||||
const char *type = nvpair_name(nvp);
|
||||
nvlist_t *tnv = fnvpair_value_nvlist(nvp);
|
||||
|
||||
uint64_t start_time = fnvlist_lookup_uint64(tnv, "start_time");
|
||||
if (start_time == 0)
|
||||
continue;
|
||||
|
||||
uint64_t end_time = fnvlist_lookup_uint64(tnv, "end_time");
|
||||
uint64_t processed = fnvlist_lookup_uint64(tnv, "processed");
|
||||
uint64_t total = fnvlist_lookup_uint64(tnv, "total");
|
||||
|
||||
nvlist_t *nv = fnvlist_alloc();
|
||||
nice_num_str_nvlist(nv, "start_time", start_time,
|
||||
cb->cb_literal, cb->cb_json_as_int, ZFS_NICE_TIMESTAMP);
|
||||
if (end_time > 0)
|
||||
nice_num_str_nvlist(nv, "end_time", end_time,
|
||||
cb->cb_literal, cb->cb_json_as_int,
|
||||
ZFS_NICE_TIMESTAMP);
|
||||
nice_num_str_nvlist(nv, "processed", processed,
|
||||
cb->cb_literal, cb->cb_json_as_int, ZFS_NICENUM_1024);
|
||||
nice_num_str_nvlist(nv, "total", total,
|
||||
cb->cb_literal, cb->cb_json_as_int, ZFS_NICENUM_1024);
|
||||
|
||||
fnvlist_add_string(nv, "unit", condense_unit(type));
|
||||
|
||||
fnvlist_add_nvlist(onv, type, nv);
|
||||
fnvlist_free(nv);
|
||||
}
|
||||
|
||||
if (fnvlist_num_pairs(onv))
|
||||
fnvlist_add_nvlist(item, "condense", onv);
|
||||
|
||||
fnvlist_free(onv);
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
scan_status_nvlist(zpool_handle_t *zhp, status_cbdata_t *cb,
|
||||
nvlist_t *nvroot, nvlist_t *item)
|
||||
@@ -10099,6 +10381,7 @@ print_scan_status(zpool_handle_t *zhp, nvlist_t *nvroot)
|
||||
pool_checkpoint_stat_t *pcs = NULL;
|
||||
pool_scan_stat_t *ps = NULL;
|
||||
uint_t c;
|
||||
boolean_t is_thorough = B_FALSE;
|
||||
time_t scrub_start = 0, errorscrub_start = 0;
|
||||
|
||||
if (nvlist_lookup_uint64_array(nvroot, ZPOOL_CONFIG_SCAN_STATS,
|
||||
@@ -10111,8 +10394,10 @@ print_scan_status(zpool_handle_t *zhp, nvlist_t *nvroot)
|
||||
have_resilver = (ps->pss_func == POOL_SCAN_RESILVER);
|
||||
have_scrub = (ps->pss_func == POOL_SCAN_SCRUB);
|
||||
scrub_start = ps->pss_start_time;
|
||||
if (c > offsetof(pool_scan_stat_t,
|
||||
pss_pass_error_scrub_pause) / 8) {
|
||||
if (POOL_SCAN_STAT_VALID(pss_pass_scrub_flags, c) &&
|
||||
(ps->pss_pass_scrub_flags & POOL_SCRUB_THOROUGH) != 0)
|
||||
is_thorough = B_TRUE;
|
||||
if (POOL_SCAN_STAT_VALID(pss_pass_error_scrub_pause, c)) {
|
||||
have_errorscrub = (ps->pss_error_scrub_func ==
|
||||
POOL_SCAN_ERRORSCRUB);
|
||||
errorscrub_start = ps->pss_error_scrub_start;
|
||||
@@ -10124,7 +10409,7 @@ print_scan_status(zpool_handle_t *zhp, nvlist_t *nvroot)
|
||||
|
||||
/* Always print the scrub status when available. */
|
||||
if (have_scrub && scrub_start > errorscrub_start)
|
||||
print_scan_scrub_resilver_status(ps);
|
||||
print_scan_scrub_resilver_status(ps, is_thorough);
|
||||
else if (have_errorscrub && errorscrub_start >= scrub_start)
|
||||
print_err_scrub_status(ps);
|
||||
|
||||
@@ -10134,7 +10419,7 @@ print_scan_status(zpool_handle_t *zhp, nvlist_t *nvroot)
|
||||
*/
|
||||
if (active_resilver || (!active_rebuild && have_resilver &&
|
||||
resilver_end_time && resilver_end_time > rebuild_end_time)) {
|
||||
print_scan_scrub_resilver_status(ps);
|
||||
print_scan_scrub_resilver_status(ps, is_thorough);
|
||||
} else if (active_rebuild || (!active_resilver && have_rebuild &&
|
||||
rebuild_end_time && rebuild_end_time > resilver_end_time)) {
|
||||
print_rebuild_status(zhp, nvroot);
|
||||
@@ -10373,6 +10658,50 @@ print_checkpoint_status(pool_checkpoint_stat_t *pcs)
|
||||
space_buf);
|
||||
}
|
||||
|
||||
static void
|
||||
print_condense_status(nvlist_t *nv)
|
||||
{
|
||||
if (nv == NULL)
|
||||
return;
|
||||
|
||||
for (nvpair_t *nvp = nvlist_next_nvpair(nv, NULL);
|
||||
nvp != NULL; nvp = nvlist_next_nvpair(nv, nvp)) {
|
||||
const char *type = nvpair_name(nvp);
|
||||
nvlist_t *tnv = fnvpair_value_nvlist(nvp);
|
||||
|
||||
uint64_t start_time = fnvlist_lookup_uint64(tnv, "start_time");
|
||||
if (start_time == 0)
|
||||
continue;
|
||||
|
||||
uint64_t end_time = fnvlist_lookup_uint64(tnv, "end_time");
|
||||
uint64_t processed = fnvlist_lookup_uint64(tnv, "processed");
|
||||
uint64_t total = fnvlist_lookup_uint64(tnv, "total");
|
||||
|
||||
const char *units = condense_unit(type);
|
||||
|
||||
char cur[32], tot[32], elapsed[32];
|
||||
zfs_nicenum(processed, cur, sizeof (cur));
|
||||
zfs_nicenum(total, tot, sizeof (tot));
|
||||
|
||||
if (end_time == 0) {
|
||||
secs_to_dhms(time(NULL) - start_time, elapsed);
|
||||
(void) printf(gettext(
|
||||
"condense: %s: condensing, %s/%s %s done in %s\n"),
|
||||
type, cur, tot, units, elapsed);
|
||||
} else if (processed < total) {
|
||||
secs_to_dhms(end_time - start_time, elapsed);
|
||||
(void) printf(gettext(
|
||||
"condense: %s: cancelled, %s/%s %s done in %s\n"),
|
||||
type, cur, tot, units, elapsed);
|
||||
} else {
|
||||
secs_to_dhms(end_time - start_time, elapsed);
|
||||
(void) printf(gettext(
|
||||
"condense: %s: done, %s %s done in %s\n"),
|
||||
type, cur, units, elapsed);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
print_error_log(zpool_handle_t *zhp)
|
||||
{
|
||||
@@ -10672,8 +11001,8 @@ print_status_reason(zpool_handle_t *zhp, status_cbdata_t *cbp,
|
||||
(void) snprintf(action, AC_SIZE,
|
||||
gettext("Check the value of the "
|
||||
"'compatibility' property against the\n\t"
|
||||
"appropriate file in " ZPOOL_SYSCONF_COMPAT_D " or "
|
||||
ZPOOL_DATA_COMPAT_D ".\n"));
|
||||
"appropriate file in %s or %s.\n"),
|
||||
ZPOOL_SYSCONF_COMPAT_D, ZPOOL_DATA_COMPAT_D);
|
||||
break;
|
||||
|
||||
case ZPOOL_STATUS_INCOMPATIBLE_FEAT:
|
||||
@@ -10683,7 +11012,8 @@ print_status_reason(zpool_handle_t *zhp, status_cbdata_t *cbp,
|
||||
(void) snprintf(action, AC_SIZE, gettext("Consider setting "
|
||||
"'compatibility' to an appropriate value, or\n\t"
|
||||
"adding needed features to the relevant file in\n\t"
|
||||
ZPOOL_SYSCONF_COMPAT_D " or " ZPOOL_DATA_COMPAT_D ".\n"));
|
||||
"%s or %s.\n"),
|
||||
ZPOOL_SYSCONF_COMPAT_D, ZPOOL_DATA_COMPAT_D);
|
||||
break;
|
||||
|
||||
case ZPOOL_STATUS_UNSUP_FEAT_READ:
|
||||
@@ -10952,6 +11282,7 @@ status_callback_json(zpool_handle_t *zhp, void *data)
|
||||
scan_status_nvlist(zhp, cbp, nvroot, item);
|
||||
removal_status_nvlist(zhp, cbp, nvroot, item);
|
||||
checkpoint_status_nvlist(nvroot, cbp, item);
|
||||
condense_status_nvlist(nvroot, cbp, item);
|
||||
raidz_expand_status_nvlist(zhp, cbp, nvroot, item);
|
||||
vdev_stats_nvlist(zhp, cbp, nvroot, 0, B_FALSE, NULL, vds);
|
||||
if (cbp->cb_flat_vdevs) {
|
||||
@@ -11099,6 +11430,10 @@ status_callback(zpool_handle_t *zhp, void *data)
|
||||
ZPOOL_CONFIG_RAIDZ_EXPAND_STATS, (uint64_t **)&pres, &c);
|
||||
print_raidz_expand_status(zhp, pres);
|
||||
|
||||
nvlist_t *cnv = NULL;
|
||||
nvlist_lookup_nvlist(nvroot, ZPOOL_CONFIG_CONDENSE_STATS, &cnv);
|
||||
print_condense_status(cnv);
|
||||
|
||||
cbp->cb_namewidth = max_width(zhp, nvroot, 0, 0,
|
||||
cbp->cb_name_flags | VDEV_NAME_TYPE_ID);
|
||||
if (cbp->cb_namewidth < 10)
|
||||
@@ -13325,8 +13660,10 @@ print_wait_status_row(wait_data_t *wd, zpool_handle_t *zhp, int row)
|
||||
pool_scan_stat_t *pss = NULL;
|
||||
pool_removal_stat_t *prs = NULL;
|
||||
pool_raidz_expand_stat_t *pres = NULL;
|
||||
nvlist_t *cnv = NULL;
|
||||
const char *const headers[] = {"DISCARD", "FREE", "INITIALIZE",
|
||||
"REPLACE", "REMOVE", "RESILVER", "SCRUB", "TRIM", "RAIDZ_EXPAND"};
|
||||
"REPLACE", "REMOVE", "RESILVER", "SCRUB", "TRIM", "RAIDZ_EXPAND",
|
||||
"CONDENSE"};
|
||||
int col_widths[ZPOOL_WAIT_NUM_ACTIVITIES];
|
||||
|
||||
/* Calculate the width of each column */
|
||||
@@ -13395,6 +13732,22 @@ print_wait_status_row(wait_data_t *wd, zpool_handle_t *zhp, int row)
|
||||
bytes_rem[ZPOOL_WAIT_RAIDZ_EXPAND] = rem;
|
||||
}
|
||||
|
||||
/*
|
||||
* Count each outstanding condense item as a "byte". Its not true,
|
||||
* but its a counter, and it'll display nicely.
|
||||
*/
|
||||
if (nvlist_lookup_nvlist(nvroot,
|
||||
ZPOOL_CONFIG_CONDENSE_STATS, &cnv) == 0) {
|
||||
for (nvpair_t *nvp = nvlist_next_nvpair(cnv, NULL);
|
||||
nvp != NULL; nvp = nvlist_next_nvpair(cnv, nvp)) {
|
||||
nvlist_t *tnv = fnvpair_value_nvlist(nvp);
|
||||
uint64_t total = fnvlist_lookup_uint64(tnv, "total");
|
||||
uint64_t processed =
|
||||
fnvlist_lookup_uint64(tnv, "processed");
|
||||
bytes_rem[ZPOOL_WAIT_CONDENSE] += (total - processed);
|
||||
}
|
||||
}
|
||||
|
||||
bytes_rem[ZPOOL_WAIT_INITIALIZE] =
|
||||
vdev_activity_remaining(nvroot, ZPOOL_WAIT_INITIALIZE);
|
||||
bytes_rem[ZPOOL_WAIT_TRIM] =
|
||||
@@ -13533,7 +13886,7 @@ zpool_do_wait(int argc, char **argv)
|
||||
static const char *const col_opts[] = {
|
||||
"discard", "free", "initialize", "replace",
|
||||
"remove", "resilver", "scrub", "trim",
|
||||
"raidz_expand" };
|
||||
"raidz_expand", "condense" };
|
||||
|
||||
for (i = 0; i < ARRAY_SIZE(col_opts); ++i)
|
||||
if (strcmp(tok, col_opts[i]) == 0) {
|
||||
|
||||
@@ -1,23 +1,13 @@
|
||||
// SPDX-License-Identifier: CDDL-1.0
|
||||
/*
|
||||
* CDDL HEADER START
|
||||
* This file and its contents are supplied under the terms of the
|
||||
* Common Development and Distribution License ("CDDL"), version 1.0.
|
||||
* You may only use this file in accordance with the terms of version
|
||||
* 1.0 of the CDDL.
|
||||
*
|
||||
* The contents of this file are subject to the terms of the
|
||||
* Common Development and Distribution License (the "License").
|
||||
* You may not use this file except in compliance with the License.
|
||||
*
|
||||
* You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
|
||||
* or https://opensource.org/licenses/CDDL-1.0.
|
||||
* See the License for the specific language governing permissions
|
||||
* and limitations under the License.
|
||||
*
|
||||
* When distributing Covered Code, include this CDDL HEADER in each
|
||||
* file and include the License file at usr/src/OPENSOLARIS.LICENSE.
|
||||
* If applicable, add the following below this CDDL HEADER, with the
|
||||
* fields enclosed by brackets "[]" replaced with your own identifying
|
||||
* information: Portions Copyright [yyyy] [name of copyright owner]
|
||||
*
|
||||
* CDDL HEADER END
|
||||
* A full copy of the text of the CDDL should have accompanied this
|
||||
* source. A copy of the CDDL is also available via the Internet at
|
||||
* https://opensource.org/license/CDDL-1.0.
|
||||
*/
|
||||
/*
|
||||
* Copyright 2009 Sun Microsystems, Inc. All rights reserved.
|
||||
|
||||
@@ -1,23 +1,13 @@
|
||||
// SPDX-License-Identifier: CDDL-1.0
|
||||
/*
|
||||
* CDDL HEADER START
|
||||
* This file and its contents are supplied under the terms of the
|
||||
* Common Development and Distribution License ("CDDL"), version 1.0.
|
||||
* You may only use this file in accordance with the terms of version
|
||||
* 1.0 of the CDDL.
|
||||
*
|
||||
* The contents of this file are subject to the terms of the
|
||||
* Common Development and Distribution License (the "License").
|
||||
* You may not use this file except in compliance with the License.
|
||||
*
|
||||
* You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
|
||||
* or https://opensource.org/licenses/CDDL-1.0.
|
||||
* See the License for the specific language governing permissions
|
||||
* and limitations under the License.
|
||||
*
|
||||
* When distributing Covered Code, include this CDDL HEADER in each
|
||||
* file and include the License file at usr/src/OPENSOLARIS.LICENSE.
|
||||
* If applicable, add the following below this CDDL HEADER, with the
|
||||
* fields enclosed by brackets "[]" replaced with your own identifying
|
||||
* information: Portions Copyright [yyyy] [name of copyright owner]
|
||||
*
|
||||
* CDDL HEADER END
|
||||
* A full copy of the text of the CDDL should have accompanied this
|
||||
* source. A copy of the CDDL is also available via the Internet at
|
||||
* https://opensource.org/license/CDDL-1.0.
|
||||
*/
|
||||
/*
|
||||
* Copyright (c) 2005, 2010, Oracle and/or its affiliates. All rights reserved.
|
||||
|
||||
@@ -1,23 +1,13 @@
|
||||
// SPDX-License-Identifier: CDDL-1.0
|
||||
/*
|
||||
* CDDL HEADER START
|
||||
* This file and its contents are supplied under the terms of the
|
||||
* Common Development and Distribution License ("CDDL"), version 1.0.
|
||||
* You may only use this file in accordance with the terms of version
|
||||
* 1.0 of the CDDL.
|
||||
*
|
||||
* The contents of this file are subject to the terms of the
|
||||
* Common Development and Distribution License (the "License").
|
||||
* You may not use this file except in compliance with the License.
|
||||
*
|
||||
* You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
|
||||
* or https://opensource.org/licenses/CDDL-1.0.
|
||||
* See the License for the specific language governing permissions
|
||||
* and limitations under the License.
|
||||
*
|
||||
* When distributing Covered Code, include this CDDL HEADER in each
|
||||
* file and include the License file at usr/src/OPENSOLARIS.LICENSE.
|
||||
* If applicable, add the following below this CDDL HEADER, with the
|
||||
* fields enclosed by brackets "[]" replaced with your own identifying
|
||||
* information: Portions Copyright [yyyy] [name of copyright owner]
|
||||
*
|
||||
* CDDL HEADER END
|
||||
* A full copy of the text of the CDDL should have accompanied this
|
||||
* source. A copy of the CDDL is also available via the Internet at
|
||||
* https://opensource.org/license/CDDL-1.0.
|
||||
*/
|
||||
|
||||
/*
|
||||
|
||||
@@ -50,22 +50,14 @@
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*
|
||||
* CDDL HEADER START
|
||||
* This file and its contents are supplied under the terms of the
|
||||
* Common Development and Distribution License ("CDDL"), version 1.0.
|
||||
* You may only use this file in accordance with the terms of version
|
||||
* 1.0 of the CDDL.
|
||||
*
|
||||
* The contents of this file are subject to the terms of the
|
||||
* Common Development and Distribution License (the "License").
|
||||
* You may not use this file except in compliance with the License.
|
||||
*
|
||||
* The contents of this file are subject to the terms of the
|
||||
* Common Development and Distribution License Version 1.0 (CDDL-1.0).
|
||||
* You can obtain a copy of the license from the top-level file
|
||||
* "OPENSOLARIS.LICENSE" or at <http://opensource.org/licenses/CDDL-1.0>.
|
||||
* You may not use this file except in compliance with the license.
|
||||
*
|
||||
* See the License for the specific language governing permissions
|
||||
* and limitations under the License.
|
||||
*
|
||||
* CDDL HEADER END
|
||||
* A full copy of the text of the CDDL should have accompanied this
|
||||
* source. A copy of the CDDL is also available via the Internet at
|
||||
* https://opensource.org/license/CDDL-1.0.
|
||||
*/
|
||||
#include <string.h>
|
||||
#include <getopt.h>
|
||||
@@ -361,6 +353,7 @@ print_summary_stats(nvlist_t *nvroot, const char *pool_name,
|
||||
print_kv(",write_ops", vs->vs_ops[ZIO_TYPE_WRITE]);
|
||||
print_kv(",checksum_errors", vs->vs_checksum_errors);
|
||||
print_kv(",fragmentation", vs->vs_fragmentation);
|
||||
print_kv(",slow_ios", vs->vs_slow_ios);
|
||||
printf(" %llu\n", (u_longlong_t)timestamp);
|
||||
return (0);
|
||||
}
|
||||
|
||||
@@ -7,6 +7,8 @@ CPPCHECKTARGETS += zstream
|
||||
zstream_SOURCES = \
|
||||
%D%/zstream.c \
|
||||
%D%/zstream.h \
|
||||
%D%/zstream_backtrace.c \
|
||||
%D%/zstream_backtrace.h \
|
||||
%D%/zstream_byteswap.c \
|
||||
%D%/zstream_byteswap.h \
|
||||
%D%/zstream_chain.c \
|
||||
@@ -14,15 +16,22 @@ zstream_SOURCES = \
|
||||
%D%/zstream_decompress.c \
|
||||
%D%/zstream_drop_record.c \
|
||||
%D%/zstream_dump.c \
|
||||
%D%/zstream_dump.h \
|
||||
%D%/zstream_fletcher4.c \
|
||||
%D%/zstream_fletcher4.h \
|
||||
%D%/zstream_io.c \
|
||||
%D%/zstream_io.h \
|
||||
%D%/zstream_modules.h \
|
||||
%D%/zstream_raw.c \
|
||||
%D%/zstream_recompress.c \
|
||||
%D%/zstream_recompress.h \
|
||||
%D%/zstream_redup.c \
|
||||
%D%/zstream_selftest.c \
|
||||
%D%/zstream_selftest.h \
|
||||
%D%/zstream_selftest_queue.c \
|
||||
%D%/zstream_token.c \
|
||||
%D%/zstream_queue.c \
|
||||
%D%/zstream_queue.h \
|
||||
%D%/zstream_util.c \
|
||||
%D%/zstream_util.h \
|
||||
%D%/zstream_validate.c \
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
// SPDX-License-Identifier: CDDL-1.0
|
||||
/*
|
||||
* CDDL HEADER START
|
||||
*
|
||||
* This file and its contents are supplied under the terms of the
|
||||
* Common Development and Distribution License ("CDDL"), version 1.0.
|
||||
* You may only use this file in accordance with the terms of version
|
||||
@@ -9,9 +7,7 @@
|
||||
*
|
||||
* A full copy of the text of the CDDL should have accompanied this
|
||||
* source. A copy of the CDDL is also available via the Internet at
|
||||
* http://www.illumos.org/license/CDDL.
|
||||
*
|
||||
* CDDL HEADER END
|
||||
* https://opensource.org/license/CDDL-1.0.
|
||||
*/
|
||||
|
||||
/*
|
||||
@@ -19,11 +15,13 @@
|
||||
* Copyright (c) 2020 by Datto Inc. All rights reserved.
|
||||
*/
|
||||
|
||||
#include <signal.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "zstream.h"
|
||||
#include "zstream_util.h"
|
||||
|
||||
void
|
||||
zstream_usage(void)
|
||||
@@ -39,7 +37,10 @@ zstream_usage(void)
|
||||
"\n"
|
||||
"\tzstream drop_record [-v] [OBJECT,OFFSET] ...\n"
|
||||
"\n"
|
||||
"\tzstream recompress [ -l level] TYPE\n"
|
||||
"\tzstream raw [-v] [-b blocks] [-g guid] IMAGE|DEVICE FILE\n"
|
||||
"\t... | zstream raw [-v] [-b blocks] [-g guid] IMAGE|DEVICE\n"
|
||||
"\n"
|
||||
"\tzstream recompress [-t num_threads] [-l level] TYPE\n"
|
||||
"\n"
|
||||
"\tzstream token resume_token\n"
|
||||
"\n"
|
||||
@@ -47,9 +48,27 @@ zstream_usage(void)
|
||||
exit(1);
|
||||
}
|
||||
|
||||
/*
|
||||
* Set the signal mask to allow THREAD_BACKTRACE_SIGNAL. WATCHDOG_SIGNAL
|
||||
* must be blocked in all threads so that its intended recipient can listen
|
||||
* for it with sigwait(), which detects only pending signals.
|
||||
*/
|
||||
static void
|
||||
set_signal_mask(void)
|
||||
{
|
||||
sigset_t mask;
|
||||
|
||||
safe_pthread_sigmask(SIG_SETMASK, NULL, &mask);
|
||||
sigaddset(&mask, WATCHDOG_SIGNAL);
|
||||
sigdelset(&mask, THREAD_BACKTRACE_SIGNAL);
|
||||
safe_pthread_sigmask(SIG_SETMASK, &mask, NULL);
|
||||
}
|
||||
|
||||
int
|
||||
main(int argc, char *argv[])
|
||||
{
|
||||
set_signal_mask();
|
||||
|
||||
char *basename = strrchr(argv[0], '/');
|
||||
basename = basename ? (basename + 1) : argv[0];
|
||||
if (argc >= 1 && strcmp(basename, "zstreamdump") == 0)
|
||||
@@ -66,12 +85,17 @@ main(int argc, char *argv[])
|
||||
return (zstream_do_decompress(argc - 1, argv + 1));
|
||||
} else if (strcmp(subcommand, "drop_record") == 0) {
|
||||
return (zstream_do_drop_record(argc - 1, argv + 1));
|
||||
} else if (strcmp(subcommand, "raw") == 0) {
|
||||
return (zstream_do_raw(argc - 1, argv + 1));
|
||||
} else if (strcmp(subcommand, "recompress") == 0) {
|
||||
return (zstream_do_recompress(argc - 1, argv + 1));
|
||||
} else if (strcmp(subcommand, "token") == 0) {
|
||||
return (zstream_do_token(argc - 1, argv + 1));
|
||||
} else if (strcmp(subcommand, "redup") == 0) {
|
||||
return (zstream_do_redup(argc - 1, argv + 1));
|
||||
} else if (strcmp(subcommand, "selftest") == 0) {
|
||||
/* Undocumented; used by the ZFS test suite */
|
||||
return (zstream_do_selftest(argc - 1, argv + 1));
|
||||
} else {
|
||||
zstream_usage();
|
||||
}
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
// SPDX-License-Identifier: CDDL-1.0
|
||||
/*
|
||||
* CDDL HEADER START
|
||||
*
|
||||
* This file and its contents are supplied under the terms of the
|
||||
* Common Development and Distribution License ("CDDL"), version 1.0.
|
||||
* You may only use this file in accordance with the terms of version
|
||||
@@ -9,9 +7,7 @@
|
||||
*
|
||||
* A full copy of the text of the CDDL should have accompanied this
|
||||
* source. A copy of the CDDL is also available via the Internet at
|
||||
* http://www.illumos.org/license/CDDL.
|
||||
*
|
||||
* CDDL HEADER END
|
||||
* https://opensource.org/license/CDDL-1.0.
|
||||
*/
|
||||
|
||||
/*
|
||||
@@ -21,17 +17,28 @@
|
||||
#ifndef _ZSTREAM_H
|
||||
#define _ZSTREAM_H
|
||||
|
||||
#include <signal.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/*
|
||||
* Signals used by the watchdog timer. The signal mask must be set properly
|
||||
* before any threads are spawned.
|
||||
*/
|
||||
#define WATCHDOG_SIGNAL SIGALRM
|
||||
#define THREAD_BACKTRACE_SIGNAL SIGRTMIN
|
||||
|
||||
extern int zstream_do_redup(int, char *[]);
|
||||
extern int zstream_do_dump(int, char *[]);
|
||||
extern int zstream_do_decompress(int argc, char *argv[]);
|
||||
extern int zstream_do_drop_record(int argc, char *argv[]);
|
||||
extern int zstream_do_recompress(int argc, char *argv[]);
|
||||
extern int zstream_do_token(int, char *[]);
|
||||
extern void zstream_usage(void);
|
||||
extern int zstream_do_raw(int, char *[]);
|
||||
extern int zstream_do_selftest(int, char *[]);
|
||||
extern void zstream_usage(void) __attribute__((noreturn));
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
|
||||
@@ -0,0 +1,181 @@
|
||||
// SPDX-License-Identifier: CDDL-1.0
|
||||
/*
|
||||
* This file and its contents are supplied under the terms of the
|
||||
* Common Development and Distribution License ("CDDL"), version 1.0.
|
||||
* You may only use this file in accordance with the terms of version
|
||||
* 1.0 of the CDDL.
|
||||
*
|
||||
* A full copy of the text of the CDDL should have accompanied this
|
||||
* source. A copy of the CDDL is also available via the Internet at
|
||||
* https://opensource.org/license/CDDL-1.0.
|
||||
*/
|
||||
|
||||
/*
|
||||
* Copyright (c) 2026 by Garth Snyder. All rights reserved.
|
||||
*/
|
||||
|
||||
/*
|
||||
* This is a watchdog timer and multithread backtrace dumper that's used by
|
||||
* zstream selftest. libspl_backtrace() does all the real work, but it can
|
||||
* only dump the current thread's stack. We need to get every thread to call
|
||||
* libspl_backtrace() in an organized sequence. However, there's no "give me
|
||||
* a list of all pthreads" function in the POSIX API.
|
||||
*
|
||||
* Rather than constructing an ad-hoc thread registry, we can approach the
|
||||
* problem by unblocking THREAD_BACKTRACE_SIGNAL (an arbitrary choice) when
|
||||
* zstream first starts. All created threads then inherit this signal mask.
|
||||
*
|
||||
* In selftest mode, we add the backtrace dumper as a handler for
|
||||
* THREAD_BACKTRACE_SIGNAL. The handler calls libspl_backtrace(), which is
|
||||
* signal-handler safe. It then signals a semaphore to indicate that the
|
||||
* current thread has finished its dump and suspends itself.
|
||||
*
|
||||
* The watchdog supervisor is a separate thread that sigwait()s for SIGALRM.
|
||||
* It blocks THREAD_BACKTRACE_SIGNAL and runs its own libspl_backtrace(). It
|
||||
* then enters a loop in which it sends THREAD_BACKTRACE_SIGNAL to the
|
||||
* process as a whole and runs sem_timedwait() to see if any thread woke up
|
||||
* and dumped its backtrace. If that call times out, either the receiving
|
||||
* thread wedged while trying to backtrace or there were no more threads to
|
||||
* backtrace.
|
||||
*
|
||||
* These two scenarios can be distinguished by calling sigpending(). If
|
||||
* THREAD_BACKTRACE_SIGNAL still shows as being pending on the process, then
|
||||
* there was no thread to receive it and we are done. If there's no pending
|
||||
* signal, then some thread did receive the signal but failed to post to the
|
||||
* semaphore; we print a "thread wedged while backtracing" message and
|
||||
* continue the loop.
|
||||
*/
|
||||
|
||||
#include <err.h>
|
||||
#include <errno.h>
|
||||
#include <semaphore.h>
|
||||
#include <signal.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <sys/backtrace.h>
|
||||
#include <sys/debug.h>
|
||||
#include <sys/stdtypes.h>
|
||||
#include <sys/types.h>
|
||||
#include <time.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "zstream.h"
|
||||
#include "zstream_backtrace.h"
|
||||
#include "zstream_util.h"
|
||||
|
||||
/*
|
||||
* Watchdog timeout in seconds. A test that hits this limit is almost
|
||||
* certainly deadlocked, and the watchdog converts the hang into a test
|
||||
* failure instead of a stuck test run.
|
||||
*/
|
||||
#define WATCHDOG_TIMEOUT_SECS 120
|
||||
#define MAX_SECS_FOR_BACKTRACE 2
|
||||
|
||||
static sem_t sem_thread_bt_complete; /* thread -> watchdog */
|
||||
|
||||
/*
|
||||
* Signal handler for THREAD_BACKTRACE_SIGNAL, run by all threads except the
|
||||
* watchdog thread
|
||||
*/
|
||||
static void
|
||||
backtrace_self(int signal)
|
||||
{
|
||||
(void) signal;
|
||||
ssize_t dummy __maybe_unused = write(STDERR_FILENO, "\n", 1);
|
||||
libspl_backtrace(STDERR_FILENO);
|
||||
sem_post(&sem_thread_bt_complete);
|
||||
|
||||
sigset_t mask;
|
||||
sigfillset(&mask);
|
||||
sigsuspend(&mask);
|
||||
}
|
||||
|
||||
static void
|
||||
backtrace_all_threads(void)
|
||||
{
|
||||
while (B_TRUE) {
|
||||
if (kill(getpid(), THREAD_BACKTRACE_SIGNAL) != 0)
|
||||
err(1, "failed to send thread backtrace signal");
|
||||
struct timespec deadline;
|
||||
clock_gettime(CLOCK_REALTIME, &deadline);
|
||||
deadline.tv_sec += MAX_SECS_FOR_BACKTRACE;
|
||||
if (sem_timedwait(&sem_thread_bt_complete, &deadline) != 0) {
|
||||
sigset_t pending;
|
||||
if (sigpending(&pending) != 0)
|
||||
err(1, "sigpending failed");
|
||||
if (sigismember(&pending, THREAD_BACKTRACE_SIGNAL)) {
|
||||
return;
|
||||
} else {
|
||||
warnx("a thread failed to generate a backtrace,"
|
||||
" continuing...");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Body of the watchdog thread
|
||||
*/
|
||||
static void *
|
||||
watchdog(void *nope)
|
||||
{
|
||||
(void) nope;
|
||||
int signal;
|
||||
sigset_t bt_mask, dog_mask;
|
||||
|
||||
/*
|
||||
* This thread does its own backtrace, so we block the
|
||||
* backtrace signal.
|
||||
*/
|
||||
sigemptyset(&bt_mask);
|
||||
sigaddset(&bt_mask, THREAD_BACKTRACE_SIGNAL);
|
||||
if (pthread_sigmask(SIG_BLOCK, &bt_mask, NULL) != 0)
|
||||
err(1, "pthread_sigmask failed");
|
||||
|
||||
sigemptyset(&dog_mask);
|
||||
sigaddset(&dog_mask, WATCHDOG_SIGNAL);
|
||||
|
||||
int rc = sigwait(&dog_mask, &signal);
|
||||
if (rc != 0) {
|
||||
errno = rc;
|
||||
err(1, "watchdog sigwait failed");
|
||||
} else if (signal != WATCHDOG_SIGNAL) {
|
||||
errx(1, "unexpected signal %d received by watchdog", signal);
|
||||
}
|
||||
|
||||
fprintf(stderr, "\n\nWATCHDOG TIMER EXPIRED\n"
|
||||
"Dumping backtrace for all threads...\n\n");
|
||||
fflush(stderr);
|
||||
libspl_backtrace(STDERR_FILENO);
|
||||
backtrace_all_threads();
|
||||
fprintf(stderr, "\nAll threads backtraced, exiting.\n");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
void
|
||||
watchdog_init(void)
|
||||
{
|
||||
if (sem_init(&sem_thread_bt_complete, 0, 0) != 0)
|
||||
err(1, "watchdog sem_init failed");
|
||||
|
||||
safe_create_thread(watchdog, NULL, "watchdog", B_TRUE);
|
||||
|
||||
struct sigaction sa = {
|
||||
.sa_handler = backtrace_self,
|
||||
.sa_flags = SA_RESTART
|
||||
};
|
||||
if (sigaction(THREAD_BACKTRACE_SIGNAL, &sa, NULL) != 0)
|
||||
err(1, "backtrace sigaction failed");
|
||||
}
|
||||
|
||||
void
|
||||
watchdog_arm(void)
|
||||
{
|
||||
(void) alarm(WATCHDOG_TIMEOUT_SECS);
|
||||
}
|
||||
|
||||
void
|
||||
watchdog_disarm(void)
|
||||
{
|
||||
(void) alarm(0);
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
// SPDX-License-Identifier: CDDL-1.0
|
||||
/*
|
||||
* This file and its contents are supplied under the terms of the
|
||||
* Common Development and Distribution License ("CDDL"), version 1.0.
|
||||
* You may only use this file in accordance with the terms of version
|
||||
* 1.0 of the CDDL.
|
||||
*
|
||||
* A full copy of the text of the CDDL should have accompanied this
|
||||
* source. A copy of the CDDL is also available via the Internet at
|
||||
* https://opensource.org/license/CDDL-1.0.
|
||||
*/
|
||||
|
||||
/*
|
||||
* Copyright (c) 2026 by Garth Snyder. All rights reserved.
|
||||
*/
|
||||
|
||||
#ifndef _ZSTREAM_BACKTRACE_H
|
||||
#define _ZSTREAM_BACKTRACE_H
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
void
|
||||
watchdog_init(void);
|
||||
|
||||
void
|
||||
watchdog_arm(void);
|
||||
|
||||
void
|
||||
watchdog_disarm(void);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* _ZSTREAM_BACKTRACE_H */
|
||||
@@ -1,16 +1,13 @@
|
||||
// SPDX-License-Identifier: CDDL-1.0
|
||||
/*
|
||||
* CDDL HEADER START
|
||||
* This file and its contents are supplied under the terms of the
|
||||
* Common Development and Distribution License ("CDDL"), version 1.0.
|
||||
* You may only use this file in accordance with the terms of version
|
||||
* 1.0 of the CDDL.
|
||||
*
|
||||
* This file and its contents are supplied under the terms of the Common
|
||||
* Development and Distribution License ("CDDL"), version 1.0. You may only use
|
||||
* this file in accordance with the terms of version 1.0 of the CDDL.
|
||||
*
|
||||
* A full copy of the text of the CDDL should have accompanied this source. A
|
||||
* copy of the CDDL is also available via the Internet at
|
||||
* http://www.illumos.org/license/CDDL.
|
||||
*
|
||||
* CDDL HEADER END
|
||||
* A full copy of the text of the CDDL should have accompanied this
|
||||
* source. A copy of the CDDL is also available via the Internet at
|
||||
* https://opensource.org/license/CDDL-1.0.
|
||||
*/
|
||||
|
||||
/*
|
||||
@@ -40,8 +37,11 @@ static byteswap_context_t byteswap_contexts[MAX_BYTESWAP];
|
||||
static int next_context = 0;
|
||||
|
||||
static disposition_t
|
||||
chain_byteswap(drr_packet_t *item, byteswap_context_t *context)
|
||||
chain_byteswap(void *item_in, void *context_in)
|
||||
{
|
||||
drr_packet_t *item = (drr_packet_t *)item_in;
|
||||
byteswap_context_t *context = (byteswap_context_t *)context_in;
|
||||
|
||||
if (item == NULL) {
|
||||
return (D_OK);
|
||||
}
|
||||
@@ -191,7 +191,7 @@ serial_byteswap(byteswap_stage_t stage)
|
||||
.cs_out_size = sizeof (drr_packet_t),
|
||||
.cs_context = bsc,
|
||||
.cs_serial = {
|
||||
.process = (zc_serial_process_f *)chain_byteswap,
|
||||
.process = chain_byteswap,
|
||||
}
|
||||
};
|
||||
return (step);
|
||||
|
||||
@@ -1,16 +1,13 @@
|
||||
// SPDX-License-Identifier: CDDL-1.0
|
||||
/*
|
||||
* CDDL HEADER START
|
||||
* This file and its contents are supplied under the terms of the
|
||||
* Common Development and Distribution License ("CDDL"), version 1.0.
|
||||
* You may only use this file in accordance with the terms of version
|
||||
* 1.0 of the CDDL.
|
||||
*
|
||||
* This file and its contents are supplied under the terms of the Common
|
||||
* Development and Distribution License ("CDDL"), version 1.0. You may only use
|
||||
* this file in accordance with the terms of version 1.0 of the CDDL.
|
||||
*
|
||||
* A full copy of the text of the CDDL should have accompanied this source. A
|
||||
* copy of the CDDL is also available via the Internet at
|
||||
* http://www.illumos.org/license/CDDL.
|
||||
*
|
||||
* CDDL HEADER END
|
||||
* A full copy of the text of the CDDL should have accompanied this
|
||||
* source. A copy of the CDDL is also available via the Internet at
|
||||
* https://opensource.org/license/CDDL-1.0.
|
||||
*/
|
||||
|
||||
/*
|
||||
|
||||
@@ -1,16 +1,13 @@
|
||||
// SPDX-License-Identifier: CDDL-1.0
|
||||
/*
|
||||
* CDDL HEADER START
|
||||
* This file and its contents are supplied under the terms of the
|
||||
* Common Development and Distribution License ("CDDL"), version 1.0.
|
||||
* You may only use this file in accordance with the terms of version
|
||||
* 1.0 of the CDDL.
|
||||
*
|
||||
* This file and its contents are supplied under the terms of the Common
|
||||
* Development and Distribution License ("CDDL"), version 1.0. You may only use
|
||||
* this file in accordance with the terms of version 1.0 of the CDDL.
|
||||
*
|
||||
* A full copy of the text of the CDDL should have accompanied this source. A
|
||||
* copy of the CDDL is also available via the Internet at
|
||||
* http://www.illumos.org/license/CDDL.
|
||||
*
|
||||
* CDDL HEADER END
|
||||
* A full copy of the text of the CDDL should have accompanied this
|
||||
* source. A copy of the CDDL is also available via the Internet at
|
||||
* https://opensource.org/license/CDDL-1.0.
|
||||
*/
|
||||
|
||||
/*
|
||||
@@ -20,41 +17,47 @@
|
||||
#include <assert.h>
|
||||
#include <err.h>
|
||||
#include <libspl.h>
|
||||
#include <pthread.h>
|
||||
#include <stddef.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <sys/abd.h>
|
||||
#include <sys/param.h>
|
||||
#include <sys/stdtypes.h>
|
||||
#include <sys/zio.h>
|
||||
#include <sys/zstd/zstd.h>
|
||||
#include <sys/zfs_refcount.h>
|
||||
#include <zfs_fletcher.h>
|
||||
|
||||
#include "zstream_chain.h"
|
||||
#include "zstream_queue.h"
|
||||
#include "zstream_util.h"
|
||||
|
||||
#define MAX_CHAIN_LENGTH 32
|
||||
|
||||
/*
|
||||
* Calculated information about a chain. Not to be confused with
|
||||
* chain_attrs_t, which is a global set of options and stream attributes
|
||||
* available to all chain steps.
|
||||
*/
|
||||
typedef struct {
|
||||
int ct_num_steps;
|
||||
int ct_num_queues;
|
||||
size_t ct_item_size;
|
||||
} chain_stats_t;
|
||||
|
||||
/*
|
||||
* Data passed to worker threads
|
||||
*/
|
||||
typedef struct {
|
||||
chain_step_t *wc_steps;
|
||||
int wc_num_steps;
|
||||
size_t wc_buffer_size;
|
||||
zstream_queue_t *wc_in_queue;
|
||||
zstream_queue_t *wc_out_queue;
|
||||
} worker_context_t;
|
||||
|
||||
chain_attrs_t *chain_attrs;
|
||||
|
||||
static disposition_t
|
||||
chain_null_step(void *item, void *context)
|
||||
{
|
||||
(void) item;
|
||||
(void) context;
|
||||
return (D_OK);
|
||||
}
|
||||
|
||||
chain_step_t
|
||||
serial_null_step(void)
|
||||
{
|
||||
chain_step_t step = {
|
||||
.cs_type = CS_SERIAL,
|
||||
.cs_serial = {
|
||||
.process = (zc_serial_process_f *)chain_null_step
|
||||
}
|
||||
};
|
||||
return (step);
|
||||
}
|
||||
|
||||
chain_step_t
|
||||
chain_terminator(void)
|
||||
{
|
||||
@@ -85,67 +88,239 @@ libraries_fini(void)
|
||||
}
|
||||
|
||||
/*
|
||||
* Execute a chain of serial processing steps.
|
||||
* Body function for worker threads
|
||||
*/
|
||||
static void *
|
||||
zstream_chain_worker(void *ctxt_in)
|
||||
{
|
||||
worker_context_t *ctxt = (worker_context_t *)ctxt_in;
|
||||
uint8_t buffer[ctxt->wc_buffer_size];
|
||||
boolean_t done = B_FALSE;
|
||||
|
||||
while (!done) {
|
||||
for (int i = 0; i < ctxt->wc_num_steps; i++) {
|
||||
chain_step_t *step = &ctxt->wc_steps[i];
|
||||
if (step->cs_type == CS_SERIAL) {
|
||||
if (done) {
|
||||
(void) step->cs_serial.process(NULL,
|
||||
step->cs_context);
|
||||
} else {
|
||||
disposition_t dispo =
|
||||
step->cs_serial.process(buffer,
|
||||
step->cs_context);
|
||||
if (dispo == D_EOF) {
|
||||
done = B_TRUE;
|
||||
} else if (dispo == D_DROP) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else if (i == 0) {
|
||||
done = done ||
|
||||
!zstream_dequeue(ctxt->wc_in_queue, buffer);
|
||||
} else if (done) {
|
||||
zstream_queue_fini(ctxt->wc_out_queue);
|
||||
} else {
|
||||
zstream_enqueue(ctxt->wc_out_queue, buffer);
|
||||
}
|
||||
}
|
||||
}
|
||||
return (NULL);
|
||||
}
|
||||
|
||||
/*
|
||||
* Validate chain and calculate number of steps, max packet size, and number
|
||||
* of zstream_queues that must be created.
|
||||
*/
|
||||
static chain_stats_t
|
||||
validate_chain(zstream_chain_t chain)
|
||||
{
|
||||
int num_steps = 0;
|
||||
int num_queues = 0;
|
||||
size_t item_size = 0;
|
||||
|
||||
while (chain[num_steps].cs_type != CS_TERMINATE) {
|
||||
if (num_steps >= MAX_CHAIN_LENGTH) {
|
||||
errx(1, "unterminated zstream_chain");
|
||||
}
|
||||
chain_step_t *step = &chain[num_steps];
|
||||
item_size = MAX(item_size, step->cs_out_size);
|
||||
if (step->cs_type == CS_PARALLEL) {
|
||||
num_queues++;
|
||||
}
|
||||
num_steps++;
|
||||
}
|
||||
VERIFY3U(num_steps, >, 0);
|
||||
|
||||
boolean_t first_parallel = chain[0].cs_type == CS_PARALLEL;
|
||||
boolean_t last_parallel = chain[num_steps-1].cs_type == CS_PARALLEL;
|
||||
if (first_parallel || last_parallel) {
|
||||
errx(1, "a chain cannot start or end with a parallel step");
|
||||
}
|
||||
|
||||
/*
|
||||
* Check for consistency of input and output packet sizes in
|
||||
* adjacent steps.
|
||||
*/
|
||||
for (int i = 0; i < num_steps; i++) {
|
||||
if (i > 0 && chain[i].cs_in_size != chain[i-1].cs_out_size) {
|
||||
warnx("adjacent chain steps %d and %d declare "
|
||||
"incompatible packet sizes", i - 1, i);
|
||||
}
|
||||
}
|
||||
|
||||
chain_stats_t stats = {
|
||||
.ct_num_steps = num_steps,
|
||||
.ct_num_queues = num_queues,
|
||||
.ct_item_size = item_size
|
||||
};
|
||||
return (stats);
|
||||
}
|
||||
|
||||
/*
|
||||
* Execute a chain of processing steps, some parallel and some serial.
|
||||
*
|
||||
* For simplicity, we normalize the chain item size to that of the largest
|
||||
* output of any step. Packets with data beyond the base drr_record_t should
|
||||
* add their additional data to the end of the packet, and this area may be
|
||||
* reused for different purposes as items travel down the chain.
|
||||
* output of any step. Payloads are allocated on the heap, so the maximal
|
||||
* item size is typically on the order of 512 bytes.
|
||||
*
|
||||
* Each item traverses the entire chain before the next item is read.
|
||||
* Packets with data beyond the base drr_record_t should add their
|
||||
* additional data to the end of the packet, and this area may be reused for
|
||||
* different purposes as items travel down the chain.
|
||||
*
|
||||
* One worker thread is assigned to every contiguous sequence of serial
|
||||
* steps plus the parallel steps on either side of that block (if any).
|
||||
* Adjacent parallel steps also receive a worker. This isn't a special case,
|
||||
* it's just the same rule with the serial block consisting of zero steps.
|
||||
*
|
||||
* Parallel steps are double-covered, which is the intended behavior. If a
|
||||
* worker's domain begins with a parallel step, it dequeues items from the
|
||||
* associated queue. If the domain ends with a parallel step, it submits
|
||||
* items to that queue.
|
||||
*/
|
||||
void
|
||||
zstream_chain_exec(zstream_chain_t chain, chain_attrs_t *attrs)
|
||||
{
|
||||
int num_steps = 0;
|
||||
size_t packet_size = 0;
|
||||
chain_attrs_t backup_attrs = {0};
|
||||
chain_stats_t stats = validate_chain(chain);
|
||||
|
||||
chain_attrs = attrs ? attrs : &backup_attrs;
|
||||
|
||||
while (chain[num_steps].cs_type != CS_TERMINATE) {
|
||||
packet_size = MAX(packet_size, chain[num_steps].cs_out_size);
|
||||
num_steps++;
|
||||
if (num_steps >= MAX_CHAIN_LENGTH) {
|
||||
errx(1, "unterminated zstream_chain");
|
||||
}
|
||||
}
|
||||
VERIFY3U(num_steps, >, 0);
|
||||
int num_workers = stats.ct_num_queues + 1;
|
||||
worker_context_t contexts[num_workers];
|
||||
pthread_t worker_threads[num_workers];
|
||||
zstream_queue_t *queue;
|
||||
|
||||
/*
|
||||
* Check for consistency of input and output packet sizes in
|
||||
* adjacent steps. A declared packet size of zero waives this check.
|
||||
* Create parallel queues and worker thread contexts
|
||||
*
|
||||
* We do not need to track zstream_queues independently of worker
|
||||
* contexts because queues clean themselves up once the last item
|
||||
* has been dequeued. The stream eventually ends, so some worker
|
||||
* thread will eventually call zstream_queue_fini() on every queue.
|
||||
*/
|
||||
for (int i = 0; i < num_steps; i++) {
|
||||
boolean_t mismatch = i > 0 &&
|
||||
chain[i].cs_in_size != 0 &&
|
||||
chain[i-1].cs_out_size != 0 &&
|
||||
chain[i].cs_in_size != chain[i-1].cs_out_size;
|
||||
if (mismatch) {
|
||||
warnx("note - chain steps %d and %d have "
|
||||
"mismatched packet sizes", i - 1, i);
|
||||
|
||||
int worker = 0;
|
||||
|
||||
worker_context_t context = {
|
||||
.wc_steps = chain,
|
||||
.wc_num_steps = 0,
|
||||
.wc_buffer_size = stats.ct_item_size,
|
||||
.wc_in_queue = NULL
|
||||
};
|
||||
contexts[worker] = context;
|
||||
|
||||
for (int i = 0; i < stats.ct_num_steps; i++) {
|
||||
contexts[worker].wc_num_steps++;
|
||||
if (chain[i].cs_type == CS_PARALLEL) {
|
||||
chain_step_t *cs = &chain[i];
|
||||
zq_params_t queue_params = {
|
||||
.qp_process = cs->cs_parallel.process,
|
||||
.qp_cost = cs->cs_parallel.cost,
|
||||
.qp_item_size = stats.ct_item_size,
|
||||
.qp_batch_budget = cs->cs_parallel.batch_budget,
|
||||
.qp_queue_length = cs->cs_parallel.queue_length,
|
||||
.qp_context = cs->cs_context
|
||||
};
|
||||
queue = zstream_queue_create(&queue_params);
|
||||
contexts[worker].wc_out_queue = queue;
|
||||
worker++;
|
||||
worker_context_t next_context = {
|
||||
.wc_steps = cs,
|
||||
.wc_num_steps = 1,
|
||||
.wc_buffer_size = stats.ct_item_size,
|
||||
.wc_in_queue = queue
|
||||
};
|
||||
contexts[worker] = next_context;
|
||||
}
|
||||
}
|
||||
|
||||
contexts[worker].wc_out_queue = NULL;
|
||||
|
||||
chain_attrs_t backup_attrs = {0};
|
||||
chain_attrs = attrs ? attrs : &backup_attrs;
|
||||
|
||||
libraries_init();
|
||||
|
||||
uint8_t buffer[packet_size];
|
||||
/* Spawn threads */
|
||||
for (int i = 0; i < num_workers; i++) {
|
||||
char name[32];
|
||||
snprintf(name, sizeof (name), "chain-%d", i);
|
||||
worker_threads[i] = safe_create_thread(zstream_chain_worker,
|
||||
&contexts[i], name, B_FALSE);
|
||||
}
|
||||
|
||||
/* Reap threads */
|
||||
for (int i = 0; i < num_workers; i++) {
|
||||
int ret = pthread_join(worker_threads[i], NULL);
|
||||
VERIFY3S(ret, ==, 0);
|
||||
}
|
||||
|
||||
libraries_fini();
|
||||
}
|
||||
|
||||
/*
|
||||
* Execute a chain linearly, without queues and without multithreading. This
|
||||
* form of execution is intended as a debugging aid, both for clients and
|
||||
* for the chain mechanism itself. If this variant doesn't produce results
|
||||
* identical to zstream_chain_exec(), there's a multithreading-related bug
|
||||
* somewhere.
|
||||
*
|
||||
* It is not necessary to remove parallel steps from the input chain. They
|
||||
* are accepted as-is, but their execution won't be parallelized.
|
||||
*/
|
||||
void
|
||||
zstream_chain_exec_serialized(zstream_chain_t chain, chain_attrs_t *attrs)
|
||||
{
|
||||
chain_stats_t stats = validate_chain(chain);
|
||||
|
||||
uint8_t buffer[stats.ct_item_size];
|
||||
boolean_t done = B_FALSE;
|
||||
|
||||
chain_attrs_t backup_attrs = {0};
|
||||
chain_attrs = attrs ? attrs : &backup_attrs;
|
||||
|
||||
libraries_init();
|
||||
|
||||
while (!done) {
|
||||
for (int i = 0; i < num_steps; i++) {
|
||||
if (done) {
|
||||
(void) chain[i].cs_serial.process(NULL,
|
||||
chain[i].cs_context);
|
||||
} else {
|
||||
disposition_t dispo =
|
||||
chain[i].cs_serial.process(buffer,
|
||||
chain[i].cs_context);
|
||||
if (dispo == D_EOF) {
|
||||
VERIFY0(i);
|
||||
done = B_TRUE;
|
||||
} else if (dispo == D_DROP) {
|
||||
break;
|
||||
for (int i = 0; i < stats.ct_num_steps; i++) {
|
||||
chain_step_t *step = &chain[i];
|
||||
if (step->cs_type == CS_SERIAL) {
|
||||
if (done) {
|
||||
(void) step->cs_serial.process(NULL,
|
||||
step->cs_context);
|
||||
} else {
|
||||
disposition_t dispo =
|
||||
step->cs_serial.process(buffer,
|
||||
step->cs_context);
|
||||
if (dispo == D_EOF) {
|
||||
done = B_TRUE;
|
||||
} else if (dispo == D_DROP) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else if (!done) {
|
||||
size_t cost = step->cs_parallel.cost(buffer,
|
||||
step->cs_context);
|
||||
if (cost > 0) {
|
||||
step->cs_parallel.process(buffer,
|
||||
step->cs_context);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,16 +1,13 @@
|
||||
// SPDX-License-Identifier: CDDL-1.0
|
||||
/*
|
||||
* CDDL HEADER START
|
||||
* This file and its contents are supplied under the terms of the
|
||||
* Common Development and Distribution License ("CDDL"), version 1.0.
|
||||
* You may only use this file in accordance with the terms of version
|
||||
* 1.0 of the CDDL.
|
||||
*
|
||||
* This file and its contents are supplied under the terms of the Common
|
||||
* Development and Distribution License ("CDDL"), version 1.0. You may only use
|
||||
* this file in accordance with the terms of version 1.0 of the CDDL.
|
||||
*
|
||||
* A full copy of the text of the CDDL should have accompanied this source. A
|
||||
* copy of the CDDL is also available via the Internet at
|
||||
* http://www.illumos.org/license/CDDL.
|
||||
*
|
||||
* CDDL HEADER END
|
||||
* A full copy of the text of the CDDL should have accompanied this
|
||||
* source. A copy of the CDDL is also available via the Internet at
|
||||
* https://opensource.org/license/CDDL-1.0.
|
||||
*/
|
||||
|
||||
/*
|
||||
@@ -28,6 +25,8 @@ extern "C" {
|
||||
#include <stdint.h>
|
||||
#include <sys/zfs_ioctl.h>
|
||||
|
||||
#include "zstream_queue.h"
|
||||
|
||||
/*
|
||||
* A chain is a linear series of steps that process packets of data. It's
|
||||
* designed to modularize common functionality, reduce code duplication, and
|
||||
@@ -36,20 +35,21 @@ extern "C" {
|
||||
* Some terms:
|
||||
*
|
||||
* **STEP** - A chain_step_t struct that represents a packet-processing
|
||||
* module and any arguments or context that it needs. Chain modules
|
||||
* generally define a function named serial_* that produces a chain_step_t
|
||||
* that can be incorporated directly into a chain.
|
||||
* module and any arguments or context that it needs. Chain participants
|
||||
* generally define a function named serial_xxx() or parallel_xxx() that
|
||||
* produces a chain_step_t that can be incorporated directly into a chain.
|
||||
*
|
||||
* **CHAIN** - An array of chain_step_t's. It's just data, so you can create
|
||||
* the array however you like. But normally you'd just declare the whole
|
||||
* thing:
|
||||
* chain at once, e.g.:
|
||||
*
|
||||
* zstream_chain_t dump_chain = {
|
||||
* serial_read_stream(infile),
|
||||
* parallel_calc_fletcher4(1024),
|
||||
* serial_validate_fletcher4(),
|
||||
* serial_byteswap(BS_INCOMING),
|
||||
* serial_validate_records(),
|
||||
* serial_dump_records(&dump_args),
|
||||
* serial_dump_records(),
|
||||
* serial_null_output(),
|
||||
* chain_terminator()
|
||||
* }
|
||||
@@ -58,7 +58,7 @@ extern "C" {
|
||||
*
|
||||
* zstream_chain_t dump_chain = {
|
||||
* STANDARD_INPUT_STACK(infile),
|
||||
* serial_dump_records(&dump_args),
|
||||
* serial_dump_records(),
|
||||
* NULL_OUTPUT_STACK()
|
||||
* };
|
||||
*
|
||||
@@ -73,20 +73,26 @@ extern "C" {
|
||||
* **PROCESSING FUNCTION** - Each step names a processing function that does
|
||||
* the actual work of transforming an input buffer into an output buffer.
|
||||
* The transformation happens in place, in a single buffer provided by the
|
||||
* chain.
|
||||
* chain. (Steps that run in parallel must also identify a cost function; see
|
||||
* zstream_queue.h.)
|
||||
*
|
||||
* The processing function should return a disposition_t, normally D_OK. A
|
||||
* function can return D_DROP to remove an item from the stream entirely. It
|
||||
* can also return D_EOF to indicate that no more data will be forthcoming.
|
||||
* However, only the first step in the chain should ever return D_EOF.
|
||||
* The processing function for a serial step should return a disposition_t,
|
||||
* normally D_OK. A processing function can return D_DROP to remove an item
|
||||
* from the stream entirely. It can also return D_EOF to indicate that no
|
||||
* more data will be forthcoming. However, only the first step in the chain
|
||||
* should ever return D_EOF.
|
||||
*
|
||||
* Functions are called with a NULL packet pointer when the end of the
|
||||
* stream passes by them.
|
||||
* Serial functions are called with a NULL packet when the end of the
|
||||
* stream passes by them. Since parallel functions may see packets in any
|
||||
* order, they have no concept of "end of stream" and do not receive this
|
||||
* notification.
|
||||
*
|
||||
* **CONTEXT** - An arbitrary void * that the chain passes along to the
|
||||
* processing function as an argument.
|
||||
*
|
||||
* **CHAIN ATTRIBUTES** - A global set of flags available to all steps.
|
||||
* **CHAIN ATTRIBUTES** - A global set of flags available to all steps. The
|
||||
* chain is also responsible for tracking general statistics such as the
|
||||
* number of records of each type that have been processed.
|
||||
*/
|
||||
|
||||
#define CA_BYTESWAPPED (1ULL << 0) /* ca_attrs */
|
||||
@@ -94,17 +100,19 @@ extern "C" {
|
||||
#define CA_LITTLE_ENDIAN_INPUT (1ULL << 2)
|
||||
|
||||
#define CA_VERBOSE (1ULL << 0) /* ca_command_opts */
|
||||
#define CA_VERY_VERBOSE (1ULL << 1)
|
||||
#define CA_DUMP_DATA (1ULL << 2)
|
||||
#define CA_IGNORE_CKSUMS (1ULL << 3)
|
||||
#define CA_DO_NOT_VALIDATE (1ULL << 4)
|
||||
#define CA_FORBID_DEDUP (1ULL << 5)
|
||||
#define CA_REQUIRE_DEDUP (1ULL << 6)
|
||||
#define CA_REQUIRE_NATIVE_ENDIAN (1ULL << 7)
|
||||
#define CA_BYTESWAP_ON_OUTPUT (1ULL << 8)
|
||||
#define CA_BIG_ENDIAN_OUT (1ULL << 9)
|
||||
#define CA_LITTLE_ENDIAN_OUT (1ULL << 10)
|
||||
#define CA_OPPOSITE_ENDIAN_OUT (1ULL << 11)
|
||||
#define CA_DUMP_BEGIN_AND_END (1ULL << 1)
|
||||
#define CA_DUMP_ALL_RECORDS (1ULL << 2)
|
||||
#define CA_DUMP_CHECKSUMS (1ULL << 3)
|
||||
#define CA_DUMP_DATA (1ULL << 4)
|
||||
#define CA_IGNORE_CKSUMS (1ULL << 5)
|
||||
#define CA_DO_NOT_VALIDATE (1ULL << 6)
|
||||
#define CA_FORBID_DEDUP (1ULL << 7)
|
||||
#define CA_REQUIRE_DEDUP (1ULL << 8)
|
||||
#define CA_REQUIRE_NATIVE_ENDIAN (1ULL << 9)
|
||||
#define CA_BYTESWAP_ON_OUTPUT (1ULL << 10)
|
||||
#define CA_BIG_ENDIAN_OUT (1ULL << 11)
|
||||
#define CA_LITTLE_ENDIAN_OUT (1ULL << 12)
|
||||
#define CA_OPPOSITE_ENDIAN_OUT (1ULL << 13)
|
||||
|
||||
#define OPTION_ENABLED(option) (!!(chain_attrs->ca_command_opts & (option)))
|
||||
#define STREAM_HAS_FEATURE(feat) (!!(chain_attrs->ca_feature_flags & (feat)))
|
||||
@@ -133,7 +141,7 @@ typedef struct {
|
||||
record_stats_t ca_stats_out[DRR_NUMTYPES];
|
||||
} chain_attrs_t;
|
||||
|
||||
typedef enum { CS_SERIAL, CS_TERMINATE } step_type_t;
|
||||
typedef enum { CS_SERIAL, CS_PARALLEL, CS_TERMINATE } step_type_t;
|
||||
typedef enum { D_OK, D_EOF, D_DROP } disposition_t;
|
||||
|
||||
typedef disposition_t
|
||||
@@ -145,31 +153,47 @@ typedef struct chain_step
|
||||
size_t cs_in_size;
|
||||
size_t cs_out_size;
|
||||
void *cs_context;
|
||||
struct {
|
||||
zc_serial_process_f *process;
|
||||
} cs_serial;
|
||||
union {
|
||||
struct {
|
||||
zc_serial_process_f *process; /* serial */
|
||||
} cs_serial;
|
||||
struct {
|
||||
size_t queue_length; /* parallel */
|
||||
size_t batch_budget;
|
||||
zq_estimate_cost_f *cost;
|
||||
zq_process_item_f *process;
|
||||
} cs_parallel;
|
||||
};
|
||||
} chain_step_t;
|
||||
|
||||
typedef chain_step_t zstream_chain_t[];
|
||||
|
||||
/*
|
||||
* Chain attributes accessible to any step on the chain. In theory this
|
||||
* could cause a race condition between reading and setting, but all
|
||||
* attributes are typically set by the time the first record has been read.
|
||||
* Ergo, nobody else will be executing while that first chain_read() runs.
|
||||
* Chain attributes accessible to any step on the chain. These are normally
|
||||
* accessed through the macros defined above.
|
||||
*/
|
||||
extern chain_attrs_t *chain_attrs;
|
||||
|
||||
/*
|
||||
* Execute a chain. Returns once execution is complete. You can pass NULL
|
||||
* for the attrs if you're not interested in preserving them after the chain
|
||||
* has run.
|
||||
* has run. (The chain will allocate and dispose of a buffer for them.)
|
||||
*/
|
||||
void
|
||||
zstream_chain_exec(zstream_chain_t chain, chain_attrs_t *attrs);
|
||||
|
||||
chain_step_t
|
||||
serial_null_step(void);
|
||||
/*
|
||||
* Execute a chain linearly, without queues and without multithreading. This
|
||||
* form of execution is intended as a debugging aid, both for clients and
|
||||
* for the chain mechanism itself. If this variant doesn't produce results
|
||||
* identical to zstream_chain_exec(), there's a multithreading-related bug
|
||||
* somewhere.
|
||||
*
|
||||
* It is not necessary to remove parallel steps from the input chain. They
|
||||
* are accepted as-is, but their execution won't be parallelized.
|
||||
*/
|
||||
void
|
||||
zstream_chain_exec_serialized(zstream_chain_t chain, chain_attrs_t *attrs);
|
||||
|
||||
chain_step_t
|
||||
chain_terminator(void);
|
||||
|
||||
@@ -1,23 +1,13 @@
|
||||
// SPDX-License-Identifier: CDDL-1.0
|
||||
/*
|
||||
* CDDL HEADER START
|
||||
* This file and its contents are supplied under the terms of the
|
||||
* Common Development and Distribution License ("CDDL"), version 1.0.
|
||||
* You may only use this file in accordance with the terms of version
|
||||
* 1.0 of the CDDL.
|
||||
*
|
||||
* The contents of this file are subject to the terms of the
|
||||
* Common Development and Distribution License (the "License").
|
||||
* You may not use this file except in compliance with the License.
|
||||
*
|
||||
* You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
|
||||
* or https://opensource.org/licenses/CDDL-1.0.
|
||||
* See the License for the specific language governing permissions
|
||||
* and limitations under the License.
|
||||
*
|
||||
* When distributing Covered Code, include this CDDL HEADER in each
|
||||
* file and include the License file at usr/src/OPENSOLARIS.LICENSE.
|
||||
* If applicable, add the following below this CDDL HEADER, with the
|
||||
* fields enclosed by brackets "[]" replaced with your own identifying
|
||||
* information: Portions Copyright [yyyy] [name of copyright owner]
|
||||
*
|
||||
* CDDL HEADER END
|
||||
* A full copy of the text of the CDDL should have accompanied this
|
||||
* source. A copy of the CDDL is also available via the Internet at
|
||||
* https://opensource.org/license/CDDL-1.0.
|
||||
*/
|
||||
|
||||
/*
|
||||
@@ -47,9 +37,11 @@
|
||||
#define KEYSIZE 64
|
||||
|
||||
static disposition_t
|
||||
chain_decompress_named_writes(drr_packet_t *item, void *context)
|
||||
chain_decompress_named_writes(void *item_in, void *context)
|
||||
{
|
||||
(void) context;
|
||||
drr_packet_t *item = (drr_packet_t *)item_in;
|
||||
|
||||
if (item == NULL) {
|
||||
return (D_OK);
|
||||
}
|
||||
@@ -134,8 +126,7 @@ serial_decompress_named_writes(void)
|
||||
.cs_out_size = sizeof (drr_packet_t),
|
||||
.cs_context = NULL,
|
||||
.cs_serial = {
|
||||
.process =
|
||||
(zc_serial_process_f *)chain_decompress_named_writes
|
||||
.process = chain_decompress_named_writes
|
||||
}
|
||||
};
|
||||
return (step);
|
||||
@@ -155,7 +146,6 @@ zstream_do_decompress(int argc, char *argv[])
|
||||
case '?':
|
||||
fprintf(stderr, "invalid option '%c'\n", optopt);
|
||||
zstream_usage();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -176,10 +166,8 @@ zstream_do_decompress(int argc, char *argv[])
|
||||
enum zio_compress type = ZIO_COMPRESS_INHERIT;
|
||||
|
||||
obj_str = strsep(&argv[i], ",");
|
||||
if (argv[i] == NULL) {
|
||||
if (argv[i] == NULL)
|
||||
zstream_usage();
|
||||
exit(2);
|
||||
}
|
||||
errno = 0;
|
||||
object = strtoull(obj_str, &end, 0);
|
||||
if (errno || *end != '\0')
|
||||
|
||||
@@ -1,23 +1,13 @@
|
||||
// SPDX-License-Identifier: CDDL-1.0
|
||||
/*
|
||||
* CDDL HEADER START
|
||||
* This file and its contents are supplied under the terms of the
|
||||
* Common Development and Distribution License ("CDDL"), version 1.0.
|
||||
* You may only use this file in accordance with the terms of version
|
||||
* 1.0 of the CDDL.
|
||||
*
|
||||
* The contents of this file are subject to the terms of the
|
||||
* Common Development and Distribution License (the "License").
|
||||
* You may not use this file except in compliance with the License.
|
||||
*
|
||||
* You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
|
||||
* or https://opensource.org/licenses/CDDL-1.0.
|
||||
* See the License for the specific language governing permissions
|
||||
* and limitations under the License.
|
||||
*
|
||||
* When distributing Covered Code, include this CDDL HEADER in each
|
||||
* file and include the License file at usr/src/OPENSOLARIS.LICENSE.
|
||||
* If applicable, add the following below this CDDL HEADER, with the
|
||||
* fields enclosed by brackets "[]" replaced with your own identifying
|
||||
* information: Portions Copyright [yyyy] [name of copyright owner]
|
||||
*
|
||||
* CDDL HEADER END
|
||||
* A full copy of the text of the CDDL should have accompanied this
|
||||
* source. A copy of the CDDL is also available via the Internet at
|
||||
* https://opensource.org/license/CDDL-1.0.
|
||||
*/
|
||||
|
||||
/*
|
||||
@@ -42,9 +32,11 @@
|
||||
#define KEYSIZE 64
|
||||
|
||||
static disposition_t
|
||||
chain_drop_records(drr_packet_t *item, void *context)
|
||||
chain_drop_records(void *item_in, void *context)
|
||||
{
|
||||
(void) context;
|
||||
drr_packet_t *item = (drr_packet_t *)item_in;
|
||||
|
||||
if (item == NULL)
|
||||
return (D_OK);
|
||||
|
||||
@@ -105,7 +97,7 @@ serial_drop_records(void)
|
||||
.cs_out_size = sizeof (drr_packet_t),
|
||||
.cs_context = NULL,
|
||||
.cs_serial = {
|
||||
.process = (zc_serial_process_f *)chain_drop_records
|
||||
.process = chain_drop_records
|
||||
}
|
||||
};
|
||||
return (step);
|
||||
@@ -125,7 +117,6 @@ zstream_do_drop_record(int argc, char *argv[])
|
||||
case '?':
|
||||
warnx("invalid option '%c'\n", optopt);
|
||||
zstream_usage();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -146,10 +137,8 @@ zstream_do_drop_record(int argc, char *argv[])
|
||||
char *end;
|
||||
|
||||
obj_str = strsep(&argv[i], ",");
|
||||
if (argv[i] == NULL) {
|
||||
if (argv[i] == NULL)
|
||||
zstream_usage();
|
||||
exit(2);
|
||||
}
|
||||
errno = 0;
|
||||
object = strtoull(obj_str, &end, 0);
|
||||
if (errno || *end != '\0')
|
||||
|
||||
@@ -1,23 +1,13 @@
|
||||
// SPDX-License-Identifier: CDDL-1.0
|
||||
/*
|
||||
* CDDL HEADER START
|
||||
* This file and its contents are supplied under the terms of the
|
||||
* Common Development and Distribution License ("CDDL"), version 1.0.
|
||||
* You may only use this file in accordance with the terms of version
|
||||
* 1.0 of the CDDL.
|
||||
*
|
||||
* The contents of this file are subject to the terms of the
|
||||
* Common Development and Distribution License (the "License").
|
||||
* You may not use this file except in compliance with the License.
|
||||
*
|
||||
* You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
|
||||
* or https://opensource.org/licenses/CDDL-1.0.
|
||||
* See the License for the specific language governing permissions
|
||||
* and limitations under the License.
|
||||
*
|
||||
* When distributing Covered Code, include this CDDL HEADER in each
|
||||
* file and include the License file at usr/src/OPENSOLARIS.LICENSE.
|
||||
* If applicable, add the following below this CDDL HEADER, with the
|
||||
* fields enclosed by brackets "[]" replaced with your own identifying
|
||||
* information: Portions Copyright [yyyy] [name of copyright owner]
|
||||
*
|
||||
* CDDL HEADER END
|
||||
* A full copy of the text of the CDDL should have accompanied this
|
||||
* source. A copy of the CDDL is also available via the Internet at
|
||||
* https://opensource.org/license/CDDL-1.0.
|
||||
*/
|
||||
|
||||
/*
|
||||
@@ -67,7 +57,7 @@ typedef void dumper_f(drr_packet_t *item);
|
||||
typedef struct {
|
||||
const char *rt_typename;
|
||||
dumper_f *rt_dumper;
|
||||
} record_type_t;
|
||||
} record_dumper_t;
|
||||
|
||||
static int stream_error;
|
||||
|
||||
@@ -183,6 +173,10 @@ dump_begin_record(drr_packet_t *item)
|
||||
dmu_replay_record_t *drr = &item->dp_drr;
|
||||
struct drr_begin *drrb = &item->dp_drr.drr_u.drr_begin;
|
||||
|
||||
if (!OPTION_ENABLED(CA_DUMP_BEGIN_AND_END) &&
|
||||
!OPTION_ENABLED(CA_DUMP_ALL_RECORDS))
|
||||
return;
|
||||
|
||||
printf("BEGIN record\n");
|
||||
printf("\thdrtype = %llu\n",
|
||||
DMU_GET_STREAM_HDRTYPE(drrb->drr_versioninfo));
|
||||
@@ -240,6 +234,10 @@ dump_end_record(drr_packet_t *item)
|
||||
{
|
||||
struct drr_end *drre = &item->dp_drr.drr_u.drr_end;
|
||||
|
||||
if (!OPTION_ENABLED(CA_DUMP_BEGIN_AND_END) &&
|
||||
!OPTION_ENABLED(CA_DUMP_ALL_RECORDS))
|
||||
return;
|
||||
|
||||
printf("END checksum = %llx/%llx/%llx/%llx\n",
|
||||
(u_longlong_t)drre->drr_checksum.zc_word[0],
|
||||
(u_longlong_t)drre->drr_checksum.zc_word[1],
|
||||
@@ -252,7 +250,7 @@ dump_object_record(drr_packet_t *item)
|
||||
{
|
||||
struct drr_object *drro = &item->dp_drr.drr_u.drr_object;
|
||||
|
||||
if (OPTION_ENABLED(CA_VERBOSE)) {
|
||||
if (OPTION_ENABLED(CA_DUMP_ALL_RECORDS)) {
|
||||
printf("OBJECT object = %llu type = %u "
|
||||
"bonustype = %u blksz = %u bonuslen = %u "
|
||||
"dn_slots = %u raw_bonuslen = %u "
|
||||
@@ -282,7 +280,7 @@ dump_freeobjects_record(drr_packet_t *item)
|
||||
{
|
||||
struct drr_freeobjects *drrfo = &item->dp_drr.drr_u.drr_freeobjects;
|
||||
|
||||
if (OPTION_ENABLED(CA_VERBOSE)) {
|
||||
if (OPTION_ENABLED(CA_DUMP_ALL_RECORDS)) {
|
||||
printf("FREEOBJECTS firstobj = %llu numobjs = %llu\n",
|
||||
(u_longlong_t)drrfo->drr_firstobj,
|
||||
(u_longlong_t)drrfo->drr_numobjs);
|
||||
@@ -294,7 +292,7 @@ dump_write_record(drr_packet_t *item)
|
||||
{
|
||||
struct drr_write *drrw = &item->dp_drr.drr_u.drr_write;
|
||||
|
||||
if (OPTION_ENABLED(CA_VERBOSE)) {
|
||||
if (OPTION_ENABLED(CA_DUMP_ALL_RECORDS)) {
|
||||
printf("WRITE object = %llu type = %u "
|
||||
"checksum type = %u compression type = %u "
|
||||
"flags = %u offset = %llu "
|
||||
@@ -322,7 +320,7 @@ dump_write_byref_record(drr_packet_t *item)
|
||||
{
|
||||
struct drr_write_byref *drrwbr = &item->dp_drr.drr_u.drr_write_byref;
|
||||
|
||||
if (OPTION_ENABLED(CA_VERBOSE)) {
|
||||
if (OPTION_ENABLED(CA_DUMP_ALL_RECORDS)) {
|
||||
printf("WRITE_BYREF object = %llu "
|
||||
"checksum type = %u props = %llx "
|
||||
"offset = %llu length = %llu "
|
||||
@@ -345,7 +343,7 @@ dump_free_record(drr_packet_t *item)
|
||||
{
|
||||
struct drr_free *drrf = &item->dp_drr.drr_u.drr_free;
|
||||
|
||||
if (OPTION_ENABLED(CA_VERBOSE)) {
|
||||
if (OPTION_ENABLED(CA_DUMP_ALL_RECORDS)) {
|
||||
printf("FREE object = %llu "
|
||||
"offset = %llu length = %lld\n",
|
||||
(u_longlong_t)drrf->drr_object,
|
||||
@@ -359,7 +357,7 @@ dump_spill_record(drr_packet_t *item)
|
||||
{
|
||||
struct drr_spill *drrs = &item->dp_drr.drr_u.drr_spill;
|
||||
|
||||
if (OPTION_ENABLED(CA_VERBOSE)) {
|
||||
if (OPTION_ENABLED(CA_DUMP_ALL_RECORDS)) {
|
||||
printf("SPILL block for object = %llu "
|
||||
"length = %llu flags = %u "
|
||||
"compression type = %u "
|
||||
@@ -383,7 +381,7 @@ dump_write_embedded_record(drr_packet_t *item)
|
||||
struct drr_write_embedded *drrwe =
|
||||
&item->dp_drr.drr_u.drr_write_embedded;
|
||||
|
||||
if (OPTION_ENABLED(CA_VERBOSE)) {
|
||||
if (OPTION_ENABLED(CA_DUMP_ALL_RECORDS)) {
|
||||
printf("WRITE_EMBEDDED object = %llu "
|
||||
"offset = %llu length = %llu "
|
||||
"toguid = %llx comp = %u etype = %u "
|
||||
@@ -405,7 +403,7 @@ dump_object_range_record(drr_packet_t *item)
|
||||
{
|
||||
struct drr_object_range *drror = &item->dp_drr.drr_u.drr_object_range;
|
||||
|
||||
if (OPTION_ENABLED(CA_VERBOSE)) {
|
||||
if (OPTION_ENABLED(CA_DUMP_ALL_RECORDS)) {
|
||||
printf("OBJECT_RANGE firstobj = %llu "
|
||||
"numslots = %llu flags = %u "
|
||||
"%s\n",
|
||||
@@ -421,7 +419,7 @@ dump_redact_record(drr_packet_t *item)
|
||||
{
|
||||
struct drr_redact *drrr = &item->dp_drr.drr_u.drr_redact;
|
||||
|
||||
if (OPTION_ENABLED(CA_VERBOSE)) {
|
||||
if (OPTION_ENABLED(CA_DUMP_ALL_RECORDS)) {
|
||||
printf("REDACT object = %llu offset = "
|
||||
"%llu length = %llu\n",
|
||||
(u_longlong_t)drrr->drr_object,
|
||||
@@ -430,9 +428,26 @@ dump_redact_record(drr_packet_t *item)
|
||||
}
|
||||
}
|
||||
|
||||
static const record_dumper_t record_dumpers[] = {
|
||||
{ "DRR_BEGIN", dump_begin_record },
|
||||
{ "DRR_OBJECT", dump_object_record },
|
||||
{ "DRR_FREEOBJECTS", dump_freeobjects_record },
|
||||
{ "DRR_WRITE", dump_write_record },
|
||||
{ "DRR_FREE", dump_free_record },
|
||||
{ "DRR_END", dump_end_record },
|
||||
{ "DRR_WRITE_BYREF", dump_write_byref_record },
|
||||
{ "DRR_SPILL", dump_spill_record },
|
||||
{ "DRR_WRITE_EMBEDDED", dump_write_embedded_record },
|
||||
{ "DRR_OBJECT_RANGE", dump_object_range_record },
|
||||
{ "DRR_REDACT", dump_redact_record }
|
||||
};
|
||||
|
||||
static disposition_t
|
||||
chain_dump_record(drr_packet_t *item, record_type_t *context)
|
||||
chain_dump_records(void *item_in, void *context)
|
||||
{
|
||||
(void) context;
|
||||
drr_packet_t *item = (drr_packet_t *)item_in;
|
||||
|
||||
if (item == NULL) {
|
||||
return (D_OK);
|
||||
}
|
||||
@@ -441,9 +456,12 @@ chain_dump_record(drr_packet_t *item, record_type_t *context)
|
||||
zio_cksum_t *cksum = &drr->drr_u.drr_checksum.drr_checksum;
|
||||
int type = (int)drr->drr_type;
|
||||
|
||||
context[type].rt_dumper(item);
|
||||
if (type < 0 || type >= DRR_NUMTYPES)
|
||||
errx(1, "unknown record type: %d", type);
|
||||
|
||||
if (type != DRR_BEGIN && OPTION_ENABLED(CA_VERY_VERBOSE)) {
|
||||
record_dumpers[type].rt_dumper(item);
|
||||
|
||||
if (type != DRR_BEGIN && OPTION_ENABLED(CA_DUMP_CHECKSUMS)) {
|
||||
printf(" checksum = %llx/%llx/%llx/%llx\n",
|
||||
(u_longlong_t)cksum->zc_word[0],
|
||||
(u_longlong_t)cksum->zc_word[1],
|
||||
@@ -454,16 +472,23 @@ chain_dump_record(drr_packet_t *item, record_type_t *context)
|
||||
return (D_OK);
|
||||
}
|
||||
|
||||
static chain_step_t
|
||||
serial_dump_records(record_type_t *context)
|
||||
chain_step_t
|
||||
serial_dump_records(void)
|
||||
{
|
||||
size_t all_dumpers = sizeof (record_dumpers);
|
||||
size_t one_dumper = sizeof (record_dumpers[0]);
|
||||
|
||||
if (all_dumpers / one_dumper != DRR_NUMTYPES) {
|
||||
errx(1, "number of record_dumpers must match DRR_NUMTYPES");
|
||||
}
|
||||
|
||||
chain_step_t step = {
|
||||
.cs_type = CS_SERIAL,
|
||||
.cs_in_size = sizeof (drr_packet_t),
|
||||
.cs_out_size = sizeof (drr_packet_t),
|
||||
.cs_context = context,
|
||||
.cs_context = NULL,
|
||||
.cs_serial = {
|
||||
.process = (zc_serial_process_f *)chain_dump_record
|
||||
.process = chain_dump_records
|
||||
}
|
||||
};
|
||||
return (step);
|
||||
@@ -476,19 +501,7 @@ zstream_do_dump(int argc, char *argv[])
|
||||
const char *input_file = NULL;
|
||||
int c;
|
||||
|
||||
record_type_t record_types[] = {
|
||||
{ "DRR_BEGIN", dump_begin_record },
|
||||
{ "DRR_OBJECT", dump_object_record },
|
||||
{ "DRR_FREEOBJECTS", dump_freeobjects_record },
|
||||
{ "DRR_WRITE", dump_write_record },
|
||||
{ "DRR_FREE", dump_free_record },
|
||||
{ "DRR_END", dump_end_record },
|
||||
{ "DRR_WRITE_BYREF", dump_write_byref_record },
|
||||
{ "DRR_SPILL", dump_spill_record },
|
||||
{ "DRR_WRITE_EMBEDDED", dump_write_embedded_record },
|
||||
{ "DRR_OBJECT_RANGE", dump_object_range_record },
|
||||
{ "DRR_REDACT", dump_redact_record }
|
||||
};
|
||||
ENABLE_OPTION(&attrs, CA_DUMP_BEGIN_AND_END);
|
||||
|
||||
while ((c = getopt(argc, argv, ":vCd")) != -1) {
|
||||
switch (c) {
|
||||
@@ -497,24 +510,24 @@ zstream_do_dump(int argc, char *argv[])
|
||||
break;
|
||||
case 'v':
|
||||
if (attrs.ca_command_opts & CA_VERBOSE) {
|
||||
ENABLE_OPTION(&attrs, CA_VERY_VERBOSE);
|
||||
ENABLE_OPTION(&attrs, CA_DUMP_CHECKSUMS);
|
||||
} else {
|
||||
ENABLE_OPTION(&attrs, CA_VERBOSE);
|
||||
ENABLE_OPTION(&attrs, CA_DUMP_ALL_RECORDS);
|
||||
}
|
||||
break;
|
||||
case 'd':
|
||||
ENABLE_OPTION(&attrs, CA_VERBOSE);
|
||||
ENABLE_OPTION(&attrs, CA_VERY_VERBOSE);
|
||||
ENABLE_OPTION(&attrs, CA_DUMP_ALL_RECORDS);
|
||||
ENABLE_OPTION(&attrs, CA_DUMP_CHECKSUMS);
|
||||
ENABLE_OPTION(&attrs, CA_DUMP_DATA);
|
||||
break;
|
||||
case ':':
|
||||
warnx("missing argument for '%c' option\n", optopt);
|
||||
zstream_usage();
|
||||
break;
|
||||
case '?':
|
||||
warnx("invalid option '%c'\n", optopt);
|
||||
zstream_usage();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -524,7 +537,7 @@ zstream_do_dump(int argc, char *argv[])
|
||||
|
||||
zstream_chain_t dump_chain = {
|
||||
STANDARD_INPUT_STACK(input_file),
|
||||
serial_dump_records(record_types),
|
||||
serial_dump_records(),
|
||||
NULL_OUTPUT_STACK()
|
||||
};
|
||||
|
||||
@@ -543,7 +556,7 @@ zstream_do_dump(int argc, char *argv[])
|
||||
printf("SUMMARY:\n");
|
||||
for (int i = 0; i < DRR_NUMTYPES; i++) {
|
||||
int type = print_order[i];
|
||||
record_type_t *rec = &record_types[type];
|
||||
const record_dumper_t *rec = &record_dumpers[type];
|
||||
record_stats_t *stats = &attrs.ca_stats_in[type];
|
||||
printf("\tTotal %s records = %llu (%llu bytes)\n",
|
||||
rec->rt_typename,
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
// SPDX-License-Identifier: CDDL-1.0
|
||||
/*
|
||||
* This file and its contents are supplied under the terms of the
|
||||
* Common Development and Distribution License ("CDDL"), version 1.0.
|
||||
* You may only use this file in accordance with the terms of version
|
||||
* 1.0 of the CDDL.
|
||||
*
|
||||
* A full copy of the text of the CDDL should have accompanied this
|
||||
* source. A copy of the CDDL is also available via the Internet at
|
||||
* https://opensource.org/license/CDDL-1.0.
|
||||
*/
|
||||
|
||||
/*
|
||||
* Copyright (c) 2026 by Garth Snyder. All rights reserved.
|
||||
*/
|
||||
|
||||
#ifndef _ZSTREAM_DUMP_H
|
||||
#define _ZSTREAM_DUMP_H
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#include "zstream_chain.h"
|
||||
|
||||
chain_step_t
|
||||
serial_dump_records(void);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* _ZSTREAM_DUMP_H */
|
||||
@@ -1,16 +1,13 @@
|
||||
// SPDX-License-Identifier: CDDL-1.0
|
||||
/*
|
||||
* CDDL HEADER START
|
||||
* This file and its contents are supplied under the terms of the
|
||||
* Common Development and Distribution License ("CDDL"), version 1.0.
|
||||
* You may only use this file in accordance with the terms of version
|
||||
* 1.0 of the CDDL.
|
||||
*
|
||||
* This file and its contents are supplied under the terms of the Common
|
||||
* Development and Distribution License ("CDDL"), version 1.0. You may only use
|
||||
* this file in accordance with the terms of version 1.0 of the CDDL.
|
||||
*
|
||||
* A full copy of the text of the CDDL should have accompanied this source. A
|
||||
* copy of the CDDL is also available via the Internet at
|
||||
* http://www.illumos.org/license/CDDL.
|
||||
*
|
||||
* CDDL HEADER END
|
||||
* A full copy of the text of the CDDL should have accompanied this
|
||||
* source. A copy of the CDDL is also available via the Internet at
|
||||
* https://opensource.org/license/CDDL-1.0.
|
||||
*/
|
||||
|
||||
/*
|
||||
@@ -20,22 +17,36 @@
|
||||
#include <assert.h>
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
#include <sys/byteorder.h>
|
||||
#include <sys/param.h>
|
||||
#include <sys/spa_checksum.h>
|
||||
#include <sys/stdtypes.h>
|
||||
#include <sys/sysmacros.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/zfs_ioctl.h>
|
||||
#include <zfs_fletcher.h>
|
||||
|
||||
#include "zstream_fletcher4.h"
|
||||
#include "zstream_modules.h"
|
||||
#include "zstream_queue.h"
|
||||
#include "zstream_util.h"
|
||||
|
||||
#define CK_OFFSET offsetof(dmu_replay_record_t, drr_u.drr_checksum.drr_checksum)
|
||||
#define END_CK_OFFSET offsetof(dmu_replay_record_t, drr_u.drr_end.drr_checksum)
|
||||
|
||||
/*
|
||||
* Copied from zfs_fletcher.c. See comments below regarding the
|
||||
* fletcher_4_incremental_combine() function.
|
||||
*/
|
||||
#define MAX_FLETCHER_BLOCK (8ULL << 20)
|
||||
|
||||
typedef enum { F4_SET, F4_VALIDATE } fletcher4_op_t;
|
||||
|
||||
typedef zio_cksum_t fletcher4_context_t;
|
||||
typedef struct {
|
||||
zio_cksum_t fc_stream_cksum;
|
||||
fletcher4_op_t fc_operation;
|
||||
} fletcher4_context_t;
|
||||
|
||||
static fletcher4_context_t fletcher4_contexts[MAX_FLETCHER_4];
|
||||
static int next_context = 0;
|
||||
@@ -50,9 +61,130 @@ fletcher_4_incremental(boolean_t swap, void *buff, size_t size, void *cksum)
|
||||
}
|
||||
}
|
||||
|
||||
static inline void
|
||||
fletcher_4(boolean_t swap, void *buff, size_t size, void *cksum)
|
||||
{
|
||||
if (swap) {
|
||||
fletcher_4_byteswap(buff, size, NULL, cksum);
|
||||
} else {
|
||||
fletcher_4_native(buff, size, NULL, cksum);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Emit or validate (below) a replay record with proper checksums and with
|
||||
* proper maintenance of the stream checksum. That is:
|
||||
* The function below and the MAX_FLETCHER_BLOCK define are copied from
|
||||
* zfs_fletcher.c, where they're internal.
|
||||
*
|
||||
* Fletcher checksums CAN be computed in parallel, with the segments later
|
||||
* being reassembled. However, the combine function needs to know the
|
||||
* original length of each segment, and there's a hard limit as to how long
|
||||
* any given segment can be because 64-bit coefficients used in the combine
|
||||
* operation may overflow if the size is larger than 8MB.
|
||||
*
|
||||
* My understanding of this is that the checksum fields themselves can and
|
||||
* will overflow for long hash texts. However, they still function properly
|
||||
* as checksums when this happens. But overflow has to be handled correctly
|
||||
* in a structured fashion, not by allowing intermediate calculations to
|
||||
* overflow.
|
||||
*/
|
||||
static inline void
|
||||
fletcher4_incremental_combine(zio_cksum_t *zcp, const uint64_t size,
|
||||
const zio_cksum_t *nzcp)
|
||||
{
|
||||
const uint64_t c1 = size / sizeof (uint32_t);
|
||||
const uint64_t c2 = c1 * (c1 + 1) / 2;
|
||||
const uint64_t c3 = c2 * (c1 + 2) / 3;
|
||||
|
||||
/*
|
||||
* Value of 'c3' overflows on buffer sizes close to 16MiB. For that
|
||||
* reason we split incremental fletcher4 computation of large buffers
|
||||
* to steps of (MAX_FLETCHER_BLOCK) size.
|
||||
*/
|
||||
ASSERT3U(size, <=, MAX_FLETCHER_BLOCK);
|
||||
|
||||
zcp->zc_word[3] += nzcp->zc_word[3] + c1 * zcp->zc_word[2] +
|
||||
c2 * zcp->zc_word[1] + c3 * zcp->zc_word[0];
|
||||
zcp->zc_word[2] += nzcp->zc_word[2] + c1 * zcp->zc_word[1] +
|
||||
c2 * zcp->zc_word[0];
|
||||
zcp->zc_word[1] += nzcp->zc_word[1] + c1 * zcp->zc_word[0];
|
||||
zcp->zc_word[0] += nzcp->zc_word[0];
|
||||
}
|
||||
|
||||
/*
|
||||
* This is the parallel portion of checksum calculation. We calculate only
|
||||
* the checksum blocks for payloads. The records themselves are summed in
|
||||
* the serial step.
|
||||
*
|
||||
* Because MAX_FLETCHER_BLOCK is 8MB, the great majority of payloads need
|
||||
* only a single checksum calculation. The drr_fletcher4_t struct has both a
|
||||
* first-block checksum field and a pointer to an overflow block on the
|
||||
* heap. The overflow block is not allocated unless the payload size is
|
||||
* greater than MAX_FLETCHER_BLOCK.
|
||||
*/
|
||||
static void
|
||||
chain_calc_fletcher4(queue_item_t *item_in, void *context)
|
||||
{
|
||||
(void) context;
|
||||
drr_fletcher4_t *item = (drr_fletcher4_t *)item_in;
|
||||
|
||||
VERIFY3U(item->dp_base.dp_payload_size, >, 0);
|
||||
|
||||
ssize_t remaining = item->dp_base.dp_payload_size;
|
||||
uint8_t *data = item->dp_base.dp_payload;
|
||||
size_t write_size = MIN(remaining, MAX_FLETCHER_BLOCK);
|
||||
int num_overflow = DIV_ROUND_UP(remaining, MAX_FLETCHER_BLOCK) - 1;
|
||||
zio_cksum_t *fragment = &item->dp_fletcher4_payload;
|
||||
boolean_t swap = ATTR_IS_SET(CA_BYTESWAPPED);
|
||||
|
||||
fletcher_4(swap, data, write_size, fragment);
|
||||
if (num_overflow) {
|
||||
fragment = safe_calloc(num_overflow * sizeof (zio_cksum_t));
|
||||
item->dp_fletcher4_overflow = fragment;
|
||||
} else {
|
||||
item->dp_fletcher4_overflow = NULL;
|
||||
}
|
||||
while (remaining -= write_size) {
|
||||
data += write_size;
|
||||
write_size = MIN(remaining, MAX_FLETCHER_BLOCK);
|
||||
fletcher_4(swap, data, write_size, fragment);
|
||||
fragment++;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Combine the payload checksum(s) produced by the parallel phase into the
|
||||
* stream checksum. Used by the serial validation or inscription step.
|
||||
*/
|
||||
static void
|
||||
assemble_payload_cksum(drr_fletcher4_t *item, zio_cksum_t *stream_ck)
|
||||
{
|
||||
ssize_t remaining = item->dp_base.dp_payload_size;
|
||||
size_t read_size = MIN(remaining, MAX_FLETCHER_BLOCK);
|
||||
zio_cksum_t *fragment = item->dp_fletcher4_overflow;
|
||||
|
||||
if (remaining == 0)
|
||||
return;
|
||||
fletcher4_incremental_combine(stream_ck, read_size,
|
||||
&item->dp_fletcher4_payload);
|
||||
while (remaining -= read_size) {
|
||||
read_size = MIN(remaining, MAX_FLETCHER_BLOCK);
|
||||
fletcher4_incremental_combine(stream_ck, read_size, fragment);
|
||||
fragment++;
|
||||
}
|
||||
if (item->dp_fletcher4_overflow != NULL) {
|
||||
free(item->dp_fletcher4_overflow);
|
||||
item->dp_fletcher4_overflow = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* This function implements the serial portions of both validation and
|
||||
* inscription, based on the fc_operation field of the context struct.
|
||||
* Checksumming is either very early in a chain or very late, so records
|
||||
* are potentially in non-native endianness in either mode.
|
||||
*
|
||||
* This function emits or validates a replay record with proper checksums
|
||||
* and with proper maintenance of the stream checksum. That is:
|
||||
*
|
||||
* 1) Update stream checksum with the record header up to drr_checksum.
|
||||
* 2) Update drr_checksum field in the record header from stream checksum.
|
||||
@@ -62,33 +194,74 @@ fletcher_4_incremental(boolean_t swap, void *buff, size_t size, void *cksum)
|
||||
* DRR_BEGIN records do not have record checksums. They can't, because the
|
||||
* drr_begin struct overlaps with space that would otherwise be used for the
|
||||
* end-record checksum.
|
||||
*
|
||||
* DRR_END records normally do have end-record checksums. However, records
|
||||
* emitted by send_conclusion_record() in libzfs_sendrecv.c have the
|
||||
* checksum set to zero. zfs receive ignores those checksums. DRR_END records
|
||||
* also have an internal checksum that applies to the stream-to-date since the
|
||||
* most recent DRR_BEGIN.
|
||||
*
|
||||
* Null zstream transformations should be idempotent. E.g., a zstream redup
|
||||
* that does not redup anything should yield a stream that is bit-for-bit
|
||||
* identical to the original stream. So, it's helpful to emulate zfs send's
|
||||
* checksumming pattern just to minimize spurious differences between
|
||||
* input and output streams.
|
||||
*/
|
||||
static disposition_t
|
||||
chain_add_fletcher4(drr_packet_t *item, zio_cksum_t *stream_cksum)
|
||||
chain_fletcher4(queue_item_t *item_in, void *context_in)
|
||||
{
|
||||
if (item == NULL)
|
||||
drr_fletcher4_t *item = (drr_fletcher4_t *)item_in;
|
||||
fletcher4_context_t *context = (fletcher4_context_t *)context_in;
|
||||
|
||||
if (item == NULL || (context->fc_operation == F4_VALIDATE &&
|
||||
OPTION_ENABLED(CA_IGNORE_CKSUMS))) {
|
||||
return (D_OK);
|
||||
}
|
||||
|
||||
dmu_replay_record_t *drr = &item->dp_drr;
|
||||
struct drr_end *drre = &item->dp_drr.drr_u.drr_end;
|
||||
zio_cksum_t *record_cksum = &drr->drr_u.drr_checksum.drr_checksum;
|
||||
zio_cksum_t *end_cksum = &drre->drr_checksum;
|
||||
zio_cksum_t *stream_cksum = &context->fc_stream_cksum;
|
||||
dmu_replay_record_t *drr = &item->dp_base.dp_drr;
|
||||
struct drr_end *drre = &drr->drr_u.drr_end;
|
||||
zio_cksum_t *record_cksum = &drr->drr_u.drr_checksum.drr_checksum;
|
||||
zio_cksum_t *end_cksum = &drre->drr_checksum;
|
||||
|
||||
boolean_t swap = OPTION_ENABLED(CA_BYTESWAP_ON_OUTPUT);
|
||||
boolean_t swap = (context->fc_operation == F4_VALIDATE &&
|
||||
ATTR_IS_SET(CA_BYTESWAPPED)) || (context->fc_operation == F4_SET &&
|
||||
OPTION_ENABLED(CA_BYTESWAP_ON_OUTPUT));
|
||||
uint32_t drr_type = swap ? BSWAP_32(drr->drr_type) : drr->drr_type;
|
||||
off_t ck_offset = offsetof(dmu_replay_record_t,
|
||||
drr_u.drr_checksum.drr_checksum);
|
||||
|
||||
if (item->dp_base.dp_stream_offset == 0) {
|
||||
VERIFY3U(ck_offset, ==, sizeof (dmu_replay_record_t) -
|
||||
sizeof (zio_cksum_t));
|
||||
}
|
||||
if (drr_type == DRR_BEGIN) {
|
||||
ZIO_SET_CHECKSUM(stream_cksum, 0, 0, 0, 0);
|
||||
} else if (drr_type == DRR_END) {
|
||||
*end_cksum = *stream_cksum;
|
||||
if (swap)
|
||||
ZIO_CHECKSUM_BSWAP(end_cksum);
|
||||
if (context->fc_operation == F4_VALIDATE) {
|
||||
off_t stream_offset = item->dp_base.dp_stream_offset +
|
||||
offsetof(dmu_replay_record_t,
|
||||
drr_u.drr_end.drr_checksum);
|
||||
validate_or_exit(stream_cksum, end_cksum, swap,
|
||||
"in DRR_END record", stream_offset);
|
||||
} else {
|
||||
*end_cksum = *stream_cksum;
|
||||
if (swap)
|
||||
ZIO_CHECKSUM_BSWAP(end_cksum);
|
||||
}
|
||||
}
|
||||
fletcher_4_incremental(swap, drr, CK_OFFSET, stream_cksum);
|
||||
fletcher_4_incremental(swap, drr, ck_offset, stream_cksum);
|
||||
if (drr_type != DRR_BEGIN && !IS_CONCLUSION(drr, drr_type)) {
|
||||
*record_cksum = *stream_cksum;
|
||||
if (swap)
|
||||
ZIO_CHECKSUM_BSWAP(record_cksum);
|
||||
if (context->fc_operation == F4_VALIDATE) {
|
||||
off_t stream_offset =
|
||||
item->dp_base.dp_stream_offset + ck_offset;
|
||||
validate_or_exit(stream_cksum, record_cksum,
|
||||
swap, "at DRR record end", stream_offset);
|
||||
} else {
|
||||
*record_cksum = *stream_cksum;
|
||||
if (swap)
|
||||
ZIO_CHECKSUM_BSWAP(record_cksum);
|
||||
}
|
||||
}
|
||||
if (drr_type == DRR_END) {
|
||||
ZIO_SET_CHECKSUM(stream_cksum, 0, 0, 0, 0);
|
||||
@@ -96,54 +269,32 @@ chain_add_fletcher4(drr_packet_t *item, zio_cksum_t *stream_cksum)
|
||||
fletcher_4_incremental(swap, record_cksum,
|
||||
sizeof (drr->drr_u.drr_checksum.drr_checksum),
|
||||
stream_cksum);
|
||||
if (item->dp_payload_size > 0) {
|
||||
fletcher_4_incremental(swap, item->dp_payload,
|
||||
item->dp_payload_size, stream_cksum);
|
||||
}
|
||||
assemble_payload_cksum(item, stream_cksum);
|
||||
}
|
||||
return (D_OK);
|
||||
}
|
||||
|
||||
static disposition_t
|
||||
chain_validate_fletcher4(drr_packet_t *item, zio_cksum_t *stream_cksum)
|
||||
/*
|
||||
* Since checksumming is either very early or very late in the chain, these
|
||||
* queues effectively double as I/O buffers. Ergo, the default queue length
|
||||
* is long. The batch budget is also large because Fletcher 4 calculations
|
||||
* are fast.
|
||||
*/
|
||||
chain_step_t
|
||||
parallel_calc_fletcher4(int queue_length)
|
||||
{
|
||||
if (item == NULL || OPTION_ENABLED(CA_IGNORE_CKSUMS)) {
|
||||
return (D_OK);
|
||||
}
|
||||
|
||||
dmu_replay_record_t *drr = &item->dp_drr;
|
||||
struct drr_end *drre = &item->dp_drr.drr_u.drr_end;
|
||||
zio_cksum_t *record_cksum = &drr->drr_u.drr_checksum.drr_checksum;
|
||||
zio_cksum_t *end_cksum = &drre->drr_checksum;
|
||||
|
||||
boolean_t swap = ATTR_IS_SET(CA_BYTESWAPPED);
|
||||
uint32_t drr_type = swap ? BSWAP_32(drr->drr_type) : drr->drr_type;
|
||||
|
||||
if (drr_type == DRR_BEGIN) {
|
||||
ZIO_SET_CHECKSUM(stream_cksum, 0, 0, 0, 0);
|
||||
} else if (drr_type == DRR_END) {
|
||||
off_t stream_offset = item->dp_stream_offset + END_CK_OFFSET;
|
||||
validate_or_exit(stream_cksum, end_cksum, swap,
|
||||
"in DRR_END record", stream_offset);
|
||||
}
|
||||
fletcher_4_incremental(swap, drr, CK_OFFSET, stream_cksum);
|
||||
if (drr_type != DRR_BEGIN && !IS_CONCLUSION(drr, drr_type)) {
|
||||
off_t stream_offset = item->dp_stream_offset + CK_OFFSET;
|
||||
validate_or_exit(stream_cksum, record_cksum,
|
||||
swap, "at DRR record end", stream_offset);
|
||||
}
|
||||
if (drr_type == DRR_END) {
|
||||
ZIO_SET_CHECKSUM(stream_cksum, 0, 0, 0, 0);
|
||||
} else {
|
||||
fletcher_4_incremental(swap, record_cksum,
|
||||
sizeof (drr->drr_u.drr_checksum.drr_checksum),
|
||||
stream_cksum);
|
||||
if (item->dp_payload_size > 0) {
|
||||
fletcher_4_incremental(swap, item->dp_payload,
|
||||
item->dp_payload_size, stream_cksum);
|
||||
}
|
||||
}
|
||||
return (D_OK);
|
||||
chain_step_t step = {
|
||||
.cs_type = CS_PARALLEL,
|
||||
.cs_in_size = sizeof (drr_packet_t),
|
||||
.cs_out_size = sizeof (drr_fletcher4_t),
|
||||
.cs_parallel = {
|
||||
.queue_length = queue_length,
|
||||
.batch_budget = 256 * 1024,
|
||||
.process = chain_calc_fletcher4,
|
||||
.cost = payload_size_as_cost
|
||||
}
|
||||
};
|
||||
return (step);
|
||||
}
|
||||
|
||||
static chain_step_t
|
||||
@@ -152,17 +303,17 @@ fletcher4_serial_step(fletcher4_op_t operation)
|
||||
int context_ix = next_context++ % MAX_FLETCHER_4;
|
||||
fletcher4_context_t *context = &fletcher4_contexts[context_ix];
|
||||
|
||||
ZIO_SET_CHECKSUM(context, 0, 0, 0, 0);
|
||||
context->fc_operation = operation;
|
||||
ZIO_SET_CHECKSUM(&context->fc_stream_cksum, 0, 0, 0, 0);
|
||||
|
||||
chain_step_t step = {
|
||||
.cs_type = CS_SERIAL,
|
||||
.cs_in_size = sizeof (drr_packet_t),
|
||||
.cs_out_size = sizeof (drr_packet_t),
|
||||
.cs_context = context,
|
||||
.cs_serial = {
|
||||
.process = (zc_serial_process_f *)
|
||||
((operation == F4_VALIDATE) ?
|
||||
chain_validate_fletcher4 : chain_add_fletcher4)
|
||||
}
|
||||
.cs_type = CS_SERIAL,
|
||||
.cs_in_size = sizeof (drr_fletcher4_t),
|
||||
.cs_out_size = sizeof (drr_packet_t),
|
||||
.cs_context = context,
|
||||
.cs_serial = {
|
||||
.process = chain_fletcher4,
|
||||
}
|
||||
};
|
||||
return (step);
|
||||
}
|
||||
|
||||
@@ -1,16 +1,13 @@
|
||||
// SPDX-License-Identifier: CDDL-1.0
|
||||
/*
|
||||
* CDDL HEADER START
|
||||
* This file and its contents are supplied under the terms of the
|
||||
* Common Development and Distribution License ("CDDL"), version 1.0.
|
||||
* You may only use this file in accordance with the terms of version
|
||||
* 1.0 of the CDDL.
|
||||
*
|
||||
* This file and its contents are supplied under the terms of the Common
|
||||
* Development and Distribution License ("CDDL"), version 1.0. You may only use
|
||||
* this file in accordance with the terms of version 1.0 of the CDDL.
|
||||
*
|
||||
* A full copy of the text of the CDDL should have accompanied this source. A
|
||||
* copy of the CDDL is also available via the Internet at
|
||||
* http://www.illumos.org/license/CDDL.
|
||||
*
|
||||
* CDDL HEADER END
|
||||
* A full copy of the text of the CDDL should have accompanied this
|
||||
* source. A copy of the CDDL is also available via the Internet at
|
||||
* https://opensource.org/license/CDDL-1.0.
|
||||
*/
|
||||
|
||||
/*
|
||||
@@ -24,19 +21,30 @@
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#include <sys/spa_checksum.h>
|
||||
#include <sys/zfs_ioctl.h>
|
||||
|
||||
#include "zstream_io.h"
|
||||
|
||||
/*
|
||||
* zstream_chain module for calculating, validating, and inscribing
|
||||
* Fletcher4 checksums.
|
||||
*
|
||||
* serial_validate_fletcher4() validates record checksums against the
|
||||
* running stream checksum and fails loudly on any mismatch.
|
||||
* Checksums are calculated for the entire stream between a DRR_BEGIN record
|
||||
* and its corresponding DRR_END, so the final checksum assembly must be
|
||||
* performed as a serial step. However, we can pre-calculate the checksums
|
||||
* for individual payloads in parallel.
|
||||
*
|
||||
* serial_add_fletcher4() inscribes record checksums from the running
|
||||
* stream checksum, in theory replacing whatever was there before. But
|
||||
* see note in zstream_fletcher4.c regarding zero checksums generated
|
||||
* by send_conclusion_record(), which are preserved.
|
||||
* The normal sequence is a parallel_calc_fletcher4() step followed by a
|
||||
* serial_validate_fletcher4() or serial_add_fletcher4() step.
|
||||
*
|
||||
* Fletcher4 is just addition, so it's quite fast on its own. However, not
|
||||
* parallelizing these calculations reduces aggregate throughput because of
|
||||
* [Amdahl's Law](https://en.wikipedia.org/wiki/Amdahl%27s_law). Briefly
|
||||
* stated: nonparallelizable work limits the total speedup obtainable
|
||||
* through parallelization, often to a nonintuitive degree. Here, the effect
|
||||
* is magnified because parallelizable work such as recompression lies
|
||||
* beyond a Fletcher4 bottleneck.
|
||||
*/
|
||||
|
||||
/*
|
||||
@@ -65,6 +73,15 @@ extern "C" {
|
||||
*/
|
||||
#define MAX_FLETCHER_4 8
|
||||
|
||||
typedef struct {
|
||||
drr_packet_t dp_base;
|
||||
zio_cksum_t dp_fletcher4_payload;
|
||||
zio_cksum_t *dp_fletcher4_overflow;
|
||||
} drr_fletcher4_t;
|
||||
|
||||
chain_step_t
|
||||
parallel_calc_fletcher4(int queue_length);
|
||||
|
||||
chain_step_t
|
||||
serial_validate_fletcher4(void);
|
||||
|
||||
|
||||
@@ -1,16 +1,13 @@
|
||||
// SPDX-License-Identifier: CDDL-1.0
|
||||
/*
|
||||
* CDDL HEADER START
|
||||
* This file and its contents are supplied under the terms of the
|
||||
* Common Development and Distribution License ("CDDL"), version 1.0.
|
||||
* You may only use this file in accordance with the terms of version
|
||||
* 1.0 of the CDDL.
|
||||
*
|
||||
* This file and its contents are supplied under the terms of the Common
|
||||
* Development and Distribution License ("CDDL"), version 1.0. You may only use
|
||||
* this file in accordance with the terms of version 1.0 of the CDDL.
|
||||
*
|
||||
* A full copy of the text of the CDDL should have accompanied this source. A
|
||||
* copy of the CDDL is also available via the Internet at
|
||||
* http://www.illumos.org/license/CDDL.
|
||||
*
|
||||
* CDDL HEADER END
|
||||
* A full copy of the text of the CDDL should have accompanied this
|
||||
* source. A copy of the CDDL is also available via the Internet at
|
||||
* https://opensource.org/license/CDDL-1.0.
|
||||
*/
|
||||
|
||||
/*
|
||||
@@ -19,11 +16,10 @@
|
||||
|
||||
#include <arpa/inet.h>
|
||||
#include <err.h>
|
||||
#include <errno.h>
|
||||
#include <libzutil.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <sys/byteorder.h>
|
||||
#include <sys/stdtypes.h>
|
||||
#include <sys/sysmacros.h>
|
||||
@@ -32,7 +28,6 @@
|
||||
#include <time.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "zstream_chain.h"
|
||||
#include "zstream_modules.h"
|
||||
#include "zstream_util.h"
|
||||
|
||||
@@ -59,6 +54,9 @@ static int next_io_context = 0;
|
||||
static checkpoint_context_t checkpoint_contexts[MAX_IO_STREAMS];
|
||||
static int next_checkpoint_context = 0;
|
||||
|
||||
static uint32_t drop_contexts[MAX_DROP_FILTERS];
|
||||
static int next_drop_context = 0;
|
||||
|
||||
/*
|
||||
* Run from within chain execution to initialize I/O. A NULL filename
|
||||
* indicates stdin or stdout.
|
||||
@@ -213,8 +211,11 @@ set_stream_attributes(drr_packet_t *item)
|
||||
}
|
||||
|
||||
static disposition_t
|
||||
chain_read(drr_packet_t *item, io_context_t *context)
|
||||
chain_read(void *item_in, void *context_in)
|
||||
{
|
||||
drr_packet_t *item = (drr_packet_t *)item_in;
|
||||
io_context_t *context = (io_context_t *)context_in;
|
||||
|
||||
if (item == NULL)
|
||||
return (D_OK);
|
||||
|
||||
@@ -293,8 +294,11 @@ chain_read(drr_packet_t *item, io_context_t *context)
|
||||
}
|
||||
|
||||
static disposition_t
|
||||
chain_write(drr_packet_t *item, io_context_t *context)
|
||||
chain_write(void *item_in, void *context_in)
|
||||
{
|
||||
drr_packet_t *item = (drr_packet_t *)item_in;
|
||||
io_context_t *context = (io_context_t *)context_in;
|
||||
|
||||
if (item == NULL) {
|
||||
if (context->ic_fp) {
|
||||
if (fclose(context->ic_fp) != 0)
|
||||
@@ -343,9 +347,11 @@ chain_write(drr_packet_t *item, io_context_t *context)
|
||||
* Even if the chain doesn't write out a stream, payloads still need freed.
|
||||
*/
|
||||
static disposition_t
|
||||
chain_null_output(drr_packet_t *item, void *context)
|
||||
chain_null_output(void *item_in, void *context)
|
||||
{
|
||||
(void) context;
|
||||
drr_packet_t *item = (drr_packet_t *)item_in;
|
||||
|
||||
if (item && item->dp_payload != NULL && item->dp_payload_size > 0) {
|
||||
free(item->dp_payload);
|
||||
item->dp_payload = NULL;
|
||||
@@ -370,12 +376,11 @@ setup_io(const char *filename, boolean_t for_reading)
|
||||
|
||||
chain_step_t step = {
|
||||
.cs_type = CS_SERIAL,
|
||||
.cs_in_size = 0,
|
||||
.cs_out_size = sizeof (drr_packet_t),
|
||||
.cs_in_size = for_reading ? 0 : sizeof (drr_packet_t),
|
||||
.cs_out_size = for_reading ? sizeof (drr_packet_t) : 0,
|
||||
.cs_context = &io_contexts[context_num],
|
||||
.cs_serial = {
|
||||
.process = (zc_serial_process_f *)
|
||||
(for_reading ? chain_read : chain_write),
|
||||
.process = for_reading ? chain_read : chain_write
|
||||
}
|
||||
};
|
||||
return (step);
|
||||
@@ -402,15 +407,34 @@ serial_null_output(void)
|
||||
.cs_out_size = 0,
|
||||
.cs_context = NULL,
|
||||
.cs_serial = {
|
||||
.process = (zc_serial_process_f *)chain_null_output
|
||||
.process = chain_null_output
|
||||
}
|
||||
};
|
||||
return (step);
|
||||
}
|
||||
|
||||
static disposition_t
|
||||
chain_checkpoint(drr_packet_t *item, checkpoint_context_t *ctxt)
|
||||
size_t
|
||||
constant_cost_of_one(queue_item_t *packet, void *context)
|
||||
{
|
||||
(void) context;
|
||||
(void) packet;
|
||||
return (1);
|
||||
}
|
||||
|
||||
size_t
|
||||
payload_size_as_cost(queue_item_t *packet_in, void *context)
|
||||
{
|
||||
(void) context;
|
||||
drr_packet_t *packet = (drr_packet_t *)packet_in;
|
||||
return (packet->dp_payload_size);
|
||||
}
|
||||
|
||||
static disposition_t
|
||||
chain_checkpoint(void *item_in, void *ctxt_in)
|
||||
{
|
||||
drr_packet_t *item = (drr_packet_t *)item_in;
|
||||
checkpoint_context_t *ctxt = (checkpoint_context_t *)ctxt_in;
|
||||
|
||||
struct timespec now;
|
||||
char buff[32];
|
||||
uint64_t delta_b, dbdt;
|
||||
@@ -455,7 +479,54 @@ serial_checkpoint(const char *name)
|
||||
.cs_out_size = sizeof (drr_packet_t),
|
||||
.cs_context = &checkpoint_contexts[context_no],
|
||||
.cs_serial = {
|
||||
.process = (zc_serial_process_f *)chain_checkpoint
|
||||
.process = chain_checkpoint
|
||||
},
|
||||
};
|
||||
return (step);
|
||||
}
|
||||
|
||||
static disposition_t
|
||||
chain_drop_record_types(void *item_in, void *context_in)
|
||||
{
|
||||
drr_packet_t *item = (drr_packet_t *)item_in;
|
||||
uint32_t *context = (uint32_t *)context_in;
|
||||
|
||||
if (item == NULL)
|
||||
return (D_OK);
|
||||
|
||||
uint32_t type = (uint32_t)item->dp_drr.drr_type;
|
||||
if (type >= DRR_NUMTYPES) {
|
||||
errx(1, "invalid record type %u found at offset %llu "
|
||||
"(place drop filter downstream of byteswapping?)",
|
||||
type, (u_longlong_t)item->dp_stream_offset);
|
||||
}
|
||||
|
||||
if (((UINT32_C(1) << type) & *context) != 0) {
|
||||
if (item->dp_payload != NULL) {
|
||||
free(item->dp_payload);
|
||||
item->dp_payload = NULL;
|
||||
item->dp_payload_size = 0;
|
||||
}
|
||||
return (D_DROP);
|
||||
}
|
||||
return (D_OK);
|
||||
}
|
||||
|
||||
chain_step_t
|
||||
serial_drop_record_types(uint32_t drop_mask)
|
||||
{
|
||||
int context_no = next_drop_context++ % MAX_DROP_FILTERS;
|
||||
uint32_t *context = &drop_contexts[context_no];
|
||||
|
||||
*context = drop_mask;
|
||||
|
||||
chain_step_t step = {
|
||||
.cs_type = CS_SERIAL,
|
||||
.cs_in_size = sizeof (drr_packet_t),
|
||||
.cs_out_size = sizeof (drr_packet_t),
|
||||
.cs_context = context,
|
||||
.cs_serial = {
|
||||
.process = chain_drop_record_types
|
||||
},
|
||||
};
|
||||
return (step);
|
||||
|
||||
@@ -1,16 +1,13 @@
|
||||
// SPDX-License-Identifier: CDDL-1.0
|
||||
/*
|
||||
* CDDL HEADER START
|
||||
* This file and its contents are supplied under the terms of the
|
||||
* Common Development and Distribution License ("CDDL"), version 1.0.
|
||||
* You may only use this file in accordance with the terms of version
|
||||
* 1.0 of the CDDL.
|
||||
*
|
||||
* This file and its contents are supplied under the terms of the Common
|
||||
* Development and Distribution License ("CDDL"), version 1.0. You may only use
|
||||
* this file in accordance with the terms of version 1.0 of the CDDL.
|
||||
*
|
||||
* A full copy of the text of the CDDL should have accompanied this source. A
|
||||
* copy of the CDDL is also available via the Internet at
|
||||
* http://www.illumos.org/license/CDDL.
|
||||
*
|
||||
* CDDL HEADER END
|
||||
* A full copy of the text of the CDDL should have accompanied this
|
||||
* source. A copy of the CDDL is also available via the Internet at
|
||||
* https://opensource.org/license/CDDL-1.0.
|
||||
*/
|
||||
|
||||
/*
|
||||
@@ -24,12 +21,31 @@
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/zfs_ioctl.h>
|
||||
|
||||
#include "zstream_chain.h"
|
||||
|
||||
#define MAX_IO_STREAMS 4
|
||||
#define MAX_IO_STREAMS 4
|
||||
#define MAX_DROP_FILTERS 4
|
||||
|
||||
/*
|
||||
* Masks for serial_drop_record_types()
|
||||
*/
|
||||
|
||||
#define DROP_BEGIN (UINT32_C(1) << DRR_BEGIN)
|
||||
#define DROP_OBJECT (UINT32_C(1) << DRR_OBJECT)
|
||||
#define DROP_FREEOBJECTS (UINT32_C(1) << DRR_FREEOBJECTS)
|
||||
#define DROP_WRITE (UINT32_C(1) << DRR_WRITE)
|
||||
#define DROP_FREE (UINT32_C(1) << DRR_FREE)
|
||||
#define DROP_END (UINT32_C(1) << DRR_END)
|
||||
#define DROP_WRITE_BYREF (UINT32_C(1) << DRR_WRITE_BYREF)
|
||||
#define DROP_SPILL (UINT32_C(1) << DRR_SPILL)
|
||||
#define DROP_WRITE_EMBEDDED (UINT32_C(1) << DRR_WRITE_EMBEDDED)
|
||||
#define DROP_OBJECT_RANGE (UINT32_C(1) << DRR_OBJECT_RANGE)
|
||||
#define DROP_REDACT (UINT32_C(1) << DRR_REDACT)
|
||||
|
||||
/*
|
||||
* The stream offset is the offset within the original source stream.
|
||||
@@ -55,10 +71,30 @@ serial_read_stream(const char *filename);
|
||||
chain_step_t
|
||||
serial_write_stream(const char *filename);
|
||||
|
||||
/* Report throughput periodically */
|
||||
/*
|
||||
* Report throughput periodically
|
||||
*/
|
||||
chain_step_t
|
||||
serial_checkpoint(const char *name);
|
||||
|
||||
/*
|
||||
* Winnow the stream by dropping records of the given types. This frees up
|
||||
* payload memory used by records you won't be inspecting. If there are
|
||||
* parallel operations downstream of the filter, removing records allows the
|
||||
* parallel queues to be used more efficiently.
|
||||
*
|
||||
* Use the DROP_* defines to construct a mask of the records you want to
|
||||
* remove. If you want to remove most records, it's fine to pass an inverted
|
||||
* mask formed by enumerating only the records you want to keep, e.g.:
|
||||
*
|
||||
* serial_drop_record_types((uint32_t)~(DROP_WRITE | DROP_WRITE_EMBEDDED))
|
||||
*
|
||||
* This step should be placed downstream of byteswapping, since it relies on
|
||||
* being able to read drr->drr_type.
|
||||
*/
|
||||
chain_step_t
|
||||
serial_drop_record_types(uint32_t drop_mask);
|
||||
|
||||
/*
|
||||
* Usually the output step is responsible for freeing payloads. Subcommands
|
||||
* that don't have stream outputs still need to free this memory. A
|
||||
@@ -67,6 +103,14 @@ serial_checkpoint(const char *name);
|
||||
chain_step_t
|
||||
serial_null_output(void);
|
||||
|
||||
/* Off-the-shelf zstream_queue cost functions */
|
||||
|
||||
size_t
|
||||
constant_cost_of_one(queue_item_t *packet, void *context);
|
||||
|
||||
size_t
|
||||
payload_size_as_cost(queue_item_t *packet, void *context);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -1,16 +1,13 @@
|
||||
// SPDX-License-Identifier: CDDL-1.0
|
||||
/*
|
||||
* CDDL HEADER START
|
||||
* This file and its contents are supplied under the terms of the
|
||||
* Common Development and Distribution License ("CDDL"), version 1.0.
|
||||
* You may only use this file in accordance with the terms of version
|
||||
* 1.0 of the CDDL.
|
||||
*
|
||||
* This file and its contents are supplied under the terms of the Common
|
||||
* Development and Distribution License ("CDDL"), version 1.0. You may only use
|
||||
* this file in accordance with the terms of version 1.0 of the CDDL.
|
||||
*
|
||||
* A full copy of the text of the CDDL should have accompanied this source. A
|
||||
* copy of the CDDL is also available via the Internet at
|
||||
* http://www.illumos.org/license/CDDL.
|
||||
*
|
||||
* CDDL HEADER END
|
||||
* A full copy of the text of the CDDL should have accompanied this
|
||||
* source. A copy of the CDDL is also available via the Internet at
|
||||
* https://opensource.org/license/CDDL-1.0.
|
||||
*/
|
||||
|
||||
/*
|
||||
@@ -31,26 +28,30 @@ extern "C" {
|
||||
|
||||
#include "zstream_byteswap.h"
|
||||
#include "zstream_chain.h"
|
||||
#include "zstream_dump.h"
|
||||
#include "zstream_fletcher4.h"
|
||||
#include "zstream_io.h"
|
||||
#include "zstream_recompress.h"
|
||||
#include "zstream_util.h"
|
||||
#include "zstream_validate.h"
|
||||
|
||||
#define READ_STEP 0
|
||||
|
||||
#define STANDARD_INPUT_STACK(infile) \
|
||||
#define STANDARD_INPUT_STACK_Q(infile, queue_size) \
|
||||
serial_read_stream(infile), \
|
||||
parallel_calc_fletcher4(queue_size), \
|
||||
serial_validate_fletcher4(), \
|
||||
serial_byteswap(BS_INCOMING), \
|
||||
serial_validate_records()
|
||||
|
||||
#define STANDARD_OUTPUT_STACK(outfile) \
|
||||
#define STANDARD_OUTPUT_STACK_Q(outfile, queue_size) \
|
||||
serial_byteswap(BS_OUTGOING), \
|
||||
parallel_calc_fletcher4(queue_size), \
|
||||
serial_add_fletcher4(), \
|
||||
serial_write_stream(outfile), \
|
||||
chain_terminator()
|
||||
|
||||
#define STANDARD_INPUT_STACK(infile) STANDARD_INPUT_STACK_Q(infile, 1024)
|
||||
#define STANDARD_OUTPUT_STACK(outfile) STANDARD_OUTPUT_STACK_Q(outfile, 512)
|
||||
|
||||
#define NULL_OUTPUT_STACK() \
|
||||
serial_null_output(), \
|
||||
chain_terminator()
|
||||
|
||||
@@ -0,0 +1,925 @@
|
||||
// SPDX-License-Identifier: CDDL-1.0
|
||||
/*
|
||||
* This file and its contents are supplied under the terms of the
|
||||
* Common Development and Distribution License ("CDDL"), version 1.0.
|
||||
* You may only use this file in accordance with the terms of version
|
||||
* 1.0 of the CDDL.
|
||||
*
|
||||
* A full copy of the text of the CDDL should have accompanied this
|
||||
* source. A copy of the CDDL is also available via the Internet at
|
||||
* https://opensource.org/license/CDDL-1.0.
|
||||
*/
|
||||
|
||||
/*
|
||||
* Copyright (c) 2026 by Garth Snyder. All rights reserved.
|
||||
*/
|
||||
|
||||
#include <assert.h>
|
||||
#include <atomic.h>
|
||||
#include <err.h>
|
||||
#include <errno.h>
|
||||
#include <pthread.h>
|
||||
#include <sched.h>
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <sys/param.h>
|
||||
#include <sys/random.h>
|
||||
#include <sys/stdtypes.h>
|
||||
#include <sys/sysmacros.h>
|
||||
#include <sys/time.h>
|
||||
#include <time.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "zstream_queue.h"
|
||||
#include "zstream_util.h"
|
||||
|
||||
#define ENQUEUE_DELAY_NSEC (100 * 1000) /* 100us */
|
||||
#define DISPATCH_BACKUP_NSEC (1000 * 1000) /* 1ms */
|
||||
|
||||
#define PLENTY_OF_WORK 6 /* "Many" items to claim */
|
||||
#define NO_WORK 1.0E-6 /* No-work score threshold */
|
||||
#define DEQUEUE_SCORE_WEIGHT 0.3 /* Dequeue score relative weight */
|
||||
|
||||
#define Q_MOD(queue, index) ((index) % (queue)->zq_params.qp_queue_length)
|
||||
#define Q_SLOT(queue, index) ((queue)->zq_slots[Q_MOD((queue), (index))])
|
||||
|
||||
#define Q_FULL(queue) ((queue)->zq_ix.enqueue - (queue)->zq_ix.dequeue >= \
|
||||
(queue)->zq_params.qp_queue_length)
|
||||
|
||||
/*
|
||||
* A zstream_queue is a ring buffer with four indexes: enqueue, claim,
|
||||
* complete, and dequeue, in that order. No index can move beyond its
|
||||
* preceding index. Every interval between indexes contains work items in a
|
||||
* particular state: enqueued, claimed for work, or completed. Items never
|
||||
* leave the ring buffer, so FIFO order is guaranteed on dequeueing.
|
||||
*
|
||||
* In concept, every index has a corresponding condition that threads can
|
||||
* wait on if they are interested in knowing when that index moves:
|
||||
* enqueued, claimed, completed, dequeued. However, the reality deviates
|
||||
* from this model in two ways:
|
||||
*
|
||||
* - There is no "claimed" condition, because no thread would wait on it.
|
||||
* Claiming and processing are one unified operation. Dequeuers await the
|
||||
* "completed" condition.
|
||||
*
|
||||
* - All queues share one thread pool, so idle threads are not bound to any
|
||||
* particular queue. Instead of having queue-specific "enqueued" conditions,
|
||||
* queues share a centralized dispatch system. On being awakened, worker
|
||||
* threads assign themselves to a queue through a scoring mechanism
|
||||
* described in the comments at score_queue().
|
||||
*
|
||||
* LOCKING
|
||||
*
|
||||
* There are three types of lock:
|
||||
*
|
||||
* - One global lock that gates changes to the thread pool and queue cohort.
|
||||
* This lock also acts as the mutex for the tp_wake_worker condition.
|
||||
*
|
||||
* - A second, low-contention global lock that protects the dispatch system
|
||||
*
|
||||
* - One lock for each queue
|
||||
*
|
||||
* Any operation that adds or removes queues or threads should hold the pool
|
||||
* lock. Any operation that moves a queue's indexes should hold the queue
|
||||
* lock. Any thread waiting for work waits on tp_wake_worker.
|
||||
*
|
||||
* Worker threads hold no locks while they are actually processing items.
|
||||
*
|
||||
* The global locking order is dispatch -> pool -> queue.
|
||||
*
|
||||
* DISPATCH
|
||||
*
|
||||
* Four events trigger dispatch loops:
|
||||
*
|
||||
* 1) A worker thread completing its batch. Threads always check to see if
|
||||
* there's more claimable work before going to sleep.
|
||||
*
|
||||
* 2) A worker thread discovering more work than it can handle on its own.
|
||||
* Before starting work on its own batch, the worker attempts to signal
|
||||
* another thread to wake up and assess the current state.
|
||||
*
|
||||
* 3) Enqueues. These go through the dispatch system and are batched.
|
||||
* Roughly ENQUEUE_DELAY_NSEC after an enqueue (on any queue), the dispatch
|
||||
* thread attempts to awaken a worker.
|
||||
*
|
||||
* 4) The expiration of a backup timer. The atomic value tp_unclaimed tracks
|
||||
* the total number of enqueued-but-unclaimed items across all queues. When
|
||||
* zero, it indicates that no worker dispatch is currently necessary. This
|
||||
* value is rigorously maintained and the increments and decrements are
|
||||
* sequentially consistent. However, the reads are relaxed, so a reader may
|
||||
* see a stale value. In the event that a critical worker wakeup is dropped,
|
||||
* the backup timer intervenes to keep dispatches running.
|
||||
*/
|
||||
|
||||
typedef struct {
|
||||
queue_item_t *qs_item;
|
||||
size_t qs_cost;
|
||||
boolean_t qs_completed;
|
||||
boolean_t qs_end_of_stream;
|
||||
} queue_slot_t;
|
||||
|
||||
typedef struct {
|
||||
uint64_t enqueue;
|
||||
uint64_t claim;
|
||||
uint64_t complete;
|
||||
uint64_t dequeue;
|
||||
} zq_indexes_t;
|
||||
|
||||
typedef struct {
|
||||
pthread_cond_t completed;
|
||||
pthread_cond_t dequeued;
|
||||
} zq_conditions_t;
|
||||
|
||||
typedef struct {
|
||||
int min_depth;
|
||||
int max_depth;
|
||||
} zq_stats_t;
|
||||
|
||||
struct zstream_queue {
|
||||
int zq_id;
|
||||
queue_slot_t *zq_slots;
|
||||
pthread_mutex_t zq_mutex;
|
||||
zq_indexes_t zq_ix;
|
||||
zq_conditions_t zq_cond;
|
||||
zq_params_t zq_params;
|
||||
boolean_t zq_disallow_enqueue;
|
||||
#ifdef MONITOR_QUEUES
|
||||
zq_stats_t zq_stats;
|
||||
uint64_t zq_histogram[ZQ_MAX_BATCH+1]; /* Batch sizes */
|
||||
#endif
|
||||
};
|
||||
|
||||
typedef struct {
|
||||
pthread_mutex_t tp_pool_mutex;
|
||||
pthread_cond_t tp_wake_worker; /* Awaited by workers */
|
||||
|
||||
pthread_mutex_t tp_dispatch_mutex;
|
||||
pthread_cond_t tp_request_dispatch; /* By dispatch thread */
|
||||
boolean_t tp_dispatch_requested;
|
||||
|
||||
zstream_queue_t *tp_queues[ZQ_MAX_QUEUES];
|
||||
int tp_num_queues;
|
||||
|
||||
boolean_t tp_threads_created;
|
||||
int tp_num_threads;
|
||||
|
||||
uint64_t tp_unclaimed; /* Atomic, all queues */
|
||||
} thread_pool_t;
|
||||
|
||||
typedef union {
|
||||
long long ll;
|
||||
long double ld;
|
||||
void *p;
|
||||
void (*fp)(void);
|
||||
} worst_case_alignment_t;
|
||||
|
||||
static void *queue_worker(void *);
|
||||
static void *dispatch_worker(void *);
|
||||
|
||||
#ifdef MONITOR_QUEUES
|
||||
static void *cpu_and_queue_monitor(void *);
|
||||
static void print_batch_size_histogram(zstream_queue_t *);
|
||||
#endif
|
||||
|
||||
static thread_pool_t pool = {0};
|
||||
static pthread_once_t once_control = PTHREAD_ONCE_INIT;
|
||||
|
||||
/*
|
||||
* The dispatch timer needs sub-millisecond accuracy. POSIX timers on
|
||||
* FreeBSD don't implement that, but nanosleep() works fine.
|
||||
*/
|
||||
static void
|
||||
sleep_nsec(uint64_t nsec)
|
||||
{
|
||||
struct timespec ts = {
|
||||
.tv_sec = nsec / NANOSEC,
|
||||
.tv_nsec = nsec % NANOSEC
|
||||
};
|
||||
while (nanosleep(&ts, &ts) != 0) {
|
||||
if (errno != EINTR)
|
||||
err(1, "nanosleep failed");
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
thread_pool_init(void)
|
||||
{
|
||||
pthread_mutex_init(&pool.tp_pool_mutex, NULL);
|
||||
pthread_cond_init(&pool.tp_wake_worker, NULL);
|
||||
|
||||
pthread_mutex_init(&pool.tp_dispatch_mutex, NULL);
|
||||
pthread_cond_init(&pool.tp_request_dispatch, NULL);
|
||||
|
||||
safe_create_thread(dispatch_worker, NULL, "dispatch", B_TRUE);
|
||||
}
|
||||
|
||||
/*
|
||||
* If this function is to be called at all, it must be called before any
|
||||
* queues have been created.
|
||||
*/
|
||||
void
|
||||
zstream_queue_set_num_threads(int n)
|
||||
{
|
||||
pthread_once(&once_control, thread_pool_init);
|
||||
pthread_mutex_lock(&pool.tp_pool_mutex);
|
||||
if (pool.tp_threads_created) {
|
||||
errx(1, "thread pool size must be set before creating queues");
|
||||
} else if (n < 1) {
|
||||
errx(1, "number of threads must be at least 1");
|
||||
} else if (n < ZQ_MIN_THREADS) {
|
||||
warnx("using only %d threads may limit performance, setting "
|
||||
"anyway...", n);
|
||||
} else if (n > 256) {
|
||||
warnx("num_threads = %d seems suspiciously high, setting "
|
||||
"anyway...", n);
|
||||
}
|
||||
pool.tp_num_threads = n;
|
||||
pthread_mutex_unlock(&pool.tp_pool_mutex);
|
||||
}
|
||||
|
||||
/*
|
||||
* Locking: the caller must hold the pool mutex.
|
||||
*
|
||||
* If tp_num_threads is nonzero, it sets the number of threads to spawn.
|
||||
* Otherwise, one thread is spawned per core, with a minimum of 6 threads.
|
||||
*
|
||||
* sched_getaffinity() is a better estimate of available threads than
|
||||
* sysconf because sysconf doesn't account for limits that might be set on,
|
||||
* e.g., a container.
|
||||
*/
|
||||
static void
|
||||
thread_pool_spinup(void)
|
||||
{
|
||||
if (pool.tp_num_threads == 0) {
|
||||
#ifdef CPU_COUNT
|
||||
cpu_set_t cpu_set;
|
||||
if (sched_getaffinity(0, sizeof (cpu_set_t), &cpu_set) != 0) {
|
||||
warn("sched_getaffinity failed, using sysconf");
|
||||
pool.tp_num_threads = sysconf(_SC_NPROCESSORS_ONLN);
|
||||
} else {
|
||||
pool.tp_num_threads = CPU_COUNT(&cpu_set);
|
||||
}
|
||||
#else
|
||||
pool.tp_num_threads = sysconf(_SC_NPROCESSORS_ONLN);
|
||||
#endif
|
||||
pool.tp_num_threads = MAX(pool.tp_num_threads, ZQ_MIN_THREADS);
|
||||
}
|
||||
for (int i = 0; i < pool.tp_num_threads; i++) {
|
||||
char name[32];
|
||||
snprintf(name, sizeof (name), "queue-%d", i);
|
||||
safe_create_thread(queue_worker, NULL, name, B_TRUE);
|
||||
}
|
||||
#ifdef MONITOR_QUEUES
|
||||
safe_create_thread(cpu_and_queue_monitor, NULL, "monitor", B_TRUE);
|
||||
#endif
|
||||
}
|
||||
|
||||
zstream_queue_t *
|
||||
zstream_queue_create(zq_params_t *params)
|
||||
{
|
||||
static int next_queue_id = 0;
|
||||
|
||||
VERIFY3P(params->qp_process, !=, NULL);
|
||||
VERIFY3P(params->qp_cost, !=, NULL);
|
||||
VERIFY3U(params->qp_item_size, >, 0);
|
||||
VERIFY3U(params->qp_queue_length, >, 0);
|
||||
VERIFY3U(params->qp_queue_length, <, 1 << 18);
|
||||
|
||||
pthread_once(&once_control, thread_pool_init);
|
||||
pthread_mutex_lock(&pool.tp_pool_mutex);
|
||||
VERIFY3S(pool.tp_num_queues, <, ZQ_MAX_QUEUES);
|
||||
|
||||
if (!pool.tp_threads_created) {
|
||||
thread_pool_spinup();
|
||||
pool.tp_threads_created = B_TRUE;
|
||||
}
|
||||
|
||||
zstream_queue_t *queue = safe_malloc(sizeof (zstream_queue_t));
|
||||
*queue = (zstream_queue_t) {
|
||||
.zq_id = next_queue_id++,
|
||||
.zq_params = *params,
|
||||
.zq_slots = safe_malloc(params->qp_queue_length *
|
||||
(sizeof (queue_slot_t))),
|
||||
#ifdef MONITOR_QUEUES
|
||||
.zq_stats.min_depth = INT_MAX
|
||||
#endif
|
||||
};
|
||||
pool.tp_queues[pool.tp_num_queues] = queue;
|
||||
|
||||
size_t qpis_rounded = P2ROUNDUP(params->qp_item_size,
|
||||
_Alignof(worst_case_alignment_t));
|
||||
uint8_t *items = safe_malloc(params->qp_queue_length * qpis_rounded);
|
||||
for (size_t i = 0; i < params->qp_queue_length; i++) {
|
||||
queue->zq_slots[i].qs_item =
|
||||
(queue_item_t *)(items + i * qpis_rounded);
|
||||
}
|
||||
|
||||
pthread_mutex_init(&queue->zq_mutex, NULL);
|
||||
pthread_cond_init(&queue->zq_cond.completed, NULL);
|
||||
pthread_cond_init(&queue->zq_cond.dequeued, NULL);
|
||||
|
||||
pool.tp_num_queues++;
|
||||
pthread_mutex_unlock(&pool.tp_pool_mutex);
|
||||
return (queue);
|
||||
}
|
||||
|
||||
/*
|
||||
* Try to advance the "claim" and "complete" indexes as far as possible by
|
||||
* examining the qs_completed flag on each item. This can't be done directly
|
||||
* by the threads that complete work, for a couple of reasons:
|
||||
*
|
||||
* - Items can be completed in any order. Just because you (a thread) have
|
||||
* finished your batch doesn't mean that all prior batches have completed.
|
||||
* If there are uncompleted items ahead of you in the ring buffer, you can't
|
||||
* advance the completion index past them on your way out.
|
||||
*
|
||||
* - Items for which the cost function returns 0 are marked as qs_completed
|
||||
* on enqueue and are never seen by a worker thread. So, there needs to be
|
||||
* an independent mechanism to sweep the completion index past these items
|
||||
* whenever that becomes possible.
|
||||
*
|
||||
* This function is called:
|
||||
*
|
||||
* - Whenever a thread completes a batch
|
||||
* - Whenever a thread claims a batch
|
||||
* - Whenever an item of cost 0 is enqueued
|
||||
*
|
||||
* Strictly speaking, advancing on claiming a batch is not logically
|
||||
* necessary. However, the claimer already holds the queue mutex, and it's
|
||||
* in our interest to make completed items available for dequeueing as
|
||||
* expeditiously as possible.
|
||||
*
|
||||
* Sweeping of the "claim" index is also an optimization. It is not
|
||||
* necessary for correctness. However, if we don't do it here, it can only
|
||||
* be done by threads as they claim jobs to work on. In some cases, not
|
||||
* advancing the "claim" index here can result in an empty batch and a
|
||||
* wasted claim cycle.
|
||||
*
|
||||
* Locking: the caller must hold the queue mutex.
|
||||
*/
|
||||
static inline void
|
||||
advance_indexes(zstream_queue_t *queue)
|
||||
{
|
||||
boolean_t any_completed = B_FALSE;
|
||||
uint64_t claimed = 0;
|
||||
|
||||
while (queue->zq_ix.claim < queue->zq_ix.enqueue &&
|
||||
Q_SLOT(queue, queue->zq_ix.claim).qs_completed) {
|
||||
queue->zq_ix.claim++;
|
||||
claimed++;
|
||||
}
|
||||
if (claimed > 0) {
|
||||
/*
|
||||
* tp_unclaimed is decremented both here and in
|
||||
* claim_batch(). The conditions are mutually exclusive, so
|
||||
* double counting will not occur.
|
||||
*/
|
||||
atomic_sub_64(&pool.tp_unclaimed, claimed);
|
||||
}
|
||||
while (queue->zq_ix.complete < queue->zq_ix.claim &&
|
||||
Q_SLOT(queue, queue->zq_ix.complete).qs_completed) {
|
||||
queue->zq_ix.complete++;
|
||||
any_completed = B_TRUE;
|
||||
}
|
||||
if (any_completed) {
|
||||
pthread_cond_signal(&queue->zq_cond.completed);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Score a queue according to its need for workers. Higher is better. The
|
||||
* scoring tries to assign threads to queues that are running out of space
|
||||
* for new enqueuements or that have little completed work available to
|
||||
* dequeue. The broader goal is to try to avoid pipeline stalls.
|
||||
*
|
||||
* Two measures are used for scoring. The "open score" is 1/M where M is the
|
||||
* number of slots available to receive new items. The "dequeue score" is
|
||||
* 1/N where N is the number of completed items available to dequeue. These
|
||||
* two measures are added together with the dequeue score scaled by
|
||||
* DEQUEUE_SCORE_WEIGHT.
|
||||
*
|
||||
* The composite score is scaled by a factor that reflects how much work is
|
||||
* actually available to be claimed on the queue; there's no point assigning
|
||||
* threads to queues that have no work.
|
||||
*
|
||||
* Locking: the caller must hold the thread pool mutex and the queue mutex.
|
||||
*/
|
||||
static inline double
|
||||
score_queue(zstream_queue_t *queue)
|
||||
{
|
||||
uint64_t claimable = queue->zq_ix.enqueue - queue->zq_ix.claim;
|
||||
uint64_t dequeueable = queue->zq_ix.complete - queue->zq_ix.dequeue;
|
||||
uint64_t in_queue = queue->zq_ix.enqueue - queue->zq_ix.dequeue;
|
||||
uint64_t open_slots = queue->zq_params.qp_queue_length - in_queue;
|
||||
|
||||
double open_score = (open_slots > 0) ? (1.0 / open_slots) : 2.0;
|
||||
double dq_score = (dequeueable > 0) ? (1.0 / dequeueable) : 2.0;
|
||||
double claim_factor = MIN(claimable, (uint64_t)PLENTY_OF_WORK) /
|
||||
(double)PLENTY_OF_WORK;
|
||||
double need = open_score + dq_score * DEQUEUE_SCORE_WEIGHT;
|
||||
return (need * claim_factor);
|
||||
}
|
||||
|
||||
/*
|
||||
* Return a random index from an array of doubles, with the likelihood of
|
||||
* index i being selected equal to weights[i] / sum(weights). Returns index
|
||||
* 0 if no weight is greater than 0.
|
||||
*/
|
||||
static inline int
|
||||
select_stochastic(double weights[], int num_values)
|
||||
{
|
||||
const double denominator = (double)UINT64_MAX;
|
||||
uint64_t numerator;
|
||||
double total = 0.0;
|
||||
|
||||
for (int i = 0; i < num_values; i++) {
|
||||
total += weights[i];
|
||||
}
|
||||
random_get_pseudo_bytes((uint8_t *)&numerator, sizeof (numerator));
|
||||
double select_val = total * numerator / denominator;
|
||||
for (int i = 0; i < num_values; i++) {
|
||||
if (select_val < weights[i])
|
||||
return (i);
|
||||
select_val -= weights[i];
|
||||
}
|
||||
/* Fallback in case of FP rounding not producing a winner */
|
||||
for (int i = num_values - 1; i >= 0; i--) {
|
||||
if (weights[i] != 0.0)
|
||||
return (i);
|
||||
}
|
||||
return (0);
|
||||
}
|
||||
|
||||
/*
|
||||
* Claim up to ZQ_MAX_BATCH work items from the given queue, trying to
|
||||
* accumulate at least qp_batch_budget worth of work data (== "cost"). All
|
||||
* items in a batch will be drawn from the same queue.
|
||||
*
|
||||
* Does not block waiting to fill the budget; returns whatever is available.
|
||||
*
|
||||
* Locking: this function must be called with both the queue mutex and the
|
||||
* thread pool mutex held. zstream_queue_destroy() can't hold a queue's
|
||||
* mutex while destroying it (because destruction entails destroying the
|
||||
* queue mutex, which must be unlocked), so holding the queue mutex while
|
||||
* attempting to claim work is not a sufficient guarantee of correctness.
|
||||
*
|
||||
* In other contexts, we have more certainty about whether a queue still has
|
||||
* work to do. If it does, it can't be destroyed while we hold the queue
|
||||
* mutex alone. But here, we merely suspect that there's work available
|
||||
* based on possibly outdated queue scoring information. By the time we get
|
||||
* here, the queue might already have been finalized. Holding the thread
|
||||
* pool mutex guarantees that the queue won't have been destroyed out from
|
||||
* under us.
|
||||
*/
|
||||
static int
|
||||
claim_batch(zstream_queue_t *queue, queue_slot_t **batch)
|
||||
{
|
||||
size_t cost_claimed = 0;
|
||||
int count = 0;
|
||||
uint64_t passed = 0;
|
||||
boolean_t more_to_claim, more_slots, more_budget;
|
||||
boolean_t first_and_only, ok_to_claim;
|
||||
|
||||
while (B_TRUE) {
|
||||
more_to_claim = queue->zq_ix.claim < queue->zq_ix.enqueue;
|
||||
more_slots = count < ZQ_MAX_BATCH;
|
||||
more_budget = cost_claimed < queue->zq_params.qp_batch_budget;
|
||||
first_and_only = queue->zq_params.qp_batch_budget == 0 &&
|
||||
count == 0;
|
||||
ok_to_claim = first_and_only || more_budget;
|
||||
|
||||
if (!more_to_claim || !more_slots || !ok_to_claim) {
|
||||
break;
|
||||
}
|
||||
queue_slot_t *slot = &Q_SLOT(queue, queue->zq_ix.claim);
|
||||
if (!slot->qs_completed) {
|
||||
cost_claimed += slot->qs_cost;
|
||||
batch[count++] = slot;
|
||||
}
|
||||
queue->zq_ix.claim++;
|
||||
passed++;
|
||||
}
|
||||
|
||||
/*
|
||||
* Every slot the claim index moved over leaves the unclaimed pool,
|
||||
* whether we took it for the batch or skipped it as already complete.
|
||||
*/
|
||||
if (passed > 0) {
|
||||
atomic_sub_64(&pool.tp_unclaimed, passed);
|
||||
}
|
||||
advance_indexes(queue);
|
||||
#ifdef MONITOR_QUEUES
|
||||
queue->zq_histogram[count]++;
|
||||
#endif
|
||||
return (count);
|
||||
}
|
||||
|
||||
/*
|
||||
* Threads are assigned to a queue on each loop so they can be shifted
|
||||
* dynamically to follow available work. Idle threads will typically be
|
||||
* waiting on the tp_wake_worker condition within this function.
|
||||
*
|
||||
* Locking: we hold the pool mutex throughout, both to keep a queue from
|
||||
* being destroyed out from under us while we score it or claim from it, and
|
||||
* because it is the mutex for tp_wake_worker. Individual queues are locked
|
||||
* for only as long as it takes to score or claim from them.
|
||||
*/
|
||||
static int
|
||||
assign_queue_and_get_work(zstream_queue_t **queue, queue_slot_t **batch)
|
||||
{
|
||||
pthread_mutex_lock(&pool.tp_pool_mutex);
|
||||
|
||||
while (B_TRUE) {
|
||||
int num_queues = pool.tp_num_queues;
|
||||
double weights[ZQ_MAX_QUEUES];
|
||||
int queues_with_work = 0;
|
||||
|
||||
for (int i = 0; i < num_queues; i++) {
|
||||
zstream_queue_t *to_score = pool.tp_queues[i];
|
||||
pthread_mutex_lock(&to_score->zq_mutex);
|
||||
weights[i] = score_queue(to_score);
|
||||
pthread_mutex_unlock(&to_score->zq_mutex);
|
||||
if (weights[i] > NO_WORK)
|
||||
queues_with_work++;
|
||||
}
|
||||
if (!queues_with_work) {
|
||||
pthread_cond_wait(&pool.tp_wake_worker,
|
||||
&pool.tp_pool_mutex);
|
||||
} else {
|
||||
int q = select_stochastic(weights, num_queues);
|
||||
*queue = pool.tp_queues[q];
|
||||
pthread_mutex_lock(&(*queue)->zq_mutex);
|
||||
int count = claim_batch(*queue, batch);
|
||||
pthread_mutex_unlock(&(*queue)->zq_mutex);
|
||||
/*
|
||||
* Try to wake up another worker thread if there
|
||||
* still seems to be work available (on any queue).
|
||||
*/
|
||||
if (atomic_load_64(&pool.tp_unclaimed) > 0) {
|
||||
pthread_cond_signal(&pool.tp_wake_worker);
|
||||
}
|
||||
pthread_mutex_unlock(&pool.tp_pool_mutex);
|
||||
return (count);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Batches are processed without holding any locks. The existence of the
|
||||
* items we're working on guarantees that the queue can't be destroyed out
|
||||
* from under us.
|
||||
*
|
||||
* However, we can't mark items completed without holding the queue lock
|
||||
* because that creates a potential race condition with advance_indexes()
|
||||
* being called on another thread.
|
||||
*/
|
||||
static void *
|
||||
queue_worker(void *dummy)
|
||||
{
|
||||
(void) dummy;
|
||||
zstream_queue_t *queue;
|
||||
queue_slot_t *batch[ZQ_MAX_BATCH];
|
||||
int count;
|
||||
|
||||
while (B_TRUE) {
|
||||
count = assign_queue_and_get_work(&queue, batch);
|
||||
if (count) {
|
||||
zq_process_item_f *process =
|
||||
queue->zq_params.qp_process;
|
||||
void *context = queue->zq_params.qp_context;
|
||||
for (int i = 0; i < count; i++) {
|
||||
process(batch[i]->qs_item, context);
|
||||
}
|
||||
pthread_mutex_lock(&queue->zq_mutex);
|
||||
for (int i = 0; i < count; i++) {
|
||||
batch[i]->qs_completed = B_TRUE;
|
||||
}
|
||||
advance_indexes(queue);
|
||||
pthread_mutex_unlock(&queue->zq_mutex);
|
||||
}
|
||||
}
|
||||
return (NULL);
|
||||
}
|
||||
|
||||
/*
|
||||
* Locking: must be called with the dispatch mutex held
|
||||
*
|
||||
* Skips the wakeup if tp_unclaimed == 0.
|
||||
*/
|
||||
static inline void
|
||||
maybe_wake_worker(void)
|
||||
{
|
||||
pool.tp_dispatch_requested = B_FALSE;
|
||||
if (atomic_load_64(&pool.tp_unclaimed) > 0) {
|
||||
pthread_mutex_lock(&pool.tp_pool_mutex);
|
||||
pthread_cond_signal(&pool.tp_wake_worker);
|
||||
pthread_mutex_unlock(&pool.tp_pool_mutex);
|
||||
}
|
||||
}
|
||||
|
||||
static inline struct timespec
|
||||
timeout_timespec(void)
|
||||
{
|
||||
struct timespec expire;
|
||||
struct timeval tv;
|
||||
|
||||
if (gettimeofday(&tv, NULL) != 0)
|
||||
err(1, "couldn't gettimeofday()");
|
||||
uint64_t nsec = tv.tv_usec * 1000 + DISPATCH_BACKUP_NSEC;
|
||||
expire.tv_sec = tv.tv_sec + nsec / NANOSEC;
|
||||
expire.tv_nsec = nsec % NANOSEC;
|
||||
return (expire);
|
||||
}
|
||||
|
||||
/*
|
||||
* The enqueue notification pacing thread, which converts a notification
|
||||
* from an enqueuer into a possible worker wakeup roughly ENQUEUE_DELAY_NSEC
|
||||
* later. The delay facilitates larger batch sizes and keeps enqueuers on a
|
||||
* less-contested mutex.
|
||||
*
|
||||
* The condwait timeout is necessary because the tp_unclaimed count is not
|
||||
* the final word on whether there is actually any work to claim. It is
|
||||
* calculated rigorously. However, it's a bare atomic and therefore
|
||||
* potentially out of date at any given moment. A backup strategy is
|
||||
* necessary to restart processing in the event of a race.
|
||||
*/
|
||||
static void *
|
||||
dispatch_worker(void *nope)
|
||||
{
|
||||
(void) nope;
|
||||
pthread_mutex_lock(&pool.tp_dispatch_mutex);
|
||||
while (B_TRUE) {
|
||||
while (!pool.tp_dispatch_requested) {
|
||||
int rc;
|
||||
struct timespec expire = timeout_timespec();
|
||||
rc = pthread_cond_timedwait(&pool.tp_request_dispatch,
|
||||
&pool.tp_dispatch_mutex, &expire);
|
||||
if (rc == ETIMEDOUT) {
|
||||
maybe_wake_worker();
|
||||
} else if (rc != 0) {
|
||||
errx(1, "pthread_cond_timedwait() failed: %s",
|
||||
strerror(rc));
|
||||
}
|
||||
}
|
||||
pthread_mutex_unlock(&pool.tp_dispatch_mutex);
|
||||
sleep_nsec(ENQUEUE_DELAY_NSEC);
|
||||
pthread_mutex_lock(&pool.tp_dispatch_mutex);
|
||||
maybe_wake_worker();
|
||||
}
|
||||
return (NULL);
|
||||
}
|
||||
|
||||
/*
|
||||
* Implements both _enqueue and _fini. item == NULL for fini.
|
||||
*/
|
||||
void
|
||||
zstream_enqueue(zstream_queue_t *queue, queue_item_t *item)
|
||||
{
|
||||
VERIFY3P(queue, !=, NULL);
|
||||
pthread_mutex_lock(&queue->zq_mutex);
|
||||
|
||||
VERIFY3B(queue->zq_disallow_enqueue, ==, B_FALSE);
|
||||
while (Q_FULL(queue)) {
|
||||
pthread_cond_wait(&queue->zq_cond.dequeued, &queue->zq_mutex);
|
||||
}
|
||||
VERIFY3B(queue->zq_disallow_enqueue, ==, B_FALSE);
|
||||
queue_slot_t *slot = &Q_SLOT(queue, queue->zq_ix.enqueue);
|
||||
if (item) {
|
||||
slot->qs_cost =
|
||||
queue->zq_params.qp_cost(item, queue->zq_params.qp_context);
|
||||
slot->qs_completed = slot->qs_cost == 0;
|
||||
slot->qs_end_of_stream = B_FALSE;
|
||||
memcpy(slot->qs_item, item, queue->zq_params.qp_item_size);
|
||||
} else {
|
||||
slot->qs_cost = 0;
|
||||
slot->qs_completed = B_TRUE;
|
||||
slot->qs_end_of_stream = B_TRUE;
|
||||
queue->zq_disallow_enqueue = B_TRUE;
|
||||
}
|
||||
queue->zq_ix.enqueue++;
|
||||
atomic_inc_64(&pool.tp_unclaimed);
|
||||
if (slot->qs_cost == 0)
|
||||
advance_indexes(queue);
|
||||
|
||||
#ifdef MONITOR_QUEUES
|
||||
/* Maintain queue usage data per monitor interval */
|
||||
uint64_t depth = queue->zq_ix.enqueue - queue->zq_ix.dequeue;
|
||||
queue->zq_stats.max_depth = MAX(queue->zq_stats.max_depth, depth);
|
||||
queue->zq_stats.min_depth = MIN(queue->zq_stats.min_depth, depth);
|
||||
#endif
|
||||
|
||||
pthread_mutex_unlock(&queue->zq_mutex);
|
||||
|
||||
pthread_mutex_lock(&pool.tp_dispatch_mutex);
|
||||
pool.tp_dispatch_requested = B_TRUE;
|
||||
pthread_cond_signal(&pool.tp_request_dispatch);
|
||||
pthread_mutex_unlock(&pool.tp_dispatch_mutex);
|
||||
}
|
||||
|
||||
void
|
||||
zstream_queue_fini(zstream_queue_t *queue)
|
||||
{
|
||||
zstream_enqueue(queue, NULL);
|
||||
}
|
||||
|
||||
/*
|
||||
* This function is not public. The only way to destroy a queue through the
|
||||
* public API is to call zstream_queue_fini(), wait for all items to be
|
||||
* processed, and then dequeue all items. As a consequence, threads are
|
||||
* entitled to assume that any queue with unprocessed work will not be
|
||||
* removed without locking the pool mutex.
|
||||
*
|
||||
* Locking: the caller must NOT hold the queue lock. The pool mutex is held
|
||||
* while destroying the queue.
|
||||
*/
|
||||
static void
|
||||
zstream_queue_destroy(zstream_queue_t *queue)
|
||||
{
|
||||
pthread_mutex_lock(&pool.tp_pool_mutex);
|
||||
|
||||
#ifdef MONITOR_QUEUES
|
||||
print_batch_size_histogram(queue);
|
||||
#endif
|
||||
|
||||
VERIFY0(pthread_mutex_destroy(&queue->zq_mutex));
|
||||
VERIFY0(pthread_cond_destroy(&queue->zq_cond.dequeued));
|
||||
if (pthread_cond_destroy(&queue->zq_cond.completed) != 0) {
|
||||
errx(1, "cannot destroy zstream_queue completed condition - "
|
||||
"are you attempting to dequeue from multiple threads "
|
||||
"simultaneously?");
|
||||
}
|
||||
pool.tp_num_queues--;
|
||||
if (pool.tp_num_queues > 0) {
|
||||
/* Gaps are not allowed in the tp_queues array */
|
||||
zstream_queue_t **qscan = &pool.tp_queues[0];
|
||||
int i = pool.tp_num_queues;
|
||||
while (*qscan != queue) { qscan++; i--; }
|
||||
if (i > 0)
|
||||
memmove(qscan, qscan + 1, i * sizeof (*qscan));
|
||||
}
|
||||
/*
|
||||
* Items are allocated as a single block. The address of the first
|
||||
* item field is in fact the start of the block.
|
||||
*/
|
||||
free(queue->zq_slots[0].qs_item);
|
||||
free(queue->zq_slots);
|
||||
queue->zq_slots = NULL;
|
||||
free(queue);
|
||||
|
||||
pthread_mutex_unlock(&pool.tp_pool_mutex);
|
||||
}
|
||||
|
||||
/*
|
||||
* Locking: if more than one thread attempts to dequeue items
|
||||
* simultaneously, disaster is likely. It will work fine until the end of
|
||||
* the stream, at which point it becomes a tossup between a race condition
|
||||
* with multiple attempts to destroy the whole queue vs. an attempt to
|
||||
* delete a condition that another thread is waiting on. Hence the warning
|
||||
* not to do multithreaded dequeues in zstream_queue.h.
|
||||
*
|
||||
* Returns B_TRUE if real data is returned, B_FALSE if the end of the queue
|
||||
* has been reached.
|
||||
*/
|
||||
boolean_t
|
||||
zstream_dequeue(zstream_queue_t *queue, queue_item_t *item)
|
||||
{
|
||||
pthread_mutex_lock(&queue->zq_mutex);
|
||||
while (queue->zq_ix.dequeue >= queue->zq_ix.complete) {
|
||||
pthread_cond_wait(&queue->zq_cond.completed, &queue->zq_mutex);
|
||||
}
|
||||
queue_slot_t *slot = &Q_SLOT(queue, queue->zq_ix.dequeue);
|
||||
queue->zq_ix.dequeue++;
|
||||
if (slot->qs_end_of_stream) {
|
||||
pthread_mutex_unlock(&queue->zq_mutex);
|
||||
/* Potential multi-dequeuer race point */
|
||||
zstream_queue_destroy(queue);
|
||||
return (B_FALSE);
|
||||
} else {
|
||||
memcpy(item, slot->qs_item, queue->zq_params.qp_item_size);
|
||||
pthread_cond_signal(&queue->zq_cond.dequeued);
|
||||
pthread_mutex_unlock(&queue->zq_mutex);
|
||||
return (B_TRUE);
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef MONITOR_QUEUES
|
||||
|
||||
#define USEC_PER_JIFFY 10000
|
||||
#define SAMPLE_DURATION_USEC 1000000
|
||||
#define CPU_FIELD_WIDTH 14
|
||||
|
||||
/*
|
||||
* Called only during zstream_queue_destroy(), under the pool mutex
|
||||
*/
|
||||
static void
|
||||
print_batch_size_histogram(zstream_queue_t *queue)
|
||||
{
|
||||
int last_nonzero = 0;
|
||||
static int lines_printed = 0;
|
||||
|
||||
if (lines_printed++ == 0)
|
||||
fprintf(stderr, "\nBatch size histograms:\n");
|
||||
for (last_nonzero = ZQ_MAX_BATCH; last_nonzero >= 0; last_nonzero--) {
|
||||
if (queue->zq_histogram[last_nonzero] > 0)
|
||||
break;
|
||||
}
|
||||
fprintf(stderr, "Queue %d: ", queue->zq_id);
|
||||
const char *sep = "";
|
||||
for (int i = 0; i <= last_nonzero; i++) {
|
||||
fprintf(stderr, "%s%llu", sep,
|
||||
(u_longlong_t)queue->zq_histogram[i]);
|
||||
sep = ", ";
|
||||
}
|
||||
fprintf(stderr, "\n");
|
||||
fflush(stderr);
|
||||
}
|
||||
|
||||
/*
|
||||
* Monitor queue and CPU usage from a separate thread. This is all
|
||||
* Linux-specific, but it's needed only while tuning queue lengths and
|
||||
* batch sizes. Prints the minimum and maximum queue depth observed
|
||||
* during each period.
|
||||
*
|
||||
* Example output:
|
||||
*
|
||||
* CPU: 99.85% Queue 0: 745-1024 Queue 1: 183-256
|
||||
*/
|
||||
static void *
|
||||
cpu_and_queue_monitor(void *dummy)
|
||||
{
|
||||
(void) dummy;
|
||||
uint64_t period = SAMPLE_DURATION_USEC;
|
||||
struct timespec clock = {0};
|
||||
uint64_t start_us, end_us;
|
||||
uint64_t cpu_jif_prior = 0;
|
||||
uint64_t delta_jif, delta_cpu_jif;
|
||||
long unsigned int utime, stime;
|
||||
char buff[1024];
|
||||
FILE *fp;
|
||||
|
||||
fprintf(stderr, "Queue depths:\n");
|
||||
|
||||
while (B_TRUE) {
|
||||
|
||||
usleep(period);
|
||||
|
||||
fp = fopen("/proc/self/stat", "r");
|
||||
VERIFY3P(fp, !=, NULL);
|
||||
VERIFY3P(fgets(buff, sizeof (buff), fp), !=, NULL);
|
||||
fclose(fp);
|
||||
char *p = strrchr(buff, ')');
|
||||
VERIFY3P(p, !=, NULL);
|
||||
p += 2; /* skip ") " and fields 3-13 */
|
||||
for (int i = 0; i < 11; i++) {
|
||||
p = strchr(p, ' ');
|
||||
VERIFY3P(p, !=, NULL);
|
||||
p++;
|
||||
}
|
||||
VERIFY3U(sscanf(p, "%lu %lu", &utime, &stime), ==, 2);
|
||||
|
||||
pthread_mutex_lock(&pool.tp_pool_mutex);
|
||||
|
||||
clock_gettime(CLOCK_MONOTONIC, &clock);
|
||||
end_us = clock.tv_sec * 1000000 + clock.tv_nsec / 1000;
|
||||
|
||||
if (cpu_jif_prior > 0) {
|
||||
delta_cpu_jif = utime + stime - cpu_jif_prior;
|
||||
delta_jif = (end_us - start_us) / USEC_PER_JIFFY;
|
||||
double cpu_pct = (double)delta_cpu_jif /
|
||||
(pool.tp_num_threads * delta_jif);
|
||||
cpu_pct = MIN(cpu_pct, 0.9999); /* Don't print 100% */
|
||||
fprintf(stderr, "CPU: %5.2f%% ", 100 * cpu_pct);
|
||||
} else {
|
||||
/* No CPU data available for the first interval */
|
||||
fprintf(stderr, "%*s", CPU_FIELD_WIDTH, "");
|
||||
}
|
||||
|
||||
for (int i = 0; i < pool.tp_num_queues; i++) {
|
||||
zstream_queue_t *q = pool.tp_queues[i];
|
||||
pthread_mutex_lock(&q->zq_mutex);
|
||||
int min = q->zq_stats.min_depth;
|
||||
int max = q->zq_stats.max_depth;
|
||||
if (min > max)
|
||||
min = max = 0;
|
||||
fprintf(stderr, "Queue %d: %4d-%-4d ",
|
||||
q->zq_id, min, max);
|
||||
q->zq_stats.min_depth = INT_MAX;
|
||||
q->zq_stats.max_depth = 0;
|
||||
pthread_mutex_unlock(&q->zq_mutex);
|
||||
}
|
||||
|
||||
pthread_mutex_unlock(&pool.tp_pool_mutex);
|
||||
|
||||
fprintf(stderr, "\n");
|
||||
fflush(stderr);
|
||||
|
||||
cpu_jif_prior = utime + stime;
|
||||
start_us = end_us;
|
||||
}
|
||||
return (NULL);
|
||||
}
|
||||
|
||||
#endif /* MONITOR_QUEUES */
|
||||
@@ -0,0 +1,140 @@
|
||||
// SPDX-License-Identifier: CDDL-1.0
|
||||
/*
|
||||
* This file and its contents are supplied under the terms of the
|
||||
* Common Development and Distribution License ("CDDL"), version 1.0.
|
||||
* You may only use this file in accordance with the terms of version
|
||||
* 1.0 of the CDDL.
|
||||
*
|
||||
* A full copy of the text of the CDDL should have accompanied this
|
||||
* source. A copy of the CDDL is also available via the Internet at
|
||||
* https://opensource.org/license/CDDL-1.0.
|
||||
*/
|
||||
|
||||
/*
|
||||
* Copyright (c) 2026 by Garth Snyder. All rights reserved.
|
||||
*/
|
||||
|
||||
#ifndef _ZSTREAM_QUEUE_H
|
||||
#define _ZSTREAM_QUEUE_H
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#include <stddef.h>
|
||||
#include <sys/stdtypes.h>
|
||||
|
||||
/*
|
||||
* This is a generalized implementation of multithreaded FIFO work queues.
|
||||
*
|
||||
* Callers define a fixed item size to be used by each queue and supply two
|
||||
* thread-safe functions that 1) estimate individual items' processing costs
|
||||
* and 2) perform the actual processing. The queue never inspects or
|
||||
* interprets items in the queue, so processing functions can modify them as
|
||||
* they wish.
|
||||
*
|
||||
* The cost function assigns a size_t cost that estimates the amount of work
|
||||
* needed to process an item. For operations like hashing and data
|
||||
* compression, the natural cost is typically payload length.
|
||||
*
|
||||
* It's expected that only a subset of input items will require processing.
|
||||
* If an item's cost is 0, it is fast-tracked and never presented to the
|
||||
* processing function.
|
||||
*
|
||||
* The cost function is run as items enter the queue, with the queue mutex
|
||||
* held, so it should return a value promptly. If cost estimation is
|
||||
* expensive and important, use a separate queue to implement it.
|
||||
*
|
||||
* Dispatch granularity is specified as a per-batch budget that is set for
|
||||
* each queue in the same (arbitrary) units used for item costs. Threads
|
||||
* claim items until the budget is met, there are no more items available,
|
||||
* or ZQ_MAX_BATCH items have been claimed. When claiming items to work on,
|
||||
* threads never block waiting for additional work to arrive. They start
|
||||
* work as quickly as possible even if the budget has not been reached.
|
||||
*
|
||||
* A batch budget of 0 means that all batches will have a size of 1.
|
||||
*
|
||||
* All queues share a single thread pool that is managed to avoid
|
||||
* contention. Threads are assigned to queues dynamically according to where
|
||||
* work is available. When multiple queues have work, threads are allocated
|
||||
* among them stochastically with an eye toward preventing pipeline stalls.
|
||||
*
|
||||
* The shared thread pool persists until the process exits.
|
||||
*/
|
||||
|
||||
#define ZQ_MAX_BATCH 32 /* The most items that can be claimed at once */
|
||||
#define ZQ_MAX_QUEUES 16 /* The maximum number of simultaneous queues */
|
||||
#define ZQ_MIN_THREADS 6
|
||||
|
||||
typedef void queue_item_t;
|
||||
|
||||
typedef struct zstream_queue zstream_queue_t;
|
||||
|
||||
/*
|
||||
* Signatures that cost and processing functions must conform to.
|
||||
*/
|
||||
typedef size_t
|
||||
zq_estimate_cost_f(queue_item_t *item, void *context);
|
||||
|
||||
typedef void
|
||||
zq_process_item_f(queue_item_t *item, void *context);
|
||||
|
||||
/*
|
||||
* Set the number of threads to be spawned for queue work. Since all queues
|
||||
* share a thread pool, this value affects all queues. The value must be set
|
||||
* before any queues are created. By default, one thread is spawned for
|
||||
* every CPU core, but always at least ZQ_MIN_THREADS threads.
|
||||
*/
|
||||
void
|
||||
zstream_queue_set_num_threads(int num_threads);
|
||||
|
||||
/*
|
||||
* Create a queue. The qp_context field is passed to the cost and processing
|
||||
* functions and is not examined by the queue itself.
|
||||
*/
|
||||
|
||||
typedef struct {
|
||||
zq_process_item_f *qp_process;
|
||||
zq_estimate_cost_f *qp_cost;
|
||||
void *qp_context;
|
||||
size_t qp_item_size;
|
||||
size_t qp_batch_budget;
|
||||
size_t qp_queue_length;
|
||||
} zq_params_t;
|
||||
|
||||
zstream_queue_t *
|
||||
zstream_queue_create(zq_params_t *params);
|
||||
|
||||
/*
|
||||
* Submit a work item. Blocks if the queue is full. The work item is
|
||||
* shallow-copied into the queue. Multiple threads may enqueue at once.
|
||||
*/
|
||||
void
|
||||
zstream_enqueue(zstream_queue_t *queue, queue_item_t *item);
|
||||
|
||||
/*
|
||||
* Retrieve a completed work item. The caller must provide a buffer into
|
||||
* which the dequeued item is shallow-copied. If the next item is not yet
|
||||
* ready, this call will block.
|
||||
*
|
||||
* If zstream_dequeue returns B_FALSE, the stream is complete. The returned
|
||||
* item is not valid and no further calls may be made on the queue.
|
||||
*
|
||||
* Only one thread may dequeue at once.
|
||||
*/
|
||||
boolean_t
|
||||
zstream_dequeue(zstream_queue_t *queue, queue_item_t *item);
|
||||
|
||||
/*
|
||||
* Declare that all items have been submitted. The queue will continue to
|
||||
* function normally for dequeuers and worker threads until zstream_dequeue()
|
||||
* returns B_FALSE, at which point the queue will be destroyed.
|
||||
*/
|
||||
void
|
||||
zstream_queue_fini(zstream_queue_t *queue);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* _ZSTREAM_QUEUE_H */
|
||||
@@ -0,0 +1,522 @@
|
||||
// SPDX-License-Identifier: CDDL-1.0
|
||||
/*
|
||||
* This file and its contents are supplied under the terms of the
|
||||
* Common Development and Distribution License ("CDDL"), version 1.0.
|
||||
* You may only use this file in accordance with the terms of version
|
||||
* 1.0 of the CDDL.
|
||||
*
|
||||
* A full copy of the text of the CDDL should have accompanied this
|
||||
* source. A copy of the CDDL is also available via the Internet at
|
||||
* https://opensource.org/license/CDDL-1.0.
|
||||
*/
|
||||
|
||||
/*
|
||||
* Copyright 2026 Klara, Inc.
|
||||
*/
|
||||
|
||||
#ifdef __FreeBSD__
|
||||
#include <sys/disk.h>
|
||||
#endif
|
||||
#include <sys/dmu.h>
|
||||
#include <sys/ioctl.h>
|
||||
#include <sys/spa.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/uio.h>
|
||||
#include <sys/zap.h>
|
||||
#include <sys/zap_impl.h>
|
||||
#include <sys/zfs_ioctl.h>
|
||||
#include <sys/zio.h>
|
||||
#include <sys/zstd/zstd.h>
|
||||
#include <sys/zvol.h>
|
||||
#include <err.h>
|
||||
#include <libnvpair.h>
|
||||
#ifdef __linux__
|
||||
#include <linux/falloc.h>
|
||||
#include <linux/fs.h>
|
||||
#endif
|
||||
#include <stddef.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "zstream.h"
|
||||
#include "zstream_modules.h"
|
||||
#include "zstream_util.h"
|
||||
|
||||
/*
|
||||
* Supported feature flags (in drr_versioninfo)
|
||||
*/
|
||||
#define SUPPORTED_FEATURES (DMU_BACKUP_FEATURE_EMBED_DATA | \
|
||||
DMU_BACKUP_FEATURE_LZ4 | DMU_BACKUP_FEATURE_LARGE_BLOCKS | \
|
||||
DMU_BACKUP_FEATURE_COMPRESSED | DMU_BACKUP_FEATURE_ZSTD)
|
||||
|
||||
typedef struct {
|
||||
struct raw_stream {
|
||||
uint64_t guid;
|
||||
boolean_t inprop;
|
||||
boolean_t inzvol;
|
||||
} stream;
|
||||
struct raw_volume {
|
||||
int fd;
|
||||
size_t size;
|
||||
uint64_t sectorsize;
|
||||
boolean_t isreg;
|
||||
boolean_t punch_holes;
|
||||
unsigned long freeop;
|
||||
} volume;
|
||||
struct raw_limits {
|
||||
long iov_max;
|
||||
long buffers_max;
|
||||
long pagesize;
|
||||
} limits;
|
||||
struct raw_zeros {
|
||||
struct iovec *iov;
|
||||
} zeros;
|
||||
struct raw_buffer {
|
||||
struct iovec *iov;
|
||||
off_t position;
|
||||
size_t length;
|
||||
int iovcnt;
|
||||
} buffer;
|
||||
} raw_context_t;
|
||||
|
||||
static void
|
||||
write_zeros(raw_context_t *context, off_t offset, size_t length)
|
||||
{
|
||||
ASSERT3U(offset + length, >=, offset);
|
||||
|
||||
struct raw_limits *limits = &context->limits;
|
||||
int iovcnt = MIN(howmany(length, limits->pagesize), limits->iov_max);
|
||||
if (iovcnt == 0)
|
||||
return;
|
||||
|
||||
int fd = context->volume.fd;
|
||||
long pagesize = limits->pagesize;
|
||||
struct iovec *iov = context->zeros.iov;
|
||||
size_t resid = length;
|
||||
while (resid > 0) {
|
||||
size_t iovsz = resid;
|
||||
int i;
|
||||
|
||||
for (i = 0; i < iovcnt && resid > 0; i++) {
|
||||
iov[i].iov_len = MIN(resid, pagesize);
|
||||
resid -= iov[i].iov_len;
|
||||
}
|
||||
ssize_t res = pwritev(fd, iov, i, offset);
|
||||
if (res < 0)
|
||||
err(EXIT_FAILURE, "pwritev");
|
||||
iovsz -= resid;
|
||||
VERIFY3U(res, ==, iovsz);
|
||||
offset += iovsz;
|
||||
}
|
||||
ASSERT0(resid);
|
||||
}
|
||||
|
||||
/*
|
||||
* buffer_write - pwrite with buffer vectoring and error handling
|
||||
*
|
||||
* Appends buf to a buffer vector, issuing the pending vector if not contiguous.
|
||||
* Ownership of buf is taken; it will be freed after issuing the write.
|
||||
*/
|
||||
static void
|
||||
buffer_write(raw_context_t *context, void *buf, size_t nbytes, off_t offset)
|
||||
{
|
||||
struct raw_limits *limits = &context->limits;
|
||||
struct raw_buffer *buffer = &context->buffer;
|
||||
struct iovec *iov = buffer->iov;
|
||||
|
||||
if (buffer->iovcnt == 0)
|
||||
buffer->position = offset;
|
||||
else if (buffer->position + buffer->length != offset ||
|
||||
buffer->iovcnt == limits->buffers_max) {
|
||||
ASSERT3U(offset + nbytes, >=, offset);
|
||||
ASSERT3U(buffer->iovcnt, >, 0);
|
||||
ssize_t res = pwritev(context->volume.fd, iov, buffer->iovcnt,
|
||||
buffer->position);
|
||||
if (res < 0)
|
||||
err(EXIT_FAILURE, "pwritev");
|
||||
VERIFY3U(res, ==, buffer->length);
|
||||
buffer->position = offset;
|
||||
buffer->length = 0;
|
||||
for (int i = 0; i < buffer->iovcnt; i++) {
|
||||
free(iov[i].iov_base);
|
||||
iov[i].iov_base = NULL;
|
||||
}
|
||||
buffer->iovcnt = 0;
|
||||
}
|
||||
if (buf == NULL) {
|
||||
/* Sentinel buf for flush. */
|
||||
ASSERT0(buffer->iovcnt);
|
||||
return;
|
||||
}
|
||||
iov[buffer->iovcnt].iov_base = buf;
|
||||
iov[buffer->iovcnt].iov_len = nbytes;
|
||||
buffer->length += nbytes;
|
||||
buffer->iovcnt++;
|
||||
}
|
||||
|
||||
static inline void
|
||||
buffer_finish(raw_context_t *context)
|
||||
{
|
||||
buffer_write(context, NULL, 0, 0);
|
||||
if (fsync(context->volume.fd) != 0)
|
||||
err(EXIT_FAILURE, "fsync");
|
||||
}
|
||||
|
||||
static inline void
|
||||
resize(raw_context_t *context, size_t size)
|
||||
{
|
||||
if (ftruncate(context->volume.fd, size) < 0)
|
||||
err(EXIT_FAILURE, "ftruncate");
|
||||
}
|
||||
|
||||
static inline void
|
||||
free_tail(raw_context_t *context, off_t offset)
|
||||
{
|
||||
/*
|
||||
* This style of FREE is frequently a large range covering most of the
|
||||
* volume from the offset to the end. Truncating the file and extending
|
||||
* it back out works cheaply even on filesystems that do not support
|
||||
* hole punching, avoiding the need to write zeros.
|
||||
*/
|
||||
resize(context, offset);
|
||||
if (offset < context->volume.size)
|
||||
resize(context, context->volume.size);
|
||||
else
|
||||
/* Volume size unknown, but it must be at least this big. */
|
||||
context->volume.size = offset;
|
||||
}
|
||||
|
||||
static inline boolean_t
|
||||
punch_hole(raw_context_t *context, off_t offset, size_t length)
|
||||
{
|
||||
int fd = context->volume.fd;
|
||||
|
||||
#if defined(__FreeBSD__)
|
||||
struct spacectl_range range = { offset, length };
|
||||
return (fspacectl(fd, SPACECTL_DEALLOC, &range, 0, NULL) == 0);
|
||||
#elif defined(__linux__)
|
||||
int mode = FALLOC_FL_PUNCH_HOLE | FALLOC_FL_KEEP_SIZE;
|
||||
return (fallocate(fd, mode, offset, length) == 0);
|
||||
#else
|
||||
(void) fd, (void) offset, (void) length;
|
||||
return (B_FALSE);
|
||||
#endif
|
||||
}
|
||||
|
||||
static boolean_t
|
||||
free_blocks(raw_context_t *context, off_t offset, size_t length)
|
||||
{
|
||||
/*
|
||||
* Ensure sector alignment in case the source zvol's sector size is
|
||||
* smaller than the target device's.
|
||||
*
|
||||
* TODO: Optional secure erase.
|
||||
*/
|
||||
uint64_t sectorsize = context->volume.sectorsize;
|
||||
uint64_t start = P2ROUNDUP(offset, sectorsize);
|
||||
uint64_t limit = offset + length;
|
||||
uint64_t end = P2ALIGN_TYPED(limit, sectorsize, uint64_t);
|
||||
if (offset < start)
|
||||
write_zeros(context, offset, start - offset);
|
||||
if (start < end) {
|
||||
#ifdef __FreeBSD__
|
||||
off_t range[2];
|
||||
#else
|
||||
uint64_t range[2];
|
||||
#endif
|
||||
int fd = context->volume.fd;
|
||||
|
||||
range[0] = start;
|
||||
range[1] = end - start;
|
||||
if (ioctl(fd, context->volume.freeop, range) != 0) {
|
||||
ASSERT3U(errno, ==, EOPNOTSUPP);
|
||||
context->volume.freeop = B_FALSE;
|
||||
return (B_FALSE);
|
||||
}
|
||||
}
|
||||
if (end < limit)
|
||||
write_zeros(context, end, limit - end);
|
||||
return (B_TRUE);
|
||||
}
|
||||
|
||||
static void
|
||||
free_range(raw_context_t *context, off_t offset, size_t length)
|
||||
{
|
||||
if (context->volume.isreg) {
|
||||
if (length == (size_t)-1) {
|
||||
free_tail(context, offset);
|
||||
return;
|
||||
}
|
||||
if (context->volume.punch_holes) {
|
||||
if (punch_hole(context, offset, length))
|
||||
return;
|
||||
context->volume.punch_holes = B_FALSE;
|
||||
}
|
||||
}
|
||||
if (length == (size_t)-1)
|
||||
length = context->volume.size - offset;
|
||||
if (context->volume.freeop && free_blocks(context, offset, length))
|
||||
return;
|
||||
/* If all else fails, the range must be zeroed the slow way. */
|
||||
write_zeros(context, offset, length);
|
||||
}
|
||||
|
||||
/*
|
||||
* apply_properties - read the properties zap to adjust file size.
|
||||
*
|
||||
* Returns the value of the "size" property.
|
||||
*/
|
||||
static uint64_t
|
||||
apply_properties(raw_context_t *context, void *buf, size_t len)
|
||||
{
|
||||
const mzap_phys_t *mzap = buf;
|
||||
|
||||
ASSERT(context->volume.isreg);
|
||||
ASSERT3U(len, >=, sizeof (*mzap));
|
||||
ASSERT3U(MZAP_ENT_LEN, ==, sizeof (mzap_ent_phys_t));
|
||||
|
||||
if (mzap->mz_block_type == BSWAP_64(ZBT_MICRO))
|
||||
zap_byteswap(buf, len);
|
||||
|
||||
ASSERT3U(mzap->mz_block_type, ==, ZBT_MICRO);
|
||||
ASSERT0(strcmp(mzap->mz_chunk[0].mze_name, "size"));
|
||||
|
||||
uint64_t size = mzap->mz_chunk[0].mze_value;
|
||||
resize(context, size);
|
||||
return (size);
|
||||
}
|
||||
|
||||
static disposition_t
|
||||
chain_replay_raw(void *item_in, void *context_in)
|
||||
{
|
||||
drr_packet_t *item = item_in;
|
||||
if (item == NULL)
|
||||
return (D_OK);
|
||||
|
||||
raw_context_t *context = context_in;
|
||||
dmu_replay_record_t *drr = &item->dp_drr;
|
||||
|
||||
switch (drr->drr_type) {
|
||||
case DRR_BEGIN: {
|
||||
struct drr_begin *drrb = &drr->drr_u.drr_begin;
|
||||
|
||||
uint64_t featureflags, unsupported_features;
|
||||
featureflags = DMU_GET_FEATUREFLAGS(drrb->drr_versioninfo);
|
||||
unsupported_features = featureflags & ~SUPPORTED_FEATURES;
|
||||
if (unsupported_features != 0)
|
||||
errx(EXIT_FAILURE, "unsupported stream features: "
|
||||
"%#llx of %#llx, aborting...",
|
||||
(u_longlong_t)unsupported_features,
|
||||
(u_longlong_t)featureflags);
|
||||
|
||||
int hdrtype = DMU_GET_STREAM_HDRTYPE(drrb->drr_versioninfo);
|
||||
if (hdrtype == DMU_SUBSTREAM) {
|
||||
uint64_t guid = context->stream.guid;
|
||||
if (guid != 0 && drrb->drr_fromguid != guid)
|
||||
errx(EXIT_FAILURE, "wrong fromguid: "
|
||||
"%llu != %llu, aborting...",
|
||||
(u_longlong_t)drrb->drr_fromguid,
|
||||
(u_longlong_t)guid);
|
||||
context->stream.guid = drrb->drr_toguid;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case DRR_OBJECT: {
|
||||
struct drr_object *drro = &drr->drr_u.drr_object;
|
||||
|
||||
context->stream.inzvol = drro->drr_object == ZVOL_OBJ &&
|
||||
drro->drr_type == DMU_OT_ZVOL;
|
||||
context->stream.inprop = drro->drr_object == ZVOL_ZAP_OBJ &&
|
||||
drro->drr_type == DMU_OT_ZVOL_PROP;
|
||||
break;
|
||||
}
|
||||
case DRR_WRITE: {
|
||||
struct drr_write *drrw = &drr->drr_u.drr_write;
|
||||
|
||||
if (context->stream.inzvol) {
|
||||
buffer_write(context, item->dp_payload,
|
||||
item->dp_payload_size, drrw->drr_offset);
|
||||
/*
|
||||
* The buffer is no longer owned by the chain. We will
|
||||
* free it when safe.
|
||||
*/
|
||||
item->dp_payload = NULL;
|
||||
} else if (context->volume.isreg && context->stream.inprop) {
|
||||
ASSERT0(drrw->drr_offset);
|
||||
context->volume.size = apply_properties(context,
|
||||
item->dp_payload, item->dp_payload_size);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case DRR_FREE: {
|
||||
if (!context->stream.inzvol)
|
||||
break;
|
||||
|
||||
struct drr_free *drrf = &drr->drr_u.drr_free;
|
||||
free_range(context, drrf->drr_offset, drrf->drr_length);
|
||||
break;
|
||||
}
|
||||
case DRR_WRITE_EMBEDDED: {
|
||||
struct drr_write_embedded *drrwe =
|
||||
&drr->drr_u.drr_write_embedded;
|
||||
|
||||
if (!ctype_is_uncompressed(drrwe->drr_compression)) {
|
||||
uint8_t *buffer = item->dp_payload;
|
||||
uint32_t lsize = drrwe->drr_lsize;
|
||||
|
||||
ASSERT3U(item->dp_payload_size, <=, lsize);
|
||||
|
||||
item->dp_payload = decompress_buffer(buffer,
|
||||
item->dp_payload_size, lsize,
|
||||
drrwe->drr_compression);
|
||||
if (item->dp_payload == NULL)
|
||||
errx(EXIT_FAILURE,
|
||||
"decompression failed at offset %llu",
|
||||
(u_longlong_t)drrwe->drr_offset);
|
||||
item->dp_payload_size = lsize;
|
||||
free(buffer);
|
||||
}
|
||||
if (context->stream.inzvol) {
|
||||
buffer_write(context, item->dp_payload,
|
||||
item->dp_payload_size, drrwe->drr_offset);
|
||||
/*
|
||||
* The buffer is no longer owned by the chain. We will
|
||||
* free it when safe.
|
||||
*/
|
||||
item->dp_payload = NULL;
|
||||
} else if (context->volume.isreg && context->stream.inprop) {
|
||||
ASSERT0(drrwe->drr_offset);
|
||||
context->volume.size = apply_properties(context,
|
||||
item->dp_payload, item->dp_payload_size);
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return (D_OK);
|
||||
}
|
||||
|
||||
/* Keep this small enough to not accidentally run systems out of memory. */
|
||||
#define BUFFERS_MAX_DEFAULT 32
|
||||
|
||||
int
|
||||
zstream_do_raw(int argc, char *argv[])
|
||||
{
|
||||
raw_context_t context = { 0 };
|
||||
context.limits.buffers_max = BUFFERS_MAX_DEFAULT;
|
||||
|
||||
chain_attrs_t attrs = { 0 };
|
||||
ENABLE_OPTION(&attrs, CA_FORBID_DEDUP);
|
||||
|
||||
int c;
|
||||
while ((c = getopt(argc, argv, ":b:g:v")) != -1) {
|
||||
switch (c) {
|
||||
case 'b':
|
||||
context.limits.buffers_max = strtol(optarg, NULL, 0);
|
||||
if (context.limits.buffers_max <= 0) {
|
||||
warnx("invalid number of buffers");
|
||||
zstream_usage();
|
||||
}
|
||||
break;
|
||||
case 'g':
|
||||
context.stream.guid = strtoull(optarg, NULL, 0);
|
||||
if (context.stream.guid == 0) {
|
||||
warnx("invalid guid");
|
||||
zstream_usage();
|
||||
}
|
||||
break;
|
||||
case 'v':
|
||||
ENABLE_OPTION(&attrs, CA_VERBOSE);
|
||||
ENABLE_OPTION(&attrs, CA_DUMP_ALL_RECORDS);
|
||||
ENABLE_OPTION(&attrs, CA_DUMP_CHECKSUMS);
|
||||
break;
|
||||
case ':':
|
||||
warnx("missing argument for '%c' option", optopt);
|
||||
zstream_usage();
|
||||
case '?':
|
||||
warnx("invalid option '%c'", optopt);
|
||||
zstream_usage();
|
||||
}
|
||||
}
|
||||
argc -= optind;
|
||||
argv += optind;
|
||||
if (argc < 1) {
|
||||
warnx("missing path to raw volume");
|
||||
zstream_usage();
|
||||
}
|
||||
|
||||
const char *raw_path = argv[0];
|
||||
int fd = open(raw_path, O_WRONLY | O_CREAT, 0666);
|
||||
if (fd < 0)
|
||||
err(EXIT_FAILURE, "error while opening file '%s'", raw_path);
|
||||
context.volume.fd = fd;
|
||||
struct stat64 st;
|
||||
if (fstat64_blk(fd, &st) < 0)
|
||||
err(EXIT_FAILURE, "fstat64_blk");
|
||||
context.volume.size = st.st_size;
|
||||
context.volume.isreg = S_ISREG(st.st_mode);
|
||||
if (!context.volume.isreg) {
|
||||
#if defined(__FreeBSD__)
|
||||
uint_t sectorsize;
|
||||
|
||||
if (ioctl(fd, DIOCGSECTORSIZE, §orsize) == 0) {
|
||||
context.volume.sectorsize = sectorsize;
|
||||
context.volume.freeop = DIOCGDELETE;
|
||||
}
|
||||
#elif defined(__linux__)
|
||||
if (ioctl(fd, BLKSSZGET, &context.volume.sectorsize) == 0) {
|
||||
/* TODO: optional BLKSECDISCARD/BLKZEROOUT */
|
||||
context.volume.freeop = BLKDISCARD;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
long iov_max = sysconf(_SC_IOV_MAX);
|
||||
long pagesize = sysconf(_SC_PAGESIZE);
|
||||
|
||||
context.limits.iov_max = iov_max;
|
||||
context.limits.buffers_max = MIN(context.limits.buffers_max, iov_max);
|
||||
context.limits.pagesize = pagesize;
|
||||
|
||||
context.buffer.iov = safe_calloc(context.limits.buffers_max *
|
||||
sizeof (struct iovec));
|
||||
|
||||
context.zeros.iov = safe_malloc(iov_max * sizeof (struct iovec));
|
||||
void *zero_page = safe_calloc(pagesize);
|
||||
for (int i = 0; i < iov_max; i++)
|
||||
context.zeros.iov[i].iov_base = zero_page;
|
||||
|
||||
uint32_t drop_mask = DROP_END | DROP_FREEOBJECTS | DROP_OBJECT_RANGE |
|
||||
DROP_REDACT | DROP_SPILL;
|
||||
zstream_chain_t raw_chain = {
|
||||
STANDARD_INPUT_STACK((argc > 1) ? argv[1] : NULL),
|
||||
serial_dump_records(),
|
||||
serial_drop_record_types(drop_mask),
|
||||
parallel_decompress_writes(NULL),
|
||||
{
|
||||
.cs_type = CS_SERIAL,
|
||||
.cs_in_size = sizeof (drr_packet_t),
|
||||
.cs_out_size = sizeof (drr_packet_t),
|
||||
.cs_context = &context,
|
||||
.cs_serial = {.process = chain_replay_raw},
|
||||
},
|
||||
NULL_OUTPUT_STACK()
|
||||
};
|
||||
zstream_chain_exec(raw_chain, &attrs);
|
||||
|
||||
buffer_finish(&context);
|
||||
free(zero_page);
|
||||
free(context.zeros.iov);
|
||||
free(context.buffer.iov);
|
||||
|
||||
if (OPTION_ENABLED(CA_VERBOSE))
|
||||
(void) printf("now at guid %llu\n",
|
||||
(u_longlong_t)context.stream.guid);
|
||||
else
|
||||
(void) printf("%llu\n", (u_longlong_t)context.stream.guid);
|
||||
|
||||
return (EXIT_SUCCESS);
|
||||
}
|
||||
@@ -1,23 +1,13 @@
|
||||
// SPDX-License-Identifier: CDDL-1.0
|
||||
/*
|
||||
* CDDL HEADER START
|
||||
* This file and its contents are supplied under the terms of the
|
||||
* Common Development and Distribution License ("CDDL"), version 1.0.
|
||||
* You may only use this file in accordance with the terms of version
|
||||
* 1.0 of the CDDL.
|
||||
*
|
||||
* The contents of this file are subject to the terms of the
|
||||
* Common Development and Distribution License (the "License").
|
||||
* You may not use this file except in compliance with the License.
|
||||
*
|
||||
* You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
|
||||
* or https://opensource.org/licenses/CDDL-1.0.
|
||||
* See the License for the specific language governing permissions
|
||||
* and limitations under the License.
|
||||
*
|
||||
* When distributing Covered Code, include this CDDL HEADER in each
|
||||
* file and include the License file at usr/src/OPENSOLARIS.LICENSE.
|
||||
* If applicable, add the following below this CDDL HEADER, with the
|
||||
* fields enclosed by brackets "[]" replaced with your own identifying
|
||||
* information: Portions Copyright [yyyy] [name of copyright owner]
|
||||
*
|
||||
* CDDL HEADER END
|
||||
* A full copy of the text of the CDDL should have accompanied this
|
||||
* source. A copy of the CDDL is also available via the Internet at
|
||||
* https://opensource.org/license/CDDL-1.0.
|
||||
*/
|
||||
|
||||
/*
|
||||
@@ -35,6 +25,7 @@
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <sys/dmu.h>
|
||||
#include <sys/zfs_ioctl.h>
|
||||
#include <sys/zio_compress.h>
|
||||
#include <sys/zstd/zstd.h>
|
||||
@@ -44,7 +35,8 @@
|
||||
#include "zstream.h"
|
||||
#include "zstream_chain.h"
|
||||
#include "zstream_modules.h"
|
||||
#include "zstream_util.h"
|
||||
#include "zstream_queue.h"
|
||||
#include "zstream_recompress.h"
|
||||
|
||||
#define MAX_COMPRESSION_STEPS 4
|
||||
|
||||
@@ -125,20 +117,22 @@ needs_decompression(drr_packet_t *item, compression_spec_t *context)
|
||||
return (needs_modification(item, context));
|
||||
}
|
||||
|
||||
static disposition_t
|
||||
chain_decompress_writes(drr_packet_t *item, compression_spec_t *context)
|
||||
/*
|
||||
* We can ignore the context here because it's already been evaluated by the
|
||||
* cost function. If the cost function returned something other than zero,
|
||||
* we have to decompress.
|
||||
*/
|
||||
static void
|
||||
chain_decompress_writes(queue_item_t *item_in, void *context)
|
||||
{
|
||||
if (item == NULL)
|
||||
return (D_OK);
|
||||
(void) context;
|
||||
drr_packet_t *item = (drr_packet_t *)item_in;
|
||||
|
||||
dmu_replay_record_t *drr = &item->dp_drr;
|
||||
struct drr_write *drrw = &drr->drr_u.drr_write;
|
||||
struct drr_write *drrw = &drr->drr_u.drr_write;
|
||||
uint8_t *debuff;
|
||||
|
||||
if (drr->drr_type != DRR_WRITE || !needs_decompression(item, context)) {
|
||||
return (D_OK);
|
||||
}
|
||||
|
||||
VERIFY3U(drr->drr_type, ==, DRR_WRITE);
|
||||
debuff = decompress_buffer(item->dp_payload, item->dp_payload_size,
|
||||
drrw->drr_logical_size, drrw->drr_compressiontype);
|
||||
if (debuff == NULL) {
|
||||
@@ -152,29 +146,30 @@ chain_decompress_writes(drr_packet_t *item, compression_spec_t *context)
|
||||
item->dp_payload_size = drrw->drr_logical_size;
|
||||
drrw->drr_compressed_size = 0;
|
||||
drrw->drr_compressiontype = 0;
|
||||
return (D_OK);
|
||||
}
|
||||
|
||||
static disposition_t
|
||||
chain_compress_writes(drr_packet_t *item, compression_spec_t *context)
|
||||
/*
|
||||
* As with chain_decompress_writes(), all the important decisions were made
|
||||
* by the cost function. If we're here, we need to compress.
|
||||
*/
|
||||
static void
|
||||
chain_compress_writes(queue_item_t *item_in, void *context_in)
|
||||
{
|
||||
if (item == NULL)
|
||||
return (D_OK);
|
||||
drr_packet_t *item = (drr_packet_t *)item_in;
|
||||
compression_spec_t *context = (compression_spec_t *)context_in;
|
||||
|
||||
dmu_replay_record_t *drr = &item->dp_drr;
|
||||
|
||||
if (drr->drr_type != DRR_WRITE || !needs_compression(item, context)) {
|
||||
return (D_OK);
|
||||
}
|
||||
|
||||
struct drr_write *drrw = &drr->drr_u.drr_write;
|
||||
struct drr_write *drrw = &drr->drr_u.drr_write;
|
||||
enum zio_compress ctype = drrw->drr_compressiontype;
|
||||
uint8_t *cbuff;
|
||||
size_t csize;
|
||||
|
||||
VERIFY3U(drr->drr_type, ==, DRR_WRITE);
|
||||
VERIFY3B(ctype_is_uncompressed(ctype), ==, B_TRUE);
|
||||
cbuff = compress_buffer(item->dp_payload, item->dp_payload_size,
|
||||
*context, &csize);
|
||||
|
||||
if (cbuff == NULL) {
|
||||
drrw->drr_compressiontype = 0;
|
||||
drrw->drr_compressed_size = 0;
|
||||
@@ -185,7 +180,51 @@ chain_compress_writes(drr_packet_t *item, compression_spec_t *context)
|
||||
drrw->drr_compressed_size = csize;
|
||||
drrw->drr_compressiontype = context->cs_type;
|
||||
}
|
||||
return (D_OK);
|
||||
}
|
||||
|
||||
/*
|
||||
* A cost of zero waives processing for the current item. If we want to
|
||||
* process it, the cost will always be item->dp_payload_size. So in these
|
||||
* two cost functions, we're mostly determining which packets need
|
||||
* attention. A packet that's already compressed with the target compression
|
||||
* profile can be ignored.
|
||||
*/
|
||||
static size_t
|
||||
chain_compress_cost(queue_item_t *item_in, void *context_in)
|
||||
{
|
||||
compression_spec_t *context = (compression_spec_t *)context_in;
|
||||
drr_packet_t *item = (drr_packet_t *)item_in;
|
||||
dmu_replay_record_t *drr = &item->dp_drr;
|
||||
|
||||
if (drr->drr_type != DRR_WRITE) {
|
||||
return (0);
|
||||
}
|
||||
struct drr_write *drrw = &drr->drr_u.drr_write;
|
||||
return (needs_compression(item, context) ? drrw->drr_logical_size : 0);
|
||||
}
|
||||
|
||||
/*
|
||||
* Don't decompress packets that aren't compressed. And don't decompress
|
||||
* them if their ultimate fate is to be recompressed using the compression
|
||||
* profile that's already in use.
|
||||
*/
|
||||
static size_t
|
||||
chain_decompress_cost(queue_item_t *item_in, void *context_in)
|
||||
{
|
||||
compression_spec_t *context = (compression_spec_t *)context_in;
|
||||
drr_packet_t *item = (drr_packet_t *)item_in;
|
||||
dmu_replay_record_t *drr = &item->dp_drr;
|
||||
|
||||
if (drr->drr_type != DRR_WRITE)
|
||||
return (0);
|
||||
|
||||
struct drr_write *drrw = &drr->drr_u.drr_write;
|
||||
enum zio_compress ctype = drrw->drr_compressiontype;
|
||||
|
||||
if (ctype_is_uncompressed(ctype))
|
||||
return (0);
|
||||
|
||||
return (needs_decompression(item, context) ? item->dp_payload_size : 0);
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -194,7 +233,7 @@ chain_compress_writes(drr_packet_t *item, compression_spec_t *context)
|
||||
* uncompressed).
|
||||
*/
|
||||
chain_step_t
|
||||
serial_decompress_writes(compression_spec_t *target)
|
||||
parallel_decompress_writes(compression_spec_t *target)
|
||||
{
|
||||
int this_spec = next_spec++ % MAX_COMPRESSION_STEPS;
|
||||
compression_spec_t *context = &specs[this_spec];
|
||||
@@ -205,34 +244,107 @@ serial_decompress_writes(compression_spec_t *target)
|
||||
*context = *target;
|
||||
}
|
||||
chain_step_t step = {
|
||||
.cs_type = CS_SERIAL,
|
||||
.cs_in_size = sizeof (drr_packet_t),
|
||||
.cs_out_size = sizeof (drr_packet_t),
|
||||
.cs_context = context,
|
||||
.cs_serial = {
|
||||
.process = (zc_serial_process_f *)chain_decompress_writes
|
||||
}
|
||||
.cs_type = CS_PARALLEL,
|
||||
.cs_in_size = sizeof (drr_packet_t),
|
||||
.cs_out_size = sizeof (drr_packet_t),
|
||||
.cs_context = context,
|
||||
.cs_parallel = {
|
||||
.queue_length = 256,
|
||||
.batch_budget = 256 * 1024,
|
||||
.process = chain_decompress_writes,
|
||||
.cost = chain_decompress_cost
|
||||
}
|
||||
};
|
||||
return (step);
|
||||
}
|
||||
|
||||
chain_step_t
|
||||
serial_compress_writes(compression_spec_t *target)
|
||||
parallel_compress_writes(compression_spec_t *target)
|
||||
{
|
||||
int this_spec = next_spec++ % MAX_COMPRESSION_STEPS;
|
||||
compression_spec_t *context = &specs[this_spec];
|
||||
|
||||
VERIFY3P(target, !=, NULL);
|
||||
*context = *target;
|
||||
|
||||
chain_step_t step = {
|
||||
.cs_type = CS_SERIAL,
|
||||
.cs_in_size = sizeof (drr_packet_t),
|
||||
.cs_out_size = sizeof (drr_packet_t),
|
||||
.cs_context = context,
|
||||
.cs_serial = {
|
||||
.process =
|
||||
(zc_serial_process_f *)chain_compress_writes
|
||||
.cs_type = CS_PARALLEL,
|
||||
.cs_in_size = sizeof (drr_packet_t),
|
||||
.cs_out_size = sizeof (drr_packet_t),
|
||||
.cs_context = context,
|
||||
.cs_parallel = {
|
||||
.queue_length = 1024,
|
||||
.batch_budget = 32 * 1024,
|
||||
.process = chain_compress_writes,
|
||||
.cost = chain_compress_cost
|
||||
}
|
||||
};
|
||||
return (step);
|
||||
}
|
||||
|
||||
/*
|
||||
* Keep DRR_BEGIN feature flags consistent with the WRITE payloads we emit.
|
||||
* Compressed WRITEs require DMU_BACKUP_FEATURE_COMPRESSED (and LZ4/ZSTD as
|
||||
* appropriate). Recompressing to off clears COMPRESSED for non-raw streams;
|
||||
* raw streams may retain encrypted compressed WRITEs that recompress skips.
|
||||
* LZ4/ZSTD are cleared only when neither EMBED_DATA nor RAW remains, since
|
||||
* recompress does not rewrite those record types.
|
||||
*/
|
||||
static disposition_t
|
||||
chain_update_compress_features(void *item_in, void *context_in)
|
||||
{
|
||||
drr_packet_t *item = (drr_packet_t *)item_in;
|
||||
compression_spec_t *spec = (compression_spec_t *)context_in;
|
||||
struct drr_begin *drrb;
|
||||
uint64_t flags;
|
||||
|
||||
if (item == NULL)
|
||||
return (D_OK);
|
||||
|
||||
if (item->dp_drr.drr_type != DRR_BEGIN)
|
||||
return (D_OK);
|
||||
|
||||
drrb = &item->dp_drr.drr_u.drr_begin;
|
||||
flags = DMU_GET_FEATUREFLAGS(drrb->drr_versioninfo);
|
||||
|
||||
if (ctype_is_uncompressed(spec->cs_type)) {
|
||||
if (!(flags & DMU_BACKUP_FEATURE_RAW))
|
||||
flags &= ~DMU_BACKUP_FEATURE_COMPRESSED;
|
||||
if ((flags & (DMU_BACKUP_FEATURE_EMBED_DATA |
|
||||
DMU_BACKUP_FEATURE_RAW)) == 0) {
|
||||
flags &= ~(DMU_BACKUP_FEATURE_LZ4 |
|
||||
DMU_BACKUP_FEATURE_ZSTD);
|
||||
}
|
||||
} else {
|
||||
flags |= DMU_BACKUP_FEATURE_COMPRESSED;
|
||||
if (spec->cs_type == ZIO_COMPRESS_ZSTD) {
|
||||
flags |= DMU_BACKUP_FEATURE_ZSTD;
|
||||
} else if (spec->cs_type >= ZIO_COMPRESS_LZ4) {
|
||||
flags |= DMU_BACKUP_FEATURE_LZ4;
|
||||
}
|
||||
}
|
||||
|
||||
DMU_SET_FEATUREFLAGS(drrb->drr_versioninfo, flags);
|
||||
return (D_OK);
|
||||
}
|
||||
|
||||
static chain_step_t
|
||||
serial_update_compress_features(compression_spec_t *target)
|
||||
{
|
||||
int this_spec = next_spec++ % MAX_COMPRESSION_STEPS;
|
||||
compression_spec_t *context = &specs[this_spec];
|
||||
|
||||
VERIFY3P(target, !=, NULL);
|
||||
*context = *target;
|
||||
|
||||
chain_step_t step = {
|
||||
.cs_type = CS_SERIAL,
|
||||
.cs_in_size = sizeof (drr_packet_t),
|
||||
.cs_out_size = sizeof (drr_packet_t),
|
||||
.cs_context = context,
|
||||
.cs_serial = {
|
||||
.process = chain_update_compress_features,
|
||||
}
|
||||
};
|
||||
return (step);
|
||||
}
|
||||
@@ -242,10 +354,11 @@ zstream_do_recompress(int argc, char *argv[])
|
||||
{
|
||||
int c;
|
||||
int level = ZIO_COMPLEVEL_DEFAULT;
|
||||
int num_threads = 0;
|
||||
|
||||
chain_attrs_t attrs = { .ca_command_opts = CA_FORBID_DEDUP };
|
||||
|
||||
while ((c = getopt(argc, argv, "l:")) != -1) {
|
||||
while ((c = getopt(argc, argv, "t:l:")) != -1) {
|
||||
switch (c) {
|
||||
case 'l':
|
||||
if (sscanf(optarg, "%d", &level) != 1) {
|
||||
@@ -253,10 +366,17 @@ zstream_do_recompress(int argc, char *argv[])
|
||||
zstream_usage();
|
||||
}
|
||||
break;
|
||||
case 't':
|
||||
if (sscanf(optarg, "%d", &num_threads) != 1) {
|
||||
warnx("failed to parse num_threads '%s'",
|
||||
optarg);
|
||||
zstream_usage();
|
||||
}
|
||||
zstream_queue_set_num_threads(num_threads);
|
||||
break;
|
||||
case '?':
|
||||
warnx("invalid option '%c'", optopt);
|
||||
zstream_usage();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -284,8 +404,9 @@ zstream_do_recompress(int argc, char *argv[])
|
||||
|
||||
zstream_chain_t recompress_chain = {
|
||||
STANDARD_INPUT_STACK(NULL),
|
||||
serial_decompress_writes(&spec),
|
||||
serial_compress_writes(&spec),
|
||||
parallel_decompress_writes(&spec),
|
||||
parallel_compress_writes(&spec),
|
||||
serial_update_compress_features(&spec),
|
||||
STANDARD_OUTPUT_STACK(NULL)
|
||||
};
|
||||
|
||||
|
||||
@@ -1,16 +1,13 @@
|
||||
// SPDX-License-Identifier: CDDL-1.0
|
||||
/*
|
||||
* CDDL HEADER START
|
||||
* This file and its contents are supplied under the terms of the
|
||||
* Common Development and Distribution License ("CDDL"), version 1.0.
|
||||
* You may only use this file in accordance with the terms of version
|
||||
* 1.0 of the CDDL.
|
||||
*
|
||||
* This file and its contents are supplied under the terms of the Common
|
||||
* Development and Distribution License ("CDDL"), version 1.0. You may only use
|
||||
* this file in accordance with the terms of version 1.0 of the CDDL.
|
||||
*
|
||||
* A full copy of the text of the CDDL should have accompanied this source. A
|
||||
* copy of the CDDL is also available via the Internet at
|
||||
* http://www.illumos.org/license/CDDL.
|
||||
*
|
||||
* CDDL HEADER END
|
||||
* A full copy of the text of the CDDL should have accompanied this
|
||||
* source. A copy of the CDDL is also available via the Internet at
|
||||
* https://opensource.org/license/CDDL-1.0.
|
||||
*/
|
||||
|
||||
/*
|
||||
@@ -28,10 +25,10 @@ extern "C" {
|
||||
#include "zstream_io.h"
|
||||
|
||||
chain_step_t
|
||||
serial_decompress_writes(compression_spec_t *target);
|
||||
parallel_decompress_writes(compression_spec_t *target);
|
||||
|
||||
chain_step_t
|
||||
serial_compress_writes(compression_spec_t *target);
|
||||
parallel_compress_writes(compression_spec_t *target);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
// SPDX-License-Identifier: CDDL-1.0
|
||||
/*
|
||||
* CDDL HEADER START
|
||||
*
|
||||
* This file and its contents are supplied under the terms of the
|
||||
* Common Development and Distribution License ("CDDL"), version 1.0.
|
||||
* You may only use this file in accordance with the terms of version
|
||||
@@ -9,9 +7,7 @@
|
||||
*
|
||||
* A full copy of the text of the CDDL should have accompanied this
|
||||
* source. A copy of the CDDL is also available via the Internet at
|
||||
* http://www.illumos.org/license/CDDL.
|
||||
*
|
||||
* CDDL HEADER END
|
||||
* https://opensource.org/license/CDDL-1.0.
|
||||
*/
|
||||
|
||||
/*
|
||||
@@ -102,8 +98,11 @@ rdt_lookup(redup_table_t *rdt,
|
||||
}
|
||||
|
||||
static disposition_t
|
||||
chain_redup_writes(drr_packet_t *item, redup_context_t *context)
|
||||
chain_redup_writes(void *item_in, void *context_in)
|
||||
{
|
||||
drr_packet_t *item = (drr_packet_t *)item_in;
|
||||
redup_context_t *context = (redup_context_t *)context_in;
|
||||
|
||||
if (item == NULL) {
|
||||
return (D_OK);
|
||||
}
|
||||
@@ -188,7 +187,7 @@ serial_redup_writes(redup_context_t *context)
|
||||
.cs_out_size = sizeof (drr_packet_t),
|
||||
.cs_context = context,
|
||||
.cs_serial = {
|
||||
.process = (zc_serial_process_f *)chain_redup_writes
|
||||
.process = chain_redup_writes
|
||||
}
|
||||
};
|
||||
return (step);
|
||||
@@ -210,7 +209,6 @@ zstream_do_redup(int argc, char *argv[])
|
||||
case '?':
|
||||
warnx("invalid option '%c'", optopt);
|
||||
zstream_usage();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,205 @@
|
||||
// SPDX-License-Identifier: CDDL-1.0
|
||||
/*
|
||||
* This file and its contents are supplied under the terms of the
|
||||
* Common Development and Distribution License ("CDDL"), version 1.0.
|
||||
* You may only use this file in accordance with the terms of version
|
||||
* 1.0 of the CDDL.
|
||||
*
|
||||
* A full copy of the text of the CDDL should have accompanied this
|
||||
* source. A copy of the CDDL is also available via the Internet at
|
||||
* https://opensource.org/license/CDDL-1.0.
|
||||
*/
|
||||
|
||||
/*
|
||||
* Copyright (c) 2026 by Garth Snyder. All rights reserved.
|
||||
*/
|
||||
|
||||
/*
|
||||
* zstream selftest: in-process unit tests for zstream's internal machinery.
|
||||
*
|
||||
* zstream selftest [-l] [-s seed] [-t nthreads] module [test ...]
|
||||
*
|
||||
* Tests are grouped into modules (see zstream_selftest.h). With no test
|
||||
* names, all of a module's tests run in order. -l lists the available
|
||||
* tests. -s replays a previous run's PRNG seed. -t sets the size of the
|
||||
* shared worker thread pool before any test runs.
|
||||
*
|
||||
* This subcommand is intentionally undocumented in zstream_usage() and the
|
||||
* man page; it exists to be driven by the ZFS test suite.
|
||||
*/
|
||||
|
||||
#include <assert.h>
|
||||
#include <err.h>
|
||||
#include <libspl.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <sys/random.h>
|
||||
#include <sys/stdtypes.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "zstream.h"
|
||||
#include "zstream_backtrace.h"
|
||||
#include "zstream_queue.h"
|
||||
#include "zstream_selftest.h"
|
||||
|
||||
typedef struct {
|
||||
const char *sm_name;
|
||||
const test_case_t *sm_cases;
|
||||
} selftest_module_t;
|
||||
|
||||
static const selftest_module_t modules[] = {
|
||||
{ "queue", selftest_queue_cases },
|
||||
};
|
||||
|
||||
#define NUM_MODULES (sizeof (modules) / sizeof (modules[0]))
|
||||
|
||||
uint64_t selftest_seed;
|
||||
|
||||
static const char *current_test = "(startup)";
|
||||
|
||||
static void
|
||||
selftest_usage(void)
|
||||
{
|
||||
(void) fprintf(stderr,
|
||||
"usage: zstream selftest [-l] [-s seed] [-t nthreads] "
|
||||
"module [test ...]\n"
|
||||
"\n"
|
||||
"\t-l list available tests\n"
|
||||
"\t-s seed seed for pseudo-random workloads (for replays)\n"
|
||||
"\t-t num size of the shared worker thread pool\n"
|
||||
"\n"
|
||||
"Available modules:");
|
||||
for (int i = 0; i < NUM_MODULES; i++)
|
||||
(void) fprintf(stderr, " %s", modules[i].sm_name);
|
||||
(void) fprintf(stderr, "\n");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
static const selftest_module_t *
|
||||
find_module(const char *name)
|
||||
{
|
||||
for (int i = 0; i < NUM_MODULES; i++) {
|
||||
if (strcmp(name, modules[i].sm_name) == 0)
|
||||
return (&modules[i]);
|
||||
}
|
||||
warnx("unknown module '%s'", name);
|
||||
selftest_usage();
|
||||
return (NULL); /* NOTREACHED */
|
||||
}
|
||||
|
||||
static void
|
||||
list_tests(const selftest_module_t *module)
|
||||
{
|
||||
for (int i = 0; i < NUM_MODULES; i++) {
|
||||
if (module != NULL && module != &modules[i])
|
||||
continue;
|
||||
(void) printf("%s:\n", modules[i].sm_name);
|
||||
for (const test_case_t *tc = modules[i].sm_cases;
|
||||
tc->tc_name != NULL; tc++) {
|
||||
(void) printf("\t%s\n", tc->tc_name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
run_case(const test_case_t *tc)
|
||||
{
|
||||
(void) printf("Running %-20s ... ", tc->tc_name);
|
||||
(void) fflush(stdout);
|
||||
current_test = tc->tc_name;
|
||||
watchdog_arm();
|
||||
tc->tc_func();
|
||||
watchdog_disarm();
|
||||
(void) printf("OK\n");
|
||||
}
|
||||
|
||||
static const test_case_t *
|
||||
find_case(const selftest_module_t *module, const char *name)
|
||||
{
|
||||
for (const test_case_t *tc = module->sm_cases;
|
||||
tc->tc_name != NULL; tc++) {
|
||||
if (strcmp(name, tc->tc_name) == 0)
|
||||
return (tc);
|
||||
}
|
||||
errx(2, "module '%s' has no test named '%s' (try -l)",
|
||||
module->sm_name, name);
|
||||
return (NULL); /* NOTREACHED */
|
||||
}
|
||||
|
||||
int
|
||||
zstream_do_selftest(int argc, char *argv[])
|
||||
{
|
||||
boolean_t list_only = B_FALSE;
|
||||
boolean_t have_seed = B_FALSE;
|
||||
int nthreads = 0;
|
||||
char *end;
|
||||
int c;
|
||||
|
||||
while ((c = getopt(argc, argv, "ls:t:")) != -1) {
|
||||
switch (c) {
|
||||
case 'l':
|
||||
list_only = B_TRUE;
|
||||
break;
|
||||
case 's':
|
||||
selftest_seed = strtoull(optarg, &end, 0);
|
||||
if (*optarg == '\0' || *end != '\0') {
|
||||
warnx("failed to parse seed '%s'", optarg);
|
||||
selftest_usage();
|
||||
}
|
||||
have_seed = B_TRUE;
|
||||
break;
|
||||
case 't':
|
||||
if (sscanf(optarg, "%d", &nthreads) != 1) {
|
||||
warnx("failed to parse num_threads '%s'",
|
||||
optarg);
|
||||
selftest_usage();
|
||||
}
|
||||
zstream_queue_set_num_threads(nthreads);
|
||||
break;
|
||||
case '?':
|
||||
warnx("invalid option '%c'", optopt);
|
||||
selftest_usage();
|
||||
break;
|
||||
}
|
||||
}
|
||||
argc -= optind;
|
||||
argv += optind;
|
||||
|
||||
if (list_only) {
|
||||
list_tests(argc > 0 ? find_module(argv[0]) : NULL);
|
||||
return (0);
|
||||
}
|
||||
if (argc < 1)
|
||||
selftest_usage();
|
||||
|
||||
const selftest_module_t *module = find_module(argv[0]);
|
||||
|
||||
/* Needed for random_get_pseudo_bytes() */
|
||||
libspl_init();
|
||||
watchdog_init();
|
||||
|
||||
if (!have_seed)
|
||||
random_get_pseudo_bytes((uint8_t *)&selftest_seed,
|
||||
sizeof (selftest_seed));
|
||||
(void) printf("Using seed 0x%016jx (replay with -s 0x%jx)\n",
|
||||
(uintmax_t)selftest_seed, (uintmax_t)selftest_seed);
|
||||
|
||||
int count = 0;
|
||||
if (argc == 1) {
|
||||
for (const test_case_t *tc = module->sm_cases;
|
||||
tc->tc_name != NULL; tc++) {
|
||||
run_case(tc);
|
||||
count++;
|
||||
}
|
||||
} else {
|
||||
for (int i = 1; i < argc; i++) {
|
||||
run_case(find_case(module, argv[i]));
|
||||
count++;
|
||||
}
|
||||
}
|
||||
(void) printf("All %d %s selftest%s passed\n", count, module->sm_name,
|
||||
count == 1 ? "" : "s");
|
||||
libspl_fini();
|
||||
return (0);
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
// SPDX-License-Identifier: CDDL-1.0
|
||||
/*
|
||||
* This file and its contents are supplied under the terms of the
|
||||
* Common Development and Distribution License ("CDDL"), version 1.0.
|
||||
* You may only use this file in accordance with the terms of version
|
||||
* 1.0 of the CDDL.
|
||||
*
|
||||
* A full copy of the text of the CDDL should have accompanied this
|
||||
* source. A copy of the CDDL is also available via the Internet at
|
||||
* https://opensource.org/license/CDDL-1.0.
|
||||
*/
|
||||
|
||||
/*
|
||||
* Copyright (c) 2026 by Garth Snyder. All rights reserved.
|
||||
*/
|
||||
|
||||
#ifndef _ZSTREAM_SELFTEST_H
|
||||
#define _ZSTREAM_SELFTEST_H
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
/*
|
||||
* Shared harness for "zstream selftest". Each module under test supplies a
|
||||
* NULL-terminated array of named test cases. The harness in
|
||||
* zstream_selftest.c handles argument parsing, test selection, seeding of
|
||||
* pseudo-random number generators, watchdog timeouts, and status output.
|
||||
*
|
||||
* Test cases report failure by exiting with a nonzero exit code. Any test
|
||||
* case that returns has passed.
|
||||
*/
|
||||
|
||||
typedef void test_function_f(void);
|
||||
|
||||
typedef struct {
|
||||
const char *tc_name;
|
||||
test_function_f *tc_func;
|
||||
} test_case_t;
|
||||
|
||||
/*
|
||||
* Modules with test cases to offer. Each array ends with a NULL tc_name.
|
||||
*/
|
||||
extern const test_case_t selftest_queue_cases[];
|
||||
|
||||
/*
|
||||
* The seed for this run, set by the harness before any test runs. Printed
|
||||
* at startup and settable with -s so failures can be replayed.
|
||||
*/
|
||||
extern uint64_t selftest_seed;
|
||||
|
||||
/*
|
||||
* A small deterministic PRNG (splitmix64). Tests derive per-thread
|
||||
* generators from selftest_seed plus a caller-chosen stream number, so
|
||||
* workloads are reproducible for a given seed regardless of scheduling.
|
||||
*/
|
||||
typedef struct {
|
||||
uint64_t sr_state;
|
||||
} selftest_rng_t;
|
||||
|
||||
static inline uint64_t
|
||||
selftest_mix64(uint64_t z)
|
||||
{
|
||||
z += 0x9e3779b97f4a7c15ULL;
|
||||
z = (z ^ (z >> 30)) * 0xbf58476d1ce4e5b9ULL;
|
||||
z = (z ^ (z >> 27)) * 0x94d049bb133111ebULL;
|
||||
return (z ^ (z >> 31));
|
||||
}
|
||||
|
||||
static inline void
|
||||
selftest_rng_init(selftest_rng_t *rng, uint64_t stream)
|
||||
{
|
||||
rng->sr_state = selftest_seed ^ selftest_mix64(stream);
|
||||
}
|
||||
|
||||
static inline uint64_t
|
||||
selftest_rng_next(selftest_rng_t *rng)
|
||||
{
|
||||
rng->sr_state += 0x9e3779b97f4a7c15ULL;
|
||||
return (selftest_mix64(rng->sr_state));
|
||||
}
|
||||
|
||||
/* Uniform value in [0, bound); returns 0 if bound is 0 */
|
||||
static inline uint64_t
|
||||
selftest_rng_below(selftest_rng_t *rng, uint64_t bound)
|
||||
{
|
||||
return (bound ? selftest_rng_next(rng) % bound : 0);
|
||||
}
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* _ZSTREAM_SELFTEST_H */
|
||||
@@ -0,0 +1,564 @@
|
||||
// SPDX-License-Identifier: CDDL-1.0
|
||||
/*
|
||||
* This file and its contents are supplied under the terms of the
|
||||
* Common Development and Distribution License ("CDDL"), version 1.0.
|
||||
* You may only use this file in accordance with the terms of version
|
||||
* 1.0 of the CDDL.
|
||||
*
|
||||
* A full copy of the text of the CDDL should have accompanied this
|
||||
* source. A copy of the CDDL is also available via the Internet at
|
||||
* https://opensource.org/license/CDDL-1.0.
|
||||
*/
|
||||
|
||||
/*
|
||||
* Copyright (c) 2026 by Garth Snyder. All rights reserved.
|
||||
*/
|
||||
|
||||
/*
|
||||
* Selftests for the zstream_queue multithreaded FIFO queue API.
|
||||
*
|
||||
* All tests are built on one generic workload runner. A workload is
|
||||
* described by a qtest_config_t: some number of producer threads each
|
||||
* enqueue a stream of self-describing items with randomized costs,
|
||||
* payloads, and processing delays, while one consumer thread per queue
|
||||
* dequeues and verifies. Several workloads can run concurrently on separate
|
||||
* queues to exercise the shared thread pool.
|
||||
*
|
||||
* Every item carries enough information to be verified independently:
|
||||
*
|
||||
* - The tuple (qi_producer, qi_seq) identifies each item; the consumer
|
||||
* checks that each producer's items arrive in the same order they
|
||||
* were enqueued.
|
||||
*
|
||||
* - qi_check is a hash of (qi_seed, qi_producer, qi_seq). The processing
|
||||
* function verifies it and then XORs in TRANSFORM_MAGIC. The consumer
|
||||
* checks that the transform happened iff cost > 0.
|
||||
*
|
||||
* - qi_pattern[] is filled from qi_check and verified both by the process
|
||||
* function and the consumer, to catch any corruption of the shallow
|
||||
* copies in and out of the ring buffer.
|
||||
*
|
||||
* - qi_process_count counts invocations of the process function, which
|
||||
* must be exactly one for cost > 0 items and zero for cost == 0 items.
|
||||
*
|
||||
* Global conservation checks: the number of items dequeued must equal the
|
||||
* number enqueued, and the total number of process-function invocations
|
||||
* must equal the number of nonzero-cost items enqueued.
|
||||
*/
|
||||
|
||||
#include <assert.h>
|
||||
#include <atomic.h>
|
||||
#include <err.h>
|
||||
#include <pthread.h>
|
||||
#include <stdalign.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "zstream_queue.h"
|
||||
#include "zstream_selftest.h"
|
||||
|
||||
#define TRANSFORM_MAGIC 0xf00dfeedbeefcafeULL
|
||||
|
||||
/*
|
||||
* Number of times per 1000 processing function invocations to use an
|
||||
* extra-long "outlier" processing delay to force overtly out-of-order
|
||||
* completion.
|
||||
*/
|
||||
#define LONG_DELAYS_PER_THOUSAND 3
|
||||
#define LONG_DELAY_MULTIPLIER 20
|
||||
|
||||
typedef struct {
|
||||
uint32_t qi_producer;
|
||||
uint32_t qi_delay_us;
|
||||
uint64_t qi_seq;
|
||||
uint64_t qi_check;
|
||||
size_t qi_cost;
|
||||
uint32_t qi_process_count;
|
||||
uint8_t qi_pattern[];
|
||||
} qtest_item_t;
|
||||
|
||||
typedef struct {
|
||||
uint32_t qc_producers; /* Number of producers */
|
||||
uint64_t qc_items; /* Items per producer */
|
||||
size_t qc_queue_length;
|
||||
size_t qc_batch_budget;
|
||||
size_t qc_pattern_len; /* Extra payload bytes */
|
||||
uint32_t qc_zero_cost_pct; /* % of items fast-tracked */
|
||||
size_t qc_max_cost; /* Nonzero costs are 1..max */
|
||||
uint32_t qc_delay_pct; /* % of items slept on */
|
||||
uint32_t qc_max_delay_us;
|
||||
uint32_t qc_producer_stall_pct; /* % chance producer naps */
|
||||
uint32_t qc_consumer_stall_pct; /* % chance consumer naps */
|
||||
uint32_t qc_stall_max_us;
|
||||
uint64_t qc_rng_stream; /* base PRNG stream number */
|
||||
} qtest_config_t;
|
||||
|
||||
typedef struct {
|
||||
const qtest_config_t *qr_cfg;
|
||||
zstream_queue_t *qr_queue;
|
||||
uint32_t qr_producers_left;
|
||||
uint64_t qr_expect_processed; /* Atomic */
|
||||
uint64_t qr_processed; /* Atomic */
|
||||
uint64_t qr_dequeued;
|
||||
} qtest_run_t;
|
||||
|
||||
typedef struct {
|
||||
qtest_run_t *qp_run;
|
||||
uint32_t qp_id;
|
||||
} qtest_producer_arg_t;
|
||||
|
||||
static uint64_t
|
||||
item_check_value(uint32_t producer, uint64_t seq)
|
||||
{
|
||||
return (selftest_mix64(selftest_seed ^
|
||||
(((uint64_t)producer << 40) + seq)));
|
||||
}
|
||||
|
||||
static void
|
||||
fill_pattern(uint8_t *pattern, size_t len, uint64_t check)
|
||||
{
|
||||
for (size_t i = 0; i < len; i++)
|
||||
pattern[i] = (uint8_t)(check >> ((i & 7) << 3)) ^ (uint8_t)i;
|
||||
}
|
||||
|
||||
static void
|
||||
verify_pattern(const uint8_t *pattern, size_t len, uint64_t check,
|
||||
const char *who)
|
||||
{
|
||||
for (size_t i = 0; i < len; i++) {
|
||||
uint8_t expect =
|
||||
(uint8_t)(check >> ((i & 7) << 3)) ^ (uint8_t)i;
|
||||
if (pattern[i] != expect) {
|
||||
errx(1, "%s: payload corrupted at byte %zu "
|
||||
"(0x%02x != 0x%02x)", who, i, pattern[i], expect);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static size_t
|
||||
qtest_cost(void *item_in, void *context)
|
||||
{
|
||||
(void) context;
|
||||
qtest_item_t *item = item_in;
|
||||
return (item->qi_cost);
|
||||
}
|
||||
|
||||
static void
|
||||
qtest_process(void *item_in, void *context)
|
||||
{
|
||||
qtest_run_t *run = context;
|
||||
qtest_item_t *item = item_in;
|
||||
|
||||
/* Cost-0 items should never reach the process function */
|
||||
VERIFY3U(item->qi_cost, >, 0);
|
||||
VERIFY3U(item->qi_check, ==,
|
||||
item_check_value(item->qi_producer, item->qi_seq));
|
||||
verify_pattern(item->qi_pattern, run->qr_cfg->qc_pattern_len,
|
||||
item->qi_check, "process");
|
||||
VERIFY3U(atomic_add_32_nv(&item->qi_process_count, 1), ==, 1);
|
||||
|
||||
if (item->qi_delay_us > 0)
|
||||
(void) usleep(item->qi_delay_us);
|
||||
|
||||
item->qi_check ^= TRANSFORM_MAGIC;
|
||||
atomic_add_64(&run->qr_processed, 1);
|
||||
}
|
||||
|
||||
/*
|
||||
* Pthreads worker function for enqueuers
|
||||
*/
|
||||
static void *
|
||||
qtest_producer(void *arg)
|
||||
{
|
||||
qtest_producer_arg_t *pa = arg;
|
||||
qtest_run_t *run = pa->qp_run;
|
||||
const qtest_config_t *cfg = run->qr_cfg;
|
||||
uint64_t local_expect = 0;
|
||||
selftest_rng_t rng;
|
||||
alignas(uint64_t) uint8_t item_buffer[sizeof (qtest_item_t) +
|
||||
cfg->qc_pattern_len];
|
||||
qtest_item_t *item = (qtest_item_t *)item_buffer;
|
||||
|
||||
selftest_rng_init(&rng, cfg->qc_rng_stream + 1000 + pa->qp_id);
|
||||
|
||||
for (uint64_t seq = 0; seq < cfg->qc_items; seq++) {
|
||||
|
||||
qtest_item_t item_xfer = {
|
||||
.qi_producer = pa->qp_id,
|
||||
.qi_seq = seq,
|
||||
.qi_process_count = 0,
|
||||
.qi_check = item_check_value(pa->qp_id, seq)
|
||||
};
|
||||
*item = item_xfer;
|
||||
fill_pattern(item->qi_pattern, cfg->qc_pattern_len,
|
||||
item->qi_check);
|
||||
|
||||
if (selftest_rng_below(&rng, 100) < cfg->qc_zero_cost_pct) {
|
||||
item->qi_cost = 0;
|
||||
} else {
|
||||
item->qi_cost =
|
||||
1 + selftest_rng_below(&rng, cfg->qc_max_cost);
|
||||
local_expect++;
|
||||
}
|
||||
|
||||
if (item->qi_cost > 0 && cfg->qc_max_delay_us > 0) {
|
||||
if (selftest_rng_below(&rng, 1000) <
|
||||
LONG_DELAYS_PER_THOUSAND) {
|
||||
item->qi_delay_us = cfg->qc_max_delay_us *
|
||||
LONG_DELAY_MULTIPLIER;
|
||||
} else if (selftest_rng_below(&rng, 100) <
|
||||
cfg->qc_delay_pct) {
|
||||
item->qi_delay_us = selftest_rng_below(&rng,
|
||||
cfg->qc_max_delay_us);
|
||||
}
|
||||
}
|
||||
|
||||
if (cfg->qc_producer_stall_pct > 0 &&
|
||||
selftest_rng_below(&rng, 100) < cfg->qc_producer_stall_pct)
|
||||
(void) usleep(selftest_rng_below(&rng,
|
||||
cfg->qc_stall_max_us));
|
||||
|
||||
zstream_enqueue(run->qr_queue, item);
|
||||
}
|
||||
|
||||
atomic_add_64(&run->qr_expect_processed, local_expect);
|
||||
if (atomic_add_32_nv(&run->qr_producers_left, -1) == 0)
|
||||
zstream_queue_fini(run->qr_queue);
|
||||
return (NULL);
|
||||
}
|
||||
|
||||
/*
|
||||
* Pthreads worker function for dequeuers
|
||||
*/
|
||||
static void *
|
||||
qtest_consumer(void *arg)
|
||||
{
|
||||
qtest_run_t *run = arg;
|
||||
const qtest_config_t *cfg = run->qr_cfg;
|
||||
selftest_rng_t rng;
|
||||
uint64_t expected_seq[cfg->qc_producers];
|
||||
alignas(uint64_t) uint8_t item_buffer[sizeof (qtest_item_t) +
|
||||
cfg->qc_pattern_len];
|
||||
qtest_item_t *item = (qtest_item_t *)item_buffer;
|
||||
|
||||
memset(expected_seq, 0, sizeof (expected_seq));
|
||||
selftest_rng_init(&rng, cfg->qc_rng_stream + 999);
|
||||
|
||||
while (zstream_dequeue(run->qr_queue, item)) {
|
||||
VERIFY3U(item->qi_producer, <, cfg->qc_producers);
|
||||
if (item->qi_seq != expected_seq[item->qi_producer]) {
|
||||
errx(1, "consumer: FIFO order violated: got "
|
||||
"producer %u seq %ju, expected seq %ju",
|
||||
item->qi_producer, (uintmax_t)item->qi_seq,
|
||||
(uintmax_t)expected_seq[item->qi_producer]);
|
||||
}
|
||||
expected_seq[item->qi_producer]++;
|
||||
|
||||
uint64_t check =
|
||||
item_check_value(item->qi_producer, item->qi_seq);
|
||||
if (item->qi_cost > 0) {
|
||||
VERIFY3U(item->qi_process_count, ==, 1);
|
||||
VERIFY3U(item->qi_check, ==, check ^ TRANSFORM_MAGIC);
|
||||
} else {
|
||||
VERIFY3U(item->qi_process_count, ==, 0);
|
||||
VERIFY3U(item->qi_check, ==, check);
|
||||
}
|
||||
verify_pattern(item->qi_pattern, cfg->qc_pattern_len, check,
|
||||
"consumer");
|
||||
run->qr_dequeued++;
|
||||
|
||||
if (cfg->qc_consumer_stall_pct > 0 &&
|
||||
selftest_rng_below(&rng, 100) < cfg->qc_consumer_stall_pct)
|
||||
(void) usleep(selftest_rng_below(&rng,
|
||||
cfg->qc_stall_max_us));
|
||||
}
|
||||
|
||||
for (uint32_t p = 0; p < cfg->qc_producers; p++)
|
||||
VERIFY3U(expected_seq[p], ==, cfg->qc_items);
|
||||
VERIFY3U(run->qr_dequeued, ==,
|
||||
(uint64_t)cfg->qc_producers * cfg->qc_items);
|
||||
|
||||
return (NULL);
|
||||
}
|
||||
|
||||
/*
|
||||
* Run several workloads at once, one queue per config, with a dedicated
|
||||
* consumer thread and qc_producers producer threads per queue. Returns
|
||||
* after every queue has been drained to end-of-stream (and therefore
|
||||
* destroyed) and all verification checks have passed.
|
||||
*/
|
||||
static void
|
||||
run_queue_workloads(const qtest_config_t *cfgs, int ncfg)
|
||||
{
|
||||
qtest_run_t runs[ncfg];
|
||||
pthread_t consumers[ncfg];
|
||||
uint32_t total_producers = 0;
|
||||
|
||||
for (int i = 0; i < ncfg; i++)
|
||||
total_producers += cfgs[i].qc_producers;
|
||||
|
||||
pthread_t producers[total_producers];
|
||||
qtest_producer_arg_t pargs[total_producers];
|
||||
memset(runs, 0, sizeof (runs));
|
||||
memset(pargs, 0, sizeof (pargs));
|
||||
|
||||
for (int i = 0; i < ncfg; i++) {
|
||||
runs[i].qr_cfg = &cfgs[i];
|
||||
runs[i].qr_producers_left = cfgs[i].qc_producers;
|
||||
zq_params_t params = {
|
||||
.qp_process = qtest_process,
|
||||
.qp_cost = qtest_cost,
|
||||
.qp_context = &runs[i],
|
||||
.qp_item_size =
|
||||
sizeof (qtest_item_t) + cfgs[i].qc_pattern_len,
|
||||
.qp_batch_budget = cfgs[i].qc_batch_budget,
|
||||
.qp_queue_length = cfgs[i].qc_queue_length,
|
||||
};
|
||||
runs[i].qr_queue = zstream_queue_create(¶ms);
|
||||
}
|
||||
|
||||
int p = 0;
|
||||
for (int i = 0; i < ncfg; i++) {
|
||||
VERIFY3S(pthread_create(&consumers[i], NULL, qtest_consumer,
|
||||
&runs[i]), ==, 0);
|
||||
for (uint32_t j = 0; j < cfgs[i].qc_producers; j++, p++) {
|
||||
pargs[p].qp_run = &runs[i];
|
||||
pargs[p].qp_id = j;
|
||||
VERIFY3S(pthread_create(&producers[p], NULL,
|
||||
qtest_producer, &pargs[p]), ==, 0);
|
||||
}
|
||||
}
|
||||
|
||||
for (uint32_t i = 0; i < total_producers; i++)
|
||||
VERIFY3S(pthread_join(producers[i], NULL), ==, 0);
|
||||
for (int i = 0; i < ncfg; i++)
|
||||
VERIFY3S(pthread_join(consumers[i], NULL), ==, 0);
|
||||
|
||||
for (int i = 0; i < ncfg; i++)
|
||||
VERIFY3U(runs[i].qr_processed, ==, runs[i].qr_expect_processed);
|
||||
}
|
||||
|
||||
static void
|
||||
run_queue_workload(const qtest_config_t *cfg)
|
||||
{
|
||||
run_queue_workloads(cfg, 1);
|
||||
}
|
||||
|
||||
/*
|
||||
* Basic single-producer smoke test: deterministic-ish costs, no delays.
|
||||
*/
|
||||
static void
|
||||
queue_basic(void)
|
||||
{
|
||||
qtest_config_t cfg = {
|
||||
.qc_producers = 1,
|
||||
.qc_items = 5000,
|
||||
.qc_queue_length = 64,
|
||||
.qc_batch_budget = 256,
|
||||
.qc_pattern_len = 32,
|
||||
.qc_zero_cost_pct = 20,
|
||||
.qc_max_cost = 64,
|
||||
};
|
||||
run_queue_workload(&cfg);
|
||||
}
|
||||
|
||||
/*
|
||||
* A long, randomized stream with heavy-tailed processing delays, a large
|
||||
* fraction of fast-tracked items, costs that exceed the batch budget, and a
|
||||
* consumer that periodically stalls so the queue backs up and enqueue
|
||||
* blocks on Q_FULL. The ring indices wrap hundreds of times.
|
||||
*/
|
||||
static void
|
||||
queue_torture(void)
|
||||
{
|
||||
qtest_config_t cfg = {
|
||||
.qc_producers = 1,
|
||||
.qc_items = 100000,
|
||||
.qc_queue_length = 512,
|
||||
.qc_batch_budget = 2048,
|
||||
.qc_pattern_len = 64,
|
||||
.qc_zero_cost_pct = 30,
|
||||
.qc_max_cost = 4096,
|
||||
.qc_delay_pct = 5,
|
||||
.qc_max_delay_us = 100,
|
||||
.qc_consumer_stall_pct = 1,
|
||||
.qc_stall_max_us = 500,
|
||||
.qc_rng_stream = 100,
|
||||
};
|
||||
run_queue_workload(&cfg);
|
||||
}
|
||||
|
||||
/*
|
||||
* Off-by-one hunting: sweep the degenerate corners of queue length,
|
||||
* batch budget, and stream length, including a zero-item stream and
|
||||
* zero-length payloads.
|
||||
*/
|
||||
static void
|
||||
queue_edge_cases(void)
|
||||
{
|
||||
static const size_t lengths[] =
|
||||
{ 1, 2, ZQ_MAX_BATCH - 1, ZQ_MAX_BATCH, ZQ_MAX_BATCH + 1, 64 };
|
||||
static const size_t budgets[] = { 0, 1, 16, SIZE_MAX / 2 };
|
||||
uint64_t stream = 200;
|
||||
|
||||
for (int l = 0; l < 6; l++) {
|
||||
for (int b = 0; b < 4; b++) {
|
||||
uint64_t counts[] = { 0, lengths[l], lengths[l] + 1,
|
||||
4 * lengths[l] + 3 };
|
||||
for (int n = 0; n < 4; n++) {
|
||||
qtest_config_t cfg = {
|
||||
.qc_producers = 1,
|
||||
.qc_items = counts[n],
|
||||
.qc_queue_length = lengths[l],
|
||||
.qc_batch_budget = budgets[b],
|
||||
.qc_pattern_len =
|
||||
(lengths[l] & 1) ? 0 : 24,
|
||||
.qc_zero_cost_pct = 25,
|
||||
.qc_max_cost = 8,
|
||||
.qc_rng_stream = stream++,
|
||||
};
|
||||
run_queue_workload(&cfg);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* All items cost 0, so every item takes the fast track and the process
|
||||
* function must never run (qtest_process VERIFYs cost > 0, and the
|
||||
* conservation check at the end of the run confirms zero invocations).
|
||||
* This exercises the completion-index sweep for items no worker ever
|
||||
* touches.
|
||||
*/
|
||||
static void
|
||||
queue_zero_cost(void)
|
||||
{
|
||||
qtest_config_t cfg = {
|
||||
.qc_producers = 1,
|
||||
.qc_items = 20000,
|
||||
.qc_queue_length = 128,
|
||||
.qc_batch_budget = 1024,
|
||||
.qc_pattern_len = 16,
|
||||
.qc_zero_cost_pct = 100,
|
||||
.qc_max_cost = 8,
|
||||
.qc_rng_stream = 300,
|
||||
};
|
||||
run_queue_workload(&cfg);
|
||||
}
|
||||
|
||||
/*
|
||||
* Eight producer threads hammering one queue with random pacing. The
|
||||
* consumer verifies per-producer FIFO order and exact counts.
|
||||
*/
|
||||
static void
|
||||
queue_multi_producer(void)
|
||||
{
|
||||
qtest_config_t cfg = {
|
||||
.qc_producers = 8,
|
||||
.qc_items = 15000,
|
||||
.qc_queue_length = 256,
|
||||
.qc_batch_budget = 512,
|
||||
.qc_pattern_len = 24,
|
||||
.qc_zero_cost_pct = 25,
|
||||
.qc_max_cost = 512,
|
||||
.qc_delay_pct = 2,
|
||||
.qc_max_delay_us = 50,
|
||||
.qc_producer_stall_pct = 1,
|
||||
.qc_stall_max_us = 200,
|
||||
.qc_rng_stream = 400,
|
||||
};
|
||||
run_queue_workload(&cfg);
|
||||
}
|
||||
|
||||
/*
|
||||
* Many dissimilar queues live at once, stressing worker scoring and
|
||||
* assignment, per-queue index isolation, and destruction of queues while
|
||||
* others remain active (which compacts the pool's queue array).
|
||||
*/
|
||||
static void
|
||||
queue_multi_queue(void)
|
||||
{
|
||||
qtest_config_t cfgs[12];
|
||||
for (int i = 0; i < 12; i++) {
|
||||
uint32_t producers = 1 + i % 3;
|
||||
qtest_config_t cfg = {
|
||||
.qc_producers = producers,
|
||||
.qc_items = 4000 / producers,
|
||||
.qc_queue_length = (size_t)4 << (i % 6),
|
||||
.qc_batch_budget =
|
||||
(i % 4 == 0) ? 0 : (size_t)64 << (i % 5),
|
||||
.qc_pattern_len = 8 * (i % 5),
|
||||
.qc_zero_cost_pct = 10 * (i % 6),
|
||||
.qc_max_cost = (size_t)16 << (i % 8),
|
||||
.qc_delay_pct = i % 3,
|
||||
.qc_max_delay_us = 60,
|
||||
.qc_rng_stream = 500 + i * 10000,
|
||||
};
|
||||
cfgs[i] = cfg;
|
||||
}
|
||||
run_queue_workloads(cfgs, 12);
|
||||
}
|
||||
|
||||
/*
|
||||
* Seeded chaos: randomize every workload parameter within sane bounds
|
||||
* and run a few rounds of concurrent queues. Whatever the targeted tests
|
||||
* miss, this net catches over many CI runs; failures replay with -s.
|
||||
*/
|
||||
static void
|
||||
queue_stress(void)
|
||||
{
|
||||
selftest_rng_t rng;
|
||||
selftest_rng_init(&rng, 900);
|
||||
|
||||
for (int iter = 0; iter < 8; iter++) {
|
||||
int nqueues = 1 + selftest_rng_below(&rng, 4);
|
||||
qtest_config_t cfgs[4];
|
||||
|
||||
for (int i = 0; i < nqueues; i++) {
|
||||
uint32_t producers = 1 + selftest_rng_below(&rng, 4);
|
||||
qtest_config_t cfg = {
|
||||
.qc_producers = producers,
|
||||
.qc_items = (2000 +
|
||||
selftest_rng_below(&rng, 8000)) /
|
||||
producers,
|
||||
.qc_queue_length = (size_t)1 <<
|
||||
selftest_rng_below(&rng, 10),
|
||||
.qc_batch_budget =
|
||||
(selftest_rng_below(&rng, 3) == 0) ? 0 :
|
||||
selftest_rng_below(&rng, 4096),
|
||||
.qc_pattern_len =
|
||||
selftest_rng_below(&rng, 64),
|
||||
.qc_zero_cost_pct =
|
||||
selftest_rng_below(&rng, 101),
|
||||
.qc_max_cost =
|
||||
1 + selftest_rng_below(&rng, 2048),
|
||||
.qc_delay_pct = selftest_rng_below(&rng, 4),
|
||||
.qc_max_delay_us =
|
||||
selftest_rng_below(&rng, 120),
|
||||
.qc_producer_stall_pct =
|
||||
selftest_rng_below(&rng, 2),
|
||||
.qc_consumer_stall_pct =
|
||||
selftest_rng_below(&rng, 2),
|
||||
.qc_stall_max_us =
|
||||
selftest_rng_below(&rng, 400),
|
||||
.qc_rng_stream = 1000000 + iter * 1000 +
|
||||
i * 100,
|
||||
};
|
||||
cfgs[i] = cfg;
|
||||
}
|
||||
run_queue_workloads(cfgs, nqueues);
|
||||
}
|
||||
}
|
||||
|
||||
const test_case_t selftest_queue_cases[] = {
|
||||
{ "queue_basic", queue_basic },
|
||||
{ "queue_edge_cases", queue_edge_cases },
|
||||
{ "queue_zero_cost", queue_zero_cost },
|
||||
{ "queue_torture", queue_torture },
|
||||
{ "queue_multi_producer", queue_multi_producer },
|
||||
{ "queue_multi_queue", queue_multi_queue },
|
||||
{ "queue_stress", queue_stress },
|
||||
{ NULL, NULL },
|
||||
};
|
||||
@@ -1,23 +1,13 @@
|
||||
// SPDX-License-Identifier: CDDL-1.0
|
||||
/*
|
||||
* CDDL HEADER START
|
||||
* This file and its contents are supplied under the terms of the
|
||||
* Common Development and Distribution License ("CDDL"), version 1.0.
|
||||
* You may only use this file in accordance with the terms of version
|
||||
* 1.0 of the CDDL.
|
||||
*
|
||||
* The contents of this file are subject to the terms of the
|
||||
* Common Development and Distribution License (the "License").
|
||||
* You may not use this file except in compliance with the License.
|
||||
*
|
||||
* You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
|
||||
* or https://opensource.org/licenses/CDDL-1.0.
|
||||
* See the License for the specific language governing permissions
|
||||
* and limitations under the License.
|
||||
*
|
||||
* When distributing Covered Code, include this CDDL HEADER in each
|
||||
* file and include the License file at usr/src/OPENSOLARIS.LICENSE.
|
||||
* If applicable, add the following below this CDDL HEADER, with the
|
||||
* fields enclosed by brackets "[]" replaced with your own identifying
|
||||
* information: Portions Copyright [yyyy] [name of copyright owner]
|
||||
*
|
||||
* CDDL HEADER END
|
||||
* A full copy of the text of the CDDL should have accompanied this
|
||||
* source. A copy of the CDDL is also available via the Internet at
|
||||
* https://opensource.org/license/CDDL-1.0.
|
||||
*/
|
||||
|
||||
/*
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
// SPDX-License-Identifier: CDDL-1.0
|
||||
/*
|
||||
* CDDL HEADER START
|
||||
*
|
||||
* This file and its contents are supplied under the terms of the
|
||||
* Common Development and Distribution License ("CDDL"), version 1.0.
|
||||
* You may only use this file in accordance with the terms of version
|
||||
@@ -9,9 +7,7 @@
|
||||
*
|
||||
* A full copy of the text of the CDDL should have accompanied this
|
||||
* source. A copy of the CDDL is also available via the Internet at
|
||||
* http://www.illumos.org/license/CDDL.
|
||||
*
|
||||
* CDDL HEADER END
|
||||
* https://opensource.org/license/CDDL-1.0.
|
||||
*/
|
||||
|
||||
/*
|
||||
@@ -31,10 +27,10 @@
|
||||
#include <assert.h>
|
||||
#include <err.h>
|
||||
#include <errno.h>
|
||||
#include <pthread.h>
|
||||
#include <stddef.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <sys/abd.h>
|
||||
#include <sys/fs/zfs.h>
|
||||
#include <sys/stdtypes.h>
|
||||
@@ -43,7 +39,6 @@
|
||||
#include <sys/zio.h>
|
||||
#include <sys/zio_compress.h>
|
||||
#include <unistd.h>
|
||||
#include <zfs_fletcher.h>
|
||||
|
||||
#include "zstream_util.h"
|
||||
|
||||
@@ -67,6 +62,54 @@ safe_calloc(size_t size)
|
||||
return (rv);
|
||||
}
|
||||
|
||||
void
|
||||
safe_pthread_sigmask(int how, const sigset_t *set, sigset_t *oldset)
|
||||
{
|
||||
int error = pthread_sigmask(how, set, oldset);
|
||||
if (error != 0) {
|
||||
errno = error;
|
||||
err(1, "pthread_sigmask failed");
|
||||
}
|
||||
}
|
||||
|
||||
pthread_t
|
||||
safe_create_thread(thread_f *body, void *body_arg, const char *name,
|
||||
boolean_t detach)
|
||||
{
|
||||
pthread_t tid;
|
||||
int ret;
|
||||
int name_attempts = 3;
|
||||
|
||||
ret = pthread_create(&tid, NULL, body, body_arg);
|
||||
if (ret != 0) {
|
||||
errno = ret;
|
||||
err(1, "pthread_create for %s failed", name);
|
||||
}
|
||||
/*
|
||||
* pthread_setname_np() fails randomly on some Debian systems
|
||||
* because of difficulty reading /proc/self/task. The characteristic
|
||||
* error is "No such file or directory." The name is just a
|
||||
* debugging aid, so we can ignore the error, but we'll make three
|
||||
* attempts to set it. This code previously printed a warning
|
||||
* message, but that interferes with zstream dump output comparisons
|
||||
* in ZTS.
|
||||
*/
|
||||
while (name_attempts-- > 0) {
|
||||
ret = pthread_setname_np(tid, name);
|
||||
if (ret == 0)
|
||||
break;
|
||||
usleep(100);
|
||||
}
|
||||
if (detach) {
|
||||
ret = pthread_detach(tid);
|
||||
if (ret != 0) {
|
||||
errno = ret;
|
||||
err(1, "failed to detach %s thread", name);
|
||||
}
|
||||
}
|
||||
return (tid);
|
||||
}
|
||||
|
||||
char *
|
||||
checksum_str(zio_cksum_t *cksum, char *buff, size_t buff_size)
|
||||
{
|
||||
|
||||
Loaded 100 of 3388 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user