github.com/flyinox/gosm@v0.0.0-20171117061539-16768cb62077/src/plugin/plugin_dlopen.go (about)

     1  // Copyright 2016 The Go Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  // +build linux,cgo darwin,cgo
     6  
     7  package plugin
     8  
     9  /*
    10  #cgo linux LDFLAGS: -ldl
    11  #include <dlfcn.h>
    12  #include <limits.h>
    13  #include <stdlib.h>
    14  #include <stdint.h>
    15  
    16  #include <stdio.h>
    17  
    18  static uintptr_t pluginOpen(const char* path, char** err) {
    19  	void* h = dlopen(path, RTLD_NOW|RTLD_GLOBAL);
    20  	if (h == NULL) {
    21  		*err = (char*)dlerror();
    22  	}
    23  	return (uintptr_t)h;
    24  }
    25  
    26  static void* pluginLookup(uintptr_t h, const char* name, char** err) {
    27  	void* r = dlsym((void*)h, name);
    28  	if (r == NULL) {
    29  		*err = (char*)dlerror();
    30  	}
    31  	return r;
    32  }
    33  */
    34  import "C"
    35  
    36  import (
    37  	"errors"
    38  	"sync"
    39  	"unsafe"
    40  )
    41  
    42  // avoid a dependency on strings
    43  func lastIndexByte(s string, c byte) int {
    44  	for i := len(s) - 1; i >= 0; i-- {
    45  		if s[i] == c {
    46  			return i
    47  		}
    48  	}
    49  	return -1
    50  }
    51  
    52  // pathToPrefix converts raw string to the prefix that will be used in the symbol
    53  // table. If modifying, modify the version in internal/obj/sym.go as well.
    54  func pathToPrefix(s string) string {
    55  	slash := lastIndexByte(s, '/')
    56  	// check for chars that need escaping
    57  	n := 0
    58  	for r := 0; r < len(s); r++ {
    59  		if c := s[r]; c <= ' ' || (c == '.' && r > slash) || c == '%' || c == '"' || c >= 0x7F {
    60  			n++
    61  		}
    62  	}
    63  
    64  	// quick exit
    65  	if n == 0 {
    66  		return s
    67  	}
    68  
    69  	// escape
    70  	const hex = "0123456789abcdef"
    71  	p := make([]byte, 0, len(s)+2*n)
    72  	for r := 0; r < len(s); r++ {
    73  		if c := s[r]; c <= ' ' || (c == '.' && r > slash) || c == '%' || c == '"' || c >= 0x7F {
    74  			p = append(p, '%', hex[c>>4], hex[c&0xF])
    75  		} else {
    76  			p = append(p, c)
    77  		}
    78  	}
    79  
    80  	return string(p)
    81  }
    82  
    83  func open(name string) (*Plugin, error) {
    84  	cPath := (*C.char)(C.malloc(C.PATH_MAX + 1))
    85  	defer C.free(unsafe.Pointer(cPath))
    86  
    87  	cRelName := C.CString(name)
    88  	defer C.free(unsafe.Pointer(cRelName))
    89  	if C.realpath(cRelName, cPath) == nil {
    90  		return nil, errors.New("plugin.Open(" + name + "): realpath failed")
    91  	}
    92  
    93  	filepath := C.GoString(cPath)
    94  
    95  	pluginsMu.Lock()
    96  	if p := plugins[filepath]; p != nil {
    97  		pluginsMu.Unlock()
    98  		<-p.loaded
    99  		return p, nil
   100  	}
   101  	var cErr *C.char
   102  	h := C.pluginOpen(cPath, &cErr)
   103  	if h == 0 {
   104  		pluginsMu.Unlock()
   105  		return nil, errors.New("plugin.Open: " + C.GoString(cErr))
   106  	}
   107  	// TODO(crawshaw): look for plugin note, confirm it is a Go plugin
   108  	// and it was built with the correct toolchain.
   109  	if len(name) > 3 && name[len(name)-3:] == ".so" {
   110  		name = name[:len(name)-3]
   111  	}
   112  
   113  	pluginpath, syms, mismatchpkg := lastmoduleinit()
   114  	if mismatchpkg != "" {
   115  		pluginsMu.Unlock()
   116  		return nil, errors.New("plugin.Open: plugin was built with a different version of package " + mismatchpkg)
   117  	}
   118  	if plugins == nil {
   119  		plugins = make(map[string]*Plugin)
   120  	}
   121  	// This function can be called from the init function of a plugin.
   122  	// Drop a placeholder in the map so subsequent opens can wait on it.
   123  	p := &Plugin{
   124  		pluginpath: pluginpath,
   125  		loaded:     make(chan struct{}),
   126  	}
   127  	plugins[filepath] = p
   128  	pluginsMu.Unlock()
   129  
   130  	initStr := C.CString(pluginpath + ".init")
   131  	initFuncPC := C.pluginLookup(h, initStr, &cErr)
   132  	C.free(unsafe.Pointer(initStr))
   133  	if initFuncPC != nil {
   134  		initFuncP := &initFuncPC
   135  		initFunc := *(*func())(unsafe.Pointer(&initFuncP))
   136  		initFunc()
   137  	}
   138  
   139  	// Fill out the value of each plugin symbol.
   140  	updatedSyms := map[string]interface{}{}
   141  	for symName, sym := range syms {
   142  		isFunc := symName[0] == '.'
   143  		if isFunc {
   144  			delete(syms, symName)
   145  			symName = symName[1:]
   146  		}
   147  		cname := C.CString(pathToPrefix(pluginpath) + "." + symName)
   148  		p := C.pluginLookup(h, cname, &cErr)
   149  		C.free(unsafe.Pointer(cname))
   150  		if p == nil {
   151  			return nil, errors.New("plugin.Open: could not find symbol " + symName + ": " + C.GoString(cErr))
   152  		}
   153  		valp := (*[2]unsafe.Pointer)(unsafe.Pointer(&sym))
   154  		if isFunc {
   155  			(*valp)[1] = unsafe.Pointer(&p)
   156  		} else {
   157  			(*valp)[1] = p
   158  		}
   159  		// we can't add to syms during iteration as we'll end up processing
   160  		// some symbols twice with the inability to tell if the symbol is a function
   161  		updatedSyms[symName] = sym
   162  	}
   163  	p.syms = updatedSyms
   164  
   165  	close(p.loaded)
   166  	return p, nil
   167  }
   168  
   169  func lookup(p *Plugin, symName string) (Symbol, error) {
   170  	if s := p.syms[symName]; s != nil {
   171  		return s, nil
   172  	}
   173  	return nil, errors.New("plugin: symbol " + symName + " not found in plugin " + p.pluginpath)
   174  }
   175  
   176  var (
   177  	pluginsMu sync.Mutex
   178  	plugins   map[string]*Plugin
   179  )
   180  
   181  // lastmoduleinit is defined in package runtime
   182  func lastmoduleinit() (pluginpath string, syms map[string]interface{}, mismatchpkg string)