podman/pkg/machine/wsl/machine.go

768 lines
22 KiB
Go

//go:build windows
package wsl
import (
"bufio"
"bytes"
"errors"
"fmt"
"io"
"os"
"os/exec"
"path/filepath"
"strconv"
"strings"
"github.com/containers/common/pkg/config"
"github.com/containers/common/pkg/strongunits"
"github.com/containers/podman/v5/pkg/machine"
"github.com/containers/podman/v5/pkg/machine/define"
"github.com/containers/podman/v5/pkg/machine/env"
"github.com/containers/podman/v5/pkg/machine/ignition"
"github.com/containers/podman/v5/pkg/machine/vmconfigs"
"github.com/containers/podman/v5/utils"
"github.com/containers/storage/pkg/homedir"
"github.com/sirupsen/logrus"
"golang.org/x/text/encoding/unicode"
"golang.org/x/text/transform"
)
var (
// vmtype refers to qemu (vs libvirt, krun, etc)
vmtype = define.WSLVirt
ErrWslNotSupported = errors.New("wsl features not supported or configured correctly")
)
type ExitCodeError struct {
code uint
}
func (e *ExitCodeError) Error() string {
return fmt.Sprintf("Process failed with exit code: %d", e.code)
}
//nolint:unused
func getConfigPath(name string) (string, error) {
return getConfigPathExt(name, "json")
}
//nolint:unused
func getConfigPathExt(name string, extension string) (string, error) {
vmConfigDir, err := env.GetConfDir(vmtype)
if err != nil {
return "", err
}
return filepath.Join(vmConfigDir, fmt.Sprintf("%s.%s", name, extension)), nil
}
// TODO like provisionWSL, i think this needs to be pushed to use common
// paths and types where possible
func unprovisionWSL(mc *vmconfigs.MachineConfig) error {
dist := env.WithPodmanPrefix(mc.Name)
if err := terminateDist(dist); err != nil {
logrus.Error(err)
}
if err := unregisterDist(dist); err != nil {
logrus.Error(err)
}
vmDataDir, err := env.GetDataDir(vmtype)
if err != nil {
return err
}
distDir := filepath.Join(vmDataDir, "wsldist")
distTarget := filepath.Join(distDir, mc.Name)
return utils.GuardedRemoveAll(distTarget)
}
// TODO there are some differences here that I dont fully groak but I think
// we should push this stuff be more common (dir names, etc) and also use
// typed things where possible like vmfiles
func provisionWSLDist(name string, imagePath string, prompt string) (string, error) {
vmDataDir, err := env.GetDataDir(vmtype)
if err != nil {
return "", err
}
distDir := filepath.Join(vmDataDir, "wsldist")
distTarget := filepath.Join(distDir, name)
if err := os.MkdirAll(distDir, 0755); err != nil {
return "", fmt.Errorf("could not create wsldist directory: %w", err)
}
dist := env.WithPodmanPrefix(name)
fmt.Println(prompt)
// Run WSL import and analyze output for specific errors.
// If the 'Virtual Machine Platform' feature is disabled, we expect a failure
// with HCS service-related errors such as:
// 1. Wsl/Service/RegisterDistro/CreateVm/HCS/ERROR_NOT_SUPPORTED
// 2. Wsl/Service/RegisterDistro/CreateVm/HCS/HCS_E_SERVICE_NOT_AVAILABLE
cmdOutput := &bytes.Buffer{}
err = runCmdPassThroughTee(cmdOutput, "wsl", "--import", dist, distTarget, imagePath, "--version", "2")
decoder := unicode.UTF16(unicode.LittleEndian, unicode.UseBOM).NewDecoder()
decoded, _, decodeErr := transform.Bytes(decoder, cmdOutput.Bytes())
if decodeErr != nil {
return "", fmt.Errorf("failed to decode WSL output: %w", decodeErr)
}
decodedStr := strings.ToLower(string(decoded))
for _, substr := range []string{"hcs/error_not_supported", "hcs/hcs_e_service_not_available"} {
if strings.Contains(decodedStr, substr) {
return "", ErrWslNotSupported
}
}
if err != nil {
return "", fmt.Errorf("the WSL import of guest OS failed: %w", err)
}
// Fixes newuidmap
if err = wslInvoke(dist, "rpm", "--restore", "shadow-utils"); err != nil {
return "", fmt.Errorf("package permissions restore of shadow-utils on guest OS failed: %w", err)
}
if err = wslInvoke(dist, "mkdir", "-p", "/usr/local/bin"); err != nil {
return "", fmt.Errorf("could not create /usr/local/bin: %w", err)
}
if err = wslInvoke(dist, "ln", "-f", "-s", gvForwarderPath, "/usr/local/bin/vm"); err != nil {
return "", fmt.Errorf("could not setup compatibility link: %w", err)
}
return dist, nil
}
func createKeys(mc *vmconfigs.MachineConfig, dist string) error {
user := mc.SSH.RemoteUsername
if err := terminateDist(dist); err != nil {
return fmt.Errorf("could not cycle WSL dist: %w", err)
}
identityPath := mc.SSH.IdentityPath + ".pub"
// TODO We could audit vmfile reads and see if a 'ReadToString'
// method makes sense.
pubKey, err := os.ReadFile(identityPath)
if err != nil {
return fmt.Errorf("could not create ssh keys: %w", err)
}
key := string(pubKey)
if err := wslPipe(key+"\n", dist, "sh", "-c", "mkdir -p /root/.ssh;"+
"cat >> /root/.ssh/authorized_keys; chmod 600 /root/.ssh/authorized_keys"); err != nil {
return fmt.Errorf("could not create root authorized keys on guest OS: %w", err)
}
userAuthCmd := withUser("mkdir -p /home/[USER]/.ssh;"+
"cat >> /home/[USER]/.ssh/authorized_keys; chown -R [USER]:[USER] /home/[USER]/.ssh;"+
"chmod 600 /home/[USER]/.ssh/authorized_keys", user)
if err := wslPipe(key+"\n", dist, "sh", "-c", userAuthCmd); err != nil {
return fmt.Errorf("could not create '%s' authorized keys on guest OS: %w", user, err)
}
return nil
}
func configureSystem(mc *vmconfigs.MachineConfig, dist string, ansibleConfig *vmconfigs.AnsibleConfig) error {
user := mc.SSH.RemoteUsername
if err := wslInvoke(dist, "sh", "-c", fmt.Sprintf(appendPort, mc.SSH.Port, mc.SSH.Port)); err != nil {
return fmt.Errorf("could not configure SSH port for guest OS: %w", err)
}
if err := wslPipe(withUser(configServices, user), dist, "sh"); err != nil {
return fmt.Errorf("could not configure systemd settings for guest OS: %w", err)
}
if err := wslPipe(sudoers, dist, "sh", "-c", "cat >> /etc/sudoers"); err != nil {
return fmt.Errorf("could not add wheel to sudoers: %w", err)
}
if err := wslPipe(overrideSysusers, dist, "sh", "-c",
"cat > /etc/systemd/system/systemd-sysusers.service.d/override.conf"); err != nil {
return fmt.Errorf("could not generate systemd-sysusers override for guest OS: %w", err)
}
if ansibleConfig != nil {
if err := wslPipe(ansibleConfig.Contents, dist, "sh", "-c", fmt.Sprintf("cat > %s", ansibleConfig.PlaybookPath)); err != nil {
return fmt.Errorf("could not generate playbook file for guest os: %w", err)
}
}
if err := enableUserLinger(mc, dist); err != nil {
return err
}
if err := wslPipe(containersConf, dist, "sh", "-c", "cat > /etc/containers/containers.conf"); err != nil {
return fmt.Errorf("could not create containers.conf for guest OS: %w", err)
}
if err := configureRegistries(dist); err != nil {
return err
}
if err := setupPodmanDockerSock(dist, mc.HostUser.Rootful); err != nil {
return err
}
if err := wslInvoke(dist, "sh", "-c", "echo wsl > /etc/containers/podman-machine"); err != nil {
return fmt.Errorf("could not create podman-machine file for guest OS: %w", err)
}
if err := configureBindMounts(dist, user); err != nil {
return err
}
return changeDistUserModeNetworking(dist, user, mc.ImagePath.GetPath(), mc.WSLHypervisor.UserModeNetworking)
}
func configureBindMounts(dist string, user string) error {
if err := wslPipe(fmt.Sprintf(bindMountSystemService, dist), dist, "sh", "-c", "cat > /etc/systemd/system/podman-mnt-bindings.service"); err != nil {
return fmt.Errorf("could not create podman binding service file for guest OS: %w", err)
}
if err := wslPipe(getConfigBindServicesScript(user), dist, "sh"); err != nil {
return fmt.Errorf("could not configure podman binding services for guest OS: %w", err)
}
catUserService := "cat > " + getUserUnitPath(user)
if err := wslPipe(getBindMountUserService(dist), dist, "sh", "-c", catUserService); err != nil {
return fmt.Errorf("could not create podman binding user service file for guest OS: %w", err)
}
if err := wslPipe(getBindMountFsTab(dist), dist, "sh", "-c", "cat >> /etc/fstab"); err != nil {
return fmt.Errorf("could not create podman binding fstab entry for guest OS: %w", err)
}
catGroupDropin := fmt.Sprintf("cat > %s/%s", podmanSocketDropinPath, "10-group.conf")
if err := wslPipe(overrideSocketGroup, dist, "sh", "-c", catGroupDropin); err != nil {
return fmt.Errorf("could not configure podman socket group override: %w", err)
}
return nil
}
func getConfigBindServicesScript(user string) string {
return fmt.Sprintf(configBindServices, user)
}
func getBindMountUserService(dist string) string {
return fmt.Sprintf(bindMountUserService, dist)
}
func getUserUnitPath(user string) string {
return fmt.Sprintf(bindUserUnitPath, user)
}
func getBindMountFsTab(dist string) string {
return fmt.Sprintf(bindMountFsTab, dist)
}
func setupPodmanDockerSock(dist string, rootful bool) error {
content := ignition.GetPodmanDockerTmpConfig(1000, rootful, true)
if err := wslPipe(content, dist, "sh", "-c", "cat > "+ignition.PodmanDockerTmpConfPath); err != nil {
return fmt.Errorf("could not create internal docker sock conf: %w", err)
}
return nil
}
func enableUserLinger(mc *vmconfigs.MachineConfig, dist string) error {
lingerCmd := "mkdir -p /var/lib/systemd/linger; touch /var/lib/systemd/linger/" + mc.SSH.RemoteUsername
if err := wslInvoke(dist, "sh", "-c", lingerCmd); err != nil {
return fmt.Errorf("could not enable linger for remote user on guest OS: %w", err)
}
return nil
}
func configureRegistries(dist string) error {
cmd := "cat > /etc/containers/registries.conf.d/999-podman-machine.conf"
if err := wslPipe(registriesConf, dist, "sh", "-c", cmd); err != nil {
return fmt.Errorf("could not configure registries on guest OS: %w", err)
}
return nil
}
func installScripts(dist string) error {
if err := wslPipe(enterns, dist, "sh", "-c",
"cat > /usr/local/bin/enterns; chmod 755 /usr/local/bin/enterns"); err != nil {
return fmt.Errorf("could not create enterns script for guest OS: %w", err)
}
if err := wslPipe(profile, dist, "sh", "-c",
"cat > /etc/profile.d/enterns.sh"); err != nil {
return fmt.Errorf("could not create motd profile script for guest OS: %w", err)
}
if err := wslPipe(wslmotd, dist, "sh", "-c", "cat > /etc/wslmotd"); err != nil {
return fmt.Errorf("could not create a WSL MOTD for guest OS: %w", err)
}
if err := wslPipe(bootstrap, dist, "sh", "-c",
"cat > /root/bootstrap; chmod 755 /root/bootstrap"); err != nil {
return fmt.Errorf("could not create bootstrap script for guest OS: %w", err)
}
return nil
}
func writeWslConf(dist string, user string) error {
if err := wslPipe(withUser(wslConf, user), dist, "sh", "-c", "cat > /etc/wsl.conf"); err != nil {
return fmt.Errorf("could not configure wsl config for guest OS: %w", err)
}
return nil
}
func attemptFeatureInstall(reExec, admin bool) error {
if !winVersionAtLeast(10, 0, 18362) {
return errors.New("your version of Windows does not support WSL. Update to Windows 10 Build 19041 or later")
} else if !winVersionAtLeast(10, 0, 19041) {
fmt.Fprint(os.Stderr, wslOldVersion)
return errors.New("the WSL can not be automatically installed")
}
message := "WSL is not installed on this system, installing it.\n\n"
if !admin {
message += "Since you are not running as admin, a new window will open and " +
"require you to approve administrator privileges.\n\n"
}
message += "NOTE: A system reboot will be required as part of this process. " +
"If you prefer, you may abort now, and perform a manual installation using the \"wsl --install\" command."
if !reExec && MessageBox(message, "Podman Machine", false) != 1 {
return fmt.Errorf("the WSL installation aborted: %w", define.ErrInitRelaunchAttempt)
}
if !reExec && !admin {
return launchElevate("install the Windows WSL Features")
}
return installWsl()
}
func launchElevate(operation string) error {
if err := createOrTruncateElevatedOutputFile(); err != nil {
return err
}
err := relaunchElevatedWait()
if err != nil {
if eerr, ok := err.(*ExitCodeError); ok {
if eerr.code == ErrorSuccessRebootRequired {
fmt.Println("Reboot is required to continue installation, please reboot at your convenience")
return define.ErrInitRelaunchAttempt
}
}
fmt.Fprintf(os.Stderr, "Elevated process failed with error: %v\n\n", err)
dumpOutputFile()
fmt.Fprintf(os.Stderr, wslInstallError, operation)
return fmt.Errorf("%w: %w", err, define.ErrInitRelaunchAttempt)
}
return define.ErrInitRelaunchAttempt
}
func installWsl() error {
log, err := getElevatedOutputFileWrite()
if err != nil {
return err
}
defer log.Close()
if err := runCmdPassThroughTee(log, "dism", "/online", "/enable-feature",
"/featurename:Microsoft-Windows-Subsystem-Linux", "/all", "/norestart"); isMsiError(err) {
return fmt.Errorf("could not enable WSL Feature: %w", err)
}
if err = runCmdPassThroughTee(log, "dism", "/online", "/enable-feature",
"/featurename:VirtualMachinePlatform", "/all", "/norestart"); isMsiError(err) {
return fmt.Errorf("could not enable Virtual Machine Feature: %w", err)
}
return reboot()
}
func getElevatedOutputFileName() (string, error) {
dir, err := homedir.GetDataHome()
if err != nil {
return "", err
}
return filepath.Join(dir, "podman-elevated-output.log"), nil
}
func dumpOutputFile() {
file, err := getElevatedOutputFileRead()
if err != nil {
logrus.Debug("could not find elevated child output file")
return
}
defer file.Close()
_, _ = io.Copy(os.Stdout, file)
}
func getElevatedOutputFileRead() (*os.File, error) {
return getElevatedOutputFile(os.O_RDONLY)
}
func getElevatedOutputFileWrite() (*os.File, error) {
return getElevatedOutputFile(os.O_WRONLY | os.O_CREATE | os.O_APPEND)
}
func createOrTruncateElevatedOutputFile() error {
name, err := getElevatedOutputFileName()
if err != nil {
return err
}
_, err = os.Create(name)
return err
}
func getElevatedOutputFile(mode int) (*os.File, error) {
name, err := getElevatedOutputFileName()
if err != nil {
return nil, err
}
dir, err := homedir.GetDataHome()
if err != nil {
return nil, err
}
if err = os.MkdirAll(dir, 0755); err != nil {
return nil, err
}
return os.OpenFile(name, mode, 0644)
}
func isMsiError(err error) bool {
if err == nil {
return false
}
if eerr, ok := err.(*exec.ExitError); ok {
switch eerr.ExitCode() {
case 0:
fallthrough
case ErrorSuccessRebootInitiated:
fallthrough
case ErrorSuccessRebootRequired:
return false
}
}
return true
}
func withUser(s string, user string) string {
return strings.ReplaceAll(s, "[USER]", user)
}
func wslInvoke(dist string, arg ...string) error {
newArgs := []string{"-u", "root", "-d", dist}
newArgs = append(newArgs, arg...)
return runCmdPassThrough("wsl", newArgs...)
}
func wslPipe(input string, dist string, arg ...string) error {
newArgs := []string{"-u", "root", "-d", dist}
newArgs = append(newArgs, arg...)
return pipeCmdPassThrough("wsl", input, newArgs...)
}
//nolint:unused
func wslCreateKeys(identityPath string, dist string) (string, error) {
return machine.CreateSSHKeysPrefix(identityPath, true, true, "wsl", "-u", "root", "-d", dist)
}
func runCmdPassThrough(name string, arg ...string) error {
logrus.Debugf("Running command: %s %v", name, arg)
cmd := exec.Command(name, arg...)
cmd.Stdin = os.Stdin
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
if err := cmd.Run(); err != nil {
return fmt.Errorf("command %s %v failed: %w", name, arg, err)
}
return nil
}
func runCmdPassThroughTee(out io.Writer, name string, arg ...string) error {
logrus.Debugf("Running command: %s %v", name, arg)
// TODO - Perhaps improve this with a conpty pseudo console so that
// dism installer text bars mirror console behavior (redraw)
cmd := exec.Command(name, arg...)
cmd.Stdin = os.Stdin
cmd.Stdout = io.MultiWriter(os.Stdout, out)
cmd.Stderr = io.MultiWriter(os.Stderr, out)
if err := cmd.Run(); isMsiError(err) {
return fmt.Errorf("command %s %v failed: %w", name, arg, err)
}
return nil
}
func pipeCmdPassThrough(name string, input string, arg ...string) error {
logrus.Debugf("Running command: %s %v", name, arg)
cmd := exec.Command(name, arg...)
cmd.Stdin = strings.NewReader(input)
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
if err := cmd.Run(); err != nil {
return fmt.Errorf("command %s %v failed: %w", name, arg, err)
}
return nil
}
func setupWslProxyEnv() (hasProxy bool) {
current, _ := os.LookupEnv("WSLENV")
for _, key := range config.ProxyEnv {
if value, _ := os.LookupEnv(key); len(value) < 1 {
continue
}
hasProxy = true
delim := ""
if len(current) > 0 {
delim = ":"
}
current = fmt.Sprintf("%s%s%s/u", current, delim, key)
}
if hasProxy {
os.Setenv("WSLENV", current)
}
return
}
//nolint:unused
func obtainGlobalConfigLock() (*fileLock, error) {
lockDir, err := env.GetGlobalDataDir()
if err != nil {
return nil, err
}
// Lock file needs to be above all backends
// TODO: This should be changed to a common.Config lock mechanism when available
return lockFile(filepath.Join(lockDir, "podman-config.lck"))
}
func isWSLRunning(dist string) (bool, error) {
return wslCheckExists(dist, true)
}
func isWSLExist(dist string) (bool, error) {
return wslCheckExists(dist, false)
}
func wslCheckExists(dist string, running bool) (bool, error) {
all, err := getAllWSLDistros(running)
if err != nil {
return false, err
}
_, exists := all[dist]
return exists, nil
}
func getAllWSLDistros(running bool) (map[string]struct{}, error) {
args := []string{"-l", "--quiet"}
if running {
args = append(args, "--running")
}
cmd := exec.Command("wsl", args...)
out, err := cmd.StdoutPipe()
if err != nil {
return nil, err
}
stderr := &bytes.Buffer{}
cmd.Stderr = stderr
if err = cmd.Start(); err != nil {
return nil, fmt.Errorf("failed to start command %s %v: %w", cmd.Path, args, err)
}
all := make(map[string]struct{})
scanner := bufio.NewScanner(transform.NewReader(out, unicode.UTF16(unicode.LittleEndian, unicode.UseBOM).NewDecoder()))
for scanner.Scan() {
fields := strings.Fields(scanner.Text())
if len(fields) > 0 {
all[fields[0]] = struct{}{}
}
}
err = cmd.Wait()
if err != nil {
return nil, fmt.Errorf("command %s %v failed: %w (%s)", cmd.Path, args, err, strings.TrimSpace(stderr.String()))
}
return all, nil
}
func isSystemdRunning(dist string) (bool, error) {
cmd := exec.Command("wsl", "-u", "root", "-d", dist, "sh")
cmd.Stdin = strings.NewReader(sysdpid + "\necho $SYSDPID\n")
out, err := cmd.StdoutPipe()
if err != nil {
return false, err
}
stderr := &bytes.Buffer{}
cmd.Stderr = stderr
if err = cmd.Start(); err != nil {
return false, err
}
scanner := bufio.NewScanner(out)
result := false
if scanner.Scan() {
text := scanner.Text()
i, err := strconv.Atoi(text)
if err == nil && i > 0 {
result = true
}
}
err = cmd.Wait()
if err != nil {
return false, fmt.Errorf("command %s %v failed: %w (%s)", cmd.Path, cmd.Args, err, strings.TrimSpace(stderr.String()))
}
return result, nil
}
func terminateDist(dist string) error {
cmd := exec.Command("wsl", "--terminate", dist)
out, err := cmd.CombinedOutput()
if err != nil {
return fmt.Errorf("command %s %v failed: %w (%s)", cmd.Path, cmd.Args, err, strings.TrimSpace(string(out)))
}
return nil
}
func unregisterDist(dist string) error {
cmd := exec.Command("wsl", "--unregister", dist)
out, err := cmd.CombinedOutput()
if err != nil {
return fmt.Errorf("command %s %v failed: %w (%s)", cmd.Path, cmd.Args, err, strings.TrimSpace(string(out)))
}
return nil
}
func isRunning(name string) (bool, error) {
dist := env.WithPodmanPrefix(name)
wsl, err := isWSLRunning(dist)
if err != nil {
return false, err
}
sysd := false
if wsl {
sysd, err = isSystemdRunning(dist)
if err != nil {
return false, err
}
}
return sysd, err
}
//nolint:unused
func getDiskSize(name string) strongunits.GiB {
vmDataDir, err := env.GetDataDir(vmtype)
if err != nil {
return 0
}
distDir := filepath.Join(vmDataDir, "wsldist")
disk := filepath.Join(distDir, name, "ext4.vhdx")
info, err := os.Stat(disk)
if err != nil {
return 0
}
return strongunits.ToGiB(strongunits.B(info.Size()))
}
//nolint:unused
func getCPUs(name string) (uint64, error) {
dist := env.WithPodmanPrefix(name)
if run, _ := isWSLRunning(dist); !run {
return 0, nil
}
cmd := exec.Command("wsl", "-u", "root", "-d", dist, "nproc")
out, err := cmd.StdoutPipe()
if err != nil {
return 0, err
}
stderr := &bytes.Buffer{}
cmd.Stderr = stderr
if err = cmd.Start(); err != nil {
return 0, err
}
scanner := bufio.NewScanner(out)
var result string
for scanner.Scan() {
result = scanner.Text()
}
err = cmd.Wait()
if err != nil {
return 0, fmt.Errorf("command %s %v failed: %w (%s)", cmd.Path, cmd.Args, err, strings.TrimSpace(strings.TrimSpace(stderr.String())))
}
ret, err := strconv.Atoi(result)
return uint64(ret), err
}
//nolint:unused
func getMem(name string) (strongunits.MiB, error) {
dist := env.WithPodmanPrefix(name)
if run, _ := isWSLRunning(dist); !run {
return 0, nil
}
cmd := exec.Command("wsl", "-u", "root", "-d", dist, "cat", "/proc/meminfo")
out, err := cmd.StdoutPipe()
if err != nil {
return 0, err
}
stderr := &bytes.Buffer{}
cmd.Stderr = stderr
if err = cmd.Start(); err != nil {
return 0, err
}
scanner := bufio.NewScanner(out)
var (
total, available uint64
t, a int
)
for scanner.Scan() {
// fields are in kB so div to mb
fields := strings.Fields(scanner.Text())
if strings.HasPrefix(fields[0], "MemTotal") && len(fields) >= 2 {
t, err = strconv.Atoi(fields[1])
total = uint64(t) / 1024
} else if strings.HasPrefix(fields[0], "MemAvailable") && len(fields) >= 2 {
a, err = strconv.Atoi(fields[1])
available = uint64(a) / 1024
}
if err != nil {
break
}
}
err = cmd.Wait()
if err != nil {
return 0, fmt.Errorf("command %s %v failed: %w (%s)", cmd.Path, cmd.Args, err, strings.TrimSpace(stderr.String()))
}
return strongunits.MiB(total - available), err
}
//nolint:unused
func getResources(mc *vmconfigs.MachineConfig) (resources vmconfigs.ResourceConfig) {
resources.CPUs, _ = getCPUs(mc.Name)
resources.Memory, _ = getMem(mc.Name)
resources.DiskSize = getDiskSize(mc.Name)
return
}