github.com/rclone/rclone@v1.66.1-0.20240517100346-7b89735ae726/fs/rc/internal.go (about)

     1  // Define the internal rc functions
     2  
     3  package rc
     4  
     5  import (
     6  	"context"
     7  	"fmt"
     8  	"net/http"
     9  	"os"
    10  	"os/exec"
    11  	"runtime"
    12  	"strings"
    13  	"time"
    14  
    15  	"github.com/coreos/go-semver/semver"
    16  
    17  	"github.com/rclone/rclone/fs"
    18  	"github.com/rclone/rclone/fs/config/obscure"
    19  	"github.com/rclone/rclone/lib/atexit"
    20  	"github.com/rclone/rclone/lib/buildinfo"
    21  	"github.com/rclone/rclone/lib/debug"
    22  )
    23  
    24  func init() {
    25  	Add(Call{
    26  		Path:         "rc/noopauth",
    27  		AuthRequired: true,
    28  		Fn:           rcNoop,
    29  		Title:        "Echo the input to the output parameters requiring auth",
    30  		Help: `
    31  This echoes the input parameters to the output parameters for testing
    32  purposes.  It can be used to check that rclone is still alive and to
    33  check that parameter passing is working properly.`,
    34  	})
    35  	Add(Call{
    36  		Path:  "rc/noop",
    37  		Fn:    rcNoop,
    38  		Title: "Echo the input to the output parameters",
    39  		Help: `
    40  This echoes the input parameters to the output parameters for testing
    41  purposes.  It can be used to check that rclone is still alive and to
    42  check that parameter passing is working properly.`,
    43  	})
    44  }
    45  
    46  // Echo the input to the output parameters
    47  func rcNoop(ctx context.Context, in Params) (out Params, err error) {
    48  	return in, nil
    49  }
    50  
    51  func init() {
    52  	Add(Call{
    53  		Path:  "rc/error",
    54  		Fn:    rcError,
    55  		Title: "This returns an error",
    56  		Help: `
    57  This returns an error with the input as part of its error string.
    58  Useful for testing error handling.`,
    59  	})
    60  }
    61  
    62  // Return an error regardless
    63  func rcError(ctx context.Context, in Params) (out Params, err error) {
    64  	return nil, fmt.Errorf("arbitrary error on input %+v", in)
    65  }
    66  
    67  func init() {
    68  	Add(Call{
    69  		Path:  "rc/list",
    70  		Fn:    rcList,
    71  		Title: "List all the registered remote control commands",
    72  		Help: `
    73  This lists all the registered remote control commands as a JSON map in
    74  the commands response.`,
    75  	})
    76  }
    77  
    78  // List the registered commands
    79  func rcList(ctx context.Context, in Params) (out Params, err error) {
    80  	out = make(Params)
    81  	out["commands"] = Calls.List()
    82  	return out, nil
    83  }
    84  
    85  func init() {
    86  	Add(Call{
    87  		Path:  "core/pid",
    88  		Fn:    rcPid,
    89  		Title: "Return PID of current process",
    90  		Help: `
    91  This returns PID of current process.
    92  Useful for stopping rclone process.`,
    93  	})
    94  }
    95  
    96  // Return PID of current process
    97  func rcPid(ctx context.Context, in Params) (out Params, err error) {
    98  	out = make(Params)
    99  	out["pid"] = os.Getpid()
   100  	return out, nil
   101  }
   102  
   103  func init() {
   104  	Add(Call{
   105  		Path:  "core/memstats",
   106  		Fn:    rcMemStats,
   107  		Title: "Returns the memory statistics",
   108  		Help: `
   109  This returns the memory statistics of the running program.  What the values mean
   110  are explained in the go docs: https://golang.org/pkg/runtime/#MemStats
   111  
   112  The most interesting values for most people are:
   113  
   114  - HeapAlloc - this is the amount of memory rclone is actually using
   115  - HeapSys - this is the amount of memory rclone has obtained from the OS
   116  - Sys - this is the total amount of memory requested from the OS
   117     - It is virtual memory so may include unused memory
   118  `,
   119  	})
   120  }
   121  
   122  // Return the memory statistics
   123  func rcMemStats(ctx context.Context, in Params) (out Params, err error) {
   124  	out = make(Params)
   125  	var m runtime.MemStats
   126  	runtime.ReadMemStats(&m)
   127  	out["Alloc"] = m.Alloc
   128  	out["TotalAlloc"] = m.TotalAlloc
   129  	out["Sys"] = m.Sys
   130  	out["Mallocs"] = m.Mallocs
   131  	out["Frees"] = m.Frees
   132  	out["HeapAlloc"] = m.HeapAlloc
   133  	out["HeapSys"] = m.HeapSys
   134  	out["HeapIdle"] = m.HeapIdle
   135  	out["HeapInuse"] = m.HeapInuse
   136  	out["HeapReleased"] = m.HeapReleased
   137  	out["HeapObjects"] = m.HeapObjects
   138  	out["StackInuse"] = m.StackInuse
   139  	out["StackSys"] = m.StackSys
   140  	out["MSpanInuse"] = m.MSpanInuse
   141  	out["MSpanSys"] = m.MSpanSys
   142  	out["MCacheInuse"] = m.MCacheInuse
   143  	out["MCacheSys"] = m.MCacheSys
   144  	out["BuckHashSys"] = m.BuckHashSys
   145  	out["GCSys"] = m.GCSys
   146  	out["OtherSys"] = m.OtherSys
   147  	return out, nil
   148  }
   149  
   150  func init() {
   151  	Add(Call{
   152  		Path:  "core/gc",
   153  		Fn:    rcGc,
   154  		Title: "Runs a garbage collection.",
   155  		Help: `
   156  This tells the go runtime to do a garbage collection run.  It isn't
   157  necessary to call this normally, but it can be useful for debugging
   158  memory problems.
   159  `,
   160  	})
   161  }
   162  
   163  // Do a garbage collection run
   164  func rcGc(ctx context.Context, in Params) (out Params, err error) {
   165  	runtime.GC()
   166  	return nil, nil
   167  }
   168  
   169  func init() {
   170  	Add(Call{
   171  		Path:  "core/version",
   172  		Fn:    rcVersion,
   173  		Title: "Shows the current version of rclone and the go runtime.",
   174  		Help: `
   175  This shows the current version of go and the go runtime:
   176  
   177  - version - rclone version, e.g. "v1.53.0"
   178  - decomposed - version number as [major, minor, patch]
   179  - isGit - boolean - true if this was compiled from the git version
   180  - isBeta - boolean - true if this is a beta version
   181  - os - OS in use as according to Go
   182  - arch - cpu architecture in use according to Go
   183  - goVersion - version of Go runtime in use
   184  - linking - type of rclone executable (static or dynamic)
   185  - goTags - space separated build tags or "none"
   186  
   187  `,
   188  	})
   189  }
   190  
   191  // Return version info
   192  func rcVersion(ctx context.Context, in Params) (out Params, err error) {
   193  	version, err := semver.NewVersion(fs.Version[1:])
   194  	if err != nil {
   195  		return nil, err
   196  	}
   197  	linking, tagString := buildinfo.GetLinkingAndTags()
   198  	out = Params{
   199  		"version":    fs.Version,
   200  		"decomposed": version.Slice(),
   201  		"isGit":      strings.HasSuffix(fs.Version, "-DEV"),
   202  		"isBeta":     version.PreRelease != "",
   203  		"os":         runtime.GOOS,
   204  		"arch":       runtime.GOARCH,
   205  		"goVersion":  runtime.Version(),
   206  		"linking":    linking,
   207  		"goTags":     tagString,
   208  	}
   209  	return out, nil
   210  }
   211  
   212  func init() {
   213  	Add(Call{
   214  		Path:  "core/obscure",
   215  		Fn:    rcObscure,
   216  		Title: "Obscures a string passed in.",
   217  		Help: `
   218  Pass a clear string and rclone will obscure it for the config file:
   219  - clear - string
   220  
   221  Returns:
   222  - obscured - string
   223  `,
   224  	})
   225  }
   226  
   227  // Return obscured string
   228  func rcObscure(ctx context.Context, in Params) (out Params, err error) {
   229  	clear, err := in.GetString("clear")
   230  	if err != nil {
   231  		return nil, err
   232  	}
   233  	obscured, err := obscure.Obscure(clear)
   234  	if err != nil {
   235  		return nil, err
   236  	}
   237  	out = Params{
   238  		"obscured": obscured,
   239  	}
   240  	return out, nil
   241  }
   242  
   243  func init() {
   244  	Add(Call{
   245  		Path:  "core/quit",
   246  		Fn:    rcQuit,
   247  		Title: "Terminates the app.",
   248  		Help: `
   249  (Optional) Pass an exit code to be used for terminating the app:
   250  - exitCode - int
   251  `,
   252  	})
   253  }
   254  
   255  // Terminates app
   256  func rcQuit(ctx context.Context, in Params) (out Params, err error) {
   257  	code, err := in.GetInt64("exitCode")
   258  
   259  	if IsErrParamInvalid(err) {
   260  		return nil, err
   261  	}
   262  	if IsErrParamNotFound(err) {
   263  		code = 0
   264  	}
   265  	exitCode := int(code)
   266  
   267  	go func(exitCode int) {
   268  		time.Sleep(time.Millisecond * 1500)
   269  		atexit.Run()
   270  		os.Exit(exitCode)
   271  	}(exitCode)
   272  
   273  	return nil, nil
   274  }
   275  
   276  func init() {
   277  	Add(Call{
   278  		Path:  "debug/set-mutex-profile-fraction",
   279  		Fn:    rcSetMutexProfileFraction,
   280  		Title: "Set runtime.SetMutexProfileFraction for mutex profiling.",
   281  		Help: `
   282  SetMutexProfileFraction controls the fraction of mutex contention
   283  events that are reported in the mutex profile. On average 1/rate
   284  events are reported. The previous rate is returned.
   285  
   286  To turn off profiling entirely, pass rate 0. To just read the current
   287  rate, pass rate < 0. (For n>1 the details of sampling may change.)
   288  
   289  Once this is set you can look use this to profile the mutex contention:
   290  
   291      go tool pprof http://localhost:5572/debug/pprof/mutex
   292  
   293  Parameters:
   294  
   295  - rate - int
   296  
   297  Results:
   298  
   299  - previousRate - int
   300  `,
   301  	})
   302  }
   303  
   304  func rcSetMutexProfileFraction(ctx context.Context, in Params) (out Params, err error) {
   305  	rate, err := in.GetInt64("rate")
   306  	if err != nil {
   307  		return nil, err
   308  	}
   309  	previousRate := runtime.SetMutexProfileFraction(int(rate))
   310  	out = make(Params)
   311  	out["previousRate"] = previousRate
   312  	return out, nil
   313  }
   314  
   315  func init() {
   316  	Add(Call{
   317  		Path:  "debug/set-block-profile-rate",
   318  		Fn:    rcSetBlockProfileRate,
   319  		Title: "Set runtime.SetBlockProfileRate for blocking profiling.",
   320  		Help: `
   321  SetBlockProfileRate controls the fraction of goroutine blocking events
   322  that are reported in the blocking profile. The profiler aims to sample
   323  an average of one blocking event per rate nanoseconds spent blocked.
   324  
   325  To include every blocking event in the profile, pass rate = 1. To turn
   326  off profiling entirely, pass rate <= 0.
   327  
   328  After calling this you can use this to see the blocking profile:
   329  
   330      go tool pprof http://localhost:5572/debug/pprof/block
   331  
   332  Parameters:
   333  
   334  - rate - int
   335  `,
   336  	})
   337  }
   338  
   339  func rcSetBlockProfileRate(ctx context.Context, in Params) (out Params, err error) {
   340  	rate, err := in.GetInt64("rate")
   341  	if err != nil {
   342  		return nil, err
   343  	}
   344  	runtime.SetBlockProfileRate(int(rate))
   345  	return nil, nil
   346  }
   347  
   348  func init() {
   349  	Add(Call{
   350  		Path:  "debug/set-soft-memory-limit",
   351  		Fn:    rcSetSoftMemoryLimit,
   352  		Title: "Call runtime/debug.SetMemoryLimit for setting a soft memory limit for the runtime.",
   353  		Help: `
   354  SetMemoryLimit provides the runtime with a soft memory limit.
   355  
   356  The runtime undertakes several processes to try to respect this memory limit, including
   357  adjustments to the frequency of garbage collections and returning memory to the underlying
   358  system more aggressively. This limit will be respected even if GOGC=off (or, if SetGCPercent(-1) is executed).
   359  
   360  The input limit is provided as bytes, and includes all memory mapped, managed, and not
   361  released by the Go runtime. Notably, it does not account for space used by the Go binary
   362  and memory external to Go, such as memory managed by the underlying system on behalf of
   363  the process, or memory managed by non-Go code inside the same process.
   364  Examples of excluded memory sources include: OS kernel memory held on behalf of the process,
   365  memory allocated by C code, and memory mapped by syscall.Mmap (because it is not managed by the Go runtime).
   366  
   367  A zero limit or a limit that's lower than the amount of memory used by the Go runtime may cause
   368  the garbage collector to run nearly continuously. However, the application may still make progress.
   369  
   370  The memory limit is always respected by the Go runtime, so to effectively disable this behavior,
   371  set the limit very high. math.MaxInt64 is the canonical value for disabling the limit, but values
   372  much greater than the available memory on the underlying system work just as well.
   373  
   374  See https://go.dev/doc/gc-guide for a detailed guide explaining the soft memory limit in more detail,
   375  as well as a variety of common use-cases and scenarios.
   376  
   377  SetMemoryLimit returns the previously set memory limit. A negative input does not adjust the limit,
   378  and allows for retrieval of the currently set memory limit.
   379  
   380  Parameters:
   381  
   382  - mem-limit - int
   383  `,
   384  	})
   385  }
   386  
   387  func rcSetSoftMemoryLimit(ctx context.Context, in Params) (out Params, err error) {
   388  	memLimit, err := in.GetInt64("mem-limit")
   389  	if err != nil {
   390  		return nil, err
   391  	}
   392  	oldMemLimit := debug.SetMemoryLimit(memLimit)
   393  	out = Params{
   394  		"existing-mem-limit": oldMemLimit,
   395  	}
   396  	return out, nil
   397  }
   398  
   399  func init() {
   400  	Add(Call{
   401  		Path:  "debug/set-gc-percent",
   402  		Fn:    rcSetGCPercent,
   403  		Title: "Call runtime/debug.SetGCPercent for setting the garbage collection target percentage.",
   404  		Help: `
   405  SetGCPercent sets the garbage collection target percentage: a collection is triggered
   406  when the ratio of freshly allocated data to live data remaining after the previous collection
   407  reaches this percentage. SetGCPercent returns the previous setting. The initial setting is the
   408  value of the GOGC environment variable at startup, or 100 if the variable is not set.
   409  
   410  This setting may be effectively reduced in order to maintain a memory limit.
   411  A negative percentage effectively disables garbage collection, unless the memory limit is reached.
   412  
   413  See https://pkg.go.dev/runtime/debug#SetMemoryLimit for more details.
   414  
   415  Parameters:
   416  
   417  - gc-percent - int
   418  `,
   419  	})
   420  }
   421  
   422  func rcSetGCPercent(ctx context.Context, in Params) (out Params, err error) {
   423  	gcPercent, err := in.GetInt64("gc-percent")
   424  	if err != nil {
   425  		return nil, err
   426  	}
   427  	oldGCPercent := debug.SetGCPercent(int(gcPercent))
   428  	out = Params{
   429  		"existing-gc-percent": oldGCPercent,
   430  	}
   431  	return out, nil
   432  }
   433  
   434  func init() {
   435  	Add(Call{
   436  		Path:          "core/command",
   437  		AuthRequired:  true,
   438  		Fn:            rcRunCommand,
   439  		NeedsRequest:  true,
   440  		NeedsResponse: true,
   441  		Title:         "Run a rclone terminal command over rc.",
   442  		Help: `This takes the following parameters:
   443  
   444  - command - a string with the command name.
   445  - arg - a list of arguments for the backend command.
   446  - opt - a map of string to string of options.
   447  - returnType - one of ("COMBINED_OUTPUT", "STREAM", "STREAM_ONLY_STDOUT", "STREAM_ONLY_STDERR").
   448      - Defaults to "COMBINED_OUTPUT" if not set.
   449      - The STREAM returnTypes will write the output to the body of the HTTP message.
   450      - The COMBINED_OUTPUT will write the output to the "result" parameter.
   451  
   452  Returns:
   453  
   454  - result - result from the backend command.
   455      - Only set when using returnType "COMBINED_OUTPUT".
   456  - error	 - set if rclone exits with an error code.
   457  - returnType - one of ("COMBINED_OUTPUT", "STREAM", "STREAM_ONLY_STDOUT", "STREAM_ONLY_STDERR").
   458  
   459  Example:
   460  
   461      rclone rc core/command command=ls -a mydrive:/ -o max-depth=1
   462      rclone rc core/command -a ls -a mydrive:/ -o max-depth=1
   463  
   464  Returns:
   465  
   466  ` + "```" + `
   467  {
   468  	"error": false,
   469  	"result": "<Raw command line output>"
   470  }
   471  
   472  OR
   473  {
   474  	"error": true,
   475  	"result": "<Raw command line output>"
   476  }
   477  
   478  ` + "```" + `
   479  `,
   480  	})
   481  }
   482  
   483  // rcRunCommand runs an rclone command with the given args and flags
   484  func rcRunCommand(ctx context.Context, in Params) (out Params, err error) {
   485  	command, err := in.GetString("command")
   486  	if err != nil {
   487  		command = ""
   488  	}
   489  
   490  	var opt = map[string]string{}
   491  	err = in.GetStructMissingOK("opt", &opt)
   492  	if err != nil {
   493  		return nil, err
   494  	}
   495  
   496  	var arg = []string{}
   497  	err = in.GetStructMissingOK("arg", &arg)
   498  	if err != nil {
   499  		return nil, err
   500  	}
   501  
   502  	returnType, err := in.GetString("returnType")
   503  	if err != nil {
   504  		returnType = "COMBINED_OUTPUT"
   505  	}
   506  
   507  	var httpResponse http.ResponseWriter
   508  	httpResponse, err = in.GetHTTPResponseWriter()
   509  	if err != nil {
   510  		return nil, fmt.Errorf("response object is required\n" + err.Error())
   511  	}
   512  
   513  	var allArgs = []string{}
   514  	if command != "" {
   515  		// Add the command e.g.: ls to the args
   516  		allArgs = append(allArgs, command)
   517  	}
   518  	// Add all from arg
   519  	allArgs = append(allArgs, arg...)
   520  
   521  	// Add flags to args for e.g. --max-depth 1 comes in as { max-depth 1 }.
   522  	// Convert it to [ max-depth, 1 ] and append to args list
   523  	for key, value := range opt {
   524  		if len(key) == 1 {
   525  			allArgs = append(allArgs, "-"+key)
   526  		} else {
   527  			allArgs = append(allArgs, "--"+key)
   528  		}
   529  		allArgs = append(allArgs, value)
   530  	}
   531  
   532  	// Get the path for the current executable which was used to run rclone.
   533  	ex, err := os.Executable()
   534  	if err != nil {
   535  		return nil, err
   536  	}
   537  
   538  	cmd := exec.CommandContext(ctx, ex, allArgs...)
   539  
   540  	if returnType == "COMBINED_OUTPUT" {
   541  		// Run the command and get the output for error and stdout combined.
   542  
   543  		out, err := cmd.CombinedOutput()
   544  
   545  		if err != nil {
   546  			return Params{
   547  				"result": string(out),
   548  				"error":  true,
   549  			}, nil
   550  		}
   551  		return Params{
   552  			"result": string(out),
   553  			"error":  false,
   554  		}, nil
   555  	} else if returnType == "STREAM_ONLY_STDOUT" {
   556  		cmd.Stdout = httpResponse
   557  	} else if returnType == "STREAM_ONLY_STDERR" {
   558  		cmd.Stderr = httpResponse
   559  	} else if returnType == "STREAM" {
   560  		cmd.Stdout = httpResponse
   561  		cmd.Stderr = httpResponse
   562  	} else {
   563  		return nil, fmt.Errorf("unknown returnType %q", returnType)
   564  	}
   565  
   566  	err = cmd.Run()
   567  	return nil, err
   568  }