Skip to content

Commit

Permalink
initial work for fs api forwarding
Browse files Browse the repository at this point in the history
squashed these commit

real syscall.O_XX flag values

abstract away differences in filesystem support best effort. Windows is not working

upgrade action versions to avoid deprecation warnings

update windows stat functions

fix issues with mode on windows impl

more windows testing

remove console log
  • Loading branch information
mlctrez committed Sep 3, 2023
1 parent 37ec9e7 commit 8c4afd5
Show file tree
Hide file tree
Showing 11 changed files with 628 additions and 59 deletions.
4 changes: 2 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,11 @@ jobs:
runs-on: ${{ matrix.os }}
steps:
- name: Install Go
uses: actions/setup-go@v2
uses: actions/setup-go@v4
with:
go-version: ${{ matrix.go-version }}
- name: Checkout code
uses: actions/checkout@v2
uses: actions/checkout@v3
- name: Lint
run: |
go vet .
Expand Down
7 changes: 7 additions & 0 deletions filesys/fd.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
//go:build darwin || linux

package filesys

func FdType(fd int) int {
return fd
}
7 changes: 7 additions & 0 deletions filesys/fd_windows.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package filesys

import "syscall"

func FdType(fd int) syscall.Handle {
return syscall.Handle(fd)
}
293 changes: 293 additions & 0 deletions filesys/handler.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,293 @@
package filesys

import (
"encoding/base64"
"encoding/json"
"log"
"net/http"
"os"
"strings"
"syscall"
)

// FsHandler translates json payload data to and from system calls like syscall.Stat
type FsHandler struct {
debug bool
securityToken string
logger *log.Logger
}

func NewHandler(securityToken string, logger *log.Logger) *FsHandler {
return &FsHandler{
debug: os.Getenv("DEBUG_FS_HANDLER") != "",
securityToken: securityToken,
logger: logger,
}
}

func (fa *FsHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
if r.Header.Get("WBT-Token") != fa.securityToken {
fa.doError("not implemented", "ENOSYS", w)
return
}
switch r.URL.Path {
case "/fs/stat":
fa.handle(&Stat{}, w, r)
case "/fs/fstat":
fa.handle(&Fstat{}, w, r)
case "/fs/open":
fa.handle(&Open{}, w, r)
case "/fs/write":
fa.handle(&Write{}, w, r)
case "/fs/close":
fa.handle(&Close{}, w, r)
case "/fs/rename":
fa.handle(&Rename{}, w, r)
case "/fs/readdir":
fa.handle(&Readdir{}, w, r)
case "/fs/lstat":
fa.handle(&Lstat{}, w, r)
case "/fs/read":
fa.handle(&Read{}, w, r)
case "/fs/mkdir":
fa.handle(&Mkdir{}, w, r)
case "/fs/unlink":
fa.handle(&Unlink{}, w, r)
case "/fs/rmdir":
fa.handle(&Rmdir{}, w, r)
default:
fa.doError("not implemented", "ENOSYS", w)
}
}

type Responder interface {
WriteResponse(fa *FsHandler, w http.ResponseWriter)
}

func (fa *FsHandler) handle(responder Responder, w http.ResponseWriter, r *http.Request) {
if err := json.NewDecoder(r.Body).Decode(responder); err != nil {
if fa.debug {
fa.logger.Printf("ERROR handle : %s\n", err)
}
w.WriteHeader(http.StatusBadRequest)
return
}
if fa.debug {
fa.logger.Printf("handle %s %+v\n", r.URL.Path, responder)
}
responder.WriteResponse(fa, w)
}

type ErrorCode struct {
Error string `json:"error"`
Code string `json:"code"`
}

func (fa *FsHandler) doError(msg, code string, w http.ResponseWriter) {
if fa.debug {
fa.logger.Printf("doError %s : %s\n", msg, code)
}
e := &ErrorCode{Error: msg, Code: code}

w.WriteHeader(http.StatusBadRequest)
w.Header().Set("Content-Type", "application/json")
if err := json.NewEncoder(w).Encode(e); err != nil {
fa.logger.Println("doError", err)
}
}

func (fa *FsHandler) okResponse(data any, w http.ResponseWriter) {
if marshal, err := json.Marshal(data); err != nil {
fa.logger.Println("okResponse :", err)
w.WriteHeader(http.StatusInternalServerError)

} else {
if fa.debug {
fa.logger.Printf("okResponse %s\n", string(marshal))
}

w.WriteHeader(http.StatusOK)
w.Header().Set("Content-Type", "application/json")
_, _ = w.Write(marshal)
}
}

func fixPath(path string) string {
return strings.TrimPrefix(path, "/fs/")
}

type Stat struct {
Path string `json:"path,omitempty"`
}

type Open struct {
Path string `json:"path"`
Flags int `json:"flags"`
Mode uint32 `json:"mode"`
}

func (o *Open) WriteResponse(fa *FsHandler, w http.ResponseWriter) {
fd, err := syscall.Open(fixPath(o.Path), o.Flags, o.Mode)
if err != nil {
fa.doError(err.Error(), "ENOSYS", w)
return
}
response := map[string]any{"fd": fd}
fa.okResponse(response, w)
}

type Fstat struct {
Fd int `json:"fd"`
}

type Write struct {
Fd int `json:"fd"`
Buffer string `json:"buffer"`
Offset int `json:"offset"`
Length int `json:"length"`
Position *int `json:"position,omitempty"`
}

func (wr *Write) WriteResponse(fa *FsHandler, w http.ResponseWriter) {

if wr.Position != nil || wr.Offset != 0 {
fa.doError("not implemented", "ENOSYS", w)
return
}

bytes, err := base64.StdEncoding.DecodeString(wr.Buffer)
if err != nil {
fa.doError("not implemented", "ENOSYS", w)
return
}

var written int
written, err = syscall.Write(FdType(wr.Fd), bytes)
if err != nil {
fa.doError("not implemented", "ENOSYS", w)
return
}

fa.okResponse(map[string]any{"written": written}, w)
}

type Close struct {
Fd int `json:"fd"`
}

func (c *Close) WriteResponse(fa *FsHandler, w http.ResponseWriter) {
err := syscall.Close(FdType(c.Fd))
if err != nil {
fa.doError("not implemented", "ENOSYS", w)
return
}
fa.okResponse(map[string]any{}, w)
}

type Rename struct {
From string `json:"from"`
To string `json:"to"`
}

func (r *Rename) WriteResponse(fa *FsHandler, w http.ResponseWriter) {
err := syscall.Rename(fixPath(r.From), fixPath(r.To))
if err != nil {
fa.doError("not implemented", "ENOSYS", w)
}
fa.okResponse(map[string]any{}, w)
}

type Readdir struct {
Path string `json:"path"`
}

func (r *Readdir) WriteResponse(fa *FsHandler, w http.ResponseWriter) {
entries, err := os.ReadDir(fixPath(r.Path))
if err != nil {
fa.doError("not implemented", "ENOSYS", w)
return
}
stringNames := make([]string, 0)
for _, entry := range entries {
stringNames = append(stringNames, entry.Name())
}
fa.okResponse(map[string]any{"entries": stringNames}, w)
}

type Lstat struct {
Path string `json:"path"`
}

type Read struct {
Fd int `json:"fd"`
Offset int `json:"offset"`
Length int `json:"length"`
Position *int `json:"position,omitempty"`
}

func (r *Read) WriteResponse(fa *FsHandler, w http.ResponseWriter) {
if r.Offset != 0 {
fa.doError("not implemented", "ENOSYS", w)
return
}
if r.Position != nil {
_, err := syscall.Seek(FdType(r.Fd), int64(*r.Position), 0)
if err != nil {
fa.doError("not implemented", "ENOSYS", w)
return
}
}

buffer := make([]byte, r.Length)
read, err := syscall.Read(FdType(r.Fd), buffer)
if err != nil {
fa.doError("not implemented", "ENOSYS", w)
return
}
response := map[string]any{
"read": read,
"buffer": base64.StdEncoding.EncodeToString(buffer[:read]),
}
fa.okResponse(response, w)

}

type Mkdir struct {
Path string `json:"path"`
Perm uint32 `json:"perm"`
}

func (m *Mkdir) WriteResponse(fa *FsHandler, w http.ResponseWriter) {
err := syscall.Mkdir(fixPath(m.Path), m.Perm)
if err != nil {
fa.doError("not implemented", "ENOSYS", w)
return
}
fa.okResponse(map[string]any{}, w)
}

type Unlink struct {
Path string `json:"path"`
}

func (u *Unlink) WriteResponse(fa *FsHandler, w http.ResponseWriter) {
err := syscall.Unlink(fixPath(u.Path))
if err != nil {
fa.doError("not implemented", "ENOSYS", w)
return
}
fa.okResponse(map[string]any{}, w)
}

type Rmdir struct {
Path string `json:"path"`
}

func (r *Rmdir) WriteResponse(fa *FsHandler, w http.ResponseWriter) {
err := syscall.Rmdir(fixPath(r.Path))
if err != nil {
fa.doError("not implemented", "ENOSYS", w)
return
}
fa.okResponse(map[string]any{}, w)
}
47 changes: 47 additions & 0 deletions filesys/stat.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
//go:build darwin || linux

package filesys

import (
"net/http"
"os"
"syscall"
)

func (st *Stat) WriteResponse(fa *FsHandler, w http.ResponseWriter) {
s := &syscall.Stat_t{}
err := syscall.Stat(fixPath(st.Path), s)
if err != nil {
if os.IsNotExist(err) {
fa.doError(err.Error(), "ENOENT", w)
return
}
fa.doError(err.Error(), "ENOSYS", w)
return
}
fa.okResponse(mapOfStatT(s), w)
}

func (f *Fstat) WriteResponse(fa *FsHandler, w http.ResponseWriter) {
s := &syscall.Stat_t{}
err := syscall.Fstat(f.Fd, s)
if err != nil {
fa.doError(err.Error(), "ENOSYS", w)
return
}
fa.okResponse(mapOfStatT(s), w)
}

func (ls *Lstat) WriteResponse(fa *FsHandler, w http.ResponseWriter) {
s := &syscall.Stat_t{}
err := syscall.Lstat(fixPath(ls.Path), s)
if err != nil {
if os.IsNotExist(err) {
fa.doError(err.Error(), "ENOENT", w)
return
}
fa.doError("not implemented", "ENOSYS", w)
return
}
fa.okResponse(mapOfStatT(s), w)
}
Loading

0 comments on commit 8c4afd5

Please sign in to comment.