diff --git a/cmd/migrate_vhd.go b/cmd/migrate_vhd.go index b0cab55..6f3dc2e 100644 --- a/cmd/migrate_vhd.go +++ b/cmd/migrate_vhd.go @@ -89,9 +89,13 @@ func (m *MigrateVHDCommand) Run(ctx context.Context) error { } // Copy over the files from the backup for this server. - cmd := exec.CommandContext(ctx, "cp", "-a", bak + "/.", s.Filesystem().Path()) + cmd := exec.CommandContext(ctx, "mv", bak + "/*", s.Filesystem().Path()) + s.Log().Debug(cmd.String()) if err := cmd.Run(); err != nil { - return errors.WithStack(err) + return errors.Wrap(err, "migrate: failed to move old server files into new direcotry") + } + if err := os.Remove(bak); err != nil { + s.Log().WithField("directory", bak).Warn("failed to remove backup directory") } s.Log().Info("finished migration to virtual disk...") diff --git a/internal/vhd/vhd.go b/internal/vhd/vhd.go index 9f82bce..39dfed5 100644 --- a/internal/vhd/vhd.go +++ b/internal/vhd/vhd.go @@ -9,13 +9,15 @@ import ( "strings" "emperror.dev/errors" + "github.com/pterodactyl/wings/config" "github.com/spf13/afero" ) var ( - ErrPathNotDirectory = errors.Sentinel("vhd: filesystem path is not a directory") - ErrFilesystemMounted = errors.Sentinel("vhd: filesystem is already mounted") - ErrFilesystemExists = errors.Sentinel("vhd: filesystem already exists on disk") + ErrInvalidDiskPathTarget = errors.Sentinel("vhd: disk path is a directory or symlink") + ErrMountPathNotDirectory = errors.Sentinel("vhd: mount point is not a directory") + ErrFilesystemMounted = errors.Sentinel("vhd: filesystem is already mounted") + ErrFilesystemExists = errors.Sentinel("vhd: filesystem already exists on disk") ) // hasExitCode allows this code to test the response error to see if there is @@ -45,8 +47,11 @@ type CfgOption func(d *Disk) *Disk // Disk represents the underlying virtual disk for the instance. type Disk struct { // The total size of the disk allowed in bytes. - size int64 - diskPath string + size int64 + // The path where the disk image should be created. + diskPath string + // The point at which this disk should be made available on the system. This + // is where files can be read/written to. mountAt string fs afero.Fs commander CommanderProvider @@ -92,7 +97,7 @@ func WithCommander(c CommanderProvider) func(*Disk) { // Exists reports if the disk exists on the system yet or not. This only verifies // the presence of the disk image, not the validity of it. An error is returned -// if the provided disk path exists but is not a directory. +// if the path exists but the destination is not a file or is a symlink. func (d *Disk) Exists() (bool, error) { st, err := d.fs.Stat(d.diskPath) if err != nil && os.IsNotExist(err) { @@ -100,10 +105,10 @@ func (d *Disk) Exists() (bool, error) { } else if err != nil { return false, errors.WithStack(err) } - if st.IsDir() { + if !st.IsDir() && st.Mode()&os.ModeSymlink == 0 { return true, nil } - return false, errors.WithStack(ErrPathNotDirectory) + return false, errors.WithStack(ErrInvalidDiskPathTarget) } // IsMounted checks to see if the given disk is currently mounted. @@ -129,6 +134,11 @@ func (d *Disk) IsMounted(ctx context.Context) (bool, error) { // returned to the caller. If the disk is already mounted an ErrFilesystemMounted // error is returned to the caller. func (d *Disk) Mount(ctx context.Context) error { + if isMounted, err := d.IsMounted(ctx); err != nil { + return errors.WithStackIf(err) + } else if isMounted { + return ErrFilesystemMounted + } if st, err := d.fs.Stat(d.mountAt); err != nil && !os.IsNotExist(err) { return errors.Wrap(err, "vhd: failed to stat mount path") } else if os.IsNotExist(err) { @@ -136,12 +146,11 @@ func (d *Disk) Mount(ctx context.Context) error { return errors.Wrap(err, "vhd: failed to create mount path") } } else if !st.IsDir() { - return errors.WithStack(ErrPathNotDirectory) + return errors.WithStack(ErrMountPathNotDirectory) } - if isMounted, err := d.IsMounted(ctx); err != nil { - return errors.WithStackIf(err) - } else if isMounted { - return ErrFilesystemMounted + u := config.Get().System.User + if err := d.fs.Chown(d.mountAt, u.Uid, u.Gid); err != nil { + return errors.Wrap(err, "vhd: failed to chown mount point") } cmd := d.commander(ctx, "mount", "-t", "auto", "-o", "loop", d.diskPath, d.mountAt) if _, err := cmd.Output(); err != nil { @@ -184,13 +193,13 @@ func (d *Disk) Allocate(ctx context.Context) error { return errors.Wrap(err, "vhd: failed to check for existence of root disk") } trim := path.Base(d.diskPath) - if err := os.MkdirAll(strings.TrimSuffix(d.diskPath, trim), 0600); err != nil { + if err := d.fs.MkdirAll(strings.TrimSuffix(d.diskPath, trim), 0600); err != nil { return errors.Wrap(err, "vhd: failed to create base vhd disk directory") } // We use 1024 as the multiplier for all of the disk space logic within the // application. Passing "K" (/1024) is the same as "KiB" for fallocate, but // is different than "KB" (/1000). - cmd := d.commander(ctx, "fallocate", "-l", fmt.Sprintf("%dK", d.size / 1024), d.diskPath) + cmd := d.commander(ctx, "fallocate", "-l", fmt.Sprintf("%dK", d.size/1024), d.diskPath) if _, err := cmd.Output(); err != nil { msg := "vhd: failed to execute fallocate command" if v, ok := err.(*exec.ExitError); ok { diff --git a/internal/vhd/vhd_test.go b/internal/vhd/vhd_test.go index a92e79a..364f1bf 100644 --- a/internal/vhd/vhd_test.go +++ b/internal/vhd/vhd_test.go @@ -8,11 +8,24 @@ import ( "os/exec" "testing" + "github.com/pterodactyl/wings/config" "github.com/spf13/afero" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) +func init() { + config.Set(&config.Configuration{ + AuthenticationToken: "token123", + System: config.SystemConfiguration{ + User: struct { + Uid int + Gid int + }{Uid: 10, Gid: 10}, + }, + }) +} + type mockCmd struct { run func() error output func() ([]byte, error) @@ -62,16 +75,16 @@ func newMockDisk(c CommanderProvider) *Disk { if c != nil { w = c } - return New(100 * 1024 * 1024, "/foo", "/bar", WithFs(afero.NewMemMapFs()), WithCommander(w)) + return New(100 * 1024 * 1024, "/disk.img", "/mnt", WithFs(afero.NewMemMapFs()), WithCommander(w)) } func Test_New(t *testing.T) { t.Run("creates expected struct", func(t *testing.T) { - d := New(100 * 1024 * 1024, "/foo", "/bar") + d := New(100 * 1024 * 1024, "/disk.img", "/mnt") assert.NotNil(t, d) assert.Equal(t, int64(100 * 1024 * 1024), d.size) - assert.Equal(t, "/foo", d.diskPath) - assert.Equal(t, "/bar", d.mountAt) + assert.Equal(t, "/disk.img", d.diskPath) + assert.Equal(t, "/mnt", d.mountAt) // Ensure by default we get a commander interface returned and that it // returns an *exec.Cmd. @@ -93,7 +106,7 @@ func Test_New(t *testing.T) { return &cprov } - d := New(100, "/foo", "/bar", WithFs(fs), WithCommander(c)) + d := New(100, "/disk.img", "/mnt", WithFs(fs), WithCommander(c)) assert.NotNil(t, d) assert.Same(t, fs, d.fs) assert.Same(t, &cprov, d.commander(context.TODO(), "")) @@ -113,8 +126,9 @@ func Test_New(t *testing.T) { func TestDisk_Exists(t *testing.T) { t.Run("it exists", func(t *testing.T) { d := newMockDisk(nil) - err := d.fs.Mkdir("/foo", 0600) + f, err := d.fs.Create("/disk.img") require.NoError(t, err) + _ = f.Close() exists, err := d.Exists() assert.NoError(t, err) @@ -130,13 +144,13 @@ func TestDisk_Exists(t *testing.T) { t.Run("it reports errors", func(t *testing.T) { d := newMockDisk(nil) - _, err := d.fs.Create("/foo") + err := d.fs.Mkdir("/disk.img", 0600) require.NoError(t, err) exists, err := d.Exists() assert.Error(t, err) assert.False(t, exists) - assert.EqualError(t, err, ErrPathNotDirectory.Error()) + assert.EqualError(t, err, ErrInvalidDiskPathTarget.Error()) }) } @@ -151,7 +165,7 @@ func TestDisk_IsMounted(t *testing.T) { is.Same(pctx, ctx) is.Equal("grep", name) is.Len(args, 3) - is.Equal([]string{"-qs", "/bar ext4", "/proc/mounts"}, args) + is.Equal([]string{"-qs", "/mnt ext4", "/proc/mounts"}, args) return &mockCmd{} } @@ -198,18 +212,24 @@ func TestDisk_IsMounted(t *testing.T) { } func TestDisk_Mount(t *testing.T) { + failedCmd := func(ctx context.Context, name string, args ...string) Commander { + return &mockCmd{run: func() error { + return &mockedExitCode{code: 1} + }} + } + t.Run("error is returned if mount point is not a directory", func(t *testing.T) { - disk := newMockDisk(nil) - _, err := disk.fs.Create("/bar") + disk := newMockDisk(failedCmd) + _, err := disk.fs.Create("/mnt") require.NoError(t, err) err = disk.Mount(context.TODO()) assert.Error(t, err) - assert.EqualError(t, err, ErrPathNotDirectory.Error()) + assert.EqualError(t, err, ErrMountPathNotDirectory.Error()) }) t.Run("error is returned if mount point cannot be created", func(t *testing.T) { - disk := newMockDisk(nil) + disk := newMockDisk(failedCmd) disk.fs = afero.NewReadOnlyFs(disk.fs) err := disk.Mount(context.TODO()) @@ -235,7 +255,7 @@ func TestDisk_Mount(t *testing.T) { called = true assert.Equal(t, "mount", name) - assert.Equal(t, []string{"-t", "auto", "-o", "loop", "/foo", "/bar"}, args) + assert.Equal(t, []string{"-t", "auto", "-o", "loop", "/disk.img", "/mnt"}, args) return nil, &exec.ExitError{ ProcessState: &os.ProcessState{}, @@ -253,35 +273,19 @@ func TestDisk_Mount(t *testing.T) { }) t.Run("disk can be mounted at existing path", func(t *testing.T) { - var cmd CommanderProvider = func(ctx context.Context, name string, args ...string) Commander { - return &mockCmd{ - run: func() error { - return &mockedExitCode{code: 1} - }, - } - } - - disk := newMockDisk(cmd) - require.NoError(t, disk.fs.Mkdir("/bar", 0600)) + disk := newMockDisk(failedCmd) + require.NoError(t, disk.fs.Mkdir("/mnt", 0600)) err := disk.Mount(context.TODO()) assert.NoError(t, err) }) t.Run("disk can be mounted at non-existing path", func(t *testing.T) { - var cmd CommanderProvider = func(ctx context.Context, name string, args ...string) Commander { - return &mockCmd{ - run: func() error { - return &mockedExitCode{code: 1} - }, - } - } - - disk := newMockDisk(cmd) + disk := newMockDisk(failedCmd) err := disk.Mount(context.TODO()) assert.NoError(t, err) - st, err := disk.fs.Stat("/bar") + st, err := disk.fs.Stat("/mnt") assert.NoError(t, err) assert.True(t, st.IsDir()) }) @@ -298,7 +302,7 @@ func TestDisk_Unmount(t *testing.T) { is.Same(pctx, ctx) is.Equal("umount", name) - is.Equal([]string{"/bar"}, args) + is.Equal([]string{"/mnt"}, args) return &mockCmd{} } @@ -360,14 +364,14 @@ func TestDisk_Allocate(t *testing.T) { output: func() ([]byte, error) { called = true assert.Equal(t, "fallocate", name) - assert.Equal(t, []string{"-l", "102400K", "/foo"}, args) + assert.Equal(t, []string{"-l", "102400K", "/disk.img"}, args) return nil, nil }, } } disk := newMockDisk(cmd) - err := disk.fs.Mkdir("/foo", 0600) + err := disk.fs.Mkdir("/mnt", 0600) require.NoError(t, err) err = disk.Allocate(context.TODO()) @@ -394,7 +398,7 @@ func TestDisk_Allocate(t *testing.T) { } disk := newMockDisk(cmd) - err := disk.fs.Mkdir("/foo", 0600) + _, err := disk.fs.Create("/disk.img") require.NoError(t, err) err = disk.Allocate(context.TODO()) @@ -416,7 +420,7 @@ func TestDisk_MakeFilesystem(t *testing.T) { } called = true assert.Equal(t, "mkfs", name) - assert.Equal(t, []string{"-t", "ext4", "/foo"}, args) + assert.Equal(t, []string{"-t", "ext4", "/disk.img"}, args) return nil }, output: func() ([]byte, error) { @@ -443,7 +447,7 @@ func TestDisk_MakeFilesystem(t *testing.T) { } called = true assert.Equal(t, "mkfs", name) - assert.Equal(t, []string{"-t", "ext4", "/foo"}, args) + assert.Equal(t, []string{"-t", "ext4", "/disk.img"}, args) return nil }, output: func() ([]byte, error) {