// Copyright 2026 The gVisor Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package boot

import (
	"bytes"
	"encoding/json"
	"fmt"
	"io"
	"strings"
	"time"

	specs "github.com/opencontainers/runtime-spec/specs-go"
	"golang.org/x/sys/unix"
	"gvisor.dev/gvisor/pkg/cleanup"
	"gvisor.dev/gvisor/pkg/context"
	"gvisor.dev/gvisor/pkg/errors/linuxerr"
	"gvisor.dev/gvisor/pkg/fd"
	"gvisor.dev/gvisor/pkg/hostarch"
	"gvisor.dev/gvisor/pkg/log"
	"gvisor.dev/gvisor/pkg/sentry/checkpoint"
	"gvisor.dev/gvisor/pkg/sentry/fscheckpoint"
	"gvisor.dev/gvisor/pkg/sentry/kernel"
	"gvisor.dev/gvisor/pkg/sentry/pgalloc"
	"gvisor.dev/gvisor/pkg/sentry/state/checkpointfiles"
	"gvisor.dev/gvisor/pkg/sentry/state/stateio"
	"gvisor.dev/gvisor/pkg/sentry/state/stateipc"
	"gvisor.dev/gvisor/pkg/sync"
	"gvisor.dev/gvisor/pkg/unet"
	"gvisor.dev/gvisor/pkg/urpc"
	"gvisor.dev/gvisor/runsc/specutils"
	"gvisor.dev/gvisor/runsc/version"
)

const (
	annotationFSCheckpointPrefix = "dev.gvisor.internal.fscheckpoint."

	// annotationFSCheckpointEnable indicates whether files under /proc/gvisor
	// should be present in the container to allow the workload to trigger a
	// filesystem checkpoint.
	annotationFSCheckpointEnable = annotationFSCheckpointPrefix + "enable"

	// annotationFSCheckpointPath is the path to the directory where the
	// filesystem checkpoint files will be created. When present, it allows for
	// the workload running inside to trigger a filesystem checkpoint without
	// having to use the runsc CLI.
	annotationFSCheckpointPath = annotationFSCheckpointPrefix + "path"

	// annotationFSCheckpointResume indicates whether the sandbox should
	// continue running after filesystem checkpoint saving triggered via
	// /proc/gvisor. Optional, defaults to false.
	annotationFSCheckpointResume = annotationFSCheckpointPrefix + "resume"

	// annotationFSCheckpointDirect indicates whether filesystem checkpoint
	// I/Os triggered via /proc/gvisor should use O_DIRECT. Optional, defaults
	// to false.
	annotationFSCheckpointDirect = annotationFSCheckpointPrefix + "direct"

	// annotationFSCheckpointPaths is a comma-separated list of paths inside the
	// containers to save. Optional.
	annotationFSCheckpointPaths = annotationFSCheckpointPrefix + "paths"
)

// GetAnnotationFSCheckpointPath returns the filesystem checkpoint path
// specified in the container annotation. Return empty string if no annotation
// is specified.
func GetAnnotationFSCheckpointPath(spec *specs.Spec) string {
	return spec.Annotations[annotationFSCheckpointPath]
}

// GetAnnotationFSCheckpointDirect returns true if filesystem checkpoint I/O
// controlled by the containing annotation should use O_DIRECT.
func GetAnnotationFSCheckpointDirect(spec *specs.Spec) bool {
	return specutils.AnnotationToBool(spec, annotationFSCheckpointDirect)
}

// FSSave implements kernel.Saver.FSSave.
func (l *Loader) FSSave() error {
	l.mu.Lock()
	fsSaveFDs := l.fsSaveFDs
	l.fsSaveFDs = nil
	useCheckpointGofer := l.fsSaveCheckpointGofer
	l.mu.Unlock()
	if len(fsSaveFDs) == 0 {
		return linuxerr.ENXIO
	}
	paths, err := parseFSCheckpointPaths(l.root.spec.Annotations[annotationFSCheckpointPaths])
	if err != nil {
		return err
	}
	args := FSSaveArgs{
		ExitAfterSaving: !specutils.AnnotationToBool(l.root.spec, annotationFSCheckpointResume),
		Paths:           paths,
	}
	args.FilePayload.Files = fd.ReleaseToFiles(fsSaveFDs, "fs-checkpoint")
	args.UseCheckpointGofer = useCheckpointGofer
	opts := kernel.FSSaveOpts{
		RunscVersion: version.Version(),
		Paths:        paths,
	}
	if err := setKernelFSSaveOptsFiles(&args, &opts); err != nil {
		return err
	}
	return l.k.FSSave(context.Background(), &opts)
}

func parseFSCheckpointPaths(val string) ([]checkpoint.ResourceID, error) {
	if val == "" {
		return nil, nil
	}
	var paths []checkpoint.ResourceID
	for _, part := range strings.Split(val, ",") {
		part = strings.TrimSpace(part)
		if part == "" {
			continue
		}
		subparts := strings.SplitN(part, ":", 2)
		if len(subparts) == 1 {
			p := strings.TrimSpace(subparts[0])
			if p == "" {
				return nil, fmt.Errorf("empty path in fscheckpoint paths: %q", val)
			}
			paths = append(paths, checkpoint.ResourceID{Path: p})
		} else {
			c := strings.TrimSpace(subparts[0])
			p := strings.TrimSpace(subparts[1])
			if p == "" {
				return nil, fmt.Errorf("empty path in fscheckpoint paths: %q", val)
			}
			paths = append(paths, checkpoint.ResourceID{ContainerName: c, Path: p})
		}
	}
	return paths, nil
}

func convertToKernelFSSaveOpts(args *FSSaveArgs) (kernel.FSSaveOpts, error) {
	opts := kernel.FSSaveOpts{
		RunscVersion:    version.Version(),
		ExitAfterSaving: args.ExitAfterSaving,
		Paths:           args.Paths,
	}
	if err := setKernelFSSaveOptsFiles(args, &opts); err != nil {
		return kernel.FSSaveOpts{}, err
	}
	return opts, nil
}

func setKernelFSSaveOptsFiles(args *FSSaveArgs, opts *kernel.FSSaveOpts) error {
	if args.UseCheckpointGofer {
		return setKernelFSSaveOptsFilesForCheckpointGofer(args, opts)
	}
	return setKernelFSSaveOptsFilesForLocalCheckpoint(args, opts)
}

func setKernelFSSaveOptsFilesForLocalCheckpoint(args *FSSaveArgs, opts *kernel.FSSaveOpts) error {
	if len(args.FilePayload.Files) != 4 {
		return fmt.Errorf("got %d files, want 4", len(args.FilePayload.Files))
	}
	manifestFile, err := args.ReleaseFD(0)
	if err != nil {
		return err
	}
	multiTarFile, err := args.ReleaseFD(1)
	if err != nil {
		return err
	}
	pagesMetadataFile, err := args.ReleaseFD(2)
	if err != nil {
		return err
	}
	pagesFile, err := args.ReleaseFD(3)
	if err != nil {
		return err
	}
	opts.ManifestFile = stateio.NewBufioWriteCloser(manifestFile)
	opts.MultiTarFile = stateio.NewBufioWriteCloser(multiTarFile)
	opts.PagesMetadataFile = stateio.NewBufioWriteCloser(pagesMetadataFile)
	opts.PagesFile = stateio.NewPagesFileFDWriterDefault(int32(pagesFile.Release()))
	return nil
}

func setKernelFSSaveOptsFilesForCheckpointGofer(args *FSSaveArgs, opts *kernel.FSSaveOpts) error {
	clientFD, err := unix.Dup(int(args.Files[0].Fd()))
	if err != nil {
		return fmt.Errorf("failed to dup checkpoint gofer client FD: %w", err)
	}
	clientSock, err := unet.NewSocket(clientFD)
	if err != nil {
		unix.Close(clientFD)
		return fmt.Errorf("failed to create unet.Socket for checkpoint gofer client FD: %w", err)
	}
	afc, err := stateipc.NewAsyncFileClient(urpc.NewClient(clientSock) /* transfers ownership */)
	if err != nil {
		return fmt.Errorf("failed to create stateipc client: %w", err)
	}
	defer afc.DecRef()

	manifestFileAsync, err := afc.OpenWrite(checkpointfiles.FSCheckpointManifestFileName)
	if err != nil {
		return fmt.Errorf("failed to open manifest file: %w", err)
	}
	manifestFile, err := stateio.NewBufWriter(manifestFileAsync /* transfers ownership */, 2<<20 /* size = 2 MiB */)
	if err != nil {
		return fmt.Errorf("failed to buffer manifest file: %w", err)
	}
	closeCleanup := cleanup.Make(func() { manifestFile.Close() })
	defer closeCleanup.Clean()

	multiTarFileAsync, err := afc.OpenWrite(checkpointfiles.FSCheckpointMultiTarFileName)
	if err != nil {
		return fmt.Errorf("failed to open multi-tar file: %w", err)
	}
	multiTarFile, err := stateio.NewBufWriter(multiTarFileAsync /* transfers ownership */, 8<<20 /* size = 8 MiB */)
	if err != nil {
		return fmt.Errorf("failed to buffer multi-tar file: %w", err)
	}
	closeCleanup.Add(func() { multiTarFile.Close() })

	pagesMetadataFileAsync, err := afc.OpenWrite(checkpointfiles.PagesMetadataFileName)
	if err != nil {
		return fmt.Errorf("failed to open pages metadata file: %w", err)
	}
	pagesMetadataFile, err := stateio.NewBufWriter(pagesMetadataFileAsync /* transfers ownership */, 8<<20 /* size = 8 MiB */)
	if err != nil {
		return fmt.Errorf("failed to buffer pages metadata file: %w", err)
	}
	closeCleanup.Add(func() { pagesMetadataFile.Close() })

	pagesFile, err := afc.OpenWrite(checkpointfiles.PagesFileName)
	if err != nil {
		return fmt.Errorf("failed to open pages file: %w", err)
	}

	closeCleanup.Release()
	opts.ManifestFile = manifestFile
	opts.MultiTarFile = multiTarFile
	opts.PagesMetadataFile = pagesMetadataFile
	opts.PagesFile = pagesFile
	return nil
}

// fsRestore holds the state of a filesystem restore.
type fsRestore struct {
	wg sync.WaitGroup

	// immutable
	getPagesMetadata func() ([]byte, error)
	getMultiTar      func() ([]byte, error)

	// immutable after wg.Wait()
	manifestErr error
	apfl        *pgalloc.AsyncPagesFileLoad
	mfs         map[checkpoint.ResourceID]*fscheckpoint.MemoryFile
	tmpfs       map[checkpoint.ResourceID]*fscheckpoint.Tmpfs

	waitMu  sync.Mutex
	waitMap map[string]*fsRestoreContainer // key is container ID
}

type fsRestoreContainer struct {
	// These fields are protected by fsRestore.waitMu.
	err        error
	asyncLoads int // number of MemoryFiles currently in async page loading
	cond       sync.Cond
}

// fsRestoreOpts holds options to startFSRestore.
type fsRestoreOpts struct {
	// These correspond to files specified by the fscheckpoint package, and are
	// all required.
	ManifestFile      io.ReadCloser
	MultiTarFile      io.ReadCloser
	PagesMetadataFile io.ReadCloser
	PagesFile         stateio.AsyncReader
}

func makeFSRestoreOpts(args *Args) (fsRestoreOpts, error) {
	if args.FSRestoreCheckpointGofer {
		return makeFSRestoreOptsForCheckpointGofer(args)
	}
	return makeFSRestoreOptsForLocalCheckpoint(args)
}

func makeFSRestoreOptsForLocalCheckpoint(args *Args) (fsRestoreOpts, error) {
	if len(args.FSRestoreFDs) != 4 {
		return fsRestoreOpts{}, fmt.Errorf("got %d files in -fs-restore-fds, want 4", len(args.FSRestoreFDs))
	}
	return fsRestoreOpts{
		ManifestFile:      stateio.NewBufioReadCloser(args.FSRestoreFDs[0].ReleaseToFile(checkpointfiles.FSCheckpointManifestFileName)),
		MultiTarFile:      stateio.NewBufioReadCloser(args.FSRestoreFDs[1].ReleaseToFile(checkpointfiles.FSCheckpointMultiTarFileName)),
		PagesMetadataFile: stateio.NewBufioReadCloser(args.FSRestoreFDs[2].ReleaseToFile(checkpointfiles.PagesMetadataFileName)),
		PagesFile:         stateio.NewPagesFileFDReaderDefault(int32(args.FSRestoreFDs[3].Release())),
	}, nil
}

func makeFSRestoreOptsForCheckpointGofer(args *Args) (fsRestoreOpts, error) {
	if len(args.FSRestoreFDs) != 1 {
		return fsRestoreOpts{}, fmt.Errorf("got %d files in -fs-restore-fds, want 1", len(args.FSRestoreFDs))
	}
	clientFD := args.FSRestoreFDs[0].Release()
	clientSock, err := unet.NewSocket(clientFD)
	if err != nil {
		unix.Close(clientFD)
		return fsRestoreOpts{}, fmt.Errorf("failed to create unet.Socket for checkpoint gofer client FD: %w", err)
	}
	afc, err := stateipc.NewAsyncFileClient(urpc.NewClient(clientSock) /* transfers ownership */)
	if err != nil {
		return fsRestoreOpts{}, fmt.Errorf("failed to create stateipc client: %w", err)
	}
	defer afc.DecRef()

	manifestFileAsync, err := afc.OpenRead(checkpointfiles.FSCheckpointManifestFileName)
	if err != nil {
		return fsRestoreOpts{}, fmt.Errorf("failed to open manifest file: %w", err)
	}
	manifestFile, err := stateio.NewBufReader(manifestFileAsync /* transfers ownership */, 2<<20 /* size = 2 MiB */)
	if err != nil {
		return fsRestoreOpts{}, fmt.Errorf("failed to buffer manifest file: %w", err)
	}
	closeCleanup := cleanup.Make(func() { manifestFile.Close() })
	defer closeCleanup.Clean()

	multiTarFileAsync, err := afc.OpenRead(checkpointfiles.FSCheckpointMultiTarFileName)
	if err != nil {
		return fsRestoreOpts{}, fmt.Errorf("failed to open multi-tar file: %w", err)
	}
	multiTarFile, err := stateio.NewBufReader(multiTarFileAsync /* transfers ownership */, 8<<20 /* size = 8 MiB */)
	if err != nil {
		return fsRestoreOpts{}, fmt.Errorf("failed to buffer multi-tar file: %w", err)
	}
	closeCleanup.Add(func() { multiTarFile.Close() })

	pagesMetadataFileAsync, err := afc.OpenRead(checkpointfiles.PagesMetadataFileName)
	if err != nil {
		return fsRestoreOpts{}, fmt.Errorf("failed to open pages metadata file: %w", err)
	}
	pagesMetadataFile, err := stateio.NewBufReader(pagesMetadataFileAsync /* transfers ownership */, 8<<20 /* size = 8 MiB */)
	if err != nil {
		return fsRestoreOpts{}, fmt.Errorf("failed to buffer pages metadata file: %w", err)
	}
	closeCleanup.Add(func() { pagesMetadataFile.Close() })

	pagesFile, err := afc.OpenRead(checkpointfiles.PagesFileName)
	if err != nil {
		return fsRestoreOpts{}, fmt.Errorf("failed to open pages file: %w", err)
	}

	closeCleanup.Release()
	return fsRestoreOpts{
		ManifestFile:      manifestFile,
		MultiTarFile:      multiTarFile,
		PagesMetadataFile: pagesMetadataFile,
		PagesFile:         pagesFile,
	}, nil
}

// startFSRestore takes ownership of resources in opts.
func startFSRestore(opts *fsRestoreOpts) (*fsRestore, error) {
	fsr := &fsRestore{
		mfs:     make(map[checkpoint.ResourceID]*fscheckpoint.MemoryFile),
		tmpfs:   make(map[checkpoint.ResourceID]*fscheckpoint.Tmpfs),
		waitMap: make(map[string]*fsRestoreContainer),
	}

	// TODO: NOLINT - Currently we read the whole pages metadata file into a
	// []byte, then pass pieces of that []byte to MemoryFile construction. This
	// is necessary because opts.PagesMetadataFile is io.Reader (read
	// sequentially), and tmpfs filesystems and their private MemoryFiles may
	// be restored in a different order than checkpoint order (disk-backed
	// filestore files are not available until container creation).
	//
	// We could make opts.PagesMetadataFile io.ReaderAt to avoid this copy.
	// However, when the multi-tar file is accessed via stateio.AsyncReader,
	// this requires an implementation of io.ReaderAt that wraps
	// stateio.AsyncReader, akin to stateio.BufReader. AsyncReader already
	// supports random reads, but has a fixed maximum parallelism per
	// AsyncReader that would need to be shared between readers. Furthermore,
	// BufReader asynchronously fills its buffer with reads to minimize
	// latency; our io.ReaderAt implementation would need to do something
	// comparable to avoid regressions.
	//
	// Alternatively, we could implement io.ReaderAt by asynchronously
	// buffering the whole file in memory, which is probably better overall
	// (equivalent to what we are doing now, but permits reading parts of the
	// file that have been read before the whole file is read) but requires
	// adding a stateio.AsyncReader method to get file size.
	//
	// All of the above also applies to the multi-tar file.
	readOnce := func(desc string, optsR *io.ReadCloser) func() ([]byte, error) {
		r := *optsR
		*optsR = nil
		f := sync.OnceValues(func() ([]byte, error) {
			timeStart := time.Now()
			data, err := io.ReadAll(r)
			dur := time.Since(timeStart)
			// Close r immediately to release any memory used for buffering.
			r.Close()
			r = nil
			if err == nil {
				log.Infof("Read filesystem checkpoint %s (%d bytes) in %s", desc, len(data), dur)
			}
			return data, err
		})
		// Start reading immediately.
		go f()
		return f
	}
	fsr.getPagesMetadata = readOnce("pages metadata file", &opts.PagesMetadataFile)
	fsr.getMultiTar = readOnce("multi-tar file", &opts.MultiTarFile)

	// Read and handle the manifest in parallel.
	fsr.manifestErr = fmt.Errorf("loading manifest panicked")
	fsr.wg.Go(func() {
		defer func() {
			if opts.ManifestFile != nil {
				opts.ManifestFile.Close()
				opts.ManifestFile = nil
			}
			if opts.PagesFile != nil {
				opts.PagesFile.Close()
				opts.PagesFile = nil
			}
		}()
		fsr.manifestErr = func() error {
			var manifest fscheckpoint.Manifest
			timeStart := time.Now()
			if err := json.NewDecoder(opts.ManifestFile).Decode(&manifest); err != nil {
				return fmt.Errorf("failed to read manifest: %w", err)
			}
			log.Infof("Read filesystem checkpoint manifest in %s", time.Since(timeStart))
			if manifest.Version != 0 {
				return fmt.Errorf("unsupported filesystem checkpoint version: %d", manifest.Version)
			}
			if manifest.RunscVersion != version.Version() {
				return fmt.Errorf("filesystem checkpoint runsc version %q does not match current runsc version %q", manifest.RunscVersion, version.Version())
			}
			if manifest.PageSize != hostarch.PageSize {
				return fmt.Errorf("filesystem checkpoint page size %d does not match current page size %d", manifest.PageSize, hostarch.PageSize)
			}
			if endian := hostarch.EndianString(); manifest.Endian != endian {
				return fmt.Errorf("filesystem checkpoint endianness %q does not match current endianness %q", manifest.Endian, endian)
			}

			apfl, err := pgalloc.StartAsyncPagesFileLoad(opts.PagesFile /* transfers ownership */, func(err error) {
				if err != nil {
					log.Warningf("Failed to load filesystem checkpoint pages: %v", err)
				} else if log.IsLogging(log.Debug) {
					log.Debugf("Finished loading filesystem checkpoint pages")
				}
			}, nil)
			opts.PagesFile = nil
			if err != nil {
				return fmt.Errorf("failed to start async page loading: %w", err)
			}
			fsr.apfl = apfl
			for i := range manifest.MemoryFiles {
				mmf := &manifest.MemoryFiles[i]
				fsr.mfs[mmf.ResourceID] = mmf
			}
			for i := range manifest.Tmpfs {
				mt := &manifest.Tmpfs[i]
				fsr.tmpfs[mt.ResourceID] = mt
			}
			// Note that apfl.MemoryFilesDone() will never be called. This is
			// because if a container fails and is restarted, we also want to
			// restore the filesystem of the restarted container, so we need to
			// keep filesystem restore operational indefinitely.
			return nil
		}()
	})

	return fsr, nil
}

// +checklocks:fsr.waitMu
func (fsr *fsRestore) ensureContainer(cid string) *fsRestoreContainer {
	c := fsr.waitMap[cid]
	if c == nil {
		c = &fsRestoreContainer{}
		c.cond.L = &fsr.waitMu
		fsr.waitMap[cid] = c
	}
	return c
}

func (c *fsRestoreContainer) setError(err error) error {
	if c.err == nil && err != nil {
		c.err = err
		c.cond.Broadcast()
	}
	return err
}

func (fsr *fsRestore) memoryFileLoadArgs(id checkpoint.ResourceID, cid string) (io.Reader, uint64, func(error), error) {
	if fsr == nil {
		return nil, 0, func(error) {}, nil
	}

	fsr.wg.Wait()
	if fsr.manifestErr != nil {
		return nil, 0, nil, fsr.manifestErr
	}
	mmf := fsr.mfs[id]
	if mmf == nil {
		return nil, 0, func(error) {}, nil
	}
	pagesMetadata, err := fsr.getPagesMetadata()

	fsr.waitMu.Lock()
	defer fsr.waitMu.Unlock()
	c := fsr.ensureContainer(cid)
	if mmf.PagesMetadataEnd > uint64(len(pagesMetadata)) {
		if err != nil {
			return nil, 0, nil, c.setError(fmt.Errorf("failed to read pages metadata: %w", err))
		}
		return nil, 0, nil, c.setError(fmt.Errorf("MemoryFile %q has pages metadata range [%d, %d) beyond pages metadata file size %d", mmf.ResourceID, mmf.PagesMetadataStart, mmf.PagesMetadataEnd, len(pagesMetadata)))
	}
	c.asyncLoads++
	return bytes.NewReader(pagesMetadata[mmf.PagesMetadataStart:mmf.PagesMetadataEnd]), mmf.PagesStart, func(err error) {
		fsr.waitMu.Lock()
		defer fsr.waitMu.Unlock()
		c.asyncLoads--
		switch {
		case err != nil:
			if c.err == nil {
				c.err = err
			}
			fallthrough
		case c.asyncLoads == 0:
			c.cond.Broadcast()
		}
	}, nil
}

func (fsr *fsRestore) tmpfsSourceTar(id checkpoint.ResourceID, cid string) (io.ReadCloser, error) {
	if fsr == nil {
		return nil, nil
	}

	fsr.wg.Wait()
	if fsr.manifestErr != nil {
		return nil, fsr.manifestErr
	}
	mt := fsr.tmpfs[id]
	if mt == nil {
		return nil, nil
	}
	multiTar, err := fsr.getMultiTar()

	fsr.waitMu.Lock()
	defer fsr.waitMu.Unlock()
	if mt.TarEnd <= uint64(len(multiTar)) {
		return io.NopCloser(bytes.NewReader(multiTar[mt.TarStart:mt.TarEnd])), nil
	}
	c := fsr.ensureContainer(cid)
	if err != nil {
		return nil, c.setError(fmt.Errorf("failed to read tar archive: %w", err))
	}
	return nil, c.setError(fmt.Errorf("tmpfs %q has tar range [%d, %d) beyond multi-tar file size %d", mt.ResourceID, mt.TarStart, mt.TarEnd, len(multiTar)))
}

// wait blocks until either all filesystems have been restored for the
// container with the given ID, or an error occurs while restoring filesystems
// for that container.
func (fsr *fsRestore) wait(cid string) error {
	if fsr == nil {
		return fmt.Errorf("filesystem restore is not enabled")
	}
	fsr.wg.Wait()
	if fsr.manifestErr != nil {
		return fsr.manifestErr
	}
	fsr.waitMu.Lock()
	defer fsr.waitMu.Unlock()
	c := fsr.waitMap[cid]
	if c == nil {
		return fmt.Errorf("no filesystems restored for container %s", cid)
	}
	for {
		if c.err != nil {
			return c.err
		}
		if c.asyncLoads == 0 {
			return nil
		}
		c.cond.Wait()
	}
}
