Skip to content

Commit ca52b93

Browse files
committed
fs.CopyDir: support sockets and pipes
Signed-off-by: Akihiro Suda <[email protected]>
1 parent cd17c99 commit ca52b93

7 files changed

Lines changed: 122 additions & 73 deletions

File tree

fs/copy.go

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@ import (
2222
"os"
2323
"path/filepath"
2424
"sync"
25+
26+
"github.com/sirupsen/logrus"
2527
)
2628

2729
var bufferPool = &sync.Pool{
@@ -152,15 +154,15 @@ func copyDirectory(dst, src string, inodes map[uint64]string, o *copyDirOpts) er
152154
if err := os.Symlink(link, target); err != nil {
153155
return fmt.Errorf("failed to create symlink: %s: %w", target, err)
154156
}
155-
case (fi.Mode() & os.ModeDevice) == os.ModeDevice:
156-
if err := copyDevice(target, fi); err != nil {
157-
return fmt.Errorf("failed to create device: %w", err)
157+
case (fi.Mode() & os.ModeDevice) == os.ModeDevice,
158+
(fi.Mode() & os.ModeNamedPipe) == os.ModeNamedPipe,
159+
(fi.Mode() & os.ModeSocket) == os.ModeSocket:
160+
if err := copyIrregular(target, fi); err != nil {
161+
return fmt.Errorf("failed to create irregular file: %w", err)
158162
}
159163
default:
160-
// TODO: Support pipes and sockets
161-
if err != nil {
162-
return fmt.Errorf("unsupported mode %s: %w", fi.Mode(), err)
163-
}
164+
logrus.Warnf("unsupported mode: %s: %s", source, fi.Mode())
165+
continue
164166
}
165167

166168
if err := copyFileInfo(fi, source, target); err != nil {

fs/copy_freebsd.go

Lines changed: 0 additions & 36 deletions
This file was deleted.
Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,3 @@
1-
//go:build darwin
2-
// +build darwin
3-
41
/*
52
Copyright The containerd Authors.
63
@@ -20,17 +17,20 @@
2017
package fs
2118

2219
import (
23-
"errors"
20+
"fmt"
2421
"os"
2522
"syscall"
26-
27-
"golang.org/x/sys/unix"
2823
)
2924

30-
func copyDevice(dst string, fi os.FileInfo) error {
31-
st, ok := fi.Sys().(*syscall.Stat_t)
25+
// copyIrregular covers devices, pipes, and sockets
26+
func copyIrregular(dst string, fi os.FileInfo) error {
27+
st, ok := fi.Sys().(*syscall.Stat_t) // not *unix.Stat_t
3228
if !ok {
33-
return errors.New("unsupported stat type")
29+
return fmt.Errorf("unsupported stat type: %s: %v", dst, fi.Mode())
30+
}
31+
var rDev uint64 // uint64 on FreeBSD, int on other unixen
32+
if fi.Mode()&os.ModeDevice == os.ModeDevice {
33+
rDev = st.Rdev
3434
}
35-
return unix.Mknod(dst, uint32(fi.Mode()), int(st.Rdev))
35+
return syscall.Mknod(dst, uint32(st.Mode), rDev)
3636
}
Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
//go:build openbsd || solaris || netbsd
2-
// +build openbsd solaris netbsd
1+
//go:build !windows && !freebsd
2+
// +build !windows,!freebsd
33

44
/*
55
Copyright The containerd Authors.
@@ -20,17 +20,21 @@
2020
package fs
2121

2222
import (
23-
"errors"
23+
"fmt"
2424
"os"
2525
"syscall"
26-
27-
"golang.org/x/sys/unix"
2826
)
2927

30-
func copyDevice(dst string, fi os.FileInfo) error {
31-
st, ok := fi.Sys().(*syscall.Stat_t)
28+
// copyIrregular covers devices, pipes, and sockets
29+
func copyIrregular(dst string, fi os.FileInfo) error {
30+
st, ok := fi.Sys().(*syscall.Stat_t) // not *unix.Stat_t
3231
if !ok {
33-
return errors.New("unsupported stat type")
32+
return fmt.Errorf("unsupported stat type: %s: %v", dst, fi.Mode())
33+
}
34+
var rDev int
35+
if fi.Mode()&os.ModeDevice == os.ModeDevice {
36+
rDev = int(st.Rdev)
3437
}
35-
return unix.Mknod(dst, uint32(fi.Mode()), int(st.Rdev))
38+
//nolint:unconvert
39+
return syscall.Mknod(dst, uint32(st.Mode), rDev)
3640
}

fs/copy_linux.go

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@
1717
package fs
1818

1919
import (
20-
"errors"
2120
"fmt"
2221
"io"
2322
"os"
@@ -144,11 +143,3 @@ func copyXAttrs(dst, src string, excludes map[string]struct{}, errorHandler XAtt
144143

145144
return nil
146145
}
147-
148-
func copyDevice(dst string, fi os.FileInfo) error {
149-
st, ok := fi.Sys().(*syscall.Stat_t)
150-
if !ok {
151-
return errors.New("unsupported stat type")
152-
}
153-
return unix.Mknod(dst, uint32(fi.Mode()), int(st.Rdev))
154-
}

fs/copy_unix_test.go

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,10 +22,13 @@ package fs
2222
import (
2323
"io/ioutil"
2424
"os"
25+
"path/filepath"
26+
"syscall"
2527
"testing"
2628

2729
"github.com/containerd/continuity/fs/fstest"
2830
"github.com/containerd/continuity/sysx"
31+
"golang.org/x/sys/unix"
2932
)
3033

3134
func assertXAttr(t *testing.T, dir, xattr, xval string, xerr error) {
@@ -86,3 +89,88 @@ func TestCopyDirWithXAttrExcludes(t *testing.T) {
8689
assertXAttr(t, dst, "user.test-x", "", sysx.ENODATA)
8790
})
8891
}
92+
93+
func TestCopyIrregular(t *testing.T) {
94+
var prepared int
95+
prepareSrc := func(src string) {
96+
f0Pipe := filepath.Join(src, "f0.pipe")
97+
if err := unix.Mkfifo(f0Pipe, 0600); err != nil {
98+
t.Fatal(err)
99+
}
100+
prepared++
101+
f1Normal := filepath.Join(src, "f1.normal")
102+
if err := ioutil.WriteFile(f1Normal, []byte("content of f1.normal"), 0600); err != nil {
103+
t.Fatal(err)
104+
}
105+
prepared++
106+
f2Socket := filepath.Join(src, "f2.sock")
107+
if err := unix.Mknod(f2Socket, 0600|unix.S_IFSOCK, 0); err != nil {
108+
t.Fatal(err)
109+
}
110+
prepared++
111+
f3Dev := filepath.Join(src, "f3.dev")
112+
if err := unix.Mknod(f3Dev, 0600|unix.S_IFCHR, 42); err != nil {
113+
t.Logf("skipping testing S_IFCHR: %v", err)
114+
} else {
115+
prepared++
116+
}
117+
}
118+
119+
verifyDst := func(dst string) {
120+
entries, err := ioutil.ReadDir(dst)
121+
if err != nil {
122+
t.Fatal(err)
123+
}
124+
var verified int
125+
for _, f := range entries {
126+
name := f.Name()
127+
full := filepath.Join(dst, name)
128+
fi, err := os.Stat(full)
129+
if err != nil {
130+
t.Fatal(err)
131+
}
132+
mode := fi.Mode()
133+
switch name {
134+
case "f0.pipe":
135+
if mode&os.ModeNamedPipe != os.ModeNamedPipe {
136+
t.Fatalf("unexpected mode of %s: %v", name, mode)
137+
}
138+
case "f1.normal":
139+
b, err := ioutil.ReadFile(full)
140+
if err != nil {
141+
t.Fatal(err)
142+
}
143+
if string(b) != "content of f1.normal" {
144+
t.Fatalf("unexpected content of %s: %q", name, string(b))
145+
}
146+
case "f2.sock":
147+
if mode&os.ModeSocket != os.ModeSocket {
148+
t.Fatalf("unexpected mode of %s: %v", name, mode)
149+
}
150+
case "f3.dev":
151+
if mode&os.ModeDevice != os.ModeDevice {
152+
t.Fatalf("unexpected mode of %s: %v", name, mode)
153+
}
154+
sys, ok := fi.Sys().(*syscall.Stat_t)
155+
if !ok {
156+
t.Fatalf("unexpected type: %v", fi.Sys())
157+
}
158+
if sys.Rdev != 42 {
159+
t.Fatalf("unexpected rdev of %s: %d", name, sys.Rdev)
160+
}
161+
}
162+
verified++
163+
}
164+
if verified != prepared {
165+
t.Fatalf("prepared %d files, verified %d files", prepared, verified)
166+
}
167+
}
168+
169+
src := t.TempDir()
170+
dst := t.TempDir()
171+
prepareSrc(src)
172+
if err := CopyDir(dst, src); err != nil {
173+
t.Fatal(err)
174+
}
175+
verifyDst(dst)
176+
}

fs/copy_windows.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,6 @@ func copyXAttrs(dst, src string, excludes map[string]struct{}, errorHandler XAtt
8585
return nil
8686
}
8787

88-
func copyDevice(dst string, fi os.FileInfo) error {
89-
return errors.New("device copy not supported")
88+
func copyIrregular(dst string, fi os.FileInfo) error {
89+
return errors.New("irregular copy not supported")
9090
}

0 commit comments

Comments
 (0)