Revert "update to use containerd seccomp package"
This reverts commit 4f8e065faf055d3f0463a92622297ca3afac07f4.
This commit is contained in:
parent
09243b740c
commit
60f032f6f5
8199 changed files with 1598219 additions and 30742 deletions
11
vendor/github.com/containerd/continuity/cmd/continuity/main.go
generated
vendored
Normal file
11
vendor/github.com/containerd/continuity/cmd/continuity/main.go
generated
vendored
Normal file
|
@ -0,0 +1,11 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
_ "crypto/sha256"
|
||||
|
||||
"github.com/containerd/continuity/commands"
|
||||
)
|
||||
|
||||
func main() {
|
||||
commands.MainCmd.Execute()
|
||||
}
|
36
vendor/github.com/containerd/continuity/commands/apply.go
generated
vendored
Normal file
36
vendor/github.com/containerd/continuity/commands/apply.go
generated
vendored
Normal file
|
@ -0,0 +1,36 @@
|
|||
package commands
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"log"
|
||||
|
||||
"github.com/containerd/continuity"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var ApplyCmd = &cobra.Command{
|
||||
Use: "apply <root> [<manifest>]",
|
||||
Short: "Apply the manifest to the provided root",
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
root, path := args[0], args[1]
|
||||
|
||||
p, err := ioutil.ReadFile(path)
|
||||
if err != nil {
|
||||
log.Fatalf("error reading manifest: %v", err)
|
||||
}
|
||||
|
||||
m, err := continuity.Unmarshal(p)
|
||||
if err != nil {
|
||||
log.Fatalf("error unmarshaling manifest: %v", err)
|
||||
}
|
||||
|
||||
ctx, err := continuity.NewContext(root)
|
||||
if err != nil {
|
||||
log.Fatalf("error getting context: %v", err)
|
||||
}
|
||||
|
||||
if err := continuity.ApplyManifest(ctx, m); err != nil {
|
||||
log.Fatalf("error applying manifest: %v", err)
|
||||
}
|
||||
},
|
||||
}
|
48
vendor/github.com/containerd/continuity/commands/build.go
generated
vendored
Normal file
48
vendor/github.com/containerd/continuity/commands/build.go
generated
vendored
Normal file
|
@ -0,0 +1,48 @@
|
|||
package commands
|
||||
|
||||
import (
|
||||
"log"
|
||||
"os"
|
||||
|
||||
"github.com/containerd/continuity"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var (
|
||||
buildCmdConfig struct {
|
||||
format string
|
||||
}
|
||||
|
||||
BuildCmd = &cobra.Command{
|
||||
Use: "build <root>",
|
||||
Short: "Build a manifest for the provided root",
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
if len(args) != 1 {
|
||||
log.Fatalln("please specify a root")
|
||||
}
|
||||
|
||||
ctx, err := continuity.NewContext(args[0])
|
||||
if err != nil {
|
||||
log.Fatalf("error creating path context: %v", err)
|
||||
}
|
||||
|
||||
m, err := continuity.BuildManifest(ctx)
|
||||
if err != nil {
|
||||
log.Fatalf("error generating manifest: %v", err)
|
||||
}
|
||||
|
||||
p, err := continuity.Marshal(m)
|
||||
if err != nil {
|
||||
log.Fatalf("error marshaling manifest: %v", err)
|
||||
}
|
||||
|
||||
if _, err := os.Stdout.Write(p); err != nil {
|
||||
log.Fatalf("error writing to stdout: %v", err)
|
||||
}
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
func init() {
|
||||
BuildCmd.Flags().StringVar(&buildCmdConfig.format, "format", "pb", "specify the output format of the manifest")
|
||||
}
|
44
vendor/github.com/containerd/continuity/commands/dump.go
generated
vendored
Normal file
44
vendor/github.com/containerd/continuity/commands/dump.go
generated
vendored
Normal file
|
@ -0,0 +1,44 @@
|
|||
package commands
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"os"
|
||||
|
||||
pb "github.com/containerd/continuity/proto"
|
||||
"github.com/golang/protobuf/proto"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var DumpCmd = &cobra.Command{
|
||||
Use: "dump <manifest>",
|
||||
Short: "Dump the contents of the manifest in protobuf text format",
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
var p []byte
|
||||
var err error
|
||||
|
||||
if len(args) < 1 {
|
||||
p, err = ioutil.ReadAll(os.Stdin)
|
||||
if err != nil {
|
||||
log.Fatalf("error reading manifest: %v", err)
|
||||
}
|
||||
} else {
|
||||
p, err = ioutil.ReadFile(args[0])
|
||||
if err != nil {
|
||||
log.Fatalf("error reading manifest: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
var bm pb.Manifest
|
||||
|
||||
if err := proto.Unmarshal(p, &bm); err != nil {
|
||||
log.Fatalf("error unmarshaling manifest: %v", err)
|
||||
}
|
||||
|
||||
// TODO(stevvooe): For now, just dump the text format. Turn this into
|
||||
// nice text output later.
|
||||
if err := proto.MarshalText(os.Stdout, &bm); err != nil {
|
||||
log.Fatalf("error dumping manifest: %v", err)
|
||||
}
|
||||
},
|
||||
}
|
41
vendor/github.com/containerd/continuity/commands/ls.go
generated
vendored
Normal file
41
vendor/github.com/containerd/continuity/commands/ls.go
generated
vendored
Normal file
|
@ -0,0 +1,41 @@
|
|||
package commands
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"text/tabwriter"
|
||||
|
||||
"github.com/dustin/go-humanize"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var LSCmd = &cobra.Command{
|
||||
Use: "ls <manifest>",
|
||||
Short: "List the contents of the manifest.",
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
if len(args) != 1 {
|
||||
log.Fatalln("please specify a manifest")
|
||||
}
|
||||
|
||||
bm, err := readManifestFile(args[0])
|
||||
if err != nil {
|
||||
log.Fatalf("error reading manifest: %v", err)
|
||||
}
|
||||
|
||||
w := tabwriter.NewWriter(os.Stdout, 0, 2, 2, ' ', 0)
|
||||
|
||||
for _, entry := range bm.Resource {
|
||||
for _, path := range entry.Path {
|
||||
if os.FileMode(entry.Mode)&os.ModeSymlink != 0 {
|
||||
fmt.Fprintf(w, "%v\t%v\t%v\t%v\t%v -> %v\n", os.FileMode(entry.Mode), entry.User, entry.Group, humanize.Bytes(uint64(entry.Size)), path, entry.Target)
|
||||
} else {
|
||||
fmt.Fprintf(w, "%v\t%v\t%v\t%v\t%v\n", os.FileMode(entry.Mode), entry.User, entry.Group, humanize.Bytes(uint64(entry.Size)), path)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
w.Flush()
|
||||
},
|
||||
}
|
82
vendor/github.com/containerd/continuity/commands/main.go
generated
vendored
Normal file
82
vendor/github.com/containerd/continuity/commands/main.go
generated
vendored
Normal file
|
@ -0,0 +1,82 @@
|
|||
package commands
|
||||
|
||||
import (
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"text/tabwriter"
|
||||
|
||||
pb "github.com/containerd/continuity/proto"
|
||||
"github.com/golang/protobuf/proto"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var (
|
||||
MainCmd = &cobra.Command{
|
||||
Use: "continuity <command>",
|
||||
Short: "A transport-agnostic filesytem metadata tool.",
|
||||
}
|
||||
|
||||
// usageTemplate is nearly identical to the default template without the
|
||||
// automatic addition of flags. Instead, Command.Use is used unmodified.
|
||||
usageTemplate = `Usage:{{if .Runnable}}
|
||||
{{.UseLine}}{{end}}{{if .HasSubCommands}}
|
||||
{{ .CommandPath}} [command]{{end}}{{if gt .Aliases 0}}
|
||||
|
||||
Aliases:
|
||||
{{.NameAndAliases}}
|
||||
{{end}}{{if .HasExample}}
|
||||
|
||||
Examples:
|
||||
{{ .Example }}{{end}}{{ if .HasAvailableSubCommands}}
|
||||
|
||||
Available Commands:{{range .Commands}}{{if .IsAvailableCommand}}
|
||||
{{rpad .Name .NamePadding }} {{.Short}}{{end}}{{end}}{{end}}{{ if .HasLocalFlags}}
|
||||
|
||||
Flags:
|
||||
{{.LocalFlags.FlagUsages | trimRightSpace}}{{end}}{{ if .HasInheritedFlags}}
|
||||
|
||||
Global Flags:
|
||||
{{.InheritedFlags.FlagUsages | trimRightSpace}}{{end}}{{if .HasHelpSubCommands}}
|
||||
|
||||
Additional help topics:{{range .Commands}}{{if .IsHelpCommand}}
|
||||
{{rpad .CommandPath .CommandPathPadding}} {{.Short}}{{end}}{{end}}{{end}}{{ if .HasSubCommands }}
|
||||
|
||||
Use "{{.CommandPath}} [command] --help" for more information about a command.{{end}}
|
||||
`
|
||||
)
|
||||
|
||||
func init() {
|
||||
MainCmd.AddCommand(BuildCmd)
|
||||
MainCmd.AddCommand(VerifyCmd)
|
||||
MainCmd.AddCommand(ApplyCmd)
|
||||
MainCmd.AddCommand(LSCmd)
|
||||
MainCmd.AddCommand(StatsCmd)
|
||||
MainCmd.AddCommand(DumpCmd)
|
||||
if MountCmd != nil {
|
||||
MainCmd.AddCommand(MountCmd)
|
||||
}
|
||||
MainCmd.SetUsageTemplate(usageTemplate)
|
||||
}
|
||||
|
||||
// readManifestFile reads the manifest from the given path. This should
|
||||
// probably be provided by the continuity library.
|
||||
func readManifestFile(path string) (*pb.Manifest, error) {
|
||||
p, err := ioutil.ReadFile(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var bm pb.Manifest
|
||||
|
||||
if err := proto.Unmarshal(p, &bm); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &bm, nil
|
||||
}
|
||||
|
||||
// newTabwriter provides a common tabwriter with defaults.
|
||||
func newTabwriter(w io.Writer) *tabwriter.Writer {
|
||||
return tabwriter.NewWriter(os.Stdout, 0, 2, 2, ' ', 0)
|
||||
}
|
115
vendor/github.com/containerd/continuity/commands/mount.go
generated
vendored
Normal file
115
vendor/github.com/containerd/continuity/commands/mount.go
generated
vendored
Normal file
|
@ -0,0 +1,115 @@
|
|||
// +build linux darwin freebsd
|
||||
|
||||
package commands
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"os"
|
||||
"os/signal"
|
||||
"path/filepath"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"bazil.org/fuse"
|
||||
"bazil.org/fuse/fs"
|
||||
|
||||
"github.com/containerd/continuity"
|
||||
"github.com/containerd/continuity/continuityfs"
|
||||
"github.com/containerd/continuity/driver"
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var MountCmd = &cobra.Command{
|
||||
Use: "mount <mountpoint> [<manifest>] [<source directory>]",
|
||||
Short: "Mount the manifest to the provided mountpoint using content from a source directory",
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
if len(args) != 3 {
|
||||
log.Fatal("Must specify mountpoint, manifest, and source directory")
|
||||
}
|
||||
mountpoint := args[0]
|
||||
manifest, source := args[1], args[2]
|
||||
|
||||
manifestName := filepath.Base(manifest)
|
||||
|
||||
p, err := ioutil.ReadFile(manifest)
|
||||
if err != nil {
|
||||
log.Fatalf("error reading manifest: %v", err)
|
||||
}
|
||||
|
||||
m, err := continuity.Unmarshal(p)
|
||||
if err != nil {
|
||||
log.Fatalf("error unmarshaling manifest: %v", err)
|
||||
}
|
||||
|
||||
driver, err := driver.NewSystemDriver()
|
||||
if err != nil {
|
||||
logrus.Fatal(err)
|
||||
}
|
||||
|
||||
provider := continuityfs.NewFSFileContentProvider(source, driver)
|
||||
|
||||
contfs, err := continuityfs.NewFSFromManifest(m, mountpoint, provider)
|
||||
if err != nil {
|
||||
logrus.Fatal(err)
|
||||
}
|
||||
|
||||
c, err := fuse.Mount(
|
||||
mountpoint,
|
||||
fuse.ReadOnly(),
|
||||
fuse.FSName(manifestName),
|
||||
fuse.Subtype("continuity"),
|
||||
// OSX Only options
|
||||
fuse.LocalVolume(),
|
||||
fuse.VolumeName("Continuity FileSystem"),
|
||||
)
|
||||
if err != nil {
|
||||
logrus.Fatal(err)
|
||||
}
|
||||
|
||||
<-c.Ready
|
||||
if err := c.MountError; err != nil {
|
||||
c.Close()
|
||||
logrus.Fatal(err)
|
||||
}
|
||||
|
||||
errChan := make(chan error, 1)
|
||||
go func() {
|
||||
// TODO: Create server directory to use context
|
||||
err = fs.Serve(c, contfs)
|
||||
if err != nil {
|
||||
errChan <- err
|
||||
}
|
||||
}()
|
||||
|
||||
sigChan := make(chan os.Signal, 1)
|
||||
signal.Notify(sigChan, os.Interrupt)
|
||||
signal.Notify(sigChan, syscall.SIGTERM)
|
||||
|
||||
select {
|
||||
case <-sigChan:
|
||||
logrus.Infof("Shutting down")
|
||||
case err = <-errChan:
|
||||
}
|
||||
|
||||
go func() {
|
||||
if err := c.Close(); err != nil {
|
||||
logrus.Errorf("Unable to close connection %s", err)
|
||||
}
|
||||
}()
|
||||
|
||||
// Wait for any inprogress requests to be handled
|
||||
time.Sleep(time.Second)
|
||||
|
||||
logrus.Infof("Attempting unmount")
|
||||
if err := fuse.Unmount(mountpoint); err != nil {
|
||||
logrus.Errorf("Error unmounting %s: %v", mountpoint, err)
|
||||
}
|
||||
|
||||
// Handle server error
|
||||
if err != nil {
|
||||
logrus.Fatalf("Error serving fuse server: %v", err)
|
||||
}
|
||||
},
|
||||
}
|
9
vendor/github.com/containerd/continuity/commands/mount_unsupported.go
generated
vendored
Normal file
9
vendor/github.com/containerd/continuity/commands/mount_unsupported.go
generated
vendored
Normal file
|
@ -0,0 +1,9 @@
|
|||
// +build windows solaris
|
||||
|
||||
package commands
|
||||
|
||||
import (
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var MountCmd *cobra.Command = nil
|
58
vendor/github.com/containerd/continuity/commands/stats.go
generated
vendored
Normal file
58
vendor/github.com/containerd/continuity/commands/stats.go
generated
vendored
Normal file
|
@ -0,0 +1,58 @@
|
|||
package commands
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
|
||||
"github.com/dustin/go-humanize"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var (
|
||||
StatsCmd = &cobra.Command{
|
||||
Use: "stats <manifest>",
|
||||
Short: "display statistics about the specified manifest",
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
if len(args) != 1 {
|
||||
log.Fatalln("please specify a manifest")
|
||||
}
|
||||
|
||||
bm, err := readManifestFile(args[0])
|
||||
if err != nil {
|
||||
log.Fatalf("error reading manifest: %v", err)
|
||||
}
|
||||
|
||||
var stats struct {
|
||||
resources int
|
||||
files int
|
||||
directories int
|
||||
totalSize int64
|
||||
symlinks int
|
||||
}
|
||||
|
||||
for _, entry := range bm.Resource {
|
||||
stats.resources++
|
||||
stats.totalSize += int64(entry.Size)
|
||||
|
||||
mode := os.FileMode(entry.Mode)
|
||||
if mode.IsRegular() {
|
||||
stats.files += len(entry.Path) // count hardlinks!
|
||||
} else if mode.IsDir() {
|
||||
stats.directories++
|
||||
} else if mode&os.ModeSymlink != 0 {
|
||||
stats.symlinks++
|
||||
}
|
||||
}
|
||||
|
||||
w := newTabwriter(os.Stdout)
|
||||
defer w.Flush()
|
||||
|
||||
fmt.Fprintf(w, "resources\t%v\n", stats.resources)
|
||||
fmt.Fprintf(w, "directories\t%v\n", stats.directories)
|
||||
fmt.Fprintf(w, "files\t%v\n", stats.files)
|
||||
fmt.Fprintf(w, "symlinks\t%v\n", stats.symlinks)
|
||||
fmt.Fprintf(w, "size\t%v\n", humanize.Bytes(uint64(stats.totalSize)))
|
||||
},
|
||||
}
|
||||
)
|
41
vendor/github.com/containerd/continuity/commands/verify.go
generated
vendored
Normal file
41
vendor/github.com/containerd/continuity/commands/verify.go
generated
vendored
Normal file
|
@ -0,0 +1,41 @@
|
|||
package commands
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"log"
|
||||
|
||||
"github.com/containerd/continuity"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var VerifyCmd = &cobra.Command{
|
||||
Use: "verify <root> [<manifest>]",
|
||||
Short: "Verify the root against the provided manifest",
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
if len(args) != 2 {
|
||||
log.Fatalln("please specify a root and manifest")
|
||||
}
|
||||
|
||||
root, path := args[0], args[1]
|
||||
|
||||
p, err := ioutil.ReadFile(path)
|
||||
if err != nil {
|
||||
log.Fatalf("error reading manifest: %v", err)
|
||||
}
|
||||
|
||||
m, err := continuity.Unmarshal(p)
|
||||
if err != nil {
|
||||
log.Fatalf("error unmarshaling manifest: %v", err)
|
||||
}
|
||||
|
||||
ctx, err := continuity.NewContext(root)
|
||||
if err != nil {
|
||||
log.Fatalf("error getting context: %v", err)
|
||||
}
|
||||
|
||||
if err := continuity.VerifyManifest(ctx, m); err != nil {
|
||||
// TODO(stevvooe): Support more interesting error reporting.
|
||||
log.Fatalf("error verifying manifest: %v", err)
|
||||
}
|
||||
},
|
||||
}
|
306
vendor/github.com/containerd/continuity/continuityfs/fuse.go
generated
vendored
Normal file
306
vendor/github.com/containerd/continuity/continuityfs/fuse.go
generated
vendored
Normal file
|
@ -0,0 +1,306 @@
|
|||
// +build linux darwin freebsd
|
||||
|
||||
package continuityfs
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"syscall"
|
||||
|
||||
"bazil.org/fuse"
|
||||
"bazil.org/fuse/fs"
|
||||
"github.com/containerd/continuity"
|
||||
"github.com/opencontainers/go-digest"
|
||||
"github.com/sirupsen/logrus"
|
||||
"golang.org/x/net/context"
|
||||
)
|
||||
|
||||
// File represents any file type (non directory) in the filesystem
|
||||
type File struct {
|
||||
inode uint64
|
||||
uid uint32
|
||||
gid uint32
|
||||
provider FileContentProvider
|
||||
resource continuity.Resource
|
||||
}
|
||||
|
||||
// NewFile creates a new file with the given inode and content provider
|
||||
func NewFile(inode uint64, provider FileContentProvider) *File {
|
||||
return &File{
|
||||
inode: inode,
|
||||
provider: provider,
|
||||
}
|
||||
}
|
||||
|
||||
func (f *File) setResource(r continuity.Resource) (err error) {
|
||||
// TODO: error out if uid excesses uint32?
|
||||
f.uid = uint32(r.UID())
|
||||
f.gid = uint32(r.GID())
|
||||
f.resource = r
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// Attr sets the fuse attribute for the file
|
||||
func (f *File) Attr(ctx context.Context, attr *fuse.Attr) (err error) {
|
||||
// Set attributes from resource metadata
|
||||
attr.Mode = f.resource.Mode()
|
||||
attr.Uid = f.uid
|
||||
attr.Gid = f.gid
|
||||
|
||||
if rf, ok := f.resource.(continuity.RegularFile); ok {
|
||||
attr.Nlink = uint32(len(rf.Paths()))
|
||||
attr.Size = uint64(rf.Size())
|
||||
} else {
|
||||
attr.Nlink = 1
|
||||
}
|
||||
|
||||
attr.Inode = f.inode
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Open opens the file for read
|
||||
// currently only regular files can be opened
|
||||
func (f *File) Open(ctx context.Context, req *fuse.OpenRequest, resp *fuse.OpenResponse) (fs.Handle, error) {
|
||||
var dgst digest.Digest
|
||||
if rf, ok := f.resource.(continuity.RegularFile); ok {
|
||||
digests := rf.Digests()
|
||||
if len(digests) > 0 {
|
||||
dgst = digests[0]
|
||||
}
|
||||
}
|
||||
// TODO(dmcgowan): else check if device can be opened for read
|
||||
r, err := f.provider.Open(f.resource.Path(), dgst)
|
||||
if err != nil {
|
||||
logrus.Debugf("Error opening handle: %v", err)
|
||||
return nil, err
|
||||
}
|
||||
return &fileHandler{
|
||||
reader: r,
|
||||
}, nil
|
||||
|
||||
}
|
||||
|
||||
func (f *File) getDirent(name string) (fuse.Dirent, error) {
|
||||
var t fuse.DirentType
|
||||
switch f.resource.(type) {
|
||||
case continuity.RegularFile:
|
||||
t = fuse.DT_File
|
||||
case continuity.SymLink:
|
||||
t = fuse.DT_Link
|
||||
case continuity.Device:
|
||||
t = fuse.DT_Block
|
||||
case continuity.NamedPipe:
|
||||
t = fuse.DT_FIFO
|
||||
default:
|
||||
t = fuse.DT_Unknown
|
||||
}
|
||||
|
||||
return fuse.Dirent{
|
||||
Inode: f.inode,
|
||||
Type: t,
|
||||
Name: name,
|
||||
}, nil
|
||||
}
|
||||
|
||||
type fileHandler struct {
|
||||
offset int64
|
||||
reader io.ReadCloser
|
||||
}
|
||||
|
||||
func (h *fileHandler) Read(ctx context.Context, req *fuse.ReadRequest, resp *fuse.ReadResponse) error {
|
||||
if h.offset != req.Offset {
|
||||
if seeker, ok := h.reader.(io.Seeker); ok {
|
||||
if _, err := seeker.Seek(req.Offset, os.SEEK_SET); err != nil {
|
||||
logrus.Debugf("Error seeking: %v", err)
|
||||
return err
|
||||
}
|
||||
h.offset = req.Offset
|
||||
} else {
|
||||
return errors.New("unable to seek to offset")
|
||||
}
|
||||
}
|
||||
|
||||
n, err := h.reader.Read(resp.Data[:req.Size])
|
||||
if err != nil {
|
||||
logrus.Debugf("Read error: %v", err)
|
||||
return err
|
||||
}
|
||||
h.offset = h.offset + int64(n)
|
||||
|
||||
resp.Data = resp.Data[:n]
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (h *fileHandler) Release(ctx context.Context, req *fuse.ReleaseRequest) error {
|
||||
return h.reader.Close()
|
||||
}
|
||||
|
||||
// Dir represents a file system directory
|
||||
type Dir struct {
|
||||
inode uint64
|
||||
uid uint32
|
||||
gid uint32
|
||||
nodes map[string]fs.Node
|
||||
provider FileContentProvider
|
||||
resource continuity.Resource
|
||||
}
|
||||
|
||||
// Attr sets the fuse attributes for the directory
|
||||
func (d *Dir) Attr(ctx context.Context, attr *fuse.Attr) (err error) {
|
||||
if d.resource == nil {
|
||||
attr.Mode = os.ModeDir | 0555
|
||||
} else {
|
||||
attr.Mode = d.resource.Mode()
|
||||
}
|
||||
|
||||
attr.Uid = d.uid
|
||||
attr.Gid = d.gid
|
||||
attr.Inode = d.inode
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *Dir) getDirent(name string) (fuse.Dirent, error) {
|
||||
return fuse.Dirent{
|
||||
Inode: d.inode,
|
||||
Type: fuse.DT_Dir,
|
||||
Name: name,
|
||||
}, nil
|
||||
}
|
||||
|
||||
type direnter interface {
|
||||
getDirent(name string) (fuse.Dirent, error)
|
||||
}
|
||||
|
||||
// Lookup looks up the filesystem node for the name within the directory
|
||||
func (d *Dir) Lookup(ctx context.Context, name string) (fs.Node, error) {
|
||||
node, ok := d.nodes[name]
|
||||
if !ok {
|
||||
return nil, fuse.ENOENT
|
||||
}
|
||||
return node, nil
|
||||
}
|
||||
|
||||
// ReadDirAll reads all the directory entries
|
||||
func (d *Dir) ReadDirAll(ctx context.Context) ([]fuse.Dirent, error) {
|
||||
ents := make([]fuse.Dirent, 0, len(d.nodes))
|
||||
for name, node := range d.nodes {
|
||||
if nd, ok := node.(direnter); ok {
|
||||
de, err := nd.getDirent(name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ents = append(ents, de)
|
||||
} else {
|
||||
logrus.Errorf("%s does not have a directory entry", name)
|
||||
}
|
||||
}
|
||||
|
||||
return ents, nil
|
||||
}
|
||||
|
||||
func (d *Dir) setResource(r continuity.Resource) (err error) {
|
||||
d.uid = uint32(r.UID())
|
||||
d.gid = uint32(r.GID())
|
||||
d.resource = r
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// NewDir creates a new directory object
|
||||
func NewDir(inode uint64, provider FileContentProvider) *Dir {
|
||||
return &Dir{
|
||||
inode: inode,
|
||||
nodes: map[string]fs.Node{},
|
||||
provider: provider,
|
||||
}
|
||||
}
|
||||
|
||||
var (
|
||||
rootPath = fmt.Sprintf("%c", filepath.Separator)
|
||||
)
|
||||
|
||||
func addNode(path string, node fs.Node, cache map[string]*Dir, provider FileContentProvider) {
|
||||
dirPath, file := filepath.Split(path)
|
||||
d, ok := cache[dirPath]
|
||||
if !ok {
|
||||
d = NewDir(0, provider)
|
||||
cache[dirPath] = d
|
||||
addNode(filepath.Clean(dirPath), d, cache, provider)
|
||||
}
|
||||
d.nodes[file] = node
|
||||
logrus.Debugf("%s (%#v) added to %s", file, node, dirPath)
|
||||
}
|
||||
|
||||
type treeRoot struct {
|
||||
root *Dir
|
||||
}
|
||||
|
||||
func (t treeRoot) Root() (fs.Node, error) {
|
||||
logrus.Debugf("Returning root with %#v", t.root.nodes)
|
||||
return t.root, nil
|
||||
}
|
||||
|
||||
// NewFSFromManifest creates a fuse filesystem using the given manifest
|
||||
// to create the node tree and the content provider to serve up
|
||||
// content for regular files.
|
||||
func NewFSFromManifest(manifest *continuity.Manifest, mountRoot string, provider FileContentProvider) (fs.FS, error) {
|
||||
tree := treeRoot{
|
||||
root: NewDir(0, provider),
|
||||
}
|
||||
|
||||
fi, err := os.Stat(mountRoot)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
st, ok := fi.Sys().(*syscall.Stat_t)
|
||||
if !ok {
|
||||
return nil, errors.New("could not access directory")
|
||||
}
|
||||
tree.root.uid = st.Uid
|
||||
tree.root.gid = st.Gid
|
||||
|
||||
dirCache := map[string]*Dir{
|
||||
rootPath: tree.root,
|
||||
}
|
||||
|
||||
for i, resource := range manifest.Resources {
|
||||
inode := uint64(i) + 1
|
||||
if _, ok := resource.(continuity.Directory); ok {
|
||||
cleanPath := filepath.Clean(resource.Path())
|
||||
keyPath := fmt.Sprintf("%s%c", cleanPath, filepath.Separator)
|
||||
d, ok := dirCache[keyPath]
|
||||
if !ok {
|
||||
d = NewDir(inode, provider)
|
||||
dirCache[keyPath] = d
|
||||
addNode(cleanPath, d, dirCache, provider)
|
||||
} else {
|
||||
d.inode = inode
|
||||
}
|
||||
if err := d.setResource(resource); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
continue
|
||||
}
|
||||
f := NewFile(inode, provider)
|
||||
if err := f.setResource(resource); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if rf, ok := resource.(continuity.RegularFile); ok {
|
||||
|
||||
for _, p := range rf.Paths() {
|
||||
addNode(p, f, dirCache, provider)
|
||||
}
|
||||
} else {
|
||||
addNode(resource.Path(), f, dirCache, provider)
|
||||
}
|
||||
}
|
||||
|
||||
return tree, nil
|
||||
}
|
43
vendor/github.com/containerd/continuity/continuityfs/provider.go
generated
vendored
Normal file
43
vendor/github.com/containerd/continuity/continuityfs/provider.go
generated
vendored
Normal file
|
@ -0,0 +1,43 @@
|
|||
// +build linux darwin freebsd
|
||||
|
||||
package continuityfs
|
||||
|
||||
import (
|
||||
"io"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/containerd/continuity/driver"
|
||||
"github.com/opencontainers/go-digest"
|
||||
)
|
||||
|
||||
// FileContentProvider is an object which is used to fetch
|
||||
// data and inode information about a path or digest.
|
||||
// TODO(dmcgowan): Update GetContentPath to provide a
|
||||
// filehandle or ReadWriteCloser.
|
||||
type FileContentProvider interface {
|
||||
Path(string, digest.Digest) (string, error)
|
||||
Open(string, digest.Digest) (io.ReadCloser, error)
|
||||
}
|
||||
|
||||
type fsContentProvider struct {
|
||||
root string
|
||||
driver driver.Driver
|
||||
}
|
||||
|
||||
// NewFSFileContentProvider creates a new content provider which
|
||||
// gets content from a directory on an existing filesystem based
|
||||
// on the resource path.
|
||||
func NewFSFileContentProvider(root string, driver driver.Driver) FileContentProvider {
|
||||
return &fsContentProvider{
|
||||
root: root,
|
||||
driver: driver,
|
||||
}
|
||||
}
|
||||
|
||||
func (p *fsContentProvider) Path(path string, dgst digest.Digest) (string, error) {
|
||||
return filepath.Join(p.root, path), nil
|
||||
}
|
||||
|
||||
func (p *fsContentProvider) Open(path string, dgst digest.Digest) (io.ReadCloser, error) {
|
||||
return p.driver.Open(filepath.Join(p.root, path))
|
||||
}
|
5
vendor/github.com/containerd/continuity/devices/devices.go
generated
vendored
Normal file
5
vendor/github.com/containerd/continuity/devices/devices.go
generated
vendored
Normal file
|
@ -0,0 +1,5 @@
|
|||
package devices
|
||||
|
||||
import "fmt"
|
||||
|
||||
var ErrNotSupported = fmt.Errorf("not supported")
|
58
vendor/github.com/containerd/continuity/devices/devices_unix.go
generated
vendored
Normal file
58
vendor/github.com/containerd/continuity/devices/devices_unix.go
generated
vendored
Normal file
|
@ -0,0 +1,58 @@
|
|||
// +build linux darwin freebsd solaris
|
||||
|
||||
package devices
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"syscall"
|
||||
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
func DeviceInfo(fi os.FileInfo) (uint64, uint64, error) {
|
||||
sys, ok := fi.Sys().(*syscall.Stat_t)
|
||||
if !ok {
|
||||
return 0, 0, fmt.Errorf("cannot extract device from os.FileInfo")
|
||||
}
|
||||
|
||||
dev := uint64(sys.Rdev)
|
||||
return uint64(unix.Major(dev)), uint64(unix.Minor(dev)), nil
|
||||
}
|
||||
|
||||
// mknod provides a shortcut for syscall.Mknod
|
||||
func Mknod(p string, mode os.FileMode, maj, min int) error {
|
||||
var (
|
||||
m = syscallMode(mode.Perm())
|
||||
dev uint64
|
||||
)
|
||||
|
||||
if mode&os.ModeDevice != 0 {
|
||||
dev = unix.Mkdev(uint32(maj), uint32(min))
|
||||
|
||||
if mode&os.ModeCharDevice != 0 {
|
||||
m |= unix.S_IFCHR
|
||||
} else {
|
||||
m |= unix.S_IFBLK
|
||||
}
|
||||
} else if mode&os.ModeNamedPipe != 0 {
|
||||
m |= unix.S_IFIFO
|
||||
}
|
||||
|
||||
return unix.Mknod(p, m, int(dev))
|
||||
}
|
||||
|
||||
// syscallMode returns the syscall-specific mode bits from Go's portable mode bits.
|
||||
func syscallMode(i os.FileMode) (o uint32) {
|
||||
o |= uint32(i.Perm())
|
||||
if i&os.ModeSetuid != 0 {
|
||||
o |= unix.S_ISUID
|
||||
}
|
||||
if i&os.ModeSetgid != 0 {
|
||||
o |= unix.S_ISGID
|
||||
}
|
||||
if i&os.ModeSticky != 0 {
|
||||
o |= unix.S_ISVTX
|
||||
}
|
||||
return
|
||||
}
|
11
vendor/github.com/containerd/continuity/devices/devices_windows.go
generated
vendored
Normal file
11
vendor/github.com/containerd/continuity/devices/devices_windows.go
generated
vendored
Normal file
|
@ -0,0 +1,11 @@
|
|||
package devices
|
||||
|
||||
import (
|
||||
"os"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
func DeviceInfo(fi os.FileInfo) (uint64, uint64, error) {
|
||||
return 0, 0, errors.Wrap(ErrNotSupported, "cannot get device info on windows")
|
||||
}
|
162
vendor/github.com/containerd/continuity/driver/driver.go
generated
vendored
Normal file
162
vendor/github.com/containerd/continuity/driver/driver.go
generated
vendored
Normal file
|
@ -0,0 +1,162 @@
|
|||
package driver
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
)
|
||||
|
||||
var ErrNotSupported = fmt.Errorf("not supported")
|
||||
|
||||
// Driver provides all of the system-level functions in a common interface.
|
||||
// The context should call these with full paths and should never use the `os`
|
||||
// package or any other package to access resources on the filesystem. This
|
||||
// mechanism let's us carefully control access to the context and maintain
|
||||
// path and resource integrity. It also gives us an interface to reason about
|
||||
// direct resource access.
|
||||
//
|
||||
// Implementations don't need to do much other than meet the interface. For
|
||||
// example, it is not required to wrap os.FileInfo to return correct paths for
|
||||
// the call to Name().
|
||||
type Driver interface {
|
||||
// Note that Open() returns a File interface instead of *os.File. This
|
||||
// is because os.File is a struct, so if Open was to return *os.File,
|
||||
// the only way to fulfill the interface would be to call os.Open()
|
||||
Open(path string) (File, error)
|
||||
OpenFile(path string, flag int, perm os.FileMode) (File, error)
|
||||
|
||||
Stat(path string) (os.FileInfo, error)
|
||||
Lstat(path string) (os.FileInfo, error)
|
||||
Readlink(p string) (string, error)
|
||||
Mkdir(path string, mode os.FileMode) error
|
||||
Remove(path string) error
|
||||
|
||||
Link(oldname, newname string) error
|
||||
Lchmod(path string, mode os.FileMode) error
|
||||
Lchown(path string, uid, gid int64) error
|
||||
Symlink(oldname, newname string) error
|
||||
|
||||
MkdirAll(path string, perm os.FileMode) error
|
||||
RemoveAll(path string) error
|
||||
|
||||
// TODO(aaronl): These methods might move outside the main Driver
|
||||
// interface in the future as more platforms are added.
|
||||
Mknod(path string, mode os.FileMode, major int, minor int) error
|
||||
Mkfifo(path string, mode os.FileMode) error
|
||||
}
|
||||
|
||||
// File is the interface for interacting with files returned by continuity's Open
|
||||
// This is needed since os.File is a struct, instead of an interface, so it can't
|
||||
// be used.
|
||||
type File interface {
|
||||
io.ReadWriteCloser
|
||||
io.Seeker
|
||||
Readdir(n int) ([]os.FileInfo, error)
|
||||
}
|
||||
|
||||
func NewSystemDriver() (Driver, error) {
|
||||
// TODO(stevvooe): Consider having this take a "hint" path argument, which
|
||||
// would be the context root. The hint could be used to resolve required
|
||||
// filesystem support when assembling the driver to use.
|
||||
return &driver{}, nil
|
||||
}
|
||||
|
||||
// XAttrDriver should be implemented on operation systems and filesystems that
|
||||
// have xattr support for regular files and directories.
|
||||
type XAttrDriver interface {
|
||||
// Getxattr returns all of the extended attributes for the file at path.
|
||||
// Typically, this takes a syscall call to Listxattr and Getxattr.
|
||||
Getxattr(path string) (map[string][]byte, error)
|
||||
|
||||
// Setxattr sets all of the extended attributes on file at path, following
|
||||
// any symbolic links, if necessary. All attributes on the target are
|
||||
// replaced by the values from attr. If the operation fails to set any
|
||||
// attribute, those already applied will not be rolled back.
|
||||
Setxattr(path string, attr map[string][]byte) error
|
||||
}
|
||||
|
||||
// LXAttrDriver should be implemented by drivers on operating systems and
|
||||
// filesystems that support setting and getting extended attributes on
|
||||
// symbolic links. If this is not implemented, extended attributes will be
|
||||
// ignored on symbolic links.
|
||||
type LXAttrDriver interface {
|
||||
// LGetxattr returns all of the extended attributes for the file at path
|
||||
// and does not follow symlinks. Typically, this takes a syscall call to
|
||||
// Llistxattr and Lgetxattr.
|
||||
LGetxattr(path string) (map[string][]byte, error)
|
||||
|
||||
// LSetxattr sets all of the extended attributes on file at path, without
|
||||
// following symbolic links. All attributes on the target are replaced by
|
||||
// the values from attr. If the operation fails to set any attribute,
|
||||
// those already applied will not be rolled back.
|
||||
LSetxattr(path string, attr map[string][]byte) error
|
||||
}
|
||||
|
||||
type DeviceInfoDriver interface {
|
||||
DeviceInfo(fi os.FileInfo) (maj uint64, min uint64, err error)
|
||||
}
|
||||
|
||||
// driver is a simple default implementation that sends calls out to the "os"
|
||||
// package. Extend the "driver" type in system-specific files to add support,
|
||||
// such as xattrs, which can add support at compile time.
|
||||
type driver struct{}
|
||||
|
||||
var _ File = &os.File{}
|
||||
|
||||
// LocalDriver is the exported Driver struct for convenience.
|
||||
var LocalDriver Driver = &driver{}
|
||||
|
||||
func (d *driver) Open(p string) (File, error) {
|
||||
return os.Open(p)
|
||||
}
|
||||
|
||||
func (d *driver) OpenFile(path string, flag int, perm os.FileMode) (File, error) {
|
||||
return os.OpenFile(path, flag, perm)
|
||||
}
|
||||
|
||||
func (d *driver) Stat(p string) (os.FileInfo, error) {
|
||||
return os.Stat(p)
|
||||
}
|
||||
|
||||
func (d *driver) Lstat(p string) (os.FileInfo, error) {
|
||||
return os.Lstat(p)
|
||||
}
|
||||
|
||||
func (d *driver) Readlink(p string) (string, error) {
|
||||
return os.Readlink(p)
|
||||
}
|
||||
|
||||
func (d *driver) Mkdir(p string, mode os.FileMode) error {
|
||||
return os.Mkdir(p, mode)
|
||||
}
|
||||
|
||||
// Remove is used to unlink files and remove directories.
|
||||
// This is following the golang os package api which
|
||||
// combines the operations into a higher level Remove
|
||||
// function. If explicit unlinking or directory removal
|
||||
// to mirror system call is required, they should be
|
||||
// split up at that time.
|
||||
func (d *driver) Remove(path string) error {
|
||||
return os.Remove(path)
|
||||
}
|
||||
|
||||
func (d *driver) Link(oldname, newname string) error {
|
||||
return os.Link(oldname, newname)
|
||||
}
|
||||
|
||||
func (d *driver) Lchown(name string, uid, gid int64) error {
|
||||
// TODO: error out if uid excesses int bit width?
|
||||
return os.Lchown(name, int(uid), int(gid))
|
||||
}
|
||||
|
||||
func (d *driver) Symlink(oldname, newname string) error {
|
||||
return os.Symlink(oldname, newname)
|
||||
}
|
||||
|
||||
func (d *driver) MkdirAll(path string, perm os.FileMode) error {
|
||||
return os.MkdirAll(path, perm)
|
||||
}
|
||||
|
||||
func (d *driver) RemoveAll(path string) error {
|
||||
return os.RemoveAll(path)
|
||||
}
|
122
vendor/github.com/containerd/continuity/driver/driver_unix.go
generated
vendored
Normal file
122
vendor/github.com/containerd/continuity/driver/driver_unix.go
generated
vendored
Normal file
|
@ -0,0 +1,122 @@
|
|||
// +build linux darwin freebsd solaris
|
||||
|
||||
package driver
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
|
||||
"github.com/containerd/continuity/devices"
|
||||
"github.com/containerd/continuity/sysx"
|
||||
)
|
||||
|
||||
func (d *driver) Mknod(path string, mode os.FileMode, major, minor int) error {
|
||||
return devices.Mknod(path, mode, major, minor)
|
||||
}
|
||||
|
||||
func (d *driver) Mkfifo(path string, mode os.FileMode) error {
|
||||
if mode&os.ModeNamedPipe == 0 {
|
||||
return errors.New("mode passed to Mkfifo does not have the named pipe bit set")
|
||||
}
|
||||
// mknod with a mode that has ModeNamedPipe set creates a fifo, not a
|
||||
// device.
|
||||
return devices.Mknod(path, mode, 0, 0)
|
||||
}
|
||||
|
||||
// Lchmod changes the mode of an file not following symlinks.
|
||||
func (d *driver) Lchmod(path string, mode os.FileMode) (err error) {
|
||||
if !filepath.IsAbs(path) {
|
||||
path, err = filepath.Abs(path)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
return sysx.Fchmodat(0, path, uint32(mode), sysx.AtSymlinkNofollow)
|
||||
}
|
||||
|
||||
// Getxattr returns all of the extended attributes for the file at path p.
|
||||
func (d *driver) Getxattr(p string) (map[string][]byte, error) {
|
||||
xattrs, err := sysx.Listxattr(p)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("listing %s xattrs: %v", p, err)
|
||||
}
|
||||
|
||||
sort.Strings(xattrs)
|
||||
m := make(map[string][]byte, len(xattrs))
|
||||
|
||||
for _, attr := range xattrs {
|
||||
value, err := sysx.Getxattr(p, attr)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("getting %q xattr on %s: %v", attr, p, err)
|
||||
}
|
||||
|
||||
// NOTE(stevvooe): This append/copy tricky relies on unique
|
||||
// xattrs. Break this out into an alloc/copy if xattrs are no
|
||||
// longer unique.
|
||||
m[attr] = append(m[attr], value...)
|
||||
}
|
||||
|
||||
return m, nil
|
||||
}
|
||||
|
||||
// Setxattr sets all of the extended attributes on file at path, following
|
||||
// any symbolic links, if necessary. All attributes on the target are
|
||||
// replaced by the values from attr. If the operation fails to set any
|
||||
// attribute, those already applied will not be rolled back.
|
||||
func (d *driver) Setxattr(path string, attrMap map[string][]byte) error {
|
||||
for attr, value := range attrMap {
|
||||
if err := sysx.Setxattr(path, attr, value, 0); err != nil {
|
||||
return fmt.Errorf("error setting xattr %q on %s: %v", attr, path, err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// LGetxattr returns all of the extended attributes for the file at path p
|
||||
// not following symbolic links.
|
||||
func (d *driver) LGetxattr(p string) (map[string][]byte, error) {
|
||||
xattrs, err := sysx.LListxattr(p)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("listing %s xattrs: %v", p, err)
|
||||
}
|
||||
|
||||
sort.Strings(xattrs)
|
||||
m := make(map[string][]byte, len(xattrs))
|
||||
|
||||
for _, attr := range xattrs {
|
||||
value, err := sysx.LGetxattr(p, attr)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("getting %q xattr on %s: %v", attr, p, err)
|
||||
}
|
||||
|
||||
// NOTE(stevvooe): This append/copy tricky relies on unique
|
||||
// xattrs. Break this out into an alloc/copy if xattrs are no
|
||||
// longer unique.
|
||||
m[attr] = append(m[attr], value...)
|
||||
}
|
||||
|
||||
return m, nil
|
||||
}
|
||||
|
||||
// LSetxattr sets all of the extended attributes on file at path, not
|
||||
// following any symbolic links. All attributes on the target are
|
||||
// replaced by the values from attr. If the operation fails to set any
|
||||
// attribute, those already applied will not be rolled back.
|
||||
func (d *driver) LSetxattr(path string, attrMap map[string][]byte) error {
|
||||
for attr, value := range attrMap {
|
||||
if err := sysx.LSetxattr(path, attr, value, 0); err != nil {
|
||||
return fmt.Errorf("error setting xattr %q on %s: %v", attr, path, err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *driver) DeviceInfo(fi os.FileInfo) (maj uint64, min uint64, err error) {
|
||||
return devices.DeviceInfo(fi)
|
||||
}
|
21
vendor/github.com/containerd/continuity/driver/driver_windows.go
generated
vendored
Normal file
21
vendor/github.com/containerd/continuity/driver/driver_windows.go
generated
vendored
Normal file
|
@ -0,0 +1,21 @@
|
|||
package driver
|
||||
|
||||
import (
|
||||
"os"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
func (d *driver) Mknod(path string, mode os.FileMode, major, minor int) error {
|
||||
return errors.Wrap(ErrNotSupported, "cannot create device node on Windows")
|
||||
}
|
||||
|
||||
func (d *driver) Mkfifo(path string, mode os.FileMode) error {
|
||||
return errors.Wrap(ErrNotSupported, "cannot create fifo on Windows")
|
||||
}
|
||||
|
||||
// Lchmod changes the mode of an file not following symlinks.
|
||||
func (d *driver) Lchmod(path string, mode os.FileMode) (err error) {
|
||||
// TODO: Use Window's equivalent
|
||||
return os.Chmod(path, mode)
|
||||
}
|
74
vendor/github.com/containerd/continuity/driver/utils.go
generated
vendored
Normal file
74
vendor/github.com/containerd/continuity/driver/utils.go
generated
vendored
Normal file
|
@ -0,0 +1,74 @@
|
|||
package driver
|
||||
|
||||
import (
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"sort"
|
||||
)
|
||||
|
||||
// ReadFile works the same as ioutil.ReadFile with the Driver abstraction
|
||||
func ReadFile(r Driver, filename string) ([]byte, error) {
|
||||
f, err := r.Open(filename)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
data, err := ioutil.ReadAll(f)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return data, nil
|
||||
}
|
||||
|
||||
// WriteFile works the same as ioutil.WriteFile with the Driver abstraction
|
||||
func WriteFile(r Driver, filename string, data []byte, perm os.FileMode) error {
|
||||
f, err := r.OpenFile(filename, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, perm)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
n, err := f.Write(data)
|
||||
if err != nil {
|
||||
return err
|
||||
} else if n != len(data) {
|
||||
return io.ErrShortWrite
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// ReadDir works the same as ioutil.ReadDir with the Driver abstraction
|
||||
func ReadDir(r Driver, dirname string) ([]os.FileInfo, error) {
|
||||
f, err := r.Open(dirname)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
dirs, err := f.Readdir(-1)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
sort.Sort(fileInfos(dirs))
|
||||
return dirs, nil
|
||||
}
|
||||
|
||||
// Simple implementation of the sort.Interface for os.FileInfo
|
||||
type fileInfos []os.FileInfo
|
||||
|
||||
func (fis fileInfos) Len() int {
|
||||
return len(fis)
|
||||
}
|
||||
|
||||
func (fis fileInfos) Less(i, j int) bool {
|
||||
return fis[i].Name() < fis[j].Name()
|
||||
}
|
||||
|
||||
func (fis fileInfos) Swap(i, j int) {
|
||||
fis[i], fis[j] = fis[j], fis[i]
|
||||
}
|
119
vendor/github.com/containerd/continuity/fs/copy.go
generated
vendored
Normal file
119
vendor/github.com/containerd/continuity/fs/copy.go
generated
vendored
Normal file
|
@ -0,0 +1,119 @@
|
|||
package fs
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sync"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
var bufferPool = &sync.Pool{
|
||||
New: func() interface{} {
|
||||
buffer := make([]byte, 32*1024)
|
||||
return &buffer
|
||||
},
|
||||
}
|
||||
|
||||
// CopyDir copies the directory from src to dst.
|
||||
// Most efficient copy of files is attempted.
|
||||
func CopyDir(dst, src string) error {
|
||||
inodes := map[uint64]string{}
|
||||
return copyDirectory(dst, src, inodes)
|
||||
}
|
||||
|
||||
func copyDirectory(dst, src string, inodes map[uint64]string) error {
|
||||
stat, err := os.Stat(src)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "failed to stat %s", src)
|
||||
}
|
||||
if !stat.IsDir() {
|
||||
return errors.Errorf("source is not directory")
|
||||
}
|
||||
|
||||
if st, err := os.Stat(dst); err != nil {
|
||||
if err := os.Mkdir(dst, stat.Mode()); err != nil {
|
||||
return errors.Wrapf(err, "failed to mkdir %s", dst)
|
||||
}
|
||||
} else if !st.IsDir() {
|
||||
return errors.Errorf("cannot copy to non-directory: %s", dst)
|
||||
} else {
|
||||
if err := os.Chmod(dst, stat.Mode()); err != nil {
|
||||
return errors.Wrapf(err, "failed to chmod on %s", dst)
|
||||
}
|
||||
}
|
||||
|
||||
fis, err := ioutil.ReadDir(src)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "failed to read %s", src)
|
||||
}
|
||||
|
||||
if err := copyFileInfo(stat, dst); err != nil {
|
||||
return errors.Wrapf(err, "failed to copy file info for %s", dst)
|
||||
}
|
||||
|
||||
for _, fi := range fis {
|
||||
source := filepath.Join(src, fi.Name())
|
||||
target := filepath.Join(dst, fi.Name())
|
||||
|
||||
switch {
|
||||
case fi.IsDir():
|
||||
if err := copyDirectory(target, source, inodes); err != nil {
|
||||
return err
|
||||
}
|
||||
continue
|
||||
case (fi.Mode() & os.ModeType) == 0:
|
||||
link, err := getLinkSource(target, fi, inodes)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to get hardlink")
|
||||
}
|
||||
if link != "" {
|
||||
if err := os.Link(link, target); err != nil {
|
||||
return errors.Wrap(err, "failed to create hard link")
|
||||
}
|
||||
} else if err := copyFile(source, target); err != nil {
|
||||
return errors.Wrap(err, "failed to copy files")
|
||||
}
|
||||
case (fi.Mode() & os.ModeSymlink) == os.ModeSymlink:
|
||||
link, err := os.Readlink(source)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "failed to read link: %s", source)
|
||||
}
|
||||
if err := os.Symlink(link, target); err != nil {
|
||||
return errors.Wrapf(err, "failed to create symlink: %s", target)
|
||||
}
|
||||
case (fi.Mode() & os.ModeDevice) == os.ModeDevice:
|
||||
if err := copyDevice(target, fi); err != nil {
|
||||
return errors.Wrapf(err, "failed to create device")
|
||||
}
|
||||
default:
|
||||
// TODO: Support pipes and sockets
|
||||
return errors.Wrapf(err, "unsupported mode %s", fi.Mode())
|
||||
}
|
||||
if err := copyFileInfo(fi, target); err != nil {
|
||||
return errors.Wrap(err, "failed to copy file info")
|
||||
}
|
||||
|
||||
if err := copyXAttrs(target, source); err != nil {
|
||||
return errors.Wrap(err, "failed to copy xattrs")
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func copyFile(source, target string) error {
|
||||
src, err := os.Open(source)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "failed to open source %s", source)
|
||||
}
|
||||
defer src.Close()
|
||||
tgt, err := os.Create(target)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "failed to open target %s", target)
|
||||
}
|
||||
defer tgt.Close()
|
||||
|
||||
return copyFileContent(tgt, src)
|
||||
}
|
95
vendor/github.com/containerd/continuity/fs/copy_linux.go
generated
vendored
Normal file
95
vendor/github.com/containerd/continuity/fs/copy_linux.go
generated
vendored
Normal file
|
@ -0,0 +1,95 @@
|
|||
package fs
|
||||
|
||||
import (
|
||||
"io"
|
||||
"os"
|
||||
"syscall"
|
||||
|
||||
"github.com/containerd/continuity/sysx"
|
||||
"github.com/pkg/errors"
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
func copyFileInfo(fi os.FileInfo, name string) error {
|
||||
st := fi.Sys().(*syscall.Stat_t)
|
||||
if err := os.Lchown(name, int(st.Uid), int(st.Gid)); err != nil {
|
||||
if os.IsPermission(err) {
|
||||
// Normally if uid/gid are the same this would be a no-op, but some
|
||||
// filesystems may still return EPERM... for instance NFS does this.
|
||||
// In such a case, this is not an error.
|
||||
if dstStat, err2 := os.Lstat(name); err2 == nil {
|
||||
st2 := dstStat.Sys().(*syscall.Stat_t)
|
||||
if st.Uid == st2.Uid && st.Gid == st2.Gid {
|
||||
err = nil
|
||||
}
|
||||
}
|
||||
}
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "failed to chown %s", name)
|
||||
}
|
||||
}
|
||||
|
||||
if (fi.Mode() & os.ModeSymlink) != os.ModeSymlink {
|
||||
if err := os.Chmod(name, fi.Mode()); err != nil {
|
||||
return errors.Wrapf(err, "failed to chmod %s", name)
|
||||
}
|
||||
}
|
||||
|
||||
timespec := []unix.Timespec{unix.Timespec(StatAtime(st)), unix.Timespec(StatMtime(st))}
|
||||
if err := unix.UtimesNanoAt(unix.AT_FDCWD, name, timespec, unix.AT_SYMLINK_NOFOLLOW); err != nil {
|
||||
return errors.Wrapf(err, "failed to utime %s", name)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func copyFileContent(dst, src *os.File) error {
|
||||
st, err := src.Stat()
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "unable to stat source")
|
||||
}
|
||||
|
||||
n, err := unix.CopyFileRange(int(src.Fd()), nil, int(dst.Fd()), nil, int(st.Size()), 0)
|
||||
if err != nil {
|
||||
if err != unix.ENOSYS && err != unix.EXDEV {
|
||||
return errors.Wrap(err, "copy file range failed")
|
||||
}
|
||||
|
||||
buf := bufferPool.Get().(*[]byte)
|
||||
_, err = io.CopyBuffer(dst, src, *buf)
|
||||
bufferPool.Put(buf)
|
||||
return err
|
||||
}
|
||||
|
||||
if int64(n) != st.Size() {
|
||||
return errors.Wrapf(err, "short copy: %d of %d", int64(n), st.Size())
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func copyXAttrs(dst, src string) error {
|
||||
xattrKeys, err := sysx.LListxattr(src)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "failed to list xattrs on %s", src)
|
||||
}
|
||||
for _, xattr := range xattrKeys {
|
||||
data, err := sysx.LGetxattr(src, xattr)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "failed to get xattr %q on %s", xattr, src)
|
||||
}
|
||||
if err := sysx.LSetxattr(dst, xattr, data, 0); err != nil {
|
||||
return errors.Wrapf(err, "failed to set xattr %q on %s", xattr, dst)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func copyDevice(dst string, fi os.FileInfo) error {
|
||||
st, ok := fi.Sys().(*syscall.Stat_t)
|
||||
if !ok {
|
||||
return errors.New("unsupported stat type")
|
||||
}
|
||||
return unix.Mknod(dst, uint32(fi.Mode()), int(st.Rdev))
|
||||
}
|
71
vendor/github.com/containerd/continuity/fs/copy_test.go
generated
vendored
Normal file
71
vendor/github.com/containerd/continuity/fs/copy_test.go
generated
vendored
Normal file
|
@ -0,0 +1,71 @@
|
|||
package fs
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
_ "crypto/sha256"
|
||||
|
||||
"github.com/containerd/continuity/fs/fstest"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
// TODO: Create copy directory which requires privilege
|
||||
// chown
|
||||
// mknod
|
||||
// setxattr fstest.SetXAttr("/home", "trusted.overlay.opaque", "y"),
|
||||
|
||||
func TestCopyDirectory(t *testing.T) {
|
||||
apply := fstest.Apply(
|
||||
fstest.CreateDir("/etc/", 0755),
|
||||
fstest.CreateFile("/etc/hosts", []byte("localhost 127.0.0.1"), 0644),
|
||||
fstest.Link("/etc/hosts", "/etc/hosts.allow"),
|
||||
fstest.CreateDir("/usr/local/lib", 0755),
|
||||
fstest.CreateFile("/usr/local/lib/libnothing.so", []byte{0x00, 0x00}, 0755),
|
||||
fstest.Symlink("libnothing.so", "/usr/local/lib/libnothing.so.2"),
|
||||
fstest.CreateDir("/home", 0755),
|
||||
)
|
||||
|
||||
if err := testCopy(apply); err != nil {
|
||||
t.Fatalf("Copy test failed: %+v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// This test used to fail because link-no-nothing.txt would be copied first,
|
||||
// then file operations in dst during the CopyDir would follow the symlink and
|
||||
// fail.
|
||||
func TestCopyDirectoryWithLocalSymlink(t *testing.T) {
|
||||
apply := fstest.Apply(
|
||||
fstest.CreateFile("nothing.txt", []byte{0x00, 0x00}, 0755),
|
||||
fstest.Symlink("nothing.txt", "link-no-nothing.txt"),
|
||||
)
|
||||
|
||||
if err := testCopy(apply); err != nil {
|
||||
t.Fatalf("Copy test failed: %+v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func testCopy(apply fstest.Applier) error {
|
||||
t1, err := ioutil.TempDir("", "test-copy-src-")
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to create temporary directory")
|
||||
}
|
||||
defer os.RemoveAll(t1)
|
||||
|
||||
t2, err := ioutil.TempDir("", "test-copy-dst-")
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to create temporary directory")
|
||||
}
|
||||
defer os.RemoveAll(t2)
|
||||
|
||||
if err := apply.Apply(t1); err != nil {
|
||||
return errors.Wrap(err, "failed to apply changes")
|
||||
}
|
||||
|
||||
if err := CopyDir(t2, t1); err != nil {
|
||||
return errors.Wrap(err, "failed to copy")
|
||||
}
|
||||
|
||||
return fstest.CheckDirectoryEqual(t1, t2)
|
||||
}
|
80
vendor/github.com/containerd/continuity/fs/copy_unix.go
generated
vendored
Normal file
80
vendor/github.com/containerd/continuity/fs/copy_unix.go
generated
vendored
Normal file
|
@ -0,0 +1,80 @@
|
|||
// +build solaris darwin freebsd
|
||||
|
||||
package fs
|
||||
|
||||
import (
|
||||
"io"
|
||||
"os"
|
||||
"syscall"
|
||||
|
||||
"github.com/containerd/continuity/sysx"
|
||||
"github.com/pkg/errors"
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
func copyFileInfo(fi os.FileInfo, name string) error {
|
||||
st := fi.Sys().(*syscall.Stat_t)
|
||||
if err := os.Lchown(name, int(st.Uid), int(st.Gid)); err != nil {
|
||||
if os.IsPermission(err) {
|
||||
// Normally if uid/gid are the same this would be a no-op, but some
|
||||
// filesystems may still return EPERM... for instance NFS does this.
|
||||
// In such a case, this is not an error.
|
||||
if dstStat, err2 := os.Lstat(name); err2 == nil {
|
||||
st2 := dstStat.Sys().(*syscall.Stat_t)
|
||||
if st.Uid == st2.Uid && st.Gid == st2.Gid {
|
||||
err = nil
|
||||
}
|
||||
}
|
||||
}
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "failed to chown %s", name)
|
||||
}
|
||||
}
|
||||
|
||||
if (fi.Mode() & os.ModeSymlink) != os.ModeSymlink {
|
||||
if err := os.Chmod(name, fi.Mode()); err != nil {
|
||||
return errors.Wrapf(err, "failed to chmod %s", name)
|
||||
}
|
||||
}
|
||||
|
||||
timespec := []syscall.Timespec{StatAtime(st), StatMtime(st)}
|
||||
if err := syscall.UtimesNano(name, timespec); err != nil {
|
||||
return errors.Wrapf(err, "failed to utime %s", name)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func copyFileContent(dst, src *os.File) error {
|
||||
buf := bufferPool.Get().(*[]byte)
|
||||
_, err := io.CopyBuffer(dst, src, *buf)
|
||||
bufferPool.Put(buf)
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func copyXAttrs(dst, src string) error {
|
||||
xattrKeys, err := sysx.LListxattr(src)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "failed to list xattrs on %s", src)
|
||||
}
|
||||
for _, xattr := range xattrKeys {
|
||||
data, err := sysx.LGetxattr(src, xattr)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "failed to get xattr %q on %s", xattr, src)
|
||||
}
|
||||
if err := sysx.LSetxattr(dst, xattr, data, 0); err != nil {
|
||||
return errors.Wrapf(err, "failed to set xattr %q on %s", xattr, dst)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func copyDevice(dst string, fi os.FileInfo) error {
|
||||
st, ok := fi.Sys().(*syscall.Stat_t)
|
||||
if !ok {
|
||||
return errors.New("unsupported stat type")
|
||||
}
|
||||
return unix.Mknod(dst, uint32(fi.Mode()), int(st.Rdev))
|
||||
}
|
33
vendor/github.com/containerd/continuity/fs/copy_windows.go
generated
vendored
Normal file
33
vendor/github.com/containerd/continuity/fs/copy_windows.go
generated
vendored
Normal file
|
@ -0,0 +1,33 @@
|
|||
package fs
|
||||
|
||||
import (
|
||||
"io"
|
||||
"os"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
func copyFileInfo(fi os.FileInfo, name string) error {
|
||||
if err := os.Chmod(name, fi.Mode()); err != nil {
|
||||
return errors.Wrapf(err, "failed to chmod %s", name)
|
||||
}
|
||||
|
||||
// TODO: copy windows specific metadata
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func copyFileContent(dst, src *os.File) error {
|
||||
buf := bufferPool.Get().(*[]byte)
|
||||
_, err := io.CopyBuffer(dst, src, *buf)
|
||||
bufferPool.Put(buf)
|
||||
return err
|
||||
}
|
||||
|
||||
func copyXAttrs(dst, src string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func copyDevice(dst string, fi os.FileInfo) error {
|
||||
return errors.New("device copy not supported")
|
||||
}
|
310
vendor/github.com/containerd/continuity/fs/diff.go
generated
vendored
Normal file
310
vendor/github.com/containerd/continuity/fs/diff.go
generated
vendored
Normal file
|
@ -0,0 +1,310 @@
|
|||
package fs
|
||||
|
||||
import (
|
||||
"context"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"golang.org/x/sync/errgroup"
|
||||
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
// ChangeKind is the type of modification that
|
||||
// a change is making.
|
||||
type ChangeKind int
|
||||
|
||||
const (
|
||||
// ChangeKindUnmodified represents an unmodified
|
||||
// file
|
||||
ChangeKindUnmodified = iota
|
||||
|
||||
// ChangeKindAdd represents an addition of
|
||||
// a file
|
||||
ChangeKindAdd
|
||||
|
||||
// ChangeKindModify represents a change to
|
||||
// an existing file
|
||||
ChangeKindModify
|
||||
|
||||
// ChangeKindDelete represents a delete of
|
||||
// a file
|
||||
ChangeKindDelete
|
||||
)
|
||||
|
||||
func (k ChangeKind) String() string {
|
||||
switch k {
|
||||
case ChangeKindUnmodified:
|
||||
return "unmodified"
|
||||
case ChangeKindAdd:
|
||||
return "add"
|
||||
case ChangeKindModify:
|
||||
return "modify"
|
||||
case ChangeKindDelete:
|
||||
return "delete"
|
||||
default:
|
||||
return ""
|
||||
}
|
||||
}
|
||||
|
||||
// Change represents single change between a diff and its parent.
|
||||
type Change struct {
|
||||
Kind ChangeKind
|
||||
Path string
|
||||
}
|
||||
|
||||
// ChangeFunc is the type of function called for each change
|
||||
// computed during a directory changes calculation.
|
||||
type ChangeFunc func(ChangeKind, string, os.FileInfo, error) error
|
||||
|
||||
// Changes computes changes between two directories calling the
|
||||
// given change function for each computed change. The first
|
||||
// directory is intended to the base directory and second
|
||||
// directory the changed directory.
|
||||
//
|
||||
// The change callback is called by the order of path names and
|
||||
// should be appliable in that order.
|
||||
// Due to this apply ordering, the following is true
|
||||
// - Removed directory trees only create a single change for the root
|
||||
// directory removed. Remaining changes are implied.
|
||||
// - A directory which is modified to become a file will not have
|
||||
// delete entries for sub-path items, their removal is implied
|
||||
// by the removal of the parent directory.
|
||||
//
|
||||
// Opaque directories will not be treated specially and each file
|
||||
// removed from the base directory will show up as a removal.
|
||||
//
|
||||
// File content comparisons will be done on files which have timestamps
|
||||
// which may have been truncated. If either of the files being compared
|
||||
// has a zero value nanosecond value, each byte will be compared for
|
||||
// differences. If 2 files have the same seconds value but different
|
||||
// nanosecond values where one of those values is zero, the files will
|
||||
// be considered unchanged if the content is the same. This behavior
|
||||
// is to account for timestamp truncation during archiving.
|
||||
func Changes(ctx context.Context, a, b string, changeFn ChangeFunc) error {
|
||||
if a == "" {
|
||||
logrus.Debugf("Using single walk diff for %s", b)
|
||||
return addDirChanges(ctx, changeFn, b)
|
||||
} else if diffOptions := detectDirDiff(b, a); diffOptions != nil {
|
||||
logrus.Debugf("Using single walk diff for %s from %s", diffOptions.diffDir, a)
|
||||
return diffDirChanges(ctx, changeFn, a, diffOptions)
|
||||
}
|
||||
|
||||
logrus.Debugf("Using double walk diff for %s from %s", b, a)
|
||||
return doubleWalkDiff(ctx, changeFn, a, b)
|
||||
}
|
||||
|
||||
func addDirChanges(ctx context.Context, changeFn ChangeFunc, root string) error {
|
||||
return filepath.Walk(root, func(path string, f os.FileInfo, err error) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Rebase path
|
||||
path, err = filepath.Rel(root, path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
path = filepath.Join(string(os.PathSeparator), path)
|
||||
|
||||
// Skip root
|
||||
if path == string(os.PathSeparator) {
|
||||
return nil
|
||||
}
|
||||
|
||||
return changeFn(ChangeKindAdd, path, f, nil)
|
||||
})
|
||||
}
|
||||
|
||||
// diffDirOptions is used when the diff can be directly calculated from
|
||||
// a diff directory to its base, without walking both trees.
|
||||
type diffDirOptions struct {
|
||||
diffDir string
|
||||
skipChange func(string) (bool, error)
|
||||
deleteChange func(string, string, os.FileInfo) (string, error)
|
||||
}
|
||||
|
||||
// diffDirChanges walks the diff directory and compares changes against the base.
|
||||
func diffDirChanges(ctx context.Context, changeFn ChangeFunc, base string, o *diffDirOptions) error {
|
||||
changedDirs := make(map[string]struct{})
|
||||
return filepath.Walk(o.diffDir, func(path string, f os.FileInfo, err error) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Rebase path
|
||||
path, err = filepath.Rel(o.diffDir, path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
path = filepath.Join(string(os.PathSeparator), path)
|
||||
|
||||
// Skip root
|
||||
if path == string(os.PathSeparator) {
|
||||
return nil
|
||||
}
|
||||
|
||||
// TODO: handle opaqueness, start new double walker at this
|
||||
// location to get deletes, and skip tree in single walker
|
||||
|
||||
if o.skipChange != nil {
|
||||
if skip, err := o.skipChange(path); skip {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
var kind ChangeKind
|
||||
|
||||
deletedFile, err := o.deleteChange(o.diffDir, path, f)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Find out what kind of modification happened
|
||||
if deletedFile != "" {
|
||||
path = deletedFile
|
||||
kind = ChangeKindDelete
|
||||
f = nil
|
||||
} else {
|
||||
// Otherwise, the file was added
|
||||
kind = ChangeKindAdd
|
||||
|
||||
// ...Unless it already existed in a base, in which case, it's a modification
|
||||
stat, err := os.Stat(filepath.Join(base, path))
|
||||
if err != nil && !os.IsNotExist(err) {
|
||||
return err
|
||||
}
|
||||
if err == nil {
|
||||
// The file existed in the base, so that's a modification
|
||||
|
||||
// However, if it's a directory, maybe it wasn't actually modified.
|
||||
// If you modify /foo/bar/baz, then /foo will be part of the changed files only because it's the parent of bar
|
||||
if stat.IsDir() && f.IsDir() {
|
||||
if f.Size() == stat.Size() && f.Mode() == stat.Mode() && sameFsTime(f.ModTime(), stat.ModTime()) {
|
||||
// Both directories are the same, don't record the change
|
||||
return nil
|
||||
}
|
||||
}
|
||||
kind = ChangeKindModify
|
||||
}
|
||||
}
|
||||
|
||||
// If /foo/bar/file.txt is modified, then /foo/bar must be part of the changed files.
|
||||
// This block is here to ensure the change is recorded even if the
|
||||
// modify time, mode and size of the parent directory in the rw and ro layers are all equal.
|
||||
// Check https://github.com/docker/docker/pull/13590 for details.
|
||||
if f.IsDir() {
|
||||
changedDirs[path] = struct{}{}
|
||||
}
|
||||
if kind == ChangeKindAdd || kind == ChangeKindDelete {
|
||||
parent := filepath.Dir(path)
|
||||
if _, ok := changedDirs[parent]; !ok && parent != "/" {
|
||||
pi, err := os.Stat(filepath.Join(o.diffDir, parent))
|
||||
if err := changeFn(ChangeKindModify, parent, pi, err); err != nil {
|
||||
return err
|
||||
}
|
||||
changedDirs[parent] = struct{}{}
|
||||
}
|
||||
}
|
||||
|
||||
return changeFn(kind, path, f, nil)
|
||||
})
|
||||
}
|
||||
|
||||
// doubleWalkDiff walks both directories to create a diff
|
||||
func doubleWalkDiff(ctx context.Context, changeFn ChangeFunc, a, b string) (err error) {
|
||||
g, ctx := errgroup.WithContext(ctx)
|
||||
|
||||
var (
|
||||
c1 = make(chan *currentPath)
|
||||
c2 = make(chan *currentPath)
|
||||
|
||||
f1, f2 *currentPath
|
||||
rmdir string
|
||||
)
|
||||
g.Go(func() error {
|
||||
defer close(c1)
|
||||
return pathWalk(ctx, a, c1)
|
||||
})
|
||||
g.Go(func() error {
|
||||
defer close(c2)
|
||||
return pathWalk(ctx, b, c2)
|
||||
})
|
||||
g.Go(func() error {
|
||||
for c1 != nil || c2 != nil {
|
||||
if f1 == nil && c1 != nil {
|
||||
f1, err = nextPath(ctx, c1)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if f1 == nil {
|
||||
c1 = nil
|
||||
}
|
||||
}
|
||||
|
||||
if f2 == nil && c2 != nil {
|
||||
f2, err = nextPath(ctx, c2)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if f2 == nil {
|
||||
c2 = nil
|
||||
}
|
||||
}
|
||||
if f1 == nil && f2 == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
var f os.FileInfo
|
||||
k, p := pathChange(f1, f2)
|
||||
switch k {
|
||||
case ChangeKindAdd:
|
||||
if rmdir != "" {
|
||||
rmdir = ""
|
||||
}
|
||||
f = f2.f
|
||||
f2 = nil
|
||||
case ChangeKindDelete:
|
||||
// Check if this file is already removed by being
|
||||
// under of a removed directory
|
||||
if rmdir != "" && strings.HasPrefix(f1.path, rmdir) {
|
||||
f1 = nil
|
||||
continue
|
||||
} else if f1.f.IsDir() {
|
||||
rmdir = f1.path + string(os.PathSeparator)
|
||||
} else if rmdir != "" {
|
||||
rmdir = ""
|
||||
}
|
||||
f1 = nil
|
||||
case ChangeKindModify:
|
||||
same, err := sameFile(f1, f2)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if f1.f.IsDir() && !f2.f.IsDir() {
|
||||
rmdir = f1.path + string(os.PathSeparator)
|
||||
} else if rmdir != "" {
|
||||
rmdir = ""
|
||||
}
|
||||
f = f2.f
|
||||
f1 = nil
|
||||
f2 = nil
|
||||
if same {
|
||||
if !isLinked(f) {
|
||||
continue
|
||||
}
|
||||
k = ChangeKindUnmodified
|
||||
}
|
||||
}
|
||||
if err := changeFn(k, p, f, nil); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
})
|
||||
|
||||
return g.Wait()
|
||||
}
|
406
vendor/github.com/containerd/continuity/fs/diff_test.go
generated
vendored
Normal file
406
vendor/github.com/containerd/continuity/fs/diff_test.go
generated
vendored
Normal file
|
@ -0,0 +1,406 @@
|
|||
package fs
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/containerd/continuity/fs/fstest"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
// TODO: Additional tests
|
||||
// - capability test (requires privilege)
|
||||
// - chown test (requires privilege)
|
||||
// - symlink test
|
||||
// - hardlink test
|
||||
|
||||
func skipDiffTestOnWindows(t *testing.T) {
|
||||
if runtime.GOOS == "windows" {
|
||||
t.Skip("diff implementation is incomplete on windows")
|
||||
}
|
||||
}
|
||||
|
||||
func TestSimpleDiff(t *testing.T) {
|
||||
skipDiffTestOnWindows(t)
|
||||
l1 := fstest.Apply(
|
||||
fstest.CreateDir("/etc", 0755),
|
||||
fstest.CreateFile("/etc/hosts", []byte("mydomain 10.0.0.1"), 0644),
|
||||
fstest.CreateFile("/etc/profile", []byte("PATH=/usr/bin"), 0644),
|
||||
fstest.CreateFile("/etc/unchanged", []byte("PATH=/usr/bin"), 0644),
|
||||
fstest.CreateFile("/etc/unexpected", []byte("#!/bin/sh"), 0644),
|
||||
)
|
||||
l2 := fstest.Apply(
|
||||
fstest.CreateFile("/etc/hosts", []byte("mydomain 10.0.0.120"), 0644),
|
||||
fstest.CreateFile("/etc/profile", []byte("PATH=/usr/bin"), 0666),
|
||||
fstest.CreateDir("/root", 0700),
|
||||
fstest.CreateFile("/root/.bashrc", []byte("PATH=/usr/sbin:/usr/bin"), 0644),
|
||||
fstest.Remove("/etc/unexpected"),
|
||||
)
|
||||
diff := []TestChange{
|
||||
Modify("/etc/hosts"),
|
||||
Modify("/etc/profile"),
|
||||
Delete("/etc/unexpected"),
|
||||
Add("/root"),
|
||||
Add("/root/.bashrc"),
|
||||
}
|
||||
|
||||
if err := testDiffWithBase(l1, l2, diff); err != nil {
|
||||
t.Fatalf("Failed diff with base: %+v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestNestedDeletion(t *testing.T) {
|
||||
skipDiffTestOnWindows(t)
|
||||
l1 := fstest.Apply(
|
||||
fstest.CreateDir("/d0", 0755),
|
||||
fstest.CreateDir("/d1", 0755),
|
||||
fstest.CreateDir("/d1/d2", 0755),
|
||||
fstest.CreateFile("/d1/d2/f1", []byte("mydomain 10.0.0.1"), 0644),
|
||||
)
|
||||
l2 := fstest.Apply(
|
||||
fstest.RemoveAll("/d0"),
|
||||
fstest.RemoveAll("/d1"),
|
||||
)
|
||||
diff := []TestChange{
|
||||
Delete("/d0"),
|
||||
Delete("/d1"),
|
||||
}
|
||||
|
||||
if err := testDiffWithBase(l1, l2, diff); err != nil {
|
||||
t.Fatalf("Failed diff with base: %+v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDirectoryReplace(t *testing.T) {
|
||||
skipDiffTestOnWindows(t)
|
||||
l1 := fstest.Apply(
|
||||
fstest.CreateDir("/dir1", 0755),
|
||||
fstest.CreateFile("/dir1/f1", []byte("#####"), 0644),
|
||||
fstest.CreateDir("/dir1/f2", 0755),
|
||||
fstest.CreateFile("/dir1/f2/f3", []byte("#!/bin/sh"), 0644),
|
||||
)
|
||||
l2 := fstest.Apply(
|
||||
fstest.CreateFile("/dir1/f11", []byte("#New file here"), 0644),
|
||||
fstest.RemoveAll("/dir1/f2"),
|
||||
fstest.CreateFile("/dir1/f2", []byte("Now file"), 0666),
|
||||
)
|
||||
diff := []TestChange{
|
||||
Add("/dir1/f11"),
|
||||
Modify("/dir1/f2"),
|
||||
}
|
||||
|
||||
if err := testDiffWithBase(l1, l2, diff); err != nil {
|
||||
t.Fatalf("Failed diff with base: %+v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRemoveDirectoryTree(t *testing.T) {
|
||||
l1 := fstest.Apply(
|
||||
fstest.CreateDir("/dir1/dir2/dir3", 0755),
|
||||
fstest.CreateFile("/dir1/f1", []byte("f1"), 0644),
|
||||
fstest.CreateFile("/dir1/dir2/f2", []byte("f2"), 0644),
|
||||
)
|
||||
l2 := fstest.Apply(
|
||||
fstest.RemoveAll("/dir1"),
|
||||
)
|
||||
diff := []TestChange{
|
||||
Delete("/dir1"),
|
||||
}
|
||||
|
||||
if err := testDiffWithBase(l1, l2, diff); err != nil {
|
||||
t.Fatalf("Failed diff with base: %+v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestFileReplace(t *testing.T) {
|
||||
l1 := fstest.Apply(
|
||||
fstest.CreateFile("/dir1", []byte("a file, not a directory"), 0644),
|
||||
)
|
||||
l2 := fstest.Apply(
|
||||
fstest.Remove("/dir1"),
|
||||
fstest.CreateDir("/dir1/dir2", 0755),
|
||||
fstest.CreateFile("/dir1/dir2/f1", []byte("also a file"), 0644),
|
||||
)
|
||||
diff := []TestChange{
|
||||
Modify("/dir1"),
|
||||
Add("/dir1/dir2"),
|
||||
Add("/dir1/dir2/f1"),
|
||||
}
|
||||
|
||||
if err := testDiffWithBase(l1, l2, diff); err != nil {
|
||||
t.Fatalf("Failed diff with base: %+v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestParentDirectoryPermission(t *testing.T) {
|
||||
skipDiffTestOnWindows(t)
|
||||
l1 := fstest.Apply(
|
||||
fstest.CreateDir("/dir1", 0700),
|
||||
fstest.CreateDir("/dir2", 0751),
|
||||
fstest.CreateDir("/dir3", 0777),
|
||||
)
|
||||
l2 := fstest.Apply(
|
||||
fstest.CreateDir("/dir1/d", 0700),
|
||||
fstest.CreateFile("/dir1/d/f", []byte("irrelevant"), 0644),
|
||||
fstest.CreateFile("/dir1/f", []byte("irrelevant"), 0644),
|
||||
fstest.CreateFile("/dir2/f", []byte("irrelevant"), 0644),
|
||||
fstest.CreateFile("/dir3/f", []byte("irrelevant"), 0644),
|
||||
)
|
||||
diff := []TestChange{
|
||||
Add("/dir1/d"),
|
||||
Add("/dir1/d/f"),
|
||||
Add("/dir1/f"),
|
||||
Add("/dir2/f"),
|
||||
Add("/dir3/f"),
|
||||
}
|
||||
|
||||
if err := testDiffWithBase(l1, l2, diff); err != nil {
|
||||
t.Fatalf("Failed diff with base: %+v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestUpdateWithSameTime(t *testing.T) {
|
||||
skipDiffTestOnWindows(t)
|
||||
tt := time.Now().Truncate(time.Second)
|
||||
t1 := tt.Add(5 * time.Nanosecond)
|
||||
t2 := tt.Add(6 * time.Nanosecond)
|
||||
l1 := fstest.Apply(
|
||||
fstest.CreateFile("/file-modified-time", []byte("1"), 0644),
|
||||
fstest.Chtimes("/file-modified-time", t1, t1),
|
||||
fstest.CreateFile("/file-no-change", []byte("1"), 0644),
|
||||
fstest.Chtimes("/file-no-change", t1, t1),
|
||||
fstest.CreateFile("/file-same-time", []byte("1"), 0644),
|
||||
fstest.Chtimes("/file-same-time", t1, t1),
|
||||
fstest.CreateFile("/file-truncated-time-1", []byte("1"), 0644),
|
||||
fstest.Chtimes("/file-truncated-time-1", tt, tt),
|
||||
fstest.CreateFile("/file-truncated-time-2", []byte("1"), 0644),
|
||||
fstest.Chtimes("/file-truncated-time-2", tt, tt),
|
||||
fstest.CreateFile("/file-truncated-time-3", []byte("1"), 0644),
|
||||
fstest.Chtimes("/file-truncated-time-3", t1, t1),
|
||||
)
|
||||
l2 := fstest.Apply(
|
||||
fstest.CreateFile("/file-modified-time", []byte("2"), 0644),
|
||||
fstest.Chtimes("/file-modified-time", t2, t2),
|
||||
fstest.CreateFile("/file-no-change", []byte("1"), 0644),
|
||||
fstest.Chtimes("/file-no-change", t1, t1),
|
||||
fstest.CreateFile("/file-same-time", []byte("2"), 0644),
|
||||
fstest.Chtimes("/file-same-time", t1, t1),
|
||||
fstest.CreateFile("/file-truncated-time-1", []byte("1"), 0644),
|
||||
fstest.Chtimes("/file-truncated-time-1", t1, t1),
|
||||
fstest.CreateFile("/file-truncated-time-2", []byte("2"), 0644),
|
||||
fstest.Chtimes("/file-truncated-time-2", tt, tt),
|
||||
fstest.CreateFile("/file-truncated-time-3", []byte("1"), 0644),
|
||||
fstest.Chtimes("/file-truncated-time-3", tt, tt),
|
||||
)
|
||||
diff := []TestChange{
|
||||
Modify("/file-modified-time"),
|
||||
// Include changes with truncated timestamps. Comparing newly
|
||||
// extracted tars which have truncated timestamps will be
|
||||
// expected to produce changes. The expectation is that diff
|
||||
// archives are generated once and kept, newly generated diffs
|
||||
// will not consider cases where only one side is truncated.
|
||||
Modify("/file-truncated-time-1"),
|
||||
Modify("/file-truncated-time-2"),
|
||||
Modify("/file-truncated-time-3"),
|
||||
}
|
||||
|
||||
if err := testDiffWithBase(l1, l2, diff); err != nil {
|
||||
t.Fatalf("Failed diff with base: %+v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// buildkit#172
|
||||
func TestLchtimes(t *testing.T) {
|
||||
skipDiffTestOnWindows(t)
|
||||
mtimes := []time.Time{
|
||||
time.Unix(0, 0), // nsec is 0
|
||||
time.Unix(0, 42), // nsec > 0
|
||||
}
|
||||
for _, mtime := range mtimes {
|
||||
atime := time.Unix(424242, 42)
|
||||
l1 := fstest.Apply(
|
||||
fstest.CreateFile("/foo", []byte("foo"), 0644),
|
||||
fstest.Symlink("/foo", "/lnk0"),
|
||||
fstest.Lchtimes("/lnk0", atime, mtime),
|
||||
)
|
||||
l2 := fstest.Apply() // empty
|
||||
diff := []TestChange{}
|
||||
if err := testDiffWithBase(l1, l2, diff); err != nil {
|
||||
t.Fatalf("Failed diff with base: %+v", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func testDiffWithBase(base, diff fstest.Applier, expected []TestChange) error {
|
||||
t1, err := ioutil.TempDir("", "diff-with-base-lower-")
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to create temp dir")
|
||||
}
|
||||
defer os.RemoveAll(t1)
|
||||
t2, err := ioutil.TempDir("", "diff-with-base-upper-")
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to create temp dir")
|
||||
}
|
||||
defer os.RemoveAll(t2)
|
||||
|
||||
if err := base.Apply(t1); err != nil {
|
||||
return errors.Wrap(err, "failed to apply base filesystem")
|
||||
}
|
||||
|
||||
if err := CopyDir(t2, t1); err != nil {
|
||||
return errors.Wrap(err, "failed to copy base directory")
|
||||
}
|
||||
|
||||
if err := diff.Apply(t2); err != nil {
|
||||
return errors.Wrap(err, "failed to apply diff filesystem")
|
||||
}
|
||||
|
||||
changes, err := collectChanges(t1, t2)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to collect changes")
|
||||
}
|
||||
|
||||
return checkChanges(t2, changes, expected)
|
||||
}
|
||||
|
||||
func TestBaseDirectoryChanges(t *testing.T) {
|
||||
apply := fstest.Apply(
|
||||
fstest.CreateDir("/etc", 0755),
|
||||
fstest.CreateFile("/etc/hosts", []byte("mydomain 10.0.0.1"), 0644),
|
||||
fstest.CreateFile("/etc/profile", []byte("PATH=/usr/bin"), 0644),
|
||||
fstest.CreateDir("/root", 0700),
|
||||
fstest.CreateFile("/root/.bashrc", []byte("PATH=/usr/sbin:/usr/bin"), 0644),
|
||||
)
|
||||
changes := []TestChange{
|
||||
Add("/etc"),
|
||||
Add("/etc/hosts"),
|
||||
Add("/etc/profile"),
|
||||
Add("/root"),
|
||||
Add("/root/.bashrc"),
|
||||
}
|
||||
|
||||
if err := testDiffWithoutBase(apply, changes); err != nil {
|
||||
t.Fatalf("Failed diff without base: %+v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func testDiffWithoutBase(apply fstest.Applier, expected []TestChange) error {
|
||||
tmp, err := ioutil.TempDir("", "diff-without-base-")
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to create temp dir")
|
||||
}
|
||||
defer os.RemoveAll(tmp)
|
||||
|
||||
if err := apply.Apply(tmp); err != nil {
|
||||
return errors.Wrap(err, "failed to apply filesytem changes")
|
||||
}
|
||||
|
||||
changes, err := collectChanges("", tmp)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to collect changes")
|
||||
}
|
||||
|
||||
return checkChanges(tmp, changes, expected)
|
||||
}
|
||||
|
||||
func checkChanges(root string, changes, expected []TestChange) error {
|
||||
if len(changes) != len(expected) {
|
||||
return errors.Errorf("Unexpected number of changes:\n%s", diffString(changes, expected))
|
||||
}
|
||||
for i := range changes {
|
||||
if changes[i].Path != expected[i].Path || changes[i].Kind != expected[i].Kind {
|
||||
return errors.Errorf("Unexpected change at %d:\n%s", i, diffString(changes, expected))
|
||||
}
|
||||
if changes[i].Kind != ChangeKindDelete {
|
||||
filename := filepath.Join(root, changes[i].Path)
|
||||
efi, err := os.Stat(filename)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "failed to stat %q", filename)
|
||||
}
|
||||
afi := changes[i].FileInfo
|
||||
if afi.Size() != efi.Size() {
|
||||
return errors.Errorf("Unexpected change size %d, %q has size %d", afi.Size(), filename, efi.Size())
|
||||
}
|
||||
if afi.Mode() != efi.Mode() {
|
||||
return errors.Errorf("Unexpected change mode %s, %q has mode %s", afi.Mode(), filename, efi.Mode())
|
||||
}
|
||||
if afi.ModTime() != efi.ModTime() {
|
||||
return errors.Errorf("Unexpected change modtime %s, %q has modtime %s", afi.ModTime(), filename, efi.ModTime())
|
||||
}
|
||||
if expected := filepath.Join(root, changes[i].Path); changes[i].Source != expected {
|
||||
return errors.Errorf("Unexpected source path %s, expected %s", changes[i].Source, expected)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
type TestChange struct {
|
||||
Kind ChangeKind
|
||||
Path string
|
||||
FileInfo os.FileInfo
|
||||
Source string
|
||||
}
|
||||
|
||||
func collectChanges(a, b string) ([]TestChange, error) {
|
||||
changes := []TestChange{}
|
||||
err := Changes(context.Background(), a, b, func(k ChangeKind, p string, f os.FileInfo, err error) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
changes = append(changes, TestChange{
|
||||
Kind: k,
|
||||
Path: p,
|
||||
FileInfo: f,
|
||||
Source: filepath.Join(b, p),
|
||||
})
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to compute changes")
|
||||
}
|
||||
|
||||
return changes, nil
|
||||
}
|
||||
|
||||
func diffString(c1, c2 []TestChange) string {
|
||||
return fmt.Sprintf("got(%d):\n%s\nexpected(%d):\n%s", len(c1), changesString(c1), len(c2), changesString(c2))
|
||||
|
||||
}
|
||||
|
||||
func changesString(c []TestChange) string {
|
||||
strs := make([]string, len(c))
|
||||
for i := range c {
|
||||
strs[i] = fmt.Sprintf("\t%s\t%s", c[i].Kind, c[i].Path)
|
||||
}
|
||||
return strings.Join(strs, "\n")
|
||||
}
|
||||
|
||||
func Add(p string) TestChange {
|
||||
return TestChange{
|
||||
Kind: ChangeKindAdd,
|
||||
Path: filepath.FromSlash(p),
|
||||
}
|
||||
}
|
||||
|
||||
func Delete(p string) TestChange {
|
||||
return TestChange{
|
||||
Kind: ChangeKindDelete,
|
||||
Path: filepath.FromSlash(p),
|
||||
}
|
||||
}
|
||||
|
||||
func Modify(p string) TestChange {
|
||||
return TestChange{
|
||||
Kind: ChangeKindModify,
|
||||
Path: filepath.FromSlash(p),
|
||||
}
|
||||
}
|
58
vendor/github.com/containerd/continuity/fs/diff_unix.go
generated
vendored
Normal file
58
vendor/github.com/containerd/continuity/fs/diff_unix.go
generated
vendored
Normal file
|
@ -0,0 +1,58 @@
|
|||
// +build !windows
|
||||
|
||||
package fs
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"os"
|
||||
"syscall"
|
||||
|
||||
"github.com/containerd/continuity/sysx"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
// detectDirDiff returns diff dir options if a directory could
|
||||
// be found in the mount info for upper which is the direct
|
||||
// diff with the provided lower directory
|
||||
func detectDirDiff(upper, lower string) *diffDirOptions {
|
||||
// TODO: get mount options for upper
|
||||
// TODO: detect AUFS
|
||||
// TODO: detect overlay
|
||||
return nil
|
||||
}
|
||||
|
||||
// compareSysStat returns whether the stats are equivalent,
|
||||
// whether the files are considered the same file, and
|
||||
// an error
|
||||
func compareSysStat(s1, s2 interface{}) (bool, error) {
|
||||
ls1, ok := s1.(*syscall.Stat_t)
|
||||
if !ok {
|
||||
return false, nil
|
||||
}
|
||||
ls2, ok := s2.(*syscall.Stat_t)
|
||||
if !ok {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
return ls1.Mode == ls2.Mode && ls1.Uid == ls2.Uid && ls1.Gid == ls2.Gid && ls1.Rdev == ls2.Rdev, nil
|
||||
}
|
||||
|
||||
func compareCapabilities(p1, p2 string) (bool, error) {
|
||||
c1, err := sysx.LGetxattr(p1, "security.capability")
|
||||
if err != nil && err != sysx.ENODATA {
|
||||
return false, errors.Wrapf(err, "failed to get xattr for %s", p1)
|
||||
}
|
||||
c2, err := sysx.LGetxattr(p2, "security.capability")
|
||||
if err != nil && err != sysx.ENODATA {
|
||||
return false, errors.Wrapf(err, "failed to get xattr for %s", p2)
|
||||
}
|
||||
return bytes.Equal(c1, c2), nil
|
||||
}
|
||||
|
||||
func isLinked(f os.FileInfo) bool {
|
||||
s, ok := f.Sys().(*syscall.Stat_t)
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
return !f.IsDir() && s.Nlink > 1
|
||||
}
|
32
vendor/github.com/containerd/continuity/fs/diff_windows.go
generated
vendored
Normal file
32
vendor/github.com/containerd/continuity/fs/diff_windows.go
generated
vendored
Normal file
|
@ -0,0 +1,32 @@
|
|||
package fs
|
||||
|
||||
import (
|
||||
"os"
|
||||
|
||||
"golang.org/x/sys/windows"
|
||||
)
|
||||
|
||||
func detectDirDiff(upper, lower string) *diffDirOptions {
|
||||
return nil
|
||||
}
|
||||
|
||||
func compareSysStat(s1, s2 interface{}) (bool, error) {
|
||||
f1, ok := s1.(windows.Win32FileAttributeData)
|
||||
if !ok {
|
||||
return false, nil
|
||||
}
|
||||
f2, ok := s2.(windows.Win32FileAttributeData)
|
||||
if !ok {
|
||||
return false, nil
|
||||
}
|
||||
return f1.FileAttributes == f2.FileAttributes, nil
|
||||
}
|
||||
|
||||
func compareCapabilities(p1, p2 string) (bool, error) {
|
||||
// TODO: Use windows equivalent
|
||||
return true, nil
|
||||
}
|
||||
|
||||
func isLinked(os.FileInfo) bool {
|
||||
return false
|
||||
}
|
87
vendor/github.com/containerd/continuity/fs/dtype_linux.go
generated
vendored
Normal file
87
vendor/github.com/containerd/continuity/fs/dtype_linux.go
generated
vendored
Normal file
|
@ -0,0 +1,87 @@
|
|||
// +build linux
|
||||
|
||||
package fs
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"syscall"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
func locateDummyIfEmpty(path string) (string, error) {
|
||||
children, err := ioutil.ReadDir(path)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if len(children) != 0 {
|
||||
return "", nil
|
||||
}
|
||||
dummyFile, err := ioutil.TempFile(path, "fsutils-dummy")
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
name := dummyFile.Name()
|
||||
err = dummyFile.Close()
|
||||
return name, err
|
||||
}
|
||||
|
||||
// SupportsDType returns whether the filesystem mounted on path supports d_type
|
||||
func SupportsDType(path string) (bool, error) {
|
||||
// locate dummy so that we have at least one dirent
|
||||
dummy, err := locateDummyIfEmpty(path)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
if dummy != "" {
|
||||
defer os.Remove(dummy)
|
||||
}
|
||||
|
||||
visited := 0
|
||||
supportsDType := true
|
||||
fn := func(ent *syscall.Dirent) bool {
|
||||
visited++
|
||||
if ent.Type == syscall.DT_UNKNOWN {
|
||||
supportsDType = false
|
||||
// stop iteration
|
||||
return true
|
||||
}
|
||||
// continue iteration
|
||||
return false
|
||||
}
|
||||
if err = iterateReadDir(path, fn); err != nil {
|
||||
return false, err
|
||||
}
|
||||
if visited == 0 {
|
||||
return false, fmt.Errorf("did not hit any dirent during iteration %s", path)
|
||||
}
|
||||
return supportsDType, nil
|
||||
}
|
||||
|
||||
func iterateReadDir(path string, fn func(*syscall.Dirent) bool) error {
|
||||
d, err := os.Open(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer d.Close()
|
||||
fd := int(d.Fd())
|
||||
buf := make([]byte, 4096)
|
||||
for {
|
||||
nbytes, err := syscall.ReadDirent(fd, buf)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if nbytes == 0 {
|
||||
break
|
||||
}
|
||||
for off := 0; off < nbytes; {
|
||||
ent := (*syscall.Dirent)(unsafe.Pointer(&buf[off]))
|
||||
if stop := fn(ent); stop {
|
||||
return nil
|
||||
}
|
||||
off += int(ent.Reclen)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
59
vendor/github.com/containerd/continuity/fs/dtype_linux_test.go
generated
vendored
Normal file
59
vendor/github.com/containerd/continuity/fs/dtype_linux_test.go
generated
vendored
Normal file
|
@ -0,0 +1,59 @@
|
|||
// +build linux
|
||||
|
||||
package fs
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"os/exec"
|
||||
"testing"
|
||||
|
||||
"github.com/containerd/continuity/testutil"
|
||||
)
|
||||
|
||||
func testSupportsDType(t *testing.T, expected bool, mkfs ...string) {
|
||||
testutil.RequiresRoot(t)
|
||||
mnt, err := ioutil.TempDir("", "containerd-fs-test-supports-dtype")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer os.RemoveAll(mnt)
|
||||
|
||||
deviceName, cleanupDevice, err := testutil.NewLoopback(100 << 20) // 100 MB
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if out, err := exec.Command(mkfs[0], append(mkfs[1:], deviceName)...).CombinedOutput(); err != nil {
|
||||
// not fatal
|
||||
t.Skipf("could not mkfs (%v) %s: %v (out: %q)", mkfs, deviceName, err, string(out))
|
||||
}
|
||||
if out, err := exec.Command("mount", deviceName, mnt).CombinedOutput(); err != nil {
|
||||
// not fatal
|
||||
t.Skipf("could not mount %s: %v (out: %q)", deviceName, err, string(out))
|
||||
}
|
||||
defer func() {
|
||||
testutil.Unmount(t, mnt)
|
||||
cleanupDevice()
|
||||
}()
|
||||
// check whether it supports d_type
|
||||
result, err := SupportsDType(mnt)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
t.Logf("Supports d_type: %v", result)
|
||||
if expected != result {
|
||||
t.Fatalf("expected %+v, got: %+v", expected, result)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSupportsDTypeWithFType0XFS(t *testing.T) {
|
||||
testSupportsDType(t, false, "mkfs.xfs", "-m", "crc=0", "-n", "ftype=0")
|
||||
}
|
||||
|
||||
func TestSupportsDTypeWithFType1XFS(t *testing.T) {
|
||||
testSupportsDType(t, true, "mkfs.xfs", "-m", "crc=0", "-n", "ftype=1")
|
||||
}
|
||||
|
||||
func TestSupportsDTypeWithExt4(t *testing.T) {
|
||||
testSupportsDType(t, true, "mkfs.ext4", "-F")
|
||||
}
|
26
vendor/github.com/containerd/continuity/fs/dtype_test.go
generated
vendored
Normal file
26
vendor/github.com/containerd/continuity/fs/dtype_test.go
generated
vendored
Normal file
|
@ -0,0 +1,26 @@
|
|||
package fs
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/containerd/continuity/testutil"
|
||||
)
|
||||
|
||||
func TestRequiresRootNOP(t *testing.T) {
|
||||
|
||||
// This is a dummy test case that exist to call
|
||||
// testutil.RequiresRoot() on non-linux platforms. This is
|
||||
// needed because the Makfile root-coverage tests target
|
||||
// determines which packages contain root test by grepping for
|
||||
// testutil.RequiresRoot. Within the fs package, the only test
|
||||
// that references this symbol is in dtype_linux_test.go, but
|
||||
// that file is only built on linux. Since the Makefile is not
|
||||
// go build tag aware it sees this file and then tries to run
|
||||
// the following command on all platforms: "go test ...
|
||||
// github.com/containerd/continuity/fs -test.root". On
|
||||
// non-linux platforms this fails because there are no tests in
|
||||
// the "fs" package that reference testutil.RequiresRoot. To
|
||||
// fix this problem we'll add a reference to this symbol below.
|
||||
|
||||
testutil.RequiresRoot(t)
|
||||
}
|
22
vendor/github.com/containerd/continuity/fs/du.go
generated
vendored
Normal file
22
vendor/github.com/containerd/continuity/fs/du.go
generated
vendored
Normal file
|
@ -0,0 +1,22 @@
|
|||
package fs
|
||||
|
||||
import "context"
|
||||
|
||||
// Usage of disk information
|
||||
type Usage struct {
|
||||
Inodes int64
|
||||
Size int64
|
||||
}
|
||||
|
||||
// DiskUsage counts the number of inodes and disk usage for the resources under
|
||||
// path.
|
||||
func DiskUsage(roots ...string) (Usage, error) {
|
||||
return diskUsage(roots...)
|
||||
}
|
||||
|
||||
// DiffUsage counts the numbers of inodes and disk usage in the
|
||||
// diff between the 2 directories. The first path is intended
|
||||
// as the base directory and the second as the changed directory.
|
||||
func DiffUsage(ctx context.Context, a, b string) (Usage, error) {
|
||||
return diffUsage(ctx, a, b)
|
||||
}
|
88
vendor/github.com/containerd/continuity/fs/du_unix.go
generated
vendored
Normal file
88
vendor/github.com/containerd/continuity/fs/du_unix.go
generated
vendored
Normal file
|
@ -0,0 +1,88 @@
|
|||
// +build !windows
|
||||
|
||||
package fs
|
||||
|
||||
import (
|
||||
"context"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"syscall"
|
||||
)
|
||||
|
||||
type inode struct {
|
||||
// TODO(stevvooe): Can probably reduce memory usage by not tracking
|
||||
// device, but we can leave this right for now.
|
||||
dev, ino uint64
|
||||
}
|
||||
|
||||
func newInode(stat *syscall.Stat_t) inode {
|
||||
return inode{
|
||||
// Dev is uint32 on darwin/bsd, uint64 on linux/solaris
|
||||
dev: uint64(stat.Dev), // nolint: unconvert
|
||||
// Ino is uint32 on bsd, uint64 on darwin/linux/solaris
|
||||
ino: uint64(stat.Ino), // nolint: unconvert
|
||||
}
|
||||
}
|
||||
|
||||
func diskUsage(roots ...string) (Usage, error) {
|
||||
|
||||
var (
|
||||
size int64
|
||||
inodes = map[inode]struct{}{} // expensive!
|
||||
)
|
||||
|
||||
for _, root := range roots {
|
||||
if err := filepath.Walk(root, func(path string, fi os.FileInfo, err error) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
inoKey := newInode(fi.Sys().(*syscall.Stat_t))
|
||||
if _, ok := inodes[inoKey]; !ok {
|
||||
inodes[inoKey] = struct{}{}
|
||||
size += fi.Size()
|
||||
}
|
||||
|
||||
return nil
|
||||
}); err != nil {
|
||||
return Usage{}, err
|
||||
}
|
||||
}
|
||||
|
||||
return Usage{
|
||||
Inodes: int64(len(inodes)),
|
||||
Size: size,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func diffUsage(ctx context.Context, a, b string) (Usage, error) {
|
||||
var (
|
||||
size int64
|
||||
inodes = map[inode]struct{}{} // expensive!
|
||||
)
|
||||
|
||||
if err := Changes(ctx, a, b, func(kind ChangeKind, _ string, fi os.FileInfo, err error) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if kind == ChangeKindAdd || kind == ChangeKindModify {
|
||||
inoKey := newInode(fi.Sys().(*syscall.Stat_t))
|
||||
if _, ok := inodes[inoKey]; !ok {
|
||||
inodes[inoKey] = struct{}{}
|
||||
size += fi.Size()
|
||||
}
|
||||
|
||||
return nil
|
||||
|
||||
}
|
||||
return nil
|
||||
}); err != nil {
|
||||
return Usage{}, err
|
||||
}
|
||||
|
||||
return Usage{
|
||||
Inodes: int64(len(inodes)),
|
||||
Size: size,
|
||||
}, nil
|
||||
}
|
60
vendor/github.com/containerd/continuity/fs/du_windows.go
generated
vendored
Normal file
60
vendor/github.com/containerd/continuity/fs/du_windows.go
generated
vendored
Normal file
|
@ -0,0 +1,60 @@
|
|||
// +build windows
|
||||
|
||||
package fs
|
||||
|
||||
import (
|
||||
"context"
|
||||
"os"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
func diskUsage(roots ...string) (Usage, error) {
|
||||
var (
|
||||
size int64
|
||||
)
|
||||
|
||||
// TODO(stevvooe): Support inodes (or equivalent) for windows.
|
||||
|
||||
for _, root := range roots {
|
||||
if err := filepath.Walk(root, func(path string, fi os.FileInfo, err error) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
size += fi.Size()
|
||||
return nil
|
||||
}); err != nil {
|
||||
return Usage{}, err
|
||||
}
|
||||
}
|
||||
|
||||
return Usage{
|
||||
Size: size,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func diffUsage(ctx context.Context, a, b string) (Usage, error) {
|
||||
var (
|
||||
size int64
|
||||
)
|
||||
|
||||
if err := Changes(ctx, a, b, func(kind ChangeKind, _ string, fi os.FileInfo, err error) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if kind == ChangeKindAdd || kind == ChangeKindModify {
|
||||
size += fi.Size()
|
||||
|
||||
return nil
|
||||
|
||||
}
|
||||
return nil
|
||||
}); err != nil {
|
||||
return Usage{}, err
|
||||
}
|
||||
|
||||
return Usage{
|
||||
Size: size,
|
||||
}, nil
|
||||
}
|
53
vendor/github.com/containerd/continuity/fs/fstest/compare.go
generated
vendored
Normal file
53
vendor/github.com/containerd/continuity/fs/fstest/compare.go
generated
vendored
Normal file
|
@ -0,0 +1,53 @@
|
|||
package fstest
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"os"
|
||||
|
||||
"github.com/containerd/continuity"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
// CheckDirectoryEqual compares two directory paths to make sure that
|
||||
// the content of the directories is the same.
|
||||
func CheckDirectoryEqual(d1, d2 string) error {
|
||||
c1, err := continuity.NewContext(d1)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to build context")
|
||||
}
|
||||
|
||||
c2, err := continuity.NewContext(d2)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to build context")
|
||||
}
|
||||
|
||||
m1, err := continuity.BuildManifest(c1)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to build manifest")
|
||||
}
|
||||
|
||||
m2, err := continuity.BuildManifest(c2)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to build manifest")
|
||||
}
|
||||
|
||||
diff := diffResourceList(m1.Resources, m2.Resources)
|
||||
if diff.HasDiff() {
|
||||
return errors.Errorf("directory diff between %s and %s\n%s", d1, d2, diff.String())
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// CheckDirectoryEqualWithApplier compares directory against applier
|
||||
func CheckDirectoryEqualWithApplier(root string, a Applier) error {
|
||||
applied, err := ioutil.TempDir("", "fstest")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer os.RemoveAll(applied)
|
||||
if err := a.Apply(applied); err != nil {
|
||||
return err
|
||||
}
|
||||
return CheckDirectoryEqual(applied, root)
|
||||
}
|
189
vendor/github.com/containerd/continuity/fs/fstest/continuity_util.go
generated
vendored
Normal file
189
vendor/github.com/containerd/continuity/fs/fstest/continuity_util.go
generated
vendored
Normal file
|
@ -0,0 +1,189 @@
|
|||
package fstest
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
|
||||
"github.com/containerd/continuity"
|
||||
)
|
||||
|
||||
type resourceUpdate struct {
|
||||
Original continuity.Resource
|
||||
Updated continuity.Resource
|
||||
}
|
||||
|
||||
func (u resourceUpdate) String() string {
|
||||
return fmt.Sprintf("%s(mode: %o, uid: %d, gid: %d) -> %s(mode: %o, uid: %d, gid: %d)",
|
||||
u.Original.Path(), u.Original.Mode(), u.Original.UID(), u.Original.GID(),
|
||||
u.Updated.Path(), u.Updated.Mode(), u.Updated.UID(), u.Updated.GID(),
|
||||
)
|
||||
}
|
||||
|
||||
type resourceListDifference struct {
|
||||
Additions []continuity.Resource
|
||||
Deletions []continuity.Resource
|
||||
Updates []resourceUpdate
|
||||
}
|
||||
|
||||
func (l resourceListDifference) HasDiff() bool {
|
||||
return len(l.Additions) > 0 || len(l.Deletions) > 0 || len(l.Updates) > 0
|
||||
}
|
||||
|
||||
func (l resourceListDifference) String() string {
|
||||
buf := bytes.NewBuffer(nil)
|
||||
for _, add := range l.Additions {
|
||||
fmt.Fprintf(buf, "+ %s\n", add.Path())
|
||||
}
|
||||
for _, del := range l.Deletions {
|
||||
fmt.Fprintf(buf, "- %s\n", del.Path())
|
||||
}
|
||||
for _, upt := range l.Updates {
|
||||
fmt.Fprintf(buf, "~ %s\n", upt.String())
|
||||
}
|
||||
return string(buf.Bytes())
|
||||
}
|
||||
|
||||
// diffManifest compares two resource lists and returns the list
|
||||
// of adds updates and deletes, resource lists are not reordered
|
||||
// before doing difference.
|
||||
func diffResourceList(r1, r2 []continuity.Resource) resourceListDifference {
|
||||
i1 := 0
|
||||
i2 := 0
|
||||
var d resourceListDifference
|
||||
|
||||
for i1 < len(r1) && i2 < len(r2) {
|
||||
p1 := r1[i1].Path()
|
||||
p2 := r2[i2].Path()
|
||||
switch {
|
||||
case p1 < p2:
|
||||
d.Deletions = append(d.Deletions, r1[i1])
|
||||
i1++
|
||||
case p1 == p2:
|
||||
if !compareResource(r1[i1], r2[i2]) {
|
||||
d.Updates = append(d.Updates, resourceUpdate{
|
||||
Original: r1[i1],
|
||||
Updated: r2[i2],
|
||||
})
|
||||
}
|
||||
i1++
|
||||
i2++
|
||||
case p1 > p2:
|
||||
d.Additions = append(d.Additions, r2[i2])
|
||||
i2++
|
||||
}
|
||||
}
|
||||
|
||||
for i1 < len(r1) {
|
||||
d.Deletions = append(d.Deletions, r1[i1])
|
||||
i1++
|
||||
|
||||
}
|
||||
for i2 < len(r2) {
|
||||
d.Additions = append(d.Additions, r2[i2])
|
||||
i2++
|
||||
}
|
||||
|
||||
return d
|
||||
}
|
||||
|
||||
func compareResource(r1, r2 continuity.Resource) bool {
|
||||
if r1.Path() != r2.Path() {
|
||||
return false
|
||||
}
|
||||
if r1.Mode() != r2.Mode() {
|
||||
return false
|
||||
}
|
||||
if r1.UID() != r2.UID() {
|
||||
return false
|
||||
}
|
||||
if r1.GID() != r2.GID() {
|
||||
return false
|
||||
}
|
||||
|
||||
// TODO(dmcgowan): Check if is XAttrer
|
||||
|
||||
return compareResourceTypes(r1, r2)
|
||||
|
||||
}
|
||||
|
||||
func compareResourceTypes(r1, r2 continuity.Resource) bool {
|
||||
switch t1 := r1.(type) {
|
||||
case continuity.RegularFile:
|
||||
t2, ok := r2.(continuity.RegularFile)
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
return compareRegularFile(t1, t2)
|
||||
case continuity.Directory:
|
||||
t2, ok := r2.(continuity.Directory)
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
return compareDirectory(t1, t2)
|
||||
case continuity.SymLink:
|
||||
t2, ok := r2.(continuity.SymLink)
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
return compareSymLink(t1, t2)
|
||||
case continuity.NamedPipe:
|
||||
t2, ok := r2.(continuity.NamedPipe)
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
return compareNamedPipe(t1, t2)
|
||||
case continuity.Device:
|
||||
t2, ok := r2.(continuity.Device)
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
return compareDevice(t1, t2)
|
||||
default:
|
||||
// TODO(dmcgowan): Should this panic?
|
||||
return r1 == r2
|
||||
}
|
||||
}
|
||||
|
||||
func compareRegularFile(r1, r2 continuity.RegularFile) bool {
|
||||
if r1.Size() != r2.Size() {
|
||||
return false
|
||||
}
|
||||
p1 := r1.Paths()
|
||||
p2 := r2.Paths()
|
||||
if len(p1) != len(p2) {
|
||||
return false
|
||||
}
|
||||
for i := range p1 {
|
||||
if p1[i] != p2[i] {
|
||||
return false
|
||||
}
|
||||
}
|
||||
d1 := r1.Digests()
|
||||
d2 := r2.Digests()
|
||||
if len(d1) != len(d2) {
|
||||
return false
|
||||
}
|
||||
for i := range d1 {
|
||||
if d1[i] != d2[i] {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
func compareSymLink(r1, r2 continuity.SymLink) bool {
|
||||
return r1.Target() == r2.Target()
|
||||
}
|
||||
|
||||
func compareDirectory(r1, r2 continuity.Directory) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func compareNamedPipe(r1, r2 continuity.NamedPipe) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func compareDevice(r1, r2 continuity.Device) bool {
|
||||
return r1.Major() == r2.Major() && r1.Minor() == r2.Minor()
|
||||
}
|
120
vendor/github.com/containerd/continuity/fs/fstest/file.go
generated
vendored
Normal file
120
vendor/github.com/containerd/continuity/fs/fstest/file.go
generated
vendored
Normal file
|
@ -0,0 +1,120 @@
|
|||
package fstest
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Applier applies single file changes
|
||||
type Applier interface {
|
||||
Apply(root string) error
|
||||
}
|
||||
|
||||
type applyFn func(root string) error
|
||||
|
||||
func (a applyFn) Apply(root string) error {
|
||||
return a(root)
|
||||
}
|
||||
|
||||
// CreateFile returns a file applier which creates a file as the
|
||||
// provided name with the given content and permission.
|
||||
func CreateFile(name string, content []byte, perm os.FileMode) Applier {
|
||||
return applyFn(func(root string) error {
|
||||
fullPath := filepath.Join(root, name)
|
||||
if err := ioutil.WriteFile(fullPath, content, perm); err != nil {
|
||||
return err
|
||||
}
|
||||
return os.Chmod(fullPath, perm)
|
||||
})
|
||||
}
|
||||
|
||||
// Remove returns a file applier which removes the provided file name
|
||||
func Remove(name string) Applier {
|
||||
return applyFn(func(root string) error {
|
||||
return os.Remove(filepath.Join(root, name))
|
||||
})
|
||||
}
|
||||
|
||||
// RemoveAll returns a file applier which removes the provided file name
|
||||
// as in os.RemoveAll
|
||||
func RemoveAll(name string) Applier {
|
||||
return applyFn(func(root string) error {
|
||||
return os.RemoveAll(filepath.Join(root, name))
|
||||
})
|
||||
}
|
||||
|
||||
// CreateDir returns a file applier to create the directory with
|
||||
// the provided name and permission
|
||||
func CreateDir(name string, perm os.FileMode) Applier {
|
||||
return applyFn(func(root string) error {
|
||||
fullPath := filepath.Join(root, name)
|
||||
if err := os.MkdirAll(fullPath, perm); err != nil {
|
||||
return err
|
||||
}
|
||||
return os.Chmod(fullPath, perm)
|
||||
})
|
||||
}
|
||||
|
||||
// Rename returns a file applier which renames a file
|
||||
func Rename(old, new string) Applier {
|
||||
return applyFn(func(root string) error {
|
||||
return os.Rename(filepath.Join(root, old), filepath.Join(root, new))
|
||||
})
|
||||
}
|
||||
|
||||
// Chown returns a file applier which changes the ownership of a file
|
||||
func Chown(name string, uid, gid int) Applier {
|
||||
return applyFn(func(root string) error {
|
||||
return os.Chown(filepath.Join(root, name), uid, gid)
|
||||
})
|
||||
}
|
||||
|
||||
// Chtimes changes access and mod time of file.
|
||||
// Use Lchtimes for symbolic links.
|
||||
func Chtimes(name string, atime, mtime time.Time) Applier {
|
||||
return applyFn(func(root string) error {
|
||||
return os.Chtimes(filepath.Join(root, name), atime, mtime)
|
||||
})
|
||||
}
|
||||
|
||||
// Chmod returns a file applier which changes the file permission
|
||||
func Chmod(name string, perm os.FileMode) Applier {
|
||||
return applyFn(func(root string) error {
|
||||
return os.Chmod(filepath.Join(root, name), perm)
|
||||
})
|
||||
}
|
||||
|
||||
// Symlink returns a file applier which creates a symbolic link
|
||||
func Symlink(oldname, newname string) Applier {
|
||||
return applyFn(func(root string) error {
|
||||
return os.Symlink(oldname, filepath.Join(root, newname))
|
||||
})
|
||||
}
|
||||
|
||||
// Link returns a file applier which creates a hard link
|
||||
func Link(oldname, newname string) Applier {
|
||||
return applyFn(func(root string) error {
|
||||
return os.Link(filepath.Join(root, oldname), filepath.Join(root, newname))
|
||||
})
|
||||
}
|
||||
|
||||
// TODO: Make platform specific, windows applier is always no-op
|
||||
//func Mknod(name string, mode int32, dev int) Applier {
|
||||
// return func(root string) error {
|
||||
// return return syscall.Mknod(path, mode, dev)
|
||||
// }
|
||||
//}
|
||||
|
||||
// Apply returns a new applier from the given appliers
|
||||
func Apply(appliers ...Applier) Applier {
|
||||
return applyFn(func(root string) error {
|
||||
for _, a := range appliers {
|
||||
if err := a.Apply(root); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
29
vendor/github.com/containerd/continuity/fs/fstest/file_unix.go
generated
vendored
Normal file
29
vendor/github.com/containerd/continuity/fs/fstest/file_unix.go
generated
vendored
Normal file
|
@ -0,0 +1,29 @@
|
|||
// +build !windows
|
||||
|
||||
package fstest
|
||||
|
||||
import (
|
||||
"path/filepath"
|
||||
"time"
|
||||
|
||||
"github.com/containerd/continuity/sysx"
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
// SetXAttr sets the xatter for the file
|
||||
func SetXAttr(name, key, value string) Applier {
|
||||
return applyFn(func(root string) error {
|
||||
return sysx.LSetxattr(name, key, []byte(value), 0)
|
||||
})
|
||||
}
|
||||
|
||||
// Lchtimes changes access and mod time of file without following symlink
|
||||
func Lchtimes(name string, atime, mtime time.Time) Applier {
|
||||
return applyFn(func(root string) error {
|
||||
path := filepath.Join(root, name)
|
||||
at := unix.NsecToTimespec(atime.UnixNano())
|
||||
mt := unix.NsecToTimespec(mtime.UnixNano())
|
||||
utimes := [2]unix.Timespec{at, mt}
|
||||
return unix.UtimesNanoAt(unix.AT_FDCWD, path, utimes[0:], unix.AT_SYMLINK_NOFOLLOW)
|
||||
})
|
||||
}
|
14
vendor/github.com/containerd/continuity/fs/fstest/file_windows.go
generated
vendored
Normal file
14
vendor/github.com/containerd/continuity/fs/fstest/file_windows.go
generated
vendored
Normal file
|
@ -0,0 +1,14 @@
|
|||
package fstest
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
// Lchtimes changes access and mod time of file without following symlink
|
||||
func Lchtimes(name string, atime, mtime time.Time) Applier {
|
||||
return applyFn(func(root string) error {
|
||||
return errors.New("Not implemented")
|
||||
})
|
||||
}
|
220
vendor/github.com/containerd/continuity/fs/fstest/testsuite.go
generated
vendored
Normal file
220
vendor/github.com/containerd/continuity/fs/fstest/testsuite.go
generated
vendored
Normal file
|
@ -0,0 +1,220 @@
|
|||
package fstest
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// TestApplier applies the test context
|
||||
type TestApplier interface {
|
||||
TestContext(context.Context) (context.Context, func(), error)
|
||||
Apply(context.Context, Applier) (string, func(), error)
|
||||
}
|
||||
|
||||
// FSSuite runs the path test suite
|
||||
func FSSuite(t *testing.T, a TestApplier) {
|
||||
t.Run("Basic", makeTest(t, a, basicTest))
|
||||
t.Run("Deletion", makeTest(t, a, deletionTest))
|
||||
t.Run("Update", makeTest(t, a, updateTest))
|
||||
t.Run("DirectoryPermission", makeTest(t, a, directoryPermissionsTest))
|
||||
t.Run("ParentDirectoryPermission", makeTest(t, a, parentDirectoryPermissionsTest))
|
||||
t.Run("HardlinkUnmodified", makeTest(t, a, hardlinkUnmodified))
|
||||
t.Run("HardlinkBeforeUnmodified", makeTest(t, a, hardlinkBeforeUnmodified))
|
||||
t.Run("HardlinkBeforeModified", makeTest(t, a, hardlinkBeforeModified))
|
||||
}
|
||||
|
||||
func makeTest(t *testing.T, ta TestApplier, as []Applier) func(t *testing.T) {
|
||||
return func(t *testing.T) {
|
||||
ctx, cleanup, err := ta.TestContext(context.Background())
|
||||
if err != nil {
|
||||
t.Fatalf("Unable to get test context: %+v", err)
|
||||
}
|
||||
defer cleanup()
|
||||
|
||||
applyDir, err := ioutil.TempDir("", "test-expected-")
|
||||
if err != nil {
|
||||
t.Fatalf("Unable to make temp directory: %+v", err)
|
||||
}
|
||||
defer os.RemoveAll(applyDir)
|
||||
|
||||
for i, a := range as {
|
||||
testDir, c, err := ta.Apply(ctx, a)
|
||||
if err != nil {
|
||||
t.Fatalf("Apply failed at %d: %+v", i, err)
|
||||
}
|
||||
if err := a.Apply(applyDir); err != nil {
|
||||
if c != nil {
|
||||
c()
|
||||
}
|
||||
t.Fatalf("Error applying change to apply directory: %+v", err)
|
||||
}
|
||||
|
||||
err = CheckDirectoryEqual(applyDir, testDir)
|
||||
if c != nil {
|
||||
c()
|
||||
}
|
||||
if err != nil {
|
||||
t.Fatalf("Directories not equal at %d (expected <> tested): %+v", i, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var (
|
||||
// baseApplier creates a basic filesystem layout
|
||||
// with multiple types of files for basic tests.
|
||||
baseApplier = Apply(
|
||||
CreateDir("/etc/", 0755),
|
||||
CreateFile("/etc/hosts", []byte("127.0.0.1 localhost"), 0644),
|
||||
Link("/etc/hosts", "/etc/hosts.allow"),
|
||||
CreateDir("/usr/local/lib", 0755),
|
||||
CreateFile("/usr/local/lib/libnothing.so", []byte{0x00, 0x00}, 0755),
|
||||
Symlink("libnothing.so", "/usr/local/lib/libnothing.so.2"),
|
||||
CreateDir("/home", 0755),
|
||||
CreateDir("/home/derek", 0700),
|
||||
)
|
||||
|
||||
// basicTest covers basic operations
|
||||
basicTest = []Applier{
|
||||
baseApplier,
|
||||
Apply(
|
||||
CreateFile("/etc/hosts", []byte("127.0.0.1 localhost.localdomain"), 0644),
|
||||
CreateFile("/etc/fstab", []byte("/dev/sda1\t/\text4\tdefaults 1 1\n"), 0600),
|
||||
CreateFile("/etc/badfile", []byte(""), 0666),
|
||||
CreateFile("/home/derek/.zshrc", []byte("#ZSH is just better\n"), 0640),
|
||||
),
|
||||
Apply(
|
||||
Remove("/etc/badfile"),
|
||||
Rename("/home/derek", "/home/notderek"),
|
||||
),
|
||||
Apply(
|
||||
RemoveAll("/usr"),
|
||||
Remove("/etc/hosts.allow"),
|
||||
),
|
||||
Apply(
|
||||
RemoveAll("/home"),
|
||||
CreateDir("/home/derek", 0700),
|
||||
CreateFile("/home/derek/.bashrc", []byte("#not going away\n"), 0640),
|
||||
Link("/etc/hosts", "/etc/hosts.allow"),
|
||||
),
|
||||
}
|
||||
|
||||
// deletionTest covers various deletion scenarios to ensure
|
||||
// deletions are properly picked up and applied
|
||||
deletionTest = []Applier{
|
||||
Apply(
|
||||
CreateDir("/test/somedir", 0755),
|
||||
CreateDir("/lib", 0700),
|
||||
CreateFile("/lib/hidden", []byte{}, 0644),
|
||||
),
|
||||
Apply(
|
||||
CreateFile("/test/a", []byte{}, 0644),
|
||||
CreateFile("/test/b", []byte{}, 0644),
|
||||
CreateDir("/test/otherdir", 0755),
|
||||
CreateFile("/test/otherdir/.empty", []byte{}, 0644),
|
||||
RemoveAll("/lib"),
|
||||
CreateDir("/lib", 0700),
|
||||
CreateFile("/lib/not-hidden", []byte{}, 0644),
|
||||
),
|
||||
Apply(
|
||||
Remove("/test/a"),
|
||||
Remove("/test/b"),
|
||||
RemoveAll("/test/otherdir"),
|
||||
CreateFile("/lib/newfile", []byte{}, 0644),
|
||||
),
|
||||
}
|
||||
|
||||
// updateTest covers file updates for content and permission
|
||||
updateTest = []Applier{
|
||||
Apply(
|
||||
CreateDir("/d1", 0755),
|
||||
CreateDir("/d2", 0700),
|
||||
CreateFile("/d1/f1", []byte("something..."), 0644),
|
||||
CreateFile("/d1/f2", []byte("else..."), 0644),
|
||||
CreateFile("/d1/f3", []byte("entirely..."), 0644),
|
||||
),
|
||||
Apply(
|
||||
CreateFile("/d1/f1", []byte("file content of a different length"), 0664),
|
||||
Remove("/d1/f3"),
|
||||
CreateFile("/d1/f3", []byte("updated content"), 0664),
|
||||
Chmod("/d1/f2", 0766),
|
||||
Chmod("/d2", 0777),
|
||||
),
|
||||
}
|
||||
|
||||
// directoryPermissionsTest covers directory permissions on update
|
||||
directoryPermissionsTest = []Applier{
|
||||
Apply(
|
||||
CreateDir("/d1", 0700),
|
||||
CreateDir("/d2", 0751),
|
||||
CreateDir("/d3", 0777),
|
||||
),
|
||||
Apply(
|
||||
CreateFile("/d1/f", []byte("irrelevant"), 0644),
|
||||
CreateDir("/d1/d", 0700),
|
||||
CreateFile("/d1/d/f", []byte("irrelevant"), 0644),
|
||||
CreateFile("/d2/f", []byte("irrelevant"), 0644),
|
||||
CreateFile("/d3/f", []byte("irrelevant"), 0644),
|
||||
),
|
||||
}
|
||||
|
||||
// parentDirectoryPermissionsTest covers directory permissions for updated
|
||||
// files
|
||||
parentDirectoryPermissionsTest = []Applier{
|
||||
Apply(
|
||||
CreateDir("/d1", 0700),
|
||||
CreateDir("/d1/a", 0700),
|
||||
CreateDir("/d1/a/b", 0700),
|
||||
CreateDir("/d1/a/b/c", 0700),
|
||||
CreateFile("/d1/a/b/f", []byte("content1"), 0644),
|
||||
CreateDir("/d2", 0751),
|
||||
CreateDir("/d2/a/b", 0751),
|
||||
CreateDir("/d2/a/b/c", 0751),
|
||||
CreateFile("/d2/a/b/f", []byte("content1"), 0644),
|
||||
),
|
||||
Apply(
|
||||
CreateFile("/d1/a/b/f", []byte("content1"), 0644),
|
||||
Chmod("/d1/a/b/c", 0700),
|
||||
CreateFile("/d2/a/b/f", []byte("content2"), 0644),
|
||||
Chmod("/d2/a/b/c", 0751),
|
||||
),
|
||||
}
|
||||
|
||||
hardlinkUnmodified = []Applier{
|
||||
baseApplier,
|
||||
Apply(
|
||||
CreateFile("/etc/hosts", []byte("127.0.0.1 localhost.localdomain"), 0644),
|
||||
),
|
||||
Apply(
|
||||
Link("/etc/hosts", "/etc/hosts.deny"),
|
||||
),
|
||||
}
|
||||
|
||||
// Hardlink name before with modification
|
||||
// Tests link is created for unmodified files when new hardlinked file is seen first
|
||||
hardlinkBeforeUnmodified = []Applier{
|
||||
baseApplier,
|
||||
Apply(
|
||||
CreateFile("/etc/hosts", []byte("127.0.0.1 localhost.localdomain"), 0644),
|
||||
),
|
||||
Apply(
|
||||
Link("/etc/hosts", "/etc/before-hosts"),
|
||||
),
|
||||
}
|
||||
|
||||
// Hardlink name after without modification
|
||||
// tests link is created for modified file with new hardlink
|
||||
hardlinkBeforeModified = []Applier{
|
||||
baseApplier,
|
||||
Apply(
|
||||
CreateFile("/etc/hosts", []byte("127.0.0.1 localhost.localdomain"), 0644),
|
||||
),
|
||||
Apply(
|
||||
Remove("/etc/hosts"),
|
||||
CreateFile("/etc/hosts", []byte("127.0.0.1 localhost"), 0644),
|
||||
Link("/etc/hosts", "/etc/before-hosts"),
|
||||
),
|
||||
}
|
||||
)
|
27
vendor/github.com/containerd/continuity/fs/hardlink.go
generated
vendored
Normal file
27
vendor/github.com/containerd/continuity/fs/hardlink.go
generated
vendored
Normal file
|
@ -0,0 +1,27 @@
|
|||
package fs
|
||||
|
||||
import "os"
|
||||
|
||||
// GetLinkInfo returns an identifier representing the node a hardlink is pointing
|
||||
// to. If the file is not hard linked then 0 will be returned.
|
||||
func GetLinkInfo(fi os.FileInfo) (uint64, bool) {
|
||||
return getLinkInfo(fi)
|
||||
}
|
||||
|
||||
// getLinkSource returns a path for the given name and
|
||||
// file info to its link source in the provided inode
|
||||
// map. If the given file name is not in the map and
|
||||
// has other links, it is added to the inode map
|
||||
// to be a source for other link locations.
|
||||
func getLinkSource(name string, fi os.FileInfo, inodes map[uint64]string) (string, error) {
|
||||
inode, isHardlink := getLinkInfo(fi)
|
||||
if !isHardlink {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
path, ok := inodes[inode]
|
||||
if !ok {
|
||||
inodes[inode] = name
|
||||
}
|
||||
return path, nil
|
||||
}
|
18
vendor/github.com/containerd/continuity/fs/hardlink_unix.go
generated
vendored
Normal file
18
vendor/github.com/containerd/continuity/fs/hardlink_unix.go
generated
vendored
Normal file
|
@ -0,0 +1,18 @@
|
|||
// +build !windows
|
||||
|
||||
package fs
|
||||
|
||||
import (
|
||||
"os"
|
||||
"syscall"
|
||||
)
|
||||
|
||||
func getLinkInfo(fi os.FileInfo) (uint64, bool) {
|
||||
s, ok := fi.Sys().(*syscall.Stat_t)
|
||||
if !ok {
|
||||
return 0, false
|
||||
}
|
||||
|
||||
// Ino is uint32 on bsd, uint64 on darwin/linux/solaris
|
||||
return uint64(s.Ino), !fi.IsDir() && s.Nlink > 1 // nolint: unconvert
|
||||
}
|
7
vendor/github.com/containerd/continuity/fs/hardlink_windows.go
generated
vendored
Normal file
7
vendor/github.com/containerd/continuity/fs/hardlink_windows.go
generated
vendored
Normal file
|
@ -0,0 +1,7 @@
|
|||
package fs
|
||||
|
||||
import "os"
|
||||
|
||||
func getLinkInfo(fi os.FileInfo) (uint64, bool) {
|
||||
return 0, false
|
||||
}
|
276
vendor/github.com/containerd/continuity/fs/path.go
generated
vendored
Normal file
276
vendor/github.com/containerd/continuity/fs/path.go
generated
vendored
Normal file
|
@ -0,0 +1,276 @@
|
|||
package fs
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
var (
|
||||
errTooManyLinks = errors.New("too many links")
|
||||
)
|
||||
|
||||
type currentPath struct {
|
||||
path string
|
||||
f os.FileInfo
|
||||
fullPath string
|
||||
}
|
||||
|
||||
func pathChange(lower, upper *currentPath) (ChangeKind, string) {
|
||||
if lower == nil {
|
||||
if upper == nil {
|
||||
panic("cannot compare nil paths")
|
||||
}
|
||||
return ChangeKindAdd, upper.path
|
||||
}
|
||||
if upper == nil {
|
||||
return ChangeKindDelete, lower.path
|
||||
}
|
||||
// TODO: compare by directory
|
||||
|
||||
switch i := strings.Compare(lower.path, upper.path); {
|
||||
case i < 0:
|
||||
// File in lower that is not in upper
|
||||
return ChangeKindDelete, lower.path
|
||||
case i > 0:
|
||||
// File in upper that is not in lower
|
||||
return ChangeKindAdd, upper.path
|
||||
default:
|
||||
return ChangeKindModify, upper.path
|
||||
}
|
||||
}
|
||||
|
||||
func sameFile(f1, f2 *currentPath) (bool, error) {
|
||||
if os.SameFile(f1.f, f2.f) {
|
||||
return true, nil
|
||||
}
|
||||
|
||||
equalStat, err := compareSysStat(f1.f.Sys(), f2.f.Sys())
|
||||
if err != nil || !equalStat {
|
||||
return equalStat, err
|
||||
}
|
||||
|
||||
if eq, err := compareCapabilities(f1.fullPath, f2.fullPath); err != nil || !eq {
|
||||
return eq, err
|
||||
}
|
||||
|
||||
// If not a directory also check size, modtime, and content
|
||||
if !f1.f.IsDir() {
|
||||
if f1.f.Size() != f2.f.Size() {
|
||||
return false, nil
|
||||
}
|
||||
t1 := f1.f.ModTime()
|
||||
t2 := f2.f.ModTime()
|
||||
|
||||
if t1.Unix() != t2.Unix() {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
// If the timestamp may have been truncated in both of the
|
||||
// files, check content of file to determine difference
|
||||
if t1.Nanosecond() == 0 && t2.Nanosecond() == 0 {
|
||||
var eq bool
|
||||
if (f1.f.Mode() & os.ModeSymlink) == os.ModeSymlink {
|
||||
eq, err = compareSymlinkTarget(f1.fullPath, f2.fullPath)
|
||||
} else if f1.f.Size() > 0 {
|
||||
eq, err = compareFileContent(f1.fullPath, f2.fullPath)
|
||||
}
|
||||
if err != nil || !eq {
|
||||
return eq, err
|
||||
}
|
||||
} else if t1.Nanosecond() != t2.Nanosecond() {
|
||||
return false, nil
|
||||
}
|
||||
}
|
||||
|
||||
return true, nil
|
||||
}
|
||||
|
||||
func compareSymlinkTarget(p1, p2 string) (bool, error) {
|
||||
t1, err := os.Readlink(p1)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
t2, err := os.Readlink(p2)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
return t1 == t2, nil
|
||||
}
|
||||
|
||||
const compareChuckSize = 32 * 1024
|
||||
|
||||
// compareFileContent compares the content of 2 same sized files
|
||||
// by comparing each byte.
|
||||
func compareFileContent(p1, p2 string) (bool, error) {
|
||||
f1, err := os.Open(p1)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
defer f1.Close()
|
||||
f2, err := os.Open(p2)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
defer f2.Close()
|
||||
|
||||
b1 := make([]byte, compareChuckSize)
|
||||
b2 := make([]byte, compareChuckSize)
|
||||
for {
|
||||
n1, err1 := f1.Read(b1)
|
||||
if err1 != nil && err1 != io.EOF {
|
||||
return false, err1
|
||||
}
|
||||
n2, err2 := f2.Read(b2)
|
||||
if err2 != nil && err2 != io.EOF {
|
||||
return false, err2
|
||||
}
|
||||
if n1 != n2 || !bytes.Equal(b1[:n1], b2[:n2]) {
|
||||
return false, nil
|
||||
}
|
||||
if err1 == io.EOF && err2 == io.EOF {
|
||||
return true, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func pathWalk(ctx context.Context, root string, pathC chan<- *currentPath) error {
|
||||
return filepath.Walk(root, func(path string, f os.FileInfo, err error) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Rebase path
|
||||
path, err = filepath.Rel(root, path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
path = filepath.Join(string(os.PathSeparator), path)
|
||||
|
||||
// Skip root
|
||||
if path == string(os.PathSeparator) {
|
||||
return nil
|
||||
}
|
||||
|
||||
p := ¤tPath{
|
||||
path: path,
|
||||
f: f,
|
||||
fullPath: filepath.Join(root, path),
|
||||
}
|
||||
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return ctx.Err()
|
||||
case pathC <- p:
|
||||
return nil
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func nextPath(ctx context.Context, pathC <-chan *currentPath) (*currentPath, error) {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return nil, ctx.Err()
|
||||
case p := <-pathC:
|
||||
return p, nil
|
||||
}
|
||||
}
|
||||
|
||||
// RootPath joins a path with a root, evaluating and bounding any
|
||||
// symlink to the root directory.
|
||||
func RootPath(root, path string) (string, error) {
|
||||
if path == "" {
|
||||
return root, nil
|
||||
}
|
||||
var linksWalked int // to protect against cycles
|
||||
for {
|
||||
i := linksWalked
|
||||
newpath, err := walkLinks(root, path, &linksWalked)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
path = newpath
|
||||
if i == linksWalked {
|
||||
newpath = filepath.Join("/", newpath)
|
||||
if path == newpath {
|
||||
return filepath.Join(root, newpath), nil
|
||||
}
|
||||
path = newpath
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func walkLink(root, path string, linksWalked *int) (newpath string, islink bool, err error) {
|
||||
if *linksWalked > 255 {
|
||||
return "", false, errTooManyLinks
|
||||
}
|
||||
|
||||
path = filepath.Join("/", path)
|
||||
if path == "/" {
|
||||
return path, false, nil
|
||||
}
|
||||
realPath := filepath.Join(root, path)
|
||||
|
||||
fi, err := os.Lstat(realPath)
|
||||
if err != nil {
|
||||
// If path does not yet exist, treat as non-symlink
|
||||
if os.IsNotExist(err) {
|
||||
return path, false, nil
|
||||
}
|
||||
return "", false, err
|
||||
}
|
||||
if fi.Mode()&os.ModeSymlink == 0 {
|
||||
return path, false, nil
|
||||
}
|
||||
newpath, err = os.Readlink(realPath)
|
||||
if err != nil {
|
||||
return "", false, err
|
||||
}
|
||||
if filepath.IsAbs(newpath) && strings.HasPrefix(newpath, root) {
|
||||
newpath = newpath[:len(root)]
|
||||
if !strings.HasPrefix(newpath, "/") {
|
||||
newpath = "/" + newpath
|
||||
}
|
||||
}
|
||||
*linksWalked++
|
||||
return newpath, true, nil
|
||||
}
|
||||
|
||||
func walkLinks(root, path string, linksWalked *int) (string, error) {
|
||||
switch dir, file := filepath.Split(path); {
|
||||
case dir == "":
|
||||
newpath, _, err := walkLink(root, file, linksWalked)
|
||||
return newpath, err
|
||||
case file == "":
|
||||
if os.IsPathSeparator(dir[len(dir)-1]) {
|
||||
if dir == "/" {
|
||||
return dir, nil
|
||||
}
|
||||
return walkLinks(root, dir[:len(dir)-1], linksWalked)
|
||||
}
|
||||
newpath, _, err := walkLink(root, dir, linksWalked)
|
||||
return newpath, err
|
||||
default:
|
||||
newdir, err := walkLinks(root, dir, linksWalked)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
newpath, islink, err := walkLink(root, filepath.Join(newdir, file), linksWalked)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if !islink {
|
||||
return newpath, nil
|
||||
}
|
||||
if filepath.IsAbs(newpath) {
|
||||
return newpath, nil
|
||||
}
|
||||
return filepath.Join(newdir, newpath), nil
|
||||
}
|
||||
}
|
294
vendor/github.com/containerd/continuity/fs/path_test.go
generated
vendored
Normal file
294
vendor/github.com/containerd/continuity/fs/path_test.go
generated
vendored
Normal file
|
@ -0,0 +1,294 @@
|
|||
// +build !windows
|
||||
|
||||
package fs
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"github.com/containerd/continuity/fs/fstest"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
type RootCheck struct {
|
||||
unresolved string
|
||||
expected string
|
||||
scope func(string) string
|
||||
cause error
|
||||
}
|
||||
|
||||
func TestRootPath(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
apply fstest.Applier
|
||||
checks []RootCheck
|
||||
}{
|
||||
{
|
||||
name: "SymlinkAbsolute",
|
||||
apply: Symlink("/b", "fs/a/d"),
|
||||
checks: Check("fs/a/d/c/data", "b/c/data"),
|
||||
},
|
||||
{
|
||||
name: "SymlinkRelativePath",
|
||||
apply: Symlink("a", "fs/i"),
|
||||
checks: Check("fs/i", "fs/a"),
|
||||
},
|
||||
{
|
||||
name: "SymlinkSkipSymlinksOutsideScope",
|
||||
apply: Symlink("realdir", "linkdir"),
|
||||
checks: CheckWithScope("foo/bar", "foo/bar", "linkdir"),
|
||||
},
|
||||
{
|
||||
name: "SymlinkLastLink",
|
||||
apply: Symlink("/b", "fs/a/d"),
|
||||
checks: Check("fs/a/d", "b"),
|
||||
},
|
||||
{
|
||||
name: "SymlinkRelativeLinkChangeScope",
|
||||
apply: Symlink("../b", "fs/a/e"),
|
||||
checks: CheckAll(
|
||||
Check("fs/a/e/c/data", "fs/b/c/data"),
|
||||
CheckWithScope("e", "b", "fs/a"), // Original return
|
||||
),
|
||||
},
|
||||
{
|
||||
name: "SymlinkDeepRelativeLinkChangeScope",
|
||||
apply: Symlink("../../../../test", "fs/a/f"),
|
||||
checks: CheckAll(
|
||||
Check("fs/a/f", "test"), // Original return
|
||||
CheckWithScope("a/f", "test", "fs"), // Original return
|
||||
),
|
||||
},
|
||||
{
|
||||
name: "SymlinkRelativeLinkChain",
|
||||
apply: fstest.Apply(
|
||||
Symlink("../g", "fs/b/h"),
|
||||
fstest.Symlink("../../../../../../../../../../../../root", "fs/g"),
|
||||
),
|
||||
checks: Check("fs/b/h", "root"),
|
||||
},
|
||||
{
|
||||
name: "SymlinkBreakoutPath",
|
||||
apply: Symlink("../i/a", "fs/j/k"),
|
||||
checks: CheckWithScope("k", "i/a", "fs/j"),
|
||||
},
|
||||
{
|
||||
name: "SymlinkToRoot",
|
||||
apply: Symlink("/", "foo"),
|
||||
checks: Check("foo", ""),
|
||||
},
|
||||
{
|
||||
name: "SymlinkSlashDotdot",
|
||||
apply: Symlink("/../../", "foo"),
|
||||
checks: Check("foo", ""),
|
||||
},
|
||||
{
|
||||
name: "SymlinkDotdot",
|
||||
apply: Symlink("../../", "foo"),
|
||||
checks: Check("foo", ""),
|
||||
},
|
||||
{
|
||||
name: "SymlinkRelativePath2",
|
||||
apply: Symlink("baz/target", "bar/foo"),
|
||||
checks: Check("bar/foo", "bar/baz/target"),
|
||||
},
|
||||
{
|
||||
name: "SymlinkScopeLink",
|
||||
apply: fstest.Apply(
|
||||
Symlink("root2", "root"),
|
||||
Symlink("../bar", "root2/foo"),
|
||||
),
|
||||
checks: CheckWithScope("foo", "bar", "root"),
|
||||
},
|
||||
{
|
||||
name: "SymlinkSelf",
|
||||
apply: fstest.Apply(
|
||||
Symlink("foo", "root/foo"),
|
||||
),
|
||||
checks: ErrorWithScope("foo", "root", errTooManyLinks),
|
||||
},
|
||||
{
|
||||
name: "SymlinkCircular",
|
||||
apply: fstest.Apply(
|
||||
Symlink("foo", "bar"),
|
||||
Symlink("bar", "foo"),
|
||||
),
|
||||
checks: ErrorWithScope("foo", "", errTooManyLinks), //TODO: Test for circular error
|
||||
},
|
||||
{
|
||||
name: "SymlinkCircularUnderRoot",
|
||||
apply: fstest.Apply(
|
||||
Symlink("baz", "root/bar"),
|
||||
Symlink("../bak", "root/baz"),
|
||||
Symlink("/bar", "root/bak"),
|
||||
),
|
||||
checks: ErrorWithScope("bar", "root", errTooManyLinks), // TODO: Test for circular error
|
||||
},
|
||||
{
|
||||
name: "SymlinkComplexChain",
|
||||
apply: fstest.Apply(
|
||||
fstest.CreateDir("root2", 0777),
|
||||
Symlink("root2", "root"),
|
||||
Symlink("r/s", "root/a"),
|
||||
Symlink("../root/t", "root/r"),
|
||||
Symlink("/../u", "root/root/t/s/b"),
|
||||
Symlink(".", "root/u/c"),
|
||||
Symlink("../v", "root/u/x/y"),
|
||||
Symlink("/../w", "root/u/v"),
|
||||
),
|
||||
checks: CheckWithScope("a/b/c/x/y/z", "w/z", "root"), // Original return
|
||||
},
|
||||
{
|
||||
name: "SymlinkBreakoutNonExistent",
|
||||
apply: fstest.Apply(
|
||||
Symlink("/", "root/slash"),
|
||||
Symlink("/idontexist/../slash", "root/sym"),
|
||||
),
|
||||
checks: CheckWithScope("sym/file", "file", "root"),
|
||||
},
|
||||
{
|
||||
name: "SymlinkNoLexicalCleaning",
|
||||
apply: fstest.Apply(
|
||||
Symlink("/foo/bar", "root/sym"),
|
||||
Symlink("/sym/../baz", "root/hello"),
|
||||
),
|
||||
checks: CheckWithScope("hello", "foo/baz", "root"),
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, makeRootPathTest(t, test.apply, test.checks))
|
||||
}
|
||||
|
||||
// Add related tests which are unable to follow same pattern
|
||||
t.Run("SymlinkRootScope", testRootPathSymlinkRootScope)
|
||||
t.Run("SymlinkEmpty", testRootPathSymlinkEmpty)
|
||||
}
|
||||
|
||||
func testRootPathSymlinkRootScope(t *testing.T) {
|
||||
tmpdir, err := ioutil.TempDir("", "TestFollowSymlinkRootScope")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer os.RemoveAll(tmpdir)
|
||||
|
||||
expected, err := filepath.EvalSymlinks(tmpdir)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
rewrite, err := RootPath("/", tmpdir)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if rewrite != expected {
|
||||
t.Fatalf("expected %q got %q", expected, rewrite)
|
||||
}
|
||||
}
|
||||
func testRootPathSymlinkEmpty(t *testing.T) {
|
||||
wd, err := os.Getwd()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
res, err := RootPath(wd, "")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if res != wd {
|
||||
t.Fatalf("expected %q got %q", wd, res)
|
||||
}
|
||||
}
|
||||
|
||||
func makeRootPathTest(t *testing.T, apply fstest.Applier, checks []RootCheck) func(t *testing.T) {
|
||||
return func(t *testing.T) {
|
||||
applyDir, err := ioutil.TempDir("", "test-root-path-")
|
||||
if err != nil {
|
||||
t.Fatalf("Unable to make temp directory: %+v", err)
|
||||
}
|
||||
defer os.RemoveAll(applyDir)
|
||||
|
||||
if apply != nil {
|
||||
if err := apply.Apply(applyDir); err != nil {
|
||||
t.Fatalf("Apply failed: %+v", err)
|
||||
}
|
||||
}
|
||||
|
||||
for i, check := range checks {
|
||||
root := applyDir
|
||||
if check.scope != nil {
|
||||
root = check.scope(root)
|
||||
}
|
||||
|
||||
actual, err := RootPath(root, check.unresolved)
|
||||
if check.cause != nil {
|
||||
if err == nil {
|
||||
t.Errorf("(Check %d) Expected error %q, %q evaluated as %q", i+1, check.cause.Error(), check.unresolved, actual)
|
||||
}
|
||||
if errors.Cause(err) != check.cause {
|
||||
t.Fatalf("(Check %d) Failed to evaluate root path: %+v", i+1, err)
|
||||
}
|
||||
} else {
|
||||
expected := filepath.Join(root, check.expected)
|
||||
if err != nil {
|
||||
t.Fatalf("(Check %d) Failed to evaluate root path: %+v", i+1, err)
|
||||
}
|
||||
if actual != expected {
|
||||
t.Errorf("(Check %d) Unexpected evaluated path %q, expected %q", i+1, actual, expected)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func Check(unresolved, expected string) []RootCheck {
|
||||
return []RootCheck{
|
||||
{
|
||||
unresolved: unresolved,
|
||||
expected: expected,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func CheckWithScope(unresolved, expected, scope string) []RootCheck {
|
||||
return []RootCheck{
|
||||
{
|
||||
unresolved: unresolved,
|
||||
expected: expected,
|
||||
scope: func(root string) string {
|
||||
return filepath.Join(root, scope)
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func ErrorWithScope(unresolved, scope string, cause error) []RootCheck {
|
||||
return []RootCheck{
|
||||
{
|
||||
unresolved: unresolved,
|
||||
cause: cause,
|
||||
scope: func(root string) string {
|
||||
return filepath.Join(root, scope)
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func CheckAll(checks ...[]RootCheck) []RootCheck {
|
||||
all := make([]RootCheck, 0, len(checks))
|
||||
for _, c := range checks {
|
||||
all = append(all, c...)
|
||||
}
|
||||
return all
|
||||
}
|
||||
|
||||
func Symlink(oldname, newname string) fstest.Applier {
|
||||
dir := filepath.Dir(newname)
|
||||
if dir != "" {
|
||||
return fstest.Apply(
|
||||
fstest.CreateDir(dir, 0755),
|
||||
fstest.Symlink(oldname, newname),
|
||||
)
|
||||
}
|
||||
return fstest.Symlink(oldname, newname)
|
||||
}
|
28
vendor/github.com/containerd/continuity/fs/stat_bsd.go
generated
vendored
Normal file
28
vendor/github.com/containerd/continuity/fs/stat_bsd.go
generated
vendored
Normal file
|
@ -0,0 +1,28 @@
|
|||
// +build darwin freebsd
|
||||
|
||||
package fs
|
||||
|
||||
import (
|
||||
"syscall"
|
||||
"time"
|
||||
)
|
||||
|
||||
// StatAtime returns the access time from a stat struct
|
||||
func StatAtime(st *syscall.Stat_t) syscall.Timespec {
|
||||
return st.Atimespec
|
||||
}
|
||||
|
||||
// StatCtime returns the created time from a stat struct
|
||||
func StatCtime(st *syscall.Stat_t) syscall.Timespec {
|
||||
return st.Ctimespec
|
||||
}
|
||||
|
||||
// StatMtime returns the modified time from a stat struct
|
||||
func StatMtime(st *syscall.Stat_t) syscall.Timespec {
|
||||
return st.Mtimespec
|
||||
}
|
||||
|
||||
// StatATimeAsTime returns the access time as a time.Time
|
||||
func StatATimeAsTime(st *syscall.Stat_t) time.Time {
|
||||
return time.Unix(int64(st.Atimespec.Sec), int64(st.Atimespec.Nsec)) // nolint: unconvert
|
||||
}
|
27
vendor/github.com/containerd/continuity/fs/stat_linux.go
generated
vendored
Normal file
27
vendor/github.com/containerd/continuity/fs/stat_linux.go
generated
vendored
Normal file
|
@ -0,0 +1,27 @@
|
|||
package fs
|
||||
|
||||
import (
|
||||
"syscall"
|
||||
"time"
|
||||
)
|
||||
|
||||
// StatAtime returns the Atim
|
||||
func StatAtime(st *syscall.Stat_t) syscall.Timespec {
|
||||
return st.Atim
|
||||
}
|
||||
|
||||
// StatCtime returns the Ctim
|
||||
func StatCtime(st *syscall.Stat_t) syscall.Timespec {
|
||||
return st.Ctim
|
||||
}
|
||||
|
||||
// StatMtime returns the Mtim
|
||||
func StatMtime(st *syscall.Stat_t) syscall.Timespec {
|
||||
return st.Mtim
|
||||
}
|
||||
|
||||
// StatATimeAsTime returns st.Atim as a time.Time
|
||||
func StatATimeAsTime(st *syscall.Stat_t) time.Time {
|
||||
// The int64 conversions ensure the line compiles for 32-bit systems as well.
|
||||
return time.Unix(int64(st.Atim.Sec), int64(st.Atim.Nsec)) // nolint: unconvert
|
||||
}
|
13
vendor/github.com/containerd/continuity/fs/time.go
generated
vendored
Normal file
13
vendor/github.com/containerd/continuity/fs/time.go
generated
vendored
Normal file
|
@ -0,0 +1,13 @@
|
|||
package fs
|
||||
|
||||
import "time"
|
||||
|
||||
// Gnu tar and the go tar writer don't have sub-second mtime
|
||||
// precision, which is problematic when we apply changes via tar
|
||||
// files, we handle this by comparing for exact times, *or* same
|
||||
// second count and either a or b having exactly 0 nanoseconds
|
||||
func sameFsTime(a, b time.Time) bool {
|
||||
return a == b ||
|
||||
(a.Unix() == b.Unix() &&
|
||||
(a.Nanosecond() == 0 || b.Nanosecond() == 0))
|
||||
}
|
3
vendor/github.com/containerd/continuity/proto/gen.go
generated
vendored
Normal file
3
vendor/github.com/containerd/continuity/proto/gen.go
generated
vendored
Normal file
|
@ -0,0 +1,3 @@
|
|||
package proto
|
||||
|
||||
//go:generate protoc --go_out=. manifest.proto
|
181
vendor/github.com/containerd/continuity/proto/manifest.pb.go
generated
vendored
Normal file
181
vendor/github.com/containerd/continuity/proto/manifest.pb.go
generated
vendored
Normal file
|
@ -0,0 +1,181 @@
|
|||
// Code generated by protoc-gen-go.
|
||||
// source: manifest.proto
|
||||
// DO NOT EDIT!
|
||||
|
||||
/*
|
||||
Package proto is a generated protocol buffer package.
|
||||
|
||||
It is generated from these files:
|
||||
manifest.proto
|
||||
|
||||
It has these top-level messages:
|
||||
Manifest
|
||||
Resource
|
||||
XAttr
|
||||
ADSEntry
|
||||
*/
|
||||
package proto
|
||||
|
||||
import proto1 "github.com/golang/protobuf/proto"
|
||||
import fmt "fmt"
|
||||
import math "math"
|
||||
|
||||
// Reference imports to suppress errors if they are not otherwise used.
|
||||
var _ = proto1.Marshal
|
||||
var _ = fmt.Errorf
|
||||
var _ = math.Inf
|
||||
|
||||
// This is a compile-time assertion to ensure that this generated file
|
||||
// is compatible with the proto package it is being compiled against.
|
||||
// A compilation error at this line likely means your copy of the
|
||||
// proto package needs to be updated.
|
||||
const _ = proto1.ProtoPackageIsVersion2 // please upgrade the proto package
|
||||
|
||||
// Manifest specifies the entries in a container bundle, keyed and sorted by
|
||||
// path.
|
||||
type Manifest struct {
|
||||
Resource []*Resource `protobuf:"bytes,1,rep,name=resource" json:"resource,omitempty"`
|
||||
}
|
||||
|
||||
func (m *Manifest) Reset() { *m = Manifest{} }
|
||||
func (m *Manifest) String() string { return proto1.CompactTextString(m) }
|
||||
func (*Manifest) ProtoMessage() {}
|
||||
func (*Manifest) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{0} }
|
||||
|
||||
func (m *Manifest) GetResource() []*Resource {
|
||||
if m != nil {
|
||||
return m.Resource
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type Resource struct {
|
||||
// Path specifies the path from the bundle root. If more than one
|
||||
// path is present, the entry may represent a hardlink, rather than using
|
||||
// a link target. The path format is operating system specific.
|
||||
Path []string `protobuf:"bytes,1,rep,name=path" json:"path,omitempty"`
|
||||
// Uid specifies the user id for the resource.
|
||||
Uid int64 `protobuf:"varint,2,opt,name=uid" json:"uid,omitempty"`
|
||||
// Gid specifies the group id for the resource.
|
||||
Gid int64 `protobuf:"varint,3,opt,name=gid" json:"gid,omitempty"`
|
||||
// user and group are not currently used but their field numbers have been
|
||||
// reserved for future use. As such, they are marked as deprecated.
|
||||
User string `protobuf:"bytes,4,opt,name=user" json:"user,omitempty"`
|
||||
Group string `protobuf:"bytes,5,opt,name=group" json:"group,omitempty"`
|
||||
// Mode defines the file mode and permissions. We've used the same
|
||||
// bit-packing from Go's os package,
|
||||
// http://golang.org/pkg/os/#FileMode, since they've done the work of
|
||||
// creating a cross-platform layout.
|
||||
Mode uint32 `protobuf:"varint,6,opt,name=mode" json:"mode,omitempty"`
|
||||
// Size specifies the size in bytes of the resource. This is only valid
|
||||
// for regular files.
|
||||
Size uint64 `protobuf:"varint,7,opt,name=size" json:"size,omitempty"`
|
||||
// Digest specifies the content digest of the target file. Only valid for
|
||||
// regular files. The strings are formatted in OCI style, i.e. <alg>:<encoded>.
|
||||
// For detailed information about the format, please refer to OCI Image Spec:
|
||||
// https://github.com/opencontainers/image-spec/blob/master/descriptor.md#digests-and-verification
|
||||
// The digests are sorted in lexical order and implementations may choose
|
||||
// which algorithms they prefer.
|
||||
Digest []string `protobuf:"bytes,8,rep,name=digest" json:"digest,omitempty"`
|
||||
// Target defines the target of a hard or soft link. Absolute links start
|
||||
// with a slash and specify the resource relative to the bundle root.
|
||||
// Relative links do not start with a slash and are relative to the
|
||||
// resource path.
|
||||
Target string `protobuf:"bytes,9,opt,name=target" json:"target,omitempty"`
|
||||
// Major specifies the major device number for character and block devices.
|
||||
Major uint64 `protobuf:"varint,10,opt,name=major" json:"major,omitempty"`
|
||||
// Minor specifies the minor device number for character and block devices.
|
||||
Minor uint64 `protobuf:"varint,11,opt,name=minor" json:"minor,omitempty"`
|
||||
// Xattr provides storage for extended attributes for the target resource.
|
||||
Xattr []*XAttr `protobuf:"bytes,12,rep,name=xattr" json:"xattr,omitempty"`
|
||||
// Ads stores one or more alternate data streams for the target resource.
|
||||
Ads []*ADSEntry `protobuf:"bytes,13,rep,name=ads" json:"ads,omitempty"`
|
||||
}
|
||||
|
||||
func (m *Resource) Reset() { *m = Resource{} }
|
||||
func (m *Resource) String() string { return proto1.CompactTextString(m) }
|
||||
func (*Resource) ProtoMessage() {}
|
||||
func (*Resource) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{1} }
|
||||
|
||||
func (m *Resource) GetXattr() []*XAttr {
|
||||
if m != nil {
|
||||
return m.Xattr
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *Resource) GetAds() []*ADSEntry {
|
||||
if m != nil {
|
||||
return m.Ads
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// XAttr encodes extended attributes for a resource.
|
||||
type XAttr struct {
|
||||
// Name specifies the attribute name.
|
||||
Name string `protobuf:"bytes,1,opt,name=name" json:"name,omitempty"`
|
||||
// Data specifies the associated data for the attribute.
|
||||
Data []byte `protobuf:"bytes,2,opt,name=data,proto3" json:"data,omitempty"`
|
||||
}
|
||||
|
||||
func (m *XAttr) Reset() { *m = XAttr{} }
|
||||
func (m *XAttr) String() string { return proto1.CompactTextString(m) }
|
||||
func (*XAttr) ProtoMessage() {}
|
||||
func (*XAttr) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{2} }
|
||||
|
||||
// ADSEntry encodes information for a Windows Alternate Data Stream.
|
||||
type ADSEntry struct {
|
||||
// Name specifices the stream name.
|
||||
Name string `protobuf:"bytes,1,opt,name=name" json:"name,omitempty"`
|
||||
// Data specifies the stream data.
|
||||
// See also the description about the digest below.
|
||||
Data []byte `protobuf:"bytes,2,opt,name=data,proto3" json:"data,omitempty"`
|
||||
// Digest is a CAS representation of the stream data.
|
||||
//
|
||||
// At least one of data or digest MUST be specified, and either one of them
|
||||
// SHOULD be specified.
|
||||
//
|
||||
// How to access the actual data using the digest is implementation-specific,
|
||||
// and implementations can choose not to implement digest.
|
||||
// So, digest SHOULD be used only when the stream data is large.
|
||||
Digest string `protobuf:"bytes,3,opt,name=digest" json:"digest,omitempty"`
|
||||
}
|
||||
|
||||
func (m *ADSEntry) Reset() { *m = ADSEntry{} }
|
||||
func (m *ADSEntry) String() string { return proto1.CompactTextString(m) }
|
||||
func (*ADSEntry) ProtoMessage() {}
|
||||
func (*ADSEntry) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{3} }
|
||||
|
||||
func init() {
|
||||
proto1.RegisterType((*Manifest)(nil), "proto.Manifest")
|
||||
proto1.RegisterType((*Resource)(nil), "proto.Resource")
|
||||
proto1.RegisterType((*XAttr)(nil), "proto.XAttr")
|
||||
proto1.RegisterType((*ADSEntry)(nil), "proto.ADSEntry")
|
||||
}
|
||||
|
||||
func init() { proto1.RegisterFile("manifest.proto", fileDescriptor0) }
|
||||
|
||||
var fileDescriptor0 = []byte{
|
||||
// 317 bytes of a gzipped FileDescriptorProto
|
||||
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0x8c, 0x90, 0x4f, 0x4b, 0xf3, 0x40,
|
||||
0x10, 0xc6, 0x49, 0x93, 0xf4, 0x4d, 0xa7, 0xed, 0xab, 0x2c, 0x52, 0xe6, 0x18, 0x73, 0x0a, 0x08,
|
||||
0x15, 0xf4, 0xe0, 0xb9, 0xa2, 0x17, 0xc1, 0xcb, 0x7a, 0xf1, 0xba, 0xba, 0x6b, 0x5c, 0x21, 0xd9,
|
||||
0xb0, 0xd9, 0x80, 0xfa, 0xe5, 0xfc, 0x6a, 0x32, 0xb3, 0x69, 0xd1, 0x9b, 0xa7, 0x3c, 0xcf, 0x6f,
|
||||
0xfe, 0x64, 0xf6, 0x81, 0xff, 0xad, 0xea, 0xec, 0x8b, 0x19, 0xc2, 0xb6, 0xf7, 0x2e, 0x38, 0x91,
|
||||
0xf3, 0xa7, 0xba, 0x82, 0xe2, 0x7e, 0x2a, 0x88, 0x33, 0x28, 0xbc, 0x19, 0xdc, 0xe8, 0x9f, 0x0d,
|
||||
0x26, 0x65, 0x5a, 0x2f, 0x2f, 0x8e, 0x62, 0xf3, 0x56, 0x4e, 0x58, 0x1e, 0x1a, 0xaa, 0xaf, 0x19,
|
||||
0x14, 0x7b, 0x2c, 0x04, 0x64, 0xbd, 0x0a, 0xaf, 0x3c, 0xb5, 0x90, 0xac, 0xc5, 0x31, 0xa4, 0xa3,
|
||||
0xd5, 0x38, 0x2b, 0x93, 0x3a, 0x95, 0x24, 0x89, 0x34, 0x56, 0x63, 0x1a, 0x49, 0x63, 0xb5, 0xd8,
|
||||
0x40, 0x36, 0x0e, 0xc6, 0x63, 0x56, 0x26, 0xf5, 0xe2, 0x7a, 0x86, 0x89, 0x64, 0x2f, 0x10, 0xf2,
|
||||
0xc6, 0xbb, 0xb1, 0xc7, 0xfc, 0x50, 0x88, 0x80, 0xfe, 0xd4, 0x3a, 0x6d, 0x70, 0x5e, 0x26, 0xf5,
|
||||
0x5a, 0xb2, 0x26, 0x36, 0xd8, 0x4f, 0x83, 0xff, 0xca, 0xa4, 0xce, 0x24, 0x6b, 0xb1, 0x81, 0xb9,
|
||||
0xb6, 0x8d, 0x19, 0x02, 0x16, 0x7c, 0xd3, 0xe4, 0x88, 0x07, 0xe5, 0x1b, 0x13, 0x70, 0x41, 0xab,
|
||||
0xe5, 0xe4, 0xc4, 0x09, 0xe4, 0xad, 0x7a, 0x73, 0x1e, 0x81, 0x97, 0x44, 0xc3, 0xd4, 0x76, 0xce,
|
||||
0xe3, 0x72, 0xa2, 0x64, 0x44, 0x05, 0xf9, 0xbb, 0x0a, 0xc1, 0xe3, 0x8a, 0x43, 0x5a, 0x4d, 0x21,
|
||||
0x3d, 0xee, 0x42, 0xf0, 0x32, 0x96, 0xc4, 0x29, 0xa4, 0x4a, 0x0f, 0xb8, 0xfe, 0x15, 0xe3, 0xee,
|
||||
0xe6, 0xe1, 0xb6, 0x0b, 0xfe, 0x43, 0x52, 0xad, 0x3a, 0x87, 0x9c, 0x47, 0xe8, 0xfe, 0x4e, 0xb5,
|
||||
0x94, 0x39, 0x5d, 0xc4, 0x9a, 0x98, 0x56, 0x41, 0x71, 0x7c, 0x2b, 0xc9, 0xba, 0xba, 0x83, 0x62,
|
||||
0xbf, 0xe1, 0xaf, 0x33, 0x3f, 0x72, 0x48, 0xe3, 0x7b, 0xa3, 0x7b, 0x9a, 0xf3, 0x45, 0x97, 0xdf,
|
||||
0x01, 0x00, 0x00, 0xff, 0xff, 0xef, 0x27, 0x99, 0xf7, 0x17, 0x02, 0x00, 0x00,
|
||||
}
|
97
vendor/github.com/containerd/continuity/proto/manifest.proto
generated
vendored
Normal file
97
vendor/github.com/containerd/continuity/proto/manifest.proto
generated
vendored
Normal file
|
@ -0,0 +1,97 @@
|
|||
syntax = "proto3";
|
||||
|
||||
package proto;
|
||||
|
||||
// Manifest specifies the entries in a container bundle, keyed and sorted by
|
||||
// path.
|
||||
message Manifest {
|
||||
repeated Resource resource = 1;
|
||||
}
|
||||
|
||||
message Resource {
|
||||
// Path specifies the path from the bundle root. If more than one
|
||||
// path is present, the entry may represent a hardlink, rather than using
|
||||
// a link target. The path format is operating system specific.
|
||||
repeated string path = 1;
|
||||
|
||||
// NOTE(stevvooe): Need to define clear precedence for user/group/uid/gid precedence.
|
||||
|
||||
// Uid specifies the user id for the resource.
|
||||
int64 uid = 2;
|
||||
|
||||
// Gid specifies the group id for the resource.
|
||||
int64 gid = 3;
|
||||
|
||||
// user and group are not currently used but their field numbers have been
|
||||
// reserved for future use. As such, they are marked as deprecated.
|
||||
string user = 4 [deprecated=true]; // "deprecated" stands for "reserved" here
|
||||
string group = 5 [deprecated=true]; // "deprecated" stands for "reserved" here
|
||||
|
||||
// Mode defines the file mode and permissions. We've used the same
|
||||
// bit-packing from Go's os package,
|
||||
// http://golang.org/pkg/os/#FileMode, since they've done the work of
|
||||
// creating a cross-platform layout.
|
||||
uint32 mode = 6;
|
||||
|
||||
// NOTE(stevvooe): Beyond here, we start defining type specific fields.
|
||||
|
||||
// Size specifies the size in bytes of the resource. This is only valid
|
||||
// for regular files.
|
||||
uint64 size = 7;
|
||||
|
||||
// Digest specifies the content digest of the target file. Only valid for
|
||||
// regular files. The strings are formatted in OCI style, i.e. <alg>:<encoded>.
|
||||
// For detailed information about the format, please refer to OCI Image Spec:
|
||||
// https://github.com/opencontainers/image-spec/blob/master/descriptor.md#digests-and-verification
|
||||
// The digests are sorted in lexical order and implementations may choose
|
||||
// which algorithms they prefer.
|
||||
repeated string digest = 8;
|
||||
|
||||
// Target defines the target of a hard or soft link. Absolute links start
|
||||
// with a slash and specify the resource relative to the bundle root.
|
||||
// Relative links do not start with a slash and are relative to the
|
||||
// resource path.
|
||||
string target = 9;
|
||||
|
||||
// Major specifies the major device number for character and block devices.
|
||||
uint64 major = 10;
|
||||
|
||||
// Minor specifies the minor device number for character and block devices.
|
||||
uint64 minor = 11;
|
||||
|
||||
// Xattr provides storage for extended attributes for the target resource.
|
||||
repeated XAttr xattr = 12;
|
||||
|
||||
// Ads stores one or more alternate data streams for the target resource.
|
||||
repeated ADSEntry ads = 13;
|
||||
|
||||
}
|
||||
|
||||
// XAttr encodes extended attributes for a resource.
|
||||
message XAttr {
|
||||
// Name specifies the attribute name.
|
||||
string name = 1;
|
||||
|
||||
// Data specifies the associated data for the attribute.
|
||||
bytes data = 2;
|
||||
}
|
||||
|
||||
// ADSEntry encodes information for a Windows Alternate Data Stream.
|
||||
message ADSEntry {
|
||||
// Name specifices the stream name.
|
||||
string name = 1;
|
||||
|
||||
// Data specifies the stream data.
|
||||
// See also the description about the digest below.
|
||||
bytes data = 2;
|
||||
|
||||
// Digest is a CAS representation of the stream data.
|
||||
//
|
||||
// At least one of data or digest MUST be specified, and either one of them
|
||||
// SHOULD be specified.
|
||||
//
|
||||
// How to access the actual data using the digest is implementation-specific,
|
||||
// and implementations can choose not to implement digest.
|
||||
// So, digest SHOULD be used only when the stream data is large.
|
||||
string digest = 3;
|
||||
}
|
11
vendor/github.com/containerd/continuity/testutil/helpers.go
generated
vendored
Normal file
11
vendor/github.com/containerd/continuity/testutil/helpers.go
generated
vendored
Normal file
|
@ -0,0 +1,11 @@
|
|||
package testutil
|
||||
|
||||
import (
|
||||
"flag"
|
||||
)
|
||||
|
||||
var rootEnabled bool
|
||||
|
||||
func init() {
|
||||
flag.BoolVar(&rootEnabled, "test.root", false, "enable tests that require root")
|
||||
}
|
41
vendor/github.com/containerd/continuity/testutil/helpers_unix.go
generated
vendored
Normal file
41
vendor/github.com/containerd/continuity/testutil/helpers_unix.go
generated
vendored
Normal file
|
@ -0,0 +1,41 @@
|
|||
// +build !windows
|
||||
|
||||
package testutil
|
||||
|
||||
import (
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
// Unmount unmounts a given mountPoint and sets t.Error if it fails
|
||||
func Unmount(t *testing.T, mountPoint string) {
|
||||
t.Log("unmount", mountPoint)
|
||||
if err := unmountAll(mountPoint); err != nil {
|
||||
t.Error("Could not umount", mountPoint, err)
|
||||
}
|
||||
}
|
||||
|
||||
// RequiresRoot skips tests that require root, unless the test.root flag has
|
||||
// been set
|
||||
func RequiresRoot(t testing.TB) {
|
||||
if !rootEnabled {
|
||||
t.Skip("skipping test that requires root")
|
||||
return
|
||||
}
|
||||
if os.Getuid() != 0 {
|
||||
t.Error("This test must be run as root.")
|
||||
}
|
||||
}
|
||||
|
||||
func unmountAll(mountpoint string) error {
|
||||
for {
|
||||
if err := unix.Unmount(mountpoint, unmountFlags); err != nil {
|
||||
if err == unix.EINVAL {
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
16
vendor/github.com/containerd/continuity/testutil/helpers_windows.go
generated
vendored
Normal file
16
vendor/github.com/containerd/continuity/testutil/helpers_windows.go
generated
vendored
Normal file
|
@ -0,0 +1,16 @@
|
|||
package testutil
|
||||
|
||||
import "testing"
|
||||
|
||||
// RequiresRoot does nothing on Windows
|
||||
func RequiresRoot(t testing.TB) {
|
||||
}
|
||||
|
||||
// RequiresRootM is similar to RequiresRoot but intended to be called from *testing.M.
|
||||
func RequiresRootM() {
|
||||
}
|
||||
|
||||
// Unmount unmounts a given mountPoint and sets t.Error if it fails
|
||||
// Does nothing on Windows
|
||||
func Unmount(t *testing.T, mountPoint string) {
|
||||
}
|
53
vendor/github.com/containerd/continuity/testutil/loopback_linux.go
generated
vendored
Normal file
53
vendor/github.com/containerd/continuity/testutil/loopback_linux.go
generated
vendored
Normal file
|
@ -0,0 +1,53 @@
|
|||
// +build linux
|
||||
|
||||
package testutil
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"os/exec"
|
||||
"strings"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
// NewLoopback creates a loopback device, and returns its device name (/dev/loopX), and its clean-up function.
|
||||
func NewLoopback(size int64) (string, func() error, error) {
|
||||
// create temporary file for the disk image
|
||||
file, err := ioutil.TempFile("", "containerd-test-loopback")
|
||||
if err != nil {
|
||||
return "", nil, errors.Wrap(err, "could not create temporary file for loopback")
|
||||
}
|
||||
|
||||
if err := file.Truncate(size); err != nil {
|
||||
return "", nil, errors.Wrap(err, "failed to resize temp file")
|
||||
}
|
||||
file.Close()
|
||||
|
||||
// create device
|
||||
losetup := exec.Command("losetup", "--find", "--show", file.Name())
|
||||
p, err := losetup.Output()
|
||||
if err != nil {
|
||||
return "", nil, errors.Wrap(err, "loopback setup failed")
|
||||
}
|
||||
|
||||
deviceName := strings.TrimSpace(string(p))
|
||||
logrus.Debugf("Created loop device %s (using %s)", deviceName, file.Name())
|
||||
|
||||
cleanup := func() error {
|
||||
// detach device
|
||||
logrus.Debugf("Removing loop device %s", deviceName)
|
||||
losetup := exec.Command("losetup", "--detach", deviceName)
|
||||
err := losetup.Run()
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "Could not remove loop device %s", deviceName)
|
||||
}
|
||||
|
||||
// remove file
|
||||
logrus.Debugf("Removing temporary file %s", file.Name())
|
||||
return os.Remove(file.Name())
|
||||
}
|
||||
|
||||
return deviceName, cleanup, nil
|
||||
}
|
5
vendor/github.com/containerd/continuity/testutil/mount_linux.go
generated
vendored
Normal file
5
vendor/github.com/containerd/continuity/testutil/mount_linux.go
generated
vendored
Normal file
|
@ -0,0 +1,5 @@
|
|||
package testutil
|
||||
|
||||
import "golang.org/x/sys/unix"
|
||||
|
||||
const unmountFlags int = unix.MNT_DETACH
|
5
vendor/github.com/containerd/continuity/testutil/mount_other.go
generated
vendored
Normal file
5
vendor/github.com/containerd/continuity/testutil/mount_other.go
generated
vendored
Normal file
|
@ -0,0 +1,5 @@
|
|||
// +build !linux,!windows
|
||||
|
||||
package testutil
|
||||
|
||||
const unmountFlags int = 0
|
11
vendor/github.com/containerd/continuity/version/version.go
generated
vendored
Normal file
11
vendor/github.com/containerd/continuity/version/version.go
generated
vendored
Normal file
|
@ -0,0 +1,11 @@
|
|||
package version
|
||||
|
||||
// Package is the overall, canonical project import path under which the
|
||||
// package was built.
|
||||
var Package = "github.com/containerd/continuity"
|
||||
|
||||
// Version indicates which version of the binary is running. This is set to
|
||||
// the latest release tag by hand, always suffixed by "+unknown". During
|
||||
// build, it will be replaced by the actual version. The value here will be
|
||||
// used if the registry is run after a go get based install.
|
||||
var Version = "0.0.0+unknown"
|
Loading…
Add table
Add a link
Reference in a new issue