99 lines
3.2 KiB
Go
99 lines
3.2 KiB
Go
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)
|
|
}
|