func (fs *Filesystem) ListDirectory(p string) ([]Stat, error) {
	cleaned, err := fs.SafePath(p)
	if err != nil {
		return nil, err
	}
	files, err := ioutil.ReadDir(cleaned)
	if err != nil {
		return nil, err
	}
	var wg sync.WaitGroup
	out := make([]Stat, len(files))
	for i, file := range files {
		wg.Add(1)
		go func(idx int, f os.FileInfo) {
			defer wg.Done()
			var m *mimetype.MIME
			var d = "inode/directory"
			if !f.IsDir() {
				cleanedp := filepath.Join(cleaned, f.Name())
				if f.Mode()&os.ModeSymlink != 0 {
					cleanedp, _ = fs.SafePath(filepath.Join(cleaned, f.Name()))
				}
				if cleanedp != "" {
					m, _ = mimetype.DetectFile(filepath.Join(cleaned, f.Name()))
				} else {
					d = "application/octet-stream"
				}
			}
			st := Stat{FileInfo: f, Mimetype: d}
			if m != nil {
				st.Mimetype = m.String()
			}
			out[idx] = st
		}(i, file)
	}
	wg.Wait()
	sort.SliceStable(out, func(i, j int) bool {
		if out[i].Name() == out[j].Name() || out[i].Name() > out[j].Name() {
			return true
		}
		return false
	})
	sort.SliceStable(out, func(i, j int) bool {
		return out[i].IsDir()
	})
	return out, nil
}
ListDirectory lists the contents of a given directory and returns stat
information about each file and folder within it.
package filesystem
import (
	"bufio"
	"io"
	"io/ioutil"
	"os"
	"path"
	"path/filepath"
	"sort"
	"strconv"
	"strings"
	"sync"
	"sync/atomic"
	"time"
	"emperror.dev/errors"
	"github.com/gabriel-vasile/mimetype"
	"github.com/karrick/godirwalk"
	ignore "github.com/sabhiram/go-gitignore"
	"github.com/pterodactyl/wings/config"
	"github.com/pterodactyl/wings/system"
)
type Filesystem struct {
	mu                sync.RWMutex
	lastLookupTime    *usageLookupTime
	lookupInProgress  *system.AtomicBool
	diskUsed          int64
	diskCheckInterval time.Duration
	denylist          *ignore.GitIgnore
	diskLimit int64
	root string
	isTest bool
}
func New(root string, size int64, denylist []string) *Filesystem {
	return &Filesystem{
		root:              root,
		diskLimit:         size,
		diskCheckInterval: time.Duration(config.Get().System.DiskCheckInterval),
		lastLookupTime:    &usageLookupTime{},
		lookupInProgress:  system.NewAtomicBool(false),
		denylist:          ignore.CompileIgnoreLines(denylist...),
	}
}
func (fs *Filesystem) Path() string {
	return fs.root
}
func (fs *Filesystem) File(p string) (*os.File, Stat, error) {
	cleaned, err := fs.SafePath(p)
	if err != nil {
		return nil, Stat{}, err
	}
	st, err := fs.Stat(cleaned)
	if err != nil {
		return nil, Stat{}, err
	}
	if st.IsDir() {
		return nil, Stat{}, newFilesystemError(ErrCodeIsDirectory, nil)
	}
	f, err := os.Open(cleaned)
	if err != nil {
		return nil, Stat{}, err
	}
	return f, st, nil
}
func (fs *Filesystem) Touch(p string, flag int) (*os.File, error) {
	cleaned, err := fs.SafePath(p)
	if err != nil {
		return nil, err
	}
	f, err := os.OpenFile(cleaned, flag, 0644)
	if err == nil {
		return f, nil
	}
	if !errors.Is(err, os.ErrNotExist) {
		return nil, errors.Wrap(err, "server/filesystem: touch: failed to open file handle")
	}
	if _, err := os.Stat(filepath.Dir(cleaned)); errors.Is(err, os.ErrNotExist) {
		if err := os.MkdirAll(filepath.Dir(cleaned), 0755); err != nil {
			return nil, errors.Wrap(err, "server/filesystem: touch: failed to create directory tree")
		}
		if err := fs.Chown(filepath.Dir(cleaned)); err != nil {
			return nil, err
		}
	}
	o := &fileOpener{}
	f, err = o.open(cleaned, flag, 0644)
	if err != nil {
		return nil, errors.Wrap(err, "server/filesystem: touch: failed to open file with wait")
	}
	_ = fs.Chown(cleaned)
	return f, nil
}
func (fs *Filesystem) Readfile(p string, w io.Writer) error {
	file, _, err := fs.File(p)
	if err != nil {
		return err
	}
	defer file.Close()
	_, err = bufio.NewReader(file).WriteTo(w)
	return err
}
func (fs *Filesystem) Writefile(p string, r io.Reader) error {
	cleaned, err := fs.SafePath(p)
	if err != nil {
		return err
	}
	var currentSize int64
	stat, err := os.Stat(cleaned)
	if err != nil && !os.IsNotExist(err) {
		return errors.Wrap(err, "server/filesystem: writefile: failed to stat file")
	} else if err == nil {
		if stat.IsDir() {
			return errors.WithStack(&Error{code: ErrCodeIsDirectory, resolved: cleaned})
		}
		currentSize = stat.Size()
	}
	br := bufio.NewReader(r)
	if err := fs.HasSpaceFor(int64(br.Size()) - currentSize); err != nil {
		return err
	}
	file, err := fs.Touch(cleaned, os.O_RDWR|os.O_CREATE|os.O_TRUNC)
	if err != nil {
		return err
	}
	defer file.Close()
	buf := make([]byte, 1024*4)
	sz, err := io.CopyBuffer(file, r, buf)
	fs.addDisk(sz - currentSize)
	return fs.Chown(cleaned)
}
func (fs *Filesystem) CreateDirectory(name string, p string) error {
	cleaned, err := fs.SafePath(path.Join(p, name))
	if err != nil {
		return err
	}
	return os.MkdirAll(cleaned, 0755)
}
func (fs *Filesystem) Rename(from string, to string) error {
	cleanedFrom, err := fs.SafePath(from)
	if err != nil {
		return err
	}
	cleanedTo, err := fs.SafePath(to)
	if err != nil {
		return err
	}
	if _, err := os.Stat(cleanedTo); err == nil {
		return os.ErrExist
	}
	if cleanedTo == fs.Path() {
		return errors.New("attempting to rename into an invalid directory space")
	}
	d := strings.TrimSuffix(cleanedTo, path.Base(cleanedTo))
	if d != fs.Path() {
		if mkerr := os.MkdirAll(d, 0755); mkerr != nil {
			return errors.WithMessage(mkerr, "failed to create directory structure for file rename")
		}
	}
	return os.Rename(cleanedFrom, cleanedTo)
}
func (fs *Filesystem) Chown(path string) error {
	cleaned, err := fs.SafePath(path)
	if err != nil {
		return err
	}
	if fs.isTest {
		return nil
	}
	uid := config.Get().System.User.Uid
	gid := config.Get().System.User.Gid
	if err := os.Chown(cleaned, uid, gid); err != nil {
		return errors.Wrap(err, "server/filesystem: chown: failed to chown path")
	}
	if st, err := os.Stat(cleaned); err != nil || !st.IsDir() {
		return nil
	}
	err = godirwalk.Walk(cleaned, &godirwalk.Options{
		Unsorted: true,
		Callback: func(p string, e *godirwalk.Dirent) error {
			if e.IsSymlink() {
				if e.IsDir() {
					return godirwalk.SkipThis
				}
				return nil
			}
			return os.Chown(p, uid, gid)
		},
	})
	return errors.Wrap(err, "server/filesystem: chown: failed to chown during walk function")
}
func (fs *Filesystem) Chmod(path string, mode os.FileMode) error {
	cleaned, err := fs.SafePath(path)
	if err != nil {
		return err
	}
	if fs.isTest {
		return nil
	}
	if err := os.Chmod(cleaned, mode); err != nil {
		return err
	}
	return nil
}
func (fs *Filesystem) findCopySuffix(dir string, name string, extension string) (string, error) {
	var i int
	suffix := " copy"
	for i = 0; i < 51; i++ {
		if i > 0 {
			suffix = " copy " + strconv.Itoa(i)
		}
		n := name + suffix + extension
		if _, err := fs.Stat(path.Join(dir, n)); err != nil {
			if !errors.Is(err, os.ErrNotExist) {
				return "", err
			}
			break
		}
		if i == 50 {
			suffix = "copy." + time.Now().Format(time.RFC3339)
		}
	}
	return name + suffix + extension, nil
}
func (fs *Filesystem) Copy(p string) error {
	cleaned, err := fs.SafePath(p)
	if err != nil {
		return err
	}
	s, err := os.Stat(cleaned)
	if err != nil {
		return err
	} else if s.IsDir() || !s.Mode().IsRegular() {
		return os.ErrNotExist
	}
	if err := fs.HasSpaceFor(s.Size()); err != nil {
		return err
	}
	base := filepath.Base(cleaned)
	relative := strings.TrimSuffix(strings.TrimPrefix(cleaned, fs.Path()), base)
	extension := filepath.Ext(base)
	name := strings.TrimSuffix(base, extension)
	if strings.HasSuffix(name, ".tar") {
		extension = ".tar" + extension
		name = strings.TrimSuffix(name, ".tar")
	}
	source, err := os.Open(cleaned)
	if err != nil {
		return err
	}
	defer source.Close()
	n, err := fs.findCopySuffix(relative, name, extension)
	if err != nil {
		return err
	}
	return fs.Writefile(path.Join(relative, n), source)
}
func (fs *Filesystem) TruncateRootDirectory() error {
	if err := os.RemoveAll(fs.Path()); err != nil {
		return err
	}
	if err := os.Mkdir(fs.Path(), 0755); err != nil {
		return err
	}
	atomic.StoreInt64(&fs.diskUsed, 0)
	return nil
}
func (fs *Filesystem) Delete(p string) error {
	wg := sync.WaitGroup{}
	resolved := fs.unsafeFilePath(p)
	if !fs.unsafeIsInDataDirectory(resolved) {
		return NewBadPathResolution(p, resolved)
	}
	if resolved == fs.Path() {
		return errors.New("cannot delete root server directory")
	}
	if st, err := os.Lstat(resolved); err != nil {
		if !os.IsNotExist(err) {
			fs.error(err).Warn("error while attempting to stat file before deletion")
		}
	} else {
		if !st.IsDir() {
			fs.addDisk(-st.Size())
		} else {
			wg.Add(1)
			go func(wg *sync.WaitGroup, st os.FileInfo, resolved string) {
				defer wg.Done()
				if s, err := fs.DirectorySize(resolved); err == nil {
					fs.addDisk(-s)
				}
			}(&wg, st, resolved)
		}
	}
	wg.Wait()
	return os.RemoveAll(resolved)
}
type fileOpener struct {
	busy uint
}
func (fo *fileOpener) open(path string, flags int, perm os.FileMode) (*os.File, error) {
	for {
		f, err := os.OpenFile(path, flags, perm)
		if err != nil && fo.busy < 3 && strings.Contains(err.Error(), "text file busy") {
			time.Sleep(100 * time.Millisecond << fo.busy)
			fo.busy++
			continue
		}
		return f, err
	}
}