From 5aaf77f2765623bf9c387b6f301bb59b55c1aed8 Mon Sep 17 00:00:00 2001 From: Martchus Date: Sun, 8 Apr 2018 20:01:43 +0200 Subject: [PATCH] Add basic C bindings to start and stop Syncthing and invoke its CLI --- c-bindings/c_bindings.c | 11 ++ c-bindings/c_bindings.go | 241 +++++++++++++++++++++++++ c-bindings/c_bindings.h | 11 ++ c-bindings/debug.go | 15 ++ cmd/syncthing/blockprof.go | 2 +- cmd/syncthing/cli/main.go | 53 ++---- cmd/syncthing/crash_reporting.go | 2 +- cmd/syncthing/crash_reporting_test.go | 2 +- cmd/syncthing/debug.go | 4 +- cmd/syncthing/heapprof.go | 2 +- cmd/syncthing/main.go | 10 +- cmd/syncthing/monitor.go | 2 +- cmd/syncthing/monitor_test.go | 2 +- cmd/syncthing/openurl_unix.go | 2 +- cmd/syncthing/openurl_windows.go | 2 +- cmd/syncthing/perfstats_unix.go | 2 +- cmd/syncthing/perfstats_unsupported.go | 2 +- cmd/syncthing/traceback.go | 2 +- 18 files changed, 312 insertions(+), 55 deletions(-) create mode 100644 c-bindings/c_bindings.c create mode 100644 c-bindings/c_bindings.go create mode 100644 c-bindings/c_bindings.h create mode 100644 c-bindings/debug.go diff --git a/c-bindings/c_bindings.c b/c-bindings/c_bindings.c new file mode 100644 index 000000000..1dd98157f --- /dev/null +++ b/c-bindings/c_bindings.c @@ -0,0 +1,11 @@ +#include "c_bindings.h" + +libst_logging_callback_function_t libst_logging_callback_function = NULL; + +void libst_invoke_logging_callback(int log_level, const char *message, size_t message_size) +{ + if (!libst_logging_callback_function) { + return; + } + libst_logging_callback_function(log_level, message, message_size); +} diff --git a/c-bindings/c_bindings.go b/c-bindings/c_bindings.go new file mode 100644 index 000000000..910c07c89 --- /dev/null +++ b/c-bindings/c_bindings.go @@ -0,0 +1,241 @@ +package main + +import ( + "context" + "fmt" + "os" + "unsafe" + "path/filepath" + _ "net/http/pprof" // Need to import this to support STPROFILER. + + "github.com/syncthing/syncthing/lib/build" + "github.com/syncthing/syncthing/lib/config" + "github.com/syncthing/syncthing/lib/events" + "github.com/syncthing/syncthing/lib/logger" + "github.com/syncthing/syncthing/lib/locations" + "github.com/syncthing/syncthing/lib/protocol" + "github.com/syncthing/syncthing/lib/svcutil" + "github.com/syncthing/syncthing/lib/syncthing" + "github.com/syncthing/syncthing/cmd/syncthing/cli" + "github.com/syncthing/syncthing/cmd/syncthing" + "github.com/thejerf/suture/v4" +) + +// include header for required C helper functions (so the following comment is NO comment) + +// #include "c_bindings.h" +import "C" + +var theApp *syncthing.App +var myID protocol.DeviceID +var cliArgs []string + +const ( + tlsDefaultCommonName = "syncthing" +) + +//export libst_own_device_id +func libst_own_device_id() string { + return myID.String() +} + +//export libst_init_logging +func libst_init_logging() { + l.AddHandler(logger.LevelVerbose, func(level logger.LogLevel, msg string) { + runes := []byte(msg) + length := len(runes) + if length <= 0 { + return + } + C.libst_invoke_logging_callback(C.int(level), (*C.char)(unsafe.Pointer(&runes[0])), C.size_t(len(runes))) + }) +} + +//export libst_clear_cli_args +func libst_clear_cli_args(command string) { + if command == "cli" { + cliArgs = []string{} + } else { + cliArgs = []string{command} + } +} + +//export libst_append_cli_arg +func libst_append_cli_arg(arg string) { + cliArgs = append(cliArgs, arg) +} + +//export libst_run_cli +func libst_run_cli() int { + if err := cli.RunWithArgs(cliArgs); err != nil { + fmt.Println(err) + return 1 + } + return 0 +} + +//export libst_run_main +func libst_run_main() int { + if err := syncthing_main.RunWithArgs(cliArgs); err != nil { + fmt.Println(err) + return 1 + } + return 0 +} + +//export libst_run_syncthing +func libst_run_syncthing(configDir string, dataDir string, guiAddress string, guiApiKey string, verbose bool, allowNewerConfig bool, noDefaultConfig bool, skipPortProbing bool, ensureConfigDirExists bool, ensureDataDirExists bool) int { + // return if already running (for simplicity we only allow one Syncthing instance at at time for now) + if theApp != nil { + return 0 + } + + // set specified GUI address and API key + if guiAddress != "" { + os.Setenv("STGUIADDRESS", guiAddress) + } + if guiApiKey != "" { + os.Setenv("STGUIAPIKEY", guiApiKey) + } + + // set specified config dir + if configDir != "" { + if !filepath.IsAbs(configDir) { + var err error + configDir, err = filepath.Abs(configDir) + if err != nil { + l.Warnln("Failed to make config path absolute:", err) + return 3 + } + } + if err := locations.SetBaseDir(locations.ConfigBaseDir, configDir); err != nil { + l.Warnln(err) + return 3 + } + } + + // set specified database dir + if dataDir != "" { + if !filepath.IsAbs(dataDir) { + var err error + dataDir, err = filepath.Abs(dataDir) + if err != nil { + l.Warnln("Failed to make database path absolute:", err) + return 3 + } + } + if err := locations.SetBaseDir(locations.DataBaseDir, dataDir); err != nil { + l.Warnln(err) + return 3 + } + } + + // ensure that the config directory exists + if ensureConfigDirExists { + if err := syncthing.EnsureDir(locations.GetBaseDir(locations.ConfigBaseDir), 0700); err != nil { + l.Warnln("Failed to create config directory:", err) + return 4 + } + } + + // ensure that the database directory exists + if dataDir != "" && ensureDataDirExists { + if err := syncthing.EnsureDir(locations.GetBaseDir(locations.DataBaseDir), 0700); err != nil { + l.Warnln("Failed to create database directory:", err) + return 4 + } + } + + // ensure that we have a certificate and key + cert, certErr := syncthing.LoadOrGenerateCertificate( + locations.Get(locations.CertFile), + locations.Get(locations.KeyFile), + ) + if certErr != nil { + l.Warnln("Failed to load/generate certificate:", certErr) + return 1 + } + + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + // earlyService is a supervisor that runs the services needed for or + // before app startup; the event logger, and the config service. + spec := svcutil.SpecWithDebugLogger(l) + earlyService := suture.New("early", spec) + earlyService.ServeBackground(ctx) + + evLogger := events.NewLogger() + earlyService.Add(evLogger) + + // load config + configLocation := locations.Get(locations.ConfigFile) + l.Infoln("Loading config from:", configLocation) + cfgWrapper, cfgErr := syncthing.LoadConfigAtStartup(configLocation, cert, evLogger, allowNewerConfig, noDefaultConfig, skipPortProbing) + if cfgErr != nil { + l.Warnln("Failed to initialize config:", cfgErr) + return 2 + } + if cfgService, ok := cfgWrapper.(suture.Service); ok { + earlyService.Add(cfgService) + } + + // open database + dbFile := locations.Get(locations.Database) + l.Infoln("Opening database from:", dbFile) + ldb, dbErr := syncthing.OpenDBBackend(dbFile, config.TuningAuto) + if dbErr != nil { + l.Warnln("Error opening database:", dbErr) + return 4 + } + + appOpts := syncthing.Options{ + ProfilerAddr: os.Getenv("STPROFILER"), + NoUpgrade: true, + Verbose: verbose, + } + var err error + theApp, err = syncthing.New(cfgWrapper, ldb, evLogger, cert, appOpts) + if err != nil { + l.Warnln("Failed to start Syncthing:", err) + return svcutil.ExitError.AsInt() + } + + // start Syncthing and block until it has finished + returnCode := 0 + if err := theApp.Start(); err != nil { + returnCode = svcutil.ExitError.AsInt() + } + returnCode = theApp.Wait().AsInt(); + theApp = nil + return returnCode +} + +//export libst_stop_syncthing +func libst_stop_syncthing() int { + if theApp != nil { + return int(theApp.Stop(svcutil.ExitSuccess)) + } else { + return 0; + } +} + +//export libst_reset_database +func libst_reset_database() { + os.RemoveAll(locations.Get(locations.Database)) +} + +//export libst_syncthing_version +func libst_syncthing_version() *C.char { + return C.CString(build.Version) +} + +//export libst_long_syncthing_version +func libst_long_syncthing_version() *C.char { + return C.CString(build.LongVersion) +} + +func main() { + // prevent "runtime.main_mainĀ·f: function main is undeclared in the main package" +} + diff --git a/c-bindings/c_bindings.h b/c-bindings/c_bindings.h new file mode 100644 index 000000000..e4e32ecb3 --- /dev/null +++ b/c-bindings/c_bindings.h @@ -0,0 +1,11 @@ +#ifndef LIBSYNCTHING_INTERNAL_H +#define LIBSYNCTHING_INTERNAL_H + +#include + +// allow registration of callback function +typedef void (*libst_logging_callback_function_t) (int logLevel, const char *msg, size_t msgSize); +extern libst_logging_callback_function_t libst_logging_callback_function; +extern void libst_invoke_logging_callback(int log_level, const char *message, size_t message_size); + +#endif // LIBSYNCTHING_INTERNAL_H diff --git a/c-bindings/debug.go b/c-bindings/debug.go new file mode 100644 index 000000000..c55544a3e --- /dev/null +++ b/c-bindings/debug.go @@ -0,0 +1,15 @@ +// Copyright (C) 2014 The Syncthing Authors. +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this file, +// You can obtain one at https://mozilla.org/MPL/2.0/. + +package main + +import ( + "github.com/syncthing/syncthing/lib/logger" +) + +var ( + l = logger.DefaultLogger.NewFacility("main", "Main package") +) diff --git a/cmd/syncthing/blockprof.go b/cmd/syncthing/blockprof.go index 0dbee8893..7ceb9fa6b 100644 --- a/cmd/syncthing/blockprof.go +++ b/cmd/syncthing/blockprof.go @@ -4,7 +4,7 @@ // License, v. 2.0. If a copy of the MPL was not distributed with this file, // You can obtain one at https://mozilla.org/MPL/2.0/. -package main +package syncthing_main import ( "fmt" diff --git a/cmd/syncthing/cli/main.go b/cmd/syncthing/cli/main.go index 4d9fb5727..17707cc20 100644 --- a/cmd/syncthing/cli/main.go +++ b/cmd/syncthing/cli/main.go @@ -7,12 +7,10 @@ package cli import ( - "bufio" "fmt" - "os" "github.com/alecthomas/kong" - "github.com/flynn-archive/go-shlex" + "github.com/willabides/kongplete" "github.com/syncthing/syncthing/cmd/syncthing/cmdutil" "github.com/syncthing/syncthing/lib/config" @@ -59,37 +57,22 @@ func (cli CLI) AfterApply(kongCtx *kong.Context) error { type stdinCommand struct{} -func (*stdinCommand) Run() error { - // Drop the `-` not to recurse into self. - args := make([]string, len(os.Args)-1) - copy(args, os.Args) - - fmt.Println("Reading commands from stdin...", args) - scanner := bufio.NewScanner(os.Stdin) - for scanner.Scan() { - input, err := shlex.Split(scanner.Text()) - if err != nil { - return fmt.Errorf("parsing input: %w", err) - } - if len(input) == 0 { - continue - } - - var cli CLI - p, err := kong.New(&cli) - if err != nil { - // can't happen, really - return fmt.Errorf("creating parser: %w", err) - } - ctx, err := p.Parse(input) - if err != nil { - fmt.Println("Error:", err) - continue - } - if err := ctx.Run(); err != nil { - fmt.Println("Error:", err) - continue - } +func RunWithArgs(args []string) error { + var cli CLI + p, err := kong.New(&cli) + if err != nil { + // can't happen, really + return fmt.Errorf("creating parser: %w", err) } - return scanner.Err() + kongplete.Complete(p) + ctx, err := p.Parse(args) + if err != nil { + fmt.Println("Error:", err) + return err + } + if err := ctx.Run(); err != nil { + fmt.Println("Error:", err) + return err + } + return nil } diff --git a/cmd/syncthing/crash_reporting.go b/cmd/syncthing/crash_reporting.go index 27896ff8a..2ece8a090 100644 --- a/cmd/syncthing/crash_reporting.go +++ b/cmd/syncthing/crash_reporting.go @@ -4,7 +4,7 @@ // License, v. 2.0. If a copy of the MPL was not distributed with this file, // You can obtain one at https://mozilla.org/MPL/2.0/. -package main +package syncthing_main import ( "bytes" diff --git a/cmd/syncthing/crash_reporting_test.go b/cmd/syncthing/crash_reporting_test.go index 045a10187..a21bcb1db 100644 --- a/cmd/syncthing/crash_reporting_test.go +++ b/cmd/syncthing/crash_reporting_test.go @@ -4,7 +4,7 @@ // License, v. 2.0. If a copy of the MPL was not distributed with this file, // You can obtain one at https://mozilla.org/MPL/2.0/. -package main +package syncthing_main import ( "bytes" diff --git a/cmd/syncthing/debug.go b/cmd/syncthing/debug.go index c55544a3e..a6a05b0e9 100644 --- a/cmd/syncthing/debug.go +++ b/cmd/syncthing/debug.go @@ -4,12 +4,12 @@ // License, v. 2.0. If a copy of the MPL was not distributed with this file, // You can obtain one at https://mozilla.org/MPL/2.0/. -package main +package syncthing_main import ( "github.com/syncthing/syncthing/lib/logger" ) var ( - l = logger.DefaultLogger.NewFacility("main", "Main package") + l = logger.DefaultLogger.NewFacility("syncthing_main", "Syncthing package") ) diff --git a/cmd/syncthing/heapprof.go b/cmd/syncthing/heapprof.go index ca552d6a4..3332ce5cd 100644 --- a/cmd/syncthing/heapprof.go +++ b/cmd/syncthing/heapprof.go @@ -4,7 +4,7 @@ // License, v. 2.0. If a copy of the MPL was not distributed with this file, // You can obtain one at https://mozilla.org/MPL/2.0/. -package main +package syncthing_main import ( "fmt" diff --git a/cmd/syncthing/main.go b/cmd/syncthing/main.go index 2c5cc02a2..5e01f734b 100644 --- a/cmd/syncthing/main.go +++ b/cmd/syncthing/main.go @@ -4,7 +4,7 @@ // License, v. 2.0. If a copy of the MPL was not distributed with this file, // You can obtain one at https://mozilla.org/MPL/2.0/. -package main +package syncthing_main import ( "bytes" @@ -34,7 +34,6 @@ import ( "github.com/thejerf/suture/v4" "github.com/willabides/kongplete" - "github.com/syncthing/syncthing/cmd/syncthing/cli" "github.com/syncthing/syncthing/cmd/syncthing/cmdutil" "github.com/syncthing/syncthing/cmd/syncthing/decrypt" "github.com/syncthing/syncthing/cmd/syncthing/generate" @@ -137,8 +136,6 @@ var entrypoint struct { Serve serveOptions `cmd:"" help:"Run Syncthing"` Generate generate.CLI `cmd:"" help:"Generate key and config, then exit"` Decrypt decrypt.CLI `cmd:"" help:"Decrypt or verify an encrypted folder"` - Cli cli.CLI `cmd:"" help:"Command line interface for Syncthing"` - InstallCompletions kongplete.InstallCompletions `cmd:"" help:"Print commands to install shell completions"` } // serveOptions are the options for the `syncthing serve` command. @@ -210,12 +207,10 @@ func defaultVars() kong.Vars { return vars } -func main() { +func RunWithArgs(args []string) error { // First some massaging of the raw command line to fit the new model. // Basically this means adding the default command at the front, and // converting -options to --options. - - args := os.Args[1:] switch { case len(args) == 0: // Empty command line is equivalent to just calling serve @@ -255,6 +250,7 @@ func main() { ctx.BindTo(l, (*logger.Logger)(nil)) // main logger available to subcommands err = ctx.Run() parser.FatalIfErrorf(err) + return err } func helpHandler(options kong.HelpOptions, ctx *kong.Context) error { diff --git a/cmd/syncthing/monitor.go b/cmd/syncthing/monitor.go index 3863bcefd..a689d7019 100644 --- a/cmd/syncthing/monitor.go +++ b/cmd/syncthing/monitor.go @@ -4,7 +4,7 @@ // License, v. 2.0. If a copy of the MPL was not distributed with this file, // You can obtain one at https://mozilla.org/MPL/2.0/. -package main +package syncthing_main import ( "bufio" diff --git a/cmd/syncthing/monitor_test.go b/cmd/syncthing/monitor_test.go index d0bc3b033..6291f0fdf 100644 --- a/cmd/syncthing/monitor_test.go +++ b/cmd/syncthing/monitor_test.go @@ -4,7 +4,7 @@ // License, v. 2.0. If a copy of the MPL was not distributed with this file, // You can obtain one at https://mozilla.org/MPL/2.0/. -package main +package syncthing_main import ( "io" diff --git a/cmd/syncthing/openurl_unix.go b/cmd/syncthing/openurl_unix.go index 51e9b7350..4c0d09d57 100644 --- a/cmd/syncthing/openurl_unix.go +++ b/cmd/syncthing/openurl_unix.go @@ -7,7 +7,7 @@ //go:build !windows // +build !windows -package main +package syncthing_main import ( "os/exec" diff --git a/cmd/syncthing/openurl_windows.go b/cmd/syncthing/openurl_windows.go index 2f5eca8ef..f1cebbead 100644 --- a/cmd/syncthing/openurl_windows.go +++ b/cmd/syncthing/openurl_windows.go @@ -7,7 +7,7 @@ //go:build windows // +build windows -package main +package syncthing_main import "os/exec" diff --git a/cmd/syncthing/perfstats_unix.go b/cmd/syncthing/perfstats_unix.go index 6da5a2116..764223e18 100644 --- a/cmd/syncthing/perfstats_unix.go +++ b/cmd/syncthing/perfstats_unix.go @@ -7,7 +7,7 @@ //go:build !solaris && !windows // +build !solaris,!windows -package main +package syncthing_main import ( "fmt" diff --git a/cmd/syncthing/perfstats_unsupported.go b/cmd/syncthing/perfstats_unsupported.go index 2b7c07ba7..43aee8f69 100644 --- a/cmd/syncthing/perfstats_unsupported.go +++ b/cmd/syncthing/perfstats_unsupported.go @@ -7,7 +7,7 @@ //go:build solaris || windows // +build solaris windows -package main +package syncthing_main func startPerfStats() { } diff --git a/cmd/syncthing/traceback.go b/cmd/syncthing/traceback.go index 746bb2d87..6355e21c3 100644 --- a/cmd/syncthing/traceback.go +++ b/cmd/syncthing/traceback.go @@ -7,7 +7,7 @@ //go:build go1.7 // +build go1.7 -package main +package syncthing_main import "runtime/debug"