Skip to content

Commit 4451f3c

Browse files
committed
wip 2
Signed-off-by: Harsh Rawat <harshrawat@microsoft.com>
1 parent 9fc8315 commit 4451f3c

11 files changed

Lines changed: 718 additions & 24 deletions

File tree

internal/controller/mount/doc.go

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
//go:build windows
2+
3+
// Package mount manages the lifecycle of guest-level mounts for Hyper-V
4+
// utility VMs.
5+
//
6+
// The [Manager] type is the primary entry point. It exposes a unified
7+
// [Controller] interface that handles guest mounts for both SCSI disks and
8+
// Plan9 shares, with a dedicated Mount/Unmount API for each device type.
9+
//
10+
// # Device controller integration
11+
//
12+
// This package is designed to work downstream of the device controllers:
13+
//
14+
// - After [scsi.Controller.AttachDiskToVM] returns a [VMSlot], pass its
15+
// Controller and LUN fields to [Controller.MountSCSI].
16+
// - After [plan9.Controller.AddToVM] returns a shareName, pass it to
17+
// [Controller.MountPlan9].
18+
//
19+
// The device controller owns the host-side lifetime of the device. The mount
20+
// controller owns only the guest-side mount. Callers must ensure the device
21+
// is attached before mounting, and must unmount before detaching.
22+
//
23+
// # SCSI mounts
24+
//
25+
// SCSI mounts are supported on both LCOW and WCOW guests. The Manager
26+
// delegates to [linuxGuestSCSI] or [windowsGuestSCSI] based on the build
27+
// tag (linux container vs. windows container).
28+
//
29+
// # Plan9 mounts
30+
//
31+
// Plan9 mounts are LCOW-only (Plan9 is a Linux guest protocol). On WCOW
32+
// builds, [Controller.MountPlan9] and [Controller.UnmountPlan9] return an
33+
// error immediately. The Manager delegates to [linuxGuestPlan9] for
34+
// the actual GCS add/remove-mapped-directory requests.
35+
//
36+
// # Reference counting
37+
//
38+
// Both SCSI and Plan9 mounts are ref-counted. When two callers mount the
39+
// same resource at the same guest path with the same config, they share a
40+
// single mount entry. The guest-side unmount only happens when the last
41+
// reference is released.
42+
//
43+
// # State machine
44+
//
45+
// Every mount (SCSI or Plan9) carries a [mountState]. States move forward
46+
// only; on partial failure the state records the furthest point reached so
47+
// that a retry never repeats work that already succeeded.
48+
//
49+
// Mount lifecycle:
50+
//
51+
// (none) ─ mountInGuest ─────────────► Mounted
52+
// Mounted ─ unmountFromGuest ────────► Unmounted
53+
//
54+
// # Error handling
55+
//
56+
// During the mount phase, if the GCS call fails, the mount is not recorded —
57+
// control returns to the caller with an error.
58+
//
59+
// During unmount, errors do not restore the refCount so that a retry knows
60+
// whether the unmount was attempted.
61+
package mount
Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
1+
//go:build windows
2+
3+
package mount
4+
5+
import (
6+
"context"
7+
"sync"
8+
9+
"github.com/Microsoft/hcsshim/internal/protocol/guestresource"
10+
)
11+
12+
// Controller exposes unified guest mount operations for SCSI and Plan9 devices.
13+
//
14+
// Each device type has its own dedicated Mount/Unmount API. Callers use the
15+
// output of the corresponding device controller's Add call to drive the mount:
16+
// - SCSI: [scsi.Controller.AttachDiskToVM] returns a VMSlot → [Controller.MountSCSI]
17+
// - Plan9: [plan9.Controller.AddToVM] returns a shareName → [Controller.MountPlan9]
18+
type Controller interface {
19+
// MountSCSI mounts a SCSI disk (identified by controller + LUN) inside the
20+
// guest at the path described by cfg. Returns the resolved guest path.
21+
MountSCSI(ctx context.Context, controller, lun uint, cfg SCSIMountConfig) (string, error)
22+
23+
// UnmountSCSI releases a previously mounted SCSI guest path.
24+
UnmountSCSI(ctx context.Context, guestPath string) error
25+
26+
// MountPlan9 mounts a Plan9 share (identified by shareName returned from the
27+
// Plan9 device controller's AddToVM) inside the guest. Returns the resolved
28+
// guest path.
29+
MountPlan9(ctx context.Context, shareName string, cfg Plan9MountConfig) (string, error)
30+
31+
// UnmountPlan9 releases a previously mounted Plan9 guest path.
32+
UnmountPlan9(ctx context.Context, guestPath string) error
33+
}
34+
35+
// todo: maybe have a Linux controller vs windows controller
36+
37+
// SCSIMountConfig describes how a SCSI disk should be mounted inside the guest.
38+
type SCSIMountConfig struct {
39+
// GuestPath is the path inside the guest where the disk will be mounted.
40+
// If empty, a unique path is generated automatically.
41+
GuestPath string
42+
// Partition is the target partition index (1-based) on a partitioned device.
43+
// Only supported for LCOW.
44+
Partition uint64
45+
// ReadOnly mounts the disk read-only.
46+
ReadOnly bool
47+
// Encrypted encrypts the device with dm-crypt. Only supported for LCOW.
48+
Encrypted bool
49+
// Options are mount flags or data passed to the guest mount call. Only supported for LCOW.
50+
Options []string
51+
// EnsureFilesystem formats the mount as Filesystem if not already formatted.
52+
// Only supported for LCOW.
53+
EnsureFilesystem bool
54+
// Filesystem is the target filesystem type. Only supported for LCOW.
55+
Filesystem string
56+
// BlockDev mounts the device as a block device. Only supported for LCOW.
57+
BlockDev bool
58+
// FormatWithRefs formats the disk using refs. Only supported for WCOW scratch disks.
59+
FormatWithRefs bool
60+
}
61+
62+
// Plan9MountConfig describes how a Plan9 share should be mounted inside the guest.
63+
type Plan9MountConfig struct {
64+
// GuestPath is the path inside the guest where the share will be mounted.
65+
// If empty, a unique path is generated automatically.
66+
GuestPath string
67+
// ReadOnly mounts the share read-only.
68+
ReadOnly bool
69+
}
70+
71+
// linuxGuestSCSI performs LCOW SCSI guest mount/unmount operations.
72+
type linuxGuestSCSI interface {
73+
AddLCOWMappedVirtualDisk(ctx context.Context, settings guestresource.LCOWMappedVirtualDisk) error
74+
RemoveLCOWMappedVirtualDisk(ctx context.Context, settings guestresource.LCOWMappedVirtualDisk) error
75+
}
76+
77+
// windowsGuestSCSI performs WCOW SCSI guest mount/unmount operations.
78+
type windowsGuestSCSI interface {
79+
AddWCOWMappedVirtualDisk(ctx context.Context, settings guestresource.WCOWMappedVirtualDisk) error
80+
AddWCOWMappedVirtualDiskForContainerScratch(ctx context.Context, settings guestresource.WCOWMappedVirtualDisk) error
81+
RemoveWCOWMappedVirtualDisk(ctx context.Context, settings guestresource.WCOWMappedVirtualDisk) error
82+
}
83+
84+
// linuxGuestPlan9 performs Plan9 guest mount/unmount operations in an LCOW guest.
85+
type linuxGuestPlan9 interface {
86+
AddLCOWMappedDirectory(ctx context.Context, settings guestresource.LCOWMappedDirectory) error
87+
RemoveLCOWMappedDirectory(ctx context.Context, settings guestresource.LCOWMappedDirectory) error
88+
}
89+
90+
// ==============================================================================
91+
// Internal data structures
92+
// ==============================================================================
93+
94+
// refTracker holds the per-mount concurrency and lifecycle fields shared by
95+
// both scsiMount (SCSI) and plan9Mount (Plan9).
96+
type refTracker struct {
97+
// mu serializes state transitions and broadcasts completion to concurrent
98+
// waiters: a goroutine that finds a Pending entry simply locks mu and waits
99+
// for the owner to move the state to Mounted or Invalid.
100+
mu sync.Mutex
101+
102+
// state is the forward-only lifecycle position of this mount.
103+
// Access must be guarded by mu.
104+
state mountState
105+
106+
// stateErr records the error that caused a transition to mountInvalid.
107+
// Waiters that find the mount in the invalid state return this error so
108+
// every caller sees the original failure reason.
109+
stateErr error
110+
111+
// refCount is the number of active callers sharing this mount.
112+
// Access must be guarded by mu.
113+
refCount uint
114+
}
115+
116+
// scsiMount tracks one SCSI disk mounted in the guest.
117+
type scsiMount struct {
118+
refTracker
119+
120+
guestPath string
121+
controller uint
122+
lun uint
123+
config *SCSIMountConfig
124+
}
125+
126+
// plan9Mount tracks one Plan9 share mounted in the guest.
127+
type plan9Mount struct {
128+
refTracker
129+
130+
// guestPath is the path inside the guest where the share is mounted.
131+
guestPath string
132+
// shareName is the Plan9 share name returned by [plan9.Controller.AddToVM].
133+
shareName string
134+
config *Plan9MountConfig
135+
}

internal/controller/mount/mount.go

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
//go:build windows
2+
3+
package mount
4+
5+
import "sync"
6+
7+
// Manager tracks guest-level mounts for SCSI and Plan9 devices, and delegates
8+
// to the appropriate OS-specific guest manager for the actual GCS calls.
9+
type Manager struct {
10+
// globalMu protects the scsiMounts and plan9Mounts maps and serializes
11+
// path allocation across concurrent callers.
12+
globalMu sync.Mutex
13+
14+
// scsiMounts is the global index of SCSI mounts. Key = resolved guestPath.
15+
// Two callers mounting the same disk at the same path share one scsiMount
16+
// entry and jointly hold its refCount.
17+
scsiMounts map[string]*scsiMount
18+
19+
// plan9Mounts is the global index of Plan9 mounts. Key = resolved guestPath.
20+
plan9Mounts map[string]*plan9Mount
21+
22+
// nextSCSIMountIdx generates stable unique guest paths for SCSI auto-paths.
23+
nextSCSIMountIdx int
24+
25+
// nextPlan9MountIdx generates stable unique guest paths for Plan9 auto-paths.
26+
nextPlan9MountIdx int
27+
28+
linuxGuestSCSI linuxGuestSCSI
29+
windowsGuestSCSI windowsGuestSCSI
30+
linuxGuestPlan9 linuxGuestPlan9
31+
}
32+
33+
var _ Controller = (*Manager)(nil)
34+
35+
// New creates a Manager that delegates mount operations to the given guest
36+
// managers. Pass nil for guest types that are not applicable to the VM
37+
// (e.g. pass nil windowsGuestSCSI and nil linuxGuestPlan9 for an LCOW VM that has no WCOW
38+
// support and no Plan9 shares respectively).
39+
func New(
40+
linuxGuestSCSI linuxGuestSCSI,
41+
windowsGuestSCSI windowsGuestSCSI,
42+
linuxGuestPlan9 linuxGuestPlan9,
43+
) *Manager {
44+
return &Manager{
45+
scsiMounts: make(map[string]*scsiMount),
46+
plan9Mounts: make(map[string]*plan9Mount),
47+
linuxGuestSCSI: linuxGuestSCSI,
48+
windowsGuestSCSI: windowsGuestSCSI,
49+
linuxGuestPlan9: linuxGuestPlan9,
50+
}
51+
}

0 commit comments

Comments
 (0)