From f112a2e5648b50248e8914d2c769345c545cf3ad Mon Sep 17 00:00:00 2001 From: Sasha Koshka Date: Tue, 10 Dec 2024 15:51:34 -0500 Subject: [PATCH] Plugins work now oughghgghughgghg --- cmd/stepd/main.go | 32 ++++++++-------- environment.go | 51 ------------------------ error.go | 6 +++ plugin.go | 98 +++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 121 insertions(+), 66 deletions(-) create mode 100644 plugin.go diff --git a/cmd/stepd/main.go b/cmd/stepd/main.go index 4708fe6..896f063 100644 --- a/cmd/stepd/main.go +++ b/cmd/stepd/main.go @@ -129,29 +129,31 @@ func main () { Config: config, } environment.Providers = providers.All() - err := environment.Init(context.Background()) - if err != nil { log.Fatal(err) } // load plugins - for _, pat := range pluginPath { - entries, err := os.ReadDir(pat) - if err != nil { continue } - for _, entry := range entries { - pluginPath := filepath.Join(pat, entry.Name()) - ext := filepath.Ext(pluginPath) - if ext != ".so" { continue } - if flagUnsafePlugins.Value == "true" { - _, err = environment.LoadProviderPluginUnsafe(pluginPath) - } else { - _, err = environment.LoadProviderPlugin(pluginPath) - } - if err != nil { + var err error + var plugins []step.Provider + if flagUnsafePlugins.Value == "true" { + plugins, err = step.LoadAllProviderPluginsUnsafe(pluginPath...) + } else { + plugins, err = step.LoadAllProviderPlugins(pluginPath...) + } + if err != nil { + if errs, ok := err.(step.Errors); ok { + for _, err := range errs.Unwrap() { log.Println("!!!", err) } + } else { + log.Println("!!!", err) } } + environment.Providers = append(environment.Providers, plugins...) logProviders(environment.Providers) + // initialize the environment + err = environment.Init(context.Background()) + if err != nil { log.Fatal(err) } + // set up the HTTP handler handler := stephttp.Handler { Environment: &environment, diff --git a/environment.go b/environment.go index 670d724..96075d9 100644 --- a/environment.go +++ b/environment.go @@ -5,9 +5,7 @@ import "io" import "fmt" import "time" import "io/fs" -import "plugin" import "errors" -import "syscall" import "context" import "path/filepath" import "html/template" @@ -105,55 +103,6 @@ func (this *Environment) Unload (name string) { delete(documents, name) } -// LoadProviderPlugin loads a plugin given its file path. The file must: -// -// - Be a shared library -// - Be built with go -buildmode=plugin -// - Be built with the same version of Go -// - Be built with the same version of STEP -// - Be owned by root -// -// Plugins cannot be unloaded from the current program once they are loaded. -// Sorgy :( its Go's fault. -func (this *Environment) LoadProviderPlugin (name string) (Provider, error) { - return this.loadProviderPlugin(name, true) -} - -// LoadProviderPluginUnsafe is like LoadProviderPlugin, but does not check to -// see that the file is owned by root, thereby making it easier to run a random -// plugin you just compiled. This should not be used otherwise. -func (this *Environment) LoadProviderPluginUnsafe (name string) (Provider, error) { - return this.loadProviderPlugin(name, false) -} - -func (this *Environment) loadProviderPlugin (name string, checkRoot bool) (Provider, error) { - plugin, err := this.loadPlugin(name, checkRoot) - if err != nil { return nil, err } - providerSymbol, err := plugin.Lookup("NewProvider") - if err != nil { return nil, err } - providerFactory, ok := providerSymbol.(func () Provider) - if !ok { return nil, ErrPluginBadSymbol } - provider := providerFactory() - this.Providers = append(this.Providers, provider) - return provider, nil -} - -func (this *Environment) loadPlugin (name string, checkRoot bool) (*plugin.Plugin, error) { - name = filepath.Clean(name) - if checkRoot { - info, err := os.Stat(name) - if err != nil { return nil, err } - if info, ok := info.Sys().(*syscall.Stat_t); ok { - if info.Uid != 0 { - return nil, ErrPluginNotOwnedByRoot - } - } else { - return nil, ErrInsufficientSystem - } - } - return plugin.Open(name) -} - func (this *Environment) parse (name string, modTime time.Time, input io.Reader) (*Document, error) { documents, done := this.documents.Borrow() defer done() diff --git a/error.go b/error.go index 46a1317..72d651a 100644 --- a/error.go +++ b/error.go @@ -8,6 +8,7 @@ type Error string; const ( ErrTypeMismatch Error = "type mismatch" ErrPluginBadSymbol Error = "plugin has an incorrect symbol" ErrPluginNotOwnedByRoot Error = "plugin is not owned by the root user" + ErrPathNotAbsolute Error = "path is not absolute" ErrInsufficientSystem Error = "the system cannot perform this action" ) @@ -15,3 +16,8 @@ type Error string; const ( func (err Error) Error () string { return string(err) } + +// Errors is any error that unwraps to a list of sub-errors. +type Errors interface { + Unwrap () []error +} diff --git a/plugin.go b/plugin.go new file mode 100644 index 0000000..21bd090 --- /dev/null +++ b/plugin.go @@ -0,0 +1,98 @@ +package step + +import "os" +import "errors" +import "plugin" +import "syscall" +import "path/filepath" + +// LoadAllProviderPlugins loads all plugins according to the given search path. +// +// If a non-nil error is returned, the error should be treated as a list of +// warning messages and the return value should be used normally. +func LoadAllProviderPlugins (pluginPath ...string) ([]Provider, error) { + return loadAllProviderPlugins(pluginPath, true) +} + +// LoadAllProviderPluginsUnsafe is like LoadAllProviderPlugins, but does not +// check to see that the files are owned by root, thereby making it easier to +// run a random plugin you just compiled. This should not be used otherwise. +// +// If a non-nil error is returned, the error should be treated as a list of +// warning messages and the return value should be used normally. +func LoadAllProviderPluginsUnsafe (pluginPath ...string) ([]Provider, error) { + return loadAllProviderPlugins(pluginPath, false) +} + +func loadAllProviderPlugins (pluginPath []string, checkRoot bool) ([]Provider, error) { + providers := []Provider { } + errs := []error { } + for _, pat := range pluginPath { + entries, err := os.ReadDir(pat) + if err != nil { continue } + for _, entry := range entries { + pluginPath := filepath.Join(pat, entry.Name()) + ext := filepath.Ext(pluginPath) + if ext != ".so" { continue } + provider, err := loadProviderPlugin(pluginPath, checkRoot) + if err != nil { + errs = append(errs, err) + continue + } + providers = append(providers, provider) + } + } + return providers, errors.Join(errs...) +} + +// LoadProviderPlugin loads a plugin given its file path. The file must: +// +// - Be a shared library +// - Be addressed with an absolute path +// - Be built with go -buildmode=plugin +// - Be built with the same version of Go +// - Be built with the same version of STEP +// - Be owned by root +// +// Plugins cannot be unloaded from the current program once they are loaded. +// Sorgy :( its Go's fault. +func LoadProviderPlugin (name string) (Provider, error) { + return loadProviderPlugin(name, true) +} + +// LoadProviderPluginUnsafe is like LoadProviderPlugin, but does not check to +// see that the file is owned by root, thereby making it easier to run a random +// plugin you just compiled. This should not be used otherwise. +func LoadProviderPluginUnsafe (name string) (Provider, error) { + return loadProviderPlugin(name, false) +} + +func loadProviderPlugin (name string, checkRoot bool) (Provider, error) { + plugin, err := loadPlugin(name, checkRoot) + if err != nil { return nil, err } + providerSymbol, err := plugin.Lookup("NewProvider") + if err != nil { return nil, err } + providerFactory, ok := providerSymbol.(func () Provider) + if !ok { return nil, ErrPluginBadSymbol } + provider := providerFactory() + return provider, nil +} + +func loadPlugin (name string, checkRoot bool) (*plugin.Plugin, error) { + name = filepath.Clean(name) + if !filepath.IsAbs(name) { + return nil, ErrPathNotAbsolute + } + if checkRoot { + info, err := os.Stat(name) + if err != nil { return nil, err } + if info, ok := info.Sys().(*syscall.Stat_t); ok { + if info.Uid != 0 { + return nil, ErrPluginNotOwnedByRoot + } + } else { + return nil, ErrInsufficientSystem + } + } + return plugin.Open(name) +}