Skip to content

Commit 237f0b6

Browse files
authored
initial work for fs api forwarding (#49)
* implement fs api forwarding * changes from first review * fix cwd return to match original, rename constant * second review updates * fix missed arg, remove commented out code
1 parent 37ec9e7 commit 237f0b6

11 files changed

+1129
-60
lines changed

filesys/fd.go

+7
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
//go:build darwin || linux
2+
3+
package filesys
4+
5+
func FdType(fd int) int {
6+
return fd
7+
}

filesys/fd_windows.go

+7
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
package filesys
2+
3+
import "syscall"
4+
5+
func FdType(fd int) syscall.Handle {
6+
return syscall.Handle(fd)
7+
}

filesys/handler.go

+319
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,319 @@
1+
package filesys
2+
3+
import (
4+
"encoding/base64"
5+
"encoding/json"
6+
"errors"
7+
"fmt"
8+
"log"
9+
"net/http"
10+
"os"
11+
"strings"
12+
"syscall"
13+
)
14+
15+
// Handler translates json payload data to and from system calls like syscall.Stat
16+
type Handler struct {
17+
debug bool
18+
securityToken string
19+
logger *log.Logger
20+
}
21+
22+
func NewHandler(securityToken string, logger *log.Logger) *Handler {
23+
return &Handler{
24+
debug: false,
25+
securityToken: securityToken,
26+
logger: logger,
27+
}
28+
}
29+
30+
func (fa *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
31+
if r.Header.Get("WBT-Token") != fa.securityToken {
32+
fa.doError("not implemented", "ENOSYS", w, errors.New("missing WBT-token"))
33+
return
34+
}
35+
switch r.URL.Path {
36+
case "/fs/stat":
37+
fa.handle(&Stat{}, w, r)
38+
case "/fs/fstat":
39+
fa.handle(&Fstat{}, w, r)
40+
case "/fs/open":
41+
fa.handle(&Open{}, w, r)
42+
case "/fs/write":
43+
fa.handle(&Write{}, w, r)
44+
case "/fs/close":
45+
fa.handle(&Close{}, w, r)
46+
case "/fs/rename":
47+
fa.handle(&Rename{}, w, r)
48+
case "/fs/readdir":
49+
fa.handle(&Readdir{}, w, r)
50+
case "/fs/lstat":
51+
fa.handle(&Lstat{}, w, r)
52+
case "/fs/read":
53+
fa.handle(&Read{}, w, r)
54+
case "/fs/mkdir":
55+
fa.handle(&Mkdir{}, w, r)
56+
case "/fs/unlink":
57+
fa.handle(&Unlink{}, w, r)
58+
case "/fs/rmdir":
59+
fa.handle(&Rmdir{}, w, r)
60+
default:
61+
fa.doError("not implemented", "ENOSYS", w,
62+
fmt.Errorf("unsupported api path %q", r.URL.Path))
63+
}
64+
}
65+
66+
type Responder interface {
67+
WriteResponse(fa *Handler, w http.ResponseWriter)
68+
}
69+
70+
func (fa *Handler) handle(responder Responder, w http.ResponseWriter, r *http.Request) {
71+
if err := json.NewDecoder(r.Body).Decode(responder); err != nil {
72+
fa.logger.Printf("ERROR handle : %v\n", err)
73+
w.WriteHeader(http.StatusBadRequest)
74+
return
75+
}
76+
if fa.debug {
77+
fa.logger.Printf("handle %s %+v\n", r.URL.Path, responder)
78+
}
79+
responder.WriteResponse(fa, w)
80+
}
81+
82+
type ErrorCode struct {
83+
Error string `json:"error"`
84+
Code string `json:"code"`
85+
}
86+
87+
func (fa *Handler) doError(msg, code string, w http.ResponseWriter, err error) {
88+
if fa.debug {
89+
fa.logger.Printf("doError %s : %s\n", msg, code)
90+
}
91+
if err != nil {
92+
fa.logger.Printf("Error: %v", err)
93+
}
94+
95+
e := &ErrorCode{Error: msg, Code: code}
96+
97+
w.WriteHeader(http.StatusBadRequest)
98+
w.Header().Set("Content-Type", "application/json")
99+
if err := json.NewEncoder(w).Encode(e); err != nil {
100+
fa.logger.Printf("Error encoding json: %v", err)
101+
}
102+
}
103+
104+
func (fa *Handler) okResponse(data any, w http.ResponseWriter) {
105+
var marshal []byte
106+
var err error
107+
if marshal, err = json.Marshal(data); err != nil {
108+
fa.logger.Println("okResponse json error:", err)
109+
w.WriteHeader(http.StatusInternalServerError)
110+
return
111+
}
112+
if fa.debug {
113+
fa.logger.Printf("okResponse %s\n", string(marshal))
114+
}
115+
116+
w.WriteHeader(http.StatusOK)
117+
w.Header().Set("Content-Type", "application/json")
118+
if _, err = w.Write(marshal); err != nil {
119+
fa.logger.Printf("Error writing json: %v", err)
120+
}
121+
}
122+
123+
func fixPath(path string) string {
124+
return strings.TrimPrefix(path, "/fs/")
125+
}
126+
127+
type Stat struct {
128+
Path string `json:"path,omitempty"`
129+
}
130+
131+
type Open struct {
132+
Path string `json:"path"`
133+
Flags int `json:"flags"`
134+
Mode uint32 `json:"mode"`
135+
}
136+
137+
func (o *Open) WriteResponse(fa *Handler, w http.ResponseWriter) {
138+
fd, err := syscall.Open(fixPath(o.Path), o.Flags, o.Mode)
139+
if fa.handleError(w, err, true) {
140+
return
141+
}
142+
response := map[string]any{"fd": fd}
143+
fa.okResponse(response, w)
144+
}
145+
146+
type Fstat struct {
147+
Fd int `json:"fd"`
148+
}
149+
150+
type Write struct {
151+
Fd int `json:"fd"`
152+
Buffer string `json:"buffer"`
153+
Offset int `json:"offset"`
154+
Length int `json:"length"`
155+
Position *int `json:"position,omitempty"`
156+
}
157+
158+
func (wr *Write) WriteResponse(fa *Handler, w http.ResponseWriter) {
159+
if wr.Offset != 0 {
160+
fa.doError("not implemented", "ENOSYS", w,
161+
fmt.Errorf("write offset %d not supported", wr.Offset))
162+
return
163+
}
164+
if wr.Position != nil {
165+
_, err := syscall.Seek(FdType(wr.Fd), int64(*wr.Position), 0)
166+
if err != nil {
167+
fa.doError("not implemented", "ENOSYS", w, err)
168+
return
169+
}
170+
}
171+
172+
bytes, err := base64.StdEncoding.DecodeString(wr.Buffer)
173+
if err != nil {
174+
fa.doError("not implemented", "ENOSYS", w, err)
175+
return
176+
}
177+
178+
var written int
179+
written, err = syscall.Write(FdType(wr.Fd), bytes)
180+
if err != nil {
181+
fa.doError("not implemented", "ENOSYS", w, err)
182+
return
183+
}
184+
185+
fa.okResponse(map[string]any{"written": written}, w)
186+
}
187+
188+
type Close struct {
189+
Fd int `json:"fd"`
190+
}
191+
192+
func (c *Close) WriteResponse(fa *Handler, w http.ResponseWriter) {
193+
err := syscall.Close(FdType(c.Fd))
194+
if fa.handleError(w, err, false) {
195+
return
196+
}
197+
fa.okResponse(map[string]any{}, w)
198+
}
199+
200+
type Rename struct {
201+
From string `json:"from"`
202+
To string `json:"to"`
203+
}
204+
205+
func (r *Rename) WriteResponse(fa *Handler, w http.ResponseWriter) {
206+
err := syscall.Rename(fixPath(r.From), fixPath(r.To))
207+
if fa.handleError(w, err, true) {
208+
return
209+
}
210+
fa.okResponse(map[string]any{}, w)
211+
}
212+
213+
type Readdir struct {
214+
Path string `json:"path"`
215+
}
216+
217+
func (r *Readdir) WriteResponse(fa *Handler, w http.ResponseWriter) {
218+
entries, err := os.ReadDir(fixPath(r.Path))
219+
if fa.handleError(w, err, false) {
220+
return
221+
}
222+
stringNames := make([]string, len(entries))
223+
for i, entry := range entries {
224+
stringNames[i] = entry.Name()
225+
}
226+
fa.okResponse(map[string]any{"entries": stringNames}, w)
227+
}
228+
229+
type Lstat struct {
230+
Path string `json:"path"`
231+
}
232+
233+
type Read struct {
234+
Fd int `json:"fd"`
235+
Offset int `json:"offset"`
236+
Length int `json:"length"`
237+
Position *int `json:"position,omitempty"`
238+
}
239+
240+
func (r *Read) WriteResponse(fa *Handler, w http.ResponseWriter) {
241+
if r.Offset != 0 {
242+
fa.doError("not implemented", "ENOSYS", w,
243+
fmt.Errorf("read offset %d not supported", r.Offset))
244+
return
245+
}
246+
if r.Position != nil {
247+
_, err := syscall.Seek(FdType(r.Fd), int64(*r.Position), 0)
248+
if err != nil {
249+
fa.doError("not implemented", "ENOSYS", w, err)
250+
return
251+
}
252+
}
253+
254+
buffer := make([]byte, r.Length)
255+
read, err := syscall.Read(FdType(r.Fd), buffer)
256+
if err != nil {
257+
fa.doError("not implemented", "ENOSYS", w, err)
258+
return
259+
}
260+
response := map[string]any{
261+
"read": read,
262+
"buffer": base64.StdEncoding.EncodeToString(buffer[:read]),
263+
}
264+
fa.okResponse(response, w)
265+
266+
}
267+
268+
type Mkdir struct {
269+
Path string `json:"path"`
270+
Perm uint32 `json:"perm"`
271+
}
272+
273+
func (m *Mkdir) WriteResponse(fa *Handler, w http.ResponseWriter) {
274+
err := syscall.Mkdir(fixPath(m.Path), m.Perm)
275+
if err != nil {
276+
fa.doError("not implemented", "ENOSYS", w, err)
277+
return
278+
}
279+
fa.okResponse(map[string]any{}, w)
280+
}
281+
282+
type Unlink struct {
283+
Path string `json:"path"`
284+
}
285+
286+
func (u *Unlink) WriteResponse(fa *Handler, w http.ResponseWriter) {
287+
err := syscall.Unlink(fixPath(u.Path))
288+
if err != nil {
289+
fa.doError("not implemented", "ENOSYS", w, err)
290+
return
291+
}
292+
fa.okResponse(map[string]any{}, w)
293+
}
294+
295+
type Rmdir struct {
296+
Path string `json:"path"`
297+
}
298+
299+
func (r *Rmdir) WriteResponse(fa *Handler, w http.ResponseWriter) {
300+
err := syscall.Rmdir(fixPath(r.Path))
301+
if fa.handleError(w, err, true) {
302+
return
303+
}
304+
fa.okResponse(map[string]any{}, w)
305+
}
306+
307+
func (fa *Handler) handleError(w http.ResponseWriter, err error, noEnt bool) bool {
308+
if err == nil {
309+
return false
310+
}
311+
if noEnt && os.IsNotExist(err) {
312+
// We're not passing the error down for logging here since this is a
313+
// file not found condition, not an actual error condition.
314+
fa.doError(syscall.ENOENT.Error(), "ENOENT", w, nil)
315+
} else {
316+
fa.doError(syscall.ENOSYS.Error(), "ENOSYS", w, err)
317+
}
318+
return true
319+
}

0 commit comments

Comments
 (0)