AdGuardHome/internal/home/options.go

328 lines
8.5 KiB
Go
Raw Normal View History

package home
import (
"fmt"
"os"
"strconv"
"github.com/AdguardTeam/AdGuardHome/internal/version"
)
// options passed from command-line arguments
type options struct {
verbose bool // is verbose logging enabled
configFilename string // path to the config file
workDir string // path to the working directory where we will store the filters data and the querylog
bindHost string // host address to bind HTTP server on
bindPort int // port to serve HTTP pages on
logFile string // Path to the log file. If empty, write to stdout. If "syslog", writes to syslog
pidFile string // File name to save PID to
checkConfig bool // Check configuration and exit
disableUpdate bool // If set, don't check for updates
// service control action (see service.ControlAction array + "status" command)
serviceControlAction string
// runningAsService flag is set to true when options are passed from the service runner
runningAsService bool
// disableMemoryOptimization - disables memory optimization hacks
// see memoryUsage() function for the details
disableMemoryOptimization bool
glinetMode bool // Activate GL-Inet compatibility mode
}
// functions used for their side-effects
type effect func() error
type arg struct {
description string // a short, English description of the argument
longName string // the name of the argument used after '--'
shortName string // the name of the argument used after '-'
// only one of updateWithValue, updateNoValue, and effect should be present
updateWithValue func(o options, v string) (options, error) // the mutator for arguments with parameters
updateNoValue func(o options) (options, error) // the mutator for arguments without parameters
effect func(o options, exec string) (f effect, err error) // the side-effect closure generator
serialize func(o options) []string // the re-serialization function back to arguments (return nil for omit)
}
// {type}SliceOrNil functions check their parameter of type {type}
// against its zero value and return nil if the parameter value is
// zero otherwise they return a string slice of the parameter
func stringSliceOrNil(s string) []string {
if s == "" {
return nil
}
return []string{s}
}
func intSliceOrNil(i int) []string {
if i == 0 {
return nil
}
return []string{strconv.Itoa(i)}
}
func boolSliceOrNil(b bool) []string {
if b {
return []string{}
}
return nil
}
var args []arg
var configArg = arg{
"Path to the config file",
"config", "c",
func(o options, v string) (options, error) { o.configFilename = v; return o, nil },
nil,
nil,
func(o options) []string { return stringSliceOrNil(o.configFilename) },
}
var workDirArg = arg{
"Path to the working directory",
"work-dir", "w",
func(o options, v string) (options, error) { o.workDir = v; return o, nil }, nil, nil,
func(o options) []string { return stringSliceOrNil(o.workDir) },
}
var hostArg = arg{
"Host address to bind HTTP server on",
"host", "h",
func(o options, v string) (options, error) { o.bindHost = v; return o, nil }, nil, nil,
func(o options) []string { return stringSliceOrNil(o.bindHost) },
}
var portArg = arg{
"Port to serve HTTP pages on",
"port", "p",
func(o options, v string) (options, error) {
var err error
var p int
minPort, maxPort := 0, 1<<16-1
if p, err = strconv.Atoi(v); err != nil {
Pull request:* all: remove github.com/joomcode/errorx dependency Merge in DNS/adguard-home from 2240-removing-errorx-dependency to master Squashed commit of the following: commit 5bbe0567356f06e3b9ee5b3dc38d357b472cacb1 Merge: a6040850d 02d16a0b4 Author: Eugene Burkov <e.burkov@adguard.com> Date: Thu Nov 5 14:32:22 2020 +0300 Merge branch 'master' into 2240-removing-errorx-dependency commit a6040850da3cefb131208097477b0956e80063fb Author: Eugene Burkov <e.burkov@adguard.com> Date: Thu Nov 5 14:23:36 2020 +0300 * dhcpd: convert some abbreviations to lowercase. commit d05bd51b994906b0ff52c5a8e779bd1f512f4bb7 Author: Eugene Burkov <e.burkov@adguard.com> Date: Thu Nov 5 12:47:20 2020 +0300 * agherr: last final fixes commit 164bca55035ff44e50b0abb33e129a0d24ffe87c Author: Ainar Garipov <A.Garipov@AdGuard.COM> Date: Tue Nov 3 19:11:10 2020 +0300 * all: final fixes again commit a0ac26f409c0b28a176cf2861d52c2f471b59484 Author: Ainar Garipov <A.Garipov@AdGuard.COM> Date: Tue Nov 3 18:51:39 2020 +0300 * all: final fixes commit 6147b02d402b513323b07e85856b348884f3a088 Merge: 9fd3af1a3 62cc334f4 Author: Eugene Burkov <e.burkov@adguard.com> Date: Tue Nov 3 18:26:03 2020 +0300 Merge branch 'master' into 2240-removing-errorx-dependency commit 9fd3af1a39a3189b5c41315a8ad1568ae5cb4fc9 Author: Eugene Burkov <e.burkov@adguard.com> Date: Tue Nov 3 18:23:08 2020 +0300 * all: remove useless helper commit 7cd9aeae639762b28b25f354d69c5cf74f670211 Author: Eugene Burkov <e.burkov@adguard.com> Date: Tue Nov 3 17:19:26 2020 +0300 * agherr: improved code tidiness commit a74a49236e9aaace070646dac710de9201105262 Merge: dc9dedbf2 df34ee5c0 Author: Eugene Burkov <e.burkov@adguard.com> Date: Tue Nov 3 16:54:29 2020 +0300 Merge branch 'master' into 2240-removing-errorx-dependency commit dc9dedbf205756e3adaa3bc776d349bf3d8c69a5 Author: Eugene Burkov <e.burkov@adguard.com> Date: Tue Nov 3 16:40:08 2020 +0300 * agherr: improve and cover by tests commit fd6bfe9e282156cc60e006cb7cd46cce4d3a07a8 Author: Eugene Burkov <e.burkov@adguard.com> Date: Tue Nov 3 14:06:27 2020 +0300 * all: improve code quality commit ea00c2f8c5060e9611f9a80cfd0e4a039526d0c4 Author: Eugene Burkov <e.burkov@adguard.com> Date: Tue Nov 3 13:03:57 2020 +0300 * all: fix linter style warnings commit 8e75e1a681a7218c2b4c69adfa2b7e1e2966f9ac Author: Eugene Burkov <e.burkov@adguard.com> Date: Tue Nov 3 12:29:26 2020 +0300 * all: remove github.com/joomcode/errorx dependency Closes #2240.
2020-11-05 12:20:57 +00:00
err = fmt.Errorf("port %q is not a number", v)
} else if p < minPort || p > maxPort {
err = fmt.Errorf("port %d not in range %d - %d", p, minPort, maxPort)
} else {
o.bindPort = p
}
return o, err
}, nil, nil,
func(o options) []string { return intSliceOrNil(o.bindPort) },
}
var serviceArg = arg{
"Service control action: status, install, uninstall, start, stop, restart, reload (configuration)",
"service", "s",
func(o options, v string) (options, error) {
o.serviceControlAction = v
return o, nil
}, nil, nil,
func(o options) []string { return stringSliceOrNil(o.serviceControlAction) },
}
var logfileArg = arg{
"Path to log file. If empty: write to stdout; if 'syslog': write to system log",
"logfile", "l",
func(o options, v string) (options, error) { o.logFile = v; return o, nil }, nil, nil,
func(o options) []string { return stringSliceOrNil(o.logFile) },
}
var pidfileArg = arg{
"Path to a file where PID is stored",
"pidfile", "",
func(o options, v string) (options, error) { o.pidFile = v; return o, nil }, nil, nil,
func(o options) []string { return stringSliceOrNil(o.pidFile) },
}
var checkConfigArg = arg{
"Check configuration and exit",
"check-config", "",
nil, func(o options) (options, error) { o.checkConfig = true; return o, nil }, nil,
func(o options) []string { return boolSliceOrNil(o.checkConfig) },
}
var noCheckUpdateArg = arg{
"Don't check for updates",
"no-check-update", "",
nil, func(o options) (options, error) { o.disableUpdate = true; return o, nil }, nil,
func(o options) []string { return boolSliceOrNil(o.disableUpdate) },
}
var disableMemoryOptimizationArg = arg{
"Disable memory optimization",
"no-mem-optimization", "",
nil, func(o options) (options, error) { o.disableMemoryOptimization = true; return o, nil }, nil,
func(o options) []string { return boolSliceOrNil(o.disableMemoryOptimization) },
}
var verboseArg = arg{
"Enable verbose output",
"verbose", "v",
nil, func(o options) (options, error) { o.verbose = true; return o, nil }, nil,
func(o options) []string { return boolSliceOrNil(o.verbose) },
}
var glinetArg = arg{
"Run in GL-Inet compatibility mode",
"glinet", "",
nil, func(o options) (options, error) { o.glinetMode = true; return o, nil }, nil,
func(o options) []string { return boolSliceOrNil(o.glinetMode) },
}
var versionArg = arg{
"Show the version and exit",
"version", "",
nil, nil, func(o options, exec string) (effect, error) {
return func() error { fmt.Println(version.Full()); os.Exit(0); return nil }, nil
},
func(o options) []string { return nil },
}
var helpArg = arg{
"Print this help",
"help", "",
nil, nil, func(o options, exec string) (effect, error) {
return func() error { _ = printHelp(exec); os.Exit(64); return nil }, nil
},
func(o options) []string { return nil },
}
func init() {
args = []arg{
configArg,
workDirArg,
hostArg,
portArg,
serviceArg,
logfileArg,
pidfileArg,
checkConfigArg,
noCheckUpdateArg,
disableMemoryOptimizationArg,
verboseArg,
glinetArg,
versionArg,
helpArg,
}
}
func getUsageLines(exec string, args []arg) []string {
usage := []string{
"Usage:",
"",
fmt.Sprintf("%s [options]", exec),
"",
"Options:",
}
for _, arg := range args {
val := ""
if arg.updateWithValue != nil {
val = " VALUE"
}
if arg.shortName != "" {
usage = append(usage, fmt.Sprintf(" -%s, %-30s %s",
arg.shortName,
"--"+arg.longName+val,
arg.description))
} else {
usage = append(usage, fmt.Sprintf(" %-34s %s",
"--"+arg.longName+val,
arg.description))
}
}
return usage
}
func printHelp(exec string) error {
for _, line := range getUsageLines(exec, args) {
_, err := fmt.Println(line)
if err != nil {
return err
}
}
return nil
}
func argMatches(a arg, v string) bool {
return v == "--"+a.longName || (a.shortName != "" && v == "-"+a.shortName)
}
func parse(exec string, ss []string) (o options, f effect, err error) {
for i := 0; i < len(ss); i++ {
v := ss[i]
knownParam := false
for _, arg := range args {
if argMatches(arg, v) {
if arg.updateWithValue != nil {
if i+1 >= len(ss) {
return o, f, fmt.Errorf("got %s without argument", v)
}
i++
o, err = arg.updateWithValue(o, ss[i])
if err != nil {
return
}
} else if arg.updateNoValue != nil {
o, err = arg.updateNoValue(o)
if err != nil {
return
}
} else if arg.effect != nil {
var eff effect
eff, err = arg.effect(o, exec)
if err != nil {
return
}
if eff != nil {
prevf := f
f = func() error {
var err error
if prevf != nil {
err = prevf()
}
if err == nil {
err = eff()
}
return err
}
}
}
knownParam = true
break
}
}
if !knownParam {
return o, f, fmt.Errorf("unknown option %v", v)
}
}
return
}
func shortestFlag(a arg) string {
if a.shortName != "" {
return "-" + a.shortName
}
return "--" + a.longName
}
func serialize(o options) []string {
ss := []string{}
for _, arg := range args {
s := arg.serialize(o)
if s != nil {
ss = append(ss, append([]string{shortestFlag(arg)}, s...)...)
}
}
return ss
}