github.com/likebike/go--@v0.0.0-20190911215757-0bd925d16e96/go/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  func open(name string) (*Plugin, error) {
    53  	cPath := make([]byte, C.PATH_MAX+1)
    54  	cRelName := make([]byte, len(name)+1)
    55  	copy(cRelName, name)
    56  	if C.realpath(
    57  		(*C.char)(unsafe.Pointer(&cRelName[0])),
    58  		(*C.char)(unsafe.Pointer(&cPath[0]))) == nil {
    59  		return nil, errors.New(`plugin.Open("` + name + `"): realpath failed`)
    60  	}
    61  
    62  	filepath := C.GoString((*C.char)(unsafe.Pointer(&cPath[0])))
    63  
    64  	pluginsMu.Lock()
    65  	if p := plugins[filepath]; p != nil {
    66  		pluginsMu.Unlock()
    67  		if p.err != "" {
    68  			return nil, errors.New(`plugin.Open("` + name + `"): ` + p.err + ` (previous failure)`)
    69  		}
    70  		<-p.loaded
    71  		return p, nil
    72  	}
    73  	var cErr *C.char
    74  	h := C.pluginOpen((*C.char)(unsafe.Pointer(&cPath[0])), &cErr)
    75  	if h == 0 {
    76  		pluginsMu.Unlock()
    77  		return nil, errors.New(`plugin.Open("` + name + `"): ` + C.GoString(cErr))
    78  	}
    79  	// TODO(crawshaw): look for plugin note, confirm it is a Go plugin
    80  	// and it was built with the correct toolchain.
    81  	if len(name) > 3 && name[len(name)-3:] == ".so" {
    82  		name = name[:len(name)-3]
    83  	}
    84  	if plugins == nil {
    85  		plugins = make(map[string]*Plugin)
    86  	}
    87  	pluginpath, syms, errstr := lastmoduleinit()
    88  	if errstr != "" {
    89  		plugins[filepath] = &Plugin{
    90  			pluginpath: pluginpath,
    91  			err:        errstr,
    92  		}
    93  		pluginsMu.Unlock()
    94  		return nil, errors.New(`plugin.Open("` + name + `"): ` + errstr)
    95  	}
    96  	// This function can be called from the init function of a plugin.
    97  	// Drop a placeholder in the map so subsequent opens can wait on it.
    98  	p := &Plugin{
    99  		pluginpath: pluginpath,
   100  		loaded:     make(chan struct{}),
   101  	}
   102  	plugins[filepath] = p
   103  	pluginsMu.Unlock()
   104  
   105  	initStr := make([]byte, len(pluginpath)+6)
   106  	copy(initStr, pluginpath)
   107  	copy(initStr[len(pluginpath):], ".init")
   108  
   109  	initFuncPC := C.pluginLookup(h, (*C.char)(unsafe.Pointer(&initStr[0])), &cErr)
   110  	if initFuncPC != nil {
   111  		initFuncP := &initFuncPC
   112  		initFunc := *(*func())(unsafe.Pointer(&initFuncP))
   113  		initFunc()
   114  	}
   115  
   116  	// Fill out the value of each plugin symbol.
   117  	updatedSyms := map[string]interface{}{}
   118  	for symName, sym := range syms {
   119  		isFunc := symName[0] == '.'
   120  		if isFunc {
   121  			delete(syms, symName)
   122  			symName = symName[1:]
   123  		}
   124  
   125  		fullName := pluginpath + "." + symName
   126  		cname := make([]byte, len(fullName)+1)
   127  		copy(cname, fullName)
   128  
   129  		p := C.pluginLookup(h, (*C.char)(unsafe.Pointer(&cname[0])), &cErr)
   130  		if p == nil {
   131  			return nil, errors.New(`plugin.Open("` + name + `"): could not find symbol ` + symName + `: ` + C.GoString(cErr))
   132  		}
   133  		valp := (*[2]unsafe.Pointer)(unsafe.Pointer(&sym))
   134  		if isFunc {
   135  			(*valp)[1] = unsafe.Pointer(&p)
   136  		} else {
   137  			(*valp)[1] = p
   138  		}
   139  		// we can't add to syms during iteration as we'll end up processing
   140  		// some symbols twice with the inability to tell if the symbol is a function
   141  		updatedSyms[symName] = sym
   142  	}
   143  	p.syms = updatedSyms
   144  
   145  	close(p.loaded)
   146  	return p, nil
   147  }
   148  
   149  func lookup(p *Plugin, symName string) (Symbol, error) {
   150  	if s := p.syms[symName]; s != nil {
   151  		return s, nil
   152  	}
   153  	return nil, errors.New("plugin: symbol " + symName + " not found in plugin " + p.pluginpath)
   154  }
   155  
   156  var (
   157  	pluginsMu sync.Mutex
   158  	plugins   map[string]*Plugin
   159  )
   160  
   161  // lastmoduleinit is defined in package runtime
   162  func lastmoduleinit() (pluginpath string, syms map[string]interface{}, errstr string)