Merge pull request #614 from crosbymichael/no-stdin
Provide nil stdin for standard build tasksdocker-18.09
commit
48655e27d7
|
@ -79,6 +79,7 @@ func TestClientIntegration(t *testing.T) {
|
|||
testNetworkMode,
|
||||
testFrontendMetadataReturn,
|
||||
testSSHMount,
|
||||
testStdinClosed,
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -86,6 +87,23 @@ func newContainerd(cdAddress string) (*containerd.Client, error) {
|
|||
return containerd.New(cdAddress, containerd.WithTimeout(60*time.Second))
|
||||
}
|
||||
|
||||
// moby/buildkit#614
|
||||
func testStdinClosed(t *testing.T, sb integration.Sandbox) {
|
||||
t.Parallel()
|
||||
|
||||
c, err := New(context.TODO(), sb.Address())
|
||||
require.NoError(t, err)
|
||||
defer c.Close()
|
||||
|
||||
st := llb.Image("busybox:latest").Run(llb.Shlex("cat"))
|
||||
|
||||
def, err := st.Marshal()
|
||||
require.NoError(t, err)
|
||||
|
||||
_, err = c.Solve(context.TODO(), def, SolveOpt{}, nil)
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
func testSSHMount(t *testing.T, sb integration.Sandbox) {
|
||||
t.Parallel()
|
||||
|
||||
|
|
|
@ -133,10 +133,6 @@ func (w containerdExecutor) Exec(ctx context.Context, meta executor.Meta, root c
|
|||
}
|
||||
}()
|
||||
|
||||
if stdin == nil {
|
||||
stdin = &emptyReadCloser{}
|
||||
}
|
||||
|
||||
task, err := container.NewTask(ctx, cio.NewCreator(cio.WithStreams(stdin, stdout, stderr)), containerd.WithRootFS(rootMounts))
|
||||
if err != nil {
|
||||
return err
|
||||
|
@ -177,13 +173,3 @@ func (w containerdExecutor) Exec(ctx context.Context, meta executor.Meta, root c
|
|||
}
|
||||
|
||||
}
|
||||
|
||||
type emptyReadCloser struct{}
|
||||
|
||||
func (*emptyReadCloser) Read([]byte) (int, error) {
|
||||
return 0, io.EOF
|
||||
}
|
||||
|
||||
func (*emptyReadCloser) Close() error {
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -6,7 +6,7 @@ github.com/davecgh/go-spew v1.1.0
|
|||
github.com/pmezard/go-difflib v1.0.0
|
||||
golang.org/x/sys 1b2967e3c290b7c545b3db0deeda16e9be4f98a2
|
||||
|
||||
github.com/containerd/containerd v1.2.0-beta.2
|
||||
github.com/containerd/containerd 1597270d0468ccebd29b78164c2e902514f426fd
|
||||
github.com/containerd/typeurl a93fcdb778cd272c6e9b3028b2f42d813e785d40
|
||||
golang.org/x/sync 450f422ab23cf9881c94e2db30cac0eb1b7cf80c
|
||||
github.com/sirupsen/logrus v1.0.0
|
||||
|
@ -16,7 +16,7 @@ golang.org/x/net 0ed95abb35c445290478a5348a7b38bb154135fd
|
|||
github.com/gogo/protobuf v1.0.0
|
||||
github.com/gogo/googleapis b23578765ee54ff6bceff57f397d833bf4ca6869
|
||||
github.com/golang/protobuf v1.1.0
|
||||
github.com/containerd/continuity d3c23511c1bf5851696cba83143d9cbcd666869b
|
||||
github.com/containerd/continuity f44b615e492bdfb371aae2f76ec694d9da1db537
|
||||
github.com/opencontainers/image-spec v1.0.1
|
||||
github.com/opencontainers/runc 20aff4f0488c6d4b8df4d85b4f63f1f704c11abd
|
||||
github.com/Microsoft/go-winio v0.4.10
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
[![GoDoc](https://godoc.org/github.com/containerd/containerd?status.svg)](https://godoc.org/github.com/containerd/containerd)
|
||||
[![Build Status](https://travis-ci.org/containerd/containerd.svg?branch=master)](https://travis-ci.org/containerd/containerd)
|
||||
[![Windows Build Status](https://ci.appveyor.com/api/projects/status/github/containerd/containerd?branch=master&svg=true)](https://ci.appveyor.com/project/mlaventure/containerd-3g73f?branch=master)
|
||||
[![FOSSA Status](https://app.fossa.io/api/projects/git%2Bhttps%3A%2F%2Fgithub.com%2Fcontainerd%2Fcontainerd.svg?type=shield)](https://app.fossa.io/projects/git%2Bhttps%3A%2F%2Fgithub.com%2Fcontainerd%2Fcontainerd?ref=badge_shield)
|
||||
[![Go Report Card](https://goreportcard.com/badge/github.com/containerd/containerd)](https://goreportcard.com/report/github.com/containerd/containerd)
|
||||
[![CII Best Practices](https://bestpractices.coreinfrastructure.org/projects/1271/badge)](https://bestpractices.coreinfrastructure.org/projects/1271)
|
||||
|
|
|
@ -141,7 +141,7 @@ type EventsClient interface {
|
|||
// Forward sends an event that has already been packaged into an envelope
|
||||
// with a timestamp and namespace.
|
||||
//
|
||||
// This is useful if earlier timestamping is required or when fowarding on
|
||||
// This is useful if earlier timestamping is required or when forwarding on
|
||||
// behalf of another component, namespace or publisher.
|
||||
Forward(ctx context.Context, in *ForwardRequest, opts ...grpc.CallOption) (*google_protobuf2.Empty, error)
|
||||
// Subscribe to a stream of events, possibly returning only that match any
|
||||
|
@ -223,7 +223,7 @@ type EventsServer interface {
|
|||
// Forward sends an event that has already been packaged into an envelope
|
||||
// with a timestamp and namespace.
|
||||
//
|
||||
// This is useful if earlier timestamping is required or when fowarding on
|
||||
// This is useful if earlier timestamping is required or when forwarding on
|
||||
// behalf of another component, namespace or publisher.
|
||||
Forward(context.Context, *ForwardRequest) (*google_protobuf2.Empty, error)
|
||||
// Subscribe to a stream of events, possibly returning only that match any
|
||||
|
|
|
@ -20,7 +20,7 @@ service Events {
|
|||
// Forward sends an event that has already been packaged into an envelope
|
||||
// with a timestamp and namespace.
|
||||
//
|
||||
// This is useful if earlier timestamping is required or when fowarding on
|
||||
// This is useful if earlier timestamping is required or when forwarding on
|
||||
// behalf of another component, namespace or publisher.
|
||||
rpc Forward(ForwardRequest) returns (google.protobuf.Empty);
|
||||
|
||||
|
|
|
@ -141,6 +141,15 @@ func NewCreator(opts ...Opt) Creator {
|
|||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if streams.Stdin == nil {
|
||||
fifos.Stdin = ""
|
||||
}
|
||||
if streams.Stdout == nil {
|
||||
fifos.Stdout = ""
|
||||
}
|
||||
if streams.Stderr == nil {
|
||||
fifos.Stderr = ""
|
||||
}
|
||||
return copyIO(fifos, streams)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -20,25 +20,21 @@ package containerd
|
|||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"syscall"
|
||||
|
||||
"github.com/containerd/containerd/api/types"
|
||||
"github.com/containerd/containerd/containers"
|
||||
"github.com/containerd/containerd/content"
|
||||
"github.com/containerd/containerd/errdefs"
|
||||
"github.com/containerd/containerd/images"
|
||||
"github.com/containerd/containerd/mount"
|
||||
"github.com/containerd/containerd/platforms"
|
||||
"github.com/containerd/containerd/runtime/linux/runctypes"
|
||||
"github.com/gogo/protobuf/proto"
|
||||
protobuf "github.com/gogo/protobuf/types"
|
||||
"github.com/opencontainers/image-spec/identity"
|
||||
"github.com/opencontainers/image-spec/specs-go/v1"
|
||||
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
|
@ -105,44 +101,6 @@ func WithCheckpoint(im Image, snapshotKey string) NewContainerOpts {
|
|||
}
|
||||
}
|
||||
|
||||
// WithTaskCheckpoint allows a task to be created with live runtime and memory data from a
|
||||
// previous checkpoint. Additional software such as CRIU may be required to
|
||||
// restore a task from a checkpoint
|
||||
func WithTaskCheckpoint(im Image) NewTaskOpts {
|
||||
return func(ctx context.Context, c *Client, info *TaskInfo) error {
|
||||
desc := im.Target()
|
||||
id := desc.Digest
|
||||
index, err := decodeIndex(ctx, c.ContentStore(), desc)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, m := range index.Manifests {
|
||||
if m.MediaType == images.MediaTypeContainerd1Checkpoint {
|
||||
info.Checkpoint = &types.Descriptor{
|
||||
MediaType: m.MediaType,
|
||||
Size_: m.Size,
|
||||
Digest: m.Digest,
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
return fmt.Errorf("checkpoint not found in index %s", id)
|
||||
}
|
||||
}
|
||||
|
||||
func decodeIndex(ctx context.Context, store content.Provider, desc ocispec.Descriptor) (*v1.Index, error) {
|
||||
var index v1.Index
|
||||
p, err := content.ReadBlob(ctx, store, desc)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := json.Unmarshal(p, &index); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &index, nil
|
||||
}
|
||||
|
||||
// WithRemappedSnapshot creates a new snapshot and remaps the uid/gid for the
|
||||
// filesystem to be used by a container with user namespaces
|
||||
func WithRemappedSnapshot(id string, i Image, uid, gid uint32) NewContainerOpts {
|
||||
|
@ -221,19 +179,3 @@ func incrementFS(root string, uidInc, gidInc uint32) filepath.WalkFunc {
|
|||
return os.Lchown(path, u, g)
|
||||
}
|
||||
}
|
||||
|
||||
// WithNoPivotRoot instructs the runtime not to you pivot_root
|
||||
func WithNoPivotRoot(_ context.Context, _ *Client, info *TaskInfo) error {
|
||||
if info.Options == nil {
|
||||
info.Options = &runctypes.CreateOptions{
|
||||
NoPivotRoot: true,
|
||||
}
|
||||
return nil
|
||||
}
|
||||
copts, ok := info.Options.(*runctypes.CreateOptions)
|
||||
if !ok {
|
||||
return errors.New("invalid options type, expected runctypes.CreateOptions")
|
||||
}
|
||||
copts.NoPivotRoot = true
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -33,6 +33,8 @@ import (
|
|||
"github.com/containerd/containerd/errdefs"
|
||||
"github.com/containerd/containerd/filters"
|
||||
"github.com/containerd/containerd/log"
|
||||
|
||||
"github.com/containerd/continuity"
|
||||
digest "github.com/opencontainers/go-digest"
|
||||
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
|
||||
"github.com/pkg/errors"
|
||||
|
@ -651,5 +653,5 @@ func writeTimestampFile(p string, t time.Time) error {
|
|||
return err
|
||||
}
|
||||
|
||||
return ioutil.WriteFile(p, b, 0666)
|
||||
return continuity.AtomicWriteFile(p, b, 0666)
|
||||
}
|
||||
|
|
|
@ -30,7 +30,7 @@ import (
|
|||
)
|
||||
|
||||
// WithProfile receives the name of a file stored on disk comprising a json
|
||||
// formated seccomp profile, as specified by the opencontainers/runtime-spec.
|
||||
// formatted seccomp profile, as specified by the opencontainers/runtime-spec.
|
||||
// The profile is read from the file, unmarshaled, and set to the spec.
|
||||
func WithProfile(profile string) oci.SpecOpts {
|
||||
return func(_ context.Context, _ oci.Client, _ *containers.Container, s *specs.Spec) error {
|
||||
|
|
|
@ -52,7 +52,7 @@ var _ events.Subscriber = &Exchange{}
|
|||
|
||||
// Forward accepts an envelope to be direcly distributed on the exchange.
|
||||
//
|
||||
// This is useful when an event is forwaded on behalf of another namespace or
|
||||
// This is useful when an event is forwarded on behalf of another namespace or
|
||||
// when the event is propagated on behalf of another publisher.
|
||||
func (e *Exchange) Forward(ctx context.Context, envelope *events.Envelope) (err error) {
|
||||
if err := validateEnvelope(envelope); err != nil {
|
||||
|
|
|
@ -0,0 +1,57 @@
|
|||
/*
|
||||
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 containerd
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io"
|
||||
|
||||
"github.com/containerd/containerd/images"
|
||||
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
|
||||
)
|
||||
|
||||
type exportOpts struct {
|
||||
}
|
||||
|
||||
// ExportOpt allows the caller to specify export-specific options
|
||||
type ExportOpt func(c *exportOpts) error
|
||||
|
||||
func resolveExportOpt(opts ...ExportOpt) (exportOpts, error) {
|
||||
var eopts exportOpts
|
||||
for _, o := range opts {
|
||||
if err := o(&eopts); err != nil {
|
||||
return eopts, err
|
||||
}
|
||||
}
|
||||
return eopts, nil
|
||||
}
|
||||
|
||||
// Export exports an image to a Tar stream.
|
||||
// OCI format is used by default.
|
||||
// It is up to caller to put "org.opencontainers.image.ref.name" annotation to desc.
|
||||
// TODO(AkihiroSuda): support exporting multiple descriptors at once to a single archive stream.
|
||||
func (c *Client) Export(ctx context.Context, exporter images.Exporter, desc ocispec.Descriptor, opts ...ExportOpt) (io.ReadCloser, error) {
|
||||
_, err := resolveExportOpt(opts...) // unused now
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
pr, pw := io.Pipe()
|
||||
go func() {
|
||||
pw.CloseWithError(exporter.Export(ctx, c.ContentStore(), desc, pw))
|
||||
}()
|
||||
return pr, nil
|
||||
}
|
|
@ -22,7 +22,6 @@ import (
|
|||
|
||||
"github.com/containerd/containerd/errdefs"
|
||||
"github.com/containerd/containerd/images"
|
||||
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
|
||||
)
|
||||
|
||||
type importOpts struct {
|
||||
|
@ -84,35 +83,3 @@ func (c *Client) Import(ctx context.Context, importer images.Importer, reader io
|
|||
}
|
||||
return images, nil
|
||||
}
|
||||
|
||||
type exportOpts struct {
|
||||
}
|
||||
|
||||
// ExportOpt allows the caller to specify export-specific options
|
||||
type ExportOpt func(c *exportOpts) error
|
||||
|
||||
func resolveExportOpt(opts ...ExportOpt) (exportOpts, error) {
|
||||
var eopts exportOpts
|
||||
for _, o := range opts {
|
||||
if err := o(&eopts); err != nil {
|
||||
return eopts, err
|
||||
}
|
||||
}
|
||||
return eopts, nil
|
||||
}
|
||||
|
||||
// Export exports an image to a Tar stream.
|
||||
// OCI format is used by default.
|
||||
// It is up to caller to put "org.opencontainers.image.ref.name" annotation to desc.
|
||||
// TODO(AkihiroSuda): support exporting multiple descriptors at once to a single archive stream.
|
||||
func (c *Client) Export(ctx context.Context, exporter images.Exporter, desc ocispec.Descriptor, opts ...ExportOpt) (io.ReadCloser, error) {
|
||||
_, err := resolveExportOpt(opts...) // unused now
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
pr, pw := io.Pipe()
|
||||
go func() {
|
||||
pw.CloseWithError(exporter.Export(ctx, c.ContentStore(), desc, pw))
|
||||
}()
|
||||
return pr, nil
|
||||
}
|
||||
|
|
|
@ -32,6 +32,10 @@ var (
|
|||
|
||||
// Mount to the provided target
|
||||
func (m *Mount) Mount(target string) error {
|
||||
if m.Type != "windows-layer" {
|
||||
return errors.Errorf("invalid windows mount type: '%s'", m.Type)
|
||||
}
|
||||
|
||||
home, layerID := filepath.Split(m.Source)
|
||||
|
||||
parentLayerPaths, err := m.GetParentPaths()
|
||||
|
|
|
@ -18,11 +18,27 @@ package oci
|
|||
|
||||
import (
|
||||
"context"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
|
||||
"github.com/containerd/containerd/namespaces"
|
||||
"github.com/containerd/containerd/platforms"
|
||||
|
||||
"github.com/containerd/containerd/containers"
|
||||
specs "github.com/opencontainers/runtime-spec/specs-go"
|
||||
)
|
||||
|
||||
const (
|
||||
rwm = "rwm"
|
||||
defaultRootfsPath = "rootfs"
|
||||
)
|
||||
|
||||
var (
|
||||
defaultUnixEnv = []string{
|
||||
"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
|
||||
}
|
||||
)
|
||||
|
||||
// Spec is a type alias to the OCI runtime spec to allow third part SpecOpts
|
||||
// to be created without the "issues" with go vendoring and package imports
|
||||
type Spec = specs.Spec
|
||||
|
@ -30,12 +46,36 @@ type Spec = specs.Spec
|
|||
// GenerateSpec will generate a default spec from the provided image
|
||||
// for use as a containerd container
|
||||
func GenerateSpec(ctx context.Context, client Client, c *containers.Container, opts ...SpecOpts) (*Spec, error) {
|
||||
s, err := createDefaultSpec(ctx, c.ID)
|
||||
if err != nil {
|
||||
return GenerateSpecWithPlatform(ctx, client, platforms.DefaultString(), c, opts...)
|
||||
}
|
||||
|
||||
// GenerateSpecWithPlatform will generate a default spec from the provided image
|
||||
// for use as a containerd container in the platform requested.
|
||||
func GenerateSpecWithPlatform(ctx context.Context, client Client, platform string, c *containers.Container, opts ...SpecOpts) (*Spec, error) {
|
||||
var s Spec
|
||||
if err := generateDefaultSpecWithPlatform(ctx, platform, c.ID, &s); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return s, ApplyOpts(ctx, client, c, s, opts...)
|
||||
return &s, ApplyOpts(ctx, client, c, &s, opts...)
|
||||
}
|
||||
|
||||
func generateDefaultSpecWithPlatform(ctx context.Context, platform, id string, s *Spec) error {
|
||||
plat, err := platforms.Parse(platform)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if plat.OS == "windows" {
|
||||
err = populateDefaultWindowsSpec(ctx, s, id)
|
||||
} else {
|
||||
err = populateDefaultUnixSpec(ctx, s, id)
|
||||
if err == nil && runtime.GOOS == "windows" {
|
||||
// To run LCOW we have a Linux and Windows section. Add an empty one now.
|
||||
s.Windows = &specs.Windows{}
|
||||
}
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// ApplyOpts applys the options to the given spec, injecting data from the
|
||||
|
@ -50,7 +90,173 @@ func ApplyOpts(ctx context.Context, client Client, c *containers.Container, s *S
|
|||
return nil
|
||||
}
|
||||
|
||||
func createDefaultSpec(ctx context.Context, id string) (*Spec, error) {
|
||||
var s Spec
|
||||
return &s, populateDefaultSpec(ctx, &s, id)
|
||||
func defaultUnixCaps() []string {
|
||||
return []string{
|
||||
"CAP_CHOWN",
|
||||
"CAP_DAC_OVERRIDE",
|
||||
"CAP_FSETID",
|
||||
"CAP_FOWNER",
|
||||
"CAP_MKNOD",
|
||||
"CAP_NET_RAW",
|
||||
"CAP_SETGID",
|
||||
"CAP_SETUID",
|
||||
"CAP_SETFCAP",
|
||||
"CAP_SETPCAP",
|
||||
"CAP_NET_BIND_SERVICE",
|
||||
"CAP_SYS_CHROOT",
|
||||
"CAP_KILL",
|
||||
"CAP_AUDIT_WRITE",
|
||||
}
|
||||
}
|
||||
|
||||
func defaultUnixNamespaces() []specs.LinuxNamespace {
|
||||
return []specs.LinuxNamespace{
|
||||
{
|
||||
Type: specs.PIDNamespace,
|
||||
},
|
||||
{
|
||||
Type: specs.IPCNamespace,
|
||||
},
|
||||
{
|
||||
Type: specs.UTSNamespace,
|
||||
},
|
||||
{
|
||||
Type: specs.MountNamespace,
|
||||
},
|
||||
{
|
||||
Type: specs.NetworkNamespace,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func populateDefaultUnixSpec(ctx context.Context, s *Spec, id string) error {
|
||||
ns, err := namespaces.NamespaceRequired(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
*s = Spec{
|
||||
Version: specs.Version,
|
||||
Root: &specs.Root{
|
||||
Path: defaultRootfsPath,
|
||||
},
|
||||
Process: &specs.Process{
|
||||
Env: defaultUnixEnv,
|
||||
Cwd: "/",
|
||||
NoNewPrivileges: true,
|
||||
User: specs.User{
|
||||
UID: 0,
|
||||
GID: 0,
|
||||
},
|
||||
Capabilities: &specs.LinuxCapabilities{
|
||||
Bounding: defaultUnixCaps(),
|
||||
Permitted: defaultUnixCaps(),
|
||||
Inheritable: defaultUnixCaps(),
|
||||
Effective: defaultUnixCaps(),
|
||||
},
|
||||
Rlimits: []specs.POSIXRlimit{
|
||||
{
|
||||
Type: "RLIMIT_NOFILE",
|
||||
Hard: uint64(1024),
|
||||
Soft: uint64(1024),
|
||||
},
|
||||
},
|
||||
},
|
||||
Mounts: []specs.Mount{
|
||||
{
|
||||
Destination: "/proc",
|
||||
Type: "proc",
|
||||
Source: "proc",
|
||||
},
|
||||
{
|
||||
Destination: "/dev",
|
||||
Type: "tmpfs",
|
||||
Source: "tmpfs",
|
||||
Options: []string{"nosuid", "strictatime", "mode=755", "size=65536k"},
|
||||
},
|
||||
{
|
||||
Destination: "/dev/pts",
|
||||
Type: "devpts",
|
||||
Source: "devpts",
|
||||
Options: []string{"nosuid", "noexec", "newinstance", "ptmxmode=0666", "mode=0620", "gid=5"},
|
||||
},
|
||||
{
|
||||
Destination: "/dev/shm",
|
||||
Type: "tmpfs",
|
||||
Source: "shm",
|
||||
Options: []string{"nosuid", "noexec", "nodev", "mode=1777", "size=65536k"},
|
||||
},
|
||||
{
|
||||
Destination: "/dev/mqueue",
|
||||
Type: "mqueue",
|
||||
Source: "mqueue",
|
||||
Options: []string{"nosuid", "noexec", "nodev"},
|
||||
},
|
||||
{
|
||||
Destination: "/sys",
|
||||
Type: "sysfs",
|
||||
Source: "sysfs",
|
||||
Options: []string{"nosuid", "noexec", "nodev", "ro"},
|
||||
},
|
||||
{
|
||||
Destination: "/run",
|
||||
Type: "tmpfs",
|
||||
Source: "tmpfs",
|
||||
Options: []string{"nosuid", "strictatime", "mode=755", "size=65536k"},
|
||||
},
|
||||
},
|
||||
Linux: &specs.Linux{
|
||||
MaskedPaths: []string{
|
||||
"/proc/acpi",
|
||||
"/proc/kcore",
|
||||
"/proc/keys",
|
||||
"/proc/latency_stats",
|
||||
"/proc/timer_list",
|
||||
"/proc/timer_stats",
|
||||
"/proc/sched_debug",
|
||||
"/sys/firmware",
|
||||
"/proc/scsi",
|
||||
},
|
||||
ReadonlyPaths: []string{
|
||||
"/proc/asound",
|
||||
"/proc/bus",
|
||||
"/proc/fs",
|
||||
"/proc/irq",
|
||||
"/proc/sys",
|
||||
"/proc/sysrq-trigger",
|
||||
},
|
||||
CgroupsPath: filepath.Join("/", ns, id),
|
||||
Resources: &specs.LinuxResources{
|
||||
Devices: []specs.LinuxDeviceCgroup{
|
||||
{
|
||||
Allow: false,
|
||||
Access: rwm,
|
||||
},
|
||||
},
|
||||
},
|
||||
Namespaces: defaultUnixNamespaces(),
|
||||
},
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func populateDefaultWindowsSpec(ctx context.Context, s *Spec, id string) error {
|
||||
*s = Spec{
|
||||
Version: specs.Version,
|
||||
Root: &specs.Root{},
|
||||
Process: &specs.Process{
|
||||
Cwd: `C:\`,
|
||||
ConsoleSize: &specs.Box{
|
||||
Width: 80,
|
||||
Height: 20,
|
||||
},
|
||||
},
|
||||
Windows: &specs.Windows{
|
||||
IgnoreFlushesDuringBoot: true,
|
||||
Network: &specs.WindowsNetwork{
|
||||
AllowUnqualifiedDNSQuery: true,
|
||||
},
|
||||
},
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -19,12 +19,25 @@ package oci
|
|||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/containerd/containerd/containers"
|
||||
"github.com/containerd/containerd/content"
|
||||
"github.com/containerd/containerd/images"
|
||||
"github.com/containerd/containerd/mount"
|
||||
"github.com/containerd/containerd/namespaces"
|
||||
"github.com/containerd/containerd/platforms"
|
||||
"github.com/containerd/continuity/fs"
|
||||
"github.com/opencontainers/image-spec/specs-go/v1"
|
||||
"github.com/opencontainers/runc/libcontainer/user"
|
||||
specs "github.com/opencontainers/runtime-spec/specs-go"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/syndtr/gocapability/capability"
|
||||
)
|
||||
|
||||
// SpecOpts sets spec specific information to a newly generated OCI spec
|
||||
|
@ -49,13 +62,45 @@ func setProcess(s *Spec) {
|
|||
}
|
||||
}
|
||||
|
||||
// setRoot sets Root to empty if unset
|
||||
func setRoot(s *Spec) {
|
||||
if s.Root == nil {
|
||||
s.Root = &specs.Root{}
|
||||
}
|
||||
}
|
||||
|
||||
// setLinux sets Linux to empty if unset
|
||||
func setLinux(s *Spec) {
|
||||
if s.Linux == nil {
|
||||
s.Linux = &specs.Linux{}
|
||||
}
|
||||
}
|
||||
|
||||
// setCapabilities sets Linux Capabilities to empty if unset
|
||||
func setCapabilities(s *Spec) {
|
||||
setProcess(s)
|
||||
if s.Process.Capabilities == nil {
|
||||
s.Process.Capabilities = &specs.LinuxCapabilities{}
|
||||
}
|
||||
}
|
||||
|
||||
// WithDefaultSpec returns a SpecOpts that will populate the spec with default
|
||||
// values.
|
||||
//
|
||||
// Use as the first option to clear the spec, then apply options afterwards.
|
||||
func WithDefaultSpec() SpecOpts {
|
||||
return func(ctx context.Context, _ Client, c *containers.Container, s *Spec) error {
|
||||
return populateDefaultSpec(ctx, s, c.ID)
|
||||
return generateDefaultSpecWithPlatform(ctx, platforms.DefaultString(), c.ID, s)
|
||||
}
|
||||
}
|
||||
|
||||
// WithDefaultSpecForPlatform returns a SpecOpts that will populate the spec
|
||||
// with default values for a given platform.
|
||||
//
|
||||
// Use as the first option to clear the spec, then apply options afterwards.
|
||||
func WithDefaultSpecForPlatform(platform string) SpecOpts {
|
||||
return func(ctx context.Context, _ Client, c *containers.Container, s *Spec) error {
|
||||
return generateDefaultSpecWithPlatform(ctx, platform, c.ID, s)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -81,32 +126,6 @@ func WithSpecFromFile(filename string) SpecOpts {
|
|||
}
|
||||
}
|
||||
|
||||
// WithProcessArgs replaces the args on the generated spec
|
||||
func WithProcessArgs(args ...string) SpecOpts {
|
||||
return func(_ context.Context, _ Client, _ *containers.Container, s *Spec) error {
|
||||
setProcess(s)
|
||||
s.Process.Args = args
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// WithProcessCwd replaces the current working directory on the generated spec
|
||||
func WithProcessCwd(cwd string) SpecOpts {
|
||||
return func(_ context.Context, _ Client, _ *containers.Container, s *Spec) error {
|
||||
setProcess(s)
|
||||
s.Process.Cwd = cwd
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// WithHostname sets the container's hostname
|
||||
func WithHostname(name string) SpecOpts {
|
||||
return func(_ context.Context, _ Client, _ *containers.Container, s *Spec) error {
|
||||
s.Hostname = name
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// WithEnv appends environment variables
|
||||
func WithEnv(environmentVariables []string) SpecOpts {
|
||||
return func(_ context.Context, _ Client, _ *containers.Container, s *Spec) error {
|
||||
|
@ -118,14 +137,6 @@ func WithEnv(environmentVariables []string) SpecOpts {
|
|||
}
|
||||
}
|
||||
|
||||
// WithMounts appends mounts
|
||||
func WithMounts(mounts []specs.Mount) SpecOpts {
|
||||
return func(_ context.Context, _ Client, _ *containers.Container, s *Spec) error {
|
||||
s.Mounts = append(s.Mounts, mounts...)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// replaceOrAppendEnvValues returns the defaults with the overrides either
|
||||
// replaced by env key or appended to the list
|
||||
func replaceOrAppendEnvValues(defaults, overrides []string) []string {
|
||||
|
@ -163,3 +174,741 @@ func replaceOrAppendEnvValues(defaults, overrides []string) []string {
|
|||
|
||||
return defaults
|
||||
}
|
||||
|
||||
// WithProcessArgs replaces the args on the generated spec
|
||||
func WithProcessArgs(args ...string) SpecOpts {
|
||||
return func(_ context.Context, _ Client, _ *containers.Container, s *Spec) error {
|
||||
setProcess(s)
|
||||
s.Process.Args = args
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// WithProcessCwd replaces the current working directory on the generated spec
|
||||
func WithProcessCwd(cwd string) SpecOpts {
|
||||
return func(_ context.Context, _ Client, _ *containers.Container, s *Spec) error {
|
||||
setProcess(s)
|
||||
s.Process.Cwd = cwd
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// WithTTY sets the information on the spec as well as the environment variables for
|
||||
// using a TTY
|
||||
func WithTTY(_ context.Context, _ Client, _ *containers.Container, s *Spec) error {
|
||||
setProcess(s)
|
||||
s.Process.Terminal = true
|
||||
if s.Linux != nil {
|
||||
s.Process.Env = append(s.Process.Env, "TERM=xterm")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// WithTTYSize sets the information on the spec as well as the environment variables for
|
||||
// using a TTY
|
||||
func WithTTYSize(width, height int) SpecOpts {
|
||||
return func(_ context.Context, _ Client, _ *containers.Container, s *Spec) error {
|
||||
setProcess(s)
|
||||
if s.Process.ConsoleSize == nil {
|
||||
s.Process.ConsoleSize = &specs.Box{}
|
||||
}
|
||||
s.Process.ConsoleSize.Width = uint(width)
|
||||
s.Process.ConsoleSize.Height = uint(height)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// WithHostname sets the container's hostname
|
||||
func WithHostname(name string) SpecOpts {
|
||||
return func(_ context.Context, _ Client, _ *containers.Container, s *Spec) error {
|
||||
s.Hostname = name
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// WithMounts appends mounts
|
||||
func WithMounts(mounts []specs.Mount) SpecOpts {
|
||||
return func(_ context.Context, _ Client, _ *containers.Container, s *Spec) error {
|
||||
s.Mounts = append(s.Mounts, mounts...)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// WithHostNamespace allows a task to run inside the host's linux namespace
|
||||
func WithHostNamespace(ns specs.LinuxNamespaceType) SpecOpts {
|
||||
return func(_ context.Context, _ Client, _ *containers.Container, s *Spec) error {
|
||||
setLinux(s)
|
||||
for i, n := range s.Linux.Namespaces {
|
||||
if n.Type == ns {
|
||||
s.Linux.Namespaces = append(s.Linux.Namespaces[:i], s.Linux.Namespaces[i+1:]...)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// WithLinuxNamespace uses the passed in namespace for the spec. If a namespace of the same type already exists in the
|
||||
// spec, the existing namespace is replaced by the one provided.
|
||||
func WithLinuxNamespace(ns specs.LinuxNamespace) SpecOpts {
|
||||
return func(_ context.Context, _ Client, _ *containers.Container, s *Spec) error {
|
||||
setLinux(s)
|
||||
for i, n := range s.Linux.Namespaces {
|
||||
if n.Type == ns.Type {
|
||||
before := s.Linux.Namespaces[:i]
|
||||
after := s.Linux.Namespaces[i+1:]
|
||||
s.Linux.Namespaces = append(before, ns)
|
||||
s.Linux.Namespaces = append(s.Linux.Namespaces, after...)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
s.Linux.Namespaces = append(s.Linux.Namespaces, ns)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// WithImageConfig configures the spec to from the configuration of an Image
|
||||
func WithImageConfig(image Image) SpecOpts {
|
||||
return WithImageConfigArgs(image, nil)
|
||||
}
|
||||
|
||||
// WithImageConfigArgs configures the spec to from the configuration of an Image with additional args that
|
||||
// replaces the CMD of the image
|
||||
func WithImageConfigArgs(image Image, args []string) SpecOpts {
|
||||
return func(ctx context.Context, client Client, c *containers.Container, s *Spec) error {
|
||||
ic, err := image.Config(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var (
|
||||
ociimage v1.Image
|
||||
config v1.ImageConfig
|
||||
)
|
||||
switch ic.MediaType {
|
||||
case v1.MediaTypeImageConfig, images.MediaTypeDockerSchema2Config:
|
||||
p, err := content.ReadBlob(ctx, image.ContentStore(), ic)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := json.Unmarshal(p, &ociimage); err != nil {
|
||||
return err
|
||||
}
|
||||
config = ociimage.Config
|
||||
default:
|
||||
return fmt.Errorf("unknown image config media type %s", ic.MediaType)
|
||||
}
|
||||
|
||||
setProcess(s)
|
||||
if s.Linux != nil {
|
||||
s.Process.Env = append(s.Process.Env, config.Env...)
|
||||
cmd := config.Cmd
|
||||
if len(args) > 0 {
|
||||
cmd = args
|
||||
}
|
||||
s.Process.Args = append(config.Entrypoint, cmd...)
|
||||
|
||||
cwd := config.WorkingDir
|
||||
if cwd == "" {
|
||||
cwd = "/"
|
||||
}
|
||||
s.Process.Cwd = cwd
|
||||
if config.User != "" {
|
||||
return WithUser(config.User)(ctx, client, c, s)
|
||||
}
|
||||
} else if s.Windows != nil {
|
||||
s.Process.Env = config.Env
|
||||
s.Process.Args = append(config.Entrypoint, config.Cmd...)
|
||||
s.Process.User = specs.User{
|
||||
Username: config.User,
|
||||
}
|
||||
} else {
|
||||
return errors.New("spec does not contain Linux or Windows section")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// WithRootFSPath specifies unmanaged rootfs path.
|
||||
func WithRootFSPath(path string) SpecOpts {
|
||||
return func(_ context.Context, _ Client, _ *containers.Container, s *Spec) error {
|
||||
setRoot(s)
|
||||
s.Root.Path = path
|
||||
// Entrypoint is not set here (it's up to caller)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// WithRootFSReadonly sets specs.Root.Readonly to true
|
||||
func WithRootFSReadonly() SpecOpts {
|
||||
return func(_ context.Context, _ Client, _ *containers.Container, s *Spec) error {
|
||||
setRoot(s)
|
||||
s.Root.Readonly = true
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// WithNoNewPrivileges sets no_new_privileges on the process for the container
|
||||
func WithNoNewPrivileges(_ context.Context, _ Client, _ *containers.Container, s *Spec) error {
|
||||
setProcess(s)
|
||||
s.Process.NoNewPrivileges = true
|
||||
return nil
|
||||
}
|
||||
|
||||
// WithHostHostsFile bind-mounts the host's /etc/hosts into the container as readonly
|
||||
func WithHostHostsFile(_ context.Context, _ Client, _ *containers.Container, s *Spec) error {
|
||||
s.Mounts = append(s.Mounts, specs.Mount{
|
||||
Destination: "/etc/hosts",
|
||||
Type: "bind",
|
||||
Source: "/etc/hosts",
|
||||
Options: []string{"rbind", "ro"},
|
||||
})
|
||||
return nil
|
||||
}
|
||||
|
||||
// WithHostResolvconf bind-mounts the host's /etc/resolv.conf into the container as readonly
|
||||
func WithHostResolvconf(_ context.Context, _ Client, _ *containers.Container, s *Spec) error {
|
||||
s.Mounts = append(s.Mounts, specs.Mount{
|
||||
Destination: "/etc/resolv.conf",
|
||||
Type: "bind",
|
||||
Source: "/etc/resolv.conf",
|
||||
Options: []string{"rbind", "ro"},
|
||||
})
|
||||
return nil
|
||||
}
|
||||
|
||||
// WithHostLocaltime bind-mounts the host's /etc/localtime into the container as readonly
|
||||
func WithHostLocaltime(_ context.Context, _ Client, _ *containers.Container, s *Spec) error {
|
||||
s.Mounts = append(s.Mounts, specs.Mount{
|
||||
Destination: "/etc/localtime",
|
||||
Type: "bind",
|
||||
Source: "/etc/localtime",
|
||||
Options: []string{"rbind", "ro"},
|
||||
})
|
||||
return nil
|
||||
}
|
||||
|
||||
// WithUserNamespace sets the uid and gid mappings for the task
|
||||
// this can be called multiple times to add more mappings to the generated spec
|
||||
func WithUserNamespace(container, host, size uint32) SpecOpts {
|
||||
return func(_ context.Context, _ Client, _ *containers.Container, s *Spec) error {
|
||||
var hasUserns bool
|
||||
setLinux(s)
|
||||
for _, ns := range s.Linux.Namespaces {
|
||||
if ns.Type == specs.UserNamespace {
|
||||
hasUserns = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !hasUserns {
|
||||
s.Linux.Namespaces = append(s.Linux.Namespaces, specs.LinuxNamespace{
|
||||
Type: specs.UserNamespace,
|
||||
})
|
||||
}
|
||||
mapping := specs.LinuxIDMapping{
|
||||
ContainerID: container,
|
||||
HostID: host,
|
||||
Size: size,
|
||||
}
|
||||
s.Linux.UIDMappings = append(s.Linux.UIDMappings, mapping)
|
||||
s.Linux.GIDMappings = append(s.Linux.GIDMappings, mapping)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// WithCgroup sets the container's cgroup path
|
||||
func WithCgroup(path string) SpecOpts {
|
||||
return func(_ context.Context, _ Client, _ *containers.Container, s *Spec) error {
|
||||
setLinux(s)
|
||||
s.Linux.CgroupsPath = path
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// WithNamespacedCgroup uses the namespace set on the context to create a
|
||||
// root directory for containers in the cgroup with the id as the subcgroup
|
||||
func WithNamespacedCgroup() SpecOpts {
|
||||
return func(ctx context.Context, _ Client, c *containers.Container, s *Spec) error {
|
||||
namespace, err := namespaces.NamespaceRequired(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
setLinux(s)
|
||||
s.Linux.CgroupsPath = filepath.Join("/", namespace, c.ID)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// WithUser sets the user to be used within the container.
|
||||
// It accepts a valid user string in OCI Image Spec v1.0.0:
|
||||
// user, uid, user:group, uid:gid, uid:group, user:gid
|
||||
func WithUser(userstr string) SpecOpts {
|
||||
return func(ctx context.Context, client Client, c *containers.Container, s *Spec) error {
|
||||
setProcess(s)
|
||||
parts := strings.Split(userstr, ":")
|
||||
switch len(parts) {
|
||||
case 1:
|
||||
v, err := strconv.Atoi(parts[0])
|
||||
if err != nil {
|
||||
// if we cannot parse as a uint they try to see if it is a username
|
||||
return WithUsername(userstr)(ctx, client, c, s)
|
||||
}
|
||||
return WithUserID(uint32(v))(ctx, client, c, s)
|
||||
case 2:
|
||||
var (
|
||||
username string
|
||||
groupname string
|
||||
)
|
||||
var uid, gid uint32
|
||||
v, err := strconv.Atoi(parts[0])
|
||||
if err != nil {
|
||||
username = parts[0]
|
||||
} else {
|
||||
uid = uint32(v)
|
||||
}
|
||||
if v, err = strconv.Atoi(parts[1]); err != nil {
|
||||
groupname = parts[1]
|
||||
} else {
|
||||
gid = uint32(v)
|
||||
}
|
||||
if username == "" && groupname == "" {
|
||||
s.Process.User.UID, s.Process.User.GID = uid, gid
|
||||
return nil
|
||||
}
|
||||
f := func(root string) error {
|
||||
if username != "" {
|
||||
uid, _, err = getUIDGIDFromPath(root, func(u user.User) bool {
|
||||
return u.Name == username
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if groupname != "" {
|
||||
gid, err = getGIDFromPath(root, func(g user.Group) bool {
|
||||
return g.Name == groupname
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
s.Process.User.UID, s.Process.User.GID = uid, gid
|
||||
return nil
|
||||
}
|
||||
if c.Snapshotter == "" && c.SnapshotKey == "" {
|
||||
if !isRootfsAbs(s.Root.Path) {
|
||||
return errors.New("rootfs absolute path is required")
|
||||
}
|
||||
return f(s.Root.Path)
|
||||
}
|
||||
if c.Snapshotter == "" {
|
||||
return errors.New("no snapshotter set for container")
|
||||
}
|
||||
if c.SnapshotKey == "" {
|
||||
return errors.New("rootfs snapshot not created for container")
|
||||
}
|
||||
snapshotter := client.SnapshotService(c.Snapshotter)
|
||||
mounts, err := snapshotter.Mounts(ctx, c.SnapshotKey)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return mount.WithTempMount(ctx, mounts, f)
|
||||
default:
|
||||
return fmt.Errorf("invalid USER value %s", userstr)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// WithUIDGID allows the UID and GID for the Process to be set
|
||||
func WithUIDGID(uid, gid uint32) SpecOpts {
|
||||
return func(_ context.Context, _ Client, _ *containers.Container, s *Spec) error {
|
||||
setProcess(s)
|
||||
s.Process.User.UID = uid
|
||||
s.Process.User.GID = gid
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// WithUserID sets the correct UID and GID for the container based
|
||||
// on the image's /etc/passwd contents. If /etc/passwd does not exist,
|
||||
// or uid is not found in /etc/passwd, it sets the requested uid,
|
||||
// additionally sets the gid to 0, and does not return an error.
|
||||
func WithUserID(uid uint32) SpecOpts {
|
||||
return func(ctx context.Context, client Client, c *containers.Container, s *Spec) (err error) {
|
||||
setProcess(s)
|
||||
if c.Snapshotter == "" && c.SnapshotKey == "" {
|
||||
if !isRootfsAbs(s.Root.Path) {
|
||||
return errors.Errorf("rootfs absolute path is required")
|
||||
}
|
||||
uuid, ugid, err := getUIDGIDFromPath(s.Root.Path, func(u user.User) bool {
|
||||
return u.Uid == int(uid)
|
||||
})
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) || err == errNoUsersFound {
|
||||
s.Process.User.UID, s.Process.User.GID = uid, 0
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
}
|
||||
s.Process.User.UID, s.Process.User.GID = uuid, ugid
|
||||
return nil
|
||||
|
||||
}
|
||||
if c.Snapshotter == "" {
|
||||
return errors.Errorf("no snapshotter set for container")
|
||||
}
|
||||
if c.SnapshotKey == "" {
|
||||
return errors.Errorf("rootfs snapshot not created for container")
|
||||
}
|
||||
snapshotter := client.SnapshotService(c.Snapshotter)
|
||||
mounts, err := snapshotter.Mounts(ctx, c.SnapshotKey)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return mount.WithTempMount(ctx, mounts, func(root string) error {
|
||||
uuid, ugid, err := getUIDGIDFromPath(root, func(u user.User) bool {
|
||||
return u.Uid == int(uid)
|
||||
})
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) || err == errNoUsersFound {
|
||||
s.Process.User.UID, s.Process.User.GID = uid, 0
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
}
|
||||
s.Process.User.UID, s.Process.User.GID = uuid, ugid
|
||||
return nil
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// WithUsername sets the correct UID and GID for the container
|
||||
// based on the the image's /etc/passwd contents. If /etc/passwd
|
||||
// does not exist, or the username is not found in /etc/passwd,
|
||||
// it returns error.
|
||||
func WithUsername(username string) SpecOpts {
|
||||
return func(ctx context.Context, client Client, c *containers.Container, s *Spec) (err error) {
|
||||
setProcess(s)
|
||||
if s.Linux != nil {
|
||||
if c.Snapshotter == "" && c.SnapshotKey == "" {
|
||||
if !isRootfsAbs(s.Root.Path) {
|
||||
return errors.Errorf("rootfs absolute path is required")
|
||||
}
|
||||
uid, gid, err := getUIDGIDFromPath(s.Root.Path, func(u user.User) bool {
|
||||
return u.Name == username
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
s.Process.User.UID, s.Process.User.GID = uid, gid
|
||||
return nil
|
||||
}
|
||||
if c.Snapshotter == "" {
|
||||
return errors.Errorf("no snapshotter set for container")
|
||||
}
|
||||
if c.SnapshotKey == "" {
|
||||
return errors.Errorf("rootfs snapshot not created for container")
|
||||
}
|
||||
snapshotter := client.SnapshotService(c.Snapshotter)
|
||||
mounts, err := snapshotter.Mounts(ctx, c.SnapshotKey)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return mount.WithTempMount(ctx, mounts, func(root string) error {
|
||||
uid, gid, err := getUIDGIDFromPath(root, func(u user.User) bool {
|
||||
return u.Name == username
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
s.Process.User.UID, s.Process.User.GID = uid, gid
|
||||
return nil
|
||||
})
|
||||
} else if s.Windows != nil {
|
||||
s.Process.User.Username = username
|
||||
} else {
|
||||
return errors.New("spec does not contain Linux or Windows section")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// WithCapabilities sets Linux capabilities on the process
|
||||
func WithCapabilities(caps []string) SpecOpts {
|
||||
return func(_ context.Context, _ Client, _ *containers.Container, s *Spec) error {
|
||||
setCapabilities(s)
|
||||
|
||||
s.Process.Capabilities.Bounding = caps
|
||||
s.Process.Capabilities.Effective = caps
|
||||
s.Process.Capabilities.Permitted = caps
|
||||
s.Process.Capabilities.Inheritable = caps
|
||||
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// WithAllCapabilities sets all linux capabilities for the process
|
||||
var WithAllCapabilities = WithCapabilities(getAllCapabilities())
|
||||
|
||||
func getAllCapabilities() []string {
|
||||
last := capability.CAP_LAST_CAP
|
||||
// hack for RHEL6 which has no /proc/sys/kernel/cap_last_cap
|
||||
if last == capability.Cap(63) {
|
||||
last = capability.CAP_BLOCK_SUSPEND
|
||||
}
|
||||
var caps []string
|
||||
for _, cap := range capability.List() {
|
||||
if cap > last {
|
||||
continue
|
||||
}
|
||||
caps = append(caps, "CAP_"+strings.ToUpper(cap.String()))
|
||||
}
|
||||
return caps
|
||||
}
|
||||
|
||||
// WithAmbientCapabilities set the Linux ambient capabilities for the process
|
||||
// Ambient capabilities should only be set for non-root users or the caller should
|
||||
// understand how these capabilities are used and set
|
||||
func WithAmbientCapabilities(caps []string) SpecOpts {
|
||||
return func(_ context.Context, _ Client, _ *containers.Container, s *Spec) error {
|
||||
setCapabilities(s)
|
||||
|
||||
s.Process.Capabilities.Ambient = caps
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
var errNoUsersFound = errors.New("no users found")
|
||||
|
||||
func getUIDGIDFromPath(root string, filter func(user.User) bool) (uid, gid uint32, err error) {
|
||||
ppath, err := fs.RootPath(root, "/etc/passwd")
|
||||
if err != nil {
|
||||
return 0, 0, err
|
||||
}
|
||||
users, err := user.ParsePasswdFileFilter(ppath, filter)
|
||||
if err != nil {
|
||||
return 0, 0, err
|
||||
}
|
||||
if len(users) == 0 {
|
||||
return 0, 0, errNoUsersFound
|
||||
}
|
||||
u := users[0]
|
||||
return uint32(u.Uid), uint32(u.Gid), nil
|
||||
}
|
||||
|
||||
var errNoGroupsFound = errors.New("no groups found")
|
||||
|
||||
func getGIDFromPath(root string, filter func(user.Group) bool) (gid uint32, err error) {
|
||||
gpath, err := fs.RootPath(root, "/etc/group")
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
groups, err := user.ParseGroupFileFilter(gpath, filter)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if len(groups) == 0 {
|
||||
return 0, errNoGroupsFound
|
||||
}
|
||||
g := groups[0]
|
||||
return uint32(g.Gid), nil
|
||||
}
|
||||
|
||||
func isRootfsAbs(root string) bool {
|
||||
return filepath.IsAbs(root)
|
||||
}
|
||||
|
||||
// WithMaskedPaths sets the masked paths option
|
||||
func WithMaskedPaths(paths []string) SpecOpts {
|
||||
return func(_ context.Context, _ Client, _ *containers.Container, s *Spec) error {
|
||||
setLinux(s)
|
||||
s.Linux.MaskedPaths = paths
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// WithReadonlyPaths sets the read only paths option
|
||||
func WithReadonlyPaths(paths []string) SpecOpts {
|
||||
return func(_ context.Context, _ Client, _ *containers.Container, s *Spec) error {
|
||||
setLinux(s)
|
||||
s.Linux.ReadonlyPaths = paths
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// WithWriteableSysfs makes any sysfs mounts writeable
|
||||
func WithWriteableSysfs(_ context.Context, _ Client, _ *containers.Container, s *Spec) error {
|
||||
for i, m := range s.Mounts {
|
||||
if m.Type == "sysfs" {
|
||||
var options []string
|
||||
for _, o := range m.Options {
|
||||
if o == "ro" {
|
||||
o = "rw"
|
||||
}
|
||||
options = append(options, o)
|
||||
}
|
||||
s.Mounts[i].Options = options
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// WithWriteableCgroupfs makes any cgroup mounts writeable
|
||||
func WithWriteableCgroupfs(_ context.Context, _ Client, _ *containers.Container, s *Spec) error {
|
||||
for i, m := range s.Mounts {
|
||||
if m.Type == "cgroup" {
|
||||
var options []string
|
||||
for _, o := range m.Options {
|
||||
if o == "ro" {
|
||||
o = "rw"
|
||||
}
|
||||
options = append(options, o)
|
||||
}
|
||||
s.Mounts[i].Options = options
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// WithSelinuxLabel sets the process SELinux label
|
||||
func WithSelinuxLabel(label string) SpecOpts {
|
||||
return func(_ context.Context, _ Client, _ *containers.Container, s *Spec) error {
|
||||
setProcess(s)
|
||||
s.Process.SelinuxLabel = label
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// WithApparmorProfile sets the Apparmor profile for the process
|
||||
func WithApparmorProfile(profile string) SpecOpts {
|
||||
return func(_ context.Context, _ Client, _ *containers.Container, s *Spec) error {
|
||||
setProcess(s)
|
||||
s.Process.ApparmorProfile = profile
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// WithSeccompUnconfined clears the seccomp profile
|
||||
func WithSeccompUnconfined(_ context.Context, _ Client, _ *containers.Container, s *Spec) error {
|
||||
setLinux(s)
|
||||
s.Linux.Seccomp = nil
|
||||
return nil
|
||||
}
|
||||
|
||||
// WithParentCgroupDevices uses the default cgroup setup to inherit the container's parent cgroup's
|
||||
// allowed and denied devices
|
||||
func WithParentCgroupDevices(_ context.Context, _ Client, _ *containers.Container, s *Spec) error {
|
||||
setLinux(s)
|
||||
if s.Linux.Resources == nil {
|
||||
s.Linux.Resources = &specs.LinuxResources{}
|
||||
}
|
||||
s.Linux.Resources.Devices = nil
|
||||
return nil
|
||||
}
|
||||
|
||||
// WithDefaultUnixDevices adds the default devices for unix such as /dev/null, /dev/random to
|
||||
// the container's resource cgroup spec
|
||||
func WithDefaultUnixDevices(_ context.Context, _ Client, _ *containers.Container, s *Spec) error {
|
||||
setLinux(s)
|
||||
if s.Linux.Resources == nil {
|
||||
s.Linux.Resources = &specs.LinuxResources{}
|
||||
}
|
||||
intptr := func(i int64) *int64 {
|
||||
return &i
|
||||
}
|
||||
s.Linux.Resources.Devices = append(s.Linux.Resources.Devices, []specs.LinuxDeviceCgroup{
|
||||
{
|
||||
// "/dev/null",
|
||||
Type: "c",
|
||||
Major: intptr(1),
|
||||
Minor: intptr(3),
|
||||
Access: rwm,
|
||||
Allow: true,
|
||||
},
|
||||
{
|
||||
// "/dev/random",
|
||||
Type: "c",
|
||||
Major: intptr(1),
|
||||
Minor: intptr(8),
|
||||
Access: rwm,
|
||||
Allow: true,
|
||||
},
|
||||
{
|
||||
// "/dev/full",
|
||||
Type: "c",
|
||||
Major: intptr(1),
|
||||
Minor: intptr(7),
|
||||
Access: rwm,
|
||||
Allow: true,
|
||||
},
|
||||
{
|
||||
// "/dev/tty",
|
||||
Type: "c",
|
||||
Major: intptr(5),
|
||||
Minor: intptr(0),
|
||||
Access: rwm,
|
||||
Allow: true,
|
||||
},
|
||||
{
|
||||
// "/dev/zero",
|
||||
Type: "c",
|
||||
Major: intptr(1),
|
||||
Minor: intptr(5),
|
||||
Access: rwm,
|
||||
Allow: true,
|
||||
},
|
||||
{
|
||||
// "/dev/urandom",
|
||||
Type: "c",
|
||||
Major: intptr(1),
|
||||
Minor: intptr(9),
|
||||
Access: rwm,
|
||||
Allow: true,
|
||||
},
|
||||
{
|
||||
// "/dev/console",
|
||||
Type: "c",
|
||||
Major: intptr(5),
|
||||
Minor: intptr(1),
|
||||
Access: rwm,
|
||||
Allow: true,
|
||||
},
|
||||
// /dev/pts/ - pts namespaces are "coming soon"
|
||||
{
|
||||
Type: "c",
|
||||
Major: intptr(136),
|
||||
Access: rwm,
|
||||
Allow: true,
|
||||
},
|
||||
{
|
||||
Type: "c",
|
||||
Major: intptr(5),
|
||||
Minor: intptr(2),
|
||||
Access: rwm,
|
||||
Allow: true,
|
||||
},
|
||||
{
|
||||
// tuntap
|
||||
Type: "c",
|
||||
Major: intptr(10),
|
||||
Minor: intptr(200),
|
||||
Access: rwm,
|
||||
Allow: true,
|
||||
},
|
||||
}...)
|
||||
return nil
|
||||
}
|
||||
|
||||
// WithPrivileged sets up options for a privileged container
|
||||
// TODO(justincormack) device handling
|
||||
var WithPrivileged = Compose(
|
||||
WithAllCapabilities,
|
||||
WithMaskedPaths(nil),
|
||||
WithReadonlyPaths(nil),
|
||||
WithWriteableSysfs,
|
||||
WithWriteableCgroupfs,
|
||||
WithSelinuxLabel(""),
|
||||
WithApparmorProfile(""),
|
||||
WithSeccompUnconfined,
|
||||
)
|
||||
|
|
|
@ -1,733 +0,0 @@
|
|||
// +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 oci
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/containerd/containerd/containers"
|
||||
"github.com/containerd/containerd/content"
|
||||
"github.com/containerd/containerd/images"
|
||||
"github.com/containerd/containerd/mount"
|
||||
"github.com/containerd/containerd/namespaces"
|
||||
"github.com/containerd/continuity/fs"
|
||||
"github.com/opencontainers/image-spec/specs-go/v1"
|
||||
"github.com/opencontainers/runc/libcontainer/user"
|
||||
specs "github.com/opencontainers/runtime-spec/specs-go"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/syndtr/gocapability/capability"
|
||||
)
|
||||
|
||||
// WithTTY sets the information on the spec as well as the environment variables for
|
||||
// using a TTY
|
||||
func WithTTY(_ context.Context, _ Client, _ *containers.Container, s *Spec) error {
|
||||
setProcess(s)
|
||||
s.Process.Terminal = true
|
||||
s.Process.Env = append(s.Process.Env, "TERM=xterm")
|
||||
return nil
|
||||
}
|
||||
|
||||
// setRoot sets Root to empty if unset
|
||||
func setRoot(s *Spec) {
|
||||
if s.Root == nil {
|
||||
s.Root = &specs.Root{}
|
||||
}
|
||||
}
|
||||
|
||||
// setLinux sets Linux to empty if unset
|
||||
func setLinux(s *Spec) {
|
||||
if s.Linux == nil {
|
||||
s.Linux = &specs.Linux{}
|
||||
}
|
||||
}
|
||||
|
||||
// setCapabilities sets Linux Capabilities to empty if unset
|
||||
func setCapabilities(s *Spec) {
|
||||
setProcess(s)
|
||||
if s.Process.Capabilities == nil {
|
||||
s.Process.Capabilities = &specs.LinuxCapabilities{}
|
||||
}
|
||||
}
|
||||
|
||||
// WithHostNamespace allows a task to run inside the host's linux namespace
|
||||
func WithHostNamespace(ns specs.LinuxNamespaceType) SpecOpts {
|
||||
return func(_ context.Context, _ Client, _ *containers.Container, s *Spec) error {
|
||||
setLinux(s)
|
||||
for i, n := range s.Linux.Namespaces {
|
||||
if n.Type == ns {
|
||||
s.Linux.Namespaces = append(s.Linux.Namespaces[:i], s.Linux.Namespaces[i+1:]...)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// WithLinuxNamespace uses the passed in namespace for the spec. If a namespace of the same type already exists in the
|
||||
// spec, the existing namespace is replaced by the one provided.
|
||||
func WithLinuxNamespace(ns specs.LinuxNamespace) SpecOpts {
|
||||
return func(_ context.Context, _ Client, _ *containers.Container, s *Spec) error {
|
||||
setLinux(s)
|
||||
for i, n := range s.Linux.Namespaces {
|
||||
if n.Type == ns.Type {
|
||||
before := s.Linux.Namespaces[:i]
|
||||
after := s.Linux.Namespaces[i+1:]
|
||||
s.Linux.Namespaces = append(before, ns)
|
||||
s.Linux.Namespaces = append(s.Linux.Namespaces, after...)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
s.Linux.Namespaces = append(s.Linux.Namespaces, ns)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// WithImageConfig configures the spec to from the configuration of an Image
|
||||
func WithImageConfig(image Image) SpecOpts {
|
||||
return WithImageConfigArgs(image, nil)
|
||||
}
|
||||
|
||||
// WithImageConfigArgs configures the spec to from the configuration of an Image with additional args that
|
||||
// replaces the CMD of the image
|
||||
func WithImageConfigArgs(image Image, args []string) SpecOpts {
|
||||
return func(ctx context.Context, client Client, c *containers.Container, s *Spec) error {
|
||||
ic, err := image.Config(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var (
|
||||
ociimage v1.Image
|
||||
config v1.ImageConfig
|
||||
)
|
||||
switch ic.MediaType {
|
||||
case v1.MediaTypeImageConfig, images.MediaTypeDockerSchema2Config:
|
||||
p, err := content.ReadBlob(ctx, image.ContentStore(), ic)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := json.Unmarshal(p, &ociimage); err != nil {
|
||||
return err
|
||||
}
|
||||
config = ociimage.Config
|
||||
default:
|
||||
return fmt.Errorf("unknown image config media type %s", ic.MediaType)
|
||||
}
|
||||
|
||||
setProcess(s)
|
||||
s.Process.Env = append(s.Process.Env, config.Env...)
|
||||
cmd := config.Cmd
|
||||
if len(args) > 0 {
|
||||
cmd = args
|
||||
}
|
||||
s.Process.Args = append(config.Entrypoint, cmd...)
|
||||
|
||||
cwd := config.WorkingDir
|
||||
if cwd == "" {
|
||||
cwd = "/"
|
||||
}
|
||||
s.Process.Cwd = cwd
|
||||
if config.User != "" {
|
||||
return WithUser(config.User)(ctx, client, c, s)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// WithRootFSPath specifies unmanaged rootfs path.
|
||||
func WithRootFSPath(path string) SpecOpts {
|
||||
return func(_ context.Context, _ Client, _ *containers.Container, s *Spec) error {
|
||||
setRoot(s)
|
||||
s.Root.Path = path
|
||||
// Entrypoint is not set here (it's up to caller)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// WithRootFSReadonly sets specs.Root.Readonly to true
|
||||
func WithRootFSReadonly() SpecOpts {
|
||||
return func(_ context.Context, _ Client, _ *containers.Container, s *Spec) error {
|
||||
setRoot(s)
|
||||
s.Root.Readonly = true
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// WithNoNewPrivileges sets no_new_privileges on the process for the container
|
||||
func WithNoNewPrivileges(_ context.Context, _ Client, _ *containers.Container, s *Spec) error {
|
||||
setProcess(s)
|
||||
s.Process.NoNewPrivileges = true
|
||||
return nil
|
||||
}
|
||||
|
||||
// WithHostHostsFile bind-mounts the host's /etc/hosts into the container as readonly
|
||||
func WithHostHostsFile(_ context.Context, _ Client, _ *containers.Container, s *Spec) error {
|
||||
s.Mounts = append(s.Mounts, specs.Mount{
|
||||
Destination: "/etc/hosts",
|
||||
Type: "bind",
|
||||
Source: "/etc/hosts",
|
||||
Options: []string{"rbind", "ro"},
|
||||
})
|
||||
return nil
|
||||
}
|
||||
|
||||
// WithHostResolvconf bind-mounts the host's /etc/resolv.conf into the container as readonly
|
||||
func WithHostResolvconf(_ context.Context, _ Client, _ *containers.Container, s *Spec) error {
|
||||
s.Mounts = append(s.Mounts, specs.Mount{
|
||||
Destination: "/etc/resolv.conf",
|
||||
Type: "bind",
|
||||
Source: "/etc/resolv.conf",
|
||||
Options: []string{"rbind", "ro"},
|
||||
})
|
||||
return nil
|
||||
}
|
||||
|
||||
// WithHostLocaltime bind-mounts the host's /etc/localtime into the container as readonly
|
||||
func WithHostLocaltime(_ context.Context, _ Client, _ *containers.Container, s *Spec) error {
|
||||
s.Mounts = append(s.Mounts, specs.Mount{
|
||||
Destination: "/etc/localtime",
|
||||
Type: "bind",
|
||||
Source: "/etc/localtime",
|
||||
Options: []string{"rbind", "ro"},
|
||||
})
|
||||
return nil
|
||||
}
|
||||
|
||||
// WithUserNamespace sets the uid and gid mappings for the task
|
||||
// this can be called multiple times to add more mappings to the generated spec
|
||||
func WithUserNamespace(container, host, size uint32) SpecOpts {
|
||||
return func(_ context.Context, _ Client, _ *containers.Container, s *Spec) error {
|
||||
var hasUserns bool
|
||||
setLinux(s)
|
||||
for _, ns := range s.Linux.Namespaces {
|
||||
if ns.Type == specs.UserNamespace {
|
||||
hasUserns = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !hasUserns {
|
||||
s.Linux.Namespaces = append(s.Linux.Namespaces, specs.LinuxNamespace{
|
||||
Type: specs.UserNamespace,
|
||||
})
|
||||
}
|
||||
mapping := specs.LinuxIDMapping{
|
||||
ContainerID: container,
|
||||
HostID: host,
|
||||
Size: size,
|
||||
}
|
||||
s.Linux.UIDMappings = append(s.Linux.UIDMappings, mapping)
|
||||
s.Linux.GIDMappings = append(s.Linux.GIDMappings, mapping)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// WithCgroup sets the container's cgroup path
|
||||
func WithCgroup(path string) SpecOpts {
|
||||
return func(_ context.Context, _ Client, _ *containers.Container, s *Spec) error {
|
||||
setLinux(s)
|
||||
s.Linux.CgroupsPath = path
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// WithNamespacedCgroup uses the namespace set on the context to create a
|
||||
// root directory for containers in the cgroup with the id as the subcgroup
|
||||
func WithNamespacedCgroup() SpecOpts {
|
||||
return func(ctx context.Context, _ Client, c *containers.Container, s *Spec) error {
|
||||
namespace, err := namespaces.NamespaceRequired(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
setLinux(s)
|
||||
s.Linux.CgroupsPath = filepath.Join("/", namespace, c.ID)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// WithUser sets the user to be used within the container.
|
||||
// It accepts a valid user string in OCI Image Spec v1.0.0:
|
||||
// user, uid, user:group, uid:gid, uid:group, user:gid
|
||||
func WithUser(userstr string) SpecOpts {
|
||||
return func(ctx context.Context, client Client, c *containers.Container, s *Spec) error {
|
||||
setProcess(s)
|
||||
parts := strings.Split(userstr, ":")
|
||||
switch len(parts) {
|
||||
case 1:
|
||||
v, err := strconv.Atoi(parts[0])
|
||||
if err != nil {
|
||||
// if we cannot parse as a uint they try to see if it is a username
|
||||
return WithUsername(userstr)(ctx, client, c, s)
|
||||
}
|
||||
return WithUserID(uint32(v))(ctx, client, c, s)
|
||||
case 2:
|
||||
var (
|
||||
username string
|
||||
groupname string
|
||||
)
|
||||
var uid, gid uint32
|
||||
v, err := strconv.Atoi(parts[0])
|
||||
if err != nil {
|
||||
username = parts[0]
|
||||
} else {
|
||||
uid = uint32(v)
|
||||
}
|
||||
if v, err = strconv.Atoi(parts[1]); err != nil {
|
||||
groupname = parts[1]
|
||||
} else {
|
||||
gid = uint32(v)
|
||||
}
|
||||
if username == "" && groupname == "" {
|
||||
s.Process.User.UID, s.Process.User.GID = uid, gid
|
||||
return nil
|
||||
}
|
||||
f := func(root string) error {
|
||||
if username != "" {
|
||||
uid, _, err = getUIDGIDFromPath(root, func(u user.User) bool {
|
||||
return u.Name == username
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if groupname != "" {
|
||||
gid, err = getGIDFromPath(root, func(g user.Group) bool {
|
||||
return g.Name == groupname
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
s.Process.User.UID, s.Process.User.GID = uid, gid
|
||||
return nil
|
||||
}
|
||||
if c.Snapshotter == "" && c.SnapshotKey == "" {
|
||||
if !isRootfsAbs(s.Root.Path) {
|
||||
return errors.New("rootfs absolute path is required")
|
||||
}
|
||||
return f(s.Root.Path)
|
||||
}
|
||||
if c.Snapshotter == "" {
|
||||
return errors.New("no snapshotter set for container")
|
||||
}
|
||||
if c.SnapshotKey == "" {
|
||||
return errors.New("rootfs snapshot not created for container")
|
||||
}
|
||||
snapshotter := client.SnapshotService(c.Snapshotter)
|
||||
mounts, err := snapshotter.Mounts(ctx, c.SnapshotKey)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return mount.WithTempMount(ctx, mounts, f)
|
||||
default:
|
||||
return fmt.Errorf("invalid USER value %s", userstr)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// WithUIDGID allows the UID and GID for the Process to be set
|
||||
func WithUIDGID(uid, gid uint32) SpecOpts {
|
||||
return func(_ context.Context, _ Client, _ *containers.Container, s *Spec) error {
|
||||
setProcess(s)
|
||||
s.Process.User.UID = uid
|
||||
s.Process.User.GID = gid
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// WithUserID sets the correct UID and GID for the container based
|
||||
// on the image's /etc/passwd contents. If /etc/passwd does not exist,
|
||||
// or uid is not found in /etc/passwd, it sets the requested uid,
|
||||
// additionally sets the gid to 0, and does not return an error.
|
||||
func WithUserID(uid uint32) SpecOpts {
|
||||
return func(ctx context.Context, client Client, c *containers.Container, s *Spec) (err error) {
|
||||
setProcess(s)
|
||||
if c.Snapshotter == "" && c.SnapshotKey == "" {
|
||||
if !isRootfsAbs(s.Root.Path) {
|
||||
return errors.Errorf("rootfs absolute path is required")
|
||||
}
|
||||
uuid, ugid, err := getUIDGIDFromPath(s.Root.Path, func(u user.User) bool {
|
||||
return u.Uid == int(uid)
|
||||
})
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) || err == errNoUsersFound {
|
||||
s.Process.User.UID, s.Process.User.GID = uid, 0
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
}
|
||||
s.Process.User.UID, s.Process.User.GID = uuid, ugid
|
||||
return nil
|
||||
|
||||
}
|
||||
if c.Snapshotter == "" {
|
||||
return errors.Errorf("no snapshotter set for container")
|
||||
}
|
||||
if c.SnapshotKey == "" {
|
||||
return errors.Errorf("rootfs snapshot not created for container")
|
||||
}
|
||||
snapshotter := client.SnapshotService(c.Snapshotter)
|
||||
mounts, err := snapshotter.Mounts(ctx, c.SnapshotKey)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return mount.WithTempMount(ctx, mounts, func(root string) error {
|
||||
uuid, ugid, err := getUIDGIDFromPath(root, func(u user.User) bool {
|
||||
return u.Uid == int(uid)
|
||||
})
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) || err == errNoUsersFound {
|
||||
s.Process.User.UID, s.Process.User.GID = uid, 0
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
}
|
||||
s.Process.User.UID, s.Process.User.GID = uuid, ugid
|
||||
return nil
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// WithUsername sets the correct UID and GID for the container
|
||||
// based on the the image's /etc/passwd contents. If /etc/passwd
|
||||
// does not exist, or the username is not found in /etc/passwd,
|
||||
// it returns error.
|
||||
func WithUsername(username string) SpecOpts {
|
||||
return func(ctx context.Context, client Client, c *containers.Container, s *Spec) (err error) {
|
||||
setProcess(s)
|
||||
if c.Snapshotter == "" && c.SnapshotKey == "" {
|
||||
if !isRootfsAbs(s.Root.Path) {
|
||||
return errors.Errorf("rootfs absolute path is required")
|
||||
}
|
||||
uid, gid, err := getUIDGIDFromPath(s.Root.Path, func(u user.User) bool {
|
||||
return u.Name == username
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
s.Process.User.UID, s.Process.User.GID = uid, gid
|
||||
return nil
|
||||
}
|
||||
if c.Snapshotter == "" {
|
||||
return errors.Errorf("no snapshotter set for container")
|
||||
}
|
||||
if c.SnapshotKey == "" {
|
||||
return errors.Errorf("rootfs snapshot not created for container")
|
||||
}
|
||||
snapshotter := client.SnapshotService(c.Snapshotter)
|
||||
mounts, err := snapshotter.Mounts(ctx, c.SnapshotKey)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return mount.WithTempMount(ctx, mounts, func(root string) error {
|
||||
uid, gid, err := getUIDGIDFromPath(root, func(u user.User) bool {
|
||||
return u.Name == username
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
s.Process.User.UID, s.Process.User.GID = uid, gid
|
||||
return nil
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// WithCapabilities sets Linux capabilities on the process
|
||||
func WithCapabilities(caps []string) SpecOpts {
|
||||
return func(_ context.Context, _ Client, _ *containers.Container, s *Spec) error {
|
||||
setCapabilities(s)
|
||||
|
||||
s.Process.Capabilities.Bounding = caps
|
||||
s.Process.Capabilities.Effective = caps
|
||||
s.Process.Capabilities.Permitted = caps
|
||||
s.Process.Capabilities.Inheritable = caps
|
||||
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// WithAllCapabilities sets all linux capabilities for the process
|
||||
var WithAllCapabilities = WithCapabilities(getAllCapabilities())
|
||||
|
||||
func getAllCapabilities() []string {
|
||||
last := capability.CAP_LAST_CAP
|
||||
// hack for RHEL6 which has no /proc/sys/kernel/cap_last_cap
|
||||
if last == capability.Cap(63) {
|
||||
last = capability.CAP_BLOCK_SUSPEND
|
||||
}
|
||||
var caps []string
|
||||
for _, cap := range capability.List() {
|
||||
if cap > last {
|
||||
continue
|
||||
}
|
||||
caps = append(caps, "CAP_"+strings.ToUpper(cap.String()))
|
||||
}
|
||||
return caps
|
||||
}
|
||||
|
||||
// WithAmbientCapabilities set the Linux ambient capabilities for the process
|
||||
// Ambient capabilities should only be set for non-root users or the caller should
|
||||
// understand how these capabilities are used and set
|
||||
func WithAmbientCapabilities(caps []string) SpecOpts {
|
||||
return func(_ context.Context, _ Client, _ *containers.Container, s *Spec) error {
|
||||
setCapabilities(s)
|
||||
|
||||
s.Process.Capabilities.Ambient = caps
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
var errNoUsersFound = errors.New("no users found")
|
||||
|
||||
func getUIDGIDFromPath(root string, filter func(user.User) bool) (uid, gid uint32, err error) {
|
||||
ppath, err := fs.RootPath(root, "/etc/passwd")
|
||||
if err != nil {
|
||||
return 0, 0, err
|
||||
}
|
||||
users, err := user.ParsePasswdFileFilter(ppath, filter)
|
||||
if err != nil {
|
||||
return 0, 0, err
|
||||
}
|
||||
if len(users) == 0 {
|
||||
return 0, 0, errNoUsersFound
|
||||
}
|
||||
u := users[0]
|
||||
return uint32(u.Uid), uint32(u.Gid), nil
|
||||
}
|
||||
|
||||
var errNoGroupsFound = errors.New("no groups found")
|
||||
|
||||
func getGIDFromPath(root string, filter func(user.Group) bool) (gid uint32, err error) {
|
||||
gpath, err := fs.RootPath(root, "/etc/group")
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
groups, err := user.ParseGroupFileFilter(gpath, filter)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if len(groups) == 0 {
|
||||
return 0, errNoGroupsFound
|
||||
}
|
||||
g := groups[0]
|
||||
return uint32(g.Gid), nil
|
||||
}
|
||||
|
||||
func isRootfsAbs(root string) bool {
|
||||
return filepath.IsAbs(root)
|
||||
}
|
||||
|
||||
// WithMaskedPaths sets the masked paths option
|
||||
func WithMaskedPaths(paths []string) SpecOpts {
|
||||
return func(_ context.Context, _ Client, _ *containers.Container, s *Spec) error {
|
||||
setLinux(s)
|
||||
s.Linux.MaskedPaths = paths
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// WithReadonlyPaths sets the read only paths option
|
||||
func WithReadonlyPaths(paths []string) SpecOpts {
|
||||
return func(_ context.Context, _ Client, _ *containers.Container, s *Spec) error {
|
||||
setLinux(s)
|
||||
s.Linux.ReadonlyPaths = paths
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// WithWriteableSysfs makes any sysfs mounts writeable
|
||||
func WithWriteableSysfs(_ context.Context, _ Client, _ *containers.Container, s *Spec) error {
|
||||
for i, m := range s.Mounts {
|
||||
if m.Type == "sysfs" {
|
||||
var options []string
|
||||
for _, o := range m.Options {
|
||||
if o == "ro" {
|
||||
o = "rw"
|
||||
}
|
||||
options = append(options, o)
|
||||
}
|
||||
s.Mounts[i].Options = options
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// WithWriteableCgroupfs makes any cgroup mounts writeable
|
||||
func WithWriteableCgroupfs(_ context.Context, _ Client, _ *containers.Container, s *Spec) error {
|
||||
for i, m := range s.Mounts {
|
||||
if m.Type == "cgroup" {
|
||||
var options []string
|
||||
for _, o := range m.Options {
|
||||
if o == "ro" {
|
||||
o = "rw"
|
||||
}
|
||||
options = append(options, o)
|
||||
}
|
||||
s.Mounts[i].Options = options
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// WithSelinuxLabel sets the process SELinux label
|
||||
func WithSelinuxLabel(label string) SpecOpts {
|
||||
return func(_ context.Context, _ Client, _ *containers.Container, s *Spec) error {
|
||||
setProcess(s)
|
||||
s.Process.SelinuxLabel = label
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// WithApparmorProfile sets the Apparmor profile for the process
|
||||
func WithApparmorProfile(profile string) SpecOpts {
|
||||
return func(_ context.Context, _ Client, _ *containers.Container, s *Spec) error {
|
||||
setProcess(s)
|
||||
s.Process.ApparmorProfile = profile
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// WithSeccompUnconfined clears the seccomp profile
|
||||
func WithSeccompUnconfined(_ context.Context, _ Client, _ *containers.Container, s *Spec) error {
|
||||
setLinux(s)
|
||||
s.Linux.Seccomp = nil
|
||||
return nil
|
||||
}
|
||||
|
||||
// WithParentCgroupDevices uses the default cgroup setup to inherit the container's parent cgroup's
|
||||
// allowed and denied devices
|
||||
func WithParentCgroupDevices(_ context.Context, _ Client, _ *containers.Container, s *Spec) error {
|
||||
setLinux(s)
|
||||
if s.Linux.Resources == nil {
|
||||
s.Linux.Resources = &specs.LinuxResources{}
|
||||
}
|
||||
s.Linux.Resources.Devices = nil
|
||||
return nil
|
||||
}
|
||||
|
||||
// WithDefaultUnixDevices adds the default devices for unix such as /dev/null, /dev/random to
|
||||
// the container's resource cgroup spec
|
||||
func WithDefaultUnixDevices(_ context.Context, _ Client, _ *containers.Container, s *Spec) error {
|
||||
setLinux(s)
|
||||
if s.Linux.Resources == nil {
|
||||
s.Linux.Resources = &specs.LinuxResources{}
|
||||
}
|
||||
intptr := func(i int64) *int64 {
|
||||
return &i
|
||||
}
|
||||
s.Linux.Resources.Devices = append(s.Linux.Resources.Devices, []specs.LinuxDeviceCgroup{
|
||||
{
|
||||
// "/dev/null",
|
||||
Type: "c",
|
||||
Major: intptr(1),
|
||||
Minor: intptr(3),
|
||||
Access: rwm,
|
||||
Allow: true,
|
||||
},
|
||||
{
|
||||
// "/dev/random",
|
||||
Type: "c",
|
||||
Major: intptr(1),
|
||||
Minor: intptr(8),
|
||||
Access: rwm,
|
||||
Allow: true,
|
||||
},
|
||||
{
|
||||
// "/dev/full",
|
||||
Type: "c",
|
||||
Major: intptr(1),
|
||||
Minor: intptr(7),
|
||||
Access: rwm,
|
||||
Allow: true,
|
||||
},
|
||||
{
|
||||
// "/dev/tty",
|
||||
Type: "c",
|
||||
Major: intptr(5),
|
||||
Minor: intptr(0),
|
||||
Access: rwm,
|
||||
Allow: true,
|
||||
},
|
||||
{
|
||||
// "/dev/zero",
|
||||
Type: "c",
|
||||
Major: intptr(1),
|
||||
Minor: intptr(5),
|
||||
Access: rwm,
|
||||
Allow: true,
|
||||
},
|
||||
{
|
||||
// "/dev/urandom",
|
||||
Type: "c",
|
||||
Major: intptr(1),
|
||||
Minor: intptr(9),
|
||||
Access: rwm,
|
||||
Allow: true,
|
||||
},
|
||||
{
|
||||
// "/dev/console",
|
||||
Type: "c",
|
||||
Major: intptr(5),
|
||||
Minor: intptr(1),
|
||||
Access: rwm,
|
||||
Allow: true,
|
||||
},
|
||||
// /dev/pts/ - pts namespaces are "coming soon"
|
||||
{
|
||||
Type: "c",
|
||||
Major: intptr(136),
|
||||
Access: rwm,
|
||||
Allow: true,
|
||||
},
|
||||
{
|
||||
Type: "c",
|
||||
Major: intptr(5),
|
||||
Minor: intptr(2),
|
||||
Access: rwm,
|
||||
Allow: true,
|
||||
},
|
||||
{
|
||||
// tuntap
|
||||
Type: "c",
|
||||
Major: intptr(10),
|
||||
Minor: intptr(200),
|
||||
Access: rwm,
|
||||
Allow: true,
|
||||
},
|
||||
}...)
|
||||
return nil
|
||||
}
|
||||
|
||||
// WithPrivileged sets up options for a privileged container
|
||||
// TODO(justincormack) device handling
|
||||
var WithPrivileged = Compose(
|
||||
WithAllCapabilities,
|
||||
WithMaskedPaths(nil),
|
||||
WithReadonlyPaths(nil),
|
||||
WithWriteableSysfs,
|
||||
WithWriteableCgroupfs,
|
||||
WithSelinuxLabel(""),
|
||||
WithApparmorProfile(""),
|
||||
WithSeccompUnconfined,
|
||||
)
|
|
@ -1,89 +0,0 @@
|
|||
// +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 oci
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
||||
"github.com/containerd/containerd/containers"
|
||||
"github.com/containerd/containerd/content"
|
||||
"github.com/containerd/containerd/images"
|
||||
"github.com/opencontainers/image-spec/specs-go/v1"
|
||||
specs "github.com/opencontainers/runtime-spec/specs-go"
|
||||
)
|
||||
|
||||
// WithImageConfig configures the spec to from the configuration of an Image
|
||||
func WithImageConfig(image Image) SpecOpts {
|
||||
return func(ctx context.Context, client Client, _ *containers.Container, s *Spec) error {
|
||||
setProcess(s)
|
||||
ic, err := image.Config(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var (
|
||||
ociimage v1.Image
|
||||
config v1.ImageConfig
|
||||
)
|
||||
switch ic.MediaType {
|
||||
case v1.MediaTypeImageConfig, images.MediaTypeDockerSchema2Config:
|
||||
p, err := content.ReadBlob(ctx, image.ContentStore(), ic)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := json.Unmarshal(p, &ociimage); err != nil {
|
||||
return err
|
||||
}
|
||||
config = ociimage.Config
|
||||
default:
|
||||
return fmt.Errorf("unknown image config media type %s", ic.MediaType)
|
||||
}
|
||||
s.Process.Env = config.Env
|
||||
s.Process.Args = append(config.Entrypoint, config.Cmd...)
|
||||
s.Process.User = specs.User{
|
||||
Username: config.User,
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// WithTTY sets the information on the spec as well as the environment variables for
|
||||
// using a TTY
|
||||
func WithTTY(width, height int) SpecOpts {
|
||||
return func(_ context.Context, _ Client, _ *containers.Container, s *Spec) error {
|
||||
setProcess(s)
|
||||
s.Process.Terminal = true
|
||||
if s.Process.ConsoleSize == nil {
|
||||
s.Process.ConsoleSize = &specs.Box{}
|
||||
}
|
||||
s.Process.ConsoleSize.Width = uint(width)
|
||||
s.Process.ConsoleSize.Height = uint(height)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// WithUsername sets the username on the process
|
||||
func WithUsername(username string) SpecOpts {
|
||||
return func(ctx context.Context, client Client, c *containers.Container, s *Spec) error {
|
||||
setProcess(s)
|
||||
s.Process.User.Username = username
|
||||
return nil
|
||||
}
|
||||
}
|
|
@ -1,188 +0,0 @@
|
|||
// +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 oci
|
||||
|
||||
import (
|
||||
"context"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/containerd/containerd/namespaces"
|
||||
specs "github.com/opencontainers/runtime-spec/specs-go"
|
||||
)
|
||||
|
||||
const (
|
||||
rwm = "rwm"
|
||||
defaultRootfsPath = "rootfs"
|
||||
)
|
||||
|
||||
var (
|
||||
defaultEnv = []string{
|
||||
"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
|
||||
}
|
||||
)
|
||||
|
||||
func defaultCaps() []string {
|
||||
return []string{
|
||||
"CAP_CHOWN",
|
||||
"CAP_DAC_OVERRIDE",
|
||||
"CAP_FSETID",
|
||||
"CAP_FOWNER",
|
||||
"CAP_MKNOD",
|
||||
"CAP_NET_RAW",
|
||||
"CAP_SETGID",
|
||||
"CAP_SETUID",
|
||||
"CAP_SETFCAP",
|
||||
"CAP_SETPCAP",
|
||||
"CAP_NET_BIND_SERVICE",
|
||||
"CAP_SYS_CHROOT",
|
||||
"CAP_KILL",
|
||||
"CAP_AUDIT_WRITE",
|
||||
}
|
||||
}
|
||||
|
||||
func defaultNamespaces() []specs.LinuxNamespace {
|
||||
return []specs.LinuxNamespace{
|
||||
{
|
||||
Type: specs.PIDNamespace,
|
||||
},
|
||||
{
|
||||
Type: specs.IPCNamespace,
|
||||
},
|
||||
{
|
||||
Type: specs.UTSNamespace,
|
||||
},
|
||||
{
|
||||
Type: specs.MountNamespace,
|
||||
},
|
||||
{
|
||||
Type: specs.NetworkNamespace,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func populateDefaultSpec(ctx context.Context, s *Spec, id string) error {
|
||||
ns, err := namespaces.NamespaceRequired(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
*s = Spec{
|
||||
Version: specs.Version,
|
||||
Root: &specs.Root{
|
||||
Path: defaultRootfsPath,
|
||||
},
|
||||
Process: &specs.Process{
|
||||
Env: defaultEnv,
|
||||
Cwd: "/",
|
||||
NoNewPrivileges: true,
|
||||
User: specs.User{
|
||||
UID: 0,
|
||||
GID: 0,
|
||||
},
|
||||
Capabilities: &specs.LinuxCapabilities{
|
||||
Bounding: defaultCaps(),
|
||||
Permitted: defaultCaps(),
|
||||
Inheritable: defaultCaps(),
|
||||
Effective: defaultCaps(),
|
||||
},
|
||||
Rlimits: []specs.POSIXRlimit{
|
||||
{
|
||||
Type: "RLIMIT_NOFILE",
|
||||
Hard: uint64(1024),
|
||||
Soft: uint64(1024),
|
||||
},
|
||||
},
|
||||
},
|
||||
Mounts: []specs.Mount{
|
||||
{
|
||||
Destination: "/proc",
|
||||
Type: "proc",
|
||||
Source: "proc",
|
||||
},
|
||||
{
|
||||
Destination: "/dev",
|
||||
Type: "tmpfs",
|
||||
Source: "tmpfs",
|
||||
Options: []string{"nosuid", "strictatime", "mode=755", "size=65536k"},
|
||||
},
|
||||
{
|
||||
Destination: "/dev/pts",
|
||||
Type: "devpts",
|
||||
Source: "devpts",
|
||||
Options: []string{"nosuid", "noexec", "newinstance", "ptmxmode=0666", "mode=0620", "gid=5"},
|
||||
},
|
||||
{
|
||||
Destination: "/dev/shm",
|
||||
Type: "tmpfs",
|
||||
Source: "shm",
|
||||
Options: []string{"nosuid", "noexec", "nodev", "mode=1777", "size=65536k"},
|
||||
},
|
||||
{
|
||||
Destination: "/dev/mqueue",
|
||||
Type: "mqueue",
|
||||
Source: "mqueue",
|
||||
Options: []string{"nosuid", "noexec", "nodev"},
|
||||
},
|
||||
{
|
||||
Destination: "/sys",
|
||||
Type: "sysfs",
|
||||
Source: "sysfs",
|
||||
Options: []string{"nosuid", "noexec", "nodev", "ro"},
|
||||
},
|
||||
{
|
||||
Destination: "/run",
|
||||
Type: "tmpfs",
|
||||
Source: "tmpfs",
|
||||
Options: []string{"nosuid", "strictatime", "mode=755", "size=65536k"},
|
||||
},
|
||||
},
|
||||
Linux: &specs.Linux{
|
||||
MaskedPaths: []string{
|
||||
"/proc/acpi",
|
||||
"/proc/kcore",
|
||||
"/proc/keys",
|
||||
"/proc/latency_stats",
|
||||
"/proc/timer_list",
|
||||
"/proc/timer_stats",
|
||||
"/proc/sched_debug",
|
||||
"/sys/firmware",
|
||||
"/proc/scsi",
|
||||
},
|
||||
ReadonlyPaths: []string{
|
||||
"/proc/asound",
|
||||
"/proc/bus",
|
||||
"/proc/fs",
|
||||
"/proc/irq",
|
||||
"/proc/sys",
|
||||
"/proc/sysrq-trigger",
|
||||
},
|
||||
CgroupsPath: filepath.Join("/", ns, id),
|
||||
Resources: &specs.LinuxResources{
|
||||
Devices: []specs.LinuxDeviceCgroup{
|
||||
{
|
||||
Allow: false,
|
||||
Access: rwm,
|
||||
},
|
||||
},
|
||||
},
|
||||
Namespaces: defaultNamespaces(),
|
||||
},
|
||||
}
|
||||
return nil
|
||||
}
|
|
@ -22,11 +22,6 @@ import (
|
|||
specs "github.com/opencontainers/image-spec/specs-go/v1"
|
||||
)
|
||||
|
||||
// Default returns the default matcher for the platform.
|
||||
func Default() MatchComparer {
|
||||
return Only(DefaultSpec())
|
||||
}
|
||||
|
||||
// DefaultString returns the default string specifier for the platform.
|
||||
func DefaultString() string {
|
||||
return Format(DefaultSpec())
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
// +build !windows
|
||||
|
||||
/*
|
||||
Copyright The containerd Authors.
|
||||
|
||||
|
@ -14,31 +16,9 @@
|
|||
limitations under the License.
|
||||
*/
|
||||
|
||||
package oci
|
||||
package platforms
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
specs "github.com/opencontainers/runtime-spec/specs-go"
|
||||
)
|
||||
|
||||
func populateDefaultSpec(ctx context.Context, s *Spec, id string) error {
|
||||
*s = Spec{
|
||||
Version: specs.Version,
|
||||
Root: &specs.Root{},
|
||||
Process: &specs.Process{
|
||||
Cwd: `C:\`,
|
||||
ConsoleSize: &specs.Box{
|
||||
Width: 80,
|
||||
Height: 20,
|
||||
},
|
||||
},
|
||||
Windows: &specs.Windows{
|
||||
IgnoreFlushesDuringBoot: true,
|
||||
Network: &specs.WindowsNetwork{
|
||||
AllowUnqualifiedDNSQuery: true,
|
||||
},
|
||||
},
|
||||
}
|
||||
return nil
|
||||
// Default returns the default matcher for the platform.
|
||||
func Default() MatchComparer {
|
||||
return Only(DefaultSpec())
|
||||
}
|
|
@ -1,3 +1,5 @@
|
|||
// +build windows
|
||||
|
||||
/*
|
||||
Copyright The containerd Authors.
|
||||
|
||||
|
@ -14,18 +16,16 @@
|
|||
limitations under the License.
|
||||
*/
|
||||
|
||||
package containerd
|
||||
package platforms
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
specs "github.com/opencontainers/runtime-spec/specs-go"
|
||||
specs "github.com/opencontainers/image-spec/specs-go/v1"
|
||||
)
|
||||
|
||||
// WithResources sets the provided resources on the spec for task updates
|
||||
func WithResources(resources *specs.WindowsResources) UpdateTaskOpts {
|
||||
return func(ctx context.Context, client *Client, r *UpdateTaskInfo) error {
|
||||
r.Resources = resources
|
||||
return nil
|
||||
}
|
||||
// Default returns the default matcher for the platform.
|
||||
func Default() MatchComparer {
|
||||
return Ordered(DefaultSpec(), specs.Platform{
|
||||
OS: "linux",
|
||||
Architecture: "amd64",
|
||||
})
|
||||
}
|
|
@ -117,7 +117,7 @@ func (r dockerFetcher) open(ctx context.Context, u, mediatype string, offset int
|
|||
}
|
||||
} else {
|
||||
// TODO: Should any cases where use of content range
|
||||
// without the proper header be considerd?
|
||||
// without the proper header be considered?
|
||||
// 206 responses?
|
||||
|
||||
// Discard up to offset
|
||||
|
|
|
@ -134,7 +134,7 @@ func (hrs *httpReadSeeker) reader() (io.Reader, error) {
|
|||
// There is an edge case here where offset == size of the content. If
|
||||
// we seek, we will probably get an error for content that cannot be
|
||||
// sought (?). In that case, we should err on committing the content,
|
||||
// as the length is already satisified but we just return the empty
|
||||
// as the length is already satisfied but we just return the empty
|
||||
// reader instead.
|
||||
|
||||
hrs.rc = ioutil.NopCloser(bytes.NewReader([]byte{}))
|
||||
|
|
|
@ -272,8 +272,14 @@ func (c *Converter) fetchBlob(ctx context.Context, desc ocispec.Descriptor) erro
|
|||
return err
|
||||
}
|
||||
|
||||
// TODO: Check if blob -> diff id mapping already exists
|
||||
// TODO: Check if blob empty label exists
|
||||
reuse, err := c.reuseLabelBlobState(ctx, desc)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if reuse {
|
||||
return nil
|
||||
}
|
||||
|
||||
ra, err := c.contentStore.ReaderAt(ctx, desc)
|
||||
if err != nil {
|
||||
|
@ -343,6 +349,17 @@ func (c *Converter) fetchBlob(ctx context.Context, desc ocispec.Descriptor) erro
|
|||
|
||||
state := calc.State()
|
||||
|
||||
cinfo := content.Info{
|
||||
Digest: desc.Digest,
|
||||
Labels: map[string]string{
|
||||
"containerd.io/uncompressed": state.diffID.String(),
|
||||
},
|
||||
}
|
||||
|
||||
if _, err := c.contentStore.Update(ctx, cinfo, "labels.containerd.io/uncompressed"); err != nil {
|
||||
return errors.Wrap(err, "failed to update uncompressed label")
|
||||
}
|
||||
|
||||
c.mu.Lock()
|
||||
c.blobMap[desc.Digest] = state
|
||||
c.layerBlobs[state.diffID] = desc
|
||||
|
@ -351,6 +368,40 @@ func (c *Converter) fetchBlob(ctx context.Context, desc ocispec.Descriptor) erro
|
|||
return nil
|
||||
}
|
||||
|
||||
func (c *Converter) reuseLabelBlobState(ctx context.Context, desc ocispec.Descriptor) (bool, error) {
|
||||
cinfo, err := c.contentStore.Info(ctx, desc.Digest)
|
||||
if err != nil {
|
||||
return false, errors.Wrap(err, "failed to get blob info")
|
||||
}
|
||||
desc.Size = cinfo.Size
|
||||
|
||||
diffID, ok := cinfo.Labels["containerd.io/uncompressed"]
|
||||
if !ok {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
bState := blobState{empty: false}
|
||||
|
||||
if bState.diffID, err = digest.Parse(diffID); err != nil {
|
||||
log.G(ctx).WithField("id", desc.Digest).Warnf("failed to parse digest from label containerd.io/uncompressed: %v", diffID)
|
||||
return false, nil
|
||||
}
|
||||
|
||||
// NOTE: there is no need to read header to get compression method
|
||||
// because there are only two kinds of methods.
|
||||
if bState.diffID == desc.Digest {
|
||||
desc.MediaType = images.MediaTypeDockerSchema2Layer
|
||||
} else {
|
||||
desc.MediaType = images.MediaTypeDockerSchema2LayerGzip
|
||||
}
|
||||
|
||||
c.mu.Lock()
|
||||
c.blobMap[desc.Digest] = bState
|
||||
c.layerBlobs[bState.diffID] = desc
|
||||
c.mu.Unlock()
|
||||
return true, nil
|
||||
}
|
||||
|
||||
func (c *Converter) schema1ManifestHistory() ([]ocispec.History, []digest.Digest, error) {
|
||||
if c.pulledManifest == nil {
|
||||
return nil, nil, errors.New("missing schema 1 manifest for conversion")
|
||||
|
|
|
@ -28,6 +28,7 @@ import (
|
|||
"github.com/containerd/containerd/plugin"
|
||||
"github.com/containerd/containerd/snapshots"
|
||||
"github.com/containerd/containerd/snapshots/storage"
|
||||
|
||||
"github.com/containerd/continuity/fs"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
@ -120,7 +121,7 @@ func (o *snapshotter) Usage(ctx context.Context, key string) (snapshots.Usage, e
|
|||
}
|
||||
|
||||
if info.Kind == snapshots.KindActive {
|
||||
du, err := fs.DiskUsage(o.getSnapshotDir(id))
|
||||
du, err := fs.DiskUsage(ctx, o.getSnapshotDir(id))
|
||||
if err != nil {
|
||||
return snapshots.Usage{}, err
|
||||
}
|
||||
|
@ -166,7 +167,7 @@ func (o *snapshotter) Commit(ctx context.Context, name, key string, opts ...snap
|
|||
return err
|
||||
}
|
||||
|
||||
usage, err := fs.DiskUsage(o.getSnapshotDir(id))
|
||||
usage, err := fs.DiskUsage(ctx, o.getSnapshotDir(id))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
|
@ -71,7 +71,7 @@ func supportsMultipleLowerDir(d string) error {
|
|||
}
|
||||
|
||||
// Supported returns nil when the overlayfs is functional on the system with the root directory.
|
||||
// Suppported is not called during plugin initialization, but exposed for downstream projects which uses
|
||||
// Supported is not called during plugin initialization, but exposed for downstream projects which uses
|
||||
// this snapshotter as a library.
|
||||
func Supported(root string) error {
|
||||
if err := os.MkdirAll(root, 0700); err != nil {
|
||||
|
|
|
@ -168,7 +168,7 @@ func (o *snapshotter) Usage(ctx context.Context, key string) (snapshots.Usage, e
|
|||
upperPath := o.upperPath(id)
|
||||
|
||||
if info.Kind == snapshots.KindActive {
|
||||
du, err := fs.DiskUsage(upperPath)
|
||||
du, err := fs.DiskUsage(ctx, upperPath)
|
||||
if err != nil {
|
||||
// TODO(stevvooe): Consider not reporting an error in this case.
|
||||
return snapshots.Usage{}, err
|
||||
|
@ -225,7 +225,7 @@ func (o *snapshotter) Commit(ctx context.Context, name, key string, opts ...snap
|
|||
return err
|
||||
}
|
||||
|
||||
usage, err := fs.DiskUsage(o.upperPath(id))
|
||||
usage, err := fs.DiskUsage(ctx, o.upperPath(id))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
|
@ -42,7 +42,7 @@ func CreateUnixSocket(path string) (net.Listener, error) {
|
|||
return net.Listen("unix", path)
|
||||
}
|
||||
|
||||
// GetLocalListener returns a listerner out of a unix socket.
|
||||
// GetLocalListener returns a listener out of a unix socket.
|
||||
func GetLocalListener(path string, uid, gid int) (net.Listener, error) {
|
||||
// Ensure parent directory is created
|
||||
if err := mkdirAs(filepath.Dir(path), uid, gid); err != nil {
|
||||
|
|
|
@ -18,10 +18,18 @@ package containerd
|
|||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"syscall"
|
||||
|
||||
"github.com/containerd/containerd/api/types"
|
||||
"github.com/containerd/containerd/content"
|
||||
"github.com/containerd/containerd/errdefs"
|
||||
"github.com/containerd/containerd/images"
|
||||
"github.com/containerd/containerd/mount"
|
||||
imagespec "github.com/opencontainers/image-spec/specs-go/v1"
|
||||
"github.com/opencontainers/runtime-spec/specs-go"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
// NewTaskOpts allows the caller to set options on a new task
|
||||
|
@ -35,6 +43,44 @@ func WithRootFS(mounts []mount.Mount) NewTaskOpts {
|
|||
}
|
||||
}
|
||||
|
||||
// WithTaskCheckpoint allows a task to be created with live runtime and memory data from a
|
||||
// previous checkpoint. Additional software such as CRIU may be required to
|
||||
// restore a task from a checkpoint
|
||||
func WithTaskCheckpoint(im Image) NewTaskOpts {
|
||||
return func(ctx context.Context, c *Client, info *TaskInfo) error {
|
||||
desc := im.Target()
|
||||
id := desc.Digest
|
||||
index, err := decodeIndex(ctx, c.ContentStore(), desc)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, m := range index.Manifests {
|
||||
if m.MediaType == images.MediaTypeContainerd1Checkpoint {
|
||||
info.Checkpoint = &types.Descriptor{
|
||||
MediaType: m.MediaType,
|
||||
Size_: m.Size,
|
||||
Digest: m.Digest,
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
return fmt.Errorf("checkpoint not found in index %s", id)
|
||||
}
|
||||
}
|
||||
|
||||
func decodeIndex(ctx context.Context, store content.Provider, desc imagespec.Descriptor) (*imagespec.Index, error) {
|
||||
var index imagespec.Index
|
||||
p, err := content.ReadBlob(ctx, store, desc)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := json.Unmarshal(p, &index); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &index, nil
|
||||
}
|
||||
|
||||
// WithCheckpointName sets the image name for the checkpoint
|
||||
func WithCheckpointName(name string) CheckpointTaskOpts {
|
||||
return func(r *CheckpointTaskInfo) error {
|
||||
|
@ -92,3 +138,19 @@ func WithKillExecID(execID string) KillOpts {
|
|||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// WithResources sets the provided resources for task updates. Resources must be
|
||||
// either a *specs.LinuxResources or a *specs.WindowsResources
|
||||
func WithResources(resources interface{}) UpdateTaskOpts {
|
||||
return func(ctx context.Context, client *Client, r *UpdateTaskInfo) error {
|
||||
switch resources.(type) {
|
||||
case *specs.LinuxResources:
|
||||
case *specs.WindowsResources:
|
||||
default:
|
||||
return errors.New("WithResources requires a *specs.LinuxResources or *specs.WindowsResources")
|
||||
}
|
||||
|
||||
r.Resources = resources
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
// +build !windows
|
||||
|
||||
/*
|
||||
Copyright The containerd Authors.
|
||||
|
||||
|
@ -18,20 +20,11 @@ package containerd
|
|||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
|
||||
"github.com/containerd/containerd/runtime/linux/runctypes"
|
||||
"github.com/opencontainers/runtime-spec/specs-go"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
// WithResources sets the provided resources for task updates
|
||||
func WithResources(resources *specs.LinuxResources) UpdateTaskOpts {
|
||||
return func(ctx context.Context, client *Client, r *UpdateTaskInfo) error {
|
||||
r.Resources = resources
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// WithNoNewKeyring causes tasks not to be created with a new keyring for secret storage.
|
||||
// There is an upper limit on the number of keyrings in a linux system
|
||||
func WithNoNewKeyring(ctx context.Context, c *Client, ti *TaskInfo) error {
|
||||
|
@ -46,3 +39,19 @@ func WithNoNewKeyring(ctx context.Context, c *Client, ti *TaskInfo) error {
|
|||
opts.NoNewKeyring = true
|
||||
return nil
|
||||
}
|
||||
|
||||
// WithNoPivotRoot instructs the runtime not to you pivot_root
|
||||
func WithNoPivotRoot(_ context.Context, _ *Client, info *TaskInfo) error {
|
||||
if info.Options == nil {
|
||||
info.Options = &runctypes.CreateOptions{
|
||||
NoPivotRoot: true,
|
||||
}
|
||||
return nil
|
||||
}
|
||||
opts, ok := info.Options.(*runctypes.CreateOptions)
|
||||
if !ok {
|
||||
return errors.New("invalid options type, expected runctypes.CreateOptions")
|
||||
}
|
||||
opts.NoPivotRoot = true
|
||||
return nil
|
||||
}
|
|
@ -1,10 +1,10 @@
|
|||
github.com/containerd/go-runc acb7c88cac264acca9b5eae187a117f4d77a1292
|
||||
github.com/containerd/go-runc 5a6d9f37cfa36b15efba46dc7ea349fa9b7143c3
|
||||
github.com/containerd/console c12b1e7919c14469339a5d38f2f8ed9b64a9de23
|
||||
github.com/containerd/cgroups 5e610833b72089b37d0e615de9a92dfc043757c2
|
||||
github.com/containerd/typeurl a93fcdb778cd272c6e9b3028b2f42d813e785d40
|
||||
github.com/containerd/fifo 3d5202aec260678c48179c56f40e6f38a095738c
|
||||
github.com/containerd/btrfs 2e1aa0ddf94f91fa282b6ed87c23bf0d64911244
|
||||
github.com/containerd/continuity d3c23511c1bf5851696cba83143d9cbcd666869b
|
||||
github.com/containerd/continuity f44b615e492bdfb371aae2f76ec694d9da1db537
|
||||
github.com/coreos/go-systemd 48702e0da86bd25e76cfef347e2adeb434a0d0a6
|
||||
github.com/docker/go-metrics 4ea375f7759c82740c893fc030bc37088d2ec098
|
||||
github.com/docker/go-events 9461782956ad83b30282bf90e31fa6a70c255ba9
|
||||
|
@ -85,4 +85,4 @@ github.com/mistifyio/go-zfs 166add352731e515512690329794ee593f1aaff2
|
|||
github.com/pborman/uuid c65b2f87fee37d1c7854c9164a450713c28d50cd
|
||||
|
||||
# aufs dependencies
|
||||
github.com/containerd/aufs a7fbd554da7a9eafbe5a460a421313a9fd18d988
|
||||
github.com/containerd/aufs ffa39970e26ad01d81f540b21e65f9c1841a5f92
|
||||
|
|
|
@ -12,11 +12,14 @@ import (
|
|||
"github.com/containerd/continuity/devices"
|
||||
driverpkg "github.com/containerd/continuity/driver"
|
||||
"github.com/containerd/continuity/pathdriver"
|
||||
|
||||
"github.com/opencontainers/go-digest"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrNotFound = fmt.Errorf("not found")
|
||||
// ErrNotFound represents the resource not found
|
||||
ErrNotFound = fmt.Errorf("not found")
|
||||
// ErrNotSupported represents the resource not supported
|
||||
ErrNotSupported = fmt.Errorf("not supported")
|
||||
)
|
||||
|
||||
|
@ -36,6 +39,7 @@ type Context interface {
|
|||
// not under the given root.
|
||||
type SymlinkPath func(root, linkname, target string) (string, error)
|
||||
|
||||
// ContextOptions represents options to create a new context.
|
||||
type ContextOptions struct {
|
||||
Digester Digester
|
||||
Driver driverpkg.Driver
|
||||
|
@ -379,7 +383,7 @@ func (c *context) checkoutFile(fp string, rf RegularFile) error {
|
|||
}
|
||||
defer r.Close()
|
||||
|
||||
return atomicWriteFile(fp, r, rf)
|
||||
return atomicWriteFile(fp, r, rf.Size(), rf.Mode())
|
||||
}
|
||||
|
||||
// Apply the resource to the contexts. An error will be returned if the
|
||||
|
@ -473,10 +477,6 @@ func (c *context) Apply(resource Resource) error {
|
|||
}
|
||||
}
|
||||
|
||||
// NOTE(stevvooe): Chmod on symlink is not supported on linux. We
|
||||
// may want to maintain support for other platforms that have it.
|
||||
chmod = false
|
||||
|
||||
case Device:
|
||||
if fi == nil {
|
||||
if err := c.driver.Mknod(fp, resource.Mode(), int(r.Major()), int(r.Minor())); err != nil {
|
||||
|
|
|
@ -6,7 +6,6 @@ import (
|
|||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
|
||||
"github.com/containerd/continuity/devices"
|
||||
|
@ -26,18 +25,6 @@ func (d *driver) Mkfifo(path string, mode os.FileMode) error {
|
|||
return devices.Mknod(path, mode, 0, 0)
|
||||
}
|
||||
|
||||
// Lchmod changes the mode of an file not following symlinks.
|
||||
func (d *driver) Lchmod(path string, mode os.FileMode) (err error) {
|
||||
if !filepath.IsAbs(path) {
|
||||
path, err = filepath.Abs(path)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
return sysx.Fchmodat(0, path, uint32(mode), sysx.AtSymlinkNofollow)
|
||||
}
|
||||
|
||||
// Getxattr returns all of the extended attributes for the file at path p.
|
||||
func (d *driver) Getxattr(p string) (map[string][]byte, error) {
|
||||
xattrs, err := sysx.Listxattr(p)
|
||||
|
|
|
@ -0,0 +1,19 @@
|
|||
package driver
|
||||
|
||||
import (
|
||||
"os"
|
||||
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
// Lchmod changes the mode of a file not following symlinks.
|
||||
func (d *driver) Lchmod(path string, mode os.FileMode) error {
|
||||
// On Linux, file mode is not supported for symlinks,
|
||||
// and fchmodat() does not support AT_SYMLINK_NOFOLLOW,
|
||||
// so symlinks need to be skipped entirely.
|
||||
if st, err := os.Stat(path); err == nil && st.Mode()&os.ModeSymlink != 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
return unix.Fchmodat(unix.AT_FDCWD, path, uint32(mode), 0)
|
||||
}
|
|
@ -0,0 +1,14 @@
|
|||
// +build darwin freebsd solaris
|
||||
|
||||
package driver
|
||||
|
||||
import (
|
||||
"os"
|
||||
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
// Lchmod changes the mode of a file not following symlinks.
|
||||
func (d *driver) Lchmod(path string, mode os.FileMode) error {
|
||||
return unix.Fchmodat(unix.AT_FDCWD, path, uint32(mode), unix.AT_SYMLINK_NOFOLLOW)
|
||||
}
|
|
@ -10,8 +10,8 @@ type Usage struct {
|
|||
|
||||
// DiskUsage counts the number of inodes and disk usage for the resources under
|
||||
// path.
|
||||
func DiskUsage(roots ...string) (Usage, error) {
|
||||
return diskUsage(roots...)
|
||||
func DiskUsage(ctx context.Context, roots ...string) (Usage, error) {
|
||||
return diskUsage(ctx, roots...)
|
||||
}
|
||||
|
||||
// DiffUsage counts the numbers of inodes and disk usage in the
|
||||
|
|
|
@ -24,7 +24,7 @@ func newInode(stat *syscall.Stat_t) inode {
|
|||
}
|
||||
}
|
||||
|
||||
func diskUsage(roots ...string) (Usage, error) {
|
||||
func diskUsage(ctx context.Context, roots ...string) (Usage, error) {
|
||||
|
||||
var (
|
||||
size int64
|
||||
|
@ -37,6 +37,12 @@ func diskUsage(roots ...string) (Usage, error) {
|
|||
return err
|
||||
}
|
||||
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return ctx.Err()
|
||||
default:
|
||||
}
|
||||
|
||||
inoKey := newInode(fi.Sys().(*syscall.Stat_t))
|
||||
if _, ok := inodes[inoKey]; !ok {
|
||||
inodes[inoKey] = struct{}{}
|
||||
|
|
|
@ -8,7 +8,7 @@ import (
|
|||
"path/filepath"
|
||||
)
|
||||
|
||||
func diskUsage(roots ...string) (Usage, error) {
|
||||
func diskUsage(ctx context.Context, roots ...string) (Usage, error) {
|
||||
var (
|
||||
size int64
|
||||
)
|
||||
|
@ -21,6 +21,12 @@ func diskUsage(roots ...string) (Usage, error) {
|
|||
return err
|
||||
}
|
||||
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return ctx.Err()
|
||||
default:
|
||||
}
|
||||
|
||||
size += fi.Size()
|
||||
return nil
|
||||
}); err != nil {
|
||||
|
|
|
@ -1,26 +1,34 @@
|
|||
package continuity
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
// AtomicWriteFile atomically writes data to a file by first writing to a
|
||||
// temp file and calling rename.
|
||||
func AtomicWriteFile(filename string, data []byte, perm os.FileMode) error {
|
||||
buf := bytes.NewBuffer(data)
|
||||
return atomicWriteFile(filename, buf, int64(len(data)), perm)
|
||||
}
|
||||
|
||||
// atomicWriteFile writes data to a file by first writing to a temp
|
||||
// file and calling rename.
|
||||
func atomicWriteFile(filename string, r io.Reader, rf RegularFile) error {
|
||||
func atomicWriteFile(filename string, r io.Reader, dataSize int64, perm os.FileMode) error {
|
||||
f, err := ioutil.TempFile(filepath.Dir(filename), ".tmp-"+filepath.Base(filename))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = os.Chmod(f.Name(), rf.Mode())
|
||||
err = os.Chmod(f.Name(), perm)
|
||||
if err != nil {
|
||||
f.Close()
|
||||
return err
|
||||
}
|
||||
n, err := io.Copy(f, r)
|
||||
if err == nil && n < rf.Size() {
|
||||
if err == nil && n < dataSize {
|
||||
f.Close()
|
||||
return io.ErrShortWrite
|
||||
}
|
||||
|
|
|
@ -0,0 +1,3 @@
|
|||
This package is for internal use only. It is intended to only have
|
||||
temporary changes before they are upstreamed to golang.org/x/sys/
|
||||
(a.k.a. https://github.com/golang/sys).
|
|
@ -1,10 +0,0 @@
|
|||
// Copyright 2014 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// +build !gccgo
|
||||
|
||||
#include "textflag.h"
|
||||
|
||||
TEXT ·use(SB),NOSPLIT,$0
|
||||
RET
|
|
@ -1,18 +0,0 @@
|
|||
package sysx
|
||||
|
||||
const (
|
||||
// AtSymlinkNoFollow defined from AT_SYMLINK_NOFOLLOW in <sys/fcntl.h>
|
||||
AtSymlinkNofollow = 0x20
|
||||
)
|
||||
|
||||
const (
|
||||
|
||||
// SYS_FCHMODAT defined from golang.org/sys/unix
|
||||
SYS_FCHMODAT = 467
|
||||
)
|
||||
|
||||
// These functions will be generated by generate.sh
|
||||
// $ GOOS=darwin GOARCH=386 ./generate.sh chmod
|
||||
// $ GOOS=darwin GOARCH=amd64 ./generate.sh chmod
|
||||
|
||||
//sys Fchmodat(dirfd int, path string, mode uint32, flags int) (err error)
|
|
@ -1,25 +0,0 @@
|
|||
// mksyscall.pl -l32 chmod_darwin.go
|
||||
// MACHINE GENERATED BY THE COMMAND ABOVE; DO NOT EDIT
|
||||
|
||||
package sysx
|
||||
|
||||
import (
|
||||
"syscall"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
|
||||
|
||||
func Fchmodat(dirfd int, path string, mode uint32, flags int) (err error) {
|
||||
var _p0 *byte
|
||||
_p0, err = syscall.BytePtrFromString(path)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
_, _, e1 := syscall.Syscall6(SYS_FCHMODAT, uintptr(dirfd), uintptr(unsafe.Pointer(_p0)), uintptr(mode), uintptr(flags), 0, 0)
|
||||
use(unsafe.Pointer(_p0))
|
||||
if e1 != 0 {
|
||||
err = errnoErr(e1)
|
||||
}
|
||||
return
|
||||
}
|
|
@ -1,25 +0,0 @@
|
|||
// mksyscall.pl chmod_darwin.go
|
||||
// MACHINE GENERATED BY THE COMMAND ABOVE; DO NOT EDIT
|
||||
|
||||
package sysx
|
||||
|
||||
import (
|
||||
"syscall"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
|
||||
|
||||
func Fchmodat(dirfd int, path string, mode uint32, flags int) (err error) {
|
||||
var _p0 *byte
|
||||
_p0, err = syscall.BytePtrFromString(path)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
_, _, e1 := syscall.Syscall6(SYS_FCHMODAT, uintptr(dirfd), uintptr(unsafe.Pointer(_p0)), uintptr(mode), uintptr(flags), 0, 0)
|
||||
use(unsafe.Pointer(_p0))
|
||||
if e1 != 0 {
|
||||
err = errnoErr(e1)
|
||||
}
|
||||
return
|
||||
}
|
|
@ -1,17 +0,0 @@
|
|||
package sysx
|
||||
|
||||
const (
|
||||
// AtSymlinkNoFollow defined from AT_SYMLINK_NOFOLLOW in <sys/fcntl.h>
|
||||
AtSymlinkNofollow = 0x200
|
||||
)
|
||||
|
||||
const (
|
||||
|
||||
// SYS_FCHMODAT defined from golang.org/sys/unix
|
||||
SYS_FCHMODAT = 490
|
||||
)
|
||||
|
||||
// These functions will be generated by generate.sh
|
||||
// $ GOOS=freebsd GOARCH=amd64 ./generate.sh chmod
|
||||
|
||||
//sys Fchmodat(dirfd int, path string, mode uint32, flags int) (err error)
|
|
@ -1,25 +0,0 @@
|
|||
// mksyscall.pl chmod_freebsd.go
|
||||
// MACHINE GENERATED BY THE COMMAND ABOVE; DO NOT EDIT
|
||||
|
||||
package sysx
|
||||
|
||||
import (
|
||||
"syscall"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
|
||||
|
||||
func Fchmodat(dirfd int, path string, mode uint32, flags int) (err error) {
|
||||
var _p0 *byte
|
||||
_p0, err = syscall.BytePtrFromString(path)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
_, _, e1 := syscall.Syscall6(SYS_FCHMODAT, uintptr(dirfd), uintptr(unsafe.Pointer(_p0)), uintptr(mode), uintptr(flags), 0, 0)
|
||||
use(unsafe.Pointer(_p0))
|
||||
if e1 != 0 {
|
||||
err = errnoErr(e1)
|
||||
}
|
||||
return
|
||||
}
|
|
@ -1,12 +0,0 @@
|
|||
package sysx
|
||||
|
||||
import "syscall"
|
||||
|
||||
const (
|
||||
// AtSymlinkNoFollow defined from AT_SYMLINK_NOFOLLOW in /usr/include/linux/fcntl.h
|
||||
AtSymlinkNofollow = 0x100
|
||||
)
|
||||
|
||||
func Fchmodat(dirfd int, path string, mode uint32, flags int) error {
|
||||
return syscall.Fchmodat(dirfd, path, mode, flags)
|
||||
}
|
|
@ -1,11 +0,0 @@
|
|||
package sysx
|
||||
|
||||
import "golang.org/x/sys/unix"
|
||||
|
||||
const (
|
||||
AtSymlinkNofollow = unix.AT_SYMLINK_NOFOLLOW
|
||||
)
|
||||
|
||||
func Fchmodat(dirfd int, path string, mode uint32, flags int) error {
|
||||
return unix.Fchmodat(dirfd, path, mode, flags)
|
||||
}
|
|
@ -1,37 +0,0 @@
|
|||
package sysx
|
||||
|
||||
import (
|
||||
"syscall"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
var _zero uintptr
|
||||
|
||||
// use is a no-op, but the compiler cannot see that it is.
|
||||
// Calling use(p) ensures that p is kept live until that point.
|
||||
//go:noescape
|
||||
func use(p unsafe.Pointer)
|
||||
|
||||
// Do the interface allocations only once for common
|
||||
// Errno values.
|
||||
var (
|
||||
errEAGAIN error = syscall.EAGAIN
|
||||
errEINVAL error = syscall.EINVAL
|
||||
errENOENT error = syscall.ENOENT
|
||||
)
|
||||
|
||||
// errnoErr returns common boxed Errno values, to prevent
|
||||
// allocations at runtime.
|
||||
func errnoErr(e syscall.Errno) error {
|
||||
switch e {
|
||||
case 0:
|
||||
return nil
|
||||
case syscall.EAGAIN:
|
||||
return errEAGAIN
|
||||
case syscall.EINVAL:
|
||||
return errEINVAL
|
||||
case syscall.ENOENT:
|
||||
return errENOENT
|
||||
}
|
||||
return e
|
||||
}
|
|
@ -1,14 +1,56 @@
|
|||
// +build linux darwin
|
||||
|
||||
package sysx
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"syscall"
|
||||
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
const defaultXattrBufferSize = 5
|
||||
// Listxattr calls syscall listxattr and reads all content
|
||||
// and returns a string array
|
||||
func Listxattr(path string) ([]string, error) {
|
||||
return listxattrAll(path, unix.Listxattr)
|
||||
}
|
||||
|
||||
var ErrNotSupported = fmt.Errorf("not supported")
|
||||
// Removexattr calls syscall removexattr
|
||||
func Removexattr(path string, attr string) (err error) {
|
||||
return unix.Removexattr(path, attr)
|
||||
}
|
||||
|
||||
// Setxattr calls syscall setxattr
|
||||
func Setxattr(path string, attr string, data []byte, flags int) (err error) {
|
||||
return unix.Setxattr(path, attr, data, flags)
|
||||
}
|
||||
|
||||
// Getxattr calls syscall getxattr
|
||||
func Getxattr(path, attr string) ([]byte, error) {
|
||||
return getxattrAll(path, attr, unix.Getxattr)
|
||||
}
|
||||
|
||||
// LListxattr lists xattrs, not following symlinks
|
||||
func LListxattr(path string) ([]string, error) {
|
||||
return listxattrAll(path, unix.Llistxattr)
|
||||
}
|
||||
|
||||
// LRemovexattr removes an xattr, not following symlinks
|
||||
func LRemovexattr(path string, attr string) (err error) {
|
||||
return unix.Lremovexattr(path, attr)
|
||||
}
|
||||
|
||||
// LSetxattr sets an xattr, not following symlinks
|
||||
func LSetxattr(path string, attr string, data []byte, flags int) (err error) {
|
||||
return unix.Lsetxattr(path, attr, data, flags)
|
||||
}
|
||||
|
||||
// LGetxattr gets an xattr, not following symlinks
|
||||
func LGetxattr(path, attr string) ([]byte, error) {
|
||||
return getxattrAll(path, attr, unix.Lgetxattr)
|
||||
}
|
||||
|
||||
const defaultXattrBufferSize = 5
|
||||
|
||||
type listxattrFunc func(path string, dest []byte) (int, error)
|
||||
|
||||
|
|
|
@ -1,71 +0,0 @@
|
|||
package sysx
|
||||
|
||||
// These functions will be generated by generate.sh
|
||||
// $ GOOS=darwin GOARCH=386 ./generate.sh xattr
|
||||
// $ GOOS=darwin GOARCH=amd64 ./generate.sh xattr
|
||||
|
||||
//sys getxattr(path string, attr string, dest []byte, pos int, options int) (sz int, err error)
|
||||
//sys setxattr(path string, attr string, data []byte, flags int) (err error)
|
||||
//sys removexattr(path string, attr string, options int) (err error)
|
||||
//sys listxattr(path string, dest []byte, options int) (sz int, err error)
|
||||
//sys Fchmodat(dirfd int, path string, mode uint32, flags int) (err error)
|
||||
|
||||
const (
|
||||
xattrNoFollow = 0x01
|
||||
)
|
||||
|
||||
func listxattrFollow(path string, dest []byte) (sz int, err error) {
|
||||
return listxattr(path, dest, 0)
|
||||
}
|
||||
|
||||
// Listxattr calls syscall getxattr
|
||||
func Listxattr(path string) ([]string, error) {
|
||||
return listxattrAll(path, listxattrFollow)
|
||||
}
|
||||
|
||||
// Removexattr calls syscall getxattr
|
||||
func Removexattr(path string, attr string) (err error) {
|
||||
return removexattr(path, attr, 0)
|
||||
}
|
||||
|
||||
// Setxattr calls syscall setxattr
|
||||
func Setxattr(path string, attr string, data []byte, flags int) (err error) {
|
||||
return setxattr(path, attr, data, flags)
|
||||
}
|
||||
|
||||
func getxattrFollow(path, attr string, dest []byte) (sz int, err error) {
|
||||
return getxattr(path, attr, dest, 0, 0)
|
||||
}
|
||||
|
||||
// Getxattr calls syscall getxattr
|
||||
func Getxattr(path, attr string) ([]byte, error) {
|
||||
return getxattrAll(path, attr, getxattrFollow)
|
||||
}
|
||||
|
||||
func listxattrNoFollow(path string, dest []byte) (sz int, err error) {
|
||||
return listxattr(path, dest, xattrNoFollow)
|
||||
}
|
||||
|
||||
// LListxattr calls syscall listxattr with XATTR_NOFOLLOW
|
||||
func LListxattr(path string) ([]string, error) {
|
||||
return listxattrAll(path, listxattrNoFollow)
|
||||
}
|
||||
|
||||
// LRemovexattr calls syscall removexattr with XATTR_NOFOLLOW
|
||||
func LRemovexattr(path string, attr string) (err error) {
|
||||
return removexattr(path, attr, xattrNoFollow)
|
||||
}
|
||||
|
||||
// Setxattr calls syscall setxattr with XATTR_NOFOLLOW
|
||||
func LSetxattr(path string, attr string, data []byte, flags int) (err error) {
|
||||
return setxattr(path, attr, data, flags|xattrNoFollow)
|
||||
}
|
||||
|
||||
func getxattrNoFollow(path, attr string, dest []byte) (sz int, err error) {
|
||||
return getxattr(path, attr, dest, 0, xattrNoFollow)
|
||||
}
|
||||
|
||||
// LGetxattr calls syscall getxattr with XATTR_NOFOLLOW
|
||||
func LGetxattr(path, attr string) ([]byte, error) {
|
||||
return getxattrAll(path, attr, getxattrNoFollow)
|
||||
}
|
|
@ -1,111 +0,0 @@
|
|||
// mksyscall.pl -l32 xattr_darwin.go
|
||||
// MACHINE GENERATED BY THE COMMAND ABOVE; DO NOT EDIT
|
||||
|
||||
package sysx
|
||||
|
||||
import (
|
||||
"syscall"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
|
||||
|
||||
func getxattr(path string, attr string, dest []byte, pos int, options int) (sz int, err error) {
|
||||
var _p0 *byte
|
||||
_p0, err = syscall.BytePtrFromString(path)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
var _p1 *byte
|
||||
_p1, err = syscall.BytePtrFromString(attr)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
var _p2 unsafe.Pointer
|
||||
if len(dest) > 0 {
|
||||
_p2 = unsafe.Pointer(&dest[0])
|
||||
} else {
|
||||
_p2 = unsafe.Pointer(&_zero)
|
||||
}
|
||||
r0, _, e1 := syscall.Syscall6(syscall.SYS_GETXATTR, uintptr(unsafe.Pointer(_p0)), uintptr(unsafe.Pointer(_p1)), uintptr(_p2), uintptr(len(dest)), uintptr(pos), uintptr(options))
|
||||
use(unsafe.Pointer(_p0))
|
||||
use(unsafe.Pointer(_p1))
|
||||
sz = int(r0)
|
||||
if e1 != 0 {
|
||||
err = errnoErr(e1)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
|
||||
|
||||
func setxattr(path string, attr string, data []byte, flags int) (err error) {
|
||||
var _p0 *byte
|
||||
_p0, err = syscall.BytePtrFromString(path)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
var _p1 *byte
|
||||
_p1, err = syscall.BytePtrFromString(attr)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
var _p2 unsafe.Pointer
|
||||
if len(data) > 0 {
|
||||
_p2 = unsafe.Pointer(&data[0])
|
||||
} else {
|
||||
_p2 = unsafe.Pointer(&_zero)
|
||||
}
|
||||
_, _, e1 := syscall.Syscall6(syscall.SYS_SETXATTR, uintptr(unsafe.Pointer(_p0)), uintptr(unsafe.Pointer(_p1)), uintptr(_p2), uintptr(len(data)), uintptr(flags), 0)
|
||||
use(unsafe.Pointer(_p0))
|
||||
use(unsafe.Pointer(_p1))
|
||||
if e1 != 0 {
|
||||
err = errnoErr(e1)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
|
||||
|
||||
func removexattr(path string, attr string, options int) (err error) {
|
||||
var _p0 *byte
|
||||
_p0, err = syscall.BytePtrFromString(path)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
var _p1 *byte
|
||||
_p1, err = syscall.BytePtrFromString(attr)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
_, _, e1 := syscall.Syscall(syscall.SYS_REMOVEXATTR, uintptr(unsafe.Pointer(_p0)), uintptr(unsafe.Pointer(_p1)), uintptr(options))
|
||||
use(unsafe.Pointer(_p0))
|
||||
use(unsafe.Pointer(_p1))
|
||||
if e1 != 0 {
|
||||
err = errnoErr(e1)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
|
||||
|
||||
func listxattr(path string, dest []byte, options int) (sz int, err error) {
|
||||
var _p0 *byte
|
||||
_p0, err = syscall.BytePtrFromString(path)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
var _p1 unsafe.Pointer
|
||||
if len(dest) > 0 {
|
||||
_p1 = unsafe.Pointer(&dest[0])
|
||||
} else {
|
||||
_p1 = unsafe.Pointer(&_zero)
|
||||
}
|
||||
r0, _, e1 := syscall.Syscall6(syscall.SYS_LISTXATTR, uintptr(unsafe.Pointer(_p0)), uintptr(_p1), uintptr(len(dest)), uintptr(options), 0, 0)
|
||||
use(unsafe.Pointer(_p0))
|
||||
sz = int(r0)
|
||||
if e1 != 0 {
|
||||
err = errnoErr(e1)
|
||||
}
|
||||
return
|
||||
}
|
|
@ -1,111 +0,0 @@
|
|||
// mksyscall.pl xattr_darwin.go
|
||||
// MACHINE GENERATED BY THE COMMAND ABOVE; DO NOT EDIT
|
||||
|
||||
package sysx
|
||||
|
||||
import (
|
||||
"syscall"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
|
||||
|
||||
func getxattr(path string, attr string, dest []byte, pos int, options int) (sz int, err error) {
|
||||
var _p0 *byte
|
||||
_p0, err = syscall.BytePtrFromString(path)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
var _p1 *byte
|
||||
_p1, err = syscall.BytePtrFromString(attr)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
var _p2 unsafe.Pointer
|
||||
if len(dest) > 0 {
|
||||
_p2 = unsafe.Pointer(&dest[0])
|
||||
} else {
|
||||
_p2 = unsafe.Pointer(&_zero)
|
||||
}
|
||||
r0, _, e1 := syscall.Syscall6(syscall.SYS_GETXATTR, uintptr(unsafe.Pointer(_p0)), uintptr(unsafe.Pointer(_p1)), uintptr(_p2), uintptr(len(dest)), uintptr(pos), uintptr(options))
|
||||
use(unsafe.Pointer(_p0))
|
||||
use(unsafe.Pointer(_p1))
|
||||
sz = int(r0)
|
||||
if e1 != 0 {
|
||||
err = errnoErr(e1)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
|
||||
|
||||
func setxattr(path string, attr string, data []byte, flags int) (err error) {
|
||||
var _p0 *byte
|
||||
_p0, err = syscall.BytePtrFromString(path)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
var _p1 *byte
|
||||
_p1, err = syscall.BytePtrFromString(attr)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
var _p2 unsafe.Pointer
|
||||
if len(data) > 0 {
|
||||
_p2 = unsafe.Pointer(&data[0])
|
||||
} else {
|
||||
_p2 = unsafe.Pointer(&_zero)
|
||||
}
|
||||
_, _, e1 := syscall.Syscall6(syscall.SYS_SETXATTR, uintptr(unsafe.Pointer(_p0)), uintptr(unsafe.Pointer(_p1)), uintptr(_p2), uintptr(len(data)), uintptr(flags), 0)
|
||||
use(unsafe.Pointer(_p0))
|
||||
use(unsafe.Pointer(_p1))
|
||||
if e1 != 0 {
|
||||
err = errnoErr(e1)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
|
||||
|
||||
func removexattr(path string, attr string, options int) (err error) {
|
||||
var _p0 *byte
|
||||
_p0, err = syscall.BytePtrFromString(path)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
var _p1 *byte
|
||||
_p1, err = syscall.BytePtrFromString(attr)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
_, _, e1 := syscall.Syscall(syscall.SYS_REMOVEXATTR, uintptr(unsafe.Pointer(_p0)), uintptr(unsafe.Pointer(_p1)), uintptr(options))
|
||||
use(unsafe.Pointer(_p0))
|
||||
use(unsafe.Pointer(_p1))
|
||||
if e1 != 0 {
|
||||
err = errnoErr(e1)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
|
||||
|
||||
func listxattr(path string, dest []byte, options int) (sz int, err error) {
|
||||
var _p0 *byte
|
||||
_p0, err = syscall.BytePtrFromString(path)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
var _p1 unsafe.Pointer
|
||||
if len(dest) > 0 {
|
||||
_p1 = unsafe.Pointer(&dest[0])
|
||||
} else {
|
||||
_p1 = unsafe.Pointer(&_zero)
|
||||
}
|
||||
r0, _, e1 := syscall.Syscall6(syscall.SYS_LISTXATTR, uintptr(unsafe.Pointer(_p0)), uintptr(_p1), uintptr(len(dest)), uintptr(options), 0, 0)
|
||||
use(unsafe.Pointer(_p0))
|
||||
sz = int(r0)
|
||||
if e1 != 0 {
|
||||
err = errnoErr(e1)
|
||||
}
|
||||
return
|
||||
}
|
|
@ -1,12 +0,0 @@
|
|||
package sysx
|
||||
|
||||
import (
|
||||
"errors"
|
||||
)
|
||||
|
||||
// Initial stub version for FreeBSD. FreeBSD has a different
|
||||
// syscall API from Darwin and Linux for extended attributes;
|
||||
// it is also not widely used. It is not exposed at all by the
|
||||
// Go syscall package, so we need to implement directly eventually.
|
||||
|
||||
var unsupported = errors.New("extended attributes unsupported on FreeBSD")
|
|
@ -1,44 +0,0 @@
|
|||
package sysx
|
||||
|
||||
import "golang.org/x/sys/unix"
|
||||
|
||||
// Listxattr calls syscall listxattr and reads all content
|
||||
// and returns a string array
|
||||
func Listxattr(path string) ([]string, error) {
|
||||
return listxattrAll(path, unix.Listxattr)
|
||||
}
|
||||
|
||||
// Removexattr calls syscall removexattr
|
||||
func Removexattr(path string, attr string) (err error) {
|
||||
return unix.Removexattr(path, attr)
|
||||
}
|
||||
|
||||
// Setxattr calls syscall setxattr
|
||||
func Setxattr(path string, attr string, data []byte, flags int) (err error) {
|
||||
return unix.Setxattr(path, attr, data, flags)
|
||||
}
|
||||
|
||||
// Getxattr calls syscall getxattr
|
||||
func Getxattr(path, attr string) ([]byte, error) {
|
||||
return getxattrAll(path, attr, unix.Getxattr)
|
||||
}
|
||||
|
||||
// LListxattr lists xattrs, not following symlinks
|
||||
func LListxattr(path string) ([]string, error) {
|
||||
return listxattrAll(path, unix.Llistxattr)
|
||||
}
|
||||
|
||||
// LRemovexattr removes an xattr, not following symlinks
|
||||
func LRemovexattr(path string, attr string) (err error) {
|
||||
return unix.Lremovexattr(path, attr)
|
||||
}
|
||||
|
||||
// LSetxattr sets an xattr, not following symlinks
|
||||
func LSetxattr(path string, attr string, data []byte, flags int) (err error) {
|
||||
return unix.Lsetxattr(path, attr, data, flags)
|
||||
}
|
||||
|
||||
// LGetxattr gets an xattr, not following symlinks
|
||||
func LGetxattr(path, attr string) ([]byte, error) {
|
||||
return getxattrAll(path, attr, unix.Lgetxattr)
|
||||
}
|
|
@ -1,7 +0,0 @@
|
|||
package sysx
|
||||
|
||||
import (
|
||||
"errors"
|
||||
)
|
||||
|
||||
var unsupported = errors.New("extended attributes unsupported on OpenBSD")
|
|
@ -1,12 +0,0 @@
|
|||
package sysx
|
||||
|
||||
import (
|
||||
"errors"
|
||||
)
|
||||
|
||||
// Initial stub version for Solaris. Solaris has a different
|
||||
// syscall API from Darwin and Linux for extended attributes;
|
||||
// it is also not widely used. It is not exposed at all by the
|
||||
// Go syscall package, so we need to implement directly eventually.
|
||||
|
||||
var unsupported = errors.New("extended attributes unsupported on Solaris")
|
|
@ -1,7 +1,14 @@
|
|||
// +build freebsd openbsd solaris
|
||||
// +build !linux,!darwin
|
||||
|
||||
package sysx
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"runtime"
|
||||
)
|
||||
|
||||
var unsupported = errors.New("extended attributes unsupported on " + runtime.GOOS)
|
||||
|
||||
// Listxattr calls syscall listxattr and reads all content
|
||||
// and returns a string array
|
||||
func Listxattr(path string) ([]string, error) {
|
||||
|
|
|
@ -10,4 +10,4 @@ github.com/spf13/pflag 4c012f6dcd9546820e378d0bdda4d8fc772cdfea
|
|||
golang.org/x/crypto 9f005a07e0d31d45e6656d241bb5c0f2efd4bc94
|
||||
golang.org/x/net a337091b0525af65de94df2eb7e98bd9962dcbe2
|
||||
golang.org/x/sync 450f422ab23cf9881c94e2db30cac0eb1b7cf80c
|
||||
golang.org/x/sys 665f6529cca930e27b831a0d1dafffbe1c172924
|
||||
golang.org/x/sys 77b0e4315053a57ed2962443614bdb28db152054
|
||||
|
|
Loading…
Reference in New Issue