From 316e65589b20c8181afa07aa5ea936d42ae58e13 Mon Sep 17 00:00:00 2001 From: lewisxy Date: Wed, 30 Dec 2020 18:32:52 -0800 Subject: [PATCH] librclone: export the rclone RC as a C library #4891 --- librclone/ctest/Makefile | 22 +++++++ librclone/ctest/ctest.c | 32 ++++++++++ librclone/librclone.go | 133 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 187 insertions(+) create mode 100644 librclone/ctest/Makefile create mode 100644 librclone/ctest/ctest.c create mode 100644 librclone/librclone.go diff --git a/librclone/ctest/Makefile b/librclone/ctest/Makefile new file mode 100644 index 000000000..ab8035b3d --- /dev/null +++ b/librclone/ctest/Makefile @@ -0,0 +1,22 @@ +CFLAGS = -g -Wall +LDFLAGS = -L. -lrclone -lpthread -ldl + +static: ctest + +shared: + go build --buildmode=c-shared -o librclone.so github.com/rclone/rclone/librclone + +ctest: ctest.o librclone.h + $(CC) $(CFLAGS) -o $@ $< $(LDFLAGS) + +ctest.o: ctest.c librclone.h + $(CC) $(CFLAGS) -c $^ $(LDFLAGS) + +build: + go build + +librclone.h: + go build --buildmode=c-archive -o librclone.a github.com/rclone/rclone/librclone + +clean: + rm -f tmp ctest *.o *.a *.h *.gch diff --git a/librclone/ctest/ctest.c b/librclone/ctest/ctest.c new file mode 100644 index 000000000..c84486f0c --- /dev/null +++ b/librclone/ctest/ctest.c @@ -0,0 +1,32 @@ +#include +#include +#include +#include +#include "librclone.h" + +// copy file using "operations/copyfile" command +void testCopyFile() { + struct CRPC_return res = CRPC("operations/copyfile", "{ \"srcFs\": \"/tmp\", \"srcRemote\": \"tmpfile\", \"dstFs\": \"/tmp\", \"dstRemote\": \"tmpfile2\" }"); + printf("%d\n", res.r1); // status + printf("%s\n", res.r0); // output + free(res.r0); +} + +// noop command +void testNoOp() { + struct CRPC_return res = CRPC("rc/noop", "{ \"p1\": [1,\"2\",null,4], \"p2\": { \"a\":1, \"b\":2 } }"); + printf("%d\n", res.r1); // status + printf("%s\n", res.r0); // output + free(res.r0); +} + +int main(int argc, char** argv) { + printf("c main begin\n"); + Cinit(); + + //testNoOp(); + testCopyFile(); + + Cdestroy(); + return EXIT_SUCCESS; +} diff --git a/librclone/librclone.go b/librclone/librclone.go new file mode 100644 index 000000000..bcc014814 --- /dev/null +++ b/librclone/librclone.go @@ -0,0 +1,133 @@ +// Package exports exports function for c library + +package main + +import ( + "C" + "bytes" + "context" + "encoding/json" + "io" + "net/http" + "runtime" + "strings" + + "github.com/pkg/errors" + "github.com/rclone/rclone/fs" + "github.com/rclone/rclone/fs/rc" + "github.com/rclone/rclone/fs/rc/jobs" + + _ "github.com/rclone/rclone/backend/all" // import all backends + _ "github.com/rclone/rclone/cmd/all" // import all commands + _ "github.com/rclone/rclone/lib/plugin" // import plugins +) + +func init() { + // do nothing +} + +// call to init the library +//export Cinit +func Cinit() { + // TODO: what need to be initialized manually? +} + +// call to destroy the whole thing +//export Cdestroy +func Cdestroy() { + // TODO: how to clean up? what happens when rcserver terminates? + // what about unfinished async jobs? + runtime.GC() +} + +// Caller is responsible for freeing the memory for output +// TODO: how to specify config file? +//export CRPC +func CRPC(method *C.char, input *C.char) (output *C.char, status C.int) { + res, s := callFunctionJSON(C.GoString(method), C.GoString(input)) + return C.CString(res), C.int(s) +} + +// copied from rcserver.go +// writeError writes a formatted error to the output +func writeError(path string, in rc.Params, w io.Writer, err error, status int) { + fs.Errorf(nil, "rc: %q: error: %v", path, err) + // Adjust the error return for some well known errors + errOrig := errors.Cause(err) + switch { + case errOrig == fs.ErrorDirNotFound || errOrig == fs.ErrorObjectNotFound: + status = http.StatusNotFound + case rc.IsErrParamInvalid(err) || rc.IsErrParamNotFound(err): + status = http.StatusBadRequest + } + // w.WriteHeader(status) + err = rc.WriteJSON(w, rc.Params{ + "status": status, + "error": err.Error(), + "input": in, + "path": path, + }) + if err != nil { + // can't return the error at this point + fs.Errorf(nil, "rc: failed to write JSON output: %v", err) + } +} + +// operations/uploadfile and core/command are not supported as they need request or response object +// modified from handlePost in rcserver.go +// call a rc function using JSON to input parameters and output the resulted JSON +func callFunctionJSON(method string, input string) (output string, status int) { + // create a buffer to capture the output + buf := new(bytes.Buffer) + in := make(rc.Params) + err := json.NewDecoder(strings.NewReader(input)).Decode(&in) + if err != nil { + // TODO: handle error + writeError(method, in, buf, errors.Wrap(err, "failed to read input JSON"), http.StatusBadRequest) + return buf.String(), http.StatusBadRequest + } + + // Find the call + call := rc.Calls.Get(method) + if call == nil { + writeError(method, in, buf, errors.Errorf("couldn't find method %q", method), http.StatusNotFound) + return buf.String(), http.StatusNotFound + } + + // TODO: handle these cases + if call.NeedsRequest { + writeError(method, in, buf, errors.Errorf("method %q needs request, not supported", method), http.StatusNotFound) + return buf.String(), http.StatusNotFound + // Add the request to RC + //in["_request"] = r + } + if call.NeedsResponse { + writeError(method, in, buf, errors.Errorf("method %q need response, not supported", method), http.StatusNotFound) + return buf.String(), http.StatusNotFound + //in["_response"] = w + } + + fs.Debugf(nil, "rc: %q: with parameters %+v", method, in) + // TODO: what is r.Context()? use Background() for the moment + _, out, err := jobs.NewJob(context.Background(), call.Fn, in) + if err != nil { + // handle error + writeError(method, in, buf, err, http.StatusInternalServerError) + return buf.String(), http.StatusInternalServerError + } + if out == nil { + out = make(rc.Params) + } + + fs.Debugf(nil, "rc: %q: reply %+v: %v", method, out, err) + err = rc.WriteJSON(buf, out) + if err != nil { + writeError(method, in, buf, err, http.StatusInternalServerError) + return buf.String(), http.StatusInternalServerError + fs.Errorf(nil, "rc: failed to write JSON output: %v", err) + } + return buf.String(), http.StatusOK +} + +// do nothing here +func main() {}