Date: Wed, 8 Aug 2007 10:33:32 GMT From: Ulf Lilleengen <lulf@FreeBSD.org> To: Perforce Change Reviews <perforce@FreeBSD.org> Subject: PERFORCE change 124889 for review Message-ID: <200708081033.l78AXWFj030804@repoman.freebsd.org>
next in thread | raw e-mail | index | archive | help
http://perforce.freebsd.org/chv.cgi?CH=124889 Change 124889 by lulf@lulf_carrot on 2007/08/08 10:32:43 - Add possibility to wait for events to be completed. This means that certain functions like mirror, concat or raid5 can make sure that all object-creating events are processed. Since they sends more than one request down to the kernel, they are dependent on this. And, we don't have to have ugly sleep-hacks. Affected files ... .. //depot/projects/soc2007/lulf/gvinum_fixup/sbin/gvinum/gvinum.c#18 edit .. //depot/projects/soc2007/lulf/gvinum_fixup/sys/geom/vinum/geom_vinum.c#33 edit .. //depot/projects/soc2007/lulf/gvinum_fixup/sys/geom/vinum/geom_vinum.h#26 edit .. //depot/projects/soc2007/lulf/gvinum_fixup/sys/geom/vinum/geom_vinum_create.c#8 edit .. //depot/projects/soc2007/lulf/gvinum_fixup/sys/geom/vinum/geom_vinum_events.c#12 edit .. //depot/projects/soc2007/lulf/gvinum_fixup/sys/geom/vinum/geom_vinum_init.c#21 edit .. //depot/projects/soc2007/lulf/gvinum_fixup/sys/geom/vinum/geom_vinum_move.c#6 edit .. //depot/projects/soc2007/lulf/gvinum_fixup/sys/geom/vinum/geom_vinum_rename.c#5 edit .. //depot/projects/soc2007/lulf/gvinum_fixup/sys/geom/vinum/geom_vinum_rm.c#14 edit .. //depot/projects/soc2007/lulf/gvinum_fixup/sys/geom/vinum/geom_vinum_state.c#21 edit .. //depot/projects/soc2007/lulf/gvinum_main/sys/geom/vinum/geom_vinum_events.c#4 edit Differences ... ==== //depot/projects/soc2007/lulf/gvinum_fixup/sbin/gvinum/gvinum.c#18 (text+ko) ==== @@ -432,14 +432,8 @@ gctl_ro_param(req, "plexes", sizeof(int), &plexes); gctl_ro_param(req, "subdisks", sizeof(int), &subdisks); errstr = gctl_issue(req); - if (errstr != NULL) { + if (errstr != NULL) warnx("error creating drive: %s", errstr); - gctl_free(req); - free(drivename); - usleep(100000); /* sleep for 0.1s. */ - printf("retrying...\n"); - return (create_drive(device)); - } gctl_free(req); return (drivename); } ==== //depot/projects/soc2007/lulf/gvinum_fixup/sys/geom/vinum/geom_vinum.c#33 (text+ko) ==== @@ -75,7 +75,7 @@ g_trace(G_T_TOPOLOGY, "gv_orphan(%s)", gp->name); - gv_post_event(sc, GV_EVENT_DRIVE_LOST, d, NULL, 0, 0); + gv_post_event(sc, GV_EVENT_DRIVE_LOST, d, NULL, 0, 0, 0); } void @@ -188,7 +188,7 @@ sc = gp->softc; if (sc != NULL) { - gv_post_event(sc, GV_EVENT_THREAD_EXIT, NULL, NULL, 0, 0); + gv_post_event(sc, GV_EVENT_THREAD_EXIT, NULL, NULL, 0, 0, 0); gp->softc = NULL; g_wither_geom(gp, ENXIO); return (EAGAIN); @@ -240,7 +240,8 @@ } v = gv_find_vol(sc, parent); p = gv_find_plex(sc, child); - gv_post_event(sc, GV_EVENT_ATTACH_PLEX, p, v, *offset, *rename); + gv_post_event(sc, GV_EVENT_ATTACH_PLEX, p, v, *offset, *rename, + 0); break; case GV_TYPE_SD: if (type_parent != GV_TYPE_PLEX) { @@ -253,7 +254,7 @@ break; } s = gv_find_sd(sc, child); - gv_post_event(sc, GV_EVENT_ATTACH_SD, s, p, *offset, *rename); + gv_post_event(sc, GV_EVENT_ATTACH_SD, s, p, *offset, *rename, 0); break; default: gctl_error(req, "invalid child type"); @@ -281,11 +282,11 @@ switch (type) { case GV_TYPE_PLEX: p = gv_find_plex(sc, object); - gv_post_event(sc, GV_EVENT_DETACH_PLEX, p, NULL, *flags, 0); + gv_post_event(sc, GV_EVENT_DETACH_PLEX, p, NULL, *flags, 0, 0); break; case GV_TYPE_SD: s = gv_find_sd(sc, object); - gv_post_event(sc, GV_EVENT_DETACH_SD, s, NULL, *flags, 0); + gv_post_event(sc, GV_EVENT_DETACH_SD, s, NULL, *flags, 0, 0); break; default: gctl_error(req, "invalid object type"); @@ -338,7 +339,7 @@ d = g_malloc(sizeof(*d), M_WAITOK | M_ZERO); bcopy(d2, d, sizeof(*d)); - gv_post_event(sc, GV_EVENT_CREATE_DRIVE, d, NULL, 0, 0); + gv_post_event(sc, GV_EVENT_CREATE_DRIVE, d, NULL, 0, 0, 0); } /* ... then volume definitions ... */ @@ -355,7 +356,7 @@ v = g_malloc(sizeof(*v), M_WAITOK | M_ZERO); bcopy(v2, v, sizeof(*v)); - gv_post_event(sc, GV_EVENT_CREATE_VOLUME, v, NULL, 0, 0); + gv_post_event(sc, GV_EVENT_CREATE_VOLUME, v, NULL, 0, 0, 0); } /* ... then plex definitions ... */ @@ -372,7 +373,7 @@ p = g_malloc(sizeof(*p), M_WAITOK | M_ZERO); bcopy(p2, p, sizeof(*p)); - gv_post_event(sc, GV_EVENT_CREATE_PLEX, p, NULL, 0, 0); + gv_post_event(sc, GV_EVENT_CREATE_PLEX, p, NULL, 0, 0, 0); } /* ... and, finally, subdisk definitions. */ @@ -389,12 +390,12 @@ s = g_malloc(sizeof(*s), M_WAITOK | M_ZERO); bcopy(s2, s, sizeof(*s)); - gv_post_event(sc, GV_EVENT_CREATE_SD, s, NULL, 0, 0); + gv_post_event(sc, GV_EVENT_CREATE_SD, s, NULL, 0, 0, 0); } error: - gv_post_event(sc, GV_EVENT_SETUP_OBJECTS, sc, NULL, 0, 0); - gv_post_event(sc, GV_EVENT_SAVE_CONFIG, sc, NULL, 0, 0); + gv_post_event(sc, GV_EVENT_SETUP_OBJECTS, sc, NULL, 0, 0, 0); + gv_post_event(sc, GV_EVENT_SAVE_CONFIG, sc, NULL, 0, 0, 1); return (0); } @@ -426,7 +427,7 @@ /* Save our configuration back to disk. */ } else if (!strcmp(verb, "saveconfig")) { - gv_post_event(sc, GV_EVENT_SAVE_CONFIG, sc, NULL, 0, 0); + gv_post_event(sc, GV_EVENT_SAVE_CONFIG, sc, NULL, 0, 0, 0); /* Return configuration in string form. */ } else if (!strcmp(verb, "getconfig")) { @@ -461,7 +462,7 @@ gv_rename(gp, req); } else if (!strcmp(verb, "resetconfig")) { - gv_post_event(sc, GV_EVENT_RESET_CONFIG, sc, NULL, 0, 0); + gv_post_event(sc, GV_EVENT_RESET_CONFIG, sc, NULL, 0, 0, 0); } else if (!strcmp(verb, "start")) { gv_start_obj(gp, req); @@ -522,9 +523,9 @@ /* XXX: The state of the plex might have changed when this event is * picked up ... We should perhaps check this afterwards. */ if (*rebuild) - gv_post_event(sc, GV_EVENT_PARITY_REBUILD, p, NULL, 0, 0); + gv_post_event(sc, GV_EVENT_PARITY_REBUILD, p, NULL, 0, 0, 0); else - gv_post_event(sc, GV_EVENT_PARITY_CHECK, p, NULL, 0, 0); + gv_post_event(sc, GV_EVENT_PARITY_CHECK, p, NULL, 0, 0, 0); } @@ -571,7 +572,7 @@ if (vhdr != NULL) { if (vhdr->magic == GV_MAGIC) gv_post_event(sc, GV_EVENT_DRIVE_TASTED, pp, NULL, 0, - 0); + 0, 0); g_free(vhdr); } @@ -898,6 +899,7 @@ printf("VINUM: unknown event %d\n", ev->type); } + wakeup(ev); g_free(ev); mtx_lock(&sc->queue_mtx); ==== //depot/projects/soc2007/lulf/gvinum_fixup/sys/geom/vinum/geom_vinum.h#26 (text+ko) ==== @@ -108,7 +108,7 @@ void gv_worker(void *); void gv_post_event(struct gv_softc *, int, void *, void *, intmax_t, - intmax_t); + intmax_t, int); void gv_drive_tasted(struct gv_softc *, struct g_provider *); void gv_drive_lost(struct gv_softc *, struct gv_drive *); void gv_setup_objects(struct gv_softc *); ==== //depot/projects/soc2007/lulf/gvinum_fixup/sys/geom/vinum/geom_vinum_create.c#8 (text+ko) ==== @@ -324,7 +324,7 @@ v = g_malloc(sizeof(*v), M_WAITOK | M_ZERO); strlcpy(v->name, vol, GV_MAXVOLNAME); v->state = GV_VOL_UP; - gv_post_event(sc, GV_EVENT_CREATE_VOLUME, v, NULL, 0, 0); + gv_post_event(sc, GV_EVENT_CREATE_VOLUME, v, NULL, 0, 0, 0); /* Then we create the plex. */ p = g_malloc(sizeof(*p), M_WAITOK | M_ZERO); @@ -332,7 +332,7 @@ strlcpy(p->volume, v->name, GV_MAXVOLNAME); p->org = GV_PLEX_CONCAT; p->stripesize = 0; - gv_post_event(sc, GV_EVENT_CREATE_PLEX, p, NULL, 0, 0); + gv_post_event(sc, GV_EVENT_CREATE_PLEX, p, NULL, 0, 0, 0); /* Drives are first (right now) priority */ for (dcount = 0; dcount < *drives; dcount++) { @@ -350,10 +350,10 @@ s->plex_offset = -1; s->drive_offset = -1; s->size = -1; - gv_post_event(sc, GV_EVENT_CREATE_SD, s, NULL, 0, 0); + gv_post_event(sc, GV_EVENT_CREATE_SD, s, NULL, 0, 0, 0); } - gv_post_event(sc, GV_EVENT_SETUP_OBJECTS, sc, NULL, 0, 0); - gv_post_event(sc, GV_EVENT_SAVE_CONFIG, sc, NULL, 0, 0); + gv_post_event(sc, GV_EVENT_SETUP_OBJECTS, sc, NULL, 0, 0, 0); + gv_post_event(sc, GV_EVENT_SAVE_CONFIG, sc, NULL, 0, 0, 1); } /* @@ -402,7 +402,7 @@ v = g_malloc(sizeof(*v), M_WAITOK | M_ZERO); strlcpy(v->name, vol, GV_MAXVOLNAME); v->state = GV_VOL_UP; - gv_post_event(sc, GV_EVENT_CREATE_VOLUME, v, NULL, 0, 0); + gv_post_event(sc, GV_EVENT_CREATE_VOLUME, v, NULL, 0, 0, 0); /* Then we create the plexes. */ for (pcount = 0; pcount < 2; pcount++) { @@ -417,7 +417,7 @@ p->org = GV_PLEX_CONCAT; p->stripesize = -1; } - gv_post_event(sc, GV_EVENT_CREATE_PLEX, p, NULL, 0, 0); + gv_post_event(sc, GV_EVENT_CREATE_PLEX, p, NULL, 0, 0, 0); /* We just gives each even drive to plex one, and each odd to * plex two. */ @@ -440,12 +440,12 @@ s->plex_offset = -1; s->drive_offset = -1; s->size = -1; - gv_post_event(sc, GV_EVENT_CREATE_SD, s, NULL, 0, 0); + gv_post_event(sc, GV_EVENT_CREATE_SD, s, NULL, 0, 0, 0); scount++; } } - gv_post_event(sc, GV_EVENT_SETUP_OBJECTS, sc, NULL, 0, 0); - gv_post_event(sc, GV_EVENT_SAVE_CONFIG, sc, NULL, 0, 0); + gv_post_event(sc, GV_EVENT_SETUP_OBJECTS, sc, NULL, 0, 0, 0); + gv_post_event(sc, GV_EVENT_SAVE_CONFIG, sc, NULL, 0, 0, 1); } void @@ -492,7 +492,7 @@ v = g_malloc(sizeof(*v), M_WAITOK | M_ZERO); strlcpy(v->name, vol, GV_MAXVOLNAME); v->state = GV_VOL_UP; - gv_post_event(sc, GV_EVENT_CREATE_VOLUME, v, NULL, 0, 0); + gv_post_event(sc, GV_EVENT_CREATE_VOLUME, v, NULL, 0, 0, 0); /* Then we create the plex. */ p = g_malloc(sizeof(*p), M_WAITOK | M_ZERO); @@ -500,7 +500,7 @@ strlcpy(p->volume, v->name, GV_MAXVOLNAME); p->org = GV_PLEX_RAID5; p->stripesize = *stripesize; - gv_post_event(sc, GV_EVENT_CREATE_PLEX, p, NULL, 0, 0); + gv_post_event(sc, GV_EVENT_CREATE_PLEX, p, NULL, 0, 0, 0); /* Create subdisks on drives. */ for (dcount = 0; dcount < *drives; dcount++) { @@ -518,10 +518,10 @@ s->plex_offset = -1; s->drive_offset = -1; s->size = -1; - gv_post_event(sc, GV_EVENT_CREATE_SD, s, NULL, 0, 0); + gv_post_event(sc, GV_EVENT_CREATE_SD, s, NULL, 0, 0, 0); } - gv_post_event(sc, GV_EVENT_SETUP_OBJECTS, sc, NULL, 0, 0); - gv_post_event(sc, GV_EVENT_SAVE_CONFIG, sc, NULL, 0, 0); + gv_post_event(sc, GV_EVENT_SETUP_OBJECTS, sc, NULL, 0, 0, 0); + gv_post_event(sc, GV_EVENT_SAVE_CONFIG, sc, NULL, 0, 0, 1); } /* @@ -564,7 +564,7 @@ v = g_malloc(sizeof(*v), M_WAITOK | M_ZERO); strlcpy(v->name, vol, GV_MAXVOLNAME); v->state = GV_VOL_UP; - gv_post_event(sc, GV_EVENT_CREATE_VOLUME, v, NULL, 0, 0); + gv_post_event(sc, GV_EVENT_CREATE_VOLUME, v, NULL, 0, 0, 0); /* Then we create the plex. */ p = g_malloc(sizeof(*p), M_WAITOK | M_ZERO); @@ -572,7 +572,7 @@ strlcpy(p->volume, v->name, GV_MAXVOLNAME); p->org = GV_PLEX_STRIPED; p->stripesize = 262144; - gv_post_event(sc, GV_EVENT_CREATE_PLEX, p, NULL, 0, 0); + gv_post_event(sc, GV_EVENT_CREATE_PLEX, p, NULL, 0, 0, 0); /* Create subdisks on drives. */ for (dcount = 0; dcount < *drives; dcount++) { @@ -590,8 +590,8 @@ s->plex_offset = -1; s->drive_offset = -1; s->size = -1; - gv_post_event(sc, GV_EVENT_CREATE_SD, s, NULL, 0, 0); + gv_post_event(sc, GV_EVENT_CREATE_SD, s, NULL, 0, 0, 0); } - gv_post_event(sc, GV_EVENT_SETUP_OBJECTS, sc, NULL, 0, 0); - gv_post_event(sc, GV_EVENT_SAVE_CONFIG, sc, NULL, 0, 0); + gv_post_event(sc, GV_EVENT_SETUP_OBJECTS, sc, NULL, 0, 0, 0); + gv_post_event(sc, GV_EVENT_SAVE_CONFIG, sc, NULL, 0, 0, 1); } ==== //depot/projects/soc2007/lulf/gvinum_fixup/sys/geom/vinum/geom_vinum_events.c#12 (text+ko) ==== @@ -29,8 +29,10 @@ __FBSDID("$FreeBSD$"); #include <sys/param.h> +#include <sys/kernel.h> #include <sys/lock.h> #include <sys/malloc.h> +#include <sys/mutex.h> #include <sys/systm.h> #include <geom/geom.h> @@ -39,7 +41,7 @@ void gv_post_event(struct gv_softc *sc, int event, void *arg1, void *arg2, - intmax_t arg3, intmax_t arg4) + intmax_t arg3, intmax_t arg4, int wait) { struct gv_event *ev; @@ -53,6 +55,8 @@ mtx_lock(&sc->queue_mtx); TAILQ_INSERT_TAIL(&sc->equeue, ev, events); wakeup(sc); + if (wait) + msleep(ev, &sc->queue_mtx, 0, "gv_post_event", hz / 4); mtx_unlock(&sc->queue_mtx); } @@ -169,7 +173,7 @@ if (cp->nstart != cp->nend) { printf("VINUM: dead drive '%s' has still active " "requests, can't detach consumer\n", d->name); - gv_post_event(sc, GV_EVENT_DRIVE_LOST, d, NULL, 0, 0); + gv_post_event(sc, GV_EVENT_DRIVE_LOST, d, NULL, 0, 0, 0); return; } g_topology_lock(); ==== //depot/projects/soc2007/lulf/gvinum_fixup/sys/geom/vinum/geom_vinum_init.c#21 (text+ko) ==== @@ -72,14 +72,14 @@ v = gv_find_vol(sc, argv); if (v != NULL) gv_post_event(sc, GV_EVENT_START_VOLUME, v, - NULL, *initsize, 0); + NULL, *initsize, 0, 0); break; case GV_TYPE_PLEX: p = gv_find_plex(sc, argv); if (p != NULL) gv_post_event(sc, GV_EVENT_START_PLEX, p, NULL, - *initsize, 0); + *initsize, 0, 0); break; case GV_TYPE_SD: ==== //depot/projects/soc2007/lulf/gvinum_fixup/sys/geom/vinum/geom_vinum_move.c#6 (text+ko) ==== @@ -85,7 +85,7 @@ gctl_error(req, "unknown subdisk '%s'", object); return; } - gv_post_event(sc, GV_EVENT_MOVE_SD, s, d, *flags, 0); + gv_post_event(sc, GV_EVENT_MOVE_SD, s, d, *flags, 0, 0); } } ==== //depot/projects/soc2007/lulf/gvinum_fixup/sys/geom/vinum/geom_vinum_rename.c#5 (text+ko) ==== @@ -77,7 +77,7 @@ } name = g_malloc(GV_MAXVOLNAME, M_WAITOK | M_ZERO); strlcpy(name, newname, GV_MAXVOLNAME); - gv_post_event(sc, GV_EVENT_RENAME_VOL, v, name, *flags, 0); + gv_post_event(sc, GV_EVENT_RENAME_VOL, v, name, *flags, 0, 0); break; case GV_TYPE_PLEX: p = gv_find_plex(sc, object); @@ -87,7 +87,7 @@ } name = g_malloc(GV_MAXPLEXNAME, M_WAITOK | M_ZERO); strlcpy(name, newname, GV_MAXPLEXNAME); - gv_post_event(sc, GV_EVENT_RENAME_PLEX, p, name, *flags, 0); + gv_post_event(sc, GV_EVENT_RENAME_PLEX, p, name, *flags, 0, 0); break; case GV_TYPE_SD: s = gv_find_sd(sc, object); @@ -97,7 +97,7 @@ } name = g_malloc(GV_MAXSDNAME, M_WAITOK | M_ZERO); strlcpy(name, newname, GV_MAXSDNAME); - gv_post_event(sc, GV_EVENT_RENAME_SD, s, name, *flags, 0); + gv_post_event(sc, GV_EVENT_RENAME_SD, s, name, *flags, 0, 0); break; case GV_TYPE_DRIVE: d = gv_find_drive(sc, object); @@ -107,7 +107,7 @@ } name = g_malloc(GV_MAXDRIVENAME, M_WAITOK | M_ZERO); strlcpy(name, newname, GV_MAXDRIVENAME); - gv_post_event(sc, GV_EVENT_RENAME_DRIVE, d, name, *flags, 0); + gv_post_event(sc, GV_EVENT_RENAME_DRIVE, d, name, *flags, 0, 0); break; default: gctl_error(req, "unknown object '%s'", object); ==== //depot/projects/soc2007/lulf/gvinum_fixup/sys/geom/vinum/geom_vinum_rm.c#14 (text+ko) ==== @@ -81,7 +81,7 @@ return; } - gv_post_event(sc, GV_EVENT_RM_VOLUME, v, NULL, 0, 0); + gv_post_event(sc, GV_EVENT_RM_VOLUME, v, NULL, 0, 0, 0); break; case GV_TYPE_PLEX: @@ -106,7 +106,7 @@ return; } - gv_post_event(sc, GV_EVENT_RM_PLEX, p, NULL, 0, 0); + gv_post_event(sc, GV_EVENT_RM_PLEX, p, NULL, 0, 0, 0); break; case GV_TYPE_SD: @@ -119,7 +119,7 @@ return; } - gv_post_event(sc, GV_EVENT_RM_SD, s, NULL, 0, 0); + gv_post_event(sc, GV_EVENT_RM_SD, s, NULL, 0, 0, 0); break; case GV_TYPE_DRIVE: @@ -138,7 +138,8 @@ return; }*/ - gv_post_event(sc, GV_EVENT_RM_DRIVE, d, NULL, *flags, 0); + gv_post_event(sc, GV_EVENT_RM_DRIVE, d, NULL, *flags, 0, + 0); break; default: @@ -147,7 +148,7 @@ } } - gv_post_event(sc, GV_EVENT_SAVE_CONFIG, sc, NULL, 0, 0); + gv_post_event(sc, GV_EVENT_SAVE_CONFIG, sc, NULL, 0, 0, 0); } /* Resets configuration */ @@ -175,7 +176,7 @@ LIST_FOREACH_SAFE(v, &sc->volumes, volume, v2) gv_rm_vol(sc, v); - gv_post_event(sc, GV_EVENT_SAVE_CONFIG, sc, NULL, 0, 0); + gv_post_event(sc, GV_EVENT_SAVE_CONFIG, sc, NULL, 0, 0, 0); return (0); } ==== //depot/projects/soc2007/lulf/gvinum_fixup/sys/geom/vinum/geom_vinum_state.c#21 (text+ko) ==== @@ -78,7 +78,7 @@ } v = gv_find_vol(sc, obj); gv_post_event(sc, GV_EVENT_SET_VOL_STATE, v, NULL, - gv_volstatei(state), f); + gv_volstatei(state), f, 0); break; case GV_TYPE_PLEX: @@ -88,7 +88,7 @@ } p = gv_find_plex(sc, obj); gv_post_event(sc, GV_EVENT_SET_PLEX_STATE, p, NULL, - gv_plexstatei(state), f); + gv_plexstatei(state), f, 0); break; case GV_TYPE_SD: @@ -98,7 +98,7 @@ } s = gv_find_sd(sc, obj); gv_post_event(sc, GV_EVENT_SET_SD_STATE, s, NULL, - gv_sdstatei(state), f); + gv_sdstatei(state), f, 0); break; case GV_TYPE_DRIVE: @@ -108,7 +108,7 @@ } d = gv_find_drive(sc, obj); gv_post_event(sc, GV_EVENT_SET_DRIVE_STATE, d, NULL, - gv_drivestatei(state), f); + gv_drivestatei(state), f, 0); break; default: ==== //depot/projects/soc2007/lulf/gvinum_main/sys/geom/vinum/geom_vinum_events.c#4 (text+ko) ==== @@ -64,9 +64,11 @@ struct gv_hdr *hdr; struct gv_drive *d; char *buf; + int updatesd; hdr = NULL; buf = NULL; + updatesd = 0; printf("DEBUG: tasted drive on '%s'\n", pp->name); @@ -107,6 +109,7 @@ } else if (d->flags & GV_DRIVE_REFERENCED) { strncpy(d->device, pp->name, GV_MAXDRIVENAME); d->flags &= ~GV_DRIVE_REFERENCED; + updatesd = 1; } else { printf("DEBUG: drive '%s' is already known\n", d->name); g_free(hdr);
Want to link to this message? Use this URL: <https://mail-archive.FreeBSD.org/cgi/mid.cgi?200708081033.l78AXWFj030804>