Date: Mon, 19 Jun 2006 21:57:32 +0200 (CEST) From: Ulf Lilleengen <lulf@stud.ntnu.no> To: FreeBSD-gnats-submit@FreeBSD.org Cc: le@FreeBSD.org Subject: kern/99186: [PATCH] Gvinum dumpconfig/setstate/stop implementation Message-ID: <20060619195732.4D81533C3B@studby.ntnu.no> Resent-Message-ID: <200606192000.k5JK0bop099503@freefall.freebsd.org>
next in thread | raw e-mail | index | archive | help
>Number: 99186 >Category: kern >Synopsis: [PATCH] Gvinum dumpconfig/setstate/stop implementation >Confidential: no >Severity: non-critical >Priority: medium >Responsible: freebsd-bugs >State: open >Quarter: >Keywords: >Date-Required: >Class: sw-bug >Submitter-Id: current-users >Arrival-Date: Mon Jun 19 20:00:35 GMT 2006 >Closed-Date: >Last-Modified: >Originator: Ulf Lilleengen >Release: FreeBSD 7.0-CURRENT i386 >Organization: >Environment: System: FreeBSD vimes.studby.ntnu.no 7.0-CURRENT FreeBSD 7.0-CURRENT #6: Fri Jun 2 20:19:30 CEST 2006 lulf@vimes.studby.ntnu.no:/usr/obj/usr/src/sys/VIMES i386 >Description: This patch does the following: - Add gv_set_plex_state and gv_set_vol_state to make the setstate command be able to set the states of volumes and plexes. - Add a gv_plexdown function to count number of plexes down, used in gv_set_plex_state. - Add all these functions to geom_vinum.h. - Add gv_dump_config to geom_vinum_subr.c - Add gv_read_config to geom_vinum_drive.c - Add gvinum_dumpconfig to sbin/gvinum/gvinum.c - Add gvinum dumpconfig option. - Add geom_vinum_stop.c, and add it to the build. - geom_vinum_stop.c will contain code to stop gvinum objects. - Add gv_stop_obj, for use by the gvinum stop option. - The stop option now supports stopping invidual objects. The force flag is also considered. Make gvinum.c in sbin/gvinum support this, and not unload the kernel module if arguments are given. - Add description of stop option to help. Patch AND geom_vinum_stop.c attached (Not sure if having _stop.c in a separate file, but i imagine i can put more stuff there later so). >How-To-Repeat: >Fix: --- gvinum_patch.diff begins here --- Index: sys/geom/vinum/geom_vinum.c =================================================================== RCS file: /home/ncvs/src/sys/geom/vinum/geom_vinum.c,v retrieving revision 1.21 diff -u -r1.21 geom_vinum.c --- geom_vinum.c 30 Mar 2006 14:01:25 -0000 1.21 +++ geom_vinum.c 19 Jun 2006 19:45:01 -0000 @@ -411,6 +411,9 @@ gctl_set_param(req, "config", sbuf_data(sb), sbuf_len(sb) + 1); sbuf_delete(sb); + } else if (!strcmp(verb, "dumpconfig")) { + gv_dump_config(sc, req); + } else if (!strcmp(verb, "create")) { gv_create(gp, req); @@ -432,6 +435,9 @@ } else if (!strcmp(verb, "start")) { gv_start_obj(gp, req); + } else if (!strcmp(verb, "stop")) { + gv_stop_obj(gp, req); + } else if (!strcmp(verb, "setstate")) { gv_setstate(gp, req); Index: sys/geom/vinum/geom_vinum.h =================================================================== RCS file: /home/ncvs/src/sys/geom/vinum/geom_vinum.h,v retrieving revision 1.12 diff -u -r1.12 geom_vinum.h --- geom_vinum.h 30 Mar 2006 14:01:25 -0000 1.12 +++ geom_vinum.h 19 Jun 2006 19:45:01 -0000 @@ -35,6 +35,7 @@ void gv_config_new_drive(struct gv_drive *); void gv_drive_modify(struct gv_drive *); void gv_save_config_all(struct gv_softc *); +void gv_read_config(struct gv_drive *, struct sbuf *); void gv_save_config(struct g_consumer *, struct gv_drive *, struct gv_softc *); @@ -63,13 +64,19 @@ /* geom_vinum_state.c */ int gv_sdstatemap(struct gv_plex *); +int gv_plexdown(struct gv_volume *); void gv_setstate(struct g_geom *, struct gctl_req *); int gv_set_drive_state(struct gv_drive *, int, int); int gv_set_sd_state(struct gv_sd *, int, int); +int gv_set_plex_state(struct gv_plex *, int, int); +int gv_set_vol_state(struct gv_volume *, int, int); void gv_update_sd_state(struct gv_sd *); void gv_update_plex_state(struct gv_plex *); void gv_update_vol_state(struct gv_volume *); +/* geom_vinum_stop.c */ +void gv_stop_obj(struct g_geom *, struct gctl_req *); + /* geom_vinum_subr.c */ void gv_adjust_freespace(struct gv_sd *, off_t); void gv_free_sd(struct gv_sd *); @@ -79,6 +86,7 @@ struct gv_sd *gv_find_sd(struct gv_softc *, char *); struct gv_volume *gv_find_vol(struct gv_softc *, char *); void gv_format_config(struct gv_softc *, struct sbuf *, int, char *); +void gv_dump_config(struct gv_softc *, struct gctl_req *); int gv_is_striped(struct gv_plex *); int gv_is_open(struct g_geom *); void gv_kill_drive_thread(struct gv_drive *); Index: sys/geom/vinum/geom_vinum_drive.c =================================================================== RCS file: /home/ncvs/src/sys/geom/vinum/geom_vinum_drive.c,v retrieving revision 1.25 diff -u -r1.25 geom_vinum_drive.c --- geom_vinum_drive.c 6 Jan 2006 18:03:17 -0000 1.25 +++ geom_vinum_drive.c 19 Jun 2006 19:45:01 -0000 @@ -98,6 +98,45 @@ } } +/* Read the vinum configuration from disk */ +void +gv_read_config(struct gv_drive *d, struct sbuf *sb) +{ + struct gv_plex *p; + struct gv_sd *s; + struct gv_volume *v; + struct gv_softc *vinumconf; + + KASSERT(d != NULL, ("gv_read_config: null d")); + vinumconf = d->vinumconf; + sbuf_printf(sb, "Drive %s:\tDevice /dev/%s\n", d->name, d->device); + sbuf_printf(sb, "\t\t\tSize:\t %jd bytes (%jd MB)\n", (intmax_t)d->size, + (intmax_t)d->size / MEGABYTE); + LIST_FOREACH(v, &vinumconf->volumes, volume) + sbuf_printf(sb, "volume %s state %s\n", v->name, + gv_volstate(v->state)); + LIST_FOREACH(p, &vinumconf->plexes, plex) { + sbuf_printf(sb, "plex name %s state %s org %s ", p->name, + gv_plexstate(p->state), gv_plexorg(p->org)); + if (gv_is_striped(p)) + sbuf_printf(sb, "%ds ", p->stripesize / 512); + if (p->vol_sc != NULL) + sbuf_printf(sb, "vol %s", p->volume); + } + LIST_FOREACH(s, &vinumconf->subdisks, sd) { + sbuf_printf(sb, "sd name %s drive %s len %jds " + "driveoffset %jds state %s", s->name, s->drive, + s->size / 512, s->drive_offset / 512, + gv_sdstate(s->state)); + if (s->plex_sc != NULL) + sbuf_printf(sb, " plex %s plexoffset %jds", + s->plex, s->plex_offset / 512); + sbuf_printf(sb, "\n"); + } + sbuf_printf(sb, "\nDrive /dev/%s: %jd MB (%jd bytes)\n", d->device, + (intmax_t)d->size / MEGABYTE, (intmax_t)d->size); +} + /* Save the vinum configuration back to disk. */ void gv_save_config(struct g_consumer *cp, struct gv_drive *d, struct gv_softc *sc) Index: sys/geom/vinum/geom_vinum_state.c =================================================================== RCS file: /home/ncvs/src/sys/geom/vinum/geom_vinum_state.c,v retrieving revision 1.8 diff -u -r1.8 geom_vinum_state.c --- geom_vinum_state.c 30 Mar 2006 14:01:25 -0000 1.8 +++ geom_vinum_state.c 19 Jun 2006 19:45:01 -0000 @@ -43,6 +43,8 @@ struct gv_softc *sc; struct gv_sd *s; struct gv_drive *d; + struct gv_plex *p; + struct gv_volume *v; char *obj, *state; int err, f, *flags, newstate, type; @@ -72,8 +74,27 @@ type = gv_object_type(sc, obj); switch (type) { case GV_TYPE_VOL: + newstate = gv_volstatei(state); + if (newstate < 0) { + gctl_error(req, "invalid volume state '%s'", state); + break; + } + v = gv_find_vol(sc, obj); + err = gv_set_vol_state(v, newstate, f); + if (err) + gctl_error(req, "cannot set volume state"); + break; + case GV_TYPE_PLEX: - gctl_error(req, "volume or plex state cannot be set currently"); + newstate = gv_plexstatei(state); + if (newstate < 0) { + gctl_error(req, "invalid plex state '%s'", state); + break; + } + p = gv_find_plex(sc, obj); + err = gv_set_plex_state(p, newstate, f); + if (err) + gctl_error(req, "cannot set plex state"); break; case GV_TYPE_SD: @@ -288,6 +309,81 @@ gv_update_plex_state(s->plex_sc); } +int +gv_set_plex_state(struct gv_plex *p, int newstate, int flags) +{ + struct gv_volume *v; + struct gv_sd *s; + int oldstate, plexdown; + + KASSERT(p != NULL, ("gv_set_plex_state: NULL p")); + + oldstate = p->state; + v = p->vol_sc; + plexdown = 0; + + if (newstate == oldstate) + return (0); + + switch (newstate) { + case GV_PLEX_UP: + /* Let update_plex handle if the plex can come up */ + gv_update_plex_state(p); /* XXX: Should return error if fail */ + break; + + case GV_PLEX_DOWN: + if (v != NULL) { + /* If the only one, or only one up, force is needed. */ + plexdown = gv_plexdown(v); + if (((v->plexcount == 1) || + ((v->plexcount - plexdown) == 1)) && + ((flags & GV_SETSTATE_FORCE) == 0)) + return (-1); + } + p->state = newstate; + /* Force down all subdisks. */ + LIST_FOREACH(s, &p->subdisks, sd) { + if ((s->state == GV_SD_UP || + s->state == GV_SD_REVIVING) && + s->plex_sc == p) { + gv_set_sd_state(s, GV_SD_DOWN, flags); + } + } + break; + + /* + * Added this for potential use internally as in vinum. We + * really do trust ourselves here. + */ + case GV_PLEX_DEGRADED: + p->state = newstate; + LIST_FOREACH(s, &p->subdisks, sd) { + if ((s->state == GV_SD_UP || + s->state == GV_SD_REVIVING) && + s->plex_sc == p) { + gv_set_sd_state(s, GV_SD_DOWN, flags); + } + } + break; + + case GV_PLEX_INITIALIZING: + /* XXX: As with vinum, consider what safeguards we need here */ + if ((flags & GV_SETSTATE_FORCE) == 0) + return (-1); + p->state = newstate; + break; + } + + /* Update our volume, if we have one. */ + if (v != NULL) + gv_update_vol_state(v); + + /* Save config */ + if (flags & GV_SETSTATE_CONFIG) + gv_save_config_all(v->vinumconf); + return (0); +} + /* Update the state of a plex based on its environment. */ void gv_update_plex_state(struct gv_plex *p) @@ -332,6 +428,40 @@ gv_update_vol_state(p->vol_sc); } +int +gv_set_vol_state(struct gv_volume *v, int newstate, int flags) +{ + int oldstate; + + KASSERT(v != NULL, ("gv_set_vol_state: NULL v")); + + oldstate = v->state; + + if (newstate == oldstate) + return (0); + + switch (newstate) { + case GV_VOL_UP: + /* Let update handle if the volume can come up */ + gv_update_vol_state(v); /* XXX: Should return error for this. */ + break; + case GV_VOL_DOWN: + /* + * Set state to GV_VOL_DOWN only if noone is using the volume, + * or if the state should be forced. + */ + if ((gv_is_open(v->geom) != 0) && + ((flags & GV_SETSTATE_FORCE) == 0)) + return (-1); + v->state = newstate; + break; + } + /* Save config */ + if (flags & GV_SETSTATE_CONFIG) + gv_save_config_all(v->vinumconf); + return (0); +} + /* Update the volume state based on its plexes. */ void gv_update_vol_state(struct gv_volume *v) @@ -364,6 +494,32 @@ v->state = GV_VOL_DOWN; } +/* Walk over plexes in a volume and count how many are down. */ +int +gv_plexdown(struct gv_volume *v) +{ + int plexdown; + struct gv_plex *p; + + KASSERT(v != NULL, ("gv_plexdown: NULL v")); + + plexdown = 0; + + LIST_FOREACH(p, &v->plexes, plex) { + switch (p->state) { + case GV_PLEX_DOWN: + plexdown++; + break; + case GV_PLEX_DEGRADED: + case GV_PLEX_UP: + plexdown--; + break; + } + } + + return (plexdown); +} + /* Return a state map for the subdisks of a plex. */ int gv_sdstatemap(struct gv_plex *p) Index: sys/geom/vinum/geom_vinum_subr.c =================================================================== RCS file: /home/ncvs/src/sys/geom/vinum/geom_vinum_subr.c,v retrieving revision 1.15 diff -u -r1.15 geom_vinum_subr.c --- geom_vinum_subr.c 30 Mar 2006 14:01:25 -0000 1.15 +++ geom_vinum_subr.c 19 Jun 2006 19:45:01 -0000 @@ -169,6 +169,63 @@ } } +/* Get configuration from all disks */ +void +gv_dump_config(struct gv_softc *sc, struct gctl_req *req) +{ + struct gv_drive *d; + struct sbuf *sb; + int *argc, drives, i, type; + char *object, buf[64]; + + drives = 0; + i = 0; + argc = gctl_get_paraml(req, "argc", sizeof(*argc)); + if (argc == NULL || *argc == 0) { + gctl_error(req, "No argument given"); + return; + } + + if (*argc > 1) { + /* Only if argument is specified */ + sb = sbuf_new(NULL, NULL, GV_CFG_LEN * (*argc), SBUF_FIXEDLEN); + for (i = 0; i < *argc; i++) { + snprintf(buf, sizeof(buf), "argv%d", i); + object = gctl_get_param(req, buf, NULL); + if (object == NULL) + continue; + type = gv_object_type(sc, object); + if (type == GV_TYPE_DRIVE) { + d = gv_find_drive(sc, object); + gv_read_config(d, sb); + } else { + LIST_FOREACH(d, &sc->drives, drive) { + snprintf(buf, sizeof(buf), "/dev/%s", + d->device); + if (!strcmp(d->device, object) || + !strcmp(buf, object)) { + gv_read_config(d, sb); + } + } + } + } + } else { + /* + * First count number of drives, then allocate enough space for + * them. + */ + LIST_FOREACH(d, &sc->drives, drive) + drives++; + sb = sbuf_new(NULL, NULL, GV_CFG_LEN * drives, SBUF_FIXEDLEN); + LIST_FOREACH(d, &sc->drives, drive) { + gv_read_config(d, sb); + } + } + sbuf_finish(sb); + gctl_set_param(req, "config", sbuf_data(sb), sbuf_len(sb) + 1); + sbuf_delete(sb); +} + /* * Format the vinum configuration properly. If ondisk is non-zero then the * configuration is intended to be written to disk later. Index: sys/modules/geom/geom_vinum/Makefile =================================================================== RCS file: /home/ncvs/src/sys/modules/geom/geom_vinum/Makefile,v retrieving revision 1.4 diff -u -r1.4 Makefile --- Makefile 24 Nov 2005 15:11:41 -0000 1.4 +++ Makefile 19 Jun 2006 19:47:22 -0000 @@ -6,7 +6,7 @@ SRCS= geom_vinum.c geom_vinum_drive.c geom_vinum_plex.c \ geom_vinum_volume.c geom_vinum_subr.c geom_vinum_raid5.c \ geom_vinum_share.c geom_vinum_list.c geom_vinum_rm.c \ - geom_vinum_init.c geom_vinum_state.c geom_vinum_rename.c \ - geom_vinum_move.c + geom_vinum_init.c geom_vinum_state.c geom_vinum_stop.c \ + geom_vinum_rename.c geom_vinum_move.c .include <bsd.kmod.mk> Index: sbin/gvinum/gvinum.c =================================================================== RCS file: /home/ncvs/src/sbin/gvinum/gvinum.c,v retrieving revision 1.8 diff -u -r1.8 gvinum.c --- gvinum.c 23 Mar 2006 19:58:43 -0000 1.8 +++ gvinum.c 19 Jun 2006 19:50:41 -0000 @@ -55,6 +55,7 @@ #include "gvinum.h" void gvinum_create(int, char **); +void gvinum_dumpconfig(FILE *, int, char **); void gvinum_help(void); void gvinum_list(int, char **); void gvinum_move(int, char **); @@ -323,6 +324,32 @@ gvinum_list(0, NULL); } +/* Dump config from specified disk drives */ +void +gvinum_dumpconfig(FILE *of, int argc, char **argv) +{ + char buf[(GV_CFG_LEN * 4) + 1]; + const char *errstr; + struct gctl_req *req; + int i; + + req = gctl_get_handle(); + gctl_ro_param(req, "class", -1, "VINUM"); + gctl_ro_param(req, "verb", -1, "dumpconfig"); + gctl_ro_param(req, "argc", sizeof(int), &argc); + gctl_rw_param(req, "config", sizeof(buf), buf); + + for (i = 1; i < argc; i++) { + snprintf(buf, sizeof(buf), "argv%d", i); + gctl_ro_param(req, buf, -1, argv[i]); + } + errstr = gctl_issue(req); + if (errstr != NULL) + warnx("can't dump configuration of drive(s): %s", errstr); + gctl_free(req); + fprintf(of, buf); +} + void gvinum_help(void) { @@ -331,6 +358,9 @@ " Check the parity blocks of a RAID-5 plex.\n" "create description-file\n" " Create as per description-file or open editor.\n" + "dumpconfig [drive ...]\n" + " List the configuration information stored on the\n" + " specified drives or all drives if none specified\n" "l | list [-r] [-v] [-V] [volume | plex | subdisk]\n" " List information about specified objects.\n" "ld [-r] [-v] [-V] [volume]\n" @@ -363,6 +393,9 @@ " poses only.\n" "start [-S size] volume | plex | subdisk\n" " Allow the system to access the objects.\n" + "stop [-f] volume | plex | subdisk\n" + " Terminate access to these objects, or stop gvinum if no" + " parameters are specified\n" ); return; @@ -839,20 +872,64 @@ void gvinum_stop(int argc, char **argv) { + struct gctl_req *req; + int flags, i; + const char *errstr; + char buf[30]; int fileid; - fileid = kldfind(GVINUMMOD); - if (fileid == -1) { - warn("cannot find " GVINUMMOD); - return; - } - if (kldunload(fileid) != 0) { - warn("cannot unload " GVINUMMOD); - return; - } + flags = 0; + i = 0; + req = NULL; - warnx(GVINUMMOD " unloaded"); - exit(0); + optreset = 1; + optind = 1; + + /* Do not unload the kernel module if we have arguments. */ + if (argc > 1) { + while ((i = getopt(argc, argv, "f")) != -1) { + switch(i) { + case 'f': + flags |= GV_FLAG_F; + break; + case '?': + default: + warn("invalid flag: %c", i); + return; + } + } + + argc -= optind; + argv += optind; + + req = gctl_get_handle(); + gctl_ro_param(req, "class", -1, "VINUM"); + gctl_ro_param(req, "verb", -1, "stop"); + gctl_ro_param(req, "argc", sizeof(int), &argc); + gctl_ro_param(req, "flags", sizeof(int), &flags); + for (i = 0; i < argc; i++) { + snprintf(buf, sizeof(buf), "argv%d", i); + gctl_ro_param(req, buf, -1, argv[i]); + } + errstr = gctl_issue(req); + if (errstr != NULL) + warnx("can't stop object(s): %s", errstr); + gctl_free(req); + + } else { + fileid = kldfind(GVINUMMOD); + if (fileid == -1) { + warn("cannot find " GVINUMMOD); + return; + } + if (kldunload(fileid) != 0) { + warn("cannot unload " GVINUMMOD); + return; + } + + warnx(GVINUMMOD " unloaded"); + exit(0); + } } void @@ -883,6 +960,8 @@ gvinum_move(argc, argv); else if (!strcmp(argv[0], "printconfig")) gvinum_printconfig(argc, argv); + else if (!strcmp(argv[0], "dumpconfig")) + gvinum_dumpconfig(stdout, argc, argv); else if (!strcmp(argv[0], "rename")) gvinum_rename(argc, argv); else if (!strcmp(argv[0], "resetconfig")) --- gvinum_patch.diff ends here --- --- geom_vinum_stop.c begins here --- /*- * Copyright (c) 2006 Ulf Lilleengen * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #include <sys/param.h> #include <sys/kernel.h> #include <sys/libkern.h> #include <sys/malloc.h> #include <geom/geom.h> #include <geom/vinum/geom_vinum_var.h> #include <geom/vinum/geom_vinum.h> #include <geom/vinum/geom_vinum_share.h> void gv_stop_obj(struct g_geom *gp, struct gctl_req *req) { struct gv_softc *sc; struct gv_volume *v; struct gv_plex *p; struct gv_drive *d; struct gv_sd *s; char buf[30], *object; int *argc, i, type, err, *flags, object_flags; object_flags = GV_SETSTATE_CONFIG; /* Save configuration. */ sc = gp->softc; argc = gctl_get_paraml(req, "argc", sizeof(*argc)); if (argc == NULL || *argc == 0) { gctl_error(req, "No arguments given"); return; } /* Check if we have the -f flag. */ flags = gctl_get_paraml(req, "flags", sizeof(*flags)); if (*flags & GV_FLAG_F) object_flags |= GV_SETSTATE_FORCE; for (i = 0; i < *argc; i++) { snprintf(buf, sizeof(buf), "argv%d", i); object = gctl_get_param(req, buf, NULL); if (object == NULL) continue; type = gv_object_type(sc, object); switch (type) { case GV_TYPE_VOL: v = gv_find_vol(sc, object); err = gv_set_vol_state(v, GV_VOL_DOWN, object_flags); if (err) { gctl_error(req, "cannot stop volume " "'%s'", object); return; } break; case GV_TYPE_PLEX: p = gv_find_plex(sc, object); err = gv_set_plex_state(p, GV_PLEX_DOWN, object_flags); if (err) { gctl_error(req, "cannot stop plex " "'%s'", object); return; } break; case GV_TYPE_SD: s = gv_find_sd(sc, object); err = gv_set_sd_state(s, GV_SD_DOWN, object_flags); if (err) { gctl_error(req, "cannot stop sd " "'%s'", object); return; } break; case GV_TYPE_DRIVE: d = gv_find_drive(sc, object); err = gv_set_drive_state(d, GV_DRIVE_DOWN, object_flags); if (err) { gctl_error(req, "cannot stop drive " "'%s'", object); return; } break; default: gctl_error(req, "unknown object '%s'", object); break; } } } --- geom_vinum_stop.c ends here --- >Release-Note: >Audit-Trail: >Unformatted:
Want to link to this message? Use this URL: <https://mail-archive.FreeBSD.org/cgi/mid.cgi?20060619195732.4D81533C3B>