15e96a66cSDavid du Colombier #include "stdinc.h"
25e96a66cSDavid du Colombier #include "dat.h"
35e96a66cSDavid du Colombier #include "fns.h"
45e96a66cSDavid du Colombier #include "error.h"
55e96a66cSDavid du Colombier
65e96a66cSDavid du Colombier /*
75e96a66cSDavid du Colombier * Lock watcher. Check that locking of blocks is always down.
85e96a66cSDavid du Colombier *
95e96a66cSDavid du Colombier * This is REALLY slow, and it won't work when the blocks aren't
105e96a66cSDavid du Colombier * arranged in a tree (e.g., after the first snapshot). But it's great
115e96a66cSDavid du Colombier * for debugging.
125e96a66cSDavid du Colombier */
135e96a66cSDavid du Colombier enum
145e96a66cSDavid du Colombier {
155e96a66cSDavid du Colombier MaxLock = 16,
165e96a66cSDavid du Colombier HashSize = 1009,
175e96a66cSDavid du Colombier };
185e96a66cSDavid du Colombier
195e96a66cSDavid du Colombier /*
205e96a66cSDavid du Colombier * Thread-specific watch state.
215e96a66cSDavid du Colombier */
225e96a66cSDavid du Colombier typedef struct WThread WThread;
235e96a66cSDavid du Colombier struct WThread
245e96a66cSDavid du Colombier {
255e96a66cSDavid du Colombier Block *b[MaxLock]; /* blocks currently held */
265e96a66cSDavid du Colombier uint nb;
275e96a66cSDavid du Colombier uint pid;
285e96a66cSDavid du Colombier };
295e96a66cSDavid du Colombier
305e96a66cSDavid du Colombier typedef struct WMap WMap;
315e96a66cSDavid du Colombier typedef struct WEntry WEntry;
325e96a66cSDavid du Colombier
335e96a66cSDavid du Colombier struct WEntry
345e96a66cSDavid du Colombier {
355e96a66cSDavid du Colombier uchar c[VtScoreSize];
365e96a66cSDavid du Colombier uchar p[VtScoreSize];
375e96a66cSDavid du Colombier int off;
385e96a66cSDavid du Colombier
395e96a66cSDavid du Colombier WEntry *cprev;
405e96a66cSDavid du Colombier WEntry *cnext;
415e96a66cSDavid du Colombier WEntry *pprev;
425e96a66cSDavid du Colombier WEntry *pnext;
435e96a66cSDavid du Colombier };
445e96a66cSDavid du Colombier
455e96a66cSDavid du Colombier struct WMap
465e96a66cSDavid du Colombier {
47*d7aba6c3SDavid du Colombier QLock lk;
485e96a66cSDavid du Colombier
495e96a66cSDavid du Colombier WEntry *hchild[HashSize];
505e96a66cSDavid du Colombier WEntry *hparent[HashSize];
515e96a66cSDavid du Colombier };
525e96a66cSDavid du Colombier
535e96a66cSDavid du Colombier static WMap map;
545e96a66cSDavid du Colombier static void **wp;
555e96a66cSDavid du Colombier static uint blockSize;
565e96a66cSDavid du Colombier static WEntry *pool;
575e96a66cSDavid du Colombier uint bwatchDisabled;
585e96a66cSDavid du Colombier
595e96a66cSDavid du Colombier static uint
hash(uchar score[VtScoreSize])605e96a66cSDavid du Colombier hash(uchar score[VtScoreSize])
615e96a66cSDavid du Colombier {
625e96a66cSDavid du Colombier uint i, h;
635e96a66cSDavid du Colombier
645e96a66cSDavid du Colombier h = 0;
655e96a66cSDavid du Colombier for(i=0; i<VtScoreSize; i++)
665e96a66cSDavid du Colombier h = h*37 + score[i];
675e96a66cSDavid du Colombier return h%HashSize;
685e96a66cSDavid du Colombier }
695e96a66cSDavid du Colombier
705e96a66cSDavid du Colombier #include <pool.h>
715e96a66cSDavid du Colombier static void
freeWEntry(WEntry * e)725e96a66cSDavid du Colombier freeWEntry(WEntry *e)
735e96a66cSDavid du Colombier {
745e96a66cSDavid du Colombier memset(e, 0, sizeof(WEntry));
755e96a66cSDavid du Colombier e->pnext = pool;
765e96a66cSDavid du Colombier pool = e;
775e96a66cSDavid du Colombier }
785e96a66cSDavid du Colombier
795e96a66cSDavid du Colombier static WEntry*
allocWEntry(void)805e96a66cSDavid du Colombier allocWEntry(void)
815e96a66cSDavid du Colombier {
825e96a66cSDavid du Colombier int i;
835e96a66cSDavid du Colombier WEntry *w;
845e96a66cSDavid du Colombier
855e96a66cSDavid du Colombier w = pool;
865e96a66cSDavid du Colombier if(w == nil){
87*d7aba6c3SDavid du Colombier w = vtmallocz(1024*sizeof(WEntry));
885e96a66cSDavid du Colombier for(i=0; i<1024; i++)
895e96a66cSDavid du Colombier freeWEntry(&w[i]);
905e96a66cSDavid du Colombier w = pool;
915e96a66cSDavid du Colombier }
925e96a66cSDavid du Colombier pool = w->pnext;
935e96a66cSDavid du Colombier memset(w, 0, sizeof(WEntry));
945e96a66cSDavid du Colombier return w;
955e96a66cSDavid du Colombier }
965e96a66cSDavid du Colombier
975e96a66cSDavid du Colombier /*
985e96a66cSDavid du Colombier * remove all dependencies with score as a parent
995e96a66cSDavid du Colombier */
1005e96a66cSDavid du Colombier static void
_bwatchResetParent(uchar * score)1015e96a66cSDavid du Colombier _bwatchResetParent(uchar *score)
1025e96a66cSDavid du Colombier {
1035e96a66cSDavid du Colombier WEntry *w, *next;
1045e96a66cSDavid du Colombier uint h;
1055e96a66cSDavid du Colombier
1065e96a66cSDavid du Colombier h = hash(score);
1075e96a66cSDavid du Colombier for(w=map.hparent[h]; w; w=next){
1085e96a66cSDavid du Colombier next = w->pnext;
1095e96a66cSDavid du Colombier if(memcmp(w->p, score, VtScoreSize) == 0){
1105e96a66cSDavid du Colombier if(w->pnext)
1115e96a66cSDavid du Colombier w->pnext->pprev = w->pprev;
1125e96a66cSDavid du Colombier if(w->pprev)
1135e96a66cSDavid du Colombier w->pprev->pnext = w->pnext;
1145e96a66cSDavid du Colombier else
1155e96a66cSDavid du Colombier map.hparent[h] = w->pnext;
1165e96a66cSDavid du Colombier if(w->cnext)
1175e96a66cSDavid du Colombier w->cnext->cprev = w->cprev;
1185e96a66cSDavid du Colombier if(w->cprev)
1195e96a66cSDavid du Colombier w->cprev->cnext = w->cnext;
1205e96a66cSDavid du Colombier else
1215e96a66cSDavid du Colombier map.hchild[hash(w->c)] = w->cnext;
1225e96a66cSDavid du Colombier freeWEntry(w);
1235e96a66cSDavid du Colombier }
1245e96a66cSDavid du Colombier }
1255e96a66cSDavid du Colombier }
1265e96a66cSDavid du Colombier /*
1275e96a66cSDavid du Colombier * and child
1285e96a66cSDavid du Colombier */
1295e96a66cSDavid du Colombier static void
_bwatchResetChild(uchar * score)1305e96a66cSDavid du Colombier _bwatchResetChild(uchar *score)
1315e96a66cSDavid du Colombier {
1325e96a66cSDavid du Colombier WEntry *w, *next;
1335e96a66cSDavid du Colombier uint h;
1345e96a66cSDavid du Colombier
1355e96a66cSDavid du Colombier h = hash(score);
1365e96a66cSDavid du Colombier for(w=map.hchild[h]; w; w=next){
1375e96a66cSDavid du Colombier next = w->cnext;
1385e96a66cSDavid du Colombier if(memcmp(w->c, score, VtScoreSize) == 0){
1395e96a66cSDavid du Colombier if(w->pnext)
1405e96a66cSDavid du Colombier w->pnext->pprev = w->pprev;
1415e96a66cSDavid du Colombier if(w->pprev)
1425e96a66cSDavid du Colombier w->pprev->pnext = w->pnext;
1435e96a66cSDavid du Colombier else
1445e96a66cSDavid du Colombier map.hparent[hash(w->p)] = w->pnext;
1455e96a66cSDavid du Colombier if(w->cnext)
1465e96a66cSDavid du Colombier w->cnext->cprev = w->cprev;
1475e96a66cSDavid du Colombier if(w->cprev)
1485e96a66cSDavid du Colombier w->cprev->cnext = w->cnext;
1495e96a66cSDavid du Colombier else
1505e96a66cSDavid du Colombier map.hchild[h] = w->cnext;
1515e96a66cSDavid du Colombier freeWEntry(w);
1525e96a66cSDavid du Colombier }
1535e96a66cSDavid du Colombier }
1545e96a66cSDavid du Colombier }
1555e96a66cSDavid du Colombier
1565e96a66cSDavid du Colombier static uchar*
parent(uchar c[VtScoreSize],int * off)1575e96a66cSDavid du Colombier parent(uchar c[VtScoreSize], int *off)
1585e96a66cSDavid du Colombier {
1595e96a66cSDavid du Colombier WEntry *w;
1605e96a66cSDavid du Colombier uint h;
1615e96a66cSDavid du Colombier
1625e96a66cSDavid du Colombier h = hash(c);
1635e96a66cSDavid du Colombier for(w=map.hchild[h]; w; w=w->cnext)
1645e96a66cSDavid du Colombier if(memcmp(w->c, c, VtScoreSize) == 0){
1655e96a66cSDavid du Colombier *off = w->off;
1665e96a66cSDavid du Colombier return w->p;
1675e96a66cSDavid du Colombier }
1685e96a66cSDavid du Colombier return nil;
1695e96a66cSDavid du Colombier }
1705e96a66cSDavid du Colombier
1715e96a66cSDavid du Colombier static void
addChild(uchar p[VtEntrySize],uchar c[VtEntrySize],int off)1725e96a66cSDavid du Colombier addChild(uchar p[VtEntrySize], uchar c[VtEntrySize], int off)
1735e96a66cSDavid du Colombier {
1745e96a66cSDavid du Colombier uint h;
1755e96a66cSDavid du Colombier WEntry *w;
1765e96a66cSDavid du Colombier
1775e96a66cSDavid du Colombier w = allocWEntry();
1785e96a66cSDavid du Colombier memmove(w->p, p, VtScoreSize);
1795e96a66cSDavid du Colombier memmove(w->c, c, VtScoreSize);
1805e96a66cSDavid du Colombier w->off = off;
1815e96a66cSDavid du Colombier
1825e96a66cSDavid du Colombier h = hash(p);
1835e96a66cSDavid du Colombier w->pnext = map.hparent[h];
1845e96a66cSDavid du Colombier if(w->pnext)
1855e96a66cSDavid du Colombier w->pnext->pprev = w;
1865e96a66cSDavid du Colombier map.hparent[h] = w;
1875e96a66cSDavid du Colombier
1885e96a66cSDavid du Colombier h = hash(c);
1895e96a66cSDavid du Colombier w->cnext = map.hchild[h];
1905e96a66cSDavid du Colombier if(w->cnext)
1915e96a66cSDavid du Colombier w->cnext->cprev = w;
1925e96a66cSDavid du Colombier map.hchild[h] = w;
1935e96a66cSDavid du Colombier }
1945e96a66cSDavid du Colombier
1955e96a66cSDavid du Colombier void
bwatchReset(uchar score[VtScoreSize])1965e96a66cSDavid du Colombier bwatchReset(uchar score[VtScoreSize])
1975e96a66cSDavid du Colombier {
198*d7aba6c3SDavid du Colombier qlock(&map.lk);
1995e96a66cSDavid du Colombier _bwatchResetParent(score);
2005e96a66cSDavid du Colombier _bwatchResetChild(score);
201*d7aba6c3SDavid du Colombier qunlock(&map.lk);
2025e96a66cSDavid du Colombier }
2035e96a66cSDavid du Colombier
2045e96a66cSDavid du Colombier void
bwatchInit(void)2055e96a66cSDavid du Colombier bwatchInit(void)
2065e96a66cSDavid du Colombier {
2075e96a66cSDavid du Colombier wp = privalloc();
2085e96a66cSDavid du Colombier *wp = nil;
2095e96a66cSDavid du Colombier }
2105e96a66cSDavid du Colombier
2115e96a66cSDavid du Colombier void
bwatchSetBlockSize(uint bs)2125e96a66cSDavid du Colombier bwatchSetBlockSize(uint bs)
2135e96a66cSDavid du Colombier {
2145e96a66cSDavid du Colombier blockSize = bs;
2155e96a66cSDavid du Colombier }
2165e96a66cSDavid du Colombier
2175e96a66cSDavid du Colombier static WThread*
getWThread(void)2185e96a66cSDavid du Colombier getWThread(void)
2195e96a66cSDavid du Colombier {
2205e96a66cSDavid du Colombier WThread *w;
2215e96a66cSDavid du Colombier
2225e96a66cSDavid du Colombier w = *wp;
2235e96a66cSDavid du Colombier if(w == nil || w->pid != getpid()){
224*d7aba6c3SDavid du Colombier w = vtmallocz(sizeof(WThread));
2255e96a66cSDavid du Colombier *wp = w;
2265e96a66cSDavid du Colombier w->pid = getpid();
2275e96a66cSDavid du Colombier }
2285e96a66cSDavid du Colombier return w;
2295e96a66cSDavid du Colombier }
2305e96a66cSDavid du Colombier
2315e96a66cSDavid du Colombier /*
2325e96a66cSDavid du Colombier * Derive dependencies from the contents of b.
2335e96a66cSDavid du Colombier */
2345e96a66cSDavid du Colombier void
bwatchDependency(Block * b)2355e96a66cSDavid du Colombier bwatchDependency(Block *b)
2365e96a66cSDavid du Colombier {
2375e96a66cSDavid du Colombier int i, epb, ppb;
2385e96a66cSDavid du Colombier Entry e;
2395e96a66cSDavid du Colombier
2405e96a66cSDavid du Colombier if(bwatchDisabled)
2415e96a66cSDavid du Colombier return;
2425e96a66cSDavid du Colombier
243*d7aba6c3SDavid du Colombier qlock(&map.lk);
2445e96a66cSDavid du Colombier _bwatchResetParent(b->score);
2455e96a66cSDavid du Colombier
2465e96a66cSDavid du Colombier switch(b->l.type){
2475e96a66cSDavid du Colombier case BtData:
2485e96a66cSDavid du Colombier break;
2495e96a66cSDavid du Colombier
2505e96a66cSDavid du Colombier case BtDir:
2515e96a66cSDavid du Colombier epb = blockSize / VtEntrySize;
2525e96a66cSDavid du Colombier for(i=0; i<epb; i++){
2535e96a66cSDavid du Colombier entryUnpack(&e, b->data, i);
2545e96a66cSDavid du Colombier if(!(e.flags & VtEntryActive))
2555e96a66cSDavid du Colombier continue;
2565e96a66cSDavid du Colombier addChild(b->score, e.score, i);
2575e96a66cSDavid du Colombier }
2585e96a66cSDavid du Colombier break;
2595e96a66cSDavid du Colombier
2605e96a66cSDavid du Colombier default:
2615e96a66cSDavid du Colombier ppb = blockSize / VtScoreSize;
2625e96a66cSDavid du Colombier for(i=0; i<ppb; i++)
2635e96a66cSDavid du Colombier addChild(b->score, b->data+i*VtScoreSize, i);
2645e96a66cSDavid du Colombier break;
2655e96a66cSDavid du Colombier }
266*d7aba6c3SDavid du Colombier qunlock(&map.lk);
2675e96a66cSDavid du Colombier }
2685e96a66cSDavid du Colombier
2695e96a66cSDavid du Colombier static int
depth(uchar * s)2705e96a66cSDavid du Colombier depth(uchar *s)
2715e96a66cSDavid du Colombier {
2725e96a66cSDavid du Colombier int d, x;
2735e96a66cSDavid du Colombier
2745e96a66cSDavid du Colombier d = -1;
2755e96a66cSDavid du Colombier while(s){
2765e96a66cSDavid du Colombier d++;
2775e96a66cSDavid du Colombier s = parent(s, &x);
2785e96a66cSDavid du Colombier }
2795e96a66cSDavid du Colombier return d;
2805e96a66cSDavid du Colombier }
2815e96a66cSDavid du Colombier
2825e96a66cSDavid du Colombier static int
lockConflicts(uchar xhave[VtScoreSize],uchar xwant[VtScoreSize])2835e96a66cSDavid du Colombier lockConflicts(uchar xhave[VtScoreSize], uchar xwant[VtScoreSize])
2845e96a66cSDavid du Colombier {
2855e96a66cSDavid du Colombier uchar *have, *want;
2865e96a66cSDavid du Colombier int havedepth, wantdepth, havepos, wantpos;
2875e96a66cSDavid du Colombier
2885e96a66cSDavid du Colombier have = xhave;
2895e96a66cSDavid du Colombier want = xwant;
2905e96a66cSDavid du Colombier
2915e96a66cSDavid du Colombier havedepth = depth(have);
2925e96a66cSDavid du Colombier wantdepth = depth(want);
2935e96a66cSDavid du Colombier
2945e96a66cSDavid du Colombier /*
2955e96a66cSDavid du Colombier * walk one or the other up until they're both
2965e96a66cSDavid du Colombier * at the same level.
2975e96a66cSDavid du Colombier */
2985e96a66cSDavid du Colombier havepos = -1;
2995e96a66cSDavid du Colombier wantpos = -1;
3005e96a66cSDavid du Colombier have = xhave;
3015e96a66cSDavid du Colombier want = xwant;
3025e96a66cSDavid du Colombier while(wantdepth > havedepth){
3035e96a66cSDavid du Colombier wantdepth--;
3045e96a66cSDavid du Colombier want = parent(want, &wantpos);
3055e96a66cSDavid du Colombier }
3065e96a66cSDavid du Colombier while(havedepth > wantdepth){
3075e96a66cSDavid du Colombier havedepth--;
3085e96a66cSDavid du Colombier have = parent(have, &havepos);
3095e96a66cSDavid du Colombier }
3105e96a66cSDavid du Colombier
3115e96a66cSDavid du Colombier /*
3125e96a66cSDavid du Colombier * walk them up simultaneously until we reach
3135e96a66cSDavid du Colombier * a common ancestor.
3145e96a66cSDavid du Colombier */
3155e96a66cSDavid du Colombier while(have && want && memcmp(have, want, VtScoreSize) != 0){
3165e96a66cSDavid du Colombier have = parent(have, &havepos);
3175e96a66cSDavid du Colombier want = parent(want, &wantpos);
3185e96a66cSDavid du Colombier }
3195e96a66cSDavid du Colombier
3205e96a66cSDavid du Colombier /*
3215e96a66cSDavid du Colombier * not part of same tree. happens mainly with
3225e96a66cSDavid du Colombier * newly allocated blocks.
3235e96a66cSDavid du Colombier */
3245e96a66cSDavid du Colombier if(!have || !want)
3255e96a66cSDavid du Colombier return 0;
3265e96a66cSDavid du Colombier
3275e96a66cSDavid du Colombier /*
3285e96a66cSDavid du Colombier * never walked want: means we want to lock
3295e96a66cSDavid du Colombier * an ancestor of have. no no.
3305e96a66cSDavid du Colombier */
3315e96a66cSDavid du Colombier if(wantpos == -1)
3325e96a66cSDavid du Colombier return 1;
3335e96a66cSDavid du Colombier
3345e96a66cSDavid du Colombier /*
3355e96a66cSDavid du Colombier * never walked have: means we want to lock a
3365e96a66cSDavid du Colombier * child of have. that's okay.
3375e96a66cSDavid du Colombier */
3385e96a66cSDavid du Colombier if(havepos == -1)
3395e96a66cSDavid du Colombier return 0;
3405e96a66cSDavid du Colombier
3415e96a66cSDavid du Colombier /*
3425e96a66cSDavid du Colombier * walked both: they're from different places in the tree.
3435e96a66cSDavid du Colombier * require that the left one be locked before the right one.
3445e96a66cSDavid du Colombier * (this is questionable, but it puts a total order on the block tree).
3455e96a66cSDavid du Colombier */
3465e96a66cSDavid du Colombier return havepos < wantpos;
3475e96a66cSDavid du Colombier }
3485e96a66cSDavid du Colombier
3495e96a66cSDavid du Colombier static void
stop(void)3505e96a66cSDavid du Colombier stop(void)
3515e96a66cSDavid du Colombier {
3525e96a66cSDavid du Colombier int fd;
3535e96a66cSDavid du Colombier char buf[32];
3545e96a66cSDavid du Colombier
3555e96a66cSDavid du Colombier snprint(buf, sizeof buf, "#p/%d/ctl", getpid());
3565e96a66cSDavid du Colombier fd = open(buf, OWRITE);
3575e96a66cSDavid du Colombier write(fd, "stop", 4);
3585e96a66cSDavid du Colombier close(fd);
3595e96a66cSDavid du Colombier }
3605e96a66cSDavid du Colombier
3615e96a66cSDavid du Colombier /*
3625e96a66cSDavid du Colombier * Check whether the calling thread can validly lock b.
3635e96a66cSDavid du Colombier * That is, check that the calling thread doesn't hold
3645e96a66cSDavid du Colombier * locks for any of b's children.
3655e96a66cSDavid du Colombier */
3665e96a66cSDavid du Colombier void
bwatchLock(Block * b)3675e96a66cSDavid du Colombier bwatchLock(Block *b)
3685e96a66cSDavid du Colombier {
3695e96a66cSDavid du Colombier int i;
3705e96a66cSDavid du Colombier WThread *w;
3715e96a66cSDavid du Colombier
3725e96a66cSDavid du Colombier if(bwatchDisabled)
3735e96a66cSDavid du Colombier return;
3745e96a66cSDavid du Colombier
3755e96a66cSDavid du Colombier if(b->part != PartData)
3765e96a66cSDavid du Colombier return;
3775e96a66cSDavid du Colombier
378*d7aba6c3SDavid du Colombier qlock(&map.lk);
3795e96a66cSDavid du Colombier w = getWThread();
3805e96a66cSDavid du Colombier for(i=0; i<w->nb; i++){
3815e96a66cSDavid du Colombier if(lockConflicts(w->b[i]->score, b->score)){
3825e96a66cSDavid du Colombier fprint(2, "%d: have block %V; shouldn't lock %V\n",
3835e96a66cSDavid du Colombier w->pid, w->b[i]->score, b->score);
3845e96a66cSDavid du Colombier stop();
3855e96a66cSDavid du Colombier }
3865e96a66cSDavid du Colombier }
387*d7aba6c3SDavid du Colombier qunlock(&map.lk);
3885e96a66cSDavid du Colombier if(w->nb >= MaxLock){
3895e96a66cSDavid du Colombier fprint(2, "%d: too many blocks held\n", w->pid);
3905e96a66cSDavid du Colombier stop();
3915e96a66cSDavid du Colombier }else
3925e96a66cSDavid du Colombier w->b[w->nb++] = b;
3935e96a66cSDavid du Colombier }
3945e96a66cSDavid du Colombier
3955e96a66cSDavid du Colombier /*
3965e96a66cSDavid du Colombier * Note that the calling thread is about to unlock b.
3975e96a66cSDavid du Colombier */
3985e96a66cSDavid du Colombier void
bwatchUnlock(Block * b)3995e96a66cSDavid du Colombier bwatchUnlock(Block *b)
4005e96a66cSDavid du Colombier {
4015e96a66cSDavid du Colombier int i;
4025e96a66cSDavid du Colombier WThread *w;
4035e96a66cSDavid du Colombier
4045e96a66cSDavid du Colombier if(bwatchDisabled)
4055e96a66cSDavid du Colombier return;
4065e96a66cSDavid du Colombier
4075e96a66cSDavid du Colombier if(b->part != PartData)
4085e96a66cSDavid du Colombier return;
4095e96a66cSDavid du Colombier
4105e96a66cSDavid du Colombier w = getWThread();
4115e96a66cSDavid du Colombier for(i=0; i<w->nb; i++)
4125e96a66cSDavid du Colombier if(w->b[i] == b)
4135e96a66cSDavid du Colombier break;
4145e96a66cSDavid du Colombier if(i>=w->nb){
4155e96a66cSDavid du Colombier fprint(2, "%d: unlock of unlocked block %V\n", w->pid, b->score);
4165e96a66cSDavid du Colombier stop();
4175e96a66cSDavid du Colombier }else
4185e96a66cSDavid du Colombier w->b[i] = w->b[--w->nb];
4195e96a66cSDavid du Colombier }
4205e96a66cSDavid du Colombier
421