/*
 * Functions in this file are used to forward syslog messages to the
 * host and must be quite careful about their own logging. In general
 * error messages should go via the console log.Logger defined in this
 * file.
 */
package main

import (
	"errors"
	"fmt"
	"log"
	"net"
	"os"
	"strconv"
	"strings"

	"github.com/rneugeba/virtsock/go/hvsock"
	"github.com/rneugeba/virtsock/go/vsock"
)

var (
	console     *log.Logger
	currentConn vConn

	alreadyConnectedOnce bool

	/* When writing we don't discover e.g. EPIPE until the _next_
	 * attempt to write. Therefore we keep a copy of the last
	 * message sent such that we can repeat it after an error.
	 *
	 * Note that this is imperfect since their can be multiple
	 * messages in flight at the point a connection collapses
	 * which will then be lost. This only handles the delayed
	 * notification of such an error to this code.
	 */
	lastMessage []byte
)

/* rfc5425 like scheme, see section 4.3 */
func rfc5425Write(conn vConn, buf []byte) error {

	msglen := fmt.Sprintf("%d ", len(buf))

	_, err := conn.Write([]byte(msglen))
	/* XXX todo, check for serious vs retriable errors */
	if err != nil {
		console.Printf("Error in length write: %s", err)
		return err
	}

	_, err = conn.Write(buf)
	/* XXX todo, check for serious vs retriable errors */
	if err != nil {
		console.Printf("Error in buf write: %s", err)
	}

	return err
}

func forwardSyslogDatagram(buf []byte, portstr string) error {
	for try := 0; try < 2; try++ {
		conn := currentConn
		if conn == nil {
			if strings.Contains(portstr, "-") {
				svcid, err := hvsock.GuidFromString(portstr)
				if err != nil {
					console.Fatalln("Failed to parse GUID", portstr, err)
				}

				conn, err = hvsock.Dial(hvsock.HypervAddr{VmId: hvsock.GUID_WILDCARD, ServiceId: svcid})
				if err != nil {
					console.Printf("Failed to dial hvsock port: %s", err)
					continue
				}
			} else {
				port, err := strconv.ParseUint(portstr, 10, 32)
				if err != nil {
					console.Fatalln("Can't convert %s to a uint.", portstr, err)
				}

				conn, err = vsock.Dial(vsock.VSOCK_CID_HOST, uint(port))
				if err != nil {
					console.Printf("Failed to dial vsock port %d: %s", port, err)
					continue
				}
			}

			conn.CloseRead()

			/*
			 * Only log on reconnection, not the initial connection since
			 * that is mostly uninteresting
			 */
			if alreadyConnectedOnce {
				console.Printf("Opened new conn to %s: %#v", portstr, conn)
			}
			alreadyConnectedOnce = true

			if lastMessage != nil {
				console.Printf("Replaying last message: %s", lastMessage)
				err := rfc5425Write(conn, lastMessage)
				if err != nil {
					conn.Close()
					continue
				}
				lastMessage = nil
			}

			currentConn = conn
		}

		err := rfc5425Write(conn, buf)
		if err != nil {
			currentConn.Close()
			currentConn = nil
			console.Printf("Failed to write: %s", string(buf))
			continue
		}

		if try > 0 {
			console.Printf("Forwarded on attempt %d: %s", try+1, string(buf))
		}

		// Keep a copy in case we get an EPIPE from the next write
		lastMessage = make([]byte, len(buf))
		copy(lastMessage, buf)

		return nil
	}

	lastMessage = nil // No point repeating this now
	return errors.New("Failed to send datagram, too many retries")
}

func handleSyslogForward(cfg string) {
	// logging to the default syslog while trying to do syslog
	// forwarding would result in infinite loops, so log all
	// messages in this callchain to the console instead.
	logFile, err := os.OpenFile("/dev/console", os.O_WRONLY, 0)
	if err != nil {
		/* What are the chances of this going anywhere useful... */
		log.Fatalln("Failed to open /dev/console for syslog forward logging", err)
	}

	console = log.New(logFile, "vsyslog: ", log.LstdFlags)

	s := strings.SplitN(cfg, ":", 2)
	if len(s) != 2 {
		console.Fatalf("Failed to parse: %s", cfg)
	}
	vsock := s[0]
	usock := s[1]

	err = os.Remove(usock)
	if err != nil && !os.IsNotExist(err) {
		console.Printf("Failed to remove %s: %s", usock, err)
		/* Try and carry on... */
	}

	l, err := net.ListenUnixgram("unixgram", &net.UnixAddr{usock, "unixgram"})
	if err != nil {
		console.Fatalf("Failed to listen to unixgram:%s: %s", usock, err)
	}

	var buf [4096]byte // Ugh, no way to peek at the next message's size in Go
	for {
		r, err := l.Read(buf[:])
		if err != nil {
			console.Fatalf("Failed to read: %s", err)
		}

		err = forwardSyslogDatagram(buf[:r], vsock)
		if err != nil {
			console.Printf("Failed to send log: %s: %s",
				err, string(buf[:r]))
		}
	}
}
