package home import ( "fmt" "os" "strconv" ) // 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 glinetMode bool // Activate GL-Inet 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 } 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, } 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, } 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, } 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 { err = fmt.Errorf("port '%s' 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, } 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, } 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, } 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, } var checkConfigArg = arg{ "Check configuration and exit", "check-config", "", nil, func(o options) (options, error) { o.checkConfig = true; return o, nil }, nil, } var noCheckUpdateArg = arg{ "Don't check for updates", "no-check-update", "", nil, func(o options) (options, error) { o.disableUpdate = true; return o, nil }, nil, } var verboseArg = arg{ "Enable verbose output", "verbose", "v", nil, func(o options) (options, error) { o.verbose = true; return o, nil }, nil, } var glinetArg = arg{ "Run in GL-Inet compatibility mode", "glinet", "", nil, func(o options) (options, error) { o.glinetMode = true; return o, nil }, nil, } var versionArg = arg{ "Show the version and exit", "version", "", nil, nil, func(o options, exec string) (effect, error) { return func() error { fmt.Println(version()); os.Exit(0); return nil }, 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 init() { args = []arg{ configArg, workDirArg, hostArg, portArg, serviceArg, logfileArg, pidfileArg, checkConfigArg, noCheckUpdateArg, 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 }