-
Notifications
You must be signed in to change notification settings - Fork 91
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Tests run only on linux and need u9fs (https://github.com/unofficial-mirror/u9fs). Runs with go test -tags u9fs
- Loading branch information
Showing
5 changed files
with
321 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,252 @@ | ||
// Package fs implements io/fs.FS for a 9p filesystem. | ||
// An example is to run a local http.FileServer with a remote 9p system. | ||
package fs | ||
|
||
import ( | ||
"io" | ||
"io/fs" | ||
"strings" | ||
"time" | ||
|
||
"9fans.net/go/plan9" | ||
"9fans.net/go/plan9/client" | ||
) | ||
|
||
// NewFS returns an io/fs.FS filesystem that wraps a 9p filesystem. | ||
func NewFS(fsys *client.Fsys) fs.FS { | ||
return fs9p{fsys: fsys} | ||
} | ||
|
||
// MountWithNames attaches to the 9p filesystem at network!addr with the | ||
// provided user and attach names and wraps an io/fs.FS filesystem around it. | ||
// Assumes the 9p server does not require Auth. | ||
func MountWithNames(network, addr, uname, aname string) (fs.FS, error) { | ||
c, err := client.Dial(network, addr) | ||
if err != nil { | ||
return nil, err | ||
} | ||
fsys, err := c.Attach(nil, uname, aname) | ||
if err != nil { | ||
c.Close() | ||
} | ||
return fs9p{fsys: fsys}, nil | ||
} | ||
|
||
// fs9p implements io/fs.FS. | ||
type fs9p struct { | ||
fsys *client.Fsys | ||
} | ||
|
||
func (f fs9p) Open(name string) (fs.File, error) { | ||
if !fs.ValidPath(name) { | ||
return nil, &fs.PathError{ | ||
Op: "open", | ||
Path: name, | ||
Err: fs.ErrInvalid, | ||
} | ||
} | ||
|
||
fid, err := f.fsys.Open(name, plan9.OREAD) | ||
if err != nil { | ||
// error detection is based on freebsd/u9fs | ||
werr := err | ||
if strings.Contains(werr.Error(), "Permission denied") { | ||
werr = fs.ErrPermission | ||
} else if strings.Contains(werr.Error(), "No such file or directory") { | ||
werr = fs.ErrNotExist | ||
} | ||
return nil, &fs.PathError{ | ||
Op: "open", | ||
Path: name, | ||
Err: werr, | ||
} | ||
} | ||
|
||
if fid.Qid().Type&plan9.QTDIR > 0 { | ||
return &dir9p{file9p: file9p{fid: fid}}, nil | ||
} | ||
return file9p{fid: fid}, nil | ||
} | ||
|
||
// file9p implements io/fs.File. | ||
type file9p struct { | ||
fid *client.Fid | ||
} | ||
|
||
func (f file9p) Stat() (fs.FileInfo, error) { | ||
dir, err := f.fid.Stat() | ||
if err != nil { | ||
return nil, err | ||
} | ||
return fileInfo9p{sys: dir}, nil | ||
|
||
} | ||
|
||
func (f file9p) Read(b []byte) (int, error) { | ||
return f.fid.Read(b) | ||
} | ||
|
||
func (f file9p) Close() error { | ||
return f.fid.Close() | ||
} | ||
|
||
func (f file9p) ReadAt(p []byte, off int64) (n int, err error) { | ||
return f.fid.ReadAt(p, off) | ||
} | ||
|
||
func (f file9p) Seek(offset int64, whence int) (int64, error) { | ||
return f.fid.Seek(offset, whence) | ||
} | ||
|
||
// dir9p implements io/fs.ReadDirFile. | ||
type dir9p struct { | ||
file9p | ||
|
||
dirsRead []*plan9.Dir // directories read ahead by dirread | ||
} | ||
|
||
func (d *dir9p) ReadDir(n int) ([]fs.DirEntry, error) { | ||
var dirs []*plan9.Dir | ||
var err error | ||
|
||
if n <= 0 { | ||
dirs, err = d.fid.Dirreadall() | ||
dirs = append(d.dirsRead, dirs...) // preserve directory order | ||
d.dirsRead = d.dirsRead[:0] | ||
} else { | ||
for err == nil && len(d.dirsRead) < n { | ||
dirs, err = d.fid.Dirread() | ||
d.dirsRead = append(d.dirsRead, dirs...) // preserve directory order | ||
} | ||
if len(d.dirsRead) >= n { | ||
dirs = d.dirsRead[0:n] | ||
d.dirsRead = d.dirsRead[n:] | ||
} else { | ||
dirs = d.dirsRead | ||
d.dirsRead = d.dirsRead[:0] | ||
} | ||
} | ||
|
||
if len(d.dirsRead) > 0 && err == io.EOF { | ||
err = nil | ||
} | ||
|
||
entries := make([]fs.DirEntry, len(dirs)) | ||
for i, dir := range dirs { | ||
entries[i] = dirEntry9p{sys: dir} | ||
} | ||
return entries, err | ||
} | ||
|
||
// fileInfo9p implements io/fs.FileInfo. | ||
type fileInfo9p struct { | ||
sys *plan9.Dir | ||
} | ||
|
||
func (f fileInfo9p) Name() string { | ||
return f.sys.Name | ||
} | ||
|
||
func (f fileInfo9p) Size() int64 { | ||
// for directories size is implementation defined. Use 0 for portability. | ||
if f.sys.Mode&plan9.DMDIR > 0 { | ||
return 0 | ||
} | ||
|
||
// 9p uses uint64 but FileInfo uses int64. For most practical cases, | ||
// the conversion it OK. | ||
return int64(f.sys.Length) | ||
} | ||
|
||
func (f fileInfo9p) Mode() fs.FileMode { | ||
// init mode to the permission bits and then set the others | ||
mode := fs.FileMode(f.sys.Mode & 0777) | ||
if f.sys.Mode&plan9.DMDIR > 0 { | ||
mode |= fs.ModeDir | ||
} | ||
if f.sys.Mode&plan9.DMAPPEND > 0 { | ||
mode |= fs.ModeAppend | ||
} | ||
if f.sys.Mode&plan9.DMEXCL > 0 { | ||
mode |= fs.ModeExclusive | ||
} | ||
if f.sys.Mode&plan9.DMTMP > 0 { | ||
mode |= fs.ModeTemporary | ||
} | ||
|
||
// The following are not defined by 9p (http://9p.io/sys/man/5/INDEX.html) | ||
// but are defined by the plan9 client | ||
if f.sys.Mode&plan9.DMSYMLINK > 0 { | ||
mode |= fs.ModeSymlink | ||
} | ||
if f.sys.Mode&plan9.DMDEVICE > 0 { | ||
mode |= fs.ModeDevice | ||
} | ||
if f.sys.Mode&plan9.DMNAMEDPIPE > 0 { | ||
mode |= fs.ModeNamedPipe | ||
} | ||
if f.sys.Mode&plan9.DMSOCKET > 0 { | ||
mode |= fs.ModeSocket | ||
} | ||
if f.sys.Mode&plan9.DMSETUID > 0 { | ||
mode |= fs.ModeSetuid | ||
} | ||
if f.sys.Mode&plan9.DMSETGID > 0 { | ||
mode |= fs.ModeSetgid | ||
} | ||
|
||
return mode | ||
} | ||
|
||
func (f fileInfo9p) ModTime() time.Time { | ||
return time.Unix(int64(f.sys.Mtime), 0) | ||
} | ||
|
||
func (f fileInfo9p) IsDir() bool { | ||
return f.sys.Mode&plan9.DMDIR > 0 | ||
} | ||
|
||
func (f fileInfo9p) Sys() interface{} { | ||
return f.sys | ||
} | ||
|
||
// dirEntry9p implements io/fs.DirEntry. | ||
type dirEntry9p struct { | ||
sys *plan9.Dir | ||
} | ||
|
||
func (d dirEntry9p) Name() string { | ||
return d.sys.Name | ||
} | ||
|
||
func (d dirEntry9p) IsDir() bool { | ||
return d.sys.Mode&plan9.DMDIR > 0 | ||
} | ||
|
||
func (d dirEntry9p) Info() (fs.FileInfo, error) { | ||
return fileInfo9p{sys: d.sys}, nil | ||
} | ||
|
||
func (d dirEntry9p) Type() fs.FileMode { | ||
var mode fs.FileMode | ||
if d.sys.Mode&plan9.DMDIR > 0 { | ||
mode |= fs.ModeDir | ||
} | ||
|
||
// The following are not defined by 9p (http://9p.io/sys/man/5/INDEX.html) | ||
// but are defined by the plan9 client | ||
if d.sys.Mode&plan9.DMSYMLINK > 0 { | ||
mode |= fs.ModeSymlink | ||
} | ||
if d.sys.Mode&plan9.DMDEVICE > 0 { | ||
mode |= fs.ModeDevice | ||
} | ||
if d.sys.Mode&plan9.DMNAMEDPIPE > 0 { | ||
mode |= fs.ModeNamedPipe | ||
} | ||
if d.sys.Mode&plan9.DMSOCKET > 0 { | ||
mode |= fs.ModeSocket | ||
} | ||
|
||
return mode | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,66 @@ | ||
//go:build u9fs && linux | ||
|
||
package fs | ||
|
||
import ( | ||
"flag" | ||
"os" | ||
"os/exec" | ||
"path" | ||
"strings" | ||
"syscall" | ||
"testing" | ||
"testing/fstest" | ||
|
||
"9fans.net/go/plan9/client" | ||
"golang.org/x/sys/unix" | ||
) | ||
|
||
// tests an io.FS backedup by a 9p server. Needs u9fs https://github.com/unofficial-mirror/u9fs | ||
// Use the root flag to test any folder, for example | ||
// go test -tags u9fs -root /home/user/images -exp 'gopher.png,glenda.png' -timeout 0 | ||
|
||
var root = flag.String("root", "./testdata", "the root tree to check") | ||
var exp = flag.String("exp", "fortunes.txt", "a comma separated list of files expected to find in root tree") | ||
|
||
func TestFS(t *testing.T) { | ||
execPath, err := exec.LookPath("u9fs") | ||
check(t, err, "u9fs is not in PATH") | ||
|
||
// create a socket pair to connect the 9p client with a u9fs serving root | ||
fds, err := unix.Socketpair(syscall.AF_UNIX, syscall.SOCK_STREAM, 0) | ||
check(t, err, "failed to create socket pair") | ||
defer unix.Close(fds[0]) | ||
defer unix.Close(fds[1]) | ||
|
||
user := os.Getenv("USER") | ||
|
||
// first init the server | ||
srv := os.NewFile(uintptr(fds[1]), "srv") | ||
cmd := exec.Cmd{ | ||
Path: execPath, | ||
Args: []string{"u9fs", "-u", user, "-n", "-a", "none", | ||
"-l", path.Join(t.TempDir(), "u9fs.log"), *root}, | ||
Stdin: srv, | ||
Stdout: srv, | ||
} | ||
check(t, cmd.Start(), "failed to start u9fs") | ||
defer cmd.Process.Kill() | ||
|
||
// init the client last because server must be up to read Tversion | ||
cli := os.NewFile(uintptr(fds[0]), "cli") | ||
conn, err := client.NewConn(cli) | ||
check(t, err, "failed to create client") | ||
fsys, err := conn.Attach(nil, user, "") | ||
check(t, err, "failed to attach client") | ||
|
||
// create and check the filesystem | ||
err = fstest.TestFS(NewFS(fsys), strings.Split(*exp, ",")...) | ||
check(t, err, "FS test failed") | ||
} | ||
|
||
func check(t *testing.T, err error, msg string) { | ||
if err != nil { | ||
t.Fatalf("%s: %s", msg, err) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
The whole UNIX operating system whose documentation fits in a chinese computer terminal! |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
A watched terminal never prints. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
You should be automatically redirected to the new page in 0 seconds. |