package main

import (
	"fmt"
	"io"
	"io/ioutil"
	"os"
	"path/filepath"
	"time"

	"git.thisco.de/vbatts/fuzz-walker/walker"
	"github.com/sirupsen/logrus"
	"github.com/urfave/cli"
)

var (
	dirs   = []string{"/sys", "/proc"}
	fuzzes = map[string]walker.FuzzFunc{
		"chmod":      chmodFuzz,
		"read":       readFuzz,
		"writeBytes": writeBytesFuzz,
		"writeNum":   writeNumFuzz,
	}
	fuzzTimeout   = 500 * time.Millisecond
	walkerPlugins []walker.Fuzzer
)

func appInit(c *cli.Context) error {
	matches, err := filepath.Glob(c.String("plugins"))
	if err != nil {
		return err
	}
	for _, match := range matches {
		ff, err := walker.LoadPlugin(match)
		if err != nil {
			return err
		}
		walkerPlugins = append(walkerPlugins, ff)
	}
	return nil
}

func main() {
	app := cli.NewApp()
	app.Name = "fuzz-walker"
	app.Usage = "a walker to poke and prod at /proc and /sys"
	app.Before = appInit
	app.Flags = []cli.Flag{
		cli.StringFlag{
			Name:  "plugins",
			Value: "*.so",
			Usage: "pattern to glob walker plugins",
		},
	}
	app.Action = func(c *cli.Context) error {
		hasError := false
		for _, dir := range dirs {
			err := filepath.Walk(dir, func(path string, info os.FileInfo, err error) error {
				if err != nil {
					return err
				}
				if !info.Mode().IsRegular() {
					return nil
				}
				for fname, fuzz := range fuzzes {
					if err := fuzz(path, info, fuzzTimeout); err != nil {
						logrus.Warnf("%s: %q fuzz failed with: %v", path, fname, err)
					}
				}
				return nil
			})
			if err != nil {
				logrus.Warnf("%s: %v", dir, err)
				hasError = true
			}
		}
		if hasError {
			return fmt.Errorf("errors reported")
		}
		return nil
	}

	app.Run(os.Args)
}

func readFuzz(path string, info os.FileInfo, timeout time.Duration) error {
	c1 := make(chan error, 1)
	go func() {
		fd, err := os.Open(path)
		if err != nil {
			c1 <- err
		}
		defer fd.Close()
		_, err = io.Copy(ioutil.Discard, fd)
		c1 <- err
	}()

	select {
	case err := <-c1:
		return err
	case <-time.After(timeout):
		return fmt.Errorf("timeout reached")
	}
	return nil
}

func writeNumFuzz(path string, info os.FileInfo, timeout time.Duration) error {
	return nil
}

func writeBytesFuzz(path string, info os.FileInfo, timeout time.Duration) error {
	return nil
}

func chmodFuzz(path string, info os.FileInfo, timeout time.Duration) error {
	return nil
}

// maybe have an ioctl fuzzer
