forked from syncthing/syncthing
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add basic C bindings to start and stop Syncthing and invoke its CLI
- Loading branch information
Showing
5 changed files
with
292 additions
and
4 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,253 @@ | ||
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/fs" | ||
"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/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() { | ||
cliArgs = []string{"syncthing", "cli"} | ||
} | ||
|
||
//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 | ||
} | ||
|
||
// C&P from main.go; used to ensure that the config directory exists | ||
func ensureDir(dir string, mode fs.FileMode) error { | ||
fs := fs.NewFilesystem(fs.FilesystemTypeBasic, dir) | ||
err := fs.MkdirAll(".", mode) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
if fi, err := fs.Stat("."); err == nil { | ||
// Apprently the stat may fail even though the mkdirall passed. If it | ||
// does, we'll just assume things are in order and let other things | ||
// fail (like loading or creating the config...). | ||
currentMode := fi.Mode() & 0777 | ||
if currentMode != mode { | ||
err := fs.Chmod(".", mode) | ||
// This can fail on crappy filesystems, nothing we can do about it. | ||
if err != nil { | ||
l.Warnln(err) | ||
} | ||
} | ||
} | ||
return nil | ||
} | ||
|
||
//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 := 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 := 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{ | ||
AssetDir: os.Getenv("STGUIASSETS"), | ||
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" | ||
} | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
#ifndef LIBSYNCTHING_INTERNAL_H | ||
#define LIBSYNCTHING_INTERNAL_H | ||
|
||
#include <stddef.h> | ||
|
||
// 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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
../cmd/syncthing/debug.go |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters