internal/util/util.go (146 lines of code) (raw):

// Copyright (c) 2016 Uber Technologies, Inc. // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. package util import ( "bufio" "io/ioutil" "os" "os/signal" "path" "strconv" "syscall" "time" "github.com/uber/arachne/internal/ip" "github.com/uber/arachne/internal/log" "github.com/uber/arachne/metrics" "github.com/fatih/color" "github.com/pkg/errors" "go.uber.org/zap" ) const bannerText = ` ____________________________________________________________/\\\______________________________________ ___________________________________________________________\/\\\______________________________________ ___________________________________________________________\/\\\______________________________________ __/\\\\\\\\\_____/\\/\\\\\\\___/\\\\\\\\\________/\\\\\\\\_\/\\\__________/\\/\\\\\\_______/\\\\\\\\__ _\////////\\\___\/\\\/////\\\_\////////\\\_____/\\\//////__\/\\\\\\\\\\__\/\\\////\\\____/\\\/////\\\_ ___/\\\\\\\\\\__\/\\\___\///____/\\\\\\\\\\___/\\\_________\/\\\/////\\\_\/\\\__\//\\\__/\\\\\\\\\\\__ __/\\\/////\\\__\/\\\__________/\\\/////\\\__\//\\\________\/\\\___\/\\\_\/\\\___\/\\\_\//\\///////___ _\//\\\\\\\\/\\_\/\\\_________\//\\\\\\\\/\\__\///\\\\\\\\_\/\\\___\/\\\_\/\\\___\/\\\__\//\\\\\\\\\\_ __\////////\//__\///___________\////////\//_____\////////__\///____\///__\///____\///____\//////////__ ` // KillChannels includes all channels to tell goroutines to terminate. type KillChannels struct { Receiver chan struct{} Echo chan struct{} Collector chan struct{} DNSRefresh chan struct{} } // PrintBanner prints the binary's banner. func PrintBanner() { color.Set(color.FgHiYellow, color.Bold) defer color.Unset() f := bufio.NewWriter(os.Stdout) defer f.Flush() f.Write([]byte(bannerText)) } // UnixSignals handles the UNIX signals received. func UnixSignals(sigC chan struct{}, logger *log.Logger) { // Set up channel on which to send signal notifications. // We must use a buffered channel or risk missing the signal // if we're not ready to receive when the signal is sent. sigc := make(chan os.Signal, 1) signal.Notify(sigc, syscall.SIGINT, syscall.SIGQUIT, syscall.SIGKILL, syscall.SIGTERM, syscall.SIGUSR1, syscall.SIGUSR2, syscall.SIGHUP, os.Interrupt) go func() { sigType := <-sigc switch sigType { //TODO Handle following cases case os.Interrupt: //handle SIGINT case syscall.SIGHUP: logger.Info("got Hangup/SIGHUP - portable number 1") case syscall.SIGINT: logger.Info("got Terminal interrupt signal/SIGINT - portable number 2") case syscall.SIGQUIT: logger.Fatal("got Terminal quit signal/SIGQUIT - portable number 3 - will core dump") case syscall.SIGABRT: logger.Fatal("got Process abort signal/SIGABRT - portable number 6 - will core dump") case syscall.SIGKILL: logger.Info("got Kill signal/SIGKILL - portable number 9") case syscall.SIGALRM: logger.Fatal("got Alarm clock signal/SIGALRM - portable number 14") case syscall.SIGTERM: logger.Info("got Termination signal/SIGTERM - portable number 15") case syscall.SIGUSR1: logger.Info("got User-defined signal 1/SIGUSR1") case syscall.SIGUSR2: logger.Info("got User-defined signal 2/SIGUSR2") default: logger.Fatal("unhandled Unix signal", zap.Any("sig_type", sigType)) } sigC <- struct{}{} return }() } // CheckPID checks if another Arachne process is already running. func CheckPID(fname string, logger *log.Logger) error { if _, err := os.Stat(fname); os.IsNotExist(err) { return savePID(fname, os.Getpid(), logger) } content, err := ioutil.ReadFile(fname) if err != nil { logger.Error("unable to read PID file", zap.String("file", fname), zap.Error(err)) return err } readPID, err := strconv.Atoi(string(content)) if err != nil { logger.Error("invalid content inside PID file", zap.String("file", fname), zap.Error(err)) return savePID(fname, os.Getpid(), logger) } // Sending the signal 0 to a given PID just checks if any process with the given PID is running // and you have the permission to send a signal to it. if err = syscall.Kill(readPID, 0); err == nil { logger.Error("Arachne already running and different from self PID", zap.Int("other_PID", readPID), zap.Int("self_PID", os.Getpid())) return errors.New("Arachne already running and different from self PID") } return savePID(fname, os.Getpid(), logger) } func savePID(fname string, pid int, logger *log.Logger) error { if err := os.MkdirAll(path.Dir(fname), 0777); err != nil { logger.Error("failed to create PID directory", zap.String("path", path.Dir(fname)), zap.Error(err)) return err } if err := ioutil.WriteFile(fname, []byte(strconv.Itoa(pid)), 0644); err != nil { logger.Error("failed to create PID file", zap.String("file", fname), zap.Error(err)) return err } logger.Debug("Created PID file", zap.String("name", fname), zap.Int("PID", pid)) return nil } // RemovePID removes the PID file. func RemovePID(fname string, logger *log.Logger) { if err := os.Remove(fname); err != nil { logger.Error("failed to remove PID file", zap.String("name", fname), zap.Error(err)) } else { logger.Debug("PID file removed", zap.String("name", fname)) } } // CleanUpRefresh removes state of past refresh. func CleanUpRefresh(killC *KillChannels, receiverOnlyMode bool, senderOnlyMode bool, resolveDNS bool) { // Close all the channels if !receiverOnlyMode { close(killC.Echo) } if !senderOnlyMode { close(killC.Receiver) time.Sleep(500 * time.Millisecond) } if !receiverOnlyMode && !senderOnlyMode { close(killC.Collector) time.Sleep(50 * time.Millisecond) } if resolveDNS { close(killC.DNSRefresh) } } // CleanUpAll conducts a clean exit. func CleanUpAll( killC *KillChannels, receiverOnlyMode bool, senderOnlyMode bool, resolveDNS bool, conn *ip.Conn, PIDPath string, sr metrics.Reporter, logger *log.Logger, ) { CleanUpRefresh(killC, receiverOnlyMode, senderOnlyMode, resolveDNS) conn.Close(logger) sr.Close() RemovePID(PIDPath, logger) }