Changed groups and hosts to use maps in config, reorganized code, cleaned up command line arguments

This commit is contained in:
Christopher Ramey 2021-01-01 09:26:49 -09:00
parent aa1d4d10fa
commit 826564a060
10 changed files with 845 additions and 105 deletions

8
alrmrc
View File

@ -1,11 +1,13 @@
set interval 30 set interval 30
monitor host gateway address 10.11.135.100
monitor group webservers monitor group webservers
host www1.example.com address 10.11.135.101 host www1.example.com address 10.11.135.101
check ping check ping
host www2.example.com address 10.11.135.102 host www2.example.com address 10.11.135.102
check ping # comments can occur at the beginning
check ping # or the end of a line
check ping # checks are not named, so multiple is okay
monitor host gateway address 10.11.135.100
monitor host database monitor host database

View File

@ -1,13 +1,18 @@
package main package check
import ( import (
"fmt" "fmt"
) )
type AlrmCheck interface {
Parse(string) (bool, error)
Check() error
}
func NewCheck(name string, addr string) (AlrmCheck, error) { func NewCheck(name string, addr string) (AlrmCheck, error) {
switch name { switch name {
case "ping": case "ping":
return &CheckPing{Address: addr}, nil return &CheckPing{Type: "ping", Address: addr}, nil
default: default:
return nil, fmt.Errorf("unknown check name \"%s\"", name) return nil, fmt.Errorf("unknown check name \"%s\"", name)
} }

View File

@ -1,6 +1,7 @@
package main package check
type CheckPing struct { type CheckPing struct {
Type string
Address string Address string
} }

660
check/ping/ping.go Normal file
View File

@ -0,0 +1,660 @@
// Package ping is an ICMP ping library seeking to emulate the unix "ping"
// command.
//
// Here is a very simple example that sends & receives 3 packets:
//
// pinger, err := ping.NewPinger("www.google.com")
// if err != nil {
// panic(err)
// }
//
// pinger.Count = 3
// pinger.Run() // blocks until finished
// stats := pinger.Statistics() // get send/receive/rtt stats
//
// Here is an example that emulates the unix ping command:
//
// pinger, err := ping.NewPinger("www.google.com")
// if err != nil {
// fmt.Printf("ERROR: %s\n", err.Error())
// return
// }
//
// pinger.OnRecv = func(pkt *ping.Packet) {
// fmt.Printf("%d bytes from %s: icmp_seq=%d time=%v\n",
// pkt.Nbytes, pkt.IPAddr, pkt.Seq, pkt.Rtt)
// }
// pinger.OnFinish = func(stats *ping.Statistics) {
// fmt.Printf("\n--- %s ping statistics ---\n", stats.Addr)
// fmt.Printf("%d packets transmitted, %d packets received, %v%% packet loss\n",
// stats.PacketsSent, stats.PacketsRecv, stats.PacketLoss)
// fmt.Printf("round-trip min/avg/max/stddev = %v/%v/%v/%v\n",
// stats.MinRtt, stats.AvgRtt, stats.MaxRtt, stats.StdDevRtt)
// }
//
// fmt.Printf("PING %s (%s):\n", pinger.Addr(), pinger.IPAddr())
// pinger.Run()
//
// It sends ICMP packet(s) and waits for a response. If it receives a response,
// it calls the "receive" callback. When it's finished, it calls the "finish"
// callback.
//
// For a full ping example, see "cmd/ping/ping.go".
//
package ping
import (
"bytes"
"encoding/binary"
"errors"
"fmt"
"math"
"math/rand"
"net"
"runtime"
"sync"
"syscall"
"time"
"golang.org/x/net/icmp"
"golang.org/x/net/ipv4"
"golang.org/x/net/ipv6"
)
const (
timeSliceLength = 8
trackerLength = 8
protocolICMP = 1
protocolIPv6ICMP = 58
)
var (
ipv4Proto = map[string]string{"icmp": "ip4:icmp", "udp": "udp4"}
ipv6Proto = map[string]string{"icmp": "ip6:ipv6-icmp", "udp": "udp6"}
)
// New returns a new Pinger struct pointer.
func New(addr string) *Pinger {
r := rand.New(rand.NewSource(time.Now().UnixNano()))
return &Pinger{
Count: -1,
Interval: time.Second,
RecordRtts: true,
Size: timeSliceLength,
Timeout: time.Second * 100000,
Tracker: r.Int63n(math.MaxInt64),
addr: addr,
done: make(chan bool),
id: r.Intn(math.MaxInt16),
ipaddr: nil,
ipv4: false,
network: "ip",
protocol: "udp",
}
}
// NewPinger returns a new Pinger and resolves the address.
func NewPinger(addr string) (*Pinger, error) {
p := New(addr)
return p, p.Resolve()
}
// Pinger represents a packet sender/receiver.
type Pinger struct {
// Interval is the wait time between each packet send. Default is 1s.
Interval time.Duration
// Timeout specifies a timeout before ping exits, regardless of how many
// packets have been received.
Timeout time.Duration
// Count tells pinger to stop after sending (and receiving) Count echo
// packets. If this option is not specified, pinger will operate until
// interrupted.
Count int
// Debug runs in debug mode
Debug bool
// Number of packets sent
PacketsSent int
// Number of packets received
PacketsRecv int
// If true, keep a record of rtts of all received packets.
// Set to false to avoid memory bloat for long running pings.
RecordRtts bool
// rtts is all of the Rtts
rtts []time.Duration
// OnSend is called when Pinger sends a packet
OnSend func(*Packet)
// OnRecv is called when Pinger receives and processes a packet
OnRecv func(*Packet)
// OnFinish is called when Pinger exits
OnFinish func(*Statistics)
// Size of packet being sent
Size int
// Tracker: Used to uniquely identify packet when non-priviledged
Tracker int64
// Source is the source IP address
Source string
// stop chan bool
done chan bool
ipaddr *net.IPAddr
addr string
ipv4 bool
id int
sequence int
// network is one of "ip", "ip4", or "ip6".
network string
// protocol is "icmp" or "udp".
protocol string
}
type packet struct {
bytes []byte
nbytes int
ttl int
}
// Packet represents a received and processed ICMP echo packet.
type Packet struct {
// Rtt is the round-trip time it took to ping.
Rtt time.Duration
// IPAddr is the address of the host being pinged.
IPAddr *net.IPAddr
// Addr is the string address of the host being pinged.
Addr string
// NBytes is the number of bytes in the message.
Nbytes int
// Seq is the ICMP sequence number.
Seq int
// TTL is the Time To Live on the packet.
Ttl int
}
// Statistics represent the stats of a currently running or finished
// pinger operation.
type Statistics struct {
// PacketsRecv is the number of packets received.
PacketsRecv int
// PacketsSent is the number of packets sent.
PacketsSent int
// PacketLoss is the percentage of packets lost.
PacketLoss float64
// IPAddr is the address of the host being pinged.
IPAddr *net.IPAddr
// Addr is the string address of the host being pinged.
Addr string
// Rtts is all of the round-trip times sent via this pinger.
Rtts []time.Duration
// MinRtt is the minimum round-trip time sent via this pinger.
MinRtt time.Duration
// MaxRtt is the maximum round-trip time sent via this pinger.
MaxRtt time.Duration
// AvgRtt is the average round-trip time sent via this pinger.
AvgRtt time.Duration
// StdDevRtt is the standard deviation of the round-trip times sent via
// this pinger.
StdDevRtt time.Duration
}
// SetIPAddr sets the ip address of the target host.
func (p *Pinger) SetIPAddr(ipaddr *net.IPAddr) {
p.ipv4 = isIPv4(ipaddr.IP)
p.ipaddr = ipaddr
p.addr = ipaddr.String()
}
// IPAddr returns the ip address of the target host.
func (p *Pinger) IPAddr() *net.IPAddr {
return p.ipaddr
}
// Resolve does the DNS lookup for the Pinger address and sets IP protocol.
func (p *Pinger) Resolve() error {
if len(p.addr) == 0 {
return errors.New("addr cannot be empty")
}
ipaddr, err := net.ResolveIPAddr(p.network, p.addr)
if err != nil {
return err
}
p.ipv4 = isIPv4(ipaddr.IP)
p.ipaddr = ipaddr
return nil
}
// SetAddr resolves and sets the ip address of the target host, addr can be a
// DNS name like "www.google.com" or IP like "127.0.0.1".
func (p *Pinger) SetAddr(addr string) error {
oldAddr := p.addr
p.addr = addr
err := p.Resolve()
if err != nil {
p.addr = oldAddr
return err
}
return nil
}
// Addr returns the string ip address of the target host.
func (p *Pinger) Addr() string {
return p.addr
}
// SetNetwork allows configuration of DNS resolution.
// * "ip" will automatically select IPv4 or IPv6.
// * "ip4" will select IPv4.
// * "ip6" will select IPv6.
func (p *Pinger) SetNetwork(n string) {
switch n {
case "ip4":
p.network = "ip4"
case "ip6":
p.network = "ip6"
default:
p.network = "ip"
}
}
// SetPrivileged sets the type of ping pinger will send.
// false means pinger will send an "unprivileged" UDP ping.
// true means pinger will send a "privileged" raw ICMP ping.
// NOTE: setting to true requires that it be run with super-user privileges.
func (p *Pinger) SetPrivileged(privileged bool) {
if privileged {
p.protocol = "icmp"
} else {
p.protocol = "udp"
}
}
// Privileged returns whether pinger is running in privileged mode.
func (p *Pinger) Privileged() bool {
return p.protocol == "icmp"
}
// Run runs the pinger. This is a blocking function that will exit when it's
// done. If Count or Interval are not specified, it will run continuously until
// it is interrupted.
func (p *Pinger) Run() error {
var conn *icmp.PacketConn
var err error
if p.ipaddr == nil {
err = p.Resolve()
}
if err != nil {
return err
}
if p.ipv4 {
if conn, err = p.listen(ipv4Proto[p.protocol]); err != nil {
return err
}
if err = conn.IPv4PacketConn().SetControlMessage(ipv4.FlagTTL, true); runtime.GOOS != "windows" && err != nil {
return err
}
} else {
if conn, err = p.listen(ipv6Proto[p.protocol]); err != nil {
return err
}
if err = conn.IPv6PacketConn().SetControlMessage(ipv6.FlagHopLimit, true); runtime.GOOS != "windows" && err != nil {
return err
}
}
defer conn.Close()
defer p.finish()
var wg sync.WaitGroup
recv := make(chan *packet, 5)
defer close(recv)
wg.Add(1)
//nolint:errcheck
go p.recvICMP(conn, recv, &wg)
err = p.sendICMP(conn)
if err != nil {
return err
}
timeout := time.NewTicker(p.Timeout)
defer timeout.Stop()
interval := time.NewTicker(p.Interval)
defer interval.Stop()
for {
select {
case <-p.done:
wg.Wait()
return nil
case <-timeout.C:
close(p.done)
wg.Wait()
return nil
case <-interval.C:
if p.Count > 0 && p.PacketsSent >= p.Count {
continue
}
err = p.sendICMP(conn)
if err != nil {
// FIXME: this logs as FATAL but continues
fmt.Println("FATAL: ", err.Error())
}
case r := <-recv:
err := p.processPacket(r)
if err != nil {
// FIXME: this logs as FATAL but continues
fmt.Println("FATAL: ", err.Error())
}
}
if p.Count > 0 && p.PacketsRecv >= p.Count {
close(p.done)
wg.Wait()
return nil
}
}
}
func (p *Pinger) Stop() {
close(p.done)
}
func (p *Pinger) finish() {
handler := p.OnFinish
if handler != nil {
s := p.Statistics()
handler(s)
}
}
// Statistics returns the statistics of the pinger. This can be run while the
// pinger is running or after it is finished. OnFinish calls this function to
// get it's finished statistics.
func (p *Pinger) Statistics() *Statistics {
loss := float64(p.PacketsSent-p.PacketsRecv) / float64(p.PacketsSent) * 100
var min, max, total time.Duration
if len(p.rtts) > 0 {
min = p.rtts[0]
max = p.rtts[0]
}
for _, rtt := range p.rtts {
if rtt < min {
min = rtt
}
if rtt > max {
max = rtt
}
total += rtt
}
s := Statistics{
PacketsSent: p.PacketsSent,
PacketsRecv: p.PacketsRecv,
PacketLoss: loss,
Rtts: p.rtts,
Addr: p.addr,
IPAddr: p.ipaddr,
MaxRtt: max,
MinRtt: min,
}
if len(p.rtts) > 0 {
s.AvgRtt = total / time.Duration(len(p.rtts))
var sumsquares time.Duration
for _, rtt := range p.rtts {
sumsquares += (rtt - s.AvgRtt) * (rtt - s.AvgRtt)
}
s.StdDevRtt = time.Duration(math.Sqrt(
float64(sumsquares / time.Duration(len(p.rtts)))))
}
return &s
}
func (p *Pinger) recvICMP(
conn *icmp.PacketConn,
recv chan<- *packet,
wg *sync.WaitGroup,
) error {
defer wg.Done()
for {
select {
case <-p.done:
return nil
default:
bytes := make([]byte, 512)
if err := conn.SetReadDeadline(time.Now().Add(time.Millisecond * 100)); err != nil {
return err
}
var n, ttl int
var err error
if p.ipv4 {
var cm *ipv4.ControlMessage
n, cm, _, err = conn.IPv4PacketConn().ReadFrom(bytes)
if cm != nil {
ttl = cm.TTL
}
} else {
var cm *ipv6.ControlMessage
n, cm, _, err = conn.IPv6PacketConn().ReadFrom(bytes)
if cm != nil {
ttl = cm.HopLimit
}
}
if err != nil {
if neterr, ok := err.(*net.OpError); ok {
if neterr.Timeout() {
// Read timeout
continue
} else {
close(p.done)
return err
}
}
}
select {
case <-p.done:
return nil
case recv <- &packet{bytes: bytes, nbytes: n, ttl: ttl}:
}
}
}
}
func (p *Pinger) processPacket(recv *packet) error {
receivedAt := time.Now()
var proto int
if p.ipv4 {
proto = protocolICMP
} else {
proto = protocolIPv6ICMP
}
var m *icmp.Message
var err error
if m, err = icmp.ParseMessage(proto, recv.bytes); err != nil {
return fmt.Errorf("error parsing icmp message: %s", err.Error())
}
if m.Type != ipv4.ICMPTypeEchoReply && m.Type != ipv6.ICMPTypeEchoReply {
// Not an echo reply, ignore it
return nil
}
outPkt := &Packet{
Nbytes: recv.nbytes,
IPAddr: p.ipaddr,
Addr: p.addr,
Ttl: recv.ttl,
}
switch pkt := m.Body.(type) {
case *icmp.Echo:
// If we are priviledged, we can match icmp.ID
if p.protocol == "icmp" {
// Check if reply from same ID
if pkt.ID != p.id {
return nil
}
}
if len(pkt.Data) < timeSliceLength+trackerLength {
return fmt.Errorf("insufficient data received; got: %d %v",
len(pkt.Data), pkt.Data)
}
tracker := bytesToInt(pkt.Data[timeSliceLength:])
timestamp := bytesToTime(pkt.Data[:timeSliceLength])
if tracker != p.Tracker {
return nil
}
outPkt.Rtt = receivedAt.Sub(timestamp)
outPkt.Seq = pkt.Seq
p.PacketsRecv++
default:
// Very bad, not sure how this can happen
return fmt.Errorf("invalid ICMP echo reply; type: '%T', '%v'", pkt, pkt)
}
if p.RecordRtts {
p.rtts = append(p.rtts, outPkt.Rtt)
}
handler := p.OnRecv
if handler != nil {
handler(outPkt)
}
return nil
}
func (p *Pinger) sendICMP(conn *icmp.PacketConn) error {
var typ icmp.Type
if p.ipv4 {
typ = ipv4.ICMPTypeEcho
} else {
typ = ipv6.ICMPTypeEchoRequest
}
var dst net.Addr = p.ipaddr
if p.protocol == "udp" {
dst = &net.UDPAddr{IP: p.ipaddr.IP, Zone: p.ipaddr.Zone}
}
t := append(timeToBytes(time.Now()), intToBytes(p.Tracker)...)
if remainSize := p.Size - timeSliceLength - trackerLength; remainSize > 0 {
t = append(t, bytes.Repeat([]byte{1}, remainSize)...)
}
body := &icmp.Echo{
ID: p.id,
Seq: p.sequence,
Data: t,
}
msg := &icmp.Message{
Type: typ,
Code: 0,
Body: body,
}
msgBytes, err := msg.Marshal(nil)
if err != nil {
return err
}
for {
if _, err := conn.WriteTo(msgBytes, dst); err != nil {
if neterr, ok := err.(*net.OpError); ok {
if neterr.Err == syscall.ENOBUFS {
continue
}
}
}
handler := p.OnSend
if handler != nil {
outPkt := &Packet{
Nbytes: len(msgBytes),
IPAddr: p.ipaddr,
Addr: p.addr,
Seq: p.sequence,
}
handler(outPkt)
}
p.PacketsSent++
p.sequence++
break
}
return nil
}
func (p *Pinger) listen(netProto string) (*icmp.PacketConn, error) {
conn, err := icmp.ListenPacket(netProto, p.Source)
if err != nil {
close(p.done)
return nil, err
}
return conn, nil
}
func bytesToTime(b []byte) time.Time {
var nsec int64
for i := uint8(0); i < 8; i++ {
nsec += int64(b[i]) << ((7 - i) * 8)
}
return time.Unix(nsec/1000000000, nsec%1000000000)
}
func isIPv4(ip net.IP) bool {
return len(ip.To4()) == net.IPv4len
}
func timeToBytes(t time.Time) []byte {
nsec := t.UnixNano()
b := make([]byte, 8)
for i := uint8(0); i < 8; i++ {
b[i] = byte((nsec >> ((7 - i) * 8)) & 0xff)
}
return b
}
func bytesToInt(b []byte) int64 {
return int64(binary.BigEndian.Uint64(b))
}
func intToBytes(tracker int64) []byte {
b := make([]byte, 8)
binary.BigEndian.PutUint64(b, uint64(tracker))
return b
}

View File

@ -1,50 +0,0 @@
package main
type AlrmConfig struct {
Groups []*AlrmGroup
Interval int
}
func (ac *AlrmConfig) NewGroup() *AlrmGroup {
group := &AlrmGroup{}
ac.Groups = append(ac.Groups, group)
return group
}
type AlrmGroup struct {
Name string
Hosts []*AlrmHost
}
func (ag *AlrmGroup) NewHost() *AlrmHost {
host := &AlrmHost{}
ag.Hosts = append(ag.Hosts, host)
return host
}
type AlrmHost struct {
Name string
Address string
Checks []AlrmCheck
}
func (ah *AlrmHost) GetAddress() string {
if ah.Address != "" {
return ah.Address
}
return ah.Name
}
type AlrmCheck interface {
Parse(string) (bool, error)
Check() error
}
func ReadConfig(fn string) (*AlrmConfig, error) {
parser := &Parser{}
config, err := parser.Parse(fn)
if err != nil {
return nil, err
}
return config, nil
}

44
config/config.go Normal file
View File

@ -0,0 +1,44 @@
package config
import (
"fmt"
"strconv"
)
type AlrmConfig struct {
Groups map[string]*AlrmGroup
Interval int
}
func (ac *AlrmConfig) NewGroup(name string) (*AlrmGroup, error) {
if ac.Groups == nil {
ac.Groups = make(map[string]*AlrmGroup)
}
if _, exists := ac.Groups[name]; exists {
return nil, fmt.Errorf("group %s already exists", name)
}
group := &AlrmGroup{Name: name}
ac.Groups[name] = group
return group, nil
}
func (ac *AlrmConfig) SetInterval(val string) error {
interval, err := strconv.Atoi(val)
if err != nil {
return err
}
ac.Interval = interval
return nil
}
func ReadConfig(fn string, debuglvl int) (*AlrmConfig, error) {
parser := &Parser{DebugLevel: debuglvl}
config, err := parser.Parse(fn)
if err != nil {
return nil, err
}
return config, nil
}

24
config/group.go Normal file
View File

@ -0,0 +1,24 @@
package config
import (
"fmt"
)
type AlrmGroup struct {
Name string
Hosts map[string]*AlrmHost
}
func (ag *AlrmGroup) NewHost(name string) (*AlrmHost, error) {
if ag.Hosts == nil {
ag.Hosts = make(map[string]*AlrmHost)
}
if _, exists := ag.Hosts[name]; exists {
return nil, fmt.Errorf("host %s already exists", name)
}
host := &AlrmHost{Name: name}
ag.Hosts[name] = host
return host, nil
}

27
config/host.go Normal file
View File

@ -0,0 +1,27 @@
package config
import (
"alrm/check"
)
type AlrmHost struct {
Name string
Address string
Checks []check.AlrmCheck
}
func (ah *AlrmHost) GetAddress() string {
if ah.Address != "" {
return ah.Address
}
return ah.Name
}
func (ah *AlrmHost) NewCheck(name string) (check.AlrmCheck, error) {
chk, err := check.NewCheck(name, ah.GetAddress())
if err != nil {
return nil, err
}
ah.Checks = append(ah.Checks, chk)
return chk, nil
}

View File

@ -1,10 +1,10 @@
package main package config
import ( import (
"alrm/check"
"bufio" "bufio"
"fmt" "fmt"
"os" "os"
"strconv"
"strings" "strings"
) )
@ -18,8 +18,12 @@ const (
) )
type Parser struct { type Parser struct {
Line int DebugLevel int
states []int Line int
states []int
lasthost *AlrmHost
lastgroup *AlrmGroup
lastcheck check.AlrmCheck
} }
func (p *Parser) Parse(fn string) (*AlrmConfig, error) { func (p *Parser) Parse(fn string) (*AlrmConfig, error) {
@ -30,9 +34,6 @@ func (p *Parser) Parse(fn string) (*AlrmConfig, error) {
defer file.Close() defer file.Close()
config := &AlrmConfig{} config := &AlrmConfig{}
var group *AlrmGroup
var host *AlrmHost
var check AlrmCheck
scan := bufio.NewScanner(file) scan := bufio.NewScanner(file)
scan.Split(p.Split) scan.Split(p.Split)
@ -61,7 +62,7 @@ func (p *Parser) Parse(fn string) (*AlrmConfig, error) {
value := scan.Text() value := scan.Text()
switch key { switch key {
case "interval": case "interval":
config.Interval, err = strconv.Atoi(value) err := config.SetInterval(value)
if err != nil { if err != nil {
return nil, fmt.Errorf( return nil, fmt.Errorf(
"invalid number for interval in %s, line %d: \"%s\"", "invalid number for interval in %s, line %d: \"%s\"",
@ -78,12 +79,9 @@ func (p *Parser) Parse(fn string) (*AlrmConfig, error) {
case TK_MONITOR: case TK_MONITOR:
switch strings.ToLower(tk) { switch strings.ToLower(tk) {
case "host": case "host":
group = config.NewGroup()
host = group.NewHost()
p.setState(TK_HOST) p.setState(TK_HOST)
case "group": case "group":
group = config.NewGroup()
p.setState(TK_GROUP) p.setState(TK_GROUP)
default: default:
@ -92,33 +90,43 @@ func (p *Parser) Parse(fn string) (*AlrmConfig, error) {
} }
case TK_GROUP: case TK_GROUP:
if group == nil { if p.lastgroup == nil {
return nil, fmt.Errorf("group without initialization") p.lastgroup, err = config.NewGroup(tk)
if err != nil {
return nil, fmt.Errorf("%s in %s, line %d",
err.Error(), fn, p.Line+1,
)
}
continue
} }
switch strings.ToLower(tk) { switch strings.ToLower(tk) {
case "host": case "host":
host = group.NewHost()
p.setState(TK_HOST) p.setState(TK_HOST)
continue
default: default:
if group.Name == "" {
group.Name = tk
continue
}
p.prevState() p.prevState()
goto stateswitch goto stateswitch
} }
case TK_HOST: case TK_HOST:
if host == nil { // If a host has no group, inherit the host name
return nil, fmt.Errorf("host token without initialization") if p.lastgroup == nil {
p.lastgroup, err = config.NewGroup(tk)
if err != nil {
return nil, fmt.Errorf("%s in %s, line %d",
err.Error(), fn, p.Line+1,
)
}
} }
if host.Name == "" { if p.lasthost == nil {
host.Name = tk p.lasthost, err = p.lastgroup.NewHost(tk)
if err != nil {
return nil, fmt.Errorf("%s in %s, line %d",
err.Error(), fn, p.Line+1,
)
}
continue continue
} }
@ -128,10 +136,9 @@ func (p *Parser) Parse(fn string) (*AlrmConfig, error) {
return nil, fmt.Errorf("empty address for host in %s, line %d", return nil, fmt.Errorf("empty address for host in %s, line %d",
fn, p.Line+1) fn, p.Line+1)
} }
host.Address = scan.Text() p.lasthost.Address = scan.Text()
case "check": case "check":
check = nil
p.setState(TK_CHECK) p.setState(TK_CHECK)
default: default:
@ -140,23 +147,20 @@ func (p *Parser) Parse(fn string) (*AlrmConfig, error) {
} }
case TK_CHECK: case TK_CHECK:
if check == nil { if p.lastcheck == nil {
if host == nil { p.lastcheck, err = p.lasthost.NewCheck(tk)
return nil, fmt.Errorf("check token without initialization")
}
check, err = NewCheck(strings.ToLower(tk), host.GetAddress())
if err != nil { if err != nil {
return nil, fmt.Errorf("%s in %s, line %d", return nil, fmt.Errorf("%s in %s, line %d",
err.Error(), fn, p.Line+1) err.Error(), fn, p.Line+1)
} }
host.Checks = append(host.Checks, check)
continue continue
} }
cont, err := check.Parse(tk) cont, err := p.lastcheck.Parse(tk)
if err != nil { if err != nil {
return nil, err return nil, err
} }
if !cont { if !cont {
p.lastcheck = nil
p.prevState() p.prevState()
goto stateswitch goto stateswitch
} }
@ -179,9 +183,24 @@ func (p *Parser) state() int {
} }
func (p *Parser) setState(state int) { func (p *Parser) setState(state int) {
// fmt.Printf("%s", p.stateName()) switch state {
case TK_SET, TK_MONITOR:
fallthrough
case TK_GROUP:
p.lastgroup = nil
fallthrough
case TK_HOST:
p.lasthost = nil
p.lastcheck = nil
}
if p.DebugLevel > 0 {
fmt.Printf("Parser state: %s", p.stateName())
}
p.states = append(p.states, state) p.states = append(p.states, state)
// fmt.Printf(" -> %s\n", p.stateName()) if p.DebugLevel > 0 {
fmt.Printf(" -> %s\n", p.stateName())
}
} }
func (p *Parser) prevState() int { func (p *Parser) prevState() int {
@ -222,7 +241,7 @@ func (p *Parser) Split(data []byte, atEOF bool) (int, []byte, error) {
for i := 0; i < len(data); i++ { for i := 0; i < len(data); i++ {
c := data[i] c := data[i]
// fmt.Printf("%c (%t) (%t)\n", c, started, ignoreline) // fmt.Printf("%c (%t) (%t)\n", c, started, ignoreline)
switch c { switch c {
case '\f', '\n', '\r': case '\f', '\n', '\r':
p.Line++ p.Line++

34
main.go
View File

@ -6,43 +6,51 @@ import (
"flag" "flag"
"os" "os"
"strings" "strings"
"alrm/config"
) )
func main() { func main() {
configPath := flag.String("config", "", "path to configuration file") cfgPath := flag.String("c", "", "path to configuration file")
debuglvl := flag.Int("d", 0, "debug level")
flag.Parse() flag.Parse()
if *configPath == "" { if *cfgPath == "" {
if _, err := os.Stat("./alrmrc"); err == nil { if _, err := os.Stat("./alrmrc"); err == nil {
*configPath = "./alrmrc" *cfgPath = "./alrmrc"
} }
if _, err := os.Stat("/etc/alrmrc"); err == nil { if _, err := os.Stat("/etc/alrmrc"); err == nil {
*configPath = "/etc/alrmrc" *cfgPath = "/etc/alrmrc"
} }
if *configPath == "" { if *cfgPath == "" {
fmt.Fprintf(os.Stderr, "Cannot find configuration\n") fmt.Fprintf(os.Stderr, "Cannot find configuration\n")
os.Exit(1) os.Exit(1)
} }
} }
config, err := ReadConfig(*configPath)
if err != nil {
fmt.Fprintf(os.Stderr, "%s\n", err.Error())
os.Exit(1)
}
command := strings.ToLower(flag.Arg(0)) command := strings.ToLower(flag.Arg(0))
switch command { switch command {
case "json": case "json":
o, err := json.MarshalIndent(config, "", " ") cfg, err := config.ReadConfig(*cfgPath, *debuglvl)
if err != nil {
fmt.Fprintf(os.Stderr, "%s\n", err.Error())
os.Exit(1)
}
o, err := json.MarshalIndent(cfg, "", " ")
if err != nil { if err != nil {
fmt.Fprintf(os.Stderr, "JSON error: %s\n", err.Error()) fmt.Fprintf(os.Stderr, "JSON error: %s\n", err.Error())
os.Exit(1) os.Exit(1)
} }
fmt.Fprintf(os.Stdout, "%s", string(o)) fmt.Fprintf(os.Stdout, "%s\n", string(o))
case "", "config": case "", "config":
_, err := config.ReadConfig(*cfgPath, *debuglvl)
if err != nil {
fmt.Fprintf(os.Stderr, "%s\n", err.Error())
os.Exit(1)
}
fmt.Fprintf(os.Stdout, "Config is OK.\n") fmt.Fprintf(os.Stdout, "Config is OK.\n")
os.Exit(0) os.Exit(0)