Code cleanup; use a worker pool for updating file permissions to avoid run-away go-routines

Co-Authored-By: Jakob <schrej@users.noreply.github.com>
This commit is contained in:
Dane Everitt 2020-07-31 21:14:49 -07:00
parent 38efb68e8a
commit 5e8425ad6a
No known key found for this signature in database
GPG Key ID: EEA66103B3D71F53

View File

@ -1,12 +1,13 @@
package config package config
import ( import (
"errors"
"fmt" "fmt"
"github.com/apex/log" "github.com/apex/log"
"github.com/cobaugh/osrelease" "github.com/cobaugh/osrelease"
"github.com/creasty/defaults" "github.com/creasty/defaults"
"github.com/gammazero/workerpool"
"github.com/gbrlsnchs/jwt/v3" "github.com/gbrlsnchs/jwt/v3"
"github.com/pkg/errors"
"gopkg.in/yaml.v2" "gopkg.in/yaml.v2"
"io/ioutil" "io/ioutil"
"os" "os"
@ -14,6 +15,7 @@ import (
"os/user" "os/user"
"path" "path"
"regexp" "regexp"
"runtime"
"strconv" "strconv"
"strings" "strings"
"sync" "sync"
@ -154,7 +156,7 @@ func ReadConfiguration(path string) (*Configuration, error) {
return c, nil return c, nil
} }
var Mutex sync.RWMutex var mu sync.RWMutex
var _config *Configuration var _config *Configuration
var _jwtAlgo *jwt.HMACSHA var _jwtAlgo *jwt.HMACSHA
@ -164,14 +166,14 @@ var _debugViaFlag bool
// anything trying to set a different configuration value, or read the configuration // anything trying to set a different configuration value, or read the configuration
// will be paused until it is complete. // will be paused until it is complete.
func Set(c *Configuration) { func Set(c *Configuration) {
Mutex.Lock() mu.Lock()
if _config == nil || _config.AuthenticationToken != c.AuthenticationToken { if _config == nil || _config.AuthenticationToken != c.AuthenticationToken {
_jwtAlgo = jwt.NewHS256([]byte(c.AuthenticationToken)) _jwtAlgo = jwt.NewHS256([]byte(c.AuthenticationToken))
} }
_config = c _config = c
Mutex.Unlock() mu.Unlock()
} }
func SetDebugViaFlag(d bool) { func SetDebugViaFlag(d bool) {
@ -181,16 +183,16 @@ func SetDebugViaFlag(d bool) {
// Get the global configuration instance. This is a read-safe operation that will block // Get the global configuration instance. This is a read-safe operation that will block
// if the configuration is presently being modified. // if the configuration is presently being modified.
func Get() *Configuration { func Get() *Configuration {
Mutex.RLock() mu.RLock()
defer Mutex.RUnlock() defer mu.RUnlock()
return _config return _config
} }
// Returns the in-memory JWT algorithm. // Returns the in-memory JWT algorithm.
func GetJwtAlgorithm() *jwt.HMACSHA { func GetJwtAlgorithm() *jwt.HMACSHA {
Mutex.RLock() mu.RLock()
defer Mutex.RUnlock() defer mu.RUnlock()
return _jwtAlgo return _jwtAlgo
} }
@ -199,7 +201,7 @@ func GetJwtAlgorithm() *jwt.HMACSHA {
func NewFromPath(path string) (*Configuration, error) { func NewFromPath(path string) (*Configuration, error) {
c := new(Configuration) c := new(Configuration)
if err := defaults.Set(c); err != nil { if err := defaults.Set(c); err != nil {
return c, err return c, errors.WithStack(err)
} }
c.unsafeSetPath(path) c.unsafeSetPath(path)
@ -237,12 +239,12 @@ func (c *Configuration) EnsurePterodactylUser() (*user.User, error) {
if err == nil { if err == nil {
return u, c.setSystemUser(u) return u, c.setSystemUser(u)
} else if _, ok := err.(user.UnknownUserError); !ok { } else if _, ok := err.(user.UnknownUserError); !ok {
return nil, err return nil, errors.WithStack(err)
} }
sysName, err := getSystemName() sysName, err := getSystemName()
if err != nil { if err != nil {
return nil, err return nil, errors.WithStack(err)
} }
var command = fmt.Sprintf("useradd --system --no-create-home --shell /bin/false %s", c.System.Username) var command = fmt.Sprintf("useradd --system --no-create-home --shell /bin/false %s", c.System.Username)
@ -255,17 +257,17 @@ func (c *Configuration) EnsurePterodactylUser() (*user.User, error) {
// We have to create the group first on Alpine, so do that here before continuing on // We have to create the group first on Alpine, so do that here before continuing on
// to the user creation process. // to the user creation process.
if _, err := exec.Command("addgroup", "-S", c.System.Username).Output(); err != nil { if _, err := exec.Command("addgroup", "-S", c.System.Username).Output(); err != nil {
return nil, err return nil, errors.WithStack(err)
} }
} }
split := strings.Split(command, " ") split := strings.Split(command, " ")
if _, err := exec.Command(split[0], split[1:]...).Output(); err != nil { if _, err := exec.Command(split[0], split[1:]...).Output(); err != nil {
return nil, err return nil, errors.WithStack(err)
} }
if u, err := user.Lookup(c.System.Username); err != nil { if u, err := user.Lookup(c.System.Username); err != nil {
return nil, err return nil, errors.WithStack(err)
} else { } else {
return u, c.setSystemUser(u) return u, c.setSystemUser(u)
} }
@ -286,6 +288,8 @@ func (c *Configuration) setSystemUser(u *user.User) error {
return c.WriteToDisk() return c.WriteToDisk()
} }
var uuid4Regex = regexp.MustCompile("^[a-f0-9]{8}-[a-f0-9]{4}-4[a-f0-9]{3}-[89ab][a-f0-9]{3}-[a-f0-9]{12}$")
// Ensures that the configured data directory has the correct permissions assigned to // Ensures that the configured data directory has the correct permissions assigned to
// all of the files and folders within. // all of the files and folders within.
func (c *Configuration) EnsureFilePermissions() error { func (c *Configuration) EnsureFilePermissions() error {
@ -295,45 +299,27 @@ func (c *Configuration) EnsureFilePermissions() error {
return nil return nil
} }
r := regexp.MustCompile("^[a-f0-9]{8}-[a-f0-9]{4}-4[a-f0-9]{3}-[89ab][a-f0-9]{3}-[a-f0-9]{12}$")
files, err := ioutil.ReadDir(c.System.Data) files, err := ioutil.ReadDir(c.System.Data)
if err != nil { if err != nil {
return err return errors.WithStack(err)
} }
su, err := user.Lookup(c.System.Username) pool := workerpool.New(runtime.GOMAXPROCS(0))
if err != nil {
return err
}
wg := new(sync.WaitGroup)
for _, file := range files { for _, file := range files {
wg.Add(1) f := file
if !f.IsDir() || !uuid4Regex.MatchString(f.Name()) {
// Asynchronously run through the list of files and folders in the data directory. If continue
// the item is not a folder, or is not a folder that matches the expected UUIDv4 format
// skip over it.
//
// If we do have a positive match, run a chown against the directory.
go func(f os.FileInfo) {
defer wg.Done()
if !f.IsDir() || !r.MatchString(f.Name()) {
return
} }
uid, _ := strconv.Atoi(su.Uid) pool.Submit(func() {
gid, _ := strconv.Atoi(su.Gid) if err := os.Chown(path.Join(c.System.Data, f.Name()), c.System.User.Uid, c.System.User.Gid); err != nil {
if err := os.Chown(path.Join(c.System.Data, f.Name()), uid, gid); err != nil {
log.WithField("error", err).WithField("directory", f.Name()).Warn("failed to chown server directory") log.WithField("error", err).WithField("directory", f.Name()).Warn("failed to chown server directory")
} }
}(file) })
} }
wg.Wait() pool.StopWait()
return nil return nil
} }
@ -359,11 +345,11 @@ func (c *Configuration) WriteToDisk() error {
b, err := yaml.Marshal(&ccopy) b, err := yaml.Marshal(&ccopy)
if err != nil { if err != nil {
return err return errors.WithStack(err)
} }
if err := ioutil.WriteFile(c.GetPath(), b, 0644); err != nil { if err := ioutil.WriteFile(c.GetPath(), b, 0644); err != nil {
return err return errors.WithStack(err)
} }
return nil return nil
@ -373,7 +359,7 @@ func (c *Configuration) WriteToDisk() error {
func getSystemName() (string, error) { func getSystemName() (string, error) {
// use osrelease to get release version and ID // use osrelease to get release version and ID
if release, err := osrelease.Read(); err != nil { if release, err := osrelease.Read(); err != nil {
return "", err return "", errors.WithStack(err)
} else { } else {
return release["ID"], nil return release["ID"], nil
} }