feat: add filesystem probing library (#298)

This commit is contained in:
Andrew Rynhard 2018-12-24 07:42:30 -08:00 committed by GitHub
parent 7918d38e0f
commit 42b722b0eb
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
43 changed files with 1133 additions and 772 deletions

View File

@ -75,10 +75,6 @@ RUN ../configure \
--disable-static
RUN make -j $(($(nproc) / 2))
RUN make install DESTDIR=/rootfs
# libblkid
RUN cp /toolchain/lib/libblkid.* /rootfs/lib
# libuuid
RUN cp /toolchain/lib/libuuid.* /rootfs/lib
# ca-certificates
RUN mkdir -p /rootfs/etc/ssl/certs
RUN curl -o /rootfs/etc/ssl/certs/ca-certificates.crt https://curl.haxx.se/ca/cacert.pem
@ -113,11 +109,10 @@ FROM base AS initramfs
ARG SHA
ARG TAG
ARG VERSION_PKG="github.com/autonomy/talos/internal/pkg/version"
ENV CGO_ENABLED 1
RUN apt update \
&& apt install -y util-linux libblkid-dev cpio xz-utils
&& apt install -y cpio xz-utils
WORKDIR /src/internal/app/init
RUN go build -a -ldflags "-s -w -X ${VERSION_PKG}.Name=Server -X ${VERSION_PKG}.SHA=${SHA} -X ${VERSION_PKG}.Tag=${TAG}" -o /init
RUN go build -a -ldflags "-s -w -X ${VERSION_PKG}.Name=Talos -X ${VERSION_PKG}.SHA=${SHA} -X ${VERSION_PKG}.Tag=${TAG}" -o /init
RUN chmod +x /init
WORKDIR /initramfs
RUN cp /init ./
@ -188,7 +183,7 @@ ENTRYPOINT ["/blockd"]
FROM base AS test
RUN apt update \
&& apt install -y util-linux libblkid-dev xfsprogs
&& apt install -y xfsprogs
RUN curl -sfL https://install.goreleaser.com/github.com/golangci/golangci-lint.sh | bash -s -- -b $GOPATH/bin v1.12.3
RUN chmod +x ./hack/golang/test.sh
ENV PATH /rootfs/bin:$PATH

6
go.mod
View File

@ -62,7 +62,7 @@ require (
golang.org/x/text v0.3.0
golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2 // indirect
google.golang.org/appengine v1.2.0 // indirect
google.golang.org/genproto v0.0.0-20180831171423-11092d34479b // indirect
google.golang.org/genproto v0.0.0-20181221175505-bd9b4fb69e2f // indirect
google.golang.org/grpc v1.17.0
gopkg.in/airbrake/gobrake.v2 v2.0.9 // indirect
gopkg.in/fsnotify.v1 v1.4.7 // indirect
@ -71,9 +71,9 @@ require (
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 // indirect
gopkg.in/yaml.v2 v2.2.2
gotest.tools v2.1.0+incompatible // indirect
k8s.io/api v0.0.0-20181213150558-05914d821849
k8s.io/api v0.0.0-20181221193117-173ce66c1e39
k8s.io/apiextensions-apiserver v0.0.0-20181213153335-0fe22c71c476 // indirect
k8s.io/apimachinery v0.0.0-20181127025237-2b1284ed4c93
k8s.io/apimachinery v0.0.0-20181220065808-98853ca904e8
k8s.io/apiserver v0.0.0-20181213151703-3ccfe8365421 // indirect
k8s.io/client-go v10.0.0+incompatible
k8s.io/cluster-bootstrap v0.0.0-20181108060158-bf9d13f1fbeb // indirect

11
go.sum
View File

@ -46,6 +46,7 @@ github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekf
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/groupcache v0.0.0-20180513044358-24b0969c4cb7 h1:u4bArs140e9+AfE52mFHOXVFnOSBJBRlzTHrOPLOIhE=
github.com/golang/groupcache v0.0.0-20180513044358-24b0969c4cb7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:tluoj9z5200jBnyusfRPU2LqT6J+DAorxEvtC7LHB+E=
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/protobuf v1.2.0 h1:P3YflyNX/ehuJFLhxviNdFxQPkGK5cDcApsge1SqnvM=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
@ -112,9 +113,11 @@ github.com/vmware/vmw-guestinfo v0.0.0-20170707015358-25eff159a728 h1:sH9mEk+fly
github.com/vmware/vmw-guestinfo v0.0.0-20170707015358-25eff159a728/go.mod h1:x9oS4Wk2s2u4tS29nEaDLdzvuHdB19CvSGJjPgkZJNk=
golang.org/x/crypto v0.0.0-20180515001509-1a580b3eff78 h1:uJIReYEB1ZZLarzi83Pmig1HhZ/cwFCysx05l0PFBIk=
golang.org/x/crypto v0.0.0-20180515001509-1a580b3eff78/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181106065722-10aee1819953/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181201002055-351d144fa1fc h1:a3CU5tJYVj92DY2LaA1kUkrsqD5/3mLDhx2NcNqyW+0=
golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
@ -137,6 +140,9 @@ google.golang.org/appengine v1.2.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20180831171423-11092d34479b h1:lohp5blsw53GBXtLyLNaTXPXS9pJ1tiTw61ZHUoE9Qw=
google.golang.org/genproto v0.0.0-20180831171423-11092d34479b/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20181221175505-bd9b4fb69e2f h1:eT3B0O2ghdSPzjAOznr3oOLyN1HFeYUncYl7FRwg4VI=
google.golang.org/genproto v0.0.0-20181221175505-bd9b4fb69e2f/go.mod h1:7Ep/1NZk928CDR8SjdVbjWNpdIf6nzjE3BTgJDr2Atg=
google.golang.org/grpc v1.16.0/go.mod h1:0JHn/cJsOMiMfNA9+DeHDlAU7KAAB5GDlYFpa9MZMio=
google.golang.org/grpc v1.17.0 h1:TRJYBgMclJvGYn2rIMjj+h9KtMt5r1Ij7ODVRIZkwhk=
google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs=
gopkg.in/airbrake/gobrake.v2 v2.0.9 h1:7z2uVWwn7oVeeugY1DtlPAy5H+KYgB1KeKTnqjNatLo=
@ -156,12 +162,17 @@ gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gotest.tools v2.1.0+incompatible h1:5USw7CrJBYKqjg9R7QlA6jzqZKEAtvW82aNmsxxGPxw=
gotest.tools v2.1.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw=
honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
k8s.io/api v0.0.0-20181130031204-d04500c8c3dd h1:5aHsneN62ehs/tdtS9tWZlhVk68V7yms/Qw7nsGmvCA=
k8s.io/api v0.0.0-20181213150558-05914d821849 h1:WZFcFPXmLR7g5CxQNmjWv0mg8qulJLxDghbzS4pQtzY=
k8s.io/api v0.0.0-20181213150558-05914d821849/go.mod h1:iuAfoD4hCxJ8Onx9kaTIt30j7jUFS00AXQi6QMi99vA=
k8s.io/api v0.0.0-20181221193117-173ce66c1e39 h1:iGq7zEPXFb0IeXAQK5RiYT1SVKX/af9F9Wv0M+yudPY=
k8s.io/api v0.0.0-20181221193117-173ce66c1e39/go.mod h1:iuAfoD4hCxJ8Onx9kaTIt30j7jUFS00AXQi6QMi99vA=
k8s.io/apiextensions-apiserver v0.0.0-20181213153335-0fe22c71c476 h1:Ws9zfxsgV19Durts9ftyTG7TO0A/QLhmu98VqNWLiH8=
k8s.io/apiextensions-apiserver v0.0.0-20181213153335-0fe22c71c476/go.mod h1:IxkesAMoaCRoLrPJdZNZUQp9NfZnzqaVzLhb2VEQzXE=
k8s.io/apimachinery v0.0.0-20181127025237-2b1284ed4c93 h1:tT6oQBi0qwLbbZSfDkdIsb23EwaLY85hoAV4SpXfdao=
k8s.io/apimachinery v0.0.0-20181127025237-2b1284ed4c93/go.mod h1:ccL7Eh7zubPUSh9A3USN90/OzHNSVN6zxzde07TDCL0=
k8s.io/apimachinery v0.0.0-20181220065808-98853ca904e8 h1:WLypux0abPAfOJJKJNA1+g5yphAOk+ESOeSqWMwMnqA=
k8s.io/apimachinery v0.0.0-20181220065808-98853ca904e8/go.mod h1:ccL7Eh7zubPUSh9A3USN90/OzHNSVN6zxzde07TDCL0=
k8s.io/apiserver v0.0.0-20181213151703-3ccfe8365421 h1:NyOpnIh+7SLvC05NGCIXF9c4KhnkTZQE2SxF+m9otww=
k8s.io/apiserver v0.0.0-20181213151703-3ccfe8365421/go.mod h1:6bqaTSOSJavUIXUtfaR9Os9JtTCm8ZqH2SUl2S60C4w=
k8s.io/client-go v10.0.0+incompatible h1:F1IqCqw7oMBzDkqlcBymRq1450wD0eNqLE9jzUrIi34=

View File

@ -14,4 +14,5 @@ done
mkdir ${PREFIX}/lib/modules
mkdir -p ${PREFIX}/usr/libexec
mkdir -p ${PREFIX}/var/libexec/kubernetes
ln -sv ../../var/libexec/kubernetes ${PREFIX}/usr/libexec/kubernetes

View File

@ -1,29 +0,0 @@
package util
import "C"
import (
"strings"
)
// PartNo returns the partition number.
func PartNo(partname string) string {
if strings.HasPrefix(partname, "/dev/nvme") {
idx := strings.Index(partname, "p")
return partname[idx+1:]
} else if strings.HasPrefix(partname, "/dev/sd") || strings.HasPrefix(partname, "/dev/hd") {
return strings.TrimLeft(partname, "/abcdefghijklmnopqrstuvwxyz")
}
return ""
}
// DevnameFromPartname returns the device name from a partition name.
func DevnameFromPartname(partname, partno string) string {
if strings.HasPrefix(partname, "/dev/nvme") {
return strings.TrimRight(partname, "p"+partno)
} else if strings.HasPrefix(partname, "/dev/sd") || strings.HasPrefix(partname, "/dev/hd") {
return strings.TrimRight(partname, partno)
}
return ""
}

View File

@ -1,136 +0,0 @@
// +build linux
// Package blkid provides bindings to libblkid.
package blkid
/*
#cgo CFLAGS: -I/usr/include
#cgo LDFLAGS: -L/usr/lib -lblkid
#include <stdio.h>
#include <stdlib.h>
#include <blkid/blkid.h>
*/
import "C"
import (
"fmt"
"unsafe"
"github.com/pkg/errors"
)
const (
// BlkidSublksLabel read LABEL from superblock.
BlkidSublksLabel = (1 << 1)
// BlkidSublksUUID read UUID from superblock.
BlkidSublksUUID = (1 << 3)
// BlkidSublksType reads the TYPE from superblock.
BlkidSublksType = (1 << 5)
// BlkidPartsEntryDetails reads the partition details from superblock.
BlkidPartsEntryDetails = (1 << 2)
)
// GetDevWithAttribute returns the dev name of a block device matching the ATTRIBUTE=VALUE
// pair. Supported attributes are:
// TYPE: filesystem type
// UUID: filesystem uuid
// LABEL: filesystem label
func GetDevWithAttribute(attribute, value string) (string, error) {
var cache C.blkid_cache
ret := C.blkid_get_cache(&cache, nil)
if ret != 0 {
return "", fmt.Errorf("failed to get blkid cache: %d", ret)
}
C.blkid_probe_all(cache)
csAttribute := C.CString(attribute)
csValue := C.CString(value)
defer C.free(unsafe.Pointer(csAttribute))
defer C.free(unsafe.Pointer(csValue))
devname := C.blkid_get_devname(cache, csAttribute, csValue)
defer C.free(unsafe.Pointer(devname))
// If you have called blkid_get_cache(), you should call blkid_put_cache()
// when you are done using the blkid library functions. This will save the
// cache to the blkid.tab file, if you have write access to the file. It
// will also free all associated devices and tags:
C.blkid_put_cache(cache)
return C.GoString(devname), nil
}
// NewProbeFromFilename executes lblkid blkid_new_probe_from_filename.
func NewProbeFromFilename(s string) (C.blkid_probe, error) {
cs := C.CString(s)
defer C.free(unsafe.Pointer(cs))
var pr C.blkid_probe = C.blkid_new_probe_from_filename(cs)
if pr == nil {
return nil, fmt.Errorf("failed to open device %s", C.GoString(cs))
}
return pr, nil
}
// DoProbe executes lblkid blkid_do_probe.
func DoProbe(pr C.blkid_probe) error {
if retval := C.blkid_do_probe(pr); retval != 0 {
return errors.Errorf("%d", retval)
}
return nil
}
// DoSafeProbe executes lblkid blkid_do_safeprobe.
func DoSafeProbe(pr C.blkid_probe) error {
if retval := C.blkid_do_safeprobe(pr); retval != 0 {
return errors.Errorf("%d", retval)
}
return nil
}
// ProbeLookupValue implements:
// int blkid_probe_lookup_value (blkid_probe pr, const char *name, const char **data, size_t *len);
func ProbeLookupValue(pr C.blkid_probe, name string, size *int) (string, error) {
cs := C.CString(name)
defer C.free(unsafe.Pointer(cs))
var data *C.char
defer C.free(unsafe.Pointer(data))
C.blkid_probe_enable_superblocks(pr, 1)
C.blkid_probe_set_superblocks_flags(pr, BlkidSublksLabel|BlkidSublksUUID|BlkidSublksType)
C.blkid_probe_enable_partitions(pr, 1)
C.blkid_probe_set_partitions_flags(pr, BlkidPartsEntryDetails)
if err := DoSafeProbe(pr); err != nil {
return "", errors.Errorf("failed to do safe probe: %v", err)
}
retval := C.blkid_probe_lookup_value(pr, cs, &data, nil)
if retval != 0 {
return "", errors.Errorf("failed to lookup value %s: %d", name, retval)
}
return C.GoString(data), nil
}
// ProbeGetPartitions implements:
// blkid_partlist blkid_probe_get_partitions (blkid_probe pr);
func ProbeGetPartitions(pr C.blkid_probe) C.blkid_partlist {
return C.blkid_probe_get_partitions(pr)
}
// ProbeGetPartitionsPartlistNumOfPartitions implements:
// int blkid_partlist_numof_partitions (blkid_partlist ls);
func ProbeGetPartitionsPartlistNumOfPartitions(ls C.blkid_partlist) int {
return int(C.blkid_partlist_numof_partitions(ls))
}
// FreeProbe implements:
// int blkid_partlist_numof_partitions (blkid_partlist ls);
func FreeProbe(pr C.blkid_probe) {
C.blkid_free_probe(pr)
}

View File

@ -1,387 +0,0 @@
// +build linux
package mount
import (
"fmt"
"log"
"os"
"path"
"strings"
"time"
"github.com/autonomy/talos/internal/app/init/internal/fs/xfs"
"github.com/autonomy/talos/internal/app/init/internal/mount/blkid"
"github.com/autonomy/talos/internal/pkg/blockdevice"
gptpartition "github.com/autonomy/talos/internal/pkg/blockdevice/table/gpt/partition"
"github.com/autonomy/talos/internal/pkg/constants"
"github.com/pkg/errors"
"golang.org/x/sys/unix"
)
var (
instance struct {
special map[string]*Point
blockdevices map[string]*Point
}
special = map[string]*Point{
"dev": {"devtmpfs", "/dev", "devtmpfs", unix.MS_NOSUID, "mode=0755"},
"proc": {"proc", "/proc", "proc", unix.MS_NOSUID | unix.MS_NOEXEC | unix.MS_NODEV, ""},
"sys": {"sysfs", "/sys", "sysfs", unix.MS_NOSUID | unix.MS_NOEXEC | unix.MS_NODEV, ""},
"run": {"tmpfs", "/run", "tmpfs", 0, ""},
"tmp": {"tmpfs", "/tmp", "tmpfs", 0, ""},
}
)
// Point represents a linux mount point.
type Point struct {
source string
target string
fstype string
flags uintptr
data string
}
// BlockDevice represents the metadata on a block device probed by libblkid.
type BlockDevice struct {
dev string
Type string
UUID string
Label string
PartEntryName string
PartEntryUUID string
}
// init initializes the instance metadata
func init() {
instance = struct {
special map[string]*Point
blockdevices map[string]*Point
}{
special,
map[string]*Point{},
}
}
// InitSpecial initializes the special device mount points.
func InitSpecial(s string) (err error) {
return mountSpecialDevices()
}
// InitBlock initializes the block device mount points.
func InitBlock(s string) (err error) {
blockdevices, err := probe()
if err != nil {
return fmt.Errorf("error probing block devices: %v", err)
}
if err = mountBlockDevices(blockdevices, s); err != nil {
return fmt.Errorf("error mounting partitions: %v", err)
}
return nil
}
// Move moves the mount points created in Init, to the new root.
func Move(s string) error {
if err := os.MkdirAll(s, os.ModeDir); err != nil {
return err
}
// Move the special mounts to the new root.
for label, mountpoint := range instance.special {
target := path.Join(s, mountpoint.target)
if err := UnixMountWithRetry(mountpoint.target, target, "", unix.MS_MOVE, ""); err != nil {
return fmt.Errorf("move mount point %s to %s: %v", mountpoint.target, target, err)
}
if label == "dev" {
mountpoint = &Point{"devpts", path.Join(s, "/dev/pts"), "devpts", unix.MS_NOSUID | unix.MS_NOEXEC, "ptmxmode=000,mode=620,gid=5"}
if err := os.MkdirAll(mountpoint.target, os.ModeDir); err != nil {
return fmt.Errorf("error creating mount point directory %s: %v", mountpoint.target, err)
}
if err := UnixMountWithRetry(mountpoint.source, mountpoint.target, mountpoint.fstype, mountpoint.flags, mountpoint.data); err != nil {
return fmt.Errorf("error moving special device from %s to %s: %v", mountpoint.source, mountpoint.target, err)
}
}
}
return nil
}
// Finalize moves the mount points created in Init to the new root.
func Finalize(s string) error {
return unix.Mount(s, "/", "", unix.MS_MOVE, "")
}
// Mount moves the mount points created in Init to the new root.
func Mount(s string) error {
if err := os.MkdirAll(s, os.ModeDir); err != nil {
return err
}
mountpoint, ok := instance.blockdevices[constants.RootPartitionLabel]
if ok {
mountpoint.flags = unix.MS_RDONLY | unix.MS_NOATIME
if err := unix.Mount(mountpoint.source, mountpoint.target, mountpoint.fstype, mountpoint.flags, mountpoint.data); err != nil {
return fmt.Errorf("error mounting partition %s: %v", mountpoint.target, err)
}
// MS_SHARED:
// Make this mount point shared. Mount and unmount events
// immediately under this mount point will propagate to the
// other mount points that are members of this mount's peer
// group. Propagation here means that the same mount or
// unmount will automatically occur under all of the other
// mount points in the peer group. Conversely, mount and
// unmount events that take place under peer mount points
// will propagate to this mount point.
// See http://man7.org/linux/man-pages/man2/mount.2.html
// https://github.com/kubernetes/kubernetes/issues/61058
if err := unix.Mount("", mountpoint.target, "", unix.MS_SHARED, ""); err != nil {
return fmt.Errorf("error making making mount point %s shared: %v", mountpoint.target, err)
}
}
mountpoint, ok = instance.blockdevices[constants.DataPartitionLabel]
if ok {
if err := unix.Mount(mountpoint.source, mountpoint.target, mountpoint.fstype, mountpoint.flags, mountpoint.data); err != nil {
return fmt.Errorf("error mounting partition %s: %v", mountpoint.target, err)
}
}
return nil
}
// Unmount unmounts the ROOT and DATA block devices.
func Unmount() error {
for _, disk := range []string{constants.BootPartitionLabel, constants.DataPartitionLabel, constants.RootPartitionLabel} {
mountpoint, ok := instance.blockdevices[disk]
if ok {
if err := unix.Unmount(mountpoint.target, 0); err != nil {
return fmt.Errorf("unmount mount point %s: %v", mountpoint.target, err)
}
}
}
return nil
}
func mountSpecialDevices() (err error) {
for _, mountpoint := range instance.special {
if err = os.MkdirAll(mountpoint.target, os.ModeDir); err != nil {
return fmt.Errorf("error creating mount point directory %s: %v", mountpoint.target, err)
}
if err = UnixMountWithRetry(mountpoint.source, mountpoint.target, mountpoint.fstype, mountpoint.flags, mountpoint.data); err != nil {
return fmt.Errorf("error mounting special device %s: %v", mountpoint.target, err)
}
}
return nil
}
// UnixMountWithRetry attempts to retry a mount on EBUSY. It will attempt a
// retry every 100 milliseconds over the course of 5 seconds.
func UnixMountWithRetry(source string, target string, fstype string, flags uintptr, data string) (err error) {
for i := 0; i < 50; i++ {
if err = unix.Mount(source, target, fstype, flags, data); err != nil {
switch err {
case unix.EBUSY:
time.Sleep(100 * time.Millisecond)
continue
default:
return err
}
}
return nil
}
return errors.Errorf("mount timeout: %v", err)
}
// nolint: gocyclo
func fixDataPartition(blockdevices []*BlockDevice) error {
for _, b := range blockdevices {
if b.PartEntryName == constants.DataPartitionLabel {
devname := devnameFromPartname(b.dev)
bd, err := blockdevice.Open(devname)
if err != nil {
return fmt.Errorf("error opening block device %q: %v", devname, err)
}
// nolint: errcheck
defer bd.Close()
pt, err := bd.PartitionTable(false)
if err != nil {
return err
}
if err := pt.Read(); err != nil {
return err
}
if err := pt.Repair(); err != nil {
return err
}
for _, partition := range pt.Partitions() {
if partition.(*gptpartition.Partition).Name == constants.DataPartitionLabel {
if err := pt.Resize(partition); err != nil {
return err
}
}
}
if err := pt.Write(); err != nil {
return err
}
// Rereading the partition table requires that all partitions be unmounted
// or it will fail with EBUSY.
if err := bd.RereadPartitionTable(); err != nil {
return err
}
}
}
return nil
}
// nolint: gocyclo
func mountBlockDevices(blockdevices []*BlockDevice, s string) (err error) {
if err = fixDataPartition(blockdevices); err != nil {
return fmt.Errorf("error fixing data partition: %v", err)
}
for _, b := range blockdevices {
mountpoint := &Point{
source: b.dev,
fstype: b.Type,
flags: unix.MS_NOATIME,
data: "",
}
switch b.PartEntryName {
case constants.RootPartitionLabel:
mountpoint.target = s
case constants.DataPartitionLabel:
mountpoint.target = path.Join(s, "var")
case constants.BootPartitionLabel:
mountpoint.target = path.Join(s, "boot")
default:
continue
}
if err = os.MkdirAll(mountpoint.target, os.ModeDir); err != nil {
return fmt.Errorf("error creating mount point directory %s: %v", mountpoint.target, err)
}
if err = unix.Mount(mountpoint.source, mountpoint.target, mountpoint.fstype, mountpoint.flags, mountpoint.data); err != nil {
return fmt.Errorf("error mounting partition %s: %v", mountpoint.target, err)
}
if b.PartEntryName == constants.DataPartitionLabel {
// The XFS partition MUST be mounted, or this will fail.
if err = xfs.GrowFS(mountpoint.target); err != nil {
return fmt.Errorf("error growing XFS file system: %v", err)
}
}
instance.blockdevices[b.PartEntryName] = mountpoint
}
return nil
}
func probe() (b []*BlockDevice, err error) {
b = []*BlockDevice{}
for _, disk := range []string{constants.RootPartitionLabel, constants.DataPartitionLabel, constants.BootPartitionLabel} {
if err := appendBlockDeviceWithLabel(&b, disk); err != nil {
return nil, err
}
}
return b, nil
}
func appendBlockDeviceWithLabel(b *[]*BlockDevice, value string) error {
devname, err := blkid.GetDevWithAttribute("PARTLABEL", value)
if err != nil {
return fmt.Errorf("failed to get dev with attribute: %v", err)
}
if devname == "" {
return fmt.Errorf("no device with attribute \"PART_ENTRY_NAME=%s\" found", value)
}
blockDevice, err := ProbeDevice(devname)
if err != nil {
return fmt.Errorf("failed to probe block device %q: %v", devname, err)
}
*b = append(*b, blockDevice)
return nil
}
// ProbeDevice looks up UUID/TYPE/LABEL/PART_ENTRY_NAME/PART_ENTRY_UUID from a block device
func ProbeDevice(devname string) (*BlockDevice, error) {
pr, err := blkid.NewProbeFromFilename(devname)
defer blkid.FreeProbe(pr)
if err != nil {
return nil, fmt.Errorf("failed to probe %s: %s", devname, err)
}
UUID, err := blkid.ProbeLookupValue(pr, "UUID", nil)
if err != nil {
return nil, err
}
Type, err := blkid.ProbeLookupValue(pr, "TYPE", nil)
if err != nil {
return nil, err
}
Label, err := blkid.ProbeLookupValue(pr, "LABEL", nil)
if err != nil {
log.Printf("WARNING: %v", err)
}
PartEntryName, err := blkid.ProbeLookupValue(pr, "PART_ENTRY_NAME", nil)
if err != nil {
return nil, err
}
PartEntryUUID, err := blkid.ProbeLookupValue(pr, "PART_ENTRY_UUID", nil)
if err != nil {
return nil, err
}
return &BlockDevice{
dev: devname,
UUID: UUID,
Type: Type,
Label: Label,
PartEntryName: PartEntryName,
PartEntryUUID: PartEntryUUID,
}, nil
}
// TODO(andrewrynhard): Should we return an error here?
// TODO(andrewrynhard): Move the PartNo function in the xfs package to an
// appropriate package and use that instead of this.
func partNo(partname string) string {
if strings.HasPrefix(partname, "/dev/nvme") {
idx := strings.Index(partname, "p")
return partname[idx+1:]
} else if strings.HasPrefix(partname, "/dev/sd") || strings.HasPrefix(partname, "/dev/hd") || strings.HasPrefix(partname, "/dev/vd") {
return strings.TrimLeft(partname, "/abcdefghijklmnopqrstuvwxyz")
}
return ""
}
// TODO(andrewrynhard): Should we return an error here?
// TODO(andrewrynhard): Move the DevnameFromPartname function in the xfs
// package to an appropriate package and use that instead of this.
func devnameFromPartname(partname string) string {
partno := partNo(partname)
if strings.HasPrefix(partname, "/dev/nvme") {
return strings.TrimRight(partname, "p"+partno)
} else if strings.HasPrefix(partname, "/dev/sd") || strings.HasPrefix(partname, "/dev/hd") || strings.HasPrefix(partname, "/dev/vd") {
return strings.TrimRight(partname, partno)
}
return ""
}

View File

@ -1,11 +1,8 @@
// +build linux
package baremetal
import (
"archive/tar"
"compress/gzip"
"fmt"
"io"
"io/ioutil"
"log"
@ -18,17 +15,19 @@ import (
"strconv"
"strings"
"github.com/autonomy/talos/internal/app/init/internal/fs/xfs"
"github.com/autonomy/talos/internal/app/init/internal/kernel"
"github.com/autonomy/talos/internal/app/init/internal/mount"
"github.com/autonomy/talos/internal/app/init/internal/mount/blkid"
"github.com/autonomy/talos/internal/pkg/blockdevice"
"github.com/autonomy/talos/internal/pkg/blockdevice/filesystem/xfs"
"github.com/autonomy/talos/internal/pkg/blockdevice/probe"
"github.com/autonomy/talos/internal/pkg/blockdevice/table"
"github.com/autonomy/talos/internal/pkg/blockdevice/table/gpt/partition"
"github.com/autonomy/talos/internal/pkg/constants"
"github.com/autonomy/talos/internal/pkg/kernel"
"github.com/autonomy/talos/internal/pkg/userdata"
"github.com/autonomy/talos/internal/pkg/version"
"github.com/pkg/errors"
"golang.org/x/sys/unix"
yaml "gopkg.in/yaml.v2"
)
@ -53,31 +52,31 @@ func (b *BareMetal) UserData() (data userdata.UserData, err error) {
option, ok := arguments[constants.KernelParamUserData]
if !ok {
return data, fmt.Errorf("no user data option was found")
return data, errors.Errorf("no user data option was found")
}
if option == constants.UserDataCIData {
var devname string
devname, err = blkid.GetDevWithAttribute("LABEL", constants.UserDataCIData)
var dev *probe.ProbedBlockDevice
dev, err = probe.GetDevWithFileSystemLabel(constants.UserDataCIData)
if err != nil {
return data, fmt.Errorf("failed to find %s iso: %v", constants.UserDataCIData, err)
return data, errors.Errorf("failed to find %s iso: %v", constants.UserDataCIData, err)
}
if err = os.Mkdir(mnt, 0700); err != nil {
return data, fmt.Errorf("failed to mkdir: %v", err)
return data, errors.Errorf("failed to mkdir: %v", err)
}
if err = unix.Mount(devname, mnt, "iso9660", unix.MS_RDONLY, ""); err != nil {
return data, fmt.Errorf("failed to mount iso: %v", err)
if err = unix.Mount(dev.Path, mnt, "iso9660", unix.MS_RDONLY, ""); err != nil {
return data, errors.Errorf("failed to mount iso: %v", err)
}
var dataBytes []byte
dataBytes, err = ioutil.ReadFile(path.Join(mnt, "user-data"))
if err != nil {
return data, fmt.Errorf("read user data: %s", err.Error())
return data, errors.Errorf("read user data: %s", err.Error())
}
if err = unix.Unmount(mnt, 0); err != nil {
return data, fmt.Errorf("failed to unmount: %v", err)
return data, errors.Errorf("failed to unmount: %v", err)
}
if err = yaml.Unmarshal(dataBytes, &data); err != nil {
return data, fmt.Errorf("unmarshal user data: %s", err.Error())
return data, errors.Errorf("unmarshal user data: %s", err.Error())
}
return data, nil
@ -97,15 +96,16 @@ func (b *BareMetal) Prepare(data userdata.UserData) (err error) {
func (b *BareMetal) Install(data userdata.UserData) error {
var err error
log.Println("Starting installation")
// No installation necessary
if data.Install == nil {
return err
}
log.Println("starting installation")
// Root Device Init
if data.Install.Root.Device == "" {
return fmt.Errorf("%s", "install.rootdevice is required")
return errors.Errorf("%s", "install.rootdevice is required")
}
if data.Install.Root.Size == 0 {
@ -153,17 +153,20 @@ func (b *BareMetal) Install(data userdata.UserData) error {
// Verify that the disks are unused
// Maybe a simple check against bd.UUID is more appropriate?
if !data.Install.Wipe {
var bd *mount.BlockDevice
var dev *probe.ProbedBlockDevice
for _, device := range []string{data.Install.Boot.Device, data.Install.Root.Device, data.Install.Data.Device} {
bd, err = mount.ProbeDevice(device)
dev, err = probe.GetDevWithFileSystemLabel(device)
if err != nil {
return err
// We continue here because we only care if we can discover the
// device successfully and confirm that the disk is not in use.
// TODO(andrewrynhard): We should return a custom error type here
// that we can use to confirm the device was not found.
continue
}
if bd.Label == "" || bd.Type == "" || bd.PartEntryName == "" {
return fmt.Errorf("%s: %s", "target install device is not empty", device)
if dev.SuperBlock != nil {
return errors.Errorf("target install device %s is not empty, found existing %s file system", device, dev.SuperBlock.Type())
}
}
}
// Create a map of all the devices we need to be concerned with
@ -340,13 +343,13 @@ func (d *Device) Partition() error {
case "amd64":
typeID = "4F68BCE3-E8CD-4DB1-96E7-FBCAF984B709"
default:
return fmt.Errorf("%s", "unsupported cpu architecture")
return errors.Errorf("%s", "unsupported cpu architecture")
}
case constants.DataPartitionLabel:
// Data Partition
typeID = "AF3DC60F-8384-7247-8E79-3D69D8477DE4"
default:
return fmt.Errorf("%s", "unknown partition label")
return errors.Errorf("%s", "unknown partition label")
}
part, err := d.PartitionTable.Add(uint64(d.Size), partition.WithPartitionType(typeID), partition.WithPartitionName(d.Label), partition.WithPartitionTest(d.Test))
@ -539,7 +542,7 @@ func downloader(artifact *url.URL, base string) (*os.File, error) {
if resp.StatusCode != 200 {
// nolint: errcheck
out.Close()
return nil, fmt.Errorf("Failed to download %s, got %d", artifact, resp.StatusCode)
return nil, errors.Errorf("Failed to download %s, got %d", artifact, resp.StatusCode)
}
// Write the body to file

View File

@ -1,5 +1,3 @@
// +build linux
package aws
import (

View File

@ -1,16 +1,15 @@
// +build linux
package vmware
import (
"encoding/base64"
"fmt"
"github.com/autonomy/talos/internal/app/init/internal/kernel"
"github.com/autonomy/talos/internal/pkg/constants"
"github.com/autonomy/talos/internal/pkg/kernel"
"github.com/autonomy/talos/internal/pkg/userdata"
"github.com/vmware/vmw-guestinfo/rpcvmx"
"github.com/vmware/vmw-guestinfo/vmcheck"
yaml "gopkg.in/yaml.v2"
)

View File

@ -1,15 +1,13 @@
// +build linux
package platform
import (
"fmt"
"github.com/autonomy/talos/internal/app/init/internal/kernel"
"github.com/autonomy/talos/internal/app/init/internal/platform/baremetal"
"github.com/autonomy/talos/internal/app/init/internal/platform/cloud/aws"
"github.com/autonomy/talos/internal/app/init/internal/platform/cloud/vmware"
"github.com/autonomy/talos/internal/pkg/constants"
"github.com/autonomy/talos/internal/pkg/kernel"
"github.com/autonomy/talos/internal/pkg/userdata"
)

View File

@ -29,7 +29,7 @@ ID={{ .ID }}
VERSION_ID={{ .Version }}
PRETTY_NAME="{{ .Name }} ({{ .Version }}) by Autonomy"
HOME_URL="https://talos.autonomy.io/"
BUG_REPORT_URL="https://github.com/autonomy/talos/src/issues"
BUG_REPORT_URL="https://github.com/autonomy/talos/issues"
`
// Hosts renders a valid /etc/hosts file and writes it to disk.

View File

@ -0,0 +1,336 @@
package mount
import (
"os"
"path"
"github.com/autonomy/talos/internal/pkg/blockdevice"
"github.com/autonomy/talos/internal/pkg/blockdevice/filesystem/xfs"
"github.com/autonomy/talos/internal/pkg/blockdevice/probe"
gptpartition "github.com/autonomy/talos/internal/pkg/blockdevice/table/gpt/partition"
"github.com/autonomy/talos/internal/pkg/blockdevice/util"
"github.com/autonomy/talos/internal/pkg/constants"
"github.com/autonomy/talos/internal/pkg/mount"
"github.com/autonomy/talos/internal/pkg/mount/cgroups"
"github.com/pkg/errors"
"golang.org/x/sys/unix"
)
// Initializer represents the early boot initialization control.
type Initializer struct {
prefix string
owned *mount.Points
special *mount.Points
}
// NewInitializer initializes and returns an Initializer struct.
func NewInitializer(prefix string) (initializer *Initializer, err error) {
special := mount.NewMountPoints()
special.Set("dev", mount.NewMountPoint("devtmpfs", "/dev", "devtmpfs", unix.MS_NOSUID, "mode=0755"))
special.Set("proc", mount.NewMountPoint("proc", "/proc", "proc", unix.MS_NOSUID|unix.MS_NOEXEC|unix.MS_NODEV, ""))
special.Set("sys", mount.NewMountPoint("sysfs", "/sys", "sysfs", unix.MS_NOSUID|unix.MS_NOEXEC|unix.MS_NODEV, ""))
special.Set("run", mount.NewMountPoint("tmpfs", "/run", "tmpfs", 0, ""))
special.Set("tmp", mount.NewMountPoint("tmpfs", "/tmp", "tmpfs", 0, ""))
initializer = &Initializer{
prefix: prefix,
special: special,
}
return initializer, nil
}
// Owned returns the OS owned block devices.
func (i *Initializer) Owned() *mount.Points {
return i.owned
}
// Special returns the special devices.
func (i *Initializer) Special() *mount.Points {
return i.special
}
// InitSpecial initializes and mounts the special devices in the early boot
// stage.
func (i *Initializer) InitSpecial() (err error) {
iter := i.special.Iter()
for iter.Next() {
if err = mount.WithRetry(iter.Value()); err != nil {
return errors.Errorf("error initializing special device at %s: %v", iter.Value().Target(), err)
}
}
if iter.Err() != nil {
return iter.Err()
}
return nil
}
// MoveSpecial moves the special device mount points to the new root.
func (i *Initializer) MoveSpecial() (err error) {
iter := i.special.Iter()
for iter.Next() {
mountpoint := mount.NewMountPoint(iter.Value().Target(), iter.Value().Target(), "", unix.MS_MOVE, "")
if err := mount.WithRetry(mountpoint, mount.WithPrefix(i.prefix)); err != nil {
return errors.Errorf("error moving mount point %s: %v", iter.Value().Target(), err)
}
}
if iter.Err() != nil {
return iter.Err()
}
if err := mount.WithRetry(mount.NewMountPoint("devpts", "/dev/pts", "devpts", unix.MS_NOSUID|unix.MS_NOEXEC, "ptmxmode=000,mode=620,gid=5"), mount.WithPrefix(i.prefix)); err != nil {
return errors.Errorf("error mounting mount point %s: %v", iter.Value().Target(), err)
}
return nil
}
// InitOwned initializes and mounts the OS owned block devices in the early boot
// stage.
func (i *Initializer) InitOwned() (err error) {
var owned *mount.Points
if owned, err = mountpoints(); err != nil {
return errors.Errorf("error initializing owned block devices: %v", err)
}
i.owned = owned
if mountpoint, ok := i.owned.Get(constants.DataPartitionLabel); ok {
if err = repair(mountpoint); err != nil {
return errors.Errorf("error fixing data partition: %v", err)
}
}
iter := i.owned.Iter()
for iter.Next() {
if err = mount.WithRetry(iter.Value(), mount.WithPrefix(i.prefix)); err != nil {
return errors.Errorf("error mounting partitions: %v", err)
}
}
if iter.Err() != nil {
return iter.Err()
}
if mountpoint, ok := i.owned.Get(constants.DataPartitionLabel); ok {
// NB: The XFS partition MUST be mounted, or this will fail.
if err = xfs.GrowFS(path.Join(i.prefix, mountpoint.Target())); err != nil {
return errors.Errorf("error growing data partition file system: %v", err)
}
}
return nil
}
// MountOwned mounts the OS owned block devices.
func (i *Initializer) MountOwned() (err error) {
iter := i.owned.Iter()
for iter.Next() {
if iter.Key() == constants.RootPartitionLabel {
if err = mount.WithRetry(iter.Value(), mount.WithPrefix(i.prefix), mount.WithReadOnly(true), mount.WithShared(true)); err != nil {
return errors.Errorf("error mounting partitions: %v", err)
}
} else {
if err = mount.WithRetry(iter.Value(), mount.WithPrefix(i.prefix)); err != nil {
return errors.Errorf("error mounting partitions: %v", err)
}
}
}
if iter.Err() != nil {
return iter.Err()
}
return nil
}
// UnmountOwned unmounts the OS owned block devices.
func (i *Initializer) UnmountOwned() (err error) {
iter := i.owned.IterRev()
for iter.Next() {
if err = mount.UnWithRetry(iter.Value(), mount.WithPrefix(i.prefix)); err != nil {
return errors.Errorf("error mounting partitions: %v", err)
}
}
if iter.Err() != nil {
return iter.Err()
}
return nil
}
// Switch moves the root to a specified directory. See
// https://github.com/karelzak/util-linux/blob/master/sys-utils/switch_root.c.
// nolint: gocyclo
func (i *Initializer) Switch() (err error) {
// Unmount the ROOT and DATA block devices.
if err = i.UnmountOwned(); err != nil {
return err
}
// Mount the ROOT and DATA block devices at the new root.
if err = i.MountOwned(); err != nil {
return errors.Wrap(err, "error mounting block device")
}
// Move the special mount points to the new root.
if err = i.MoveSpecial(); err != nil {
return errors.Wrap(err, "error moving special devices")
}
// Mount the cgroups to the new root.
if err = cgroups.Mount(i.prefix); err != nil {
return errors.Wrap(err, "error mounting cgroups")
}
if err = unix.Chdir(i.prefix); err != nil {
return errors.Wrapf(err, "error changing working directory to %s", i.prefix)
}
var old *os.File
if old, err = os.Open("/"); err != nil {
return errors.Wrap(err, "error opening /")
}
// nolint: errcheck
defer old.Close()
if err = unix.Mount(i.prefix, "/", "", unix.MS_MOVE, ""); err != nil {
return errors.Wrap(err, "error moving /")
}
if err = unix.Chroot("."); err != nil {
return errors.Wrap(err, "error chroot")
}
if err = recursiveDelete(int(old.Fd())); err != nil {
return errors.Wrap(err, "error deleting initramfs")
}
if err = unix.Exec("/proc/self/exe", []string{"exe", "--switch-root"}, []string{}); err != nil {
return errors.Wrap(err, "error executing /proc/self/exe")
}
return nil
}
func mountpoints() (mountpoints *mount.Points, err error) {
mountpoints = mount.NewMountPoints()
for _, name := range []string{constants.RootPartitionLabel, constants.DataPartitionLabel} {
var target string
switch name {
case constants.RootPartitionLabel:
target = "/"
case constants.DataPartitionLabel:
target = "/var"
}
var dev *probe.ProbedBlockDevice
if dev, err = probe.GetDevWithFileSystemLabel(name); err != nil {
return nil, errors.Errorf("failed to find device with label %s: %v", name, err)
}
mountpoint := mount.NewMountPoint(dev.Path, target, dev.SuperBlock.Type(), unix.MS_NOATIME, "")
mountpoints.Set(name, mountpoint)
}
return mountpoints, nil
}
func repair(mountpoint *mount.Point) (err error) {
var devname string
if devname, err = util.DevnameFromPartname(mountpoint.Source()); err != nil {
return err
}
bd, err := blockdevice.Open("/dev/" + devname)
if err != nil {
return errors.Errorf("error opening block device %q: %v", devname, err)
}
// nolint: errcheck
defer bd.Close()
pt, err := bd.PartitionTable(true)
if err != nil {
return err
}
if err := pt.Repair(); err != nil {
return err
}
for _, partition := range pt.Partitions() {
if partition.(*gptpartition.Partition).Name == constants.DataPartitionLabel {
if err := pt.Resize(partition); err != nil {
return err
}
}
}
if err := pt.Write(); err != nil {
return err
}
// Rereading the partition table requires that all partitions be unmounted
// or it will fail with EBUSY.
if err := bd.RereadPartitionTable(); err != nil {
return err
}
return nil
}
func recursiveDelete(fd int) error {
parentDev, err := getDev(fd)
if err != nil {
return err
}
dir := os.NewFile(uintptr(fd), "__ignored__")
// nolint: errcheck
defer dir.Close()
names, err := dir.Readdirnames(-1)
if err != nil {
return err
}
for _, name := range names {
if err := recusiveDeleteInner(fd, parentDev, name); err != nil {
return err
}
}
return nil
}
func recusiveDeleteInner(parentFd int, parentDev uint64, childName string) error {
childFd, err := unix.Openat(parentFd, childName, unix.O_DIRECTORY|unix.O_NOFOLLOW, unix.O_RDWR)
if err != nil {
if err := unix.Unlinkat(parentFd, childName, 0); err != nil {
return err
}
} else {
// nolint: errcheck
defer unix.Close(childFd)
if childFdDev, err := getDev(childFd); err != nil {
return err
} else if childFdDev != parentDev {
return nil
}
if err := recursiveDelete(childFd); err != nil {
return err
}
if err := unix.Unlinkat(parentFd, childName, unix.AT_REMOVEDIR); err != nil {
return err
}
}
return nil
}
func getDev(fd int) (dev uint64, err error) {
var stat unix.Stat_t
if err := unix.Fstat(fd, &stat); err != nil {
return 0, err
}
return stat.Dev, nil
}

View File

@ -1,124 +0,0 @@
// +build linux
package switchroot
import (
"os"
"syscall"
"github.com/autonomy/talos/internal/app/init/internal/mount"
"github.com/autonomy/talos/internal/app/init/internal/mount/cgroups"
"github.com/pkg/errors"
"golang.org/x/sys/unix"
)
func recursiveDelete(fd int) error {
parentDev, err := getDev(fd)
if err != nil {
return err
}
// The file descriptor is already open, but allocating a os.File here makes
// reading the files in the dir so much nicer.
dir := os.NewFile(uintptr(fd), "__ignored__")
// nolint: errcheck
defer dir.Close()
names, err := dir.Readdirnames(-1)
if err != nil {
return err
}
for _, name := range names {
// Loop here, but handle loop in separate function to make defer work as
// expected.
if err := recusiveDeleteInner(fd, parentDev, name); err != nil {
return err
}
}
return nil
}
func recusiveDeleteInner(parentFd int, parentDev uint64, childName string) error {
// O_DIRECTORY and O_NOFOLLOW make this open fail for all files and all
// symlinks (even when pointing to a dir). We need to filter out symlinks
// because getDev later follows them.
childFd, err := unix.Openat(parentFd, childName, unix.O_DIRECTORY|unix.O_NOFOLLOW, unix.O_RDWR)
if err != nil {
// childName points to either a file or a symlink, delete in any case.
if err := unix.Unlinkat(parentFd, childName, 0); err != nil {
return err
}
} else {
// Open succeeded, which means childName points to a real directory.
// nolint: errcheck
defer unix.Close(childFd)
// Don't descent into other file systems.
if childFdDev, err := getDev(childFd); err != nil {
return err
} else if childFdDev != parentDev {
// This means continue in recursiveDelete.
return nil
}
if err := recursiveDelete(childFd); err != nil {
return err
}
// Back from recursion, the directory is now empty, delete.
if err := unix.Unlinkat(parentFd, childName, unix.AT_REMOVEDIR); err != nil {
return err
}
}
return nil
}
func getDev(fd int) (dev uint64, err error) {
var stat unix.Stat_t
if err := unix.Fstat(fd, &stat); err != nil {
return 0, err
}
return stat.Dev, nil
}
// Switch performs a switch_root. The caller must ensure that the ROOT and DATA
// partitions are already mounted. See
// https://github.com/karelzak/util-linux/blob/master/sys-utils/switch_root.c
func Switch(s string) error {
// Mount the ROOT and DATA block devices at the new root.
if err := mount.Mount(s); err != nil {
return errors.Wrap(err, "error mounting block device")
}
// Move the special mount points to the new root.
if err := mount.Move(s); err != nil {
return errors.Wrap(err, "error moving special devices")
}
// Mount the cgroups file systems to the new root.
if err := cgroups.Mount(s); err != nil {
return errors.Wrap(err, "error mounting cgroups")
}
if err := unix.Chdir(s); err != nil {
return errors.Wrapf(err, "error changing working directory to %s", s)
}
oldRoot, err := os.Open("/")
if err != nil {
return errors.Wrap(err, "error opening /")
}
// nolint: errcheck
defer oldRoot.Close()
if err := mount.Finalize(s); err != nil {
return errors.Wrap(err, "error moving /")
}
if err := unix.Chroot("."); err != nil {
return errors.Wrap(err, "error chroot")
}
if err := recursiveDelete(int(oldRoot.Fd())); err != nil {
return errors.Wrap(err, "error deleting initramfs")
}
if err := syscall.Exec("/proc/self/exe", []string{"exe", "--switch-root"}, []string{}); err != nil {
return errors.Wrap(err, "error executing /proc/self/exe")
}
return nil
}

View File

@ -1,9 +1,5 @@
// +build linux
package main
import "C"
import (
"flag"
"fmt"
@ -11,10 +7,9 @@ import (
"os"
"time"
"github.com/autonomy/talos/internal/app/init/internal/mount"
"github.com/autonomy/talos/internal/app/init/internal/platform"
"github.com/autonomy/talos/internal/app/init/internal/rootfs"
"github.com/autonomy/talos/internal/app/init/internal/switchroot"
"github.com/autonomy/talos/internal/app/init/internal/rootfs/mount"
"github.com/autonomy/talos/internal/app/init/pkg/system"
"github.com/autonomy/talos/internal/app/init/pkg/system/services"
"github.com/autonomy/talos/internal/pkg/constants"
@ -46,54 +41,55 @@ func kmsg(prefix string) (*os.File, error) {
}
// nolint: gocyclo
func initram() error {
// Read the special filesystems and populate the mount point definitions.
if err := mount.InitSpecial(constants.NewRoot); err != nil {
func initram() (err error) {
var initializer *mount.Initializer
if initializer, err = mount.NewInitializer(constants.NewRoot); err != nil {
return err
}
// Mount the special devices.
if err = initializer.InitSpecial(); err != nil {
return err
}
// Setup logging to /dev/kmsg.
_, err := kmsg("[talos] [initramfs]")
_, err = kmsg("[talos] [initramfs]")
if err != nil {
return err
}
// Discover the platform.
log.Println("discovering the platform")
p, err := platform.NewPlatform()
if err != nil {
var p platform.Platform
if p, err = platform.NewPlatform(); err != nil {
return err
}
log.Printf("platform is: %s", p.Name())
// Retrieve the user data.
log.Printf("retrieving the user data for the platform: %s", p.Name())
data, err := p.UserData()
if err != nil {
log.Printf("retrieving the user data")
var data userdata.UserData
if data, err = p.UserData(); err != nil {
return err
}
// Perform rootfs/datafs installation if defined
if err := p.Install(data); err != nil {
// Perform rootfs/datafs installation if needed.
if err = p.Install(data); err != nil {
return err
}
// Read the block devices and populate the mount point definitions.
if err := mount.InitBlock(constants.NewRoot); err != nil {
// Mount the owned partitions.
log.Printf("mounting the partitions")
if err = initializer.InitOwned(); err != nil {
return err
}
log.Printf("preparing the node for the platform: %s", p.Name())
// Perform any tasks required by a particular platform.
if err := p.Prepare(data); err != nil {
log.Printf("performing platform specific tasks")
if err = p.Prepare(data); err != nil {
return err
}
// Prepare the necessary files in the rootfs.
log.Println("preparing the root filesystem")
if err := rootfs.Prepare(constants.NewRoot, data); err != nil {
return err
}
// Unmount the ROOT and DATA block devices.
log.Println("unmounting the ROOT and DATA partitions")
if err := mount.Unmount(); err != nil {
if err = rootfs.Prepare(constants.NewRoot, data); err != nil {
return err
}
// Perform the equivalent of switch_root.
log.Println("entering the new root")
if err := switchroot.Switch(constants.NewRoot); err != nil {
if err = initializer.Switch(); err != nil {
return err
}

View File

@ -0,0 +1,8 @@
package filesystem
// SuperBlocker describes the requirements for file system super blocks.
type SuperBlocker interface {
Is() bool
Offset() int64
Type() string
}

View File

@ -0,0 +1 @@
package iso9660

View File

@ -0,0 +1,19 @@
package iso9660
// Options is the functional options struct.
type Options struct {
}
// Option is the functional option func.
type Option func(*Options)
// NewDefaultOptions initializes a Options struct with default values.
func NewDefaultOptions(setters ...Option) *Options {
opts := &Options{}
for _, setter := range setters {
setter(opts)
}
return opts
}

View File

@ -0,0 +1,42 @@
package iso9660
import "bytes"
const (
// Magic is the ISO 9660 magic signature.
Magic = "CD001"
)
// SuperBlock represents the ISO 9660 super block.
type SuperBlock struct {
FType uint8
ID [5]uint8
Version uint8
Flags uint8
SystemID [32]uint8
VolumeID [32]uint8
_ [8]uint8
SpaceSize [8]uint8
EscapeSequences [8]uint8
_ [222]uint8
PublisherID [128]uint8
_ [128]uint8
ApplicationID [128]uint8
_ [111]uint8
}
// Is implements the SuperBlocker interface.
func (sb *SuperBlock) Is() bool {
trimmed := bytes.Trim(sb.ID[:], " ")
return bytes.Equal(trimmed, []byte(Magic))
}
// Offset implements the SuperBlocker interface.
func (sb *SuperBlock) Offset() int64 {
return 0x8000
}
// Type implements the SuperBlocker interface.
func (sb *SuperBlock) Type() string {
return "iso9660"
}

View File

@ -0,0 +1,19 @@
package vfat
// Options is the functional options struct.
type Options struct {
}
// Option is the functional option func.
type Option func(*Options)
// NewDefaultOptions initializes a Options struct with default values.
func NewDefaultOptions(setters ...Option) *Options {
opts := &Options{}
for _, setter := range setters {
setter(opts)
}
return opts
}

View File

@ -0,0 +1,57 @@
package vfat
import (
"bytes"
)
const (
// Magic is the VFAT magic signature.
Magic = "FAT32"
)
// SuperBlock represents the vfat super block.
type SuperBlock struct {
Ignored [3]uint8
Sysid [8]uint8
SectorSize [2]uint8
ClusterSize uint8
Reserved uint16
Fats uint8
DirEntries [2]uint8
Sectors [2]uint8
Media uint8
FatLength uint16
SecsTrack uint16
Heads uint16
Hidden uint32
TotalSect uint32
Fat32Length uint32
Flags uint16
Version [2]uint8
RootCluster uint32
FsinfoSector uint16
BackupBoot uint16
Reserved2 [6]uint16
Unknown [3]uint8
Serno [4]uint8
Label [11]uint8
Magic [8]uint8
Dummy2 [0x1fe - 0x5a]uint8
Pmagic [2]uint8
}
// Is implements the SuperBlocker interface.
func (sb *SuperBlock) Is() bool {
trimmed := bytes.Trim(sb.Magic[:], " ")
return bytes.Equal(trimmed, []byte(Magic))
}
// Offset implements the SuperBlocker interface.
func (sb *SuperBlock) Offset() int64 {
return 0x0
}
// Type implements the SuperBlocker interface.
func (sb *SuperBlock) Type() string {
return "fat32"
}

View File

@ -0,0 +1 @@
package vfat

View File

@ -0,0 +1,57 @@
package xfs
const (
// Magic is the XFS magic number.
Magic = 0x58465342
)
// SuperBlock represents the xfs super block.
type SuperBlock struct {
Magic uint32
Blocksize uint32
Dblocks uint64
Rblocks uint64
Rextents uint64
UUID [16]uint8
Logstart uint64
Rootino uint64
Rbmino uint64
Rsumino uint64
Rextsize uint32
Agblocks uint32
Agcount uint32
Rbmblocks uint32
Logblocks uint32
Versionnum uint16
Sectsize uint16
Inodesize uint16
Inopblock uint16
Fname [12]uint8
Blocklog uint8
Sectlog uint8
Inodelog uint8
Inopblog uint8
Agblklog uint8
Rextslog uint8
Inprogress uint8
ImaxPct uint8
Icount uint64
Ifree uint64
Fdblocks uint64
Frextents uint64
}
// Is implements the SuperBlocker interface.
func (sb *SuperBlock) Is() bool {
return sb.Magic == Magic
}
// Offset implements the SuperBlocker interface.
func (sb *SuperBlock) Offset() int64 {
return 0x0
}
// Type implements the SuperBlocker interface.
func (sb *SuperBlock) Type() string {
return "xfs"
}

View File

@ -0,0 +1,133 @@
package probe
import (
"bytes"
"encoding/binary"
"fmt"
"io"
"io/ioutil"
"os"
"github.com/autonomy/talos/internal/pkg/blockdevice"
"github.com/autonomy/talos/internal/pkg/blockdevice/filesystem"
"github.com/autonomy/talos/internal/pkg/blockdevice/filesystem/iso9660"
"github.com/autonomy/talos/internal/pkg/blockdevice/filesystem/vfat"
"github.com/autonomy/talos/internal/pkg/blockdevice/filesystem/xfs"
"github.com/pkg/errors"
)
// ProbedBlockDevice represents a probed block device.
type ProbedBlockDevice struct {
*blockdevice.BlockDevice
SuperBlock filesystem.SuperBlocker
Path string
}
// All probes a block device's file system for the given label.
func All() (probed []*ProbedBlockDevice, err error) {
var infos []os.FileInfo
if infos, err = ioutil.ReadDir("/sys/block"); err != nil {
return nil, err
}
probe := func(devpath string) (sb filesystem.SuperBlocker) {
// nolint: errcheck
sb, _ = FileSystem(devpath)
return sb
}
for _, info := range infos {
var sb filesystem.SuperBlocker
devpath := "/dev/" + info.Name()
bd, err := blockdevice.Open(devpath)
if err != nil {
// A partition table was not found, but it is still possible that a
// file system exists without a partition table.
if sb = probe(devpath); sb != nil {
probed = append(probed, &ProbedBlockDevice{BlockDevice: bd, SuperBlock: sb, Path: devpath})
}
continue
}
pt, err := bd.PartitionTable(true)
if err != nil {
// A partition table was not found, and we have already checked for
// a file system on the block device.
continue
}
// A partition table was found, now probe each partition's file system.
for _, p := range pt.Partitions() {
devpath = fmt.Sprintf("/dev/%s%d", info.Name(), p.No())
if sb = probe(devpath); sb != nil {
probed = append(probed, &ProbedBlockDevice{BlockDevice: bd, SuperBlock: sb, Path: devpath})
}
}
}
return probed, nil
}
// FileSystem probes the provided path's file system.
func FileSystem(path string) (sb filesystem.SuperBlocker, err error) {
var f *os.File
if f, err = os.Open(path); err != nil {
return nil, err
}
// nolint: errcheck
defer f.Close()
superblocks := []filesystem.SuperBlocker{
&iso9660.SuperBlock{},
&vfat.SuperBlock{},
&xfs.SuperBlock{},
}
for _, sb := range superblocks {
if _, err = f.Seek(sb.Offset(), io.SeekStart); err != nil {
return nil, err
}
err = binary.Read(f, binary.BigEndian, sb)
if err != nil {
return nil, err
}
if sb.Is() {
return sb, nil
}
}
return nil, nil
}
// GetDevWithFileSystemLabel probes a block device's file system for the given label.
func GetDevWithFileSystemLabel(value string) (probe *ProbedBlockDevice, err error) {
var probed []*ProbedBlockDevice
if probed, err = All(); err != nil {
return nil, err
}
for _, probe = range probed {
switch sb := probe.SuperBlock.(type) {
case *iso9660.SuperBlock:
trimmed := bytes.Trim(sb.VolumeID[:], " \x00")
if bytes.Equal(trimmed, []byte(value)) {
return probe, nil
}
case *vfat.SuperBlock:
trimmed := bytes.Trim(sb.Label[:], " \x00")
if bytes.Equal(trimmed, []byte(value)) {
return probe, nil
}
case *xfs.SuperBlock:
trimmed := bytes.Trim(sb.Fname[:], " \x00")
if bytes.Equal(trimmed, []byte(value)) {
return probe, nil
}
}
}
return nil, errors.Errorf("no device found with label %s", value)
}

View File

@ -7,11 +7,11 @@ import (
"syscall"
"unsafe"
"github.com/autonomy/talos/internal/pkg/blockdevice/pkg/lba"
"github.com/autonomy/talos/internal/pkg/blockdevice/pkg/serde"
"github.com/autonomy/talos/internal/pkg/blockdevice/lba"
"github.com/autonomy/talos/internal/pkg/blockdevice/table"
"github.com/autonomy/talos/internal/pkg/blockdevice/table/gpt/header"
"github.com/autonomy/talos/internal/pkg/blockdevice/table/gpt/partition"
"github.com/autonomy/talos/internal/pkg/serde"
"github.com/google/uuid"
"github.com/pkg/errors"
"golang.org/x/sys/unix"

View File

@ -7,8 +7,8 @@ import (
"fmt"
"hash/crc32"
"github.com/autonomy/talos/internal/pkg/blockdevice/pkg/lba"
"github.com/autonomy/talos/internal/pkg/blockdevice/pkg/serde"
"github.com/autonomy/talos/internal/pkg/blockdevice/lba"
"github.com/autonomy/talos/internal/pkg/serde"
"github.com/google/uuid"
)

View File

@ -6,7 +6,7 @@ import (
"encoding/binary"
"fmt"
"github.com/autonomy/talos/internal/pkg/blockdevice/pkg/serde"
"github.com/autonomy/talos/internal/pkg/serde"
"github.com/google/uuid"
"golang.org/x/text/encoding/unicode"
)

View File

@ -1,7 +1,7 @@
// Package table provides a library for working with block device partition tables.
package table
import "github.com/autonomy/talos/internal/pkg/blockdevice/pkg/serde"
import "github.com/autonomy/talos/internal/pkg/serde"
// Table represents a partition table.
type Table = []byte

View File

@ -0,0 +1,36 @@
package util
import (
"strings"
"github.com/pkg/errors"
)
// PartNo returns the partition number.
func PartNo(partname string) (partno string, err error) {
partname = strings.TrimPrefix(partname, "/dev/")
if strings.HasPrefix(partname, "nvme") {
idx := strings.Index(partname, "p")
return partname[idx+1:], nil
} else if strings.HasPrefix(partname, "sd") || strings.HasPrefix(partname, "hd") || strings.HasPrefix(partname, "vd") {
return strings.TrimLeft(partname, "/abcdefghijklmnopqrstuvwxyz"), nil
}
return "", errors.New("could not determine partition number from partition name")
}
// DevnameFromPartname returns the device name from a partition name.
func DevnameFromPartname(partname string) (devname string, err error) {
partname = strings.TrimPrefix(partname, "/dev/")
var partno string
if partno, err = PartNo(partname); err != nil {
return "", err
}
if strings.HasPrefix(partname, "nvme") {
return strings.TrimRight(partname, "p"+partno), nil
} else if strings.HasPrefix(partname, "sd") || strings.HasPrefix(partname, "hd") || strings.HasPrefix(partname, "vd") {
return strings.TrimRight(partname, partno), nil
}
return "", errors.New("could not determine dev name from partition name")
}

View File

@ -17,49 +17,64 @@ func Test_PartNo(t *testing.T) {
{
name: "hda1",
args: args{
devname: "/dev/hda1",
devname: "hda1",
},
want: "1",
},
{
name: "hda10",
args: args{
devname: "/dev/hda10",
devname: "hda10",
},
want: "10",
},
{
name: "sda1",
args: args{
devname: "/dev/sda1",
devname: "sda1",
},
want: "1",
},
{
name: "sda10",
args: args{
devname: "/dev/sda10",
devname: "sda10",
},
want: "10",
},
{
name: "nvme1n2p2",
args: args{
devname: "/dev/nvme1n2p2",
devname: "nvme1n2p2",
},
want: "2",
},
{
name: "nvme1n2p11",
args: args{
devname: "/dev/nvme1n2p11",
devname: "nvme1n2p11",
},
want: "11",
},
{
name: "vda1",
args: args{
devname: "vda1",
},
want: "1",
},
{
name: "vda10",
args: args{
devname: "vda10",
},
want: "10",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := PartNo(tt.args.devname); got != tt.want {
// nolint: errcheck
if got, _ := PartNo(tt.args.devname); got != tt.want {
t.Errorf("PartNo() = %v, want %v", got, tt.want)
}
})
@ -79,23 +94,40 @@ func Test_DevnameFromPartname(t *testing.T) {
{
name: "hda1",
args: args{
devname: "/dev/hda1",
partno: PartNo("/dev/hda1"),
devname: "hda1",
partno: "1",
},
want: "/dev/hda",
want: "hda",
},
{
name: "sda1",
args: args{
devname: "sda1",
partno: "1",
},
want: "sda",
},
{
name: "vda1",
args: args{
devname: "vda1",
partno: "1",
},
want: "vda",
},
{
name: "nvme1n2p11",
args: args{
devname: "/dev/nvme1n2p11",
partno: PartNo("/dev/nvme1n2p11"),
devname: "nvme1n2p11",
partno: "11",
},
want: "/dev/nvme1n2",
want: "nvme1n2",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := DevnameFromPartname(tt.args.devname, tt.args.partno); got != tt.want {
// nolint: errcheck
if got, _ := DevnameFromPartname(tt.args.devname); got != tt.want {
t.Errorf("DevnameFromPartname() = %v, want %v", got, tt.want)
}
})

103
internal/pkg/mount/iter.go Normal file
View File

@ -0,0 +1,103 @@
package mount
// PointsIterator represents an iteratable group of mount points.
type PointsIterator struct {
p *Points
value *Point
key string
index int
end int
err error
reverse bool
}
// Iter initializes and returns a mount point iterator.
func (p *Points) Iter() *PointsIterator {
return &PointsIterator{
p: p,
index: -1,
end: len(p.order) - 1,
value: nil,
}
}
// IterRev initializes and returns a mount point iterator that advances in
// reverse.
func (p *Points) IterRev() *PointsIterator {
return &PointsIterator{
p: p,
reverse: true,
index: len(p.points),
end: 0,
value: nil,
}
}
// Set sets an ordered value.
func (p *Points) Set(key string, value *Point) {
if _, ok := p.points[key]; ok {
for i := range p.order {
if p.order[i] == key {
p.order = append(p.order[:i], p.order[i+1:]...)
}
}
}
p.order = append(p.order, key)
p.points[key] = value
}
// Get gets an ordered value.
func (p *Points) Get(key string) (value *Point, ok bool) {
if value, ok = p.points[key]; ok {
return value, true
}
return nil, false
}
// Key returns the current key.
func (i *PointsIterator) Key() string {
return i.key
}
// Value returns current mount point.
func (i *PointsIterator) Value() *Point {
if i.err != nil || i.index > len(i.p.points) {
panic("invoked Value on expired iterator")
}
return i.value
}
// Err returns an error.
func (i *PointsIterator) Err() error {
return i.err
}
// Next advances the iterator to the next value.
func (i *PointsIterator) Next() bool {
if i.err != nil {
return false
}
if i.reverse {
i.index--
if i.index < i.end {
return false
}
} else {
i.index++
if i.index > i.end {
return false
}
}
i.key = i.p.order[i.index]
i.value = i.p.points[i.key]
if i.reverse {
return i.index >= i.end
}
return i.index <= i.end
}

145
internal/pkg/mount/mount.go Normal file
View File

@ -0,0 +1,145 @@
package mount
import (
"os"
"path"
"time"
"github.com/pkg/errors"
"golang.org/x/sys/unix"
)
// Point represents a linux mount point.
type Point struct {
source string
target string
fstype string
flags uintptr
data string
}
// PointMap represents a unique set of mount points.
type PointMap = map[string]*Point
// Points represents an ordered set of mount points.
type Points struct {
points PointMap
order []string
}
// NewMountPoint initializes and returns a Point struct.
func NewMountPoint(source string, target string, fstype string, flags uintptr, data string) *Point {
return &Point{
source: source,
target: target,
fstype: fstype,
flags: flags,
data: data,
}
}
// NewMountPoints initializes and returns a Points struct.
func NewMountPoints() *Points {
return &Points{
points: make(PointMap, 0),
}
}
// Source returns the mount points source field.
func (p *Point) Source() string {
return p.source
}
// Target returns the mount points target field.
func (p *Point) Target() string {
return p.target
}
// Fstype returns the mount points fstype field.
func (p *Point) Fstype() string {
return p.fstype
}
// Flags returns the mount points flags field.
func (p *Point) Flags() uintptr {
return p.flags
}
// Data returns the mount points data field.
func (p *Point) Data() string {
return p.data
}
// WithRetry attempts to retry a mount on EBUSY. It will attempt a retry
// every 100 milliseconds over the course of 5 seconds.
func WithRetry(mountpoint *Point, setters ...Option) (err error) {
opts := NewDefaultOptions(setters...)
if opts.ReadOnly {
mountpoint.flags |= unix.O_RDONLY
}
target := path.Join(opts.Prefix, mountpoint.target)
if err = os.MkdirAll(target, os.ModeDir); err != nil {
return errors.Errorf("error creating mount point directory %s: %v", target, err)
}
retry := func(source string, target string, fstype string, flags uintptr, data string) error {
for i := 0; i < 50; i++ {
if err = unix.Mount(source, target, fstype, flags, data); err != nil {
switch err {
case unix.EBUSY:
time.Sleep(100 * time.Millisecond)
continue
default:
return err
}
}
return nil
}
return errors.Errorf("mount timeout: %v", err)
}
if err = retry(mountpoint.source, target, mountpoint.fstype, mountpoint.flags, mountpoint.data); err != nil {
return err
}
if opts.Shared {
if err = retry("", target, "", unix.MS_SHARED, ""); err != nil {
return errors.Errorf("error mounting shared mount point %s: %v", target, err)
}
}
return nil
}
// UnWithRetry attempts to retry an unmount on EBUSY. It will attempt a
// retry every 100 milliseconds over the course of 5 seconds.
func UnWithRetry(mountpoint *Point, setters ...Option) (err error) {
opts := NewDefaultOptions(setters...)
retry := func(target string, flags int) error {
for i := 0; i < 50; i++ {
if err = unix.Unmount(target, flags); err != nil {
switch err {
case unix.EBUSY:
time.Sleep(100 * time.Millisecond)
continue
default:
return err
}
}
return nil
}
return errors.Errorf("mount timeout: %v", err)
}
target := path.Join(opts.Prefix, mountpoint.target)
if err := retry(target, 0); err != nil {
return err
}
return nil
}

View File

@ -0,0 +1,47 @@
package mount
// Options is the functional options struct.
type Options struct {
Prefix string
ReadOnly bool
Shared bool
}
// Option is the functional option func.
type Option func(*Options)
// WithPrefix is a functional option for setting the mount point prefix.
func WithPrefix(o string) Option {
return func(args *Options) {
args.Prefix = o
}
}
// WithReadOnly is a functional option for setting the mount point as readonly.
func WithReadOnly(o bool) Option {
return func(args *Options) {
args.ReadOnly = o
}
}
// WithShared is a functional option for setting the mount point as shared.
func WithShared(o bool) Option {
return func(args *Options) {
args.Shared = o
}
}
// NewDefaultOptions initializes a Options struct with default values.
func NewDefaultOptions(setters ...Option) *Options {
opts := &Options{
Prefix: "",
ReadOnly: false,
Shared: false,
}
for _, setter := range setters {
setter(opts)
}
return opts
}