Merge pull request #51636 from robmry/nri-import

NRI: import and instantiate containerd's NRI adaptation package
This commit is contained in:
Paweł Gronowski
2025-12-09 10:54:56 +00:00
committed by GitHub
364 changed files with 112004 additions and 0 deletions

View File

@@ -37,6 +37,7 @@ import (
networktypes "github.com/moby/moby/api/types/network"
registrytypes "github.com/moby/moby/api/types/registry"
"github.com/moby/moby/api/types/swarm"
"github.com/moby/moby/v2/daemon/internal/nri"
"github.com/moby/sys/user"
"github.com/moby/sys/userns"
"github.com/pkg/errors"
@@ -116,6 +117,7 @@ type Daemon struct {
shutdown bool
idMapping user.IdentityMapping
PluginStore *plugin.Store // TODO: remove
nri *nri.NRI
pluginManager *plugin.Manager
linkIndex *linkIndex
containerdClient *containerd.Client
@@ -1096,6 +1098,14 @@ func NewDaemon(ctx context.Context, config *config.Config, pluginStore *plugin.S
return nil, err
}
d.nri, err = nri.NewNRI(ctx, nri.Config{
DaemonConfig: config.NRIOpts,
ContainerLister: d.containers,
})
if err != nil {
return nil, err
}
driverName := getDriverOverride(ctx, cfgStore.GraphDriver, imgStoreChoice)
var migrationConfig migration.Config
@@ -1486,6 +1496,10 @@ func (daemon *Daemon) Shutdown(ctx context.Context) error {
// Shutdown plugins after containers and layerstore. Don't change the order.
daemon.pluginShutdown()
if daemon.nri != nil {
daemon.nri.Shutdown(ctx)
}
// trigger libnetwork Stop only if it's initialized
if daemon.netController != nil {
daemon.netController.Stop()

View File

@@ -0,0 +1,29 @@
package nri
import (
"context"
"github.com/containerd/log"
nrilog "github.com/containerd/nri/pkg/log"
)
type logShim struct{}
// logShim implements interface nrilog.Logger.
var _ nrilog.Logger = (*logShim)(nil)
func (nls *logShim) Debugf(ctx context.Context, format string, args ...any) {
log.G(ctx).Debugf("NRI: "+format, args...)
}
func (nls *logShim) Infof(ctx context.Context, format string, args ...any) {
log.G(ctx).Infof("NRI: "+format, args...)
}
func (nls *logShim) Warnf(ctx context.Context, format string, args ...any) {
log.G(ctx).Warnf("NRI: "+format, args...)
}
func (nls *logShim) Errorf(ctx context.Context, format string, args ...any) {
log.G(ctx).Errorf("NRI: "+format, args...)
}

130
daemon/internal/nri/nri.go Normal file
View File

@@ -0,0 +1,130 @@
package nri
import (
"context"
"errors"
"fmt"
"path/filepath"
"sync"
"github.com/containerd/log"
"github.com/containerd/nri/pkg/adaptation"
nrilog "github.com/containerd/nri/pkg/log"
"github.com/moby/moby/v2/daemon/container"
"github.com/moby/moby/v2/daemon/internal/rootless"
"github.com/moby/moby/v2/daemon/pkg/opts"
"github.com/moby/moby/v2/dockerversion"
"github.com/moby/moby/v2/pkg/homedir"
)
const (
// defaultPluginSubdir is the default location for NRI plugins under libexec,
// which is in a different location for rootful/rootless Docker.
defaultPluginSubdir = "docker/nri-plugins"
// defaultPluginConfigSubdir is the default location for NRI plugin config under etc,
// which is in a different location for rootful/rootless Docker.
defaultPluginConfigSubdir = "docker/nri/conf.d"
)
type NRI struct {
cfg Config
// mu protects nri - read lock for container operations, write lock for sync and shutdown.
mu sync.RWMutex
nri *adaptation.Adaptation
}
type ContainerLister interface {
List() []*container.Container
}
type Config struct {
DaemonConfig opts.NRIOpts
ContainerLister ContainerLister
}
func NewNRI(ctx context.Context, cfg Config) (*NRI, error) {
n := &NRI{cfg: cfg}
if !n.cfg.DaemonConfig.Enable {
log.G(ctx).Info("NRI is disabled")
return n, nil
}
if err := setDefaultPaths(&n.cfg.DaemonConfig); err != nil {
return nil, err
}
log.G(ctx).WithFields(log.Fields{
"pluginPath": n.cfg.DaemonConfig.PluginPath,
"pluginConfigPath": n.cfg.DaemonConfig.PluginConfigPath,
"socketPath": n.cfg.DaemonConfig.SocketPath,
}).Info("Starting NRI")
nrilog.Set(&logShim{})
var err error
n.nri, err = adaptation.New("docker", dockerversion.Version, n.syncFn, n.updateFn, nriOptions(n.cfg.DaemonConfig)...)
if err != nil {
return nil, err
}
if err := n.nri.Start(); err != nil {
return nil, err
}
return n, nil
}
func (n *NRI) Shutdown(ctx context.Context) {
n.mu.Lock()
defer n.mu.Unlock()
if n.nri == nil {
return
}
log.G(ctx).Info("Shutting down NRI")
n.nri.Stop()
n.nri = nil
}
func (n *NRI) syncFn(ctx context.Context, syncCB adaptation.SyncCB) error {
return nil
}
func (n *NRI) updateFn(context.Context, []*adaptation.ContainerUpdate) ([]*adaptation.ContainerUpdate, error) {
return nil, errors.New("not implemented")
}
func setDefaultPaths(cfg *opts.NRIOpts) error {
if cfg.PluginPath != "" && cfg.PluginConfigPath != "" {
return nil
}
libexecDir := "/usr/libexec"
etcDir := "/etc"
if rootless.RunningWithRootlessKit() {
var err error
libexecDir, err = homedir.GetLibexecHome()
if err != nil {
return fmt.Errorf("configuring NRI: %w", err)
}
etcDir, err = homedir.GetConfigHome()
if err != nil {
return fmt.Errorf("configuring NRI: %w", err)
}
}
if cfg.PluginPath == "" {
cfg.PluginPath = filepath.Join(libexecDir, defaultPluginSubdir)
}
if cfg.PluginConfigPath == "" {
cfg.PluginConfigPath = filepath.Join(etcDir, defaultPluginConfigSubdir)
}
return nil
}
func nriOptions(cfg opts.NRIOpts) []adaptation.Option {
res := []adaptation.Option{
adaptation.WithPluginPath(cfg.PluginPath),
adaptation.WithPluginConfigPath(cfg.PluginConfigPath),
}
if cfg.SocketPath == "" {
res = append(res, adaptation.WithDisabledExternalConnections())
} else {
res = append(res, adaptation.WithSocketPath(cfg.SocketPath))
}
return res
}

3
go.mod
View File

@@ -27,6 +27,7 @@ require (
github.com/containerd/errdefs v1.0.0
github.com/containerd/fifo v1.1.0
github.com/containerd/log v0.1.0
github.com/containerd/nri v0.10.0
github.com/containerd/platforms v1.0.0-rc.2
github.com/containerd/typeurl/v2 v2.2.3
github.com/coreos/go-systemd/v22 v22.6.0
@@ -194,6 +195,7 @@ require (
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/jmoiron/sqlx v1.3.3 // indirect
github.com/klauspost/compress v1.18.2 // indirect
github.com/knqyf263/go-plugin v0.9.0 // indirect
github.com/mitchellh/hashstructure/v2 v2.0.2 // indirect
github.com/mitchellh/reflectwalk v1.0.2 // indirect
github.com/moby/sys/capability v0.4.0 // indirect
@@ -215,6 +217,7 @@ require (
github.com/shibumi/go-pathspec v1.3.0 // indirect
github.com/spdx/tools-golang v0.5.5 // indirect
github.com/stretchr/testify v1.11.1 // indirect
github.com/tetratelabs/wazero v1.9.0 // indirect
github.com/tinylib/msgp v1.3.0 // indirect
github.com/tonistiigi/dchapes-mode v0.0.0-20250318174251-73d941a28323 // indirect
github.com/tonistiigi/fsutil v0.0.0-20250605211040-586307ad452f // indirect

6
go.sum
View File

@@ -154,6 +154,8 @@ github.com/containerd/go-runc v1.1.0 h1:OX4f+/i2y5sUT7LhmcJH7GYrjjhHa1QI4e8yO0gG
github.com/containerd/go-runc v1.1.0/go.mod h1:xJv2hFF7GvHtTJd9JqTS2UVxMkULUYw4JN5XAUZqH5U=
github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I=
github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo=
github.com/containerd/nri v0.10.0 h1:bt2NzfvlY6OJE0i+fB5WVeGQEycxY7iFVQpEbh7J3Go=
github.com/containerd/nri v0.10.0/go.mod h1:5VyvLa/4uL8FjyO8nis1UjbCutXDpngil17KvBSL6BU=
github.com/containerd/nydus-snapshotter v0.15.4 h1:l59kGRVMtwMLDLh322HsWhEsBCkRKMkGWYV5vBeLYCE=
github.com/containerd/nydus-snapshotter v0.15.4/go.mod h1:eRJqnxQDr48HNop15kZdLZpFF5B6vf6Q11Aq1K0E4Ms=
github.com/containerd/platforms v1.0.0-rc.2 h1:0SPgaNZPVWGEi4grZdV8VRYQn78y+nm6acgLGv/QzE4=
@@ -377,6 +379,8 @@ github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/klauspost/compress v1.18.2 h1:iiPHWW0YrcFgpBYhsA6D1+fqHssJscY/Tm/y2Uqnapk=
github.com/klauspost/compress v1.18.2/go.mod h1:R0h/fSBs8DE4ENlcrlib3PsXS61voFxhIs2DeRhCvJ4=
github.com/knqyf263/go-plugin v0.9.0 h1:CQs2+lOPIlkZVtcb835ZYDEoyyWJWLbSTWeCs0EwTwI=
github.com/knqyf263/go-plugin v0.9.0/go.mod h1:2z5lCO1/pez6qGo8CvCxSlBFSEat4MEp1DrnA+f7w8Q=
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
@@ -598,6 +602,8 @@ github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
github.com/tedsuo/ifrit v0.0.0-20230516164442-7862c310ad26 h1:mWCRvpoEMVlslxEvvptKgIUb35va9yj9Oq5wGw/er5I=
github.com/tedsuo/ifrit v0.0.0-20230516164442-7862c310ad26/go.mod h1:0uD3VMXkZ7Bw0ojGCwDzebBBzPBXtzEZeXai+56BLX4=
github.com/tetratelabs/wazero v1.9.0 h1:IcZ56OuxrtaEz8UYNRHBrUa9bYeX9oVY93KspZZBf/I=
github.com/tetratelabs/wazero v1.9.0/go.mod h1:TSbcXCfFP0L2FGkRPxHphadXPjo1T6W+CseNNY7EkjM=
github.com/tinylib/msgp v1.3.0 h1:ULuf7GPooDaIlbyvgAxBV/FI7ynli6LZ1/nVUNu+0ww=
github.com/tinylib/msgp v1.3.0/go.mod h1:ykjzy2wzgrlvpDCRc4LA8UXy6D8bzMSuAF3WD57Gok0=
github.com/tonistiigi/dchapes-mode v0.0.0-20250318174251-73d941a28323 h1:r0p7fK56l8WPequOaR3i9LBqfPtEdXIQbUTzT55iqT4=

View File

@@ -103,3 +103,13 @@ func GetLibHome() (string, error) {
}
return filepath.Join(home, ".local/lib"), nil
}
// GetLibexecHome returns $HOME/.local/libexec
// If HOME is not set, getpwent(3) is consulted to determine the users home directory.
func GetLibexecHome() (string, error) {
home := Get()
if home == "" {
return "", errors.New("could not get HOME")
}
return filepath.Join(home, ".local/libexec"), nil
}

View File

@@ -30,3 +30,8 @@ func GetConfigHome() (string, error) {
func GetLibHome() (string, error) {
return "", errors.New("homedir.GetLibHome() is not supported on this system")
}
// GetLibexecHome is unsupported on non-linux system.
func GetLibexecHome() (string, error) {
return "", errors.New("homedir.GetLibexecHome() is not supported on this system")
}

201
vendor/github.com/containerd/nri/LICENSE generated vendored Normal file
View File

@@ -0,0 +1,201 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

View File

@@ -0,0 +1,731 @@
/*
Copyright The containerd Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package adaptation
import (
"context"
"errors"
"fmt"
"io/fs"
"net"
"os"
"path/filepath"
"sort"
"sync"
"github.com/containerd/nri/pkg/adaptation/builtin"
"github.com/containerd/nri/pkg/api"
"github.com/containerd/nri/pkg/log"
validator "github.com/containerd/nri/plugins/default-validator/builtin"
"github.com/containerd/ttrpc"
"github.com/tetratelabs/wazero"
"github.com/tetratelabs/wazero/imports/wasi_snapshot_preview1"
"google.golang.org/protobuf/proto"
)
const (
// DefaultPluginPath is the default path to search for NRI plugins.
DefaultPluginPath = "/opt/nri/plugins"
// DefaultSocketPath is the default socket path for external plugins.
DefaultSocketPath = api.DefaultSocketPath
// PluginConfigDir is the drop-in directory for NRI-launched plugin configuration.
DefaultPluginConfigPath = "/etc/nri/conf.d"
)
// SyncFn is a container runtime function for state synchronization.
type SyncFn func(context.Context, SyncCB) error
// SyncCB is an NRI function used to synchronize plugins with the runtime.
type SyncCB func(context.Context, []*PodSandbox, []*Container) ([]*ContainerUpdate, error)
// UpdateFn is a container runtime function for unsolicited container updates.
type UpdateFn func(context.Context, []*ContainerUpdate) ([]*ContainerUpdate, error)
// Adaptation is the NRI abstraction for container runtime NRI adaptation/integration.
type Adaptation struct {
sync.Mutex
name string
version string
dropinPath string
pluginPath string
socketPath string
dontListen bool
syncFn SyncFn
updateFn UpdateFn
clientOpts []ttrpc.ClientOpts
serverOpts []ttrpc.ServerOpt
listener net.Listener
plugins []*plugin
validators []*plugin
builtin []*builtin.BuiltinPlugin
syncLock sync.RWMutex
wasmService *api.PluginPlugin
}
var (
// Used instead of nil Context in logging.
noCtx = context.TODO()
)
// Option to apply to the NRI runtime.
type Option func(*Adaptation) error
// WithPluginPath returns an option to override the default NRI plugin path.
func WithPluginPath(path string) Option {
return func(r *Adaptation) error {
r.pluginPath = path
return nil
}
}
// WithPluginConfigPath returns an option to override the default NRI plugin config path.
func WithPluginConfigPath(path string) Option {
return func(r *Adaptation) error {
r.dropinPath = path
return nil
}
}
// WithSocketPath returns an option to override the default NRI socket path.
func WithSocketPath(path string) Option {
return func(r *Adaptation) error {
r.socketPath = path
return nil
}
}
// WithDisabledExternalConnections returns an options to disable accepting plugin connections.
func WithDisabledExternalConnections() Option {
return func(r *Adaptation) error {
r.dontListen = true
return nil
}
}
// WithTTRPCOptions sets extra client and server options to use for ttrpc.
func WithTTRPCOptions(clientOpts []ttrpc.ClientOpts, serverOpts []ttrpc.ServerOpt) Option {
return func(r *Adaptation) error {
r.clientOpts = append(r.clientOpts, clientOpts...)
r.serverOpts = append(r.serverOpts, serverOpts...)
return nil
}
}
// WithBuiltinPlugins sets extra builtin plugins to register.
func WithBuiltinPlugins(plugins ...*builtin.BuiltinPlugin) Option {
return func(r *Adaptation) error {
r.builtin = append(r.builtin, plugins...)
return nil
}
}
// WithDefaultValidator sets up builtin validator plugin if it is configured.
func WithDefaultValidator(cfg *validator.DefaultValidatorConfig) Option {
return func(r *Adaptation) error {
if plugin := validator.GetDefaultValidator(cfg); plugin != nil {
r.builtin = append([]*builtin.BuiltinPlugin{plugin}, r.builtin...)
}
return nil
}
}
// New creates a new NRI Runtime.
func New(name, version string, syncFn SyncFn, updateFn UpdateFn, opts ...Option) (*Adaptation, error) {
var err error
if syncFn == nil {
return nil, fmt.Errorf("failed to create NRI adaptation, nil SyncFn")
}
if updateFn == nil {
return nil, fmt.Errorf("failed to create NRI adaptation, nil UpdateFn")
}
wasmWithCloseOnContextDone := func(ctx context.Context) (wazero.Runtime, error) {
var (
cfg = wazero.NewRuntimeConfig().WithCloseOnContextDone(true)
r = wazero.NewRuntimeWithConfig(ctx, cfg)
)
if _, err := wasi_snapshot_preview1.Instantiate(ctx, r); err != nil {
return nil, err
}
return r, nil
}
wasmPlugins, err := api.NewPluginPlugin(
context.Background(),
api.WazeroRuntime(wasmWithCloseOnContextDone),
)
if err != nil {
return nil, fmt.Errorf("unable to initialize WASM service: %w", err)
}
r := &Adaptation{
name: name,
version: version,
syncFn: syncFn,
updateFn: updateFn,
pluginPath: DefaultPluginPath,
dropinPath: DefaultPluginConfigPath,
socketPath: DefaultSocketPath,
syncLock: sync.RWMutex{},
wasmService: wasmPlugins,
}
for _, o := range opts {
if err = o(r); err != nil {
return nil, fmt.Errorf("failed to apply option: %w", err)
}
}
log.Infof(noCtx, "runtime interface created")
return r, nil
}
// Start up the NRI runtime.
func (r *Adaptation) Start() error {
log.Infof(noCtx, "runtime interface starting up...")
r.Lock()
defer r.Unlock()
if err := r.startPlugins(); err != nil {
return err
}
if err := r.startListener(); err != nil {
return err
}
return nil
}
// Stop the NRI runtime.
func (r *Adaptation) Stop() {
log.Infof(noCtx, "runtime interface shutting down...")
r.Lock()
defer r.Unlock()
r.stopListener()
r.stopPlugins()
}
// RunPodSandbox relays the corresponding CRI event to plugins.
func (r *Adaptation) RunPodSandbox(ctx context.Context, evt *StateChangeEvent) error {
evt.Event = Event_RUN_POD_SANDBOX
return r.StateChange(ctx, evt)
}
// UpdatePodSandbox relays the corresponding CRI request to plugins.
func (r *Adaptation) UpdatePodSandbox(ctx context.Context, req *UpdatePodSandboxRequest) (*UpdatePodSandboxResponse, error) {
r.Lock()
defer r.Unlock()
defer r.removeClosedPlugins()
for _, plugin := range r.plugins {
_, err := plugin.updatePodSandbox(ctx, req)
if err != nil {
return nil, err
}
}
return &UpdatePodSandboxResponse{}, nil
}
// PostUpdatePodSandbox relays the corresponding CRI event to plugins.
func (r *Adaptation) PostUpdatePodSandbox(ctx context.Context, evt *StateChangeEvent) error {
evt.Event = Event_POST_UPDATE_POD_SANDBOX
return r.StateChange(ctx, evt)
}
// StopPodSandbox relays the corresponding CRI event to plugins.
func (r *Adaptation) StopPodSandbox(ctx context.Context, evt *StateChangeEvent) error {
evt.Event = Event_STOP_POD_SANDBOX
return r.StateChange(ctx, evt)
}
// RemovePodSandbox relays the corresponding CRI event to plugins.
func (r *Adaptation) RemovePodSandbox(ctx context.Context, evt *StateChangeEvent) error {
evt.Event = Event_REMOVE_POD_SANDBOX
return r.StateChange(ctx, evt)
}
// CreateContainer relays the corresponding CRI request to plugins.
func (r *Adaptation) CreateContainer(ctx context.Context, req *CreateContainerRequest) (*CreateContainerResponse, error) {
r.Lock()
defer r.Unlock()
defer r.removeClosedPlugins()
var (
result = collectCreateContainerResult(req)
validate *ValidateContainerAdjustmentRequest
)
if r.hasValidators() {
validate = &ValidateContainerAdjustmentRequest{
Pod: req.Pod,
Container: proto.Clone(req.Container).(*Container),
}
}
for _, plugin := range r.plugins {
if validate != nil {
validate.AddPlugin(plugin.base, plugin.idx)
}
rpl, err := plugin.createContainer(ctx, req)
if err != nil {
return nil, err
}
err = result.apply(rpl, plugin.name())
if err != nil {
return nil, err
}
}
return r.validateContainerAdjustment(ctx, validate, result)
}
// PostCreateContainer relays the corresponding CRI event to plugins.
func (r *Adaptation) PostCreateContainer(ctx context.Context, evt *StateChangeEvent) error {
evt.Event = Event_POST_CREATE_CONTAINER
return r.StateChange(ctx, evt)
}
// StartContainer relays the corresponding CRI event to plugins.
func (r *Adaptation) StartContainer(ctx context.Context, evt *StateChangeEvent) error {
evt.Event = Event_START_CONTAINER
return r.StateChange(ctx, evt)
}
// PostStartContainer relays the corresponding CRI event to plugins.
func (r *Adaptation) PostStartContainer(ctx context.Context, evt *StateChangeEvent) error {
evt.Event = Event_POST_START_CONTAINER
return r.StateChange(ctx, evt)
}
// UpdateContainer relays the corresponding CRI request to plugins.
func (r *Adaptation) UpdateContainer(ctx context.Context, req *UpdateContainerRequest) (*UpdateContainerResponse, error) {
r.Lock()
defer r.Unlock()
defer r.removeClosedPlugins()
result := collectUpdateContainerResult(req)
for _, plugin := range r.plugins {
rpl, err := plugin.updateContainer(ctx, req)
if err != nil {
return nil, err
}
err = result.apply(rpl, plugin.name())
if err != nil {
return nil, err
}
}
return result.updateContainerResponse(), nil
}
// PostUpdateContainer relays the corresponding CRI event to plugins.
func (r *Adaptation) PostUpdateContainer(ctx context.Context, evt *StateChangeEvent) error {
evt.Event = Event_POST_UPDATE_CONTAINER
return r.StateChange(ctx, evt)
}
// StopContainer relays the corresponding CRI request to plugins.
func (r *Adaptation) StopContainer(ctx context.Context, req *StopContainerRequest) (*StopContainerResponse, error) {
r.Lock()
defer r.Unlock()
defer r.removeClosedPlugins()
result := collectStopContainerResult()
for _, plugin := range r.plugins {
rpl, err := plugin.stopContainer(ctx, req)
if err != nil {
return nil, err
}
err = result.apply(rpl, plugin.name())
if err != nil {
return nil, err
}
}
return result.stopContainerResponse(), nil
}
// RemoveContainer relays the corresponding CRI event to plugins.
func (r *Adaptation) RemoveContainer(ctx context.Context, evt *StateChangeEvent) error {
evt.Event = Event_REMOVE_CONTAINER
return r.StateChange(ctx, evt)
}
// StateChange relays pod- or container events to plugins.
func (r *Adaptation) StateChange(ctx context.Context, evt *StateChangeEvent) error {
if evt.Event == Event_UNKNOWN {
return errors.New("invalid (unset) event in state change notification")
}
r.Lock()
defer r.Unlock()
defer r.removeClosedPlugins()
for _, plugin := range r.plugins {
err := plugin.StateChange(ctx, evt)
if err != nil {
return err
}
}
return nil
}
// Perform a set of unsolicited container updates requested by a plugin.
func (r *Adaptation) updateContainers(ctx context.Context, req []*ContainerUpdate) ([]*ContainerUpdate, error) {
r.Lock()
defer r.Unlock()
return r.updateFn(ctx, req)
}
// Validate requested container adjustments.
func (r *Adaptation) validateContainerAdjustment(ctx context.Context, req *ValidateContainerAdjustmentRequest, result *result) (*CreateContainerResponse, error) {
rpl := result.createContainerResponse()
if req == nil || len(r.validators) == 0 {
return rpl, nil
}
req.AddResponse(rpl)
req.AddOwners(result.owners)
errors := make(chan error, len(r.validators))
wg := sync.WaitGroup{}
for _, p := range r.validators {
wg.Add(1)
go func(p *plugin) {
defer wg.Done()
errors <- p.ValidateContainerAdjustment(ctx, req)
}(p)
}
wg.Wait()
close(errors)
for err := range errors {
if err != nil {
return nil, err
}
}
return rpl, nil
}
// Start up pre-installed plugins.
func (r *Adaptation) startPlugins() (retErr error) {
var plugins []*plugin
log.Infof(noCtx, "starting plugins...")
ids, names, configs, err := r.discoverPlugins()
if err != nil {
return err
}
defer func() {
if retErr != nil {
for _, p := range plugins {
p.stop()
}
}
}()
for _, b := range r.builtin {
log.Infof(noCtx, "starting builtin NRI plugin %q...", b.Index+"-"+b.Base)
p, err := r.newBuiltinPlugin(b)
if err != nil {
return fmt.Errorf("failed to initialize builtin NRI plugin %q: %v", b.Base, err)
}
if err := p.start(r.name, r.version); err != nil {
return fmt.Errorf("failed to start builtin NRI plugin %q: %v", b.Base, err)
}
plugins = append(plugins, p)
}
for i, name := range names {
log.Infof(noCtx, "starting pre-installed NRI plugin %q...", name)
id := ids[i]
p, err := r.newLaunchedPlugin(r.pluginPath, id, name, configs[i])
if err != nil {
log.Warnf(noCtx, "failed to initialize pre-installed NRI plugin %q: %v", name, err)
continue
}
if err := p.start(r.name, r.version); err != nil {
log.Warnf(noCtx, "failed to start pre-installed NRI plugin %q: %v", name, err)
continue
}
plugins = append(plugins, p)
}
// Although the error returned by syncPlugins may not be nil, r.syncFn could still ignores this error and returns a nil error.
// We need to make sure that the plugins are successfully synchronized in the `plugins`
syncPlugins := func(ctx context.Context, pods []*PodSandbox, containers []*Container) (updates []*ContainerUpdate, err error) {
startedPlugins := plugins
plugins = make([]*plugin, 0, len(plugins))
for _, plugin := range startedPlugins {
us, err := plugin.synchronize(ctx, pods, containers)
if err != nil {
plugin.stop()
log.Warnf(noCtx, "failed to synchronize pre-installed NRI plugin %q: %v", plugin.name(), err)
continue
}
plugins = append(plugins, plugin)
updates = append(updates, us...)
log.Infof(noCtx, "pre-installed NRI plugin %q synchronization success", plugin.name())
}
return updates, nil
}
if err := r.syncFn(noCtx, syncPlugins); err != nil {
return fmt.Errorf("failed to synchronize pre-installed NRI Plugins: %w", err)
}
r.plugins = plugins
r.sortPlugins()
return nil
}
// Stop plugins.
func (r *Adaptation) stopPlugins() {
log.Infof(noCtx, "stopping plugins...")
for _, p := range r.plugins {
p.stop()
}
r.plugins = nil
}
func (r *Adaptation) removeClosedPlugins() {
var active, closed, validators []*plugin
for _, p := range r.plugins {
if p.isClosed() {
closed = append(closed, p)
} else {
active = append(active, p)
if p.isContainerAdjustmentValidator() {
validators = append(validators, p)
}
}
}
if len(closed) != 0 {
go func() {
for _, plugin := range closed {
plugin.stop()
}
}()
}
r.plugins = active
r.validators = validators
}
func (r *Adaptation) startListener() error {
if r.dontListen {
log.Infof(noCtx, "connection from external plugins disabled")
return nil
}
os.Remove(r.socketPath)
if err := os.MkdirAll(filepath.Dir(r.socketPath), 0700); err != nil {
return fmt.Errorf("failed to create socket %q: %w", r.socketPath, err)
}
l, err := net.ListenUnix("unix", &net.UnixAddr{
Name: r.socketPath,
Net: "unix",
})
if err != nil {
return fmt.Errorf("failed to create socket %q: %w", r.socketPath, err)
}
r.acceptPluginConnections(l)
return nil
}
func (r *Adaptation) stopListener() {
if r.listener != nil {
r.listener.Close()
}
}
func (r *Adaptation) acceptPluginConnections(l net.Listener) error {
r.listener = l
ctx := context.Background()
go func() {
for {
conn, err := l.Accept()
if err != nil {
log.Infof(ctx, "stopped accepting plugin connections (%v)", err)
return
}
p, err := r.newExternalPlugin(conn)
if err != nil {
log.Errorf(ctx, "failed to create external plugin: %v", err)
continue
}
if err := p.start(r.name, r.version); err != nil {
log.Errorf(ctx, "failed to start external plugin: %v", err)
continue
}
r.requestPluginSync()
err = r.syncFn(ctx, p.synchronize)
if err != nil {
log.Infof(ctx, "failed to synchronize plugin: %v", err)
} else {
r.Lock()
r.plugins = append(r.plugins, p)
if p.isContainerAdjustmentValidator() {
r.validators = append(r.validators, p)
}
r.sortPlugins()
r.Unlock()
log.Infof(ctx, "plugin %q connected and synchronized", p.name())
}
r.finishedPluginSync()
}
}()
return nil
}
func (r *Adaptation) discoverPlugins() ([]string, []string, []string, error) {
var (
plugins []string
indices []string
configs []string
entries []os.DirEntry
info fs.FileInfo
err error
)
if entries, err = os.ReadDir(r.pluginPath); err != nil {
if os.IsNotExist(err) {
return nil, nil, nil, nil
}
return nil, nil, nil, fmt.Errorf("failed to discover plugins in %s: %w",
r.pluginPath, err)
}
for _, e := range entries {
if e.IsDir() {
continue
}
if info, err = e.Info(); err != nil {
continue
}
if info.Mode()&fs.FileMode(0o111) == 0 {
continue
}
name := e.Name()
idx, base, err := api.ParsePluginName(name)
if err != nil {
return nil, nil, nil, fmt.Errorf("failed to discover plugins in %s: %w",
r.pluginPath, err)
}
cfg, err := r.getPluginConfig(idx, base)
if err != nil {
return nil, nil, nil, fmt.Errorf("failed to discover plugins in %s: %w",
r.pluginPath, err)
}
log.Infof(noCtx, "discovered plugin %s", name)
indices = append(indices, idx)
plugins = append(plugins, base)
configs = append(configs, cfg)
}
return indices, plugins, configs, nil
}
func (r *Adaptation) sortPlugins() {
r.removeClosedPlugins()
sort.Slice(r.plugins, func(i, j int) bool {
return r.plugins[i].idx < r.plugins[j].idx
})
sort.Slice(r.validators, func(i, j int) bool {
return r.validators[i].idx < r.validators[j].idx
})
if len(r.plugins) > 0 {
log.Infof(noCtx, "plugin invocation order")
for i, p := range r.plugins {
log.Infof(noCtx, " #%d: %q (%s)", i+1, p.name(), p.qualifiedName())
}
}
if len(r.validators) > 0 {
log.Infof(noCtx, "validator plugins")
for _, p := range r.validators {
log.Infof(noCtx, " %q (%s)", p.name(), p.qualifiedName())
}
}
}
func (r *Adaptation) hasValidators() bool {
return len(r.validators) > 0
}
func (r *Adaptation) requestPluginSync() {
r.syncLock.Lock()
}
func (r *Adaptation) finishedPluginSync() {
r.syncLock.Unlock()
}
type PluginSyncBlock struct {
r *Adaptation
}
// BlockPluginSync blocks plugins from being synchronized/fully registered.
func (r *Adaptation) BlockPluginSync() *PluginSyncBlock {
r.syncLock.RLock()
return &PluginSyncBlock{r: r}
}
// Unblock a plugin sync. block put in place by BlockPluginSync. Safe to call
// multiple times but only from a single goroutine.
func (b *PluginSyncBlock) Unblock() {
if b != nil && b.r != nil {
b.r.syncLock.RUnlock()
b.r = nil
}
}

175
vendor/github.com/containerd/nri/pkg/adaptation/api.go generated vendored Normal file
View File

@@ -0,0 +1,175 @@
/*
Copyright The containerd Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package adaptation
import (
"github.com/containerd/nri/pkg/api"
)
//
// Alias types, consts and functions from api for the runtime.
//
// Aliased request/response/event types for api/api.proto.
// nolint
type (
RegisterPluginRequest = api.RegisterPluginRequest
RegisterPluginResponse = api.Empty
UpdateContainersRequest = api.UpdateContainersRequest
UpdateContainersResponse = api.UpdateContainersResponse
ConfigureRequest = api.ConfigureRequest
ConfigureResponse = api.ConfigureResponse
SynchronizeRequest = api.SynchronizeRequest
SynchronizeResponse = api.SynchronizeResponse
ShutdownRequest = api.Empty
ShutdownResponse = api.Empty
CreateContainerRequest = api.CreateContainerRequest
CreateContainerResponse = api.CreateContainerResponse
UpdateContainerRequest = api.UpdateContainerRequest
UpdateContainerResponse = api.UpdateContainerResponse
StopContainerRequest = api.StopContainerRequest
StopContainerResponse = api.StopContainerResponse
StateChangeEvent = api.StateChangeEvent
StateChangeResponse = api.StateChangeResponse
RunPodSandboxRequest = api.RunPodSandboxRequest
UpdatePodSandboxRequest = api.UpdatePodSandboxRequest
UpdatePodSandboxResponse = api.UpdatePodSandboxResponse
StopPodSandboxRequest = api.StopPodSandboxRequest
RemovePodSandboxRequest = api.RemovePodSandboxRequest
PostUpdatePodSandboxRequest = api.PostUpdatePodSandboxRequest
PostUpdatePodSandboxResponse = api.PostUpdatePodSandboxResponse
StartContainerRequest = api.StartContainerRequest
StartContainerResponse = api.StartContainerResponse
RemoveContainerRequest = api.RemoveContainerRequest
RemoveContainerResponse = api.RemoveContainerResponse
PostCreateContainerRequest = api.PostCreateContainerRequest
PostCreateContainerResponse = api.PostCreateContainerResponse
PostStartContainerRequest = api.PostStartContainerRequest
PostStartContainerResponse = api.PostStartContainerResponse
PostUpdateContainerRequest = api.PostUpdateContainerRequest
PostUpdateContainerResponse = api.PostUpdateContainerResponse
ValidateContainerAdjustmentRequest = api.ValidateContainerAdjustmentRequest
ValidateContainerAdjustmentResponse = api.ValidateContainerAdjustmentResponse
PluginInstance = api.PluginInstance
PodSandbox = api.PodSandbox
LinuxPodSandbox = api.LinuxPodSandbox
Container = api.Container
ContainerAdjustment = api.ContainerAdjustment
LinuxContainerAdjustment = api.LinuxContainerAdjustment
ContainerUpdate = api.ContainerUpdate
LinuxContainerUpdate = api.LinuxContainerUpdate
ContainerEviction = api.ContainerEviction
ContainerState = api.ContainerState
KeyValue = api.KeyValue
Mount = api.Mount
LinuxContainer = api.LinuxContainer
LinuxNamespace = api.LinuxNamespace
LinuxResources = api.LinuxResources
LinuxCPU = api.LinuxCPU
LinuxMemory = api.LinuxMemory
LinuxDevice = api.LinuxDevice
LinuxDeviceCgroup = api.LinuxDeviceCgroup
LinuxIOPriority = api.LinuxIOPriority
LinuxSeccomp = api.LinuxSeccomp
CDIDevice = api.CDIDevice
HugepageLimit = api.HugepageLimit
Hooks = api.Hooks
Hook = api.Hook
POSIXRlimit = api.POSIXRlimit
SecurityProfile = api.SecurityProfile
EventMask = api.EventMask
)
// Aliased consts for api/api.proto.
// nolint
const (
Event_UNKNOWN = api.Event_UNKNOWN
Event_RUN_POD_SANDBOX = api.Event_RUN_POD_SANDBOX
Event_UPDATE_POD_SANDBOX = api.Event_UPDATE_POD_SANDBOX
Event_POST_UPDATE_POD_SANDBOX = api.Event_POST_UPDATE_POD_SANDBOX
Event_STOP_POD_SANDBOX = api.Event_STOP_POD_SANDBOX
Event_REMOVE_POD_SANDBOX = api.Event_REMOVE_POD_SANDBOX
Event_CREATE_CONTAINER = api.Event_CREATE_CONTAINER
Event_POST_CREATE_CONTAINER = api.Event_POST_CREATE_CONTAINER
Event_START_CONTAINER = api.Event_START_CONTAINER
Event_POST_START_CONTAINER = api.Event_POST_START_CONTAINER
Event_UPDATE_CONTAINER = api.Event_UPDATE_CONTAINER
Event_POST_UPDATE_CONTAINER = api.Event_POST_UPDATE_CONTAINER
Event_STOP_CONTAINER = api.Event_STOP_CONTAINER
Event_REMOVE_CONTAINER = api.Event_REMOVE_CONTAINER
Event_VALIDATE_CONTAINER_ADJUSTMENT = api.Event_VALIDATE_CONTAINER_ADJUSTMENT
ValidEvents = api.ValidEvents
ContainerState_CONTAINER_UNKNOWN = api.ContainerState_CONTAINER_UNKNOWN
ContainerState_CONTAINER_CREATED = api.ContainerState_CONTAINER_CREATED
ContainerState_CONTAINER_PAUSED = api.ContainerState_CONTAINER_PAUSED
ContainerState_CONTAINER_RUNNING = api.ContainerState_CONTAINER_RUNNING
ContainerState_CONTAINER_STOPPED = api.ContainerState_CONTAINER_STOPPED
ContainerState_CONTAINER_EXITED = api.ContainerState_CONTAINER_STOPPED
SecurityProfile_RUNTIME_DEFAULT = api.SecurityProfile_RUNTIME_DEFAULT
SecurityProfile_UNCONFINED = api.SecurityProfile_UNCONFINED
SecurityProfile_LOCALHOST = api.SecurityProfile_LOCALHOST
)
// Aliased types for api/optional.go.
// nolint
type (
OptionalString = api.OptionalString
OptionalInt = api.OptionalInt
OptionalInt32 = api.OptionalInt32
OptionalUInt32 = api.OptionalUInt32
OptionalInt64 = api.OptionalInt64
OptionalUInt64 = api.OptionalUInt64
OptionalBool = api.OptionalBool
OptionalFileMode = api.OptionalFileMode
)
// Aliased functions for api/optional.go.
// nolint
var (
String = api.String
Int = api.Int
Int32 = api.Int32
UInt32 = api.UInt32
Int64 = api.Int64
UInt64 = api.UInt64
Bool = api.Bool
FileMode = api.FileMode
)
// Aliased functions for api/types.go.
// nolint
var (
FromOCIMounts = api.FromOCIMounts
FromOCIHooks = api.FromOCIHooks
FromOCILinuxNamespaces = api.FromOCILinuxNamespaces
FromOCILinuxDevices = api.FromOCILinuxDevices
FromOCILinuxResources = api.FromOCILinuxResources
FromOCILinuxIOPriority = api.FromOCILinuxIOPriority
DupStringSlice = api.DupStringSlice
DupStringMap = api.DupStringMap
IsMarkedForRemoval = api.IsMarkedForRemoval
MarkForRemoval = api.MarkForRemoval
)

View File

@@ -0,0 +1,210 @@
/*
Copyright The containerd Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package builtin
import (
"context"
"github.com/containerd/nri/pkg/api"
)
type BuiltinPlugin struct {
Base string
Index string
Handlers BuiltinHandlers
}
type BuiltinHandlers struct {
Configure func(context.Context, *api.ConfigureRequest) (*api.ConfigureResponse, error)
Synchronize func(context.Context, *api.SynchronizeRequest) (*api.SynchronizeResponse, error)
RunPodSandbox func(context.Context, *api.RunPodSandboxRequest) error
StopPodSandbox func(context.Context, *api.StopPodSandboxRequest) error
RemovePodSandbox func(context.Context, *api.RemovePodSandboxRequest) error
UpdatePodSandbox func(context.Context, *api.UpdatePodSandboxRequest) (*api.UpdatePodSandboxResponse, error)
PostUpdatePodSandbox func(context.Context, *api.PostUpdatePodSandboxRequest) error
CreateContainer func(context.Context, *api.CreateContainerRequest) (*api.CreateContainerResponse, error)
PostCreateContainer func(context.Context, *api.PostCreateContainerRequest) error
StartContainer func(context.Context, *api.StartContainerRequest) error
PostStartContainer func(context.Context, *api.PostStartContainerRequest) error
UpdateContainer func(context.Context, *api.UpdateContainerRequest) (*api.UpdateContainerResponse, error)
PostUpdateContainer func(context.Context, *api.PostUpdateContainerRequest) error
StopContainer func(context.Context, *api.StopContainerRequest) (*api.StopContainerResponse, error)
RemoveContainer func(context.Context, *api.RemoveContainerRequest) error
ValidateContainerAdjustment func(context.Context, *api.ValidateContainerAdjustmentRequest) error
}
func (b *BuiltinPlugin) Configure(ctx context.Context, req *api.ConfigureRequest) (*api.ConfigureResponse, error) {
var (
rpl = &api.ConfigureResponse{}
err error
)
if b.Handlers.Configure != nil {
rpl, err = b.Handlers.Configure(ctx, req)
}
if rpl.Events == 0 {
var events api.EventMask
if b.Handlers.RunPodSandbox != nil {
events.Set(api.Event_RUN_POD_SANDBOX)
}
if b.Handlers.StopPodSandbox != nil {
events.Set(api.Event_STOP_POD_SANDBOX)
}
if b.Handlers.RemovePodSandbox != nil {
events.Set(api.Event_REMOVE_POD_SANDBOX)
}
if b.Handlers.UpdatePodSandbox != nil {
events.Set(api.Event_UPDATE_POD_SANDBOX)
}
if b.Handlers.PostUpdatePodSandbox != nil {
events.Set(api.Event_POST_UPDATE_POD_SANDBOX)
}
if b.Handlers.CreateContainer != nil {
events.Set(api.Event_CREATE_CONTAINER)
}
if b.Handlers.PostCreateContainer != nil {
events.Set(api.Event_POST_CREATE_CONTAINER)
}
if b.Handlers.StartContainer != nil {
events.Set(api.Event_START_CONTAINER)
}
if b.Handlers.PostStartContainer != nil {
events.Set(api.Event_POST_START_CONTAINER)
}
if b.Handlers.UpdateContainer != nil {
events.Set(api.Event_UPDATE_CONTAINER)
}
if b.Handlers.PostUpdateContainer != nil {
events.Set(api.Event_POST_UPDATE_CONTAINER)
}
if b.Handlers.StopContainer != nil {
events.Set(api.Event_STOP_CONTAINER)
}
if b.Handlers.RemoveContainer != nil {
events.Set(api.Event_REMOVE_CONTAINER)
}
if b.Handlers.ValidateContainerAdjustment != nil {
events.Set(api.Event_VALIDATE_CONTAINER_ADJUSTMENT)
}
rpl.Events = int32(events)
}
return rpl, err
}
func (b *BuiltinPlugin) Synchronize(ctx context.Context, req *api.SynchronizeRequest) (*api.SynchronizeResponse, error) {
if b.Handlers.Synchronize != nil {
return b.Handlers.Synchronize(ctx, req)
}
return &api.SynchronizeResponse{}, nil
}
func (b *BuiltinPlugin) Shutdown(context.Context, *api.ShutdownRequest) (*api.ShutdownResponse, error) {
return &api.ShutdownResponse{}, nil
}
func (b *BuiltinPlugin) CreateContainer(ctx context.Context, req *api.CreateContainerRequest) (*api.CreateContainerResponse, error) {
if b.Handlers.CreateContainer != nil {
return b.Handlers.CreateContainer(ctx, req)
}
return &api.CreateContainerResponse{}, nil
}
func (b *BuiltinPlugin) UpdateContainer(ctx context.Context, req *api.UpdateContainerRequest) (*api.UpdateContainerResponse, error) {
if b.Handlers.UpdateContainer != nil {
return b.Handlers.UpdateContainer(ctx, req)
}
return &api.UpdateContainerResponse{}, nil
}
func (b *BuiltinPlugin) StopContainer(ctx context.Context, req *api.StopContainerRequest) (*api.StopContainerResponse, error) {
if b.Handlers.StopContainer != nil {
return b.Handlers.StopContainer(ctx, req)
}
return &api.StopContainerResponse{}, nil
}
func (b *BuiltinPlugin) StateChange(ctx context.Context, evt *api.StateChangeEvent) (*api.StateChangeResponse, error) {
var err error
switch evt.Event {
case api.Event_RUN_POD_SANDBOX:
if b.Handlers.RunPodSandbox != nil {
err = b.Handlers.RunPodSandbox(ctx, evt)
}
case api.Event_STOP_POD_SANDBOX:
if b.Handlers.StopPodSandbox != nil {
err = b.Handlers.StopPodSandbox(ctx, evt)
}
case api.Event_REMOVE_POD_SANDBOX:
if b.Handlers.RemovePodSandbox != nil {
err = b.Handlers.RemovePodSandbox(ctx, evt)
}
case api.Event_POST_CREATE_CONTAINER:
if b.Handlers.PostCreateContainer != nil {
err = b.Handlers.PostCreateContainer(ctx, evt)
}
case api.Event_START_CONTAINER:
if b.Handlers.StartContainer != nil {
err = b.Handlers.StartContainer(ctx, evt)
}
case api.Event_POST_START_CONTAINER:
if b.Handlers.PostStartContainer != nil {
err = b.Handlers.PostStartContainer(ctx, evt)
}
case api.Event_POST_UPDATE_CONTAINER:
if b.Handlers.PostUpdateContainer != nil {
err = b.Handlers.PostUpdateContainer(ctx, evt)
}
case api.Event_REMOVE_CONTAINER:
if b.Handlers.RemoveContainer != nil {
err = b.Handlers.RemoveContainer(ctx, evt)
}
}
return &api.StateChangeResponse{}, err
}
func (b *BuiltinPlugin) UpdatePodSandbox(ctx context.Context, req *api.UpdatePodSandboxRequest) (*api.UpdatePodSandboxResponse, error) {
if b.Handlers.UpdatePodSandbox != nil {
return b.Handlers.UpdatePodSandbox(ctx, req)
}
return &api.UpdatePodSandboxResponse{}, nil
}
func (b *BuiltinPlugin) PostUpdatePodSandbox(ctx context.Context, req *api.PostUpdatePodSandboxRequest) error {
if b.Handlers.PostUpdatePodSandbox != nil {
return b.Handlers.PostUpdatePodSandbox(ctx, req)
}
return nil
}
func (b *BuiltinPlugin) ValidateContainerAdjustment(ctx context.Context, req *api.ValidateContainerAdjustmentRequest) (*api.ValidateContainerAdjustmentResponse, error) {
if b.Handlers.ValidateContainerAdjustment != nil {
if err := b.Handlers.ValidateContainerAdjustment(ctx, req); err != nil {
return &api.ValidateContainerAdjustmentResponse{
Reject: true,
Reason: err.Error(),
}, nil
}
}
return &api.ValidateContainerAdjustmentResponse{}, nil
}

View File

@@ -0,0 +1,758 @@
/*
Copyright The containerd Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package adaptation
import (
"context"
"errors"
"fmt"
stdnet "net"
"os"
"os/exec"
"path/filepath"
"strconv"
"sync"
"time"
"github.com/containerd/nri/pkg/adaptation/builtin"
"github.com/containerd/nri/pkg/api"
"github.com/containerd/nri/pkg/log"
"github.com/containerd/nri/pkg/net"
"github.com/containerd/nri/pkg/net/multiplex"
"github.com/containerd/ttrpc"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
)
const (
// DefaultPluginRegistrationTimeout is the default timeout for plugin registration.
DefaultPluginRegistrationTimeout = api.DefaultPluginRegistrationTimeout
// DefaultPluginRequestTimeout is the default timeout for plugins to handle a request.
DefaultPluginRequestTimeout = api.DefaultPluginRequestTimeout
)
var (
pluginRegistrationTimeout = DefaultPluginRegistrationTimeout
pluginRequestTimeout = DefaultPluginRequestTimeout
timeoutCfgLock sync.RWMutex
)
type plugin struct {
sync.Mutex
idx string
base string
cfg string
pid int
cmd *exec.Cmd
mux multiplex.Mux
rpcc *ttrpc.Client
rpcl stdnet.Listener
rpcs *ttrpc.Server
events EventMask
closed bool
regC chan error
closeC chan struct{}
r *Adaptation
impl *pluginType
}
// SetPluginRegistrationTimeout sets the timeout for plugin registration.
func SetPluginRegistrationTimeout(t time.Duration) {
timeoutCfgLock.Lock()
defer timeoutCfgLock.Unlock()
pluginRegistrationTimeout = t
}
func getPluginRegistrationTimeout() time.Duration {
timeoutCfgLock.RLock()
defer timeoutCfgLock.RUnlock()
return pluginRegistrationTimeout
}
// SetPluginRequestTimeout sets the timeout for plugins to handle a request.
func SetPluginRequestTimeout(t time.Duration) {
timeoutCfgLock.Lock()
defer timeoutCfgLock.Unlock()
pluginRequestTimeout = t
}
func getPluginRequestTimeout() time.Duration {
timeoutCfgLock.RLock()
defer timeoutCfgLock.RUnlock()
return pluginRequestTimeout
}
// newLaunchedPlugin launches a pre-installed plugin with a pre-connected socketpair.
// If the plugin is a wasm binary, then it will use the internal wasm service
// to setup the plugin.
func (r *Adaptation) newLaunchedPlugin(dir, idx, base, cfg string) (p *plugin, retErr error) {
name := idx + "-" + base
fullPath := filepath.Join(dir, name)
if isWasm(fullPath) {
log.Infof(noCtx, "Found WASM plugin: %s", fullPath)
wasm, err := r.wasmService.Load(context.Background(), fullPath, wasmHostFunctions{})
if err != nil {
return nil, fmt.Errorf("load WASM plugin %s: %w", fullPath, err)
}
return &plugin{
cfg: cfg,
idx: idx,
base: base,
r: r,
impl: &pluginType{wasmImpl: wasm},
}, nil
}
sockets, err := net.NewSocketPair()
if err != nil {
return nil, fmt.Errorf("failed to create plugin connection for plugin %q: %w", name, err)
}
defer sockets.Close()
conn, err := sockets.LocalConn()
if err != nil {
return nil, fmt.Errorf("failed to set up local connection for plugin %q: %w", name, err)
}
peerFile := sockets.PeerFile()
defer func() {
peerFile.Close()
if retErr != nil {
conn.Close()
}
}()
cmd := exec.Command(fullPath)
cmd.ExtraFiles = []*os.File{peerFile}
cmd.Env = []string{
api.PluginNameEnvVar + "=" + base,
api.PluginIdxEnvVar + "=" + idx,
api.PluginSocketEnvVar + "=3",
}
p = &plugin{
cfg: cfg,
cmd: cmd,
idx: idx,
base: base,
regC: make(chan error, 1),
closeC: make(chan struct{}),
r: r,
}
if err = p.cmd.Start(); err != nil {
return nil, fmt.Errorf("failed to launch plugin %q: %w", p.name(), err)
}
if err = p.connect(conn); err != nil {
return nil, err
}
return p, nil
}
func (r *Adaptation) newBuiltinPlugin(b *builtin.BuiltinPlugin) (*plugin, error) {
if b.Base == "" || b.Index == "" {
return nil, fmt.Errorf("builtin plugin without index or name (%q, %q)", b.Index, b.Base)
}
return &plugin{
idx: b.Index,
base: b.Base,
closeC: make(chan struct{}),
r: r,
impl: &pluginType{builtinImpl: b},
}, nil
}
func isWasm(path string) bool {
file, err := os.Open(path)
if err != nil {
log.Errorf(noCtx, "Unable to open file %s: %v", path, err)
return false
}
defer file.Close()
const headerLen = 8
buf := make([]byte, headerLen)
if _, err := file.Read(buf); err != nil {
log.Errorf(noCtx, "Unable to read file %s: %v", path, err)
return false
}
// WASM has starts with `\0asm`, followed by the version.
// http://webassembly.github.io/spec/core/binary/modules.html#binary-magic
return len(buf) >= headerLen &&
buf[0] == 0x00 && buf[1] == 0x61 &&
buf[2] == 0x73 && buf[3] == 0x6D &&
buf[4] == 0x01 && buf[5] == 0x00 &&
buf[6] == 0x00 && buf[7] == 0x00
}
// Create a plugin (stub) for an accepted external plugin connection.
func (r *Adaptation) newExternalPlugin(conn stdnet.Conn) (p *plugin, retErr error) {
p = &plugin{
regC: make(chan error, 1),
closeC: make(chan struct{}),
r: r,
}
if err := p.connect(conn); err != nil {
return nil, err
}
return p, nil
}
// Get plugin-specific configuration for an NRI-launched plugin.
func (r *Adaptation) getPluginConfig(id, base string) (string, error) {
name := id + "-" + base
dropIns := []string{
filepath.Join(r.dropinPath, name+".conf"),
filepath.Join(r.dropinPath, base+".conf"),
}
for _, path := range dropIns {
buf, err := os.ReadFile(path)
if err == nil {
return string(buf), nil
}
if !os.IsNotExist(err) {
return "", fmt.Errorf("failed to read configuration for plugin %q: %w", name, err)
}
}
return "", nil
}
// Check if the plugin is external (was not launched by us).
func (p *plugin) isExternal() bool {
return p.cmd == nil
}
// Check if the plugin is a container adjustment validator.
func (p *plugin) isContainerAdjustmentValidator() bool {
return p.events.IsSet(Event_VALIDATE_CONTAINER_ADJUSTMENT)
}
// 'connect' a plugin, setting up multiplexing on its socket.
func (p *plugin) connect(conn stdnet.Conn) (retErr error) {
mux := multiplex.Multiplex(conn, multiplex.WithBlockedRead())
defer func() {
if retErr != nil {
mux.Close()
}
}()
pconn, err := mux.Open(multiplex.PluginServiceConn)
if err != nil {
return fmt.Errorf("failed to mux plugin connection for plugin %q: %w", p.name(), err)
}
clientOpts := []ttrpc.ClientOpts{
ttrpc.WithOnClose(
func() {
log.Infof(noCtx, "connection to plugin %q closed", p.name())
close(p.closeC)
p.close()
}),
}
rpcc := ttrpc.NewClient(pconn, append(clientOpts, p.r.clientOpts...)...)
defer func() {
if retErr != nil {
rpcc.Close()
}
}()
rpcs, err := ttrpc.NewServer(p.r.serverOpts...)
if err != nil {
return fmt.Errorf("failed to create ttrpc server for plugin %q: %w", p.name(), err)
}
defer func() {
if retErr != nil {
rpcs.Close()
}
}()
rpcl, err := mux.Listen(multiplex.RuntimeServiceConn)
if err != nil {
return fmt.Errorf("failed to create mux runtime listener for plugin %q: %w", p.name(), err)
}
p.mux = mux
p.rpcc = rpcc
p.rpcl = rpcl
p.rpcs = rpcs
p.impl = &pluginType{ttrpcImpl: api.NewPluginClient(rpcc)}
p.pid, err = getPeerPid(p.mux.Trunk())
if err != nil {
log.Warnf(noCtx, "failed to determine plugin pid: %v", err)
}
api.RegisterRuntimeService(p.rpcs, p)
return nil
}
// Start Runtime service, wait for plugin to register, then configure it.
func (p *plugin) start(name, version string) (err error) {
// skip start for WASM and builtin plugins and head right to the registration for
// events config
if p.impl.isTtrpc() {
var (
err error
timeout = getPluginRegistrationTimeout()
)
go func() {
err := p.rpcs.Serve(context.Background(), p.rpcl)
if err != ttrpc.ErrServerClosed {
log.Infof(noCtx, "ttrpc server for plugin %q closed (%v)", p.name(), err)
}
p.close()
}()
p.mux.Unblock()
select {
case err = <-p.regC:
if err != nil {
return fmt.Errorf("failed to register plugin: %w", err)
}
case <-p.closeC:
return fmt.Errorf("failed to register plugin, connection closed")
case <-time.After(timeout):
p.close()
p.stop()
return errors.New("plugin registration timed out")
}
}
err = p.configure(context.Background(), name, version, p.cfg)
if err != nil {
p.close()
p.stop()
return err
}
return nil
}
// close a plugin shutting down its multiplexed ttrpc connections.
func (p *plugin) close() {
if p.impl.isWasm() || p.impl.isBuiltin() {
p.closed = true
return
}
p.Lock()
defer p.Unlock()
if p.closed {
return
}
p.closed = true
p.mux.Close()
p.rpcc.Close()
p.rpcs.Close()
p.rpcl.Close()
}
func (p *plugin) isClosed() bool {
p.Lock()
defer p.Unlock()
return p.closed
}
// stop a plugin (if it was launched by us)
func (p *plugin) stop() error {
if p.isExternal() || p.cmd.Process == nil || p.impl.isWasm() || p.impl.isBuiltin() {
return nil
}
// TODO(klihub):
// We should attempt a graceful shutdown of the process here...
// - send it SIGINT
// - give the it some slack waiting with a timeout
// - butcher it with SIGKILL after the timeout
p.cmd.Process.Kill()
p.cmd.Process.Wait()
p.cmd.Process.Release()
return nil
}
// Name returns a string indentication for the plugin.
func (p *plugin) name() string {
return p.idx + "-" + p.base
}
func (p *plugin) qualifiedName() string {
var kind, idx, base, pid string
if p.impl.isBuiltin() {
kind = "builtin"
} else {
if p.isExternal() {
kind = "external"
} else {
kind = "pre-connected"
}
if p.impl.isWasm() {
kind += "-wasm"
} else {
pid = "[" + strconv.Itoa(p.pid) + "]"
}
}
if idx = p.idx; idx == "" {
idx = "??"
}
if base = p.base; base == "" {
base = "plugin"
}
return kind + ":" + idx + "-" + base + pid
}
// RegisterPlugin handles the plugin's registration request.
func (p *plugin) RegisterPlugin(ctx context.Context, req *RegisterPluginRequest) (*RegisterPluginResponse, error) {
if p.isExternal() {
if req.PluginName == "" {
p.regC <- fmt.Errorf("plugin %q registered with an empty name", p.qualifiedName())
return &RegisterPluginResponse{}, errors.New("invalid (empty) plugin name")
}
if err := api.CheckPluginIndex(req.PluginIdx); err != nil {
p.regC <- fmt.Errorf("plugin %q registered with an invalid index: %w", req.PluginName, err)
return &RegisterPluginResponse{}, fmt.Errorf("invalid plugin index: %w", err)
}
p.base = req.PluginName
p.idx = req.PluginIdx
}
log.Infof(ctx, "plugin %q registered as %q", p.qualifiedName(), p.name())
p.regC <- nil
return &RegisterPluginResponse{}, nil
}
// UpdateContainers relays container update request to the runtime.
func (p *plugin) UpdateContainers(ctx context.Context, req *UpdateContainersRequest) (*UpdateContainersResponse, error) {
log.Infof(ctx, "plugin %q requested container updates", p.name())
failed, err := p.r.updateContainers(ctx, req.Update)
return &UpdateContainersResponse{
Failed: failed,
}, err
}
// configure the plugin and subscribe it for the events it requested.
func (p *plugin) configure(ctx context.Context, name, version, config string) (err error) {
ctx, cancel := context.WithTimeout(ctx, getPluginRequestTimeout())
defer cancel()
req := &ConfigureRequest{
Config: config,
RuntimeName: name,
RuntimeVersion: version,
RegistrationTimeout: getPluginRegistrationTimeout().Milliseconds(),
RequestTimeout: getPluginRequestTimeout().Milliseconds(),
}
rpl, err := p.impl.Configure(ctx, req)
if err != nil {
return fmt.Errorf("failed to configure plugin: %w", err)
}
events := EventMask(rpl.Events)
if events != 0 {
if extra := events &^ ValidEvents; extra != 0 {
return fmt.Errorf("invalid plugin events: 0x%x", extra)
}
} else {
events = ValidEvents
}
p.events = events
return nil
}
// synchronize the plugin with the current state of the runtime.
func (p *plugin) synchronize(ctx context.Context, pods []*PodSandbox, containers []*Container) ([]*ContainerUpdate, error) {
log.Infof(ctx, "synchronizing plugin %s", p.name())
ctx, cancel := context.WithTimeout(ctx, getPluginRequestTimeout())
defer cancel()
var (
podsToSend = pods
ctrsToSend = containers
podsPerMsg = len(pods)
ctrsPerMsg = len(containers)
rpl *SynchronizeResponse
err error
)
for {
req := &SynchronizeRequest{
Pods: podsToSend[:podsPerMsg],
Containers: ctrsToSend[:ctrsPerMsg],
More: len(podsToSend) > podsPerMsg || len(ctrsToSend) > ctrsPerMsg,
}
log.Debugf(ctx, "sending sync message, %d/%d, %d/%d (more: %v)",
len(req.Pods), len(podsToSend), len(req.Containers), len(ctrsToSend), req.More)
rpl, err = p.impl.Synchronize(ctx, req)
if err == nil {
if !req.More {
break
}
if len(rpl.Update) > 0 || rpl.More != req.More {
p.close()
return nil, fmt.Errorf("plugin does not handle split sync requests")
}
podsToSend = podsToSend[podsPerMsg:]
ctrsToSend = ctrsToSend[ctrsPerMsg:]
if podsPerMsg > len(podsToSend) {
podsPerMsg = len(podsToSend)
}
if ctrsPerMsg > len(ctrsToSend) {
ctrsPerMsg = len(ctrsToSend)
}
} else {
podsPerMsg, ctrsPerMsg, err = recalcObjsPerSyncMsg(podsPerMsg, ctrsPerMsg, err)
if err != nil {
p.close()
return nil, err
}
log.Debugf(ctx, "oversized message, retrying in smaller chunks")
}
}
return rpl.Update, nil
}
func recalcObjsPerSyncMsg(pods, ctrs int, err error) (int, int, error) {
const (
minObjsPerMsg = 8
)
if status.Code(err) != codes.ResourceExhausted {
return pods, ctrs, err
}
if pods+ctrs <= minObjsPerMsg {
return pods, ctrs, fmt.Errorf("failed to synchronize plugin with split messages")
}
var e *ttrpc.OversizedMessageErr
if !errors.As(err, &e) {
return pods, ctrs, fmt.Errorf("failed to synchronize plugin with split messages")
}
maxLen := e.MaximumLength()
msgLen := e.RejectedLength()
if msgLen == 0 || maxLen == 0 || msgLen <= maxLen {
return pods, ctrs, fmt.Errorf("failed to synchronize plugin with split messages")
}
factor := float64(maxLen) / float64(msgLen)
if factor > 0.9 {
factor = 0.9
}
pods = int(float64(pods) * factor)
ctrs = int(float64(ctrs) * factor)
if pods+ctrs < minObjsPerMsg {
pods = minObjsPerMsg / 2
ctrs = minObjsPerMsg / 2
}
return pods, ctrs, nil
}
// Relay CreateContainer request to plugin.
func (p *plugin) createContainer(ctx context.Context, req *CreateContainerRequest) (*CreateContainerResponse, error) {
if !p.events.IsSet(Event_CREATE_CONTAINER) {
return nil, nil
}
ctx, cancel := context.WithTimeout(ctx, getPluginRequestTimeout())
defer cancel()
rpl, err := p.impl.CreateContainer(ctx, req)
if err != nil {
if isFatalError(err) {
log.Errorf(ctx, "closing plugin %s, failed to handle CreateContainer request: %v",
p.name(), err)
p.close()
return nil, nil
}
return nil, err
}
return rpl, nil
}
// Relay UpdateContainer request to plugin.
func (p *plugin) updateContainer(ctx context.Context, req *UpdateContainerRequest) (*UpdateContainerResponse, error) {
if !p.events.IsSet(Event_UPDATE_CONTAINER) {
return nil, nil
}
ctx, cancel := context.WithTimeout(ctx, getPluginRequestTimeout())
defer cancel()
rpl, err := p.impl.UpdateContainer(ctx, req)
if err != nil {
if isFatalError(err) {
log.Errorf(ctx, "closing plugin %s, failed to handle UpdateContainer request: %v",
p.name(), err)
p.close()
return nil, nil
}
return nil, err
}
return rpl, nil
}
// Relay StopContainer request to the plugin.
func (p *plugin) stopContainer(ctx context.Context, req *StopContainerRequest) (rpl *StopContainerResponse, err error) {
if !p.events.IsSet(Event_STOP_CONTAINER) {
return nil, nil
}
ctx, cancel := context.WithTimeout(ctx, getPluginRequestTimeout())
defer cancel()
rpl, err = p.impl.StopContainer(ctx, req)
if err != nil {
if isFatalError(err) {
log.Errorf(ctx, "closing plugin %s, failed to handle StopContainer request: %v",
p.name(), err)
p.close()
return nil, nil
}
return nil, err
}
return rpl, nil
}
func (p *plugin) updatePodSandbox(ctx context.Context, req *UpdatePodSandboxRequest) (*UpdatePodSandboxResponse, error) {
if !p.events.IsSet(Event_UPDATE_POD_SANDBOX) {
return nil, nil
}
ctx, cancel := context.WithTimeout(ctx, getPluginRequestTimeout())
defer cancel()
if _, err := p.impl.UpdatePodSandbox(ctx, req); err != nil {
if isFatalError(err) {
log.Errorf(ctx, "closing plugin %s, failed to handle event %d: %v",
p.name(), Event_UPDATE_POD_SANDBOX, err)
p.close()
return nil, nil
}
return nil, err
}
return &UpdatePodSandboxResponse{}, nil
}
// Relay other pod or container state change events to the plugin.
func (p *plugin) StateChange(ctx context.Context, evt *StateChangeEvent) (err error) {
if !p.events.IsSet(evt.Event) {
return nil
}
ctx, cancel := context.WithTimeout(ctx, getPluginRequestTimeout())
defer cancel()
if err = p.impl.StateChange(ctx, evt); err != nil {
if isFatalError(err) {
log.Errorf(ctx, "closing plugin %s, failed to handle event %d: %v",
p.name(), evt.Event, err)
p.close()
return nil
}
return err
}
return nil
}
func (p *plugin) ValidateContainerAdjustment(ctx context.Context, req *ValidateContainerAdjustmentRequest) error {
if !p.events.IsSet(Event_VALIDATE_CONTAINER_ADJUSTMENT) {
return nil
}
ctx, cancel := context.WithTimeout(ctx, getPluginRequestTimeout())
defer cancel()
rpl, err := p.impl.ValidateContainerAdjustment(ctx, req)
if err != nil {
if isFatalError(err) {
log.Errorf(ctx, "closing plugin %s, failed to validate request: %v", p.name(), err)
p.close()
}
return fmt.Errorf("validator plugin %s failed: %v", p.name(), err)
}
return rpl.ValidationResult(p.name())
}
// isFatalError returns true if the error is fatal and the plugin connection should be closed.
func isFatalError(err error) bool {
switch {
case errors.Is(err, ttrpc.ErrClosed):
return true
case errors.Is(err, ttrpc.ErrServerClosed):
return true
case errors.Is(err, ttrpc.ErrProtocol):
return true
case errors.Is(err, context.DeadlineExceeded):
return true
}
return false
}
// wasmHostFunctions implements the webassembly host functions
type wasmHostFunctions struct{}
func (wasmHostFunctions) Log(ctx context.Context, request *api.LogRequest) (*api.Empty, error) {
switch request.GetLevel() {
case api.LogRequest_LEVEL_INFO:
log.Infof(ctx, request.GetMsg())
case api.LogRequest_LEVEL_WARN:
log.Warnf(ctx, request.GetMsg())
case api.LogRequest_LEVEL_ERROR:
log.Errorf(ctx, request.GetMsg())
default:
log.Debugf(ctx, request.GetMsg())
}
return &api.Empty{}, nil
}

View File

@@ -0,0 +1,54 @@
//go:build linux
/*
Copyright The containerd Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package adaptation
import (
"errors"
"fmt"
stdnet "net"
"golang.org/x/sys/unix"
)
// getPeerPid returns the process id at the other end of the connection.
func getPeerPid(conn stdnet.Conn) (int, error) {
var cred *unix.Ucred
uc, ok := conn.(*stdnet.UnixConn)
if !ok {
return 0, errors.New("invalid connection, not *net.UnixConn")
}
raw, err := uc.SyscallConn()
if err != nil {
return 0, fmt.Errorf("failed to get raw unix domain connection: %w", err)
}
ctrlErr := raw.Control(func(fd uintptr) {
cred, err = unix.GetsockoptUcred(int(fd), unix.SOL_SOCKET, unix.SO_PEERCRED)
})
if err != nil {
return 0, fmt.Errorf("failed to get process credentials: %w", err)
}
if ctrlErr != nil {
return 0, fmt.Errorf("uc.SyscallConn().Control() failed: %w", ctrlErr)
}
return int(cred.Pid), nil
}

View File

@@ -0,0 +1,30 @@
//go:build !linux
/*
Copyright The containerd Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package adaptation
import (
"fmt"
"net"
"runtime"
)
// getPeerPid returns the process id at the other end of the connection.
func getPeerPid(conn net.Conn) (int, error) {
return 0, fmt.Errorf("getPeerPid() unimplemented on %s", runtime.GOOS)
}

View File

@@ -0,0 +1,151 @@
/*
Copyright The containerd Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package adaptation
import (
"context"
"errors"
"github.com/containerd/nri/pkg/api"
)
type pluginType struct {
wasmImpl api.Plugin
ttrpcImpl api.PluginService
builtinImpl api.PluginService
}
var (
errUnknownImpl = errors.New("unknown plugin implementation type")
)
func (p *pluginType) isWasm() bool {
return p.wasmImpl != nil
}
func (p *pluginType) isTtrpc() bool {
return p.ttrpcImpl != nil
}
func (p *pluginType) isBuiltin() bool {
return p.builtinImpl != nil
}
func (p *pluginType) Synchronize(ctx context.Context, req *SynchronizeRequest) (*SynchronizeResponse, error) {
switch {
case p.ttrpcImpl != nil:
return p.ttrpcImpl.Synchronize(ctx, req)
case p.builtinImpl != nil:
return p.builtinImpl.Synchronize(ctx, req)
case p.wasmImpl != nil:
return p.wasmImpl.Synchronize(ctx, req)
}
return nil, errUnknownImpl
}
func (p *pluginType) Configure(ctx context.Context, req *ConfigureRequest) (*ConfigureResponse, error) {
switch {
case p.ttrpcImpl != nil:
return p.ttrpcImpl.Configure(ctx, req)
case p.builtinImpl != nil:
return p.builtinImpl.Configure(ctx, req)
case p.wasmImpl != nil:
return p.wasmImpl.Configure(ctx, req)
}
return nil, errUnknownImpl
}
func (p *pluginType) CreateContainer(ctx context.Context, req *CreateContainerRequest) (*CreateContainerResponse, error) {
switch {
case p.ttrpcImpl != nil:
return p.ttrpcImpl.CreateContainer(ctx, req)
case p.builtinImpl != nil:
return p.builtinImpl.CreateContainer(ctx, req)
case p.wasmImpl != nil:
return p.wasmImpl.CreateContainer(ctx, req)
}
return nil, errUnknownImpl
}
func (p *pluginType) UpdateContainer(ctx context.Context, req *UpdateContainerRequest) (*UpdateContainerResponse, error) {
switch {
case p.ttrpcImpl != nil:
return p.ttrpcImpl.UpdateContainer(ctx, req)
case p.builtinImpl != nil:
return p.builtinImpl.UpdateContainer(ctx, req)
case p.wasmImpl != nil:
return p.wasmImpl.UpdateContainer(ctx, req)
}
return nil, errUnknownImpl
}
func (p *pluginType) StopContainer(ctx context.Context, req *StopContainerRequest) (*StopContainerResponse, error) {
switch {
case p.ttrpcImpl != nil:
return p.ttrpcImpl.StopContainer(ctx, req)
case p.builtinImpl != nil:
return p.builtinImpl.StopContainer(ctx, req)
case p.wasmImpl != nil:
return p.wasmImpl.StopContainer(ctx, req)
}
return nil, errUnknownImpl
}
func (p *pluginType) UpdatePodSandbox(ctx context.Context, req *UpdatePodSandboxRequest) (*UpdatePodSandboxResponse, error) {
switch {
case p.ttrpcImpl != nil:
return p.ttrpcImpl.UpdatePodSandbox(ctx, req)
case p.builtinImpl != nil:
return p.builtinImpl.UpdatePodSandbox(ctx, req)
case p.wasmImpl != nil:
return p.wasmImpl.UpdatePodSandbox(ctx, req)
}
return nil, errUnknownImpl
}
func (p *pluginType) StateChange(ctx context.Context, req *StateChangeEvent) (err error) {
switch {
case p.ttrpcImpl != nil:
_, err = p.ttrpcImpl.StateChange(ctx, req)
case p.builtinImpl != nil:
_, err = p.builtinImpl.StateChange(ctx, req)
case p.wasmImpl != nil:
_, err = p.wasmImpl.StateChange(ctx, req)
default:
err = errUnknownImpl
}
return err
}
func (p *pluginType) ValidateContainerAdjustment(ctx context.Context, req *ValidateContainerAdjustmentRequest) (*ValidateContainerAdjustmentResponse, error) {
switch {
case p.ttrpcImpl != nil:
return p.ttrpcImpl.ValidateContainerAdjustment(ctx, req)
case p.builtinImpl != nil:
return p.builtinImpl.ValidateContainerAdjustment(ctx, req)
case p.wasmImpl != nil:
return p.wasmImpl.ValidateContainerAdjustment(ctx, req)
}
return nil, errUnknownImpl
}

1069
vendor/github.com/containerd/nri/pkg/adaptation/result.go generated vendored Normal file

File diff suppressed because it is too large Load Diff

380
vendor/github.com/containerd/nri/pkg/api/adjustment.go generated vendored Normal file
View File

@@ -0,0 +1,380 @@
/*
Copyright The containerd Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package api
import "slices"
//
// Notes:
// Adjustment of metadata that is stored in maps (labels and annotations)
// currently assumes that a single plugin will never do an add prior to a
// delete for any key. IOW, it is always assumed that if both a deletion
// and an addition/setting was recorded for a key then the final desired
// state is the addition. This seems like a reasonably safe assumption. A
// removal is usually done only to protect against triggering the conflict
// in the runtime when a plugin intends to touch a key which is known to
// have been put there or already modified by another plugin.
//
// An alternative without this implicit ordering assumption would be to
// store the adjustment for such data as a sequence of add/del operations
// in a slice. At the moment that does not seem to be necessary.
//
// AddAnnotation records the addition of the annotation key=value.
func (a *ContainerAdjustment) AddAnnotation(key, value string) {
a.initAnnotations()
a.Annotations[key] = value
}
// RemoveAnnotation records the removal of the annotation for the given key.
// Normally it is an error for a plugin to try and alter an annotation
// touched by another plugin. However, this is not an error if the plugin
// removes that annotation prior to touching it.
func (a *ContainerAdjustment) RemoveAnnotation(key string) {
a.initAnnotations()
a.Annotations[MarkForRemoval(key)] = ""
}
// AddMount records the addition of a mount to a container.
func (a *ContainerAdjustment) AddMount(m *Mount) {
a.Mounts = append(a.Mounts, m) // TODO: should we dup m here ?
}
// RemoveMount records the removal of a mount from a container.
// Normally it is an error for a plugin to try and alter a mount
// touched by another plugin. However, this is not an error if the
// plugin removes that mount prior to touching it.
func (a *ContainerAdjustment) RemoveMount(ContainerPath string) {
a.Mounts = append(a.Mounts, &Mount{
Destination: MarkForRemoval(ContainerPath),
})
}
// AddEnv records the addition of an environment variable to a container.
func (a *ContainerAdjustment) AddEnv(key, value string) {
a.Env = append(a.Env, &KeyValue{
Key: key,
Value: value,
})
}
// RemoveEnv records the removal of an environment variable from a container.
// Normally it is an error for a plugin to try and alter an environment
// variable touched by another container. However, this is not an error if
// the plugin removes that variable prior to touching it.
func (a *ContainerAdjustment) RemoveEnv(key string) {
a.Env = append(a.Env, &KeyValue{
Key: MarkForRemoval(key),
})
}
// SetArgs overrides the container command with the given arguments.
func (a *ContainerAdjustment) SetArgs(args []string) {
a.Args = slices.Clone(args)
}
// UpdateArgs overrides the container command with the given arguments.
// It won't fail if another plugin has already set the command line.
func (a *ContainerAdjustment) UpdateArgs(args []string) {
a.Args = append([]string{""}, args...)
}
// AddHooks records the addition of the given hooks to a container.
func (a *ContainerAdjustment) AddHooks(h *Hooks) {
a.initHooks()
if h.Prestart != nil {
a.Hooks.Prestart = append(a.Hooks.Prestart, h.Prestart...)
}
if h.CreateRuntime != nil {
a.Hooks.CreateRuntime = append(a.Hooks.CreateRuntime, h.CreateRuntime...)
}
if h.CreateContainer != nil {
a.Hooks.CreateContainer = append(a.Hooks.CreateContainer, h.CreateContainer...)
}
if h.StartContainer != nil {
a.Hooks.StartContainer = append(a.Hooks.StartContainer, h.StartContainer...)
}
if h.Poststart != nil {
a.Hooks.Poststart = append(a.Hooks.Poststart, h.Poststart...)
}
if h.Poststop != nil {
a.Hooks.Poststop = append(a.Hooks.Poststop, h.Poststop...)
}
}
func (a *ContainerAdjustment) AddRlimit(typ string, hard, soft uint64) {
a.initRlimits()
a.Rlimits = append(a.Rlimits, &POSIXRlimit{
Type: typ,
Hard: hard,
Soft: soft,
})
}
// AddDevice records the addition of the given device to a container.
func (a *ContainerAdjustment) AddDevice(d *LinuxDevice) {
a.initLinux()
a.Linux.Devices = append(a.Linux.Devices, d) // TODO: should we dup d here ?
}
// RemoveDevice records the removal of a device from a container.
// Normally it is an error for a plugin to try and alter an device
// touched by another container. However, this is not an error if
// the plugin removes that device prior to touching it.
func (a *ContainerAdjustment) RemoveDevice(path string) {
a.initLinux()
a.Linux.Devices = append(a.Linux.Devices, &LinuxDevice{
Path: MarkForRemoval(path),
})
}
// AddCDIDevice records the addition of the given CDI device to a container.
func (a *ContainerAdjustment) AddCDIDevice(d *CDIDevice) {
a.CDIDevices = append(a.CDIDevices, d) // TODO: should we dup d here ?
}
// AddOrReplaceNamespace records the addition or replacement of the given namespace to a container.
func (a *ContainerAdjustment) AddOrReplaceNamespace(n *LinuxNamespace) {
a.initLinuxNamespaces()
a.Linux.Namespaces = append(a.Linux.Namespaces, n) // TODO: should we dup n here ?
}
// RemoveNamespace records the removal of the given namespace from a container.
func (a *ContainerAdjustment) RemoveNamespace(n *LinuxNamespace) {
a.initLinuxNamespaces()
a.Linux.Namespaces = append(a.Linux.Namespaces, &LinuxNamespace{
Type: MarkForRemoval(n.Type),
})
}
// SetLinuxMemoryLimit records setting the memory limit for a container.
func (a *ContainerAdjustment) SetLinuxMemoryLimit(value int64) {
a.initLinuxResourcesMemory()
a.Linux.Resources.Memory.Limit = Int64(value)
}
// SetLinuxMemoryReservation records setting the memory reservation for a container.
func (a *ContainerAdjustment) SetLinuxMemoryReservation(value int64) {
a.initLinuxResourcesMemory()
a.Linux.Resources.Memory.Reservation = Int64(value)
}
// SetLinuxMemorySwap records records setting the memory swap limit for a container.
func (a *ContainerAdjustment) SetLinuxMemorySwap(value int64) {
a.initLinuxResourcesMemory()
a.Linux.Resources.Memory.Swap = Int64(value)
}
// SetLinuxMemoryKernel records setting the memory kernel limit for a container.
func (a *ContainerAdjustment) SetLinuxMemoryKernel(value int64) {
a.initLinuxResourcesMemory()
a.Linux.Resources.Memory.Kernel = Int64(value)
}
// SetLinuxMemoryKernelTCP records setting the memory kernel TCP limit for a container.
func (a *ContainerAdjustment) SetLinuxMemoryKernelTCP(value int64) {
a.initLinuxResourcesMemory()
a.Linux.Resources.Memory.KernelTcp = Int64(value)
}
// SetLinuxMemorySwappiness records setting the memory swappiness for a container.
func (a *ContainerAdjustment) SetLinuxMemorySwappiness(value uint64) {
a.initLinuxResourcesMemory()
a.Linux.Resources.Memory.Swappiness = UInt64(value)
}
// SetLinuxMemoryDisableOomKiller records disabling the OOM killer for a container.
func (a *ContainerAdjustment) SetLinuxMemoryDisableOomKiller() {
a.initLinuxResourcesMemory()
a.Linux.Resources.Memory.DisableOomKiller = Bool(true)
}
// SetLinuxMemoryUseHierarchy records enabling hierarchical memory accounting for a container.
func (a *ContainerAdjustment) SetLinuxMemoryUseHierarchy() {
a.initLinuxResourcesMemory()
a.Linux.Resources.Memory.UseHierarchy = Bool(true)
}
// SetLinuxCPUShares records setting the scheduler's CPU shares for a container.
func (a *ContainerAdjustment) SetLinuxCPUShares(value uint64) {
a.initLinuxResourcesCPU()
a.Linux.Resources.Cpu.Shares = UInt64(value)
}
// SetLinuxCPUQuota records setting the scheduler's CPU quota for a container.
func (a *ContainerAdjustment) SetLinuxCPUQuota(value int64) {
a.initLinuxResourcesCPU()
a.Linux.Resources.Cpu.Quota = Int64(value)
}
// SetLinuxCPUPeriod records setting the scheduler's CPU period for a container.
func (a *ContainerAdjustment) SetLinuxCPUPeriod(value int64) {
a.initLinuxResourcesCPU()
a.Linux.Resources.Cpu.Period = UInt64(value)
}
// SetLinuxCPURealtimeRuntime records setting the scheduler's realtime runtime for a container.
func (a *ContainerAdjustment) SetLinuxCPURealtimeRuntime(value int64) {
a.initLinuxResourcesCPU()
a.Linux.Resources.Cpu.RealtimeRuntime = Int64(value)
}
// SetLinuxCPURealtimePeriod records setting the scheduler's realtime period for a container.
func (a *ContainerAdjustment) SetLinuxCPURealtimePeriod(value uint64) {
a.initLinuxResourcesCPU()
a.Linux.Resources.Cpu.RealtimePeriod = UInt64(value)
}
// SetLinuxCPUSetCPUs records setting the cpuset CPUs for a container.
func (a *ContainerAdjustment) SetLinuxCPUSetCPUs(value string) {
a.initLinuxResourcesCPU()
a.Linux.Resources.Cpu.Cpus = value
}
// SetLinuxCPUSetMems records setting the cpuset memory for a container.
func (a *ContainerAdjustment) SetLinuxCPUSetMems(value string) {
a.initLinuxResourcesCPU()
a.Linux.Resources.Cpu.Mems = value
}
// SetLinuxPidLimits records setting the pid max number for a container.
func (a *ContainerAdjustment) SetLinuxPidLimits(value int64) {
a.initLinuxResourcesPids()
a.Linux.Resources.Pids.Limit = value
}
// AddLinuxHugepageLimit records adding a hugepage limit for a container.
func (a *ContainerAdjustment) AddLinuxHugepageLimit(pageSize string, value uint64) {
a.initLinuxResources()
a.Linux.Resources.HugepageLimits = append(a.Linux.Resources.HugepageLimits,
&HugepageLimit{
PageSize: pageSize,
Limit: value,
})
}
// SetLinuxBlockIOClass records setting the Block I/O class for a container.
func (a *ContainerAdjustment) SetLinuxBlockIOClass(value string) {
a.initLinuxResources()
a.Linux.Resources.BlockioClass = String(value)
}
// SetLinuxRDTClass records setting the RDT class for a container.
func (a *ContainerAdjustment) SetLinuxRDTClass(value string) {
a.initLinuxResources()
a.Linux.Resources.RdtClass = String(value)
}
// AddLinuxUnified sets a cgroupv2 unified resource.
func (a *ContainerAdjustment) AddLinuxUnified(key, value string) {
a.initLinuxResourcesUnified()
a.Linux.Resources.Unified[key] = value
}
// SetLinuxCgroupsPath records setting the cgroups path for a container.
func (a *ContainerAdjustment) SetLinuxCgroupsPath(value string) {
a.initLinux()
a.Linux.CgroupsPath = value
}
// SetLinuxOomScoreAdj records setting the kernel's Out-Of-Memory (OOM) killer score for a container.
func (a *ContainerAdjustment) SetLinuxOomScoreAdj(value *int) {
a.initLinux()
a.Linux.OomScoreAdj = Int(value) // using Int(value) from ./options.go to optionally allocate a pointer to normalized copy of value
}
// SetLinuxIOPriority records setting the I/O priority for a container.
func (a *ContainerAdjustment) SetLinuxIOPriority(ioprio *LinuxIOPriority) {
a.initLinux()
a.Linux.IoPriority = ioprio
}
// SetLinuxSeccompPolicy overrides the container seccomp policy with the given arguments.
func (a *ContainerAdjustment) SetLinuxSeccompPolicy(seccomp *LinuxSeccomp) {
a.initLinux()
a.Linux.SeccompPolicy = seccomp
}
//
// Initializing a container adjustment and container update.
//
func (a *ContainerAdjustment) initAnnotations() {
if a.Annotations == nil {
a.Annotations = make(map[string]string)
}
}
func (a *ContainerAdjustment) initHooks() {
if a.Hooks == nil {
a.Hooks = &Hooks{}
}
}
func (a *ContainerAdjustment) initRlimits() {
if a.Rlimits == nil {
a.Rlimits = []*POSIXRlimit{}
}
}
func (a *ContainerAdjustment) initLinux() {
if a.Linux == nil {
a.Linux = &LinuxContainerAdjustment{}
}
}
func (a *ContainerAdjustment) initLinuxNamespaces() {
a.initLinux()
if a.Linux.Namespaces == nil {
a.Linux.Namespaces = []*LinuxNamespace{}
}
}
func (a *ContainerAdjustment) initLinuxResources() {
a.initLinux()
if a.Linux.Resources == nil {
a.Linux.Resources = &LinuxResources{}
}
}
func (a *ContainerAdjustment) initLinuxResourcesMemory() {
a.initLinuxResources()
if a.Linux.Resources.Memory == nil {
a.Linux.Resources.Memory = &LinuxMemory{}
}
}
func (a *ContainerAdjustment) initLinuxResourcesCPU() {
a.initLinuxResources()
if a.Linux.Resources.Cpu == nil {
a.Linux.Resources.Cpu = &LinuxCPU{}
}
}
func (a *ContainerAdjustment) initLinuxResourcesPids() {
a.initLinuxResources()
if a.Linux.Resources.Pids == nil {
a.Linux.Resources.Pids = &LinuxPids{}
}
}
func (a *ContainerAdjustment) initLinuxResourcesUnified() {
a.initLinuxResources()
if a.Linux.Resources.Unified == nil {
a.Linux.Resources.Unified = make(map[string]string)
}
}

6424
vendor/github.com/containerd/nri/pkg/api/api.pb.go generated vendored Normal file

File diff suppressed because it is too large Load Diff

668
vendor/github.com/containerd/nri/pkg/api/api.proto generated vendored Normal file
View File

@@ -0,0 +1,668 @@
/*
Copyright The containerd Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
syntax = "proto3";
package nri.pkg.api.v1alpha1;
option go_package = "github.com/containerd/nri/pkg/api;api";
// Runtime service is the public API runtimes expose for NRI plugins.
// On this interface RPC requests are initiated by the plugin. This
// only covers plugin registration and unsolicited container updates.
// The rest of the API is defined by the Plugin service.
service Runtime {
// RegisterPlugin registers the plugin with the runtime.
rpc RegisterPlugin(RegisterPluginRequest) returns (Empty);
// UpdateContainers requests unsolicited updates to a set of containers.
rpc UpdateContainers(UpdateContainersRequest) returns (UpdateContainersResponse);
}
message RegisterPluginRequest {
// Name of the plugin to register.
string plugin_name = 1;
// Plugin invocation index. Plugins are called in ascending index order.
string plugin_idx = 2;
}
message UpdateContainersRequest {
// List of containers to update.
repeated ContainerUpdate update = 1;
// List of containers to evict.
repeated ContainerEviction evict = 2;
}
message UpdateContainersResponse {
// Containers that the runtime failed to update.
repeated ContainerUpdate failed = 1;
}
//
// Plugin is the API NRI uses to interact with plugins. It is used to
// - configure a plugin and subscribe it for lifecycle events
// - synchronize the state of a plugin with that of the runtime
// - hook a plugin into the lifecycle events of its interest
//
// During configuration the plugin tells the runtime which lifecycle events
// it wishes to get hooked into. Once configured, the plugin is synchronized
// with the runtime by receiving the list of pods and containers known to
// the runtime. The plugin can request changes to any of the containers in
// response. After initial synchronization the plugin starts receiving the
// events it subscribed for as they occur in the runtime. For container
// creation, update, and stop events, the plugin can request changes, both
// to the container that triggered the event or any other existing container
// in the runtime.
//
// For a subset of the container lifecycle events, NRI defines an additional
// Post-variant of the event. These variants are defined for CreateContainer,
// StartContainer, and UpdateContainer. For creation and update, these events
// can be used by plugins to discover the full extent of changes applied to
// the container, including any changes made by other active plugins.
//
// go:plugin type=plugin version=1
service Plugin {
// Configure the plugin and get its event subscription.
rpc Configure(ConfigureRequest) returns (ConfigureResponse);
// Synchronize the plugin with the state of the runtime.
rpc Synchronize(SynchronizeRequest) returns (SynchronizeResponse);
// Shutdown a plugin (let it know the runtime is going down).
rpc Shutdown(Empty) returns (Empty);
// CreateContainer relays the corresponding request to the plugin. In
// response, the plugin can adjust the container being created, and
// update other containers in the runtime. Container adjustment can
// alter labels, annotations, mounts, devices, environment variables,
// OCI hooks, and assigned container resources. Updates can alter
// assigned container resources.
rpc CreateContainer(CreateContainerRequest) returns (CreateContainerResponse);
// UpdateContainer relays the corresponding request to the plugin.
// The plugin can alter how the container is updated and request updates
// to additional containers in the runtime.
rpc UpdateContainer(UpdateContainerRequest) returns (UpdateContainerResponse);
// StopContainer relays the corresponding request to the plugin. The plugin
// can update any of the remaining containers in the runtime in response.
rpc StopContainer(StopContainerRequest) returns (StopContainerResponse);
// UpdatePodSandbox relays the corresponding request to the plugin.
rpc UpdatePodSandbox(UpdatePodSandboxRequest) returns (UpdatePodSandboxResponse);
// StateChange relays any remaining pod or container lifecycle/state change
// events the plugin has subscribed for. These can be used to trigger any
// plugin-specific processing which needs to occur in connection with any of
// these events.
rpc StateChange(StateChangeEvent) returns (Empty);
// ValidateContainerAdjustment relays a container adjustment validation request
// to the plugin. Container creation will fail the plugin rejects the adjustments.
rpc ValidateContainerAdjustment(ValidateContainerAdjustmentRequest) returns (ValidateContainerAdjustmentResponse);
}
// go:plugin type=host
service HostFunctions {
// Log displays a log message
rpc Log(LogRequest) returns (Empty) {}
}
message LogRequest {
string msg = 1;
enum Level {
LEVEL_UNSPECIFIED = 0;
LEVEL_DEBUG = 1;
LEVEL_INFO = 2;
LEVEL_WARN = 3;
LEVEL_ERROR = 4;
}
Level level = 2;
}
message ConfigureRequest {
// Any plugin-specific data, if present among the NRI configuration.
string config = 1;
// Name of the runtime NRI is running in.
string runtime_name = 2;
// Version of the runtime NRI is running in.
string runtime_version = 3;
// Configured registration timeout in milliseconds.
int64 registration_timeout = 4;
// Configured request processing timeout in milliseconds.
int64 request_timeout = 5;
}
message ConfigureResponse {
// Events to subscribe the plugin for. Each bit set corresponds to an
// enumerated Event.
int32 events = 2;
}
message SynchronizeRequest {
// Pods known to the runtime.
repeated PodSandbox pods = 1;
// Containers known to the runtime.
repeated Container containers = 2;
// Whether there are more pods and containers to follow.
bool more = 3;
}
message SynchronizeResponse {
// Updates to containers requested by the plugin.
repeated ContainerUpdate update = 1;
// Whether the client is able to handle more advertised pods and containers.
bool more = 2;
}
message CreateContainerRequest {
// Pod of container being created.
PodSandbox pod = 1;
// Container being created.
Container container = 2;
}
message CreateContainerResponse {
// Requested adjustments to container being created.
ContainerAdjustment adjust = 1;
// Requested updates to other existing containers.
repeated ContainerUpdate update = 2;
// Requested eviction of existing containers.
repeated ContainerEviction evict = 3;
}
message UpdateContainerRequest {
// Pod of container being updated.
PodSandbox pod = 1;
// Container being updated.
Container container = 2;
// Resources to update.
LinuxResources linux_resources = 3;
}
message UpdateContainerResponse {
// Requested updates to containers.
repeated ContainerUpdate update = 1;
// Requested eviction of containers.
repeated ContainerEviction evict = 2;
}
message StopContainerRequest {
// Pod of container being stopped.
PodSandbox pod = 1;
// Container being stopped.
Container container = 2;
}
message StopContainerResponse {
// Requested updates to containers.
repeated ContainerUpdate update = 1;
}
message UpdatePodSandboxRequest {
// Pod being updated.
PodSandbox pod = 1;
// Overhead associated with this pod.
LinuxResources overhead_linux_resources = 2;
// Sum of container resources for this pod.
LinuxResources linux_resources = 3;
}
message UpdatePodSandboxResponse {}
message StateChangeEvent {
// Event type of notification.
Event event = 1;
// Pod this notification is sent for. If this event is related to a container,
// pod is set to the pod of the container.
PodSandbox pod = 2;
// Container this notification is sent for. If the event is related to a pod,
// container is nil.
Container container = 3;
}
message ValidateContainerAdjustmentRequest {
// Pod of container being adjusted.
PodSandbox pod = 1;
// Container being adjusted in its pristine state.
Container container = 2;
// Pending container adjustments.
ContainerAdjustment adjust = 3;
// Pending updates to other containers.
repeated ContainerUpdate update = 4;
// Plugins that made the adjustments and updates.
OwningPlugins owners = 5;
// Plugins consulted for adjustments and updates.
repeated PluginInstance plugins = 6;
}
message PluginInstance {
string name = 1;
string index = 2;
}
message ValidateContainerAdjustmentResponse {
bool reject = 1;
string reason = 2;
}
// Empty response for those *Requests that are semantically events.
message Empty {}
// Events that plugins can subscribe to in ConfigureResponse.
enum Event {
UNKNOWN = 0;
RUN_POD_SANDBOX = 1;
STOP_POD_SANDBOX = 2;
REMOVE_POD_SANDBOX = 3;
CREATE_CONTAINER = 4;
POST_CREATE_CONTAINER = 5;
START_CONTAINER = 6;
POST_START_CONTAINER = 7;
UPDATE_CONTAINER = 8;
POST_UPDATE_CONTAINER = 9;
STOP_CONTAINER = 10;
REMOVE_CONTAINER = 11;
UPDATE_POD_SANDBOX = 12;
POST_UPDATE_POD_SANDBOX = 13;
VALIDATE_CONTAINER_ADJUSTMENT = 14;
LAST = 15;
}
// Pod metadata that is considered relevant for a plugin.
message PodSandbox {
string id = 1;
string name = 2;
string uid = 3;
string namespace = 4;
map<string, string> labels = 5;
map<string, string> annotations = 6;
string runtime_handler = 7;
LinuxPodSandbox linux = 8;
uint32 pid = 9; // for NRI v1 emulation
repeated string ips = 10;
}
// PodSandbox linux-specific metadata
message LinuxPodSandbox {
LinuxResources pod_overhead = 1;
LinuxResources pod_resources = 2;
string cgroup_parent = 3;
string cgroups_path = 4; // for NRI v1 emulation
repeated LinuxNamespace namespaces = 5; // for NRI v1 emulation
LinuxResources resources = 6; // for NRI v1 emulation
}
// Container metadata that is considered relevant for a plugin.
message Container {
string id = 1;
string pod_sandbox_id = 2;
string name = 3;
ContainerState state = 4;
map<string, string> labels = 5;
map<string, string> annotations = 6;
repeated string args = 7;
repeated string env = 8;
repeated Mount mounts = 9;
Hooks hooks = 10;
LinuxContainer linux = 11;
uint32 pid = 12; // for NRI v1 emulation
repeated POSIXRlimit rlimits = 13;
int64 created_at = 14;
int64 started_at = 15;
int64 finished_at = 16;
int32 exit_code = 17;
string status_reason = 18;
string status_message = 19;
repeated CDIDevice CDI_devices = 20;
}
// Possible container states.
enum ContainerState {
CONTAINER_UNKNOWN = 0;
CONTAINER_CREATED = 1;
CONTAINER_PAUSED = 2; // is this useful/necessary ?
CONTAINER_RUNNING = 3;
CONTAINER_STOPPED = 4;
}
// A container mount.
message Mount {
string destination = 1;
string type = 2;
string source = 3;
repeated string options = 4;
}
// Container OCI hooks.
message Hooks {
repeated Hook prestart = 1;
repeated Hook create_runtime = 2;
repeated Hook create_container = 3;
repeated Hook start_container = 4;
repeated Hook poststart = 5;
repeated Hook poststop = 6;
}
// One OCI hook.
message Hook {
string path = 1;
repeated string args = 2;
repeated string env = 3;
OptionalInt timeout = 4;
}
// Container (linux) metadata.
message LinuxContainer {
repeated LinuxNamespace namespaces = 1;
repeated LinuxDevice devices = 2;
LinuxResources resources = 3;
OptionalInt oom_score_adj = 4;
string cgroups_path = 5;
LinuxIOPriority io_priority = 6;
SecurityProfile seccomp_profile = 7;
LinuxSeccomp seccomp_policy = 8;
}
// A linux namespace.
message LinuxNamespace {
string type = 1;
string path = 2;
}
// A container (linux) device.
message LinuxDevice {
string path = 1;
string type = 2;
int64 major = 3;
int64 minor = 4;
OptionalFileMode file_mode = 5;
OptionalUInt32 uid = 6;
OptionalUInt32 gid = 7;
}
// A linux device cgroup controller rule.
message LinuxDeviceCgroup {
bool allow = 1;
string type = 2;
OptionalInt64 major = 3;
OptionalInt64 minor = 4;
string access = 5;
}
// A CDI device reference.
message CDIDevice {
string name = 1;
}
// Container (linux) resources.
message LinuxResources {
LinuxMemory memory = 1;
LinuxCPU cpu = 2;
repeated HugepageLimit hugepage_limits = 3;
OptionalString blockio_class = 4;
OptionalString rdt_class = 5;
map<string, string> unified = 6;
repeated LinuxDeviceCgroup devices = 7; // for NRI v1 emulation
LinuxPids pids = 8;
}
// Memory-related parts of (linux) resources.
message LinuxMemory {
OptionalInt64 limit = 1;
OptionalInt64 reservation = 2;
OptionalInt64 swap = 3;
OptionalInt64 kernel = 4;
OptionalInt64 kernel_tcp = 5;
OptionalUInt64 swappiness = 6;
OptionalBool disable_oom_killer = 7;
OptionalBool use_hierarchy = 8;
}
// CPU-related parts of (linux) resources.
message LinuxCPU {
OptionalUInt64 shares = 1;
OptionalInt64 quota = 2;
OptionalUInt64 period = 3;
OptionalInt64 realtime_runtime = 4;
OptionalUInt64 realtime_period = 5;
string cpus = 6;
string mems = 7;
}
// Container huge page limit.
message HugepageLimit {
string page_size = 1;
uint64 limit = 2;
}
// SecurityProfile for container.
message SecurityProfile {
enum ProfileType {
RUNTIME_DEFAULT = 0;
UNCONFINED = 1;
LOCALHOST = 2;
}
ProfileType profile_type = 1;
string localhost_ref = 2;
}
// Container rlimits
message POSIXRlimit {
string type = 1;
uint64 hard = 2;
uint64 soft = 3;
}
// Pids-related parts of (linux) resources.
message LinuxPids {
int64 limit = 1;
}
message LinuxIOPriority {
// Scheduling class of the IO priority.
IOPrioClass class = 1;
// The value of the IO priority.
int32 priority = 2;
}
enum IOPrioClass {
IOPRIO_CLASS_NONE = 0;
IOPRIO_CLASS_RT = 1;
IOPRIO_CLASS_BE = 2;
IOPRIO_CLASS_IDLE = 3;
}
// Requested adjustments to a container being created.
message ContainerAdjustment {
map<string, string> annotations = 2;
repeated Mount mounts = 3;
repeated KeyValue env = 4;
Hooks hooks = 5;
LinuxContainerAdjustment linux = 6;
repeated POSIXRlimit rlimits = 7;
repeated CDIDevice CDI_devices = 8;
repeated string args = 9;
}
// Adjustments to (linux) resources.
message LinuxContainerAdjustment {
repeated LinuxDevice devices = 1;
LinuxResources resources = 2;
string cgroups_path = 3;
OptionalInt oom_score_adj = 4;
LinuxIOPriority io_priority = 5;
LinuxSeccomp seccomp_policy = 6;
repeated LinuxNamespace namespaces = 7;
}
message LinuxSeccomp {
string default_action = 1;
OptionalUInt32 default_errno = 2;
repeated string architectures = 3;
repeated string flags = 4;
string listener_path = 5;
string listener_metadata = 6;
repeated LinuxSyscall syscalls = 7;
}
message LinuxSyscall {
repeated string names = 1;
string action = 2;
OptionalUInt32 errno_ret = 3;
repeated LinuxSeccompArg args = 4;
}
message LinuxSeccompArg {
uint32 index = 1;
uint64 value = 2;
uint64 value_two = 3;
string op = 4;
}
// Requested update to an already created container.
message ContainerUpdate {
string container_id = 1;
LinuxContainerUpdate linux = 2;
bool ignore_failure = 3;
}
// Updates to (linux) resources.
message LinuxContainerUpdate {
LinuxResources resources = 1;
}
// Request to evict (IOW unsolicitedly stop) a container.
message ContainerEviction {
// Container to evict.
string container_id = 1;
// Human-readable reason for eviction.
string reason = 2;
}
// KeyValue represents an environment variable.
message KeyValue {
string key = 1;
string value = 2;
}
// An optional string value.
message OptionalString {
string value = 1;
}
// An optional signed integer value.
message OptionalInt {
int64 value = 1;
}
// An optional 32-bit signed integer value.
message OptionalInt32 {
int32 value = 1;
}
// An optional 32-bit unsigned integer value.
message OptionalUInt32 {
uint32 value = 1;
}
// An optional 64-bit signed integer value.
message OptionalInt64 {
int64 value = 1;
}
// An optional 64-bit unsigned integer value.
message OptionalUInt64 {
uint64 value = 1;
}
// An optional boolean value.
message OptionalBool {
bool value = 1;
}
// An optional value of file permissions.
message OptionalFileMode {
uint32 value = 1;
}
// CompoundFieldOwners tracks 'plugin ownership' of compound fields
// which can be adjusted entry by entry, typically maps or slices.
// It is used to track ownership for annotations, mounts, devices,
// environment variables, hugepage limits, etc. The key identifies
// the owned entry (annotation key, mount destination, device path,
// environment variable name, etc.). The value is the owning plugin.
message CompoundFieldOwners {
map<string, string> owners = 1;
}
// FieldOwners tracks field 'plugin ownership' for a single container.
// Keys represent adjustable fields of a container. For simple fields,
// the value is the plugin that last modified the field. For compound
// fields, the value is a CompoundFieldOwners which provides tracking
// 'plugin ownership' per field for compound data, typically maps and
// slices. Field enum values are used to index both maps, using Key()
// to get the int32 for the Field.
message FieldOwners {
map<int32, string> simple = 1;
map<int32, CompoundFieldOwners> compound = 2;
}
// OwningPlugins tracks field 'plugin ownership' for multiple containers.
// The string keys are container IDs. The values are FieldOwners which
// track 'plugin ownership' per adjustable field for the container.
message OwningPlugins {
map<string, FieldOwners> owners = 1;
}
// Field enumerates all fields that can be adjusted by plugins.
enum Field {
None = 0;
Annotations = 1;
Mounts = 2;
OciHooks = 3;
Devices = 4;
CdiDevices = 5;
Env = 6;
Args = 7;
MemLimit = 8;
MemReservation = 9;
MemSwapLimit = 10;
MemKernelLimit = 11;
MemTCPLimit = 12;
MemSwappiness = 13;
MemDisableOomKiller = 14;
MemUseHierarchy = 15;
CPUShares = 16;
CPUQuota = 17;
CPUPeriod = 18;
CPURealtimeRuntime = 19;
CPURealtimePeriod = 20;
CPUSetCPUs = 21;
CPUSetMems = 22;
PidsLimit = 23;
HugepageLimits = 24;
BlockioClass = 25;
RdtClass = 26;
CgroupsUnified = 27;
CgroupsPath = 28;
OomScoreAdj = 29;
Rlimits = 30;
IoPriority = 31;
SeccompPolicy = 32;
Namespace = 33;
}

801
vendor/github.com/containerd/nri/pkg/api/api_host.pb.go generated vendored Normal file
View File

@@ -0,0 +1,801 @@
//go:build !wasip1
//
//Copyright The containerd Authors.
//
//Licensed under the Apache License, Version 2.0 (the "License");
//you may not use this file except in compliance with the License.
//You may obtain a copy of the License at
//
//http://www.apache.org/licenses/LICENSE-2.0
//
//Unless required by applicable law or agreed to in writing, software
//distributed under the License is distributed on an "AS IS" BASIS,
//WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//See the License for the specific language governing permissions and
//limitations under the License.
// Code generated by protoc-gen-go-plugin. DO NOT EDIT.
// versions:
// protoc-gen-go-plugin v0.1.0
// protoc v3.20.1
// source: pkg/api/api.proto
package api
import (
context "context"
errors "errors"
fmt "fmt"
wasm "github.com/knqyf263/go-plugin/wasm"
wazero "github.com/tetratelabs/wazero"
api "github.com/tetratelabs/wazero/api"
sys "github.com/tetratelabs/wazero/sys"
os "os"
)
const (
i32 = api.ValueTypeI32
i64 = api.ValueTypeI64
)
type _hostFunctions struct {
HostFunctions
}
// Instantiate a Go-defined module named "env" that exports host functions.
func (h _hostFunctions) Instantiate(ctx context.Context, r wazero.Runtime) error {
envBuilder := r.NewHostModuleBuilder("env")
envBuilder.NewFunctionBuilder().
WithGoModuleFunction(api.GoModuleFunc(h._Log), []api.ValueType{i32, i32}, []api.ValueType{i64}).
WithParameterNames("offset", "size").
Export("log")
_, err := envBuilder.Instantiate(ctx)
return err
}
// Log displays a log message
func (h _hostFunctions) _Log(ctx context.Context, m api.Module, stack []uint64) {
offset, size := uint32(stack[0]), uint32(stack[1])
buf, err := wasm.ReadMemory(m.Memory(), offset, size)
if err != nil {
panic(err)
}
request := new(LogRequest)
err = request.UnmarshalVT(buf)
if err != nil {
panic(err)
}
resp, err := h.Log(ctx, request)
if err != nil {
panic(err)
}
buf, err = resp.MarshalVT()
if err != nil {
panic(err)
}
ptr, err := wasm.WriteMemory(ctx, m, buf)
if err != nil {
panic(err)
}
ptrLen := (ptr << uint64(32)) | uint64(len(buf))
stack[0] = ptrLen
}
const PluginPluginAPIVersion = 1
type PluginPlugin struct {
newRuntime func(context.Context) (wazero.Runtime, error)
moduleConfig wazero.ModuleConfig
}
func NewPluginPlugin(ctx context.Context, opts ...wazeroConfigOption) (*PluginPlugin, error) {
o := &WazeroConfig{
newRuntime: DefaultWazeroRuntime(),
moduleConfig: wazero.NewModuleConfig().WithStartFunctions("_initialize"),
}
for _, opt := range opts {
opt(o)
}
return &PluginPlugin{
newRuntime: o.newRuntime,
moduleConfig: o.moduleConfig,
}, nil
}
type plugin interface {
Close(ctx context.Context) error
Plugin
}
func (p *PluginPlugin) Load(ctx context.Context, pluginPath string, hostFunctions HostFunctions) (plugin, error) {
b, err := os.ReadFile(pluginPath)
if err != nil {
return nil, err
}
// Create a new runtime so that multiple modules will not conflict
r, err := p.newRuntime(ctx)
if err != nil {
return nil, err
}
h := _hostFunctions{hostFunctions}
if err := h.Instantiate(ctx, r); err != nil {
return nil, err
}
// Compile the WebAssembly module using the default configuration.
code, err := r.CompileModule(ctx, b)
if err != nil {
return nil, err
}
// InstantiateModule runs the "_start" function, WASI's "main".
module, err := r.InstantiateModule(ctx, code, p.moduleConfig)
if err != nil {
// Note: Most compilers do not exit the module after running "_start",
// unless there was an Error. This allows you to call exported functions.
if exitErr, ok := err.(*sys.ExitError); ok && exitErr.ExitCode() != 0 {
return nil, fmt.Errorf("unexpected exit_code: %d", exitErr.ExitCode())
} else if !ok {
return nil, err
}
}
// Compare API versions with the loading plugin
apiVersion := module.ExportedFunction("plugin_api_version")
if apiVersion == nil {
return nil, errors.New("plugin_api_version is not exported")
}
results, err := apiVersion.Call(ctx)
if err != nil {
return nil, err
} else if len(results) != 1 {
return nil, errors.New("invalid plugin_api_version signature")
}
if results[0] != PluginPluginAPIVersion {
return nil, fmt.Errorf("API version mismatch, host: %d, plugin: %d", PluginPluginAPIVersion, results[0])
}
configure := module.ExportedFunction("plugin_configure")
if configure == nil {
return nil, errors.New("plugin_configure is not exported")
}
synchronize := module.ExportedFunction("plugin_synchronize")
if synchronize == nil {
return nil, errors.New("plugin_synchronize is not exported")
}
shutdown := module.ExportedFunction("plugin_shutdown")
if shutdown == nil {
return nil, errors.New("plugin_shutdown is not exported")
}
createcontainer := module.ExportedFunction("plugin_create_container")
if createcontainer == nil {
return nil, errors.New("plugin_create_container is not exported")
}
updatecontainer := module.ExportedFunction("plugin_update_container")
if updatecontainer == nil {
return nil, errors.New("plugin_update_container is not exported")
}
stopcontainer := module.ExportedFunction("plugin_stop_container")
if stopcontainer == nil {
return nil, errors.New("plugin_stop_container is not exported")
}
updatepodsandbox := module.ExportedFunction("plugin_update_pod_sandbox")
if updatepodsandbox == nil {
return nil, errors.New("plugin_update_pod_sandbox is not exported")
}
statechange := module.ExportedFunction("plugin_state_change")
if statechange == nil {
return nil, errors.New("plugin_state_change is not exported")
}
validatecontaineradjustment := module.ExportedFunction("plugin_validate_container_adjustment")
if validatecontaineradjustment == nil {
return nil, errors.New("plugin_validate_container_adjustment is not exported")
}
malloc := module.ExportedFunction("malloc")
if malloc == nil {
return nil, errors.New("malloc is not exported")
}
free := module.ExportedFunction("free")
if free == nil {
return nil, errors.New("free is not exported")
}
return &pluginPlugin{
runtime: r,
module: module,
malloc: malloc,
free: free,
configure: configure,
synchronize: synchronize,
shutdown: shutdown,
createcontainer: createcontainer,
updatecontainer: updatecontainer,
stopcontainer: stopcontainer,
updatepodsandbox: updatepodsandbox,
statechange: statechange,
validatecontaineradjustment: validatecontaineradjustment,
}, nil
}
func (p *pluginPlugin) Close(ctx context.Context) (err error) {
if r := p.runtime; r != nil {
r.Close(ctx)
}
return
}
type pluginPlugin struct {
runtime wazero.Runtime
module api.Module
malloc api.Function
free api.Function
configure api.Function
synchronize api.Function
shutdown api.Function
createcontainer api.Function
updatecontainer api.Function
stopcontainer api.Function
updatepodsandbox api.Function
statechange api.Function
validatecontaineradjustment api.Function
}
func (p *pluginPlugin) Configure(ctx context.Context, request *ConfigureRequest) (*ConfigureResponse, error) {
data, err := request.MarshalVT()
if err != nil {
return nil, err
}
dataSize := uint64(len(data))
var dataPtr uint64
// If the input data is not empty, we must allocate the in-Wasm memory to store it, and pass to the plugin.
if dataSize != 0 {
results, err := p.malloc.Call(ctx, dataSize)
if err != nil {
return nil, err
}
dataPtr = results[0]
// This pointer is managed by the Wasm module, which is unaware of external usage.
// So, we have to free it when finished
defer p.free.Call(ctx, dataPtr)
// The pointer is a linear memory offset, which is where we write the name.
if !p.module.Memory().Write(uint32(dataPtr), data) {
return nil, fmt.Errorf("Memory.Write(%d, %d) out of range of memory size %d", dataPtr, dataSize, p.module.Memory().Size())
}
}
ptrSize, err := p.configure.Call(ctx, dataPtr, dataSize)
if err != nil {
return nil, err
}
resPtr := uint32(ptrSize[0] >> 32)
resSize := uint32(ptrSize[0])
var isErrResponse bool
if (resSize & (1 << 31)) > 0 {
isErrResponse = true
resSize &^= (1 << 31)
}
// We don't need the memory after deserialization: make sure it is freed.
if resPtr != 0 {
defer p.free.Call(ctx, uint64(resPtr))
}
// The pointer is a linear memory offset, which is where we write the name.
bytes, ok := p.module.Memory().Read(resPtr, resSize)
if !ok {
return nil, fmt.Errorf("Memory.Read(%d, %d) out of range of memory size %d",
resPtr, resSize, p.module.Memory().Size())
}
if isErrResponse {
return nil, errors.New(string(bytes))
}
response := new(ConfigureResponse)
if err = response.UnmarshalVT(bytes); err != nil {
return nil, err
}
return response, nil
}
func (p *pluginPlugin) Synchronize(ctx context.Context, request *SynchronizeRequest) (*SynchronizeResponse, error) {
data, err := request.MarshalVT()
if err != nil {
return nil, err
}
dataSize := uint64(len(data))
var dataPtr uint64
// If the input data is not empty, we must allocate the in-Wasm memory to store it, and pass to the plugin.
if dataSize != 0 {
results, err := p.malloc.Call(ctx, dataSize)
if err != nil {
return nil, err
}
dataPtr = results[0]
// This pointer is managed by the Wasm module, which is unaware of external usage.
// So, we have to free it when finished
defer p.free.Call(ctx, dataPtr)
// The pointer is a linear memory offset, which is where we write the name.
if !p.module.Memory().Write(uint32(dataPtr), data) {
return nil, fmt.Errorf("Memory.Write(%d, %d) out of range of memory size %d", dataPtr, dataSize, p.module.Memory().Size())
}
}
ptrSize, err := p.synchronize.Call(ctx, dataPtr, dataSize)
if err != nil {
return nil, err
}
resPtr := uint32(ptrSize[0] >> 32)
resSize := uint32(ptrSize[0])
var isErrResponse bool
if (resSize & (1 << 31)) > 0 {
isErrResponse = true
resSize &^= (1 << 31)
}
// We don't need the memory after deserialization: make sure it is freed.
if resPtr != 0 {
defer p.free.Call(ctx, uint64(resPtr))
}
// The pointer is a linear memory offset, which is where we write the name.
bytes, ok := p.module.Memory().Read(resPtr, resSize)
if !ok {
return nil, fmt.Errorf("Memory.Read(%d, %d) out of range of memory size %d",
resPtr, resSize, p.module.Memory().Size())
}
if isErrResponse {
return nil, errors.New(string(bytes))
}
response := new(SynchronizeResponse)
if err = response.UnmarshalVT(bytes); err != nil {
return nil, err
}
return response, nil
}
func (p *pluginPlugin) Shutdown(ctx context.Context, request *Empty) (*Empty, error) {
data, err := request.MarshalVT()
if err != nil {
return nil, err
}
dataSize := uint64(len(data))
var dataPtr uint64
// If the input data is not empty, we must allocate the in-Wasm memory to store it, and pass to the plugin.
if dataSize != 0 {
results, err := p.malloc.Call(ctx, dataSize)
if err != nil {
return nil, err
}
dataPtr = results[0]
// This pointer is managed by the Wasm module, which is unaware of external usage.
// So, we have to free it when finished
defer p.free.Call(ctx, dataPtr)
// The pointer is a linear memory offset, which is where we write the name.
if !p.module.Memory().Write(uint32(dataPtr), data) {
return nil, fmt.Errorf("Memory.Write(%d, %d) out of range of memory size %d", dataPtr, dataSize, p.module.Memory().Size())
}
}
ptrSize, err := p.shutdown.Call(ctx, dataPtr, dataSize)
if err != nil {
return nil, err
}
resPtr := uint32(ptrSize[0] >> 32)
resSize := uint32(ptrSize[0])
var isErrResponse bool
if (resSize & (1 << 31)) > 0 {
isErrResponse = true
resSize &^= (1 << 31)
}
// We don't need the memory after deserialization: make sure it is freed.
if resPtr != 0 {
defer p.free.Call(ctx, uint64(resPtr))
}
// The pointer is a linear memory offset, which is where we write the name.
bytes, ok := p.module.Memory().Read(resPtr, resSize)
if !ok {
return nil, fmt.Errorf("Memory.Read(%d, %d) out of range of memory size %d",
resPtr, resSize, p.module.Memory().Size())
}
if isErrResponse {
return nil, errors.New(string(bytes))
}
response := new(Empty)
if err = response.UnmarshalVT(bytes); err != nil {
return nil, err
}
return response, nil
}
func (p *pluginPlugin) CreateContainer(ctx context.Context, request *CreateContainerRequest) (*CreateContainerResponse, error) {
data, err := request.MarshalVT()
if err != nil {
return nil, err
}
dataSize := uint64(len(data))
var dataPtr uint64
// If the input data is not empty, we must allocate the in-Wasm memory to store it, and pass to the plugin.
if dataSize != 0 {
results, err := p.malloc.Call(ctx, dataSize)
if err != nil {
return nil, err
}
dataPtr = results[0]
// This pointer is managed by the Wasm module, which is unaware of external usage.
// So, we have to free it when finished
defer p.free.Call(ctx, dataPtr)
// The pointer is a linear memory offset, which is where we write the name.
if !p.module.Memory().Write(uint32(dataPtr), data) {
return nil, fmt.Errorf("Memory.Write(%d, %d) out of range of memory size %d", dataPtr, dataSize, p.module.Memory().Size())
}
}
ptrSize, err := p.createcontainer.Call(ctx, dataPtr, dataSize)
if err != nil {
return nil, err
}
resPtr := uint32(ptrSize[0] >> 32)
resSize := uint32(ptrSize[0])
var isErrResponse bool
if (resSize & (1 << 31)) > 0 {
isErrResponse = true
resSize &^= (1 << 31)
}
// We don't need the memory after deserialization: make sure it is freed.
if resPtr != 0 {
defer p.free.Call(ctx, uint64(resPtr))
}
// The pointer is a linear memory offset, which is where we write the name.
bytes, ok := p.module.Memory().Read(resPtr, resSize)
if !ok {
return nil, fmt.Errorf("Memory.Read(%d, %d) out of range of memory size %d",
resPtr, resSize, p.module.Memory().Size())
}
if isErrResponse {
return nil, errors.New(string(bytes))
}
response := new(CreateContainerResponse)
if err = response.UnmarshalVT(bytes); err != nil {
return nil, err
}
return response, nil
}
func (p *pluginPlugin) UpdateContainer(ctx context.Context, request *UpdateContainerRequest) (*UpdateContainerResponse, error) {
data, err := request.MarshalVT()
if err != nil {
return nil, err
}
dataSize := uint64(len(data))
var dataPtr uint64
// If the input data is not empty, we must allocate the in-Wasm memory to store it, and pass to the plugin.
if dataSize != 0 {
results, err := p.malloc.Call(ctx, dataSize)
if err != nil {
return nil, err
}
dataPtr = results[0]
// This pointer is managed by the Wasm module, which is unaware of external usage.
// So, we have to free it when finished
defer p.free.Call(ctx, dataPtr)
// The pointer is a linear memory offset, which is where we write the name.
if !p.module.Memory().Write(uint32(dataPtr), data) {
return nil, fmt.Errorf("Memory.Write(%d, %d) out of range of memory size %d", dataPtr, dataSize, p.module.Memory().Size())
}
}
ptrSize, err := p.updatecontainer.Call(ctx, dataPtr, dataSize)
if err != nil {
return nil, err
}
resPtr := uint32(ptrSize[0] >> 32)
resSize := uint32(ptrSize[0])
var isErrResponse bool
if (resSize & (1 << 31)) > 0 {
isErrResponse = true
resSize &^= (1 << 31)
}
// We don't need the memory after deserialization: make sure it is freed.
if resPtr != 0 {
defer p.free.Call(ctx, uint64(resPtr))
}
// The pointer is a linear memory offset, which is where we write the name.
bytes, ok := p.module.Memory().Read(resPtr, resSize)
if !ok {
return nil, fmt.Errorf("Memory.Read(%d, %d) out of range of memory size %d",
resPtr, resSize, p.module.Memory().Size())
}
if isErrResponse {
return nil, errors.New(string(bytes))
}
response := new(UpdateContainerResponse)
if err = response.UnmarshalVT(bytes); err != nil {
return nil, err
}
return response, nil
}
func (p *pluginPlugin) StopContainer(ctx context.Context, request *StopContainerRequest) (*StopContainerResponse, error) {
data, err := request.MarshalVT()
if err != nil {
return nil, err
}
dataSize := uint64(len(data))
var dataPtr uint64
// If the input data is not empty, we must allocate the in-Wasm memory to store it, and pass to the plugin.
if dataSize != 0 {
results, err := p.malloc.Call(ctx, dataSize)
if err != nil {
return nil, err
}
dataPtr = results[0]
// This pointer is managed by the Wasm module, which is unaware of external usage.
// So, we have to free it when finished
defer p.free.Call(ctx, dataPtr)
// The pointer is a linear memory offset, which is where we write the name.
if !p.module.Memory().Write(uint32(dataPtr), data) {
return nil, fmt.Errorf("Memory.Write(%d, %d) out of range of memory size %d", dataPtr, dataSize, p.module.Memory().Size())
}
}
ptrSize, err := p.stopcontainer.Call(ctx, dataPtr, dataSize)
if err != nil {
return nil, err
}
resPtr := uint32(ptrSize[0] >> 32)
resSize := uint32(ptrSize[0])
var isErrResponse bool
if (resSize & (1 << 31)) > 0 {
isErrResponse = true
resSize &^= (1 << 31)
}
// We don't need the memory after deserialization: make sure it is freed.
if resPtr != 0 {
defer p.free.Call(ctx, uint64(resPtr))
}
// The pointer is a linear memory offset, which is where we write the name.
bytes, ok := p.module.Memory().Read(resPtr, resSize)
if !ok {
return nil, fmt.Errorf("Memory.Read(%d, %d) out of range of memory size %d",
resPtr, resSize, p.module.Memory().Size())
}
if isErrResponse {
return nil, errors.New(string(bytes))
}
response := new(StopContainerResponse)
if err = response.UnmarshalVT(bytes); err != nil {
return nil, err
}
return response, nil
}
func (p *pluginPlugin) UpdatePodSandbox(ctx context.Context, request *UpdatePodSandboxRequest) (*UpdatePodSandboxResponse, error) {
data, err := request.MarshalVT()
if err != nil {
return nil, err
}
dataSize := uint64(len(data))
var dataPtr uint64
// If the input data is not empty, we must allocate the in-Wasm memory to store it, and pass to the plugin.
if dataSize != 0 {
results, err := p.malloc.Call(ctx, dataSize)
if err != nil {
return nil, err
}
dataPtr = results[0]
// This pointer is managed by the Wasm module, which is unaware of external usage.
// So, we have to free it when finished
defer p.free.Call(ctx, dataPtr)
// The pointer is a linear memory offset, which is where we write the name.
if !p.module.Memory().Write(uint32(dataPtr), data) {
return nil, fmt.Errorf("Memory.Write(%d, %d) out of range of memory size %d", dataPtr, dataSize, p.module.Memory().Size())
}
}
ptrSize, err := p.updatepodsandbox.Call(ctx, dataPtr, dataSize)
if err != nil {
return nil, err
}
resPtr := uint32(ptrSize[0] >> 32)
resSize := uint32(ptrSize[0])
var isErrResponse bool
if (resSize & (1 << 31)) > 0 {
isErrResponse = true
resSize &^= (1 << 31)
}
// We don't need the memory after deserialization: make sure it is freed.
if resPtr != 0 {
defer p.free.Call(ctx, uint64(resPtr))
}
// The pointer is a linear memory offset, which is where we write the name.
bytes, ok := p.module.Memory().Read(resPtr, resSize)
if !ok {
return nil, fmt.Errorf("Memory.Read(%d, %d) out of range of memory size %d",
resPtr, resSize, p.module.Memory().Size())
}
if isErrResponse {
return nil, errors.New(string(bytes))
}
response := new(UpdatePodSandboxResponse)
if err = response.UnmarshalVT(bytes); err != nil {
return nil, err
}
return response, nil
}
func (p *pluginPlugin) StateChange(ctx context.Context, request *StateChangeEvent) (*Empty, error) {
data, err := request.MarshalVT()
if err != nil {
return nil, err
}
dataSize := uint64(len(data))
var dataPtr uint64
// If the input data is not empty, we must allocate the in-Wasm memory to store it, and pass to the plugin.
if dataSize != 0 {
results, err := p.malloc.Call(ctx, dataSize)
if err != nil {
return nil, err
}
dataPtr = results[0]
// This pointer is managed by the Wasm module, which is unaware of external usage.
// So, we have to free it when finished
defer p.free.Call(ctx, dataPtr)
// The pointer is a linear memory offset, which is where we write the name.
if !p.module.Memory().Write(uint32(dataPtr), data) {
return nil, fmt.Errorf("Memory.Write(%d, %d) out of range of memory size %d", dataPtr, dataSize, p.module.Memory().Size())
}
}
ptrSize, err := p.statechange.Call(ctx, dataPtr, dataSize)
if err != nil {
return nil, err
}
resPtr := uint32(ptrSize[0] >> 32)
resSize := uint32(ptrSize[0])
var isErrResponse bool
if (resSize & (1 << 31)) > 0 {
isErrResponse = true
resSize &^= (1 << 31)
}
// We don't need the memory after deserialization: make sure it is freed.
if resPtr != 0 {
defer p.free.Call(ctx, uint64(resPtr))
}
// The pointer is a linear memory offset, which is where we write the name.
bytes, ok := p.module.Memory().Read(resPtr, resSize)
if !ok {
return nil, fmt.Errorf("Memory.Read(%d, %d) out of range of memory size %d",
resPtr, resSize, p.module.Memory().Size())
}
if isErrResponse {
return nil, errors.New(string(bytes))
}
response := new(Empty)
if err = response.UnmarshalVT(bytes); err != nil {
return nil, err
}
return response, nil
}
func (p *pluginPlugin) ValidateContainerAdjustment(ctx context.Context, request *ValidateContainerAdjustmentRequest) (*ValidateContainerAdjustmentResponse, error) {
data, err := request.MarshalVT()
if err != nil {
return nil, err
}
dataSize := uint64(len(data))
var dataPtr uint64
// If the input data is not empty, we must allocate the in-Wasm memory to store it, and pass to the plugin.
if dataSize != 0 {
results, err := p.malloc.Call(ctx, dataSize)
if err != nil {
return nil, err
}
dataPtr = results[0]
// This pointer is managed by the Wasm module, which is unaware of external usage.
// So, we have to free it when finished
defer p.free.Call(ctx, dataPtr)
// The pointer is a linear memory offset, which is where we write the name.
if !p.module.Memory().Write(uint32(dataPtr), data) {
return nil, fmt.Errorf("Memory.Write(%d, %d) out of range of memory size %d", dataPtr, dataSize, p.module.Memory().Size())
}
}
ptrSize, err := p.validatecontaineradjustment.Call(ctx, dataPtr, dataSize)
if err != nil {
return nil, err
}
resPtr := uint32(ptrSize[0] >> 32)
resSize := uint32(ptrSize[0])
var isErrResponse bool
if (resSize & (1 << 31)) > 0 {
isErrResponse = true
resSize &^= (1 << 31)
}
// We don't need the memory after deserialization: make sure it is freed.
if resPtr != 0 {
defer p.free.Call(ctx, uint64(resPtr))
}
// The pointer is a linear memory offset, which is where we write the name.
bytes, ok := p.module.Memory().Read(resPtr, resSize)
if !ok {
return nil, fmt.Errorf("Memory.Read(%d, %d) out of range of memory size %d",
resPtr, resSize, p.module.Memory().Size())
}
if isErrResponse {
return nil, errors.New(string(bytes))
}
response := new(ValidateContainerAdjustmentResponse)
if err = response.UnmarshalVT(bytes); err != nil {
return nil, err
}
return response, nil
}

View File

@@ -0,0 +1,62 @@
//go:build !wasip1
//
//Copyright The containerd Authors.
//
//Licensed under the Apache License, Version 2.0 (the "License");
//you may not use this file except in compliance with the License.
//You may obtain a copy of the License at
//
//http://www.apache.org/licenses/LICENSE-2.0
//
//Unless required by applicable law or agreed to in writing, software
//distributed under the License is distributed on an "AS IS" BASIS,
//WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//See the License for the specific language governing permissions and
//limitations under the License.
// Code generated by protoc-gen-go-plugin. DO NOT EDIT.
// versions:
// protoc-gen-go-plugin v0.1.0
// protoc v3.20.1
// source: pkg/api/api.proto
package api
import (
context "context"
wazero "github.com/tetratelabs/wazero"
wasi_snapshot_preview1 "github.com/tetratelabs/wazero/imports/wasi_snapshot_preview1"
)
type wazeroConfigOption func(plugin *WazeroConfig)
type WazeroNewRuntime func(context.Context) (wazero.Runtime, error)
type WazeroConfig struct {
newRuntime func(context.Context) (wazero.Runtime, error)
moduleConfig wazero.ModuleConfig
}
func WazeroRuntime(newRuntime WazeroNewRuntime) wazeroConfigOption {
return func(h *WazeroConfig) {
h.newRuntime = newRuntime
}
}
func DefaultWazeroRuntime() WazeroNewRuntime {
return func(ctx context.Context) (wazero.Runtime, error) {
r := wazero.NewRuntime(ctx)
if _, err := wasi_snapshot_preview1.Instantiate(ctx, r); err != nil {
return nil, err
}
return r, nil
}
}
func WazeroModuleConfig(moduleConfig wazero.ModuleConfig) wazeroConfigOption {
return func(h *WazeroConfig) {
h.moduleConfig = moduleConfig
}
}

View File

@@ -0,0 +1,288 @@
//go:build wasip1
//
//Copyright The containerd Authors.
//
//Licensed under the Apache License, Version 2.0 (the "License");
//you may not use this file except in compliance with the License.
//You may obtain a copy of the License at
//
//http://www.apache.org/licenses/LICENSE-2.0
//
//Unless required by applicable law or agreed to in writing, software
//distributed under the License is distributed on an "AS IS" BASIS,
//WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//See the License for the specific language governing permissions and
//limitations under the License.
// Code generated by protoc-gen-go-plugin. DO NOT EDIT.
// versions:
// protoc-gen-go-plugin v0.1.0
// protoc v3.20.1
// source: pkg/api/api.proto
package api
import (
context "context"
wasm "github.com/knqyf263/go-plugin/wasm"
_ "unsafe"
)
const PluginPluginAPIVersion = 1
//go:wasmexport plugin_api_version
func _plugin_api_version() uint64 {
return PluginPluginAPIVersion
}
var plugin Plugin
func RegisterPlugin(p Plugin) {
plugin = p
}
//go:wasmexport plugin_configure
func _plugin_configure(ptr, size uint32) uint64 {
b := wasm.PtrToByte(ptr, size)
req := new(ConfigureRequest)
if err := req.UnmarshalVT(b); err != nil {
return 0
}
response, err := plugin.Configure(context.Background(), req)
if err != nil {
ptr, size = wasm.ByteToPtr([]byte(err.Error()))
return (uint64(ptr) << uint64(32)) | uint64(size) |
// Indicate that this is the error string by setting the 32-th bit, assuming that
// no data exceeds 31-bit size (2 GiB).
(1 << 31)
}
b, err = response.MarshalVT()
if err != nil {
return 0
}
ptr, size = wasm.ByteToPtr(b)
return (uint64(ptr) << uint64(32)) | uint64(size)
}
//go:wasmexport plugin_synchronize
func _plugin_synchronize(ptr, size uint32) uint64 {
b := wasm.PtrToByte(ptr, size)
req := new(SynchronizeRequest)
if err := req.UnmarshalVT(b); err != nil {
return 0
}
response, err := plugin.Synchronize(context.Background(), req)
if err != nil {
ptr, size = wasm.ByteToPtr([]byte(err.Error()))
return (uint64(ptr) << uint64(32)) | uint64(size) |
// Indicate that this is the error string by setting the 32-th bit, assuming that
// no data exceeds 31-bit size (2 GiB).
(1 << 31)
}
b, err = response.MarshalVT()
if err != nil {
return 0
}
ptr, size = wasm.ByteToPtr(b)
return (uint64(ptr) << uint64(32)) | uint64(size)
}
//go:wasmexport plugin_shutdown
func _plugin_shutdown(ptr, size uint32) uint64 {
b := wasm.PtrToByte(ptr, size)
req := new(Empty)
if err := req.UnmarshalVT(b); err != nil {
return 0
}
response, err := plugin.Shutdown(context.Background(), req)
if err != nil {
ptr, size = wasm.ByteToPtr([]byte(err.Error()))
return (uint64(ptr) << uint64(32)) | uint64(size) |
// Indicate that this is the error string by setting the 32-th bit, assuming that
// no data exceeds 31-bit size (2 GiB).
(1 << 31)
}
b, err = response.MarshalVT()
if err != nil {
return 0
}
ptr, size = wasm.ByteToPtr(b)
return (uint64(ptr) << uint64(32)) | uint64(size)
}
//go:wasmexport plugin_create_container
func _plugin_create_container(ptr, size uint32) uint64 {
b := wasm.PtrToByte(ptr, size)
req := new(CreateContainerRequest)
if err := req.UnmarshalVT(b); err != nil {
return 0
}
response, err := plugin.CreateContainer(context.Background(), req)
if err != nil {
ptr, size = wasm.ByteToPtr([]byte(err.Error()))
return (uint64(ptr) << uint64(32)) | uint64(size) |
// Indicate that this is the error string by setting the 32-th bit, assuming that
// no data exceeds 31-bit size (2 GiB).
(1 << 31)
}
b, err = response.MarshalVT()
if err != nil {
return 0
}
ptr, size = wasm.ByteToPtr(b)
return (uint64(ptr) << uint64(32)) | uint64(size)
}
//go:wasmexport plugin_update_container
func _plugin_update_container(ptr, size uint32) uint64 {
b := wasm.PtrToByte(ptr, size)
req := new(UpdateContainerRequest)
if err := req.UnmarshalVT(b); err != nil {
return 0
}
response, err := plugin.UpdateContainer(context.Background(), req)
if err != nil {
ptr, size = wasm.ByteToPtr([]byte(err.Error()))
return (uint64(ptr) << uint64(32)) | uint64(size) |
// Indicate that this is the error string by setting the 32-th bit, assuming that
// no data exceeds 31-bit size (2 GiB).
(1 << 31)
}
b, err = response.MarshalVT()
if err != nil {
return 0
}
ptr, size = wasm.ByteToPtr(b)
return (uint64(ptr) << uint64(32)) | uint64(size)
}
//go:wasmexport plugin_stop_container
func _plugin_stop_container(ptr, size uint32) uint64 {
b := wasm.PtrToByte(ptr, size)
req := new(StopContainerRequest)
if err := req.UnmarshalVT(b); err != nil {
return 0
}
response, err := plugin.StopContainer(context.Background(), req)
if err != nil {
ptr, size = wasm.ByteToPtr([]byte(err.Error()))
return (uint64(ptr) << uint64(32)) | uint64(size) |
// Indicate that this is the error string by setting the 32-th bit, assuming that
// no data exceeds 31-bit size (2 GiB).
(1 << 31)
}
b, err = response.MarshalVT()
if err != nil {
return 0
}
ptr, size = wasm.ByteToPtr(b)
return (uint64(ptr) << uint64(32)) | uint64(size)
}
//go:wasmexport plugin_update_pod_sandbox
func _plugin_update_pod_sandbox(ptr, size uint32) uint64 {
b := wasm.PtrToByte(ptr, size)
req := new(UpdatePodSandboxRequest)
if err := req.UnmarshalVT(b); err != nil {
return 0
}
response, err := plugin.UpdatePodSandbox(context.Background(), req)
if err != nil {
ptr, size = wasm.ByteToPtr([]byte(err.Error()))
return (uint64(ptr) << uint64(32)) | uint64(size) |
// Indicate that this is the error string by setting the 32-th bit, assuming that
// no data exceeds 31-bit size (2 GiB).
(1 << 31)
}
b, err = response.MarshalVT()
if err != nil {
return 0
}
ptr, size = wasm.ByteToPtr(b)
return (uint64(ptr) << uint64(32)) | uint64(size)
}
//go:wasmexport plugin_state_change
func _plugin_state_change(ptr, size uint32) uint64 {
b := wasm.PtrToByte(ptr, size)
req := new(StateChangeEvent)
if err := req.UnmarshalVT(b); err != nil {
return 0
}
response, err := plugin.StateChange(context.Background(), req)
if err != nil {
ptr, size = wasm.ByteToPtr([]byte(err.Error()))
return (uint64(ptr) << uint64(32)) | uint64(size) |
// Indicate that this is the error string by setting the 32-th bit, assuming that
// no data exceeds 31-bit size (2 GiB).
(1 << 31)
}
b, err = response.MarshalVT()
if err != nil {
return 0
}
ptr, size = wasm.ByteToPtr(b)
return (uint64(ptr) << uint64(32)) | uint64(size)
}
//go:wasmexport plugin_validate_container_adjustment
func _plugin_validate_container_adjustment(ptr, size uint32) uint64 {
b := wasm.PtrToByte(ptr, size)
req := new(ValidateContainerAdjustmentRequest)
if err := req.UnmarshalVT(b); err != nil {
return 0
}
response, err := plugin.ValidateContainerAdjustment(context.Background(), req)
if err != nil {
ptr, size = wasm.ByteToPtr([]byte(err.Error()))
return (uint64(ptr) << uint64(32)) | uint64(size) |
// Indicate that this is the error string by setting the 32-th bit, assuming that
// no data exceeds 31-bit size (2 GiB).
(1 << 31)
}
b, err = response.MarshalVT()
if err != nil {
return 0
}
ptr, size = wasm.ByteToPtr(b)
return (uint64(ptr) << uint64(32)) | uint64(size)
}
type hostFunctions struct{}
func NewHostFunctions() HostFunctions {
return hostFunctions{}
}
//go:wasmimport env log
func _log(ptr uint32, size uint32) uint64
func (h hostFunctions) Log(ctx context.Context, request *LogRequest) (*Empty, error) {
buf, err := request.MarshalVT()
if err != nil {
return nil, err
}
ptr, size := wasm.ByteToPtr(buf)
ptrSize := _log(ptr, size)
wasm.Free(ptr)
ptr = uint32(ptrSize >> 32)
size = uint32(ptrSize)
buf = wasm.PtrToByte(ptr, size)
response := new(Empty)
if err = response.UnmarshalVT(buf); err != nil {
return nil, err
}
return response, nil
}

View File

@@ -0,0 +1,95 @@
//
//Copyright The containerd Authors.
//
//Licensed under the Apache License, Version 2.0 (the "License");
//you may not use this file except in compliance with the License.
//You may obtain a copy of the License at
//
//http://www.apache.org/licenses/LICENSE-2.0
//
//Unless required by applicable law or agreed to in writing, software
//distributed under the License is distributed on an "AS IS" BASIS,
//WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//See the License for the specific language governing permissions and
//limitations under the License.
// Code generated by protoc-gen-go-plugin. DO NOT EDIT.
// versions:
// protoc-gen-go-plugin v0.1.0
// protoc v3.20.1
// source: pkg/api/api.proto
package api
import (
context "context"
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
)
const (
// Verify that this generated code is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
// Verify that runtime/protoimpl is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
)
// Plugin is the API NRI uses to interact with plugins. It is used to
// - configure a plugin and subscribe it for lifecycle events
// - synchronize the state of a plugin with that of the runtime
// - hook a plugin into the lifecycle events of its interest
//
// During configuration the plugin tells the runtime which lifecycle events
// it wishes to get hooked into. Once configured, the plugin is synchronized
// with the runtime by receiving the list of pods and containers known to
// the runtime. The plugin can request changes to any of the containers in
// response. After initial synchronization the plugin starts receiving the
// events it subscribed for as they occur in the runtime. For container
// creation, update, and stop events, the plugin can request changes, both
// to the container that triggered the event or any other existing container
// in the runtime.
//
// For a subset of the container lifecycle events, NRI defines an additional
// Post-variant of the event. These variants are defined for CreateContainer,
// StartContainer, and UpdateContainer. For creation and update, these events
// can be used by plugins to discover the full extent of changes applied to
// the container, including any changes made by other active plugins.
//
// go:plugin type=plugin version=1
type Plugin interface {
// Configure the plugin and get its event subscription.
Configure(context.Context, *ConfigureRequest) (*ConfigureResponse, error)
// Synchronize the plugin with the state of the runtime.
Synchronize(context.Context, *SynchronizeRequest) (*SynchronizeResponse, error)
// Shutdown a plugin (let it know the runtime is going down).
Shutdown(context.Context, *Empty) (*Empty, error)
// CreateContainer relays the corresponding request to the plugin. In
// response, the plugin can adjust the container being created, and
// update other containers in the runtime. Container adjustment can
// alter labels, annotations, mounts, devices, environment variables,
// OCI hooks, and assigned container resources. Updates can alter
// assigned container resources.
CreateContainer(context.Context, *CreateContainerRequest) (*CreateContainerResponse, error)
// UpdateContainer relays the corresponding request to the plugin.
// The plugin can alter how the container is updated and request updates
// to additional containers in the runtime.
UpdateContainer(context.Context, *UpdateContainerRequest) (*UpdateContainerResponse, error)
// StopContainer relays the corresponding request to the plugin. The plugin
// can update any of the remaining containers in the runtime in response.
StopContainer(context.Context, *StopContainerRequest) (*StopContainerResponse, error)
// UpdatePodSandbox relays the corresponding request to the plugin.
UpdatePodSandbox(context.Context, *UpdatePodSandboxRequest) (*UpdatePodSandboxResponse, error)
// StateChange relays any remaining pod or container lifecycle/state change
// events the plugin has subscribed for. These can be used to trigger any
// plugin-specific processing which needs to occur in connection with any of
// these events.
StateChange(context.Context, *StateChangeEvent) (*Empty, error)
// ValidateContainerAdjustment relays a container adjustment validation request
// to the plugin. Container creation will fail the plugin rejects the adjustments.
ValidateContainerAdjustment(context.Context, *ValidateContainerAdjustmentRequest) (*ValidateContainerAdjustmentResponse, error)
}
// go:plugin type=host
type HostFunctions interface {
// Log displays a log message
Log(context.Context, *LogRequest) (*Empty, error)
}

View File

@@ -0,0 +1,262 @@
//go:build !wasip1
// Code generated by protoc-gen-go-ttrpc. DO NOT EDIT.
// source: pkg/api/api.proto
package api
import (
context "context"
ttrpc "github.com/containerd/ttrpc"
)
type RuntimeService interface {
RegisterPlugin(context.Context, *RegisterPluginRequest) (*Empty, error)
UpdateContainers(context.Context, *UpdateContainersRequest) (*UpdateContainersResponse, error)
}
func RegisterRuntimeService(srv *ttrpc.Server, svc RuntimeService) {
srv.RegisterService("nri.pkg.api.v1alpha1.Runtime", &ttrpc.ServiceDesc{
Methods: map[string]ttrpc.Method{
"RegisterPlugin": func(ctx context.Context, unmarshal func(interface{}) error) (interface{}, error) {
var req RegisterPluginRequest
if err := unmarshal(&req); err != nil {
return nil, err
}
return svc.RegisterPlugin(ctx, &req)
},
"UpdateContainers": func(ctx context.Context, unmarshal func(interface{}) error) (interface{}, error) {
var req UpdateContainersRequest
if err := unmarshal(&req); err != nil {
return nil, err
}
return svc.UpdateContainers(ctx, &req)
},
},
})
}
type runtimeClient struct {
client *ttrpc.Client
}
func NewRuntimeClient(client *ttrpc.Client) RuntimeService {
return &runtimeClient{
client: client,
}
}
func (c *runtimeClient) RegisterPlugin(ctx context.Context, req *RegisterPluginRequest) (*Empty, error) {
var resp Empty
if err := c.client.Call(ctx, "nri.pkg.api.v1alpha1.Runtime", "RegisterPlugin", req, &resp); err != nil {
return nil, err
}
return &resp, nil
}
func (c *runtimeClient) UpdateContainers(ctx context.Context, req *UpdateContainersRequest) (*UpdateContainersResponse, error) {
var resp UpdateContainersResponse
if err := c.client.Call(ctx, "nri.pkg.api.v1alpha1.Runtime", "UpdateContainers", req, &resp); err != nil {
return nil, err
}
return &resp, nil
}
type PluginService interface {
Configure(context.Context, *ConfigureRequest) (*ConfigureResponse, error)
Synchronize(context.Context, *SynchronizeRequest) (*SynchronizeResponse, error)
Shutdown(context.Context, *Empty) (*Empty, error)
CreateContainer(context.Context, *CreateContainerRequest) (*CreateContainerResponse, error)
UpdateContainer(context.Context, *UpdateContainerRequest) (*UpdateContainerResponse, error)
StopContainer(context.Context, *StopContainerRequest) (*StopContainerResponse, error)
UpdatePodSandbox(context.Context, *UpdatePodSandboxRequest) (*UpdatePodSandboxResponse, error)
StateChange(context.Context, *StateChangeEvent) (*Empty, error)
ValidateContainerAdjustment(context.Context, *ValidateContainerAdjustmentRequest) (*ValidateContainerAdjustmentResponse, error)
}
func RegisterPluginService(srv *ttrpc.Server, svc PluginService) {
srv.RegisterService("nri.pkg.api.v1alpha1.Plugin", &ttrpc.ServiceDesc{
Methods: map[string]ttrpc.Method{
"Configure": func(ctx context.Context, unmarshal func(interface{}) error) (interface{}, error) {
var req ConfigureRequest
if err := unmarshal(&req); err != nil {
return nil, err
}
return svc.Configure(ctx, &req)
},
"Synchronize": func(ctx context.Context, unmarshal func(interface{}) error) (interface{}, error) {
var req SynchronizeRequest
if err := unmarshal(&req); err != nil {
return nil, err
}
return svc.Synchronize(ctx, &req)
},
"Shutdown": func(ctx context.Context, unmarshal func(interface{}) error) (interface{}, error) {
var req Empty
if err := unmarshal(&req); err != nil {
return nil, err
}
return svc.Shutdown(ctx, &req)
},
"CreateContainer": func(ctx context.Context, unmarshal func(interface{}) error) (interface{}, error) {
var req CreateContainerRequest
if err := unmarshal(&req); err != nil {
return nil, err
}
return svc.CreateContainer(ctx, &req)
},
"UpdateContainer": func(ctx context.Context, unmarshal func(interface{}) error) (interface{}, error) {
var req UpdateContainerRequest
if err := unmarshal(&req); err != nil {
return nil, err
}
return svc.UpdateContainer(ctx, &req)
},
"StopContainer": func(ctx context.Context, unmarshal func(interface{}) error) (interface{}, error) {
var req StopContainerRequest
if err := unmarshal(&req); err != nil {
return nil, err
}
return svc.StopContainer(ctx, &req)
},
"UpdatePodSandbox": func(ctx context.Context, unmarshal func(interface{}) error) (interface{}, error) {
var req UpdatePodSandboxRequest
if err := unmarshal(&req); err != nil {
return nil, err
}
return svc.UpdatePodSandbox(ctx, &req)
},
"StateChange": func(ctx context.Context, unmarshal func(interface{}) error) (interface{}, error) {
var req StateChangeEvent
if err := unmarshal(&req); err != nil {
return nil, err
}
return svc.StateChange(ctx, &req)
},
"ValidateContainerAdjustment": func(ctx context.Context, unmarshal func(interface{}) error) (interface{}, error) {
var req ValidateContainerAdjustmentRequest
if err := unmarshal(&req); err != nil {
return nil, err
}
return svc.ValidateContainerAdjustment(ctx, &req)
},
},
})
}
type pluginClient struct {
client *ttrpc.Client
}
func NewPluginClient(client *ttrpc.Client) PluginService {
return &pluginClient{
client: client,
}
}
func (c *pluginClient) Configure(ctx context.Context, req *ConfigureRequest) (*ConfigureResponse, error) {
var resp ConfigureResponse
if err := c.client.Call(ctx, "nri.pkg.api.v1alpha1.Plugin", "Configure", req, &resp); err != nil {
return nil, err
}
return &resp, nil
}
func (c *pluginClient) Synchronize(ctx context.Context, req *SynchronizeRequest) (*SynchronizeResponse, error) {
var resp SynchronizeResponse
if err := c.client.Call(ctx, "nri.pkg.api.v1alpha1.Plugin", "Synchronize", req, &resp); err != nil {
return nil, err
}
return &resp, nil
}
func (c *pluginClient) Shutdown(ctx context.Context, req *Empty) (*Empty, error) {
var resp Empty
if err := c.client.Call(ctx, "nri.pkg.api.v1alpha1.Plugin", "Shutdown", req, &resp); err != nil {
return nil, err
}
return &resp, nil
}
func (c *pluginClient) CreateContainer(ctx context.Context, req *CreateContainerRequest) (*CreateContainerResponse, error) {
var resp CreateContainerResponse
if err := c.client.Call(ctx, "nri.pkg.api.v1alpha1.Plugin", "CreateContainer", req, &resp); err != nil {
return nil, err
}
return &resp, nil
}
func (c *pluginClient) UpdateContainer(ctx context.Context, req *UpdateContainerRequest) (*UpdateContainerResponse, error) {
var resp UpdateContainerResponse
if err := c.client.Call(ctx, "nri.pkg.api.v1alpha1.Plugin", "UpdateContainer", req, &resp); err != nil {
return nil, err
}
return &resp, nil
}
func (c *pluginClient) StopContainer(ctx context.Context, req *StopContainerRequest) (*StopContainerResponse, error) {
var resp StopContainerResponse
if err := c.client.Call(ctx, "nri.pkg.api.v1alpha1.Plugin", "StopContainer", req, &resp); err != nil {
return nil, err
}
return &resp, nil
}
func (c *pluginClient) UpdatePodSandbox(ctx context.Context, req *UpdatePodSandboxRequest) (*UpdatePodSandboxResponse, error) {
var resp UpdatePodSandboxResponse
if err := c.client.Call(ctx, "nri.pkg.api.v1alpha1.Plugin", "UpdatePodSandbox", req, &resp); err != nil {
return nil, err
}
return &resp, nil
}
func (c *pluginClient) StateChange(ctx context.Context, req *StateChangeEvent) (*Empty, error) {
var resp Empty
if err := c.client.Call(ctx, "nri.pkg.api.v1alpha1.Plugin", "StateChange", req, &resp); err != nil {
return nil, err
}
return &resp, nil
}
func (c *pluginClient) ValidateContainerAdjustment(ctx context.Context, req *ValidateContainerAdjustmentRequest) (*ValidateContainerAdjustmentResponse, error) {
var resp ValidateContainerAdjustmentResponse
if err := c.client.Call(ctx, "nri.pkg.api.v1alpha1.Plugin", "ValidateContainerAdjustment", req, &resp); err != nil {
return nil, err
}
return &resp, nil
}
type HostFunctionsService interface {
Log(context.Context, *LogRequest) (*Empty, error)
}
func RegisterHostFunctionsService(srv *ttrpc.Server, svc HostFunctionsService) {
srv.RegisterService("nri.pkg.api.v1alpha1.HostFunctions", &ttrpc.ServiceDesc{
Methods: map[string]ttrpc.Method{
"Log": func(ctx context.Context, unmarshal func(interface{}) error) (interface{}, error) {
var req LogRequest
if err := unmarshal(&req); err != nil {
return nil, err
}
return svc.Log(ctx, &req)
},
},
})
}
type hostFunctionsClient struct {
client *ttrpc.Client
}
func NewHostFunctionsClient(client *ttrpc.Client) HostFunctionsService {
return &hostFunctionsClient{
client: client,
}
}
func (c *hostFunctionsClient) Log(ctx context.Context, req *LogRequest) (*Empty, error) {
var resp Empty
if err := c.client.Call(ctx, "nri.pkg.api.v1alpha1.HostFunctions", "Log", req, &resp); err != nil {
return nil, err
}
return &resp, nil
}

15865
vendor/github.com/containerd/nri/pkg/api/api_vtproto.pb.go generated vendored Normal file

File diff suppressed because it is too large Load Diff

43
vendor/github.com/containerd/nri/pkg/api/container.go generated vendored Normal file
View File

@@ -0,0 +1,43 @@
/*
Copyright The containerd Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package api
import "time"
func (x *Container) GetCreatedAtTime() time.Time {
t := time.Time{}
if x != nil {
return t.Add(time.Duration(x.CreatedAt) * time.Nanosecond)
}
return t
}
func (x *Container) GetStartedAtTime() time.Time {
t := time.Time{}
if x != nil {
return t.Add(time.Duration(x.StartedAt) * time.Nanosecond)
}
return t
}
func (x *Container) GetFinishedAtTime() time.Time {
t := time.Time{}
if x != nil {
return t.Add(time.Duration(x.FinishedAt) * time.Nanosecond)
}
return t
}

89
vendor/github.com/containerd/nri/pkg/api/device.go generated vendored Normal file
View File

@@ -0,0 +1,89 @@
/*
Copyright The containerd Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package api
import (
rspec "github.com/opencontainers/runtime-spec/specs-go"
)
// FromOCILinuxDevices returns a device slice from an OCI runtime Spec.
func FromOCILinuxDevices(o []rspec.LinuxDevice) []*LinuxDevice {
var devices []*LinuxDevice
for _, d := range o {
devices = append(devices, &LinuxDevice{
Path: d.Path,
Type: d.Type,
Major: d.Major,
Minor: d.Minor,
FileMode: FileMode(d.FileMode),
Uid: UInt32(d.UID),
Gid: UInt32(d.GID),
})
}
return devices
}
// ToOCI returns the linux devices for an OCI runtime Spec.
func (d *LinuxDevice) ToOCI() rspec.LinuxDevice {
if d == nil {
return rspec.LinuxDevice{}
}
return rspec.LinuxDevice{
Path: d.Path,
Type: d.Type,
Major: d.Major,
Minor: d.Minor,
FileMode: d.FileMode.Get(),
UID: d.Uid.Get(),
GID: d.Gid.Get(),
}
}
// AccessString returns an OCI access string for the device.
func (d *LinuxDevice) AccessString() string {
r, w, m := "r", "w", ""
if mode := d.FileMode.Get(); mode != nil {
perm := mode.Perm()
if (perm & 0444) != 0 {
r = "r"
}
if (perm & 0222) != 0 {
w = "w"
}
}
if d.Type == "b" {
m = "m"
}
return r + w + m
}
// Cmp returns true if the devices are equal.
func (d *LinuxDevice) Cmp(v *LinuxDevice) bool {
if v == nil {
return false
}
return d.Major != v.Major || d.Minor != v.Minor
}
// IsMarkedForRemoval checks if a LinuxDevice is marked for removal.
func (d *LinuxDevice) IsMarkedForRemoval() (string, bool) {
key, marked := IsMarkedForRemoval(d.Path)
return key, marked
}

17
vendor/github.com/containerd/nri/pkg/api/doc.go generated vendored Normal file
View File

@@ -0,0 +1,17 @@
/*
Copyright The containerd Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package api

60
vendor/github.com/containerd/nri/pkg/api/env.go generated vendored Normal file
View File

@@ -0,0 +1,60 @@
/*
Copyright The containerd Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package api
import (
"strings"
)
// ToOCI returns an OCI Env entry for the KeyValue.
func (e *KeyValue) ToOCI() string {
return e.Key + "=" + e.Value
}
// FromOCIEnv returns KeyValues from an OCI runtime Spec environment.
func FromOCIEnv(in []string) []*KeyValue {
if in == nil {
return nil
}
out := []*KeyValue{}
for _, keyval := range in {
var key, val string
split := strings.SplitN(keyval, "=", 2)
switch len(split) {
case 0:
continue
case 1:
key = split[0]
case 2:
key = split[0]
val = split[1]
default:
val = strings.Join(split[1:], "=")
}
out = append(out, &KeyValue{
Key: key,
Value: val,
})
}
return out
}
// IsMarkedForRemoval checks if an environment variable is marked for removal.
func (e *KeyValue) IsMarkedForRemoval() (string, bool) {
key, marked := IsMarkedForRemoval(e.Key)
return key, marked
}

180
vendor/github.com/containerd/nri/pkg/api/event.go generated vendored Normal file
View File

@@ -0,0 +1,180 @@
/*
Copyright The containerd Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package api
import (
"fmt"
"strings"
)
const (
// ValidEvents is the event mask of all valid events.
ValidEvents = EventMask((1 << (Event_LAST - 1)) - 1)
)
// nolint
type (
// Define *Request/*Response type aliases for *Event/Empty pairs.
StateChangeResponse = Empty
RunPodSandboxRequest = StateChangeEvent
RunPodSandboxResponse = Empty
StopPodSandboxRequest = StateChangeEvent
StopPodSandboxResponse = Empty
RemovePodSandboxRequest = StateChangeEvent
RemovePodSandboxResponse = Empty
PostUpdatePodSandboxRequest = StateChangeEvent
PostUpdatePodSandboxResponse = Empty
StartContainerRequest = StateChangeEvent
StartContainerResponse = Empty
RemoveContainerRequest = StateChangeEvent
RemoveContainerResponse = Empty
PostCreateContainerRequest = StateChangeEvent
PostCreateContainerResponse = Empty
PostStartContainerRequest = StateChangeEvent
PostStartContainerResponse = Empty
PostUpdateContainerRequest = StateChangeEvent
PostUpdateContainerResponse = Empty
ShutdownRequest = Empty
ShutdownResponse = Empty
)
// EventMask corresponds to a set of enumerated Events.
type EventMask int32
// ParseEventMask parses a string representation into an EventMask.
func ParseEventMask(events ...string) (EventMask, error) {
var mask EventMask
bits := map[string]Event{
"runpodsandbox": Event_RUN_POD_SANDBOX,
"updatepodsandbox": Event_UPDATE_POD_SANDBOX,
"postupdatepodsandbox": Event_POST_UPDATE_POD_SANDBOX,
"stoppodsandbox": Event_STOP_POD_SANDBOX,
"removepodsandbox": Event_REMOVE_POD_SANDBOX,
"createcontainer": Event_CREATE_CONTAINER,
"postcreatecontainer": Event_POST_CREATE_CONTAINER,
"startcontainer": Event_START_CONTAINER,
"poststartcontainer": Event_POST_START_CONTAINER,
"updatecontainer": Event_UPDATE_CONTAINER,
"postupdatecontainer": Event_POST_UPDATE_CONTAINER,
"stopcontainer": Event_STOP_CONTAINER,
"removecontainer": Event_REMOVE_CONTAINER,
"validatecontaineradjustment": Event_VALIDATE_CONTAINER_ADJUSTMENT,
}
for _, event := range events {
lcEvents := strings.ToLower(event)
for _, name := range strings.Split(lcEvents, ",") {
switch name {
case "all":
mask |= ValidEvents
continue
case "pod", "podsandbox":
for name, bit := range bits {
if strings.Contains(name, "pod") {
mask.Set(bit)
}
}
continue
case "container":
for name, bit := range bits {
if strings.Contains(name, "container") {
mask.Set(bit)
}
}
continue
}
bit, ok := bits[strings.TrimSpace(name)]
if !ok {
return 0, fmt.Errorf("unknown event %q", name)
}
mask.Set(bit)
}
}
return mask, nil
}
// MustParseEventMask parses the given events, panic()ing on errors.
func MustParseEventMask(events ...string) EventMask {
mask, err := ParseEventMask(events...)
if err != nil {
panic(fmt.Sprintf("failed to parse events %s", strings.Join(events, " ")))
}
return mask
}
// PrettyString returns a human-readable string representation of an EventMask.
func (m *EventMask) PrettyString() string {
names := map[Event]string{
Event_RUN_POD_SANDBOX: "RunPodSandbox",
Event_UPDATE_POD_SANDBOX: "UpdatePodSandbox",
Event_POST_UPDATE_POD_SANDBOX: "PostUpdatePodSandbox",
Event_STOP_POD_SANDBOX: "StopPodSandbox",
Event_REMOVE_POD_SANDBOX: "RemovePodSandbox",
Event_CREATE_CONTAINER: "CreateContainer",
Event_POST_CREATE_CONTAINER: "PostCreateContainer",
Event_START_CONTAINER: "StartContainer",
Event_POST_START_CONTAINER: "PostStartContainer",
Event_UPDATE_CONTAINER: "UpdateContainer",
Event_POST_UPDATE_CONTAINER: "PostUpdateContainer",
Event_STOP_CONTAINER: "StopContainer",
Event_REMOVE_CONTAINER: "RemoveContainer",
Event_VALIDATE_CONTAINER_ADJUSTMENT: "ValidateContainerAdjustment",
}
mask := *m
events, sep := "", ""
for bit := Event_UNKNOWN + 1; bit <= Event_LAST; bit++ {
if mask.IsSet(bit) {
events += sep + names[bit]
sep = ","
mask.Clear(bit)
}
}
if mask != 0 {
events += sep + fmt.Sprintf("unknown(0x%x)", mask)
}
return events
}
// Set sets the given Events in the mask.
func (m *EventMask) Set(events ...Event) *EventMask {
for _, e := range events {
*m |= (1 << (e - 1))
}
return m
}
// Clear clears the given Events in the mask.
func (m *EventMask) Clear(events ...Event) *EventMask {
for _, e := range events {
*m &^= (1 << (e - 1))
}
return m
}
// IsSet check if the given Event is set in the mask.
func (m *EventMask) IsSet(e Event) bool {
return *m&(1<<(e-1)) != 0
}

71
vendor/github.com/containerd/nri/pkg/api/helpers.go generated vendored Normal file
View File

@@ -0,0 +1,71 @@
/*
Copyright The containerd Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package api
// DupStringSlice creates a copy of a string slice.
func DupStringSlice(in []string) []string {
if in == nil {
return nil
}
out := make([]string, len(in))
copy(out, in)
return out
}
// DupStringMap creates a copy of a map with string keys and values.
func DupStringMap(in map[string]string) map[string]string {
if in == nil {
return nil
}
out := map[string]string{}
for k, v := range in {
out[k] = v
}
return out
}
// IsMarkedForRemoval checks if a key is marked for removal.
//
// The key can be an annotation name, a mount container path, a device path,
// a namespace type, or an environment variable name.
// These are all marked for removal in adjustments by preceding
// their corresponding key with a '-'.
func IsMarkedForRemoval(key string) (string, bool) {
if key == "" {
return "", false
}
if key[0] != '-' {
return key, false
}
return key[1:], true
}
// MarkForRemoval returns a key marked for removal.
func MarkForRemoval(key string) string {
return "-" + key
}
// ClearRemovalMarker returns a key cleared from any removal marker.
func ClearRemovalMarker(key string) string {
if key == "" {
return ""
}
if key[0] == '-' {
return key[1:]
}
return key
}

103
vendor/github.com/containerd/nri/pkg/api/hooks.go generated vendored Normal file
View File

@@ -0,0 +1,103 @@
/*
Copyright The containerd Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package api
import (
rspec "github.com/opencontainers/runtime-spec/specs-go"
)
// Append appends the given hooks to the existing ones.
func (hooks *Hooks) Append(h *Hooks) *Hooks {
if h == nil {
return hooks
}
hooks.Prestart = append(hooks.Prestart, h.Prestart...)
hooks.CreateRuntime = append(hooks.CreateRuntime, h.CreateRuntime...)
hooks.CreateContainer = append(hooks.CreateContainer, h.CreateContainer...)
hooks.StartContainer = append(hooks.StartContainer, h.StartContainer...)
hooks.Poststart = append(hooks.Poststart, h.Poststart...)
hooks.Poststop = append(hooks.Poststop, h.Poststop...)
return hooks
}
// Hooks returns itself it any of its hooks is set. Otherwise it returns nil.
func (hooks *Hooks) Hooks() *Hooks {
if hooks == nil {
return nil
}
if len(hooks.Prestart) > 0 {
return hooks
}
if len(hooks.CreateRuntime) > 0 {
return hooks
}
if len(hooks.CreateContainer) > 0 {
return hooks
}
if len(hooks.StartContainer) > 0 {
return hooks
}
if len(hooks.Poststart) > 0 {
return hooks
}
if len(hooks.Poststop) > 0 {
return hooks
}
return nil
}
// ToOCI returns the hook for an OCI runtime Spec.
func (h *Hook) ToOCI() rspec.Hook {
return rspec.Hook{
Path: h.Path,
Args: DupStringSlice(h.Args),
Env: DupStringSlice(h.Env),
Timeout: h.Timeout.Get(),
}
}
// FromOCIHooks returns hooks from an OCI runtime Spec.
func FromOCIHooks(o *rspec.Hooks) *Hooks {
if o == nil {
return nil
}
return &Hooks{
Prestart: FromOCIHookSlice(o.Prestart),
CreateRuntime: FromOCIHookSlice(o.CreateRuntime),
CreateContainer: FromOCIHookSlice(o.CreateContainer),
StartContainer: FromOCIHookSlice(o.StartContainer),
Poststart: FromOCIHookSlice(o.Poststart),
Poststop: FromOCIHookSlice(o.Poststop),
}
}
// FromOCIHookSlice returns a hook slice from an OCI runtime Spec.
func FromOCIHookSlice(o []rspec.Hook) []*Hook {
var hooks []*Hook
for _, h := range o {
hooks = append(hooks, &Hook{
Path: h.Path,
Args: DupStringSlice(h.Args),
Env: DupStringSlice(h.Env),
Timeout: Int(h.Timeout),
})
}
return hooks
}

63
vendor/github.com/containerd/nri/pkg/api/ioprio.go generated vendored Normal file
View File

@@ -0,0 +1,63 @@
/*
Copyright The containerd Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package api
import (
rspec "github.com/opencontainers/runtime-spec/specs-go"
)
// FromOCILinuxIOPriority returns a LinuxIOPriority corresponding to the
// OCI LinuxIOPriority.
func FromOCILinuxIOPriority(o *rspec.LinuxIOPriority) *LinuxIOPriority {
if o == nil {
return nil
}
ioprio := &LinuxIOPriority{
Class: FromOCIIOPriorityClass(o.Class),
Priority: int32(o.Priority),
}
return ioprio
}
// ToOCI returns the OCI LinuxIOPriority corresponding to the LinuxIOPriority.
func (ioprio *LinuxIOPriority) ToOCI() *rspec.LinuxIOPriority {
if ioprio == nil {
return nil
}
return &rspec.LinuxIOPriority{
Class: ioprio.Class.ToOCI(),
Priority: int(ioprio.Priority),
}
}
// FromOCIIOPrioClass returns the IOPrioClass corresponding the the given
// OCI IOPriorityClass.
func FromOCIIOPriorityClass(o rspec.IOPriorityClass) IOPrioClass {
return IOPrioClass(IOPrioClass_value[string(o)])
}
// ToOCI returns the OCI IOPriorityClass corresponding to the given
// IOPrioClass.
func (c IOPrioClass) ToOCI() rspec.IOPriorityClass {
if c == IOPrioClass_IOPRIO_CLASS_NONE {
return rspec.IOPriorityClass("")
}
return rspec.IOPriorityClass(IOPrioClass_name[int32(c)])
}

88
vendor/github.com/containerd/nri/pkg/api/mount.go generated vendored Normal file
View File

@@ -0,0 +1,88 @@
/*
Copyright The containerd Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package api
import (
"sort"
rspec "github.com/opencontainers/runtime-spec/specs-go"
)
const (
// SELinuxRelabel is a Mount pseudo-option to request relabeling.
SELinuxRelabel = "relabel"
)
// FromOCIMounts returns a Mount slice for an OCI runtime Spec.
func FromOCIMounts(o []rspec.Mount) []*Mount {
var mounts []*Mount
for _, m := range o {
mounts = append(mounts, &Mount{
Destination: m.Destination,
Type: m.Type,
Source: m.Source,
Options: DupStringSlice(m.Options),
})
}
return mounts
}
// ToOCI returns a Mount for an OCI runtime Spec.
func (m *Mount) ToOCI(propagationQuery *string) rspec.Mount {
o := rspec.Mount{
Destination: m.Destination,
Type: m.Type,
Source: m.Source,
}
for _, opt := range m.Options {
o.Options = append(o.Options, opt)
if propagationQuery != nil && (opt == "rprivate" || opt == "rshared" || opt == "rslave") {
*propagationQuery = opt
}
}
return o
}
// Cmp returns true if the mounts are equal.
func (m *Mount) Cmp(v *Mount) bool {
if v == nil {
return false
}
if m.Destination != v.Destination || m.Type != v.Type || m.Source != v.Source ||
len(m.Options) != len(v.Options) {
return false
}
mOpts := make([]string, len(m.Options))
vOpts := make([]string, len(m.Options))
sort.Strings(mOpts)
sort.Strings(vOpts)
for i, o := range mOpts {
if vOpts[i] != o {
return false
}
}
return true
}
// IsMarkedForRemoval checks if a Mount is marked for removal.
func (m *Mount) IsMarkedForRemoval() (string, bool) {
key, marked := IsMarkedForRemoval(m.Destination)
return key, marked
}

38
vendor/github.com/containerd/nri/pkg/api/namespace.go generated vendored Normal file
View File

@@ -0,0 +1,38 @@
/*
Copyright The containerd Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package api
import (
rspec "github.com/opencontainers/runtime-spec/specs-go"
)
// FromOCILinuxNamespaces returns a namespace slice from an OCI runtime Spec.
func FromOCILinuxNamespaces(o []rspec.LinuxNamespace) []*LinuxNamespace {
var namespaces []*LinuxNamespace
for _, ns := range o {
namespaces = append(namespaces, &LinuxNamespace{
Type: string(ns.Type),
Path: ns.Path,
})
}
return namespaces
}
// IsMarkedForRemoval checks if a LinuxNamespace is marked for removal.
func (n *LinuxNamespace) IsMarkedForRemoval() (string, bool) {
return IsMarkedForRemoval(n.Type)
}

341
vendor/github.com/containerd/nri/pkg/api/optional.go generated vendored Normal file
View File

@@ -0,0 +1,341 @@
/*
Copyright The containerd Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package api
import (
"os"
)
//
// XXX FIXME:
//
// The optional interface constructor should be updated/split up
// to avoid having to take an interface{} argument. Instead The
// optional types should have a
// - constructor taking the underlying native type
// - a Copy() function for copying them
// - a FromPointer constructor to create them from an optionally nil
// pointer to the underlying native type (to help constructing from
// structures that use a pointer to the native underlying type to
// denote optionality (OCI Spec mostly))
// Creating from any other type should use one of these with any explicit
// cast for the argument as necessary.
//
// String creates an Optional wrapper from its argument.
func String(v interface{}) *OptionalString {
var value string
switch o := v.(type) {
case string:
value = o
case *string:
if o == nil {
return nil
}
value = *o
case *OptionalString:
if o == nil {
return nil
}
value = o.Value
default:
return nil
}
return &OptionalString{
Value: value,
}
}
// Get returns nil if its value is unset or a pointer to the value itself.
func (o *OptionalString) Get() *string {
if o == nil {
return nil
}
v := o.Value
return &v
}
// Int creates an Optional wrapper from its argument.
func Int(v interface{}) *OptionalInt {
var value int64
switch o := v.(type) {
case int:
value = int64(o)
case *int:
if o == nil {
return nil
}
value = int64(*o)
case *OptionalInt:
if o == nil {
return nil
}
value = o.Value
default:
return nil
}
return &OptionalInt{
Value: value,
}
}
// Get returns nil if its value is unset or a pointer to the value itself.
func (o *OptionalInt) Get() *int {
if o == nil {
return nil
}
v := int(o.Value)
return &v
}
// Int32 creates an Optional wrapper from its argument.
func Int32(v interface{}) *OptionalInt32 {
var value int32
switch o := v.(type) {
case int32:
value = o
case *int32:
if o == nil {
return nil
}
value = *o
case *OptionalInt32:
if o == nil {
return nil
}
value = o.Value
default:
return nil
}
return &OptionalInt32{
Value: value,
}
}
// Get returns nil if its value is unset or a pointer to the value itself.
func (o *OptionalInt32) Get() *int32 {
if o == nil {
return nil
}
v := o.Value
return &v
}
// UInt32 creates an Optional wrapper from its argument.
func UInt32(v interface{}) *OptionalUInt32 {
var value uint32
switch o := v.(type) {
case uint32:
value = o
case *uint32:
if o == nil {
return nil
}
value = *o
case *OptionalUInt32:
if o == nil {
return nil
}
value = o.Value
default:
return nil
}
return &OptionalUInt32{
Value: value,
}
}
// Get returns nil if its value is unset or a pointer to the value itself.
func (o *OptionalUInt32) Get() *uint32 {
if o == nil {
return nil
}
v := o.Value
return &v
}
// Int64 creates an Optional wrapper from its argument.
func Int64(v interface{}) *OptionalInt64 {
var value int64
switch o := v.(type) {
case int:
value = int64(o)
case uint:
value = int64(o)
case uint64:
value = int64(o)
case int64:
value = o
case *int64:
if o == nil {
return nil
}
value = *o
case *uint64:
if o == nil {
return nil
}
value = int64(*o)
case *OptionalInt64:
if o == nil {
return nil
}
value = o.Value
default:
return nil
}
return &OptionalInt64{
Value: value,
}
}
// Get returns nil if its value is unset or a pointer to the value itself.
func (o *OptionalInt64) Get() *int64 {
if o == nil {
return nil
}
v := o.Value
return &v
}
// UInt64 creates an Optional wrapper from its argument.
func UInt64(v interface{}) *OptionalUInt64 {
var value uint64
switch o := v.(type) {
case int:
value = uint64(o)
case uint:
value = uint64(o)
case int64:
value = uint64(o)
case uint64:
value = o
case *int64:
if o == nil {
return nil
}
value = uint64(*o)
case *uint64:
if o == nil {
return nil
}
value = *o
case *OptionalUInt64:
if o == nil {
return nil
}
value = o.Value
default:
return nil
}
return &OptionalUInt64{
Value: value,
}
}
// Get returns nil if its value is unset or a pointer to the value itself.
func (o *OptionalUInt64) Get() *uint64 {
if o == nil {
return nil
}
v := o.Value
return &v
}
// Bool creates an Optional wrapper from its argument.
func Bool(v interface{}) *OptionalBool {
var value bool
switch o := v.(type) {
case bool:
value = o
case *bool:
if o == nil {
return nil
}
value = *o
case *OptionalBool:
if o == nil {
return nil
}
value = o.Value
default:
return nil
}
return &OptionalBool{
Value: value,
}
}
// Get returns nil if its value is unset or a pointer to the value itself.
func (o *OptionalBool) Get() *bool {
if o == nil {
return nil
}
v := o.Value
return &v
}
// FileMode creates an Optional wrapper from its argument.
func FileMode(v interface{}) *OptionalFileMode {
var value os.FileMode
switch o := v.(type) {
case *os.FileMode:
if o == nil {
return nil
}
value = *o
case os.FileMode:
value = o
case *OptionalFileMode:
if o == nil {
return nil
}
value = os.FileMode(o.Value)
case uint32:
value = os.FileMode(o)
default:
return nil
}
return &OptionalFileMode{
Value: uint32(value),
}
}
// Get returns nil if its value is unset or a pointer to the value itself.
func (o *OptionalFileMode) Get() *os.FileMode {
if o == nil {
return nil
}
v := os.FileMode(o.Value)
return &v
}

749
vendor/github.com/containerd/nri/pkg/api/owners.go generated vendored Normal file
View File

@@ -0,0 +1,749 @@
/*
Copyright The containerd Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package api
import (
"fmt"
"strings"
)
//
// Notes:
// OwningPlugins, FieldOwners and CompoundFieldOwners are not protected
// against concurrent access and therefore not goroutine safe.
//
// None of these functions are used by plugins directly. These are used by
// the runtime adaptation code to track container adjustments and updates
// requested by plugins, and to detect conflicting requests.
//
func NewOwningPlugins() *OwningPlugins {
return &OwningPlugins{
Owners: make(map[string]*FieldOwners),
}
}
func (o *OwningPlugins) ClaimAnnotation(id, key, plugin string) error {
return o.mustOwnersFor(id).ClaimAnnotation(key, plugin)
}
func (o *OwningPlugins) ClaimMount(id, destination, plugin string) error {
return o.mustOwnersFor(id).ClaimMount(destination, plugin)
}
func (o *OwningPlugins) ClaimHooks(id, plugin string) error {
return o.mustOwnersFor(id).ClaimHooks(plugin)
}
func (o *OwningPlugins) ClaimDevice(id, path, plugin string) error {
return o.mustOwnersFor(id).ClaimDevice(path, plugin)
}
func (o *OwningPlugins) ClaimNamespace(id, typ, plugin string) error {
return o.mustOwnersFor(id).ClaimNamespace(typ, plugin)
}
func (o *OwningPlugins) ClaimCdiDevice(id, name, plugin string) error {
return o.mustOwnersFor(id).ClaimCdiDevice(name, plugin)
}
func (o *OwningPlugins) ClaimEnv(id, name, plugin string) error {
return o.mustOwnersFor(id).ClaimEnv(name, plugin)
}
func (o *OwningPlugins) ClaimArgs(id, plugin string) error {
return o.mustOwnersFor(id).ClaimArgs(plugin)
}
func (o *OwningPlugins) ClaimMemLimit(id, plugin string) error {
return o.mustOwnersFor(id).ClaimMemLimit(plugin)
}
func (o *OwningPlugins) ClaimMemReservation(id, plugin string) error {
return o.mustOwnersFor(id).ClaimMemReservation(plugin)
}
func (o *OwningPlugins) ClaimMemSwapLimit(id, plugin string) error {
return o.mustOwnersFor(id).ClaimMemSwapLimit(plugin)
}
func (o *OwningPlugins) ClaimMemKernelLimit(id, plugin string) error {
return o.mustOwnersFor(id).ClaimMemKernelLimit(plugin)
}
func (o *OwningPlugins) ClaimMemTCPLimit(id, plugin string) error {
return o.mustOwnersFor(id).ClaimMemTCPLimit(plugin)
}
func (o *OwningPlugins) ClaimMemSwappiness(id, plugin string) error {
return o.mustOwnersFor(id).ClaimMemSwappiness(plugin)
}
func (o *OwningPlugins) ClaimMemDisableOomKiller(id, plugin string) error {
return o.mustOwnersFor(id).ClaimMemDisableOomKiller(plugin)
}
func (o *OwningPlugins) ClaimMemUseHierarchy(id, plugin string) error {
return o.mustOwnersFor(id).ClaimMemUseHierarchy(plugin)
}
func (o *OwningPlugins) ClaimCPUShares(id, plugin string) error {
return o.mustOwnersFor(id).ClaimCPUShares(plugin)
}
func (o *OwningPlugins) ClaimCPUQuota(id, plugin string) error {
return o.mustOwnersFor(id).ClaimCPUQuota(plugin)
}
func (o *OwningPlugins) ClaimCPUPeriod(id, plugin string) error {
return o.mustOwnersFor(id).ClaimCPUPeriod(plugin)
}
func (o *OwningPlugins) ClaimCPURealtimeRuntime(id, plugin string) error {
return o.mustOwnersFor(id).ClaimCPURealtimeRuntime(plugin)
}
func (o *OwningPlugins) ClaimCPURealtimePeriod(id, plugin string) error {
return o.mustOwnersFor(id).ClaimCPURealtimePeriod(plugin)
}
func (o *OwningPlugins) ClaimCPUSetCPUs(id, plugin string) error {
return o.mustOwnersFor(id).ClaimCPUSetCPUs(plugin)
}
func (o *OwningPlugins) ClaimCPUSetMems(id, plugin string) error {
return o.mustOwnersFor(id).ClaimCPUSetMems(plugin)
}
func (o *OwningPlugins) ClaimPidsLimit(id, plugin string) error {
return o.mustOwnersFor(id).ClaimPidsLimit(plugin)
}
func (o *OwningPlugins) ClaimHugepageLimit(id, size, plugin string) error {
return o.mustOwnersFor(id).ClaimHugepageLimit(size, plugin)
}
func (o *OwningPlugins) ClaimBlockioClass(id, plugin string) error {
return o.mustOwnersFor(id).ClaimBlockioClass(plugin)
}
func (o *OwningPlugins) ClaimRdtClass(id, plugin string) error {
return o.mustOwnersFor(id).ClaimRdtClass(plugin)
}
func (o *OwningPlugins) ClaimCgroupsUnified(id, key, plugin string) error {
return o.mustOwnersFor(id).ClaimCgroupsUnified(key, plugin)
}
func (o *OwningPlugins) ClaimCgroupsPath(id, plugin string) error {
return o.mustOwnersFor(id).ClaimCgroupsPath(plugin)
}
func (o *OwningPlugins) ClaimOomScoreAdj(id, plugin string) error {
return o.mustOwnersFor(id).ClaimOomScoreAdj(plugin)
}
func (o *OwningPlugins) ClaimRlimit(id, typ, plugin string) error {
return o.mustOwnersFor(id).ClaimRlimit(typ, plugin)
}
func (o *OwningPlugins) ClaimIOPriority(id, plugin string) error {
return o.mustOwnersFor(id).ClaimIOPriority(plugin)
}
func (o *OwningPlugins) ClaimSeccompPolicy(id, plugin string) error {
return o.mustOwnersFor(id).ClaimSeccompPolicy(plugin)
}
func (o *OwningPlugins) ClearAnnotation(id, key, plugin string) {
o.mustOwnersFor(id).ClearAnnotation(key, plugin)
}
func (o *OwningPlugins) ClearMount(id, key, plugin string) {
o.mustOwnersFor(id).ClearMount(key, plugin)
}
func (o *OwningPlugins) ClearDevice(id, key, plugin string) {
o.mustOwnersFor(id).ClearDevice(key, plugin)
}
func (o *OwningPlugins) ClearEnv(id, key, plugin string) {
o.mustOwnersFor(id).ClearEnv(key, plugin)
}
func (o *OwningPlugins) ClearArgs(id, plugin string) {
o.mustOwnersFor(id).ClearArgs(plugin)
}
func (o *OwningPlugins) AnnotationOwner(id, key string) (string, bool) {
return o.ownersFor(id).compoundOwner(Field_Annotations.Key(), key)
}
func (o *OwningPlugins) MountOwner(id, destination string) (string, bool) {
return o.ownersFor(id).compoundOwner(Field_Mounts.Key(), destination)
}
func (o *OwningPlugins) HooksOwner(id string) (string, bool) {
return o.ownersFor(id).simpleOwner(Field_OciHooks.Key())
}
func (o *OwningPlugins) DeviceOwner(id, path string) (string, bool) {
return o.ownersFor(id).compoundOwner(Field_Devices.Key(), path)
}
func (o *OwningPlugins) NamespaceOwner(id, path string) (string, bool) {
return o.ownersFor(id).compoundOwner(Field_Namespace.Key(), path)
}
func (o *OwningPlugins) NamespaceOwners(id string) (map[string]string, bool) {
return o.ownersFor(id).compoundOwnerMap(Field_Namespace.Key())
}
func (o *OwningPlugins) EnvOwner(id, name string) (string, bool) {
return o.ownersFor(id).compoundOwner(Field_Env.Key(), name)
}
func (o *OwningPlugins) ArgsOwner(id string) (string, bool) {
return o.ownersFor(id).simpleOwner(Field_Args.Key())
}
func (o *OwningPlugins) MemLimitOwner(id string) (string, bool) {
return o.ownersFor(id).simpleOwner(Field_MemLimit.Key())
}
func (o *OwningPlugins) MemReservationOwner(id string) (string, bool) {
return o.ownersFor(id).simpleOwner(Field_MemReservation.Key())
}
func (o *OwningPlugins) MemSwapLimitOwner(id string) (string, bool) {
return o.ownersFor(id).simpleOwner(Field_MemSwapLimit.Key())
}
func (o *OwningPlugins) MemKernelLimitOwner(id string) (string, bool) {
return o.ownersFor(id).simpleOwner(Field_MemKernelLimit.Key())
}
func (o *OwningPlugins) MemTCPLimitOwner(id string) (string, bool) {
return o.ownersFor(id).simpleOwner(Field_MemTCPLimit.Key())
}
func (o *OwningPlugins) MemSwappinessOwner(id string) (string, bool) {
return o.ownersFor(id).simpleOwner(Field_MemSwappiness.Key())
}
func (o *OwningPlugins) MemDisableOomKillerOwner(id string) (string, bool) {
return o.ownersFor(id).simpleOwner(Field_MemDisableOomKiller.Key())
}
func (o *OwningPlugins) MemUseHierarchyOwner(id string) (string, bool) {
return o.ownersFor(id).simpleOwner(Field_MemUseHierarchy.Key())
}
func (o *OwningPlugins) CPUSharesOwner(id string) (string, bool) {
return o.ownersFor(id).simpleOwner(Field_CPUShares.Key())
}
func (o *OwningPlugins) CPUQuotaOwner(id string) (string, bool) {
return o.ownersFor(id).simpleOwner(Field_CPUQuota.Key())
}
func (o *OwningPlugins) CPUPeriodOwner(id string) (string, bool) {
return o.ownersFor(id).simpleOwner(Field_CPUPeriod.Key())
}
func (o *OwningPlugins) CPURealtimeRuntimeOwner(id string) (string, bool) {
return o.ownersFor(id).simpleOwner(Field_CPURealtimeRuntime.Key())
}
func (o *OwningPlugins) CPURealtimePeriodOwner(id string) (string, bool) {
return o.ownersFor(id).simpleOwner(Field_CPURealtimePeriod.Key())
}
func (o *OwningPlugins) CPUSetCPUsOwner(id string) (string, bool) {
return o.ownersFor(id).simpleOwner(Field_CPUSetCPUs.Key())
}
func (o *OwningPlugins) CPUSetMemsOwner(id string) (string, bool) {
return o.ownersFor(id).simpleOwner(Field_CPUSetMems.Key())
}
func (o *OwningPlugins) PidsLimitOwner(id string) (string, bool) {
return o.ownersFor(id).simpleOwner(Field_PidsLimit.Key())
}
func (o *OwningPlugins) HugepageLimitOwner(id, size string) (string, bool) {
return o.ownersFor(id).compoundOwner(Field_HugepageLimits.Key(), size)
}
func (o *OwningPlugins) BlockioClassOwner(id string) (string, bool) {
return o.ownersFor(id).simpleOwner(Field_BlockioClass.Key())
}
func (o *OwningPlugins) RdtClassOwner(id string) (string, bool) {
return o.ownersFor(id).simpleOwner(Field_RdtClass.Key())
}
func (o *OwningPlugins) CgroupsUnifiedOwner(id, key string) (string, bool) {
return o.ownersFor(id).compoundOwner(Field_CgroupsUnified.Key(), key)
}
func (o *OwningPlugins) CgroupsPathOwner(id string) (string, bool) {
return o.ownersFor(id).simpleOwner(Field_CgroupsPath.Key())
}
func (o *OwningPlugins) OomScoreAdjOwner(id string) (string, bool) {
return o.ownersFor(id).simpleOwner(Field_OomScoreAdj.Key())
}
func (o *OwningPlugins) RlimitOwner(id, typ string) (string, bool) {
return o.ownersFor(id).compoundOwner(Field_Rlimits.Key(), typ)
}
func (o *OwningPlugins) IOPriorityOwner(id string) (string, bool) {
return o.ownersFor(id).simpleOwner(Field_IoPriority.Key())
}
func (o *OwningPlugins) SeccompPolicyOwner(id string) (string, bool) {
return o.ownersFor(id).simpleOwner(Field_SeccompPolicy.Key())
}
func (o *OwningPlugins) mustOwnersFor(id string) *FieldOwners {
f, ok := o.Owners[id]
if !ok {
f = NewFieldOwners()
o.Owners[id] = f
}
return f
}
func (o *OwningPlugins) ownersFor(id string) *FieldOwners {
f, ok := o.Owners[id]
if !ok {
return nil
}
return f
}
func NewFieldOwners() *FieldOwners {
return &FieldOwners{
Simple: make(map[int32]string),
Compound: make(map[int32]*CompoundFieldOwners),
}
}
func (f *FieldOwners) IsCompoundConflict(field int32, key, plugin string) error {
m, ok := f.Compound[field]
if !ok {
f.Compound[field] = NewCompoundFieldOwners()
return nil
}
other, claimed := m.Owners[key]
if !claimed {
return nil
}
clearer, ok := IsMarkedForRemoval(other)
if ok {
if clearer == plugin {
return nil
}
other = clearer
}
return f.Conflict(field, plugin, other, key)
}
func (f *FieldOwners) IsSimpleConflict(field int32, plugin string) error {
other, claimed := f.Simple[field]
if !claimed {
return nil
}
clearer, ok := IsMarkedForRemoval(other)
if ok {
if clearer == plugin {
return nil
}
other = clearer
}
return f.Conflict(field, plugin, other)
}
func (f *FieldOwners) claimCompound(field int32, entry, plugin string) error {
if err := f.IsCompoundConflict(field, entry, plugin); err != nil {
return err
}
f.Compound[field].Owners[entry] = plugin
return nil
}
func (f *FieldOwners) claimSimple(field int32, plugin string) error {
if err := f.IsSimpleConflict(field, plugin); err != nil {
return err
}
f.Simple[field] = plugin
return nil
}
func (f *FieldOwners) ClaimAnnotation(key, plugin string) error {
return f.claimCompound(Field_Annotations.Key(), key, plugin)
}
func (f *FieldOwners) ClaimMount(destination, plugin string) error {
return f.claimCompound(Field_Mounts.Key(), destination, plugin)
}
func (f *FieldOwners) ClaimHooks(plugin string) error {
plugins := plugin
if current, ok := f.simpleOwner(Field_OciHooks.Key()); ok {
f.clearSimple(Field_OciHooks.Key(), plugin)
plugins = current + "," + plugin
}
f.claimSimple(Field_OciHooks.Key(), plugins)
return nil
}
func (f *FieldOwners) ClaimDevice(path, plugin string) error {
return f.claimCompound(Field_Devices.Key(), path, plugin)
}
func (f *FieldOwners) ClaimCdiDevice(name, plugin string) error {
return f.claimCompound(Field_CdiDevices.Key(), name, plugin)
}
func (f *FieldOwners) ClaimNamespace(typ, plugin string) error {
return f.claimCompound(Field_Namespace.Key(), typ, plugin)
}
func (f *FieldOwners) ClaimEnv(name, plugin string) error {
return f.claimCompound(Field_Env.Key(), name, plugin)
}
func (f *FieldOwners) ClaimArgs(plugin string) error {
return f.claimSimple(Field_Args.Key(), plugin)
}
func (f *FieldOwners) ClaimMemLimit(plugin string) error {
return f.claimSimple(Field_MemLimit.Key(), plugin)
}
func (f *FieldOwners) ClaimMemReservation(plugin string) error {
return f.claimSimple(Field_MemReservation.Key(), plugin)
}
func (f *FieldOwners) ClaimMemSwapLimit(plugin string) error {
return f.claimSimple(Field_MemSwapLimit.Key(), plugin)
}
func (f *FieldOwners) ClaimMemKernelLimit(plugin string) error {
return f.claimSimple(Field_MemKernelLimit.Key(), plugin)
}
func (f *FieldOwners) ClaimMemTCPLimit(plugin string) error {
return f.claimSimple(Field_MemTCPLimit.Key(), plugin)
}
func (f *FieldOwners) ClaimMemSwappiness(plugin string) error {
return f.claimSimple(Field_MemSwappiness.Key(), plugin)
}
func (f *FieldOwners) ClaimMemDisableOomKiller(plugin string) error {
return f.claimSimple(Field_MemDisableOomKiller.Key(), plugin)
}
func (f *FieldOwners) ClaimMemUseHierarchy(plugin string) error {
return f.claimSimple(Field_MemUseHierarchy.Key(), plugin)
}
func (f *FieldOwners) ClaimCPUShares(plugin string) error {
return f.claimSimple(Field_CPUShares.Key(), plugin)
}
func (f *FieldOwners) ClaimCPUQuota(plugin string) error {
return f.claimSimple(Field_CPUQuota.Key(), plugin)
}
func (f *FieldOwners) ClaimCPUPeriod(plugin string) error {
return f.claimSimple(Field_CPUPeriod.Key(), plugin)
}
func (f *FieldOwners) ClaimCPURealtimeRuntime(plugin string) error {
return f.claimSimple(Field_CPURealtimeRuntime.Key(), plugin)
}
func (f *FieldOwners) ClaimCPURealtimePeriod(plugin string) error {
return f.claimSimple(Field_CPURealtimePeriod.Key(), plugin)
}
func (f *FieldOwners) ClaimCPUSetCPUs(plugin string) error {
return f.claimSimple(Field_CPUSetCPUs.Key(), plugin)
}
func (f *FieldOwners) ClaimCPUSetMems(plugin string) error {
return f.claimSimple(Field_CPUSetMems.Key(), plugin)
}
func (f *FieldOwners) ClaimPidsLimit(plugin string) error {
return f.claimSimple(Field_PidsLimit.Key(), plugin)
}
func (f *FieldOwners) ClaimHugepageLimit(size, plugin string) error {
return f.claimCompound(Field_HugepageLimits.Key(), size, plugin)
}
func (f *FieldOwners) ClaimBlockioClass(plugin string) error {
return f.claimSimple(Field_BlockioClass.Key(), plugin)
}
func (f *FieldOwners) ClaimRdtClass(plugin string) error {
return f.claimSimple(Field_RdtClass.Key(), plugin)
}
func (f *FieldOwners) ClaimCgroupsUnified(key, plugin string) error {
return f.claimCompound(Field_CgroupsUnified.Key(), key, plugin)
}
func (f *FieldOwners) ClaimCgroupsPath(plugin string) error {
return f.claimSimple(Field_CgroupsPath.Key(), plugin)
}
func (f *FieldOwners) ClaimOomScoreAdj(plugin string) error {
return f.claimSimple(Field_OomScoreAdj.Key(), plugin)
}
func (f *FieldOwners) ClaimRlimit(typ, plugin string) error {
return f.claimCompound(Field_Rlimits.Key(), typ, plugin)
}
func (f *FieldOwners) ClaimIOPriority(plugin string) error {
return f.claimSimple(Field_IoPriority.Key(), plugin)
}
func (f *FieldOwners) ClaimSeccompPolicy(plugin string) error {
return f.claimSimple(Field_SeccompPolicy.Key(), plugin)
}
func (f *FieldOwners) clearCompound(field int32, key, plugin string) {
m, ok := f.Compound[field]
if !ok {
m = NewCompoundFieldOwners()
f.Compound[field] = m
}
m.Owners[key] = MarkForRemoval(plugin)
}
func (f *FieldOwners) clearSimple(field int32, plugin string) {
f.Simple[field] = MarkForRemoval(plugin)
}
func (f *FieldOwners) ClearAnnotation(key, plugin string) {
f.clearCompound(Field_Annotations.Key(), key, plugin)
}
func (f *FieldOwners) ClearMount(destination, plugin string) {
f.clearCompound(Field_Mounts.Key(), destination, plugin)
}
func (f *FieldOwners) ClearDevice(path, plugin string) {
f.clearCompound(Field_Devices.Key(), path, plugin)
}
func (f *FieldOwners) ClearEnv(name, plugin string) {
f.clearCompound(Field_Env.Key(), name, plugin)
}
func (f *FieldOwners) ClearArgs(plugin string) {
f.clearSimple(Field_Args.Key(), plugin)
}
func (f *FieldOwners) Conflict(field int32, plugin, other string, qualifiers ...string) error {
return fmt.Errorf("plugins %q and %q both tried to set %s",
plugin, other, qualify(field, qualifiers...))
}
func (f *FieldOwners) compoundOwnerMap(field int32) (map[string]string, bool) {
if f == nil {
return nil, false
}
m, ok := f.Compound[field]
if !ok {
return nil, false
}
return m.Owners, true
}
func (f *FieldOwners) compoundOwner(field int32, key string) (string, bool) {
if f == nil {
return "", false
}
m, ok := f.Compound[field]
if !ok {
return "", false
}
plugin, ok := m.Owners[key]
return plugin, ok
}
func (f *FieldOwners) simpleOwner(field int32) (string, bool) {
if f == nil {
return "", false
}
plugin, ok := f.Simple[field]
return plugin, ok
}
func (f *FieldOwners) AnnotationOwner(key string) (string, bool) {
return f.compoundOwner(Field_Annotations.Key(), key)
}
func (f *FieldOwners) MountOwner(destination string) (string, bool) {
return f.compoundOwner(Field_Mounts.Key(), destination)
}
func (f *FieldOwners) DeviceOwner(path string) (string, bool) {
return f.compoundOwner(Field_Devices.Key(), path)
}
func (f *FieldOwners) NamespaceOwner(typ string) (string, bool) {
return f.compoundOwner(Field_Devices.Key(), typ)
}
func (f *FieldOwners) EnvOwner(name string) (string, bool) {
return f.compoundOwner(Field_Env.Key(), name)
}
func (f *FieldOwners) ArgsOwner() (string, bool) {
return f.simpleOwner(Field_Args.Key())
}
func (f *FieldOwners) MemLimitOwner() (string, bool) {
return f.simpleOwner(Field_MemLimit.Key())
}
func (f *FieldOwners) MemReservationOwner() (string, bool) {
return f.simpleOwner(Field_MemReservation.Key())
}
func (f *FieldOwners) MemSwapLimitOwner() (string, bool) {
return f.simpleOwner(Field_MemSwapLimit.Key())
}
func (f *FieldOwners) MemKernelLimitOwner() (string, bool) {
return f.simpleOwner(Field_MemKernelLimit.Key())
}
func (f *FieldOwners) MemTCPLimitOwner() (string, bool) {
return f.simpleOwner(Field_MemTCPLimit.Key())
}
func (f *FieldOwners) MemSwappinessOwner() (string, bool) {
return f.simpleOwner(Field_MemSwappiness.Key())
}
func (f *FieldOwners) MemDisableOomKillerOwner() (string, bool) {
return f.simpleOwner(Field_MemDisableOomKiller.Key())
}
func (f *FieldOwners) MemUseHierarchyOwner() (string, bool) {
return f.simpleOwner(Field_MemUseHierarchy.Key())
}
func (f *FieldOwners) CPUSharesOwner() (string, bool) {
return f.simpleOwner(Field_CPUShares.Key())
}
func (f *FieldOwners) CPUQuotaOwner() (string, bool) {
return f.simpleOwner(Field_CPUQuota.Key())
}
func (f *FieldOwners) CPUPeriodOwner() (string, bool) {
return f.simpleOwner(Field_CPUPeriod.Key())
}
func (f *FieldOwners) CPURealtimeRuntimeOwner() (string, bool) {
return f.simpleOwner(Field_CPURealtimeRuntime.Key())
}
func (f *FieldOwners) CPURealtimePeriodOwner() (string, bool) {
return f.simpleOwner(Field_CPURealtimePeriod.Key())
}
func (f *FieldOwners) CPUSetCPUsOwner() (string, bool) {
return f.simpleOwner(Field_CPUSetCPUs.Key())
}
func (f *FieldOwners) CPUSetMemsOwner() (string, bool) {
return f.simpleOwner(Field_CPUSetMems.Key())
}
func (f *FieldOwners) PidsLimitOwner() (string, bool) {
return f.simpleOwner(Field_PidsLimit.Key())
}
func (f *FieldOwners) HugepageLimitOwner(size string) (string, bool) {
return f.compoundOwner(Field_HugepageLimits.Key(), size)
}
func (f *FieldOwners) BlockioClassOwner() (string, bool) {
return f.simpleOwner(Field_BlockioClass.Key())
}
func (f *FieldOwners) RdtClassOwner() (string, bool) {
return f.simpleOwner(Field_RdtClass.Key())
}
func (f *FieldOwners) CgroupsUnifiedOwner(key string) (string, bool) {
return f.compoundOwner(Field_CgroupsUnified.Key(), key)
}
func (f *FieldOwners) CgroupsPathOwner() (string, bool) {
return f.simpleOwner(Field_CgroupsPath.Key())
}
func (f *FieldOwners) OomScoreAdjOwner() (string, bool) {
return f.simpleOwner(Field_OomScoreAdj.Key())
}
func (f *FieldOwners) RlimitOwner(typ string) (string, bool) {
return f.compoundOwner(Field_Rlimits.Key(), typ)
}
func qualify(field int32, qualifiers ...string) string {
return Field(field).String() + " " + strings.Join(append([]string{}, qualifiers...), " ")
}
func NewCompoundFieldOwners() *CompoundFieldOwners {
return &CompoundFieldOwners{
Owners: make(map[string]string),
}
}
func (f Field) Key() int32 {
return int32(f)
}

58
vendor/github.com/containerd/nri/pkg/api/plugin.go generated vendored Normal file
View File

@@ -0,0 +1,58 @@
/*
Copyright The containerd Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package api
import (
"fmt"
"strings"
)
const (
// DefaultSocketPath is the default socket path for external plugins.
DefaultSocketPath = "/var/run/nri/nri.sock"
// PluginSocketEnvVar is used to inform plugins about pre-connected sockets.
PluginSocketEnvVar = "NRI_PLUGIN_SOCKET"
// PluginNameEnvVar is used to inform NRI-launched plugins about their name.
PluginNameEnvVar = "NRI_PLUGIN_NAME"
// PluginIdxEnvVar is used to inform NRI-launched plugins about their ID.
PluginIdxEnvVar = "NRI_PLUGIN_IDX"
)
// ParsePluginName parses the (file)name of a plugin into an index and a base.
func ParsePluginName(name string) (string, string, error) {
split := strings.SplitN(name, "-", 2)
if len(split) < 2 {
return "", "", fmt.Errorf("invalid plugin name %q, idx-pluginname expected", name)
}
if err := CheckPluginIndex(split[0]); err != nil {
return "", "", err
}
return split[0], split[1], nil
}
// CheckPluginIndex checks the validity of a plugin index.
func CheckPluginIndex(idx string) error {
if len(idx) != 2 {
return fmt.Errorf("invalid plugin index %q, must be 2 digits", idx)
}
if !('0' <= idx[0] && idx[0] <= '9') || !('0' <= idx[1] && idx[1] <= '9') {
return fmt.Errorf("invalid plugin index %q (not [0-9][0-9])", idx)
}
return nil
}

203
vendor/github.com/containerd/nri/pkg/api/resources.go generated vendored Normal file
View File

@@ -0,0 +1,203 @@
//go:build !tinygo.wasm
/*
Copyright The containerd Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package api
import (
rspec "github.com/opencontainers/runtime-spec/specs-go"
)
// FromOCILinuxResources returns resources from an OCI runtime Spec.
func FromOCILinuxResources(o *rspec.LinuxResources, _ map[string]string) *LinuxResources {
if o == nil {
return nil
}
l := &LinuxResources{}
if m := o.Memory; m != nil {
l.Memory = &LinuxMemory{
Limit: Int64(m.Limit),
Reservation: Int64(m.Reservation),
Swap: Int64(m.Swap),
Kernel: Int64(m.Kernel),
KernelTcp: Int64(m.KernelTCP),
Swappiness: UInt64(m.Swappiness),
DisableOomKiller: Bool(m.DisableOOMKiller),
UseHierarchy: Bool(m.UseHierarchy),
}
}
if c := o.CPU; c != nil {
l.Cpu = &LinuxCPU{
Shares: UInt64(c.Shares),
Quota: Int64(c.Quota),
Period: UInt64(c.Period),
RealtimeRuntime: Int64(c.RealtimeRuntime),
RealtimePeriod: UInt64(c.RealtimePeriod),
Cpus: c.Cpus,
Mems: c.Mems,
}
}
for _, h := range o.HugepageLimits {
l.HugepageLimits = append(l.HugepageLimits, &HugepageLimit{
PageSize: h.Pagesize,
Limit: h.Limit,
})
}
for _, d := range o.Devices {
l.Devices = append(l.Devices, &LinuxDeviceCgroup{
Allow: d.Allow,
Type: d.Type,
Major: Int64(d.Major),
Minor: Int64(d.Minor),
Access: d.Access,
})
}
if p := o.Pids; p != nil {
l.Pids = &LinuxPids{
Limit: p.Limit,
}
}
if len(o.Unified) != 0 {
l.Unified = make(map[string]string)
for k, v := range o.Unified {
l.Unified[k] = v
}
}
return l
}
// ToOCI returns resources for an OCI runtime Spec.
func (r *LinuxResources) ToOCI() *rspec.LinuxResources {
if r == nil {
return nil
}
o := &rspec.LinuxResources{
CPU: &rspec.LinuxCPU{},
Memory: &rspec.LinuxMemory{},
}
if r.Memory != nil {
o.Memory = &rspec.LinuxMemory{
Limit: r.Memory.Limit.Get(),
Reservation: r.Memory.Reservation.Get(),
Swap: r.Memory.Swap.Get(),
Kernel: r.Memory.Kernel.Get(),
KernelTCP: r.Memory.KernelTcp.Get(),
Swappiness: r.Memory.Swappiness.Get(),
DisableOOMKiller: r.Memory.DisableOomKiller.Get(),
UseHierarchy: r.Memory.UseHierarchy.Get(),
}
}
if r.Cpu != nil {
o.CPU = &rspec.LinuxCPU{
Shares: r.Cpu.Shares.Get(),
Quota: r.Cpu.Quota.Get(),
Period: r.Cpu.Period.Get(),
RealtimeRuntime: r.Cpu.RealtimeRuntime.Get(),
RealtimePeriod: r.Cpu.RealtimePeriod.Get(),
Cpus: r.Cpu.Cpus,
Mems: r.Cpu.Mems,
}
}
for _, l := range r.HugepageLimits {
o.HugepageLimits = append(o.HugepageLimits, rspec.LinuxHugepageLimit{
Pagesize: l.PageSize,
Limit: l.Limit,
})
}
if len(r.Unified) != 0 {
o.Unified = make(map[string]string)
for k, v := range r.Unified {
o.Unified[k] = v
}
}
for _, d := range r.Devices {
o.Devices = append(o.Devices, rspec.LinuxDeviceCgroup{
Allow: d.Allow,
Type: d.Type,
Major: d.Major.Get(),
Minor: d.Minor.Get(),
Access: d.Access,
})
}
if r.Pids != nil {
o.Pids = &rspec.LinuxPids{
Limit: r.Pids.Limit,
}
}
return o
}
// Copy creates a copy of the resources.
func (r *LinuxResources) Copy() *LinuxResources {
if r == nil {
return nil
}
o := &LinuxResources{}
if r.Memory != nil {
o.Memory = &LinuxMemory{
Limit: Int64(r.Memory.GetLimit()),
Reservation: Int64(r.Memory.GetReservation()),
Swap: Int64(r.Memory.GetSwap()),
Kernel: Int64(r.Memory.GetKernel()),
KernelTcp: Int64(r.Memory.GetKernelTcp()),
Swappiness: UInt64(r.Memory.GetSwappiness()),
DisableOomKiller: Bool(r.Memory.GetDisableOomKiller()),
UseHierarchy: Bool(r.Memory.GetUseHierarchy()),
}
}
if r.Cpu != nil {
o.Cpu = &LinuxCPU{
Shares: UInt64(r.Cpu.GetShares()),
Quota: Int64(r.Cpu.GetQuota()),
Period: UInt64(r.Cpu.GetPeriod()),
RealtimeRuntime: Int64(r.Cpu.GetRealtimeRuntime()),
RealtimePeriod: UInt64(r.Cpu.GetRealtimePeriod()),
Cpus: r.Cpu.GetCpus(),
Mems: r.Cpu.GetMems(),
}
}
for _, l := range r.HugepageLimits {
o.HugepageLimits = append(o.HugepageLimits, &HugepageLimit{
PageSize: l.PageSize,
Limit: l.Limit,
})
}
if len(r.Unified) != 0 {
o.Unified = make(map[string]string)
for k, v := range r.Unified {
o.Unified[k] = v
}
}
if r.Pids != nil {
o.Pids = &LinuxPids{
Limit: r.Pids.Limit,
}
}
o.BlockioClass = String(r.BlockioClass)
o.RdtClass = String(r.RdtClass)
for _, d := range r.Devices {
o.Devices = append(o.Devices, &LinuxDeviceCgroup{
Allow: d.Allow,
Type: d.Type,
Access: d.Access,
Major: Int64(d.Major),
Minor: Int64(d.Minor),
})
}
return o
}

119
vendor/github.com/containerd/nri/pkg/api/seccomp.go generated vendored Normal file
View File

@@ -0,0 +1,119 @@
/*
Copyright The containerd Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package api
import (
rspec "github.com/opencontainers/runtime-spec/specs-go"
)
func FromOCILinuxSeccomp(o *rspec.LinuxSeccomp) *LinuxSeccomp {
var errno *OptionalUInt32
if o.DefaultErrnoRet != nil {
errno = &OptionalUInt32{Value: uint32(*o.DefaultErrnoRet)}
}
arches := make([]string, len(o.Architectures))
for i, arch := range o.Architectures {
arches[i] = string(arch)
}
flags := make([]string, len(o.Flags))
for i, flag := range o.Flags {
flags[i] = string(flag)
}
return &LinuxSeccomp{
DefaultAction: string(o.DefaultAction),
DefaultErrno: errno,
Architectures: arches,
Flags: flags,
ListenerPath: o.ListenerPath,
ListenerMetadata: o.ListenerMetadata,
Syscalls: FromOCILinuxSyscalls(o.Syscalls),
}
}
func FromOCILinuxSyscalls(o []rspec.LinuxSyscall) []*LinuxSyscall {
syscalls := make([]*LinuxSyscall, len(o))
for i, syscall := range o {
var errno *OptionalUInt32
if syscall.ErrnoRet != nil {
errno = &OptionalUInt32{Value: uint32(*syscall.ErrnoRet)}
}
syscalls[i] = &LinuxSyscall{
Names: syscall.Names,
Action: string(syscall.Action),
ErrnoRet: errno,
Args: FromOCILinuxSeccompArgs(syscall.Args),
}
}
return syscalls
}
func FromOCILinuxSeccompArgs(o []rspec.LinuxSeccompArg) []*LinuxSeccompArg {
args := make([]*LinuxSeccompArg, len(o))
for i, arg := range o {
args[i] = &LinuxSeccompArg{
Index: uint32(arg.Index),
Value: arg.Value,
ValueTwo: arg.ValueTwo,
Op: string(arg.Op),
}
}
return args
}
func ToOCILinuxSyscalls(o []*LinuxSyscall) []rspec.LinuxSyscall {
syscalls := make([]rspec.LinuxSyscall, len(o))
for i, syscall := range o {
var errnoRet *uint
if syscall.ErrnoRet != nil {
*errnoRet = uint(syscall.ErrnoRet.Value)
}
syscalls[i] = rspec.LinuxSyscall{
Names: syscall.Names,
Action: rspec.LinuxSeccompAction(syscall.Action),
ErrnoRet: errnoRet,
Args: ToOCILinuxSeccompArgs(syscall.Args),
}
}
return syscalls
}
func ToOCILinuxSeccompArgs(o []*LinuxSeccompArg) []rspec.LinuxSeccompArg {
args := make([]rspec.LinuxSeccompArg, len(o))
for i, arg := range o {
args[i] = rspec.LinuxSeccompArg{
Index: uint(arg.Index),
Value: arg.Value,
ValueTwo: arg.ValueTwo,
Op: rspec.LinuxSeccompOperator(arg.Op),
}
}
return args
}

343
vendor/github.com/containerd/nri/pkg/api/strip.go generated vendored Normal file
View File

@@ -0,0 +1,343 @@
/*
Copyright The containerd Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package api
//
// Notes:
// These stripping functions are used in tests to compare values for
// semantic equality using go-cmp. They reduce their receiver to a
// unique canonical representation by replacing empty slices, maps,
// and struct type fields by nil. These are destructive (IOW might
// alter the receiver) and should only be used for testing.
//
// TODO(klihub):
// Starting with 1.36.6, we could use protobuf/proto.CloneOf() to
// create a deep copy before stripping. However, we can't update
// beyond 1.35.2 yet, before all supported release branches of our
// downstream dependencies have bumped their direct dependencies.
// Strip empty fields from a container adjustment, reducing a fully empty
// one to nil. Strip allows comparison of two adjustments for semantic
// equality using go-cmp.
func (a *ContainerAdjustment) Strip() *ContainerAdjustment {
if a == nil {
return nil
}
empty := true
if len(a.Annotations) == 0 {
a.Annotations = nil
} else {
empty = false
}
if len(a.Mounts) == 0 {
a.Mounts = nil
} else {
empty = false
}
if len(a.Env) == 0 {
a.Env = nil
} else {
empty = false
}
if len(a.Rlimits) == 0 {
a.Rlimits = nil
} else {
empty = false
}
if len(a.CDIDevices) == 0 {
a.CDIDevices = nil
} else {
empty = false
}
if len(a.Args) == 0 {
a.Args = nil
} else {
empty = false
}
if a.Hooks = a.Hooks.Strip(); a.Hooks != nil {
empty = false
}
if a.Linux = a.Linux.Strip(); a.Linux != nil {
empty = false
}
if empty {
return nil
}
return a
}
// Strip empty fields from a linux container adjustment, reducing a fully
// empty one to nil. Strip allows comparison of two adjustments for semantic
// equality using go-cmp.
func (l *LinuxContainerAdjustment) Strip() *LinuxContainerAdjustment {
if l == nil {
return nil
}
empty := true
if len(l.Devices) == 0 {
l.Devices = nil
} else {
empty = false
}
if l.Resources = l.Resources.Strip(); l.Resources != nil {
empty = false
}
if l.CgroupsPath != "" {
empty = false
}
if l.OomScoreAdj != nil {
empty = false
}
if empty {
return nil
}
return l
}
// Strip empty fields from Hooks, reducing a fully empty one to nil. Strip
// allows comparison of two Hooks for semantic equality using go-cmp.
func (h *Hooks) Strip() *Hooks {
if h == nil {
return nil
}
empty := true
if len(h.Prestart) == 0 {
h.Prestart = nil
} else {
empty = false
}
if len(h.CreateRuntime) == 0 {
h.CreateRuntime = nil
} else {
empty = false
}
if len(h.CreateContainer) == 0 {
h.CreateContainer = nil
} else {
empty = false
}
if len(h.StartContainer) == 0 {
h.StartContainer = nil
} else {
empty = false
}
if len(h.Poststart) == 0 {
h.Poststart = nil
} else {
empty = false
}
if len(h.Poststop) == 0 {
h.Poststop = nil
} else {
empty = false
}
if empty {
return nil
}
return h
}
// Strip empty fields from a linux resources, reducing a fully empty one
// to nil. Strip allows comparison of two sets of resources for semantic
// equality using go-cmp.
func (r *LinuxResources) Strip() *LinuxResources {
if r == nil {
return nil
}
empty := true
if r.Memory = r.Memory.Strip(); r.Memory != nil {
empty = false
}
if r.Cpu = r.Cpu.Strip(); r.Cpu != nil {
empty = false
}
if len(r.HugepageLimits) == 0 {
r.HugepageLimits = nil
} else {
empty = false
}
if r.BlockioClass != nil {
empty = false
}
if r.RdtClass != nil {
empty = false
}
if len(r.Unified) == 0 {
r.Unified = nil
} else {
empty = false
}
if len(r.Devices) == 0 {
r.Devices = nil
} else {
empty = false
}
if r.Pids != nil {
empty = false
}
if empty {
return nil
}
return r
}
// Strip empty fields from linux CPU attributes, reducing a fully empty one
// to nil. Strip allows comparison of two sets of attributes for semantic
// equality using go-cmp.
func (c *LinuxCPU) Strip() *LinuxCPU {
if c == nil {
return nil
}
empty := true
if c.Shares != nil {
empty = false
}
if c.Quota != nil {
empty = false
}
if c.Period != nil {
empty = false
}
if c.RealtimeRuntime != nil {
empty = false
}
if c.RealtimePeriod != nil {
empty = false
}
if c.Cpus != "" {
empty = false
}
if c.Mems != "" {
empty = false
}
if empty {
return nil
}
return c
}
// Strip empty fields from linux memory attributes, reducing a fully empty
// one to nil. Strip allows comparison of two sets of attributes for semantic
// equality using go-cmp.
func (m *LinuxMemory) Strip() *LinuxMemory {
if m == nil {
return nil
}
empty := true
if m.Limit != nil {
empty = false
}
if m.Reservation != nil {
empty = false
}
if m.Swap != nil {
empty = false
}
if m.Kernel != nil {
empty = false
}
if m.KernelTcp != nil {
empty = false
}
if m.Swappiness != nil {
empty = false
}
if m.DisableOomKiller != nil {
empty = false
}
if m.UseHierarchy != nil {
empty = false
}
if empty {
return nil
}
return m
}
// Strip empty fields from a container update, reducing a fully empty one
// to nil. Strip allows comparison of two updates for semantic equality
// using go-cmp.
func (u *ContainerUpdate) Strip() *ContainerUpdate {
if u == nil {
return nil
}
empty := true
if u.Linux = u.Linux.Strip(); u.Linux != nil {
empty = false
}
if u.IgnoreFailure {
empty = false
}
if empty {
return nil
}
return u
}
// Strip empty fields from a linux container update, reducing a fully empty
// one to nil. Strip allows comparison of two updates for semantic equality
// using go-cmp.
func (l *LinuxContainerUpdate) Strip() *LinuxContainerUpdate {
if l == nil {
return nil
}
empty := true
if l.Resources = l.Resources.Strip(); l.Resources != nil {
empty = false
}
if empty {
return nil
}
return l
}

28
vendor/github.com/containerd/nri/pkg/api/timeouts.go generated vendored Normal file
View File

@@ -0,0 +1,28 @@
/*
Copyright The containerd Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package api
import (
"time"
)
const (
// DefaultPluginRegistrationTimeout is the default timeout for plugin registration.
DefaultPluginRegistrationTimeout = 5 * time.Second
// DefaultPluginRequestTimeout is the default timeout for plugins to handle a request.
DefaultPluginRequestTimeout = 2 * time.Second
)

199
vendor/github.com/containerd/nri/pkg/api/update.go generated vendored Normal file
View File

@@ -0,0 +1,199 @@
/*
Copyright The containerd Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package api
//nolint
// SetContainerId sets the id of the container to update.
func (u *ContainerUpdate) SetContainerId(id string) {
u.ContainerId = id
}
// SetLinuxMemoryLimit records setting the memory limit for a container.
func (u *ContainerUpdate) SetLinuxMemoryLimit(value int64) {
u.initLinuxResourcesMemory()
u.Linux.Resources.Memory.Limit = Int64(value)
}
// SetLinuxMemoryReservation records setting the memory reservation for a container.
func (u *ContainerUpdate) SetLinuxMemoryReservation(value int64) {
u.initLinuxResourcesMemory()
u.Linux.Resources.Memory.Reservation = Int64(value)
}
// SetLinuxMemorySwap records records setting the memory swap limit for a container.
func (u *ContainerUpdate) SetLinuxMemorySwap(value int64) {
u.initLinuxResourcesMemory()
u.Linux.Resources.Memory.Swap = Int64(value)
}
// SetLinuxMemoryKernel records setting the memory kernel limit for a container.
func (u *ContainerUpdate) SetLinuxMemoryKernel(value int64) {
u.initLinuxResourcesMemory()
u.Linux.Resources.Memory.Kernel = Int64(value)
}
// SetLinuxMemoryKernelTCP records setting the memory kernel TCP limit for a container.
func (u *ContainerUpdate) SetLinuxMemoryKernelTCP(value int64) {
u.initLinuxResourcesMemory()
u.Linux.Resources.Memory.KernelTcp = Int64(value)
}
// SetLinuxMemorySwappiness records setting the memory swappiness for a container.
func (u *ContainerUpdate) SetLinuxMemorySwappiness(value uint64) {
u.initLinuxResourcesMemory()
u.Linux.Resources.Memory.Swappiness = UInt64(value)
}
// SetLinuxMemoryDisableOomKiller records disabling the OOM killer for a container.
func (u *ContainerUpdate) SetLinuxMemoryDisableOomKiller() {
u.initLinuxResourcesMemory()
u.Linux.Resources.Memory.DisableOomKiller = Bool(true)
}
// SetLinuxMemoryUseHierarchy records enabling hierarchical memory accounting for a container.
func (u *ContainerUpdate) SetLinuxMemoryUseHierarchy() {
u.initLinuxResourcesMemory()
u.Linux.Resources.Memory.UseHierarchy = Bool(true)
}
// SetLinuxCPUShares records setting the scheduler's CPU shares for a container.
func (u *ContainerUpdate) SetLinuxCPUShares(value uint64) {
u.initLinuxResourcesCPU()
u.Linux.Resources.Cpu.Shares = UInt64(value)
}
// SetLinuxCPUQuota records setting the scheduler's CPU quota for a container.
func (u *ContainerUpdate) SetLinuxCPUQuota(value int64) {
u.initLinuxResourcesCPU()
u.Linux.Resources.Cpu.Quota = Int64(value)
}
// SetLinuxCPUPeriod records setting the scheduler's CPU period for a container.
func (u *ContainerUpdate) SetLinuxCPUPeriod(value int64) {
u.initLinuxResourcesCPU()
u.Linux.Resources.Cpu.Period = UInt64(value)
}
// SetLinuxCPURealtimeRuntime records setting the scheduler's realtime runtime for a container.
func (u *ContainerUpdate) SetLinuxCPURealtimeRuntime(value int64) {
u.initLinuxResourcesCPU()
u.Linux.Resources.Cpu.RealtimeRuntime = Int64(value)
}
// SetLinuxCPURealtimePeriod records setting the scheduler's realtime period for a container.
func (u *ContainerUpdate) SetLinuxCPURealtimePeriod(value uint64) {
u.initLinuxResourcesCPU()
u.Linux.Resources.Cpu.RealtimePeriod = UInt64(value)
}
// SetLinuxCPUSetCPUs records setting the cpuset CPUs for a container.
func (u *ContainerUpdate) SetLinuxCPUSetCPUs(value string) {
u.initLinuxResourcesCPU()
u.Linux.Resources.Cpu.Cpus = value
}
// SetLinuxCPUSetMems records setting the cpuset memory for a container.
func (u *ContainerUpdate) SetLinuxCPUSetMems(value string) {
u.initLinuxResourcesCPU()
u.Linux.Resources.Cpu.Mems = value
}
// SetLinuxPidLimits records setting the pid max number for a container.
func (u *ContainerUpdate) SetLinuxPidLimits(value int64) {
u.initLinuxResourcesPids()
u.Linux.Resources.Pids.Limit = value
}
// AddLinuxHugepageLimit records adding a hugepage limit for a container.
func (u *ContainerUpdate) AddLinuxHugepageLimit(pageSize string, value uint64) {
u.initLinuxResources()
u.Linux.Resources.HugepageLimits = append(u.Linux.Resources.HugepageLimits,
&HugepageLimit{
PageSize: pageSize,
Limit: value,
})
}
// SetLinuxBlockIOClass records setting the Block I/O class for a container.
func (u *ContainerUpdate) SetLinuxBlockIOClass(value string) {
u.initLinuxResources()
u.Linux.Resources.BlockioClass = String(value)
}
// SetLinuxRDTClass records setting the RDT class for a container.
func (u *ContainerUpdate) SetLinuxRDTClass(value string) {
u.initLinuxResources()
u.Linux.Resources.RdtClass = String(value)
}
// AddLinuxUnified sets a cgroupv2 unified resource.
func (u *ContainerUpdate) AddLinuxUnified(key, value string) {
u.initLinuxResourcesUnified()
u.Linux.Resources.Unified[key] = value
}
// SetIgnoreFailure marks an Update as ignored for failures.
// Such updates will not prevent the related container operation
// from succeeding if the update fails.
func (u *ContainerUpdate) SetIgnoreFailure() {
u.IgnoreFailure = true
}
//
// Initializing a container update.
//
func (u *ContainerUpdate) initLinux() {
if u.Linux == nil {
u.Linux = &LinuxContainerUpdate{}
}
}
func (u *ContainerUpdate) initLinuxResources() {
u.initLinux()
if u.Linux.Resources == nil {
u.Linux.Resources = &LinuxResources{}
}
}
func (u *ContainerUpdate) initLinuxResourcesMemory() {
u.initLinuxResources()
if u.Linux.Resources.Memory == nil {
u.Linux.Resources.Memory = &LinuxMemory{}
}
}
func (u *ContainerUpdate) initLinuxResourcesCPU() {
u.initLinuxResources()
if u.Linux.Resources.Cpu == nil {
u.Linux.Resources.Cpu = &LinuxCPU{}
}
}
func (u *ContainerUpdate) initLinuxResourcesUnified() {
u.initLinuxResources()
if u.Linux.Resources.Unified == nil {
u.Linux.Resources.Unified = make(map[string]string)
}
}
func (u *ContainerUpdate) initLinuxResourcesPids() {
u.initLinuxResources()
if u.Linux.Resources.Pids == nil {
u.Linux.Resources.Pids = &LinuxPids{}
}
}

64
vendor/github.com/containerd/nri/pkg/api/validate.go generated vendored Normal file
View File

@@ -0,0 +1,64 @@
/*
Copyright The containerd Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package api
import (
"fmt"
)
func (v *ValidateContainerAdjustmentRequest) AddPlugin(name, index string) {
v.Plugins = append(v.Plugins, &PluginInstance{
Name: name,
Index: index,
})
}
func (v *ValidateContainerAdjustmentRequest) AddResponse(rpl *CreateContainerResponse) {
v.Adjust = rpl.Adjust
v.Update = rpl.Update
}
func (v *ValidateContainerAdjustmentRequest) AddOwners(owners *OwningPlugins) {
v.Owners = owners
}
func (v *ValidateContainerAdjustmentResponse) ValidationResult(plugin string) error {
if !v.Reject {
return nil
}
reason := v.Reason
if reason == "" {
reason = "unknown rejection reason"
}
return fmt.Errorf("validator %q rejected container adjustment, reason: %s", plugin, reason)
}
func (v *ValidateContainerAdjustmentRequest) GetPluginMap() map[string]*PluginInstance {
if v == nil {
return nil
}
plugins := make(map[string]*PluginInstance)
for _, p := range v.Plugins {
plugins[p.Name] = &PluginInstance{Name: p.Name}
plugins[p.Index+"-"+p.Name] = p
}
return plugins
}

87
vendor/github.com/containerd/nri/pkg/log/log.go generated vendored Normal file
View File

@@ -0,0 +1,87 @@
/*
Copyright The containerd Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package log
import (
"context"
"github.com/sirupsen/logrus"
)
var (
log Logger = &fallbackLogger{}
)
// Logger is the interface NRI uses for logging.
type Logger interface {
Debugf(ctx context.Context, format string, args ...interface{})
Infof(ctx context.Context, format string, args ...interface{})
Warnf(ctx context.Context, format string, args ...interface{})
Errorf(ctx context.Context, format string, args ...interface{})
}
// Set the logger used by NRI.
func Set(l Logger) {
log = l
}
// Get the logger used by NRI.
func Get() Logger {
return log
}
// Debugf logs a formatted debug message.
func Debugf(ctx context.Context, format string, args ...interface{}) {
log.Debugf(ctx, format, args...)
}
// Infof logs a formatted informational message.
func Infof(ctx context.Context, format string, args ...interface{}) {
log.Infof(ctx, format, args...)
}
// Warnf logs a formatted warning message.
func Warnf(ctx context.Context, format string, args ...interface{}) {
log.Warnf(ctx, format, args...)
}
// Errorf logs a formatted error message.
func Errorf(ctx context.Context, format string, args ...interface{}) {
log.Errorf(ctx, format, args...)
}
type fallbackLogger struct{}
// Debugf logs a formatted debug message.
func (f *fallbackLogger) Debugf(ctx context.Context, format string, args ...interface{}) {
logrus.WithContext(ctx).Debugf(format, args...)
}
// Infof logs a formatted informational message.
func (f *fallbackLogger) Infof(ctx context.Context, format string, args ...interface{}) {
logrus.WithContext(ctx).Infof(format, args...)
}
// Warnf logs a formatted warning message.
func (f *fallbackLogger) Warnf(ctx context.Context, format string, args ...interface{}) {
logrus.WithContext(ctx).Warnf(format, args...)
}
// Errorf logs a formatted error message.
func (f *fallbackLogger) Errorf(ctx context.Context, format string, args ...interface{}) {
logrus.WithContext(ctx).Errorf(format, args...)
}

93
vendor/github.com/containerd/nri/pkg/net/conn.go generated vendored Normal file
View File

@@ -0,0 +1,93 @@
/*
Copyright The containerd Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package net
import (
"fmt"
"io"
"net"
"os"
"strconv"
"sync"
)
// NewFdConn creates a net.Conn for the given (socket) fd.
func NewFdConn(fd int) (net.Conn, error) {
f := os.NewFile(uintptr(fd), "fd #"+strconv.Itoa(fd))
conn, err := net.FileConn(f)
if err != nil {
return nil, fmt.Errorf("failed to create net.Conn for fd #%d: %w", fd, err)
}
f.Close()
return conn, nil
}
// connListener wraps a pre-connected socket in a net.Listener.
type connListener struct {
next chan net.Conn
conn net.Conn
addr net.Addr
lock sync.RWMutex // for Close()
closed bool
}
// NewConnListener wraps an existing net.Conn in a net.Listener.
//
// The first call to Accept() on the listener will return the wrapped
// connection. Subsequent calls to Accept() block until the listener
// is closed, then return io.EOF. Close() closes the listener and the
// wrapped connection.
func NewConnListener(conn net.Conn) net.Listener {
next := make(chan net.Conn, 1)
next <- conn
return &connListener{
next: next,
conn: conn,
addr: conn.LocalAddr(),
}
}
// Accept returns the wrapped connection when it is called the first
// time. Later calls to Accept block until the listener is closed, then
// return io.EOF.
func (l *connListener) Accept() (net.Conn, error) {
conn := <-l.next
if conn == nil {
return nil, io.EOF
}
return conn, nil
}
// Close closes the listener and the wrapped connection.
func (l *connListener) Close() error {
l.lock.Lock()
defer l.lock.Unlock()
if l.closed {
return nil
}
close(l.next)
l.closed = true
return l.conn.Close()
}
// Addr returns the local address of the wrapped connection.
func (l *connListener) Addr() net.Addr {
return l.addr
}

View File

@@ -0,0 +1,460 @@
/*
Copyright The containerd Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package multiplex
import (
"encoding/binary"
"errors"
"fmt"
"io"
"net"
"sync"
"syscall"
"time"
nrinet "github.com/containerd/nri/pkg/net"
"github.com/containerd/ttrpc"
)
// Mux multiplexes several logical connections over a single net.Conn.
//
// Connections are identified within a Mux by ConnIDs which are simple
// 32-bit unsigned integers. Opening a connection returns a net.Conn
// corrponding to the ConnID. This can then be used to write and read
// data through the connection with the Mux performing multiplexing
// and demultiplexing of data.
//
// Writing to a connection is fully synchronous. The caller can safely
// reuse the buffer once the call returns. Reading from a connection
// returns the oldest demultiplexed buffer for the connection, blocking
// if the connections incoming queue is empty. If any incoming queue is
// ever overflown the underlying trunk and all multiplexed connections
// are closed and an error is recorded. This error is later returned by
// any subsequent read from any connection. All connections of the Mux
// have the same fixed incoming queue length which can be configured
// using the WithReadQueueLength Option during Mux creation.
//
// The Mux interface also provides functions that emulate net.Dial and
// net.Listen for a connection. Usually these can be used for passing
// multiplexed connections to packages that insist to Dial or Accept
// themselves for connection establishment.
//
// Note that opening a connection is a virtual operation in the sense
// that it has no effects outside the Mux. It is performed without any
// signalling or other communication. It merely acquires the net.Conn
// corresponding to the connection and blindly assumes that the same
// ConnID is or will be opened at the other end of the Mux.
type Mux interface {
// Open the connection for the given ConnID.
Open(ConnID) (net.Conn, error)
// Close the Mux and all connections associated with it.
Close() error
// Dialer returns a net.Dial-like function for the connection.
//
// Calling the returned function (with arguments) will return a
// net.Conn for the connection.
Dialer(ConnID) func(string, string) (net.Conn, error)
// Listener returns a net.Listener for the connection. The first
// call to Accept() on the listener will return a net.Conn for the
// connection. Subsequent calls to Accept() will block until the
// connection is closed then return io.EOF.
Listen(ConnID) (net.Listener, error)
// Trunk returns the trunk connection for the Mux.
Trunk() net.Conn
// Unblock unblocks the Mux reader.
Unblock()
}
// ConnID uniquely identifies a logical connection within a Mux.
type ConnID uint32
const (
// ConnID 0 is reserved for future use.
reservedConnID ConnID = iota
// LowestConnID is the lowest externally usable ConnID.
LowestConnID
)
// Option to apply to a Mux.
type Option func(*mux)
// WithBlockedRead causes the Mux to be blocked for reading until gets Unblock()'ed.
func WithBlockedRead() Option {
return func(m *mux) {
if m.blockC == nil {
m.blockC = make(chan struct{})
}
}
}
// WithReadQueueLength overrides the default read queue size.
func WithReadQueueLength(length int) Option {
return func(m *mux) {
m.qlen = length
}
}
// Multiplex returns a multiplexer for the given connection.
func Multiplex(trunk net.Conn, options ...Option) Mux {
return newMux(trunk, options...)
}
// mux is our implementation of Mux.
type mux struct {
trunk net.Conn
writeLock sync.Mutex
conns map[ConnID]*conn
connLock sync.RWMutex
qlen int
errOnce sync.Once
err error
unblkOnce sync.Once
blockC chan struct{}
closeOnce sync.Once
doneC chan struct{}
}
const (
// default read queue length for a single connection
readQueueLen = 256
// length of frame header: 4-byte ConnID, 4-byte payload length
headerLen = 8
// max. allowed payload size
maxPayloadSize = ttrpcMessageHeaderLength + ttrpcMessageLengthMax
)
// conn represents a single multiplexed connection.
type conn struct {
id ConnID
mux *mux
readC chan []byte
closeOnce sync.Once
doneC chan error
}
func newMux(trunk net.Conn, options ...Option) *mux {
m := &mux{
trunk: trunk,
conns: make(map[ConnID]*conn),
qlen: readQueueLen,
doneC: make(chan struct{}),
}
for _, o := range options {
o(m)
}
if m.blockC == nil {
WithBlockedRead()(m)
m.Unblock()
}
go m.reader()
return m
}
func (m *mux) Trunk() net.Conn {
return m.trunk
}
func (m *mux) Unblock() {
m.unblkOnce.Do(func() {
close(m.blockC)
})
}
func (m *mux) Open(id ConnID) (net.Conn, error) {
if id == reservedConnID {
return nil, fmt.Errorf("ConnID %d is reserved", id)
}
m.connLock.Lock()
defer m.connLock.Unlock()
c, ok := m.conns[id]
if !ok {
c = &conn{
id: id,
mux: m,
doneC: make(chan error, 1),
readC: make(chan []byte, m.qlen),
}
m.conns[id] = c
}
return c, nil
}
func (m *mux) Close() error {
m.closeOnce.Do(func() {
m.connLock.Lock()
defer m.connLock.Unlock()
for _, conn := range m.conns {
conn.close()
}
close(m.doneC)
m.trunk.Close()
})
return nil
}
func (m *mux) Dialer(id ConnID) func(string, string) (net.Conn, error) {
return func(string, string) (net.Conn, error) {
return m.Open(id)
}
}
func (m *mux) Listen(id ConnID) (net.Listener, error) {
conn, err := m.Open(id)
if err != nil {
return nil, err
}
return nrinet.NewConnListener(conn), nil
}
func (m *mux) write(id ConnID, buf []byte) (int, error) {
var (
hdr [headerLen]byte
data = buf[:]
size = len(data)
)
m.writeLock.Lock()
defer m.writeLock.Unlock()
for {
if size > maxPayloadSize {
size = maxPayloadSize
}
binary.BigEndian.PutUint32(hdr[0:4], uint32(id))
binary.BigEndian.PutUint32(hdr[4:8], uint32(size))
n, err := m.trunk.Write(hdr[:])
if err != nil {
err = fmt.Errorf("failed to write header to trunk: %w", err)
if n != 0 {
m.setError(err)
m.Close()
}
return 0, err
}
n, err = m.trunk.Write(data[:size])
if err != nil {
err = fmt.Errorf("failed to write payload to trunk: %w", err)
if n != 0 {
m.setError(err)
m.Close()
}
return 0, err
}
data = data[size:]
if size > len(data) {
size = len(data)
}
if size == 0 {
break
}
}
return len(buf), nil
}
func (m *mux) reader() {
var (
hdr [headerLen]byte
cid uint32
cnt uint32
buf []byte
err error
)
<-m.blockC
for {
select {
case <-m.doneC:
return
default:
}
_, err = io.ReadFull(m.trunk, hdr[:])
if err != nil {
switch {
case errors.Is(err, io.EOF):
case errors.Is(err, ttrpc.ErrClosed):
err = io.EOF
case errors.Is(err, ttrpc.ErrServerClosed):
err = io.EOF
case errors.Is(err, net.ErrClosed):
err = io.EOF
default:
err = fmt.Errorf("failed to read header from trunk: %w", err)
}
m.setError(err)
m.Close()
return
}
cid = binary.BigEndian.Uint32(hdr[0:4])
cnt = binary.BigEndian.Uint32(hdr[4:8])
buf = make([]byte, int(cnt))
_, err = io.ReadFull(m.trunk, buf)
if err != nil {
switch {
case errors.Is(err, io.EOF):
case errors.Is(err, ttrpc.ErrClosed):
err = io.EOF
case errors.Is(err, ttrpc.ErrServerClosed):
err = io.EOF
case errors.Is(err, net.ErrClosed):
err = io.EOF
default:
err = fmt.Errorf("failed to read payload from trunk: %w", err)
}
m.setError(err)
m.Close()
return
}
m.connLock.RLock()
conn, ok := m.conns[ConnID(cid)]
m.connLock.RUnlock()
if ok {
select {
case conn.readC <- buf:
default:
m.setError(errors.New("failed to queue payload for reading"))
m.Close()
return
}
}
}
}
func (m *mux) setError(err error) {
m.errOnce.Do(func() {
m.err = err
})
}
// nolint
func (m *mux) error() error {
m.errOnce.Do(func() {
if m.err == nil {
m.err = io.EOF
}
})
return m.err
}
//
// multiplexed connections
//
// Reads reads the next message from the multiplexed connection.
func (c *conn) Read(buf []byte) (int, error) {
var (
msg []byte
err error
ok bool
)
select {
case err, ok = <-c.doneC:
if !ok || err == nil {
err = c.mux.error()
}
return 0, err
case msg, ok = <-c.readC:
if !ok {
return 0, c.mux.error()
}
if cap(buf) < len(msg) {
return 0, syscall.ENOMEM
}
}
copy(buf, msg)
return len(msg), nil
}
// Write writes the given data to the multiplexed connection.
func (c *conn) Write(b []byte) (int, error) {
select {
case err := <-c.doneC:
if err == nil {
err = io.EOF
}
return 0, err
default:
}
return c.mux.write(c.id, b)
}
// Close closes the multiplexed connection.
func (c *conn) Close() error {
c.mux.connLock.Lock()
defer c.mux.connLock.Unlock()
if c.mux.conns[c.id] == c {
delete(c.mux.conns, c.id)
}
return c.close()
}
func (c *conn) close() error {
c.closeOnce.Do(func() {
close(c.doneC)
})
return nil
}
// LocalAddr is the unimplemented stub for the corresponding net.Conn function.
func (c *conn) LocalAddr() net.Addr {
return nil
}
// RemoteAddr is the unimplemented stub for the corresponding net.Conn function.
func (c *conn) RemoteAddr() net.Addr {
return nil
}
// SetDeadline is the unimplemented stub for the corresponding net.Conn function.
func (c *conn) SetDeadline(_ time.Time) error {
return nil
}
// SetReadDeadline is the unimplemented stub for the corresponding net.Conn function.
func (c *conn) SetReadDeadline(_ time.Time) error {
return nil
}
// SetWriteDeadline is the unimplemented stub for the corresponding net.Conn function.
func (c *conn) SetWriteDeadline(_ time.Time) error {
return nil
}

View File

@@ -0,0 +1,29 @@
/*
Copyright The containerd Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package multiplex
const (
// PluginServiceConn is the mux connection ID for NRI plugin services.
PluginServiceConn ConnID = iota + 1
// RuntimeServiceConn is the mux connection ID for NRI runtime services.
RuntimeServiceConn
)
const (
ttrpcMessageHeaderLength = 10
ttrpcMessageLengthMax = 4 << 20
)

93
vendor/github.com/containerd/nri/pkg/net/socketpair.go generated vendored Normal file
View File

@@ -0,0 +1,93 @@
/*
Copyright The containerd Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package net
import (
"fmt"
"net"
"os"
)
// SocketPair contains the os.File of a connected pair of sockets.
type SocketPair struct {
local, peer *os.File
}
// NewSocketPair returns a connected pair of sockets.
func NewSocketPair() (SocketPair, error) {
fds, err := newSocketPairCLOEXEC()
if err != nil {
return SocketPair{nil, nil}, fmt.Errorf("failed to create socketpair: %w", err)
}
filename := fmt.Sprintf("socketpair-#%d:%d", fds[0], fds[1])
return SocketPair{
os.NewFile(uintptr(fds[0]), filename+"[0]"),
os.NewFile(uintptr(fds[1]), filename+"[1]"),
}, nil
}
// LocalFile returns the local end of the socketpair as an *os.File.
func (sp SocketPair) LocalFile() *os.File {
return sp.local
}
// PeerFile returns the peer end of the socketpair as an *os.File.
func (sp SocketPair) PeerFile() *os.File {
return sp.peer
}
// LocalConn returns a net.Conn for the local end of the socketpair.
// This closes LocalFile().
func (sp SocketPair) LocalConn() (net.Conn, error) {
file := sp.LocalFile()
defer file.Close()
conn, err := net.FileConn(file)
if err != nil {
return nil, fmt.Errorf("failed to create net.Conn for %s: %w", file.Name(), err)
}
return conn, nil
}
// PeerConn returns a net.Conn for the peer end of the socketpair.
// This closes PeerFile().
func (sp SocketPair) PeerConn() (net.Conn, error) {
file := sp.PeerFile()
defer file.Close()
conn, err := net.FileConn(file)
if err != nil {
return nil, fmt.Errorf("failed to create net.Conn for %s: %w", file.Name(), err)
}
return conn, nil
}
// Close closes both ends of the socketpair.
func (sp SocketPair) Close() {
sp.LocalClose()
sp.PeerClose()
}
// LocalClose closes the local end of the socketpair.
func (sp SocketPair) LocalClose() {
sp.local.Close()
}
// PeerClose closes the peer end of the socketpair.
func (sp SocketPair) PeerClose() {
sp.peer.Close()
}

View File

@@ -0,0 +1,27 @@
//go:build linux
/*
Copyright The containerd Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package net
import (
"golang.org/x/sys/unix"
)
func newSocketPairCLOEXEC() ([2]int, error) {
return unix.Socketpair(unix.AF_UNIX, unix.SOCK_STREAM|unix.SOCK_CLOEXEC, 0)
}

View File

@@ -0,0 +1,38 @@
//go:build !linux && !windows
/*
Copyright The containerd Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package net
import (
"syscall"
"golang.org/x/sys/unix"
)
func newSocketPairCLOEXEC() ([2]int, error) {
syscall.ForkLock.RLock()
defer syscall.ForkLock.RUnlock()
fds, err := unix.Socketpair(unix.AF_UNIX, unix.SOCK_STREAM, 0)
if err != nil {
return fds, err
}
unix.CloseOnExec(fds[0])
unix.CloseOnExec(fds[1])
return fds, err
}

View File

@@ -0,0 +1,30 @@
//go:build windows
/*
Copyright The containerd Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package net
import (
"errors"
sys "golang.org/x/sys/windows"
)
func newSocketPairCLOEXEC() ([2]sys.Handle, error) {
// when implementing do use WSA_FLAG_NO_HANDLE_INHERIT to avoid leaking FDs
return [2]sys.Handle{sys.InvalidHandle, sys.InvalidHandle}, errors.New("newSocketPairCLOEXEC unimplemented for windows")
}

View File

@@ -0,0 +1,59 @@
/*
Copyright The containerd Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package plugin
import (
"github.com/containerd/nri/pkg/api"
)
const (
// AnnotationDomain is the domain used for NRI-specific annotations.
AnnotationDomain = "noderesource.dev"
// RequiredPluginsAnnotation can be used to annotate pods with a list
// of pod- or container-specific plugins which must process containers
// during creation. If enabled, the default validator checks for this
// and rejects the creation of containers which fail this check.
RequiredPluginsAnnotation = "required-plugins." + AnnotationDomain
)
// GetEffectiveAnnotation retrieves a custom annotation from a pod which
// applies to given container. The syntax allows both pod- and container-
// scoped annotations. Container-scoped annotations take precedence over
// pod-scoped ones. The key syntax defines the scope of the annotation.
// - container-scope: <key>/container.<container-name>
// - pod-scope: <key>/pod, or just <key>
func GetEffectiveAnnotation(pod *api.PodSandbox, key, container string) (string, bool) {
annotations := pod.GetAnnotations()
if len(annotations) == 0 {
return "", false
}
keys := []string{
key + "/container." + container,
key + "/pod",
key,
}
for _, k := range keys {
if v, ok := annotations[k]; ok {
return v, true
}
}
return "", false
}

View File

@@ -0,0 +1,48 @@
/*
Copyright The containerd Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package builtin
import (
"context"
"github.com/containerd/nri/pkg/adaptation/builtin"
"github.com/containerd/nri/pkg/log"
validator "github.com/containerd/nri/plugins/default-validator"
)
type (
DefaultValidatorConfig = validator.DefaultValidatorConfig
)
// GetDefaultValidator returns a configured instance of the default validator.
// If default validation is disabled nil is returned.
func GetDefaultValidator(cfg *DefaultValidatorConfig) *builtin.BuiltinPlugin {
if cfg == nil || !cfg.Enable {
log.Infof(context.TODO(), "built-in NRI default validator is disabled")
return nil
}
v := validator.NewDefaultValidator(cfg)
return &builtin.BuiltinPlugin{
Base: "default-validator",
Index: "00",
Handlers: builtin.BuiltinHandlers{
ValidateContainerAdjustment: v.ValidateContainerAdjustment,
},
}
}

View File

@@ -0,0 +1,258 @@
/*
Copyright The containerd Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package validator
import (
"context"
"errors"
"fmt"
"slices"
"strconv"
"strings"
"github.com/containerd/nri/pkg/api"
"github.com/containerd/nri/pkg/log"
"github.com/containerd/nri/pkg/plugin"
yaml "gopkg.in/yaml.v3"
)
type DefaultValidatorConfig struct {
// Enable the default validator plugin.
Enable bool `yaml:"enable" toml:"enable"`
// RejectOCIHookAdjustment fails validation if OCI hooks are adjusted.
RejectOCIHookAdjustment bool `yaml:"rejectOCIHookAdjustment" toml:"reject_oci_hook_adjustment"`
// RejectRuntimeDefaultSeccompAdjustment fails validation if a runtime default seccomp
// policy is adjusted.
RejectRuntimeDefaultSeccompAdjustment bool `yaml:"rejectRuntimeDefaultSeccompAdjustment" toml:"reject_runtime_default_seccomp_adjustment"`
// RejectUnconfinedSeccompAdjustment fails validation if an unconfined seccomp policy is
// adjusted.
RejectUnconfinedSeccompAdjustment bool `yaml:"rejectUnconfinedSeccompAdjustment" toml:"reject_unconfined_seccomp_adjustment"`
// RejectCustomSeccompAdjustment fails validation if a custom seccomp policy (aka LOCALHOST)
// is adjusted.
RejectCustomSeccompAdjustment bool `yaml:"rejectCustomSeccompAdjustment" toml:"reject_custom_seccomp_adjustment"`
// RejectNamespaceAdjustment fails validation if any plugin adjusts Linux namespaces.
RejectNamespaceAdjustment bool `yaml:"rejectNamespaceAdjustment" toml:"reject_namespace_adjustment"`
// RequiredPlugins list globally required plugins. These must be present
// or otherwise validation will fail.
// WARNING: This is a global setting and will affect all containers. In
// particular, if you configure any globally required plugins, you should
// annotate your static pods to tolerate missing plugins. Failing to do
// so will prevent static pods from starting.
// Notes:
// Containers can be annotated to tolerate missing plugins using the
// toleration annotation, if one is set.
RequiredPlugins []string `yaml:"requiredPlugins" toml:"required_plugins"`
// TolerateMissingPlugins is an optional annotation key. If set, it can
// be used to annotate containers to tolerate missing required plugins.
TolerateMissingAnnotation string `yaml:"tolerateMissingPluginsAnnotation" toml:"tolerate_missing_plugins_annotation"`
}
// DefaultValidator implements default validation.
type DefaultValidator struct {
cfg DefaultValidatorConfig
}
const (
// RequiredPlugins is the annotation key for extra required plugins.
RequiredPlugins = plugin.RequiredPluginsAnnotation
)
var (
// ErrValidation is returned if validation rejects an adjustment.
ErrValidation = errors.New("validation error")
)
// NewDefaultValidator creates a new instance of the validator.
func NewDefaultValidator(cfg *DefaultValidatorConfig) *DefaultValidator {
return &DefaultValidator{cfg: *cfg}
}
// SetConfig sets new configuration for the validator.
func (v *DefaultValidator) SetConfig(cfg *DefaultValidatorConfig) {
if cfg == nil {
return
}
v.cfg = *cfg
}
// ValidateContainerAdjustment validates a container adjustment.
func (v *DefaultValidator) ValidateContainerAdjustment(ctx context.Context, req *api.ValidateContainerAdjustmentRequest) error {
log.Debugf(ctx, "Validating adjustment of container %s/%s/%s",
req.GetPod().GetNamespace(), req.GetPod().GetName(), req.GetContainer().GetName())
if err := v.validateOCIHooks(req); err != nil {
log.Errorf(ctx, "rejecting adjustment: %v", err)
return err
}
if err := v.validateSeccompPolicy(req); err != nil {
log.Errorf(ctx, "rejecting adjustment: %v", err)
return err
}
if err := v.validateNamespaces(req); err != nil {
log.Errorf(ctx, "rejecting adjustment: %v", err)
return err
}
if err := v.validateRequiredPlugins(req); err != nil {
log.Errorf(ctx, "rejecting adjustment: %v", err)
return err
}
return nil
}
func (v *DefaultValidator) validateOCIHooks(req *api.ValidateContainerAdjustmentRequest) error {
if req.Adjust == nil {
return nil
}
if !v.cfg.RejectOCIHookAdjustment {
return nil
}
owners, claimed := req.Owners.HooksOwner(req.Container.Id)
if !claimed {
return nil
}
offender := ""
if !strings.Contains(owners, ",") {
offender = fmt.Sprintf("plugin %q", owners)
} else {
offender = fmt.Sprintf("plugins %q", owners)
}
return fmt.Errorf("%w: %s attempted restricted OCI hook injection", ErrValidation, offender)
}
func (v *DefaultValidator) validateSeccompPolicy(req *api.ValidateContainerAdjustmentRequest) error {
if req.Adjust == nil {
return nil
}
owner, claimed := req.Owners.SeccompPolicyOwner(req.Container.Id)
if !claimed {
return nil
}
profile := req.Container.GetLinux().GetSeccompProfile()
switch {
case profile == nil || profile.GetProfileType() == api.SecurityProfile_UNCONFINED:
if v.cfg.RejectUnconfinedSeccompAdjustment {
return fmt.Errorf("%w: plugin %s attempted restricted "+
" unconfined seccomp policy adjustment", ErrValidation, owner)
}
case profile.GetProfileType() == api.SecurityProfile_RUNTIME_DEFAULT:
if v.cfg.RejectRuntimeDefaultSeccompAdjustment {
return fmt.Errorf("%w: plugin %s attempted restricted "+
"runtime default seccomp policy adjustment", ErrValidation, owner)
}
case profile.GetProfileType() == api.SecurityProfile_LOCALHOST:
if v.cfg.RejectCustomSeccompAdjustment {
return fmt.Errorf("%w: plugin %s attempted restricted "+
" custom seccomp policy adjustment", ErrValidation, owner)
}
}
return nil
}
func (v *DefaultValidator) validateNamespaces(req *api.ValidateContainerAdjustmentRequest) error {
if req.Adjust == nil {
return nil
}
if !v.cfg.RejectNamespaceAdjustment {
return nil
}
owners, claimed := req.Owners.NamespaceOwners(req.Container.Id)
if !claimed {
return nil
}
offenders := "plugin(s) "
sep := ""
for ns, plugin := range owners {
offenders += sep + fmt.Sprintf("%q (namespace %q)", plugin, ns)
sep = ", "
}
return fmt.Errorf("%w: attempted restricted namespace adjustment by %s",
ErrValidation, offenders)
}
func (v *DefaultValidator) validateRequiredPlugins(req *api.ValidateContainerAdjustmentRequest) error {
var (
container = req.GetContainer().GetName()
required = slices.Clone(v.cfg.RequiredPlugins)
)
if tolerateMissing := v.cfg.TolerateMissingAnnotation; tolerateMissing != "" {
value, ok := plugin.GetEffectiveAnnotation(req.GetPod(), tolerateMissing, container)
if ok {
tolerate, err := strconv.ParseBool(value)
if err != nil {
return fmt.Errorf("invalid %s annotation %q: %w", tolerateMissing, value, err)
}
if tolerate {
return nil
}
}
}
if value, ok := plugin.GetEffectiveAnnotation(req.GetPod(), RequiredPlugins, container); ok {
var annotated []string
if err := yaml.Unmarshal([]byte(value), &annotated); err != nil {
return fmt.Errorf("invalid %s annotation %q: %w", RequiredPlugins, value, err)
}
required = append(required, annotated...)
}
if len(required) == 0 {
return nil
}
plugins := req.GetPluginMap()
missing := []string{}
for _, r := range required {
if _, ok := plugins[r]; !ok {
missing = append(missing, r)
}
}
if len(missing) == 0 {
return nil
}
offender := ""
if len(missing) == 1 {
offender = fmt.Sprintf("required plugin %q", missing[0])
} else {
offender = fmt.Sprintf("required plugins %q", strings.Join(missing, ","))
}
return fmt.Errorf("%w: %s not present", ErrValidation, offender)
}

21
vendor/github.com/knqyf263/go-plugin/LICENSE generated vendored Normal file
View File

@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2022 Teppei Fukuda
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

47
vendor/github.com/knqyf263/go-plugin/wasm/host.go generated vendored Normal file
View File

@@ -0,0 +1,47 @@
//go:build !wasip1
// This file is designed to be imported by hosts.
package wasm
import (
"context"
"errors"
"fmt"
"github.com/tetratelabs/wazero/api"
)
func ReadMemory(mem api.Memory, offset, size uint32) ([]byte, error) {
buf, ok := mem.Read(offset, size)
if !ok {
return nil, fmt.Errorf("Memory.Read(%d, %d) out of range", offset, size)
}
return buf, nil
}
func WriteMemory(ctx context.Context, m api.Module, data []byte) (uint64, error) {
malloc := m.ExportedFunction("malloc")
if malloc == nil {
return 0, errors.New("malloc is not exported")
}
l := uint64(len(data))
if l == 0 {
return 0, nil
}
results, err := malloc.Call(ctx, l)
if err != nil {
return 0, err
}
dataPtr := results[0]
// The pointer is a linear memory offset, which is where we write the name.
if !m.Memory().Write(uint32(dataPtr), data) {
return 0, fmt.Errorf("Memory.Write(%d, %d) out of range of memory size %d",
dataPtr, len(data), m.Memory().Size())
}
return dataPtr, nil
}

54
vendor/github.com/knqyf263/go-plugin/wasm/plugin.go generated vendored Normal file
View File

@@ -0,0 +1,54 @@
//go:build wasip1 && !tinygo.wasm
// This file is designed to be imported by plugins.
package wasm
import (
"unsafe"
)
// allocations keeps track of each allocated byte slice, keyed by a fake pointer.
// This map ensures the GC will not collect these slices while still in use.
var allocations = make(map[uint32][]byte)
// allocate creates a new byte slice of the given size and stores it in the
// allocations map so that it remains valid (not garbage-collected).
func allocate(size uint32) uint32 {
if size == 0 {
return 0
}
// Create a new byte slice on the Go heap
b := make([]byte, size)
// Obtain the 'address' of the first element in b by converting its pointer to a uint32.
ptr := uint32(uintptr(unsafe.Pointer(&b[0])))
// Store the byte slice in the map, keyed by the pointer
allocations[ptr] = b
return ptr
}
//go:wasmexport malloc
func Malloc(size uint32) uint32 {
return allocate(size)
}
//go:wasmexport free
func Free(ptr uint32) {
// Remove the slice from the allocations map so the GC can reclaim it later.
delete(allocations, ptr)
}
func PtrToByte(ptr, size uint32) []byte {
return unsafe.Slice((*byte)(unsafe.Pointer(uintptr(ptr))), size)
}
func ByteToPtr(buf []byte) (uint32, uint32) {
if len(buf) == 0 {
return 0, 0
}
ptr := &buf[0]
unsafePtr := uintptr(unsafe.Pointer(ptr))
return uint32(unsafePtr), uint32(len(buf))
}

View File

@@ -0,0 +1,35 @@
//go:build tinygo.wasm
// This file is designed to be imported by plugins.
package wasm
// #include <stdlib.h>
import "C"
import (
"unsafe"
)
func PtrToByte(ptr, size uint32) []byte {
b := unsafe.Slice((*byte)(unsafe.Pointer(uintptr(ptr))), size)
return b
}
func ByteToPtr(buf []byte) (uint32, uint32) {
if len(buf) == 0 {
return 0, 0
}
size := C.ulong(len(buf))
ptr := unsafe.Pointer(C.malloc(size))
copy(unsafe.Slice((*byte)(ptr), size), buf)
return uint32(uintptr(ptr)), uint32(len(buf))
}
func Free(ptr uint32) {
C.free(unsafe.Pointer(uintptr(ptr)))
}

2
vendor/github.com/tetratelabs/wazero/.gitattributes generated vendored Normal file
View File

@@ -0,0 +1,2 @@
# Improves experience of commands like `make format` on Windows
* text=auto eol=lf

46
vendor/github.com/tetratelabs/wazero/.gitignore generated vendored Normal file
View File

@@ -0,0 +1,46 @@
# If you prefer the allow list template instead of the deny list, see community template:
# https://github.com/github/gitignore/blob/main/community/Golang/Go.AllowList.gitignore
#
# Binaries for programs and plugins
*.exe
*.exe~
*.dll
*.so
*.dylib
/wazero
build
dist
# Test binary, built with `go test -c`
*.test
# Output of the go coverage tool, specifically when used with LiteIDE
*.out
# Dependency directories (remove the comment below to include it)
# vendor/
# Go workspace file
go.work
# Goland
.idea
# AssemblyScript
node_modules
package-lock.json
# codecov.io
/coverage.txt
.vagrant
zig-cache/
.zig-cache/
zig-out/
.DS_Store
# Ignore compiled stdlib test cases.
/internal/integration_test/stdlibs/testdata
/internal/integration_test/libsodium/testdata

3
vendor/github.com/tetratelabs/wazero/.gitmodules generated vendored Normal file
View File

@@ -0,0 +1,3 @@
[submodule "site/themes/hello-friend"]
path = site/themes/hello-friend
url = https://github.com/panr/hugo-theme-hello-friend.git

75
vendor/github.com/tetratelabs/wazero/CONTRIBUTING.md generated vendored Normal file
View File

@@ -0,0 +1,75 @@
# Contributing
We welcome contributions from the community. Please read the following guidelines carefully to maximize the chances of your PR being merged.
## Coding Style
- To ensure your change passes format checks, run `make check`. To format your files, you can run `make format`.
- We follow standard Go table-driven tests and use an internal [testing library](./internal/testing/require) to assert correctness. To verify all tests pass, you can run `make test`.
## DCO
We require DCO signoff line in every commit to this repo.
The sign-off is a simple line at the end of the explanation for the
patch, which certifies that you wrote it or otherwise have the right to
pass it on as an open-source patch. The rules are pretty simple: if you
can certify the below (from
[developercertificate.org](https://developercertificate.org/)):
```
Developer Certificate of Origin
Version 1.1
Copyright (C) 2004, 2006 The Linux Foundation and its contributors.
660 York Street, Suite 102,
San Francisco, CA 94110 USA
Everyone is permitted to copy and distribute verbatim copies of this
license document, but changing it is not allowed.
Developer's Certificate of Origin 1.1
By making a contribution to this project, I certify that:
(a) The contribution was created in whole or in part by me and I
have the right to submit it under the open source license
indicated in the file; or
(b) The contribution is based upon previous work that, to the best
of my knowledge, is covered under an appropriate open source
license and I have the right under that license to submit that
work with modifications, whether created in whole or in part
by me, under the same open source license (unless I am
permitted to submit under a different license), as indicated
in the file; or
(c) The contribution was provided directly to me by some other
person who certified (a), (b) or (c) and I have not modified
it.
(d) I understand and agree that this project and the contribution
are public and that a record of the contribution (including all
personal information I submit with it, including my sign-off) is
maintained indefinitely and may be redistributed consistent with
this project or the open source license(s) involved.
```
then you just add a line to every git commit message:
Signed-off-by: Joe Smith <joe@gmail.com>
using your real name (sorry, no pseudonyms or anonymous contributions.)
You can add the sign off when creating the git commit via `git commit -s`.
## Code Reviews
* The pull request title should describe what the change does and not embed issue numbers.
The pull request should only be blank when the change is minor. Any feature should include
a description of the change and what motivated it. If the change or design changes through
review, please keep the title and description updated accordingly.
* A single approval is sufficient to merge. If a reviewer asks for
changes in a PR they should be addressed before the PR is merged,
even if another reviewer has already approved the PR.
* During the review, address the comments and commit the changes
_without_ squashing the commits. This facilitates incremental reviews
since the reviewer does not go through all the code again to find out
what has changed since the last review. When a change goes out of sync with main,
please rebase and force push, keeping the original commits where practical.
* Commits are squashed prior to merging a pull request, using the title
as commit message by default. Maintainers may request contributors to
edit the pull request tite to ensure that it remains descriptive as a
commit message. Alternatively, maintainers may change the commit message directly.

201
vendor/github.com/tetratelabs/wazero/LICENSE generated vendored Normal file
View File

@@ -0,0 +1,201 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright 2020-2023 wazero authors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

364
vendor/github.com/tetratelabs/wazero/Makefile generated vendored Normal file
View File

@@ -0,0 +1,364 @@
gofumpt := mvdan.cc/gofumpt@v0.6.0
gosimports := github.com/rinchsan/gosimports/cmd/gosimports@v0.3.8
golangci_lint := github.com/golangci/golangci-lint/cmd/golangci-lint@v1.64.5
asmfmt := github.com/klauspost/asmfmt/cmd/asmfmt@v1.3.2
# sync this with netlify.toml!
hugo := github.com/gohugoio/hugo@v0.115.2
# Make 3.81 doesn't support '**' globbing: Set explicitly instead of recursion.
all_sources := $(wildcard *.go */*.go */*/*.go */*/*/*.go */*/*/*.go */*/*/*/*.go)
all_testdata := $(wildcard testdata/* */testdata/* */*/testdata/* */*/testdata/*/* */*/*/testdata/*)
all_testing := $(wildcard internal/testing/* internal/testing/*/* internal/testing/*/*/*)
all_examples := $(wildcard examples/* examples/*/* examples/*/*/* */*/example/* */*/example/*/* */*/example/*/*/*)
all_it := $(wildcard internal/integration_test/* internal/integration_test/*/* internal/integration_test/*/*/*)
# main_sources exclude any test or example related code
main_sources := $(wildcard $(filter-out %_test.go $(all_testdata) $(all_testing) $(all_examples) $(all_it), $(all_sources)))
# main_packages collect the unique main source directories (sort will dedupe).
# Paths need to all start with ./, so we do that manually vs foreach which strips it.
main_packages := $(sort $(foreach f,$(dir $(main_sources)),$(if $(findstring ./,$(f)),./,./$(f))))
go_test_options ?= -timeout 300s
.PHONY: test.examples
test.examples:
@go test $(go_test_options) ./examples/... ./imports/assemblyscript/example/... ./imports/emscripten/... ./imports/wasi_snapshot_preview1/example/...
.PHONY: build.examples.as
build.examples.as:
@cd ./imports/assemblyscript/example/testdata && npm install && npm run build
%.wasm: %.zig
@(cd $(@D); zig build -Doptimize=ReleaseSmall)
@mv $(@D)/zig-out/*/$(@F) $(@D)
.PHONY: build.examples.zig
build.examples.zig: examples/allocation/zig/testdata/greet.wasm imports/wasi_snapshot_preview1/example/testdata/zig/cat.wasm imports/wasi_snapshot_preview1/testdata/zig/wasi.wasm
@cd internal/testing/dwarftestdata/testdata/zig; zig build; mv zig-out/*/main.wasm ./ # Need DWARF custom sections.
tinygo_reactor_sources_reactor := examples/basic/testdata/add.go examples/allocation/tinygo/testdata/greet.go
.PHONY: build.examples.tinygo_reactor
build.examples.tinygo_reactor: $(tinygo_sources_reactor)
@for f in $^; do \
tinygo build -o $$(echo $$f | sed -e 's/\.go/\.wasm/') -scheduler=none --no-debug --target=wasip1 -buildmode=c-shared $$f; \
done
tinygo_sources_clis := examples/cli/testdata/cli.go imports/wasi_snapshot_preview1/example/testdata/tinygo/cat.go imports/wasi_snapshot_preview1/testdata/tinygo/wasi.go cmd/wazero/testdata/cat/cat.go
.PHONY: build.examples.tinygo_clis
build.examples.tinygo_clis: $(tinygo_sources_clis)
@for f in $^; do \
tinygo build -o $$(echo $$f | sed -e 's/\.go/\.wasm/') -scheduler=none --no-debug --target=wasip1 $$f; \
done
@mv cmd/wazero/testdata/cat/cat.wasm cmd/wazero/testdata/cat/cat-tinygo.wasm
.PHONY: build.examples.tinygo
build.examples.tinygo: build.examples.tinygo_reactor build.examples.tinygo_clis
# We use zig to build C as it is easy to install and embeds a copy of zig-cc.
# Note: Don't use "-Oz" as that breaks our wasi sock example.
c_sources := imports/wasi_snapshot_preview1/example/testdata/zig-cc/cat.c imports/wasi_snapshot_preview1/testdata/zig-cc/wasi.c internal/testing/dwarftestdata/testdata/zig-cc/main.c
.PHONY: build.examples.zig-cc
build.examples.zig-cc: $(c_sources)
@for f in $^; do \
zig cc --target=wasm32-wasi -o $$(echo $$f | sed -e 's/\.c/\.wasm/') $$f; \
done
# Here are the emcc args we use:
#
# * `-Oz` - most optimization for code size.
# * `--profiling` - adds the name section.
# * `-s STANDALONE_WASM` - ensures wasm is built for a non-js runtime.
# * `-s EXPORTED_FUNCTIONS=_malloc,_free` - export allocation functions so that
# they can be used externally as "malloc" and "free".
# * `-s WARN_ON_UNDEFINED_SYMBOLS=0` - imports not defined in JavaScript error
# otherwise. See https://github.com/emscripten-core/emscripten/issues/13641
# * `-s TOTAL_STACK=8KB -s TOTAL_MEMORY=64KB` - reduce memory default from 16MB
# to one page (64KB). To do this, we have to reduce the stack size.
# * `-s ALLOW_MEMORY_GROWTH` - allows "memory.grow" instructions to succeed, but
# requires a function import "emscripten_notify_memory_growth".
emscripten_sources := $(wildcard imports/emscripten/testdata/*.cc)
.PHONY: build.examples.emscripten
build.examples.emscripten: $(emscripten_sources)
@for f in $^; do \
em++ -Oz --profiling \
-s STANDALONE_WASM \
-s EXPORTED_FUNCTIONS=_malloc,_free \
-s WARN_ON_UNDEFINED_SYMBOLS=0 \
-s TOTAL_STACK=8KB -s TOTAL_MEMORY=64KB \
-s ALLOW_MEMORY_GROWTH \
--std=c++17 -o $$(echo $$f | sed -e 's/\.cc/\.wasm/') $$f; \
done
%/greet.wasm : cargo_target := wasm32-unknown-unknown
%/cat.wasm : cargo_target := wasm32-wasip1
%/wasi.wasm : cargo_target := wasm32-wasip1
.PHONY: build.examples.rust
build.examples.rust: examples/allocation/rust/testdata/greet.wasm imports/wasi_snapshot_preview1/example/testdata/cargo-wasi/cat.wasm imports/wasi_snapshot_preview1/testdata/cargo-wasi/wasi.wasm internal/testing/dwarftestdata/testdata/rust/main.wasm.xz
# Normally, we build release because it is smaller. Testing dwarf requires the debug build.
internal/testing/dwarftestdata/testdata/rust/main.wasm.xz:
cd $(@D) && cargo build --target wasm32-wasip1
mv $(@D)/target/wasm32-wasip1/debug/main.wasm $(@D)
cd $(@D) && xz -k -f ./main.wasm # Rust's DWARF section is huge, so compress it.
# Builds rust using cargo normally
%.wasm: %.rs
@(cd $(@D); cargo build --target $(cargo_target) --release)
@mv $(@D)/target/$(cargo_target)/release/$(@F) $(@D)
spectest_base_dir := internal/integration_test/spectest
spectest_v1_dir := $(spectest_base_dir)/v1
spectest_v1_testdata_dir := $(spectest_v1_dir)/testdata
spec_version_v1 := wg-1.0
spectest_v2_dir := $(spectest_base_dir)/v2
spectest_v2_testdata_dir := $(spectest_v2_dir)/testdata
# Latest draft state as of March 12, 2024.
spec_version_v2 := 1c5e5d178bd75c79b7a12881c529098beaee2a05
spectest_threads_dir := $(spectest_base_dir)/threads
spectest_threads_testdata_dir := $(spectest_threads_dir)/testdata
# From https://github.com/WebAssembly/threads/tree/upstream-rebuild which has not been merged to main yet.
# It will likely be renamed to main in the future - https://github.com/WebAssembly/threads/issues/216.
spec_version_threads := 3635ca51a17e57e106988846c5b0e0cc48ac04fc
.PHONY: build.spectest
build.spectest:
@$(MAKE) build.spectest.v1
@$(MAKE) build.spectest.v2
.PHONY: build.spectest.v1
build.spectest.v1: # Note: wabt by default uses >1.0 features, so wast2json flags might drift as they include more. See WebAssembly/wabt#1878
@rm -rf $(spectest_v1_testdata_dir)
@mkdir -p $(spectest_v1_testdata_dir)
@cd $(spectest_v1_testdata_dir) \
&& curl -sSL 'https://api.github.com/repos/WebAssembly/spec/contents/test/core?ref=$(spec_version_v1)' | jq -r '.[]| .download_url' | grep -E ".wast" | xargs -Iurl curl -sJL url -O
@cd $(spectest_v1_testdata_dir) && for f in `find . -name '*.wast'`; do \
perl -pi -e 's/\(assert_return_canonical_nan\s(\(invoke\s"f32.demote_f64"\s\((f[0-9]{2})\.const\s[a-z0-9.+:-]+\)\))\)/\(assert_return $$1 \(f32.const nan:canonical\)\)/g' $$f; \
perl -pi -e 's/\(assert_return_arithmetic_nan\s(\(invoke\s"f32.demote_f64"\s\((f[0-9]{2})\.const\s[a-z0-9.+:-]+\)\))\)/\(assert_return $$1 \(f32.const nan:arithmetic\)\)/g' $$f; \
perl -pi -e 's/\(assert_return_canonical_nan\s(\(invoke\s"f64\.promote_f32"\s\((f[0-9]{2})\.const\s[a-z0-9.+:-]+\)\))\)/\(assert_return $$1 \(f64.const nan:canonical\)\)/g' $$f; \
perl -pi -e 's/\(assert_return_arithmetic_nan\s(\(invoke\s"f64\.promote_f32"\s\((f[0-9]{2})\.const\s[a-z0-9.+:-]+\)\))\)/\(assert_return $$1 \(f64.const nan:arithmetic\)\)/g' $$f; \
perl -pi -e 's/\(assert_return_canonical_nan\s(\(invoke\s"[a-z._0-9]+"\s\((f[0-9]{2})\.const\s[a-z0-9.+:-]+\)\))\)/\(assert_return $$1 \($$2.const nan:canonical\)\)/g' $$f; \
perl -pi -e 's/\(assert_return_arithmetic_nan\s(\(invoke\s"[a-z._0-9]+"\s\((f[0-9]{2})\.const\s[a-z0-9.+:-]+\)\))\)/\(assert_return $$1 \($$2.const nan:arithmetic\)\)/g' $$f; \
perl -pi -e 's/\(assert_return_canonical_nan\s(\(invoke\s"[a-z._0-9]+"\s\((f[0-9]{2})\.const\s[a-z0-9.+:-]+\)\s\([a-z0-9.\s+-:]+\)\))\)/\(assert_return $$1 \($$2.const nan:canonical\)\)/g' $$f; \
perl -pi -e 's/\(assert_return_arithmetic_nan\s(\(invoke\s"[a-z._0-9]+"\s\((f[0-9]{2})\.const\s[a-z0-9.+:-]+\)\s\([a-z0-9.\s+-:]+\)\))\)/\(assert_return $$1 \($$2.const nan:arithmetic\)\)/g' $$f; \
perl -pi -e 's/\(assert_return_canonical_nan\s(\(invoke\s"[a-z._0-9]+"\s\((f[0-9]{2})\.const\s[a-z0-9.+:-]+\)\))\)/\(assert_return $$1 \($$2.const nan:canonical\)\)/g' $$f; \
perl -pi -e 's/\(assert_return_arithmetic_nan\s(\(invoke\s"[a-z._0-9]+"\s\((f[0-9]{2})\.const\s[a-z0-9.+:-]+\)\))\)/\(assert_return $$1 \($$2.const nan:arithmetic\)\)/g' $$f; \
wast2json \
--disable-saturating-float-to-int \
--disable-sign-extension \
--disable-simd \
--disable-multi-value \
--disable-bulk-memory \
--disable-reference-types \
--debug-names $$f; \
done
.PHONY: build.spectest.v2
build.spectest.v2: # Note: SIMD cases are placed in the "simd" subdirectory.
@mkdir -p $(spectest_v2_testdata_dir)
@cd $(spectest_v2_testdata_dir) \
&& curl -sSL 'https://api.github.com/repos/WebAssembly/spec/contents/test/core?ref=$(spec_version_v2)' | jq -r '.[]| .download_url' | grep -E ".wast" | xargs -Iurl curl -sJL url -O
@cd $(spectest_v2_testdata_dir) \
&& curl -sSL 'https://api.github.com/repos/WebAssembly/spec/contents/test/core/simd?ref=$(spec_version_v2)' | jq -r '.[]| .download_url' | grep -E ".wast" | xargs -Iurl curl -sJL url -O
@cd $(spectest_v2_testdata_dir) && for f in `find . -name '*.wast'`; do \
wast2json --debug-names --no-check $$f || true; \
done # Ignore the error here as some tests (e.g. comments.wast right now) are not supported by wast2json yet.
# Note: We currently cannot build the "threads" subdirectory that spawns threads due to missing support in wast2json.
# https://github.com/WebAssembly/wabt/issues/2348#issuecomment-1878003959
.PHONY: build.spectest.threads
build.spectest.threads:
@mkdir -p $(spectest_threads_testdata_dir)
@cd $(spectest_threads_testdata_dir) \
&& curl -sSL 'https://api.github.com/repos/WebAssembly/threads/contents/test/core?ref=$(spec_version_threads)' | jq -r '.[]| .download_url' | grep -E "atomic.wast" | xargs -Iurl curl -sJL url -O
@cd $(spectest_threads_testdata_dir) && for f in `find . -name '*.wast'`; do \
wast2json --enable-threads --debug-names $$f; \
done
.PHONY: test
test:
@go test $(go_test_options) ./...
@cd internal/version/testdata && go test $(go_test_options) ./...
@cd internal/integration_test/fuzz/wazerolib && CGO_ENABLED=0 WASM_BINARY_PATH=testdata/test.wasm go test ./...
.PHONY: coverage
# replace spaces with commas
coverpkg = $(shell echo $(main_packages) | tr ' ' ',')
coverage: ## Generate test coverage
@go test -coverprofile=coverage.txt -covermode=atomic --coverpkg=$(coverpkg) $(main_packages)
@go tool cover -func coverage.txt
golangci_lint_path := $(shell go env GOPATH)/bin/golangci-lint
$(golangci_lint_path):
@go install $(golangci_lint)
golangci_lint_goarch ?= $(shell go env GOARCH)
.PHONY: lint
lint: $(golangci_lint_path)
@GOARCH=$(golangci_lint_goarch) CGO_ENABLED=0 $(golangci_lint_path) run --timeout 5m -E testableexamples
.PHONY: format
format:
@go run $(gofumpt) -l -w .
@go run $(gosimports) -local github.com/tetratelabs/ -w $(shell find . -name '*.go' -type f)
@go run $(asmfmt) -w $(shell find . -name '*.s' -type f)
.PHONY: check # Pre-flight check for pull requests
check:
# The following checks help ensure our platform-specific code used for system
# calls safely falls back on a platform unsupported by the compiler engine.
# This makes sure the intepreter can be used. Most often the package that can
# drift here is "platform" or "sysfs":
#
# Ensure we build on plan9. See #1578
@GOARCH=amd64 GOOS=plan9 go build ./...
# Ensure we build on gojs. See #1526.
@GOARCH=wasm GOOS=js go build ./...
# Ensure we build on wasip1. See #1526.
@GOARCH=wasm GOOS=wasip1 go build ./...
# Ensure we build on aix. See #1723
@GOARCH=ppc64 GOOS=aix go build ./...
# Ensure we build on windows:
@GOARCH=amd64 GOOS=windows go build ./...
# Ensure we build on an arbitrary operating system:
@GOARCH=amd64 GOOS=dragonfly go build ./...
# Ensure we build on solaris/illumos:
@GOARCH=amd64 GOOS=illumos go build ./...
@GOARCH=amd64 GOOS=solaris go build ./...
# Ensure we build on linux arm for Dapr:
# gh release view -R dapr/dapr --json assets --jq 'first(.assets[] | select(.name = "daprd_linux_arm.tar.gz") | {url, downloadCount})'
@GOARCH=arm GOOS=linux go build ./...
# Ensure we build on linux 386 for Trivy:
# gh release view -R aquasecurity/trivy --json assets --jq 'first(.assets[] | select(.name| test("Linux-32bit.*tar.gz")) | {url, downloadCount})'
@GOARCH=386 GOOS=linux go build ./...
# Ensure we build on FreeBSD amd64 for Trivy:
# gh release view -R aquasecurity/trivy --json assets --jq 'first(.assets[] | select(.name| test("FreeBSD-64bit.*tar.gz")) | {url, downloadCount})'
@GOARCH=amd64 GOOS=freebsd go build ./...
@$(MAKE) lint golangci_lint_goarch=arm64
@$(MAKE) lint golangci_lint_goarch=amd64
@$(MAKE) format
@go mod tidy
@if [ ! -z "`git status -s`" ]; then \
echo "The following differences will fail CI until committed:"; \
git diff --exit-code; \
fi
.PHONY: site
site: ## Serve website content
@git submodule update --init
@cd site && go run $(hugo) server --minify --disableFastRender --baseURL localhost:1313 --cleanDestinationDir -D
.PHONY: clean
clean: ## Ensure a clean build
@rm -rf dist build coverage.txt
@go clean -testcache
fuzz_default_flags := --no-trace-compares --sanitizer=none -- -rss_limit_mb=8192
fuzz_timeout_seconds ?= 10
.PHONY: fuzz
fuzz:
@cd internal/integration_test/fuzz && cargo test
@cd internal/integration_test/fuzz && cargo fuzz run logging_no_diff $(fuzz_default_flags) -max_total_time=$(fuzz_timeout_seconds)
@cd internal/integration_test/fuzz && cargo fuzz run no_diff $(fuzz_default_flags) -max_total_time=$(fuzz_timeout_seconds)
@cd internal/integration_test/fuzz && cargo fuzz run memory_no_diff $(fuzz_default_flags) -max_total_time=$(fuzz_timeout_seconds)
@cd internal/integration_test/fuzz && cargo fuzz run validation $(fuzz_default_flags) -max_total_time=$(fuzz_timeout_seconds)
libsodium:
cd ./internal/integration_test/libsodium/testdata && \
curl -s "https://api.github.com/repos/jedisct1/webassembly-benchmarks/contents/2022-12/wasm?ref=7e86d68e99e60130899fbe3b3ab6e9dce9187a7c" \
| jq -r '.[] | .download_url' | xargs -n 1 curl -LO
#### CLI release related ####
VERSION ?= dev
# Default to a dummy version 0.0.1.1, which is always lower than a real release.
# Legal version values should look like 'x.x.x.x' where x is an integer from 0 to 65534.
# https://learn.microsoft.com/en-us/windows/win32/msi/productversion?redirectedfrom=MSDN
# https://stackoverflow.com/questions/9312221/msi-version-numbers
MSI_VERSION ?= 0.0.1.1
non_windows_platforms := darwin_amd64 darwin_arm64 linux_amd64 linux_arm64
non_windows_archives := $(non_windows_platforms:%=dist/wazero_$(VERSION)_%.tar.gz)
windows_platforms := windows_amd64 # TODO: add arm64 windows once we start testing on it.
windows_archives := $(windows_platforms:%=dist/wazero_$(VERSION)_%.zip) $(windows_platforms:%=dist/wazero_$(VERSION)_%.msi)
checksum_txt := dist/wazero_$(VERSION)_checksums.txt
# define macros for multi-platform builds. these parse the filename being built
go-arch = $(if $(findstring amd64,$1),amd64,arm64)
go-os = $(if $(findstring .exe,$1),windows,$(if $(findstring linux,$1),linux,darwin))
# msi-arch is a macro so we can detect it based on the file naming convention
msi-arch = $(if $(findstring amd64,$1),x64,arm64)
build/wazero_%/wazero:
$(call go-build,$@,$<)
build/wazero_%/wazero.exe:
$(call go-build,$@,$<)
dist/wazero_$(VERSION)_%.tar.gz: build/wazero_%/wazero
@echo tar.gz "tarring $@"
@mkdir -p $(@D)
# On Windows, we pass the special flag `--mode='+rx' to ensure that we set the executable flag.
# This is only supported by GNU Tar, so we set it conditionally.
@tar -C $(<D) -cpzf $@ $(if $(findstring Windows_NT,$(OS)),--mode='+rx',) $(<F)
@echo tar.gz "ok"
define go-build
@echo "building $1"
@# $(go:go=) removes the trailing 'go', so we can insert cross-build variables
@$(go:go=) CGO_ENABLED=0 GOOS=$(call go-os,$1) GOARCH=$(call go-arch,$1) go build \
-ldflags "-s -w -X github.com/tetratelabs/wazero/internal/version.version=$(VERSION)" \
-o $1 $2 ./cmd/wazero
@echo build "ok"
endef
# this makes a marker file ending in .signed to avoid repeatedly calling codesign
%.signed: %
$(call codesign,$<)
@touch $@
# This requires osslsigncode package (apt or brew) or latest windows release from mtrojnar/osslsigncode
#
# Default is self-signed while production should be a Digicert signing key
#
# Ex.
# ```bash
# keytool -genkey -alias wazero -storetype PKCS12 -keyalg RSA -keysize 2048 -storepass wazero-bunch \
# -keystore wazero.p12 -dname "O=wazero,CN=wazero.io" -validity 3650
# ```
WINDOWS_CODESIGN_P12 ?= packaging/msi/wazero.p12
WINDOWS_CODESIGN_PASSWORD ?= wazero-bunch
define codesign
@printf "$(ansi_format_dark)" codesign "signing $1"
@osslsigncode sign -h sha256 -pkcs12 ${WINDOWS_CODESIGN_P12} -pass "${WINDOWS_CODESIGN_PASSWORD}" \
-n "wazero is the zero dependency WebAssembly runtime for Go developers" -i https://wazero.io -t http://timestamp.digicert.com \
$(if $(findstring msi,$(1)),-add-msi-dse) -in $1 -out $1-signed
@mv $1-signed $1
@printf "$(ansi_format_bright)" codesign "ok"
endef
# This task is only supported on Windows, where we use candle.exe (compile wxs to wixobj) and light.exe (link to msi)
dist/wazero_$(VERSION)_%.msi: build/wazero_%/wazero.exe.signed
ifeq ($(OS),Windows_NT)
@echo msi "building $@"
@mkdir -p $(@D)
@candle -nologo -arch $(call msi-arch,$@) -dVersion=$(MSI_VERSION) -dBin=$(<:.signed=) -o build/wazero.wixobj packaging/msi/wazero.wxs
@light -nologo -o $@ build/wazero.wixobj -spdb
$(call codesign,$@)
@echo msi "ok"
endif
dist/wazero_$(VERSION)_%.zip: build/wazero_%/wazero.exe.signed
@echo zip "zipping $@"
@mkdir -p $(@D)
@zip -qj $@ $(<:.signed=)
@echo zip "ok"
# Darwin doesn't have sha256sum. See https://github.com/actions/virtual-environments/issues/90
sha256sum := $(if $(findstring darwin,$(shell go env GOOS)),shasum -a 256,sha256sum)
$(checksum_txt):
@cd $(@D); touch $(@F); $(sha256sum) * >> $(@F)
dist: $(non_windows_archives) $(if $(findstring Windows_NT,$(OS)),$(windows_archives),) $(checksum_txt)

2
vendor/github.com/tetratelabs/wazero/NOTICE generated vendored Normal file
View File

@@ -0,0 +1,2 @@
wazero
Copyright 2020-2023 wazero authors

1587
vendor/github.com/tetratelabs/wazero/RATIONALE.md generated vendored Normal file

File diff suppressed because it is too large Load Diff

139
vendor/github.com/tetratelabs/wazero/README.md generated vendored Normal file
View File

@@ -0,0 +1,139 @@
# wazero: the zero dependency WebAssembly runtime for Go developers
[![Go Reference](https://pkg.go.dev/badge/github.com/tetratelabs/wazero.svg)](https://pkg.go.dev/github.com/tetratelabs/wazero) [![License](https://img.shields.io/badge/License-Apache_2.0-blue.svg)](https://opensource.org/licenses/Apache-2.0)
WebAssembly is a way to safely run code compiled in other languages. Runtimes
execute WebAssembly Modules (Wasm), which are most often binaries with a `.wasm`
extension.
wazero is a WebAssembly Core Specification [1.0][1] and [2.0][2] compliant
runtime written in Go. It has *zero dependencies*, and doesn't rely on CGO.
This means you can run applications in other languages and still keep cross
compilation.
Import wazero and extend your Go application with code written in any language!
## Example
The best way to learn wazero is by trying one of our [examples](examples/README.md). The
most [basic example](examples/basic) extends a Go application with an addition
function defined in WebAssembly.
## Runtime
There are two runtime configurations supported in wazero: _Compiler_ is default:
By default, ex `wazero.NewRuntime(ctx)`, the Compiler is used if supported. You
can also force the interpreter like so:
```go
r := wazero.NewRuntimeWithConfig(ctx, wazero.NewRuntimeConfigInterpreter())
```
### Interpreter
Interpreter is a naive interpreter-based implementation of Wasm virtual
machine. Its implementation doesn't have any platform (GOARCH, GOOS) specific
code, therefore _interpreter_ can be used for any compilation target available
for Go (such as `riscv64`).
### Compiler
Compiler compiles WebAssembly modules into machine code ahead of time (AOT),
during `Runtime.CompileModule`. This means your WebAssembly functions execute
natively at runtime. Compiler is faster than Interpreter, often by order of
magnitude (10x) or more. This is done without host-specific dependencies.
### Conformance
Both runtimes pass WebAssembly Core [1.0][7] and [2.0][14] specification tests
on supported platforms:
| Runtime | Usage | amd64 | arm64 | others |
|:-----------:|:--------------------------------------:|:-----:|:-----:|:------:|
| Interpreter | `wazero.NewRuntimeConfigInterpreter()` | ✅ | ✅ | ✅ |
| Compiler | `wazero.NewRuntimeConfigCompiler()` | ✅ | ✅ | ❌ |
## Support Policy
The below support policy focuses on compatibility concerns of those embedding
wazero into their Go applications.
### wazero
wazero's [1.0 release][15] happened in March 2023, and is [in use][16] by many
projects and production sites.
We offer an API stability promise with semantic versioning. In other words, we
promise to not break any exported function signature without incrementing the
major version. This does not mean no innovation: New features and behaviors
happen with a minor version increment, e.g. 1.0.11 to 1.2.0. We also fix bugs
or change internal details with a patch version, e.g. 1.0.0 to 1.0.1.
You can get the latest version of wazero like this.
```bash
go get github.com/tetratelabs/wazero@latest
```
Please give us a [star][17] if you end up using wazero!
### Go
wazero has no dependencies except Go, so the only source of conflict in your
project's use of wazero is the Go version.
wazero follows the same version policy as Go's [Release Policy][10]: two
versions. wazero will ensure these versions work and bugs are valid if there's
an issue with a current Go version.
Additionally, wazero intentionally delays usage of language or standard library
features one additional version. For example, when Go 1.29 is released, wazero
can use language features or standard libraries added in 1.27. This is a
convenience for embedders who have a slower version policy than Go. However,
only supported Go versions may be used to raise support issues.
### Platform
wazero has two runtime modes: Interpreter and Compiler. The only supported operating
systems are ones we test, but that doesn't necessarily mean other operating
system versions won't work.
We currently test Linux (Ubuntu and scratch), MacOS and Windows as packaged by
[GitHub Actions][11], as well as nested VMs running on Linux for FreeBSD, NetBSD,
OpenBSD, DragonFly BSD, illumos and Solaris.
We also test cross compilation for many `GOOS` and `GOARCH` combinations.
* Interpreter
* Linux is tested on amd64 (native) as well arm64 and riscv64 via emulation.
* Windows, FreeBSD, NetBSD, OpenBSD, DragonFly BSD, illumos and Solaris are
tested only on amd64.
* macOS is tested only on arm64.
* Compiler
* Linux is tested on amd64 (native) as well arm64 via emulation.
* Windows, FreeBSD, NetBSD, DragonFly BSD, illumos and Solaris are
tested only on amd64.
* macOS is tested only on arm64.
wazero has no dependencies and doesn't require CGO. This means it can also be
embedded in an application that doesn't use an operating system. This is a main
differentiator between wazero and alternatives.
We verify zero dependencies by running tests in Docker's [scratch image][12].
This approach ensures compatibility with any parent image.
-----
wazero is a registered trademark of Tetrate.io, Inc. in the United States and/or other countries
[1]: https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/
[2]: https://www.w3.org/TR/2022/WD-wasm-core-2-20220419/
[4]: https://github.com/WebAssembly/meetings/blob/main/process/subgroups.md
[5]: https://github.com/WebAssembly/WASI
[6]: https://pkg.go.dev/golang.org/x/sys/unix
[7]: https://github.com/WebAssembly/spec/tree/wg-1.0/test/core
[9]: https://github.com/tetratelabs/wazero/issues/506
[10]: https://go.dev/doc/devel/release
[11]: https://github.com/actions/virtual-environments
[12]: https://docs.docker.com/develop/develop-images/baseimages/#create-a-simple-parent-image-using-scratch
[13]: https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md
[14]: https://github.com/WebAssembly/spec/tree/d39195773112a22b245ffbe864bab6d1182ccb06/test/core
[15]: https://tetrate.io/blog/introducing-wazero-from-tetrate/
[16]: https://wazero.io/community/users/
[17]: https://github.com/tetratelabs/wazero/stargazers

214
vendor/github.com/tetratelabs/wazero/api/features.go generated vendored Normal file
View File

@@ -0,0 +1,214 @@
package api
import (
"fmt"
"strings"
)
// CoreFeatures is a bit flag of WebAssembly Core specification features. See
// https://github.com/WebAssembly/proposals for proposals and their status.
//
// Constants define individual features, such as CoreFeatureMultiValue, or
// groups of "finished" features, assigned to a WebAssembly Core Specification
// version, e.g. CoreFeaturesV1 or CoreFeaturesV2.
//
// Note: Numeric values are not intended to be interpreted except as bit flags.
type CoreFeatures uint64
// CoreFeaturesV1 are features included in the WebAssembly Core Specification
// 1.0. As of late 2022, this is the only version that is a Web Standard (W3C
// Recommendation).
//
// See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/
const CoreFeaturesV1 = CoreFeatureMutableGlobal
// CoreFeaturesV2 are features included in the WebAssembly Core Specification
// 2.0 (20220419). As of late 2022, version 2.0 is a W3C working draft, not yet
// a Web Standard (W3C Recommendation).
//
// See https://www.w3.org/TR/2022/WD-wasm-core-2-20220419/appendix/changes.html#release-1-1
const CoreFeaturesV2 = CoreFeaturesV1 |
CoreFeatureBulkMemoryOperations |
CoreFeatureMultiValue |
CoreFeatureNonTrappingFloatToIntConversion |
CoreFeatureReferenceTypes |
CoreFeatureSignExtensionOps |
CoreFeatureSIMD
const (
// CoreFeatureBulkMemoryOperations adds instructions modify ranges of
// memory or table entries ("bulk-memory-operations"). This is included in
// CoreFeaturesV2, but not CoreFeaturesV1.
//
// Here are the notable effects:
// - Adds `memory.fill`, `memory.init`, `memory.copy` and `data.drop`
// instructions.
// - Adds `table.init`, `table.copy` and `elem.drop` instructions.
// - Introduces a "passive" form of element and data segments.
// - Stops checking "active" element and data segment boundaries at
// compile-time, meaning they can error at runtime.
//
// Note: "bulk-memory-operations" is mixed with the "reference-types"
// proposal due to the WebAssembly Working Group merging them
// "mutually dependent". Therefore, enabling this feature requires enabling
// CoreFeatureReferenceTypes, and vice-versa.
//
// See https://github.com/WebAssembly/spec/blob/wg-2.0.draft1/proposals/bulk-memory-operations/Overview.md
// https://github.com/WebAssembly/spec/blob/wg-2.0.draft1/proposals/reference-types/Overview.md and
// https://github.com/WebAssembly/spec/pull/1287
CoreFeatureBulkMemoryOperations CoreFeatures = 1 << iota
// CoreFeatureMultiValue enables multiple values ("multi-value"). This is
// included in CoreFeaturesV2, but not CoreFeaturesV1.
//
// Here are the notable effects:
// - Function (`func`) types allow more than one result.
// - Block types (`block`, `loop` and `if`) can be arbitrary function
// types.
//
// See https://github.com/WebAssembly/spec/blob/wg-2.0.draft1/proposals/multi-value/Overview.md
CoreFeatureMultiValue
// CoreFeatureMutableGlobal allows globals to be mutable. This is included
// in both CoreFeaturesV1 and CoreFeaturesV2.
//
// When false, an api.Global can never be cast to an api.MutableGlobal, and
// any wasm that includes global vars will fail to parse.
CoreFeatureMutableGlobal
// CoreFeatureNonTrappingFloatToIntConversion enables non-trapping
// float-to-int conversions ("nontrapping-float-to-int-conversion"). This
// is included in CoreFeaturesV2, but not CoreFeaturesV1.
//
// The only effect of enabling is allowing the following instructions,
// which return 0 on NaN instead of panicking.
// - `i32.trunc_sat_f32_s`
// - `i32.trunc_sat_f32_u`
// - `i32.trunc_sat_f64_s`
// - `i32.trunc_sat_f64_u`
// - `i64.trunc_sat_f32_s`
// - `i64.trunc_sat_f32_u`
// - `i64.trunc_sat_f64_s`
// - `i64.trunc_sat_f64_u`
//
// See https://github.com/WebAssembly/spec/blob/wg-2.0.draft1/proposals/nontrapping-float-to-int-conversion/Overview.md
CoreFeatureNonTrappingFloatToIntConversion
// CoreFeatureReferenceTypes enables various instructions and features
// related to table and new reference types. This is included in
// CoreFeaturesV2, but not CoreFeaturesV1.
//
// - Introduction of new value types: `funcref` and `externref`.
// - Support for the following new instructions:
// - `ref.null`
// - `ref.func`
// - `ref.is_null`
// - `table.fill`
// - `table.get`
// - `table.grow`
// - `table.set`
// - `table.size`
// - Support for multiple tables per module:
// - `call_indirect`, `table.init`, `table.copy` and `elem.drop`
// - Support for instructions can take non-zero table index.
// - Element segments can take non-zero table index.
//
// Note: "reference-types" is mixed with the "bulk-memory-operations"
// proposal due to the WebAssembly Working Group merging them
// "mutually dependent". Therefore, enabling this feature requires enabling
// CoreFeatureBulkMemoryOperations, and vice-versa.
//
// See https://github.com/WebAssembly/spec/blob/wg-2.0.draft1/proposals/bulk-memory-operations/Overview.md
// https://github.com/WebAssembly/spec/blob/wg-2.0.draft1/proposals/reference-types/Overview.md and
// https://github.com/WebAssembly/spec/pull/1287
CoreFeatureReferenceTypes
// CoreFeatureSignExtensionOps enables sign extension instructions
// ("sign-extension-ops"). This is included in CoreFeaturesV2, but not
// CoreFeaturesV1.
//
// Adds instructions:
// - `i32.extend8_s`
// - `i32.extend16_s`
// - `i64.extend8_s`
// - `i64.extend16_s`
// - `i64.extend32_s`
//
// See https://github.com/WebAssembly/spec/blob/wg-2.0.draft1/proposals/sign-extension-ops/Overview.md
CoreFeatureSignExtensionOps
// CoreFeatureSIMD enables the vector value type and vector instructions
// (aka SIMD). This is included in CoreFeaturesV2, but not CoreFeaturesV1.
//
// Note: The instruction list is too long to enumerate in godoc.
// See https://github.com/WebAssembly/spec/blob/wg-2.0.draft1/proposals/simd/SIMD.md
CoreFeatureSIMD
// Update experimental/features.go when adding elements here.
)
// SetEnabled enables or disables the feature or group of features.
func (f CoreFeatures) SetEnabled(feature CoreFeatures, val bool) CoreFeatures {
if val {
return f | feature
}
return f &^ feature
}
// IsEnabled returns true if the feature (or group of features) is enabled.
func (f CoreFeatures) IsEnabled(feature CoreFeatures) bool {
return f&feature != 0
}
// RequireEnabled returns an error if the feature (or group of features) is not
// enabled.
func (f CoreFeatures) RequireEnabled(feature CoreFeatures) error {
if f&feature == 0 {
return fmt.Errorf("feature %q is disabled", feature)
}
return nil
}
// String implements fmt.Stringer by returning each enabled feature.
func (f CoreFeatures) String() string {
var builder strings.Builder
for i := 0; i <= 63; i++ { // cycle through all bits to reduce code and maintenance
target := CoreFeatures(1 << i)
if f.IsEnabled(target) {
if name := featureName(target); name != "" {
if builder.Len() > 0 {
builder.WriteByte('|')
}
builder.WriteString(name)
}
}
}
return builder.String()
}
func featureName(f CoreFeatures) string {
switch f {
case CoreFeatureMutableGlobal:
// match https://github.com/WebAssembly/mutable-global
return "mutable-global"
case CoreFeatureSignExtensionOps:
// match https://github.com/WebAssembly/spec/blob/wg-2.0.draft1/proposals/sign-extension-ops/Overview.md
return "sign-extension-ops"
case CoreFeatureMultiValue:
// match https://github.com/WebAssembly/spec/blob/wg-2.0.draft1/proposals/multi-value/Overview.md
return "multi-value"
case CoreFeatureNonTrappingFloatToIntConversion:
// match https://github.com/WebAssembly/spec/blob/wg-2.0.draft1/proposals/nontrapping-float-to-int-conversion/Overview.md
return "nontrapping-float-to-int-conversion"
case CoreFeatureBulkMemoryOperations:
// match https://github.com/WebAssembly/spec/blob/wg-2.0.draft1/proposals/bulk-memory-operations/Overview.md
return "bulk-memory-operations"
case CoreFeatureReferenceTypes:
// match https://github.com/WebAssembly/spec/blob/wg-2.0.draft1/proposals/reference-types/Overview.md
return "reference-types"
case CoreFeatureSIMD:
// match https://github.com/WebAssembly/spec/blob/wg-2.0.draft1/proposals/simd/SIMD.md
return "simd"
}
return ""
}

766
vendor/github.com/tetratelabs/wazero/api/wasm.go generated vendored Normal file
View File

@@ -0,0 +1,766 @@
// Package api includes constants and interfaces used by both end-users and internal implementations.
package api
import (
"context"
"fmt"
"math"
"github.com/tetratelabs/wazero/internal/internalapi"
)
// ExternType classifies imports and exports with their respective types.
//
// See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#external-types%E2%91%A0
type ExternType = byte
const (
ExternTypeFunc ExternType = 0x00
ExternTypeTable ExternType = 0x01
ExternTypeMemory ExternType = 0x02
ExternTypeGlobal ExternType = 0x03
)
// The below are exported to consolidate parsing behavior for external types.
const (
// ExternTypeFuncName is the name of the WebAssembly 1.0 (20191205) Text Format field for ExternTypeFunc.
ExternTypeFuncName = "func"
// ExternTypeTableName is the name of the WebAssembly 1.0 (20191205) Text Format field for ExternTypeTable.
ExternTypeTableName = "table"
// ExternTypeMemoryName is the name of the WebAssembly 1.0 (20191205) Text Format field for ExternTypeMemory.
ExternTypeMemoryName = "memory"
// ExternTypeGlobalName is the name of the WebAssembly 1.0 (20191205) Text Format field for ExternTypeGlobal.
ExternTypeGlobalName = "global"
)
// ExternTypeName returns the name of the WebAssembly 1.0 (20191205) Text Format field of the given type.
//
// See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#exports%E2%91%A4
func ExternTypeName(et ExternType) string {
switch et {
case ExternTypeFunc:
return ExternTypeFuncName
case ExternTypeTable:
return ExternTypeTableName
case ExternTypeMemory:
return ExternTypeMemoryName
case ExternTypeGlobal:
return ExternTypeGlobalName
}
return fmt.Sprintf("%#x", et)
}
// ValueType describes a parameter or result type mapped to a WebAssembly
// function signature.
//
// The following describes how to convert between Wasm and Golang types:
//
// - ValueTypeI32 - EncodeU32 DecodeU32 for uint32 / EncodeI32 DecodeI32 for int32
// - ValueTypeI64 - uint64(int64)
// - ValueTypeF32 - EncodeF32 DecodeF32 from float32
// - ValueTypeF64 - EncodeF64 DecodeF64 from float64
// - ValueTypeExternref - unintptr(unsafe.Pointer(p)) where p is any pointer
// type in Go (e.g. *string)
//
// e.g. Given a Text Format type use (param i64) (result i64), no conversion is
// necessary.
//
// results, _ := fn(ctx, input)
// result := result[0]
//
// e.g. Given a Text Format type use (param f64) (result f64), conversion is
// necessary.
//
// results, _ := fn(ctx, api.EncodeF64(input))
// result := api.DecodeF64(result[0])
//
// Note: This is a type alias as it is easier to encode and decode in the
// binary format.
//
// See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#binary-valtype
type ValueType = byte
const (
// ValueTypeI32 is a 32-bit integer.
ValueTypeI32 ValueType = 0x7f
// ValueTypeI64 is a 64-bit integer.
ValueTypeI64 ValueType = 0x7e
// ValueTypeF32 is a 32-bit floating point number.
ValueTypeF32 ValueType = 0x7d
// ValueTypeF64 is a 64-bit floating point number.
ValueTypeF64 ValueType = 0x7c
// ValueTypeExternref is a externref type.
//
// Note: in wazero, externref type value are opaque raw 64-bit pointers,
// and the ValueTypeExternref type in the signature will be translated as
// uintptr in wazero's API level.
//
// For example, given the import function:
// (func (import "env" "f") (param externref) (result externref))
//
// This can be defined in Go as:
// r.NewHostModuleBuilder("env").
// NewFunctionBuilder().
// WithFunc(func(context.Context, _ uintptr) (_ uintptr) { return }).
// Export("f")
//
// Note: The usage of this type is toggled with api.CoreFeatureBulkMemoryOperations.
ValueTypeExternref ValueType = 0x6f
)
// ValueTypeName returns the type name of the given ValueType as a string.
// These type names match the names used in the WebAssembly text format.
//
// Note: This returns "unknown", if an undefined ValueType value is passed.
func ValueTypeName(t ValueType) string {
switch t {
case ValueTypeI32:
return "i32"
case ValueTypeI64:
return "i64"
case ValueTypeF32:
return "f32"
case ValueTypeF64:
return "f64"
case ValueTypeExternref:
return "externref"
}
return "unknown"
}
// Module is a sandboxed, ready to execute Wasm module. This can be used to get exported functions, etc.
//
// In WebAssembly terminology, this corresponds to a "Module Instance", but wazero calls pre-instantiation module as
// "Compiled Module" as in wazero.CompiledModule, therefore we call this post-instantiation module simply "Module".
// See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#module-instances%E2%91%A0
//
// # Notes
//
// - This is an interface for decoupling, not third-party implementations.
// All implementations are in wazero.
// - Closing the wazero.Runtime closes any Module it instantiated.
type Module interface {
fmt.Stringer
// Name is the name this module was instantiated with. Exported functions can be imported with this name.
Name() string
// Memory returns a memory defined in this module or nil if there are none wasn't.
Memory() Memory
// ExportedFunction returns a function exported from this module or nil if it wasn't.
//
// # Notes
// - The default wazero.ModuleConfig attempts to invoke `_start`, which
// in rare cases can close the module. When in doubt, check IsClosed prior
// to invoking a function export after instantiation.
// - The semantics of host functions assumes the existence of an "importing module" because, for example, the host function needs access to
// the memory of the importing module. Therefore, direct use of ExportedFunction is forbidden for host modules.
// Practically speaking, it is usually meaningless to directly call a host function from Go code as it is already somewhere in Go code.
ExportedFunction(name string) Function
// ExportedFunctionDefinitions returns all the exported function
// definitions in this module, keyed on export name.
ExportedFunctionDefinitions() map[string]FunctionDefinition
// TODO: Table
// ExportedMemory returns a memory exported from this module or nil if it wasn't.
//
// WASI modules require exporting a Memory named "memory". This means that a module successfully initialized
// as a WASI Command or Reactor will never return nil for this name.
//
// See https://github.com/WebAssembly/WASI/blob/snapshot-01/design/application-abi.md#current-unstable-abi
ExportedMemory(name string) Memory
// ExportedMemoryDefinitions returns all the exported memory definitions
// in this module, keyed on export name.
//
// Note: As of WebAssembly Core Specification 2.0, there can be at most one
// memory.
ExportedMemoryDefinitions() map[string]MemoryDefinition
// ExportedGlobal a global exported from this module or nil if it wasn't.
ExportedGlobal(name string) Global
// CloseWithExitCode releases resources allocated for this Module. Use a non-zero exitCode parameter to indicate a
// failure to ExportedFunction callers.
//
// The error returned here, if present, is about resource de-allocation (such as I/O errors). Only the last error is
// returned, so a non-nil return means at least one error happened. Regardless of error, this Module will
// be removed, making its name available again.
//
// Calling this inside a host function is safe, and may cause ExportedFunction callers to receive a sys.ExitError
// with the exitCode.
CloseWithExitCode(ctx context.Context, exitCode uint32) error
// Closer closes this module by delegating to CloseWithExitCode with an exit code of zero.
Closer
// IsClosed returns true if the module is closed, so no longer usable.
//
// This can happen for the following reasons:
// - Closer was called directly.
// - A guest function called Closer indirectly, such as `_start` calling
// `proc_exit`, which internally closed the module.
// - wazero.RuntimeConfig `WithCloseOnContextDone` was enabled and a
// context completion closed the module.
//
// Where any of the above are possible, check this value before calling an
// ExportedFunction, even if you didn't formerly receive a sys.ExitError.
// sys.ExitError is only returned on non-zero code, something that closes
// the module successfully will not result it one.
IsClosed() bool
internalapi.WazeroOnly
}
// Closer closes a resource.
//
// # Notes
//
// - This is an interface for decoupling, not third-party implementations.
// All implementations are in wazero.
type Closer interface {
// Close closes the resource.
//
// Note: The context parameter is used for value lookup, such as for
// logging. A canceled or otherwise done context will not prevent Close
// from succeeding.
Close(context.Context) error
}
// ExportDefinition is a WebAssembly type exported in a module
// (wazero.CompiledModule).
//
// See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#exports%E2%91%A0
//
// # Notes
//
// - This is an interface for decoupling, not third-party implementations.
// All implementations are in wazero.
type ExportDefinition interface {
// ModuleName is the possibly empty name of the module defining this
// export.
//
// Note: This may be different from Module.Name, because a compiled module
// can be instantiated multiple times as different names.
ModuleName() string
// Index is the position in the module's index, imports first.
Index() uint32
// Import returns true with the module and name when this was imported.
// Otherwise, it returns false.
//
// Note: Empty string is valid for both names in the WebAssembly Core
// Specification, so "" "" is possible.
Import() (moduleName, name string, isImport bool)
// ExportNames include all exported names.
//
// Note: The empty name is allowed in the WebAssembly Core Specification,
// so "" is possible.
ExportNames() []string
internalapi.WazeroOnly
}
// MemoryDefinition is a WebAssembly memory exported in a module
// (wazero.CompiledModule). Units are in pages (64KB).
//
// See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#exports%E2%91%A0
//
// # Notes
//
// - This is an interface for decoupling, not third-party implementations.
// All implementations are in wazero.
type MemoryDefinition interface {
ExportDefinition
// Min returns the possibly zero initial count of 64KB pages.
Min() uint32
// Max returns the possibly zero max count of 64KB pages, or false if
// unbounded.
Max() (uint32, bool)
internalapi.WazeroOnly
}
// FunctionDefinition is a WebAssembly function exported in a module
// (wazero.CompiledModule).
//
// See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#exports%E2%91%A0
//
// # Notes
//
// - This is an interface for decoupling, not third-party implementations.
// All implementations are in wazero.
type FunctionDefinition interface {
ExportDefinition
// Name is the module-defined name of the function, which is not necessarily
// the same as its export name.
Name() string
// DebugName identifies this function based on its Index or Name in the
// module. This is used for errors and stack traces. e.g. "env.abort".
//
// When the function name is empty, a substitute name is generated by
// prefixing '$' to its position in the index. Ex ".$0" is the
// first function (possibly imported) in an unnamed module.
//
// The format is dot-delimited module and function name, but there are no
// restrictions on the module and function name. This means either can be
// empty or include dots. e.g. "x.x.x" could mean module "x" and name "x.x",
// or it could mean module "x.x" and name "x".
//
// Note: This name is stable regardless of import or export. For example,
// if Import returns true, the value is still based on the Name or Index
// and not the imported function name.
DebugName() string
// GoFunction is non-nil when implemented by the embedder instead of a wasm
// binary, e.g. via wazero.HostModuleBuilder
//
// The expected results are nil, GoFunction or GoModuleFunction.
GoFunction() interface{}
// ParamTypes are the possibly empty sequence of value types accepted by a
// function with this signature.
//
// See ValueType documentation for encoding rules.
ParamTypes() []ValueType
// ParamNames are index-correlated with ParamTypes or nil if not available
// for one or more parameters.
ParamNames() []string
// ResultTypes are the results of the function.
//
// When WebAssembly 1.0 (20191205), there can be at most one result.
// See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#result-types%E2%91%A0
//
// See ValueType documentation for encoding rules.
ResultTypes() []ValueType
// ResultNames are index-correlated with ResultTypes or nil if not
// available for one or more results.
ResultNames() []string
internalapi.WazeroOnly
}
// Function is a WebAssembly function exported from an instantiated module
// (wazero.Runtime InstantiateModule).
//
// See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#syntax-func
//
// # Notes
//
// - This is an interface for decoupling, not third-party implementations.
// All implementations are in wazero.
type Function interface {
// Definition is metadata about this function from its defining module.
Definition() FunctionDefinition
// Call invokes the function with the given parameters and returns any
// results or an error for any failure looking up or invoking the function.
//
// Encoding is described in Definition, and supplying an incorrect count of
// parameters vs FunctionDefinition.ParamTypes is an error.
//
// If the exporting Module was closed during this call, the error returned
// may be a sys.ExitError. See Module.CloseWithExitCode for details.
//
// Call is not goroutine-safe, therefore it is recommended to create
// another Function if you want to invoke the same function concurrently.
// On the other hand, sequential invocations of Call is allowed.
// However, this should not be called multiple times until the previous Call returns.
//
// To safely encode/decode params/results expressed as uint64, users are encouraged to
// use api.EncodeXXX or DecodeXXX functions. See the docs on api.ValueType.
//
// When RuntimeConfig.WithCloseOnContextDone is toggled, the invocation of this Call method is ensured to be closed
// whenever one of the three conditions is met. In the event of close, sys.ExitError will be returned and
// the api.Module from which this api.Function is derived will be made closed. See the documentation of
// WithCloseOnContextDone on wazero.RuntimeConfig for detail. See examples in context_done_example_test.go for
// the end-to-end demonstrations of how these terminations can be performed.
Call(ctx context.Context, params ...uint64) ([]uint64, error)
// CallWithStack is an optimized variation of Call that saves memory
// allocations when the stack slice is reused across calls.
//
// Stack length must be at least the max of parameter or result length.
// The caller adds parameters in order to the stack, and reads any results
// in order from the stack, except in the error case.
//
// For example, the following reuses the same stack slice to call searchFn
// repeatedly saving one allocation per iteration:
//
// stack := make([]uint64, 4)
// for i, search := range searchParams {
// // copy the next params to the stack
// copy(stack, search)
// if err := searchFn.CallWithStack(ctx, stack); err != nil {
// return err
// } else if stack[0] == 1 { // found
// return i // searchParams[i] matched!
// }
// }
//
// # Notes
//
// - This is similar to GoModuleFunction, except for using calling functions
// instead of implementing them. Moreover, this is used regardless of
// whether the callee is a host or wasm defined function.
CallWithStack(ctx context.Context, stack []uint64) error
internalapi.WazeroOnly
}
// GoModuleFunction is a Function implemented in Go instead of a wasm binary.
// The Module parameter is the calling module, used to access memory or
// exported functions. See GoModuleFunc for an example.
//
// The stack is includes any parameters encoded according to their ValueType.
// Its length is the max of parameter or result length. When there are results,
// write them in order beginning at index zero. Do not use the stack after the
// function returns.
//
// Here's a typical way to read three parameters and write back one.
//
// // read parameters off the stack in index order
// argv, argvBuf := api.DecodeU32(stack[0]), api.DecodeU32(stack[1])
//
// // write results back to the stack in index order
// stack[0] = api.EncodeU32(ErrnoSuccess)
//
// This function can be non-deterministic or cause side effects. It also
// has special properties not defined in the WebAssembly Core specification.
// Notably, this uses the caller's memory (via Module.Memory). See
// https://www.w3.org/TR/wasm-core-1/#host-functions%E2%91%A0
//
// Most end users will not define functions directly with this, as they will
// use reflection or code generators instead. These approaches are more
// idiomatic as they can map go types to ValueType. This type is exposed for
// those willing to trade usability and safety for performance.
//
// To safely decode/encode values from/to the uint64 stack, users are encouraged to use
// api.EncodeXXX or api.DecodeXXX functions. See the docs on api.ValueType.
type GoModuleFunction interface {
Call(ctx context.Context, mod Module, stack []uint64)
}
// GoModuleFunc is a convenience for defining an inlined function.
//
// For example, the following returns an uint32 value read from parameter zero:
//
// api.GoModuleFunc(func(ctx context.Context, mod api.Module, stack []uint64) {
// offset := api.DecodeU32(stack[0]) // read the parameter from the stack
//
// ret, ok := mod.Memory().ReadUint32Le(offset)
// if !ok {
// panic("out of memory")
// }
//
// stack[0] = api.EncodeU32(ret) // add the result back to the stack.
// })
type GoModuleFunc func(ctx context.Context, mod Module, stack []uint64)
// Call implements GoModuleFunction.Call.
func (f GoModuleFunc) Call(ctx context.Context, mod Module, stack []uint64) {
f(ctx, mod, stack)
}
// GoFunction is an optimized form of GoModuleFunction which doesn't require
// the Module parameter. See GoFunc for an example.
//
// For example, this function does not need to use the importing module's
// memory or exported functions.
type GoFunction interface {
Call(ctx context.Context, stack []uint64)
}
// GoFunc is a convenience for defining an inlined function.
//
// For example, the following returns the sum of two uint32 parameters:
//
// api.GoFunc(func(ctx context.Context, stack []uint64) {
// x, y := api.DecodeU32(stack[0]), api.DecodeU32(stack[1])
// stack[0] = api.EncodeU32(x + y)
// })
type GoFunc func(ctx context.Context, stack []uint64)
// Call implements GoFunction.Call.
func (f GoFunc) Call(ctx context.Context, stack []uint64) {
f(ctx, stack)
}
// Global is a WebAssembly 1.0 (20191205) global exported from an instantiated module (wazero.Runtime InstantiateModule).
//
// For example, if the value is not mutable, you can read it once:
//
// offset := module.ExportedGlobal("memory.offset").Get()
//
// Globals are allowed by specification to be mutable. However, this can be disabled by configuration. When in doubt,
// safe cast to find out if the value can change. Here's an example:
//
// offset := module.ExportedGlobal("memory.offset")
// if _, ok := offset.(api.MutableGlobal); ok {
// // value can change
// } else {
// // value is constant
// }
//
// See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#globals%E2%91%A0
//
// # Notes
//
// - This is an interface for decoupling, not third-party implementations.
// All implementations are in wazero.
type Global interface {
fmt.Stringer
// Type describes the numeric type of the global.
Type() ValueType
// Get returns the last known value of this global.
//
// See Type for how to decode this value to a Go type.
Get() uint64
}
// MutableGlobal is a Global whose value can be updated at runtime (variable).
//
// # Notes
//
// - This is an interface for decoupling, not third-party implementations.
// All implementations are in wazero.
type MutableGlobal interface {
Global
// Set updates the value of this global.
//
// See Global.Type for how to encode this value from a Go type.
Set(v uint64)
internalapi.WazeroOnly
}
// Memory allows restricted access to a module's memory. Notably, this does not allow growing.
//
// See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#storage%E2%91%A0
//
// # Notes
//
// - This is an interface for decoupling, not third-party implementations.
// All implementations are in wazero.
// - This includes all value types available in WebAssembly 1.0 (20191205) and all are encoded little-endian.
type Memory interface {
// Definition is metadata about this memory from its defining module.
Definition() MemoryDefinition
// Size returns the memory size in bytes available.
// e.g. If the underlying memory has 1 page: 65536
//
// # Notes
//
// - This overflows (returns zero) if the memory has the maximum 65536 pages.
// As a workaround until wazero v2 to fix the return type, use Grow(0) to obtain the current pages and
// multiply by 65536.
//
// See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#-hrefsyntax-instr-memorymathsfmemorysize%E2%91%A0
Size() uint32
// Grow increases memory by the delta in pages (65536 bytes per page).
// The return val is the previous memory size in pages, or false if the
// delta was ignored as it exceeds MemoryDefinition.Max.
//
// # Notes
//
// - This is the same as the "memory.grow" instruction defined in the
// WebAssembly Core Specification, except returns false instead of -1.
// - When this returns true, any shared views via Read must be refreshed.
//
// See MemorySizer Read and https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#grow-mem
Grow(deltaPages uint32) (previousPages uint32, ok bool)
// ReadByte reads a single byte from the underlying buffer at the offset or returns false if out of range.
ReadByte(offset uint32) (byte, bool)
// ReadUint16Le reads a uint16 in little-endian encoding from the underlying buffer at the offset in or returns
// false if out of range.
ReadUint16Le(offset uint32) (uint16, bool)
// ReadUint32Le reads a uint32 in little-endian encoding from the underlying buffer at the offset in or returns
// false if out of range.
ReadUint32Le(offset uint32) (uint32, bool)
// ReadFloat32Le reads a float32 from 32 IEEE 754 little-endian encoded bits in the underlying buffer at the offset
// or returns false if out of range.
// See math.Float32bits
ReadFloat32Le(offset uint32) (float32, bool)
// ReadUint64Le reads a uint64 in little-endian encoding from the underlying buffer at the offset or returns false
// if out of range.
ReadUint64Le(offset uint32) (uint64, bool)
// ReadFloat64Le reads a float64 from 64 IEEE 754 little-endian encoded bits in the underlying buffer at the offset
// or returns false if out of range.
//
// See math.Float64bits
ReadFloat64Le(offset uint32) (float64, bool)
// Read reads byteCount bytes from the underlying buffer at the offset or
// returns false if out of range.
//
// For example, to search for a NUL-terminated string:
// buf, _ = memory.Read(offset, byteCount)
// n := bytes.IndexByte(buf, 0)
// if n < 0 {
// // Not found!
// }
//
// Write-through
//
// This returns a view of the underlying memory, not a copy. This means any
// writes to the slice returned are visible to Wasm, and any updates from
// Wasm are visible reading the returned slice.
//
// For example:
// buf, _ = memory.Read(offset, byteCount)
// buf[1] = 'a' // writes through to memory, meaning Wasm code see 'a'.
//
// If you don't intend-write through, make a copy of the returned slice.
//
// When to refresh Read
//
// The returned slice disconnects on any capacity change. For example,
// `buf = append(buf, 'a')` might result in a slice that is no longer
// shared. The same exists Wasm side. For example, if Wasm changes its
// memory capacity, ex via "memory.grow"), the host slice is no longer
// shared. Those who need a stable view must set Wasm memory min=max, or
// use wazero.RuntimeConfig WithMemoryCapacityPages to ensure max is always
// allocated.
Read(offset, byteCount uint32) ([]byte, bool)
// WriteByte writes a single byte to the underlying buffer at the offset in or returns false if out of range.
WriteByte(offset uint32, v byte) bool
// WriteUint16Le writes the value in little-endian encoding to the underlying buffer at the offset in or returns
// false if out of range.
WriteUint16Le(offset uint32, v uint16) bool
// WriteUint32Le writes the value in little-endian encoding to the underlying buffer at the offset in or returns
// false if out of range.
WriteUint32Le(offset, v uint32) bool
// WriteFloat32Le writes the value in 32 IEEE 754 little-endian encoded bits to the underlying buffer at the offset
// or returns false if out of range.
//
// See math.Float32bits
WriteFloat32Le(offset uint32, v float32) bool
// WriteUint64Le writes the value in little-endian encoding to the underlying buffer at the offset in or returns
// false if out of range.
WriteUint64Le(offset uint32, v uint64) bool
// WriteFloat64Le writes the value in 64 IEEE 754 little-endian encoded bits to the underlying buffer at the offset
// or returns false if out of range.
//
// See math.Float64bits
WriteFloat64Le(offset uint32, v float64) bool
// Write writes the slice to the underlying buffer at the offset or returns false if out of range.
Write(offset uint32, v []byte) bool
// WriteString writes the string to the underlying buffer at the offset or returns false if out of range.
WriteString(offset uint32, v string) bool
internalapi.WazeroOnly
}
// CustomSection contains the name and raw data of a custom section.
//
// # Notes
//
// - This is an interface for decoupling, not third-party implementations.
// All implementations are in wazero.
type CustomSection interface {
// Name is the name of the custom section
Name() string
// Data is the raw data of the custom section
Data() []byte
internalapi.WazeroOnly
}
// EncodeExternref encodes the input as a ValueTypeExternref.
//
// See DecodeExternref
func EncodeExternref(input uintptr) uint64 {
return uint64(input)
}
// DecodeExternref decodes the input as a ValueTypeExternref.
//
// See EncodeExternref
func DecodeExternref(input uint64) uintptr {
return uintptr(input)
}
// EncodeI32 encodes the input as a ValueTypeI32.
func EncodeI32(input int32) uint64 {
return uint64(uint32(input))
}
// DecodeI32 decodes the input as a ValueTypeI32.
func DecodeI32(input uint64) int32 {
return int32(input)
}
// EncodeU32 encodes the input as a ValueTypeI32.
func EncodeU32(input uint32) uint64 {
return uint64(input)
}
// DecodeU32 decodes the input as a ValueTypeI32.
func DecodeU32(input uint64) uint32 {
return uint32(input)
}
// EncodeI64 encodes the input as a ValueTypeI64.
func EncodeI64(input int64) uint64 {
return uint64(input)
}
// EncodeF32 encodes the input as a ValueTypeF32.
//
// See DecodeF32
func EncodeF32(input float32) uint64 {
return uint64(math.Float32bits(input))
}
// DecodeF32 decodes the input as a ValueTypeF32.
//
// See EncodeF32
func DecodeF32(input uint64) float32 {
return math.Float32frombits(uint32(input))
}
// EncodeF64 encodes the input as a ValueTypeF64.
//
// See EncodeF32
func EncodeF64(input float64) uint64 {
return math.Float64bits(input)
}
// DecodeF64 decodes the input as a ValueTypeF64.
//
// See EncodeF64
func DecodeF64(input uint64) float64 {
return math.Float64frombits(input)
}

367
vendor/github.com/tetratelabs/wazero/builder.go generated vendored Normal file
View File

@@ -0,0 +1,367 @@
package wazero
import (
"context"
"github.com/tetratelabs/wazero/api"
"github.com/tetratelabs/wazero/internal/wasm"
)
// HostFunctionBuilder defines a host function (in Go), so that a
// WebAssembly binary (e.g. %.wasm file) can import and use it.
//
// Here's an example of an addition function:
//
// hostModuleBuilder.NewFunctionBuilder().
// WithFunc(func(cxt context.Context, x, y uint32) uint32 {
// return x + y
// }).
// Export("add")
//
// # Memory
//
// All host functions act on the importing api.Module, including any memory
// exported in its binary (%.wasm file). If you are reading or writing memory,
// it is sand-boxed Wasm memory defined by the guest.
//
// Below, `m` is the importing module, defined in Wasm. `fn` is a host function
// added via Export. This means that `x` was read from memory defined in Wasm,
// not arbitrary memory in the process.
//
// fn := func(ctx context.Context, m api.Module, offset uint32) uint32 {
// x, _ := m.Memory().ReadUint32Le(ctx, offset)
// return x
// }
//
// # Notes
//
// - This is an interface for decoupling, not third-party implementations.
// All implementations are in wazero.
type HostFunctionBuilder interface {
// WithGoFunction is an advanced feature for those who need higher
// performance than WithFunc at the cost of more complexity.
//
// Here's an example addition function:
//
// builder.WithGoFunction(api.GoFunc(func(ctx context.Context, stack []uint64) {
// x, y := api.DecodeI32(stack[0]), api.DecodeI32(stack[1])
// sum := x + y
// stack[0] = api.EncodeI32(sum)
// }), []api.ValueType{api.ValueTypeI32, api.ValueTypeI32}, []api.ValueType{api.ValueTypeI32})
//
// As you can see above, defining in this way implies knowledge of which
// WebAssembly api.ValueType is appropriate for each parameter and result.
//
// See WithGoModuleFunction if you also need to access the calling module.
WithGoFunction(fn api.GoFunction, params, results []api.ValueType) HostFunctionBuilder
// WithGoModuleFunction is an advanced feature for those who need higher
// performance than WithFunc at the cost of more complexity.
//
// Here's an example addition function that loads operands from memory:
//
// builder.WithGoModuleFunction(api.GoModuleFunc(func(ctx context.Context, m api.Module, stack []uint64) {
// mem := m.Memory()
// offset := api.DecodeU32(stack[0])
//
// x, _ := mem.ReadUint32Le(ctx, offset)
// y, _ := mem.ReadUint32Le(ctx, offset + 4) // 32 bits == 4 bytes!
// sum := x + y
//
// stack[0] = api.EncodeU32(sum)
// }), []api.ValueType{api.ValueTypeI32}, []api.ValueType{api.ValueTypeI32})
//
// As you can see above, defining in this way implies knowledge of which
// WebAssembly api.ValueType is appropriate for each parameter and result.
//
// See WithGoFunction if you don't need access to the calling module.
WithGoModuleFunction(fn api.GoModuleFunction, params, results []api.ValueType) HostFunctionBuilder
// WithFunc uses reflect.Value to map a go `func` to a WebAssembly
// compatible Signature. An input that isn't a `func` will fail to
// instantiate.
//
// Here's an example of an addition function:
//
// builder.WithFunc(func(cxt context.Context, x, y uint32) uint32 {
// return x + y
// })
//
// # Defining a function
//
// Except for the context.Context and optional api.Module, all parameters
// or result types must map to WebAssembly numeric value types. This means
// uint32, int32, uint64, int64, float32 or float64.
//
// api.Module may be specified as the second parameter, usually to access
// memory. This is important because there are only numeric types in Wasm.
// The only way to share other data is via writing memory and sharing
// offsets.
//
// builder.WithFunc(func(ctx context.Context, m api.Module, offset uint32) uint32 {
// mem := m.Memory()
// x, _ := mem.ReadUint32Le(ctx, offset)
// y, _ := mem.ReadUint32Le(ctx, offset + 4) // 32 bits == 4 bytes!
// return x + y
// })
//
// This example propagates context properly when calling other functions
// exported in the api.Module:
//
// builder.WithFunc(func(ctx context.Context, m api.Module, offset, byteCount uint32) uint32 {
// fn = m.ExportedFunction("__read")
// results, err := fn(ctx, offset, byteCount)
// --snip--
WithFunc(interface{}) HostFunctionBuilder
// WithName defines the optional module-local name of this function, e.g.
// "random_get"
//
// Note: This is not required to match the Export name.
WithName(name string) HostFunctionBuilder
// WithParameterNames defines optional parameter names of the function
// signature, e.x. "buf", "buf_len"
//
// Note: When defined, names must be provided for all parameters.
WithParameterNames(names ...string) HostFunctionBuilder
// WithResultNames defines optional result names of the function
// signature, e.x. "errno"
//
// Note: When defined, names must be provided for all results.
WithResultNames(names ...string) HostFunctionBuilder
// Export exports this to the HostModuleBuilder as the given name, e.g.
// "random_get"
Export(name string) HostModuleBuilder
}
// HostModuleBuilder is a way to define host functions (in Go), so that a
// WebAssembly binary (e.g. %.wasm file) can import and use them.
//
// Specifically, this implements the host side of an Application Binary
// Interface (ABI) like WASI or AssemblyScript.
//
// For example, this defines and instantiates a module named "env" with one
// function:
//
// ctx := context.Background()
// r := wazero.NewRuntime(ctx)
// defer r.Close(ctx) // This closes everything this Runtime created.
//
// hello := func() {
// println("hello!")
// }
// env, _ := r.NewHostModuleBuilder("env").
// NewFunctionBuilder().WithFunc(hello).Export("hello").
// Instantiate(ctx)
//
// If the same module may be instantiated multiple times, it is more efficient
// to separate steps. Here's an example:
//
// compiled, _ := r.NewHostModuleBuilder("env").
// NewFunctionBuilder().WithFunc(getRandomString).Export("get_random_string").
// Compile(ctx)
//
// env1, _ := r.InstantiateModule(ctx, compiled, wazero.NewModuleConfig().WithName("env.1"))
// env2, _ := r.InstantiateModule(ctx, compiled, wazero.NewModuleConfig().WithName("env.2"))
//
// See HostFunctionBuilder for valid host function signatures and other details.
//
// # Notes
//
// - This is an interface for decoupling, not third-party implementations.
// All implementations are in wazero.
// - HostModuleBuilder is mutable: each method returns the same instance for
// chaining.
// - methods do not return errors, to allow chaining. Any validation errors
// are deferred until Compile.
// - Functions are indexed in order of calls to NewFunctionBuilder as
// insertion ordering is needed by ABI such as Emscripten (invoke_*).
// - The semantics of host functions assumes the existence of an "importing module" because, for example, the host function needs access to
// the memory of the importing module. Therefore, direct use of ExportedFunction is forbidden for host modules.
// Practically speaking, it is usually meaningless to directly call a host function from Go code as it is already somewhere in Go code.
type HostModuleBuilder interface {
// Note: until golang/go#5860, we can't use example tests to embed code in interface godocs.
// NewFunctionBuilder begins the definition of a host function.
NewFunctionBuilder() HostFunctionBuilder
// Compile returns a CompiledModule that can be instantiated by Runtime.
Compile(context.Context) (CompiledModule, error)
// Instantiate is a convenience that calls Compile, then Runtime.InstantiateModule.
// This can fail for reasons documented on Runtime.InstantiateModule.
//
// Here's an example:
//
// ctx := context.Background()
// r := wazero.NewRuntime(ctx)
// defer r.Close(ctx) // This closes everything this Runtime created.
//
// hello := func() {
// println("hello!")
// }
// env, _ := r.NewHostModuleBuilder("env").
// NewFunctionBuilder().WithFunc(hello).Export("hello").
// Instantiate(ctx)
//
// # Notes
//
// - Closing the Runtime has the same effect as closing the result.
// - Fields in the builder are copied during instantiation: Later changes do not affect the instantiated result.
// - To avoid using configuration defaults, use Compile instead.
Instantiate(context.Context) (api.Module, error)
}
// hostModuleBuilder implements HostModuleBuilder
type hostModuleBuilder struct {
r *runtime
moduleName string
exportNames []string
nameToHostFunc map[string]*wasm.HostFunc
}
// NewHostModuleBuilder implements Runtime.NewHostModuleBuilder
func (r *runtime) NewHostModuleBuilder(moduleName string) HostModuleBuilder {
return &hostModuleBuilder{
r: r,
moduleName: moduleName,
nameToHostFunc: map[string]*wasm.HostFunc{},
}
}
// hostFunctionBuilder implements HostFunctionBuilder
type hostFunctionBuilder struct {
b *hostModuleBuilder
fn interface{}
name string
paramNames []string
resultNames []string
}
// WithGoFunction implements HostFunctionBuilder.WithGoFunction
func (h *hostFunctionBuilder) WithGoFunction(fn api.GoFunction, params, results []api.ValueType) HostFunctionBuilder {
h.fn = &wasm.HostFunc{ParamTypes: params, ResultTypes: results, Code: wasm.Code{GoFunc: fn}}
return h
}
// WithGoModuleFunction implements HostFunctionBuilder.WithGoModuleFunction
func (h *hostFunctionBuilder) WithGoModuleFunction(fn api.GoModuleFunction, params, results []api.ValueType) HostFunctionBuilder {
h.fn = &wasm.HostFunc{ParamTypes: params, ResultTypes: results, Code: wasm.Code{GoFunc: fn}}
return h
}
// WithFunc implements HostFunctionBuilder.WithFunc
func (h *hostFunctionBuilder) WithFunc(fn interface{}) HostFunctionBuilder {
h.fn = fn
return h
}
// WithName implements HostFunctionBuilder.WithName
func (h *hostFunctionBuilder) WithName(name string) HostFunctionBuilder {
h.name = name
return h
}
// WithParameterNames implements HostFunctionBuilder.WithParameterNames
func (h *hostFunctionBuilder) WithParameterNames(names ...string) HostFunctionBuilder {
h.paramNames = names
return h
}
// WithResultNames implements HostFunctionBuilder.WithResultNames
func (h *hostFunctionBuilder) WithResultNames(names ...string) HostFunctionBuilder {
h.resultNames = names
return h
}
// Export implements HostFunctionBuilder.Export
func (h *hostFunctionBuilder) Export(exportName string) HostModuleBuilder {
var hostFn *wasm.HostFunc
if fn, ok := h.fn.(*wasm.HostFunc); ok {
hostFn = fn
} else {
hostFn = &wasm.HostFunc{Code: wasm.Code{GoFunc: h.fn}}
}
// Assign any names from the builder
hostFn.ExportName = exportName
if h.name != "" {
hostFn.Name = h.name
}
if len(h.paramNames) != 0 {
hostFn.ParamNames = h.paramNames
}
if len(h.resultNames) != 0 {
hostFn.ResultNames = h.resultNames
}
h.b.ExportHostFunc(hostFn)
return h.b
}
// ExportHostFunc implements wasm.HostFuncExporter
func (b *hostModuleBuilder) ExportHostFunc(fn *wasm.HostFunc) {
if _, ok := b.nameToHostFunc[fn.ExportName]; !ok { // add a new name
b.exportNames = append(b.exportNames, fn.ExportName)
}
b.nameToHostFunc[fn.ExportName] = fn
}
// NewFunctionBuilder implements HostModuleBuilder.NewFunctionBuilder
func (b *hostModuleBuilder) NewFunctionBuilder() HostFunctionBuilder {
return &hostFunctionBuilder{b: b}
}
// Compile implements HostModuleBuilder.Compile
func (b *hostModuleBuilder) Compile(ctx context.Context) (CompiledModule, error) {
module, err := wasm.NewHostModule(b.moduleName, b.exportNames, b.nameToHostFunc, b.r.enabledFeatures)
if err != nil {
return nil, err
} else if err = module.Validate(b.r.enabledFeatures); err != nil {
return nil, err
}
c := &compiledModule{module: module, compiledEngine: b.r.store.Engine}
listeners, err := buildFunctionListeners(ctx, module)
if err != nil {
return nil, err
}
if err = b.r.store.Engine.CompileModule(ctx, module, listeners, false); err != nil {
return nil, err
}
// typeIDs are static and compile-time known.
typeIDs, err := b.r.store.GetFunctionTypeIDs(module.TypeSection)
if err != nil {
return nil, err
}
c.typeIDs = typeIDs
return c, nil
}
// hostModuleInstance is a wrapper around api.Module that prevents calling ExportedFunction.
type hostModuleInstance struct{ api.Module }
// ExportedFunction implements api.Module ExportedFunction.
func (h hostModuleInstance) ExportedFunction(name string) api.Function {
panic("calling ExportedFunction is forbidden on host modules. See the note on ExportedFunction interface")
}
// Instantiate implements HostModuleBuilder.Instantiate
func (b *hostModuleBuilder) Instantiate(ctx context.Context) (api.Module, error) {
if compiled, err := b.Compile(ctx); err != nil {
return nil, err
} else {
compiled.(*compiledModule).closeWithModule = true
m, err := b.r.InstantiateModule(ctx, compiled, NewModuleConfig())
if err != nil {
return nil, err
}
return hostModuleInstance{m}, nil
}
}

123
vendor/github.com/tetratelabs/wazero/cache.go generated vendored Normal file
View File

@@ -0,0 +1,123 @@
package wazero
import (
"context"
"errors"
"fmt"
"os"
"path"
"path/filepath"
goruntime "runtime"
"sync"
"github.com/tetratelabs/wazero/api"
"github.com/tetratelabs/wazero/internal/filecache"
"github.com/tetratelabs/wazero/internal/version"
"github.com/tetratelabs/wazero/internal/wasm"
)
// CompilationCache reduces time spent compiling (Runtime.CompileModule) the same wasm module.
//
// # Notes
//
// - This is an interface for decoupling, not third-party implementations.
// All implementations are in wazero.
// - Instances of this can be reused across multiple runtimes, if configured
// via RuntimeConfig.
// - The cache check happens before the compilation, so if multiple Goroutines are
// trying to compile the same module simultaneously, it is possible that they
// all compile the module. The design here is that the lock isn't held for the action "Compile"
// but only for checking and saving the compiled result. Therefore, we strongly recommend that the embedder
// does the centralized compilation in a single Goroutines (or multiple Goroutines per Wasm binary) to generate cache rather than
// trying to Compile in parallel for a single module. In other words, we always recommend to produce CompiledModule
// share it across multiple Goroutines to avoid trying to compile the same module simultaneously.
type CompilationCache interface{ api.Closer }
// NewCompilationCache returns a new CompilationCache to be passed to RuntimeConfig.
// This configures only in-memory cache, and doesn't persist to the file system. See wazero.NewCompilationCacheWithDir for detail.
//
// The returned CompilationCache can be used to share the in-memory compilation results across multiple instances of wazero.Runtime.
func NewCompilationCache() CompilationCache {
return &cache{}
}
// NewCompilationCacheWithDir is like wazero.NewCompilationCache except the result also writes
// state into the directory specified by `dirname` parameter.
//
// If the dirname doesn't exist, this creates it or returns an error.
//
// Those running wazero as a CLI or frequently restarting a process using the same wasm should
// use this feature to reduce time waiting to compile the same module a second time.
//
// The contents written into dirname are wazero-version specific, meaning different versions of
// wazero will duplicate entries for the same input wasm.
//
// Note: The embedder must safeguard this directory from external changes.
func NewCompilationCacheWithDir(dirname string) (CompilationCache, error) {
c := &cache{}
err := c.ensuresFileCache(dirname, version.GetWazeroVersion())
return c, err
}
// cache implements Cache interface.
type cache struct {
// eng is the engine for this cache. If the cache is configured, the engine is shared across multiple instances of
// Runtime, and its lifetime is not bound to them. Instead, the engine is alive until Cache.Close is called.
engs [engineKindCount]wasm.Engine
fileCache filecache.Cache
initOnces [engineKindCount]sync.Once
}
func (c *cache) initEngine(ek engineKind, ne newEngine, ctx context.Context, features api.CoreFeatures) wasm.Engine {
c.initOnces[ek].Do(func() { c.engs[ek] = ne(ctx, features, c.fileCache) })
return c.engs[ek]
}
// Close implements the same method on the Cache interface.
func (c *cache) Close(_ context.Context) (err error) {
for _, eng := range c.engs {
if eng != nil {
if err = eng.Close(); err != nil {
return
}
}
}
return
}
func (c *cache) ensuresFileCache(dir string, wazeroVersion string) error {
// Resolve a potentially relative directory into an absolute one.
var err error
dir, err = filepath.Abs(dir)
if err != nil {
return err
}
// Ensure the user-supplied directory.
if err = mkdir(dir); err != nil {
return err
}
// Create a version-specific directory to avoid conflicts.
dirname := path.Join(dir, "wazero-"+wazeroVersion+"-"+goruntime.GOARCH+"-"+goruntime.GOOS)
if err = mkdir(dirname); err != nil {
return err
}
c.fileCache = filecache.New(dirname)
return nil
}
func mkdir(dirname string) error {
if st, err := os.Stat(dirname); errors.Is(err, os.ErrNotExist) {
// If the directory not found, create the cache dir.
if err = os.MkdirAll(dirname, 0o700); err != nil {
return fmt.Errorf("create directory %s: %v", dirname, err)
}
} else if err != nil {
return err
} else if !st.IsDir() {
return fmt.Errorf("%s is not dir", dirname)
}
return nil
}

9
vendor/github.com/tetratelabs/wazero/codecov.yml generated vendored Normal file
View File

@@ -0,0 +1,9 @@
# Codecov for main is visible here https://app.codecov.io/gh/tetratelabs/wazero
# We use codecov only as a UI, so we disable PR comments and commit status.
# See https://docs.codecov.com/docs/pull-request-comments
comment: false
coverage:
status:
project: off
patch: off

899
vendor/github.com/tetratelabs/wazero/config.go generated vendored Normal file
View File

@@ -0,0 +1,899 @@
package wazero
import (
"context"
"errors"
"fmt"
"io"
"io/fs"
"math"
"net"
"time"
"github.com/tetratelabs/wazero/api"
experimentalsys "github.com/tetratelabs/wazero/experimental/sys"
"github.com/tetratelabs/wazero/internal/filecache"
"github.com/tetratelabs/wazero/internal/internalapi"
"github.com/tetratelabs/wazero/internal/platform"
internalsock "github.com/tetratelabs/wazero/internal/sock"
internalsys "github.com/tetratelabs/wazero/internal/sys"
"github.com/tetratelabs/wazero/internal/wasm"
"github.com/tetratelabs/wazero/sys"
)
// RuntimeConfig controls runtime behavior, with the default implementation as
// NewRuntimeConfig
//
// The example below explicitly limits to Wasm Core 1.0 features as opposed to
// relying on defaults:
//
// rConfig = wazero.NewRuntimeConfig().WithCoreFeatures(api.CoreFeaturesV1)
//
// # Notes
//
// - This is an interface for decoupling, not third-party implementations.
// All implementations are in wazero.
// - RuntimeConfig is immutable. Each WithXXX function returns a new instance
// including the corresponding change.
type RuntimeConfig interface {
// WithCoreFeatures sets the WebAssembly Core specification features this
// runtime supports. Defaults to api.CoreFeaturesV2.
//
// Example of disabling a specific feature:
// features := api.CoreFeaturesV2.SetEnabled(api.CoreFeatureMutableGlobal, false)
// rConfig = wazero.NewRuntimeConfig().WithCoreFeatures(features)
//
// # Why default to version 2.0?
//
// Many compilers that target WebAssembly require features after
// api.CoreFeaturesV1 by default. For example, TinyGo v0.24+ requires
// api.CoreFeatureBulkMemoryOperations. To avoid runtime errors, wazero
// defaults to api.CoreFeaturesV2, even though it is not yet a Web
// Standard (REC).
WithCoreFeatures(api.CoreFeatures) RuntimeConfig
// WithMemoryLimitPages overrides the maximum pages allowed per memory. The
// default is 65536, allowing 4GB total memory per instance if the maximum is
// not encoded in a Wasm binary. Setting a value larger than default will panic.
//
// This example reduces the largest possible memory size from 4GB to 128KB:
// rConfig = wazero.NewRuntimeConfig().WithMemoryLimitPages(2)
//
// Note: Wasm has 32-bit memory and each page is 65536 (2^16) bytes. This
// implies a max of 65536 (2^16) addressable pages.
// See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#grow-mem
WithMemoryLimitPages(memoryLimitPages uint32) RuntimeConfig
// WithMemoryCapacityFromMax eagerly allocates max memory, unless max is
// not defined. The default is false, which means minimum memory is
// allocated and any call to grow memory results in re-allocations.
//
// This example ensures any memory.grow instruction will never re-allocate:
// rConfig = wazero.NewRuntimeConfig().WithMemoryCapacityFromMax(true)
//
// See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#grow-mem
//
// Note: if the memory maximum is not encoded in a Wasm binary, this
// results in allocating 4GB. See the doc on WithMemoryLimitPages for detail.
WithMemoryCapacityFromMax(memoryCapacityFromMax bool) RuntimeConfig
// WithDebugInfoEnabled toggles DWARF based stack traces in the face of
// runtime errors. Defaults to true.
//
// Those who wish to disable this, can like so:
//
// r := wazero.NewRuntimeWithConfig(wazero.NewRuntimeConfig().WithDebugInfoEnabled(false)
//
// When disabled, a stack trace message looks like:
//
// wasm stack trace:
// .runtime._panic(i32)
// .myFunc()
// .main.main()
// .runtime.run()
// ._start()
//
// When enabled, the stack trace includes source code information:
//
// wasm stack trace:
// .runtime._panic(i32)
// 0x16e2: /opt/homebrew/Cellar/tinygo/0.26.0/src/runtime/runtime_tinygowasm.go:73:6
// .myFunc()
// 0x190b: /Users/XXXXX/wazero/internal/testing/dwarftestdata/testdata/main.go:19:7
// .main.main()
// 0x18ed: /Users/XXXXX/wazero/internal/testing/dwarftestdata/testdata/main.go:4:3
// .runtime.run()
// 0x18cc: /opt/homebrew/Cellar/tinygo/0.26.0/src/runtime/scheduler_none.go:26:10
// ._start()
// 0x18b6: /opt/homebrew/Cellar/tinygo/0.26.0/src/runtime/runtime_wasm_wasi.go:22:5
//
// Note: This only takes into effect when the original Wasm binary has the
// DWARF "custom sections" that are often stripped, depending on
// optimization flags passed to the compiler.
WithDebugInfoEnabled(bool) RuntimeConfig
// WithCompilationCache configures how runtime caches the compiled modules. In the default configuration, compilation results are
// only in-memory until Runtime.Close is closed, and not shareable by multiple Runtime.
//
// Below defines the shared cache across multiple instances of Runtime:
//
// // Creates the new Cache and the runtime configuration with it.
// cache := wazero.NewCompilationCache()
// defer cache.Close()
// config := wazero.NewRuntimeConfig().WithCompilationCache(c)
//
// // Creates two runtimes while sharing compilation caches.
// foo := wazero.NewRuntimeWithConfig(context.Background(), config)
// bar := wazero.NewRuntimeWithConfig(context.Background(), config)
//
// # Cache Key
//
// Cached files are keyed on the version of wazero. This is obtained from go.mod of your application,
// and we use it to verify the compatibility of caches against the currently-running wazero.
// However, if you use this in tests of a package not named as `main`, then wazero cannot obtain the correct
// version of wazero due to the known issue of debug.BuildInfo function: https://github.com/golang/go/issues/33976.
// As a consequence, your cache won't contain the correct version information and always be treated as `dev` version.
// To avoid this issue, you can pass -ldflags "-X github.com/tetratelabs/wazero/internal/version.version=foo" when running tests.
WithCompilationCache(CompilationCache) RuntimeConfig
// WithCustomSections toggles parsing of "custom sections". Defaults to false.
//
// When enabled, it is possible to retrieve custom sections from a CompiledModule:
//
// config := wazero.NewRuntimeConfig().WithCustomSections(true)
// r := wazero.NewRuntimeWithConfig(ctx, config)
// c, err := r.CompileModule(ctx, wasm)
// customSections := c.CustomSections()
WithCustomSections(bool) RuntimeConfig
// WithCloseOnContextDone ensures the executions of functions to be terminated under one of the following circumstances:
//
// - context.Context passed to the Call method of api.Function is canceled during execution. (i.e. ctx by context.WithCancel)
// - context.Context passed to the Call method of api.Function reaches timeout during execution. (i.e. ctx by context.WithTimeout or context.WithDeadline)
// - Close or CloseWithExitCode of api.Module is explicitly called during execution.
//
// This is especially useful when one wants to run untrusted Wasm binaries since otherwise, any invocation of
// api.Function can potentially block the corresponding Goroutine forever. Moreover, it might block the
// entire underlying OS thread which runs the api.Function call. See "Why it's safe to execute runtime-generated
// machine codes against async Goroutine preemption" section in RATIONALE.md for detail.
//
// Upon the termination of the function executions, api.Module is closed.
//
// Note that this comes with a bit of extra cost when enabled. The reason is that internally this forces
// interpreter and compiler runtimes to insert the periodical checks on the conditions above. For that reason,
// this is disabled by default.
//
// See examples in context_done_example_test.go for the end-to-end demonstrations.
//
// When the invocations of api.Function are closed due to this, sys.ExitError is raised to the callers and
// the api.Module from which the functions are derived is made closed.
WithCloseOnContextDone(bool) RuntimeConfig
}
// NewRuntimeConfig returns a RuntimeConfig using the compiler if it is supported in this environment,
// or the interpreter otherwise.
func NewRuntimeConfig() RuntimeConfig {
ret := engineLessConfig.clone()
ret.engineKind = engineKindAuto
return ret
}
type newEngine func(context.Context, api.CoreFeatures, filecache.Cache) wasm.Engine
type runtimeConfig struct {
enabledFeatures api.CoreFeatures
memoryLimitPages uint32
memoryCapacityFromMax bool
engineKind engineKind
dwarfDisabled bool // negative as defaults to enabled
newEngine newEngine
cache CompilationCache
storeCustomSections bool
ensureTermination bool
}
// engineLessConfig helps avoid copy/pasting the wrong defaults.
var engineLessConfig = &runtimeConfig{
enabledFeatures: api.CoreFeaturesV2,
memoryLimitPages: wasm.MemoryLimitPages,
memoryCapacityFromMax: false,
dwarfDisabled: false,
}
type engineKind int
const (
engineKindAuto engineKind = iota - 1
engineKindCompiler
engineKindInterpreter
engineKindCount
)
// NewRuntimeConfigCompiler compiles WebAssembly modules into
// runtime.GOARCH-specific assembly for optimal performance.
//
// The default implementation is AOT (Ahead of Time) compilation, applied at
// Runtime.CompileModule. This allows consistent runtime performance, as well
// the ability to reduce any first request penalty.
//
// Note: While this is technically AOT, this does not imply any action on your
// part. wazero automatically performs ahead-of-time compilation as needed when
// Runtime.CompileModule is invoked.
//
// # Warning
//
// - This panics at runtime if the runtime.GOOS or runtime.GOARCH does not
// support compiler. Use NewRuntimeConfig to safely detect and fallback to
// NewRuntimeConfigInterpreter if needed.
//
// - If you are using wazero in buildmode=c-archive or c-shared, make sure that you set up the alternate signal stack
// by using, e.g. `sigaltstack` combined with `SA_ONSTACK` flag on `sigaction` on Linux,
// before calling any api.Function. This is because the Go runtime does not set up the alternate signal stack
// for c-archive or c-shared modes, and wazero uses the different stack than the calling Goroutine.
// Hence, the signal handler might get invoked on the wazero's stack, which may cause a stack overflow.
// https://github.com/tetratelabs/wazero/blob/2092c0a879f30d49d7b37f333f4547574b8afe0d/internal/integration_test/fuzz/fuzz/tests/sigstack.rs#L19-L36
func NewRuntimeConfigCompiler() RuntimeConfig {
ret := engineLessConfig.clone()
ret.engineKind = engineKindCompiler
return ret
}
// NewRuntimeConfigInterpreter interprets WebAssembly modules instead of compiling them into assembly.
func NewRuntimeConfigInterpreter() RuntimeConfig {
ret := engineLessConfig.clone()
ret.engineKind = engineKindInterpreter
return ret
}
// clone makes a deep copy of this runtime config.
func (c *runtimeConfig) clone() *runtimeConfig {
ret := *c // copy except maps which share a ref
return &ret
}
// WithCoreFeatures implements RuntimeConfig.WithCoreFeatures
func (c *runtimeConfig) WithCoreFeatures(features api.CoreFeatures) RuntimeConfig {
ret := c.clone()
ret.enabledFeatures = features
return ret
}
// WithCloseOnContextDone implements RuntimeConfig.WithCloseOnContextDone
func (c *runtimeConfig) WithCloseOnContextDone(ensure bool) RuntimeConfig {
ret := c.clone()
ret.ensureTermination = ensure
return ret
}
// WithMemoryLimitPages implements RuntimeConfig.WithMemoryLimitPages
func (c *runtimeConfig) WithMemoryLimitPages(memoryLimitPages uint32) RuntimeConfig {
ret := c.clone()
// This panics instead of returning an error as it is unlikely.
if memoryLimitPages > wasm.MemoryLimitPages {
panic(fmt.Errorf("memoryLimitPages invalid: %d > %d", memoryLimitPages, wasm.MemoryLimitPages))
}
ret.memoryLimitPages = memoryLimitPages
return ret
}
// WithCompilationCache implements RuntimeConfig.WithCompilationCache
func (c *runtimeConfig) WithCompilationCache(ca CompilationCache) RuntimeConfig {
ret := c.clone()
ret.cache = ca
return ret
}
// WithMemoryCapacityFromMax implements RuntimeConfig.WithMemoryCapacityFromMax
func (c *runtimeConfig) WithMemoryCapacityFromMax(memoryCapacityFromMax bool) RuntimeConfig {
ret := c.clone()
ret.memoryCapacityFromMax = memoryCapacityFromMax
return ret
}
// WithDebugInfoEnabled implements RuntimeConfig.WithDebugInfoEnabled
func (c *runtimeConfig) WithDebugInfoEnabled(dwarfEnabled bool) RuntimeConfig {
ret := c.clone()
ret.dwarfDisabled = !dwarfEnabled
return ret
}
// WithCustomSections implements RuntimeConfig.WithCustomSections
func (c *runtimeConfig) WithCustomSections(storeCustomSections bool) RuntimeConfig {
ret := c.clone()
ret.storeCustomSections = storeCustomSections
return ret
}
// CompiledModule is a WebAssembly module ready to be instantiated (Runtime.InstantiateModule) as an api.Module.
//
// In WebAssembly terminology, this is a decoded, validated, and possibly also compiled module. wazero avoids using
// the name "Module" for both before and after instantiation as the name conflation has caused confusion.
// See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#semantic-phases%E2%91%A0
//
// # Notes
//
// - This is an interface for decoupling, not third-party implementations.
// All implementations are in wazero.
// - Closing the wazero.Runtime closes any CompiledModule it compiled.
type CompiledModule interface {
// Name returns the module name encoded into the binary or empty if not.
Name() string
// ImportedFunctions returns all the imported functions
// (api.FunctionDefinition) in this module or nil if there are none.
//
// Note: Unlike ExportedFunctions, there is no unique constraint on
// imports.
ImportedFunctions() []api.FunctionDefinition
// ExportedFunctions returns all the exported functions
// (api.FunctionDefinition) in this module keyed on export name.
ExportedFunctions() map[string]api.FunctionDefinition
// ImportedMemories returns all the imported memories
// (api.MemoryDefinition) in this module or nil if there are none.
//
// ## Notes
// - As of WebAssembly Core Specification 2.0, there can be at most one
// memory.
// - Unlike ExportedMemories, there is no unique constraint on imports.
ImportedMemories() []api.MemoryDefinition
// ExportedMemories returns all the exported memories
// (api.MemoryDefinition) in this module keyed on export name.
//
// Note: As of WebAssembly Core Specification 2.0, there can be at most one
// memory.
ExportedMemories() map[string]api.MemoryDefinition
// CustomSections returns all the custom sections
// (api.CustomSection) in this module keyed on the section name.
CustomSections() []api.CustomSection
// Close releases all the allocated resources for this CompiledModule.
//
// Note: It is safe to call Close while having outstanding calls from an
// api.Module instantiated from this.
Close(context.Context) error
}
// compile-time check to ensure compiledModule implements CompiledModule
var _ CompiledModule = &compiledModule{}
type compiledModule struct {
module *wasm.Module
// compiledEngine holds an engine on which `module` is compiled.
compiledEngine wasm.Engine
// closeWithModule prevents leaking compiled code when a module is compiled implicitly.
closeWithModule bool
typeIDs []wasm.FunctionTypeID
}
// Name implements CompiledModule.Name
func (c *compiledModule) Name() (moduleName string) {
if ns := c.module.NameSection; ns != nil {
moduleName = ns.ModuleName
}
return
}
// Close implements CompiledModule.Close
func (c *compiledModule) Close(context.Context) error {
c.compiledEngine.DeleteCompiledModule(c.module)
// It is possible the underlying may need to return an error later, but in any case this matches api.Module.Close.
return nil
}
// ImportedFunctions implements CompiledModule.ImportedFunctions
func (c *compiledModule) ImportedFunctions() []api.FunctionDefinition {
return c.module.ImportedFunctions()
}
// ExportedFunctions implements CompiledModule.ExportedFunctions
func (c *compiledModule) ExportedFunctions() map[string]api.FunctionDefinition {
return c.module.ExportedFunctions()
}
// ImportedMemories implements CompiledModule.ImportedMemories
func (c *compiledModule) ImportedMemories() []api.MemoryDefinition {
return c.module.ImportedMemories()
}
// ExportedMemories implements CompiledModule.ExportedMemories
func (c *compiledModule) ExportedMemories() map[string]api.MemoryDefinition {
return c.module.ExportedMemories()
}
// CustomSections implements CompiledModule.CustomSections
func (c *compiledModule) CustomSections() []api.CustomSection {
ret := make([]api.CustomSection, len(c.module.CustomSections))
for i, d := range c.module.CustomSections {
ret[i] = &customSection{data: d.Data, name: d.Name}
}
return ret
}
// customSection implements wasm.CustomSection
type customSection struct {
internalapi.WazeroOnlyType
name string
data []byte
}
// Name implements wasm.CustomSection.Name
func (c *customSection) Name() string {
return c.name
}
// Data implements wasm.CustomSection.Data
func (c *customSection) Data() []byte {
return c.data
}
// ModuleConfig configures resources needed by functions that have low-level interactions with the host operating
// system. Using this, resources such as STDIN can be isolated, so that the same module can be safely instantiated
// multiple times.
//
// Here's an example:
//
// // Initialize base configuration:
// config := wazero.NewModuleConfig().WithStdout(buf).WithSysNanotime()
//
// // Assign different configuration on each instantiation
// mod, _ := r.InstantiateModule(ctx, compiled, config.WithName("rotate").WithArgs("rotate", "angle=90", "dir=cw"))
//
// While wazero supports Windows as a platform, host functions using ModuleConfig follow a UNIX dialect.
// See RATIONALE.md for design background and relationship to WebAssembly System Interfaces (WASI).
//
// # Notes
//
// - This is an interface for decoupling, not third-party implementations.
// All implementations are in wazero.
// - ModuleConfig is immutable. Each WithXXX function returns a new instance
// including the corresponding change.
type ModuleConfig interface {
// WithArgs assigns command-line arguments visible to an imported function that reads an arg vector (argv). Defaults to
// none. Runtime.InstantiateModule errs if any arg is empty.
//
// These values are commonly read by the functions like "args_get" in "wasi_snapshot_preview1" although they could be
// read by functions imported from other modules.
//
// Similar to os.Args and exec.Cmd Env, many implementations would expect a program name to be argv[0]. However, neither
// WebAssembly nor WebAssembly System Interfaces (WASI) define this. Regardless, you may choose to set the first
// argument to the same value set via WithName.
//
// Note: This does not default to os.Args as that violates sandboxing.
//
// See https://linux.die.net/man/3/argv and https://en.wikipedia.org/wiki/Null-terminated_string
WithArgs(...string) ModuleConfig
// WithEnv sets an environment variable visible to a Module that imports functions. Defaults to none.
// Runtime.InstantiateModule errs if the key is empty or contains a NULL(0) or equals("") character.
//
// Validation is the same as os.Setenv on Linux and replaces any existing value. Unlike exec.Cmd Env, this does not
// default to the current process environment as that would violate sandboxing. This also does not preserve order.
//
// Environment variables are commonly read by the functions like "environ_get" in "wasi_snapshot_preview1" although
// they could be read by functions imported from other modules.
//
// While similar to process configuration, there are no assumptions that can be made about anything OS-specific. For
// example, neither WebAssembly nor WebAssembly System Interfaces (WASI) define concerns processes have, such as
// case-sensitivity on environment keys. For portability, define entries with case-insensitively unique keys.
//
// See https://linux.die.net/man/3/environ and https://en.wikipedia.org/wiki/Null-terminated_string
WithEnv(key, value string) ModuleConfig
// WithFS is a convenience that calls WithFSConfig with an FSConfig of the
// input for the root ("/") guest path.
WithFS(fs.FS) ModuleConfig
// WithFSConfig configures the filesystem available to each guest
// instantiated with this configuration. By default, no file access is
// allowed, so functions like `path_open` result in unsupported errors
// (e.g. syscall.ENOSYS).
WithFSConfig(FSConfig) ModuleConfig
// WithName configures the module name. Defaults to what was decoded from
// the name section. Duplicate names are not allowed in a single Runtime.
//
// Calling this with the empty string "" makes the module anonymous.
// That is useful when you want to instantiate the same CompiledModule multiple times like below:
//
// for i := 0; i < N; i++ {
// // Instantiate a new Wasm module from the already compiled `compiledWasm` anonymously without a name.
// instance, err := r.InstantiateModule(ctx, compiledWasm, wazero.NewModuleConfig().WithName(""))
// // ....
// }
//
// See the `concurrent-instantiation` example for a complete usage.
//
// Non-empty named modules are available for other modules to import by name.
WithName(string) ModuleConfig
// WithStartFunctions configures the functions to call after the module is
// instantiated. Defaults to "_start".
//
// Clearing the default is supported, via `WithStartFunctions()`.
//
// # Notes
//
// - If a start function doesn't exist, it is skipped. However, any that
// do exist are called in order.
// - Start functions are not intended to be called multiple times.
// Functions that should be called multiple times should be invoked
// manually via api.Module's `ExportedFunction` method.
// - Start functions commonly exit the module during instantiation,
// preventing use of any functions later. This is the case in "wasip1",
// which defines the default value "_start".
// - See /RATIONALE.md for motivation of this feature.
WithStartFunctions(...string) ModuleConfig
// WithStderr configures where standard error (file descriptor 2) is written. Defaults to io.Discard.
//
// This writer is most commonly used by the functions like "fd_write" in "wasi_snapshot_preview1" although it could
// be used by functions imported from other modules.
//
// # Notes
//
// - The caller is responsible to close any io.Writer they supply: It is not closed on api.Module Close.
// - This does not default to os.Stderr as that both violates sandboxing and prevents concurrent modules.
//
// See https://linux.die.net/man/3/stderr
WithStderr(io.Writer) ModuleConfig
// WithStdin configures where standard input (file descriptor 0) is read. Defaults to return io.EOF.
//
// This reader is most commonly used by the functions like "fd_read" in "wasi_snapshot_preview1" although it could
// be used by functions imported from other modules.
//
// # Notes
//
// - The caller is responsible to close any io.Reader they supply: It is not closed on api.Module Close.
// - This does not default to os.Stdin as that both violates sandboxing and prevents concurrent modules.
//
// See https://linux.die.net/man/3/stdin
WithStdin(io.Reader) ModuleConfig
// WithStdout configures where standard output (file descriptor 1) is written. Defaults to io.Discard.
//
// This writer is most commonly used by the functions like "fd_write" in "wasi_snapshot_preview1" although it could
// be used by functions imported from other modules.
//
// # Notes
//
// - The caller is responsible to close any io.Writer they supply: It is not closed on api.Module Close.
// - This does not default to os.Stdout as that both violates sandboxing and prevents concurrent modules.
//
// See https://linux.die.net/man/3/stdout
WithStdout(io.Writer) ModuleConfig
// WithWalltime configures the wall clock, sometimes referred to as the
// real time clock. sys.Walltime returns the current unix/epoch time,
// seconds since midnight UTC 1 January 1970, with a nanosecond fraction.
// This defaults to a fake result that increases by 1ms on each reading.
//
// Here's an example that uses a custom clock:
// moduleConfig = moduleConfig.
// WithWalltime(func(context.Context) (sec int64, nsec int32) {
// return clock.walltime()
// }, sys.ClockResolution(time.Microsecond.Nanoseconds()))
//
// # Notes:
// - This does not default to time.Now as that violates sandboxing.
// - This is used to implement host functions such as WASI
// `clock_time_get` with the `realtime` clock ID.
// - Use WithSysWalltime for a usable implementation.
WithWalltime(sys.Walltime, sys.ClockResolution) ModuleConfig
// WithSysWalltime uses time.Now for sys.Walltime with a resolution of 1us
// (1000ns).
//
// See WithWalltime
WithSysWalltime() ModuleConfig
// WithNanotime configures the monotonic clock, used to measure elapsed
// time in nanoseconds. Defaults to a fake result that increases by 1ms
// on each reading.
//
// Here's an example that uses a custom clock:
// moduleConfig = moduleConfig.
// WithNanotime(func(context.Context) int64 {
// return clock.nanotime()
// }, sys.ClockResolution(time.Microsecond.Nanoseconds()))
//
// # Notes:
// - This does not default to time.Since as that violates sandboxing.
// - This is used to implement host functions such as WASI
// `clock_time_get` with the `monotonic` clock ID.
// - Some compilers implement sleep by looping on sys.Nanotime (e.g. Go).
// - If you set this, you should probably set WithNanosleep also.
// - Use WithSysNanotime for a usable implementation.
WithNanotime(sys.Nanotime, sys.ClockResolution) ModuleConfig
// WithSysNanotime uses time.Now for sys.Nanotime with a resolution of 1us.
//
// See WithNanotime
WithSysNanotime() ModuleConfig
// WithNanosleep configures the how to pause the current goroutine for at
// least the configured nanoseconds. Defaults to return immediately.
//
// This example uses a custom sleep function:
// moduleConfig = moduleConfig.
// WithNanosleep(func(ns int64) {
// rel := unix.NsecToTimespec(ns)
// remain := unix.Timespec{}
// for { // loop until no more time remaining
// err := unix.ClockNanosleep(unix.CLOCK_MONOTONIC, 0, &rel, &remain)
// --snip--
//
// # Notes:
// - This does not default to time.Sleep as that violates sandboxing.
// - This is used to implement host functions such as WASI `poll_oneoff`.
// - Some compilers implement sleep by looping on sys.Nanotime (e.g. Go).
// - If you set this, you should probably set WithNanotime also.
// - Use WithSysNanosleep for a usable implementation.
WithNanosleep(sys.Nanosleep) ModuleConfig
// WithOsyield yields the processor, typically to implement spin-wait
// loops. Defaults to return immediately.
//
// # Notes:
// - This primarily supports `sched_yield` in WASI
// - This does not default to runtime.osyield as that violates sandboxing.
WithOsyield(sys.Osyield) ModuleConfig
// WithSysNanosleep uses time.Sleep for sys.Nanosleep.
//
// See WithNanosleep
WithSysNanosleep() ModuleConfig
// WithRandSource configures a source of random bytes. Defaults to return a
// deterministic source. You might override this with crypto/rand.Reader
//
// This reader is most commonly used by the functions like "random_get" in
// "wasi_snapshot_preview1", "seed" in AssemblyScript standard "env", and
// "getRandomData" when runtime.GOOS is "js".
//
// Note: The caller is responsible to close any io.Reader they supply: It
// is not closed on api.Module Close.
WithRandSource(io.Reader) ModuleConfig
}
type moduleConfig struct {
name string
nameSet bool
startFunctions []string
stdin io.Reader
stdout io.Writer
stderr io.Writer
randSource io.Reader
walltime sys.Walltime
walltimeResolution sys.ClockResolution
nanotime sys.Nanotime
nanotimeResolution sys.ClockResolution
nanosleep sys.Nanosleep
osyield sys.Osyield
args [][]byte
// environ is pair-indexed to retain order similar to os.Environ.
environ [][]byte
// environKeys allow overwriting of existing values.
environKeys map[string]int
// fsConfig is the file system configuration for ABI like WASI.
fsConfig FSConfig
// sockConfig is the network listener configuration for ABI like WASI.
sockConfig *internalsock.Config
}
// NewModuleConfig returns a ModuleConfig that can be used for configuring module instantiation.
func NewModuleConfig() ModuleConfig {
return &moduleConfig{
startFunctions: []string{"_start"},
environKeys: map[string]int{},
}
}
// clone makes a deep copy of this module config.
func (c *moduleConfig) clone() *moduleConfig {
ret := *c // copy except maps which share a ref
ret.environKeys = make(map[string]int, len(c.environKeys))
for key, value := range c.environKeys {
ret.environKeys[key] = value
}
return &ret
}
// WithArgs implements ModuleConfig.WithArgs
func (c *moduleConfig) WithArgs(args ...string) ModuleConfig {
ret := c.clone()
ret.args = toByteSlices(args)
return ret
}
func toByteSlices(strings []string) (result [][]byte) {
if len(strings) == 0 {
return
}
result = make([][]byte, len(strings))
for i, a := range strings {
result[i] = []byte(a)
}
return
}
// WithEnv implements ModuleConfig.WithEnv
func (c *moduleConfig) WithEnv(key, value string) ModuleConfig {
ret := c.clone()
// Check to see if this key already exists and update it.
if i, ok := ret.environKeys[key]; ok {
ret.environ[i+1] = []byte(value) // environ is pair-indexed, so the value is 1 after the key.
} else {
ret.environKeys[key] = len(ret.environ)
ret.environ = append(ret.environ, []byte(key), []byte(value))
}
return ret
}
// WithFS implements ModuleConfig.WithFS
func (c *moduleConfig) WithFS(fs fs.FS) ModuleConfig {
var config FSConfig
if fs != nil {
config = NewFSConfig().WithFSMount(fs, "")
}
return c.WithFSConfig(config)
}
// WithFSConfig implements ModuleConfig.WithFSConfig
func (c *moduleConfig) WithFSConfig(config FSConfig) ModuleConfig {
ret := c.clone()
ret.fsConfig = config
return ret
}
// WithName implements ModuleConfig.WithName
func (c *moduleConfig) WithName(name string) ModuleConfig {
ret := c.clone()
ret.nameSet = true
ret.name = name
return ret
}
// WithStartFunctions implements ModuleConfig.WithStartFunctions
func (c *moduleConfig) WithStartFunctions(startFunctions ...string) ModuleConfig {
ret := c.clone()
ret.startFunctions = startFunctions
return ret
}
// WithStderr implements ModuleConfig.WithStderr
func (c *moduleConfig) WithStderr(stderr io.Writer) ModuleConfig {
ret := c.clone()
ret.stderr = stderr
return ret
}
// WithStdin implements ModuleConfig.WithStdin
func (c *moduleConfig) WithStdin(stdin io.Reader) ModuleConfig {
ret := c.clone()
ret.stdin = stdin
return ret
}
// WithStdout implements ModuleConfig.WithStdout
func (c *moduleConfig) WithStdout(stdout io.Writer) ModuleConfig {
ret := c.clone()
ret.stdout = stdout
return ret
}
// WithWalltime implements ModuleConfig.WithWalltime
func (c *moduleConfig) WithWalltime(walltime sys.Walltime, resolution sys.ClockResolution) ModuleConfig {
ret := c.clone()
ret.walltime = walltime
ret.walltimeResolution = resolution
return ret
}
// We choose arbitrary resolutions here because there's no perfect alternative. For example, according to the
// source in time.go, windows monotonic resolution can be 15ms. This chooses arbitrarily 1us for wall time and
// 1ns for monotonic. See RATIONALE.md for more context.
// WithSysWalltime implements ModuleConfig.WithSysWalltime
func (c *moduleConfig) WithSysWalltime() ModuleConfig {
return c.WithWalltime(platform.Walltime, sys.ClockResolution(time.Microsecond.Nanoseconds()))
}
// WithNanotime implements ModuleConfig.WithNanotime
func (c *moduleConfig) WithNanotime(nanotime sys.Nanotime, resolution sys.ClockResolution) ModuleConfig {
ret := c.clone()
ret.nanotime = nanotime
ret.nanotimeResolution = resolution
return ret
}
// WithSysNanotime implements ModuleConfig.WithSysNanotime
func (c *moduleConfig) WithSysNanotime() ModuleConfig {
return c.WithNanotime(platform.Nanotime, sys.ClockResolution(1))
}
// WithNanosleep implements ModuleConfig.WithNanosleep
func (c *moduleConfig) WithNanosleep(nanosleep sys.Nanosleep) ModuleConfig {
ret := *c // copy
ret.nanosleep = nanosleep
return &ret
}
// WithOsyield implements ModuleConfig.WithOsyield
func (c *moduleConfig) WithOsyield(osyield sys.Osyield) ModuleConfig {
ret := *c // copy
ret.osyield = osyield
return &ret
}
// WithSysNanosleep implements ModuleConfig.WithSysNanosleep
func (c *moduleConfig) WithSysNanosleep() ModuleConfig {
return c.WithNanosleep(platform.Nanosleep)
}
// WithRandSource implements ModuleConfig.WithRandSource
func (c *moduleConfig) WithRandSource(source io.Reader) ModuleConfig {
ret := c.clone()
ret.randSource = source
return ret
}
// toSysContext creates a baseline wasm.Context configured by ModuleConfig.
func (c *moduleConfig) toSysContext() (sysCtx *internalsys.Context, err error) {
var environ [][]byte // Intentionally doesn't pre-allocate to reduce logic to default to nil.
// Same validation as syscall.Setenv for Linux
for i := 0; i < len(c.environ); i += 2 {
key, value := c.environ[i], c.environ[i+1]
keyLen := len(key)
if keyLen == 0 {
err = errors.New("environ invalid: empty key")
return
}
valueLen := len(value)
result := make([]byte, keyLen+valueLen+1)
j := 0
for ; j < keyLen; j++ {
if k := key[j]; k == '=' { // NUL enforced in NewContext
err = errors.New("environ invalid: key contains '=' character")
return
} else {
result[j] = k
}
}
result[j] = '='
copy(result[j+1:], value)
environ = append(environ, result)
}
var fs []experimentalsys.FS
var guestPaths []string
if f, ok := c.fsConfig.(*fsConfig); ok {
fs, guestPaths = f.preopens()
}
var listeners []*net.TCPListener
if n := c.sockConfig; n != nil {
if listeners, err = n.BuildTCPListeners(); err != nil {
return
}
}
return internalsys.NewContext(
math.MaxUint32,
c.args,
environ,
c.stdin,
c.stdout,
c.stderr,
c.randSource,
c.walltime, c.walltimeResolution,
c.nanotime, c.nanotimeResolution,
c.nanosleep, c.osyield,
fs, guestPaths,
listeners,
)
}

View File

@@ -0,0 +1,35 @@
package experimental
import (
"context"
"github.com/tetratelabs/wazero/internal/expctxkeys"
)
// Snapshot holds the execution state at the time of a Snapshotter.Snapshot call.
type Snapshot interface {
// Restore sets the Wasm execution state to the capture. Because a host function
// calling this is resetting the pointer to the executation stack, the host function
// will not be able to return values in the normal way. ret is a slice of values the
// host function intends to return from the restored function.
Restore(ret []uint64)
}
// Snapshotter allows host functions to snapshot the WebAssembly execution environment.
type Snapshotter interface {
// Snapshot captures the current execution state.
Snapshot() Snapshot
}
// WithSnapshotter enables snapshots.
// Passing the returned context to a exported function invocation enables snapshots,
// and allows host functions to retrieve the Snapshotter using GetSnapshotter.
func WithSnapshotter(ctx context.Context) context.Context {
return context.WithValue(ctx, expctxkeys.EnableSnapshotterKey{}, struct{}{})
}
// GetSnapshotter gets the Snapshotter from a host function.
// It is only present if WithSnapshotter was called with the function invocation context.
func GetSnapshotter(ctx context.Context) Snapshotter {
return ctx.Value(expctxkeys.SnapshotterKey{}).(Snapshotter)
}

View File

@@ -0,0 +1,63 @@
package experimental
import (
"context"
"github.com/tetratelabs/wazero/internal/expctxkeys"
)
// CloseNotifier is a notification hook, invoked when a module is closed.
//
// Note: This is experimental progress towards #1197, and likely to change. Do
// not expose this in shared libraries as it can cause version locks.
type CloseNotifier interface {
// CloseNotify is a notification that occurs *before* an api.Module is
// closed. `exitCode` is zero on success or in the case there was no exit
// code.
//
// Notes:
// - This does not return an error because the module will be closed
// unconditionally.
// - Do not panic from this function as it doing so could cause resource
// leaks.
// - While this is only called once per module, if configured for
// multiple modules, it will be called for each, e.g. on runtime close.
CloseNotify(ctx context.Context, exitCode uint32)
}
// ^-- Note: This might need to be a part of the listener or become a part of
// host state implementation. For example, if this is used to implement state
// cleanup for host modules, possibly something like below would be better, as
// it could be implemented in a way that allows concurrent module use.
//
// // key is like a context key, stateFactory is invoked per instantiate and
// // is associated with the key (exposed as `Module.State` similar to go
// // context). Using a key is better than the module name because we can
// // de-dupe it for host modules that can be instantiated into different
// // names. Also, you can make the key package private.
// HostModuleBuilder.WithState(key any, stateFactory func() Cleanup)`
//
// Such a design could work to isolate state only needed for wasip1, for
// example the dirent cache. However, if end users use this for different
// things, we may need separate designs.
//
// In summary, the purpose of this iteration is to identify projects that
// would use something like this, and then we can figure out which way it
// should go.
// CloseNotifyFunc is a convenience for defining inlining a CloseNotifier.
type CloseNotifyFunc func(ctx context.Context, exitCode uint32)
// CloseNotify implements CloseNotifier.CloseNotify.
func (f CloseNotifyFunc) CloseNotify(ctx context.Context, exitCode uint32) {
f(ctx, exitCode)
}
// WithCloseNotifier registers the given CloseNotifier into the given
// context.Context.
func WithCloseNotifier(ctx context.Context, notifier CloseNotifier) context.Context {
if notifier != nil {
return context.WithValue(ctx, expctxkeys.CloseNotifierKey{}, notifier)
}
return ctx
}

View File

@@ -0,0 +1,41 @@
// Package experimental includes features we aren't yet sure about. These are enabled with context.Context keys.
//
// Note: All features here may be changed or deleted at any time, so use with caution!
package experimental
import (
"github.com/tetratelabs/wazero/api"
)
// InternalModule is an api.Module that exposes additional
// information.
type InternalModule interface {
api.Module
// NumGlobal returns the count of all globals in the module.
NumGlobal() int
// Global provides a read-only view for a given global index.
//
// The methods panics if i is out of bounds.
Global(i int) api.Global
}
// ProgramCounter is an opaque value representing a specific execution point in
// a module. It is meant to be used with Function.SourceOffsetForPC and
// StackIterator.
type ProgramCounter uint64
// InternalFunction exposes some information about a function instance.
type InternalFunction interface {
// Definition provides introspection into the function's names and
// signature.
Definition() api.FunctionDefinition
// SourceOffsetForPC resolves a program counter into its corresponding
// offset in the Code section of the module this function belongs to.
// The source offset is meant to help map the function calls to their
// location in the original source files. Returns 0 if the offset cannot
// be calculated.
SourceOffsetForPC(pc ProgramCounter) uint64
}

View File

@@ -0,0 +1,15 @@
package experimental
import "github.com/tetratelabs/wazero/api"
// CoreFeaturesThreads enables threads instructions ("threads").
//
// # Notes
//
// - The instruction list is too long to enumerate in godoc.
// See https://github.com/WebAssembly/threads/blob/main/proposals/threads/Overview.md
// - Atomic operations are guest-only until api.Memory or otherwise expose them to host functions.
// - On systems without mmap available, the memory will pre-allocate to the maximum size. Many
// binaries will use a theroetical maximum like 4GB, so if using such a binary on a system
// without mmap, consider editing the binary to reduce the max size setting of memory.
const CoreFeaturesThreads = api.CoreFeatureSIMD << 1

View File

@@ -0,0 +1,19 @@
package experimental
import (
"context"
"github.com/tetratelabs/wazero/api"
"github.com/tetratelabs/wazero/internal/expctxkeys"
)
// ImportResolver is an experimental func type that, if set,
// will be used as the first step in resolving imports.
// See issue 2294.
// If the import name is not found, it should return nil.
type ImportResolver func(name string) api.Module
// WithImportResolver returns a new context with the given ImportResolver.
func WithImportResolver(ctx context.Context, resolver ImportResolver) context.Context {
return context.WithValue(ctx, expctxkeys.ImportResolverKey{}, resolver)
}

View File

@@ -0,0 +1,324 @@
package experimental
import (
"context"
"github.com/tetratelabs/wazero/api"
"github.com/tetratelabs/wazero/internal/expctxkeys"
)
// StackIterator allows iterating on each function of the call stack, starting
// from the top. At least one call to Next() is required to start the iteration.
//
// Note: The iterator provides a view of the call stack at the time of
// iteration. As a result, parameter values may be different than the ones their
// function was called with.
type StackIterator interface {
// Next moves the iterator to the next function in the stack. Returns
// false if it reached the bottom of the stack.
Next() bool
// Function describes the function called by the current frame.
Function() InternalFunction
// ProgramCounter returns the program counter associated with the
// function call.
ProgramCounter() ProgramCounter
}
// WithFunctionListenerFactory registers a FunctionListenerFactory
// with the context.
func WithFunctionListenerFactory(ctx context.Context, factory FunctionListenerFactory) context.Context {
return context.WithValue(ctx, expctxkeys.FunctionListenerFactoryKey{}, factory)
}
// FunctionListenerFactory returns FunctionListeners to be notified when a
// function is called.
type FunctionListenerFactory interface {
// NewFunctionListener returns a FunctionListener for a defined function.
// If nil is returned, no listener will be notified.
NewFunctionListener(api.FunctionDefinition) FunctionListener
// ^^ A single instance can be returned to avoid instantiating a listener
// per function, especially as they may be thousands of functions. Shared
// listeners use their FunctionDefinition parameter to clarify.
}
// FunctionListener can be registered for any function via
// FunctionListenerFactory to be notified when the function is called.
type FunctionListener interface {
// Before is invoked before a function is called.
//
// There is always one corresponding call to After or Abort for each call to
// Before. This guarantee allows the listener to maintain an internal stack
// to perform correlations between the entry and exit of functions.
//
// # Params
//
// - ctx: the context of the caller function which must be the same
// instance or parent of the result.
// - mod: the calling module.
// - def: the function definition.
// - params: api.ValueType encoded parameters.
// - stackIterator: iterator on the call stack. At least one entry is
// guaranteed (the called function), whose Args() will be equal to
// params. The iterator will be reused between calls to Before.
//
// Note: api.Memory is meant for inspection, not modification.
// mod can be cast to InternalModule to read non-exported globals.
Before(ctx context.Context, mod api.Module, def api.FunctionDefinition, params []uint64, stackIterator StackIterator)
// After is invoked after a function is called.
//
// # Params
//
// - ctx: the context of the caller function.
// - mod: the calling module.
// - def: the function definition.
// - results: api.ValueType encoded results.
//
// # Notes
//
// - api.Memory is meant for inspection, not modification.
// - This is not called when a host function panics, or a guest function traps.
// See Abort for more details.
After(ctx context.Context, mod api.Module, def api.FunctionDefinition, results []uint64)
// Abort is invoked when a function does not return due to a trap or panic.
//
// # Params
//
// - ctx: the context of the caller function.
// - mod: the calling module.
// - def: the function definition.
// - err: the error value representing the reason why the function aborted.
//
// # Notes
//
// - api.Memory is meant for inspection, not modification.
Abort(ctx context.Context, mod api.Module, def api.FunctionDefinition, err error)
}
// FunctionListenerFunc is a function type implementing the FunctionListener
// interface, making it possible to use regular functions and methods as
// listeners of function invocation.
//
// The FunctionListener interface declares two methods (Before and After),
// but this type invokes its value only when Before is called. It is best
// suites for cases where the host does not need to perform correlation
// between the start and end of the function call.
type FunctionListenerFunc func(context.Context, api.Module, api.FunctionDefinition, []uint64, StackIterator)
// Before satisfies the FunctionListener interface, calls f.
func (f FunctionListenerFunc) Before(ctx context.Context, mod api.Module, def api.FunctionDefinition, params []uint64, stackIterator StackIterator) {
f(ctx, mod, def, params, stackIterator)
}
// After is declared to satisfy the FunctionListener interface, but it does
// nothing.
func (f FunctionListenerFunc) After(context.Context, api.Module, api.FunctionDefinition, []uint64) {
}
// Abort is declared to satisfy the FunctionListener interface, but it does
// nothing.
func (f FunctionListenerFunc) Abort(context.Context, api.Module, api.FunctionDefinition, error) {
}
// FunctionListenerFactoryFunc is a function type implementing the
// FunctionListenerFactory interface, making it possible to use regular
// functions and methods as factory of function listeners.
type FunctionListenerFactoryFunc func(api.FunctionDefinition) FunctionListener
// NewFunctionListener satisfies the FunctionListenerFactory interface, calls f.
func (f FunctionListenerFactoryFunc) NewFunctionListener(def api.FunctionDefinition) FunctionListener {
return f(def)
}
// MultiFunctionListenerFactory constructs a FunctionListenerFactory which
// combines the listeners created by each of the factories passed as arguments.
//
// This function is useful when multiple listeners need to be hooked to a module
// because the propagation mechanism based on installing a listener factory in
// the context.Context used when instantiating modules allows for a single
// listener to be installed.
//
// The stack iterator passed to the Before method is reset so that each listener
// can iterate the call stack independently without impacting the ability of
// other listeners to do so.
func MultiFunctionListenerFactory(factories ...FunctionListenerFactory) FunctionListenerFactory {
multi := make(multiFunctionListenerFactory, len(factories))
copy(multi, factories)
return multi
}
type multiFunctionListenerFactory []FunctionListenerFactory
func (multi multiFunctionListenerFactory) NewFunctionListener(def api.FunctionDefinition) FunctionListener {
var lstns []FunctionListener
for _, factory := range multi {
if lstn := factory.NewFunctionListener(def); lstn != nil {
lstns = append(lstns, lstn)
}
}
switch len(lstns) {
case 0:
return nil
case 1:
return lstns[0]
default:
return &multiFunctionListener{lstns: lstns}
}
}
type multiFunctionListener struct {
lstns []FunctionListener
stack stackIterator
}
func (multi *multiFunctionListener) Before(ctx context.Context, mod api.Module, def api.FunctionDefinition, params []uint64, si StackIterator) {
multi.stack.base = si
for _, lstn := range multi.lstns {
multi.stack.index = -1
lstn.Before(ctx, mod, def, params, &multi.stack)
}
}
func (multi *multiFunctionListener) After(ctx context.Context, mod api.Module, def api.FunctionDefinition, results []uint64) {
for _, lstn := range multi.lstns {
lstn.After(ctx, mod, def, results)
}
}
func (multi *multiFunctionListener) Abort(ctx context.Context, mod api.Module, def api.FunctionDefinition, err error) {
for _, lstn := range multi.lstns {
lstn.Abort(ctx, mod, def, err)
}
}
type stackIterator struct {
base StackIterator
index int
pcs []uint64
fns []InternalFunction
}
func (si *stackIterator) Next() bool {
if si.base != nil {
si.pcs = si.pcs[:0]
si.fns = si.fns[:0]
for si.base.Next() {
si.pcs = append(si.pcs, uint64(si.base.ProgramCounter()))
si.fns = append(si.fns, si.base.Function())
}
si.base = nil
}
si.index++
return si.index < len(si.pcs)
}
func (si *stackIterator) ProgramCounter() ProgramCounter {
return ProgramCounter(si.pcs[si.index])
}
func (si *stackIterator) Function() InternalFunction {
return si.fns[si.index]
}
// StackFrame represents a frame on the call stack.
type StackFrame struct {
Function api.Function
Params []uint64
Results []uint64
PC uint64
SourceOffset uint64
}
type internalFunction struct {
definition api.FunctionDefinition
sourceOffset uint64
}
func (f internalFunction) Definition() api.FunctionDefinition {
return f.definition
}
func (f internalFunction) SourceOffsetForPC(pc ProgramCounter) uint64 {
return f.sourceOffset
}
// stackFrameIterator is an implementation of the experimental.stackFrameIterator
// interface.
type stackFrameIterator struct {
index int
stack []StackFrame
fndef []api.FunctionDefinition
}
func (si *stackFrameIterator) Next() bool {
si.index++
return si.index < len(si.stack)
}
func (si *stackFrameIterator) Function() InternalFunction {
return internalFunction{
definition: si.fndef[si.index],
sourceOffset: si.stack[si.index].SourceOffset,
}
}
func (si *stackFrameIterator) ProgramCounter() ProgramCounter {
return ProgramCounter(si.stack[si.index].PC)
}
// NewStackIterator constructs a stack iterator from a list of stack frames.
// The top most frame is the last one.
func NewStackIterator(stack ...StackFrame) StackIterator {
si := &stackFrameIterator{
index: -1,
stack: make([]StackFrame, len(stack)),
fndef: make([]api.FunctionDefinition, len(stack)),
}
for i := range stack {
si.stack[i] = stack[len(stack)-(i+1)]
}
// The size of function definition is only one pointer which should allow
// the compiler to optimize the conversion to api.FunctionDefinition; but
// the presence of internal.WazeroOnlyType, despite being defined as an
// empty struct, forces a heap allocation that we amortize by caching the
// result.
for i, frame := range stack {
si.fndef[i] = frame.Function.Definition()
}
return si
}
// BenchmarkFunctionListener implements a benchmark for function listeners.
//
// The benchmark calls Before and After methods repeatedly using the provided
// module an stack frames to invoke the methods.
//
// The stack frame is a representation of the call stack that the Before method
// will be invoked with. The top of the stack is stored at index zero. The stack
// must contain at least one frame or the benchmark will fail.
func BenchmarkFunctionListener(n int, module api.Module, stack []StackFrame, listener FunctionListener) {
if len(stack) == 0 {
panic("cannot benchmark function listener with an empty stack")
}
ctx := context.Background()
def := stack[0].Function.Definition()
params := stack[0].Params
results := stack[0].Results
stackIterator := &stackIterator{base: NewStackIterator(stack...)}
for i := 0; i < n; i++ {
stackIterator.index = -1
listener.Before(ctx, module, def, params, stackIterator)
listener.After(ctx, module, def, results)
}
}
// TODO: the calls to Abort are not yet tested in internal/testing/enginetest,
// but they are validated indirectly in tests which exercise host logging,
// like Test_procExit in imports/wasi_snapshot_preview1. Eventually we should
// add dedicated tests to validate the behavior of the interpreter and compiler
// engines independently.

View File

@@ -0,0 +1,52 @@
package experimental
import (
"context"
"github.com/tetratelabs/wazero/internal/expctxkeys"
)
// MemoryAllocator is a memory allocation hook,
// invoked to create a LinearMemory.
type MemoryAllocator interface {
// Allocate should create a new LinearMemory with the given specification:
// cap is the suggested initial capacity for the backing []byte,
// and max the maximum length that will ever be requested.
//
// Notes:
// - To back a shared memory, the address of the backing []byte cannot
// change. This is checked at runtime. Implementations should document
// if the returned LinearMemory meets this requirement.
Allocate(cap, max uint64) LinearMemory
}
// MemoryAllocatorFunc is a convenience for defining inlining a MemoryAllocator.
type MemoryAllocatorFunc func(cap, max uint64) LinearMemory
// Allocate implements MemoryAllocator.Allocate.
func (f MemoryAllocatorFunc) Allocate(cap, max uint64) LinearMemory {
return f(cap, max)
}
// LinearMemory is an expandable []byte that backs a Wasm linear memory.
type LinearMemory interface {
// Reallocates the linear memory to size bytes in length.
//
// Notes:
// - To back a shared memory, Reallocate can't change the address of the
// backing []byte (only its length/capacity may change).
// - Reallocate may return nil if fails to grow the LinearMemory. This
// condition may or may not be handled gracefully by the Wasm module.
Reallocate(size uint64) []byte
// Free the backing memory buffer.
Free()
}
// WithMemoryAllocator registers the given MemoryAllocator into the given
// context.Context. The context must be passed when initializing a module.
func WithMemoryAllocator(ctx context.Context, allocator MemoryAllocator) context.Context {
if allocator != nil {
return context.WithValue(ctx, expctxkeys.MemoryAllocatorKey{}, allocator)
}
return ctx
}

View File

@@ -0,0 +1,92 @@
package sys
import (
"fmt"
"io/fs"
"github.com/tetratelabs/wazero/sys"
)
// FileType is fs.FileMode masked on fs.ModeType. For example, zero is a
// regular file, fs.ModeDir is a directory and fs.ModeIrregular is unknown.
//
// Note: This is defined by Linux, not POSIX.
type FileType = fs.FileMode
// Dirent is an entry read from a directory via File.Readdir.
//
// # Notes
//
// - This extends `dirent` defined in POSIX with some fields defined by
// Linux. See https://man7.org/linux/man-pages/man3/readdir.3.html and
// https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/dirent.h.html
// - This has a subset of fields defined in sys.Stat_t. Notably, there is no
// field corresponding to Stat_t.Dev because that value will be constant
// for all files in a directory. To get the Dev value, call File.Stat on
// the directory File.Readdir was called on.
type Dirent struct {
// Ino is the file serial number, or zero if not available. See Ino for
// more details including impact returning a zero value.
Ino sys.Inode
// Name is the base name of the directory entry. Empty is invalid.
Name string
// Type is fs.FileMode masked on fs.ModeType. For example, zero is a
// regular file, fs.ModeDir is a directory and fs.ModeIrregular is unknown.
//
// Note: This is defined by Linux, not POSIX.
Type fs.FileMode
}
func (d *Dirent) String() string {
return fmt.Sprintf("name=%s, type=%v, ino=%d", d.Name, d.Type, d.Ino)
}
// IsDir returns true if the Type is fs.ModeDir.
func (d *Dirent) IsDir() bool {
return d.Type == fs.ModeDir
}
// DirFile is embeddable to reduce the amount of functions to implement a file.
type DirFile struct{}
// IsAppend implements File.IsAppend
func (DirFile) IsAppend() bool {
return false
}
// SetAppend implements File.SetAppend
func (DirFile) SetAppend(bool) Errno {
return EISDIR
}
// IsDir implements File.IsDir
func (DirFile) IsDir() (bool, Errno) {
return true, 0
}
// Read implements File.Read
func (DirFile) Read([]byte) (int, Errno) {
return 0, EISDIR
}
// Pread implements File.Pread
func (DirFile) Pread([]byte, int64) (int, Errno) {
return 0, EISDIR
}
// Write implements File.Write
func (DirFile) Write([]byte) (int, Errno) {
return 0, EISDIR
}
// Pwrite implements File.Pwrite
func (DirFile) Pwrite([]byte, int64) (int, Errno) {
return 0, EISDIR
}
// Truncate implements File.Truncate
func (DirFile) Truncate(int64) Errno {
return EISDIR
}

View File

@@ -0,0 +1,98 @@
package sys
import "strconv"
// Errno is a subset of POSIX errno used by wazero interfaces. Zero is not an
// error. Other values should not be interpreted numerically, rather by constants
// prefixed with 'E'.
//
// See https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/errno.h.html
type Errno uint16
// ^-- Note: This will eventually move to the public /sys package. It is
// experimental until we audit the socket related APIs to ensure we have all
// the Errno it returns, and we export fs.FS. This is not in /internal/sys as
// that would introduce a package cycle.
// This is a subset of errors to reduce implementation burden. `wasip1` defines
// almost all POSIX error numbers, but not all are used in practice. wazero
// will add ones needed in POSIX order, as needed by functions that explicitly
// document returning them.
//
// https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#-errno-enumu16
const (
EACCES Errno = iota + 1
EAGAIN
EBADF
EEXIST
EFAULT
EINTR
EINVAL
EIO
EISDIR
ELOOP
ENAMETOOLONG
ENOENT
ENOSYS
ENOTDIR
ERANGE
ENOTEMPTY
ENOTSOCK
ENOTSUP
EPERM
EROFS
// NOTE ENOTCAPABLE is defined in wasip1, but not in POSIX. wasi-libc
// converts it to EBADF, ESPIPE or EINVAL depending on the call site.
// It isn't known if compilers who don't use ENOTCAPABLE would crash on it.
)
// Error implements error
func (e Errno) Error() string {
switch e {
case 0: // not an error
return "success"
case EACCES:
return "permission denied"
case EAGAIN:
return "resource unavailable, try again"
case EBADF:
return "bad file descriptor"
case EEXIST:
return "file exists"
case EFAULT:
return "bad address"
case EINTR:
return "interrupted function"
case EINVAL:
return "invalid argument"
case EIO:
return "input/output error"
case EISDIR:
return "is a directory"
case ELOOP:
return "too many levels of symbolic links"
case ENAMETOOLONG:
return "filename too long"
case ENOENT:
return "no such file or directory"
case ENOSYS:
return "functionality not supported"
case ENOTDIR:
return "not a directory or a symbolic link to a directory"
case ERANGE:
return "result too large"
case ENOTEMPTY:
return "directory not empty"
case ENOTSOCK:
return "not a socket"
case ENOTSUP:
return "not supported (may be the same value as [EOPNOTSUPP])"
case EPERM:
return "operation not permitted"
case EROFS:
return "read-only file system"
default:
return "Errno(" + strconv.Itoa(int(e)) + ")"
}
}

View File

@@ -0,0 +1,45 @@
package sys
import (
"io"
"io/fs"
"os"
)
// UnwrapOSError returns an Errno or zero if the input is nil.
func UnwrapOSError(err error) Errno {
if err == nil {
return 0
}
err = underlyingError(err)
switch err {
case nil, io.EOF:
return 0 // EOF is not a Errno
case fs.ErrInvalid:
return EINVAL
case fs.ErrPermission:
return EPERM
case fs.ErrExist:
return EEXIST
case fs.ErrNotExist:
return ENOENT
case fs.ErrClosed:
return EBADF
}
return errorToErrno(err)
}
// underlyingError returns the underlying error if a well-known OS error type.
//
// This impl is basically the same as os.underlyingError in os/error.go
func underlyingError(err error) error {
switch err := err.(type) {
case *os.PathError:
return err.Err
case *os.LinkError:
return err.Err
case *os.SyscallError:
return err.Err
}
return err
}

View File

@@ -0,0 +1,316 @@
package sys
import "github.com/tetratelabs/wazero/sys"
// File is a writeable fs.File bridge backed by syscall functions needed for ABI
// including WASI.
//
// Implementations should embed UnimplementedFile for forward compatibility. Any
// unsupported method or parameter should return ENOSYS.
//
// # Errors
//
// All methods that can return an error return a Errno, which is zero
// on success.
//
// Restricting to Errno matches current WebAssembly host functions,
// which are constrained to well-known error codes. For example, WASI maps syscall
// errors to u32 numeric values.
//
// # Notes
//
// - You must call Close to avoid file resource conflicts. For example,
// Windows cannot delete the underlying directory while a handle to it
// remains open.
// - A writable filesystem abstraction is not yet implemented as of Go 1.20.
// See https://github.com/golang/go/issues/45757
type File interface {
// Dev returns the device ID (Stat_t.Dev) of this file, zero if unknown or
// an error retrieving it.
//
// # Errors
//
// Possible errors are those from Stat, except ENOSYS should not
// be returned. Zero should be returned if there is no implementation.
//
// # Notes
//
// - Implementations should cache this result.
// - This combined with Ino can implement os.SameFile.
Dev() (uint64, Errno)
// Ino returns the serial number (Stat_t.Ino) of this file, zero if unknown
// or an error retrieving it.
//
// # Errors
//
// Possible errors are those from Stat, except ENOSYS should not
// be returned. Zero should be returned if there is no implementation.
//
// # Notes
//
// - Implementations should cache this result.
// - This combined with Dev can implement os.SameFile.
Ino() (sys.Inode, Errno)
// IsDir returns true if this file is a directory or an error there was an
// error retrieving this information.
//
// # Errors
//
// Possible errors are those from Stat, except ENOSYS should not
// be returned. false should be returned if there is no implementation.
//
// # Notes
//
// - Implementations should cache this result.
IsDir() (bool, Errno)
// IsAppend returns true if the file was opened with O_APPEND, or
// SetAppend was successfully enabled on this file.
//
// # Notes
//
// - This might not match the underlying state of the file descriptor if
// the file was not opened via OpenFile.
IsAppend() bool
// SetAppend toggles the append mode (O_APPEND) of this file.
//
// # Errors
//
// A zero Errno is success. The below are expected otherwise:
// - ENOSYS: the implementation does not support this function.
// - EBADF: the file or directory was closed.
//
// # Notes
//
// - There is no `O_APPEND` for `fcntl` in POSIX, so implementations may
// have to re-open the underlying file to apply this. See
// https://pubs.opengroup.org/onlinepubs/9699919799/functions/open.html
SetAppend(enable bool) Errno
// Stat is similar to syscall.Fstat.
//
// # Errors
//
// A zero Errno is success. The below are expected otherwise:
// - ENOSYS: the implementation does not support this function.
// - EBADF: the file or directory was closed.
//
// # Notes
//
// - This is like syscall.Fstat and `fstatat` with `AT_FDCWD` in POSIX.
// See https://pubs.opengroup.org/onlinepubs/9699919799/functions/stat.html
// - A fs.FileInfo backed implementation sets atim, mtim and ctim to the
// same value.
// - Windows allows you to stat a closed directory.
Stat() (sys.Stat_t, Errno)
// Read attempts to read all bytes in the file into `buf`, and returns the
// count read even on error.
//
// # Errors
//
// A zero Errno is success. The below are expected otherwise:
// - ENOSYS: the implementation does not support this function.
// - EBADF: the file or directory was closed or not readable.
// - EISDIR: the file was a directory.
//
// # Notes
//
// - This is like io.Reader and `read` in POSIX, preferring semantics of
// io.Reader. See https://pubs.opengroup.org/onlinepubs/9699919799/functions/read.html
// - Unlike io.Reader, there is no io.EOF returned on end-of-file. To
// read the file completely, the caller must repeat until `n` is zero.
Read(buf []byte) (n int, errno Errno)
// Pread attempts to read all bytes in the file into `p`, starting at the
// offset `off`, and returns the count read even on error.
//
// # Errors
//
// A zero Errno is success. The below are expected otherwise:
// - ENOSYS: the implementation does not support this function.
// - EBADF: the file or directory was closed or not readable.
// - EINVAL: the offset was negative.
// - EISDIR: the file was a directory.
//
// # Notes
//
// - This is like io.ReaderAt and `pread` in POSIX, preferring semantics
// of io.ReaderAt. See https://pubs.opengroup.org/onlinepubs/9699919799/functions/pread.html
// - Unlike io.ReaderAt, there is no io.EOF returned on end-of-file. To
// read the file completely, the caller must repeat until `n` is zero.
Pread(buf []byte, off int64) (n int, errno Errno)
// Seek attempts to set the next offset for Read or Write and returns the
// resulting absolute offset or an error.
//
// # Parameters
//
// The `offset` parameters is interpreted in terms of `whence`:
// - io.SeekStart: relative to the start of the file, e.g. offset=0 sets
// the next Read or Write to the beginning of the file.
// - io.SeekCurrent: relative to the current offset, e.g. offset=16 sets
// the next Read or Write 16 bytes past the prior.
// - io.SeekEnd: relative to the end of the file, e.g. offset=-1 sets the
// next Read or Write to the last byte in the file.
//
// # Behavior when a directory
//
// The only supported use case for a directory is seeking to `offset` zero
// (`whence` = io.SeekStart). This should have the same behavior as
// os.File, which resets any internal state used by Readdir.
//
// # Errors
//
// A zero Errno is success. The below are expected otherwise:
// - ENOSYS: the implementation does not support this function.
// - EBADF: the file or directory was closed or not readable.
// - EINVAL: the offset was negative.
//
// # Notes
//
// - This is like io.Seeker and `fseek` in POSIX, preferring semantics
// of io.Seeker. See https://pubs.opengroup.org/onlinepubs/9699919799/functions/fseek.html
Seek(offset int64, whence int) (newOffset int64, errno Errno)
// Readdir reads the contents of the directory associated with file and
// returns a slice of up to n Dirent values in an arbitrary order. This is
// a stateful function, so subsequent calls return any next values.
//
// If n > 0, Readdir returns at most n entries or an error.
// If n <= 0, Readdir returns all remaining entries or an error.
//
// # Errors
//
// A zero Errno is success. The below are expected otherwise:
// - ENOSYS: the implementation does not support this function.
// - EBADF: the file was closed or not a directory.
// - ENOENT: the directory could not be read (e.g. deleted).
//
// # Notes
//
// - This is like `Readdir` on os.File, but unlike `readdir` in POSIX.
// See https://pubs.opengroup.org/onlinepubs/9699919799/functions/readdir.html
// - Unlike os.File, there is no io.EOF returned on end-of-directory. To
// read the directory completely, the caller must repeat until the
// count read (`len(dirents)`) is less than `n`.
// - See /RATIONALE.md for design notes.
Readdir(n int) (dirents []Dirent, errno Errno)
// Write attempts to write all bytes in `p` to the file, and returns the
// count written even on error.
//
// # Errors
//
// A zero Errno is success. The below are expected otherwise:
// - ENOSYS: the implementation does not support this function.
// - EBADF: the file was closed, not writeable, or a directory.
//
// # Notes
//
// - This is like io.Writer and `write` in POSIX, preferring semantics of
// io.Writer. See https://pubs.opengroup.org/onlinepubs/9699919799/functions/write.html
Write(buf []byte) (n int, errno Errno)
// Pwrite attempts to write all bytes in `p` to the file at the given
// offset `off`, and returns the count written even on error.
//
// # Errors
//
// A zero Errno is success. The below are expected otherwise:
// - ENOSYS: the implementation does not support this function.
// - EBADF: the file or directory was closed or not writeable.
// - EINVAL: the offset was negative.
// - EISDIR: the file was a directory.
//
// # Notes
//
// - This is like io.WriterAt and `pwrite` in POSIX, preferring semantics
// of io.WriterAt. See https://pubs.opengroup.org/onlinepubs/9699919799/functions/pwrite.html
Pwrite(buf []byte, off int64) (n int, errno Errno)
// Truncate truncates a file to a specified length.
//
// # Errors
//
// A zero Errno is success. The below are expected otherwise:
// - ENOSYS: the implementation does not support this function.
// - EBADF: the file or directory was closed.
// - EINVAL: the `size` is negative.
// - EISDIR: the file was a directory.
//
// # Notes
//
// - This is like syscall.Ftruncate and `ftruncate` in POSIX. See
// https://pubs.opengroup.org/onlinepubs/9699919799/functions/ftruncate.html
// - Windows does not error when calling Truncate on a closed file.
Truncate(size int64) Errno
// Sync synchronizes changes to the file.
//
// # Errors
//
// A zero Errno is success. The below are expected otherwise:
// - EBADF: the file or directory was closed.
//
// # Notes
//
// - This is like syscall.Fsync and `fsync` in POSIX. See
// https://pubs.opengroup.org/onlinepubs/9699919799/functions/fsync.html
// - This returns with no error instead of ENOSYS when
// unimplemented. This prevents fake filesystems from erring.
// - Windows does not error when calling Sync on a closed file.
Sync() Errno
// Datasync synchronizes the data of a file.
//
// # Errors
//
// A zero Errno is success. The below are expected otherwise:
// - EBADF: the file or directory was closed.
//
// # Notes
//
// - This is like syscall.Fdatasync and `fdatasync` in POSIX. See
// https://pubs.opengroup.org/onlinepubs/9699919799/functions/fdatasync.html
// - This returns with no error instead of ENOSYS when
// unimplemented. This prevents fake filesystems from erring.
// - As this is commonly missing, some implementations dispatch to Sync.
Datasync() Errno
// Utimens set file access and modification times of this file, at
// nanosecond precision.
//
// # Parameters
//
// The `atim` and `mtim` parameters refer to access and modification time
// stamps as defined in sys.Stat_t. To retain one or the other, substitute
// it with the pseudo-timestamp UTIME_OMIT.
//
// # Errors
//
// A zero Errno is success. The below are expected otherwise:
// - ENOSYS: the implementation does not support this function.
// - EBADF: the file or directory was closed.
//
// # Notes
//
// - This is like syscall.UtimesNano and `futimens` in POSIX. See
// https://pubs.opengroup.org/onlinepubs/9699919799/functions/futimens.html
// - Windows requires files to be open with O_RDWR, which means you
// cannot use this to update timestamps on a directory (EPERM).
Utimens(atim, mtim int64) Errno
// Close closes the underlying file.
//
// A zero Errno is returned if unimplemented or success.
//
// # Notes
//
// - This is like syscall.Close and `close` in POSIX. See
// https://pubs.opengroup.org/onlinepubs/9699919799/functions/close.html
Close() Errno
}

View File

@@ -0,0 +1,292 @@
package sys
import (
"io/fs"
"github.com/tetratelabs/wazero/sys"
)
// FS is a writeable fs.FS bridge backed by syscall functions needed for ABI
// including WASI.
//
// Implementations should embed UnimplementedFS for forward compatibility. Any
// unsupported method or parameter should return ENO
//
// # Errors
//
// All methods that can return an error return a Errno, which is zero
// on success.
//
// Restricting to Errno matches current WebAssembly host functions,
// which are constrained to well-known error codes. For example, WASI maps syscall
// errors to u32 numeric values.
//
// # Notes
//
// A writable filesystem abstraction is not yet implemented as of Go 1.20. See
// https://github.com/golang/go/issues/45757
type FS interface {
// OpenFile opens a file. It should be closed via Close on File.
//
// # Errors
//
// A zero Errno is success. The below are expected otherwise:
// - ENOSYS: the implementation does not support this function.
// - EINVAL: `path` or `flag` is invalid.
// - EISDIR: the path was a directory, but flag included O_RDWR or
// O_WRONLY
// - ENOENT: `path` doesn't exist and `flag` doesn't contain O_CREAT.
//
// # Constraints on the returned file
//
// Implementations that can read flags should enforce them regardless of
// the type returned. For example, while os.File implements io.Writer,
// attempts to write to a directory or a file opened with O_RDONLY fail
// with a EBADF.
//
// Some implementations choose whether to enforce read-only opens, namely
// fs.FS. While fs.FS is supported (Adapt), wazero cannot runtime enforce
// open flags. Instead, we encourage good behavior and test our built-in
// implementations.
//
// # Notes
//
// - This is like os.OpenFile, except the path is relative to this file
// system, and Errno is returned instead of os.PathError.
// - Implications of permissions when O_CREAT are described in Chmod notes.
// - This is like `open` in POSIX. See
// https://pubs.opengroup.org/onlinepubs/9699919799/functions/open.html
OpenFile(path string, flag Oflag, perm fs.FileMode) (File, Errno)
// Lstat gets file status without following symbolic links.
//
// # Errors
//
// A zero Errno is success. The below are expected otherwise:
// - ENOSYS: the implementation does not support this function.
// - ENOENT: `path` doesn't exist.
//
// # Notes
//
// - This is like syscall.Lstat, except the `path` is relative to this
// file system.
// - This is like `lstat` in POSIX. See
// https://pubs.opengroup.org/onlinepubs/9699919799/functions/lstat.html
// - An fs.FileInfo backed implementation sets atim, mtim and ctim to the
// same value.
// - When the path is a symbolic link, the stat returned is for the link,
// not the file it refers to.
Lstat(path string) (sys.Stat_t, Errno)
// Stat gets file status.
//
// # Errors
//
// A zero Errno is success. The below are expected otherwise:
// - ENOSYS: the implementation does not support this function.
// - ENOENT: `path` doesn't exist.
//
// # Notes
//
// - This is like syscall.Stat, except the `path` is relative to this
// file system.
// - This is like `stat` in POSIX. See
// https://pubs.opengroup.org/onlinepubs/9699919799/functions/stat.html
// - An fs.FileInfo backed implementation sets atim, mtim and ctim to the
// same value.
// - When the path is a symbolic link, the stat returned is for the file
// it refers to.
Stat(path string) (sys.Stat_t, Errno)
// Mkdir makes a directory.
//
// # Errors
//
// A zero Errno is success. The below are expected otherwise:
// - ENOSYS: the implementation does not support this function.
// - EINVAL: `path` is invalid.
// - EEXIST: `path` exists and is a directory.
// - ENOTDIR: `path` exists and is a file.
//
// # Notes
//
// - This is like syscall.Mkdir, except the `path` is relative to this
// file system.
// - This is like `mkdir` in POSIX. See
// https://pubs.opengroup.org/onlinepubs/9699919799/functions/mkdir.html
// - Implications of permissions are described in Chmod notes.
Mkdir(path string, perm fs.FileMode) Errno
// Chmod changes the mode of the file.
//
// # Errors
//
// A zero Errno is success. The below are expected otherwise:
// - ENOSYS: the implementation does not support this function.
// - EINVAL: `path` is invalid.
// - ENOENT: `path` does not exist.
//
// # Notes
//
// - This is like syscall.Chmod, except the `path` is relative to this
// file system.
// - This is like `chmod` in POSIX. See
// https://pubs.opengroup.org/onlinepubs/9699919799/functions/chmod.html
// - Windows ignores the execute bit, and any permissions come back as
// group and world. For example, chmod of 0400 reads back as 0444, and
// 0700 0666. Also, permissions on directories aren't supported at all.
Chmod(path string, perm fs.FileMode) Errno
// Rename renames file or directory.
//
// # Errors
//
// A zero Errno is success. The below are expected otherwise:
// - ENOSYS: the implementation does not support this function.
// - EINVAL: `from` or `to` is invalid.
// - ENOENT: `from` or `to` don't exist.
// - ENOTDIR: `from` is a directory and `to` exists as a file.
// - EISDIR: `from` is a file and `to` exists as a directory.
// - ENOTEMPTY: `both from` and `to` are existing directory, but
// `to` is not empty.
//
// # Notes
//
// - This is like syscall.Rename, except the paths are relative to this
// file system.
// - This is like `rename` in POSIX. See
// https://pubs.opengroup.org/onlinepubs/9699919799/functions/rename.html
// - Windows doesn't let you overwrite an existing directory.
Rename(from, to string) Errno
// Rmdir removes a directory.
//
// # Errors
//
// A zero Errno is success. The below are expected otherwise:
// - ENOSYS: the implementation does not support this function.
// - EINVAL: `path` is invalid.
// - ENOENT: `path` doesn't exist.
// - ENOTDIR: `path` exists, but isn't a directory.
// - ENOTEMPTY: `path` exists, but isn't empty.
//
// # Notes
//
// - This is like syscall.Rmdir, except the `path` is relative to this
// file system.
// - This is like `rmdir` in POSIX. See
// https://pubs.opengroup.org/onlinepubs/9699919799/functions/rmdir.html
// - As of Go 1.19, Windows maps ENOTDIR to ENOENT.
Rmdir(path string) Errno
// Unlink removes a directory entry.
//
// # Errors
//
// A zero Errno is success. The below are expected otherwise:
// - ENOSYS: the implementation does not support this function.
// - EINVAL: `path` is invalid.
// - ENOENT: `path` doesn't exist.
// - EISDIR: `path` exists, but is a directory.
//
// # Notes
//
// - This is like syscall.Unlink, except the `path` is relative to this
// file system.
// - This is like `unlink` in POSIX. See
// https://pubs.opengroup.org/onlinepubs/9699919799/functions/unlink.html
// - On Windows, syscall.Unlink doesn't delete symlink to directory unlike other platforms. Implementations might
// want to combine syscall.RemoveDirectory with syscall.Unlink in order to delete such links on Windows.
// See https://learn.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-removedirectorya
Unlink(path string) Errno
// Link creates a "hard" link from oldPath to newPath, in contrast to a
// soft link (via Symlink).
//
// # Errors
//
// A zero Errno is success. The below are expected otherwise:
// - ENOSYS: the implementation does not support this function.
// - EPERM: `oldPath` is invalid.
// - ENOENT: `oldPath` doesn't exist.
// - EISDIR: `newPath` exists, but is a directory.
//
// # Notes
//
// - This is like syscall.Link, except the `oldPath` is relative to this
// file system.
// - This is like `link` in POSIX. See
// https://pubs.opengroup.org/onlinepubs/9699919799/functions/link.html
Link(oldPath, newPath string) Errno
// Symlink creates a "soft" link from oldPath to newPath, in contrast to a
// hard link (via Link).
//
// # Errors
//
// A zero Errno is success. The below are expected otherwise:
// - ENOSYS: the implementation does not support this function.
// - EPERM: `oldPath` or `newPath` is invalid.
// - EEXIST: `newPath` exists.
//
// # Notes
//
// - This is like syscall.Symlink, except the `oldPath` is relative to
// this file system.
// - This is like `symlink` in POSIX. See
// https://pubs.opengroup.org/onlinepubs/9699919799/functions/symlink.html
// - Only `newPath` is relative to this file system and `oldPath` is kept
// as-is. That is because the link is only resolved relative to the
// directory when dereferencing it (e.g. ReadLink).
// See https://github.com/bytecodealliance/cap-std/blob/v1.0.4/cap-std/src/fs/dir.rs#L404-L409
// for how others implement this.
// - Symlinks in Windows requires `SeCreateSymbolicLinkPrivilege`.
// Otherwise, EPERM results.
// See https://learn.microsoft.com/en-us/windows/security/threat-protection/security-policy-settings/create-symbolic-links
Symlink(oldPath, linkName string) Errno
// Readlink reads the contents of a symbolic link.
//
// # Errors
//
// A zero Errno is success. The below are expected otherwise:
// - ENOSYS: the implementation does not support this function.
// - EINVAL: `path` is invalid.
//
// # Notes
//
// - This is like syscall.Readlink, except the path is relative to this
// filesystem.
// - This is like `readlink` in POSIX. See
// https://pubs.opengroup.org/onlinepubs/9699919799/functions/readlink.html
// - On Windows, the path separator is different from other platforms,
// but to provide consistent results to Wasm, this normalizes to a "/"
// separator.
Readlink(path string) (string, Errno)
// Utimens set file access and modification times on a path relative to
// this file system, at nanosecond precision.
//
// # Parameters
//
// If the path is a symbolic link, the target of expanding that link is
// updated.
//
// The `atim` and `mtim` parameters refer to access and modification time
// stamps as defined in sys.Stat_t. To retain one or the other, substitute
// it with the pseudo-timestamp UTIME_OMIT.
//
// # Errors
//
// A zero Errno is success. The below are expected otherwise:
// - ENOSYS: the implementation does not support this function.
// - EINVAL: `path` is invalid.
// - EEXIST: `path` exists and is a directory.
// - ENOTDIR: `path` exists and is a file.
//
// # Notes
//
// - This is like syscall.UtimesNano and `utimensat` with `AT_FDCWD` in
// POSIX. See https://pubs.opengroup.org/onlinepubs/9699919799/functions/futimens.html
Utimens(path string, atim, mtim int64) Errno
}

View File

@@ -0,0 +1,70 @@
package sys
// Oflag are flags used for FS.OpenFile. Values, including zero, should not be
// interpreted numerically. Instead, use by constants prefixed with 'O_' with
// special casing noted below.
//
// # Notes
//
// - O_RDONLY, O_RDWR and O_WRONLY are mutually exclusive, while the other
// flags can coexist bitwise.
// - This is like `flag` in os.OpenFile and `oflag` in POSIX. See
// https://pubs.opengroup.org/onlinepubs/9699919799/functions/open.html
type Oflag uint32
// This is a subset of oflags to reduce implementation burden. `wasip1` splits
// these across `oflags` and `fdflags`. We can't rely on the Go `os` package,
// as it is missing some values. Any flags added will be defined in POSIX
// order, as needed by functions that explicitly document accepting them.
//
// https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#-oflags-flagsu16
// https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#-fdflags-flagsu16
const (
// O_RDONLY is like os.O_RDONLY
O_RDONLY Oflag = iota
// O_RDWR is like os.O_RDWR
O_RDWR
// O_WRONLY is like os.O_WRONLY
O_WRONLY
// Define bitflags as they are in POSIX `open`: alphabetically
// O_APPEND is like os.O_APPEND
O_APPEND Oflag = 1 << iota
// O_CREAT is link os.O_CREATE
O_CREAT
// O_DIRECTORY is defined on some platforms as syscall.O_DIRECTORY.
//
// Note: This ensures that the opened file is a directory. Those emulating
// on platforms that don't support the O_DIRECTORY, can double-check the
// result with File.IsDir (or stat) and err if not a directory.
O_DIRECTORY
// O_DSYNC is defined on some platforms as syscall.O_DSYNC.
O_DSYNC
// O_EXCL is defined on some platforms as syscall.O_EXCL.
O_EXCL
// O_NOFOLLOW is defined on some platforms as syscall.O_NOFOLLOW.
//
// Note: This allows programs to ensure that if the opened file is a
// symbolic link, the link itself is opened instead of its target.
O_NOFOLLOW
// O_NONBLOCK is defined on some platforms as syscall.O_NONBLOCK.
O_NONBLOCK
// O_RSYNC is defined on some platforms as syscall.O_RSYNC.
O_RSYNC
// O_SYNC is defined on some platforms as syscall.O_SYNC.
O_SYNC
// O_TRUNC is defined on some platforms as syscall.O_TRUNC.
O_TRUNC
)

View File

@@ -0,0 +1,106 @@
//go:build !plan9 && !aix
package sys
import "syscall"
func syscallToErrno(err error) (Errno, bool) {
errno, ok := err.(syscall.Errno)
if !ok {
return 0, false
}
switch errno {
case 0:
return 0, true
case syscall.EACCES:
return EACCES, true
case syscall.EAGAIN:
return EAGAIN, true
case syscall.EBADF:
return EBADF, true
case syscall.EEXIST:
return EEXIST, true
case syscall.EFAULT:
return EFAULT, true
case syscall.EINTR:
return EINTR, true
case syscall.EINVAL:
return EINVAL, true
case syscall.EIO:
return EIO, true
case syscall.EISDIR:
return EISDIR, true
case syscall.ELOOP:
return ELOOP, true
case syscall.ENAMETOOLONG:
return ENAMETOOLONG, true
case syscall.ENOENT:
return ENOENT, true
case syscall.ENOSYS:
return ENOSYS, true
case syscall.ENOTDIR:
return ENOTDIR, true
case syscall.ERANGE:
return ERANGE, true
case syscall.ENOTEMPTY:
return ENOTEMPTY, true
case syscall.ENOTSOCK:
return ENOTSOCK, true
case syscall.ENOTSUP:
return ENOTSUP, true
case syscall.EPERM:
return EPERM, true
case syscall.EROFS:
return EROFS, true
default:
return EIO, true
}
}
// Unwrap is a convenience for runtime.GOOS which define syscall.Errno.
func (e Errno) Unwrap() error {
switch e {
case 0:
return nil
case EACCES:
return syscall.EACCES
case EAGAIN:
return syscall.EAGAIN
case EBADF:
return syscall.EBADF
case EEXIST:
return syscall.EEXIST
case EFAULT:
return syscall.EFAULT
case EINTR:
return syscall.EINTR
case EINVAL:
return syscall.EINVAL
case EIO:
return syscall.EIO
case EISDIR:
return syscall.EISDIR
case ELOOP:
return syscall.ELOOP
case ENAMETOOLONG:
return syscall.ENAMETOOLONG
case ENOENT:
return syscall.ENOENT
case ENOSYS:
return syscall.ENOSYS
case ENOTDIR:
return syscall.ENOTDIR
case ENOTEMPTY:
return syscall.ENOTEMPTY
case ENOTSOCK:
return syscall.ENOTSOCK
case ENOTSUP:
return syscall.ENOTSUP
case EPERM:
return syscall.EPERM
case EROFS:
return syscall.EROFS
default:
return syscall.EIO
}
}

View File

@@ -0,0 +1,13 @@
//go:build !windows
package sys
func errorToErrno(err error) Errno {
if errno, ok := err.(Errno); ok {
return errno
}
if errno, ok := syscallToErrno(err); ok {
return errno
}
return EIO
}

View File

@@ -0,0 +1,7 @@
//go:build plan9 || aix
package sys
func syscallToErrno(err error) (Errno, bool) {
return 0, false
}

View File

@@ -0,0 +1,66 @@
package sys
import "syscall"
// These are errors not defined in the syscall package. They are prefixed with
// underscore to avoid exporting them.
//
// See https://learn.microsoft.com/en-us/windows/win32/debug/system-error-codes--0-499-
const (
// _ERROR_INVALID_HANDLE is a Windows error returned by syscall.Write
// instead of syscall.EBADF
_ERROR_INVALID_HANDLE = syscall.Errno(6)
// _ERROR_INVALID_NAME is a Windows error returned by open when a file
// path has a trailing slash
_ERROR_INVALID_NAME = syscall.Errno(0x7B)
// _ERROR_NEGATIVE_SEEK is a Windows error returned by os.Truncate
// instead of syscall.EINVAL
_ERROR_NEGATIVE_SEEK = syscall.Errno(0x83)
// _ERROR_DIRECTORY is a Windows error returned by syscall.Rmdir
// instead of syscall.ENOTDIR
_ERROR_DIRECTORY = syscall.Errno(0x10B)
// _ERROR_NOT_A_REPARSE_POINT is a Windows error returned by os.Readlink
// instead of syscall.EINVAL
_ERROR_NOT_A_REPARSE_POINT = syscall.Errno(0x1126)
// _ERROR_INVALID_SOCKET is a Windows error returned by winsock_select
// when a given handle is not a socket.
_ERROR_INVALID_SOCKET = syscall.Errno(0x2736)
)
func errorToErrno(err error) Errno {
switch err := err.(type) {
case Errno:
return err
case syscall.Errno:
// Note: In windows, _ERROR_PATH_NOT_FOUND(0x3) maps to syscall.ENOTDIR
switch err {
case syscall.ERROR_ALREADY_EXISTS:
return EEXIST
case _ERROR_DIRECTORY:
return ENOTDIR
case syscall.ERROR_DIR_NOT_EMPTY:
return ENOTEMPTY
case syscall.ERROR_FILE_EXISTS:
return EEXIST
case _ERROR_INVALID_HANDLE, _ERROR_INVALID_SOCKET:
return EBADF
case syscall.ERROR_ACCESS_DENIED:
// POSIX read and write functions expect EBADF, not EACCES when not
// open for reading or writing.
return EBADF
case syscall.ERROR_PRIVILEGE_NOT_HELD:
return EPERM
case _ERROR_NEGATIVE_SEEK, _ERROR_INVALID_NAME, _ERROR_NOT_A_REPARSE_POINT:
return EINVAL
}
errno, _ := syscallToErrno(err)
return errno
default:
return EIO
}
}

View File

@@ -0,0 +1,10 @@
package sys
import "math"
// UTIME_OMIT is a special constant for use in updating times via FS.Utimens
// or File.Utimens. When used for atim or mtim, the value is retained.
//
// Note: This may be implemented via a stat when the underlying filesystem
// does not support this value.
const UTIME_OMIT int64 = math.MinInt64

View File

@@ -0,0 +1,160 @@
package sys
import (
"io/fs"
"github.com/tetratelabs/wazero/sys"
)
// UnimplementedFS is an FS that returns ENOSYS for all functions,
// This should be embedded to have forward compatible implementations.
type UnimplementedFS struct{}
// OpenFile implements FS.OpenFile
func (UnimplementedFS) OpenFile(path string, flag Oflag, perm fs.FileMode) (File, Errno) {
return nil, ENOSYS
}
// Lstat implements FS.Lstat
func (UnimplementedFS) Lstat(path string) (sys.Stat_t, Errno) {
return sys.Stat_t{}, ENOSYS
}
// Stat implements FS.Stat
func (UnimplementedFS) Stat(path string) (sys.Stat_t, Errno) {
return sys.Stat_t{}, ENOSYS
}
// Readlink implements FS.Readlink
func (UnimplementedFS) Readlink(path string) (string, Errno) {
return "", ENOSYS
}
// Mkdir implements FS.Mkdir
func (UnimplementedFS) Mkdir(path string, perm fs.FileMode) Errno {
return ENOSYS
}
// Chmod implements FS.Chmod
func (UnimplementedFS) Chmod(path string, perm fs.FileMode) Errno {
return ENOSYS
}
// Rename implements FS.Rename
func (UnimplementedFS) Rename(from, to string) Errno {
return ENOSYS
}
// Rmdir implements FS.Rmdir
func (UnimplementedFS) Rmdir(path string) Errno {
return ENOSYS
}
// Link implements FS.Link
func (UnimplementedFS) Link(_, _ string) Errno {
return ENOSYS
}
// Symlink implements FS.Symlink
func (UnimplementedFS) Symlink(_, _ string) Errno {
return ENOSYS
}
// Unlink implements FS.Unlink
func (UnimplementedFS) Unlink(path string) Errno {
return ENOSYS
}
// Utimens implements FS.Utimens
func (UnimplementedFS) Utimens(path string, atim, mtim int64) Errno {
return ENOSYS
}
// UnimplementedFile is a File that returns ENOSYS for all functions,
// except where no-op are otherwise documented.
//
// This should be embedded to have forward compatible implementations.
type UnimplementedFile struct{}
// Dev implements File.Dev
func (UnimplementedFile) Dev() (uint64, Errno) {
return 0, 0
}
// Ino implements File.Ino
func (UnimplementedFile) Ino() (sys.Inode, Errno) {
return 0, 0
}
// IsDir implements File.IsDir
func (UnimplementedFile) IsDir() (bool, Errno) {
return false, 0
}
// IsAppend implements File.IsAppend
func (UnimplementedFile) IsAppend() bool {
return false
}
// SetAppend implements File.SetAppend
func (UnimplementedFile) SetAppend(bool) Errno {
return ENOSYS
}
// Stat implements File.Stat
func (UnimplementedFile) Stat() (sys.Stat_t, Errno) {
return sys.Stat_t{}, ENOSYS
}
// Read implements File.Read
func (UnimplementedFile) Read([]byte) (int, Errno) {
return 0, ENOSYS
}
// Pread implements File.Pread
func (UnimplementedFile) Pread([]byte, int64) (int, Errno) {
return 0, ENOSYS
}
// Seek implements File.Seek
func (UnimplementedFile) Seek(int64, int) (int64, Errno) {
return 0, ENOSYS
}
// Readdir implements File.Readdir
func (UnimplementedFile) Readdir(int) (dirents []Dirent, errno Errno) {
return nil, ENOSYS
}
// Write implements File.Write
func (UnimplementedFile) Write([]byte) (int, Errno) {
return 0, ENOSYS
}
// Pwrite implements File.Pwrite
func (UnimplementedFile) Pwrite([]byte, int64) (int, Errno) {
return 0, ENOSYS
}
// Truncate implements File.Truncate
func (UnimplementedFile) Truncate(int64) Errno {
return ENOSYS
}
// Sync implements File.Sync
func (UnimplementedFile) Sync() Errno {
return 0 // not ENOSYS
}
// Datasync implements File.Datasync
func (UnimplementedFile) Datasync() Errno {
return 0 // not ENOSYS
}
// Utimens implements File.Utimens
func (UnimplementedFile) Utimens(int64, int64) Errno {
return ENOSYS
}
// Close implements File.Close
func (UnimplementedFile) Close() (errno Errno) { return }

213
vendor/github.com/tetratelabs/wazero/fsconfig.go generated vendored Normal file
View File

@@ -0,0 +1,213 @@
package wazero
import (
"io/fs"
experimentalsys "github.com/tetratelabs/wazero/experimental/sys"
"github.com/tetratelabs/wazero/internal/sys"
"github.com/tetratelabs/wazero/internal/sysfs"
)
// FSConfig configures filesystem paths the embedding host allows the wasm
// guest to access. Unconfigured paths are not allowed, so functions like
// `path_open` result in unsupported errors (e.g. syscall.ENOSYS).
//
// # Guest Path
//
// `guestPath` is the name of the path the guest should use a filesystem for, or
// empty for any files.
//
// All `guestPath` paths are normalized, specifically removing any leading or
// trailing slashes. This means "/", "./" or "." all coerce to empty "".
//
// Multiple `guestPath` values can be configured, but the last longest match
// wins. For example, if "tmp", then "" were added, a request to open
// "tmp/foo.txt" use the filesystem associated with "tmp" even though a wider
// path, "" (all files), was added later.
//
// A `guestPath` of "." coerces to the empty string "" because the current
// directory is handled by the guest. In other words, the guest resolves ites
// current directory prior to requesting files.
//
// More notes on `guestPath`
// - Working directories are typically tracked in wasm, though possible some
// relative paths are requested. For example, TinyGo may attempt to resolve
// a path "../.." in unit tests.
// - Zig uses the first path name it sees as the initial working directory of
// the process.
//
// # Scope
//
// Configuration here is module instance scoped. This means you can use the
// same configuration for multiple calls to Runtime.InstantiateModule. Each
// module will have a different file descriptor table. Any errors accessing
// resources allowed here are deferred to instantiation time of each module.
//
// Any host resources present at the time of configuration, but deleted before
// Runtime.InstantiateModule will trap/panic when the guest wasm initializes or
// calls functions like `fd_read`.
//
// # Windows
//
// While wazero supports Windows as a platform, all known compilers use POSIX
// conventions at runtime. For example, even when running on Windows, paths
// used by wasm are separated by forward slash (/), not backslash (\).
//
// # Notes
//
// - This is an interface for decoupling, not third-party implementations.
// All implementations are in wazero.
// - FSConfig is immutable. Each WithXXX function returns a new instance
// including the corresponding change.
// - RATIONALE.md includes design background and relationship to WebAssembly
// System Interfaces (WASI).
type FSConfig interface {
// WithDirMount assigns a directory at `dir` to any paths beginning at
// `guestPath`.
//
// For example, `dirPath` as / (or c:\ in Windows), makes the entire host
// volume writeable to the path on the guest. The `guestPath` is always a
// POSIX style path, slash (/) delimited, even if run on Windows.
//
// If the same `guestPath` was assigned before, this overrides its value,
// retaining the original precedence. See the documentation of FSConfig for
// more details on `guestPath`.
//
// # Isolation
//
// The guest will have full access to this directory including escaping it
// via relative path lookups like "../../". Full access includes operations
// such as creating or deleting files, limited to any host level access
// controls.
//
// # os.DirFS
//
// This configuration optimizes for WASI compatibility which is sometimes
// at odds with the behavior of os.DirFS. Hence, this will not behave
// exactly the same as os.DirFS. See /RATIONALE.md for more.
WithDirMount(dir, guestPath string) FSConfig
// WithReadOnlyDirMount assigns a directory at `dir` to any paths
// beginning at `guestPath`.
//
// This is the same as WithDirMount except only read operations are
// permitted. However, escaping the directory via relative path lookups
// like "../../" is still allowed.
WithReadOnlyDirMount(dir, guestPath string) FSConfig
// WithFSMount assigns a fs.FS file system for any paths beginning at
// `guestPath`.
//
// If the same `guestPath` was assigned before, this overrides its value,
// retaining the original precedence. See the documentation of FSConfig for
// more details on `guestPath`.
//
// # Isolation
//
// fs.FS does not restrict the ability to overwrite returned files via
// io.Writer. Moreover, os.DirFS documentation includes important notes
// about isolation, which also applies to fs.Sub. As of Go 1.19, the
// built-in file-systems are not jailed (chroot). See
// https://github.com/golang/go/issues/42322
//
// # os.DirFS
//
// Due to limited control and functionality available in os.DirFS, we
// advise using WithDirMount instead. There will be behavior differences
// between os.DirFS and WithDirMount, as the latter biases towards what's
// expected from WASI implementations.
//
// # Custom fs.FileInfo
//
// The underlying implementation supports data not usually in fs.FileInfo
// when `info.Sys` returns *sys.Stat_t. For example, a custom fs.FS can use
// this approach to generate or mask sys.Inode data. Such a filesystem
// needs to decorate any functions that can return fs.FileInfo:
//
// - `Stat` as defined on `fs.File` (always)
// - `Readdir` as defined on `os.File` (if defined)
//
// See sys.NewStat_t for examples.
WithFSMount(fs fs.FS, guestPath string) FSConfig
}
type fsConfig struct {
// fs are the currently configured filesystems.
fs []experimentalsys.FS
// guestPaths are the user-supplied names of the filesystems, retained for
// error messages and fmt.Stringer.
guestPaths []string
// guestPathToFS are the normalized paths to the currently configured
// filesystems, used for de-duplicating.
guestPathToFS map[string]int
}
// NewFSConfig returns a FSConfig that can be used for configuring module instantiation.
func NewFSConfig() FSConfig {
return &fsConfig{guestPathToFS: map[string]int{}}
}
// clone makes a deep copy of this module config.
func (c *fsConfig) clone() *fsConfig {
ret := *c // copy except slice and maps which share a ref
ret.fs = make([]experimentalsys.FS, 0, len(c.fs))
ret.fs = append(ret.fs, c.fs...)
ret.guestPaths = make([]string, 0, len(c.guestPaths))
ret.guestPaths = append(ret.guestPaths, c.guestPaths...)
ret.guestPathToFS = make(map[string]int, len(c.guestPathToFS))
for key, value := range c.guestPathToFS {
ret.guestPathToFS[key] = value
}
return &ret
}
// WithDirMount implements FSConfig.WithDirMount
func (c *fsConfig) WithDirMount(dir, guestPath string) FSConfig {
return c.WithSysFSMount(sysfs.DirFS(dir), guestPath)
}
// WithReadOnlyDirMount implements FSConfig.WithReadOnlyDirMount
func (c *fsConfig) WithReadOnlyDirMount(dir, guestPath string) FSConfig {
return c.WithSysFSMount(&sysfs.ReadFS{FS: sysfs.DirFS(dir)}, guestPath)
}
// WithFSMount implements FSConfig.WithFSMount
func (c *fsConfig) WithFSMount(fs fs.FS, guestPath string) FSConfig {
var adapted experimentalsys.FS
if fs != nil {
adapted = &sysfs.AdaptFS{FS: fs}
}
return c.WithSysFSMount(adapted, guestPath)
}
// WithSysFSMount implements sysfs.FSConfig
func (c *fsConfig) WithSysFSMount(fs experimentalsys.FS, guestPath string) FSConfig {
if _, ok := fs.(experimentalsys.UnimplementedFS); ok {
return c // don't add fake paths.
}
cleaned := sys.StripPrefixesAndTrailingSlash(guestPath)
ret := c.clone()
if i, ok := ret.guestPathToFS[cleaned]; ok {
ret.fs[i] = fs
ret.guestPaths[i] = guestPath
} else if fs != nil {
ret.guestPathToFS[cleaned] = len(ret.fs)
ret.fs = append(ret.fs, fs)
ret.guestPaths = append(ret.guestPaths, guestPath)
}
return ret
}
// preopens returns the possible nil index-correlated preopened filesystems
// with guest paths.
func (c *fsConfig) preopens() ([]experimentalsys.FS, []string) {
preopenCount := len(c.fs)
if preopenCount == 0 {
return nil, nil
}
fs := make([]experimentalsys.FS, len(c.fs))
copy(fs, c.fs)
guestPaths := make([]string, len(c.guestPaths))
copy(guestPaths, c.guestPaths)
return fs, guestPaths
}

View File

@@ -0,0 +1,97 @@
package wasi_snapshot_preview1
import (
"context"
"github.com/tetratelabs/wazero/api"
"github.com/tetratelabs/wazero/experimental/sys"
"github.com/tetratelabs/wazero/internal/wasip1"
"github.com/tetratelabs/wazero/internal/wasm"
)
// argsGet is the WASI function named ArgsGetName that reads command-line
// argument data.
//
// # Parameters
//
// - argv: offset to begin writing argument offsets in uint32 little-endian
// encoding to api.Memory
// - argsSizesGet result argc * 4 bytes are written to this offset
// - argvBuf: offset to write the null terminated arguments to api.Memory
// - argsSizesGet result argv_len bytes are written to this offset
//
// Result (Errno)
//
// The return value is ErrnoSuccess except the following error conditions:
// - sys.EFAULT: there is not enough memory to write results
//
// For example, if argsSizesGet wrote argc=2 and argvLen=5 for arguments:
// "a" and "bc" parameters argv=7 and argvBuf=1, this function writes the below
// to api.Memory:
//
// argvLen uint32le uint32le
// +----------------+ +--------+ +--------+
// | | | | | |
// []byte{?, 'a', 0, 'b', 'c', 0, ?, 1, 0, 0, 0, 3, 0, 0, 0, ?}
// argvBuf --^ ^ ^
// argv --| |
// offset that begins "a" --+ |
// offset that begins "bc" --+
//
// See argsSizesGet
// See https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#args_get
// See https://en.wikipedia.org/wiki/Null-terminated_string
var argsGet = newHostFunc(wasip1.ArgsGetName, argsGetFn, []api.ValueType{i32, i32}, "argv", "argv_buf")
func argsGetFn(_ context.Context, mod api.Module, params []uint64) sys.Errno {
sysCtx := mod.(*wasm.ModuleInstance).Sys
argv, argvBuf := uint32(params[0]), uint32(params[1])
return writeOffsetsAndNullTerminatedValues(mod.Memory(), sysCtx.Args(), argv, argvBuf, sysCtx.ArgsSize())
}
// argsSizesGet is the WASI function named ArgsSizesGetName that reads
// command-line argument sizes.
//
// # Parameters
//
// - resultArgc: offset to write the argument count to api.Memory
// - resultArgvLen: offset to write the null-terminated argument length to
// api.Memory
//
// Result (Errno)
//
// The return value is ErrnoSuccess except the following error conditions:
// - sys.EFAULT: there is not enough memory to write results
//
// For example, if args are "a", "bc" and parameters resultArgc=1 and
// resultArgvLen=6, this function writes the below to api.Memory:
//
// uint32le uint32le
// +--------+ +--------+
// | | | |
// []byte{?, 2, 0, 0, 0, ?, 5, 0, 0, 0, ?}
// resultArgc --^ ^
// 2 args --+ |
// resultArgvLen --|
// len([]byte{'a',0,'b',c',0}) --+
//
// See argsGet
// See https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#args_sizes_get
// See https://en.wikipedia.org/wiki/Null-terminated_string
var argsSizesGet = newHostFunc(wasip1.ArgsSizesGetName, argsSizesGetFn, []api.ValueType{i32, i32}, "result.argc", "result.argv_len")
func argsSizesGetFn(_ context.Context, mod api.Module, params []uint64) sys.Errno {
sysCtx := mod.(*wasm.ModuleInstance).Sys
mem := mod.Memory()
resultArgc, resultArgvLen := uint32(params[0]), uint32(params[1])
// argc and argv_len offsets are not necessarily sequential, so we have to
// write them independently.
if !mem.WriteUint32Le(resultArgc, uint32(len(sysCtx.Args()))) {
return sys.EFAULT
}
if !mem.WriteUint32Le(resultArgvLen, sysCtx.ArgsSize()) {
return sys.EFAULT
}
return 0
}

View File

@@ -0,0 +1,116 @@
package wasi_snapshot_preview1
import (
"context"
"github.com/tetratelabs/wazero/api"
"github.com/tetratelabs/wazero/experimental/sys"
"github.com/tetratelabs/wazero/internal/wasip1"
"github.com/tetratelabs/wazero/internal/wasm"
)
// clockResGet is the WASI function named ClockResGetName that returns the
// resolution of time values returned by clockTimeGet.
//
// # Parameters
//
// - id: clock ID to use
// - resultResolution: offset to write the resolution to api.Memory
// - the resolution is an uint64 little-endian encoding
//
// Result (Errno)
//
// The return value is 0 except the following error conditions:
// - sys.ENOTSUP: the clock ID is not supported.
// - sys.EINVAL: the clock ID is invalid.
// - sys.EFAULT: there is not enough memory to write results
//
// For example, if the resolution is 100ns, this function writes the below to
// api.Memory:
//
// uint64le
// +-------------------------------------+
// | |
// []byte{?, 0x64, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, ?}
// resultResolution --^
//
// Note: This is similar to `clock_getres` in POSIX.
// See https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#-clock_res_getid-clockid---errno-timestamp
// See https://linux.die.net/man/3/clock_getres
var clockResGet = newHostFunc(wasip1.ClockResGetName, clockResGetFn, []api.ValueType{i32, i32}, "id", "result.resolution")
func clockResGetFn(_ context.Context, mod api.Module, params []uint64) sys.Errno {
sysCtx := mod.(*wasm.ModuleInstance).Sys
id, resultResolution := uint32(params[0]), uint32(params[1])
var resolution uint64 // ns
switch id {
case wasip1.ClockIDRealtime:
resolution = uint64(sysCtx.WalltimeResolution())
case wasip1.ClockIDMonotonic:
resolution = uint64(sysCtx.NanotimeResolution())
default:
return sys.EINVAL
}
if !mod.Memory().WriteUint64Le(resultResolution, resolution) {
return sys.EFAULT
}
return 0
}
// clockTimeGet is the WASI function named ClockTimeGetName that returns
// the time value of a name (time.Now).
//
// # Parameters
//
// - id: clock ID to use
// - precision: maximum lag (exclusive) that the returned time value may have,
// compared to its actual value
// - resultTimestamp: offset to write the timestamp to api.Memory
// - the timestamp is epoch nanos encoded as a little-endian uint64
//
// Result (Errno)
//
// The return value is 0 except the following error conditions:
// - sys.ENOTSUP: the clock ID is not supported.
// - sys.EINVAL: the clock ID is invalid.
// - sys.EFAULT: there is not enough memory to write results
//
// For example, if time.Now returned exactly midnight UTC 2022-01-01
// (1640995200000000000), and parameters resultTimestamp=1, this function
// writes the below to api.Memory:
//
// uint64le
// +------------------------------------------+
// | |
// []byte{?, 0x0, 0x0, 0x1f, 0xa6, 0x70, 0xfc, 0xc5, 0x16, ?}
// resultTimestamp --^
//
// Note: This is similar to `clock_gettime` in POSIX.
// See https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#-clock_time_getid-clockid-precision-timestamp---errno-timestamp
// See https://linux.die.net/man/3/clock_gettime
var clockTimeGet = newHostFunc(wasip1.ClockTimeGetName, clockTimeGetFn, []api.ValueType{i32, i64, i32}, "id", "precision", "result.timestamp")
func clockTimeGetFn(_ context.Context, mod api.Module, params []uint64) sys.Errno {
sysCtx := mod.(*wasm.ModuleInstance).Sys
id := uint32(params[0])
// TODO: precision is currently ignored.
// precision = params[1]
resultTimestamp := uint32(params[2])
var val int64
switch id {
case wasip1.ClockIDRealtime:
val = sysCtx.WalltimeNanos()
case wasip1.ClockIDMonotonic:
val = sysCtx.Nanotime()
default:
return sys.EINVAL
}
if !mod.Memory().WriteUint64Le(resultTimestamp, uint64(val)) {
return sys.EFAULT
}
return 0
}

View File

@@ -0,0 +1,100 @@
package wasi_snapshot_preview1
import (
"context"
"github.com/tetratelabs/wazero/api"
"github.com/tetratelabs/wazero/experimental/sys"
"github.com/tetratelabs/wazero/internal/wasip1"
"github.com/tetratelabs/wazero/internal/wasm"
)
// environGet is the WASI function named EnvironGetName that reads
// environment variables.
//
// # Parameters
//
// - environ: offset to begin writing environment offsets in uint32
// little-endian encoding to api.Memory
// - environSizesGet result environc * 4 bytes are written to this offset
// - environBuf: offset to write the null-terminated variables to api.Memory
// - the format is like os.Environ: null-terminated "key=val" entries
// - environSizesGet result environLen bytes are written to this offset
//
// Result (Errno)
//
// The return value is 0 except the following error conditions:
// - sys.EFAULT: there is not enough memory to write results
//
// For example, if environSizesGet wrote environc=2 and environLen=9 for
// environment variables: "a=b", "b=cd" and parameters environ=11 and
// environBuf=1, this function writes the below to api.Memory:
//
// environLen uint32le uint32le
// +------------------------------------+ +--------+ +--------+
// | | | | | |
// []byte{?, 'a', '=', 'b', 0, 'b', '=', 'c', 'd', 0, ?, 1, 0, 0, 0, 5, 0, 0, 0, ?}
// environBuf --^ ^ ^
// environ offset for "a=b" --+ |
// environ offset for "b=cd" --+
//
// See environSizesGet
// See https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#environ_get
// See https://en.wikipedia.org/wiki/Null-terminated_string
var environGet = newHostFunc(wasip1.EnvironGetName, environGetFn, []api.ValueType{i32, i32}, "environ", "environ_buf")
func environGetFn(_ context.Context, mod api.Module, params []uint64) sys.Errno {
sysCtx := mod.(*wasm.ModuleInstance).Sys
environ, environBuf := uint32(params[0]), uint32(params[1])
return writeOffsetsAndNullTerminatedValues(mod.Memory(), sysCtx.Environ(), environ, environBuf, sysCtx.EnvironSize())
}
// environSizesGet is the WASI function named EnvironSizesGetName that
// reads environment variable sizes.
//
// # Parameters
//
// - resultEnvironc: offset to write the count of environment variables to
// api.Memory
// - resultEnvironvLen: offset to write the null-terminated environment
// variable length to api.Memory
//
// Result (Errno)
//
// The return value is 0 except the following error conditions:
// - sys.EFAULT: there is not enough memory to write results
//
// For example, if environ are "a=b","b=cd" and parameters resultEnvironc=1 and
// resultEnvironvLen=6, this function writes the below to api.Memory:
//
// uint32le uint32le
// +--------+ +--------+
// | | | |
// []byte{?, 2, 0, 0, 0, ?, 9, 0, 0, 0, ?}
// resultEnvironc --^ ^
// 2 variables --+ |
// resultEnvironvLen --|
// len([]byte{'a','=','b',0, |
// 'b','=','c','d',0}) --+
//
// See environGet
// https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#environ_sizes_get
// and https://en.wikipedia.org/wiki/Null-terminated_string
var environSizesGet = newHostFunc(wasip1.EnvironSizesGetName, environSizesGetFn, []api.ValueType{i32, i32}, "result.environc", "result.environv_len")
func environSizesGetFn(_ context.Context, mod api.Module, params []uint64) sys.Errno {
sysCtx := mod.(*wasm.ModuleInstance).Sys
mem := mod.Memory()
resultEnvironc, resultEnvironvLen := uint32(params[0]), uint32(params[1])
// environc and environv_len offsets are not necessarily sequential, so we
// have to write them independently.
if !mem.WriteUint32Le(resultEnvironc, uint32(len(sysCtx.Environ()))) {
return sys.EFAULT
}
if !mem.WriteUint32Le(resultEnvironvLen, sysCtx.EnvironSize()) {
return sys.EFAULT
}
return 0
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,237 @@
package wasi_snapshot_preview1
import (
"context"
"time"
"github.com/tetratelabs/wazero/api"
"github.com/tetratelabs/wazero/experimental/sys"
"github.com/tetratelabs/wazero/internal/fsapi"
internalsys "github.com/tetratelabs/wazero/internal/sys"
"github.com/tetratelabs/wazero/internal/wasip1"
"github.com/tetratelabs/wazero/internal/wasm"
)
// pollOneoff is the WASI function named PollOneoffName that concurrently
// polls for the occurrence of a set of events.
//
// # Parameters
//
// - in: pointer to the subscriptions (48 bytes each)
// - out: pointer to the resulting events (32 bytes each)
// - nsubscriptions: count of subscriptions, zero returns sys.EINVAL.
// - resultNevents: count of events.
//
// Result (Errno)
//
// The return value is 0 except the following error conditions:
// - sys.EINVAL: the parameters are invalid
// - sys.ENOTSUP: a parameters is valid, but not yet supported.
// - sys.EFAULT: there is not enough memory to read the subscriptions or
// write results.
//
// # Notes
//
// - Since the `out` pointer nests Errno, the result is always 0.
// - This is similar to `poll` in POSIX.
//
// See https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#poll_oneoff
// See https://linux.die.net/man/3/poll
var pollOneoff = newHostFunc(
wasip1.PollOneoffName, pollOneoffFn,
[]api.ValueType{i32, i32, i32, i32},
"in", "out", "nsubscriptions", "result.nevents",
)
type event struct {
eventType byte
userData []byte
errno wasip1.Errno
}
func pollOneoffFn(_ context.Context, mod api.Module, params []uint64) sys.Errno {
in := uint32(params[0])
out := uint32(params[1])
nsubscriptions := uint32(params[2])
resultNevents := uint32(params[3])
if nsubscriptions == 0 {
return sys.EINVAL
}
mem := mod.Memory()
// Ensure capacity prior to the read loop to reduce error handling.
inBuf, ok := mem.Read(in, nsubscriptions*48)
if !ok {
return sys.EFAULT
}
outBuf, ok := mem.Read(out, nsubscriptions*32)
// zero-out all buffer before writing
clear(outBuf)
if !ok {
return sys.EFAULT
}
// Eagerly write the number of events which will equal subscriptions unless
// there's a fault in parsing (not processing).
if !mod.Memory().WriteUint32Le(resultNevents, nsubscriptions) {
return sys.EFAULT
}
// Loop through all subscriptions and write their output.
// Extract FS context, used in the body of the for loop for FS access.
fsc := mod.(*wasm.ModuleInstance).Sys.FS()
// Slice of events that are processed out of the loop (blocking stdin subscribers).
var blockingStdinSubs []*event
// The timeout is initialized at max Duration, the loop will find the minimum.
var timeout time.Duration = 1<<63 - 1
// Count of all the subscriptions that have been already written back to outBuf.
// nevents*32 returns at all times the offset where the next event should be written:
// this way we ensure that there are no gaps between records.
nevents := uint32(0)
// Layout is subscription_u: Union
// https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#subscription_u
for i := uint32(0); i < nsubscriptions; i++ {
inOffset := i * 48
outOffset := nevents * 32
eventType := inBuf[inOffset+8] // +8 past userdata
// +8 past userdata +8 contents_offset
argBuf := inBuf[inOffset+8+8:]
userData := inBuf[inOffset : inOffset+8]
evt := &event{
eventType: eventType,
userData: userData,
errno: wasip1.ErrnoSuccess,
}
switch eventType {
case wasip1.EventTypeClock: // handle later
newTimeout, err := processClockEvent(argBuf)
if err != 0 {
return err
}
// Min timeout.
if newTimeout < timeout {
timeout = newTimeout
}
// Ack the clock event to the outBuf.
writeEvent(outBuf[outOffset:], evt)
nevents++
case wasip1.EventTypeFdRead:
fd := int32(le.Uint32(argBuf))
if fd < 0 {
return sys.EBADF
}
if file, ok := fsc.LookupFile(fd); !ok {
evt.errno = wasip1.ErrnoBadf
writeEvent(outBuf[outOffset:], evt)
nevents++
} else if fd != internalsys.FdStdin && file.File.IsNonblock() {
writeEvent(outBuf[outOffset:], evt)
nevents++
} else {
// if the fd is Stdin, and it is in blocking mode,
// do not ack yet, append to a slice for delayed evaluation.
blockingStdinSubs = append(blockingStdinSubs, evt)
}
case wasip1.EventTypeFdWrite:
fd := int32(le.Uint32(argBuf))
if fd < 0 {
return sys.EBADF
}
if _, ok := fsc.LookupFile(fd); ok {
evt.errno = wasip1.ErrnoNotsup
} else {
evt.errno = wasip1.ErrnoBadf
}
nevents++
writeEvent(outBuf[outOffset:], evt)
default:
return sys.EINVAL
}
}
sysCtx := mod.(*wasm.ModuleInstance).Sys
if nevents == nsubscriptions {
// We already wrote back all the results. We already wrote this number
// earlier to offset `resultNevents`.
// We only need to observe the timeout (nonzero if there are clock subscriptions)
// and return.
if timeout > 0 {
sysCtx.Nanosleep(int64(timeout))
}
return 0
}
// If there are blocking stdin subscribers, check for data with given timeout.
stdin, ok := fsc.LookupFile(internalsys.FdStdin)
if !ok {
return sys.EBADF
}
// Wait for the timeout to expire, or for some data to become available on Stdin.
if stdinReady, errno := stdin.File.Poll(fsapi.POLLIN, int32(timeout.Milliseconds())); errno != 0 {
return errno
} else if stdinReady {
// stdin has data ready to for reading, write back all the events
for i := range blockingStdinSubs {
evt := blockingStdinSubs[i]
evt.errno = 0
writeEvent(outBuf[nevents*32:], evt)
nevents++
}
}
if nevents != nsubscriptions {
if !mod.Memory().WriteUint32Le(resultNevents, nevents) {
return sys.EFAULT
}
}
return 0
}
// processClockEvent supports only relative name events, as that's what's used
// to implement sleep in various compilers including Rust, Zig and TinyGo.
func processClockEvent(inBuf []byte) (time.Duration, sys.Errno) {
_ /* ID */ = le.Uint32(inBuf[0:8]) // See below
timeout := le.Uint64(inBuf[8:16]) // nanos if relative
_ /* precision */ = le.Uint64(inBuf[16:24]) // Unused
flags := le.Uint16(inBuf[24:32])
var err sys.Errno
// subclockflags has only one flag defined: subscription_clock_abstime
switch flags {
case 0: // relative time
case 1: // subscription_clock_abstime
err = sys.ENOTSUP
default: // subclockflags has only one flag defined.
err = sys.EINVAL
}
if err != 0 {
return 0, err
} else {
// https://linux.die.net/man/3/clock_settime says relative timers are
// unaffected. Since this function only supports relative timeout, we can
// skip name ID validation and use a single sleep function.
return time.Duration(timeout), 0
}
}
// writeEvent writes the event corresponding to the processed subscription.
// https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#-event-struct
func writeEvent(outBuf []byte, evt *event) {
copy(outBuf, evt.userData) // userdata
outBuf[8] = byte(evt.errno) // uint16, but safe as < 255
outBuf[9] = 0
le.PutUint32(outBuf[10:], uint32(evt.eventType))
// TODO: When FD events are supported, write outOffset+16
}

View File

@@ -0,0 +1,44 @@
package wasi_snapshot_preview1
import (
"context"
"github.com/tetratelabs/wazero/api"
"github.com/tetratelabs/wazero/internal/wasip1"
"github.com/tetratelabs/wazero/internal/wasm"
"github.com/tetratelabs/wazero/sys"
)
// procExit is the WASI function named ProcExitName that terminates the
// execution of the module with an exit code. The only successful exit code is
// zero.
//
// # Parameters
//
// - exitCode: exit code.
//
// See https://github.com/WebAssembly/WASI/blob/main/phases/snapshot/docs.md#proc_exit
var procExit = &wasm.HostFunc{
ExportName: wasip1.ProcExitName,
Name: wasip1.ProcExitName,
ParamTypes: []api.ValueType{i32},
ParamNames: []string{"rval"},
Code: wasm.Code{GoFunc: api.GoModuleFunc(procExitFn)},
}
func procExitFn(ctx context.Context, mod api.Module, params []uint64) {
exitCode := uint32(params[0])
// Ensure other callers see the exit code.
_ = mod.CloseWithExitCode(ctx, exitCode)
// Prevent any code from executing after this function. For example, LLVM
// inserts unreachable instructions after calls to exit.
// See: https://github.com/emscripten-core/emscripten/issues/12322
panic(sys.NewExitError(exitCode))
}
// procRaise is stubbed and will never be supported, as it was removed.
//
// See https://github.com/WebAssembly/WASI/pull/136
var procRaise = stubFunction(wasip1.ProcRaiseName, []api.ValueType{i32}, "sig")

Some files were not shown because too many files have changed in this diff Show More