vendor latest c/common

To include the pkg/netns changes.

Signed-off-by: Paul Holzinger <pholzing@redhat.com>
This commit is contained in:
Paul Holzinger
2024-09-20 13:17:45 +02:00
parent f7be7a365a
commit f6bda786ed
11 changed files with 343 additions and 83 deletions

View File

@ -60,6 +60,13 @@ type CopyOptions struct {
CertDirPath string
// Force layer compression when copying to a `dir` transport destination.
DirForceCompress bool
// ImageListSelection is one of CopySystemImage, CopyAllImages, or
// CopySpecificImages, to control whether, when the source reference is a list,
// copy.Image() copies only an image which matches the current runtime
// environment, or all images which match the supplied reference, or only
// specific images from the source reference.
ImageListSelection copy.ImageListSelection
// Allow contacting registries over HTTP, or HTTPS with failed TLS
// verification. Note that this does not affect other TLS connections.
InsecureSkipTLSVerify types.OptionalBool
@ -206,13 +213,17 @@ func getDockerAuthConfig(name, passwd, creds, idToken string) (*types.DockerAuth
}
}
// NewCopier is a simple, exported wrapper for newCopier
func NewCopier(options *CopyOptions, sc *types.SystemContext) (*copier, error) {
return newCopier(options, sc)
}
// newCopier creates a copier. Note that fields in options *may* overwrite the
// counterparts of the specified system context. Please make sure to call
// `(*copier).close()`.
func (r *Runtime) newCopier(options *CopyOptions) (*copier, error) {
func newCopier(options *CopyOptions, sc *types.SystemContext) (*copier, error) {
c := copier{extendTimeoutSocket: options.extendTimeoutSocket}
c.systemContext = r.systemContextCopy()
c.systemContext = sc
if options.SourceLookupReferenceFunc != nil {
c.sourceLookup = options.SourceLookupReferenceFunc
}
@ -300,6 +311,7 @@ func (r *Runtime) newCopier(options *CopyOptions) (*copier, error) {
c.imageCopyOptions.ProgressInterval = time.Second
}
c.imageCopyOptions.ImageListSelection = options.ImageListSelection
c.imageCopyOptions.ForceCompressionFormat = options.ForceCompressionFormat
c.imageCopyOptions.ForceManifestMIMEType = options.ManifestMIMEType
c.imageCopyOptions.SourceCtx = c.systemContext
@ -325,14 +337,22 @@ func (r *Runtime) newCopier(options *CopyOptions) (*copier, error) {
return &c, nil
}
// close open resources.
func (c *copier) close() error {
// newCopier creates a copier. Note that fields in options *may* overwrite the
// counterparts of the specified system context. Please make sure to call
// `(*copier).close()`.
func (r *Runtime) newCopier(options *CopyOptions) (*copier, error) {
sc := r.systemContextCopy()
return newCopier(options, sc)
}
// Close open resources.
func (c *copier) Close() error {
return c.policyContext.Destroy()
}
// copy the source to the destination. Returns the bytes of the copied
// Copy the source to the destination. Returns the bytes of the copied
// manifest which may be used for digest computation.
func (c *copier) copy(ctx context.Context, source, destination types.ImageReference) ([]byte, error) {
func (c *copier) Copy(ctx context.Context, source, destination types.ImageReference) ([]byte, error) {
logrus.Debugf("Copying source image %s to destination image %s", source.StringWithinTransport(), destination.StringWithinTransport())
// Avoid running out of time when running inside a systemd unit by

View File

@ -108,9 +108,9 @@ func (r *Runtime) Import(ctx context.Context, path string, options *ImportOption
if err != nil {
return "", err
}
defer c.close()
defer c.Close()
if _, err := c.copy(ctx, srcRef, destRef); err != nil {
if _, err := c.Copy(ctx, srcRef, destRef); err != nil {
return "", err
}

View File

@ -7,20 +7,28 @@ import (
"errors"
"fmt"
"maps"
"os"
"path/filepath"
"slices"
"time"
"github.com/containers/common/libimage/define"
"github.com/containers/common/libimage/manifests"
manifesterrors "github.com/containers/common/pkg/manifests"
"github.com/containers/common/pkg/supplemented"
imageCopy "github.com/containers/image/v5/copy"
"github.com/containers/image/v5/docker"
"github.com/containers/image/v5/manifest"
"github.com/containers/image/v5/oci/layout"
"github.com/containers/image/v5/signature"
"github.com/containers/image/v5/transports/alltransports"
"github.com/containers/image/v5/types"
"github.com/containers/storage"
structcopier "github.com/jinzhu/copier"
"github.com/opencontainers/go-digest"
imgspec "github.com/opencontainers/image-spec/specs-go"
imgspecv1 "github.com/opencontainers/image-spec/specs-go/v1"
"github.com/sirupsen/logrus"
)
// NOTE: the abstractions and APIs here are a first step to further merge
@ -101,8 +109,157 @@ func (r *Runtime) lookupManifestList(name string) (*Image, manifests.List, error
return image, list, nil
}
// ConvertToManifestList converts the image into a manifest list if it is not
// already also a list. An error is returned if the conversion fails.
func (i *Image) ConvertToManifestList(ctx context.Context) (*ManifestList, error) {
// If we don't need to do anything, don't do anything.
if list, err := i.ToManifestList(); err == nil || !errors.Is(err, ErrNotAManifestList) {
return list, err
}
// Determine which type we prefer for the new manifest list or image index.
_, imageManifestType, err := i.Manifest(ctx)
if err != nil {
return nil, fmt.Errorf("reading the image's manifest: %w", err)
}
var preferredListType string
switch imageManifestType {
case manifest.DockerV2Schema2MediaType,
manifest.DockerV2Schema1SignedMediaType,
manifest.DockerV2Schema1MediaType,
manifest.DockerV2ListMediaType:
preferredListType = manifest.DockerV2ListMediaType
case imgspecv1.MediaTypeImageManifest, imgspecv1.MediaTypeImageIndex:
preferredListType = imgspecv1.MediaTypeImageIndex
default:
preferredListType = ""
}
// Create a list and add the image's manifest to it. Use OCI format
// for now. If we need to convert it to Docker format, we'll do that
// while copying it.
list := manifests.Create()
if _, err := list.Add(ctx, &i.runtime.systemContext, i.storageReference, false); err != nil {
return nil, fmt.Errorf("generating new image index: %w", err)
}
listBytes, err := list.Serialize(imgspecv1.MediaTypeImageIndex)
if err != nil {
return nil, fmt.Errorf("serializing image index: %w", err)
}
listDigest, err := manifest.Digest(listBytes)
if err != nil {
return nil, fmt.Errorf("digesting image index: %w", err)
}
// Build an OCI layout containing the image index as the only item.
tmp, err := os.MkdirTemp("", "")
if err != nil {
return nil, fmt.Errorf("serializing initial list: %w", err)
}
defer os.RemoveAll(tmp)
// Drop our image index in there.
if err := os.Mkdir(filepath.Join(tmp, imgspecv1.ImageBlobsDir), 0o755); err != nil {
return nil, fmt.Errorf("creating directory for blobs: %w", err)
}
if err := os.Mkdir(filepath.Join(tmp, imgspecv1.ImageBlobsDir, listDigest.Algorithm().String()), 0o755); err != nil {
return nil, fmt.Errorf("creating directory for %s blobs: %w", listDigest.Algorithm().String(), err)
}
listFile := filepath.Join(tmp, imgspecv1.ImageBlobsDir, listDigest.Algorithm().String(), listDigest.Encoded())
if err := os.WriteFile(listFile, listBytes, 0o644); err != nil {
return nil, fmt.Errorf("writing image index for OCI layout: %w", err)
}
// Build the index for the layout.
index := imgspecv1.Index{
Versioned: imgspec.Versioned{
SchemaVersion: 2,
},
MediaType: imgspecv1.MediaTypeImageIndex,
Manifests: []imgspecv1.Descriptor{{
MediaType: imgspecv1.MediaTypeImageIndex,
Digest: listDigest,
Size: int64(len(listBytes)),
}},
}
indexBytes, err := json.Marshal(&index)
if err != nil {
return nil, fmt.Errorf("encoding image index for OCI layout: %w", err)
}
// Write the index for the layout.
indexFile := filepath.Join(tmp, imgspecv1.ImageIndexFile)
if err := os.WriteFile(indexFile, indexBytes, 0o644); err != nil {
return nil, fmt.Errorf("writing top-level index for OCI layout: %w", err)
}
// Write the "why yes, this is an OCI layout" file.
layoutFile := filepath.Join(tmp, imgspecv1.ImageLayoutFile)
layoutBytes, err := json.Marshal(imgspecv1.ImageLayout{Version: imgspecv1.ImageLayoutVersion})
if err != nil {
return nil, fmt.Errorf("encoding image layout structure for OCI layout: %w", err)
}
if err := os.WriteFile(layoutFile, layoutBytes, 0o644); err != nil {
return nil, fmt.Errorf("writing oci-layout file: %w", err)
}
// Build an OCI layout reference to use as a source.
tmpRef, err := layout.NewReference(tmp, "")
if err != nil {
return nil, fmt.Errorf("creating reference to directory: %w", err)
}
bundle := supplemented.Reference(tmpRef, []types.ImageReference{i.storageReference}, imageCopy.CopySystemImage, nil)
// Build a policy that ensures we don't prevent ourselves from reading
// this reference.
signaturePolicy, err := signature.DefaultPolicy(&i.runtime.systemContext)
if err != nil {
return nil, fmt.Errorf("obtaining default signature policy: %w", err)
}
acceptAnything := signature.PolicyTransportScopes{
"": []signature.PolicyRequirement{signature.NewPRInsecureAcceptAnything()},
}
signaturePolicy.Transports[i.storageReference.Transport().Name()] = acceptAnything
signaturePolicy.Transports[tmpRef.Transport().Name()] = acceptAnything
policyContext, err := signature.NewPolicyContext(signaturePolicy)
if err != nil {
return nil, fmt.Errorf("creating new signature policy context: %w", err)
}
defer func() {
if err2 := policyContext.Destroy(); err2 != nil {
logrus.Errorf("Destroying signature policy context: %v", err2)
}
}()
// Copy from the OCI layout into the same image record, so that it gets
// both its own manifest and the image index.
copyOptions := imageCopy.Options{
ForceManifestMIMEType: imageManifestType,
}
if _, err := imageCopy.Image(ctx, policyContext, i.storageReference, bundle, &copyOptions); err != nil {
return nil, fmt.Errorf("writing updates to image: %w", err)
}
// Now explicitly write the list's manifest to the image as its "main"
// manifest.
if _, err := list.SaveToImage(i.runtime.store, i.ID(), i.storageImage.Names, preferredListType); err != nil {
return nil, fmt.Errorf("saving image index: %w", err)
}
// Reload the record.
if err = i.reload(); err != nil {
return nil, fmt.Errorf("reloading image record: %w", err)
}
mList, err := i.runtime.LookupManifestList(i.storageImage.ID)
if err != nil {
return nil, fmt.Errorf("looking up new manifest list: %w", err)
}
return mList, nil
}
// ToManifestList converts the image into a manifest list. An error is thrown
// if the image is no manifest list.
// if the image is not a manifest list.
func (i *Image) ToManifestList() (*ManifestList, error) {
list, err := i.getManifestList()
if err != nil {
@ -194,6 +351,9 @@ func (m *ManifestList) reload() error {
// getManifestList is a helper to obtain a manifest list
func (i *Image) getManifestList() (manifests.List, error) {
_, list, err := manifests.LoadFromImage(i.runtime.store, i.ID())
if errors.Is(err, manifesterrors.ErrManifestTypeNotSupported) {
err = fmt.Errorf("%s: %w", err.Error(), ErrNotAManifestList)
}
return list, err
}
@ -636,7 +796,7 @@ func (m *ManifestList) Push(ctx context.Context, destination string, options *Ma
if err != nil {
return "", err
}
defer copier.close()
defer copier.Close()
pushOptions := manifests.PushOptions{
AddCompression: options.AddCompression,

View File

@ -342,8 +342,7 @@ func (l *list) Reference(store storage.Store, multiple cp.ImageListSelection, in
}
}
// write the index that refers to this one artifact image
tag := "latest"
indexFile := filepath.Join(tmp, "index.json")
indexFile := filepath.Join(tmp, v1.ImageIndexFile)
index := v1.Index{
Versioned: imgspec.Versioned{
SchemaVersion: 2,
@ -353,9 +352,6 @@ func (l *list) Reference(store storage.Store, multiple cp.ImageListSelection, in
MediaType: v1.MediaTypeImageManifest,
Digest: artifactManifestDigest,
Size: int64(len(contents)),
Annotations: map[string]string{
v1.AnnotationRefName: tag,
},
}},
}
indexBytes, err := json.Marshal(&index)
@ -366,12 +362,16 @@ func (l *list) Reference(store storage.Store, multiple cp.ImageListSelection, in
return nil, fmt.Errorf("writing image index for OCI layout: %w", err)
}
// write the layout file
layoutFile := filepath.Join(tmp, "oci-layout")
if err := os.WriteFile(layoutFile, []byte(`{"imageLayoutVersion": "1.0.0"}`), 0o644); err != nil {
layoutFile := filepath.Join(tmp, v1.ImageLayoutFile)
layoutBytes, err := json.Marshal(v1.ImageLayout{Version: v1.ImageLayoutVersion})
if err != nil {
return nil, fmt.Errorf("encoding image layout for OCI layout: %w", err)
}
if err := os.WriteFile(layoutFile, layoutBytes, 0o644); err != nil {
return nil, fmt.Errorf("writing oci-layout file: %w", err)
}
// build the reference to this artifact image's oci layout
ref, err := ocilayout.NewReference(tmp, tag)
ref, err := ocilayout.NewReference(tmp, "")
if err != nil {
return nil, fmt.Errorf("creating ImageReference for artifact with files %q: %w", symlinkedFiles, err)
}
@ -676,14 +676,14 @@ func (l *list) Add(ctx context.Context, sys *types.SystemContext, ref types.Imag
// This should provide for all of the ways to construct a manifest outlined in
// https://github.com/opencontainers/image-spec/blob/main/manifest.md#guidelines-for-artifact-usage
// * no blobs → set ManifestArtifactType
// * blobs, no configuration → set ManifestArtifactType and possibly LayerMediaType, and provide file names
// * blobs and configuration → set ManifestArtifactType, possibly LayerMediaType, and ConfigDescriptor, and provide file names
// - no blobs → set ManifestArtifactType
// - blobs, no configuration → set ManifestArtifactType and possibly LayerMediaType, and provide file names
// - blobs and configuration → set ManifestArtifactType, possibly LayerMediaType, and ConfigDescriptor, and provide file names
//
// The older style of describing artifacts:
// * leave ManifestArtifactType blank
// * specify a zero-length application/vnd.oci.image.config.v1+json config blob
// * set LayerMediaType to a custom type
// - leave ManifestArtifactType blank
// - specify a zero-length application/vnd.oci.image.config.v1+json config blob
// - set LayerMediaType to a custom type
//
// When reading data produced elsewhere, note that newer tooling will produce
// manifests with ArtifactType set. If the manifest's ArtifactType is not set,

View File

@ -235,7 +235,7 @@ func (r *Runtime) copyFromDefault(ctx context.Context, ref types.ImageReference,
if err != nil {
return nil, err
}
defer c.close()
defer c.Close()
// Figure out a name for the storage destination.
var storageName, imageName string
@ -321,7 +321,7 @@ func (r *Runtime) copyFromDefault(ctx context.Context, ref types.ImageReference,
return nil, fmt.Errorf("parsing %q: %w", storageName, err)
}
_, err = c.copy(ctx, ref, destRef)
_, err = c.Copy(ctx, ref, destRef)
return []string{imageName}, err
}
@ -391,7 +391,7 @@ func (r *Runtime) copyFromDockerArchiveReaderReference(ctx context.Context, read
if err != nil {
return nil, err
}
defer c.close()
defer c.Close()
// Get a slice of storage references we can copy.
references, destNames, err := r.storageReferencesReferencesFromArchiveReader(ctx, readerRef, reader)
@ -401,7 +401,7 @@ func (r *Runtime) copyFromDockerArchiveReaderReference(ctx context.Context, read
// Now copy all of the images. Use readerRef for performance.
for _, destRef := range references {
if _, err := c.copy(ctx, readerRef, destRef); err != nil {
if _, err := c.Copy(ctx, readerRef, destRef); err != nil {
return nil, err
}
}
@ -640,7 +640,7 @@ func (r *Runtime) copySingleImageFromRegistry(ctx context.Context, imageName str
if err != nil {
return nil, err
}
defer c.close()
defer c.Close()
var pullErrors []error
for _, candidate := range resolved.PullCandidates {
@ -678,7 +678,7 @@ func (r *Runtime) copySingleImageFromRegistry(ctx context.Context, imageName str
}
}
var manifestBytes []byte
if manifestBytes, err = c.copy(ctx, srcRef, destRef); err != nil {
if manifestBytes, err = c.Copy(ctx, srcRef, destRef); err != nil {
logrus.Debugf("Error pulling candidate %s: %v", candidateString, err)
pullErrors = append(pullErrors, err)
continue

View File

@ -114,7 +114,7 @@ func (r *Runtime) Push(ctx context.Context, source, destination string, options
return nil, err
}
defer c.close()
defer c.Close()
return c.copy(ctx, srcRef, destRef)
return c.Copy(ctx, srcRef, destRef)
}

View File

@ -123,9 +123,9 @@ func (r *Runtime) saveSingleImage(ctx context.Context, name, format, path string
if err != nil {
return err
}
defer c.close()
defer c.Close()
_, err = c.copy(ctx, srcRef, destRef)
_, err = c.Copy(ctx, srcRef, destRef)
return err
}
@ -208,7 +208,7 @@ func (r *Runtime) saveDockerArchive(ctx context.Context, names []string, path st
if err != nil {
return err
}
defer c.close()
defer c.Close()
destRef, err := writer.NewReference(nil)
if err != nil {
@ -220,7 +220,7 @@ func (r *Runtime) saveDockerArchive(ctx context.Context, names []string, path st
return err
}
if _, err := c.copy(ctx, srcRef, destRef); err != nil {
if _, err := c.Copy(ctx, srcRef, destRef); err != nil {
return err
}
}

View File

@ -40,6 +40,8 @@ import (
// threadNsPath is the /proc path to the current netns handle for the current thread
const threadNsPath = "/proc/thread-self/ns/net"
var errNoFreeName = errors.New("failed to find free netns path name")
// GetNSRunDir returns the dir of where to create the netNS. When running
// rootless, it needs to be at a location writable by user.
func GetNSRunDir() (string, error) {
@ -60,14 +62,26 @@ func NewNSAtPath(nsPath string) (ns.NetNS, error) {
// NewNS creates a new persistent (bind-mounted) network namespace and returns
// an object representing that namespace, without switching to it.
func NewNS() (ns.NetNS, error) {
nsRunDir, err := GetNSRunDir()
if err != nil {
return nil, err
}
// Create the directory for mounting network namespaces
// This needs to be a shared mountpoint in case it is mounted in to
// other namespaces (containers)
err = makeNetnsDir(nsRunDir)
if err != nil {
return nil, err
}
for range 10000 {
b := make([]byte, 16)
_, err := rand.Reader.Read(b)
nsName, err := getRandomNetnsName()
if err != nil {
return nil, fmt.Errorf("failed to generate random netns name: %v", err)
return nil, err
}
nsName := fmt.Sprintf("netns-%x-%x-%x-%x-%x", b[0:4], b[4:6], b[6:8], b[8:10], b[10:])
ns, err := NewNSWithName(nsName)
nsPath := path.Join(nsRunDir, nsName)
ns, err := newNSPath(nsPath)
if err == nil {
return ns, nil
}
@ -77,62 +91,128 @@ func NewNS() (ns.NetNS, error) {
}
return nil, err
}
return nil, errors.New("failed to find free netns path name")
return nil, errNoFreeName
}
// NewNSWithName creates a new persistent (bind-mounted) network namespace and returns
// an object representing that namespace, without switching to it.
func NewNSWithName(name string) (ns.NetNS, error) {
// NewNSFrom creates a persistent (bind-mounted) network namespace from the
// given netns path, i.e. /proc/<pid>/ns/net, and returns the new full path to
// the bind mounted file in the netns run dir.
func NewNSFrom(fromNetns string) (string, error) {
nsRunDir, err := GetNSRunDir()
if err != nil {
return nil, err
return "", err
}
// Create the directory for mounting network namespaces
// This needs to be a shared mountpoint in case it is mounted in to
// other namespaces (containers)
err = os.MkdirAll(nsRunDir, 0o755)
err = makeNetnsDir(nsRunDir)
if err != nil {
return nil, err
return "", err
}
// Remount the namespace directory shared. This will fail if it is not
// already a mountpoint, so bind-mount it on to itself to "upgrade" it
// to a mountpoint.
for range 10000 {
nsName, err := getRandomNetnsName()
if err != nil {
return "", err
}
nsPath := filepath.Join(nsRunDir, nsName)
// create an empty file to use as at the mount point
err = createNetnsFile(nsPath)
if err != nil {
// retry when the name already exists
if errors.Is(err, os.ErrExist) {
continue
}
return "", err
}
err = unix.Mount(fromNetns, nsPath, "none", unix.MS_BIND|unix.MS_SHARED|unix.MS_REC, "")
if err != nil {
// Do not leak the ns on errors
_ = os.RemoveAll(nsPath)
return "", fmt.Errorf("failed to bind mount ns at %s: %v", nsPath, err)
}
return nsPath, nil
}
return "", errNoFreeName
}
func getRandomNetnsName() (string, error) {
b := make([]byte, 16)
_, err := rand.Reader.Read(b)
if err != nil {
return "", fmt.Errorf("failed to generate random netns name: %v", err)
}
return fmt.Sprintf("netns-%x-%x-%x-%x-%x", b[0:4], b[4:6], b[6:8], b[8:10], b[10:]), nil
}
func makeNetnsDir(nsRunDir string) error {
err := os.MkdirAll(nsRunDir, 0o755)
if err != nil {
return err
}
// Important, the bind mount setup is racy if two process try to set it up in parallel.
// This can have very bad consequences because we end up with two duplicated mounts
// for the netns file that then might have a different parent mounts.
// Also because as root netns dir is also created by ip netns we should not race against them.
// Use a lock on the netns dir like they do, compare the iproute2 ip netns add code.
// https://github.com/iproute2/iproute2/blob/8b9d9ea42759c91d950356ca43930a975d0c352b/ip/ipnetns.c#L806-L815
dirFD, err := unix.Open(nsRunDir, unix.O_RDONLY|unix.O_DIRECTORY|unix.O_CLOEXEC, 0)
if err != nil {
return &os.PathError{Op: "open", Path: nsRunDir, Err: err}
}
// closing the fd will also unlock so we do not have to call flock(fd,LOCK_UN)
defer unix.Close(dirFD)
err = unix.Flock(dirFD, unix.LOCK_EX)
if err != nil {
return fmt.Errorf("failed to lock %s dir: %w", nsRunDir, err)
}
// Remount the namespace directory shared. This will fail with EINVAL
// if it is not already a mountpoint, so bind-mount it on to itself
// to "upgrade" it to a mountpoint.
err = unix.Mount("", nsRunDir, "none", unix.MS_SHARED|unix.MS_REC, "")
if err == nil {
return nil
}
if err != unix.EINVAL {
return fmt.Errorf("mount --make-rshared %s failed: %q", nsRunDir, err)
}
// Recursively remount /run/netns on itself. The recursive flag is
// so that any existing netns bindmounts are carried over.
err = unix.Mount(nsRunDir, nsRunDir, "none", unix.MS_BIND|unix.MS_REC, "")
if err != nil {
return fmt.Errorf("mount --rbind %s %s failed: %q", nsRunDir, nsRunDir, err)
}
// Now we can make it shared
err = unix.Mount("", nsRunDir, "none", unix.MS_SHARED|unix.MS_REC, "")
if err != nil {
if err != unix.EINVAL {
return nil, fmt.Errorf("mount --make-rshared %s failed: %q", nsRunDir, err)
}
// Recursively remount /run/netns on itself. The recursive flag is
// so that any existing netns bindmounts are carried over.
err = unix.Mount(nsRunDir, nsRunDir, "none", unix.MS_BIND|unix.MS_REC, "")
if err != nil {
return nil, fmt.Errorf("mount --rbind %s %s failed: %q", nsRunDir, nsRunDir, err)
}
// Now we can make it shared
err = unix.Mount("", nsRunDir, "none", unix.MS_SHARED|unix.MS_REC, "")
if err != nil {
return nil, fmt.Errorf("mount --make-rshared %s failed: %q", nsRunDir, err)
}
return fmt.Errorf("mount --make-rshared %s failed: %q", nsRunDir, err)
}
nsPath := path.Join(nsRunDir, name)
return newNSPath(nsPath)
return nil
}
// createNetnsFile created the file with O_EXCL to ensure there are no conflicts with others
// Callers should check for ErrExist and loop over it to find a free file.
func createNetnsFile(path string) error {
mountPointFd, err := os.OpenFile(path, os.O_RDWR|os.O_CREATE|os.O_EXCL, 0o600)
if err != nil {
return err
}
return mountPointFd.Close()
}
func newNSPath(nsPath string) (ns.NetNS, error) {
// create an empty file at the mount point
mountPointFd, err := os.OpenFile(nsPath, os.O_RDWR|os.O_CREATE|os.O_EXCL, 0o600)
// create an empty file to use as at the mount point
err := createNetnsFile(nsPath)
if err != nil {
return nil, err
}
if err := mountPointFd.Close(); err != nil {
return nil, err
}
// Ensure the mount point is cleaned up on errors; if the namespace
// was successfully mounted this will have no effect because the file
// is in-use