Skip to content

Commit

Permalink
implement fs api forwarding
Browse files Browse the repository at this point in the history
  • Loading branch information
mlctrez committed Sep 4, 2023
1 parent 37ec9e7 commit b49d670
Show file tree
Hide file tree
Showing 12 changed files with 1,090 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)
}
302 changes: 302 additions & 0 deletions filesys/handler.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,302 @@
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 json error :", err)
}
}

func (fa *FsHandler) okResponse(data any, w http.ResponseWriter) {
if marshal, err := json.Marshal(data); err != nil {
fa.logger.Println("okResponse json error:", 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 fa.handleError(w, err, true) {
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(syscall.ENOSYS.Error(), "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 fa.handleError(w, err, true) {
return
}
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(syscall.ENOSYS.Error(), "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 fa.handleError(w, err, true) {
return
}
fa.okResponse(map[string]any{}, w)
}

func (fa *FsHandler) handleError(w http.ResponseWriter, err error, noEnt bool) bool {
if err == nil {
return false
}
if noEnt && os.IsNotExist(err) {
fa.doError(syscall.ENOENT.Error(), "ENOENT", w)
} else {
fa.doError(syscall.ENOSYS.Error(), "ENOSYS", w)
}
return true
}
Loading

0 comments on commit b49d670

Please sign in to comment.