chore(deps): bump go to 1.20
- update all dependencies - switch to devincarr/quic-go - drop go 1.18 as quic-go no longer supports it
This commit is contained in:
parent
9426b60308
commit
d8fb43afe9
106
go.mod
106
go.mod
|
@ -1,107 +1,105 @@
|
||||||
module github.com/cloudflare/cloudflared
|
module github.com/cloudflare/cloudflared
|
||||||
|
|
||||||
go 1.19
|
go 1.20
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/cloudflare/brotli-go v0.0.0-20191101163834-d34379f7ff93
|
github.com/cloudflare/brotli-go v0.0.0-20191101163834-d34379f7ff93
|
||||||
github.com/cloudflare/golibs v0.0.0-20170913112048-333127dbecfc
|
github.com/cloudflare/golibs v0.0.0-20210909181612-21743d7dd02a
|
||||||
github.com/coredns/coredns v1.10.0
|
github.com/coredns/coredns v1.10.1
|
||||||
github.com/coreos/go-oidc/v3 v3.4.0
|
github.com/coreos/go-oidc/v3 v3.5.0
|
||||||
github.com/coreos/go-systemd v0.0.0-20191104093116-d3cd4ed1dbcf
|
github.com/coreos/go-systemd v0.0.0-20191104093116-d3cd4ed1dbcf
|
||||||
github.com/facebookgo/grace v0.0.0-20180706040059-75cf19382434
|
github.com/facebookgo/grace v0.0.0-20180706040059-75cf19382434
|
||||||
github.com/fsnotify/fsnotify v1.4.9
|
github.com/fsnotify/fsnotify v1.6.0
|
||||||
github.com/getsentry/raven-go v0.2.0
|
github.com/getsentry/raven-go v0.2.0
|
||||||
github.com/getsentry/sentry-go v0.16.0
|
github.com/getsentry/sentry-go v0.21.0
|
||||||
github.com/go-chi/chi/v5 v5.0.8
|
github.com/go-chi/chi/v5 v5.0.8
|
||||||
github.com/go-jose/go-jose/v3 v3.0.0
|
github.com/go-jose/go-jose/v3 v3.0.0
|
||||||
github.com/gobwas/ws v1.0.4
|
github.com/gobwas/ws v1.2.0
|
||||||
github.com/golang-collections/collections v0.0.0-20130729185459-604e922904d3
|
github.com/golang-collections/collections v0.0.0-20130729185459-604e922904d3
|
||||||
github.com/google/gopacket v1.1.19
|
github.com/google/gopacket v1.1.19
|
||||||
github.com/google/uuid v1.3.0
|
github.com/google/uuid v1.3.0
|
||||||
github.com/gorilla/websocket v1.4.2
|
github.com/gorilla/websocket v1.5.0
|
||||||
github.com/json-iterator/go v1.1.12
|
github.com/json-iterator/go v1.1.12
|
||||||
github.com/mattn/go-colorable v0.1.13
|
github.com/mattn/go-colorable v0.1.13
|
||||||
github.com/miekg/dns v1.1.50
|
github.com/miekg/dns v1.1.54
|
||||||
github.com/mitchellh/go-homedir v1.1.0
|
github.com/mitchellh/go-homedir v1.1.0
|
||||||
github.com/pkg/errors v0.9.1
|
github.com/pkg/errors v0.9.1
|
||||||
github.com/prometheus/client_golang v1.13.0
|
github.com/prometheus/client_golang v1.15.1
|
||||||
github.com/prometheus/client_model v0.2.0
|
github.com/prometheus/client_model v0.4.0
|
||||||
github.com/quic-go/quic-go v0.0.0-00010101000000-000000000000
|
github.com/quic-go/quic-go v0.34.0
|
||||||
github.com/rs/zerolog v1.20.0
|
github.com/rs/zerolog v1.29.1
|
||||||
github.com/stretchr/testify v1.8.1
|
github.com/stretchr/testify v1.8.2
|
||||||
github.com/urfave/cli/v2 v2.3.0
|
github.com/urfave/cli/v2 v2.25.3
|
||||||
go.opentelemetry.io/contrib/propagators v0.22.0
|
go.opentelemetry.io/contrib/propagators v0.22.0
|
||||||
go.opentelemetry.io/otel v1.6.3
|
go.opentelemetry.io/otel v1.15.1
|
||||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.6.3
|
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.15.1
|
||||||
go.opentelemetry.io/otel/sdk v1.6.3
|
go.opentelemetry.io/otel/sdk v1.15.1
|
||||||
go.opentelemetry.io/otel/trace v1.6.3
|
go.opentelemetry.io/otel/trace v1.15.1
|
||||||
go.opentelemetry.io/proto/otlp v0.15.0
|
go.opentelemetry.io/proto/otlp v0.19.0
|
||||||
go.uber.org/automaxprocs v1.4.0
|
go.uber.org/automaxprocs v1.5.2
|
||||||
golang.org/x/crypto v0.8.0
|
golang.org/x/crypto v0.9.0
|
||||||
golang.org/x/net v0.9.0
|
golang.org/x/net v0.10.0
|
||||||
golang.org/x/sync v0.1.0
|
golang.org/x/sync v0.2.0
|
||||||
golang.org/x/sys v0.7.0
|
golang.org/x/sys v0.8.0
|
||||||
golang.org/x/term v0.7.0
|
golang.org/x/term v0.8.0
|
||||||
google.golang.org/protobuf v1.28.1
|
google.golang.org/protobuf v1.30.0
|
||||||
gopkg.in/coreos/go-oidc.v2 v2.2.1
|
gopkg.in/coreos/go-oidc.v2 v2.2.1
|
||||||
gopkg.in/natefinch/lumberjack.v2 v2.0.0
|
gopkg.in/natefinch/lumberjack.v2 v2.2.1
|
||||||
gopkg.in/square/go-jose.v2 v2.6.0
|
gopkg.in/square/go-jose.v2 v2.6.0
|
||||||
gopkg.in/yaml.v3 v3.0.1
|
gopkg.in/yaml.v3 v3.0.1
|
||||||
nhooyr.io/websocket v1.8.7
|
nhooyr.io/websocket v1.8.7
|
||||||
zombiezen.com/go/capnproto2 v2.18.0+incompatible
|
zombiezen.com/go/capnproto2 v2.18.2+incompatible
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/BurntSushi/toml v1.2.0 // indirect
|
github.com/BurntSushi/toml v1.2.1 // indirect
|
||||||
github.com/apparentlymart/go-cidr v1.1.0 // indirect
|
github.com/apparentlymart/go-cidr v1.1.0 // indirect
|
||||||
github.com/beorn7/perks v1.0.1 // indirect
|
github.com/beorn7/perks v1.0.1 // indirect
|
||||||
github.com/certifi/gocertifi v0.0.0-20210507211836-431795d63e8d // indirect
|
github.com/certifi/gocertifi v0.0.0-20210507211836-431795d63e8d // indirect
|
||||||
github.com/cespare/xxhash/v2 v2.1.2 // indirect
|
github.com/cespare/xxhash/v2 v2.2.0 // indirect
|
||||||
github.com/cloudflare/circl v1.2.1-0.20220809205628-0a9554f37a47 // indirect
|
github.com/cloudflare/circl v1.3.3 // indirect
|
||||||
github.com/coredns/caddy v1.1.1 // indirect
|
github.com/coredns/caddy v1.1.1 // indirect
|
||||||
github.com/cpuguy83/go-md2man/v2 v2.0.0 // indirect
|
github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect
|
||||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||||
github.com/facebookgo/ensure v0.0.0-20160127193407-b4ab57deab51 // indirect
|
github.com/facebookgo/ensure v0.0.0-20160127193407-b4ab57deab51 // indirect
|
||||||
github.com/facebookgo/freeport v0.0.0-20150612182905-d4adf43b75b9 // indirect
|
github.com/facebookgo/freeport v0.0.0-20150612182905-d4adf43b75b9 // indirect
|
||||||
github.com/facebookgo/stack v0.0.0-20160209184415-751773369052 // indirect
|
github.com/facebookgo/stack v0.0.0-20160209184415-751773369052 // indirect
|
||||||
github.com/facebookgo/subset v0.0.0-20150612182917-8dac2c3c4870 // indirect
|
github.com/facebookgo/subset v0.0.0-20150612182917-8dac2c3c4870 // indirect
|
||||||
github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568 // indirect
|
github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568 // indirect
|
||||||
github.com/go-logr/logr v1.2.3 // indirect
|
github.com/go-logr/logr v1.2.4 // indirect
|
||||||
github.com/go-logr/stdr v1.2.2 // indirect
|
github.com/go-logr/stdr v1.2.2 // indirect
|
||||||
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0 // indirect
|
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect
|
||||||
github.com/gobwas/httphead v0.0.0-20200921212729-da3d93bc3c58 // indirect
|
github.com/gobwas/httphead v0.1.0 // indirect
|
||||||
github.com/gobwas/pool v0.2.1 // indirect
|
github.com/gobwas/pool v0.2.1 // indirect
|
||||||
github.com/golang/mock v1.6.0 // indirect
|
github.com/golang/mock v1.6.0 // indirect
|
||||||
github.com/golang/protobuf v1.5.2 // indirect
|
github.com/golang/protobuf v1.5.3 // indirect
|
||||||
github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1 // indirect
|
github.com/google/pprof v0.0.0-20230510103437-eeec1cb781c3 // indirect
|
||||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.7.0 // indirect
|
github.com/grpc-ecosystem/grpc-gateway/v2 v2.15.2 // indirect
|
||||||
github.com/grpc-ecosystem/grpc-opentracing v0.0.0-20180507213350-8e809c8a8645 // indirect
|
github.com/grpc-ecosystem/grpc-opentracing v0.0.0-20180507213350-8e809c8a8645 // indirect
|
||||||
github.com/klauspost/compress v1.15.11 // indirect
|
github.com/klauspost/compress v1.16.5 // indirect
|
||||||
github.com/kr/text v0.2.0 // indirect
|
github.com/kr/text v0.2.0 // indirect
|
||||||
github.com/kylelemons/godebug v1.1.0 // indirect
|
github.com/kylelemons/godebug v1.1.0 // indirect
|
||||||
github.com/mattn/go-isatty v0.0.16 // indirect
|
github.com/mattn/go-isatty v0.0.18 // indirect
|
||||||
github.com/matttproud/golang_protobuf_extensions v1.0.1 // indirect
|
github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect
|
||||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||||
github.com/onsi/ginkgo/v2 v2.4.0 // indirect
|
github.com/onsi/ginkgo/v2 v2.9.4 // indirect
|
||||||
github.com/onsi/gomega v1.23.0 // indirect
|
|
||||||
github.com/opentracing/opentracing-go v1.2.0 // indirect
|
github.com/opentracing/opentracing-go v1.2.0 // indirect
|
||||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||||
github.com/pquerna/cachecontrol v0.0.0-20180517163645-1555304b9b35 // indirect
|
github.com/pquerna/cachecontrol v0.1.0 // indirect
|
||||||
github.com/prometheus/common v0.37.0 // indirect
|
github.com/prometheus/common v0.43.0 // indirect
|
||||||
github.com/prometheus/procfs v0.8.0 // indirect
|
github.com/prometheus/procfs v0.9.0 // indirect
|
||||||
github.com/quic-go/qtls-go1-19 v0.3.2 // indirect
|
github.com/quic-go/qtls-go1-19 v0.3.2 // indirect
|
||||||
github.com/quic-go/qtls-go1-20 v0.2.2 // indirect
|
github.com/quic-go/qtls-go1-20 v0.2.2 // indirect
|
||||||
github.com/russross/blackfriday/v2 v2.1.0 // indirect
|
github.com/russross/blackfriday/v2 v2.1.0 // indirect
|
||||||
golang.org/x/exp v0.0.0-20221205204356-47842c84f3db // indirect
|
golang.org/x/exp v0.0.0-20230510235704-dd950f8aeaea // indirect
|
||||||
golang.org/x/mod v0.8.0 // indirect
|
golang.org/x/mod v0.10.0 // indirect
|
||||||
golang.org/x/oauth2 v0.4.0 // indirect
|
golang.org/x/oauth2 v0.8.0 // indirect
|
||||||
golang.org/x/text v0.9.0 // indirect
|
golang.org/x/text v0.9.0 // indirect
|
||||||
golang.org/x/tools v0.6.0 // indirect
|
golang.org/x/tools v0.9.1 // indirect
|
||||||
google.golang.org/appengine v1.6.7 // indirect
|
google.golang.org/appengine v1.6.7 // indirect
|
||||||
google.golang.org/genproto v0.0.0-20221202195650-67e5cbc046fd // indirect
|
google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1 // indirect
|
||||||
google.golang.org/grpc v1.51.0 // indirect
|
google.golang.org/grpc v1.55.0 // indirect
|
||||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
|
|
||||||
gopkg.in/yaml.v2 v2.4.0 // indirect
|
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
535
go.sum
535
go.sum
|
@ -13,36 +13,15 @@ cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKV
|
||||||
cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs=
|
cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs=
|
||||||
cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc=
|
cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc=
|
||||||
cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY=
|
cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY=
|
||||||
cloud.google.com/go v0.72.0/go.mod h1:M+5Vjvlc2wnp6tjzE102Dw08nGShTscUx2nZMufOKPI=
|
|
||||||
cloud.google.com/go v0.74.0/go.mod h1:VV1xSbzvo+9QJOxLDaJfTjx5e+MePCpCWwvftOeQmWk=
|
|
||||||
cloud.google.com/go v0.78.0/go.mod h1:QjdrLG0uq+YwhjoVOLsS1t7TW8fs36kLs4XO5R5ECHg=
|
|
||||||
cloud.google.com/go v0.79.0/go.mod h1:3bzgcEeQlzbuEAYu4mrWhKqWjmpprinYgKJLgKHnbb8=
|
|
||||||
cloud.google.com/go v0.81.0/go.mod h1:mk/AM35KwGk/Nm2YSeZbxXdrNK3KZOYHmLkOqC2V6E0=
|
|
||||||
cloud.google.com/go v0.83.0/go.mod h1:Z7MJUsANfY0pYPdw0lbnivPx4/vhy/e2FEkSkF7vAVY=
|
|
||||||
cloud.google.com/go v0.84.0/go.mod h1:RazrYuxIK6Kb7YrzzhPoLmCVzl7Sup4NrbKPg8KHSUM=
|
|
||||||
cloud.google.com/go v0.87.0/go.mod h1:TpDYlFy7vuLzZMMZ+B6iRiELaY7z/gJPaqbMx6mlWcY=
|
|
||||||
cloud.google.com/go v0.90.0/go.mod h1:kRX0mNRHe0e2rC6oNakvwQqzyDmg57xJ+SZU1eT2aDQ=
|
|
||||||
cloud.google.com/go v0.93.3/go.mod h1:8utlLll2EF5XMAV15woO4lSbWQlk8rer9aLOfLh7+YI=
|
|
||||||
cloud.google.com/go v0.94.1/go.mod h1:qAlAugsXlC+JWO+Bke5vCtc9ONxjQT3drlTTnAplMW4=
|
|
||||||
cloud.google.com/go v0.97.0/go.mod h1:GF7l59pYBVlXQIBLx3a761cZ41F9bBH3JUlihCt2Udc=
|
|
||||||
cloud.google.com/go v0.99.0/go.mod h1:w0Xx2nLzqWJPuozYQX+hFfCSI8WioryfRDzkoI/Y2ZA=
|
|
||||||
cloud.google.com/go v0.100.2/go.mod h1:4Xra9TjzAeYHrl5+oeLlzbM2k3mjVhZh4UqTZ//w99A=
|
|
||||||
cloud.google.com/go v0.102.0/go.mod h1:oWcCzKlqJ5zgHQt9YsaeTY9KzIvjyy0ArmiBUgpQ+nc=
|
|
||||||
cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=
|
cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=
|
||||||
cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE=
|
cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE=
|
||||||
cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc=
|
cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc=
|
||||||
cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg=
|
cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg=
|
||||||
cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc=
|
cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc=
|
||||||
cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ=
|
cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ=
|
||||||
cloud.google.com/go/compute v0.1.0/go.mod h1:GAesmwr110a34z04OlxYkATPBEfVhkymfTBXtfbBFow=
|
cloud.google.com/go/compute/metadata v0.2.0/go.mod h1:zFmK7XCadkQkj6TtorcaGlCW1hT1fIilQDwofLpJ20k=
|
||||||
cloud.google.com/go/compute v1.3.0/go.mod h1:cCZiE1NHEtai4wiufUhW8I8S1JKkAnhnQJWM7YD99wM=
|
|
||||||
cloud.google.com/go/compute v1.5.0/go.mod h1:9SMHyhJlzhlkJqrPAc839t2BZFTSk6Jdj6mkzQJeu0M=
|
|
||||||
cloud.google.com/go/compute v1.6.0/go.mod h1:T29tfhtVbq1wvAPo0E3+7vhgmkOYeXjhFvz/FMzPu0s=
|
|
||||||
cloud.google.com/go/compute v1.6.1/go.mod h1:g85FgpzFvNULZ+S8AYq87axRKuf2Kh7deLqV/jJ3thU=
|
|
||||||
cloud.google.com/go/compute v1.7.0/go.mod h1:435lt8av5oL9P3fv1OEzSbSUe+ybHXGMPQHHZWZxy9U=
|
|
||||||
cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=
|
cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=
|
||||||
cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk=
|
cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk=
|
||||||
cloud.google.com/go/iam v0.3.0/go.mod h1:XzJPvDayI+9zsASAFO68Hk07u3z+f+JrT2xXNdp4bnY=
|
|
||||||
cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I=
|
cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I=
|
||||||
cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw=
|
cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw=
|
||||||
cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA=
|
cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA=
|
||||||
|
@ -52,69 +31,58 @@ cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0Zeo
|
||||||
cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk=
|
cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk=
|
||||||
cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs=
|
cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs=
|
||||||
cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0=
|
cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0=
|
||||||
cloud.google.com/go/storage v1.22.1/go.mod h1:S8N1cAStu7BOeFfE8KAQzmyyLkK8p/vmRq6kuBTW58Y=
|
|
||||||
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
|
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
|
||||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||||
github.com/BurntSushi/toml v1.2.0 h1:Rt8g24XnyGTyglgET/PRUNlrUeu9F5L+7FilkXfZgs0=
|
github.com/BurntSushi/toml v1.2.1 h1:9F2/+DoOYIOksmaJFPw1tGFy1eDnIJXg+UHjuD8lTak=
|
||||||
github.com/BurntSushi/toml v1.2.0/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
|
github.com/BurntSushi/toml v1.2.1/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
|
||||||
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
|
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
|
||||||
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
|
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
|
||||||
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
|
|
||||||
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
|
|
||||||
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
|
|
||||||
github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
|
|
||||||
github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho=
|
|
||||||
github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY=
|
github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY=
|
||||||
github.com/apparentlymart/go-cidr v1.1.0 h1:2mAhrMoF+nhXqxTzSZMUzDHkLjmIHC+Zzn4tdgBZjnU=
|
github.com/apparentlymart/go-cidr v1.1.0 h1:2mAhrMoF+nhXqxTzSZMUzDHkLjmIHC+Zzn4tdgBZjnU=
|
||||||
github.com/apparentlymart/go-cidr v1.1.0/go.mod h1:EBcsNrHc3zQeuaeCeCtQruQm+n9/YjEn/vI25Lg7Gwc=
|
github.com/apparentlymart/go-cidr v1.1.0/go.mod h1:EBcsNrHc3zQeuaeCeCtQruQm+n9/YjEn/vI25Lg7Gwc=
|
||||||
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
|
|
||||||
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
|
|
||||||
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
|
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
|
||||||
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
|
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
|
||||||
github.com/bwesterb/go-ristretto v1.2.2/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7NFEuV9ekS419A0=
|
|
||||||
github.com/cenkalti/backoff/v4 v4.1.2/go.mod h1:scbssz8iZGpm3xbr14ovlUdkxfGXNInqkPWOWmG2CLw=
|
|
||||||
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
||||||
github.com/certifi/gocertifi v0.0.0-20210507211836-431795d63e8d h1:S2NE3iHSwP0XV47EEXL8mWmRdEfGscSJ+7EgePNgt0s=
|
github.com/certifi/gocertifi v0.0.0-20210507211836-431795d63e8d h1:S2NE3iHSwP0XV47EEXL8mWmRdEfGscSJ+7EgePNgt0s=
|
||||||
github.com/certifi/gocertifi v0.0.0-20210507211836-431795d63e8d/go.mod h1:sGbDF6GwGcLpkNXPUTkMRoywsNa/ol15pxFe6ERfguA=
|
github.com/certifi/gocertifi v0.0.0-20210507211836-431795d63e8d/go.mod h1:sGbDF6GwGcLpkNXPUTkMRoywsNa/ol15pxFe6ERfguA=
|
||||||
|
github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko=
|
||||||
github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
|
github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
|
||||||
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||||
github.com/cespare/xxhash/v2 v2.1.2 h1:YRXhKfTDauu4ajMg1TPgFO5jnlC2HCbmLXMcTG5cbYE=
|
github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44=
|
||||||
github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||||
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
|
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
|
||||||
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
|
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
|
||||||
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
|
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
|
||||||
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
||||||
github.com/cloudflare/brotli-go v0.0.0-20191101163834-d34379f7ff93 h1:QrGfkZDnMxcWHaYDdB7CmqS9i26OAnUj/xcus/abYkY=
|
github.com/cloudflare/brotli-go v0.0.0-20191101163834-d34379f7ff93 h1:QrGfkZDnMxcWHaYDdB7CmqS9i26OAnUj/xcus/abYkY=
|
||||||
github.com/cloudflare/brotli-go v0.0.0-20191101163834-d34379f7ff93/go.mod h1:QiTe66jFdP7cUKMCCf/WrvDyYdtdmdZfVcdoLbzaKVY=
|
github.com/cloudflare/brotli-go v0.0.0-20191101163834-d34379f7ff93/go.mod h1:QiTe66jFdP7cUKMCCf/WrvDyYdtdmdZfVcdoLbzaKVY=
|
||||||
github.com/cloudflare/circl v1.2.1-0.20220809205628-0a9554f37a47 h1:YzpECHxZ9TzO7LpnKmPxItSd79lLgrR5heIlnqU4dTU=
|
github.com/cloudflare/circl v1.3.3 h1:fE/Qz0QdIGqeWfnwq0RE0R7MI51s0M2E4Ga9kq5AEMs=
|
||||||
github.com/cloudflare/circl v1.2.1-0.20220809205628-0a9554f37a47/go.mod h1:qhx8gBILsYlbam7h09SvHDSkjpe3TfLA7b/z4rxJvkE=
|
github.com/cloudflare/circl v1.3.3/go.mod h1:5XYMA4rFBvNIrhs50XuiBJ15vF2pZn4nnUKZrLbUZFA=
|
||||||
github.com/cloudflare/golibs v0.0.0-20170913112048-333127dbecfc h1:Dvk3ySBsOm5EviLx6VCyILnafPcQinXGP5jbTdHUJgE=
|
github.com/cloudflare/golibs v0.0.0-20210909181612-21743d7dd02a h1:v+0H/eaA84deS6fymIq7Z0ASwfL21Z5FRdZsW+DhMGc=
|
||||||
github.com/cloudflare/golibs v0.0.0-20170913112048-333127dbecfc/go.mod h1:HlgKKR8V5a1wroIDDIz3/A+T+9Janfq+7n1P5sEFdi0=
|
github.com/cloudflare/golibs v0.0.0-20210909181612-21743d7dd02a/go.mod h1:HlgKKR8V5a1wroIDDIz3/A+T+9Janfq+7n1P5sEFdi0=
|
||||||
github.com/cloudflare/qtls-pq v0.0.0-20230320122459-4ed280d0d633 h1:ZTub2XMOBpxyBiJf6Q+UKqAi07yt1rZmFitriHvFd8M=
|
github.com/cloudflare/qtls-pq v0.0.0-20230320122459-4ed280d0d633 h1:ZTub2XMOBpxyBiJf6Q+UKqAi07yt1rZmFitriHvFd8M=
|
||||||
github.com/cloudflare/qtls-pq v0.0.0-20230320122459-4ed280d0d633/go.mod h1:j/igSUc4PgBMayIsBGjAFu2i7g663rm6kZrKy4htb7E=
|
github.com/cloudflare/qtls-pq v0.0.0-20230320122459-4ed280d0d633/go.mod h1:j/igSUc4PgBMayIsBGjAFu2i7g663rm6kZrKy4htb7E=
|
||||||
github.com/cloudflare/qtls-pq v0.0.0-20230320123031-3faac1a945b2 h1:0/KuLjh9lBMiXlooAdwoo+FbLVD5DABtquB0ImEFOK0=
|
github.com/cloudflare/qtls-pq v0.0.0-20230320123031-3faac1a945b2 h1:0/KuLjh9lBMiXlooAdwoo+FbLVD5DABtquB0ImEFOK0=
|
||||||
github.com/cloudflare/qtls-pq v0.0.0-20230320123031-3faac1a945b2/go.mod h1:XzuZIjv4mF5cM205RHHW1d60PQtWGwMR6jx38YKuYHs=
|
github.com/cloudflare/qtls-pq v0.0.0-20230320123031-3faac1a945b2/go.mod h1:XzuZIjv4mF5cM205RHHW1d60PQtWGwMR6jx38YKuYHs=
|
||||||
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
|
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
|
||||||
github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
|
|
||||||
github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
|
github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
|
||||||
github.com/cncf/udpa/go v0.0.0-20210930031921-04548b0d99d4/go.mod h1:6pvJx4me5XPnfI9Z40ddWsdw2W/uZgQLFXToKeRcDiI=
|
github.com/cncf/udpa/go v0.0.0-20210930031921-04548b0d99d4/go.mod h1:6pvJx4me5XPnfI9Z40ddWsdw2W/uZgQLFXToKeRcDiI=
|
||||||
github.com/cncf/xds/go v0.0.0-20210312221358-fbca930ec8ed/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
|
github.com/cncf/xds/go v0.0.0-20210312221358-fbca930ec8ed/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
|
||||||
github.com/cncf/xds/go v0.0.0-20210805033703-aa0b78936158/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
|
github.com/cncf/xds/go v0.0.0-20210805033703-aa0b78936158/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
|
||||||
github.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
|
github.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
|
||||||
github.com/cncf/xds/go v0.0.0-20211001041855-01bcc9b48dfe/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
|
|
||||||
github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
|
github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
|
||||||
github.com/coredns/caddy v1.1.1 h1:2eYKZT7i6yxIfGP3qLJoJ7HAsDJqYB+X68g4NYjSrE0=
|
github.com/coredns/caddy v1.1.1 h1:2eYKZT7i6yxIfGP3qLJoJ7HAsDJqYB+X68g4NYjSrE0=
|
||||||
github.com/coredns/caddy v1.1.1/go.mod h1:A6ntJQlAWuQfFlsd9hvigKbo2WS0VUs2l1e2F+BawD4=
|
github.com/coredns/caddy v1.1.1/go.mod h1:A6ntJQlAWuQfFlsd9hvigKbo2WS0VUs2l1e2F+BawD4=
|
||||||
github.com/coredns/coredns v1.10.0 h1:jCfuWsBjTs0dapkkhISfPCzn5LqvSRtrFtaf/Tjj4DI=
|
github.com/coredns/coredns v1.10.1 h1:6OyL7tcvYxeNHONj5iQlVM2GXBzAOq57L3/LUKP1DbA=
|
||||||
github.com/coredns/coredns v1.10.0/go.mod h1:CIfRU5TgpuoIiJBJ4XrofQzfFQpPFh32ERpUevrSlaw=
|
github.com/coredns/coredns v1.10.1/go.mod h1:oGgoY6cRrdJzKgNrsT30Hztu7/MutSHCYwqGDWngXCc=
|
||||||
github.com/coreos/go-oidc/v3 v3.4.0 h1:xz7elHb/LDwm/ERpwHd+5nb7wFHL32rsr6bBOgaeu6g=
|
github.com/coreos/go-oidc/v3 v3.5.0 h1:VxKtbccHZxs8juq7RdJntSqtXFtde9YpNpGn0yqgEHw=
|
||||||
github.com/coreos/go-oidc/v3 v3.4.0/go.mod h1:eHUXhZtXPQLgEaDrOVTgwbgmz1xGOkJNye6h3zkD2Pw=
|
github.com/coreos/go-oidc/v3 v3.5.0/go.mod h1:ecXRtV4romGPeO6ieExAsUK9cb/3fp9hXNz1tlv8PIM=
|
||||||
github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
|
|
||||||
github.com/coreos/go-systemd v0.0.0-20191104093116-d3cd4ed1dbcf h1:iW4rZ826su+pqaw19uhpSCzhj44qo35pNgKFGqzDKkU=
|
github.com/coreos/go-systemd v0.0.0-20191104093116-d3cd4ed1dbcf h1:iW4rZ826su+pqaw19uhpSCzhj44qo35pNgKFGqzDKkU=
|
||||||
github.com/coreos/go-systemd v0.0.0-20191104093116-d3cd4ed1dbcf/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
|
github.com/coreos/go-systemd v0.0.0-20191104093116-d3cd4ed1dbcf/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
|
||||||
|
github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
|
||||||
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
|
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
|
||||||
github.com/cpuguy83/go-md2man/v2 v2.0.0 h1:EoUDS0afbrsXAZ9YQ9jdu/mZ2sXgT1/2yyNng4PGlyM=
|
github.com/cpuguy83/go-md2man/v2 v2.0.2 h1:p1EgwI/C7NhT0JmVkwCD2ZBK8j4aeHQX2pMHHBfMQ6w=
|
||||||
github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
|
github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
|
||||||
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
||||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
|
@ -124,12 +92,9 @@ github.com/devincarr/quic-go v0.0.0-20230502200822-d1f4edacbee7/go.mod h1:+4CVgV
|
||||||
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||||
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||||
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
|
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
|
||||||
github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po=
|
|
||||||
github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
|
github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
|
||||||
github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
|
|
||||||
github.com/envoyproxy/go-control-plane v0.9.9-0.20210512163311-63b5d3c536b0/go.mod h1:hliV/p42l8fGbc6Y9bQ70uLwIvmJyVE5k4iMKlh8wCQ=
|
github.com/envoyproxy/go-control-plane v0.9.9-0.20210512163311-63b5d3c536b0/go.mod h1:hliV/p42l8fGbc6Y9bQ70uLwIvmJyVE5k4iMKlh8wCQ=
|
||||||
github.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021/go.mod h1:AFq3mo9L8Lqqiid3OhADV3RfLJnjiw63cSpi+fDTRC0=
|
github.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021/go.mod h1:AFq3mo9L8Lqqiid3OhADV3RfLJnjiw63cSpi+fDTRC0=
|
||||||
github.com/envoyproxy/go-control-plane v0.10.2-0.20220325020618-49ff273808a1/go.mod h1:KJwIaB5Mv44NWtYuAOFCVOjcI94vtpEz2JU/D2v6IjE=
|
|
||||||
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
|
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
|
||||||
github.com/facebookgo/ensure v0.0.0-20160127193407-b4ab57deab51 h1:0JZ+dUmQeA8IIVUMzysrX4/AKuQwWhV2dYQuPZdvdSQ=
|
github.com/facebookgo/ensure v0.0.0-20160127193407-b4ab57deab51 h1:0JZ+dUmQeA8IIVUMzysrX4/AKuQwWhV2dYQuPZdvdSQ=
|
||||||
github.com/facebookgo/ensure v0.0.0-20160127193407-b4ab57deab51/go.mod h1:Yg+htXGokKKdzcwhuNDwVvN+uBxDGXJ7G/VN1d8fa64=
|
github.com/facebookgo/ensure v0.0.0-20160127193407-b4ab57deab51/go.mod h1:Yg+htXGokKKdzcwhuNDwVvN+uBxDGXJ7G/VN1d8fa64=
|
||||||
|
@ -143,12 +108,12 @@ github.com/facebookgo/subset v0.0.0-20150612182917-8dac2c3c4870 h1:E2s37DuLxFhQD
|
||||||
github.com/facebookgo/subset v0.0.0-20150612182917-8dac2c3c4870/go.mod h1:5tD+neXqOorC30/tWg0LCSkrqj/AR6gu8yY8/fpw1q0=
|
github.com/facebookgo/subset v0.0.0-20150612182917-8dac2c3c4870/go.mod h1:5tD+neXqOorC30/tWg0LCSkrqj/AR6gu8yY8/fpw1q0=
|
||||||
github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568 h1:BHsljHzVlRcyQhjrss6TZTdY2VfCqZPbv5k3iBFa2ZQ=
|
github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568 h1:BHsljHzVlRcyQhjrss6TZTdY2VfCqZPbv5k3iBFa2ZQ=
|
||||||
github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc=
|
github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc=
|
||||||
github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4=
|
github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY=
|
||||||
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
|
github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw=
|
||||||
github.com/getsentry/raven-go v0.2.0 h1:no+xWJRb5ZI7eE8TWgIq1jLulQiIoLG0IfYxv5JYMGs=
|
github.com/getsentry/raven-go v0.2.0 h1:no+xWJRb5ZI7eE8TWgIq1jLulQiIoLG0IfYxv5JYMGs=
|
||||||
github.com/getsentry/raven-go v0.2.0/go.mod h1:KungGk8q33+aIAZUIVWZDr2OfAEBsO49PX4NzFV5kcQ=
|
github.com/getsentry/raven-go v0.2.0/go.mod h1:KungGk8q33+aIAZUIVWZDr2OfAEBsO49PX4NzFV5kcQ=
|
||||||
github.com/getsentry/sentry-go v0.16.0 h1:owk+S+5XcgJLlGR/3+3s6N4d+uKwqYvh/eS0AIMjPWo=
|
github.com/getsentry/sentry-go v0.21.0 h1:c9l5F1nPF30JIppulk4veau90PK6Smu3abgVtVQWon4=
|
||||||
github.com/getsentry/sentry-go v0.16.0/go.mod h1:ZXCloQLj0pG7mja5NK6NPf2V4A88YJ4pNlc2mOHwh6Y=
|
github.com/getsentry/sentry-go v0.21.0/go.mod h1:lc76E2QywIyW8WuBnwl8Lc4bkmQH4+w1gwTf25trprY=
|
||||||
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
|
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
|
||||||
github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
|
github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
|
||||||
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
|
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
|
||||||
|
@ -162,17 +127,9 @@ github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2
|
||||||
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
|
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
|
||||||
github.com/go-jose/go-jose/v3 v3.0.0 h1:s6rrhirfEP/CGIoc6p+PZAeogN2SxKav6Wp7+dyMWVo=
|
github.com/go-jose/go-jose/v3 v3.0.0 h1:s6rrhirfEP/CGIoc6p+PZAeogN2SxKav6Wp7+dyMWVo=
|
||||||
github.com/go-jose/go-jose/v3 v3.0.0/go.mod h1:RNkWWRld676jZEYoV3+XK8L2ZnNSvIsxFMht0mSX+u8=
|
github.com/go-jose/go-jose/v3 v3.0.0/go.mod h1:RNkWWRld676jZEYoV3+XK8L2ZnNSvIsxFMht0mSX+u8=
|
||||||
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
|
|
||||||
github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
|
|
||||||
github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY=
|
|
||||||
github.com/go-kit/log v0.2.0/go.mod h1:NwTd00d/i8cPZ3xOwwiv2PO5MOcx78fFErGNcVmBjv0=
|
|
||||||
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
|
|
||||||
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
|
|
||||||
github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A=
|
|
||||||
github.com/go-logfmt/logfmt v0.5.1/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs=
|
|
||||||
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
|
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
|
||||||
github.com/go-logr/logr v1.2.3 h1:2DntVwHkVopvECVRSlL5PSo9eG+cAkDCuckLubN+rq0=
|
github.com/go-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ=
|
||||||
github.com/go-logr/logr v1.2.3/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
|
github.com/go-logr/logr v1.2.4/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
|
||||||
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
|
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
|
||||||
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
|
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
|
||||||
github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
|
github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
|
||||||
|
@ -182,25 +139,24 @@ github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+
|
||||||
github.com/go-playground/universal-translator v0.18.0 h1:82dyy6p4OuJq4/CByFNOn/jYrnRPArHwAcmLoJZxyho=
|
github.com/go-playground/universal-translator v0.18.0 h1:82dyy6p4OuJq4/CByFNOn/jYrnRPArHwAcmLoJZxyho=
|
||||||
github.com/go-playground/validator/v10 v10.2.0/go.mod h1:uOYAAleCW8F/7oMFd6aG0GOhaH6EGOAJShg8Id5JGkI=
|
github.com/go-playground/validator/v10 v10.2.0/go.mod h1:uOYAAleCW8F/7oMFd6aG0GOhaH6EGOAJShg8Id5JGkI=
|
||||||
github.com/go-playground/validator/v10 v10.11.1 h1:prmOlTVv+YjZjmRmNSF3VmspqJIxJWXmqUsHwfTRRkQ=
|
github.com/go-playground/validator/v10 v10.11.1 h1:prmOlTVv+YjZjmRmNSF3VmspqJIxJWXmqUsHwfTRRkQ=
|
||||||
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
|
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI=
|
||||||
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0 h1:p104kn46Q8WdvHunIJ9dAyjPVtrBPhSr3KT2yUst43I=
|
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls=
|
||||||
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE=
|
|
||||||
github.com/gobwas/httphead v0.0.0-20180130184737-2c6c146eadee/go.mod h1:L0fX3K22YWvt/FAX9NnzrNzcI4wNYi9Yku4O0LKYflo=
|
github.com/gobwas/httphead v0.0.0-20180130184737-2c6c146eadee/go.mod h1:L0fX3K22YWvt/FAX9NnzrNzcI4wNYi9Yku4O0LKYflo=
|
||||||
github.com/gobwas/httphead v0.0.0-20200921212729-da3d93bc3c58 h1:YyrUZvJaU8Q0QsoVo+xLFBgWDTam29PKea6GYmwvSiQ=
|
github.com/gobwas/httphead v0.1.0 h1:exrUm0f4YX0L7EBwZHuCF4GDp8aJfVeBrlLQrs6NqWU=
|
||||||
github.com/gobwas/httphead v0.0.0-20200921212729-da3d93bc3c58/go.mod h1:L0fX3K22YWvt/FAX9NnzrNzcI4wNYi9Yku4O0LKYflo=
|
github.com/gobwas/httphead v0.1.0/go.mod h1:O/RXo79gxV8G+RqlR/otEwx4Q36zl9rqC5u12GKvMCM=
|
||||||
github.com/gobwas/pool v0.2.0/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw=
|
github.com/gobwas/pool v0.2.0/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw=
|
||||||
github.com/gobwas/pool v0.2.1 h1:xfeeEhW7pwmX8nuLVlqbzVc7udMDrwetjEv+TZIz1og=
|
github.com/gobwas/pool v0.2.1 h1:xfeeEhW7pwmX8nuLVlqbzVc7udMDrwetjEv+TZIz1og=
|
||||||
github.com/gobwas/pool v0.2.1/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw=
|
github.com/gobwas/pool v0.2.1/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw=
|
||||||
github.com/gobwas/ws v1.0.2/go.mod h1:szmBTxLgaFppYjEmNtny/v3w89xOydFnnZMcgRRu/EM=
|
github.com/gobwas/ws v1.0.2/go.mod h1:szmBTxLgaFppYjEmNtny/v3w89xOydFnnZMcgRRu/EM=
|
||||||
github.com/gobwas/ws v1.0.4 h1:5eXU1CZhpQdq5kXbKb+sECH5Ia5KiO6CYzIzdlVx6Bs=
|
github.com/gobwas/ws v1.2.0 h1:u0p9s3xLYpZCA1z5JgCkMeB34CKCMMQbM+G8Ii7YD0I=
|
||||||
github.com/gobwas/ws v1.0.4/go.mod h1:szmBTxLgaFppYjEmNtny/v3w89xOydFnnZMcgRRu/EM=
|
github.com/gobwas/ws v1.2.0/go.mod h1:hRKAFb8wOxFROYNsT1bqfWnhX+b5MFeJM9r2ZSwg/KY=
|
||||||
github.com/goccy/go-json v0.9.11 h1:/pAaQDLHEoCq/5FFmSKBswWmK6H0e8g4159Kc/X/nqk=
|
github.com/goccy/go-json v0.9.11 h1:/pAaQDLHEoCq/5FFmSKBswWmK6H0e8g4159Kc/X/nqk=
|
||||||
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
|
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
|
||||||
github.com/golang-collections/collections v0.0.0-20130729185459-604e922904d3 h1:zN2lZNZRflqFyxVaTIU61KNKQ9C0055u9CAfpmqUvo4=
|
github.com/golang-collections/collections v0.0.0-20130729185459-604e922904d3 h1:zN2lZNZRflqFyxVaTIU61KNKQ9C0055u9CAfpmqUvo4=
|
||||||
github.com/golang-collections/collections v0.0.0-20130729185459-604e922904d3/go.mod h1:nPpo7qLxd6XL3hWJG/O60sR8ZKfMCiIoNap5GvD12KU=
|
github.com/golang-collections/collections v0.0.0-20130729185459-604e922904d3/go.mod h1:nPpo7qLxd6XL3hWJG/O60sR8ZKfMCiIoNap5GvD12KU=
|
||||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
||||||
github.com/golang/glog v1.0.0 h1:nfP3RFugxnNRyKgeWd4oI1nYvXpxrx8ck8ZrcizshdQ=
|
|
||||||
github.com/golang/glog v1.0.0/go.mod h1:EWib/APOK0SL3dFbYqvxE3UYd8E6s1ouQ7iEp/0LWV4=
|
github.com/golang/glog v1.0.0/go.mod h1:EWib/APOK0SL3dFbYqvxE3UYd8E6s1ouQ7iEp/0LWV4=
|
||||||
|
github.com/golang/glog v1.1.0 h1:/d3pCKDPWNnvIWe0vVUpNP32qc8U3PDVxySP/y360qE=
|
||||||
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||||
github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||||
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||||
|
@ -211,7 +167,6 @@ github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt
|
||||||
github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
|
github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
|
||||||
github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
|
github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
|
||||||
github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4=
|
github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4=
|
||||||
github.com/golang/mock v1.5.0/go.mod h1:CWnOUgYIOo4TcNZ0wHX3YZCqsaM1I1Jvs6v3mP3KVu8=
|
|
||||||
github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc=
|
github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc=
|
||||||
github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs=
|
github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs=
|
||||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||||
|
@ -229,10 +184,9 @@ github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QD
|
||||||
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
|
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
|
||||||
github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
|
github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
|
||||||
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
|
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
|
||||||
github.com/golang/protobuf v1.5.1/go.mod h1:DopwsBzvsk0Fs44TXzsVbJyPhcCPeIwnvohx4u74HPM=
|
|
||||||
github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw=
|
|
||||||
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
|
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
|
||||||
github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
|
||||||
|
github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
|
||||||
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
||||||
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
||||||
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
|
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
|
||||||
|
@ -242,12 +196,8 @@ github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
|
||||||
github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||||
github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||||
github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||||
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
|
||||||
github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
|
||||||
github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
|
||||||
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||||
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||||
github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE=
|
|
||||||
github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||||
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
|
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
|
||||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||||
|
@ -255,8 +205,6 @@ github.com/google/gopacket v1.1.19 h1:ves8RnFZPGiFnTS0uPQStjwru6uO6h+nlr9j6fL7kF
|
||||||
github.com/google/gopacket v1.1.19/go.mod h1:iJ8V8n6KS+z2U1A8pUwu8bW5SyEMkXJB8Yo/Vo+TKTo=
|
github.com/google/gopacket v1.1.19/go.mod h1:iJ8V8n6KS+z2U1A8pUwu8bW5SyEMkXJB8Yo/Vo+TKTo=
|
||||||
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
|
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
|
||||||
github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
|
github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
|
||||||
github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
|
|
||||||
github.com/google/martian/v3 v3.2.1/go.mod h1:oBOf6HBosgwRXnUGWUB05QECsc6uvmMiJ3+6W4l/CUk=
|
|
||||||
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
|
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
|
||||||
github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
|
github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
|
||||||
github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
|
github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
|
||||||
|
@ -264,63 +212,41 @@ github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hf
|
||||||
github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
|
github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
|
||||||
github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
|
github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
|
||||||
github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
|
github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
|
||||||
github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
|
github.com/google/pprof v0.0.0-20230510103437-eeec1cb781c3 h1:2XF1Vzq06X+inNqgJ9tRnGuw+ZVCB3FazXODD6JE1R8=
|
||||||
github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
|
github.com/google/pprof v0.0.0-20230510103437-eeec1cb781c3/go.mod h1:79YE0hCXdHag9sBkw2o+N/YnZtTkXi0UT9Nnixa5eYk=
|
||||||
github.com/google/pprof v0.0.0-20210122040257-d980be63207e/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
|
|
||||||
github.com/google/pprof v0.0.0-20210226084205-cbba55b83ad5/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
|
|
||||||
github.com/google/pprof v0.0.0-20210601050228-01bbb1931b22/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
|
|
||||||
github.com/google/pprof v0.0.0-20210609004039-a478d1d731e9/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
|
|
||||||
github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1 h1:K6RDEckDVWvDI9JAJYCmNdQXq6neHJOYx3V6jnqNEec=
|
|
||||||
github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
|
|
||||||
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
|
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
|
||||||
github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
|
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
|
||||||
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
github.com/googleapis/enterprise-certificate-proxy v0.0.0-20220520183353-fd19c99a87aa/go.mod h1:17drOmN3MwGY7t0e+Ei9b45FFGA3fBs3x36SsCg1hq8=
|
|
||||||
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
|
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
|
||||||
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
|
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
|
||||||
github.com/googleapis/gax-go/v2 v2.1.0/go.mod h1:Q3nei7sK6ybPYH7twZdmQpAd1MKb7pfu6SK+H1/DsU0=
|
|
||||||
github.com/googleapis/gax-go/v2 v2.1.1/go.mod h1:hddJymUZASv3XPyGkUpKj8pPO47Rmb0eJc8R6ouapiM=
|
|
||||||
github.com/googleapis/gax-go/v2 v2.2.0/go.mod h1:as02EH8zWkzwUoLbBaFeQ+arQaj/OthfcblKl4IGNaM=
|
|
||||||
github.com/googleapis/gax-go/v2 v2.3.0/go.mod h1:b8LNqSzNabLiUpXKkY7HAR5jr6bIT99EXz9pXxye9YM=
|
|
||||||
github.com/googleapis/gax-go/v2 v2.4.0/go.mod h1:XOTVJ59hdnfJLIP/dh8n5CGryZR2LxK9wbMD5+iXC6c=
|
|
||||||
github.com/googleapis/go-type-adapters v1.0.0/go.mod h1:zHW75FOG2aur7gAO2B+MLby+cLsWGBF62rFAi7WjWO4=
|
|
||||||
github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||||
github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc=
|
github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc=
|
||||||
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||||
|
github.com/grpc-ecosystem/grpc-gateway v1.16.0 h1:gmcG1KaJ57LophUzW0Hy8NmPhnMZb4M0+kPpLofRdBo=
|
||||||
github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw=
|
github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw=
|
||||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.7.0 h1:BZHcxBETFHIdVyhyEfOvn/RdU/QGdLI4y34qQGjGWO0=
|
|
||||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.7.0/go.mod h1:hgWBS7lorOAVIJEQMi4ZsPv9hVvWI6+ch50m39Pf2Ks=
|
github.com/grpc-ecosystem/grpc-gateway/v2 v2.7.0/go.mod h1:hgWBS7lorOAVIJEQMi4ZsPv9hVvWI6+ch50m39Pf2Ks=
|
||||||
|
github.com/grpc-ecosystem/grpc-gateway/v2 v2.15.2 h1:gDLXvp5S9izjldquuoAhDzccbskOL6tDC5jMSyx3zxE=
|
||||||
|
github.com/grpc-ecosystem/grpc-gateway/v2 v2.15.2/go.mod h1:7pdNwVWBBHGiCxa9lAszqCJMbfTISJ7oMftp8+UGV08=
|
||||||
github.com/grpc-ecosystem/grpc-opentracing v0.0.0-20180507213350-8e809c8a8645 h1:MJG/KsmcqMwFAkh8mTnAwhyKoB+sTAnY4CACC110tbU=
|
github.com/grpc-ecosystem/grpc-opentracing v0.0.0-20180507213350-8e809c8a8645 h1:MJG/KsmcqMwFAkh8mTnAwhyKoB+sTAnY4CACC110tbU=
|
||||||
github.com/grpc-ecosystem/grpc-opentracing v0.0.0-20180507213350-8e809c8a8645/go.mod h1:6iZfnjpejD4L/4DwD7NryNaJyCQdzwWwH2MWhCA90Kw=
|
github.com/grpc-ecosystem/grpc-opentracing v0.0.0-20180507213350-8e809c8a8645/go.mod h1:6iZfnjpejD4L/4DwD7NryNaJyCQdzwWwH2MWhCA90Kw=
|
||||||
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
||||||
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
||||||
github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
|
github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
|
||||||
github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
|
|
||||||
github.com/ipostelnik/cli/v2 v2.3.1-0.20210324024421-b6ea8234fe3d h1:PRDnysJ9dF1vUMmEzBu6aHQeUluSQy4eWH3RsSSy/vI=
|
github.com/ipostelnik/cli/v2 v2.3.1-0.20210324024421-b6ea8234fe3d h1:PRDnysJ9dF1vUMmEzBu6aHQeUluSQy4eWH3RsSSy/vI=
|
||||||
github.com/ipostelnik/cli/v2 v2.3.1-0.20210324024421-b6ea8234fe3d/go.mod h1:LJmUH05zAU44vOAcrfzZQKsZbVcdbOG8rtL3/XcUArI=
|
github.com/ipostelnik/cli/v2 v2.3.1-0.20210324024421-b6ea8234fe3d/go.mod h1:LJmUH05zAU44vOAcrfzZQKsZbVcdbOG8rtL3/XcUArI=
|
||||||
github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4=
|
|
||||||
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
|
|
||||||
github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
|
github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
|
||||||
github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
|
|
||||||
github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
|
|
||||||
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
|
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
|
||||||
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
|
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
|
||||||
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
|
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
|
||||||
github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=
|
github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=
|
||||||
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
|
|
||||||
github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM=
|
|
||||||
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
||||||
github.com/klauspost/compress v1.10.3/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs=
|
github.com/klauspost/compress v1.10.3/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs=
|
||||||
github.com/klauspost/compress v1.15.11 h1:Lcadnb3RKGin4FYM/orgq0qde+nc15E5Cbqg4B9Sx9c=
|
github.com/klauspost/compress v1.16.5 h1:IFV2oUNUzZaz+XyusxpLzpzS8Pt5rh0Z16For/djlyI=
|
||||||
github.com/klauspost/compress v1.15.11/go.mod h1:QPwzmACJjUTFsnSHH934V6woptycfrDDJnH7hvFVbGM=
|
github.com/klauspost/compress v1.16.5/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE=
|
||||||
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
|
||||||
github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
|
||||||
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
|
|
||||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||||
github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI=
|
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||||
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
|
|
||||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||||
|
@ -329,86 +255,64 @@ github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0
|
||||||
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
|
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
|
||||||
github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII=
|
github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII=
|
||||||
github.com/leodido/go-urn v1.2.1 h1:BqpAaACuzVSgi/VLzGZIobT2z4v53pjosyNd9Yv6n/w=
|
github.com/leodido/go-urn v1.2.1 h1:BqpAaACuzVSgi/VLzGZIobT2z4v53pjosyNd9Yv6n/w=
|
||||||
|
github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4=
|
||||||
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
|
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
|
||||||
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
|
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
|
||||||
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
|
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
|
||||||
github.com/mattn/go-isatty v0.0.16 h1:bq3VjFmv/sOjHtdEhmkEV4x1AJtvUvOJ2PFAZ5+peKQ=
|
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
|
||||||
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
||||||
github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU=
|
github.com/mattn/go-isatty v0.0.18 h1:DOKFKCQ7FNG2L1rbrmstDN4QVRdS89Nkh85u68Uwp98=
|
||||||
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
|
github.com/mattn/go-isatty v0.0.18/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||||
github.com/miekg/dns v1.1.50 h1:DQUfb9uc6smULcREF09Uc+/Gd46YWqJd5DbpPE9xkcA=
|
github.com/matttproud/golang_protobuf_extensions v1.0.4 h1:mmDVorXM7PCGKw94cs5zkfA9PSy5pEvNWRP0ET0TIVo=
|
||||||
github.com/miekg/dns v1.1.50/go.mod h1:e3IlAVfNqAllflbibAZEWOXOQ+Ynzk/dDozDxY7XnME=
|
github.com/matttproud/golang_protobuf_extensions v1.0.4/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4=
|
||||||
|
github.com/miekg/dns v1.1.54 h1:5jon9mWcb0sFJGpnI99tOMhCPyJ+RPVz5b63MQG0VWI=
|
||||||
|
github.com/miekg/dns v1.1.54/go.mod h1:uInx36IzPl7FYnDcMeVWxj9byh7DutNykX4G9Sj60FY=
|
||||||
github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y=
|
github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y=
|
||||||
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
|
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
|
||||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
|
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
|
||||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||||
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
||||||
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
|
||||||
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
|
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
|
||||||
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
|
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
|
||||||
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
|
github.com/onsi/ginkgo/v2 v2.9.4 h1:xR7vG4IXt5RWx6FfIjyAtsoMAtnc3C/rFXBBd2AjZwE=
|
||||||
github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
|
github.com/onsi/ginkgo/v2 v2.9.4/go.mod h1:gCQYp2Q+kSoIj7ykSVb9nskRSsR6PUj4AiLywzIhbKM=
|
||||||
github.com/onsi/ginkgo/v2 v2.4.0 h1:+Ig9nvqgS5OBSACXNk15PLdp0U9XPYROt9CFzVdFGIs=
|
github.com/onsi/gomega v1.27.6 h1:ENqfyGeS5AX/rlXDd/ETokDz93u0YufY1Pgxuy/PvWE=
|
||||||
github.com/onsi/ginkgo/v2 v2.4.0/go.mod h1:iHkDK1fKGcBoEHT5W7YBq4RFWaQulw+caOMkAt4OrFo=
|
|
||||||
github.com/onsi/gomega v1.23.0 h1:/oxKu9c2HVap+F3PfKort2Hw5DEU+HGlW8n+tguWsys=
|
|
||||||
github.com/onsi/gomega v1.23.0/go.mod h1:Z/NWtiqwBrwUt4/2loMmHL63EDLnYHmVbuBpDr2vQAg=
|
|
||||||
github.com/opentracing/opentracing-go v1.2.0 h1:uEJPy/1a5RIPAJ0Ov+OIO8OxWu77jEv+1B0VhjKrZUs=
|
github.com/opentracing/opentracing-go v1.2.0 h1:uEJPy/1a5RIPAJ0Ov+OIO8OxWu77jEv+1B0VhjKrZUs=
|
||||||
github.com/opentracing/opentracing-go v1.2.0/go.mod h1:GxEUsuufX4nBwe+T+Wl9TAgYrxe9dPLANfrWvHYVTgc=
|
github.com/opentracing/opentracing-go v1.2.0/go.mod h1:GxEUsuufX4nBwe+T+Wl9TAgYrxe9dPLANfrWvHYVTgc=
|
||||||
github.com/pelletier/go-toml/v2 v2.0.5 h1:ipoSadvV8oGUjnUbMub59IDPPwfxF694nG/jwbMiyQg=
|
github.com/pelletier/go-toml/v2 v2.0.5 h1:ipoSadvV8oGUjnUbMub59IDPPwfxF694nG/jwbMiyQg=
|
||||||
github.com/philhofer/fwd v1.1.1 h1:GdGcTjf5RNAxwS4QLsiMzJYj5KEvPJD3Abr261yRQXQ=
|
github.com/philhofer/fwd v1.1.1 h1:GdGcTjf5RNAxwS4QLsiMzJYj5KEvPJD3Abr261yRQXQ=
|
||||||
github.com/pingcap/errors v0.11.4 h1:lFuQV/oaUMGcD2tqt+01ROSmJs75VG1ToEOkZIZ4nE4=
|
github.com/pingcap/errors v0.11.4 h1:lFuQV/oaUMGcD2tqt+01ROSmJs75VG1ToEOkZIZ4nE4=
|
||||||
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
|
||||||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
|
||||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
github.com/pquerna/cachecontrol v0.0.0-20180517163645-1555304b9b35 h1:J9b7z+QKAmPf4YLrFg6oQUotqHQeUNWwkvo7jZp1GLU=
|
github.com/pquerna/cachecontrol v0.1.0 h1:yJMy84ti9h/+OEWa752kBTKv4XC30OtVVHYv/8cTqKc=
|
||||||
github.com/pquerna/cachecontrol v0.0.0-20180517163645-1555304b9b35/go.mod h1:prYjPmNq4d1NPVmpShWobRqXY3q7Vp+80DqgxxUrUIA=
|
github.com/pquerna/cachecontrol v0.1.0/go.mod h1:NrUG3Z7Rdu85UNR3vm7SOsl1nFIeSiQnrHV5K9mBcUI=
|
||||||
github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
|
github.com/prashantv/gostub v1.1.0 h1:BTyx3RfQjRHnUWaGF9oQos79AlQ5k8WNktv7VGvVH4g=
|
||||||
github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo=
|
github.com/prometheus/client_golang v1.15.1 h1:8tXpTmJbyH5lydzFPoxSIJ0J46jdh3tylbvM1xCv0LI=
|
||||||
github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M=
|
github.com/prometheus/client_golang v1.15.1/go.mod h1:e9yaBhRPU2pPNsZwE+JdQl0KEt1N9XgF6zxWmaC0xOk=
|
||||||
github.com/prometheus/client_golang v1.11.0/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0=
|
|
||||||
github.com/prometheus/client_golang v1.12.1/go.mod h1:3Z9XVyYiZYEO+YQWt3RD2R3jrbd179Rt297l4aS6nDY=
|
|
||||||
github.com/prometheus/client_golang v1.13.0 h1:b71QUfeo5M8gq2+evJdTPfZhYMAU0uKPkyPJ7TPsloU=
|
|
||||||
github.com/prometheus/client_golang v1.13.0/go.mod h1:vTeo+zgvILHsnnj/39Ou/1fPN5nJFOEMgftOUOmlvYQ=
|
|
||||||
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
|
|
||||||
github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
|
||||||
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||||
github.com/prometheus/client_model v0.2.0 h1:uq5h0d+GuxiXLJLNABMgp2qUWDPiLvgCzz2dUR+/W/M=
|
github.com/prometheus/client_model v0.4.0 h1:5lQXD3cAg1OXBf4Wq03gTrXHeaV0TQvGfUooCfx1yqY=
|
||||||
github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
github.com/prometheus/client_model v0.4.0/go.mod h1:oMQmHW1/JoDwqLtg57MGgP/Fb1CJEYF2imWWhWtMkYU=
|
||||||
github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
|
github.com/prometheus/common v0.43.0 h1:iq+BVjvYLei5f27wiuNiB1DN6DYQkp1c8Bx0Vykh5us=
|
||||||
github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo=
|
github.com/prometheus/common v0.43.0/go.mod h1:NCvr5cQIh3Y/gy73/RdVtC9r8xxrxwJnB+2lB3BxrFc=
|
||||||
github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc=
|
github.com/prometheus/procfs v0.9.0 h1:wzCHvIvM5SxWqYvwgVL7yJY8Lz3PKn49KQtpgMYJfhI=
|
||||||
github.com/prometheus/common v0.32.1/go.mod h1:vu+V0TpY+O6vW9J44gczi3Ap/oXXR10b+M/gUGO4Hls=
|
github.com/prometheus/procfs v0.9.0/go.mod h1:+pB4zwohETzFnmlpe6yd2lSc+0/46IYZRB/chUwxUZY=
|
||||||
github.com/prometheus/common v0.37.0 h1:ccBbHCgIiT9uSoFY0vX8H3zsNR5eLt17/RQLUvn8pXE=
|
|
||||||
github.com/prometheus/common v0.37.0/go.mod h1:phzohg0JFMnBEFGxTDbfu3QyL5GI8gTQJFhYO5B3mfA=
|
|
||||||
github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
|
|
||||||
github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
|
|
||||||
github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU=
|
|
||||||
github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA=
|
|
||||||
github.com/prometheus/procfs v0.7.3/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA=
|
|
||||||
github.com/prometheus/procfs v0.8.0 h1:ODq8ZFEaYeCaZOJlZZdJA2AbQR98dSHSM1KW/You5mo=
|
|
||||||
github.com/prometheus/procfs v0.8.0/go.mod h1:z7EfXMXOkbkqb9IINtpCn86r/to3BnA0uaxHdg830/4=
|
|
||||||
github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=
|
github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=
|
||||||
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
||||||
github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ=
|
github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ=
|
||||||
github.com/rs/zerolog v1.20.0 h1:38k9hgtUBdxFwE34yS8rTHmHBa4eN16E4DJlv177LNs=
|
github.com/rs/xid v1.4.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg=
|
||||||
github.com/rs/zerolog v1.20.0/go.mod h1:IzD0RJ65iWH0w97OQQebJEvTZYvsCUm9WVLWBQrJRjo=
|
github.com/rs/zerolog v1.29.1 h1:cO+d60CHkknCbvzEWxP0S9K6KqyTjrCNUy1LdQLCGPc=
|
||||||
|
github.com/rs/zerolog v1.29.1/go.mod h1:Le6ESbR7hc+DP6Lt1THiV8CQSdkkNrd3R0XbEgp3ZBU=
|
||||||
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||||
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
|
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
|
||||||
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||||
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
|
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
|
||||||
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
|
|
||||||
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
|
|
||||||
github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88=
|
|
||||||
github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
|
github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
|
||||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
|
||||||
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||||
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
||||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
|
||||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||||
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
|
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
|
||||||
|
@ -416,9 +320,9 @@ github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/
|
||||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||||
github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk=
|
github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8=
|
||||||
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||||
github.com/tinylib/msgp v1.1.2 h1:gWmO7n0Ys2RBEb7GPYB9Ujq8Mk5p2U08lRnmMcGy6BQ=
|
github.com/tinylib/msgp v1.1.6 h1:i+SbKraHhnrf9M5MYmvQhFnbLhAXSDWF8WWsuyRdocw=
|
||||||
github.com/ugorji/go v1.1.7 h1:/68gy2h+1mWMrwZFeD1kQialdSzAb432dtpeJ42ovdo=
|
github.com/ugorji/go v1.1.7 h1:/68gy2h+1mWMrwZFeD1kQialdSzAb432dtpeJ42ovdo=
|
||||||
github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw=
|
github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw=
|
||||||
github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY=
|
github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY=
|
||||||
|
@ -426,43 +330,39 @@ github.com/ugorji/go/codec v1.2.7 h1:YPXUKf7fYbp/y8xloBqZOw2qaVggbfwMlI8WM3wZUJ0
|
||||||
github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||||
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||||
github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||||
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
|
||||||
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
|
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
|
||||||
|
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||||
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
|
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
|
||||||
go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
|
go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
|
||||||
go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
||||||
go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
||||||
go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
||||||
go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk=
|
|
||||||
go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E=
|
|
||||||
go.opentelemetry.io/contrib/propagators v0.22.0 h1:KGdv58M2//veiYLIhb31mofaI2LgkIPXXAZVeYVyfd8=
|
go.opentelemetry.io/contrib/propagators v0.22.0 h1:KGdv58M2//veiYLIhb31mofaI2LgkIPXXAZVeYVyfd8=
|
||||||
go.opentelemetry.io/contrib/propagators v0.22.0/go.mod h1:xGOuXr6lLIF9BXipA4pm6UuOSI0M98U6tsI3khbOiwU=
|
go.opentelemetry.io/contrib/propagators v0.22.0/go.mod h1:xGOuXr6lLIF9BXipA4pm6UuOSI0M98U6tsI3khbOiwU=
|
||||||
go.opentelemetry.io/otel v1.0.0-RC2/go.mod h1:w1thVQ7qbAy8MHb0IFj8a5Q2QU0l2ksf8u/CN8m3NOM=
|
go.opentelemetry.io/otel v1.0.0-RC2/go.mod h1:w1thVQ7qbAy8MHb0IFj8a5Q2QU0l2ksf8u/CN8m3NOM=
|
||||||
go.opentelemetry.io/otel v1.6.3 h1:FLOfo8f9JzFVFVyU+MSRJc2HdEAXQgm7pIv2uFKRSZE=
|
go.opentelemetry.io/otel v1.15.1 h1:3Iwq3lfRByPaws0f6bU3naAqOR1n5IeDWd9390kWHa8=
|
||||||
go.opentelemetry.io/otel v1.6.3/go.mod h1:7BgNga5fNlF/iZjG06hM3yofffp0ofKCDwSXx1GC4dI=
|
go.opentelemetry.io/otel v1.15.1/go.mod h1:mHHGEHVDLal6YrKMmk9LqC4a3sF5g+fHfrttQIB1NTc=
|
||||||
go.opentelemetry.io/otel/exporters/otlp/internal/retry v1.6.3/go.mod h1:NEu79Xo32iVb+0gVNV8PMd7GoWqnyDXRlj04yFjqz40=
|
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.15.1 h1:tyoeaUh8REKay72DVYsSEBYV18+fGONe+YYPaOxgLoE=
|
||||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.6.3 h1:4/UjHWMVVc5VwX/KAtqJOHErKigMCH8NexChMuanb/o=
|
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.15.1/go.mod h1:HUSnrjQQ19KX9ECjpQxufsF+3ioD3zISPMlauTPZu2g=
|
||||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.6.3/go.mod h1:UJmXdiVVBaZ63umRUTwJuCMAV//GCMvDiQwn703/GoY=
|
go.opentelemetry.io/otel/sdk v1.15.1 h1:5FKR+skgpzvhPQHIEfcwMYjCBr14LWzs3uSqKiQzETI=
|
||||||
go.opentelemetry.io/otel/sdk v1.6.3 h1:prSHYdwCQOX5DrsEzxowH3nLhoAzEBdZhvrR79scfLs=
|
go.opentelemetry.io/otel/sdk v1.15.1/go.mod h1:8rVtxQfrbmbHKfqzpQkT5EzZMcbMBwTzNAggbEAM0KA=
|
||||||
go.opentelemetry.io/otel/sdk v1.6.3/go.mod h1:A4iWF7HTXa+GWL/AaqESz28VuSBIcZ+0CV+IzJ5NMiQ=
|
|
||||||
go.opentelemetry.io/otel/trace v1.0.0-RC2/go.mod h1:JPQ+z6nNw9mqEGT8o3eoPTdnNI+Aj5JcxEsVGREIAy4=
|
go.opentelemetry.io/otel/trace v1.0.0-RC2/go.mod h1:JPQ+z6nNw9mqEGT8o3eoPTdnNI+Aj5JcxEsVGREIAy4=
|
||||||
go.opentelemetry.io/otel/trace v1.6.3 h1:IqN4L+5b0mPNjdXIiZ90Ni4Bl5BRkDQywePLWemd9bc=
|
go.opentelemetry.io/otel/trace v1.15.1 h1:uXLo6iHJEzDfrNC0L0mNjItIp06SyaBQxu5t3xMlngY=
|
||||||
go.opentelemetry.io/otel/trace v1.6.3/go.mod h1:GNJQusJlUgZl9/TQBPKU/Y/ty+0iVB5fjhKeJGZPGFs=
|
go.opentelemetry.io/otel/trace v1.15.1/go.mod h1:IWdQG/5N1x7f6YUlmdLeJvH9yxtuJAfc4VW5Agv9r/8=
|
||||||
go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI=
|
go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI=
|
||||||
go.opentelemetry.io/proto/otlp v0.15.0 h1:h0bKrvdrT/9sBwEJ6iWUqT/N/xPcS66bL4u3isneJ6w=
|
go.opentelemetry.io/proto/otlp v0.19.0 h1:IVN6GR+mhC4s5yfcTbmzHYODqvWAp3ZedA2SJPI1Nnw=
|
||||||
go.opentelemetry.io/proto/otlp v0.15.0/go.mod h1:H7XAot3MsfNsj7EXtrA2q5xSNQ10UqI405h3+duxN4U=
|
go.opentelemetry.io/proto/otlp v0.19.0/go.mod h1:H7XAot3MsfNsj7EXtrA2q5xSNQ10UqI405h3+duxN4U=
|
||||||
go.uber.org/automaxprocs v1.4.0 h1:CpDZl6aOlLhReez+8S3eEotD7Jx0Os++lemPlMULQP0=
|
go.uber.org/automaxprocs v1.5.2 h1:2LxUOGiR3O6tw8ui5sZa2LAaHnsviZdVOUZw4fvbnME=
|
||||||
go.uber.org/automaxprocs v1.4.0/go.mod h1:/mTEdr7LvHhs0v7mjdxDreTz1OG5zdZGqgOnhWiR/+Q=
|
go.uber.org/automaxprocs v1.5.2/go.mod h1:eRbA25aqJrxAbsLO0xy5jVwPt7FQnRgjW+efnwa1WM0=
|
||||||
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
|
||||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||||
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||||
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||||
golang.org/x/crypto v0.0.0-20190911031432-227b76d455e7/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
golang.org/x/crypto v0.0.0-20190911031432-227b76d455e7/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||||
golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||||
golang.org/x/crypto v0.8.0 h1:pd9TJtTueMTVQXzk8E2XESSMQDj/U7OUu0PqJqPXQjQ=
|
golang.org/x/crypto v0.9.0 h1:LF6fAI+IutBocDJ2OT0Q1g8plpYljMZ4+lty+dsqw3g=
|
||||||
golang.org/x/crypto v0.8.0/go.mod h1:mRqEX+O9/h5TFCrQhkgjo2yKi0yYA+9ecGkdQoHrywE=
|
golang.org/x/crypto v0.9.0/go.mod h1:yrmDGqONDYtNj3tH8X9dzUun2m2lzPa9ngI6/RUPGR0=
|
||||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||||
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||||
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
|
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
|
||||||
|
@ -473,8 +373,8 @@ golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u0
|
||||||
golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
|
golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
|
||||||
golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM=
|
golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM=
|
||||||
golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=
|
golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=
|
||||||
golang.org/x/exp v0.0.0-20221205204356-47842c84f3db h1:D/cFflL63o2KSLJIwjlcIt8PR064j/xsmdEJL/YvY/o=
|
golang.org/x/exp v0.0.0-20230510235704-dd950f8aeaea h1:vLCWI/yYrdEHyN2JzIzPO3aaQJHQdp89IZBA/+azVC4=
|
||||||
golang.org/x/exp v0.0.0-20221205204356-47842c84f3db/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc=
|
golang.org/x/exp v0.0.0-20230510235704-dd950f8aeaea/go.mod h1:V1LtkGg67GoY2N1AnLN78QLrzxkLyJw7RJb1gzOOz9w=
|
||||||
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
|
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
|
||||||
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
||||||
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||||
|
@ -487,8 +387,6 @@ golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHl
|
||||||
golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs=
|
golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs=
|
||||||
golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
|
golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
|
||||||
golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
|
golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
|
||||||
golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
|
|
||||||
golang.org/x/lint v0.0.0-20210508222113-6edffad5e616/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
|
|
||||||
golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
|
golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
|
||||||
golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
|
golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
|
||||||
golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
|
golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
|
||||||
|
@ -497,14 +395,12 @@ golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzB
|
||||||
golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
|
golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
|
||||||
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||||
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||||
golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
|
||||||
golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
|
||||||
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||||
golang.org/x/mod v0.8.0 h1:LUYupSeNrTNCGzR/hVBk2NHZO4hXcVaW1k4Qx7rjPx8=
|
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||||
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
golang.org/x/mod v0.10.0 h1:lFO9qtOdlre5W1jxS3r/4szv2/6iXxScdzjoBMXNhYk=
|
||||||
|
golang.org/x/mod v0.10.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
|
||||||
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||||
|
@ -512,7 +408,6 @@ golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn
|
||||||
golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||||
golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||||
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
|
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
|
||||||
golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
|
||||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
|
@ -530,51 +425,22 @@ golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/
|
||||||
golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
||||||
golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
||||||
golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
||||||
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
|
||||||
golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
|
||||||
golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
|
||||||
golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
|
||||||
golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
|
||||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||||
golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc=
|
|
||||||
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
|
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
|
||||||
golang.org/x/net v0.0.0-20210503060351-7fd8e65b6420/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||||
golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
golang.org/x/net v0.3.0/go.mod h1:MBQ8lrhLObU/6UmLb4fmbmk5OcyYmqtbGd/9yIeKjEE=
|
||||||
golang.org/x/net v0.0.0-20210726213435-c6fcb2dbf985/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
golang.org/x/net v0.4.0/go.mod h1:MBQ8lrhLObU/6UmLb4fmbmk5OcyYmqtbGd/9yIeKjEE=
|
||||||
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
golang.org/x/net v0.10.0 h1:X2//UzNDwYmtCLn7To6G58Wr6f5ahEAQgKNzv9Y951M=
|
||||||
golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
|
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
|
||||||
golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
|
|
||||||
golang.org/x/net v0.0.0-20220325170049-de3da57026de/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
|
|
||||||
golang.org/x/net v0.0.0-20220412020605-290c469a71a5/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
|
|
||||||
golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
|
|
||||||
golang.org/x/net v0.0.0-20220607020251-c690dde0001d/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
|
||||||
golang.org/x/net v0.0.0-20220624214902-1bab6f366d9e/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
|
||||||
golang.org/x/net v0.0.0-20220826154423-83b083e8dc8b/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk=
|
|
||||||
golang.org/x/net v0.9.0 h1:aWJ/m6xSmxWBx+V0XRHTlrYrPG56jKsLdTFmsSsCzOM=
|
|
||||||
golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns=
|
|
||||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||||
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||||
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||||
golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||||
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||||
golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
|
||||||
golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
|
||||||
golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
|
||||||
golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
|
||||||
golang.org/x/oauth2 v0.0.0-20210220000619-9bb904979d93/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
|
||||||
golang.org/x/oauth2 v0.0.0-20210313182246-cd4f82c27b84/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
|
||||||
golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
|
||||||
golang.org/x/oauth2 v0.0.0-20210628180205-a41e5a781914/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
|
||||||
golang.org/x/oauth2 v0.0.0-20210805134026-6f1e6394065a/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
|
||||||
golang.org/x/oauth2 v0.0.0-20210819190943-2bc19b11175f/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
|
||||||
golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
||||||
golang.org/x/oauth2 v0.0.0-20220223155221-ee480838109b/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc=
|
golang.org/x/oauth2 v0.3.0/go.mod h1:rQrIauxkUhJ6CuwEXwymO2/eh4xz2ZWF1nBkcxS+tGk=
|
||||||
golang.org/x/oauth2 v0.0.0-20220309155454-6242fa91716a/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc=
|
golang.org/x/oauth2 v0.8.0 h1:6dkIjl3j3LtZ/O3sTgZTMsLKSftL/B8Zgq4huOIIUu8=
|
||||||
golang.org/x/oauth2 v0.0.0-20220411215720-9780585627b5/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc=
|
golang.org/x/oauth2 v0.8.0/go.mod h1:yr7u4HXZRm1R1kBWqr/xKNqewf0plRYoB7sla+BCIXE=
|
||||||
golang.org/x/oauth2 v0.0.0-20220608161450-d0670ef3b1eb/go.mod h1:jaDAt6Dkxork7LmZnYtzbRWj0W47D86a3TGe0YHBvmE=
|
|
||||||
golang.org/x/oauth2 v0.0.0-20220822191816-0ebed06d0094/go.mod h1:h4gKUeWbJ4rQPri7E0u6Gs4e9Ri2zaLxzw5DI5XGrYg=
|
|
||||||
golang.org/x/oauth2 v0.4.0 h1:NF0gk8LVPg1Ml7SSbGyySuoxdsXitj7TvgvuRxIMc/M=
|
|
||||||
golang.org/x/oauth2 v0.4.0/go.mod h1:RznEsdpjGAINPTOF0UH/t+xJ75L18YO3Ho6Pyn+uRec=
|
|
||||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
@ -583,29 +449,22 @@ golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJ
|
||||||
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
|
||||||
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
|
||||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.0.0-20220601150217-0de741cfad7f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o=
|
golang.org/x/sync v0.2.0 h1:PUR+T4wwASmuSTYdKjYHI5TD22Wy5ogLU5qZCOLxBrI=
|
||||||
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.2.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
|
||||||
golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
|
||||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
@ -619,64 +478,34 @@ golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7w
|
||||||
golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20210220050731-9a76102bfb43/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20210305230114-8fe3ee5dd75b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20210315160823-c6e025ad8005/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20210514084401-e8d321eab015/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
|
||||||
golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
|
||||||
golang.org/x/sys v0.0.0-20210603125802-9665404d3644/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
|
||||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
|
||||||
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20210823070655-63515b42dcdf/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
|
||||||
golang.org/x/sys v0.0.0-20210908233432-aa78b53d3365/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
|
||||||
golang.org/x/sys v0.0.0-20211124211545-fe61309f8881/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
|
||||||
golang.org/x/sys v0.0.0-20211210111614-af8b64212486/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
|
||||||
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
|
||||||
golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
|
||||||
golang.org/x/sys v0.0.0-20220128215802-99c3d69c2c27/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
|
||||||
golang.org/x/sys v0.0.0-20220209214540-3681064d5158/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
|
||||||
golang.org/x/sys v0.0.0-20220227234510-4e6760a101f9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
|
||||||
golang.org/x/sys v0.0.0-20220328115105-d36c6a25d886/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
|
||||||
golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
|
||||||
golang.org/x/sys v0.0.0-20220502124256-b6088ccd6cba/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
|
||||||
golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
|
||||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20220610221304-9f5ed59c137d/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
|
||||||
golang.org/x/sys v0.0.0-20220808155132-1c4a2a72c664/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
|
||||||
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.7.0 h1:3jlCCIQZPdOYu1h8BkNvLz8Kgwtae2cagcG/VamtZRU=
|
golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.8.0 h1:EBmGv8NaZBZTWvrbjNoL6HVt+IVy3QDQpJs7VRIw3tU=
|
||||||
|
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||||
golang.org/x/term v0.7.0 h1:BEvjmm5fURWqcfbSKTdpkDXYBrUS1c0m8agp14W48vQ=
|
golang.org/x/term v0.3.0/go.mod h1:q750SLmJuPmVoN1blW3UFBPREJfb1KmY3vwxfr+nFDA=
|
||||||
golang.org/x/term v0.7.0/go.mod h1:P32HKFT3hSsZrRxla30E9HqToFYAQPCMs/zFMBUFqPY=
|
golang.org/x/term v0.8.0 h1:n5xxQn2i3PC0yLAbjTpNT85q/Kgzcr2gIoX9OrJUols=
|
||||||
|
golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
|
||||||
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
|
||||||
golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
|
||||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||||
|
golang.org/x/text v0.5.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||||
golang.org/x/text v0.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE=
|
golang.org/x/text v0.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE=
|
||||||
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
||||||
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||||
|
@ -695,7 +524,6 @@ golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgw
|
||||||
golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
||||||
golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
||||||
golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||||
golang.org/x/tools v0.0.0-20190828213141-aed303cbaa74/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
|
||||||
golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||||
golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||||
golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||||
|
@ -723,27 +551,14 @@ golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roY
|
||||||
golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
|
golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
|
||||||
golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
|
golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
|
||||||
golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
|
golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
|
||||||
golang.org/x/tools v0.0.0-20200904185747-39188db58858/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE=
|
|
||||||
golang.org/x/tools v0.0.0-20201110124207-079ba7bd75cd/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
|
||||||
golang.org/x/tools v0.0.0-20201201161351-ac6f37ff4c2a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
|
||||||
golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
|
||||||
golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
|
||||||
golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0=
|
|
||||||
golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
||||||
golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||||
golang.org/x/tools v0.1.3/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
golang.org/x/tools v0.9.1 h1:8WMNJAz3zrtPmnYC7ISf5dEn3MT0gY7jBJfw27yrrLo=
|
||||||
golang.org/x/tools v0.1.4/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
golang.org/x/tools v0.9.1/go.mod h1:owI94Op576fPu3cIGQeHs3joujW/2Oc6MtlxbF5dfNc=
|
||||||
golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
|
||||||
golang.org/x/tools v0.1.6-0.20210726203631-07bc1bf47fb2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
|
||||||
golang.org/x/tools v0.6.0 h1:BOw41kyTf3PuCW1pVQf8+Cyg8pMlkYB1oo9iJ6D/lKM=
|
|
||||||
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
|
|
||||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
golang.org/x/xerrors v0.0.0-20220411194840-2f41105eb62f/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
|
||||||
golang.org/x/xerrors v0.0.0-20220517211312-f3a8303e98df/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8=
|
|
||||||
golang.org/x/xerrors v0.0.0-20220609144429-65e65417b02f/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8=
|
|
||||||
google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
|
google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
|
||||||
google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=
|
google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=
|
||||||
google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
|
google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
|
||||||
|
@ -760,29 +575,6 @@ google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0M
|
||||||
google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=
|
google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=
|
||||||
google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM=
|
google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM=
|
||||||
google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc=
|
google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc=
|
||||||
google.golang.org/api v0.35.0/go.mod h1:/XrVsuzM0rZmrsbjJutiuftIzeuTQcEeaYcSk/mQ1dg=
|
|
||||||
google.golang.org/api v0.36.0/go.mod h1:+z5ficQTmoYpPn8LCUNVpK5I7hwkpjbcgqA7I34qYtE=
|
|
||||||
google.golang.org/api v0.40.0/go.mod h1:fYKFpnQN0DsDSKRVRcQSDQNtqWPfM9i+zNPxepjRCQ8=
|
|
||||||
google.golang.org/api v0.41.0/go.mod h1:RkxM5lITDfTzmyKFPt+wGrCJbVfniCr2ool8kTBzRTU=
|
|
||||||
google.golang.org/api v0.43.0/go.mod h1:nQsDGjRXMo4lvh5hP0TKqF244gqhGcr/YSIykhUk/94=
|
|
||||||
google.golang.org/api v0.47.0/go.mod h1:Wbvgpq1HddcWVtzsVLyfLp8lDg6AA241LmgIL59tHXo=
|
|
||||||
google.golang.org/api v0.48.0/go.mod h1:71Pr1vy+TAZRPkPs/xlCf5SsU8WjuAWv1Pfjbtukyy4=
|
|
||||||
google.golang.org/api v0.50.0/go.mod h1:4bNT5pAuq5ji4SRZm+5QIkjny9JAyVD/3gaSihNefaw=
|
|
||||||
google.golang.org/api v0.51.0/go.mod h1:t4HdrdoNgyN5cbEfm7Lum0lcLDLiise1F8qDKX00sOU=
|
|
||||||
google.golang.org/api v0.54.0/go.mod h1:7C4bFFOvVDGXjfDTAsgGwDgAxRDeQ4X8NvUedIt6z3k=
|
|
||||||
google.golang.org/api v0.55.0/go.mod h1:38yMfeP1kfjsl8isn0tliTjIb1rJXcQi4UXlbqivdVE=
|
|
||||||
google.golang.org/api v0.56.0/go.mod h1:38yMfeP1kfjsl8isn0tliTjIb1rJXcQi4UXlbqivdVE=
|
|
||||||
google.golang.org/api v0.57.0/go.mod h1:dVPlbZyBo2/OjBpmvNdpn2GRm6rPy75jyU7bmhdrMgI=
|
|
||||||
google.golang.org/api v0.61.0/go.mod h1:xQRti5UdCmoCEqFxcz93fTl338AVqDgyaDRuOZ3hg9I=
|
|
||||||
google.golang.org/api v0.63.0/go.mod h1:gs4ij2ffTRXwuzzgJl/56BdwJaA194ijkfn++9tDuPo=
|
|
||||||
google.golang.org/api v0.67.0/go.mod h1:ShHKP8E60yPsKNw/w8w+VYaj9H6buA5UqDp8dhbQZ6g=
|
|
||||||
google.golang.org/api v0.70.0/go.mod h1:Bs4ZM2HGifEvXwd50TtW70ovgJffJYw2oRCOFU/SkfA=
|
|
||||||
google.golang.org/api v0.71.0/go.mod h1:4PyU6e6JogV1f9eA4voyrTY2batOLdgZ5qZ5HOCc4j8=
|
|
||||||
google.golang.org/api v0.74.0/go.mod h1:ZpfMZOVRMywNyvJFeqL9HRWBgAuRfSjJFpe9QtRRyDs=
|
|
||||||
google.golang.org/api v0.75.0/go.mod h1:pU9QmyHLnzlpar1Mjt4IbapUCy8J+6HD6GeELN69ljA=
|
|
||||||
google.golang.org/api v0.78.0/go.mod h1:1Sg78yoMLOhlQTeF+ARBoytAcH1NNyyl390YMy6rKmw=
|
|
||||||
google.golang.org/api v0.80.0/go.mod h1:xY3nI94gbvBrE0J6NHXhxOmW97HG7Khjkku6AFB3Hyg=
|
|
||||||
google.golang.org/api v0.84.0/go.mod h1:NTsGnUFJMYROtiquksZHBWtHfeMC7iYthki7Eq3pa8o=
|
|
||||||
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
|
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
|
||||||
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||||
google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||||
|
@ -821,56 +613,9 @@ google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7Fc
|
||||||
google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||||
google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||||
google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||||
google.golang.org/genproto v0.0.0-20200904004341-0bd0a958aa1d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
|
||||||
google.golang.org/genproto v0.0.0-20201109203340-2640f1f9cdfb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
|
||||||
google.golang.org/genproto v0.0.0-20201201144952-b05cb90ed32e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
|
||||||
google.golang.org/genproto v0.0.0-20201210142538-e3217bee35cc/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
|
||||||
google.golang.org/genproto v0.0.0-20201214200347-8c77b98c765d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
|
||||||
google.golang.org/genproto v0.0.0-20210222152913-aa3ee6e6a81c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
|
||||||
google.golang.org/genproto v0.0.0-20210303154014-9728d6b83eeb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
|
||||||
google.golang.org/genproto v0.0.0-20210310155132-4ce2db91004e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
|
||||||
google.golang.org/genproto v0.0.0-20210319143718-93e7006c17a6/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
|
||||||
google.golang.org/genproto v0.0.0-20210329143202-679c6ae281ee/go.mod h1:9lPAdzaEmUacj36I+k7YKbEc5CXzPIeORRgDAUOu28A=
|
|
||||||
google.golang.org/genproto v0.0.0-20210402141018-6c239bbf2bb1/go.mod h1:9lPAdzaEmUacj36I+k7YKbEc5CXzPIeORRgDAUOu28A=
|
|
||||||
google.golang.org/genproto v0.0.0-20210513213006-bf773b8c8384/go.mod h1:P3QM42oQyzQSnHPnZ/vqoCdDmzH28fzWByN9asMeM8A=
|
|
||||||
google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0=
|
|
||||||
google.golang.org/genproto v0.0.0-20210604141403-392c879c8b08/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0=
|
|
||||||
google.golang.org/genproto v0.0.0-20210608205507-b6d2f5bf0d7d/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0=
|
|
||||||
google.golang.org/genproto v0.0.0-20210624195500-8bfb893ecb84/go.mod h1:SzzZ/N+nwJDaO1kznhnlzqS8ocJICar6hYhVyhi++24=
|
|
||||||
google.golang.org/genproto v0.0.0-20210713002101-d411969a0d9a/go.mod h1:AxrInvYm1dci+enl5hChSFPOmmUF1+uAa/UsgNRWd7k=
|
|
||||||
google.golang.org/genproto v0.0.0-20210716133855-ce7ef5c701ea/go.mod h1:AxrInvYm1dci+enl5hChSFPOmmUF1+uAa/UsgNRWd7k=
|
|
||||||
google.golang.org/genproto v0.0.0-20210728212813-7823e685a01f/go.mod h1:ob2IJxKrgPT52GcgX759i1sleT07tiKowYBGbczaW48=
|
|
||||||
google.golang.org/genproto v0.0.0-20210805201207-89edb61ffb67/go.mod h1:ob2IJxKrgPT52GcgX759i1sleT07tiKowYBGbczaW48=
|
|
||||||
google.golang.org/genproto v0.0.0-20210813162853-db860fec028c/go.mod h1:cFeNkxwySK631ADgubI+/XFU/xp8FD5KIVV4rj8UC5w=
|
|
||||||
google.golang.org/genproto v0.0.0-20210821163610-241b8fcbd6c8/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY=
|
|
||||||
google.golang.org/genproto v0.0.0-20210828152312-66f60bf46e71/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY=
|
|
||||||
google.golang.org/genproto v0.0.0-20210831024726-fe130286e0e2/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY=
|
|
||||||
google.golang.org/genproto v0.0.0-20210903162649-d08c68adba83/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY=
|
|
||||||
google.golang.org/genproto v0.0.0-20210909211513-a8c4777a87af/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY=
|
|
||||||
google.golang.org/genproto v0.0.0-20210924002016-3dee208752a0/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
|
|
||||||
google.golang.org/genproto v0.0.0-20211118181313-81c1377c94b1/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
|
google.golang.org/genproto v0.0.0-20211118181313-81c1377c94b1/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
|
||||||
google.golang.org/genproto v0.0.0-20211206160659-862468c7d6e0/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
|
google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1 h1:KpwkzHKEF7B9Zxg18WzOa7djJ+Ha5DzthMyZYQfEn2A=
|
||||||
google.golang.org/genproto v0.0.0-20211208223120-3a66f561d7aa/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
|
google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1/go.mod h1:nKE/iIaLqn2bQwXBg8f1g2Ylh6r5MN5CmZvuzZCgsCU=
|
||||||
google.golang.org/genproto v0.0.0-20211221195035-429b39de9b1c/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
|
|
||||||
google.golang.org/genproto v0.0.0-20220126215142-9970aeb2e350/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
|
|
||||||
google.golang.org/genproto v0.0.0-20220207164111-0872dc986b00/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
|
|
||||||
google.golang.org/genproto v0.0.0-20220218161850-94dd64e39d7c/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI=
|
|
||||||
google.golang.org/genproto v0.0.0-20220222213610-43724f9ea8cf/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI=
|
|
||||||
google.golang.org/genproto v0.0.0-20220304144024-325a89244dc8/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI=
|
|
||||||
google.golang.org/genproto v0.0.0-20220310185008-1973136f34c6/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI=
|
|
||||||
google.golang.org/genproto v0.0.0-20220324131243-acbaeb5b85eb/go.mod h1:hAL49I2IFola2sVEjAn7MEwsja0xp51I0tlGAf9hz4E=
|
|
||||||
google.golang.org/genproto v0.0.0-20220407144326-9054f6ed7bac/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo=
|
|
||||||
google.golang.org/genproto v0.0.0-20220413183235-5e96e2839df9/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo=
|
|
||||||
google.golang.org/genproto v0.0.0-20220414192740-2d67ff6cf2b4/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo=
|
|
||||||
google.golang.org/genproto v0.0.0-20220421151946-72621c1f0bd3/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo=
|
|
||||||
google.golang.org/genproto v0.0.0-20220429170224-98d788798c3e/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo=
|
|
||||||
google.golang.org/genproto v0.0.0-20220505152158-f39f71e6c8f3/go.mod h1:RAyBrSAP7Fh3Nc84ghnVLDPuV51xc9agzmm4Ph6i0Q4=
|
|
||||||
google.golang.org/genproto v0.0.0-20220518221133-4f43b3371335/go.mod h1:RAyBrSAP7Fh3Nc84ghnVLDPuV51xc9agzmm4Ph6i0Q4=
|
|
||||||
google.golang.org/genproto v0.0.0-20220523171625-347a074981d8/go.mod h1:RAyBrSAP7Fh3Nc84ghnVLDPuV51xc9agzmm4Ph6i0Q4=
|
|
||||||
google.golang.org/genproto v0.0.0-20220608133413-ed9918b62aac/go.mod h1:KEWEmljWE5zPzLBa/oHl6DaEt9LmfH6WtH1OHIvleBA=
|
|
||||||
google.golang.org/genproto v0.0.0-20220616135557-88e70c0c3a90/go.mod h1:KEWEmljWE5zPzLBa/oHl6DaEt9LmfH6WtH1OHIvleBA=
|
|
||||||
google.golang.org/genproto v0.0.0-20221202195650-67e5cbc046fd h1:OjndDrsik+Gt+e6fs45z9AxiewiKyLKYpA45W5Kpkks=
|
|
||||||
google.golang.org/genproto v0.0.0-20221202195650-67e5cbc046fd/go.mod h1:cTsE614GARnxrLsqKREzmNYJACSWWpAWdNMwnD7c2BE=
|
|
||||||
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
||||||
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
|
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
|
||||||
google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
|
google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
|
||||||
|
@ -883,29 +628,12 @@ google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKa
|
||||||
google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk=
|
google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk=
|
||||||
google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
|
google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
|
||||||
google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
|
google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
|
||||||
google.golang.org/grpc v1.31.1/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
|
|
||||||
google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0=
|
google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0=
|
||||||
google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc=
|
|
||||||
google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA51WJ8=
|
|
||||||
google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
|
|
||||||
google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
|
google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
|
||||||
google.golang.org/grpc v1.36.1/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
|
|
||||||
google.golang.org/grpc v1.37.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM=
|
|
||||||
google.golang.org/grpc v1.37.1/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM=
|
|
||||||
google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM=
|
|
||||||
google.golang.org/grpc v1.39.0/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnDzfrE=
|
|
||||||
google.golang.org/grpc v1.39.1/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnDzfrE=
|
|
||||||
google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34=
|
google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34=
|
||||||
google.golang.org/grpc v1.40.1/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34=
|
|
||||||
google.golang.org/grpc v1.42.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU=
|
google.golang.org/grpc v1.42.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU=
|
||||||
google.golang.org/grpc v1.44.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU=
|
google.golang.org/grpc v1.55.0 h1:3Oj82/tFSCeUrRTg/5E/7d/W5A1tj6Ky1ABAuZuv5ag=
|
||||||
google.golang.org/grpc v1.45.0/go.mod h1:lN7owxKUQEqMfSyQikvvk5tf/6zMPsrK+ONuO11+0rQ=
|
google.golang.org/grpc v1.55.0/go.mod h1:iYEXKGkEBhg1PjZQvoYEVPTDkHo1/bjTnfwTeGONTY8=
|
||||||
google.golang.org/grpc v1.46.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk=
|
|
||||||
google.golang.org/grpc v1.46.2/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk=
|
|
||||||
google.golang.org/grpc v1.47.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk=
|
|
||||||
google.golang.org/grpc v1.51.0 h1:E1eGv1FTqoLIdnBCZufiSHgKjlqG6fKFf6pPWtMTh8U=
|
|
||||||
google.golang.org/grpc v1.51.0/go.mod h1:wgNDFcnuBGmxLKI/qn4T+m5BtEBYXJPvibbUPsAIPww=
|
|
||||||
google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0/go.mod h1:6Kw0yEErY5E/yWrBtf03jp27GLLJujG4z/JK95pnjjw=
|
|
||||||
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
|
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
|
||||||
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
|
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
|
||||||
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
|
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
|
||||||
|
@ -920,28 +648,21 @@ google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp0
|
||||||
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
||||||
google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
||||||
google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
|
google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
|
||||||
google.golang.org/protobuf v1.28.1 h1:d0NfwRgPtno5B1Wa6L2DAG+KivqkdutMf1UhdNx175w=
|
google.golang.org/protobuf v1.30.0 h1:kPPoIgf3TsEvrm0PFe15JQ+570QVxYzEvvHqChK+cng=
|
||||||
google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
|
google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
|
||||||
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
|
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
|
||||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
|
||||||
gopkg.in/coreos/go-oidc.v2 v2.2.1 h1:MY5SZClJ7vhjKfr64a4nHAOV/c3WH2gB9BMrR64J1Mc=
|
gopkg.in/coreos/go-oidc.v2 v2.2.1 h1:MY5SZClJ7vhjKfr64a4nHAOV/c3WH2gB9BMrR64J1Mc=
|
||||||
gopkg.in/coreos/go-oidc.v2 v2.2.1/go.mod h1:fYaTe2FS96wZZwR17YTDHwG+Mw6fmyqJNxN2eNCGPCI=
|
gopkg.in/coreos/go-oidc.v2 v2.2.1/go.mod h1:fYaTe2FS96wZZwR17YTDHwG+Mw6fmyqJNxN2eNCGPCI=
|
||||||
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
|
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
|
||||||
gopkg.in/natefinch/lumberjack.v2 v2.0.0 h1:1Lc07Kr7qY4U2YPouBjpCLxpiyxIVoxqXgkXLknAOE8=
|
gopkg.in/natefinch/lumberjack.v2 v2.2.1 h1:bBRl1b0OH9s/DuPhuXpNl+VtCaJXFZ5/uEFST95x9zc=
|
||||||
gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k=
|
gopkg.in/natefinch/lumberjack.v2 v2.2.1/go.mod h1:YD8tP3GAjkrDg1eZH7EGmyESg/lsYskCTPBJVb9jqSc=
|
||||||
gopkg.in/square/go-jose.v2 v2.6.0 h1:NGk74WTnPKBNUhNzQX7PYcTLUjoq7mzKk2OKbvwk2iI=
|
gopkg.in/square/go-jose.v2 v2.6.0 h1:NGk74WTnPKBNUhNzQX7PYcTLUjoq7mzKk2OKbvwk2iI=
|
||||||
gopkg.in/square/go-jose.v2 v2.6.0/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI=
|
gopkg.in/square/go-jose.v2 v2.6.0/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI=
|
||||||
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
|
||||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
|
||||||
gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
|
||||||
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
|
||||||
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
|
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
|
||||||
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
||||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||||
|
@ -958,5 +679,5 @@ nhooyr.io/websocket v1.8.7/go.mod h1:B70DZP8IakI65RVQ51MsWP/8jndNma26DVA/nFSCgW0
|
||||||
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
|
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
|
||||||
rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
|
rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
|
||||||
rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=
|
rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=
|
||||||
zombiezen.com/go/capnproto2 v2.18.0+incompatible h1:mwfXZniffG5mXokQGHUJWGnqIBggoPfT/CEwon9Yess=
|
zombiezen.com/go/capnproto2 v2.18.2+incompatible h1:v3BD1zbruvffn7zjJUU5Pn8nZAB11bhZSQC4W+YnnKo=
|
||||||
zombiezen.com/go/capnproto2 v2.18.0+incompatible/go.mod h1:XO5Pr2SbXgqZwn0m0Ru54QBqpOf4K5AYBO+8LAOBQEQ=
|
zombiezen.com/go/capnproto2 v2.18.2+incompatible/go.mod h1:XO5Pr2SbXgqZwn0m0Ru54QBqpOf4K5AYBO+8LAOBQEQ=
|
||||||
|
|
|
@ -132,7 +132,6 @@ func createResourceSpan(spans []*tracepb.Span) *tracepb.ResourceSpans {
|
||||||
SchemaUrl: instrumentSchemaUrl,
|
SchemaUrl: instrumentSchemaUrl,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
InstrumentationLibrarySpans: nil,
|
|
||||||
SchemaUrl: resourceSchemaUrl,
|
SchemaUrl: resourceSchemaUrl,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,7 +21,9 @@ type Unmarshaler interface {
|
||||||
UnmarshalTOML(interface{}) error
|
UnmarshalTOML(interface{}) error
|
||||||
}
|
}
|
||||||
|
|
||||||
// Unmarshal decodes the contents of `data` in TOML format into a pointer `v`.
|
// Unmarshal decodes the contents of data in TOML format into a pointer v.
|
||||||
|
//
|
||||||
|
// See [Decoder] for a description of the decoding process.
|
||||||
func Unmarshal(data []byte, v interface{}) error {
|
func Unmarshal(data []byte, v interface{}) error {
|
||||||
_, err := NewDecoder(bytes.NewReader(data)).Decode(v)
|
_, err := NewDecoder(bytes.NewReader(data)).Decode(v)
|
||||||
return err
|
return err
|
||||||
|
@ -29,13 +31,12 @@ func Unmarshal(data []byte, v interface{}) error {
|
||||||
|
|
||||||
// Decode the TOML data in to the pointer v.
|
// Decode the TOML data in to the pointer v.
|
||||||
//
|
//
|
||||||
// See the documentation on Decoder for a description of the decoding process.
|
// See [Decoder] for a description of the decoding process.
|
||||||
func Decode(data string, v interface{}) (MetaData, error) {
|
func Decode(data string, v interface{}) (MetaData, error) {
|
||||||
return NewDecoder(strings.NewReader(data)).Decode(v)
|
return NewDecoder(strings.NewReader(data)).Decode(v)
|
||||||
}
|
}
|
||||||
|
|
||||||
// DecodeFile is just like Decode, except it will automatically read the
|
// DecodeFile reads the contents of a file and decodes it with [Decode].
|
||||||
// contents of the file at path and decode it for you.
|
|
||||||
func DecodeFile(path string, v interface{}) (MetaData, error) {
|
func DecodeFile(path string, v interface{}) (MetaData, error) {
|
||||||
fp, err := os.Open(path)
|
fp, err := os.Open(path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -48,7 +49,7 @@ func DecodeFile(path string, v interface{}) (MetaData, error) {
|
||||||
// Primitive is a TOML value that hasn't been decoded into a Go value.
|
// 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.
|
// 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.
|
// You can use [PrimitiveDecode] to "manually" decode these values.
|
||||||
//
|
//
|
||||||
// NOTE: The underlying representation of a `Primitive` value is subject to
|
// NOTE: The underlying representation of a `Primitive` value is subject to
|
||||||
// change. Do not rely on it.
|
// change. Do not rely on it.
|
||||||
|
@ -70,15 +71,15 @@ const (
|
||||||
|
|
||||||
// Decoder decodes TOML data.
|
// Decoder decodes TOML data.
|
||||||
//
|
//
|
||||||
// TOML tables correspond to Go structs or maps (dealer's choice – they can be
|
// TOML tables correspond to Go structs or maps; they can be used
|
||||||
// used interchangeably).
|
// interchangeably, but structs offer better type safety.
|
||||||
//
|
//
|
||||||
// TOML table arrays correspond to either a slice of structs or a slice of maps.
|
// 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
|
// TOML datetimes correspond to [time.Time]. Local datetimes are parsed in the
|
||||||
// in the local timezone.
|
// local timezone.
|
||||||
//
|
//
|
||||||
// time.Duration types are treated as nanoseconds if the TOML value is an
|
// [time.Duration] types are treated as nanoseconds if the TOML value is an
|
||||||
// integer, or they're parsed with time.ParseDuration() if they're strings.
|
// integer, or they're parsed with time.ParseDuration() if they're strings.
|
||||||
//
|
//
|
||||||
// All other TOML types (float, string, int, bool and array) correspond to the
|
// All other TOML types (float, string, int, bool and array) correspond to the
|
||||||
|
@ -90,7 +91,7 @@ const (
|
||||||
// UnmarshalText method. See the Unmarshaler example for a demonstration with
|
// UnmarshalText method. See the Unmarshaler example for a demonstration with
|
||||||
// email addresses.
|
// email addresses.
|
||||||
//
|
//
|
||||||
// Key mapping
|
// ### Key mapping
|
||||||
//
|
//
|
||||||
// TOML keys can map to either keys in a Go map or field names in a Go struct.
|
// 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
|
// The special `toml` struct tag can be used to map TOML keys to struct fields
|
||||||
|
@ -168,17 +169,16 @@ func (dec *Decoder) Decode(v interface{}) (MetaData, error) {
|
||||||
return md, md.unify(p.mapping, rv)
|
return md, md.unify(p.mapping, rv)
|
||||||
}
|
}
|
||||||
|
|
||||||
// PrimitiveDecode is just like the other `Decode*` functions, except it
|
// PrimitiveDecode is just like the other Decode* functions, except it decodes a
|
||||||
// decodes a TOML value that has already been parsed. Valid primitive values
|
// TOML value that has already been parsed. Valid primitive values can *only* be
|
||||||
// can *only* be obtained from values filled by the decoder functions,
|
// obtained from values filled by the decoder functions, including this method.
|
||||||
// including this method. (i.e., `v` may contain more `Primitive`
|
// (i.e., v may contain more [Primitive] values.)
|
||||||
// values.)
|
|
||||||
//
|
//
|
||||||
// Meta data for primitive values is included in the meta data returned by
|
// Meta data for primitive values is included in the meta data returned by the
|
||||||
// the `Decode*` functions with one exception: keys returned by the Undecoded
|
// Decode* functions with one exception: keys returned by the Undecoded method
|
||||||
// method will only reflect keys that were decoded. Namely, any keys hidden
|
// will only reflect keys that were decoded. Namely, any keys hidden behind a
|
||||||
// behind a Primitive will be considered undecoded. Executing this method will
|
// Primitive will be considered undecoded. Executing this method will update the
|
||||||
// update the undecoded keys in the meta data. (See the example.)
|
// undecoded keys in the meta data. (See the example.)
|
||||||
func (md *MetaData) PrimitiveDecode(primValue Primitive, v interface{}) error {
|
func (md *MetaData) PrimitiveDecode(primValue Primitive, v interface{}) error {
|
||||||
md.context = primValue.context
|
md.context = primValue.context
|
||||||
defer func() { md.context = nil }()
|
defer func() { md.context = nil }()
|
||||||
|
|
|
@ -7,8 +7,8 @@ import (
|
||||||
"io/fs"
|
"io/fs"
|
||||||
)
|
)
|
||||||
|
|
||||||
// DecodeFS is just like Decode, except it will automatically read the contents
|
// DecodeFS reads the contents of a file from [fs.FS] and decodes it with
|
||||||
// of the file at `path` from a fs.FS instance.
|
// [Decode].
|
||||||
func DecodeFS(fsys fs.FS, path string, v interface{}) (MetaData, error) {
|
func DecodeFS(fsys fs.FS, path string, v interface{}) (MetaData, error) {
|
||||||
fp, err := fsys.Open(path)
|
fp, err := fsys.Open(path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
@ -1,13 +1,11 @@
|
||||||
/*
|
// Package toml implements decoding and encoding of TOML files.
|
||||||
Package toml implements decoding and encoding of TOML files.
|
//
|
||||||
|
// This package supports TOML v1.0.0, as specified at https://toml.io
|
||||||
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
|
||||||
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.
|
||||||
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,
|
||||||
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
|
||||||
and can be used to verify if TOML document is valid. It can also be used to
|
// print the type of each key.
|
||||||
print the type of each key.
|
|
||||||
*/
|
|
||||||
package toml
|
package toml
|
||||||
|
|
|
@ -79,12 +79,12 @@ type Marshaler interface {
|
||||||
// Encoder encodes a Go to a TOML document.
|
// Encoder encodes a Go to a TOML document.
|
||||||
//
|
//
|
||||||
// The mapping between Go values and TOML values should be precisely the same as
|
// The mapping between Go values and TOML values should be precisely the same as
|
||||||
// for the Decode* functions.
|
// for [Decode].
|
||||||
//
|
//
|
||||||
// time.Time is encoded as a RFC 3339 string, and time.Duration as its string
|
// time.Time is encoded as a RFC 3339 string, and time.Duration as its string
|
||||||
// representation.
|
// representation.
|
||||||
//
|
//
|
||||||
// The toml.Marshaler and encoder.TextMarshaler interfaces are supported to
|
// The [Marshaler] and [encoding.TextMarshaler] interfaces are supported to
|
||||||
// encoding the value as custom TOML.
|
// encoding the value as custom TOML.
|
||||||
//
|
//
|
||||||
// If you want to write arbitrary binary data then you will need to use
|
// If you want to write arbitrary binary data then you will need to use
|
||||||
|
@ -130,7 +130,7 @@ func NewEncoder(w io.Writer) *Encoder {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Encode writes a TOML representation of the Go value to the Encoder's writer.
|
// 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
|
// An error is returned if the value given cannot be encoded to a valid TOML
|
||||||
// document.
|
// document.
|
||||||
|
@ -261,7 +261,7 @@ func (enc *Encoder) eElement(rv reflect.Value) {
|
||||||
enc.eElement(reflect.ValueOf(v))
|
enc.eElement(reflect.ValueOf(v))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
encPanic(errors.New(fmt.Sprintf("Unable to convert \"%s\" to neither int64 nor float64", n)))
|
encPanic(fmt.Errorf("unable to convert %q to int64 or float64", n))
|
||||||
}
|
}
|
||||||
|
|
||||||
switch rv.Kind() {
|
switch rv.Kind() {
|
||||||
|
@ -504,7 +504,8 @@ func (enc *Encoder) eStruct(key Key, rv reflect.Value, inline bool) {
|
||||||
if opts.name != "" {
|
if opts.name != "" {
|
||||||
keyName = opts.name
|
keyName = opts.name
|
||||||
}
|
}
|
||||||
if opts.omitempty && isEmpty(fieldVal) {
|
|
||||||
|
if opts.omitempty && enc.isEmpty(fieldVal) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if opts.omitzero && isZero(fieldVal) {
|
if opts.omitzero && isZero(fieldVal) {
|
||||||
|
@ -648,12 +649,26 @@ func isZero(rv reflect.Value) bool {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
func isEmpty(rv reflect.Value) bool {
|
func (enc *Encoder) isEmpty(rv reflect.Value) bool {
|
||||||
switch rv.Kind() {
|
switch rv.Kind() {
|
||||||
case reflect.Array, reflect.Slice, reflect.Map, reflect.String:
|
case reflect.Array, reflect.Slice, reflect.Map, reflect.String:
|
||||||
return rv.Len() == 0
|
return rv.Len() == 0
|
||||||
case reflect.Struct:
|
case reflect.Struct:
|
||||||
|
if rv.Type().Comparable() {
|
||||||
return reflect.Zero(rv.Type()).Interface() == rv.Interface()
|
return reflect.Zero(rv.Type()).Interface() == rv.Interface()
|
||||||
|
}
|
||||||
|
// Need to also check if all the fields are empty, otherwise something
|
||||||
|
// like this with uncomparable types will always return true:
|
||||||
|
//
|
||||||
|
// type a struct{ field b }
|
||||||
|
// type b struct{ s []string }
|
||||||
|
// s := a{field: b{s: []string{"AAA"}}}
|
||||||
|
for i := 0; i < rv.NumField(); i++ {
|
||||||
|
if !enc.isEmpty(rv.Field(i)) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
case reflect.Bool:
|
case reflect.Bool:
|
||||||
return !rv.Bool()
|
return !rv.Bool()
|
||||||
}
|
}
|
||||||
|
@ -673,11 +688,10 @@ func (enc *Encoder) newline() {
|
||||||
// This is also used for "k = v" in inline tables; so something like this will
|
// This is also used for "k = v" in inline tables; so something like this will
|
||||||
// be written in three calls:
|
// be written in three calls:
|
||||||
//
|
//
|
||||||
// ┌────────────────────┐
|
// ┌───────────────────┐
|
||||||
// │ ┌───┐ ┌─────┐│
|
// │ ┌───┐ ┌────┐│
|
||||||
// v v v v vv
|
// v v v v vv
|
||||||
// key = {k = v, k2 = v2}
|
// key = {k = 1, k2 = 2}
|
||||||
//
|
|
||||||
func (enc *Encoder) writeKeyValue(key Key, val reflect.Value, inline bool) {
|
func (enc *Encoder) writeKeyValue(key Key, val reflect.Value, inline bool) {
|
||||||
if len(key) == 0 {
|
if len(key) == 0 {
|
||||||
encPanic(errNoKey)
|
encPanic(errNoKey)
|
||||||
|
|
|
@ -5,12 +5,11 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
// ParseError is returned when there is an error parsing the TOML syntax.
|
// ParseError is returned when there is an error parsing the TOML syntax such as
|
||||||
//
|
// invalid syntax, duplicate keys, etc.
|
||||||
// For example invalid syntax, duplicate keys, etc.
|
|
||||||
//
|
//
|
||||||
// In addition to the error message itself, you can also print detailed location
|
// In addition to the error message itself, you can also print detailed location
|
||||||
// information with context by using ErrorWithPosition():
|
// information with context by using [ErrorWithPosition]:
|
||||||
//
|
//
|
||||||
// toml: error: Key 'fruit' was already created and cannot be used as an array.
|
// toml: error: Key 'fruit' was already created and cannot be used as an array.
|
||||||
//
|
//
|
||||||
|
@ -21,8 +20,8 @@ import (
|
||||||
// 4 | [[fruit]] # Not allowed
|
// 4 | [[fruit]] # Not allowed
|
||||||
// ^^^^^
|
// ^^^^^
|
||||||
//
|
//
|
||||||
// Furthermore, the ErrorWithUsage() can be used to print the above with some
|
// [ErrorWithUsage] can be used to print the above with some more detailed usage
|
||||||
// more detailed usage guidance:
|
// guidance:
|
||||||
//
|
//
|
||||||
// toml: error: newlines not allowed within inline tables
|
// toml: error: newlines not allowed within inline tables
|
||||||
//
|
//
|
||||||
|
@ -55,7 +54,11 @@ type ParseError struct {
|
||||||
Usage string // Longer message with usage guidance; may be blank.
|
Usage string // Longer message with usage guidance; may be blank.
|
||||||
Position Position // Position of the error
|
Position Position // Position of the error
|
||||||
LastKey string // Last parsed key, may be blank.
|
LastKey string // Last parsed key, may be blank.
|
||||||
Line int // Line the error occurred. Deprecated: use Position.
|
|
||||||
|
// Line the error occurred.
|
||||||
|
//
|
||||||
|
// Deprecated: use [Position].
|
||||||
|
Line int
|
||||||
|
|
||||||
err error
|
err error
|
||||||
input string
|
input string
|
||||||
|
@ -83,7 +86,7 @@ func (pe ParseError) Error() string {
|
||||||
|
|
||||||
// ErrorWithUsage() returns the error with detailed location context.
|
// ErrorWithUsage() returns the error with detailed location context.
|
||||||
//
|
//
|
||||||
// See the documentation on ParseError.
|
// See the documentation on [ParseError].
|
||||||
func (pe ParseError) ErrorWithPosition() string {
|
func (pe ParseError) ErrorWithPosition() string {
|
||||||
if pe.input == "" { // Should never happen, but just in case.
|
if pe.input == "" { // Should never happen, but just in case.
|
||||||
return pe.Error()
|
return pe.Error()
|
||||||
|
@ -124,7 +127,7 @@ func (pe ParseError) ErrorWithPosition() string {
|
||||||
// ErrorWithUsage() returns the error with detailed location context and usage
|
// ErrorWithUsage() returns the error with detailed location context and usage
|
||||||
// guidance.
|
// guidance.
|
||||||
//
|
//
|
||||||
// See the documentation on ParseError.
|
// See the documentation on [ParseError].
|
||||||
func (pe ParseError) ErrorWithUsage() string {
|
func (pe ParseError) ErrorWithUsage() string {
|
||||||
m := pe.ErrorWithPosition()
|
m := pe.ErrorWithPosition()
|
||||||
if u, ok := pe.err.(interface{ Usage() string }); ok && u.Usage() != "" {
|
if u, ok := pe.err.(interface{ Usage() string }); ok && u.Usage() != "" {
|
||||||
|
|
|
@ -771,7 +771,7 @@ func lexRawString(lx *lexer) stateFn {
|
||||||
}
|
}
|
||||||
|
|
||||||
// lexMultilineRawString consumes a raw string. Nothing can be escaped in such
|
// lexMultilineRawString consumes a raw string. Nothing can be escaped in such
|
||||||
// a string. It assumes that the beginning "'''" has already been consumed and
|
// a string. It assumes that the beginning ''' has already been consumed and
|
||||||
// ignored.
|
// ignored.
|
||||||
func lexMultilineRawString(lx *lexer) stateFn {
|
func lexMultilineRawString(lx *lexer) stateFn {
|
||||||
r := lx.next()
|
r := lx.next()
|
||||||
|
|
|
@ -71,7 +71,7 @@ func (md *MetaData) Keys() []Key {
|
||||||
// Undecoded returns all keys that have not been decoded in the order in which
|
// Undecoded returns all keys that have not been decoded in the order in which
|
||||||
// they appear in the original TOML document.
|
// they appear in the original TOML document.
|
||||||
//
|
//
|
||||||
// This includes keys that haven't been decoded because of a Primitive value.
|
// 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.
|
// 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,
|
// Also note that decoding into an empty interface will result in no decoding,
|
||||||
|
@ -89,7 +89,7 @@ func (md *MetaData) Undecoded() []Key {
|
||||||
return undecoded
|
return undecoded
|
||||||
}
|
}
|
||||||
|
|
||||||
// Key represents any TOML key, including key groups. Use (MetaData).Keys to get
|
// Key represents any TOML key, including key groups. Use [MetaData.Keys] to get
|
||||||
// values of this type.
|
// values of this type.
|
||||||
type Key []string
|
type Key []string
|
||||||
|
|
||||||
|
|
|
@ -3,8 +3,7 @@
|
||||||
[](https://pkg.go.dev/github.com/cespare/xxhash/v2)
|
[](https://pkg.go.dev/github.com/cespare/xxhash/v2)
|
||||||
[](https://github.com/cespare/xxhash/actions/workflows/test.yml)
|
[](https://github.com/cespare/xxhash/actions/workflows/test.yml)
|
||||||
|
|
||||||
xxhash is a Go implementation of the 64-bit
|
xxhash is a Go implementation of the 64-bit [xxHash] algorithm, XXH64. This is a
|
||||||
[xxHash](http://cyan4973.github.io/xxHash/) algorithm, XXH64. This is a
|
|
||||||
high-quality hashing algorithm that is much faster than anything in the Go
|
high-quality hashing algorithm that is much faster than anything in the Go
|
||||||
standard library.
|
standard library.
|
||||||
|
|
||||||
|
@ -25,8 +24,11 @@ func (*Digest) WriteString(string) (int, error)
|
||||||
func (*Digest) Sum64() uint64
|
func (*Digest) Sum64() uint64
|
||||||
```
|
```
|
||||||
|
|
||||||
This implementation provides a fast pure-Go implementation and an even faster
|
The package is written with optimized pure Go and also contains even faster
|
||||||
assembly implementation for amd64.
|
assembly implementations for amd64 and arm64. If desired, the `purego` build tag
|
||||||
|
opts into using the Go code even on those architectures.
|
||||||
|
|
||||||
|
[xxHash]: http://cyan4973.github.io/xxHash/
|
||||||
|
|
||||||
## Compatibility
|
## Compatibility
|
||||||
|
|
||||||
|
@ -46,18 +48,19 @@ Here are some quick benchmarks comparing the pure-Go and assembly
|
||||||
implementations of Sum64.
|
implementations of Sum64.
|
||||||
|
|
||||||
| input size | purego | asm |
|
| input size | purego | asm |
|
||||||
| --- | --- | --- |
|
| ---------- | --------- | --------- |
|
||||||
| 5 B | 979.66 MB/s | 1291.17 MB/s |
|
| 4 B | 1.3 GB/s | 1.2 GB/s |
|
||||||
| 100 B | 7475.26 MB/s | 7973.40 MB/s |
|
| 16 B | 2.9 GB/s | 3.5 GB/s |
|
||||||
| 4 KB | 17573.46 MB/s | 17602.65 MB/s |
|
| 100 B | 6.9 GB/s | 8.1 GB/s |
|
||||||
| 10 MB | 17131.46 MB/s | 17142.16 MB/s |
|
| 4 KB | 11.7 GB/s | 16.7 GB/s |
|
||||||
|
| 10 MB | 12.0 GB/s | 17.3 GB/s |
|
||||||
|
|
||||||
These numbers were generated on Ubuntu 18.04 with an Intel i7-8700K CPU using
|
These numbers were generated on Ubuntu 20.04 with an Intel Xeon Platinum 8252C
|
||||||
the following commands under Go 1.11.2:
|
CPU using the following commands under Go 1.19.2:
|
||||||
|
|
||||||
```
|
```
|
||||||
$ go test -tags purego -benchtime 10s -bench '/xxhash,direct,bytes'
|
benchstat <(go test -tags purego -benchtime 500ms -count 15 -bench 'Sum64$')
|
||||||
$ go test -benchtime 10s -bench '/xxhash,direct,bytes'
|
benchstat <(go test -benchtime 500ms -count 15 -bench 'Sum64$')
|
||||||
```
|
```
|
||||||
|
|
||||||
## Projects using this package
|
## Projects using this package
|
||||||
|
|
|
@ -0,0 +1,10 @@
|
||||||
|
#!/bin/bash
|
||||||
|
set -eu -o pipefail
|
||||||
|
|
||||||
|
# Small convenience script for running the tests with various combinations of
|
||||||
|
# arch/tags. This assumes we're running on amd64 and have qemu available.
|
||||||
|
|
||||||
|
go test ./...
|
||||||
|
go test -tags purego ./...
|
||||||
|
GOARCH=arm64 go test
|
||||||
|
GOARCH=arm64 go test -tags purego
|
|
@ -16,19 +16,11 @@ const (
|
||||||
prime5 uint64 = 2870177450012600261
|
prime5 uint64 = 2870177450012600261
|
||||||
)
|
)
|
||||||
|
|
||||||
// NOTE(caleb): I'm using both consts and vars of the primes. Using consts where
|
// Store the primes in an array as well.
|
||||||
// possible in the Go code is worth a small (but measurable) performance boost
|
//
|
||||||
// by avoiding some MOVQs. Vars are needed for the asm and also are useful for
|
// The consts are used when possible in Go code to avoid MOVs but we need a
|
||||||
// convenience in the Go code in a few places where we need to intentionally
|
// contiguous array of the assembly code.
|
||||||
// avoid constant arithmetic (e.g., v1 := prime1 + prime2 fails because the
|
var primes = [...]uint64{prime1, prime2, prime3, prime4, prime5}
|
||||||
// result overflows a uint64).
|
|
||||||
var (
|
|
||||||
prime1v = prime1
|
|
||||||
prime2v = prime2
|
|
||||||
prime3v = prime3
|
|
||||||
prime4v = prime4
|
|
||||||
prime5v = prime5
|
|
||||||
)
|
|
||||||
|
|
||||||
// Digest implements hash.Hash64.
|
// Digest implements hash.Hash64.
|
||||||
type Digest struct {
|
type Digest struct {
|
||||||
|
@ -50,10 +42,10 @@ func New() *Digest {
|
||||||
|
|
||||||
// Reset clears the Digest's state so that it can be reused.
|
// Reset clears the Digest's state so that it can be reused.
|
||||||
func (d *Digest) Reset() {
|
func (d *Digest) Reset() {
|
||||||
d.v1 = prime1v + prime2
|
d.v1 = primes[0] + prime2
|
||||||
d.v2 = prime2
|
d.v2 = prime2
|
||||||
d.v3 = 0
|
d.v3 = 0
|
||||||
d.v4 = -prime1v
|
d.v4 = -primes[0]
|
||||||
d.total = 0
|
d.total = 0
|
||||||
d.n = 0
|
d.n = 0
|
||||||
}
|
}
|
||||||
|
@ -69,21 +61,23 @@ func (d *Digest) Write(b []byte) (n int, err error) {
|
||||||
n = len(b)
|
n = len(b)
|
||||||
d.total += uint64(n)
|
d.total += uint64(n)
|
||||||
|
|
||||||
|
memleft := d.mem[d.n&(len(d.mem)-1):]
|
||||||
|
|
||||||
if d.n+n < 32 {
|
if d.n+n < 32 {
|
||||||
// This new data doesn't even fill the current block.
|
// This new data doesn't even fill the current block.
|
||||||
copy(d.mem[d.n:], b)
|
copy(memleft, b)
|
||||||
d.n += n
|
d.n += n
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if d.n > 0 {
|
if d.n > 0 {
|
||||||
// Finish off the partial block.
|
// Finish off the partial block.
|
||||||
copy(d.mem[d.n:], b)
|
c := copy(memleft, b)
|
||||||
d.v1 = round(d.v1, u64(d.mem[0:8]))
|
d.v1 = round(d.v1, u64(d.mem[0:8]))
|
||||||
d.v2 = round(d.v2, u64(d.mem[8:16]))
|
d.v2 = round(d.v2, u64(d.mem[8:16]))
|
||||||
d.v3 = round(d.v3, u64(d.mem[16:24]))
|
d.v3 = round(d.v3, u64(d.mem[16:24]))
|
||||||
d.v4 = round(d.v4, u64(d.mem[24:32]))
|
d.v4 = round(d.v4, u64(d.mem[24:32]))
|
||||||
b = b[32-d.n:]
|
b = b[c:]
|
||||||
d.n = 0
|
d.n = 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -133,21 +127,20 @@ func (d *Digest) Sum64() uint64 {
|
||||||
|
|
||||||
h += d.total
|
h += d.total
|
||||||
|
|
||||||
i, end := 0, d.n
|
b := d.mem[:d.n&(len(d.mem)-1)]
|
||||||
for ; i+8 <= end; i += 8 {
|
for ; len(b) >= 8; b = b[8:] {
|
||||||
k1 := round(0, u64(d.mem[i:i+8]))
|
k1 := round(0, u64(b[:8]))
|
||||||
h ^= k1
|
h ^= k1
|
||||||
h = rol27(h)*prime1 + prime4
|
h = rol27(h)*prime1 + prime4
|
||||||
}
|
}
|
||||||
if i+4 <= end {
|
if len(b) >= 4 {
|
||||||
h ^= uint64(u32(d.mem[i:i+4])) * prime1
|
h ^= uint64(u32(b[:4])) * prime1
|
||||||
h = rol23(h)*prime2 + prime3
|
h = rol23(h)*prime2 + prime3
|
||||||
i += 4
|
b = b[4:]
|
||||||
}
|
}
|
||||||
for i < end {
|
for ; len(b) > 0; b = b[1:] {
|
||||||
h ^= uint64(d.mem[i]) * prime5
|
h ^= uint64(b[0]) * prime5
|
||||||
h = rol11(h) * prime1
|
h = rol11(h) * prime1
|
||||||
i++
|
|
||||||
}
|
}
|
||||||
|
|
||||||
h ^= h >> 33
|
h ^= h >> 33
|
||||||
|
|
|
@ -1,215 +1,209 @@
|
||||||
|
//go:build !appengine && gc && !purego
|
||||||
// +build !appengine
|
// +build !appengine
|
||||||
// +build gc
|
// +build gc
|
||||||
// +build !purego
|
// +build !purego
|
||||||
|
|
||||||
#include "textflag.h"
|
#include "textflag.h"
|
||||||
|
|
||||||
// Register allocation:
|
// Registers:
|
||||||
// AX h
|
#define h AX
|
||||||
// SI pointer to advance through b
|
#define d AX
|
||||||
// DX n
|
#define p SI // pointer to advance through b
|
||||||
// BX loop end
|
#define n DX
|
||||||
// R8 v1, k1
|
#define end BX // loop end
|
||||||
// R9 v2
|
#define v1 R8
|
||||||
// R10 v3
|
#define v2 R9
|
||||||
// R11 v4
|
#define v3 R10
|
||||||
// R12 tmp
|
#define v4 R11
|
||||||
// R13 prime1v
|
#define x R12
|
||||||
// R14 prime2v
|
#define prime1 R13
|
||||||
// DI prime4v
|
#define prime2 R14
|
||||||
|
#define prime4 DI
|
||||||
|
|
||||||
// round reads from and advances the buffer pointer in SI.
|
#define round(acc, x) \
|
||||||
// It assumes that R13 has prime1v and R14 has prime2v.
|
IMULQ prime2, x \
|
||||||
#define round(r) \
|
ADDQ x, acc \
|
||||||
MOVQ (SI), R12 \
|
ROLQ $31, acc \
|
||||||
ADDQ $8, SI \
|
IMULQ prime1, acc
|
||||||
IMULQ R14, R12 \
|
|
||||||
ADDQ R12, r \
|
|
||||||
ROLQ $31, r \
|
|
||||||
IMULQ R13, r
|
|
||||||
|
|
||||||
// mergeRound applies a merge round on the two registers acc and val.
|
// round0 performs the operation x = round(0, x).
|
||||||
// It assumes that R13 has prime1v, R14 has prime2v, and DI has prime4v.
|
#define round0(x) \
|
||||||
#define mergeRound(acc, val) \
|
IMULQ prime2, x \
|
||||||
IMULQ R14, val \
|
ROLQ $31, x \
|
||||||
ROLQ $31, val \
|
IMULQ prime1, x
|
||||||
IMULQ R13, val \
|
|
||||||
XORQ val, acc \
|
// mergeRound applies a merge round on the two registers acc and x.
|
||||||
IMULQ R13, acc \
|
// It assumes that prime1, prime2, and prime4 have been loaded.
|
||||||
ADDQ DI, acc
|
#define mergeRound(acc, x) \
|
||||||
|
round0(x) \
|
||||||
|
XORQ x, acc \
|
||||||
|
IMULQ prime1, acc \
|
||||||
|
ADDQ prime4, acc
|
||||||
|
|
||||||
|
// blockLoop processes as many 32-byte blocks as possible,
|
||||||
|
// updating v1, v2, v3, and v4. It assumes that there is at least one block
|
||||||
|
// to process.
|
||||||
|
#define blockLoop() \
|
||||||
|
loop: \
|
||||||
|
MOVQ +0(p), x \
|
||||||
|
round(v1, x) \
|
||||||
|
MOVQ +8(p), x \
|
||||||
|
round(v2, x) \
|
||||||
|
MOVQ +16(p), x \
|
||||||
|
round(v3, x) \
|
||||||
|
MOVQ +24(p), x \
|
||||||
|
round(v4, x) \
|
||||||
|
ADDQ $32, p \
|
||||||
|
CMPQ p, end \
|
||||||
|
JLE loop
|
||||||
|
|
||||||
// func Sum64(b []byte) uint64
|
// func Sum64(b []byte) uint64
|
||||||
TEXT ·Sum64(SB), NOSPLIT, $0-32
|
TEXT ·Sum64(SB), NOSPLIT|NOFRAME, $0-32
|
||||||
// Load fixed primes.
|
// Load fixed primes.
|
||||||
MOVQ ·prime1v(SB), R13
|
MOVQ ·primes+0(SB), prime1
|
||||||
MOVQ ·prime2v(SB), R14
|
MOVQ ·primes+8(SB), prime2
|
||||||
MOVQ ·prime4v(SB), DI
|
MOVQ ·primes+24(SB), prime4
|
||||||
|
|
||||||
// Load slice.
|
// Load slice.
|
||||||
MOVQ b_base+0(FP), SI
|
MOVQ b_base+0(FP), p
|
||||||
MOVQ b_len+8(FP), DX
|
MOVQ b_len+8(FP), n
|
||||||
LEAQ (SI)(DX*1), BX
|
LEAQ (p)(n*1), end
|
||||||
|
|
||||||
// The first loop limit will be len(b)-32.
|
// The first loop limit will be len(b)-32.
|
||||||
SUBQ $32, BX
|
SUBQ $32, end
|
||||||
|
|
||||||
// Check whether we have at least one block.
|
// Check whether we have at least one block.
|
||||||
CMPQ DX, $32
|
CMPQ n, $32
|
||||||
JLT noBlocks
|
JLT noBlocks
|
||||||
|
|
||||||
// Set up initial state (v1, v2, v3, v4).
|
// Set up initial state (v1, v2, v3, v4).
|
||||||
MOVQ R13, R8
|
MOVQ prime1, v1
|
||||||
ADDQ R14, R8
|
ADDQ prime2, v1
|
||||||
MOVQ R14, R9
|
MOVQ prime2, v2
|
||||||
XORQ R10, R10
|
XORQ v3, v3
|
||||||
XORQ R11, R11
|
XORQ v4, v4
|
||||||
SUBQ R13, R11
|
SUBQ prime1, v4
|
||||||
|
|
||||||
// Loop until SI > BX.
|
blockLoop()
|
||||||
blockLoop:
|
|
||||||
round(R8)
|
|
||||||
round(R9)
|
|
||||||
round(R10)
|
|
||||||
round(R11)
|
|
||||||
|
|
||||||
CMPQ SI, BX
|
MOVQ v1, h
|
||||||
JLE blockLoop
|
ROLQ $1, h
|
||||||
|
MOVQ v2, x
|
||||||
|
ROLQ $7, x
|
||||||
|
ADDQ x, h
|
||||||
|
MOVQ v3, x
|
||||||
|
ROLQ $12, x
|
||||||
|
ADDQ x, h
|
||||||
|
MOVQ v4, x
|
||||||
|
ROLQ $18, x
|
||||||
|
ADDQ x, h
|
||||||
|
|
||||||
MOVQ R8, AX
|
mergeRound(h, v1)
|
||||||
ROLQ $1, AX
|
mergeRound(h, v2)
|
||||||
MOVQ R9, R12
|
mergeRound(h, v3)
|
||||||
ROLQ $7, R12
|
mergeRound(h, v4)
|
||||||
ADDQ R12, AX
|
|
||||||
MOVQ R10, R12
|
|
||||||
ROLQ $12, R12
|
|
||||||
ADDQ R12, AX
|
|
||||||
MOVQ R11, R12
|
|
||||||
ROLQ $18, R12
|
|
||||||
ADDQ R12, AX
|
|
||||||
|
|
||||||
mergeRound(AX, R8)
|
|
||||||
mergeRound(AX, R9)
|
|
||||||
mergeRound(AX, R10)
|
|
||||||
mergeRound(AX, R11)
|
|
||||||
|
|
||||||
JMP afterBlocks
|
JMP afterBlocks
|
||||||
|
|
||||||
noBlocks:
|
noBlocks:
|
||||||
MOVQ ·prime5v(SB), AX
|
MOVQ ·primes+32(SB), h
|
||||||
|
|
||||||
afterBlocks:
|
afterBlocks:
|
||||||
ADDQ DX, AX
|
ADDQ n, h
|
||||||
|
|
||||||
// Right now BX has len(b)-32, and we want to loop until SI > len(b)-8.
|
ADDQ $24, end
|
||||||
ADDQ $24, BX
|
CMPQ p, end
|
||||||
|
JG try4
|
||||||
|
|
||||||
CMPQ SI, BX
|
loop8:
|
||||||
JG fourByte
|
MOVQ (p), x
|
||||||
|
ADDQ $8, p
|
||||||
|
round0(x)
|
||||||
|
XORQ x, h
|
||||||
|
ROLQ $27, h
|
||||||
|
IMULQ prime1, h
|
||||||
|
ADDQ prime4, h
|
||||||
|
|
||||||
wordLoop:
|
CMPQ p, end
|
||||||
// Calculate k1.
|
JLE loop8
|
||||||
MOVQ (SI), R8
|
|
||||||
ADDQ $8, SI
|
|
||||||
IMULQ R14, R8
|
|
||||||
ROLQ $31, R8
|
|
||||||
IMULQ R13, R8
|
|
||||||
|
|
||||||
XORQ R8, AX
|
try4:
|
||||||
ROLQ $27, AX
|
ADDQ $4, end
|
||||||
IMULQ R13, AX
|
CMPQ p, end
|
||||||
ADDQ DI, AX
|
JG try1
|
||||||
|
|
||||||
CMPQ SI, BX
|
MOVL (p), x
|
||||||
JLE wordLoop
|
ADDQ $4, p
|
||||||
|
IMULQ prime1, x
|
||||||
|
XORQ x, h
|
||||||
|
|
||||||
fourByte:
|
ROLQ $23, h
|
||||||
ADDQ $4, BX
|
IMULQ prime2, h
|
||||||
CMPQ SI, BX
|
ADDQ ·primes+16(SB), h
|
||||||
JG singles
|
|
||||||
|
|
||||||
MOVL (SI), R8
|
try1:
|
||||||
ADDQ $4, SI
|
ADDQ $4, end
|
||||||
IMULQ R13, R8
|
CMPQ p, end
|
||||||
XORQ R8, AX
|
|
||||||
|
|
||||||
ROLQ $23, AX
|
|
||||||
IMULQ R14, AX
|
|
||||||
ADDQ ·prime3v(SB), AX
|
|
||||||
|
|
||||||
singles:
|
|
||||||
ADDQ $4, BX
|
|
||||||
CMPQ SI, BX
|
|
||||||
JGE finalize
|
JGE finalize
|
||||||
|
|
||||||
singlesLoop:
|
loop1:
|
||||||
MOVBQZX (SI), R12
|
MOVBQZX (p), x
|
||||||
ADDQ $1, SI
|
ADDQ $1, p
|
||||||
IMULQ ·prime5v(SB), R12
|
IMULQ ·primes+32(SB), x
|
||||||
XORQ R12, AX
|
XORQ x, h
|
||||||
|
ROLQ $11, h
|
||||||
|
IMULQ prime1, h
|
||||||
|
|
||||||
ROLQ $11, AX
|
CMPQ p, end
|
||||||
IMULQ R13, AX
|
JL loop1
|
||||||
|
|
||||||
CMPQ SI, BX
|
|
||||||
JL singlesLoop
|
|
||||||
|
|
||||||
finalize:
|
finalize:
|
||||||
MOVQ AX, R12
|
MOVQ h, x
|
||||||
SHRQ $33, R12
|
SHRQ $33, x
|
||||||
XORQ R12, AX
|
XORQ x, h
|
||||||
IMULQ R14, AX
|
IMULQ prime2, h
|
||||||
MOVQ AX, R12
|
MOVQ h, x
|
||||||
SHRQ $29, R12
|
SHRQ $29, x
|
||||||
XORQ R12, AX
|
XORQ x, h
|
||||||
IMULQ ·prime3v(SB), AX
|
IMULQ ·primes+16(SB), h
|
||||||
MOVQ AX, R12
|
MOVQ h, x
|
||||||
SHRQ $32, R12
|
SHRQ $32, x
|
||||||
XORQ R12, AX
|
XORQ x, h
|
||||||
|
|
||||||
MOVQ AX, ret+24(FP)
|
MOVQ h, ret+24(FP)
|
||||||
RET
|
RET
|
||||||
|
|
||||||
// writeBlocks uses the same registers as above except that it uses AX to store
|
|
||||||
// the d pointer.
|
|
||||||
|
|
||||||
// func writeBlocks(d *Digest, b []byte) int
|
// func writeBlocks(d *Digest, b []byte) int
|
||||||
TEXT ·writeBlocks(SB), NOSPLIT, $0-40
|
TEXT ·writeBlocks(SB), NOSPLIT|NOFRAME, $0-40
|
||||||
// Load fixed primes needed for round.
|
// Load fixed primes needed for round.
|
||||||
MOVQ ·prime1v(SB), R13
|
MOVQ ·primes+0(SB), prime1
|
||||||
MOVQ ·prime2v(SB), R14
|
MOVQ ·primes+8(SB), prime2
|
||||||
|
|
||||||
// Load slice.
|
// Load slice.
|
||||||
MOVQ b_base+8(FP), SI
|
MOVQ b_base+8(FP), p
|
||||||
MOVQ b_len+16(FP), DX
|
MOVQ b_len+16(FP), n
|
||||||
LEAQ (SI)(DX*1), BX
|
LEAQ (p)(n*1), end
|
||||||
SUBQ $32, BX
|
SUBQ $32, end
|
||||||
|
|
||||||
// Load vN from d.
|
// Load vN from d.
|
||||||
MOVQ d+0(FP), AX
|
MOVQ s+0(FP), d
|
||||||
MOVQ 0(AX), R8 // v1
|
MOVQ 0(d), v1
|
||||||
MOVQ 8(AX), R9 // v2
|
MOVQ 8(d), v2
|
||||||
MOVQ 16(AX), R10 // v3
|
MOVQ 16(d), v3
|
||||||
MOVQ 24(AX), R11 // v4
|
MOVQ 24(d), v4
|
||||||
|
|
||||||
// We don't need to check the loop condition here; this function is
|
// We don't need to check the loop condition here; this function is
|
||||||
// always called with at least one block of data to process.
|
// always called with at least one block of data to process.
|
||||||
blockLoop:
|
blockLoop()
|
||||||
round(R8)
|
|
||||||
round(R9)
|
|
||||||
round(R10)
|
|
||||||
round(R11)
|
|
||||||
|
|
||||||
CMPQ SI, BX
|
|
||||||
JLE blockLoop
|
|
||||||
|
|
||||||
// Copy vN back to d.
|
// Copy vN back to d.
|
||||||
MOVQ R8, 0(AX)
|
MOVQ v1, 0(d)
|
||||||
MOVQ R9, 8(AX)
|
MOVQ v2, 8(d)
|
||||||
MOVQ R10, 16(AX)
|
MOVQ v3, 16(d)
|
||||||
MOVQ R11, 24(AX)
|
MOVQ v4, 24(d)
|
||||||
|
|
||||||
// The number of bytes written is SI minus the old base pointer.
|
// The number of bytes written is p minus the old base pointer.
|
||||||
SUBQ b_base+8(FP), SI
|
SUBQ b_base+8(FP), p
|
||||||
MOVQ SI, ret+32(FP)
|
MOVQ p, ret+32(FP)
|
||||||
|
|
||||||
RET
|
RET
|
||||||
|
|
|
@ -0,0 +1,183 @@
|
||||||
|
//go:build !appengine && gc && !purego
|
||||||
|
// +build !appengine
|
||||||
|
// +build gc
|
||||||
|
// +build !purego
|
||||||
|
|
||||||
|
#include "textflag.h"
|
||||||
|
|
||||||
|
// Registers:
|
||||||
|
#define digest R1
|
||||||
|
#define h R2 // return value
|
||||||
|
#define p R3 // input pointer
|
||||||
|
#define n R4 // input length
|
||||||
|
#define nblocks R5 // n / 32
|
||||||
|
#define prime1 R7
|
||||||
|
#define prime2 R8
|
||||||
|
#define prime3 R9
|
||||||
|
#define prime4 R10
|
||||||
|
#define prime5 R11
|
||||||
|
#define v1 R12
|
||||||
|
#define v2 R13
|
||||||
|
#define v3 R14
|
||||||
|
#define v4 R15
|
||||||
|
#define x1 R20
|
||||||
|
#define x2 R21
|
||||||
|
#define x3 R22
|
||||||
|
#define x4 R23
|
||||||
|
|
||||||
|
#define round(acc, x) \
|
||||||
|
MADD prime2, acc, x, acc \
|
||||||
|
ROR $64-31, acc \
|
||||||
|
MUL prime1, acc
|
||||||
|
|
||||||
|
// round0 performs the operation x = round(0, x).
|
||||||
|
#define round0(x) \
|
||||||
|
MUL prime2, x \
|
||||||
|
ROR $64-31, x \
|
||||||
|
MUL prime1, x
|
||||||
|
|
||||||
|
#define mergeRound(acc, x) \
|
||||||
|
round0(x) \
|
||||||
|
EOR x, acc \
|
||||||
|
MADD acc, prime4, prime1, acc
|
||||||
|
|
||||||
|
// blockLoop processes as many 32-byte blocks as possible,
|
||||||
|
// updating v1, v2, v3, and v4. It assumes that n >= 32.
|
||||||
|
#define blockLoop() \
|
||||||
|
LSR $5, n, nblocks \
|
||||||
|
PCALIGN $16 \
|
||||||
|
loop: \
|
||||||
|
LDP.P 16(p), (x1, x2) \
|
||||||
|
LDP.P 16(p), (x3, x4) \
|
||||||
|
round(v1, x1) \
|
||||||
|
round(v2, x2) \
|
||||||
|
round(v3, x3) \
|
||||||
|
round(v4, x4) \
|
||||||
|
SUB $1, nblocks \
|
||||||
|
CBNZ nblocks, loop
|
||||||
|
|
||||||
|
// func Sum64(b []byte) uint64
|
||||||
|
TEXT ·Sum64(SB), NOSPLIT|NOFRAME, $0-32
|
||||||
|
LDP b_base+0(FP), (p, n)
|
||||||
|
|
||||||
|
LDP ·primes+0(SB), (prime1, prime2)
|
||||||
|
LDP ·primes+16(SB), (prime3, prime4)
|
||||||
|
MOVD ·primes+32(SB), prime5
|
||||||
|
|
||||||
|
CMP $32, n
|
||||||
|
CSEL LT, prime5, ZR, h // if n < 32 { h = prime5 } else { h = 0 }
|
||||||
|
BLT afterLoop
|
||||||
|
|
||||||
|
ADD prime1, prime2, v1
|
||||||
|
MOVD prime2, v2
|
||||||
|
MOVD $0, v3
|
||||||
|
NEG prime1, v4
|
||||||
|
|
||||||
|
blockLoop()
|
||||||
|
|
||||||
|
ROR $64-1, v1, x1
|
||||||
|
ROR $64-7, v2, x2
|
||||||
|
ADD x1, x2
|
||||||
|
ROR $64-12, v3, x3
|
||||||
|
ROR $64-18, v4, x4
|
||||||
|
ADD x3, x4
|
||||||
|
ADD x2, x4, h
|
||||||
|
|
||||||
|
mergeRound(h, v1)
|
||||||
|
mergeRound(h, v2)
|
||||||
|
mergeRound(h, v3)
|
||||||
|
mergeRound(h, v4)
|
||||||
|
|
||||||
|
afterLoop:
|
||||||
|
ADD n, h
|
||||||
|
|
||||||
|
TBZ $4, n, try8
|
||||||
|
LDP.P 16(p), (x1, x2)
|
||||||
|
|
||||||
|
round0(x1)
|
||||||
|
|
||||||
|
// NOTE: here and below, sequencing the EOR after the ROR (using a
|
||||||
|
// rotated register) is worth a small but measurable speedup for small
|
||||||
|
// inputs.
|
||||||
|
ROR $64-27, h
|
||||||
|
EOR x1 @> 64-27, h, h
|
||||||
|
MADD h, prime4, prime1, h
|
||||||
|
|
||||||
|
round0(x2)
|
||||||
|
ROR $64-27, h
|
||||||
|
EOR x2 @> 64-27, h, h
|
||||||
|
MADD h, prime4, prime1, h
|
||||||
|
|
||||||
|
try8:
|
||||||
|
TBZ $3, n, try4
|
||||||
|
MOVD.P 8(p), x1
|
||||||
|
|
||||||
|
round0(x1)
|
||||||
|
ROR $64-27, h
|
||||||
|
EOR x1 @> 64-27, h, h
|
||||||
|
MADD h, prime4, prime1, h
|
||||||
|
|
||||||
|
try4:
|
||||||
|
TBZ $2, n, try2
|
||||||
|
MOVWU.P 4(p), x2
|
||||||
|
|
||||||
|
MUL prime1, x2
|
||||||
|
ROR $64-23, h
|
||||||
|
EOR x2 @> 64-23, h, h
|
||||||
|
MADD h, prime3, prime2, h
|
||||||
|
|
||||||
|
try2:
|
||||||
|
TBZ $1, n, try1
|
||||||
|
MOVHU.P 2(p), x3
|
||||||
|
AND $255, x3, x1
|
||||||
|
LSR $8, x3, x2
|
||||||
|
|
||||||
|
MUL prime5, x1
|
||||||
|
ROR $64-11, h
|
||||||
|
EOR x1 @> 64-11, h, h
|
||||||
|
MUL prime1, h
|
||||||
|
|
||||||
|
MUL prime5, x2
|
||||||
|
ROR $64-11, h
|
||||||
|
EOR x2 @> 64-11, h, h
|
||||||
|
MUL prime1, h
|
||||||
|
|
||||||
|
try1:
|
||||||
|
TBZ $0, n, finalize
|
||||||
|
MOVBU (p), x4
|
||||||
|
|
||||||
|
MUL prime5, x4
|
||||||
|
ROR $64-11, h
|
||||||
|
EOR x4 @> 64-11, h, h
|
||||||
|
MUL prime1, h
|
||||||
|
|
||||||
|
finalize:
|
||||||
|
EOR h >> 33, h
|
||||||
|
MUL prime2, h
|
||||||
|
EOR h >> 29, h
|
||||||
|
MUL prime3, h
|
||||||
|
EOR h >> 32, h
|
||||||
|
|
||||||
|
MOVD h, ret+24(FP)
|
||||||
|
RET
|
||||||
|
|
||||||
|
// func writeBlocks(d *Digest, b []byte) int
|
||||||
|
TEXT ·writeBlocks(SB), NOSPLIT|NOFRAME, $0-40
|
||||||
|
LDP ·primes+0(SB), (prime1, prime2)
|
||||||
|
|
||||||
|
// Load state. Assume v[1-4] are stored contiguously.
|
||||||
|
MOVD d+0(FP), digest
|
||||||
|
LDP 0(digest), (v1, v2)
|
||||||
|
LDP 16(digest), (v3, v4)
|
||||||
|
|
||||||
|
LDP b_base+8(FP), (p, n)
|
||||||
|
|
||||||
|
blockLoop()
|
||||||
|
|
||||||
|
// Store updated state.
|
||||||
|
STP (v1, v2), 0(digest)
|
||||||
|
STP (v3, v4), 16(digest)
|
||||||
|
|
||||||
|
BIC $31, n
|
||||||
|
MOVD n, ret+32(FP)
|
||||||
|
RET
|
|
@ -1,3 +1,5 @@
|
||||||
|
//go:build (amd64 || arm64) && !appengine && gc && !purego
|
||||||
|
// +build amd64 arm64
|
||||||
// +build !appengine
|
// +build !appengine
|
||||||
// +build gc
|
// +build gc
|
||||||
// +build !purego
|
// +build !purego
|
|
@ -1,4 +1,5 @@
|
||||||
// +build !amd64 appengine !gc purego
|
//go:build (!amd64 && !arm64) || appengine || !gc || purego
|
||||||
|
// +build !amd64,!arm64 appengine !gc purego
|
||||||
|
|
||||||
package xxhash
|
package xxhash
|
||||||
|
|
||||||
|
@ -14,10 +15,10 @@ func Sum64(b []byte) uint64 {
|
||||||
var h uint64
|
var h uint64
|
||||||
|
|
||||||
if n >= 32 {
|
if n >= 32 {
|
||||||
v1 := prime1v + prime2
|
v1 := primes[0] + prime2
|
||||||
v2 := prime2
|
v2 := prime2
|
||||||
v3 := uint64(0)
|
v3 := uint64(0)
|
||||||
v4 := -prime1v
|
v4 := -primes[0]
|
||||||
for len(b) >= 32 {
|
for len(b) >= 32 {
|
||||||
v1 = round(v1, u64(b[0:8:len(b)]))
|
v1 = round(v1, u64(b[0:8:len(b)]))
|
||||||
v2 = round(v2, u64(b[8:16:len(b)]))
|
v2 = round(v2, u64(b[8:16:len(b)]))
|
||||||
|
@ -36,19 +37,18 @@ func Sum64(b []byte) uint64 {
|
||||||
|
|
||||||
h += uint64(n)
|
h += uint64(n)
|
||||||
|
|
||||||
i, end := 0, len(b)
|
for ; len(b) >= 8; b = b[8:] {
|
||||||
for ; i+8 <= end; i += 8 {
|
k1 := round(0, u64(b[:8]))
|
||||||
k1 := round(0, u64(b[i:i+8:len(b)]))
|
|
||||||
h ^= k1
|
h ^= k1
|
||||||
h = rol27(h)*prime1 + prime4
|
h = rol27(h)*prime1 + prime4
|
||||||
}
|
}
|
||||||
if i+4 <= end {
|
if len(b) >= 4 {
|
||||||
h ^= uint64(u32(b[i:i+4:len(b)])) * prime1
|
h ^= uint64(u32(b[:4])) * prime1
|
||||||
h = rol23(h)*prime2 + prime3
|
h = rol23(h)*prime2 + prime3
|
||||||
i += 4
|
b = b[4:]
|
||||||
}
|
}
|
||||||
for ; i < end; i++ {
|
for ; len(b) > 0; b = b[1:] {
|
||||||
h ^= uint64(b[i]) * prime5
|
h ^= uint64(b[0]) * prime5
|
||||||
h = rol11(h) * prime1
|
h = rol11(h) * prime1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
//go:build appengine
|
||||||
// +build appengine
|
// +build appengine
|
||||||
|
|
||||||
// This file contains the safe implementations of otherwise unsafe-using code.
|
// This file contains the safe implementations of otherwise unsafe-using code.
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
//go:build !appengine
|
||||||
// +build !appengine
|
// +build !appengine
|
||||||
|
|
||||||
// This file encapsulates usage of unsafe.
|
// This file encapsulates usage of unsafe.
|
||||||
|
@ -11,7 +12,7 @@ import (
|
||||||
|
|
||||||
// In the future it's possible that compiler optimizations will make these
|
// In the future it's possible that compiler optimizations will make these
|
||||||
// XxxString functions unnecessary by realizing that calls such as
|
// XxxString functions unnecessary by realizing that calls such as
|
||||||
// Sum64([]byte(s)) don't need to copy s. See https://golang.org/issue/2205.
|
// Sum64([]byte(s)) don't need to copy s. See https://go.dev/issue/2205.
|
||||||
// If that happens, even if we keep these functions they can be replaced with
|
// If that happens, even if we keep these functions they can be replaced with
|
||||||
// the trivial safe code.
|
// the trivial safe code.
|
||||||
|
|
||||||
|
|
|
@ -22,11 +22,11 @@ func (k *Key) clamp(in *Key) *Key {
|
||||||
// isValidPubKey verifies if the public key is not a low-order point.
|
// isValidPubKey verifies if the public key is not a low-order point.
|
||||||
func (k *Key) isValidPubKey() bool {
|
func (k *Key) isValidPubKey() bool {
|
||||||
fp.Modp((*fp.Elt)(k))
|
fp.Modp((*fp.Elt)(k))
|
||||||
isLowOrder := false
|
var isLowOrder int
|
||||||
for _, P := range lowOrderPoints {
|
for _, P := range lowOrderPoints {
|
||||||
isLowOrder = isLowOrder || subtle.ConstantTimeCompare(P[:], k[:]) != 0
|
isLowOrder |= subtle.ConstantTimeCompare(P[:], k[:])
|
||||||
}
|
}
|
||||||
return !isLowOrder
|
return isLowOrder == 0
|
||||||
}
|
}
|
||||||
|
|
||||||
// KeyGen obtains a public key given a secret key.
|
// KeyGen obtains a public key given a secret key.
|
||||||
|
|
|
@ -22,11 +22,11 @@ func (k *Key) clamp(in *Key) *Key {
|
||||||
// isValidPubKey verifies if the public key is not a low-order point.
|
// isValidPubKey verifies if the public key is not a low-order point.
|
||||||
func (k *Key) isValidPubKey() bool {
|
func (k *Key) isValidPubKey() bool {
|
||||||
fp.Modp((*fp.Elt)(k))
|
fp.Modp((*fp.Elt)(k))
|
||||||
isLowOrder := false
|
var isLowOrder int
|
||||||
for _, P := range lowOrderPoints {
|
for _, P := range lowOrderPoints {
|
||||||
isLowOrder = isLowOrder || subtle.ConstantTimeCompare(P[:], k[:]) != 0
|
isLowOrder |= subtle.ConstantTimeCompare(P[:], k[:])
|
||||||
}
|
}
|
||||||
return !isLowOrder
|
return isLowOrder == 0
|
||||||
}
|
}
|
||||||
|
|
||||||
// KeyGen obtains a public key given a secret key.
|
// KeyGen obtains a public key given a secret key.
|
||||||
|
|
|
@ -2,13 +2,11 @@
|
||||||
// Use of this source code is governed by a BSD-style
|
// Use of this source code is governed by a BSD-style
|
||||||
// license that can be found in the LICENSE file.
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
//go:build !amd64 || appengine || gccgo
|
|
||||||
// +build !amd64 appengine gccgo
|
|
||||||
|
|
||||||
package sha3
|
package sha3
|
||||||
|
|
||||||
// KeccakF1600 applies the Keccak permutation to a 1600b-wide
|
// KeccakF1600 applies the Keccak permutation to a 1600b-wide
|
||||||
// state represented as a slice of 25 uint64s.
|
// state represented as a slice of 25 uint64s.
|
||||||
|
// nolint:funlen
|
||||||
func KeccakF1600(a *[25]uint64) {
|
func KeccakF1600(a *[25]uint64) {
|
||||||
// Implementation translated from Keccak-inplace.c
|
// Implementation translated from Keccak-inplace.c
|
||||||
// in the keccak reference code.
|
// in the keccak reference code.
|
||||||
|
|
|
@ -1,14 +0,0 @@
|
||||||
// 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.
|
|
||||||
|
|
||||||
//go:build amd64 && !appengine && !gccgo
|
|
||||||
// +build amd64,!appengine,!gccgo
|
|
||||||
|
|
||||||
package sha3
|
|
||||||
|
|
||||||
// This function is implemented in keccakf_amd64.s.
|
|
||||||
|
|
||||||
//go:noescape
|
|
||||||
|
|
||||||
func KeccakF1600(state *[25]uint64)
|
|
|
@ -1,390 +0,0 @@
|
||||||
// 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 amd64,!appengine,!gccgo
|
|
||||||
|
|
||||||
// This code was translated into a form compatible with 6a from the public
|
|
||||||
// domain sources at https://github.com/gvanas/KeccakCodePackage
|
|
||||||
|
|
||||||
// Offsets in state
|
|
||||||
#define _ba (0*8)
|
|
||||||
#define _be (1*8)
|
|
||||||
#define _bi (2*8)
|
|
||||||
#define _bo (3*8)
|
|
||||||
#define _bu (4*8)
|
|
||||||
#define _ga (5*8)
|
|
||||||
#define _ge (6*8)
|
|
||||||
#define _gi (7*8)
|
|
||||||
#define _go (8*8)
|
|
||||||
#define _gu (9*8)
|
|
||||||
#define _ka (10*8)
|
|
||||||
#define _ke (11*8)
|
|
||||||
#define _ki (12*8)
|
|
||||||
#define _ko (13*8)
|
|
||||||
#define _ku (14*8)
|
|
||||||
#define _ma (15*8)
|
|
||||||
#define _me (16*8)
|
|
||||||
#define _mi (17*8)
|
|
||||||
#define _mo (18*8)
|
|
||||||
#define _mu (19*8)
|
|
||||||
#define _sa (20*8)
|
|
||||||
#define _se (21*8)
|
|
||||||
#define _si (22*8)
|
|
||||||
#define _so (23*8)
|
|
||||||
#define _su (24*8)
|
|
||||||
|
|
||||||
// Temporary registers
|
|
||||||
#define rT1 AX
|
|
||||||
|
|
||||||
// Round vars
|
|
||||||
#define rpState DI
|
|
||||||
#define rpStack SP
|
|
||||||
|
|
||||||
#define rDa BX
|
|
||||||
#define rDe CX
|
|
||||||
#define rDi DX
|
|
||||||
#define rDo R8
|
|
||||||
#define rDu R9
|
|
||||||
|
|
||||||
#define rBa R10
|
|
||||||
#define rBe R11
|
|
||||||
#define rBi R12
|
|
||||||
#define rBo R13
|
|
||||||
#define rBu R14
|
|
||||||
|
|
||||||
#define rCa SI
|
|
||||||
#define rCe BP
|
|
||||||
#define rCi rBi
|
|
||||||
#define rCo rBo
|
|
||||||
#define rCu R15
|
|
||||||
|
|
||||||
#define MOVQ_RBI_RCE MOVQ rBi, rCe
|
|
||||||
#define XORQ_RT1_RCA XORQ rT1, rCa
|
|
||||||
#define XORQ_RT1_RCE XORQ rT1, rCe
|
|
||||||
#define XORQ_RBA_RCU XORQ rBa, rCu
|
|
||||||
#define XORQ_RBE_RCU XORQ rBe, rCu
|
|
||||||
#define XORQ_RDU_RCU XORQ rDu, rCu
|
|
||||||
#define XORQ_RDA_RCA XORQ rDa, rCa
|
|
||||||
#define XORQ_RDE_RCE XORQ rDe, rCe
|
|
||||||
|
|
||||||
#define mKeccakRound(iState, oState, rc, B_RBI_RCE, G_RT1_RCA, G_RT1_RCE, G_RBA_RCU, K_RT1_RCA, K_RT1_RCE, K_RBA_RCU, M_RT1_RCA, M_RT1_RCE, M_RBE_RCU, S_RDU_RCU, S_RDA_RCA, S_RDE_RCE) \
|
|
||||||
/* Prepare round */ \
|
|
||||||
MOVQ rCe, rDa; \
|
|
||||||
ROLQ $1, rDa; \
|
|
||||||
\
|
|
||||||
MOVQ _bi(iState), rCi; \
|
|
||||||
XORQ _gi(iState), rDi; \
|
|
||||||
XORQ rCu, rDa; \
|
|
||||||
XORQ _ki(iState), rCi; \
|
|
||||||
XORQ _mi(iState), rDi; \
|
|
||||||
XORQ rDi, rCi; \
|
|
||||||
\
|
|
||||||
MOVQ rCi, rDe; \
|
|
||||||
ROLQ $1, rDe; \
|
|
||||||
\
|
|
||||||
MOVQ _bo(iState), rCo; \
|
|
||||||
XORQ _go(iState), rDo; \
|
|
||||||
XORQ rCa, rDe; \
|
|
||||||
XORQ _ko(iState), rCo; \
|
|
||||||
XORQ _mo(iState), rDo; \
|
|
||||||
XORQ rDo, rCo; \
|
|
||||||
\
|
|
||||||
MOVQ rCo, rDi; \
|
|
||||||
ROLQ $1, rDi; \
|
|
||||||
\
|
|
||||||
MOVQ rCu, rDo; \
|
|
||||||
XORQ rCe, rDi; \
|
|
||||||
ROLQ $1, rDo; \
|
|
||||||
\
|
|
||||||
MOVQ rCa, rDu; \
|
|
||||||
XORQ rCi, rDo; \
|
|
||||||
ROLQ $1, rDu; \
|
|
||||||
\
|
|
||||||
/* Result b */ \
|
|
||||||
MOVQ _ba(iState), rBa; \
|
|
||||||
MOVQ _ge(iState), rBe; \
|
|
||||||
XORQ rCo, rDu; \
|
|
||||||
MOVQ _ki(iState), rBi; \
|
|
||||||
MOVQ _mo(iState), rBo; \
|
|
||||||
MOVQ _su(iState), rBu; \
|
|
||||||
XORQ rDe, rBe; \
|
|
||||||
ROLQ $44, rBe; \
|
|
||||||
XORQ rDi, rBi; \
|
|
||||||
XORQ rDa, rBa; \
|
|
||||||
ROLQ $43, rBi; \
|
|
||||||
\
|
|
||||||
MOVQ rBe, rCa; \
|
|
||||||
MOVQ rc, rT1; \
|
|
||||||
ORQ rBi, rCa; \
|
|
||||||
XORQ rBa, rT1; \
|
|
||||||
XORQ rT1, rCa; \
|
|
||||||
MOVQ rCa, _ba(oState); \
|
|
||||||
\
|
|
||||||
XORQ rDu, rBu; \
|
|
||||||
ROLQ $14, rBu; \
|
|
||||||
MOVQ rBa, rCu; \
|
|
||||||
ANDQ rBe, rCu; \
|
|
||||||
XORQ rBu, rCu; \
|
|
||||||
MOVQ rCu, _bu(oState); \
|
|
||||||
\
|
|
||||||
XORQ rDo, rBo; \
|
|
||||||
ROLQ $21, rBo; \
|
|
||||||
MOVQ rBo, rT1; \
|
|
||||||
ANDQ rBu, rT1; \
|
|
||||||
XORQ rBi, rT1; \
|
|
||||||
MOVQ rT1, _bi(oState); \
|
|
||||||
\
|
|
||||||
NOTQ rBi; \
|
|
||||||
ORQ rBa, rBu; \
|
|
||||||
ORQ rBo, rBi; \
|
|
||||||
XORQ rBo, rBu; \
|
|
||||||
XORQ rBe, rBi; \
|
|
||||||
MOVQ rBu, _bo(oState); \
|
|
||||||
MOVQ rBi, _be(oState); \
|
|
||||||
B_RBI_RCE; \
|
|
||||||
\
|
|
||||||
/* Result g */ \
|
|
||||||
MOVQ _gu(iState), rBe; \
|
|
||||||
XORQ rDu, rBe; \
|
|
||||||
MOVQ _ka(iState), rBi; \
|
|
||||||
ROLQ $20, rBe; \
|
|
||||||
XORQ rDa, rBi; \
|
|
||||||
ROLQ $3, rBi; \
|
|
||||||
MOVQ _bo(iState), rBa; \
|
|
||||||
MOVQ rBe, rT1; \
|
|
||||||
ORQ rBi, rT1; \
|
|
||||||
XORQ rDo, rBa; \
|
|
||||||
MOVQ _me(iState), rBo; \
|
|
||||||
MOVQ _si(iState), rBu; \
|
|
||||||
ROLQ $28, rBa; \
|
|
||||||
XORQ rBa, rT1; \
|
|
||||||
MOVQ rT1, _ga(oState); \
|
|
||||||
G_RT1_RCA; \
|
|
||||||
\
|
|
||||||
XORQ rDe, rBo; \
|
|
||||||
ROLQ $45, rBo; \
|
|
||||||
MOVQ rBi, rT1; \
|
|
||||||
ANDQ rBo, rT1; \
|
|
||||||
XORQ rBe, rT1; \
|
|
||||||
MOVQ rT1, _ge(oState); \
|
|
||||||
G_RT1_RCE; \
|
|
||||||
\
|
|
||||||
XORQ rDi, rBu; \
|
|
||||||
ROLQ $61, rBu; \
|
|
||||||
MOVQ rBu, rT1; \
|
|
||||||
ORQ rBa, rT1; \
|
|
||||||
XORQ rBo, rT1; \
|
|
||||||
MOVQ rT1, _go(oState); \
|
|
||||||
\
|
|
||||||
ANDQ rBe, rBa; \
|
|
||||||
XORQ rBu, rBa; \
|
|
||||||
MOVQ rBa, _gu(oState); \
|
|
||||||
NOTQ rBu; \
|
|
||||||
G_RBA_RCU; \
|
|
||||||
\
|
|
||||||
ORQ rBu, rBo; \
|
|
||||||
XORQ rBi, rBo; \
|
|
||||||
MOVQ rBo, _gi(oState); \
|
|
||||||
\
|
|
||||||
/* Result k */ \
|
|
||||||
MOVQ _be(iState), rBa; \
|
|
||||||
MOVQ _gi(iState), rBe; \
|
|
||||||
MOVQ _ko(iState), rBi; \
|
|
||||||
MOVQ _mu(iState), rBo; \
|
|
||||||
MOVQ _sa(iState), rBu; \
|
|
||||||
XORQ rDi, rBe; \
|
|
||||||
ROLQ $6, rBe; \
|
|
||||||
XORQ rDo, rBi; \
|
|
||||||
ROLQ $25, rBi; \
|
|
||||||
MOVQ rBe, rT1; \
|
|
||||||
ORQ rBi, rT1; \
|
|
||||||
XORQ rDe, rBa; \
|
|
||||||
ROLQ $1, rBa; \
|
|
||||||
XORQ rBa, rT1; \
|
|
||||||
MOVQ rT1, _ka(oState); \
|
|
||||||
K_RT1_RCA; \
|
|
||||||
\
|
|
||||||
XORQ rDu, rBo; \
|
|
||||||
ROLQ $8, rBo; \
|
|
||||||
MOVQ rBi, rT1; \
|
|
||||||
ANDQ rBo, rT1; \
|
|
||||||
XORQ rBe, rT1; \
|
|
||||||
MOVQ rT1, _ke(oState); \
|
|
||||||
K_RT1_RCE; \
|
|
||||||
\
|
|
||||||
XORQ rDa, rBu; \
|
|
||||||
ROLQ $18, rBu; \
|
|
||||||
NOTQ rBo; \
|
|
||||||
MOVQ rBo, rT1; \
|
|
||||||
ANDQ rBu, rT1; \
|
|
||||||
XORQ rBi, rT1; \
|
|
||||||
MOVQ rT1, _ki(oState); \
|
|
||||||
\
|
|
||||||
MOVQ rBu, rT1; \
|
|
||||||
ORQ rBa, rT1; \
|
|
||||||
XORQ rBo, rT1; \
|
|
||||||
MOVQ rT1, _ko(oState); \
|
|
||||||
\
|
|
||||||
ANDQ rBe, rBa; \
|
|
||||||
XORQ rBu, rBa; \
|
|
||||||
MOVQ rBa, _ku(oState); \
|
|
||||||
K_RBA_RCU; \
|
|
||||||
\
|
|
||||||
/* Result m */ \
|
|
||||||
MOVQ _ga(iState), rBe; \
|
|
||||||
XORQ rDa, rBe; \
|
|
||||||
MOVQ _ke(iState), rBi; \
|
|
||||||
ROLQ $36, rBe; \
|
|
||||||
XORQ rDe, rBi; \
|
|
||||||
MOVQ _bu(iState), rBa; \
|
|
||||||
ROLQ $10, rBi; \
|
|
||||||
MOVQ rBe, rT1; \
|
|
||||||
MOVQ _mi(iState), rBo; \
|
|
||||||
ANDQ rBi, rT1; \
|
|
||||||
XORQ rDu, rBa; \
|
|
||||||
MOVQ _so(iState), rBu; \
|
|
||||||
ROLQ $27, rBa; \
|
|
||||||
XORQ rBa, rT1; \
|
|
||||||
MOVQ rT1, _ma(oState); \
|
|
||||||
M_RT1_RCA; \
|
|
||||||
\
|
|
||||||
XORQ rDi, rBo; \
|
|
||||||
ROLQ $15, rBo; \
|
|
||||||
MOVQ rBi, rT1; \
|
|
||||||
ORQ rBo, rT1; \
|
|
||||||
XORQ rBe, rT1; \
|
|
||||||
MOVQ rT1, _me(oState); \
|
|
||||||
M_RT1_RCE; \
|
|
||||||
\
|
|
||||||
XORQ rDo, rBu; \
|
|
||||||
ROLQ $56, rBu; \
|
|
||||||
NOTQ rBo; \
|
|
||||||
MOVQ rBo, rT1; \
|
|
||||||
ORQ rBu, rT1; \
|
|
||||||
XORQ rBi, rT1; \
|
|
||||||
MOVQ rT1, _mi(oState); \
|
|
||||||
\
|
|
||||||
ORQ rBa, rBe; \
|
|
||||||
XORQ rBu, rBe; \
|
|
||||||
MOVQ rBe, _mu(oState); \
|
|
||||||
\
|
|
||||||
ANDQ rBa, rBu; \
|
|
||||||
XORQ rBo, rBu; \
|
|
||||||
MOVQ rBu, _mo(oState); \
|
|
||||||
M_RBE_RCU; \
|
|
||||||
\
|
|
||||||
/* Result s */ \
|
|
||||||
MOVQ _bi(iState), rBa; \
|
|
||||||
MOVQ _go(iState), rBe; \
|
|
||||||
MOVQ _ku(iState), rBi; \
|
|
||||||
XORQ rDi, rBa; \
|
|
||||||
MOVQ _ma(iState), rBo; \
|
|
||||||
ROLQ $62, rBa; \
|
|
||||||
XORQ rDo, rBe; \
|
|
||||||
MOVQ _se(iState), rBu; \
|
|
||||||
ROLQ $55, rBe; \
|
|
||||||
\
|
|
||||||
XORQ rDu, rBi; \
|
|
||||||
MOVQ rBa, rDu; \
|
|
||||||
XORQ rDe, rBu; \
|
|
||||||
ROLQ $2, rBu; \
|
|
||||||
ANDQ rBe, rDu; \
|
|
||||||
XORQ rBu, rDu; \
|
|
||||||
MOVQ rDu, _su(oState); \
|
|
||||||
\
|
|
||||||
ROLQ $39, rBi; \
|
|
||||||
S_RDU_RCU; \
|
|
||||||
NOTQ rBe; \
|
|
||||||
XORQ rDa, rBo; \
|
|
||||||
MOVQ rBe, rDa; \
|
|
||||||
ANDQ rBi, rDa; \
|
|
||||||
XORQ rBa, rDa; \
|
|
||||||
MOVQ rDa, _sa(oState); \
|
|
||||||
S_RDA_RCA; \
|
|
||||||
\
|
|
||||||
ROLQ $41, rBo; \
|
|
||||||
MOVQ rBi, rDe; \
|
|
||||||
ORQ rBo, rDe; \
|
|
||||||
XORQ rBe, rDe; \
|
|
||||||
MOVQ rDe, _se(oState); \
|
|
||||||
S_RDE_RCE; \
|
|
||||||
\
|
|
||||||
MOVQ rBo, rDi; \
|
|
||||||
MOVQ rBu, rDo; \
|
|
||||||
ANDQ rBu, rDi; \
|
|
||||||
ORQ rBa, rDo; \
|
|
||||||
XORQ rBi, rDi; \
|
|
||||||
XORQ rBo, rDo; \
|
|
||||||
MOVQ rDi, _si(oState); \
|
|
||||||
MOVQ rDo, _so(oState) \
|
|
||||||
|
|
||||||
// func KeccakF1600(state *[25]uint64)
|
|
||||||
TEXT ·KeccakF1600(SB), 0, $200-8
|
|
||||||
MOVQ state+0(FP), rpState
|
|
||||||
|
|
||||||
// Convert the user state into an internal state
|
|
||||||
NOTQ _be(rpState)
|
|
||||||
NOTQ _bi(rpState)
|
|
||||||
NOTQ _go(rpState)
|
|
||||||
NOTQ _ki(rpState)
|
|
||||||
NOTQ _mi(rpState)
|
|
||||||
NOTQ _sa(rpState)
|
|
||||||
|
|
||||||
// Execute the KeccakF permutation
|
|
||||||
MOVQ _ba(rpState), rCa
|
|
||||||
MOVQ _be(rpState), rCe
|
|
||||||
MOVQ _bu(rpState), rCu
|
|
||||||
|
|
||||||
XORQ _ga(rpState), rCa
|
|
||||||
XORQ _ge(rpState), rCe
|
|
||||||
XORQ _gu(rpState), rCu
|
|
||||||
|
|
||||||
XORQ _ka(rpState), rCa
|
|
||||||
XORQ _ke(rpState), rCe
|
|
||||||
XORQ _ku(rpState), rCu
|
|
||||||
|
|
||||||
XORQ _ma(rpState), rCa
|
|
||||||
XORQ _me(rpState), rCe
|
|
||||||
XORQ _mu(rpState), rCu
|
|
||||||
|
|
||||||
XORQ _sa(rpState), rCa
|
|
||||||
XORQ _se(rpState), rCe
|
|
||||||
MOVQ _si(rpState), rDi
|
|
||||||
MOVQ _so(rpState), rDo
|
|
||||||
XORQ _su(rpState), rCu
|
|
||||||
|
|
||||||
mKeccakRound(rpState, rpStack, $0x0000000000000001, MOVQ_RBI_RCE, XORQ_RT1_RCA, XORQ_RT1_RCE, XORQ_RBA_RCU, XORQ_RT1_RCA, XORQ_RT1_RCE, XORQ_RBA_RCU, XORQ_RT1_RCA, XORQ_RT1_RCE, XORQ_RBE_RCU, XORQ_RDU_RCU, XORQ_RDA_RCA, XORQ_RDE_RCE)
|
|
||||||
mKeccakRound(rpStack, rpState, $0x0000000000008082, MOVQ_RBI_RCE, XORQ_RT1_RCA, XORQ_RT1_RCE, XORQ_RBA_RCU, XORQ_RT1_RCA, XORQ_RT1_RCE, XORQ_RBA_RCU, XORQ_RT1_RCA, XORQ_RT1_RCE, XORQ_RBE_RCU, XORQ_RDU_RCU, XORQ_RDA_RCA, XORQ_RDE_RCE)
|
|
||||||
mKeccakRound(rpState, rpStack, $0x800000000000808a, MOVQ_RBI_RCE, XORQ_RT1_RCA, XORQ_RT1_RCE, XORQ_RBA_RCU, XORQ_RT1_RCA, XORQ_RT1_RCE, XORQ_RBA_RCU, XORQ_RT1_RCA, XORQ_RT1_RCE, XORQ_RBE_RCU, XORQ_RDU_RCU, XORQ_RDA_RCA, XORQ_RDE_RCE)
|
|
||||||
mKeccakRound(rpStack, rpState, $0x8000000080008000, MOVQ_RBI_RCE, XORQ_RT1_RCA, XORQ_RT1_RCE, XORQ_RBA_RCU, XORQ_RT1_RCA, XORQ_RT1_RCE, XORQ_RBA_RCU, XORQ_RT1_RCA, XORQ_RT1_RCE, XORQ_RBE_RCU, XORQ_RDU_RCU, XORQ_RDA_RCA, XORQ_RDE_RCE)
|
|
||||||
mKeccakRound(rpState, rpStack, $0x000000000000808b, MOVQ_RBI_RCE, XORQ_RT1_RCA, XORQ_RT1_RCE, XORQ_RBA_RCU, XORQ_RT1_RCA, XORQ_RT1_RCE, XORQ_RBA_RCU, XORQ_RT1_RCA, XORQ_RT1_RCE, XORQ_RBE_RCU, XORQ_RDU_RCU, XORQ_RDA_RCA, XORQ_RDE_RCE)
|
|
||||||
mKeccakRound(rpStack, rpState, $0x0000000080000001, MOVQ_RBI_RCE, XORQ_RT1_RCA, XORQ_RT1_RCE, XORQ_RBA_RCU, XORQ_RT1_RCA, XORQ_RT1_RCE, XORQ_RBA_RCU, XORQ_RT1_RCA, XORQ_RT1_RCE, XORQ_RBE_RCU, XORQ_RDU_RCU, XORQ_RDA_RCA, XORQ_RDE_RCE)
|
|
||||||
mKeccakRound(rpState, rpStack, $0x8000000080008081, MOVQ_RBI_RCE, XORQ_RT1_RCA, XORQ_RT1_RCE, XORQ_RBA_RCU, XORQ_RT1_RCA, XORQ_RT1_RCE, XORQ_RBA_RCU, XORQ_RT1_RCA, XORQ_RT1_RCE, XORQ_RBE_RCU, XORQ_RDU_RCU, XORQ_RDA_RCA, XORQ_RDE_RCE)
|
|
||||||
mKeccakRound(rpStack, rpState, $0x8000000000008009, MOVQ_RBI_RCE, XORQ_RT1_RCA, XORQ_RT1_RCE, XORQ_RBA_RCU, XORQ_RT1_RCA, XORQ_RT1_RCE, XORQ_RBA_RCU, XORQ_RT1_RCA, XORQ_RT1_RCE, XORQ_RBE_RCU, XORQ_RDU_RCU, XORQ_RDA_RCA, XORQ_RDE_RCE)
|
|
||||||
mKeccakRound(rpState, rpStack, $0x000000000000008a, MOVQ_RBI_RCE, XORQ_RT1_RCA, XORQ_RT1_RCE, XORQ_RBA_RCU, XORQ_RT1_RCA, XORQ_RT1_RCE, XORQ_RBA_RCU, XORQ_RT1_RCA, XORQ_RT1_RCE, XORQ_RBE_RCU, XORQ_RDU_RCU, XORQ_RDA_RCA, XORQ_RDE_RCE)
|
|
||||||
mKeccakRound(rpStack, rpState, $0x0000000000000088, MOVQ_RBI_RCE, XORQ_RT1_RCA, XORQ_RT1_RCE, XORQ_RBA_RCU, XORQ_RT1_RCA, XORQ_RT1_RCE, XORQ_RBA_RCU, XORQ_RT1_RCA, XORQ_RT1_RCE, XORQ_RBE_RCU, XORQ_RDU_RCU, XORQ_RDA_RCA, XORQ_RDE_RCE)
|
|
||||||
mKeccakRound(rpState, rpStack, $0x0000000080008009, MOVQ_RBI_RCE, XORQ_RT1_RCA, XORQ_RT1_RCE, XORQ_RBA_RCU, XORQ_RT1_RCA, XORQ_RT1_RCE, XORQ_RBA_RCU, XORQ_RT1_RCA, XORQ_RT1_RCE, XORQ_RBE_RCU, XORQ_RDU_RCU, XORQ_RDA_RCA, XORQ_RDE_RCE)
|
|
||||||
mKeccakRound(rpStack, rpState, $0x000000008000000a, MOVQ_RBI_RCE, XORQ_RT1_RCA, XORQ_RT1_RCE, XORQ_RBA_RCU, XORQ_RT1_RCA, XORQ_RT1_RCE, XORQ_RBA_RCU, XORQ_RT1_RCA, XORQ_RT1_RCE, XORQ_RBE_RCU, XORQ_RDU_RCU, XORQ_RDA_RCA, XORQ_RDE_RCE)
|
|
||||||
mKeccakRound(rpState, rpStack, $0x000000008000808b, MOVQ_RBI_RCE, XORQ_RT1_RCA, XORQ_RT1_RCE, XORQ_RBA_RCU, XORQ_RT1_RCA, XORQ_RT1_RCE, XORQ_RBA_RCU, XORQ_RT1_RCA, XORQ_RT1_RCE, XORQ_RBE_RCU, XORQ_RDU_RCU, XORQ_RDA_RCA, XORQ_RDE_RCE)
|
|
||||||
mKeccakRound(rpStack, rpState, $0x800000000000008b, MOVQ_RBI_RCE, XORQ_RT1_RCA, XORQ_RT1_RCE, XORQ_RBA_RCU, XORQ_RT1_RCA, XORQ_RT1_RCE, XORQ_RBA_RCU, XORQ_RT1_RCA, XORQ_RT1_RCE, XORQ_RBE_RCU, XORQ_RDU_RCU, XORQ_RDA_RCA, XORQ_RDE_RCE)
|
|
||||||
mKeccakRound(rpState, rpStack, $0x8000000000008089, MOVQ_RBI_RCE, XORQ_RT1_RCA, XORQ_RT1_RCE, XORQ_RBA_RCU, XORQ_RT1_RCA, XORQ_RT1_RCE, XORQ_RBA_RCU, XORQ_RT1_RCA, XORQ_RT1_RCE, XORQ_RBE_RCU, XORQ_RDU_RCU, XORQ_RDA_RCA, XORQ_RDE_RCE)
|
|
||||||
mKeccakRound(rpStack, rpState, $0x8000000000008003, MOVQ_RBI_RCE, XORQ_RT1_RCA, XORQ_RT1_RCE, XORQ_RBA_RCU, XORQ_RT1_RCA, XORQ_RT1_RCE, XORQ_RBA_RCU, XORQ_RT1_RCA, XORQ_RT1_RCE, XORQ_RBE_RCU, XORQ_RDU_RCU, XORQ_RDA_RCA, XORQ_RDE_RCE)
|
|
||||||
mKeccakRound(rpState, rpStack, $0x8000000000008002, MOVQ_RBI_RCE, XORQ_RT1_RCA, XORQ_RT1_RCE, XORQ_RBA_RCU, XORQ_RT1_RCA, XORQ_RT1_RCE, XORQ_RBA_RCU, XORQ_RT1_RCA, XORQ_RT1_RCE, XORQ_RBE_RCU, XORQ_RDU_RCU, XORQ_RDA_RCA, XORQ_RDE_RCE)
|
|
||||||
mKeccakRound(rpStack, rpState, $0x8000000000000080, MOVQ_RBI_RCE, XORQ_RT1_RCA, XORQ_RT1_RCE, XORQ_RBA_RCU, XORQ_RT1_RCA, XORQ_RT1_RCE, XORQ_RBA_RCU, XORQ_RT1_RCA, XORQ_RT1_RCE, XORQ_RBE_RCU, XORQ_RDU_RCU, XORQ_RDA_RCA, XORQ_RDE_RCE)
|
|
||||||
mKeccakRound(rpState, rpStack, $0x000000000000800a, MOVQ_RBI_RCE, XORQ_RT1_RCA, XORQ_RT1_RCE, XORQ_RBA_RCU, XORQ_RT1_RCA, XORQ_RT1_RCE, XORQ_RBA_RCU, XORQ_RT1_RCA, XORQ_RT1_RCE, XORQ_RBE_RCU, XORQ_RDU_RCU, XORQ_RDA_RCA, XORQ_RDE_RCE)
|
|
||||||
mKeccakRound(rpStack, rpState, $0x800000008000000a, MOVQ_RBI_RCE, XORQ_RT1_RCA, XORQ_RT1_RCE, XORQ_RBA_RCU, XORQ_RT1_RCA, XORQ_RT1_RCE, XORQ_RBA_RCU, XORQ_RT1_RCA, XORQ_RT1_RCE, XORQ_RBE_RCU, XORQ_RDU_RCU, XORQ_RDA_RCA, XORQ_RDE_RCE)
|
|
||||||
mKeccakRound(rpState, rpStack, $0x8000000080008081, MOVQ_RBI_RCE, XORQ_RT1_RCA, XORQ_RT1_RCE, XORQ_RBA_RCU, XORQ_RT1_RCA, XORQ_RT1_RCE, XORQ_RBA_RCU, XORQ_RT1_RCA, XORQ_RT1_RCE, XORQ_RBE_RCU, XORQ_RDU_RCU, XORQ_RDA_RCA, XORQ_RDE_RCE)
|
|
||||||
mKeccakRound(rpStack, rpState, $0x8000000000008080, MOVQ_RBI_RCE, XORQ_RT1_RCA, XORQ_RT1_RCE, XORQ_RBA_RCU, XORQ_RT1_RCA, XORQ_RT1_RCE, XORQ_RBA_RCU, XORQ_RT1_RCA, XORQ_RT1_RCE, XORQ_RBE_RCU, XORQ_RDU_RCU, XORQ_RDA_RCA, XORQ_RDE_RCE)
|
|
||||||
mKeccakRound(rpState, rpStack, $0x0000000080000001, MOVQ_RBI_RCE, XORQ_RT1_RCA, XORQ_RT1_RCE, XORQ_RBA_RCU, XORQ_RT1_RCA, XORQ_RT1_RCE, XORQ_RBA_RCU, XORQ_RT1_RCA, XORQ_RT1_RCE, XORQ_RBE_RCU, XORQ_RDU_RCU, XORQ_RDA_RCA, XORQ_RDE_RCE)
|
|
||||||
mKeccakRound(rpStack, rpState, $0x8000000080008008, NOP, NOP, NOP, NOP, NOP, NOP, NOP, NOP, NOP, NOP, NOP, NOP, NOP)
|
|
||||||
|
|
||||||
// Revert the internal state to the user state
|
|
||||||
NOTQ _be(rpState)
|
|
||||||
NOTQ _bi(rpState)
|
|
||||||
NOTQ _go(rpState)
|
|
||||||
NOTQ _ki(rpState)
|
|
||||||
NOTQ _mi(rpState)
|
|
||||||
NOTQ _sa(rpState)
|
|
||||||
|
|
||||||
RET
|
|
|
@ -0,0 +1,207 @@
|
||||||
|
package hybrid
|
||||||
|
|
||||||
|
// TODO move over to crypto/ecdh once we can assume Go 1.20.
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/elliptic"
|
||||||
|
cryptoRand "crypto/rand"
|
||||||
|
"crypto/subtle"
|
||||||
|
"math/big"
|
||||||
|
|
||||||
|
"github.com/cloudflare/circl/kem"
|
||||||
|
"github.com/cloudflare/circl/xof"
|
||||||
|
)
|
||||||
|
|
||||||
|
type cPublicKey struct {
|
||||||
|
scheme *cScheme
|
||||||
|
x, y *big.Int
|
||||||
|
}
|
||||||
|
type cPrivateKey struct {
|
||||||
|
scheme *cScheme
|
||||||
|
key []byte
|
||||||
|
}
|
||||||
|
type cScheme struct {
|
||||||
|
curve elliptic.Curve
|
||||||
|
}
|
||||||
|
|
||||||
|
var p256Kem = &cScheme{elliptic.P256()}
|
||||||
|
|
||||||
|
func (sch *cScheme) scSize() int {
|
||||||
|
return (sch.curve.Params().N.BitLen() + 7) / 8
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sch *cScheme) ptSize() int {
|
||||||
|
return (sch.curve.Params().BitSize + 7) / 8
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sch *cScheme) Name() string {
|
||||||
|
return sch.curve.Params().Name
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sch *cScheme) PublicKeySize() int {
|
||||||
|
return 2*sch.ptSize() + 1
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sch *cScheme) PrivateKeySize() int {
|
||||||
|
return sch.scSize()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sch *cScheme) SeedSize() int {
|
||||||
|
return sch.PrivateKeySize()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sch *cScheme) SharedKeySize() int {
|
||||||
|
return sch.ptSize()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sch *cScheme) CiphertextSize() int {
|
||||||
|
return sch.PublicKeySize()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sch *cScheme) EncapsulationSeedSize() int {
|
||||||
|
return sch.SeedSize()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sk *cPrivateKey) Scheme() kem.Scheme { return sk.scheme }
|
||||||
|
func (pk *cPublicKey) Scheme() kem.Scheme { return pk.scheme }
|
||||||
|
|
||||||
|
func (sk *cPrivateKey) MarshalBinary() ([]byte, error) {
|
||||||
|
ret := make([]byte, len(sk.key))
|
||||||
|
copy(ret, sk.key)
|
||||||
|
return ret, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sk *cPrivateKey) Equal(other kem.PrivateKey) bool {
|
||||||
|
oth, ok := other.(*cPrivateKey)
|
||||||
|
if !ok {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if oth.scheme != sk.scheme {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return subtle.ConstantTimeCompare(oth.key, sk.key) == 1
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sk *cPrivateKey) Public() kem.PublicKey {
|
||||||
|
x, y := sk.scheme.curve.ScalarBaseMult(sk.key)
|
||||||
|
return &cPublicKey{
|
||||||
|
sk.scheme,
|
||||||
|
x,
|
||||||
|
y,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (pk *cPublicKey) Equal(other kem.PublicKey) bool {
|
||||||
|
oth, ok := other.(*cPublicKey)
|
||||||
|
if !ok {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if oth.scheme != pk.scheme {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return oth.x.Cmp(pk.x) == 0 && oth.y.Cmp(pk.y) == 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (pk *cPublicKey) MarshalBinary() ([]byte, error) {
|
||||||
|
return elliptic.Marshal(pk.scheme.curve, pk.x, pk.y), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sch *cScheme) GenerateKeyPair() (kem.PublicKey, kem.PrivateKey, error) {
|
||||||
|
seed := make([]byte, sch.SeedSize())
|
||||||
|
_, err := cryptoRand.Read(seed)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
pk, sk := sch.DeriveKeyPair(seed)
|
||||||
|
return pk, sk, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sch *cScheme) DeriveKeyPair(seed []byte) (kem.PublicKey, kem.PrivateKey) {
|
||||||
|
if len(seed) != sch.SeedSize() {
|
||||||
|
panic(kem.ErrSeedSize)
|
||||||
|
}
|
||||||
|
h := xof.SHAKE256.New()
|
||||||
|
_, _ = h.Write(seed)
|
||||||
|
key, x, y, err := elliptic.GenerateKey(sch.curve, h)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
sk := cPrivateKey{scheme: sch, key: key}
|
||||||
|
pk := cPublicKey{scheme: sch, x: x, y: y}
|
||||||
|
|
||||||
|
return &pk, &sk
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sch *cScheme) Encapsulate(pk kem.PublicKey) (ct, ss []byte, err error) {
|
||||||
|
seed := make([]byte, sch.EncapsulationSeedSize())
|
||||||
|
_, err = cryptoRand.Read(seed)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return sch.EncapsulateDeterministically(pk, seed)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (pk *cPublicKey) X(sk *cPrivateKey) []byte {
|
||||||
|
if pk.scheme != sk.scheme {
|
||||||
|
panic(kem.ErrTypeMismatch)
|
||||||
|
}
|
||||||
|
|
||||||
|
sharedKey := make([]byte, pk.scheme.SharedKeySize())
|
||||||
|
xShared, _ := pk.scheme.curve.ScalarMult(pk.x, pk.y, sk.key)
|
||||||
|
xShared.FillBytes(sharedKey)
|
||||||
|
return sharedKey
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sch *cScheme) EncapsulateDeterministically(
|
||||||
|
pk kem.PublicKey, seed []byte,
|
||||||
|
) (ct, ss []byte, err error) {
|
||||||
|
if len(seed) != sch.EncapsulationSeedSize() {
|
||||||
|
return nil, nil, kem.ErrSeedSize
|
||||||
|
}
|
||||||
|
pub, ok := pk.(*cPublicKey)
|
||||||
|
if !ok || pub.scheme != sch {
|
||||||
|
return nil, nil, kem.ErrTypeMismatch
|
||||||
|
}
|
||||||
|
|
||||||
|
pk2, sk2 := sch.DeriveKeyPair(seed)
|
||||||
|
ss = pub.X(sk2.(*cPrivateKey))
|
||||||
|
ct, _ = pk2.MarshalBinary()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sch *cScheme) Decapsulate(sk kem.PrivateKey, ct []byte) ([]byte, error) {
|
||||||
|
if len(ct) != sch.CiphertextSize() {
|
||||||
|
return nil, kem.ErrCiphertextSize
|
||||||
|
}
|
||||||
|
|
||||||
|
priv, ok := sk.(*cPrivateKey)
|
||||||
|
if !ok || priv.scheme != sch {
|
||||||
|
return nil, kem.ErrTypeMismatch
|
||||||
|
}
|
||||||
|
|
||||||
|
pk, err := sch.UnmarshalBinaryPublicKey(ct)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
ss := pk.(*cPublicKey).X(priv)
|
||||||
|
return ss, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sch *cScheme) UnmarshalBinaryPublicKey(buf []byte) (kem.PublicKey, error) {
|
||||||
|
if len(buf) != sch.PublicKeySize() {
|
||||||
|
return nil, kem.ErrPubKeySize
|
||||||
|
}
|
||||||
|
x, y := elliptic.Unmarshal(sch.curve, buf)
|
||||||
|
return &cPublicKey{sch, x, y}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sch *cScheme) UnmarshalBinaryPrivateKey(buf []byte) (kem.PrivateKey, error) {
|
||||||
|
if len(buf) != sch.PrivateKeySize() {
|
||||||
|
return nil, kem.ErrPrivKeySize
|
||||||
|
}
|
||||||
|
ret := cPrivateKey{sch, make([]byte, sch.PrivateKeySize())}
|
||||||
|
copy(ret.key, buf)
|
||||||
|
return &ret, nil
|
||||||
|
}
|
|
@ -42,18 +42,27 @@ import (
|
||||||
|
|
||||||
var ErrUninitialized = errors.New("public or private key not initialized")
|
var ErrUninitialized = errors.New("public or private key not initialized")
|
||||||
|
|
||||||
// Returns the hybrid KEM of Kyber512 and X25519.
|
// Returns the hybrid KEM of Kyber512Draft00 and X25519.
|
||||||
func Kyber512X25519() kem.Scheme { return kyber512X }
|
func Kyber512X25519() kem.Scheme { return kyber512X }
|
||||||
|
|
||||||
// Returns the hybrid KEM of Kyber768 and X25519.
|
// Returns the hybrid KEM of Kyber768Draft00 and X25519.
|
||||||
func Kyber768X25519() kem.Scheme { return kyber768X }
|
func Kyber768X25519() kem.Scheme { return kyber768X }
|
||||||
|
|
||||||
// Returns the hybrid KEM of Kyber768 and X448.
|
// Returns the hybrid KEM of Kyber768Draft00 and X448.
|
||||||
func Kyber768X448() kem.Scheme { return kyber768X4 }
|
func Kyber768X448() kem.Scheme { return kyber768X4 }
|
||||||
|
|
||||||
// Returns the hybrid KEM of Kyber1024 and X448.
|
// Returns the hybrid KEM of Kyber1024Draft00 and X448.
|
||||||
func Kyber1024X448() kem.Scheme { return kyber1024X }
|
func Kyber1024X448() kem.Scheme { return kyber1024X }
|
||||||
|
|
||||||
|
// Returns the hybrid KEM of Kyber768Draft00 and P-256.
|
||||||
|
func P256Kyber768Draft00() kem.Scheme { return p256Kyber768Draft00 }
|
||||||
|
|
||||||
|
var p256Kyber768Draft00 kem.Scheme = &scheme{
|
||||||
|
"P256Kyber768Draft00",
|
||||||
|
p256Kem,
|
||||||
|
kyber768.Scheme(),
|
||||||
|
}
|
||||||
|
|
||||||
var kyber512X kem.Scheme = &scheme{
|
var kyber512X kem.Scheme = &scheme{
|
||||||
"Kyber512-X25519",
|
"Kyber512-X25519",
|
||||||
x25519Kem,
|
x25519Kem,
|
||||||
|
|
|
@ -106,7 +106,9 @@ func GenerateKeyPair(rand io.Reader) (*PublicKey, *PrivateKey, error) {
|
||||||
func (pk *PublicKey) EncapsulateTo(ct, ss []byte, seed []byte) {
|
func (pk *PublicKey) EncapsulateTo(ct, ss []byte, seed []byte) {
|
||||||
if seed == nil {
|
if seed == nil {
|
||||||
seed = make([]byte, EncapsulationSeedSize)
|
seed = make([]byte, EncapsulationSeedSize)
|
||||||
cryptoRand.Read(seed[:])
|
if _, err := cryptoRand.Read(seed[:]); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
if len(seed) != EncapsulationSeedSize {
|
if len(seed) != EncapsulationSeedSize {
|
||||||
panic("seed must be of length EncapsulationSeedSize")
|
panic("seed must be of length EncapsulationSeedSize")
|
||||||
|
@ -296,7 +298,7 @@ func (sk *PrivateKey) Equal(other kem.PrivateKey) bool {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
if !bytes.Equal(sk.hpk[:], oth.hpk[:]) ||
|
if !bytes.Equal(sk.hpk[:], oth.hpk[:]) ||
|
||||||
!bytes.Equal(sk.z[:], oth.z[:]) {
|
subtle.ConstantTimeCompare(sk.z[:], oth.z[:]) != 1 {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
return sk.sk.Equal(oth.sk)
|
return sk.sk.Equal(oth.sk)
|
||||||
|
|
|
@ -106,7 +106,9 @@ func GenerateKeyPair(rand io.Reader) (*PublicKey, *PrivateKey, error) {
|
||||||
func (pk *PublicKey) EncapsulateTo(ct, ss []byte, seed []byte) {
|
func (pk *PublicKey) EncapsulateTo(ct, ss []byte, seed []byte) {
|
||||||
if seed == nil {
|
if seed == nil {
|
||||||
seed = make([]byte, EncapsulationSeedSize)
|
seed = make([]byte, EncapsulationSeedSize)
|
||||||
cryptoRand.Read(seed[:])
|
if _, err := cryptoRand.Read(seed[:]); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
if len(seed) != EncapsulationSeedSize {
|
if len(seed) != EncapsulationSeedSize {
|
||||||
panic("seed must be of length EncapsulationSeedSize")
|
panic("seed must be of length EncapsulationSeedSize")
|
||||||
|
@ -296,7 +298,7 @@ func (sk *PrivateKey) Equal(other kem.PrivateKey) bool {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
if !bytes.Equal(sk.hpk[:], oth.hpk[:]) ||
|
if !bytes.Equal(sk.hpk[:], oth.hpk[:]) ||
|
||||||
!bytes.Equal(sk.z[:], oth.z[:]) {
|
subtle.ConstantTimeCompare(sk.z[:], oth.z[:]) != 1 {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
return sk.sk.Equal(oth.sk)
|
return sk.sk.Equal(oth.sk)
|
||||||
|
|
|
@ -106,7 +106,9 @@ func GenerateKeyPair(rand io.Reader) (*PublicKey, *PrivateKey, error) {
|
||||||
func (pk *PublicKey) EncapsulateTo(ct, ss []byte, seed []byte) {
|
func (pk *PublicKey) EncapsulateTo(ct, ss []byte, seed []byte) {
|
||||||
if seed == nil {
|
if seed == nil {
|
||||||
seed = make([]byte, EncapsulationSeedSize)
|
seed = make([]byte, EncapsulationSeedSize)
|
||||||
cryptoRand.Read(seed[:])
|
if _, err := cryptoRand.Read(seed[:]); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
if len(seed) != EncapsulationSeedSize {
|
if len(seed) != EncapsulationSeedSize {
|
||||||
panic("seed must be of length EncapsulationSeedSize")
|
panic("seed must be of length EncapsulationSeedSize")
|
||||||
|
@ -296,7 +298,7 @@ func (sk *PrivateKey) Equal(other kem.PrivateKey) bool {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
if !bytes.Equal(sk.hpk[:], oth.hpk[:]) ||
|
if !bytes.Equal(sk.hpk[:], oth.hpk[:]) ||
|
||||||
!bytes.Equal(sk.z[:], oth.z[:]) {
|
subtle.ConstantTimeCompare(sk.z[:], oth.z[:]) != 1 {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
return sk.sk.Equal(oth.sk)
|
return sk.sk.Equal(oth.sk)
|
||||||
|
|
|
@ -99,6 +99,7 @@
|
||||||
// Uses: AX, DX, R8-R15, FLAGS
|
// Uses: AX, DX, R8-R15, FLAGS
|
||||||
// Instr: x86_64, bmi2, adx
|
// Instr: x86_64, bmi2, adx
|
||||||
#define integerMulAdx(z,x,y) \
|
#define integerMulAdx(z,x,y) \
|
||||||
|
MOVL $0,R15; \
|
||||||
MOVQ 0+y, DX; XORL AX, AX; \
|
MOVQ 0+y, DX; XORL AX, AX; \
|
||||||
MULXQ 0+x, AX, R8; MOVQ AX, 0+z; \
|
MULXQ 0+x, AX, R8; MOVQ AX, 0+z; \
|
||||||
MULXQ 8+x, AX, R9; ADCXQ AX, R8; \
|
MULXQ 8+x, AX, R9; ADCXQ AX, R8; \
|
||||||
|
|
|
@ -158,6 +158,7 @@
|
||||||
// Uses: AX, DX, R8-R15, FLAGS
|
// Uses: AX, DX, R8-R15, FLAGS
|
||||||
// Instr: x86_64, bmi2, adx
|
// Instr: x86_64, bmi2, adx
|
||||||
#define integerMulAdx(z,x,y) \
|
#define integerMulAdx(z,x,y) \
|
||||||
|
MOVL $0,R15; \
|
||||||
MOVQ 0+y, DX; XORL AX, AX; MOVQ $0, R8; \
|
MOVQ 0+y, DX; XORL AX, AX; MOVQ $0, R8; \
|
||||||
MULXQ 0+x, AX, R9; MOVQ AX, 0+z; \
|
MULXQ 0+x, AX, R9; MOVQ AX, 0+z; \
|
||||||
MULXQ 8+x, AX, R10; ADCXQ AX, R9; \
|
MULXQ 8+x, AX, R10; ADCXQ AX, R9; \
|
||||||
|
|
|
@ -8,10 +8,10 @@ const (
|
||||||
Q int16 = 3329
|
Q int16 = 3329
|
||||||
|
|
||||||
// N is the parameter N: the length of the polynomials
|
// N is the parameter N: the length of the polynomials
|
||||||
N int = 256
|
N = 256
|
||||||
|
|
||||||
// PolySize is the size of a packed polynomial.
|
// PolySize is the size of a packed polynomial.
|
||||||
PolySize int = 384
|
PolySize = 384
|
||||||
|
|
||||||
// PlaintextSize is the size of the plaintext
|
// PlaintextSize is the size of the plaintext
|
||||||
PlaintextSize = 32
|
PlaintextSize = 32
|
||||||
|
|
|
@ -0,0 +1,72 @@
|
||||||
|
// Package xof provides an interface for eXtendable-Output Functions.
|
||||||
|
//
|
||||||
|
// # Available Functions
|
||||||
|
//
|
||||||
|
// SHAKE functions are defined in FIPS-202, see https://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.202.pdf.
|
||||||
|
// BLAKE2Xb and BLAKE2Xs are defined in https://www.blake2.net/blake2x.pdf.
|
||||||
|
package xof
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
|
||||||
|
"github.com/cloudflare/circl/internal/sha3"
|
||||||
|
"golang.org/x/crypto/blake2b"
|
||||||
|
"golang.org/x/crypto/blake2s"
|
||||||
|
)
|
||||||
|
|
||||||
|
// XOF defines the interface to hash functions that support arbitrary-length output.
|
||||||
|
type XOF interface {
|
||||||
|
// Write absorbs more data into the XOF's state. It panics if called
|
||||||
|
// after Read.
|
||||||
|
io.Writer
|
||||||
|
|
||||||
|
// Read reads more output from the XOF. It returns io.EOF if the limit
|
||||||
|
// has been reached.
|
||||||
|
io.Reader
|
||||||
|
|
||||||
|
// Clone returns a copy of the XOF in its current state.
|
||||||
|
Clone() XOF
|
||||||
|
|
||||||
|
// Reset restores the XOF to its initial state and discards all data appended by Write.
|
||||||
|
Reset()
|
||||||
|
}
|
||||||
|
|
||||||
|
type ID uint
|
||||||
|
|
||||||
|
const (
|
||||||
|
SHAKE128 ID = iota + 1
|
||||||
|
SHAKE256
|
||||||
|
BLAKE2XB
|
||||||
|
BLAKE2XS
|
||||||
|
)
|
||||||
|
|
||||||
|
func (x ID) New() XOF {
|
||||||
|
switch x {
|
||||||
|
case SHAKE128:
|
||||||
|
s := sha3.NewShake128()
|
||||||
|
return shakeBody{&s}
|
||||||
|
case SHAKE256:
|
||||||
|
s := sha3.NewShake256()
|
||||||
|
return shakeBody{&s}
|
||||||
|
case BLAKE2XB:
|
||||||
|
x, _ := blake2b.NewXOF(blake2b.OutputLengthUnknown, nil)
|
||||||
|
return blake2xb{x}
|
||||||
|
case BLAKE2XS:
|
||||||
|
x, _ := blake2s.NewXOF(blake2s.OutputLengthUnknown, nil)
|
||||||
|
return blake2xs{x}
|
||||||
|
default:
|
||||||
|
panic("crypto: requested unavailable XOF function")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type shakeBody struct{ sha3.ShakeHash }
|
||||||
|
|
||||||
|
func (s shakeBody) Clone() XOF { return shakeBody{s.ShakeHash.Clone()} }
|
||||||
|
|
||||||
|
type blake2xb struct{ blake2b.XOF }
|
||||||
|
|
||||||
|
func (s blake2xb) Clone() XOF { return blake2xb{s.XOF.Clone()} }
|
||||||
|
|
||||||
|
type blake2xs struct{ blake2s.XOF }
|
||||||
|
|
||||||
|
func (s blake2xs) Clone() XOF { return blake2xs{s.XOF.Clone()} }
|
|
@ -128,7 +128,7 @@ func (b *LRUCache) touchEntry(e *entry) {
|
||||||
b.lruList.MoveToFront(&e.element)
|
b.lruList.MoveToFront(&e.element)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add an item to the cache overwriting existing one if it
|
// SetNow adds an item to the cache overwriting existing one if it
|
||||||
// exists. Allows specifing current time required to expire an item
|
// exists. Allows specifing current time required to expire an item
|
||||||
// when no more slots are used. O(log(n)) if expiry is set, O(1) when
|
// when no more slots are used. O(log(n)) if expiry is set, O(1) when
|
||||||
// clear.
|
// clear.
|
||||||
|
@ -157,7 +157,7 @@ func (b *LRUCache) SetNow(key string, value interface{}, expire time.Time, now t
|
||||||
b.insertEntry(e)
|
b.insertEntry(e)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add an item to the cache overwriting existing one if it
|
// Set adds an item to the cache overwriting existing one if it
|
||||||
// exists. O(log(n)) if expiry is set, O(1) when clear.
|
// exists. O(log(n)) if expiry is set, O(1) when clear.
|
||||||
func (b *LRUCache) Set(key string, value interface{}, expire time.Time) {
|
func (b *LRUCache) Set(key string, value interface{}, expire time.Time) {
|
||||||
b.SetNow(key, value, expire, time.Time{})
|
b.SetNow(key, value, expire, time.Time{})
|
||||||
|
@ -177,7 +177,7 @@ func (b *LRUCache) Get(key string) (v interface{}, ok bool) {
|
||||||
return e.value, true
|
return e.value, true
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get a key from the cache, possibly stale. Don't modify its LRU score. O(1)
|
// GetQuiet gets a key from the cache, possibly stale. Don't modify its LRU score. O(1)
|
||||||
func (b *LRUCache) GetQuiet(key string) (v interface{}, ok bool) {
|
func (b *LRUCache) GetQuiet(key string) (v interface{}, ok bool) {
|
||||||
b.lock.Lock()
|
b.lock.Lock()
|
||||||
defer b.lock.Unlock()
|
defer b.lock.Unlock()
|
||||||
|
@ -190,13 +190,13 @@ func (b *LRUCache) GetQuiet(key string) (v interface{}, ok bool) {
|
||||||
return e.value, true
|
return e.value, true
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get a key from the cache, make sure it's not stale. Update its
|
// GetNotStale gets a key from the cache, make sure it's not stale. Update its
|
||||||
// LRU score. O(log(n)) if the item is expired.
|
// LRU score. O(log(n)) if the item is expired.
|
||||||
func (b *LRUCache) GetNotStale(key string) (value interface{}, ok bool) {
|
func (b *LRUCache) GetNotStale(key string) (value interface{}, ok bool) {
|
||||||
return b.GetNotStaleNow(key, time.Now())
|
return b.GetNotStaleNow(key, time.Now())
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get a key from the cache, make sure it's not stale. Update its
|
// GetNotStaleNow gets a key from the cache, make sure it's not stale. Update its
|
||||||
// LRU score. O(log(n)) if the item is expired.
|
// LRU score. O(log(n)) if the item is expired.
|
||||||
func (b *LRUCache) GetNotStaleNow(key string, now time.Time) (value interface{}, ok bool) {
|
func (b *LRUCache) GetNotStaleNow(key string, now time.Time) (value interface{}, ok bool) {
|
||||||
b.lock.Lock()
|
b.lock.Lock()
|
||||||
|
@ -219,13 +219,13 @@ func (b *LRUCache) GetNotStaleNow(key string, now time.Time) (value interface{},
|
||||||
return e.value, true
|
return e.value, true
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get a key from the cache, possibly stale. Update its LRU
|
// GetStale gets a key from the cache, possibly stale. Update its LRU
|
||||||
// score. O(1) always.
|
// score. O(1) always.
|
||||||
func (b *LRUCache) GetStale(key string) (value interface{}, ok, expired bool) {
|
func (b *LRUCache) GetStale(key string) (value interface{}, ok, expired bool) {
|
||||||
return b.GetStaleNow(key, time.Now())
|
return b.GetStaleNow(key, time.Now())
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get a key from the cache, possibly stale. Update its LRU
|
// GetStaleNow gets a key from the cache, possibly stale. Update its LRU
|
||||||
// score. O(1) always.
|
// score. O(1) always.
|
||||||
func (b *LRUCache) GetStaleNow(key string, now time.Time) (value interface{}, ok, expired bool) {
|
func (b *LRUCache) GetStaleNow(key string, now time.Time) (value interface{}, ok, expired bool) {
|
||||||
b.lock.Lock()
|
b.lock.Lock()
|
||||||
|
@ -240,7 +240,7 @@ func (b *LRUCache) GetStaleNow(key string, now time.Time) (value interface{}, ok
|
||||||
return e.value, true, e.expire.Before(now)
|
return e.value, true, e.expire.Before(now)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get and remove a key from the cache. O(log(n)) if the item is using expiry, O(1) otherwise.
|
// Del gets and remove a key from the cache. O(log(n)) if the item is using expiry, O(1) otherwise.
|
||||||
func (b *LRUCache) Del(key string) (v interface{}, ok bool) {
|
func (b *LRUCache) Del(key string) (v interface{}, ok bool) {
|
||||||
b.lock.Lock()
|
b.lock.Lock()
|
||||||
defer b.lock.Unlock()
|
defer b.lock.Unlock()
|
||||||
|
@ -306,7 +306,7 @@ func (b *LRUCache) Len() int {
|
||||||
return b.lruList.Len()
|
return b.lruList.Len()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get the total capacity of the LRU
|
// Capacity gets the total capacity of the LRU
|
||||||
func (b *LRUCache) Capacity() int {
|
func (b *LRUCache) Capacity() int {
|
||||||
// yes. this stupid thing requires locking
|
// yes. this stupid thing requires locking
|
||||||
b.lock.Lock()
|
b.lock.Lock()
|
||||||
|
|
|
@ -5,6 +5,7 @@ import (
|
||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/coredns/caddy"
|
"github.com/coredns/caddy"
|
||||||
"github.com/coredns/coredns/plugin"
|
"github.com/coredns/coredns/plugin"
|
||||||
|
@ -53,6 +54,11 @@ type Config struct {
|
||||||
// TLSConfig when listening for encrypted connections (gRPC, DNS-over-TLS).
|
// TLSConfig when listening for encrypted connections (gRPC, DNS-over-TLS).
|
||||||
TLSConfig *tls.Config
|
TLSConfig *tls.Config
|
||||||
|
|
||||||
|
// Timeouts for TCP, TLS and HTTPS servers.
|
||||||
|
ReadTimeout time.Duration
|
||||||
|
WriteTimeout time.Duration
|
||||||
|
IdleTimeout time.Duration
|
||||||
|
|
||||||
// TSIG secrets, [name]key.
|
// TSIG secrets, [name]key.
|
||||||
TsigSecret map[string]string
|
TsigSecret map[string]string
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
package dnsserver
|
package dnsserver
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"flag"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
"time"
|
"time"
|
||||||
|
@ -17,12 +16,7 @@ import (
|
||||||
|
|
||||||
const serverType = "dns"
|
const serverType = "dns"
|
||||||
|
|
||||||
// Any flags defined here, need to be namespaced to the serverType other
|
|
||||||
// wise they potentially clash with other server types.
|
|
||||||
func init() {
|
func init() {
|
||||||
flag.StringVar(&Port, serverType+".port", DefaultPort, "Default port")
|
|
||||||
flag.StringVar(&Port, "p", DefaultPort, "Default port")
|
|
||||||
|
|
||||||
caddy.RegisterServerType(serverType, caddy.ServerType{
|
caddy.RegisterServerType(serverType, caddy.ServerType{
|
||||||
Directives: func() []string { return Directives },
|
Directives: func() []string { return Directives },
|
||||||
DefaultInput: func() caddy.Input {
|
DefaultInput: func() caddy.Input {
|
||||||
|
@ -147,7 +141,12 @@ func (h *dnsContext) MakeServers() ([]caddy.Server, error) {
|
||||||
c.ListenHosts = c.firstConfigInBlock.ListenHosts
|
c.ListenHosts = c.firstConfigInBlock.ListenHosts
|
||||||
c.Debug = c.firstConfigInBlock.Debug
|
c.Debug = c.firstConfigInBlock.Debug
|
||||||
c.Stacktrace = c.firstConfigInBlock.Stacktrace
|
c.Stacktrace = c.firstConfigInBlock.Stacktrace
|
||||||
c.TLSConfig = c.firstConfigInBlock.TLSConfig
|
|
||||||
|
// Fork TLSConfig for each encrypted connection
|
||||||
|
c.TLSConfig = c.firstConfigInBlock.TLSConfig.Clone()
|
||||||
|
c.ReadTimeout = c.firstConfigInBlock.ReadTimeout
|
||||||
|
c.WriteTimeout = c.firstConfigInBlock.WriteTimeout
|
||||||
|
c.IdleTimeout = c.firstConfigInBlock.IdleTimeout
|
||||||
c.TsigSecret = c.firstConfigInBlock.TsigSecret
|
c.TsigSecret = c.firstConfigInBlock.TsigSecret
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -221,6 +220,7 @@ func (c *Config) AddPlugin(m plugin.Plugin) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// registerHandler adds a handler to a site's handler registration. Handlers
|
// registerHandler adds a handler to a site's handler registration. Handlers
|
||||||
|
//
|
||||||
// use this to announce that they exist to other plugin.
|
// use this to announce that they exist to other plugin.
|
||||||
func (c *Config) registerHandler(h plugin.Handler) {
|
func (c *Config) registerHandler(h plugin.Handler) {
|
||||||
if c.registry == nil {
|
if c.registry == nil {
|
||||||
|
|
|
@ -44,6 +44,9 @@ type Server struct {
|
||||||
debug bool // disable recover()
|
debug bool // disable recover()
|
||||||
stacktrace bool // enable stacktrace in recover error log
|
stacktrace bool // enable stacktrace in recover error log
|
||||||
classChaos bool // allow non-INET class queries
|
classChaos bool // allow non-INET class queries
|
||||||
|
idleTimeout time.Duration // Idle timeout for TCP
|
||||||
|
readTimeout time.Duration // Read timeout for TCP
|
||||||
|
writeTimeout time.Duration // Write timeout for TCP
|
||||||
|
|
||||||
tsigSecret map[string]string
|
tsigSecret map[string]string
|
||||||
}
|
}
|
||||||
|
@ -60,6 +63,9 @@ func NewServer(addr string, group []*Config) (*Server, error) {
|
||||||
Addr: addr,
|
Addr: addr,
|
||||||
zones: make(map[string][]*Config),
|
zones: make(map[string][]*Config),
|
||||||
graceTimeout: 5 * time.Second,
|
graceTimeout: 5 * time.Second,
|
||||||
|
idleTimeout: 10 * time.Second,
|
||||||
|
readTimeout: 3 * time.Second,
|
||||||
|
writeTimeout: 5 * time.Second,
|
||||||
tsigSecret: make(map[string]string),
|
tsigSecret: make(map[string]string),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -81,6 +87,17 @@ func NewServer(addr string, group []*Config) (*Server, error) {
|
||||||
// append the config to the zone's configs
|
// append the config to the zone's configs
|
||||||
s.zones[site.Zone] = append(s.zones[site.Zone], site)
|
s.zones[site.Zone] = append(s.zones[site.Zone], site)
|
||||||
|
|
||||||
|
// set timeouts
|
||||||
|
if site.ReadTimeout != 0 {
|
||||||
|
s.readTimeout = site.ReadTimeout
|
||||||
|
}
|
||||||
|
if site.WriteTimeout != 0 {
|
||||||
|
s.writeTimeout = site.WriteTimeout
|
||||||
|
}
|
||||||
|
if site.IdleTimeout != 0 {
|
||||||
|
s.idleTimeout = site.IdleTimeout
|
||||||
|
}
|
||||||
|
|
||||||
// copy tsig secrets
|
// copy tsig secrets
|
||||||
for key, secret := range site.TsigSecret {
|
for key, secret := range site.TsigSecret {
|
||||||
s.tsigSecret[key] = secret
|
s.tsigSecret[key] = secret
|
||||||
|
@ -130,11 +147,22 @@ var _ caddy.GracefulServer = &Server{}
|
||||||
// This implements caddy.TCPServer interface.
|
// This implements caddy.TCPServer interface.
|
||||||
func (s *Server) Serve(l net.Listener) error {
|
func (s *Server) Serve(l net.Listener) error {
|
||||||
s.m.Lock()
|
s.m.Lock()
|
||||||
s.server[tcp] = &dns.Server{Listener: l, Net: "tcp", Handler: dns.HandlerFunc(func(w dns.ResponseWriter, r *dns.Msg) {
|
|
||||||
|
s.server[tcp] = &dns.Server{Listener: l,
|
||||||
|
Net: "tcp",
|
||||||
|
TsigSecret: s.tsigSecret,
|
||||||
|
MaxTCPQueries: tcpMaxQueries,
|
||||||
|
ReadTimeout: s.readTimeout,
|
||||||
|
WriteTimeout: s.writeTimeout,
|
||||||
|
IdleTimeout: func() time.Duration {
|
||||||
|
return s.idleTimeout
|
||||||
|
},
|
||||||
|
Handler: dns.HandlerFunc(func(w dns.ResponseWriter, r *dns.Msg) {
|
||||||
ctx := context.WithValue(context.Background(), Key{}, s)
|
ctx := context.WithValue(context.Background(), Key{}, s)
|
||||||
ctx = context.WithValue(ctx, LoopKey{}, 0)
|
ctx = context.WithValue(ctx, LoopKey{}, 0)
|
||||||
s.ServeDNS(ctx, w, r)
|
s.ServeDNS(ctx, w, r)
|
||||||
}), TsigSecret: s.tsigSecret}
|
})}
|
||||||
|
|
||||||
s.m.Unlock()
|
s.m.Unlock()
|
||||||
|
|
||||||
return s.server[tcp].ActivateAndServe()
|
return s.server[tcp].ActivateAndServe()
|
||||||
|
@ -404,6 +432,8 @@ func errorAndMetricsFunc(server string, w dns.ResponseWriter, r *dns.Msg, rc int
|
||||||
const (
|
const (
|
||||||
tcp = 0
|
tcp = 0
|
||||||
udp = 1
|
udp = 1
|
||||||
|
|
||||||
|
tcpMaxQueries = -1
|
||||||
)
|
)
|
||||||
|
|
||||||
type (
|
type (
|
||||||
|
|
|
@ -75,9 +75,9 @@ func NewServerHTTPS(addr string, group []*Config) (*ServerHTTPS, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
srv := &http.Server{
|
srv := &http.Server{
|
||||||
ReadTimeout: 5 * time.Second,
|
ReadTimeout: s.readTimeout,
|
||||||
WriteTimeout: 10 * time.Second,
|
WriteTimeout: s.writeTimeout,
|
||||||
IdleTimeout: 120 * time.Second,
|
IdleTimeout: s.idleTimeout,
|
||||||
ErrorLog: stdlog.New(&loggerAdapter{}, "", 0),
|
ErrorLog: stdlog.New(&loggerAdapter{}, "", 0),
|
||||||
}
|
}
|
||||||
sh := &ServerHTTPS{
|
sh := &ServerHTTPS{
|
||||||
|
|
|
@ -5,6 +5,7 @@ import (
|
||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/coredns/caddy"
|
"github.com/coredns/caddy"
|
||||||
"github.com/coredns/coredns/plugin/pkg/reuseport"
|
"github.com/coredns/coredns/plugin/pkg/reuseport"
|
||||||
|
@ -50,11 +51,20 @@ func (s *ServerTLS) Serve(l net.Listener) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Only fill out the TCP server for this one.
|
// Only fill out the TCP server for this one.
|
||||||
s.server[tcp] = &dns.Server{Listener: l, Net: "tcp-tls", Handler: dns.HandlerFunc(func(w dns.ResponseWriter, r *dns.Msg) {
|
s.server[tcp] = &dns.Server{Listener: l,
|
||||||
|
Net: "tcp-tls",
|
||||||
|
MaxTCPQueries: tlsMaxQueries,
|
||||||
|
ReadTimeout: s.readTimeout,
|
||||||
|
WriteTimeout: s.writeTimeout,
|
||||||
|
IdleTimeout: func() time.Duration {
|
||||||
|
return s.idleTimeout
|
||||||
|
},
|
||||||
|
Handler: dns.HandlerFunc(func(w dns.ResponseWriter, r *dns.Msg) {
|
||||||
ctx := context.WithValue(context.Background(), Key{}, s.Server)
|
ctx := context.WithValue(context.Background(), Key{}, s.Server)
|
||||||
ctx = context.WithValue(ctx, LoopKey{}, 0)
|
ctx = context.WithValue(ctx, LoopKey{}, 0)
|
||||||
s.ServeDNS(ctx, w, r)
|
s.ServeDNS(ctx, w, r)
|
||||||
})}
|
})}
|
||||||
|
|
||||||
s.m.Unlock()
|
s.m.Unlock()
|
||||||
|
|
||||||
return s.server[tcp].ActivateAndServe()
|
return s.server[tcp].ActivateAndServe()
|
||||||
|
@ -87,3 +97,7 @@ func (s *ServerTLS) OnStartupComplete() {
|
||||||
fmt.Print(out)
|
fmt.Print(out)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
tlsMaxQueries = -1
|
||||||
|
)
|
||||||
|
|
|
@ -14,6 +14,7 @@ var Directives = []string{
|
||||||
"geoip",
|
"geoip",
|
||||||
"cancel",
|
"cancel",
|
||||||
"tls",
|
"tls",
|
||||||
|
"timeouts",
|
||||||
"reload",
|
"reload",
|
||||||
"nsid",
|
"nsid",
|
||||||
"bufsize",
|
"bufsize",
|
||||||
|
|
|
@ -28,6 +28,9 @@ func init() {
|
||||||
caddy.RegisterCaddyfileLoader("flag", caddy.LoaderFunc(confLoader))
|
caddy.RegisterCaddyfileLoader("flag", caddy.LoaderFunc(confLoader))
|
||||||
caddy.SetDefaultCaddyfileLoader("default", caddy.LoaderFunc(defaultLoader))
|
caddy.SetDefaultCaddyfileLoader("default", caddy.LoaderFunc(defaultLoader))
|
||||||
|
|
||||||
|
flag.StringVar(&dnsserver.Port, serverType+".port", dnsserver.DefaultPort, "Default port")
|
||||||
|
flag.StringVar(&dnsserver.Port, "p", dnsserver.DefaultPort, "Default port")
|
||||||
|
|
||||||
caddy.AppName = coreName
|
caddy.AppName = coreName
|
||||||
caddy.AppVersion = CoreVersion
|
caddy.AppVersion = CoreVersion
|
||||||
}
|
}
|
||||||
|
@ -170,6 +173,7 @@ var (
|
||||||
|
|
||||||
// Build information obtained with the help of -ldflags
|
// Build information obtained with the help of -ldflags
|
||||||
var (
|
var (
|
||||||
|
// nolint
|
||||||
appVersion = "(untracked dev build)" // inferred at startup
|
appVersion = "(untracked dev build)" // inferred at startup
|
||||||
devBuild = true // inferred at startup
|
devBuild = true // inferred at startup
|
||||||
|
|
||||||
|
|
|
@ -2,7 +2,7 @@ package coremain
|
||||||
|
|
||||||
// Various CoreDNS constants.
|
// Various CoreDNS constants.
|
||||||
const (
|
const (
|
||||||
CoreVersion = "1.10.0"
|
CoreVersion = "1.10.1"
|
||||||
coreName = "CoreDNS"
|
coreName = "CoreDNS"
|
||||||
serverType = "dns"
|
serverType = "dns"
|
||||||
)
|
)
|
||||||
|
|
|
@ -10,8 +10,7 @@ With *cache* enabled, all records except zone transfers and metadata records wil
|
||||||
3600s. Caching is mostly useful in a scenario when fetching data from the backend (upstream,
|
3600s. Caching is mostly useful in a scenario when fetching data from the backend (upstream,
|
||||||
database, etc.) is expensive.
|
database, etc.) is expensive.
|
||||||
|
|
||||||
*Cache* will change the query to enable DNSSEC (DNSSEC OK; DO) if it passes through the plugin. If
|
*Cache* will pass DNSSEC (DNSSEC OK; DO) options through the plugin for upstream queries.
|
||||||
the client didn't request any DNSSEC (records), these are filtered out when replying.
|
|
||||||
|
|
||||||
This plugin can only be used once per Server Block.
|
This plugin can only be used once per Server Block.
|
||||||
|
|
||||||
|
@ -40,6 +39,7 @@ cache [TTL] [ZONES...] {
|
||||||
serve_stale [DURATION] [REFRESH_MODE]
|
serve_stale [DURATION] [REFRESH_MODE]
|
||||||
servfail DURATION
|
servfail DURATION
|
||||||
disable success|denial [ZONES...]
|
disable success|denial [ZONES...]
|
||||||
|
keepttl
|
||||||
}
|
}
|
||||||
~~~
|
~~~
|
||||||
|
|
||||||
|
@ -70,6 +70,11 @@ cache [TTL] [ZONES...] {
|
||||||
greater than 5 minutes.
|
greater than 5 minutes.
|
||||||
* `disable` disable the success or denial cache for the listed **ZONES**. If no **ZONES** are given, the specified
|
* `disable` disable the success or denial cache for the listed **ZONES**. If no **ZONES** are given, the specified
|
||||||
cache will be disabled for all zones.
|
cache will be disabled for all zones.
|
||||||
|
* `keepttl` do not age TTL when serving responses from cache. The entry will still be removed from cache
|
||||||
|
when the TTL expires as normal, but until it expires responses will include the original TTL instead
|
||||||
|
of the remaining TTL. This can be useful if CoreDNS is used as an authoritative server and you want
|
||||||
|
to serve a consistent TTL to downstream clients. This is **NOT** recommended when CoreDNS is caching
|
||||||
|
records it is not authoritative for because it could result in downstream clients using stale answers.
|
||||||
|
|
||||||
## Capacity and Eviction
|
## Capacity and Eviction
|
||||||
|
|
||||||
|
|
|
@ -48,6 +48,9 @@ type Cache struct {
|
||||||
pexcept []string
|
pexcept []string
|
||||||
nexcept []string
|
nexcept []string
|
||||||
|
|
||||||
|
// Keep ttl option
|
||||||
|
keepttl bool
|
||||||
|
|
||||||
// Testing.
|
// Testing.
|
||||||
now func() time.Time
|
now func() time.Time
|
||||||
}
|
}
|
||||||
|
@ -76,7 +79,7 @@ func New() *Cache {
|
||||||
// key returns key under which we store the item, -1 will be returned if we don't store the message.
|
// key returns key under which we store the item, -1 will be returned if we don't store the message.
|
||||||
// Currently we do not cache Truncated, errors zone transfers or dynamic update messages.
|
// Currently we do not cache Truncated, errors zone transfers or dynamic update messages.
|
||||||
// qname holds the already lowercased qname.
|
// qname holds the already lowercased qname.
|
||||||
func key(qname string, m *dns.Msg, t response.Type) (bool, uint64) {
|
func key(qname string, m *dns.Msg, t response.Type, do bool) (bool, uint64) {
|
||||||
// We don't store truncated responses.
|
// We don't store truncated responses.
|
||||||
if m.Truncated {
|
if m.Truncated {
|
||||||
return false, 0
|
return false, 0
|
||||||
|
@ -86,11 +89,21 @@ func key(qname string, m *dns.Msg, t response.Type) (bool, uint64) {
|
||||||
return false, 0
|
return false, 0
|
||||||
}
|
}
|
||||||
|
|
||||||
return true, hash(qname, m.Question[0].Qtype)
|
return true, hash(qname, m.Question[0].Qtype, do)
|
||||||
}
|
}
|
||||||
|
|
||||||
func hash(qname string, qtype uint16) uint64 {
|
var one = []byte("1")
|
||||||
|
var zero = []byte("0")
|
||||||
|
|
||||||
|
func hash(qname string, qtype uint16, do bool) uint64 {
|
||||||
h := fnv.New64()
|
h := fnv.New64()
|
||||||
|
|
||||||
|
if do {
|
||||||
|
h.Write(one)
|
||||||
|
} else {
|
||||||
|
h.Write(zero)
|
||||||
|
}
|
||||||
|
|
||||||
h.Write([]byte{byte(qtype >> 8)})
|
h.Write([]byte{byte(qtype >> 8)})
|
||||||
h.Write([]byte{byte(qtype)})
|
h.Write([]byte{byte(qtype)})
|
||||||
h.Write([]byte(qname))
|
h.Write([]byte(qname))
|
||||||
|
@ -145,6 +158,7 @@ func newPrefetchResponseWriter(server string, state request.Request, c *Cache) *
|
||||||
Cache: c,
|
Cache: c,
|
||||||
state: state,
|
state: state,
|
||||||
server: server,
|
server: server,
|
||||||
|
do: state.Do(),
|
||||||
prefetch: true,
|
prefetch: true,
|
||||||
remoteAddr: addr,
|
remoteAddr: addr,
|
||||||
}
|
}
|
||||||
|
@ -163,7 +177,7 @@ func (w *ResponseWriter) WriteMsg(res *dns.Msg) error {
|
||||||
mt, _ := response.Typify(res, w.now().UTC())
|
mt, _ := response.Typify(res, w.now().UTC())
|
||||||
|
|
||||||
// key returns empty string for anything we don't want to cache.
|
// key returns empty string for anything we don't want to cache.
|
||||||
hasKey, key := key(w.state.Name(), res, mt)
|
hasKey, key := key(w.state.Name(), res, mt, w.do)
|
||||||
|
|
||||||
msgTTL := dnsutil.MinimalTTL(res, mt)
|
msgTTL := dnsutil.MinimalTTL(res, mt)
|
||||||
var duration time.Duration
|
var duration time.Duration
|
||||||
|
@ -191,11 +205,10 @@ func (w *ResponseWriter) WriteMsg(res *dns.Msg) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Apply capped TTL to this reply to avoid jarring TTL experience 1799 -> 8 (e.g.)
|
// Apply capped TTL to this reply to avoid jarring TTL experience 1799 -> 8 (e.g.)
|
||||||
// We also may need to filter out DNSSEC records, see toMsg() for similar code.
|
|
||||||
ttl := uint32(duration.Seconds())
|
ttl := uint32(duration.Seconds())
|
||||||
res.Answer = filterRRSlice(res.Answer, ttl, w.do, false)
|
res.Answer = filterRRSlice(res.Answer, ttl, false)
|
||||||
res.Ns = filterRRSlice(res.Ns, ttl, w.do, false)
|
res.Ns = filterRRSlice(res.Ns, ttl, false)
|
||||||
res.Extra = filterRRSlice(res.Extra, ttl, w.do, false)
|
res.Extra = filterRRSlice(res.Extra, ttl, false)
|
||||||
|
|
||||||
if !w.do && !w.ad {
|
if !w.do && !w.ad {
|
||||||
// unset AD bit if requester is not OK with DNSSEC
|
// unset AD bit if requester is not OK with DNSSEC
|
||||||
|
|
|
@ -2,35 +2,13 @@ package cache
|
||||||
|
|
||||||
import "github.com/miekg/dns"
|
import "github.com/miekg/dns"
|
||||||
|
|
||||||
// isDNSSEC returns true if r is a DNSSEC record. NSEC,NSEC3,DS and RRSIG/SIG
|
// filterRRSlice filters out OPT RRs, and sets all RR TTLs to ttl.
|
||||||
// are DNSSEC records. DNSKEYs is not in this list on the assumption that the
|
// If dup is true the RRs in rrs are _copied_ into the slice that is
|
||||||
// client explicitly asked for it.
|
|
||||||
func isDNSSEC(r dns.RR) bool {
|
|
||||||
switch r.Header().Rrtype {
|
|
||||||
case dns.TypeNSEC:
|
|
||||||
return true
|
|
||||||
case dns.TypeNSEC3:
|
|
||||||
return true
|
|
||||||
case dns.TypeDS:
|
|
||||||
return true
|
|
||||||
case dns.TypeRRSIG:
|
|
||||||
return true
|
|
||||||
case dns.TypeSIG:
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// filterRRSlice filters rrs and removes DNSSEC RRs when do is false. In the returned slice
|
|
||||||
// the TTLs are set to ttl. If dup is true the RRs in rrs are _copied_ into the slice that is
|
|
||||||
// returned.
|
// returned.
|
||||||
func filterRRSlice(rrs []dns.RR, ttl uint32, do, dup bool) []dns.RR {
|
func filterRRSlice(rrs []dns.RR, ttl uint32, dup bool) []dns.RR {
|
||||||
j := 0
|
j := 0
|
||||||
rs := make([]dns.RR, len(rrs))
|
rs := make([]dns.RR, len(rrs))
|
||||||
for _, r := range rrs {
|
for _, r := range rrs {
|
||||||
if !do && isDNSSEC(r) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if r.Header().Rrtype == dns.TypeOPT {
|
if r.Header().Rrtype == dns.TypeOPT {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
|
@ -28,12 +28,10 @@ func (c *Cache) ServeDNS(ctx context.Context, w dns.ResponseWriter, r *dns.Msg)
|
||||||
now := c.now().UTC()
|
now := c.now().UTC()
|
||||||
server := metrics.WithServer(ctx)
|
server := metrics.WithServer(ctx)
|
||||||
|
|
||||||
// On cache miss, if the request has the OPT record and the DO bit set we leave the message as-is. If there isn't a DO bit
|
// On cache refresh, we will just use the DO bit from the incoming query for the refresh since we key our cache
|
||||||
// set we will modify the request to _add_ one. This means we will always do DNSSEC lookups on cache misses.
|
// with the query DO bit. That means two separate cache items for the query DO bit true or false. In the situation
|
||||||
// When writing to cache, any DNSSEC RRs in the response are written to cache with the response.
|
// in which upstream doesn't support DNSSEC, the two cache items will effectively be the same. Regardless, any
|
||||||
// When sending a response to a non-DNSSEC client, we remove DNSSEC RRs from the response. We use a 2048 buffer size, which is
|
// DNSSEC RRs in the response are written to cache with the response.
|
||||||
// less than 4096 (and older default) and more than 1024 which may be too small. We might need to tweaks this
|
|
||||||
// value to be smaller still to prevent UDP fragmentation?
|
|
||||||
|
|
||||||
ttl := 0
|
ttl := 0
|
||||||
i := c.getIgnoreTTL(now, state, server)
|
i := c.getIgnoreTTL(now, state, server)
|
||||||
|
@ -73,6 +71,11 @@ func (c *Cache) ServeDNS(ctx context.Context, w dns.ResponseWriter, r *dns.Msg)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if c.keepttl {
|
||||||
|
// If keepttl is enabled we fake the current time to the stored
|
||||||
|
// one so that we always get the original TTL
|
||||||
|
now = i.stored
|
||||||
|
}
|
||||||
resp := i.toMsg(r, now, do, ad)
|
resp := i.toMsg(r, now, do, ad)
|
||||||
w.WriteMsg(resp)
|
w.WriteMsg(resp)
|
||||||
return dns.RcodeSuccess, nil
|
return dns.RcodeSuccess, nil
|
||||||
|
@ -101,9 +104,6 @@ func (c *Cache) doPrefetch(ctx context.Context, state request.Request, cw *Respo
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Cache) doRefresh(ctx context.Context, state request.Request, cw dns.ResponseWriter) (int, error) {
|
func (c *Cache) doRefresh(ctx context.Context, state request.Request, cw dns.ResponseWriter) (int, error) {
|
||||||
if !state.Do() {
|
|
||||||
setDo(state.Req)
|
|
||||||
}
|
|
||||||
return plugin.NextOrFailure(c.Name(), c.Next, ctx, cw, state.Req)
|
return plugin.NextOrFailure(c.Name(), c.Next, ctx, cw, state.Req)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -121,7 +121,7 @@ func (c *Cache) Name() string { return "cache" }
|
||||||
|
|
||||||
// getIgnoreTTL unconditionally returns an item if it exists in the cache.
|
// getIgnoreTTL unconditionally returns an item if it exists in the cache.
|
||||||
func (c *Cache) getIgnoreTTL(now time.Time, state request.Request, server string) *item {
|
func (c *Cache) getIgnoreTTL(now time.Time, state request.Request, server string) *item {
|
||||||
k := hash(state.Name(), state.QType())
|
k := hash(state.Name(), state.QType(), state.Do())
|
||||||
cacheRequests.WithLabelValues(server, c.zonesMetricLabel, c.viewMetricLabel).Inc()
|
cacheRequests.WithLabelValues(server, c.zonesMetricLabel, c.viewMetricLabel).Inc()
|
||||||
|
|
||||||
if i, ok := c.ncache.Get(k); ok {
|
if i, ok := c.ncache.Get(k); ok {
|
||||||
|
@ -145,7 +145,7 @@ func (c *Cache) getIgnoreTTL(now time.Time, state request.Request, server string
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Cache) exists(state request.Request) *item {
|
func (c *Cache) exists(state request.Request) *item {
|
||||||
k := hash(state.Name(), state.QType())
|
k := hash(state.Name(), state.QType(), state.Do())
|
||||||
if i, ok := c.ncache.Get(k); ok {
|
if i, ok := c.ncache.Get(k); ok {
|
||||||
return i.(*item)
|
return i.(*item)
|
||||||
}
|
}
|
||||||
|
@ -154,22 +154,3 @@ func (c *Cache) exists(state request.Request) *item {
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// setDo sets the DO bit and UDP buffer size in the message m.
|
|
||||||
func setDo(m *dns.Msg) {
|
|
||||||
o := m.IsEdns0()
|
|
||||||
if o != nil {
|
|
||||||
o.SetDo()
|
|
||||||
o.SetUDPSize(defaultUDPBufSize)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
o = &dns.OPT{Hdr: dns.RR_Header{Name: ".", Rrtype: dns.TypeOPT}}
|
|
||||||
o.SetDo()
|
|
||||||
o.SetUDPSize(defaultUDPBufSize)
|
|
||||||
m.Extra = append(m.Extra, o)
|
|
||||||
}
|
|
||||||
|
|
||||||
// defaultUDPBufsize is the bufsize the cache plugin uses on outgoing requests that don't
|
|
||||||
// have an OPT RR.
|
|
||||||
const defaultUDPBufSize = 2048
|
|
||||||
|
|
|
@ -87,9 +87,9 @@ func (i *item) toMsg(m *dns.Msg, now time.Time, do bool, ad bool) *dns.Msg {
|
||||||
m1.Extra = make([]dns.RR, len(i.Extra))
|
m1.Extra = make([]dns.RR, len(i.Extra))
|
||||||
|
|
||||||
ttl := uint32(i.ttl(now))
|
ttl := uint32(i.ttl(now))
|
||||||
m1.Answer = filterRRSlice(i.Answer, ttl, do, true)
|
m1.Answer = filterRRSlice(i.Answer, ttl, true)
|
||||||
m1.Ns = filterRRSlice(i.Ns, ttl, do, true)
|
m1.Ns = filterRRSlice(i.Ns, ttl, true)
|
||||||
m1.Extra = filterRRSlice(i.Extra, ttl, do, true)
|
m1.Extra = filterRRSlice(i.Extra, ttl, true)
|
||||||
|
|
||||||
return m1
|
return m1
|
||||||
}
|
}
|
||||||
|
|
|
@ -240,6 +240,11 @@ func cacheParse(c *caddy.Controller) (*Cache, error) {
|
||||||
default:
|
default:
|
||||||
return nil, fmt.Errorf("cache type for disable must be %q or %q", Success, Denial)
|
return nil, fmt.Errorf("cache type for disable must be %q or %q", Success, Denial)
|
||||||
}
|
}
|
||||||
|
case "keepttl":
|
||||||
|
if len(args) != 0 {
|
||||||
|
return nil, c.ArgErr()
|
||||||
|
}
|
||||||
|
ca.keepttl = true
|
||||||
default:
|
default:
|
||||||
return nil, c.ArgErr()
|
return nil, c.ArgErr()
|
||||||
}
|
}
|
||||||
|
|
|
@ -36,8 +36,7 @@ func SupportedOption(option uint16) bool {
|
||||||
|
|
||||||
// Version checks the EDNS version in the request. If error
|
// Version checks the EDNS version in the request. If error
|
||||||
// is nil everything is OK and we can invoke the plugin. If non-nil, the
|
// is nil everything is OK and we can invoke the plugin. If non-nil, the
|
||||||
// returned Msg is valid to be returned to the client (and should). For some
|
// returned Msg is valid to be returned to the client (and should).
|
||||||
// reason this response should not contain a question RR in the question section.
|
|
||||||
func Version(req *dns.Msg) (*dns.Msg, error) {
|
func Version(req *dns.Msg) (*dns.Msg, error) {
|
||||||
opt := req.IsEdns0()
|
opt := req.IsEdns0()
|
||||||
if opt == nil {
|
if opt == nil {
|
||||||
|
@ -48,8 +47,6 @@ func Version(req *dns.Msg) (*dns.Msg, error) {
|
||||||
}
|
}
|
||||||
m := new(dns.Msg)
|
m := new(dns.Msg)
|
||||||
m.SetReply(req)
|
m.SetReply(req)
|
||||||
// zero out question section, wtf.
|
|
||||||
m.Question = nil
|
|
||||||
|
|
||||||
o := new(dns.OPT)
|
o := new(dns.OPT)
|
||||||
o.Hdr.Name = "."
|
o.Hdr.Name = "."
|
||||||
|
|
|
@ -12,7 +12,7 @@ import (
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
jose "gopkg.in/square/go-jose.v2"
|
jose "github.com/go-jose/go-jose/v3"
|
||||||
)
|
)
|
||||||
|
|
||||||
// StaticKeySet is a verifier that validates JWT against a static set of public keys.
|
// StaticKeySet is a verifier that validates JWT against a static set of public keys.
|
||||||
|
|
|
@ -12,8 +12,8 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
jose "github.com/go-jose/go-jose/v3"
|
||||||
"golang.org/x/oauth2"
|
"golang.org/x/oauth2"
|
||||||
jose "gopkg.in/square/go-jose.v2"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
|
|
@ -15,7 +15,7 @@ type roffRenderer struct {
|
||||||
extensions blackfriday.Extensions
|
extensions blackfriday.Extensions
|
||||||
listCounters []int
|
listCounters []int
|
||||||
firstHeader bool
|
firstHeader bool
|
||||||
defineTerm bool
|
firstDD bool
|
||||||
listDepth int
|
listDepth int
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -42,7 +42,8 @@ const (
|
||||||
quoteCloseTag = "\n.RE\n"
|
quoteCloseTag = "\n.RE\n"
|
||||||
listTag = "\n.RS\n"
|
listTag = "\n.RS\n"
|
||||||
listCloseTag = "\n.RE\n"
|
listCloseTag = "\n.RE\n"
|
||||||
arglistTag = "\n.TP\n"
|
dtTag = "\n.TP\n"
|
||||||
|
dd2Tag = "\n"
|
||||||
tableStart = "\n.TS\nallbox;\n"
|
tableStart = "\n.TS\nallbox;\n"
|
||||||
tableEnd = ".TE\n"
|
tableEnd = ".TE\n"
|
||||||
tableCellStart = "T{\n"
|
tableCellStart = "T{\n"
|
||||||
|
@ -90,7 +91,7 @@ func (r *roffRenderer) RenderNode(w io.Writer, node *blackfriday.Node, entering
|
||||||
|
|
||||||
switch node.Type {
|
switch node.Type {
|
||||||
case blackfriday.Text:
|
case blackfriday.Text:
|
||||||
r.handleText(w, node, entering)
|
escapeSpecialChars(w, node.Literal)
|
||||||
case blackfriday.Softbreak:
|
case blackfriday.Softbreak:
|
||||||
out(w, crTag)
|
out(w, crTag)
|
||||||
case blackfriday.Hardbreak:
|
case blackfriday.Hardbreak:
|
||||||
|
@ -150,40 +151,21 @@ func (r *roffRenderer) RenderNode(w io.Writer, node *blackfriday.Node, entering
|
||||||
out(w, codeCloseTag)
|
out(w, codeCloseTag)
|
||||||
case blackfriday.Table:
|
case blackfriday.Table:
|
||||||
r.handleTable(w, node, entering)
|
r.handleTable(w, node, entering)
|
||||||
case blackfriday.TableCell:
|
|
||||||
r.handleTableCell(w, node, entering)
|
|
||||||
case blackfriday.TableHead:
|
case blackfriday.TableHead:
|
||||||
case blackfriday.TableBody:
|
case blackfriday.TableBody:
|
||||||
case blackfriday.TableRow:
|
case blackfriday.TableRow:
|
||||||
// no action as cell entries do all the nroff formatting
|
// no action as cell entries do all the nroff formatting
|
||||||
return blackfriday.GoToNext
|
return blackfriday.GoToNext
|
||||||
|
case blackfriday.TableCell:
|
||||||
|
r.handleTableCell(w, node, entering)
|
||||||
|
case blackfriday.HTMLSpan:
|
||||||
|
// ignore other HTML tags
|
||||||
default:
|
default:
|
||||||
fmt.Fprintln(os.Stderr, "WARNING: go-md2man does not handle node type "+node.Type.String())
|
fmt.Fprintln(os.Stderr, "WARNING: go-md2man does not handle node type "+node.Type.String())
|
||||||
}
|
}
|
||||||
return walkAction
|
return walkAction
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *roffRenderer) handleText(w io.Writer, node *blackfriday.Node, entering bool) {
|
|
||||||
var (
|
|
||||||
start, end string
|
|
||||||
)
|
|
||||||
// handle special roff table cell text encapsulation
|
|
||||||
if node.Parent.Type == blackfriday.TableCell {
|
|
||||||
if len(node.Literal) > 30 {
|
|
||||||
start = tableCellStart
|
|
||||||
end = tableCellEnd
|
|
||||||
} else {
|
|
||||||
// end rows that aren't terminated by "tableCellEnd" with a cr if end of row
|
|
||||||
if node.Parent.Next == nil && !node.Parent.IsHeader {
|
|
||||||
end = crTag
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
out(w, start)
|
|
||||||
escapeSpecialChars(w, node.Literal)
|
|
||||||
out(w, end)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *roffRenderer) handleHeading(w io.Writer, node *blackfriday.Node, entering bool) {
|
func (r *roffRenderer) handleHeading(w io.Writer, node *blackfriday.Node, entering bool) {
|
||||||
if entering {
|
if entering {
|
||||||
switch node.Level {
|
switch node.Level {
|
||||||
|
@ -230,15 +212,20 @@ func (r *roffRenderer) handleItem(w io.Writer, node *blackfriday.Node, entering
|
||||||
if node.ListFlags&blackfriday.ListTypeOrdered != 0 {
|
if node.ListFlags&blackfriday.ListTypeOrdered != 0 {
|
||||||
out(w, fmt.Sprintf(".IP \"%3d.\" 5\n", r.listCounters[len(r.listCounters)-1]))
|
out(w, fmt.Sprintf(".IP \"%3d.\" 5\n", r.listCounters[len(r.listCounters)-1]))
|
||||||
r.listCounters[len(r.listCounters)-1]++
|
r.listCounters[len(r.listCounters)-1]++
|
||||||
|
} else if node.ListFlags&blackfriday.ListTypeTerm != 0 {
|
||||||
|
// DT (definition term): line just before DD (see below).
|
||||||
|
out(w, dtTag)
|
||||||
|
r.firstDD = true
|
||||||
} else if node.ListFlags&blackfriday.ListTypeDefinition != 0 {
|
} else if node.ListFlags&blackfriday.ListTypeDefinition != 0 {
|
||||||
// state machine for handling terms and following definitions
|
// DD (definition description): line that starts with ": ".
|
||||||
// since blackfriday does not distinguish them properly, nor
|
//
|
||||||
// does it seperate them into separate lists as it should
|
// We have to distinguish between the first DD and the
|
||||||
if !r.defineTerm {
|
// subsequent ones, as there should be no vertical
|
||||||
out(w, arglistTag)
|
// whitespace between the DT and the first DD.
|
||||||
r.defineTerm = true
|
if r.firstDD {
|
||||||
|
r.firstDD = false
|
||||||
} else {
|
} else {
|
||||||
r.defineTerm = false
|
out(w, dd2Tag)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
out(w, ".IP \\(bu 2\n")
|
out(w, ".IP \\(bu 2\n")
|
||||||
|
@ -261,28 +248,41 @@ func (r *roffRenderer) handleTable(w io.Writer, node *blackfriday.Node, entering
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *roffRenderer) handleTableCell(w io.Writer, node *blackfriday.Node, entering bool) {
|
func (r *roffRenderer) handleTableCell(w io.Writer, node *blackfriday.Node, entering bool) {
|
||||||
var (
|
|
||||||
start, end string
|
|
||||||
)
|
|
||||||
if node.IsHeader {
|
|
||||||
start = codespanTag
|
|
||||||
end = codespanCloseTag
|
|
||||||
}
|
|
||||||
if entering {
|
if entering {
|
||||||
|
var start string
|
||||||
if node.Prev != nil && node.Prev.Type == blackfriday.TableCell {
|
if node.Prev != nil && node.Prev.Type == blackfriday.TableCell {
|
||||||
out(w, "\t"+start)
|
start = "\t"
|
||||||
} else {
|
|
||||||
out(w, start)
|
|
||||||
}
|
}
|
||||||
|
if node.IsHeader {
|
||||||
|
start += codespanTag
|
||||||
|
} else if nodeLiteralSize(node) > 30 {
|
||||||
|
start += tableCellStart
|
||||||
|
}
|
||||||
|
out(w, start)
|
||||||
} else {
|
} else {
|
||||||
// need to carriage return if we are at the end of the header row
|
var end string
|
||||||
if node.IsHeader && node.Next == nil {
|
if node.IsHeader {
|
||||||
end = end + crTag
|
end = codespanCloseTag
|
||||||
|
} else if nodeLiteralSize(node) > 30 {
|
||||||
|
end = tableCellEnd
|
||||||
|
}
|
||||||
|
if node.Next == nil && end != tableCellEnd {
|
||||||
|
// Last cell: need to carriage return if we are at the end of the
|
||||||
|
// header row and content isn't wrapped in a "tablecell"
|
||||||
|
end += crTag
|
||||||
}
|
}
|
||||||
out(w, end)
|
out(w, end)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func nodeLiteralSize(node *blackfriday.Node) int {
|
||||||
|
total := 0
|
||||||
|
for n := node.FirstChild; n != nil; n = n.FirstChild {
|
||||||
|
total += len(n.Literal)
|
||||||
|
}
|
||||||
|
return total
|
||||||
|
}
|
||||||
|
|
||||||
// because roff format requires knowing the column count before outputting any table
|
// because roff format requires knowing the column count before outputting any table
|
||||||
// data we need to walk a table tree and count the columns
|
// data we need to walk a table tree and count the columns
|
||||||
func countColumns(node *blackfriday.Node) int {
|
func countColumns(node *blackfriday.Node) int {
|
||||||
|
@ -309,15 +309,6 @@ func out(w io.Writer, output string) {
|
||||||
io.WriteString(w, output) // nolint: errcheck
|
io.WriteString(w, output) // nolint: errcheck
|
||||||
}
|
}
|
||||||
|
|
||||||
func needsBackslash(c byte) bool {
|
|
||||||
for _, r := range []byte("-_&\\~") {
|
|
||||||
if c == r {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
func escapeSpecialChars(w io.Writer, text []byte) {
|
func escapeSpecialChars(w io.Writer, text []byte) {
|
||||||
for i := 0; i < len(text); i++ {
|
for i := 0; i < len(text); i++ {
|
||||||
// escape initial apostrophe or period
|
// escape initial apostrophe or period
|
||||||
|
@ -328,7 +319,7 @@ func escapeSpecialChars(w io.Writer, text []byte) {
|
||||||
// directly copy normal characters
|
// directly copy normal characters
|
||||||
org := i
|
org := i
|
||||||
|
|
||||||
for i < len(text) && !needsBackslash(text[i]) {
|
for i < len(text) && text[i] != '\\' {
|
||||||
i++
|
i++
|
||||||
}
|
}
|
||||||
if i > org {
|
if i > org {
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
# Setup a Global .gitignore for OS and editor generated files:
|
# go test -c output
|
||||||
# https://help.github.com/articles/ignoring-files
|
*.test
|
||||||
# git config --global core.excludesfile ~/.gitignore_global
|
*.test.exe
|
||||||
|
|
||||||
.vagrant
|
# Output of go build ./cmd/fsnotify
|
||||||
*.sublime-project
|
/fsnotify
|
||||||
|
|
|
@ -0,0 +1,2 @@
|
||||||
|
Chris Howey <howeyc@gmail.com> <chris@howey.me>
|
||||||
|
Nathan Youngman <git@nathany.com> <4566+nathany@users.noreply.github.com>
|
|
@ -1,36 +0,0 @@
|
||||||
sudo: false
|
|
||||||
language: go
|
|
||||||
|
|
||||||
go:
|
|
||||||
- "stable"
|
|
||||||
- "1.11.x"
|
|
||||||
- "1.10.x"
|
|
||||||
- "1.9.x"
|
|
||||||
|
|
||||||
matrix:
|
|
||||||
include:
|
|
||||||
- go: "stable"
|
|
||||||
env: GOLINT=true
|
|
||||||
allow_failures:
|
|
||||||
- go: tip
|
|
||||||
fast_finish: true
|
|
||||||
|
|
||||||
|
|
||||||
before_install:
|
|
||||||
- if [ ! -z "${GOLINT}" ]; then go get -u golang.org/x/lint/golint; fi
|
|
||||||
|
|
||||||
script:
|
|
||||||
- go test --race ./...
|
|
||||||
|
|
||||||
after_script:
|
|
||||||
- test -z "$(gofmt -s -l -w . | tee /dev/stderr)"
|
|
||||||
- if [ ! -z "${GOLINT}" ]; then echo running golint; golint --set_exit_status ./...; else echo skipping golint; fi
|
|
||||||
- go vet ./...
|
|
||||||
|
|
||||||
os:
|
|
||||||
- linux
|
|
||||||
- osx
|
|
||||||
- windows
|
|
||||||
|
|
||||||
notifications:
|
|
||||||
email: false
|
|
|
@ -1,52 +0,0 @@
|
||||||
# Names should be added to this file as
|
|
||||||
# Name or Organization <email address>
|
|
||||||
# The email address is not required for organizations.
|
|
||||||
|
|
||||||
# You can update this list using the following command:
|
|
||||||
#
|
|
||||||
# $ git shortlog -se | awk '{print $2 " " $3 " " $4}'
|
|
||||||
|
|
||||||
# Please keep the list sorted.
|
|
||||||
|
|
||||||
Aaron L <aaron@bettercoder.net>
|
|
||||||
Adrien Bustany <adrien@bustany.org>
|
|
||||||
Amit Krishnan <amit.krishnan@oracle.com>
|
|
||||||
Anmol Sethi <me@anmol.io>
|
|
||||||
Bjørn Erik Pedersen <bjorn.erik.pedersen@gmail.com>
|
|
||||||
Bruno Bigras <bigras.bruno@gmail.com>
|
|
||||||
Caleb Spare <cespare@gmail.com>
|
|
||||||
Case Nelson <case@teammating.com>
|
|
||||||
Chris Howey <chris@howey.me> <howeyc@gmail.com>
|
|
||||||
Christoffer Buchholz <christoffer.buchholz@gmail.com>
|
|
||||||
Daniel Wagner-Hall <dawagner@gmail.com>
|
|
||||||
Dave Cheney <dave@cheney.net>
|
|
||||||
Evan Phoenix <evan@fallingsnow.net>
|
|
||||||
Francisco Souza <f@souza.cc>
|
|
||||||
Hari haran <hariharan.uno@gmail.com>
|
|
||||||
John C Barstow
|
|
||||||
Kelvin Fo <vmirage@gmail.com>
|
|
||||||
Ken-ichirou MATSUZAWA <chamas@h4.dion.ne.jp>
|
|
||||||
Matt Layher <mdlayher@gmail.com>
|
|
||||||
Nathan Youngman <git@nathany.com>
|
|
||||||
Nickolai Zeldovich <nickolai@csail.mit.edu>
|
|
||||||
Patrick <patrick@dropbox.com>
|
|
||||||
Paul Hammond <paul@paulhammond.org>
|
|
||||||
Pawel Knap <pawelknap88@gmail.com>
|
|
||||||
Pieter Droogendijk <pieter@binky.org.uk>
|
|
||||||
Pursuit92 <JoshChase@techpursuit.net>
|
|
||||||
Riku Voipio <riku.voipio@linaro.org>
|
|
||||||
Rob Figueiredo <robfig@gmail.com>
|
|
||||||
Rodrigo Chiossi <rodrigochiossi@gmail.com>
|
|
||||||
Slawek Ligus <root@ooz.ie>
|
|
||||||
Soge Zhang <zhssoge@gmail.com>
|
|
||||||
Tiffany Jernigan <tiffany.jernigan@intel.com>
|
|
||||||
Tilak Sharma <tilaks@google.com>
|
|
||||||
Tom Payne <twpayne@gmail.com>
|
|
||||||
Travis Cline <travis.cline@gmail.com>
|
|
||||||
Tudor Golubenco <tudor.g@gmail.com>
|
|
||||||
Vahe Khachikyan <vahe@live.ca>
|
|
||||||
Yukang <moorekang@gmail.com>
|
|
||||||
bronze1man <bronze1man@gmail.com>
|
|
||||||
debrando <denis.brandolini@gmail.com>
|
|
||||||
henrikedwards <henrik.edwards@gmail.com>
|
|
||||||
铁哥 <guotie.9@gmail.com>
|
|
|
@ -1,6 +1,159 @@
|
||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
## v1.4.7 / 2018-01-09
|
All notable changes to this project will be documented in this file.
|
||||||
|
|
||||||
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
||||||
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||||
|
|
||||||
|
## [Unreleased]
|
||||||
|
|
||||||
|
Nothing yet.
|
||||||
|
|
||||||
|
## [1.6.0] - 2022-10-13
|
||||||
|
|
||||||
|
This version of fsnotify needs Go 1.16 (this was already the case since 1.5.1,
|
||||||
|
but not documented). It also increases the minimum Linux version to 2.6.32.
|
||||||
|
|
||||||
|
### Additions
|
||||||
|
|
||||||
|
- all: add `Event.Has()` and `Op.Has()` ([#477])
|
||||||
|
|
||||||
|
This makes checking events a lot easier; for example:
|
||||||
|
|
||||||
|
if event.Op&Write == Write && !(event.Op&Remove == Remove) {
|
||||||
|
}
|
||||||
|
|
||||||
|
Becomes:
|
||||||
|
|
||||||
|
if event.Has(Write) && !event.Has(Remove) {
|
||||||
|
}
|
||||||
|
|
||||||
|
- all: add cmd/fsnotify ([#463])
|
||||||
|
|
||||||
|
A command-line utility for testing and some examples.
|
||||||
|
|
||||||
|
### Changes and fixes
|
||||||
|
|
||||||
|
- inotify: don't ignore events for files that don't exist ([#260], [#470])
|
||||||
|
|
||||||
|
Previously the inotify watcher would call `os.Lstat()` to check if a file
|
||||||
|
still exists before emitting events.
|
||||||
|
|
||||||
|
This was inconsistent with other platforms and resulted in inconsistent event
|
||||||
|
reporting (e.g. when a file is quickly removed and re-created), and generally
|
||||||
|
a source of confusion. It was added in 2013 to fix a memory leak that no
|
||||||
|
longer exists.
|
||||||
|
|
||||||
|
- all: return `ErrNonExistentWatch` when `Remove()` is called on a path that's
|
||||||
|
not watched ([#460])
|
||||||
|
|
||||||
|
- inotify: replace epoll() with non-blocking inotify ([#434])
|
||||||
|
|
||||||
|
Non-blocking inotify was not generally available at the time this library was
|
||||||
|
written in 2014, but now it is. As a result, the minimum Linux version is
|
||||||
|
bumped from 2.6.27 to 2.6.32. This hugely simplifies the code and is faster.
|
||||||
|
|
||||||
|
- kqueue: don't check for events every 100ms ([#480])
|
||||||
|
|
||||||
|
The watcher would wake up every 100ms, even when there was nothing to do. Now
|
||||||
|
it waits until there is something to do.
|
||||||
|
|
||||||
|
- macos: retry opening files on EINTR ([#475])
|
||||||
|
|
||||||
|
- kqueue: skip unreadable files ([#479])
|
||||||
|
|
||||||
|
kqueue requires a file descriptor for every file in a directory; this would
|
||||||
|
fail if a file was unreadable by the current user. Now these files are simply
|
||||||
|
skipped.
|
||||||
|
|
||||||
|
- windows: fix renaming a watched directory if the parent is also watched ([#370])
|
||||||
|
|
||||||
|
- windows: increase buffer size from 4K to 64K ([#485])
|
||||||
|
|
||||||
|
- windows: close file handle on Remove() ([#288])
|
||||||
|
|
||||||
|
- kqueue: put pathname in the error if watching a file fails ([#471])
|
||||||
|
|
||||||
|
- inotify, windows: calling Close() more than once could race ([#465])
|
||||||
|
|
||||||
|
- kqueue: improve Close() performance ([#233])
|
||||||
|
|
||||||
|
- all: various documentation additions and clarifications.
|
||||||
|
|
||||||
|
[#233]: https://github.com/fsnotify/fsnotify/pull/233
|
||||||
|
[#260]: https://github.com/fsnotify/fsnotify/pull/260
|
||||||
|
[#288]: https://github.com/fsnotify/fsnotify/pull/288
|
||||||
|
[#370]: https://github.com/fsnotify/fsnotify/pull/370
|
||||||
|
[#434]: https://github.com/fsnotify/fsnotify/pull/434
|
||||||
|
[#460]: https://github.com/fsnotify/fsnotify/pull/460
|
||||||
|
[#463]: https://github.com/fsnotify/fsnotify/pull/463
|
||||||
|
[#465]: https://github.com/fsnotify/fsnotify/pull/465
|
||||||
|
[#470]: https://github.com/fsnotify/fsnotify/pull/470
|
||||||
|
[#471]: https://github.com/fsnotify/fsnotify/pull/471
|
||||||
|
[#475]: https://github.com/fsnotify/fsnotify/pull/475
|
||||||
|
[#477]: https://github.com/fsnotify/fsnotify/pull/477
|
||||||
|
[#479]: https://github.com/fsnotify/fsnotify/pull/479
|
||||||
|
[#480]: https://github.com/fsnotify/fsnotify/pull/480
|
||||||
|
[#485]: https://github.com/fsnotify/fsnotify/pull/485
|
||||||
|
|
||||||
|
## [1.5.4] - 2022-04-25
|
||||||
|
|
||||||
|
* Windows: add missing defer to `Watcher.WatchList` [#447](https://github.com/fsnotify/fsnotify/pull/447)
|
||||||
|
* go.mod: use latest x/sys [#444](https://github.com/fsnotify/fsnotify/pull/444)
|
||||||
|
* Fix compilation for OpenBSD [#443](https://github.com/fsnotify/fsnotify/pull/443)
|
||||||
|
|
||||||
|
## [1.5.3] - 2022-04-22
|
||||||
|
|
||||||
|
* This version is retracted. An incorrect branch is published accidentally [#445](https://github.com/fsnotify/fsnotify/issues/445)
|
||||||
|
|
||||||
|
## [1.5.2] - 2022-04-21
|
||||||
|
|
||||||
|
* Add a feature to return the directories and files that are being monitored [#374](https://github.com/fsnotify/fsnotify/pull/374)
|
||||||
|
* Fix potential crash on windows if `raw.FileNameLength` exceeds `syscall.MAX_PATH` [#361](https://github.com/fsnotify/fsnotify/pull/361)
|
||||||
|
* Allow build on unsupported GOOS [#424](https://github.com/fsnotify/fsnotify/pull/424)
|
||||||
|
* Don't set `poller.fd` twice in `newFdPoller` [#406](https://github.com/fsnotify/fsnotify/pull/406)
|
||||||
|
* fix go vet warnings: call to `(*T).Fatalf` from a non-test goroutine [#416](https://github.com/fsnotify/fsnotify/pull/416)
|
||||||
|
|
||||||
|
## [1.5.1] - 2021-08-24
|
||||||
|
|
||||||
|
* Revert Add AddRaw to not follow symlinks [#394](https://github.com/fsnotify/fsnotify/pull/394)
|
||||||
|
|
||||||
|
## [1.5.0] - 2021-08-20
|
||||||
|
|
||||||
|
* Go: Increase minimum required version to Go 1.12 [#381](https://github.com/fsnotify/fsnotify/pull/381)
|
||||||
|
* Feature: Add AddRaw method which does not follow symlinks when adding a watch [#289](https://github.com/fsnotify/fsnotify/pull/298)
|
||||||
|
* Windows: Follow symlinks by default like on all other systems [#289](https://github.com/fsnotify/fsnotify/pull/289)
|
||||||
|
* CI: Use GitHub Actions for CI and cover go 1.12-1.17
|
||||||
|
[#378](https://github.com/fsnotify/fsnotify/pull/378)
|
||||||
|
[#381](https://github.com/fsnotify/fsnotify/pull/381)
|
||||||
|
[#385](https://github.com/fsnotify/fsnotify/pull/385)
|
||||||
|
* Go 1.14+: Fix unsafe pointer conversion [#325](https://github.com/fsnotify/fsnotify/pull/325)
|
||||||
|
|
||||||
|
## [1.4.9] - 2020-03-11
|
||||||
|
|
||||||
|
* Move example usage to the readme #329. This may resolve #328.
|
||||||
|
|
||||||
|
## [1.4.8] - 2020-03-10
|
||||||
|
|
||||||
|
* CI: test more go versions (@nathany 1d13583d846ea9d66dcabbfefbfb9d8e6fb05216)
|
||||||
|
* Tests: Queued inotify events could have been read by the test before max_queued_events was hit (@matthias-stone #265)
|
||||||
|
* Tests: t.Fatalf -> t.Errorf in go routines (@gdey #266)
|
||||||
|
* CI: Less verbosity (@nathany #267)
|
||||||
|
* Tests: Darwin: Exchangedata is deprecated on 10.13 (@nathany #267)
|
||||||
|
* Tests: Check if channels are closed in the example (@alexeykazakov #244)
|
||||||
|
* CI: Only run golint on latest version of go and fix issues (@cpuguy83 #284)
|
||||||
|
* CI: Add windows to travis matrix (@cpuguy83 #284)
|
||||||
|
* Docs: Remover appveyor badge (@nathany 11844c0959f6fff69ba325d097fce35bd85a8e93)
|
||||||
|
* Linux: create epoll and pipe fds with close-on-exec (@JohannesEbke #219)
|
||||||
|
* Linux: open files with close-on-exec (@linxiulei #273)
|
||||||
|
* Docs: Plan to support fanotify (@nathany ab058b44498e8b7566a799372a39d150d9ea0119 )
|
||||||
|
* Project: Add go.mod (@nathany #309)
|
||||||
|
* Project: Revise editor config (@nathany #309)
|
||||||
|
* Project: Update copyright for 2019 (@nathany #309)
|
||||||
|
* CI: Drop go1.8 from CI matrix (@nathany #309)
|
||||||
|
* Docs: Updating the FAQ section for supportability with NFS & FUSE filesystems (@Pratik32 4bf2d1fec78374803a39307bfb8d340688f4f28e )
|
||||||
|
|
||||||
|
## [1.4.7] - 2018-01-09
|
||||||
|
|
||||||
* BSD/macOS: Fix possible deadlock on closing the watcher on kqueue (thanks @nhooyr and @glycerine)
|
* BSD/macOS: Fix possible deadlock on closing the watcher on kqueue (thanks @nhooyr and @glycerine)
|
||||||
* Tests: Fix missing verb on format string (thanks @rchiossi)
|
* Tests: Fix missing verb on format string (thanks @rchiossi)
|
||||||
|
@ -10,62 +163,62 @@
|
||||||
* Linux: Properly handle inotify's IN_Q_OVERFLOW event (thanks @zeldovich)
|
* Linux: Properly handle inotify's IN_Q_OVERFLOW event (thanks @zeldovich)
|
||||||
* Docs: replace references to OS X with macOS
|
* Docs: replace references to OS X with macOS
|
||||||
|
|
||||||
## v1.4.2 / 2016-10-10
|
## [1.4.2] - 2016-10-10
|
||||||
|
|
||||||
* Linux: use InotifyInit1 with IN_CLOEXEC to stop leaking a file descriptor to a child process when using fork/exec [#178](https://github.com/fsnotify/fsnotify/pull/178) (thanks @pattyshack)
|
* Linux: use InotifyInit1 with IN_CLOEXEC to stop leaking a file descriptor to a child process when using fork/exec [#178](https://github.com/fsnotify/fsnotify/pull/178) (thanks @pattyshack)
|
||||||
|
|
||||||
## v1.4.1 / 2016-10-04
|
## [1.4.1] - 2016-10-04
|
||||||
|
|
||||||
* Fix flaky inotify stress test on Linux [#177](https://github.com/fsnotify/fsnotify/pull/177) (thanks @pattyshack)
|
* Fix flaky inotify stress test on Linux [#177](https://github.com/fsnotify/fsnotify/pull/177) (thanks @pattyshack)
|
||||||
|
|
||||||
## v1.4.0 / 2016-10-01
|
## [1.4.0] - 2016-10-01
|
||||||
|
|
||||||
* add a String() method to Event.Op [#165](https://github.com/fsnotify/fsnotify/pull/165) (thanks @oozie)
|
* add a String() method to Event.Op [#165](https://github.com/fsnotify/fsnotify/pull/165) (thanks @oozie)
|
||||||
|
|
||||||
## v1.3.1 / 2016-06-28
|
## [1.3.1] - 2016-06-28
|
||||||
|
|
||||||
* Windows: fix for double backslash when watching the root of a drive [#151](https://github.com/fsnotify/fsnotify/issues/151) (thanks @brunoqc)
|
* Windows: fix for double backslash when watching the root of a drive [#151](https://github.com/fsnotify/fsnotify/issues/151) (thanks @brunoqc)
|
||||||
|
|
||||||
## v1.3.0 / 2016-04-19
|
## [1.3.0] - 2016-04-19
|
||||||
|
|
||||||
* Support linux/arm64 by [patching](https://go-review.googlesource.com/#/c/21971/) x/sys/unix and switching to to it from syscall (thanks @suihkulokki) [#135](https://github.com/fsnotify/fsnotify/pull/135)
|
* Support linux/arm64 by [patching](https://go-review.googlesource.com/#/c/21971/) x/sys/unix and switching to to it from syscall (thanks @suihkulokki) [#135](https://github.com/fsnotify/fsnotify/pull/135)
|
||||||
|
|
||||||
## v1.2.10 / 2016-03-02
|
## [1.2.10] - 2016-03-02
|
||||||
|
|
||||||
* Fix golint errors in windows.go [#121](https://github.com/fsnotify/fsnotify/pull/121) (thanks @tiffanyfj)
|
* Fix golint errors in windows.go [#121](https://github.com/fsnotify/fsnotify/pull/121) (thanks @tiffanyfj)
|
||||||
|
|
||||||
## v1.2.9 / 2016-01-13
|
## [1.2.9] - 2016-01-13
|
||||||
|
|
||||||
kqueue: Fix logic for CREATE after REMOVE [#111](https://github.com/fsnotify/fsnotify/pull/111) (thanks @bep)
|
kqueue: Fix logic for CREATE after REMOVE [#111](https://github.com/fsnotify/fsnotify/pull/111) (thanks @bep)
|
||||||
|
|
||||||
## v1.2.8 / 2015-12-17
|
## [1.2.8] - 2015-12-17
|
||||||
|
|
||||||
* kqueue: fix race condition in Close [#105](https://github.com/fsnotify/fsnotify/pull/105) (thanks @djui for reporting the issue and @ppknap for writing a failing test)
|
* kqueue: fix race condition in Close [#105](https://github.com/fsnotify/fsnotify/pull/105) (thanks @djui for reporting the issue and @ppknap for writing a failing test)
|
||||||
* inotify: fix race in test
|
* inotify: fix race in test
|
||||||
* enable race detection for continuous integration (Linux, Mac, Windows)
|
* enable race detection for continuous integration (Linux, Mac, Windows)
|
||||||
|
|
||||||
## v1.2.5 / 2015-10-17
|
## [1.2.5] - 2015-10-17
|
||||||
|
|
||||||
* inotify: use epoll_create1 for arm64 support (requires Linux 2.6.27 or later) [#100](https://github.com/fsnotify/fsnotify/pull/100) (thanks @suihkulokki)
|
* inotify: use epoll_create1 for arm64 support (requires Linux 2.6.27 or later) [#100](https://github.com/fsnotify/fsnotify/pull/100) (thanks @suihkulokki)
|
||||||
* inotify: fix path leaks [#73](https://github.com/fsnotify/fsnotify/pull/73) (thanks @chamaken)
|
* inotify: fix path leaks [#73](https://github.com/fsnotify/fsnotify/pull/73) (thanks @chamaken)
|
||||||
* kqueue: watch for rename events on subdirectories [#83](https://github.com/fsnotify/fsnotify/pull/83) (thanks @guotie)
|
* kqueue: watch for rename events on subdirectories [#83](https://github.com/fsnotify/fsnotify/pull/83) (thanks @guotie)
|
||||||
* kqueue: avoid infinite loops from symlinks cycles [#101](https://github.com/fsnotify/fsnotify/pull/101) (thanks @illicitonion)
|
* kqueue: avoid infinite loops from symlinks cycles [#101](https://github.com/fsnotify/fsnotify/pull/101) (thanks @illicitonion)
|
||||||
|
|
||||||
## v1.2.1 / 2015-10-14
|
## [1.2.1] - 2015-10-14
|
||||||
|
|
||||||
* kqueue: don't watch named pipes [#98](https://github.com/fsnotify/fsnotify/pull/98) (thanks @evanphx)
|
* kqueue: don't watch named pipes [#98](https://github.com/fsnotify/fsnotify/pull/98) (thanks @evanphx)
|
||||||
|
|
||||||
## v1.2.0 / 2015-02-08
|
## [1.2.0] - 2015-02-08
|
||||||
|
|
||||||
* inotify: use epoll to wake up readEvents [#66](https://github.com/fsnotify/fsnotify/pull/66) (thanks @PieterD)
|
* inotify: use epoll to wake up readEvents [#66](https://github.com/fsnotify/fsnotify/pull/66) (thanks @PieterD)
|
||||||
* inotify: closing watcher should now always shut down goroutine [#63](https://github.com/fsnotify/fsnotify/pull/63) (thanks @PieterD)
|
* inotify: closing watcher should now always shut down goroutine [#63](https://github.com/fsnotify/fsnotify/pull/63) (thanks @PieterD)
|
||||||
* kqueue: close kqueue after removing watches, fixes [#59](https://github.com/fsnotify/fsnotify/issues/59)
|
* kqueue: close kqueue after removing watches, fixes [#59](https://github.com/fsnotify/fsnotify/issues/59)
|
||||||
|
|
||||||
## v1.1.1 / 2015-02-05
|
## [1.1.1] - 2015-02-05
|
||||||
|
|
||||||
* inotify: Retry read on EINTR [#61](https://github.com/fsnotify/fsnotify/issues/61) (thanks @PieterD)
|
* inotify: Retry read on EINTR [#61](https://github.com/fsnotify/fsnotify/issues/61) (thanks @PieterD)
|
||||||
|
|
||||||
## v1.1.0 / 2014-12-12
|
## [1.1.0] - 2014-12-12
|
||||||
|
|
||||||
* kqueue: rework internals [#43](https://github.com/fsnotify/fsnotify/pull/43)
|
* kqueue: rework internals [#43](https://github.com/fsnotify/fsnotify/pull/43)
|
||||||
* add low-level functions
|
* add low-level functions
|
||||||
|
@ -77,22 +230,22 @@ kqueue: Fix logic for CREATE after REMOVE [#111](https://github.com/fsnotify/fsn
|
||||||
* kqueue: fix regression in rework causing subdirectories to be watched [#48](https://github.com/fsnotify/fsnotify/issues/48)
|
* kqueue: fix regression in rework causing subdirectories to be watched [#48](https://github.com/fsnotify/fsnotify/issues/48)
|
||||||
* kqueue: cleanup internal watch before sending remove event [#51](https://github.com/fsnotify/fsnotify/issues/51)
|
* kqueue: cleanup internal watch before sending remove event [#51](https://github.com/fsnotify/fsnotify/issues/51)
|
||||||
|
|
||||||
## v1.0.4 / 2014-09-07
|
## [1.0.4] - 2014-09-07
|
||||||
|
|
||||||
* kqueue: add dragonfly to the build tags.
|
* kqueue: add dragonfly to the build tags.
|
||||||
* Rename source code files, rearrange code so exported APIs are at the top.
|
* Rename source code files, rearrange code so exported APIs are at the top.
|
||||||
* Add done channel to example code. [#37](https://github.com/fsnotify/fsnotify/pull/37) (thanks @chenyukang)
|
* Add done channel to example code. [#37](https://github.com/fsnotify/fsnotify/pull/37) (thanks @chenyukang)
|
||||||
|
|
||||||
## v1.0.3 / 2014-08-19
|
## [1.0.3] - 2014-08-19
|
||||||
|
|
||||||
* [Fix] Windows MOVED_TO now translates to Create like on BSD and Linux. [#36](https://github.com/fsnotify/fsnotify/issues/36)
|
* [Fix] Windows MOVED_TO now translates to Create like on BSD and Linux. [#36](https://github.com/fsnotify/fsnotify/issues/36)
|
||||||
|
|
||||||
## v1.0.2 / 2014-08-17
|
## [1.0.2] - 2014-08-17
|
||||||
|
|
||||||
* [Fix] Missing create events on macOS. [#14](https://github.com/fsnotify/fsnotify/issues/14) (thanks @zhsso)
|
* [Fix] Missing create events on macOS. [#14](https://github.com/fsnotify/fsnotify/issues/14) (thanks @zhsso)
|
||||||
* [Fix] Make ./path and path equivalent. (thanks @zhsso)
|
* [Fix] Make ./path and path equivalent. (thanks @zhsso)
|
||||||
|
|
||||||
## v1.0.0 / 2014-08-15
|
## [1.0.0] - 2014-08-15
|
||||||
|
|
||||||
* [API] Remove AddWatch on Windows, use Add.
|
* [API] Remove AddWatch on Windows, use Add.
|
||||||
* Improve documentation for exported identifiers. [#30](https://github.com/fsnotify/fsnotify/issues/30)
|
* Improve documentation for exported identifiers. [#30](https://github.com/fsnotify/fsnotify/issues/30)
|
||||||
|
@ -146,51 +299,51 @@ kqueue: Fix logic for CREATE after REMOVE [#111](https://github.com/fsnotify/fsn
|
||||||
* no tests for the current implementation
|
* no tests for the current implementation
|
||||||
* not fully implemented on Windows [#93](https://github.com/howeyc/fsnotify/issues/93#issuecomment-39285195)
|
* not fully implemented on Windows [#93](https://github.com/howeyc/fsnotify/issues/93#issuecomment-39285195)
|
||||||
|
|
||||||
## v0.9.3 / 2014-12-31
|
## [0.9.3] - 2014-12-31
|
||||||
|
|
||||||
* kqueue: cleanup internal watch before sending remove event [#51](https://github.com/fsnotify/fsnotify/issues/51)
|
* kqueue: cleanup internal watch before sending remove event [#51](https://github.com/fsnotify/fsnotify/issues/51)
|
||||||
|
|
||||||
## v0.9.2 / 2014-08-17
|
## [0.9.2] - 2014-08-17
|
||||||
|
|
||||||
* [Backport] Fix missing create events on macOS. [#14](https://github.com/fsnotify/fsnotify/issues/14) (thanks @zhsso)
|
* [Backport] Fix missing create events on macOS. [#14](https://github.com/fsnotify/fsnotify/issues/14) (thanks @zhsso)
|
||||||
|
|
||||||
## v0.9.1 / 2014-06-12
|
## [0.9.1] - 2014-06-12
|
||||||
|
|
||||||
* Fix data race on kevent buffer (thanks @tilaks) [#98](https://github.com/howeyc/fsnotify/pull/98)
|
* Fix data race on kevent buffer (thanks @tilaks) [#98](https://github.com/howeyc/fsnotify/pull/98)
|
||||||
|
|
||||||
## v0.9.0 / 2014-01-17
|
## [0.9.0] - 2014-01-17
|
||||||
|
|
||||||
* IsAttrib() for events that only concern a file's metadata [#79][] (thanks @abustany)
|
* IsAttrib() for events that only concern a file's metadata [#79][] (thanks @abustany)
|
||||||
* [Fix] kqueue: fix deadlock [#77][] (thanks @cespare)
|
* [Fix] kqueue: fix deadlock [#77][] (thanks @cespare)
|
||||||
* [NOTICE] Development has moved to `code.google.com/p/go.exp/fsnotify` in preparation for inclusion in the Go standard library.
|
* [NOTICE] Development has moved to `code.google.com/p/go.exp/fsnotify` in preparation for inclusion in the Go standard library.
|
||||||
|
|
||||||
## v0.8.12 / 2013-11-13
|
## [0.8.12] - 2013-11-13
|
||||||
|
|
||||||
* [API] Remove FD_SET and friends from Linux adapter
|
* [API] Remove FD_SET and friends from Linux adapter
|
||||||
|
|
||||||
## v0.8.11 / 2013-11-02
|
## [0.8.11] - 2013-11-02
|
||||||
|
|
||||||
* [Doc] Add Changelog [#72][] (thanks @nathany)
|
* [Doc] Add Changelog [#72][] (thanks @nathany)
|
||||||
* [Doc] Spotlight and double modify events on macOS [#62][] (reported by @paulhammond)
|
* [Doc] Spotlight and double modify events on macOS [#62][] (reported by @paulhammond)
|
||||||
|
|
||||||
## v0.8.10 / 2013-10-19
|
## [0.8.10] - 2013-10-19
|
||||||
|
|
||||||
* [Fix] kqueue: remove file watches when parent directory is removed [#71][] (reported by @mdwhatcott)
|
* [Fix] kqueue: remove file watches when parent directory is removed [#71][] (reported by @mdwhatcott)
|
||||||
* [Fix] kqueue: race between Close and readEvents [#70][] (reported by @bernerdschaefer)
|
* [Fix] kqueue: race between Close and readEvents [#70][] (reported by @bernerdschaefer)
|
||||||
* [Doc] specify OS-specific limits in README (thanks @debrando)
|
* [Doc] specify OS-specific limits in README (thanks @debrando)
|
||||||
|
|
||||||
## v0.8.9 / 2013-09-08
|
## [0.8.9] - 2013-09-08
|
||||||
|
|
||||||
* [Doc] Contributing (thanks @nathany)
|
* [Doc] Contributing (thanks @nathany)
|
||||||
* [Doc] update package path in example code [#63][] (thanks @paulhammond)
|
* [Doc] update package path in example code [#63][] (thanks @paulhammond)
|
||||||
* [Doc] GoCI badge in README (Linux only) [#60][]
|
* [Doc] GoCI badge in README (Linux only) [#60][]
|
||||||
* [Doc] Cross-platform testing with Vagrant [#59][] (thanks @nathany)
|
* [Doc] Cross-platform testing with Vagrant [#59][] (thanks @nathany)
|
||||||
|
|
||||||
## v0.8.8 / 2013-06-17
|
## [0.8.8] - 2013-06-17
|
||||||
|
|
||||||
* [Fix] Windows: handle `ERROR_MORE_DATA` on Windows [#49][] (thanks @jbowtie)
|
* [Fix] Windows: handle `ERROR_MORE_DATA` on Windows [#49][] (thanks @jbowtie)
|
||||||
|
|
||||||
## v0.8.7 / 2013-06-03
|
## [0.8.7] - 2013-06-03
|
||||||
|
|
||||||
* [API] Make syscall flags internal
|
* [API] Make syscall flags internal
|
||||||
* [Fix] inotify: ignore event changes
|
* [Fix] inotify: ignore event changes
|
||||||
|
@ -198,74 +351,74 @@ kqueue: Fix logic for CREATE after REMOVE [#111](https://github.com/fsnotify/fsn
|
||||||
* [Fix] tests on Windows
|
* [Fix] tests on Windows
|
||||||
* lower case error messages
|
* lower case error messages
|
||||||
|
|
||||||
## v0.8.6 / 2013-05-23
|
## [0.8.6] - 2013-05-23
|
||||||
|
|
||||||
* kqueue: Use EVT_ONLY flag on Darwin
|
* kqueue: Use EVT_ONLY flag on Darwin
|
||||||
* [Doc] Update README with full example
|
* [Doc] Update README with full example
|
||||||
|
|
||||||
## v0.8.5 / 2013-05-09
|
## [0.8.5] - 2013-05-09
|
||||||
|
|
||||||
* [Fix] inotify: allow monitoring of "broken" symlinks (thanks @tsg)
|
* [Fix] inotify: allow monitoring of "broken" symlinks (thanks @tsg)
|
||||||
|
|
||||||
## v0.8.4 / 2013-04-07
|
## [0.8.4] - 2013-04-07
|
||||||
|
|
||||||
* [Fix] kqueue: watch all file events [#40][] (thanks @ChrisBuchholz)
|
* [Fix] kqueue: watch all file events [#40][] (thanks @ChrisBuchholz)
|
||||||
|
|
||||||
## v0.8.3 / 2013-03-13
|
## [0.8.3] - 2013-03-13
|
||||||
|
|
||||||
* [Fix] inoitfy/kqueue memory leak [#36][] (reported by @nbkolchin)
|
* [Fix] inoitfy/kqueue memory leak [#36][] (reported by @nbkolchin)
|
||||||
* [Fix] kqueue: use fsnFlags for watching a directory [#33][] (reported by @nbkolchin)
|
* [Fix] kqueue: use fsnFlags for watching a directory [#33][] (reported by @nbkolchin)
|
||||||
|
|
||||||
## v0.8.2 / 2013-02-07
|
## [0.8.2] - 2013-02-07
|
||||||
|
|
||||||
* [Doc] add Authors
|
* [Doc] add Authors
|
||||||
* [Fix] fix data races for map access [#29][] (thanks @fsouza)
|
* [Fix] fix data races for map access [#29][] (thanks @fsouza)
|
||||||
|
|
||||||
## v0.8.1 / 2013-01-09
|
## [0.8.1] - 2013-01-09
|
||||||
|
|
||||||
* [Fix] Windows path separators
|
* [Fix] Windows path separators
|
||||||
* [Doc] BSD License
|
* [Doc] BSD License
|
||||||
|
|
||||||
## v0.8.0 / 2012-11-09
|
## [0.8.0] - 2012-11-09
|
||||||
|
|
||||||
* kqueue: directory watching improvements (thanks @vmirage)
|
* kqueue: directory watching improvements (thanks @vmirage)
|
||||||
* inotify: add `IN_MOVED_TO` [#25][] (requested by @cpisto)
|
* inotify: add `IN_MOVED_TO` [#25][] (requested by @cpisto)
|
||||||
* [Fix] kqueue: deleting watched directory [#24][] (reported by @jakerr)
|
* [Fix] kqueue: deleting watched directory [#24][] (reported by @jakerr)
|
||||||
|
|
||||||
## v0.7.4 / 2012-10-09
|
## [0.7.4] - 2012-10-09
|
||||||
|
|
||||||
* [Fix] inotify: fixes from https://codereview.appspot.com/5418045/ (ugorji)
|
* [Fix] inotify: fixes from https://codereview.appspot.com/5418045/ (ugorji)
|
||||||
* [Fix] kqueue: preserve watch flags when watching for delete [#21][] (reported by @robfig)
|
* [Fix] kqueue: preserve watch flags when watching for delete [#21][] (reported by @robfig)
|
||||||
* [Fix] kqueue: watch the directory even if it isn't a new watch (thanks @robfig)
|
* [Fix] kqueue: watch the directory even if it isn't a new watch (thanks @robfig)
|
||||||
* [Fix] kqueue: modify after recreation of file
|
* [Fix] kqueue: modify after recreation of file
|
||||||
|
|
||||||
## v0.7.3 / 2012-09-27
|
## [0.7.3] - 2012-09-27
|
||||||
|
|
||||||
* [Fix] kqueue: watch with an existing folder inside the watched folder (thanks @vmirage)
|
* [Fix] kqueue: watch with an existing folder inside the watched folder (thanks @vmirage)
|
||||||
* [Fix] kqueue: no longer get duplicate CREATE events
|
* [Fix] kqueue: no longer get duplicate CREATE events
|
||||||
|
|
||||||
## v0.7.2 / 2012-09-01
|
## [0.7.2] - 2012-09-01
|
||||||
|
|
||||||
* kqueue: events for created directories
|
* kqueue: events for created directories
|
||||||
|
|
||||||
## v0.7.1 / 2012-07-14
|
## [0.7.1] - 2012-07-14
|
||||||
|
|
||||||
* [Fix] for renaming files
|
* [Fix] for renaming files
|
||||||
|
|
||||||
## v0.7.0 / 2012-07-02
|
## [0.7.0] - 2012-07-02
|
||||||
|
|
||||||
* [Feature] FSNotify flags
|
* [Feature] FSNotify flags
|
||||||
* [Fix] inotify: Added file name back to event path
|
* [Fix] inotify: Added file name back to event path
|
||||||
|
|
||||||
## v0.6.0 / 2012-06-06
|
## [0.6.0] - 2012-06-06
|
||||||
|
|
||||||
* kqueue: watch files after directory created (thanks @tmc)
|
* kqueue: watch files after directory created (thanks @tmc)
|
||||||
|
|
||||||
## v0.5.1 / 2012-05-22
|
## [0.5.1] - 2012-05-22
|
||||||
|
|
||||||
* [Fix] inotify: remove all watches before Close()
|
* [Fix] inotify: remove all watches before Close()
|
||||||
|
|
||||||
## v0.5.0 / 2012-05-03
|
## [0.5.0] - 2012-05-03
|
||||||
|
|
||||||
* [API] kqueue: return errors during watch instead of sending over channel
|
* [API] kqueue: return errors during watch instead of sending over channel
|
||||||
* kqueue: match symlink behavior on Linux
|
* kqueue: match symlink behavior on Linux
|
||||||
|
@ -273,22 +426,22 @@ kqueue: Fix logic for CREATE after REMOVE [#111](https://github.com/fsnotify/fsn
|
||||||
* [Fix] kqueue: handle EINTR (reported by @robfig)
|
* [Fix] kqueue: handle EINTR (reported by @robfig)
|
||||||
* [Doc] Godoc example [#1][] (thanks @davecheney)
|
* [Doc] Godoc example [#1][] (thanks @davecheney)
|
||||||
|
|
||||||
## v0.4.0 / 2012-03-30
|
## [0.4.0] - 2012-03-30
|
||||||
|
|
||||||
* Go 1 released: build with go tool
|
* Go 1 released: build with go tool
|
||||||
* [Feature] Windows support using winfsnotify
|
* [Feature] Windows support using winfsnotify
|
||||||
* Windows does not have attribute change notifications
|
* Windows does not have attribute change notifications
|
||||||
* Roll attribute notifications into IsModify
|
* Roll attribute notifications into IsModify
|
||||||
|
|
||||||
## v0.3.0 / 2012-02-19
|
## [0.3.0] - 2012-02-19
|
||||||
|
|
||||||
* kqueue: add files when watch directory
|
* kqueue: add files when watch directory
|
||||||
|
|
||||||
## v0.2.0 / 2011-12-30
|
## [0.2.0] - 2011-12-30
|
||||||
|
|
||||||
* update to latest Go weekly code
|
* update to latest Go weekly code
|
||||||
|
|
||||||
## v0.1.0 / 2011-10-19
|
## [0.1.0] - 2011-10-19
|
||||||
|
|
||||||
* kqueue: add watch on file creation to match inotify
|
* kqueue: add watch on file creation to match inotify
|
||||||
* kqueue: create file event
|
* kqueue: create file event
|
||||||
|
|
|
@ -1,77 +1,26 @@
|
||||||
# Contributing
|
Thank you for your interest in contributing to fsnotify! We try to review and
|
||||||
|
merge PRs in a reasonable timeframe, but please be aware that:
|
||||||
|
|
||||||
## Issues
|
- To avoid "wasted" work, please discus changes on the issue tracker first. You
|
||||||
|
can just send PRs, but they may end up being rejected for one reason or the
|
||||||
|
other.
|
||||||
|
|
||||||
* Request features and report bugs using the [GitHub Issue Tracker](https://github.com/fsnotify/fsnotify/issues).
|
- fsnotify is a cross-platform library, and changes must work reasonably well on
|
||||||
* Please indicate the platform you are using fsnotify on.
|
all supported platforms.
|
||||||
* A code example to reproduce the problem is appreciated.
|
|
||||||
|
|
||||||
## Pull Requests
|
- Changes will need to be compatible; old code should still compile, and the
|
||||||
|
runtime behaviour can't change in ways that are likely to lead to problems for
|
||||||
|
users.
|
||||||
|
|
||||||
### Contributor License Agreement
|
Testing
|
||||||
|
-------
|
||||||
|
Just `go test ./...` runs all the tests; the CI runs this on all supported
|
||||||
|
platforms. Testing different platforms locally can be done with something like
|
||||||
|
[goon] or [Vagrant], but this isn't super-easy to set up at the moment.
|
||||||
|
|
||||||
fsnotify is derived from code in the [golang.org/x/exp](https://godoc.org/golang.org/x/exp) package and it may be included [in the standard library](https://github.com/fsnotify/fsnotify/issues/1) in the future. Therefore fsnotify carries the same [LICENSE](https://github.com/fsnotify/fsnotify/blob/master/LICENSE) as Go. Contributors retain their copyright, so you need to fill out a short form before we can accept your contribution: [Google Individual Contributor License Agreement](https://developers.google.com/open-source/cla/individual).
|
Use the `-short` flag to make the "stress test" run faster.
|
||||||
|
|
||||||
Please indicate that you have signed the CLA in your pull request.
|
|
||||||
|
|
||||||
### How fsnotify is Developed
|
[goon]: https://github.com/arp242/goon
|
||||||
|
[Vagrant]: https://www.vagrantup.com/
|
||||||
* Development is done on feature branches.
|
[integration_test.go]: /integration_test.go
|
||||||
* Tests are run on BSD, Linux, macOS and Windows.
|
|
||||||
* Pull requests are reviewed and [applied to master][am] using [hub][].
|
|
||||||
* Maintainers may modify or squash commits rather than asking contributors to.
|
|
||||||
* To issue a new release, the maintainers will:
|
|
||||||
* Update the CHANGELOG
|
|
||||||
* Tag a version, which will become available through gopkg.in.
|
|
||||||
|
|
||||||
### How to Fork
|
|
||||||
|
|
||||||
For smooth sailing, always use the original import path. Installing with `go get` makes this easy.
|
|
||||||
|
|
||||||
1. Install from GitHub (`go get -u github.com/fsnotify/fsnotify`)
|
|
||||||
2. Create your feature branch (`git checkout -b my-new-feature`)
|
|
||||||
3. Ensure everything works and the tests pass (see below)
|
|
||||||
4. Commit your changes (`git commit -am 'Add some feature'`)
|
|
||||||
|
|
||||||
Contribute upstream:
|
|
||||||
|
|
||||||
1. Fork fsnotify on GitHub
|
|
||||||
2. Add your remote (`git remote add fork git@github.com:mycompany/repo.git`)
|
|
||||||
3. Push to the branch (`git push fork my-new-feature`)
|
|
||||||
4. Create a new Pull Request on GitHub
|
|
||||||
|
|
||||||
This workflow is [thoroughly explained by Katrina Owen](https://splice.com/blog/contributing-open-source-git-repositories-go/).
|
|
||||||
|
|
||||||
### Testing
|
|
||||||
|
|
||||||
fsnotify uses build tags to compile different code on Linux, BSD, macOS, and Windows.
|
|
||||||
|
|
||||||
Before doing a pull request, please do your best to test your changes on multiple platforms, and list which platforms you were able/unable to test on.
|
|
||||||
|
|
||||||
To aid in cross-platform testing there is a Vagrantfile for Linux and BSD.
|
|
||||||
|
|
||||||
* Install [Vagrant](http://www.vagrantup.com/) and [VirtualBox](https://www.virtualbox.org/)
|
|
||||||
* Setup [Vagrant Gopher](https://github.com/nathany/vagrant-gopher) in your `src` folder.
|
|
||||||
* Run `vagrant up` from the project folder. You can also setup just one box with `vagrant up linux` or `vagrant up bsd` (note: the BSD box doesn't support Windows hosts at this time, and NFS may prompt for your host OS password)
|
|
||||||
* Once setup, you can run the test suite on a given OS with a single command `vagrant ssh linux -c 'cd fsnotify/fsnotify; go test'`.
|
|
||||||
* When you're done, you will want to halt or destroy the Vagrant boxes.
|
|
||||||
|
|
||||||
Notice: fsnotify file system events won't trigger in shared folders. The tests get around this limitation by using the /tmp directory.
|
|
||||||
|
|
||||||
Right now there is no equivalent solution for Windows and macOS, but there are Windows VMs [freely available from Microsoft](http://www.modern.ie/en-us/virtualization-tools#downloads).
|
|
||||||
|
|
||||||
### Maintainers
|
|
||||||
|
|
||||||
Help maintaining fsnotify is welcome. To be a maintainer:
|
|
||||||
|
|
||||||
* Submit a pull request and sign the CLA as above.
|
|
||||||
* You must be able to run the test suite on Mac, Windows, Linux and BSD.
|
|
||||||
|
|
||||||
To keep master clean, the fsnotify project uses the "apply mail" workflow outlined in Nathaniel Talbott's post ["Merge pull request" Considered Harmful][am]. This requires installing [hub][].
|
|
||||||
|
|
||||||
All code changes should be internal pull requests.
|
|
||||||
|
|
||||||
Releases are tagged using [Semantic Versioning](http://semver.org/).
|
|
||||||
|
|
||||||
[hub]: https://github.com/github/hub
|
|
||||||
[am]: http://blog.spreedly.com/2014/06/24/merge-pull-request-considered-harmful/#.VGa5yZPF_Zs
|
|
||||||
|
|
|
@ -1,28 +1,25 @@
|
||||||
Copyright (c) 2012 The Go Authors. All rights reserved.
|
Copyright © 2012 The Go Authors. All rights reserved.
|
||||||
Copyright (c) 2012-2019 fsnotify Authors. All rights reserved.
|
Copyright © fsnotify Authors. All rights reserved.
|
||||||
|
|
||||||
Redistribution and use in source and binary forms, with or without
|
Redistribution and use in source and binary forms, with or without modification,
|
||||||
modification, are permitted provided that the following conditions are
|
are permitted provided that the following conditions are met:
|
||||||
met:
|
|
||||||
|
|
||||||
* Redistributions of source code must retain the above copyright
|
* Redistributions of source code must retain the above copyright notice, this
|
||||||
notice, this list of conditions and the following disclaimer.
|
list of conditions and the following disclaimer.
|
||||||
* Redistributions in binary form must reproduce the above
|
* Redistributions in binary form must reproduce the above copyright notice, this
|
||||||
copyright notice, this list of conditions and the following disclaimer
|
list of conditions and the following disclaimer in the documentation and/or
|
||||||
in the documentation and/or other materials provided with the
|
other materials provided with the distribution.
|
||||||
distribution.
|
* Neither the name of Google Inc. nor the names of its contributors may be used
|
||||||
* Neither the name of Google Inc. nor the names of its
|
to endorse or promote products derived from this software without specific
|
||||||
contributors may be used to endorse or promote products derived from
|
prior written permission.
|
||||||
this software without specific prior written permission.
|
|
||||||
|
|
||||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||||
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||||
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
|
||||||
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||||
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||||
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
|
||||||
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
||||||
|
|
|
@ -1,39 +1,33 @@
|
||||||
# File system notifications for Go
|
fsnotify is a Go library to provide cross-platform filesystem notifications on
|
||||||
|
Windows, Linux, macOS, and BSD systems.
|
||||||
|
|
||||||
[](https://godoc.org/github.com/fsnotify/fsnotify) [](https://goreportcard.com/report/github.com/fsnotify/fsnotify)
|
Go 1.16 or newer is required; the full documentation is at
|
||||||
|
https://pkg.go.dev/github.com/fsnotify/fsnotify
|
||||||
|
|
||||||
fsnotify utilizes [golang.org/x/sys](https://godoc.org/golang.org/x/sys) rather than `syscall` from the standard library. Ensure you have the latest version installed by running:
|
**It's best to read the documentation at pkg.go.dev, as it's pinned to the last
|
||||||
|
released version, whereas this README is for the last development version which
|
||||||
|
may include additions/changes.**
|
||||||
|
|
||||||
```console
|
---
|
||||||
go get -u golang.org/x/sys/...
|
|
||||||
```
|
|
||||||
|
|
||||||
Cross platform: Windows, Linux, BSD and macOS.
|
Platform support:
|
||||||
|
|
||||||
| Adapter | OS | Status |
|
| Adapter | OS | Status |
|
||||||
| --------------------- | -------------------------------- | ------------------------------------------------------------------------------------------------------------------------------- |
|
| --------------------- | ---------------| -------------------------------------------------------------|
|
||||||
| inotify | Linux 2.6.27 or later, Android\* | Supported [](https://travis-ci.org/fsnotify/fsnotify) |
|
| inotify | Linux 2.6.32+ | Supported |
|
||||||
| kqueue | BSD, macOS, iOS\* | Supported [](https://travis-ci.org/fsnotify/fsnotify) |
|
| kqueue | BSD, macOS | Supported |
|
||||||
| ReadDirectoryChangesW | Windows | Supported [](https://travis-ci.org/fsnotify/fsnotify) |
|
| ReadDirectoryChangesW | Windows | Supported |
|
||||||
| FSEvents | macOS | [Planned](https://github.com/fsnotify/fsnotify/issues/11) |
|
| FSEvents | macOS | [Planned](https://github.com/fsnotify/fsnotify/issues/11) |
|
||||||
| FEN | Solaris 11 | [In Progress](https://github.com/fsnotify/fsnotify/issues/12) |
|
| FEN | Solaris 11 | [In Progress](https://github.com/fsnotify/fsnotify/pull/371) |
|
||||||
| fanotify | Linux 2.6.37+ | [Planned](https://github.com/fsnotify/fsnotify/issues/114) |
|
| fanotify | Linux 5.9+ | [Maybe](https://github.com/fsnotify/fsnotify/issues/114) |
|
||||||
| USN Journals | Windows | [Maybe](https://github.com/fsnotify/fsnotify/issues/53) |
|
| USN Journals | Windows | [Maybe](https://github.com/fsnotify/fsnotify/issues/53) |
|
||||||
| Polling | *All* | [Maybe](https://github.com/fsnotify/fsnotify/issues/9) |
|
| Polling | *All* | [Maybe](https://github.com/fsnotify/fsnotify/issues/9) |
|
||||||
|
|
||||||
\* Android and iOS are untested.
|
Linux and macOS should include Android and iOS, but these are currently untested.
|
||||||
|
|
||||||
Please see [the documentation](https://godoc.org/github.com/fsnotify/fsnotify) and consult the [FAQ](#faq) for usage information.
|
Usage
|
||||||
|
-----
|
||||||
## API stability
|
A basic example:
|
||||||
|
|
||||||
fsnotify is a fork of [howeyc/fsnotify](https://godoc.org/github.com/howeyc/fsnotify) with a new API as of v1.0. The API is based on [this design document](http://goo.gl/MrYxyA).
|
|
||||||
|
|
||||||
All [releases](https://github.com/fsnotify/fsnotify/releases) are tagged based on [Semantic Versioning](http://semver.org/). Further API changes are [planned](https://github.com/fsnotify/fsnotify/milestones), and will be tagged with a new major revision number.
|
|
||||||
|
|
||||||
Go 1.6 supports dependencies located in the `vendor/` folder. Unless you are creating a library, it is recommended that you copy fsnotify into `vendor/github.com/fsnotify/fsnotify` within your project, and likewise for `golang.org/x/sys`.
|
|
||||||
|
|
||||||
## Usage
|
|
||||||
|
|
||||||
```go
|
```go
|
||||||
package main
|
package main
|
||||||
|
@ -45,13 +39,14 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
|
// Create new watcher.
|
||||||
watcher, err := fsnotify.NewWatcher()
|
watcher, err := fsnotify.NewWatcher()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
defer watcher.Close()
|
defer watcher.Close()
|
||||||
|
|
||||||
done := make(chan bool)
|
// Start listening for events.
|
||||||
go func() {
|
go func() {
|
||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
|
@ -60,7 +55,7 @@ func main() {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
log.Println("event:", event)
|
log.Println("event:", event)
|
||||||
if event.Op&fsnotify.Write == fsnotify.Write {
|
if event.Has(fsnotify.Write) {
|
||||||
log.Println("modified file:", event.Name)
|
log.Println("modified file:", event.Name)
|
||||||
}
|
}
|
||||||
case err, ok := <-watcher.Errors:
|
case err, ok := <-watcher.Errors:
|
||||||
|
@ -72,59 +67,95 @@ func main() {
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
err = watcher.Add("/tmp/foo")
|
// Add a path.
|
||||||
|
err = watcher.Add("/tmp")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
<-done
|
|
||||||
|
// Block main goroutine forever.
|
||||||
|
<-make(chan struct{})
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
## Contributing
|
Some more examples can be found in [cmd/fsnotify](cmd/fsnotify), which can be
|
||||||
|
run with:
|
||||||
|
|
||||||
Please refer to [CONTRIBUTING][] before opening an issue or pull request.
|
% go run ./cmd/fsnotify
|
||||||
|
|
||||||
## Example
|
FAQ
|
||||||
|
---
|
||||||
|
### Will a file still be watched when it's moved to another directory?
|
||||||
|
No, not unless you are watching the location it was moved to.
|
||||||
|
|
||||||
See [example_test.go](https://github.com/fsnotify/fsnotify/blob/master/example_test.go).
|
### Are subdirectories watched too?
|
||||||
|
No, you must add watches for any directory you want to watch (a recursive
|
||||||
|
watcher is on the roadmap: [#18]).
|
||||||
|
|
||||||
## FAQ
|
|
||||||
|
|
||||||
**When a file is moved to another directory is it still being watched?**
|
|
||||||
|
|
||||||
No (it shouldn't be, unless you are watching where it was moved to).
|
|
||||||
|
|
||||||
**When I watch a directory, are all subdirectories watched as well?**
|
|
||||||
|
|
||||||
No, you must add watches for any directory you want to watch (a recursive watcher is on the roadmap [#18][]).
|
|
||||||
|
|
||||||
**Do I have to watch the Error and Event channels in a separate goroutine?**
|
|
||||||
|
|
||||||
As of now, yes. Looking into making this single-thread friendly (see [howeyc #7][#7])
|
|
||||||
|
|
||||||
**Why am I receiving multiple events for the same file on OS X?**
|
|
||||||
|
|
||||||
Spotlight indexing on OS X can result in multiple events (see [howeyc #62][#62]). A temporary workaround is to add your folder(s) to the *Spotlight Privacy settings* until we have a native FSEvents implementation (see [#11][]).
|
|
||||||
|
|
||||||
**How many files can be watched at once?**
|
|
||||||
|
|
||||||
There are OS-specific limits as to how many watches can be created:
|
|
||||||
* Linux: /proc/sys/fs/inotify/max_user_watches contains the limit, reaching this limit results in a "no space left on device" error.
|
|
||||||
* BSD / OSX: sysctl variables "kern.maxfiles" and "kern.maxfilesperproc", reaching these limits results in a "too many open files" error.
|
|
||||||
|
|
||||||
**Why don't notifications work with NFS filesystems or filesystem in userspace (FUSE)?**
|
|
||||||
|
|
||||||
fsnotify requires support from underlying OS to work. The current NFS protocol does not provide network level support for file notifications.
|
|
||||||
|
|
||||||
[#62]: https://github.com/howeyc/fsnotify/issues/62
|
|
||||||
[#18]: https://github.com/fsnotify/fsnotify/issues/18
|
[#18]: https://github.com/fsnotify/fsnotify/issues/18
|
||||||
|
|
||||||
|
### Do I have to watch the Error and Event channels in a goroutine?
|
||||||
|
As of now, yes (you can read both channels in the same goroutine using `select`,
|
||||||
|
you don't need a separate goroutine for both channels; see the example).
|
||||||
|
|
||||||
|
### Why don't notifications work with NFS, SMB, FUSE, /proc, or /sys?
|
||||||
|
fsnotify requires support from underlying OS to work. The current NFS and SMB
|
||||||
|
protocols does not provide network level support for file notifications, and
|
||||||
|
neither do the /proc and /sys virtual filesystems.
|
||||||
|
|
||||||
|
This could be fixed with a polling watcher ([#9]), but it's not yet implemented.
|
||||||
|
|
||||||
|
[#9]: https://github.com/fsnotify/fsnotify/issues/9
|
||||||
|
|
||||||
|
Platform-specific notes
|
||||||
|
-----------------------
|
||||||
|
### Linux
|
||||||
|
When a file is removed a REMOVE event won't be emitted until all file
|
||||||
|
descriptors are closed; it will emit a CHMOD instead:
|
||||||
|
|
||||||
|
fp := os.Open("file")
|
||||||
|
os.Remove("file") // CHMOD
|
||||||
|
fp.Close() // REMOVE
|
||||||
|
|
||||||
|
This is the event that inotify sends, so not much can be changed about this.
|
||||||
|
|
||||||
|
The `fs.inotify.max_user_watches` sysctl variable specifies the upper limit for
|
||||||
|
the number of watches per user, and `fs.inotify.max_user_instances` specifies
|
||||||
|
the maximum number of inotify instances per user. Every Watcher you create is an
|
||||||
|
"instance", and every path you add is a "watch".
|
||||||
|
|
||||||
|
These are also exposed in `/proc` as `/proc/sys/fs/inotify/max_user_watches` and
|
||||||
|
`/proc/sys/fs/inotify/max_user_instances`
|
||||||
|
|
||||||
|
To increase them you can use `sysctl` or write the value to proc file:
|
||||||
|
|
||||||
|
# The default values on Linux 5.18
|
||||||
|
sysctl fs.inotify.max_user_watches=124983
|
||||||
|
sysctl fs.inotify.max_user_instances=128
|
||||||
|
|
||||||
|
To make the changes persist on reboot edit `/etc/sysctl.conf` or
|
||||||
|
`/usr/lib/sysctl.d/50-default.conf` (details differ per Linux distro; check your
|
||||||
|
distro's documentation):
|
||||||
|
|
||||||
|
fs.inotify.max_user_watches=124983
|
||||||
|
fs.inotify.max_user_instances=128
|
||||||
|
|
||||||
|
Reaching the limit will result in a "no space left on device" or "too many open
|
||||||
|
files" error.
|
||||||
|
|
||||||
|
### kqueue (macOS, all BSD systems)
|
||||||
|
kqueue requires opening a file descriptor for every file that's being watched;
|
||||||
|
so if you're watching a directory with five files then that's six file
|
||||||
|
descriptors. You will run in to your system's "max open files" limit faster on
|
||||||
|
these platforms.
|
||||||
|
|
||||||
|
The sysctl variables `kern.maxfiles` and `kern.maxfilesperproc` can be used to
|
||||||
|
control the maximum number of open files.
|
||||||
|
|
||||||
|
### macOS
|
||||||
|
Spotlight indexing on macOS can result in multiple events (see [#15]). A temporary
|
||||||
|
workaround is to add your folder(s) to the *Spotlight Privacy settings* until we
|
||||||
|
have a native FSEvents implementation (see [#11]).
|
||||||
|
|
||||||
[#11]: https://github.com/fsnotify/fsnotify/issues/11
|
[#11]: https://github.com/fsnotify/fsnotify/issues/11
|
||||||
[#7]: https://github.com/howeyc/fsnotify/issues/7
|
[#15]: https://github.com/fsnotify/fsnotify/issues/15
|
||||||
|
|
||||||
[contributing]: https://github.com/fsnotify/fsnotify/blob/master/CONTRIBUTING.md
|
|
||||||
|
|
||||||
## Related Projects
|
|
||||||
|
|
||||||
* [notify](https://github.com/rjeczalik/notify)
|
|
||||||
* [fsevents](https://github.com/fsnotify/fsevents)
|
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,162 @@
|
||||||
|
//go:build solaris
|
||||||
|
// +build solaris
|
||||||
|
|
||||||
|
package fsnotify
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Watcher watches a set of paths, delivering events on a channel.
|
||||||
|
//
|
||||||
|
// A watcher should not be copied (e.g. pass it by pointer, rather than by
|
||||||
|
// value).
|
||||||
|
//
|
||||||
|
// # Linux notes
|
||||||
|
//
|
||||||
|
// When a file is removed a Remove event won't be emitted until all file
|
||||||
|
// descriptors are closed, and deletes will always emit a Chmod. For example:
|
||||||
|
//
|
||||||
|
// fp := os.Open("file")
|
||||||
|
// os.Remove("file") // Triggers Chmod
|
||||||
|
// fp.Close() // Triggers Remove
|
||||||
|
//
|
||||||
|
// This is the event that inotify sends, so not much can be changed about this.
|
||||||
|
//
|
||||||
|
// The fs.inotify.max_user_watches sysctl variable specifies the upper limit
|
||||||
|
// for the number of watches per user, and fs.inotify.max_user_instances
|
||||||
|
// specifies the maximum number of inotify instances per user. Every Watcher you
|
||||||
|
// create is an "instance", and every path you add is a "watch".
|
||||||
|
//
|
||||||
|
// These are also exposed in /proc as /proc/sys/fs/inotify/max_user_watches and
|
||||||
|
// /proc/sys/fs/inotify/max_user_instances
|
||||||
|
//
|
||||||
|
// To increase them you can use sysctl or write the value to the /proc file:
|
||||||
|
//
|
||||||
|
// # Default values on Linux 5.18
|
||||||
|
// sysctl fs.inotify.max_user_watches=124983
|
||||||
|
// sysctl fs.inotify.max_user_instances=128
|
||||||
|
//
|
||||||
|
// To make the changes persist on reboot edit /etc/sysctl.conf or
|
||||||
|
// /usr/lib/sysctl.d/50-default.conf (details differ per Linux distro; check
|
||||||
|
// your distro's documentation):
|
||||||
|
//
|
||||||
|
// fs.inotify.max_user_watches=124983
|
||||||
|
// fs.inotify.max_user_instances=128
|
||||||
|
//
|
||||||
|
// Reaching the limit will result in a "no space left on device" or "too many open
|
||||||
|
// files" error.
|
||||||
|
//
|
||||||
|
// # kqueue notes (macOS, BSD)
|
||||||
|
//
|
||||||
|
// kqueue requires opening a file descriptor for every file that's being watched;
|
||||||
|
// so if you're watching a directory with five files then that's six file
|
||||||
|
// descriptors. You will run in to your system's "max open files" limit faster on
|
||||||
|
// these platforms.
|
||||||
|
//
|
||||||
|
// The sysctl variables kern.maxfiles and kern.maxfilesperproc can be used to
|
||||||
|
// control the maximum number of open files, as well as /etc/login.conf on BSD
|
||||||
|
// systems.
|
||||||
|
//
|
||||||
|
// # macOS notes
|
||||||
|
//
|
||||||
|
// Spotlight indexing on macOS can result in multiple events (see [#15]). A
|
||||||
|
// temporary workaround is to add your folder(s) to the "Spotlight Privacy
|
||||||
|
// Settings" until we have a native FSEvents implementation (see [#11]).
|
||||||
|
//
|
||||||
|
// [#11]: https://github.com/fsnotify/fsnotify/issues/11
|
||||||
|
// [#15]: https://github.com/fsnotify/fsnotify/issues/15
|
||||||
|
type Watcher struct {
|
||||||
|
// Events sends the filesystem change events.
|
||||||
|
//
|
||||||
|
// fsnotify can send the following events; a "path" here can refer to a
|
||||||
|
// file, directory, symbolic link, or special file like a FIFO.
|
||||||
|
//
|
||||||
|
// fsnotify.Create A new path was created; this may be followed by one
|
||||||
|
// or more Write events if data also gets written to a
|
||||||
|
// file.
|
||||||
|
//
|
||||||
|
// fsnotify.Remove A path was removed.
|
||||||
|
//
|
||||||
|
// fsnotify.Rename A path was renamed. A rename is always sent with the
|
||||||
|
// old path as Event.Name, and a Create event will be
|
||||||
|
// sent with the new name. Renames are only sent for
|
||||||
|
// paths that are currently watched; e.g. moving an
|
||||||
|
// unmonitored file into a monitored directory will
|
||||||
|
// show up as just a Create. Similarly, renaming a file
|
||||||
|
// to outside a monitored directory will show up as
|
||||||
|
// only a Rename.
|
||||||
|
//
|
||||||
|
// fsnotify.Write A file or named pipe was written to. A Truncate will
|
||||||
|
// also trigger a Write. A single "write action"
|
||||||
|
// initiated by the user may show up as one or multiple
|
||||||
|
// writes, depending on when the system syncs things to
|
||||||
|
// disk. For example when compiling a large Go program
|
||||||
|
// you may get hundreds of Write events, so you
|
||||||
|
// probably want to wait until you've stopped receiving
|
||||||
|
// them (see the dedup example in cmd/fsnotify).
|
||||||
|
//
|
||||||
|
// fsnotify.Chmod Attributes were changed. On Linux this is also sent
|
||||||
|
// when a file is removed (or more accurately, when a
|
||||||
|
// link to an inode is removed). On kqueue it's sent
|
||||||
|
// and on kqueue when a file is truncated. On Windows
|
||||||
|
// it's never sent.
|
||||||
|
Events chan Event
|
||||||
|
|
||||||
|
// Errors sends any errors.
|
||||||
|
Errors chan error
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewWatcher creates a new Watcher.
|
||||||
|
func NewWatcher() (*Watcher, error) {
|
||||||
|
return nil, errors.New("FEN based watcher not yet supported for fsnotify\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close removes all watches and closes the events channel.
|
||||||
|
func (w *Watcher) Close() error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add starts monitoring the path for changes.
|
||||||
|
//
|
||||||
|
// A path can only be watched once; attempting to watch it more than once will
|
||||||
|
// return an error. Paths that do not yet exist on the filesystem cannot be
|
||||||
|
// added. A watch will be automatically removed if the path is deleted.
|
||||||
|
//
|
||||||
|
// A path will remain watched if it gets renamed to somewhere else on the same
|
||||||
|
// filesystem, but the monitor will get removed if the path gets deleted and
|
||||||
|
// re-created, or if it's moved to a different filesystem.
|
||||||
|
//
|
||||||
|
// Notifications on network filesystems (NFS, SMB, FUSE, etc.) or special
|
||||||
|
// filesystems (/proc, /sys, etc.) generally don't work.
|
||||||
|
//
|
||||||
|
// # Watching directories
|
||||||
|
//
|
||||||
|
// All files in a directory are monitored, including new files that are created
|
||||||
|
// after the watcher is started. Subdirectories are not watched (i.e. it's
|
||||||
|
// non-recursive).
|
||||||
|
//
|
||||||
|
// # Watching files
|
||||||
|
//
|
||||||
|
// Watching individual files (rather than directories) is generally not
|
||||||
|
// recommended as many tools update files atomically. Instead of "just" writing
|
||||||
|
// to the file a temporary file will be written to first, and if successful the
|
||||||
|
// temporary file is moved to to destination removing the original, or some
|
||||||
|
// variant thereof. The watcher on the original file is now lost, as it no
|
||||||
|
// longer exists.
|
||||||
|
//
|
||||||
|
// Instead, watch the parent directory and use Event.Name to filter out files
|
||||||
|
// you're not interested in. There is an example of this in [cmd/fsnotify/file.go].
|
||||||
|
func (w *Watcher) Add(name string) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove stops monitoring the path for changes.
|
||||||
|
//
|
||||||
|
// Directories are always removed non-recursively. For example, if you added
|
||||||
|
// /tmp/dir and /tmp/dir/subdir then you will need to remove both.
|
||||||
|
//
|
||||||
|
// Removing a path that has not yet been added returns [ErrNonExistentWatch].
|
||||||
|
func (w *Watcher) Remove(name string) error {
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -0,0 +1,459 @@
|
||||||
|
//go:build linux
|
||||||
|
// +build linux
|
||||||
|
|
||||||
|
package fsnotify
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
"unsafe"
|
||||||
|
|
||||||
|
"golang.org/x/sys/unix"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Watcher watches a set of paths, delivering events on a channel.
|
||||||
|
//
|
||||||
|
// A watcher should not be copied (e.g. pass it by pointer, rather than by
|
||||||
|
// value).
|
||||||
|
//
|
||||||
|
// # Linux notes
|
||||||
|
//
|
||||||
|
// When a file is removed a Remove event won't be emitted until all file
|
||||||
|
// descriptors are closed, and deletes will always emit a Chmod. For example:
|
||||||
|
//
|
||||||
|
// fp := os.Open("file")
|
||||||
|
// os.Remove("file") // Triggers Chmod
|
||||||
|
// fp.Close() // Triggers Remove
|
||||||
|
//
|
||||||
|
// This is the event that inotify sends, so not much can be changed about this.
|
||||||
|
//
|
||||||
|
// The fs.inotify.max_user_watches sysctl variable specifies the upper limit
|
||||||
|
// for the number of watches per user, and fs.inotify.max_user_instances
|
||||||
|
// specifies the maximum number of inotify instances per user. Every Watcher you
|
||||||
|
// create is an "instance", and every path you add is a "watch".
|
||||||
|
//
|
||||||
|
// These are also exposed in /proc as /proc/sys/fs/inotify/max_user_watches and
|
||||||
|
// /proc/sys/fs/inotify/max_user_instances
|
||||||
|
//
|
||||||
|
// To increase them you can use sysctl or write the value to the /proc file:
|
||||||
|
//
|
||||||
|
// # Default values on Linux 5.18
|
||||||
|
// sysctl fs.inotify.max_user_watches=124983
|
||||||
|
// sysctl fs.inotify.max_user_instances=128
|
||||||
|
//
|
||||||
|
// To make the changes persist on reboot edit /etc/sysctl.conf or
|
||||||
|
// /usr/lib/sysctl.d/50-default.conf (details differ per Linux distro; check
|
||||||
|
// your distro's documentation):
|
||||||
|
//
|
||||||
|
// fs.inotify.max_user_watches=124983
|
||||||
|
// fs.inotify.max_user_instances=128
|
||||||
|
//
|
||||||
|
// Reaching the limit will result in a "no space left on device" or "too many open
|
||||||
|
// files" error.
|
||||||
|
//
|
||||||
|
// # kqueue notes (macOS, BSD)
|
||||||
|
//
|
||||||
|
// kqueue requires opening a file descriptor for every file that's being watched;
|
||||||
|
// so if you're watching a directory with five files then that's six file
|
||||||
|
// descriptors. You will run in to your system's "max open files" limit faster on
|
||||||
|
// these platforms.
|
||||||
|
//
|
||||||
|
// The sysctl variables kern.maxfiles and kern.maxfilesperproc can be used to
|
||||||
|
// control the maximum number of open files, as well as /etc/login.conf on BSD
|
||||||
|
// systems.
|
||||||
|
//
|
||||||
|
// # macOS notes
|
||||||
|
//
|
||||||
|
// Spotlight indexing on macOS can result in multiple events (see [#15]). A
|
||||||
|
// temporary workaround is to add your folder(s) to the "Spotlight Privacy
|
||||||
|
// Settings" until we have a native FSEvents implementation (see [#11]).
|
||||||
|
//
|
||||||
|
// [#11]: https://github.com/fsnotify/fsnotify/issues/11
|
||||||
|
// [#15]: https://github.com/fsnotify/fsnotify/issues/15
|
||||||
|
type Watcher struct {
|
||||||
|
// Events sends the filesystem change events.
|
||||||
|
//
|
||||||
|
// fsnotify can send the following events; a "path" here can refer to a
|
||||||
|
// file, directory, symbolic link, or special file like a FIFO.
|
||||||
|
//
|
||||||
|
// fsnotify.Create A new path was created; this may be followed by one
|
||||||
|
// or more Write events if data also gets written to a
|
||||||
|
// file.
|
||||||
|
//
|
||||||
|
// fsnotify.Remove A path was removed.
|
||||||
|
//
|
||||||
|
// fsnotify.Rename A path was renamed. A rename is always sent with the
|
||||||
|
// old path as Event.Name, and a Create event will be
|
||||||
|
// sent with the new name. Renames are only sent for
|
||||||
|
// paths that are currently watched; e.g. moving an
|
||||||
|
// unmonitored file into a monitored directory will
|
||||||
|
// show up as just a Create. Similarly, renaming a file
|
||||||
|
// to outside a monitored directory will show up as
|
||||||
|
// only a Rename.
|
||||||
|
//
|
||||||
|
// fsnotify.Write A file or named pipe was written to. A Truncate will
|
||||||
|
// also trigger a Write. A single "write action"
|
||||||
|
// initiated by the user may show up as one or multiple
|
||||||
|
// writes, depending on when the system syncs things to
|
||||||
|
// disk. For example when compiling a large Go program
|
||||||
|
// you may get hundreds of Write events, so you
|
||||||
|
// probably want to wait until you've stopped receiving
|
||||||
|
// them (see the dedup example in cmd/fsnotify).
|
||||||
|
//
|
||||||
|
// fsnotify.Chmod Attributes were changed. On Linux this is also sent
|
||||||
|
// when a file is removed (or more accurately, when a
|
||||||
|
// link to an inode is removed). On kqueue it's sent
|
||||||
|
// and on kqueue when a file is truncated. On Windows
|
||||||
|
// it's never sent.
|
||||||
|
Events chan Event
|
||||||
|
|
||||||
|
// Errors sends any errors.
|
||||||
|
Errors chan error
|
||||||
|
|
||||||
|
// Store fd here as os.File.Read() will no longer return on close after
|
||||||
|
// calling Fd(). See: https://github.com/golang/go/issues/26439
|
||||||
|
fd int
|
||||||
|
mu sync.Mutex // Map access
|
||||||
|
inotifyFile *os.File
|
||||||
|
watches map[string]*watch // Map of inotify watches (key: path)
|
||||||
|
paths map[int]string // Map of watched paths (key: watch descriptor)
|
||||||
|
done chan struct{} // Channel for sending a "quit message" to the reader goroutine
|
||||||
|
doneResp chan struct{} // Channel to respond to Close
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewWatcher creates a new Watcher.
|
||||||
|
func NewWatcher() (*Watcher, error) {
|
||||||
|
// Create inotify fd
|
||||||
|
// Need to set the FD to nonblocking mode in order for SetDeadline methods to work
|
||||||
|
// Otherwise, blocking i/o operations won't terminate on close
|
||||||
|
fd, errno := unix.InotifyInit1(unix.IN_CLOEXEC | unix.IN_NONBLOCK)
|
||||||
|
if fd == -1 {
|
||||||
|
return nil, errno
|
||||||
|
}
|
||||||
|
|
||||||
|
w := &Watcher{
|
||||||
|
fd: fd,
|
||||||
|
inotifyFile: os.NewFile(uintptr(fd), ""),
|
||||||
|
watches: make(map[string]*watch),
|
||||||
|
paths: make(map[int]string),
|
||||||
|
Events: make(chan Event),
|
||||||
|
Errors: make(chan error),
|
||||||
|
done: make(chan struct{}),
|
||||||
|
doneResp: make(chan struct{}),
|
||||||
|
}
|
||||||
|
|
||||||
|
go w.readEvents()
|
||||||
|
return w, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns true if the event was sent, or false if watcher is closed.
|
||||||
|
func (w *Watcher) sendEvent(e Event) bool {
|
||||||
|
select {
|
||||||
|
case w.Events <- e:
|
||||||
|
return true
|
||||||
|
case <-w.done:
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns true if the error was sent, or false if watcher is closed.
|
||||||
|
func (w *Watcher) sendError(err error) bool {
|
||||||
|
select {
|
||||||
|
case w.Errors <- err:
|
||||||
|
return true
|
||||||
|
case <-w.done:
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *Watcher) isClosed() bool {
|
||||||
|
select {
|
||||||
|
case <-w.done:
|
||||||
|
return true
|
||||||
|
default:
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close removes all watches and closes the events channel.
|
||||||
|
func (w *Watcher) Close() error {
|
||||||
|
w.mu.Lock()
|
||||||
|
if w.isClosed() {
|
||||||
|
w.mu.Unlock()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Send 'close' signal to goroutine, and set the Watcher to closed.
|
||||||
|
close(w.done)
|
||||||
|
w.mu.Unlock()
|
||||||
|
|
||||||
|
// Causes any blocking reads to return with an error, provided the file
|
||||||
|
// still supports deadline operations.
|
||||||
|
err := w.inotifyFile.Close()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wait for goroutine to close
|
||||||
|
<-w.doneResp
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add starts monitoring the path for changes.
|
||||||
|
//
|
||||||
|
// A path can only be watched once; attempting to watch it more than once will
|
||||||
|
// return an error. Paths that do not yet exist on the filesystem cannot be
|
||||||
|
// added. A watch will be automatically removed if the path is deleted.
|
||||||
|
//
|
||||||
|
// A path will remain watched if it gets renamed to somewhere else on the same
|
||||||
|
// filesystem, but the monitor will get removed if the path gets deleted and
|
||||||
|
// re-created, or if it's moved to a different filesystem.
|
||||||
|
//
|
||||||
|
// Notifications on network filesystems (NFS, SMB, FUSE, etc.) or special
|
||||||
|
// filesystems (/proc, /sys, etc.) generally don't work.
|
||||||
|
//
|
||||||
|
// # Watching directories
|
||||||
|
//
|
||||||
|
// All files in a directory are monitored, including new files that are created
|
||||||
|
// after the watcher is started. Subdirectories are not watched (i.e. it's
|
||||||
|
// non-recursive).
|
||||||
|
//
|
||||||
|
// # Watching files
|
||||||
|
//
|
||||||
|
// Watching individual files (rather than directories) is generally not
|
||||||
|
// recommended as many tools update files atomically. Instead of "just" writing
|
||||||
|
// to the file a temporary file will be written to first, and if successful the
|
||||||
|
// temporary file is moved to to destination removing the original, or some
|
||||||
|
// variant thereof. The watcher on the original file is now lost, as it no
|
||||||
|
// longer exists.
|
||||||
|
//
|
||||||
|
// Instead, watch the parent directory and use Event.Name to filter out files
|
||||||
|
// you're not interested in. There is an example of this in [cmd/fsnotify/file.go].
|
||||||
|
func (w *Watcher) Add(name string) error {
|
||||||
|
name = filepath.Clean(name)
|
||||||
|
if w.isClosed() {
|
||||||
|
return errors.New("inotify instance already closed")
|
||||||
|
}
|
||||||
|
|
||||||
|
var flags uint32 = unix.IN_MOVED_TO | unix.IN_MOVED_FROM |
|
||||||
|
unix.IN_CREATE | unix.IN_ATTRIB | unix.IN_MODIFY |
|
||||||
|
unix.IN_MOVE_SELF | unix.IN_DELETE | unix.IN_DELETE_SELF
|
||||||
|
|
||||||
|
w.mu.Lock()
|
||||||
|
defer w.mu.Unlock()
|
||||||
|
watchEntry := w.watches[name]
|
||||||
|
if watchEntry != nil {
|
||||||
|
flags |= watchEntry.flags | unix.IN_MASK_ADD
|
||||||
|
}
|
||||||
|
wd, errno := unix.InotifyAddWatch(w.fd, name, flags)
|
||||||
|
if wd == -1 {
|
||||||
|
return errno
|
||||||
|
}
|
||||||
|
|
||||||
|
if watchEntry == nil {
|
||||||
|
w.watches[name] = &watch{wd: uint32(wd), flags: flags}
|
||||||
|
w.paths[wd] = name
|
||||||
|
} else {
|
||||||
|
watchEntry.wd = uint32(wd)
|
||||||
|
watchEntry.flags = flags
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove stops monitoring the path for changes.
|
||||||
|
//
|
||||||
|
// Directories are always removed non-recursively. For example, if you added
|
||||||
|
// /tmp/dir and /tmp/dir/subdir then you will need to remove both.
|
||||||
|
//
|
||||||
|
// Removing a path that has not yet been added returns [ErrNonExistentWatch].
|
||||||
|
func (w *Watcher) Remove(name string) error {
|
||||||
|
name = filepath.Clean(name)
|
||||||
|
|
||||||
|
// Fetch the watch.
|
||||||
|
w.mu.Lock()
|
||||||
|
defer w.mu.Unlock()
|
||||||
|
watch, ok := w.watches[name]
|
||||||
|
|
||||||
|
// Remove it from inotify.
|
||||||
|
if !ok {
|
||||||
|
return fmt.Errorf("%w: %s", ErrNonExistentWatch, name)
|
||||||
|
}
|
||||||
|
|
||||||
|
// We successfully removed the watch if InotifyRmWatch doesn't return an
|
||||||
|
// error, we need to clean up our internal state to ensure it matches
|
||||||
|
// inotify's kernel state.
|
||||||
|
delete(w.paths, int(watch.wd))
|
||||||
|
delete(w.watches, name)
|
||||||
|
|
||||||
|
// inotify_rm_watch will return EINVAL if the file has been deleted;
|
||||||
|
// the inotify will already have been removed.
|
||||||
|
// watches and pathes are deleted in ignoreLinux() implicitly and asynchronously
|
||||||
|
// by calling inotify_rm_watch() below. e.g. readEvents() goroutine receives IN_IGNORE
|
||||||
|
// so that EINVAL means that the wd is being rm_watch()ed or its file removed
|
||||||
|
// by another thread and we have not received IN_IGNORE event.
|
||||||
|
success, errno := unix.InotifyRmWatch(w.fd, watch.wd)
|
||||||
|
if success == -1 {
|
||||||
|
// TODO: Perhaps it's not helpful to return an error here in every case;
|
||||||
|
// The only two possible errors are:
|
||||||
|
//
|
||||||
|
// - EBADF, which happens when w.fd is not a valid file descriptor
|
||||||
|
// of any kind.
|
||||||
|
// - EINVAL, which is when fd is not an inotify descriptor or wd
|
||||||
|
// is not a valid watch descriptor. Watch descriptors are
|
||||||
|
// invalidated when they are removed explicitly or implicitly;
|
||||||
|
// explicitly by inotify_rm_watch, implicitly when the file they
|
||||||
|
// are watching is deleted.
|
||||||
|
return errno
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// WatchList returns all paths added with [Add] (and are not yet removed).
|
||||||
|
func (w *Watcher) WatchList() []string {
|
||||||
|
w.mu.Lock()
|
||||||
|
defer w.mu.Unlock()
|
||||||
|
|
||||||
|
entries := make([]string, 0, len(w.watches))
|
||||||
|
for pathname := range w.watches {
|
||||||
|
entries = append(entries, pathname)
|
||||||
|
}
|
||||||
|
|
||||||
|
return entries
|
||||||
|
}
|
||||||
|
|
||||||
|
type watch struct {
|
||||||
|
wd uint32 // Watch descriptor (as returned by the inotify_add_watch() syscall)
|
||||||
|
flags uint32 // inotify flags of this watch (see inotify(7) for the list of valid flags)
|
||||||
|
}
|
||||||
|
|
||||||
|
// readEvents reads from the inotify file descriptor, converts the
|
||||||
|
// received events into Event objects and sends them via the Events channel
|
||||||
|
func (w *Watcher) readEvents() {
|
||||||
|
defer func() {
|
||||||
|
close(w.doneResp)
|
||||||
|
close(w.Errors)
|
||||||
|
close(w.Events)
|
||||||
|
}()
|
||||||
|
|
||||||
|
var (
|
||||||
|
buf [unix.SizeofInotifyEvent * 4096]byte // Buffer for a maximum of 4096 raw events
|
||||||
|
errno error // Syscall errno
|
||||||
|
)
|
||||||
|
for {
|
||||||
|
// See if we have been closed.
|
||||||
|
if w.isClosed() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
n, err := w.inotifyFile.Read(buf[:])
|
||||||
|
switch {
|
||||||
|
case errors.Unwrap(err) == os.ErrClosed:
|
||||||
|
return
|
||||||
|
case err != nil:
|
||||||
|
if !w.sendError(err) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if n < unix.SizeofInotifyEvent {
|
||||||
|
var err error
|
||||||
|
if n == 0 {
|
||||||
|
// If EOF is received. This should really never happen.
|
||||||
|
err = io.EOF
|
||||||
|
} else if n < 0 {
|
||||||
|
// If an error occurred while reading.
|
||||||
|
err = errno
|
||||||
|
} else {
|
||||||
|
// Read was too short.
|
||||||
|
err = errors.New("notify: short read in readEvents()")
|
||||||
|
}
|
||||||
|
if !w.sendError(err) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
var offset uint32
|
||||||
|
// We don't know how many events we just read into the buffer
|
||||||
|
// While the offset points to at least one whole event...
|
||||||
|
for offset <= uint32(n-unix.SizeofInotifyEvent) {
|
||||||
|
var (
|
||||||
|
// Point "raw" to the event in the buffer
|
||||||
|
raw = (*unix.InotifyEvent)(unsafe.Pointer(&buf[offset]))
|
||||||
|
mask = uint32(raw.Mask)
|
||||||
|
nameLen = uint32(raw.Len)
|
||||||
|
)
|
||||||
|
|
||||||
|
if mask&unix.IN_Q_OVERFLOW != 0 {
|
||||||
|
if !w.sendError(ErrEventOverflow) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the event happened to the watched directory or the watched file, the kernel
|
||||||
|
// doesn't append the filename to the event, but we would like to always fill the
|
||||||
|
// the "Name" field with a valid filename. We retrieve the path of the watch from
|
||||||
|
// the "paths" map.
|
||||||
|
w.mu.Lock()
|
||||||
|
name, ok := w.paths[int(raw.Wd)]
|
||||||
|
// IN_DELETE_SELF occurs when the file/directory being watched is removed.
|
||||||
|
// This is a sign to clean up the maps, otherwise we are no longer in sync
|
||||||
|
// with the inotify kernel state which has already deleted the watch
|
||||||
|
// automatically.
|
||||||
|
if ok && mask&unix.IN_DELETE_SELF == unix.IN_DELETE_SELF {
|
||||||
|
delete(w.paths, int(raw.Wd))
|
||||||
|
delete(w.watches, name)
|
||||||
|
}
|
||||||
|
w.mu.Unlock()
|
||||||
|
|
||||||
|
if nameLen > 0 {
|
||||||
|
// Point "bytes" at the first byte of the filename
|
||||||
|
bytes := (*[unix.PathMax]byte)(unsafe.Pointer(&buf[offset+unix.SizeofInotifyEvent]))[:nameLen:nameLen]
|
||||||
|
// The filename is padded with NULL bytes. TrimRight() gets rid of those.
|
||||||
|
name += "/" + strings.TrimRight(string(bytes[0:nameLen]), "\000")
|
||||||
|
}
|
||||||
|
|
||||||
|
event := w.newEvent(name, mask)
|
||||||
|
|
||||||
|
// Send the events that are not ignored on the events channel
|
||||||
|
if mask&unix.IN_IGNORED == 0 {
|
||||||
|
if !w.sendEvent(event) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Move to the next event in the buffer
|
||||||
|
offset += unix.SizeofInotifyEvent + nameLen
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// newEvent returns an platform-independent Event based on an inotify mask.
|
||||||
|
func (w *Watcher) newEvent(name string, mask uint32) Event {
|
||||||
|
e := Event{Name: name}
|
||||||
|
if mask&unix.IN_CREATE == unix.IN_CREATE || mask&unix.IN_MOVED_TO == unix.IN_MOVED_TO {
|
||||||
|
e.Op |= Create
|
||||||
|
}
|
||||||
|
if mask&unix.IN_DELETE_SELF == unix.IN_DELETE_SELF || mask&unix.IN_DELETE == unix.IN_DELETE {
|
||||||
|
e.Op |= Remove
|
||||||
|
}
|
||||||
|
if mask&unix.IN_MODIFY == unix.IN_MODIFY {
|
||||||
|
e.Op |= Write
|
||||||
|
}
|
||||||
|
if mask&unix.IN_MOVE_SELF == unix.IN_MOVE_SELF || mask&unix.IN_MOVED_FROM == unix.IN_MOVED_FROM {
|
||||||
|
e.Op |= Rename
|
||||||
|
}
|
||||||
|
if mask&unix.IN_ATTRIB == unix.IN_ATTRIB {
|
||||||
|
e.Op |= Chmod
|
||||||
|
}
|
||||||
|
return e
|
||||||
|
}
|
|
@ -0,0 +1,707 @@
|
||||||
|
//go:build freebsd || openbsd || netbsd || dragonfly || darwin
|
||||||
|
// +build freebsd openbsd netbsd dragonfly darwin
|
||||||
|
|
||||||
|
package fsnotify
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"golang.org/x/sys/unix"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Watcher watches a set of paths, delivering events on a channel.
|
||||||
|
//
|
||||||
|
// A watcher should not be copied (e.g. pass it by pointer, rather than by
|
||||||
|
// value).
|
||||||
|
//
|
||||||
|
// # Linux notes
|
||||||
|
//
|
||||||
|
// When a file is removed a Remove event won't be emitted until all file
|
||||||
|
// descriptors are closed, and deletes will always emit a Chmod. For example:
|
||||||
|
//
|
||||||
|
// fp := os.Open("file")
|
||||||
|
// os.Remove("file") // Triggers Chmod
|
||||||
|
// fp.Close() // Triggers Remove
|
||||||
|
//
|
||||||
|
// This is the event that inotify sends, so not much can be changed about this.
|
||||||
|
//
|
||||||
|
// The fs.inotify.max_user_watches sysctl variable specifies the upper limit
|
||||||
|
// for the number of watches per user, and fs.inotify.max_user_instances
|
||||||
|
// specifies the maximum number of inotify instances per user. Every Watcher you
|
||||||
|
// create is an "instance", and every path you add is a "watch".
|
||||||
|
//
|
||||||
|
// These are also exposed in /proc as /proc/sys/fs/inotify/max_user_watches and
|
||||||
|
// /proc/sys/fs/inotify/max_user_instances
|
||||||
|
//
|
||||||
|
// To increase them you can use sysctl or write the value to the /proc file:
|
||||||
|
//
|
||||||
|
// # Default values on Linux 5.18
|
||||||
|
// sysctl fs.inotify.max_user_watches=124983
|
||||||
|
// sysctl fs.inotify.max_user_instances=128
|
||||||
|
//
|
||||||
|
// To make the changes persist on reboot edit /etc/sysctl.conf or
|
||||||
|
// /usr/lib/sysctl.d/50-default.conf (details differ per Linux distro; check
|
||||||
|
// your distro's documentation):
|
||||||
|
//
|
||||||
|
// fs.inotify.max_user_watches=124983
|
||||||
|
// fs.inotify.max_user_instances=128
|
||||||
|
//
|
||||||
|
// Reaching the limit will result in a "no space left on device" or "too many open
|
||||||
|
// files" error.
|
||||||
|
//
|
||||||
|
// # kqueue notes (macOS, BSD)
|
||||||
|
//
|
||||||
|
// kqueue requires opening a file descriptor for every file that's being watched;
|
||||||
|
// so if you're watching a directory with five files then that's six file
|
||||||
|
// descriptors. You will run in to your system's "max open files" limit faster on
|
||||||
|
// these platforms.
|
||||||
|
//
|
||||||
|
// The sysctl variables kern.maxfiles and kern.maxfilesperproc can be used to
|
||||||
|
// control the maximum number of open files, as well as /etc/login.conf on BSD
|
||||||
|
// systems.
|
||||||
|
//
|
||||||
|
// # macOS notes
|
||||||
|
//
|
||||||
|
// Spotlight indexing on macOS can result in multiple events (see [#15]). A
|
||||||
|
// temporary workaround is to add your folder(s) to the "Spotlight Privacy
|
||||||
|
// Settings" until we have a native FSEvents implementation (see [#11]).
|
||||||
|
//
|
||||||
|
// [#11]: https://github.com/fsnotify/fsnotify/issues/11
|
||||||
|
// [#15]: https://github.com/fsnotify/fsnotify/issues/15
|
||||||
|
type Watcher struct {
|
||||||
|
// Events sends the filesystem change events.
|
||||||
|
//
|
||||||
|
// fsnotify can send the following events; a "path" here can refer to a
|
||||||
|
// file, directory, symbolic link, or special file like a FIFO.
|
||||||
|
//
|
||||||
|
// fsnotify.Create A new path was created; this may be followed by one
|
||||||
|
// or more Write events if data also gets written to a
|
||||||
|
// file.
|
||||||
|
//
|
||||||
|
// fsnotify.Remove A path was removed.
|
||||||
|
//
|
||||||
|
// fsnotify.Rename A path was renamed. A rename is always sent with the
|
||||||
|
// old path as Event.Name, and a Create event will be
|
||||||
|
// sent with the new name. Renames are only sent for
|
||||||
|
// paths that are currently watched; e.g. moving an
|
||||||
|
// unmonitored file into a monitored directory will
|
||||||
|
// show up as just a Create. Similarly, renaming a file
|
||||||
|
// to outside a monitored directory will show up as
|
||||||
|
// only a Rename.
|
||||||
|
//
|
||||||
|
// fsnotify.Write A file or named pipe was written to. A Truncate will
|
||||||
|
// also trigger a Write. A single "write action"
|
||||||
|
// initiated by the user may show up as one or multiple
|
||||||
|
// writes, depending on when the system syncs things to
|
||||||
|
// disk. For example when compiling a large Go program
|
||||||
|
// you may get hundreds of Write events, so you
|
||||||
|
// probably want to wait until you've stopped receiving
|
||||||
|
// them (see the dedup example in cmd/fsnotify).
|
||||||
|
//
|
||||||
|
// fsnotify.Chmod Attributes were changed. On Linux this is also sent
|
||||||
|
// when a file is removed (or more accurately, when a
|
||||||
|
// link to an inode is removed). On kqueue it's sent
|
||||||
|
// and on kqueue when a file is truncated. On Windows
|
||||||
|
// it's never sent.
|
||||||
|
Events chan Event
|
||||||
|
|
||||||
|
// Errors sends any errors.
|
||||||
|
Errors chan error
|
||||||
|
|
||||||
|
done chan struct{}
|
||||||
|
kq int // File descriptor (as returned by the kqueue() syscall).
|
||||||
|
closepipe [2]int // Pipe used for closing.
|
||||||
|
mu sync.Mutex // Protects access to watcher data
|
||||||
|
watches map[string]int // Watched file descriptors (key: path).
|
||||||
|
watchesByDir map[string]map[int]struct{} // Watched file descriptors indexed by the parent directory (key: dirname(path)).
|
||||||
|
userWatches map[string]struct{} // Watches added with Watcher.Add()
|
||||||
|
dirFlags map[string]uint32 // Watched directories to fflags used in kqueue.
|
||||||
|
paths map[int]pathInfo // File descriptors to path names for processing kqueue events.
|
||||||
|
fileExists map[string]struct{} // Keep track of if we know this file exists (to stop duplicate create events).
|
||||||
|
isClosed bool // Set to true when Close() is first called
|
||||||
|
}
|
||||||
|
|
||||||
|
type pathInfo struct {
|
||||||
|
name string
|
||||||
|
isDir bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewWatcher creates a new Watcher.
|
||||||
|
func NewWatcher() (*Watcher, error) {
|
||||||
|
kq, closepipe, err := newKqueue()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
w := &Watcher{
|
||||||
|
kq: kq,
|
||||||
|
closepipe: closepipe,
|
||||||
|
watches: make(map[string]int),
|
||||||
|
watchesByDir: make(map[string]map[int]struct{}),
|
||||||
|
dirFlags: make(map[string]uint32),
|
||||||
|
paths: make(map[int]pathInfo),
|
||||||
|
fileExists: make(map[string]struct{}),
|
||||||
|
userWatches: make(map[string]struct{}),
|
||||||
|
Events: make(chan Event),
|
||||||
|
Errors: make(chan error),
|
||||||
|
done: make(chan struct{}),
|
||||||
|
}
|
||||||
|
|
||||||
|
go w.readEvents()
|
||||||
|
return w, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// newKqueue creates a new kernel event queue and returns a descriptor.
|
||||||
|
//
|
||||||
|
// This registers a new event on closepipe, which will trigger an event when
|
||||||
|
// it's closed. This way we can use kevent() without timeout/polling; without
|
||||||
|
// the closepipe, it would block forever and we wouldn't be able to stop it at
|
||||||
|
// all.
|
||||||
|
func newKqueue() (kq int, closepipe [2]int, err error) {
|
||||||
|
kq, err = unix.Kqueue()
|
||||||
|
if kq == -1 {
|
||||||
|
return kq, closepipe, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Register the close pipe.
|
||||||
|
err = unix.Pipe(closepipe[:])
|
||||||
|
if err != nil {
|
||||||
|
unix.Close(kq)
|
||||||
|
return kq, closepipe, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Register changes to listen on the closepipe.
|
||||||
|
changes := make([]unix.Kevent_t, 1)
|
||||||
|
// SetKevent converts int to the platform-specific types.
|
||||||
|
unix.SetKevent(&changes[0], closepipe[0], unix.EVFILT_READ,
|
||||||
|
unix.EV_ADD|unix.EV_ENABLE|unix.EV_ONESHOT)
|
||||||
|
|
||||||
|
ok, err := unix.Kevent(kq, changes, nil, nil)
|
||||||
|
if ok == -1 {
|
||||||
|
unix.Close(kq)
|
||||||
|
unix.Close(closepipe[0])
|
||||||
|
unix.Close(closepipe[1])
|
||||||
|
return kq, closepipe, err
|
||||||
|
}
|
||||||
|
return kq, closepipe, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns true if the event was sent, or false if watcher is closed.
|
||||||
|
func (w *Watcher) sendEvent(e Event) bool {
|
||||||
|
select {
|
||||||
|
case w.Events <- e:
|
||||||
|
return true
|
||||||
|
case <-w.done:
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns true if the error was sent, or false if watcher is closed.
|
||||||
|
func (w *Watcher) sendError(err error) bool {
|
||||||
|
select {
|
||||||
|
case w.Errors <- err:
|
||||||
|
return true
|
||||||
|
case <-w.done:
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close removes all watches and closes the events channel.
|
||||||
|
func (w *Watcher) Close() error {
|
||||||
|
w.mu.Lock()
|
||||||
|
if w.isClosed {
|
||||||
|
w.mu.Unlock()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
w.isClosed = true
|
||||||
|
|
||||||
|
// copy paths to remove while locked
|
||||||
|
pathsToRemove := make([]string, 0, len(w.watches))
|
||||||
|
for name := range w.watches {
|
||||||
|
pathsToRemove = append(pathsToRemove, name)
|
||||||
|
}
|
||||||
|
w.mu.Unlock() // Unlock before calling Remove, which also locks
|
||||||
|
for _, name := range pathsToRemove {
|
||||||
|
w.Remove(name)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Send "quit" message to the reader goroutine.
|
||||||
|
unix.Close(w.closepipe[1])
|
||||||
|
close(w.done)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add starts monitoring the path for changes.
|
||||||
|
//
|
||||||
|
// A path can only be watched once; attempting to watch it more than once will
|
||||||
|
// return an error. Paths that do not yet exist on the filesystem cannot be
|
||||||
|
// added. A watch will be automatically removed if the path is deleted.
|
||||||
|
//
|
||||||
|
// A path will remain watched if it gets renamed to somewhere else on the same
|
||||||
|
// filesystem, but the monitor will get removed if the path gets deleted and
|
||||||
|
// re-created, or if it's moved to a different filesystem.
|
||||||
|
//
|
||||||
|
// Notifications on network filesystems (NFS, SMB, FUSE, etc.) or special
|
||||||
|
// filesystems (/proc, /sys, etc.) generally don't work.
|
||||||
|
//
|
||||||
|
// # Watching directories
|
||||||
|
//
|
||||||
|
// All files in a directory are monitored, including new files that are created
|
||||||
|
// after the watcher is started. Subdirectories are not watched (i.e. it's
|
||||||
|
// non-recursive).
|
||||||
|
//
|
||||||
|
// # Watching files
|
||||||
|
//
|
||||||
|
// Watching individual files (rather than directories) is generally not
|
||||||
|
// recommended as many tools update files atomically. Instead of "just" writing
|
||||||
|
// to the file a temporary file will be written to first, and if successful the
|
||||||
|
// temporary file is moved to to destination removing the original, or some
|
||||||
|
// variant thereof. The watcher on the original file is now lost, as it no
|
||||||
|
// longer exists.
|
||||||
|
//
|
||||||
|
// Instead, watch the parent directory and use Event.Name to filter out files
|
||||||
|
// you're not interested in. There is an example of this in [cmd/fsnotify/file.go].
|
||||||
|
func (w *Watcher) Add(name string) error {
|
||||||
|
w.mu.Lock()
|
||||||
|
w.userWatches[name] = struct{}{}
|
||||||
|
w.mu.Unlock()
|
||||||
|
_, err := w.addWatch(name, noteAllEvents)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove stops monitoring the path for changes.
|
||||||
|
//
|
||||||
|
// Directories are always removed non-recursively. For example, if you added
|
||||||
|
// /tmp/dir and /tmp/dir/subdir then you will need to remove both.
|
||||||
|
//
|
||||||
|
// Removing a path that has not yet been added returns [ErrNonExistentWatch].
|
||||||
|
func (w *Watcher) Remove(name string) error {
|
||||||
|
name = filepath.Clean(name)
|
||||||
|
w.mu.Lock()
|
||||||
|
watchfd, ok := w.watches[name]
|
||||||
|
w.mu.Unlock()
|
||||||
|
if !ok {
|
||||||
|
return fmt.Errorf("%w: %s", ErrNonExistentWatch, name)
|
||||||
|
}
|
||||||
|
|
||||||
|
err := w.register([]int{watchfd}, unix.EV_DELETE, 0)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
unix.Close(watchfd)
|
||||||
|
|
||||||
|
w.mu.Lock()
|
||||||
|
isDir := w.paths[watchfd].isDir
|
||||||
|
delete(w.watches, name)
|
||||||
|
delete(w.userWatches, name)
|
||||||
|
|
||||||
|
parentName := filepath.Dir(name)
|
||||||
|
delete(w.watchesByDir[parentName], watchfd)
|
||||||
|
|
||||||
|
if len(w.watchesByDir[parentName]) == 0 {
|
||||||
|
delete(w.watchesByDir, parentName)
|
||||||
|
}
|
||||||
|
|
||||||
|
delete(w.paths, watchfd)
|
||||||
|
delete(w.dirFlags, name)
|
||||||
|
delete(w.fileExists, name)
|
||||||
|
w.mu.Unlock()
|
||||||
|
|
||||||
|
// Find all watched paths that are in this directory that are not external.
|
||||||
|
if isDir {
|
||||||
|
var pathsToRemove []string
|
||||||
|
w.mu.Lock()
|
||||||
|
for fd := range w.watchesByDir[name] {
|
||||||
|
path := w.paths[fd]
|
||||||
|
if _, ok := w.userWatches[path.name]; !ok {
|
||||||
|
pathsToRemove = append(pathsToRemove, path.name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
w.mu.Unlock()
|
||||||
|
for _, name := range pathsToRemove {
|
||||||
|
// Since these are internal, not much sense in propagating error
|
||||||
|
// to the user, as that will just confuse them with an error about
|
||||||
|
// a path they did not explicitly watch themselves.
|
||||||
|
w.Remove(name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// WatchList returns all paths added with [Add] (and are not yet removed).
|
||||||
|
func (w *Watcher) WatchList() []string {
|
||||||
|
w.mu.Lock()
|
||||||
|
defer w.mu.Unlock()
|
||||||
|
|
||||||
|
entries := make([]string, 0, len(w.userWatches))
|
||||||
|
for pathname := range w.userWatches {
|
||||||
|
entries = append(entries, pathname)
|
||||||
|
}
|
||||||
|
|
||||||
|
return entries
|
||||||
|
}
|
||||||
|
|
||||||
|
// Watch all events (except NOTE_EXTEND, NOTE_LINK, NOTE_REVOKE)
|
||||||
|
const noteAllEvents = unix.NOTE_DELETE | unix.NOTE_WRITE | unix.NOTE_ATTRIB | unix.NOTE_RENAME
|
||||||
|
|
||||||
|
// addWatch adds name to the watched file set.
|
||||||
|
// The flags are interpreted as described in kevent(2).
|
||||||
|
// Returns the real path to the file which was added, if any, which may be different from the one passed in the case of symlinks.
|
||||||
|
func (w *Watcher) addWatch(name string, flags uint32) (string, error) {
|
||||||
|
var isDir bool
|
||||||
|
// Make ./name and name equivalent
|
||||||
|
name = filepath.Clean(name)
|
||||||
|
|
||||||
|
w.mu.Lock()
|
||||||
|
if w.isClosed {
|
||||||
|
w.mu.Unlock()
|
||||||
|
return "", errors.New("kevent instance already closed")
|
||||||
|
}
|
||||||
|
watchfd, alreadyWatching := w.watches[name]
|
||||||
|
// We already have a watch, but we can still override flags.
|
||||||
|
if alreadyWatching {
|
||||||
|
isDir = w.paths[watchfd].isDir
|
||||||
|
}
|
||||||
|
w.mu.Unlock()
|
||||||
|
|
||||||
|
if !alreadyWatching {
|
||||||
|
fi, err := os.Lstat(name)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Don't watch sockets or named pipes
|
||||||
|
if (fi.Mode()&os.ModeSocket == os.ModeSocket) || (fi.Mode()&os.ModeNamedPipe == os.ModeNamedPipe) {
|
||||||
|
return "", nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Follow Symlinks
|
||||||
|
//
|
||||||
|
// Linux can add unresolvable symlinks to the watch list without issue,
|
||||||
|
// and Windows can't do symlinks period. To maintain consistency, we
|
||||||
|
// will act like everything is fine if the link can't be resolved.
|
||||||
|
// There will simply be no file events for broken symlinks. Hence the
|
||||||
|
// returns of nil on errors.
|
||||||
|
if fi.Mode()&os.ModeSymlink == os.ModeSymlink {
|
||||||
|
name, err = filepath.EvalSymlinks(name)
|
||||||
|
if err != nil {
|
||||||
|
return "", nil
|
||||||
|
}
|
||||||
|
|
||||||
|
w.mu.Lock()
|
||||||
|
_, alreadyWatching = w.watches[name]
|
||||||
|
w.mu.Unlock()
|
||||||
|
|
||||||
|
if alreadyWatching {
|
||||||
|
return name, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
fi, err = os.Lstat(name)
|
||||||
|
if err != nil {
|
||||||
|
return "", nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Retry on EINTR; open() can return EINTR in practice on macOS.
|
||||||
|
// See #354, and go issues 11180 and 39237.
|
||||||
|
for {
|
||||||
|
watchfd, err = unix.Open(name, openMode, 0)
|
||||||
|
if err == nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if errors.Is(err, unix.EINTR) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
isDir = fi.IsDir()
|
||||||
|
}
|
||||||
|
|
||||||
|
err := w.register([]int{watchfd}, unix.EV_ADD|unix.EV_CLEAR|unix.EV_ENABLE, flags)
|
||||||
|
if err != nil {
|
||||||
|
unix.Close(watchfd)
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
if !alreadyWatching {
|
||||||
|
w.mu.Lock()
|
||||||
|
parentName := filepath.Dir(name)
|
||||||
|
w.watches[name] = watchfd
|
||||||
|
|
||||||
|
watchesByDir, ok := w.watchesByDir[parentName]
|
||||||
|
if !ok {
|
||||||
|
watchesByDir = make(map[int]struct{}, 1)
|
||||||
|
w.watchesByDir[parentName] = watchesByDir
|
||||||
|
}
|
||||||
|
watchesByDir[watchfd] = struct{}{}
|
||||||
|
|
||||||
|
w.paths[watchfd] = pathInfo{name: name, isDir: isDir}
|
||||||
|
w.mu.Unlock()
|
||||||
|
}
|
||||||
|
|
||||||
|
if isDir {
|
||||||
|
// Watch the directory if it has not been watched before,
|
||||||
|
// or if it was watched before, but perhaps only a NOTE_DELETE (watchDirectoryFiles)
|
||||||
|
w.mu.Lock()
|
||||||
|
|
||||||
|
watchDir := (flags&unix.NOTE_WRITE) == unix.NOTE_WRITE &&
|
||||||
|
(!alreadyWatching || (w.dirFlags[name]&unix.NOTE_WRITE) != unix.NOTE_WRITE)
|
||||||
|
// Store flags so this watch can be updated later
|
||||||
|
w.dirFlags[name] = flags
|
||||||
|
w.mu.Unlock()
|
||||||
|
|
||||||
|
if watchDir {
|
||||||
|
if err := w.watchDirectoryFiles(name); err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return name, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// readEvents reads from kqueue and converts the received kevents into
|
||||||
|
// Event values that it sends down the Events channel.
|
||||||
|
func (w *Watcher) readEvents() {
|
||||||
|
defer func() {
|
||||||
|
err := unix.Close(w.kq)
|
||||||
|
if err != nil {
|
||||||
|
w.Errors <- err
|
||||||
|
}
|
||||||
|
unix.Close(w.closepipe[0])
|
||||||
|
close(w.Events)
|
||||||
|
close(w.Errors)
|
||||||
|
}()
|
||||||
|
|
||||||
|
eventBuffer := make([]unix.Kevent_t, 10)
|
||||||
|
for closed := false; !closed; {
|
||||||
|
kevents, err := w.read(eventBuffer)
|
||||||
|
// EINTR is okay, the syscall was interrupted before timeout expired.
|
||||||
|
if err != nil && err != unix.EINTR {
|
||||||
|
if !w.sendError(fmt.Errorf("fsnotify.readEvents: %w", err)) {
|
||||||
|
closed = true
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Flush the events we received to the Events channel
|
||||||
|
for _, kevent := range kevents {
|
||||||
|
var (
|
||||||
|
watchfd = int(kevent.Ident)
|
||||||
|
mask = uint32(kevent.Fflags)
|
||||||
|
)
|
||||||
|
|
||||||
|
// Shut down the loop when the pipe is closed, but only after all
|
||||||
|
// other events have been processed.
|
||||||
|
if watchfd == w.closepipe[0] {
|
||||||
|
closed = true
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
w.mu.Lock()
|
||||||
|
path := w.paths[watchfd]
|
||||||
|
w.mu.Unlock()
|
||||||
|
|
||||||
|
event := w.newEvent(path.name, mask)
|
||||||
|
|
||||||
|
if path.isDir && !event.Has(Remove) {
|
||||||
|
// Double check to make sure the directory exists. This can
|
||||||
|
// happen when we do a rm -fr on a recursively watched folders
|
||||||
|
// and we receive a modification event first but the folder has
|
||||||
|
// been deleted and later receive the delete event.
|
||||||
|
if _, err := os.Lstat(event.Name); os.IsNotExist(err) {
|
||||||
|
event.Op |= Remove
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if event.Has(Rename) || event.Has(Remove) {
|
||||||
|
w.Remove(event.Name)
|
||||||
|
w.mu.Lock()
|
||||||
|
delete(w.fileExists, event.Name)
|
||||||
|
w.mu.Unlock()
|
||||||
|
}
|
||||||
|
|
||||||
|
if path.isDir && event.Has(Write) && !event.Has(Remove) {
|
||||||
|
w.sendDirectoryChangeEvents(event.Name)
|
||||||
|
} else {
|
||||||
|
if !w.sendEvent(event) {
|
||||||
|
closed = true
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if event.Has(Remove) {
|
||||||
|
// Look for a file that may have overwritten this.
|
||||||
|
// For example, mv f1 f2 will delete f2, then create f2.
|
||||||
|
if path.isDir {
|
||||||
|
fileDir := filepath.Clean(event.Name)
|
||||||
|
w.mu.Lock()
|
||||||
|
_, found := w.watches[fileDir]
|
||||||
|
w.mu.Unlock()
|
||||||
|
if found {
|
||||||
|
// make sure the directory exists before we watch for changes. When we
|
||||||
|
// do a recursive watch and perform rm -fr, the parent directory might
|
||||||
|
// have gone missing, ignore the missing directory and let the
|
||||||
|
// upcoming delete event remove the watch from the parent directory.
|
||||||
|
if _, err := os.Lstat(fileDir); err == nil {
|
||||||
|
w.sendDirectoryChangeEvents(fileDir)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
filePath := filepath.Clean(event.Name)
|
||||||
|
if fileInfo, err := os.Lstat(filePath); err == nil {
|
||||||
|
w.sendFileCreatedEventIfNew(filePath, fileInfo)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// newEvent returns an platform-independent Event based on kqueue Fflags.
|
||||||
|
func (w *Watcher) newEvent(name string, mask uint32) Event {
|
||||||
|
e := Event{Name: name}
|
||||||
|
if mask&unix.NOTE_DELETE == unix.NOTE_DELETE {
|
||||||
|
e.Op |= Remove
|
||||||
|
}
|
||||||
|
if mask&unix.NOTE_WRITE == unix.NOTE_WRITE {
|
||||||
|
e.Op |= Write
|
||||||
|
}
|
||||||
|
if mask&unix.NOTE_RENAME == unix.NOTE_RENAME {
|
||||||
|
e.Op |= Rename
|
||||||
|
}
|
||||||
|
if mask&unix.NOTE_ATTRIB == unix.NOTE_ATTRIB {
|
||||||
|
e.Op |= Chmod
|
||||||
|
}
|
||||||
|
return e
|
||||||
|
}
|
||||||
|
|
||||||
|
// watchDirectoryFiles to mimic inotify when adding a watch on a directory
|
||||||
|
func (w *Watcher) watchDirectoryFiles(dirPath string) error {
|
||||||
|
// Get all files
|
||||||
|
files, err := ioutil.ReadDir(dirPath)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, fileInfo := range files {
|
||||||
|
path := filepath.Join(dirPath, fileInfo.Name())
|
||||||
|
|
||||||
|
cleanPath, err := w.internalWatch(path, fileInfo)
|
||||||
|
if err != nil {
|
||||||
|
// No permission to read the file; that's not a problem: just skip.
|
||||||
|
// But do add it to w.fileExists to prevent it from being picked up
|
||||||
|
// as a "new" file later (it still shows up in the directory
|
||||||
|
// listing).
|
||||||
|
switch {
|
||||||
|
case errors.Is(err, unix.EACCES) || errors.Is(err, unix.EPERM):
|
||||||
|
cleanPath = filepath.Clean(path)
|
||||||
|
default:
|
||||||
|
return fmt.Errorf("%q: %w", filepath.Join(dirPath, fileInfo.Name()), err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
w.mu.Lock()
|
||||||
|
w.fileExists[cleanPath] = struct{}{}
|
||||||
|
w.mu.Unlock()
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Search the directory for new files and send an event for them.
|
||||||
|
//
|
||||||
|
// This functionality is to have the BSD watcher match the inotify, which sends
|
||||||
|
// a create event for files created in a watched directory.
|
||||||
|
func (w *Watcher) sendDirectoryChangeEvents(dir string) {
|
||||||
|
// Get all files
|
||||||
|
files, err := ioutil.ReadDir(dir)
|
||||||
|
if err != nil {
|
||||||
|
if !w.sendError(fmt.Errorf("fsnotify.sendDirectoryChangeEvents: %w", err)) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Search for new files
|
||||||
|
for _, fi := range files {
|
||||||
|
err := w.sendFileCreatedEventIfNew(filepath.Join(dir, fi.Name()), fi)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// sendFileCreatedEvent sends a create event if the file isn't already being tracked.
|
||||||
|
func (w *Watcher) sendFileCreatedEventIfNew(filePath string, fileInfo os.FileInfo) (err error) {
|
||||||
|
w.mu.Lock()
|
||||||
|
_, doesExist := w.fileExists[filePath]
|
||||||
|
w.mu.Unlock()
|
||||||
|
if !doesExist {
|
||||||
|
if !w.sendEvent(Event{Name: filePath, Op: Create}) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// like watchDirectoryFiles (but without doing another ReadDir)
|
||||||
|
filePath, err = w.internalWatch(filePath, fileInfo)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
w.mu.Lock()
|
||||||
|
w.fileExists[filePath] = struct{}{}
|
||||||
|
w.mu.Unlock()
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *Watcher) internalWatch(name string, fileInfo os.FileInfo) (string, error) {
|
||||||
|
if fileInfo.IsDir() {
|
||||||
|
// mimic Linux providing delete events for subdirectories
|
||||||
|
// but preserve the flags used if currently watching subdirectory
|
||||||
|
w.mu.Lock()
|
||||||
|
flags := w.dirFlags[name]
|
||||||
|
w.mu.Unlock()
|
||||||
|
|
||||||
|
flags |= unix.NOTE_DELETE | unix.NOTE_RENAME
|
||||||
|
return w.addWatch(name, flags)
|
||||||
|
}
|
||||||
|
|
||||||
|
// watch file to mimic Linux inotify
|
||||||
|
return w.addWatch(name, noteAllEvents)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Register events with the queue.
|
||||||
|
func (w *Watcher) register(fds []int, flags int, fflags uint32) error {
|
||||||
|
changes := make([]unix.Kevent_t, len(fds))
|
||||||
|
for i, fd := range fds {
|
||||||
|
// SetKevent converts int to the platform-specific types.
|
||||||
|
unix.SetKevent(&changes[i], fd, unix.EVFILT_VNODE, flags)
|
||||||
|
changes[i].Fflags = fflags
|
||||||
|
}
|
||||||
|
|
||||||
|
// Register the events.
|
||||||
|
success, err := unix.Kevent(w.kq, changes, nil, nil)
|
||||||
|
if success == -1 {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// read retrieves pending events, or waits until an event occurs.
|
||||||
|
func (w *Watcher) read(events []unix.Kevent_t) ([]unix.Kevent_t, error) {
|
||||||
|
n, err := unix.Kevent(w.kq, nil, events, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return events[0:n], nil
|
||||||
|
}
|
|
@ -0,0 +1,66 @@
|
||||||
|
//go:build !darwin && !dragonfly && !freebsd && !openbsd && !linux && !netbsd && !solaris && !windows
|
||||||
|
// +build !darwin,!dragonfly,!freebsd,!openbsd,!linux,!netbsd,!solaris,!windows
|
||||||
|
|
||||||
|
package fsnotify
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"runtime"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Watcher watches a set of files, delivering events to a channel.
|
||||||
|
type Watcher struct{}
|
||||||
|
|
||||||
|
// NewWatcher creates a new Watcher.
|
||||||
|
func NewWatcher() (*Watcher, error) {
|
||||||
|
return nil, fmt.Errorf("fsnotify not supported on %s", runtime.GOOS)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close removes all watches and closes the events channel.
|
||||||
|
func (w *Watcher) Close() error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add starts monitoring the path for changes.
|
||||||
|
//
|
||||||
|
// A path can only be watched once; attempting to watch it more than once will
|
||||||
|
// return an error. Paths that do not yet exist on the filesystem cannot be
|
||||||
|
// added. A watch will be automatically removed if the path is deleted.
|
||||||
|
//
|
||||||
|
// A path will remain watched if it gets renamed to somewhere else on the same
|
||||||
|
// filesystem, but the monitor will get removed if the path gets deleted and
|
||||||
|
// re-created, or if it's moved to a different filesystem.
|
||||||
|
//
|
||||||
|
// Notifications on network filesystems (NFS, SMB, FUSE, etc.) or special
|
||||||
|
// filesystems (/proc, /sys, etc.) generally don't work.
|
||||||
|
//
|
||||||
|
// # Watching directories
|
||||||
|
//
|
||||||
|
// All files in a directory are monitored, including new files that are created
|
||||||
|
// after the watcher is started. Subdirectories are not watched (i.e. it's
|
||||||
|
// non-recursive).
|
||||||
|
//
|
||||||
|
// # Watching files
|
||||||
|
//
|
||||||
|
// Watching individual files (rather than directories) is generally not
|
||||||
|
// recommended as many tools update files atomically. Instead of "just" writing
|
||||||
|
// to the file a temporary file will be written to first, and if successful the
|
||||||
|
// temporary file is moved to to destination removing the original, or some
|
||||||
|
// variant thereof. The watcher on the original file is now lost, as it no
|
||||||
|
// longer exists.
|
||||||
|
//
|
||||||
|
// Instead, watch the parent directory and use Event.Name to filter out files
|
||||||
|
// you're not interested in. There is an example of this in [cmd/fsnotify/file.go].
|
||||||
|
func (w *Watcher) Add(name string) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove stops monitoring the path for changes.
|
||||||
|
//
|
||||||
|
// Directories are always removed non-recursively. For example, if you added
|
||||||
|
// /tmp/dir and /tmp/dir/subdir then you will need to remove both.
|
||||||
|
//
|
||||||
|
// Removing a path that has not yet been added returns [ErrNonExistentWatch].
|
||||||
|
func (w *Watcher) Remove(name string) error {
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -0,0 +1,746 @@
|
||||||
|
//go:build windows
|
||||||
|
// +build windows
|
||||||
|
|
||||||
|
package fsnotify
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"reflect"
|
||||||
|
"runtime"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
"unsafe"
|
||||||
|
|
||||||
|
"golang.org/x/sys/windows"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Watcher watches a set of paths, delivering events on a channel.
|
||||||
|
//
|
||||||
|
// A watcher should not be copied (e.g. pass it by pointer, rather than by
|
||||||
|
// value).
|
||||||
|
//
|
||||||
|
// # Linux notes
|
||||||
|
//
|
||||||
|
// When a file is removed a Remove event won't be emitted until all file
|
||||||
|
// descriptors are closed, and deletes will always emit a Chmod. For example:
|
||||||
|
//
|
||||||
|
// fp := os.Open("file")
|
||||||
|
// os.Remove("file") // Triggers Chmod
|
||||||
|
// fp.Close() // Triggers Remove
|
||||||
|
//
|
||||||
|
// This is the event that inotify sends, so not much can be changed about this.
|
||||||
|
//
|
||||||
|
// The fs.inotify.max_user_watches sysctl variable specifies the upper limit
|
||||||
|
// for the number of watches per user, and fs.inotify.max_user_instances
|
||||||
|
// specifies the maximum number of inotify instances per user. Every Watcher you
|
||||||
|
// create is an "instance", and every path you add is a "watch".
|
||||||
|
//
|
||||||
|
// These are also exposed in /proc as /proc/sys/fs/inotify/max_user_watches and
|
||||||
|
// /proc/sys/fs/inotify/max_user_instances
|
||||||
|
//
|
||||||
|
// To increase them you can use sysctl or write the value to the /proc file:
|
||||||
|
//
|
||||||
|
// # Default values on Linux 5.18
|
||||||
|
// sysctl fs.inotify.max_user_watches=124983
|
||||||
|
// sysctl fs.inotify.max_user_instances=128
|
||||||
|
//
|
||||||
|
// To make the changes persist on reboot edit /etc/sysctl.conf or
|
||||||
|
// /usr/lib/sysctl.d/50-default.conf (details differ per Linux distro; check
|
||||||
|
// your distro's documentation):
|
||||||
|
//
|
||||||
|
// fs.inotify.max_user_watches=124983
|
||||||
|
// fs.inotify.max_user_instances=128
|
||||||
|
//
|
||||||
|
// Reaching the limit will result in a "no space left on device" or "too many open
|
||||||
|
// files" error.
|
||||||
|
//
|
||||||
|
// # kqueue notes (macOS, BSD)
|
||||||
|
//
|
||||||
|
// kqueue requires opening a file descriptor for every file that's being watched;
|
||||||
|
// so if you're watching a directory with five files then that's six file
|
||||||
|
// descriptors. You will run in to your system's "max open files" limit faster on
|
||||||
|
// these platforms.
|
||||||
|
//
|
||||||
|
// The sysctl variables kern.maxfiles and kern.maxfilesperproc can be used to
|
||||||
|
// control the maximum number of open files, as well as /etc/login.conf on BSD
|
||||||
|
// systems.
|
||||||
|
//
|
||||||
|
// # macOS notes
|
||||||
|
//
|
||||||
|
// Spotlight indexing on macOS can result in multiple events (see [#15]). A
|
||||||
|
// temporary workaround is to add your folder(s) to the "Spotlight Privacy
|
||||||
|
// Settings" until we have a native FSEvents implementation (see [#11]).
|
||||||
|
//
|
||||||
|
// [#11]: https://github.com/fsnotify/fsnotify/issues/11
|
||||||
|
// [#15]: https://github.com/fsnotify/fsnotify/issues/15
|
||||||
|
type Watcher struct {
|
||||||
|
// Events sends the filesystem change events.
|
||||||
|
//
|
||||||
|
// fsnotify can send the following events; a "path" here can refer to a
|
||||||
|
// file, directory, symbolic link, or special file like a FIFO.
|
||||||
|
//
|
||||||
|
// fsnotify.Create A new path was created; this may be followed by one
|
||||||
|
// or more Write events if data also gets written to a
|
||||||
|
// file.
|
||||||
|
//
|
||||||
|
// fsnotify.Remove A path was removed.
|
||||||
|
//
|
||||||
|
// fsnotify.Rename A path was renamed. A rename is always sent with the
|
||||||
|
// old path as Event.Name, and a Create event will be
|
||||||
|
// sent with the new name. Renames are only sent for
|
||||||
|
// paths that are currently watched; e.g. moving an
|
||||||
|
// unmonitored file into a monitored directory will
|
||||||
|
// show up as just a Create. Similarly, renaming a file
|
||||||
|
// to outside a monitored directory will show up as
|
||||||
|
// only a Rename.
|
||||||
|
//
|
||||||
|
// fsnotify.Write A file or named pipe was written to. A Truncate will
|
||||||
|
// also trigger a Write. A single "write action"
|
||||||
|
// initiated by the user may show up as one or multiple
|
||||||
|
// writes, depending on when the system syncs things to
|
||||||
|
// disk. For example when compiling a large Go program
|
||||||
|
// you may get hundreds of Write events, so you
|
||||||
|
// probably want to wait until you've stopped receiving
|
||||||
|
// them (see the dedup example in cmd/fsnotify).
|
||||||
|
//
|
||||||
|
// fsnotify.Chmod Attributes were changed. On Linux this is also sent
|
||||||
|
// when a file is removed (or more accurately, when a
|
||||||
|
// link to an inode is removed). On kqueue it's sent
|
||||||
|
// and on kqueue when a file is truncated. On Windows
|
||||||
|
// it's never sent.
|
||||||
|
Events chan Event
|
||||||
|
|
||||||
|
// Errors sends any errors.
|
||||||
|
Errors chan error
|
||||||
|
|
||||||
|
port windows.Handle // Handle to completion port
|
||||||
|
input chan *input // Inputs to the reader are sent on this channel
|
||||||
|
quit chan chan<- error
|
||||||
|
|
||||||
|
mu sync.Mutex // Protects access to watches, isClosed
|
||||||
|
watches watchMap // Map of watches (key: i-number)
|
||||||
|
isClosed bool // Set to true when Close() is first called
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewWatcher creates a new Watcher.
|
||||||
|
func NewWatcher() (*Watcher, error) {
|
||||||
|
port, err := windows.CreateIoCompletionPort(windows.InvalidHandle, 0, 0, 0)
|
||||||
|
if err != nil {
|
||||||
|
return nil, os.NewSyscallError("CreateIoCompletionPort", err)
|
||||||
|
}
|
||||||
|
w := &Watcher{
|
||||||
|
port: port,
|
||||||
|
watches: make(watchMap),
|
||||||
|
input: make(chan *input, 1),
|
||||||
|
Events: make(chan Event, 50),
|
||||||
|
Errors: make(chan error),
|
||||||
|
quit: make(chan chan<- error, 1),
|
||||||
|
}
|
||||||
|
go w.readEvents()
|
||||||
|
return w, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *Watcher) sendEvent(name string, mask uint64) bool {
|
||||||
|
if mask == 0 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
event := w.newEvent(name, uint32(mask))
|
||||||
|
select {
|
||||||
|
case ch := <-w.quit:
|
||||||
|
w.quit <- ch
|
||||||
|
case w.Events <- event:
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns true if the error was sent, or false if watcher is closed.
|
||||||
|
func (w *Watcher) sendError(err error) bool {
|
||||||
|
select {
|
||||||
|
case w.Errors <- err:
|
||||||
|
return true
|
||||||
|
case <-w.quit:
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close removes all watches and closes the events channel.
|
||||||
|
func (w *Watcher) Close() error {
|
||||||
|
w.mu.Lock()
|
||||||
|
if w.isClosed {
|
||||||
|
w.mu.Unlock()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
w.isClosed = true
|
||||||
|
w.mu.Unlock()
|
||||||
|
|
||||||
|
// Send "quit" message to the reader goroutine
|
||||||
|
ch := make(chan error)
|
||||||
|
w.quit <- ch
|
||||||
|
if err := w.wakeupReader(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return <-ch
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add starts monitoring the path for changes.
|
||||||
|
//
|
||||||
|
// A path can only be watched once; attempting to watch it more than once will
|
||||||
|
// return an error. Paths that do not yet exist on the filesystem cannot be
|
||||||
|
// added. A watch will be automatically removed if the path is deleted.
|
||||||
|
//
|
||||||
|
// A path will remain watched if it gets renamed to somewhere else on the same
|
||||||
|
// filesystem, but the monitor will get removed if the path gets deleted and
|
||||||
|
// re-created, or if it's moved to a different filesystem.
|
||||||
|
//
|
||||||
|
// Notifications on network filesystems (NFS, SMB, FUSE, etc.) or special
|
||||||
|
// filesystems (/proc, /sys, etc.) generally don't work.
|
||||||
|
//
|
||||||
|
// # Watching directories
|
||||||
|
//
|
||||||
|
// All files in a directory are monitored, including new files that are created
|
||||||
|
// after the watcher is started. Subdirectories are not watched (i.e. it's
|
||||||
|
// non-recursive).
|
||||||
|
//
|
||||||
|
// # Watching files
|
||||||
|
//
|
||||||
|
// Watching individual files (rather than directories) is generally not
|
||||||
|
// recommended as many tools update files atomically. Instead of "just" writing
|
||||||
|
// to the file a temporary file will be written to first, and if successful the
|
||||||
|
// temporary file is moved to to destination removing the original, or some
|
||||||
|
// variant thereof. The watcher on the original file is now lost, as it no
|
||||||
|
// longer exists.
|
||||||
|
//
|
||||||
|
// Instead, watch the parent directory and use Event.Name to filter out files
|
||||||
|
// you're not interested in. There is an example of this in [cmd/fsnotify/file.go].
|
||||||
|
func (w *Watcher) Add(name string) error {
|
||||||
|
w.mu.Lock()
|
||||||
|
if w.isClosed {
|
||||||
|
w.mu.Unlock()
|
||||||
|
return errors.New("watcher already closed")
|
||||||
|
}
|
||||||
|
w.mu.Unlock()
|
||||||
|
|
||||||
|
in := &input{
|
||||||
|
op: opAddWatch,
|
||||||
|
path: filepath.Clean(name),
|
||||||
|
flags: sysFSALLEVENTS,
|
||||||
|
reply: make(chan error),
|
||||||
|
}
|
||||||
|
w.input <- in
|
||||||
|
if err := w.wakeupReader(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return <-in.reply
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove stops monitoring the path for changes.
|
||||||
|
//
|
||||||
|
// Directories are always removed non-recursively. For example, if you added
|
||||||
|
// /tmp/dir and /tmp/dir/subdir then you will need to remove both.
|
||||||
|
//
|
||||||
|
// Removing a path that has not yet been added returns [ErrNonExistentWatch].
|
||||||
|
func (w *Watcher) Remove(name string) error {
|
||||||
|
in := &input{
|
||||||
|
op: opRemoveWatch,
|
||||||
|
path: filepath.Clean(name),
|
||||||
|
reply: make(chan error),
|
||||||
|
}
|
||||||
|
w.input <- in
|
||||||
|
if err := w.wakeupReader(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return <-in.reply
|
||||||
|
}
|
||||||
|
|
||||||
|
// WatchList returns all paths added with [Add] (and are not yet removed).
|
||||||
|
func (w *Watcher) WatchList() []string {
|
||||||
|
w.mu.Lock()
|
||||||
|
defer w.mu.Unlock()
|
||||||
|
|
||||||
|
entries := make([]string, 0, len(w.watches))
|
||||||
|
for _, entry := range w.watches {
|
||||||
|
for _, watchEntry := range entry {
|
||||||
|
entries = append(entries, watchEntry.path)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return entries
|
||||||
|
}
|
||||||
|
|
||||||
|
// These options are from the old golang.org/x/exp/winfsnotify, where you could
|
||||||
|
// add various options to the watch. This has long since been removed.
|
||||||
|
//
|
||||||
|
// The "sys" in the name is misleading as they're not part of any "system".
|
||||||
|
//
|
||||||
|
// This should all be removed at some point, and just use windows.FILE_NOTIFY_*
|
||||||
|
const (
|
||||||
|
sysFSALLEVENTS = 0xfff
|
||||||
|
sysFSATTRIB = 0x4
|
||||||
|
sysFSCREATE = 0x100
|
||||||
|
sysFSDELETE = 0x200
|
||||||
|
sysFSDELETESELF = 0x400
|
||||||
|
sysFSMODIFY = 0x2
|
||||||
|
sysFSMOVE = 0xc0
|
||||||
|
sysFSMOVEDFROM = 0x40
|
||||||
|
sysFSMOVEDTO = 0x80
|
||||||
|
sysFSMOVESELF = 0x800
|
||||||
|
sysFSIGNORED = 0x8000
|
||||||
|
)
|
||||||
|
|
||||||
|
func (w *Watcher) newEvent(name string, mask uint32) Event {
|
||||||
|
e := Event{Name: name}
|
||||||
|
if mask&sysFSCREATE == sysFSCREATE || mask&sysFSMOVEDTO == sysFSMOVEDTO {
|
||||||
|
e.Op |= Create
|
||||||
|
}
|
||||||
|
if mask&sysFSDELETE == sysFSDELETE || mask&sysFSDELETESELF == sysFSDELETESELF {
|
||||||
|
e.Op |= Remove
|
||||||
|
}
|
||||||
|
if mask&sysFSMODIFY == sysFSMODIFY {
|
||||||
|
e.Op |= Write
|
||||||
|
}
|
||||||
|
if mask&sysFSMOVE == sysFSMOVE || mask&sysFSMOVESELF == sysFSMOVESELF || mask&sysFSMOVEDFROM == sysFSMOVEDFROM {
|
||||||
|
e.Op |= Rename
|
||||||
|
}
|
||||||
|
if mask&sysFSATTRIB == sysFSATTRIB {
|
||||||
|
e.Op |= Chmod
|
||||||
|
}
|
||||||
|
return e
|
||||||
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
opAddWatch = iota
|
||||||
|
opRemoveWatch
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
provisional uint64 = 1 << (32 + iota)
|
||||||
|
)
|
||||||
|
|
||||||
|
type input struct {
|
||||||
|
op int
|
||||||
|
path string
|
||||||
|
flags uint32
|
||||||
|
reply chan error
|
||||||
|
}
|
||||||
|
|
||||||
|
type inode struct {
|
||||||
|
handle windows.Handle
|
||||||
|
volume uint32
|
||||||
|
index uint64
|
||||||
|
}
|
||||||
|
|
||||||
|
type watch struct {
|
||||||
|
ov windows.Overlapped
|
||||||
|
ino *inode // i-number
|
||||||
|
path string // Directory path
|
||||||
|
mask uint64 // Directory itself is being watched with these notify flags
|
||||||
|
names map[string]uint64 // Map of names being watched and their notify flags
|
||||||
|
rename string // Remembers the old name while renaming a file
|
||||||
|
buf [65536]byte // 64K buffer
|
||||||
|
}
|
||||||
|
|
||||||
|
type (
|
||||||
|
indexMap map[uint64]*watch
|
||||||
|
watchMap map[uint32]indexMap
|
||||||
|
)
|
||||||
|
|
||||||
|
func (w *Watcher) wakeupReader() error {
|
||||||
|
err := windows.PostQueuedCompletionStatus(w.port, 0, 0, nil)
|
||||||
|
if err != nil {
|
||||||
|
return os.NewSyscallError("PostQueuedCompletionStatus", err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *Watcher) getDir(pathname string) (dir string, err error) {
|
||||||
|
attr, err := windows.GetFileAttributes(windows.StringToUTF16Ptr(pathname))
|
||||||
|
if err != nil {
|
||||||
|
return "", os.NewSyscallError("GetFileAttributes", err)
|
||||||
|
}
|
||||||
|
if attr&windows.FILE_ATTRIBUTE_DIRECTORY != 0 {
|
||||||
|
dir = pathname
|
||||||
|
} else {
|
||||||
|
dir, _ = filepath.Split(pathname)
|
||||||
|
dir = filepath.Clean(dir)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *Watcher) getIno(path string) (ino *inode, err error) {
|
||||||
|
h, err := windows.CreateFile(windows.StringToUTF16Ptr(path),
|
||||||
|
windows.FILE_LIST_DIRECTORY,
|
||||||
|
windows.FILE_SHARE_READ|windows.FILE_SHARE_WRITE|windows.FILE_SHARE_DELETE,
|
||||||
|
nil, windows.OPEN_EXISTING,
|
||||||
|
windows.FILE_FLAG_BACKUP_SEMANTICS|windows.FILE_FLAG_OVERLAPPED, 0)
|
||||||
|
if err != nil {
|
||||||
|
return nil, os.NewSyscallError("CreateFile", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var fi windows.ByHandleFileInformation
|
||||||
|
err = windows.GetFileInformationByHandle(h, &fi)
|
||||||
|
if err != nil {
|
||||||
|
windows.CloseHandle(h)
|
||||||
|
return nil, os.NewSyscallError("GetFileInformationByHandle", err)
|
||||||
|
}
|
||||||
|
ino = &inode{
|
||||||
|
handle: h,
|
||||||
|
volume: fi.VolumeSerialNumber,
|
||||||
|
index: uint64(fi.FileIndexHigh)<<32 | uint64(fi.FileIndexLow),
|
||||||
|
}
|
||||||
|
return ino, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Must run within the I/O thread.
|
||||||
|
func (m watchMap) get(ino *inode) *watch {
|
||||||
|
if i := m[ino.volume]; i != nil {
|
||||||
|
return i[ino.index]
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Must run within the I/O thread.
|
||||||
|
func (m watchMap) set(ino *inode, watch *watch) {
|
||||||
|
i := m[ino.volume]
|
||||||
|
if i == nil {
|
||||||
|
i = make(indexMap)
|
||||||
|
m[ino.volume] = i
|
||||||
|
}
|
||||||
|
i[ino.index] = watch
|
||||||
|
}
|
||||||
|
|
||||||
|
// Must run within the I/O thread.
|
||||||
|
func (w *Watcher) addWatch(pathname string, flags uint64) error {
|
||||||
|
dir, err := w.getDir(pathname)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
ino, err := w.getIno(dir)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
w.mu.Lock()
|
||||||
|
watchEntry := w.watches.get(ino)
|
||||||
|
w.mu.Unlock()
|
||||||
|
if watchEntry == nil {
|
||||||
|
_, err := windows.CreateIoCompletionPort(ino.handle, w.port, 0, 0)
|
||||||
|
if err != nil {
|
||||||
|
windows.CloseHandle(ino.handle)
|
||||||
|
return os.NewSyscallError("CreateIoCompletionPort", err)
|
||||||
|
}
|
||||||
|
watchEntry = &watch{
|
||||||
|
ino: ino,
|
||||||
|
path: dir,
|
||||||
|
names: make(map[string]uint64),
|
||||||
|
}
|
||||||
|
w.mu.Lock()
|
||||||
|
w.watches.set(ino, watchEntry)
|
||||||
|
w.mu.Unlock()
|
||||||
|
flags |= provisional
|
||||||
|
} else {
|
||||||
|
windows.CloseHandle(ino.handle)
|
||||||
|
}
|
||||||
|
if pathname == dir {
|
||||||
|
watchEntry.mask |= flags
|
||||||
|
} else {
|
||||||
|
watchEntry.names[filepath.Base(pathname)] |= flags
|
||||||
|
}
|
||||||
|
|
||||||
|
err = w.startRead(watchEntry)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if pathname == dir {
|
||||||
|
watchEntry.mask &= ^provisional
|
||||||
|
} else {
|
||||||
|
watchEntry.names[filepath.Base(pathname)] &= ^provisional
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Must run within the I/O thread.
|
||||||
|
func (w *Watcher) remWatch(pathname string) error {
|
||||||
|
dir, err := w.getDir(pathname)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
ino, err := w.getIno(dir)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
w.mu.Lock()
|
||||||
|
watch := w.watches.get(ino)
|
||||||
|
w.mu.Unlock()
|
||||||
|
|
||||||
|
err = windows.CloseHandle(ino.handle)
|
||||||
|
if err != nil {
|
||||||
|
w.sendError(os.NewSyscallError("CloseHandle", err))
|
||||||
|
}
|
||||||
|
if watch == nil {
|
||||||
|
return fmt.Errorf("%w: %s", ErrNonExistentWatch, pathname)
|
||||||
|
}
|
||||||
|
if pathname == dir {
|
||||||
|
w.sendEvent(watch.path, watch.mask&sysFSIGNORED)
|
||||||
|
watch.mask = 0
|
||||||
|
} else {
|
||||||
|
name := filepath.Base(pathname)
|
||||||
|
w.sendEvent(filepath.Join(watch.path, name), watch.names[name]&sysFSIGNORED)
|
||||||
|
delete(watch.names, name)
|
||||||
|
}
|
||||||
|
|
||||||
|
return w.startRead(watch)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Must run within the I/O thread.
|
||||||
|
func (w *Watcher) deleteWatch(watch *watch) {
|
||||||
|
for name, mask := range watch.names {
|
||||||
|
if mask&provisional == 0 {
|
||||||
|
w.sendEvent(filepath.Join(watch.path, name), mask&sysFSIGNORED)
|
||||||
|
}
|
||||||
|
delete(watch.names, name)
|
||||||
|
}
|
||||||
|
if watch.mask != 0 {
|
||||||
|
if watch.mask&provisional == 0 {
|
||||||
|
w.sendEvent(watch.path, watch.mask&sysFSIGNORED)
|
||||||
|
}
|
||||||
|
watch.mask = 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Must run within the I/O thread.
|
||||||
|
func (w *Watcher) startRead(watch *watch) error {
|
||||||
|
err := windows.CancelIo(watch.ino.handle)
|
||||||
|
if err != nil {
|
||||||
|
w.sendError(os.NewSyscallError("CancelIo", err))
|
||||||
|
w.deleteWatch(watch)
|
||||||
|
}
|
||||||
|
mask := w.toWindowsFlags(watch.mask)
|
||||||
|
for _, m := range watch.names {
|
||||||
|
mask |= w.toWindowsFlags(m)
|
||||||
|
}
|
||||||
|
if mask == 0 {
|
||||||
|
err := windows.CloseHandle(watch.ino.handle)
|
||||||
|
if err != nil {
|
||||||
|
w.sendError(os.NewSyscallError("CloseHandle", err))
|
||||||
|
}
|
||||||
|
w.mu.Lock()
|
||||||
|
delete(w.watches[watch.ino.volume], watch.ino.index)
|
||||||
|
w.mu.Unlock()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
rdErr := windows.ReadDirectoryChanges(watch.ino.handle, &watch.buf[0],
|
||||||
|
uint32(unsafe.Sizeof(watch.buf)), false, mask, nil, &watch.ov, 0)
|
||||||
|
if rdErr != nil {
|
||||||
|
err := os.NewSyscallError("ReadDirectoryChanges", rdErr)
|
||||||
|
if rdErr == windows.ERROR_ACCESS_DENIED && watch.mask&provisional == 0 {
|
||||||
|
// Watched directory was probably removed
|
||||||
|
w.sendEvent(watch.path, watch.mask&sysFSDELETESELF)
|
||||||
|
err = nil
|
||||||
|
}
|
||||||
|
w.deleteWatch(watch)
|
||||||
|
w.startRead(watch)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// readEvents reads from the I/O completion port, converts the
|
||||||
|
// received events into Event objects and sends them via the Events channel.
|
||||||
|
// Entry point to the I/O thread.
|
||||||
|
func (w *Watcher) readEvents() {
|
||||||
|
var (
|
||||||
|
n uint32
|
||||||
|
key uintptr
|
||||||
|
ov *windows.Overlapped
|
||||||
|
)
|
||||||
|
runtime.LockOSThread()
|
||||||
|
|
||||||
|
for {
|
||||||
|
qErr := windows.GetQueuedCompletionStatus(w.port, &n, &key, &ov, windows.INFINITE)
|
||||||
|
// This error is handled after the watch == nil check below. NOTE: this
|
||||||
|
// seems odd, note sure if it's correct.
|
||||||
|
|
||||||
|
watch := (*watch)(unsafe.Pointer(ov))
|
||||||
|
if watch == nil {
|
||||||
|
select {
|
||||||
|
case ch := <-w.quit:
|
||||||
|
w.mu.Lock()
|
||||||
|
var indexes []indexMap
|
||||||
|
for _, index := range w.watches {
|
||||||
|
indexes = append(indexes, index)
|
||||||
|
}
|
||||||
|
w.mu.Unlock()
|
||||||
|
for _, index := range indexes {
|
||||||
|
for _, watch := range index {
|
||||||
|
w.deleteWatch(watch)
|
||||||
|
w.startRead(watch)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
err := windows.CloseHandle(w.port)
|
||||||
|
if err != nil {
|
||||||
|
err = os.NewSyscallError("CloseHandle", err)
|
||||||
|
}
|
||||||
|
close(w.Events)
|
||||||
|
close(w.Errors)
|
||||||
|
ch <- err
|
||||||
|
return
|
||||||
|
case in := <-w.input:
|
||||||
|
switch in.op {
|
||||||
|
case opAddWatch:
|
||||||
|
in.reply <- w.addWatch(in.path, uint64(in.flags))
|
||||||
|
case opRemoveWatch:
|
||||||
|
in.reply <- w.remWatch(in.path)
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
switch qErr {
|
||||||
|
case windows.ERROR_MORE_DATA:
|
||||||
|
if watch == nil {
|
||||||
|
w.sendError(errors.New("ERROR_MORE_DATA has unexpectedly null lpOverlapped buffer"))
|
||||||
|
} else {
|
||||||
|
// The i/o succeeded but the buffer is full.
|
||||||
|
// In theory we should be building up a full packet.
|
||||||
|
// In practice we can get away with just carrying on.
|
||||||
|
n = uint32(unsafe.Sizeof(watch.buf))
|
||||||
|
}
|
||||||
|
case windows.ERROR_ACCESS_DENIED:
|
||||||
|
// Watched directory was probably removed
|
||||||
|
w.sendEvent(watch.path, watch.mask&sysFSDELETESELF)
|
||||||
|
w.deleteWatch(watch)
|
||||||
|
w.startRead(watch)
|
||||||
|
continue
|
||||||
|
case windows.ERROR_OPERATION_ABORTED:
|
||||||
|
// CancelIo was called on this handle
|
||||||
|
continue
|
||||||
|
default:
|
||||||
|
w.sendError(os.NewSyscallError("GetQueuedCompletionPort", qErr))
|
||||||
|
continue
|
||||||
|
case nil:
|
||||||
|
}
|
||||||
|
|
||||||
|
var offset uint32
|
||||||
|
for {
|
||||||
|
if n == 0 {
|
||||||
|
w.sendError(errors.New("short read in readEvents()"))
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
// Point "raw" to the event in the buffer
|
||||||
|
raw := (*windows.FileNotifyInformation)(unsafe.Pointer(&watch.buf[offset]))
|
||||||
|
|
||||||
|
// Create a buf that is the size of the path name
|
||||||
|
size := int(raw.FileNameLength / 2)
|
||||||
|
var buf []uint16
|
||||||
|
// TODO: Use unsafe.Slice in Go 1.17; https://stackoverflow.com/questions/51187973
|
||||||
|
sh := (*reflect.SliceHeader)(unsafe.Pointer(&buf))
|
||||||
|
sh.Data = uintptr(unsafe.Pointer(&raw.FileName))
|
||||||
|
sh.Len = size
|
||||||
|
sh.Cap = size
|
||||||
|
name := windows.UTF16ToString(buf)
|
||||||
|
fullname := filepath.Join(watch.path, name)
|
||||||
|
|
||||||
|
var mask uint64
|
||||||
|
switch raw.Action {
|
||||||
|
case windows.FILE_ACTION_REMOVED:
|
||||||
|
mask = sysFSDELETESELF
|
||||||
|
case windows.FILE_ACTION_MODIFIED:
|
||||||
|
mask = sysFSMODIFY
|
||||||
|
case windows.FILE_ACTION_RENAMED_OLD_NAME:
|
||||||
|
watch.rename = name
|
||||||
|
case windows.FILE_ACTION_RENAMED_NEW_NAME:
|
||||||
|
// Update saved path of all sub-watches.
|
||||||
|
old := filepath.Join(watch.path, watch.rename)
|
||||||
|
w.mu.Lock()
|
||||||
|
for _, watchMap := range w.watches {
|
||||||
|
for _, ww := range watchMap {
|
||||||
|
if strings.HasPrefix(ww.path, old) {
|
||||||
|
ww.path = filepath.Join(fullname, strings.TrimPrefix(ww.path, old))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
w.mu.Unlock()
|
||||||
|
|
||||||
|
if watch.names[watch.rename] != 0 {
|
||||||
|
watch.names[name] |= watch.names[watch.rename]
|
||||||
|
delete(watch.names, watch.rename)
|
||||||
|
mask = sysFSMOVESELF
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sendNameEvent := func() {
|
||||||
|
w.sendEvent(fullname, watch.names[name]&mask)
|
||||||
|
}
|
||||||
|
if raw.Action != windows.FILE_ACTION_RENAMED_NEW_NAME {
|
||||||
|
sendNameEvent()
|
||||||
|
}
|
||||||
|
if raw.Action == windows.FILE_ACTION_REMOVED {
|
||||||
|
w.sendEvent(fullname, watch.names[name]&sysFSIGNORED)
|
||||||
|
delete(watch.names, name)
|
||||||
|
}
|
||||||
|
|
||||||
|
w.sendEvent(fullname, watch.mask&w.toFSnotifyFlags(raw.Action))
|
||||||
|
if raw.Action == windows.FILE_ACTION_RENAMED_NEW_NAME {
|
||||||
|
fullname = filepath.Join(watch.path, watch.rename)
|
||||||
|
sendNameEvent()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Move to the next event in the buffer
|
||||||
|
if raw.NextEntryOffset == 0 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
offset += raw.NextEntryOffset
|
||||||
|
|
||||||
|
// Error!
|
||||||
|
if offset >= n {
|
||||||
|
w.sendError(errors.New(
|
||||||
|
"Windows system assumed buffer larger than it is, events have likely been missed."))
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := w.startRead(watch); err != nil {
|
||||||
|
w.sendError(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *Watcher) toWindowsFlags(mask uint64) uint32 {
|
||||||
|
var m uint32
|
||||||
|
if mask&sysFSMODIFY != 0 {
|
||||||
|
m |= windows.FILE_NOTIFY_CHANGE_LAST_WRITE
|
||||||
|
}
|
||||||
|
if mask&sysFSATTRIB != 0 {
|
||||||
|
m |= windows.FILE_NOTIFY_CHANGE_ATTRIBUTES
|
||||||
|
}
|
||||||
|
if mask&(sysFSMOVE|sysFSCREATE|sysFSDELETE) != 0 {
|
||||||
|
m |= windows.FILE_NOTIFY_CHANGE_FILE_NAME | windows.FILE_NOTIFY_CHANGE_DIR_NAME
|
||||||
|
}
|
||||||
|
return m
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *Watcher) toFSnotifyFlags(action uint32) uint64 {
|
||||||
|
switch action {
|
||||||
|
case windows.FILE_ACTION_ADDED:
|
||||||
|
return sysFSCREATE
|
||||||
|
case windows.FILE_ACTION_REMOVED:
|
||||||
|
return sysFSDELETE
|
||||||
|
case windows.FILE_ACTION_MODIFIED:
|
||||||
|
return sysFSMODIFY
|
||||||
|
case windows.FILE_ACTION_RENAMED_OLD_NAME:
|
||||||
|
return sysFSMOVEDFROM
|
||||||
|
case windows.FILE_ACTION_RENAMED_NEW_NAME:
|
||||||
|
return sysFSMOVEDTO
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
|
@ -1,37 +0,0 @@
|
||||||
// Copyright 2010 The Go Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a BSD-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
// +build solaris
|
|
||||||
|
|
||||||
package fsnotify
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Watcher watches a set of files, delivering events to a channel.
|
|
||||||
type Watcher struct {
|
|
||||||
Events chan Event
|
|
||||||
Errors chan error
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewWatcher establishes a new watcher with the underlying OS and begins waiting for events.
|
|
||||||
func NewWatcher() (*Watcher, error) {
|
|
||||||
return nil, errors.New("FEN based watcher not yet supported for fsnotify\n")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Close removes all watches and closes the events channel.
|
|
||||||
func (w *Watcher) Close() error {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add starts watching the named file or directory (non-recursively).
|
|
||||||
func (w *Watcher) Add(name string) error {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Remove stops watching the the named file or directory (non-recursively).
|
|
||||||
func (w *Watcher) Remove(name string) error {
|
|
||||||
return nil
|
|
||||||
}
|
|
|
@ -1,28 +1,37 @@
|
||||||
// Copyright 2012 The Go Authors. All rights reserved.
|
//go:build !plan9
|
||||||
// Use of this source code is governed by a BSD-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
// +build !plan9
|
// +build !plan9
|
||||||
|
|
||||||
// Package fsnotify provides a platform-independent interface for file system notifications.
|
// Package fsnotify provides a cross-platform interface for file system
|
||||||
|
// notifications.
|
||||||
package fsnotify
|
package fsnotify
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Event represents a single file system notification.
|
// Event represents a file system notification.
|
||||||
type Event struct {
|
type Event struct {
|
||||||
Name string // Relative path to the file or directory.
|
// Path to the file or directory.
|
||||||
Op Op // File operation that triggered the event.
|
//
|
||||||
|
// Paths are relative to the input; for example with Add("dir") the Name
|
||||||
|
// will be set to "dir/file" if you create that file, but if you use
|
||||||
|
// Add("/path/to/dir") it will be "/path/to/dir/file".
|
||||||
|
Name string
|
||||||
|
|
||||||
|
// File operation that triggered the event.
|
||||||
|
//
|
||||||
|
// This is a bitmask and some systems may send multiple operations at once.
|
||||||
|
// Use the Event.Has() method instead of comparing with ==.
|
||||||
|
Op Op
|
||||||
}
|
}
|
||||||
|
|
||||||
// Op describes a set of file operations.
|
// Op describes a set of file operations.
|
||||||
type Op uint32
|
type Op uint32
|
||||||
|
|
||||||
// These are the generalized file operations that can trigger a notification.
|
// The operations fsnotify can trigger; see the documentation on [Watcher] for a
|
||||||
|
// full description, and check them with [Event.Has].
|
||||||
const (
|
const (
|
||||||
Create Op = 1 << iota
|
Create Op = 1 << iota
|
||||||
Write
|
Write
|
||||||
|
@ -31,38 +40,42 @@ const (
|
||||||
Chmod
|
Chmod
|
||||||
)
|
)
|
||||||
|
|
||||||
func (op Op) String() string {
|
|
||||||
// Use a buffer for efficient string concatenation
|
|
||||||
var buffer bytes.Buffer
|
|
||||||
|
|
||||||
if op&Create == Create {
|
|
||||||
buffer.WriteString("|CREATE")
|
|
||||||
}
|
|
||||||
if op&Remove == Remove {
|
|
||||||
buffer.WriteString("|REMOVE")
|
|
||||||
}
|
|
||||||
if op&Write == Write {
|
|
||||||
buffer.WriteString("|WRITE")
|
|
||||||
}
|
|
||||||
if op&Rename == Rename {
|
|
||||||
buffer.WriteString("|RENAME")
|
|
||||||
}
|
|
||||||
if op&Chmod == Chmod {
|
|
||||||
buffer.WriteString("|CHMOD")
|
|
||||||
}
|
|
||||||
if buffer.Len() == 0 {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
return buffer.String()[1:] // Strip leading pipe
|
|
||||||
}
|
|
||||||
|
|
||||||
// String returns a string representation of the event in the form
|
|
||||||
// "file: REMOVE|WRITE|..."
|
|
||||||
func (e Event) String() string {
|
|
||||||
return fmt.Sprintf("%q: %s", e.Name, e.Op.String())
|
|
||||||
}
|
|
||||||
|
|
||||||
// Common errors that can be reported by a watcher
|
// Common errors that can be reported by a watcher
|
||||||
var (
|
var (
|
||||||
|
ErrNonExistentWatch = errors.New("can't remove non-existent watcher")
|
||||||
ErrEventOverflow = errors.New("fsnotify queue overflow")
|
ErrEventOverflow = errors.New("fsnotify queue overflow")
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func (op Op) String() string {
|
||||||
|
var b strings.Builder
|
||||||
|
if op.Has(Create) {
|
||||||
|
b.WriteString("|CREATE")
|
||||||
|
}
|
||||||
|
if op.Has(Remove) {
|
||||||
|
b.WriteString("|REMOVE")
|
||||||
|
}
|
||||||
|
if op.Has(Write) {
|
||||||
|
b.WriteString("|WRITE")
|
||||||
|
}
|
||||||
|
if op.Has(Rename) {
|
||||||
|
b.WriteString("|RENAME")
|
||||||
|
}
|
||||||
|
if op.Has(Chmod) {
|
||||||
|
b.WriteString("|CHMOD")
|
||||||
|
}
|
||||||
|
if b.Len() == 0 {
|
||||||
|
return "[no events]"
|
||||||
|
}
|
||||||
|
return b.String()[1:]
|
||||||
|
}
|
||||||
|
|
||||||
|
// Has reports if this operation has the given operation.
|
||||||
|
func (o Op) Has(h Op) bool { return o&h == h }
|
||||||
|
|
||||||
|
// Has reports if this event has the given operation.
|
||||||
|
func (e Event) Has(op Op) bool { return e.Op.Has(op) }
|
||||||
|
|
||||||
|
// String returns a string representation of the event with their path.
|
||||||
|
func (e Event) String() string {
|
||||||
|
return fmt.Sprintf("%-13s %q", e.Op.String(), e.Name)
|
||||||
|
}
|
||||||
|
|
|
@ -1,337 +0,0 @@
|
||||||
// Copyright 2010 The Go Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a BSD-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
// +build linux
|
|
||||||
|
|
||||||
package fsnotify
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
"strings"
|
|
||||||
"sync"
|
|
||||||
"unsafe"
|
|
||||||
|
|
||||||
"golang.org/x/sys/unix"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Watcher watches a set of files, delivering events to a channel.
|
|
||||||
type Watcher struct {
|
|
||||||
Events chan Event
|
|
||||||
Errors chan error
|
|
||||||
mu sync.Mutex // Map access
|
|
||||||
fd int
|
|
||||||
poller *fdPoller
|
|
||||||
watches map[string]*watch // Map of inotify watches (key: path)
|
|
||||||
paths map[int]string // Map of watched paths (key: watch descriptor)
|
|
||||||
done chan struct{} // Channel for sending a "quit message" to the reader goroutine
|
|
||||||
doneResp chan struct{} // Channel to respond to Close
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewWatcher establishes a new watcher with the underlying OS and begins waiting for events.
|
|
||||||
func NewWatcher() (*Watcher, error) {
|
|
||||||
// Create inotify fd
|
|
||||||
fd, errno := unix.InotifyInit1(unix.IN_CLOEXEC)
|
|
||||||
if fd == -1 {
|
|
||||||
return nil, errno
|
|
||||||
}
|
|
||||||
// Create epoll
|
|
||||||
poller, err := newFdPoller(fd)
|
|
||||||
if err != nil {
|
|
||||||
unix.Close(fd)
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
w := &Watcher{
|
|
||||||
fd: fd,
|
|
||||||
poller: poller,
|
|
||||||
watches: make(map[string]*watch),
|
|
||||||
paths: make(map[int]string),
|
|
||||||
Events: make(chan Event),
|
|
||||||
Errors: make(chan error),
|
|
||||||
done: make(chan struct{}),
|
|
||||||
doneResp: make(chan struct{}),
|
|
||||||
}
|
|
||||||
|
|
||||||
go w.readEvents()
|
|
||||||
return w, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *Watcher) isClosed() bool {
|
|
||||||
select {
|
|
||||||
case <-w.done:
|
|
||||||
return true
|
|
||||||
default:
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Close removes all watches and closes the events channel.
|
|
||||||
func (w *Watcher) Close() error {
|
|
||||||
if w.isClosed() {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Send 'close' signal to goroutine, and set the Watcher to closed.
|
|
||||||
close(w.done)
|
|
||||||
|
|
||||||
// Wake up goroutine
|
|
||||||
w.poller.wake()
|
|
||||||
|
|
||||||
// Wait for goroutine to close
|
|
||||||
<-w.doneResp
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add starts watching the named file or directory (non-recursively).
|
|
||||||
func (w *Watcher) Add(name string) error {
|
|
||||||
name = filepath.Clean(name)
|
|
||||||
if w.isClosed() {
|
|
||||||
return errors.New("inotify instance already closed")
|
|
||||||
}
|
|
||||||
|
|
||||||
const agnosticEvents = unix.IN_MOVED_TO | unix.IN_MOVED_FROM |
|
|
||||||
unix.IN_CREATE | unix.IN_ATTRIB | unix.IN_MODIFY |
|
|
||||||
unix.IN_MOVE_SELF | unix.IN_DELETE | unix.IN_DELETE_SELF
|
|
||||||
|
|
||||||
var flags uint32 = agnosticEvents
|
|
||||||
|
|
||||||
w.mu.Lock()
|
|
||||||
defer w.mu.Unlock()
|
|
||||||
watchEntry := w.watches[name]
|
|
||||||
if watchEntry != nil {
|
|
||||||
flags |= watchEntry.flags | unix.IN_MASK_ADD
|
|
||||||
}
|
|
||||||
wd, errno := unix.InotifyAddWatch(w.fd, name, flags)
|
|
||||||
if wd == -1 {
|
|
||||||
return errno
|
|
||||||
}
|
|
||||||
|
|
||||||
if watchEntry == nil {
|
|
||||||
w.watches[name] = &watch{wd: uint32(wd), flags: flags}
|
|
||||||
w.paths[wd] = name
|
|
||||||
} else {
|
|
||||||
watchEntry.wd = uint32(wd)
|
|
||||||
watchEntry.flags = flags
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Remove stops watching the named file or directory (non-recursively).
|
|
||||||
func (w *Watcher) Remove(name string) error {
|
|
||||||
name = filepath.Clean(name)
|
|
||||||
|
|
||||||
// Fetch the watch.
|
|
||||||
w.mu.Lock()
|
|
||||||
defer w.mu.Unlock()
|
|
||||||
watch, ok := w.watches[name]
|
|
||||||
|
|
||||||
// Remove it from inotify.
|
|
||||||
if !ok {
|
|
||||||
return fmt.Errorf("can't remove non-existent inotify watch for: %s", name)
|
|
||||||
}
|
|
||||||
|
|
||||||
// We successfully removed the watch if InotifyRmWatch doesn't return an
|
|
||||||
// error, we need to clean up our internal state to ensure it matches
|
|
||||||
// inotify's kernel state.
|
|
||||||
delete(w.paths, int(watch.wd))
|
|
||||||
delete(w.watches, name)
|
|
||||||
|
|
||||||
// inotify_rm_watch will return EINVAL if the file has been deleted;
|
|
||||||
// the inotify will already have been removed.
|
|
||||||
// watches and pathes are deleted in ignoreLinux() implicitly and asynchronously
|
|
||||||
// by calling inotify_rm_watch() below. e.g. readEvents() goroutine receives IN_IGNORE
|
|
||||||
// so that EINVAL means that the wd is being rm_watch()ed or its file removed
|
|
||||||
// by another thread and we have not received IN_IGNORE event.
|
|
||||||
success, errno := unix.InotifyRmWatch(w.fd, watch.wd)
|
|
||||||
if success == -1 {
|
|
||||||
// TODO: Perhaps it's not helpful to return an error here in every case.
|
|
||||||
// the only two possible errors are:
|
|
||||||
// EBADF, which happens when w.fd is not a valid file descriptor of any kind.
|
|
||||||
// EINVAL, which is when fd is not an inotify descriptor or wd is not a valid watch descriptor.
|
|
||||||
// Watch descriptors are invalidated when they are removed explicitly or implicitly;
|
|
||||||
// explicitly by inotify_rm_watch, implicitly when the file they are watching is deleted.
|
|
||||||
return errno
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type watch struct {
|
|
||||||
wd uint32 // Watch descriptor (as returned by the inotify_add_watch() syscall)
|
|
||||||
flags uint32 // inotify flags of this watch (see inotify(7) for the list of valid flags)
|
|
||||||
}
|
|
||||||
|
|
||||||
// readEvents reads from the inotify file descriptor, converts the
|
|
||||||
// received events into Event objects and sends them via the Events channel
|
|
||||||
func (w *Watcher) readEvents() {
|
|
||||||
var (
|
|
||||||
buf [unix.SizeofInotifyEvent * 4096]byte // Buffer for a maximum of 4096 raw events
|
|
||||||
n int // Number of bytes read with read()
|
|
||||||
errno error // Syscall errno
|
|
||||||
ok bool // For poller.wait
|
|
||||||
)
|
|
||||||
|
|
||||||
defer close(w.doneResp)
|
|
||||||
defer close(w.Errors)
|
|
||||||
defer close(w.Events)
|
|
||||||
defer unix.Close(w.fd)
|
|
||||||
defer w.poller.close()
|
|
||||||
|
|
||||||
for {
|
|
||||||
// See if we have been closed.
|
|
||||||
if w.isClosed() {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
ok, errno = w.poller.wait()
|
|
||||||
if errno != nil {
|
|
||||||
select {
|
|
||||||
case w.Errors <- errno:
|
|
||||||
case <-w.done:
|
|
||||||
return
|
|
||||||
}
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if !ok {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
n, errno = unix.Read(w.fd, buf[:])
|
|
||||||
// If a signal interrupted execution, see if we've been asked to close, and try again.
|
|
||||||
// http://man7.org/linux/man-pages/man7/signal.7.html :
|
|
||||||
// "Before Linux 3.8, reads from an inotify(7) file descriptor were not restartable"
|
|
||||||
if errno == unix.EINTR {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
// unix.Read might have been woken up by Close. If so, we're done.
|
|
||||||
if w.isClosed() {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if n < unix.SizeofInotifyEvent {
|
|
||||||
var err error
|
|
||||||
if n == 0 {
|
|
||||||
// If EOF is received. This should really never happen.
|
|
||||||
err = io.EOF
|
|
||||||
} else if n < 0 {
|
|
||||||
// If an error occurred while reading.
|
|
||||||
err = errno
|
|
||||||
} else {
|
|
||||||
// Read was too short.
|
|
||||||
err = errors.New("notify: short read in readEvents()")
|
|
||||||
}
|
|
||||||
select {
|
|
||||||
case w.Errors <- err:
|
|
||||||
case <-w.done:
|
|
||||||
return
|
|
||||||
}
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
var offset uint32
|
|
||||||
// We don't know how many events we just read into the buffer
|
|
||||||
// While the offset points to at least one whole event...
|
|
||||||
for offset <= uint32(n-unix.SizeofInotifyEvent) {
|
|
||||||
// Point "raw" to the event in the buffer
|
|
||||||
raw := (*unix.InotifyEvent)(unsafe.Pointer(&buf[offset]))
|
|
||||||
|
|
||||||
mask := uint32(raw.Mask)
|
|
||||||
nameLen := uint32(raw.Len)
|
|
||||||
|
|
||||||
if mask&unix.IN_Q_OVERFLOW != 0 {
|
|
||||||
select {
|
|
||||||
case w.Errors <- ErrEventOverflow:
|
|
||||||
case <-w.done:
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// If the event happened to the watched directory or the watched file, the kernel
|
|
||||||
// doesn't append the filename to the event, but we would like to always fill the
|
|
||||||
// the "Name" field with a valid filename. We retrieve the path of the watch from
|
|
||||||
// the "paths" map.
|
|
||||||
w.mu.Lock()
|
|
||||||
name, ok := w.paths[int(raw.Wd)]
|
|
||||||
// IN_DELETE_SELF occurs when the file/directory being watched is removed.
|
|
||||||
// This is a sign to clean up the maps, otherwise we are no longer in sync
|
|
||||||
// with the inotify kernel state which has already deleted the watch
|
|
||||||
// automatically.
|
|
||||||
if ok && mask&unix.IN_DELETE_SELF == unix.IN_DELETE_SELF {
|
|
||||||
delete(w.paths, int(raw.Wd))
|
|
||||||
delete(w.watches, name)
|
|
||||||
}
|
|
||||||
w.mu.Unlock()
|
|
||||||
|
|
||||||
if nameLen > 0 {
|
|
||||||
// Point "bytes" at the first byte of the filename
|
|
||||||
bytes := (*[unix.PathMax]byte)(unsafe.Pointer(&buf[offset+unix.SizeofInotifyEvent]))
|
|
||||||
// The filename is padded with NULL bytes. TrimRight() gets rid of those.
|
|
||||||
name += "/" + strings.TrimRight(string(bytes[0:nameLen]), "\000")
|
|
||||||
}
|
|
||||||
|
|
||||||
event := newEvent(name, mask)
|
|
||||||
|
|
||||||
// Send the events that are not ignored on the events channel
|
|
||||||
if !event.ignoreLinux(mask) {
|
|
||||||
select {
|
|
||||||
case w.Events <- event:
|
|
||||||
case <-w.done:
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Move to the next event in the buffer
|
|
||||||
offset += unix.SizeofInotifyEvent + nameLen
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Certain types of events can be "ignored" and not sent over the Events
|
|
||||||
// channel. Such as events marked ignore by the kernel, or MODIFY events
|
|
||||||
// against files that do not exist.
|
|
||||||
func (e *Event) ignoreLinux(mask uint32) bool {
|
|
||||||
// Ignore anything the inotify API says to ignore
|
|
||||||
if mask&unix.IN_IGNORED == unix.IN_IGNORED {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
// If the event is not a DELETE or RENAME, the file must exist.
|
|
||||||
// Otherwise the event is ignored.
|
|
||||||
// *Note*: this was put in place because it was seen that a MODIFY
|
|
||||||
// event was sent after the DELETE. This ignores that MODIFY and
|
|
||||||
// assumes a DELETE will come or has come if the file doesn't exist.
|
|
||||||
if !(e.Op&Remove == Remove || e.Op&Rename == Rename) {
|
|
||||||
_, statErr := os.Lstat(e.Name)
|
|
||||||
return os.IsNotExist(statErr)
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// newEvent returns an platform-independent Event based on an inotify mask.
|
|
||||||
func newEvent(name string, mask uint32) Event {
|
|
||||||
e := Event{Name: name}
|
|
||||||
if mask&unix.IN_CREATE == unix.IN_CREATE || mask&unix.IN_MOVED_TO == unix.IN_MOVED_TO {
|
|
||||||
e.Op |= Create
|
|
||||||
}
|
|
||||||
if mask&unix.IN_DELETE_SELF == unix.IN_DELETE_SELF || mask&unix.IN_DELETE == unix.IN_DELETE {
|
|
||||||
e.Op |= Remove
|
|
||||||
}
|
|
||||||
if mask&unix.IN_MODIFY == unix.IN_MODIFY {
|
|
||||||
e.Op |= Write
|
|
||||||
}
|
|
||||||
if mask&unix.IN_MOVE_SELF == unix.IN_MOVE_SELF || mask&unix.IN_MOVED_FROM == unix.IN_MOVED_FROM {
|
|
||||||
e.Op |= Rename
|
|
||||||
}
|
|
||||||
if mask&unix.IN_ATTRIB == unix.IN_ATTRIB {
|
|
||||||
e.Op |= Chmod
|
|
||||||
}
|
|
||||||
return e
|
|
||||||
}
|
|
|
@ -1,187 +0,0 @@
|
||||||
// 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 linux
|
|
||||||
|
|
||||||
package fsnotify
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
|
|
||||||
"golang.org/x/sys/unix"
|
|
||||||
)
|
|
||||||
|
|
||||||
type fdPoller struct {
|
|
||||||
fd int // File descriptor (as returned by the inotify_init() syscall)
|
|
||||||
epfd int // Epoll file descriptor
|
|
||||||
pipe [2]int // Pipe for waking up
|
|
||||||
}
|
|
||||||
|
|
||||||
func emptyPoller(fd int) *fdPoller {
|
|
||||||
poller := new(fdPoller)
|
|
||||||
poller.fd = fd
|
|
||||||
poller.epfd = -1
|
|
||||||
poller.pipe[0] = -1
|
|
||||||
poller.pipe[1] = -1
|
|
||||||
return poller
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create a new inotify poller.
|
|
||||||
// This creates an inotify handler, and an epoll handler.
|
|
||||||
func newFdPoller(fd int) (*fdPoller, error) {
|
|
||||||
var errno error
|
|
||||||
poller := emptyPoller(fd)
|
|
||||||
defer func() {
|
|
||||||
if errno != nil {
|
|
||||||
poller.close()
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
poller.fd = fd
|
|
||||||
|
|
||||||
// Create epoll fd
|
|
||||||
poller.epfd, errno = unix.EpollCreate1(unix.EPOLL_CLOEXEC)
|
|
||||||
if poller.epfd == -1 {
|
|
||||||
return nil, errno
|
|
||||||
}
|
|
||||||
// Create pipe; pipe[0] is the read end, pipe[1] the write end.
|
|
||||||
errno = unix.Pipe2(poller.pipe[:], unix.O_NONBLOCK|unix.O_CLOEXEC)
|
|
||||||
if errno != nil {
|
|
||||||
return nil, errno
|
|
||||||
}
|
|
||||||
|
|
||||||
// Register inotify fd with epoll
|
|
||||||
event := unix.EpollEvent{
|
|
||||||
Fd: int32(poller.fd),
|
|
||||||
Events: unix.EPOLLIN,
|
|
||||||
}
|
|
||||||
errno = unix.EpollCtl(poller.epfd, unix.EPOLL_CTL_ADD, poller.fd, &event)
|
|
||||||
if errno != nil {
|
|
||||||
return nil, errno
|
|
||||||
}
|
|
||||||
|
|
||||||
// Register pipe fd with epoll
|
|
||||||
event = unix.EpollEvent{
|
|
||||||
Fd: int32(poller.pipe[0]),
|
|
||||||
Events: unix.EPOLLIN,
|
|
||||||
}
|
|
||||||
errno = unix.EpollCtl(poller.epfd, unix.EPOLL_CTL_ADD, poller.pipe[0], &event)
|
|
||||||
if errno != nil {
|
|
||||||
return nil, errno
|
|
||||||
}
|
|
||||||
|
|
||||||
return poller, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Wait using epoll.
|
|
||||||
// Returns true if something is ready to be read,
|
|
||||||
// false if there is not.
|
|
||||||
func (poller *fdPoller) wait() (bool, error) {
|
|
||||||
// 3 possible events per fd, and 2 fds, makes a maximum of 6 events.
|
|
||||||
// I don't know whether epoll_wait returns the number of events returned,
|
|
||||||
// or the total number of events ready.
|
|
||||||
// I decided to catch both by making the buffer one larger than the maximum.
|
|
||||||
events := make([]unix.EpollEvent, 7)
|
|
||||||
for {
|
|
||||||
n, errno := unix.EpollWait(poller.epfd, events, -1)
|
|
||||||
if n == -1 {
|
|
||||||
if errno == unix.EINTR {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
return false, errno
|
|
||||||
}
|
|
||||||
if n == 0 {
|
|
||||||
// If there are no events, try again.
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if n > 6 {
|
|
||||||
// This should never happen. More events were returned than should be possible.
|
|
||||||
return false, errors.New("epoll_wait returned more events than I know what to do with")
|
|
||||||
}
|
|
||||||
ready := events[:n]
|
|
||||||
epollhup := false
|
|
||||||
epollerr := false
|
|
||||||
epollin := false
|
|
||||||
for _, event := range ready {
|
|
||||||
if event.Fd == int32(poller.fd) {
|
|
||||||
if event.Events&unix.EPOLLHUP != 0 {
|
|
||||||
// This should not happen, but if it does, treat it as a wakeup.
|
|
||||||
epollhup = true
|
|
||||||
}
|
|
||||||
if event.Events&unix.EPOLLERR != 0 {
|
|
||||||
// If an error is waiting on the file descriptor, we should pretend
|
|
||||||
// something is ready to read, and let unix.Read pick up the error.
|
|
||||||
epollerr = true
|
|
||||||
}
|
|
||||||
if event.Events&unix.EPOLLIN != 0 {
|
|
||||||
// There is data to read.
|
|
||||||
epollin = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if event.Fd == int32(poller.pipe[0]) {
|
|
||||||
if event.Events&unix.EPOLLHUP != 0 {
|
|
||||||
// Write pipe descriptor was closed, by us. This means we're closing down the
|
|
||||||
// watcher, and we should wake up.
|
|
||||||
}
|
|
||||||
if event.Events&unix.EPOLLERR != 0 {
|
|
||||||
// If an error is waiting on the pipe file descriptor.
|
|
||||||
// This is an absolute mystery, and should never ever happen.
|
|
||||||
return false, errors.New("Error on the pipe descriptor.")
|
|
||||||
}
|
|
||||||
if event.Events&unix.EPOLLIN != 0 {
|
|
||||||
// This is a regular wakeup, so we have to clear the buffer.
|
|
||||||
err := poller.clearWake()
|
|
||||||
if err != nil {
|
|
||||||
return false, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if epollhup || epollerr || epollin {
|
|
||||||
return true, nil
|
|
||||||
}
|
|
||||||
return false, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Close the write end of the poller.
|
|
||||||
func (poller *fdPoller) wake() error {
|
|
||||||
buf := make([]byte, 1)
|
|
||||||
n, errno := unix.Write(poller.pipe[1], buf)
|
|
||||||
if n == -1 {
|
|
||||||
if errno == unix.EAGAIN {
|
|
||||||
// Buffer is full, poller will wake.
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return errno
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (poller *fdPoller) clearWake() error {
|
|
||||||
// You have to be woken up a LOT in order to get to 100!
|
|
||||||
buf := make([]byte, 100)
|
|
||||||
n, errno := unix.Read(poller.pipe[0], buf)
|
|
||||||
if n == -1 {
|
|
||||||
if errno == unix.EAGAIN {
|
|
||||||
// Buffer is empty, someone else cleared our wake.
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return errno
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Close all poller file descriptors, but not the one passed to it.
|
|
||||||
func (poller *fdPoller) close() {
|
|
||||||
if poller.pipe[1] != -1 {
|
|
||||||
unix.Close(poller.pipe[1])
|
|
||||||
}
|
|
||||||
if poller.pipe[0] != -1 {
|
|
||||||
unix.Close(poller.pipe[0])
|
|
||||||
}
|
|
||||||
if poller.epfd != -1 {
|
|
||||||
unix.Close(poller.epfd)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,521 +0,0 @@
|
||||||
// Copyright 2010 The Go Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a BSD-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
// +build freebsd openbsd netbsd dragonfly darwin
|
|
||||||
|
|
||||||
package fsnotify
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"io/ioutil"
|
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
"sync"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"golang.org/x/sys/unix"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Watcher watches a set of files, delivering events to a channel.
|
|
||||||
type Watcher struct {
|
|
||||||
Events chan Event
|
|
||||||
Errors chan error
|
|
||||||
done chan struct{} // Channel for sending a "quit message" to the reader goroutine
|
|
||||||
|
|
||||||
kq int // File descriptor (as returned by the kqueue() syscall).
|
|
||||||
|
|
||||||
mu sync.Mutex // Protects access to watcher data
|
|
||||||
watches map[string]int // Map of watched file descriptors (key: path).
|
|
||||||
externalWatches map[string]bool // Map of watches added by user of the library.
|
|
||||||
dirFlags map[string]uint32 // Map of watched directories to fflags used in kqueue.
|
|
||||||
paths map[int]pathInfo // Map file descriptors to path names for processing kqueue events.
|
|
||||||
fileExists map[string]bool // Keep track of if we know this file exists (to stop duplicate create events).
|
|
||||||
isClosed bool // Set to true when Close() is first called
|
|
||||||
}
|
|
||||||
|
|
||||||
type pathInfo struct {
|
|
||||||
name string
|
|
||||||
isDir bool
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewWatcher establishes a new watcher with the underlying OS and begins waiting for events.
|
|
||||||
func NewWatcher() (*Watcher, error) {
|
|
||||||
kq, err := kqueue()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
w := &Watcher{
|
|
||||||
kq: kq,
|
|
||||||
watches: make(map[string]int),
|
|
||||||
dirFlags: make(map[string]uint32),
|
|
||||||
paths: make(map[int]pathInfo),
|
|
||||||
fileExists: make(map[string]bool),
|
|
||||||
externalWatches: make(map[string]bool),
|
|
||||||
Events: make(chan Event),
|
|
||||||
Errors: make(chan error),
|
|
||||||
done: make(chan struct{}),
|
|
||||||
}
|
|
||||||
|
|
||||||
go w.readEvents()
|
|
||||||
return w, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Close removes all watches and closes the events channel.
|
|
||||||
func (w *Watcher) Close() error {
|
|
||||||
w.mu.Lock()
|
|
||||||
if w.isClosed {
|
|
||||||
w.mu.Unlock()
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
w.isClosed = true
|
|
||||||
|
|
||||||
// copy paths to remove while locked
|
|
||||||
var pathsToRemove = make([]string, 0, len(w.watches))
|
|
||||||
for name := range w.watches {
|
|
||||||
pathsToRemove = append(pathsToRemove, name)
|
|
||||||
}
|
|
||||||
w.mu.Unlock()
|
|
||||||
// unlock before calling Remove, which also locks
|
|
||||||
|
|
||||||
for _, name := range pathsToRemove {
|
|
||||||
w.Remove(name)
|
|
||||||
}
|
|
||||||
|
|
||||||
// send a "quit" message to the reader goroutine
|
|
||||||
close(w.done)
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add starts watching the named file or directory (non-recursively).
|
|
||||||
func (w *Watcher) Add(name string) error {
|
|
||||||
w.mu.Lock()
|
|
||||||
w.externalWatches[name] = true
|
|
||||||
w.mu.Unlock()
|
|
||||||
_, err := w.addWatch(name, noteAllEvents)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Remove stops watching the the named file or directory (non-recursively).
|
|
||||||
func (w *Watcher) Remove(name string) error {
|
|
||||||
name = filepath.Clean(name)
|
|
||||||
w.mu.Lock()
|
|
||||||
watchfd, ok := w.watches[name]
|
|
||||||
w.mu.Unlock()
|
|
||||||
if !ok {
|
|
||||||
return fmt.Errorf("can't remove non-existent kevent watch for: %s", name)
|
|
||||||
}
|
|
||||||
|
|
||||||
const registerRemove = unix.EV_DELETE
|
|
||||||
if err := register(w.kq, []int{watchfd}, registerRemove, 0); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
unix.Close(watchfd)
|
|
||||||
|
|
||||||
w.mu.Lock()
|
|
||||||
isDir := w.paths[watchfd].isDir
|
|
||||||
delete(w.watches, name)
|
|
||||||
delete(w.paths, watchfd)
|
|
||||||
delete(w.dirFlags, name)
|
|
||||||
w.mu.Unlock()
|
|
||||||
|
|
||||||
// Find all watched paths that are in this directory that are not external.
|
|
||||||
if isDir {
|
|
||||||
var pathsToRemove []string
|
|
||||||
w.mu.Lock()
|
|
||||||
for _, path := range w.paths {
|
|
||||||
wdir, _ := filepath.Split(path.name)
|
|
||||||
if filepath.Clean(wdir) == name {
|
|
||||||
if !w.externalWatches[path.name] {
|
|
||||||
pathsToRemove = append(pathsToRemove, path.name)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
w.mu.Unlock()
|
|
||||||
for _, name := range pathsToRemove {
|
|
||||||
// Since these are internal, not much sense in propagating error
|
|
||||||
// to the user, as that will just confuse them with an error about
|
|
||||||
// a path they did not explicitly watch themselves.
|
|
||||||
w.Remove(name)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Watch all events (except NOTE_EXTEND, NOTE_LINK, NOTE_REVOKE)
|
|
||||||
const noteAllEvents = unix.NOTE_DELETE | unix.NOTE_WRITE | unix.NOTE_ATTRIB | unix.NOTE_RENAME
|
|
||||||
|
|
||||||
// keventWaitTime to block on each read from kevent
|
|
||||||
var keventWaitTime = durationToTimespec(100 * time.Millisecond)
|
|
||||||
|
|
||||||
// addWatch adds name to the watched file set.
|
|
||||||
// The flags are interpreted as described in kevent(2).
|
|
||||||
// Returns the real path to the file which was added, if any, which may be different from the one passed in the case of symlinks.
|
|
||||||
func (w *Watcher) addWatch(name string, flags uint32) (string, error) {
|
|
||||||
var isDir bool
|
|
||||||
// Make ./name and name equivalent
|
|
||||||
name = filepath.Clean(name)
|
|
||||||
|
|
||||||
w.mu.Lock()
|
|
||||||
if w.isClosed {
|
|
||||||
w.mu.Unlock()
|
|
||||||
return "", errors.New("kevent instance already closed")
|
|
||||||
}
|
|
||||||
watchfd, alreadyWatching := w.watches[name]
|
|
||||||
// We already have a watch, but we can still override flags.
|
|
||||||
if alreadyWatching {
|
|
||||||
isDir = w.paths[watchfd].isDir
|
|
||||||
}
|
|
||||||
w.mu.Unlock()
|
|
||||||
|
|
||||||
if !alreadyWatching {
|
|
||||||
fi, err := os.Lstat(name)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Don't watch sockets.
|
|
||||||
if fi.Mode()&os.ModeSocket == os.ModeSocket {
|
|
||||||
return "", nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Don't watch named pipes.
|
|
||||||
if fi.Mode()&os.ModeNamedPipe == os.ModeNamedPipe {
|
|
||||||
return "", nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Follow Symlinks
|
|
||||||
// Unfortunately, Linux can add bogus symlinks to watch list without
|
|
||||||
// issue, and Windows can't do symlinks period (AFAIK). To maintain
|
|
||||||
// consistency, we will act like everything is fine. There will simply
|
|
||||||
// be no file events for broken symlinks.
|
|
||||||
// Hence the returns of nil on errors.
|
|
||||||
if fi.Mode()&os.ModeSymlink == os.ModeSymlink {
|
|
||||||
name, err = filepath.EvalSymlinks(name)
|
|
||||||
if err != nil {
|
|
||||||
return "", nil
|
|
||||||
}
|
|
||||||
|
|
||||||
w.mu.Lock()
|
|
||||||
_, alreadyWatching = w.watches[name]
|
|
||||||
w.mu.Unlock()
|
|
||||||
|
|
||||||
if alreadyWatching {
|
|
||||||
return name, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
fi, err = os.Lstat(name)
|
|
||||||
if err != nil {
|
|
||||||
return "", nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
watchfd, err = unix.Open(name, openMode, 0700)
|
|
||||||
if watchfd == -1 {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
isDir = fi.IsDir()
|
|
||||||
}
|
|
||||||
|
|
||||||
const registerAdd = unix.EV_ADD | unix.EV_CLEAR | unix.EV_ENABLE
|
|
||||||
if err := register(w.kq, []int{watchfd}, registerAdd, flags); err != nil {
|
|
||||||
unix.Close(watchfd)
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
if !alreadyWatching {
|
|
||||||
w.mu.Lock()
|
|
||||||
w.watches[name] = watchfd
|
|
||||||
w.paths[watchfd] = pathInfo{name: name, isDir: isDir}
|
|
||||||
w.mu.Unlock()
|
|
||||||
}
|
|
||||||
|
|
||||||
if isDir {
|
|
||||||
// Watch the directory if it has not been watched before,
|
|
||||||
// or if it was watched before, but perhaps only a NOTE_DELETE (watchDirectoryFiles)
|
|
||||||
w.mu.Lock()
|
|
||||||
|
|
||||||
watchDir := (flags&unix.NOTE_WRITE) == unix.NOTE_WRITE &&
|
|
||||||
(!alreadyWatching || (w.dirFlags[name]&unix.NOTE_WRITE) != unix.NOTE_WRITE)
|
|
||||||
// Store flags so this watch can be updated later
|
|
||||||
w.dirFlags[name] = flags
|
|
||||||
w.mu.Unlock()
|
|
||||||
|
|
||||||
if watchDir {
|
|
||||||
if err := w.watchDirectoryFiles(name); err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return name, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// readEvents reads from kqueue and converts the received kevents into
|
|
||||||
// Event values that it sends down the Events channel.
|
|
||||||
func (w *Watcher) readEvents() {
|
|
||||||
eventBuffer := make([]unix.Kevent_t, 10)
|
|
||||||
|
|
||||||
loop:
|
|
||||||
for {
|
|
||||||
// See if there is a message on the "done" channel
|
|
||||||
select {
|
|
||||||
case <-w.done:
|
|
||||||
break loop
|
|
||||||
default:
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get new events
|
|
||||||
kevents, err := read(w.kq, eventBuffer, &keventWaitTime)
|
|
||||||
// EINTR is okay, the syscall was interrupted before timeout expired.
|
|
||||||
if err != nil && err != unix.EINTR {
|
|
||||||
select {
|
|
||||||
case w.Errors <- err:
|
|
||||||
case <-w.done:
|
|
||||||
break loop
|
|
||||||
}
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
// Flush the events we received to the Events channel
|
|
||||||
for len(kevents) > 0 {
|
|
||||||
kevent := &kevents[0]
|
|
||||||
watchfd := int(kevent.Ident)
|
|
||||||
mask := uint32(kevent.Fflags)
|
|
||||||
w.mu.Lock()
|
|
||||||
path := w.paths[watchfd]
|
|
||||||
w.mu.Unlock()
|
|
||||||
event := newEvent(path.name, mask)
|
|
||||||
|
|
||||||
if path.isDir && !(event.Op&Remove == Remove) {
|
|
||||||
// Double check to make sure the directory exists. This can happen when
|
|
||||||
// we do a rm -fr on a recursively watched folders and we receive a
|
|
||||||
// modification event first but the folder has been deleted and later
|
|
||||||
// receive the delete event
|
|
||||||
if _, err := os.Lstat(event.Name); os.IsNotExist(err) {
|
|
||||||
// mark is as delete event
|
|
||||||
event.Op |= Remove
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if event.Op&Rename == Rename || event.Op&Remove == Remove {
|
|
||||||
w.Remove(event.Name)
|
|
||||||
w.mu.Lock()
|
|
||||||
delete(w.fileExists, event.Name)
|
|
||||||
w.mu.Unlock()
|
|
||||||
}
|
|
||||||
|
|
||||||
if path.isDir && event.Op&Write == Write && !(event.Op&Remove == Remove) {
|
|
||||||
w.sendDirectoryChangeEvents(event.Name)
|
|
||||||
} else {
|
|
||||||
// Send the event on the Events channel.
|
|
||||||
select {
|
|
||||||
case w.Events <- event:
|
|
||||||
case <-w.done:
|
|
||||||
break loop
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if event.Op&Remove == Remove {
|
|
||||||
// Look for a file that may have overwritten this.
|
|
||||||
// For example, mv f1 f2 will delete f2, then create f2.
|
|
||||||
if path.isDir {
|
|
||||||
fileDir := filepath.Clean(event.Name)
|
|
||||||
w.mu.Lock()
|
|
||||||
_, found := w.watches[fileDir]
|
|
||||||
w.mu.Unlock()
|
|
||||||
if found {
|
|
||||||
// make sure the directory exists before we watch for changes. When we
|
|
||||||
// do a recursive watch and perform rm -fr, the parent directory might
|
|
||||||
// have gone missing, ignore the missing directory and let the
|
|
||||||
// upcoming delete event remove the watch from the parent directory.
|
|
||||||
if _, err := os.Lstat(fileDir); err == nil {
|
|
||||||
w.sendDirectoryChangeEvents(fileDir)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
filePath := filepath.Clean(event.Name)
|
|
||||||
if fileInfo, err := os.Lstat(filePath); err == nil {
|
|
||||||
w.sendFileCreatedEventIfNew(filePath, fileInfo)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Move to next event
|
|
||||||
kevents = kevents[1:]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// cleanup
|
|
||||||
err := unix.Close(w.kq)
|
|
||||||
if err != nil {
|
|
||||||
// only way the previous loop breaks is if w.done was closed so we need to async send to w.Errors.
|
|
||||||
select {
|
|
||||||
case w.Errors <- err:
|
|
||||||
default:
|
|
||||||
}
|
|
||||||
}
|
|
||||||
close(w.Events)
|
|
||||||
close(w.Errors)
|
|
||||||
}
|
|
||||||
|
|
||||||
// newEvent returns an platform-independent Event based on kqueue Fflags.
|
|
||||||
func newEvent(name string, mask uint32) Event {
|
|
||||||
e := Event{Name: name}
|
|
||||||
if mask&unix.NOTE_DELETE == unix.NOTE_DELETE {
|
|
||||||
e.Op |= Remove
|
|
||||||
}
|
|
||||||
if mask&unix.NOTE_WRITE == unix.NOTE_WRITE {
|
|
||||||
e.Op |= Write
|
|
||||||
}
|
|
||||||
if mask&unix.NOTE_RENAME == unix.NOTE_RENAME {
|
|
||||||
e.Op |= Rename
|
|
||||||
}
|
|
||||||
if mask&unix.NOTE_ATTRIB == unix.NOTE_ATTRIB {
|
|
||||||
e.Op |= Chmod
|
|
||||||
}
|
|
||||||
return e
|
|
||||||
}
|
|
||||||
|
|
||||||
func newCreateEvent(name string) Event {
|
|
||||||
return Event{Name: name, Op: Create}
|
|
||||||
}
|
|
||||||
|
|
||||||
// watchDirectoryFiles to mimic inotify when adding a watch on a directory
|
|
||||||
func (w *Watcher) watchDirectoryFiles(dirPath string) error {
|
|
||||||
// Get all files
|
|
||||||
files, err := ioutil.ReadDir(dirPath)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, fileInfo := range files {
|
|
||||||
filePath := filepath.Join(dirPath, fileInfo.Name())
|
|
||||||
filePath, err = w.internalWatch(filePath, fileInfo)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
w.mu.Lock()
|
|
||||||
w.fileExists[filePath] = true
|
|
||||||
w.mu.Unlock()
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// sendDirectoryEvents searches the directory for newly created files
|
|
||||||
// and sends them over the event channel. This functionality is to have
|
|
||||||
// the BSD version of fsnotify match Linux inotify which provides a
|
|
||||||
// create event for files created in a watched directory.
|
|
||||||
func (w *Watcher) sendDirectoryChangeEvents(dirPath string) {
|
|
||||||
// Get all files
|
|
||||||
files, err := ioutil.ReadDir(dirPath)
|
|
||||||
if err != nil {
|
|
||||||
select {
|
|
||||||
case w.Errors <- err:
|
|
||||||
case <-w.done:
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Search for new files
|
|
||||||
for _, fileInfo := range files {
|
|
||||||
filePath := filepath.Join(dirPath, fileInfo.Name())
|
|
||||||
err := w.sendFileCreatedEventIfNew(filePath, fileInfo)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// sendFileCreatedEvent sends a create event if the file isn't already being tracked.
|
|
||||||
func (w *Watcher) sendFileCreatedEventIfNew(filePath string, fileInfo os.FileInfo) (err error) {
|
|
||||||
w.mu.Lock()
|
|
||||||
_, doesExist := w.fileExists[filePath]
|
|
||||||
w.mu.Unlock()
|
|
||||||
if !doesExist {
|
|
||||||
// Send create event
|
|
||||||
select {
|
|
||||||
case w.Events <- newCreateEvent(filePath):
|
|
||||||
case <-w.done:
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// like watchDirectoryFiles (but without doing another ReadDir)
|
|
||||||
filePath, err = w.internalWatch(filePath, fileInfo)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
w.mu.Lock()
|
|
||||||
w.fileExists[filePath] = true
|
|
||||||
w.mu.Unlock()
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *Watcher) internalWatch(name string, fileInfo os.FileInfo) (string, error) {
|
|
||||||
if fileInfo.IsDir() {
|
|
||||||
// mimic Linux providing delete events for subdirectories
|
|
||||||
// but preserve the flags used if currently watching subdirectory
|
|
||||||
w.mu.Lock()
|
|
||||||
flags := w.dirFlags[name]
|
|
||||||
w.mu.Unlock()
|
|
||||||
|
|
||||||
flags |= unix.NOTE_DELETE | unix.NOTE_RENAME
|
|
||||||
return w.addWatch(name, flags)
|
|
||||||
}
|
|
||||||
|
|
||||||
// watch file to mimic Linux inotify
|
|
||||||
return w.addWatch(name, noteAllEvents)
|
|
||||||
}
|
|
||||||
|
|
||||||
// kqueue creates a new kernel event queue and returns a descriptor.
|
|
||||||
func kqueue() (kq int, err error) {
|
|
||||||
kq, err = unix.Kqueue()
|
|
||||||
if kq == -1 {
|
|
||||||
return kq, err
|
|
||||||
}
|
|
||||||
return kq, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// register events with the queue
|
|
||||||
func register(kq int, fds []int, flags int, fflags uint32) error {
|
|
||||||
changes := make([]unix.Kevent_t, len(fds))
|
|
||||||
|
|
||||||
for i, fd := range fds {
|
|
||||||
// SetKevent converts int to the platform-specific types:
|
|
||||||
unix.SetKevent(&changes[i], fd, unix.EVFILT_VNODE, flags)
|
|
||||||
changes[i].Fflags = fflags
|
|
||||||
}
|
|
||||||
|
|
||||||
// register the events
|
|
||||||
success, err := unix.Kevent(kq, changes, nil, nil)
|
|
||||||
if success == -1 {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// read retrieves pending events, or waits until an event occurs.
|
|
||||||
// A timeout of nil blocks indefinitely, while 0 polls the queue.
|
|
||||||
func read(kq int, events []unix.Kevent_t, timeout *unix.Timespec) ([]unix.Kevent_t, error) {
|
|
||||||
n, err := unix.Kevent(kq, nil, events, timeout)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return events[0:n], nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// durationToTimespec prepares a timeout value
|
|
||||||
func durationToTimespec(d time.Duration) unix.Timespec {
|
|
||||||
return unix.NsecToTimespec(d.Nanoseconds())
|
|
||||||
}
|
|
|
@ -0,0 +1,208 @@
|
||||||
|
#!/usr/bin/env zsh
|
||||||
|
[ "${ZSH_VERSION:-}" = "" ] && echo >&2 "Only works with zsh" && exit 1
|
||||||
|
setopt err_exit no_unset pipefail extended_glob
|
||||||
|
|
||||||
|
# Simple script to update the godoc comments on all watchers. Probably took me
|
||||||
|
# more time to write this than doing it manually, but ah well 🙃
|
||||||
|
|
||||||
|
watcher=$(<<EOF
|
||||||
|
// Watcher watches a set of paths, delivering events on a channel.
|
||||||
|
//
|
||||||
|
// A watcher should not be copied (e.g. pass it by pointer, rather than by
|
||||||
|
// value).
|
||||||
|
//
|
||||||
|
// # Linux notes
|
||||||
|
//
|
||||||
|
// When a file is removed a Remove event won't be emitted until all file
|
||||||
|
// descriptors are closed, and deletes will always emit a Chmod. For example:
|
||||||
|
//
|
||||||
|
// fp := os.Open("file")
|
||||||
|
// os.Remove("file") // Triggers Chmod
|
||||||
|
// fp.Close() // Triggers Remove
|
||||||
|
//
|
||||||
|
// This is the event that inotify sends, so not much can be changed about this.
|
||||||
|
//
|
||||||
|
// The fs.inotify.max_user_watches sysctl variable specifies the upper limit
|
||||||
|
// for the number of watches per user, and fs.inotify.max_user_instances
|
||||||
|
// specifies the maximum number of inotify instances per user. Every Watcher you
|
||||||
|
// create is an "instance", and every path you add is a "watch".
|
||||||
|
//
|
||||||
|
// These are also exposed in /proc as /proc/sys/fs/inotify/max_user_watches and
|
||||||
|
// /proc/sys/fs/inotify/max_user_instances
|
||||||
|
//
|
||||||
|
// To increase them you can use sysctl or write the value to the /proc file:
|
||||||
|
//
|
||||||
|
// # Default values on Linux 5.18
|
||||||
|
// sysctl fs.inotify.max_user_watches=124983
|
||||||
|
// sysctl fs.inotify.max_user_instances=128
|
||||||
|
//
|
||||||
|
// To make the changes persist on reboot edit /etc/sysctl.conf or
|
||||||
|
// /usr/lib/sysctl.d/50-default.conf (details differ per Linux distro; check
|
||||||
|
// your distro's documentation):
|
||||||
|
//
|
||||||
|
// fs.inotify.max_user_watches=124983
|
||||||
|
// fs.inotify.max_user_instances=128
|
||||||
|
//
|
||||||
|
// Reaching the limit will result in a "no space left on device" or "too many open
|
||||||
|
// files" error.
|
||||||
|
//
|
||||||
|
// # kqueue notes (macOS, BSD)
|
||||||
|
//
|
||||||
|
// kqueue requires opening a file descriptor for every file that's being watched;
|
||||||
|
// so if you're watching a directory with five files then that's six file
|
||||||
|
// descriptors. You will run in to your system's "max open files" limit faster on
|
||||||
|
// these platforms.
|
||||||
|
//
|
||||||
|
// The sysctl variables kern.maxfiles and kern.maxfilesperproc can be used to
|
||||||
|
// control the maximum number of open files, as well as /etc/login.conf on BSD
|
||||||
|
// systems.
|
||||||
|
//
|
||||||
|
// # macOS notes
|
||||||
|
//
|
||||||
|
// Spotlight indexing on macOS can result in multiple events (see [#15]). A
|
||||||
|
// temporary workaround is to add your folder(s) to the "Spotlight Privacy
|
||||||
|
// Settings" until we have a native FSEvents implementation (see [#11]).
|
||||||
|
//
|
||||||
|
// [#11]: https://github.com/fsnotify/fsnotify/issues/11
|
||||||
|
// [#15]: https://github.com/fsnotify/fsnotify/issues/15
|
||||||
|
EOF
|
||||||
|
)
|
||||||
|
|
||||||
|
new=$(<<EOF
|
||||||
|
// NewWatcher creates a new Watcher.
|
||||||
|
EOF
|
||||||
|
)
|
||||||
|
|
||||||
|
add=$(<<EOF
|
||||||
|
// Add starts monitoring the path for changes.
|
||||||
|
//
|
||||||
|
// A path can only be watched once; attempting to watch it more than once will
|
||||||
|
// return an error. Paths that do not yet exist on the filesystem cannot be
|
||||||
|
// added. A watch will be automatically removed if the path is deleted.
|
||||||
|
//
|
||||||
|
// A path will remain watched if it gets renamed to somewhere else on the same
|
||||||
|
// filesystem, but the monitor will get removed if the path gets deleted and
|
||||||
|
// re-created, or if it's moved to a different filesystem.
|
||||||
|
//
|
||||||
|
// Notifications on network filesystems (NFS, SMB, FUSE, etc.) or special
|
||||||
|
// filesystems (/proc, /sys, etc.) generally don't work.
|
||||||
|
//
|
||||||
|
// # Watching directories
|
||||||
|
//
|
||||||
|
// All files in a directory are monitored, including new files that are created
|
||||||
|
// after the watcher is started. Subdirectories are not watched (i.e. it's
|
||||||
|
// non-recursive).
|
||||||
|
//
|
||||||
|
// # Watching files
|
||||||
|
//
|
||||||
|
// Watching individual files (rather than directories) is generally not
|
||||||
|
// recommended as many tools update files atomically. Instead of "just" writing
|
||||||
|
// to the file a temporary file will be written to first, and if successful the
|
||||||
|
// temporary file is moved to to destination removing the original, or some
|
||||||
|
// variant thereof. The watcher on the original file is now lost, as it no
|
||||||
|
// longer exists.
|
||||||
|
//
|
||||||
|
// Instead, watch the parent directory and use Event.Name to filter out files
|
||||||
|
// you're not interested in. There is an example of this in [cmd/fsnotify/file.go].
|
||||||
|
EOF
|
||||||
|
)
|
||||||
|
|
||||||
|
remove=$(<<EOF
|
||||||
|
// Remove stops monitoring the path for changes.
|
||||||
|
//
|
||||||
|
// Directories are always removed non-recursively. For example, if you added
|
||||||
|
// /tmp/dir and /tmp/dir/subdir then you will need to remove both.
|
||||||
|
//
|
||||||
|
// Removing a path that has not yet been added returns [ErrNonExistentWatch].
|
||||||
|
EOF
|
||||||
|
)
|
||||||
|
|
||||||
|
close=$(<<EOF
|
||||||
|
// Close removes all watches and closes the events channel.
|
||||||
|
EOF
|
||||||
|
)
|
||||||
|
|
||||||
|
watchlist=$(<<EOF
|
||||||
|
// WatchList returns all paths added with [Add] (and are not yet removed).
|
||||||
|
EOF
|
||||||
|
)
|
||||||
|
|
||||||
|
events=$(<<EOF
|
||||||
|
// Events sends the filesystem change events.
|
||||||
|
//
|
||||||
|
// fsnotify can send the following events; a "path" here can refer to a
|
||||||
|
// file, directory, symbolic link, or special file like a FIFO.
|
||||||
|
//
|
||||||
|
// fsnotify.Create A new path was created; this may be followed by one
|
||||||
|
// or more Write events if data also gets written to a
|
||||||
|
// file.
|
||||||
|
//
|
||||||
|
// fsnotify.Remove A path was removed.
|
||||||
|
//
|
||||||
|
// fsnotify.Rename A path was renamed. A rename is always sent with the
|
||||||
|
// old path as Event.Name, and a Create event will be
|
||||||
|
// sent with the new name. Renames are only sent for
|
||||||
|
// paths that are currently watched; e.g. moving an
|
||||||
|
// unmonitored file into a monitored directory will
|
||||||
|
// show up as just a Create. Similarly, renaming a file
|
||||||
|
// to outside a monitored directory will show up as
|
||||||
|
// only a Rename.
|
||||||
|
//
|
||||||
|
// fsnotify.Write A file or named pipe was written to. A Truncate will
|
||||||
|
// also trigger a Write. A single "write action"
|
||||||
|
// initiated by the user may show up as one or multiple
|
||||||
|
// writes, depending on when the system syncs things to
|
||||||
|
// disk. For example when compiling a large Go program
|
||||||
|
// you may get hundreds of Write events, so you
|
||||||
|
// probably want to wait until you've stopped receiving
|
||||||
|
// them (see the dedup example in cmd/fsnotify).
|
||||||
|
//
|
||||||
|
// fsnotify.Chmod Attributes were changed. On Linux this is also sent
|
||||||
|
// when a file is removed (or more accurately, when a
|
||||||
|
// link to an inode is removed). On kqueue it's sent
|
||||||
|
// and on kqueue when a file is truncated. On Windows
|
||||||
|
// it's never sent.
|
||||||
|
EOF
|
||||||
|
)
|
||||||
|
|
||||||
|
errors=$(<<EOF
|
||||||
|
// Errors sends any errors.
|
||||||
|
EOF
|
||||||
|
)
|
||||||
|
|
||||||
|
set-cmt() {
|
||||||
|
local pat=$1
|
||||||
|
local cmt=$2
|
||||||
|
|
||||||
|
IFS=$'\n' local files=($(grep -n $pat backend_*~*_test.go))
|
||||||
|
for f in $files; do
|
||||||
|
IFS=':' local fields=($=f)
|
||||||
|
local file=$fields[1]
|
||||||
|
local end=$(( $fields[2] - 1 ))
|
||||||
|
|
||||||
|
# Find start of comment.
|
||||||
|
local start=0
|
||||||
|
IFS=$'\n' local lines=($(head -n$end $file))
|
||||||
|
for (( i = 1; i <= $#lines; i++ )); do
|
||||||
|
local line=$lines[-$i]
|
||||||
|
if ! grep -q '^[[:space:]]*//' <<<$line; then
|
||||||
|
start=$(( end - (i - 2) ))
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
head -n $(( start - 1 )) $file >/tmp/x
|
||||||
|
print -r -- $cmt >>/tmp/x
|
||||||
|
tail -n+$(( end + 1 )) $file >>/tmp/x
|
||||||
|
mv /tmp/x $file
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
set-cmt '^type Watcher struct ' $watcher
|
||||||
|
set-cmt '^func NewWatcher(' $new
|
||||||
|
set-cmt '^func (w \*Watcher) Add(' $add
|
||||||
|
set-cmt '^func (w \*Watcher) Remove(' $remove
|
||||||
|
set-cmt '^func (w \*Watcher) Close(' $close
|
||||||
|
set-cmt '^func (w \*Watcher) WatchList(' $watchlist
|
||||||
|
set-cmt '^[[:space:]]*Events *chan Event$' $events
|
||||||
|
set-cmt '^[[:space:]]*Errors *chan error$' $errors
|
|
@ -1,7 +1,4 @@
|
||||||
// Copyright 2013 The Go Authors. All rights reserved.
|
//go:build freebsd || openbsd || netbsd || dragonfly
|
||||||
// Use of this source code is governed by a BSD-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
// +build freebsd openbsd netbsd dragonfly
|
// +build freebsd openbsd netbsd dragonfly
|
||||||
|
|
||||||
package fsnotify
|
package fsnotify
|
|
@ -1,7 +1,4 @@
|
||||||
// Copyright 2013 The Go Authors. All rights reserved.
|
//go:build darwin
|
||||||
// Use of this source code is governed by a BSD-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
// +build darwin
|
// +build darwin
|
||||||
|
|
||||||
package fsnotify
|
package fsnotify
|
|
@ -1,561 +0,0 @@
|
||||||
// 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.
|
|
||||||
|
|
||||||
// +build windows
|
|
||||||
|
|
||||||
package fsnotify
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
"runtime"
|
|
||||||
"sync"
|
|
||||||
"syscall"
|
|
||||||
"unsafe"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Watcher watches a set of files, delivering events to a channel.
|
|
||||||
type Watcher struct {
|
|
||||||
Events chan Event
|
|
||||||
Errors chan error
|
|
||||||
isClosed bool // Set to true when Close() is first called
|
|
||||||
mu sync.Mutex // Map access
|
|
||||||
port syscall.Handle // Handle to completion port
|
|
||||||
watches watchMap // Map of watches (key: i-number)
|
|
||||||
input chan *input // Inputs to the reader are sent on this channel
|
|
||||||
quit chan chan<- error
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewWatcher establishes a new watcher with the underlying OS and begins waiting for events.
|
|
||||||
func NewWatcher() (*Watcher, error) {
|
|
||||||
port, e := syscall.CreateIoCompletionPort(syscall.InvalidHandle, 0, 0, 0)
|
|
||||||
if e != nil {
|
|
||||||
return nil, os.NewSyscallError("CreateIoCompletionPort", e)
|
|
||||||
}
|
|
||||||
w := &Watcher{
|
|
||||||
port: port,
|
|
||||||
watches: make(watchMap),
|
|
||||||
input: make(chan *input, 1),
|
|
||||||
Events: make(chan Event, 50),
|
|
||||||
Errors: make(chan error),
|
|
||||||
quit: make(chan chan<- error, 1),
|
|
||||||
}
|
|
||||||
go w.readEvents()
|
|
||||||
return w, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Close removes all watches and closes the events channel.
|
|
||||||
func (w *Watcher) Close() error {
|
|
||||||
if w.isClosed {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
w.isClosed = true
|
|
||||||
|
|
||||||
// Send "quit" message to the reader goroutine
|
|
||||||
ch := make(chan error)
|
|
||||||
w.quit <- ch
|
|
||||||
if err := w.wakeupReader(); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return <-ch
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add starts watching the named file or directory (non-recursively).
|
|
||||||
func (w *Watcher) Add(name string) error {
|
|
||||||
if w.isClosed {
|
|
||||||
return errors.New("watcher already closed")
|
|
||||||
}
|
|
||||||
in := &input{
|
|
||||||
op: opAddWatch,
|
|
||||||
path: filepath.Clean(name),
|
|
||||||
flags: sysFSALLEVENTS,
|
|
||||||
reply: make(chan error),
|
|
||||||
}
|
|
||||||
w.input <- in
|
|
||||||
if err := w.wakeupReader(); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return <-in.reply
|
|
||||||
}
|
|
||||||
|
|
||||||
// Remove stops watching the the named file or directory (non-recursively).
|
|
||||||
func (w *Watcher) Remove(name string) error {
|
|
||||||
in := &input{
|
|
||||||
op: opRemoveWatch,
|
|
||||||
path: filepath.Clean(name),
|
|
||||||
reply: make(chan error),
|
|
||||||
}
|
|
||||||
w.input <- in
|
|
||||||
if err := w.wakeupReader(); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return <-in.reply
|
|
||||||
}
|
|
||||||
|
|
||||||
const (
|
|
||||||
// Options for AddWatch
|
|
||||||
sysFSONESHOT = 0x80000000
|
|
||||||
sysFSONLYDIR = 0x1000000
|
|
||||||
|
|
||||||
// Events
|
|
||||||
sysFSACCESS = 0x1
|
|
||||||
sysFSALLEVENTS = 0xfff
|
|
||||||
sysFSATTRIB = 0x4
|
|
||||||
sysFSCLOSE = 0x18
|
|
||||||
sysFSCREATE = 0x100
|
|
||||||
sysFSDELETE = 0x200
|
|
||||||
sysFSDELETESELF = 0x400
|
|
||||||
sysFSMODIFY = 0x2
|
|
||||||
sysFSMOVE = 0xc0
|
|
||||||
sysFSMOVEDFROM = 0x40
|
|
||||||
sysFSMOVEDTO = 0x80
|
|
||||||
sysFSMOVESELF = 0x800
|
|
||||||
|
|
||||||
// Special events
|
|
||||||
sysFSIGNORED = 0x8000
|
|
||||||
sysFSQOVERFLOW = 0x4000
|
|
||||||
)
|
|
||||||
|
|
||||||
func newEvent(name string, mask uint32) Event {
|
|
||||||
e := Event{Name: name}
|
|
||||||
if mask&sysFSCREATE == sysFSCREATE || mask&sysFSMOVEDTO == sysFSMOVEDTO {
|
|
||||||
e.Op |= Create
|
|
||||||
}
|
|
||||||
if mask&sysFSDELETE == sysFSDELETE || mask&sysFSDELETESELF == sysFSDELETESELF {
|
|
||||||
e.Op |= Remove
|
|
||||||
}
|
|
||||||
if mask&sysFSMODIFY == sysFSMODIFY {
|
|
||||||
e.Op |= Write
|
|
||||||
}
|
|
||||||
if mask&sysFSMOVE == sysFSMOVE || mask&sysFSMOVESELF == sysFSMOVESELF || mask&sysFSMOVEDFROM == sysFSMOVEDFROM {
|
|
||||||
e.Op |= Rename
|
|
||||||
}
|
|
||||||
if mask&sysFSATTRIB == sysFSATTRIB {
|
|
||||||
e.Op |= Chmod
|
|
||||||
}
|
|
||||||
return e
|
|
||||||
}
|
|
||||||
|
|
||||||
const (
|
|
||||||
opAddWatch = iota
|
|
||||||
opRemoveWatch
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
provisional uint64 = 1 << (32 + iota)
|
|
||||||
)
|
|
||||||
|
|
||||||
type input struct {
|
|
||||||
op int
|
|
||||||
path string
|
|
||||||
flags uint32
|
|
||||||
reply chan error
|
|
||||||
}
|
|
||||||
|
|
||||||
type inode struct {
|
|
||||||
handle syscall.Handle
|
|
||||||
volume uint32
|
|
||||||
index uint64
|
|
||||||
}
|
|
||||||
|
|
||||||
type watch struct {
|
|
||||||
ov syscall.Overlapped
|
|
||||||
ino *inode // i-number
|
|
||||||
path string // Directory path
|
|
||||||
mask uint64 // Directory itself is being watched with these notify flags
|
|
||||||
names map[string]uint64 // Map of names being watched and their notify flags
|
|
||||||
rename string // Remembers the old name while renaming a file
|
|
||||||
buf [4096]byte
|
|
||||||
}
|
|
||||||
|
|
||||||
type indexMap map[uint64]*watch
|
|
||||||
type watchMap map[uint32]indexMap
|
|
||||||
|
|
||||||
func (w *Watcher) wakeupReader() error {
|
|
||||||
e := syscall.PostQueuedCompletionStatus(w.port, 0, 0, nil)
|
|
||||||
if e != nil {
|
|
||||||
return os.NewSyscallError("PostQueuedCompletionStatus", e)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func getDir(pathname string) (dir string, err error) {
|
|
||||||
attr, e := syscall.GetFileAttributes(syscall.StringToUTF16Ptr(pathname))
|
|
||||||
if e != nil {
|
|
||||||
return "", os.NewSyscallError("GetFileAttributes", e)
|
|
||||||
}
|
|
||||||
if attr&syscall.FILE_ATTRIBUTE_DIRECTORY != 0 {
|
|
||||||
dir = pathname
|
|
||||||
} else {
|
|
||||||
dir, _ = filepath.Split(pathname)
|
|
||||||
dir = filepath.Clean(dir)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func getIno(path string) (ino *inode, err error) {
|
|
||||||
h, e := syscall.CreateFile(syscall.StringToUTF16Ptr(path),
|
|
||||||
syscall.FILE_LIST_DIRECTORY,
|
|
||||||
syscall.FILE_SHARE_READ|syscall.FILE_SHARE_WRITE|syscall.FILE_SHARE_DELETE,
|
|
||||||
nil, syscall.OPEN_EXISTING,
|
|
||||||
syscall.FILE_FLAG_BACKUP_SEMANTICS|syscall.FILE_FLAG_OVERLAPPED, 0)
|
|
||||||
if e != nil {
|
|
||||||
return nil, os.NewSyscallError("CreateFile", e)
|
|
||||||
}
|
|
||||||
var fi syscall.ByHandleFileInformation
|
|
||||||
if e = syscall.GetFileInformationByHandle(h, &fi); e != nil {
|
|
||||||
syscall.CloseHandle(h)
|
|
||||||
return nil, os.NewSyscallError("GetFileInformationByHandle", e)
|
|
||||||
}
|
|
||||||
ino = &inode{
|
|
||||||
handle: h,
|
|
||||||
volume: fi.VolumeSerialNumber,
|
|
||||||
index: uint64(fi.FileIndexHigh)<<32 | uint64(fi.FileIndexLow),
|
|
||||||
}
|
|
||||||
return ino, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Must run within the I/O thread.
|
|
||||||
func (m watchMap) get(ino *inode) *watch {
|
|
||||||
if i := m[ino.volume]; i != nil {
|
|
||||||
return i[ino.index]
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Must run within the I/O thread.
|
|
||||||
func (m watchMap) set(ino *inode, watch *watch) {
|
|
||||||
i := m[ino.volume]
|
|
||||||
if i == nil {
|
|
||||||
i = make(indexMap)
|
|
||||||
m[ino.volume] = i
|
|
||||||
}
|
|
||||||
i[ino.index] = watch
|
|
||||||
}
|
|
||||||
|
|
||||||
// Must run within the I/O thread.
|
|
||||||
func (w *Watcher) addWatch(pathname string, flags uint64) error {
|
|
||||||
dir, err := getDir(pathname)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if flags&sysFSONLYDIR != 0 && pathname != dir {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
ino, err := getIno(dir)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
w.mu.Lock()
|
|
||||||
watchEntry := w.watches.get(ino)
|
|
||||||
w.mu.Unlock()
|
|
||||||
if watchEntry == nil {
|
|
||||||
if _, e := syscall.CreateIoCompletionPort(ino.handle, w.port, 0, 0); e != nil {
|
|
||||||
syscall.CloseHandle(ino.handle)
|
|
||||||
return os.NewSyscallError("CreateIoCompletionPort", e)
|
|
||||||
}
|
|
||||||
watchEntry = &watch{
|
|
||||||
ino: ino,
|
|
||||||
path: dir,
|
|
||||||
names: make(map[string]uint64),
|
|
||||||
}
|
|
||||||
w.mu.Lock()
|
|
||||||
w.watches.set(ino, watchEntry)
|
|
||||||
w.mu.Unlock()
|
|
||||||
flags |= provisional
|
|
||||||
} else {
|
|
||||||
syscall.CloseHandle(ino.handle)
|
|
||||||
}
|
|
||||||
if pathname == dir {
|
|
||||||
watchEntry.mask |= flags
|
|
||||||
} else {
|
|
||||||
watchEntry.names[filepath.Base(pathname)] |= flags
|
|
||||||
}
|
|
||||||
if err = w.startRead(watchEntry); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if pathname == dir {
|
|
||||||
watchEntry.mask &= ^provisional
|
|
||||||
} else {
|
|
||||||
watchEntry.names[filepath.Base(pathname)] &= ^provisional
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Must run within the I/O thread.
|
|
||||||
func (w *Watcher) remWatch(pathname string) error {
|
|
||||||
dir, err := getDir(pathname)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
ino, err := getIno(dir)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
w.mu.Lock()
|
|
||||||
watch := w.watches.get(ino)
|
|
||||||
w.mu.Unlock()
|
|
||||||
if watch == nil {
|
|
||||||
return fmt.Errorf("can't remove non-existent watch for: %s", pathname)
|
|
||||||
}
|
|
||||||
if pathname == dir {
|
|
||||||
w.sendEvent(watch.path, watch.mask&sysFSIGNORED)
|
|
||||||
watch.mask = 0
|
|
||||||
} else {
|
|
||||||
name := filepath.Base(pathname)
|
|
||||||
w.sendEvent(filepath.Join(watch.path, name), watch.names[name]&sysFSIGNORED)
|
|
||||||
delete(watch.names, name)
|
|
||||||
}
|
|
||||||
return w.startRead(watch)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Must run within the I/O thread.
|
|
||||||
func (w *Watcher) deleteWatch(watch *watch) {
|
|
||||||
for name, mask := range watch.names {
|
|
||||||
if mask&provisional == 0 {
|
|
||||||
w.sendEvent(filepath.Join(watch.path, name), mask&sysFSIGNORED)
|
|
||||||
}
|
|
||||||
delete(watch.names, name)
|
|
||||||
}
|
|
||||||
if watch.mask != 0 {
|
|
||||||
if watch.mask&provisional == 0 {
|
|
||||||
w.sendEvent(watch.path, watch.mask&sysFSIGNORED)
|
|
||||||
}
|
|
||||||
watch.mask = 0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Must run within the I/O thread.
|
|
||||||
func (w *Watcher) startRead(watch *watch) error {
|
|
||||||
if e := syscall.CancelIo(watch.ino.handle); e != nil {
|
|
||||||
w.Errors <- os.NewSyscallError("CancelIo", e)
|
|
||||||
w.deleteWatch(watch)
|
|
||||||
}
|
|
||||||
mask := toWindowsFlags(watch.mask)
|
|
||||||
for _, m := range watch.names {
|
|
||||||
mask |= toWindowsFlags(m)
|
|
||||||
}
|
|
||||||
if mask == 0 {
|
|
||||||
if e := syscall.CloseHandle(watch.ino.handle); e != nil {
|
|
||||||
w.Errors <- os.NewSyscallError("CloseHandle", e)
|
|
||||||
}
|
|
||||||
w.mu.Lock()
|
|
||||||
delete(w.watches[watch.ino.volume], watch.ino.index)
|
|
||||||
w.mu.Unlock()
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
e := syscall.ReadDirectoryChanges(watch.ino.handle, &watch.buf[0],
|
|
||||||
uint32(unsafe.Sizeof(watch.buf)), false, mask, nil, &watch.ov, 0)
|
|
||||||
if e != nil {
|
|
||||||
err := os.NewSyscallError("ReadDirectoryChanges", e)
|
|
||||||
if e == syscall.ERROR_ACCESS_DENIED && watch.mask&provisional == 0 {
|
|
||||||
// Watched directory was probably removed
|
|
||||||
if w.sendEvent(watch.path, watch.mask&sysFSDELETESELF) {
|
|
||||||
if watch.mask&sysFSONESHOT != 0 {
|
|
||||||
watch.mask = 0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
err = nil
|
|
||||||
}
|
|
||||||
w.deleteWatch(watch)
|
|
||||||
w.startRead(watch)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// readEvents reads from the I/O completion port, converts the
|
|
||||||
// received events into Event objects and sends them via the Events channel.
|
|
||||||
// Entry point to the I/O thread.
|
|
||||||
func (w *Watcher) readEvents() {
|
|
||||||
var (
|
|
||||||
n, key uint32
|
|
||||||
ov *syscall.Overlapped
|
|
||||||
)
|
|
||||||
runtime.LockOSThread()
|
|
||||||
|
|
||||||
for {
|
|
||||||
e := syscall.GetQueuedCompletionStatus(w.port, &n, &key, &ov, syscall.INFINITE)
|
|
||||||
watch := (*watch)(unsafe.Pointer(ov))
|
|
||||||
|
|
||||||
if watch == nil {
|
|
||||||
select {
|
|
||||||
case ch := <-w.quit:
|
|
||||||
w.mu.Lock()
|
|
||||||
var indexes []indexMap
|
|
||||||
for _, index := range w.watches {
|
|
||||||
indexes = append(indexes, index)
|
|
||||||
}
|
|
||||||
w.mu.Unlock()
|
|
||||||
for _, index := range indexes {
|
|
||||||
for _, watch := range index {
|
|
||||||
w.deleteWatch(watch)
|
|
||||||
w.startRead(watch)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
var err error
|
|
||||||
if e := syscall.CloseHandle(w.port); e != nil {
|
|
||||||
err = os.NewSyscallError("CloseHandle", e)
|
|
||||||
}
|
|
||||||
close(w.Events)
|
|
||||||
close(w.Errors)
|
|
||||||
ch <- err
|
|
||||||
return
|
|
||||||
case in := <-w.input:
|
|
||||||
switch in.op {
|
|
||||||
case opAddWatch:
|
|
||||||
in.reply <- w.addWatch(in.path, uint64(in.flags))
|
|
||||||
case opRemoveWatch:
|
|
||||||
in.reply <- w.remWatch(in.path)
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
}
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
switch e {
|
|
||||||
case syscall.ERROR_MORE_DATA:
|
|
||||||
if watch == nil {
|
|
||||||
w.Errors <- errors.New("ERROR_MORE_DATA has unexpectedly null lpOverlapped buffer")
|
|
||||||
} else {
|
|
||||||
// The i/o succeeded but the buffer is full.
|
|
||||||
// In theory we should be building up a full packet.
|
|
||||||
// In practice we can get away with just carrying on.
|
|
||||||
n = uint32(unsafe.Sizeof(watch.buf))
|
|
||||||
}
|
|
||||||
case syscall.ERROR_ACCESS_DENIED:
|
|
||||||
// Watched directory was probably removed
|
|
||||||
w.sendEvent(watch.path, watch.mask&sysFSDELETESELF)
|
|
||||||
w.deleteWatch(watch)
|
|
||||||
w.startRead(watch)
|
|
||||||
continue
|
|
||||||
case syscall.ERROR_OPERATION_ABORTED:
|
|
||||||
// CancelIo was called on this handle
|
|
||||||
continue
|
|
||||||
default:
|
|
||||||
w.Errors <- os.NewSyscallError("GetQueuedCompletionPort", e)
|
|
||||||
continue
|
|
||||||
case nil:
|
|
||||||
}
|
|
||||||
|
|
||||||
var offset uint32
|
|
||||||
for {
|
|
||||||
if n == 0 {
|
|
||||||
w.Events <- newEvent("", sysFSQOVERFLOW)
|
|
||||||
w.Errors <- errors.New("short read in readEvents()")
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
// Point "raw" to the event in the buffer
|
|
||||||
raw := (*syscall.FileNotifyInformation)(unsafe.Pointer(&watch.buf[offset]))
|
|
||||||
buf := (*[syscall.MAX_PATH]uint16)(unsafe.Pointer(&raw.FileName))
|
|
||||||
name := syscall.UTF16ToString(buf[:raw.FileNameLength/2])
|
|
||||||
fullname := filepath.Join(watch.path, name)
|
|
||||||
|
|
||||||
var mask uint64
|
|
||||||
switch raw.Action {
|
|
||||||
case syscall.FILE_ACTION_REMOVED:
|
|
||||||
mask = sysFSDELETESELF
|
|
||||||
case syscall.FILE_ACTION_MODIFIED:
|
|
||||||
mask = sysFSMODIFY
|
|
||||||
case syscall.FILE_ACTION_RENAMED_OLD_NAME:
|
|
||||||
watch.rename = name
|
|
||||||
case syscall.FILE_ACTION_RENAMED_NEW_NAME:
|
|
||||||
if watch.names[watch.rename] != 0 {
|
|
||||||
watch.names[name] |= watch.names[watch.rename]
|
|
||||||
delete(watch.names, watch.rename)
|
|
||||||
mask = sysFSMOVESELF
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
sendNameEvent := func() {
|
|
||||||
if w.sendEvent(fullname, watch.names[name]&mask) {
|
|
||||||
if watch.names[name]&sysFSONESHOT != 0 {
|
|
||||||
delete(watch.names, name)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if raw.Action != syscall.FILE_ACTION_RENAMED_NEW_NAME {
|
|
||||||
sendNameEvent()
|
|
||||||
}
|
|
||||||
if raw.Action == syscall.FILE_ACTION_REMOVED {
|
|
||||||
w.sendEvent(fullname, watch.names[name]&sysFSIGNORED)
|
|
||||||
delete(watch.names, name)
|
|
||||||
}
|
|
||||||
if w.sendEvent(fullname, watch.mask&toFSnotifyFlags(raw.Action)) {
|
|
||||||
if watch.mask&sysFSONESHOT != 0 {
|
|
||||||
watch.mask = 0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if raw.Action == syscall.FILE_ACTION_RENAMED_NEW_NAME {
|
|
||||||
fullname = filepath.Join(watch.path, watch.rename)
|
|
||||||
sendNameEvent()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Move to the next event in the buffer
|
|
||||||
if raw.NextEntryOffset == 0 {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
offset += raw.NextEntryOffset
|
|
||||||
|
|
||||||
// Error!
|
|
||||||
if offset >= n {
|
|
||||||
w.Errors <- errors.New("Windows system assumed buffer larger than it is, events have likely been missed.")
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := w.startRead(watch); err != nil {
|
|
||||||
w.Errors <- err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *Watcher) sendEvent(name string, mask uint64) bool {
|
|
||||||
if mask == 0 {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
event := newEvent(name, uint32(mask))
|
|
||||||
select {
|
|
||||||
case ch := <-w.quit:
|
|
||||||
w.quit <- ch
|
|
||||||
case w.Events <- event:
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
func toWindowsFlags(mask uint64) uint32 {
|
|
||||||
var m uint32
|
|
||||||
if mask&sysFSACCESS != 0 {
|
|
||||||
m |= syscall.FILE_NOTIFY_CHANGE_LAST_ACCESS
|
|
||||||
}
|
|
||||||
if mask&sysFSMODIFY != 0 {
|
|
||||||
m |= syscall.FILE_NOTIFY_CHANGE_LAST_WRITE
|
|
||||||
}
|
|
||||||
if mask&sysFSATTRIB != 0 {
|
|
||||||
m |= syscall.FILE_NOTIFY_CHANGE_ATTRIBUTES
|
|
||||||
}
|
|
||||||
if mask&(sysFSMOVE|sysFSCREATE|sysFSDELETE) != 0 {
|
|
||||||
m |= syscall.FILE_NOTIFY_CHANGE_FILE_NAME | syscall.FILE_NOTIFY_CHANGE_DIR_NAME
|
|
||||||
}
|
|
||||||
return m
|
|
||||||
}
|
|
||||||
|
|
||||||
func toFSnotifyFlags(action uint32) uint64 {
|
|
||||||
switch action {
|
|
||||||
case syscall.FILE_ACTION_ADDED:
|
|
||||||
return sysFSCREATE
|
|
||||||
case syscall.FILE_ACTION_REMOVED:
|
|
||||||
return sysFSDELETE
|
|
||||||
case syscall.FILE_ACTION_MODIFIED:
|
|
||||||
return sysFSMODIFY
|
|
||||||
case syscall.FILE_ACTION_RENAMED_OLD_NAME:
|
|
||||||
return sysFSMOVEDFROM
|
|
||||||
case syscall.FILE_ACTION_RENAMED_NEW_NAME:
|
|
||||||
return sysFSMOVEDTO
|
|
||||||
}
|
|
||||||
return 0
|
|
||||||
}
|
|
|
@ -0,0 +1,13 @@
|
||||||
|
codecov:
|
||||||
|
# across
|
||||||
|
notify:
|
||||||
|
# Do not notify until at least this number of reports have been uploaded
|
||||||
|
# from the CI pipeline. We normally have more than that number, but 6
|
||||||
|
# should be enough to get a first notification.
|
||||||
|
after_n_builds: 6
|
||||||
|
coverage:
|
||||||
|
status:
|
||||||
|
project:
|
||||||
|
default:
|
||||||
|
# Do not fail the commit status if the coverage was reduced up to this value
|
||||||
|
threshold: 0.5%
|
|
@ -1,11 +1,13 @@
|
||||||
minVersion: 0.23.1
|
minVersion: 0.35.0
|
||||||
changelogPolicy: simple
|
changelogPolicy: simple
|
||||||
artifactProvider:
|
artifactProvider:
|
||||||
name: none
|
name: none
|
||||||
targets:
|
targets:
|
||||||
- name: github
|
- name: github
|
||||||
includeNames: /none/
|
|
||||||
tagPrefix: v
|
tagPrefix: v
|
||||||
|
- name: github
|
||||||
|
tagPrefix: otel/v
|
||||||
|
tagOnly: true
|
||||||
- name: registry
|
- name: registry
|
||||||
sdks:
|
sdks:
|
||||||
github:getsentry/sentry-go:
|
github:getsentry/sentry-go:
|
||||||
|
|
|
@ -1,4 +1,8 @@
|
||||||
|
# Code coverage artifacts
|
||||||
coverage.txt
|
coverage.txt
|
||||||
|
coverage.out
|
||||||
|
coverage.html
|
||||||
|
.coverage/
|
||||||
|
|
||||||
# Just my personal way of tracking stuff — Kamil
|
# Just my personal way of tracking stuff — Kamil
|
||||||
FIXME.md
|
FIXME.md
|
||||||
|
|
|
@ -35,6 +35,7 @@ issues:
|
||||||
exclude-rules:
|
exclude-rules:
|
||||||
- path: _test\.go
|
- path: _test\.go
|
||||||
linters:
|
linters:
|
||||||
|
- goconst
|
||||||
- prealloc
|
- prealloc
|
||||||
- path: _test\.go
|
- path: _test\.go
|
||||||
text: "G306:"
|
text: "G306:"
|
||||||
|
|
|
@ -1,5 +1,206 @@
|
||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
## 0.21.0
|
||||||
|
|
||||||
|
The Sentry SDK team is happy to announce the immediate availability of Sentry Go SDK v0.21.0.
|
||||||
|
|
||||||
|
Note: this release includes one **breaking change** and some **deprecations**, which are listed below.
|
||||||
|
|
||||||
|
### Breaking Changes
|
||||||
|
|
||||||
|
**This change does not apply if you use [https://sentry.io](https://sentry.io)**
|
||||||
|
|
||||||
|
- Remove support for the `/store` endpoint ([#631](https://github.com/getsentry/sentry-go/pull/631))
|
||||||
|
- This change requires a self-hosted version of Sentry 20.6.0 or higher. If you are using a version of [self-hosted Sentry](https://develop.sentry.dev/self-hosted/) (aka *on-premise*) older than 20.6.0, then you will need to [upgrade](https://develop.sentry.dev/self-hosted/releases/) your instance.
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
- Rename four span option functions ([#611](https://github.com/getsentry/sentry-go/pull/611), [#624](https://github.com/getsentry/sentry-go/pull/624))
|
||||||
|
- `TransctionSource` -> `WithTransactionSource`
|
||||||
|
- `SpanSampled` -> `WithSpanSampled`
|
||||||
|
- `OpName` -> `WithOpName`
|
||||||
|
- `TransactionName` -> `WithTransactionName`
|
||||||
|
- Old functions `TransctionSource`, `SpanSampled`, `OpName`, and `TransactionName` are still available but are now **deprecated** and will be removed in a future release.
|
||||||
|
- Make `client.EventFromMessage` and `client.EventFromException` methods public ([#607](https://github.com/getsentry/sentry-go/pull/607))
|
||||||
|
- Add `client.SetException` method ([#607](https://github.com/getsentry/sentry-go/pull/607))
|
||||||
|
- This allows to set or add errors to an existing `Event`.
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
- Protect from panics while doing concurrent reads/writes to Span data fields ([#609](https://github.com/getsentry/sentry-go/pull/609))
|
||||||
|
- [otel] Improve detection of Sentry-related spans ([#632](https://github.com/getsentry/sentry-go/pull/632), [#636](https://github.com/getsentry/sentry-go/pull/636))
|
||||||
|
- Fixes cases when HTTP spans containing requests to Sentry were captured by Sentry ([#627](https://github.com/getsentry/sentry-go/issues/627))
|
||||||
|
|
||||||
|
### Misc
|
||||||
|
|
||||||
|
- Drop testing in (legacy) GOPATH mode ([#618](https://github.com/getsentry/sentry-go/pull/618))
|
||||||
|
- Remove outdated documentation from https://pkg.go.dev/github.com/getsentry/sentry-go ([#623](https://github.com/getsentry/sentry-go/pull/623))
|
||||||
|
|
||||||
|
## 0.20.0
|
||||||
|
|
||||||
|
The Sentry SDK team is happy to announce the immediate availability of Sentry Go SDK v0.20.0.
|
||||||
|
|
||||||
|
Note: this release has some **breaking changes**, which are listed below.
|
||||||
|
|
||||||
|
### Breaking Changes
|
||||||
|
|
||||||
|
- Remove the following methods: `Scope.SetTransaction()`, `Scope.Transaction()` ([#605](https://github.com/getsentry/sentry-go/pull/605))
|
||||||
|
|
||||||
|
Span.Name should be used instead to access the transaction's name.
|
||||||
|
|
||||||
|
For example, the following [`TracesSampler`](https://docs.sentry.io/platforms/go/configuration/sampling/#setting-a-sampling-function) function should be now written as follows:
|
||||||
|
|
||||||
|
**Before:**
|
||||||
|
```go
|
||||||
|
TracesSampler: func(ctx sentry.SamplingContext) float64 {
|
||||||
|
hub := sentry.GetHubFromContext(ctx.Span.Context())
|
||||||
|
if hub.Scope().Transaction() == "GET /health" {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
return 1
|
||||||
|
},
|
||||||
|
```
|
||||||
|
|
||||||
|
**After:**
|
||||||
|
```go
|
||||||
|
TracesSampler: func(ctx sentry.SamplingContext) float64 {
|
||||||
|
if ctx.Span.Name == "GET /health" {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
return 1
|
||||||
|
},
|
||||||
|
```
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
- Add `Span.SetContext()` method ([#599](https://github.com/getsentry/sentry-go/pull/599/))
|
||||||
|
- It is recommended to use it instead of `hub.Scope().SetContext` when setting or updating context on transactions.
|
||||||
|
- Add `DebugMeta` interface to `Event` and extend `Frame` structure with more fields ([#606](https://github.com/getsentry/sentry-go/pull/606))
|
||||||
|
- More about DebugMeta interface [here](https://develop.sentry.dev/sdk/event-payloads/debugmeta/).
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
- [otel] Fix missing OpenTelemetry context on some events ([#599](https://github.com/getsentry/sentry-go/pull/599), [#605](https://github.com/getsentry/sentry-go/pull/605))
|
||||||
|
- Fixes ([#596](https://github.com/getsentry/sentry-go/issues/596)).
|
||||||
|
- [otel] Better handling for HTTP span attributes ([#610](https://github.com/getsentry/sentry-go/pull/610))
|
||||||
|
|
||||||
|
### Misc
|
||||||
|
|
||||||
|
- Bump minimum versions: `github.com/kataras/iris/v12` to 12.2.0, `github.com/labstack/echo/v4` to v4.10.0 ([#595](https://github.com/getsentry/sentry-go/pull/595))
|
||||||
|
- Resolves [GO-2022-1144 / CVE-2022-41717](https://deps.dev/advisory/osv/GO-2022-1144), [GO-2023-1495 / CVE-2022-41721](https://deps.dev/advisory/osv/GO-2023-1495), [GO-2022-1059 / CVE-2022-32149](https://deps.dev/advisory/osv/GO-2022-1059).
|
||||||
|
- Bump `google.golang.org/protobuf` minimum required version to 1.29.1 ([#604](https://github.com/getsentry/sentry-go/pull/604))
|
||||||
|
- This fixes a potential denial of service issue ([CVE-2023-24535](https://github.com/advisories/GHSA-hw7c-3rfg-p46j)).
|
||||||
|
- Exclude the `otel` module when building in GOPATH mode ([#615](https://github.com/getsentry/sentry-go/pull/615))
|
||||||
|
|
||||||
|
## 0.19.0
|
||||||
|
|
||||||
|
The Sentry SDK team is happy to announce the immediate availability of Sentry Go SDK v0.19.0.
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
- Add support for exception mechanism metadata ([#564](https://github.com/getsentry/sentry-go/pull/564/))
|
||||||
|
- More about exception mechanisms [here](https://develop.sentry.dev/sdk/event-payloads/exception/#exception-mechanism).
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
- [otel] Use the correct "trace" context when sending a Sentry error ([#580](https://github.com/getsentry/sentry-go/pull/580/))
|
||||||
|
|
||||||
|
|
||||||
|
### Misc
|
||||||
|
- Drop support for Go 1.17, add support for Go 1.20 ([#563](https://github.com/getsentry/sentry-go/pull/563/))
|
||||||
|
- According to our policy, we're officially supporting the last three minor releases of Go.
|
||||||
|
- Switch repository license to MIT ([#583](https://github.com/getsentry/sentry-go/pull/583/))
|
||||||
|
- More about Sentry licensing [here](https://open.sentry.io/licensing/).
|
||||||
|
- Bump `golang.org/x/text` minimum required version to 0.3.8 ([#586](https://github.com/getsentry/sentry-go/pull/586))
|
||||||
|
- This fixes [CVE-2022-32149](https://github.com/advisories/GHSA-69ch-w2m2-3vjp) vulnerability.
|
||||||
|
|
||||||
|
## 0.18.0
|
||||||
|
|
||||||
|
The Sentry SDK team is happy to announce the immediate availability of Sentry Go SDK v0.18.0.
|
||||||
|
This release contains initial support for [OpenTelemetry](https://opentelemetry.io/) and various other bug fixes and improvements.
|
||||||
|
|
||||||
|
**Note**: This is the last release supporting Go 1.17.
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
- Initial support for [OpenTelemetry](https://opentelemetry.io/).
|
||||||
|
You can now send all your OpenTelemetry spans to Sentry.
|
||||||
|
|
||||||
|
Install the `otel` module
|
||||||
|
|
||||||
|
```bash
|
||||||
|
go get github.com/getsentry/sentry-go \
|
||||||
|
github.com/getsentry/sentry-go/otel
|
||||||
|
```
|
||||||
|
|
||||||
|
Configure the Sentry and OpenTelemetry SDKs
|
||||||
|
|
||||||
|
```go
|
||||||
|
import (
|
||||||
|
"go.opentelemetry.io/otel"
|
||||||
|
sdktrace "go.opentelemetry.io/otel/sdk/trace"
|
||||||
|
"github.com/getsentry/sentry-go"
|
||||||
|
"github.com/getsentry/sentry-go/otel"
|
||||||
|
// ...
|
||||||
|
)
|
||||||
|
|
||||||
|
// Initlaize the Sentry SDK
|
||||||
|
sentry.Init(sentry.ClientOptions{
|
||||||
|
Dsn: "__DSN__",
|
||||||
|
EnableTracing: true,
|
||||||
|
TracesSampleRate: 1.0,
|
||||||
|
})
|
||||||
|
|
||||||
|
// Set up the Sentry span processor
|
||||||
|
tp := sdktrace.NewTracerProvider(
|
||||||
|
sdktrace.WithSpanProcessor(sentryotel.NewSentrySpanProcessor()),
|
||||||
|
// ...
|
||||||
|
)
|
||||||
|
otel.SetTracerProvider(tp)
|
||||||
|
|
||||||
|
// Set up the Sentry propagator
|
||||||
|
otel.SetTextMapPropagator(sentryotel.NewSentryPropagator())
|
||||||
|
```
|
||||||
|
|
||||||
|
You can read more about using OpenTelemetry with Sentry in our [docs](https://docs.sentry.io/platforms/go/performance/instrumentation/opentelemetry/).
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
- Do not freeze the Dynamic Sampling Context when no Sentry values are present in the baggage header ([#532](https://github.com/getsentry/sentry-go/pull/532))
|
||||||
|
- Create a frozen Dynamic Sampling Context when calling `span.ToBaggage()` ([#566](https://github.com/getsentry/sentry-go/pull/566))
|
||||||
|
- Fix baggage parsing and encoding in vendored otel package ([#568](https://github.com/getsentry/sentry-go/pull/568))
|
||||||
|
|
||||||
|
### Misc
|
||||||
|
|
||||||
|
- Add `Span.SetDynamicSamplingContext()` ([#539](https://github.com/getsentry/sentry-go/pull/539/))
|
||||||
|
- Add various getters for `Dsn` ([#540](https://github.com/getsentry/sentry-go/pull/540))
|
||||||
|
- Add `SpanOption::SpanSampled` ([#546](https://github.com/getsentry/sentry-go/pull/546))
|
||||||
|
- Add `Span.SetData()` ([#542](https://github.com/getsentry/sentry-go/pull/542))
|
||||||
|
- Add `Span.IsTransaction()` ([#543](https://github.com/getsentry/sentry-go/pull/543))
|
||||||
|
- Add `Span.GetTransaction()` method ([#558](https://github.com/getsentry/sentry-go/pull/558))
|
||||||
|
|
||||||
|
## 0.17.0
|
||||||
|
|
||||||
|
The Sentry SDK team is happy to announce the immediate availability of Sentry Go SDK v0.17.0.
|
||||||
|
This release contains a new `BeforeSendTransaction` hook option and corrects two regressions introduced in `0.16.0`.
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
- Add `BeforeSendTransaction` hook to `ClientOptions` ([#517](https://github.com/getsentry/sentry-go/pull/517))
|
||||||
|
- Here's [an example](https://github.com/getsentry/sentry-go/blob/master/_examples/http/main.go#L56-L66) of how BeforeSendTransaction can be used to modify or drop transaction events.
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
- Do not crash in Span.Finish() when the Client is empty [#520](https://github.com/getsentry/sentry-go/pull/520)
|
||||||
|
- Fixes [#518](https://github.com/getsentry/sentry-go/issues/518)
|
||||||
|
- Attach non-PII/non-sensitive request headers to events when `ClientOptions.SendDefaultPii` is set to `false` ([#524](https://github.com/getsentry/sentry-go/pull/524))
|
||||||
|
- Fixes [#523](https://github.com/getsentry/sentry-go/issues/523)
|
||||||
|
|
||||||
|
### Misc
|
||||||
|
|
||||||
|
- Clarify how to handle logrus.Fatalf events ([#501](https://github.com/getsentry/sentry-go/pull/501/))
|
||||||
|
- Rename the `examples` directory to `_examples` ([#521](https://github.com/getsentry/sentry-go/pull/521))
|
||||||
|
- This removes an indirect dependency to `github.com/golang-jwt/jwt`
|
||||||
|
|
||||||
## 0.16.0
|
## 0.16.0
|
||||||
|
|
||||||
The Sentry SDK team is happy to announce the immediate availability of Sentry Go SDK v0.16.0.
|
The Sentry SDK team is happy to announce the immediate availability of Sentry Go SDK v0.16.0.
|
||||||
|
@ -40,7 +241,7 @@ Due to ongoing work towards a stable API for `v1.0.0`, we sadly had to include *
|
||||||
### Features
|
### Features
|
||||||
|
|
||||||
- Send errors logged with [Logrus](https://github.com/sirupsen/logrus) to Sentry.
|
- Send errors logged with [Logrus](https://github.com/sirupsen/logrus) to Sentry.
|
||||||
- Have a look at our [logrus examples](https://github.com/getsentry/sentry-go/blob/master/example/logrus/main.go) on how to use the integration.
|
- Have a look at our [logrus examples](https://github.com/getsentry/sentry-go/blob/master/_examples/logrus/main.go) on how to use the integration.
|
||||||
- Add support for Dynamic Sampling [#491](https://github.com/getsentry/sentry-go/pull/491)
|
- Add support for Dynamic Sampling [#491](https://github.com/getsentry/sentry-go/pull/491)
|
||||||
- You can read more about Dynamic Sampling in our [product docs](https://docs.sentry.io/product/data-management-settings/dynamic-sampling/).
|
- You can read more about Dynamic Sampling in our [product docs](https://docs.sentry.io/product/data-management-settings/dynamic-sampling/).
|
||||||
- Add detailed logging about the reason transactions are being dropped.
|
- Add detailed logging about the reason transactions are being dropped.
|
||||||
|
@ -128,7 +329,7 @@ There are no breaking changes and upgrading should be a smooth experience for al
|
||||||
_NOTE:_
|
_NOTE:_
|
||||||
This version introduces support for [Sentry's Performance Monitoring](https://docs.sentry.io/platforms/go/performance/).
|
This version introduces support for [Sentry's Performance Monitoring](https://docs.sentry.io/platforms/go/performance/).
|
||||||
The new tracing capabilities are beta, and we plan to expand them on future versions. Feedback is welcome, please open new issues on GitHub.
|
The new tracing capabilities are beta, and we plan to expand them on future versions. Feedback is welcome, please open new issues on GitHub.
|
||||||
The `sentryhttp` package got better API docs, an [updated usage example](https://github.com/getsentry/sentry-go/tree/master/example/http) and support for creating automatic transactions as part of Performance Monitoring.
|
The `sentryhttp` package got better API docs, an [updated usage example](https://github.com/getsentry/sentry-go/tree/master/_examples/http) and support for creating automatic transactions as part of Performance Monitoring.
|
||||||
|
|
||||||
## v0.8.0
|
## v0.8.0
|
||||||
|
|
||||||
|
|
|
@ -72,6 +72,8 @@ $ go test -race -coverprofile=coverage.txt -covermode=atomic && go tool cover -h
|
||||||
|
|
||||||
## Linting
|
## Linting
|
||||||
|
|
||||||
|
Lint with [`golangci-lint`](https://github.com/golangci/golangci-lint):
|
||||||
|
|
||||||
```console
|
```console
|
||||||
$ golangci-lint run
|
$ golangci-lint run
|
||||||
```
|
```
|
||||||
|
|
|
@ -1,9 +1,21 @@
|
||||||
Copyright (c) 2019 Sentry (https://sentry.io) and individual contributors.
|
MIT License
|
||||||
All rights reserved.
|
|
||||||
|
|
||||||
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
|
Copyright (c) 2019 Functional Software, Inc. dba Sentry
|
||||||
|
|
||||||
* Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
* 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.
|
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:
|
||||||
|
|
||||||
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 HOLDER 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.
|
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.
|
||||||
|
|
|
@ -0,0 +1,83 @@
|
||||||
|
.DEFAULT_GOAL := help
|
||||||
|
|
||||||
|
MKFILE_PATH := $(abspath $(lastword $(MAKEFILE_LIST)))
|
||||||
|
MKFILE_DIR := $(dir $(MKFILE_PATH))
|
||||||
|
ALL_GO_MOD_DIRS := $(shell find . -type f -name 'go.mod' -exec dirname {} \; | sort)
|
||||||
|
GO = go
|
||||||
|
TIMEOUT = 300
|
||||||
|
|
||||||
|
# Parse Makefile and display the help
|
||||||
|
help: ## Show help
|
||||||
|
@grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}'
|
||||||
|
.PHONY: help
|
||||||
|
|
||||||
|
build: ## Build everything
|
||||||
|
for dir in $(ALL_GO_MOD_DIRS); do \
|
||||||
|
cd "$${dir}"; \
|
||||||
|
echo ">>> Running 'go build' for module: $${dir}"; \
|
||||||
|
go build ./...; \
|
||||||
|
done;
|
||||||
|
.PHONY: build
|
||||||
|
|
||||||
|
### Tests (inspired by https://github.com/open-telemetry/opentelemetry-go/blob/main/Makefile)
|
||||||
|
TEST_TARGETS := test-short test-verbose test-race
|
||||||
|
test-race: ARGS=-race
|
||||||
|
test-short: ARGS=-short
|
||||||
|
test-verbose: ARGS=-v -race
|
||||||
|
$(TEST_TARGETS): test
|
||||||
|
test: $(ALL_GO_MOD_DIRS:%=test/%) ## Run tests
|
||||||
|
test/%: DIR=$*
|
||||||
|
test/%:
|
||||||
|
@echo ">>> Running tests for module: $(DIR)"
|
||||||
|
@# We use '-count=1' to disable test caching.
|
||||||
|
(cd $(DIR) && $(GO) test -count=1 -timeout $(TIMEOUT)s $(ARGS) ./...)
|
||||||
|
.PHONY: $(TEST_TARGETS) test
|
||||||
|
|
||||||
|
# Coverage
|
||||||
|
COVERAGE_MODE = atomic
|
||||||
|
COVERAGE_PROFILE = coverage.out
|
||||||
|
COVERAGE_REPORT_DIR = .coverage
|
||||||
|
COVERAGE_REPORT_DIR_ABS = "$(MKFILE_DIR)/$(COVERAGE_REPORT_DIR)"
|
||||||
|
$(COVERAGE_REPORT_DIR):
|
||||||
|
mkdir -p $(COVERAGE_REPORT_DIR)
|
||||||
|
clean-report-dir: $(COVERAGE_REPORT_DIR)
|
||||||
|
test $(COVERAGE_REPORT_DIR) && rm -f $(COVERAGE_REPORT_DIR)/*
|
||||||
|
test-coverage: $(COVERAGE_REPORT_DIR) clean-report-dir ## Test with coverage enabled
|
||||||
|
set -e ; \
|
||||||
|
for dir in $(ALL_GO_MOD_DIRS); do \
|
||||||
|
echo ">>> Running tests with coverage for module: $${dir}"; \
|
||||||
|
DIR_ABS=$$(python -c 'import os, sys; print(os.path.realpath(sys.argv[1]))' $${dir}) ; \
|
||||||
|
REPORT_NAME=$$(basename $${DIR_ABS}); \
|
||||||
|
(cd "$${dir}" && \
|
||||||
|
$(GO) test -count=1 -coverpkg=./... -covermode=$(COVERAGE_MODE) -coverprofile="$(COVERAGE_PROFILE)" ./... && \
|
||||||
|
cp $(COVERAGE_PROFILE) "$(COVERAGE_REPORT_DIR_ABS)/$${REPORT_NAME}_$(COVERAGE_PROFILE)" && \
|
||||||
|
$(GO) tool cover -html=$(COVERAGE_PROFILE) -o coverage.html); \
|
||||||
|
done;
|
||||||
|
.PHONY: test-coverage clean-report-dir
|
||||||
|
|
||||||
|
mod-tidy: ## Check go.mod tidiness
|
||||||
|
set -e ; \
|
||||||
|
for dir in $(ALL_GO_MOD_DIRS); do \
|
||||||
|
cd "$${dir}"; \
|
||||||
|
echo ">>> Running 'go mod tidy' for module: $${dir}"; \
|
||||||
|
go mod tidy -go=1.18 -compat=1.18; \
|
||||||
|
done; \
|
||||||
|
git diff --exit-code;
|
||||||
|
.PHONY: mod-tidy
|
||||||
|
|
||||||
|
vet: ## Run "go vet"
|
||||||
|
set -e ; \
|
||||||
|
for dir in $(ALL_GO_MOD_DIRS); do \
|
||||||
|
cd "$${dir}"; \
|
||||||
|
echo ">>> Running 'go vet' for module: $${dir}"; \
|
||||||
|
go vet ./...; \
|
||||||
|
done;
|
||||||
|
.PHONY: vet
|
||||||
|
|
||||||
|
lint: ## Lint (using "golangci-lint")
|
||||||
|
golangci-lint run
|
||||||
|
.PHONY: lint
|
||||||
|
|
||||||
|
fmt: ## Format all Go files
|
||||||
|
gofmt -l -w -s .
|
||||||
|
.PHONY: fmt
|
|
@ -42,7 +42,7 @@ though support for this configuration is best-effort.
|
||||||
$ go get github.com/getsentry/sentry-go@latest
|
$ go get github.com/getsentry/sentry-go@latest
|
||||||
```
|
```
|
||||||
|
|
||||||
Check out the [list of released versions](https://pkg.go.dev/github.com/getsentry/sentry-go?tab=versions).
|
Check out the [list of released versions](https://github.com/getsentry/sentry-go/releases).
|
||||||
|
|
||||||
## Configuration
|
## Configuration
|
||||||
|
|
||||||
|
@ -62,9 +62,9 @@ More on this in the [Configuration section of the official Sentry Go SDK documen
|
||||||
|
|
||||||
The SDK supports reporting errors and tracking application performance.
|
The SDK supports reporting errors and tracking application performance.
|
||||||
|
|
||||||
To get started, have a look at one of our [examples](example/):
|
To get started, have a look at one of our [examples](_examples/):
|
||||||
- [Basic error instrumentation](example/basic/main.go)
|
- [Basic error instrumentation](_examples/basic/main.go)
|
||||||
- [Error and tracing for HTTP servers](example/http/main.go)
|
- [Error and tracing for HTTP servers](_examples/http/main.go)
|
||||||
|
|
||||||
We also provide a [complete API reference](https://pkg.go.dev/github.com/getsentry/sentry-go).
|
We also provide a [complete API reference](https://pkg.go.dev/github.com/getsentry/sentry-go).
|
||||||
|
|
||||||
|
@ -96,7 +96,7 @@ checkout the official documentation:
|
||||||
## License
|
## License
|
||||||
|
|
||||||
Licensed under
|
Licensed under
|
||||||
[The 2-Clause BSD License](https://opensource.org/licenses/BSD-2-Clause), see
|
[The MIT License](https://opensource.org/licenses/mit/), see
|
||||||
[`LICENSE`](LICENSE).
|
[`LICENSE`](LICENSE).
|
||||||
|
|
||||||
## Community
|
## Community
|
||||||
|
|
|
@ -9,7 +9,6 @@ import (
|
||||||
"math/rand"
|
"math/rand"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
"reflect"
|
|
||||||
"sort"
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
|
@ -140,8 +139,10 @@ type ClientOptions struct {
|
||||||
SendDefaultPII bool
|
SendDefaultPII bool
|
||||||
// BeforeSend is called before error events are sent to Sentry.
|
// BeforeSend is called before error events are sent to Sentry.
|
||||||
// Use it to mutate the event or return nil to discard the event.
|
// Use it to mutate the event or return nil to discard the event.
|
||||||
// See EventProcessor if you need to mutate transactions.
|
|
||||||
BeforeSend func(event *Event, hint *EventHint) *Event
|
BeforeSend func(event *Event, hint *EventHint) *Event
|
||||||
|
// BeforeSendTransaction is called before transaction events are sent to Sentry.
|
||||||
|
// Use it to mutate the transaction or return nil to discard the transaction.
|
||||||
|
BeforeSendTransaction func(event *Event, hint *EventHint) *Event
|
||||||
// Before breadcrumb add callback.
|
// Before breadcrumb add callback.
|
||||||
BeforeBreadcrumb func(breadcrumb *Breadcrumb, hint *BreadcrumbHint) *Breadcrumb
|
BeforeBreadcrumb func(breadcrumb *Breadcrumb, hint *BreadcrumbHint) *Breadcrumb
|
||||||
// Integrations to be installed on the current Client, receives default
|
// Integrations to be installed on the current Client, receives default
|
||||||
|
@ -376,13 +377,13 @@ func (client Client) Options() ClientOptions {
|
||||||
|
|
||||||
// CaptureMessage captures an arbitrary message.
|
// CaptureMessage captures an arbitrary message.
|
||||||
func (client *Client) CaptureMessage(message string, hint *EventHint, scope EventModifier) *EventID {
|
func (client *Client) CaptureMessage(message string, hint *EventHint, scope EventModifier) *EventID {
|
||||||
event := client.eventFromMessage(message, LevelInfo)
|
event := client.EventFromMessage(message, LevelInfo)
|
||||||
return client.CaptureEvent(event, hint, scope)
|
return client.CaptureEvent(event, hint, scope)
|
||||||
}
|
}
|
||||||
|
|
||||||
// CaptureException captures an error.
|
// CaptureException captures an error.
|
||||||
func (client *Client) CaptureException(exception error, hint *EventHint, scope EventModifier) *EventID {
|
func (client *Client) CaptureException(exception error, hint *EventHint, scope EventModifier) *EventID {
|
||||||
event := client.eventFromException(exception, LevelError)
|
event := client.EventFromException(exception, LevelError)
|
||||||
return client.CaptureEvent(event, hint, scope)
|
return client.CaptureEvent(event, hint, scope)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -437,11 +438,11 @@ func (client *Client) RecoverWithContext(
|
||||||
var event *Event
|
var event *Event
|
||||||
switch err := err.(type) {
|
switch err := err.(type) {
|
||||||
case error:
|
case error:
|
||||||
event = client.eventFromException(err, LevelFatal)
|
event = client.EventFromException(err, LevelFatal)
|
||||||
case string:
|
case string:
|
||||||
event = client.eventFromMessage(err, LevelFatal)
|
event = client.EventFromMessage(err, LevelFatal)
|
||||||
default:
|
default:
|
||||||
event = client.eventFromMessage(fmt.Sprintf("%#v", err), LevelFatal)
|
event = client.EventFromMessage(fmt.Sprintf("%#v", err), LevelFatal)
|
||||||
}
|
}
|
||||||
return client.CaptureEvent(event, hint, scope)
|
return client.CaptureEvent(event, hint, scope)
|
||||||
}
|
}
|
||||||
|
@ -461,10 +462,11 @@ func (client *Client) Flush(timeout time.Duration) bool {
|
||||||
return client.Transport.Flush(timeout)
|
return client.Transport.Flush(timeout)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (client *Client) eventFromMessage(message string, level Level) *Event {
|
// EventFromMessage creates an event from the given message string.
|
||||||
|
func (client *Client) EventFromMessage(message string, level Level) *Event {
|
||||||
if message == "" {
|
if message == "" {
|
||||||
err := usageError{fmt.Errorf("%s called with empty message", callerFunctionName())}
|
err := usageError{fmt.Errorf("%s called with empty message", callerFunctionName())}
|
||||||
return client.eventFromException(err, level)
|
return client.EventFromException(err, level)
|
||||||
}
|
}
|
||||||
event := NewEvent()
|
event := NewEvent()
|
||||||
event.Level = level
|
event.Level = level
|
||||||
|
@ -481,41 +483,17 @@ func (client *Client) eventFromMessage(message string, level Level) *Event {
|
||||||
return event
|
return event
|
||||||
}
|
}
|
||||||
|
|
||||||
func (client *Client) eventFromException(exception error, level Level) *Event {
|
// EventFromException creates a new Sentry event from the given `error` instance.
|
||||||
|
func (client *Client) EventFromException(exception error, level Level) *Event {
|
||||||
|
event := NewEvent()
|
||||||
|
event.Level = level
|
||||||
|
|
||||||
err := exception
|
err := exception
|
||||||
if err == nil {
|
if err == nil {
|
||||||
err = usageError{fmt.Errorf("%s called with nil error", callerFunctionName())}
|
err = usageError{fmt.Errorf("%s called with nil error", callerFunctionName())}
|
||||||
}
|
}
|
||||||
|
|
||||||
event := NewEvent()
|
event.SetException(err, client.options.MaxErrorDepth)
|
||||||
event.Level = level
|
|
||||||
|
|
||||||
for i := 0; i < client.options.MaxErrorDepth && err != nil; i++ {
|
|
||||||
event.Exception = append(event.Exception, Exception{
|
|
||||||
Value: err.Error(),
|
|
||||||
Type: reflect.TypeOf(err).String(),
|
|
||||||
Stacktrace: ExtractStacktrace(err),
|
|
||||||
})
|
|
||||||
switch previous := err.(type) {
|
|
||||||
case interface{ Unwrap() error }:
|
|
||||||
err = previous.Unwrap()
|
|
||||||
case interface{ Cause() error }:
|
|
||||||
err = previous.Cause()
|
|
||||||
default:
|
|
||||||
err = nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add a trace of the current stack to the most recent error in a chain if
|
|
||||||
// it doesn't have a stack trace yet.
|
|
||||||
// We only add to the most recent error to avoid duplication and because the
|
|
||||||
// current stack is most likely unrelated to errors deeper in the chain.
|
|
||||||
if event.Exception[0].Stacktrace == nil {
|
|
||||||
event.Exception[0].Stacktrace = NewStacktrace()
|
|
||||||
}
|
|
||||||
|
|
||||||
// event.Exception should be sorted such that the most recent error is last.
|
|
||||||
reverse(event.Exception)
|
|
||||||
|
|
||||||
return event
|
return event
|
||||||
}
|
}
|
||||||
|
@ -570,11 +548,18 @@ func (client *Client) processEvent(event *Event, hint *EventHint, scope EventMod
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// As per spec, transactions do not go through BeforeSend.
|
// Apply beforeSend* processors
|
||||||
if event.Type != transactionType && options.BeforeSend != nil {
|
|
||||||
if hint == nil {
|
if hint == nil {
|
||||||
hint = &EventHint{}
|
hint = &EventHint{}
|
||||||
}
|
}
|
||||||
|
if event.Type == transactionType && options.BeforeSendTransaction != nil {
|
||||||
|
// Transaction events
|
||||||
|
if event = options.BeforeSendTransaction(event, hint); event == nil {
|
||||||
|
Logger.Println("Transaction dropped due to BeforeSendTransaction callback.")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
} else if event.Type != transactionType && options.BeforeSend != nil {
|
||||||
|
// All other events
|
||||||
if event = options.BeforeSend(event, hint); event == nil {
|
if event = options.BeforeSend(event, hint); event == nil {
|
||||||
Logger.Println("Event dropped due to BeforeSend callback.")
|
Logger.Println("Event dropped due to BeforeSend callback.")
|
||||||
return nil
|
return nil
|
||||||
|
|
|
@ -1,64 +1,6 @@
|
||||||
/*
|
/*
|
||||||
Package sentry is the official Sentry SDK for Go.
|
Package repository: https://github.com/getsentry/sentry-go/
|
||||||
|
|
||||||
Use it to report errors and track application performance through distributed
|
For more information about Sentry and SDK features, please have a look at the official documentation site: https://docs.sentry.io/platforms/go/
|
||||||
tracing.
|
|
||||||
|
|
||||||
For more information about Sentry and SDK features please have a look at the
|
|
||||||
documentation site https://docs.sentry.io/platforms/go/.
|
|
||||||
|
|
||||||
# Basic Usage
|
|
||||||
|
|
||||||
The first step is to initialize the SDK, providing at a minimum the DSN of your
|
|
||||||
Sentry project. This step is accomplished through a call to sentry.Init.
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
err := sentry.Init(...)
|
|
||||||
...
|
|
||||||
}
|
|
||||||
|
|
||||||
A more detailed yet simple example is available at
|
|
||||||
https://github.com/getsentry/sentry-go/blob/master/example/basic/main.go.
|
|
||||||
|
|
||||||
# Error Reporting
|
|
||||||
|
|
||||||
The Capture* functions report messages and errors to Sentry.
|
|
||||||
|
|
||||||
sentry.CaptureMessage(...)
|
|
||||||
sentry.CaptureException(...)
|
|
||||||
sentry.CaptureEvent(...)
|
|
||||||
|
|
||||||
Use similarly named functions in the Hub for concurrent programs like web
|
|
||||||
servers.
|
|
||||||
|
|
||||||
# Performance Monitoring
|
|
||||||
|
|
||||||
You can use Sentry to monitor your application's performance. More information
|
|
||||||
on the product page https://docs.sentry.io/product/performance/.
|
|
||||||
|
|
||||||
The StartSpan function creates new spans.
|
|
||||||
|
|
||||||
span := sentry.StartSpan(ctx, "operation")
|
|
||||||
...
|
|
||||||
span.Finish()
|
|
||||||
|
|
||||||
# Integrations
|
|
||||||
|
|
||||||
The SDK has support for several Go frameworks, available as subpackages.
|
|
||||||
|
|
||||||
# Getting Support
|
|
||||||
|
|
||||||
For paid Sentry.io accounts, head out to https://sentry.io/support.
|
|
||||||
|
|
||||||
For all users, support channels include:
|
|
||||||
|
|
||||||
Forum: https://forum.sentry.io
|
|
||||||
Discord: https://discord.gg/Ww9hbqr (#go channel)
|
|
||||||
|
|
||||||
If you found an issue with the SDK, please report through
|
|
||||||
https://github.com/getsentry/sentry-go/issues/new/choose.
|
|
||||||
|
|
||||||
For responsibly disclosing a security issue, please follow the steps in
|
|
||||||
https://sentry.io/security/#vulnerability-disclosure.
|
|
||||||
*/
|
*/
|
||||||
package sentry
|
package sentry
|
||||||
|
|
|
@ -145,19 +145,44 @@ func (dsn Dsn) String() string {
|
||||||
return url
|
return url
|
||||||
}
|
}
|
||||||
|
|
||||||
// StoreAPIURL returns the URL of the store endpoint of the project associated
|
// Get the scheme of the DSN.
|
||||||
// with the DSN.
|
func (dsn Dsn) GetScheme() string {
|
||||||
func (dsn Dsn) StoreAPIURL() *url.URL {
|
return string(dsn.scheme)
|
||||||
return dsn.getAPIURL("store")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// EnvelopeAPIURL returns the URL of the envelope endpoint of the project
|
// Get the public key of the DSN.
|
||||||
|
func (dsn Dsn) GetPublicKey() string {
|
||||||
|
return dsn.publicKey
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the secret key of the DSN.
|
||||||
|
func (dsn Dsn) GetSecretKey() string {
|
||||||
|
return dsn.secretKey
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the host of the DSN.
|
||||||
|
func (dsn Dsn) GetHost() string {
|
||||||
|
return dsn.host
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the port of the DSN.
|
||||||
|
func (dsn Dsn) GetPort() int {
|
||||||
|
return dsn.port
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the path of the DSN.
|
||||||
|
func (dsn Dsn) GetPath() string {
|
||||||
|
return dsn.path
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the project ID of the DSN.
|
||||||
|
func (dsn Dsn) GetProjectID() string {
|
||||||
|
return dsn.projectID
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetAPIURL returns the URL of the envelope endpoint of the project
|
||||||
// associated with the DSN.
|
// associated with the DSN.
|
||||||
func (dsn Dsn) EnvelopeAPIURL() *url.URL {
|
func (dsn Dsn) GetAPIURL() *url.URL {
|
||||||
return dsn.getAPIURL("envelope")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (dsn Dsn) getAPIURL(s string) *url.URL {
|
|
||||||
var rawURL string
|
var rawURL string
|
||||||
rawURL += fmt.Sprintf("%s://%s", dsn.scheme, dsn.host)
|
rawURL += fmt.Sprintf("%s://%s", dsn.scheme, dsn.host)
|
||||||
if dsn.port != dsn.scheme.defaultPort() {
|
if dsn.port != dsn.scheme.defaultPort() {
|
||||||
|
@ -166,7 +191,7 @@ func (dsn Dsn) getAPIURL(s string) *url.URL {
|
||||||
if dsn.path != "" {
|
if dsn.path != "" {
|
||||||
rawURL += dsn.path
|
rawURL += dsn.path
|
||||||
}
|
}
|
||||||
rawURL += fmt.Sprintf("/api/%s/%s/", dsn.projectID, s)
|
rawURL += fmt.Sprintf("/api/%s/%s/", dsn.projectID, "envelope")
|
||||||
parsedURL, _ := url.Parse(rawURL)
|
parsedURL, _ := url.Parse(rawURL)
|
||||||
return parsedURL
|
return parsedURL
|
||||||
}
|
}
|
||||||
|
|
|
@ -33,7 +33,8 @@ func DynamicSamplingContextFromHeader(header []byte) (DynamicSamplingContext, er
|
||||||
|
|
||||||
return DynamicSamplingContext{
|
return DynamicSamplingContext{
|
||||||
Entries: entries,
|
Entries: entries,
|
||||||
Frozen: true,
|
// If there's at least one Sentry value, we consider the DSC frozen
|
||||||
|
Frozen: len(entries) > 0,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -43,6 +44,14 @@ func DynamicSamplingContextFromTransaction(span *Span) DynamicSamplingContext {
|
||||||
hub := hubFromContext(span.Context())
|
hub := hubFromContext(span.Context())
|
||||||
scope := hub.Scope()
|
scope := hub.Scope()
|
||||||
client := hub.Client()
|
client := hub.Client()
|
||||||
|
|
||||||
|
if client == nil || scope == nil {
|
||||||
|
return DynamicSamplingContext{
|
||||||
|
Entries: map[string]string{},
|
||||||
|
Frozen: false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
options := client.Options()
|
options := client.Options()
|
||||||
|
|
||||||
if traceID := span.TraceID.String(); traceID != "" {
|
if traceID := span.TraceID.String(); traceID != "" {
|
||||||
|
@ -66,8 +75,8 @@ func DynamicSamplingContextFromTransaction(span *Span) DynamicSamplingContext {
|
||||||
|
|
||||||
// Only include the transaction name if it's of good quality (not empty and not SourceURL)
|
// Only include the transaction name if it's of good quality (not empty and not SourceURL)
|
||||||
if span.Source != "" && span.Source != SourceURL {
|
if span.Source != "" && span.Source != SourceURL {
|
||||||
if transactionName := scope.Transaction(); transactionName != "" {
|
if span.IsTransaction() {
|
||||||
entries["transaction"] = transactionName
|
entries["transaction"] = span.Name
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -6,6 +6,7 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"reflect"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
@ -16,6 +17,9 @@ import (
|
||||||
// transactionType is the type of a transaction event.
|
// transactionType is the type of a transaction event.
|
||||||
const transactionType = "transaction"
|
const transactionType = "transaction"
|
||||||
|
|
||||||
|
// eventType is the type of an error event.
|
||||||
|
const eventType = "event"
|
||||||
|
|
||||||
// Level marks the severity of the event.
|
// Level marks the severity of the event.
|
||||||
type Level string
|
type Level string
|
||||||
|
|
||||||
|
@ -169,8 +173,7 @@ func NewRequest(r *http.Request) *Request {
|
||||||
var env map[string]string
|
var env map[string]string
|
||||||
headers := map[string]string{}
|
headers := map[string]string{}
|
||||||
|
|
||||||
if client := CurrentHub().Client(); client != nil {
|
if client := CurrentHub().Client(); client != nil && client.Options().SendDefaultPII {
|
||||||
if client.Options().SendDefaultPII {
|
|
||||||
// We read only the first Cookie header because of the specification:
|
// We read only the first Cookie header because of the specification:
|
||||||
// https://tools.ietf.org/html/rfc6265#section-5.4
|
// https://tools.ietf.org/html/rfc6265#section-5.4
|
||||||
// When the user agent generates an HTTP request, the user agent MUST NOT
|
// When the user agent generates an HTTP request, the user agent MUST NOT
|
||||||
|
@ -184,7 +187,6 @@ func NewRequest(r *http.Request) *Request {
|
||||||
if addr, port, err := net.SplitHostPort(r.RemoteAddr); err == nil {
|
if addr, port, err := net.SplitHostPort(r.RemoteAddr); err == nil {
|
||||||
env = map[string]string{"REMOTE_ADDR": addr, "REMOTE_PORT": port}
|
env = map[string]string{"REMOTE_ADDR": addr, "REMOTE_PORT": port}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
sensitiveHeaders := getSensitiveHeaders()
|
sensitiveHeaders := getSensitiveHeaders()
|
||||||
for k, v := range r.Header {
|
for k, v := range r.Header {
|
||||||
|
@ -206,6 +208,22 @@ func NewRequest(r *http.Request) *Request {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Mechanism is the mechanism by which an exception was generated and handled.
|
||||||
|
type Mechanism struct {
|
||||||
|
Type string `json:"type,omitempty"`
|
||||||
|
Description string `json:"description,omitempty"`
|
||||||
|
HelpLink string `json:"help_link,omitempty"`
|
||||||
|
Handled *bool `json:"handled,omitempty"`
|
||||||
|
Data map[string]interface{} `json:"data,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetUnhandled indicates that the exception is an unhandled exception, i.e.
|
||||||
|
// from a panic.
|
||||||
|
func (m *Mechanism) SetUnhandled() {
|
||||||
|
h := false
|
||||||
|
m.Handled = &h
|
||||||
|
}
|
||||||
|
|
||||||
// Exception specifies an error that occurred.
|
// Exception specifies an error that occurred.
|
||||||
type Exception struct {
|
type Exception struct {
|
||||||
Type string `json:"type,omitempty"` // used as the main issue title
|
Type string `json:"type,omitempty"` // used as the main issue title
|
||||||
|
@ -213,6 +231,7 @@ type Exception struct {
|
||||||
Module string `json:"module,omitempty"`
|
Module string `json:"module,omitempty"`
|
||||||
ThreadID string `json:"thread_id,omitempty"`
|
ThreadID string `json:"thread_id,omitempty"`
|
||||||
Stacktrace *Stacktrace `json:"stacktrace,omitempty"`
|
Stacktrace *Stacktrace `json:"stacktrace,omitempty"`
|
||||||
|
Mechanism *Mechanism `json:"mechanism,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// SDKMetaData is a struct to stash data which is needed at some point in the SDK's event processing pipeline
|
// SDKMetaData is a struct to stash data which is needed at some point in the SDK's event processing pipeline
|
||||||
|
@ -226,6 +245,34 @@ type TransactionInfo struct {
|
||||||
Source TransactionSource `json:"source,omitempty"`
|
Source TransactionSource `json:"source,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// The DebugMeta interface is not used in Golang apps, but may be populated
|
||||||
|
// when proxying Events from other platforms, like iOS, Android, and the
|
||||||
|
// Web. (See: https://develop.sentry.dev/sdk/event-payloads/debugmeta/ ).
|
||||||
|
type DebugMeta struct {
|
||||||
|
SdkInfo *DebugMetaSdkInfo `json:"sdk_info,omitempty"`
|
||||||
|
Images []DebugMetaImage `json:"images,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type DebugMetaSdkInfo struct {
|
||||||
|
SdkName string `json:"sdk_name,omitempty"`
|
||||||
|
VersionMajor int `json:"version_major,omitempty"`
|
||||||
|
VersionMinor int `json:"version_minor,omitempty"`
|
||||||
|
VersionPatchlevel int `json:"version_patchlevel,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type DebugMetaImage struct {
|
||||||
|
Type string `json:"type,omitempty"` // all
|
||||||
|
ImageAddr string `json:"image_addr,omitempty"` // macho,elf,pe
|
||||||
|
ImageSize int `json:"image_size,omitempty"` // macho,elf,pe
|
||||||
|
DebugID string `json:"debug_id,omitempty"` // macho,elf,pe,wasm,sourcemap
|
||||||
|
DebugFile string `json:"debug_file,omitempty"` // macho,elf,pe,wasm
|
||||||
|
CodeID string `json:"code_id,omitempty"` // macho,elf,pe,wasm
|
||||||
|
CodeFile string `json:"code_file,omitempty"` // macho,elf,pe,wasm,sourcemap
|
||||||
|
ImageVmaddr string `json:"image_vmaddr,omitempty"` // macho,elf,pe
|
||||||
|
Arch string `json:"arch,omitempty"` // macho,elf,pe
|
||||||
|
UUID string `json:"uuid,omitempty"` // proguard
|
||||||
|
}
|
||||||
|
|
||||||
// EventID is a hexadecimal string representing a unique uuid4 for an Event.
|
// EventID is a hexadecimal string representing a unique uuid4 for an Event.
|
||||||
// An EventID must be 32 characters long, lowercase and not have any dashes.
|
// An EventID must be 32 characters long, lowercase and not have any dashes.
|
||||||
type EventID string
|
type EventID string
|
||||||
|
@ -256,6 +303,7 @@ type Event struct {
|
||||||
Modules map[string]string `json:"modules,omitempty"`
|
Modules map[string]string `json:"modules,omitempty"`
|
||||||
Request *Request `json:"request,omitempty"`
|
Request *Request `json:"request,omitempty"`
|
||||||
Exception []Exception `json:"exception,omitempty"`
|
Exception []Exception `json:"exception,omitempty"`
|
||||||
|
DebugMeta *DebugMeta `json:"debug_meta,omitempty"`
|
||||||
|
|
||||||
// The fields below are only relevant for transactions.
|
// The fields below are only relevant for transactions.
|
||||||
|
|
||||||
|
@ -269,6 +317,44 @@ type Event struct {
|
||||||
sdkMetaData SDKMetaData
|
sdkMetaData SDKMetaData
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SetException appends the unwrapped errors to the event's exception list.
|
||||||
|
//
|
||||||
|
// maxErrorDepth is the maximum depth of the error chain we will look
|
||||||
|
// into while unwrapping the errors.
|
||||||
|
func (e *Event) SetException(exception error, maxErrorDepth int) {
|
||||||
|
err := exception
|
||||||
|
if err == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := 0; i < maxErrorDepth && err != nil; i++ {
|
||||||
|
e.Exception = append(e.Exception, Exception{
|
||||||
|
Value: err.Error(),
|
||||||
|
Type: reflect.TypeOf(err).String(),
|
||||||
|
Stacktrace: ExtractStacktrace(err),
|
||||||
|
})
|
||||||
|
switch previous := err.(type) {
|
||||||
|
case interface{ Unwrap() error }:
|
||||||
|
err = previous.Unwrap()
|
||||||
|
case interface{ Cause() error }:
|
||||||
|
err = previous.Cause()
|
||||||
|
default:
|
||||||
|
err = nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add a trace of the current stack to the most recent error in a chain if
|
||||||
|
// it doesn't have a stack trace yet.
|
||||||
|
// We only add to the most recent error to avoid duplication and because the
|
||||||
|
// current stack is most likely unrelated to errors deeper in the chain.
|
||||||
|
if e.Exception[0].Stacktrace == nil {
|
||||||
|
e.Exception[0].Stacktrace = NewStacktrace()
|
||||||
|
}
|
||||||
|
|
||||||
|
// event.Exception should be sorted such that the most recent error is last.
|
||||||
|
reverse(e.Exception)
|
||||||
|
}
|
||||||
|
|
||||||
// TODO: Event.Contexts map[string]interface{} => map[string]EventContext,
|
// TODO: Event.Contexts map[string]interface{} => map[string]EventContext,
|
||||||
// to prevent accidentally storing T when we mean *T.
|
// to prevent accidentally storing T when we mean *T.
|
||||||
// For example, the TraceContext must be stored as *TraceContext to pick up the
|
// For example, the TraceContext must be stored as *TraceContext to pick up the
|
||||||
|
|
12
vendor/github.com/getsentry/sentry-go/internal/otel/baggage/README.md
generated
vendored
Normal file
12
vendor/github.com/getsentry/sentry-go/internal/otel/baggage/README.md
generated
vendored
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
## Why do we have this "otel/baggage" folder?
|
||||||
|
|
||||||
|
The root sentry-go SDK (namely, the Dynamic Sampling functionality) needs an implementation of the [baggage spec](https://www.w3.org/TR/baggage/).
|
||||||
|
For that reason, we've taken the existing baggage implementation from the [opentelemetry-go](https://github.com/open-telemetry/opentelemetry-go/) repository, and fixed a few things that in our opinion were violating the specification.
|
||||||
|
|
||||||
|
These issues are:
|
||||||
|
1. Baggage string value `one%20two` should be properly parsed as "one two"
|
||||||
|
1. Baggage string value `one+two` should be parsed as "one+two"
|
||||||
|
1. Go string value "one two" should be encoded as `one%20two` (percent encoding), and NOT as `one+two` (URL query encoding).
|
||||||
|
1. Go string value "1=1" might be encoded as `1=1`, because the spec says: "Note, value MAY contain any number of the equal sign (=) characters. Parsers MUST NOT assume that the equal sign is only used to separate key and value.". `1%3D1` is also valid, but to simplify the implementation we're not doing it.
|
||||||
|
|
||||||
|
Changes were made in this PR: https://github.com/getsentry/sentry-go/pull/568
|
|
@ -1,7 +1,6 @@
|
||||||
// This file was vendored in unmodified from
|
// Adapted from https://github.com/open-telemetry/opentelemetry-go/blob/c21b6b6bb31a2f74edd06e262f1690f3f6ea3d5c/baggage/baggage.go
|
||||||
// https://github.com/open-telemetry/opentelemetry-go/blob/c21b6b6bb31a2f74edd06e262f1690f3f6ea3d5c/baggage/baggage.go
|
|
||||||
//
|
//
|
||||||
// # Copyright The OpenTelemetry Authors
|
// Copyright The OpenTelemetry Authors
|
||||||
//
|
//
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
// you may not use this file except in compliance with the License.
|
// you may not use this file except in compliance with the License.
|
||||||
|
@ -23,6 +22,7 @@ import (
|
||||||
"net/url"
|
"net/url"
|
||||||
"regexp"
|
"regexp"
|
||||||
"strings"
|
"strings"
|
||||||
|
"unicode/utf8"
|
||||||
|
|
||||||
"github.com/getsentry/sentry-go/internal/otel/baggage/internal/baggage"
|
"github.com/getsentry/sentry-go/internal/otel/baggage/internal/baggage"
|
||||||
)
|
)
|
||||||
|
@ -267,11 +267,12 @@ func NewMember(key, value string, props ...Property) (Member, error) {
|
||||||
if err := m.validate(); err != nil {
|
if err := m.validate(); err != nil {
|
||||||
return newInvalidMember(), err
|
return newInvalidMember(), err
|
||||||
}
|
}
|
||||||
decodedValue, err := url.QueryUnescape(value)
|
//// NOTE(anton): I don't think we need to unescape here
|
||||||
if err != nil {
|
// decodedValue, err := url.PathUnescape(value)
|
||||||
return newInvalidMember(), fmt.Errorf("%w: %q", errInvalidValue, value)
|
// if err != nil {
|
||||||
}
|
// return newInvalidMember(), fmt.Errorf("%w: %q", errInvalidValue, value)
|
||||||
m.value = decodedValue
|
// }
|
||||||
|
// m.value = decodedValue
|
||||||
return m, nil
|
return m, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -315,17 +316,19 @@ func parseMember(member string) (Member, error) {
|
||||||
// "Leading and trailing whitespaces are allowed but MUST be trimmed
|
// "Leading and trailing whitespaces are allowed but MUST be trimmed
|
||||||
// when converting the header into a data structure."
|
// when converting the header into a data structure."
|
||||||
key = strings.TrimSpace(kv[0])
|
key = strings.TrimSpace(kv[0])
|
||||||
|
value = strings.TrimSpace(kv[1])
|
||||||
var err error
|
var err error
|
||||||
value, err = url.QueryUnescape(strings.TrimSpace(kv[1]))
|
|
||||||
if err != nil {
|
|
||||||
return newInvalidMember(), fmt.Errorf("%w: %q", err, value)
|
|
||||||
}
|
|
||||||
if !keyRe.MatchString(key) {
|
if !keyRe.MatchString(key) {
|
||||||
return newInvalidMember(), fmt.Errorf("%w: %q", errInvalidKey, key)
|
return newInvalidMember(), fmt.Errorf("%w: %q", errInvalidKey, key)
|
||||||
}
|
}
|
||||||
if !valueRe.MatchString(value) {
|
if !valueRe.MatchString(value) {
|
||||||
return newInvalidMember(), fmt.Errorf("%w: %q", errInvalidValue, value)
|
return newInvalidMember(), fmt.Errorf("%w: %q", errInvalidValue, value)
|
||||||
}
|
}
|
||||||
|
decodedValue, err := url.PathUnescape(value)
|
||||||
|
if err != nil {
|
||||||
|
return newInvalidMember(), fmt.Errorf("%w: %q", err, value)
|
||||||
|
}
|
||||||
|
value = decodedValue
|
||||||
default:
|
default:
|
||||||
// This should never happen unless a developer has changed the string
|
// This should never happen unless a developer has changed the string
|
||||||
// splitting somehow. Panic instead of failing silently and allowing
|
// splitting somehow. Panic instead of failing silently and allowing
|
||||||
|
@ -347,9 +350,10 @@ func (m Member) validate() error {
|
||||||
if !keyRe.MatchString(m.key) {
|
if !keyRe.MatchString(m.key) {
|
||||||
return fmt.Errorf("%w: %q", errInvalidKey, m.key)
|
return fmt.Errorf("%w: %q", errInvalidKey, m.key)
|
||||||
}
|
}
|
||||||
if !valueRe.MatchString(m.value) {
|
//// NOTE(anton): IMO it's too early to validate the value here.
|
||||||
return fmt.Errorf("%w: %q", errInvalidValue, m.value)
|
// if !valueRe.MatchString(m.value) {
|
||||||
}
|
// return fmt.Errorf("%w: %q", errInvalidValue, m.value)
|
||||||
|
// }
|
||||||
return m.properties.validate()
|
return m.properties.validate()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -366,13 +370,40 @@ func (m Member) Properties() []Property { return m.properties.Copy() }
|
||||||
// specification.
|
// specification.
|
||||||
func (m Member) String() string {
|
func (m Member) String() string {
|
||||||
// A key is just an ASCII string, but a value is URL encoded UTF-8.
|
// A key is just an ASCII string, but a value is URL encoded UTF-8.
|
||||||
s := fmt.Sprintf("%s%s%s", m.key, keyValueDelimiter, url.QueryEscape(m.value))
|
s := fmt.Sprintf("%s%s%s", m.key, keyValueDelimiter, percentEncodeValue(m.value))
|
||||||
if len(m.properties) > 0 {
|
if len(m.properties) > 0 {
|
||||||
s = fmt.Sprintf("%s%s%s", s, propertyDelimiter, m.properties.String())
|
s = fmt.Sprintf("%s%s%s", s, propertyDelimiter, m.properties.String())
|
||||||
}
|
}
|
||||||
return s
|
return s
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// percentEncodeValue encodes the baggage value, using percent-encoding for
|
||||||
|
// disallowed octets.
|
||||||
|
func percentEncodeValue(s string) string {
|
||||||
|
const upperhex = "0123456789ABCDEF"
|
||||||
|
var sb strings.Builder
|
||||||
|
|
||||||
|
for byteIndex, width := 0, 0; byteIndex < len(s); byteIndex += width {
|
||||||
|
runeValue, w := utf8.DecodeRuneInString(s[byteIndex:])
|
||||||
|
width = w
|
||||||
|
char := string(runeValue)
|
||||||
|
if valueRe.MatchString(char) && char != "%" {
|
||||||
|
// The character is returned as is, no need to percent-encode
|
||||||
|
sb.WriteString(char)
|
||||||
|
} else {
|
||||||
|
// We need to percent-encode each byte of the multi-octet character
|
||||||
|
for j := 0; j < width; j++ {
|
||||||
|
b := s[byteIndex+j]
|
||||||
|
sb.WriteByte('%')
|
||||||
|
// Bitwise operations are inspired by "net/url"
|
||||||
|
sb.WriteByte(upperhex[b>>4])
|
||||||
|
sb.WriteByte(upperhex[b&15])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return sb.String()
|
||||||
|
}
|
||||||
|
|
||||||
// Baggage is a list of baggage members representing the baggage-string as
|
// Baggage is a list of baggage members representing the baggage-string as
|
||||||
// defined by the W3C Baggage specification.
|
// defined by the W3C Baggage specification.
|
||||||
type Baggage struct { //nolint:golint
|
type Baggage struct { //nolint:golint
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
// This file was vendored in unmodified from
|
// Adapted from https://github.com/open-telemetry/opentelemetry-go/blob/c21b6b6bb31a2f74edd06e262f1690f3f6ea3d5c/internal/baggage/baggage.go
|
||||||
// https://github.com/open-telemetry/opentelemetry-go/blob/c21b6b6bb31a2f74edd06e262f1690f3f6ea3d5c/internal/baggage/baggage.go
|
|
||||||
//
|
//
|
||||||
// Copyright The OpenTelemetry Authors
|
// Copyright The OpenTelemetry Authors
|
||||||
//
|
//
|
||||||
|
|
|
@ -31,7 +31,6 @@ type Scope struct {
|
||||||
extra map[string]interface{}
|
extra map[string]interface{}
|
||||||
fingerprint []string
|
fingerprint []string
|
||||||
level Level
|
level Level
|
||||||
transaction string
|
|
||||||
request *http.Request
|
request *http.Request
|
||||||
// requestBody holds a reference to the original request.Body.
|
// requestBody holds a reference to the original request.Body.
|
||||||
requestBody interface {
|
requestBody interface {
|
||||||
|
@ -275,22 +274,6 @@ func (scope *Scope) SetLevel(level Level) {
|
||||||
scope.level = level
|
scope.level = level
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetTransaction sets the transaction name for the current transaction.
|
|
||||||
func (scope *Scope) SetTransaction(name string) {
|
|
||||||
scope.mu.Lock()
|
|
||||||
defer scope.mu.Unlock()
|
|
||||||
|
|
||||||
scope.transaction = name
|
|
||||||
}
|
|
||||||
|
|
||||||
// Transaction returns the transaction name for the current transaction.
|
|
||||||
func (scope *Scope) Transaction() (name string) {
|
|
||||||
scope.mu.RLock()
|
|
||||||
defer scope.mu.RUnlock()
|
|
||||||
|
|
||||||
return scope.transaction
|
|
||||||
}
|
|
||||||
|
|
||||||
// Clone returns a copy of the current scope with all data copied over.
|
// Clone returns a copy of the current scope with all data copied over.
|
||||||
func (scope *Scope) Clone() *Scope {
|
func (scope *Scope) Clone() *Scope {
|
||||||
scope.mu.RLock()
|
scope.mu.RLock()
|
||||||
|
@ -312,7 +295,6 @@ func (scope *Scope) Clone() *Scope {
|
||||||
clone.fingerprint = make([]string, len(scope.fingerprint))
|
clone.fingerprint = make([]string, len(scope.fingerprint))
|
||||||
copy(clone.fingerprint, scope.fingerprint)
|
copy(clone.fingerprint, scope.fingerprint)
|
||||||
clone.level = scope.level
|
clone.level = scope.level
|
||||||
clone.transaction = scope.transaction
|
|
||||||
clone.request = scope.request
|
clone.request = scope.request
|
||||||
clone.requestBody = scope.requestBody
|
clone.requestBody = scope.requestBody
|
||||||
clone.eventProcessors = scope.eventProcessors
|
clone.eventProcessors = scope.eventProcessors
|
||||||
|
@ -395,10 +377,6 @@ func (scope *Scope) ApplyToEvent(event *Event, hint *EventHint) *Event {
|
||||||
event.Level = scope.level
|
event.Level = scope.level
|
||||||
}
|
}
|
||||||
|
|
||||||
if scope.transaction != "" {
|
|
||||||
event.Transaction = scope.transaction
|
|
||||||
}
|
|
||||||
|
|
||||||
if event.Request == nil && scope.request != nil {
|
if event.Request == nil && scope.request != nil {
|
||||||
event.Request = NewRequest(scope.request)
|
event.Request = NewRequest(scope.request)
|
||||||
// NOTE: The SDK does not attempt to send partial request body data.
|
// NOTE: The SDK does not attempt to send partial request body data.
|
||||||
|
|
|
@ -9,7 +9,7 @@ import (
|
||||||
const Version = SDKVersion
|
const Version = SDKVersion
|
||||||
|
|
||||||
// Version is the version of the SDK.
|
// Version is the version of the SDK.
|
||||||
const SDKVersion = "0.16.0"
|
const SDKVersion = "0.21.0"
|
||||||
|
|
||||||
// The identifier of the SDK.
|
// The identifier of the SDK.
|
||||||
const SDKIdentifier = "sentry.go"
|
const SDKIdentifier = "sentry.go"
|
||||||
|
|
|
@ -167,12 +167,6 @@ type Frame struct {
|
||||||
// Module is, despite the name, the Sentry protocol equivalent of a Go
|
// Module is, despite the name, the Sentry protocol equivalent of a Go
|
||||||
// package's import path.
|
// package's import path.
|
||||||
Module string `json:"module,omitempty"`
|
Module string `json:"module,omitempty"`
|
||||||
// Package is not used for Go stack trace frames. In other platforms it
|
|
||||||
// refers to a container where the Module can be found. For example, a
|
|
||||||
// Java JAR, a .NET Assembly, or a native dynamic library.
|
|
||||||
// It exists for completeness, allowing the construction and reporting
|
|
||||||
// of custom event payloads.
|
|
||||||
Package string `json:"package,omitempty"`
|
|
||||||
Filename string `json:"filename,omitempty"`
|
Filename string `json:"filename,omitempty"`
|
||||||
AbsPath string `json:"abs_path,omitempty"`
|
AbsPath string `json:"abs_path,omitempty"`
|
||||||
Lineno int `json:"lineno,omitempty"`
|
Lineno int `json:"lineno,omitempty"`
|
||||||
|
@ -182,6 +176,18 @@ type Frame struct {
|
||||||
PostContext []string `json:"post_context,omitempty"`
|
PostContext []string `json:"post_context,omitempty"`
|
||||||
InApp bool `json:"in_app,omitempty"`
|
InApp bool `json:"in_app,omitempty"`
|
||||||
Vars map[string]interface{} `json:"vars,omitempty"`
|
Vars map[string]interface{} `json:"vars,omitempty"`
|
||||||
|
// Package and the below are not used for Go stack trace frames. In
|
||||||
|
// other platforms it refers to a container where the Module can be
|
||||||
|
// found. For example, a Java JAR, a .NET Assembly, or a native
|
||||||
|
// dynamic library. They exists for completeness, allowing the
|
||||||
|
// construction and reporting of custom event payloads.
|
||||||
|
Package string `json:"package,omitempty"`
|
||||||
|
InstructionAddr string `json:"instruction_addr,omitempty"`
|
||||||
|
AddrMode string `json:"addr_mode,omitempty"`
|
||||||
|
SymbolAddr string `json:"symbol_addr,omitempty"`
|
||||||
|
ImageAddr string `json:"image_addr,omitempty"`
|
||||||
|
Platform string `json:"platform,omitempty"`
|
||||||
|
StackStart bool `json:"stack_start,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewFrame assembles a stacktrace frame out of runtime.Frame.
|
// NewFrame assembles a stacktrace frame out of runtime.Frame.
|
||||||
|
|
|
@ -9,9 +9,15 @@ import (
|
||||||
"net/http"
|
"net/http"
|
||||||
"regexp"
|
"regexp"
|
||||||
"strings"
|
"strings"
|
||||||
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
SentryTraceHeader = "sentry-trace"
|
||||||
|
SentryBaggageHeader = "baggage"
|
||||||
|
)
|
||||||
|
|
||||||
// A Span is the building block of a Sentry transaction. Spans build up a tree
|
// A Span is the building block of a Sentry transaction. Spans build up a tree
|
||||||
// structure of timed operations. The span tree makes up a transaction event
|
// structure of timed operations. The span tree makes up a transaction event
|
||||||
// that is sent to Sentry when the root span is finished.
|
// that is sent to Sentry when the root span is finished.
|
||||||
|
@ -21,6 +27,7 @@ type Span struct { //nolint: maligned // prefer readability over optimal memory
|
||||||
TraceID TraceID `json:"trace_id"`
|
TraceID TraceID `json:"trace_id"`
|
||||||
SpanID SpanID `json:"span_id"`
|
SpanID SpanID `json:"span_id"`
|
||||||
ParentSpanID SpanID `json:"parent_span_id"`
|
ParentSpanID SpanID `json:"parent_span_id"`
|
||||||
|
Name string `json:"name,omitempty"`
|
||||||
Op string `json:"op,omitempty"`
|
Op string `json:"op,omitempty"`
|
||||||
Description string `json:"description,omitempty"`
|
Description string `json:"description,omitempty"`
|
||||||
Status SpanStatus `json:"status,omitempty"`
|
Status SpanStatus `json:"status,omitempty"`
|
||||||
|
@ -31,6 +38,8 @@ type Span struct { //nolint: maligned // prefer readability over optimal memory
|
||||||
Sampled Sampled `json:"-"`
|
Sampled Sampled `json:"-"`
|
||||||
Source TransactionSource `json:"-"`
|
Source TransactionSource `json:"-"`
|
||||||
|
|
||||||
|
// mu protects concurrent writes to map fields
|
||||||
|
mu sync.RWMutex
|
||||||
// sample rate the span was sampled with.
|
// sample rate the span was sampled with.
|
||||||
sampleRate float64
|
sampleRate float64
|
||||||
// ctx is the context where the span was started. Always non-nil.
|
// ctx is the context where the span was started. Always non-nil.
|
||||||
|
@ -47,6 +56,23 @@ type Span struct { //nolint: maligned // prefer readability over optimal memory
|
||||||
isTransaction bool
|
isTransaction bool
|
||||||
// recorder stores all spans in a transaction. Guaranteed to be non-nil.
|
// recorder stores all spans in a transaction. Guaranteed to be non-nil.
|
||||||
recorder *spanRecorder
|
recorder *spanRecorder
|
||||||
|
// span context, can only be set on transactions
|
||||||
|
contexts map[string]Context
|
||||||
|
}
|
||||||
|
|
||||||
|
// TraceParentContext describes the context of a (remote) parent span.
|
||||||
|
//
|
||||||
|
// The context is normally extracted from a received "sentry-trace" header and
|
||||||
|
// used to initialize a new transaction.
|
||||||
|
//
|
||||||
|
// Note: the name might be not the best one. It was taken mostly to stay aligned
|
||||||
|
// with other SDKs, and it alludes to W3C "traceparent" header (https://www.w3.org/TR/trace-context/),
|
||||||
|
// which serves a similar purpose to "sentry-trace". We should eventually consider
|
||||||
|
// making this type internal-only and give it a better name.
|
||||||
|
type TraceParentContext struct {
|
||||||
|
TraceID TraceID
|
||||||
|
ParentSpanID SpanID
|
||||||
|
Sampled Sampled
|
||||||
}
|
}
|
||||||
|
|
||||||
// (*) Note on maligned:
|
// (*) Note on maligned:
|
||||||
|
@ -88,6 +114,7 @@ func StartSpan(ctx context.Context, operation string, options ...SpanOption) *Sp
|
||||||
parent: parent,
|
parent: parent,
|
||||||
isTransaction: !hasParent,
|
isTransaction: !hasParent,
|
||||||
}
|
}
|
||||||
|
|
||||||
if hasParent {
|
if hasParent {
|
||||||
span.TraceID = parent.TraceID
|
span.TraceID = parent.TraceID
|
||||||
} else {
|
} else {
|
||||||
|
@ -179,9 +206,6 @@ func (s *Span) Finish() {
|
||||||
// (see https://github.com/getsentry/sentry-python/blob/f6f3525f8812f609/sentry_sdk/tracing.py#L372)
|
// (see https://github.com/getsentry/sentry-python/blob/f6f3525f8812f609/sentry_sdk/tracing.py#L372)
|
||||||
|
|
||||||
hub := hubFromContext(s.ctx)
|
hub := hubFromContext(s.ctx)
|
||||||
if hub.Scope().Transaction() == "" {
|
|
||||||
Logger.Printf("Missing transaction name for span with op = %q", s.Op)
|
|
||||||
}
|
|
||||||
hub.CaptureEvent(event)
|
hub.CaptureEvent(event)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -200,12 +224,66 @@ func (s *Span) StartChild(operation string, options ...SpanOption) *Span {
|
||||||
// accessing the tags map directly as SetTag takes care of initializing the map
|
// accessing the tags map directly as SetTag takes care of initializing the map
|
||||||
// when necessary.
|
// when necessary.
|
||||||
func (s *Span) SetTag(name, value string) {
|
func (s *Span) SetTag(name, value string) {
|
||||||
|
s.mu.Lock()
|
||||||
|
defer s.mu.Unlock()
|
||||||
|
|
||||||
if s.Tags == nil {
|
if s.Tags == nil {
|
||||||
s.Tags = make(map[string]string)
|
s.Tags = make(map[string]string)
|
||||||
}
|
}
|
||||||
s.Tags[name] = value
|
s.Tags[name] = value
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SetData sets a data on the span. It is recommended to use SetData instead of
|
||||||
|
// accessing the data map directly as SetData takes care of initializing the map
|
||||||
|
// when necessary.
|
||||||
|
func (s *Span) SetData(name, value string) {
|
||||||
|
s.mu.Lock()
|
||||||
|
defer s.mu.Unlock()
|
||||||
|
|
||||||
|
if s.Data == nil {
|
||||||
|
s.Data = make(map[string]interface{})
|
||||||
|
}
|
||||||
|
s.Data[name] = value
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetContext sets a context on the span. It is recommended to use SetContext instead of
|
||||||
|
// accessing the contexts map directly as SetContext takes care of initializing the map
|
||||||
|
// when necessary.
|
||||||
|
func (s *Span) SetContext(key string, value Context) {
|
||||||
|
s.mu.Lock()
|
||||||
|
defer s.mu.Unlock()
|
||||||
|
|
||||||
|
if s.contexts == nil {
|
||||||
|
s.contexts = make(map[string]Context)
|
||||||
|
}
|
||||||
|
s.contexts[key] = value
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsTransaction checks if the given span is a transaction.
|
||||||
|
func (s *Span) IsTransaction() bool {
|
||||||
|
return s.isTransaction
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetTransaction returns the transaction that contains this span.
|
||||||
|
//
|
||||||
|
// For transaction spans it returns itself. For spans that were created manually
|
||||||
|
// the method returns "nil".
|
||||||
|
func (s *Span) GetTransaction() *Span {
|
||||||
|
spanRecorder := s.spanRecorder()
|
||||||
|
if spanRecorder == nil {
|
||||||
|
// This probably means that the Span was created manually (not via
|
||||||
|
// StartTransaction/StartSpan or StartChild).
|
||||||
|
// Return "nil" to indicate that it's not a normal situation.
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
recorderRoot := spanRecorder.root()
|
||||||
|
if recorderRoot == nil {
|
||||||
|
// Same as above: manually created Span.
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return recorderRoot
|
||||||
|
}
|
||||||
|
|
||||||
// TODO(tracing): maybe add shortcuts to get/set transaction name. Right now the
|
// TODO(tracing): maybe add shortcuts to get/set transaction name. Right now the
|
||||||
// transaction name is in the Scope, as it has existed there historically, prior
|
// transaction name is in the Scope, as it has existed there historically, prior
|
||||||
// to tracing.
|
// to tracing.
|
||||||
|
@ -215,8 +293,9 @@ func (s *Span) SetTag(name, value string) {
|
||||||
// func (s *Span) TransactionName() string
|
// func (s *Span) TransactionName() string
|
||||||
// func (s *Span) SetTransactionName(name string)
|
// func (s *Span) SetTransactionName(name string)
|
||||||
|
|
||||||
// ToSentryTrace returns the trace propagation value used with the sentry-trace
|
// ToSentryTrace returns the seralized TraceParentContext from a transaction/sapn.
|
||||||
// HTTP header.
|
// Use this function to propagate the TraceParentContext to a downstream SDK,
|
||||||
|
// either as the value of the "sentry-trace" HTTP header, or as an html "sentry-trace" meta tag.
|
||||||
func (s *Span) ToSentryTrace() string {
|
func (s *Span) ToSentryTrace() string {
|
||||||
// TODO(tracing): add instrumentation for outgoing HTTP requests using
|
// TODO(tracing): add instrumentation for outgoing HTTP requests using
|
||||||
// ToSentryTrace.
|
// ToSentryTrace.
|
||||||
|
@ -231,8 +310,29 @@ func (s *Span) ToSentryTrace() string {
|
||||||
return b.String()
|
return b.String()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ToBaggage returns the serialized DynamicSamplingContext from a transaction.
|
||||||
|
// Use this function to propagate the DynamicSamplingContext to a downstream SDK,
|
||||||
|
// either as the value of the "baggage" HTTP header, or as an html "baggage" meta tag.
|
||||||
func (s *Span) ToBaggage() string {
|
func (s *Span) ToBaggage() string {
|
||||||
return s.dynamicSamplingContext.String()
|
if containingTransaction := s.GetTransaction(); containingTransaction != nil {
|
||||||
|
// In case there is currently no frozen DynamicSamplingContext attached to the transaction,
|
||||||
|
// create one from the properties of the transaction.
|
||||||
|
if !s.dynamicSamplingContext.IsFrozen() {
|
||||||
|
// This will return a frozen DynamicSamplingContext.
|
||||||
|
s.dynamicSamplingContext = DynamicSamplingContextFromTransaction(containingTransaction)
|
||||||
|
}
|
||||||
|
|
||||||
|
return containingTransaction.dynamicSamplingContext.String()
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetDynamicSamplingContext sets the given dynamic sampling context on the
|
||||||
|
// current transaction.
|
||||||
|
func (s *Span) SetDynamicSamplingContext(dsc DynamicSamplingContext) {
|
||||||
|
if s.isTransaction {
|
||||||
|
s.dynamicSamplingContext = dsc
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// sentryTracePattern matches either
|
// sentryTracePattern matches either
|
||||||
|
@ -248,12 +348,13 @@ var sentryTracePattern = regexp.MustCompile(`^([[:xdigit:]]{32})-([[:xdigit:]]{1
|
||||||
|
|
||||||
// updateFromSentryTrace parses a sentry-trace HTTP header (as returned by
|
// updateFromSentryTrace parses a sentry-trace HTTP header (as returned by
|
||||||
// ToSentryTrace) and updates fields of the span. If the header cannot be
|
// ToSentryTrace) and updates fields of the span. If the header cannot be
|
||||||
// recognized as valid, the span is left unchanged.
|
// recognized as valid, the span is left unchanged. The returned value indicates
|
||||||
func (s *Span) updateFromSentryTrace(header []byte) {
|
// whether the span was updated.
|
||||||
|
func (s *Span) updateFromSentryTrace(header []byte) (updated bool) {
|
||||||
m := sentryTracePattern.FindSubmatch(header)
|
m := sentryTracePattern.FindSubmatch(header)
|
||||||
if m == nil {
|
if m == nil {
|
||||||
// no match
|
// no match
|
||||||
return
|
return false
|
||||||
}
|
}
|
||||||
_, _ = hex.Decode(s.TraceID[:], m[1])
|
_, _ = hex.Decode(s.TraceID[:], m[1])
|
||||||
_, _ = hex.Decode(s.ParentSpanID[:], m[2])
|
_, _ = hex.Decode(s.ParentSpanID[:], m[2])
|
||||||
|
@ -265,6 +366,7 @@ func (s *Span) updateFromSentryTrace(header []byte) {
|
||||||
s.Sampled = SampledTrue
|
s.Sampled = SampledTrue
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Span) updateFromBaggage(header []byte) {
|
func (s *Span) updateFromBaggage(header []byte) {
|
||||||
|
@ -333,7 +435,11 @@ func (s *Span) sample() Sampled {
|
||||||
|
|
||||||
// #3 use TracesSampler from ClientOptions.
|
// #3 use TracesSampler from ClientOptions.
|
||||||
sampler := clientOptions.TracesSampler
|
sampler := clientOptions.TracesSampler
|
||||||
samplingContext := SamplingContext{Span: s, Parent: s.parent}
|
samplingContext := SamplingContext{
|
||||||
|
Span: s,
|
||||||
|
Parent: s.parent,
|
||||||
|
}
|
||||||
|
|
||||||
if sampler != nil {
|
if sampler != nil {
|
||||||
tracesSamplerSampleRate := sampler.Sample(samplingContext)
|
tracesSamplerSampleRate := sampler.Sample(samplingContext)
|
||||||
s.sampleRate = tracesSamplerSampleRate
|
s.sampleRate = tracesSamplerSampleRate
|
||||||
|
@ -384,10 +490,12 @@ func (s *Span) sample() Sampled {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Span) toEvent() *Event {
|
func (s *Span) toEvent() *Event {
|
||||||
|
s.mu.Lock()
|
||||||
|
defer s.mu.Unlock()
|
||||||
|
|
||||||
if !s.isTransaction {
|
if !s.isTransaction {
|
||||||
return nil // only transactions can be transformed into events
|
return nil // only transactions can be transformed into events
|
||||||
}
|
}
|
||||||
hub := hubFromContext(s.ctx)
|
|
||||||
|
|
||||||
children := s.recorder.children()
|
children := s.recorder.children()
|
||||||
finished := make([]*Span, 0, len(children))
|
finished := make([]*Span, 0, len(children))
|
||||||
|
@ -405,12 +513,16 @@ func (s *Span) toEvent() *Event {
|
||||||
s.dynamicSamplingContext = DynamicSamplingContextFromTransaction(s)
|
s.dynamicSamplingContext = DynamicSamplingContextFromTransaction(s)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
contexts := map[string]Context{}
|
||||||
|
for k, v := range s.contexts {
|
||||||
|
contexts[k] = v
|
||||||
|
}
|
||||||
|
contexts["trace"] = s.traceContext().Map()
|
||||||
|
|
||||||
return &Event{
|
return &Event{
|
||||||
Type: transactionType,
|
Type: transactionType,
|
||||||
Transaction: hub.Scope().Transaction(),
|
Transaction: s.Name,
|
||||||
Contexts: map[string]Context{
|
Contexts: contexts,
|
||||||
"trace": s.traceContext().Map(),
|
|
||||||
},
|
|
||||||
Tags: s.Tags,
|
Tags: s.Tags,
|
||||||
Extra: s.Data,
|
Extra: s.Data,
|
||||||
Timestamp: s.EndTime,
|
Timestamp: s.EndTime,
|
||||||
|
@ -439,6 +551,23 @@ func (s *Span) traceContext() *TraceContext {
|
||||||
// spanRecorder stores the span tree. Guaranteed to be non-nil.
|
// spanRecorder stores the span tree. Guaranteed to be non-nil.
|
||||||
func (s *Span) spanRecorder() *spanRecorder { return s.recorder }
|
func (s *Span) spanRecorder() *spanRecorder { return s.recorder }
|
||||||
|
|
||||||
|
// ParseTraceParentContext parses a sentry-trace header and builds a TraceParentContext from the
|
||||||
|
// parsed values. If the header was parsed correctly, the second returned argument
|
||||||
|
// ("valid") will be set to true, otherwise (e.g., empty or malformed header) it will
|
||||||
|
// be false.
|
||||||
|
func ParseTraceParentContext(header []byte) (traceParentContext TraceParentContext, valid bool) {
|
||||||
|
s := Span{}
|
||||||
|
updated := s.updateFromSentryTrace(header)
|
||||||
|
if !updated {
|
||||||
|
return TraceParentContext{}, false
|
||||||
|
}
|
||||||
|
return TraceParentContext{
|
||||||
|
TraceID: s.TraceID,
|
||||||
|
ParentSpanID: s.ParentSpanID,
|
||||||
|
Sampled: s.Sampled,
|
||||||
|
}, true
|
||||||
|
}
|
||||||
|
|
||||||
// TraceID identifies a trace.
|
// TraceID identifies a trace.
|
||||||
type TraceID [16]byte
|
type TraceID [16]byte
|
||||||
|
|
||||||
|
@ -658,35 +787,74 @@ type SpanOption func(s *Span)
|
||||||
// A span tree has a single transaction name, therefore using this option when
|
// A span tree has a single transaction name, therefore using this option when
|
||||||
// starting a span affects the span tree as a whole, potentially overwriting a
|
// starting a span affects the span tree as a whole, potentially overwriting a
|
||||||
// name set previously.
|
// name set previously.
|
||||||
|
//
|
||||||
|
// Deprecated: Use WithTransactionSource() instead.
|
||||||
func TransactionName(name string) SpanOption {
|
func TransactionName(name string) SpanOption {
|
||||||
|
return WithTransactionName(name)
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithTransactionName option sets the name of the current transaction.
|
||||||
|
//
|
||||||
|
// A span tree has a single transaction name, therefore using this option when
|
||||||
|
// starting a span affects the span tree as a whole, potentially overwriting a
|
||||||
|
// name set previously.
|
||||||
|
func WithTransactionName(name string) SpanOption {
|
||||||
return func(s *Span) {
|
return func(s *Span) {
|
||||||
hubFromContext(s.Context()).Scope().SetTransaction(name)
|
s.Name = name
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// OpName sets the operation name for a given span.
|
// OpName sets the operation name for a given span.
|
||||||
|
//
|
||||||
|
// Deprecated: Use WithOpName() instead.
|
||||||
func OpName(name string) SpanOption {
|
func OpName(name string) SpanOption {
|
||||||
|
return WithOpName(name)
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithOpName sets the operation name for a given span.
|
||||||
|
func WithOpName(name string) SpanOption {
|
||||||
return func(s *Span) {
|
return func(s *Span) {
|
||||||
s.Op = name
|
s.Op = name
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TransctionSource sets the source of the transaction name.
|
// TransctionSource sets the source of the transaction name.
|
||||||
|
//
|
||||||
|
// Deprecated: Use WithTransactionSource() instead.
|
||||||
func TransctionSource(source TransactionSource) SpanOption {
|
func TransctionSource(source TransactionSource) SpanOption {
|
||||||
|
return WithTransactionSource(source)
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithTransactionSource sets the source of the transaction name.
|
||||||
|
func WithTransactionSource(source TransactionSource) SpanOption {
|
||||||
return func(s *Span) {
|
return func(s *Span) {
|
||||||
s.Source = source
|
s.Source = source
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SpanSampled updates the sampling flag for a given span.
|
||||||
|
//
|
||||||
|
// Deprecated: Use WithSpanSampled() instead.
|
||||||
|
func SpanSampled(sampled Sampled) SpanOption {
|
||||||
|
return WithSpanSampled(sampled)
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithSpanSampled updates the sampling flag for a given span.
|
||||||
|
func WithSpanSampled(sampled Sampled) SpanOption {
|
||||||
|
return func(s *Span) {
|
||||||
|
s.Sampled = sampled
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// ContinueFromRequest returns a span option that updates the span to continue
|
// ContinueFromRequest returns a span option that updates the span to continue
|
||||||
// an existing trace. If it cannot detect an existing trace in the request, the
|
// an existing trace. If it cannot detect an existing trace in the request, the
|
||||||
// span will be left unchanged.
|
// span will be left unchanged.
|
||||||
//
|
//
|
||||||
// ContinueFromRequest is an alias for:
|
// ContinueFromRequest is an alias for:
|
||||||
//
|
//
|
||||||
// ContinueFromHeaders(r.Header.Get("sentry-trace"), r.Header.Get("baggage")).
|
// ContinueFromHeaders(r.Header.Get(SentryTraceHeader), r.Header.Get(SentryBaggageHeader)).
|
||||||
func ContinueFromRequest(r *http.Request) SpanOption {
|
func ContinueFromRequest(r *http.Request) SpanOption {
|
||||||
return ContinueFromHeaders(r.Header.Get("sentry-trace"), r.Header.Get("baggage"))
|
return ContinueFromHeaders(r.Header.Get(SentryTraceHeader), r.Header.Get(SentryBaggageHeader))
|
||||||
}
|
}
|
||||||
|
|
||||||
// ContinueFromHeaders returns a span option that updates the span to continue
|
// ContinueFromHeaders returns a span option that updates the span to continue
|
||||||
|
@ -699,9 +867,10 @@ func ContinueFromHeaders(trace, baggage string) SpanOption {
|
||||||
if baggage != "" {
|
if baggage != "" {
|
||||||
s.updateFromBaggage([]byte(baggage))
|
s.updateFromBaggage([]byte(baggage))
|
||||||
}
|
}
|
||||||
// In case a sentry-trace header is present but no baggage header,
|
|
||||||
// create an empty, frozen DynamicSamplingContext.
|
// In case a sentry-trace header is present but there are no sentry-related
|
||||||
if trace != "" && baggage == "" {
|
// values in the baggage, create an empty, frozen DynamicSamplingContext.
|
||||||
|
if trace != "" && !s.dynamicSamplingContext.HasEntries() {
|
||||||
s.dynamicSamplingContext = DynamicSamplingContext{
|
s.dynamicSamplingContext = DynamicSamplingContext{
|
||||||
Frozen: true,
|
Frozen: true,
|
||||||
}
|
}
|
||||||
|
@ -767,7 +936,7 @@ func StartTransaction(ctx context.Context, name string, options ...SpanOption) *
|
||||||
return currentTransaction
|
return currentTransaction
|
||||||
}
|
}
|
||||||
|
|
||||||
options = append(options, TransactionName(name))
|
options = append(options, WithTransactionName(name))
|
||||||
return StartSpan(
|
return StartSpan(
|
||||||
ctx,
|
ctx,
|
||||||
"",
|
"",
|
||||||
|
|
|
@ -94,7 +94,7 @@ func getRequestBodyFromEvent(event *Event) []byte {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func transactionEnvelopeFromBody(event *Event, dsn *Dsn, sentAt time.Time, body json.RawMessage) (*bytes.Buffer, error) {
|
func envelopeFromBody(event *Event, dsn *Dsn, sentAt time.Time, body json.RawMessage) (*bytes.Buffer, error) {
|
||||||
var b bytes.Buffer
|
var b bytes.Buffer
|
||||||
enc := json.NewEncoder(&b)
|
enc := json.NewEncoder(&b)
|
||||||
|
|
||||||
|
@ -127,12 +127,20 @@ func transactionEnvelopeFromBody(event *Event, dsn *Dsn, sentAt time.Time, body
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var itemType string
|
||||||
|
switch event.Type {
|
||||||
|
case transactionType:
|
||||||
|
itemType = transactionType
|
||||||
|
default:
|
||||||
|
itemType = eventType
|
||||||
|
}
|
||||||
|
|
||||||
// Item header
|
// Item header
|
||||||
err = enc.Encode(struct {
|
err = enc.Encode(struct {
|
||||||
Type string `json:"type"`
|
Type string `json:"type"`
|
||||||
Length int `json:"length"`
|
Length int `json:"length"`
|
||||||
}{
|
}{
|
||||||
Type: transactionType,
|
Type: itemType,
|
||||||
Length: len(body),
|
Length: len(body),
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -157,21 +165,14 @@ func getRequestFromEvent(event *Event, dsn *Dsn) (r *http.Request, err error) {
|
||||||
if body == nil {
|
if body == nil {
|
||||||
return nil, errors.New("event could not be marshaled")
|
return nil, errors.New("event could not be marshaled")
|
||||||
}
|
}
|
||||||
if event.Type == transactionType {
|
envelope, err := envelopeFromBody(event, dsn, time.Now(), body)
|
||||||
b, err := transactionEnvelopeFromBody(event, dsn, time.Now(), body)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return http.NewRequest(
|
return http.NewRequest(
|
||||||
http.MethodPost,
|
http.MethodPost,
|
||||||
dsn.EnvelopeAPIURL().String(),
|
dsn.GetAPIURL().String(),
|
||||||
b,
|
envelope,
|
||||||
)
|
|
||||||
}
|
|
||||||
return http.NewRequest(
|
|
||||||
http.MethodPost,
|
|
||||||
dsn.StoreAPIURL().String(),
|
|
||||||
bytes.NewReader(body),
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -6,7 +6,6 @@ linters:
|
||||||
disable-all: true
|
disable-all: true
|
||||||
enable:
|
enable:
|
||||||
- asciicheck
|
- asciicheck
|
||||||
- deadcode
|
|
||||||
- errcheck
|
- errcheck
|
||||||
- forcetypeassert
|
- forcetypeassert
|
||||||
- gocritic
|
- gocritic
|
||||||
|
@ -18,10 +17,8 @@ linters:
|
||||||
- misspell
|
- misspell
|
||||||
- revive
|
- revive
|
||||||
- staticcheck
|
- staticcheck
|
||||||
- structcheck
|
|
||||||
- typecheck
|
- typecheck
|
||||||
- unused
|
- unused
|
||||||
- varcheck
|
|
||||||
|
|
||||||
issues:
|
issues:
|
||||||
exclude-use-default: false
|
exclude-use-default: false
|
||||||
|
|
|
@ -20,35 +20,5 @@ package logr
|
||||||
// used whenever the caller is not interested in the logs. Logger instances
|
// used whenever the caller is not interested in the logs. Logger instances
|
||||||
// produced by this function always compare as equal.
|
// produced by this function always compare as equal.
|
||||||
func Discard() Logger {
|
func Discard() Logger {
|
||||||
return Logger{
|
return New(nil)
|
||||||
level: 0,
|
|
||||||
sink: discardLogSink{},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// discardLogSink is a LogSink that discards all messages.
|
|
||||||
type discardLogSink struct{}
|
|
||||||
|
|
||||||
// Verify that it actually implements the interface
|
|
||||||
var _ LogSink = discardLogSink{}
|
|
||||||
|
|
||||||
func (l discardLogSink) Init(RuntimeInfo) {
|
|
||||||
}
|
|
||||||
|
|
||||||
func (l discardLogSink) Enabled(int) bool {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
func (l discardLogSink) Info(int, string, ...interface{}) {
|
|
||||||
}
|
|
||||||
|
|
||||||
func (l discardLogSink) Error(error, string, ...interface{}) {
|
|
||||||
}
|
|
||||||
|
|
||||||
func (l discardLogSink) WithValues(...interface{}) LogSink {
|
|
||||||
return l
|
|
||||||
}
|
|
||||||
|
|
||||||
func (l discardLogSink) WithName(string) LogSink {
|
|
||||||
return l
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,13 +21,13 @@ limitations under the License.
|
||||||
// github.com/go-logr/logr.LogSink with output through an arbitrary
|
// github.com/go-logr/logr.LogSink with output through an arbitrary
|
||||||
// "write" function. See New and NewJSON for details.
|
// "write" function. See New and NewJSON for details.
|
||||||
//
|
//
|
||||||
// Custom LogSinks
|
// # Custom LogSinks
|
||||||
//
|
//
|
||||||
// For users who need more control, a funcr.Formatter can be embedded inside
|
// For users who need more control, a funcr.Formatter can be embedded inside
|
||||||
// your own custom LogSink implementation. This is useful when the LogSink
|
// your own custom LogSink implementation. This is useful when the LogSink
|
||||||
// needs to implement additional methods, for example.
|
// needs to implement additional methods, for example.
|
||||||
//
|
//
|
||||||
// Formatting
|
// # Formatting
|
||||||
//
|
//
|
||||||
// This will respect logr.Marshaler, fmt.Stringer, and error interfaces for
|
// This will respect logr.Marshaler, fmt.Stringer, and error interfaces for
|
||||||
// values which are being logged. When rendering a struct, funcr will use Go's
|
// values which are being logged. When rendering a struct, funcr will use Go's
|
||||||
|
@ -37,6 +37,7 @@ package funcr
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"encoding"
|
"encoding"
|
||||||
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"reflect"
|
"reflect"
|
||||||
|
@ -217,7 +218,7 @@ func newFormatter(opts Options, outfmt outputFormat) Formatter {
|
||||||
prefix: "",
|
prefix: "",
|
||||||
values: nil,
|
values: nil,
|
||||||
depth: 0,
|
depth: 0,
|
||||||
opts: opts,
|
opts: &opts,
|
||||||
}
|
}
|
||||||
return f
|
return f
|
||||||
}
|
}
|
||||||
|
@ -231,7 +232,7 @@ type Formatter struct {
|
||||||
values []interface{}
|
values []interface{}
|
||||||
valuesStr string
|
valuesStr string
|
||||||
depth int
|
depth int
|
||||||
opts Options
|
opts *Options
|
||||||
}
|
}
|
||||||
|
|
||||||
// outputFormat indicates which outputFormat to use.
|
// outputFormat indicates which outputFormat to use.
|
||||||
|
@ -447,6 +448,7 @@ func (f Formatter) prettyWithFlags(value interface{}, flags uint32, depth int) s
|
||||||
if flags&flagRawStruct == 0 {
|
if flags&flagRawStruct == 0 {
|
||||||
buf.WriteByte('{')
|
buf.WriteByte('{')
|
||||||
}
|
}
|
||||||
|
printComma := false // testing i>0 is not enough because of JSON omitted fields
|
||||||
for i := 0; i < t.NumField(); i++ {
|
for i := 0; i < t.NumField(); i++ {
|
||||||
fld := t.Field(i)
|
fld := t.Field(i)
|
||||||
if fld.PkgPath != "" {
|
if fld.PkgPath != "" {
|
||||||
|
@ -478,9 +480,10 @@ func (f Formatter) prettyWithFlags(value interface{}, flags uint32, depth int) s
|
||||||
if omitempty && isEmpty(v.Field(i)) {
|
if omitempty && isEmpty(v.Field(i)) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if i > 0 {
|
if printComma {
|
||||||
buf.WriteByte(',')
|
buf.WriteByte(',')
|
||||||
}
|
}
|
||||||
|
printComma = true // if we got here, we are rendering a field
|
||||||
if fld.Anonymous && fld.Type.Kind() == reflect.Struct && name == "" {
|
if fld.Anonymous && fld.Type.Kind() == reflect.Struct && name == "" {
|
||||||
buf.WriteString(f.prettyWithFlags(v.Field(i).Interface(), flags|flagRawStruct, depth+1))
|
buf.WriteString(f.prettyWithFlags(v.Field(i).Interface(), flags|flagRawStruct, depth+1))
|
||||||
continue
|
continue
|
||||||
|
@ -500,6 +503,20 @@ func (f Formatter) prettyWithFlags(value interface{}, flags uint32, depth int) s
|
||||||
}
|
}
|
||||||
return buf.String()
|
return buf.String()
|
||||||
case reflect.Slice, reflect.Array:
|
case reflect.Slice, reflect.Array:
|
||||||
|
// If this is outputing as JSON make sure this isn't really a json.RawMessage.
|
||||||
|
// If so just emit "as-is" and don't pretty it as that will just print
|
||||||
|
// it as [X,Y,Z,...] which isn't terribly useful vs the string form you really want.
|
||||||
|
if f.outputFormat == outputJSON {
|
||||||
|
if rm, ok := value.(json.RawMessage); ok {
|
||||||
|
// If it's empty make sure we emit an empty value as the array style would below.
|
||||||
|
if len(rm) > 0 {
|
||||||
|
buf.Write(rm)
|
||||||
|
} else {
|
||||||
|
buf.WriteString("null")
|
||||||
|
}
|
||||||
|
return buf.String()
|
||||||
|
}
|
||||||
|
}
|
||||||
buf.WriteByte('[')
|
buf.WriteByte('[')
|
||||||
for i := 0; i < v.Len(); i++ {
|
for i := 0; i < v.Len(); i++ {
|
||||||
if i > 0 {
|
if i > 0 {
|
||||||
|
|
|
@ -21,7 +21,7 @@ limitations under the License.
|
||||||
// to back that API. Packages in the Go ecosystem can depend on this package,
|
// to back that API. Packages in the Go ecosystem can depend on this package,
|
||||||
// while callers can implement logging with whatever backend is appropriate.
|
// while callers can implement logging with whatever backend is appropriate.
|
||||||
//
|
//
|
||||||
// Usage
|
// # Usage
|
||||||
//
|
//
|
||||||
// Logging is done using a Logger instance. Logger is a concrete type with
|
// Logging is done using a Logger instance. Logger is a concrete type with
|
||||||
// methods, which defers the actual logging to a LogSink interface. The main
|
// methods, which defers the actual logging to a LogSink interface. The main
|
||||||
|
@ -30,15 +30,19 @@ limitations under the License.
|
||||||
// "structured logging".
|
// "structured logging".
|
||||||
//
|
//
|
||||||
// With Go's standard log package, we might write:
|
// With Go's standard log package, we might write:
|
||||||
|
//
|
||||||
// log.Printf("setting target value %s", targetValue)
|
// log.Printf("setting target value %s", targetValue)
|
||||||
//
|
//
|
||||||
// With logr's structured logging, we'd write:
|
// With logr's structured logging, we'd write:
|
||||||
|
//
|
||||||
// logger.Info("setting target", "value", targetValue)
|
// logger.Info("setting target", "value", targetValue)
|
||||||
//
|
//
|
||||||
// Errors are much the same. Instead of:
|
// Errors are much the same. Instead of:
|
||||||
|
//
|
||||||
// log.Printf("failed to open the pod bay door for user %s: %v", user, err)
|
// log.Printf("failed to open the pod bay door for user %s: %v", user, err)
|
||||||
//
|
//
|
||||||
// We'd write:
|
// We'd write:
|
||||||
|
//
|
||||||
// logger.Error(err, "failed to open the pod bay door", "user", user)
|
// logger.Error(err, "failed to open the pod bay door", "user", user)
|
||||||
//
|
//
|
||||||
// Info() and Error() are very similar, but they are separate methods so that
|
// Info() and Error() are very similar, but they are separate methods so that
|
||||||
|
@ -47,7 +51,7 @@ limitations under the License.
|
||||||
// always logged, regardless of the current verbosity. If there is no error
|
// always logged, regardless of the current verbosity. If there is no error
|
||||||
// instance available, passing nil is valid.
|
// instance available, passing nil is valid.
|
||||||
//
|
//
|
||||||
// Verbosity
|
// # Verbosity
|
||||||
//
|
//
|
||||||
// Often we want to log information only when the application in "verbose
|
// Often we want to log information only when the application in "verbose
|
||||||
// mode". To write log lines that are more verbose, Logger has a V() method.
|
// mode". To write log lines that are more verbose, Logger has a V() method.
|
||||||
|
@ -58,14 +62,16 @@ limitations under the License.
|
||||||
// Error messages do not have a verbosity level and are always logged.
|
// Error messages do not have a verbosity level and are always logged.
|
||||||
//
|
//
|
||||||
// Where we might have written:
|
// Where we might have written:
|
||||||
|
//
|
||||||
// if flVerbose >= 2 {
|
// if flVerbose >= 2 {
|
||||||
// log.Printf("an unusual thing happened")
|
// log.Printf("an unusual thing happened")
|
||||||
// }
|
// }
|
||||||
//
|
//
|
||||||
// We can write:
|
// We can write:
|
||||||
|
//
|
||||||
// logger.V(2).Info("an unusual thing happened")
|
// logger.V(2).Info("an unusual thing happened")
|
||||||
//
|
//
|
||||||
// Logger Names
|
// # Logger Names
|
||||||
//
|
//
|
||||||
// Logger instances can have name strings so that all messages logged through
|
// Logger instances can have name strings so that all messages logged through
|
||||||
// that instance have additional context. For example, you might want to add
|
// that instance have additional context. For example, you might want to add
|
||||||
|
@ -82,17 +88,19 @@ limitations under the License.
|
||||||
// joining operation (e.g. whitespace, commas, periods, slashes, brackets,
|
// joining operation (e.g. whitespace, commas, periods, slashes, brackets,
|
||||||
// quotes, etc).
|
// quotes, etc).
|
||||||
//
|
//
|
||||||
// Saved Values
|
// # Saved Values
|
||||||
//
|
//
|
||||||
// Logger instances can store any number of key/value pairs, which will be
|
// Logger instances can store any number of key/value pairs, which will be
|
||||||
// logged alongside all messages logged through that instance. For example,
|
// logged alongside all messages logged through that instance. For example,
|
||||||
// you might want to create a Logger instance per managed object:
|
// you might want to create a Logger instance per managed object:
|
||||||
//
|
//
|
||||||
// With the standard log package, we might write:
|
// With the standard log package, we might write:
|
||||||
|
//
|
||||||
// log.Printf("decided to set field foo to value %q for object %s/%s",
|
// log.Printf("decided to set field foo to value %q for object %s/%s",
|
||||||
// targetValue, object.Namespace, object.Name)
|
// targetValue, object.Namespace, object.Name)
|
||||||
//
|
//
|
||||||
// With logr we'd write:
|
// With logr we'd write:
|
||||||
|
//
|
||||||
// // Elsewhere: set up the logger to log the object name.
|
// // Elsewhere: set up the logger to log the object name.
|
||||||
// obj.logger = mainLogger.WithValues(
|
// obj.logger = mainLogger.WithValues(
|
||||||
// "name", obj.name, "namespace", obj.namespace)
|
// "name", obj.name, "namespace", obj.namespace)
|
||||||
|
@ -100,7 +108,7 @@ limitations under the License.
|
||||||
// // later on...
|
// // later on...
|
||||||
// obj.logger.Info("setting foo", "value", targetValue)
|
// obj.logger.Info("setting foo", "value", targetValue)
|
||||||
//
|
//
|
||||||
// Best Practices
|
// # Best Practices
|
||||||
//
|
//
|
||||||
// Logger has very few hard rules, with the goal that LogSink implementations
|
// Logger has very few hard rules, with the goal that LogSink implementations
|
||||||
// might have a lot of freedom to differentiate. There are, however, some
|
// might have a lot of freedom to differentiate. There are, however, some
|
||||||
|
@ -124,15 +132,15 @@ limitations under the License.
|
||||||
// around. For cases where passing a logger is optional, a pointer to Logger
|
// around. For cases where passing a logger is optional, a pointer to Logger
|
||||||
// should be used.
|
// should be used.
|
||||||
//
|
//
|
||||||
// Key Naming Conventions
|
// # Key Naming Conventions
|
||||||
//
|
//
|
||||||
// Keys are not strictly required to conform to any specification or regex, but
|
// Keys are not strictly required to conform to any specification or regex, but
|
||||||
// it is recommended that they:
|
// it is recommended that they:
|
||||||
// * be human-readable and meaningful (not auto-generated or simple ordinals)
|
// - be human-readable and meaningful (not auto-generated or simple ordinals)
|
||||||
// * be constant (not dependent on input data)
|
// - be constant (not dependent on input data)
|
||||||
// * contain only printable characters
|
// - contain only printable characters
|
||||||
// * not contain whitespace or punctuation
|
// - not contain whitespace or punctuation
|
||||||
// * use lower case for simple keys and lowerCamelCase for more complex ones
|
// - use lower case for simple keys and lowerCamelCase for more complex ones
|
||||||
//
|
//
|
||||||
// These guidelines help ensure that log data is processed properly regardless
|
// These guidelines help ensure that log data is processed properly regardless
|
||||||
// of the log implementation. For example, log implementations will try to
|
// of the log implementation. For example, log implementations will try to
|
||||||
|
@ -141,24 +149,25 @@ limitations under the License.
|
||||||
// While users are generally free to use key names of their choice, it's
|
// While users are generally free to use key names of their choice, it's
|
||||||
// generally best to avoid using the following keys, as they're frequently used
|
// generally best to avoid using the following keys, as they're frequently used
|
||||||
// by implementations:
|
// by implementations:
|
||||||
// * "caller": the calling information (file/line) of a particular log line
|
// - "caller": the calling information (file/line) of a particular log line
|
||||||
// * "error": the underlying error value in the `Error` method
|
// - "error": the underlying error value in the `Error` method
|
||||||
// * "level": the log level
|
// - "level": the log level
|
||||||
// * "logger": the name of the associated logger
|
// - "logger": the name of the associated logger
|
||||||
// * "msg": the log message
|
// - "msg": the log message
|
||||||
// * "stacktrace": the stack trace associated with a particular log line or
|
// - "stacktrace": the stack trace associated with a particular log line or
|
||||||
// error (often from the `Error` message)
|
// error (often from the `Error` message)
|
||||||
// * "ts": the timestamp for a log line
|
// - "ts": the timestamp for a log line
|
||||||
//
|
//
|
||||||
// Implementations are encouraged to make use of these keys to represent the
|
// Implementations are encouraged to make use of these keys to represent the
|
||||||
// above concepts, when necessary (for example, in a pure-JSON output form, it
|
// above concepts, when necessary (for example, in a pure-JSON output form, it
|
||||||
// would be necessary to represent at least message and timestamp as ordinary
|
// would be necessary to represent at least message and timestamp as ordinary
|
||||||
// named values).
|
// named values).
|
||||||
//
|
//
|
||||||
// Break Glass
|
// # Break Glass
|
||||||
//
|
//
|
||||||
// Implementations may choose to give callers access to the underlying
|
// Implementations may choose to give callers access to the underlying
|
||||||
// logging implementation. The recommended pattern for this is:
|
// logging implementation. The recommended pattern for this is:
|
||||||
|
//
|
||||||
// // Underlier exposes access to the underlying logging implementation.
|
// // Underlier exposes access to the underlying logging implementation.
|
||||||
// // Since callers only have a logr.Logger, they have to know which
|
// // Since callers only have a logr.Logger, they have to know which
|
||||||
// // implementation is in use, so this interface is less of an abstraction
|
// // implementation is in use, so this interface is less of an abstraction
|
||||||
|
@ -168,8 +177,9 @@ limitations under the License.
|
||||||
// }
|
// }
|
||||||
//
|
//
|
||||||
// Logger grants access to the sink to enable type assertions like this:
|
// Logger grants access to the sink to enable type assertions like this:
|
||||||
|
//
|
||||||
// func DoSomethingWithImpl(log logr.Logger) {
|
// func DoSomethingWithImpl(log logr.Logger) {
|
||||||
// if underlier, ok := log.GetSink()(impl.Underlier) {
|
// if underlier, ok := log.GetSink().(impl.Underlier); ok {
|
||||||
// implLogger := underlier.GetUnderlying()
|
// implLogger := underlier.GetUnderlying()
|
||||||
// ...
|
// ...
|
||||||
// }
|
// }
|
||||||
|
@ -177,11 +187,12 @@ limitations under the License.
|
||||||
//
|
//
|
||||||
// Custom `With*` functions can be implemented by copying the complete
|
// Custom `With*` functions can be implemented by copying the complete
|
||||||
// Logger struct and replacing the sink in the copy:
|
// Logger struct and replacing the sink in the copy:
|
||||||
|
//
|
||||||
// // WithFooBar changes the foobar parameter in the log sink and returns a
|
// // WithFooBar changes the foobar parameter in the log sink and returns a
|
||||||
// // new logger with that modified sink. It does nothing for loggers where
|
// // new logger with that modified sink. It does nothing for loggers where
|
||||||
// // the sink doesn't support that parameter.
|
// // the sink doesn't support that parameter.
|
||||||
// func WithFoobar(log logr.Logger, foobar int) logr.Logger {
|
// func WithFoobar(log logr.Logger, foobar int) logr.Logger {
|
||||||
// if foobarLogSink, ok := log.GetSink()(FoobarSink); ok {
|
// if foobarLogSink, ok := log.GetSink().(FoobarSink); ok {
|
||||||
// log = log.WithSink(foobarLogSink.WithFooBar(foobar))
|
// log = log.WithSink(foobarLogSink.WithFooBar(foobar))
|
||||||
// }
|
// }
|
||||||
// return log
|
// return log
|
||||||
|
@ -201,11 +212,14 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
// New returns a new Logger instance. This is primarily used by libraries
|
// New returns a new Logger instance. This is primarily used by libraries
|
||||||
// implementing LogSink, rather than end users.
|
// implementing LogSink, rather than end users. Passing a nil sink will create
|
||||||
|
// a Logger which discards all log lines.
|
||||||
func New(sink LogSink) Logger {
|
func New(sink LogSink) Logger {
|
||||||
logger := Logger{}
|
logger := Logger{}
|
||||||
logger.setSink(sink)
|
logger.setSink(sink)
|
||||||
|
if sink != nil {
|
||||||
sink.Init(runtimeInfo)
|
sink.Init(runtimeInfo)
|
||||||
|
}
|
||||||
return logger
|
return logger
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -244,7 +258,7 @@ type Logger struct {
|
||||||
// Enabled tests whether this Logger is enabled. For example, commandline
|
// Enabled tests whether this Logger is enabled. For example, commandline
|
||||||
// flags might be used to set the logging verbosity and disable some info logs.
|
// flags might be used to set the logging verbosity and disable some info logs.
|
||||||
func (l Logger) Enabled() bool {
|
func (l Logger) Enabled() bool {
|
||||||
return l.sink.Enabled(l.level)
|
return l.sink != nil && l.sink.Enabled(l.level)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Info logs a non-error message with the given key/value pairs as context.
|
// Info logs a non-error message with the given key/value pairs as context.
|
||||||
|
@ -254,6 +268,9 @@ func (l Logger) Enabled() bool {
|
||||||
// information. The key/value pairs must alternate string keys and arbitrary
|
// information. The key/value pairs must alternate string keys and arbitrary
|
||||||
// values.
|
// values.
|
||||||
func (l Logger) Info(msg string, keysAndValues ...interface{}) {
|
func (l Logger) Info(msg string, keysAndValues ...interface{}) {
|
||||||
|
if l.sink == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
if l.Enabled() {
|
if l.Enabled() {
|
||||||
if withHelper, ok := l.sink.(CallStackHelperLogSink); ok {
|
if withHelper, ok := l.sink.(CallStackHelperLogSink); ok {
|
||||||
withHelper.GetCallStackHelper()()
|
withHelper.GetCallStackHelper()()
|
||||||
|
@ -273,6 +290,9 @@ func (l Logger) Info(msg string, keysAndValues ...interface{}) {
|
||||||
// triggered this log line, if present. The err parameter is optional
|
// triggered this log line, if present. The err parameter is optional
|
||||||
// and nil may be passed instead of an error instance.
|
// and nil may be passed instead of an error instance.
|
||||||
func (l Logger) Error(err error, msg string, keysAndValues ...interface{}) {
|
func (l Logger) Error(err error, msg string, keysAndValues ...interface{}) {
|
||||||
|
if l.sink == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
if withHelper, ok := l.sink.(CallStackHelperLogSink); ok {
|
if withHelper, ok := l.sink.(CallStackHelperLogSink); ok {
|
||||||
withHelper.GetCallStackHelper()()
|
withHelper.GetCallStackHelper()()
|
||||||
}
|
}
|
||||||
|
@ -284,6 +304,9 @@ func (l Logger) Error(err error, msg string, keysAndValues ...interface{}) {
|
||||||
// level means a log message is less important. Negative V-levels are treated
|
// level means a log message is less important. Negative V-levels are treated
|
||||||
// as 0.
|
// as 0.
|
||||||
func (l Logger) V(level int) Logger {
|
func (l Logger) V(level int) Logger {
|
||||||
|
if l.sink == nil {
|
||||||
|
return l
|
||||||
|
}
|
||||||
if level < 0 {
|
if level < 0 {
|
||||||
level = 0
|
level = 0
|
||||||
}
|
}
|
||||||
|
@ -294,6 +317,9 @@ func (l Logger) V(level int) Logger {
|
||||||
// WithValues returns a new Logger instance with additional key/value pairs.
|
// WithValues returns a new Logger instance with additional key/value pairs.
|
||||||
// See Info for documentation on how key/value pairs work.
|
// See Info for documentation on how key/value pairs work.
|
||||||
func (l Logger) WithValues(keysAndValues ...interface{}) Logger {
|
func (l Logger) WithValues(keysAndValues ...interface{}) Logger {
|
||||||
|
if l.sink == nil {
|
||||||
|
return l
|
||||||
|
}
|
||||||
l.setSink(l.sink.WithValues(keysAndValues...))
|
l.setSink(l.sink.WithValues(keysAndValues...))
|
||||||
return l
|
return l
|
||||||
}
|
}
|
||||||
|
@ -304,6 +330,9 @@ func (l Logger) WithValues(keysAndValues ...interface{}) Logger {
|
||||||
// contain only letters, digits, and hyphens (see the package documentation for
|
// contain only letters, digits, and hyphens (see the package documentation for
|
||||||
// more information).
|
// more information).
|
||||||
func (l Logger) WithName(name string) Logger {
|
func (l Logger) WithName(name string) Logger {
|
||||||
|
if l.sink == nil {
|
||||||
|
return l
|
||||||
|
}
|
||||||
l.setSink(l.sink.WithName(name))
|
l.setSink(l.sink.WithName(name))
|
||||||
return l
|
return l
|
||||||
}
|
}
|
||||||
|
@ -324,6 +353,9 @@ func (l Logger) WithName(name string) Logger {
|
||||||
// WithCallDepth(1) because it works with implementions that support the
|
// WithCallDepth(1) because it works with implementions that support the
|
||||||
// CallDepthLogSink and/or CallStackHelperLogSink interfaces.
|
// CallDepthLogSink and/or CallStackHelperLogSink interfaces.
|
||||||
func (l Logger) WithCallDepth(depth int) Logger {
|
func (l Logger) WithCallDepth(depth int) Logger {
|
||||||
|
if l.sink == nil {
|
||||||
|
return l
|
||||||
|
}
|
||||||
if withCallDepth, ok := l.sink.(CallDepthLogSink); ok {
|
if withCallDepth, ok := l.sink.(CallDepthLogSink); ok {
|
||||||
l.setSink(withCallDepth.WithCallDepth(depth))
|
l.setSink(withCallDepth.WithCallDepth(depth))
|
||||||
}
|
}
|
||||||
|
@ -345,6 +377,9 @@ func (l Logger) WithCallDepth(depth int) Logger {
|
||||||
// implementation does not support either of these, the original Logger will be
|
// implementation does not support either of these, the original Logger will be
|
||||||
// returned.
|
// returned.
|
||||||
func (l Logger) WithCallStackHelper() (func(), Logger) {
|
func (l Logger) WithCallStackHelper() (func(), Logger) {
|
||||||
|
if l.sink == nil {
|
||||||
|
return func() {}, l
|
||||||
|
}
|
||||||
var helper func()
|
var helper func()
|
||||||
if withCallDepth, ok := l.sink.(CallDepthLogSink); ok {
|
if withCallDepth, ok := l.sink.(CallDepthLogSink); ok {
|
||||||
l.setSink(withCallDepth.WithCallDepth(1))
|
l.setSink(withCallDepth.WithCallDepth(1))
|
||||||
|
@ -357,6 +392,11 @@ func (l Logger) WithCallStackHelper() (func(), Logger) {
|
||||||
return helper, l
|
return helper, l
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// IsZero returns true if this logger is an uninitialized zero value
|
||||||
|
func (l Logger) IsZero() bool {
|
||||||
|
return l.sink == nil
|
||||||
|
}
|
||||||
|
|
||||||
// contextKey is how we find Loggers in a context.Context.
|
// contextKey is how we find Loggers in a context.Context.
|
||||||
type contextKey struct{}
|
type contextKey struct{}
|
||||||
|
|
||||||
|
@ -442,7 +482,7 @@ type LogSink interface {
|
||||||
WithName(name string) LogSink
|
WithName(name string) LogSink
|
||||||
}
|
}
|
||||||
|
|
||||||
// CallDepthLogSink represents a Logger that knows how to climb the call stack
|
// CallDepthLogSink represents a LogSink that knows how to climb the call stack
|
||||||
// to identify the original call site and can offset the depth by a specified
|
// to identify the original call site and can offset the depth by a specified
|
||||||
// number of frames. This is useful for users who have helper functions
|
// number of frames. This is useful for users who have helper functions
|
||||||
// between the "real" call site and the actual calls to Logger methods.
|
// between the "real" call site and the actual calls to Logger methods.
|
||||||
|
@ -467,7 +507,7 @@ type CallDepthLogSink interface {
|
||||||
WithCallDepth(depth int) LogSink
|
WithCallDepth(depth int) LogSink
|
||||||
}
|
}
|
||||||
|
|
||||||
// CallStackHelperLogSink represents a Logger that knows how to climb
|
// CallStackHelperLogSink represents a LogSink that knows how to climb
|
||||||
// the call stack to identify the original call site and can skip
|
// the call stack to identify the original call site and can skip
|
||||||
// intermediate helper functions if they mark themselves as
|
// intermediate helper functions if they mark themselves as
|
||||||
// helper. Go's testing package uses that approach.
|
// helper. Go's testing package uses that approach.
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue