diff --git a/fs/copy.go b/fs/copy.go index d3df8397..b7af7a30 100644 --- a/fs/copy.go +++ b/fs/copy.go @@ -22,6 +22,8 @@ import ( "os" "path/filepath" "sync" + + "github.com/sirupsen/logrus" ) var bufferPool = &sync.Pool{ @@ -152,15 +154,15 @@ func copyDirectory(dst, src string, inodes map[uint64]string, o *copyDirOpts) er if err := os.Symlink(link, target); err != nil { return fmt.Errorf("failed to create symlink: %s: %w", target, err) } - case (fi.Mode() & os.ModeDevice) == os.ModeDevice: - if err := copyDevice(target, fi); err != nil { - return fmt.Errorf("failed to create device: %w", err) + case (fi.Mode() & os.ModeDevice) == os.ModeDevice, + (fi.Mode() & os.ModeNamedPipe) == os.ModeNamedPipe, + (fi.Mode() & os.ModeSocket) == os.ModeSocket: + if err := copyIrregular(target, fi); err != nil { + return fmt.Errorf("failed to create irregular file: %w", err) } default: - // TODO: Support pipes and sockets - if err != nil { - return fmt.Errorf("unsupported mode %s: %w", fi.Mode(), err) - } + logrus.Warnf("unsupported mode: %s: %s", source, fi.Mode()) + continue } if err := copyFileInfo(fi, source, target); err != nil { diff --git a/fs/copy_freebsd.go b/fs/copy_freebsd.go deleted file mode 100644 index 4aaf743e..00000000 --- a/fs/copy_freebsd.go +++ /dev/null @@ -1,36 +0,0 @@ -//go:build freebsd -// +build freebsd - -/* - Copyright The containerd Authors. - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ - -package fs - -import ( - "errors" - "os" - "syscall" - - "golang.org/x/sys/unix" -) - -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()), st.Rdev) -} diff --git a/fs/copy_darwin.go b/fs/copy_irregular_freebsd.go similarity index 60% rename from fs/copy_darwin.go rename to fs/copy_irregular_freebsd.go index ce55f0aa..cfe9d802 100644 --- a/fs/copy_darwin.go +++ b/fs/copy_irregular_freebsd.go @@ -1,6 +1,3 @@ -//go:build darwin -// +build darwin - /* Copyright The containerd Authors. @@ -20,17 +17,20 @@ package fs import ( - "errors" + "fmt" "os" "syscall" - - "golang.org/x/sys/unix" ) -func copyDevice(dst string, fi os.FileInfo) error { - st, ok := fi.Sys().(*syscall.Stat_t) +// copyIrregular covers devices, pipes, and sockets +func copyIrregular(dst string, fi os.FileInfo) error { + st, ok := fi.Sys().(*syscall.Stat_t) // not *unix.Stat_t if !ok { - return errors.New("unsupported stat type") + return fmt.Errorf("unsupported stat type: %s: %v", dst, fi.Mode()) + } + var rDev uint64 // uint64 on FreeBSD, int on other unixen + if fi.Mode()&os.ModeDevice == os.ModeDevice { + rDev = st.Rdev } - return unix.Mknod(dst, uint32(fi.Mode()), int(st.Rdev)) + return syscall.Mknod(dst, uint32(st.Mode), rDev) } diff --git a/fs/copy_device_unix.go b/fs/copy_irregular_unix.go similarity index 58% rename from fs/copy_device_unix.go rename to fs/copy_irregular_unix.go index f821890c..99fc8a96 100644 --- a/fs/copy_device_unix.go +++ b/fs/copy_irregular_unix.go @@ -1,5 +1,5 @@ -//go:build openbsd || solaris || netbsd -// +build openbsd solaris netbsd +//go:build !windows && !freebsd +// +build !windows,!freebsd /* Copyright The containerd Authors. @@ -20,17 +20,21 @@ package fs import ( - "errors" + "fmt" "os" "syscall" - - "golang.org/x/sys/unix" ) -func copyDevice(dst string, fi os.FileInfo) error { - st, ok := fi.Sys().(*syscall.Stat_t) +// copyIrregular covers devices, pipes, and sockets +func copyIrregular(dst string, fi os.FileInfo) error { + st, ok := fi.Sys().(*syscall.Stat_t) // not *unix.Stat_t if !ok { - return errors.New("unsupported stat type") + return fmt.Errorf("unsupported stat type: %s: %v", dst, fi.Mode()) + } + var rDev int + if fi.Mode()&os.ModeDevice == os.ModeDevice { + rDev = int(st.Rdev) } - return unix.Mknod(dst, uint32(fi.Mode()), int(st.Rdev)) + //nolint:unconvert + return syscall.Mknod(dst, uint32(st.Mode), rDev) } diff --git a/fs/copy_linux.go b/fs/copy_linux.go index 93840766..1906e5e0 100644 --- a/fs/copy_linux.go +++ b/fs/copy_linux.go @@ -17,7 +17,6 @@ package fs import ( - "errors" "fmt" "io" "os" @@ -144,11 +143,3 @@ func copyXAttrs(dst, src string, excludes map[string]struct{}, errorHandler XAtt 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)) -} diff --git a/fs/copy_unix_test.go b/fs/copy_unix_test.go index 6f6d2f41..69fa5d8a 100644 --- a/fs/copy_unix_test.go +++ b/fs/copy_unix_test.go @@ -22,10 +22,13 @@ package fs import ( "io/ioutil" "os" + "path/filepath" + "syscall" "testing" "github.com/containerd/continuity/fs/fstest" "github.com/containerd/continuity/sysx" + "golang.org/x/sys/unix" ) func assertXAttr(t *testing.T, dir, xattr, xval string, xerr error) { @@ -86,3 +89,88 @@ func TestCopyDirWithXAttrExcludes(t *testing.T) { assertXAttr(t, dst, "user.test-x", "", sysx.ENODATA) }) } + +func TestCopyIrregular(t *testing.T) { + var prepared int + prepareSrc := func(src string) { + f0Pipe := filepath.Join(src, "f0.pipe") + if err := unix.Mkfifo(f0Pipe, 0600); err != nil { + t.Fatal(err) + } + prepared++ + f1Normal := filepath.Join(src, "f1.normal") + if err := ioutil.WriteFile(f1Normal, []byte("content of f1.normal"), 0600); err != nil { + t.Fatal(err) + } + prepared++ + f2Socket := filepath.Join(src, "f2.sock") + if err := unix.Mknod(f2Socket, 0600|unix.S_IFSOCK, 0); err != nil { + t.Fatal(err) + } + prepared++ + f3Dev := filepath.Join(src, "f3.dev") + if err := unix.Mknod(f3Dev, 0600|unix.S_IFCHR, 42); err != nil { + t.Logf("skipping testing S_IFCHR: %v", err) + } else { + prepared++ + } + } + + verifyDst := func(dst string) { + entries, err := ioutil.ReadDir(dst) + if err != nil { + t.Fatal(err) + } + var verified int + for _, f := range entries { + name := f.Name() + full := filepath.Join(dst, name) + fi, err := os.Stat(full) + if err != nil { + t.Fatal(err) + } + mode := fi.Mode() + switch name { + case "f0.pipe": + if mode&os.ModeNamedPipe != os.ModeNamedPipe { + t.Fatalf("unexpected mode of %s: %v", name, mode) + } + case "f1.normal": + b, err := ioutil.ReadFile(full) + if err != nil { + t.Fatal(err) + } + if string(b) != "content of f1.normal" { + t.Fatalf("unexpected content of %s: %q", name, string(b)) + } + case "f2.sock": + if mode&os.ModeSocket != os.ModeSocket { + t.Fatalf("unexpected mode of %s: %v", name, mode) + } + case "f3.dev": + if mode&os.ModeDevice != os.ModeDevice { + t.Fatalf("unexpected mode of %s: %v", name, mode) + } + sys, ok := fi.Sys().(*syscall.Stat_t) + if !ok { + t.Fatalf("unexpected type: %v", fi.Sys()) + } + if sys.Rdev != 42 { + t.Fatalf("unexpected rdev of %s: %d", name, sys.Rdev) + } + } + verified++ + } + if verified != prepared { + t.Fatalf("prepared %d files, verified %d files", prepared, verified) + } + } + + src := t.TempDir() + dst := t.TempDir() + prepareSrc(src) + if err := CopyDir(dst, src); err != nil { + t.Fatal(err) + } + verifyDst(dst) +} diff --git a/fs/copy_windows.go b/fs/copy_windows.go index e3f0cdd5..4dad9441 100644 --- a/fs/copy_windows.go +++ b/fs/copy_windows.go @@ -85,6 +85,6 @@ func copyXAttrs(dst, src string, excludes map[string]struct{}, errorHandler XAtt return nil } -func copyDevice(dst string, fi os.FileInfo) error { - return errors.New("device copy not supported") +func copyIrregular(dst string, fi os.FileInfo) error { + return errors.New("irregular copy not supported") }