Skip to content

Commit

Permalink
Add Plan 9 support
Browse files Browse the repository at this point in the history
Fix build on Plan 9 and make all tests pass.

Note: running `go test ./...` may hang in `TestTempFileMany` or
`TestTempFileManyWithUtil`. I'm not sure if it's a go tool issue,
standard library issue, or something wrong with the tests.  All tests
pass if tests are run with `go test -count 1 ./...`.

Fixes #52

Signed-off-by: Fazlul Shahriar <[email protected]>
  • Loading branch information
fhs committed Dec 12, 2019
1 parent fd409ff commit 3d275f0
Show file tree
Hide file tree
Showing 9 changed files with 173 additions and 3 deletions.
3 changes: 2 additions & 1 deletion .ci/test-building-binaries-for-supported-os.sh
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ os_archs=(
linux/amd64
solaris/amd64
windows/amd64
plan9/386
)

for os_arch in "${os_archs[@]}"
Expand All @@ -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!"
echo "Succeeded building binaries for all supported OS/ARCH pairs!"
2 changes: 2 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -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
2 changes: 1 addition & 1 deletion osfs/os.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
83 changes: 83 additions & 0 deletions osfs/os_plan9.go
Original file line number Diff line number Diff line change
@@ -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
}
8 changes: 7 additions & 1 deletion osfs/os_posix.go
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
// +build !windows
// +build !plan9,!windows

package osfs

import (
"os"

"golang.org/x/sys/unix"
)

Expand All @@ -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)
}
9 changes: 9 additions & 0 deletions osfs/os_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"io/ioutil"
"os"
"path/filepath"
"runtime"
"testing"

"gopkg.in/src-d/go-billy.v4"
Expand All @@ -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))
}

Expand Down
4 changes: 4 additions & 0 deletions osfs/os_windows.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,3 +55,7 @@ func (f *file) Unlock() error {
}
return nil
}

func rename(from, to string) error {
return os.Rename(from, to)
}
16 changes: 16 additions & 0 deletions test/fs.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package test

import (
"os"
"runtime"

. "gopkg.in/check.v1"
. "gopkg.in/src-d/go-billy.v4"
Expand Down Expand Up @@ -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)

Expand All @@ -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)

Expand Down Expand Up @@ -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)
Expand All @@ -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)

Expand All @@ -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")

Expand Down
49 changes: 49 additions & 0 deletions test/symlink.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package test
import (
"io/ioutil"
"os"
"runtime"

. "gopkg.in/check.v1"
. "gopkg.in/src-d/go-billy.v4"
Expand All @@ -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)

Expand All @@ -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)

Expand All @@ -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)

Expand All @@ -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)

Expand All @@ -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)

Expand All @@ -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)

Expand All @@ -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)

Expand All @@ -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)

Expand All @@ -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)

Expand All @@ -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)

Expand All @@ -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)

Expand All @@ -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")

Expand All @@ -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")

Expand All @@ -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)

Expand All @@ -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)

Expand Down

0 comments on commit 3d275f0

Please sign in to comment.