1717package mount
1818
1919import (
20+ "context"
2021 "encoding/json"
22+ "errors"
2123 "fmt"
2224 "os"
2325 "path/filepath"
2426 "strings"
2527
28+ "github.com/Microsoft/go-winio/pkg/bindfilter"
2629 "github.com/Microsoft/hcsshim"
30+ "github.com/containerd/containerd/log"
31+ "golang.org/x/sys/windows"
32+ )
33+
34+ const sourceStreamName = "containerd.io-source"
35+
36+ var (
37+ // ErrNotImplementOnWindows is returned when an action is not implemented for windows
38+ ErrNotImplementOnWindows = errors .New ("not implemented under windows" )
2739)
2840
2941// Mount to the provided target.
30- func (m * Mount ) mount (target string ) error {
42+ func (m * Mount ) mount (target string ) ( retErr error ) {
3143 if m .Type != "windows-layer" {
3244 return fmt .Errorf ("invalid windows mount type: '%s'" , m .Type )
3345 }
@@ -43,25 +55,60 @@ func (m *Mount) mount(target string) error {
4355 HomeDir : home ,
4456 }
4557
46- if err = hcsshim .ActivateLayer (di , layerID ); err != nil {
58+ if err : = hcsshim .ActivateLayer (di , layerID ); err != nil {
4759 return fmt .Errorf ("failed to activate layer %s: %w" , m .Source , err )
4860 }
61+ defer func () {
62+ if retErr != nil {
63+ if layerErr := hcsshim .DeactivateLayer (di , layerID ); layerErr != nil {
64+ log .G (context .TODO ()).WithError (layerErr ).Error ("failed to deactivate layer during mount failure cleanup" )
65+ }
66+ }
67+ }()
4968
50- if err = hcsshim .PrepareLayer (di , layerID , parentLayerPaths ); err != nil {
69+ if err : = hcsshim .PrepareLayer (di , layerID , parentLayerPaths ); err != nil {
5170 return fmt .Errorf ("failed to prepare layer %s: %w" , m .Source , err )
5271 }
5372
54- // We can link the layer mount path to the given target. It is an UNC path, and it needs
55- // a trailing backslash.
56- mountPath , err := hcsshim .GetLayerMountPath (di , layerID )
73+ defer func () {
74+ if retErr != nil {
75+ if layerErr := hcsshim .UnprepareLayer (di , layerID ); layerErr != nil {
76+ log .G (context .TODO ()).WithError (layerErr ).Error ("failed to unprepare layer during mount failure cleanup" )
77+ }
78+ }
79+ }()
80+
81+ volume , err := hcsshim .GetLayerMountPath (di , layerID )
5782 if err != nil {
58- return fmt .Errorf ("failed to get layer mount path for %s: %w" , m .Source , err )
83+ return fmt .Errorf ("failed to get volume path for layer %s: %w" , m .Source , err )
84+ }
85+
86+ if len (parentLayerPaths ) == 0 {
87+ // this is a base layer. It gets mounted without going through WCIFS. We need to mount the Files
88+ // folder, not the actual source, or the client may inadvertently remove metadata files.
89+ volume = filepath .Join (volume , "Files" )
90+ if _ , err := os .Stat (volume ); err != nil {
91+ return fmt .Errorf ("no Files folder in layer %s" , layerID )
92+ }
5993 }
60- mountPath = mountPath + `\`
94+ if err := bindfilter .ApplyFileBinding (target , volume , m .ReadOnly ()); err != nil {
95+ return fmt .Errorf ("failed to set volume mount path for layer %s: %w" , m .Source , err )
96+ }
97+ defer func () {
98+ if retErr != nil {
99+ if bindErr := bindfilter .RemoveFileBinding (target ); bindErr != nil {
100+ log .G (context .TODO ()).WithError (bindErr ).Error ("failed to remove binding during mount failure cleanup" )
101+ }
102+ }
103+ }()
61104
62- if err = os .Symlink (mountPath , target ); err != nil {
63- return fmt .Errorf ("failed to link mount to target %s: %w" , target , err )
105+ // Add an Alternate Data Stream to record the layer source.
106+ // See https://docs.microsoft.com/en-au/archive/blogs/askcore/alternate-data-streams-in-ntfs
107+ // for details on Alternate Data Streams.
108+ if err := os .WriteFile (filepath .Clean (target )+ ":" + sourceStreamName , []byte (m .Source ), 0666 ); err != nil {
109+ return fmt .Errorf ("failed to record source for layer %s: %w" , m .Source , err )
64110 }
111+
65112 return nil
66113}
67114
@@ -85,25 +132,55 @@ func (m *Mount) GetParentPaths() ([]string, error) {
85132
86133// Unmount the mount at the provided path
87134func Unmount (mount string , flags int ) error {
88- var (
89- home , layerID = filepath .Split (mount )
90- di = hcsshim.DriverInfo {
91- HomeDir : home ,
135+ mount = filepath .Clean (mount )
136+ adsFile := mount + ":" + sourceStreamName
137+ var layerPath string
138+
139+ if _ , err := os .Lstat (adsFile ); err == nil {
140+ layerPathb , err := os .ReadFile (mount + ":" + sourceStreamName )
141+ if err != nil {
142+ return fmt .Errorf ("failed to retrieve source for layer %s: %w" , mount , err )
92143 }
93- )
94-
95- if err := hcsshim .UnprepareLayer (di , layerID ); err != nil {
96- return fmt .Errorf ("failed to unprepare layer %s: %w" , mount , err )
144+ layerPath = string (layerPathb )
97145 }
98- if err := hcsshim .DeactivateLayer (di , layerID ); err != nil {
99- return fmt .Errorf ("failed to deactivate layer %s: %w" , mount , err )
146+
147+ if err := bindfilter .RemoveFileBinding (mount ); err != nil {
148+ if errors .Is (err , windows .ERROR_INVALID_PARAMETER ) || errors .Is (err , windows .ERROR_NOT_FOUND ) {
149+ // not a mount point
150+ return nil
151+ }
152+ return fmt .Errorf ("removing mount: %w" , err )
100153 }
101154
155+ if layerPath != "" {
156+ var (
157+ home , layerID = filepath .Split (layerPath )
158+ di = hcsshim.DriverInfo {
159+ HomeDir : home ,
160+ }
161+ )
162+
163+ if err := hcsshim .UnprepareLayer (di , layerID ); err != nil {
164+ return fmt .Errorf ("failed to unprepare layer %s: %w" , mount , err )
165+ }
166+
167+ if err := hcsshim .DeactivateLayer (di , layerID ); err != nil {
168+ return fmt .Errorf ("failed to deactivate layer %s: %w" , mount , err )
169+ }
170+ }
102171 return nil
103172}
104173
105174// UnmountAll unmounts from the provided path
106175func UnmountAll (mount string , flags int ) error {
176+ if mount == "" {
177+ // This isn't an error, per the EINVAL handling in the Linux version
178+ return nil
179+ }
180+ if _ , err := os .Stat (mount ); os .IsNotExist (err ) {
181+ return nil
182+ }
183+
107184 return Unmount (mount , flags )
108185}
109186
0 commit comments