<commit_before>// Virtua SmartOS Prometheus exporter
//
// Worflow :
//  - detect if launched in GZ or into a Zone
//  - retrieve useful metrics
//  - expose them to http://xxx:9100/metrics (same as node_exporter)

package main

import (
        "log"
        "net/http"
        "os/exec"
        "strconv"
        "strings"
//      "fmt"
        // Prometheus Go toolset
        "github.com/prometheus/client_golang/prometheus"
        "github.com/prometheus/client_golang/prometheus/promhttp"
)

var (
    // Global variables
    exporterPort = ":9100"
    // Metrics definitions
    gzCpuUsage = prometheus.NewGaugeVec(prometheus.GaugeOpts{
        Name: "smartos_gz_cpu_usage_total",
        Help: "CPU usage exposed in percent.",
        },
        []string{"cpu","type"},
    )
    gzFreeMem = prometheus.NewGaugeVec(prometheus.GaugeOpts{
        Name: "smartos_gz_memory_free_bytes_total",
        Help: "Total free memory (both RAM and Swap) of the CN.",
        },
        []string{"type"},
    )
    gzMlagUsage = prometheus.NewGaugeVec(prometheus.GaugeOpts{
        Name: "smartos_gz_network_mlag_bytes_total",
        Help: "MLAG (aggr0) usage of the CN.",
        },
        []string{"device","type"},
    )
    gzZpoolList = prometheus.NewGaugeVec(prometheus.GaugeOpts{
        Name: "smartos_gz_zpool_list_total",
        Help: "ZFS zpool list summary.",
        },
        []string{"zpool","type"},
    )
    gzDiskErrors = prometheus.NewCounterVec(prometheus.CounterOpts{
        Name: "smartos_gz_disk_errors_total",
        Help: "Number of hardware disk errors.",
        },
        []string{"device","type"},
    )
)

// Global Helpers

// try to determine if its executed inside the GZ or not.
// return 1 if in GZ
//        0 if in zone
func isGlobalZone() (int) {
    out, eerr := exec.Command("zonename").Output()
    if eerr != nil {
        log.Fatal(eerr)
    }
    if (strings.Contains(string(out), "global")) == false {
        return 0
    } else {
        return 1
    }
}

// SmartOS command / tool callers
func iostat() {
    out, eerr := exec.Command("iostat", "-en").Output()
    if eerr != nil {
        log.Fatal(eerr)
    }
    perr := parseIostatOutput(string(out))
    if perr != nil {
        log.Fatal(perr)
    }
}

func mpstat() {
    out, eerr := exec.Command("mpstat", "1", "1").Output()
    if eerr != nil {
        log.Fatal(eerr)
    }
    perr := parseMpstatOutput(string(out))
    if perr != nil {
        log.Fatal(perr)
    }
}

func nicstat() {
    out, eerr := exec.Command("nicstat", "-i", "aggr0").Output()
    if eerr != nil {
        log.Fatal(eerr)
    }
    perr := parseNicstatOutput(string(out))
    if perr != nil {
        log.Fatal(perr)
    }
}

func vmstat() {
    out, eerr := exec.Command("vmstat", "1", "1").Output()
    if eerr != nil {
        log.Fatal(eerr)
    }
    perr := parseVmstatOutput(string(out))
    if perr != nil {
        log.Fatal(perr)
    }
}

func zpoolList() {
    out, eerr := exec.Command("zpool", "list", "-p", "zones").Output()
    if eerr != nil {
        log.Fatal(eerr)
    }
    perr := parseZpoolStatusOutput(string(out))
    if perr != nil {
        log.Fatal(perr)
    }
}

// Parsers

func parseIostatOutput(out string) (error) {
    outlines := strings.Split(out, "\n")
    l := len(outlines)
    for _, line := range outlines[2:l-1] {
        parsedLine := strings.Fields(line)
        deviceName := parsedLine[4]
        softErr, err := strconv.ParseFloat(parsedLine[0], 64)
        if err != nil {
            return err
        }
        hardErr, err := strconv.ParseFloat(parsedLine[1], 64)
        if err != nil {
            return err
        }
        trnErr, err := strconv.ParseFloat(parsedLine[2], 64)
        if err != nil {
            return err
        }
        gzDiskErrors.With(prometheus.Labels{"device":deviceName,"type":"soft"}).Add(softErr)
        gzDiskErrors.With(prometheus.Labels{"device":deviceName,"type":"hard"}).Add(hardErr)
        gzDiskErrors.With(prometheus.Labels{"device":deviceName,"type":"trn"}).Add(trnErr)
    }
    return nil
}

func parseMpstatOutput(out string) (error) {
    outlines := strings.Split(out, "\n")
    l := len(outlines)
    for _, line := range outlines[1:l-1] {
        parsedLine := strings.Fields(line)
        cpuId := parsedLine[0]
        cpuUsr, err := strconv.ParseFloat(parsedLine[12], 64)
        if err != nil {
            return err
        }
        cpuSys, err := strconv.ParseFloat(parsedLine[13], 64)
        if err != nil {
            return err
        }
        cpuIdl, err := strconv.ParseFloat(parsedLine[15], 64)
        if err != nil {
            return err
        }
        gzCpuUsage.With(prometheus.Labels{"cpu": cpuId, "type":"user"}).Set(cpuUsr)
        gzCpuUsage.With(prometheus.Labels{"cpu": cpuId, "type":"system"}).Set(cpuSys)
        gzCpuUsage.With(prometheus.Labels{"cpu": cpuId, "type":"idle"}).Set(cpuIdl)
        //fmt.Printf("cpuId : %d, cpuUsr : %d, cpuSys : %d \n", cpuId, cpuUsr, cpuSys)
    }
    return nil
}

func parseNicstatOutput(out string) (error) {
    outlines := strings.Split(out, "\n")
    l := len(outlines)
    for _, line := range outlines[1:l-1] {
        parsedLine := strings.Fields(line)
        readKb, err := strconv.ParseFloat(parsedLine[2], 64)
        if err != nil {
            return err
        }
        writeKb, err := strconv.ParseFloat(parsedLine[3], 64)
        if err != nil {
            return err
        }
        gzMlagUsage.With(prometheus.Labels{"device":"aggr0", "type":"read"}).Set(readKb)
        gzMlagUsage.With(prometheus.Labels{"device":"aggr0", "type":"write"}).Set(writeKb)
    }
    return nil
}

func parseVmstatOutput(out string) (error) {
    outlines := strings.Split(out, "\n")
    l := len(outlines)
    for _, line := range outlines[2:l-1] {
        parsedLine := strings.Fields(line)
        freeSwap, err := strconv.ParseFloat(parsedLine[3], 64)
        if err != nil {
            return err
        }
        freeRam, err := strconv.ParseFloat(parsedLine[4], 64)
        if err != nil {
            return err
        }
        gzFreeMem.With(prometheus.Labels{"type":"swap"}).Set(freeSwap)
        gzFreeMem.With(prometheus.Labels{"type":"ram"}).Set(freeRam)
    }
    return nil
}

func parseZpoolStatusOutput(out string) (error) {
    outlines := strings.Split(out, "\n")
    l := len(outlines)
    for _, line := range outlines[1:l-1] {
        parsedLine := strings.Fields(line)
        sizeBytes, err := strconv.ParseFloat(parsedLine[1], 64)
        if err != nil {
            return err
        }
        allocBytes, err := strconv.ParseFloat(parsedLine[2], 64)
        if err != nil {
            return err
        }
        freeBytes, err := strconv.ParseFloat(parsedLine[3], 64)
        if err != nil {
            return err
        }
        fragPercent := strings.TrimSuffix(parsedLine[5], "%")
        fragPercentTrim, err := strconv.ParseFloat(fragPercent, 64)
        if err != nil {
            return err
        }
        capPercent := strings.TrimSuffix(parsedLine[6], "%")
        capPercentTrim, err := strconv.ParseFloat(capPercent, 64)
        if err != nil {
            return err
        }
        health := parsedLine[8]
        if (strings.Contains(health, "ONLINE")) == true {
          gzZpoolList.With(prometheus.Labels{"zpool":"zones", "type":"faulty"}).Set(0)
        } else {
          gzZpoolList.With(prometheus.Labels{"zpool":"zones", "type":"faulty"}).Set(1)
        }

        gzZpoolList.With(prometheus.Labels{"zpool":"zones", "type":"size"}).Set(sizeBytes)
        gzZpoolList.With(prometheus.Labels{"zpool":"zones", "type":"alloc"}).Set(allocBytes)
        gzZpoolList.With(prometheus.Labels{"zpool":"zones", "type":"free"}).Set(freeBytes)
        gzZpoolList.With(prometheus.Labels{"zpool":"zones", "type":"frag"}).Set(fragPercentTrim)
        gzZpoolList.With(prometheus.Labels{"zpool":"zones", "type":"capacity"}).Set(capPercentTrim)
    }
    return nil
}

// program starter

func init() {
    // Metrics have to be registered to be exposed:
    gz := isGlobalZone()
    if gz == 1 {
        prometheus.MustRegister(gzCpuUsage)
        prometheus.MustRegister(gzFreeMem)
        prometheus.MustRegister(gzMlagUsage)
        prometheus.MustRegister(gzDiskErrors)
        prometheus.MustRegister(gzZpoolList)
    } else {
        // not yet implemented
        // XXX
        log.Fatal("zone statistics gathering is not yet implemented.")
    }
}

func main() {
    gz := isGlobalZone()
    if gz == 1 {
        mpstat()
        vmstat()
        nicstat()
        iostat()
        zpoolList()
    } else {
        // not yet implemented
        // XXX
        log.Fatal("zone statistics gathering is not yet implemented.")
    }

    // The Handler function provides a default handler to expose metrics
    // via an HTTP server. "/metrics" is the usual endpoint for that.
    http.Handle("/metrics", promhttp.Handler())
    log.Fatal(http.ListenAndServe(exporterPort, nil))
}
<commit_msg>Add loadaverage retrieval via uptime<commit_after>// Virtua SmartOS Prometheus exporter
//
// Worflow :
//  - detect if launched in GZ or into a Zone
//  - retrieve useful metrics
//  - expose them to http://xxx:9100/metrics (same as node_exporter)

package main

import (
        "log"
        "net/http"
        "os/exec"
        "strconv"
        "strings"
//      "fmt"
        // Prometheus Go toolset
        "github.com/prometheus/client_golang/prometheus"
        "github.com/prometheus/client_golang/prometheus/promhttp"
)

var (
    // Global variables
    exporterPort = ":9100"
    // Metrics definitions
    // common metrics
    LoadAverage1 = prometheus.NewGauge(prometheus.GaugeOpts{
        Name: "smartos_cpu_load1",
        Help: "CPU load average 1 minute.",
    })
    LoadAverage5 = prometheus.NewGauge(prometheus.GaugeOpts{
        Name: "smartos_cpu_load5",
        Help: "CPU load average 5 minutes.",
    })
    LoadAverage15 = prometheus.NewGauge(prometheus.GaugeOpts{
        Name: "smartos_cpu_load15",
        Help: "CPU load average 15 minutes.",
    })
    // global zone metrics
    gzCpuUsage = prometheus.NewGaugeVec(prometheus.GaugeOpts{
        Name: "smartos_gz_cpu_usage_total",
        Help: "CPU usage exposed in percent.",
        },
        []string{"cpu","type"},
    )
    gzDiskErrors = prometheus.NewCounterVec(prometheus.CounterOpts{
        Name: "smartos_gz_disk_errors_total",
        Help: "Number of hardware disk errors.",
        },
        []string{"device","type"},
    )
    gzFreeMem = prometheus.NewGaugeVec(prometheus.GaugeOpts{
        Name: "smartos_gz_memory_free_bytes_total",
        Help: "Total free memory (both RAM and Swap) of the CN.",
        },
        []string{"type"},
    )
    gzMlagUsage = prometheus.NewGaugeVec(prometheus.GaugeOpts{
        Name: "smartos_gz_network_mlag_bytes_total",
        Help: "MLAG (aggr0) usage of the CN.",
        },
        []string{"device","type"},
    )
    gzZpoolList = prometheus.NewGaugeVec(prometheus.GaugeOpts{
        Name: "smartos_gz_zpool_list_total",
        Help: "ZFS zpool list summary.",
        },
        []string{"zpool","type"},
    )
    // zone metrics
)

// Global Helpers

// try to determine if its executed inside the GZ or not.
// return 1 if in GZ
//        0 if in zone
func isGlobalZone() (int) {
    out, eerr := exec.Command("zonename").Output()
    if eerr != nil {
        log.Fatal(eerr)
    }
    if (strings.Contains(string(out), "global")) == false {
        return 0
    } else {
        return 1
    }
}

// SmartOS command / tool callers
func iostat() {
    out, eerr := exec.Command("iostat", "-en").Output()
    if eerr != nil {
        log.Fatal(eerr)
    }
    perr := parseIostatOutput(string(out))
    if perr != nil {
        log.Fatal(perr)
    }
}

func mpstat() {
    out, eerr := exec.Command("mpstat", "1", "1").Output()
    if eerr != nil {
        log.Fatal(eerr)
    }
    perr := parseMpstatOutput(string(out))
    if perr != nil {
        log.Fatal(perr)
    }
}

func nicstat() {
    out, eerr := exec.Command("nicstat", "-i", "aggr0").Output()
    if eerr != nil {
        log.Fatal(eerr)
    }
    perr := parseNicstatOutput(string(out))
    if perr != nil {
        log.Fatal(perr)
    }
}

func uptime() {
    out, eerr := exec.Command("uptime").Output()
    if eerr != nil {
        log.Fatal(eerr)
    }
    perr := parseUptimeOutput(string(out))
    if perr != nil {
        log.Fatal(perr)
    }
}

func vmstat() {
    out, eerr := exec.Command("vmstat", "1", "1").Output()
    if eerr != nil {
        log.Fatal(eerr)
    }
    perr := parseVmstatOutput(string(out))
    if perr != nil {
        log.Fatal(perr)
    }
}

func zpoolList() {
    out, eerr := exec.Command("zpool", "list", "-p", "zones").Output()
    if eerr != nil {
        log.Fatal(eerr)
    }
    perr := parseZpoolStatusOutput(string(out))
    if perr != nil {
        log.Fatal(perr)
    }
}

// Parsers

func parseIostatOutput(out string) (error) {
    outlines := strings.Split(out, "\n")
    l := len(outlines)
    for _, line := range outlines[2:l-1] {
        parsedLine := strings.Fields(line)
        deviceName := parsedLine[4]
        softErr, err := strconv.ParseFloat(parsedLine[0], 64)
        if err != nil {
            return err
        }
        hardErr, err := strconv.ParseFloat(parsedLine[1], 64)
        if err != nil {
            return err
        }
        trnErr, err := strconv.ParseFloat(parsedLine[2], 64)
        if err != nil {
            return err
        }
        gzDiskErrors.With(prometheus.Labels{"device":deviceName,"type":"soft"}).Add(softErr)
        gzDiskErrors.With(prometheus.Labels{"device":deviceName,"type":"hard"}).Add(hardErr)
        gzDiskErrors.With(prometheus.Labels{"device":deviceName,"type":"trn"}).Add(trnErr)
    }
    return nil
}

func parseMpstatOutput(out string) (error) {
    outlines := strings.Split(out, "\n")
    l := len(outlines)
    for _, line := range outlines[1:l-1] {
        parsedLine := strings.Fields(line)
        cpuId := parsedLine[0]
        cpuUsr, err := strconv.ParseFloat(parsedLine[12], 64)
        if err != nil {
            return err
        }
        cpuSys, err := strconv.ParseFloat(parsedLine[13], 64)
        if err != nil {
            return err
        }
        cpuIdl, err := strconv.ParseFloat(parsedLine[15], 64)
        if err != nil {
            return err
        }
        gzCpuUsage.With(prometheus.Labels{"cpu": cpuId, "type":"user"}).Set(cpuUsr)
        gzCpuUsage.With(prometheus.Labels{"cpu": cpuId, "type":"system"}).Set(cpuSys)
        gzCpuUsage.With(prometheus.Labels{"cpu": cpuId, "type":"idle"}).Set(cpuIdl)
        //fmt.Printf("cpuId : %d, cpuUsr : %d, cpuSys : %d \n", cpuId, cpuUsr, cpuSys)
    }
    return nil
}

func parseNicstatOutput(out string) (error) {
    outlines := strings.Split(out, "\n")
    l := len(outlines)
    for _, line := range outlines[1:l-1] {
        parsedLine := strings.Fields(line)
        readKb, err := strconv.ParseFloat(parsedLine[2], 64)
        if err != nil {
            return err
        }
        writeKb, err := strconv.ParseFloat(parsedLine[3], 64)
        if err != nil {
            return err
        }
        gzMlagUsage.With(prometheus.Labels{"device":"aggr0", "type":"read"}).Set(readKb)
        gzMlagUsage.With(prometheus.Labels{"device":"aggr0", "type":"write"}).Set(writeKb)
    }
    return nil
}

func parseUptimeOutput(out string) (error) {
    line := strings.Split(out, "\n")
    parsedLine := strings.Fields(line[0])
    load1, err := strconv.ParseFloat(parsedLine[9], 64)
    if err != nil {
        return err
    }
    load5, err := strconv.ParseFloat(parsedLine[10], 64)
    if err != nil {
        return err
    }
    load15, err := strconv.ParseFloat(parsedLine[11], 64)
    if err != nil {
        return err
    }
    LoadAverage1.Set(load1)
    LoadAverage5.Set(load5)
    LoadAverage15.Set(load15)
    return nil
}

func parseVmstatOutput(out string) (error) {
    outlines := strings.Split(out, "\n")
    l := len(outlines)
    for _, line := range outlines[2:l-1] {
        parsedLine := strings.Fields(line)
        freeSwap, err := strconv.ParseFloat(parsedLine[3], 64)
        if err != nil {
            return err
        }
        freeRam, err := strconv.ParseFloat(parsedLine[4], 64)
        if err != nil {
            return err
        }
        gzFreeMem.With(prometheus.Labels{"type":"swap"}).Set(freeSwap)
        gzFreeMem.With(prometheus.Labels{"type":"ram"}).Set(freeRam)
    }
    return nil
}

func parseZpoolStatusOutput(out string) (error) {
    outlines := strings.Split(out, "\n")
    l := len(outlines)
    for _, line := range outlines[1:l-1] {
        parsedLine := strings.Fields(line)
        sizeBytes, err := strconv.ParseFloat(parsedLine[1], 64)
        if err != nil {
            return err
        }
        allocBytes, err := strconv.ParseFloat(parsedLine[2], 64)
        if err != nil {
            return err
        }
        freeBytes, err := strconv.ParseFloat(parsedLine[3], 64)
        if err != nil {
            return err
        }
        fragPercent := strings.TrimSuffix(parsedLine[5], "%")
        fragPercentTrim, err := strconv.ParseFloat(fragPercent, 64)
        if err != nil {
            return err
        }
        capPercent := strings.TrimSuffix(parsedLine[6], "%")
        capPercentTrim, err := strconv.ParseFloat(capPercent, 64)
        if err != nil {
            return err
        }
        health := parsedLine[8]
        if (strings.Contains(health, "ONLINE")) == true {
          gzZpoolList.With(prometheus.Labels{"zpool":"zones", "type":"faulty"}).Set(0)
        } else {
          gzZpoolList.With(prometheus.Labels{"zpool":"zones", "type":"faulty"}).Set(1)
        }

        gzZpoolList.With(prometheus.Labels{"zpool":"zones", "type":"size"}).Set(sizeBytes)
        gzZpoolList.With(prometheus.Labels{"zpool":"zones", "type":"alloc"}).Set(allocBytes)
        gzZpoolList.With(prometheus.Labels{"zpool":"zones", "type":"free"}).Set(freeBytes)
        gzZpoolList.With(prometheus.Labels{"zpool":"zones", "type":"frag"}).Set(fragPercentTrim)
        gzZpoolList.With(prometheus.Labels{"zpool":"zones", "type":"capacity"}).Set(capPercentTrim)
    }
    return nil
}

// program starter

func init() {
    // Metrics have to be registered to be exposed:
    gz := isGlobalZone()
    if gz == 1 {
        prometheus.MustRegister(gzCpuUsage)
        prometheus.MustRegister(gzDiskErrors)
        prometheus.MustRegister(gzFreeMem)
        prometheus.MustRegister(gzMlagUsage)
        prometheus.MustRegister(gzZpoolList)
        prometheus.MustRegister(LoadAverage1)
        prometheus.MustRegister(LoadAverage5)
        prometheus.MustRegister(LoadAverage15)
    } else {
        // not yet implemented
        // XXX
        log.Fatal("zone statistics gathering is not yet implemented.")
    }
}

func main() {
    gz := isGlobalZone()
    if gz == 1 {
        iostat()
        mpstat()
        nicstat()
        uptime()
        vmstat()
        zpoolList()
    } else {
        // not yet implemented
        // XXX
        log.Fatal("zone statistics gathering is not yet implemented.")
    }

    // The Handler function provides a default handler to expose metrics
    // via an HTTP server. "/metrics" is the usual endpoint for that.
    http.Handle("/metrics", promhttp.Handler())
    log.Fatal(http.ListenAndServe(exporterPort, nil))
}
<|endoftext|>


<commit_before>package termite

import (
	"fmt"
	"log"
	"os"
	"path/filepath"
	"strings"
	"sync"
)

// A in-memory cache of attributes.
//
// Invariants: for all entries, we have their parent directories too
type AttributeCache struct {
	mutex      sync.RWMutex
	attributes map[string]*FileAttr
	cond       *sync.Cond
	busy       map[string]bool
	getter     func(name string) *FileAttr
	statter    func(name string) *os.FileInfo

	clients map[string]*attrCachePending

	Paranoia bool
}

type attrCachePending struct {
	client  AttributeCacheClient
	pending []*FileAttr
	busy    bool
}

type AttributeCacheClient interface {
	Id() string
	Send([]*FileAttr) os.Error
}

func (me *AttributeCache) RmClient(client AttributeCacheClient) {
	id := client.Id()
	me.mutex.Lock()
	defer me.mutex.Unlock()

	c := me.clients[id]
	if c != nil {
		c.pending = nil
		c.busy = false
		me.clients[id] = nil, false
		me.cond.Broadcast()
	}
}

func (me *AttributeCache) AddClient(client AttributeCacheClient) {
	id := client.Id()
	me.mutex.Lock()
	defer me.mutex.Unlock()
	_, ok := me.clients[id]
	if ok {
		log.Panicf("Already have client %q", id)
	}

	clData := attrCachePending{
		client:  client,
		pending: me.copyFiles().Files,
	}

	me.clients[id] = &clData
}

func (me *AttributeCache) Send(client AttributeCacheClient) os.Error {
	me.mutex.Lock()
	defer me.mutex.Unlock()
	id := client.Id()
	c := me.clients[id]
	if c == nil {
		return fmt.Errorf("client %q disappeared", id)
	}

	for c.busy {
		me.cond.Wait()
	}

	if len(c.pending) == 0 {
		return nil
	}
	p := c.pending
	c.pending = nil
	c.busy = true
	me.mutex.Unlock()
	err := c.client.Send(p)
	me.mutex.Lock()
	c.busy = false
	me.cond.Broadcast()
	return err
}

func (me *AttributeCache) Queue(fs FileSet) {
	me.mutex.Lock()
	defer me.mutex.Unlock()
	for _, w := range me.clients {
		w.pending = append(w.pending, fs.Files...)
	}
}

func NewAttributeCache(getter func(n string) *FileAttr,
	statter func(n string) *os.FileInfo) *AttributeCache {
	me := &AttributeCache{
		attributes: make(map[string]*FileAttr),
		busy:       map[string]bool{},
	}
	me.cond = sync.NewCond(&me.mutex)
	me.getter = getter
	me.statter = statter
	me.clients = make(map[string]*attrCachePending)
	return me
}

func (me *AttributeCache) Verify() {
	if !me.Paranoia {
		return
	}
	me.mutex.RLock()
	defer me.mutex.RUnlock()
	me.verify()
}

func (me *AttributeCache) verify() {
	if !me.Paranoia {
		return
	}
	for k, v := range me.attributes {
		if k != "" && filepath.Clean(k) != k {
			log.Panicf("Unclean path %q", k)
		}
		if v.Path != k {
			log.Panicf("attributes mismatch %q %#v", k, v)
		}
		if _, ok := me.busy[k]; ok {
			log.Panicf("busy and attributes entry for %q", k)
		}
		if v.Deletion() {
			log.Panicf("Attribute cache may not contain deletions %q", k)
		}
		if v.IsDirectory() && v.NameModeMap == nil {
			log.Panicf("dir has no NameModeMap %q", k)
		}
		for childName, mode := range v.NameModeMap {
			if strings.Contains(childName, "\000") || strings.Contains(childName, "/") || len(childName) == 0 {
				log.Panicf("%q has illegal child name %q: %o", k, childName, mode)
			}
			if mode == 0 {
				log.Panicf("child has 0 mode: %q.%q", k, childName)
			}
		}
		dir, base := SplitPath(k)
		if base != k {
			parent := me.attributes[dir]
			if v.Deletion() && parent != nil && parent.NameModeMap[base] != 0 {
				log.Panicf("Parent %q has entry for deleted %q", dir, base)
			}
			if !v.Deletion() && parent == nil {
				log.Panicf("Missing parent for %q", k)
			}
			if !v.Deletion() && parent.NameModeMap[base] == 0 {
				log.Panicf("Parent %q has no entry for %q", dir, base)
			}
		}
	}
}

func (me *AttributeCache) Have(name string) bool {
	me.mutex.RLock()
	defer me.mutex.RUnlock()
	_, ok := me.attributes[name]
	return ok
}

func (me *AttributeCache) Get(name string) (rep *FileAttr) {
	return me.get(name, false)
}

func (me *AttributeCache) GetDir(name string) (rep *FileAttr) {
	return me.get(name, true)
}

func (me *AttributeCache) localGet(name string, withdir bool) (rep *FileAttr) {
	me.mutex.RLock()
	defer me.mutex.RUnlock()

	rep, ok := me.attributes[name]
	if ok {
		return rep.Copy(withdir)
	}

	if name != "" {
		dir, base := SplitPath(name)
		dirAttr := me.attributes[dir]
		if dirAttr != nil && dirAttr.NameModeMap != nil && dirAttr.NameModeMap[base] == 0 {
			return &FileAttr{Path: name}
		}
	}
	return nil
}

func (me *AttributeCache) get(name string, withdir bool) (rep *FileAttr) {
	rep = me.localGet(name, withdir)

	if rep != nil {
		return rep
	}

	me.mutex.Lock()
	defer me.mutex.Unlock()
	return me.unsafeGet(name, withdir)
}

func (me *AttributeCache) unsafeGet(name string, withdir bool) (rep *FileAttr) {
	if name != "" {
		dir, base := SplitPath(name)
		dirAttr := me.unsafeGet(dir, true)
		if dirAttr.Deletion() || !dirAttr.IsDirectory() || dirAttr.NameModeMap[base] == 0 {
			return &FileAttr{Path: name}
		}
	}

	for me.busy[name] && me.attributes[name] == nil {
		me.cond.Wait()
	}
	rep, ok := me.attributes[name]
	if ok {
		return rep
	}
	me.busy[name] = true
	me.mutex.Unlock()

	rep = me.getter(name)

	me.mutex.Lock()
	if rep == nil {
		// This is an error, but what can we do?
		return &FileAttr{Path: name}
	}
	rep.Path = name

	if !rep.Deletion() {
		me.attributes[name] = rep
	}
	me.cond.Broadcast()
	me.busy[name] = false, false
	me.verify()
	return rep.Copy(withdir)
}

func (me *AttributeCache) Update(files []*FileAttr) {
	me.mutex.Lock()
	defer me.mutex.Unlock()
	me.update(files)
}

func (me *AttributeCache) update(files []*FileAttr) {
	defer me.verify()
	attributes := me.attributes
	for _, inF := range files {
		r := *inF
		if len(r.Path) > 0 && r.Path[0] == '/' {
			panic("Leading slash.")
		}

		dir, basename := SplitPath(r.Path)
		if basename != "" {
			dirAttr := attributes[dir]
			if dirAttr == nil {
				log.Println("Discarding update: ", r)
				continue
			}
			if dirAttr.NameModeMap == nil {
				log.Panicf("parent dir has no NameModeMap: %q", dir)
			}
			if r.Deletion() {
				dirAttr.NameModeMap[basename] = 0, false
			} else {
				dirAttr.NameModeMap[basename] = FileMode(r.Mode &^ 07777)
			}
		}

		if r.Deletion() {
			attributes[r.Path] = nil, false
			continue
		}

		old := attributes[r.Path]
		if old == nil && r.IsDirectory() && r.NameModeMap == nil {
			// This is a metadata update only. If it does
			// not come with contents, we can't use it to
			// short-cut deletion queries.
			log.Println("Discarding contentless dir update: ", r)
			continue
		}

		if old == nil {
			attributes[r.Path] = &r
		} else {
			old.Merge(r)
		}
		me.busy[r.Path] = false, false
	}
	me.cond.Broadcast()
}

func (me *AttributeCache) Refresh(prefix string) FileSet {
	me.mutex.Lock()
	defer me.mutex.Unlock()

	if prefix != "" && prefix[0] == '/' {
		panic("leading /")
	}

	updated := []*FileAttr{}
	for key, attr := range me.attributes {
		// TODO -should just do everything?
		if !strings.HasPrefix(key, prefix) {
			continue
		}

		fi := me.statter(key)
		if fi == nil && !attr.Deletion() {
			del := FileAttr{
				Path: key,
			}
			updated = append(updated, &del)
		}

		// TODO - should generate deletion based on dir
		// contents too?

		// TODO - does this handle symlinks corrrectly?
		if fi != nil && attr.FileInfo != nil && EncodeFileInfo(*attr.FileInfo) != EncodeFileInfo(*fi) {
			newEnt := me.getter(key)
			newEnt.Path = key
			updated = append(updated, newEnt)
		}
	}
	fs := FileSet{Files: updated}
	fs.Sort()

	me.update(fs.Files)
	return fs
}

func (me *AttributeCache) Copy() FileSet {
	me.mutex.RLock()
	defer me.mutex.RUnlock()

	return me.copyFiles()
}

func (me *AttributeCache) copyFiles() FileSet {
	dump := []*FileAttr{}
	for _, attr := range me.attributes {
		dump = append(dump, attr.Copy(true))
	}

	fs := FileSet{Files: dump}
	fs.Sort()
	return fs
}
<commit_msg>For every getattr, send a copy to all clients.<commit_after>package termite

import (
	"fmt"
	"log"
	"os"
	"path/filepath"
	"strings"
	"sync"
)

// A in-memory cache of attributes.
//
// Invariants: for all entries, we have their parent directories too
type AttributeCache struct {
	mutex      sync.RWMutex
	attributes map[string]*FileAttr
	cond       *sync.Cond
	busy       map[string]bool
	getter     func(name string) *FileAttr
	statter    func(name string) *os.FileInfo

	clients map[string]*attrCachePending

	Paranoia bool
}

type attrCachePending struct {
	client  AttributeCacheClient
	pending []*FileAttr
	busy    bool
}

type AttributeCacheClient interface {
	Id() string
	Send([]*FileAttr) os.Error
}

func (me *AttributeCache) RmClient(client AttributeCacheClient) {
	id := client.Id()
	me.mutex.Lock()
	defer me.mutex.Unlock()

	c := me.clients[id]
	if c != nil {
		c.pending = nil
		c.busy = false
		me.clients[id] = nil, false
		me.cond.Broadcast()
	}
}

func (me *AttributeCache) AddClient(client AttributeCacheClient) {
	id := client.Id()
	me.mutex.Lock()
	defer me.mutex.Unlock()
	_, ok := me.clients[id]
	if ok {
		log.Panicf("Already have client %q", id)
	}

	clData := attrCachePending{
		client:  client,
		pending: me.copyFiles().Files,
	}

	me.clients[id] = &clData
}

func (me *AttributeCache) Send(client AttributeCacheClient) os.Error {
	me.mutex.Lock()
	defer me.mutex.Unlock()
	id := client.Id()
	c := me.clients[id]
	if c == nil {
		return fmt.Errorf("client %q disappeared", id)
	}

	for c.busy {
		me.cond.Wait()
	}

	if len(c.pending) == 0 {
		return nil
	}
	p := c.pending
	c.pending = nil
	c.busy = true
	me.mutex.Unlock()
	err := c.client.Send(p)
	me.mutex.Lock()
	c.busy = false
	me.cond.Broadcast()
	return err
}

func (me *AttributeCache) Queue(fs FileSet) {
	me.mutex.Lock()
	defer me.mutex.Unlock()
	for _, w := range me.clients {
		w.pending = append(w.pending, fs.Files...)
	}
}

func NewAttributeCache(getter func(n string) *FileAttr,
	statter func(n string) *os.FileInfo) *AttributeCache {
	me := &AttributeCache{
		attributes: make(map[string]*FileAttr),
		busy:       map[string]bool{},
	}
	me.cond = sync.NewCond(&me.mutex)
	me.getter = getter
	me.statter = statter
	me.clients = make(map[string]*attrCachePending)
	return me
}

func (me *AttributeCache) Verify() {
	if !me.Paranoia {
		return
	}
	me.mutex.RLock()
	defer me.mutex.RUnlock()
	me.verify()
}

func (me *AttributeCache) verify() {
	if !me.Paranoia {
		return
	}
	for k, v := range me.attributes {
		if k != "" && filepath.Clean(k) != k {
			log.Panicf("Unclean path %q", k)
		}
		if v.Path != k {
			log.Panicf("attributes mismatch %q %#v", k, v)
		}
		if _, ok := me.busy[k]; ok {
			log.Panicf("busy and attributes entry for %q", k)
		}
		if v.Deletion() {
			log.Panicf("Attribute cache may not contain deletions %q", k)
		}
		if v.IsDirectory() && v.NameModeMap == nil {
			log.Panicf("dir has no NameModeMap %q", k)
		}
		for childName, mode := range v.NameModeMap {
			if strings.Contains(childName, "\000") || strings.Contains(childName, "/") || len(childName) == 0 {
				log.Panicf("%q has illegal child name %q: %o", k, childName, mode)
			}
			if mode == 0 {
				log.Panicf("child has 0 mode: %q.%q", k, childName)
			}
		}
		dir, base := SplitPath(k)
		if base != k {
			parent := me.attributes[dir]
			if v.Deletion() && parent != nil && parent.NameModeMap[base] != 0 {
				log.Panicf("Parent %q has entry for deleted %q", dir, base)
			}
			if !v.Deletion() && parent == nil {
				log.Panicf("Missing parent for %q", k)
			}
			if !v.Deletion() && parent.NameModeMap[base] == 0 {
				log.Panicf("Parent %q has no entry for %q", dir, base)
			}
		}
	}
}

func (me *AttributeCache) Have(name string) bool {
	me.mutex.RLock()
	defer me.mutex.RUnlock()
	_, ok := me.attributes[name]
	return ok
}

func (me *AttributeCache) Get(name string) (rep *FileAttr) {
	return me.get(name, false)
}

func (me *AttributeCache) GetDir(name string) (rep *FileAttr) {
	return me.get(name, true)
}

func (me *AttributeCache) localGet(name string, withdir bool) (rep *FileAttr) {
	me.mutex.RLock()
	defer me.mutex.RUnlock()

	rep, ok := me.attributes[name]
	if ok {
		return rep.Copy(withdir)
	}

	if name != "" {
		dir, base := SplitPath(name)
		dirAttr := me.attributes[dir]
		if dirAttr != nil && dirAttr.NameModeMap != nil && dirAttr.NameModeMap[base] == 0 {
			return &FileAttr{Path: name}
		}
	}
	return nil
}

func (me *AttributeCache) get(name string, withdir bool) (rep *FileAttr) {
	rep = me.localGet(name, withdir)

	if rep != nil {
		return rep
	}

	me.mutex.Lock()
	defer me.mutex.Unlock()
	return me.unsafeGet(name, withdir)
}

func (me *AttributeCache) unsafeGet(name string, withdir bool) (rep *FileAttr) {
	if name != "" {
		dir, base := SplitPath(name)
		dirAttr := me.unsafeGet(dir, true)
		if dirAttr.Deletion() || !dirAttr.IsDirectory() || dirAttr.NameModeMap[base] == 0 {
			return &FileAttr{Path: name}
		}
	}

	for me.busy[name] && me.attributes[name] == nil {
		me.cond.Wait()
	}
	rep, ok := me.attributes[name]
	if ok {
		return rep
	}
	me.busy[name] = true
	me.mutex.Unlock()

	rep = me.getter(name)

	me.mutex.Lock()
	if rep == nil {
		// This is an error, but what can we do?
		return &FileAttr{Path: name}
	}
	rep.Path = name

	if !rep.Deletion() {
		me.attributes[name] = rep
	}
	me.cond.Broadcast()
	me.busy[name] = false, false
	me.verify()

	c := rep.Copy(withdir)
	sendCopy := rep.Copy(true)
	for _, w := range me.clients {
		w.pending = append(w.pending, sendCopy)
	}
	return c
}

func (me *AttributeCache) Update(files []*FileAttr) {
	me.mutex.Lock()
	defer me.mutex.Unlock()
	me.update(files)
}

func (me *AttributeCache) update(files []*FileAttr) {
	defer me.verify()
	attributes := me.attributes
	for _, inF := range files {
		r := *inF
		if len(r.Path) > 0 && r.Path[0] == '/' {
			panic("Leading slash.")
		}

		dir, basename := SplitPath(r.Path)
		if basename != "" {
			dirAttr := attributes[dir]
			if dirAttr == nil {
				log.Println("Discarding update: ", r)
				continue
			}
			if dirAttr.NameModeMap == nil {
				log.Panicf("parent dir has no NameModeMap: %q", dir)
			}
			if r.Deletion() {
				dirAttr.NameModeMap[basename] = 0, false
			} else {
				dirAttr.NameModeMap[basename] = FileMode(r.Mode &^ 07777)
			}
		}

		if r.Deletion() {
			attributes[r.Path] = nil, false
			continue
		}

		old := attributes[r.Path]
		if old == nil && r.IsDirectory() && r.NameModeMap == nil {
			// This is a metadata update only. If it does
			// not come with contents, we can't use it to
			// short-cut deletion queries.
			log.Println("Discarding contentless dir update: ", r)
			continue
		}

		if old == nil {
			attributes[r.Path] = &r
		} else {
			old.Merge(r)
		}
		me.busy[r.Path] = false, false
	}
	me.cond.Broadcast()
}

func (me *AttributeCache) Refresh(prefix string) FileSet {
	me.mutex.Lock()
	defer me.mutex.Unlock()

	if prefix != "" && prefix[0] == '/' {
		panic("leading /")
	}

	updated := []*FileAttr{}
	for key, attr := range me.attributes {
		// TODO -should just do everything?
		if !strings.HasPrefix(key, prefix) {
			continue
		}

		fi := me.statter(key)
		if fi == nil && !attr.Deletion() {
			del := FileAttr{
				Path: key,
			}
			updated = append(updated, &del)
		}

		// TODO - should generate deletion based on dir
		// contents too?

		// TODO - does this handle symlinks corrrectly?
		if fi != nil && attr.FileInfo != nil && EncodeFileInfo(*attr.FileInfo) != EncodeFileInfo(*fi) {
			newEnt := me.getter(key)
			newEnt.Path = key
			updated = append(updated, newEnt)
		}
	}
	fs := FileSet{Files: updated}
	fs.Sort()

	me.update(fs.Files)
	return fs
}

func (me *AttributeCache) Copy() FileSet {
	me.mutex.RLock()
	defer me.mutex.RUnlock()

	return me.copyFiles()
}

func (me *AttributeCache) copyFiles() FileSet {
	dump := []*FileAttr{}
	for _, attr := range me.attributes {
		dump = append(dump, attr.Copy(true))
	}

	fs := FileSet{Files: dump}
	fs.Sort()
	return fs
}
<|endoftext|>


<commit_before>package reader

import (
	"fmt"
	"io"
	"os"

	"gitlab.com/gomidi/midi/smf"
	"gitlab.com/gomidi/midi/smf/smfreader"
)

// ReadSMFFile open, reads and closes a complete SMF file.
// If the read content was a valid midi file, nil is returned.
//
// The messages are dispatched to the corresponding attached functions of the handler.
//
// They must be attached before Reader.ReadSMF is called
// and they must not be unset or replaced until ReadSMF returns.
// For more infomation about dealing with the SMF midi messages, see Reader and
// SMFPosition.
func ReadSMFFile(r *Reader, file string, options ...smfreader.Option) error {
	r.errSMF = nil
	r.pos = &Position{}
	r.reset()
	err := smfreader.ReadFile(file, r.readSMF2, options...)
	if err != nil && err != smf.ErrFinished {
		return err
	}
	if r.errSMF == smf.ErrFinished {
		return nil
	}
	return nil
}

// ReadSMFFileHeader reads just the header of a SMF file
func ReadSMFFileHeader(file string, options ...smfreader.Option) (smf.Header, error) {
	r := New(NoLogger())
	r.errSMF = nil
	r.pos = &Position{}
	r.reset()
	f, err := os.Open(file)
	if err != nil {
		fmt.Printf("can't open file: %v", err)
		return smf.Header{}, err
	}
	r.reader = smfreader.New(f, options...)

	err2 := r.ReadHeader()

	f.Close()

	if err2 != nil && err2 != smf.ErrFinished {
		return r.Header(), err2
	}
	return r.Header(), nil
}

func ReadSMFFrom(r *Reader, rd smf.Reader) error {
	r.errSMF = nil
	r.pos = &Position{}
	r.reset()
	r.reader = rd

	err := r.ReadHeader()
	if err != nil {
		return err
	}
	r.readSMF()

	if r.errSMF == smf.ErrFinished {
		return nil
	}
	return r.errSMF
}

// ReadAllSMF reads midi messages from src (which is supposed to be the content of a standard midi file (SMF))
// until an error or io.EOF happens.
//
// ReadAllSMF does not close the src.
//
// If the read content was a valid midi file, nil is returned.
//
// The messages are dispatched to the corresponding attached functions of the Reader.
//
// They must be attached before Reader.ReadSMF is called
// and they must not be unset or replaced until ReadSMF returns.
// For more infomation about dealing with the SMF midi messages, see Reader and
// SMFPosition.
func ReadSMF(r *Reader, src io.Reader, options ...smfreader.Option) error {
	return ReadSMFFrom(r, smfreader.New(src, options...))
}

func (r *Reader) setHeader(hd smf.Header) {
	r.header = hd

	if metric, isMetric := r.header.TimeFormat.(smf.MetricTicks); isMetric {
		r.resolution = metric
	}

	if r.smfheader != nil {
		r.smfheader(r.header)
	}
}

func (r *Reader) readSMF2(rd smf.Reader) {
	r.reader = rd
	err := r.ReadHeader()
	if err != nil {
		r.errSMF = err
	}
	r.readSMF()
}

func (r *Reader) readSMF() {
	err := r.dispatch()
	if err != io.EOF {
		r.errSMF = err
	}
}

/*
These are differences  some differences between the two:
  - SMF events have a message and a delta time while live MIDI just has messages
  - a SMF file has a header
  - a SMF file may have meta messages (e.g. track name, copyright etc) while live MIDI must not have them
  - live MIDI may have realtime and syscommon messages while a SMF must not have them

In sum the only MIDI message handling that could be shared in practice is the handling of channel
and sysex messages.

That is reflected in the type signature of the callbacks and results in three kinds of callbacks:
  - The ones that don't receive a SMFPosition are called for messages that may only appear live
  - The ones with that receive a SMFPosition are called for messages that may only appear
    within a SMF file
  - The onse with that receive a *SMFPosition are called for messages that may appear live
    and within a SMF file. In a SMF file the pointer is never nil while in a live situation it always is.

Due to the nature of a SMF file, the tracks have no "assigned numbers", and can in the worst case just be
distinguished by the order in which they appear inside the file.

In addition the delta times are just relative to the previous message inside the track.

This has some severe consequences:

  - Eeven if the user is just interested in some kind of messages (e.g. note on and note off),
    he still has to deal with the delta times of every message.
  - At the beginning of each track he has to reset the tracking time.

This procedure is error prone and therefor SMFPosition provides a helper that contains not just
the original delta (which shouldn't be needed most of the time), but also the absolute position (in ticks)
and order number of the track in which the message appeared.

This way the user can ignore the messages and tracks he is not interested in.

See the example for a handler that handles both live and SMF messages.

For writing there is a LiveWriter for "over the wire" writing and a SMFWriter to write SMF files.

*/
<commit_msg>add ReadSMFHeader<commit_after>package reader

import (
	"fmt"
	"io"
	"os"

	"gitlab.com/gomidi/midi/smf"
	"gitlab.com/gomidi/midi/smf/smfreader"
)

// ReadSMFFile open, reads and closes a complete SMF file.
// If the read content was a valid midi file, nil is returned.
//
// The messages are dispatched to the corresponding attached functions of the handler.
//
// They must be attached before Reader.ReadSMF is called
// and they must not be unset or replaced until ReadSMF returns.
// For more infomation about dealing with the SMF midi messages, see Reader and
// SMFPosition.
func ReadSMFFile(r *Reader, file string, options ...smfreader.Option) error {
	r.errSMF = nil
	r.pos = &Position{}
	r.reset()
	err := smfreader.ReadFile(file, r.readSMF2, options...)
	if err != nil && err != smf.ErrFinished {
		return err
	}
	if r.errSMF == smf.ErrFinished {
		return nil
	}
	return nil
}

// ReadSMFFileHeader reads just the header of a SMF file
func ReadSMFFileHeader(file string, options ...smfreader.Option) (smf.Header, error) {
	r := New(NoLogger())
	r.errSMF = nil
	r.pos = &Position{}
	r.reset()
	f, err := os.Open(file)
	if err != nil {
		fmt.Printf("can't open file: %v", err)
		return smf.Header{}, err
	}
	r.reader = smfreader.New(f, options...)

	err2 := r.ReadHeader()

	f.Close()

	if err2 != nil && err2 != smf.ErrFinished {
		return r.Header(), err2
	}
	return r.Header(), nil
}

func ReadSMFFrom(r *Reader, rd smf.Reader) error {
	r.errSMF = nil
	r.pos = &Position{}
	r.reset()
	r.reader = rd

	err := r.ReadHeader()
	if err != nil {
		return err
	}
	r.readSMF()

	if r.errSMF == smf.ErrFinished {
		return nil
	}
	return r.errSMF
}

// ReadAllSMF reads midi messages from src (which is supposed to be the content of a standard midi file (SMF))
// until an error or io.EOF happens.
//
// ReadAllSMF does not close the src.
//
// If the read content was a valid midi file, nil is returned.
//
// The messages are dispatched to the corresponding attached functions of the Reader.
//
// They must be attached before Reader.ReadSMF is called
// and they must not be unset or replaced until ReadSMF returns.
// For more infomation about dealing with the SMF midi messages, see Reader and
// SMFPosition.
func ReadSMF(r *Reader, src io.Reader, options ...smfreader.Option) error {
	return ReadSMFFrom(r, smfreader.New(src, options...))
}

func (r *Reader) setHeader(hd smf.Header) {
	r.header = hd

	if metric, isMetric := r.header.TimeFormat.(smf.MetricTicks); isMetric {
		r.resolution = metric
	}

	if r.smfheader != nil {
		r.smfheader(r.header)
	}
}

// ReadSMFHeader reads just the header of a SMF
func ReadSMFHeader(in io.Reader, options ...smfreader.Option) (smf.Header, error) {
	r := New(NoLogger())
	r.errSMF = nil
	r.pos = &Position{}
	r.reset()
	r.reader = smfreader.New(in, options...)
	err := r.ReadHeader()
	if err != nil && err != smf.ErrFinished {
		return r.Header(), err
	}
	return r.Header(), nil
}

func (r *Reader) readSMF2(rd smf.Reader) {
	r.reader = rd
	err := r.ReadHeader()
	if err != nil {
		r.errSMF = err
	}
	r.readSMF()
}

func (r *Reader) readSMF() {
	err := r.dispatch()
	if err != io.EOF {
		r.errSMF = err
	}
}

/*
These are differences  some differences between the two:
  - SMF events have a message and a delta time while live MIDI just has messages
  - a SMF file has a header
  - a SMF file may have meta messages (e.g. track name, copyright etc) while live MIDI must not have them
  - live MIDI may have realtime and syscommon messages while a SMF must not have them

In sum the only MIDI message handling that could be shared in practice is the handling of channel
and sysex messages.

That is reflected in the type signature of the callbacks and results in three kinds of callbacks:
  - The ones that don't receive a SMFPosition are called for messages that may only appear live
  - The ones with that receive a SMFPosition are called for messages that may only appear
    within a SMF file
  - The onse with that receive a *SMFPosition are called for messages that may appear live
    and within a SMF file. In a SMF file the pointer is never nil while in a live situation it always is.

Due to the nature of a SMF file, the tracks have no "assigned numbers", and can in the worst case just be
distinguished by the order in which they appear inside the file.

In addition the delta times are just relative to the previous message inside the track.

This has some severe consequences:

  - Eeven if the user is just interested in some kind of messages (e.g. note on and note off),
    he still has to deal with the delta times of every message.
  - At the beginning of each track he has to reset the tracking time.

This procedure is error prone and therefor SMFPosition provides a helper that contains not just
the original delta (which shouldn't be needed most of the time), but also the absolute position (in ticks)
and order number of the track in which the message appeared.

This way the user can ignore the messages and tracks he is not interested in.

See the example for a handler that handles both live and SMF messages.

For writing there is a LiveWriter for "over the wire" writing and a SMFWriter to write SMF files.

*/
<|endoftext|>


<commit_before>package terraform

import (
	"fmt"

	"github.com/hashicorp/go-version"
)

// The main version number that is being run at the moment.
const Version = "0.7.0"

// A pre-release marker for the version. If this is "" (empty string)
// then it means that it is a final release. Otherwise, this is a pre-release
// such as "dev" (in development), "beta", "rc1", etc.
const VersionPrerelease = "dev"

// SemVersion is an instance of version.Version. This has the secondary
// benefit of verifying during tests and init time that our version is a
// proper semantic version, which should always be the case.
var SemVersion = version.Must(version.NewVersion(Version))

// VersionHeader is the header name used to send the current terraform version
// in http requests.
const VersionHeader = "Terraform-Version"

func VersionString() string {
	if VersionPrerelease != "" {
		return fmt.Sprintf("%s-%s", Version, VersionPrerelease)
	}
	return Version
}
<commit_msg>v0.7.0-rc4<commit_after>package terraform

import (
	"fmt"

	"github.com/hashicorp/go-version"
)

// The main version number that is being run at the moment.
const Version = "0.7.0"

// A pre-release marker for the version. If this is "" (empty string)
// then it means that it is a final release. Otherwise, this is a pre-release
// such as "dev" (in development), "beta", "rc1", etc.
const VersionPrerelease = "rc4"

// SemVersion is an instance of version.Version. This has the secondary
// benefit of verifying during tests and init time that our version is a
// proper semantic version, which should always be the case.
var SemVersion = version.Must(version.NewVersion(Version))

// VersionHeader is the header name used to send the current terraform version
// in http requests.
const VersionHeader = "Terraform-Version"

func VersionString() string {
	if VersionPrerelease != "" {
		return fmt.Sprintf("%s-%s", Version, VersionPrerelease)
	}
	return Version
}
<|endoftext|>


<commit_before>// Copyright 2017 The Go 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 sfnt

/*
This file contains opt-in tests for popular, high quality, proprietary fonts,
made by companies such as Adobe and Microsoft. These fonts are generally
available, but copies are not explicitly included in this repository due to
licensing differences or file size concerns. To opt-in, run:

go test golang.org/x/image/font/sfnt -args -proprietary

Not all tests pass out-of-the-box on all systems. For example, the Microsoft
Times New Roman font is downloadable gratis even on non-Windows systems, but as
per the ttf-mscorefonts-installer Debian package, this requires accepting an
End User License Agreement (EULA) and a CAB format decoder. These tests assume
that such fonts have already been installed. You may need to specify the
directories for these fonts:

go test golang.org/x/image/font/sfnt -args -proprietary -adobeDir=/foo/bar/aFonts -microsoftDir=/foo/bar/mFonts

To only run those tests for the Microsoft fonts:

go test golang.org/x/image/font/sfnt -test.run=ProprietaryMicrosoft -args -proprietary
*/

// TODO: add Apple system fonts? Google fonts (Droid? Noto?)? Emoji fonts?

// TODO: enable Apple/Microsoft tests by default on Darwin/Windows?

import (
	"flag"
	"io/ioutil"
	"path/filepath"
	"testing"
)

var (
	proprietary = flag.Bool("proprietary", false, "test proprietary fonts not included in this repository")

	adobeDir = flag.String(
		"adobeDir",
		// This needs to be set explicitly. There is no default dir on Debian:
		// https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=736680
		//
		// Get the fonts from https://github.com/adobe-fonts, e.g.:
		//	- https://github.com/adobe-fonts/source-code-pro/releases/latest
		//	- https://github.com/adobe-fonts/source-han-sans/releases/latest
		//	- https://github.com/adobe-fonts/source-sans-pro/releases/latest
		//
		// Copy all of the TTF and OTF files to the one directory, such as
		// $HOME/adobe-fonts, and pass that as the -adobeDir flag here.
		"",
		"directory name for the Adobe proprietary fonts",
	)

	microsoftDir = flag.String(
		"microsoftDir",
		"/usr/share/fonts/truetype/msttcorefonts",
		"directory name for the Microsoft proprietary fonts",
	)
)

func TestProprietaryAdobeSourceCodeProOTF(t *testing.T) {
	testProprietary(t, "adobe", "SourceCodePro-Regular.otf", 1500, 2)
}

func TestProprietaryAdobeSourceCodeProTTF(t *testing.T) {
	testProprietary(t, "adobe", "SourceCodePro-Regular.ttf", 1500, 36)
}

func TestProprietaryAdobeSourceHanSansSC(t *testing.T) {
	testProprietary(t, "adobe", "SourceHanSansSC-Regular.otf", 65535, 2)
}

func TestProprietaryAdobeSourceSansProOTF(t *testing.T) {
	testProprietary(t, "adobe", "SourceSansPro-Regular.otf", 1800, 2)
}

func TestProprietaryAdobeSourceSansProTTF(t *testing.T) {
	// The 1000 here is smaller than the 1800 above. For some reason, the TTF
	// version of the file has fewer glyphs than the (presumably canonical) OTF
	// version. The number of glyphs in the .otf and .ttf files can be verified
	// with the ttx tool.
	testProprietary(t, "adobe", "SourceSansPro-Regular.ttf", 1000, 56)
}

func TestProprietaryMicrosoftArial(t *testing.T) {
	testProprietary(t, "microsoft", "Arial.ttf", 1200, 98)
}

func TestProprietaryMicrosoftComicSansMS(t *testing.T) {
	testProprietary(t, "microsoft", "Comic_Sans_MS.ttf", 550, 98)
}

func TestProprietaryMicrosoftTimesNewRoman(t *testing.T) {
	testProprietary(t, "microsoft", "Times_New_Roman.ttf", 1200, 98)
}

func TestProprietaryMicrosoftWebdings(t *testing.T) {
	testProprietary(t, "microsoft", "Webdings.ttf", 200, -1)
}

// testProprietary tests that we can load every glyph in the named font.
//
// The exact number of glyphs in the font can differ across its various
// versions, but as a sanity check, there should be at least minNumGlyphs.
//
// While this package is a work-in-progress, not every glyph can be loaded. The
// firstUnsupportedGlyph argument, if non-negative, is the index of the first
// unsupported glyph in the font. This number should increase over time (or set
// negative), as the TODO's in this package are done.
func testProprietary(t *testing.T, proprietor, filename string, minNumGlyphs, firstUnsupportedGlyph int) {
	if !*proprietary {
		t.Skip("skipping proprietary font test")
	}

	file, err := []byte(nil), error(nil)
	switch proprietor {
	case "adobe":
		file, err = ioutil.ReadFile(filepath.Join(*adobeDir, filename))
		if err != nil {
			t.Fatalf("%v\nPerhaps you need to set the -adobeDir=%v flag?", err, *adobeDir)
		}
	case "microsoft":
		file, err = ioutil.ReadFile(filepath.Join(*microsoftDir, filename))
		if err != nil {
			t.Fatalf("%v\nPerhaps you need to set the -microsoftDir=%v flag?", err, *microsoftDir)
		}
	default:
		panic("unreachable")
	}
	f, err := Parse(file)
	if err != nil {
		t.Fatalf("Parse: %v", err)
	}

	numGlyphs := f.NumGlyphs()
	if numGlyphs < minNumGlyphs {
		t.Fatalf("NumGlyphs: got %d, want at least %d", numGlyphs, minNumGlyphs)
	}

	var buf Buffer
	iMax := numGlyphs
	if firstUnsupportedGlyph >= 0 {
		iMax = firstUnsupportedGlyph
	}
	for i, numErrors := 0, 0; i < iMax; i++ {
		if _, err := f.LoadGlyph(&buf, GlyphIndex(i), nil); err != nil {
			t.Errorf("LoadGlyph(%d): %v", i, err)
			numErrors++
		}
		if numErrors == 10 {
			t.Fatal("LoadGlyph: too many errors")
		}
	}

	for r, want := range proprietaryGlyphIndexTestCases[proprietor+"/"+filename] {
		got, err := f.GlyphIndex(&buf, r)
		if err != nil {
			t.Errorf("GlyphIndex(%q): %v", r, err)
			continue
		}
		if got != want {
			t.Errorf("GlyphIndex(%q): got %d, want %d", r, got, want)
			continue
		}
	}
}

// proprietaryGlyphIndexTestCases hold a sample of each font's rune to glyph
// index cmap. The numerical values can be verified by running the ttx tool.
var proprietaryGlyphIndexTestCases = map[string]map[rune]GlyphIndex{
	"adobe/SourceCodePro-Regular.otf": {
		'\u0030':     877,  // U+0030 DIGIT ZERO
		'\u0041':     2,    // U+0041 LATIN CAPITAL LETTER A
		'\u0061':     28,   // U+0061 LATIN SMALL LETTER A
		'\u0104':     64,   // U+0104 LATIN CAPITAL LETTER A WITH OGONEK
		'\u0125':     323,  // U+0125 LATIN SMALL LETTER H WITH CIRCUMFLEX
		'\u01f4':     111,  // U+01F4 LATIN CAPITAL LETTER G WITH ACUTE
		'\u03a3':     623,  // U+03A3 GREEK CAPITAL LETTER SIGMA
		'\u2569':     1500, // U+2569 BOX DRAWINGS DOUBLE UP AND HORIZONTAL
		'\U0001f100': 0,    // U+0001F100 DIGIT ZERO FULL STOP
	},
	"adobe/SourceCodePro-Regular.ttf": {
		'\u0030': 877, // U+0030 DIGIT ZERO
		'\u0041': 2,   // U+0041 LATIN CAPITAL LETTER A
		'\u01f4': 111, // U+01F4 LATIN CAPITAL LETTER G WITH ACUTE
	},
	"adobe/SourceHanSansSC-Regular.otf": {
		'\u0030':     17,    // U+0030 DIGIT ZERO
		'\u0041':     34,    // U+0041 LATIN CAPITAL LETTER A
		'\u00d7':     150,   // U+00D7 MULTIPLICATION SIGN
		'\u1100':     365,   // U+1100 HANGUL CHOSEONG KIYEOK
		'\u25ca':     1254,  // U+25CA LOZENGE
		'\u2e9c':     1359,  // U+2E9C CJK RADICAL SUN
		'\u304b':     1463,  // U+304B HIRAGANA LETTER KA
		'\u4e2d':     9893,  // U+4E2D <CJK Ideograph>, 中
		'\ua960':     47537, // U+A960 HANGUL CHOSEONG TIKEUT-MIEUM
		'\ufb00':     58919, // U+FB00 LATIN SMALL LIGATURE FF
		'\uffee':     59213, // U+FFEE HALFWIDTH WHITE CIRCLE
		'\U0001f100': 59214, // U+0001F100 DIGIT ZERO FULL STOP
		'\U0001f248': 59449, // U+0001F248 TORTOISE SHELL BRACKETED CJK UNIFIED IDEOGRAPH-6557
		'\U0002f9f4': 61768, // U+0002F9F4 CJK COMPATIBILITY IDEOGRAPH-2F9F4
	},
	"adobe/SourceSansPro-Regular.otf": {
		'\u0041': 2,    // U+0041 LATIN CAPITAL LETTER A
		'\u03a3': 592,  // U+03A3 GREEK CAPITAL LETTER SIGMA
		'\u0435': 999,  // U+0435 CYRILLIC SMALL LETTER IE
		'\u2030': 1728, // U+2030 PER MILLE SIGN
	},
	"adobe/SourceSansPro-Regular.ttf": {
		'\u0041': 4,   // U+0041 LATIN CAPITAL LETTER A
		'\u03a3': 0,   // U+03A3 GREEK CAPITAL LETTER SIGMA
		'\u0435': 0,   // U+0435 CYRILLIC SMALL LETTER IE
		'\u2030': 675, // U+2030 PER MILLE SIGN
	},

	"microsoft/Arial.ttf": {
		'\u0041':     36,   // U+0041 LATIN CAPITAL LETTER A
		'\u00f1':     120,  // U+00F1 LATIN SMALL LETTER N WITH TILDE
		'\u0401':     556,  // U+0401 CYRILLIC CAPITAL LETTER IO
		'\u200d':     745,  // U+200D ZERO WIDTH JOINER
		'\u20ab':     1150, // U+20AB DONG SIGN
		'\u2229':     320,  // U+2229 INTERSECTION
		'\u04e9':     1319, // U+04E9 CYRILLIC SMALL LETTER BARRED O
		'\U0001f100': 0,    // U+0001F100 DIGIT ZERO FULL STOP
	},
	"microsoft/Comic_Sans_MS.ttf": {
		'\u0041': 36,  // U+0041 LATIN CAPITAL LETTER A
		'\u03af': 573, // U+03AF GREEK SMALL LETTER IOTA WITH TONOS
	},
	"microsoft/Times_New_Roman.ttf": {
		'\u0041': 36,  // U+0041 LATIN CAPITAL LETTER A
		'\u0042': 37,  // U+0041 LATIN CAPITAL LETTER B
		'\u266a': 392, // U+266A EIGHTH NOTE
		'\uf041': 0,   // PRIVATE USE AREA
		'\uf042': 0,   // PRIVATE USE AREA
	},
	"microsoft/Webdings.ttf": {
		'\u0041': 0,  // U+0041 LATIN CAPITAL LETTER A
		'\u0042': 0,  // U+0041 LATIN CAPITAL LETTER B
		'\u266a': 0,  // U+266A EIGHTH NOTE
		'\uf041': 36, // PRIVATE USE AREA
		'\uf042': 37, // PRIVATE USE AREA
	},
}
<commit_msg>font/sfnt: update test for adobe/SourceSansPro-Regular.ttf.<commit_after>// Copyright 2017 The Go 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 sfnt

/*
This file contains opt-in tests for popular, high quality, proprietary fonts,
made by companies such as Adobe and Microsoft. These fonts are generally
available, but copies are not explicitly included in this repository due to
licensing differences or file size concerns. To opt-in, run:

go test golang.org/x/image/font/sfnt -args -proprietary

Not all tests pass out-of-the-box on all systems. For example, the Microsoft
Times New Roman font is downloadable gratis even on non-Windows systems, but as
per the ttf-mscorefonts-installer Debian package, this requires accepting an
End User License Agreement (EULA) and a CAB format decoder. These tests assume
that such fonts have already been installed. You may need to specify the
directories for these fonts:

go test golang.org/x/image/font/sfnt -args -proprietary -adobeDir=/foo/bar/aFonts -microsoftDir=/foo/bar/mFonts

To only run those tests for the Microsoft fonts:

go test golang.org/x/image/font/sfnt -test.run=ProprietaryMicrosoft -args -proprietary
*/

// TODO: add Apple system fonts? Google fonts (Droid? Noto?)? Emoji fonts?

// TODO: enable Apple/Microsoft tests by default on Darwin/Windows?

import (
	"flag"
	"io/ioutil"
	"path/filepath"
	"testing"
)

var (
	proprietary = flag.Bool("proprietary", false, "test proprietary fonts not included in this repository")

	adobeDir = flag.String(
		"adobeDir",
		// This needs to be set explicitly. There is no default dir on Debian:
		// https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=736680
		//
		// Get the fonts from https://github.com/adobe-fonts, e.g.:
		//	- https://github.com/adobe-fonts/source-code-pro/releases/latest
		//	- https://github.com/adobe-fonts/source-han-sans/releases/latest
		//	- https://github.com/adobe-fonts/source-sans-pro/releases/latest
		//
		// Copy all of the TTF and OTF files to the one directory, such as
		// $HOME/adobe-fonts, and pass that as the -adobeDir flag here.
		"",
		"directory name for the Adobe proprietary fonts",
	)

	microsoftDir = flag.String(
		"microsoftDir",
		"/usr/share/fonts/truetype/msttcorefonts",
		"directory name for the Microsoft proprietary fonts",
	)
)

func TestProprietaryAdobeSourceCodeProOTF(t *testing.T) {
	testProprietary(t, "adobe", "SourceCodePro-Regular.otf", 1500, 2)
}

func TestProprietaryAdobeSourceCodeProTTF(t *testing.T) {
	testProprietary(t, "adobe", "SourceCodePro-Regular.ttf", 1500, 36)
}

func TestProprietaryAdobeSourceHanSansSC(t *testing.T) {
	testProprietary(t, "adobe", "SourceHanSansSC-Regular.otf", 65535, 2)
}

func TestProprietaryAdobeSourceSansProOTF(t *testing.T) {
	testProprietary(t, "adobe", "SourceSansPro-Regular.otf", 1800, 2)
}

func TestProprietaryAdobeSourceSansProTTF(t *testing.T) {
	testProprietary(t, "adobe", "SourceSansPro-Regular.ttf", 1800, 54)
}

func TestProprietaryMicrosoftArial(t *testing.T) {
	testProprietary(t, "microsoft", "Arial.ttf", 1200, 98)
}

func TestProprietaryMicrosoftComicSansMS(t *testing.T) {
	testProprietary(t, "microsoft", "Comic_Sans_MS.ttf", 550, 98)
}

func TestProprietaryMicrosoftTimesNewRoman(t *testing.T) {
	testProprietary(t, "microsoft", "Times_New_Roman.ttf", 1200, 98)
}

func TestProprietaryMicrosoftWebdings(t *testing.T) {
	testProprietary(t, "microsoft", "Webdings.ttf", 200, -1)
}

// testProprietary tests that we can load every glyph in the named font.
//
// The exact number of glyphs in the font can differ across its various
// versions, but as a sanity check, there should be at least minNumGlyphs.
//
// While this package is a work-in-progress, not every glyph can be loaded. The
// firstUnsupportedGlyph argument, if non-negative, is the index of the first
// unsupported glyph in the font. This number should increase over time (or set
// negative), as the TODO's in this package are done.
func testProprietary(t *testing.T, proprietor, filename string, minNumGlyphs, firstUnsupportedGlyph int) {
	if !*proprietary {
		t.Skip("skipping proprietary font test")
	}

	file, err := []byte(nil), error(nil)
	switch proprietor {
	case "adobe":
		file, err = ioutil.ReadFile(filepath.Join(*adobeDir, filename))
		if err != nil {
			t.Fatalf("%v\nPerhaps you need to set the -adobeDir=%v flag?", err, *adobeDir)
		}
	case "microsoft":
		file, err = ioutil.ReadFile(filepath.Join(*microsoftDir, filename))
		if err != nil {
			t.Fatalf("%v\nPerhaps you need to set the -microsoftDir=%v flag?", err, *microsoftDir)
		}
	default:
		panic("unreachable")
	}
	f, err := Parse(file)
	if err != nil {
		t.Fatalf("Parse: %v", err)
	}

	numGlyphs := f.NumGlyphs()
	if numGlyphs < minNumGlyphs {
		t.Fatalf("NumGlyphs: got %d, want at least %d", numGlyphs, minNumGlyphs)
	}

	var buf Buffer
	iMax := numGlyphs
	if firstUnsupportedGlyph >= 0 {
		iMax = firstUnsupportedGlyph
	}
	for i, numErrors := 0, 0; i < iMax; i++ {
		if _, err := f.LoadGlyph(&buf, GlyphIndex(i), nil); err != nil {
			t.Errorf("LoadGlyph(%d): %v", i, err)
			numErrors++
		}
		if numErrors == 10 {
			t.Fatal("LoadGlyph: too many errors")
		}
	}

	for r, want := range proprietaryGlyphIndexTestCases[proprietor+"/"+filename] {
		got, err := f.GlyphIndex(&buf, r)
		if err != nil {
			t.Errorf("GlyphIndex(%q): %v", r, err)
			continue
		}
		if got != want {
			t.Errorf("GlyphIndex(%q): got %d, want %d", r, got, want)
			continue
		}
	}
}

// proprietaryGlyphIndexTestCases hold a sample of each font's rune to glyph
// index cmap. The numerical values can be verified by running the ttx tool.
var proprietaryGlyphIndexTestCases = map[string]map[rune]GlyphIndex{
	"adobe/SourceCodePro-Regular.otf": {
		'\u0030':     877,  // U+0030 DIGIT ZERO
		'\u0041':     2,    // U+0041 LATIN CAPITAL LETTER A
		'\u0061':     28,   // U+0061 LATIN SMALL LETTER A
		'\u0104':     64,   // U+0104 LATIN CAPITAL LETTER A WITH OGONEK
		'\u0125':     323,  // U+0125 LATIN SMALL LETTER H WITH CIRCUMFLEX
		'\u01f4':     111,  // U+01F4 LATIN CAPITAL LETTER G WITH ACUTE
		'\u03a3':     623,  // U+03A3 GREEK CAPITAL LETTER SIGMA
		'\u2569':     1500, // U+2569 BOX DRAWINGS DOUBLE UP AND HORIZONTAL
		'\U0001f100': 0,    // U+0001F100 DIGIT ZERO FULL STOP
	},
	"adobe/SourceCodePro-Regular.ttf": {
		'\u0030': 877, // U+0030 DIGIT ZERO
		'\u0041': 2,   // U+0041 LATIN CAPITAL LETTER A
		'\u01f4': 111, // U+01F4 LATIN CAPITAL LETTER G WITH ACUTE
	},
	"adobe/SourceHanSansSC-Regular.otf": {
		'\u0030':     17,    // U+0030 DIGIT ZERO
		'\u0041':     34,    // U+0041 LATIN CAPITAL LETTER A
		'\u00d7':     150,   // U+00D7 MULTIPLICATION SIGN
		'\u1100':     365,   // U+1100 HANGUL CHOSEONG KIYEOK
		'\u25ca':     1254,  // U+25CA LOZENGE
		'\u2e9c':     1359,  // U+2E9C CJK RADICAL SUN
		'\u304b':     1463,  // U+304B HIRAGANA LETTER KA
		'\u4e2d':     9893,  // U+4E2D <CJK Ideograph>, 中
		'\ua960':     47537, // U+A960 HANGUL CHOSEONG TIKEUT-MIEUM
		'\ufb00':     58919, // U+FB00 LATIN SMALL LIGATURE FF
		'\uffee':     59213, // U+FFEE HALFWIDTH WHITE CIRCLE
		'\U0001f100': 59214, // U+0001F100 DIGIT ZERO FULL STOP
		'\U0001f248': 59449, // U+0001F248 TORTOISE SHELL BRACKETED CJK UNIFIED IDEOGRAPH-6557
		'\U0002f9f4': 61768, // U+0002F9F4 CJK COMPATIBILITY IDEOGRAPH-2F9F4
	},
	"adobe/SourceSansPro-Regular.otf": {
		'\u0041': 2,    // U+0041 LATIN CAPITAL LETTER A
		'\u03a3': 592,  // U+03A3 GREEK CAPITAL LETTER SIGMA
		'\u0435': 999,  // U+0435 CYRILLIC SMALL LETTER IE
		'\u2030': 1728, // U+2030 PER MILLE SIGN
	},
	"adobe/SourceSansPro-Regular.ttf": {
		'\u0041': 2,    // U+0041 LATIN CAPITAL LETTER A
		'\u03a3': 592,  // U+03A3 GREEK CAPITAL LETTER SIGMA
		'\u0435': 999,  // U+0435 CYRILLIC SMALL LETTER IE
		'\u2030': 1728, // U+2030 PER MILLE SIGN
	},

	"microsoft/Arial.ttf": {
		'\u0041':     36,   // U+0041 LATIN CAPITAL LETTER A
		'\u00f1':     120,  // U+00F1 LATIN SMALL LETTER N WITH TILDE
		'\u0401':     556,  // U+0401 CYRILLIC CAPITAL LETTER IO
		'\u200d':     745,  // U+200D ZERO WIDTH JOINER
		'\u20ab':     1150, // U+20AB DONG SIGN
		'\u2229':     320,  // U+2229 INTERSECTION
		'\u04e9':     1319, // U+04E9 CYRILLIC SMALL LETTER BARRED O
		'\U0001f100': 0,    // U+0001F100 DIGIT ZERO FULL STOP
	},
	"microsoft/Comic_Sans_MS.ttf": {
		'\u0041': 36,  // U+0041 LATIN CAPITAL LETTER A
		'\u03af': 573, // U+03AF GREEK SMALL LETTER IOTA WITH TONOS
	},
	"microsoft/Times_New_Roman.ttf": {
		'\u0041': 36,  // U+0041 LATIN CAPITAL LETTER A
		'\u0042': 37,  // U+0041 LATIN CAPITAL LETTER B
		'\u266a': 392, // U+266A EIGHTH NOTE
		'\uf041': 0,   // PRIVATE USE AREA
		'\uf042': 0,   // PRIVATE USE AREA
	},
	"microsoft/Webdings.ttf": {
		'\u0041': 0,  // U+0041 LATIN CAPITAL LETTER A
		'\u0042': 0,  // U+0041 LATIN CAPITAL LETTER B
		'\u266a': 0,  // U+266A EIGHTH NOTE
		'\uf041': 36, // PRIVATE USE AREA
		'\uf042': 37, // PRIVATE USE AREA
	},
}
<|endoftext|>


<commit_before>// Copyright © 2016 The Things Network
// Use of this source code is governed by the MIT license that can be found in the LICENSE file.

package util

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

	yaml "gopkg.in/yaml.v2"

	"github.com/TheThingsNetwork/ttn/core/types"
	"github.com/apex/log"
	"github.com/spf13/viper"
)

const (
	appFilename = "app"
	euiKey      = "eui"
	idKey       = "id"
)

// GetConfigFile returns the location of the configuration file.
// It checks the following (in this order):
// the --config flag
// $XDG_CONFIG_HOME/ttnctl/config.yml (if $XDG_CONFIG_HOME is set)
// $HOME/.ttnctl.yml
func GetConfigFile() string {
	flag := viper.GetString("config")
	xdg := path.Join(os.Getenv("XDG_CONFIG_HOME"), "ttnctl", "config.yml")
	homeyml := path.Join(os.Getenv("HOME"), ".ttnctl.yml")
	homeyaml := path.Join(os.Getenv("HOME"), ".ttnctl.yaml")

	try_files := []string{
		flag,
		xdg,
		homeyml,
		homeyaml,
	}

	// find a file that exists, and use that
	for _, file := range try_files {
		if file != "" {
			if _, err := os.Stat(file); err == nil {
				return file
			}
		}
	}

	// no file found, set up correct fallback
	if os.Getenv("XDG_CONFIG_HOME") != "" {
		return xdg
	} else {
		return homeyml
	}
}

// GetDataDir returns the location of the data directory used for
// sotring data.
// It checks the following (in this order):
// the --data flag
// $XDG_DATA_HOME/ttnctl (if $XDG_DATA_HOME is set)
// $XDG_CACHE_HOME/ttnctl (if $XDG_CACHE_HOME is set)
// $HOME/.ttnctl
func GetDataDir() string {
	file := viper.GetString("data")
	if file != "" {
		return file
	}

	xdg := os.Getenv("XDG_DATA_HOME")
	if xdg != "" {
		return path.Join(xdg, "ttnctl")
	}

	xdg = os.Getenv("XDG_CACHE_HOME")
	if xdg != "" {
		return path.Join(xdg, "ttnctl")
	}

	return path.Join(os.Getenv("HOME"), ".ttnctl")
}

func readData(file string) map[string]interface{} {
	fullpath := path.Join(GetDataDir(), file)

	c := make(map[string]interface{})

	// Read config file
	data, err := ioutil.ReadFile(fullpath)
	if err != nil {
		return c
	}

	err = yaml.Unmarshal(data, &c)
	if err != nil {
		return c
	}

	return c
}

func writeData(file string, data map[string]interface{}) error {
	fullpath := path.Join(GetDataDir(), file)

	// Generate yaml contents
	d, err := yaml.Marshal(&data)
	if err != nil {
		return fmt.Errorf("Could not generate configiguration file contents: %s", err.Error())
	}

	// Write to file
	err = ioutil.WriteFile(fullpath, d, 0644)
	if err != nil {
		return fmt.Errorf("Could not write configuration file: %s", err.Error())
	}

	return nil
}

func setData(file, key string, data interface{}) error {
	config := readData(file)
	config[key] = data
	return writeData(file, config)
}

// GetAppEUI returns the AppEUI that must be set in the command options or config
func GetAppEUI(ctx log.Interface) types.AppEUI {
	appEUIString := viper.GetString("app-eui")
	if appEUIString == "" {
		appData := readData(appFilename)
		eui, ok := appData[euiKey].(string)
		if !ok {
			ctx.Fatal("Invalid AppEUI in config file")
		}
		appEUIString = eui
	}

	if appEUIString == "" {
		ctx.Fatal("Missing AppEUI. You should select an application to use with \"ttnctl applications select\"")
	}

	eui, err := types.ParseAppEUI(appEUIString)
	if err != nil {
		ctx.WithError(err).Fatal("Invalid AppEUI")
	}
	return eui
}

// SetApp stores the app EUI preference
func SetAppEUI(ctx log.Interface, appEUI types.AppEUI) {
	err := setData(appFilename, euiKey, appEUI.String())
	if err != nil {
		ctx.WithError(err).Fatal("Could not save app EUI")
	}
}

// GetAppID returns the AppID that must be set in the command options or config
func GetAppID(ctx log.Interface) string {
	appID := viper.GetString("app-id")
	if appID == "" {
		appData := readData(appFilename)
		id, ok := appData[idKey].(string)
		if !ok {
			ctx.Fatal("Invalid appID in config file.")
		}
		appID = id
	}

	if appID == "" {
		ctx.Fatal("Missing AppID. You should select an application to use with \"ttnctl applications select\"")
	}
	return appID
}

// SetApp stores the app ID and app EUI preferences
func SetApp(ctx log.Interface, appID string, appEUI types.AppEUI) {
	config := readData(appFilename)
	config[idKey] = appID
	config[euiKey] = appEUI.String()
	err := writeData(appFilename, config)
	if err != nil {
		ctx.WithError(err).Fatal("Could not save app preference")
	}
}
<commit_msg>Prevent empty environment variables to lead to files in /<commit_after>// Copyright © 2016 The Things Network
// Use of this source code is governed by the MIT license that can be found in the LICENSE file.

package util

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

	yaml "gopkg.in/yaml.v2"

	"github.com/TheThingsNetwork/ttn/core/types"
	"github.com/apex/log"
	"github.com/spf13/viper"
)

const (
	appFilename = "app"
	euiKey      = "eui"
	idKey       = "id"
)

// GetConfigFile returns the location of the configuration file.
// It checks the following (in this order):
// the --config flag
// $XDG_CONFIG_HOME/ttnctl/config.yml (if $XDG_CONFIG_HOME is set)
// $HOME/.ttnctl.yml
func GetConfigFile() string {
	flag := viper.GetString("config")

	xdg := os.Getenv("XDG_CONFIG_HOME")
	if xdg != "" {
		xdg = path.Join(xdg, "ttnctl", "config.yml")
	}

	home := os.Getenv("HOME")
	homeyml := ""
	homeyaml := ""

	if home != "" {
		homeyml = path.Join(home, ".ttnctl.yml")
		homeyaml = path.Join(home, ".ttnctl.yaml")
	}

	try_files := []string{
		flag,
		xdg,
		homeyml,
		homeyaml,
	}

	// find a file that exists, and use that
	for _, file := range try_files {
		if file != "" {
			if _, err := os.Stat(file); err == nil {
				return file
			}
		}
	}

	// no file found, set up correct fallback
	if os.Getenv("XDG_CONFIG_HOME") != "" {
		return xdg
	} else {
		return homeyml
	}
}

// GetDataDir returns the location of the data directory used for
// sotring data.
// It checks the following (in this order):
// the --data flag
// $XDG_DATA_HOME/ttnctl (if $XDG_DATA_HOME is set)
// $XDG_CACHE_HOME/ttnctl (if $XDG_CACHE_HOME is set)
// $HOME/.ttnctl
func GetDataDir() string {
	file := viper.GetString("data")
	if file != "" {
		return file
	}

	xdg := os.Getenv("XDG_DATA_HOME")
	if xdg != "" {
		return path.Join(xdg, "ttnctl")
	}

	xdg = os.Getenv("XDG_CACHE_HOME")
	if xdg != "" {
		return path.Join(xdg, "ttnctl")
	}

	return path.Join(os.Getenv("HOME"), ".ttnctl")
}

func readData(file string) map[string]interface{} {
	fullpath := path.Join(GetDataDir(), file)

	c := make(map[string]interface{})

	// Read config file
	data, err := ioutil.ReadFile(fullpath)
	if err != nil {
		return c
	}

	err = yaml.Unmarshal(data, &c)
	if err != nil {
		return c
	}

	return c
}

func writeData(file string, data map[string]interface{}) error {
	fullpath := path.Join(GetDataDir(), file)

	// Generate yaml contents
	d, err := yaml.Marshal(&data)
	if err != nil {
		return fmt.Errorf("Could not generate configiguration file contents: %s", err.Error())
	}

	// Write to file
	err = ioutil.WriteFile(fullpath, d, 0644)
	if err != nil {
		return fmt.Errorf("Could not write configuration file: %s", err.Error())
	}

	return nil
}

func setData(file, key string, data interface{}) error {
	config := readData(file)
	config[key] = data
	return writeData(file, config)
}

// GetAppEUI returns the AppEUI that must be set in the command options or config
func GetAppEUI(ctx log.Interface) types.AppEUI {
	appEUIString := viper.GetString("app-eui")
	if appEUIString == "" {
		appData := readData(appFilename)
		eui, ok := appData[euiKey].(string)
		if !ok {
			ctx.Fatal("Invalid AppEUI in config file")
		}
		appEUIString = eui
	}

	if appEUIString == "" {
		ctx.Fatal("Missing AppEUI. You should select an application to use with \"ttnctl applications select\"")
	}

	eui, err := types.ParseAppEUI(appEUIString)
	if err != nil {
		ctx.WithError(err).Fatal("Invalid AppEUI")
	}
	return eui
}

// SetApp stores the app EUI preference
func SetAppEUI(ctx log.Interface, appEUI types.AppEUI) {
	err := setData(appFilename, euiKey, appEUI.String())
	if err != nil {
		ctx.WithError(err).Fatal("Could not save app EUI")
	}
}

// GetAppID returns the AppID that must be set in the command options or config
func GetAppID(ctx log.Interface) string {
	appID := viper.GetString("app-id")
	if appID == "" {
		appData := readData(appFilename)
		id, ok := appData[idKey].(string)
		if !ok {
			ctx.Fatal("Invalid appID in config file.")
		}
		appID = id
	}

	if appID == "" {
		ctx.Fatal("Missing AppID. You should select an application to use with \"ttnctl applications select\"")
	}
	return appID
}

// SetApp stores the app ID and app EUI preferences
func SetApp(ctx log.Interface, appID string, appEUI types.AppEUI) {
	config := readData(appFilename)
	config[idKey] = appID
	config[euiKey] = appEUI.String()
	err := writeData(appFilename, config)
	if err != nil {
		ctx.WithError(err).Fatal("Could not save app preference")
	}
}
<|endoftext|>


<commit_before>package handlers

import (
	"net/http"
	"net/http/httptest"
	"testing"
)

func TestLogoutHandler(t *testing.T) {
	setUp("/config/testing/handler_logout_url.yml")
	handler := http.HandlerFunc(LogoutHandler)

	tests := []struct {
		name     string
		url      string
		wantcode int
	}{
		{"allowed", "http://myapp.example.com/login", http.StatusFound},
		{"allowed", "https://oauth2.googleapis.com/revoke", http.StatusFound},
		{"not allowed", "http://myapp.example.com/loginagain", http.StatusBadRequest},
		{"not allowed", "http://google.com/", http.StatusBadRequest},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			req, err := http.NewRequest("GET", "/logout?url="+tt.url, nil)
			if err != nil {
				t.Fatal(err)
			}
			rr := httptest.NewRecorder()
			handler.ServeHTTP(rr, req)
			if rr.Code != tt.wantcode {
				t.Errorf("LogoutHandler() = %v, want %v", rr.Code, tt.wantcode)
			}
		})
	}
}
<commit_msg>Validate redirect for logout tests<commit_after>package handlers

import (
	"net/http"
	"net/http/httptest"
	"testing"
)

func TestLogoutHandler(t *testing.T) {
	setUp("/config/testing/handler_logout_url.yml")
	handler := http.HandlerFunc(LogoutHandler)

	tests := []struct {
		name     string
		url      string
		wantcode int
	}{
		{"allowed", "http://myapp.example.com/login", http.StatusFound},
		{"allowed", "https://oauth2.googleapis.com/revoke", http.StatusFound},
		{"not allowed", "http://myapp.example.com/loginagain", http.StatusBadRequest},
		{"not allowed", "http://google.com/", http.StatusBadRequest},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			req, err := http.NewRequest("GET", "/logout?url="+tt.url, nil)
			if err != nil {
				t.Fatal(err)
			}
			rr := httptest.NewRecorder()
			handler.ServeHTTP(rr, req)
			if rr.Code != tt.wantcode {
				t.Errorf("LogoutHandler() status = %v, want %v", rr.Code, tt.wantcode)
			}
			if (rr.Code == http.StatusFound && rr.Header().Get("Location") != tt.url) {
				t.Errorf("LogoutHandler() redirect = %s, want %s", rr.Header().Get("Location"), tt.url)
			}
		})
	}
}
<|endoftext|>


<commit_before>package haproxy

import (
	"fmt"
)

func ExampleNew() {
	// start
	ha := New("/var/run/haproxy.sock")
	// clear all entries in ACL
	_ = ha.ClearACL("inc/blacklist.lst")
	// Output:
	//
}

func ExampleAddACL() {
	// Initialize
	ha := New("/var/run/haproxy.sock")

	// Get ACL entries from file in config (via -f in haproxy cfg)
	acls, _ := ha.GetACL("inc/blacklist.lst")

	// Check if it exists and add
	if acls["/bad/path"] == "" {
		ha.AddACL("inc/blacklist.lst", acls["/bad/path"])
	}
}

func ExampleDeleteACL() {
	// Initialize
	ha := New("/var/run/haproxy.sock")

	// Get ACL entries from file in config (via -f in haproxy cfg)
	acls, _ := ha.GetACL("inc/blacklist.lst")

	// Check if it exists and delete
	if acls["/bad/path"] != "" {
		ha.DeleteACL("inc/blacklist.lst", acls["/bad/path"])
	}
}

func ExampleGetACL() {
	// Initialize
	ha := New("/var/run/haproxy.sock")

	// Get ACL entries from file in config (via -f in haproxy cfg)
	acls, _ := ha.GetACL("inc/blacklist.lst")

	for k, v := range acls {
        fmt.Println(k, ":\t", v)
    }
	// Output:
	// /bad/path:    0x121e940
}

func ExampleClearACL() {
	// Initialize
	ha := New("/var/run/haproxy.sock")

	// clear all entries in ACL
	_ = ha.ClearACL("inc/blacklist.lst")
}

<commit_msg>need to be prefixed with type<commit_after>package haproxy

import (
	"fmt"
)

func ExampleNew() {
	// start
	ha := New("/var/run/haproxy.sock")
	// clear all entries in ACL
	_ = ha.ClearACL("inc/blacklist.lst")
}

func ExampleConn_AddACL() {
	// Initialize
	ha := New("/var/run/haproxy.sock")

	// Get ACL entries from file in config (via -f in haproxy cfg)
	acls, _ := ha.GetACL("inc/blacklist.lst")

	// Check if it exists and add
	if acls["/bad/path"] == "" {
		ha.AddACL("inc/blacklist.lst", acls["/bad/path"])
	}
}

func ExampleConn_DeleteACL() {
	// Initialize
	ha := New("/var/run/haproxy.sock")

	// Get ACL entries from file in config (via -f in haproxy cfg)
	acls, _ := ha.GetACL("inc/blacklist.lst")

	// Check if it exists and delete
	if acls["/bad/path"] != "" {
		ha.DeleteACL("inc/blacklist.lst", acls["/bad/path"])
	}
}

func ExampleConn_GetACL() {
	// Initialize
	ha := New("/var/run/haproxy.sock")

	// Get ACL entries from file in config (via -f in haproxy cfg)
	acls, _ := ha.GetACL("inc/blacklist.lst")

	for k, v := range acls {
        fmt.Println(k, ":\t", v)
    }
	// Output:
	// /bad/path:    0x121e940
}

func ExampleConn_ClearACL() {
	// Initialize
	ha := New("/var/run/haproxy.sock")

	// clear all entries in ACL
	_ = ha.ClearACL("inc/blacklist.lst")
}

<|endoftext|>


<commit_before>package core

import (
	"fmt"
	"io"
	"os"
	"syscall"

	. "github.com/conclave/pcduino/hardware/sunxi"
)

const (
	GPIO_MODE_DIR           = "/sys/devices/virtual/misc/gpio/mode/"
	GPIO_PIN_DIR            = "/sys/devices/virtual/misc/gpio/pin/"
	GPIO_IF_PREFIX          = "gpio"
	ADC_IF                  = "/proc/adc"
	MAX_PWM_LEVEL           = 255
	EXTERNAL_NUM_INTERRUPTS = 2
)

// wiring_analog 1
const (
	PWMTMR_START    = 0x101
	PWMTMR_STOP     = 0x102
	PWMTMR_FUNC     = 0x103
	PWMTMR_TONE     = 0x104
	PWM_CONFIG      = 0x105
	HWPWM_DUTY      = 0x106
	PWM_FREQ        = 0x107
	MAX_PWMTMR_FREQ = 2000  //2kHz pin 3,9,10,11
	MIN_PWMTMR_FREQ = 126   //126Hz pin 3,9,10,11
	MAX_PWMHW_FREQ  = 20000 //20kHz pin 5,6
)

const pwm_dev = "/dev/pwmtimer"
const spi1_dev = "/dev/spidev1.0"
const spi2_dev = "/dev/spidev2.0"

var gpio_pin_fd []*os.File
var gpio_mode_fd []*os.File
var adc_fd []*os.File
var pwm_fd []*os.File

func init() {
	gpio_pin_fd = make([]*os.File, MAX_GPIO_NUM)
	gpio_mode_fd = make([]*os.File, MAX_GPIO_NUM)
	adc_fd = make([]*os.File, MAX_ADC_NUM)
}

// wiring
func Init() {
	var err error
	for i := 0; i < MAX_GPIO_NUM; i++ {
		name := fmt.Sprintf("%s%s%d", GPIO_PIN_DIR, GPIO_IF_PREFIX, i)
		gpio_pin_fd[i], err = os.OpenFile(name, os.O_RDWR, 0644)
		if err != nil {
			fmt.Fprintf(os.Stderr, "open %s failed: %v\n", name, err)
			return
		}
		name = fmt.Sprintf("%s%s%d", GPIO_MODE_DIR, GPIO_IF_PREFIX, i)
		gpio_mode_fd[i], err = os.OpenFile(name, os.O_RDWR, 0644)
		if err != nil {
			fmt.Fprintf(os.Stderr, "open %s failed: %v\n", name, err)
			return
		}
	}
	for i := 0; i < 5; i++ { // why not MAX_ADC_NUM here?
		name := fmt.Sprintf("%s%d", ADC_IF, i)
		adc_fd[i], err = os.OpenFile(name, os.O_RDONLY, 0644)
		if err != nil {
			fmt.Fprintf(os.Stderr, "open %s failed: %v\n", name, err)
			return
		}
	}
}

// wiring_digital
func write_to_file(fd *os.File, data []byte) error {
	fd.Seek(0, os.SEEK_SET)
	n, err := fd.Write(data)
	if err == nil && n < len(data) {
		err = io.ErrShortWrite
	}
	return err
}

func ioctl(fd, cmd, arg uintptr) (err error) {
	_, _, e1 := syscall.Syscall6(syscall.SYS_IOCTL, fd, cmd, arg, 0, 0, 0)
	if e1 != 0 {
		err = e1
	}
	return
}

func Hw_PinMode(pin, mode byte) {
	if pin >= 0 && pin <= MAX_GPIO_NUM && mode <= MAX_GPIO_MODE_NUM {
		// data := []byte{mode}
		data := []byte(fmt.Sprintf("%d", mode))
		if err := write_to_file(gpio_mode_fd[pin], data); err != nil {
			fmt.Fprintf(os.Stderr, "write gpio %d mode failed\n", pin)
			os.Exit(-1)
		}
	} else {
		fmt.Fprintf(os.Stderr, "hw_pinmode error: invalid pin or mode, pin=%d, mode=%d\n", pin, mode)
		os.Exit(-1)
	}
}

func PinMode(pin, mode byte) {
	switch pin {
	case 3, 9, 10, 11:
		fd, err := os.Open("/dev/pwmtimer")
		if err != nil {
			panic("open pwm device fail")
		}
		defer fd.Close()
		if err = ioctl(fd.Fd(), 0x102, uintptr(pin)); err != nil {
			panic("can't set PWMTMR_STOP")
		}
	}
	switch mode {
	case INPUT, OUTPUT:
		Hw_PinMode(pin, mode)
	case INPUT_PULLUP:
		Hw_PinMode(pin, 8)
	}
}

func DigitalWrite(pin, value byte) {
	if pin >= 0 && pin <= MAX_GPIO_NUM && (value == HIGH || value == LOW) {
		data := []byte(fmt.Sprintf("%d", value))
		if err := write_to_file(gpio_pin_fd[pin], data); err != nil {
			fmt.Fprintf(os.Stderr, "write gpio %d failed\n", pin)
			os.Exit(-1)
		}
	} else {
		fmt.Fprintf(os.Stderr, "digitalWrite error: invalid pin or mode, pin=%d, value=%d\n", pin, value)
		os.Exit(-1)
	}
}

func DigitalRead(pin byte) byte {
	if pin >= 0 && pin <= MAX_GPIO_NUM {
		fd := gpio_pin_fd[pin]
		b := make([]byte, 1)
		fd.Seek(0, os.SEEK_SET)
		if _, err := fd.Read(b); err != nil {
			fmt.Fprintf(os.Stderr, "read gpio %d failed\n", pin)
			os.Exit(-1)
		}
		b[0] -= '0'
		switch b[0] {
		case LOW:
			return LOW
		case HIGH:
			return HIGH
		default:
			return 0xFF
		}
	} else {
		fmt.Fprintf(os.Stderr, "digitalRead error: invalid pin or mode, pin=%d\n", pin)
		os.Exit(-1)
	}
	return 0xFF
}

// wiring_pulse
//caution: if the pulse and timeout is too large, the CPU will continue 100% usage until the func quit.
func PulseIn(pin, state byte, timeout int64) int64 {
	PinMode(pin, INPUT)
	start := Micros()
	for DigitalRead(pin) == state {
		if Micros()-start > timeout {
			return 0
		}
	}
	for DigitalRead(pin) != state {
		if Micros()-start > timeout {
			return 0
		}
	}
	value := Micros()
	for DigitalRead(pin) == state {
		if Micros()-start > timeout {
			return 0
		}
	}
	return Micros() - value
}

// wiring_shift
func ShiftIn(dataPin, clockPin, bitOrder byte) byte {
	var value byte = 0
	PinMode(clockPin, OUTPUT)
	PinMode(dataPin, INPUT)
	for i := 0; i < 8; i++ {
		DigitalWrite(clockPin, HIGH)
		if bitOrder == LSBFIRST {
			value |= (DigitalRead(dataPin) << uint(i))
		} else {
			value |= (DigitalRead(dataPin) << uint(7-i))
		}
		DigitalWrite(clockPin, LOW)
	}
	return value
}

func ShiftOut(dataPin, clockPin, bitOrder, value byte) {
	PinMode(clockPin, OUTPUT)
	PinMode(dataPin, OUTPUT)
	for i := 0; i < 8; i++ {
		if bitOrder == LSBFIRST {
			DigitalWrite(dataPin, (value & (1 << uint(i))))
		} else {
			DigitalWrite(dataPin, (value & (1 << uint(7-i))))
		}
		DigitalWrite(clockPin, HIGH)
		DigitalWrite(clockPin, LOW)
	}
}

// wiring_analog 2
<commit_msg>update ioctl impl<commit_after>package core

import (
	"fmt"
	"io"
	"os"
	"syscall"
	"unsafe"

	. "github.com/conclave/pcduino/hardware/sunxi"
)

const (
	GPIO_MODE_DIR           = "/sys/devices/virtual/misc/gpio/mode/"
	GPIO_PIN_DIR            = "/sys/devices/virtual/misc/gpio/pin/"
	GPIO_IF_PREFIX          = "gpio"
	ADC_IF                  = "/proc/adc"
	MAX_PWM_LEVEL           = 255
	EXTERNAL_NUM_INTERRUPTS = 2
)

// wiring_analog 1
const (
	PWMTMR_START    = 0x101
	PWMTMR_STOP     = 0x102
	PWMTMR_FUNC     = 0x103
	PWMTMR_TONE     = 0x104
	PWM_CONFIG      = 0x105
	HWPWM_DUTY      = 0x106
	PWM_FREQ        = 0x107
	MAX_PWMTMR_FREQ = 2000  //2kHz pin 3,9,10,11
	MIN_PWMTMR_FREQ = 126   //126Hz pin 3,9,10,11
	MAX_PWMHW_FREQ  = 20000 //20kHz pin 5,6
)

const pwm_dev = "/dev/pwmtimer"
const spi1_dev = "/dev/spidev1.0"
const spi2_dev = "/dev/spidev2.0"

var gpio_pin_fd []*os.File
var gpio_mode_fd []*os.File
var adc_fd []*os.File
var pwm_fd []*os.File

func init() {
	gpio_pin_fd = make([]*os.File, MAX_GPIO_NUM)
	gpio_mode_fd = make([]*os.File, MAX_GPIO_NUM)
	adc_fd = make([]*os.File, MAX_ADC_NUM)
}

// wiring
func Init() {
	var err error
	for i := 0; i < MAX_GPIO_NUM; i++ {
		name := fmt.Sprintf("%s%s%d", GPIO_PIN_DIR, GPIO_IF_PREFIX, i)
		gpio_pin_fd[i], err = os.OpenFile(name, os.O_RDWR, 0644)
		if err != nil {
			fmt.Fprintf(os.Stderr, "open %s failed: %v\n", name, err)
			return
		}
		name = fmt.Sprintf("%s%s%d", GPIO_MODE_DIR, GPIO_IF_PREFIX, i)
		gpio_mode_fd[i], err = os.OpenFile(name, os.O_RDWR, 0644)
		if err != nil {
			fmt.Fprintf(os.Stderr, "open %s failed: %v\n", name, err)
			return
		}
	}
	for i := 0; i < 5; i++ { // why not MAX_ADC_NUM here?
		name := fmt.Sprintf("%s%d", ADC_IF, i)
		adc_fd[i], err = os.OpenFile(name, os.O_RDONLY, 0644)
		if err != nil {
			fmt.Fprintf(os.Stderr, "open %s failed: %v\n", name, err)
			return
		}
	}
}

// wiring_digital
func write_to_file(fd *os.File, data []byte) error {
	fd.Seek(0, os.SEEK_SET)
	n, err := fd.Write(data)
	if err == nil && n < len(data) {
		err = io.ErrShortWrite
	}
	return err
}

func ioctl(fd int, request, argp uintptr) error {
	_, _, errno := syscall.Syscall(syscall.SYS_IOCTL, uintptr(fd), request, argp)
	return os.NewSyscallError("ioctl", errno)
}

func Hw_PinMode(pin, mode byte) {
	if pin >= 0 && pin <= MAX_GPIO_NUM && mode <= MAX_GPIO_MODE_NUM {
		// data := []byte{mode}
		data := []byte(fmt.Sprintf("%d", mode))
		if err := write_to_file(gpio_mode_fd[pin], data); err != nil {
			fmt.Fprintf(os.Stderr, "write gpio %d mode failed\n", pin)
			os.Exit(-1)
		}
	} else {
		fmt.Fprintf(os.Stderr, "hw_pinmode error: invalid pin or mode, pin=%d, mode=%d\n", pin, mode)
		os.Exit(-1)
	}
}

func PinMode(pin, mode byte) {
	switch pin {
	case 3, 9, 10, 11:
		fd, err := syscall.Open("/dev/pwmtimer", os.O_RDONLY|syscall.O_CLOEXEC, 0666)
		if err != nil {
			panic("open pwm device fail")
		}
		defer syscall.Close(fd)
		var val uint32 = uint32(pin)
		if err = ioctl(fd, 0x102, uintptr(unsafe.Pointer(&val))); err != nil {
			panic("can't set PWMTMR_STOP")
		}
	}
	switch mode {
	case INPUT, OUTPUT:
		Hw_PinMode(pin, mode)
	case INPUT_PULLUP:
		Hw_PinMode(pin, 8)
	}
}

func DigitalWrite(pin, value byte) {
	if pin >= 0 && pin <= MAX_GPIO_NUM && (value == HIGH || value == LOW) {
		data := []byte(fmt.Sprintf("%d", value))
		if err := write_to_file(gpio_pin_fd[pin], data); err != nil {
			fmt.Fprintf(os.Stderr, "write gpio %d failed\n", pin)
			os.Exit(-1)
		}
	} else {
		fmt.Fprintf(os.Stderr, "digitalWrite error: invalid pin or mode, pin=%d, value=%d\n", pin, value)
		os.Exit(-1)
	}
}

func DigitalRead(pin byte) byte {
	if pin >= 0 && pin <= MAX_GPIO_NUM {
		fd := gpio_pin_fd[pin]
		b := make([]byte, 1)
		fd.Seek(0, os.SEEK_SET)
		if _, err := fd.Read(b); err != nil {
			fmt.Fprintf(os.Stderr, "read gpio %d failed\n", pin)
			os.Exit(-1)
		}
		b[0] -= '0'
		switch b[0] {
		case LOW:
			return LOW
		case HIGH:
			return HIGH
		default:
			return 0xFF
		}
	} else {
		fmt.Fprintf(os.Stderr, "digitalRead error: invalid pin or mode, pin=%d\n", pin)
		os.Exit(-1)
	}
	return 0xFF
}

// wiring_pulse
//caution: if the pulse and timeout is too large, the CPU will continue 100% usage until the func quit.
func PulseIn(pin, state byte, timeout int64) int64 {
	PinMode(pin, INPUT)
	start := Micros()
	for DigitalRead(pin) == state {
		if Micros()-start > timeout {
			return 0
		}
	}
	for DigitalRead(pin) != state {
		if Micros()-start > timeout {
			return 0
		}
	}
	value := Micros()
	for DigitalRead(pin) == state {
		if Micros()-start > timeout {
			return 0
		}
	}
	return Micros() - value
}

// wiring_shift
func ShiftIn(dataPin, clockPin, bitOrder byte) byte {
	var value byte = 0
	PinMode(clockPin, OUTPUT)
	PinMode(dataPin, INPUT)
	for i := 0; i < 8; i++ {
		DigitalWrite(clockPin, HIGH)
		if bitOrder == LSBFIRST {
			value |= (DigitalRead(dataPin) << uint(i))
		} else {
			value |= (DigitalRead(dataPin) << uint(7-i))
		}
		DigitalWrite(clockPin, LOW)
	}
	return value
}

func ShiftOut(dataPin, clockPin, bitOrder, value byte) {
	PinMode(clockPin, OUTPUT)
	PinMode(dataPin, OUTPUT)
	for i := 0; i < 8; i++ {
		if bitOrder == LSBFIRST {
			DigitalWrite(dataPin, (value & (1 << uint(i))))
		} else {
			DigitalWrite(dataPin, (value & (1 << uint(7-i))))
		}
		DigitalWrite(clockPin, HIGH)
		DigitalWrite(clockPin, LOW)
	}
}

// wiring_analog 2
<|endoftext|>


<commit_before>package main

import (
  "bufio"
  "fmt"
  "io"
  "os"
  "log"
  "time"
  "flag"
  "strings"
  "os/exec"
)

func ReadChanges(file *os.File) chan string {

    changes := make(chan string)

    file.Seek(0, os.SEEK_END)
    bf := bufio.NewReader(file)

    go func() {
        for {
            line, _, err := bf.ReadLine()
            if len(line) != 0 {
                changes <- string(line)
            } else if err == io.EOF {
                time.Sleep(1 * time.Second)
            }
        }
    }()
    return changes
}

func ProcessRequest(file_name string, repository_name string, rotation int) {

    file, err := os.OpenFile(file_name, os.O_RDONLY, 0755)
    if err != nil {
        log.Fatalf("OpenFile: %s", err)
        }

    if rotation == 1 {
	// To make sure, we haven't miss any image during rotation
	scanner := bufio.NewScanner(file)
	scanner.Split(bufio.ScanLines)
	for scanner.Scan() {
	    ExecDucc(scanner.Text(), repository_name)
	}
	rotation = 0
    }

    changes := ReadChanges(file)

    for {

        msg := <-changes

        if msg == "xx|file rotation|xx" {
		file.Close()
		rotation = 1
		ProcessRequest(file_name, repository_name, rotation)
        }

        ExecDucc(msg, repository_name)
    }
}

func ExecDucc(msg string, repository_name string) {

    msg_split := strings.Split(msg, "|")
    image := msg_split[len(msg_split)-1]
    image = strings.ReplaceAll(image, "https://", "")

    fmt.Printf("DUCC ingestion for %s started...\n", image)

    _, err := exec.Command("cvmfs_ducc", "convert-single-image", "-p", image, repository_name, "--skip-thin-image", "--skip-podman").Output()
    if err != nil {
        log.Fatal(err)
    }

    fmt.Printf("[done]\n")
}

func main() {

    var rotation int

    file_name := flag.String("notifications_file", "notifications.txt", "Notification file")
    repository_name := flag.String("repository_name", "test-unpacked.cern.ch", "Repository")
    flag.Parse()

    fname := *file_name
    rname := *repository_name

    ProcessRequest(fname, rname, rotation)
}
<commit_msg>Correct typo<commit_after>package main

import (
  "bufio"
  "fmt"
  "io"
  "os"
  "log"
  "time"
  "flag"
  "strings"
  "os/exec"
)

func ReadChanges(file *os.File) chan string {

    changes := make(chan string)

    file.Seek(0, os.SEEK_END)
    bf := bufio.NewReader(file)

    go func() {
        for {
            line, _, err := bf.ReadLine()
            if len(line) != 0 {
                changes <- string(line)
            } else if err == io.EOF {
                time.Sleep(1 * time.Second)
            }
        }
    }()
    return changes
}

func ProcessRequest(file_name string, repository_name string, rotation int) {

    file, err := os.OpenFile(file_name, os.O_RDONLY, 0755)
    if err != nil {
        log.Fatalf("OpenFile: %s", err)
        }

    if rotation == 1 {
	// To make sure we haven't miss any image during rotation
	scanner := bufio.NewScanner(file)
	scanner.Split(bufio.ScanLines)
	for scanner.Scan() {
	    ExecDucc(scanner.Text(), repository_name)
	}
	rotation = 0
    }

    changes := ReadChanges(file)

    for {

        msg := <-changes

        if msg == "xx|file rotation|xx" {
		file.Close()
		rotation = 1
		ProcessRequest(file_name, repository_name, rotation)
        }

        ExecDucc(msg, repository_name)
    }
}

func ExecDucc(msg string, repository_name string) {

    msg_split := strings.Split(msg, "|")
    image := msg_split[len(msg_split)-1]
    image = strings.ReplaceAll(image, "https://", "")

    fmt.Printf("DUCC ingestion for %s started...\n", image)

    _, err := exec.Command("cvmfs_ducc", "convert-single-image", "-p", image, repository_name, "--skip-thin-image", "--skip-podman").Output()
    if err != nil {
        log.Fatal(err)
    }

    fmt.Printf("[done]\n")
}

func main() {

    var rotation int

    file_name := flag.String("notifications_file", "notifications.txt", "Notification file")
    repository_name := flag.String("repository_name", "test-unpacked.cern.ch", "Repository")
    flag.Parse()

    fname := *file_name
    rname := *repository_name

    ProcessRequest(fname, rname, rotation)
}
<|endoftext|>


<commit_before>package main_test

import (
	//	. "code.cloudfoundry.org/lds/cmd/lds"

	"fmt"
	"io/ioutil"
	"net/http"
	"os"
	"os/exec"
	"strconv"

	. "github.com/onsi/ginkgo"
	"github.com/onsi/ginkgo/config"
	. "github.com/onsi/gomega"
	"github.com/onsi/gomega/gbytes"
	"github.com/onsi/gomega/gexec"
)

var _ = Describe("Lds", func() {

	var (
		port          int
		fileContents  string
		listenersFile *os.File
		session       *gexec.Session
		ldsCommand    *exec.Cmd
	)

	var expectGetIndexToReturn = func(expectedContent string) {
		var resp *http.Response
		Eventually(func() error {
			ldsURL := fmt.Sprintf("http://127.0.0.1:%d", port)

			var err error
			resp, err = http.Get(ldsURL)
			return err

		}).Should(BeNil())

		body, err := ioutil.ReadAll(resp.Body)
		Expect(err).ToNot(HaveOccurred())

		resp.Body.Close()

		Expect(string(body)).To(Equal(expectedContent))

	}

	BeforeEach(func() {
		port = 62000 + config.GinkgoConfig.ParallelNode

		var err error
		listenersFile, err = ioutil.TempFile("", "listeners-config")
		Expect(err).ToNot(HaveOccurred())

		fileContents = `{"contents": "config"}`

		listenersFile.Write([]byte(fileContents))
		listenersFile.Sync()
		listenersFile.Close()

		ldsCommand = exec.Command(ldsBinPath, "-port="+strconv.Itoa(port), "-listener-config="+listenersFile.Name())
	})
	JustBeforeEach(func() {
		var err error
		session, err = gexec.Start(ldsCommand, GinkgoWriter, GinkgoWriter)
		Expect(err).ToNot(HaveOccurred())
	})

	AfterEach(func() {
		session.Kill()
		os.Remove(listenersFile.Name())
	})

	It("returns the listeners in the listeners config", func() {
		expectGetIndexToReturn(fileContents)
	})

	Context("when the config file does not exist", func() {
		BeforeEach(func() {
			ldsCommand = exec.Command(ldsBinPath, "-port="+strconv.Itoa(port), "-listener-config=non-existant.txt")
		})

		It("exits with 1 and the server never comes up", func() {
			Eventually(session).Should(gexec.Exit(1))
		})

		It("prints a message to stderr", func() {
			Eventually(session.Err).Should(gbytes.Say("could not find container proxy listener config at 'non-existant.txt'"))
		})
	})

	Context("when the server fails to come up", func() {
		BeforeEach(func() {
			ldsCommand = exec.Command(ldsBinPath, "-port=70000", "-listener-config="+listenersFile.Name())
		})

		It("exits with 1 and the server never comes up", func() {
			Eventually(session).Should(gexec.Exit(1))
		})

		It("prints a message to stderr", func() {
			Eventually(session.Err).Should(gbytes.Say("container proxy listener discovery service failed to come up"))
		})
	})

	Context("when the config file changes", func() {
		It("serves the new content", func() {
			expectGetIndexToReturn(`{"contents": "config"}`)

			fileContents = `{"new-contents": "new-config"}`

			ioutil.WriteFile(listenersFile.Name(), []byte(fileContents), 0666)

			expectGetIndexToReturn(`{"new-contents": "new-config"}`)

		})
	})

	It("dies after receiving SIGTERM", func() {
		session.Terminate()
		Eventually(session).Should(gexec.Exit())
	})
})
<commit_msg>remove SIGTERM test for LDS<commit_after>package main_test

import (
	//	. "code.cloudfoundry.org/lds/cmd/lds"

	"fmt"
	"io/ioutil"
	"net/http"
	"os"
	"os/exec"
	"strconv"

	. "github.com/onsi/ginkgo"
	"github.com/onsi/ginkgo/config"
	. "github.com/onsi/gomega"
	"github.com/onsi/gomega/gbytes"
	"github.com/onsi/gomega/gexec"
)

var _ = Describe("Lds", func() {

	var (
		port          int
		fileContents  string
		listenersFile *os.File
		session       *gexec.Session
		ldsCommand    *exec.Cmd
	)

	var expectGetIndexToReturn = func(expectedContent string) {
		var resp *http.Response
		Eventually(func() error {
			ldsURL := fmt.Sprintf("http://127.0.0.1:%d", port)

			var err error
			resp, err = http.Get(ldsURL)
			return err

		}).Should(BeNil())

		body, err := ioutil.ReadAll(resp.Body)
		Expect(err).ToNot(HaveOccurred())

		resp.Body.Close()

		Expect(string(body)).To(Equal(expectedContent))

	}

	BeforeEach(func() {
		port = 62000 + config.GinkgoConfig.ParallelNode

		var err error
		listenersFile, err = ioutil.TempFile("", "listeners-config")
		Expect(err).ToNot(HaveOccurred())

		fileContents = `{"contents": "config"}`

		listenersFile.Write([]byte(fileContents))
		listenersFile.Sync()
		listenersFile.Close()

		ldsCommand = exec.Command(ldsBinPath, "-port="+strconv.Itoa(port), "-listener-config="+listenersFile.Name())
	})
	JustBeforeEach(func() {
		var err error
		session, err = gexec.Start(ldsCommand, GinkgoWriter, GinkgoWriter)
		Expect(err).ToNot(HaveOccurred())
	})

	AfterEach(func() {
		session.Kill()
		os.Remove(listenersFile.Name())
	})

	It("returns the listeners in the listeners config", func() {
		expectGetIndexToReturn(fileContents)
	})

	Context("when the config file does not exist", func() {
		BeforeEach(func() {
			ldsCommand = exec.Command(ldsBinPath, "-port="+strconv.Itoa(port), "-listener-config=non-existant.txt")
		})

		It("exits with 1 and the server never comes up", func() {
			Eventually(session).Should(gexec.Exit(1))
		})

		It("prints a message to stderr", func() {
			Eventually(session.Err).Should(gbytes.Say("could not find container proxy listener config at 'non-existant.txt'"))
		})
	})

	Context("when the server fails to come up", func() {
		BeforeEach(func() {
			ldsCommand = exec.Command(ldsBinPath, "-port=70000", "-listener-config="+listenersFile.Name())
		})

		It("exits with 1 and the server never comes up", func() {
			Eventually(session).Should(gexec.Exit(1))
		})

		It("prints a message to stderr", func() {
			Eventually(session.Err).Should(gbytes.Say("container proxy listener discovery service failed to come up"))
		})
	})

	Context("when the config file changes", func() {
		It("serves the new content", func() {
			expectGetIndexToReturn(`{"contents": "config"}`)

			fileContents = `{"new-contents": "new-config"}`

			ioutil.WriteFile(listenersFile.Name(), []byte(fileContents), 0666)

			expectGetIndexToReturn(`{"new-contents": "new-config"}`)

		})
	})
})
<|endoftext|>