Add cimfs differ and snapshotter
Details about CimFs project are discussed in #8346 Signed-off-by: Amit Barve <ambarve@microsoft.com>
This commit is contained in:
291
vendor/github.com/Microsoft/hcsshim/pkg/cimfs/cim_writer_windows.go
generated
vendored
Normal file
291
vendor/github.com/Microsoft/hcsshim/pkg/cimfs/cim_writer_windows.go
generated
vendored
Normal file
@@ -0,0 +1,291 @@
|
||||
//go:build windows
|
||||
// +build windows
|
||||
|
||||
package cimfs
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"unsafe"
|
||||
|
||||
"github.com/Microsoft/go-winio"
|
||||
"github.com/Microsoft/hcsshim/internal/log"
|
||||
"github.com/Microsoft/hcsshim/internal/winapi"
|
||||
"github.com/sirupsen/logrus"
|
||||
"golang.org/x/sys/windows"
|
||||
)
|
||||
|
||||
// CimFsWriter represents a writer to a single CimFS filesystem instance. On disk, the
|
||||
// image is composed of a filesystem file and several object ID and region files.
|
||||
// Note: The CimFsWriter isn't thread safe!
|
||||
type CimFsWriter struct {
|
||||
// name of this cim. Usually a <name>.cim file will be created to represent this cim.
|
||||
name string
|
||||
// handle is the CIMFS_IMAGE_HANDLE that must be passed when calling CIMFS APIs.
|
||||
handle winapi.FsHandle
|
||||
// name of the active file i.e the file to which we are currently writing.
|
||||
activeName string
|
||||
// stream to currently active file.
|
||||
activeStream winapi.StreamHandle
|
||||
// amount of bytes that can be written to the activeStream.
|
||||
activeLeft uint64
|
||||
}
|
||||
|
||||
// Create creates a new cim image. The CimFsWriter returned can then be used to do
|
||||
// operations on this cim.
|
||||
func Create(imagePath string, oldFSName string, newFSName string) (_ *CimFsWriter, err error) {
|
||||
var oldNameBytes *uint16
|
||||
// CimCreateImage API call has different behavior if the value of oldNameBytes / newNameBytes
|
||||
// is empty than if it is nil. So we have to convert those strings into *uint16 here.
|
||||
fsName := oldFSName
|
||||
if oldFSName != "" {
|
||||
oldNameBytes, err = windows.UTF16PtrFromString(oldFSName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
var newNameBytes *uint16
|
||||
if newFSName != "" {
|
||||
fsName = newFSName
|
||||
newNameBytes, err = windows.UTF16PtrFromString(newFSName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
var handle winapi.FsHandle
|
||||
if err := winapi.CimCreateImage(imagePath, oldNameBytes, newNameBytes, &handle); err != nil {
|
||||
return nil, fmt.Errorf("failed to create cim image at path %s, oldName: %s, newName: %s: %w", imagePath, oldFSName, newFSName, err)
|
||||
}
|
||||
return &CimFsWriter{handle: handle, name: filepath.Join(imagePath, fsName)}, nil
|
||||
}
|
||||
|
||||
// CreateAlternateStream creates alternate stream of given size at the given path inside the cim. This will
|
||||
// replace the current active stream. Always, finish writing current active stream and then create an
|
||||
// alternate stream.
|
||||
func (c *CimFsWriter) CreateAlternateStream(path string, size uint64) (err error) {
|
||||
err = c.closeStream()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = winapi.CimCreateAlternateStream(c.handle, path, size, &c.activeStream)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create alternate stream for path %s: %w", path, err)
|
||||
}
|
||||
c.activeName = path
|
||||
return nil
|
||||
}
|
||||
|
||||
// closes the currently active stream.
|
||||
func (c *CimFsWriter) closeStream() error {
|
||||
if c.activeStream == 0 {
|
||||
return nil
|
||||
}
|
||||
err := winapi.CimCloseStream(c.activeStream)
|
||||
if err == nil && c.activeLeft > 0 {
|
||||
// Validate here because CimCloseStream does not and this improves error
|
||||
// reporting. Otherwise the error will occur in the context of
|
||||
// cimWriteStream.
|
||||
err = fmt.Errorf("incomplete write, %d bytes left in the stream %s", c.activeLeft, c.activeName)
|
||||
}
|
||||
if err != nil {
|
||||
err = &PathError{Cim: c.name, Op: "closeStream", Path: c.activeName, Err: err}
|
||||
}
|
||||
c.activeLeft = 0
|
||||
c.activeStream = 0
|
||||
c.activeName = ""
|
||||
return err
|
||||
}
|
||||
|
||||
// AddFile adds a new file to the image. The file is added at the specified path. After
|
||||
// calling this function, the file is set as the active stream for the image, so data can
|
||||
// be written by calling `Write`.
|
||||
func (c *CimFsWriter) AddFile(path string, info *winio.FileBasicInfo, fileSize int64, securityDescriptor []byte, extendedAttributes []byte, reparseData []byte) error {
|
||||
err := c.closeStream()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fileMetadata := &winapi.CimFsFileMetadata{
|
||||
Attributes: info.FileAttributes,
|
||||
FileSize: fileSize,
|
||||
CreationTime: info.CreationTime,
|
||||
LastWriteTime: info.LastWriteTime,
|
||||
ChangeTime: info.ChangeTime,
|
||||
LastAccessTime: info.LastAccessTime,
|
||||
}
|
||||
if len(securityDescriptor) == 0 {
|
||||
// Passing an empty security descriptor creates a CIM in a weird state.
|
||||
// Pass the NULL DACL.
|
||||
securityDescriptor = nullSd
|
||||
}
|
||||
fileMetadata.SecurityDescriptorBuffer = unsafe.Pointer(&securityDescriptor[0])
|
||||
fileMetadata.SecurityDescriptorSize = uint32(len(securityDescriptor))
|
||||
if len(reparseData) > 0 {
|
||||
fileMetadata.ReparseDataBuffer = unsafe.Pointer(&reparseData[0])
|
||||
fileMetadata.ReparseDataSize = uint32(len(reparseData))
|
||||
}
|
||||
if len(extendedAttributes) > 0 {
|
||||
fileMetadata.ExtendedAttributes = unsafe.Pointer(&extendedAttributes[0])
|
||||
fileMetadata.EACount = uint32(len(extendedAttributes))
|
||||
}
|
||||
// remove the trailing `\` if present, otherwise it trips off the cim writer
|
||||
path = strings.TrimSuffix(path, "\\")
|
||||
err = winapi.CimCreateFile(c.handle, path, fileMetadata, &c.activeStream)
|
||||
if err != nil {
|
||||
return &PathError{Cim: c.name, Op: "addFile", Path: path, Err: err}
|
||||
}
|
||||
c.activeName = path
|
||||
if info.FileAttributes&(windows.FILE_ATTRIBUTE_DIRECTORY) == 0 {
|
||||
c.activeLeft = uint64(fileSize)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Write writes bytes to the active stream.
|
||||
func (c *CimFsWriter) Write(p []byte) (int, error) {
|
||||
if c.activeStream == 0 {
|
||||
return 0, fmt.Errorf("no active stream")
|
||||
}
|
||||
if uint64(len(p)) > c.activeLeft {
|
||||
return 0, &PathError{Cim: c.name, Op: "write", Path: c.activeName, Err: fmt.Errorf("wrote too much")}
|
||||
}
|
||||
err := winapi.CimWriteStream(c.activeStream, uintptr(unsafe.Pointer(&p[0])), uint32(len(p)))
|
||||
if err != nil {
|
||||
err = &PathError{Cim: c.name, Op: "write", Path: c.activeName, Err: err}
|
||||
return 0, err
|
||||
}
|
||||
c.activeLeft -= uint64(len(p))
|
||||
return len(p), nil
|
||||
}
|
||||
|
||||
// AddLink adds a hard link from `oldPath` to `newPath` in the image.
|
||||
func (c *CimFsWriter) AddLink(oldPath string, newPath string) error {
|
||||
err := c.closeStream()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = winapi.CimCreateHardLink(c.handle, newPath, oldPath)
|
||||
if err != nil {
|
||||
err = &LinkError{Cim: c.name, Op: "addLink", Old: oldPath, New: newPath, Err: err}
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// Unlink deletes the file at `path` from the image.
|
||||
func (c *CimFsWriter) Unlink(path string) error {
|
||||
err := c.closeStream()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
//TODO(ambarve): CimDeletePath currently returns an error if the file isn't found but we ideally want
|
||||
// to put a tombstone at that path so that when cims are merged it removes that file from the lower
|
||||
// layer
|
||||
err = winapi.CimDeletePath(c.handle, path)
|
||||
if err != nil && !os.IsNotExist(err) {
|
||||
err = &PathError{Cim: c.name, Op: "unlink", Path: path, Err: err}
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *CimFsWriter) commit() error {
|
||||
err := c.closeStream()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = winapi.CimCommitImage(c.handle)
|
||||
if err != nil {
|
||||
err = &OpError{Cim: c.name, Op: "commit", Err: err}
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// Close closes the CimFS filesystem.
|
||||
func (c *CimFsWriter) Close() error {
|
||||
if c.handle == 0 {
|
||||
return fmt.Errorf("invalid writer")
|
||||
}
|
||||
if err := c.commit(); err != nil {
|
||||
return &OpError{Cim: c.name, Op: "commit", Err: err}
|
||||
}
|
||||
if err := winapi.CimCloseImage(c.handle); err != nil {
|
||||
return &OpError{Cim: c.name, Op: "close", Err: err}
|
||||
}
|
||||
c.handle = 0
|
||||
return nil
|
||||
}
|
||||
|
||||
// DestroyCim finds out the region files, object files of this cim and then delete
|
||||
// the region files, object files and the <layer-id>.cim file itself.
|
||||
func DestroyCim(ctx context.Context, cimPath string) (retErr error) {
|
||||
regionFilePaths, err := getRegionFilePaths(ctx, cimPath)
|
||||
if err != nil {
|
||||
log.G(ctx).WithError(err).Warnf("get region files for cim %s", cimPath)
|
||||
if retErr == nil { //nolint:govet // nilness: consistency with below
|
||||
retErr = err
|
||||
}
|
||||
}
|
||||
objectFilePaths, err := getObjectIDFilePaths(ctx, cimPath)
|
||||
if err != nil {
|
||||
log.G(ctx).WithError(err).Warnf("get objectid file for cim %s", cimPath)
|
||||
if retErr == nil {
|
||||
retErr = err
|
||||
}
|
||||
}
|
||||
|
||||
log.G(ctx).WithFields(logrus.Fields{
|
||||
"cimPath": cimPath,
|
||||
"regionFiles": regionFilePaths,
|
||||
"objectFiles": objectFilePaths,
|
||||
}).Debug("destroy cim")
|
||||
|
||||
for _, regFilePath := range regionFilePaths {
|
||||
if err := os.Remove(regFilePath); err != nil {
|
||||
log.G(ctx).WithError(err).Warnf("remove file %s", regFilePath)
|
||||
if retErr == nil {
|
||||
retErr = err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for _, objFilePath := range objectFilePaths {
|
||||
if err := os.Remove(objFilePath); err != nil {
|
||||
log.G(ctx).WithError(err).Warnf("remove file %s", objFilePath)
|
||||
if retErr == nil {
|
||||
retErr = err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if err := os.Remove(cimPath); err != nil {
|
||||
log.G(ctx).WithError(err).Warnf("remove file %s", cimPath)
|
||||
if retErr == nil {
|
||||
retErr = err
|
||||
}
|
||||
}
|
||||
return retErr
|
||||
}
|
||||
|
||||
// GetCimUsage returns the total disk usage in bytes by the cim at path `cimPath`.
|
||||
func GetCimUsage(ctx context.Context, cimPath string) (uint64, error) {
|
||||
regionFilePaths, err := getRegionFilePaths(ctx, cimPath)
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("get region file paths for cim %s: %w", cimPath, err)
|
||||
}
|
||||
objectFilePaths, err := getObjectIDFilePaths(ctx, cimPath)
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("get objectid file for cim %s: %w", cimPath, err)
|
||||
}
|
||||
|
||||
var totalUsage uint64
|
||||
for _, f := range append(regionFilePaths, objectFilePaths...) {
|
||||
fi, err := os.Stat(f)
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("stat file %s: %w", f, err)
|
||||
}
|
||||
totalUsage += uint64(fi.Size())
|
||||
}
|
||||
return totalUsage, nil
|
||||
}
|
||||
17
vendor/github.com/Microsoft/hcsshim/pkg/cimfs/cimfs.go
generated
vendored
Normal file
17
vendor/github.com/Microsoft/hcsshim/pkg/cimfs/cimfs.go
generated
vendored
Normal file
@@ -0,0 +1,17 @@
|
||||
//go:build windows
|
||||
// +build windows
|
||||
|
||||
package cimfs
|
||||
|
||||
import (
|
||||
"github.com/Microsoft/hcsshim/osversion"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
func IsCimFSSupported() bool {
|
||||
rv, err := osversion.BuildRevision()
|
||||
if err != nil {
|
||||
logrus.WithError(err).Warn("get build revision")
|
||||
}
|
||||
return osversion.Build() == 20348 && rv >= 2031
|
||||
}
|
||||
134
vendor/github.com/Microsoft/hcsshim/pkg/cimfs/common.go
generated
vendored
Normal file
134
vendor/github.com/Microsoft/hcsshim/pkg/cimfs/common.go
generated
vendored
Normal file
@@ -0,0 +1,134 @@
|
||||
//go:build windows
|
||||
// +build windows
|
||||
|
||||
package cimfs
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/Microsoft/hcsshim/internal/log"
|
||||
"github.com/Microsoft/hcsshim/pkg/cimfs/format"
|
||||
)
|
||||
|
||||
var (
|
||||
// Equivalent to SDDL of "D:NO_ACCESS_CONTROL".
|
||||
nullSd = []byte{1, 0, 4, 128, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}
|
||||
)
|
||||
|
||||
type OpError struct {
|
||||
Cim string
|
||||
Op string
|
||||
Err error
|
||||
}
|
||||
|
||||
func (e *OpError) Error() string {
|
||||
s := "cim " + e.Op + " " + e.Cim
|
||||
s += ": " + e.Err.Error()
|
||||
return s
|
||||
}
|
||||
|
||||
// PathError is the error type returned by most functions in this package.
|
||||
type PathError struct {
|
||||
Cim string
|
||||
Op string
|
||||
Path string
|
||||
Err error
|
||||
}
|
||||
|
||||
func (e *PathError) Error() string {
|
||||
s := "cim " + e.Op + " " + e.Cim
|
||||
s += ":" + e.Path
|
||||
s += ": " + e.Err.Error()
|
||||
return s
|
||||
}
|
||||
|
||||
type LinkError struct {
|
||||
Cim string
|
||||
Op string
|
||||
Old string
|
||||
New string
|
||||
Err error
|
||||
}
|
||||
|
||||
func (e *LinkError) Error() string {
|
||||
return "cim " + e.Op + " " + e.Old + " " + e.New + ": " + e.Err.Error()
|
||||
}
|
||||
|
||||
func validateHeader(h *format.CommonHeader) error {
|
||||
if !bytes.Equal(h.Magic[:], format.MagicValue[:]) {
|
||||
return fmt.Errorf("not a cim file")
|
||||
}
|
||||
if h.Version.Major > format.CurrentVersion.Major || h.Version.Major < format.MinSupportedVersion.Major {
|
||||
return fmt.Errorf("unsupported cim version. cim version %v must be between %v & %v", h.Version, format.MinSupportedVersion, format.CurrentVersion)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func readFilesystemHeader(f *os.File) (format.FilesystemHeader, error) {
|
||||
var fsh format.FilesystemHeader
|
||||
|
||||
if err := binary.Read(f, binary.LittleEndian, &fsh); err != nil {
|
||||
return fsh, fmt.Errorf("reading filesystem header: %w", err)
|
||||
}
|
||||
|
||||
if err := validateHeader(&fsh.Common); err != nil {
|
||||
return fsh, fmt.Errorf("validating filesystem header: %w", err)
|
||||
}
|
||||
return fsh, nil
|
||||
}
|
||||
|
||||
// Returns the paths of all the objectID files associated with the cim at `cimPath`.
|
||||
func getObjectIDFilePaths(ctx context.Context, cimPath string) ([]string, error) {
|
||||
f, err := os.Open(cimPath)
|
||||
if err != nil {
|
||||
return []string{}, fmt.Errorf("open cim file %s: %w", cimPath, err)
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
fsh, err := readFilesystemHeader(f)
|
||||
if err != nil {
|
||||
return []string{}, fmt.Errorf("readingp cim header: %w", err)
|
||||
}
|
||||
|
||||
paths := []string{}
|
||||
for i := 0; i < int(fsh.Regions.Count); i++ {
|
||||
path := filepath.Join(filepath.Dir(cimPath), fmt.Sprintf("%s_%v_%d", format.ObjectIDFileName, fsh.Regions.ID, i))
|
||||
if _, err := os.Stat(path); err == nil {
|
||||
paths = append(paths, path)
|
||||
} else {
|
||||
log.G(ctx).WithError(err).Warnf("stat for object file %s", path)
|
||||
}
|
||||
|
||||
}
|
||||
return paths, nil
|
||||
}
|
||||
|
||||
// Returns the paths of all the region files associated with the cim at `cimPath`.
|
||||
func getRegionFilePaths(ctx context.Context, cimPath string) ([]string, error) {
|
||||
f, err := os.Open(cimPath)
|
||||
if err != nil {
|
||||
return []string{}, fmt.Errorf("open cim file %s: %w", cimPath, err)
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
fsh, err := readFilesystemHeader(f)
|
||||
if err != nil {
|
||||
return []string{}, fmt.Errorf("reading cim header: %w", err)
|
||||
}
|
||||
|
||||
paths := []string{}
|
||||
for i := 0; i < int(fsh.Regions.Count); i++ {
|
||||
path := filepath.Join(filepath.Dir(cimPath), fmt.Sprintf("%s_%v_%d", format.RegionFileName, fsh.Regions.ID, i))
|
||||
if _, err := os.Stat(path); err == nil {
|
||||
paths = append(paths, path)
|
||||
} else {
|
||||
log.G(ctx).WithError(err).Warnf("stat for region file %s", path)
|
||||
}
|
||||
}
|
||||
return paths, nil
|
||||
}
|
||||
3
vendor/github.com/Microsoft/hcsshim/pkg/cimfs/doc.go
generated
vendored
Normal file
3
vendor/github.com/Microsoft/hcsshim/pkg/cimfs/doc.go
generated
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
// This package provides simple go wrappers on top of the win32 CIMFS mount APIs.
|
||||
// The mounting/unmount of cim layers is done by the cim mount functions the internal/wclayer/cim package.
|
||||
package cimfs
|
||||
4
vendor/github.com/Microsoft/hcsshim/pkg/cimfs/format/doc.go
generated
vendored
Normal file
4
vendor/github.com/Microsoft/hcsshim/pkg/cimfs/format/doc.go
generated
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
// format package maintains some basic structures to allows us to read header of a cim file. This is mostly
|
||||
// required to understand the region & objectid files associated with a particular cim. Otherwise, we don't
|
||||
// need to parse the cim format.
|
||||
package format
|
||||
61
vendor/github.com/Microsoft/hcsshim/pkg/cimfs/format/format.go
generated
vendored
Normal file
61
vendor/github.com/Microsoft/hcsshim/pkg/cimfs/format/format.go
generated
vendored
Normal file
@@ -0,0 +1,61 @@
|
||||
//go:build windows
|
||||
// +build windows
|
||||
|
||||
package format
|
||||
|
||||
import "github.com/Microsoft/go-winio/pkg/guid"
|
||||
|
||||
const (
|
||||
RegionFileName = "region"
|
||||
ObjectIDFileName = "objectid"
|
||||
)
|
||||
|
||||
// Magic specifies the magic number at the beginning of a file.
|
||||
type Magic [8]uint8
|
||||
|
||||
var MagicValue = Magic([8]uint8{'c', 'i', 'm', 'f', 'i', 'l', 'e', '0'})
|
||||
|
||||
type Version struct {
|
||||
Major, Minor uint32
|
||||
}
|
||||
|
||||
var CurrentVersion = Version{3, 0}
|
||||
|
||||
var MinSupportedVersion = Version{2, 0}
|
||||
|
||||
type FileType uint8
|
||||
|
||||
// RegionOffset encodes an offset to objects as index of the region file
|
||||
// containing the object and the byte offset within that file.
|
||||
type RegionOffset uint64
|
||||
|
||||
// CommonHeader is the common header for all CIM-related files.
|
||||
type CommonHeader struct {
|
||||
Magic Magic
|
||||
HeaderLength uint32
|
||||
Type FileType
|
||||
Reserved uint8
|
||||
Reserved2 uint16
|
||||
Version Version
|
||||
Reserved3 uint64
|
||||
}
|
||||
|
||||
type RegionSet struct {
|
||||
ID guid.GUID
|
||||
Count uint16
|
||||
Reserved uint16
|
||||
Reserved1 uint32
|
||||
}
|
||||
|
||||
// FilesystemHeader is the header for a filesystem file.
|
||||
//
|
||||
// The filesystem file points to the filesystem object inside a region
|
||||
// file and specifies regions sets.
|
||||
type FilesystemHeader struct {
|
||||
Common CommonHeader
|
||||
Regions RegionSet
|
||||
FilesystemOffset RegionOffset
|
||||
Reserved uint32
|
||||
Reserved1 uint16
|
||||
ParentCount uint16
|
||||
}
|
||||
65
vendor/github.com/Microsoft/hcsshim/pkg/cimfs/mount_cim.go
generated
vendored
Normal file
65
vendor/github.com/Microsoft/hcsshim/pkg/cimfs/mount_cim.go
generated
vendored
Normal file
@@ -0,0 +1,65 @@
|
||||
//go:build windows
|
||||
// +build windows
|
||||
|
||||
package cimfs
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/Microsoft/go-winio/pkg/guid"
|
||||
"github.com/Microsoft/hcsshim/internal/winapi"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
type MountError struct {
|
||||
Cim string
|
||||
Op string
|
||||
VolumeGUID guid.GUID
|
||||
Err error
|
||||
}
|
||||
|
||||
func (e *MountError) Error() string {
|
||||
s := "cim " + e.Op
|
||||
if e.Cim != "" {
|
||||
s += " " + e.Cim
|
||||
}
|
||||
s += " " + e.VolumeGUID.String() + ": " + e.Err.Error()
|
||||
return s
|
||||
}
|
||||
|
||||
// Mount mounts the given cim at a volume with given GUID. Returns the full volume
|
||||
// path if mount is successful.
|
||||
func Mount(cimPath string, volumeGUID guid.GUID, mountFlags uint32) (string, error) {
|
||||
if err := winapi.CimMountImage(filepath.Dir(cimPath), filepath.Base(cimPath), mountFlags, &volumeGUID); err != nil {
|
||||
return "", &MountError{Cim: cimPath, Op: "Mount", VolumeGUID: volumeGUID, Err: err}
|
||||
}
|
||||
return fmt.Sprintf("\\\\?\\Volume{%s}\\", volumeGUID.String()), nil
|
||||
}
|
||||
|
||||
// Unmount unmounts the cim at mounted at path `volumePath`.
|
||||
func Unmount(volumePath string) error {
|
||||
// The path is expected to be in the \\?\Volume{GUID}\ format
|
||||
if volumePath[len(volumePath)-1] != '\\' {
|
||||
volumePath += "\\"
|
||||
}
|
||||
|
||||
if !(strings.HasPrefix(volumePath, "\\\\?\\Volume{") && strings.HasSuffix(volumePath, "}\\")) {
|
||||
return errors.Errorf("volume path %s is not in the expected format", volumePath)
|
||||
}
|
||||
|
||||
trimmedStr := strings.TrimPrefix(volumePath, "\\\\?\\Volume{")
|
||||
trimmedStr = strings.TrimSuffix(trimmedStr, "}\\")
|
||||
|
||||
volGUID, err := guid.FromString(trimmedStr)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "guid parsing failed for %s", trimmedStr)
|
||||
}
|
||||
|
||||
if err := winapi.CimDismountImage(&volGUID); err != nil {
|
||||
return &MountError{VolumeGUID: volGUID, Op: "Unmount", Err: err}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
Reference in New Issue
Block a user