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