2010-08-29
A Bourne shell irritation: no wildcard matching operator
Here is an irritation that gets me every so often: the Bourne shell has
no wildcard match operator that you can use in if
checks and the like.
You can do wildcard matches, but only in case
statements.
(Bash has the [[ =~ ]]
operator, but it uses regular expressions
instead of shell wildcards. I know, I pick nits, but shell wildcards are
often simpler and they match what you use in other sh contexts.)
This comes up surprisingly often, at least in the sort of shell scripts
that I write. It's not insurmountable but it is inconvenient and it
can make my shell scripts read less clearly. Later shells, such as
Plan 9's rc
, get this right and have built in wildcard matching and
non-matching operators, and I have wound up using them relatively
frequently.
(Yes, there is a workaround if you are doing this often enough.)
Of course, like a lot of things about the Bourne shell there are
historical and philosophical reasons for this. The biggest one is
a programming language design issue: you really want your wildcard
matching operator to have shell support so that you do not have to keep
quoting the wildcards themselves. Philosophically, the only good place
to put this in the Bourne shell is as part of explicit shell syntax (ie,
in a case
statement); inventing a magic operator that didn't do shell
wildcard expansion when used as if it was a command would be at least
inconsistent.
(Tom Duff was willing to be this magical when creating rc
,
fortunately. It may be inconsistent but it's very convenient.)
The difficulty is compounded because the natural place to put such an
operator is in test
, and test
started out as an external program,
not something built in to the shell. If not expanding wildcards in
something that looks like a command is odd in the Bourne shell, doing so
for some arguments to an external program is outright serious magic.
PS: expr
is not an adequate substitute for various reasons.
Sidebar: the workaround
case
conditions will do variable expansion and then, if the variable
expands to a wildcard, do wildcard matching on the result. So the simple
way around this is to define a function:
match() { case "$1" in $2) return 0;; esac return 1 }
Then you can say 'if match $var "*.c"
' and the like. If you have to
you can even write vaguely crazy things like 'if match $var "*.c" &&
[ -f $var ];
'.
How many uberblock copies ZFS writes on transaction group commits
The ZFS uberblock is the root of the tree of ZFS objects in a pool (technically it is more a pointer to the root, but it's how ZFS finds everything). Because it is the root, it has to be updated on every ZFS update. Also, because it is so important ZFS keeps quite a lot of copies of it (and has a clever 'copy on write' scheme for it, so that it is not quite updated in place).
Given the impact of writes on mirrors, it becomes interesting to ask how many copies of the uberblock ZFS actually writes out. First off, the raw numbers: there are four copies of the uberblock on every physical disk in the pool, stored as part of the four ZFS label areas (two at the start of the disk and two at the end).
At the top level, there are two cases for uberblock updates. If the vdev or pool configuration is considered 'dirty', the uberblock is synced on all vdevs. If the pool configuration is unchanged, the uberblock is synced to at most three vdevs, chosen by picking a random start point in the list of top level vdevs. If your pool only has three (or less) top level vdevs, the two cases are equivalent.
(My powers of reading the OpenSolaris source code are not quite up to determining exactly what makes the pool configuration dirty. Certainly adding or removing devices does it, and I think that devices changing their state does as well. There are likely to be other things as well.)
When ZFS syncs the uberblock to a top level vdev, it writes copies of the new uberblock to all physical devices in the vdev (well, all currently live devices); to all mirrors in a mirrored vdev, and to all disks in a raidzN vdev. Syncing the uberblock to a physical device involves four separate writes. But wait, we're not done. To actually activate the new uberblock, the ZFS labels themselves must be updated, which is done separately from writing the uberblocks. This takes another four writes for each physical disk that is having its uberblock(s) updated.
So the simple answer is that if your pool has three or less top level vdevs, you will do eight writes per physical device every time a transaction group commits in order to write the uberblocks out. Fortunately transaction groups don't commit very often.
Sidebar: the uberblock update sequence
The best documentation for the logic of the uberblock update sequence
is in vdev_config_sync()
in uts/common/fs/zfs/vdev_label.c
.
The short version is that it goes:
- update the first two labels. They now do not match any uberblock and are thus invalid, but the last two labels are still good.
- write out all of the new uberblocks. If this works, the first two labels are valid; if it doesn't work, the last two labels are still pointing to valid older uberblocks.
- update the last two labels.
(ZFS labels include what transaction group (aka 'txg') that they are valid for. They do not explicitly point to an uberblock. Uberblocks contain a transaction group number and a pointer to that txg's metaobject set (MOS), which is the real root of the ZFS pool.)