diff --git a/context.go b/context.go index eedbc5dd..92c09e7a 100644 --- a/context.go +++ b/context.go @@ -8,7 +8,6 @@ import ( "os" "path/filepath" "strings" - "syscall" "github.com/opencontainers/go-digest" ) @@ -115,19 +114,12 @@ func (c *context) Resource(p string, fi os.FileInfo) (Resource, error) { } } - // TODO(stevvooe): This need to be resolved for the container's root, - // where here we are really getting the host OS's value. We need to allow - // this be passed in and fixed up to make these uid/gid mappings portable. - // Either this can be part of the driver or we can achieve it through some - // other mechanism. - sys, ok := fi.Sys().(*syscall.Stat_t) - if !ok { - // TODO(stevvooe): This may not be a hard error for all platforms. We - // may want to move this to the driver. - return nil, fmt.Errorf("unable to resolve syscall.Stat_t from (os.FileInfo).Sys(): %#v", fi) + uid, gid, err := getUidGidFromFileInfo(fi) + if err != nil { + return nil, err } - base, err := newBaseResource(p, fi.Mode(), fmt.Sprint(sys.Uid), fmt.Sprint(sys.Gid)) + base, err := newBaseResource(p, fi.Mode(), fmt.Sprint(uid), fmt.Sprint(gid)) if err != nil { return nil, err } diff --git a/context_unix.go b/context_unix.go new file mode 100644 index 00000000..0533efeb --- /dev/null +++ b/context_unix.go @@ -0,0 +1,26 @@ +// +build linux darwin + +package continuity + +import ( + "fmt" + "os" + "syscall" +) + +// getUidGidFromFileInfo extracts the user and group IDs from a fileinfo. +// This is Unix-specific functionality. +func getUidGidFromFileInfo(fi os.FileInfo) (uint32, uint32, error) { + // TODO(stevvooe): This need to be resolved for the container's root, + // where here we are really getting the host OS's value. We need to allow + // this be passed in and fixed up to make these uid/gid mappings portable. + // Either this can be part of the driver or we can achieve it through some + // other mechanism. + sys, ok := fi.Sys().(*syscall.Stat_t) + if !ok { + // TODO(stevvooe): This may not be a hard error for all platforms. We + // may want to move this to the driver. + return 0, 0, fmt.Errorf("unable to resolve syscall.Stat_t from (os.FileInfo).Sys(): %#v", fi) + } + return sys.Uid, sys.Gid, nil +} diff --git a/context_windows.go b/context_windows.go new file mode 100644 index 00000000..9ed7541a --- /dev/null +++ b/context_windows.go @@ -0,0 +1,9 @@ +package continuity + +import "os" + +// getUidGidFromFileInfo extracts the user and group IDs from a fileinfo. +// This is Unix-specific functionality. +func getUidGidFromFileInfo(fi os.FileInfo) (uint32, uint32, error) { + return 0, 0, nil +} diff --git a/driver.go b/driver.go index 6af7f023..81b2959d 100644 --- a/driver.go +++ b/driver.go @@ -1,7 +1,6 @@ package continuity import ( - "errors" "os" "strconv" ) @@ -144,15 +143,6 @@ func (d *driver) Symlink(oldname, newname string) error { return os.Symlink(oldname, newname) } -func (d *driver) Mknod(path string, mode os.FileMode, major, minor int) error { - return 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 mknod(path, mode, 0, 0) +func (d *driver) DeviceInfo(fi os.FileInfo) (maj uint64, min uint64, err error) { + return deviceInfo(fi) } diff --git a/driver_darwin.go b/driver_darwin.go deleted file mode 100644 index 96dbc933..00000000 --- a/driver_darwin.go +++ /dev/null @@ -1,7 +0,0 @@ -package continuity - -import "os" - -func (d *driver) DeviceInfo(fi os.FileInfo) (maj uint64, min uint64, err error) { - return deviceInfo(fi) -} diff --git a/driver_unix.go b/driver_unix.go index 337ab12a..0c36647e 100644 --- a/driver_unix.go +++ b/driver_unix.go @@ -3,6 +3,7 @@ package continuity import ( + "errors" "fmt" "os" "path/filepath" @@ -102,6 +103,15 @@ func (d *driver) LSetxattr(path string, attrMap map[string][]byte) error { return nil } -func (d *driver) DeviceInfo(fi os.FileInfo) (maj uint64, min uint64, err error) { - return deviceInfo(fi) +func (d *driver) Mknod(path string, mode os.FileMode, major, minor int) error { + return 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 mknod(path, mode, 0, 0) } diff --git a/driver_windows.go b/driver_windows.go new file mode 100644 index 00000000..a40118d9 --- /dev/null +++ b/driver_windows.go @@ -0,0 +1,30 @@ +package continuity + +import ( + "fmt" + "os" +) + +var ( + errdeviceInfoNotImplemented = fmt.Errorf("deviceInfo not implemented on Windows") + errLchmodNotImplemented = fmt.Errorf("Lchmod not implemented on Windows") + errMknodNotImplemented = fmt.Errorf("Mknod not implemented on Windows") + errMkfifoNotImplemented = fmt.Errorf("Mkfifo not implemented on Windows") +) + +func deviceInfo(fi os.FileInfo) (maj uint64, min uint64, err error) { + return 0, 0, errdeviceInfoNotImplemented +} + +// Lchmod changes the mode of an file not following symlinks. +func (d *driver) Lchmod(path string, mode os.FileMode) (err error) { + return errLchmodNotImplemented +} + +func (d *driver) Mknod(path string, mode os.FileMode, major, minor int) error { + return errMknodNotImplemented +} + +func (d *driver) Mkfifo(path string, mode os.FileMode) error { + return errMkfifoNotImplemented +} diff --git a/groups_unix.go b/groups_unix.go index e15c14ff..6164bdbd 100644 --- a/groups_unix.go +++ b/groups_unix.go @@ -1,3 +1,5 @@ +// +build linux darwin + package continuity import ( diff --git a/hardlinks.go b/hardlinks.go index 8b39bd06..4765e968 100644 --- a/hardlinks.go +++ b/hardlinks.go @@ -6,7 +6,8 @@ import ( ) var ( - errNotAHardLink = fmt.Errorf("invalid hardlink") + errNotAHardLink = fmt.Errorf("invalid hardlink") + errNotHardLinkImplemented = fmt.Errorf("hardlink not implemented") ) type hardlinkManager struct { diff --git a/hardlinks_unix.go b/hardlinks_unix.go index 5880f069..3a3c36eb 100644 --- a/hardlinks_unix.go +++ b/hardlinks_unix.go @@ -1,3 +1,5 @@ +// +build linux darwin + package continuity import ( diff --git a/hardlinks_windows.go b/hardlinks_windows.go index 9d55972c..cea23e6a 100644 --- a/hardlinks_windows.go +++ b/hardlinks_windows.go @@ -1,5 +1,19 @@ package continuity +import "os" + // NOTE(stevvooe): Obviously, this is not yet implemented. However, the // makings of an implementation are available in src/os/types_windows.go. More // investigation needs to be done to figure out exactly how to do this. + +// hardlinkKey provides a tuple-key for managing hardlinks. This is system- +// specific. +type hardlinkKey struct { +} + +// newHardlinkKey returns a hardlink key for the provided file info. If the +// resource does not represent a possible hardlink, errNotAHardLink will be +// returned. +func newHardlinkKey(fi os.FileInfo) (hardlinkKey, error) { + return hardlinkKey{}, errNotHardLinkImplemented +} diff --git a/manifest_test.go b/manifest_test.go index 629dd209..079ede55 100644 --- a/manifest_test.go +++ b/manifest_test.go @@ -1,420 +1,424 @@ -package continuity - -import ( - "bytes" - _ "crypto/sha256" - "errors" - "fmt" - "io/ioutil" - "math/rand" - "os" - "path/filepath" - "sort" - "strconv" - "syscall" - "testing" - - "github.com/opencontainers/go-digest" -) - -// Hard things: -// 1. Groups/gid - no standard library support. -// 2. xattrs - must choose package to provide this. -// 3. ADS - no clue where to start. - -func TestWalkFS(t *testing.T) { - rand.Seed(1) - - // Testing: - // 1. Setup different files: - // - links - // - sibling directory - relative - // - sibling directory - absolute - // - parent directory - absolute - // - parent directory - relative - // - illegal links - // - parent directory - relative, out of root - // - parent directory - absolute, out of root - // - regular files - // - character devices - // - what about sticky bits? - // 2. Build the manifest. - // 3. Verify expected result. - testResources := []dresource{ - { - path: "a", - mode: 0644, - }, - { - kind: rhardlink, - path: "a-hardlink", - target: "a", - }, - { - kind: rdirectory, - path: "b", - mode: 0755, - }, - { - kind: rhardlink, - path: "b/a-hardlink", - target: "a", - }, - { - path: "b/a", - mode: 0600 | os.ModeSticky, - }, - { - kind: rdirectory, - path: "c", - mode: 0755, - }, - { - path: "c/a", - mode: 0644, - }, - { - kind: rrelsymlink, - path: "c/ca-relsymlink", - mode: 0600, - target: "a", - }, - { - kind: rrelsymlink, - path: "c/a-relsymlink", - mode: 0600, - target: "../a", - }, - { - kind: rabssymlink, - path: "c/a-abssymlink", - mode: 0600, - target: "a", - }, - // TODO(stevvooe): Make sure we can test this case and get proper - // errors when it is encountered. - // { - // // create a bad symlink and make sure we don't include it. - // kind: relsymlink, - // path: "c/a-badsymlink", - // mode: 0600, - // target: "../../..", - // }, - - // TODO(stevvooe): Must add tests for xattrs, with symlinks, - // directorys and regular files. - - { - kind: rnamedpipe, - path: "fifo", - mode: 0666 | os.ModeNamedPipe, - }, - - { - kind: rdirectory, - path: "/dev", - mode: 0755, - }, - - // NOTE(stevvooe): Below here, we add a few simple character devices. - // Block devices are untested but should be nearly the same as - // character devices. - // devNullResource, - // devZeroResource, - } - - root, err := ioutil.TempDir("", "continuity-test-") - if err != nil { - t.Fatalf("error creating temporary directory: %v", err) - } - - defer os.RemoveAll(root) - - generateTestFiles(t, root, testResources) - - ctx, err := NewContext(root) - if err != nil { - t.Fatalf("error getting context: %v", err) - } - - m, err := BuildManifest(ctx) - if err != nil { - t.Fatalf("error building manifest: %v", err) - } - - var b bytes.Buffer - MarshalText(&b, m) - t.Log(b.String()) - - // TODO(dmcgowan): always verify, currently hard links not supported - //if err := VerifyManifest(ctx, m); err != nil { - // t.Fatalf("error verifying manifest: %v") - //} - - expectedResources, err := expectedResourceList(root, testResources) - if err != nil { - // TODO(dmcgowan): update function to panic, this would mean test setup error - t.Fatalf("error creating resource list: %v", err) - } - - // Diff resources - diff := diffResourceList(expectedResources, m.Resources) - if diff.HasDiff() { - t.Log("Resource list difference") - for _, a := range diff.Additions { - t.Logf("Unexpected resource: %#v", a) - } - for _, d := range diff.Deletions { - t.Logf("Missing resource: %#v", d) - } - for _, u := range diff.Updates { - t.Logf("Changed resource:\n\tExpected: %#v\n\tActual: %#v", u.Original, u.Updated) - } - - t.FailNow() - } -} - -// TODO(stevvooe): At this time, we have a nice testing framework to define -// and build resources. This will likely be a pre-cursor to the packages -// public interface. -type kind int - -func (k kind) String() string { - switch k { - case rfile: - return "file" - case rdirectory: - return "directory" - case rhardlink: - return "hardlink" - case rchardev: - return "chardev" - case rnamedpipe: - return "namedpipe" - } - - panic(fmt.Sprintf("unknown kind: %v", int(k))) -} - -const ( - rfile kind = iota - rdirectory - rhardlink - rrelsymlink - rabssymlink - rchardev - rnamedpipe -) - -type dresource struct { - kind kind - path string - mode os.FileMode - target string // hard/soft link target - digest digest.Digest - size int - uid int - gid int - major, minor int -} - -func generateTestFiles(t *testing.T, root string, resources []dresource) { - for i, resource := range resources { - p := filepath.Join(root, resource.path) - switch resource.kind { - case rfile: - size := rand.Intn(4 << 20) - d := make([]byte, size) - randomBytes(d) - dgst := digest.FromBytes(d) - resources[i].digest = dgst - resources[i].size = size - - // this relies on the proper directory parent being defined. - if err := ioutil.WriteFile(p, d, resource.mode); err != nil { - t.Fatalf("error writing %q: %v", p, err) - } - case rdirectory: - if err := os.Mkdir(p, resource.mode); err != nil { - t.Fatalf("error creating directory %q: %v", p, err) - } - case rhardlink: - target := filepath.Join(root, resource.target) - if err := os.Link(target, p); err != nil { - t.Fatalf("error creating hardlink: %v", err) - } - case rrelsymlink: - if err := os.Symlink(resource.target, p); err != nil { - t.Fatalf("error creating symlink: %v", err) - } - case rabssymlink: - // for absolute links, we join with root. - target := filepath.Join(root, resource.target) - - if err := os.Symlink(target, p); err != nil { - t.Fatalf("error creating symlink: %v", err) - } - case rchardev, rnamedpipe: - if err := mknod(p, resource.mode, resource.major, resource.minor); err != nil { - t.Fatalf("error creating device %q: %v", p, err) - } - default: - t.Fatalf("unknown resource type: %v", resource.kind) - } - - st, err := os.Lstat(p) - if err != nil { - t.Fatalf("error statting after creation: %v", err) - } - resources[i].uid = int(st.Sys().(*syscall.Stat_t).Uid) - resources[i].gid = int(st.Sys().(*syscall.Stat_t).Gid) - resources[i].mode = st.Mode() - - // TODO: Readback and join xattr - } - - // log the test root for future debugging - if err := filepath.Walk(root, func(p string, fi os.FileInfo, err error) error { - if fi.Mode()&os.ModeSymlink != 0 { - target, err := os.Readlink(p) - if err != nil { - return err - } - t.Log(fi.Mode(), p, "->", target) - } else { - t.Log(fi.Mode(), p) - } - - return nil - }); err != nil { - t.Fatalf("error walking created root: %v", err) - } - - var b bytes.Buffer - if err := tree(&b, root); err != nil { - t.Fatalf("error running tree: %v", err) - } - t.Logf("\n%s", b.String()) -} - -func randomBytes(p []byte) { - for i := range p { - p[i] = byte(rand.Intn(1<<8 - 1)) - } -} - -// expectedResourceList sorts the set of resources into the order -// expected in the manifest and collapses hardlinks -func expectedResourceList(root string, resources []dresource) ([]Resource, error) { - resourceMap := map[string]Resource{} - paths := []string{} - for _, r := range resources { - absPath := r.path - if !filepath.IsAbs(absPath) { - absPath = "/" + absPath - } - uidStr := strconv.Itoa(r.uid) - gidStr := strconv.Itoa(r.gid) - switch r.kind { - case rfile: - f := ®ularFile{ - resource: resource{ - paths: []string{absPath}, - mode: r.mode, - uid: uidStr, - gid: gidStr, - }, - size: int64(r.size), - digests: []digest.Digest{r.digest}, - } - resourceMap[absPath] = f - paths = append(paths, absPath) - case rdirectory: - d := &directory{ - resource: resource{ - paths: []string{absPath}, - mode: r.mode, - uid: uidStr, - gid: gidStr, - }, - } - resourceMap[absPath] = d - paths = append(paths, absPath) - case rhardlink: - targetPath := r.target - if !filepath.IsAbs(targetPath) { - targetPath = "/" + targetPath - } - target, ok := resourceMap[targetPath] - if !ok { - return nil, errors.New("must specify target before hardlink for test resources") - } - rf, ok := target.(*regularFile) - if !ok { - return nil, errors.New("hardlink target must be regular file") - } - // TODO(dmcgowan): full merge - rf.paths = append(rf.paths, absPath) - // TODO(dmcgowan): check if first path is now different, changes source order and should update - // resource map key, to avoid canonically ordered first should be regular file - sort.Stable(sort.StringSlice(rf.paths)) - case rrelsymlink, rabssymlink: - targetPath := r.target - if r.kind == rabssymlink && !filepath.IsAbs(r.target) { - // for absolute links, we join with root. - targetPath = filepath.Join(root, targetPath) - } - s := &symLink{ - resource: resource{ - paths: []string{absPath}, - mode: r.mode, - uid: uidStr, - gid: gidStr, - }, - target: targetPath, - } - resourceMap[absPath] = s - paths = append(paths, absPath) - case rchardev: - d := &device{ - resource: resource{ - paths: []string{absPath}, - mode: r.mode, - uid: uidStr, - gid: gidStr, - }, - major: uint64(r.major), - minor: uint64(r.minor), - } - resourceMap[absPath] = d - paths = append(paths, absPath) - case rnamedpipe: - p := &namedPipe{ - resource: resource{ - paths: []string{absPath}, - mode: r.mode, - uid: uidStr, - gid: gidStr, - }, - } - resourceMap[absPath] = p - paths = append(paths, absPath) - default: - return nil, fmt.Errorf("unknown resource type: %v", r.kind) - } - } - - if len(resourceMap) < len(paths) { - return nil, errors.New("resource list has duplicated paths") - } - - sort.Strings(paths) - - manifestResources := make([]Resource, len(paths)) - for i, p := range paths { - manifestResources[i] = resourceMap[p] - } - - return manifestResources, nil -} +package continuity + +import ( + "bytes" + _ "crypto/sha256" + "errors" + "fmt" + "io/ioutil" + "math/rand" + "os" + "path/filepath" + "sort" + "strconv" + "testing" + + "github.com/opencontainers/go-digest" +) + +// Hard things: +// 1. Groups/gid - no standard library support. +// 2. xattrs - must choose package to provide this. +// 3. ADS - no clue where to start. + +func TestWalkFS(t *testing.T) { + rand.Seed(1) + + // Testing: + // 1. Setup different files: + // - links + // - sibling directory - relative + // - sibling directory - absolute + // - parent directory - absolute + // - parent directory - relative + // - illegal links + // - parent directory - relative, out of root + // - parent directory - absolute, out of root + // - regular files + // - character devices + // - what about sticky bits? + // 2. Build the manifest. + // 3. Verify expected result. + testResources := []dresource{ + { + path: "a", + mode: 0644, + }, + { + kind: rhardlink, + path: "a-hardlink", + target: "a", + }, + { + kind: rdirectory, + path: "b", + mode: 0755, + }, + { + kind: rhardlink, + path: "b/a-hardlink", + target: "a", + }, + { + path: "b/a", + mode: 0600 | os.ModeSticky, + }, + { + kind: rdirectory, + path: "c", + mode: 0755, + }, + { + path: "c/a", + mode: 0644, + }, + { + kind: rrelsymlink, + path: "c/ca-relsymlink", + mode: 0600, + target: "a", + }, + { + kind: rrelsymlink, + path: "c/a-relsymlink", + mode: 0600, + target: "../a", + }, + { + kind: rabssymlink, + path: "c/a-abssymlink", + mode: 0600, + target: "a", + }, + // TODO(stevvooe): Make sure we can test this case and get proper + // errors when it is encountered. + // { + // // create a bad symlink and make sure we don't include it. + // kind: relsymlink, + // path: "c/a-badsymlink", + // mode: 0600, + // target: "../../..", + // }, + + // TODO(stevvooe): Must add tests for xattrs, with symlinks, + // directorys and regular files. + + { + kind: rnamedpipe, + path: "fifo", + mode: 0666 | os.ModeNamedPipe, + }, + + { + kind: rdirectory, + path: "/dev", + mode: 0755, + }, + + // NOTE(stevvooe): Below here, we add a few simple character devices. + // Block devices are untested but should be nearly the same as + // character devices. + // devNullResource, + // devZeroResource, + } + + root, err := ioutil.TempDir("", "continuity-test-") + if err != nil { + t.Fatalf("error creating temporary directory: %v", err) + } + + defer os.RemoveAll(root) + + generateTestFiles(t, root, testResources) + + ctx, err := NewContext(root) + if err != nil { + t.Fatalf("error getting context: %v", err) + } + + m, err := BuildManifest(ctx) + if err != nil { + t.Fatalf("error building manifest: %v", err) + } + + var b bytes.Buffer + MarshalText(&b, m) + t.Log(b.String()) + + // TODO(dmcgowan): always verify, currently hard links not supported + //if err := VerifyManifest(ctx, m); err != nil { + // t.Fatalf("error verifying manifest: %v") + //} + + expectedResources, err := expectedResourceList(root, testResources) + if err != nil { + // TODO(dmcgowan): update function to panic, this would mean test setup error + t.Fatalf("error creating resource list: %v", err) + } + + // Diff resources + diff := diffResourceList(expectedResources, m.Resources) + if diff.HasDiff() { + t.Log("Resource list difference") + for _, a := range diff.Additions { + t.Logf("Unexpected resource: %#v", a) + } + for _, d := range diff.Deletions { + t.Logf("Missing resource: %#v", d) + } + for _, u := range diff.Updates { + t.Logf("Changed resource:\n\tExpected: %#v\n\tActual: %#v", u.Original, u.Updated) + } + + t.FailNow() + } +} + +// TODO(stevvooe): At this time, we have a nice testing framework to define +// and build resources. This will likely be a pre-cursor to the packages +// public interface. +type kind int + +func (k kind) String() string { + switch k { + case rfile: + return "file" + case rdirectory: + return "directory" + case rhardlink: + return "hardlink" + case rchardev: + return "chardev" + case rnamedpipe: + return "namedpipe" + } + + panic(fmt.Sprintf("unknown kind: %v", int(k))) +} + +const ( + rfile kind = iota + rdirectory + rhardlink + rrelsymlink + rabssymlink + rchardev + rnamedpipe +) + +type dresource struct { + kind kind + path string + mode os.FileMode + target string // hard/soft link target + digest digest.Digest + size int + uid int + gid int + major, minor int +} + +func generateTestFiles(t *testing.T, root string, resources []dresource) { + for i, resource := range resources { + p := filepath.Join(root, resource.path) + switch resource.kind { + case rfile: + size := rand.Intn(4 << 20) + d := make([]byte, size) + randomBytes(d) + dgst := digest.FromBytes(d) + resources[i].digest = dgst + resources[i].size = size + + // this relies on the proper directory parent being defined. + if err := ioutil.WriteFile(p, d, resource.mode); err != nil { + t.Fatalf("error writing %q: %v", p, err) + } + case rdirectory: + if err := os.Mkdir(p, resource.mode); err != nil { + t.Fatalf("error creating directory %q: %v", p, err) + } + case rhardlink: + target := filepath.Join(root, resource.target) + if err := os.Link(target, p); err != nil { + t.Fatalf("error creating hardlink: %v", err) + } + case rrelsymlink: + if err := os.Symlink(resource.target, p); err != nil { + t.Fatalf("error creating symlink: %v", err) + } + case rabssymlink: + // for absolute links, we join with root. + target := filepath.Join(root, resource.target) + + if err := os.Symlink(target, p); err != nil { + t.Fatalf("error creating symlink: %v", err) + } + case rchardev, rnamedpipe: + if err := mknod(p, resource.mode, resource.major, resource.minor); err != nil { + t.Fatalf("error creating device %q: %v", p, err) + } + default: + t.Fatalf("unknown resource type: %v", resource.kind) + } + + st, err := os.Lstat(p) + if err != nil { + t.Fatalf("error statting after creation: %v", err) + } + + uid, gid, err := getUidGidFromFileInfo(st) + if err != nil { + t.Fatalf("error getting Uid/Gid: %v", err) + } + resources[i].uid = int(uid) + resources[i].gid = int(gid) + resources[i].mode = st.Mode() + + // TODO: Readback and join xattr + } + + // log the test root for future debugging + if err := filepath.Walk(root, func(p string, fi os.FileInfo, err error) error { + if fi.Mode()&os.ModeSymlink != 0 { + target, err := os.Readlink(p) + if err != nil { + return err + } + t.Log(fi.Mode(), p, "->", target) + } else { + t.Log(fi.Mode(), p) + } + + return nil + }); err != nil { + t.Fatalf("error walking created root: %v", err) + } + + var b bytes.Buffer + if err := tree(&b, root); err != nil { + t.Fatalf("error running tree: %v", err) + } + t.Logf("\n%s", b.String()) +} + +func randomBytes(p []byte) { + for i := range p { + p[i] = byte(rand.Intn(1<<8 - 1)) + } +} + +// expectedResourceList sorts the set of resources into the order +// expected in the manifest and collapses hardlinks +func expectedResourceList(root string, resources []dresource) ([]Resource, error) { + resourceMap := map[string]Resource{} + paths := []string{} + for _, r := range resources { + absPath := r.path + if !filepath.IsAbs(absPath) { + absPath = "/" + absPath + } + uidStr := strconv.Itoa(r.uid) + gidStr := strconv.Itoa(r.gid) + switch r.kind { + case rfile: + f := ®ularFile{ + resource: resource{ + paths: []string{absPath}, + mode: r.mode, + uid: uidStr, + gid: gidStr, + }, + size: int64(r.size), + digests: []digest.Digest{r.digest}, + } + resourceMap[absPath] = f + paths = append(paths, absPath) + case rdirectory: + d := &directory{ + resource: resource{ + paths: []string{absPath}, + mode: r.mode, + uid: uidStr, + gid: gidStr, + }, + } + resourceMap[absPath] = d + paths = append(paths, absPath) + case rhardlink: + targetPath := r.target + if !filepath.IsAbs(targetPath) { + targetPath = "/" + targetPath + } + target, ok := resourceMap[targetPath] + if !ok { + return nil, errors.New("must specify target before hardlink for test resources") + } + rf, ok := target.(*regularFile) + if !ok { + return nil, errors.New("hardlink target must be regular file") + } + // TODO(dmcgowan): full merge + rf.paths = append(rf.paths, absPath) + // TODO(dmcgowan): check if first path is now different, changes source order and should update + // resource map key, to avoid canonically ordered first should be regular file + sort.Stable(sort.StringSlice(rf.paths)) + case rrelsymlink, rabssymlink: + targetPath := r.target + if r.kind == rabssymlink && !filepath.IsAbs(r.target) { + // for absolute links, we join with root. + targetPath = filepath.Join(root, targetPath) + } + s := &symLink{ + resource: resource{ + paths: []string{absPath}, + mode: r.mode, + uid: uidStr, + gid: gidStr, + }, + target: targetPath, + } + resourceMap[absPath] = s + paths = append(paths, absPath) + case rchardev: + d := &device{ + resource: resource{ + paths: []string{absPath}, + mode: r.mode, + uid: uidStr, + gid: gidStr, + }, + major: uint64(r.major), + minor: uint64(r.minor), + } + resourceMap[absPath] = d + paths = append(paths, absPath) + case rnamedpipe: + p := &namedPipe{ + resource: resource{ + paths: []string{absPath}, + mode: r.mode, + uid: uidStr, + gid: gidStr, + }, + } + resourceMap[absPath] = p + paths = append(paths, absPath) + default: + return nil, fmt.Errorf("unknown resource type: %v", r.kind) + } + } + + if len(resourceMap) < len(paths) { + return nil, errors.New("resource list has duplicated paths") + } + + sort.Strings(paths) + + manifestResources := make([]Resource, len(paths)) + for i, p := range paths { + manifestResources[i] = resourceMap[p] + } + + return manifestResources, nil +} diff --git a/resource.go b/resource.go index ac644a06..855970fc 100644 --- a/resource.go +++ b/resource.go @@ -528,7 +528,7 @@ func fromProto(b *pb.Resource) (Resource, error) { base.xattrs = make(map[string][]byte, len(b.Xattr)) - for _, attr:= range b.Xattr { + for _, attr := range b.Xattr { base.xattrs[attr.Name] = attr.Data }