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
import (
"errors"
"fmt"
"github.com/apex/log"
"github.com/cobaugh/osrelease"
"github.com/creasty/defaults"
"github.com/gammazero/workerpool"
"github.com/gbrlsnchs/jwt/v3"
"github.com/pkg/errors"
"gopkg.in/yaml.v2"
"io/ioutil"
"os"
@ -14,6 +15,7 @@ import (
"os/user"
"path"
"regexp"
"runtime"
"strconv"
"strings"
"sync"
@ -154,7 +156,7 @@ func ReadConfiguration(path string) (*Configuration, error) {
return c, nil
}
var Mutex sync.RWMutex
var mu sync.RWMutex
var _config *Configuration
var _jwtAlgo *jwt.HMACSHA
@ -164,14 +166,14 @@ var _debugViaFlag bool
// anything trying to set a different configuration value, or read the configuration
// will be paused until it is complete.
func Set(c *Configuration) {
Mutex.Lock()
mu.Lock()
if _config == nil || _config.AuthenticationToken != c.AuthenticationToken {
_jwtAlgo = jwt.NewHS256([]byte(c.AuthenticationToken))
}
_config = c
Mutex.Unlock()
mu.Unlock()
}
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
// if the configuration is presently being modified.
func Get() *Configuration {
Mutex.RLock()
defer Mutex.RUnlock()
mu.RLock()
defer mu.RUnlock()
return _config
}
// Returns the in-memory JWT algorithm.
func GetJwtAlgorithm() *jwt.HMACSHA {
Mutex.RLock()
defer Mutex.RUnlock()
mu.RLock()
defer mu.RUnlock()
return _jwtAlgo
}
@ -199,7 +201,7 @@ func GetJwtAlgorithm() *jwt.HMACSHA {
func NewFromPath(path string) (*Configuration, error) {
c := new(Configuration)
if err := defaults.Set(c); err != nil {
return c, err
return c, errors.WithStack(err)
}
c.unsafeSetPath(path)
@ -237,12 +239,12 @@ func (c *Configuration) EnsurePterodactylUser() (*user.User, error) {
if err == nil {
return u, c.setSystemUser(u)
} else if _, ok := err.(user.UnknownUserError); !ok {
return nil, err
return nil, errors.WithStack(err)
}
sysName, err := getSystemName()
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)
@ -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
// to the user creation process.
if _, err := exec.Command("addgroup", "-S", c.System.Username).Output(); err != nil {
return nil, err
return nil, errors.WithStack(err)
}
}
split := strings.Split(command, " ")
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 {
return nil, err
return nil, errors.WithStack(err)
} else {
return u, c.setSystemUser(u)
}
@ -286,6 +288,8 @@ func (c *Configuration) setSystemUser(u *user.User) error {
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
// all of the files and folders within.
func (c *Configuration) EnsureFilePermissions() error {
@ -295,45 +299,27 @@ func (c *Configuration) EnsureFilePermissions() error {
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)
if err != nil {
return err
return errors.WithStack(err)
}
su, err := user.Lookup(c.System.Username)
if err != nil {
return err
}
wg := new(sync.WaitGroup)
pool := workerpool.New(runtime.GOMAXPROCS(0))
for _, file := range files {
wg.Add(1)
// Asynchronously run through the list of files and folders in the data directory. If
// 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
f := file
if !f.IsDir() || !uuid4Regex.MatchString(f.Name()) {
continue
}
uid, _ := strconv.Atoi(su.Uid)
gid, _ := strconv.Atoi(su.Gid)
if err := os.Chown(path.Join(c.System.Data, f.Name()), uid, gid); err != nil {
pool.Submit(func() {
if err := os.Chown(path.Join(c.System.Data, f.Name()), c.System.User.Uid, c.System.User.Gid); err != nil {
log.WithField("error", err).WithField("directory", f.Name()).Warn("failed to chown server directory")
}
}(file)
})
}
wg.Wait()
pool.StopWait()
return nil
}
@ -359,11 +345,11 @@ func (c *Configuration) WriteToDisk() error {
b, err := yaml.Marshal(&ccopy)
if err != nil {
return err
return errors.WithStack(err)
}
if err := ioutil.WriteFile(c.GetPath(), b, 0644); err != nil {
return err
return errors.WithStack(err)
}
return nil
@ -373,7 +359,7 @@ func (c *Configuration) WriteToDisk() error {
func getSystemName() (string, error) {
// use osrelease to get release version and ID
if release, err := osrelease.Read(); err != nil {
return "", err
return "", errors.WithStack(err)
} else {
return release["ID"], nil
}