Wandering Thoughts archives

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 ];'.

programming/BourneNoMatchOperator written at 23:20:14; Add Comment

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.)

solaris/ZFSUberblockWrites written at 01:55:34; Add Comment


Page tools: See As Normal.
Search:
Login: Password:
Atom Syndication: Recent Pages, Recent Comments.

This dinky wiki is brought to you by the Insane Hackers Guild, Python sub-branch.