blob: 3237598f06b22748e55b53714fc1940da149dfc5 [file] [log] [blame]
// 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.
// +build linux,cgo darwin,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"
)
// avoid a dependency on strings
func lastIndexByte(s string, c byte) int {
for i := len(s) - 1; i >= 0; i-- {
if s[i] == c {
return i
}
}
return -1
}
// pathToPrefix converts raw string to the prefix that will be used in the symbol
// table. If modifying, modify the version in internal/obj/sym.go as well.
func pathToPrefix(s string) string {
slash := lastIndexByte(s, '/')
// check for chars that need escaping
n := 0
for r := 0; r < len(s); r++ {
if c := s[r]; c <= ' ' || (c == '.' && r > slash) || c == '%' || c == '"' || c >= 0x7F {
n++
}
}
// quick exit
if n == 0 {
return s
}
// escape
const hex = "0123456789abcdef"
p := make([]byte, 0, len(s)+2*n)
for r := 0; r < len(s); r++ {
if c := s[r]; c <= ' ' || (c == '.' && r > slash) || c == '%' || c == '"' || c >= 0x7F {
p = append(p, '%', hex[c>>4], hex[c&0xF])
} else {
p = append(p, c)
}
}
return string(p)
}
func open(name string) (*Plugin, error) {
cPath := (*C.char)(C.malloc(C.PATH_MAX + 1))
defer C.free(unsafe.Pointer(cPath))
cRelName := C.CString(name)
defer C.free(unsafe.Pointer(cRelName))
if C.realpath(cRelName, cPath) == nil {
return nil, errors.New("plugin.Open(" + name + "): realpath failed")
}
filepath := C.GoString(cPath)
pluginsMu.Lock()
if p := plugins[filepath]; p != nil {
pluginsMu.Unlock()
<-p.loaded
return p, nil
}
var cErr *C.char
h := C.pluginOpen(cPath, &cErr)
if h == 0 {
pluginsMu.Unlock()
return nil, errors.New("plugin.Open: " + 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]
}
pluginpath, syms, mismatchpkg := lastmoduleinit()
if mismatchpkg != "" {
pluginsMu.Unlock()
return nil, errors.New("plugin.Open: plugin was built with a different version of package " + mismatchpkg)
}
if plugins == nil {
plugins = make(map[string]*Plugin)
}
// 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 := C.CString(pluginpath + ".init")
initFuncPC := C.pluginLookup(h, initStr, &cErr)
C.free(unsafe.Pointer(initStr))
if initFuncPC != nil {
initFuncP := &initFuncPC
initFunc := *(*func())(unsafe.Pointer(&initFuncP))
initFunc()
}
// Fill out the value of each plugin symbol.
updatedSyms := map[string]interface{}{}
for symName, sym := range syms {
isFunc := symName[0] == '.'
if isFunc {
delete(syms, symName)
symName = symName[1:]
}
cname := C.CString(pathToPrefix(pluginpath) + "." + symName)
p := C.pluginLookup(h, cname, &cErr)
C.free(unsafe.Pointer(cname))
if p == nil {
return nil, errors.New("plugin.Open: 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]interface{}, mismatchpkg string)