| // Copyright 2016 The Go Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style |
| // license that can be found in the LICENSE file. |
| |
| //go:build (linux && cgo) || (darwin && cgo) || (freebsd && cgo) |
| |
| package plugin |
| |
| /* |
| #cgo linux LDFLAGS: -ldl |
| #include <dlfcn.h> |
| #include <limits.h> |
| #include <stdlib.h> |
| #include <stdint.h> |
| |
| #include <stdio.h> |
| |
| static uintptr_t pluginOpen(const char* path, char** err) { |
| void* h = dlopen(path, RTLD_NOW|RTLD_GLOBAL); |
| if (h == NULL) { |
| *err = (char*)dlerror(); |
| } |
| return (uintptr_t)h; |
| } |
| |
| static void* pluginLookup(uintptr_t h, const char* name, char** err) { |
| void* r = dlsym((void*)h, name); |
| if (r == NULL) { |
| *err = (char*)dlerror(); |
| } |
| return r; |
| } |
| */ |
| import "C" |
| |
| import ( |
| "errors" |
| "sync" |
| "unsafe" |
| ) |
| |
| func open(name string) (*Plugin, error) { |
| cPath := make([]byte, C.PATH_MAX+1) |
| cRelName := make([]byte, len(name)+1) |
| copy(cRelName, name) |
| if C.realpath( |
| (*C.char)(unsafe.Pointer(&cRelName[0])), |
| (*C.char)(unsafe.Pointer(&cPath[0]))) == nil { |
| return nil, errors.New(`plugin.Open("` + name + `"): realpath failed`) |
| } |
| |
| filepath := C.GoString((*C.char)(unsafe.Pointer(&cPath[0]))) |
| |
| pluginsMu.Lock() |
| if p := plugins[filepath]; p != nil { |
| pluginsMu.Unlock() |
| if p.err != "" { |
| return nil, errors.New(`plugin.Open("` + name + `"): ` + p.err + ` (previous failure)`) |
| } |
| <-p.loaded |
| return p, nil |
| } |
| var cErr *C.char |
| h := C.pluginOpen((*C.char)(unsafe.Pointer(&cPath[0])), &cErr) |
| if h == 0 { |
| pluginsMu.Unlock() |
| return nil, errors.New(`plugin.Open("` + name + `"): ` + C.GoString(cErr)) |
| } |
| // TODO(crawshaw): look for plugin note, confirm it is a Go plugin |
| // and it was built with the correct toolchain. |
| if len(name) > 3 && name[len(name)-3:] == ".so" { |
| name = name[:len(name)-3] |
| } |
| if plugins == nil { |
| plugins = make(map[string]*Plugin) |
| } |
| pluginpath, syms, errstr := lastmoduleinit() |
| if errstr != "" { |
| plugins[filepath] = &Plugin{ |
| pluginpath: pluginpath, |
| err: errstr, |
| } |
| pluginsMu.Unlock() |
| return nil, errors.New(`plugin.Open("` + name + `"): ` + errstr) |
| } |
| // This function can be called from the init function of a plugin. |
| // Drop a placeholder in the map so subsequent opens can wait on it. |
| p := &Plugin{ |
| pluginpath: pluginpath, |
| loaded: make(chan struct{}), |
| } |
| plugins[filepath] = p |
| pluginsMu.Unlock() |
| |
| initStr := make([]byte, len(pluginpath)+len("..inittask")+1) // +1 for terminating NUL |
| copy(initStr, pluginpath) |
| copy(initStr[len(pluginpath):], "..inittask") |
| |
| initTask := C.pluginLookup(h, (*C.char)(unsafe.Pointer(&initStr[0])), &cErr) |
| if initTask != nil { |
| doInit(initTask) |
| } |
| |
| // Fill out the value of each plugin symbol. |
| updatedSyms := map[string]any{} |
| for symName, sym := range syms { |
| isFunc := symName[0] == '.' |
| if isFunc { |
| delete(syms, symName) |
| symName = symName[1:] |
| } |
| |
| fullName := pluginpath + "." + symName |
| cname := make([]byte, len(fullName)+1) |
| copy(cname, fullName) |
| |
| p := C.pluginLookup(h, (*C.char)(unsafe.Pointer(&cname[0])), &cErr) |
| if p == nil { |
| return nil, errors.New(`plugin.Open("` + name + `"): could not find symbol ` + symName + `: ` + C.GoString(cErr)) |
| } |
| valp := (*[2]unsafe.Pointer)(unsafe.Pointer(&sym)) |
| if isFunc { |
| (*valp)[1] = unsafe.Pointer(&p) |
| } else { |
| (*valp)[1] = p |
| } |
| // we can't add to syms during iteration as we'll end up processing |
| // some symbols twice with the inability to tell if the symbol is a function |
| updatedSyms[symName] = sym |
| } |
| p.syms = updatedSyms |
| |
| close(p.loaded) |
| return p, nil |
| } |
| |
| func lookup(p *Plugin, symName string) (Symbol, error) { |
| if s := p.syms[symName]; s != nil { |
| return s, nil |
| } |
| return nil, errors.New("plugin: symbol " + symName + " not found in plugin " + p.pluginpath) |
| } |
| |
| var ( |
| pluginsMu sync.Mutex |
| plugins map[string]*Plugin |
| ) |
| |
| // lastmoduleinit is defined in package runtime |
| func lastmoduleinit() (pluginpath string, syms map[string]any, errstr string) |
| |
| // doInit is defined in package runtime |
| // |
| //go:linkname doInit runtime.doInit |
| func doInit(t unsafe.Pointer) // t should be a *runtime.initTask |