cmd/monfront - import monfront from separate repository
This is the import from the separate monfront repository. The history could not be imported, but this should suffice.
This commit is contained in:
parent
41d4805d58
commit
fa05045d31
36
Makefile
36
Makefile
@ -9,6 +9,8 @@ prefix ?= /usr/local
|
|||||||
exec_prefix ?= ${prefix}
|
exec_prefix ?= ${prefix}
|
||||||
bindir ?= ${exec_prefix}/bin
|
bindir ?= ${exec_prefix}/bin
|
||||||
sysconfdir ?= ${prefix}/etc/${NAME}
|
sysconfdir ?= ${prefix}/etc/${NAME}
|
||||||
|
datarootdir ?= ${prefix}/share
|
||||||
|
datadir ?= ${datarootdir}/${NAME}
|
||||||
WRKDIR ?= build
|
WRKDIR ?= build
|
||||||
GOBIN ?= go
|
GOBIN ?= go
|
||||||
|
|
||||||
@ -19,23 +21,51 @@ LDFLAGS += -B ${BUILDID}
|
|||||||
BUILD_DATE ?= `date +%FT%T%z`
|
BUILD_DATE ?= `date +%FT%T%z`
|
||||||
LDFLAGS += -X main.BUILD_DATE=${BUILD_DATE}
|
LDFLAGS += -X main.BUILD_DATE=${BUILD_DATE}
|
||||||
|
|
||||||
build: clean
|
MONFRONT_FILES = $(wildcard cmd/monfront/*.go) $(wildcard *.go)
|
||||||
|
|
||||||
|
all: build
|
||||||
|
|
||||||
|
build: env/${WRKDIR} moncheck monwork monfront
|
||||||
|
|
||||||
|
env/${WRKDIR}:
|
||||||
mkdir -p ${WRKDIR}
|
mkdir -p ${WRKDIR}
|
||||||
|
|
||||||
|
moncheck:
|
||||||
GOOS=${GOOS} CGO_ENABLED=false go build -ldflags="${LDFLAGS}" -o ${WRKDIR}/moncheck ${PKGNAME}/cmd/moncheck
|
GOOS=${GOOS} CGO_ENABLED=false go build -ldflags="${LDFLAGS}" -o ${WRKDIR}/moncheck ${PKGNAME}/cmd/moncheck
|
||||||
|
|
||||||
|
monwork:
|
||||||
GOOS=${GOOS} CGO_ENABLED=false go build -ldflags="${LDFLAGS}" -o ${WRKDIR}/monwork ${PKGNAME}/cmd/monwork
|
GOOS=${GOOS} CGO_ENABLED=false go build -ldflags="${LDFLAGS}" -o ${WRKDIR}/monwork ${PKGNAME}/cmd/monwork
|
||||||
|
|
||||||
|
monfront:
|
||||||
|
GOOS=${GOOS} CGO_ENABLED=false go build -ldflags="${LDFLAGS}" -o ${WRKDIR}/monfront ${PKGNAME}/cmd/monfront
|
||||||
|
|
||||||
clean:
|
clean:
|
||||||
-rm -r ${WRKDIR}
|
-rm -r ${WRKDIR}
|
||||||
|
|
||||||
install: build
|
install: build preinstall install-monwork install-moncheck install-monfront
|
||||||
|
|
||||||
|
preinstall:
|
||||||
install -d -m 0755 ${DESTDIR}${bindir}
|
install -d -m 0755 ${DESTDIR}${bindir}
|
||||||
install -d -m 0755 ${DESTDIR}${sysconfdir}
|
install -d -m 0755 ${DESTDIR}${sysconfdir}
|
||||||
|
|
||||||
|
install-moncheck: preinstall
|
||||||
install -m 0755 ${WRKDIR}/moncheck ${DESTDIR}${bindir}
|
install -m 0755 ${WRKDIR}/moncheck ${DESTDIR}${bindir}
|
||||||
install -m 0755 ${WRKDIR}/monwork ${DESTDIR}${bindir}
|
|
||||||
install -m 0644 moncheck.conf.example ${DESTDIR}${sysconfdir}
|
install -m 0644 moncheck.conf.example ${DESTDIR}${sysconfdir}
|
||||||
|
|
||||||
|
install-monwork: preinstall
|
||||||
|
install -m 0755 ${WRKDIR}/monwork ${DESTDIR}${bindir}
|
||||||
install -m 0644 monwork.conf.example ${DESTDIR}${sysconfdir}
|
install -m 0644 monwork.conf.example ${DESTDIR}${sysconfdir}
|
||||||
|
|
||||||
|
install-monfront: preinstall
|
||||||
|
install -m 0755 ${WRKDIR}/monfront ${DESTDIR}${bindir}
|
||||||
|
install -m 0644 monfront.conf.example ${DESTDIR}${sysconfdir}
|
||||||
|
install -d -m 0755 ${DESTDIR}${datadir}/templates
|
||||||
|
sed -i'' "s-\#template_path.*-template_path = \"${datadir}/templates\"-g" ${DESTDIR}${sysconfdir}/monfront.conf.example
|
||||||
|
find cmd/monfront/templates -type f -exec install -m 0644 "{}" ${DESTDIR}${datadir}/templates \;
|
||||||
|
|
||||||
package: DESTDIR = ${NAME}-${VERSION}
|
package: DESTDIR = ${NAME}-${VERSION}
|
||||||
package: install
|
package: install
|
||||||
tar -czf ${NAME}-${VERSION}.tar.gz ${DESTDIR}
|
tar -czf ${NAME}-${VERSION}.tar.gz ${DESTDIR}
|
||||||
rm -R ${DESTDIR}
|
rm -R ${DESTDIR}
|
||||||
|
|
||||||
|
.PHONY: clean build moncheck monwork monfront
|
||||||
|
72
cmd/monfront/README.md
Normal file
72
cmd/monfront/README.md
Normal file
@ -0,0 +1,72 @@
|
|||||||
|
monfront
|
||||||
|
========
|
||||||
|
|
||||||
|
Monfront is the frontend to manage monzero. Monzero consists of the other two
|
||||||
|
components moncheck and monwork too.
|
||||||
|
|
||||||
|
requirements
|
||||||
|
------------
|
||||||
|
|
||||||
|
runtime requirements:
|
||||||
|
* PostgreSQL >= 10.0
|
||||||
|
|
||||||
|
build requirements:
|
||||||
|
* Go >= 1.11
|
||||||
|
|
||||||
|
components
|
||||||
|
----------
|
||||||
|
|
||||||
|
The following components exist:
|
||||||
|
|
||||||
|
### monfront
|
||||||
|
|
||||||
|
Monfront is a webfrontend to view the current state of all checks, configure
|
||||||
|
hosts, groups, checks and view current notifications.
|
||||||
|
It is possible to run multiple instances.
|
||||||
|
|
||||||
|
configuration
|
||||||
|
-------------
|
||||||
|
|
||||||
|
To get the system working, first install the database. After that, create an
|
||||||
|
alarm mapping:
|
||||||
|
|
||||||
|
```
|
||||||
|
insert into mappings(name, description) values ('default', 'The default mapping');
|
||||||
|
insert into mapping_level values (1, 0, 0, 'okay', 'green');
|
||||||
|
insert into mapping_level values (1, 1, 1, 'okay', 'orange');
|
||||||
|
insert into mapping_level values (1, 2, 2, 'okay', 'red');
|
||||||
|
insert into mapping_level values (1, 3, 3, 'okay', 'gray');
|
||||||
|
```
|
||||||
|
|
||||||
|
Next is to create a notifier. This feature doesn't work 100% yet and needs some
|
||||||
|
work and may look different later:
|
||||||
|
|
||||||
|
```
|
||||||
|
insert into notifier(name) values ('default');
|
||||||
|
```
|
||||||
|
|
||||||
|
After that create a check command:
|
||||||
|
|
||||||
|
```
|
||||||
|
insert into commands(name, command, message) values ('ping', 'ping -n -c 1 {{ .ip }}', 'Ping a target');
|
||||||
|
```
|
||||||
|
|
||||||
|
This command can contain variables that are set in the check. It will be executed by moncheck and the result stored.
|
||||||
|
|
||||||
|
After that, create a node which will get the checks attached:
|
||||||
|
|
||||||
|
```
|
||||||
|
insert into nodes(name, message) values ('localhost', 'My localhost is my castle');
|
||||||
|
```
|
||||||
|
|
||||||
|
With that prepared, create the first check:
|
||||||
|
|
||||||
|
```
|
||||||
|
insert into checks(node_id, command_id, notifier_id, message, options)
|
||||||
|
values (1, 1, 1, 'This is my localhost ping check!', '{"ip": "127.0.0.1"}');
|
||||||
|
```
|
||||||
|
|
||||||
|
Now start the daemons moncheck, monfront and monwork.
|
||||||
|
|
||||||
|
monwork will transform the configured check into an active check, while moncheck
|
||||||
|
will run the actual checks. Through monfront one can view the current status.
|
183
cmd/monfront/authenticater.go
Normal file
183
cmd/monfront/authenticater.go
Normal file
@ -0,0 +1,183 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/hmac"
|
||||||
|
"crypto/rand"
|
||||||
|
"crypto/sha256"
|
||||||
|
"database/sql"
|
||||||
|
"encoding/base64"
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
BasicAuthPrompt = `Basic realm="auth for monfront"`
|
||||||
|
SessionCookie = `session`
|
||||||
|
UserAnonymous = `anonymous`
|
||||||
|
)
|
||||||
|
|
||||||
|
type (
|
||||||
|
// Authenticator is a middleware taking a context and authenticating
|
||||||
|
// the user.
|
||||||
|
Authenticator struct {
|
||||||
|
db *sql.DB
|
||||||
|
Mode string
|
||||||
|
Token []byte
|
||||||
|
AllowAnonymous bool
|
||||||
|
Header string
|
||||||
|
List [][]string
|
||||||
|
ClientCA string
|
||||||
|
|
||||||
|
sessions map[string]*session // maps a session key to a user
|
||||||
|
}
|
||||||
|
|
||||||
|
session struct {
|
||||||
|
user string
|
||||||
|
t time.Time
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
// Handler returns the handler for the authentication configuration.
|
||||||
|
func (a *Authenticator) Handler() (func(*Context) error, error) {
|
||||||
|
switch a.Mode {
|
||||||
|
case "none":
|
||||||
|
return func(_ *Context) error { return nil }, nil
|
||||||
|
case "header":
|
||||||
|
if a.Header == "" {
|
||||||
|
return nil, fmt.Errorf("authentication mode is 'header' but no header was provided")
|
||||||
|
}
|
||||||
|
return func(c *Context) error {
|
||||||
|
if user := c.r.Header.Get(a.Header); user == "" {
|
||||||
|
if a.AllowAnonymous {
|
||||||
|
c.User = UserAnonymous
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return a.Unauthorized(c)
|
||||||
|
} else {
|
||||||
|
c.User = user
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}, nil
|
||||||
|
case "list":
|
||||||
|
return func(c *Context) error {
|
||||||
|
user, pass, ok := c.r.BasicAuth()
|
||||||
|
if !ok || user == "" || pass == "" {
|
||||||
|
if a.AllowAnonymous {
|
||||||
|
c.User = UserAnonymous
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
c.w.Header().Set("WWW-Authenticate", BasicAuthPrompt)
|
||||||
|
return a.Unauthorized(c)
|
||||||
|
}
|
||||||
|
var found string
|
||||||
|
for _, entry := range a.List {
|
||||||
|
if entry[0] == user {
|
||||||
|
found = entry[1]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if found == "" {
|
||||||
|
c.w.Header().Set("WWW-Authenticate", BasicAuthPrompt)
|
||||||
|
return a.Unauthorized(c)
|
||||||
|
}
|
||||||
|
p := pwHash{}
|
||||||
|
if err := p.Parse(found); err != nil {
|
||||||
|
log.Printf("could not parse hash for user '%s': %s", user, err)
|
||||||
|
return a.Unauthorized(c)
|
||||||
|
}
|
||||||
|
if ok, err := p.compare(pass); err != nil {
|
||||||
|
c.w.Header().Set("WWW-Authenticate", BasicAuthPrompt)
|
||||||
|
return a.Unauthorized(c)
|
||||||
|
} else if !ok {
|
||||||
|
c.w.Header().Set("WWW-Authenticate", BasicAuthPrompt)
|
||||||
|
return a.Unauthorized(c)
|
||||||
|
}
|
||||||
|
c.User = user
|
||||||
|
return nil
|
||||||
|
}, nil
|
||||||
|
case "db":
|
||||||
|
return func(c *Context) error {
|
||||||
|
sessCookie := c.GetCookieVal(SessionCookie)
|
||||||
|
if sessCookie != "" {
|
||||||
|
ses := a.getSession(sessCookie)
|
||||||
|
if ses != "" {
|
||||||
|
// TODO fix time limit to make it variable
|
||||||
|
c.SetCookie(SessionCookie, sessCookie, time.Now().Add(2*time.Hour))
|
||||||
|
c.User = ses
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return fmt.Errorf("NOT YET IMPLEMENTED")
|
||||||
|
}, fmt.Errorf("NOT YET IMPLEMENTED")
|
||||||
|
case "cert":
|
||||||
|
return func(c *Context) error {
|
||||||
|
return fmt.Errorf("NOT YET IMPLEMENTED")
|
||||||
|
}, fmt.Errorf("NOT YET IMPLEMENTED")
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf("unknown mode '%s' for authentication", a.Mode)
|
||||||
|
}
|
||||||
|
return nil, fmt.Errorf("could not create authenticator")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *Authenticator) Unauthorized(c *Context) error {
|
||||||
|
c.w.WriteHeader(http.StatusUnauthorized)
|
||||||
|
fmt.Fprintf(c.w, "unauthorized\n")
|
||||||
|
return fmt.Errorf("no authentication")
|
||||||
|
}
|
||||||
|
|
||||||
|
// creates a session for a user
|
||||||
|
func (a *Authenticator) createSession(user string) (string, error) {
|
||||||
|
raw := make([]byte, 32)
|
||||||
|
if _, err := rand.Read(raw); err != nil {
|
||||||
|
return "", fmt.Errorf("could not generate new session key")
|
||||||
|
}
|
||||||
|
res := a.mac(raw)
|
||||||
|
ses := fmt.Sprintf(
|
||||||
|
"%s-%s",
|
||||||
|
base64.StdEncoding.EncodeToString(raw),
|
||||||
|
base64.StdEncoding.EncodeToString(res),
|
||||||
|
)
|
||||||
|
a.sessions[ses] = &session{user: user, t: time.Now()}
|
||||||
|
return ses, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *Authenticator) mac(input []byte) []byte {
|
||||||
|
mac := hmac.New(sha256.New, a.Token)
|
||||||
|
mac.Write(input)
|
||||||
|
return mac.Sum(nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
// getSession returns the username of the current session.
|
||||||
|
func (a *Authenticator) getSession(session string) string {
|
||||||
|
if session == "" {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
parts := strings.Split(session, "-")
|
||||||
|
if len(parts) != 2 {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
msg, err := base64.StdEncoding.DecodeString(parts[0])
|
||||||
|
if err != nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
mac, err := base64.StdEncoding.DecodeString(parts[1])
|
||||||
|
if err != nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
verify := a.mac(msg)
|
||||||
|
if !hmac.Equal(mac, verify) {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
if ses, found := a.sessions[session]; found {
|
||||||
|
// TODO make timeout a config option
|
||||||
|
if time.Now().Sub(ses.t) < 8*time.Hour {
|
||||||
|
delete(a.sessions, session)
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
ses.t = time.Now()
|
||||||
|
return ses.user
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
35
cmd/monfront/authorizer.go
Normal file
35
cmd/monfront/authorizer.go
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"database/sql"
|
||||||
|
"fmt"
|
||||||
|
)
|
||||||
|
|
||||||
|
type (
|
||||||
|
Authorizer struct {
|
||||||
|
db *sql.DB
|
||||||
|
Mode string
|
||||||
|
List []string
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
func (a *Authorizer) Handler() (func(c *Context) error, error) {
|
||||||
|
switch a.Mode {
|
||||||
|
case "none":
|
||||||
|
return func(_ *Context) error { return nil }, nil
|
||||||
|
case "list":
|
||||||
|
return func(c *Context) error {
|
||||||
|
for _, user := range a.List {
|
||||||
|
if user == c.User {
|
||||||
|
c.CanEdit = true
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}, nil
|
||||||
|
case "all":
|
||||||
|
return func(c *Context) error { c.CanEdit = true; return nil }, nil
|
||||||
|
default:
|
||||||
|
return func(_ *Context) error { return nil }, fmt.Errorf("authorization mode '%s' is unsupported", a.Mode)
|
||||||
|
}
|
||||||
|
}
|
226
cmd/monfront/checks.go
Normal file
226
cmd/monfront/checks.go
Normal file
@ -0,0 +1,226 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"database/sql"
|
||||||
|
"log"
|
||||||
|
"net/http"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/lib/pq"
|
||||||
|
)
|
||||||
|
|
||||||
|
type (
|
||||||
|
check struct {
|
||||||
|
NodeId int
|
||||||
|
NodeName string
|
||||||
|
CommandName string
|
||||||
|
CheckID int64
|
||||||
|
CheckName string
|
||||||
|
MappingId int
|
||||||
|
State int
|
||||||
|
Enabled bool
|
||||||
|
Notify bool
|
||||||
|
Notice sql.NullString
|
||||||
|
NextTime time.Time
|
||||||
|
Msg string
|
||||||
|
StateSince time.Time
|
||||||
|
}
|
||||||
|
|
||||||
|
checkDetails struct {
|
||||||
|
Id int64
|
||||||
|
Name string
|
||||||
|
Message string
|
||||||
|
Enabled bool
|
||||||
|
Updated time.Time
|
||||||
|
LastRefresh time.Time
|
||||||
|
NextTime time.Time
|
||||||
|
MappingId int
|
||||||
|
MappingName string
|
||||||
|
NodeId int
|
||||||
|
NodeName string
|
||||||
|
NodeMessage string
|
||||||
|
CommandId int
|
||||||
|
CommandName string
|
||||||
|
CommandLine []string
|
||||||
|
CommandMessage string
|
||||||
|
States []int64
|
||||||
|
Notice sql.NullString
|
||||||
|
Notifiers []notifier
|
||||||
|
Notifications []notification
|
||||||
|
CheckerID int
|
||||||
|
CheckerName string
|
||||||
|
CheckerMsg string
|
||||||
|
}
|
||||||
|
|
||||||
|
notifier struct {
|
||||||
|
Id int
|
||||||
|
Name string
|
||||||
|
Enabled bool
|
||||||
|
}
|
||||||
|
|
||||||
|
notification struct {
|
||||||
|
Id int64
|
||||||
|
State int
|
||||||
|
Output string
|
||||||
|
Inserted time.Time
|
||||||
|
Sent pq.NullTime
|
||||||
|
NotifierName string
|
||||||
|
MappingId int
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
// showCheck loads shows the notifications for a specific check.
|
||||||
|
func showCheck(con *Context) {
|
||||||
|
cd := checkDetails{}
|
||||||
|
con.CheckDetails = &cd
|
||||||
|
id, found := con.r.URL.Query()["check_id"]
|
||||||
|
if !found {
|
||||||
|
con.Error = "no check given to view"
|
||||||
|
returnError(http.StatusNotFound, con, con.w)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
query := `select c.id, c.name, c.message, c.enabled, c.updated, c.last_refresh,
|
||||||
|
m.id, m.name, n.id, n.name, n.message, co.id, co.Name, co.message,
|
||||||
|
ac.cmdline, ac.states, ac.msg, ac.next_time, ch.id, ch.name, ch.description
|
||||||
|
from checks c
|
||||||
|
join active_checks ac on c.id = ac.check_id
|
||||||
|
join nodes n on c.node_id = n.id
|
||||||
|
join commands co on c.command_id = co.id
|
||||||
|
join mappings m on ac.mapping_id = m.id
|
||||||
|
join checkers ch on c.checker_id = ch.id
|
||||||
|
where c.id = $1::bigint`
|
||||||
|
err := DB.QueryRow(query, id[0]).Scan(&cd.Id, &cd.Name, &cd.Message, &cd.Enabled,
|
||||||
|
&cd.Updated, &cd.LastRefresh, &cd.MappingId, &cd.MappingName, &cd.NodeId,
|
||||||
|
&cd.NodeName, &cd.NodeMessage, &cd.CommandId, &cd.CommandName, &cd.CommandMessage,
|
||||||
|
pq.Array(&cd.CommandLine), pq.Array(&cd.States), &cd.Notice, &cd.NextTime,
|
||||||
|
&cd.CheckerID, &cd.CheckerName, &cd.CheckerMsg)
|
||||||
|
if err != nil && err == sql.ErrNoRows {
|
||||||
|
con.w.Header()["Location"] = []string{"/"}
|
||||||
|
con.w.WriteHeader(http.StatusSeeOther)
|
||||||
|
return
|
||||||
|
} else if err != nil {
|
||||||
|
con.w.WriteHeader(http.StatusInternalServerError)
|
||||||
|
con.w.Write([]byte("problems with the database"))
|
||||||
|
log.Printf("could not get check details for check id %s: %s", id[0], err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
query = `select n.id, states[1], output, inserted, sent, no.name, n.mapping_id
|
||||||
|
from notifications n
|
||||||
|
join notifier no on n.notifier_id = no.id
|
||||||
|
where check_id = $1::bigint
|
||||||
|
order by inserted desc
|
||||||
|
limit 500`
|
||||||
|
rows, err := DB.Query(query, cd.Id)
|
||||||
|
defer rows.Close()
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("could not load notifications: %s", err)
|
||||||
|
con.Error = "could not load notification information"
|
||||||
|
returnError(http.StatusInternalServerError, con, con.w)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
cd.Notifications = []notification{}
|
||||||
|
for rows.Next() {
|
||||||
|
if err := rows.Err(); err != nil {
|
||||||
|
log.Printf("could not load notifications: %s", err)
|
||||||
|
con.Error = "could not load notification information"
|
||||||
|
returnError(http.StatusInternalServerError, con, con.w)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
no := notification{}
|
||||||
|
if err := rows.Scan(&no.Id, &no.State, &no.Output, &no.Inserted,
|
||||||
|
&no.Sent, &no.NotifierName, &no.MappingId); err != nil {
|
||||||
|
log.Printf("could not scan notifications: %s", err)
|
||||||
|
con.Error = "could not load notification information"
|
||||||
|
returnError(http.StatusInternalServerError, con, con.w)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
cd.Notifications = append(cd.Notifications, no)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := con.loadMappings(); err != nil {
|
||||||
|
con.w.WriteHeader(http.StatusInternalServerError)
|
||||||
|
con.w.Write([]byte("problem with the mappings"))
|
||||||
|
log.Printf("could not load mappings: %s", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
con.w.Header()["Content-Type"] = []string{"text/html"}
|
||||||
|
con.Render("check")
|
||||||
|
}
|
||||||
|
|
||||||
|
func showChecks(con *Context) {
|
||||||
|
query := `select c.id, c.name, n.id, n.name, co.name, ac.mapping_id, ac.states[1] as state,
|
||||||
|
ac.enabled, ac.notice, ac.next_time, ac.msg,
|
||||||
|
case when cn.check_id is null then false else true end as notify_enabled,
|
||||||
|
state_since
|
||||||
|
from active_checks ac
|
||||||
|
join checks c on ac.check_id = c.id
|
||||||
|
join nodes n on c.node_id = n.id
|
||||||
|
join commands co on c.command_id = co.id
|
||||||
|
left join ( select distinct check_id from checks_notify where enabled = true) cn on c.id = cn.check_id`
|
||||||
|
filter := newFilter()
|
||||||
|
con.Filter = filter
|
||||||
|
if id, found := con.r.URL.Query()["group_id"]; found {
|
||||||
|
query += ` join nodes_groups ng on n.id = ng.node_id`
|
||||||
|
filter.Add("ng.group_id", "=", id[0], "int")
|
||||||
|
}
|
||||||
|
filter.filterChecks(con)
|
||||||
|
if search, found := con.r.URL.Query()["search"]; found {
|
||||||
|
filter.AddSpecial(
|
||||||
|
`to_tsvector('english', regexp_replace(n.name, '[.-/]', ' ', 'g'))`,
|
||||||
|
`@@`,
|
||||||
|
`to_tsquery('english', regexp_replace($%d, '[.-/]', ' & ', 'g') || ':*')`,
|
||||||
|
search[0])
|
||||||
|
}
|
||||||
|
if id, found := con.r.URL.Query()["node_id"]; found {
|
||||||
|
filter.Add("n.id", "=", id[0], "int")
|
||||||
|
}
|
||||||
|
if id, found := con.r.URL.Query()["check_id"]; found {
|
||||||
|
filter.Add("c.id", "=", id[0], "int")
|
||||||
|
}
|
||||||
|
where, params := filter.Join()
|
||||||
|
if len(where) > 0 {
|
||||||
|
query += " where " + where
|
||||||
|
}
|
||||||
|
query += ` order by n.name, c.name, co.name`
|
||||||
|
rows, err := DB.Query(query, params...)
|
||||||
|
if err != nil {
|
||||||
|
con.w.WriteHeader(http.StatusInternalServerError)
|
||||||
|
con.w.Write([]byte("problems with the database"))
|
||||||
|
log.Printf("could not get check list: %s", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer rows.Close()
|
||||||
|
|
||||||
|
checks := []check{}
|
||||||
|
for rows.Next() {
|
||||||
|
c := check{}
|
||||||
|
err := rows.Scan(&c.CheckID, &c.CheckName, &c.NodeId, &c.NodeName, &c.CommandName, &c.MappingId,
|
||||||
|
&c.State, &c.Enabled, &c.Notice, &c.NextTime, &c.Msg, &c.Notify, &c.StateSince)
|
||||||
|
if err != nil {
|
||||||
|
con.w.WriteHeader(http.StatusInternalServerError)
|
||||||
|
returnError(http.StatusInternalServerError, con, con.w)
|
||||||
|
log.Printf("could not get check list: %s", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
checks = append(checks, c)
|
||||||
|
}
|
||||||
|
con.Checks = checks
|
||||||
|
if err := con.loadCommands(); err != nil {
|
||||||
|
con.Error = "could not load commands"
|
||||||
|
returnError(http.StatusInternalServerError, con, con.w)
|
||||||
|
log.Printf("could not get commands: %s", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err := con.loadMappings(); err != nil {
|
||||||
|
con.Error = "could not load mapping data"
|
||||||
|
con.w.WriteHeader(http.StatusInternalServerError)
|
||||||
|
con.w.Write([]byte("problem with the mappings"))
|
||||||
|
log.Printf("could not load mappings: %s", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
con.w.Header()["Content-Type"] = []string{"text/html"}
|
||||||
|
con.Render("checklist")
|
||||||
|
return
|
||||||
|
}
|
96
cmd/monfront/filter.go
Normal file
96
cmd/monfront/filter.go
Normal file
@ -0,0 +1,96 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
type (
|
||||||
|
filter struct {
|
||||||
|
idx int
|
||||||
|
where []string
|
||||||
|
params []interface{}
|
||||||
|
Vals map[string]string
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
func newFilter() *filter {
|
||||||
|
return &filter{
|
||||||
|
idx: 0,
|
||||||
|
where: []string{},
|
||||||
|
params: []interface{}{},
|
||||||
|
Vals: map[string]string{},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *filter) filterChecks(c *Context) {
|
||||||
|
args := c.r.URL.Query()
|
||||||
|
for name, val := range args {
|
||||||
|
if !strings.HasPrefix(name, "filter-") {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
arg := strings.TrimPrefix(name, "filter-")
|
||||||
|
switch arg {
|
||||||
|
case "command":
|
||||||
|
if val[0] == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
f.Add("co.id", "=", val[0], "int")
|
||||||
|
f.Vals[arg] = val[0]
|
||||||
|
case "search":
|
||||||
|
if val[0] == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
f.Add(`n.name`, `like`, strings.ReplaceAll(val[0], "*", "%"), "text")
|
||||||
|
f.Vals[arg] = val[0]
|
||||||
|
case "state":
|
||||||
|
if val[0] == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
f.Add("states[1]", ">=", val[0], "int")
|
||||||
|
f.Vals[arg] = val[0]
|
||||||
|
case "ack":
|
||||||
|
if val[0] == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if val[0] != "true" && val[0] != "false" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
f.Add("acknowledged", "=", val[0], "boolean")
|
||||||
|
f.Vals[arg] = val[0]
|
||||||
|
case "mapping":
|
||||||
|
if val[0] == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
f.Add("ac.mapping_id", "=", val[0], "int")
|
||||||
|
f.Vals[arg] = val[0]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add a new where clause element which will be joined at the end.
|
||||||
|
func (f *filter) Add(field, op string, arg interface{}, castTo string) {
|
||||||
|
f.idx += 1
|
||||||
|
f.where = append(f.where, fmt.Sprintf("%s %s $%d::%s", field, op, f.idx, castTo))
|
||||||
|
f.params = append(f.params, arg)
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddSpecial lets you add a special where clause comparison where you can
|
||||||
|
// wrap the argument in whatevery you like.
|
||||||
|
//
|
||||||
|
// Your string has to contain %d. This will place the index of the variable
|
||||||
|
// in the query string.
|
||||||
|
//
|
||||||
|
// Example:
|
||||||
|
// AddSpecial("foo", "=", "to_tsvector('english', $%d), search)
|
||||||
|
func (f *filter) AddSpecial(field, op, special string, arg interface{}) {
|
||||||
|
f.idx += 1
|
||||||
|
f.where = append(f.where, fmt.Sprintf("%s %s "+special, field, op, f.idx))
|
||||||
|
f.params = append(f.params, arg)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Join takes all where clauses and joins them together with the AND operator.
|
||||||
|
// The result and all collected parameters are then returned.
|
||||||
|
func (f *filter) Join() (string, []interface{}) {
|
||||||
|
return strings.Join(f.where, " and "), f.params
|
||||||
|
}
|
85
cmd/monfront/groups.go
Normal file
85
cmd/monfront/groups.go
Normal file
@ -0,0 +1,85 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
type (
|
||||||
|
group struct {
|
||||||
|
GroupId int
|
||||||
|
Name string
|
||||||
|
NodeId int
|
||||||
|
NodeName string
|
||||||
|
State int
|
||||||
|
MappingId int
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
func showGroups(con *Context) {
|
||||||
|
query := `select
|
||||||
|
group_id,
|
||||||
|
group_name,
|
||||||
|
node_id,
|
||||||
|
node_name,
|
||||||
|
mapping_id,
|
||||||
|
state
|
||||||
|
from (
|
||||||
|
select
|
||||||
|
g.id group_id,
|
||||||
|
g.name group_name,
|
||||||
|
n.id node_id,
|
||||||
|
n.name node_name,
|
||||||
|
ac.states[1] state,
|
||||||
|
ac.mapping_id,
|
||||||
|
ac.acknowledged,
|
||||||
|
row_number() over (partition by c.node_id order by ac.states[1] desc) maxstate
|
||||||
|
from groups g
|
||||||
|
join nodes_groups ng on g.id = ng.group_id
|
||||||
|
join nodes n on ng.node_id = n.id
|
||||||
|
join checks c on n.id = c.node_id
|
||||||
|
join active_checks ac on c.id = ac.check_id
|
||||||
|
%s
|
||||||
|
order by g.name, n.name
|
||||||
|
) groups
|
||||||
|
where maxstate = 1`
|
||||||
|
if strings.HasPrefix(con.r.URL.Path, "/unhandled") {
|
||||||
|
query = fmt.Sprintf(query, `where ac.states[1] != 0 and acknowledged = false`)
|
||||||
|
con.Unhandled = true
|
||||||
|
} else {
|
||||||
|
query = fmt.Sprintf(query, "")
|
||||||
|
}
|
||||||
|
|
||||||
|
rows, err := DB.Query(query)
|
||||||
|
if err != nil {
|
||||||
|
con.w.WriteHeader(http.StatusInternalServerError)
|
||||||
|
con.w.Write([]byte("problems with the database"))
|
||||||
|
log.Printf("could not get check list: %s", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
groups := []group{}
|
||||||
|
for rows.Next() {
|
||||||
|
g := group{}
|
||||||
|
err := rows.Scan(&g.GroupId, &g.Name, &g.NodeId, &g.NodeName, &g.MappingId, &g.State)
|
||||||
|
if err != nil {
|
||||||
|
con.w.WriteHeader(http.StatusInternalServerError)
|
||||||
|
con.w.Write([]byte("problems with the database"))
|
||||||
|
log.Printf("could not get check list: %s", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
groups = append(groups, g)
|
||||||
|
}
|
||||||
|
con.Groups = groups
|
||||||
|
if err := con.loadMappings(); err != nil {
|
||||||
|
con.w.WriteHeader(http.StatusInternalServerError)
|
||||||
|
con.w.Write([]byte("problem with the mappings"))
|
||||||
|
log.Printf("could not load mappings: %s", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
con.w.Header()["Content-Type"] = []string{"text/html"}
|
||||||
|
con.Render("grouplist")
|
||||||
|
return
|
||||||
|
}
|
426
cmd/monfront/main.go
Normal file
426
cmd/monfront/main.go
Normal file
@ -0,0 +1,426 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/tls"
|
||||||
|
"database/sql"
|
||||||
|
"flag"
|
||||||
|
"fmt"
|
||||||
|
"html/template"
|
||||||
|
"io/ioutil"
|
||||||
|
"log"
|
||||||
|
"net"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"path"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/BurntSushi/toml"
|
||||||
|
"github.com/lib/pq"
|
||||||
|
"golang.org/x/crypto/ssh/terminal"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
configPath = flag.String("config", "monfront.conf", "path to the config file")
|
||||||
|
DB *sql.DB
|
||||||
|
Tmpl *template.Template
|
||||||
|
)
|
||||||
|
|
||||||
|
type (
|
||||||
|
Config struct {
|
||||||
|
DB string `toml:"db"`
|
||||||
|
Listen string `toml:"listen"`
|
||||||
|
TemplatePath string `toml:"template_path"`
|
||||||
|
SSL struct {
|
||||||
|
Enable bool `toml:"enable"`
|
||||||
|
Priv string `toml:"private_key"`
|
||||||
|
Cert string `toml:"certificate"`
|
||||||
|
} `toml:"ssl"`
|
||||||
|
Authentication struct {
|
||||||
|
Mode string `toml:"mode"`
|
||||||
|
Token string `toml:"session_token"`
|
||||||
|
AllowAnonymous bool `toml:"allow_anonymous"`
|
||||||
|
Header string `toml:"header"`
|
||||||
|
List [][]string `toml:"list"`
|
||||||
|
ClientCA string `toml:"cert"`
|
||||||
|
} `toml:"authentication"`
|
||||||
|
Authorization struct {
|
||||||
|
Mode string `toml:"mode"`
|
||||||
|
List []string `toml:"list"`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
MapEntry struct {
|
||||||
|
Name string
|
||||||
|
Title string
|
||||||
|
Color string
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
flag.Parse()
|
||||||
|
|
||||||
|
if len(flag.Args()) > 0 {
|
||||||
|
switch flag.Arg(0) {
|
||||||
|
case "pwgen":
|
||||||
|
fmt.Printf("enter password: ")
|
||||||
|
pw, err := terminal.ReadPassword(0)
|
||||||
|
fmt.Println()
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("could not read password: %s", err)
|
||||||
|
}
|
||||||
|
hash, err := newHash(string(pw))
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("could not generate password hash: %s", err)
|
||||||
|
}
|
||||||
|
fmt.Printf("generated password hash: %s\n", hash)
|
||||||
|
os.Exit(0)
|
||||||
|
default:
|
||||||
|
log.Fatalf("unknown command '%s'", flag.Arg(0))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if info, err := os.Stat(*configPath); err != nil {
|
||||||
|
log.Fatalf("could not find config '%s': %s", *configPath, err)
|
||||||
|
} else if info.Mode() != 0600 && info.Mode() != 0400 {
|
||||||
|
log.Fatalf("config '%s' is world readable!", *configPath)
|
||||||
|
}
|
||||||
|
|
||||||
|
raw, err := ioutil.ReadFile(*configPath)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("could not read config: %s", err)
|
||||||
|
}
|
||||||
|
config := Config{
|
||||||
|
Listen: "127.0.0.1:8080",
|
||||||
|
TemplatePath: "templates",
|
||||||
|
}
|
||||||
|
if err := toml.Unmarshal(raw, &config); err != nil {
|
||||||
|
log.Fatalf("could not parse config: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
db, err := sql.Open("postgres", config.DB)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("could not open database connection: %s", err)
|
||||||
|
}
|
||||||
|
DB = db
|
||||||
|
|
||||||
|
authenticator := Authenticator{
|
||||||
|
db: db,
|
||||||
|
Mode: config.Authentication.Mode,
|
||||||
|
Token: []byte(config.Authentication.Token),
|
||||||
|
AllowAnonymous: config.Authentication.AllowAnonymous,
|
||||||
|
Header: config.Authentication.Header,
|
||||||
|
List: config.Authentication.List,
|
||||||
|
ClientCA: config.Authentication.ClientCA,
|
||||||
|
}
|
||||||
|
auth, err := authenticator.Handler()
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("could not start authenticator")
|
||||||
|
}
|
||||||
|
authorizer := Authorizer{
|
||||||
|
db: db,
|
||||||
|
Mode: config.Authorization.Mode,
|
||||||
|
List: config.Authorization.List,
|
||||||
|
}
|
||||||
|
autho, err := authorizer.Handler()
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("could not start authorizer")
|
||||||
|
}
|
||||||
|
|
||||||
|
tmpl := template.New("main")
|
||||||
|
tmpl.Funcs(Funcs)
|
||||||
|
files, err := ioutil.ReadDir(config.TemplatePath)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("could not read directory '%s': %s", config.TemplatePath, err)
|
||||||
|
}
|
||||||
|
for _, file := range files {
|
||||||
|
if !file.Mode().IsRegular() {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if !strings.HasSuffix(file.Name(), ".html") {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
raw, err := ioutil.ReadFile(path.Join(config.TemplatePath, file.Name()))
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("could not read file '%s': %s", path.Join(config.TemplatePath, file.Name()), err)
|
||||||
|
}
|
||||||
|
template.Must(tmpl.New(strings.TrimSuffix(file.Name(), ".html")).Parse(string(raw)))
|
||||||
|
}
|
||||||
|
Tmpl = tmpl
|
||||||
|
|
||||||
|
if config.Listen == "" {
|
||||||
|
config.Listen = "127.0.0.1:8080"
|
||||||
|
}
|
||||||
|
l, err := net.Listen("tcp", config.Listen)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("could not create listener: %s", err)
|
||||||
|
}
|
||||||
|
if config.SSL.Enable {
|
||||||
|
cert, err := tls.LoadX509KeyPair(config.SSL.Cert, config.SSL.Priv)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("could not load certificate: %s", err)
|
||||||
|
}
|
||||||
|
tlsConf := &tls.Config{
|
||||||
|
Certificates: []tls.Certificate{cert},
|
||||||
|
NextProtos: []string{"h2", "1.1"},
|
||||||
|
}
|
||||||
|
l = tls.NewListener(l, tlsConf)
|
||||||
|
}
|
||||||
|
|
||||||
|
s := newServer(l, db, tmpl, auth, autho)
|
||||||
|
s.Handle("/", showChecks)
|
||||||
|
s.Handle("/check", showCheck)
|
||||||
|
s.Handle("/checks", showChecks)
|
||||||
|
s.Handle("/groups", showGroups)
|
||||||
|
s.Handle("/action", checkAction)
|
||||||
|
s.HandleStatic("/static/", showStatic)
|
||||||
|
log.Fatalf("http server stopped: %s", s.ListenAndServe())
|
||||||
|
}
|
||||||
|
|
||||||
|
func checkAction(con *Context) {
|
||||||
|
if con.r.Method != "POST" {
|
||||||
|
con.w.WriteHeader(http.StatusMethodNotAllowed)
|
||||||
|
con.w.Write([]byte("method is not supported"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if !con.CanEdit {
|
||||||
|
con.w.WriteHeader(http.StatusForbidden)
|
||||||
|
con.w.Write([]byte("no permission to change data"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err := con.r.ParseForm(); err != nil {
|
||||||
|
con.w.WriteHeader(http.StatusBadRequest)
|
||||||
|
fmt.Fprintf(con.w, "could not parse parameters: %s", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
ref, found := con.r.Header["Referer"]
|
||||||
|
if found {
|
||||||
|
con.w.Header()["Location"] = ref
|
||||||
|
} else {
|
||||||
|
con.w.Header()["Location"] = []string{"/"}
|
||||||
|
}
|
||||||
|
checks := con.r.PostForm["checks"]
|
||||||
|
action := con.r.PostForm.Get("action")
|
||||||
|
if action == "" || len(checks) == 0 {
|
||||||
|
con.w.WriteHeader(http.StatusSeeOther)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
setTable := "checks"
|
||||||
|
setClause := ""
|
||||||
|
|
||||||
|
comment := con.r.PostForm.Get("comment")
|
||||||
|
run_in := con.r.PostForm.Get("run_in")
|
||||||
|
if action == "comment" && comment == "" && run_in != "" {
|
||||||
|
action = "reschedule"
|
||||||
|
}
|
||||||
|
|
||||||
|
switch action {
|
||||||
|
case "mute":
|
||||||
|
setTable = "checks_notify"
|
||||||
|
setClause = "enabled = false"
|
||||||
|
case "unmute":
|
||||||
|
setTable = "checks_notify"
|
||||||
|
setClause = "enabled = true"
|
||||||
|
case "enable":
|
||||||
|
setClause = "enabled = true, updated = now()"
|
||||||
|
case "disable":
|
||||||
|
setClause = "enabled = false, updated = now()"
|
||||||
|
case "delete_check":
|
||||||
|
if _, err := DB.Exec(`delete from checks where id = any ($1::bigint[])`, pq.Array(checks)); err != nil {
|
||||||
|
log.Printf("could not delete checks '%s': %s", checks, err)
|
||||||
|
con.Error = "could not delete checks"
|
||||||
|
returnError(http.StatusInternalServerError, con, con.w)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
con.w.WriteHeader(http.StatusSeeOther)
|
||||||
|
return
|
||||||
|
case "create_check":
|
||||||
|
|
||||||
|
case "reschedule":
|
||||||
|
setClause = "next_time = now()"
|
||||||
|
if run_in != "" {
|
||||||
|
runNum, err := strconv.Atoi(run_in)
|
||||||
|
if err != nil {
|
||||||
|
con.Error = "run_in is not a valid number"
|
||||||
|
returnError(http.StatusBadRequest, con, con.w)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
setClause = fmt.Sprintf("next_time = now() + '%dmin'::interval", runNum)
|
||||||
|
}
|
||||||
|
setTable = "active_checks"
|
||||||
|
case "deack":
|
||||||
|
setClause = "acknowledged = false"
|
||||||
|
setTable = "active_checks"
|
||||||
|
case "ack":
|
||||||
|
setClause = "acknowledged = true"
|
||||||
|
setTable = "active_checks"
|
||||||
|
|
||||||
|
hostname, err := os.Hostname()
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("could not resolve hostname: %s", err)
|
||||||
|
con.Error = "could not resolve hostname"
|
||||||
|
returnError(http.StatusInternalServerError, con, con.w)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if _, err := DB.Exec(`insert into notifications(check_id, states, output, mapping_id, notifier_id, check_host)
|
||||||
|
select ac.check_id, 0 || states[1:4], 'check acknowledged', ac.mapping_id,
|
||||||
|
cn.notifier_id, $2
|
||||||
|
from checks_notify cn
|
||||||
|
join active_checks ac on cn.check_id = ac.check_id
|
||||||
|
where cn.check_id = any ($1::bigint[])`, pq.Array(&checks), &hostname); err != nil {
|
||||||
|
log.Printf("could not acknowledge check: %s", err)
|
||||||
|
con.Error = "could not acknowledge check"
|
||||||
|
returnError(http.StatusInternalServerError, con, con.w)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
case "comment":
|
||||||
|
if comment == "" {
|
||||||
|
con.w.WriteHeader(http.StatusSeeOther)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
_, err := DB.Exec(
|
||||||
|
"update active_checks set notice = $2 where check_id = any ($1::bigint[]);",
|
||||||
|
pq.Array(&checks),
|
||||||
|
comment)
|
||||||
|
if err != nil {
|
||||||
|
con.w.WriteHeader(http.StatusInternalServerError)
|
||||||
|
fmt.Fprintf(con.w, "could not store changes")
|
||||||
|
log.Printf("could not adjust checks %#v: %s", checks, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
con.w.WriteHeader(http.StatusSeeOther)
|
||||||
|
return
|
||||||
|
case "uncomment":
|
||||||
|
_, err := DB.Exec(`update active_checks set notice = null where check_id = any($1::bigint[]);`,
|
||||||
|
pq.Array(&checks))
|
||||||
|
if err != nil {
|
||||||
|
con.Error = "could not uncomment checks"
|
||||||
|
returnError(http.StatusInternalServerError, con, con.w)
|
||||||
|
log.Printf("could not uncomment checks: %s", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
con.w.WriteHeader(http.StatusSeeOther)
|
||||||
|
return
|
||||||
|
default:
|
||||||
|
con.Error = fmt.Sprintf("requested action '%s' does not exist", action[0])
|
||||||
|
returnError(http.StatusNotFound, con, con.w)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
whereColumn := "id"
|
||||||
|
if setTable == "active_checks" || setTable == "checks_notify" {
|
||||||
|
whereColumn = "check_id"
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err := DB.Exec("update "+setTable+" set "+setClause+" where "+whereColumn+" = any ($1::bigint[]);", pq.Array(&checks))
|
||||||
|
if err != nil {
|
||||||
|
con.w.WriteHeader(http.StatusInternalServerError)
|
||||||
|
fmt.Fprintf(con.w, "could not store changes")
|
||||||
|
log.Printf("could not adjust checks %#v: %s", checks, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
con.w.WriteHeader(http.StatusSeeOther)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func returnError(status int, con interface{}, w http.ResponseWriter) {
|
||||||
|
w.Header()["Content-Type"] = []string{"text/html"}
|
||||||
|
w.WriteHeader(status)
|
||||||
|
if err := Tmpl.ExecuteTemplate(w, "error", con); err != nil {
|
||||||
|
w.WriteHeader(http.StatusInternalServerError)
|
||||||
|
w.Write([]byte("problem with a template"))
|
||||||
|
log.Printf("could not execute template: %s", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Context) loadCommands() error {
|
||||||
|
c.Commands = map[string]int{}
|
||||||
|
rows, err := DB.Query(`select id, name from commands order by name`)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
for rows.Next() {
|
||||||
|
if rows.Err() != nil {
|
||||||
|
return rows.Err()
|
||||||
|
}
|
||||||
|
var (
|
||||||
|
id int
|
||||||
|
name string
|
||||||
|
)
|
||||||
|
if err := rows.Scan(&id, &name); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
c.Commands[name] = id
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Context) loadMappings() error {
|
||||||
|
c.Mappings = map[int]map[int]MapEntry{}
|
||||||
|
rows, err := DB.Query(SQLShowMappings)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
for rows.Next() {
|
||||||
|
if rows.Err() != nil {
|
||||||
|
return rows.Err()
|
||||||
|
}
|
||||||
|
var (
|
||||||
|
mapId int
|
||||||
|
name string
|
||||||
|
target int
|
||||||
|
title string
|
||||||
|
color string
|
||||||
|
)
|
||||||
|
if err := rows.Scan(&mapId, &name, &target, &title, &color); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
ma, found := c.Mappings[mapId]
|
||||||
|
if !found {
|
||||||
|
ma = map[int]MapEntry{}
|
||||||
|
c.Mappings[mapId] = ma
|
||||||
|
}
|
||||||
|
ma[target] = MapEntry{Title: title, Color: color, Name: name}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func showStatic(w http.ResponseWriter, r *http.Request) {
|
||||||
|
file := strings.TrimPrefix(r.URL.Path, "/static/")
|
||||||
|
raw, found := Static[file]
|
||||||
|
if !found {
|
||||||
|
w.WriteHeader(http.StatusNotFound)
|
||||||
|
w.Write([]byte("file does not exist"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
w.Header()["Content-Type"] = []string{"image/svg+xml"}
|
||||||
|
w.WriteHeader(http.StatusOK)
|
||||||
|
w.Write([]byte(raw))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
SQLShowMappings = `select mapping_id, name, target, title, color
|
||||||
|
from mappings m join mapping_level ml on m.id = ml.mapping_id`
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
Templates = map[string]string{}
|
||||||
|
Static = map[string]string{
|
||||||
|
"icon-mute": `<?xml version="1.0" encoding="UTF-8"?><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 35.3 35.3" version="1.1"><title>Check is muted</title><style>.s0{fill:#191919;}</style><g transform="translate(0,-261.72223)"><path d="m17.6 261.7v35.3L5.3 284.7H0v-10.6l5.3 0zM30.2 273.1l-3.7 3.7-3.7-3.7-2.5 2.5 3.7 3.7-3.7 3.7 2.5 2.5 3.7-3.7 3.7 3.7 2.5-2.5-3.7-3.7 3.7-3.7z" fill="#191919"/></g></svg>`,
|
||||||
|
"icon-notice": `<?xml version="1.0" encoding="UTF-8"?><svg xmlns="http://www.w3.org/2000/svg" width="36" height="36"><path d="M2.572.19h30.857c1.319 0 2.38 1.356 2.38 3.041v19.98c0 1.685-1.061 3.04-2.38 3.04H15.941L4 35.81v-9.56H2.572C1.252 26.252.19 24.897.19 23.212V3.232C.19 1.545 1.252.19 2.57.19z" stroke="#000" stroke-width=".38" stroke-linejoin="round"/></svg>`,
|
||||||
|
"error": `{{ template "header" . }}{{ template "footer" . }}`,
|
||||||
|
}
|
||||||
|
TmplUnhandledGroups = `TODO`
|
||||||
|
Funcs = template.FuncMap{
|
||||||
|
"int": func(in int64) int { return int(in) },
|
||||||
|
"sub": func(base, amount int) int { return base - amount },
|
||||||
|
"in": func(t time.Time) time.Duration { return t.Sub(time.Now()).Round(1 * time.Second) },
|
||||||
|
"since": func(t time.Time) time.Duration { return time.Now().Sub(t).Round(1 * time.Second) },
|
||||||
|
"now": func() time.Time { return time.Now() },
|
||||||
|
"join": func(args []string, c string) string { return strings.Join(args, c) },
|
||||||
|
"mapString": func(mapId, target int) string { return fmt.Sprintf("%d-%d", mapId, target) },
|
||||||
|
"itoa": func(i int) string { return strconv.Itoa(i) },
|
||||||
|
}
|
||||||
|
)
|
99
cmd/monfront/pw.go
Normal file
99
cmd/monfront/pw.go
Normal file
@ -0,0 +1,99 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"crypto/rand"
|
||||||
|
"encoding/base64"
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"golang.org/x/crypto/scrypt"
|
||||||
|
)
|
||||||
|
|
||||||
|
type (
|
||||||
|
pwHash struct {
|
||||||
|
salt []byte
|
||||||
|
hash []byte
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
// Create a new password hash.
|
||||||
|
func newHash(pw string) (*pwHash, error) {
|
||||||
|
hash := pwHash{}
|
||||||
|
if err := hash.genSalt(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
h, err := hash.Hash(pw)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
hash.hash = h
|
||||||
|
return &hash, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// generate a hash for the given salt and password
|
||||||
|
func (p *pwHash) Hash(pw string) ([]byte, error) {
|
||||||
|
if len(p.salt) == 0 {
|
||||||
|
return []byte{}, fmt.Errorf("salt not initialized")
|
||||||
|
}
|
||||||
|
// constants taken from https://godoc.org/golang.org/x/crypto/scrypt
|
||||||
|
hash, err := scrypt.Key([]byte(pw), p.salt, 32768, 8, 1, 32)
|
||||||
|
if err != nil {
|
||||||
|
return []byte{}, fmt.Errorf("could not compute hash: %s", err)
|
||||||
|
}
|
||||||
|
return hash, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// genSalt generates 8 bytes of salt.
|
||||||
|
func (p *pwHash) genSalt() error {
|
||||||
|
salt := make([]byte, 8)
|
||||||
|
_, err := rand.Read(salt)
|
||||||
|
p.salt = salt
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// compare a hash to a password and return true, when it matches.
|
||||||
|
func (p *pwHash) compare(pw string) (bool, error) {
|
||||||
|
hash, err := p.Hash(pw)
|
||||||
|
if err != nil {
|
||||||
|
return false, fmt.Errorf("could not check password")
|
||||||
|
}
|
||||||
|
if bytes.Compare(p.hash, hash) == 0 {
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Encode a hash and salt to a string.
|
||||||
|
func (p *pwHash) String() string {
|
||||||
|
return fmt.Sprintf(
|
||||||
|
"1$%s$%s",
|
||||||
|
base64.StdEncoding.EncodeToString(p.salt),
|
||||||
|
base64.StdEncoding.EncodeToString(p.hash),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse a hash from a file or anywhere.
|
||||||
|
func (p *pwHash) Parse(raw string) error {
|
||||||
|
if len(raw) == 0 {
|
||||||
|
return fmt.Errorf("no hash found")
|
||||||
|
}
|
||||||
|
parts := strings.Split(raw, "$")
|
||||||
|
if len(parts) != 3 {
|
||||||
|
return fmt.Errorf("format error")
|
||||||
|
}
|
||||||
|
if parts[0] != "1" {
|
||||||
|
return fmt.Errorf("unknown hash version")
|
||||||
|
}
|
||||||
|
salt, err := base64.StdEncoding.DecodeString(parts[1])
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("could not parse salt: %s", err)
|
||||||
|
}
|
||||||
|
hash, err := base64.StdEncoding.DecodeString(parts[2])
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("could not parse salt: %s", err)
|
||||||
|
}
|
||||||
|
p.salt = salt
|
||||||
|
p.hash = hash
|
||||||
|
return nil
|
||||||
|
}
|
153
cmd/monfront/server.go
Normal file
153
cmd/monfront/server.go
Normal file
@ -0,0 +1,153 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"compress/gzip"
|
||||||
|
"database/sql"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"html/template"
|
||||||
|
"io"
|
||||||
|
"log"
|
||||||
|
"net"
|
||||||
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type (
|
||||||
|
server struct {
|
||||||
|
listen net.Listener
|
||||||
|
db *sql.DB
|
||||||
|
h *http.ServeMux
|
||||||
|
tmpl *template.Template
|
||||||
|
auth func(c *Context) error // authentication
|
||||||
|
autho func(c *Context) error // authorization
|
||||||
|
}
|
||||||
|
|
||||||
|
handleFunc func(c *Context)
|
||||||
|
|
||||||
|
Context struct {
|
||||||
|
// internal maintenance stuff
|
||||||
|
w http.ResponseWriter
|
||||||
|
r *http.Request
|
||||||
|
tmpl *template.Template
|
||||||
|
db *sql.DB
|
||||||
|
|
||||||
|
User string `json:"-"`
|
||||||
|
Filter *filter `json:"-"`
|
||||||
|
CanEdit bool `json:"-"` // has user permission to edit stuff?
|
||||||
|
|
||||||
|
Title string `json:"title,omitempty"`
|
||||||
|
CurrentPath string `json:"-"`
|
||||||
|
Error string `json:"error,omitempty"`
|
||||||
|
Mappings map[int]map[int]MapEntry `json:"mappings,omitempty"`
|
||||||
|
Commands map[string]int `json:"commands,omitempty"`
|
||||||
|
Checks []check `json:"checks,omitempty"`
|
||||||
|
CheckDetails *checkDetails `json:"check_details,omitempty"`
|
||||||
|
Groups []group `json:"groups,omitempty"`
|
||||||
|
Unhandled bool `json:"-"` // set this flag when unhandled was called
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
func newServer(l net.Listener, db *sql.DB, tmpl *template.Template, auth func(c *Context) error, autho func(c *Context) error) *server {
|
||||||
|
s := &server{
|
||||||
|
listen: l,
|
||||||
|
db: db,
|
||||||
|
tmpl: tmpl,
|
||||||
|
h: http.NewServeMux(),
|
||||||
|
auth: auth,
|
||||||
|
autho: autho,
|
||||||
|
}
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *server) ListenAndServe() error {
|
||||||
|
server := http.Server{Handler: s.h}
|
||||||
|
return server.Serve(s.listen)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *server) Handle(path string, fun handleFunc) {
|
||||||
|
s.h.HandleFunc(path, func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
c := &Context{
|
||||||
|
w: w,
|
||||||
|
r: r,
|
||||||
|
tmpl: s.tmpl,
|
||||||
|
db: s.db,
|
||||||
|
}
|
||||||
|
if err := s.auth(c); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err := s.autho(c); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
fun(c)
|
||||||
|
return
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *server) HandleStatic(path string, h func(w http.ResponseWriter, r *http.Request)) {
|
||||||
|
s.h.HandleFunc(path, h)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Render calls the template with the given name to
|
||||||
|
// render the appropiate content.
|
||||||
|
// In case of an error, a error message is automatically pushed
|
||||||
|
// to the client.
|
||||||
|
func (c *Context) Render(t string) error {
|
||||||
|
var w io.Writer = c.w
|
||||||
|
if strings.Contains(c.r.Header.Get("Accept-Encoding"), "gzip") {
|
||||||
|
gz, err := gzip.NewWriterLevel(w, 5)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("could not create gzip writer: %s", err)
|
||||||
|
return fmt.Errorf("could not create gzip writer: %s", err)
|
||||||
|
}
|
||||||
|
defer gz.Close()
|
||||||
|
w = gz
|
||||||
|
c.w.Header().Set("Content-Encoding", "gzip")
|
||||||
|
}
|
||||||
|
|
||||||
|
if c.r.Header.Get("Accept") == "application/json" {
|
||||||
|
c.w.Header().Set("Content-Type", "application/json")
|
||||||
|
enc := json.NewEncoder(w)
|
||||||
|
enc.SetIndent("", "") // disable indentation to save traffic
|
||||||
|
if err := enc.Encode(c); err != nil {
|
||||||
|
c.w.WriteHeader(http.StatusInternalServerError)
|
||||||
|
c.w.Write([]byte("could not write json output"))
|
||||||
|
log.Printf("could not write json output: %s", err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := c.tmpl.ExecuteTemplate(w, t, c); err != nil {
|
||||||
|
c.w.WriteHeader(http.StatusInternalServerError)
|
||||||
|
c.w.Write([]byte("problem with a template"))
|
||||||
|
log.Printf("could not execute template: %s", err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get a cookie value.
|
||||||
|
func (c *Context) GetCookieVal(name string) string {
|
||||||
|
cook, err := c.r.Cookie(name)
|
||||||
|
if err == http.ErrNoCookie {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return cook.Value
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set a new key value cookie with a deadline.
|
||||||
|
func (c *Context) SetCookie(name, val string, expire time.Time) {
|
||||||
|
cook := http.Cookie{
|
||||||
|
Name: name,
|
||||||
|
Value: val,
|
||||||
|
Expires: expire,
|
||||||
|
Secure: true,
|
||||||
|
SameSite: http.SameSiteStrictMode,
|
||||||
|
HttpOnly: true,
|
||||||
|
Path: "/",
|
||||||
|
}
|
||||||
|
http.SetCookie(c.w, &cook)
|
||||||
|
return
|
||||||
|
}
|
51
cmd/monfront/templates/check.html
Normal file
51
cmd/monfront/templates/check.html
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
{{ template "header" . }}
|
||||||
|
<section id="content">
|
||||||
|
{{ template "checkformheader" . }}
|
||||||
|
{{ $mapping := .Mappings }}
|
||||||
|
{{ with .CheckDetails }}
|
||||||
|
<input type="hidden" name="checks" value="{{ .Id }}" />
|
||||||
|
<article class="detail">
|
||||||
|
<h1>check for service {{ .Name }}</h1>
|
||||||
|
<div><span class="label">current state</span><span class="value state-{{ index .States 0 }}"></span></div>
|
||||||
|
<div><span class="label">current notice</span><span class="value">{{ if .Notice }}{{ .Notice.String }}{{ end }}</span></div>
|
||||||
|
<div><span class="label">Message</span><span class="value">{{ .Message }}</span></div>
|
||||||
|
<div><span class="label">enabled</span><span class="value">{{ .Enabled }}</span></div>
|
||||||
|
<div><span class="label">updated</span><span class="value">{{ .Updated.Format "2006.01.02 15:04:05" }}</span></div>
|
||||||
|
<div><span class="label">next check</span><span class="value">{{ .NextTime.Format "2006.01.02 15:04:05" }}</span></div>
|
||||||
|
<div><span class="label">last refresh</span><span class="value">{{ .LastRefresh.Format "2006.01.02 15:04:05" }}</span></div>
|
||||||
|
<div><span class="label">mapping</span><span class="value">{{ .MappingId }}</span></div>
|
||||||
|
</article>
|
||||||
|
<article class="detail">
|
||||||
|
<h1>node <a href="/checks?node_id={{ .NodeId }}">{{ .NodeName }}</a></h1>
|
||||||
|
<div><span class="label">Message</span><span class="value">{{ .NodeMessage }}</span></div>
|
||||||
|
</article>
|
||||||
|
<article class="detail">
|
||||||
|
<h1>command {{ .CommandName }}</h1>
|
||||||
|
<div><span class="label">Message</span><span class="value">{{ .CommandMessage }}</span></div>
|
||||||
|
<div><span class="label">command line</span><span class="value"><code>{{ join .CommandLine " " }}</code></span></div>
|
||||||
|
</article>
|
||||||
|
<article class="detail">
|
||||||
|
<h1>checker {{ .CheckerName }}</h1>
|
||||||
|
<div><span class="label">Description</span><span class="value">{{ .CheckerMsg }}</span></div>
|
||||||
|
</article>
|
||||||
|
<article>
|
||||||
|
<h1>notifications</h1>
|
||||||
|
<table>
|
||||||
|
<thead><tr><th>notifier</th><th>state</th><th>created</th><th>sent</th><th>output</th></thead>
|
||||||
|
<tbody>
|
||||||
|
{{ range .Notifications -}}
|
||||||
|
<tr>
|
||||||
|
<td>{{ .NotifierName }}</td>
|
||||||
|
<td class="state-{{ .MappingId }}-{{ .State }}">{{ (index $mapping .MappingId .State).Title }}</td>
|
||||||
|
<td>{{ .Inserted.Format "2006.01.02 15:04:05" }}</td>
|
||||||
|
<td>{{ if .Sent.Valid }}{{ .Sent.Time.Format "2006.01.02 15:04:05" }}{{ end }}</td>
|
||||||
|
<td>{{ .Output }}</td>
|
||||||
|
</tr>
|
||||||
|
{{ end -}}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</article>
|
||||||
|
{{ end }}
|
||||||
|
{{ template "checkformfooter" . }}
|
||||||
|
</section>
|
||||||
|
{{ template "footer" . }}
|
49
cmd/monfront/templates/checkfilter.html
Normal file
49
cmd/monfront/templates/checkfilter.html
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
<form action="/checks" method="get">
|
||||||
|
<aside id="edit">
|
||||||
|
<div class="option">
|
||||||
|
<select id="filter-state" name="filter-state" class="states">
|
||||||
|
<option value="">filter state</option>
|
||||||
|
{{ $FilterValsState := "" }}
|
||||||
|
{{ if .Filter.Vals.state }}{{$FilterValsState = .Filter.Vals.state }}{{ end }}
|
||||||
|
<option value="0" {{ if eq (itoa 0) $FilterValsState }}selected{{ end }}>>= OK</option>
|
||||||
|
<option value="1" {{ if eq (itoa 1) $FilterValsState }}selected{{ end }}>>= Warning</option>
|
||||||
|
<option value="2" {{ if eq (itoa 2) $FilterValsState }}selected{{ end }}>>= Error</option>
|
||||||
|
<option value="3" {{ if eq (itoa 3) $FilterValsState }}selected{{ end }}>>= Unknown</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div class="option">
|
||||||
|
<select id="filter-ack" name="filter-ack">
|
||||||
|
<option value="">filter acknowledged</option>
|
||||||
|
<option value="false" {{ if eq "false" (index .Filter.Vals "ack") }}selected{{ end }}>unacknowledged</option>
|
||||||
|
<option value="true" {{ if eq "true" (index .Filter.Vals "ack") }}selected{{ end }}>acknowledged</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div class="option">
|
||||||
|
<select id="filter-mapping" name="filter-mapping">
|
||||||
|
<option value="">filter mapping</option>
|
||||||
|
{{ $FilterValsMapping := "" }}
|
||||||
|
{{ if .Filter.Vals.mapping }}{{ $FilterValsMapping = .Filter.Vals.mapping }}{{ end }}
|
||||||
|
{{ range $mapId, $mapping := .Mappings -}}
|
||||||
|
<option value="{{ $mapId }}" {{ if eq (itoa $mapId) $FilterValsMapping }}selected{{ end }}>{{ (index $mapping 0).Name }}</option>
|
||||||
|
{{ end }}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div class="option">
|
||||||
|
<select id="filter-command" name="filter-command">
|
||||||
|
<option value="">filter command</option>
|
||||||
|
{{ $FilterValsCommands := "" }}
|
||||||
|
{{ if .Filter.Vals.command }}{{ $FilterValsCommands = .Filter.Vals.command }}{{ end }}
|
||||||
|
{{ range $command, $comId := .Commands -}}
|
||||||
|
<option value="{{ $comId }}" {{ if eq (itoa $comId) $FilterValsCommands }}selected{{ end }}>{{ $command }}</option>
|
||||||
|
{{ end }}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div class="option">
|
||||||
|
<input name="filter-search" placeholder="hostname" value="{{ .Filter.Vals.search }}" />
|
||||||
|
</div>
|
||||||
|
<div class="option">
|
||||||
|
<button name="filter" value="1">filter</button>
|
||||||
|
<button name="reset" value="1">reset</button>
|
||||||
|
</div>
|
||||||
|
</aside>
|
||||||
|
</form>
|
1
cmd/monfront/templates/checkformfooter.html
Normal file
1
cmd/monfront/templates/checkformfooter.html
Normal file
@ -0,0 +1 @@
|
|||||||
|
</form>
|
43
cmd/monfront/templates/checkformheader.html
Normal file
43
cmd/monfront/templates/checkformheader.html
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
{{ if .CanEdit }}
|
||||||
|
<form action="/action" method="post">
|
||||||
|
<aside id="edit">
|
||||||
|
<button class="default_button" name="action" value="comment"></button>
|
||||||
|
<div class="option input">
|
||||||
|
<input type="number" name="run_in" placeholder="run in" title="define the number of minutes after which the check should be run again" />
|
||||||
|
<button name="action" value="reschedule">run now</button>
|
||||||
|
</div>
|
||||||
|
<div class="option">
|
||||||
|
<button name="action" value="deack">deack</button>
|
||||||
|
<button name="action" value="ack">ack</button>
|
||||||
|
</div>
|
||||||
|
<div class="option">
|
||||||
|
<button name="action" value="enable">enable</button>
|
||||||
|
<button name="action" value="disable">disable</button>
|
||||||
|
</div>
|
||||||
|
<div class="option">
|
||||||
|
<button name="action" value="mute">mute</button>
|
||||||
|
<button name="action" value="unmute">unmute</button>
|
||||||
|
</div>
|
||||||
|
<div class="option input">
|
||||||
|
<input size="" name="comment" placeholder="enter comment ..." />
|
||||||
|
</div>
|
||||||
|
<div class="option">
|
||||||
|
<button name="action" value="comment">comment</button>
|
||||||
|
<button name="action" value="uncomment">uncomment</button>
|
||||||
|
</div>
|
||||||
|
<div class="option">
|
||||||
|
<button type="button" name="create_check">create</button>
|
||||||
|
<button name="action" value="delete_check">delete</button>
|
||||||
|
</div>
|
||||||
|
</aside>
|
||||||
|
<aside id="create_check" class="hidden">
|
||||||
|
<div class="option">
|
||||||
|
<label for="host">host</label>
|
||||||
|
<input name="host" placeholder="hostname" />
|
||||||
|
</div>
|
||||||
|
<div class="option">
|
||||||
|
<label for="command">command</label>
|
||||||
|
<input name="command" placeholder="command" />
|
||||||
|
</div>
|
||||||
|
</aside>
|
||||||
|
{{ end }}
|
29
cmd/monfront/templates/checklist.html
Normal file
29
cmd/monfront/templates/checklist.html
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
{{ template "header" . }}
|
||||||
|
<section id="content">
|
||||||
|
{{ template "checkfilter" . }}
|
||||||
|
{{ template "checkformheader" . }}
|
||||||
|
<table>
|
||||||
|
<thead><tr><th><input type="checkbox" title="select all" /></th><th>host</th><th>service</th><th>status</th><th title="shows how long the check is already in that state">for</th><th>next check in</th><th>message</th></tr></thead>
|
||||||
|
<tbody>
|
||||||
|
{{ $current := "" }}
|
||||||
|
{{ $mapping := .Mappings }}
|
||||||
|
{{ range .Checks }}
|
||||||
|
<tr>
|
||||||
|
<td><input type="checkbox" name="checks" value="{{ .CheckID }}" /></td>
|
||||||
|
<td>{{ if ne $current .NodeName }}{{ $current = .NodeName }}<a href="/checks?node_id={{ .NodeId }}">{{ .NodeName }}</a>{{ end }}</td>
|
||||||
|
<td>{{ .CheckName }}</td>
|
||||||
|
<td class="state-{{ .State }}">
|
||||||
|
{{- if ne .Notify true }}<span class="icon mute"></span>{{ end -}}
|
||||||
|
{{- if .Notice.Valid }}<span class="icon notice" title="{{ .Notice.String }}"></span>{{ end -}}
|
||||||
|
<a href="/check?check_id={{ .CheckID }}">{{ .CommandName }}</a>
|
||||||
|
</td>
|
||||||
|
<td>{{ since .StateSince }}</td>
|
||||||
|
<td>{{ in .NextTime }}</td>
|
||||||
|
<td><code>{{ .Msg }}</code></td>
|
||||||
|
</tr>
|
||||||
|
{{ end }}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
{{ template "checkformfooter" . }}
|
||||||
|
</section>
|
||||||
|
{{ template "footer" . }}
|
1
cmd/monfront/templates/error.html
Normal file
1
cmd/monfront/templates/error.html
Normal file
@ -0,0 +1 @@
|
|||||||
|
{{ .Error }}
|
76
cmd/monfront/templates/footer.html
Normal file
76
cmd/monfront/templates/footer.html
Normal file
@ -0,0 +1,76 @@
|
|||||||
|
<script type="text/javascript">
|
||||||
|
function row_head_click_event(event) {
|
||||||
|
check = false;
|
||||||
|
current = event.target;
|
||||||
|
while (current != null) {
|
||||||
|
if (current.nodeName == 'TABLE') {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (current.nodeName == 'TR') {
|
||||||
|
check = !current.children[0].children[0].checked;
|
||||||
|
current.children[0].children[0].checked = check;
|
||||||
|
}
|
||||||
|
current = current.parentNode;
|
||||||
|
}
|
||||||
|
lines = current.children[1].children
|
||||||
|
for (i = 0; i < lines.length; i++) {
|
||||||
|
select_row(event, lines[i], lines[i].children[0].children[0], check);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function row_click_event(event) {
|
||||||
|
if (event.target.nodeName == 'INPUT') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
current = event.target;
|
||||||
|
while (current = current.parentNode) {
|
||||||
|
if (current.nodeName == 'BODY') {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (current.nodeName != 'TR') {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
e = current.children[0].children[0];
|
||||||
|
check = !e.checked;
|
||||||
|
select_row(event, current, e, check);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function select_row(event, row, input, check) {
|
||||||
|
if (input != event.target) {
|
||||||
|
input.checked = check;
|
||||||
|
}
|
||||||
|
if (input.checked) {
|
||||||
|
row.classList.add("selected");
|
||||||
|
} else {
|
||||||
|
row.classList.remove("selected");
|
||||||
|
}
|
||||||
|
input.focus();
|
||||||
|
}
|
||||||
|
|
||||||
|
for (selector of ['thead > tr', 'thead input']) {
|
||||||
|
els = document.querySelectorAll(selector);
|
||||||
|
for (i = 0; i < els.length; i++) {
|
||||||
|
els[i].addEventListener('click', {handleEvent: row_head_click_event});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (selector of ['tbody > tr', 'tbody input']) {
|
||||||
|
els = document.querySelectorAll(selector);
|
||||||
|
for (i = 0; i < els.length; i++) {
|
||||||
|
els[i].addEventListener('click', {handleEvent: row_click_event});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
butt = document.querySelectorAll('button[type=button][name=create_check]');
|
||||||
|
for (i = 0; i < butt.length; i++) {
|
||||||
|
butt[i].addEventListener('click', {handleEvent: function(event){
|
||||||
|
cur = document.querySelector('#create_check').style.display;
|
||||||
|
console.log("meh: " + cur);
|
||||||
|
if (cur == 'block') {
|
||||||
|
document.querySelector('#create_check').style.display = 'none';
|
||||||
|
} else {
|
||||||
|
document.querySelector('#create_check').style.display = 'block';
|
||||||
|
}
|
||||||
|
}});
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
21
cmd/monfront/templates/grouplist.html
Normal file
21
cmd/monfront/templates/grouplist.html
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
{{ template "header" . }}
|
||||||
|
{{ template "checkformheader" . }}
|
||||||
|
<content>
|
||||||
|
<table>
|
||||||
|
<thead><tr><th></th><th>group</th><th>host</th><th>worst state</th></tr></thead>
|
||||||
|
<tbody>
|
||||||
|
{{ $current := "" }}
|
||||||
|
{{ $mapping := .Mappings }}
|
||||||
|
{{ range .Groups }}
|
||||||
|
<tr>
|
||||||
|
<td><input type="checkbox" name="nodes" value="{{ .NodeId }}" /></td>
|
||||||
|
<td>{{ if ne $current .Name }}{{ $current = .Name }}<a href="{{ if $.Unhandled }}/unhandled{{ end }}/checks?group_id={{ .GroupId }}">{{ .Name }}</a>{{ end }}</td>
|
||||||
|
<td><a href="/checks?node_id={{ .NodeId }}">{{ .NodeName }}</a></td>
|
||||||
|
<td class="state-{{ .MappingId }}-{{ .State }}">{{ (index $mapping .MappingId .State).Title }}</td>
|
||||||
|
</tr>
|
||||||
|
{{ end }}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</content>
|
||||||
|
{{ template "checkformfooter" . }}
|
||||||
|
{{ template "footer" . }}
|
101
cmd/monfront/templates/header.html
Normal file
101
cmd/monfront/templates/header.html
Normal file
@ -0,0 +1,101 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>{{ .Title }}</title>
|
||||||
|
<meta name="referrer" content="same-origin">
|
||||||
|
<link rel="shortcut icon" href="/static/favicon" />
|
||||||
|
<style type="text/css">
|
||||||
|
:root {
|
||||||
|
--main-bg-color: #3a4149;
|
||||||
|
--dark-bg-color: #2f353a;
|
||||||
|
--light-bg-color: #626971;
|
||||||
|
--main-fg-color: #eeeeee;
|
||||||
|
|
||||||
|
--bg-okay: hsla(125, 50%, 40%, 1);
|
||||||
|
--bg-warn: hsla(40, 100%, 50%, 1);
|
||||||
|
--bg-crit: hsla(0, 75%, 50%, 1);
|
||||||
|
--bg-unkn: gray;
|
||||||
|
}
|
||||||
|
* { font-size: 100%; }
|
||||||
|
body { background: var(--dark-bg-color); padding: 0; margin: 0; color: var(--main-fg-color); }
|
||||||
|
section#content { padding: 1em; background: var(--main-bg-color); border: 1px solid black; margin: 0.5em; }
|
||||||
|
#mainmenu, aside { background: var(--main-bg-color); border-bottom: 1px solid black; }
|
||||||
|
#mainmenu ul, aside {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
align-items: center;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
align-content: center; }
|
||||||
|
aside { padding: 0.5em; border: 1px solid black; border-bottom: none; }
|
||||||
|
#mainmenu li { list-style-type: none; }
|
||||||
|
#mainmenu a, #mainmenu a:visited, #mainmenu a:active, #mainmenu a:hover, #mainmenu span {
|
||||||
|
text-decoration: none;
|
||||||
|
color: #e4e7ea;
|
||||||
|
padding: 0.5em 0.75em;
|
||||||
|
display: block; }
|
||||||
|
aside .option { display: grid; grid-template-columns: auto auto; margin: 0em 0.25em; }
|
||||||
|
input[type="number"] { width: 4em; }
|
||||||
|
button, select { background: var(--dark-bg-color); color: var(--main-fg-color); border: 1px solid black; padding: 0.25em 0.5em; }
|
||||||
|
a { color: var(--main-fg-color); }
|
||||||
|
form article { border: 1px solid black; border-bottom: none; padding: 0.5em; }
|
||||||
|
table { border-collapse: collapse; border-spacing: 0; width: 100%; }
|
||||||
|
table tr:nth-child(odd) { background: rgba(0, 0, 0, 0.15); }
|
||||||
|
table tr:nth-child(even) { background: var(--main-bg-color); }
|
||||||
|
table tr.selected:nth-child(odd) { background: var(--light-bg-color); }
|
||||||
|
table tr.selected:nth-child(even) { background: rgba(255, 255, 255, 0.45); }
|
||||||
|
table tr:hover, table tr:hover a { background: #dfdfdf; color: black; }
|
||||||
|
table th { background: var(--main-bg-color); color: var(--main-fg-color); font-weigth: 700; }
|
||||||
|
table td, table th { text-align: center; border: 1px solid black; padding: 0.35em 0.15em; }
|
||||||
|
table code { font-size: 75%; }
|
||||||
|
table td.disabled { text-decoration: line-through; }
|
||||||
|
.icon {
|
||||||
|
display: inline-block;
|
||||||
|
height: 1em;
|
||||||
|
margin: 0;
|
||||||
|
width: 1em;
|
||||||
|
vertical-align: bottom;
|
||||||
|
margin-right: 0.5em;
|
||||||
|
background-size: contain;
|
||||||
|
}
|
||||||
|
.hidden { display: none; }
|
||||||
|
.default_button { margin: 0; padding: 0; border: 0; height: 0; width: 0; }
|
||||||
|
.mute { background-image: url(/static/icon-mute); }
|
||||||
|
.notice { background-image: url(/static/icon-notice); }
|
||||||
|
.detail > div { display: grid; grid-template-columns: 25% auto; }
|
||||||
|
.detail > div:hover { background: #dfdfdf; color: black; }
|
||||||
|
.error { padding: 0.5em; background: #ffc6c6; border: 1px solid red; }
|
||||||
|
select.states option[value="0"], .state-0 { background-color: var(--bg-okay); }
|
||||||
|
select.states option[value="1"], .state-1 { background-color: var(--bg-warn); }
|
||||||
|
select.states option[value="2"], .state-2 { background-color: var(--bg-crit); }
|
||||||
|
select.states option[value="3"], .state-3 { background-color: var(--bg-unkn); }
|
||||||
|
.state-0:after { content: 'okay' }
|
||||||
|
.state-1:after { content: 'warning' }
|
||||||
|
.state-2:after { content: 'critical' }
|
||||||
|
.state-3:after { content: 'unknown' }
|
||||||
|
/* state background colors */
|
||||||
|
{{ range $mapId, $mapping := .Mappings -}}
|
||||||
|
{{ range $target, $val := $mapping -}}
|
||||||
|
.state-{{ $mapId }}-{{ $target }} { background: {{ $val.Color }}; color: black; }
|
||||||
|
{{ end -}}
|
||||||
|
{{ end -}}
|
||||||
|
</style>
|
||||||
|
<script>
|
||||||
|
setTimeout(function() { if (document.activeElement.tagName == "BODY") { location.reload(true) } }, 30000)
|
||||||
|
</script>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<nav id="mainmenu">
|
||||||
|
<ul>
|
||||||
|
<li><span>{{ now.Format "2006.01.02" }}</span></li>
|
||||||
|
<li><span>{{ now.Format "15:04:05" }}</span></li>
|
||||||
|
<li><a href="/">home</a></li>
|
||||||
|
<li><a href="/checks?filter-state=1&filter-ack=false">checks</a></li>
|
||||||
|
<li><a href="/groups">groups</a></li>
|
||||||
|
<li class="submenu">
|
||||||
|
<form action="/checks" method="get">
|
||||||
|
</form>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</nav>
|
||||||
|
{{ if .Error }}<div class="error">{{ .Error }}</div>{{ end }}
|
13
go.mod
13
go.mod
@ -1,3 +1,14 @@
|
|||||||
module git.zero-knowledge.org/gibheer/monzero
|
module git.zero-knowledge.org/gibheer/monzero
|
||||||
|
|
||||||
require github.com/lib/pq v1.0.0
|
go 1.17
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/BurntSushi/toml v0.4.1
|
||||||
|
github.com/lib/pq v1.10.4
|
||||||
|
golang.org/x/crypto v0.0.0-20211117183948-ae814b36b871
|
||||||
|
)
|
||||||
|
|
||||||
|
require (
|
||||||
|
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1 // indirect
|
||||||
|
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1 // indirect
|
||||||
|
)
|
||||||
|
11
go.sum
11
go.sum
@ -1,2 +1,13 @@
|
|||||||
|
github.com/BurntSushi/toml v0.4.1 h1:GaI7EiDXDRfa8VshkTj7Fym7ha+y8/XxIgD2okUIjLw=
|
||||||
|
github.com/BurntSushi/toml v0.4.1/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
|
||||||
github.com/lib/pq v1.0.0 h1:X5PMW56eZitiTeO7tKzZxFCSpbFZJtkMMooicw2us9A=
|
github.com/lib/pq v1.0.0 h1:X5PMW56eZitiTeO7tKzZxFCSpbFZJtkMMooicw2us9A=
|
||||||
github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
|
github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
|
||||||
|
github.com/lib/pq v1.10.4 h1:SO9z7FRPzA03QhHKJrH5BXA6HU1rS4V2nIVrrNC1iYk=
|
||||||
|
github.com/lib/pq v1.10.4/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
|
||||||
|
golang.org/x/crypto v0.0.0-20211117183948-ae814b36b871 h1:/pEO3GD/ABYAjuakUS6xSEmmlyVS4kxBNkeA9tLJiTI=
|
||||||
|
golang.org/x/crypto v0.0.0-20211117183948-ae814b36b871/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||||
|
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1 h1:SrN+KX8Art/Sf4HNj6Zcz06G7VEz+7w9tdXTPOZ7+l4=
|
||||||
|
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1 h1:v+OssWQX+hTHEmOBgwxdZxK4zHq3yOs8F9J7mk0PY8E=
|
||||||
|
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||||
|
66
monfront.conf.example
Normal file
66
monfront.conf.example
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
# db contains the database credentials.
|
||||||
|
# For available options, see https://godoc.org/github.com/lib/pq
|
||||||
|
db = "user=username password=password sslmode=disable dbname=monzero"
|
||||||
|
|
||||||
|
# Listen sets the IP and port to start listening for incoming connections.
|
||||||
|
listen = "127.0.0.1:8080"
|
||||||
|
|
||||||
|
# Change the template path to a different directory.
|
||||||
|
#template_path = "templates"
|
||||||
|
|
||||||
|
[ssl]
|
||||||
|
# Enable SSL support to start listening for incoming connections.
|
||||||
|
# This is required for some authentication modes.
|
||||||
|
enable = false
|
||||||
|
# Configure the path to the private key of the certificate.
|
||||||
|
#private_key = "monfront.priv"
|
||||||
|
# Set the path to the certificate.
|
||||||
|
#certificate = "monfront.crt"
|
||||||
|
|
||||||
|
[authentication]
|
||||||
|
# mode can be one of "none", "header", "list", "db", "cert"
|
||||||
|
# * none disables the authentication
|
||||||
|
# * header checks the header of the header parameter
|
||||||
|
# * list uses the list parameter to check usernames and passwords
|
||||||
|
# * db uses the db credentials to check usernames and passwords
|
||||||
|
# * cert uses a client certificate CA to check incoming users
|
||||||
|
#
|
||||||
|
# When setting a mode of list or db, SSL settings are required to protect
|
||||||
|
# the provided credentials.
|
||||||
|
mode = "none"
|
||||||
|
|
||||||
|
# Set a random string to generate session tokens. The token is placed in a
|
||||||
|
# cookie with secure flags, so that the session can't be misused.
|
||||||
|
#session_token = ""
|
||||||
|
|
||||||
|
# allow_anonymous allows users to view the frontend even when not
|
||||||
|
# authenticated.
|
||||||
|
#allow_anonymous = false
|
||||||
|
|
||||||
|
# When the mode is set to header, this header must contain the username the
|
||||||
|
# user was authenticated as. It will then be used to load the permissions.
|
||||||
|
# header = "X-AUTH-NAME"
|
||||||
|
|
||||||
|
# When using the mode list, this list is checked for matches of usernames
|
||||||
|
# and passwords.
|
||||||
|
#list = [
|
||||||
|
# ["user1", "passwordhash"],
|
||||||
|
# ["user2", "anotherpass"],
|
||||||
|
#]
|
||||||
|
|
||||||
|
# Set the path to the client certificate CA to check incoming client
|
||||||
|
# certificates.
|
||||||
|
#cert = "clientCA.crt"
|
||||||
|
|
||||||
|
[authorization]
|
||||||
|
# The mode decides who gets to change data in the frontend.
|
||||||
|
# It can be one of:
|
||||||
|
# * none - nobody can change data
|
||||||
|
# * list - usernames from a specific list can change data
|
||||||
|
# * db - users allowed to change data is loaded from the database
|
||||||
|
# * all - everyone can change all the data
|
||||||
|
mode = "all"
|
||||||
|
|
||||||
|
# The list defines the usernames allowed to change data in the frontend. They
|
||||||
|
# must be authenticated to get the permission.
|
||||||
|
#list = ["user1", "user2"]
|
1
schema/20190528.sql
Normal file
1
schema/20190528.sql
Normal file
@ -0,0 +1 @@
|
|||||||
|
alter table active_checks add state_since timestamp with time zone default now() not null;
|
14
schema/20190812.sql
Normal file
14
schema/20190812.sql
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
create table checkers(
|
||||||
|
id serial not null primary key,
|
||||||
|
name text not null unique,
|
||||||
|
description text
|
||||||
|
);
|
||||||
|
|
||||||
|
insert into checkers(name, description) values ('moncheck', 'moncheck provides a nagios compatible API to run checks. It calls binaries, which control the alarm state by their exit code.');
|
||||||
|
|
||||||
|
alter table checks add checker_id integer not null default 1
|
||||||
|
references checkers(id) on delete cascade;
|
||||||
|
alter table checks alter checker_id drop default;
|
||||||
|
alter table active_checks add checker_id integer not null default 1
|
||||||
|
references checkers(id) on delete cascade;
|
||||||
|
alter table active_checks alter checker_id drop default;
|
3
schema/20190910.sql
Normal file
3
schema/20190910.sql
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
-- add check instance name field
|
||||||
|
alter table checks add name text not null default 'none';
|
||||||
|
alter table checks alter name drop default;
|
2
vendor/github.com/BurntSushi/toml/.gitignore
generated
vendored
Normal file
2
vendor/github.com/BurntSushi/toml/.gitignore
generated
vendored
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
toml.test
|
||||||
|
/toml-test
|
1
vendor/github.com/BurntSushi/toml/COMPATIBLE
generated
vendored
Normal file
1
vendor/github.com/BurntSushi/toml/COMPATIBLE
generated
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
Compatible with TOML version [v1.0.0](https://toml.io/en/v1.0.0).
|
21
vendor/github.com/BurntSushi/toml/COPYING
generated
vendored
Normal file
21
vendor/github.com/BurntSushi/toml/COPYING
generated
vendored
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
The MIT License (MIT)
|
||||||
|
|
||||||
|
Copyright (c) 2013 TOML authors
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in
|
||||||
|
all copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
THE SOFTWARE.
|
220
vendor/github.com/BurntSushi/toml/README.md
generated
vendored
Normal file
220
vendor/github.com/BurntSushi/toml/README.md
generated
vendored
Normal file
@ -0,0 +1,220 @@
|
|||||||
|
## TOML parser and encoder for Go with reflection
|
||||||
|
|
||||||
|
TOML stands for Tom's Obvious, Minimal Language. This Go package provides a
|
||||||
|
reflection interface similar to Go's standard library `json` and `xml`
|
||||||
|
packages. This package also supports the `encoding.TextUnmarshaler` and
|
||||||
|
`encoding.TextMarshaler` interfaces so that you can define custom data
|
||||||
|
representations. (There is an example of this below.)
|
||||||
|
|
||||||
|
Compatible with TOML version [v1.0.0](https://toml.io/en/v1.0.0).
|
||||||
|
|
||||||
|
Documentation: https://godocs.io/github.com/BurntSushi/toml
|
||||||
|
|
||||||
|
See the [releases page](https://github.com/BurntSushi/toml/releases) for a
|
||||||
|
changelog; this information is also in the git tag annotations (e.g. `git show
|
||||||
|
v0.4.0`).
|
||||||
|
|
||||||
|
This library requires Go 1.13 or newer; install it with:
|
||||||
|
|
||||||
|
$ go get github.com/BurntSushi/toml
|
||||||
|
|
||||||
|
It also comes with a TOML validator CLI tool:
|
||||||
|
|
||||||
|
$ go get github.com/BurntSushi/toml/cmd/tomlv
|
||||||
|
$ tomlv some-toml-file.toml
|
||||||
|
|
||||||
|
### Testing
|
||||||
|
|
||||||
|
This package passes all tests in
|
||||||
|
[toml-test](https://github.com/BurntSushi/toml-test) for both the decoder
|
||||||
|
and the encoder.
|
||||||
|
|
||||||
|
### Examples
|
||||||
|
|
||||||
|
This package works similarly to how the Go standard library handles XML and
|
||||||
|
JSON. Namely, data is loaded into Go values via reflection.
|
||||||
|
|
||||||
|
For the simplest example, consider some TOML file as just a list of keys
|
||||||
|
and values:
|
||||||
|
|
||||||
|
```toml
|
||||||
|
Age = 25
|
||||||
|
Cats = [ "Cauchy", "Plato" ]
|
||||||
|
Pi = 3.14
|
||||||
|
Perfection = [ 6, 28, 496, 8128 ]
|
||||||
|
DOB = 1987-07-05T05:45:00Z
|
||||||
|
```
|
||||||
|
|
||||||
|
Which could be defined in Go as:
|
||||||
|
|
||||||
|
```go
|
||||||
|
type Config struct {
|
||||||
|
Age int
|
||||||
|
Cats []string
|
||||||
|
Pi float64
|
||||||
|
Perfection []int
|
||||||
|
DOB time.Time // requires `import time`
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
And then decoded with:
|
||||||
|
|
||||||
|
```go
|
||||||
|
var conf Config
|
||||||
|
if _, err := toml.Decode(tomlData, &conf); err != nil {
|
||||||
|
// handle error
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
You can also use struct tags if your struct field name doesn't map to a TOML
|
||||||
|
key value directly:
|
||||||
|
|
||||||
|
```toml
|
||||||
|
some_key_NAME = "wat"
|
||||||
|
```
|
||||||
|
|
||||||
|
```go
|
||||||
|
type TOML struct {
|
||||||
|
ObscureKey string `toml:"some_key_NAME"`
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Beware that like other most other decoders **only exported fields** are
|
||||||
|
considered when encoding and decoding; private fields are silently ignored.
|
||||||
|
|
||||||
|
### Using the `encoding.TextUnmarshaler` interface
|
||||||
|
|
||||||
|
Here's an example that automatically parses duration strings into
|
||||||
|
`time.Duration` values:
|
||||||
|
|
||||||
|
```toml
|
||||||
|
[[song]]
|
||||||
|
name = "Thunder Road"
|
||||||
|
duration = "4m49s"
|
||||||
|
|
||||||
|
[[song]]
|
||||||
|
name = "Stairway to Heaven"
|
||||||
|
duration = "8m03s"
|
||||||
|
```
|
||||||
|
|
||||||
|
Which can be decoded with:
|
||||||
|
|
||||||
|
```go
|
||||||
|
type song struct {
|
||||||
|
Name string
|
||||||
|
Duration duration
|
||||||
|
}
|
||||||
|
type songs struct {
|
||||||
|
Song []song
|
||||||
|
}
|
||||||
|
var favorites songs
|
||||||
|
if _, err := toml.Decode(blob, &favorites); err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, s := range favorites.Song {
|
||||||
|
fmt.Printf("%s (%s)\n", s.Name, s.Duration)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
And you'll also need a `duration` type that satisfies the
|
||||||
|
`encoding.TextUnmarshaler` interface:
|
||||||
|
|
||||||
|
```go
|
||||||
|
type duration struct {
|
||||||
|
time.Duration
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *duration) UnmarshalText(text []byte) error {
|
||||||
|
var err error
|
||||||
|
d.Duration, err = time.ParseDuration(string(text))
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
To target TOML specifically you can implement `UnmarshalTOML` TOML interface in
|
||||||
|
a similar way.
|
||||||
|
|
||||||
|
### More complex usage
|
||||||
|
|
||||||
|
Here's an example of how to load the example from the official spec page:
|
||||||
|
|
||||||
|
```toml
|
||||||
|
# This is a TOML document. Boom.
|
||||||
|
|
||||||
|
title = "TOML Example"
|
||||||
|
|
||||||
|
[owner]
|
||||||
|
name = "Tom Preston-Werner"
|
||||||
|
organization = "GitHub"
|
||||||
|
bio = "GitHub Cofounder & CEO\nLikes tater tots and beer."
|
||||||
|
dob = 1979-05-27T07:32:00Z # First class dates? Why not?
|
||||||
|
|
||||||
|
[database]
|
||||||
|
server = "192.168.1.1"
|
||||||
|
ports = [ 8001, 8001, 8002 ]
|
||||||
|
connection_max = 5000
|
||||||
|
enabled = true
|
||||||
|
|
||||||
|
[servers]
|
||||||
|
|
||||||
|
# You can indent as you please. Tabs or spaces. TOML don't care.
|
||||||
|
[servers.alpha]
|
||||||
|
ip = "10.0.0.1"
|
||||||
|
dc = "eqdc10"
|
||||||
|
|
||||||
|
[servers.beta]
|
||||||
|
ip = "10.0.0.2"
|
||||||
|
dc = "eqdc10"
|
||||||
|
|
||||||
|
[clients]
|
||||||
|
data = [ ["gamma", "delta"], [1, 2] ] # just an update to make sure parsers support it
|
||||||
|
|
||||||
|
# Line breaks are OK when inside arrays
|
||||||
|
hosts = [
|
||||||
|
"alpha",
|
||||||
|
"omega"
|
||||||
|
]
|
||||||
|
```
|
||||||
|
|
||||||
|
And the corresponding Go types are:
|
||||||
|
|
||||||
|
```go
|
||||||
|
type tomlConfig struct {
|
||||||
|
Title string
|
||||||
|
Owner ownerInfo
|
||||||
|
DB database `toml:"database"`
|
||||||
|
Servers map[string]server
|
||||||
|
Clients clients
|
||||||
|
}
|
||||||
|
|
||||||
|
type ownerInfo struct {
|
||||||
|
Name string
|
||||||
|
Org string `toml:"organization"`
|
||||||
|
Bio string
|
||||||
|
DOB time.Time
|
||||||
|
}
|
||||||
|
|
||||||
|
type database struct {
|
||||||
|
Server string
|
||||||
|
Ports []int
|
||||||
|
ConnMax int `toml:"connection_max"`
|
||||||
|
Enabled bool
|
||||||
|
}
|
||||||
|
|
||||||
|
type server struct {
|
||||||
|
IP string
|
||||||
|
DC string
|
||||||
|
}
|
||||||
|
|
||||||
|
type clients struct {
|
||||||
|
Data [][]interface{}
|
||||||
|
Hosts []string
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Note that a case insensitive match will be tried if an exact match can't be
|
||||||
|
found.
|
||||||
|
|
||||||
|
A working example of the above can be found in `_examples/example.{go,toml}`.
|
||||||
|
|
511
vendor/github.com/BurntSushi/toml/decode.go
generated
vendored
Normal file
511
vendor/github.com/BurntSushi/toml/decode.go
generated
vendored
Normal file
@ -0,0 +1,511 @@
|
|||||||
|
package toml
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
|
"math"
|
||||||
|
"os"
|
||||||
|
"reflect"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Unmarshaler is the interface implemented by objects that can unmarshal a
|
||||||
|
// TOML description of themselves.
|
||||||
|
type Unmarshaler interface {
|
||||||
|
UnmarshalTOML(interface{}) error
|
||||||
|
}
|
||||||
|
|
||||||
|
// Unmarshal decodes the contents of `p` in TOML format into a pointer `v`.
|
||||||
|
func Unmarshal(p []byte, v interface{}) error {
|
||||||
|
_, err := Decode(string(p), v)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Primitive is a TOML value that hasn't been decoded into a Go value.
|
||||||
|
//
|
||||||
|
// This type can be used for any value, which will cause decoding to be delayed.
|
||||||
|
// You can use the PrimitiveDecode() function to "manually" decode these values.
|
||||||
|
//
|
||||||
|
// NOTE: The underlying representation of a `Primitive` value is subject to
|
||||||
|
// change. Do not rely on it.
|
||||||
|
//
|
||||||
|
// NOTE: Primitive values are still parsed, so using them will only avoid the
|
||||||
|
// overhead of reflection. They can be useful when you don't know the exact type
|
||||||
|
// of TOML data until runtime.
|
||||||
|
type Primitive struct {
|
||||||
|
undecoded interface{}
|
||||||
|
context Key
|
||||||
|
}
|
||||||
|
|
||||||
|
// PrimitiveDecode is just like the other `Decode*` functions, except it
|
||||||
|
// decodes a TOML value that has already been parsed. Valid primitive values
|
||||||
|
// can *only* be obtained from values filled by the decoder functions,
|
||||||
|
// including this method. (i.e., `v` may contain more `Primitive`
|
||||||
|
// values.)
|
||||||
|
//
|
||||||
|
// Meta data for primitive values is included in the meta data returned by
|
||||||
|
// the `Decode*` functions with one exception: keys returned by the Undecoded
|
||||||
|
// method will only reflect keys that were decoded. Namely, any keys hidden
|
||||||
|
// behind a Primitive will be considered undecoded. Executing this method will
|
||||||
|
// update the undecoded keys in the meta data. (See the example.)
|
||||||
|
func (md *MetaData) PrimitiveDecode(primValue Primitive, v interface{}) error {
|
||||||
|
md.context = primValue.context
|
||||||
|
defer func() { md.context = nil }()
|
||||||
|
return md.unify(primValue.undecoded, rvalue(v))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decoder decodes TOML data.
|
||||||
|
//
|
||||||
|
// TOML tables correspond to Go structs or maps (dealer's choice – they can be
|
||||||
|
// used interchangeably).
|
||||||
|
//
|
||||||
|
// TOML table arrays correspond to either a slice of structs or a slice of maps.
|
||||||
|
//
|
||||||
|
// TOML datetimes correspond to Go time.Time values. Local datetimes are parsed
|
||||||
|
// in the local timezone.
|
||||||
|
//
|
||||||
|
// All other TOML types (float, string, int, bool and array) correspond to the
|
||||||
|
// obvious Go types.
|
||||||
|
//
|
||||||
|
// An exception to the above rules is if a type implements the TextUnmarshaler
|
||||||
|
// interface, in which case any primitive TOML value (floats, strings, integers,
|
||||||
|
// booleans, datetimes) will be converted to a []byte and given to the value's
|
||||||
|
// UnmarshalText method. See the Unmarshaler example for a demonstration with
|
||||||
|
// time duration strings.
|
||||||
|
//
|
||||||
|
// Key mapping
|
||||||
|
//
|
||||||
|
// TOML keys can map to either keys in a Go map or field names in a Go struct.
|
||||||
|
// The special `toml` struct tag can be used to map TOML keys to struct fields
|
||||||
|
// that don't match the key name exactly (see the example). A case insensitive
|
||||||
|
// match to struct names will be tried if an exact match can't be found.
|
||||||
|
//
|
||||||
|
// The mapping between TOML values and Go values is loose. That is, there may
|
||||||
|
// exist TOML values that cannot be placed into your representation, and there
|
||||||
|
// may be parts of your representation that do not correspond to TOML values.
|
||||||
|
// This loose mapping can be made stricter by using the IsDefined and/or
|
||||||
|
// Undecoded methods on the MetaData returned.
|
||||||
|
//
|
||||||
|
// This decoder does not handle cyclic types. Decode will not terminate if a
|
||||||
|
// cyclic type is passed.
|
||||||
|
type Decoder struct {
|
||||||
|
r io.Reader
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewDecoder creates a new Decoder.
|
||||||
|
func NewDecoder(r io.Reader) *Decoder {
|
||||||
|
return &Decoder{r: r}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decode TOML data in to the pointer `v`.
|
||||||
|
func (dec *Decoder) Decode(v interface{}) (MetaData, error) {
|
||||||
|
rv := reflect.ValueOf(v)
|
||||||
|
if rv.Kind() != reflect.Ptr {
|
||||||
|
return MetaData{}, e("Decode of non-pointer %s", reflect.TypeOf(v))
|
||||||
|
}
|
||||||
|
if rv.IsNil() {
|
||||||
|
return MetaData{}, e("Decode of nil %s", reflect.TypeOf(v))
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: have parser should read from io.Reader? Or at the very least, make
|
||||||
|
// it read from []byte rather than string
|
||||||
|
data, err := ioutil.ReadAll(dec.r)
|
||||||
|
if err != nil {
|
||||||
|
return MetaData{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
p, err := parse(string(data))
|
||||||
|
if err != nil {
|
||||||
|
return MetaData{}, err
|
||||||
|
}
|
||||||
|
md := MetaData{
|
||||||
|
p.mapping, p.types, p.ordered,
|
||||||
|
make(map[string]bool, len(p.ordered)), nil,
|
||||||
|
}
|
||||||
|
return md, md.unify(p.mapping, indirect(rv))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decode the TOML data in to the pointer v.
|
||||||
|
//
|
||||||
|
// See the documentation on Decoder for a description of the decoding process.
|
||||||
|
func Decode(data string, v interface{}) (MetaData, error) {
|
||||||
|
return NewDecoder(strings.NewReader(data)).Decode(v)
|
||||||
|
}
|
||||||
|
|
||||||
|
// DecodeFile is just like Decode, except it will automatically read the
|
||||||
|
// contents of the file at path and decode it for you.
|
||||||
|
func DecodeFile(path string, v interface{}) (MetaData, error) {
|
||||||
|
fp, err := os.Open(path)
|
||||||
|
if err != nil {
|
||||||
|
return MetaData{}, err
|
||||||
|
}
|
||||||
|
defer fp.Close()
|
||||||
|
return NewDecoder(fp).Decode(v)
|
||||||
|
}
|
||||||
|
|
||||||
|
// unify performs a sort of type unification based on the structure of `rv`,
|
||||||
|
// which is the client representation.
|
||||||
|
//
|
||||||
|
// Any type mismatch produces an error. Finding a type that we don't know
|
||||||
|
// how to handle produces an unsupported type error.
|
||||||
|
func (md *MetaData) unify(data interface{}, rv reflect.Value) error {
|
||||||
|
// Special case. Look for a `Primitive` value.
|
||||||
|
// TODO: #76 would make this superfluous after implemented.
|
||||||
|
if rv.Type() == reflect.TypeOf((*Primitive)(nil)).Elem() {
|
||||||
|
// Save the undecoded data and the key context into the primitive
|
||||||
|
// value.
|
||||||
|
context := make(Key, len(md.context))
|
||||||
|
copy(context, md.context)
|
||||||
|
rv.Set(reflect.ValueOf(Primitive{
|
||||||
|
undecoded: data,
|
||||||
|
context: context,
|
||||||
|
}))
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Special case. Unmarshaler Interface support.
|
||||||
|
if rv.CanAddr() {
|
||||||
|
if v, ok := rv.Addr().Interface().(Unmarshaler); ok {
|
||||||
|
return v.UnmarshalTOML(data)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Special case. Look for a value satisfying the TextUnmarshaler interface.
|
||||||
|
if v, ok := rv.Interface().(encoding.TextUnmarshaler); ok {
|
||||||
|
return md.unifyText(data, v)
|
||||||
|
}
|
||||||
|
// TODO:
|
||||||
|
// The behavior here is incorrect whenever a Go type satisfies the
|
||||||
|
// encoding.TextUnmarshaler interface but also corresponds to a TOML hash or
|
||||||
|
// array. In particular, the unmarshaler should only be applied to primitive
|
||||||
|
// TOML values. But at this point, it will be applied to all kinds of values
|
||||||
|
// and produce an incorrect error whenever those values are hashes or arrays
|
||||||
|
// (including arrays of tables).
|
||||||
|
|
||||||
|
k := rv.Kind()
|
||||||
|
|
||||||
|
// laziness
|
||||||
|
if k >= reflect.Int && k <= reflect.Uint64 {
|
||||||
|
return md.unifyInt(data, rv)
|
||||||
|
}
|
||||||
|
switch k {
|
||||||
|
case reflect.Ptr:
|
||||||
|
elem := reflect.New(rv.Type().Elem())
|
||||||
|
err := md.unify(data, reflect.Indirect(elem))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
rv.Set(elem)
|
||||||
|
return nil
|
||||||
|
case reflect.Struct:
|
||||||
|
return md.unifyStruct(data, rv)
|
||||||
|
case reflect.Map:
|
||||||
|
return md.unifyMap(data, rv)
|
||||||
|
case reflect.Array:
|
||||||
|
return md.unifyArray(data, rv)
|
||||||
|
case reflect.Slice:
|
||||||
|
return md.unifySlice(data, rv)
|
||||||
|
case reflect.String:
|
||||||
|
return md.unifyString(data, rv)
|
||||||
|
case reflect.Bool:
|
||||||
|
return md.unifyBool(data, rv)
|
||||||
|
case reflect.Interface:
|
||||||
|
// we only support empty interfaces.
|
||||||
|
if rv.NumMethod() > 0 {
|
||||||
|
return e("unsupported type %s", rv.Type())
|
||||||
|
}
|
||||||
|
return md.unifyAnything(data, rv)
|
||||||
|
case reflect.Float32:
|
||||||
|
fallthrough
|
||||||
|
case reflect.Float64:
|
||||||
|
return md.unifyFloat64(data, rv)
|
||||||
|
}
|
||||||
|
return e("unsupported type %s", rv.Kind())
|
||||||
|
}
|
||||||
|
|
||||||
|
func (md *MetaData) unifyStruct(mapping interface{}, rv reflect.Value) error {
|
||||||
|
tmap, ok := mapping.(map[string]interface{})
|
||||||
|
if !ok {
|
||||||
|
if mapping == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return e("type mismatch for %s: expected table but found %T",
|
||||||
|
rv.Type().String(), mapping)
|
||||||
|
}
|
||||||
|
|
||||||
|
for key, datum := range tmap {
|
||||||
|
var f *field
|
||||||
|
fields := cachedTypeFields(rv.Type())
|
||||||
|
for i := range fields {
|
||||||
|
ff := &fields[i]
|
||||||
|
if ff.name == key {
|
||||||
|
f = ff
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if f == nil && strings.EqualFold(ff.name, key) {
|
||||||
|
f = ff
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if f != nil {
|
||||||
|
subv := rv
|
||||||
|
for _, i := range f.index {
|
||||||
|
subv = indirect(subv.Field(i))
|
||||||
|
}
|
||||||
|
if isUnifiable(subv) {
|
||||||
|
md.decoded[md.context.add(key).String()] = true
|
||||||
|
md.context = append(md.context, key)
|
||||||
|
if err := md.unify(datum, subv); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
md.context = md.context[0 : len(md.context)-1]
|
||||||
|
} else if f.name != "" {
|
||||||
|
// Bad user! No soup for you!
|
||||||
|
return e("cannot write unexported field %s.%s",
|
||||||
|
rv.Type().String(), f.name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (md *MetaData) unifyMap(mapping interface{}, rv reflect.Value) error {
|
||||||
|
if k := rv.Type().Key().Kind(); k != reflect.String {
|
||||||
|
return fmt.Errorf(
|
||||||
|
"toml: cannot decode to a map with non-string key type (%s in %q)",
|
||||||
|
k, rv.Type())
|
||||||
|
}
|
||||||
|
|
||||||
|
tmap, ok := mapping.(map[string]interface{})
|
||||||
|
if !ok {
|
||||||
|
if tmap == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return badtype("map", mapping)
|
||||||
|
}
|
||||||
|
if rv.IsNil() {
|
||||||
|
rv.Set(reflect.MakeMap(rv.Type()))
|
||||||
|
}
|
||||||
|
for k, v := range tmap {
|
||||||
|
md.decoded[md.context.add(k).String()] = true
|
||||||
|
md.context = append(md.context, k)
|
||||||
|
|
||||||
|
rvkey := indirect(reflect.New(rv.Type().Key()))
|
||||||
|
rvval := reflect.Indirect(reflect.New(rv.Type().Elem()))
|
||||||
|
if err := md.unify(v, rvval); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
md.context = md.context[0 : len(md.context)-1]
|
||||||
|
|
||||||
|
rvkey.SetString(k)
|
||||||
|
rv.SetMapIndex(rvkey, rvval)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (md *MetaData) unifyArray(data interface{}, rv reflect.Value) error {
|
||||||
|
datav := reflect.ValueOf(data)
|
||||||
|
if datav.Kind() != reflect.Slice {
|
||||||
|
if !datav.IsValid() {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return badtype("slice", data)
|
||||||
|
}
|
||||||
|
if l := datav.Len(); l != rv.Len() {
|
||||||
|
return e("expected array length %d; got TOML array of length %d", rv.Len(), l)
|
||||||
|
}
|
||||||
|
return md.unifySliceArray(datav, rv)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (md *MetaData) unifySlice(data interface{}, rv reflect.Value) error {
|
||||||
|
datav := reflect.ValueOf(data)
|
||||||
|
if datav.Kind() != reflect.Slice {
|
||||||
|
if !datav.IsValid() {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return badtype("slice", data)
|
||||||
|
}
|
||||||
|
n := datav.Len()
|
||||||
|
if rv.IsNil() || rv.Cap() < n {
|
||||||
|
rv.Set(reflect.MakeSlice(rv.Type(), n, n))
|
||||||
|
}
|
||||||
|
rv.SetLen(n)
|
||||||
|
return md.unifySliceArray(datav, rv)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (md *MetaData) unifySliceArray(data, rv reflect.Value) error {
|
||||||
|
l := data.Len()
|
||||||
|
for i := 0; i < l; i++ {
|
||||||
|
err := md.unify(data.Index(i).Interface(), indirect(rv.Index(i)))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (md *MetaData) unifyDatetime(data interface{}, rv reflect.Value) error {
|
||||||
|
if _, ok := data.(time.Time); ok {
|
||||||
|
rv.Set(reflect.ValueOf(data))
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return badtype("time.Time", data)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (md *MetaData) unifyString(data interface{}, rv reflect.Value) error {
|
||||||
|
if s, ok := data.(string); ok {
|
||||||
|
rv.SetString(s)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return badtype("string", data)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (md *MetaData) unifyFloat64(data interface{}, rv reflect.Value) error {
|
||||||
|
if num, ok := data.(float64); ok {
|
||||||
|
switch rv.Kind() {
|
||||||
|
case reflect.Float32:
|
||||||
|
fallthrough
|
||||||
|
case reflect.Float64:
|
||||||
|
rv.SetFloat(num)
|
||||||
|
default:
|
||||||
|
panic("bug")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return badtype("float", data)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (md *MetaData) unifyInt(data interface{}, rv reflect.Value) error {
|
||||||
|
if num, ok := data.(int64); ok {
|
||||||
|
if rv.Kind() >= reflect.Int && rv.Kind() <= reflect.Int64 {
|
||||||
|
switch rv.Kind() {
|
||||||
|
case reflect.Int, reflect.Int64:
|
||||||
|
// No bounds checking necessary.
|
||||||
|
case reflect.Int8:
|
||||||
|
if num < math.MinInt8 || num > math.MaxInt8 {
|
||||||
|
return e("value %d is out of range for int8", num)
|
||||||
|
}
|
||||||
|
case reflect.Int16:
|
||||||
|
if num < math.MinInt16 || num > math.MaxInt16 {
|
||||||
|
return e("value %d is out of range for int16", num)
|
||||||
|
}
|
||||||
|
case reflect.Int32:
|
||||||
|
if num < math.MinInt32 || num > math.MaxInt32 {
|
||||||
|
return e("value %d is out of range for int32", num)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
rv.SetInt(num)
|
||||||
|
} else if rv.Kind() >= reflect.Uint && rv.Kind() <= reflect.Uint64 {
|
||||||
|
unum := uint64(num)
|
||||||
|
switch rv.Kind() {
|
||||||
|
case reflect.Uint, reflect.Uint64:
|
||||||
|
// No bounds checking necessary.
|
||||||
|
case reflect.Uint8:
|
||||||
|
if num < 0 || unum > math.MaxUint8 {
|
||||||
|
return e("value %d is out of range for uint8", num)
|
||||||
|
}
|
||||||
|
case reflect.Uint16:
|
||||||
|
if num < 0 || unum > math.MaxUint16 {
|
||||||
|
return e("value %d is out of range for uint16", num)
|
||||||
|
}
|
||||||
|
case reflect.Uint32:
|
||||||
|
if num < 0 || unum > math.MaxUint32 {
|
||||||
|
return e("value %d is out of range for uint32", num)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
rv.SetUint(unum)
|
||||||
|
} else {
|
||||||
|
panic("unreachable")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return badtype("integer", data)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (md *MetaData) unifyBool(data interface{}, rv reflect.Value) error {
|
||||||
|
if b, ok := data.(bool); ok {
|
||||||
|
rv.SetBool(b)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return badtype("boolean", data)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (md *MetaData) unifyAnything(data interface{}, rv reflect.Value) error {
|
||||||
|
rv.Set(reflect.ValueOf(data))
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (md *MetaData) unifyText(data interface{}, v encoding.TextUnmarshaler) error {
|
||||||
|
var s string
|
||||||
|
switch sdata := data.(type) {
|
||||||
|
case TextMarshaler:
|
||||||
|
text, err := sdata.MarshalText()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
s = string(text)
|
||||||
|
case fmt.Stringer:
|
||||||
|
s = sdata.String()
|
||||||
|
case string:
|
||||||
|
s = sdata
|
||||||
|
case bool:
|
||||||
|
s = fmt.Sprintf("%v", sdata)
|
||||||
|
case int64:
|
||||||
|
s = fmt.Sprintf("%d", sdata)
|
||||||
|
case float64:
|
||||||
|
s = fmt.Sprintf("%f", sdata)
|
||||||
|
default:
|
||||||
|
return badtype("primitive (string-like)", data)
|
||||||
|
}
|
||||||
|
if err := v.UnmarshalText([]byte(s)); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// rvalue returns a reflect.Value of `v`. All pointers are resolved.
|
||||||
|
func rvalue(v interface{}) reflect.Value {
|
||||||
|
return indirect(reflect.ValueOf(v))
|
||||||
|
}
|
||||||
|
|
||||||
|
// indirect returns the value pointed to by a pointer.
|
||||||
|
// Pointers are followed until the value is not a pointer.
|
||||||
|
// New values are allocated for each nil pointer.
|
||||||
|
//
|
||||||
|
// An exception to this rule is if the value satisfies an interface of
|
||||||
|
// interest to us (like encoding.TextUnmarshaler).
|
||||||
|
func indirect(v reflect.Value) reflect.Value {
|
||||||
|
if v.Kind() != reflect.Ptr {
|
||||||
|
if v.CanSet() {
|
||||||
|
pv := v.Addr()
|
||||||
|
if _, ok := pv.Interface().(encoding.TextUnmarshaler); ok {
|
||||||
|
return pv
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
if v.IsNil() {
|
||||||
|
v.Set(reflect.New(v.Type().Elem()))
|
||||||
|
}
|
||||||
|
return indirect(reflect.Indirect(v))
|
||||||
|
}
|
||||||
|
|
||||||
|
func isUnifiable(rv reflect.Value) bool {
|
||||||
|
if rv.CanSet() {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if _, ok := rv.Interface().(encoding.TextUnmarshaler); ok {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func e(format string, args ...interface{}) error {
|
||||||
|
return fmt.Errorf("toml: "+format, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func badtype(expected string, data interface{}) error {
|
||||||
|
return e("cannot load TOML value of type %T into a Go %s", data, expected)
|
||||||
|
}
|
18
vendor/github.com/BurntSushi/toml/decode_go116.go
generated
vendored
Normal file
18
vendor/github.com/BurntSushi/toml/decode_go116.go
generated
vendored
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
// +build go1.16
|
||||||
|
|
||||||
|
package toml
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io/fs"
|
||||||
|
)
|
||||||
|
|
||||||
|
// DecodeFS is just like Decode, except it will automatically read the contents
|
||||||
|
// of the file at `path` from a fs.FS instance.
|
||||||
|
func DecodeFS(fsys fs.FS, path string, v interface{}) (MetaData, error) {
|
||||||
|
fp, err := fsys.Open(path)
|
||||||
|
if err != nil {
|
||||||
|
return MetaData{}, err
|
||||||
|
}
|
||||||
|
defer fp.Close()
|
||||||
|
return NewDecoder(fp).Decode(v)
|
||||||
|
}
|
123
vendor/github.com/BurntSushi/toml/decode_meta.go
generated
vendored
Normal file
123
vendor/github.com/BurntSushi/toml/decode_meta.go
generated
vendored
Normal file
@ -0,0 +1,123 @@
|
|||||||
|
package toml
|
||||||
|
|
||||||
|
import "strings"
|
||||||
|
|
||||||
|
// MetaData allows access to meta information about TOML data that may not be
|
||||||
|
// inferable via reflection. In particular, whether a key has been defined and
|
||||||
|
// the TOML type of a key.
|
||||||
|
type MetaData struct {
|
||||||
|
mapping map[string]interface{}
|
||||||
|
types map[string]tomlType
|
||||||
|
keys []Key
|
||||||
|
decoded map[string]bool
|
||||||
|
context Key // Used only during decoding.
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsDefined reports if the key exists in the TOML data.
|
||||||
|
//
|
||||||
|
// The key should be specified hierarchically, for example to access the TOML
|
||||||
|
// key "a.b.c" you would use:
|
||||||
|
//
|
||||||
|
// IsDefined("a", "b", "c")
|
||||||
|
//
|
||||||
|
// IsDefined will return false if an empty key given. Keys are case sensitive.
|
||||||
|
func (md *MetaData) IsDefined(key ...string) bool {
|
||||||
|
if len(key) == 0 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
var hash map[string]interface{}
|
||||||
|
var ok bool
|
||||||
|
var hashOrVal interface{} = md.mapping
|
||||||
|
for _, k := range key {
|
||||||
|
if hash, ok = hashOrVal.(map[string]interface{}); !ok {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if hashOrVal, ok = hash[k]; !ok {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Type returns a string representation of the type of the key specified.
|
||||||
|
//
|
||||||
|
// Type will return the empty string if given an empty key or a key that does
|
||||||
|
// not exist. Keys are case sensitive.
|
||||||
|
func (md *MetaData) Type(key ...string) string {
|
||||||
|
fullkey := strings.Join(key, ".")
|
||||||
|
if typ, ok := md.types[fullkey]; ok {
|
||||||
|
return typ.typeString()
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
// Key represents any TOML key, including key groups. Use (MetaData).Keys to get
|
||||||
|
// values of this type.
|
||||||
|
type Key []string
|
||||||
|
|
||||||
|
func (k Key) String() string { return strings.Join(k, ".") }
|
||||||
|
|
||||||
|
func (k Key) maybeQuotedAll() string {
|
||||||
|
var ss []string
|
||||||
|
for i := range k {
|
||||||
|
ss = append(ss, k.maybeQuoted(i))
|
||||||
|
}
|
||||||
|
return strings.Join(ss, ".")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (k Key) maybeQuoted(i int) string {
|
||||||
|
if k[i] == "" {
|
||||||
|
return `""`
|
||||||
|
}
|
||||||
|
quote := false
|
||||||
|
for _, c := range k[i] {
|
||||||
|
if !isBareKeyChar(c) {
|
||||||
|
quote = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if quote {
|
||||||
|
return `"` + quotedReplacer.Replace(k[i]) + `"`
|
||||||
|
}
|
||||||
|
return k[i]
|
||||||
|
}
|
||||||
|
|
||||||
|
func (k Key) add(piece string) Key {
|
||||||
|
newKey := make(Key, len(k)+1)
|
||||||
|
copy(newKey, k)
|
||||||
|
newKey[len(k)] = piece
|
||||||
|
return newKey
|
||||||
|
}
|
||||||
|
|
||||||
|
// Keys returns a slice of every key in the TOML data, including key groups.
|
||||||
|
//
|
||||||
|
// Each key is itself a slice, where the first element is the top of the
|
||||||
|
// hierarchy and the last is the most specific. The list will have the same
|
||||||
|
// order as the keys appeared in the TOML data.
|
||||||
|
//
|
||||||
|
// All keys returned are non-empty.
|
||||||
|
func (md *MetaData) Keys() []Key {
|
||||||
|
return md.keys
|
||||||
|
}
|
||||||
|
|
||||||
|
// Undecoded returns all keys that have not been decoded in the order in which
|
||||||
|
// they appear in the original TOML document.
|
||||||
|
//
|
||||||
|
// This includes keys that haven't been decoded because of a Primitive value.
|
||||||
|
// Once the Primitive value is decoded, the keys will be considered decoded.
|
||||||
|
//
|
||||||
|
// Also note that decoding into an empty interface will result in no decoding,
|
||||||
|
// and so no keys will be considered decoded.
|
||||||
|
//
|
||||||
|
// In this sense, the Undecoded keys correspond to keys in the TOML document
|
||||||
|
// that do not have a concrete type in your representation.
|
||||||
|
func (md *MetaData) Undecoded() []Key {
|
||||||
|
undecoded := make([]Key, 0, len(md.keys))
|
||||||
|
for _, key := range md.keys {
|
||||||
|
if !md.decoded[key.String()] {
|
||||||
|
undecoded = append(undecoded, key)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return undecoded
|
||||||
|
}
|
33
vendor/github.com/BurntSushi/toml/deprecated.go
generated
vendored
Normal file
33
vendor/github.com/BurntSushi/toml/deprecated.go
generated
vendored
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
package toml
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding"
|
||||||
|
"io"
|
||||||
|
)
|
||||||
|
|
||||||
|
// DEPRECATED!
|
||||||
|
//
|
||||||
|
// Use the identical encoding.TextMarshaler instead. It is defined here to
|
||||||
|
// support Go 1.1 and older.
|
||||||
|
type TextMarshaler encoding.TextMarshaler
|
||||||
|
|
||||||
|
// DEPRECATED!
|
||||||
|
//
|
||||||
|
// Use the identical encoding.TextUnmarshaler instead. It is defined here to
|
||||||
|
// support Go 1.1 and older.
|
||||||
|
type TextUnmarshaler encoding.TextUnmarshaler
|
||||||
|
|
||||||
|
// DEPRECATED!
|
||||||
|
//
|
||||||
|
// Use MetaData.PrimitiveDecode instead.
|
||||||
|
func PrimitiveDecode(primValue Primitive, v interface{}) error {
|
||||||
|
md := MetaData{decoded: make(map[string]bool)}
|
||||||
|
return md.unify(primValue.undecoded, rvalue(v))
|
||||||
|
}
|
||||||
|
|
||||||
|
// DEPRECATED!
|
||||||
|
//
|
||||||
|
// Use NewDecoder(reader).Decode(&v) instead.
|
||||||
|
func DecodeReader(r io.Reader, v interface{}) (MetaData, error) {
|
||||||
|
return NewDecoder(r).Decode(v)
|
||||||
|
}
|
13
vendor/github.com/BurntSushi/toml/doc.go
generated
vendored
Normal file
13
vendor/github.com/BurntSushi/toml/doc.go
generated
vendored
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
/*
|
||||||
|
Package toml implements decoding and encoding of TOML files.
|
||||||
|
|
||||||
|
This package supports TOML v1.0.0, as listed on https://toml.io
|
||||||
|
|
||||||
|
There is also support for delaying decoding with the Primitive type, and
|
||||||
|
querying the set of keys in a TOML document with the MetaData type.
|
||||||
|
|
||||||
|
The github.com/BurntSushi/toml/cmd/tomlv package implements a TOML validator,
|
||||||
|
and can be used to verify if TOML document is valid. It can also be used to
|
||||||
|
print the type of each key.
|
||||||
|
*/
|
||||||
|
package toml
|
650
vendor/github.com/BurntSushi/toml/encode.go
generated
vendored
Normal file
650
vendor/github.com/BurntSushi/toml/encode.go
generated
vendored
Normal file
@ -0,0 +1,650 @@
|
|||||||
|
package toml
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"encoding"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"math"
|
||||||
|
"reflect"
|
||||||
|
"sort"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/BurntSushi/toml/internal"
|
||||||
|
)
|
||||||
|
|
||||||
|
type tomlEncodeError struct{ error }
|
||||||
|
|
||||||
|
var (
|
||||||
|
errArrayNilElement = errors.New("toml: cannot encode array with nil element")
|
||||||
|
errNonString = errors.New("toml: cannot encode a map with non-string key type")
|
||||||
|
errAnonNonStruct = errors.New("toml: cannot encode an anonymous field that is not a struct")
|
||||||
|
errNoKey = errors.New("toml: top-level values must be Go maps or structs")
|
||||||
|
errAnything = errors.New("") // used in testing
|
||||||
|
)
|
||||||
|
|
||||||
|
var quotedReplacer = strings.NewReplacer(
|
||||||
|
"\"", "\\\"",
|
||||||
|
"\\", "\\\\",
|
||||||
|
"\x00", `\u0000`,
|
||||||
|
"\x01", `\u0001`,
|
||||||
|
"\x02", `\u0002`,
|
||||||
|
"\x03", `\u0003`,
|
||||||
|
"\x04", `\u0004`,
|
||||||
|
"\x05", `\u0005`,
|
||||||
|
"\x06", `\u0006`,
|
||||||
|
"\x07", `\u0007`,
|
||||||
|
"\b", `\b`,
|
||||||
|
"\t", `\t`,
|
||||||
|
"\n", `\n`,
|
||||||
|
"\x0b", `\u000b`,
|
||||||
|
"\f", `\f`,
|
||||||
|
"\r", `\r`,
|
||||||
|
"\x0e", `\u000e`,
|
||||||
|
"\x0f", `\u000f`,
|
||||||
|
"\x10", `\u0010`,
|
||||||
|
"\x11", `\u0011`,
|
||||||
|
"\x12", `\u0012`,
|
||||||
|
"\x13", `\u0013`,
|
||||||
|
"\x14", `\u0014`,
|
||||||
|
"\x15", `\u0015`,
|
||||||
|
"\x16", `\u0016`,
|
||||||
|
"\x17", `\u0017`,
|
||||||
|
"\x18", `\u0018`,
|
||||||
|
"\x19", `\u0019`,
|
||||||
|
"\x1a", `\u001a`,
|
||||||
|
"\x1b", `\u001b`,
|
||||||
|
"\x1c", `\u001c`,
|
||||||
|
"\x1d", `\u001d`,
|
||||||
|
"\x1e", `\u001e`,
|
||||||
|
"\x1f", `\u001f`,
|
||||||
|
"\x7f", `\u007f`,
|
||||||
|
)
|
||||||
|
|
||||||
|
// Encoder encodes a Go to a TOML document.
|
||||||
|
//
|
||||||
|
// The mapping between Go values and TOML values should be precisely the same as
|
||||||
|
// for the Decode* functions. Similarly, the TextMarshaler interface is
|
||||||
|
// supported by encoding the resulting bytes as strings. If you want to write
|
||||||
|
// arbitrary binary data then you will need to use something like base64 since
|
||||||
|
// TOML does not have any binary types.
|
||||||
|
//
|
||||||
|
// When encoding TOML hashes (Go maps or structs), keys without any sub-hashes
|
||||||
|
// are encoded first.
|
||||||
|
//
|
||||||
|
// Go maps will be sorted alphabetically by key for deterministic output.
|
||||||
|
//
|
||||||
|
// Encoding Go values without a corresponding TOML representation will return an
|
||||||
|
// error. Examples of this includes maps with non-string keys, slices with nil
|
||||||
|
// elements, embedded non-struct types, and nested slices containing maps or
|
||||||
|
// structs. (e.g. [][]map[string]string is not allowed but []map[string]string
|
||||||
|
// is okay, as is []map[string][]string).
|
||||||
|
//
|
||||||
|
// NOTE: Only exported keys are encoded due to the use of reflection. Unexported
|
||||||
|
// keys are silently discarded.
|
||||||
|
type Encoder struct {
|
||||||
|
// The string to use for a single indentation level. The default is two
|
||||||
|
// spaces.
|
||||||
|
Indent string
|
||||||
|
|
||||||
|
// hasWritten is whether we have written any output to w yet.
|
||||||
|
hasWritten bool
|
||||||
|
w *bufio.Writer
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewEncoder create a new Encoder.
|
||||||
|
func NewEncoder(w io.Writer) *Encoder {
|
||||||
|
return &Encoder{
|
||||||
|
w: bufio.NewWriter(w),
|
||||||
|
Indent: " ",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Encode writes a TOML representation of the Go value to the Encoder's writer.
|
||||||
|
//
|
||||||
|
// An error is returned if the value given cannot be encoded to a valid TOML
|
||||||
|
// document.
|
||||||
|
func (enc *Encoder) Encode(v interface{}) error {
|
||||||
|
rv := eindirect(reflect.ValueOf(v))
|
||||||
|
if err := enc.safeEncode(Key([]string{}), rv); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return enc.w.Flush()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (enc *Encoder) safeEncode(key Key, rv reflect.Value) (err error) {
|
||||||
|
defer func() {
|
||||||
|
if r := recover(); r != nil {
|
||||||
|
if terr, ok := r.(tomlEncodeError); ok {
|
||||||
|
err = terr.error
|
||||||
|
return
|
||||||
|
}
|
||||||
|
panic(r)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
enc.encode(key, rv)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (enc *Encoder) encode(key Key, rv reflect.Value) {
|
||||||
|
// Special case. Time needs to be in ISO8601 format.
|
||||||
|
// Special case. If we can marshal the type to text, then we used that.
|
||||||
|
// Basically, this prevents the encoder for handling these types as
|
||||||
|
// generic structs (or whatever the underlying type of a TextMarshaler is).
|
||||||
|
switch t := rv.Interface().(type) {
|
||||||
|
case time.Time, encoding.TextMarshaler:
|
||||||
|
enc.writeKeyValue(key, rv, false)
|
||||||
|
return
|
||||||
|
// TODO: #76 would make this superfluous after implemented.
|
||||||
|
case Primitive:
|
||||||
|
enc.encode(key, reflect.ValueOf(t.undecoded))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
k := rv.Kind()
|
||||||
|
switch k {
|
||||||
|
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32,
|
||||||
|
reflect.Int64,
|
||||||
|
reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32,
|
||||||
|
reflect.Uint64,
|
||||||
|
reflect.Float32, reflect.Float64, reflect.String, reflect.Bool:
|
||||||
|
enc.writeKeyValue(key, rv, false)
|
||||||
|
case reflect.Array, reflect.Slice:
|
||||||
|
if typeEqual(tomlArrayHash, tomlTypeOfGo(rv)) {
|
||||||
|
enc.eArrayOfTables(key, rv)
|
||||||
|
} else {
|
||||||
|
enc.writeKeyValue(key, rv, false)
|
||||||
|
}
|
||||||
|
case reflect.Interface:
|
||||||
|
if rv.IsNil() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
enc.encode(key, rv.Elem())
|
||||||
|
case reflect.Map:
|
||||||
|
if rv.IsNil() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
enc.eTable(key, rv)
|
||||||
|
case reflect.Ptr:
|
||||||
|
if rv.IsNil() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
enc.encode(key, rv.Elem())
|
||||||
|
case reflect.Struct:
|
||||||
|
enc.eTable(key, rv)
|
||||||
|
default:
|
||||||
|
encPanic(fmt.Errorf("unsupported type for key '%s': %s", key, k))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// eElement encodes any value that can be an array element.
|
||||||
|
func (enc *Encoder) eElement(rv reflect.Value) {
|
||||||
|
switch v := rv.Interface().(type) {
|
||||||
|
case time.Time: // Using TextMarshaler adds extra quotes, which we don't want.
|
||||||
|
format := time.RFC3339Nano
|
||||||
|
switch v.Location() {
|
||||||
|
case internal.LocalDatetime:
|
||||||
|
format = "2006-01-02T15:04:05.999999999"
|
||||||
|
case internal.LocalDate:
|
||||||
|
format = "2006-01-02"
|
||||||
|
case internal.LocalTime:
|
||||||
|
format = "15:04:05.999999999"
|
||||||
|
}
|
||||||
|
switch v.Location() {
|
||||||
|
default:
|
||||||
|
enc.wf(v.Format(format))
|
||||||
|
case internal.LocalDatetime, internal.LocalDate, internal.LocalTime:
|
||||||
|
enc.wf(v.In(time.UTC).Format(format))
|
||||||
|
}
|
||||||
|
return
|
||||||
|
case encoding.TextMarshaler:
|
||||||
|
// Use text marshaler if it's available for this value.
|
||||||
|
if s, err := v.MarshalText(); err != nil {
|
||||||
|
encPanic(err)
|
||||||
|
} else {
|
||||||
|
enc.writeQuoted(string(s))
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
switch rv.Kind() {
|
||||||
|
case reflect.String:
|
||||||
|
enc.writeQuoted(rv.String())
|
||||||
|
case reflect.Bool:
|
||||||
|
enc.wf(strconv.FormatBool(rv.Bool()))
|
||||||
|
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||||
|
enc.wf(strconv.FormatInt(rv.Int(), 10))
|
||||||
|
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
|
||||||
|
enc.wf(strconv.FormatUint(rv.Uint(), 10))
|
||||||
|
case reflect.Float32:
|
||||||
|
f := rv.Float()
|
||||||
|
if math.IsNaN(f) {
|
||||||
|
enc.wf("nan")
|
||||||
|
} else if math.IsInf(f, 0) {
|
||||||
|
enc.wf("%cinf", map[bool]byte{true: '-', false: '+'}[math.Signbit(f)])
|
||||||
|
} else {
|
||||||
|
enc.wf(floatAddDecimal(strconv.FormatFloat(f, 'f', -1, 32)))
|
||||||
|
}
|
||||||
|
case reflect.Float64:
|
||||||
|
f := rv.Float()
|
||||||
|
if math.IsNaN(f) {
|
||||||
|
enc.wf("nan")
|
||||||
|
} else if math.IsInf(f, 0) {
|
||||||
|
enc.wf("%cinf", map[bool]byte{true: '-', false: '+'}[math.Signbit(f)])
|
||||||
|
} else {
|
||||||
|
enc.wf(floatAddDecimal(strconv.FormatFloat(f, 'f', -1, 64)))
|
||||||
|
}
|
||||||
|
case reflect.Array, reflect.Slice:
|
||||||
|
enc.eArrayOrSliceElement(rv)
|
||||||
|
case reflect.Struct:
|
||||||
|
enc.eStruct(nil, rv, true)
|
||||||
|
case reflect.Map:
|
||||||
|
enc.eMap(nil, rv, true)
|
||||||
|
case reflect.Interface:
|
||||||
|
enc.eElement(rv.Elem())
|
||||||
|
default:
|
||||||
|
encPanic(fmt.Errorf("unexpected primitive type: %T", rv.Interface()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// By the TOML spec, all floats must have a decimal with at least one number on
|
||||||
|
// either side.
|
||||||
|
func floatAddDecimal(fstr string) string {
|
||||||
|
if !strings.Contains(fstr, ".") {
|
||||||
|
return fstr + ".0"
|
||||||
|
}
|
||||||
|
return fstr
|
||||||
|
}
|
||||||
|
|
||||||
|
func (enc *Encoder) writeQuoted(s string) {
|
||||||
|
enc.wf("\"%s\"", quotedReplacer.Replace(s))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (enc *Encoder) eArrayOrSliceElement(rv reflect.Value) {
|
||||||
|
length := rv.Len()
|
||||||
|
enc.wf("[")
|
||||||
|
for i := 0; i < length; i++ {
|
||||||
|
elem := rv.Index(i)
|
||||||
|
enc.eElement(elem)
|
||||||
|
if i != length-1 {
|
||||||
|
enc.wf(", ")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
enc.wf("]")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (enc *Encoder) eArrayOfTables(key Key, rv reflect.Value) {
|
||||||
|
if len(key) == 0 {
|
||||||
|
encPanic(errNoKey)
|
||||||
|
}
|
||||||
|
for i := 0; i < rv.Len(); i++ {
|
||||||
|
trv := rv.Index(i)
|
||||||
|
if isNil(trv) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
enc.newline()
|
||||||
|
enc.wf("%s[[%s]]", enc.indentStr(key), key.maybeQuotedAll())
|
||||||
|
enc.newline()
|
||||||
|
enc.eMapOrStruct(key, trv, false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (enc *Encoder) eTable(key Key, rv reflect.Value) {
|
||||||
|
if len(key) == 1 {
|
||||||
|
// Output an extra newline between top-level tables.
|
||||||
|
// (The newline isn't written if nothing else has been written though.)
|
||||||
|
enc.newline()
|
||||||
|
}
|
||||||
|
if len(key) > 0 {
|
||||||
|
enc.wf("%s[%s]", enc.indentStr(key), key.maybeQuotedAll())
|
||||||
|
enc.newline()
|
||||||
|
}
|
||||||
|
enc.eMapOrStruct(key, rv, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (enc *Encoder) eMapOrStruct(key Key, rv reflect.Value, inline bool) {
|
||||||
|
switch rv := eindirect(rv); rv.Kind() {
|
||||||
|
case reflect.Map:
|
||||||
|
enc.eMap(key, rv, inline)
|
||||||
|
case reflect.Struct:
|
||||||
|
enc.eStruct(key, rv, inline)
|
||||||
|
default:
|
||||||
|
// Should never happen?
|
||||||
|
panic("eTable: unhandled reflect.Value Kind: " + rv.Kind().String())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (enc *Encoder) eMap(key Key, rv reflect.Value, inline bool) {
|
||||||
|
rt := rv.Type()
|
||||||
|
if rt.Key().Kind() != reflect.String {
|
||||||
|
encPanic(errNonString)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sort keys so that we have deterministic output. And write keys directly
|
||||||
|
// underneath this key first, before writing sub-structs or sub-maps.
|
||||||
|
var mapKeysDirect, mapKeysSub []string
|
||||||
|
for _, mapKey := range rv.MapKeys() {
|
||||||
|
k := mapKey.String()
|
||||||
|
if typeIsHash(tomlTypeOfGo(rv.MapIndex(mapKey))) {
|
||||||
|
mapKeysSub = append(mapKeysSub, k)
|
||||||
|
} else {
|
||||||
|
mapKeysDirect = append(mapKeysDirect, k)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var writeMapKeys = func(mapKeys []string, trailC bool) {
|
||||||
|
sort.Strings(mapKeys)
|
||||||
|
for i, mapKey := range mapKeys {
|
||||||
|
val := rv.MapIndex(reflect.ValueOf(mapKey))
|
||||||
|
if isNil(val) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if inline {
|
||||||
|
enc.writeKeyValue(Key{mapKey}, val, true)
|
||||||
|
if trailC || i != len(mapKeys)-1 {
|
||||||
|
enc.wf(", ")
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
enc.encode(key.add(mapKey), val)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if inline {
|
||||||
|
enc.wf("{")
|
||||||
|
}
|
||||||
|
writeMapKeys(mapKeysDirect, len(mapKeysSub) > 0)
|
||||||
|
writeMapKeys(mapKeysSub, false)
|
||||||
|
if inline {
|
||||||
|
enc.wf("}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (enc *Encoder) eStruct(key Key, rv reflect.Value, inline bool) {
|
||||||
|
// Write keys for fields directly under this key first, because if we write
|
||||||
|
// a field that creates a new table then all keys under it will be in that
|
||||||
|
// table (not the one we're writing here).
|
||||||
|
//
|
||||||
|
// Fields is a [][]int: for fieldsDirect this always has one entry (the
|
||||||
|
// struct index). For fieldsSub it contains two entries: the parent field
|
||||||
|
// index from tv, and the field indexes for the fields of the sub.
|
||||||
|
var (
|
||||||
|
rt = rv.Type()
|
||||||
|
fieldsDirect, fieldsSub [][]int
|
||||||
|
addFields func(rt reflect.Type, rv reflect.Value, start []int)
|
||||||
|
)
|
||||||
|
addFields = func(rt reflect.Type, rv reflect.Value, start []int) {
|
||||||
|
for i := 0; i < rt.NumField(); i++ {
|
||||||
|
f := rt.Field(i)
|
||||||
|
if f.PkgPath != "" && !f.Anonymous { /// Skip unexported fields.
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
frv := rv.Field(i)
|
||||||
|
|
||||||
|
// Treat anonymous struct fields with tag names as though they are
|
||||||
|
// not anonymous, like encoding/json does.
|
||||||
|
//
|
||||||
|
// Non-struct anonymous fields use the normal encoding logic.
|
||||||
|
if f.Anonymous {
|
||||||
|
t := f.Type
|
||||||
|
switch t.Kind() {
|
||||||
|
case reflect.Struct:
|
||||||
|
if getOptions(f.Tag).name == "" {
|
||||||
|
addFields(t, frv, append(start, f.Index...))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
case reflect.Ptr:
|
||||||
|
if t.Elem().Kind() == reflect.Struct && getOptions(f.Tag).name == "" {
|
||||||
|
if !frv.IsNil() {
|
||||||
|
addFields(t.Elem(), frv.Elem(), append(start, f.Index...))
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if typeIsHash(tomlTypeOfGo(frv)) {
|
||||||
|
fieldsSub = append(fieldsSub, append(start, f.Index...))
|
||||||
|
} else {
|
||||||
|
fieldsDirect = append(fieldsDirect, append(start, f.Index...))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
addFields(rt, rv, nil)
|
||||||
|
|
||||||
|
writeFields := func(fields [][]int) {
|
||||||
|
for _, fieldIndex := range fields {
|
||||||
|
fieldType := rt.FieldByIndex(fieldIndex)
|
||||||
|
fieldVal := rv.FieldByIndex(fieldIndex)
|
||||||
|
|
||||||
|
if isNil(fieldVal) { /// Don't write anything for nil fields.
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
opts := getOptions(fieldType.Tag)
|
||||||
|
if opts.skip {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
keyName := fieldType.Name
|
||||||
|
if opts.name != "" {
|
||||||
|
keyName = opts.name
|
||||||
|
}
|
||||||
|
if opts.omitempty && isEmpty(fieldVal) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if opts.omitzero && isZero(fieldVal) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if inline {
|
||||||
|
enc.writeKeyValue(Key{keyName}, fieldVal, true)
|
||||||
|
if fieldIndex[0] != len(fields)-1 {
|
||||||
|
enc.wf(", ")
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
enc.encode(key.add(keyName), fieldVal)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if inline {
|
||||||
|
enc.wf("{")
|
||||||
|
}
|
||||||
|
writeFields(fieldsDirect)
|
||||||
|
writeFields(fieldsSub)
|
||||||
|
if inline {
|
||||||
|
enc.wf("}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// tomlTypeName returns the TOML type name of the Go value's type. It is
|
||||||
|
// used to determine whether the types of array elements are mixed (which is
|
||||||
|
// forbidden). If the Go value is nil, then it is illegal for it to be an array
|
||||||
|
// element, and valueIsNil is returned as true.
|
||||||
|
|
||||||
|
// Returns the TOML type of a Go value. The type may be `nil`, which means
|
||||||
|
// no concrete TOML type could be found.
|
||||||
|
func tomlTypeOfGo(rv reflect.Value) tomlType {
|
||||||
|
if isNil(rv) || !rv.IsValid() {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
switch rv.Kind() {
|
||||||
|
case reflect.Bool:
|
||||||
|
return tomlBool
|
||||||
|
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32,
|
||||||
|
reflect.Int64,
|
||||||
|
reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32,
|
||||||
|
reflect.Uint64:
|
||||||
|
return tomlInteger
|
||||||
|
case reflect.Float32, reflect.Float64:
|
||||||
|
return tomlFloat
|
||||||
|
case reflect.Array, reflect.Slice:
|
||||||
|
if typeEqual(tomlHash, tomlArrayType(rv)) {
|
||||||
|
return tomlArrayHash
|
||||||
|
}
|
||||||
|
return tomlArray
|
||||||
|
case reflect.Ptr, reflect.Interface:
|
||||||
|
return tomlTypeOfGo(rv.Elem())
|
||||||
|
case reflect.String:
|
||||||
|
return tomlString
|
||||||
|
case reflect.Map:
|
||||||
|
return tomlHash
|
||||||
|
case reflect.Struct:
|
||||||
|
switch rv.Interface().(type) {
|
||||||
|
case time.Time:
|
||||||
|
return tomlDatetime
|
||||||
|
case encoding.TextMarshaler:
|
||||||
|
return tomlString
|
||||||
|
default:
|
||||||
|
// Someone used a pointer receiver: we can make it work for pointer
|
||||||
|
// values.
|
||||||
|
if rv.CanAddr() {
|
||||||
|
_, ok := rv.Addr().Interface().(encoding.TextMarshaler)
|
||||||
|
if ok {
|
||||||
|
return tomlString
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return tomlHash
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
_, ok := rv.Interface().(encoding.TextMarshaler)
|
||||||
|
if ok {
|
||||||
|
return tomlString
|
||||||
|
}
|
||||||
|
encPanic(errors.New("unsupported type: " + rv.Kind().String()))
|
||||||
|
panic("") // Need *some* return value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// tomlArrayType returns the element type of a TOML array. The type returned
|
||||||
|
// may be nil if it cannot be determined (e.g., a nil slice or a zero length
|
||||||
|
// slize). This function may also panic if it finds a type that cannot be
|
||||||
|
// expressed in TOML (such as nil elements, heterogeneous arrays or directly
|
||||||
|
// nested arrays of tables).
|
||||||
|
func tomlArrayType(rv reflect.Value) tomlType {
|
||||||
|
if isNil(rv) || !rv.IsValid() || rv.Len() == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Don't allow nil.
|
||||||
|
rvlen := rv.Len()
|
||||||
|
for i := 1; i < rvlen; i++ {
|
||||||
|
if tomlTypeOfGo(rv.Index(i)) == nil {
|
||||||
|
encPanic(errArrayNilElement)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
firstType := tomlTypeOfGo(rv.Index(0))
|
||||||
|
if firstType == nil {
|
||||||
|
encPanic(errArrayNilElement)
|
||||||
|
}
|
||||||
|
return firstType
|
||||||
|
}
|
||||||
|
|
||||||
|
type tagOptions struct {
|
||||||
|
skip bool // "-"
|
||||||
|
name string
|
||||||
|
omitempty bool
|
||||||
|
omitzero bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func getOptions(tag reflect.StructTag) tagOptions {
|
||||||
|
t := tag.Get("toml")
|
||||||
|
if t == "-" {
|
||||||
|
return tagOptions{skip: true}
|
||||||
|
}
|
||||||
|
var opts tagOptions
|
||||||
|
parts := strings.Split(t, ",")
|
||||||
|
opts.name = parts[0]
|
||||||
|
for _, s := range parts[1:] {
|
||||||
|
switch s {
|
||||||
|
case "omitempty":
|
||||||
|
opts.omitempty = true
|
||||||
|
case "omitzero":
|
||||||
|
opts.omitzero = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return opts
|
||||||
|
}
|
||||||
|
|
||||||
|
func isZero(rv reflect.Value) bool {
|
||||||
|
switch rv.Kind() {
|
||||||
|
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||||
|
return rv.Int() == 0
|
||||||
|
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
|
||||||
|
return rv.Uint() == 0
|
||||||
|
case reflect.Float32, reflect.Float64:
|
||||||
|
return rv.Float() == 0.0
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func isEmpty(rv reflect.Value) bool {
|
||||||
|
switch rv.Kind() {
|
||||||
|
case reflect.Array, reflect.Slice, reflect.Map, reflect.String:
|
||||||
|
return rv.Len() == 0
|
||||||
|
case reflect.Bool:
|
||||||
|
return !rv.Bool()
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (enc *Encoder) newline() {
|
||||||
|
if enc.hasWritten {
|
||||||
|
enc.wf("\n")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write a key/value pair:
|
||||||
|
//
|
||||||
|
// key = <any value>
|
||||||
|
//
|
||||||
|
// If inline is true it won't add a newline at the end.
|
||||||
|
func (enc *Encoder) writeKeyValue(key Key, val reflect.Value, inline bool) {
|
||||||
|
if len(key) == 0 {
|
||||||
|
encPanic(errNoKey)
|
||||||
|
}
|
||||||
|
enc.wf("%s%s = ", enc.indentStr(key), key.maybeQuoted(len(key)-1))
|
||||||
|
enc.eElement(val)
|
||||||
|
if !inline {
|
||||||
|
enc.newline()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (enc *Encoder) wf(format string, v ...interface{}) {
|
||||||
|
if _, err := fmt.Fprintf(enc.w, format, v...); err != nil {
|
||||||
|
encPanic(err)
|
||||||
|
}
|
||||||
|
enc.hasWritten = true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (enc *Encoder) indentStr(key Key) string {
|
||||||
|
return strings.Repeat(enc.Indent, len(key)-1)
|
||||||
|
}
|
||||||
|
|
||||||
|
func encPanic(err error) {
|
||||||
|
panic(tomlEncodeError{err})
|
||||||
|
}
|
||||||
|
|
||||||
|
func eindirect(v reflect.Value) reflect.Value {
|
||||||
|
switch v.Kind() {
|
||||||
|
case reflect.Ptr, reflect.Interface:
|
||||||
|
return eindirect(v.Elem())
|
||||||
|
default:
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func isNil(rv reflect.Value) bool {
|
||||||
|
switch rv.Kind() {
|
||||||
|
case reflect.Interface, reflect.Map, reflect.Ptr, reflect.Slice:
|
||||||
|
return rv.IsNil()
|
||||||
|
default:
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
36
vendor/github.com/BurntSushi/toml/internal/tz.go
generated
vendored
Normal file
36
vendor/github.com/BurntSushi/toml/internal/tz.go
generated
vendored
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
package internal
|
||||||
|
|
||||||
|
import "time"
|
||||||
|
|
||||||
|
// Timezones used for local datetime, date, and time TOML types.
|
||||||
|
//
|
||||||
|
// The exact way times and dates without a timezone should be interpreted is not
|
||||||
|
// well-defined in the TOML specification and left to the implementation. These
|
||||||
|
// defaults to current local timezone offset of the computer, but this can be
|
||||||
|
// changed by changing these variables before decoding.
|
||||||
|
//
|
||||||
|
// TODO:
|
||||||
|
// Ideally we'd like to offer people the ability to configure the used timezone
|
||||||
|
// by setting Decoder.Timezone and Encoder.Timezone; however, this is a bit
|
||||||
|
// tricky: the reason we use three different variables for this is to support
|
||||||
|
// round-tripping – without these specific TZ names we wouldn't know which
|
||||||
|
// format to use.
|
||||||
|
//
|
||||||
|
// There isn't a good way to encode this right now though, and passing this sort
|
||||||
|
// of information also ties in to various related issues such as string format
|
||||||
|
// encoding, encoding of comments, etc.
|
||||||
|
//
|
||||||
|
// So, for the time being, just put this in internal until we can write a good
|
||||||
|
// comprehensive API for doing all of this.
|
||||||
|
//
|
||||||
|
// The reason they're exported is because they're referred from in e.g.
|
||||||
|
// internal/tag.
|
||||||
|
//
|
||||||
|
// Note that this behaviour is valid according to the TOML spec as the exact
|
||||||
|
// behaviour is left up to implementations.
|
||||||
|
var (
|
||||||
|
localOffset = func() int { _, o := time.Now().Zone(); return o }()
|
||||||
|
LocalDatetime = time.FixedZone("datetime-local", localOffset)
|
||||||
|
LocalDate = time.FixedZone("date-local", localOffset)
|
||||||
|
LocalTime = time.FixedZone("time-local", localOffset)
|
||||||
|
)
|
1225
vendor/github.com/BurntSushi/toml/lex.go
generated
vendored
Normal file
1225
vendor/github.com/BurntSushi/toml/lex.go
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
739
vendor/github.com/BurntSushi/toml/parse.go
generated
vendored
Normal file
739
vendor/github.com/BurntSushi/toml/parse.go
generated
vendored
Normal file
@ -0,0 +1,739 @@
|
|||||||
|
package toml
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
"unicode/utf8"
|
||||||
|
|
||||||
|
"github.com/BurntSushi/toml/internal"
|
||||||
|
)
|
||||||
|
|
||||||
|
type parser struct {
|
||||||
|
mapping map[string]interface{}
|
||||||
|
types map[string]tomlType
|
||||||
|
lx *lexer
|
||||||
|
|
||||||
|
ordered []Key // List of keys in the order that they appear in the TOML data.
|
||||||
|
context Key // Full key for the current hash in scope.
|
||||||
|
currentKey string // Base key name for everything except hashes.
|
||||||
|
approxLine int // Rough approximation of line number
|
||||||
|
implicits map[string]bool // Record implied keys (e.g. 'key.group.names').
|
||||||
|
}
|
||||||
|
|
||||||
|
// ParseError is used when a file can't be parsed: for example invalid integer
|
||||||
|
// literals, duplicate keys, etc.
|
||||||
|
type ParseError struct {
|
||||||
|
Message string
|
||||||
|
Line int
|
||||||
|
LastKey string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (pe ParseError) Error() string {
|
||||||
|
return fmt.Sprintf("Near line %d (last key parsed '%s'): %s",
|
||||||
|
pe.Line, pe.LastKey, pe.Message)
|
||||||
|
}
|
||||||
|
|
||||||
|
func parse(data string) (p *parser, err error) {
|
||||||
|
defer func() {
|
||||||
|
if r := recover(); r != nil {
|
||||||
|
var ok bool
|
||||||
|
if err, ok = r.(ParseError); ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
panic(r)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
// Read over BOM; do this here as the lexer calls utf8.DecodeRuneInString()
|
||||||
|
// which mangles stuff.
|
||||||
|
if strings.HasPrefix(data, "\xff\xfe") || strings.HasPrefix(data, "\xfe\xff") {
|
||||||
|
data = data[2:]
|
||||||
|
}
|
||||||
|
|
||||||
|
// Examine first few bytes for NULL bytes; this probably means it's a UTF-16
|
||||||
|
// file (second byte in surrogate pair being NULL). Again, do this here to
|
||||||
|
// avoid having to deal with UTF-8/16 stuff in the lexer.
|
||||||
|
ex := 6
|
||||||
|
if len(data) < 6 {
|
||||||
|
ex = len(data)
|
||||||
|
}
|
||||||
|
if strings.ContainsRune(data[:ex], 0) {
|
||||||
|
return nil, errors.New("files cannot contain NULL bytes; probably using UTF-16; TOML files must be UTF-8")
|
||||||
|
}
|
||||||
|
|
||||||
|
p = &parser{
|
||||||
|
mapping: make(map[string]interface{}),
|
||||||
|
types: make(map[string]tomlType),
|
||||||
|
lx: lex(data),
|
||||||
|
ordered: make([]Key, 0),
|
||||||
|
implicits: make(map[string]bool),
|
||||||
|
}
|
||||||
|
for {
|
||||||
|
item := p.next()
|
||||||
|
if item.typ == itemEOF {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
p.topLevel(item)
|
||||||
|
}
|
||||||
|
|
||||||
|
return p, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *parser) panicf(format string, v ...interface{}) {
|
||||||
|
msg := fmt.Sprintf(format, v...)
|
||||||
|
panic(ParseError{
|
||||||
|
Message: msg,
|
||||||
|
Line: p.approxLine,
|
||||||
|
LastKey: p.current(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *parser) next() item {
|
||||||
|
it := p.lx.nextItem()
|
||||||
|
//fmt.Printf("ITEM %-18s line %-3d │ %q\n", it.typ, it.line, it.val)
|
||||||
|
if it.typ == itemError {
|
||||||
|
p.panicf("%s", it.val)
|
||||||
|
}
|
||||||
|
return it
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *parser) bug(format string, v ...interface{}) {
|
||||||
|
panic(fmt.Sprintf("BUG: "+format+"\n\n", v...))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *parser) expect(typ itemType) item {
|
||||||
|
it := p.next()
|
||||||
|
p.assertEqual(typ, it.typ)
|
||||||
|
return it
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *parser) assertEqual(expected, got itemType) {
|
||||||
|
if expected != got {
|
||||||
|
p.bug("Expected '%s' but got '%s'.", expected, got)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *parser) topLevel(item item) {
|
||||||
|
switch item.typ {
|
||||||
|
case itemCommentStart: // # ..
|
||||||
|
p.approxLine = item.line
|
||||||
|
p.expect(itemText)
|
||||||
|
case itemTableStart: // [ .. ]
|
||||||
|
name := p.next()
|
||||||
|
p.approxLine = name.line
|
||||||
|
|
||||||
|
var key Key
|
||||||
|
for ; name.typ != itemTableEnd && name.typ != itemEOF; name = p.next() {
|
||||||
|
key = append(key, p.keyString(name))
|
||||||
|
}
|
||||||
|
p.assertEqual(itemTableEnd, name.typ)
|
||||||
|
|
||||||
|
p.addContext(key, false)
|
||||||
|
p.setType("", tomlHash)
|
||||||
|
p.ordered = append(p.ordered, key)
|
||||||
|
case itemArrayTableStart: // [[ .. ]]
|
||||||
|
name := p.next()
|
||||||
|
p.approxLine = name.line
|
||||||
|
|
||||||
|
var key Key
|
||||||
|
for ; name.typ != itemArrayTableEnd && name.typ != itemEOF; name = p.next() {
|
||||||
|
key = append(key, p.keyString(name))
|
||||||
|
}
|
||||||
|
p.assertEqual(itemArrayTableEnd, name.typ)
|
||||||
|
|
||||||
|
p.addContext(key, true)
|
||||||
|
p.setType("", tomlArrayHash)
|
||||||
|
p.ordered = append(p.ordered, key)
|
||||||
|
case itemKeyStart: // key = ..
|
||||||
|
outerContext := p.context
|
||||||
|
/// Read all the key parts (e.g. 'a' and 'b' in 'a.b')
|
||||||
|
k := p.next()
|
||||||
|
p.approxLine = k.line
|
||||||
|
var key Key
|
||||||
|
for ; k.typ != itemKeyEnd && k.typ != itemEOF; k = p.next() {
|
||||||
|
key = append(key, p.keyString(k))
|
||||||
|
}
|
||||||
|
p.assertEqual(itemKeyEnd, k.typ)
|
||||||
|
|
||||||
|
/// The current key is the last part.
|
||||||
|
p.currentKey = key[len(key)-1]
|
||||||
|
|
||||||
|
/// All the other parts (if any) are the context; need to set each part
|
||||||
|
/// as implicit.
|
||||||
|
context := key[:len(key)-1]
|
||||||
|
for i := range context {
|
||||||
|
p.addImplicitContext(append(p.context, context[i:i+1]...))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set value.
|
||||||
|
val, typ := p.value(p.next(), false)
|
||||||
|
p.set(p.currentKey, val, typ)
|
||||||
|
p.ordered = append(p.ordered, p.context.add(p.currentKey))
|
||||||
|
|
||||||
|
/// Remove the context we added (preserving any context from [tbl] lines).
|
||||||
|
p.context = outerContext
|
||||||
|
p.currentKey = ""
|
||||||
|
default:
|
||||||
|
p.bug("Unexpected type at top level: %s", item.typ)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Gets a string for a key (or part of a key in a table name).
|
||||||
|
func (p *parser) keyString(it item) string {
|
||||||
|
switch it.typ {
|
||||||
|
case itemText:
|
||||||
|
return it.val
|
||||||
|
case itemString, itemMultilineString,
|
||||||
|
itemRawString, itemRawMultilineString:
|
||||||
|
s, _ := p.value(it, false)
|
||||||
|
return s.(string)
|
||||||
|
default:
|
||||||
|
p.bug("Unexpected key type: %s", it.typ)
|
||||||
|
}
|
||||||
|
panic("unreachable")
|
||||||
|
}
|
||||||
|
|
||||||
|
var datetimeRepl = strings.NewReplacer(
|
||||||
|
"z", "Z",
|
||||||
|
"t", "T",
|
||||||
|
" ", "T")
|
||||||
|
|
||||||
|
// value translates an expected value from the lexer into a Go value wrapped
|
||||||
|
// as an empty interface.
|
||||||
|
func (p *parser) value(it item, parentIsArray bool) (interface{}, tomlType) {
|
||||||
|
switch it.typ {
|
||||||
|
case itemString:
|
||||||
|
return p.replaceEscapes(it.val), p.typeOfPrimitive(it)
|
||||||
|
case itemMultilineString:
|
||||||
|
return p.replaceEscapes(stripFirstNewline(stripEscapedNewlines(it.val))), p.typeOfPrimitive(it)
|
||||||
|
case itemRawString:
|
||||||
|
return it.val, p.typeOfPrimitive(it)
|
||||||
|
case itemRawMultilineString:
|
||||||
|
return stripFirstNewline(it.val), p.typeOfPrimitive(it)
|
||||||
|
case itemInteger:
|
||||||
|
return p.valueInteger(it)
|
||||||
|
case itemFloat:
|
||||||
|
return p.valueFloat(it)
|
||||||
|
case itemBool:
|
||||||
|
switch it.val {
|
||||||
|
case "true":
|
||||||
|
return true, p.typeOfPrimitive(it)
|
||||||
|
case "false":
|
||||||
|
return false, p.typeOfPrimitive(it)
|
||||||
|
default:
|
||||||
|
p.bug("Expected boolean value, but got '%s'.", it.val)
|
||||||
|
}
|
||||||
|
case itemDatetime:
|
||||||
|
return p.valueDatetime(it)
|
||||||
|
case itemArray:
|
||||||
|
return p.valueArray(it)
|
||||||
|
case itemInlineTableStart:
|
||||||
|
return p.valueInlineTable(it, parentIsArray)
|
||||||
|
default:
|
||||||
|
p.bug("Unexpected value type: %s", it.typ)
|
||||||
|
}
|
||||||
|
panic("unreachable")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *parser) valueInteger(it item) (interface{}, tomlType) {
|
||||||
|
if !numUnderscoresOK(it.val) {
|
||||||
|
p.panicf("Invalid integer %q: underscores must be surrounded by digits", it.val)
|
||||||
|
}
|
||||||
|
if numHasLeadingZero(it.val) {
|
||||||
|
p.panicf("Invalid integer %q: cannot have leading zeroes", it.val)
|
||||||
|
}
|
||||||
|
|
||||||
|
num, err := strconv.ParseInt(it.val, 0, 64)
|
||||||
|
if err != nil {
|
||||||
|
// Distinguish integer values. Normally, it'd be a bug if the lexer
|
||||||
|
// provides an invalid integer, but it's possible that the number is
|
||||||
|
// out of range of valid values (which the lexer cannot determine).
|
||||||
|
// So mark the former as a bug but the latter as a legitimate user
|
||||||
|
// error.
|
||||||
|
if e, ok := err.(*strconv.NumError); ok && e.Err == strconv.ErrRange {
|
||||||
|
p.panicf("Integer '%s' is out of the range of 64-bit signed integers.", it.val)
|
||||||
|
} else {
|
||||||
|
p.bug("Expected integer value, but got '%s'.", it.val)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return num, p.typeOfPrimitive(it)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *parser) valueFloat(it item) (interface{}, tomlType) {
|
||||||
|
parts := strings.FieldsFunc(it.val, func(r rune) bool {
|
||||||
|
switch r {
|
||||||
|
case '.', 'e', 'E':
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
})
|
||||||
|
for _, part := range parts {
|
||||||
|
if !numUnderscoresOK(part) {
|
||||||
|
p.panicf("Invalid float %q: underscores must be surrounded by digits", it.val)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(parts) > 0 && numHasLeadingZero(parts[0]) {
|
||||||
|
p.panicf("Invalid float %q: cannot have leading zeroes", it.val)
|
||||||
|
}
|
||||||
|
if !numPeriodsOK(it.val) {
|
||||||
|
// As a special case, numbers like '123.' or '1.e2',
|
||||||
|
// which are valid as far as Go/strconv are concerned,
|
||||||
|
// must be rejected because TOML says that a fractional
|
||||||
|
// part consists of '.' followed by 1+ digits.
|
||||||
|
p.panicf("Invalid float %q: '.' must be followed by one or more digits", it.val)
|
||||||
|
}
|
||||||
|
val := strings.Replace(it.val, "_", "", -1)
|
||||||
|
if val == "+nan" || val == "-nan" { // Go doesn't support this, but TOML spec does.
|
||||||
|
val = "nan"
|
||||||
|
}
|
||||||
|
num, err := strconv.ParseFloat(val, 64)
|
||||||
|
if err != nil {
|
||||||
|
if e, ok := err.(*strconv.NumError); ok && e.Err == strconv.ErrRange {
|
||||||
|
p.panicf("Float '%s' is out of the range of 64-bit IEEE-754 floating-point numbers.", it.val)
|
||||||
|
} else {
|
||||||
|
p.panicf("Invalid float value: %q", it.val)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return num, p.typeOfPrimitive(it)
|
||||||
|
}
|
||||||
|
|
||||||
|
var dtTypes = []struct {
|
||||||
|
fmt string
|
||||||
|
zone *time.Location
|
||||||
|
}{
|
||||||
|
{time.RFC3339Nano, time.Local},
|
||||||
|
{"2006-01-02T15:04:05.999999999", internal.LocalDatetime},
|
||||||
|
{"2006-01-02", internal.LocalDate},
|
||||||
|
{"15:04:05.999999999", internal.LocalTime},
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *parser) valueDatetime(it item) (interface{}, tomlType) {
|
||||||
|
it.val = datetimeRepl.Replace(it.val)
|
||||||
|
var (
|
||||||
|
t time.Time
|
||||||
|
ok bool
|
||||||
|
err error
|
||||||
|
)
|
||||||
|
for _, dt := range dtTypes {
|
||||||
|
t, err = time.ParseInLocation(dt.fmt, it.val, dt.zone)
|
||||||
|
if err == nil {
|
||||||
|
ok = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !ok {
|
||||||
|
p.panicf("Invalid TOML Datetime: %q.", it.val)
|
||||||
|
}
|
||||||
|
return t, p.typeOfPrimitive(it)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *parser) valueArray(it item) (interface{}, tomlType) {
|
||||||
|
p.setType(p.currentKey, tomlArray)
|
||||||
|
|
||||||
|
// p.setType(p.currentKey, typ)
|
||||||
|
var (
|
||||||
|
array []interface{}
|
||||||
|
types []tomlType
|
||||||
|
)
|
||||||
|
for it = p.next(); it.typ != itemArrayEnd; it = p.next() {
|
||||||
|
if it.typ == itemCommentStart {
|
||||||
|
p.expect(itemText)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
val, typ := p.value(it, true)
|
||||||
|
array = append(array, val)
|
||||||
|
types = append(types, typ)
|
||||||
|
}
|
||||||
|
return array, tomlArray
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *parser) valueInlineTable(it item, parentIsArray bool) (interface{}, tomlType) {
|
||||||
|
var (
|
||||||
|
hash = make(map[string]interface{})
|
||||||
|
outerContext = p.context
|
||||||
|
outerKey = p.currentKey
|
||||||
|
)
|
||||||
|
|
||||||
|
p.context = append(p.context, p.currentKey)
|
||||||
|
prevContext := p.context
|
||||||
|
p.currentKey = ""
|
||||||
|
|
||||||
|
p.addImplicit(p.context)
|
||||||
|
p.addContext(p.context, parentIsArray)
|
||||||
|
|
||||||
|
/// Loop over all table key/value pairs.
|
||||||
|
for it := p.next(); it.typ != itemInlineTableEnd; it = p.next() {
|
||||||
|
if it.typ == itemCommentStart {
|
||||||
|
p.expect(itemText)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Read all key parts.
|
||||||
|
k := p.next()
|
||||||
|
p.approxLine = k.line
|
||||||
|
var key Key
|
||||||
|
for ; k.typ != itemKeyEnd && k.typ != itemEOF; k = p.next() {
|
||||||
|
key = append(key, p.keyString(k))
|
||||||
|
}
|
||||||
|
p.assertEqual(itemKeyEnd, k.typ)
|
||||||
|
|
||||||
|
/// The current key is the last part.
|
||||||
|
p.currentKey = key[len(key)-1]
|
||||||
|
|
||||||
|
/// All the other parts (if any) are the context; need to set each part
|
||||||
|
/// as implicit.
|
||||||
|
context := key[:len(key)-1]
|
||||||
|
for i := range context {
|
||||||
|
p.addImplicitContext(append(p.context, context[i:i+1]...))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set the value.
|
||||||
|
val, typ := p.value(p.next(), false)
|
||||||
|
p.set(p.currentKey, val, typ)
|
||||||
|
p.ordered = append(p.ordered, p.context.add(p.currentKey))
|
||||||
|
hash[p.currentKey] = val
|
||||||
|
|
||||||
|
/// Restore context.
|
||||||
|
p.context = prevContext
|
||||||
|
}
|
||||||
|
p.context = outerContext
|
||||||
|
p.currentKey = outerKey
|
||||||
|
return hash, tomlHash
|
||||||
|
}
|
||||||
|
|
||||||
|
// numHasLeadingZero checks if this number has leading zeroes, allowing for '0',
|
||||||
|
// +/- signs, and base prefixes.
|
||||||
|
func numHasLeadingZero(s string) bool {
|
||||||
|
if len(s) > 1 && s[0] == '0' && isDigit(rune(s[1])) { // >1 to allow "0" and isDigit to allow 0x
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if len(s) > 2 && (s[0] == '-' || s[0] == '+') && s[1] == '0' {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// numUnderscoresOK checks whether each underscore in s is surrounded by
|
||||||
|
// characters that are not underscores.
|
||||||
|
func numUnderscoresOK(s string) bool {
|
||||||
|
switch s {
|
||||||
|
case "nan", "+nan", "-nan", "inf", "-inf", "+inf":
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
accept := false
|
||||||
|
for _, r := range s {
|
||||||
|
if r == '_' {
|
||||||
|
if !accept {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// isHexadecimal is a superset of all the permissable characters
|
||||||
|
// surrounding an underscore.
|
||||||
|
accept = isHexadecimal(r)
|
||||||
|
}
|
||||||
|
return accept
|
||||||
|
}
|
||||||
|
|
||||||
|
// numPeriodsOK checks whether every period in s is followed by a digit.
|
||||||
|
func numPeriodsOK(s string) bool {
|
||||||
|
period := false
|
||||||
|
for _, r := range s {
|
||||||
|
if period && !isDigit(r) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
period = r == '.'
|
||||||
|
}
|
||||||
|
return !period
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set the current context of the parser, where the context is either a hash or
|
||||||
|
// an array of hashes, depending on the value of the `array` parameter.
|
||||||
|
//
|
||||||
|
// Establishing the context also makes sure that the key isn't a duplicate, and
|
||||||
|
// will create implicit hashes automatically.
|
||||||
|
func (p *parser) addContext(key Key, array bool) {
|
||||||
|
var ok bool
|
||||||
|
|
||||||
|
// Always start at the top level and drill down for our context.
|
||||||
|
hashContext := p.mapping
|
||||||
|
keyContext := make(Key, 0)
|
||||||
|
|
||||||
|
// We only need implicit hashes for key[0:-1]
|
||||||
|
for _, k := range key[0 : len(key)-1] {
|
||||||
|
_, ok = hashContext[k]
|
||||||
|
keyContext = append(keyContext, k)
|
||||||
|
|
||||||
|
// No key? Make an implicit hash and move on.
|
||||||
|
if !ok {
|
||||||
|
p.addImplicit(keyContext)
|
||||||
|
hashContext[k] = make(map[string]interface{})
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the hash context is actually an array of tables, then set
|
||||||
|
// the hash context to the last element in that array.
|
||||||
|
//
|
||||||
|
// Otherwise, it better be a table, since this MUST be a key group (by
|
||||||
|
// virtue of it not being the last element in a key).
|
||||||
|
switch t := hashContext[k].(type) {
|
||||||
|
case []map[string]interface{}:
|
||||||
|
hashContext = t[len(t)-1]
|
||||||
|
case map[string]interface{}:
|
||||||
|
hashContext = t
|
||||||
|
default:
|
||||||
|
p.panicf("Key '%s' was already created as a hash.", keyContext)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
p.context = keyContext
|
||||||
|
if array {
|
||||||
|
// If this is the first element for this array, then allocate a new
|
||||||
|
// list of tables for it.
|
||||||
|
k := key[len(key)-1]
|
||||||
|
if _, ok := hashContext[k]; !ok {
|
||||||
|
hashContext[k] = make([]map[string]interface{}, 0, 4)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add a new table. But make sure the key hasn't already been used
|
||||||
|
// for something else.
|
||||||
|
if hash, ok := hashContext[k].([]map[string]interface{}); ok {
|
||||||
|
hashContext[k] = append(hash, make(map[string]interface{}))
|
||||||
|
} else {
|
||||||
|
p.panicf("Key '%s' was already created and cannot be used as an array.", keyContext)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
p.setValue(key[len(key)-1], make(map[string]interface{}))
|
||||||
|
}
|
||||||
|
p.context = append(p.context, key[len(key)-1])
|
||||||
|
}
|
||||||
|
|
||||||
|
// set calls setValue and setType.
|
||||||
|
func (p *parser) set(key string, val interface{}, typ tomlType) {
|
||||||
|
p.setValue(p.currentKey, val)
|
||||||
|
p.setType(p.currentKey, typ)
|
||||||
|
}
|
||||||
|
|
||||||
|
// setValue sets the given key to the given value in the current context.
|
||||||
|
// It will make sure that the key hasn't already been defined, account for
|
||||||
|
// implicit key groups.
|
||||||
|
func (p *parser) setValue(key string, value interface{}) {
|
||||||
|
var (
|
||||||
|
tmpHash interface{}
|
||||||
|
ok bool
|
||||||
|
hash = p.mapping
|
||||||
|
keyContext Key
|
||||||
|
)
|
||||||
|
for _, k := range p.context {
|
||||||
|
keyContext = append(keyContext, k)
|
||||||
|
if tmpHash, ok = hash[k]; !ok {
|
||||||
|
p.bug("Context for key '%s' has not been established.", keyContext)
|
||||||
|
}
|
||||||
|
switch t := tmpHash.(type) {
|
||||||
|
case []map[string]interface{}:
|
||||||
|
// The context is a table of hashes. Pick the most recent table
|
||||||
|
// defined as the current hash.
|
||||||
|
hash = t[len(t)-1]
|
||||||
|
case map[string]interface{}:
|
||||||
|
hash = t
|
||||||
|
default:
|
||||||
|
p.panicf("Key '%s' has already been defined.", keyContext)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
keyContext = append(keyContext, key)
|
||||||
|
|
||||||
|
if _, ok := hash[key]; ok {
|
||||||
|
// Normally redefining keys isn't allowed, but the key could have been
|
||||||
|
// defined implicitly and it's allowed to be redefined concretely. (See
|
||||||
|
// the `valid/implicit-and-explicit-after.toml` in toml-test)
|
||||||
|
//
|
||||||
|
// But we have to make sure to stop marking it as an implicit. (So that
|
||||||
|
// another redefinition provokes an error.)
|
||||||
|
//
|
||||||
|
// Note that since it has already been defined (as a hash), we don't
|
||||||
|
// want to overwrite it. So our business is done.
|
||||||
|
if p.isArray(keyContext) {
|
||||||
|
p.removeImplicit(keyContext)
|
||||||
|
hash[key] = value
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if p.isImplicit(keyContext) {
|
||||||
|
p.removeImplicit(keyContext)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Otherwise, we have a concrete key trying to override a previous
|
||||||
|
// key, which is *always* wrong.
|
||||||
|
p.panicf("Key '%s' has already been defined.", keyContext)
|
||||||
|
}
|
||||||
|
|
||||||
|
hash[key] = value
|
||||||
|
}
|
||||||
|
|
||||||
|
// setType sets the type of a particular value at a given key.
|
||||||
|
// It should be called immediately AFTER setValue.
|
||||||
|
//
|
||||||
|
// Note that if `key` is empty, then the type given will be applied to the
|
||||||
|
// current context (which is either a table or an array of tables).
|
||||||
|
func (p *parser) setType(key string, typ tomlType) {
|
||||||
|
keyContext := make(Key, 0, len(p.context)+1)
|
||||||
|
for _, k := range p.context {
|
||||||
|
keyContext = append(keyContext, k)
|
||||||
|
}
|
||||||
|
if len(key) > 0 { // allow type setting for hashes
|
||||||
|
keyContext = append(keyContext, key)
|
||||||
|
}
|
||||||
|
p.types[keyContext.String()] = typ
|
||||||
|
}
|
||||||
|
|
||||||
|
// Implicit keys need to be created when tables are implied in "a.b.c.d = 1" and
|
||||||
|
// "[a.b.c]" (the "a", "b", and "c" hashes are never created explicitly).
|
||||||
|
func (p *parser) addImplicit(key Key) { p.implicits[key.String()] = true }
|
||||||
|
func (p *parser) removeImplicit(key Key) { p.implicits[key.String()] = false }
|
||||||
|
func (p *parser) isImplicit(key Key) bool { return p.implicits[key.String()] }
|
||||||
|
func (p *parser) isArray(key Key) bool { return p.types[key.String()] == tomlArray }
|
||||||
|
func (p *parser) addImplicitContext(key Key) {
|
||||||
|
p.addImplicit(key)
|
||||||
|
p.addContext(key, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
// current returns the full key name of the current context.
|
||||||
|
func (p *parser) current() string {
|
||||||
|
if len(p.currentKey) == 0 {
|
||||||
|
return p.context.String()
|
||||||
|
}
|
||||||
|
if len(p.context) == 0 {
|
||||||
|
return p.currentKey
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("%s.%s", p.context, p.currentKey)
|
||||||
|
}
|
||||||
|
|
||||||
|
func stripFirstNewline(s string) string {
|
||||||
|
if len(s) > 0 && s[0] == '\n' {
|
||||||
|
return s[1:]
|
||||||
|
}
|
||||||
|
if len(s) > 1 && s[0] == '\r' && s[1] == '\n' {
|
||||||
|
return s[2:]
|
||||||
|
}
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove newlines inside triple-quoted strings if a line ends with "\".
|
||||||
|
func stripEscapedNewlines(s string) string {
|
||||||
|
split := strings.Split(s, "\n")
|
||||||
|
if len(split) < 1 {
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
escNL := false // Keep track of the last non-blank line was escaped.
|
||||||
|
for i, line := range split {
|
||||||
|
line = strings.TrimRight(line, " \t\r")
|
||||||
|
|
||||||
|
if len(line) == 0 || line[len(line)-1] != '\\' {
|
||||||
|
split[i] = strings.TrimRight(split[i], "\r")
|
||||||
|
if !escNL && i != len(split)-1 {
|
||||||
|
split[i] += "\n"
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
escBS := true
|
||||||
|
for j := len(line) - 1; j >= 0 && line[j] == '\\'; j-- {
|
||||||
|
escBS = !escBS
|
||||||
|
}
|
||||||
|
if escNL {
|
||||||
|
line = strings.TrimLeft(line, " \t\r")
|
||||||
|
}
|
||||||
|
escNL = !escBS
|
||||||
|
|
||||||
|
if escBS {
|
||||||
|
split[i] += "\n"
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
split[i] = line[:len(line)-1] // Remove \
|
||||||
|
if len(split)-1 > i {
|
||||||
|
split[i+1] = strings.TrimLeft(split[i+1], " \t\r")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return strings.Join(split, "")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *parser) replaceEscapes(str string) string {
|
||||||
|
var replaced []rune
|
||||||
|
s := []byte(str)
|
||||||
|
r := 0
|
||||||
|
for r < len(s) {
|
||||||
|
if s[r] != '\\' {
|
||||||
|
c, size := utf8.DecodeRune(s[r:])
|
||||||
|
r += size
|
||||||
|
replaced = append(replaced, c)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
r += 1
|
||||||
|
if r >= len(s) {
|
||||||
|
p.bug("Escape sequence at end of string.")
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
switch s[r] {
|
||||||
|
default:
|
||||||
|
p.bug("Expected valid escape code after \\, but got %q.", s[r])
|
||||||
|
return ""
|
||||||
|
case ' ', '\t':
|
||||||
|
p.panicf("invalid escape: '\\%c'", s[r])
|
||||||
|
return ""
|
||||||
|
case 'b':
|
||||||
|
replaced = append(replaced, rune(0x0008))
|
||||||
|
r += 1
|
||||||
|
case 't':
|
||||||
|
replaced = append(replaced, rune(0x0009))
|
||||||
|
r += 1
|
||||||
|
case 'n':
|
||||||
|
replaced = append(replaced, rune(0x000A))
|
||||||
|
r += 1
|
||||||
|
case 'f':
|
||||||
|
replaced = append(replaced, rune(0x000C))
|
||||||
|
r += 1
|
||||||
|
case 'r':
|
||||||
|
replaced = append(replaced, rune(0x000D))
|
||||||
|
r += 1
|
||||||
|
case '"':
|
||||||
|
replaced = append(replaced, rune(0x0022))
|
||||||
|
r += 1
|
||||||
|
case '\\':
|
||||||
|
replaced = append(replaced, rune(0x005C))
|
||||||
|
r += 1
|
||||||
|
case 'u':
|
||||||
|
// At this point, we know we have a Unicode escape of the form
|
||||||
|
// `uXXXX` at [r, r+5). (Because the lexer guarantees this
|
||||||
|
// for us.)
|
||||||
|
escaped := p.asciiEscapeToUnicode(s[r+1 : r+5])
|
||||||
|
replaced = append(replaced, escaped)
|
||||||
|
r += 5
|
||||||
|
case 'U':
|
||||||
|
// At this point, we know we have a Unicode escape of the form
|
||||||
|
// `uXXXX` at [r, r+9). (Because the lexer guarantees this
|
||||||
|
// for us.)
|
||||||
|
escaped := p.asciiEscapeToUnicode(s[r+1 : r+9])
|
||||||
|
replaced = append(replaced, escaped)
|
||||||
|
r += 9
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return string(replaced)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *parser) asciiEscapeToUnicode(bs []byte) rune {
|
||||||
|
s := string(bs)
|
||||||
|
hex, err := strconv.ParseUint(strings.ToLower(s), 16, 32)
|
||||||
|
if err != nil {
|
||||||
|
p.bug("Could not parse '%s' as a hexadecimal number, but the "+
|
||||||
|
"lexer claims it's OK: %s", s, err)
|
||||||
|
}
|
||||||
|
if !utf8.ValidRune(rune(hex)) {
|
||||||
|
p.panicf("Escaped character '\\u%s' is not valid UTF-8.", s)
|
||||||
|
}
|
||||||
|
return rune(hex)
|
||||||
|
}
|
70
vendor/github.com/BurntSushi/toml/type_check.go
generated
vendored
Normal file
70
vendor/github.com/BurntSushi/toml/type_check.go
generated
vendored
Normal file
@ -0,0 +1,70 @@
|
|||||||
|
package toml
|
||||||
|
|
||||||
|
// tomlType represents any Go type that corresponds to a TOML type.
|
||||||
|
// While the first draft of the TOML spec has a simplistic type system that
|
||||||
|
// probably doesn't need this level of sophistication, we seem to be militating
|
||||||
|
// toward adding real composite types.
|
||||||
|
type tomlType interface {
|
||||||
|
typeString() string
|
||||||
|
}
|
||||||
|
|
||||||
|
// typeEqual accepts any two types and returns true if they are equal.
|
||||||
|
func typeEqual(t1, t2 tomlType) bool {
|
||||||
|
if t1 == nil || t2 == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return t1.typeString() == t2.typeString()
|
||||||
|
}
|
||||||
|
|
||||||
|
func typeIsHash(t tomlType) bool {
|
||||||
|
return typeEqual(t, tomlHash) || typeEqual(t, tomlArrayHash)
|
||||||
|
}
|
||||||
|
|
||||||
|
type tomlBaseType string
|
||||||
|
|
||||||
|
func (btype tomlBaseType) typeString() string {
|
||||||
|
return string(btype)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (btype tomlBaseType) String() string {
|
||||||
|
return btype.typeString()
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
tomlInteger tomlBaseType = "Integer"
|
||||||
|
tomlFloat tomlBaseType = "Float"
|
||||||
|
tomlDatetime tomlBaseType = "Datetime"
|
||||||
|
tomlString tomlBaseType = "String"
|
||||||
|
tomlBool tomlBaseType = "Bool"
|
||||||
|
tomlArray tomlBaseType = "Array"
|
||||||
|
tomlHash tomlBaseType = "Hash"
|
||||||
|
tomlArrayHash tomlBaseType = "ArrayHash"
|
||||||
|
)
|
||||||
|
|
||||||
|
// typeOfPrimitive returns a tomlType of any primitive value in TOML.
|
||||||
|
// Primitive values are: Integer, Float, Datetime, String and Bool.
|
||||||
|
//
|
||||||
|
// Passing a lexer item other than the following will cause a BUG message
|
||||||
|
// to occur: itemString, itemBool, itemInteger, itemFloat, itemDatetime.
|
||||||
|
func (p *parser) typeOfPrimitive(lexItem item) tomlType {
|
||||||
|
switch lexItem.typ {
|
||||||
|
case itemInteger:
|
||||||
|
return tomlInteger
|
||||||
|
case itemFloat:
|
||||||
|
return tomlFloat
|
||||||
|
case itemDatetime:
|
||||||
|
return tomlDatetime
|
||||||
|
case itemString:
|
||||||
|
return tomlString
|
||||||
|
case itemMultilineString:
|
||||||
|
return tomlString
|
||||||
|
case itemRawString:
|
||||||
|
return tomlString
|
||||||
|
case itemRawMultilineString:
|
||||||
|
return tomlString
|
||||||
|
case itemBool:
|
||||||
|
return tomlBool
|
||||||
|
}
|
||||||
|
p.bug("Cannot infer primitive type of lex item '%s'.", lexItem)
|
||||||
|
panic("unreachable")
|
||||||
|
}
|
242
vendor/github.com/BurntSushi/toml/type_fields.go
generated
vendored
Normal file
242
vendor/github.com/BurntSushi/toml/type_fields.go
generated
vendored
Normal file
@ -0,0 +1,242 @@
|
|||||||
|
package toml
|
||||||
|
|
||||||
|
// Struct field handling is adapted from code in encoding/json:
|
||||||
|
//
|
||||||
|
// 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 Go distribution.
|
||||||
|
|
||||||
|
import (
|
||||||
|
"reflect"
|
||||||
|
"sort"
|
||||||
|
"sync"
|
||||||
|
)
|
||||||
|
|
||||||
|
// A field represents a single field found in a struct.
|
||||||
|
type field struct {
|
||||||
|
name string // the name of the field (`toml` tag included)
|
||||||
|
tag bool // whether field has a `toml` tag
|
||||||
|
index []int // represents the depth of an anonymous field
|
||||||
|
typ reflect.Type // the type of the field
|
||||||
|
}
|
||||||
|
|
||||||
|
// byName sorts field by name, breaking ties with depth,
|
||||||
|
// then breaking ties with "name came from toml tag", then
|
||||||
|
// breaking ties with index sequence.
|
||||||
|
type byName []field
|
||||||
|
|
||||||
|
func (x byName) Len() int { return len(x) }
|
||||||
|
|
||||||
|
func (x byName) Swap(i, j int) { x[i], x[j] = x[j], x[i] }
|
||||||
|
|
||||||
|
func (x byName) Less(i, j int) bool {
|
||||||
|
if x[i].name != x[j].name {
|
||||||
|
return x[i].name < x[j].name
|
||||||
|
}
|
||||||
|
if len(x[i].index) != len(x[j].index) {
|
||||||
|
return len(x[i].index) < len(x[j].index)
|
||||||
|
}
|
||||||
|
if x[i].tag != x[j].tag {
|
||||||
|
return x[i].tag
|
||||||
|
}
|
||||||
|
return byIndex(x).Less(i, j)
|
||||||
|
}
|
||||||
|
|
||||||
|
// byIndex sorts field by index sequence.
|
||||||
|
type byIndex []field
|
||||||
|
|
||||||
|
func (x byIndex) Len() int { return len(x) }
|
||||||
|
|
||||||
|
func (x byIndex) Swap(i, j int) { x[i], x[j] = x[j], x[i] }
|
||||||
|
|
||||||
|
func (x byIndex) Less(i, j int) bool {
|
||||||
|
for k, xik := range x[i].index {
|
||||||
|
if k >= len(x[j].index) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if xik != x[j].index[k] {
|
||||||
|
return xik < x[j].index[k]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return len(x[i].index) < len(x[j].index)
|
||||||
|
}
|
||||||
|
|
||||||
|
// typeFields returns a list of fields that TOML should recognize for the given
|
||||||
|
// type. The algorithm is breadth-first search over the set of structs to
|
||||||
|
// include - the top struct and then any reachable anonymous structs.
|
||||||
|
func typeFields(t reflect.Type) []field {
|
||||||
|
// Anonymous fields to explore at the current level and the next.
|
||||||
|
current := []field{}
|
||||||
|
next := []field{{typ: t}}
|
||||||
|
|
||||||
|
// Count of queued names for current level and the next.
|
||||||
|
count := map[reflect.Type]int{}
|
||||||
|
nextCount := map[reflect.Type]int{}
|
||||||
|
|
||||||
|
// Types already visited at an earlier level.
|
||||||
|
visited := map[reflect.Type]bool{}
|
||||||
|
|
||||||
|
// Fields found.
|
||||||
|
var fields []field
|
||||||
|
|
||||||
|
for len(next) > 0 {
|
||||||
|
current, next = next, current[:0]
|
||||||
|
count, nextCount = nextCount, map[reflect.Type]int{}
|
||||||
|
|
||||||
|
for _, f := range current {
|
||||||
|
if visited[f.typ] {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
visited[f.typ] = true
|
||||||
|
|
||||||
|
// Scan f.typ for fields to include.
|
||||||
|
for i := 0; i < f.typ.NumField(); i++ {
|
||||||
|
sf := f.typ.Field(i)
|
||||||
|
if sf.PkgPath != "" && !sf.Anonymous { // unexported
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
opts := getOptions(sf.Tag)
|
||||||
|
if opts.skip {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
index := make([]int, len(f.index)+1)
|
||||||
|
copy(index, f.index)
|
||||||
|
index[len(f.index)] = i
|
||||||
|
|
||||||
|
ft := sf.Type
|
||||||
|
if ft.Name() == "" && ft.Kind() == reflect.Ptr {
|
||||||
|
// Follow pointer.
|
||||||
|
ft = ft.Elem()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Record found field and index sequence.
|
||||||
|
if opts.name != "" || !sf.Anonymous || ft.Kind() != reflect.Struct {
|
||||||
|
tagged := opts.name != ""
|
||||||
|
name := opts.name
|
||||||
|
if name == "" {
|
||||||
|
name = sf.Name
|
||||||
|
}
|
||||||
|
fields = append(fields, field{name, tagged, index, ft})
|
||||||
|
if count[f.typ] > 1 {
|
||||||
|
// If there were multiple instances, add a second,
|
||||||
|
// so that the annihilation code will see a duplicate.
|
||||||
|
// It only cares about the distinction between 1 or 2,
|
||||||
|
// so don't bother generating any more copies.
|
||||||
|
fields = append(fields, fields[len(fields)-1])
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Record new anonymous struct to explore in next round.
|
||||||
|
nextCount[ft]++
|
||||||
|
if nextCount[ft] == 1 {
|
||||||
|
f := field{name: ft.Name(), index: index, typ: ft}
|
||||||
|
next = append(next, f)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sort.Sort(byName(fields))
|
||||||
|
|
||||||
|
// Delete all fields that are hidden by the Go rules for embedded fields,
|
||||||
|
// except that fields with TOML tags are promoted.
|
||||||
|
|
||||||
|
// The fields are sorted in primary order of name, secondary order
|
||||||
|
// of field index length. Loop over names; for each name, delete
|
||||||
|
// hidden fields by choosing the one dominant field that survives.
|
||||||
|
out := fields[:0]
|
||||||
|
for advance, i := 0, 0; i < len(fields); i += advance {
|
||||||
|
// One iteration per name.
|
||||||
|
// Find the sequence of fields with the name of this first field.
|
||||||
|
fi := fields[i]
|
||||||
|
name := fi.name
|
||||||
|
for advance = 1; i+advance < len(fields); advance++ {
|
||||||
|
fj := fields[i+advance]
|
||||||
|
if fj.name != name {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if advance == 1 { // Only one field with this name
|
||||||
|
out = append(out, fi)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
dominant, ok := dominantField(fields[i : i+advance])
|
||||||
|
if ok {
|
||||||
|
out = append(out, dominant)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fields = out
|
||||||
|
sort.Sort(byIndex(fields))
|
||||||
|
|
||||||
|
return fields
|
||||||
|
}
|
||||||
|
|
||||||
|
// dominantField looks through the fields, all of which are known to
|
||||||
|
// have the same name, to find the single field that dominates the
|
||||||
|
// others using Go's embedding rules, modified by the presence of
|
||||||
|
// TOML tags. If there are multiple top-level fields, the boolean
|
||||||
|
// will be false: This condition is an error in Go and we skip all
|
||||||
|
// the fields.
|
||||||
|
func dominantField(fields []field) (field, bool) {
|
||||||
|
// The fields are sorted in increasing index-length order. The winner
|
||||||
|
// must therefore be one with the shortest index length. Drop all
|
||||||
|
// longer entries, which is easy: just truncate the slice.
|
||||||
|
length := len(fields[0].index)
|
||||||
|
tagged := -1 // Index of first tagged field.
|
||||||
|
for i, f := range fields {
|
||||||
|
if len(f.index) > length {
|
||||||
|
fields = fields[:i]
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if f.tag {
|
||||||
|
if tagged >= 0 {
|
||||||
|
// Multiple tagged fields at the same level: conflict.
|
||||||
|
// Return no field.
|
||||||
|
return field{}, false
|
||||||
|
}
|
||||||
|
tagged = i
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if tagged >= 0 {
|
||||||
|
return fields[tagged], true
|
||||||
|
}
|
||||||
|
// All remaining fields have the same length. If there's more than one,
|
||||||
|
// we have a conflict (two fields named "X" at the same level) and we
|
||||||
|
// return no field.
|
||||||
|
if len(fields) > 1 {
|
||||||
|
return field{}, false
|
||||||
|
}
|
||||||
|
return fields[0], true
|
||||||
|
}
|
||||||
|
|
||||||
|
var fieldCache struct {
|
||||||
|
sync.RWMutex
|
||||||
|
m map[reflect.Type][]field
|
||||||
|
}
|
||||||
|
|
||||||
|
// cachedTypeFields is like typeFields but uses a cache to avoid repeated work.
|
||||||
|
func cachedTypeFields(t reflect.Type) []field {
|
||||||
|
fieldCache.RLock()
|
||||||
|
f := fieldCache.m[t]
|
||||||
|
fieldCache.RUnlock()
|
||||||
|
if f != nil {
|
||||||
|
return f
|
||||||
|
}
|
||||||
|
|
||||||
|
// Compute fields without lock.
|
||||||
|
// Might duplicate effort but won't hold other computations back.
|
||||||
|
f = typeFields(t)
|
||||||
|
if f == nil {
|
||||||
|
f = []field{}
|
||||||
|
}
|
||||||
|
|
||||||
|
fieldCache.Lock()
|
||||||
|
if fieldCache.m == nil {
|
||||||
|
fieldCache.m = map[reflect.Type][]field{}
|
||||||
|
}
|
||||||
|
fieldCache.m[t] = f
|
||||||
|
fieldCache.Unlock()
|
||||||
|
return f
|
||||||
|
}
|
6
vendor/github.com/lib/pq/.gitignore
generated
vendored
Normal file
6
vendor/github.com/lib/pq/.gitignore
generated
vendored
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
.db
|
||||||
|
*.test
|
||||||
|
*~
|
||||||
|
*.swp
|
||||||
|
.idea
|
||||||
|
.vscode
|
8
vendor/github.com/lib/pq/LICENSE.md
generated
vendored
Normal file
8
vendor/github.com/lib/pq/LICENSE.md
generated
vendored
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
Copyright (c) 2011-2013, 'pq' Contributors
|
||||||
|
Portions Copyright (C) 2011 Blake Mizerany
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
36
vendor/github.com/lib/pq/README.md
generated
vendored
Normal file
36
vendor/github.com/lib/pq/README.md
generated
vendored
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
# pq - A pure Go postgres driver for Go's database/sql package
|
||||||
|
|
||||||
|
[![GoDoc](https://godoc.org/github.com/lib/pq?status.svg)](https://pkg.go.dev/github.com/lib/pq?tab=doc)
|
||||||
|
|
||||||
|
## Install
|
||||||
|
|
||||||
|
go get github.com/lib/pq
|
||||||
|
|
||||||
|
## Features
|
||||||
|
|
||||||
|
* SSL
|
||||||
|
* Handles bad connections for `database/sql`
|
||||||
|
* Scan `time.Time` correctly (i.e. `timestamp[tz]`, `time[tz]`, `date`)
|
||||||
|
* Scan binary blobs correctly (i.e. `bytea`)
|
||||||
|
* Package for `hstore` support
|
||||||
|
* COPY FROM support
|
||||||
|
* pq.ParseURL for converting urls to connection strings for sql.Open.
|
||||||
|
* Many libpq compatible environment variables
|
||||||
|
* Unix socket support
|
||||||
|
* Notifications: `LISTEN`/`NOTIFY`
|
||||||
|
* pgpass support
|
||||||
|
* GSS (Kerberos) auth
|
||||||
|
|
||||||
|
## Tests
|
||||||
|
|
||||||
|
`go test` is used for testing. See [TESTS.md](TESTS.md) for more details.
|
||||||
|
|
||||||
|
## Status
|
||||||
|
|
||||||
|
This package is currently in maintenance mode, which means:
|
||||||
|
1. It generally does not accept new features.
|
||||||
|
2. It does accept bug fixes and version compatability changes provided by the community.
|
||||||
|
3. Maintainers usually do not resolve reported issues.
|
||||||
|
4. Community members are encouraged to help each other with reported issues.
|
||||||
|
|
||||||
|
For users that require new features or reliable resolution of reported bugs, we recommend using [pgx](https://github.com/jackc/pgx) which is under active development.
|
33
vendor/github.com/lib/pq/TESTS.md
generated
vendored
Normal file
33
vendor/github.com/lib/pq/TESTS.md
generated
vendored
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
# Tests
|
||||||
|
|
||||||
|
## Running Tests
|
||||||
|
|
||||||
|
`go test` is used for testing. A running PostgreSQL
|
||||||
|
server is required, with the ability to log in. The
|
||||||
|
database to connect to test with is "pqgotest," on
|
||||||
|
"localhost" but these can be overridden using [environment
|
||||||
|
variables](https://www.postgresql.org/docs/9.3/static/libpq-envars.html).
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
PGHOST=/run/postgresql go test
|
||||||
|
|
||||||
|
## Benchmarks
|
||||||
|
|
||||||
|
A benchmark suite can be run as part of the tests:
|
||||||
|
|
||||||
|
go test -bench .
|
||||||
|
|
||||||
|
## Example setup (Docker)
|
||||||
|
|
||||||
|
Run a postgres container:
|
||||||
|
|
||||||
|
```
|
||||||
|
docker run --expose 5432:5432 postgres
|
||||||
|
```
|
||||||
|
|
||||||
|
Run tests:
|
||||||
|
|
||||||
|
```
|
||||||
|
PGHOST=localhost PGPORT=5432 PGUSER=postgres PGSSLMODE=disable PGDATABASE=postgres go test
|
||||||
|
```
|
895
vendor/github.com/lib/pq/array.go
generated
vendored
Normal file
895
vendor/github.com/lib/pq/array.go
generated
vendored
Normal file
@ -0,0 +1,895 @@
|
|||||||
|
package pq
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"database/sql"
|
||||||
|
"database/sql/driver"
|
||||||
|
"encoding/hex"
|
||||||
|
"fmt"
|
||||||
|
"reflect"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
var typeByteSlice = reflect.TypeOf([]byte{})
|
||||||
|
var typeDriverValuer = reflect.TypeOf((*driver.Valuer)(nil)).Elem()
|
||||||
|
var typeSQLScanner = reflect.TypeOf((*sql.Scanner)(nil)).Elem()
|
||||||
|
|
||||||
|
// Array returns the optimal driver.Valuer and sql.Scanner for an array or
|
||||||
|
// slice of any dimension.
|
||||||
|
//
|
||||||
|
// For example:
|
||||||
|
// db.Query(`SELECT * FROM t WHERE id = ANY($1)`, pq.Array([]int{235, 401}))
|
||||||
|
//
|
||||||
|
// var x []sql.NullInt64
|
||||||
|
// db.QueryRow(`SELECT ARRAY[235, 401]`).Scan(pq.Array(&x))
|
||||||
|
//
|
||||||
|
// Scanning multi-dimensional arrays is not supported. Arrays where the lower
|
||||||
|
// bound is not one (such as `[0:0]={1}') are not supported.
|
||||||
|
func Array(a interface{}) interface {
|
||||||
|
driver.Valuer
|
||||||
|
sql.Scanner
|
||||||
|
} {
|
||||||
|
switch a := a.(type) {
|
||||||
|
case []bool:
|
||||||
|
return (*BoolArray)(&a)
|
||||||
|
case []float64:
|
||||||
|
return (*Float64Array)(&a)
|
||||||
|
case []float32:
|
||||||
|
return (*Float32Array)(&a)
|
||||||
|
case []int64:
|
||||||
|
return (*Int64Array)(&a)
|
||||||
|
case []int32:
|
||||||
|
return (*Int32Array)(&a)
|
||||||
|
case []string:
|
||||||
|
return (*StringArray)(&a)
|
||||||
|
case [][]byte:
|
||||||
|
return (*ByteaArray)(&a)
|
||||||
|
|
||||||
|
case *[]bool:
|
||||||
|
return (*BoolArray)(a)
|
||||||
|
case *[]float64:
|
||||||
|
return (*Float64Array)(a)
|
||||||
|
case *[]float32:
|
||||||
|
return (*Float32Array)(a)
|
||||||
|
case *[]int64:
|
||||||
|
return (*Int64Array)(a)
|
||||||
|
case *[]int32:
|
||||||
|
return (*Int32Array)(a)
|
||||||
|
case *[]string:
|
||||||
|
return (*StringArray)(a)
|
||||||
|
case *[][]byte:
|
||||||
|
return (*ByteaArray)(a)
|
||||||
|
}
|
||||||
|
|
||||||
|
return GenericArray{a}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ArrayDelimiter may be optionally implemented by driver.Valuer or sql.Scanner
|
||||||
|
// to override the array delimiter used by GenericArray.
|
||||||
|
type ArrayDelimiter interface {
|
||||||
|
// ArrayDelimiter returns the delimiter character(s) for this element's type.
|
||||||
|
ArrayDelimiter() string
|
||||||
|
}
|
||||||
|
|
||||||
|
// BoolArray represents a one-dimensional array of the PostgreSQL boolean type.
|
||||||
|
type BoolArray []bool
|
||||||
|
|
||||||
|
// Scan implements the sql.Scanner interface.
|
||||||
|
func (a *BoolArray) Scan(src interface{}) error {
|
||||||
|
switch src := src.(type) {
|
||||||
|
case []byte:
|
||||||
|
return a.scanBytes(src)
|
||||||
|
case string:
|
||||||
|
return a.scanBytes([]byte(src))
|
||||||
|
case nil:
|
||||||
|
*a = nil
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return fmt.Errorf("pq: cannot convert %T to BoolArray", src)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *BoolArray) scanBytes(src []byte) error {
|
||||||
|
elems, err := scanLinearArray(src, []byte{','}, "BoolArray")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if *a != nil && len(elems) == 0 {
|
||||||
|
*a = (*a)[:0]
|
||||||
|
} else {
|
||||||
|
b := make(BoolArray, len(elems))
|
||||||
|
for i, v := range elems {
|
||||||
|
if len(v) != 1 {
|
||||||
|
return fmt.Errorf("pq: could not parse boolean array index %d: invalid boolean %q", i, v)
|
||||||
|
}
|
||||||
|
switch v[0] {
|
||||||
|
case 't':
|
||||||
|
b[i] = true
|
||||||
|
case 'f':
|
||||||
|
b[i] = false
|
||||||
|
default:
|
||||||
|
return fmt.Errorf("pq: could not parse boolean array index %d: invalid boolean %q", i, v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
*a = b
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Value implements the driver.Valuer interface.
|
||||||
|
func (a BoolArray) Value() (driver.Value, error) {
|
||||||
|
if a == nil {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if n := len(a); n > 0 {
|
||||||
|
// There will be exactly two curly brackets, N bytes of values,
|
||||||
|
// and N-1 bytes of delimiters.
|
||||||
|
b := make([]byte, 1+2*n)
|
||||||
|
|
||||||
|
for i := 0; i < n; i++ {
|
||||||
|
b[2*i] = ','
|
||||||
|
if a[i] {
|
||||||
|
b[1+2*i] = 't'
|
||||||
|
} else {
|
||||||
|
b[1+2*i] = 'f'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
b[0] = '{'
|
||||||
|
b[2*n] = '}'
|
||||||
|
|
||||||
|
return string(b), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return "{}", nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ByteaArray represents a one-dimensional array of the PostgreSQL bytea type.
|
||||||
|
type ByteaArray [][]byte
|
||||||
|
|
||||||
|
// Scan implements the sql.Scanner interface.
|
||||||
|
func (a *ByteaArray) Scan(src interface{}) error {
|
||||||
|
switch src := src.(type) {
|
||||||
|
case []byte:
|
||||||
|
return a.scanBytes(src)
|
||||||
|
case string:
|
||||||
|
return a.scanBytes([]byte(src))
|
||||||
|
case nil:
|
||||||
|
*a = nil
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return fmt.Errorf("pq: cannot convert %T to ByteaArray", src)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *ByteaArray) scanBytes(src []byte) error {
|
||||||
|
elems, err := scanLinearArray(src, []byte{','}, "ByteaArray")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if *a != nil && len(elems) == 0 {
|
||||||
|
*a = (*a)[:0]
|
||||||
|
} else {
|
||||||
|
b := make(ByteaArray, len(elems))
|
||||||
|
for i, v := range elems {
|
||||||
|
b[i], err = parseBytea(v)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("could not parse bytea array index %d: %s", i, err.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
*a = b
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Value implements the driver.Valuer interface. It uses the "hex" format which
|
||||||
|
// is only supported on PostgreSQL 9.0 or newer.
|
||||||
|
func (a ByteaArray) Value() (driver.Value, error) {
|
||||||
|
if a == nil {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if n := len(a); n > 0 {
|
||||||
|
// There will be at least two curly brackets, 2*N bytes of quotes,
|
||||||
|
// 3*N bytes of hex formatting, and N-1 bytes of delimiters.
|
||||||
|
size := 1 + 6*n
|
||||||
|
for _, x := range a {
|
||||||
|
size += hex.EncodedLen(len(x))
|
||||||
|
}
|
||||||
|
|
||||||
|
b := make([]byte, size)
|
||||||
|
|
||||||
|
for i, s := 0, b; i < n; i++ {
|
||||||
|
o := copy(s, `,"\\x`)
|
||||||
|
o += hex.Encode(s[o:], a[i])
|
||||||
|
s[o] = '"'
|
||||||
|
s = s[o+1:]
|
||||||
|
}
|
||||||
|
|
||||||
|
b[0] = '{'
|
||||||
|
b[size-1] = '}'
|
||||||
|
|
||||||
|
return string(b), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return "{}", nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Float64Array represents a one-dimensional array of the PostgreSQL double
|
||||||
|
// precision type.
|
||||||
|
type Float64Array []float64
|
||||||
|
|
||||||
|
// Scan implements the sql.Scanner interface.
|
||||||
|
func (a *Float64Array) Scan(src interface{}) error {
|
||||||
|
switch src := src.(type) {
|
||||||
|
case []byte:
|
||||||
|
return a.scanBytes(src)
|
||||||
|
case string:
|
||||||
|
return a.scanBytes([]byte(src))
|
||||||
|
case nil:
|
||||||
|
*a = nil
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return fmt.Errorf("pq: cannot convert %T to Float64Array", src)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *Float64Array) scanBytes(src []byte) error {
|
||||||
|
elems, err := scanLinearArray(src, []byte{','}, "Float64Array")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if *a != nil && len(elems) == 0 {
|
||||||
|
*a = (*a)[:0]
|
||||||
|
} else {
|
||||||
|
b := make(Float64Array, len(elems))
|
||||||
|
for i, v := range elems {
|
||||||
|
if b[i], err = strconv.ParseFloat(string(v), 64); err != nil {
|
||||||
|
return fmt.Errorf("pq: parsing array element index %d: %v", i, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
*a = b
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Value implements the driver.Valuer interface.
|
||||||
|
func (a Float64Array) Value() (driver.Value, error) {
|
||||||
|
if a == nil {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if n := len(a); n > 0 {
|
||||||
|
// There will be at least two curly brackets, N bytes of values,
|
||||||
|
// and N-1 bytes of delimiters.
|
||||||
|
b := make([]byte, 1, 1+2*n)
|
||||||
|
b[0] = '{'
|
||||||
|
|
||||||
|
b = strconv.AppendFloat(b, a[0], 'f', -1, 64)
|
||||||
|
for i := 1; i < n; i++ {
|
||||||
|
b = append(b, ',')
|
||||||
|
b = strconv.AppendFloat(b, a[i], 'f', -1, 64)
|
||||||
|
}
|
||||||
|
|
||||||
|
return string(append(b, '}')), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return "{}", nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Float32Array represents a one-dimensional array of the PostgreSQL double
|
||||||
|
// precision type.
|
||||||
|
type Float32Array []float32
|
||||||
|
|
||||||
|
// Scan implements the sql.Scanner interface.
|
||||||
|
func (a *Float32Array) Scan(src interface{}) error {
|
||||||
|
switch src := src.(type) {
|
||||||
|
case []byte:
|
||||||
|
return a.scanBytes(src)
|
||||||
|
case string:
|
||||||
|
return a.scanBytes([]byte(src))
|
||||||
|
case nil:
|
||||||
|
*a = nil
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return fmt.Errorf("pq: cannot convert %T to Float32Array", src)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *Float32Array) scanBytes(src []byte) error {
|
||||||
|
elems, err := scanLinearArray(src, []byte{','}, "Float32Array")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if *a != nil && len(elems) == 0 {
|
||||||
|
*a = (*a)[:0]
|
||||||
|
} else {
|
||||||
|
b := make(Float32Array, len(elems))
|
||||||
|
for i, v := range elems {
|
||||||
|
var x float64
|
||||||
|
if x, err = strconv.ParseFloat(string(v), 32); err != nil {
|
||||||
|
return fmt.Errorf("pq: parsing array element index %d: %v", i, err)
|
||||||
|
}
|
||||||
|
b[i] = float32(x)
|
||||||
|
}
|
||||||
|
*a = b
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Value implements the driver.Valuer interface.
|
||||||
|
func (a Float32Array) Value() (driver.Value, error) {
|
||||||
|
if a == nil {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if n := len(a); n > 0 {
|
||||||
|
// There will be at least two curly brackets, N bytes of values,
|
||||||
|
// and N-1 bytes of delimiters.
|
||||||
|
b := make([]byte, 1, 1+2*n)
|
||||||
|
b[0] = '{'
|
||||||
|
|
||||||
|
b = strconv.AppendFloat(b, float64(a[0]), 'f', -1, 32)
|
||||||
|
for i := 1; i < n; i++ {
|
||||||
|
b = append(b, ',')
|
||||||
|
b = strconv.AppendFloat(b, float64(a[i]), 'f', -1, 32)
|
||||||
|
}
|
||||||
|
|
||||||
|
return string(append(b, '}')), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return "{}", nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GenericArray implements the driver.Valuer and sql.Scanner interfaces for
|
||||||
|
// an array or slice of any dimension.
|
||||||
|
type GenericArray struct{ A interface{} }
|
||||||
|
|
||||||
|
func (GenericArray) evaluateDestination(rt reflect.Type) (reflect.Type, func([]byte, reflect.Value) error, string) {
|
||||||
|
var assign func([]byte, reflect.Value) error
|
||||||
|
var del = ","
|
||||||
|
|
||||||
|
// TODO calculate the assign function for other types
|
||||||
|
// TODO repeat this section on the element type of arrays or slices (multidimensional)
|
||||||
|
{
|
||||||
|
if reflect.PtrTo(rt).Implements(typeSQLScanner) {
|
||||||
|
// dest is always addressable because it is an element of a slice.
|
||||||
|
assign = func(src []byte, dest reflect.Value) (err error) {
|
||||||
|
ss := dest.Addr().Interface().(sql.Scanner)
|
||||||
|
if src == nil {
|
||||||
|
err = ss.Scan(nil)
|
||||||
|
} else {
|
||||||
|
err = ss.Scan(src)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
goto FoundType
|
||||||
|
}
|
||||||
|
|
||||||
|
assign = func([]byte, reflect.Value) error {
|
||||||
|
return fmt.Errorf("pq: scanning to %s is not implemented; only sql.Scanner", rt)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
FoundType:
|
||||||
|
|
||||||
|
if ad, ok := reflect.Zero(rt).Interface().(ArrayDelimiter); ok {
|
||||||
|
del = ad.ArrayDelimiter()
|
||||||
|
}
|
||||||
|
|
||||||
|
return rt, assign, del
|
||||||
|
}
|
||||||
|
|
||||||
|
// Scan implements the sql.Scanner interface.
|
||||||
|
func (a GenericArray) Scan(src interface{}) error {
|
||||||
|
dpv := reflect.ValueOf(a.A)
|
||||||
|
switch {
|
||||||
|
case dpv.Kind() != reflect.Ptr:
|
||||||
|
return fmt.Errorf("pq: destination %T is not a pointer to array or slice", a.A)
|
||||||
|
case dpv.IsNil():
|
||||||
|
return fmt.Errorf("pq: destination %T is nil", a.A)
|
||||||
|
}
|
||||||
|
|
||||||
|
dv := dpv.Elem()
|
||||||
|
switch dv.Kind() {
|
||||||
|
case reflect.Slice:
|
||||||
|
case reflect.Array:
|
||||||
|
default:
|
||||||
|
return fmt.Errorf("pq: destination %T is not a pointer to array or slice", a.A)
|
||||||
|
}
|
||||||
|
|
||||||
|
switch src := src.(type) {
|
||||||
|
case []byte:
|
||||||
|
return a.scanBytes(src, dv)
|
||||||
|
case string:
|
||||||
|
return a.scanBytes([]byte(src), dv)
|
||||||
|
case nil:
|
||||||
|
if dv.Kind() == reflect.Slice {
|
||||||
|
dv.Set(reflect.Zero(dv.Type()))
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return fmt.Errorf("pq: cannot convert %T to %s", src, dv.Type())
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a GenericArray) scanBytes(src []byte, dv reflect.Value) error {
|
||||||
|
dtype, assign, del := a.evaluateDestination(dv.Type().Elem())
|
||||||
|
dims, elems, err := parseArray(src, []byte(del))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO allow multidimensional
|
||||||
|
|
||||||
|
if len(dims) > 1 {
|
||||||
|
return fmt.Errorf("pq: scanning from multidimensional ARRAY%s is not implemented",
|
||||||
|
strings.Replace(fmt.Sprint(dims), " ", "][", -1))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Treat a zero-dimensional array like an array with a single dimension of zero.
|
||||||
|
if len(dims) == 0 {
|
||||||
|
dims = append(dims, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, rt := 0, dv.Type(); i < len(dims); i, rt = i+1, rt.Elem() {
|
||||||
|
switch rt.Kind() {
|
||||||
|
case reflect.Slice:
|
||||||
|
case reflect.Array:
|
||||||
|
if rt.Len() != dims[i] {
|
||||||
|
return fmt.Errorf("pq: cannot convert ARRAY%s to %s",
|
||||||
|
strings.Replace(fmt.Sprint(dims), " ", "][", -1), dv.Type())
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
// TODO handle multidimensional
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
values := reflect.MakeSlice(reflect.SliceOf(dtype), len(elems), len(elems))
|
||||||
|
for i, e := range elems {
|
||||||
|
if err := assign(e, values.Index(i)); err != nil {
|
||||||
|
return fmt.Errorf("pq: parsing array element index %d: %v", i, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO handle multidimensional
|
||||||
|
|
||||||
|
switch dv.Kind() {
|
||||||
|
case reflect.Slice:
|
||||||
|
dv.Set(values.Slice(0, dims[0]))
|
||||||
|
case reflect.Array:
|
||||||
|
for i := 0; i < dims[0]; i++ {
|
||||||
|
dv.Index(i).Set(values.Index(i))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Value implements the driver.Valuer interface.
|
||||||
|
func (a GenericArray) Value() (driver.Value, error) {
|
||||||
|
if a.A == nil {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
rv := reflect.ValueOf(a.A)
|
||||||
|
|
||||||
|
switch rv.Kind() {
|
||||||
|
case reflect.Slice:
|
||||||
|
if rv.IsNil() {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
case reflect.Array:
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf("pq: Unable to convert %T to array", a.A)
|
||||||
|
}
|
||||||
|
|
||||||
|
if n := rv.Len(); n > 0 {
|
||||||
|
// There will be at least two curly brackets, N bytes of values,
|
||||||
|
// and N-1 bytes of delimiters.
|
||||||
|
b := make([]byte, 0, 1+2*n)
|
||||||
|
|
||||||
|
b, _, err := appendArray(b, rv, n)
|
||||||
|
return string(b), err
|
||||||
|
}
|
||||||
|
|
||||||
|
return "{}", nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Int64Array represents a one-dimensional array of the PostgreSQL integer types.
|
||||||
|
type Int64Array []int64
|
||||||
|
|
||||||
|
// Scan implements the sql.Scanner interface.
|
||||||
|
func (a *Int64Array) Scan(src interface{}) error {
|
||||||
|
switch src := src.(type) {
|
||||||
|
case []byte:
|
||||||
|
return a.scanBytes(src)
|
||||||
|
case string:
|
||||||
|
return a.scanBytes([]byte(src))
|
||||||
|
case nil:
|
||||||
|
*a = nil
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return fmt.Errorf("pq: cannot convert %T to Int64Array", src)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *Int64Array) scanBytes(src []byte) error {
|
||||||
|
elems, err := scanLinearArray(src, []byte{','}, "Int64Array")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if *a != nil && len(elems) == 0 {
|
||||||
|
*a = (*a)[:0]
|
||||||
|
} else {
|
||||||
|
b := make(Int64Array, len(elems))
|
||||||
|
for i, v := range elems {
|
||||||
|
if b[i], err = strconv.ParseInt(string(v), 10, 64); err != nil {
|
||||||
|
return fmt.Errorf("pq: parsing array element index %d: %v", i, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
*a = b
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Value implements the driver.Valuer interface.
|
||||||
|
func (a Int64Array) Value() (driver.Value, error) {
|
||||||
|
if a == nil {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if n := len(a); n > 0 {
|
||||||
|
// There will be at least two curly brackets, N bytes of values,
|
||||||
|
// and N-1 bytes of delimiters.
|
||||||
|
b := make([]byte, 1, 1+2*n)
|
||||||
|
b[0] = '{'
|
||||||
|
|
||||||
|
b = strconv.AppendInt(b, a[0], 10)
|
||||||
|
for i := 1; i < n; i++ {
|
||||||
|
b = append(b, ',')
|
||||||
|
b = strconv.AppendInt(b, a[i], 10)
|
||||||
|
}
|
||||||
|
|
||||||
|
return string(append(b, '}')), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return "{}", nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Int32Array represents a one-dimensional array of the PostgreSQL integer types.
|
||||||
|
type Int32Array []int32
|
||||||
|
|
||||||
|
// Scan implements the sql.Scanner interface.
|
||||||
|
func (a *Int32Array) Scan(src interface{}) error {
|
||||||
|
switch src := src.(type) {
|
||||||
|
case []byte:
|
||||||
|
return a.scanBytes(src)
|
||||||
|
case string:
|
||||||
|
return a.scanBytes([]byte(src))
|
||||||
|
case nil:
|
||||||
|
*a = nil
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return fmt.Errorf("pq: cannot convert %T to Int32Array", src)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *Int32Array) scanBytes(src []byte) error {
|
||||||
|
elems, err := scanLinearArray(src, []byte{','}, "Int32Array")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if *a != nil && len(elems) == 0 {
|
||||||
|
*a = (*a)[:0]
|
||||||
|
} else {
|
||||||
|
b := make(Int32Array, len(elems))
|
||||||
|
for i, v := range elems {
|
||||||
|
x, err := strconv.ParseInt(string(v), 10, 32)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("pq: parsing array element index %d: %v", i, err)
|
||||||
|
}
|
||||||
|
b[i] = int32(x)
|
||||||
|
}
|
||||||
|
*a = b
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Value implements the driver.Valuer interface.
|
||||||
|
func (a Int32Array) Value() (driver.Value, error) {
|
||||||
|
if a == nil {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if n := len(a); n > 0 {
|
||||||
|
// There will be at least two curly brackets, N bytes of values,
|
||||||
|
// and N-1 bytes of delimiters.
|
||||||
|
b := make([]byte, 1, 1+2*n)
|
||||||
|
b[0] = '{'
|
||||||
|
|
||||||
|
b = strconv.AppendInt(b, int64(a[0]), 10)
|
||||||
|
for i := 1; i < n; i++ {
|
||||||
|
b = append(b, ',')
|
||||||
|
b = strconv.AppendInt(b, int64(a[i]), 10)
|
||||||
|
}
|
||||||
|
|
||||||
|
return string(append(b, '}')), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return "{}", nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// StringArray represents a one-dimensional array of the PostgreSQL character types.
|
||||||
|
type StringArray []string
|
||||||
|
|
||||||
|
// Scan implements the sql.Scanner interface.
|
||||||
|
func (a *StringArray) Scan(src interface{}) error {
|
||||||
|
switch src := src.(type) {
|
||||||
|
case []byte:
|
||||||
|
return a.scanBytes(src)
|
||||||
|
case string:
|
||||||
|
return a.scanBytes([]byte(src))
|
||||||
|
case nil:
|
||||||
|
*a = nil
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return fmt.Errorf("pq: cannot convert %T to StringArray", src)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *StringArray) scanBytes(src []byte) error {
|
||||||
|
elems, err := scanLinearArray(src, []byte{','}, "StringArray")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if *a != nil && len(elems) == 0 {
|
||||||
|
*a = (*a)[:0]
|
||||||
|
} else {
|
||||||
|
b := make(StringArray, len(elems))
|
||||||
|
for i, v := range elems {
|
||||||
|
if b[i] = string(v); v == nil {
|
||||||
|
return fmt.Errorf("pq: parsing array element index %d: cannot convert nil to string", i)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
*a = b
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Value implements the driver.Valuer interface.
|
||||||
|
func (a StringArray) Value() (driver.Value, error) {
|
||||||
|
if a == nil {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if n := len(a); n > 0 {
|
||||||
|
// There will be at least two curly brackets, 2*N bytes of quotes,
|
||||||
|
// and N-1 bytes of delimiters.
|
||||||
|
b := make([]byte, 1, 1+3*n)
|
||||||
|
b[0] = '{'
|
||||||
|
|
||||||
|
b = appendArrayQuotedBytes(b, []byte(a[0]))
|
||||||
|
for i := 1; i < n; i++ {
|
||||||
|
b = append(b, ',')
|
||||||
|
b = appendArrayQuotedBytes(b, []byte(a[i]))
|
||||||
|
}
|
||||||
|
|
||||||
|
return string(append(b, '}')), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return "{}", nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// appendArray appends rv to the buffer, returning the extended buffer and
|
||||||
|
// the delimiter used between elements.
|
||||||
|
//
|
||||||
|
// It panics when n <= 0 or rv's Kind is not reflect.Array nor reflect.Slice.
|
||||||
|
func appendArray(b []byte, rv reflect.Value, n int) ([]byte, string, error) {
|
||||||
|
var del string
|
||||||
|
var err error
|
||||||
|
|
||||||
|
b = append(b, '{')
|
||||||
|
|
||||||
|
if b, del, err = appendArrayElement(b, rv.Index(0)); err != nil {
|
||||||
|
return b, del, err
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := 1; i < n; i++ {
|
||||||
|
b = append(b, del...)
|
||||||
|
if b, del, err = appendArrayElement(b, rv.Index(i)); err != nil {
|
||||||
|
return b, del, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return append(b, '}'), del, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// appendArrayElement appends rv to the buffer, returning the extended buffer
|
||||||
|
// and the delimiter to use before the next element.
|
||||||
|
//
|
||||||
|
// When rv's Kind is neither reflect.Array nor reflect.Slice, it is converted
|
||||||
|
// using driver.DefaultParameterConverter and the resulting []byte or string
|
||||||
|
// is double-quoted.
|
||||||
|
//
|
||||||
|
// See http://www.postgresql.org/docs/current/static/arrays.html#ARRAYS-IO
|
||||||
|
func appendArrayElement(b []byte, rv reflect.Value) ([]byte, string, error) {
|
||||||
|
if k := rv.Kind(); k == reflect.Array || k == reflect.Slice {
|
||||||
|
if t := rv.Type(); t != typeByteSlice && !t.Implements(typeDriverValuer) {
|
||||||
|
if n := rv.Len(); n > 0 {
|
||||||
|
return appendArray(b, rv, n)
|
||||||
|
}
|
||||||
|
|
||||||
|
return b, "", nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var del = ","
|
||||||
|
var err error
|
||||||
|
var iv interface{} = rv.Interface()
|
||||||
|
|
||||||
|
if ad, ok := iv.(ArrayDelimiter); ok {
|
||||||
|
del = ad.ArrayDelimiter()
|
||||||
|
}
|
||||||
|
|
||||||
|
if iv, err = driver.DefaultParameterConverter.ConvertValue(iv); err != nil {
|
||||||
|
return b, del, err
|
||||||
|
}
|
||||||
|
|
||||||
|
switch v := iv.(type) {
|
||||||
|
case nil:
|
||||||
|
return append(b, "NULL"...), del, nil
|
||||||
|
case []byte:
|
||||||
|
return appendArrayQuotedBytes(b, v), del, nil
|
||||||
|
case string:
|
||||||
|
return appendArrayQuotedBytes(b, []byte(v)), del, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
b, err = appendValue(b, iv)
|
||||||
|
return b, del, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func appendArrayQuotedBytes(b, v []byte) []byte {
|
||||||
|
b = append(b, '"')
|
||||||
|
for {
|
||||||
|
i := bytes.IndexAny(v, `"\`)
|
||||||
|
if i < 0 {
|
||||||
|
b = append(b, v...)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if i > 0 {
|
||||||
|
b = append(b, v[:i]...)
|
||||||
|
}
|
||||||
|
b = append(b, '\\', v[i])
|
||||||
|
v = v[i+1:]
|
||||||
|
}
|
||||||
|
return append(b, '"')
|
||||||
|
}
|
||||||
|
|
||||||
|
func appendValue(b []byte, v driver.Value) ([]byte, error) {
|
||||||
|
return append(b, encode(nil, v, 0)...), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// parseArray extracts the dimensions and elements of an array represented in
|
||||||
|
// text format. Only representations emitted by the backend are supported.
|
||||||
|
// Notably, whitespace around brackets and delimiters is significant, and NULL
|
||||||
|
// is case-sensitive.
|
||||||
|
//
|
||||||
|
// See http://www.postgresql.org/docs/current/static/arrays.html#ARRAYS-IO
|
||||||
|
func parseArray(src, del []byte) (dims []int, elems [][]byte, err error) {
|
||||||
|
var depth, i int
|
||||||
|
|
||||||
|
if len(src) < 1 || src[0] != '{' {
|
||||||
|
return nil, nil, fmt.Errorf("pq: unable to parse array; expected %q at offset %d", '{', 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
Open:
|
||||||
|
for i < len(src) {
|
||||||
|
switch src[i] {
|
||||||
|
case '{':
|
||||||
|
depth++
|
||||||
|
i++
|
||||||
|
case '}':
|
||||||
|
elems = make([][]byte, 0)
|
||||||
|
goto Close
|
||||||
|
default:
|
||||||
|
break Open
|
||||||
|
}
|
||||||
|
}
|
||||||
|
dims = make([]int, i)
|
||||||
|
|
||||||
|
Element:
|
||||||
|
for i < len(src) {
|
||||||
|
switch src[i] {
|
||||||
|
case '{':
|
||||||
|
if depth == len(dims) {
|
||||||
|
break Element
|
||||||
|
}
|
||||||
|
depth++
|
||||||
|
dims[depth-1] = 0
|
||||||
|
i++
|
||||||
|
case '"':
|
||||||
|
var elem = []byte{}
|
||||||
|
var escape bool
|
||||||
|
for i++; i < len(src); i++ {
|
||||||
|
if escape {
|
||||||
|
elem = append(elem, src[i])
|
||||||
|
escape = false
|
||||||
|
} else {
|
||||||
|
switch src[i] {
|
||||||
|
default:
|
||||||
|
elem = append(elem, src[i])
|
||||||
|
case '\\':
|
||||||
|
escape = true
|
||||||
|
case '"':
|
||||||
|
elems = append(elems, elem)
|
||||||
|
i++
|
||||||
|
break Element
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
for start := i; i < len(src); i++ {
|
||||||
|
if bytes.HasPrefix(src[i:], del) || src[i] == '}' {
|
||||||
|
elem := src[start:i]
|
||||||
|
if len(elem) == 0 {
|
||||||
|
return nil, nil, fmt.Errorf("pq: unable to parse array; unexpected %q at offset %d", src[i], i)
|
||||||
|
}
|
||||||
|
if bytes.Equal(elem, []byte("NULL")) {
|
||||||
|
elem = nil
|
||||||
|
}
|
||||||
|
elems = append(elems, elem)
|
||||||
|
break Element
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for i < len(src) {
|
||||||
|
if bytes.HasPrefix(src[i:], del) && depth > 0 {
|
||||||
|
dims[depth-1]++
|
||||||
|
i += len(del)
|
||||||
|
goto Element
|
||||||
|
} else if src[i] == '}' && depth > 0 {
|
||||||
|
dims[depth-1]++
|
||||||
|
depth--
|
||||||
|
i++
|
||||||
|
} else {
|
||||||
|
return nil, nil, fmt.Errorf("pq: unable to parse array; unexpected %q at offset %d", src[i], i)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Close:
|
||||||
|
for i < len(src) {
|
||||||
|
if src[i] == '}' && depth > 0 {
|
||||||
|
depth--
|
||||||
|
i++
|
||||||
|
} else {
|
||||||
|
return nil, nil, fmt.Errorf("pq: unable to parse array; unexpected %q at offset %d", src[i], i)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if depth > 0 {
|
||||||
|
err = fmt.Errorf("pq: unable to parse array; expected %q at offset %d", '}', i)
|
||||||
|
}
|
||||||
|
if err == nil {
|
||||||
|
for _, d := range dims {
|
||||||
|
if (len(elems) % d) != 0 {
|
||||||
|
err = fmt.Errorf("pq: multidimensional arrays must have elements with matching dimensions")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func scanLinearArray(src, del []byte, typ string) (elems [][]byte, err error) {
|
||||||
|
dims, elems, err := parseArray(src, del)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if len(dims) > 1 {
|
||||||
|
return nil, fmt.Errorf("pq: cannot convert ARRAY%s to %s", strings.Replace(fmt.Sprint(dims), " ", "][", -1), typ)
|
||||||
|
}
|
||||||
|
return elems, err
|
||||||
|
}
|
91
vendor/github.com/lib/pq/buf.go
generated
vendored
Normal file
91
vendor/github.com/lib/pq/buf.go
generated
vendored
Normal file
@ -0,0 +1,91 @@
|
|||||||
|
package pq
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/binary"
|
||||||
|
|
||||||
|
"github.com/lib/pq/oid"
|
||||||
|
)
|
||||||
|
|
||||||
|
type readBuf []byte
|
||||||
|
|
||||||
|
func (b *readBuf) int32() (n int) {
|
||||||
|
n = int(int32(binary.BigEndian.Uint32(*b)))
|
||||||
|
*b = (*b)[4:]
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *readBuf) oid() (n oid.Oid) {
|
||||||
|
n = oid.Oid(binary.BigEndian.Uint32(*b))
|
||||||
|
*b = (*b)[4:]
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// N.B: this is actually an unsigned 16-bit integer, unlike int32
|
||||||
|
func (b *readBuf) int16() (n int) {
|
||||||
|
n = int(binary.BigEndian.Uint16(*b))
|
||||||
|
*b = (*b)[2:]
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *readBuf) string() string {
|
||||||
|
i := bytes.IndexByte(*b, 0)
|
||||||
|
if i < 0 {
|
||||||
|
errorf("invalid message format; expected string terminator")
|
||||||
|
}
|
||||||
|
s := (*b)[:i]
|
||||||
|
*b = (*b)[i+1:]
|
||||||
|
return string(s)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *readBuf) next(n int) (v []byte) {
|
||||||
|
v = (*b)[:n]
|
||||||
|
*b = (*b)[n:]
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *readBuf) byte() byte {
|
||||||
|
return b.next(1)[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
type writeBuf struct {
|
||||||
|
buf []byte
|
||||||
|
pos int
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *writeBuf) int32(n int) {
|
||||||
|
x := make([]byte, 4)
|
||||||
|
binary.BigEndian.PutUint32(x, uint32(n))
|
||||||
|
b.buf = append(b.buf, x...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *writeBuf) int16(n int) {
|
||||||
|
x := make([]byte, 2)
|
||||||
|
binary.BigEndian.PutUint16(x, uint16(n))
|
||||||
|
b.buf = append(b.buf, x...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *writeBuf) string(s string) {
|
||||||
|
b.buf = append(append(b.buf, s...), '\000')
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *writeBuf) byte(c byte) {
|
||||||
|
b.buf = append(b.buf, c)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *writeBuf) bytes(v []byte) {
|
||||||
|
b.buf = append(b.buf, v...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *writeBuf) wrap() []byte {
|
||||||
|
p := b.buf[b.pos:]
|
||||||
|
binary.BigEndian.PutUint32(p, uint32(len(p)))
|
||||||
|
return b.buf
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *writeBuf) next(c byte) {
|
||||||
|
p := b.buf[b.pos:]
|
||||||
|
binary.BigEndian.PutUint32(p, uint32(len(p)))
|
||||||
|
b.pos = len(b.buf) + 1
|
||||||
|
b.buf = append(b.buf, c, 0, 0, 0, 0)
|
||||||
|
}
|
2060
vendor/github.com/lib/pq/conn.go
generated
vendored
Normal file
2060
vendor/github.com/lib/pq/conn.go
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
247
vendor/github.com/lib/pq/conn_go18.go
generated
vendored
Normal file
247
vendor/github.com/lib/pq/conn_go18.go
generated
vendored
Normal file
@ -0,0 +1,247 @@
|
|||||||
|
package pq
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"database/sql"
|
||||||
|
"database/sql/driver"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
watchCancelDialContextTimeout = time.Second * 10
|
||||||
|
)
|
||||||
|
|
||||||
|
// Implement the "QueryerContext" interface
|
||||||
|
func (cn *conn) QueryContext(ctx context.Context, query string, args []driver.NamedValue) (driver.Rows, error) {
|
||||||
|
list := make([]driver.Value, len(args))
|
||||||
|
for i, nv := range args {
|
||||||
|
list[i] = nv.Value
|
||||||
|
}
|
||||||
|
finish := cn.watchCancel(ctx)
|
||||||
|
r, err := cn.query(query, list)
|
||||||
|
if err != nil {
|
||||||
|
if finish != nil {
|
||||||
|
finish()
|
||||||
|
}
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
r.finish = finish
|
||||||
|
return r, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Implement the "ExecerContext" interface
|
||||||
|
func (cn *conn) ExecContext(ctx context.Context, query string, args []driver.NamedValue) (driver.Result, error) {
|
||||||
|
list := make([]driver.Value, len(args))
|
||||||
|
for i, nv := range args {
|
||||||
|
list[i] = nv.Value
|
||||||
|
}
|
||||||
|
|
||||||
|
if finish := cn.watchCancel(ctx); finish != nil {
|
||||||
|
defer finish()
|
||||||
|
}
|
||||||
|
|
||||||
|
return cn.Exec(query, list)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Implement the "ConnPrepareContext" interface
|
||||||
|
func (cn *conn) PrepareContext(ctx context.Context, query string) (driver.Stmt, error) {
|
||||||
|
if finish := cn.watchCancel(ctx); finish != nil {
|
||||||
|
defer finish()
|
||||||
|
}
|
||||||
|
return cn.Prepare(query)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Implement the "ConnBeginTx" interface
|
||||||
|
func (cn *conn) BeginTx(ctx context.Context, opts driver.TxOptions) (driver.Tx, error) {
|
||||||
|
var mode string
|
||||||
|
|
||||||
|
switch sql.IsolationLevel(opts.Isolation) {
|
||||||
|
case sql.LevelDefault:
|
||||||
|
// Don't touch mode: use the server's default
|
||||||
|
case sql.LevelReadUncommitted:
|
||||||
|
mode = " ISOLATION LEVEL READ UNCOMMITTED"
|
||||||
|
case sql.LevelReadCommitted:
|
||||||
|
mode = " ISOLATION LEVEL READ COMMITTED"
|
||||||
|
case sql.LevelRepeatableRead:
|
||||||
|
mode = " ISOLATION LEVEL REPEATABLE READ"
|
||||||
|
case sql.LevelSerializable:
|
||||||
|
mode = " ISOLATION LEVEL SERIALIZABLE"
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf("pq: isolation level not supported: %d", opts.Isolation)
|
||||||
|
}
|
||||||
|
|
||||||
|
if opts.ReadOnly {
|
||||||
|
mode += " READ ONLY"
|
||||||
|
} else {
|
||||||
|
mode += " READ WRITE"
|
||||||
|
}
|
||||||
|
|
||||||
|
tx, err := cn.begin(mode)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
cn.txnFinish = cn.watchCancel(ctx)
|
||||||
|
return tx, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cn *conn) Ping(ctx context.Context) error {
|
||||||
|
if finish := cn.watchCancel(ctx); finish != nil {
|
||||||
|
defer finish()
|
||||||
|
}
|
||||||
|
rows, err := cn.simpleQuery(";")
|
||||||
|
if err != nil {
|
||||||
|
return driver.ErrBadConn // https://golang.org/pkg/database/sql/driver/#Pinger
|
||||||
|
}
|
||||||
|
rows.Close()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cn *conn) watchCancel(ctx context.Context) func() {
|
||||||
|
if done := ctx.Done(); done != nil {
|
||||||
|
finished := make(chan struct{}, 1)
|
||||||
|
go func() {
|
||||||
|
select {
|
||||||
|
case <-done:
|
||||||
|
select {
|
||||||
|
case finished <- struct{}{}:
|
||||||
|
default:
|
||||||
|
// We raced with the finish func, let the next query handle this with the
|
||||||
|
// context.
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set the connection state to bad so it does not get reused.
|
||||||
|
cn.err.set(ctx.Err())
|
||||||
|
|
||||||
|
// At this point the function level context is canceled,
|
||||||
|
// so it must not be used for the additional network
|
||||||
|
// request to cancel the query.
|
||||||
|
// Create a new context to pass into the dial.
|
||||||
|
ctxCancel, cancel := context.WithTimeout(context.Background(), watchCancelDialContextTimeout)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
_ = cn.cancel(ctxCancel)
|
||||||
|
case <-finished:
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
return func() {
|
||||||
|
select {
|
||||||
|
case <-finished:
|
||||||
|
cn.err.set(ctx.Err())
|
||||||
|
cn.Close()
|
||||||
|
case finished <- struct{}{}:
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cn *conn) cancel(ctx context.Context) error {
|
||||||
|
// Create a new values map (copy). This makes sure the connection created
|
||||||
|
// in this method cannot write to the same underlying data, which could
|
||||||
|
// cause a concurrent map write panic. This is necessary because cancel
|
||||||
|
// is called from a goroutine in watchCancel.
|
||||||
|
o := make(values)
|
||||||
|
for k, v := range cn.opts {
|
||||||
|
o[k] = v
|
||||||
|
}
|
||||||
|
|
||||||
|
c, err := dial(ctx, cn.dialer, o)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer c.Close()
|
||||||
|
|
||||||
|
{
|
||||||
|
can := conn{
|
||||||
|
c: c,
|
||||||
|
}
|
||||||
|
err = can.ssl(o)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
w := can.writeBuf(0)
|
||||||
|
w.int32(80877102) // cancel request code
|
||||||
|
w.int32(cn.processID)
|
||||||
|
w.int32(cn.secretKey)
|
||||||
|
|
||||||
|
if err := can.sendStartupPacket(w); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read until EOF to ensure that the server received the cancel.
|
||||||
|
{
|
||||||
|
_, err := io.Copy(ioutil.Discard, c)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Implement the "StmtQueryContext" interface
|
||||||
|
func (st *stmt) QueryContext(ctx context.Context, args []driver.NamedValue) (driver.Rows, error) {
|
||||||
|
list := make([]driver.Value, len(args))
|
||||||
|
for i, nv := range args {
|
||||||
|
list[i] = nv.Value
|
||||||
|
}
|
||||||
|
finish := st.watchCancel(ctx)
|
||||||
|
r, err := st.query(list)
|
||||||
|
if err != nil {
|
||||||
|
if finish != nil {
|
||||||
|
finish()
|
||||||
|
}
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
r.finish = finish
|
||||||
|
return r, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Implement the "StmtExecContext" interface
|
||||||
|
func (st *stmt) ExecContext(ctx context.Context, args []driver.NamedValue) (driver.Result, error) {
|
||||||
|
list := make([]driver.Value, len(args))
|
||||||
|
for i, nv := range args {
|
||||||
|
list[i] = nv.Value
|
||||||
|
}
|
||||||
|
|
||||||
|
if finish := st.watchCancel(ctx); finish != nil {
|
||||||
|
defer finish()
|
||||||
|
}
|
||||||
|
|
||||||
|
return st.Exec(list)
|
||||||
|
}
|
||||||
|
|
||||||
|
// watchCancel is implemented on stmt in order to not mark the parent conn as bad
|
||||||
|
func (st *stmt) watchCancel(ctx context.Context) func() {
|
||||||
|
if done := ctx.Done(); done != nil {
|
||||||
|
finished := make(chan struct{})
|
||||||
|
go func() {
|
||||||
|
select {
|
||||||
|
case <-done:
|
||||||
|
// At this point the function level context is canceled,
|
||||||
|
// so it must not be used for the additional network
|
||||||
|
// request to cancel the query.
|
||||||
|
// Create a new context to pass into the dial.
|
||||||
|
ctxCancel, cancel := context.WithTimeout(context.Background(), watchCancelDialContextTimeout)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
_ = st.cancel(ctxCancel)
|
||||||
|
finished <- struct{}{}
|
||||||
|
case <-finished:
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
return func() {
|
||||||
|
select {
|
||||||
|
case <-finished:
|
||||||
|
case finished <- struct{}{}:
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (st *stmt) cancel(ctx context.Context) error {
|
||||||
|
return st.cn.cancel(ctx)
|
||||||
|
}
|
115
vendor/github.com/lib/pq/connector.go
generated
vendored
Normal file
115
vendor/github.com/lib/pq/connector.go
generated
vendored
Normal file
@ -0,0 +1,115 @@
|
|||||||
|
package pq
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"database/sql/driver"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Connector represents a fixed configuration for the pq driver with a given
|
||||||
|
// name. Connector satisfies the database/sql/driver Connector interface and
|
||||||
|
// can be used to create any number of DB Conn's via the database/sql OpenDB
|
||||||
|
// function.
|
||||||
|
//
|
||||||
|
// See https://golang.org/pkg/database/sql/driver/#Connector.
|
||||||
|
// See https://golang.org/pkg/database/sql/#OpenDB.
|
||||||
|
type Connector struct {
|
||||||
|
opts values
|
||||||
|
dialer Dialer
|
||||||
|
}
|
||||||
|
|
||||||
|
// Connect returns a connection to the database using the fixed configuration
|
||||||
|
// of this Connector. Context is not used.
|
||||||
|
func (c *Connector) Connect(ctx context.Context) (driver.Conn, error) {
|
||||||
|
return c.open(ctx)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Driver returns the underlying driver of this Connector.
|
||||||
|
func (c *Connector) Driver() driver.Driver {
|
||||||
|
return &Driver{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewConnector returns a connector for the pq driver in a fixed configuration
|
||||||
|
// with the given dsn. The returned connector can be used to create any number
|
||||||
|
// of equivalent Conn's. The returned connector is intended to be used with
|
||||||
|
// database/sql.OpenDB.
|
||||||
|
//
|
||||||
|
// See https://golang.org/pkg/database/sql/driver/#Connector.
|
||||||
|
// See https://golang.org/pkg/database/sql/#OpenDB.
|
||||||
|
func NewConnector(dsn string) (*Connector, error) {
|
||||||
|
var err error
|
||||||
|
o := make(values)
|
||||||
|
|
||||||
|
// A number of defaults are applied here, in this order:
|
||||||
|
//
|
||||||
|
// * Very low precedence defaults applied in every situation
|
||||||
|
// * Environment variables
|
||||||
|
// * Explicitly passed connection information
|
||||||
|
o["host"] = "localhost"
|
||||||
|
o["port"] = "5432"
|
||||||
|
// N.B.: Extra float digits should be set to 3, but that breaks
|
||||||
|
// Postgres 8.4 and older, where the max is 2.
|
||||||
|
o["extra_float_digits"] = "2"
|
||||||
|
for k, v := range parseEnviron(os.Environ()) {
|
||||||
|
o[k] = v
|
||||||
|
}
|
||||||
|
|
||||||
|
if strings.HasPrefix(dsn, "postgres://") || strings.HasPrefix(dsn, "postgresql://") {
|
||||||
|
dsn, err = ParseURL(dsn)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := parseOpts(dsn, o); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Use the "fallback" application name if necessary
|
||||||
|
if fallback, ok := o["fallback_application_name"]; ok {
|
||||||
|
if _, ok := o["application_name"]; !ok {
|
||||||
|
o["application_name"] = fallback
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// We can't work with any client_encoding other than UTF-8 currently.
|
||||||
|
// However, we have historically allowed the user to set it to UTF-8
|
||||||
|
// explicitly, and there's no reason to break such programs, so allow that.
|
||||||
|
// Note that the "options" setting could also set client_encoding, but
|
||||||
|
// parsing its value is not worth it. Instead, we always explicitly send
|
||||||
|
// client_encoding as a separate run-time parameter, which should override
|
||||||
|
// anything set in options.
|
||||||
|
if enc, ok := o["client_encoding"]; ok && !isUTF8(enc) {
|
||||||
|
return nil, errors.New("client_encoding must be absent or 'UTF8'")
|
||||||
|
}
|
||||||
|
o["client_encoding"] = "UTF8"
|
||||||
|
// DateStyle needs a similar treatment.
|
||||||
|
if datestyle, ok := o["datestyle"]; ok {
|
||||||
|
if datestyle != "ISO, MDY" {
|
||||||
|
return nil, fmt.Errorf("setting datestyle must be absent or %v; got %v", "ISO, MDY", datestyle)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
o["datestyle"] = "ISO, MDY"
|
||||||
|
}
|
||||||
|
|
||||||
|
// If a user is not provided by any other means, the last
|
||||||
|
// resort is to use the current operating system provided user
|
||||||
|
// name.
|
||||||
|
if _, ok := o["user"]; !ok {
|
||||||
|
u, err := userCurrent()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
o["user"] = u
|
||||||
|
}
|
||||||
|
|
||||||
|
// SSL is not necessary or supported over UNIX domain sockets
|
||||||
|
if network, _ := network(o); network == "unix" {
|
||||||
|
o["sslmode"] = "disable"
|
||||||
|
}
|
||||||
|
|
||||||
|
return &Connector{opts: o, dialer: defaultDialer{}}, nil
|
||||||
|
}
|
303
vendor/github.com/lib/pq/copy.go
generated
vendored
Normal file
303
vendor/github.com/lib/pq/copy.go
generated
vendored
Normal file
@ -0,0 +1,303 @@
|
|||||||
|
package pq
|
||||||
|
|
||||||
|
import (
|
||||||
|
"database/sql/driver"
|
||||||
|
"encoding/binary"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"sync"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
errCopyInClosed = errors.New("pq: copyin statement has already been closed")
|
||||||
|
errBinaryCopyNotSupported = errors.New("pq: only text format supported for COPY")
|
||||||
|
errCopyToNotSupported = errors.New("pq: COPY TO is not supported")
|
||||||
|
errCopyNotSupportedOutsideTxn = errors.New("pq: COPY is only allowed inside a transaction")
|
||||||
|
errCopyInProgress = errors.New("pq: COPY in progress")
|
||||||
|
)
|
||||||
|
|
||||||
|
// CopyIn creates a COPY FROM statement which can be prepared with
|
||||||
|
// Tx.Prepare(). The target table should be visible in search_path.
|
||||||
|
func CopyIn(table string, columns ...string) string {
|
||||||
|
stmt := "COPY " + QuoteIdentifier(table) + " ("
|
||||||
|
for i, col := range columns {
|
||||||
|
if i != 0 {
|
||||||
|
stmt += ", "
|
||||||
|
}
|
||||||
|
stmt += QuoteIdentifier(col)
|
||||||
|
}
|
||||||
|
stmt += ") FROM STDIN"
|
||||||
|
return stmt
|
||||||
|
}
|
||||||
|
|
||||||
|
// CopyInSchema creates a COPY FROM statement which can be prepared with
|
||||||
|
// Tx.Prepare().
|
||||||
|
func CopyInSchema(schema, table string, columns ...string) string {
|
||||||
|
stmt := "COPY " + QuoteIdentifier(schema) + "." + QuoteIdentifier(table) + " ("
|
||||||
|
for i, col := range columns {
|
||||||
|
if i != 0 {
|
||||||
|
stmt += ", "
|
||||||
|
}
|
||||||
|
stmt += QuoteIdentifier(col)
|
||||||
|
}
|
||||||
|
stmt += ") FROM STDIN"
|
||||||
|
return stmt
|
||||||
|
}
|
||||||
|
|
||||||
|
type copyin struct {
|
||||||
|
cn *conn
|
||||||
|
buffer []byte
|
||||||
|
rowData chan []byte
|
||||||
|
done chan bool
|
||||||
|
|
||||||
|
closed bool
|
||||||
|
|
||||||
|
mu struct {
|
||||||
|
sync.Mutex
|
||||||
|
err error
|
||||||
|
driver.Result
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const ciBufferSize = 64 * 1024
|
||||||
|
|
||||||
|
// flush buffer before the buffer is filled up and needs reallocation
|
||||||
|
const ciBufferFlushSize = 63 * 1024
|
||||||
|
|
||||||
|
func (cn *conn) prepareCopyIn(q string) (_ driver.Stmt, err error) {
|
||||||
|
if !cn.isInTransaction() {
|
||||||
|
return nil, errCopyNotSupportedOutsideTxn
|
||||||
|
}
|
||||||
|
|
||||||
|
ci := ©in{
|
||||||
|
cn: cn,
|
||||||
|
buffer: make([]byte, 0, ciBufferSize),
|
||||||
|
rowData: make(chan []byte),
|
||||||
|
done: make(chan bool, 1),
|
||||||
|
}
|
||||||
|
// add CopyData identifier + 4 bytes for message length
|
||||||
|
ci.buffer = append(ci.buffer, 'd', 0, 0, 0, 0)
|
||||||
|
|
||||||
|
b := cn.writeBuf('Q')
|
||||||
|
b.string(q)
|
||||||
|
cn.send(b)
|
||||||
|
|
||||||
|
awaitCopyInResponse:
|
||||||
|
for {
|
||||||
|
t, r := cn.recv1()
|
||||||
|
switch t {
|
||||||
|
case 'G':
|
||||||
|
if r.byte() != 0 {
|
||||||
|
err = errBinaryCopyNotSupported
|
||||||
|
break awaitCopyInResponse
|
||||||
|
}
|
||||||
|
go ci.resploop()
|
||||||
|
return ci, nil
|
||||||
|
case 'H':
|
||||||
|
err = errCopyToNotSupported
|
||||||
|
break awaitCopyInResponse
|
||||||
|
case 'E':
|
||||||
|
err = parseError(r)
|
||||||
|
case 'Z':
|
||||||
|
if err == nil {
|
||||||
|
ci.setBad(driver.ErrBadConn)
|
||||||
|
errorf("unexpected ReadyForQuery in response to COPY")
|
||||||
|
}
|
||||||
|
cn.processReadyForQuery(r)
|
||||||
|
return nil, err
|
||||||
|
default:
|
||||||
|
ci.setBad(driver.ErrBadConn)
|
||||||
|
errorf("unknown response for copy query: %q", t)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// something went wrong, abort COPY before we return
|
||||||
|
b = cn.writeBuf('f')
|
||||||
|
b.string(err.Error())
|
||||||
|
cn.send(b)
|
||||||
|
|
||||||
|
for {
|
||||||
|
t, r := cn.recv1()
|
||||||
|
switch t {
|
||||||
|
case 'c', 'C', 'E':
|
||||||
|
case 'Z':
|
||||||
|
// correctly aborted, we're done
|
||||||
|
cn.processReadyForQuery(r)
|
||||||
|
return nil, err
|
||||||
|
default:
|
||||||
|
ci.setBad(driver.ErrBadConn)
|
||||||
|
errorf("unknown response for CopyFail: %q", t)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ci *copyin) flush(buf []byte) {
|
||||||
|
// set message length (without message identifier)
|
||||||
|
binary.BigEndian.PutUint32(buf[1:], uint32(len(buf)-1))
|
||||||
|
|
||||||
|
_, err := ci.cn.c.Write(buf)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ci *copyin) resploop() {
|
||||||
|
for {
|
||||||
|
var r readBuf
|
||||||
|
t, err := ci.cn.recvMessage(&r)
|
||||||
|
if err != nil {
|
||||||
|
ci.setBad(driver.ErrBadConn)
|
||||||
|
ci.setError(err)
|
||||||
|
ci.done <- true
|
||||||
|
return
|
||||||
|
}
|
||||||
|
switch t {
|
||||||
|
case 'C':
|
||||||
|
// complete
|
||||||
|
res, _ := ci.cn.parseComplete(r.string())
|
||||||
|
ci.setResult(res)
|
||||||
|
case 'N':
|
||||||
|
if n := ci.cn.noticeHandler; n != nil {
|
||||||
|
n(parseError(&r))
|
||||||
|
}
|
||||||
|
case 'Z':
|
||||||
|
ci.cn.processReadyForQuery(&r)
|
||||||
|
ci.done <- true
|
||||||
|
return
|
||||||
|
case 'E':
|
||||||
|
err := parseError(&r)
|
||||||
|
ci.setError(err)
|
||||||
|
default:
|
||||||
|
ci.setBad(driver.ErrBadConn)
|
||||||
|
ci.setError(fmt.Errorf("unknown response during CopyIn: %q", t))
|
||||||
|
ci.done <- true
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ci *copyin) setBad(err error) {
|
||||||
|
ci.cn.err.set(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ci *copyin) getBad() error {
|
||||||
|
return ci.cn.err.get()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ci *copyin) err() error {
|
||||||
|
ci.mu.Lock()
|
||||||
|
err := ci.mu.err
|
||||||
|
ci.mu.Unlock()
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// setError() sets ci.err if one has not been set already. Caller must not be
|
||||||
|
// holding ci.Mutex.
|
||||||
|
func (ci *copyin) setError(err error) {
|
||||||
|
ci.mu.Lock()
|
||||||
|
if ci.mu.err == nil {
|
||||||
|
ci.mu.err = err
|
||||||
|
}
|
||||||
|
ci.mu.Unlock()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ci *copyin) setResult(result driver.Result) {
|
||||||
|
ci.mu.Lock()
|
||||||
|
ci.mu.Result = result
|
||||||
|
ci.mu.Unlock()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ci *copyin) getResult() driver.Result {
|
||||||
|
ci.mu.Lock()
|
||||||
|
result := ci.mu.Result
|
||||||
|
ci.mu.Unlock()
|
||||||
|
if result == nil {
|
||||||
|
return driver.RowsAffected(0)
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ci *copyin) NumInput() int {
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ci *copyin) Query(v []driver.Value) (r driver.Rows, err error) {
|
||||||
|
return nil, ErrNotSupported
|
||||||
|
}
|
||||||
|
|
||||||
|
// Exec inserts values into the COPY stream. The insert is asynchronous
|
||||||
|
// and Exec can return errors from previous Exec calls to the same
|
||||||
|
// COPY stmt.
|
||||||
|
//
|
||||||
|
// You need to call Exec(nil) to sync the COPY stream and to get any
|
||||||
|
// errors from pending data, since Stmt.Close() doesn't return errors
|
||||||
|
// to the user.
|
||||||
|
func (ci *copyin) Exec(v []driver.Value) (r driver.Result, err error) {
|
||||||
|
if ci.closed {
|
||||||
|
return nil, errCopyInClosed
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := ci.getBad(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer ci.cn.errRecover(&err)
|
||||||
|
|
||||||
|
if err := ci.err(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(v) == 0 {
|
||||||
|
if err := ci.Close(); err != nil {
|
||||||
|
return driver.RowsAffected(0), err
|
||||||
|
}
|
||||||
|
|
||||||
|
return ci.getResult(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
numValues := len(v)
|
||||||
|
for i, value := range v {
|
||||||
|
ci.buffer = appendEncodedText(&ci.cn.parameterStatus, ci.buffer, value)
|
||||||
|
if i < numValues-1 {
|
||||||
|
ci.buffer = append(ci.buffer, '\t')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ci.buffer = append(ci.buffer, '\n')
|
||||||
|
|
||||||
|
if len(ci.buffer) > ciBufferFlushSize {
|
||||||
|
ci.flush(ci.buffer)
|
||||||
|
// reset buffer, keep bytes for message identifier and length
|
||||||
|
ci.buffer = ci.buffer[:5]
|
||||||
|
}
|
||||||
|
|
||||||
|
return driver.RowsAffected(0), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ci *copyin) Close() (err error) {
|
||||||
|
if ci.closed { // Don't do anything, we're already closed
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
ci.closed = true
|
||||||
|
|
||||||
|
if err := ci.getBad(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer ci.cn.errRecover(&err)
|
||||||
|
|
||||||
|
if len(ci.buffer) > 0 {
|
||||||
|
ci.flush(ci.buffer)
|
||||||
|
}
|
||||||
|
// Avoid touching the scratch buffer as resploop could be using it.
|
||||||
|
err = ci.cn.sendSimpleMessage('c')
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
<-ci.done
|
||||||
|
ci.cn.inCopy = false
|
||||||
|
|
||||||
|
if err := ci.err(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
268
vendor/github.com/lib/pq/doc.go
generated
vendored
Normal file
268
vendor/github.com/lib/pq/doc.go
generated
vendored
Normal file
@ -0,0 +1,268 @@
|
|||||||
|
/*
|
||||||
|
Package pq is a pure Go Postgres driver for the database/sql package.
|
||||||
|
|
||||||
|
In most cases clients will use the database/sql package instead of
|
||||||
|
using this package directly. For example:
|
||||||
|
|
||||||
|
import (
|
||||||
|
"database/sql"
|
||||||
|
|
||||||
|
_ "github.com/lib/pq"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
connStr := "user=pqgotest dbname=pqgotest sslmode=verify-full"
|
||||||
|
db, err := sql.Open("postgres", connStr)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
age := 21
|
||||||
|
rows, err := db.Query("SELECT name FROM users WHERE age = $1", age)
|
||||||
|
…
|
||||||
|
}
|
||||||
|
|
||||||
|
You can also connect to a database using a URL. For example:
|
||||||
|
|
||||||
|
connStr := "postgres://pqgotest:password@localhost/pqgotest?sslmode=verify-full"
|
||||||
|
db, err := sql.Open("postgres", connStr)
|
||||||
|
|
||||||
|
|
||||||
|
Connection String Parameters
|
||||||
|
|
||||||
|
|
||||||
|
Similarly to libpq, when establishing a connection using pq you are expected to
|
||||||
|
supply a connection string containing zero or more parameters.
|
||||||
|
A subset of the connection parameters supported by libpq are also supported by pq.
|
||||||
|
Additionally, pq also lets you specify run-time parameters (such as search_path or work_mem)
|
||||||
|
directly in the connection string. This is different from libpq, which does not allow
|
||||||
|
run-time parameters in the connection string, instead requiring you to supply
|
||||||
|
them in the options parameter.
|
||||||
|
|
||||||
|
For compatibility with libpq, the following special connection parameters are
|
||||||
|
supported:
|
||||||
|
|
||||||
|
* dbname - The name of the database to connect to
|
||||||
|
* user - The user to sign in as
|
||||||
|
* password - The user's password
|
||||||
|
* host - The host to connect to. Values that start with / are for unix
|
||||||
|
domain sockets. (default is localhost)
|
||||||
|
* port - The port to bind to. (default is 5432)
|
||||||
|
* sslmode - Whether or not to use SSL (default is require, this is not
|
||||||
|
the default for libpq)
|
||||||
|
* fallback_application_name - An application_name to fall back to if one isn't provided.
|
||||||
|
* connect_timeout - Maximum wait for connection, in seconds. Zero or
|
||||||
|
not specified means wait indefinitely.
|
||||||
|
* sslcert - Cert file location. The file must contain PEM encoded data.
|
||||||
|
* sslkey - Key file location. The file must contain PEM encoded data.
|
||||||
|
* sslrootcert - The location of the root certificate file. The file
|
||||||
|
must contain PEM encoded data.
|
||||||
|
|
||||||
|
Valid values for sslmode are:
|
||||||
|
|
||||||
|
* disable - No SSL
|
||||||
|
* require - Always SSL (skip verification)
|
||||||
|
* verify-ca - Always SSL (verify that the certificate presented by the
|
||||||
|
server was signed by a trusted CA)
|
||||||
|
* verify-full - Always SSL (verify that the certification presented by
|
||||||
|
the server was signed by a trusted CA and the server host name
|
||||||
|
matches the one in the certificate)
|
||||||
|
|
||||||
|
See http://www.postgresql.org/docs/current/static/libpq-connect.html#LIBPQ-CONNSTRING
|
||||||
|
for more information about connection string parameters.
|
||||||
|
|
||||||
|
Use single quotes for values that contain whitespace:
|
||||||
|
|
||||||
|
"user=pqgotest password='with spaces'"
|
||||||
|
|
||||||
|
A backslash will escape the next character in values:
|
||||||
|
|
||||||
|
"user=space\ man password='it\'s valid'"
|
||||||
|
|
||||||
|
Note that the connection parameter client_encoding (which sets the
|
||||||
|
text encoding for the connection) may be set but must be "UTF8",
|
||||||
|
matching with the same rules as Postgres. It is an error to provide
|
||||||
|
any other value.
|
||||||
|
|
||||||
|
In addition to the parameters listed above, any run-time parameter that can be
|
||||||
|
set at backend start time can be set in the connection string. For more
|
||||||
|
information, see
|
||||||
|
http://www.postgresql.org/docs/current/static/runtime-config.html.
|
||||||
|
|
||||||
|
Most environment variables as specified at http://www.postgresql.org/docs/current/static/libpq-envars.html
|
||||||
|
supported by libpq are also supported by pq. If any of the environment
|
||||||
|
variables not supported by pq are set, pq will panic during connection
|
||||||
|
establishment. Environment variables have a lower precedence than explicitly
|
||||||
|
provided connection parameters.
|
||||||
|
|
||||||
|
The pgpass mechanism as described in http://www.postgresql.org/docs/current/static/libpq-pgpass.html
|
||||||
|
is supported, but on Windows PGPASSFILE must be specified explicitly.
|
||||||
|
|
||||||
|
|
||||||
|
Queries
|
||||||
|
|
||||||
|
|
||||||
|
database/sql does not dictate any specific format for parameter
|
||||||
|
markers in query strings, and pq uses the Postgres-native ordinal markers,
|
||||||
|
as shown above. The same marker can be reused for the same parameter:
|
||||||
|
|
||||||
|
rows, err := db.Query(`SELECT name FROM users WHERE favorite_fruit = $1
|
||||||
|
OR age BETWEEN $2 AND $2 + 3`, "orange", 64)
|
||||||
|
|
||||||
|
pq does not support the LastInsertId() method of the Result type in database/sql.
|
||||||
|
To return the identifier of an INSERT (or UPDATE or DELETE), use the Postgres
|
||||||
|
RETURNING clause with a standard Query or QueryRow call:
|
||||||
|
|
||||||
|
var userid int
|
||||||
|
err := db.QueryRow(`INSERT INTO users(name, favorite_fruit, age)
|
||||||
|
VALUES('beatrice', 'starfruit', 93) RETURNING id`).Scan(&userid)
|
||||||
|
|
||||||
|
For more details on RETURNING, see the Postgres documentation:
|
||||||
|
|
||||||
|
http://www.postgresql.org/docs/current/static/sql-insert.html
|
||||||
|
http://www.postgresql.org/docs/current/static/sql-update.html
|
||||||
|
http://www.postgresql.org/docs/current/static/sql-delete.html
|
||||||
|
|
||||||
|
For additional instructions on querying see the documentation for the database/sql package.
|
||||||
|
|
||||||
|
|
||||||
|
Data Types
|
||||||
|
|
||||||
|
|
||||||
|
Parameters pass through driver.DefaultParameterConverter before they are handled
|
||||||
|
by this package. When the binary_parameters connection option is enabled,
|
||||||
|
[]byte values are sent directly to the backend as data in binary format.
|
||||||
|
|
||||||
|
This package returns the following types for values from the PostgreSQL backend:
|
||||||
|
|
||||||
|
- integer types smallint, integer, and bigint are returned as int64
|
||||||
|
- floating-point types real and double precision are returned as float64
|
||||||
|
- character types char, varchar, and text are returned as string
|
||||||
|
- temporal types date, time, timetz, timestamp, and timestamptz are
|
||||||
|
returned as time.Time
|
||||||
|
- the boolean type is returned as bool
|
||||||
|
- the bytea type is returned as []byte
|
||||||
|
|
||||||
|
All other types are returned directly from the backend as []byte values in text format.
|
||||||
|
|
||||||
|
|
||||||
|
Errors
|
||||||
|
|
||||||
|
|
||||||
|
pq may return errors of type *pq.Error which can be interrogated for error details:
|
||||||
|
|
||||||
|
if err, ok := err.(*pq.Error); ok {
|
||||||
|
fmt.Println("pq error:", err.Code.Name())
|
||||||
|
}
|
||||||
|
|
||||||
|
See the pq.Error type for details.
|
||||||
|
|
||||||
|
|
||||||
|
Bulk imports
|
||||||
|
|
||||||
|
You can perform bulk imports by preparing a statement returned by pq.CopyIn (or
|
||||||
|
pq.CopyInSchema) in an explicit transaction (sql.Tx). The returned statement
|
||||||
|
handle can then be repeatedly "executed" to copy data into the target table.
|
||||||
|
After all data has been processed you should call Exec() once with no arguments
|
||||||
|
to flush all buffered data. Any call to Exec() might return an error which
|
||||||
|
should be handled appropriately, but because of the internal buffering an error
|
||||||
|
returned by Exec() might not be related to the data passed in the call that
|
||||||
|
failed.
|
||||||
|
|
||||||
|
CopyIn uses COPY FROM internally. It is not possible to COPY outside of an
|
||||||
|
explicit transaction in pq.
|
||||||
|
|
||||||
|
Usage example:
|
||||||
|
|
||||||
|
txn, err := db.Begin()
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
stmt, err := txn.Prepare(pq.CopyIn("users", "name", "age"))
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, user := range users {
|
||||||
|
_, err = stmt.Exec(user.Name, int64(user.Age))
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = stmt.Exec()
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = stmt.Close()
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = txn.Commit()
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
Notifications
|
||||||
|
|
||||||
|
|
||||||
|
PostgreSQL supports a simple publish/subscribe model over database
|
||||||
|
connections. See http://www.postgresql.org/docs/current/static/sql-notify.html
|
||||||
|
for more information about the general mechanism.
|
||||||
|
|
||||||
|
To start listening for notifications, you first have to open a new connection
|
||||||
|
to the database by calling NewListener. This connection can not be used for
|
||||||
|
anything other than LISTEN / NOTIFY. Calling Listen will open a "notification
|
||||||
|
channel"; once a notification channel is open, a notification generated on that
|
||||||
|
channel will effect a send on the Listener.Notify channel. A notification
|
||||||
|
channel will remain open until Unlisten is called, though connection loss might
|
||||||
|
result in some notifications being lost. To solve this problem, Listener sends
|
||||||
|
a nil pointer over the Notify channel any time the connection is re-established
|
||||||
|
following a connection loss. The application can get information about the
|
||||||
|
state of the underlying connection by setting an event callback in the call to
|
||||||
|
NewListener.
|
||||||
|
|
||||||
|
A single Listener can safely be used from concurrent goroutines, which means
|
||||||
|
that there is often no need to create more than one Listener in your
|
||||||
|
application. However, a Listener is always connected to a single database, so
|
||||||
|
you will need to create a new Listener instance for every database you want to
|
||||||
|
receive notifications in.
|
||||||
|
|
||||||
|
The channel name in both Listen and Unlisten is case sensitive, and can contain
|
||||||
|
any characters legal in an identifier (see
|
||||||
|
http://www.postgresql.org/docs/current/static/sql-syntax-lexical.html#SQL-SYNTAX-IDENTIFIERS
|
||||||
|
for more information). Note that the channel name will be truncated to 63
|
||||||
|
bytes by the PostgreSQL server.
|
||||||
|
|
||||||
|
You can find a complete, working example of Listener usage at
|
||||||
|
https://godoc.org/github.com/lib/pq/example/listen.
|
||||||
|
|
||||||
|
|
||||||
|
Kerberos Support
|
||||||
|
|
||||||
|
|
||||||
|
If you need support for Kerberos authentication, add the following to your main
|
||||||
|
package:
|
||||||
|
|
||||||
|
import "github.com/lib/pq/auth/kerberos"
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
pq.RegisterGSSProvider(func() (pq.Gss, error) { return kerberos.NewGSS() })
|
||||||
|
}
|
||||||
|
|
||||||
|
This package is in a separate module so that users who don't need Kerberos
|
||||||
|
don't have to download unnecessary dependencies.
|
||||||
|
|
||||||
|
When imported, additional connection string parameters are supported:
|
||||||
|
|
||||||
|
* krbsrvname - GSS (Kerberos) service name when constructing the
|
||||||
|
SPN (default is `postgres`). This will be combined with the host
|
||||||
|
to form the full SPN: `krbsrvname/host`.
|
||||||
|
* krbspn - GSS (Kerberos) SPN. This takes priority over
|
||||||
|
`krbsrvname` if present.
|
||||||
|
*/
|
||||||
|
package pq
|
628
vendor/github.com/lib/pq/encode.go
generated
vendored
Normal file
628
vendor/github.com/lib/pq/encode.go
generated
vendored
Normal file
@ -0,0 +1,628 @@
|
|||||||
|
package pq
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"database/sql/driver"
|
||||||
|
"encoding/binary"
|
||||||
|
"encoding/hex"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"math"
|
||||||
|
"regexp"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/lib/pq/oid"
|
||||||
|
)
|
||||||
|
|
||||||
|
var time2400Regex = regexp.MustCompile(`^(24:00(?::00(?:\.0+)?)?)(?:[Z+-].*)?$`)
|
||||||
|
|
||||||
|
func binaryEncode(parameterStatus *parameterStatus, x interface{}) []byte {
|
||||||
|
switch v := x.(type) {
|
||||||
|
case []byte:
|
||||||
|
return v
|
||||||
|
default:
|
||||||
|
return encode(parameterStatus, x, oid.T_unknown)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func encode(parameterStatus *parameterStatus, x interface{}, pgtypOid oid.Oid) []byte {
|
||||||
|
switch v := x.(type) {
|
||||||
|
case int64:
|
||||||
|
return strconv.AppendInt(nil, v, 10)
|
||||||
|
case float64:
|
||||||
|
return strconv.AppendFloat(nil, v, 'f', -1, 64)
|
||||||
|
case []byte:
|
||||||
|
if pgtypOid == oid.T_bytea {
|
||||||
|
return encodeBytea(parameterStatus.serverVersion, v)
|
||||||
|
}
|
||||||
|
|
||||||
|
return v
|
||||||
|
case string:
|
||||||
|
if pgtypOid == oid.T_bytea {
|
||||||
|
return encodeBytea(parameterStatus.serverVersion, []byte(v))
|
||||||
|
}
|
||||||
|
|
||||||
|
return []byte(v)
|
||||||
|
case bool:
|
||||||
|
return strconv.AppendBool(nil, v)
|
||||||
|
case time.Time:
|
||||||
|
return formatTs(v)
|
||||||
|
|
||||||
|
default:
|
||||||
|
errorf("encode: unknown type for %T", v)
|
||||||
|
}
|
||||||
|
|
||||||
|
panic("not reached")
|
||||||
|
}
|
||||||
|
|
||||||
|
func decode(parameterStatus *parameterStatus, s []byte, typ oid.Oid, f format) interface{} {
|
||||||
|
switch f {
|
||||||
|
case formatBinary:
|
||||||
|
return binaryDecode(parameterStatus, s, typ)
|
||||||
|
case formatText:
|
||||||
|
return textDecode(parameterStatus, s, typ)
|
||||||
|
default:
|
||||||
|
panic("not reached")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func binaryDecode(parameterStatus *parameterStatus, s []byte, typ oid.Oid) interface{} {
|
||||||
|
switch typ {
|
||||||
|
case oid.T_bytea:
|
||||||
|
return s
|
||||||
|
case oid.T_int8:
|
||||||
|
return int64(binary.BigEndian.Uint64(s))
|
||||||
|
case oid.T_int4:
|
||||||
|
return int64(int32(binary.BigEndian.Uint32(s)))
|
||||||
|
case oid.T_int2:
|
||||||
|
return int64(int16(binary.BigEndian.Uint16(s)))
|
||||||
|
case oid.T_uuid:
|
||||||
|
b, err := decodeUUIDBinary(s)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
return b
|
||||||
|
|
||||||
|
default:
|
||||||
|
errorf("don't know how to decode binary parameter of type %d", uint32(typ))
|
||||||
|
}
|
||||||
|
|
||||||
|
panic("not reached")
|
||||||
|
}
|
||||||
|
|
||||||
|
func textDecode(parameterStatus *parameterStatus, s []byte, typ oid.Oid) interface{} {
|
||||||
|
switch typ {
|
||||||
|
case oid.T_char, oid.T_varchar, oid.T_text:
|
||||||
|
return string(s)
|
||||||
|
case oid.T_bytea:
|
||||||
|
b, err := parseBytea(s)
|
||||||
|
if err != nil {
|
||||||
|
errorf("%s", err)
|
||||||
|
}
|
||||||
|
return b
|
||||||
|
case oid.T_timestamptz:
|
||||||
|
return parseTs(parameterStatus.currentLocation, string(s))
|
||||||
|
case oid.T_timestamp, oid.T_date:
|
||||||
|
return parseTs(nil, string(s))
|
||||||
|
case oid.T_time:
|
||||||
|
return mustParse("15:04:05", typ, s)
|
||||||
|
case oid.T_timetz:
|
||||||
|
return mustParse("15:04:05-07", typ, s)
|
||||||
|
case oid.T_bool:
|
||||||
|
return s[0] == 't'
|
||||||
|
case oid.T_int8, oid.T_int4, oid.T_int2:
|
||||||
|
i, err := strconv.ParseInt(string(s), 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
errorf("%s", err)
|
||||||
|
}
|
||||||
|
return i
|
||||||
|
case oid.T_float4, oid.T_float8:
|
||||||
|
// We always use 64 bit parsing, regardless of whether the input text is for
|
||||||
|
// a float4 or float8, because clients expect float64s for all float datatypes
|
||||||
|
// and returning a 32-bit parsed float64 produces lossy results.
|
||||||
|
f, err := strconv.ParseFloat(string(s), 64)
|
||||||
|
if err != nil {
|
||||||
|
errorf("%s", err)
|
||||||
|
}
|
||||||
|
return f
|
||||||
|
}
|
||||||
|
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
// appendEncodedText encodes item in text format as required by COPY
|
||||||
|
// and appends to buf
|
||||||
|
func appendEncodedText(parameterStatus *parameterStatus, buf []byte, x interface{}) []byte {
|
||||||
|
switch v := x.(type) {
|
||||||
|
case int64:
|
||||||
|
return strconv.AppendInt(buf, v, 10)
|
||||||
|
case float64:
|
||||||
|
return strconv.AppendFloat(buf, v, 'f', -1, 64)
|
||||||
|
case []byte:
|
||||||
|
encodedBytea := encodeBytea(parameterStatus.serverVersion, v)
|
||||||
|
return appendEscapedText(buf, string(encodedBytea))
|
||||||
|
case string:
|
||||||
|
return appendEscapedText(buf, v)
|
||||||
|
case bool:
|
||||||
|
return strconv.AppendBool(buf, v)
|
||||||
|
case time.Time:
|
||||||
|
return append(buf, formatTs(v)...)
|
||||||
|
case nil:
|
||||||
|
return append(buf, "\\N"...)
|
||||||
|
default:
|
||||||
|
errorf("encode: unknown type for %T", v)
|
||||||
|
}
|
||||||
|
|
||||||
|
panic("not reached")
|
||||||
|
}
|
||||||
|
|
||||||
|
func appendEscapedText(buf []byte, text string) []byte {
|
||||||
|
escapeNeeded := false
|
||||||
|
startPos := 0
|
||||||
|
var c byte
|
||||||
|
|
||||||
|
// check if we need to escape
|
||||||
|
for i := 0; i < len(text); i++ {
|
||||||
|
c = text[i]
|
||||||
|
if c == '\\' || c == '\n' || c == '\r' || c == '\t' {
|
||||||
|
escapeNeeded = true
|
||||||
|
startPos = i
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !escapeNeeded {
|
||||||
|
return append(buf, text...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// copy till first char to escape, iterate the rest
|
||||||
|
result := append(buf, text[:startPos]...)
|
||||||
|
for i := startPos; i < len(text); i++ {
|
||||||
|
c = text[i]
|
||||||
|
switch c {
|
||||||
|
case '\\':
|
||||||
|
result = append(result, '\\', '\\')
|
||||||
|
case '\n':
|
||||||
|
result = append(result, '\\', 'n')
|
||||||
|
case '\r':
|
||||||
|
result = append(result, '\\', 'r')
|
||||||
|
case '\t':
|
||||||
|
result = append(result, '\\', 't')
|
||||||
|
default:
|
||||||
|
result = append(result, c)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
func mustParse(f string, typ oid.Oid, s []byte) time.Time {
|
||||||
|
str := string(s)
|
||||||
|
|
||||||
|
// Check for a minute and second offset in the timezone.
|
||||||
|
if typ == oid.T_timestamptz || typ == oid.T_timetz {
|
||||||
|
for i := 3; i <= 6; i += 3 {
|
||||||
|
if str[len(str)-i] == ':' {
|
||||||
|
f += ":00"
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Special case for 24:00 time.
|
||||||
|
// Unfortunately, golang does not parse 24:00 as a proper time.
|
||||||
|
// In this case, we want to try "round to the next day", to differentiate.
|
||||||
|
// As such, we find if the 24:00 time matches at the beginning; if so,
|
||||||
|
// we default it back to 00:00 but add a day later.
|
||||||
|
var is2400Time bool
|
||||||
|
switch typ {
|
||||||
|
case oid.T_timetz, oid.T_time:
|
||||||
|
if matches := time2400Regex.FindStringSubmatch(str); matches != nil {
|
||||||
|
// Concatenate timezone information at the back.
|
||||||
|
str = "00:00:00" + str[len(matches[1]):]
|
||||||
|
is2400Time = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
t, err := time.Parse(f, str)
|
||||||
|
if err != nil {
|
||||||
|
errorf("decode: %s", err)
|
||||||
|
}
|
||||||
|
if is2400Time {
|
||||||
|
t = t.Add(24 * time.Hour)
|
||||||
|
}
|
||||||
|
return t
|
||||||
|
}
|
||||||
|
|
||||||
|
var errInvalidTimestamp = errors.New("invalid timestamp")
|
||||||
|
|
||||||
|
type timestampParser struct {
|
||||||
|
err error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *timestampParser) expect(str string, char byte, pos int) {
|
||||||
|
if p.err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if pos+1 > len(str) {
|
||||||
|
p.err = errInvalidTimestamp
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if c := str[pos]; c != char && p.err == nil {
|
||||||
|
p.err = fmt.Errorf("expected '%v' at position %v; got '%v'", char, pos, c)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *timestampParser) mustAtoi(str string, begin int, end int) int {
|
||||||
|
if p.err != nil {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
if begin < 0 || end < 0 || begin > end || end > len(str) {
|
||||||
|
p.err = errInvalidTimestamp
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
result, err := strconv.Atoi(str[begin:end])
|
||||||
|
if err != nil {
|
||||||
|
if p.err == nil {
|
||||||
|
p.err = fmt.Errorf("expected number; got '%v'", str)
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
// The location cache caches the time zones typically used by the client.
|
||||||
|
type locationCache struct {
|
||||||
|
cache map[int]*time.Location
|
||||||
|
lock sync.Mutex
|
||||||
|
}
|
||||||
|
|
||||||
|
// All connections share the same list of timezones. Benchmarking shows that
|
||||||
|
// about 5% speed could be gained by putting the cache in the connection and
|
||||||
|
// losing the mutex, at the cost of a small amount of memory and a somewhat
|
||||||
|
// significant increase in code complexity.
|
||||||
|
var globalLocationCache = newLocationCache()
|
||||||
|
|
||||||
|
func newLocationCache() *locationCache {
|
||||||
|
return &locationCache{cache: make(map[int]*time.Location)}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns the cached timezone for the specified offset, creating and caching
|
||||||
|
// it if necessary.
|
||||||
|
func (c *locationCache) getLocation(offset int) *time.Location {
|
||||||
|
c.lock.Lock()
|
||||||
|
defer c.lock.Unlock()
|
||||||
|
|
||||||
|
location, ok := c.cache[offset]
|
||||||
|
if !ok {
|
||||||
|
location = time.FixedZone("", offset)
|
||||||
|
c.cache[offset] = location
|
||||||
|
}
|
||||||
|
|
||||||
|
return location
|
||||||
|
}
|
||||||
|
|
||||||
|
var infinityTsEnabled = false
|
||||||
|
var infinityTsNegative time.Time
|
||||||
|
var infinityTsPositive time.Time
|
||||||
|
|
||||||
|
const (
|
||||||
|
infinityTsEnabledAlready = "pq: infinity timestamp enabled already"
|
||||||
|
infinityTsNegativeMustBeSmaller = "pq: infinity timestamp: negative value must be smaller (before) than positive"
|
||||||
|
)
|
||||||
|
|
||||||
|
// EnableInfinityTs controls the handling of Postgres' "-infinity" and
|
||||||
|
// "infinity" "timestamp"s.
|
||||||
|
//
|
||||||
|
// If EnableInfinityTs is not called, "-infinity" and "infinity" will return
|
||||||
|
// []byte("-infinity") and []byte("infinity") respectively, and potentially
|
||||||
|
// cause error "sql: Scan error on column index 0: unsupported driver -> Scan
|
||||||
|
// pair: []uint8 -> *time.Time", when scanning into a time.Time value.
|
||||||
|
//
|
||||||
|
// Once EnableInfinityTs has been called, all connections created using this
|
||||||
|
// driver will decode Postgres' "-infinity" and "infinity" for "timestamp",
|
||||||
|
// "timestamp with time zone" and "date" types to the predefined minimum and
|
||||||
|
// maximum times, respectively. When encoding time.Time values, any time which
|
||||||
|
// equals or precedes the predefined minimum time will be encoded to
|
||||||
|
// "-infinity". Any values at or past the maximum time will similarly be
|
||||||
|
// encoded to "infinity".
|
||||||
|
//
|
||||||
|
// If EnableInfinityTs is called with negative >= positive, it will panic.
|
||||||
|
// Calling EnableInfinityTs after a connection has been established results in
|
||||||
|
// undefined behavior. If EnableInfinityTs is called more than once, it will
|
||||||
|
// panic.
|
||||||
|
func EnableInfinityTs(negative time.Time, positive time.Time) {
|
||||||
|
if infinityTsEnabled {
|
||||||
|
panic(infinityTsEnabledAlready)
|
||||||
|
}
|
||||||
|
if !negative.Before(positive) {
|
||||||
|
panic(infinityTsNegativeMustBeSmaller)
|
||||||
|
}
|
||||||
|
infinityTsEnabled = true
|
||||||
|
infinityTsNegative = negative
|
||||||
|
infinityTsPositive = positive
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Testing might want to toggle infinityTsEnabled
|
||||||
|
*/
|
||||||
|
func disableInfinityTs() {
|
||||||
|
infinityTsEnabled = false
|
||||||
|
}
|
||||||
|
|
||||||
|
// This is a time function specific to the Postgres default DateStyle
|
||||||
|
// setting ("ISO, MDY"), the only one we currently support. This
|
||||||
|
// accounts for the discrepancies between the parsing available with
|
||||||
|
// time.Parse and the Postgres date formatting quirks.
|
||||||
|
func parseTs(currentLocation *time.Location, str string) interface{} {
|
||||||
|
switch str {
|
||||||
|
case "-infinity":
|
||||||
|
if infinityTsEnabled {
|
||||||
|
return infinityTsNegative
|
||||||
|
}
|
||||||
|
return []byte(str)
|
||||||
|
case "infinity":
|
||||||
|
if infinityTsEnabled {
|
||||||
|
return infinityTsPositive
|
||||||
|
}
|
||||||
|
return []byte(str)
|
||||||
|
}
|
||||||
|
t, err := ParseTimestamp(currentLocation, str)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
return t
|
||||||
|
}
|
||||||
|
|
||||||
|
// ParseTimestamp parses Postgres' text format. It returns a time.Time in
|
||||||
|
// currentLocation iff that time's offset agrees with the offset sent from the
|
||||||
|
// Postgres server. Otherwise, ParseTimestamp returns a time.Time with the
|
||||||
|
// fixed offset offset provided by the Postgres server.
|
||||||
|
func ParseTimestamp(currentLocation *time.Location, str string) (time.Time, error) {
|
||||||
|
p := timestampParser{}
|
||||||
|
|
||||||
|
monSep := strings.IndexRune(str, '-')
|
||||||
|
// this is Gregorian year, not ISO Year
|
||||||
|
// In Gregorian system, the year 1 BC is followed by AD 1
|
||||||
|
year := p.mustAtoi(str, 0, monSep)
|
||||||
|
daySep := monSep + 3
|
||||||
|
month := p.mustAtoi(str, monSep+1, daySep)
|
||||||
|
p.expect(str, '-', daySep)
|
||||||
|
timeSep := daySep + 3
|
||||||
|
day := p.mustAtoi(str, daySep+1, timeSep)
|
||||||
|
|
||||||
|
minLen := monSep + len("01-01") + 1
|
||||||
|
|
||||||
|
isBC := strings.HasSuffix(str, " BC")
|
||||||
|
if isBC {
|
||||||
|
minLen += 3
|
||||||
|
}
|
||||||
|
|
||||||
|
var hour, minute, second int
|
||||||
|
if len(str) > minLen {
|
||||||
|
p.expect(str, ' ', timeSep)
|
||||||
|
minSep := timeSep + 3
|
||||||
|
p.expect(str, ':', minSep)
|
||||||
|
hour = p.mustAtoi(str, timeSep+1, minSep)
|
||||||
|
secSep := minSep + 3
|
||||||
|
p.expect(str, ':', secSep)
|
||||||
|
minute = p.mustAtoi(str, minSep+1, secSep)
|
||||||
|
secEnd := secSep + 3
|
||||||
|
second = p.mustAtoi(str, secSep+1, secEnd)
|
||||||
|
}
|
||||||
|
remainderIdx := monSep + len("01-01 00:00:00") + 1
|
||||||
|
// Three optional (but ordered) sections follow: the
|
||||||
|
// fractional seconds, the time zone offset, and the BC
|
||||||
|
// designation. We set them up here and adjust the other
|
||||||
|
// offsets if the preceding sections exist.
|
||||||
|
|
||||||
|
nanoSec := 0
|
||||||
|
tzOff := 0
|
||||||
|
|
||||||
|
if remainderIdx < len(str) && str[remainderIdx] == '.' {
|
||||||
|
fracStart := remainderIdx + 1
|
||||||
|
fracOff := strings.IndexAny(str[fracStart:], "-+ ")
|
||||||
|
if fracOff < 0 {
|
||||||
|
fracOff = len(str) - fracStart
|
||||||
|
}
|
||||||
|
fracSec := p.mustAtoi(str, fracStart, fracStart+fracOff)
|
||||||
|
nanoSec = fracSec * (1000000000 / int(math.Pow(10, float64(fracOff))))
|
||||||
|
|
||||||
|
remainderIdx += fracOff + 1
|
||||||
|
}
|
||||||
|
if tzStart := remainderIdx; tzStart < len(str) && (str[tzStart] == '-' || str[tzStart] == '+') {
|
||||||
|
// time zone separator is always '-' or '+' (UTC is +00)
|
||||||
|
var tzSign int
|
||||||
|
switch c := str[tzStart]; c {
|
||||||
|
case '-':
|
||||||
|
tzSign = -1
|
||||||
|
case '+':
|
||||||
|
tzSign = +1
|
||||||
|
default:
|
||||||
|
return time.Time{}, fmt.Errorf("expected '-' or '+' at position %v; got %v", tzStart, c)
|
||||||
|
}
|
||||||
|
tzHours := p.mustAtoi(str, tzStart+1, tzStart+3)
|
||||||
|
remainderIdx += 3
|
||||||
|
var tzMin, tzSec int
|
||||||
|
if remainderIdx < len(str) && str[remainderIdx] == ':' {
|
||||||
|
tzMin = p.mustAtoi(str, remainderIdx+1, remainderIdx+3)
|
||||||
|
remainderIdx += 3
|
||||||
|
}
|
||||||
|
if remainderIdx < len(str) && str[remainderIdx] == ':' {
|
||||||
|
tzSec = p.mustAtoi(str, remainderIdx+1, remainderIdx+3)
|
||||||
|
remainderIdx += 3
|
||||||
|
}
|
||||||
|
tzOff = tzSign * ((tzHours * 60 * 60) + (tzMin * 60) + tzSec)
|
||||||
|
}
|
||||||
|
var isoYear int
|
||||||
|
|
||||||
|
if isBC {
|
||||||
|
isoYear = 1 - year
|
||||||
|
remainderIdx += 3
|
||||||
|
} else {
|
||||||
|
isoYear = year
|
||||||
|
}
|
||||||
|
if remainderIdx < len(str) {
|
||||||
|
return time.Time{}, fmt.Errorf("expected end of input, got %v", str[remainderIdx:])
|
||||||
|
}
|
||||||
|
t := time.Date(isoYear, time.Month(month), day,
|
||||||
|
hour, minute, second, nanoSec,
|
||||||
|
globalLocationCache.getLocation(tzOff))
|
||||||
|
|
||||||
|
if currentLocation != nil {
|
||||||
|
// Set the location of the returned Time based on the session's
|
||||||
|
// TimeZone value, but only if the local time zone database agrees with
|
||||||
|
// the remote database on the offset.
|
||||||
|
lt := t.In(currentLocation)
|
||||||
|
_, newOff := lt.Zone()
|
||||||
|
if newOff == tzOff {
|
||||||
|
t = lt
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return t, p.err
|
||||||
|
}
|
||||||
|
|
||||||
|
// formatTs formats t into a format postgres understands.
|
||||||
|
func formatTs(t time.Time) []byte {
|
||||||
|
if infinityTsEnabled {
|
||||||
|
// t <= -infinity : ! (t > -infinity)
|
||||||
|
if !t.After(infinityTsNegative) {
|
||||||
|
return []byte("-infinity")
|
||||||
|
}
|
||||||
|
// t >= infinity : ! (!t < infinity)
|
||||||
|
if !t.Before(infinityTsPositive) {
|
||||||
|
return []byte("infinity")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return FormatTimestamp(t)
|
||||||
|
}
|
||||||
|
|
||||||
|
// FormatTimestamp formats t into Postgres' text format for timestamps.
|
||||||
|
func FormatTimestamp(t time.Time) []byte {
|
||||||
|
// Need to send dates before 0001 A.D. with " BC" suffix, instead of the
|
||||||
|
// minus sign preferred by Go.
|
||||||
|
// Beware, "0000" in ISO is "1 BC", "-0001" is "2 BC" and so on
|
||||||
|
bc := false
|
||||||
|
if t.Year() <= 0 {
|
||||||
|
// flip year sign, and add 1, e.g: "0" will be "1", and "-10" will be "11"
|
||||||
|
t = t.AddDate((-t.Year())*2+1, 0, 0)
|
||||||
|
bc = true
|
||||||
|
}
|
||||||
|
b := []byte(t.Format("2006-01-02 15:04:05.999999999Z07:00"))
|
||||||
|
|
||||||
|
_, offset := t.Zone()
|
||||||
|
offset %= 60
|
||||||
|
if offset != 0 {
|
||||||
|
// RFC3339Nano already printed the minus sign
|
||||||
|
if offset < 0 {
|
||||||
|
offset = -offset
|
||||||
|
}
|
||||||
|
|
||||||
|
b = append(b, ':')
|
||||||
|
if offset < 10 {
|
||||||
|
b = append(b, '0')
|
||||||
|
}
|
||||||
|
b = strconv.AppendInt(b, int64(offset), 10)
|
||||||
|
}
|
||||||
|
|
||||||
|
if bc {
|
||||||
|
b = append(b, " BC"...)
|
||||||
|
}
|
||||||
|
return b
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse a bytea value received from the server. Both "hex" and the legacy
|
||||||
|
// "escape" format are supported.
|
||||||
|
func parseBytea(s []byte) (result []byte, err error) {
|
||||||
|
if len(s) >= 2 && bytes.Equal(s[:2], []byte("\\x")) {
|
||||||
|
// bytea_output = hex
|
||||||
|
s = s[2:] // trim off leading "\\x"
|
||||||
|
result = make([]byte, hex.DecodedLen(len(s)))
|
||||||
|
_, err := hex.Decode(result, s)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// bytea_output = escape
|
||||||
|
for len(s) > 0 {
|
||||||
|
if s[0] == '\\' {
|
||||||
|
// escaped '\\'
|
||||||
|
if len(s) >= 2 && s[1] == '\\' {
|
||||||
|
result = append(result, '\\')
|
||||||
|
s = s[2:]
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// '\\' followed by an octal number
|
||||||
|
if len(s) < 4 {
|
||||||
|
return nil, fmt.Errorf("invalid bytea sequence %v", s)
|
||||||
|
}
|
||||||
|
r, err := strconv.ParseUint(string(s[1:4]), 8, 8)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("could not parse bytea value: %s", err.Error())
|
||||||
|
}
|
||||||
|
result = append(result, byte(r))
|
||||||
|
s = s[4:]
|
||||||
|
} else {
|
||||||
|
// We hit an unescaped, raw byte. Try to read in as many as
|
||||||
|
// possible in one go.
|
||||||
|
i := bytes.IndexByte(s, '\\')
|
||||||
|
if i == -1 {
|
||||||
|
result = append(result, s...)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
result = append(result, s[:i]...)
|
||||||
|
s = s[i:]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func encodeBytea(serverVersion int, v []byte) (result []byte) {
|
||||||
|
if serverVersion >= 90000 {
|
||||||
|
// Use the hex format if we know that the server supports it
|
||||||
|
result = make([]byte, 2+hex.EncodedLen(len(v)))
|
||||||
|
result[0] = '\\'
|
||||||
|
result[1] = 'x'
|
||||||
|
hex.Encode(result[2:], v)
|
||||||
|
} else {
|
||||||
|
// .. or resort to "escape"
|
||||||
|
for _, b := range v {
|
||||||
|
if b == '\\' {
|
||||||
|
result = append(result, '\\', '\\')
|
||||||
|
} else if b < 0x20 || b > 0x7e {
|
||||||
|
result = append(result, []byte(fmt.Sprintf("\\%03o", b))...)
|
||||||
|
} else {
|
||||||
|
result = append(result, b)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
// NullTime represents a time.Time that may be null. NullTime implements the
|
||||||
|
// sql.Scanner interface so it can be used as a scan destination, similar to
|
||||||
|
// sql.NullString.
|
||||||
|
type NullTime struct {
|
||||||
|
Time time.Time
|
||||||
|
Valid bool // Valid is true if Time is not NULL
|
||||||
|
}
|
||||||
|
|
||||||
|
// Scan implements the Scanner interface.
|
||||||
|
func (nt *NullTime) Scan(value interface{}) error {
|
||||||
|
nt.Time, nt.Valid = value.(time.Time)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Value implements the driver Valuer interface.
|
||||||
|
func (nt NullTime) Value() (driver.Value, error) {
|
||||||
|
if !nt.Valid {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
return nt.Time, nil
|
||||||
|
}
|
518
vendor/github.com/lib/pq/error.go
generated
vendored
Normal file
518
vendor/github.com/lib/pq/error.go
generated
vendored
Normal file
@ -0,0 +1,518 @@
|
|||||||
|
package pq
|
||||||
|
|
||||||
|
import (
|
||||||
|
"database/sql/driver"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"net"
|
||||||
|
"runtime"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Error severities
|
||||||
|
const (
|
||||||
|
Efatal = "FATAL"
|
||||||
|
Epanic = "PANIC"
|
||||||
|
Ewarning = "WARNING"
|
||||||
|
Enotice = "NOTICE"
|
||||||
|
Edebug = "DEBUG"
|
||||||
|
Einfo = "INFO"
|
||||||
|
Elog = "LOG"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Error represents an error communicating with the server.
|
||||||
|
//
|
||||||
|
// See http://www.postgresql.org/docs/current/static/protocol-error-fields.html for details of the fields
|
||||||
|
type Error struct {
|
||||||
|
Severity string
|
||||||
|
Code ErrorCode
|
||||||
|
Message string
|
||||||
|
Detail string
|
||||||
|
Hint string
|
||||||
|
Position string
|
||||||
|
InternalPosition string
|
||||||
|
InternalQuery string
|
||||||
|
Where string
|
||||||
|
Schema string
|
||||||
|
Table string
|
||||||
|
Column string
|
||||||
|
DataTypeName string
|
||||||
|
Constraint string
|
||||||
|
File string
|
||||||
|
Line string
|
||||||
|
Routine string
|
||||||
|
}
|
||||||
|
|
||||||
|
// ErrorCode is a five-character error code.
|
||||||
|
type ErrorCode string
|
||||||
|
|
||||||
|
// Name returns a more human friendly rendering of the error code, namely the
|
||||||
|
// "condition name".
|
||||||
|
//
|
||||||
|
// See http://www.postgresql.org/docs/9.3/static/errcodes-appendix.html for
|
||||||
|
// details.
|
||||||
|
func (ec ErrorCode) Name() string {
|
||||||
|
return errorCodeNames[ec]
|
||||||
|
}
|
||||||
|
|
||||||
|
// ErrorClass is only the class part of an error code.
|
||||||
|
type ErrorClass string
|
||||||
|
|
||||||
|
// Name returns the condition name of an error class. It is equivalent to the
|
||||||
|
// condition name of the "standard" error code (i.e. the one having the last
|
||||||
|
// three characters "000").
|
||||||
|
func (ec ErrorClass) Name() string {
|
||||||
|
return errorCodeNames[ErrorCode(ec+"000")]
|
||||||
|
}
|
||||||
|
|
||||||
|
// Class returns the error class, e.g. "28".
|
||||||
|
//
|
||||||
|
// See http://www.postgresql.org/docs/9.3/static/errcodes-appendix.html for
|
||||||
|
// details.
|
||||||
|
func (ec ErrorCode) Class() ErrorClass {
|
||||||
|
return ErrorClass(ec[0:2])
|
||||||
|
}
|
||||||
|
|
||||||
|
// errorCodeNames is a mapping between the five-character error codes and the
|
||||||
|
// human readable "condition names". It is derived from the list at
|
||||||
|
// http://www.postgresql.org/docs/9.3/static/errcodes-appendix.html
|
||||||
|
var errorCodeNames = map[ErrorCode]string{
|
||||||
|
// Class 00 - Successful Completion
|
||||||
|
"00000": "successful_completion",
|
||||||
|
// Class 01 - Warning
|
||||||
|
"01000": "warning",
|
||||||
|
"0100C": "dynamic_result_sets_returned",
|
||||||
|
"01008": "implicit_zero_bit_padding",
|
||||||
|
"01003": "null_value_eliminated_in_set_function",
|
||||||
|
"01007": "privilege_not_granted",
|
||||||
|
"01006": "privilege_not_revoked",
|
||||||
|
"01004": "string_data_right_truncation",
|
||||||
|
"01P01": "deprecated_feature",
|
||||||
|
// Class 02 - No Data (this is also a warning class per the SQL standard)
|
||||||
|
"02000": "no_data",
|
||||||
|
"02001": "no_additional_dynamic_result_sets_returned",
|
||||||
|
// Class 03 - SQL Statement Not Yet Complete
|
||||||
|
"03000": "sql_statement_not_yet_complete",
|
||||||
|
// Class 08 - Connection Exception
|
||||||
|
"08000": "connection_exception",
|
||||||
|
"08003": "connection_does_not_exist",
|
||||||
|
"08006": "connection_failure",
|
||||||
|
"08001": "sqlclient_unable_to_establish_sqlconnection",
|
||||||
|
"08004": "sqlserver_rejected_establishment_of_sqlconnection",
|
||||||
|
"08007": "transaction_resolution_unknown",
|
||||||
|
"08P01": "protocol_violation",
|
||||||
|
// Class 09 - Triggered Action Exception
|
||||||
|
"09000": "triggered_action_exception",
|
||||||
|
// Class 0A - Feature Not Supported
|
||||||
|
"0A000": "feature_not_supported",
|
||||||
|
// Class 0B - Invalid Transaction Initiation
|
||||||
|
"0B000": "invalid_transaction_initiation",
|
||||||
|
// Class 0F - Locator Exception
|
||||||
|
"0F000": "locator_exception",
|
||||||
|
"0F001": "invalid_locator_specification",
|
||||||
|
// Class 0L - Invalid Grantor
|
||||||
|
"0L000": "invalid_grantor",
|
||||||
|
"0LP01": "invalid_grant_operation",
|
||||||
|
// Class 0P - Invalid Role Specification
|
||||||
|
"0P000": "invalid_role_specification",
|
||||||
|
// Class 0Z - Diagnostics Exception
|
||||||
|
"0Z000": "diagnostics_exception",
|
||||||
|
"0Z002": "stacked_diagnostics_accessed_without_active_handler",
|
||||||
|
// Class 20 - Case Not Found
|
||||||
|
"20000": "case_not_found",
|
||||||
|
// Class 21 - Cardinality Violation
|
||||||
|
"21000": "cardinality_violation",
|
||||||
|
// Class 22 - Data Exception
|
||||||
|
"22000": "data_exception",
|
||||||
|
"2202E": "array_subscript_error",
|
||||||
|
"22021": "character_not_in_repertoire",
|
||||||
|
"22008": "datetime_field_overflow",
|
||||||
|
"22012": "division_by_zero",
|
||||||
|
"22005": "error_in_assignment",
|
||||||
|
"2200B": "escape_character_conflict",
|
||||||
|
"22022": "indicator_overflow",
|
||||||
|
"22015": "interval_field_overflow",
|
||||||
|
"2201E": "invalid_argument_for_logarithm",
|
||||||
|
"22014": "invalid_argument_for_ntile_function",
|
||||||
|
"22016": "invalid_argument_for_nth_value_function",
|
||||||
|
"2201F": "invalid_argument_for_power_function",
|
||||||
|
"2201G": "invalid_argument_for_width_bucket_function",
|
||||||
|
"22018": "invalid_character_value_for_cast",
|
||||||
|
"22007": "invalid_datetime_format",
|
||||||
|
"22019": "invalid_escape_character",
|
||||||
|
"2200D": "invalid_escape_octet",
|
||||||
|
"22025": "invalid_escape_sequence",
|
||||||
|
"22P06": "nonstandard_use_of_escape_character",
|
||||||
|
"22010": "invalid_indicator_parameter_value",
|
||||||
|
"22023": "invalid_parameter_value",
|
||||||
|
"2201B": "invalid_regular_expression",
|
||||||
|
"2201W": "invalid_row_count_in_limit_clause",
|
||||||
|
"2201X": "invalid_row_count_in_result_offset_clause",
|
||||||
|
"22009": "invalid_time_zone_displacement_value",
|
||||||
|
"2200C": "invalid_use_of_escape_character",
|
||||||
|
"2200G": "most_specific_type_mismatch",
|
||||||
|
"22004": "null_value_not_allowed",
|
||||||
|
"22002": "null_value_no_indicator_parameter",
|
||||||
|
"22003": "numeric_value_out_of_range",
|
||||||
|
"2200H": "sequence_generator_limit_exceeded",
|
||||||
|
"22026": "string_data_length_mismatch",
|
||||||
|
"22001": "string_data_right_truncation",
|
||||||
|
"22011": "substring_error",
|
||||||
|
"22027": "trim_error",
|
||||||
|
"22024": "unterminated_c_string",
|
||||||
|
"2200F": "zero_length_character_string",
|
||||||
|
"22P01": "floating_point_exception",
|
||||||
|
"22P02": "invalid_text_representation",
|
||||||
|
"22P03": "invalid_binary_representation",
|
||||||
|
"22P04": "bad_copy_file_format",
|
||||||
|
"22P05": "untranslatable_character",
|
||||||
|
"2200L": "not_an_xml_document",
|
||||||
|
"2200M": "invalid_xml_document",
|
||||||
|
"2200N": "invalid_xml_content",
|
||||||
|
"2200S": "invalid_xml_comment",
|
||||||
|
"2200T": "invalid_xml_processing_instruction",
|
||||||
|
// Class 23 - Integrity Constraint Violation
|
||||||
|
"23000": "integrity_constraint_violation",
|
||||||
|
"23001": "restrict_violation",
|
||||||
|
"23502": "not_null_violation",
|
||||||
|
"23503": "foreign_key_violation",
|
||||||
|
"23505": "unique_violation",
|
||||||
|
"23514": "check_violation",
|
||||||
|
"23P01": "exclusion_violation",
|
||||||
|
// Class 24 - Invalid Cursor State
|
||||||
|
"24000": "invalid_cursor_state",
|
||||||
|
// Class 25 - Invalid Transaction State
|
||||||
|
"25000": "invalid_transaction_state",
|
||||||
|
"25001": "active_sql_transaction",
|
||||||
|
"25002": "branch_transaction_already_active",
|
||||||
|
"25008": "held_cursor_requires_same_isolation_level",
|
||||||
|
"25003": "inappropriate_access_mode_for_branch_transaction",
|
||||||
|
"25004": "inappropriate_isolation_level_for_branch_transaction",
|
||||||
|
"25005": "no_active_sql_transaction_for_branch_transaction",
|
||||||
|
"25006": "read_only_sql_transaction",
|
||||||
|
"25007": "schema_and_data_statement_mixing_not_supported",
|
||||||
|
"25P01": "no_active_sql_transaction",
|
||||||
|
"25P02": "in_failed_sql_transaction",
|
||||||
|
// Class 26 - Invalid SQL Statement Name
|
||||||
|
"26000": "invalid_sql_statement_name",
|
||||||
|
// Class 27 - Triggered Data Change Violation
|
||||||
|
"27000": "triggered_data_change_violation",
|
||||||
|
// Class 28 - Invalid Authorization Specification
|
||||||
|
"28000": "invalid_authorization_specification",
|
||||||
|
"28P01": "invalid_password",
|
||||||
|
// Class 2B - Dependent Privilege Descriptors Still Exist
|
||||||
|
"2B000": "dependent_privilege_descriptors_still_exist",
|
||||||
|
"2BP01": "dependent_objects_still_exist",
|
||||||
|
// Class 2D - Invalid Transaction Termination
|
||||||
|
"2D000": "invalid_transaction_termination",
|
||||||
|
// Class 2F - SQL Routine Exception
|
||||||
|
"2F000": "sql_routine_exception",
|
||||||
|
"2F005": "function_executed_no_return_statement",
|
||||||
|
"2F002": "modifying_sql_data_not_permitted",
|
||||||
|
"2F003": "prohibited_sql_statement_attempted",
|
||||||
|
"2F004": "reading_sql_data_not_permitted",
|
||||||
|
// Class 34 - Invalid Cursor Name
|
||||||
|
"34000": "invalid_cursor_name",
|
||||||
|
// Class 38 - External Routine Exception
|
||||||
|
"38000": "external_routine_exception",
|
||||||
|
"38001": "containing_sql_not_permitted",
|
||||||
|
"38002": "modifying_sql_data_not_permitted",
|
||||||
|
"38003": "prohibited_sql_statement_attempted",
|
||||||
|
"38004": "reading_sql_data_not_permitted",
|
||||||
|
// Class 39 - External Routine Invocation Exception
|
||||||
|
"39000": "external_routine_invocation_exception",
|
||||||
|
"39001": "invalid_sqlstate_returned",
|
||||||
|
"39004": "null_value_not_allowed",
|
||||||
|
"39P01": "trigger_protocol_violated",
|
||||||
|
"39P02": "srf_protocol_violated",
|
||||||
|
// Class 3B - Savepoint Exception
|
||||||
|
"3B000": "savepoint_exception",
|
||||||
|
"3B001": "invalid_savepoint_specification",
|
||||||
|
// Class 3D - Invalid Catalog Name
|
||||||
|
"3D000": "invalid_catalog_name",
|
||||||
|
// Class 3F - Invalid Schema Name
|
||||||
|
"3F000": "invalid_schema_name",
|
||||||
|
// Class 40 - Transaction Rollback
|
||||||
|
"40000": "transaction_rollback",
|
||||||
|
"40002": "transaction_integrity_constraint_violation",
|
||||||
|
"40001": "serialization_failure",
|
||||||
|
"40003": "statement_completion_unknown",
|
||||||
|
"40P01": "deadlock_detected",
|
||||||
|
// Class 42 - Syntax Error or Access Rule Violation
|
||||||
|
"42000": "syntax_error_or_access_rule_violation",
|
||||||
|
"42601": "syntax_error",
|
||||||
|
"42501": "insufficient_privilege",
|
||||||
|
"42846": "cannot_coerce",
|
||||||
|
"42803": "grouping_error",
|
||||||
|
"42P20": "windowing_error",
|
||||||
|
"42P19": "invalid_recursion",
|
||||||
|
"42830": "invalid_foreign_key",
|
||||||
|
"42602": "invalid_name",
|
||||||
|
"42622": "name_too_long",
|
||||||
|
"42939": "reserved_name",
|
||||||
|
"42804": "datatype_mismatch",
|
||||||
|
"42P18": "indeterminate_datatype",
|
||||||
|
"42P21": "collation_mismatch",
|
||||||
|
"42P22": "indeterminate_collation",
|
||||||
|
"42809": "wrong_object_type",
|
||||||
|
"42703": "undefined_column",
|
||||||
|
"42883": "undefined_function",
|
||||||
|
"42P01": "undefined_table",
|
||||||
|
"42P02": "undefined_parameter",
|
||||||
|
"42704": "undefined_object",
|
||||||
|
"42701": "duplicate_column",
|
||||||
|
"42P03": "duplicate_cursor",
|
||||||
|
"42P04": "duplicate_database",
|
||||||
|
"42723": "duplicate_function",
|
||||||
|
"42P05": "duplicate_prepared_statement",
|
||||||
|
"42P06": "duplicate_schema",
|
||||||
|
"42P07": "duplicate_table",
|
||||||
|
"42712": "duplicate_alias",
|
||||||
|
"42710": "duplicate_object",
|
||||||
|
"42702": "ambiguous_column",
|
||||||
|
"42725": "ambiguous_function",
|
||||||
|
"42P08": "ambiguous_parameter",
|
||||||
|
"42P09": "ambiguous_alias",
|
||||||
|
"42P10": "invalid_column_reference",
|
||||||
|
"42611": "invalid_column_definition",
|
||||||
|
"42P11": "invalid_cursor_definition",
|
||||||
|
"42P12": "invalid_database_definition",
|
||||||
|
"42P13": "invalid_function_definition",
|
||||||
|
"42P14": "invalid_prepared_statement_definition",
|
||||||
|
"42P15": "invalid_schema_definition",
|
||||||
|
"42P16": "invalid_table_definition",
|
||||||
|
"42P17": "invalid_object_definition",
|
||||||
|
// Class 44 - WITH CHECK OPTION Violation
|
||||||
|
"44000": "with_check_option_violation",
|
||||||
|
// Class 53 - Insufficient Resources
|
||||||
|
"53000": "insufficient_resources",
|
||||||
|
"53100": "disk_full",
|
||||||
|
"53200": "out_of_memory",
|
||||||
|
"53300": "too_many_connections",
|
||||||
|
"53400": "configuration_limit_exceeded",
|
||||||
|
// Class 54 - Program Limit Exceeded
|
||||||
|
"54000": "program_limit_exceeded",
|
||||||
|
"54001": "statement_too_complex",
|
||||||
|
"54011": "too_many_columns",
|
||||||
|
"54023": "too_many_arguments",
|
||||||
|
// Class 55 - Object Not In Prerequisite State
|
||||||
|
"55000": "object_not_in_prerequisite_state",
|
||||||
|
"55006": "object_in_use",
|
||||||
|
"55P02": "cant_change_runtime_param",
|
||||||
|
"55P03": "lock_not_available",
|
||||||
|
// Class 57 - Operator Intervention
|
||||||
|
"57000": "operator_intervention",
|
||||||
|
"57014": "query_canceled",
|
||||||
|
"57P01": "admin_shutdown",
|
||||||
|
"57P02": "crash_shutdown",
|
||||||
|
"57P03": "cannot_connect_now",
|
||||||
|
"57P04": "database_dropped",
|
||||||
|
// Class 58 - System Error (errors external to PostgreSQL itself)
|
||||||
|
"58000": "system_error",
|
||||||
|
"58030": "io_error",
|
||||||
|
"58P01": "undefined_file",
|
||||||
|
"58P02": "duplicate_file",
|
||||||
|
// Class F0 - Configuration File Error
|
||||||
|
"F0000": "config_file_error",
|
||||||
|
"F0001": "lock_file_exists",
|
||||||
|
// Class HV - Foreign Data Wrapper Error (SQL/MED)
|
||||||
|
"HV000": "fdw_error",
|
||||||
|
"HV005": "fdw_column_name_not_found",
|
||||||
|
"HV002": "fdw_dynamic_parameter_value_needed",
|
||||||
|
"HV010": "fdw_function_sequence_error",
|
||||||
|
"HV021": "fdw_inconsistent_descriptor_information",
|
||||||
|
"HV024": "fdw_invalid_attribute_value",
|
||||||
|
"HV007": "fdw_invalid_column_name",
|
||||||
|
"HV008": "fdw_invalid_column_number",
|
||||||
|
"HV004": "fdw_invalid_data_type",
|
||||||
|
"HV006": "fdw_invalid_data_type_descriptors",
|
||||||
|
"HV091": "fdw_invalid_descriptor_field_identifier",
|
||||||
|
"HV00B": "fdw_invalid_handle",
|
||||||
|
"HV00C": "fdw_invalid_option_index",
|
||||||
|
"HV00D": "fdw_invalid_option_name",
|
||||||
|
"HV090": "fdw_invalid_string_length_or_buffer_length",
|
||||||
|
"HV00A": "fdw_invalid_string_format",
|
||||||
|
"HV009": "fdw_invalid_use_of_null_pointer",
|
||||||
|
"HV014": "fdw_too_many_handles",
|
||||||
|
"HV001": "fdw_out_of_memory",
|
||||||
|
"HV00P": "fdw_no_schemas",
|
||||||
|
"HV00J": "fdw_option_name_not_found",
|
||||||
|
"HV00K": "fdw_reply_handle",
|
||||||
|
"HV00Q": "fdw_schema_not_found",
|
||||||
|
"HV00R": "fdw_table_not_found",
|
||||||
|
"HV00L": "fdw_unable_to_create_execution",
|
||||||
|
"HV00M": "fdw_unable_to_create_reply",
|
||||||
|
"HV00N": "fdw_unable_to_establish_connection",
|
||||||
|
// Class P0 - PL/pgSQL Error
|
||||||
|
"P0000": "plpgsql_error",
|
||||||
|
"P0001": "raise_exception",
|
||||||
|
"P0002": "no_data_found",
|
||||||
|
"P0003": "too_many_rows",
|
||||||
|
// Class XX - Internal Error
|
||||||
|
"XX000": "internal_error",
|
||||||
|
"XX001": "data_corrupted",
|
||||||
|
"XX002": "index_corrupted",
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseError(r *readBuf) *Error {
|
||||||
|
err := new(Error)
|
||||||
|
for t := r.byte(); t != 0; t = r.byte() {
|
||||||
|
msg := r.string()
|
||||||
|
switch t {
|
||||||
|
case 'S':
|
||||||
|
err.Severity = msg
|
||||||
|
case 'C':
|
||||||
|
err.Code = ErrorCode(msg)
|
||||||
|
case 'M':
|
||||||
|
err.Message = msg
|
||||||
|
case 'D':
|
||||||
|
err.Detail = msg
|
||||||
|
case 'H':
|
||||||
|
err.Hint = msg
|
||||||
|
case 'P':
|
||||||
|
err.Position = msg
|
||||||
|
case 'p':
|
||||||
|
err.InternalPosition = msg
|
||||||
|
case 'q':
|
||||||
|
err.InternalQuery = msg
|
||||||
|
case 'W':
|
||||||
|
err.Where = msg
|
||||||
|
case 's':
|
||||||
|
err.Schema = msg
|
||||||
|
case 't':
|
||||||
|
err.Table = msg
|
||||||
|
case 'c':
|
||||||
|
err.Column = msg
|
||||||
|
case 'd':
|
||||||
|
err.DataTypeName = msg
|
||||||
|
case 'n':
|
||||||
|
err.Constraint = msg
|
||||||
|
case 'F':
|
||||||
|
err.File = msg
|
||||||
|
case 'L':
|
||||||
|
err.Line = msg
|
||||||
|
case 'R':
|
||||||
|
err.Routine = msg
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fatal returns true if the Error Severity is fatal.
|
||||||
|
func (err *Error) Fatal() bool {
|
||||||
|
return err.Severity == Efatal
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get implements the legacy PGError interface. New code should use the fields
|
||||||
|
// of the Error struct directly.
|
||||||
|
func (err *Error) Get(k byte) (v string) {
|
||||||
|
switch k {
|
||||||
|
case 'S':
|
||||||
|
return err.Severity
|
||||||
|
case 'C':
|
||||||
|
return string(err.Code)
|
||||||
|
case 'M':
|
||||||
|
return err.Message
|
||||||
|
case 'D':
|
||||||
|
return err.Detail
|
||||||
|
case 'H':
|
||||||
|
return err.Hint
|
||||||
|
case 'P':
|
||||||
|
return err.Position
|
||||||
|
case 'p':
|
||||||
|
return err.InternalPosition
|
||||||
|
case 'q':
|
||||||
|
return err.InternalQuery
|
||||||
|
case 'W':
|
||||||
|
return err.Where
|
||||||
|
case 's':
|
||||||
|
return err.Schema
|
||||||
|
case 't':
|
||||||
|
return err.Table
|
||||||
|
case 'c':
|
||||||
|
return err.Column
|
||||||
|
case 'd':
|
||||||
|
return err.DataTypeName
|
||||||
|
case 'n':
|
||||||
|
return err.Constraint
|
||||||
|
case 'F':
|
||||||
|
return err.File
|
||||||
|
case 'L':
|
||||||
|
return err.Line
|
||||||
|
case 'R':
|
||||||
|
return err.Routine
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func (err Error) Error() string {
|
||||||
|
return "pq: " + err.Message
|
||||||
|
}
|
||||||
|
|
||||||
|
// PGError is an interface used by previous versions of pq. It is provided
|
||||||
|
// only to support legacy code. New code should use the Error type.
|
||||||
|
type PGError interface {
|
||||||
|
Error() string
|
||||||
|
Fatal() bool
|
||||||
|
Get(k byte) (v string)
|
||||||
|
}
|
||||||
|
|
||||||
|
func errorf(s string, args ...interface{}) {
|
||||||
|
panic(fmt.Errorf("pq: %s", fmt.Sprintf(s, args...)))
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO(ainar-g) Rename to errorf after removing panics.
|
||||||
|
func fmterrorf(s string, args ...interface{}) error {
|
||||||
|
return fmt.Errorf("pq: %s", fmt.Sprintf(s, args...))
|
||||||
|
}
|
||||||
|
|
||||||
|
func errRecoverNoErrBadConn(err *error) {
|
||||||
|
e := recover()
|
||||||
|
if e == nil {
|
||||||
|
// Do nothing
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var ok bool
|
||||||
|
*err, ok = e.(error)
|
||||||
|
if !ok {
|
||||||
|
*err = fmt.Errorf("pq: unexpected error: %#v", e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cn *conn) errRecover(err *error) {
|
||||||
|
e := recover()
|
||||||
|
switch v := e.(type) {
|
||||||
|
case nil:
|
||||||
|
// Do nothing
|
||||||
|
case runtime.Error:
|
||||||
|
cn.err.set(driver.ErrBadConn)
|
||||||
|
panic(v)
|
||||||
|
case *Error:
|
||||||
|
if v.Fatal() {
|
||||||
|
*err = driver.ErrBadConn
|
||||||
|
} else {
|
||||||
|
*err = v
|
||||||
|
}
|
||||||
|
case *net.OpError:
|
||||||
|
cn.err.set(driver.ErrBadConn)
|
||||||
|
*err = v
|
||||||
|
case *safeRetryError:
|
||||||
|
cn.err.set(driver.ErrBadConn)
|
||||||
|
*err = driver.ErrBadConn
|
||||||
|
case error:
|
||||||
|
if v == io.EOF || v.Error() == "remote error: handshake failure" {
|
||||||
|
*err = driver.ErrBadConn
|
||||||
|
} else {
|
||||||
|
*err = v
|
||||||
|
}
|
||||||
|
|
||||||
|
default:
|
||||||
|
cn.err.set(driver.ErrBadConn)
|
||||||
|
panic(fmt.Sprintf("unknown error: %#v", e))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Any time we return ErrBadConn, we need to remember it since *Tx doesn't
|
||||||
|
// mark the connection bad in database/sql.
|
||||||
|
if *err == driver.ErrBadConn {
|
||||||
|
cn.err.set(driver.ErrBadConn)
|
||||||
|
}
|
||||||
|
}
|
27
vendor/github.com/lib/pq/krb.go
generated
vendored
Normal file
27
vendor/github.com/lib/pq/krb.go
generated
vendored
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
package pq
|
||||||
|
|
||||||
|
// NewGSSFunc creates a GSS authentication provider, for use with
|
||||||
|
// RegisterGSSProvider.
|
||||||
|
type NewGSSFunc func() (GSS, error)
|
||||||
|
|
||||||
|
var newGss NewGSSFunc
|
||||||
|
|
||||||
|
// RegisterGSSProvider registers a GSS authentication provider. For example, if
|
||||||
|
// you need to use Kerberos to authenticate with your server, add this to your
|
||||||
|
// main package:
|
||||||
|
//
|
||||||
|
// import "github.com/lib/pq/auth/kerberos"
|
||||||
|
//
|
||||||
|
// func init() {
|
||||||
|
// pq.RegisterGSSProvider(func() (pq.GSS, error) { return kerberos.NewGSS() })
|
||||||
|
// }
|
||||||
|
func RegisterGSSProvider(newGssArg NewGSSFunc) {
|
||||||
|
newGss = newGssArg
|
||||||
|
}
|
||||||
|
|
||||||
|
// GSS provides GSSAPI authentication (e.g., Kerberos).
|
||||||
|
type GSS interface {
|
||||||
|
GetInitToken(host string, service string) ([]byte, error)
|
||||||
|
GetInitTokenFromSpn(spn string) ([]byte, error)
|
||||||
|
Continue(inToken []byte) (done bool, outToken []byte, err error)
|
||||||
|
}
|
72
vendor/github.com/lib/pq/notice.go
generated
vendored
Normal file
72
vendor/github.com/lib/pq/notice.go
generated
vendored
Normal file
@ -0,0 +1,72 @@
|
|||||||
|
//go:build go1.10
|
||||||
|
// +build go1.10
|
||||||
|
|
||||||
|
package pq
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"database/sql/driver"
|
||||||
|
)
|
||||||
|
|
||||||
|
// NoticeHandler returns the notice handler on the given connection, if any. A
|
||||||
|
// runtime panic occurs if c is not a pq connection. This is rarely used
|
||||||
|
// directly, use ConnectorNoticeHandler and ConnectorWithNoticeHandler instead.
|
||||||
|
func NoticeHandler(c driver.Conn) func(*Error) {
|
||||||
|
return c.(*conn).noticeHandler
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetNoticeHandler sets the given notice handler on the given connection. A
|
||||||
|
// runtime panic occurs if c is not a pq connection. A nil handler may be used
|
||||||
|
// to unset it. This is rarely used directly, use ConnectorNoticeHandler and
|
||||||
|
// ConnectorWithNoticeHandler instead.
|
||||||
|
//
|
||||||
|
// Note: Notice handlers are executed synchronously by pq meaning commands
|
||||||
|
// won't continue to be processed until the handler returns.
|
||||||
|
func SetNoticeHandler(c driver.Conn, handler func(*Error)) {
|
||||||
|
c.(*conn).noticeHandler = handler
|
||||||
|
}
|
||||||
|
|
||||||
|
// NoticeHandlerConnector wraps a regular connector and sets a notice handler
|
||||||
|
// on it.
|
||||||
|
type NoticeHandlerConnector struct {
|
||||||
|
driver.Connector
|
||||||
|
noticeHandler func(*Error)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Connect calls the underlying connector's connect method and then sets the
|
||||||
|
// notice handler.
|
||||||
|
func (n *NoticeHandlerConnector) Connect(ctx context.Context) (driver.Conn, error) {
|
||||||
|
c, err := n.Connector.Connect(ctx)
|
||||||
|
if err == nil {
|
||||||
|
SetNoticeHandler(c, n.noticeHandler)
|
||||||
|
}
|
||||||
|
return c, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// ConnectorNoticeHandler returns the currently set notice handler, if any. If
|
||||||
|
// the given connector is not a result of ConnectorWithNoticeHandler, nil is
|
||||||
|
// returned.
|
||||||
|
func ConnectorNoticeHandler(c driver.Connector) func(*Error) {
|
||||||
|
if c, ok := c.(*NoticeHandlerConnector); ok {
|
||||||
|
return c.noticeHandler
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ConnectorWithNoticeHandler creates or sets the given handler for the given
|
||||||
|
// connector. If the given connector is a result of calling this function
|
||||||
|
// previously, it is simply set on the given connector and returned. Otherwise,
|
||||||
|
// this returns a new connector wrapping the given one and setting the notice
|
||||||
|
// handler. A nil notice handler may be used to unset it.
|
||||||
|
//
|
||||||
|
// The returned connector is intended to be used with database/sql.OpenDB.
|
||||||
|
//
|
||||||
|
// Note: Notice handlers are executed synchronously by pq meaning commands
|
||||||
|
// won't continue to be processed until the handler returns.
|
||||||
|
func ConnectorWithNoticeHandler(c driver.Connector, handler func(*Error)) *NoticeHandlerConnector {
|
||||||
|
if c, ok := c.(*NoticeHandlerConnector); ok {
|
||||||
|
c.noticeHandler = handler
|
||||||
|
return c
|
||||||
|
}
|
||||||
|
return &NoticeHandlerConnector{Connector: c, noticeHandler: handler}
|
||||||
|
}
|
858
vendor/github.com/lib/pq/notify.go
generated
vendored
Normal file
858
vendor/github.com/lib/pq/notify.go
generated
vendored
Normal file
@ -0,0 +1,858 @@
|
|||||||
|
package pq
|
||||||
|
|
||||||
|
// Package pq is a pure Go Postgres driver for the database/sql package.
|
||||||
|
// This module contains support for Postgres LISTEN/NOTIFY.
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"database/sql/driver"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"sync"
|
||||||
|
"sync/atomic"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Notification represents a single notification from the database.
|
||||||
|
type Notification struct {
|
||||||
|
// Process ID (PID) of the notifying postgres backend.
|
||||||
|
BePid int
|
||||||
|
// Name of the channel the notification was sent on.
|
||||||
|
Channel string
|
||||||
|
// Payload, or the empty string if unspecified.
|
||||||
|
Extra string
|
||||||
|
}
|
||||||
|
|
||||||
|
func recvNotification(r *readBuf) *Notification {
|
||||||
|
bePid := r.int32()
|
||||||
|
channel := r.string()
|
||||||
|
extra := r.string()
|
||||||
|
|
||||||
|
return &Notification{bePid, channel, extra}
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetNotificationHandler sets the given notification handler on the given
|
||||||
|
// connection. A runtime panic occurs if c is not a pq connection. A nil handler
|
||||||
|
// may be used to unset it.
|
||||||
|
//
|
||||||
|
// Note: Notification handlers are executed synchronously by pq meaning commands
|
||||||
|
// won't continue to be processed until the handler returns.
|
||||||
|
func SetNotificationHandler(c driver.Conn, handler func(*Notification)) {
|
||||||
|
c.(*conn).notificationHandler = handler
|
||||||
|
}
|
||||||
|
|
||||||
|
// NotificationHandlerConnector wraps a regular connector and sets a notification handler
|
||||||
|
// on it.
|
||||||
|
type NotificationHandlerConnector struct {
|
||||||
|
driver.Connector
|
||||||
|
notificationHandler func(*Notification)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Connect calls the underlying connector's connect method and then sets the
|
||||||
|
// notification handler.
|
||||||
|
func (n *NotificationHandlerConnector) Connect(ctx context.Context) (driver.Conn, error) {
|
||||||
|
c, err := n.Connector.Connect(ctx)
|
||||||
|
if err == nil {
|
||||||
|
SetNotificationHandler(c, n.notificationHandler)
|
||||||
|
}
|
||||||
|
return c, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// ConnectorNotificationHandler returns the currently set notification handler, if any. If
|
||||||
|
// the given connector is not a result of ConnectorWithNotificationHandler, nil is
|
||||||
|
// returned.
|
||||||
|
func ConnectorNotificationHandler(c driver.Connector) func(*Notification) {
|
||||||
|
if c, ok := c.(*NotificationHandlerConnector); ok {
|
||||||
|
return c.notificationHandler
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ConnectorWithNotificationHandler creates or sets the given handler for the given
|
||||||
|
// connector. If the given connector is a result of calling this function
|
||||||
|
// previously, it is simply set on the given connector and returned. Otherwise,
|
||||||
|
// this returns a new connector wrapping the given one and setting the notification
|
||||||
|
// handler. A nil notification handler may be used to unset it.
|
||||||
|
//
|
||||||
|
// The returned connector is intended to be used with database/sql.OpenDB.
|
||||||
|
//
|
||||||
|
// Note: Notification handlers are executed synchronously by pq meaning commands
|
||||||
|
// won't continue to be processed until the handler returns.
|
||||||
|
func ConnectorWithNotificationHandler(c driver.Connector, handler func(*Notification)) *NotificationHandlerConnector {
|
||||||
|
if c, ok := c.(*NotificationHandlerConnector); ok {
|
||||||
|
c.notificationHandler = handler
|
||||||
|
return c
|
||||||
|
}
|
||||||
|
return &NotificationHandlerConnector{Connector: c, notificationHandler: handler}
|
||||||
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
connStateIdle int32 = iota
|
||||||
|
connStateExpectResponse
|
||||||
|
connStateExpectReadyForQuery
|
||||||
|
)
|
||||||
|
|
||||||
|
type message struct {
|
||||||
|
typ byte
|
||||||
|
err error
|
||||||
|
}
|
||||||
|
|
||||||
|
var errListenerConnClosed = errors.New("pq: ListenerConn has been closed")
|
||||||
|
|
||||||
|
// ListenerConn is a low-level interface for waiting for notifications. You
|
||||||
|
// should use Listener instead.
|
||||||
|
type ListenerConn struct {
|
||||||
|
// guards cn and err
|
||||||
|
connectionLock sync.Mutex
|
||||||
|
cn *conn
|
||||||
|
err error
|
||||||
|
|
||||||
|
connState int32
|
||||||
|
|
||||||
|
// the sending goroutine will be holding this lock
|
||||||
|
senderLock sync.Mutex
|
||||||
|
|
||||||
|
notificationChan chan<- *Notification
|
||||||
|
|
||||||
|
replyChan chan message
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewListenerConn creates a new ListenerConn. Use NewListener instead.
|
||||||
|
func NewListenerConn(name string, notificationChan chan<- *Notification) (*ListenerConn, error) {
|
||||||
|
return newDialListenerConn(defaultDialer{}, name, notificationChan)
|
||||||
|
}
|
||||||
|
|
||||||
|
func newDialListenerConn(d Dialer, name string, c chan<- *Notification) (*ListenerConn, error) {
|
||||||
|
cn, err := DialOpen(d, name)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
l := &ListenerConn{
|
||||||
|
cn: cn.(*conn),
|
||||||
|
notificationChan: c,
|
||||||
|
connState: connStateIdle,
|
||||||
|
replyChan: make(chan message, 2),
|
||||||
|
}
|
||||||
|
|
||||||
|
go l.listenerConnMain()
|
||||||
|
|
||||||
|
return l, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// We can only allow one goroutine at a time to be running a query on the
|
||||||
|
// connection for various reasons, so the goroutine sending on the connection
|
||||||
|
// must be holding senderLock.
|
||||||
|
//
|
||||||
|
// Returns an error if an unrecoverable error has occurred and the ListenerConn
|
||||||
|
// should be abandoned.
|
||||||
|
func (l *ListenerConn) acquireSenderLock() error {
|
||||||
|
// we must acquire senderLock first to avoid deadlocks; see ExecSimpleQuery
|
||||||
|
l.senderLock.Lock()
|
||||||
|
|
||||||
|
l.connectionLock.Lock()
|
||||||
|
err := l.err
|
||||||
|
l.connectionLock.Unlock()
|
||||||
|
if err != nil {
|
||||||
|
l.senderLock.Unlock()
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *ListenerConn) releaseSenderLock() {
|
||||||
|
l.senderLock.Unlock()
|
||||||
|
}
|
||||||
|
|
||||||
|
// setState advances the protocol state to newState. Returns false if moving
|
||||||
|
// to that state from the current state is not allowed.
|
||||||
|
func (l *ListenerConn) setState(newState int32) bool {
|
||||||
|
var expectedState int32
|
||||||
|
|
||||||
|
switch newState {
|
||||||
|
case connStateIdle:
|
||||||
|
expectedState = connStateExpectReadyForQuery
|
||||||
|
case connStateExpectResponse:
|
||||||
|
expectedState = connStateIdle
|
||||||
|
case connStateExpectReadyForQuery:
|
||||||
|
expectedState = connStateExpectResponse
|
||||||
|
default:
|
||||||
|
panic(fmt.Sprintf("unexpected listenerConnState %d", newState))
|
||||||
|
}
|
||||||
|
|
||||||
|
return atomic.CompareAndSwapInt32(&l.connState, expectedState, newState)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Main logic is here: receive messages from the postgres backend, forward
|
||||||
|
// notifications and query replies and keep the internal state in sync with the
|
||||||
|
// protocol state. Returns when the connection has been lost, is about to go
|
||||||
|
// away or should be discarded because we couldn't agree on the state with the
|
||||||
|
// server backend.
|
||||||
|
func (l *ListenerConn) listenerConnLoop() (err error) {
|
||||||
|
defer errRecoverNoErrBadConn(&err)
|
||||||
|
|
||||||
|
r := &readBuf{}
|
||||||
|
for {
|
||||||
|
t, err := l.cn.recvMessage(r)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
switch t {
|
||||||
|
case 'A':
|
||||||
|
// recvNotification copies all the data so we don't need to worry
|
||||||
|
// about the scratch buffer being overwritten.
|
||||||
|
l.notificationChan <- recvNotification(r)
|
||||||
|
|
||||||
|
case 'T', 'D':
|
||||||
|
// only used by tests; ignore
|
||||||
|
|
||||||
|
case 'E':
|
||||||
|
// We might receive an ErrorResponse even when not in a query; it
|
||||||
|
// is expected that the server will close the connection after
|
||||||
|
// that, but we should make sure that the error we display is the
|
||||||
|
// one from the stray ErrorResponse, not io.ErrUnexpectedEOF.
|
||||||
|
if !l.setState(connStateExpectReadyForQuery) {
|
||||||
|
return parseError(r)
|
||||||
|
}
|
||||||
|
l.replyChan <- message{t, parseError(r)}
|
||||||
|
|
||||||
|
case 'C', 'I':
|
||||||
|
if !l.setState(connStateExpectReadyForQuery) {
|
||||||
|
// protocol out of sync
|
||||||
|
return fmt.Errorf("unexpected CommandComplete")
|
||||||
|
}
|
||||||
|
// ExecSimpleQuery doesn't need to know about this message
|
||||||
|
|
||||||
|
case 'Z':
|
||||||
|
if !l.setState(connStateIdle) {
|
||||||
|
// protocol out of sync
|
||||||
|
return fmt.Errorf("unexpected ReadyForQuery")
|
||||||
|
}
|
||||||
|
l.replyChan <- message{t, nil}
|
||||||
|
|
||||||
|
case 'S':
|
||||||
|
// ignore
|
||||||
|
case 'N':
|
||||||
|
if n := l.cn.noticeHandler; n != nil {
|
||||||
|
n(parseError(r))
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return fmt.Errorf("unexpected message %q from server in listenerConnLoop", t)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// This is the main routine for the goroutine receiving on the database
|
||||||
|
// connection. Most of the main logic is in listenerConnLoop.
|
||||||
|
func (l *ListenerConn) listenerConnMain() {
|
||||||
|
err := l.listenerConnLoop()
|
||||||
|
|
||||||
|
// listenerConnLoop terminated; we're done, but we still have to clean up.
|
||||||
|
// Make sure nobody tries to start any new queries by making sure the err
|
||||||
|
// pointer is set. It is important that we do not overwrite its value; a
|
||||||
|
// connection could be closed by either this goroutine or one sending on
|
||||||
|
// the connection -- whoever closes the connection is assumed to have the
|
||||||
|
// more meaningful error message (as the other one will probably get
|
||||||
|
// net.errClosed), so that goroutine sets the error we expose while the
|
||||||
|
// other error is discarded. If the connection is lost while two
|
||||||
|
// goroutines are operating on the socket, it probably doesn't matter which
|
||||||
|
// error we expose so we don't try to do anything more complex.
|
||||||
|
l.connectionLock.Lock()
|
||||||
|
if l.err == nil {
|
||||||
|
l.err = err
|
||||||
|
}
|
||||||
|
l.cn.Close()
|
||||||
|
l.connectionLock.Unlock()
|
||||||
|
|
||||||
|
// There might be a query in-flight; make sure nobody's waiting for a
|
||||||
|
// response to it, since there's not going to be one.
|
||||||
|
close(l.replyChan)
|
||||||
|
|
||||||
|
// let the listener know we're done
|
||||||
|
close(l.notificationChan)
|
||||||
|
|
||||||
|
// this ListenerConn is done
|
||||||
|
}
|
||||||
|
|
||||||
|
// Listen sends a LISTEN query to the server. See ExecSimpleQuery.
|
||||||
|
func (l *ListenerConn) Listen(channel string) (bool, error) {
|
||||||
|
return l.ExecSimpleQuery("LISTEN " + QuoteIdentifier(channel))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Unlisten sends an UNLISTEN query to the server. See ExecSimpleQuery.
|
||||||
|
func (l *ListenerConn) Unlisten(channel string) (bool, error) {
|
||||||
|
return l.ExecSimpleQuery("UNLISTEN " + QuoteIdentifier(channel))
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnlistenAll sends an `UNLISTEN *` query to the server. See ExecSimpleQuery.
|
||||||
|
func (l *ListenerConn) UnlistenAll() (bool, error) {
|
||||||
|
return l.ExecSimpleQuery("UNLISTEN *")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ping the remote server to make sure it's alive. Non-nil error means the
|
||||||
|
// connection has failed and should be abandoned.
|
||||||
|
func (l *ListenerConn) Ping() error {
|
||||||
|
sent, err := l.ExecSimpleQuery("")
|
||||||
|
if !sent {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
// shouldn't happen
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Attempt to send a query on the connection. Returns an error if sending the
|
||||||
|
// query failed, and the caller should initiate closure of this connection.
|
||||||
|
// The caller must be holding senderLock (see acquireSenderLock and
|
||||||
|
// releaseSenderLock).
|
||||||
|
func (l *ListenerConn) sendSimpleQuery(q string) (err error) {
|
||||||
|
defer errRecoverNoErrBadConn(&err)
|
||||||
|
|
||||||
|
// must set connection state before sending the query
|
||||||
|
if !l.setState(connStateExpectResponse) {
|
||||||
|
panic("two queries running at the same time")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Can't use l.cn.writeBuf here because it uses the scratch buffer which
|
||||||
|
// might get overwritten by listenerConnLoop.
|
||||||
|
b := &writeBuf{
|
||||||
|
buf: []byte("Q\x00\x00\x00\x00"),
|
||||||
|
pos: 1,
|
||||||
|
}
|
||||||
|
b.string(q)
|
||||||
|
l.cn.send(b)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ExecSimpleQuery executes a "simple query" (i.e. one with no bindable
|
||||||
|
// parameters) on the connection. The possible return values are:
|
||||||
|
// 1) "executed" is true; the query was executed to completion on the
|
||||||
|
// database server. If the query failed, err will be set to the error
|
||||||
|
// returned by the database, otherwise err will be nil.
|
||||||
|
// 2) If "executed" is false, the query could not be executed on the remote
|
||||||
|
// server. err will be non-nil.
|
||||||
|
//
|
||||||
|
// After a call to ExecSimpleQuery has returned an executed=false value, the
|
||||||
|
// connection has either been closed or will be closed shortly thereafter, and
|
||||||
|
// all subsequently executed queries will return an error.
|
||||||
|
func (l *ListenerConn) ExecSimpleQuery(q string) (executed bool, err error) {
|
||||||
|
if err = l.acquireSenderLock(); err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
defer l.releaseSenderLock()
|
||||||
|
|
||||||
|
err = l.sendSimpleQuery(q)
|
||||||
|
if err != nil {
|
||||||
|
// We can't know what state the protocol is in, so we need to abandon
|
||||||
|
// this connection.
|
||||||
|
l.connectionLock.Lock()
|
||||||
|
// Set the error pointer if it hasn't been set already; see
|
||||||
|
// listenerConnMain.
|
||||||
|
if l.err == nil {
|
||||||
|
l.err = err
|
||||||
|
}
|
||||||
|
l.connectionLock.Unlock()
|
||||||
|
l.cn.c.Close()
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// now we just wait for a reply..
|
||||||
|
for {
|
||||||
|
m, ok := <-l.replyChan
|
||||||
|
if !ok {
|
||||||
|
// We lost the connection to server, don't bother waiting for a
|
||||||
|
// a response. err should have been set already.
|
||||||
|
l.connectionLock.Lock()
|
||||||
|
err := l.err
|
||||||
|
l.connectionLock.Unlock()
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
switch m.typ {
|
||||||
|
case 'Z':
|
||||||
|
// sanity check
|
||||||
|
if m.err != nil {
|
||||||
|
panic("m.err != nil")
|
||||||
|
}
|
||||||
|
// done; err might or might not be set
|
||||||
|
return true, err
|
||||||
|
|
||||||
|
case 'E':
|
||||||
|
// sanity check
|
||||||
|
if m.err == nil {
|
||||||
|
panic("m.err == nil")
|
||||||
|
}
|
||||||
|
// server responded with an error; ReadyForQuery to follow
|
||||||
|
err = m.err
|
||||||
|
|
||||||
|
default:
|
||||||
|
return false, fmt.Errorf("unknown response for simple query: %q", m.typ)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close closes the connection.
|
||||||
|
func (l *ListenerConn) Close() error {
|
||||||
|
l.connectionLock.Lock()
|
||||||
|
if l.err != nil {
|
||||||
|
l.connectionLock.Unlock()
|
||||||
|
return errListenerConnClosed
|
||||||
|
}
|
||||||
|
l.err = errListenerConnClosed
|
||||||
|
l.connectionLock.Unlock()
|
||||||
|
// We can't send anything on the connection without holding senderLock.
|
||||||
|
// Simply close the net.Conn to wake up everyone operating on it.
|
||||||
|
return l.cn.c.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Err returns the reason the connection was closed. It is not safe to call
|
||||||
|
// this function until l.Notify has been closed.
|
||||||
|
func (l *ListenerConn) Err() error {
|
||||||
|
return l.err
|
||||||
|
}
|
||||||
|
|
||||||
|
var errListenerClosed = errors.New("pq: Listener has been closed")
|
||||||
|
|
||||||
|
// ErrChannelAlreadyOpen is returned from Listen when a channel is already
|
||||||
|
// open.
|
||||||
|
var ErrChannelAlreadyOpen = errors.New("pq: channel is already open")
|
||||||
|
|
||||||
|
// ErrChannelNotOpen is returned from Unlisten when a channel is not open.
|
||||||
|
var ErrChannelNotOpen = errors.New("pq: channel is not open")
|
||||||
|
|
||||||
|
// ListenerEventType is an enumeration of listener event types.
|
||||||
|
type ListenerEventType int
|
||||||
|
|
||||||
|
const (
|
||||||
|
// ListenerEventConnected is emitted only when the database connection
|
||||||
|
// has been initially initialized. The err argument of the callback
|
||||||
|
// will always be nil.
|
||||||
|
ListenerEventConnected ListenerEventType = iota
|
||||||
|
|
||||||
|
// ListenerEventDisconnected is emitted after a database connection has
|
||||||
|
// been lost, either because of an error or because Close has been
|
||||||
|
// called. The err argument will be set to the reason the database
|
||||||
|
// connection was lost.
|
||||||
|
ListenerEventDisconnected
|
||||||
|
|
||||||
|
// ListenerEventReconnected is emitted after a database connection has
|
||||||
|
// been re-established after connection loss. The err argument of the
|
||||||
|
// callback will always be nil. After this event has been emitted, a
|
||||||
|
// nil pq.Notification is sent on the Listener.Notify channel.
|
||||||
|
ListenerEventReconnected
|
||||||
|
|
||||||
|
// ListenerEventConnectionAttemptFailed is emitted after a connection
|
||||||
|
// to the database was attempted, but failed. The err argument will be
|
||||||
|
// set to an error describing why the connection attempt did not
|
||||||
|
// succeed.
|
||||||
|
ListenerEventConnectionAttemptFailed
|
||||||
|
)
|
||||||
|
|
||||||
|
// EventCallbackType is the event callback type. See also ListenerEventType
|
||||||
|
// constants' documentation.
|
||||||
|
type EventCallbackType func(event ListenerEventType, err error)
|
||||||
|
|
||||||
|
// Listener provides an interface for listening to notifications from a
|
||||||
|
// PostgreSQL database. For general usage information, see section
|
||||||
|
// "Notifications".
|
||||||
|
//
|
||||||
|
// Listener can safely be used from concurrently running goroutines.
|
||||||
|
type Listener struct {
|
||||||
|
// Channel for receiving notifications from the database. In some cases a
|
||||||
|
// nil value will be sent. See section "Notifications" above.
|
||||||
|
Notify chan *Notification
|
||||||
|
|
||||||
|
name string
|
||||||
|
minReconnectInterval time.Duration
|
||||||
|
maxReconnectInterval time.Duration
|
||||||
|
dialer Dialer
|
||||||
|
eventCallback EventCallbackType
|
||||||
|
|
||||||
|
lock sync.Mutex
|
||||||
|
isClosed bool
|
||||||
|
reconnectCond *sync.Cond
|
||||||
|
cn *ListenerConn
|
||||||
|
connNotificationChan <-chan *Notification
|
||||||
|
channels map[string]struct{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewListener creates a new database connection dedicated to LISTEN / NOTIFY.
|
||||||
|
//
|
||||||
|
// name should be set to a connection string to be used to establish the
|
||||||
|
// database connection (see section "Connection String Parameters" above).
|
||||||
|
//
|
||||||
|
// minReconnectInterval controls the duration to wait before trying to
|
||||||
|
// re-establish the database connection after connection loss. After each
|
||||||
|
// consecutive failure this interval is doubled, until maxReconnectInterval is
|
||||||
|
// reached. Successfully completing the connection establishment procedure
|
||||||
|
// resets the interval back to minReconnectInterval.
|
||||||
|
//
|
||||||
|
// The last parameter eventCallback can be set to a function which will be
|
||||||
|
// called by the Listener when the state of the underlying database connection
|
||||||
|
// changes. This callback will be called by the goroutine which dispatches the
|
||||||
|
// notifications over the Notify channel, so you should try to avoid doing
|
||||||
|
// potentially time-consuming operations from the callback.
|
||||||
|
func NewListener(name string,
|
||||||
|
minReconnectInterval time.Duration,
|
||||||
|
maxReconnectInterval time.Duration,
|
||||||
|
eventCallback EventCallbackType) *Listener {
|
||||||
|
return NewDialListener(defaultDialer{}, name, minReconnectInterval, maxReconnectInterval, eventCallback)
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewDialListener is like NewListener but it takes a Dialer.
|
||||||
|
func NewDialListener(d Dialer,
|
||||||
|
name string,
|
||||||
|
minReconnectInterval time.Duration,
|
||||||
|
maxReconnectInterval time.Duration,
|
||||||
|
eventCallback EventCallbackType) *Listener {
|
||||||
|
|
||||||
|
l := &Listener{
|
||||||
|
name: name,
|
||||||
|
minReconnectInterval: minReconnectInterval,
|
||||||
|
maxReconnectInterval: maxReconnectInterval,
|
||||||
|
dialer: d,
|
||||||
|
eventCallback: eventCallback,
|
||||||
|
|
||||||
|
channels: make(map[string]struct{}),
|
||||||
|
|
||||||
|
Notify: make(chan *Notification, 32),
|
||||||
|
}
|
||||||
|
l.reconnectCond = sync.NewCond(&l.lock)
|
||||||
|
|
||||||
|
go l.listenerMain()
|
||||||
|
|
||||||
|
return l
|
||||||
|
}
|
||||||
|
|
||||||
|
// NotificationChannel returns the notification channel for this listener.
|
||||||
|
// This is the same channel as Notify, and will not be recreated during the
|
||||||
|
// life time of the Listener.
|
||||||
|
func (l *Listener) NotificationChannel() <-chan *Notification {
|
||||||
|
return l.Notify
|
||||||
|
}
|
||||||
|
|
||||||
|
// Listen starts listening for notifications on a channel. Calls to this
|
||||||
|
// function will block until an acknowledgement has been received from the
|
||||||
|
// server. Note that Listener automatically re-establishes the connection
|
||||||
|
// after connection loss, so this function may block indefinitely if the
|
||||||
|
// connection can not be re-established.
|
||||||
|
//
|
||||||
|
// Listen will only fail in three conditions:
|
||||||
|
// 1) The channel is already open. The returned error will be
|
||||||
|
// ErrChannelAlreadyOpen.
|
||||||
|
// 2) The query was executed on the remote server, but PostgreSQL returned an
|
||||||
|
// error message in response to the query. The returned error will be a
|
||||||
|
// pq.Error containing the information the server supplied.
|
||||||
|
// 3) Close is called on the Listener before the request could be completed.
|
||||||
|
//
|
||||||
|
// The channel name is case-sensitive.
|
||||||
|
func (l *Listener) Listen(channel string) error {
|
||||||
|
l.lock.Lock()
|
||||||
|
defer l.lock.Unlock()
|
||||||
|
|
||||||
|
if l.isClosed {
|
||||||
|
return errListenerClosed
|
||||||
|
}
|
||||||
|
|
||||||
|
// The server allows you to issue a LISTEN on a channel which is already
|
||||||
|
// open, but it seems useful to be able to detect this case to spot for
|
||||||
|
// mistakes in application logic. If the application genuinely does't
|
||||||
|
// care, it can check the exported error and ignore it.
|
||||||
|
_, exists := l.channels[channel]
|
||||||
|
if exists {
|
||||||
|
return ErrChannelAlreadyOpen
|
||||||
|
}
|
||||||
|
|
||||||
|
if l.cn != nil {
|
||||||
|
// If gotResponse is true but error is set, the query was executed on
|
||||||
|
// the remote server, but resulted in an error. This should be
|
||||||
|
// relatively rare, so it's fine if we just pass the error to our
|
||||||
|
// caller. However, if gotResponse is false, we could not complete the
|
||||||
|
// query on the remote server and our underlying connection is about
|
||||||
|
// to go away, so we only add relname to l.channels, and wait for
|
||||||
|
// resync() to take care of the rest.
|
||||||
|
gotResponse, err := l.cn.Listen(channel)
|
||||||
|
if gotResponse && err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
l.channels[channel] = struct{}{}
|
||||||
|
for l.cn == nil {
|
||||||
|
l.reconnectCond.Wait()
|
||||||
|
// we let go of the mutex for a while
|
||||||
|
if l.isClosed {
|
||||||
|
return errListenerClosed
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Unlisten removes a channel from the Listener's channel list. Returns
|
||||||
|
// ErrChannelNotOpen if the Listener is not listening on the specified channel.
|
||||||
|
// Returns immediately with no error if there is no connection. Note that you
|
||||||
|
// might still get notifications for this channel even after Unlisten has
|
||||||
|
// returned.
|
||||||
|
//
|
||||||
|
// The channel name is case-sensitive.
|
||||||
|
func (l *Listener) Unlisten(channel string) error {
|
||||||
|
l.lock.Lock()
|
||||||
|
defer l.lock.Unlock()
|
||||||
|
|
||||||
|
if l.isClosed {
|
||||||
|
return errListenerClosed
|
||||||
|
}
|
||||||
|
|
||||||
|
// Similarly to LISTEN, this is not an error in Postgres, but it seems
|
||||||
|
// useful to distinguish from the normal conditions.
|
||||||
|
_, exists := l.channels[channel]
|
||||||
|
if !exists {
|
||||||
|
return ErrChannelNotOpen
|
||||||
|
}
|
||||||
|
|
||||||
|
if l.cn != nil {
|
||||||
|
// Similarly to Listen (see comment in that function), the caller
|
||||||
|
// should only be bothered with an error if it came from the backend as
|
||||||
|
// a response to our query.
|
||||||
|
gotResponse, err := l.cn.Unlisten(channel)
|
||||||
|
if gotResponse && err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Don't bother waiting for resync if there's no connection.
|
||||||
|
delete(l.channels, channel)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnlistenAll removes all channels from the Listener's channel list. Returns
|
||||||
|
// immediately with no error if there is no connection. Note that you might
|
||||||
|
// still get notifications for any of the deleted channels even after
|
||||||
|
// UnlistenAll has returned.
|
||||||
|
func (l *Listener) UnlistenAll() error {
|
||||||
|
l.lock.Lock()
|
||||||
|
defer l.lock.Unlock()
|
||||||
|
|
||||||
|
if l.isClosed {
|
||||||
|
return errListenerClosed
|
||||||
|
}
|
||||||
|
|
||||||
|
if l.cn != nil {
|
||||||
|
// Similarly to Listen (see comment in that function), the caller
|
||||||
|
// should only be bothered with an error if it came from the backend as
|
||||||
|
// a response to our query.
|
||||||
|
gotResponse, err := l.cn.UnlistenAll()
|
||||||
|
if gotResponse && err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Don't bother waiting for resync if there's no connection.
|
||||||
|
l.channels = make(map[string]struct{})
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ping the remote server to make sure it's alive. Non-nil return value means
|
||||||
|
// that there is no active connection.
|
||||||
|
func (l *Listener) Ping() error {
|
||||||
|
l.lock.Lock()
|
||||||
|
defer l.lock.Unlock()
|
||||||
|
|
||||||
|
if l.isClosed {
|
||||||
|
return errListenerClosed
|
||||||
|
}
|
||||||
|
if l.cn == nil {
|
||||||
|
return errors.New("no connection")
|
||||||
|
}
|
||||||
|
|
||||||
|
return l.cn.Ping()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clean up after losing the server connection. Returns l.cn.Err(), which
|
||||||
|
// should have the reason the connection was lost.
|
||||||
|
func (l *Listener) disconnectCleanup() error {
|
||||||
|
l.lock.Lock()
|
||||||
|
defer l.lock.Unlock()
|
||||||
|
|
||||||
|
// sanity check; can't look at Err() until the channel has been closed
|
||||||
|
select {
|
||||||
|
case _, ok := <-l.connNotificationChan:
|
||||||
|
if ok {
|
||||||
|
panic("connNotificationChan not closed")
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
panic("connNotificationChan not closed")
|
||||||
|
}
|
||||||
|
|
||||||
|
err := l.cn.Err()
|
||||||
|
l.cn.Close()
|
||||||
|
l.cn = nil
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Synchronize the list of channels we want to be listening on with the server
|
||||||
|
// after the connection has been established.
|
||||||
|
func (l *Listener) resync(cn *ListenerConn, notificationChan <-chan *Notification) error {
|
||||||
|
doneChan := make(chan error)
|
||||||
|
go func(notificationChan <-chan *Notification) {
|
||||||
|
for channel := range l.channels {
|
||||||
|
// If we got a response, return that error to our caller as it's
|
||||||
|
// going to be more descriptive than cn.Err().
|
||||||
|
gotResponse, err := cn.Listen(channel)
|
||||||
|
if gotResponse && err != nil {
|
||||||
|
doneChan <- err
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we couldn't reach the server, wait for notificationChan to
|
||||||
|
// close and then return the error message from the connection, as
|
||||||
|
// per ListenerConn's interface.
|
||||||
|
if err != nil {
|
||||||
|
for range notificationChan {
|
||||||
|
}
|
||||||
|
doneChan <- cn.Err()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
doneChan <- nil
|
||||||
|
}(notificationChan)
|
||||||
|
|
||||||
|
// Ignore notifications while synchronization is going on to avoid
|
||||||
|
// deadlocks. We have to send a nil notification over Notify anyway as
|
||||||
|
// we can't possibly know which notifications (if any) were lost while
|
||||||
|
// the connection was down, so there's no reason to try and process
|
||||||
|
// these messages at all.
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case _, ok := <-notificationChan:
|
||||||
|
if !ok {
|
||||||
|
notificationChan = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
case err := <-doneChan:
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// caller should NOT be holding l.lock
|
||||||
|
func (l *Listener) closed() bool {
|
||||||
|
l.lock.Lock()
|
||||||
|
defer l.lock.Unlock()
|
||||||
|
|
||||||
|
return l.isClosed
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *Listener) connect() error {
|
||||||
|
notificationChan := make(chan *Notification, 32)
|
||||||
|
cn, err := newDialListenerConn(l.dialer, l.name, notificationChan)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
l.lock.Lock()
|
||||||
|
defer l.lock.Unlock()
|
||||||
|
|
||||||
|
err = l.resync(cn, notificationChan)
|
||||||
|
if err != nil {
|
||||||
|
cn.Close()
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
l.cn = cn
|
||||||
|
l.connNotificationChan = notificationChan
|
||||||
|
l.reconnectCond.Broadcast()
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close disconnects the Listener from the database and shuts it down.
|
||||||
|
// Subsequent calls to its methods will return an error. Close returns an
|
||||||
|
// error if the connection has already been closed.
|
||||||
|
func (l *Listener) Close() error {
|
||||||
|
l.lock.Lock()
|
||||||
|
defer l.lock.Unlock()
|
||||||
|
|
||||||
|
if l.isClosed {
|
||||||
|
return errListenerClosed
|
||||||
|
}
|
||||||
|
|
||||||
|
if l.cn != nil {
|
||||||
|
l.cn.Close()
|
||||||
|
}
|
||||||
|
l.isClosed = true
|
||||||
|
|
||||||
|
// Unblock calls to Listen()
|
||||||
|
l.reconnectCond.Broadcast()
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *Listener) emitEvent(event ListenerEventType, err error) {
|
||||||
|
if l.eventCallback != nil {
|
||||||
|
l.eventCallback(event, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Main logic here: maintain a connection to the server when possible, wait
|
||||||
|
// for notifications and emit events.
|
||||||
|
func (l *Listener) listenerConnLoop() {
|
||||||
|
var nextReconnect time.Time
|
||||||
|
|
||||||
|
reconnectInterval := l.minReconnectInterval
|
||||||
|
for {
|
||||||
|
for {
|
||||||
|
err := l.connect()
|
||||||
|
if err == nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
if l.closed() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
l.emitEvent(ListenerEventConnectionAttemptFailed, err)
|
||||||
|
|
||||||
|
time.Sleep(reconnectInterval)
|
||||||
|
reconnectInterval *= 2
|
||||||
|
if reconnectInterval > l.maxReconnectInterval {
|
||||||
|
reconnectInterval = l.maxReconnectInterval
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if nextReconnect.IsZero() {
|
||||||
|
l.emitEvent(ListenerEventConnected, nil)
|
||||||
|
} else {
|
||||||
|
l.emitEvent(ListenerEventReconnected, nil)
|
||||||
|
l.Notify <- nil
|
||||||
|
}
|
||||||
|
|
||||||
|
reconnectInterval = l.minReconnectInterval
|
||||||
|
nextReconnect = time.Now().Add(reconnectInterval)
|
||||||
|
|
||||||
|
for {
|
||||||
|
notification, ok := <-l.connNotificationChan
|
||||||
|
if !ok {
|
||||||
|
// lost connection, loop again
|
||||||
|
break
|
||||||
|
}
|
||||||
|
l.Notify <- notification
|
||||||
|
}
|
||||||
|
|
||||||
|
err := l.disconnectCleanup()
|
||||||
|
if l.closed() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
l.emitEvent(ListenerEventDisconnected, err)
|
||||||
|
|
||||||
|
time.Sleep(time.Until(nextReconnect))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *Listener) listenerMain() {
|
||||||
|
l.listenerConnLoop()
|
||||||
|
close(l.Notify)
|
||||||
|
}
|
6
vendor/github.com/lib/pq/oid/doc.go
generated
vendored
Normal file
6
vendor/github.com/lib/pq/oid/doc.go
generated
vendored
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
// Package oid contains OID constants
|
||||||
|
// as defined by the Postgres server.
|
||||||
|
package oid
|
||||||
|
|
||||||
|
// Oid is a Postgres Object ID.
|
||||||
|
type Oid uint32
|
343
vendor/github.com/lib/pq/oid/types.go
generated
vendored
Normal file
343
vendor/github.com/lib/pq/oid/types.go
generated
vendored
Normal file
@ -0,0 +1,343 @@
|
|||||||
|
// Code generated by gen.go. DO NOT EDIT.
|
||||||
|
|
||||||
|
package oid
|
||||||
|
|
||||||
|
const (
|
||||||
|
T_bool Oid = 16
|
||||||
|
T_bytea Oid = 17
|
||||||
|
T_char Oid = 18
|
||||||
|
T_name Oid = 19
|
||||||
|
T_int8 Oid = 20
|
||||||
|
T_int2 Oid = 21
|
||||||
|
T_int2vector Oid = 22
|
||||||
|
T_int4 Oid = 23
|
||||||
|
T_regproc Oid = 24
|
||||||
|
T_text Oid = 25
|
||||||
|
T_oid Oid = 26
|
||||||
|
T_tid Oid = 27
|
||||||
|
T_xid Oid = 28
|
||||||
|
T_cid Oid = 29
|
||||||
|
T_oidvector Oid = 30
|
||||||
|
T_pg_ddl_command Oid = 32
|
||||||
|
T_pg_type Oid = 71
|
||||||
|
T_pg_attribute Oid = 75
|
||||||
|
T_pg_proc Oid = 81
|
||||||
|
T_pg_class Oid = 83
|
||||||
|
T_json Oid = 114
|
||||||
|
T_xml Oid = 142
|
||||||
|
T__xml Oid = 143
|
||||||
|
T_pg_node_tree Oid = 194
|
||||||
|
T__json Oid = 199
|
||||||
|
T_smgr Oid = 210
|
||||||
|
T_index_am_handler Oid = 325
|
||||||
|
T_point Oid = 600
|
||||||
|
T_lseg Oid = 601
|
||||||
|
T_path Oid = 602
|
||||||
|
T_box Oid = 603
|
||||||
|
T_polygon Oid = 604
|
||||||
|
T_line Oid = 628
|
||||||
|
T__line Oid = 629
|
||||||
|
T_cidr Oid = 650
|
||||||
|
T__cidr Oid = 651
|
||||||
|
T_float4 Oid = 700
|
||||||
|
T_float8 Oid = 701
|
||||||
|
T_abstime Oid = 702
|
||||||
|
T_reltime Oid = 703
|
||||||
|
T_tinterval Oid = 704
|
||||||
|
T_unknown Oid = 705
|
||||||
|
T_circle Oid = 718
|
||||||
|
T__circle Oid = 719
|
||||||
|
T_money Oid = 790
|
||||||
|
T__money Oid = 791
|
||||||
|
T_macaddr Oid = 829
|
||||||
|
T_inet Oid = 869
|
||||||
|
T__bool Oid = 1000
|
||||||
|
T__bytea Oid = 1001
|
||||||
|
T__char Oid = 1002
|
||||||
|
T__name Oid = 1003
|
||||||
|
T__int2 Oid = 1005
|
||||||
|
T__int2vector Oid = 1006
|
||||||
|
T__int4 Oid = 1007
|
||||||
|
T__regproc Oid = 1008
|
||||||
|
T__text Oid = 1009
|
||||||
|
T__tid Oid = 1010
|
||||||
|
T__xid Oid = 1011
|
||||||
|
T__cid Oid = 1012
|
||||||
|
T__oidvector Oid = 1013
|
||||||
|
T__bpchar Oid = 1014
|
||||||
|
T__varchar Oid = 1015
|
||||||
|
T__int8 Oid = 1016
|
||||||
|
T__point Oid = 1017
|
||||||
|
T__lseg Oid = 1018
|
||||||
|
T__path Oid = 1019
|
||||||
|
T__box Oid = 1020
|
||||||
|
T__float4 Oid = 1021
|
||||||
|
T__float8 Oid = 1022
|
||||||
|
T__abstime Oid = 1023
|
||||||
|
T__reltime Oid = 1024
|
||||||
|
T__tinterval Oid = 1025
|
||||||
|
T__polygon Oid = 1027
|
||||||
|
T__oid Oid = 1028
|
||||||
|
T_aclitem Oid = 1033
|
||||||
|
T__aclitem Oid = 1034
|
||||||
|
T__macaddr Oid = 1040
|
||||||
|
T__inet Oid = 1041
|
||||||
|
T_bpchar Oid = 1042
|
||||||
|
T_varchar Oid = 1043
|
||||||
|
T_date Oid = 1082
|
||||||
|
T_time Oid = 1083
|
||||||
|
T_timestamp Oid = 1114
|
||||||
|
T__timestamp Oid = 1115
|
||||||
|
T__date Oid = 1182
|
||||||
|
T__time Oid = 1183
|
||||||
|
T_timestamptz Oid = 1184
|
||||||
|
T__timestamptz Oid = 1185
|
||||||
|
T_interval Oid = 1186
|
||||||
|
T__interval Oid = 1187
|
||||||
|
T__numeric Oid = 1231
|
||||||
|
T_pg_database Oid = 1248
|
||||||
|
T__cstring Oid = 1263
|
||||||
|
T_timetz Oid = 1266
|
||||||
|
T__timetz Oid = 1270
|
||||||
|
T_bit Oid = 1560
|
||||||
|
T__bit Oid = 1561
|
||||||
|
T_varbit Oid = 1562
|
||||||
|
T__varbit Oid = 1563
|
||||||
|
T_numeric Oid = 1700
|
||||||
|
T_refcursor Oid = 1790
|
||||||
|
T__refcursor Oid = 2201
|
||||||
|
T_regprocedure Oid = 2202
|
||||||
|
T_regoper Oid = 2203
|
||||||
|
T_regoperator Oid = 2204
|
||||||
|
T_regclass Oid = 2205
|
||||||
|
T_regtype Oid = 2206
|
||||||
|
T__regprocedure Oid = 2207
|
||||||
|
T__regoper Oid = 2208
|
||||||
|
T__regoperator Oid = 2209
|
||||||
|
T__regclass Oid = 2210
|
||||||
|
T__regtype Oid = 2211
|
||||||
|
T_record Oid = 2249
|
||||||
|
T_cstring Oid = 2275
|
||||||
|
T_any Oid = 2276
|
||||||
|
T_anyarray Oid = 2277
|
||||||
|
T_void Oid = 2278
|
||||||
|
T_trigger Oid = 2279
|
||||||
|
T_language_handler Oid = 2280
|
||||||
|
T_internal Oid = 2281
|
||||||
|
T_opaque Oid = 2282
|
||||||
|
T_anyelement Oid = 2283
|
||||||
|
T__record Oid = 2287
|
||||||
|
T_anynonarray Oid = 2776
|
||||||
|
T_pg_authid Oid = 2842
|
||||||
|
T_pg_auth_members Oid = 2843
|
||||||
|
T__txid_snapshot Oid = 2949
|
||||||
|
T_uuid Oid = 2950
|
||||||
|
T__uuid Oid = 2951
|
||||||
|
T_txid_snapshot Oid = 2970
|
||||||
|
T_fdw_handler Oid = 3115
|
||||||
|
T_pg_lsn Oid = 3220
|
||||||
|
T__pg_lsn Oid = 3221
|
||||||
|
T_tsm_handler Oid = 3310
|
||||||
|
T_anyenum Oid = 3500
|
||||||
|
T_tsvector Oid = 3614
|
||||||
|
T_tsquery Oid = 3615
|
||||||
|
T_gtsvector Oid = 3642
|
||||||
|
T__tsvector Oid = 3643
|
||||||
|
T__gtsvector Oid = 3644
|
||||||
|
T__tsquery Oid = 3645
|
||||||
|
T_regconfig Oid = 3734
|
||||||
|
T__regconfig Oid = 3735
|
||||||
|
T_regdictionary Oid = 3769
|
||||||
|
T__regdictionary Oid = 3770
|
||||||
|
T_jsonb Oid = 3802
|
||||||
|
T__jsonb Oid = 3807
|
||||||
|
T_anyrange Oid = 3831
|
||||||
|
T_event_trigger Oid = 3838
|
||||||
|
T_int4range Oid = 3904
|
||||||
|
T__int4range Oid = 3905
|
||||||
|
T_numrange Oid = 3906
|
||||||
|
T__numrange Oid = 3907
|
||||||
|
T_tsrange Oid = 3908
|
||||||
|
T__tsrange Oid = 3909
|
||||||
|
T_tstzrange Oid = 3910
|
||||||
|
T__tstzrange Oid = 3911
|
||||||
|
T_daterange Oid = 3912
|
||||||
|
T__daterange Oid = 3913
|
||||||
|
T_int8range Oid = 3926
|
||||||
|
T__int8range Oid = 3927
|
||||||
|
T_pg_shseclabel Oid = 4066
|
||||||
|
T_regnamespace Oid = 4089
|
||||||
|
T__regnamespace Oid = 4090
|
||||||
|
T_regrole Oid = 4096
|
||||||
|
T__regrole Oid = 4097
|
||||||
|
)
|
||||||
|
|
||||||
|
var TypeName = map[Oid]string{
|
||||||
|
T_bool: "BOOL",
|
||||||
|
T_bytea: "BYTEA",
|
||||||
|
T_char: "CHAR",
|
||||||
|
T_name: "NAME",
|
||||||
|
T_int8: "INT8",
|
||||||
|
T_int2: "INT2",
|
||||||
|
T_int2vector: "INT2VECTOR",
|
||||||
|
T_int4: "INT4",
|
||||||
|
T_regproc: "REGPROC",
|
||||||
|
T_text: "TEXT",
|
||||||
|
T_oid: "OID",
|
||||||
|
T_tid: "TID",
|
||||||
|
T_xid: "XID",
|
||||||
|
T_cid: "CID",
|
||||||
|
T_oidvector: "OIDVECTOR",
|
||||||
|
T_pg_ddl_command: "PG_DDL_COMMAND",
|
||||||
|
T_pg_type: "PG_TYPE",
|
||||||
|
T_pg_attribute: "PG_ATTRIBUTE",
|
||||||
|
T_pg_proc: "PG_PROC",
|
||||||
|
T_pg_class: "PG_CLASS",
|
||||||
|
T_json: "JSON",
|
||||||
|
T_xml: "XML",
|
||||||
|
T__xml: "_XML",
|
||||||
|
T_pg_node_tree: "PG_NODE_TREE",
|
||||||
|
T__json: "_JSON",
|
||||||
|
T_smgr: "SMGR",
|
||||||
|
T_index_am_handler: "INDEX_AM_HANDLER",
|
||||||
|
T_point: "POINT",
|
||||||
|
T_lseg: "LSEG",
|
||||||
|
T_path: "PATH",
|
||||||
|
T_box: "BOX",
|
||||||
|
T_polygon: "POLYGON",
|
||||||
|
T_line: "LINE",
|
||||||
|
T__line: "_LINE",
|
||||||
|
T_cidr: "CIDR",
|
||||||
|
T__cidr: "_CIDR",
|
||||||
|
T_float4: "FLOAT4",
|
||||||
|
T_float8: "FLOAT8",
|
||||||
|
T_abstime: "ABSTIME",
|
||||||
|
T_reltime: "RELTIME",
|
||||||
|
T_tinterval: "TINTERVAL",
|
||||||
|
T_unknown: "UNKNOWN",
|
||||||
|
T_circle: "CIRCLE",
|
||||||
|
T__circle: "_CIRCLE",
|
||||||
|
T_money: "MONEY",
|
||||||
|
T__money: "_MONEY",
|
||||||
|
T_macaddr: "MACADDR",
|
||||||
|
T_inet: "INET",
|
||||||
|
T__bool: "_BOOL",
|
||||||
|
T__bytea: "_BYTEA",
|
||||||
|
T__char: "_CHAR",
|
||||||
|
T__name: "_NAME",
|
||||||
|
T__int2: "_INT2",
|
||||||
|
T__int2vector: "_INT2VECTOR",
|
||||||
|
T__int4: "_INT4",
|
||||||
|
T__regproc: "_REGPROC",
|
||||||
|
T__text: "_TEXT",
|
||||||
|
T__tid: "_TID",
|
||||||
|
T__xid: "_XID",
|
||||||
|
T__cid: "_CID",
|
||||||
|
T__oidvector: "_OIDVECTOR",
|
||||||
|
T__bpchar: "_BPCHAR",
|
||||||
|
T__varchar: "_VARCHAR",
|
||||||
|
T__int8: "_INT8",
|
||||||
|
T__point: "_POINT",
|
||||||
|
T__lseg: "_LSEG",
|
||||||
|
T__path: "_PATH",
|
||||||
|
T__box: "_BOX",
|
||||||
|
T__float4: "_FLOAT4",
|
||||||
|
T__float8: "_FLOAT8",
|
||||||
|
T__abstime: "_ABSTIME",
|
||||||
|
T__reltime: "_RELTIME",
|
||||||
|
T__tinterval: "_TINTERVAL",
|
||||||
|
T__polygon: "_POLYGON",
|
||||||
|
T__oid: "_OID",
|
||||||
|
T_aclitem: "ACLITEM",
|
||||||
|
T__aclitem: "_ACLITEM",
|
||||||
|
T__macaddr: "_MACADDR",
|
||||||
|
T__inet: "_INET",
|
||||||
|
T_bpchar: "BPCHAR",
|
||||||
|
T_varchar: "VARCHAR",
|
||||||
|
T_date: "DATE",
|
||||||
|
T_time: "TIME",
|
||||||
|
T_timestamp: "TIMESTAMP",
|
||||||
|
T__timestamp: "_TIMESTAMP",
|
||||||
|
T__date: "_DATE",
|
||||||
|
T__time: "_TIME",
|
||||||
|
T_timestamptz: "TIMESTAMPTZ",
|
||||||
|
T__timestamptz: "_TIMESTAMPTZ",
|
||||||
|
T_interval: "INTERVAL",
|
||||||
|
T__interval: "_INTERVAL",
|
||||||
|
T__numeric: "_NUMERIC",
|
||||||
|
T_pg_database: "PG_DATABASE",
|
||||||
|
T__cstring: "_CSTRING",
|
||||||
|
T_timetz: "TIMETZ",
|
||||||
|
T__timetz: "_TIMETZ",
|
||||||
|
T_bit: "BIT",
|
||||||
|
T__bit: "_BIT",
|
||||||
|
T_varbit: "VARBIT",
|
||||||
|
T__varbit: "_VARBIT",
|
||||||
|
T_numeric: "NUMERIC",
|
||||||
|
T_refcursor: "REFCURSOR",
|
||||||
|
T__refcursor: "_REFCURSOR",
|
||||||
|
T_regprocedure: "REGPROCEDURE",
|
||||||
|
T_regoper: "REGOPER",
|
||||||
|
T_regoperator: "REGOPERATOR",
|
||||||
|
T_regclass: "REGCLASS",
|
||||||
|
T_regtype: "REGTYPE",
|
||||||
|
T__regprocedure: "_REGPROCEDURE",
|
||||||
|
T__regoper: "_REGOPER",
|
||||||
|
T__regoperator: "_REGOPERATOR",
|
||||||
|
T__regclass: "_REGCLASS",
|
||||||
|
T__regtype: "_REGTYPE",
|
||||||
|
T_record: "RECORD",
|
||||||
|
T_cstring: "CSTRING",
|
||||||
|
T_any: "ANY",
|
||||||
|
T_anyarray: "ANYARRAY",
|
||||||
|
T_void: "VOID",
|
||||||
|
T_trigger: "TRIGGER",
|
||||||
|
T_language_handler: "LANGUAGE_HANDLER",
|
||||||
|
T_internal: "INTERNAL",
|
||||||
|
T_opaque: "OPAQUE",
|
||||||
|
T_anyelement: "ANYELEMENT",
|
||||||
|
T__record: "_RECORD",
|
||||||
|
T_anynonarray: "ANYNONARRAY",
|
||||||
|
T_pg_authid: "PG_AUTHID",
|
||||||
|
T_pg_auth_members: "PG_AUTH_MEMBERS",
|
||||||
|
T__txid_snapshot: "_TXID_SNAPSHOT",
|
||||||
|
T_uuid: "UUID",
|
||||||
|
T__uuid: "_UUID",
|
||||||
|
T_txid_snapshot: "TXID_SNAPSHOT",
|
||||||
|
T_fdw_handler: "FDW_HANDLER",
|
||||||
|
T_pg_lsn: "PG_LSN",
|
||||||
|
T__pg_lsn: "_PG_LSN",
|
||||||
|
T_tsm_handler: "TSM_HANDLER",
|
||||||
|
T_anyenum: "ANYENUM",
|
||||||
|
T_tsvector: "TSVECTOR",
|
||||||
|
T_tsquery: "TSQUERY",
|
||||||
|
T_gtsvector: "GTSVECTOR",
|
||||||
|
T__tsvector: "_TSVECTOR",
|
||||||
|
T__gtsvector: "_GTSVECTOR",
|
||||||
|
T__tsquery: "_TSQUERY",
|
||||||
|
T_regconfig: "REGCONFIG",
|
||||||
|
T__regconfig: "_REGCONFIG",
|
||||||
|
T_regdictionary: "REGDICTIONARY",
|
||||||
|
T__regdictionary: "_REGDICTIONARY",
|
||||||
|
T_jsonb: "JSONB",
|
||||||
|
T__jsonb: "_JSONB",
|
||||||
|
T_anyrange: "ANYRANGE",
|
||||||
|
T_event_trigger: "EVENT_TRIGGER",
|
||||||
|
T_int4range: "INT4RANGE",
|
||||||
|
T__int4range: "_INT4RANGE",
|
||||||
|
T_numrange: "NUMRANGE",
|
||||||
|
T__numrange: "_NUMRANGE",
|
||||||
|
T_tsrange: "TSRANGE",
|
||||||
|
T__tsrange: "_TSRANGE",
|
||||||
|
T_tstzrange: "TSTZRANGE",
|
||||||
|
T__tstzrange: "_TSTZRANGE",
|
||||||
|
T_daterange: "DATERANGE",
|
||||||
|
T__daterange: "_DATERANGE",
|
||||||
|
T_int8range: "INT8RANGE",
|
||||||
|
T__int8range: "_INT8RANGE",
|
||||||
|
T_pg_shseclabel: "PG_SHSECLABEL",
|
||||||
|
T_regnamespace: "REGNAMESPACE",
|
||||||
|
T__regnamespace: "_REGNAMESPACE",
|
||||||
|
T_regrole: "REGROLE",
|
||||||
|
T__regrole: "_REGROLE",
|
||||||
|
}
|
93
vendor/github.com/lib/pq/rows.go
generated
vendored
Normal file
93
vendor/github.com/lib/pq/rows.go
generated
vendored
Normal file
@ -0,0 +1,93 @@
|
|||||||
|
package pq
|
||||||
|
|
||||||
|
import (
|
||||||
|
"math"
|
||||||
|
"reflect"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/lib/pq/oid"
|
||||||
|
)
|
||||||
|
|
||||||
|
const headerSize = 4
|
||||||
|
|
||||||
|
type fieldDesc struct {
|
||||||
|
// The object ID of the data type.
|
||||||
|
OID oid.Oid
|
||||||
|
// The data type size (see pg_type.typlen).
|
||||||
|
// Note that negative values denote variable-width types.
|
||||||
|
Len int
|
||||||
|
// The type modifier (see pg_attribute.atttypmod).
|
||||||
|
// The meaning of the modifier is type-specific.
|
||||||
|
Mod int
|
||||||
|
}
|
||||||
|
|
||||||
|
func (fd fieldDesc) Type() reflect.Type {
|
||||||
|
switch fd.OID {
|
||||||
|
case oid.T_int8:
|
||||||
|
return reflect.TypeOf(int64(0))
|
||||||
|
case oid.T_int4:
|
||||||
|
return reflect.TypeOf(int32(0))
|
||||||
|
case oid.T_int2:
|
||||||
|
return reflect.TypeOf(int16(0))
|
||||||
|
case oid.T_varchar, oid.T_text:
|
||||||
|
return reflect.TypeOf("")
|
||||||
|
case oid.T_bool:
|
||||||
|
return reflect.TypeOf(false)
|
||||||
|
case oid.T_date, oid.T_time, oid.T_timetz, oid.T_timestamp, oid.T_timestamptz:
|
||||||
|
return reflect.TypeOf(time.Time{})
|
||||||
|
case oid.T_bytea:
|
||||||
|
return reflect.TypeOf([]byte(nil))
|
||||||
|
default:
|
||||||
|
return reflect.TypeOf(new(interface{})).Elem()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (fd fieldDesc) Name() string {
|
||||||
|
return oid.TypeName[fd.OID]
|
||||||
|
}
|
||||||
|
|
||||||
|
func (fd fieldDesc) Length() (length int64, ok bool) {
|
||||||
|
switch fd.OID {
|
||||||
|
case oid.T_text, oid.T_bytea:
|
||||||
|
return math.MaxInt64, true
|
||||||
|
case oid.T_varchar, oid.T_bpchar:
|
||||||
|
return int64(fd.Mod - headerSize), true
|
||||||
|
default:
|
||||||
|
return 0, false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (fd fieldDesc) PrecisionScale() (precision, scale int64, ok bool) {
|
||||||
|
switch fd.OID {
|
||||||
|
case oid.T_numeric, oid.T__numeric:
|
||||||
|
mod := fd.Mod - headerSize
|
||||||
|
precision = int64((mod >> 16) & 0xffff)
|
||||||
|
scale = int64(mod & 0xffff)
|
||||||
|
return precision, scale, true
|
||||||
|
default:
|
||||||
|
return 0, 0, false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ColumnTypeScanType returns the value type that can be used to scan types into.
|
||||||
|
func (rs *rows) ColumnTypeScanType(index int) reflect.Type {
|
||||||
|
return rs.colTyps[index].Type()
|
||||||
|
}
|
||||||
|
|
||||||
|
// ColumnTypeDatabaseTypeName return the database system type name.
|
||||||
|
func (rs *rows) ColumnTypeDatabaseTypeName(index int) string {
|
||||||
|
return rs.colTyps[index].Name()
|
||||||
|
}
|
||||||
|
|
||||||
|
// ColumnTypeLength returns the length of the column type if the column is a
|
||||||
|
// variable length type. If the column is not a variable length type ok
|
||||||
|
// should return false.
|
||||||
|
func (rs *rows) ColumnTypeLength(index int) (length int64, ok bool) {
|
||||||
|
return rs.colTyps[index].Length()
|
||||||
|
}
|
||||||
|
|
||||||
|
// ColumnTypePrecisionScale should return the precision and scale for decimal
|
||||||
|
// types. If not applicable, ok should be false.
|
||||||
|
func (rs *rows) ColumnTypePrecisionScale(index int) (precision, scale int64, ok bool) {
|
||||||
|
return rs.colTyps[index].PrecisionScale()
|
||||||
|
}
|
264
vendor/github.com/lib/pq/scram/scram.go
generated
vendored
Normal file
264
vendor/github.com/lib/pq/scram/scram.go
generated
vendored
Normal file
@ -0,0 +1,264 @@
|
|||||||
|
// Copyright (c) 2014 - Gustavo Niemeyer <gustavo@niemeyer.net>
|
||||||
|
//
|
||||||
|
// All rights reserved.
|
||||||
|
//
|
||||||
|
// Redistribution and use in source and binary forms, with or without
|
||||||
|
// modification, are permitted provided that the following conditions are met:
|
||||||
|
//
|
||||||
|
// 1. Redistributions of source code must retain the above copyright notice, this
|
||||||
|
// list of conditions and the following disclaimer.
|
||||||
|
// 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||||
|
// this list of conditions and the following disclaimer in the documentation
|
||||||
|
// and/or other materials provided with the distribution.
|
||||||
|
//
|
||||||
|
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||||
|
// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||||
|
// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||||
|
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
|
||||||
|
// ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||||
|
// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||||
|
// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||||
|
// ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||||
|
// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
|
||||||
|
// Package scram implements a SCRAM-{SHA-1,etc} client per RFC5802.
|
||||||
|
//
|
||||||
|
// http://tools.ietf.org/html/rfc5802
|
||||||
|
//
|
||||||
|
package scram
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"crypto/hmac"
|
||||||
|
"crypto/rand"
|
||||||
|
"encoding/base64"
|
||||||
|
"fmt"
|
||||||
|
"hash"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Client implements a SCRAM-* client (SCRAM-SHA-1, SCRAM-SHA-256, etc).
|
||||||
|
//
|
||||||
|
// A Client may be used within a SASL conversation with logic resembling:
|
||||||
|
//
|
||||||
|
// var in []byte
|
||||||
|
// var client = scram.NewClient(sha1.New, user, pass)
|
||||||
|
// for client.Step(in) {
|
||||||
|
// out := client.Out()
|
||||||
|
// // send out to server
|
||||||
|
// in := serverOut
|
||||||
|
// }
|
||||||
|
// if client.Err() != nil {
|
||||||
|
// // auth failed
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
type Client struct {
|
||||||
|
newHash func() hash.Hash
|
||||||
|
|
||||||
|
user string
|
||||||
|
pass string
|
||||||
|
step int
|
||||||
|
out bytes.Buffer
|
||||||
|
err error
|
||||||
|
|
||||||
|
clientNonce []byte
|
||||||
|
serverNonce []byte
|
||||||
|
saltedPass []byte
|
||||||
|
authMsg bytes.Buffer
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewClient returns a new SCRAM-* client with the provided hash algorithm.
|
||||||
|
//
|
||||||
|
// For SCRAM-SHA-256, for example, use:
|
||||||
|
//
|
||||||
|
// client := scram.NewClient(sha256.New, user, pass)
|
||||||
|
//
|
||||||
|
func NewClient(newHash func() hash.Hash, user, pass string) *Client {
|
||||||
|
c := &Client{
|
||||||
|
newHash: newHash,
|
||||||
|
user: user,
|
||||||
|
pass: pass,
|
||||||
|
}
|
||||||
|
c.out.Grow(256)
|
||||||
|
c.authMsg.Grow(256)
|
||||||
|
return c
|
||||||
|
}
|
||||||
|
|
||||||
|
// Out returns the data to be sent to the server in the current step.
|
||||||
|
func (c *Client) Out() []byte {
|
||||||
|
if c.out.Len() == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return c.out.Bytes()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Err returns the error that occurred, or nil if there were no errors.
|
||||||
|
func (c *Client) Err() error {
|
||||||
|
return c.err
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetNonce sets the client nonce to the provided value.
|
||||||
|
// If not set, the nonce is generated automatically out of crypto/rand on the first step.
|
||||||
|
func (c *Client) SetNonce(nonce []byte) {
|
||||||
|
c.clientNonce = nonce
|
||||||
|
}
|
||||||
|
|
||||||
|
var escaper = strings.NewReplacer("=", "=3D", ",", "=2C")
|
||||||
|
|
||||||
|
// Step processes the incoming data from the server and makes the
|
||||||
|
// next round of data for the server available via Client.Out.
|
||||||
|
// Step returns false if there are no errors and more data is
|
||||||
|
// still expected.
|
||||||
|
func (c *Client) Step(in []byte) bool {
|
||||||
|
c.out.Reset()
|
||||||
|
if c.step > 2 || c.err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
c.step++
|
||||||
|
switch c.step {
|
||||||
|
case 1:
|
||||||
|
c.err = c.step1(in)
|
||||||
|
case 2:
|
||||||
|
c.err = c.step2(in)
|
||||||
|
case 3:
|
||||||
|
c.err = c.step3(in)
|
||||||
|
}
|
||||||
|
return c.step > 2 || c.err != nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) step1(in []byte) error {
|
||||||
|
if len(c.clientNonce) == 0 {
|
||||||
|
const nonceLen = 16
|
||||||
|
buf := make([]byte, nonceLen+b64.EncodedLen(nonceLen))
|
||||||
|
if _, err := rand.Read(buf[:nonceLen]); err != nil {
|
||||||
|
return fmt.Errorf("cannot read random SCRAM-SHA-256 nonce from operating system: %v", err)
|
||||||
|
}
|
||||||
|
c.clientNonce = buf[nonceLen:]
|
||||||
|
b64.Encode(c.clientNonce, buf[:nonceLen])
|
||||||
|
}
|
||||||
|
c.authMsg.WriteString("n=")
|
||||||
|
escaper.WriteString(&c.authMsg, c.user)
|
||||||
|
c.authMsg.WriteString(",r=")
|
||||||
|
c.authMsg.Write(c.clientNonce)
|
||||||
|
|
||||||
|
c.out.WriteString("n,,")
|
||||||
|
c.out.Write(c.authMsg.Bytes())
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var b64 = base64.StdEncoding
|
||||||
|
|
||||||
|
func (c *Client) step2(in []byte) error {
|
||||||
|
c.authMsg.WriteByte(',')
|
||||||
|
c.authMsg.Write(in)
|
||||||
|
|
||||||
|
fields := bytes.Split(in, []byte(","))
|
||||||
|
if len(fields) != 3 {
|
||||||
|
return fmt.Errorf("expected 3 fields in first SCRAM-SHA-256 server message, got %d: %q", len(fields), in)
|
||||||
|
}
|
||||||
|
if !bytes.HasPrefix(fields[0], []byte("r=")) || len(fields[0]) < 2 {
|
||||||
|
return fmt.Errorf("server sent an invalid SCRAM-SHA-256 nonce: %q", fields[0])
|
||||||
|
}
|
||||||
|
if !bytes.HasPrefix(fields[1], []byte("s=")) || len(fields[1]) < 6 {
|
||||||
|
return fmt.Errorf("server sent an invalid SCRAM-SHA-256 salt: %q", fields[1])
|
||||||
|
}
|
||||||
|
if !bytes.HasPrefix(fields[2], []byte("i=")) || len(fields[2]) < 6 {
|
||||||
|
return fmt.Errorf("server sent an invalid SCRAM-SHA-256 iteration count: %q", fields[2])
|
||||||
|
}
|
||||||
|
|
||||||
|
c.serverNonce = fields[0][2:]
|
||||||
|
if !bytes.HasPrefix(c.serverNonce, c.clientNonce) {
|
||||||
|
return fmt.Errorf("server SCRAM-SHA-256 nonce is not prefixed by client nonce: got %q, want %q+\"...\"", c.serverNonce, c.clientNonce)
|
||||||
|
}
|
||||||
|
|
||||||
|
salt := make([]byte, b64.DecodedLen(len(fields[1][2:])))
|
||||||
|
n, err := b64.Decode(salt, fields[1][2:])
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("cannot decode SCRAM-SHA-256 salt sent by server: %q", fields[1])
|
||||||
|
}
|
||||||
|
salt = salt[:n]
|
||||||
|
iterCount, err := strconv.Atoi(string(fields[2][2:]))
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("server sent an invalid SCRAM-SHA-256 iteration count: %q", fields[2])
|
||||||
|
}
|
||||||
|
c.saltPassword(salt, iterCount)
|
||||||
|
|
||||||
|
c.authMsg.WriteString(",c=biws,r=")
|
||||||
|
c.authMsg.Write(c.serverNonce)
|
||||||
|
|
||||||
|
c.out.WriteString("c=biws,r=")
|
||||||
|
c.out.Write(c.serverNonce)
|
||||||
|
c.out.WriteString(",p=")
|
||||||
|
c.out.Write(c.clientProof())
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) step3(in []byte) error {
|
||||||
|
var isv, ise bool
|
||||||
|
var fields = bytes.Split(in, []byte(","))
|
||||||
|
if len(fields) == 1 {
|
||||||
|
isv = bytes.HasPrefix(fields[0], []byte("v="))
|
||||||
|
ise = bytes.HasPrefix(fields[0], []byte("e="))
|
||||||
|
}
|
||||||
|
if ise {
|
||||||
|
return fmt.Errorf("SCRAM-SHA-256 authentication error: %s", fields[0][2:])
|
||||||
|
} else if !isv {
|
||||||
|
return fmt.Errorf("unsupported SCRAM-SHA-256 final message from server: %q", in)
|
||||||
|
}
|
||||||
|
if !bytes.Equal(c.serverSignature(), fields[0][2:]) {
|
||||||
|
return fmt.Errorf("cannot authenticate SCRAM-SHA-256 server signature: %q", fields[0][2:])
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) saltPassword(salt []byte, iterCount int) {
|
||||||
|
mac := hmac.New(c.newHash, []byte(c.pass))
|
||||||
|
mac.Write(salt)
|
||||||
|
mac.Write([]byte{0, 0, 0, 1})
|
||||||
|
ui := mac.Sum(nil)
|
||||||
|
hi := make([]byte, len(ui))
|
||||||
|
copy(hi, ui)
|
||||||
|
for i := 1; i < iterCount; i++ {
|
||||||
|
mac.Reset()
|
||||||
|
mac.Write(ui)
|
||||||
|
mac.Sum(ui[:0])
|
||||||
|
for j, b := range ui {
|
||||||
|
hi[j] ^= b
|
||||||
|
}
|
||||||
|
}
|
||||||
|
c.saltedPass = hi
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) clientProof() []byte {
|
||||||
|
mac := hmac.New(c.newHash, c.saltedPass)
|
||||||
|
mac.Write([]byte("Client Key"))
|
||||||
|
clientKey := mac.Sum(nil)
|
||||||
|
hash := c.newHash()
|
||||||
|
hash.Write(clientKey)
|
||||||
|
storedKey := hash.Sum(nil)
|
||||||
|
mac = hmac.New(c.newHash, storedKey)
|
||||||
|
mac.Write(c.authMsg.Bytes())
|
||||||
|
clientProof := mac.Sum(nil)
|
||||||
|
for i, b := range clientKey {
|
||||||
|
clientProof[i] ^= b
|
||||||
|
}
|
||||||
|
clientProof64 := make([]byte, b64.EncodedLen(len(clientProof)))
|
||||||
|
b64.Encode(clientProof64, clientProof)
|
||||||
|
return clientProof64
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) serverSignature() []byte {
|
||||||
|
mac := hmac.New(c.newHash, c.saltedPass)
|
||||||
|
mac.Write([]byte("Server Key"))
|
||||||
|
serverKey := mac.Sum(nil)
|
||||||
|
|
||||||
|
mac = hmac.New(c.newHash, serverKey)
|
||||||
|
mac.Write(c.authMsg.Bytes())
|
||||||
|
serverSignature := mac.Sum(nil)
|
||||||
|
|
||||||
|
encoded := make([]byte, b64.EncodedLen(len(serverSignature)))
|
||||||
|
b64.Encode(encoded, serverSignature)
|
||||||
|
return encoded
|
||||||
|
}
|
193
vendor/github.com/lib/pq/ssl.go
generated
vendored
Normal file
193
vendor/github.com/lib/pq/ssl.go
generated
vendored
Normal file
@ -0,0 +1,193 @@
|
|||||||
|
package pq
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/tls"
|
||||||
|
"crypto/x509"
|
||||||
|
"io/ioutil"
|
||||||
|
"net"
|
||||||
|
"os"
|
||||||
|
"os/user"
|
||||||
|
"path/filepath"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ssl generates a function to upgrade a net.Conn based on the "sslmode" and
|
||||||
|
// related settings. The function is nil when no upgrade should take place.
|
||||||
|
func ssl(o values) (func(net.Conn) (net.Conn, error), error) {
|
||||||
|
verifyCaOnly := false
|
||||||
|
tlsConf := tls.Config{}
|
||||||
|
switch mode := o["sslmode"]; mode {
|
||||||
|
// "require" is the default.
|
||||||
|
case "", "require":
|
||||||
|
// We must skip TLS's own verification since it requires full
|
||||||
|
// verification since Go 1.3.
|
||||||
|
tlsConf.InsecureSkipVerify = true
|
||||||
|
|
||||||
|
// From http://www.postgresql.org/docs/current/static/libpq-ssl.html:
|
||||||
|
//
|
||||||
|
// Note: For backwards compatibility with earlier versions of
|
||||||
|
// PostgreSQL, if a root CA file exists, the behavior of
|
||||||
|
// sslmode=require will be the same as that of verify-ca, meaning the
|
||||||
|
// server certificate is validated against the CA. Relying on this
|
||||||
|
// behavior is discouraged, and applications that need certificate
|
||||||
|
// validation should always use verify-ca or verify-full.
|
||||||
|
if sslrootcert, ok := o["sslrootcert"]; ok {
|
||||||
|
if _, err := os.Stat(sslrootcert); err == nil {
|
||||||
|
verifyCaOnly = true
|
||||||
|
} else {
|
||||||
|
delete(o, "sslrootcert")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case "verify-ca":
|
||||||
|
// We must skip TLS's own verification since it requires full
|
||||||
|
// verification since Go 1.3.
|
||||||
|
tlsConf.InsecureSkipVerify = true
|
||||||
|
verifyCaOnly = true
|
||||||
|
case "verify-full":
|
||||||
|
tlsConf.ServerName = o["host"]
|
||||||
|
case "disable":
|
||||||
|
return nil, nil
|
||||||
|
default:
|
||||||
|
return nil, fmterrorf(`unsupported sslmode %q; only "require" (default), "verify-full", "verify-ca", and "disable" supported`, mode)
|
||||||
|
}
|
||||||
|
|
||||||
|
err := sslClientCertificates(&tlsConf, o)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
err = sslCertificateAuthority(&tlsConf, o)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Accept renegotiation requests initiated by the backend.
|
||||||
|
//
|
||||||
|
// Renegotiation was deprecated then removed from PostgreSQL 9.5, but
|
||||||
|
// the default configuration of older versions has it enabled. Redshift
|
||||||
|
// also initiates renegotiations and cannot be reconfigured.
|
||||||
|
tlsConf.Renegotiation = tls.RenegotiateFreelyAsClient
|
||||||
|
|
||||||
|
return func(conn net.Conn) (net.Conn, error) {
|
||||||
|
client := tls.Client(conn, &tlsConf)
|
||||||
|
if verifyCaOnly {
|
||||||
|
err := sslVerifyCertificateAuthority(client, &tlsConf)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return client, nil
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// sslClientCertificates adds the certificate specified in the "sslcert" and
|
||||||
|
// "sslkey" settings, or if they aren't set, from the .postgresql directory
|
||||||
|
// in the user's home directory. The configured files must exist and have
|
||||||
|
// the correct permissions.
|
||||||
|
func sslClientCertificates(tlsConf *tls.Config, o values) error {
|
||||||
|
sslinline := o["sslinline"]
|
||||||
|
if sslinline == "true" {
|
||||||
|
cert, err := tls.X509KeyPair([]byte(o["sslcert"]), []byte(o["sslkey"]))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
tlsConf.Certificates = []tls.Certificate{cert}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// user.Current() might fail when cross-compiling. We have to ignore the
|
||||||
|
// error and continue without home directory defaults, since we wouldn't
|
||||||
|
// know from where to load them.
|
||||||
|
user, _ := user.Current()
|
||||||
|
|
||||||
|
// In libpq, the client certificate is only loaded if the setting is not blank.
|
||||||
|
//
|
||||||
|
// https://github.com/postgres/postgres/blob/REL9_6_2/src/interfaces/libpq/fe-secure-openssl.c#L1036-L1037
|
||||||
|
sslcert := o["sslcert"]
|
||||||
|
if len(sslcert) == 0 && user != nil {
|
||||||
|
sslcert = filepath.Join(user.HomeDir, ".postgresql", "postgresql.crt")
|
||||||
|
}
|
||||||
|
// https://github.com/postgres/postgres/blob/REL9_6_2/src/interfaces/libpq/fe-secure-openssl.c#L1045
|
||||||
|
if len(sslcert) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
// https://github.com/postgres/postgres/blob/REL9_6_2/src/interfaces/libpq/fe-secure-openssl.c#L1050:L1054
|
||||||
|
if _, err := os.Stat(sslcert); os.IsNotExist(err) {
|
||||||
|
return nil
|
||||||
|
} else if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// In libpq, the ssl key is only loaded if the setting is not blank.
|
||||||
|
//
|
||||||
|
// https://github.com/postgres/postgres/blob/REL9_6_2/src/interfaces/libpq/fe-secure-openssl.c#L1123-L1222
|
||||||
|
sslkey := o["sslkey"]
|
||||||
|
if len(sslkey) == 0 && user != nil {
|
||||||
|
sslkey = filepath.Join(user.HomeDir, ".postgresql", "postgresql.key")
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(sslkey) > 0 {
|
||||||
|
if err := sslKeyPermissions(sslkey); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
cert, err := tls.LoadX509KeyPair(sslcert, sslkey)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
tlsConf.Certificates = []tls.Certificate{cert}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// sslCertificateAuthority adds the RootCA specified in the "sslrootcert" setting.
|
||||||
|
func sslCertificateAuthority(tlsConf *tls.Config, o values) error {
|
||||||
|
// In libpq, the root certificate is only loaded if the setting is not blank.
|
||||||
|
//
|
||||||
|
// https://github.com/postgres/postgres/blob/REL9_6_2/src/interfaces/libpq/fe-secure-openssl.c#L950-L951
|
||||||
|
if sslrootcert := o["sslrootcert"]; len(sslrootcert) > 0 {
|
||||||
|
tlsConf.RootCAs = x509.NewCertPool()
|
||||||
|
|
||||||
|
sslinline := o["sslinline"]
|
||||||
|
|
||||||
|
var cert []byte
|
||||||
|
if sslinline == "true" {
|
||||||
|
cert = []byte(sslrootcert)
|
||||||
|
} else {
|
||||||
|
var err error
|
||||||
|
cert, err = ioutil.ReadFile(sslrootcert)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !tlsConf.RootCAs.AppendCertsFromPEM(cert) {
|
||||||
|
return fmterrorf("couldn't parse pem in sslrootcert")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// sslVerifyCertificateAuthority carries out a TLS handshake to the server and
|
||||||
|
// verifies the presented certificate against the CA, i.e. the one specified in
|
||||||
|
// sslrootcert or the system CA if sslrootcert was not specified.
|
||||||
|
func sslVerifyCertificateAuthority(client *tls.Conn, tlsConf *tls.Config) error {
|
||||||
|
err := client.Handshake()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
certs := client.ConnectionState().PeerCertificates
|
||||||
|
opts := x509.VerifyOptions{
|
||||||
|
DNSName: client.ConnectionState().ServerName,
|
||||||
|
Intermediates: x509.NewCertPool(),
|
||||||
|
Roots: tlsConf.RootCAs,
|
||||||
|
}
|
||||||
|
for i, cert := range certs {
|
||||||
|
if i == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
opts.Intermediates.AddCert(cert)
|
||||||
|
}
|
||||||
|
_, err = certs[0].Verify(opts)
|
||||||
|
return err
|
||||||
|
}
|
21
vendor/github.com/lib/pq/ssl_permissions.go
generated
vendored
Normal file
21
vendor/github.com/lib/pq/ssl_permissions.go
generated
vendored
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
//go:build !windows
|
||||||
|
// +build !windows
|
||||||
|
|
||||||
|
package pq
|
||||||
|
|
||||||
|
import "os"
|
||||||
|
|
||||||
|
// sslKeyPermissions checks the permissions on user-supplied ssl key files.
|
||||||
|
// The key file should have very little access.
|
||||||
|
//
|
||||||
|
// libpq does not check key file permissions on Windows.
|
||||||
|
func sslKeyPermissions(sslkey string) error {
|
||||||
|
info, err := os.Stat(sslkey)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if info.Mode().Perm()&0077 != 0 {
|
||||||
|
return ErrSSLKeyHasWorldPermissions
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
10
vendor/github.com/lib/pq/ssl_windows.go
generated
vendored
Normal file
10
vendor/github.com/lib/pq/ssl_windows.go
generated
vendored
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
//go:build windows
|
||||||
|
// +build windows
|
||||||
|
|
||||||
|
package pq
|
||||||
|
|
||||||
|
// sslKeyPermissions checks the permissions on user-supplied ssl key files.
|
||||||
|
// The key file should have very little access.
|
||||||
|
//
|
||||||
|
// libpq does not check key file permissions on Windows.
|
||||||
|
func sslKeyPermissions(string) error { return nil }
|
76
vendor/github.com/lib/pq/url.go
generated
vendored
Normal file
76
vendor/github.com/lib/pq/url.go
generated
vendored
Normal file
@ -0,0 +1,76 @@
|
|||||||
|
package pq
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net"
|
||||||
|
nurl "net/url"
|
||||||
|
"sort"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ParseURL no longer needs to be used by clients of this library since supplying a URL as a
|
||||||
|
// connection string to sql.Open() is now supported:
|
||||||
|
//
|
||||||
|
// sql.Open("postgres", "postgres://bob:secret@1.2.3.4:5432/mydb?sslmode=verify-full")
|
||||||
|
//
|
||||||
|
// It remains exported here for backwards-compatibility.
|
||||||
|
//
|
||||||
|
// ParseURL converts a url to a connection string for driver.Open.
|
||||||
|
// Example:
|
||||||
|
//
|
||||||
|
// "postgres://bob:secret@1.2.3.4:5432/mydb?sslmode=verify-full"
|
||||||
|
//
|
||||||
|
// converts to:
|
||||||
|
//
|
||||||
|
// "user=bob password=secret host=1.2.3.4 port=5432 dbname=mydb sslmode=verify-full"
|
||||||
|
//
|
||||||
|
// A minimal example:
|
||||||
|
//
|
||||||
|
// "postgres://"
|
||||||
|
//
|
||||||
|
// This will be blank, causing driver.Open to use all of the defaults
|
||||||
|
func ParseURL(url string) (string, error) {
|
||||||
|
u, err := nurl.Parse(url)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
if u.Scheme != "postgres" && u.Scheme != "postgresql" {
|
||||||
|
return "", fmt.Errorf("invalid connection protocol: %s", u.Scheme)
|
||||||
|
}
|
||||||
|
|
||||||
|
var kvs []string
|
||||||
|
escaper := strings.NewReplacer(`'`, `\'`, `\`, `\\`)
|
||||||
|
accrue := func(k, v string) {
|
||||||
|
if v != "" {
|
||||||
|
kvs = append(kvs, k+"='"+escaper.Replace(v)+"'")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if u.User != nil {
|
||||||
|
v := u.User.Username()
|
||||||
|
accrue("user", v)
|
||||||
|
|
||||||
|
v, _ = u.User.Password()
|
||||||
|
accrue("password", v)
|
||||||
|
}
|
||||||
|
|
||||||
|
if host, port, err := net.SplitHostPort(u.Host); err != nil {
|
||||||
|
accrue("host", u.Host)
|
||||||
|
} else {
|
||||||
|
accrue("host", host)
|
||||||
|
accrue("port", port)
|
||||||
|
}
|
||||||
|
|
||||||
|
if u.Path != "" {
|
||||||
|
accrue("dbname", u.Path[1:])
|
||||||
|
}
|
||||||
|
|
||||||
|
q := u.Query()
|
||||||
|
for k := range q {
|
||||||
|
accrue(k, q.Get(k))
|
||||||
|
}
|
||||||
|
|
||||||
|
sort.Strings(kvs) // Makes testing easier (not a performance concern)
|
||||||
|
return strings.Join(kvs, " "), nil
|
||||||
|
}
|
10
vendor/github.com/lib/pq/user_other.go
generated
vendored
Normal file
10
vendor/github.com/lib/pq/user_other.go
generated
vendored
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
// Package pq is a pure Go Postgres driver for the database/sql package.
|
||||||
|
|
||||||
|
//go:build js || android || hurd || zos
|
||||||
|
// +build js android hurd zos
|
||||||
|
|
||||||
|
package pq
|
||||||
|
|
||||||
|
func userCurrent() (string, error) {
|
||||||
|
return "", ErrCouldNotDetectUsername
|
||||||
|
}
|
25
vendor/github.com/lib/pq/user_posix.go
generated
vendored
Normal file
25
vendor/github.com/lib/pq/user_posix.go
generated
vendored
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
// Package pq is a pure Go Postgres driver for the database/sql package.
|
||||||
|
|
||||||
|
//go:build aix || darwin || dragonfly || freebsd || (linux && !android) || nacl || netbsd || openbsd || plan9 || solaris || rumprun || illumos
|
||||||
|
// +build aix darwin dragonfly freebsd linux,!android nacl netbsd openbsd plan9 solaris rumprun illumos
|
||||||
|
|
||||||
|
package pq
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"os/user"
|
||||||
|
)
|
||||||
|
|
||||||
|
func userCurrent() (string, error) {
|
||||||
|
u, err := user.Current()
|
||||||
|
if err == nil {
|
||||||
|
return u.Username, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
name := os.Getenv("USER")
|
||||||
|
if name != "" {
|
||||||
|
return name, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return "", ErrCouldNotDetectUsername
|
||||||
|
}
|
27
vendor/github.com/lib/pq/user_windows.go
generated
vendored
Normal file
27
vendor/github.com/lib/pq/user_windows.go
generated
vendored
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
// Package pq is a pure Go Postgres driver for the database/sql package.
|
||||||
|
package pq
|
||||||
|
|
||||||
|
import (
|
||||||
|
"path/filepath"
|
||||||
|
"syscall"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Perform Windows user name lookup identically to libpq.
|
||||||
|
//
|
||||||
|
// The PostgreSQL code makes use of the legacy Win32 function
|
||||||
|
// GetUserName, and that function has not been imported into stock Go.
|
||||||
|
// GetUserNameEx is available though, the difference being that a
|
||||||
|
// wider range of names are available. To get the output to be the
|
||||||
|
// same as GetUserName, only the base (or last) component of the
|
||||||
|
// result is returned.
|
||||||
|
func userCurrent() (string, error) {
|
||||||
|
pw_name := make([]uint16, 128)
|
||||||
|
pwname_size := uint32(len(pw_name)) - 1
|
||||||
|
err := syscall.GetUserNameEx(syscall.NameSamCompatible, &pw_name[0], &pwname_size)
|
||||||
|
if err != nil {
|
||||||
|
return "", ErrCouldNotDetectUsername
|
||||||
|
}
|
||||||
|
s := syscall.UTF16ToString(pw_name)
|
||||||
|
u := filepath.Base(s)
|
||||||
|
return u, nil
|
||||||
|
}
|
23
vendor/github.com/lib/pq/uuid.go
generated
vendored
Normal file
23
vendor/github.com/lib/pq/uuid.go
generated
vendored
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
package pq
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/hex"
|
||||||
|
"fmt"
|
||||||
|
)
|
||||||
|
|
||||||
|
// decodeUUIDBinary interprets the binary format of a uuid, returning it in text format.
|
||||||
|
func decodeUUIDBinary(src []byte) ([]byte, error) {
|
||||||
|
if len(src) != 16 {
|
||||||
|
return nil, fmt.Errorf("pq: unable to decode uuid; bad length: %d", len(src))
|
||||||
|
}
|
||||||
|
|
||||||
|
dst := make([]byte, 36)
|
||||||
|
dst[8], dst[13], dst[18], dst[23] = '-', '-', '-', '-'
|
||||||
|
hex.Encode(dst[0:], src[0:4])
|
||||||
|
hex.Encode(dst[9:], src[4:6])
|
||||||
|
hex.Encode(dst[14:], src[6:8])
|
||||||
|
hex.Encode(dst[19:], src[8:10])
|
||||||
|
hex.Encode(dst[24:], src[10:16])
|
||||||
|
|
||||||
|
return dst, nil
|
||||||
|
}
|
3
vendor/golang.org/x/crypto/AUTHORS
generated
vendored
Normal file
3
vendor/golang.org/x/crypto/AUTHORS
generated
vendored
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
# This source code refers to The Go Authors for copyright purposes.
|
||||||
|
# The master list of authors is in the main Go distribution,
|
||||||
|
# visible at https://tip.golang.org/AUTHORS.
|
3
vendor/golang.org/x/crypto/CONTRIBUTORS
generated
vendored
Normal file
3
vendor/golang.org/x/crypto/CONTRIBUTORS
generated
vendored
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
# This source code was written by the Go contributors.
|
||||||
|
# The master list of contributors is in the main Go distribution,
|
||||||
|
# visible at https://tip.golang.org/CONTRIBUTORS.
|
27
vendor/golang.org/x/crypto/LICENSE
generated
vendored
Normal file
27
vendor/golang.org/x/crypto/LICENSE
generated
vendored
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
Copyright (c) 2009 The Go Authors. All rights reserved.
|
||||||
|
|
||||||
|
Redistribution and use in source and binary forms, with or without
|
||||||
|
modification, are permitted provided that the following conditions are
|
||||||
|
met:
|
||||||
|
|
||||||
|
* Redistributions of source code must retain the above copyright
|
||||||
|
notice, this list of conditions and the following disclaimer.
|
||||||
|
* Redistributions in binary form must reproduce the above
|
||||||
|
copyright notice, this list of conditions and the following disclaimer
|
||||||
|
in the documentation and/or other materials provided with the
|
||||||
|
distribution.
|
||||||
|
* Neither the name of Google Inc. nor the names of its
|
||||||
|
contributors may be used to endorse or promote products derived from
|
||||||
|
this software without specific prior written permission.
|
||||||
|
|
||||||
|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||||
|
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||||
|
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||||
|
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||||
|
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||||
|
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||||
|
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||||
|
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||||
|
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||||
|
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
22
vendor/golang.org/x/crypto/PATENTS
generated
vendored
Normal file
22
vendor/golang.org/x/crypto/PATENTS
generated
vendored
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
Additional IP Rights Grant (Patents)
|
||||||
|
|
||||||
|
"This implementation" means the copyrightable works distributed by
|
||||||
|
Google as part of the Go project.
|
||||||
|
|
||||||
|
Google hereby grants to You a perpetual, worldwide, non-exclusive,
|
||||||
|
no-charge, royalty-free, irrevocable (except as stated in this section)
|
||||||
|
patent license to make, have made, use, offer to sell, sell, import,
|
||||||
|
transfer and otherwise run, modify and propagate the contents of this
|
||||||
|
implementation of Go, where such license applies only to those patent
|
||||||
|
claims, both currently owned or controlled by Google and acquired in
|
||||||
|
the future, licensable by Google that are necessarily infringed by this
|
||||||
|
implementation of Go. This grant does not include claims that would be
|
||||||
|
infringed only as a consequence of further modification of this
|
||||||
|
implementation. If you or your agent or exclusive licensee institute or
|
||||||
|
order or agree to the institution of patent litigation against any
|
||||||
|
entity (including a cross-claim or counterclaim in a lawsuit) alleging
|
||||||
|
that this implementation of Go or any code incorporated within this
|
||||||
|
implementation of Go constitutes direct or contributory patent
|
||||||
|
infringement, or inducement of patent infringement, then any patent
|
||||||
|
rights granted to you under this License for this implementation of Go
|
||||||
|
shall terminate as of the date such litigation is filed.
|
77
vendor/golang.org/x/crypto/pbkdf2/pbkdf2.go
generated
vendored
Normal file
77
vendor/golang.org/x/crypto/pbkdf2/pbkdf2.go
generated
vendored
Normal file
@ -0,0 +1,77 @@
|
|||||||
|
// Copyright 2012 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 pbkdf2 implements the key derivation function PBKDF2 as defined in RFC
|
||||||
|
2898 / PKCS #5 v2.0.
|
||||||
|
|
||||||
|
A key derivation function is useful when encrypting data based on a password
|
||||||
|
or any other not-fully-random data. It uses a pseudorandom function to derive
|
||||||
|
a secure encryption key based on the password.
|
||||||
|
|
||||||
|
While v2.0 of the standard defines only one pseudorandom function to use,
|
||||||
|
HMAC-SHA1, the drafted v2.1 specification allows use of all five FIPS Approved
|
||||||
|
Hash Functions SHA-1, SHA-224, SHA-256, SHA-384 and SHA-512 for HMAC. To
|
||||||
|
choose, you can pass the `New` functions from the different SHA packages to
|
||||||
|
pbkdf2.Key.
|
||||||
|
*/
|
||||||
|
package pbkdf2 // import "golang.org/x/crypto/pbkdf2"
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/hmac"
|
||||||
|
"hash"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Key derives a key from the password, salt and iteration count, returning a
|
||||||
|
// []byte of length keylen that can be used as cryptographic key. The key is
|
||||||
|
// derived based on the method described as PBKDF2 with the HMAC variant using
|
||||||
|
// the supplied hash function.
|
||||||
|
//
|
||||||
|
// For example, to use a HMAC-SHA-1 based PBKDF2 key derivation function, you
|
||||||
|
// can get a derived key for e.g. AES-256 (which needs a 32-byte key) by
|
||||||
|
// doing:
|
||||||
|
//
|
||||||
|
// dk := pbkdf2.Key([]byte("some password"), salt, 4096, 32, sha1.New)
|
||||||
|
//
|
||||||
|
// Remember to get a good random salt. At least 8 bytes is recommended by the
|
||||||
|
// RFC.
|
||||||
|
//
|
||||||
|
// Using a higher iteration count will increase the cost of an exhaustive
|
||||||
|
// search but will also make derivation proportionally slower.
|
||||||
|
func Key(password, salt []byte, iter, keyLen int, h func() hash.Hash) []byte {
|
||||||
|
prf := hmac.New(h, password)
|
||||||
|
hashLen := prf.Size()
|
||||||
|
numBlocks := (keyLen + hashLen - 1) / hashLen
|
||||||
|
|
||||||
|
var buf [4]byte
|
||||||
|
dk := make([]byte, 0, numBlocks*hashLen)
|
||||||
|
U := make([]byte, hashLen)
|
||||||
|
for block := 1; block <= numBlocks; block++ {
|
||||||
|
// N.B.: || means concatenation, ^ means XOR
|
||||||
|
// for each block T_i = U_1 ^ U_2 ^ ... ^ U_iter
|
||||||
|
// U_1 = PRF(password, salt || uint(i))
|
||||||
|
prf.Reset()
|
||||||
|
prf.Write(salt)
|
||||||
|
buf[0] = byte(block >> 24)
|
||||||
|
buf[1] = byte(block >> 16)
|
||||||
|
buf[2] = byte(block >> 8)
|
||||||
|
buf[3] = byte(block)
|
||||||
|
prf.Write(buf[:4])
|
||||||
|
dk = prf.Sum(dk)
|
||||||
|
T := dk[len(dk)-hashLen:]
|
||||||
|
copy(U, T)
|
||||||
|
|
||||||
|
// U_n = PRF(password, U_(n-1))
|
||||||
|
for n := 2; n <= iter; n++ {
|
||||||
|
prf.Reset()
|
||||||
|
prf.Write(U)
|
||||||
|
U = U[:0]
|
||||||
|
U = prf.Sum(U)
|
||||||
|
for x := range U {
|
||||||
|
T[x] ^= U[x]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return dk[:keyLen]
|
||||||
|
}
|
212
vendor/golang.org/x/crypto/scrypt/scrypt.go
generated
vendored
Normal file
212
vendor/golang.org/x/crypto/scrypt/scrypt.go
generated
vendored
Normal file
@ -0,0 +1,212 @@
|
|||||||
|
// Copyright 2012 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 scrypt implements the scrypt key derivation function as defined in
|
||||||
|
// Colin Percival's paper "Stronger Key Derivation via Sequential Memory-Hard
|
||||||
|
// Functions" (https://www.tarsnap.com/scrypt/scrypt.pdf).
|
||||||
|
package scrypt // import "golang.org/x/crypto/scrypt"
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/sha256"
|
||||||
|
"encoding/binary"
|
||||||
|
"errors"
|
||||||
|
"math/bits"
|
||||||
|
|
||||||
|
"golang.org/x/crypto/pbkdf2"
|
||||||
|
)
|
||||||
|
|
||||||
|
const maxInt = int(^uint(0) >> 1)
|
||||||
|
|
||||||
|
// blockCopy copies n numbers from src into dst.
|
||||||
|
func blockCopy(dst, src []uint32, n int) {
|
||||||
|
copy(dst, src[:n])
|
||||||
|
}
|
||||||
|
|
||||||
|
// blockXOR XORs numbers from dst with n numbers from src.
|
||||||
|
func blockXOR(dst, src []uint32, n int) {
|
||||||
|
for i, v := range src[:n] {
|
||||||
|
dst[i] ^= v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// salsaXOR applies Salsa20/8 to the XOR of 16 numbers from tmp and in,
|
||||||
|
// and puts the result into both tmp and out.
|
||||||
|
func salsaXOR(tmp *[16]uint32, in, out []uint32) {
|
||||||
|
w0 := tmp[0] ^ in[0]
|
||||||
|
w1 := tmp[1] ^ in[1]
|
||||||
|
w2 := tmp[2] ^ in[2]
|
||||||
|
w3 := tmp[3] ^ in[3]
|
||||||
|
w4 := tmp[4] ^ in[4]
|
||||||
|
w5 := tmp[5] ^ in[5]
|
||||||
|
w6 := tmp[6] ^ in[6]
|
||||||
|
w7 := tmp[7] ^ in[7]
|
||||||
|
w8 := tmp[8] ^ in[8]
|
||||||
|
w9 := tmp[9] ^ in[9]
|
||||||
|
w10 := tmp[10] ^ in[10]
|
||||||
|
w11 := tmp[11] ^ in[11]
|
||||||
|
w12 := tmp[12] ^ in[12]
|
||||||
|
w13 := tmp[13] ^ in[13]
|
||||||
|
w14 := tmp[14] ^ in[14]
|
||||||
|
w15 := tmp[15] ^ in[15]
|
||||||
|
|
||||||
|
x0, x1, x2, x3, x4, x5, x6, x7, x8 := w0, w1, w2, w3, w4, w5, w6, w7, w8
|
||||||
|
x9, x10, x11, x12, x13, x14, x15 := w9, w10, w11, w12, w13, w14, w15
|
||||||
|
|
||||||
|
for i := 0; i < 8; i += 2 {
|
||||||
|
x4 ^= bits.RotateLeft32(x0+x12, 7)
|
||||||
|
x8 ^= bits.RotateLeft32(x4+x0, 9)
|
||||||
|
x12 ^= bits.RotateLeft32(x8+x4, 13)
|
||||||
|
x0 ^= bits.RotateLeft32(x12+x8, 18)
|
||||||
|
|
||||||
|
x9 ^= bits.RotateLeft32(x5+x1, 7)
|
||||||
|
x13 ^= bits.RotateLeft32(x9+x5, 9)
|
||||||
|
x1 ^= bits.RotateLeft32(x13+x9, 13)
|
||||||
|
x5 ^= bits.RotateLeft32(x1+x13, 18)
|
||||||
|
|
||||||
|
x14 ^= bits.RotateLeft32(x10+x6, 7)
|
||||||
|
x2 ^= bits.RotateLeft32(x14+x10, 9)
|
||||||
|
x6 ^= bits.RotateLeft32(x2+x14, 13)
|
||||||
|
x10 ^= bits.RotateLeft32(x6+x2, 18)
|
||||||
|
|
||||||
|
x3 ^= bits.RotateLeft32(x15+x11, 7)
|
||||||
|
x7 ^= bits.RotateLeft32(x3+x15, 9)
|
||||||
|
x11 ^= bits.RotateLeft32(x7+x3, 13)
|
||||||
|
x15 ^= bits.RotateLeft32(x11+x7, 18)
|
||||||
|
|
||||||
|
x1 ^= bits.RotateLeft32(x0+x3, 7)
|
||||||
|
x2 ^= bits.RotateLeft32(x1+x0, 9)
|
||||||
|
x3 ^= bits.RotateLeft32(x2+x1, 13)
|
||||||
|
x0 ^= bits.RotateLeft32(x3+x2, 18)
|
||||||
|
|
||||||
|
x6 ^= bits.RotateLeft32(x5+x4, 7)
|
||||||
|
x7 ^= bits.RotateLeft32(x6+x5, 9)
|
||||||
|
x4 ^= bits.RotateLeft32(x7+x6, 13)
|
||||||
|
x5 ^= bits.RotateLeft32(x4+x7, 18)
|
||||||
|
|
||||||
|
x11 ^= bits.RotateLeft32(x10+x9, 7)
|
||||||
|
x8 ^= bits.RotateLeft32(x11+x10, 9)
|
||||||
|
x9 ^= bits.RotateLeft32(x8+x11, 13)
|
||||||
|
x10 ^= bits.RotateLeft32(x9+x8, 18)
|
||||||
|
|
||||||
|
x12 ^= bits.RotateLeft32(x15+x14, 7)
|
||||||
|
x13 ^= bits.RotateLeft32(x12+x15, 9)
|
||||||
|
x14 ^= bits.RotateLeft32(x13+x12, 13)
|
||||||
|
x15 ^= bits.RotateLeft32(x14+x13, 18)
|
||||||
|
}
|
||||||
|
x0 += w0
|
||||||
|
x1 += w1
|
||||||
|
x2 += w2
|
||||||
|
x3 += w3
|
||||||
|
x4 += w4
|
||||||
|
x5 += w5
|
||||||
|
x6 += w6
|
||||||
|
x7 += w7
|
||||||
|
x8 += w8
|
||||||
|
x9 += w9
|
||||||
|
x10 += w10
|
||||||
|
x11 += w11
|
||||||
|
x12 += w12
|
||||||
|
x13 += w13
|
||||||
|
x14 += w14
|
||||||
|
x15 += w15
|
||||||
|
|
||||||
|
out[0], tmp[0] = x0, x0
|
||||||
|
out[1], tmp[1] = x1, x1
|
||||||
|
out[2], tmp[2] = x2, x2
|
||||||
|
out[3], tmp[3] = x3, x3
|
||||||
|
out[4], tmp[4] = x4, x4
|
||||||
|
out[5], tmp[5] = x5, x5
|
||||||
|
out[6], tmp[6] = x6, x6
|
||||||
|
out[7], tmp[7] = x7, x7
|
||||||
|
out[8], tmp[8] = x8, x8
|
||||||
|
out[9], tmp[9] = x9, x9
|
||||||
|
out[10], tmp[10] = x10, x10
|
||||||
|
out[11], tmp[11] = x11, x11
|
||||||
|
out[12], tmp[12] = x12, x12
|
||||||
|
out[13], tmp[13] = x13, x13
|
||||||
|
out[14], tmp[14] = x14, x14
|
||||||
|
out[15], tmp[15] = x15, x15
|
||||||
|
}
|
||||||
|
|
||||||
|
func blockMix(tmp *[16]uint32, in, out []uint32, r int) {
|
||||||
|
blockCopy(tmp[:], in[(2*r-1)*16:], 16)
|
||||||
|
for i := 0; i < 2*r; i += 2 {
|
||||||
|
salsaXOR(tmp, in[i*16:], out[i*8:])
|
||||||
|
salsaXOR(tmp, in[i*16+16:], out[i*8+r*16:])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func integer(b []uint32, r int) uint64 {
|
||||||
|
j := (2*r - 1) * 16
|
||||||
|
return uint64(b[j]) | uint64(b[j+1])<<32
|
||||||
|
}
|
||||||
|
|
||||||
|
func smix(b []byte, r, N int, v, xy []uint32) {
|
||||||
|
var tmp [16]uint32
|
||||||
|
R := 32 * r
|
||||||
|
x := xy
|
||||||
|
y := xy[R:]
|
||||||
|
|
||||||
|
j := 0
|
||||||
|
for i := 0; i < R; i++ {
|
||||||
|
x[i] = binary.LittleEndian.Uint32(b[j:])
|
||||||
|
j += 4
|
||||||
|
}
|
||||||
|
for i := 0; i < N; i += 2 {
|
||||||
|
blockCopy(v[i*R:], x, R)
|
||||||
|
blockMix(&tmp, x, y, r)
|
||||||
|
|
||||||
|
blockCopy(v[(i+1)*R:], y, R)
|
||||||
|
blockMix(&tmp, y, x, r)
|
||||||
|
}
|
||||||
|
for i := 0; i < N; i += 2 {
|
||||||
|
j := int(integer(x, r) & uint64(N-1))
|
||||||
|
blockXOR(x, v[j*R:], R)
|
||||||
|
blockMix(&tmp, x, y, r)
|
||||||
|
|
||||||
|
j = int(integer(y, r) & uint64(N-1))
|
||||||
|
blockXOR(y, v[j*R:], R)
|
||||||
|
blockMix(&tmp, y, x, r)
|
||||||
|
}
|
||||||
|
j = 0
|
||||||
|
for _, v := range x[:R] {
|
||||||
|
binary.LittleEndian.PutUint32(b[j:], v)
|
||||||
|
j += 4
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Key derives a key from the password, salt, and cost parameters, returning
|
||||||
|
// a byte slice of length keyLen that can be used as cryptographic key.
|
||||||
|
//
|
||||||
|
// N is a CPU/memory cost parameter, which must be a power of two greater than 1.
|
||||||
|
// r and p must satisfy r * p < 2³⁰. If the parameters do not satisfy the
|
||||||
|
// limits, the function returns a nil byte slice and an error.
|
||||||
|
//
|
||||||
|
// For example, you can get a derived key for e.g. AES-256 (which needs a
|
||||||
|
// 32-byte key) by doing:
|
||||||
|
//
|
||||||
|
// dk, err := scrypt.Key([]byte("some password"), salt, 32768, 8, 1, 32)
|
||||||
|
//
|
||||||
|
// The recommended parameters for interactive logins as of 2017 are N=32768, r=8
|
||||||
|
// and p=1. The parameters N, r, and p should be increased as memory latency and
|
||||||
|
// CPU parallelism increases; consider setting N to the highest power of 2 you
|
||||||
|
// can derive within 100 milliseconds. Remember to get a good random salt.
|
||||||
|
func Key(password, salt []byte, N, r, p, keyLen int) ([]byte, error) {
|
||||||
|
if N <= 1 || N&(N-1) != 0 {
|
||||||
|
return nil, errors.New("scrypt: N must be > 1 and a power of 2")
|
||||||
|
}
|
||||||
|
if uint64(r)*uint64(p) >= 1<<30 || r > maxInt/128/p || r > maxInt/256 || N > maxInt/128/r {
|
||||||
|
return nil, errors.New("scrypt: parameters are too large")
|
||||||
|
}
|
||||||
|
|
||||||
|
xy := make([]uint32, 64*r)
|
||||||
|
v := make([]uint32, 32*N*r)
|
||||||
|
b := pbkdf2.Key(password, salt, 1, p*128*r, sha256.New)
|
||||||
|
|
||||||
|
for i := 0; i < p; i++ {
|
||||||
|
smix(b[i*128*r:], r, N, v, xy)
|
||||||
|
}
|
||||||
|
|
||||||
|
return pbkdf2.Key(password, b, 1, keyLen, sha256.New), nil
|
||||||
|
}
|
76
vendor/golang.org/x/crypto/ssh/terminal/terminal.go
generated
vendored
Normal file
76
vendor/golang.org/x/crypto/ssh/terminal/terminal.go
generated
vendored
Normal file
@ -0,0 +1,76 @@
|
|||||||
|
// Copyright 2011 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 terminal provides support functions for dealing with terminals, as
|
||||||
|
// commonly found on UNIX systems.
|
||||||
|
//
|
||||||
|
// Deprecated: this package moved to golang.org/x/term.
|
||||||
|
package terminal
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
|
||||||
|
"golang.org/x/term"
|
||||||
|
)
|
||||||
|
|
||||||
|
// EscapeCodes contains escape sequences that can be written to the terminal in
|
||||||
|
// order to achieve different styles of text.
|
||||||
|
type EscapeCodes = term.EscapeCodes
|
||||||
|
|
||||||
|
// Terminal contains the state for running a VT100 terminal that is capable of
|
||||||
|
// reading lines of input.
|
||||||
|
type Terminal = term.Terminal
|
||||||
|
|
||||||
|
// NewTerminal runs a VT100 terminal on the given ReadWriter. If the ReadWriter is
|
||||||
|
// a local terminal, that terminal must first have been put into raw mode.
|
||||||
|
// prompt is a string that is written at the start of each input line (i.e.
|
||||||
|
// "> ").
|
||||||
|
func NewTerminal(c io.ReadWriter, prompt string) *Terminal {
|
||||||
|
return term.NewTerminal(c, prompt)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ErrPasteIndicator may be returned from ReadLine as the error, in addition
|
||||||
|
// to valid line data. It indicates that bracketed paste mode is enabled and
|
||||||
|
// that the returned line consists only of pasted data. Programs may wish to
|
||||||
|
// interpret pasted data more literally than typed data.
|
||||||
|
var ErrPasteIndicator = term.ErrPasteIndicator
|
||||||
|
|
||||||
|
// State contains the state of a terminal.
|
||||||
|
type State = term.State
|
||||||
|
|
||||||
|
// IsTerminal returns whether the given file descriptor is a terminal.
|
||||||
|
func IsTerminal(fd int) bool {
|
||||||
|
return term.IsTerminal(fd)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReadPassword reads a line of input from a terminal without local echo. This
|
||||||
|
// is commonly used for inputting passwords and other sensitive data. The slice
|
||||||
|
// returned does not include the \n.
|
||||||
|
func ReadPassword(fd int) ([]byte, error) {
|
||||||
|
return term.ReadPassword(fd)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MakeRaw puts the terminal connected to the given file descriptor into raw
|
||||||
|
// mode and returns the previous state of the terminal so that it can be
|
||||||
|
// restored.
|
||||||
|
func MakeRaw(fd int) (*State, error) {
|
||||||
|
return term.MakeRaw(fd)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Restore restores the terminal connected to the given file descriptor to a
|
||||||
|
// previous state.
|
||||||
|
func Restore(fd int, oldState *State) error {
|
||||||
|
return term.Restore(fd, oldState)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetState returns the current state of a terminal which may be useful to
|
||||||
|
// restore the terminal after a signal.
|
||||||
|
func GetState(fd int) (*State, error) {
|
||||||
|
return term.GetState(fd)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetSize returns the dimensions of the given terminal.
|
||||||
|
func GetSize(fd int) (width, height int, err error) {
|
||||||
|
return term.GetSize(fd)
|
||||||
|
}
|
3
vendor/golang.org/x/sys/AUTHORS
generated
vendored
Normal file
3
vendor/golang.org/x/sys/AUTHORS
generated
vendored
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
# This source code refers to The Go Authors for copyright purposes.
|
||||||
|
# The master list of authors is in the main Go distribution,
|
||||||
|
# visible at http://tip.golang.org/AUTHORS.
|
3
vendor/golang.org/x/sys/CONTRIBUTORS
generated
vendored
Normal file
3
vendor/golang.org/x/sys/CONTRIBUTORS
generated
vendored
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
# This source code was written by the Go contributors.
|
||||||
|
# The master list of contributors is in the main Go distribution,
|
||||||
|
# visible at http://tip.golang.org/CONTRIBUTORS.
|
27
vendor/golang.org/x/sys/LICENSE
generated
vendored
Normal file
27
vendor/golang.org/x/sys/LICENSE
generated
vendored
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
Copyright (c) 2009 The Go Authors. All rights reserved.
|
||||||
|
|
||||||
|
Redistribution and use in source and binary forms, with or without
|
||||||
|
modification, are permitted provided that the following conditions are
|
||||||
|
met:
|
||||||
|
|
||||||
|
* Redistributions of source code must retain the above copyright
|
||||||
|
notice, this list of conditions and the following disclaimer.
|
||||||
|
* Redistributions in binary form must reproduce the above
|
||||||
|
copyright notice, this list of conditions and the following disclaimer
|
||||||
|
in the documentation and/or other materials provided with the
|
||||||
|
distribution.
|
||||||
|
* Neither the name of Google Inc. nor the names of its
|
||||||
|
contributors may be used to endorse or promote products derived from
|
||||||
|
this software without specific prior written permission.
|
||||||
|
|
||||||
|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||||
|
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||||
|
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||||
|
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||||
|
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||||
|
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||||
|
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||||
|
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||||
|
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||||
|
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
22
vendor/golang.org/x/sys/PATENTS
generated
vendored
Normal file
22
vendor/golang.org/x/sys/PATENTS
generated
vendored
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
Additional IP Rights Grant (Patents)
|
||||||
|
|
||||||
|
"This implementation" means the copyrightable works distributed by
|
||||||
|
Google as part of the Go project.
|
||||||
|
|
||||||
|
Google hereby grants to You a perpetual, worldwide, non-exclusive,
|
||||||
|
no-charge, royalty-free, irrevocable (except as stated in this section)
|
||||||
|
patent license to make, have made, use, offer to sell, sell, import,
|
||||||
|
transfer and otherwise run, modify and propagate the contents of this
|
||||||
|
implementation of Go, where such license applies only to those patent
|
||||||
|
claims, both currently owned or controlled by Google and acquired in
|
||||||
|
the future, licensable by Google that are necessarily infringed by this
|
||||||
|
implementation of Go. This grant does not include claims that would be
|
||||||
|
infringed only as a consequence of further modification of this
|
||||||
|
implementation. If you or your agent or exclusive licensee institute or
|
||||||
|
order or agree to the institution of patent litigation against any
|
||||||
|
entity (including a cross-claim or counterclaim in a lawsuit) alleging
|
||||||
|
that this implementation of Go or any code incorporated within this
|
||||||
|
implementation of Go constitutes direct or contributory patent
|
||||||
|
infringement, or inducement of patent infringement, then any patent
|
||||||
|
rights granted to you under this License for this implementation of Go
|
||||||
|
shall terminate as of the date such litigation is filed.
|
30
vendor/golang.org/x/sys/internal/unsafeheader/unsafeheader.go
generated
vendored
Normal file
30
vendor/golang.org/x/sys/internal/unsafeheader/unsafeheader.go
generated
vendored
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
// Copyright 2020 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 unsafeheader contains header declarations for the Go runtime's
|
||||||
|
// slice and string implementations.
|
||||||
|
//
|
||||||
|
// This package allows x/sys to use types equivalent to
|
||||||
|
// reflect.SliceHeader and reflect.StringHeader without introducing
|
||||||
|
// a dependency on the (relatively heavy) "reflect" package.
|
||||||
|
package unsafeheader
|
||||||
|
|
||||||
|
import (
|
||||||
|
"unsafe"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Slice is the runtime representation of a slice.
|
||||||
|
// It cannot be used safely or portably and its representation may change in a later release.
|
||||||
|
type Slice struct {
|
||||||
|
Data unsafe.Pointer
|
||||||
|
Len int
|
||||||
|
Cap int
|
||||||
|
}
|
||||||
|
|
||||||
|
// String is the runtime representation of a string.
|
||||||
|
// It cannot be used safely or portably and its representation may change in a later release.
|
||||||
|
type String struct {
|
||||||
|
Data unsafe.Pointer
|
||||||
|
Len int
|
||||||
|
}
|
8
vendor/golang.org/x/sys/plan9/asm.s
generated
vendored
Normal file
8
vendor/golang.org/x/sys/plan9/asm.s
generated
vendored
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
// Copyright 2014 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.
|
||||||
|
|
||||||
|
#include "textflag.h"
|
||||||
|
|
||||||
|
TEXT ·use(SB),NOSPLIT,$0
|
||||||
|
RET
|
30
vendor/golang.org/x/sys/plan9/asm_plan9_386.s
generated
vendored
Normal file
30
vendor/golang.org/x/sys/plan9/asm_plan9_386.s
generated
vendored
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
// Copyright 2009 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.
|
||||||
|
|
||||||
|
#include "textflag.h"
|
||||||
|
|
||||||
|
//
|
||||||
|
// System call support for 386, Plan 9
|
||||||
|
//
|
||||||
|
|
||||||
|
// Just jump to package syscall's implementation for all these functions.
|
||||||
|
// The runtime may know about them.
|
||||||
|
|
||||||
|
TEXT ·Syscall(SB),NOSPLIT,$0-32
|
||||||
|
JMP syscall·Syscall(SB)
|
||||||
|
|
||||||
|
TEXT ·Syscall6(SB),NOSPLIT,$0-44
|
||||||
|
JMP syscall·Syscall6(SB)
|
||||||
|
|
||||||
|
TEXT ·RawSyscall(SB),NOSPLIT,$0-28
|
||||||
|
JMP syscall·RawSyscall(SB)
|
||||||
|
|
||||||
|
TEXT ·RawSyscall6(SB),NOSPLIT,$0-40
|
||||||
|
JMP syscall·RawSyscall6(SB)
|
||||||
|
|
||||||
|
TEXT ·seek(SB),NOSPLIT,$0-36
|
||||||
|
JMP syscall·seek(SB)
|
||||||
|
|
||||||
|
TEXT ·exit(SB),NOSPLIT,$4-4
|
||||||
|
JMP syscall·exit(SB)
|
30
vendor/golang.org/x/sys/plan9/asm_plan9_amd64.s
generated
vendored
Normal file
30
vendor/golang.org/x/sys/plan9/asm_plan9_amd64.s
generated
vendored
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
// Copyright 2009 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.
|
||||||
|
|
||||||
|
#include "textflag.h"
|
||||||
|
|
||||||
|
//
|
||||||
|
// System call support for amd64, Plan 9
|
||||||
|
//
|
||||||
|
|
||||||
|
// Just jump to package syscall's implementation for all these functions.
|
||||||
|
// The runtime may know about them.
|
||||||
|
|
||||||
|
TEXT ·Syscall(SB),NOSPLIT,$0-64
|
||||||
|
JMP syscall·Syscall(SB)
|
||||||
|
|
||||||
|
TEXT ·Syscall6(SB),NOSPLIT,$0-88
|
||||||
|
JMP syscall·Syscall6(SB)
|
||||||
|
|
||||||
|
TEXT ·RawSyscall(SB),NOSPLIT,$0-56
|
||||||
|
JMP syscall·RawSyscall(SB)
|
||||||
|
|
||||||
|
TEXT ·RawSyscall6(SB),NOSPLIT,$0-80
|
||||||
|
JMP syscall·RawSyscall6(SB)
|
||||||
|
|
||||||
|
TEXT ·seek(SB),NOSPLIT,$0-56
|
||||||
|
JMP syscall·seek(SB)
|
||||||
|
|
||||||
|
TEXT ·exit(SB),NOSPLIT,$8-8
|
||||||
|
JMP syscall·exit(SB)
|
25
vendor/golang.org/x/sys/plan9/asm_plan9_arm.s
generated
vendored
Normal file
25
vendor/golang.org/x/sys/plan9/asm_plan9_arm.s
generated
vendored
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
// Copyright 2009 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.
|
||||||
|
|
||||||
|
#include "textflag.h"
|
||||||
|
|
||||||
|
// System call support for plan9 on arm
|
||||||
|
|
||||||
|
// Just jump to package syscall's implementation for all these functions.
|
||||||
|
// The runtime may know about them.
|
||||||
|
|
||||||
|
TEXT ·Syscall(SB),NOSPLIT,$0-32
|
||||||
|
JMP syscall·Syscall(SB)
|
||||||
|
|
||||||
|
TEXT ·Syscall6(SB),NOSPLIT,$0-44
|
||||||
|
JMP syscall·Syscall6(SB)
|
||||||
|
|
||||||
|
TEXT ·RawSyscall(SB),NOSPLIT,$0-28
|
||||||
|
JMP syscall·RawSyscall(SB)
|
||||||
|
|
||||||
|
TEXT ·RawSyscall6(SB),NOSPLIT,$0-40
|
||||||
|
JMP syscall·RawSyscall6(SB)
|
||||||
|
|
||||||
|
TEXT ·seek(SB),NOSPLIT,$0-36
|
||||||
|
JMP syscall·exit(SB)
|
70
vendor/golang.org/x/sys/plan9/const_plan9.go
generated
vendored
Normal file
70
vendor/golang.org/x/sys/plan9/const_plan9.go
generated
vendored
Normal file
@ -0,0 +1,70 @@
|
|||||||
|
package plan9
|
||||||
|
|
||||||
|
// Plan 9 Constants
|
||||||
|
|
||||||
|
// Open modes
|
||||||
|
const (
|
||||||
|
O_RDONLY = 0
|
||||||
|
O_WRONLY = 1
|
||||||
|
O_RDWR = 2
|
||||||
|
O_TRUNC = 16
|
||||||
|
O_CLOEXEC = 32
|
||||||
|
O_EXCL = 0x1000
|
||||||
|
)
|
||||||
|
|
||||||
|
// Rfork flags
|
||||||
|
const (
|
||||||
|
RFNAMEG = 1 << 0
|
||||||
|
RFENVG = 1 << 1
|
||||||
|
RFFDG = 1 << 2
|
||||||
|
RFNOTEG = 1 << 3
|
||||||
|
RFPROC = 1 << 4
|
||||||
|
RFMEM = 1 << 5
|
||||||
|
RFNOWAIT = 1 << 6
|
||||||
|
RFCNAMEG = 1 << 10
|
||||||
|
RFCENVG = 1 << 11
|
||||||
|
RFCFDG = 1 << 12
|
||||||
|
RFREND = 1 << 13
|
||||||
|
RFNOMNT = 1 << 14
|
||||||
|
)
|
||||||
|
|
||||||
|
// Qid.Type bits
|
||||||
|
const (
|
||||||
|
QTDIR = 0x80
|
||||||
|
QTAPPEND = 0x40
|
||||||
|
QTEXCL = 0x20
|
||||||
|
QTMOUNT = 0x10
|
||||||
|
QTAUTH = 0x08
|
||||||
|
QTTMP = 0x04
|
||||||
|
QTFILE = 0x00
|
||||||
|
)
|
||||||
|
|
||||||
|
// Dir.Mode bits
|
||||||
|
const (
|
||||||
|
DMDIR = 0x80000000
|
||||||
|
DMAPPEND = 0x40000000
|
||||||
|
DMEXCL = 0x20000000
|
||||||
|
DMMOUNT = 0x10000000
|
||||||
|
DMAUTH = 0x08000000
|
||||||
|
DMTMP = 0x04000000
|
||||||
|
DMREAD = 0x4
|
||||||
|
DMWRITE = 0x2
|
||||||
|
DMEXEC = 0x1
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
STATMAX = 65535
|
||||||
|
ERRMAX = 128
|
||||||
|
STATFIXLEN = 49
|
||||||
|
)
|
||||||
|
|
||||||
|
// Mount and bind flags
|
||||||
|
const (
|
||||||
|
MREPL = 0x0000
|
||||||
|
MBEFORE = 0x0001
|
||||||
|
MAFTER = 0x0002
|
||||||
|
MORDER = 0x0003
|
||||||
|
MCREATE = 0x0004
|
||||||
|
MCACHE = 0x0010
|
||||||
|
MMASK = 0x0017
|
||||||
|
)
|
212
vendor/golang.org/x/sys/plan9/dir_plan9.go
generated
vendored
Normal file
212
vendor/golang.org/x/sys/plan9/dir_plan9.go
generated
vendored
Normal file
@ -0,0 +1,212 @@
|
|||||||
|
// Copyright 2012 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.
|
||||||
|
|
||||||
|
// Plan 9 directory marshalling. See intro(5).
|
||||||
|
|
||||||
|
package plan9
|
||||||
|
|
||||||
|
import "errors"
|
||||||
|
|
||||||
|
var (
|
||||||
|
ErrShortStat = errors.New("stat buffer too short")
|
||||||
|
ErrBadStat = errors.New("malformed stat buffer")
|
||||||
|
ErrBadName = errors.New("bad character in file name")
|
||||||
|
)
|
||||||
|
|
||||||
|
// A Qid represents a 9P server's unique identification for a file.
|
||||||
|
type Qid struct {
|
||||||
|
Path uint64 // the file server's unique identification for the file
|
||||||
|
Vers uint32 // version number for given Path
|
||||||
|
Type uint8 // the type of the file (plan9.QTDIR for example)
|
||||||
|
}
|
||||||
|
|
||||||
|
// A Dir contains the metadata for a file.
|
||||||
|
type Dir struct {
|
||||||
|
// system-modified data
|
||||||
|
Type uint16 // server type
|
||||||
|
Dev uint32 // server subtype
|
||||||
|
|
||||||
|
// file data
|
||||||
|
Qid Qid // unique id from server
|
||||||
|
Mode uint32 // permissions
|
||||||
|
Atime uint32 // last read time
|
||||||
|
Mtime uint32 // last write time
|
||||||
|
Length int64 // file length
|
||||||
|
Name string // last element of path
|
||||||
|
Uid string // owner name
|
||||||
|
Gid string // group name
|
||||||
|
Muid string // last modifier name
|
||||||
|
}
|
||||||
|
|
||||||
|
var nullDir = Dir{
|
||||||
|
Type: ^uint16(0),
|
||||||
|
Dev: ^uint32(0),
|
||||||
|
Qid: Qid{
|
||||||
|
Path: ^uint64(0),
|
||||||
|
Vers: ^uint32(0),
|
||||||
|
Type: ^uint8(0),
|
||||||
|
},
|
||||||
|
Mode: ^uint32(0),
|
||||||
|
Atime: ^uint32(0),
|
||||||
|
Mtime: ^uint32(0),
|
||||||
|
Length: ^int64(0),
|
||||||
|
}
|
||||||
|
|
||||||
|
// Null assigns special "don't touch" values to members of d to
|
||||||
|
// avoid modifying them during plan9.Wstat.
|
||||||
|
func (d *Dir) Null() { *d = nullDir }
|
||||||
|
|
||||||
|
// Marshal encodes a 9P stat message corresponding to d into b
|
||||||
|
//
|
||||||
|
// If there isn't enough space in b for a stat message, ErrShortStat is returned.
|
||||||
|
func (d *Dir) Marshal(b []byte) (n int, err error) {
|
||||||
|
n = STATFIXLEN + len(d.Name) + len(d.Uid) + len(d.Gid) + len(d.Muid)
|
||||||
|
if n > len(b) {
|
||||||
|
return n, ErrShortStat
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, c := range d.Name {
|
||||||
|
if c == '/' {
|
||||||
|
return n, ErrBadName
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
b = pbit16(b, uint16(n)-2)
|
||||||
|
b = pbit16(b, d.Type)
|
||||||
|
b = pbit32(b, d.Dev)
|
||||||
|
b = pbit8(b, d.Qid.Type)
|
||||||
|
b = pbit32(b, d.Qid.Vers)
|
||||||
|
b = pbit64(b, d.Qid.Path)
|
||||||
|
b = pbit32(b, d.Mode)
|
||||||
|
b = pbit32(b, d.Atime)
|
||||||
|
b = pbit32(b, d.Mtime)
|
||||||
|
b = pbit64(b, uint64(d.Length))
|
||||||
|
b = pstring(b, d.Name)
|
||||||
|
b = pstring(b, d.Uid)
|
||||||
|
b = pstring(b, d.Gid)
|
||||||
|
b = pstring(b, d.Muid)
|
||||||
|
|
||||||
|
return n, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnmarshalDir decodes a single 9P stat message from b and returns the resulting Dir.
|
||||||
|
//
|
||||||
|
// If b is too small to hold a valid stat message, ErrShortStat is returned.
|
||||||
|
//
|
||||||
|
// If the stat message itself is invalid, ErrBadStat is returned.
|
||||||
|
func UnmarshalDir(b []byte) (*Dir, error) {
|
||||||
|
if len(b) < STATFIXLEN {
|
||||||
|
return nil, ErrShortStat
|
||||||
|
}
|
||||||
|
size, buf := gbit16(b)
|
||||||
|
if len(b) != int(size)+2 {
|
||||||
|
return nil, ErrBadStat
|
||||||
|
}
|
||||||
|
b = buf
|
||||||
|
|
||||||
|
var d Dir
|
||||||
|
d.Type, b = gbit16(b)
|
||||||
|
d.Dev, b = gbit32(b)
|
||||||
|
d.Qid.Type, b = gbit8(b)
|
||||||
|
d.Qid.Vers, b = gbit32(b)
|
||||||
|
d.Qid.Path, b = gbit64(b)
|
||||||
|
d.Mode, b = gbit32(b)
|
||||||
|
d.Atime, b = gbit32(b)
|
||||||
|
d.Mtime, b = gbit32(b)
|
||||||
|
|
||||||
|
n, b := gbit64(b)
|
||||||
|
d.Length = int64(n)
|
||||||
|
|
||||||
|
var ok bool
|
||||||
|
if d.Name, b, ok = gstring(b); !ok {
|
||||||
|
return nil, ErrBadStat
|
||||||
|
}
|
||||||
|
if d.Uid, b, ok = gstring(b); !ok {
|
||||||
|
return nil, ErrBadStat
|
||||||
|
}
|
||||||
|
if d.Gid, b, ok = gstring(b); !ok {
|
||||||
|
return nil, ErrBadStat
|
||||||
|
}
|
||||||
|
if d.Muid, b, ok = gstring(b); !ok {
|
||||||
|
return nil, ErrBadStat
|
||||||
|
}
|
||||||
|
|
||||||
|
return &d, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// pbit8 copies the 8-bit number v to b and returns the remaining slice of b.
|
||||||
|
func pbit8(b []byte, v uint8) []byte {
|
||||||
|
b[0] = byte(v)
|
||||||
|
return b[1:]
|
||||||
|
}
|
||||||
|
|
||||||
|
// pbit16 copies the 16-bit number v to b in little-endian order and returns the remaining slice of b.
|
||||||
|
func pbit16(b []byte, v uint16) []byte {
|
||||||
|
b[0] = byte(v)
|
||||||
|
b[1] = byte(v >> 8)
|
||||||
|
return b[2:]
|
||||||
|
}
|
||||||
|
|
||||||
|
// pbit32 copies the 32-bit number v to b in little-endian order and returns the remaining slice of b.
|
||||||
|
func pbit32(b []byte, v uint32) []byte {
|
||||||
|
b[0] = byte(v)
|
||||||
|
b[1] = byte(v >> 8)
|
||||||
|
b[2] = byte(v >> 16)
|
||||||
|
b[3] = byte(v >> 24)
|
||||||
|
return b[4:]
|
||||||
|
}
|
||||||
|
|
||||||
|
// pbit64 copies the 64-bit number v to b in little-endian order and returns the remaining slice of b.
|
||||||
|
func pbit64(b []byte, v uint64) []byte {
|
||||||
|
b[0] = byte(v)
|
||||||
|
b[1] = byte(v >> 8)
|
||||||
|
b[2] = byte(v >> 16)
|
||||||
|
b[3] = byte(v >> 24)
|
||||||
|
b[4] = byte(v >> 32)
|
||||||
|
b[5] = byte(v >> 40)
|
||||||
|
b[6] = byte(v >> 48)
|
||||||
|
b[7] = byte(v >> 56)
|
||||||
|
return b[8:]
|
||||||
|
}
|
||||||
|
|
||||||
|
// pstring copies the string s to b, prepending it with a 16-bit length in little-endian order, and
|
||||||
|
// returning the remaining slice of b..
|
||||||
|
func pstring(b []byte, s string) []byte {
|
||||||
|
b = pbit16(b, uint16(len(s)))
|
||||||
|
n := copy(b, s)
|
||||||
|
return b[n:]
|
||||||
|
}
|
||||||
|
|
||||||
|
// gbit8 reads an 8-bit number from b and returns it with the remaining slice of b.
|
||||||
|
func gbit8(b []byte) (uint8, []byte) {
|
||||||
|
return uint8(b[0]), b[1:]
|
||||||
|
}
|
||||||
|
|
||||||
|
// gbit16 reads a 16-bit number in little-endian order from b and returns it with the remaining slice of b.
|
||||||
|
func gbit16(b []byte) (uint16, []byte) {
|
||||||
|
return uint16(b[0]) | uint16(b[1])<<8, b[2:]
|
||||||
|
}
|
||||||
|
|
||||||
|
// gbit32 reads a 32-bit number in little-endian order from b and returns it with the remaining slice of b.
|
||||||
|
func gbit32(b []byte) (uint32, []byte) {
|
||||||
|
return uint32(b[0]) | uint32(b[1])<<8 | uint32(b[2])<<16 | uint32(b[3])<<24, b[4:]
|
||||||
|
}
|
||||||
|
|
||||||
|
// gbit64 reads a 64-bit number in little-endian order from b and returns it with the remaining slice of b.
|
||||||
|
func gbit64(b []byte) (uint64, []byte) {
|
||||||
|
lo := uint32(b[0]) | uint32(b[1])<<8 | uint32(b[2])<<16 | uint32(b[3])<<24
|
||||||
|
hi := uint32(b[4]) | uint32(b[5])<<8 | uint32(b[6])<<16 | uint32(b[7])<<24
|
||||||
|
return uint64(lo) | uint64(hi)<<32, b[8:]
|
||||||
|
}
|
||||||
|
|
||||||
|
// gstring reads a string from b, prefixed with a 16-bit length in little-endian order.
|
||||||
|
// It returns the string with the remaining slice of b and a boolean. If the length is
|
||||||
|
// greater than the number of bytes in b, the boolean will be false.
|
||||||
|
func gstring(b []byte) (string, []byte, bool) {
|
||||||
|
n, b := gbit16(b)
|
||||||
|
if int(n) > len(b) {
|
||||||
|
return "", b, false
|
||||||
|
}
|
||||||
|
return string(b[:n]), b[n:], true
|
||||||
|
}
|
31
vendor/golang.org/x/sys/plan9/env_plan9.go
generated
vendored
Normal file
31
vendor/golang.org/x/sys/plan9/env_plan9.go
generated
vendored
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
// Copyright 2011 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.
|
||||||
|
|
||||||
|
// Plan 9 environment variables.
|
||||||
|
|
||||||
|
package plan9
|
||||||
|
|
||||||
|
import (
|
||||||
|
"syscall"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Getenv(key string) (value string, found bool) {
|
||||||
|
return syscall.Getenv(key)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Setenv(key, value string) error {
|
||||||
|
return syscall.Setenv(key, value)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Clearenv() {
|
||||||
|
syscall.Clearenv()
|
||||||
|
}
|
||||||
|
|
||||||
|
func Environ() []string {
|
||||||
|
return syscall.Environ()
|
||||||
|
}
|
||||||
|
|
||||||
|
func Unsetenv(key string) error {
|
||||||
|
return syscall.Unsetenv(key)
|
||||||
|
}
|
50
vendor/golang.org/x/sys/plan9/errors_plan9.go
generated
vendored
Normal file
50
vendor/golang.org/x/sys/plan9/errors_plan9.go
generated
vendored
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
// Copyright 2011 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 plan9
|
||||||
|
|
||||||
|
import "syscall"
|
||||||
|
|
||||||
|
// Constants
|
||||||
|
const (
|
||||||
|
// Invented values to support what package os expects.
|
||||||
|
O_CREAT = 0x02000
|
||||||
|
O_APPEND = 0x00400
|
||||||
|
O_NOCTTY = 0x00000
|
||||||
|
O_NONBLOCK = 0x00000
|
||||||
|
O_SYNC = 0x00000
|
||||||
|
O_ASYNC = 0x00000
|
||||||
|
|
||||||
|
S_IFMT = 0x1f000
|
||||||
|
S_IFIFO = 0x1000
|
||||||
|
S_IFCHR = 0x2000
|
||||||
|
S_IFDIR = 0x4000
|
||||||
|
S_IFBLK = 0x6000
|
||||||
|
S_IFREG = 0x8000
|
||||||
|
S_IFLNK = 0xa000
|
||||||
|
S_IFSOCK = 0xc000
|
||||||
|
)
|
||||||
|
|
||||||
|
// Errors
|
||||||
|
var (
|
||||||
|
EINVAL = syscall.NewError("bad arg in system call")
|
||||||
|
ENOTDIR = syscall.NewError("not a directory")
|
||||||
|
EISDIR = syscall.NewError("file is a directory")
|
||||||
|
ENOENT = syscall.NewError("file does not exist")
|
||||||
|
EEXIST = syscall.NewError("file already exists")
|
||||||
|
EMFILE = syscall.NewError("no free file descriptors")
|
||||||
|
EIO = syscall.NewError("i/o error")
|
||||||
|
ENAMETOOLONG = syscall.NewError("file name too long")
|
||||||
|
EINTR = syscall.NewError("interrupted")
|
||||||
|
EPERM = syscall.NewError("permission denied")
|
||||||
|
EBUSY = syscall.NewError("no free devices")
|
||||||
|
ETIMEDOUT = syscall.NewError("connection timed out")
|
||||||
|
EPLAN9 = syscall.NewError("not supported by plan 9")
|
||||||
|
|
||||||
|
// The following errors do not correspond to any
|
||||||
|
// Plan 9 system messages. Invented to support
|
||||||
|
// what package os and others expect.
|
||||||
|
EACCES = syscall.NewError("access permission denied")
|
||||||
|
EAFNOSUPPORT = syscall.NewError("address family not supported by protocol")
|
||||||
|
)
|
150
vendor/golang.org/x/sys/plan9/mkall.sh
generated
vendored
Normal file
150
vendor/golang.org/x/sys/plan9/mkall.sh
generated
vendored
Normal file
@ -0,0 +1,150 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# Copyright 2009 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.
|
||||||
|
|
||||||
|
# The plan9 package provides access to the raw system call
|
||||||
|
# interface of the underlying operating system. Porting Go to
|
||||||
|
# a new architecture/operating system combination requires
|
||||||
|
# some manual effort, though there are tools that automate
|
||||||
|
# much of the process. The auto-generated files have names
|
||||||
|
# beginning with z.
|
||||||
|
#
|
||||||
|
# This script runs or (given -n) prints suggested commands to generate z files
|
||||||
|
# for the current system. Running those commands is not automatic.
|
||||||
|
# This script is documentation more than anything else.
|
||||||
|
#
|
||||||
|
# * asm_${GOOS}_${GOARCH}.s
|
||||||
|
#
|
||||||
|
# This hand-written assembly file implements system call dispatch.
|
||||||
|
# There are three entry points:
|
||||||
|
#
|
||||||
|
# func Syscall(trap, a1, a2, a3 uintptr) (r1, r2, err uintptr);
|
||||||
|
# func Syscall6(trap, a1, a2, a3, a4, a5, a6 uintptr) (r1, r2, err uintptr);
|
||||||
|
# func RawSyscall(trap, a1, a2, a3 uintptr) (r1, r2, err uintptr);
|
||||||
|
#
|
||||||
|
# The first and second are the standard ones; they differ only in
|
||||||
|
# how many arguments can be passed to the kernel.
|
||||||
|
# The third is for low-level use by the ForkExec wrapper;
|
||||||
|
# unlike the first two, it does not call into the scheduler to
|
||||||
|
# let it know that a system call is running.
|
||||||
|
#
|
||||||
|
# * syscall_${GOOS}.go
|
||||||
|
#
|
||||||
|
# This hand-written Go file implements system calls that need
|
||||||
|
# special handling and lists "//sys" comments giving prototypes
|
||||||
|
# for ones that can be auto-generated. Mksyscall reads those
|
||||||
|
# comments to generate the stubs.
|
||||||
|
#
|
||||||
|
# * syscall_${GOOS}_${GOARCH}.go
|
||||||
|
#
|
||||||
|
# Same as syscall_${GOOS}.go except that it contains code specific
|
||||||
|
# to ${GOOS} on one particular architecture.
|
||||||
|
#
|
||||||
|
# * types_${GOOS}.c
|
||||||
|
#
|
||||||
|
# This hand-written C file includes standard C headers and then
|
||||||
|
# creates typedef or enum names beginning with a dollar sign
|
||||||
|
# (use of $ in variable names is a gcc extension). The hardest
|
||||||
|
# part about preparing this file is figuring out which headers to
|
||||||
|
# include and which symbols need to be #defined to get the
|
||||||
|
# actual data structures that pass through to the kernel system calls.
|
||||||
|
# Some C libraries present alternate versions for binary compatibility
|
||||||
|
# and translate them on the way in and out of system calls, but
|
||||||
|
# there is almost always a #define that can get the real ones.
|
||||||
|
# See types_darwin.c and types_linux.c for examples.
|
||||||
|
#
|
||||||
|
# * zerror_${GOOS}_${GOARCH}.go
|
||||||
|
#
|
||||||
|
# This machine-generated file defines the system's error numbers,
|
||||||
|
# error strings, and signal numbers. The generator is "mkerrors.sh".
|
||||||
|
# Usually no arguments are needed, but mkerrors.sh will pass its
|
||||||
|
# arguments on to godefs.
|
||||||
|
#
|
||||||
|
# * zsyscall_${GOOS}_${GOARCH}.go
|
||||||
|
#
|
||||||
|
# Generated by mksyscall.pl; see syscall_${GOOS}.go above.
|
||||||
|
#
|
||||||
|
# * zsysnum_${GOOS}_${GOARCH}.go
|
||||||
|
#
|
||||||
|
# Generated by mksysnum_${GOOS}.
|
||||||
|
#
|
||||||
|
# * ztypes_${GOOS}_${GOARCH}.go
|
||||||
|
#
|
||||||
|
# Generated by godefs; see types_${GOOS}.c above.
|
||||||
|
|
||||||
|
GOOSARCH="${GOOS}_${GOARCH}"
|
||||||
|
|
||||||
|
# defaults
|
||||||
|
mksyscall="go run mksyscall.go"
|
||||||
|
mkerrors="./mkerrors.sh"
|
||||||
|
zerrors="zerrors_$GOOSARCH.go"
|
||||||
|
mksysctl=""
|
||||||
|
zsysctl="zsysctl_$GOOSARCH.go"
|
||||||
|
mksysnum=
|
||||||
|
mktypes=
|
||||||
|
run="sh"
|
||||||
|
|
||||||
|
case "$1" in
|
||||||
|
-syscalls)
|
||||||
|
for i in zsyscall*go
|
||||||
|
do
|
||||||
|
sed 1q $i | sed 's;^// ;;' | sh > _$i && gofmt < _$i > $i
|
||||||
|
rm _$i
|
||||||
|
done
|
||||||
|
exit 0
|
||||||
|
;;
|
||||||
|
-n)
|
||||||
|
run="cat"
|
||||||
|
shift
|
||||||
|
esac
|
||||||
|
|
||||||
|
case "$#" in
|
||||||
|
0)
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
echo 'usage: mkall.sh [-n]' 1>&2
|
||||||
|
exit 2
|
||||||
|
esac
|
||||||
|
|
||||||
|
case "$GOOSARCH" in
|
||||||
|
_* | *_ | _)
|
||||||
|
echo 'undefined $GOOS_$GOARCH:' "$GOOSARCH" 1>&2
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
plan9_386)
|
||||||
|
mkerrors=
|
||||||
|
mksyscall="go run mksyscall.go -l32 -plan9 -tags plan9,386"
|
||||||
|
mksysnum="./mksysnum_plan9.sh /n/sources/plan9/sys/src/libc/9syscall/sys.h"
|
||||||
|
mktypes="XXX"
|
||||||
|
;;
|
||||||
|
plan9_amd64)
|
||||||
|
mkerrors=
|
||||||
|
mksyscall="go run mksyscall.go -l32 -plan9 -tags plan9,amd64"
|
||||||
|
mksysnum="./mksysnum_plan9.sh /n/sources/plan9/sys/src/libc/9syscall/sys.h"
|
||||||
|
mktypes="XXX"
|
||||||
|
;;
|
||||||
|
plan9_arm)
|
||||||
|
mkerrors=
|
||||||
|
mksyscall="go run mksyscall.go -l32 -plan9 -tags plan9,arm"
|
||||||
|
mksysnum="./mksysnum_plan9.sh /n/sources/plan9/sys/src/libc/9syscall/sys.h"
|
||||||
|
mktypes="XXX"
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
echo 'unrecognized $GOOS_$GOARCH: ' "$GOOSARCH" 1>&2
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
(
|
||||||
|
if [ -n "$mkerrors" ]; then echo "$mkerrors |gofmt >$zerrors"; fi
|
||||||
|
case "$GOOS" in
|
||||||
|
plan9)
|
||||||
|
syscall_goos="syscall_$GOOS.go"
|
||||||
|
if [ -n "$mksyscall" ]; then echo "$mksyscall $syscall_goos |gofmt >zsyscall_$GOOSARCH.go"; fi
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
if [ -n "$mksysctl" ]; then echo "$mksysctl |gofmt >$zsysctl"; fi
|
||||||
|
if [ -n "$mksysnum" ]; then echo "$mksysnum |gofmt >zsysnum_$GOOSARCH.go"; fi
|
||||||
|
if [ -n "$mktypes" ]; then echo "$mktypes types_$GOOS.go |gofmt >ztypes_$GOOSARCH.go"; fi
|
||||||
|
) | $run
|
246
vendor/golang.org/x/sys/plan9/mkerrors.sh
generated
vendored
Normal file
246
vendor/golang.org/x/sys/plan9/mkerrors.sh
generated
vendored
Normal file
@ -0,0 +1,246 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# Copyright 2009 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.
|
||||||
|
|
||||||
|
# Generate Go code listing errors and other #defined constant
|
||||||
|
# values (ENAMETOOLONG etc.), by asking the preprocessor
|
||||||
|
# about the definitions.
|
||||||
|
|
||||||
|
unset LANG
|
||||||
|
export LC_ALL=C
|
||||||
|
export LC_CTYPE=C
|
||||||
|
|
||||||
|
CC=${CC:-gcc}
|
||||||
|
|
||||||
|
uname=$(uname)
|
||||||
|
|
||||||
|
includes='
|
||||||
|
#include <sys/types.h>
|
||||||
|
#include <sys/file.h>
|
||||||
|
#include <fcntl.h>
|
||||||
|
#include <dirent.h>
|
||||||
|
#include <sys/socket.h>
|
||||||
|
#include <netinet/in.h>
|
||||||
|
#include <netinet/ip.h>
|
||||||
|
#include <netinet/ip6.h>
|
||||||
|
#include <netinet/tcp.h>
|
||||||
|
#include <errno.h>
|
||||||
|
#include <sys/signal.h>
|
||||||
|
#include <signal.h>
|
||||||
|
#include <sys/resource.h>
|
||||||
|
'
|
||||||
|
|
||||||
|
ccflags="$@"
|
||||||
|
|
||||||
|
# Write go tool cgo -godefs input.
|
||||||
|
(
|
||||||
|
echo package plan9
|
||||||
|
echo
|
||||||
|
echo '/*'
|
||||||
|
indirect="includes_$(uname)"
|
||||||
|
echo "${!indirect} $includes"
|
||||||
|
echo '*/'
|
||||||
|
echo 'import "C"'
|
||||||
|
echo
|
||||||
|
echo 'const ('
|
||||||
|
|
||||||
|
# The gcc command line prints all the #defines
|
||||||
|
# it encounters while processing the input
|
||||||
|
echo "${!indirect} $includes" | $CC -x c - -E -dM $ccflags |
|
||||||
|
awk '
|
||||||
|
$1 != "#define" || $2 ~ /\(/ || $3 == "" {next}
|
||||||
|
|
||||||
|
$2 ~ /^E([ABCD]X|[BIS]P|[SD]I|S|FL)$/ {next} # 386 registers
|
||||||
|
$2 ~ /^(SIGEV_|SIGSTKSZ|SIGRT(MIN|MAX))/ {next}
|
||||||
|
$2 ~ /^(SCM_SRCRT)$/ {next}
|
||||||
|
$2 ~ /^(MAP_FAILED)$/ {next}
|
||||||
|
|
||||||
|
$2 !~ /^ETH_/ &&
|
||||||
|
$2 !~ /^EPROC_/ &&
|
||||||
|
$2 !~ /^EQUIV_/ &&
|
||||||
|
$2 !~ /^EXPR_/ &&
|
||||||
|
$2 ~ /^E[A-Z0-9_]+$/ ||
|
||||||
|
$2 ~ /^B[0-9_]+$/ ||
|
||||||
|
$2 ~ /^V[A-Z0-9]+$/ ||
|
||||||
|
$2 ~ /^CS[A-Z0-9]/ ||
|
||||||
|
$2 ~ /^I(SIG|CANON|CRNL|EXTEN|MAXBEL|STRIP|UTF8)$/ ||
|
||||||
|
$2 ~ /^IGN/ ||
|
||||||
|
$2 ~ /^IX(ON|ANY|OFF)$/ ||
|
||||||
|
$2 ~ /^IN(LCR|PCK)$/ ||
|
||||||
|
$2 ~ /(^FLU?SH)|(FLU?SH$)/ ||
|
||||||
|
$2 ~ /^C(LOCAL|READ)$/ ||
|
||||||
|
$2 == "BRKINT" ||
|
||||||
|
$2 == "HUPCL" ||
|
||||||
|
$2 == "PENDIN" ||
|
||||||
|
$2 == "TOSTOP" ||
|
||||||
|
$2 ~ /^PAR/ ||
|
||||||
|
$2 ~ /^SIG[^_]/ ||
|
||||||
|
$2 ~ /^O[CNPFP][A-Z]+[^_][A-Z]+$/ ||
|
||||||
|
$2 ~ /^IN_/ ||
|
||||||
|
$2 ~ /^LOCK_(SH|EX|NB|UN)$/ ||
|
||||||
|
$2 ~ /^(AF|SOCK|SO|SOL|IPPROTO|IP|IPV6|ICMP6|TCP|EVFILT|NOTE|EV|SHUT|PROT|MAP|PACKET|MSG|SCM|MCL|DT|MADV|PR)_/ ||
|
||||||
|
$2 == "ICMPV6_FILTER" ||
|
||||||
|
$2 == "SOMAXCONN" ||
|
||||||
|
$2 == "NAME_MAX" ||
|
||||||
|
$2 == "IFNAMSIZ" ||
|
||||||
|
$2 ~ /^CTL_(MAXNAME|NET|QUERY)$/ ||
|
||||||
|
$2 ~ /^SYSCTL_VERS/ ||
|
||||||
|
$2 ~ /^(MS|MNT)_/ ||
|
||||||
|
$2 ~ /^TUN(SET|GET|ATTACH|DETACH)/ ||
|
||||||
|
$2 ~ /^(O|F|FD|NAME|S|PTRACE|PT)_/ ||
|
||||||
|
$2 ~ /^LINUX_REBOOT_CMD_/ ||
|
||||||
|
$2 ~ /^LINUX_REBOOT_MAGIC[12]$/ ||
|
||||||
|
$2 !~ "NLA_TYPE_MASK" &&
|
||||||
|
$2 ~ /^(NETLINK|NLM|NLMSG|NLA|IFA|IFAN|RT|RTCF|RTN|RTPROT|RTNH|ARPHRD|ETH_P)_/ ||
|
||||||
|
$2 ~ /^SIOC/ ||
|
||||||
|
$2 ~ /^TIOC/ ||
|
||||||
|
$2 !~ "RTF_BITS" &&
|
||||||
|
$2 ~ /^(IFF|IFT|NET_RT|RTM|RTF|RTV|RTA|RTAX)_/ ||
|
||||||
|
$2 ~ /^BIOC/ ||
|
||||||
|
$2 ~ /^RUSAGE_(SELF|CHILDREN|THREAD)/ ||
|
||||||
|
$2 ~ /^RLIMIT_(AS|CORE|CPU|DATA|FSIZE|NOFILE|STACK)|RLIM_INFINITY/ ||
|
||||||
|
$2 ~ /^PRIO_(PROCESS|PGRP|USER)/ ||
|
||||||
|
$2 ~ /^CLONE_[A-Z_]+/ ||
|
||||||
|
$2 !~ /^(BPF_TIMEVAL)$/ &&
|
||||||
|
$2 ~ /^(BPF|DLT)_/ ||
|
||||||
|
$2 !~ "WMESGLEN" &&
|
||||||
|
$2 ~ /^W[A-Z0-9]+$/ {printf("\t%s = C.%s\n", $2, $2)}
|
||||||
|
$2 ~ /^__WCOREFLAG$/ {next}
|
||||||
|
$2 ~ /^__W[A-Z0-9]+$/ {printf("\t%s = C.%s\n", substr($2,3), $2)}
|
||||||
|
|
||||||
|
{next}
|
||||||
|
' | sort
|
||||||
|
|
||||||
|
echo ')'
|
||||||
|
) >_const.go
|
||||||
|
|
||||||
|
# Pull out the error names for later.
|
||||||
|
errors=$(
|
||||||
|
echo '#include <errno.h>' | $CC -x c - -E -dM $ccflags |
|
||||||
|
awk '$1=="#define" && $2 ~ /^E[A-Z0-9_]+$/ { print $2 }' |
|
||||||
|
sort
|
||||||
|
)
|
||||||
|
|
||||||
|
# Pull out the signal names for later.
|
||||||
|
signals=$(
|
||||||
|
echo '#include <signal.h>' | $CC -x c - -E -dM $ccflags |
|
||||||
|
awk '$1=="#define" && $2 ~ /^SIG[A-Z0-9]+$/ { print $2 }' |
|
||||||
|
egrep -v '(SIGSTKSIZE|SIGSTKSZ|SIGRT)' |
|
||||||
|
sort
|
||||||
|
)
|
||||||
|
|
||||||
|
# Again, writing regexps to a file.
|
||||||
|
echo '#include <errno.h>' | $CC -x c - -E -dM $ccflags |
|
||||||
|
awk '$1=="#define" && $2 ~ /^E[A-Z0-9_]+$/ { print "^\t" $2 "[ \t]*=" }' |
|
||||||
|
sort >_error.grep
|
||||||
|
echo '#include <signal.h>' | $CC -x c - -E -dM $ccflags |
|
||||||
|
awk '$1=="#define" && $2 ~ /^SIG[A-Z0-9]+$/ { print "^\t" $2 "[ \t]*=" }' |
|
||||||
|
egrep -v '(SIGSTKSIZE|SIGSTKSZ|SIGRT)' |
|
||||||
|
sort >_signal.grep
|
||||||
|
|
||||||
|
echo '// mkerrors.sh' "$@"
|
||||||
|
echo '// Code generated by the command above; DO NOT EDIT.'
|
||||||
|
echo
|
||||||
|
go tool cgo -godefs -- "$@" _const.go >_error.out
|
||||||
|
cat _error.out | grep -vf _error.grep | grep -vf _signal.grep
|
||||||
|
echo
|
||||||
|
echo '// Errors'
|
||||||
|
echo 'const ('
|
||||||
|
cat _error.out | grep -f _error.grep | sed 's/=\(.*\)/= Errno(\1)/'
|
||||||
|
echo ')'
|
||||||
|
|
||||||
|
echo
|
||||||
|
echo '// Signals'
|
||||||
|
echo 'const ('
|
||||||
|
cat _error.out | grep -f _signal.grep | sed 's/=\(.*\)/= Signal(\1)/'
|
||||||
|
echo ')'
|
||||||
|
|
||||||
|
# Run C program to print error and syscall strings.
|
||||||
|
(
|
||||||
|
echo -E "
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <errno.h>
|
||||||
|
#include <ctype.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <signal.h>
|
||||||
|
|
||||||
|
#define nelem(x) (sizeof(x)/sizeof((x)[0]))
|
||||||
|
|
||||||
|
enum { A = 'A', Z = 'Z', a = 'a', z = 'z' }; // avoid need for single quotes below
|
||||||
|
|
||||||
|
int errors[] = {
|
||||||
|
"
|
||||||
|
for i in $errors
|
||||||
|
do
|
||||||
|
echo -E ' '$i,
|
||||||
|
done
|
||||||
|
|
||||||
|
echo -E "
|
||||||
|
};
|
||||||
|
|
||||||
|
int signals[] = {
|
||||||
|
"
|
||||||
|
for i in $signals
|
||||||
|
do
|
||||||
|
echo -E ' '$i,
|
||||||
|
done
|
||||||
|
|
||||||
|
# Use -E because on some systems bash builtin interprets \n itself.
|
||||||
|
echo -E '
|
||||||
|
};
|
||||||
|
|
||||||
|
static int
|
||||||
|
intcmp(const void *a, const void *b)
|
||||||
|
{
|
||||||
|
return *(int*)a - *(int*)b;
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
main(void)
|
||||||
|
{
|
||||||
|
int i, j, e;
|
||||||
|
char buf[1024], *p;
|
||||||
|
|
||||||
|
printf("\n\n// Error table\n");
|
||||||
|
printf("var errors = [...]string {\n");
|
||||||
|
qsort(errors, nelem(errors), sizeof errors[0], intcmp);
|
||||||
|
for(i=0; i<nelem(errors); i++) {
|
||||||
|
e = errors[i];
|
||||||
|
if(i > 0 && errors[i-1] == e)
|
||||||
|
continue;
|
||||||
|
strcpy(buf, strerror(e));
|
||||||
|
// lowercase first letter: Bad -> bad, but STREAM -> STREAM.
|
||||||
|
if(A <= buf[0] && buf[0] <= Z && a <= buf[1] && buf[1] <= z)
|
||||||
|
buf[0] += a - A;
|
||||||
|
printf("\t%d: \"%s\",\n", e, buf);
|
||||||
|
}
|
||||||
|
printf("}\n\n");
|
||||||
|
|
||||||
|
printf("\n\n// Signal table\n");
|
||||||
|
printf("var signals = [...]string {\n");
|
||||||
|
qsort(signals, nelem(signals), sizeof signals[0], intcmp);
|
||||||
|
for(i=0; i<nelem(signals); i++) {
|
||||||
|
e = signals[i];
|
||||||
|
if(i > 0 && signals[i-1] == e)
|
||||||
|
continue;
|
||||||
|
strcpy(buf, strsignal(e));
|
||||||
|
// lowercase first letter: Bad -> bad, but STREAM -> STREAM.
|
||||||
|
if(A <= buf[0] && buf[0] <= Z && a <= buf[1] && buf[1] <= z)
|
||||||
|
buf[0] += a - A;
|
||||||
|
// cut trailing : number.
|
||||||
|
p = strrchr(buf, ":"[0]);
|
||||||
|
if(p)
|
||||||
|
*p = '\0';
|
||||||
|
printf("\t%d: \"%s\",\n", e, buf);
|
||||||
|
}
|
||||||
|
printf("}\n\n");
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
'
|
||||||
|
) >_errors.c
|
||||||
|
|
||||||
|
$CC $ccflags -o _errors _errors.c && $GORUN ./_errors && rm -f _errors.c _errors _const.go _error.grep _signal.grep _error.out
|
23
vendor/golang.org/x/sys/plan9/mksysnum_plan9.sh
generated
vendored
Normal file
23
vendor/golang.org/x/sys/plan9/mksysnum_plan9.sh
generated
vendored
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
# Copyright 2009 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.
|
||||||
|
|
||||||
|
COMMAND="mksysnum_plan9.sh $@"
|
||||||
|
|
||||||
|
cat <<EOF
|
||||||
|
// $COMMAND
|
||||||
|
// MACHINE GENERATED BY THE ABOVE COMMAND; DO NOT EDIT
|
||||||
|
|
||||||
|
package plan9
|
||||||
|
|
||||||
|
const(
|
||||||
|
EOF
|
||||||
|
|
||||||
|
SP='[ ]' # space or tab
|
||||||
|
sed "s/^#define${SP}\\([A-Z0-9_][A-Z0-9_]*\\)${SP}${SP}*\\([0-9][0-9]*\\)/SYS_\\1=\\2/g" \
|
||||||
|
< $1 | grep -v SYS__
|
||||||
|
|
||||||
|
cat <<EOF
|
||||||
|
)
|
||||||
|
EOF
|
21
vendor/golang.org/x/sys/plan9/pwd_go15_plan9.go
generated
vendored
Normal file
21
vendor/golang.org/x/sys/plan9/pwd_go15_plan9.go
generated
vendored
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
// Copyright 2015 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.
|
||||||
|
|
||||||
|
// +build go1.5
|
||||||
|
|
||||||
|
package plan9
|
||||||
|
|
||||||
|
import "syscall"
|
||||||
|
|
||||||
|
func fixwd() {
|
||||||
|
syscall.Fixwd()
|
||||||
|
}
|
||||||
|
|
||||||
|
func Getwd() (wd string, err error) {
|
||||||
|
return syscall.Getwd()
|
||||||
|
}
|
||||||
|
|
||||||
|
func Chdir(path string) error {
|
||||||
|
return syscall.Chdir(path)
|
||||||
|
}
|
23
vendor/golang.org/x/sys/plan9/pwd_plan9.go
generated
vendored
Normal file
23
vendor/golang.org/x/sys/plan9/pwd_plan9.go
generated
vendored
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
// Copyright 2015 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.
|
||||||
|
|
||||||
|
// +build !go1.5
|
||||||
|
|
||||||
|
package plan9
|
||||||
|
|
||||||
|
func fixwd() {
|
||||||
|
}
|
||||||
|
|
||||||
|
func Getwd() (wd string, err error) {
|
||||||
|
fd, err := open(".", O_RDONLY)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
defer Close(fd)
|
||||||
|
return Fd2path(fd)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Chdir(path string) error {
|
||||||
|
return chdir(path)
|
||||||
|
}
|
30
vendor/golang.org/x/sys/plan9/race.go
generated
vendored
Normal file
30
vendor/golang.org/x/sys/plan9/race.go
generated
vendored
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
// Copyright 2012 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.
|
||||||
|
|
||||||
|
// +build plan9,race
|
||||||
|
|
||||||
|
package plan9
|
||||||
|
|
||||||
|
import (
|
||||||
|
"runtime"
|
||||||
|
"unsafe"
|
||||||
|
)
|
||||||
|
|
||||||
|
const raceenabled = true
|
||||||
|
|
||||||
|
func raceAcquire(addr unsafe.Pointer) {
|
||||||
|
runtime.RaceAcquire(addr)
|
||||||
|
}
|
||||||
|
|
||||||
|
func raceReleaseMerge(addr unsafe.Pointer) {
|
||||||
|
runtime.RaceReleaseMerge(addr)
|
||||||
|
}
|
||||||
|
|
||||||
|
func raceReadRange(addr unsafe.Pointer, len int) {
|
||||||
|
runtime.RaceReadRange(addr, len)
|
||||||
|
}
|
||||||
|
|
||||||
|
func raceWriteRange(addr unsafe.Pointer, len int) {
|
||||||
|
runtime.RaceWriteRange(addr, len)
|
||||||
|
}
|
25
vendor/golang.org/x/sys/plan9/race0.go
generated
vendored
Normal file
25
vendor/golang.org/x/sys/plan9/race0.go
generated
vendored
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
// Copyright 2012 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.
|
||||||
|
|
||||||
|
// +build plan9,!race
|
||||||
|
|
||||||
|
package plan9
|
||||||
|
|
||||||
|
import (
|
||||||
|
"unsafe"
|
||||||
|
)
|
||||||
|
|
||||||
|
const raceenabled = false
|
||||||
|
|
||||||
|
func raceAcquire(addr unsafe.Pointer) {
|
||||||
|
}
|
||||||
|
|
||||||
|
func raceReleaseMerge(addr unsafe.Pointer) {
|
||||||
|
}
|
||||||
|
|
||||||
|
func raceReadRange(addr unsafe.Pointer, len int) {
|
||||||
|
}
|
||||||
|
|
||||||
|
func raceWriteRange(addr unsafe.Pointer, len int) {
|
||||||
|
}
|
22
vendor/golang.org/x/sys/plan9/str.go
generated
vendored
Normal file
22
vendor/golang.org/x/sys/plan9/str.go
generated
vendored
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
// Copyright 2009 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.
|
||||||
|
|
||||||
|
// +build plan9
|
||||||
|
|
||||||
|
package plan9
|
||||||
|
|
||||||
|
func itoa(val int) string { // do it here rather than with fmt to avoid dependency
|
||||||
|
if val < 0 {
|
||||||
|
return "-" + itoa(-val)
|
||||||
|
}
|
||||||
|
var buf [32]byte // big enough for int64
|
||||||
|
i := len(buf) - 1
|
||||||
|
for val >= 10 {
|
||||||
|
buf[i] = byte(val%10 + '0')
|
||||||
|
i--
|
||||||
|
val /= 10
|
||||||
|
}
|
||||||
|
buf[i] = byte(val + '0')
|
||||||
|
return string(buf[i:])
|
||||||
|
}
|
116
vendor/golang.org/x/sys/plan9/syscall.go
generated
vendored
Normal file
116
vendor/golang.org/x/sys/plan9/syscall.go
generated
vendored
Normal file
@ -0,0 +1,116 @@
|
|||||||
|
// Copyright 2009 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.
|
||||||
|
|
||||||
|
// +build plan9
|
||||||
|
|
||||||
|
// Package plan9 contains an interface to the low-level operating system
|
||||||
|
// primitives. OS details vary depending on the underlying system, and
|
||||||
|
// by default, godoc will display the OS-specific documentation for the current
|
||||||
|
// system. If you want godoc to display documentation for another
|
||||||
|
// system, set $GOOS and $GOARCH to the desired system. For example, if
|
||||||
|
// you want to view documentation for freebsd/arm on linux/amd64, set $GOOS
|
||||||
|
// to freebsd and $GOARCH to arm.
|
||||||
|
//
|
||||||
|
// The primary use of this package is inside other packages that provide a more
|
||||||
|
// portable interface to the system, such as "os", "time" and "net". Use
|
||||||
|
// those packages rather than this one if you can.
|
||||||
|
//
|
||||||
|
// For details of the functions and data types in this package consult
|
||||||
|
// the manuals for the appropriate operating system.
|
||||||
|
//
|
||||||
|
// These calls return err == nil to indicate success; otherwise
|
||||||
|
// err represents an operating system error describing the failure and
|
||||||
|
// holds a value of type syscall.ErrorString.
|
||||||
|
package plan9 // import "golang.org/x/sys/plan9"
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"strings"
|
||||||
|
"unsafe"
|
||||||
|
|
||||||
|
"golang.org/x/sys/internal/unsafeheader"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ByteSliceFromString returns a NUL-terminated slice of bytes
|
||||||
|
// containing the text of s. If s contains a NUL byte at any
|
||||||
|
// location, it returns (nil, EINVAL).
|
||||||
|
func ByteSliceFromString(s string) ([]byte, error) {
|
||||||
|
if strings.IndexByte(s, 0) != -1 {
|
||||||
|
return nil, EINVAL
|
||||||
|
}
|
||||||
|
a := make([]byte, len(s)+1)
|
||||||
|
copy(a, s)
|
||||||
|
return a, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// BytePtrFromString returns a pointer to a NUL-terminated array of
|
||||||
|
// bytes containing the text of s. If s contains a NUL byte at any
|
||||||
|
// location, it returns (nil, EINVAL).
|
||||||
|
func BytePtrFromString(s string) (*byte, error) {
|
||||||
|
a, err := ByteSliceFromString(s)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &a[0], nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ByteSliceToString returns a string form of the text represented by the slice s, with a terminating NUL and any
|
||||||
|
// bytes after the NUL removed.
|
||||||
|
func ByteSliceToString(s []byte) string {
|
||||||
|
if i := bytes.IndexByte(s, 0); i != -1 {
|
||||||
|
s = s[:i]
|
||||||
|
}
|
||||||
|
return string(s)
|
||||||
|
}
|
||||||
|
|
||||||
|
// BytePtrToString takes a pointer to a sequence of text and returns the corresponding string.
|
||||||
|
// If the pointer is nil, it returns the empty string. It assumes that the text sequence is terminated
|
||||||
|
// at a zero byte; if the zero byte is not present, the program may crash.
|
||||||
|
func BytePtrToString(p *byte) string {
|
||||||
|
if p == nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
if *p == 0 {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find NUL terminator.
|
||||||
|
n := 0
|
||||||
|
for ptr := unsafe.Pointer(p); *(*byte)(ptr) != 0; n++ {
|
||||||
|
ptr = unsafe.Pointer(uintptr(ptr) + 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
var s []byte
|
||||||
|
h := (*unsafeheader.Slice)(unsafe.Pointer(&s))
|
||||||
|
h.Data = unsafe.Pointer(p)
|
||||||
|
h.Len = n
|
||||||
|
h.Cap = n
|
||||||
|
|
||||||
|
return string(s)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Single-word zero for use when we need a valid pointer to 0 bytes.
|
||||||
|
// See mksyscall.pl.
|
||||||
|
var _zero uintptr
|
||||||
|
|
||||||
|
func (ts *Timespec) Unix() (sec int64, nsec int64) {
|
||||||
|
return int64(ts.Sec), int64(ts.Nsec)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (tv *Timeval) Unix() (sec int64, nsec int64) {
|
||||||
|
return int64(tv.Sec), int64(tv.Usec) * 1000
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ts *Timespec) Nano() int64 {
|
||||||
|
return int64(ts.Sec)*1e9 + int64(ts.Nsec)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (tv *Timeval) Nano() int64 {
|
||||||
|
return int64(tv.Sec)*1e9 + int64(tv.Usec)*1000
|
||||||
|
}
|
||||||
|
|
||||||
|
// use is a no-op, but the compiler cannot see that it is.
|
||||||
|
// Calling use(p) ensures that p is kept live until that point.
|
||||||
|
//go:noescape
|
||||||
|
func use(p unsafe.Pointer)
|
349
vendor/golang.org/x/sys/plan9/syscall_plan9.go
generated
vendored
Normal file
349
vendor/golang.org/x/sys/plan9/syscall_plan9.go
generated
vendored
Normal file
@ -0,0 +1,349 @@
|
|||||||
|
// Copyright 2011 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.
|
||||||
|
|
||||||
|
// Plan 9 system calls.
|
||||||
|
// This file is compiled as ordinary Go code,
|
||||||
|
// but it is also input to mksyscall,
|
||||||
|
// which parses the //sys lines and generates system call stubs.
|
||||||
|
// Note that sometimes we use a lowercase //sys name and
|
||||||
|
// wrap it in our own nicer implementation.
|
||||||
|
|
||||||
|
package plan9
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"syscall"
|
||||||
|
"unsafe"
|
||||||
|
)
|
||||||
|
|
||||||
|
// A Note is a string describing a process note.
|
||||||
|
// It implements the os.Signal interface.
|
||||||
|
type Note string
|
||||||
|
|
||||||
|
func (n Note) Signal() {}
|
||||||
|
|
||||||
|
func (n Note) String() string {
|
||||||
|
return string(n)
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
Stdin = 0
|
||||||
|
Stdout = 1
|
||||||
|
Stderr = 2
|
||||||
|
)
|
||||||
|
|
||||||
|
// For testing: clients can set this flag to force
|
||||||
|
// creation of IPv6 sockets to return EAFNOSUPPORT.
|
||||||
|
var SocketDisableIPv6 bool
|
||||||
|
|
||||||
|
func Syscall(trap, a1, a2, a3 uintptr) (r1, r2 uintptr, err syscall.ErrorString)
|
||||||
|
func Syscall6(trap, a1, a2, a3, a4, a5, a6 uintptr) (r1, r2 uintptr, err syscall.ErrorString)
|
||||||
|
func RawSyscall(trap, a1, a2, a3 uintptr) (r1, r2, err uintptr)
|
||||||
|
func RawSyscall6(trap, a1, a2, a3, a4, a5, a6 uintptr) (r1, r2, err uintptr)
|
||||||
|
|
||||||
|
func atoi(b []byte) (n uint) {
|
||||||
|
n = 0
|
||||||
|
for i := 0; i < len(b); i++ {
|
||||||
|
n = n*10 + uint(b[i]-'0')
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func cstring(s []byte) string {
|
||||||
|
i := bytes.IndexByte(s, 0)
|
||||||
|
if i == -1 {
|
||||||
|
i = len(s)
|
||||||
|
}
|
||||||
|
return string(s[:i])
|
||||||
|
}
|
||||||
|
|
||||||
|
func errstr() string {
|
||||||
|
var buf [ERRMAX]byte
|
||||||
|
|
||||||
|
RawSyscall(SYS_ERRSTR, uintptr(unsafe.Pointer(&buf[0])), uintptr(len(buf)), 0)
|
||||||
|
|
||||||
|
buf[len(buf)-1] = 0
|
||||||
|
return cstring(buf[:])
|
||||||
|
}
|
||||||
|
|
||||||
|
// Implemented in assembly to import from runtime.
|
||||||
|
func exit(code int)
|
||||||
|
|
||||||
|
func Exit(code int) { exit(code) }
|
||||||
|
|
||||||
|
func readnum(path string) (uint, error) {
|
||||||
|
var b [12]byte
|
||||||
|
|
||||||
|
fd, e := Open(path, O_RDONLY)
|
||||||
|
if e != nil {
|
||||||
|
return 0, e
|
||||||
|
}
|
||||||
|
defer Close(fd)
|
||||||
|
|
||||||
|
n, e := Pread(fd, b[:], 0)
|
||||||
|
|
||||||
|
if e != nil {
|
||||||
|
return 0, e
|
||||||
|
}
|
||||||
|
|
||||||
|
m := 0
|
||||||
|
for ; m < n && b[m] == ' '; m++ {
|
||||||
|
}
|
||||||
|
|
||||||
|
return atoi(b[m : n-1]), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func Getpid() (pid int) {
|
||||||
|
n, _ := readnum("#c/pid")
|
||||||
|
return int(n)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Getppid() (ppid int) {
|
||||||
|
n, _ := readnum("#c/ppid")
|
||||||
|
return int(n)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Read(fd int, p []byte) (n int, err error) {
|
||||||
|
return Pread(fd, p, -1)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Write(fd int, p []byte) (n int, err error) {
|
||||||
|
return Pwrite(fd, p, -1)
|
||||||
|
}
|
||||||
|
|
||||||
|
var ioSync int64
|
||||||
|
|
||||||
|
//sys fd2path(fd int, buf []byte) (err error)
|
||||||
|
func Fd2path(fd int) (path string, err error) {
|
||||||
|
var buf [512]byte
|
||||||
|
|
||||||
|
e := fd2path(fd, buf[:])
|
||||||
|
if e != nil {
|
||||||
|
return "", e
|
||||||
|
}
|
||||||
|
return cstring(buf[:]), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
//sys pipe(p *[2]int32) (err error)
|
||||||
|
func Pipe(p []int) (err error) {
|
||||||
|
if len(p) != 2 {
|
||||||
|
return syscall.ErrorString("bad arg in system call")
|
||||||
|
}
|
||||||
|
var pp [2]int32
|
||||||
|
err = pipe(&pp)
|
||||||
|
p[0] = int(pp[0])
|
||||||
|
p[1] = int(pp[1])
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Underlying system call writes to newoffset via pointer.
|
||||||
|
// Implemented in assembly to avoid allocation.
|
||||||
|
func seek(placeholder uintptr, fd int, offset int64, whence int) (newoffset int64, err string)
|
||||||
|
|
||||||
|
func Seek(fd int, offset int64, whence int) (newoffset int64, err error) {
|
||||||
|
newoffset, e := seek(0, fd, offset, whence)
|
||||||
|
|
||||||
|
if newoffset == -1 {
|
||||||
|
err = syscall.ErrorString(e)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func Mkdir(path string, mode uint32) (err error) {
|
||||||
|
fd, err := Create(path, O_RDONLY, DMDIR|mode)
|
||||||
|
|
||||||
|
if fd != -1 {
|
||||||
|
Close(fd)
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
type Waitmsg struct {
|
||||||
|
Pid int
|
||||||
|
Time [3]uint32
|
||||||
|
Msg string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w Waitmsg) Exited() bool { return true }
|
||||||
|
func (w Waitmsg) Signaled() bool { return false }
|
||||||
|
|
||||||
|
func (w Waitmsg) ExitStatus() int {
|
||||||
|
if len(w.Msg) == 0 {
|
||||||
|
// a normal exit returns no message
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
//sys await(s []byte) (n int, err error)
|
||||||
|
func Await(w *Waitmsg) (err error) {
|
||||||
|
var buf [512]byte
|
||||||
|
var f [5][]byte
|
||||||
|
|
||||||
|
n, err := await(buf[:])
|
||||||
|
|
||||||
|
if err != nil || w == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
nf := 0
|
||||||
|
p := 0
|
||||||
|
for i := 0; i < n && nf < len(f)-1; i++ {
|
||||||
|
if buf[i] == ' ' {
|
||||||
|
f[nf] = buf[p:i]
|
||||||
|
p = i + 1
|
||||||
|
nf++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
f[nf] = buf[p:]
|
||||||
|
nf++
|
||||||
|
|
||||||
|
if nf != len(f) {
|
||||||
|
return syscall.ErrorString("invalid wait message")
|
||||||
|
}
|
||||||
|
w.Pid = int(atoi(f[0]))
|
||||||
|
w.Time[0] = uint32(atoi(f[1]))
|
||||||
|
w.Time[1] = uint32(atoi(f[2]))
|
||||||
|
w.Time[2] = uint32(atoi(f[3]))
|
||||||
|
w.Msg = cstring(f[4])
|
||||||
|
if w.Msg == "''" {
|
||||||
|
// await() returns '' for no error
|
||||||
|
w.Msg = ""
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func Unmount(name, old string) (err error) {
|
||||||
|
fixwd()
|
||||||
|
oldp, err := BytePtrFromString(old)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
oldptr := uintptr(unsafe.Pointer(oldp))
|
||||||
|
|
||||||
|
var r0 uintptr
|
||||||
|
var e syscall.ErrorString
|
||||||
|
|
||||||
|
// bind(2) man page: If name is zero, everything bound or mounted upon old is unbound or unmounted.
|
||||||
|
if name == "" {
|
||||||
|
r0, _, e = Syscall(SYS_UNMOUNT, _zero, oldptr, 0)
|
||||||
|
} else {
|
||||||
|
namep, err := BytePtrFromString(name)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
r0, _, e = Syscall(SYS_UNMOUNT, uintptr(unsafe.Pointer(namep)), oldptr, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
if int32(r0) == -1 {
|
||||||
|
err = e
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func Fchdir(fd int) (err error) {
|
||||||
|
path, err := Fd2path(fd)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
return Chdir(path)
|
||||||
|
}
|
||||||
|
|
||||||
|
type Timespec struct {
|
||||||
|
Sec int32
|
||||||
|
Nsec int32
|
||||||
|
}
|
||||||
|
|
||||||
|
type Timeval struct {
|
||||||
|
Sec int32
|
||||||
|
Usec int32
|
||||||
|
}
|
||||||
|
|
||||||
|
func NsecToTimeval(nsec int64) (tv Timeval) {
|
||||||
|
nsec += 999 // round up to microsecond
|
||||||
|
tv.Usec = int32(nsec % 1e9 / 1e3)
|
||||||
|
tv.Sec = int32(nsec / 1e9)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func nsec() int64 {
|
||||||
|
var scratch int64
|
||||||
|
|
||||||
|
r0, _, _ := Syscall(SYS_NSEC, uintptr(unsafe.Pointer(&scratch)), 0, 0)
|
||||||
|
// TODO(aram): remove hack after I fix _nsec in the pc64 kernel.
|
||||||
|
if r0 == 0 {
|
||||||
|
return scratch
|
||||||
|
}
|
||||||
|
return int64(r0)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Gettimeofday(tv *Timeval) error {
|
||||||
|
nsec := nsec()
|
||||||
|
*tv = NsecToTimeval(nsec)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func Getpagesize() int { return 0x1000 }
|
||||||
|
|
||||||
|
func Getegid() (egid int) { return -1 }
|
||||||
|
func Geteuid() (euid int) { return -1 }
|
||||||
|
func Getgid() (gid int) { return -1 }
|
||||||
|
func Getuid() (uid int) { return -1 }
|
||||||
|
|
||||||
|
func Getgroups() (gids []int, err error) {
|
||||||
|
return make([]int, 0), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
//sys open(path string, mode int) (fd int, err error)
|
||||||
|
func Open(path string, mode int) (fd int, err error) {
|
||||||
|
fixwd()
|
||||||
|
return open(path, mode)
|
||||||
|
}
|
||||||
|
|
||||||
|
//sys create(path string, mode int, perm uint32) (fd int, err error)
|
||||||
|
func Create(path string, mode int, perm uint32) (fd int, err error) {
|
||||||
|
fixwd()
|
||||||
|
return create(path, mode, perm)
|
||||||
|
}
|
||||||
|
|
||||||
|
//sys remove(path string) (err error)
|
||||||
|
func Remove(path string) error {
|
||||||
|
fixwd()
|
||||||
|
return remove(path)
|
||||||
|
}
|
||||||
|
|
||||||
|
//sys stat(path string, edir []byte) (n int, err error)
|
||||||
|
func Stat(path string, edir []byte) (n int, err error) {
|
||||||
|
fixwd()
|
||||||
|
return stat(path, edir)
|
||||||
|
}
|
||||||
|
|
||||||
|
//sys bind(name string, old string, flag int) (err error)
|
||||||
|
func Bind(name string, old string, flag int) (err error) {
|
||||||
|
fixwd()
|
||||||
|
return bind(name, old, flag)
|
||||||
|
}
|
||||||
|
|
||||||
|
//sys mount(fd int, afd int, old string, flag int, aname string) (err error)
|
||||||
|
func Mount(fd int, afd int, old string, flag int, aname string) (err error) {
|
||||||
|
fixwd()
|
||||||
|
return mount(fd, afd, old, flag, aname)
|
||||||
|
}
|
||||||
|
|
||||||
|
//sys wstat(path string, edir []byte) (err error)
|
||||||
|
func Wstat(path string, edir []byte) (err error) {
|
||||||
|
fixwd()
|
||||||
|
return wstat(path, edir)
|
||||||
|
}
|
||||||
|
|
||||||
|
//sys chdir(path string) (err error)
|
||||||
|
//sys Dup(oldfd int, newfd int) (fd int, err error)
|
||||||
|
//sys Pread(fd int, p []byte, offset int64) (n int, err error)
|
||||||
|
//sys Pwrite(fd int, p []byte, offset int64) (n int, err error)
|
||||||
|
//sys Close(fd int) (err error)
|
||||||
|
//sys Fstat(fd int, edir []byte) (n int, err error)
|
||||||
|
//sys Fwstat(fd int, edir []byte) (err error)
|
284
vendor/golang.org/x/sys/plan9/zsyscall_plan9_386.go
generated
vendored
Normal file
284
vendor/golang.org/x/sys/plan9/zsyscall_plan9_386.go
generated
vendored
Normal file
@ -0,0 +1,284 @@
|
|||||||
|
// go run mksyscall.go -l32 -plan9 -tags plan9,386 syscall_plan9.go
|
||||||
|
// Code generated by the command above; see README.md. DO NOT EDIT.
|
||||||
|
|
||||||
|
// +build plan9,386
|
||||||
|
|
||||||
|
package plan9
|
||||||
|
|
||||||
|
import "unsafe"
|
||||||
|
|
||||||
|
// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
|
||||||
|
|
||||||
|
func fd2path(fd int, buf []byte) (err error) {
|
||||||
|
var _p0 unsafe.Pointer
|
||||||
|
if len(buf) > 0 {
|
||||||
|
_p0 = unsafe.Pointer(&buf[0])
|
||||||
|
} else {
|
||||||
|
_p0 = unsafe.Pointer(&_zero)
|
||||||
|
}
|
||||||
|
r0, _, e1 := Syscall(SYS_FD2PATH, uintptr(fd), uintptr(_p0), uintptr(len(buf)))
|
||||||
|
if int32(r0) == -1 {
|
||||||
|
err = e1
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
|
||||||
|
|
||||||
|
func pipe(p *[2]int32) (err error) {
|
||||||
|
r0, _, e1 := Syscall(SYS_PIPE, uintptr(unsafe.Pointer(p)), 0, 0)
|
||||||
|
if int32(r0) == -1 {
|
||||||
|
err = e1
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
|
||||||
|
|
||||||
|
func await(s []byte) (n int, err error) {
|
||||||
|
var _p0 unsafe.Pointer
|
||||||
|
if len(s) > 0 {
|
||||||
|
_p0 = unsafe.Pointer(&s[0])
|
||||||
|
} else {
|
||||||
|
_p0 = unsafe.Pointer(&_zero)
|
||||||
|
}
|
||||||
|
r0, _, e1 := Syscall(SYS_AWAIT, uintptr(_p0), uintptr(len(s)), 0)
|
||||||
|
n = int(r0)
|
||||||
|
if int32(r0) == -1 {
|
||||||
|
err = e1
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
|
||||||
|
|
||||||
|
func open(path string, mode int) (fd int, err error) {
|
||||||
|
var _p0 *byte
|
||||||
|
_p0, err = BytePtrFromString(path)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
r0, _, e1 := Syscall(SYS_OPEN, uintptr(unsafe.Pointer(_p0)), uintptr(mode), 0)
|
||||||
|
fd = int(r0)
|
||||||
|
if int32(r0) == -1 {
|
||||||
|
err = e1
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
|
||||||
|
|
||||||
|
func create(path string, mode int, perm uint32) (fd int, err error) {
|
||||||
|
var _p0 *byte
|
||||||
|
_p0, err = BytePtrFromString(path)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
r0, _, e1 := Syscall(SYS_CREATE, uintptr(unsafe.Pointer(_p0)), uintptr(mode), uintptr(perm))
|
||||||
|
fd = int(r0)
|
||||||
|
if int32(r0) == -1 {
|
||||||
|
err = e1
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
|
||||||
|
|
||||||
|
func remove(path string) (err error) {
|
||||||
|
var _p0 *byte
|
||||||
|
_p0, err = BytePtrFromString(path)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
r0, _, e1 := Syscall(SYS_REMOVE, uintptr(unsafe.Pointer(_p0)), 0, 0)
|
||||||
|
if int32(r0) == -1 {
|
||||||
|
err = e1
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
|
||||||
|
|
||||||
|
func stat(path string, edir []byte) (n int, err error) {
|
||||||
|
var _p0 *byte
|
||||||
|
_p0, err = BytePtrFromString(path)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var _p1 unsafe.Pointer
|
||||||
|
if len(edir) > 0 {
|
||||||
|
_p1 = unsafe.Pointer(&edir[0])
|
||||||
|
} else {
|
||||||
|
_p1 = unsafe.Pointer(&_zero)
|
||||||
|
}
|
||||||
|
r0, _, e1 := Syscall(SYS_STAT, uintptr(unsafe.Pointer(_p0)), uintptr(_p1), uintptr(len(edir)))
|
||||||
|
n = int(r0)
|
||||||
|
if int32(r0) == -1 {
|
||||||
|
err = e1
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
|
||||||
|
|
||||||
|
func bind(name string, old string, flag int) (err error) {
|
||||||
|
var _p0 *byte
|
||||||
|
_p0, err = BytePtrFromString(name)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var _p1 *byte
|
||||||
|
_p1, err = BytePtrFromString(old)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
r0, _, e1 := Syscall(SYS_BIND, uintptr(unsafe.Pointer(_p0)), uintptr(unsafe.Pointer(_p1)), uintptr(flag))
|
||||||
|
if int32(r0) == -1 {
|
||||||
|
err = e1
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
|
||||||
|
|
||||||
|
func mount(fd int, afd int, old string, flag int, aname string) (err error) {
|
||||||
|
var _p0 *byte
|
||||||
|
_p0, err = BytePtrFromString(old)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var _p1 *byte
|
||||||
|
_p1, err = BytePtrFromString(aname)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
r0, _, e1 := Syscall6(SYS_MOUNT, uintptr(fd), uintptr(afd), uintptr(unsafe.Pointer(_p0)), uintptr(flag), uintptr(unsafe.Pointer(_p1)), 0)
|
||||||
|
if int32(r0) == -1 {
|
||||||
|
err = e1
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
|
||||||
|
|
||||||
|
func wstat(path string, edir []byte) (err error) {
|
||||||
|
var _p0 *byte
|
||||||
|
_p0, err = BytePtrFromString(path)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var _p1 unsafe.Pointer
|
||||||
|
if len(edir) > 0 {
|
||||||
|
_p1 = unsafe.Pointer(&edir[0])
|
||||||
|
} else {
|
||||||
|
_p1 = unsafe.Pointer(&_zero)
|
||||||
|
}
|
||||||
|
r0, _, e1 := Syscall(SYS_WSTAT, uintptr(unsafe.Pointer(_p0)), uintptr(_p1), uintptr(len(edir)))
|
||||||
|
if int32(r0) == -1 {
|
||||||
|
err = e1
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
|
||||||
|
|
||||||
|
func chdir(path string) (err error) {
|
||||||
|
var _p0 *byte
|
||||||
|
_p0, err = BytePtrFromString(path)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
r0, _, e1 := Syscall(SYS_CHDIR, uintptr(unsafe.Pointer(_p0)), 0, 0)
|
||||||
|
if int32(r0) == -1 {
|
||||||
|
err = e1
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
|
||||||
|
|
||||||
|
func Dup(oldfd int, newfd int) (fd int, err error) {
|
||||||
|
r0, _, e1 := Syscall(SYS_DUP, uintptr(oldfd), uintptr(newfd), 0)
|
||||||
|
fd = int(r0)
|
||||||
|
if int32(r0) == -1 {
|
||||||
|
err = e1
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
|
||||||
|
|
||||||
|
func Pread(fd int, p []byte, offset int64) (n int, err error) {
|
||||||
|
var _p0 unsafe.Pointer
|
||||||
|
if len(p) > 0 {
|
||||||
|
_p0 = unsafe.Pointer(&p[0])
|
||||||
|
} else {
|
||||||
|
_p0 = unsafe.Pointer(&_zero)
|
||||||
|
}
|
||||||
|
r0, _, e1 := Syscall6(SYS_PREAD, uintptr(fd), uintptr(_p0), uintptr(len(p)), uintptr(offset), uintptr(offset>>32), 0)
|
||||||
|
n = int(r0)
|
||||||
|
if int32(r0) == -1 {
|
||||||
|
err = e1
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
|
||||||
|
|
||||||
|
func Pwrite(fd int, p []byte, offset int64) (n int, err error) {
|
||||||
|
var _p0 unsafe.Pointer
|
||||||
|
if len(p) > 0 {
|
||||||
|
_p0 = unsafe.Pointer(&p[0])
|
||||||
|
} else {
|
||||||
|
_p0 = unsafe.Pointer(&_zero)
|
||||||
|
}
|
||||||
|
r0, _, e1 := Syscall6(SYS_PWRITE, uintptr(fd), uintptr(_p0), uintptr(len(p)), uintptr(offset), uintptr(offset>>32), 0)
|
||||||
|
n = int(r0)
|
||||||
|
if int32(r0) == -1 {
|
||||||
|
err = e1
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
|
||||||
|
|
||||||
|
func Close(fd int) (err error) {
|
||||||
|
r0, _, e1 := Syscall(SYS_CLOSE, uintptr(fd), 0, 0)
|
||||||
|
if int32(r0) == -1 {
|
||||||
|
err = e1
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
|
||||||
|
|
||||||
|
func Fstat(fd int, edir []byte) (n int, err error) {
|
||||||
|
var _p0 unsafe.Pointer
|
||||||
|
if len(edir) > 0 {
|
||||||
|
_p0 = unsafe.Pointer(&edir[0])
|
||||||
|
} else {
|
||||||
|
_p0 = unsafe.Pointer(&_zero)
|
||||||
|
}
|
||||||
|
r0, _, e1 := Syscall(SYS_FSTAT, uintptr(fd), uintptr(_p0), uintptr(len(edir)))
|
||||||
|
n = int(r0)
|
||||||
|
if int32(r0) == -1 {
|
||||||
|
err = e1
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
|
||||||
|
|
||||||
|
func Fwstat(fd int, edir []byte) (err error) {
|
||||||
|
var _p0 unsafe.Pointer
|
||||||
|
if len(edir) > 0 {
|
||||||
|
_p0 = unsafe.Pointer(&edir[0])
|
||||||
|
} else {
|
||||||
|
_p0 = unsafe.Pointer(&_zero)
|
||||||
|
}
|
||||||
|
r0, _, e1 := Syscall(SYS_FWSTAT, uintptr(fd), uintptr(_p0), uintptr(len(edir)))
|
||||||
|
if int32(r0) == -1 {
|
||||||
|
err = e1
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
284
vendor/golang.org/x/sys/plan9/zsyscall_plan9_amd64.go
generated
vendored
Normal file
284
vendor/golang.org/x/sys/plan9/zsyscall_plan9_amd64.go
generated
vendored
Normal file
@ -0,0 +1,284 @@
|
|||||||
|
// go run mksyscall.go -l32 -plan9 -tags plan9,amd64 syscall_plan9.go
|
||||||
|
// Code generated by the command above; see README.md. DO NOT EDIT.
|
||||||
|
|
||||||
|
// +build plan9,amd64
|
||||||
|
|
||||||
|
package plan9
|
||||||
|
|
||||||
|
import "unsafe"
|
||||||
|
|
||||||
|
// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
|
||||||
|
|
||||||
|
func fd2path(fd int, buf []byte) (err error) {
|
||||||
|
var _p0 unsafe.Pointer
|
||||||
|
if len(buf) > 0 {
|
||||||
|
_p0 = unsafe.Pointer(&buf[0])
|
||||||
|
} else {
|
||||||
|
_p0 = unsafe.Pointer(&_zero)
|
||||||
|
}
|
||||||
|
r0, _, e1 := Syscall(SYS_FD2PATH, uintptr(fd), uintptr(_p0), uintptr(len(buf)))
|
||||||
|
if int32(r0) == -1 {
|
||||||
|
err = e1
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
|
||||||
|
|
||||||
|
func pipe(p *[2]int32) (err error) {
|
||||||
|
r0, _, e1 := Syscall(SYS_PIPE, uintptr(unsafe.Pointer(p)), 0, 0)
|
||||||
|
if int32(r0) == -1 {
|
||||||
|
err = e1
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
|
||||||
|
|
||||||
|
func await(s []byte) (n int, err error) {
|
||||||
|
var _p0 unsafe.Pointer
|
||||||
|
if len(s) > 0 {
|
||||||
|
_p0 = unsafe.Pointer(&s[0])
|
||||||
|
} else {
|
||||||
|
_p0 = unsafe.Pointer(&_zero)
|
||||||
|
}
|
||||||
|
r0, _, e1 := Syscall(SYS_AWAIT, uintptr(_p0), uintptr(len(s)), 0)
|
||||||
|
n = int(r0)
|
||||||
|
if int32(r0) == -1 {
|
||||||
|
err = e1
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
|
||||||
|
|
||||||
|
func open(path string, mode int) (fd int, err error) {
|
||||||
|
var _p0 *byte
|
||||||
|
_p0, err = BytePtrFromString(path)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
r0, _, e1 := Syscall(SYS_OPEN, uintptr(unsafe.Pointer(_p0)), uintptr(mode), 0)
|
||||||
|
fd = int(r0)
|
||||||
|
if int32(r0) == -1 {
|
||||||
|
err = e1
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
|
||||||
|
|
||||||
|
func create(path string, mode int, perm uint32) (fd int, err error) {
|
||||||
|
var _p0 *byte
|
||||||
|
_p0, err = BytePtrFromString(path)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
r0, _, e1 := Syscall(SYS_CREATE, uintptr(unsafe.Pointer(_p0)), uintptr(mode), uintptr(perm))
|
||||||
|
fd = int(r0)
|
||||||
|
if int32(r0) == -1 {
|
||||||
|
err = e1
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
|
||||||
|
|
||||||
|
func remove(path string) (err error) {
|
||||||
|
var _p0 *byte
|
||||||
|
_p0, err = BytePtrFromString(path)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
r0, _, e1 := Syscall(SYS_REMOVE, uintptr(unsafe.Pointer(_p0)), 0, 0)
|
||||||
|
if int32(r0) == -1 {
|
||||||
|
err = e1
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
|
||||||
|
|
||||||
|
func stat(path string, edir []byte) (n int, err error) {
|
||||||
|
var _p0 *byte
|
||||||
|
_p0, err = BytePtrFromString(path)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var _p1 unsafe.Pointer
|
||||||
|
if len(edir) > 0 {
|
||||||
|
_p1 = unsafe.Pointer(&edir[0])
|
||||||
|
} else {
|
||||||
|
_p1 = unsafe.Pointer(&_zero)
|
||||||
|
}
|
||||||
|
r0, _, e1 := Syscall(SYS_STAT, uintptr(unsafe.Pointer(_p0)), uintptr(_p1), uintptr(len(edir)))
|
||||||
|
n = int(r0)
|
||||||
|
if int32(r0) == -1 {
|
||||||
|
err = e1
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
|
||||||
|
|
||||||
|
func bind(name string, old string, flag int) (err error) {
|
||||||
|
var _p0 *byte
|
||||||
|
_p0, err = BytePtrFromString(name)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var _p1 *byte
|
||||||
|
_p1, err = BytePtrFromString(old)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
r0, _, e1 := Syscall(SYS_BIND, uintptr(unsafe.Pointer(_p0)), uintptr(unsafe.Pointer(_p1)), uintptr(flag))
|
||||||
|
if int32(r0) == -1 {
|
||||||
|
err = e1
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
|
||||||
|
|
||||||
|
func mount(fd int, afd int, old string, flag int, aname string) (err error) {
|
||||||
|
var _p0 *byte
|
||||||
|
_p0, err = BytePtrFromString(old)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var _p1 *byte
|
||||||
|
_p1, err = BytePtrFromString(aname)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
r0, _, e1 := Syscall6(SYS_MOUNT, uintptr(fd), uintptr(afd), uintptr(unsafe.Pointer(_p0)), uintptr(flag), uintptr(unsafe.Pointer(_p1)), 0)
|
||||||
|
if int32(r0) == -1 {
|
||||||
|
err = e1
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
|
||||||
|
|
||||||
|
func wstat(path string, edir []byte) (err error) {
|
||||||
|
var _p0 *byte
|
||||||
|
_p0, err = BytePtrFromString(path)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var _p1 unsafe.Pointer
|
||||||
|
if len(edir) > 0 {
|
||||||
|
_p1 = unsafe.Pointer(&edir[0])
|
||||||
|
} else {
|
||||||
|
_p1 = unsafe.Pointer(&_zero)
|
||||||
|
}
|
||||||
|
r0, _, e1 := Syscall(SYS_WSTAT, uintptr(unsafe.Pointer(_p0)), uintptr(_p1), uintptr(len(edir)))
|
||||||
|
if int32(r0) == -1 {
|
||||||
|
err = e1
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
|
||||||
|
|
||||||
|
func chdir(path string) (err error) {
|
||||||
|
var _p0 *byte
|
||||||
|
_p0, err = BytePtrFromString(path)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
r0, _, e1 := Syscall(SYS_CHDIR, uintptr(unsafe.Pointer(_p0)), 0, 0)
|
||||||
|
if int32(r0) == -1 {
|
||||||
|
err = e1
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
|
||||||
|
|
||||||
|
func Dup(oldfd int, newfd int) (fd int, err error) {
|
||||||
|
r0, _, e1 := Syscall(SYS_DUP, uintptr(oldfd), uintptr(newfd), 0)
|
||||||
|
fd = int(r0)
|
||||||
|
if int32(r0) == -1 {
|
||||||
|
err = e1
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
|
||||||
|
|
||||||
|
func Pread(fd int, p []byte, offset int64) (n int, err error) {
|
||||||
|
var _p0 unsafe.Pointer
|
||||||
|
if len(p) > 0 {
|
||||||
|
_p0 = unsafe.Pointer(&p[0])
|
||||||
|
} else {
|
||||||
|
_p0 = unsafe.Pointer(&_zero)
|
||||||
|
}
|
||||||
|
r0, _, e1 := Syscall6(SYS_PREAD, uintptr(fd), uintptr(_p0), uintptr(len(p)), uintptr(offset), uintptr(offset>>32), 0)
|
||||||
|
n = int(r0)
|
||||||
|
if int32(r0) == -1 {
|
||||||
|
err = e1
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
|
||||||
|
|
||||||
|
func Pwrite(fd int, p []byte, offset int64) (n int, err error) {
|
||||||
|
var _p0 unsafe.Pointer
|
||||||
|
if len(p) > 0 {
|
||||||
|
_p0 = unsafe.Pointer(&p[0])
|
||||||
|
} else {
|
||||||
|
_p0 = unsafe.Pointer(&_zero)
|
||||||
|
}
|
||||||
|
r0, _, e1 := Syscall6(SYS_PWRITE, uintptr(fd), uintptr(_p0), uintptr(len(p)), uintptr(offset), uintptr(offset>>32), 0)
|
||||||
|
n = int(r0)
|
||||||
|
if int32(r0) == -1 {
|
||||||
|
err = e1
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
|
||||||
|
|
||||||
|
func Close(fd int) (err error) {
|
||||||
|
r0, _, e1 := Syscall(SYS_CLOSE, uintptr(fd), 0, 0)
|
||||||
|
if int32(r0) == -1 {
|
||||||
|
err = e1
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
|
||||||
|
|
||||||
|
func Fstat(fd int, edir []byte) (n int, err error) {
|
||||||
|
var _p0 unsafe.Pointer
|
||||||
|
if len(edir) > 0 {
|
||||||
|
_p0 = unsafe.Pointer(&edir[0])
|
||||||
|
} else {
|
||||||
|
_p0 = unsafe.Pointer(&_zero)
|
||||||
|
}
|
||||||
|
r0, _, e1 := Syscall(SYS_FSTAT, uintptr(fd), uintptr(_p0), uintptr(len(edir)))
|
||||||
|
n = int(r0)
|
||||||
|
if int32(r0) == -1 {
|
||||||
|
err = e1
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
|
||||||
|
|
||||||
|
func Fwstat(fd int, edir []byte) (err error) {
|
||||||
|
var _p0 unsafe.Pointer
|
||||||
|
if len(edir) > 0 {
|
||||||
|
_p0 = unsafe.Pointer(&edir[0])
|
||||||
|
} else {
|
||||||
|
_p0 = unsafe.Pointer(&_zero)
|
||||||
|
}
|
||||||
|
r0, _, e1 := Syscall(SYS_FWSTAT, uintptr(fd), uintptr(_p0), uintptr(len(edir)))
|
||||||
|
if int32(r0) == -1 {
|
||||||
|
err = e1
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user