Black Lives Matter. Support the Equal Justice Initiative.

Source file src/plugin/plugin_dlopen.go

Documentation: 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  //go:build (linux && cgo) || (darwin && cgo) || (freebsd && cgo)
     6  // +build linux,cgo darwin,cgo freebsd,cgo
     7  
     8  package plugin
     9  
    10  /*
    11  #cgo linux LDFLAGS: -ldl
    12  #include <dlfcn.h>
    13  #include <limits.h>
    14  #include <stdlib.h>
    15  #include <stdint.h>
    16  
    17  #include <stdio.h>
    18  
    19  static uintptr_t pluginOpen(const char* path, char** err) {
    20  	void* h = dlopen(path, RTLD_NOW|RTLD_GLOBAL);
    21  	if (h == NULL) {
    22  		*err = (char*)dlerror();
    23  	}
    24  	return (uintptr_t)h;
    25  }
    26  
    27  static void* pluginLookup(uintptr_t h, const char* name, char** err) {
    28  	void* r = dlsym((void*)h, name);
    29  	if (r == NULL) {
    30  		*err = (char*)dlerror();
    31  	}
    32  	return r;
    33  }
    34  */
    35  import "C"
    36  
    37  import (
    38  	"errors"
    39  	"sync"
    40  	"unsafe"
    41  )
    42  
    43  func open(name string) (*Plugin, error) {
    44  	cPath := make([]byte, C.PATH_MAX+1)
    45  	cRelName := make([]byte, len(name)+1)
    46  	copy(cRelName, name)
    47  	if C.realpath(
    48  		(*C.char)(unsafe.Pointer(&cRelName[0])),
    49  		(*C.char)(unsafe.Pointer(&cPath[0]))) == nil {
    50  		return nil, errors.New(`plugin.Open("` + name + `"): realpath failed`)
    51  	}
    52  
    53  	filepath := C.GoString((*C.char)(unsafe.Pointer(&cPath[0])))
    54  
    55  	pluginsMu.Lock()
    56  	if p := plugins[filepath]; p != nil {
    57  		pluginsMu.Unlock()
    58  		if p.err != "" {
    59  			return nil, errors.New(`plugin.Open("` + name + `"): ` + p.err + ` (previous failure)`)
    60  		}
    61  		<-p.loaded
    62  		return p, nil
    63  	}
    64  	var cErr *C.char
    65  	h := C.pluginOpen((*C.char)(unsafe.Pointer(&cPath[0])), &cErr)
    66  	if h == 0 {
    67  		pluginsMu.Unlock()
    68  		return nil, errors.New(`plugin.Open("` + name + `"): ` + C.GoString(cErr))
    69  	}
    70  	// TODO(crawshaw): look for plugin note, confirm it is a Go plugin
    71  	// and it was built with the correct toolchain.
    72  	if len(name) > 3 && name[len(name)-3:] == ".so" {
    73  		name = name[:len(name)-3]
    74  	}
    75  	if plugins == nil {
    76  		plugins = make(map[string]*Plugin)
    77  	}
    78  	pluginpath, syms, errstr := lastmoduleinit()
    79  	if errstr != "" {
    80  		plugins[filepath] = &Plugin{
    81  			pluginpath: pluginpath,
    82  			err:        errstr,
    83  		}
    84  		pluginsMu.Unlock()
    85  		return nil, errors.New(`plugin.Open("` + name + `"): ` + errstr)
    86  	}
    87  	// This function can be called from the init function of a plugin.
    88  	// Drop a placeholder in the map so subsequent opens can wait on it.
    89  	p := &Plugin{
    90  		pluginpath: pluginpath,
    91  		loaded:     make(chan struct{}),
    92  	}
    93  	plugins[filepath] = p
    94  	pluginsMu.Unlock()
    95  
    96  	initStr := make([]byte, len(pluginpath)+len("..inittask")+1) // +1 for terminating NUL
    97  	copy(initStr, pluginpath)
    98  	copy(initStr[len(pluginpath):], "..inittask")
    99  
   100  	initTask := C.pluginLookup(h, (*C.char)(unsafe.Pointer(&initStr[0])), &cErr)
   101  	if initTask != nil {
   102  		doInit(initTask)
   103  	}
   104  
   105  	// Fill out the value of each plugin symbol.
   106  	updatedSyms := map[string]interface{}{}
   107  	for symName, sym := range syms {
   108  		isFunc := symName[0] == '.'
   109  		if isFunc {
   110  			delete(syms, symName)
   111  			symName = symName[1:]
   112  		}
   113  
   114  		fullName := pluginpath + "." + symName
   115  		cname := make([]byte, len(fullName)+1)
   116  		copy(cname, fullName)
   117  
   118  		p := C.pluginLookup(h, (*C.char)(unsafe.Pointer(&cname[0])), &cErr)
   119  		if p == nil {
   120  			return nil, errors.New(`plugin.Open("` + name + `"): could not find symbol ` + symName + `: ` + C.GoString(cErr))
   121  		}
   122  		valp := (*[2]unsafe.Pointer)(unsafe.Pointer(&sym))
   123  		if isFunc {
   124  			(*valp)[1] = unsafe.Pointer(&p)
   125  		} else {
   126  			(*valp)[1] = p
   127  		}
   128  		// we can't add to syms during iteration as we'll end up processing
   129  		// some symbols twice with the inability to tell if the symbol is a function
   130  		updatedSyms[symName] = sym
   131  	}
   132  	p.syms = updatedSyms
   133  
   134  	close(p.loaded)
   135  	return p, nil
   136  }
   137  
   138  func lookup(p *Plugin, symName string) (Symbol, error) {
   139  	if s := p.syms[symName]; s != nil {
   140  		return s, nil
   141  	}
   142  	return nil, errors.New("plugin: symbol " + symName + " not found in plugin " + p.pluginpath)
   143  }
   144  
   145  var (
   146  	pluginsMu sync.Mutex
   147  	plugins   map[string]*Plugin
   148  )
   149  
   150  // lastmoduleinit is defined in package runtime
   151  func lastmoduleinit() (pluginpath string, syms map[string]interface{}, errstr string)
   152  
   153  // doInit is defined in package runtime
   154  //go:linkname doInit runtime.doInit
   155  func doInit(t unsafe.Pointer) // t should be a *runtime.initTask
   156  

View as plain text