github.com/cs3org/reva/v2@v2.27.7/cmd/revad/main.go (about)

     1  // Copyright 2018-2021 CERN
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  //
    15  // In applying this license, CERN does not waive the privileges and immunities
    16  // granted to it by virtue of its status as an Intergovernmental Organization
    17  // or submit itself to any jurisdiction.
    18  
    19  package main
    20  
    21  import (
    22  	"flag"
    23  	"fmt"
    24  	"os"
    25  	"path"
    26  	"regexp"
    27  	"sync"
    28  	"syscall"
    29  
    30  	"github.com/cs3org/reva/v2/cmd/revad/internal/config"
    31  	"github.com/cs3org/reva/v2/cmd/revad/internal/grace"
    32  	"github.com/cs3org/reva/v2/cmd/revad/runtime"
    33  	"github.com/cs3org/reva/v2/pkg/sysinfo"
    34  
    35  	"github.com/google/uuid"
    36  )
    37  
    38  var (
    39  	versionFlag = flag.Bool("version", false, "show version and exit")
    40  	testFlag    = flag.Bool("t", false, "test configuration and exit")
    41  	signalFlag  = flag.String("s", "", "send signal to a master process: stop, quit, reload")
    42  	configFlag  = flag.String("c", "/etc/revad/revad.toml", "set configuration file")
    43  	pidFlag     = flag.String("p", "", "pid file. If empty defaults to a random file in the OS temporary directory")
    44  	logFlag     = flag.String("log", "", "log messages with the given severity or above. One of: [trace, debug, info, warn, error, fatal, panic]")
    45  	dirFlag     = flag.String("dev-dir", "", "runs any toml file in the specified directory. Intended for development use only")
    46  
    47  	// Compile time variables initialized with gcc flags.
    48  	gitCommit, buildDate, version, goVersion string
    49  )
    50  
    51  func main() {
    52  	flag.Parse()
    53  
    54  	// initialize the global system information
    55  	if err := sysinfo.InitSystemInfo(&sysinfo.RevaVersion{Version: version, BuildDate: buildDate, GitCommit: gitCommit, GoVersion: goVersion}); err != nil {
    56  		fmt.Fprintf(os.Stderr, "error initializing system info: %s\n", err.Error())
    57  		// This is not really a fatal error, so don't panic
    58  	}
    59  
    60  	handleVersionFlag()
    61  	handleSignalFlag()
    62  
    63  	confs, err := getConfigs()
    64  	if err != nil {
    65  		fmt.Fprintf(os.Stderr, "error reading the configuration file(s): %s\n", err.Error())
    66  		os.Exit(1)
    67  	}
    68  
    69  	// if there is more than one configuration available and
    70  	// the pid flag has been set we abort as the pid flag
    71  	// is meant to work only with one main configuration.
    72  	if len(confs) != 1 && *pidFlag != "" {
    73  		fmt.Fprintf(os.Stderr, "cannot run with with multiple configurations and one pid file, remote the -p flag\n")
    74  		os.Exit(1)
    75  	}
    76  
    77  	// if test flag is true we exit as this flag only tests for valid configurations.
    78  	if *testFlag {
    79  		os.Exit(0)
    80  	}
    81  
    82  	runConfigs(confs)
    83  }
    84  
    85  func handleVersionFlag() {
    86  	if *versionFlag {
    87  		fmt.Fprintf(os.Stderr, "%s\n", getVersionString())
    88  		os.Exit(0)
    89  	}
    90  }
    91  
    92  func getVersionString() string {
    93  	msg := "version=%s "
    94  	msg += "commit=%s "
    95  	msg += "go_version=%s "
    96  	msg += "build_date=%s"
    97  
    98  	return fmt.Sprintf(msg, version, gitCommit, goVersion, buildDate)
    99  }
   100  
   101  func handleSignalFlag() {
   102  	if *signalFlag != "" {
   103  		var signal syscall.Signal
   104  		switch *signalFlag {
   105  		case "reload":
   106  			signal = syscall.SIGHUP
   107  		case "quit":
   108  			signal = syscall.SIGQUIT
   109  		case "stop":
   110  			signal = syscall.SIGTERM
   111  		default:
   112  			fmt.Fprintf(os.Stderr, "unknown signal %q\n", *signalFlag)
   113  			os.Exit(1)
   114  		}
   115  
   116  		// check that we have a valid pidfile
   117  		if *pidFlag == "" {
   118  			fmt.Fprintf(os.Stderr, "-s flag not set, no clue where the pidfile is stored. Check the logs for its location.\n")
   119  			os.Exit(1)
   120  		}
   121  		process, err := grace.GetProcessFromFile(*pidFlag)
   122  		if err != nil {
   123  			fmt.Fprintf(os.Stderr, "error getting process from pidfile: %v\n", err)
   124  			os.Exit(1)
   125  		}
   126  
   127  		// kill process with signal
   128  		if err := process.Signal(signal); err != nil {
   129  			fmt.Fprintf(os.Stderr, "error signaling process %d with signal %s\n", process.Pid, signal)
   130  			os.Exit(1)
   131  		}
   132  
   133  		os.Exit(0)
   134  	}
   135  }
   136  
   137  func getConfigs() ([]map[string]interface{}, error) {
   138  	var confs []string
   139  	// give priority to read from dev-dir
   140  	if *dirFlag != "" {
   141  		cfgs, err := getConfigsFromDir(*dirFlag)
   142  		if err != nil {
   143  			return nil, err
   144  		}
   145  		confs = append(confs, cfgs...)
   146  	} else {
   147  		confs = append(confs, *configFlag)
   148  	}
   149  
   150  	// if we don't have a config file we abort
   151  	if len(confs) == 0 {
   152  		fmt.Fprintf(os.Stderr, "no configuration found\n")
   153  		os.Exit(1)
   154  	}
   155  
   156  	configs, err := readConfigs(confs)
   157  	if err != nil {
   158  		return nil, err
   159  	}
   160  
   161  	return configs, nil
   162  }
   163  
   164  func getConfigsFromDir(dir string) (confs []string, err error) {
   165  	files, err := os.ReadDir(*dirFlag)
   166  	if err != nil {
   167  		return nil, err
   168  	}
   169  
   170  	for _, value := range files {
   171  		if !value.IsDir() {
   172  			expr := regexp.MustCompile(`[\w].toml`)
   173  			if expr.Match([]byte(value.Name())) {
   174  				confs = append(confs, path.Join(dir, value.Name()))
   175  			}
   176  		}
   177  	}
   178  	return
   179  }
   180  
   181  func readConfigs(files []string) ([]map[string]interface{}, error) {
   182  	confs := make([]map[string]interface{}, 0, len(files))
   183  	for _, conf := range files {
   184  		fd, err := os.Open(conf)
   185  		if err != nil {
   186  			return nil, err
   187  		}
   188  		defer fd.Close()
   189  
   190  		v, err := config.Read(fd)
   191  		if err != nil {
   192  			return nil, err
   193  		}
   194  		confs = append(confs, v)
   195  	}
   196  	return confs, nil
   197  }
   198  
   199  func runConfigs(confs []map[string]interface{}) {
   200  	if len(confs) == 1 {
   201  		runSingle(confs[0])
   202  		return
   203  	}
   204  
   205  	runMultiple(confs)
   206  }
   207  
   208  func runSingle(conf map[string]interface{}) {
   209  	if *pidFlag == "" {
   210  		*pidFlag = getPidfile()
   211  	}
   212  
   213  	runtime.Run(conf, *pidFlag, *logFlag)
   214  }
   215  
   216  func getPidfile() string {
   217  	uuid := uuid.New().String()
   218  	name := fmt.Sprintf("revad-%s.pid", uuid)
   219  
   220  	return path.Join(os.TempDir(), name)
   221  }
   222  
   223  func runMultiple(confs []map[string]interface{}) {
   224  	var wg sync.WaitGroup
   225  	for _, conf := range confs {
   226  		wg.Add(1)
   227  		pidfile := getPidfile()
   228  		go func(wg *sync.WaitGroup, conf map[string]interface{}) {
   229  			defer wg.Done()
   230  			runtime.Run(conf, pidfile, *logFlag)
   231  		}(&wg, conf)
   232  	}
   233  	wg.Wait()
   234  	os.Exit(0)
   235  }