mirror of
				https://gitea.com/Lydanne/buildx.git
				synced 2025-11-04 18:13:42 +08:00 
			
		
		
		
	
		
			
				
	
	
		
			662 lines
		
	
	
		
			18 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			662 lines
		
	
	
		
			18 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
/*
 | 
						|
   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 continuity
 | 
						|
 | 
						|
import (
 | 
						|
	"bytes"
 | 
						|
	"errors"
 | 
						|
	"fmt"
 | 
						|
	"io"
 | 
						|
	"os"
 | 
						|
	"path/filepath"
 | 
						|
	"strings"
 | 
						|
 | 
						|
	"github.com/containerd/continuity/devices"
 | 
						|
	driverpkg "github.com/containerd/continuity/driver"
 | 
						|
	"github.com/containerd/continuity/pathdriver"
 | 
						|
 | 
						|
	"github.com/opencontainers/go-digest"
 | 
						|
)
 | 
						|
 | 
						|
var (
 | 
						|
	// ErrNotFound represents the resource not found
 | 
						|
	ErrNotFound = fmt.Errorf("not found")
 | 
						|
	// ErrNotSupported represents the resource not supported
 | 
						|
	ErrNotSupported = fmt.Errorf("not supported")
 | 
						|
)
 | 
						|
 | 
						|
// Context represents a file system context for accessing resources. The
 | 
						|
// responsibility of the context is to convert system specific resources to
 | 
						|
// generic Resource objects. Most of this is safe path manipulation, as well
 | 
						|
// as extraction of resource details.
 | 
						|
type Context interface {
 | 
						|
	Apply(Resource) error
 | 
						|
	Verify(Resource) error
 | 
						|
	Resource(string, os.FileInfo) (Resource, error)
 | 
						|
	Walk(filepath.WalkFunc) error
 | 
						|
}
 | 
						|
 | 
						|
// SymlinkPath is intended to give the symlink target value
 | 
						|
// in a root context. Target and linkname are absolute paths
 | 
						|
// 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
 | 
						|
	PathDriver pathdriver.PathDriver
 | 
						|
	Provider   ContentProvider
 | 
						|
}
 | 
						|
 | 
						|
// context represents a file system context for accessing resources.
 | 
						|
// Generally, all path qualified access and system considerations should land
 | 
						|
// here.
 | 
						|
type context struct {
 | 
						|
	driver     driverpkg.Driver
 | 
						|
	pathDriver pathdriver.PathDriver
 | 
						|
	root       string
 | 
						|
	digester   Digester
 | 
						|
	provider   ContentProvider
 | 
						|
}
 | 
						|
 | 
						|
// NewContext returns a Context associated with root. The default driver will
 | 
						|
// be used, as returned by NewDriver.
 | 
						|
func NewContext(root string) (Context, error) {
 | 
						|
	return NewContextWithOptions(root, ContextOptions{})
 | 
						|
}
 | 
						|
 | 
						|
// NewContextWithOptions returns a Context associate with the root.
 | 
						|
func NewContextWithOptions(root string, options ContextOptions) (Context, error) {
 | 
						|
	// normalize to absolute path
 | 
						|
	pathDriver := options.PathDriver
 | 
						|
	if pathDriver == nil {
 | 
						|
		pathDriver = pathdriver.LocalPathDriver
 | 
						|
	}
 | 
						|
 | 
						|
	root = pathDriver.FromSlash(root)
 | 
						|
	root, err := pathDriver.Abs(pathDriver.Clean(root))
 | 
						|
	if err != nil {
 | 
						|
		return nil, err
 | 
						|
	}
 | 
						|
 | 
						|
	driver := options.Driver
 | 
						|
	if driver == nil {
 | 
						|
		driver, err = driverpkg.NewSystemDriver()
 | 
						|
		if err != nil {
 | 
						|
			return nil, err
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	digester := options.Digester
 | 
						|
	if digester == nil {
 | 
						|
		digester = simpleDigester{digest.Canonical}
 | 
						|
	}
 | 
						|
 | 
						|
	// Check the root directory. Need to be a little careful here. We are
 | 
						|
	// allowing a link for now, but this may have odd behavior when
 | 
						|
	// canonicalizing paths. As long as all files are opened through the link
 | 
						|
	// path, this should be okay.
 | 
						|
	fi, err := driver.Stat(root)
 | 
						|
	if err != nil {
 | 
						|
		return nil, err
 | 
						|
	}
 | 
						|
 | 
						|
	if !fi.IsDir() {
 | 
						|
		return nil, &os.PathError{Op: "NewContext", Path: root, Err: os.ErrInvalid}
 | 
						|
	}
 | 
						|
 | 
						|
	return &context{
 | 
						|
		root:       root,
 | 
						|
		driver:     driver,
 | 
						|
		pathDriver: pathDriver,
 | 
						|
		digester:   digester,
 | 
						|
		provider:   options.Provider,
 | 
						|
	}, nil
 | 
						|
}
 | 
						|
 | 
						|
// Resource returns the resource as path p, populating the entry with info
 | 
						|
// from fi. The path p should be the path of the resource in the context,
 | 
						|
// typically obtained through Walk or from the value of Resource.Path(). If fi
 | 
						|
// is nil, it will be resolved.
 | 
						|
func (c *context) Resource(p string, fi os.FileInfo) (Resource, error) {
 | 
						|
	fp, err := c.fullpath(p)
 | 
						|
	if err != nil {
 | 
						|
		return nil, err
 | 
						|
	}
 | 
						|
 | 
						|
	if fi == nil {
 | 
						|
		fi, err = c.driver.Lstat(fp)
 | 
						|
		if err != nil {
 | 
						|
			return nil, err
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	base, err := newBaseResource(p, fi)
 | 
						|
	if err != nil {
 | 
						|
		return nil, err
 | 
						|
	}
 | 
						|
 | 
						|
	base.xattrs, err = c.resolveXAttrs(fp, fi, base)
 | 
						|
	if err != nil && !errors.Is(err, ErrNotSupported) {
 | 
						|
		return nil, err
 | 
						|
	}
 | 
						|
 | 
						|
	// TODO(stevvooe): Handle windows alternate data streams.
 | 
						|
 | 
						|
	if fi.Mode().IsRegular() {
 | 
						|
		dgst, err := c.digest(p)
 | 
						|
		if err != nil {
 | 
						|
			return nil, err
 | 
						|
		}
 | 
						|
 | 
						|
		return newRegularFile(*base, base.paths, fi.Size(), dgst)
 | 
						|
	}
 | 
						|
 | 
						|
	if fi.Mode().IsDir() {
 | 
						|
		return newDirectory(*base)
 | 
						|
	}
 | 
						|
 | 
						|
	if fi.Mode()&os.ModeSymlink != 0 {
 | 
						|
		// We handle relative links vs absolute links by including a
 | 
						|
		// beginning slash for absolute links. Effectively, the bundle's
 | 
						|
		// root is treated as the absolute link anchor.
 | 
						|
		target, err := c.driver.Readlink(fp)
 | 
						|
		if err != nil {
 | 
						|
			return nil, err
 | 
						|
		}
 | 
						|
 | 
						|
		return newSymLink(*base, target)
 | 
						|
	}
 | 
						|
 | 
						|
	if fi.Mode()&os.ModeNamedPipe != 0 {
 | 
						|
		return newNamedPipe(*base, base.paths)
 | 
						|
	}
 | 
						|
 | 
						|
	if fi.Mode()&os.ModeDevice != 0 {
 | 
						|
		deviceDriver, ok := c.driver.(driverpkg.DeviceInfoDriver)
 | 
						|
		if !ok {
 | 
						|
			return nil, fmt.Errorf("device extraction is not supported for %s: %w", fp, ErrNotSupported)
 | 
						|
		}
 | 
						|
 | 
						|
		// character and block devices merely need to recover the
 | 
						|
		// major/minor device number.
 | 
						|
		major, minor, err := deviceDriver.DeviceInfo(fi)
 | 
						|
		if err != nil {
 | 
						|
			return nil, err
 | 
						|
		}
 | 
						|
 | 
						|
		return newDevice(*base, base.paths, major, minor)
 | 
						|
	}
 | 
						|
 | 
						|
	return nil, fmt.Errorf("%q (%v) is not supported: %w", fp, fi.Mode(), ErrNotFound)
 | 
						|
}
 | 
						|
 | 
						|
func (c *context) verifyMetadata(resource, target Resource) error {
 | 
						|
	if target.Mode() != resource.Mode() {
 | 
						|
		return fmt.Errorf("resource %q has incorrect mode: %v != %v", target.Path(), target.Mode(), resource.Mode())
 | 
						|
	}
 | 
						|
 | 
						|
	if target.UID() != resource.UID() {
 | 
						|
		return fmt.Errorf("unexpected uid for %q: %v != %v", target.Path(), target.UID(), resource.GID())
 | 
						|
	}
 | 
						|
 | 
						|
	if target.GID() != resource.GID() {
 | 
						|
		return fmt.Errorf("unexpected gid for %q: %v != %v", target.Path(), target.GID(), target.GID())
 | 
						|
	}
 | 
						|
 | 
						|
	if xattrer, ok := resource.(XAttrer); ok {
 | 
						|
		txattrer, tok := target.(XAttrer)
 | 
						|
		if !tok {
 | 
						|
			return fmt.Errorf("resource %q has xattrs but target does not support them", resource.Path())
 | 
						|
		}
 | 
						|
 | 
						|
		// For xattrs, only ensure that we have those defined in the resource
 | 
						|
		// and their values match. We can ignore other xattrs. In other words,
 | 
						|
		// we only verify that target has the subset defined by resource.
 | 
						|
		txattrs := txattrer.XAttrs()
 | 
						|
		for attr, value := range xattrer.XAttrs() {
 | 
						|
			tvalue, ok := txattrs[attr]
 | 
						|
			if !ok {
 | 
						|
				return fmt.Errorf("resource %q target missing xattr %q", resource.Path(), attr)
 | 
						|
			}
 | 
						|
 | 
						|
			if !bytes.Equal(value, tvalue) {
 | 
						|
				return fmt.Errorf("xattr %q value differs for resource %q", attr, resource.Path())
 | 
						|
			}
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	switch r := resource.(type) {
 | 
						|
	case RegularFile:
 | 
						|
		// TODO(stevvooe): Another reason to use a record-based approach. We
 | 
						|
		// have to do another type switch to get this to work. This could be
 | 
						|
		// fixed with an Equal function, but let's study this a little more to
 | 
						|
		// be sure.
 | 
						|
		t, ok := target.(RegularFile)
 | 
						|
		if !ok {
 | 
						|
			return fmt.Errorf("resource %q target not a regular file", r.Path())
 | 
						|
		}
 | 
						|
 | 
						|
		if t.Size() != r.Size() {
 | 
						|
			return fmt.Errorf("resource %q target has incorrect size: %v != %v", t.Path(), t.Size(), r.Size())
 | 
						|
		}
 | 
						|
	case Directory:
 | 
						|
		t, ok := target.(Directory)
 | 
						|
		if !ok {
 | 
						|
			return fmt.Errorf("resource %q target not a directory", t.Path())
 | 
						|
		}
 | 
						|
	case SymLink:
 | 
						|
		t, ok := target.(SymLink)
 | 
						|
		if !ok {
 | 
						|
			return fmt.Errorf("resource %q target not a symlink", t.Path())
 | 
						|
		}
 | 
						|
 | 
						|
		if t.Target() != r.Target() {
 | 
						|
			return fmt.Errorf("resource %q target has mismatched target: %q != %q", t.Path(), t.Target(), r.Target())
 | 
						|
		}
 | 
						|
	case Device:
 | 
						|
		t, ok := target.(Device)
 | 
						|
		if !ok {
 | 
						|
			return fmt.Errorf("resource %q is not a device", t.Path())
 | 
						|
		}
 | 
						|
 | 
						|
		if t.Major() != r.Major() || t.Minor() != r.Minor() {
 | 
						|
			return fmt.Errorf("resource %q has mismatched major/minor numbers: %d,%d != %d,%d", t.Path(), t.Major(), t.Minor(), r.Major(), r.Minor())
 | 
						|
		}
 | 
						|
	case NamedPipe:
 | 
						|
		t, ok := target.(NamedPipe)
 | 
						|
		if !ok {
 | 
						|
			return fmt.Errorf("resource %q is not a named pipe", t.Path())
 | 
						|
		}
 | 
						|
	default:
 | 
						|
		return fmt.Errorf("cannot verify resource: %v", resource)
 | 
						|
	}
 | 
						|
 | 
						|
	return nil
 | 
						|
}
 | 
						|
 | 
						|
// Verify the resource in the context. An error will be returned a discrepancy
 | 
						|
// is found.
 | 
						|
func (c *context) Verify(resource Resource) error {
 | 
						|
	fp, err := c.fullpath(resource.Path())
 | 
						|
	if err != nil {
 | 
						|
		return err
 | 
						|
	}
 | 
						|
 | 
						|
	fi, err := c.driver.Lstat(fp)
 | 
						|
	if err != nil {
 | 
						|
		return err
 | 
						|
	}
 | 
						|
 | 
						|
	target, err := c.Resource(resource.Path(), fi)
 | 
						|
	if err != nil {
 | 
						|
		return err
 | 
						|
	}
 | 
						|
 | 
						|
	if target.Path() != resource.Path() {
 | 
						|
		return fmt.Errorf("resource paths do not match: %q != %q", target.Path(), resource.Path())
 | 
						|
	}
 | 
						|
 | 
						|
	if err := c.verifyMetadata(resource, target); err != nil {
 | 
						|
		return err
 | 
						|
	}
 | 
						|
 | 
						|
	if h, isHardlinkable := resource.(Hardlinkable); isHardlinkable {
 | 
						|
		hardlinkKey, err := newHardlinkKey(fi)
 | 
						|
		if err == errNotAHardLink {
 | 
						|
			if len(h.Paths()) > 1 {
 | 
						|
				return fmt.Errorf("%q is not a hardlink to %q", h.Paths()[1], resource.Path())
 | 
						|
			}
 | 
						|
		} else if err != nil {
 | 
						|
			return err
 | 
						|
		}
 | 
						|
 | 
						|
		for _, path := range h.Paths()[1:] {
 | 
						|
			fpLink, err := c.fullpath(path)
 | 
						|
			if err != nil {
 | 
						|
				return err
 | 
						|
			}
 | 
						|
 | 
						|
			fiLink, err := c.driver.Lstat(fpLink)
 | 
						|
			if err != nil {
 | 
						|
				return err
 | 
						|
			}
 | 
						|
 | 
						|
			targetLink, err := c.Resource(path, fiLink)
 | 
						|
			if err != nil {
 | 
						|
				return err
 | 
						|
			}
 | 
						|
 | 
						|
			hardlinkKeyLink, err := newHardlinkKey(fiLink)
 | 
						|
			if err != nil {
 | 
						|
				return err
 | 
						|
			}
 | 
						|
 | 
						|
			if hardlinkKeyLink != hardlinkKey {
 | 
						|
				return fmt.Errorf("%q is not a hardlink to %q", path, resource.Path())
 | 
						|
			}
 | 
						|
 | 
						|
			if err := c.verifyMetadata(resource, targetLink); err != nil {
 | 
						|
				return err
 | 
						|
			}
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	switch r := resource.(type) {
 | 
						|
	case RegularFile:
 | 
						|
		t, ok := target.(RegularFile)
 | 
						|
		if !ok {
 | 
						|
			return fmt.Errorf("resource %q target not a regular file", r.Path())
 | 
						|
		}
 | 
						|
 | 
						|
		// TODO(stevvooe): This may need to get a little more sophisticated
 | 
						|
		// for digest comparison. We may want to actually calculate the
 | 
						|
		// provided digests, rather than the implementations having an
 | 
						|
		// overlap.
 | 
						|
		if !digestsMatch(t.Digests(), r.Digests()) {
 | 
						|
			return fmt.Errorf("digests for resource %q do not match: %v != %v", t.Path(), t.Digests(), r.Digests())
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	return nil
 | 
						|
}
 | 
						|
 | 
						|
func (c *context) checkoutFile(fp string, rf RegularFile) error {
 | 
						|
	if c.provider == nil {
 | 
						|
		return fmt.Errorf("no file provider")
 | 
						|
	}
 | 
						|
	var (
 | 
						|
		r   io.ReadCloser
 | 
						|
		err error
 | 
						|
	)
 | 
						|
	for _, dgst := range rf.Digests() {
 | 
						|
		r, err = c.provider.Reader(dgst)
 | 
						|
		if err == nil {
 | 
						|
			break
 | 
						|
		}
 | 
						|
	}
 | 
						|
	if err != nil {
 | 
						|
		return fmt.Errorf("file content could not be provided: %w", err)
 | 
						|
	}
 | 
						|
	defer r.Close()
 | 
						|
 | 
						|
	return atomicWriteFile(fp, r, rf.Size(), rf.Mode())
 | 
						|
}
 | 
						|
 | 
						|
// Apply the resource to the contexts. An error will be returned if the
 | 
						|
// operation fails. Depending on the resource type, the resource may be
 | 
						|
// created. For resource that cannot be resolved, an error will be returned.
 | 
						|
func (c *context) Apply(resource Resource) error {
 | 
						|
	fp, err := c.fullpath(resource.Path())
 | 
						|
	if err != nil {
 | 
						|
		return err
 | 
						|
	}
 | 
						|
 | 
						|
	if !strings.HasPrefix(fp, c.root) {
 | 
						|
		return fmt.Errorf("resource %v escapes root", resource)
 | 
						|
	}
 | 
						|
 | 
						|
	chmod := true
 | 
						|
	fi, err := c.driver.Lstat(fp)
 | 
						|
	if err != nil {
 | 
						|
		if !os.IsNotExist(err) {
 | 
						|
			return err
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	switch r := resource.(type) {
 | 
						|
	case RegularFile:
 | 
						|
		if fi == nil {
 | 
						|
			if err := c.checkoutFile(fp, r); err != nil {
 | 
						|
				return fmt.Errorf("error checking out file %q: %w", resource.Path(), err)
 | 
						|
			}
 | 
						|
			chmod = false
 | 
						|
		} else {
 | 
						|
			if !fi.Mode().IsRegular() {
 | 
						|
				return fmt.Errorf("file %q should be a regular file, but is not", resource.Path())
 | 
						|
			}
 | 
						|
			if fi.Size() != r.Size() {
 | 
						|
				if err := c.checkoutFile(fp, r); err != nil {
 | 
						|
					return fmt.Errorf("error checking out file %q: %w", resource.Path(), err)
 | 
						|
				}
 | 
						|
			} else {
 | 
						|
				for _, dgst := range r.Digests() {
 | 
						|
					f, err := os.Open(fp)
 | 
						|
					if err != nil {
 | 
						|
						return fmt.Errorf("failure opening file for read %q: %w", resource.Path(), err)
 | 
						|
					}
 | 
						|
					compared, err := dgst.Algorithm().FromReader(f)
 | 
						|
					if err == nil && dgst != compared {
 | 
						|
						if err := c.checkoutFile(fp, r); err != nil {
 | 
						|
							return fmt.Errorf("error checking out file %q: %w", resource.Path(), err)
 | 
						|
						}
 | 
						|
						break
 | 
						|
					}
 | 
						|
					if err1 := f.Close(); err == nil {
 | 
						|
						err = err1
 | 
						|
					}
 | 
						|
					if err != nil {
 | 
						|
						return fmt.Errorf("error checking digest for %q: %w", resource.Path(), err)
 | 
						|
					}
 | 
						|
				}
 | 
						|
			}
 | 
						|
		}
 | 
						|
	case Directory:
 | 
						|
		if fi == nil {
 | 
						|
			if err := c.driver.Mkdir(fp, resource.Mode()); err != nil {
 | 
						|
				return err
 | 
						|
			}
 | 
						|
		} else if !fi.Mode().IsDir() {
 | 
						|
			return fmt.Errorf("%q should be a directory, but is not", resource.Path())
 | 
						|
		}
 | 
						|
 | 
						|
	case SymLink:
 | 
						|
		var target string // only possibly set if target resource is a symlink
 | 
						|
 | 
						|
		if fi != nil {
 | 
						|
			if fi.Mode()&os.ModeSymlink != 0 {
 | 
						|
				target, err = c.driver.Readlink(fp)
 | 
						|
				if err != nil {
 | 
						|
					return err
 | 
						|
				}
 | 
						|
			}
 | 
						|
		}
 | 
						|
 | 
						|
		if target != r.Target() {
 | 
						|
			if fi != nil {
 | 
						|
				if err := c.driver.Remove(fp); err != nil { // RemoveAll in case of directory?
 | 
						|
					return err
 | 
						|
				}
 | 
						|
			}
 | 
						|
 | 
						|
			if err := c.driver.Symlink(r.Target(), fp); err != nil {
 | 
						|
				return err
 | 
						|
			}
 | 
						|
		}
 | 
						|
 | 
						|
	case Device:
 | 
						|
		if fi == nil {
 | 
						|
			if err := c.driver.Mknod(fp, resource.Mode(), int(r.Major()), int(r.Minor())); err != nil {
 | 
						|
				return err
 | 
						|
			}
 | 
						|
		} else if (fi.Mode() & os.ModeDevice) == 0 {
 | 
						|
			return fmt.Errorf("%q should be a device, but is not", resource.Path())
 | 
						|
		} else {
 | 
						|
			major, minor, err := devices.DeviceInfo(fi)
 | 
						|
			if err != nil {
 | 
						|
				return err
 | 
						|
			}
 | 
						|
			if major != r.Major() || minor != r.Minor() {
 | 
						|
				if err := c.driver.Remove(fp); err != nil {
 | 
						|
					return err
 | 
						|
				}
 | 
						|
 | 
						|
				if err := c.driver.Mknod(fp, resource.Mode(), int(r.Major()), int(r.Minor())); err != nil {
 | 
						|
					return err
 | 
						|
				}
 | 
						|
			}
 | 
						|
		}
 | 
						|
 | 
						|
	case NamedPipe:
 | 
						|
		if fi == nil {
 | 
						|
			if err := c.driver.Mkfifo(fp, resource.Mode()); err != nil {
 | 
						|
				return err
 | 
						|
			}
 | 
						|
		} else if (fi.Mode() & os.ModeNamedPipe) == 0 {
 | 
						|
			return fmt.Errorf("%q should be a named pipe, but is not", resource.Path())
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	if h, isHardlinkable := resource.(Hardlinkable); isHardlinkable {
 | 
						|
		for _, path := range h.Paths() {
 | 
						|
			if path == resource.Path() {
 | 
						|
				continue
 | 
						|
			}
 | 
						|
 | 
						|
			lp, err := c.fullpath(path)
 | 
						|
			if err != nil {
 | 
						|
				return err
 | 
						|
			}
 | 
						|
 | 
						|
			if _, fi := c.driver.Lstat(lp); fi == nil {
 | 
						|
				c.driver.Remove(lp)
 | 
						|
			}
 | 
						|
			if err := c.driver.Link(fp, lp); err != nil {
 | 
						|
				return err
 | 
						|
			}
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	// Update filemode if file was not created
 | 
						|
	if chmod {
 | 
						|
		if err := c.driver.Lchmod(fp, resource.Mode()); err != nil {
 | 
						|
			return err
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	if err := c.driver.Lchown(fp, resource.UID(), resource.GID()); err != nil {
 | 
						|
		return err
 | 
						|
	}
 | 
						|
 | 
						|
	if xattrer, ok := resource.(XAttrer); ok {
 | 
						|
		// For xattrs, only ensure that we have those defined in the resource
 | 
						|
		// and their values are set. We can ignore other xattrs. In other words,
 | 
						|
		// we only set xattres defined by resource but never remove.
 | 
						|
 | 
						|
		if _, ok := resource.(SymLink); ok {
 | 
						|
			lxattrDriver, ok := c.driver.(driverpkg.LXAttrDriver)
 | 
						|
			if !ok {
 | 
						|
				return fmt.Errorf("unsupported symlink xattr for resource %q", resource.Path())
 | 
						|
			}
 | 
						|
			if err := lxattrDriver.LSetxattr(fp, xattrer.XAttrs()); err != nil {
 | 
						|
				return err
 | 
						|
			}
 | 
						|
		} else {
 | 
						|
			xattrDriver, ok := c.driver.(driverpkg.XAttrDriver)
 | 
						|
			if !ok {
 | 
						|
				return fmt.Errorf("unsupported xattr for resource %q", resource.Path())
 | 
						|
			}
 | 
						|
			if err := xattrDriver.Setxattr(fp, xattrer.XAttrs()); err != nil {
 | 
						|
				return err
 | 
						|
			}
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	return nil
 | 
						|
}
 | 
						|
 | 
						|
// Walk provides a convenience function to call filepath.Walk correctly for
 | 
						|
// the context. Otherwise identical to filepath.Walk, the path argument is
 | 
						|
// corrected to be contained within the context.
 | 
						|
func (c *context) Walk(fn filepath.WalkFunc) error {
 | 
						|
	root := c.root
 | 
						|
	fi, err := c.driver.Lstat(c.root)
 | 
						|
	if err == nil && fi.Mode()&os.ModeSymlink != 0 {
 | 
						|
		root, err = c.driver.Readlink(c.root)
 | 
						|
		if err != nil {
 | 
						|
			return err
 | 
						|
		}
 | 
						|
	}
 | 
						|
	return c.pathDriver.Walk(root, func(p string, fi os.FileInfo, _ error) error {
 | 
						|
		contained, err := c.containWithRoot(p, root)
 | 
						|
		return fn(contained, fi, err)
 | 
						|
	})
 | 
						|
}
 | 
						|
 | 
						|
// fullpath returns the system path for the resource, joined with the context
 | 
						|
// root. The path p must be a part of the context.
 | 
						|
func (c *context) fullpath(p string) (string, error) {
 | 
						|
	p = c.pathDriver.Join(c.root, p)
 | 
						|
	if !strings.HasPrefix(p, c.root) {
 | 
						|
		return "", fmt.Errorf("invalid context path")
 | 
						|
	}
 | 
						|
 | 
						|
	return p, nil
 | 
						|
}
 | 
						|
 | 
						|
// containWithRoot cleans and santizes the filesystem path p to be an absolute path,
 | 
						|
// effectively relative to the passed root. Extra care should be used when calling this
 | 
						|
// instead of contain. This is needed for Walk, as if context root is a symlink,
 | 
						|
// it must be evaluated prior to the Walk
 | 
						|
func (c *context) containWithRoot(p string, root string) (string, error) {
 | 
						|
	sanitized, err := c.pathDriver.Rel(root, p)
 | 
						|
	if err != nil {
 | 
						|
		return "", err
 | 
						|
	}
 | 
						|
 | 
						|
	// ZOMBIES(stevvooe): In certain cases, we may want to remap these to a
 | 
						|
	// "containment error", so the caller can decide what to do.
 | 
						|
	return c.pathDriver.Join("/", c.pathDriver.Clean(sanitized)), nil
 | 
						|
}
 | 
						|
 | 
						|
// digest returns the digest of the file at path p, relative to the root.
 | 
						|
func (c *context) digest(p string) (digest.Digest, error) {
 | 
						|
	f, err := c.driver.Open(c.pathDriver.Join(c.root, p))
 | 
						|
	if err != nil {
 | 
						|
		return "", err
 | 
						|
	}
 | 
						|
	defer f.Close()
 | 
						|
 | 
						|
	return c.digester.Digest(f)
 | 
						|
}
 | 
						|
 | 
						|
// resolveXAttrs attempts to resolve the extended attributes for the resource
 | 
						|
// at the path fp, which is the full path to the resource. If the resource
 | 
						|
// cannot have xattrs, nil will be returned.
 | 
						|
func (c *context) resolveXAttrs(fp string, fi os.FileInfo, base *resource) (map[string][]byte, error) {
 | 
						|
	if fi.Mode().IsRegular() || fi.Mode().IsDir() {
 | 
						|
		xattrDriver, ok := c.driver.(driverpkg.XAttrDriver)
 | 
						|
		if !ok {
 | 
						|
			return nil, fmt.Errorf("xattr extraction is not supported: %w", ErrNotSupported)
 | 
						|
		}
 | 
						|
 | 
						|
		return xattrDriver.Getxattr(fp)
 | 
						|
	}
 | 
						|
 | 
						|
	if fi.Mode()&os.ModeSymlink != 0 {
 | 
						|
		lxattrDriver, ok := c.driver.(driverpkg.LXAttrDriver)
 | 
						|
		if !ok {
 | 
						|
			return nil, fmt.Errorf("xattr extraction for symlinks is not supported: %w", ErrNotSupported)
 | 
						|
		}
 | 
						|
 | 
						|
		return lxattrDriver.LGetxattr(fp)
 | 
						|
	}
 | 
						|
 | 
						|
	return nil, nil
 | 
						|
}
 |