forked from Ivasoft/device-mapping-manager
All checks were successful
continuous-integration/drone/push Build is passing
225 lines
5.2 KiB
Go
225 lines
5.2 KiB
Go
//go:build linux
|
|
|
|
package main
|
|
|
|
// #include "ctypes.h"
|
|
import "C"
|
|
import (
|
|
"context"
|
|
"device-volume-driver/internal/cgroup"
|
|
"fmt"
|
|
"log"
|
|
"os"
|
|
"os/user"
|
|
"path"
|
|
"path/filepath"
|
|
"strconv"
|
|
"strings"
|
|
|
|
"github.com/docker/docker/api/types"
|
|
"github.com/docker/docker/api/types/filters"
|
|
"github.com/docker/docker/client"
|
|
"github.com/docker/go-plugins-helpers/volume"
|
|
_ "github.com/opencontainers/runtime-spec/specs-go"
|
|
"golang.org/x/sys/unix"
|
|
)
|
|
|
|
const pluginId = "dvd"
|
|
const rootPath = "/host"
|
|
|
|
type fakeDriver struct {
|
|
}
|
|
|
|
func Ptr[T any](v T) *T {
|
|
return &v
|
|
}
|
|
|
|
func main() {
|
|
listenForMounts()
|
|
}
|
|
|
|
func getDeviceInfo(devicePath string) (string, int64, int64, error) {
|
|
var stat unix.Stat_t
|
|
|
|
if err := unix.Stat(devicePath, &stat); err != nil {
|
|
log.Println(err)
|
|
return "", -1, -1, err
|
|
}
|
|
|
|
var deviceType string
|
|
|
|
switch stat.Mode & unix.S_IFMT {
|
|
case unix.S_IFBLK:
|
|
deviceType = "b"
|
|
case unix.S_IFCHR:
|
|
deviceType = "c"
|
|
default:
|
|
log.Println("aborting: device is neither a character or block device")
|
|
return "", -1, -1, fmt.Errorf("unsupported device type... aborting")
|
|
}
|
|
|
|
major := int64(unix.Major(stat.Rdev))
|
|
minor := int64(unix.Minor(stat.Rdev))
|
|
|
|
log.Printf("Found device: %s %s %d:%d\n", devicePath, deviceType, major, minor)
|
|
|
|
return deviceType, major, minor, nil
|
|
}
|
|
|
|
func listenForMounts() {
|
|
ctx := context.Background()
|
|
|
|
d := &fakeDriver{}
|
|
h := volume.NewHandler(d)
|
|
u, _ := user.Lookup("root")
|
|
gid, _ := strconv.Atoi(u.Gid)
|
|
go h.ServeUnix("/run/docker/plugins/dvd.sock", gid)
|
|
|
|
cli, err := client.NewClientWithOpts(client.FromEnv, client.WithAPIVersionNegotiation())
|
|
|
|
if err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
|
|
defer cli.Close()
|
|
|
|
msgs, errs := cli.Events(
|
|
ctx,
|
|
types.EventsOptions{Filters: filters.NewArgs(filters.Arg("event", "start"))},
|
|
)
|
|
|
|
for {
|
|
select {
|
|
case err := <-errs:
|
|
log.Fatal(err)
|
|
case msg := <-msgs:
|
|
info, err := cli.ContainerInspect(ctx, msg.Actor.ID)
|
|
|
|
if err != nil {
|
|
panic(err)
|
|
} else {
|
|
pid := info.State.Pid
|
|
version, err := cgroup.GetDeviceCGroupVersion(rootPath, pid)
|
|
log.Printf("The cgroup version for process %d is: %v\n", pid, version)
|
|
if err != nil {
|
|
log.Println(err)
|
|
break
|
|
}
|
|
|
|
log.Printf("Checking mounts for process %d\n", pid)
|
|
|
|
for _, mount := range info.Mounts {
|
|
log.Printf(
|
|
"%s/%v requested a volume mount for %s at %s\n",
|
|
msg.Actor.ID, info.State.Pid, mount.Source, mount.Destination,
|
|
)
|
|
|
|
if !strings.HasPrefix(mount.Source, "/dev") {
|
|
log.Printf("%s is not a device... skipping\n", mount.Source)
|
|
continue
|
|
}
|
|
|
|
api, err := cgroup.New(version)
|
|
cgroupPath, sysfsPath, err := api.GetDeviceCGroupMountPath(rootPath, pid)
|
|
|
|
if err != nil {
|
|
log.Println(err)
|
|
break
|
|
}
|
|
|
|
cgroupPath = path.Join(rootPath, sysfsPath, cgroupPath)
|
|
|
|
log.Printf("The cgroup path for process %d is at %v\n", pid, cgroupPath)
|
|
|
|
if fileInfo, err := os.Stat(mount.Source); err != nil {
|
|
log.Println(err)
|
|
continue
|
|
} else {
|
|
if fileInfo.IsDir() {
|
|
err := filepath.Walk(mount.Source,
|
|
func(path string, info os.FileInfo, err error) error {
|
|
if err != nil {
|
|
return err
|
|
} else if info.IsDir() {
|
|
return nil
|
|
} else if err = applyDeviceRules(api, path, cgroupPath, pid); err != nil {
|
|
log.Println(err)
|
|
}
|
|
return nil
|
|
})
|
|
if err != nil {
|
|
log.Println(err)
|
|
}
|
|
} else {
|
|
if err = applyDeviceRules(api, mount.Source, cgroupPath, pid); err != nil {
|
|
log.Println(err)
|
|
}
|
|
}
|
|
}
|
|
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func applyDeviceRules(api cgroup.Interface, mountPath string, cgroupPath string, pid int) error {
|
|
deviceType, major, minor, err := getDeviceInfo(mountPath)
|
|
|
|
if err != nil {
|
|
log.Println(err)
|
|
return err
|
|
} else {
|
|
log.Printf("Adding device rule for process %d at %s\n", pid, cgroupPath)
|
|
err = api.AddDeviceRules(cgroupPath, []cgroup.DeviceRule{
|
|
{
|
|
Access: "rwm",
|
|
Major: Ptr[int64](major),
|
|
Minor: Ptr[int64](minor),
|
|
Type: deviceType,
|
|
Allow: true,
|
|
},
|
|
})
|
|
|
|
if err != nil {
|
|
log.Println(err)
|
|
return err
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (d *fakeDriver) Capabilities() *volume.CapabilitiesResponse {
|
|
return &volume.CapabilitiesResponse{Capabilities: volume.Capability{Scope: "local"}}
|
|
}
|
|
|
|
func (d *fakeDriver) Create(r *volume.CreateRequest) error {
|
|
return fmt.Errorf("This driver device-mapping-manager cannot create volumes.")
|
|
}
|
|
|
|
func (d *fakeDriver) List() (*volume.ListResponse, error) {
|
|
var vols []*volume.Volume
|
|
return &volume.ListResponse{Volumes: vols}, nil
|
|
}
|
|
|
|
func (d *fakeDriver) Get(r *volume.GetRequest) (*volume.GetResponse, error) {
|
|
return &volume.GetResponse{}, fmt.Errorf("volume %s does not exist", r.Name)
|
|
}
|
|
|
|
func (d *fakeDriver) Remove(r *volume.RemoveRequest) error {
|
|
return fmt.Errorf("volume %s does not exist", r.Name)
|
|
}
|
|
|
|
func (d *fakeDriver) Path(r *volume.PathRequest) (*volume.PathResponse, error) {
|
|
return &volume.PathResponse{}, fmt.Errorf("volume %s does not exist", r.Name)
|
|
}
|
|
|
|
func (d *fakeDriver) Mount(r *volume.MountRequest) (*volume.MountResponse, error) {
|
|
return &volume.MountResponse{}, fmt.Errorf("volume %s does not exist", r.Name)
|
|
}
|
|
|
|
func (d *fakeDriver) Unmount(r *volume.UnmountRequest) error {
|
|
return fmt.Errorf("volume %s does not exist", r.Name)
|
|
}
|