Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
cmd/ninepd/ninepd

.DS_Store
*.orig
c.out
Expand Down
24 changes: 15 additions & 9 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -1,16 +1,22 @@
FROM golang:1.12.7-alpine
FROM golang:1.12-alpine as builder

WORKDIR /go/src/ninep
COPY . .

ENV GO111MODULE=on
RUN set -eux; apk add --no-cache \
git \
gcc \
libc-dev \
;
RUN set -eux; apk add --no-cache --virtual .build-deps \
git; \
CGO_ENABLED=0 GOOS=linux go install -a -v \
-installsuffix cgo \
-ldflags '-extldflags "-static"' ./...; \
apk del .build-deps

RUN go get -d ./...
FROM alpine:3.10

ENTRYPOINT ["go"]
CMD ["test", "./..."]
COPY --from=builder /go/bin/ninepd /bin/ninepd

EXPOSE 5640
VOLUME /export

ENTRYPOINT ["/bin/ninepd"]
CMD ["-addr", ":5640", "-export", "/export"]
20 changes: 19 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
@@ -1,5 +1,23 @@
REPO=marraison
SERVICE=ninepd
VERSION=0.0.1

help:
@echo "Usage: make COMMAND\n\nThe commands are:\n"
@echo " build-image - build ninepd daemon docker image"
@echo " push-image - push ninepd image to dockerhub"
@echo " test - test package"
@echo ""

test: build-test-image
@docker run ninep-unittest test -tags=compat -v ./...

build-test-image:
@docker build --rm --tag=ninep-unittest .
@docker build --rm --tag=ninep-unittest \
--file scripts/Dockerfile.unittest .

build-image:
@docker build --rm --tag=$(REPO)/$(SERVICE):$(VERSION) .

push-image: build-image
@docker push $(REPO)/$(SERVICE):$(VERSION)
92 changes: 92 additions & 0 deletions cmd/ninepd/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
package main

import (
"context"
"flag"
"io"
"net"
"os"
"os/signal"
"sync"

ninep "github.com/azmodb/ninep"
"github.com/azmodb/ninep/posix"
"github.com/azmodb/pkg/log"
"golang.org/x/sys/unix"
)

// Option configures how we set up a 9P2000.L file server before it starts.
type Option struct {
Export string
user string

//Network string
Address string
}

func newOptionFlag() *Option {
o := &Option{}
flag.StringVar(&o.Export, "export", "/tmp", "export path (comm-seperated)")
flag.StringVar(&o.Address, "addr", "0.0.0.0:5640", "bind to host address")
flag.StringVar(&o.user, "user", "", "run-as user")
return o
}

func (o *Option) UID() int { return 0 }
func (o *Option) GID() int { return 0 }

func main() {
opt := newOptionFlag()
flag.Parse()

fs, err := posix.Open(opt.Export, opt.UID(), opt.GID())
if err != nil {
log.Fatalf("cannot open local filesystem: %v", err)
}
defer fs.Close()

ctx := context.Background()
lc := &net.ListenConfig{}
listener, err := lc.Listen(ctx, "tcp", opt.Address)
if err != nil {
log.Fatalf("cannot announce network: %v", err)
}

c := &onceCloser{Closer: listener}
s := ninep.NewServer(fs)
Loop:
for {
select {
case err = <-listen(s, listener):
c.Close()
break Loop
case <-shutdown():
c.Close()
}
}

log.Debugf("listener received error: %v", err)
log.Infof("9P2000.L server is shut down")
}

func shutdown() <-chan os.Signal {
ch := make(chan os.Signal)
signal.Notify(ch, unix.SIGTERM, unix.SIGINT, unix.SIGKILL)
return ch
}

type onceCloser struct {
sync.Once
io.Closer
}

func (c *onceCloser) Close() (err error) {
c.Once.Do(func() { err = c.Close() })
return err
}

func listen(s *ninep.Server, listener net.Listener) <-chan error {
ch := make(chan error, 1)
go func() { ch <- s.Listen(listener) }()
return ch
}
72 changes: 71 additions & 1 deletion posix/fid.go
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
package posix

import (
"io"
"os"

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

// Attach introduces a new user to the file system, and establishes a fid
// as the root for that user on the file tree selected by export. If uid
// is not set to proto.NoUid (~0), is the uid of the user and is used in
// is not set to no uid (~0), is the uid of the user and is used in
// preference to username.
func Attach(fs FileSystem, file File, path, username string, uid int, opts ...Option) (*Fid, error) {
if _, err := fs.Stat(path); err != nil {
Expand Down Expand Up @@ -54,6 +55,24 @@ func (f *Fid) isOpened() bool { return f.file != nil }
// return nil
//}

// Walk is used to descend a directory represented by fid using
// successive path elements provided in the names slice, calling fn for
// each file or directory in the tree.
func (f *Fid) Walk(names []string, fn func(*Stat) error) (*Fid, error) {
path := f.path
for _, name := range names {
path = join(path, name)
stat, err := f.fs.Stat(path)
if err != nil {
return nil, err
}
if err = fn(stat); err != nil {
return nil, err
}
}
return newFid(f.fs, path, f.uid, f.gid)
}

// Create creates a regular file name in directory represented by fid
// and prepares it for I/O. After the call fid represents the new file.
//
Expand Down Expand Up @@ -109,6 +128,9 @@ func (f *Fid) Remove() error {
// Stat returns a Stat describing the named file.
func (f *Fid) Stat() (*Stat, error) { return f.fs.Stat(f.path) }

// StatFS returns file system statistics.
func (f *Fid) StatFS() (*StatFS, error) { return f.fs.StatFS(f.path) }

// Close closes the fid, rendering it unusable for I/O.
func (f *Fid) Close() error {
if !f.isOpened() {
Expand All @@ -119,3 +141,51 @@ func (f *Fid) Close() error {
f.file = nil
return err
}

// ReadAt reads len(b) bytes from the fid starting at byte offset. It
// returns the number of bytes read and the error, if any.
func (f *Fid) ReadAt(p []byte, offset int64) (int, error) {
if !f.isOpened() {
return 0, unix.EBADF
}
return f.file.ReadAt(p, offset)
}

// WriteAt writes len(b) bytes to the fid starting at byte offset. It
// returns the number of bytes written and an error, if any.
func (f *Fid) WriteAt(p []byte, offset int64) (int, error) {
if !f.isOpened() {
return 0, unix.EBADF
}
return f.file.WriteAt(p, offset)
}

// ReadDir returns directory entries from the directory represented by
// fid. Offset is the offset returned in the last directory entry of
// the previous call.
func (f *Fid) ReadDir(p []byte, offset int64) (n int, err error) {
if !f.isOpened() {
return 0, unix.EBADF
}

records, err := f.file.ReadDir() /// TODO(mason): cache
if err != nil {
return 0, err
}
if offset >= int64(len(records)) {
return 0, io.EOF
}

for _, rec := range records[offset:] {
if len(p[n:]) < rec.Len() {
break
}

m, err := rec.MarshalTo(p[n:])
n += m
if err != nil {
break
}
}
return n, err
}
112 changes: 112 additions & 0 deletions posix/fid_test.go
Original file line number Diff line number Diff line change
@@ -1,11 +1,78 @@
package posix

import (
"io"
"os"
"path/filepath"
"testing"

"github.com/azmodb/pkg/log"
"golang.org/x/sys/unix"
)

func testMalformedWalkPath(t *testing.T, f *Fid, names []string) {
t.Helper()

if _, err := f.Walk(names, func(st *Stat) error {
return nil
}); err == nil {
log.Fatalf("walk: unexpected <nil> error, %v", err)
}
}

func openWalkRoot(t *testing.T, dir string) *Fid {
t.Helper()

fs, err := newPosixFS(dir, -1, -1)
if err != nil {
log.Panicf("walk: cannot init posix filesystem: %v", err)
}
defer fs.Close()

f, err := newFid(fs, "/", -1, -1)
if err != nil {
log.Fatalf("walk: cannot init new fid: %v", err)
}
return f
}

func TestMalformedWalkPath(t *testing.T) {
dir := filepath.Join("testdata", "walk")
root := openWalkRoot(t, dir)
defer root.Close()

testMalformedWalkPath(t, root, []string{"a", "b", "c", "X"})
testMalformedWalkPath(t, root, []string{"a", "b", "X"})
testMalformedWalkPath(t, root, []string{"a", "X"})
testMalformedWalkPath(t, root, []string{"X"})

// []string{".."} // "redirects" to /
// []string{"../.."} // "redirects" to /
}

func TestWalk(t *testing.T) {
dir := filepath.Join("testdata", "walk")
root := openWalkRoot(t, dir)
defer root.Close()

names := []string{"a", "b", "c", "file"}
fid, err := root.Walk(names, func(st *Stat) error {
return nil
})
if err != nil {
log.Fatalf("walk: cannot walk %v: %v", names, err)
}
defer fid.Close()

st, err := fid.Stat()
if err != nil {
log.Fatalf("walk: cannot stat: %v", err)
}
if st.Mode&unix.S_IFDIR != 0 {
log.Fatalf("walk: expected file, got %d", st.Mode)
}
}

func TestFidAttach(t *testing.T) {
uid, gid := getTestUser(t)
f, err := Attach(newTestPosixFS(t), nil, "/", "", uid)
Expand All @@ -24,3 +91,48 @@ func TestFidAttach(t *testing.T) {
t.Fatalf("fid: expected %v, got %v", unix.EBADF, err)
}
}

func TestFidReadDir(t *testing.T) {
want := map[string]bool{
".": true,
"..": true,
"dir1": true,
"file1": true,
"file2": true,
"file3": true,
"file4": true,
}
dir := filepath.Join("testdata", "dirent")
root := openWalkRoot(t, dir)
defer root.Close()

root.Open(os.O_RDONLY)

data := make([]byte, 64)
var records []Record
var offset int64
for {
n, err := root.ReadDir(data, offset)
if err == io.EOF {
break
}
if err != nil {
log.Fatalf("readdir: read failed: %v", err)
}

r, err := UnmarshalRecords(data[:n])
if err != nil {
log.Fatalf("readdir: unmarshal failed: %v", err)
}
offset = int64(r[len(r)-1].Offset)
records = append(records, r...)
}
if len(records) != len(want) {
log.Fatalf("readir: exepcted entries %v, got %v", len(want), len(records))
}
for _, rec := range records {
if _, found := want[rec.Name]; !found {
log.Fatalf("readdir: found unexpected name %q", rec.Name)
}
}
}
Loading