123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673 |
- /*
- 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
- }
|