<commit_before>// Copyright 2010 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 sqlite provides access to the SQLite library, version 3.
package sqlite

/*
#cgo LDFLAGS: -lsqlite3

#include <sqlite3.h>
#include <stdlib.h>

// These wrappers are necessary because SQLITE_TRANSIENT
// is a pointer constant, and cgo doesn't translate them correctly.
// The definition in sqlite3.h is:
//
// typedef void (*sqlite3_destructor_type)(void*);
// #define SQLITE_STATIC      ((sqlite3_destructor_type)0)
// #define SQLITE_TRANSIENT   ((sqlite3_destructor_type)-1)

static int my_bind_text(sqlite3_stmt *stmt, int n, char *p, int np) {
	return sqlite3_bind_text(stmt, n, p, np, SQLITE_TRANSIENT);
}
static int my_bind_blob(sqlite3_stmt *stmt, int n, void *p, int np) {
	return sqlite3_bind_blob(stmt, n, p, np, SQLITE_TRANSIENT);
}

*/
import "C"

import (
	"errors"
	"fmt"
	"os"
	"reflect"
	"strconv"
	"unsafe"
	"time"
)

type Errno int

func (e Errno) Error() string {
	s := errText[e]
	if s == "" {
		return fmt.Sprintf("errno %d", int(e))
	}
	return s
}

var (
	ErrError      error = Errno(1)   //    /* SQL error or missing database */
	ErrInternal   error = Errno(2)   //    /* Internal logic error in SQLite */
	ErrPerm       error = Errno(3)   //    /* Access permission denied */
	ErrAbort      error = Errno(4)   //    /* Callback routine requested an abort */
	ErrBusy       error = Errno(5)   //    /* The database file is locked */
	ErrLocked     error = Errno(6)   //    /* A table in the database is locked */
	ErrNoMem      error = Errno(7)   //    /* A malloc() failed */
	ErrReadOnly   error = Errno(8)   //    /* Attempt to write a readonly database */
	ErrInterrupt  error = Errno(9)   //    /* Operation terminated by sqlite3_interrupt()*/
	ErrIOErr      error = Errno(10)  //    /* Some kind of disk I/O error occurred */
	ErrCorrupt    error = Errno(11)  //    /* The database disk image is malformed */
	ErrFull       error = Errno(13)  //    /* Insertion failed because database is full */
	ErrCantOpen   error = Errno(14)  //    /* Unable to open the database file */
	ErrEmpty      error = Errno(16)  //    /* Database is empty */
	ErrSchema     error = Errno(17)  //    /* The database schema changed */
	ErrTooBig     error = Errno(18)  //    /* String or BLOB exceeds size limit */
	ErrConstraint error = Errno(19)  //    /* Abort due to constraint violation */
	ErrMismatch   error = Errno(20)  //    /* Data type mismatch */
	ErrMisuse     error = Errno(21)  //    /* Library used incorrectly */
	ErrNolfs      error = Errno(22)  //    /* Uses OS features not supported on host */
	ErrAuth       error = Errno(23)  //    /* Authorization denied */
	ErrFormat     error = Errno(24)  //    /* Auxiliary database format error */
	ErrRange      error = Errno(25)  //    /* 2nd parameter to sqlite3_bind out of range */
	ErrNotDB      error = Errno(26)  //    /* File opened that is not a database file */
	Row                 = Errno(100) //   /* sqlite3_step() has another row ready */
	Done                = Errno(101) //   /* sqlite3_step() has finished executing */
)

var errText = map[Errno]string{
	1:   "SQL error or missing database",
	2:   "Internal logic error in SQLite",
	3:   "Access permission denied",
	4:   "Callback routine requested an abort",
	5:   "The database file is locked",
	6:   "A table in the database is locked",
	7:   "A malloc() failed",
	8:   "Attempt to write a readonly database",
	9:   "Operation terminated by sqlite3_interrupt()*/",
	10:  "Some kind of disk I/O error occurred",
	11:  "The database disk image is malformed",
	12:  "NOT USED. Table or record not found",
	13:  "Insertion failed because database is full",
	14:  "Unable to open the database file",
	15:  "NOT USED. Database lock protocol error",
	16:  "Database is empty",
	17:  "The database schema changed",
	18:  "String or BLOB exceeds size limit",
	19:  "Abort due to constraint violation",
	20:  "Data type mismatch",
	21:  "Library used incorrectly",
	22:  "Uses OS features not supported on host",
	23:  "Authorization denied",
	24:  "Auxiliary database format error",
	25:  "2nd parameter to sqlite3_bind out of range",
	26:  "File opened that is not a database file",
	100: "sqlite3_step() has another row ready",
	101: "sqlite3_step() has finished executing",
}

func (c *Conn) error(rv C.int) error {
	if c == nil || c.db == nil {
		return errors.New("nil sqlite database")
	}
	if rv == 0 {
		return nil
	}
	if rv == 21 { // misuse
		return Errno(rv)
	}
	return errors.New(Errno(rv).Error() + ": " + C.GoString(C.sqlite3_errmsg(c.db)))
}

type Conn struct {
	db *C.sqlite3
}

func Version() string {
	p := C.sqlite3_libversion()
	return C.GoString(p)
}

func Open(filename string) (*Conn, error) {
	if C.sqlite3_threadsafe() == 0 {
		return nil, errors.New("sqlite library was not compiled for thread-safe operation")
	}

	var db *C.sqlite3
	name := C.CString(filename)
	defer C.free(unsafe.Pointer(name))
	rv := C.sqlite3_open_v2(name, &db,
		C.SQLITE_OPEN_FULLMUTEX|
			C.SQLITE_OPEN_READWRITE|
			C.SQLITE_OPEN_CREATE,
		nil)
	if rv != 0 {
		return nil, Errno(rv)
	}
	if db == nil {
		return nil, errors.New("sqlite succeeded without returning a database")
	}
	return &Conn{db}, nil
}

func NewBackup(dst *Conn, dstTable string, src *Conn, srcTable string) (*Backup, error) {
	dname := C.CString(dstTable)
	sname := C.CString(srcTable)
	defer C.free(unsafe.Pointer(dname))
	defer C.free(unsafe.Pointer(sname))

	sb := C.sqlite3_backup_init(dst.db, dname, src.db, sname)
	if sb == nil {
		return nil, dst.error(C.sqlite3_errcode(dst.db))
	}
	return &Backup{sb, dst, src}, nil
}

type Backup struct {
	sb       *C.sqlite3_backup
	dst, src *Conn
}

func (b *Backup) Step(npage int) error {
	rv := C.sqlite3_backup_step(b.sb, C.int(npage))
	if rv == 0 || Errno(rv) == ErrBusy || Errno(rv) == ErrLocked {
		return nil
	}
	return Errno(rv)
}

type BackupStatus struct {
	Remaining int
	PageCount int
}

func (b *Backup) Status() BackupStatus {
	return BackupStatus{int(C.sqlite3_backup_remaining(b.sb)), int(C.sqlite3_backup_pagecount(b.sb))}
}

func (b *Backup) Run(npage int, sleepNs int64, c chan<- BackupStatus) error {
	var err error
	for {
		err = b.Step(npage)
		if err != nil {
			break
		}
		if c != nil {
			c <- b.Status()
		}
		time.Sleep(sleepNs)
	}
	return b.dst.error(C.sqlite3_errcode(b.dst.db))
}

func (b *Backup) Close() error {
	if b.sb == nil {
		return os.EINVAL
	}
	C.sqlite3_backup_finish(b.sb)
	b.sb = nil
	return nil
}

func (c *Conn) BusyTimeout(ms int) error {
	rv := C.sqlite3_busy_timeout(c.db, C.int(ms))
	if rv == 0 {
		return nil
	}
	return Errno(rv)
}

func (c *Conn) Exec(cmd string, args ...interface{}) error {
	s, err := c.Prepare(cmd)
	if err != nil {
		return err
	}
	defer s.Finalize()
	err = s.Exec(args...)
	if err != nil {
		return err
	}
	rv := C.sqlite3_step(s.stmt)
	if Errno(rv) != Done {
		return c.error(rv)
	}
	return nil
}

type Stmt struct {
	c    *Conn
	stmt *C.sqlite3_stmt
	err  error
	t0   int64
	sql  string
	args string
}

func (c *Conn) Prepare(cmd string) (*Stmt, error) {
	if c == nil || c.db == nil {
		return nil, errors.New("nil sqlite database")
	}
	cmdstr := C.CString(cmd)
	defer C.free(unsafe.Pointer(cmdstr))
	var stmt *C.sqlite3_stmt
	var tail *C.char
	rv := C.sqlite3_prepare_v2(c.db, cmdstr, C.int(len(cmd)+1), &stmt, &tail)
	if rv != 0 {
		return nil, c.error(rv)
	}
	return &Stmt{c: c, stmt: stmt, sql: cmd, t0: time.Nanoseconds()}, nil
}

func (s *Stmt) Exec(args ...interface{}) error {
	s.args = fmt.Sprintf(" %v", []interface{}(args))
	rv := C.sqlite3_reset(s.stmt)
	if rv != 0 {
		return s.c.error(rv)
	}

	n := int(C.sqlite3_bind_parameter_count(s.stmt))
	if n != len(args) {
		return errors.New(fmt.Sprintf("incorrect argument count for Stmt.Exec: have %d want %d", len(args), n))
	}

	for i, v := range args {
		var str string
		switch v := v.(type) {
		case []byte:
			var p *byte
			if len(v) > 0 {
				p = &v[0]
			}
			if rv := C.my_bind_blob(s.stmt, C.int(i+1), unsafe.Pointer(p), C.int(len(v))); rv != 0 {
				return s.c.error(rv)
			}
			continue

		case bool:
			if v {
				str = "1"
			} else {
				str = "0"
			}

		default:
			str = fmt.Sprint(v)
		}

		cstr := C.CString(str)
		rv := C.my_bind_text(s.stmt, C.int(i+1), cstr, C.int(len(str)))
		C.free(unsafe.Pointer(cstr))
		if rv != 0 {
			return s.c.error(rv)
		}
	}
	return nil
}

func (s *Stmt) Error() error {
	return s.err
}

func (s *Stmt) Next() bool {
	rv := C.sqlite3_step(s.stmt)
	err := Errno(rv)
	if err == Row {
		return true
	}
	if err != Done {
		s.err = s.c.error(rv)
	}
	return false
}

func (s *Stmt) Reset() error {
	C.sqlite3_reset(s.stmt)
	return nil
}

func (s *Stmt) Scan(args ...interface{}) error {
	n := int(C.sqlite3_column_count(s.stmt))
	if n != len(args) {
		return errors.New(fmt.Sprintf("incorrect argument count for Stmt.Scan: have %d want %d", len(args), n))
	}

	for i, v := range args {
		n := C.sqlite3_column_bytes(s.stmt, C.int(i))
		p := C.sqlite3_column_blob(s.stmt, C.int(i))
		if p == nil && n > 0 {
			return errors.New("got nil blob")
		}
		var data []byte
		if n > 0 {
			data = (*[1 << 30]byte)(unsafe.Pointer(p))[0:n]
		}
		switch v := v.(type) {
		case *[]byte:
			*v = data
		case *string:
			*v = string(data)
		case *bool:
			*v = string(data) == "1"
		case *int:
			x, err := strconv.Atoi(string(data))
			if err != nil {
				return errors.New("arg " + strconv.Itoa(i) + " as int: " + err.Error())
			}
			*v = x
		case *int64:
			x, err := strconv.Atoi64(string(data))
			if err != nil {
				return errors.New("arg " + strconv.Itoa(i) + " as int64: " + err.Error())
			}
			*v = x
		case *float64:
			x, err := strconv.Atof64(string(data))
			if err != nil {
				return errors.New("arg " + strconv.Itoa(i) + " as float64: " + err.Error())
			}
			*v = x
		default:
			return errors.New("unsupported type in Scan: " + reflect.TypeOf(v).String())
		}
	}
	return nil
}

func (s *Stmt) SQL() string {
	return s.sql + s.args
}

func (s *Stmt) Nanoseconds() int64 {
	return time.Nanoseconds() - s.t0
}

func (s *Stmt) Finalize() error {
	rv := C.sqlite3_finalize(s.stmt)
	if rv != 0 {
		return s.c.error(rv)
	}
	return nil
}

func (c *Conn) Close() error {
	if c == nil || c.db == nil {
		return errors.New("nil sqlite database")
	}
	rv := C.sqlite3_close(c.db)
	if rv != 0 {
		return c.error(rv)
	}
	c.db = nil
	return nil
}
<commit_msg>update for go 1<commit_after>// Copyright 2010 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 sqlite provides access to the SQLite library, version 3.
package sqlite

/*
#cgo LDFLAGS: -lsqlite3

#include <sqlite3.h>
#include <stdlib.h>

// These wrappers are necessary because SQLITE_TRANSIENT
// is a pointer constant, and cgo doesn't translate them correctly.
// The definition in sqlite3.h is:
//
// typedef void (*sqlite3_destructor_type)(void*);
// #define SQLITE_STATIC      ((sqlite3_destructor_type)0)
// #define SQLITE_TRANSIENT   ((sqlite3_destructor_type)-1)

static int my_bind_text(sqlite3_stmt *stmt, int n, char *p, int np) {
	return sqlite3_bind_text(stmt, n, p, np, SQLITE_TRANSIENT);
}
static int my_bind_blob(sqlite3_stmt *stmt, int n, void *p, int np) {
	return sqlite3_bind_blob(stmt, n, p, np, SQLITE_TRANSIENT);
}

*/
import "C"

import (
	"errors"
	"fmt"
	"reflect"
	"strconv"
	"time"
	"unsafe"
)

type Errno int

func (e Errno) Error() string {
	s := errText[e]
	if s == "" {
		return fmt.Sprintf("errno %d", int(e))
	}
	return s
}

var (
	ErrError      error = Errno(1)   //    /* SQL error or missing database */
	ErrInternal   error = Errno(2)   //    /* Internal logic error in SQLite */
	ErrPerm       error = Errno(3)   //    /* Access permission denied */
	ErrAbort      error = Errno(4)   //    /* Callback routine requested an abort */
	ErrBusy       error = Errno(5)   //    /* The database file is locked */
	ErrLocked     error = Errno(6)   //    /* A table in the database is locked */
	ErrNoMem      error = Errno(7)   //    /* A malloc() failed */
	ErrReadOnly   error = Errno(8)   //    /* Attempt to write a readonly database */
	ErrInterrupt  error = Errno(9)   //    /* Operation terminated by sqlite3_interrupt()*/
	ErrIOErr      error = Errno(10)  //    /* Some kind of disk I/O error occurred */
	ErrCorrupt    error = Errno(11)  //    /* The database disk image is malformed */
	ErrFull       error = Errno(13)  //    /* Insertion failed because database is full */
	ErrCantOpen   error = Errno(14)  //    /* Unable to open the database file */
	ErrEmpty      error = Errno(16)  //    /* Database is empty */
	ErrSchema     error = Errno(17)  //    /* The database schema changed */
	ErrTooBig     error = Errno(18)  //    /* String or BLOB exceeds size limit */
	ErrConstraint error = Errno(19)  //    /* Abort due to constraint violation */
	ErrMismatch   error = Errno(20)  //    /* Data type mismatch */
	ErrMisuse     error = Errno(21)  //    /* Library used incorrectly */
	ErrNolfs      error = Errno(22)  //    /* Uses OS features not supported on host */
	ErrAuth       error = Errno(23)  //    /* Authorization denied */
	ErrFormat     error = Errno(24)  //    /* Auxiliary database format error */
	ErrRange      error = Errno(25)  //    /* 2nd parameter to sqlite3_bind out of range */
	ErrNotDB      error = Errno(26)  //    /* File opened that is not a database file */
	Row                 = Errno(100) //   /* sqlite3_step() has another row ready */
	Done                = Errno(101) //   /* sqlite3_step() has finished executing */
)

var errText = map[Errno]string{
	1:   "SQL error or missing database",
	2:   "Internal logic error in SQLite",
	3:   "Access permission denied",
	4:   "Callback routine requested an abort",
	5:   "The database file is locked",
	6:   "A table in the database is locked",
	7:   "A malloc() failed",
	8:   "Attempt to write a readonly database",
	9:   "Operation terminated by sqlite3_interrupt()*/",
	10:  "Some kind of disk I/O error occurred",
	11:  "The database disk image is malformed",
	12:  "NOT USED. Table or record not found",
	13:  "Insertion failed because database is full",
	14:  "Unable to open the database file",
	15:  "NOT USED. Database lock protocol error",
	16:  "Database is empty",
	17:  "The database schema changed",
	18:  "String or BLOB exceeds size limit",
	19:  "Abort due to constraint violation",
	20:  "Data type mismatch",
	21:  "Library used incorrectly",
	22:  "Uses OS features not supported on host",
	23:  "Authorization denied",
	24:  "Auxiliary database format error",
	25:  "2nd parameter to sqlite3_bind out of range",
	26:  "File opened that is not a database file",
	100: "sqlite3_step() has another row ready",
	101: "sqlite3_step() has finished executing",
}

func (c *Conn) error(rv C.int) error {
	if c == nil || c.db == nil {
		return errors.New("nil sqlite database")
	}
	if rv == 0 {
		return nil
	}
	if rv == 21 { // misuse
		return Errno(rv)
	}
	return errors.New(Errno(rv).Error() + ": " + C.GoString(C.sqlite3_errmsg(c.db)))
}

type Conn struct {
	db *C.sqlite3
}

func Version() string {
	p := C.sqlite3_libversion()
	return C.GoString(p)
}

func Open(filename string) (*Conn, error) {
	if C.sqlite3_threadsafe() == 0 {
		return nil, errors.New("sqlite library was not compiled for thread-safe operation")
	}

	var db *C.sqlite3
	name := C.CString(filename)
	defer C.free(unsafe.Pointer(name))
	rv := C.sqlite3_open_v2(name, &db,
		C.SQLITE_OPEN_FULLMUTEX|
			C.SQLITE_OPEN_READWRITE|
			C.SQLITE_OPEN_CREATE,
		nil)
	if rv != 0 {
		return nil, Errno(rv)
	}
	if db == nil {
		return nil, errors.New("sqlite succeeded without returning a database")
	}
	return &Conn{db}, nil
}

func NewBackup(dst *Conn, dstTable string, src *Conn, srcTable string) (*Backup, error) {
	dname := C.CString(dstTable)
	sname := C.CString(srcTable)
	defer C.free(unsafe.Pointer(dname))
	defer C.free(unsafe.Pointer(sname))

	sb := C.sqlite3_backup_init(dst.db, dname, src.db, sname)
	if sb == nil {
		return nil, dst.error(C.sqlite3_errcode(dst.db))
	}
	return &Backup{sb, dst, src}, nil
}

type Backup struct {
	sb       *C.sqlite3_backup
	dst, src *Conn
}

func (b *Backup) Step(npage int) error {
	rv := C.sqlite3_backup_step(b.sb, C.int(npage))
	if rv == 0 || Errno(rv) == ErrBusy || Errno(rv) == ErrLocked {
		return nil
	}
	return Errno(rv)
}

type BackupStatus struct {
	Remaining int
	PageCount int
}

func (b *Backup) Status() BackupStatus {
	return BackupStatus{int(C.sqlite3_backup_remaining(b.sb)), int(C.sqlite3_backup_pagecount(b.sb))}
}

func (b *Backup) Run(npage int, period time.Duration, c chan<- BackupStatus) error {
	var err error
	for {
		err = b.Step(npage)
		if err != nil {
			break
		}
		if c != nil {
			c <- b.Status()
		}
		time.Sleep(period)
	}
	return b.dst.error(C.sqlite3_errcode(b.dst.db))
}

func (b *Backup) Close() error {
	if b.sb == nil {
		return errors.New("backup already closed")
	}
	C.sqlite3_backup_finish(b.sb)
	b.sb = nil
	return nil
}

func (c *Conn) BusyTimeout(ms int) error {
	rv := C.sqlite3_busy_timeout(c.db, C.int(ms))
	if rv == 0 {
		return nil
	}
	return Errno(rv)
}

func (c *Conn) Exec(cmd string, args ...interface{}) error {
	s, err := c.Prepare(cmd)
	if err != nil {
		return err
	}
	defer s.Finalize()
	err = s.Exec(args...)
	if err != nil {
		return err
	}
	rv := C.sqlite3_step(s.stmt)
	if Errno(rv) != Done {
		return c.error(rv)
	}
	return nil
}

type Stmt struct {
	c    *Conn
	stmt *C.sqlite3_stmt
	err  error
	t0   time.Time
	sql  string
	args string
}

func (c *Conn) Prepare(cmd string) (*Stmt, error) {
	if c == nil || c.db == nil {
		return nil, errors.New("nil sqlite database")
	}
	cmdstr := C.CString(cmd)
	defer C.free(unsafe.Pointer(cmdstr))
	var stmt *C.sqlite3_stmt
	var tail *C.char
	rv := C.sqlite3_prepare_v2(c.db, cmdstr, C.int(len(cmd)+1), &stmt, &tail)
	if rv != 0 {
		return nil, c.error(rv)
	}
	return &Stmt{c: c, stmt: stmt, sql: cmd, t0: time.Now()}, nil
}

func (s *Stmt) Exec(args ...interface{}) error {
	s.args = fmt.Sprintf(" %v", []interface{}(args))
	rv := C.sqlite3_reset(s.stmt)
	if rv != 0 {
		return s.c.error(rv)
	}

	n := int(C.sqlite3_bind_parameter_count(s.stmt))
	if n != len(args) {
		return errors.New(fmt.Sprintf("incorrect argument count for Stmt.Exec: have %d want %d", len(args), n))
	}

	for i, v := range args {
		var str string
		switch v := v.(type) {
		case []byte:
			var p *byte
			if len(v) > 0 {
				p = &v[0]
			}
			if rv := C.my_bind_blob(s.stmt, C.int(i+1), unsafe.Pointer(p), C.int(len(v))); rv != 0 {
				return s.c.error(rv)
			}
			continue

		case bool:
			if v {
				str = "1"
			} else {
				str = "0"
			}

		default:
			str = fmt.Sprint(v)
		}

		cstr := C.CString(str)
		rv := C.my_bind_text(s.stmt, C.int(i+1), cstr, C.int(len(str)))
		C.free(unsafe.Pointer(cstr))
		if rv != 0 {
			return s.c.error(rv)
		}
	}
	return nil
}

func (s *Stmt) Error() error {
	return s.err
}

func (s *Stmt) Next() bool {
	rv := C.sqlite3_step(s.stmt)
	err := Errno(rv)
	if err == Row {
		return true
	}
	if err != Done {
		s.err = s.c.error(rv)
	}
	return false
}

func (s *Stmt) Reset() error {
	C.sqlite3_reset(s.stmt)
	return nil
}

func (s *Stmt) Scan(args ...interface{}) error {
	n := int(C.sqlite3_column_count(s.stmt))
	if n != len(args) {
		return errors.New(fmt.Sprintf("incorrect argument count for Stmt.Scan: have %d want %d", len(args), n))
	}

	for i, v := range args {
		n := C.sqlite3_column_bytes(s.stmt, C.int(i))
		p := C.sqlite3_column_blob(s.stmt, C.int(i))
		if p == nil && n > 0 {
			return errors.New("got nil blob")
		}
		var data []byte
		if n > 0 {
			data = (*[1 << 30]byte)(unsafe.Pointer(p))[0:n]
		}
		switch v := v.(type) {
		case *[]byte:
			*v = data
		case *string:
			*v = string(data)
		case *bool:
			*v = string(data) == "1"
		case *int:
			x, err := strconv.Atoi(string(data))
			if err != nil {
				return errors.New("arg " + strconv.Itoa(i) + " as int: " + err.Error())
			}
			*v = x
		case *int64:
			x, err := strconv.ParseInt(string(data), 10, 64)
			if err != nil {
				return errors.New("arg " + strconv.Itoa(i) + " as int64: " + err.Error())
			}
			*v = x
		case *float64:
			x, err := strconv.ParseFloat(string(data), 64)
			if err != nil {
				return errors.New("arg " + strconv.Itoa(i) + " as float64: " + err.Error())
			}
			*v = x
		default:
			return errors.New("unsupported type in Scan: " + reflect.TypeOf(v).String())
		}
	}
	return nil
}

func (s *Stmt) SQL() string {
	return s.sql + s.args
}

func (s *Stmt) Nanoseconds() int64 {
	return time.Now().Sub(s.t0).Nanoseconds()
}

func (s *Stmt) Finalize() error {
	rv := C.sqlite3_finalize(s.stmt)
	if rv != 0 {
		return s.c.error(rv)
	}
	return nil
}

func (c *Conn) Close() error {
	if c == nil || c.db == nil {
		return errors.New("nil sqlite database")
	}
	rv := C.sqlite3_close(c.db)
	if rv != 0 {
		return c.error(rv)
	}
	c.db = nil
	return nil
}
<|endoftext|>


<commit_before>package client

// this file contains the basic set of event handlers
// to manage tracking an irc connection etc.

import (
	"github.com/fluffle/goirc/event"
	"strings"
)

// An IRC handler looks like this:
type IRCHandler func(*Conn, *Line)

// AddHandler() adds an event handler for a specific IRC command.
//
// Handlers are triggered on incoming Lines from the server, with the handler
// "name" being equivalent to Line.Cmd. Read the RFCs for details on what
// replies could come from the server. They'll generally be things like
// "PRIVMSG", "JOIN", etc. but all the numeric replies are left as ascii
// strings of digits like "332" (mainly because I really didn't feel like
// putting massive constant tables in).
func (conn *Conn) AddHandler(name string, f IRCHandler) {
	conn.Registry.AddHandler(name, NewHandler(f))
}

// Wrap f in an anonymous unboxing function
func NewHandler(f IRCHandler) event.Handler {
	return event.NewHandler(func(ev ...interface{}) {
		f(ev[0].(*Conn), ev[1].(*Line))
	})
}

// Basic ping/pong handler
func (conn *Conn) h_PING(line *Line) {
	conn.Raw("PONG :" + line.Args[0])
}

// Handler to trigger a "CONNECTED" event on receipt of numeric 001
func (conn *Conn) h_001(line *Line) {
	// we're connected!
	conn.Dispatcher.Dispatch("connected", conn, line)
	// and we're being given our hostname (from the server's perspective)
	t := line.Args[len(line.Args)-1]
	if idx := strings.LastIndex(t, " "); idx != -1 {
		t = t[idx+1:]
		if idx = strings.Index(t, "@"); idx != -1 {
			conn.Me.Host = t[idx+1:]
		}
	}
}

// XXX: do we need 005 protocol support message parsing here?
// probably in the future, but I can't quite be arsed yet.
/*
	:irc.pl0rt.org 005 GoTest CMDS=KNOCK,MAP,DCCALLOW,USERIP UHNAMES NAMESX SAFELIST HCN MAXCHANNELS=20 CHANLIMIT=#:20 MAXLIST=b:60,e:60,I:60 NICKLEN=30 CHANNELLEN=32 TOPICLEN=307 KICKLEN=307 AWAYLEN=307 :are supported by this server
	:irc.pl0rt.org 005 GoTest MAXTARGETS=20 WALLCHOPS WATCH=128 WATCHOPTS=A SILENCE=15 MODES=12 CHANTYPES=# PREFIX=(qaohv)~&@%+ CHANMODES=beI,kfL,lj,psmntirRcOAQKVCuzNSMT NETWORK=bb101.net CASEMAPPING=ascii EXTBAN=~,cqnr ELIST=MNUCT :are supported by this server
	:irc.pl0rt.org 005 GoTest STATUSMSG=~&@%+ EXCEPTS INVEX :are supported by this server
*/

// Handler to deal with "433 :Nickname already in use"
func (conn *Conn) h_433(line *Line) {
	// Args[1] is the new nick we were attempting to acquire
	conn.Nick(line.Args[1] + "_")
	// if this is happening before we're properly connected (i.e. the nick
	// we sent in the initial NICK command is in use) we will not receive
	// a NICK message to confirm our change of nick, so ReNick here...
	if line.Args[1] == conn.Me.Nick {
		conn.Me.ReNick(line.Args[1] + "_")
	}
}

// Handler NICK messages to inform us about nick changes
func (conn *Conn) h_NICK(line *Line) {
	// all nicks should be handled the same way, our own included
	if n := conn.GetNick(line.Nick); n != nil {
		n.ReNick(line.Args[0])
	} else {
		conn.error("irc.NICK(): buh? unknown nick %s.", line.Nick)
	}
}

// Handle VERSION requests and CTCP PING
func (conn *Conn) h_CTCP(line *Line) {
	if line.Args[0] == "VERSION" {
		conn.CtcpReply(line.Nick, "VERSION", "powered by goirc...")
	} else if line.Args[0] == "PING" {
		conn.CtcpReply(line.Nick, "PING", line.Args[2])
	}
}

// Handle JOINs to channels to maintain state
func (conn *Conn) h_JOIN(line *Line) {
	ch := conn.GetChannel(line.Args[0])
	n := conn.GetNick(line.Nick)
	if ch == nil {
		// first we've seen of this channel, so should be us joining it
		// NOTE this will also take care of n == nil && ch == nil
		if n != conn.Me {
			conn.error("irc.JOIN(): buh? JOIN to unknown channel %s recieved"+
				"from (non-me) nick %s",
				line.Args[0], line.Nick)
			return
		}
		ch = conn.NewChannel(line.Args[0])
		// since we don't know much about this channel, ask server for info
		// we get the channel users automatically in 353 and the channel
		// topic in 332 on join, so we just need to get the modes
		conn.Mode(ch.Name)
		// sending a WHO for the channel is MUCH more efficient than
		// triggering a WHOIS on every nick from the 353 handler
		conn.Who(ch.Name)
	}
	if n == nil {
		// this is the first we've seen of this nick
		n = conn.NewNick(line.Nick, line.Ident, "", line.Host)
		// since we don't know much about this nick, ask server for info
		conn.Who(n.Nick)
	}
	// this takes care of both nick and channel linking \o/
	ch.AddNick(n)
}

// Handle PARTs from channels to maintain state
func (conn *Conn) h_PART(line *Line) {
	ch := conn.GetChannel(line.Args[0])
	n := conn.GetNick(line.Nick)
	if ch != nil && n != nil {
		if _, ok := ch.Nicks[n]; ok {
			ch.DelNick(n)
		} else {
			conn.error("irc.PART(): nick %s is not on channel %s",
				line.Nick, line.Args[0])
		}
	} else {
		conn.error("irc.PART(): buh? PART of channel %s by nick %s",
			line.Args[0], line.Nick)
	}
}

// Handle KICKs from channels to maintain state
func (conn *Conn) h_KICK(line *Line) {
	// XXX: this won't handle autorejoining channels on KICK
	// it's trivial to do this in a seperate handler...
	ch := conn.GetChannel(line.Args[0])
	n := conn.GetNick(line.Args[1])
	if ch != nil && n != nil {
		if _, ok := ch.Nicks[n]; ok {
			ch.DelNick(n)
		} else {
			conn.error("irc.KICK(): nick %s is not on channel %s",
				line.Nick, line.Args[0])
		}
	} else {
		conn.error("irc.KICK(): buh? KICK from channel %s of nick %s",
			line.Args[0], line.Args[1])
	}
}

// Handle other people's QUITs
func (conn *Conn) h_QUIT(line *Line) {
	if n := conn.GetNick(line.Nick); n != nil {
		n.Delete()
	} else {
		conn.error("irc.QUIT(): buh? QUIT from unknown nick %s", line.Nick)
	}
}

// Handle MODE changes for channels we know about (and our nick personally)
func (conn *Conn) h_MODE(line *Line) {
	// channel modes first
	if ch := conn.GetChannel(line.Args[0]); ch != nil {
		conn.ParseChannelModes(ch, line.Args[1], line.Args[2:])
	} else if n := conn.GetNick(line.Args[0]); n != nil {
		// nick mode change, should be us
		if n != conn.Me {
			conn.error("irc.MODE(): buh? recieved MODE %s for (non-me) nick %s", line.Args[0], n.Nick)
			return
		}
		conn.ParseNickModes(n, line.Args[1])
	} else {
		conn.error("irc.MODE(): buh? not sure what to do with MODE %s", strings.Join(line.Args, " "))
	}
}

// Handle TOPIC changes for channels
func (conn *Conn) h_TOPIC(line *Line) {
	if ch := conn.GetChannel(line.Args[0]); ch != nil {
		ch.Topic = line.Args[1]
	} else {
		conn.error("irc.TOPIC(): buh? topic change on unknown channel %s", line.Args[0])
	}
}

// Handle 311 whois reply
func (conn *Conn) h_311(line *Line) {
	if n := conn.GetNick(line.Args[1]); n != nil {
		n.Ident = line.Args[2]
		n.Host = line.Args[3]
		n.Name = line.Args[5]
	} else {
		conn.error("irc.311(): buh? received WHOIS info for unknown nick %s", line.Args[1])
	}
}

// Handle 324 mode reply
func (conn *Conn) h_324(line *Line) {
	if ch := conn.GetChannel(line.Args[1]); ch != nil {
		conn.ParseChannelModes(ch, line.Args[2], line.Args[3:])
	} else {
		conn.error("irc.324(): buh? received MODE settings for unknown channel %s", line.Args[1])
	}
}

// Handle 332 topic reply on join to channel
func (conn *Conn) h_332(line *Line) {
	if ch := conn.GetChannel(line.Args[1]); ch != nil {
		ch.Topic = line.Args[2]
	} else {
		conn.error("irc.332(): buh? received TOPIC value for unknown channel %s", line.Args[1])
	}
}

// Handle 352 who reply
func (conn *Conn) h_352(line *Line) {
	if n := conn.GetNick(line.Args[5]); n != nil {
		n.Ident = line.Args[2]
		n.Host = line.Args[3]
		// XXX: do we care about the actual server the nick is on?
		//      or the hop count to this server?
		// last arg contains "<hop count> <real name>"
		a := strings.SplitN(line.Args[len(line.Args)-1], " ", 2)
		n.Name = a[1]
		if idx := strings.Index(line.Args[6], "*"); idx != -1 {
			n.Modes.Oper = true
		}
		if idx := strings.Index(line.Args[6], "H"); idx != -1 {
			n.Modes.Invisible = true
		}
	} else {
		conn.error("irc.352(): buh? got WHO reply for unknown nick %s", line.Args[5])
	}
}

// Handle 353 names reply
func (conn *Conn) h_353(line *Line) {
	if ch := conn.GetChannel(line.Args[2]); ch != nil {
		nicks := strings.Split(line.Args[len(line.Args)-1], " ")
		for _, nick := range nicks {
			// UnrealIRCd's coders are lazy and leave a trailing space
			if nick == "" {
				continue
			}
			switch c := nick[0]; c {
			case '~', '&', '@', '%', '+':
				nick = nick[1:]
				fallthrough
			default:
				n := conn.GetNick(nick)
				if n == nil {
					// we don't know this nick yet!
					n = conn.NewNick(nick, "", "", "")
				}
				if n != conn.Me {
					// we will be in the names list, but should also be in
					// the channel's nick list from the JOIN handler above
					ch.AddNick(n)
				}
				p := ch.Nicks[n]
				switch c {
				case '~':
					p.Owner = true
				case '&':
					p.Admin = true
				case '@':
					p.Op = true
				case '%':
					p.HalfOp = true
				case '+':
					p.Voice = true
				}
			}
		}
	} else {
		conn.error("irc.353(): buh? received NAMES list for unknown channel %s", line.Args[2])
	}
}

// Handle 671 whois reply (nick connected via SSL)
func (conn *Conn) h_671(line *Line) {
	if n := conn.GetNick(line.Args[1]); n != nil {
		n.Modes.SSL = true
	} else {
		conn.error("irc.671(): buh? received WHOIS SSL info for unknown nick %s", line.Args[1])
	}
}

// sets up the internal event handlers to do useful things with lines
func (conn *Conn) SetupHandlers() {
	conn.AddHandler("CTCP", (*Conn).h_CTCP)
	conn.AddHandler("JOIN", (*Conn).h_JOIN)
	conn.AddHandler("KICK", (*Conn).h_KICK)
	conn.AddHandler("MODE", (*Conn).h_MODE)
	conn.AddHandler("NICK", (*Conn).h_NICK)
	conn.AddHandler("PART", (*Conn).h_PART)
	conn.AddHandler("PING", (*Conn).h_PING)
	conn.AddHandler("QUIT", (*Conn).h_QUIT)
	conn.AddHandler("TOPIC", (*Conn).h_TOPIC)

	conn.AddHandler("001", (*Conn).h_001)
	conn.AddHandler("311", (*Conn).h_311)
	conn.AddHandler("324", (*Conn).h_324)
	conn.AddHandler("332", (*Conn).h_332)
	conn.AddHandler("352", (*Conn).h_352)
	conn.AddHandler("353", (*Conn).h_353)
	conn.AddHandler("433", (*Conn).h_433)
	conn.AddHandler("671", (*Conn).h_671)
}
<commit_msg>Check all nicks against current channel contents for 353 handler.<commit_after>package client

// this file contains the basic set of event handlers
// to manage tracking an irc connection etc.

import (
	"github.com/fluffle/goirc/event"
	"strings"
)

// An IRC handler looks like this:
type IRCHandler func(*Conn, *Line)

// AddHandler() adds an event handler for a specific IRC command.
//
// Handlers are triggered on incoming Lines from the server, with the handler
// "name" being equivalent to Line.Cmd. Read the RFCs for details on what
// replies could come from the server. They'll generally be things like
// "PRIVMSG", "JOIN", etc. but all the numeric replies are left as ascii
// strings of digits like "332" (mainly because I really didn't feel like
// putting massive constant tables in).
func (conn *Conn) AddHandler(name string, f IRCHandler) {
	conn.Registry.AddHandler(name, NewHandler(f))
}

// Wrap f in an anonymous unboxing function
func NewHandler(f IRCHandler) event.Handler {
	return event.NewHandler(func(ev ...interface{}) {
		f(ev[0].(*Conn), ev[1].(*Line))
	})
}

// Basic ping/pong handler
func (conn *Conn) h_PING(line *Line) {
	conn.Raw("PONG :" + line.Args[0])
}

// Handler to trigger a "CONNECTED" event on receipt of numeric 001
func (conn *Conn) h_001(line *Line) {
	// we're connected!
	conn.Dispatcher.Dispatch("connected", conn, line)
	// and we're being given our hostname (from the server's perspective)
	t := line.Args[len(line.Args)-1]
	if idx := strings.LastIndex(t, " "); idx != -1 {
		t = t[idx+1:]
		if idx = strings.Index(t, "@"); idx != -1 {
			conn.Me.Host = t[idx+1:]
		}
	}
}

// XXX: do we need 005 protocol support message parsing here?
// probably in the future, but I can't quite be arsed yet.
/*
	:irc.pl0rt.org 005 GoTest CMDS=KNOCK,MAP,DCCALLOW,USERIP UHNAMES NAMESX SAFELIST HCN MAXCHANNELS=20 CHANLIMIT=#:20 MAXLIST=b:60,e:60,I:60 NICKLEN=30 CHANNELLEN=32 TOPICLEN=307 KICKLEN=307 AWAYLEN=307 :are supported by this server
	:irc.pl0rt.org 005 GoTest MAXTARGETS=20 WALLCHOPS WATCH=128 WATCHOPTS=A SILENCE=15 MODES=12 CHANTYPES=# PREFIX=(qaohv)~&@%+ CHANMODES=beI,kfL,lj,psmntirRcOAQKVCuzNSMT NETWORK=bb101.net CASEMAPPING=ascii EXTBAN=~,cqnr ELIST=MNUCT :are supported by this server
	:irc.pl0rt.org 005 GoTest STATUSMSG=~&@%+ EXCEPTS INVEX :are supported by this server
*/

// Handler to deal with "433 :Nickname already in use"
func (conn *Conn) h_433(line *Line) {
	// Args[1] is the new nick we were attempting to acquire
	conn.Nick(line.Args[1] + "_")
	// if this is happening before we're properly connected (i.e. the nick
	// we sent in the initial NICK command is in use) we will not receive
	// a NICK message to confirm our change of nick, so ReNick here...
	if line.Args[1] == conn.Me.Nick {
		conn.Me.ReNick(line.Args[1] + "_")
	}
}

// Handler NICK messages to inform us about nick changes
func (conn *Conn) h_NICK(line *Line) {
	// all nicks should be handled the same way, our own included
	if n := conn.GetNick(line.Nick); n != nil {
		n.ReNick(line.Args[0])
	} else {
		conn.error("irc.NICK(): buh? unknown nick %s.", line.Nick)
	}
}

// Handle VERSION requests and CTCP PING
func (conn *Conn) h_CTCP(line *Line) {
	if line.Args[0] == "VERSION" {
		conn.CtcpReply(line.Nick, "VERSION", "powered by goirc...")
	} else if line.Args[0] == "PING" {
		conn.CtcpReply(line.Nick, "PING", line.Args[2])
	}
}

// Handle JOINs to channels to maintain state
func (conn *Conn) h_JOIN(line *Line) {
	ch := conn.GetChannel(line.Args[0])
	n := conn.GetNick(line.Nick)
	if ch == nil {
		// first we've seen of this channel, so should be us joining it
		// NOTE this will also take care of n == nil && ch == nil
		if n != conn.Me {
			conn.error("irc.JOIN(): buh? JOIN to unknown channel %s recieved"+
				"from (non-me) nick %s",
				line.Args[0], line.Nick)
			return
		}
		ch = conn.NewChannel(line.Args[0])
		// since we don't know much about this channel, ask server for info
		// we get the channel users automatically in 353 and the channel
		// topic in 332 on join, so we just need to get the modes
		conn.Mode(ch.Name)
		// sending a WHO for the channel is MUCH more efficient than
		// triggering a WHOIS on every nick from the 353 handler
		conn.Who(ch.Name)
	}
	if n == nil {
		// this is the first we've seen of this nick
		n = conn.NewNick(line.Nick, line.Ident, "", line.Host)
		// since we don't know much about this nick, ask server for info
		conn.Who(n.Nick)
	}
	// this takes care of both nick and channel linking \o/
	ch.AddNick(n)
}

// Handle PARTs from channels to maintain state
func (conn *Conn) h_PART(line *Line) {
	ch := conn.GetChannel(line.Args[0])
	n := conn.GetNick(line.Nick)
	if ch != nil && n != nil {
		if _, ok := ch.Nicks[n]; ok {
			ch.DelNick(n)
		} else {
			conn.error("irc.PART(): nick %s is not on channel %s",
				line.Nick, line.Args[0])
		}
	} else {
		conn.error("irc.PART(): buh? PART of channel %s by nick %s",
			line.Args[0], line.Nick)
	}
}

// Handle KICKs from channels to maintain state
func (conn *Conn) h_KICK(line *Line) {
	// XXX: this won't handle autorejoining channels on KICK
	// it's trivial to do this in a seperate handler...
	ch := conn.GetChannel(line.Args[0])
	n := conn.GetNick(line.Args[1])
	if ch != nil && n != nil {
		if _, ok := ch.Nicks[n]; ok {
			ch.DelNick(n)
		} else {
			conn.error("irc.KICK(): nick %s is not on channel %s",
				line.Nick, line.Args[0])
		}
	} else {
		conn.error("irc.KICK(): buh? KICK from channel %s of nick %s",
			line.Args[0], line.Args[1])
	}
}

// Handle other people's QUITs
func (conn *Conn) h_QUIT(line *Line) {
	if n := conn.GetNick(line.Nick); n != nil {
		n.Delete()
	} else {
		conn.error("irc.QUIT(): buh? QUIT from unknown nick %s", line.Nick)
	}
}

// Handle MODE changes for channels we know about (and our nick personally)
func (conn *Conn) h_MODE(line *Line) {
	// channel modes first
	if ch := conn.GetChannel(line.Args[0]); ch != nil {
		conn.ParseChannelModes(ch, line.Args[1], line.Args[2:])
	} else if n := conn.GetNick(line.Args[0]); n != nil {
		// nick mode change, should be us
		if n != conn.Me {
			conn.error("irc.MODE(): buh? recieved MODE %s for (non-me) nick %s", line.Args[0], n.Nick)
			return
		}
		conn.ParseNickModes(n, line.Args[1])
	} else {
		conn.error("irc.MODE(): buh? not sure what to do with MODE %s", strings.Join(line.Args, " "))
	}
}

// Handle TOPIC changes for channels
func (conn *Conn) h_TOPIC(line *Line) {
	if ch := conn.GetChannel(line.Args[0]); ch != nil {
		ch.Topic = line.Args[1]
	} else {
		conn.error("irc.TOPIC(): buh? topic change on unknown channel %s", line.Args[0])
	}
}

// Handle 311 whois reply
func (conn *Conn) h_311(line *Line) {
	if n := conn.GetNick(line.Args[1]); n != nil {
		n.Ident = line.Args[2]
		n.Host = line.Args[3]
		n.Name = line.Args[5]
	} else {
		conn.error("irc.311(): buh? received WHOIS info for unknown nick %s", line.Args[1])
	}
}

// Handle 324 mode reply
func (conn *Conn) h_324(line *Line) {
	if ch := conn.GetChannel(line.Args[1]); ch != nil {
		conn.ParseChannelModes(ch, line.Args[2], line.Args[3:])
	} else {
		conn.error("irc.324(): buh? received MODE settings for unknown channel %s", line.Args[1])
	}
}

// Handle 332 topic reply on join to channel
func (conn *Conn) h_332(line *Line) {
	if ch := conn.GetChannel(line.Args[1]); ch != nil {
		ch.Topic = line.Args[2]
	} else {
		conn.error("irc.332(): buh? received TOPIC value for unknown channel %s", line.Args[1])
	}
}

// Handle 352 who reply
func (conn *Conn) h_352(line *Line) {
	if n := conn.GetNick(line.Args[5]); n != nil {
		n.Ident = line.Args[2]
		n.Host = line.Args[3]
		// XXX: do we care about the actual server the nick is on?
		//      or the hop count to this server?
		// last arg contains "<hop count> <real name>"
		a := strings.SplitN(line.Args[len(line.Args)-1], " ", 2)
		n.Name = a[1]
		if idx := strings.Index(line.Args[6], "*"); idx != -1 {
			n.Modes.Oper = true
		}
		if idx := strings.Index(line.Args[6], "H"); idx != -1 {
			n.Modes.Invisible = true
		}
	} else {
		conn.error("irc.352(): buh? got WHO reply for unknown nick %s", line.Args[5])
	}
}

// Handle 353 names reply
func (conn *Conn) h_353(line *Line) {
	if ch := conn.GetChannel(line.Args[2]); ch != nil {
		nicks := strings.Split(line.Args[len(line.Args)-1], " ")
		for _, nick := range nicks {
			// UnrealIRCd's coders are lazy and leave a trailing space
			if nick == "" {
				continue
			}
			switch c := nick[0]; c {
			case '~', '&', '@', '%', '+':
				nick = nick[1:]
				fallthrough
			default:
				n := conn.GetNick(nick)
				if n == nil {
					// we don't know this nick yet!
					n = conn.NewNick(nick, "", "", "")
				}
				if _, ok := ch.Nicks[n]; !ok {
					// This nick isn't associated with this channel yet!
					ch.AddNick(n)
				}
				p := ch.Nicks[n]
				switch c {
				case '~':
					p.Owner = true
				case '&':
					p.Admin = true
				case '@':
					p.Op = true
				case '%':
					p.HalfOp = true
				case '+':
					p.Voice = true
				}
			}
		}
	} else {
		conn.error("irc.353(): buh? received NAMES list for unknown channel %s", line.Args[2])
	}
}

// Handle 671 whois reply (nick connected via SSL)
func (conn *Conn) h_671(line *Line) {
	if n := conn.GetNick(line.Args[1]); n != nil {
		n.Modes.SSL = true
	} else {
		conn.error("irc.671(): buh? received WHOIS SSL info for unknown nick %s", line.Args[1])
	}
}

// sets up the internal event handlers to do useful things with lines
func (conn *Conn) SetupHandlers() {
	conn.AddHandler("CTCP", (*Conn).h_CTCP)
	conn.AddHandler("JOIN", (*Conn).h_JOIN)
	conn.AddHandler("KICK", (*Conn).h_KICK)
	conn.AddHandler("MODE", (*Conn).h_MODE)
	conn.AddHandler("NICK", (*Conn).h_NICK)
	conn.AddHandler("PART", (*Conn).h_PART)
	conn.AddHandler("PING", (*Conn).h_PING)
	conn.AddHandler("QUIT", (*Conn).h_QUIT)
	conn.AddHandler("TOPIC", (*Conn).h_TOPIC)

	conn.AddHandler("001", (*Conn).h_001)
	conn.AddHandler("311", (*Conn).h_311)
	conn.AddHandler("324", (*Conn).h_324)
	conn.AddHandler("332", (*Conn).h_332)
	conn.AddHandler("352", (*Conn).h_352)
	conn.AddHandler("353", (*Conn).h_353)
	conn.AddHandler("433", (*Conn).h_433)
	conn.AddHandler("671", (*Conn).h_671)
}
<|endoftext|>


<commit_before>// Copyright 2016 conntrack-prometheus 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 main

import (
	"log"
	"strings"
	"sync"

	docker "github.com/fsouza/go-dockerclient"
	"github.com/prometheus/client_golang/prometheus"
)

var additionalLabels = []string{"state", "protocol", "destination"}

type ConntrackCollector struct {
	containerLister func() ([]*docker.Container, error)
	conntrack       func() ([]*conn, error)
	sync.Mutex
	connCount  map[string]map[string]int
	containers map[string]*docker.Container
}

func (c *ConntrackCollector) Describe(ch chan<- *prometheus.Desc) {
	ch <- prometheus.NewDesc("container_connections", "Number of outbound connections by destionation and state", []string{"id", "name"}, nil)
}

func (c *ConntrackCollector) Collect(ch chan<- prometheus.Metric) {
	counts, currContainers := c.getState()
	containers, err := c.containerLister()
	if err != nil {
		log.Print(err)
		return
	}
	conns, err := c.conntrack()
	if err != nil {
		log.Print(err)
		return
	}
	for _, container := range containers {
		for _, conn := range conns {
			value := ""
			switch container.NetworkSettings.IPAddress {
			case conn.SourceIP:
				value = conn.DestinationIP + ":" + conn.DestinationPort
			case conn.DestinationIP:
				value = conn.SourceIP + ":" + conn.SourcePort
			}
			if value != "" {
				key := conn.State + "-" + conn.Protocol + "-" + value
				if counts[container.ID] == nil {
					counts[container.ID] = make(map[string]int)
				}
				counts[container.ID][key] = counts[container.ID][key] + 1
			}
			currContainers[container.ID] = container
		}
	}
	c.setState(counts, currContainers)
	sendMetrics(counts, currContainers, ch)
}

func (c *ConntrackCollector) getState() (map[string]map[string]int, map[string]*docker.Container) {
	c.Lock()
	defer c.Unlock()
	copyCont := make(map[string]*docker.Container)
	for i, cont := range c.containers {
		copyCont[cont.ID] = c.containers[i]
	}
	copy := make(map[string]map[string]int)
	for k, v := range c.connCount {
		innerCopy := make(map[string]int)
		for ik, iv := range v {
			if iv == 0 {
				continue
			}
			innerCopy[ik] = 0
		}
		if len(innerCopy) == 0 {
			delete(copyCont, k)
			continue
		}
		copy[k] = innerCopy
	}
	return copy, copyCont
}

func (c *ConntrackCollector) setState(count map[string]map[string]int, containers map[string]*docker.Container) {
	c.Lock()
	defer c.Unlock()
	c.connCount = count
	for k, v := range containers {
		c.containers[k] = v
	}
}

func sendMetrics(metrics map[string]map[string]int, containers map[string]*docker.Container, ch chan<- prometheus.Metric) {
	for contID, count := range metrics {
		labelsMap := containerLabels(containers[contID])
		labels := make([]string, len(labelsMap)+len(additionalLabels))
		values := make([]string, len(labelsMap)+len(additionalLabels))
		i := 0
		for k, v := range labelsMap {
			labels[i] = sanitizeLabelName(k)
			values[i] = v
			i++
		}
		for _, l := range additionalLabels {
			labels[i] = l
			i++
		}
		i = i - len(additionalLabels)
		desc := prometheus.NewDesc("container_connections", "Number of outbound connections by destionation and state", labels, nil)
		for k, v := range count {
			keys := strings.SplitN(k, "-", 3)
			values[i] = keys[0]
			values[i+1] = keys[1]
			values[i+2] = keys[2]
			ch <- prometheus.MustNewConstMetric(desc, prometheus.GaugeValue, float64(v), values...)
		}
	}
}

func containerLabels(container *docker.Container) map[string]string {
	labels := map[string]string{
		"id":    container.ID,
		"name":  container.Name,
		"image": container.Config.Image,
	}
	for k, v := range container.Config.Labels {
		labels["container_label_"+k] = v
	}
	return labels
}

func sanitizeLabelName(name string) string {
	return strings.Replace(name, ".", "_", -1)
}
<commit_msg>single allocation for describe<commit_after>// Copyright 2016 conntrack-prometheus 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 main

import (
	"log"
	"strings"
	"sync"

	docker "github.com/fsouza/go-dockerclient"
	"github.com/prometheus/client_golang/prometheus"
)

var (
	additionalLabels = []string{"state", "protocol", "destination"}
	desc             = prometheus.NewDesc("container_connections", "Number of outbound connections by destionation and state", []string{"id", "name"}, nil)
)

type ConntrackCollector struct {
	containerLister func() ([]*docker.Container, error)
	conntrack       func() ([]*conn, error)
	sync.Mutex
	connCount  map[string]map[string]int
	containers map[string]*docker.Container
}

func (c *ConntrackCollector) Describe(ch chan<- *prometheus.Desc) {
	ch <- desc
}

func (c *ConntrackCollector) Collect(ch chan<- prometheus.Metric) {
	counts, currContainers := c.getState()
	containers, err := c.containerLister()
	if err != nil {
		log.Print(err)
		return
	}
	conns, err := c.conntrack()
	if err != nil {
		log.Print(err)
		return
	}
	for _, container := range containers {
		for _, conn := range conns {
			value := ""
			switch container.NetworkSettings.IPAddress {
			case conn.SourceIP:
				value = conn.DestinationIP + ":" + conn.DestinationPort
			case conn.DestinationIP:
				value = conn.SourceIP + ":" + conn.SourcePort
			}
			if value != "" {
				key := conn.State + "-" + conn.Protocol + "-" + value
				if counts[container.ID] == nil {
					counts[container.ID] = make(map[string]int)
				}
				counts[container.ID][key] = counts[container.ID][key] + 1
			}
			currContainers[container.ID] = container
		}
	}
	c.setState(counts, currContainers)
	sendMetrics(counts, currContainers, ch)
}

func (c *ConntrackCollector) getState() (map[string]map[string]int, map[string]*docker.Container) {
	c.Lock()
	defer c.Unlock()
	copyCont := make(map[string]*docker.Container)
	for i, cont := range c.containers {
		copyCont[cont.ID] = c.containers[i]
	}
	copy := make(map[string]map[string]int)
	for k, v := range c.connCount {
		innerCopy := make(map[string]int)
		for ik, iv := range v {
			if iv == 0 {
				continue
			}
			innerCopy[ik] = 0
		}
		if len(innerCopy) == 0 {
			delete(copyCont, k)
			continue
		}
		copy[k] = innerCopy
	}
	return copy, copyCont
}

func (c *ConntrackCollector) setState(count map[string]map[string]int, containers map[string]*docker.Container) {
	c.Lock()
	defer c.Unlock()
	c.connCount = count
	for k, v := range containers {
		c.containers[k] = v
	}
}

func sendMetrics(metrics map[string]map[string]int, containers map[string]*docker.Container, ch chan<- prometheus.Metric) {
	for contID, count := range metrics {
		labelsMap := containerLabels(containers[contID])
		labels := make([]string, len(labelsMap)+len(additionalLabels))
		values := make([]string, len(labelsMap)+len(additionalLabels))
		i := 0
		for k, v := range labelsMap {
			labels[i] = sanitizeLabelName(k)
			values[i] = v
			i++
		}
		for _, l := range additionalLabels {
			labels[i] = l
			i++
		}
		i = i - len(additionalLabels)
		desc := prometheus.NewDesc("container_connections", "Number of outbound connections by destionation and state", labels, nil)
		for k, v := range count {
			keys := strings.SplitN(k, "-", 3)
			values[i] = keys[0]
			values[i+1] = keys[1]
			values[i+2] = keys[2]
			ch <- prometheus.MustNewConstMetric(desc, prometheus.GaugeValue, float64(v), values...)
		}
	}
}

func containerLabels(container *docker.Container) map[string]string {
	labels := map[string]string{
		"id":    container.ID,
		"name":  container.Name,
		"image": container.Config.Image,
	}
	for k, v := range container.Config.Labels {
		labels["container_label_"+k] = v
	}
	return labels
}

func sanitizeLabelName(name string) string {
	return strings.Replace(name, ".", "_", -1)
}
<|endoftext|>


<commit_before>package main

import (
	"github.com/prometheus/client_golang/prometheus"
	"gopkg.in/oleiade/reflections.v1"
	"strconv"
	"strings"
)

type UwsgiStatsCollector struct {
	Reader           *UwsgiStatsReader
	ReadsDesc        *prometheus.Desc
	ErrorDesc        *prometheus.Desc
	UwsgiStats       []Stat
	UwsgiSocketStats []Stat
	UwsgiWorkerStats []Stat
	UwsgiAppStats    []Stat
	UwsgiCoreStats   []Stat
}

type Stat struct {
	Name           string
	PrometheusType prometheus.ValueType
	Desc           *prometheus.Desc
}

func (u *UwsgiStatsCollector) GatherUwsgiStats() *[]UwsgiStatsReadResults {
	uwsgi_stats_results := read_uwsgi_stats(u.Reader)
	return uwsgi_stats_results
}

func (u *UwsgiStatsCollector) Describe(ch chan<- *prometheus.Desc) {
	ch <- u.ReadsDesc
	ch <- u.ErrorDesc
	for _, stat := range u.UwsgiStats {
		ch <- stat.Desc
	}
	for _, stat := range u.UwsgiSocketStats {
		ch <- stat.Desc
	}
	for _, stat := range u.UwsgiWorkerStats {
		ch <- stat.Desc
	}
	for _, stat := range u.UwsgiAppStats {
		ch <- stat.Desc
	}
	for _, stat := range u.UwsgiCoreStats {
		ch <- stat.Desc
	}
}

func NewStatMetric(stat *Stat, value int, results *UwsgiStatsReadResults) prometheus.Metric {
	return prometheus.MustNewConstMetric(
		stat.Desc,
		stat.PrometheusType,
		float64(value),
		results.Type,
		results.Address,
		results.Identifier,
	)
}

func NewSocketStatMetric(stat *Stat, value int, socket *UwsgiSocket, results *UwsgiStatsReadResults) prometheus.Metric {
	return prometheus.MustNewConstMetric(
		stat.Desc,
		stat.PrometheusType,
		float64(value),
		results.Type,
		results.Address,
		results.Identifier,
		socket.Name,
		socket.Proto,
	)
}

func NewWorkerStatMetric(stat *Stat, value int, worker *UwsgiWorker, results *UwsgiStatsReadResults) prometheus.Metric {
	return prometheus.MustNewConstMetric(
		stat.Desc,
		stat.PrometheusType,
		float64(value),
		results.Type,
		results.Address,
		results.Identifier,
		strconv.Itoa(worker.Id),
		worker.Status,
	)
}

func NewAppStatMetric(stat *Stat, value int, app *UwsgiApp, worker *UwsgiWorker, results *UwsgiStatsReadResults) prometheus.Metric {
	return prometheus.MustNewConstMetric(
		stat.Desc,
		stat.PrometheusType,
		float64(value),
		results.Type,
		results.Address,
		results.Identifier,
		strconv.Itoa(worker.Id),
		worker.Status,
		strconv.Itoa(app.Id),
		app.Mountpoint,
		app.Chdir,
	)
}

func NewCoreStatMetric(stat *Stat, value int, core *UwsgiCore, worker *UwsgiWorker, results *UwsgiStatsReadResults) prometheus.Metric {
	return prometheus.MustNewConstMetric(
		stat.Desc,
		stat.PrometheusType,
		float64(value),
		results.Type,
		results.Address,
		results.Identifier,
		strconv.Itoa(worker.Id),
		worker.Status,
		strconv.Itoa(core.Id),
	)
}

func (u *UwsgiStatsCollector) Collect(ch chan<- prometheus.Metric) {
	uwsgi_stats_results := u.GatherUwsgiStats()
	for _, results := range *uwsgi_stats_results {
		if results.Error != nil {
			ch <- prometheus.MustNewConstMetric(
				u.ErrorDesc,
				prometheus.CounterValue,
				float64(1),
				results.Type,
				results.Address,
				results.Identifier,
			)
			continue
		}
		ch <- prometheus.MustNewConstMetric(
			u.ReadsDesc,
			prometheus.CounterValue,
			float64(1),
			results.Type,
			results.Address,
			results.Identifier,
			results.UwsgiStats.Version,
		)
		for _, stat := range u.UwsgiStats {
			value, err := reflections.GetField(results.UwsgiStats, stat.Name)
			if err != nil {
				panic(err)
			}
			ch <- NewStatMetric(&stat, value.(int), &results)
		}
		for _, stat := range u.UwsgiSocketStats {
			for _, uwsgi_socket := range results.UwsgiStats.Sockets {
				value, err := reflections.GetField(uwsgi_socket, stat.Name)
				if err != nil {
					panic(err)
				}
				ch <- NewSocketStatMetric(&stat, value.(int), &uwsgi_socket, &results)
			}
		}
		for _, stat := range u.UwsgiWorkerStats {
			for _, uwsgi_worker := range results.UwsgiStats.Workers {
				value, err := reflections.GetField(uwsgi_worker, stat.Name)
				if err != nil {
					panic(err)
				}
				ch <- NewWorkerStatMetric(&stat, value.(int), &uwsgi_worker, &results)
			}
		}
		for _, stat := range u.UwsgiAppStats {
			for _, worker := range results.UwsgiStats.Workers {
				for _, app := range worker.Apps {
					value, err := reflections.GetField(app, stat.Name)
					if err != nil {
						panic(err)
					}
					ch <- NewAppStatMetric(&stat, value.(int), &app, &worker, &results)
				}
			}
		}
		for _, stat := range u.UwsgiCoreStats {
			for _, worker := range results.UwsgiStats.Workers {
				for _, core := range worker.Cores {
					value, err := reflections.GetField(core, stat.Name)
					if err != nil {
						panic(err)
					}
					ch <- NewCoreStatMetric(&stat, value.(int), &core, &worker, &results)
				}
			}
		}
	}
}

func NewUwsgiGaugeStat(name string, description string, prefix string, label_names *[]string) Stat {
	prometheus_name := prefix + strings.ToLower(name)
	return Stat{
		name,
		prometheus.GaugeValue,
		prometheus.NewDesc(
			prometheus_name,
			description,
			*label_names,
			prometheus.Labels{},
		),
	}
}

func NewUwsgiStats() []Stat {
	prefix := "uwsgi_stats_"
	label_names := []string{"type", "uwsgi_stats_address", "identifier"}
	return []Stat{
		NewUwsgiGaugeStat("Listen_Queue", "Length of listen queue.", prefix, &label_names),
		NewUwsgiGaugeStat("Listen_Queue_Errors", "Number of listen queue errors.", prefix, &label_names),
		NewUwsgiGaugeStat("Signal_Queue", "Length of signal queue.", prefix, &label_names),
		NewUwsgiGaugeStat("Load", "Load.", prefix, &label_names),
	}
}

func NewUwsgiSocketStats() []Stat {
	prefix := "uwsgi_stats_socket_"
	label_names := []string{"type", "uwsgi_stats_address", "identifier", "name", "proto"}
	return []Stat{
		NewUwsgiGaugeStat("Queue", "Length of socket queue.", prefix, &label_names),
		NewUwsgiGaugeStat("Max_Queue", "Make length of socket queue.", prefix, &label_names),
		NewUwsgiGaugeStat("Shared", "Shared.", prefix, &label_names),
		NewUwsgiGaugeStat("Can_Offload", "Can socket offload?", prefix, &label_names),
	}
}

func NewUwsgiWorkerStats() []Stat {
	prefix := "uwsgi_stats_worker_"
	label_names := []string{"type", "uwsgi_stats_address", "identifier", "worker_id", "status"}
	return []Stat{
		NewUwsgiGaugeStat("Accepting", "Is this worker accepting requests?.", prefix, &label_names),
		NewUwsgiGaugeStat("Requests", "Number of requests.", prefix, &label_names),
		NewUwsgiGaugeStat("Delta_Requests", "Number of delta requests.", prefix, &label_names),
		NewUwsgiGaugeStat("Exceptions", "Number of exceptions.", prefix, &label_names),
		NewUwsgiGaugeStat("Harakiri_Count", "Number of harakiri attempts.", prefix, &label_names),
		NewUwsgiGaugeStat("Signals", "Number of signals.", prefix, &label_names),
		NewUwsgiGaugeStat("Signal_Queue", "Length of signal queue.", prefix, &label_names),
		NewUwsgiGaugeStat("Rss", "Worker RSS bytes.", prefix, &label_names),
		NewUwsgiGaugeStat("Vsz", "Worker VSZ bytes.", prefix, &label_names),
		NewUwsgiGaugeStat("Running_Time", "Worker running time.", prefix, &label_names),
		NewUwsgiGaugeStat("Last_Spawn", "Last worker respawn time.", prefix, &label_names),
		NewUwsgiGaugeStat("Respawn_Count", "Worker respawn count.", prefix, &label_names),
		NewUwsgiGaugeStat("Tx", "Worker transmitted bytes.", prefix, &label_names),
		NewUwsgiGaugeStat("Avg_Rt", "Worker average RT.", prefix, &label_names),
	}
}

func NewUwsgiAppStats() []Stat {
	prefix := "uwsgi_stats_worker_app_"
	label_names := []string{"type", "uwsgi_stats_address", "identifier", "worker_id", "status", "app_id", "mountpoint", "chdir"}
	return []Stat{
		NewUwsgiGaugeStat("Startup_Time", "How long this app took to start.", prefix, &label_names),
		NewUwsgiGaugeStat("Requests", "Number of requests.", prefix, &label_names),
		NewUwsgiGaugeStat("Exceptions", "Number of exceptions.", prefix, &label_names),
	}
}

func NewUwsgiCoreStats() []Stat {
	prefix := "uwsgi_stats_worker_core_"
	label_names := []string{"type", "uwsgi_stats_address", "identifier", "worker_id", "status", "core_id"}
	return []Stat{
		NewUwsgiGaugeStat("Requests", "Number of requests.", prefix, &label_names),
		NewUwsgiGaugeStat("Static_Requests", "Number of static requests.", prefix, &label_names),
		NewUwsgiGaugeStat("Routed_Requets", "Number of routed requests.", prefix, &label_names),
		NewUwsgiGaugeStat("Ofloaded_Requests", "Number of requests offloaded to threads.", prefix, &label_names),
		NewUwsgiGaugeStat("Write_Errors", "Number of write errors.", prefix, &label_names),
		NewUwsgiGaugeStat("Read_Errors", "Number of read errors.", prefix, &label_names),
		NewUwsgiGaugeStat("In_Requests", "Number of requests in.", prefix, &label_names),
	}
}

func NewUwsgiStatsCollector(reader *UwsgiStatsReader) *UwsgiStatsCollector {

	return &UwsgiStatsCollector{
		Reader: reader,
		ReadsDesc: prometheus.NewDesc(
			"uwsgi_stats_scrapes_total",
			"Number of times stats are scraped",
			[]string{"type", "uwsgi_stats_address", "identifier", "uwsgi_version"},
			prometheus.Labels{},
		),
		ErrorDesc: prometheus.NewDesc(
			"uwsgi_stats_read_error_total",
			"Problems reading stats",
			[]string{"type", "uwsgi_stats_address", "identifier"},
			prometheus.Labels{},
		),
		UwsgiStats:       NewUwsgiStats(),
		UwsgiSocketStats: NewUwsgiSocketStats(),
		UwsgiWorkerStats: NewUwsgiWorkerStats(),
		UwsgiAppStats:    NewUwsgiAppStats(),
		UwsgiCoreStats:   NewUwsgiCoreStats(),
	}
}
<commit_msg>Switch to counters for a bunch of the values (which are counters)<commit_after>package main

import (
	"github.com/prometheus/client_golang/prometheus"
	"gopkg.in/oleiade/reflections.v1"
	"strconv"
	"strings"
)

type UwsgiStatsCollector struct {
	Reader           *UwsgiStatsReader
	ReadsDesc        *prometheus.Desc
	ErrorDesc        *prometheus.Desc
	UwsgiStats       []Stat
	UwsgiSocketStats []Stat
	UwsgiWorkerStats []Stat
	UwsgiAppStats    []Stat
	UwsgiCoreStats   []Stat
}

type Stat struct {
	Name           string
	PrometheusType prometheus.ValueType
	Desc           *prometheus.Desc
}

func (u *UwsgiStatsCollector) GatherUwsgiStats() *[]UwsgiStatsReadResults {
	uwsgi_stats_results := read_uwsgi_stats(u.Reader)
	return uwsgi_stats_results
}

func (u *UwsgiStatsCollector) Describe(ch chan<- *prometheus.Desc) {
	ch <- u.ReadsDesc
	ch <- u.ErrorDesc
	for _, stat := range u.UwsgiStats {
		ch <- stat.Desc
	}
	for _, stat := range u.UwsgiSocketStats {
		ch <- stat.Desc
	}
	for _, stat := range u.UwsgiWorkerStats {
		ch <- stat.Desc
	}
	for _, stat := range u.UwsgiAppStats {
		ch <- stat.Desc
	}
	for _, stat := range u.UwsgiCoreStats {
		ch <- stat.Desc
	}
}

func NewStatMetric(stat *Stat, value int, results *UwsgiStatsReadResults) prometheus.Metric {
	return prometheus.MustNewConstMetric(
		stat.Desc,
		stat.PrometheusType,
		float64(value),
		results.Type,
		results.Address,
		results.Identifier,
	)
}

func NewSocketStatMetric(stat *Stat, value int, socket *UwsgiSocket, results *UwsgiStatsReadResults) prometheus.Metric {
	return prometheus.MustNewConstMetric(
		stat.Desc,
		stat.PrometheusType,
		float64(value),
		results.Type,
		results.Address,
		results.Identifier,
		socket.Name,
		socket.Proto,
	)
}

func NewWorkerStatMetric(stat *Stat, value int, worker *UwsgiWorker, results *UwsgiStatsReadResults) prometheus.Metric {
	return prometheus.MustNewConstMetric(
		stat.Desc,
		stat.PrometheusType,
		float64(value),
		results.Type,
		results.Address,
		results.Identifier,
		strconv.Itoa(worker.Id),
		worker.Status,
	)
}

func NewAppStatMetric(stat *Stat, value int, app *UwsgiApp, worker *UwsgiWorker, results *UwsgiStatsReadResults) prometheus.Metric {
	return prometheus.MustNewConstMetric(
		stat.Desc,
		stat.PrometheusType,
		float64(value),
		results.Type,
		results.Address,
		results.Identifier,
		strconv.Itoa(worker.Id),
		worker.Status,
		strconv.Itoa(app.Id),
		app.Mountpoint,
		app.Chdir,
	)
}

func NewCoreStatMetric(stat *Stat, value int, core *UwsgiCore, worker *UwsgiWorker, results *UwsgiStatsReadResults) prometheus.Metric {
	return prometheus.MustNewConstMetric(
		stat.Desc,
		stat.PrometheusType,
		float64(value),
		results.Type,
		results.Address,
		results.Identifier,
		strconv.Itoa(worker.Id),
		worker.Status,
		strconv.Itoa(core.Id),
	)
}

func (u *UwsgiStatsCollector) Collect(ch chan<- prometheus.Metric) {
	uwsgi_stats_results := u.GatherUwsgiStats()
	for _, results := range *uwsgi_stats_results {
		if results.Error != nil {
			ch <- prometheus.MustNewConstMetric(
				u.ErrorDesc,
				prometheus.CounterValue,
				float64(1),
				results.Type,
				results.Address,
				results.Identifier,
			)
			continue
		}
		ch <- prometheus.MustNewConstMetric(
			u.ReadsDesc,
			prometheus.CounterValue,
			float64(1),
			results.Type,
			results.Address,
			results.Identifier,
			results.UwsgiStats.Version,
		)
		for _, stat := range u.UwsgiStats {
			value, err := reflections.GetField(results.UwsgiStats, stat.Name)
			if err != nil {
				panic(err)
			}
			ch <- NewStatMetric(&stat, value.(int), &results)
		}
		for _, stat := range u.UwsgiSocketStats {
			for _, uwsgi_socket := range results.UwsgiStats.Sockets {
				value, err := reflections.GetField(uwsgi_socket, stat.Name)
				if err != nil {
					panic(err)
				}
				ch <- NewSocketStatMetric(&stat, value.(int), &uwsgi_socket, &results)
			}
		}
		for _, stat := range u.UwsgiWorkerStats {
			for _, uwsgi_worker := range results.UwsgiStats.Workers {
				value, err := reflections.GetField(uwsgi_worker, stat.Name)
				if err != nil {
					panic(err)
				}
				ch <- NewWorkerStatMetric(&stat, value.(int), &uwsgi_worker, &results)
			}
		}
		for _, stat := range u.UwsgiAppStats {
			for _, worker := range results.UwsgiStats.Workers {
				for _, app := range worker.Apps {
					value, err := reflections.GetField(app, stat.Name)
					if err != nil {
						panic(err)
					}
					ch <- NewAppStatMetric(&stat, value.(int), &app, &worker, &results)
				}
			}
		}
		for _, stat := range u.UwsgiCoreStats {
			for _, worker := range results.UwsgiStats.Workers {
				for _, core := range worker.Cores {
					value, err := reflections.GetField(core, stat.Name)
					if err != nil {
						panic(err)
					}
					ch <- NewCoreStatMetric(&stat, value.(int), &core, &worker, &results)
				}
			}
		}
	}
}

func NewUwsgiGaugeStat(name string, description string, prefix string, label_names *[]string) Stat {
	prometheus_name := prefix + strings.ToLower(name)
	return Stat{
		name,
		prometheus.GaugeValue,
		prometheus.NewDesc(
			prometheus_name,
			description,
			*label_names,
			prometheus.Labels{},
		),
	}
}

func NewUwsgiCounterStat(name string, description string, prefix string, suffix string, label_names *[]string) Stat {
	prometheus_name := prefix + strings.ToLower(name) + suffix
	return Stat{
		name,
		prometheus.CounterValue,
		prometheus.NewDesc(
			prometheus_name,
			description,
			*label_names,
			prometheus.Labels{},
		),
	}
}

func NewUwsgiStats() []Stat {
	prefix := "uwsgi_stats_"
	label_names := []string{"type", "uwsgi_stats_address", "identifier"}
	return []Stat{
		NewUwsgiGaugeStat("Listen_Queue", "Length of listen queue.", prefix, &label_names),
		NewUwsgiGaugeStat("Listen_Queue_Errors", "Number of listen queue errors.", prefix, &label_names),
		NewUwsgiGaugeStat("Signal_Queue", "Length of signal queue.", prefix, &label_names),
		NewUwsgiGaugeStat("Load", "Load.", prefix, &label_names),
	}
}

func NewUwsgiSocketStats() []Stat {
	prefix := "uwsgi_stats_socket_"
	label_names := []string{"type", "uwsgi_stats_address", "identifier", "name", "proto"}
	return []Stat{
		NewUwsgiGaugeStat("Queue", "Length of socket queue.", prefix, &label_names),
		NewUwsgiGaugeStat("Max_Queue", "Make length of socket queue.", prefix, &label_names),
		NewUwsgiGaugeStat("Shared", "Shared.", prefix, &label_names),
		NewUwsgiGaugeStat("Can_Offload", "Can socket offload?", prefix, &label_names),
	}
}

func NewUwsgiWorkerStats() []Stat {
	prefix := "uwsgi_stats_worker_"
	suffix := "_total"
	label_names := []string{"type", "uwsgi_stats_address", "identifier", "worker_id", "status"}
	return []Stat{
		NewUwsgiGaugeStat("Accepting", "Is this worker accepting requests?.", prefix, &label_names),
		NewUwsgiCounterStat("Requests", "Number of requests.", prefix, suffix, &label_names),
		NewUwsgiCounterStat("Delta_Requests", "Number of delta requests.", prefix, suffix, &label_names),
		NewUwsgiCounterStat("Exceptions", "Number of exceptions.", prefix, suffix, &label_names),
		NewUwsgiCounterStat("Harakiri_Count", "Number of harakiri attempts.", prefix, suffix, &label_names),
		NewUwsgiCounterStat("Signals", "Number of signals.", prefix, suffix, &label_names),
		NewUwsgiGaugeStat("Signal_Queue", "Length of signal queue.", prefix, &label_names),
		NewUwsgiGaugeStat("Rss", "Worker RSS bytes.", prefix, &label_names),
		NewUwsgiGaugeStat("Vsz", "Worker VSZ bytes.", prefix, &label_names),
		NewUwsgiGaugeStat("Running_Time", "Worker running time.", prefix, &label_names),
		NewUwsgiGaugeStat("Last_Spawn", "Last worker respawn time.", prefix, &label_names),
		NewUwsgiCounterStat("Respawn_Count", "Worker respawn count.", prefix, suffix, &label_names),
		NewUwsgiGaugeStat("Tx", "Worker transmitted bytes.", prefix, &label_names),
		NewUwsgiGaugeStat("Avg_Rt", "Worker average RT.", prefix, &label_names),
	}
}

func NewUwsgiAppStats() []Stat {
	prefix := "uwsgi_stats_worker_app_"
	suffix := "_total"
	label_names := []string{"type", "uwsgi_stats_address", "identifier", "worker_id", "status", "app_id", "mountpoint", "chdir"}
	return []Stat{
		NewUwsgiGaugeStat("Startup_Time", "How long this app took to start.", prefix, &label_names),
		NewUwsgiCounterStat("Requests", "Number of requests.", prefix, suffix, &label_names),
		NewUwsgiCounterStat("Exceptions", "Number of exceptions.", prefix, suffix, &label_names),
	}
}

func NewUwsgiCoreStats() []Stat {
	prefix := "uwsgi_stats_worker_core_"
	suffix := "_total"
	label_names := []string{"type", "uwsgi_stats_address", "identifier", "worker_id", "status", "core_id"}
	return []Stat{
		NewUwsgiCounterStat("Requests", "Number of requests.", prefix, suffix, &label_names),
		NewUwsgiCounterStat("Static_Requests", "Number of static requests.", prefix, suffix, &label_names),
		NewUwsgiCounterStat("Routed_Requets", "Number of routed requests.", prefix, suffix, &label_names),
		NewUwsgiCounterStat("Ofloaded_Requests", "Number of requests offloaded to threads.", prefix, suffix, &label_names),
		NewUwsgiCounterStat("Write_Errors", "Number of write errors.", prefix, suffix, &label_names),
		NewUwsgiCounterStat("Read_Errors", "Number of read errors.", prefix, suffix, &label_names),
		NewUwsgiCounterStat("In_Requests", "Number of requests in.", prefix, suffix, &label_names),
	}
}

func NewUwsgiStatsCollector(reader *UwsgiStatsReader) *UwsgiStatsCollector {

	return &UwsgiStatsCollector{
		Reader: reader,
		ReadsDesc: prometheus.NewDesc(
			"uwsgi_stats_scrapes_total",
			"Number of times stats are scraped",
			[]string{"type", "uwsgi_stats_address", "identifier", "uwsgi_version"},
			prometheus.Labels{},
		),
		ErrorDesc: prometheus.NewDesc(
			"uwsgi_stats_read_error_total",
			"Problems reading stats",
			[]string{"type", "uwsgi_stats_address", "identifier"},
			prometheus.Labels{},
		),
		UwsgiStats:       NewUwsgiStats(),
		UwsgiSocketStats: NewUwsgiSocketStats(),
		UwsgiWorkerStats: NewUwsgiWorkerStats(),
		UwsgiAppStats:    NewUwsgiAppStats(),
		UwsgiCoreStats:   NewUwsgiCoreStats(),
	}
}
<|endoftext|>


<commit_before>package lfsapi

import (
	"bytes"
	"fmt"
	"net/url"
	"os/exec"
	"strings"
	"sync"

	"github.com/git-lfs/git-lfs/errors"
	"github.com/rubyist/tracerx"
)

type CredentialHelper interface {
	Fill(Creds) (Creds, error)
	Reject(Creds) error
	Approve(Creds) error
}

type Creds map[string]string

func bufferCreds(c Creds) *bytes.Buffer {
	buf := new(bytes.Buffer)

	for k, v := range c {
		buf.Write([]byte(k))
		buf.Write([]byte("="))
		buf.Write([]byte(v))
		buf.Write([]byte("\n"))
	}

	return buf
}

// getCredentialHelper parses a 'credsConfig' from the git and OS environments,
// returning the appropriate CredentialHelper to authenticate requests with.
//
// It returns an error if any configuration was invalid, or otherwise
// un-useable.
func (c *Client) getCredentialHelper(u *url.URL) (CredentialHelper, Creds) {
	input := Creds{"protocol": u.Scheme, "host": u.Host}
	if u.User != nil && u.User.Username() != "" {
		input["username"] = u.User.Username()
	}
	if c.gitEnv.Bool("credential.usehttppath", false) {
		input["path"] = strings.TrimPrefix(u.Path, "/")
	}

	if c.Credentials != nil {
		return c.Credentials, input
	}

	helpers := make([]CredentialHelper, 0, 3)
	if c.cachingCredHelper != nil {
		helpers = append(helpers, c.cachingCredHelper)
	}
	if c.askpassCredHelper != nil {
		helper, _ := c.gitEnv.Get("credential.helper")
		if len(helper) == 0 {
			helpers = append(helpers, c.askpassCredHelper)
		}
	}

	return NewCredentialHelpers(append(helpers, c.commandCredHelper)), input
}

// AskPassCredentialHelper implements the CredentialHelper type for GIT_ASKPASS
// and 'core.askpass' configuration values.
type AskPassCredentialHelper struct {
	// Program is the executable program's absolute or relative name.
	Program string
}

// Fill implements fill by running the ASKPASS program and returning its output
// as a password encoded in the Creds type given the key "password".
//
// It accepts the password as coming from the program's stdout, as when invoked
// with the given arguments (see (*AskPassCredentialHelper).args() below)./
//
// If there was an error running the command, it is returned instead of a set of
// filled credentials.
func (a *AskPassCredentialHelper) Fill(what Creds) (Creds, error) {
	var user bytes.Buffer
	var pass bytes.Buffer
	var err bytes.Buffer

	u := &url.URL{
		Scheme: what["protocol"],
		Host:   what["host"],
		Path:   what["path"],
	}

	// 'ucmd' will run the GIT_ASKPASS (or core.askpass) command prompting
	// for a username.
	ucmd := exec.Command(a.Program, a.args(fmt.Sprintf("Username for %q", u))...)
	ucmd.Stderr = &err
	ucmd.Stdout = &user

	tracerx.Printf("creds: filling with GIT_ASKPASS: %s", strings.Join(ucmd.Args, " "))
	if err := ucmd.Run(); err != nil {
		return nil, err
	}

	if err.Len() > 0 {
		return nil, errors.New(err.String())
	}

	if username := strings.TrimSpace(user.String()); len(username) > 0 {
		// If a non-empty username was given, add it to the URL via func
		// 'net/url.User()'.
		u.User = url.User(username)
	}

	// Regardless, create 'pcmd' to run the GIT_ASKPASS (or core.askpass)
	// command prompting for a password.
	pcmd := exec.Command(a.Program, a.args(fmt.Sprintf("Password for %q", u))...)
	pcmd.Stderr = &err
	pcmd.Stdout = &pass

	tracerx.Printf("creds: filling with GIT_ASKPASS: %s", strings.Join(pcmd.Args, " "))
	if err := pcmd.Run(); err != nil {
		return nil, err
	}

	if err.Len() > 0 {
		return nil, errors.New(err.String())
	}

	// Finally, now that we have the username and password information,
	// store it in the creds instance that we will return to the caller.
	creds := make(Creds)
	creds["username"] = strings.TrimSpace(user.String())
	creds["password"] = strings.TrimSpace(pass.String())

	return creds, nil
}

// Approve implements CredentialHelper.Approve, and returns nil. The ASKPASS
// credential helper does not implement credential approval.
func (a *AskPassCredentialHelper) Approve(_ Creds) error { return nil }

// Reject implements CredentialHelper.Reject, and returns nil. The ASKPASS
// credential helper does not implement credential rejection.
func (a *AskPassCredentialHelper) Reject(_ Creds) error { return nil }

// args returns the arguments given to the ASKPASS program, if a prompt was
// given.

// See: https://git-scm.com/docs/gitcredentials#_requesting_credentials for
// more.
func (a *AskPassCredentialHelper) args(prompt string) []string {
	if len(prompt) == 0 {
		return nil
	}
	return []string{prompt}
}

type commandCredentialHelper struct {
	SkipPrompt bool
}

func (h *commandCredentialHelper) Fill(creds Creds) (Creds, error) {
	tracerx.Printf("creds: git credential fill (%q, %q, %q)",
		creds["protocol"], creds["host"], creds["path"])
	return h.exec("fill", creds)
}

func (h *commandCredentialHelper) Reject(creds Creds) error {
	_, err := h.exec("reject", creds)
	return err
}

func (h *commandCredentialHelper) Approve(creds Creds) error {
	tracerx.Printf("creds: git credential approve (%q, %q, %q)",
		creds["protocol"], creds["host"], creds["path"])
	_, err := h.exec("approve", creds)
	return err
}

func (h *commandCredentialHelper) exec(subcommand string, input Creds) (Creds, error) {
	output := new(bytes.Buffer)
	cmd := exec.Command("git", "credential", subcommand)
	cmd.Stdin = bufferCreds(input)
	cmd.Stdout = output
	/*
	   There is a reason we don't hook up stderr here:
	   Git's credential cache daemon helper does not close its stderr, so if this
	   process is the process that fires up the daemon, it will wait forever
	   (until the daemon exits, really) trying to read from stderr.

	   See https://github.com/git-lfs/git-lfs/issues/117 for more details.
	*/

	err := cmd.Start()
	if err == nil {
		err = cmd.Wait()
	}

	if _, ok := err.(*exec.ExitError); ok {
		if h.SkipPrompt {
			return nil, fmt.Errorf("Change the GIT_TERMINAL_PROMPT env var to be prompted to enter your credentials for %s://%s.",
				input["protocol"], input["host"])
		}

		// 'git credential' exits with 128 if the helper doesn't fill the username
		// and password values.
		if subcommand == "fill" && err.Error() == "exit status 128" {
			return nil, nil
		}
	}

	if err != nil {
		return nil, fmt.Errorf("'git credential %s' error: %s\n", subcommand, err.Error())
	}

	creds := make(Creds)
	for _, line := range strings.Split(output.String(), "\n") {
		pieces := strings.SplitN(line, "=", 2)
		if len(pieces) < 2 || len(pieces[1]) < 1 {
			continue
		}
		creds[pieces[0]] = pieces[1]
	}

	return creds, nil
}

type credentialCacher struct {
	creds map[string]Creds
	mu    sync.Mutex
}

func newCredentialCacher() *credentialCacher {
	return &credentialCacher{creds: make(map[string]Creds)}
}

func credCacheKey(creds Creds) string {
	parts := []string{
		creds["protocol"],
		creds["host"],
		creds["path"],
	}
	return strings.Join(parts, "//")
}

func (c credentialCacher) Fill(what Creds) (Creds, error) {
	key := credCacheKey(what)
	c.mu.Lock()
	cached, ok := c.creds[key]
	c.mu.Unlock()

	if ok {
		return cached, nil
	}

	return nil, credHelperNoOp
}

func (c credentialCacher) Approve(what Creds) error {
	key := credCacheKey(what)

	c.mu.Lock()
	defer c.mu.Unlock()

	if _, ok := c.creds[key]; ok {
		return nil
	}

	c.creds[key] = what
	return credHelperNoOp
}

func (c credentialCacher) Reject(what Creds) error {
	key := credCacheKey(what)
	c.mu.Lock()
	delete(c.creds, key)
	c.mu.Unlock()
	return credHelperNoOp
}

// CredentialHelpers is a []CredentialHelper that iterates through each
// credential helper to fill, reject, or approve credentials.
type CredentialHelpers struct {
	helpers        []CredentialHelper
	skippedHelpers map[int]bool
	mu             sync.Mutex
}

func NewCredentialHelpers(helpers []CredentialHelper) CredentialHelper {
	return &CredentialHelpers{
		helpers:        helpers,
		skippedHelpers: make(map[int]bool),
	}
}

var credHelperNoOp = errors.New("no-op!")

// Fill implements CredentialHelper.Fill by asking each CredentialHelper in
// order to fill the credentials.
//
// If a fill was successful, it is returned immediately, and no other
// `CredentialHelper`s are consulted. If any CredentialHelper returns an error,
// it is returned immediately.
func (s CredentialHelpers) Fill(what Creds) (Creds, error) {
	errs := make([]string, 0, len(s.helpers))
	for i, h := range s.helpers {
		if s.skipped(i) {
			continue
		}

		creds, err := h.Fill(what)
		if err != nil {
			if err != credHelperNoOp {
				s.skip(i)
				tracerx.Printf("credential fill error: %s", err)
				errs = append(errs, err.Error())
			}
			continue
		}

		if creds != nil {
			return creds, nil
		}
	}

	if len(errs) > 0 {
		return nil, errors.New("credential fill errors:\n" + strings.Join(errs, "\n"))
	}

	return nil, nil
}

// Reject implements CredentialHelper.Reject and rejects the given Creds "what"
// amongst all knonw CredentialHelpers. If any `CredentialHelper`s returned a
// non-nil error, no further `CredentialHelper`s are notified, so as to prevent
// inconsistent state.
func (s CredentialHelpers) Reject(what Creds) error {
	for i, h := range s.helpers {
		if s.skipped(i) {
			continue
		}

		if err := h.Reject(what); err != credHelperNoOp {
			return err
		}
	}

	return errors.New("no valid credential helpers to reject")
}

// Approve implements CredentialHelper.Approve and approves the given Creds
// "what" amongst all known CredentialHelpers. If any `CredentialHelper`s
// returned a non-nil error, no further `CredentialHelper`s are notified, so as
// to prevent inconsistent state.
func (s CredentialHelpers) Approve(what Creds) error {
	skipped := make(map[int]bool)
	for i, h := range s.helpers {
		if s.skipped(i) {
			skipped[i] = true
			continue
		}

		if err := h.Approve(what); err != credHelperNoOp {
			if err != nil && i > 0 { // clear any cached approvals
				for j := 0; j < i; j++ {
					if !skipped[j] {
						s.helpers[j].Reject(what)
					}
				}
			}
			return err
		}
	}

	return errors.New("no valid credential helpers to approve")
}

func (s CredentialHelpers) skip(i int) {
	s.mu.Lock()
	s.skippedHelpers[i] = true
	s.mu.Unlock()
}

func (s CredentialHelpers) skipped(i int) bool {
	s.mu.Lock()
	skipped := s.skippedHelpers[i]
	s.mu.Unlock()
	return skipped
}

type nullCredentialHelper struct{}

var (
	nullCredError = errors.New("No credential helper configured")
	nullCreds     = &nullCredentialHelper{}
)

func (h *nullCredentialHelper) Fill(input Creds) (Creds, error) {
	return nil, nullCredError
}

func (h *nullCredentialHelper) Approve(creds Creds) error {
	return nil
}

func (h *nullCredentialHelper) Reject(creds Creds) error {
	return nil
}
<commit_msg>lfsapi: add tracer message when pulling from credential cache<commit_after>package lfsapi

import (
	"bytes"
	"fmt"
	"net/url"
	"os/exec"
	"strings"
	"sync"

	"github.com/git-lfs/git-lfs/errors"
	"github.com/rubyist/tracerx"
)

type CredentialHelper interface {
	Fill(Creds) (Creds, error)
	Reject(Creds) error
	Approve(Creds) error
}

type Creds map[string]string

func bufferCreds(c Creds) *bytes.Buffer {
	buf := new(bytes.Buffer)

	for k, v := range c {
		buf.Write([]byte(k))
		buf.Write([]byte("="))
		buf.Write([]byte(v))
		buf.Write([]byte("\n"))
	}

	return buf
}

// getCredentialHelper parses a 'credsConfig' from the git and OS environments,
// returning the appropriate CredentialHelper to authenticate requests with.
//
// It returns an error if any configuration was invalid, or otherwise
// un-useable.
func (c *Client) getCredentialHelper(u *url.URL) (CredentialHelper, Creds) {
	input := Creds{"protocol": u.Scheme, "host": u.Host}
	if u.User != nil && u.User.Username() != "" {
		input["username"] = u.User.Username()
	}
	if c.gitEnv.Bool("credential.usehttppath", false) {
		input["path"] = strings.TrimPrefix(u.Path, "/")
	}

	if c.Credentials != nil {
		return c.Credentials, input
	}

	helpers := make([]CredentialHelper, 0, 3)
	if c.cachingCredHelper != nil {
		helpers = append(helpers, c.cachingCredHelper)
	}
	if c.askpassCredHelper != nil {
		helper, _ := c.gitEnv.Get("credential.helper")
		if len(helper) == 0 {
			helpers = append(helpers, c.askpassCredHelper)
		}
	}

	return NewCredentialHelpers(append(helpers, c.commandCredHelper)), input
}

// AskPassCredentialHelper implements the CredentialHelper type for GIT_ASKPASS
// and 'core.askpass' configuration values.
type AskPassCredentialHelper struct {
	// Program is the executable program's absolute or relative name.
	Program string
}

// Fill implements fill by running the ASKPASS program and returning its output
// as a password encoded in the Creds type given the key "password".
//
// It accepts the password as coming from the program's stdout, as when invoked
// with the given arguments (see (*AskPassCredentialHelper).args() below)./
//
// If there was an error running the command, it is returned instead of a set of
// filled credentials.
func (a *AskPassCredentialHelper) Fill(what Creds) (Creds, error) {
	var user bytes.Buffer
	var pass bytes.Buffer
	var err bytes.Buffer

	u := &url.URL{
		Scheme: what["protocol"],
		Host:   what["host"],
		Path:   what["path"],
	}

	// 'ucmd' will run the GIT_ASKPASS (or core.askpass) command prompting
	// for a username.
	ucmd := exec.Command(a.Program, a.args(fmt.Sprintf("Username for %q", u))...)
	ucmd.Stderr = &err
	ucmd.Stdout = &user

	tracerx.Printf("creds: filling with GIT_ASKPASS: %s", strings.Join(ucmd.Args, " "))
	if err := ucmd.Run(); err != nil {
		return nil, err
	}

	if err.Len() > 0 {
		return nil, errors.New(err.String())
	}

	if username := strings.TrimSpace(user.String()); len(username) > 0 {
		// If a non-empty username was given, add it to the URL via func
		// 'net/url.User()'.
		u.User = url.User(username)
	}

	// Regardless, create 'pcmd' to run the GIT_ASKPASS (or core.askpass)
	// command prompting for a password.
	pcmd := exec.Command(a.Program, a.args(fmt.Sprintf("Password for %q", u))...)
	pcmd.Stderr = &err
	pcmd.Stdout = &pass

	tracerx.Printf("creds: filling with GIT_ASKPASS: %s", strings.Join(pcmd.Args, " "))
	if err := pcmd.Run(); err != nil {
		return nil, err
	}

	if err.Len() > 0 {
		return nil, errors.New(err.String())
	}

	// Finally, now that we have the username and password information,
	// store it in the creds instance that we will return to the caller.
	creds := make(Creds)
	creds["username"] = strings.TrimSpace(user.String())
	creds["password"] = strings.TrimSpace(pass.String())

	return creds, nil
}

// Approve implements CredentialHelper.Approve, and returns nil. The ASKPASS
// credential helper does not implement credential approval.
func (a *AskPassCredentialHelper) Approve(_ Creds) error { return nil }

// Reject implements CredentialHelper.Reject, and returns nil. The ASKPASS
// credential helper does not implement credential rejection.
func (a *AskPassCredentialHelper) Reject(_ Creds) error { return nil }

// args returns the arguments given to the ASKPASS program, if a prompt was
// given.

// See: https://git-scm.com/docs/gitcredentials#_requesting_credentials for
// more.
func (a *AskPassCredentialHelper) args(prompt string) []string {
	if len(prompt) == 0 {
		return nil
	}
	return []string{prompt}
}

type commandCredentialHelper struct {
	SkipPrompt bool
}

func (h *commandCredentialHelper) Fill(creds Creds) (Creds, error) {
	tracerx.Printf("creds: git credential fill (%q, %q, %q)",
		creds["protocol"], creds["host"], creds["path"])
	return h.exec("fill", creds)
}

func (h *commandCredentialHelper) Reject(creds Creds) error {
	_, err := h.exec("reject", creds)
	return err
}

func (h *commandCredentialHelper) Approve(creds Creds) error {
	tracerx.Printf("creds: git credential approve (%q, %q, %q)",
		creds["protocol"], creds["host"], creds["path"])
	_, err := h.exec("approve", creds)
	return err
}

func (h *commandCredentialHelper) exec(subcommand string, input Creds) (Creds, error) {
	output := new(bytes.Buffer)
	cmd := exec.Command("git", "credential", subcommand)
	cmd.Stdin = bufferCreds(input)
	cmd.Stdout = output
	/*
	   There is a reason we don't hook up stderr here:
	   Git's credential cache daemon helper does not close its stderr, so if this
	   process is the process that fires up the daemon, it will wait forever
	   (until the daemon exits, really) trying to read from stderr.

	   See https://github.com/git-lfs/git-lfs/issues/117 for more details.
	*/

	err := cmd.Start()
	if err == nil {
		err = cmd.Wait()
	}

	if _, ok := err.(*exec.ExitError); ok {
		if h.SkipPrompt {
			return nil, fmt.Errorf("Change the GIT_TERMINAL_PROMPT env var to be prompted to enter your credentials for %s://%s.",
				input["protocol"], input["host"])
		}

		// 'git credential' exits with 128 if the helper doesn't fill the username
		// and password values.
		if subcommand == "fill" && err.Error() == "exit status 128" {
			return nil, nil
		}
	}

	if err != nil {
		return nil, fmt.Errorf("'git credential %s' error: %s\n", subcommand, err.Error())
	}

	creds := make(Creds)
	for _, line := range strings.Split(output.String(), "\n") {
		pieces := strings.SplitN(line, "=", 2)
		if len(pieces) < 2 || len(pieces[1]) < 1 {
			continue
		}
		creds[pieces[0]] = pieces[1]
	}

	return creds, nil
}

type credentialCacher struct {
	creds map[string]Creds
	mu    sync.Mutex
}

func newCredentialCacher() *credentialCacher {
	return &credentialCacher{creds: make(map[string]Creds)}
}

func credCacheKey(creds Creds) string {
	parts := []string{
		creds["protocol"],
		creds["host"],
		creds["path"],
	}
	return strings.Join(parts, "//")
}

func (c credentialCacher) Fill(what Creds) (Creds, error) {
	key := credCacheKey(what)
	c.mu.Lock()
	cached, ok := c.creds[key]
	c.mu.Unlock()

	if ok {
		tracerx.Printf("creds: git credential cache (%q, %q, %q)",
			what["protocol"], what["host"], what["path"])
		return cached, nil
	}

	return nil, credHelperNoOp
}

func (c credentialCacher) Approve(what Creds) error {
	key := credCacheKey(what)

	c.mu.Lock()
	defer c.mu.Unlock()

	if _, ok := c.creds[key]; ok {
		return nil
	}

	c.creds[key] = what
	return credHelperNoOp
}

func (c credentialCacher) Reject(what Creds) error {
	key := credCacheKey(what)
	c.mu.Lock()
	delete(c.creds, key)
	c.mu.Unlock()
	return credHelperNoOp
}

// CredentialHelpers is a []CredentialHelper that iterates through each
// credential helper to fill, reject, or approve credentials.
type CredentialHelpers struct {
	helpers        []CredentialHelper
	skippedHelpers map[int]bool
	mu             sync.Mutex
}

func NewCredentialHelpers(helpers []CredentialHelper) CredentialHelper {
	return &CredentialHelpers{
		helpers:        helpers,
		skippedHelpers: make(map[int]bool),
	}
}

var credHelperNoOp = errors.New("no-op!")

// Fill implements CredentialHelper.Fill by asking each CredentialHelper in
// order to fill the credentials.
//
// If a fill was successful, it is returned immediately, and no other
// `CredentialHelper`s are consulted. If any CredentialHelper returns an error,
// it is returned immediately.
func (s CredentialHelpers) Fill(what Creds) (Creds, error) {
	errs := make([]string, 0, len(s.helpers))
	for i, h := range s.helpers {
		if s.skipped(i) {
			continue
		}

		creds, err := h.Fill(what)
		if err != nil {
			if err != credHelperNoOp {
				s.skip(i)
				tracerx.Printf("credential fill error: %s", err)
				errs = append(errs, err.Error())
			}
			continue
		}

		if creds != nil {
			return creds, nil
		}
	}

	if len(errs) > 0 {
		return nil, errors.New("credential fill errors:\n" + strings.Join(errs, "\n"))
	}

	return nil, nil
}

// Reject implements CredentialHelper.Reject and rejects the given Creds "what"
// amongst all knonw CredentialHelpers. If any `CredentialHelper`s returned a
// non-nil error, no further `CredentialHelper`s are notified, so as to prevent
// inconsistent state.
func (s CredentialHelpers) Reject(what Creds) error {
	for i, h := range s.helpers {
		if s.skipped(i) {
			continue
		}

		if err := h.Reject(what); err != credHelperNoOp {
			return err
		}
	}

	return errors.New("no valid credential helpers to reject")
}

// Approve implements CredentialHelper.Approve and approves the given Creds
// "what" amongst all known CredentialHelpers. If any `CredentialHelper`s
// returned a non-nil error, no further `CredentialHelper`s are notified, so as
// to prevent inconsistent state.
func (s CredentialHelpers) Approve(what Creds) error {
	skipped := make(map[int]bool)
	for i, h := range s.helpers {
		if s.skipped(i) {
			skipped[i] = true
			continue
		}

		if err := h.Approve(what); err != credHelperNoOp {
			if err != nil && i > 0 { // clear any cached approvals
				for j := 0; j < i; j++ {
					if !skipped[j] {
						s.helpers[j].Reject(what)
					}
				}
			}
			return err
		}
	}

	return errors.New("no valid credential helpers to approve")
}

func (s CredentialHelpers) skip(i int) {
	s.mu.Lock()
	s.skippedHelpers[i] = true
	s.mu.Unlock()
}

func (s CredentialHelpers) skipped(i int) bool {
	s.mu.Lock()
	skipped := s.skippedHelpers[i]
	s.mu.Unlock()
	return skipped
}

type nullCredentialHelper struct{}

var (
	nullCredError = errors.New("No credential helper configured")
	nullCreds     = &nullCredentialHelper{}
)

func (h *nullCredentialHelper) Fill(input Creds) (Creds, error) {
	return nil, nullCredError
}

func (h *nullCredentialHelper) Approve(creds Creds) error {
	return nil
}

func (h *nullCredentialHelper) Reject(creds Creds) error {
	return nil
}
<|endoftext|>