diff --git a/.ci/test-building-binaries-for-supported-os.sh b/.ci/test-building-binaries-for-supported-os.sh index cf72faa..104ece3 100755 --- a/.ci/test-building-binaries-for-supported-os.sh +++ b/.ci/test-building-binaries-for-supported-os.sh @@ -6,6 +6,7 @@ os_archs=( darwin/amd64 freebsd/amd64 linux/amd64 + plan9/386 solaris/amd64 windows/amd64 ) @@ -18,4 +19,4 @@ do CGO_ENABLED=0 GOOS=${goos} GOARCH=${goarch} go build -o /dev/null ./... done -echo "Succeeded building binaries for all supported OS/ARCH pairs!" \ No newline at end of file +echo "Succeeded building binaries for all supported OS/ARCH pairs!" diff --git a/go.mod b/go.mod index 6025d18..0d51708 100644 --- a/go.mod +++ b/go.mod @@ -5,3 +5,5 @@ require ( golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 ) + +go 1.13 diff --git a/osfs/os.go b/osfs/os.go index ff35a3b..e9d6d00 100644 --- a/osfs/os.go +++ b/osfs/os.go @@ -72,7 +72,7 @@ func (fs *OS) Rename(from, to string) error { return err } - return os.Rename(from, to) + return rename(from, to) } func (fs *OS) MkdirAll(path string, perm os.FileMode) error { diff --git a/osfs/os_plan9.go b/osfs/os_plan9.go new file mode 100644 index 0000000..fe1eb85 --- /dev/null +++ b/osfs/os_plan9.go @@ -0,0 +1,83 @@ +package osfs + +import ( + "io" + "os" + "path/filepath" + "syscall" +) + +func (f *file) Lock() error { + // Plan 9 uses a mode bit instead of explicit lock/unlock syscalls. + // + // Per http://man.cat-v.org/plan_9/5/stat: “Exclusive use files may be open + // for I/O by only one fid at a time across all clients of the server. If a + // second open is attempted, it draws an error.” + // + // There is no obvious way to implement this function using the exclusive use bit. + // See https://golang.org/src/cmd/go/internal/lockedfile/lockedfile_plan9.go + // for how file locking is done by the go tool on Plan 9. + return nil +} + +func (f *file) Unlock() error { + return nil +} + +func rename(from, to string) error { + // If from and to are in different directories, copy the file + // since Plan 9 does not support cross-directory rename. + if filepath.Dir(from) != filepath.Dir(to) { + fi, err := os.Stat(from) + if err != nil { + return &os.LinkError{"rename", from, to, err} + } + if fi.Mode().IsDir() { + return &os.LinkError{"rename", from, to, syscall.EISDIR} + } + fromFile, err := os.Open(from) + if err != nil { + return &os.LinkError{"rename", from, to, err} + } + toFile, err := os.OpenFile(to, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, fi.Mode()) + if err != nil { + return &os.LinkError{"rename", from, to, err} + } + _, err = io.Copy(toFile, fromFile) + if err != nil { + return &os.LinkError{"rename", from, to, err} + } + + // Copy mtime and mode from original file. + // We need only one syscall if we avoid os.Chmod and os.Chtimes. + dir := fi.Sys().(*syscall.Dir) + var d syscall.Dir + d.Null() + d.Mtime = dir.Mtime + d.Mode = dir.Mode + if err = dirwstat(to, &d); err != nil { + return &os.LinkError{"rename", from, to, err} + } + + // Remove original file. + err = os.Remove(from) + if err != nil { + return &os.LinkError{"rename", from, to, err} + } + return nil + } + return os.Rename(from, to) +} + +func dirwstat(name string, d *syscall.Dir) error { + var buf [syscall.STATFIXLEN]byte + + n, err := d.Marshal(buf[:]) + if err != nil { + return &os.PathError{"dirwstat", name, err} + } + if err = syscall.Wstat(name, buf[:n]); err != nil { + return &os.PathError{"dirwstat", name, err} + } + return nil +} diff --git a/osfs/os_posix.go b/osfs/os_posix.go index 144cde1..7645dd5 100644 --- a/osfs/os_posix.go +++ b/osfs/os_posix.go @@ -1,8 +1,10 @@ -// +build !windows +// +build !plan9,!windows package osfs import ( + "os" + "golang.org/x/sys/unix" ) @@ -19,3 +21,7 @@ func (f *file) Unlock() error { return unix.Flock(int(f.File.Fd()), unix.LOCK_UN) } + +func rename(from, to string) error { + return os.Rename(from, to) +} diff --git a/osfs/os_test.go b/osfs/os_test.go index 3a0258b..69b4780 100644 --- a/osfs/os_test.go +++ b/osfs/os_test.go @@ -4,6 +4,7 @@ import ( "io/ioutil" "os" "path/filepath" + "runtime" "testing" "gopkg.in/src-d/go-billy.v4" @@ -23,6 +24,14 @@ var _ = Suite(&OSSuite{}) func (s *OSSuite) SetUpTest(c *C) { s.path, _ = ioutil.TempDir(os.TempDir(), "go-billy-osfs-test") + if runtime.GOOS == "plan9" { + // On Plan 9, permission mode of newly created files + // or directories are based on the permission mode of + // the containing directory (see http://man.cat-v.org/plan_9/5/open). + // Since TestOpenFileWithModes and TestStat creates files directly + // in the temporary directory, we need to make it more permissive. + c.Assert(os.Chmod(s.path, 0777), IsNil) + } s.FilesystemSuite = test.NewFilesystemSuite(New(s.path)) } diff --git a/osfs/os_windows.go b/osfs/os_windows.go index 5eb9882..8f5caeb 100644 --- a/osfs/os_windows.go +++ b/osfs/os_windows.go @@ -55,3 +55,7 @@ func (f *file) Unlock() error { } return nil } + +func rename(from, to string) error { + return os.Rename(from, to) +} diff --git a/test/fs.go b/test/fs.go index 0723191..4157983 100644 --- a/test/fs.go +++ b/test/fs.go @@ -2,6 +2,7 @@ package test import ( "os" + "runtime" . "gopkg.in/check.v1" . "gopkg.in/src-d/go-billy.v4" @@ -33,6 +34,9 @@ func NewFilesystemSuite(fs Filesystem) FilesystemSuite { } func (s *FilesystemSuite) TestSymlinkToDir(c *C) { + if runtime.GOOS == "plan9" { + c.Skip("skipping on Plan 9; symlinks are not supported") + } err := s.FS.MkdirAll("dir", 0755) c.Assert(err, IsNil) @@ -46,6 +50,9 @@ func (s *FilesystemSuite) TestSymlinkToDir(c *C) { } func (s *FilesystemSuite) TestSymlinkReadDir(c *C) { + if runtime.GOOS == "plan9" { + c.Skip("skipping on Plan 9; symlinks are not supported") + } err := util.WriteFile(s.FS, "dir/file", []byte("foo"), 0644) c.Assert(err, IsNil) @@ -85,6 +92,9 @@ func (s *ChrootSuite) TestReadDirWithChroot(c *C) { } func (s *FilesystemSuite) TestSymlinkWithChrootBasic(c *C) { + if runtime.GOOS == "plan9" { + c.Skip("skipping on Plan 9; symlinks are not supported") + } qux, _ := s.FS.Chroot("/qux") err := util.WriteFile(qux, "file", nil, 0644) @@ -103,6 +113,9 @@ func (s *FilesystemSuite) TestSymlinkWithChrootBasic(c *C) { } func (s *FilesystemSuite) TestSymlinkWithChrootCrossBounders(c *C) { + if runtime.GOOS == "plan9" { + c.Skip("skipping on Plan 9; symlinks are not supported") + } qux, _ := s.FS.Chroot("/qux") util.WriteFile(s.FS, "file", []byte("foo"), customMode) @@ -115,6 +128,9 @@ func (s *FilesystemSuite) TestSymlinkWithChrootCrossBounders(c *C) { } func (s *FilesystemSuite) TestReadDirWithLink(c *C) { + if runtime.GOOS == "plan9" { + c.Skip("skipping on Plan 9; symlinks are not supported") + } util.WriteFile(s.FS, "foo/bar", []byte("foo"), customMode) s.FS.Symlink("bar", "foo/qux") diff --git a/test/symlink.go b/test/symlink.go index 7cfafac..1ae77ab 100644 --- a/test/symlink.go +++ b/test/symlink.go @@ -3,6 +3,7 @@ package test import ( "io/ioutil" "os" + "runtime" . "gopkg.in/check.v1" . "gopkg.in/src-d/go-billy.v4" @@ -19,6 +20,9 @@ type SymlinkSuite struct { } func (s *SymlinkSuite) TestSymlink(c *C) { + if runtime.GOOS == "plan9" { + c.Skip("skipping on Plan 9; symlinks are not supported") + } err := util.WriteFile(s.FS, "file", nil, 0644) c.Assert(err, IsNil) @@ -31,6 +35,9 @@ func (s *SymlinkSuite) TestSymlink(c *C) { } func (s *SymlinkSuite) TestSymlinkCrossDirs(c *C) { + if runtime.GOOS == "plan9" { + c.Skip("skipping on Plan 9; symlinks are not supported") + } err := util.WriteFile(s.FS, "foo/file", nil, 0644) c.Assert(err, IsNil) @@ -43,6 +50,9 @@ func (s *SymlinkSuite) TestSymlinkCrossDirs(c *C) { } func (s *SymlinkSuite) TestSymlinkNested(c *C) { + if runtime.GOOS == "plan9" { + c.Skip("skipping on Plan 9; symlinks are not supported") + } err := util.WriteFile(s.FS, "file", []byte("hello world!"), 0644) c.Assert(err, IsNil) @@ -59,6 +69,9 @@ func (s *SymlinkSuite) TestSymlinkNested(c *C) { } func (s *SymlinkSuite) TestSymlinkWithNonExistentdTarget(c *C) { + if runtime.GOOS == "plan9" { + c.Skip("skipping on Plan 9; symlinks are not supported") + } err := s.FS.Symlink("file", "link") c.Assert(err, IsNil) @@ -67,6 +80,9 @@ func (s *SymlinkSuite) TestSymlinkWithNonExistentdTarget(c *C) { } func (s *SymlinkSuite) TestSymlinkWithExistingLink(c *C) { + if runtime.GOOS == "plan9" { + c.Skip("skipping on Plan 9; symlinks are not supported") + } err := util.WriteFile(s.FS, "link", nil, 0644) c.Assert(err, IsNil) @@ -75,6 +91,9 @@ func (s *SymlinkSuite) TestSymlinkWithExistingLink(c *C) { } func (s *SymlinkSuite) TestOpenWithSymlinkToRelativePath(c *C) { + if runtime.GOOS == "plan9" { + c.Skip("skipping on Plan 9; symlinks are not supported") + } err := util.WriteFile(s.FS, "dir/file", []byte("foo"), 0644) c.Assert(err, IsNil) @@ -91,6 +110,9 @@ func (s *SymlinkSuite) TestOpenWithSymlinkToRelativePath(c *C) { } func (s *SymlinkSuite) TestOpenWithSymlinkToAbsolutePath(c *C) { + if runtime.GOOS == "plan9" { + c.Skip("skipping on Plan 9; symlinks are not supported") + } err := util.WriteFile(s.FS, "dir/file", []byte("foo"), 0644) c.Assert(err, IsNil) @@ -107,6 +129,9 @@ func (s *SymlinkSuite) TestOpenWithSymlinkToAbsolutePath(c *C) { } func (s *SymlinkSuite) TestReadlink(c *C) { + if runtime.GOOS == "plan9" { + c.Skip("skipping on Plan 9; symlinks are not supported") + } err := util.WriteFile(s.FS, "file", nil, 0644) c.Assert(err, IsNil) @@ -115,6 +140,9 @@ func (s *SymlinkSuite) TestReadlink(c *C) { } func (s *SymlinkSuite) TestReadlinkWithRelativePath(c *C) { + if runtime.GOOS == "plan9" { + c.Skip("skipping on Plan 9; symlinks are not supported") + } err := util.WriteFile(s.FS, "dir/file", nil, 0644) c.Assert(err, IsNil) @@ -127,6 +155,9 @@ func (s *SymlinkSuite) TestReadlinkWithRelativePath(c *C) { } func (s *SymlinkSuite) TestReadlinkWithAbsolutePath(c *C) { + if runtime.GOOS == "plan9" { + c.Skip("skipping on Plan 9; symlinks are not supported") + } err := util.WriteFile(s.FS, "dir/file", nil, 0644) c.Assert(err, IsNil) @@ -139,6 +170,9 @@ func (s *SymlinkSuite) TestReadlinkWithAbsolutePath(c *C) { } func (s *SymlinkSuite) TestReadlinkWithNonExistentTarget(c *C) { + if runtime.GOOS == "plan9" { + c.Skip("skipping on Plan 9; symlinks are not supported") + } err := s.FS.Symlink("file", "link") c.Assert(err, IsNil) @@ -148,11 +182,17 @@ func (s *SymlinkSuite) TestReadlinkWithNonExistentTarget(c *C) { } func (s *SymlinkSuite) TestReadlinkWithNonExistentLink(c *C) { + if runtime.GOOS == "plan9" { + c.Skip("skipping on Plan 9; symlinks are not supported") + } _, err := s.FS.Readlink("link") c.Assert(os.IsNotExist(err), Equals, true) } func (s *SymlinkSuite) TestStatLink(c *C) { + if runtime.GOOS == "plan9" { + c.Skip("skipping on Plan 9; symlinks are not supported") + } util.WriteFile(s.FS, "foo/bar", []byte("foo"), customMode) s.FS.Symlink("bar", "foo/qux") @@ -178,6 +218,9 @@ func (s *SymlinkSuite) TestLstat(c *C) { } func (s *SymlinkSuite) TestLstatLink(c *C) { + if runtime.GOOS == "plan9" { + c.Skip("skipping on Plan 9; symlinks are not supported") + } util.WriteFile(s.FS, "foo/bar", []byte("fosddddaaao"), customMode) s.FS.Symlink("bar", "foo/qux") @@ -190,6 +233,9 @@ func (s *SymlinkSuite) TestLstatLink(c *C) { } func (s *SymlinkSuite) TestRenameWithSymlink(c *C) { + if runtime.GOOS == "plan9" { + c.Skip("skipping on Plan 9; symlinks are not supported") + } err := s.FS.Symlink("file", "link") c.Assert(err, IsNil) @@ -201,6 +247,9 @@ func (s *SymlinkSuite) TestRenameWithSymlink(c *C) { } func (s *SymlinkSuite) TestRemoveWithSymlink(c *C) { + if runtime.GOOS == "plan9" { + c.Skip("skipping on Plan 9; symlinks are not supported") + } err := util.WriteFile(s.FS, "file", []byte("foo"), 0644) c.Assert(err, IsNil)