Home | History | Annotate | Download | only in plugin
      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)
    163