feat: add filesystem probing library (#298)
This commit is contained in:
parent
7918d38e0f
commit
42b722b0eb
11
Dockerfile
11
Dockerfile
@ -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
6
go.mod
@ -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
11
go.sum
@ -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=
|
||||
|
@ -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
|
||||
|
@ -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 ""
|
||||
}
|
@ -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)
|
||||
}
|
@ -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 ""
|
||||
}
|
@ -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
|
||||
|
@ -1,5 +1,3 @@
|
||||
// +build linux
|
||||
|
||||
package aws
|
||||
|
||||
import (
|
||||
|
@ -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"
|
||||
)
|
||||
|
||||
|
@ -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"
|
||||
)
|
||||
|
||||
|
@ -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.
|
||||
|
336
internal/app/init/internal/rootfs/mount/mount.go
Normal file
336
internal/app/init/internal/rootfs/mount/mount.go
Normal 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
|
||||
}
|
@ -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
|
||||
}
|
@ -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
|
||||
}
|
||||
|
||||
|
8
internal/pkg/blockdevice/filesystem/fs.go
Normal file
8
internal/pkg/blockdevice/filesystem/fs.go
Normal 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
|
||||
}
|
1
internal/pkg/blockdevice/filesystem/iso9660/iso9660.go
Normal file
1
internal/pkg/blockdevice/filesystem/iso9660/iso9660.go
Normal file
@ -0,0 +1 @@
|
||||
package iso9660
|
19
internal/pkg/blockdevice/filesystem/iso9660/options.go
Normal file
19
internal/pkg/blockdevice/filesystem/iso9660/options.go
Normal 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
|
||||
}
|
42
internal/pkg/blockdevice/filesystem/iso9660/superblock.go
Normal file
42
internal/pkg/blockdevice/filesystem/iso9660/superblock.go
Normal 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"
|
||||
}
|
19
internal/pkg/blockdevice/filesystem/vfat/options.go
Normal file
19
internal/pkg/blockdevice/filesystem/vfat/options.go
Normal 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
|
||||
}
|
57
internal/pkg/blockdevice/filesystem/vfat/superblock.go
Normal file
57
internal/pkg/blockdevice/filesystem/vfat/superblock.go
Normal 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"
|
||||
}
|
1
internal/pkg/blockdevice/filesystem/vfat/vfat.go
Normal file
1
internal/pkg/blockdevice/filesystem/vfat/vfat.go
Normal file
@ -0,0 +1 @@
|
||||
package vfat
|
57
internal/pkg/blockdevice/filesystem/xfs/superblock.go
Normal file
57
internal/pkg/blockdevice/filesystem/xfs/superblock.go
Normal 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"
|
||||
}
|
133
internal/pkg/blockdevice/probe/probe.go
Normal file
133
internal/pkg/blockdevice/probe/probe.go
Normal 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)
|
||||
}
|
@ -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"
|
||||
|
@ -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"
|
||||
)
|
||||
|
||||
|
@ -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"
|
||||
)
|
||||
|
@ -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
|
||||
|
36
internal/pkg/blockdevice/util/util.go
Normal file
36
internal/pkg/blockdevice/util/util.go
Normal 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")
|
||||
}
|
@ -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
103
internal/pkg/mount/iter.go
Normal 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
145
internal/pkg/mount/mount.go
Normal 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
|
||||
}
|
47
internal/pkg/mount/options.go
Normal file
47
internal/pkg/mount/options.go
Normal 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
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user