// Copyright 2019 the u-root Authors. All rights reserved
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

// Package eventlog parses kernel event logs and saves the parsed data on a file on disk.
package eventlog

import (
	"fmt"
	"io"
	"log"
	"os"
	"strings"

	slaunch "github.com/u-root/u-root/pkg/securelaunch"
	tss "github.com/u-root/u-root/pkg/tss"
	txtlog "github.com/u-root/u-root/pkg/txtlog"
)

// EventLog stores location for dumping event logs on disk.
type EventLog struct {
	Type     string `json:"type"`
	Location string `json:"location"`
}

const (
	eventLogFile        = "/sys/kernel/security/slaunch/eventlog"
	defaultEventLogFile = "eventlog.txt" // only used if user doesn't provide any
)

var eventLogPath string

// Add writes event logs to sysfs file.
func Add(b []byte) error {
	fd, err := os.OpenFile(eventLogFile, os.O_WRONLY, 0o644)
	if err != nil {
		return err
	}

	defer fd.Close()
	_, err = fd.Write(b)
	if err != nil && err != io.EOF {
		return err
	}
	slaunch.Debug("err = %v", err)
	return nil
}

// ParseEventlog uses the tpmtool package to parse the event logs generated by a
// kernel with CONFIG_SECURE_LAUNCH enabled and and queues the data to persist queue.
//
// These event logs are originally in binary format and need to be parsed into human readable
// format. An error is returned if parsing code fails in tpmtool.
func ParseEventLog() error {
	txtlog.DefaultTCPABinaryLog = eventLogFile
	firmware := txtlog.Txt
	tpmSpecVersion := tss.TPMVersion20

	tcpaLog, err := txtlog.ParseLog(firmware, tpmSpecVersion)
	if err != nil {
		log.Printf("eventlog: ERR: could not parse eventlog file '%s': %v", eventLogFile, err)
		return fmt.Errorf("could not parse eventlog file '%s': %w", eventLogFile, err)
	}

	var dataStr strings.Builder
	for _, pcr := range tcpaLog.PcrList {
		fmt.Fprintf(&dataStr, "%s\n", pcr)
		fmt.Fprintf(&dataStr, "\n")
	}

	data := []byte(dataStr.String())

	return slaunch.AddToPersistQueue("EventLog:", data, eventLogPath, defaultEventLogFile)
}

// Parse parses the eventlog section of the policy file.
//
// The location of the file on disk is specified in the policy file by the Location tag.
// An error is returned if parsing fails or an incorrect value or format is used.
func (e *EventLog) Parse() error {
	if e.Type != "file" {
		return fmt.Errorf("unsupported eventlog type")
	}

	slaunch.Debug("Identified EventLog Type = file")

	// e.Location is of the form sda:path/to/file.txt
	eventLogPath = e.Location
	if eventLogPath == "" {
		return fmt.Errorf("empty eventlog path provided")
	}

	return nil
}
