mirror of
				https://gitea.com/Lydanne/buildx.git
				synced 2025-10-31 16:13:45 +08:00 
			
		
		
		
	
		
			
				
	
	
		
			674 lines
		
	
	
		
			18 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			674 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"
 | |
| 	"fmt"
 | |
| 	"io"
 | |
| 	"log"
 | |
| 	"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 == ErrNotSupported {
 | |
| 		log.Printf("resolving xattrs on %s not supported", fp)
 | |
| 	} else if err != nil {
 | |
| 		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 {
 | |
| 			log.Printf("device extraction not supported %s", fp)
 | |
| 			return nil, 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)
 | |
| 	}
 | |
| 
 | |
| 	log.Printf("%q (%v) is not supported", fp, fi.Mode())
 | |
| 	return nil, 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: %v", 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)
 | |
| 	}
 | |
| 
 | |
| 	var 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: %v", 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: %v", 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: %v", 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: %v", resource.Path(), err)
 | |
| 						}
 | |
| 						break
 | |
| 					}
 | |
| 					if err1 := f.Close(); err == nil {
 | |
| 						err = err1
 | |
| 					}
 | |
| 					if err != nil {
 | |
| 						return fmt.Errorf("error checking digest for %q: %v", 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, err 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
 | |
| }
 | |
| 
 | |
| // contain cleans and santizes the filesystem path p to be an absolute path,
 | |
| // effectively relative to the context root.
 | |
| func (c *context) contain(p string) (string, error) {
 | |
| 	return c.containWithRoot(p, c.root)
 | |
| }
 | |
| 
 | |
| // 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 {
 | |
| 			log.Println("xattr extraction not supported")
 | |
| 			return nil, ErrNotSupported
 | |
| 		}
 | |
| 
 | |
| 		return xattrDriver.Getxattr(fp)
 | |
| 	}
 | |
| 
 | |
| 	if fi.Mode()&os.ModeSymlink != 0 {
 | |
| 		lxattrDriver, ok := c.driver.(driverpkg.LXAttrDriver)
 | |
| 		if !ok {
 | |
| 			log.Println("xattr extraction for symlinks not supported")
 | |
| 			return nil, ErrNotSupported
 | |
| 		}
 | |
| 
 | |
| 		return lxattrDriver.LGetxattr(fp)
 | |
| 	}
 | |
| 
 | |
| 	return nil, nil
 | |
| }
 | 
