Create a new GEOM utility, gunion(8).

The gunion(8) utility is used to track changes to a read-only disk on
a writable disk. Logically, a writable disk is placed over a read-only
disk. Write requests are intercepted and stored on the writable
disk. Read requests are first checked to see if they have been
written on the top (writable disk) and if found are returned. If
they have not been written on the top disk, then they are read from
the lower disk.

The gunion(8) utility can be especially useful if you have a large
disk with a corrupted filesystem that you are unsure of how to
repair. You can use gunion(8) to place another disk over the corrupted
disk and then attempt to repair the filesystem. If the repair fails,
you can revert all the changes in the upper disk and be back to the
unchanged state of the lower disk thus allowing you to try another
approach to repairing it. If the repair is successful you can commit
all the writes recorded on the top disk to the lower disk.

Another use of the gunion(8) utility is to try out upgrades to your
system. Place the upper disk over the disk holding your filesystem
that is to be upgraded and then run the upgrade on it. If it works,
commit it; if it fails, revert the upgrade.

Further details can be found in the gunion(8) manual page.

Reviewed by: Chuck Silvers, kib (earlier version)
tested by:   Peter Holm
Differential Revision: https://reviews.freebsd.org/D32697
This commit is contained in:
Kirk McKusick 2022-02-28 16:36:08 -08:00
parent 2062ce996d
commit c7996ddf80
13 changed files with 1981 additions and 2 deletions

View File

@ -182,6 +182,8 @@
..
stripe
..
union
..
virstor
..
..

View File

@ -51,7 +51,7 @@ LSUBDIRS= dev/acpica dev/agp dev/ciss dev/filemon dev/firewire \
fs/procfs fs/smbfs fs/udf fs/unionfs \
geom/cache geom/concat geom/eli geom/gate geom/journal geom/label \
geom/mirror geom/mountver geom/multipath geom/nop \
geom/raid geom/raid3 geom/shsec geom/stripe geom/virstor \
geom/raid geom/raid3 geom/shsec geom/stripe geom/union geom/virstor \
net/altq \
net/route \
netgraph/atm netgraph/netflow \

View File

@ -22,4 +22,5 @@ GEOM_CLASSES+= raid
GEOM_CLASSES+= raid3
GEOM_CLASSES+= shsec
GEOM_CLASSES+= stripe
GEOM_CLASSES+= union
GEOM_CLASSES+= virstor

8
lib/geom/union/Makefile Normal file
View File

@ -0,0 +1,8 @@
# $FreeBSD$
PACKAGE=runtime
.PATH: ${.CURDIR:H:H}/misc
GEOM_CLASS= union
.include <bsd.lib.mk>

View File

@ -0,0 +1,19 @@
# $FreeBSD$
# Autogenerated - do NOT edit!
DIRDEPS = \
gnu/lib/csu \
include \
include/xlocale \
lib/${CSU_DIR} \
lib/libc \
lib/libcompiler_rt \
lib/libgeom \
sbin/geom/core \
.include <dirdeps.mk>
.if ${DEP_RELDIR} == ${_DEP_RELDIR}
# local dependencies - needed for -jN in clean tree
.endif

View File

@ -0,0 +1,83 @@
/*-
* SPDX-License-Identifier: BSD-2-Clause-FreeBSD
*
* Copyright (c) 2022 Marshall Kirk McKusick <mckusick@mckusick.com>
*
* 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 AUTHORS 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 AUTHORS 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 <stdio.h>
#include <stdint.h>
#include <libgeom.h>
#include <geom/union/g_union.h>
#include "core/geom.h"
uint32_t lib_version = G_LIB_VERSION;
uint32_t version = G_UNION_VERSION;
struct g_command class_commands[] = {
{ "create", G_FLAG_LOADKLD, NULL,
{
{ 'o', "offset", "0", G_TYPE_NUMBER },
{ 's', "size", "0", G_TYPE_NUMBER },
{ 'S', "secsize", "0", G_TYPE_NUMBER },
{ 'v', "verbose", NULL, G_TYPE_BOOL },
{ 'Z', "gunionname", G_VAL_OPTIONAL, G_TYPE_STRING },
G_OPT_SENTINEL
},
"[-v] [-o offset] [-s size] [-S secsize] [-Z gunionname] "
"upperdev lowerdev"
},
{ "destroy", 0, NULL,
{
{ 'f', "force", NULL, G_TYPE_BOOL },
{ 'v', "verbose", NULL, G_TYPE_BOOL },
G_OPT_SENTINEL
},
"[-fv] prov ..."
},
{ "reset", 0, NULL,
{
{ 'v', "verbose", NULL, G_TYPE_BOOL },
G_OPT_SENTINEL
},
"[-v] prov ..."
},
{ "commit", 0, NULL,
{
{ 'f', "force", NULL, G_TYPE_BOOL },
{ 'r', "reboot", NULL, G_TYPE_BOOL },
{ 'v', "verbose", NULL, G_TYPE_BOOL },
G_OPT_SENTINEL
},
"[-frv] prov ..."
},
{ "revert", 0, NULL,
{
{ 'v', "verbose", NULL, G_TYPE_BOOL },
G_OPT_SENTINEL
},
"[-v] prov ..."
},
G_CMD_SENTINEL
};

320
lib/geom/union/gunion.8 Normal file
View File

@ -0,0 +1,320 @@
.\"
.\" Copyright (c) 2022 Marshall Kirk McKusick <mckusick@mckusick.com>
.\"
.\" 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 AUTHORS 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 AUTHORS 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.
.\"
.\" $FreeBSD$
.\"
.Dd January 19, 2022
.Dt GUNION 8
.Os
.Sh NAME
.Nm gunion
.Nd "control utility for UNION GEOM class"
.Sh SYNOPSIS
.Nm
.Cm create
.Op Fl v
.Op Fl o Ar offset
.Op Fl s Ar size
.Op Fl S Ar secsize
.Op Fl Z Ar gunionname
.Ar upperdev lowerdev
.Nm
.Cm destroy
.Op Fl fv
.Ar prov ...
.Nm
.Cm reset
.Op Fl v
.Ar prov ...
.Nm
.Cm revert
.Op Fl v
.Ar prov ...
.Nm
.Cm commit
.Op Fl frv
.Ar prov ...
.Nm
.Cm list
.Nm
.Cm status
.Nm
.Cm load
.Nm
.Cm unload
.Sh DESCRIPTION
The
.Nm
utility is used to track changes to a read-only disk on a writable disk.
Logically, a writable disk is placed over a read-only disk.
Write requests are intercepted and stored on the writable disk.
Read requests are first checked to see if they have been written
on the top (writable disk) and if found are returned.
If they have not been written on the top disk,
then they are read from the lower disk.
.Pp
The
.Nm
utility can be especially useful if you have a large disk with a
corrupted filesystem that you are unsure of how to repair.
You can use
.Nm
to place another disk over the corrupted disk and then attempt
to repair the filesystem.
If the repair fails, you can revert all the changes in the upper disk
and be back to the unchanged state of the lower disk thus allowing you
to try another approach to repairing it.
If the repair is successful you can request that all the writes recorded
on the top disk be written to the lower disk.
.Pp
Another use of the
.Nm
utility is to try out upgrades to your system.
Place the upper disk over the disk holding your filesystem that
is to be upgraded and then run the upgrade on it.
If it works, commit it;
if it fails, revert the upgrade.
An example is given below.
.Pp
The upper disk must be at least the size of the disk that it covers.
The union metadata exists only for the
period of time that the union is instantiated,
so it is important to commit the updates before destroying the union.
If the top disk is about 2.5 percent larger for 512 byte sector disks
(or 0.5 percent larger for 4K sector disks) than the disk that it covers,
it is posible (thought not currently implemented) to save the union
metadata between instantiations of the union device.
.Pp
If you do not have physical media available to use for the upper layer, the
.Xr md 4
disk can be used instead.
When used in
.Cm swap
mode the changes are all held in buffer memory.
Pages get pushed out to the swap when the system is under memory pressure,
otherwise they stay in the operating memory.
If long-term persistance is desired,
.Cm vnode
mode can be used in which a regular file is used as backing store.
The disk space used by the file is based on the amount of data that
is written to the top device.
.Pp
The first argument to
.Nm
indicates an action to be performed:
.Bl -tag -width "destroy"
.It Cm create
Set up a union provider on the two given devices.
The first device given is used as the top device and must be writable.
The second device given is used as the bottom device and need only be readable.
The second device may be mounted read-only but it is recommended
that it be unmounted and accessed only through a mount of the union device.
If the operation succeeds, the new provider should appear with name
.Pa /dev/ Ns Ao Ar upperdev Ac Ns - Ns Ao Ar lowerdev Ac Ns Pa .union .
An alternate name can be specified with the
.Fl Z
flag.
The kernel module
.Pa geom_union.ko
will be loaded if it is not loaded already.
.Pp
Additional options include:
.Bl -tag -width "-Z gunionname"
.It Fl o Ar offset
Where to begin on the original provider.
The default is to start at the beginning of the disk (i.e., at offset 0).
This option may be used to skip over partitioning information stored
at the beginning of a disk.
The offset must be a multiple of the sector size.
.It Fl s Ar size
Size of the transparent provider.
The default is to be the same size as the lower disk.
Any extra space at the end of the upper disk may be used to store
union metadata.
.It Fl S Ar secsize
Sector size of the transparent provider.
The default is to be the same sector size as the lower disk.
.It Fl v
Be more verbose.
.It Fl Z Ar gunionname
The name of the new provider.
The suffix
.Dq .union
will be appended to the provider name.
.El
.It Cm destroy
Turn off the given union providers.
.Pp
Additional options include:
.Bl -tag -width "-f"
.It Fl f
Force the removal of the specified provider.
.It Fl v
Be more verbose.
.El
.It Cm revert
Discard all the changes made in the top layer thus reverting to the
original state of the lower device.
The union device may not be mounted or otherwise in use when a
.Cm revert
operation is being done.
.It Cm commit
Write all the changes made in the top device to the lower device
thus committing the lower device to have the same data as the union.
.Pp
Additional options include:
.Bl -tag -width "-f"
.It Fl f
The
.Cm commit
command will not allow the lower device to be mounted
or otherwise in use while the
.Cm commit
operation is being done.
However, the
.Fl f
flag may be specified to allow the lower device to be mounted read-only.
To prevent a filesystem panic on the mounted lower-device filesystem,
immediately after the
.Cm commit
operation finishes the lower-device filesystem should be unmounted
and then remounted to update its metadata state.
If the lower-device filesystem is currently being used as the root
filesystem then the
.Fl r
flag should be specified to reboot the system at the completion of the
.Cm commit
operation.
.It Fl r
Reboot the system at the completion of the
.Cm commit
operation.
.It Fl v
Be more verbose.
.El
.It Cm reset
Reset statistics for the given union providers.
.It Cm list
See
.Xr geom 8 .
.It Cm status
See
.Xr geom 8 .
.It Cm load
See
.Xr geom 8 .
.It Cm unload
See
.Xr geom 8 .
.El
.Sh EXIT STATUS
Exit status is 0 on success, and 1 if the command fails.
.Sh EXAMPLES
The following example shows how to create and destroy a
union provider with disks
.Pa /dev/da0p1
as the read-only disk on the bottom and
.Pa /dev/md0
as the wriable disk on the top.
.Bd -literal -offset indent
gunion create -v md0 da0p1
mount /dev/md0-da0p1.union /mnt
.Ed
.Pp
Proceed to make changes in /mnt filesystem.
If they are successful and you want to keep them.
.Bd -literal -offset indent
umount /mnt
gunion commit -v md0-da0p1.union
.Ed
.Pp
If they are unsuccessful and you want to roll back.
.Bd -literal -offset indent
umount /mnt
gunion revert -v md0-da0p1.union
.Ed
.Pp
When done eliminate the union.
.Bd -literal -offset indent
umount /mnt
gunion destroy -v md0-da0p1.union
.Ed
.Pp
All uncommitted changes will be discarded when the union is destroyed.
.Pp
If you use the name of the full disk, for example
.Pa da0
and it is labelled,
then a union name will appear for the disk as
.Pa md0-da0.union
as well as for each partition on the disk as
.Pa md0-da0p1.union ,
.Pa md0-da0p2.union ,
etc.
A commit operation can be done only on
.Pa md0-da0.union
and will commit changes to all the partitions.
If partition level commits are desired,
then a union must be created for each partition.
.Pp
The traffic statistics for the given
union providers can be obtained with the
.Cm list
command.
The example below shows the number of bytes written with
.Xr newfs 8 :
.Bd -literal -offset indent
gunion create md0 da0p1
newfs /dev/md0-da0p1.union
gunion list
.Ed
.Sh SYSCTL VARIABLES
The following
.Xr sysctl 8
variables can be used to control the behavior of the
.Nm UNION
GEOM class.
The default value is shown next to each variable.
.Bl -tag -width indent
.It Va kern.geom.union.debug : No 0
Debug level of the
.Nm UNION
GEOM class.
This can be set to a number between 0 and 4 inclusive.
If set to 0, no debug information is printed.
If set to 1, all the verbose messages are logged.
If set to 2, addition error-related information is logged.
If set to 3, mapping operations are logged.
If set to 4, the maximum amount of debug information is printed.
.El
.Sh SEE ALSO
.Xr geom 4 ,
.Xr geom 8
.Sh HISTORY
The
.Nm
utility appeared in
.Fx 14.0 .
.Sh AUTHORS
.An Marshall Kirk McKusick Aq Mt mckusick@mckusick.com

View File

@ -24,7 +24,7 @@
.\"
.\" $FreeBSD$
.\"
.Dd September 14, 2018
.Dd January 19, 2022
.Dt GEOM 8
.Os
.Sh NAME
@ -162,6 +162,8 @@ SHSEC
.It
STRIPE
.It
UNION
.It
VIRSTOR
.El
.Sh ENVIRONMENT
@ -210,6 +212,7 @@ geom md unload
.Xr gsched 8 ,
.Xr gshsec 8 ,
.Xr gstripe 8 ,
.Xr gunion 8 ,
.Xr gvirstor 8
.Sh HISTORY
The

View File

@ -3689,6 +3689,7 @@ geom/raid3/g_raid3.c optional geom_raid3
geom/raid3/g_raid3_ctl.c optional geom_raid3
geom/shsec/g_shsec.c optional geom_shsec
geom/stripe/g_stripe.c optional geom_stripe
geom/union/g_union.c optional geom_union
geom/uzip/g_uzip.c optional geom_uzip
geom/uzip/g_uzip_lzma.c optional geom_uzip
geom/uzip/g_uzip_wrkthr.c optional geom_uzip

1389
sys/geom/union/g_union.c Normal file

File diff suppressed because it is too large Load Diff

144
sys/geom/union/g_union.h Normal file
View File

@ -0,0 +1,144 @@
/*-
* SPDX-License-Identifier: BSD-2-Clause-FreeBSD
*
* Copyright (c) 2022 Marshall Kirk McKusick <mckusick@mckusick.com>
*
* 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 AUTHORS 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 AUTHORS 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.
*/
#ifndef _G_UNION_H_
#define _G_UNION_H_
#define G_UNION_CLASS_NAME "UNION"
#define G_UNION_VERSION 1
#define G_UNION_SUFFIX ".union"
/*
* Special flag to instruct gunion to passthrough the underlying provider's
* physical path
*/
#define G_UNION_PHYSPATH_PASSTHROUGH "\255"
#ifdef _KERNEL
#define G_UNION_DEBUG(lvl, ...) \
_GEOM_DEBUG("GEOM_UNION", g_union_debug, (lvl), NULL, __VA_ARGS__)
#define G_UNION_LOGREQLVL(lvl, bp, ...) \
_GEOM_DEBUG("GEOM_UNION", g_union_debug, (lvl), (bp), __VA_ARGS__)
#define G_UNION_LOGREQ(bp, ...) G_UNION_LOGREQLVL(3, (bp), __VA_ARGS__)
TAILQ_HEAD(wiplist, g_union_wip);
/*
* State maintained by each instance of a UNION GEOM.
*/
struct g_union_softc {
struct rwlock sc_rwlock; /* writemap lock */
uint64_t **sc_writemap_root; /* root of write map */
uint64_t *sc_leafused; /* 1 => leaf has allocation */
uint64_t sc_map_size; /* size of write map */
long sc_root_size; /* entries in root node */
long sc_leaf_size; /* entries in leaf node */
long sc_bits_per_leaf; /* bits per leaf node entry */
long sc_writemap_memory; /* memory used by writemap */
off_t sc_offset; /* starting offset in lower */
off_t sc_size; /* size of union geom */
off_t sc_sectorsize; /* sector size of geom */
struct g_consumer *sc_uppercp; /* upper-level provider */
struct g_consumer *sc_lowercp; /* lower-level provider */
struct wiplist sc_wiplist; /* I/O work-in-progress list */
long sc_flags; /* see flags below */
long sc_reads; /* number of reads done */
long sc_wrotebytes; /* number of bytes written */
long sc_writes; /* number of writes done */
long sc_readbytes; /* number of bytes read */
long sc_deletes; /* number of deletes done */
long sc_getattrs; /* number of getattrs done */
long sc_flushes; /* number of flushes done */
long sc_cmd0s; /* number of cmd0's done */
long sc_cmd1s; /* number of cmd1's done */
long sc_cmd2s; /* number of cmd2's done */
long sc_speedups; /* number of speedups done */
long sc_readcurrentread; /* reads current with read */
long sc_readblockwrite; /* writes blocked by read */
long sc_writeblockread; /* reads blocked by write */
long sc_writeblockwrite; /* writes blocked by write */
};
/*
* Structure to track work-in-progress I/O operations.
*
* Used to prevent overlapping I/O operations from running concurrently.
* Created for each I/O operation.
*
* In usual case of no overlap it is linked to sc_wiplist and started.
* If found to overlap an I/O on sc_wiplist, it is not started and is
* linked to wip_waiting list of the I/O that it overlaps. When an I/O
* completes, it restarts all the I/O operations on its wip_waiting list.
*/
struct g_union_wip {
struct wiplist wip_waiting; /* list of I/Os waiting on me */
TAILQ_ENTRY(g_union_wip) wip_next; /* pending or active I/O list */
struct bio *wip_bp; /* bio for this I/O */
struct g_union_softc *wip_sc; /* g_union's softc */
off_t wip_start; /* starting offset of I/O */
off_t wip_end; /* ending offset of I/O */
long wip_numios; /* BIO_READs in progress */
long wip_error; /* merged I/O errors */
};
/*
* UNION flags
*/
#define DOING_COMMIT 0x00000001 /* a commit command is in progress */
#define DOING_COMMIT_BITNUM 0 /* a commit command is in progress */
#define BITS_PER_ENTRY (sizeof(uint64_t) * NBBY)
#define G_RLOCK(sc) rw_rlock(&(sc)->sc_rwlock)
#define G_RUNLOCK(sc) rw_runlock(&(sc)->sc_rwlock)
#define G_WLOCK(sc) rw_wlock(&(sc)->sc_rwlock)
#define G_WUNLOCK(sc) rw_wunlock(&(sc)->sc_rwlock)
#define G_WLOCKOWNED(sc) rw_assert(&(sc)->sc_rwlock, RA_WLOCKED)
/*
* The writelock is held while a commit operation is in progress.
* While held union device may not be used or in use.
* Returns == 0 if lock was successfully obtained.
*/
static inline int
g_union_get_writelock(struct g_union_softc *sc)
{
return (atomic_testandset_long(&sc->sc_flags, DOING_COMMIT_BITNUM));
}
static inline void
g_union_rel_writelock(struct g_union_softc *sc)
{
long ret __diagused;
ret = atomic_testandclear_long(&sc->sc_flags, DOING_COMMIT_BITNUM);
KASSERT(ret != 0, ("UNION GEOM releasing unheld lock"));
}
#endif /* _KERNEL */
#endif /* _G_UNION_H_ */

View File

@ -21,6 +21,7 @@ SUBDIR= geom_bde \
geom_raid3 \
geom_shsec \
geom_stripe \
geom_union \
geom_uzip \
geom_vinum \
geom_virstor \

View File

@ -0,0 +1,8 @@
# $FreeBSD$
.PATH: ${SRCTOP}/sys/geom/union
KMOD= geom_union
SRCS= g_union.c
.include <bsd.kmod.mk>