github.com/muesli/go@v0.0.0-20170208044820-e410d2a81ef2/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  func open(name string) (*Plugin, error) {
    43  	cPath := (*C.char)(C.malloc(C.PATH_MAX + 1))
    44  	defer C.free(unsafe.Pointer(cPath))
    45  
    46  	cRelName := C.CString(name)
    47  	defer C.free(unsafe.Pointer(cRelName))
    48  	if C.realpath(cRelName, cPath) == nil {
    49  		return nil, errors.New("plugin.Open(" + name + "): realpath failed")
    50  	}
    51  
    52  	filepath := C.GoString(cPath)
    53  
    54  	pluginsMu.Lock()
    55  	if p := plugins[filepath]; p != nil {
    56  		pluginsMu.Unlock()
    57  		<-p.loaded
    58  		return p, nil
    59  	}
    60  	var cErr *C.char
    61  	h := C.pluginOpen(cPath, &cErr)
    62  	if h == 0 {
    63  		pluginsMu.Unlock()
    64  		return nil, errors.New("plugin.Open: " + C.GoString(cErr))
    65  	}
    66  	// TODO(crawshaw): look for plugin note, confirm it is a Go plugin
    67  	// and it was built with the correct toolchain.
    68  	if len(name) > 3 && name[len(name)-3:] == ".so" {
    69  		name = name[:len(name)-3]
    70  	}
    71  
    72  	pluginpath, syms, mismatchpkg := lastmoduleinit()
    73  	if mismatchpkg != "" {
    74  		pluginsMu.Unlock()
    75  		return nil, errors.New("plugin.Open: plugin was built with a different version of package " + mismatchpkg)
    76  	}
    77  	if plugins == nil {
    78  		plugins = make(map[string]*Plugin)
    79  	}
    80  	// This function can be called from the init function of a plugin.
    81  	// Drop a placeholder in the map so subsequent opens can wait on it.
    82  	p := &Plugin{
    83  		pluginpath: pluginpath,
    84  		loaded:     make(chan struct{}),
    85  		syms:       syms,
    86  	}
    87  	plugins[filepath] = p
    88  	pluginsMu.Unlock()
    89  
    90  	initStr := C.CString(pluginpath + ".init")
    91  	initFuncPC := C.pluginLookup(h, initStr, &cErr)
    92  	C.free(unsafe.Pointer(initStr))
    93  	if initFuncPC != nil {
    94  		initFuncP := &initFuncPC
    95  		initFunc := *(*func())(unsafe.Pointer(&initFuncP))
    96  		initFunc()
    97  	}
    98  
    99  	// Fill out the value of each plugin symbol.
   100  	for symName, sym := range syms {
   101  		isFunc := symName[0] == '.'
   102  		if isFunc {
   103  			delete(syms, symName)
   104  			symName = symName[1:]
   105  		}
   106  
   107  		cname := C.CString(pluginpath + "." + symName)
   108  		p := C.pluginLookup(h, cname, &cErr)
   109  		C.free(unsafe.Pointer(cname))
   110  		if p == nil {
   111  			return nil, errors.New("plugin.Open: could not find symbol " + symName + ": " + C.GoString(cErr))
   112  		}
   113  		valp := (*[2]unsafe.Pointer)(unsafe.Pointer(&sym))
   114  		if isFunc {
   115  			(*valp)[1] = unsafe.Pointer(&p)
   116  		} else {
   117  			(*valp)[1] = p
   118  		}
   119  		syms[symName] = sym
   120  	}
   121  	close(p.loaded)
   122  	return p, nil
   123  }
   124  
   125  func lookup(p *Plugin, symName string) (Symbol, error) {
   126  	if s := p.syms[symName]; s != nil {
   127  		return s, nil
   128  	}
   129  	return nil, errors.New("plugin: symbol " + symName + " not found in plugin " + p.pluginpath)
   130  }
   131  
   132  var (
   133  	pluginsMu sync.Mutex
   134  	plugins   map[string]*Plugin
   135  )
   136  
   137  // lastmoduleinit is defined in package runtime
   138  func lastmoduleinit() (pluginpath string, syms map[string]interface{}, mismatchpkg string)