Merge pull request #2268 from AkihiroSuda/vendor-20200325
update vendor
This commit is contained in:
commit
8df45c891f
|
@ -7,7 +7,7 @@ go:
|
|||
|
||||
matrix:
|
||||
include:
|
||||
- go: 1.13.x
|
||||
- go: 1.14.x
|
||||
name: "verify-dependencies"
|
||||
script:
|
||||
- make verify-dependencies
|
||||
|
|
29
go.mod
29
go.mod
|
@ -3,25 +3,24 @@ module github.com/opencontainers/runc
|
|||
go 1.14
|
||||
|
||||
require (
|
||||
github.com/checkpoint-restore/go-criu v0.0.0-20181120144056-17b0214f6c48
|
||||
github.com/cilium/ebpf v0.0.0-20191025125908-95b36a581eed
|
||||
github.com/containerd/console v0.0.0-20181022165439-0650fd9eeb50
|
||||
github.com/checkpoint-restore/go-criu v0.0.0-20191125063657-fcdcd07065c5
|
||||
github.com/cilium/ebpf v0.0.0-20200319110858-a7172c01168f
|
||||
github.com/containerd/console v1.0.0
|
||||
github.com/coreos/go-systemd/v22 v22.0.0
|
||||
github.com/cyphar/filepath-securejoin v0.2.2
|
||||
github.com/docker/go-units v0.3.3
|
||||
github.com/docker/go-units v0.4.0
|
||||
github.com/godbus/dbus/v5 v5.0.3
|
||||
github.com/golang/protobuf v1.0.0
|
||||
github.com/moby/sys/mountinfo v0.1.0
|
||||
github.com/golang/protobuf v1.3.5
|
||||
github.com/moby/sys/mountinfo v0.1.3
|
||||
github.com/mrunalp/fileutils v0.0.0-20171103030105-7d4729fb3618
|
||||
github.com/opencontainers/runtime-spec v1.0.2-0.20190207185410-29686dbc5559
|
||||
github.com/opencontainers/selinux v1.3.3
|
||||
github.com/pkg/errors v0.8.1
|
||||
github.com/opencontainers/runtime-spec v1.0.2
|
||||
github.com/opencontainers/selinux v1.4.0
|
||||
github.com/pkg/errors v0.9.1
|
||||
github.com/seccomp/libseccomp-golang v0.9.1
|
||||
github.com/sirupsen/logrus v1.4.1
|
||||
github.com/sirupsen/logrus v1.5.0
|
||||
github.com/syndtr/gocapability v0.0.0-20180916011248-d98352740cb2
|
||||
github.com/urfave/cli v1.20.0
|
||||
github.com/vishvananda/netlink v0.0.0-20150820014904-1e2e08e8a2dc
|
||||
github.com/vishvananda/netns v0.0.0-20191106174202-0a2b9b5464df // indirect
|
||||
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e // indirect
|
||||
golang.org/x/sys v0.0.0-20191115151921-52ab43148777
|
||||
// NOTE: urfave/cli must be <= v1.22.1 due to a regression: https://github.com/urfave/cli/issues/1092
|
||||
github.com/urfave/cli v1.22.1
|
||||
github.com/vishvananda/netlink v1.1.0
|
||||
golang.org/x/sys v0.0.0-20200327173247-9dae0f8f5775
|
||||
)
|
||||
|
|
70
go.sum
70
go.sum
|
@ -1,53 +1,65 @@
|
|||
github.com/checkpoint-restore/go-criu v0.0.0-20181120144056-17b0214f6c48 h1:AQMF0Xixllgf29MKlx/TGEhRk7bEDX5kxz8Ui8lOvEs=
|
||||
github.com/checkpoint-restore/go-criu v0.0.0-20181120144056-17b0214f6c48/go.mod h1:TrMrLQfeENAPYPRsJuq3jsqdlRh3lvi6trTZJG8+tho=
|
||||
github.com/cilium/ebpf v0.0.0-20191025125908-95b36a581eed h1:/UgmF+cZTm9kp4uJ122y/9cVhczNJCgAgAeH2FfzPeg=
|
||||
github.com/cilium/ebpf v0.0.0-20191025125908-95b36a581eed/go.mod h1:MA5e5Lr8slmEg9bt0VpxxWqJlO4iwu3FBdHUzV7wQVg=
|
||||
github.com/containerd/console v0.0.0-20181022165439-0650fd9eeb50 h1:WMpHmC6AxwWb9hMqhudkqG7A/p14KiMnl6d3r1iUMjU=
|
||||
github.com/containerd/console v0.0.0-20181022165439-0650fd9eeb50/go.mod h1:Tj/on1eG8kiEhd0+fhSDzsPAFESxzBBvdyEgyryXffw=
|
||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||
github.com/checkpoint-restore/go-criu v0.0.0-20191125063657-fcdcd07065c5 h1:950diauOSoCNaQfvLE0WaFadyI/ilKIjb6jEUQnm+hE=
|
||||
github.com/checkpoint-restore/go-criu v0.0.0-20191125063657-fcdcd07065c5/go.mod h1:TrMrLQfeENAPYPRsJuq3jsqdlRh3lvi6trTZJG8+tho=
|
||||
github.com/cilium/ebpf v0.0.0-20200319110858-a7172c01168f h1:W1RQPz3nR8RxUw/Uqk71GU3JlZ7pNa1pXrHs98h0o9U=
|
||||
github.com/cilium/ebpf v0.0.0-20200319110858-a7172c01168f/go.mod h1:XT+cAw5wfvsodedcijoh1l9cf7v1x9FlFB/3VmF/O8s=
|
||||
github.com/containerd/console v1.0.0 h1:fU3UuQapBs+zLJu82NhR11Rif1ny2zfMMAyPJzSN5tQ=
|
||||
github.com/containerd/console v1.0.0/go.mod h1:8Pf4gM6VEbTNRIT26AyyU7hxdQU3MvAvxVI0sc00XBE=
|
||||
github.com/coreos/go-systemd/v22 v22.0.0 h1:XJIw/+VlJ+87J+doOxznsAWIdmWuViOVhkQamW5YV28=
|
||||
github.com/coreos/go-systemd/v22 v22.0.0/go.mod h1:xO0FLkIi5MaZafQlIrOotqXZ90ih+1atmu1JpKERPPk=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d h1:U+s90UTSYgptZMwQh2aRr3LuazLJIa+Pg3Kc1ylSYVY=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
|
||||
github.com/cyphar/filepath-securejoin v0.2.2 h1:jCwT2GTP+PY5nBz3c/YL5PAIbusElVrPujOBSCj8xRg=
|
||||
github.com/cyphar/filepath-securejoin v0.2.2/go.mod h1:FpkQEhXnPnOthhzymB7CGsFk2G9VLXONKD9G7QGMM+4=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/docker/go-units v0.3.3 h1:Xk8S3Xj5sLGlG5g67hJmYMmUgXv5N4PhkjJHHqrwnTk=
|
||||
github.com/docker/go-units v0.3.3/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
|
||||
github.com/docker/go-units v0.4.0 h1:3uh0PgVws3nIA0Q+MwDC8yjEPf9zjRfZZWXZYDct3Tw=
|
||||
github.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
|
||||
github.com/godbus/dbus/v5 v5.0.3 h1:ZqHaoEF7TBzh4jzPmqVhE/5A1z9of6orkAe5uHoAeME=
|
||||
github.com/godbus/dbus/v5 v5.0.3/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
|
||||
github.com/golang/protobuf v1.0.0 h1:lsek0oXi8iFE9L+EXARyHIjU5rlWIhhTkjDz3vHhWWQ=
|
||||
github.com/golang/protobuf v1.0.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.3.5 h1:F768QJ1E9tib+q5Sc8MkdJi1RxLTbRcTf8LJV56aRls=
|
||||
github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk=
|
||||
github.com/konsorten/go-windows-terminal-sequences v1.0.1 h1:mweAR1A6xJ3oS2pRaGiHgQ4OO8tzTaLawm8vnODuwDk=
|
||||
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||
github.com/moby/sys/mountinfo v0.1.0 h1:r8vMRbMAFEAfiNptYVokP+nfxPJzvRuia5e2vzXtENo=
|
||||
github.com/moby/sys/mountinfo v0.1.0/go.mod h1:w2t2Avltqx8vE7gX5l+QiBKxODu2TX0+Syr3h52Tw4o=
|
||||
github.com/moby/sys/mountinfo v0.1.3 h1:KIrhRO14+AkwKvG/g2yIpNMOUVZ02xNhOw8KY1WsLOI=
|
||||
github.com/moby/sys/mountinfo v0.1.3/go.mod h1:w2t2Avltqx8vE7gX5l+QiBKxODu2TX0+Syr3h52Tw4o=
|
||||
github.com/mrunalp/fileutils v0.0.0-20171103030105-7d4729fb3618 h1:7InQ7/zrOh6SlFjaXFubv0xX0HsuC9qJsdqm7bNQpYM=
|
||||
github.com/mrunalp/fileutils v0.0.0-20171103030105-7d4729fb3618/go.mod h1:x8F1gnqOkIEiO4rqoeEEEqQbo7HjGMTvyoq3gej4iT0=
|
||||
github.com/opencontainers/runtime-spec v1.0.2-0.20190207185410-29686dbc5559 h1:Cef96rKLuXxeGzERI/0ve9yAzIeTpx0qz9JKFDZALYw=
|
||||
github.com/opencontainers/runtime-spec v1.0.2-0.20190207185410-29686dbc5559/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0=
|
||||
github.com/opencontainers/selinux v1.3.3 h1:RX0wAeqtvVSYQcr017X3pFXPkLEtB6V4NjRD7gVQgg4=
|
||||
github.com/opencontainers/selinux v1.3.3/go.mod h1:yTcKuYAh6R95iDpefGLQaPaRwJFwyzAJufJyiTt7s0g=
|
||||
github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
|
||||
github.com/opencontainers/runtime-spec v1.0.2 h1:UfAcuLBJB9Coz72x1hgl8O5RVzTdNiaglX6v2DM6FI0=
|
||||
github.com/opencontainers/runtime-spec v1.0.2/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0=
|
||||
github.com/opencontainers/selinux v1.4.0 h1:cpiX/2wWIju/6My60T6/z9CxNG7c8xTQyEmA9fChpUo=
|
||||
github.com/opencontainers/selinux v1.4.0/go.mod h1:yTcKuYAh6R95iDpefGLQaPaRwJFwyzAJufJyiTt7s0g=
|
||||
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/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
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/russross/blackfriday/v2 v2.0.1 h1:lPqVAte+HuHNfhJ/0LC98ESWRz8afy9tM/0RK8m9o+Q=
|
||||
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||
github.com/seccomp/libseccomp-golang v0.9.1 h1:NJjM5DNFOs0s3kYE1WUOr6G8V97sdt46rlXTMfXGWBo=
|
||||
github.com/seccomp/libseccomp-golang v0.9.1/go.mod h1:GbW5+tmTXfcxTToHLXlScSlAvWlF4P2Ca7zGrPiEpWo=
|
||||
github.com/sirupsen/logrus v1.4.1 h1:GL2rEmy6nsikmW0r8opw9JIRScdMF5hA8cOYLH7In1k=
|
||||
github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q=
|
||||
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo=
|
||||
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
|
||||
github.com/sirupsen/logrus v1.5.0 h1:1N5EYkVAPEywqZRJd7cwnRtCb6xJx7NH3T3WUTF980Q=
|
||||
github.com/sirupsen/logrus v1.5.0/go.mod h1:+F7Ogzej0PZc/94MaYx/nvG9jOFMD2osvC3s+Squfpo=
|
||||
github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w=
|
||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||
github.com/syndtr/gocapability v0.0.0-20180916011248-d98352740cb2 h1:b6uOv7YOFK0TYG7HtkIgExQo+2RdLuwRft63jn2HWj8=
|
||||
github.com/syndtr/gocapability v0.0.0-20180916011248-d98352740cb2/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww=
|
||||
github.com/urfave/cli v1.20.0 h1:fDqGv3UG/4jbVl/QkFwEdddtEDjh/5Ov6X+0B/3bPaw=
|
||||
github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA=
|
||||
github.com/vishvananda/netlink v0.0.0-20150820014904-1e2e08e8a2dc h1:0HAHLwEY4k1VqaO1SzBi4XxT0KA06Cv+QW2LXknBk9g=
|
||||
github.com/vishvananda/netlink v0.0.0-20150820014904-1e2e08e8a2dc/go.mod h1:+SR5DhBJrl6ZM7CoCKvpw5BKroDKQ+PJqOg65H/2ktk=
|
||||
github.com/urfave/cli v1.22.1 h1:+mkCCcOFKPnCmVYVcURKps1Xe+3zP90gSYGNfRkjoIY=
|
||||
github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
|
||||
github.com/vishvananda/netlink v1.1.0 h1:1iyaYNBLmP6L0220aDnYQpo1QEV4t4hJ+xEEhhJH8j0=
|
||||
github.com/vishvananda/netlink v1.1.0/go.mod h1:cTgwzPIzzgDAYoQrMm0EdrjRUBkTqKYppBueQtXaqoE=
|
||||
github.com/vishvananda/netns v0.0.0-20191106174202-0a2b9b5464df h1:OviZH7qLw/7ZovXvuNyL3XQl8UFofeikI1NW1Gypu7k=
|
||||
github.com/vishvananda/netns v0.0.0-20191106174202-0a2b9b5464df/go.mod h1:JP3t17pCcGlemwknint6hfoeCVQrEMVwxRLRjXpq+BU=
|
||||
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e h1:vcxGaoTs7kV8m5Np9uUNQin4BrLOthgV7252N8V+FwY=
|
||||
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20191022100944-742c48ecaeb7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191115151921-52ab43148777 h1:wejkGHRTr38uaKRqECZlsCsJ1/TGxIyFbH32x5zUdu4=
|
||||
golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190606203320-7fc4e5ec1444/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191115151921-52ab43148777/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200124204421-9fbb57f87de9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200327173247-9dae0f8f5775 h1:TC0v2RSO1u2kn1ZugjrFXkRZAEaqMN/RW+OTZkBzmLE=
|
||||
golang.org/x/sys v0.0.0-20200327173247-9dae0f8f5775/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
|
|
|
@ -1,143 +1,90 @@
|
|||
package ebpf
|
||||
|
||||
import (
|
||||
"github.com/pkg/errors"
|
||||
"bufio"
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"syscall"
|
||||
|
||||
"github.com/cilium/ebpf/internal"
|
||||
|
||||
"golang.org/x/xerrors"
|
||||
)
|
||||
|
||||
// CollectionABI describes the interface of an eBPF collection.
|
||||
type CollectionABI struct {
|
||||
Maps map[string]*MapABI
|
||||
Programs map[string]*ProgramABI
|
||||
}
|
||||
|
||||
// CheckSpec verifies that all maps and programs mentioned
|
||||
// in the ABI are present in the spec.
|
||||
func (abi *CollectionABI) CheckSpec(cs *CollectionSpec) error {
|
||||
for name := range abi.Maps {
|
||||
if cs.Maps[name] == nil {
|
||||
return errors.Errorf("missing map %s", name)
|
||||
}
|
||||
}
|
||||
|
||||
for name := range abi.Programs {
|
||||
if cs.Programs[name] == nil {
|
||||
return errors.Errorf("missing program %s", name)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Check verifies that all items in a collection conform to this ABI.
|
||||
func (abi *CollectionABI) Check(coll *Collection) error {
|
||||
for name, mapABI := range abi.Maps {
|
||||
m := coll.Maps[name]
|
||||
if m == nil {
|
||||
return errors.Errorf("missing map %s", name)
|
||||
}
|
||||
if err := mapABI.Check(m); err != nil {
|
||||
return errors.Wrapf(err, "map %s", name)
|
||||
}
|
||||
}
|
||||
|
||||
for name, progABI := range abi.Programs {
|
||||
p := coll.Programs[name]
|
||||
if p == nil {
|
||||
return errors.Errorf("missing program %s", name)
|
||||
}
|
||||
if err := progABI.Check(p); err != nil {
|
||||
return errors.Wrapf(err, "program %s", name)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// MapABI describes a Map.
|
||||
//
|
||||
// Use it to assert that a Map matches what your code expects.
|
||||
// MapABI are the attributes of a Map which are available across all supported kernels.
|
||||
type MapABI struct {
|
||||
Type MapType
|
||||
KeySize uint32
|
||||
ValueSize uint32
|
||||
MaxEntries uint32
|
||||
InnerMap *MapABI
|
||||
Flags uint32
|
||||
}
|
||||
|
||||
func newMapABIFromSpec(spec *MapSpec) *MapABI {
|
||||
var inner *MapABI
|
||||
if spec.InnerMap != nil {
|
||||
inner = newMapABIFromSpec(spec.InnerMap)
|
||||
}
|
||||
|
||||
return &MapABI{
|
||||
spec.Type,
|
||||
spec.KeySize,
|
||||
spec.ValueSize,
|
||||
spec.MaxEntries,
|
||||
inner,
|
||||
spec.Flags,
|
||||
}
|
||||
}
|
||||
|
||||
func newMapABIFromFd(fd *bpfFD) (*MapABI, error) {
|
||||
func newMapABIFromFd(fd *internal.FD) (string, *MapABI, error) {
|
||||
info, err := bpfGetMapInfoByFD(fd)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
if xerrors.Is(err, syscall.EINVAL) {
|
||||
abi, err := newMapABIFromProc(fd)
|
||||
return "", abi, err
|
||||
}
|
||||
return "", nil, err
|
||||
}
|
||||
|
||||
mapType := MapType(info.mapType)
|
||||
if mapType == ArrayOfMaps || mapType == HashOfMaps {
|
||||
return nil, errors.New("can't get map info for nested maps")
|
||||
}
|
||||
|
||||
return &MapABI{
|
||||
mapType,
|
||||
return "", &MapABI{
|
||||
MapType(info.mapType),
|
||||
info.keySize,
|
||||
info.valueSize,
|
||||
info.maxEntries,
|
||||
nil,
|
||||
info.flags,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Check verifies that a Map conforms to the ABI.
|
||||
//
|
||||
// Members of ABI which have the zero value of their type are not checked.
|
||||
func (abi *MapABI) Check(m *Map) error {
|
||||
return abi.check(&m.abi)
|
||||
func newMapABIFromProc(fd *internal.FD) (*MapABI, error) {
|
||||
var abi MapABI
|
||||
err := scanFdInfo(fd, map[string]interface{}{
|
||||
"map_type": &abi.Type,
|
||||
"key_size": &abi.KeySize,
|
||||
"value_size": &abi.ValueSize,
|
||||
"max_entries": &abi.MaxEntries,
|
||||
"map_flags": &abi.Flags,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &abi, nil
|
||||
}
|
||||
|
||||
func (abi *MapABI) check(other *MapABI) error {
|
||||
if abi.Type != UnspecifiedMap && other.Type != abi.Type {
|
||||
return errors.Errorf("expected map type %s, have %s", abi.Type, other.Type)
|
||||
// Equal returns true if two ABIs have the same values.
|
||||
func (abi *MapABI) Equal(other *MapABI) bool {
|
||||
switch {
|
||||
case abi.Type != other.Type:
|
||||
return false
|
||||
case abi.KeySize != other.KeySize:
|
||||
return false
|
||||
case abi.ValueSize != other.ValueSize:
|
||||
return false
|
||||
case abi.MaxEntries != other.MaxEntries:
|
||||
return false
|
||||
case abi.Flags != other.Flags:
|
||||
return false
|
||||
default:
|
||||
return true
|
||||
}
|
||||
if err := checkUint32("key size", abi.KeySize, other.KeySize); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := checkUint32("value size", abi.ValueSize, other.ValueSize); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := checkUint32("max entries", abi.MaxEntries, other.MaxEntries); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if abi.InnerMap == nil {
|
||||
if abi.Type == ArrayOfMaps || abi.Type == HashOfMaps {
|
||||
return errors.New("missing inner map ABI")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
if other.InnerMap == nil {
|
||||
return errors.New("missing inner map")
|
||||
}
|
||||
|
||||
return errors.Wrap(abi.InnerMap.check(other.InnerMap), "inner map")
|
||||
}
|
||||
|
||||
// ProgramABI describes a Program.
|
||||
//
|
||||
// Use it to assert that a Program matches what your code expects.
|
||||
// ProgramABI are the attributes of a Program which are available across all supported kernels.
|
||||
type ProgramABI struct {
|
||||
Type ProgramType
|
||||
}
|
||||
|
@ -148,36 +95,113 @@ func newProgramABIFromSpec(spec *ProgramSpec) *ProgramABI {
|
|||
}
|
||||
}
|
||||
|
||||
func newProgramABIFromFd(fd *bpfFD) (*ProgramABI, error) {
|
||||
func newProgramABIFromFd(fd *internal.FD) (string, *ProgramABI, error) {
|
||||
info, err := bpfGetProgInfoByFD(fd)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
if xerrors.Is(err, syscall.EINVAL) {
|
||||
return newProgramABIFromProc(fd)
|
||||
}
|
||||
|
||||
return "", nil, err
|
||||
}
|
||||
|
||||
return newProgramABIFromInfo(info), nil
|
||||
}
|
||||
var name string
|
||||
if bpfName := internal.CString(info.name[:]); bpfName != "" {
|
||||
name = bpfName
|
||||
} else {
|
||||
name = internal.CString(info.tag[:])
|
||||
}
|
||||
|
||||
func newProgramABIFromInfo(info *bpfProgInfo) *ProgramABI {
|
||||
return &ProgramABI{
|
||||
return name, &ProgramABI{
|
||||
Type: ProgramType(info.progType),
|
||||
}
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Check verifies that a Program conforms to the ABI.
|
||||
//
|
||||
// Members which have the zero value of their type
|
||||
// are not checked.
|
||||
func (abi *ProgramABI) Check(prog *Program) error {
|
||||
if abi.Type != UnspecifiedProgram && prog.abi.Type != abi.Type {
|
||||
return errors.Errorf("expected program type %s, have %s", abi.Type, prog.abi.Type)
|
||||
func newProgramABIFromProc(fd *internal.FD) (string, *ProgramABI, error) {
|
||||
var (
|
||||
abi ProgramABI
|
||||
name string
|
||||
)
|
||||
|
||||
err := scanFdInfo(fd, map[string]interface{}{
|
||||
"prog_type": &abi.Type,
|
||||
"prog_tag": &name,
|
||||
})
|
||||
if xerrors.Is(err, errMissingFields) {
|
||||
return "", nil, &internal.UnsupportedFeatureError{
|
||||
Name: "reading ABI from /proc/self/fdinfo",
|
||||
MinimumVersion: internal.Version{4, 11, 0},
|
||||
}
|
||||
}
|
||||
if err != nil {
|
||||
return "", nil, err
|
||||
}
|
||||
|
||||
return name, &abi, nil
|
||||
}
|
||||
|
||||
func scanFdInfo(fd *internal.FD, fields map[string]interface{}) error {
|
||||
raw, err := fd.Value()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
fh, err := os.Open(fmt.Sprintf("/proc/self/fdinfo/%d", raw))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer fh.Close()
|
||||
|
||||
if err := scanFdInfoReader(fh, fields); err != nil {
|
||||
return xerrors.Errorf("%s: %w", fh.Name(), err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
var errMissingFields = xerrors.New("missing fields")
|
||||
|
||||
func scanFdInfoReader(r io.Reader, fields map[string]interface{}) error {
|
||||
var (
|
||||
scanner = bufio.NewScanner(r)
|
||||
scanned int
|
||||
)
|
||||
|
||||
for scanner.Scan() {
|
||||
parts := bytes.SplitN(scanner.Bytes(), []byte("\t"), 2)
|
||||
if len(parts) != 2 {
|
||||
continue
|
||||
}
|
||||
|
||||
name := bytes.TrimSuffix(parts[0], []byte(":"))
|
||||
field, ok := fields[string(name)]
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
if n, err := fmt.Fscanln(bytes.NewReader(parts[1]), field); err != nil || n != 1 {
|
||||
return xerrors.Errorf("can't parse field %s: %v", name, err)
|
||||
}
|
||||
|
||||
scanned++
|
||||
}
|
||||
|
||||
if err := scanner.Err(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if scanned != len(fields) {
|
||||
return errMissingFields
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func checkUint32(name string, want, have uint32) error {
|
||||
if want != 0 && have != want {
|
||||
return errors.Errorf("expected %s to be %d, have %d", name, want, have)
|
||||
// Equal returns true if two ABIs have the same values.
|
||||
func (abi *ProgramABI) Equal(other *ProgramABI) bool {
|
||||
switch {
|
||||
case abi.Type != other.Type:
|
||||
return false
|
||||
default:
|
||||
return true
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -7,7 +7,7 @@ import (
|
|||
"math"
|
||||
"strings"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"golang.org/x/xerrors"
|
||||
)
|
||||
|
||||
// InstructionSize is the size of a BPF instruction in bytes
|
||||
|
@ -51,10 +51,10 @@ func (ins *Instruction) Unmarshal(r io.Reader, bo binary.ByteOrder) (uint64, err
|
|||
var bi2 bpfInstruction
|
||||
if err := binary.Read(r, bo, &bi2); err != nil {
|
||||
// No Wrap, to avoid io.EOF clash
|
||||
return 0, errors.New("64bit immediate is missing second half")
|
||||
return 0, xerrors.New("64bit immediate is missing second half")
|
||||
}
|
||||
if bi2.OpCode != 0 || bi2.Offset != 0 || bi2.Registers != 0 {
|
||||
return 0, errors.New("64bit immediate has non-zero fields")
|
||||
return 0, xerrors.New("64bit immediate has non-zero fields")
|
||||
}
|
||||
ins.Constant = int64(uint64(uint32(bi2.Constant))<<32 | uint64(uint32(bi.Constant)))
|
||||
|
||||
|
@ -64,7 +64,7 @@ func (ins *Instruction) Unmarshal(r io.Reader, bo binary.ByteOrder) (uint64, err
|
|||
// Marshal encodes a BPF instruction.
|
||||
func (ins Instruction) Marshal(w io.Writer, bo binary.ByteOrder) (uint64, error) {
|
||||
if ins.OpCode == InvalidOpCode {
|
||||
return 0, errors.New("invalid opcode")
|
||||
return 0, xerrors.New("invalid opcode")
|
||||
}
|
||||
|
||||
isDWordLoad := ins.OpCode.isDWordLoad()
|
||||
|
@ -103,22 +103,52 @@ func (ins Instruction) Marshal(w io.Writer, bo binary.ByteOrder) (uint64, error)
|
|||
|
||||
// RewriteMapPtr changes an instruction to use a new map fd.
|
||||
//
|
||||
// Returns an error if the fd is invalid, or the instruction
|
||||
// is incorrect.
|
||||
// Returns an error if the instruction doesn't load a map.
|
||||
func (ins *Instruction) RewriteMapPtr(fd int) error {
|
||||
if !ins.OpCode.isDWordLoad() {
|
||||
return errors.Errorf("%s is not a 64 bit load", ins.OpCode)
|
||||
return xerrors.Errorf("%s is not a 64 bit load", ins.OpCode)
|
||||
}
|
||||
|
||||
if fd < 0 {
|
||||
return errors.New("invalid fd")
|
||||
if ins.Src != PseudoMapFD && ins.Src != PseudoMapValue {
|
||||
return xerrors.New("not a load from a map")
|
||||
}
|
||||
|
||||
ins.Src = R1
|
||||
ins.Constant = int64(fd)
|
||||
// Preserve the offset value for direct map loads.
|
||||
offset := uint64(ins.Constant) & (math.MaxUint32 << 32)
|
||||
rawFd := uint64(uint32(fd))
|
||||
ins.Constant = int64(offset | rawFd)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ins *Instruction) mapPtr() uint32 {
|
||||
return uint32(uint64(ins.Constant) & math.MaxUint32)
|
||||
}
|
||||
|
||||
// RewriteMapOffset changes the offset of a direct load from a map.
|
||||
//
|
||||
// Returns an error if the instruction is not a direct load.
|
||||
func (ins *Instruction) RewriteMapOffset(offset uint32) error {
|
||||
if !ins.OpCode.isDWordLoad() {
|
||||
return xerrors.Errorf("%s is not a 64 bit load", ins.OpCode)
|
||||
}
|
||||
|
||||
if ins.Src != PseudoMapValue {
|
||||
return xerrors.New("not a direct load from a map")
|
||||
}
|
||||
|
||||
fd := uint64(ins.Constant) & math.MaxUint32
|
||||
ins.Constant = int64(uint64(offset)<<32 | fd)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ins *Instruction) mapOffset() uint32 {
|
||||
return uint32(uint64(ins.Constant) >> 32)
|
||||
}
|
||||
|
||||
func (ins *Instruction) isLoadFromMap() bool {
|
||||
return ins.OpCode == LoadImmOp(DWord) && (ins.Src == PseudoMapFD || ins.Src == PseudoMapValue)
|
||||
}
|
||||
|
||||
// Format implements fmt.Formatter.
|
||||
func (ins Instruction) Format(f fmt.State, c rune) {
|
||||
if c != 'v' {
|
||||
|
@ -139,6 +169,19 @@ func (ins Instruction) Format(f fmt.State, c rune) {
|
|||
return
|
||||
}
|
||||
|
||||
if ins.isLoadFromMap() {
|
||||
fd := int32(ins.mapPtr())
|
||||
switch ins.Src {
|
||||
case PseudoMapFD:
|
||||
fmt.Fprintf(f, "LoadMapPtr dst: %s fd: %d", ins.Dst, fd)
|
||||
|
||||
case PseudoMapValue:
|
||||
fmt.Fprintf(f, "LoadMapValue dst: %s, fd: %d off: %d", ins.Dst, fd, ins.mapOffset())
|
||||
}
|
||||
|
||||
goto ref
|
||||
}
|
||||
|
||||
fmt.Fprintf(f, "%v ", op)
|
||||
switch cls := op.Class(); cls {
|
||||
case LdClass, LdXClass, StClass, StXClass:
|
||||
|
@ -166,7 +209,7 @@ func (ins Instruction) Format(f fmt.State, c rune) {
|
|||
case JumpClass:
|
||||
switch jop := op.JumpOp(); jop {
|
||||
case Call:
|
||||
if ins.Src == R1 {
|
||||
if ins.Src == PseudoCall {
|
||||
// bpf-to-bpf call
|
||||
fmt.Fprint(f, ins.Constant)
|
||||
} else {
|
||||
|
@ -183,6 +226,7 @@ func (ins Instruction) Format(f fmt.State, c rune) {
|
|||
}
|
||||
}
|
||||
|
||||
ref:
|
||||
if ins.Reference != "" {
|
||||
fmt.Fprintf(f, " <%s>", ins.Reference)
|
||||
}
|
||||
|
@ -200,7 +244,7 @@ func (insns Instructions) String() string {
|
|||
// Returns an error if the symbol isn't used, see IsUnreferencedSymbol.
|
||||
func (insns Instructions) RewriteMapPtr(symbol string, fd int) error {
|
||||
if symbol == "" {
|
||||
return errors.New("empty symbol")
|
||||
return xerrors.New("empty symbol")
|
||||
}
|
||||
|
||||
found := false
|
||||
|
@ -235,7 +279,7 @@ func (insns Instructions) SymbolOffsets() (map[string]int, error) {
|
|||
}
|
||||
|
||||
if _, ok := offsets[ins.Symbol]; ok {
|
||||
return nil, errors.Errorf("duplicate symbol %s", ins.Symbol)
|
||||
return nil, xerrors.Errorf("duplicate symbol %s", ins.Symbol)
|
||||
}
|
||||
|
||||
offsets[ins.Symbol] = i
|
||||
|
@ -273,7 +317,7 @@ func (insns Instructions) marshalledOffsets() (map[string]int, error) {
|
|||
}
|
||||
|
||||
if _, ok := symbols[ins.Symbol]; ok {
|
||||
return nil, errors.Errorf("duplicate symbol %s", ins.Symbol)
|
||||
return nil, xerrors.Errorf("duplicate symbol %s", ins.Symbol)
|
||||
}
|
||||
|
||||
symbols[ins.Symbol] = currentPos
|
||||
|
@ -350,11 +394,11 @@ func (insns Instructions) Marshal(w io.Writer, bo binary.ByteOrder) error {
|
|||
num := 0
|
||||
for i, ins := range insns {
|
||||
switch {
|
||||
case ins.OpCode.JumpOp() == Call && ins.Constant == -1:
|
||||
case ins.OpCode.JumpOp() == Call && ins.Src == PseudoCall && ins.Constant == -1:
|
||||
// Rewrite bpf to bpf call
|
||||
offset, ok := absoluteOffsets[ins.Reference]
|
||||
if !ok {
|
||||
return errors.Errorf("instruction %d: reference to missing symbol %s", i, ins.Reference)
|
||||
return xerrors.Errorf("instruction %d: reference to missing symbol %s", i, ins.Reference)
|
||||
}
|
||||
|
||||
ins.Constant = int64(offset - num - 1)
|
||||
|
@ -363,7 +407,7 @@ func (insns Instructions) Marshal(w io.Writer, bo binary.ByteOrder) error {
|
|||
// Rewrite jump to label
|
||||
offset, ok := absoluteOffsets[ins.Reference]
|
||||
if !ok {
|
||||
return errors.Errorf("instruction %d: reference to missing symbol %s", i, ins.Reference)
|
||||
return xerrors.Errorf("instruction %d: reference to missing symbol %s", i, ins.Reference)
|
||||
}
|
||||
|
||||
ins.Offset = int16(offset - num - 1)
|
||||
|
@ -371,7 +415,7 @@ func (insns Instructions) Marshal(w io.Writer, bo binary.ByteOrder) error {
|
|||
|
||||
n, err := ins.Marshal(w, bo)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "instruction %d", i)
|
||||
return xerrors.Errorf("instruction %d: %w", i, err)
|
||||
}
|
||||
|
||||
num += int(n / InstructionSize)
|
||||
|
|
|
@ -95,7 +95,7 @@ func (op JumpOp) Label(label string) Instruction {
|
|||
if op == Call {
|
||||
return Instruction{
|
||||
OpCode: OpCode(JumpClass).SetJumpOp(Call),
|
||||
Src: R1,
|
||||
Src: PseudoCall,
|
||||
Constant: -1,
|
||||
Reference: label,
|
||||
}
|
||||
|
|
|
@ -110,11 +110,26 @@ func LoadMapPtr(dst Register, fd int) Instruction {
|
|||
return Instruction{
|
||||
OpCode: LoadImmOp(DWord),
|
||||
Dst: dst,
|
||||
Src: R1,
|
||||
Src: PseudoMapFD,
|
||||
Constant: int64(fd),
|
||||
}
|
||||
}
|
||||
|
||||
// LoadMapValue stores a pointer to the value at a certain offset of a map.
|
||||
func LoadMapValue(dst Register, fd int, offset uint32) Instruction {
|
||||
if fd < 0 {
|
||||
return Instruction{OpCode: InvalidOpCode}
|
||||
}
|
||||
|
||||
fdAndOffset := (uint64(offset) << 32) | uint64(uint32(fd))
|
||||
return Instruction{
|
||||
OpCode: LoadImmOp(DWord),
|
||||
Dst: dst,
|
||||
Src: PseudoMapValue,
|
||||
Constant: int64(fdAndOffset),
|
||||
}
|
||||
}
|
||||
|
||||
// LoadIndOp returns the OpCode for loading a value of given size from an sk_buff.
|
||||
func LoadIndOp(size Size) OpCode {
|
||||
return OpCode(LdClass).SetMode(IndMode).SetSize(size)
|
||||
|
|
|
@ -33,6 +33,13 @@ const (
|
|||
RFP = R10
|
||||
)
|
||||
|
||||
// Pseudo registers used by 64bit loads and jumps
|
||||
const (
|
||||
PseudoMapFD = R1 // BPF_PSEUDO_MAP_FD
|
||||
PseudoMapValue = R2 // BPF_PSEUDO_MAP_VALUE
|
||||
PseudoCall = R1 // BPF_PSEUDO_CALL
|
||||
)
|
||||
|
||||
func (r Register) String() string {
|
||||
v := uint8(r)
|
||||
if v == 10 {
|
||||
|
|
|
@ -1,8 +1,12 @@
|
|||
package ebpf
|
||||
|
||||
import (
|
||||
"math"
|
||||
|
||||
"github.com/cilium/ebpf/asm"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/cilium/ebpf/internal"
|
||||
"github.com/cilium/ebpf/internal/btf"
|
||||
"golang.org/x/xerrors"
|
||||
)
|
||||
|
||||
// CollectionOptions control loading a collection into the kernel.
|
||||
|
@ -38,6 +42,89 @@ func (cs *CollectionSpec) Copy() *CollectionSpec {
|
|||
return &cpy
|
||||
}
|
||||
|
||||
// RewriteMaps replaces all references to specific maps.
|
||||
//
|
||||
// Use this function to use pre-existing maps instead of creating new ones
|
||||
// when calling NewCollection. Any named maps are removed from CollectionSpec.Maps.
|
||||
//
|
||||
// Returns an error if a named map isn't used in at least one program.
|
||||
func (cs *CollectionSpec) RewriteMaps(maps map[string]*Map) error {
|
||||
for symbol, m := range maps {
|
||||
// have we seen a program that uses this symbol / map
|
||||
seen := false
|
||||
fd := m.FD()
|
||||
for progName, progSpec := range cs.Programs {
|
||||
err := progSpec.Instructions.RewriteMapPtr(symbol, fd)
|
||||
|
||||
switch {
|
||||
case err == nil:
|
||||
seen = true
|
||||
|
||||
case asm.IsUnreferencedSymbol(err):
|
||||
// Not all programs need to use the map
|
||||
|
||||
default:
|
||||
return xerrors.Errorf("program %s: %w", progName, err)
|
||||
}
|
||||
}
|
||||
|
||||
if !seen {
|
||||
return xerrors.Errorf("map %s not referenced by any programs", symbol)
|
||||
}
|
||||
|
||||
// Prevent NewCollection from creating rewritten maps
|
||||
delete(cs.Maps, symbol)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// RewriteConstants replaces the value of multiple constants.
|
||||
//
|
||||
// The constant must be defined like so in the C program:
|
||||
//
|
||||
// static volatile const type foobar;
|
||||
// static volatile const type foobar = default;
|
||||
//
|
||||
// Replacement values must be of the same length as the C sizeof(type).
|
||||
// If necessary, they are marshalled according to the same rules as
|
||||
// map values.
|
||||
//
|
||||
// From Linux 5.5 the verifier will use constants to eliminate dead code.
|
||||
//
|
||||
// Returns an error if a constant doesn't exist.
|
||||
func (cs *CollectionSpec) RewriteConstants(consts map[string]interface{}) error {
|
||||
rodata := cs.Maps[".rodata"]
|
||||
if rodata == nil {
|
||||
return xerrors.New("missing .rodata section")
|
||||
}
|
||||
|
||||
if rodata.BTF == nil {
|
||||
return xerrors.New(".rodata section has no BTF")
|
||||
}
|
||||
|
||||
if n := len(rodata.Contents); n != 1 {
|
||||
return xerrors.Errorf("expected one key in .rodata, found %d", n)
|
||||
}
|
||||
|
||||
kv := rodata.Contents[0]
|
||||
value, ok := kv.Value.([]byte)
|
||||
if !ok {
|
||||
return xerrors.Errorf("first value in .rodata is %T not []byte", kv.Value)
|
||||
}
|
||||
|
||||
buf := make([]byte, len(value))
|
||||
copy(buf, value)
|
||||
|
||||
err := patchValue(buf, btf.MapValue(rodata.BTF), consts)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
rodata.Contents[0] = MapKV{kv.Key, buf}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Collection is a collection of Programs and Maps associated
|
||||
// with their symbols
|
||||
type Collection struct {
|
||||
|
@ -55,45 +142,103 @@ func NewCollection(spec *CollectionSpec) (*Collection, error) {
|
|||
// NewCollectionWithOptions creates a Collection from a specification.
|
||||
//
|
||||
// Only maps referenced by at least one of the programs are initialized.
|
||||
func NewCollectionWithOptions(spec *CollectionSpec, opts CollectionOptions) (*Collection, error) {
|
||||
maps := make(map[string]*Map)
|
||||
for mapName, mapSpec := range spec.Maps {
|
||||
m, err := NewMap(mapSpec)
|
||||
func NewCollectionWithOptions(spec *CollectionSpec, opts CollectionOptions) (coll *Collection, err error) {
|
||||
var (
|
||||
maps = make(map[string]*Map)
|
||||
progs = make(map[string]*Program)
|
||||
btfs = make(map[*btf.Spec]*btf.Handle)
|
||||
)
|
||||
|
||||
defer func() {
|
||||
for _, btf := range btfs {
|
||||
btf.Close()
|
||||
}
|
||||
|
||||
if err == nil {
|
||||
return
|
||||
}
|
||||
|
||||
for _, m := range maps {
|
||||
m.Close()
|
||||
}
|
||||
|
||||
for _, p := range progs {
|
||||
p.Close()
|
||||
}
|
||||
}()
|
||||
|
||||
loadBTF := func(spec *btf.Spec) (*btf.Handle, error) {
|
||||
if btfs[spec] != nil {
|
||||
return btfs[spec], nil
|
||||
}
|
||||
|
||||
handle, err := btf.NewHandle(spec)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "map %s", mapName)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
btfs[spec] = handle
|
||||
return handle, nil
|
||||
}
|
||||
|
||||
for mapName, mapSpec := range spec.Maps {
|
||||
var handle *btf.Handle
|
||||
if mapSpec.BTF != nil {
|
||||
handle, err = loadBTF(btf.MapSpec(mapSpec.BTF))
|
||||
if err != nil && !xerrors.Is(err, btf.ErrNotSupported) {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
m, err := newMapWithBTF(mapSpec, handle)
|
||||
if err != nil {
|
||||
return nil, xerrors.Errorf("map %s: %w", mapName, err)
|
||||
}
|
||||
maps[mapName] = m
|
||||
}
|
||||
|
||||
progs := make(map[string]*Program)
|
||||
for progName, origProgSpec := range spec.Programs {
|
||||
progSpec := origProgSpec.Copy()
|
||||
|
||||
// Rewrite any reference to a valid map.
|
||||
for i := range progSpec.Instructions {
|
||||
var (
|
||||
ins = &progSpec.Instructions[i]
|
||||
m = maps[ins.Reference]
|
||||
)
|
||||
ins := &progSpec.Instructions[i]
|
||||
|
||||
if ins.Reference == "" || m == nil {
|
||||
if ins.OpCode != asm.LoadImmOp(asm.DWord) || ins.Reference == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
if ins.Src == asm.R1 {
|
||||
if uint32(ins.Constant) != math.MaxUint32 {
|
||||
// Don't overwrite maps already rewritten, users can
|
||||
// rewrite programs in the spec themselves
|
||||
continue
|
||||
}
|
||||
|
||||
m := maps[ins.Reference]
|
||||
if m == nil {
|
||||
return nil, xerrors.Errorf("program %s: missing map %s", progName, ins.Reference)
|
||||
}
|
||||
|
||||
fd := m.FD()
|
||||
if fd < 0 {
|
||||
return nil, xerrors.Errorf("map %s: %w", ins.Reference, internal.ErrClosedFd)
|
||||
}
|
||||
if err := ins.RewriteMapPtr(m.FD()); err != nil {
|
||||
return nil, errors.Wrapf(err, "progam %s: map %s", progName, ins.Reference)
|
||||
return nil, xerrors.Errorf("progam %s: map %s: %w", progName, ins.Reference, err)
|
||||
}
|
||||
}
|
||||
|
||||
prog, err := NewProgramWithOptions(progSpec, opts.Programs)
|
||||
var handle *btf.Handle
|
||||
if progSpec.BTF != nil {
|
||||
handle, err = loadBTF(btf.ProgramSpec(progSpec.BTF))
|
||||
if err != nil && !xerrors.Is(err, btf.ErrNotSupported) {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
prog, err := newProgramWithBTF(progSpec, handle, opts.Programs)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "program %s", progName)
|
||||
return nil, xerrors.Errorf("program %s: %w", progName, err)
|
||||
}
|
||||
progs[progName] = prog
|
||||
}
|
||||
|
|
|
@ -4,20 +4,25 @@ import (
|
|||
"bytes"
|
||||
"debug/elf"
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"io"
|
||||
"math"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/cilium/ebpf/asm"
|
||||
"github.com/cilium/ebpf/internal"
|
||||
"github.com/cilium/ebpf/internal/btf"
|
||||
"github.com/cilium/ebpf/internal/unix"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"golang.org/x/xerrors"
|
||||
)
|
||||
|
||||
type elfCode struct {
|
||||
*elf.File
|
||||
symbols []elf.Symbol
|
||||
symbolsPerSection map[elf.SectionIndex]map[uint64]string
|
||||
symbolsPerSection map[elf.SectionIndex]map[uint64]elf.Symbol
|
||||
license string
|
||||
version uint32
|
||||
}
|
||||
|
||||
// LoadCollectionSpec parses an ELF file into a CollectionSpec.
|
||||
|
@ -29,12 +34,15 @@ func LoadCollectionSpec(file string) (*CollectionSpec, error) {
|
|||
defer f.Close()
|
||||
|
||||
spec, err := LoadCollectionSpecFromReader(f)
|
||||
return spec, errors.Wrapf(err, "file %s", file)
|
||||
if err != nil {
|
||||
return nil, xerrors.Errorf("file %s: %w", file, err)
|
||||
}
|
||||
return spec, nil
|
||||
}
|
||||
|
||||
// LoadCollectionSpecFromReader parses an ELF file into a CollectionSpec.
|
||||
func LoadCollectionSpecFromReader(code io.ReaderAt) (*CollectionSpec, error) {
|
||||
f, err := elf.NewFile(code)
|
||||
func LoadCollectionSpecFromReader(rd io.ReaderAt) (*CollectionSpec, error) {
|
||||
f, err := elf.NewFile(rd)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -42,15 +50,21 @@ func LoadCollectionSpecFromReader(code io.ReaderAt) (*CollectionSpec, error) {
|
|||
|
||||
symbols, err := f.Symbols()
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "load symbols")
|
||||
return nil, xerrors.Errorf("load symbols: %v", err)
|
||||
}
|
||||
|
||||
ec := &elfCode{f, symbols, symbolsPerSection(symbols)}
|
||||
ec := &elfCode{f, symbols, symbolsPerSection(symbols), "", 0}
|
||||
|
||||
var (
|
||||
licenseSection *elf.Section
|
||||
versionSection *elf.Section
|
||||
btfMaps = make(map[elf.SectionIndex]*elf.Section)
|
||||
progSections = make(map[elf.SectionIndex]*elf.Section)
|
||||
relSections = make(map[elf.SectionIndex]*elf.Section)
|
||||
mapSections = make(map[elf.SectionIndex]*elf.Section)
|
||||
dataSections = make(map[elf.SectionIndex]*elf.Section)
|
||||
)
|
||||
|
||||
var licenseSection, versionSection *elf.Section
|
||||
progSections := make(map[elf.SectionIndex]*elf.Section)
|
||||
relSections := make(map[elf.SectionIndex]*elf.Section)
|
||||
mapSections := make(map[elf.SectionIndex]*elf.Section)
|
||||
for i, sec := range ec.Sections {
|
||||
switch {
|
||||
case strings.HasPrefix(sec.Name, "license"):
|
||||
|
@ -59,15 +73,19 @@ func LoadCollectionSpecFromReader(code io.ReaderAt) (*CollectionSpec, error) {
|
|||
versionSection = sec
|
||||
case strings.HasPrefix(sec.Name, "maps"):
|
||||
mapSections[elf.SectionIndex(i)] = sec
|
||||
case sec.Name == ".maps":
|
||||
btfMaps[elf.SectionIndex(i)] = sec
|
||||
case sec.Name == ".bss" || sec.Name == ".rodata" || sec.Name == ".data":
|
||||
dataSections[elf.SectionIndex(i)] = sec
|
||||
case sec.Type == elf.SHT_REL:
|
||||
if int(sec.Info) >= len(ec.Sections) {
|
||||
return nil, errors.Errorf("found relocation section %v for missing section %v", i, sec.Info)
|
||||
return nil, xerrors.Errorf("found relocation section %v for missing section %v", i, sec.Info)
|
||||
}
|
||||
|
||||
// Store relocations under the section index of the target
|
||||
idx := elf.SectionIndex(sec.Info)
|
||||
if relSections[idx] != nil {
|
||||
return nil, errors.Errorf("section %d has multiple relocation sections", idx)
|
||||
return nil, xerrors.Errorf("section %d has multiple relocation sections", sec.Info)
|
||||
}
|
||||
relSections[idx] = sec
|
||||
case sec.Type == elf.SHT_PROGBITS && (sec.Flags&elf.SHF_EXECINSTR) != 0 && sec.Size > 0:
|
||||
|
@ -75,45 +93,58 @@ func LoadCollectionSpecFromReader(code io.ReaderAt) (*CollectionSpec, error) {
|
|||
}
|
||||
}
|
||||
|
||||
license, err := loadLicense(licenseSection)
|
||||
ec.license, err = loadLicense(licenseSection)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "load license")
|
||||
return nil, xerrors.Errorf("load license: %w", err)
|
||||
}
|
||||
|
||||
version, err := loadVersion(versionSection, ec.ByteOrder)
|
||||
ec.version, err = loadVersion(versionSection, ec.ByteOrder)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "load version")
|
||||
return nil, xerrors.Errorf("load version: %w", err)
|
||||
}
|
||||
|
||||
maps, err := ec.loadMaps(mapSections)
|
||||
btfSpec, err := btf.LoadSpecFromReader(rd)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "load maps")
|
||||
return nil, xerrors.Errorf("load BTF: %w", err)
|
||||
}
|
||||
|
||||
progs, libs, err := ec.loadPrograms(progSections, relSections, license, version)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "load programs")
|
||||
maps := make(map[string]*MapSpec)
|
||||
if err := ec.loadMaps(maps, mapSections); err != nil {
|
||||
return nil, xerrors.Errorf("load maps: %w", err)
|
||||
}
|
||||
|
||||
if len(libs) > 0 {
|
||||
for name, prog := range progs {
|
||||
prog.Instructions, err = link(prog.Instructions, libs...)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "program %s", name)
|
||||
}
|
||||
if len(btfMaps) > 0 {
|
||||
if err := ec.loadBTFMaps(maps, btfMaps, btfSpec); err != nil {
|
||||
return nil, xerrors.Errorf("load BTF maps: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
if len(dataSections) > 0 {
|
||||
if err := ec.loadDataSections(maps, dataSections, btfSpec); err != nil {
|
||||
return nil, xerrors.Errorf("load data sections: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
relocations, err := ec.loadRelocations(relSections)
|
||||
if err != nil {
|
||||
return nil, xerrors.Errorf("load relocations: %w", err)
|
||||
}
|
||||
|
||||
progs, err := ec.loadPrograms(progSections, relocations, btfSpec)
|
||||
if err != nil {
|
||||
return nil, xerrors.Errorf("load programs: %w", err)
|
||||
}
|
||||
|
||||
return &CollectionSpec{maps, progs}, nil
|
||||
}
|
||||
|
||||
func loadLicense(sec *elf.Section) (string, error) {
|
||||
if sec == nil {
|
||||
return "", errors.Errorf("missing license section")
|
||||
return "", xerrors.New("missing license section")
|
||||
}
|
||||
data, err := sec.Data()
|
||||
if err != nil {
|
||||
return "", errors.Wrapf(err, "section %s", sec.Name)
|
||||
return "", xerrors.Errorf("section %s: %v", sec.Name, err)
|
||||
}
|
||||
return string(bytes.TrimRight(data, "\000")), nil
|
||||
}
|
||||
|
@ -124,92 +155,226 @@ func loadVersion(sec *elf.Section, bo binary.ByteOrder) (uint32, error) {
|
|||
}
|
||||
|
||||
var version uint32
|
||||
err := binary.Read(sec.Open(), bo, &version)
|
||||
return version, errors.Wrapf(err, "section %s", sec.Name)
|
||||
if err := binary.Read(sec.Open(), bo, &version); err != nil {
|
||||
return 0, xerrors.Errorf("section %s: %v", sec.Name, err)
|
||||
}
|
||||
return version, nil
|
||||
}
|
||||
|
||||
func (ec *elfCode) loadPrograms(progSections, relSections map[elf.SectionIndex]*elf.Section, license string, version uint32) (map[string]*ProgramSpec, []asm.Instructions, error) {
|
||||
func (ec *elfCode) loadPrograms(progSections map[elf.SectionIndex]*elf.Section, relocations map[elf.SectionIndex]map[uint64]elf.Symbol, btf *btf.Spec) (map[string]*ProgramSpec, error) {
|
||||
var (
|
||||
progs = make(map[string]*ProgramSpec)
|
||||
libs []asm.Instructions
|
||||
progs []*ProgramSpec
|
||||
libs []*ProgramSpec
|
||||
)
|
||||
for idx, prog := range progSections {
|
||||
|
||||
for idx, sec := range progSections {
|
||||
syms := ec.symbolsPerSection[idx]
|
||||
if len(syms) == 0 {
|
||||
return nil, nil, errors.Errorf("section %v: missing symbols", prog.Name)
|
||||
return nil, xerrors.Errorf("section %v: missing symbols", sec.Name)
|
||||
}
|
||||
|
||||
funcSym := syms[0]
|
||||
if funcSym == "" {
|
||||
return nil, nil, errors.Errorf("section %v: no label at start", prog.Name)
|
||||
funcSym, ok := syms[0]
|
||||
if !ok {
|
||||
return nil, xerrors.Errorf("section %v: no label at start", sec.Name)
|
||||
}
|
||||
|
||||
rels, err := ec.loadRelocations(relSections[idx])
|
||||
insns, length, err := ec.loadInstructions(sec, syms, relocations[idx])
|
||||
if err != nil {
|
||||
return nil, nil, errors.Wrapf(err, "program %s: can't load relocations", funcSym)
|
||||
return nil, xerrors.Errorf("program %s: can't unmarshal instructions: %w", funcSym.Name, err)
|
||||
}
|
||||
|
||||
insns, err := ec.loadInstructions(prog, syms, rels)
|
||||
if err != nil {
|
||||
return nil, nil, errors.Wrapf(err, "program %s: can't unmarshal instructions", funcSym)
|
||||
progType, attachType := getProgType(sec.Name)
|
||||
|
||||
spec := &ProgramSpec{
|
||||
Name: funcSym.Name,
|
||||
Type: progType,
|
||||
AttachType: attachType,
|
||||
License: ec.license,
|
||||
KernelVersion: ec.version,
|
||||
Instructions: insns,
|
||||
}
|
||||
|
||||
if progType, attachType := getProgType(prog.Name); progType == UnspecifiedProgram {
|
||||
if btf != nil {
|
||||
spec.BTF, err = btf.Program(sec.Name, length)
|
||||
if err != nil {
|
||||
return nil, xerrors.Errorf("BTF for section %s (program %s): %w", sec.Name, funcSym.Name, err)
|
||||
}
|
||||
}
|
||||
|
||||
if spec.Type == UnspecifiedProgram {
|
||||
// There is no single name we can use for "library" sections,
|
||||
// since they may contain multiple functions. We'll decode the
|
||||
// labels they contain later on, and then link sections that way.
|
||||
libs = append(libs, insns)
|
||||
libs = append(libs, spec)
|
||||
} else {
|
||||
progs[funcSym] = &ProgramSpec{
|
||||
Name: funcSym,
|
||||
Type: progType,
|
||||
AttachType: attachType,
|
||||
License: license,
|
||||
KernelVersion: version,
|
||||
Instructions: insns,
|
||||
}
|
||||
progs = append(progs, spec)
|
||||
}
|
||||
}
|
||||
return progs, libs, nil
|
||||
|
||||
res := make(map[string]*ProgramSpec, len(progs))
|
||||
for _, prog := range progs {
|
||||
err := link(prog, libs)
|
||||
if err != nil {
|
||||
return nil, xerrors.Errorf("program %s: %w", prog.Name, err)
|
||||
}
|
||||
res[prog.Name] = prog
|
||||
}
|
||||
|
||||
return res, nil
|
||||
}
|
||||
|
||||
func (ec *elfCode) loadInstructions(section *elf.Section, symbols, relocations map[uint64]string) (asm.Instructions, error) {
|
||||
func (ec *elfCode) loadInstructions(section *elf.Section, symbols, relocations map[uint64]elf.Symbol) (asm.Instructions, uint64, error) {
|
||||
var (
|
||||
r = section.Open()
|
||||
insns asm.Instructions
|
||||
ins asm.Instruction
|
||||
offset uint64
|
||||
)
|
||||
for {
|
||||
var ins asm.Instruction
|
||||
n, err := ins.Unmarshal(r, ec.ByteOrder)
|
||||
if err == io.EOF {
|
||||
return insns, nil
|
||||
return insns, offset, nil
|
||||
}
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "offset %d", offset)
|
||||
return nil, 0, xerrors.Errorf("offset %d: %w", offset, err)
|
||||
}
|
||||
|
||||
ins.Symbol = symbols[offset]
|
||||
ins.Reference = relocations[offset]
|
||||
ins.Symbol = symbols[offset].Name
|
||||
|
||||
if rel, ok := relocations[offset]; ok {
|
||||
if err = ec.relocateInstruction(&ins, rel); err != nil {
|
||||
return nil, 0, xerrors.Errorf("offset %d: can't relocate instruction: %w", offset, err)
|
||||
}
|
||||
}
|
||||
|
||||
insns = append(insns, ins)
|
||||
offset += n
|
||||
}
|
||||
}
|
||||
|
||||
func (ec *elfCode) loadMaps(mapSections map[elf.SectionIndex]*elf.Section) (map[string]*MapSpec, error) {
|
||||
func (ec *elfCode) relocateInstruction(ins *asm.Instruction, rel elf.Symbol) error {
|
||||
var (
|
||||
maps = make(map[string]*MapSpec)
|
||||
b = make([]byte, 1)
|
||||
typ = elf.ST_TYPE(rel.Info)
|
||||
bind = elf.ST_BIND(rel.Info)
|
||||
name = rel.Name
|
||||
)
|
||||
|
||||
if typ == elf.STT_SECTION {
|
||||
// Symbols with section type do not have a name set. Get it
|
||||
// from the section itself.
|
||||
idx := int(rel.Section)
|
||||
if idx > len(ec.Sections) {
|
||||
return xerrors.New("out-of-bounds section index")
|
||||
}
|
||||
|
||||
name = ec.Sections[idx].Name
|
||||
}
|
||||
|
||||
outer:
|
||||
switch {
|
||||
case ins.OpCode == asm.LoadImmOp(asm.DWord):
|
||||
// There are two distinct types of a load from a map:
|
||||
// a direct one, where the value is extracted without
|
||||
// a call to map_lookup_elem in eBPF, and an indirect one
|
||||
// that goes via the helper. They are distinguished by
|
||||
// different relocations.
|
||||
switch typ {
|
||||
case elf.STT_SECTION:
|
||||
// This is a direct load since the referenced symbol is a
|
||||
// section. Weirdly, the offset of the real symbol in the
|
||||
// section is encoded in the instruction stream.
|
||||
if bind != elf.STB_LOCAL {
|
||||
return xerrors.Errorf("direct load: %s: unsupported relocation %s", name, bind)
|
||||
}
|
||||
|
||||
// For some reason, clang encodes the offset of the symbol its
|
||||
// section in the first basic BPF instruction, while the kernel
|
||||
// expects it in the second one.
|
||||
ins.Constant <<= 32
|
||||
ins.Src = asm.PseudoMapValue
|
||||
|
||||
case elf.STT_NOTYPE:
|
||||
if bind == elf.STB_GLOBAL && rel.Section == elf.SHN_UNDEF {
|
||||
// This is a relocation generated by inline assembly.
|
||||
// We can't do more than assigning ins.Reference.
|
||||
break outer
|
||||
}
|
||||
|
||||
// This is an ELF generated on clang < 8, which doesn't tag
|
||||
// relocations appropriately.
|
||||
fallthrough
|
||||
|
||||
case elf.STT_OBJECT:
|
||||
if bind != elf.STB_GLOBAL {
|
||||
return xerrors.Errorf("load: %s: unsupported binding: %s", name, bind)
|
||||
}
|
||||
|
||||
ins.Src = asm.PseudoMapFD
|
||||
|
||||
default:
|
||||
return xerrors.Errorf("load: %s: unsupported relocation: %s", name, typ)
|
||||
}
|
||||
|
||||
// Mark the instruction as needing an update when creating the
|
||||
// collection.
|
||||
if err := ins.RewriteMapPtr(-1); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
case ins.OpCode.JumpOp() == asm.Call:
|
||||
if ins.Src != asm.PseudoCall {
|
||||
return xerrors.Errorf("call: %s: incorrect source register", name)
|
||||
}
|
||||
|
||||
switch typ {
|
||||
case elf.STT_NOTYPE, elf.STT_FUNC:
|
||||
if bind != elf.STB_GLOBAL {
|
||||
return xerrors.Errorf("call: %s: unsupported binding: %s", name, bind)
|
||||
}
|
||||
|
||||
case elf.STT_SECTION:
|
||||
if bind != elf.STB_LOCAL {
|
||||
return xerrors.Errorf("call: %s: unsupported binding: %s", name, bind)
|
||||
}
|
||||
|
||||
// The function we want to call is in the indicated section,
|
||||
// at the offset encoded in the instruction itself. Reverse
|
||||
// the calculation to find the real function we're looking for.
|
||||
// A value of -1 references the first instruction in the section.
|
||||
offset := int64(int32(ins.Constant)+1) * asm.InstructionSize
|
||||
if offset < 0 {
|
||||
return xerrors.Errorf("call: %s: invalid offset %d", name, offset)
|
||||
}
|
||||
|
||||
sym, ok := ec.symbolsPerSection[rel.Section][uint64(offset)]
|
||||
if !ok {
|
||||
return xerrors.Errorf("call: %s: no symbol at offset %d", name, offset)
|
||||
}
|
||||
|
||||
ins.Constant = -1
|
||||
name = sym.Name
|
||||
|
||||
default:
|
||||
return xerrors.Errorf("call: %s: invalid symbol type %s", name, typ)
|
||||
}
|
||||
|
||||
default:
|
||||
return xerrors.Errorf("relocation for unsupported instruction: %s", ins.OpCode)
|
||||
}
|
||||
|
||||
ins.Reference = name
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ec *elfCode) loadMaps(maps map[string]*MapSpec, mapSections map[elf.SectionIndex]*elf.Section) error {
|
||||
for idx, sec := range mapSections {
|
||||
syms := ec.symbolsPerSection[idx]
|
||||
if len(syms) == 0 {
|
||||
return nil, errors.Errorf("section %v: no symbols", sec.Name)
|
||||
return xerrors.Errorf("section %v: no symbols", sec.Name)
|
||||
}
|
||||
|
||||
if sec.Size%uint64(len(syms)) != 0 {
|
||||
return nil, errors.Errorf("section %v: map descriptors are not of equal size", sec.Name)
|
||||
return xerrors.Errorf("section %v: map descriptors are not of equal size", sec.Name)
|
||||
}
|
||||
|
||||
var (
|
||||
|
@ -217,66 +382,212 @@ func (ec *elfCode) loadMaps(mapSections map[elf.SectionIndex]*elf.Section) (map[
|
|||
size = sec.Size / uint64(len(syms))
|
||||
)
|
||||
for i, offset := 0, uint64(0); i < len(syms); i, offset = i+1, offset+size {
|
||||
mapSym := syms[offset]
|
||||
if mapSym == "" {
|
||||
fmt.Println(syms)
|
||||
return nil, errors.Errorf("section %s: missing symbol for map at offset %d", sec.Name, offset)
|
||||
mapSym, ok := syms[offset]
|
||||
if !ok {
|
||||
return xerrors.Errorf("section %s: missing symbol for map at offset %d", sec.Name, offset)
|
||||
}
|
||||
|
||||
if maps[mapSym] != nil {
|
||||
return nil, errors.Errorf("section %v: map %v already exists", sec.Name, mapSym)
|
||||
if maps[mapSym.Name] != nil {
|
||||
return xerrors.Errorf("section %v: map %v already exists", sec.Name, mapSym)
|
||||
}
|
||||
|
||||
lr := io.LimitReader(r, int64(size))
|
||||
|
||||
var spec MapSpec
|
||||
spec := MapSpec{
|
||||
Name: SanitizeName(mapSym.Name, -1),
|
||||
}
|
||||
switch {
|
||||
case binary.Read(lr, ec.ByteOrder, &spec.Type) != nil:
|
||||
return nil, errors.Errorf("map %v: missing type", mapSym)
|
||||
return xerrors.Errorf("map %v: missing type", mapSym)
|
||||
case binary.Read(lr, ec.ByteOrder, &spec.KeySize) != nil:
|
||||
return nil, errors.Errorf("map %v: missing key size", mapSym)
|
||||
return xerrors.Errorf("map %v: missing key size", mapSym)
|
||||
case binary.Read(lr, ec.ByteOrder, &spec.ValueSize) != nil:
|
||||
return nil, errors.Errorf("map %v: missing value size", mapSym)
|
||||
return xerrors.Errorf("map %v: missing value size", mapSym)
|
||||
case binary.Read(lr, ec.ByteOrder, &spec.MaxEntries) != nil:
|
||||
return nil, errors.Errorf("map %v: missing max entries", mapSym)
|
||||
return xerrors.Errorf("map %v: missing max entries", mapSym)
|
||||
case binary.Read(lr, ec.ByteOrder, &spec.Flags) != nil:
|
||||
return nil, errors.Errorf("map %v: missing flags", mapSym)
|
||||
return xerrors.Errorf("map %v: missing flags", mapSym)
|
||||
}
|
||||
|
||||
for {
|
||||
_, err := lr.Read(b)
|
||||
if err == io.EOF {
|
||||
break
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if b[0] != 0 {
|
||||
return nil, errors.Errorf("map %v: unknown and non-zero fields in definition", mapSym)
|
||||
}
|
||||
if _, err := io.Copy(internal.DiscardZeroes{}, lr); err != nil {
|
||||
return xerrors.Errorf("map %v: unknown and non-zero fields in definition", mapSym)
|
||||
}
|
||||
|
||||
maps[mapSym] = &spec
|
||||
maps[mapSym.Name] = &spec
|
||||
}
|
||||
}
|
||||
return maps, nil
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ec *elfCode) loadBTFMaps(maps map[string]*MapSpec, mapSections map[elf.SectionIndex]*elf.Section, spec *btf.Spec) error {
|
||||
if spec == nil {
|
||||
return xerrors.Errorf("missing BTF")
|
||||
}
|
||||
|
||||
for idx, sec := range mapSections {
|
||||
syms := ec.symbolsPerSection[idx]
|
||||
if len(syms) == 0 {
|
||||
return xerrors.Errorf("section %v: no symbols", sec.Name)
|
||||
}
|
||||
|
||||
for _, sym := range syms {
|
||||
name := sym.Name
|
||||
if maps[name] != nil {
|
||||
return xerrors.Errorf("section %v: map %v already exists", sec.Name, sym)
|
||||
}
|
||||
|
||||
btfMap, btfMapMembers, err := spec.Map(name)
|
||||
if err != nil {
|
||||
return xerrors.Errorf("map %v: can't get BTF: %w", name, err)
|
||||
}
|
||||
|
||||
spec, err := mapSpecFromBTF(btfMap, btfMapMembers)
|
||||
if err != nil {
|
||||
return xerrors.Errorf("map %v: %w", name, err)
|
||||
}
|
||||
|
||||
maps[name] = spec
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func mapSpecFromBTF(btfMap *btf.Map, btfMapMembers []btf.Member) (*MapSpec, error) {
|
||||
var (
|
||||
mapType, flags, maxEntries uint32
|
||||
err error
|
||||
)
|
||||
for _, member := range btfMapMembers {
|
||||
switch member.Name {
|
||||
case "type":
|
||||
mapType, err = uintFromBTF(member.Type)
|
||||
if err != nil {
|
||||
return nil, xerrors.Errorf("can't get type: %w", err)
|
||||
}
|
||||
|
||||
case "map_flags":
|
||||
flags, err = uintFromBTF(member.Type)
|
||||
if err != nil {
|
||||
return nil, xerrors.Errorf("can't get BTF map flags: %w", err)
|
||||
}
|
||||
|
||||
case "max_entries":
|
||||
maxEntries, err = uintFromBTF(member.Type)
|
||||
if err != nil {
|
||||
return nil, xerrors.Errorf("can't get BTF map max entries: %w", err)
|
||||
}
|
||||
|
||||
case "key":
|
||||
case "value":
|
||||
default:
|
||||
return nil, xerrors.Errorf("unrecognized field %s in BTF map definition", member.Name)
|
||||
}
|
||||
}
|
||||
|
||||
keySize, err := btf.Sizeof(btf.MapKey(btfMap))
|
||||
if err != nil {
|
||||
return nil, xerrors.Errorf("can't get size of BTF key: %w", err)
|
||||
}
|
||||
|
||||
valueSize, err := btf.Sizeof(btf.MapValue(btfMap))
|
||||
if err != nil {
|
||||
return nil, xerrors.Errorf("can't get size of BTF value: %w", err)
|
||||
}
|
||||
|
||||
return &MapSpec{
|
||||
Type: MapType(mapType),
|
||||
KeySize: uint32(keySize),
|
||||
ValueSize: uint32(valueSize),
|
||||
MaxEntries: maxEntries,
|
||||
Flags: flags,
|
||||
BTF: btfMap,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// uintFromBTF resolves the __uint macro, which is a pointer to a sized
|
||||
// array, e.g. for int (*foo)[10], this function will return 10.
|
||||
func uintFromBTF(typ btf.Type) (uint32, error) {
|
||||
ptr, ok := typ.(*btf.Pointer)
|
||||
if !ok {
|
||||
return 0, xerrors.Errorf("not a pointer: %v", typ)
|
||||
}
|
||||
|
||||
arr, ok := ptr.Target.(*btf.Array)
|
||||
if !ok {
|
||||
return 0, xerrors.Errorf("not a pointer to array: %v", typ)
|
||||
}
|
||||
|
||||
return arr.Nelems, nil
|
||||
}
|
||||
|
||||
func (ec *elfCode) loadDataSections(maps map[string]*MapSpec, dataSections map[elf.SectionIndex]*elf.Section, spec *btf.Spec) error {
|
||||
if spec == nil {
|
||||
return xerrors.New("data sections require BTF")
|
||||
}
|
||||
|
||||
for _, sec := range dataSections {
|
||||
btfMap, err := spec.Datasec(sec.Name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
data, err := sec.Data()
|
||||
if err != nil {
|
||||
return xerrors.Errorf("data section %s: can't get contents: %w", sec.Name, err)
|
||||
}
|
||||
|
||||
if uint64(len(data)) > math.MaxUint32 {
|
||||
return xerrors.Errorf("data section %s: contents exceed maximum size", sec.Name)
|
||||
}
|
||||
|
||||
mapSpec := &MapSpec{
|
||||
Name: SanitizeName(sec.Name, -1),
|
||||
Type: Array,
|
||||
KeySize: 4,
|
||||
ValueSize: uint32(len(data)),
|
||||
MaxEntries: 1,
|
||||
Contents: []MapKV{{uint32(0), data}},
|
||||
BTF: btfMap,
|
||||
}
|
||||
|
||||
switch sec.Name {
|
||||
case ".rodata":
|
||||
mapSpec.Flags = unix.BPF_F_RDONLY_PROG
|
||||
mapSpec.Freeze = true
|
||||
case ".bss":
|
||||
// The kernel already zero-initializes the map
|
||||
mapSpec.Contents = nil
|
||||
}
|
||||
|
||||
maps[sec.Name] = mapSpec
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func getProgType(v string) (ProgramType, AttachType) {
|
||||
types := map[string]ProgramType{
|
||||
// From https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/tools/lib/bpf/libbpf.c#n3568
|
||||
"socket": SocketFilter,
|
||||
"seccomp": SocketFilter,
|
||||
"kprobe/": Kprobe,
|
||||
"kretprobe/": Kprobe,
|
||||
"tracepoint/": TracePoint,
|
||||
"xdp": XDP,
|
||||
"perf_event": PerfEvent,
|
||||
"sockops": SockOps,
|
||||
"sk_skb": SkSKB,
|
||||
"sk_msg": SkMsg,
|
||||
"lirc_mode2": LircMode2,
|
||||
"flow_dissector": FlowDissector,
|
||||
"socket": SocketFilter,
|
||||
"seccomp": SocketFilter,
|
||||
"kprobe/": Kprobe,
|
||||
"uprobe/": Kprobe,
|
||||
"kretprobe/": Kprobe,
|
||||
"uretprobe/": Kprobe,
|
||||
"tracepoint/": TracePoint,
|
||||
"raw_tracepoint/": RawTracepoint,
|
||||
"xdp": XDP,
|
||||
"perf_event": PerfEvent,
|
||||
"lwt_in": LWTIn,
|
||||
"lwt_out": LWTOut,
|
||||
"lwt_xmit": LWTXmit,
|
||||
"lwt_seg6local": LWTSeg6Local,
|
||||
"sockops": SockOps,
|
||||
"sk_skb": SkSKB,
|
||||
"sk_msg": SkMsg,
|
||||
"lirc_mode2": LircMode2,
|
||||
"flow_dissector": FlowDissector,
|
||||
|
||||
"cgroup_skb/": CGroupSKB,
|
||||
"cgroup/dev": CGroupDevice,
|
||||
|
@ -333,38 +644,40 @@ func getProgType(v string) (ProgramType, AttachType) {
|
|||
return UnspecifiedProgram, AttachNone
|
||||
}
|
||||
|
||||
func (ec *elfCode) loadRelocations(sec *elf.Section) (map[uint64]string, error) {
|
||||
rels := make(map[uint64]string)
|
||||
if sec == nil {
|
||||
return rels, nil
|
||||
}
|
||||
func (ec *elfCode) loadRelocations(sections map[elf.SectionIndex]*elf.Section) (map[elf.SectionIndex]map[uint64]elf.Symbol, error) {
|
||||
result := make(map[elf.SectionIndex]map[uint64]elf.Symbol)
|
||||
for idx, sec := range sections {
|
||||
rels := make(map[uint64]elf.Symbol)
|
||||
|
||||
if sec.Entsize < 16 {
|
||||
return nil, errors.New("rels are less than 16 bytes")
|
||||
}
|
||||
|
||||
r := sec.Open()
|
||||
for off := uint64(0); off < sec.Size; off += sec.Entsize {
|
||||
ent := io.LimitReader(r, int64(sec.Entsize))
|
||||
|
||||
var rel elf.Rel64
|
||||
if binary.Read(ent, ec.ByteOrder, &rel) != nil {
|
||||
return nil, errors.Errorf("can't parse relocation at offset %v", off)
|
||||
if sec.Entsize < 16 {
|
||||
return nil, xerrors.Errorf("section %s: relocations are less than 16 bytes", sec.Name)
|
||||
}
|
||||
|
||||
symNo := int(elf.R_SYM64(rel.Info) - 1)
|
||||
if symNo >= len(ec.symbols) {
|
||||
return nil, errors.Errorf("relocation at offset %d: symbol %v doesnt exist", off, symNo)
|
||||
r := sec.Open()
|
||||
for off := uint64(0); off < sec.Size; off += sec.Entsize {
|
||||
ent := io.LimitReader(r, int64(sec.Entsize))
|
||||
|
||||
var rel elf.Rel64
|
||||
if binary.Read(ent, ec.ByteOrder, &rel) != nil {
|
||||
return nil, xerrors.Errorf("can't parse relocation at offset %v", off)
|
||||
}
|
||||
|
||||
symNo := int(elf.R_SYM64(rel.Info) - 1)
|
||||
if symNo >= len(ec.symbols) {
|
||||
return nil, xerrors.Errorf("relocation at offset %d: symbol %v doesnt exist", off, symNo)
|
||||
}
|
||||
|
||||
rels[rel.Off] = ec.symbols[symNo]
|
||||
}
|
||||
|
||||
rels[rel.Off] = ec.symbols[symNo].Name
|
||||
result[idx] = rels
|
||||
}
|
||||
return rels, nil
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func symbolsPerSection(symbols []elf.Symbol) map[elf.SectionIndex]map[uint64]string {
|
||||
result := make(map[elf.SectionIndex]map[uint64]string)
|
||||
for i, sym := range symbols {
|
||||
func symbolsPerSection(symbols []elf.Symbol) map[elf.SectionIndex]map[uint64]elf.Symbol {
|
||||
result := make(map[elf.SectionIndex]map[uint64]elf.Symbol)
|
||||
for _, sym := range symbols {
|
||||
switch elf.ST_TYPE(sym.Info) {
|
||||
case elf.STT_NOTYPE:
|
||||
// Older versions of LLVM doesn't tag
|
||||
|
@ -378,15 +691,19 @@ func symbolsPerSection(symbols []elf.Symbol) map[elf.SectionIndex]map[uint64]str
|
|||
continue
|
||||
}
|
||||
|
||||
if sym.Section == elf.SHN_UNDEF || sym.Section >= elf.SHN_LORESERVE {
|
||||
continue
|
||||
}
|
||||
|
||||
if sym.Name == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
idx := sym.Section
|
||||
if _, ok := result[idx]; !ok {
|
||||
result[idx] = make(map[uint64]string)
|
||||
result[idx] = make(map[uint64]elf.Symbol)
|
||||
}
|
||||
result[idx][sym.Value] = symbols[i].Name
|
||||
result[idx][sym.Value] = sym
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
|
|
@ -1,19 +0,0 @@
|
|||
package ebpf
|
||||
|
||||
import (
|
||||
"sync"
|
||||
)
|
||||
|
||||
type featureTest struct {
|
||||
Fn func() bool
|
||||
|
||||
once sync.Once
|
||||
result bool
|
||||
}
|
||||
|
||||
func (ft *featureTest) Result() bool {
|
||||
ft.once.Do(func() {
|
||||
ft.result = ft.Fn()
|
||||
})
|
||||
return ft.result
|
||||
}
|
|
@ -3,6 +3,6 @@ module github.com/cilium/ebpf
|
|||
go 1.12
|
||||
|
||||
require (
|
||||
github.com/pkg/errors v0.8.1
|
||||
golang.org/x/sys v0.0.0-20191022100944-742c48ecaeb7
|
||||
golang.org/x/sys v0.0.0-20200124204421-9fbb57f87de9
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543
|
||||
)
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
|
||||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
golang.org/x/sys v0.0.0-20191022100944-742c48ecaeb7 h1:HmbHVPwrPEKPGLAcHSrMe6+hqSUlvZU0rab6x5EXfGU=
|
||||
golang.org/x/sys v0.0.0-20191022100944-742c48ecaeb7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200124204421-9fbb57f87de9 h1:1/DFK4b7JH8DmkqhUk48onnSfrPzImPoVxuomtbT2nk=
|
||||
golang.org/x/sys v0.0.0-20200124204421-9fbb57f87de9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
|
|
|
@ -0,0 +1,613 @@
|
|||
package btf
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"debug/elf"
|
||||
"encoding/binary"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"math"
|
||||
"reflect"
|
||||
"unsafe"
|
||||
|
||||
"github.com/cilium/ebpf/internal"
|
||||
"github.com/cilium/ebpf/internal/unix"
|
||||
|
||||
"golang.org/x/xerrors"
|
||||
)
|
||||
|
||||
const btfMagic = 0xeB9F
|
||||
|
||||
// Errors returned by BTF functions.
|
||||
var (
|
||||
ErrNotSupported = internal.ErrNotSupported
|
||||
)
|
||||
|
||||
// Spec represents decoded BTF.
|
||||
type Spec struct {
|
||||
rawTypes []rawType
|
||||
strings stringTable
|
||||
types map[string][]Type
|
||||
funcInfos map[string]extInfo
|
||||
lineInfos map[string]extInfo
|
||||
}
|
||||
|
||||
type btfHeader struct {
|
||||
Magic uint16
|
||||
Version uint8
|
||||
Flags uint8
|
||||
HdrLen uint32
|
||||
|
||||
TypeOff uint32
|
||||
TypeLen uint32
|
||||
StringOff uint32
|
||||
StringLen uint32
|
||||
}
|
||||
|
||||
// LoadSpecFromReader reads BTF sections from an ELF.
|
||||
//
|
||||
// Returns a nil Spec and no error if no BTF was present.
|
||||
func LoadSpecFromReader(rd io.ReaderAt) (*Spec, error) {
|
||||
file, err := elf.NewFile(rd)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
var (
|
||||
btfSection *elf.Section
|
||||
btfExtSection *elf.Section
|
||||
sectionSizes = make(map[string]uint32)
|
||||
)
|
||||
|
||||
for _, sec := range file.Sections {
|
||||
switch sec.Name {
|
||||
case ".BTF":
|
||||
btfSection = sec
|
||||
case ".BTF.ext":
|
||||
btfExtSection = sec
|
||||
default:
|
||||
if sec.Type != elf.SHT_PROGBITS && sec.Type != elf.SHT_NOBITS {
|
||||
break
|
||||
}
|
||||
|
||||
if sec.Size > math.MaxUint32 {
|
||||
return nil, xerrors.Errorf("section %s exceeds maximum size", sec.Name)
|
||||
}
|
||||
|
||||
sectionSizes[sec.Name] = uint32(sec.Size)
|
||||
}
|
||||
}
|
||||
|
||||
if btfSection == nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
symbols, err := file.Symbols()
|
||||
if err != nil {
|
||||
return nil, xerrors.Errorf("can't read symbols: %v", err)
|
||||
}
|
||||
|
||||
variableOffsets := make(map[variable]uint32)
|
||||
for _, symbol := range symbols {
|
||||
if idx := symbol.Section; idx >= elf.SHN_LORESERVE && idx <= elf.SHN_HIRESERVE {
|
||||
// Ignore things like SHN_ABS
|
||||
continue
|
||||
}
|
||||
|
||||
secName := file.Sections[symbol.Section].Name
|
||||
if _, ok := sectionSizes[secName]; !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
if symbol.Value > math.MaxUint32 {
|
||||
return nil, xerrors.Errorf("section %s: symbol %s: size exceeds maximum", secName, symbol.Name)
|
||||
}
|
||||
|
||||
variableOffsets[variable{secName, symbol.Name}] = uint32(symbol.Value)
|
||||
}
|
||||
|
||||
rawTypes, rawStrings, err := parseBTF(btfSection.Open(), file.ByteOrder)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = fixupDatasec(rawTypes, rawStrings, sectionSizes, variableOffsets)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
types, err := inflateRawTypes(rawTypes, rawStrings)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var (
|
||||
funcInfos = make(map[string]extInfo)
|
||||
lineInfos = make(map[string]extInfo)
|
||||
)
|
||||
if btfExtSection != nil {
|
||||
funcInfos, lineInfos, err = parseExtInfos(btfExtSection.Open(), file.ByteOrder, rawStrings)
|
||||
if err != nil {
|
||||
return nil, xerrors.Errorf("can't read ext info: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
return &Spec{
|
||||
rawTypes: rawTypes,
|
||||
types: types,
|
||||
strings: rawStrings,
|
||||
funcInfos: funcInfos,
|
||||
lineInfos: lineInfos,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func parseBTF(btf io.ReadSeeker, bo binary.ByteOrder) ([]rawType, stringTable, error) {
|
||||
rawBTF, err := ioutil.ReadAll(btf)
|
||||
if err != nil {
|
||||
return nil, nil, xerrors.Errorf("can't read BTF: %v", err)
|
||||
}
|
||||
|
||||
rd := bytes.NewReader(rawBTF)
|
||||
|
||||
var header btfHeader
|
||||
if err := binary.Read(rd, bo, &header); err != nil {
|
||||
return nil, nil, xerrors.Errorf("can't read header: %v", err)
|
||||
}
|
||||
|
||||
if header.Magic != btfMagic {
|
||||
return nil, nil, xerrors.Errorf("incorrect magic value %v", header.Magic)
|
||||
}
|
||||
|
||||
if header.Version != 1 {
|
||||
return nil, nil, xerrors.Errorf("unexpected version %v", header.Version)
|
||||
}
|
||||
|
||||
if header.Flags != 0 {
|
||||
return nil, nil, xerrors.Errorf("unsupported flags %v", header.Flags)
|
||||
}
|
||||
|
||||
remainder := int64(header.HdrLen) - int64(binary.Size(&header))
|
||||
if remainder < 0 {
|
||||
return nil, nil, xerrors.New("header is too short")
|
||||
}
|
||||
|
||||
if _, err := io.CopyN(internal.DiscardZeroes{}, rd, remainder); err != nil {
|
||||
return nil, nil, xerrors.Errorf("header padding: %v", err)
|
||||
}
|
||||
|
||||
if _, err := rd.Seek(int64(header.HdrLen+header.StringOff), io.SeekStart); err != nil {
|
||||
return nil, nil, xerrors.Errorf("can't seek to start of string section: %v", err)
|
||||
}
|
||||
|
||||
rawStrings, err := readStringTable(io.LimitReader(rd, int64(header.StringLen)))
|
||||
if err != nil {
|
||||
return nil, nil, xerrors.Errorf("can't read type names: %w", err)
|
||||
}
|
||||
|
||||
if _, err := rd.Seek(int64(header.HdrLen+header.TypeOff), io.SeekStart); err != nil {
|
||||
return nil, nil, xerrors.Errorf("can't seek to start of type section: %v", err)
|
||||
}
|
||||
|
||||
rawTypes, err := readTypes(io.LimitReader(rd, int64(header.TypeLen)), bo)
|
||||
if err != nil {
|
||||
return nil, nil, xerrors.Errorf("can't read types: %w", err)
|
||||
}
|
||||
|
||||
return rawTypes, rawStrings, nil
|
||||
}
|
||||
|
||||
type variable struct {
|
||||
section string
|
||||
name string
|
||||
}
|
||||
|
||||
func fixupDatasec(rawTypes []rawType, rawStrings stringTable, sectionSizes map[string]uint32, variableOffsets map[variable]uint32) error {
|
||||
for i, rawType := range rawTypes {
|
||||
if rawType.Kind() != kindDatasec {
|
||||
continue
|
||||
}
|
||||
|
||||
name, err := rawStrings.Lookup(rawType.NameOff)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
size, ok := sectionSizes[name]
|
||||
if !ok {
|
||||
return xerrors.Errorf("data section %s: missing size", name)
|
||||
}
|
||||
|
||||
rawTypes[i].SizeType = size
|
||||
|
||||
secinfos := rawType.data.([]btfVarSecinfo)
|
||||
for j, secInfo := range secinfos {
|
||||
id := int(secInfo.Type - 1)
|
||||
if id >= len(rawTypes) {
|
||||
return xerrors.Errorf("data section %s: invalid type id %d for variable %d", name, id, j)
|
||||
}
|
||||
|
||||
varName, err := rawStrings.Lookup(rawTypes[id].NameOff)
|
||||
if err != nil {
|
||||
return xerrors.Errorf("data section %s: can't get name for type %d: %w", name, id, err)
|
||||
}
|
||||
|
||||
offset, ok := variableOffsets[variable{name, varName}]
|
||||
if !ok {
|
||||
return xerrors.Errorf("data section %s: missing offset for variable %s", name, varName)
|
||||
}
|
||||
|
||||
secinfos[j].Offset = offset
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Spec) marshal(bo binary.ByteOrder) ([]byte, error) {
|
||||
var (
|
||||
buf bytes.Buffer
|
||||
header = new(btfHeader)
|
||||
headerLen = binary.Size(header)
|
||||
)
|
||||
|
||||
// Reserve space for the header. We have to write it last since
|
||||
// we don't know the size of the type section yet.
|
||||
_, _ = buf.Write(make([]byte, headerLen))
|
||||
|
||||
// Write type section, just after the header.
|
||||
for _, typ := range s.rawTypes {
|
||||
if err := typ.Marshal(&buf, bo); err != nil {
|
||||
return nil, xerrors.Errorf("can't marshal BTF: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
typeLen := uint32(buf.Len() - headerLen)
|
||||
|
||||
// Write string section after type section.
|
||||
_, _ = buf.Write(s.strings)
|
||||
|
||||
// Fill out the header, and write it out.
|
||||
header = &btfHeader{
|
||||
Magic: btfMagic,
|
||||
Version: 1,
|
||||
Flags: 0,
|
||||
HdrLen: uint32(headerLen),
|
||||
TypeOff: 0,
|
||||
TypeLen: typeLen,
|
||||
StringOff: typeLen,
|
||||
StringLen: uint32(len(s.strings)),
|
||||
}
|
||||
|
||||
raw := buf.Bytes()
|
||||
err := binary.Write(sliceWriter(raw[:headerLen]), bo, header)
|
||||
if err != nil {
|
||||
return nil, xerrors.Errorf("can't write header: %v", err)
|
||||
}
|
||||
|
||||
return raw, nil
|
||||
}
|
||||
|
||||
type sliceWriter []byte
|
||||
|
||||
func (sw sliceWriter) Write(p []byte) (int, error) {
|
||||
if len(p) != len(sw) {
|
||||
return 0, xerrors.New("size doesn't match")
|
||||
}
|
||||
|
||||
return copy(sw, p), nil
|
||||
}
|
||||
|
||||
// Program finds the BTF for a specific section.
|
||||
//
|
||||
// Length is the number of bytes in the raw BPF instruction stream.
|
||||
//
|
||||
// Returns an error if there is no BTF.
|
||||
func (s *Spec) Program(name string, length uint64) (*Program, error) {
|
||||
if length == 0 {
|
||||
return nil, xerrors.New("length musn't be zero")
|
||||
}
|
||||
|
||||
funcInfos, funcOK := s.funcInfos[name]
|
||||
lineInfos, lineOK := s.lineInfos[name]
|
||||
|
||||
if !funcOK && !lineOK {
|
||||
return nil, xerrors.Errorf("no BTF for program %s", name)
|
||||
}
|
||||
|
||||
return &Program{s, length, funcInfos, lineInfos}, nil
|
||||
}
|
||||
|
||||
// Map finds the BTF for a map.
|
||||
//
|
||||
// Returns an error if there is no BTF for the given name.
|
||||
func (s *Spec) Map(name string) (*Map, []Member, error) {
|
||||
var mapVar Var
|
||||
if err := s.FindType(name, &mapVar); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
mapStruct, ok := mapVar.Type.(*Struct)
|
||||
if !ok {
|
||||
return nil, nil, xerrors.Errorf("expected struct, have %s", mapVar.Type)
|
||||
}
|
||||
|
||||
var key, value Type
|
||||
for _, member := range mapStruct.Members {
|
||||
switch member.Name {
|
||||
case "key":
|
||||
key = member.Type
|
||||
|
||||
case "value":
|
||||
value = member.Type
|
||||
}
|
||||
}
|
||||
|
||||
if key == nil {
|
||||
return nil, nil, xerrors.Errorf("map %s: missing 'key' in type", name)
|
||||
}
|
||||
|
||||
if value == nil {
|
||||
return nil, nil, xerrors.Errorf("map %s: missing 'value' in type", name)
|
||||
}
|
||||
|
||||
return &Map{s, key, value}, mapStruct.Members, nil
|
||||
}
|
||||
|
||||
// Datasec returns the BTF required to create maps which represent data sections.
|
||||
func (s *Spec) Datasec(name string) (*Map, error) {
|
||||
var datasec Datasec
|
||||
if err := s.FindType(name, &datasec); err != nil {
|
||||
return nil, xerrors.Errorf("data section %s: can't get BTF: %w", name, err)
|
||||
}
|
||||
|
||||
return &Map{s, &Void{}, &datasec}, nil
|
||||
}
|
||||
|
||||
var errNotFound = xerrors.New("not found")
|
||||
|
||||
// FindType searches for a type with a specific name.
|
||||
//
|
||||
// hint determines the type of the returned Type.
|
||||
//
|
||||
// Returns an error if there is no or multiple matches.
|
||||
func (s *Spec) FindType(name string, typ Type) error {
|
||||
var (
|
||||
wanted = reflect.TypeOf(typ)
|
||||
candidate Type
|
||||
)
|
||||
|
||||
for _, typ := range s.types[name] {
|
||||
if reflect.TypeOf(typ) != wanted {
|
||||
continue
|
||||
}
|
||||
|
||||
if candidate != nil {
|
||||
return xerrors.Errorf("type %s: multiple candidates for %T", name, typ)
|
||||
}
|
||||
|
||||
candidate = typ
|
||||
}
|
||||
|
||||
if candidate == nil {
|
||||
return xerrors.Errorf("type %s: %w", name, errNotFound)
|
||||
}
|
||||
|
||||
value := reflect.Indirect(reflect.ValueOf(copyType(candidate)))
|
||||
reflect.Indirect(reflect.ValueOf(typ)).Set(value)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Handle is a reference to BTF loaded into the kernel.
|
||||
type Handle struct {
|
||||
fd *internal.FD
|
||||
}
|
||||
|
||||
// NewHandle loads BTF into the kernel.
|
||||
//
|
||||
// Returns ErrNotSupported if BTF is not supported.
|
||||
func NewHandle(spec *Spec) (*Handle, error) {
|
||||
if err := haveBTF(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
btf, err := spec.marshal(internal.NativeEndian)
|
||||
if err != nil {
|
||||
return nil, xerrors.Errorf("can't marshal BTF: %w", err)
|
||||
}
|
||||
|
||||
if uint64(len(btf)) > math.MaxUint32 {
|
||||
return nil, xerrors.New("BTF exceeds the maximum size")
|
||||
}
|
||||
|
||||
attr := &bpfLoadBTFAttr{
|
||||
btf: internal.NewSlicePointer(btf),
|
||||
btfSize: uint32(len(btf)),
|
||||
}
|
||||
|
||||
fd, err := bpfLoadBTF(attr)
|
||||
if err != nil {
|
||||
logBuf := make([]byte, 64*1024)
|
||||
attr.logBuf = internal.NewSlicePointer(logBuf)
|
||||
attr.btfLogSize = uint32(len(logBuf))
|
||||
attr.btfLogLevel = 1
|
||||
_, logErr := bpfLoadBTF(attr)
|
||||
return nil, internal.ErrorWithLog(err, logBuf, logErr)
|
||||
}
|
||||
|
||||
return &Handle{fd}, nil
|
||||
}
|
||||
|
||||
// Close destroys the handle.
|
||||
//
|
||||
// Subsequent calls to FD will return an invalid value.
|
||||
func (h *Handle) Close() error {
|
||||
return h.fd.Close()
|
||||
}
|
||||
|
||||
// FD returns the file descriptor for the handle.
|
||||
func (h *Handle) FD() int {
|
||||
value, err := h.fd.Value()
|
||||
if err != nil {
|
||||
return -1
|
||||
}
|
||||
|
||||
return int(value)
|
||||
}
|
||||
|
||||
// Map is the BTF for a map.
|
||||
type Map struct {
|
||||
spec *Spec
|
||||
key, value Type
|
||||
}
|
||||
|
||||
// MapSpec should be a method on Map, but is a free function
|
||||
// to hide it from users of the ebpf package.
|
||||
func MapSpec(m *Map) *Spec {
|
||||
return m.spec
|
||||
}
|
||||
|
||||
// MapKey should be a method on Map, but is a free function
|
||||
// to hide it from users of the ebpf package.
|
||||
func MapKey(m *Map) Type {
|
||||
return m.key
|
||||
}
|
||||
|
||||
// MapValue should be a method on Map, but is a free function
|
||||
// to hide it from users of the ebpf package.
|
||||
func MapValue(m *Map) Type {
|
||||
return m.value
|
||||
}
|
||||
|
||||
// Program is the BTF information for a stream of instructions.
|
||||
type Program struct {
|
||||
spec *Spec
|
||||
length uint64
|
||||
funcInfos, lineInfos extInfo
|
||||
}
|
||||
|
||||
// ProgramSpec returns the Spec needed for loading function and line infos into the kernel.
|
||||
//
|
||||
// This is a free function instead of a method to hide it from users
|
||||
// of package ebpf.
|
||||
func ProgramSpec(s *Program) *Spec {
|
||||
return s.spec
|
||||
}
|
||||
|
||||
// ProgramAppend the information from other to the Program.
|
||||
//
|
||||
// This is a free function instead of a method to hide it from users
|
||||
// of package ebpf.
|
||||
func ProgramAppend(s, other *Program) error {
|
||||
funcInfos, err := s.funcInfos.append(other.funcInfos, s.length)
|
||||
if err != nil {
|
||||
return xerrors.Errorf("func infos: %w", err)
|
||||
}
|
||||
|
||||
lineInfos, err := s.lineInfos.append(other.lineInfos, s.length)
|
||||
if err != nil {
|
||||
return xerrors.Errorf("line infos: %w", err)
|
||||
}
|
||||
|
||||
s.length += other.length
|
||||
s.funcInfos = funcInfos
|
||||
s.lineInfos = lineInfos
|
||||
return nil
|
||||
}
|
||||
|
||||
// ProgramFuncInfos returns the binary form of BTF function infos.
|
||||
//
|
||||
// This is a free function instead of a method to hide it from users
|
||||
// of package ebpf.
|
||||
func ProgramFuncInfos(s *Program) (recordSize uint32, bytes []byte, err error) {
|
||||
bytes, err = s.funcInfos.MarshalBinary()
|
||||
if err != nil {
|
||||
return 0, nil, err
|
||||
}
|
||||
|
||||
return s.funcInfos.recordSize, bytes, nil
|
||||
}
|
||||
|
||||
// ProgramLineInfos returns the binary form of BTF line infos.
|
||||
//
|
||||
// This is a free function instead of a method to hide it from users
|
||||
// of package ebpf.
|
||||
func ProgramLineInfos(s *Program) (recordSize uint32, bytes []byte, err error) {
|
||||
bytes, err = s.lineInfos.MarshalBinary()
|
||||
if err != nil {
|
||||
return 0, nil, err
|
||||
}
|
||||
|
||||
return s.lineInfos.recordSize, bytes, nil
|
||||
}
|
||||
|
||||
type bpfLoadBTFAttr struct {
|
||||
btf internal.Pointer
|
||||
logBuf internal.Pointer
|
||||
btfSize uint32
|
||||
btfLogSize uint32
|
||||
btfLogLevel uint32
|
||||
}
|
||||
|
||||
func bpfLoadBTF(attr *bpfLoadBTFAttr) (*internal.FD, error) {
|
||||
const _BTFLoad = 18
|
||||
|
||||
fd, err := internal.BPF(_BTFLoad, unsafe.Pointer(attr), unsafe.Sizeof(*attr))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return internal.NewFD(uint32(fd)), nil
|
||||
}
|
||||
|
||||
func minimalBTF(bo binary.ByteOrder) []byte {
|
||||
const minHeaderLength = 24
|
||||
|
||||
var (
|
||||
types struct {
|
||||
Integer btfType
|
||||
Var btfType
|
||||
btfVar struct{ Linkage uint32 }
|
||||
}
|
||||
typLen = uint32(binary.Size(&types))
|
||||
strings = []byte{0, 'a', 0}
|
||||
header = btfHeader{
|
||||
Magic: btfMagic,
|
||||
Version: 1,
|
||||
HdrLen: minHeaderLength,
|
||||
TypeOff: 0,
|
||||
TypeLen: typLen,
|
||||
StringOff: typLen,
|
||||
StringLen: uint32(len(strings)),
|
||||
}
|
||||
)
|
||||
|
||||
// We use a BTF_KIND_VAR here, to make sure that
|
||||
// the kernel understands BTF at least as well as we
|
||||
// do. BTF_KIND_VAR was introduced ~5.1.
|
||||
types.Integer.SetKind(kindPointer)
|
||||
types.Var.NameOff = 1
|
||||
types.Var.SetKind(kindVar)
|
||||
types.Var.SizeType = 1
|
||||
|
||||
buf := new(bytes.Buffer)
|
||||
_ = binary.Write(buf, bo, &header)
|
||||
_ = binary.Write(buf, bo, &types)
|
||||
buf.Write(strings)
|
||||
|
||||
return buf.Bytes()
|
||||
}
|
||||
|
||||
var haveBTF = internal.FeatureTest("BTF", "5.1", func() bool {
|
||||
btf := minimalBTF(internal.NativeEndian)
|
||||
fd, err := bpfLoadBTF(&bpfLoadBTFAttr{
|
||||
btf: internal.NewSlicePointer(btf),
|
||||
btfSize: uint32(len(btf)),
|
||||
})
|
||||
if err == nil {
|
||||
fd.Close()
|
||||
}
|
||||
// Check for EINVAL specifically, rather than err != nil since we
|
||||
// otherwise misdetect due to insufficient permissions.
|
||||
return !xerrors.Is(err, unix.EINVAL)
|
||||
})
|
|
@ -0,0 +1,238 @@
|
|||
package btf
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"io"
|
||||
|
||||
"golang.org/x/xerrors"
|
||||
)
|
||||
|
||||
// btfKind describes a Type.
|
||||
type btfKind uint8
|
||||
|
||||
// Equivalents of the BTF_KIND_* constants.
|
||||
const (
|
||||
kindUnknown btfKind = iota
|
||||
kindInt
|
||||
kindPointer
|
||||
kindArray
|
||||
kindStruct
|
||||
kindUnion
|
||||
kindEnum
|
||||
kindForward
|
||||
kindTypedef
|
||||
kindVolatile
|
||||
kindConst
|
||||
kindRestrict
|
||||
// Added ~4.20
|
||||
kindFunc
|
||||
kindFuncProto
|
||||
// Added ~5.1
|
||||
kindVar
|
||||
kindDatasec
|
||||
)
|
||||
|
||||
const (
|
||||
btfTypeKindShift = 24
|
||||
btfTypeKindLen = 4
|
||||
btfTypeVlenShift = 0
|
||||
btfTypeVlenMask = 16
|
||||
)
|
||||
|
||||
// btfType is equivalent to struct btf_type in Documentation/bpf/btf.rst.
|
||||
type btfType struct {
|
||||
NameOff uint32
|
||||
/* "info" bits arrangement
|
||||
* bits 0-15: vlen (e.g. # of struct's members)
|
||||
* bits 16-23: unused
|
||||
* bits 24-27: kind (e.g. int, ptr, array...etc)
|
||||
* bits 28-30: unused
|
||||
* bit 31: kind_flag, currently used by
|
||||
* struct, union and fwd
|
||||
*/
|
||||
Info uint32
|
||||
/* "size" is used by INT, ENUM, STRUCT and UNION.
|
||||
* "size" tells the size of the type it is describing.
|
||||
*
|
||||
* "type" is used by PTR, TYPEDEF, VOLATILE, CONST, RESTRICT,
|
||||
* FUNC and FUNC_PROTO.
|
||||
* "type" is a type_id referring to another type.
|
||||
*/
|
||||
SizeType uint32
|
||||
}
|
||||
|
||||
func (k btfKind) String() string {
|
||||
switch k {
|
||||
case kindUnknown:
|
||||
return "Unknown"
|
||||
case kindInt:
|
||||
return "Integer"
|
||||
case kindPointer:
|
||||
return "Pointer"
|
||||
case kindArray:
|
||||
return "Array"
|
||||
case kindStruct:
|
||||
return "Struct"
|
||||
case kindUnion:
|
||||
return "Union"
|
||||
case kindEnum:
|
||||
return "Enumeration"
|
||||
case kindForward:
|
||||
return "Forward"
|
||||
case kindTypedef:
|
||||
return "Typedef"
|
||||
case kindVolatile:
|
||||
return "Volatile"
|
||||
case kindConst:
|
||||
return "Const"
|
||||
case kindRestrict:
|
||||
return "Restrict"
|
||||
case kindFunc:
|
||||
return "Function"
|
||||
case kindFuncProto:
|
||||
return "Function Proto"
|
||||
case kindVar:
|
||||
return "Variable"
|
||||
case kindDatasec:
|
||||
return "Section"
|
||||
default:
|
||||
return fmt.Sprintf("Unknown (%d)", k)
|
||||
}
|
||||
}
|
||||
|
||||
func mask(len uint32) uint32 {
|
||||
return (1 << len) - 1
|
||||
}
|
||||
|
||||
func (bt *btfType) info(len, shift uint32) uint32 {
|
||||
return (bt.Info >> shift) & mask(len)
|
||||
}
|
||||
|
||||
func (bt *btfType) setInfo(value, len, shift uint32) {
|
||||
bt.Info &^= mask(len) << shift
|
||||
bt.Info |= (value & mask(len)) << shift
|
||||
}
|
||||
|
||||
func (bt *btfType) Kind() btfKind {
|
||||
return btfKind(bt.info(btfTypeKindLen, btfTypeKindShift))
|
||||
}
|
||||
|
||||
func (bt *btfType) SetKind(kind btfKind) {
|
||||
bt.setInfo(uint32(kind), btfTypeKindLen, btfTypeKindShift)
|
||||
}
|
||||
|
||||
func (bt *btfType) Vlen() int {
|
||||
return int(bt.info(btfTypeVlenMask, btfTypeVlenShift))
|
||||
}
|
||||
|
||||
func (bt *btfType) SetVlen(vlen int) {
|
||||
bt.setInfo(uint32(vlen), btfTypeVlenMask, btfTypeVlenShift)
|
||||
}
|
||||
|
||||
func (bt *btfType) Type() TypeID {
|
||||
// TODO: Panic here if wrong kind?
|
||||
return TypeID(bt.SizeType)
|
||||
}
|
||||
|
||||
func (bt *btfType) Size() uint32 {
|
||||
// TODO: Panic here if wrong kind?
|
||||
return bt.SizeType
|
||||
}
|
||||
|
||||
type rawType struct {
|
||||
btfType
|
||||
data interface{}
|
||||
}
|
||||
|
||||
func (rt *rawType) Marshal(w io.Writer, bo binary.ByteOrder) error {
|
||||
if err := binary.Write(w, bo, &rt.btfType); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if rt.data == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
return binary.Write(w, bo, rt.data)
|
||||
}
|
||||
|
||||
type btfArray struct {
|
||||
Type TypeID
|
||||
IndexType TypeID
|
||||
Nelems uint32
|
||||
}
|
||||
|
||||
type btfMember struct {
|
||||
NameOff uint32
|
||||
Type TypeID
|
||||
Offset uint32
|
||||
}
|
||||
|
||||
type btfVarSecinfo struct {
|
||||
Type TypeID
|
||||
Offset uint32
|
||||
Size uint32
|
||||
}
|
||||
|
||||
type btfVariable struct {
|
||||
Linkage uint32
|
||||
}
|
||||
|
||||
func readTypes(r io.Reader, bo binary.ByteOrder) ([]rawType, error) {
|
||||
var (
|
||||
header btfType
|
||||
types []rawType
|
||||
)
|
||||
|
||||
for id := TypeID(1); ; id++ {
|
||||
if err := binary.Read(r, bo, &header); err == io.EOF {
|
||||
return types, nil
|
||||
} else if err != nil {
|
||||
return nil, xerrors.Errorf("can't read type info for id %v: %v", id, err)
|
||||
}
|
||||
|
||||
var data interface{}
|
||||
switch header.Kind() {
|
||||
case kindInt:
|
||||
// sizeof(uint32)
|
||||
data = make([]byte, 4)
|
||||
case kindPointer:
|
||||
case kindArray:
|
||||
data = new(btfArray)
|
||||
case kindStruct:
|
||||
fallthrough
|
||||
case kindUnion:
|
||||
data = make([]btfMember, header.Vlen())
|
||||
case kindEnum:
|
||||
// sizeof(struct btf_enum)
|
||||
data = make([]byte, header.Vlen()*4*2)
|
||||
case kindForward:
|
||||
case kindTypedef:
|
||||
case kindVolatile:
|
||||
case kindConst:
|
||||
case kindRestrict:
|
||||
case kindFunc:
|
||||
case kindFuncProto:
|
||||
// sizeof(struct btf_param)
|
||||
data = make([]byte, header.Vlen()*4*2)
|
||||
case kindVar:
|
||||
data = new(btfVariable)
|
||||
case kindDatasec:
|
||||
data = make([]btfVarSecinfo, header.Vlen())
|
||||
default:
|
||||
return nil, xerrors.Errorf("type id %v: unknown kind: %v", id, header.Kind())
|
||||
}
|
||||
|
||||
if data == nil {
|
||||
types = append(types, rawType{header, nil})
|
||||
continue
|
||||
}
|
||||
|
||||
if err := binary.Read(r, bo, data); err != nil {
|
||||
return nil, xerrors.Errorf("type id %d: kind %v: can't read %T: %v", id, header.Kind(), data, err)
|
||||
}
|
||||
|
||||
types = append(types, rawType{header, data})
|
||||
}
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
// Package btf handles data encoded according to the BPF Type Format.
|
||||
//
|
||||
// The canonical documentation lives in the Linux kernel repository and is
|
||||
// available at https://www.kernel.org/doc/html/latest/bpf/btf.html
|
||||
//
|
||||
// The API is very much unstable. You should only use this via the main
|
||||
// ebpf library.
|
||||
package btf
|
|
@ -0,0 +1,182 @@
|
|||
package btf
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
|
||||
"github.com/cilium/ebpf/asm"
|
||||
"github.com/cilium/ebpf/internal"
|
||||
|
||||
"golang.org/x/xerrors"
|
||||
)
|
||||
|
||||
type btfExtHeader struct {
|
||||
Magic uint16
|
||||
Version uint8
|
||||
Flags uint8
|
||||
HdrLen uint32
|
||||
|
||||
FuncInfoOff uint32
|
||||
FuncInfoLen uint32
|
||||
LineInfoOff uint32
|
||||
LineInfoLen uint32
|
||||
}
|
||||
|
||||
func parseExtInfos(r io.ReadSeeker, bo binary.ByteOrder, strings stringTable) (funcInfo, lineInfo map[string]extInfo, err error) {
|
||||
var header btfExtHeader
|
||||
if err := binary.Read(r, bo, &header); err != nil {
|
||||
return nil, nil, xerrors.Errorf("can't read header: %v", err)
|
||||
}
|
||||
|
||||
if header.Magic != btfMagic {
|
||||
return nil, nil, xerrors.Errorf("incorrect magic value %v", header.Magic)
|
||||
}
|
||||
|
||||
if header.Version != 1 {
|
||||
return nil, nil, xerrors.Errorf("unexpected version %v", header.Version)
|
||||
}
|
||||
|
||||
if header.Flags != 0 {
|
||||
return nil, nil, xerrors.Errorf("unsupported flags %v", header.Flags)
|
||||
}
|
||||
|
||||
remainder := int64(header.HdrLen) - int64(binary.Size(&header))
|
||||
if remainder < 0 {
|
||||
return nil, nil, xerrors.New("header is too short")
|
||||
}
|
||||
|
||||
// Of course, the .BTF.ext header has different semantics than the
|
||||
// .BTF ext header. We need to ignore non-null values.
|
||||
_, err = io.CopyN(ioutil.Discard, r, remainder)
|
||||
if err != nil {
|
||||
return nil, nil, xerrors.Errorf("header padding: %v", err)
|
||||
}
|
||||
|
||||
if _, err := r.Seek(int64(header.HdrLen+header.FuncInfoOff), io.SeekStart); err != nil {
|
||||
return nil, nil, xerrors.Errorf("can't seek to function info section: %v", err)
|
||||
}
|
||||
|
||||
funcInfo, err = parseExtInfo(io.LimitReader(r, int64(header.FuncInfoLen)), bo, strings)
|
||||
if err != nil {
|
||||
return nil, nil, xerrors.Errorf("function info: %w", err)
|
||||
}
|
||||
|
||||
if _, err := r.Seek(int64(header.HdrLen+header.LineInfoOff), io.SeekStart); err != nil {
|
||||
return nil, nil, xerrors.Errorf("can't seek to line info section: %v", err)
|
||||
}
|
||||
|
||||
lineInfo, err = parseExtInfo(io.LimitReader(r, int64(header.LineInfoLen)), bo, strings)
|
||||
if err != nil {
|
||||
return nil, nil, xerrors.Errorf("line info: %w", err)
|
||||
}
|
||||
|
||||
return funcInfo, lineInfo, nil
|
||||
}
|
||||
|
||||
type btfExtInfoSec struct {
|
||||
SecNameOff uint32
|
||||
NumInfo uint32
|
||||
}
|
||||
|
||||
type extInfoRecord struct {
|
||||
InsnOff uint64
|
||||
Opaque []byte
|
||||
}
|
||||
|
||||
type extInfo struct {
|
||||
recordSize uint32
|
||||
records []extInfoRecord
|
||||
}
|
||||
|
||||
func (ei extInfo) append(other extInfo, offset uint64) (extInfo, error) {
|
||||
if other.recordSize != ei.recordSize {
|
||||
return extInfo{}, xerrors.Errorf("ext_info record size mismatch, want %d (got %d)", ei.recordSize, other.recordSize)
|
||||
}
|
||||
|
||||
records := make([]extInfoRecord, 0, len(ei.records)+len(other.records))
|
||||
records = append(records, ei.records...)
|
||||
for _, info := range other.records {
|
||||
records = append(records, extInfoRecord{
|
||||
InsnOff: info.InsnOff + offset,
|
||||
Opaque: info.Opaque,
|
||||
})
|
||||
}
|
||||
return extInfo{ei.recordSize, records}, nil
|
||||
}
|
||||
|
||||
func (ei extInfo) MarshalBinary() ([]byte, error) {
|
||||
if len(ei.records) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
buf := bytes.NewBuffer(make([]byte, 0, int(ei.recordSize)*len(ei.records)))
|
||||
for _, info := range ei.records {
|
||||
// The kernel expects offsets in number of raw bpf instructions,
|
||||
// while the ELF tracks it in bytes.
|
||||
insnOff := uint32(info.InsnOff / asm.InstructionSize)
|
||||
if err := binary.Write(buf, internal.NativeEndian, insnOff); err != nil {
|
||||
return nil, xerrors.Errorf("can't write instruction offset: %v", err)
|
||||
}
|
||||
|
||||
buf.Write(info.Opaque)
|
||||
}
|
||||
|
||||
return buf.Bytes(), nil
|
||||
}
|
||||
|
||||
func parseExtInfo(r io.Reader, bo binary.ByteOrder, strings stringTable) (map[string]extInfo, error) {
|
||||
var recordSize uint32
|
||||
if err := binary.Read(r, bo, &recordSize); err != nil {
|
||||
return nil, xerrors.Errorf("can't read record size: %v", err)
|
||||
}
|
||||
|
||||
if recordSize < 4 {
|
||||
// Need at least insnOff
|
||||
return nil, xerrors.New("record size too short")
|
||||
}
|
||||
|
||||
result := make(map[string]extInfo)
|
||||
for {
|
||||
var infoHeader btfExtInfoSec
|
||||
if err := binary.Read(r, bo, &infoHeader); err == io.EOF {
|
||||
return result, nil
|
||||
} else if err != nil {
|
||||
return nil, xerrors.Errorf("can't read ext info header: %v", err)
|
||||
}
|
||||
|
||||
secName, err := strings.Lookup(infoHeader.SecNameOff)
|
||||
if err != nil {
|
||||
return nil, xerrors.Errorf("can't get section name: %w", err)
|
||||
}
|
||||
|
||||
if infoHeader.NumInfo == 0 {
|
||||
return nil, xerrors.Errorf("section %s has invalid number of records", secName)
|
||||
}
|
||||
|
||||
var records []extInfoRecord
|
||||
for i := uint32(0); i < infoHeader.NumInfo; i++ {
|
||||
var byteOff uint32
|
||||
if err := binary.Read(r, bo, &byteOff); err != nil {
|
||||
return nil, xerrors.Errorf("section %v: can't read extended info offset: %v", secName, err)
|
||||
}
|
||||
|
||||
buf := make([]byte, int(recordSize-4))
|
||||
if _, err := io.ReadFull(r, buf); err != nil {
|
||||
return nil, xerrors.Errorf("section %v: can't read record: %v", secName, err)
|
||||
}
|
||||
|
||||
if byteOff%asm.InstructionSize != 0 {
|
||||
return nil, xerrors.Errorf("section %v: offset %v is not aligned with instruction size", secName, byteOff)
|
||||
}
|
||||
|
||||
records = append(records, extInfoRecord{uint64(byteOff), buf})
|
||||
}
|
||||
|
||||
result[secName] = extInfo{
|
||||
recordSize,
|
||||
records,
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,60 @@
|
|||
package btf
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
|
||||
"golang.org/x/xerrors"
|
||||
)
|
||||
|
||||
type stringTable []byte
|
||||
|
||||
func readStringTable(r io.Reader) (stringTable, error) {
|
||||
contents, err := ioutil.ReadAll(r)
|
||||
if err != nil {
|
||||
return nil, xerrors.Errorf("can't read string table: %v", err)
|
||||
}
|
||||
|
||||
if len(contents) < 1 {
|
||||
return nil, xerrors.New("string table is empty")
|
||||
}
|
||||
|
||||
if contents[0] != '\x00' {
|
||||
return nil, xerrors.New("first item in string table is non-empty")
|
||||
}
|
||||
|
||||
if contents[len(contents)-1] != '\x00' {
|
||||
return nil, xerrors.New("string table isn't null terminated")
|
||||
}
|
||||
|
||||
return stringTable(contents), nil
|
||||
}
|
||||
|
||||
func (st stringTable) Lookup(offset uint32) (string, error) {
|
||||
if int64(offset) > int64(^uint(0)>>1) {
|
||||
return "", xerrors.Errorf("offset %d overflows int", offset)
|
||||
}
|
||||
|
||||
pos := int(offset)
|
||||
if pos >= len(st) {
|
||||
return "", xerrors.Errorf("offset %d is out of bounds", offset)
|
||||
}
|
||||
|
||||
if pos > 0 && st[pos-1] != '\x00' {
|
||||
return "", xerrors.Errorf("offset %d isn't start of a string", offset)
|
||||
}
|
||||
|
||||
str := st[pos:]
|
||||
end := bytes.IndexByte(str, '\x00')
|
||||
if end == -1 {
|
||||
return "", xerrors.Errorf("offset %d isn't null terminated", offset)
|
||||
}
|
||||
|
||||
return string(str[:end]), nil
|
||||
}
|
||||
|
||||
func (st stringTable) LookupName(offset uint32) (Name, error) {
|
||||
str, err := st.Lookup(offset)
|
||||
return Name(str), err
|
||||
}
|
|
@ -0,0 +1,586 @@
|
|||
package btf
|
||||
|
||||
import (
|
||||
"math"
|
||||
|
||||
"golang.org/x/xerrors"
|
||||
)
|
||||
|
||||
const maxTypeDepth = 32
|
||||
|
||||
// TypeID identifies a type in a BTF section.
|
||||
type TypeID uint32
|
||||
|
||||
// ID implements part of the Type interface.
|
||||
func (tid TypeID) ID() TypeID {
|
||||
return tid
|
||||
}
|
||||
|
||||
// Type represents a type described by BTF.
|
||||
type Type interface {
|
||||
ID() TypeID
|
||||
|
||||
// Make a copy of the type, without copying Type members.
|
||||
copy() Type
|
||||
|
||||
walk(*copyStack)
|
||||
}
|
||||
|
||||
// Name identifies a type.
|
||||
//
|
||||
// Anonymous types have an empty name.
|
||||
type Name string
|
||||
|
||||
func (n Name) name() string {
|
||||
return string(n)
|
||||
}
|
||||
|
||||
// Void is the unit type of BTF.
|
||||
type Void struct{}
|
||||
|
||||
func (v Void) ID() TypeID { return 0 }
|
||||
func (v Void) copy() Type { return Void{} }
|
||||
func (v Void) walk(*copyStack) {}
|
||||
|
||||
// Int is an integer of a given length.
|
||||
type Int struct {
|
||||
TypeID
|
||||
Name
|
||||
|
||||
// The size of the integer in bytes.
|
||||
Size uint32
|
||||
}
|
||||
|
||||
func (i *Int) size() uint32 { return i.Size }
|
||||
func (i *Int) walk(*copyStack) {}
|
||||
func (i *Int) copy() Type {
|
||||
cpy := *i
|
||||
return &cpy
|
||||
}
|
||||
|
||||
// Pointer is a pointer to another type.
|
||||
type Pointer struct {
|
||||
TypeID
|
||||
Target Type
|
||||
}
|
||||
|
||||
func (p *Pointer) size() uint32 { return 8 }
|
||||
func (p *Pointer) walk(cs *copyStack) { cs.push(&p.Target) }
|
||||
func (p *Pointer) copy() Type {
|
||||
cpy := *p
|
||||
return &cpy
|
||||
}
|
||||
|
||||
// Array is an array with a fixed number of elements.
|
||||
type Array struct {
|
||||
TypeID
|
||||
Type Type
|
||||
Nelems uint32
|
||||
}
|
||||
|
||||
func (arr *Array) walk(cs *copyStack) { cs.push(&arr.Type) }
|
||||
func (arr *Array) copy() Type {
|
||||
cpy := *arr
|
||||
return &cpy
|
||||
}
|
||||
|
||||
// Struct is a compound type of consecutive members.
|
||||
type Struct struct {
|
||||
TypeID
|
||||
Name
|
||||
// The size of the struct including padding, in bytes
|
||||
Size uint32
|
||||
Members []Member
|
||||
}
|
||||
|
||||
func (s *Struct) size() uint32 { return s.Size }
|
||||
|
||||
func (s *Struct) walk(cs *copyStack) {
|
||||
for i := range s.Members {
|
||||
cs.push(&s.Members[i].Type)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Struct) copy() Type {
|
||||
cpy := *s
|
||||
cpy.Members = make([]Member, len(s.Members))
|
||||
copy(cpy.Members, s.Members)
|
||||
return &cpy
|
||||
}
|
||||
|
||||
// Union is a compound type where members occupy the same memory.
|
||||
type Union struct {
|
||||
TypeID
|
||||
Name
|
||||
// The size of the union including padding, in bytes.
|
||||
Size uint32
|
||||
Members []Member
|
||||
}
|
||||
|
||||
func (u *Union) size() uint32 { return u.Size }
|
||||
|
||||
func (u *Union) walk(cs *copyStack) {
|
||||
for i := range u.Members {
|
||||
cs.push(&u.Members[i].Type)
|
||||
}
|
||||
}
|
||||
|
||||
func (u *Union) copy() Type {
|
||||
cpy := *u
|
||||
cpy.Members = make([]Member, len(u.Members))
|
||||
copy(cpy.Members, u.Members)
|
||||
return &cpy
|
||||
}
|
||||
|
||||
// Member is part of a Struct or Union.
|
||||
//
|
||||
// It is not a valid Type.
|
||||
type Member struct {
|
||||
Name
|
||||
Type Type
|
||||
Offset uint32
|
||||
}
|
||||
|
||||
// Enum lists possible values.
|
||||
type Enum struct {
|
||||
TypeID
|
||||
Name
|
||||
}
|
||||
|
||||
func (e *Enum) size() uint32 { return 4 }
|
||||
func (e *Enum) walk(*copyStack) {}
|
||||
func (e *Enum) copy() Type {
|
||||
cpy := *e
|
||||
return &cpy
|
||||
}
|
||||
|
||||
// Fwd is a forward declaration of a Type.
|
||||
type Fwd struct {
|
||||
TypeID
|
||||
Name
|
||||
}
|
||||
|
||||
func (f *Fwd) walk(*copyStack) {}
|
||||
func (f *Fwd) copy() Type {
|
||||
cpy := *f
|
||||
return &cpy
|
||||
}
|
||||
|
||||
// Typedef is an alias of a Type.
|
||||
type Typedef struct {
|
||||
TypeID
|
||||
Name
|
||||
Type Type
|
||||
}
|
||||
|
||||
func (td *Typedef) walk(cs *copyStack) { cs.push(&td.Type) }
|
||||
func (td *Typedef) copy() Type {
|
||||
cpy := *td
|
||||
return &cpy
|
||||
}
|
||||
|
||||
// Volatile is a modifier.
|
||||
type Volatile struct {
|
||||
TypeID
|
||||
Type Type
|
||||
}
|
||||
|
||||
func (v *Volatile) walk(cs *copyStack) { cs.push(&v.Type) }
|
||||
func (v *Volatile) copy() Type {
|
||||
cpy := *v
|
||||
return &cpy
|
||||
}
|
||||
|
||||
// Const is a modifier.
|
||||
type Const struct {
|
||||
TypeID
|
||||
Type Type
|
||||
}
|
||||
|
||||
func (c *Const) walk(cs *copyStack) { cs.push(&c.Type) }
|
||||
func (c *Const) copy() Type {
|
||||
cpy := *c
|
||||
return &cpy
|
||||
}
|
||||
|
||||
// Restrict is a modifier.
|
||||
type Restrict struct {
|
||||
TypeID
|
||||
Type Type
|
||||
}
|
||||
|
||||
func (r *Restrict) walk(cs *copyStack) { cs.push(&r.Type) }
|
||||
func (r *Restrict) copy() Type {
|
||||
cpy := *r
|
||||
return &cpy
|
||||
}
|
||||
|
||||
// Func is a function definition.
|
||||
type Func struct {
|
||||
TypeID
|
||||
Name
|
||||
Type Type
|
||||
}
|
||||
|
||||
func (f *Func) walk(cs *copyStack) { cs.push(&f.Type) }
|
||||
func (f *Func) copy() Type {
|
||||
cpy := *f
|
||||
return &cpy
|
||||
}
|
||||
|
||||
// FuncProto is a function declaration.
|
||||
type FuncProto struct {
|
||||
TypeID
|
||||
Return Type
|
||||
// Parameters not supported yet
|
||||
}
|
||||
|
||||
func (fp *FuncProto) walk(cs *copyStack) { cs.push(&fp.Return) }
|
||||
func (fp *FuncProto) copy() Type {
|
||||
cpy := *fp
|
||||
return &cpy
|
||||
}
|
||||
|
||||
// Var is a global variable.
|
||||
type Var struct {
|
||||
TypeID
|
||||
Name
|
||||
Type Type
|
||||
}
|
||||
|
||||
func (v *Var) walk(cs *copyStack) { cs.push(&v.Type) }
|
||||
func (v *Var) copy() Type {
|
||||
cpy := *v
|
||||
return &cpy
|
||||
}
|
||||
|
||||
// Datasec is a global program section containing data.
|
||||
type Datasec struct {
|
||||
TypeID
|
||||
Name
|
||||
Size uint32
|
||||
Vars []VarSecinfo
|
||||
}
|
||||
|
||||
func (ds *Datasec) size() uint32 { return ds.Size }
|
||||
|
||||
func (ds *Datasec) walk(cs *copyStack) {
|
||||
for i := range ds.Vars {
|
||||
cs.push(&ds.Vars[i].Type)
|
||||
}
|
||||
}
|
||||
|
||||
func (ds *Datasec) copy() Type {
|
||||
cpy := *ds
|
||||
cpy.Vars = make([]VarSecinfo, len(ds.Vars))
|
||||
copy(cpy.Vars, ds.Vars)
|
||||
return &cpy
|
||||
}
|
||||
|
||||
// VarSecinfo describes variable in a Datasec
|
||||
type VarSecinfo struct {
|
||||
Type Type
|
||||
Offset uint32
|
||||
Size uint32
|
||||
}
|
||||
|
||||
type sizer interface {
|
||||
size() uint32
|
||||
}
|
||||
|
||||
var (
|
||||
_ sizer = (*Int)(nil)
|
||||
_ sizer = (*Pointer)(nil)
|
||||
_ sizer = (*Struct)(nil)
|
||||
_ sizer = (*Union)(nil)
|
||||
_ sizer = (*Enum)(nil)
|
||||
_ sizer = (*Datasec)(nil)
|
||||
)
|
||||
|
||||
// Sizeof returns the size of a type in bytes.
|
||||
//
|
||||
// Returns an error if the size can't be computed.
|
||||
func Sizeof(typ Type) (int, error) {
|
||||
var (
|
||||
n = int64(1)
|
||||
elem int64
|
||||
)
|
||||
|
||||
for i := 0; i < maxTypeDepth; i++ {
|
||||
switch v := typ.(type) {
|
||||
case *Array:
|
||||
if n > 0 && int64(v.Nelems) > math.MaxInt64/n {
|
||||
return 0, xerrors.New("overflow")
|
||||
}
|
||||
|
||||
// Arrays may be of zero length, which allows
|
||||
// n to be zero as well.
|
||||
n *= int64(v.Nelems)
|
||||
typ = v.Type
|
||||
continue
|
||||
|
||||
case sizer:
|
||||
elem = int64(v.size())
|
||||
|
||||
case *Typedef:
|
||||
typ = v.Type
|
||||
continue
|
||||
case *Volatile:
|
||||
typ = v.Type
|
||||
continue
|
||||
case *Const:
|
||||
typ = v.Type
|
||||
continue
|
||||
case *Restrict:
|
||||
typ = v.Type
|
||||
continue
|
||||
|
||||
default:
|
||||
return 0, xerrors.Errorf("unrecognized type %T", typ)
|
||||
}
|
||||
|
||||
if n > 0 && elem > math.MaxInt64/n {
|
||||
return 0, xerrors.New("overflow")
|
||||
}
|
||||
|
||||
size := n * elem
|
||||
if int64(int(size)) != size {
|
||||
return 0, xerrors.New("overflow")
|
||||
}
|
||||
|
||||
return int(size), nil
|
||||
}
|
||||
|
||||
return 0, xerrors.New("exceeded type depth")
|
||||
}
|
||||
|
||||
// copy a Type recursively.
|
||||
//
|
||||
// typ may form a cycle.
|
||||
func copyType(typ Type) Type {
|
||||
var (
|
||||
copies = make(map[Type]Type)
|
||||
work copyStack
|
||||
)
|
||||
|
||||
for t := &typ; t != nil; t = work.pop() {
|
||||
// *t is the identity of the type.
|
||||
if cpy := copies[*t]; cpy != nil {
|
||||
*t = cpy
|
||||
continue
|
||||
}
|
||||
|
||||
cpy := (*t).copy()
|
||||
copies[*t] = cpy
|
||||
*t = cpy
|
||||
|
||||
// Mark any nested types for copying.
|
||||
cpy.walk(&work)
|
||||
}
|
||||
|
||||
return typ
|
||||
}
|
||||
|
||||
// copyStack keeps track of pointers to types which still
|
||||
// need to be copied.
|
||||
type copyStack []*Type
|
||||
|
||||
// push adds a type to the stack.
|
||||
func (cs *copyStack) push(t *Type) {
|
||||
*cs = append(*cs, t)
|
||||
}
|
||||
|
||||
// pop returns the topmost Type, or nil.
|
||||
func (cs *copyStack) pop() *Type {
|
||||
n := len(*cs)
|
||||
if n == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
t := (*cs)[n-1]
|
||||
*cs = (*cs)[:n-1]
|
||||
return t
|
||||
}
|
||||
|
||||
type namer interface {
|
||||
name() string
|
||||
}
|
||||
|
||||
var _ namer = Name("")
|
||||
|
||||
// inflateRawTypes takes a list of raw btf types linked via type IDs, and turns
|
||||
// it into a graph of Types connected via pointers.
|
||||
//
|
||||
// Returns a map of named types (so, where NameOff is non-zero). Since BTF ignores
|
||||
// compilation units, multiple types may share the same name. A Type may form a
|
||||
// cyclic graph by pointing at itself.
|
||||
func inflateRawTypes(rawTypes []rawType, rawStrings stringTable) (namedTypes map[string][]Type, err error) {
|
||||
type fixupDef struct {
|
||||
id TypeID
|
||||
expectedKind btfKind
|
||||
typ *Type
|
||||
}
|
||||
|
||||
var fixups []fixupDef
|
||||
fixup := func(id TypeID, expectedKind btfKind, typ *Type) {
|
||||
fixups = append(fixups, fixupDef{id, expectedKind, typ})
|
||||
}
|
||||
|
||||
convertMembers := func(raw []btfMember) ([]Member, error) {
|
||||
// NB: The fixup below relies on pre-allocating this array to
|
||||
// work, since otherwise append might re-allocate members.
|
||||
members := make([]Member, 0, len(raw))
|
||||
for i, btfMember := range raw {
|
||||
name, err := rawStrings.LookupName(btfMember.NameOff)
|
||||
if err != nil {
|
||||
return nil, xerrors.Errorf("can't get name for member %d: %w", i, err)
|
||||
}
|
||||
members = append(members, Member{
|
||||
Name: name,
|
||||
Offset: btfMember.Offset,
|
||||
})
|
||||
}
|
||||
for i := range members {
|
||||
fixup(raw[i].Type, kindUnknown, &members[i].Type)
|
||||
}
|
||||
return members, nil
|
||||
}
|
||||
|
||||
types := make([]Type, 0, len(rawTypes))
|
||||
types = append(types, Void{})
|
||||
namedTypes = make(map[string][]Type)
|
||||
|
||||
for i, raw := range rawTypes {
|
||||
var (
|
||||
// Void is defined to always be type ID 0, and is thus
|
||||
// omitted from BTF.
|
||||
id = TypeID(i + 1)
|
||||
typ Type
|
||||
)
|
||||
|
||||
name, err := rawStrings.LookupName(raw.NameOff)
|
||||
if err != nil {
|
||||
return nil, xerrors.Errorf("can't get name for type id %d: %w", id, err)
|
||||
}
|
||||
|
||||
switch raw.Kind() {
|
||||
case kindInt:
|
||||
typ = &Int{id, name, raw.Size()}
|
||||
|
||||
case kindPointer:
|
||||
ptr := &Pointer{id, nil}
|
||||
fixup(raw.Type(), kindUnknown, &ptr.Target)
|
||||
typ = ptr
|
||||
|
||||
case kindArray:
|
||||
btfArr := raw.data.(*btfArray)
|
||||
|
||||
// IndexType is unused according to btf.rst.
|
||||
// Don't make it available right now.
|
||||
arr := &Array{id, nil, btfArr.Nelems}
|
||||
fixup(btfArr.Type, kindUnknown, &arr.Type)
|
||||
typ = arr
|
||||
|
||||
case kindStruct:
|
||||
members, err := convertMembers(raw.data.([]btfMember))
|
||||
if err != nil {
|
||||
return nil, xerrors.Errorf("struct %s (id %d): %w", name, id, err)
|
||||
}
|
||||
typ = &Struct{id, name, raw.Size(), members}
|
||||
|
||||
case kindUnion:
|
||||
members, err := convertMembers(raw.data.([]btfMember))
|
||||
if err != nil {
|
||||
return nil, xerrors.Errorf("union %s (id %d): %w", name, id, err)
|
||||
}
|
||||
typ = &Union{id, name, raw.Size(), members}
|
||||
|
||||
case kindEnum:
|
||||
typ = &Enum{id, name}
|
||||
|
||||
case kindForward:
|
||||
typ = &Fwd{id, name}
|
||||
|
||||
case kindTypedef:
|
||||
typedef := &Typedef{id, name, nil}
|
||||
fixup(raw.Type(), kindUnknown, &typedef.Type)
|
||||
typ = typedef
|
||||
|
||||
case kindVolatile:
|
||||
volatile := &Volatile{id, nil}
|
||||
fixup(raw.Type(), kindUnknown, &volatile.Type)
|
||||
typ = volatile
|
||||
|
||||
case kindConst:
|
||||
cnst := &Const{id, nil}
|
||||
fixup(raw.Type(), kindUnknown, &cnst.Type)
|
||||
typ = cnst
|
||||
|
||||
case kindRestrict:
|
||||
restrict := &Restrict{id, nil}
|
||||
fixup(raw.Type(), kindUnknown, &restrict.Type)
|
||||
typ = restrict
|
||||
|
||||
case kindFunc:
|
||||
fn := &Func{id, name, nil}
|
||||
fixup(raw.Type(), kindFuncProto, &fn.Type)
|
||||
typ = fn
|
||||
|
||||
case kindFuncProto:
|
||||
fp := &FuncProto{id, nil}
|
||||
fixup(raw.Type(), kindUnknown, &fp.Return)
|
||||
typ = fp
|
||||
|
||||
case kindVar:
|
||||
v := &Var{id, name, nil}
|
||||
fixup(raw.Type(), kindUnknown, &v.Type)
|
||||
typ = v
|
||||
|
||||
case kindDatasec:
|
||||
btfVars := raw.data.([]btfVarSecinfo)
|
||||
vars := make([]VarSecinfo, 0, len(btfVars))
|
||||
for _, btfVar := range btfVars {
|
||||
vars = append(vars, VarSecinfo{
|
||||
Offset: btfVar.Offset,
|
||||
Size: btfVar.Size,
|
||||
})
|
||||
}
|
||||
for i := range vars {
|
||||
fixup(btfVars[i].Type, kindVar, &vars[i].Type)
|
||||
}
|
||||
typ = &Datasec{id, name, raw.SizeType, vars}
|
||||
|
||||
default:
|
||||
return nil, xerrors.Errorf("type id %d: unknown kind: %v", id, raw.Kind())
|
||||
}
|
||||
|
||||
types = append(types, typ)
|
||||
|
||||
if namer, ok := typ.(namer); ok {
|
||||
if name := namer.name(); name != "" {
|
||||
namedTypes[name] = append(namedTypes[name], typ)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for _, fixup := range fixups {
|
||||
i := int(fixup.id)
|
||||
if i >= len(types) {
|
||||
return nil, xerrors.Errorf("reference to invalid type id: %d", fixup.id)
|
||||
}
|
||||
|
||||
// Default void (id 0) to unknown
|
||||
rawKind := kindUnknown
|
||||
if i > 0 {
|
||||
rawKind = rawTypes[i-1].Kind()
|
||||
}
|
||||
|
||||
if expected := fixup.expectedKind; expected != kindUnknown && rawKind != expected {
|
||||
return nil, xerrors.Errorf("expected type id %d to have kind %s, found %s", fixup.id, expected, rawKind)
|
||||
}
|
||||
|
||||
*fixup.typ = types[i]
|
||||
}
|
||||
|
||||
return namedTypes, nil
|
||||
}
|
|
@ -2,10 +2,9 @@ package internal
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"io/ioutil"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
var sysCPU struct {
|
||||
|
@ -18,45 +17,44 @@ var sysCPU struct {
|
|||
// Logical CPU numbers must be of the form 0-n
|
||||
func PossibleCPUs() (int, error) {
|
||||
sysCPU.once.Do(func() {
|
||||
sysCPU.num, sysCPU.err = parseCPUs("/sys/devices/system/cpu/possible")
|
||||
sysCPU.num, sysCPU.err = parseCPUsFromFile("/sys/devices/system/cpu/possible")
|
||||
})
|
||||
|
||||
return sysCPU.num, sysCPU.err
|
||||
}
|
||||
|
||||
var onlineCPU struct {
|
||||
once sync.Once
|
||||
err error
|
||||
num int
|
||||
}
|
||||
|
||||
// OnlineCPUs returns the number of currently online CPUs
|
||||
// Logical CPU numbers must be of the form 0-n
|
||||
func OnlineCPUs() (int, error) {
|
||||
onlineCPU.once.Do(func() {
|
||||
onlineCPU.num, onlineCPU.err = parseCPUs("/sys/devices/system/cpu/online")
|
||||
})
|
||||
|
||||
return onlineCPU.num, onlineCPU.err
|
||||
}
|
||||
|
||||
// parseCPUs parses the number of cpus from sysfs,
|
||||
// in the format of "/sys/devices/system/cpu/{possible,online,..}.
|
||||
// Logical CPU numbers must be of the form 0-n
|
||||
func parseCPUs(path string) (int, error) {
|
||||
file, err := os.Open(path)
|
||||
func parseCPUsFromFile(path string) (int, error) {
|
||||
spec, err := ioutil.ReadFile(path)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
n, err := parseCPUs(string(spec))
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("can't parse %s: %v", path, err)
|
||||
}
|
||||
|
||||
return n, nil
|
||||
}
|
||||
|
||||
// parseCPUs parses the number of cpus from a string produced
|
||||
// by bitmap_list_string() in the Linux kernel.
|
||||
// Multiple ranges are rejected, since they can't be unified
|
||||
// into a single number.
|
||||
// This is the format of /sys/devices/system/cpu/possible, it
|
||||
// is not suitable for /sys/devices/system/cpu/online, etc.
|
||||
func parseCPUs(spec string) (int, error) {
|
||||
if strings.Trim(spec, "\n") == "0" {
|
||||
return 1, nil
|
||||
}
|
||||
|
||||
var low, high int
|
||||
n, _ := fmt.Fscanf(file, "%d-%d", &low, &high)
|
||||
if n < 1 || low != 0 {
|
||||
return 0, errors.Wrapf(err, "%s has unknown format", path)
|
||||
n, err := fmt.Sscanf(spec, "%d-%d\n", &low, &high)
|
||||
if n != 2 || err != nil {
|
||||
return 0, fmt.Errorf("invalid format: %s", spec)
|
||||
}
|
||||
if n == 1 {
|
||||
high = low
|
||||
if low != 0 {
|
||||
return 0, fmt.Errorf("CPU spec doesn't start at zero: %s", spec)
|
||||
}
|
||||
|
||||
// cpus is 0 indexed
|
||||
|
|
|
@ -0,0 +1,47 @@
|
|||
package internal
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/cilium/ebpf/internal/unix"
|
||||
"golang.org/x/xerrors"
|
||||
)
|
||||
|
||||
// ErrorWithLog returns an error that includes logs from the
|
||||
// kernel verifier.
|
||||
//
|
||||
// logErr should be the error returned by the syscall that generated
|
||||
// the log. It is used to check for truncation of the output.
|
||||
func ErrorWithLog(err error, log []byte, logErr error) error {
|
||||
logStr := strings.Trim(CString(log), "\t\r\n ")
|
||||
if xerrors.Is(logErr, unix.ENOSPC) {
|
||||
logStr += " (truncated...)"
|
||||
}
|
||||
|
||||
return &VerifierError{err, logStr}
|
||||
}
|
||||
|
||||
// VerifierError includes information from the eBPF verifier.
|
||||
type VerifierError struct {
|
||||
cause error
|
||||
log string
|
||||
}
|
||||
|
||||
func (le *VerifierError) Error() string {
|
||||
if le.log == "" {
|
||||
return le.cause.Error()
|
||||
}
|
||||
|
||||
return fmt.Sprintf("%s: %s", le.cause, le.log)
|
||||
}
|
||||
|
||||
// CString turns a NUL / zero terminated byte buffer into a string.
|
||||
func CString(in []byte) string {
|
||||
inLen := bytes.IndexByte(in, 0)
|
||||
if inLen == -1 {
|
||||
return ""
|
||||
}
|
||||
return string(in[:inLen])
|
||||
}
|
|
@ -0,0 +1,63 @@
|
|||
package internal
|
||||
|
||||
import (
|
||||
"runtime"
|
||||
"strconv"
|
||||
|
||||
"github.com/cilium/ebpf/internal/unix"
|
||||
|
||||
"golang.org/x/xerrors"
|
||||
)
|
||||
|
||||
var ErrClosedFd = xerrors.New("use of closed file descriptor")
|
||||
|
||||
type FD struct {
|
||||
raw int64
|
||||
}
|
||||
|
||||
func NewFD(value uint32) *FD {
|
||||
fd := &FD{int64(value)}
|
||||
runtime.SetFinalizer(fd, (*FD).Close)
|
||||
return fd
|
||||
}
|
||||
|
||||
func (fd *FD) String() string {
|
||||
return strconv.FormatInt(fd.raw, 10)
|
||||
}
|
||||
|
||||
func (fd *FD) Value() (uint32, error) {
|
||||
if fd.raw < 0 {
|
||||
return 0, ErrClosedFd
|
||||
}
|
||||
|
||||
return uint32(fd.raw), nil
|
||||
}
|
||||
|
||||
func (fd *FD) Close() error {
|
||||
if fd.raw < 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
value := int(fd.raw)
|
||||
fd.raw = -1
|
||||
|
||||
fd.Forget()
|
||||
return unix.Close(value)
|
||||
}
|
||||
|
||||
func (fd *FD) Forget() {
|
||||
runtime.SetFinalizer(fd, nil)
|
||||
}
|
||||
|
||||
func (fd *FD) Dup() (*FD, error) {
|
||||
if fd.raw < 0 {
|
||||
return nil, ErrClosedFd
|
||||
}
|
||||
|
||||
dup, err := unix.FcntlInt(uintptr(fd.raw), unix.F_DUPFD_CLOEXEC, 0)
|
||||
if err != nil {
|
||||
return nil, xerrors.Errorf("can't dup fd: %v", err)
|
||||
}
|
||||
|
||||
return NewFD(uint32(dup)), nil
|
||||
}
|
|
@ -0,0 +1,93 @@
|
|||
package internal
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sync"
|
||||
|
||||
"golang.org/x/xerrors"
|
||||
)
|
||||
|
||||
// ErrNotSupported indicates that a feature is not supported by the current kernel.
|
||||
var ErrNotSupported = xerrors.New("not supported")
|
||||
|
||||
// UnsupportedFeatureError is returned by FeatureTest() functions.
|
||||
type UnsupportedFeatureError struct {
|
||||
// The minimum Linux mainline version required for this feature.
|
||||
// Used for the error string, and for sanity checking during testing.
|
||||
MinimumVersion Version
|
||||
|
||||
// The name of the feature that isn't supported.
|
||||
Name string
|
||||
}
|
||||
|
||||
func (ufe *UnsupportedFeatureError) Error() string {
|
||||
return fmt.Sprintf("%s not supported (requires >= %s)", ufe.Name, ufe.MinimumVersion)
|
||||
}
|
||||
|
||||
// Is indicates that UnsupportedFeatureError is ErrNotSupported.
|
||||
func (ufe *UnsupportedFeatureError) Is(target error) bool {
|
||||
return target == ErrNotSupported
|
||||
}
|
||||
|
||||
// FeatureTest wraps a function so that it is run at most once.
|
||||
//
|
||||
// name should identify the tested feature, while version must be in the
|
||||
// form Major.Minor[.Patch].
|
||||
//
|
||||
// Returns a descriptive UnsupportedFeatureError if the feature is not available.
|
||||
func FeatureTest(name, version string, fn func() bool) func() error {
|
||||
v, err := NewVersion(version)
|
||||
if err != nil {
|
||||
return func() error { return err }
|
||||
}
|
||||
|
||||
var (
|
||||
once sync.Once
|
||||
result error
|
||||
)
|
||||
|
||||
return func() error {
|
||||
once.Do(func() {
|
||||
if !fn() {
|
||||
result = &UnsupportedFeatureError{
|
||||
MinimumVersion: v,
|
||||
Name: name,
|
||||
}
|
||||
}
|
||||
})
|
||||
return result
|
||||
}
|
||||
}
|
||||
|
||||
// A Version in the form Major.Minor.Patch.
|
||||
type Version [3]uint16
|
||||
|
||||
// NewVersion creates a version from a string like "Major.Minor.Patch".
|
||||
//
|
||||
// Patch is optional.
|
||||
func NewVersion(ver string) (Version, error) {
|
||||
var major, minor, patch uint16
|
||||
n, _ := fmt.Sscanf(ver, "%d.%d.%d", &major, &minor, &patch)
|
||||
if n < 2 {
|
||||
return Version{}, xerrors.Errorf("invalid version: %s", ver)
|
||||
}
|
||||
return Version{major, minor, patch}, nil
|
||||
}
|
||||
|
||||
func (v Version) String() string {
|
||||
if v[2] == 0 {
|
||||
return fmt.Sprintf("v%d.%d", v[0], v[1])
|
||||
}
|
||||
return fmt.Sprintf("v%d.%d.%d", v[0], v[1], v[2])
|
||||
}
|
||||
|
||||
// Less returns true if the version is less than another version.
|
||||
func (v Version) Less(other Version) bool {
|
||||
for i, a := range v {
|
||||
if a == other[i] {
|
||||
continue
|
||||
}
|
||||
return a < other[i]
|
||||
}
|
||||
return false
|
||||
}
|
|
@ -0,0 +1,16 @@
|
|||
package internal
|
||||
|
||||
import "golang.org/x/xerrors"
|
||||
|
||||
// DiscardZeroes makes sure that all written bytes are zero
|
||||
// before discarding them.
|
||||
type DiscardZeroes struct{}
|
||||
|
||||
func (DiscardZeroes) Write(p []byte) (int, error) {
|
||||
for _, b := range p {
|
||||
if b != 0 {
|
||||
return 0, xerrors.New("encountered non-zero byte")
|
||||
}
|
||||
}
|
||||
return len(p), nil
|
||||
}
|
|
@ -0,0 +1,30 @@
|
|||
package internal
|
||||
|
||||
import "unsafe"
|
||||
|
||||
// NewPointer creates a 64-bit pointer from an unsafe Pointer.
|
||||
func NewPointer(ptr unsafe.Pointer) Pointer {
|
||||
return Pointer{ptr: ptr}
|
||||
}
|
||||
|
||||
// NewSlicePointer creates a 64-bit pointer from a byte slice.
|
||||
func NewSlicePointer(buf []byte) Pointer {
|
||||
if len(buf) == 0 {
|
||||
return Pointer{}
|
||||
}
|
||||
|
||||
return Pointer{ptr: unsafe.Pointer(&buf[0])}
|
||||
}
|
||||
|
||||
// NewStringPointer creates a 64-bit pointer from a string.
|
||||
func NewStringPointer(str string) Pointer {
|
||||
if str == "" {
|
||||
return Pointer{}
|
||||
}
|
||||
|
||||
// The kernel expects strings to be zero terminated
|
||||
buf := make([]byte, len(str)+1)
|
||||
copy(buf, str)
|
||||
|
||||
return Pointer{ptr: unsafe.Pointer(&buf[0])}
|
||||
}
|
|
@ -1,14 +1,14 @@
|
|||
// +build armbe mips mips64p32
|
||||
|
||||
package ebpf
|
||||
package internal
|
||||
|
||||
import (
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
// ptr wraps an unsafe.Pointer to be 64bit to
|
||||
// Pointer wraps an unsafe.Pointer to be 64bit to
|
||||
// conform to the syscall specification.
|
||||
type syscallPtr struct {
|
||||
type Pointer struct {
|
||||
pad uint32
|
||||
ptr unsafe.Pointer
|
||||
}
|
|
@ -1,14 +1,14 @@
|
|||
// +build 386 amd64p32 arm mipsle mips64p32le
|
||||
|
||||
package ebpf
|
||||
package internal
|
||||
|
||||
import (
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
// ptr wraps an unsafe.Pointer to be 64bit to
|
||||
// Pointer wraps an unsafe.Pointer to be 64bit to
|
||||
// conform to the syscall specification.
|
||||
type syscallPtr struct {
|
||||
type Pointer struct {
|
||||
ptr unsafe.Pointer
|
||||
pad uint32
|
||||
}
|
|
@ -1,14 +1,14 @@
|
|||
// +build !386,!amd64p32,!arm,!mipsle,!mips64p32le
|
||||
// +build !armbe,!mips,!mips64p32
|
||||
|
||||
package ebpf
|
||||
package internal
|
||||
|
||||
import (
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
// ptr wraps an unsafe.Pointer to be 64bit to
|
||||
// Pointer wraps an unsafe.Pointer to be 64bit to
|
||||
// conform to the syscall specification.
|
||||
type syscallPtr struct {
|
||||
type Pointer struct {
|
||||
ptr unsafe.Pointer
|
||||
}
|
|
@ -0,0 +1,23 @@
|
|||
package internal
|
||||
|
||||
import (
|
||||
"runtime"
|
||||
"unsafe"
|
||||
|
||||
"github.com/cilium/ebpf/internal/unix"
|
||||
)
|
||||
|
||||
// BPF wraps SYS_BPF.
|
||||
//
|
||||
// Any pointers contained in attr must use the Pointer type from this package.
|
||||
func BPF(cmd int, attr unsafe.Pointer, size uintptr) (uintptr, error) {
|
||||
r1, _, errNo := unix.Syscall(unix.SYS_BPF, uintptr(cmd), uintptr(attr), size)
|
||||
runtime.KeepAlive(attr)
|
||||
|
||||
var err error
|
||||
if errNo != 0 {
|
||||
err = errNo
|
||||
}
|
||||
|
||||
return r1, err
|
||||
}
|
|
@ -14,6 +14,11 @@ const (
|
|||
ENOSPC = linux.ENOSPC
|
||||
EINVAL = linux.EINVAL
|
||||
EPOLLIN = linux.EPOLLIN
|
||||
EINTR = linux.EINTR
|
||||
ESRCH = linux.ESRCH
|
||||
ENODEV = linux.ENODEV
|
||||
BPF_F_RDONLY_PROG = linux.BPF_F_RDONLY_PROG
|
||||
BPF_F_WRONLY_PROG = linux.BPF_F_WRONLY_PROG
|
||||
BPF_OBJ_NAME_LEN = linux.BPF_OBJ_NAME_LEN
|
||||
BPF_TAG_SIZE = linux.BPF_TAG_SIZE
|
||||
SYS_BPF = linux.SYS_BPF
|
||||
|
@ -30,6 +35,7 @@ const (
|
|||
PerfBitWatermark = linux.PerfBitWatermark
|
||||
PERF_SAMPLE_RAW = linux.PERF_SAMPLE_RAW
|
||||
PERF_FLAG_FD_CLOEXEC = linux.PERF_FLAG_FD_CLOEXEC
|
||||
RLIM_INFINITY = linux.RLIM_INFINITY
|
||||
)
|
||||
|
||||
// Statfs_t is a wrapper
|
||||
|
@ -116,3 +122,26 @@ type PerfEventAttr = linux.PerfEventAttr
|
|||
func PerfEventOpen(attr *PerfEventAttr, pid int, cpu int, groupFd int, flags int) (fd int, err error) {
|
||||
return linux.PerfEventOpen(attr, pid, cpu, groupFd, flags)
|
||||
}
|
||||
|
||||
// Utsname is a wrapper
|
||||
type Utsname = linux.Utsname
|
||||
|
||||
// Uname is a wrapper
|
||||
func Uname(buf *Utsname) (err error) {
|
||||
return linux.Uname(buf)
|
||||
}
|
||||
|
||||
// Getpid is a wrapper
|
||||
func Getpid() int {
|
||||
return linux.Getpid()
|
||||
}
|
||||
|
||||
// Gettid is a wrapper
|
||||
func Gettid() int {
|
||||
return linux.Gettid()
|
||||
}
|
||||
|
||||
// Tgkill is a wrapper
|
||||
func Tgkill(tgid int, tid int, sig syscall.Signal) (err error) {
|
||||
return linux.Tgkill(tgid, tid, sig)
|
||||
}
|
||||
|
|
|
@ -15,6 +15,11 @@ const (
|
|||
EAGAIN = syscall.EAGAIN
|
||||
ENOSPC = syscall.ENOSPC
|
||||
EINVAL = syscall.EINVAL
|
||||
EINTR = syscall.EINTR
|
||||
ESRCH = syscall.ESRCH
|
||||
ENODEV = syscall.ENODEV
|
||||
BPF_F_RDONLY_PROG = 0
|
||||
BPF_F_WRONLY_PROG = 0
|
||||
BPF_OBJ_NAME_LEN = 0x10
|
||||
BPF_TAG_SIZE = 0x8
|
||||
SYS_BPF = 321
|
||||
|
@ -32,6 +37,7 @@ const (
|
|||
PerfBitWatermark = 0x4000
|
||||
PERF_SAMPLE_RAW = 0x400
|
||||
PERF_FLAG_FD_CLOEXEC = 0x8
|
||||
RLIM_INFINITY = 0x7fffffffffffffff
|
||||
)
|
||||
|
||||
// Statfs_t is a wrapper
|
||||
|
@ -181,3 +187,28 @@ type PerfEventAttr struct {
|
|||
func PerfEventOpen(attr *PerfEventAttr, pid int, cpu int, groupFd int, flags int) (fd int, err error) {
|
||||
return 0, errNonLinux
|
||||
}
|
||||
|
||||
// Utsname is a wrapper
|
||||
type Utsname struct {
|
||||
Release [65]byte
|
||||
}
|
||||
|
||||
// Uname is a wrapper
|
||||
func Uname(buf *Utsname) (err error) {
|
||||
return errNonLinux
|
||||
}
|
||||
|
||||
// Getpid is a wrapper
|
||||
func Getpid() int {
|
||||
return -1
|
||||
}
|
||||
|
||||
// Gettid is a wrapper
|
||||
func Gettid() int {
|
||||
return -1
|
||||
}
|
||||
|
||||
// Tgkill is a wrapper
|
||||
func Tgkill(tgid int, tid int, sig syscall.Signal) (err error) {
|
||||
return errNonLinux
|
||||
}
|
||||
|
|
|
@ -2,30 +2,59 @@ package ebpf
|
|||
|
||||
import (
|
||||
"github.com/cilium/ebpf/asm"
|
||||
"github.com/cilium/ebpf/internal/btf"
|
||||
|
||||
"golang.org/x/xerrors"
|
||||
)
|
||||
|
||||
// link resolves bpf-to-bpf calls.
|
||||
//
|
||||
// Each section may contain multiple functions / labels, and is only linked
|
||||
// if the program being edited references one of these functions.
|
||||
// Each library may contain multiple functions / labels, and is only linked
|
||||
// if prog references one of these functions.
|
||||
//
|
||||
// Sections must not require linking themselves.
|
||||
func link(insns asm.Instructions, sections ...asm.Instructions) (asm.Instructions, error) {
|
||||
for _, section := range sections {
|
||||
var err error
|
||||
insns, err = linkSection(insns, section)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
// Libraries also linked.
|
||||
func link(prog *ProgramSpec, libs []*ProgramSpec) error {
|
||||
var (
|
||||
linked = make(map[*ProgramSpec]bool)
|
||||
pending = []asm.Instructions{prog.Instructions}
|
||||
insns asm.Instructions
|
||||
)
|
||||
for len(pending) > 0 {
|
||||
insns, pending = pending[0], pending[1:]
|
||||
for _, lib := range libs {
|
||||
if linked[lib] {
|
||||
continue
|
||||
}
|
||||
|
||||
needed, err := needSection(insns, lib.Instructions)
|
||||
if err != nil {
|
||||
return xerrors.Errorf("linking %s: %w", lib.Name, err)
|
||||
}
|
||||
|
||||
if !needed {
|
||||
continue
|
||||
}
|
||||
|
||||
linked[lib] = true
|
||||
prog.Instructions = append(prog.Instructions, lib.Instructions...)
|
||||
pending = append(pending, lib.Instructions)
|
||||
|
||||
if prog.BTF != nil && lib.BTF != nil {
|
||||
if err := btf.ProgramAppend(prog.BTF, lib.BTF); err != nil {
|
||||
return xerrors.Errorf("linking BTF of %s: %w", lib.Name, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return insns, nil
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func linkSection(insns, section asm.Instructions) (asm.Instructions, error) {
|
||||
func needSection(insns, section asm.Instructions) (bool, error) {
|
||||
// A map of symbols to the libraries which contain them.
|
||||
symbols, err := section.SymbolOffsets()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return false, err
|
||||
}
|
||||
|
||||
for _, ins := range insns {
|
||||
|
@ -33,7 +62,7 @@ func linkSection(insns, section asm.Instructions) (asm.Instructions, error) {
|
|||
continue
|
||||
}
|
||||
|
||||
if ins.OpCode.JumpOp() != asm.Call || ins.Src != asm.R1 {
|
||||
if ins.OpCode.JumpOp() != asm.Call || ins.Src != asm.PseudoCall {
|
||||
continue
|
||||
}
|
||||
|
||||
|
@ -48,11 +77,10 @@ func linkSection(insns, section asm.Instructions) (asm.Instructions, error) {
|
|||
}
|
||||
|
||||
// At this point we know that at least one function in the
|
||||
// library is called from insns. Merge the two sections.
|
||||
// The rewrite of ins.Constant happens in asm.Instruction.Marshal.
|
||||
return append(insns, section...), nil
|
||||
// library is called from insns, so we have to link it.
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// None of the functions in the section are called. Do nothing.
|
||||
return insns, nil
|
||||
// None of the functions in the section are called.
|
||||
return false, nil
|
||||
}
|
||||
|
|
|
@ -2,14 +2,24 @@ package ebpf
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"unsafe"
|
||||
"strings"
|
||||
|
||||
"github.com/cilium/ebpf/internal"
|
||||
"github.com/cilium/ebpf/internal/btf"
|
||||
"github.com/cilium/ebpf/internal/unix"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"golang.org/x/xerrors"
|
||||
)
|
||||
|
||||
// Errors returned by Map and MapIterator methods.
|
||||
var (
|
||||
ErrKeyNotExist = xerrors.New("key does not exist")
|
||||
ErrIterationAborted = xerrors.New("iteration aborted")
|
||||
)
|
||||
|
||||
// MapID represents the unique ID of an eBPF map
|
||||
type MapID uint32
|
||||
|
||||
// MapSpec defines a Map.
|
||||
type MapSpec struct {
|
||||
// Name is passed to the kernel as a debug aid. Must only contain
|
||||
|
@ -20,8 +30,18 @@ type MapSpec struct {
|
|||
ValueSize uint32
|
||||
MaxEntries uint32
|
||||
Flags uint32
|
||||
|
||||
// The initial contents of the map. May be nil.
|
||||
Contents []MapKV
|
||||
|
||||
// Whether to freeze a map after setting its initial contents.
|
||||
Freeze bool
|
||||
|
||||
// InnerMap is used as a template for ArrayOfMaps and HashOfMaps
|
||||
InnerMap *MapSpec
|
||||
|
||||
// The BTF associated with this map.
|
||||
BTF *btf.Map
|
||||
}
|
||||
|
||||
func (ms *MapSpec) String() string {
|
||||
|
@ -29,16 +49,26 @@ func (ms *MapSpec) String() string {
|
|||
}
|
||||
|
||||
// Copy returns a copy of the spec.
|
||||
//
|
||||
// MapSpec.Contents is a shallow copy.
|
||||
func (ms *MapSpec) Copy() *MapSpec {
|
||||
if ms == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
cpy := *ms
|
||||
cpy.Contents = make([]MapKV, len(ms.Contents))
|
||||
copy(cpy.Contents, ms.Contents)
|
||||
cpy.InnerMap = ms.InnerMap.Copy()
|
||||
return &cpy
|
||||
}
|
||||
|
||||
// MapKV is used to initialize the contents of a Map.
|
||||
type MapKV struct {
|
||||
Key interface{}
|
||||
Value interface{}
|
||||
}
|
||||
|
||||
// Map represents a Map file descriptor.
|
||||
//
|
||||
// It is not safe to close a map which is used by other goroutines.
|
||||
|
@ -49,8 +79,9 @@ func (ms *MapSpec) Copy() *MapSpec {
|
|||
// Implement encoding.BinaryMarshaler or encoding.BinaryUnmarshaler
|
||||
// if you require custom encoding.
|
||||
type Map struct {
|
||||
fd *bpfFD
|
||||
abi MapABI
|
||||
name string
|
||||
fd *internal.FD
|
||||
abi MapABI
|
||||
// Per CPU maps return values larger than the size in the spec
|
||||
fullValueSize int
|
||||
}
|
||||
|
@ -60,16 +91,16 @@ type Map struct {
|
|||
// You should not use fd after calling this function.
|
||||
func NewMapFromFD(fd int) (*Map, error) {
|
||||
if fd < 0 {
|
||||
return nil, errors.New("invalid fd")
|
||||
return nil, xerrors.New("invalid fd")
|
||||
}
|
||||
bpfFd := newBPFFD(uint32(fd))
|
||||
bpfFd := internal.NewFD(uint32(fd))
|
||||
|
||||
abi, err := newMapABIFromFd(bpfFd)
|
||||
name, abi, err := newMapABIFromFd(bpfFd)
|
||||
if err != nil {
|
||||
bpfFd.forget()
|
||||
bpfFd.Forget()
|
||||
return nil, err
|
||||
}
|
||||
return newMap(bpfFd, abi)
|
||||
return newMap(bpfFd, name, abi)
|
||||
}
|
||||
|
||||
// NewMap creates a new Map.
|
||||
|
@ -77,89 +108,132 @@ func NewMapFromFD(fd int) (*Map, error) {
|
|||
// Creating a map for the first time will perform feature detection
|
||||
// by creating small, temporary maps.
|
||||
func NewMap(spec *MapSpec) (*Map, error) {
|
||||
if spec.BTF == nil {
|
||||
return newMapWithBTF(spec, nil)
|
||||
}
|
||||
|
||||
handle, err := btf.NewHandle(btf.MapSpec(spec.BTF))
|
||||
if err != nil && !xerrors.Is(err, btf.ErrNotSupported) {
|
||||
return nil, xerrors.Errorf("can't load BTF: %w", err)
|
||||
}
|
||||
|
||||
return newMapWithBTF(spec, handle)
|
||||
}
|
||||
|
||||
func newMapWithBTF(spec *MapSpec, handle *btf.Handle) (*Map, error) {
|
||||
if spec.Type != ArrayOfMaps && spec.Type != HashOfMaps {
|
||||
return createMap(spec, nil)
|
||||
return createMap(spec, nil, handle)
|
||||
}
|
||||
|
||||
if spec.InnerMap == nil {
|
||||
return nil, errors.Errorf("%s requires InnerMap", spec.Type)
|
||||
return nil, xerrors.Errorf("%s requires InnerMap", spec.Type)
|
||||
}
|
||||
|
||||
template, err := createMap(spec.InnerMap, nil)
|
||||
template, err := createMap(spec.InnerMap, nil, handle)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer template.Close()
|
||||
|
||||
return createMap(spec, template.fd)
|
||||
return createMap(spec, template.fd, handle)
|
||||
}
|
||||
|
||||
func createMap(spec *MapSpec, inner *bpfFD) (*Map, error) {
|
||||
spec = spec.Copy()
|
||||
func createMap(spec *MapSpec, inner *internal.FD, handle *btf.Handle) (*Map, error) {
|
||||
abi := newMapABIFromSpec(spec)
|
||||
|
||||
switch spec.Type {
|
||||
case ArrayOfMaps:
|
||||
fallthrough
|
||||
case HashOfMaps:
|
||||
if spec.ValueSize != 0 && spec.ValueSize != 4 {
|
||||
return nil, errors.Errorf("ValueSize must be zero or four for map of map")
|
||||
if err := haveNestedMaps(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
spec.ValueSize = 4
|
||||
|
||||
if abi.ValueSize != 0 && abi.ValueSize != 4 {
|
||||
return nil, xerrors.New("ValueSize must be zero or four for map of map")
|
||||
}
|
||||
abi.ValueSize = 4
|
||||
|
||||
case PerfEventArray:
|
||||
if spec.KeySize != 0 {
|
||||
return nil, errors.Errorf("KeySize must be zero for perf event array")
|
||||
}
|
||||
if spec.ValueSize != 0 {
|
||||
return nil, errors.Errorf("ValueSize must be zero for perf event array")
|
||||
}
|
||||
if spec.MaxEntries == 0 {
|
||||
n, err := internal.OnlineCPUs()
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "perf event array")
|
||||
}
|
||||
spec.MaxEntries = uint32(n)
|
||||
if abi.KeySize != 0 && abi.KeySize != 4 {
|
||||
return nil, xerrors.New("KeySize must be zero or four for perf event array")
|
||||
}
|
||||
abi.KeySize = 4
|
||||
|
||||
spec.KeySize = 4
|
||||
spec.ValueSize = 4
|
||||
if abi.ValueSize != 0 && abi.ValueSize != 4 {
|
||||
return nil, xerrors.New("ValueSize must be zero or four for perf event array")
|
||||
}
|
||||
abi.ValueSize = 4
|
||||
|
||||
if abi.MaxEntries == 0 {
|
||||
n, err := internal.PossibleCPUs()
|
||||
if err != nil {
|
||||
return nil, xerrors.Errorf("perf event array: %w", err)
|
||||
}
|
||||
abi.MaxEntries = uint32(n)
|
||||
}
|
||||
}
|
||||
|
||||
if abi.Flags&(unix.BPF_F_RDONLY_PROG|unix.BPF_F_WRONLY_PROG) > 0 || spec.Freeze {
|
||||
if err := haveMapMutabilityModifiers(); err != nil {
|
||||
return nil, xerrors.Errorf("map create: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
attr := bpfMapCreateAttr{
|
||||
mapType: spec.Type,
|
||||
keySize: spec.KeySize,
|
||||
valueSize: spec.ValueSize,
|
||||
maxEntries: spec.MaxEntries,
|
||||
flags: spec.Flags,
|
||||
mapType: abi.Type,
|
||||
keySize: abi.KeySize,
|
||||
valueSize: abi.ValueSize,
|
||||
maxEntries: abi.MaxEntries,
|
||||
flags: abi.Flags,
|
||||
}
|
||||
|
||||
if inner != nil {
|
||||
var err error
|
||||
attr.innerMapFd, err = inner.value()
|
||||
attr.innerMapFd, err = inner.Value()
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "map create")
|
||||
return nil, xerrors.Errorf("map create: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
name, err := newBPFObjName(spec.Name)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "map create")
|
||||
if handle != nil && spec.BTF != nil {
|
||||
attr.btfFd = uint32(handle.FD())
|
||||
attr.btfKeyTypeID = btf.MapKey(spec.BTF).ID()
|
||||
attr.btfValueTypeID = btf.MapValue(spec.BTF).ID()
|
||||
}
|
||||
|
||||
if haveObjName.Result() {
|
||||
attr.mapName = name
|
||||
if haveObjName() == nil {
|
||||
attr.mapName = newBPFObjName(spec.Name)
|
||||
}
|
||||
|
||||
fd, err := bpfMapCreate(&attr)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "map create")
|
||||
return nil, xerrors.Errorf("map create: %w", err)
|
||||
}
|
||||
|
||||
return newMap(fd, newMapABIFromSpec(spec))
|
||||
m, err := newMap(fd, spec.Name, abi)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := m.populate(spec.Contents); err != nil {
|
||||
m.Close()
|
||||
return nil, xerrors.Errorf("map create: can't set initial contents: %w", err)
|
||||
}
|
||||
|
||||
if spec.Freeze {
|
||||
if err := m.Freeze(); err != nil {
|
||||
m.Close()
|
||||
return nil, xerrors.Errorf("can't freeze map: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
return m, nil
|
||||
}
|
||||
|
||||
func newMap(fd *bpfFD, abi *MapABI) (*Map, error) {
|
||||
func newMap(fd *internal.FD, name string, abi *MapABI) (*Map, error) {
|
||||
m := &Map{
|
||||
name,
|
||||
fd,
|
||||
*abi,
|
||||
int(abi.ValueSize),
|
||||
|
@ -179,7 +253,10 @@ func newMap(fd *bpfFD, abi *MapABI) (*Map, error) {
|
|||
}
|
||||
|
||||
func (m *Map) String() string {
|
||||
return fmt.Sprintf("%s#%d", m.abi.Type, m.fd)
|
||||
if m.name != "" {
|
||||
return fmt.Sprintf("%s(%s)#%v", m.abi.Type, m.name, m.fd)
|
||||
}
|
||||
return fmt.Sprintf("%s#%v", m.abi.Type, m.fd)
|
||||
}
|
||||
|
||||
// ABI gets the ABI of the Map
|
||||
|
@ -219,9 +296,9 @@ func (m *Map) Lookup(key, valueOut interface{}) error {
|
|||
*value = m
|
||||
return nil
|
||||
case *Map:
|
||||
return errors.Errorf("can't unmarshal into %T, need %T", value, (**Map)(nil))
|
||||
return xerrors.Errorf("can't unmarshal into %T, need %T", value, (**Map)(nil))
|
||||
case Map:
|
||||
return errors.Errorf("can't unmarshal into %T, need %T", value, (**Map)(nil))
|
||||
return xerrors.Errorf("can't unmarshal into %T, need %T", value, (**Map)(nil))
|
||||
|
||||
case **Program:
|
||||
p, err := unmarshalProgram(valueBytes)
|
||||
|
@ -233,38 +310,58 @@ func (m *Map) Lookup(key, valueOut interface{}) error {
|
|||
*value = p
|
||||
return nil
|
||||
case *Program:
|
||||
return errors.Errorf("can't unmarshal into %T, need %T", value, (**Program)(nil))
|
||||
return xerrors.Errorf("can't unmarshal into %T, need %T", value, (**Program)(nil))
|
||||
case Program:
|
||||
return errors.Errorf("can't unmarshal into %T, need %T", value, (**Program)(nil))
|
||||
return xerrors.Errorf("can't unmarshal into %T, need %T", value, (**Program)(nil))
|
||||
|
||||
default:
|
||||
return unmarshalBytes(valueOut, valueBytes)
|
||||
}
|
||||
}
|
||||
|
||||
// LookupAndDelete retrieves and deletes a value from a Map.
|
||||
//
|
||||
// Returns ErrKeyNotExist if the key doesn't exist.
|
||||
func (m *Map) LookupAndDelete(key, valueOut interface{}) error {
|
||||
valuePtr, valueBytes := makeBuffer(valueOut, m.fullValueSize)
|
||||
|
||||
keyPtr, err := marshalPtr(key, int(m.abi.KeySize))
|
||||
if err != nil {
|
||||
return xerrors.Errorf("can't marshal key: %w", err)
|
||||
}
|
||||
|
||||
if err := bpfMapLookupAndDelete(m.fd, keyPtr, valuePtr); err != nil {
|
||||
return xerrors.Errorf("lookup and delete failed: %w", err)
|
||||
}
|
||||
|
||||
return unmarshalBytes(valueOut, valueBytes)
|
||||
}
|
||||
|
||||
// LookupBytes gets a value from Map.
|
||||
//
|
||||
// Returns a nil value if a key doesn't exist.
|
||||
func (m *Map) LookupBytes(key interface{}) ([]byte, error) {
|
||||
valueBytes := make([]byte, m.fullValueSize)
|
||||
valuePtr := newPtr(unsafe.Pointer(&valueBytes[0]))
|
||||
valuePtr := internal.NewSlicePointer(valueBytes)
|
||||
|
||||
err := m.lookup(key, valuePtr)
|
||||
if IsNotExist(err) {
|
||||
if xerrors.Is(err, ErrKeyNotExist) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
return valueBytes, err
|
||||
}
|
||||
|
||||
func (m *Map) lookup(key interface{}, valueOut syscallPtr) error {
|
||||
func (m *Map) lookup(key interface{}, valueOut internal.Pointer) error {
|
||||
keyPtr, err := marshalPtr(key, int(m.abi.KeySize))
|
||||
if err != nil {
|
||||
return errors.WithMessage(err, "can't marshal key")
|
||||
return xerrors.Errorf("can't marshal key: %w", err)
|
||||
}
|
||||
|
||||
err = bpfMapLookupElem(m.fd, keyPtr, valueOut)
|
||||
return errors.WithMessage(err, "lookup failed")
|
||||
if err = bpfMapLookupElem(m.fd, keyPtr, valueOut); err != nil {
|
||||
return xerrors.Errorf("lookup failed: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// MapUpdateFlags controls the behaviour of the Map.Update call.
|
||||
|
@ -292,38 +389,46 @@ func (m *Map) Put(key, value interface{}) error {
|
|||
func (m *Map) Update(key, value interface{}, flags MapUpdateFlags) error {
|
||||
keyPtr, err := marshalPtr(key, int(m.abi.KeySize))
|
||||
if err != nil {
|
||||
return errors.WithMessage(err, "can't marshal key")
|
||||
return xerrors.Errorf("can't marshal key: %w", err)
|
||||
}
|
||||
|
||||
var valuePtr syscallPtr
|
||||
var valuePtr internal.Pointer
|
||||
if m.abi.Type.hasPerCPUValue() {
|
||||
valuePtr, err = marshalPerCPUValue(value, int(m.abi.ValueSize))
|
||||
} else {
|
||||
valuePtr, err = marshalPtr(value, int(m.abi.ValueSize))
|
||||
}
|
||||
if err != nil {
|
||||
return errors.WithMessage(err, "can't marshal value")
|
||||
return xerrors.Errorf("can't marshal value: %w", err)
|
||||
}
|
||||
|
||||
return bpfMapUpdateElem(m.fd, keyPtr, valuePtr, uint64(flags))
|
||||
if err = bpfMapUpdateElem(m.fd, keyPtr, valuePtr, uint64(flags)); err != nil {
|
||||
return xerrors.Errorf("update failed: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Delete removes a value.
|
||||
//
|
||||
// Returns an error if the key does not exist, see IsNotExist.
|
||||
// Returns ErrKeyNotExist if the key does not exist.
|
||||
func (m *Map) Delete(key interface{}) error {
|
||||
keyPtr, err := marshalPtr(key, int(m.abi.KeySize))
|
||||
if err != nil {
|
||||
return errors.WithMessage(err, "can't marshal key")
|
||||
return xerrors.Errorf("can't marshal key: %w", err)
|
||||
}
|
||||
|
||||
err = bpfMapDeleteElem(m.fd, keyPtr)
|
||||
return errors.WithMessage(err, "can't delete key")
|
||||
if err = bpfMapDeleteElem(m.fd, keyPtr); err != nil {
|
||||
return xerrors.Errorf("delete failed: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// NextKey finds the key following an initial key.
|
||||
//
|
||||
// See NextKeyBytes for details.
|
||||
//
|
||||
// Returns ErrKeyNotExist if there is no next key.
|
||||
func (m *Map) NextKey(key, nextKeyOut interface{}) error {
|
||||
nextKeyPtr, nextKeyBytes := makeBuffer(nextKeyOut, int(m.abi.KeySize))
|
||||
|
||||
|
@ -335,8 +440,10 @@ func (m *Map) NextKey(key, nextKeyOut interface{}) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
err := unmarshalBytes(nextKeyOut, nextKeyBytes)
|
||||
return errors.WithMessage(err, "can't unmarshal next key")
|
||||
if err := unmarshalBytes(nextKeyOut, nextKeyBytes); err != nil {
|
||||
return xerrors.Errorf("can't unmarshal next key: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// NextKeyBytes returns the key following an initial key as a byte slice.
|
||||
|
@ -344,33 +451,37 @@ func (m *Map) NextKey(key, nextKeyOut interface{}) error {
|
|||
// Passing nil will return the first key.
|
||||
//
|
||||
// Use Iterate if you want to traverse all entries in the map.
|
||||
//
|
||||
// Returns nil if there are no more keys.
|
||||
func (m *Map) NextKeyBytes(key interface{}) ([]byte, error) {
|
||||
nextKey := make([]byte, m.abi.KeySize)
|
||||
nextKeyPtr := newPtr(unsafe.Pointer(&nextKey[0]))
|
||||
nextKeyPtr := internal.NewSlicePointer(nextKey)
|
||||
|
||||
err := m.nextKey(key, nextKeyPtr)
|
||||
if IsNotExist(err) {
|
||||
if xerrors.Is(err, ErrKeyNotExist) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
return nextKey, err
|
||||
}
|
||||
|
||||
func (m *Map) nextKey(key interface{}, nextKeyOut syscallPtr) error {
|
||||
func (m *Map) nextKey(key interface{}, nextKeyOut internal.Pointer) error {
|
||||
var (
|
||||
keyPtr syscallPtr
|
||||
keyPtr internal.Pointer
|
||||
err error
|
||||
)
|
||||
|
||||
if key != nil {
|
||||
keyPtr, err = marshalPtr(key, int(m.abi.KeySize))
|
||||
if err != nil {
|
||||
return errors.WithMessage(err, "can't marshal key")
|
||||
return xerrors.Errorf("can't marshal key: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
err = bpfMapGetNextKey(m.fd, keyPtr, nextKeyOut)
|
||||
return errors.WithMessage(err, "can't get next key")
|
||||
if err = bpfMapGetNextKey(m.fd, keyPtr, nextKeyOut); err != nil {
|
||||
return xerrors.Errorf("next key failed: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Iterate traverses a map.
|
||||
|
@ -391,14 +502,14 @@ func (m *Map) Close() error {
|
|||
return nil
|
||||
}
|
||||
|
||||
return m.fd.close()
|
||||
return m.fd.Close()
|
||||
}
|
||||
|
||||
// FD gets the file descriptor of the Map.
|
||||
//
|
||||
// Calling this function is invalid after Close has been called.
|
||||
func (m *Map) FD() int {
|
||||
fd, err := m.fd.value()
|
||||
fd, err := m.fd.Value()
|
||||
if err != nil {
|
||||
// Best effort: -1 is the number most likely to be an
|
||||
// invalid file descriptor.
|
||||
|
@ -419,12 +530,12 @@ func (m *Map) Clone() (*Map, error) {
|
|||
return nil, nil
|
||||
}
|
||||
|
||||
dup, err := m.fd.dup()
|
||||
dup, err := m.fd.Dup()
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "can't clone map")
|
||||
return nil, xerrors.Errorf("can't clone map: %w", err)
|
||||
}
|
||||
|
||||
return newMap(dup, &m.abi)
|
||||
return newMap(dup, m.name, &m.abi)
|
||||
}
|
||||
|
||||
// Pin persists the map past the lifetime of the process that created it.
|
||||
|
@ -434,21 +545,44 @@ func (m *Map) Pin(fileName string) error {
|
|||
return bpfPinObject(fileName, m.fd)
|
||||
}
|
||||
|
||||
// Freeze prevents a map to be modified from user space.
|
||||
//
|
||||
// It makes no changes to kernel-side restrictions.
|
||||
func (m *Map) Freeze() error {
|
||||
if err := haveMapMutabilityModifiers(); err != nil {
|
||||
return xerrors.Errorf("can't freeze map: %w", err)
|
||||
}
|
||||
|
||||
if err := bpfMapFreeze(m.fd); err != nil {
|
||||
return xerrors.Errorf("can't freeze map: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *Map) populate(contents []MapKV) error {
|
||||
for _, kv := range contents {
|
||||
if err := m.Put(kv.Key, kv.Value); err != nil {
|
||||
return xerrors.Errorf("key %v: %w", kv.Key, err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// LoadPinnedMap load a Map from a BPF file.
|
||||
//
|
||||
// Requires at least Linux 4.13, and is not compatible with
|
||||
// nested maps. Use LoadPinnedMapExplicit in these situations.
|
||||
// The function is not compatible with nested maps.
|
||||
// Use LoadPinnedMapExplicit in these situations.
|
||||
func LoadPinnedMap(fileName string) (*Map, error) {
|
||||
fd, err := bpfGetObject(fileName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
abi, err := newMapABIFromFd(fd)
|
||||
name, abi, err := newMapABIFromFd(fd)
|
||||
if err != nil {
|
||||
_ = fd.close()
|
||||
_ = fd.Close()
|
||||
return nil, err
|
||||
}
|
||||
return newMap(fd, abi)
|
||||
return newMap(fd, name, abi)
|
||||
}
|
||||
|
||||
// LoadPinnedMapExplicit loads a map with explicit parameters.
|
||||
|
@ -457,34 +591,23 @@ func LoadPinnedMapExplicit(fileName string, abi *MapABI) (*Map, error) {
|
|||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return newMap(fd, abi)
|
||||
return newMap(fd, "", abi)
|
||||
}
|
||||
|
||||
func unmarshalMap(buf []byte) (*Map, error) {
|
||||
if len(buf) != 4 {
|
||||
return nil, errors.New("map id requires 4 byte value")
|
||||
return nil, xerrors.New("map id requires 4 byte value")
|
||||
}
|
||||
|
||||
// Looking up an entry in a nested map or prog array returns an id,
|
||||
// not an fd.
|
||||
id := internal.NativeEndian.Uint32(buf)
|
||||
fd, err := bpfGetMapFDByID(id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
abi, err := newMapABIFromFd(fd)
|
||||
if err != nil {
|
||||
_ = fd.close()
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return newMap(fd, abi)
|
||||
return NewMapFromID(MapID(id))
|
||||
}
|
||||
|
||||
// MarshalBinary implements BinaryMarshaler.
|
||||
func (m *Map) MarshalBinary() ([]byte, error) {
|
||||
fd, err := m.fd.value()
|
||||
fd, err := m.fd.Value()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -494,6 +617,60 @@ func (m *Map) MarshalBinary() ([]byte, error) {
|
|||
return buf, nil
|
||||
}
|
||||
|
||||
func patchValue(value []byte, typ btf.Type, replacements map[string]interface{}) error {
|
||||
replaced := make(map[string]bool)
|
||||
replace := func(name string, offset, size int, replacement interface{}) error {
|
||||
if offset+size > len(value) {
|
||||
return xerrors.Errorf("%s: offset %d(+%d) is out of bounds", name, offset, size)
|
||||
}
|
||||
|
||||
buf, err := marshalBytes(replacement, size)
|
||||
if err != nil {
|
||||
return xerrors.Errorf("marshal %s: %w", name, err)
|
||||
}
|
||||
|
||||
copy(value[offset:offset+size], buf)
|
||||
replaced[name] = true
|
||||
return nil
|
||||
}
|
||||
|
||||
switch parent := typ.(type) {
|
||||
case *btf.Datasec:
|
||||
for _, secinfo := range parent.Vars {
|
||||
name := string(secinfo.Type.(*btf.Var).Name)
|
||||
replacement, ok := replacements[name]
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
err := replace(name, int(secinfo.Offset), int(secinfo.Size), replacement)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
default:
|
||||
return xerrors.Errorf("patching %T is not supported", typ)
|
||||
}
|
||||
|
||||
if len(replaced) == len(replacements) {
|
||||
return nil
|
||||
}
|
||||
|
||||
var missing []string
|
||||
for name := range replacements {
|
||||
if !replaced[name] {
|
||||
missing = append(missing, name)
|
||||
}
|
||||
}
|
||||
|
||||
if len(missing) == 1 {
|
||||
return xerrors.Errorf("unknown field: %s", missing[0])
|
||||
}
|
||||
|
||||
return xerrors.Errorf("unknown fields: %s", strings.Join(missing, ","))
|
||||
}
|
||||
|
||||
// MapIterator iterates a Map.
|
||||
//
|
||||
// See Map.Iterate.
|
||||
|
@ -514,8 +691,6 @@ func newMapIterator(target *Map) *MapIterator {
|
|||
}
|
||||
}
|
||||
|
||||
var errIterationAborted = errors.New("iteration aborted")
|
||||
|
||||
// Next decodes the next key and value.
|
||||
//
|
||||
// Iterating a hash map from which keys are being deleted is not
|
||||
|
@ -551,7 +726,7 @@ func (mi *MapIterator) Next(keyOut, valueOut interface{}) bool {
|
|||
mi.prevKey = mi.prevBytes
|
||||
|
||||
mi.err = mi.target.Lookup(nextBytes, valueOut)
|
||||
if IsNotExist(mi.err) {
|
||||
if xerrors.Is(mi.err, ErrKeyNotExist) {
|
||||
// Even though the key should be valid, we couldn't look up
|
||||
// its value. If we're iterating a hash map this is probably
|
||||
// because a concurrent delete removed the value before we
|
||||
|
@ -570,26 +745,50 @@ func (mi *MapIterator) Next(keyOut, valueOut interface{}) bool {
|
|||
return mi.err == nil
|
||||
}
|
||||
|
||||
mi.err = errIterationAborted
|
||||
mi.err = xerrors.Errorf("%w", ErrIterationAborted)
|
||||
return false
|
||||
}
|
||||
|
||||
// Err returns any encountered error.
|
||||
//
|
||||
// The method must be called after Next returns nil.
|
||||
//
|
||||
// Returns ErrIterationAborted if it wasn't possible to do a full iteration.
|
||||
func (mi *MapIterator) Err() error {
|
||||
return mi.err
|
||||
}
|
||||
|
||||
// IsNotExist returns true if the error indicates that a
|
||||
// key doesn't exist.
|
||||
func IsNotExist(err error) bool {
|
||||
return errors.Cause(err) == unix.ENOENT
|
||||
// MapGetNextID returns the ID of the next eBPF map.
|
||||
//
|
||||
// Returns ErrNotExist, if there is no next eBPF map.
|
||||
func MapGetNextID(startID MapID) (MapID, error) {
|
||||
id, err := objGetNextID(_MapGetNextID, uint32(startID))
|
||||
return MapID(id), err
|
||||
}
|
||||
|
||||
// IsIterationAborted returns true if the iteration was aborted.
|
||||
// NewMapFromID returns the map for a given id.
|
||||
//
|
||||
// This occurs when keys are deleted from a hash map during iteration.
|
||||
func IsIterationAborted(err error) bool {
|
||||
return errors.Cause(err) == errIterationAborted
|
||||
// Returns ErrNotExist, if there is no eBPF map with the given id.
|
||||
func NewMapFromID(id MapID) (*Map, error) {
|
||||
fd, err := bpfObjGetFDByID(_MapGetFDByID, uint32(id))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
name, abi, err := newMapABIFromFd(fd)
|
||||
if err != nil {
|
||||
_ = fd.Close()
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return newMap(fd, name, abi)
|
||||
}
|
||||
|
||||
// ID returns the systemwide unique ID of the map.
|
||||
func (m *Map) ID() (MapID, error) {
|
||||
info, err := bpfGetMapInfoByFD(m.fd)
|
||||
if err != nil {
|
||||
return MapID(0), err
|
||||
}
|
||||
return MapID(info.id), nil
|
||||
}
|
||||
|
|
|
@ -10,20 +10,27 @@ import (
|
|||
|
||||
"github.com/cilium/ebpf/internal"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"golang.org/x/xerrors"
|
||||
)
|
||||
|
||||
func marshalPtr(data interface{}, length int) (syscallPtr, error) {
|
||||
func marshalPtr(data interface{}, length int) (internal.Pointer, error) {
|
||||
if data == nil {
|
||||
if length == 0 {
|
||||
return internal.NewPointer(nil), nil
|
||||
}
|
||||
return internal.Pointer{}, xerrors.New("can't use nil as key of map")
|
||||
}
|
||||
|
||||
if ptr, ok := data.(unsafe.Pointer); ok {
|
||||
return newPtr(ptr), nil
|
||||
return internal.NewPointer(ptr), nil
|
||||
}
|
||||
|
||||
buf, err := marshalBytes(data, length)
|
||||
if err != nil {
|
||||
return syscallPtr{}, err
|
||||
return internal.Pointer{}, err
|
||||
}
|
||||
|
||||
return newPtr(unsafe.Pointer(&buf[0])), nil
|
||||
return internal.NewSlicePointer(buf), nil
|
||||
}
|
||||
|
||||
func marshalBytes(data interface{}, length int) (buf []byte, err error) {
|
||||
|
@ -35,11 +42,13 @@ func marshalBytes(data interface{}, length int) (buf []byte, err error) {
|
|||
case []byte:
|
||||
buf = value
|
||||
case unsafe.Pointer:
|
||||
err = errors.New("can't marshal from unsafe.Pointer")
|
||||
err = xerrors.New("can't marshal from unsafe.Pointer")
|
||||
default:
|
||||
var wr bytes.Buffer
|
||||
err = binary.Write(&wr, internal.NativeEndian, value)
|
||||
err = errors.Wrapf(err, "encoding %T", value)
|
||||
if err != nil {
|
||||
err = xerrors.Errorf("encoding %T: %v", value, err)
|
||||
}
|
||||
buf = wr.Bytes()
|
||||
}
|
||||
if err != nil {
|
||||
|
@ -47,18 +56,18 @@ func marshalBytes(data interface{}, length int) (buf []byte, err error) {
|
|||
}
|
||||
|
||||
if len(buf) != length {
|
||||
return nil, errors.Errorf("%T doesn't marshal to %d bytes", data, length)
|
||||
return nil, xerrors.Errorf("%T doesn't marshal to %d bytes", data, length)
|
||||
}
|
||||
return buf, nil
|
||||
}
|
||||
|
||||
func makeBuffer(dst interface{}, length int) (syscallPtr, []byte) {
|
||||
func makeBuffer(dst interface{}, length int) (internal.Pointer, []byte) {
|
||||
if ptr, ok := dst.(unsafe.Pointer); ok {
|
||||
return newPtr(ptr), nil
|
||||
return internal.NewPointer(ptr), nil
|
||||
}
|
||||
|
||||
buf := make([]byte, length)
|
||||
return newPtr(unsafe.Pointer(&buf[0])), buf
|
||||
return internal.NewSlicePointer(buf), buf
|
||||
}
|
||||
|
||||
func unmarshalBytes(data interface{}, buf []byte) error {
|
||||
|
@ -83,13 +92,15 @@ func unmarshalBytes(data interface{}, buf []byte) error {
|
|||
*value = buf
|
||||
return nil
|
||||
case string:
|
||||
return errors.New("require pointer to string")
|
||||
return xerrors.New("require pointer to string")
|
||||
case []byte:
|
||||
return errors.New("require pointer to []byte")
|
||||
return xerrors.New("require pointer to []byte")
|
||||
default:
|
||||
rd := bytes.NewReader(buf)
|
||||
err := binary.Read(rd, internal.NativeEndian, value)
|
||||
return errors.Wrapf(err, "decoding %T", value)
|
||||
if err := binary.Read(rd, internal.NativeEndian, value); err != nil {
|
||||
return xerrors.Errorf("decoding %T: %v", value, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -99,21 +110,21 @@ func unmarshalBytes(data interface{}, buf []byte) error {
|
|||
// Values are initialized to zero if the slice has less elements than CPUs.
|
||||
//
|
||||
// slice must have a type like []elementType.
|
||||
func marshalPerCPUValue(slice interface{}, elemLength int) (syscallPtr, error) {
|
||||
func marshalPerCPUValue(slice interface{}, elemLength int) (internal.Pointer, error) {
|
||||
sliceType := reflect.TypeOf(slice)
|
||||
if sliceType.Kind() != reflect.Slice {
|
||||
return syscallPtr{}, errors.New("per-CPU value requires slice")
|
||||
return internal.Pointer{}, xerrors.New("per-CPU value requires slice")
|
||||
}
|
||||
|
||||
possibleCPUs, err := internal.PossibleCPUs()
|
||||
if err != nil {
|
||||
return syscallPtr{}, err
|
||||
return internal.Pointer{}, err
|
||||
}
|
||||
|
||||
sliceValue := reflect.ValueOf(slice)
|
||||
sliceLen := sliceValue.Len()
|
||||
if sliceLen > possibleCPUs {
|
||||
return syscallPtr{}, errors.Errorf("per-CPU value exceeds number of CPUs")
|
||||
return internal.Pointer{}, xerrors.Errorf("per-CPU value exceeds number of CPUs")
|
||||
}
|
||||
|
||||
alignedElemLength := align(elemLength, 8)
|
||||
|
@ -123,14 +134,14 @@ func marshalPerCPUValue(slice interface{}, elemLength int) (syscallPtr, error) {
|
|||
elem := sliceValue.Index(i).Interface()
|
||||
elemBytes, err := marshalBytes(elem, elemLength)
|
||||
if err != nil {
|
||||
return syscallPtr{}, err
|
||||
return internal.Pointer{}, err
|
||||
}
|
||||
|
||||
offset := i * alignedElemLength
|
||||
copy(buf[offset:offset+elemLength], elemBytes)
|
||||
}
|
||||
|
||||
return newPtr(unsafe.Pointer(&buf[0])), nil
|
||||
return internal.NewSlicePointer(buf), nil
|
||||
}
|
||||
|
||||
// unmarshalPerCPUValue decodes a buffer into a slice containing one value per
|
||||
|
@ -140,7 +151,7 @@ func marshalPerCPUValue(slice interface{}, elemLength int) (syscallPtr, error) {
|
|||
func unmarshalPerCPUValue(slicePtr interface{}, elemLength int, buf []byte) error {
|
||||
slicePtrType := reflect.TypeOf(slicePtr)
|
||||
if slicePtrType.Kind() != reflect.Ptr || slicePtrType.Elem().Kind() != reflect.Slice {
|
||||
return errors.Errorf("per-cpu value requires pointer to slice")
|
||||
return xerrors.Errorf("per-cpu value requires pointer to slice")
|
||||
}
|
||||
|
||||
possibleCPUs, err := internal.PossibleCPUs()
|
||||
|
@ -159,7 +170,7 @@ func unmarshalPerCPUValue(slicePtr interface{}, elemLength int, buf []byte) erro
|
|||
|
||||
step := len(buf) / possibleCPUs
|
||||
if step < elemLength {
|
||||
return errors.Errorf("per-cpu element length is larger than available data")
|
||||
return xerrors.Errorf("per-cpu element length is larger than available data")
|
||||
}
|
||||
for i := 0; i < possibleCPUs; i++ {
|
||||
var elem interface{}
|
||||
|
@ -177,7 +188,7 @@ func unmarshalPerCPUValue(slicePtr interface{}, elemLength int, buf []byte) erro
|
|||
|
||||
err := unmarshalBytes(elem, elemBytes)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "cpu %d", i)
|
||||
return xerrors.Errorf("cpu %d: %w", i, err)
|
||||
}
|
||||
|
||||
buf = buf[step:]
|
||||
|
|
|
@ -4,21 +4,23 @@ import (
|
|||
"bytes"
|
||||
"fmt"
|
||||
"math"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
"unsafe"
|
||||
|
||||
"github.com/cilium/ebpf/asm"
|
||||
"github.com/cilium/ebpf/internal"
|
||||
"github.com/cilium/ebpf/internal/btf"
|
||||
"github.com/cilium/ebpf/internal/unix"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"golang.org/x/xerrors"
|
||||
)
|
||||
|
||||
var (
|
||||
errNotSupported = errors.New("ebpf: not supported by kernel")
|
||||
)
|
||||
// ErrNotSupported is returned whenever the kernel doesn't support a feature.
|
||||
var ErrNotSupported = internal.ErrNotSupported
|
||||
|
||||
// ProgramID represents the unique ID of an eBPF program
|
||||
type ProgramID uint32
|
||||
|
||||
const (
|
||||
// Number of bytes to pad the output buffer for BPF_PROG_TEST_RUN.
|
||||
|
@ -51,6 +53,11 @@ type ProgramSpec struct {
|
|||
Instructions asm.Instructions
|
||||
License string
|
||||
KernelVersion uint32
|
||||
|
||||
// The BTF associated with this program. Changing Instructions
|
||||
// will most likely invalidate the contained data, and may
|
||||
// result in errors when attempting to load it into the kernel.
|
||||
BTF *btf.Program
|
||||
}
|
||||
|
||||
// Copy returns a copy of the spec.
|
||||
|
@ -73,7 +80,7 @@ type Program struct {
|
|||
// otherwise it is empty.
|
||||
VerifierLog string
|
||||
|
||||
fd *bpfFD
|
||||
fd *internal.FD
|
||||
name string
|
||||
abi ProgramABI
|
||||
}
|
||||
|
@ -91,7 +98,20 @@ func NewProgram(spec *ProgramSpec) (*Program, error) {
|
|||
// Loading a program for the first time will perform
|
||||
// feature detection by loading small, temporary programs.
|
||||
func NewProgramWithOptions(spec *ProgramSpec, opts ProgramOptions) (*Program, error) {
|
||||
attr, err := convertProgramSpec(spec, haveObjName.Result())
|
||||
if spec.BTF == nil {
|
||||
return newProgramWithBTF(spec, nil, opts)
|
||||
}
|
||||
|
||||
handle, err := btf.NewHandle(btf.ProgramSpec(spec.BTF))
|
||||
if err != nil && !xerrors.Is(err, btf.ErrNotSupported) {
|
||||
return nil, xerrors.Errorf("can't load BTF: %w", err)
|
||||
}
|
||||
|
||||
return newProgramWithBTF(spec, handle, opts)
|
||||
}
|
||||
|
||||
func newProgramWithBTF(spec *ProgramSpec, btf *btf.Handle, opts ProgramOptions) (*Program, error) {
|
||||
attr, err := convertProgramSpec(spec, btf)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -106,62 +126,52 @@ func NewProgramWithOptions(spec *ProgramSpec, opts ProgramOptions) (*Program, er
|
|||
logBuf = make([]byte, logSize)
|
||||
attr.logLevel = opts.LogLevel
|
||||
attr.logSize = uint32(len(logBuf))
|
||||
attr.logBuf = newPtr(unsafe.Pointer(&logBuf[0]))
|
||||
attr.logBuf = internal.NewSlicePointer(logBuf)
|
||||
}
|
||||
|
||||
fd, err := bpfProgLoad(attr)
|
||||
if err == nil {
|
||||
prog := newProgram(fd, spec.Name, &ProgramABI{spec.Type})
|
||||
prog.VerifierLog = convertCString(logBuf)
|
||||
prog.VerifierLog = internal.CString(logBuf)
|
||||
return prog, nil
|
||||
}
|
||||
|
||||
truncated := errors.Cause(err) == unix.ENOSPC
|
||||
logErr := err
|
||||
if opts.LogLevel == 0 {
|
||||
// Re-run with the verifier enabled to get better error messages.
|
||||
logBuf = make([]byte, logSize)
|
||||
attr.logLevel = 1
|
||||
attr.logSize = uint32(len(logBuf))
|
||||
attr.logBuf = newPtr(unsafe.Pointer(&logBuf[0]))
|
||||
attr.logBuf = internal.NewSlicePointer(logBuf)
|
||||
|
||||
_, nerr := bpfProgLoad(attr)
|
||||
truncated = errors.Cause(nerr) == unix.ENOSPC
|
||||
_, logErr = bpfProgLoad(attr)
|
||||
}
|
||||
|
||||
logs := convertCString(logBuf)
|
||||
if truncated {
|
||||
logs += "\n(truncated...)"
|
||||
}
|
||||
|
||||
return nil, &loadError{err, logs}
|
||||
err = internal.ErrorWithLog(err, logBuf, logErr)
|
||||
return nil, xerrors.Errorf("can't load program: %w", err)
|
||||
}
|
||||
|
||||
// NewProgramFromFD creates a program from a raw fd.
|
||||
//
|
||||
// You should not use fd after calling this function.
|
||||
//
|
||||
// Requires at least Linux 4.11.
|
||||
func NewProgramFromFD(fd int) (*Program, error) {
|
||||
if fd < 0 {
|
||||
return nil, errors.New("invalid fd")
|
||||
return nil, xerrors.New("invalid fd")
|
||||
}
|
||||
bpfFd := newBPFFD(uint32(fd))
|
||||
bpfFd := internal.NewFD(uint32(fd))
|
||||
|
||||
info, err := bpfGetProgInfoByFD(bpfFd)
|
||||
name, abi, err := newProgramABIFromFd(bpfFd)
|
||||
if err != nil {
|
||||
bpfFd.forget()
|
||||
bpfFd.Forget()
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var name string
|
||||
if bpfName := convertCString(info.name[:]); bpfName != "" {
|
||||
name = bpfName
|
||||
} else {
|
||||
name = convertCString(info.tag[:])
|
||||
}
|
||||
|
||||
return newProgram(bpfFd, name, newProgramABIFromInfo(info)), nil
|
||||
return newProgram(bpfFd, name, abi), nil
|
||||
}
|
||||
|
||||
func newProgram(fd *bpfFD, name string, abi *ProgramABI) *Program {
|
||||
func newProgram(fd *internal.FD, name string, abi *ProgramABI) *Program {
|
||||
return &Program{
|
||||
name: name,
|
||||
fd: fd,
|
||||
|
@ -169,13 +179,13 @@ func newProgram(fd *bpfFD, name string, abi *ProgramABI) *Program {
|
|||
}
|
||||
}
|
||||
|
||||
func convertProgramSpec(spec *ProgramSpec, includeName bool) (*bpfProgLoadAttr, error) {
|
||||
func convertProgramSpec(spec *ProgramSpec, handle *btf.Handle) (*bpfProgLoadAttr, error) {
|
||||
if len(spec.Instructions) == 0 {
|
||||
return nil, errors.New("Instructions cannot be empty")
|
||||
return nil, xerrors.New("Instructions cannot be empty")
|
||||
}
|
||||
|
||||
if len(spec.License) == 0 {
|
||||
return nil, errors.New("License cannot be empty")
|
||||
return nil, xerrors.New("License cannot be empty")
|
||||
}
|
||||
|
||||
buf := bytes.NewBuffer(make([]byte, 0, len(spec.Instructions)*asm.InstructionSize))
|
||||
|
@ -186,22 +196,37 @@ func convertProgramSpec(spec *ProgramSpec, includeName bool) (*bpfProgLoadAttr,
|
|||
|
||||
bytecode := buf.Bytes()
|
||||
insCount := uint32(len(bytecode) / asm.InstructionSize)
|
||||
lic := []byte(spec.License)
|
||||
attr := &bpfProgLoadAttr{
|
||||
progType: spec.Type,
|
||||
expectedAttachType: spec.AttachType,
|
||||
insCount: insCount,
|
||||
instructions: newPtr(unsafe.Pointer(&bytecode[0])),
|
||||
license: newPtr(unsafe.Pointer(&lic[0])),
|
||||
instructions: internal.NewSlicePointer(bytecode),
|
||||
license: internal.NewStringPointer(spec.License),
|
||||
kernelVersion: spec.KernelVersion,
|
||||
}
|
||||
|
||||
name, err := newBPFObjName(spec.Name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
if haveObjName() == nil {
|
||||
attr.progName = newBPFObjName(spec.Name)
|
||||
}
|
||||
|
||||
if includeName {
|
||||
attr.progName = name
|
||||
if handle != nil && spec.BTF != nil {
|
||||
attr.progBTFFd = uint32(handle.FD())
|
||||
|
||||
recSize, bytes, err := btf.ProgramLineInfos(spec.BTF)
|
||||
if err != nil {
|
||||
return nil, xerrors.Errorf("can't get BTF line infos: %w", err)
|
||||
}
|
||||
attr.lineInfoRecSize = recSize
|
||||
attr.lineInfoCnt = uint32(uint64(len(bytes)) / uint64(recSize))
|
||||
attr.lineInfo = internal.NewSlicePointer(bytes)
|
||||
|
||||
recSize, bytes, err = btf.ProgramFuncInfos(spec.BTF)
|
||||
if err != nil {
|
||||
return nil, xerrors.Errorf("can't get BTF function infos: %w", err)
|
||||
}
|
||||
attr.funcInfoRecSize = recSize
|
||||
attr.funcInfoCnt = uint32(uint64(len(bytes)) / uint64(recSize))
|
||||
attr.funcInfo = internal.NewSlicePointer(bytes)
|
||||
}
|
||||
|
||||
return attr, nil
|
||||
|
@ -209,9 +234,9 @@ func convertProgramSpec(spec *ProgramSpec, includeName bool) (*bpfProgLoadAttr,
|
|||
|
||||
func (p *Program) String() string {
|
||||
if p.name != "" {
|
||||
return fmt.Sprintf("%s(%s)#%s", p.abi.Type, p.name, p.fd)
|
||||
return fmt.Sprintf("%s(%s)#%v", p.abi.Type, p.name, p.fd)
|
||||
}
|
||||
return fmt.Sprintf("%s#%s", p.abi.Type, p.fd)
|
||||
return fmt.Sprintf("%s#%v", p.abi.Type, p.fd)
|
||||
}
|
||||
|
||||
// ABI gets the ABI of the Program
|
||||
|
@ -223,7 +248,7 @@ func (p *Program) ABI() ProgramABI {
|
|||
//
|
||||
// It is invalid to call this function after Close has been called.
|
||||
func (p *Program) FD() int {
|
||||
fd, err := p.fd.value()
|
||||
fd, err := p.fd.Value()
|
||||
if err != nil {
|
||||
// Best effort: -1 is the number most likely to be an
|
||||
// invalid file descriptor.
|
||||
|
@ -243,9 +268,9 @@ func (p *Program) Clone() (*Program, error) {
|
|||
return nil, nil
|
||||
}
|
||||
|
||||
dup, err := p.fd.dup()
|
||||
dup, err := p.fd.Dup()
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "can't clone program")
|
||||
return nil, xerrors.Errorf("can't clone program: %w", err)
|
||||
}
|
||||
|
||||
return newProgram(dup, p.name, &p.abi), nil
|
||||
|
@ -255,7 +280,10 @@ func (p *Program) Clone() (*Program, error) {
|
|||
//
|
||||
// This requires bpffs to be mounted above fileName. See http://cilium.readthedocs.io/en/doc-1.0/kubernetes/install/#mounting-the-bpf-fs-optional
|
||||
func (p *Program) Pin(fileName string) error {
|
||||
return errors.Wrap(bpfPinObject(fileName, p.fd), "can't pin program")
|
||||
if err := bpfPinObject(fileName, p.fd); err != nil {
|
||||
return xerrors.Errorf("can't pin program: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Close unloads the program from the kernel.
|
||||
|
@ -264,7 +292,7 @@ func (p *Program) Close() error {
|
|||
return nil
|
||||
}
|
||||
|
||||
return p.fd.close()
|
||||
return p.fd.Close()
|
||||
}
|
||||
|
||||
// Test runs the Program in the kernel with the given input and returns the
|
||||
|
@ -275,57 +303,68 @@ func (p *Program) Close() error {
|
|||
//
|
||||
// This function requires at least Linux 4.12.
|
||||
func (p *Program) Test(in []byte) (uint32, []byte, error) {
|
||||
ret, out, _, err := p.testRun(in, 1)
|
||||
return ret, out, err
|
||||
ret, out, _, err := p.testRun(in, 1, nil)
|
||||
if err != nil {
|
||||
return ret, nil, xerrors.Errorf("can't test program: %w", err)
|
||||
}
|
||||
return ret, out, nil
|
||||
}
|
||||
|
||||
// Benchmark runs the Program with the given input for a number of times
|
||||
// and returns the time taken per iteration.
|
||||
//
|
||||
// The returned value is the return value of the last execution of
|
||||
// the program.
|
||||
// Returns the result of the last execution of the program and the time per
|
||||
// run or an error. reset is called whenever the benchmark syscall is
|
||||
// interrupted, and should be set to testing.B.ResetTimer or similar.
|
||||
//
|
||||
// Note: profiling a call to this function will skew it's results, see
|
||||
// https://github.com/cilium/ebpf/issues/24
|
||||
//
|
||||
// This function requires at least Linux 4.12.
|
||||
func (p *Program) Benchmark(in []byte, repeat int) (uint32, time.Duration, error) {
|
||||
ret, _, total, err := p.testRun(in, repeat)
|
||||
return ret, total, err
|
||||
func (p *Program) Benchmark(in []byte, repeat int, reset func()) (uint32, time.Duration, error) {
|
||||
ret, _, total, err := p.testRun(in, repeat, reset)
|
||||
if err != nil {
|
||||
return ret, total, xerrors.Errorf("can't benchmark program: %w", err)
|
||||
}
|
||||
return ret, total, nil
|
||||
}
|
||||
|
||||
var noProgTestRun = featureTest{
|
||||
Fn: func() bool {
|
||||
prog, err := NewProgram(&ProgramSpec{
|
||||
Type: SocketFilter,
|
||||
Instructions: asm.Instructions{
|
||||
asm.LoadImm(asm.R0, 0, asm.DWord),
|
||||
asm.Return(),
|
||||
},
|
||||
License: "MIT",
|
||||
})
|
||||
if err != nil {
|
||||
// This may be because we lack sufficient permissions, etc.
|
||||
return false
|
||||
}
|
||||
defer prog.Close()
|
||||
var haveProgTestRun = internal.FeatureTest("BPF_PROG_TEST_RUN", "4.12", func() bool {
|
||||
prog, err := NewProgram(&ProgramSpec{
|
||||
Type: SocketFilter,
|
||||
Instructions: asm.Instructions{
|
||||
asm.LoadImm(asm.R0, 0, asm.DWord),
|
||||
asm.Return(),
|
||||
},
|
||||
License: "MIT",
|
||||
})
|
||||
if err != nil {
|
||||
// This may be because we lack sufficient permissions, etc.
|
||||
return false
|
||||
}
|
||||
defer prog.Close()
|
||||
|
||||
fd, err := prog.fd.value()
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
fd, err := prog.fd.Value()
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
// Programs require at least 14 bytes input
|
||||
in := make([]byte, 14)
|
||||
attr := bpfProgTestRunAttr{
|
||||
fd: fd,
|
||||
dataSizeIn: uint32(len(in)),
|
||||
dataIn: newPtr(unsafe.Pointer(&in[0])),
|
||||
}
|
||||
// Programs require at least 14 bytes input
|
||||
in := make([]byte, 14)
|
||||
attr := bpfProgTestRunAttr{
|
||||
fd: fd,
|
||||
dataSizeIn: uint32(len(in)),
|
||||
dataIn: internal.NewSlicePointer(in),
|
||||
}
|
||||
|
||||
_, err = bpfCall(_ProgTestRun, unsafe.Pointer(&attr), unsafe.Sizeof(attr))
|
||||
return errors.Cause(err) == unix.EINVAL
|
||||
},
|
||||
}
|
||||
_, err = internal.BPF(_ProgTestRun, unsafe.Pointer(&attr), unsafe.Sizeof(attr))
|
||||
|
||||
func (p *Program) testRun(in []byte, repeat int) (uint32, []byte, time.Duration, error) {
|
||||
// Check for EINVAL specifically, rather than err != nil since we
|
||||
// otherwise misdetect due to insufficient permissions.
|
||||
return !xerrors.Is(err, unix.EINVAL)
|
||||
})
|
||||
|
||||
func (p *Program) testRun(in []byte, repeat int, reset func()) (uint32, []byte, time.Duration, error) {
|
||||
if uint(repeat) > math.MaxUint32 {
|
||||
return 0, nil, 0, fmt.Errorf("repeat is too high")
|
||||
}
|
||||
|
@ -338,8 +377,8 @@ func (p *Program) testRun(in []byte, repeat int) (uint32, []byte, time.Duration,
|
|||
return 0, nil, 0, fmt.Errorf("input is too long")
|
||||
}
|
||||
|
||||
if noProgTestRun.Result() {
|
||||
return 0, nil, 0, errNotSupported
|
||||
if err := haveProgTestRun(); err != nil {
|
||||
return 0, nil, 0, err
|
||||
}
|
||||
|
||||
// Older kernels ignore the dataSizeOut argument when copying to user space.
|
||||
|
@ -349,7 +388,7 @@ func (p *Program) testRun(in []byte, repeat int) (uint32, []byte, time.Duration,
|
|||
// See https://patchwork.ozlabs.org/cover/1006822/
|
||||
out := make([]byte, len(in)+outputPad)
|
||||
|
||||
fd, err := p.fd.value()
|
||||
fd, err := p.fd.Value()
|
||||
if err != nil {
|
||||
return 0, nil, 0, err
|
||||
}
|
||||
|
@ -358,14 +397,25 @@ func (p *Program) testRun(in []byte, repeat int) (uint32, []byte, time.Duration,
|
|||
fd: fd,
|
||||
dataSizeIn: uint32(len(in)),
|
||||
dataSizeOut: uint32(len(out)),
|
||||
dataIn: newPtr(unsafe.Pointer(&in[0])),
|
||||
dataOut: newPtr(unsafe.Pointer(&out[0])),
|
||||
dataIn: internal.NewSlicePointer(in),
|
||||
dataOut: internal.NewSlicePointer(out),
|
||||
repeat: uint32(repeat),
|
||||
}
|
||||
|
||||
_, err = bpfCall(_ProgTestRun, unsafe.Pointer(&attr), unsafe.Sizeof(attr))
|
||||
if err != nil {
|
||||
return 0, nil, 0, errors.Wrap(err, "can't run test")
|
||||
for {
|
||||
_, err = internal.BPF(_ProgTestRun, unsafe.Pointer(&attr), unsafe.Sizeof(attr))
|
||||
if err == nil {
|
||||
break
|
||||
}
|
||||
|
||||
if xerrors.Is(err, unix.EINTR) {
|
||||
if reset != nil {
|
||||
reset()
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
return 0, nil, 0, xerrors.Errorf("can't run test: %w", err)
|
||||
}
|
||||
|
||||
if int(attr.dataSizeOut) > cap(out) {
|
||||
|
@ -381,29 +431,18 @@ func (p *Program) testRun(in []byte, repeat int) (uint32, []byte, time.Duration,
|
|||
|
||||
func unmarshalProgram(buf []byte) (*Program, error) {
|
||||
if len(buf) != 4 {
|
||||
return nil, errors.New("program id requires 4 byte value")
|
||||
return nil, xerrors.New("program id requires 4 byte value")
|
||||
}
|
||||
|
||||
// Looking up an entry in a nested map or prog array returns an id,
|
||||
// not an fd.
|
||||
id := internal.NativeEndian.Uint32(buf)
|
||||
fd, err := bpfGetProgramFDByID(id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
abi, err := newProgramABIFromFd(fd)
|
||||
if err != nil {
|
||||
_ = fd.close()
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return newProgram(fd, "", abi), nil
|
||||
return NewProgramFromID(ProgramID(id))
|
||||
}
|
||||
|
||||
// MarshalBinary implements BinaryMarshaler.
|
||||
func (p *Program) MarshalBinary() ([]byte, error) {
|
||||
value, err := p.fd.value()
|
||||
value, err := p.fd.Value()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -416,10 +455,10 @@ func (p *Program) MarshalBinary() ([]byte, error) {
|
|||
// Attach a Program to a container object fd
|
||||
func (p *Program) Attach(fd int, typ AttachType, flags AttachFlags) error {
|
||||
if fd < 0 {
|
||||
return errors.New("invalid fd")
|
||||
return xerrors.New("invalid fd")
|
||||
}
|
||||
|
||||
pfd, err := p.fd.value()
|
||||
pfd, err := p.fd.Value()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -437,10 +476,10 @@ func (p *Program) Attach(fd int, typ AttachType, flags AttachFlags) error {
|
|||
// Detach a Program from a container object fd
|
||||
func (p *Program) Detach(fd int, typ AttachType, flags AttachFlags) error {
|
||||
if fd < 0 {
|
||||
return errors.New("invalid fd")
|
||||
return xerrors.New("invalid fd")
|
||||
}
|
||||
|
||||
pfd, err := p.fd.value()
|
||||
pfd, err := p.fd.Value()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -457,31 +496,20 @@ func (p *Program) Detach(fd int, typ AttachType, flags AttachFlags) error {
|
|||
|
||||
// LoadPinnedProgram loads a Program from a BPF file.
|
||||
//
|
||||
// Requires at least Linux 4.13, use LoadPinnedProgramExplicit on
|
||||
// earlier versions.
|
||||
// Requires at least Linux 4.11.
|
||||
func LoadPinnedProgram(fileName string) (*Program, error) {
|
||||
fd, err := bpfGetObject(fileName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
abi, err := newProgramABIFromFd(fd)
|
||||
name, abi, err := newProgramABIFromFd(fd)
|
||||
if err != nil {
|
||||
_ = fd.close()
|
||||
return nil, err
|
||||
_ = fd.Close()
|
||||
return nil, xerrors.Errorf("can't get ABI for %s: %w", fileName, err)
|
||||
}
|
||||
|
||||
return newProgram(fd, filepath.Base(fileName), abi), nil
|
||||
}
|
||||
|
||||
// LoadPinnedProgramExplicit loads a program with explicit parameters.
|
||||
func LoadPinnedProgramExplicit(fileName string, abi *ProgramABI) (*Program, error) {
|
||||
fd, err := bpfGetObject(fileName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return newProgram(fd, filepath.Base(fileName), abi), nil
|
||||
return newProgram(fd, name, abi), nil
|
||||
}
|
||||
|
||||
// SanitizeName replaces all invalid characters in name.
|
||||
|
@ -500,24 +528,37 @@ func SanitizeName(name string, replacement rune) string {
|
|||
}, name)
|
||||
}
|
||||
|
||||
type loadError struct {
|
||||
cause error
|
||||
verifierLog string
|
||||
// ProgramGetNextID returns the ID of the next eBPF program.
|
||||
//
|
||||
// Returns ErrNotExist, if there is no next eBPF program.
|
||||
func ProgramGetNextID(startID ProgramID) (ProgramID, error) {
|
||||
id, err := objGetNextID(_ProgGetNextID, uint32(startID))
|
||||
return ProgramID(id), err
|
||||
}
|
||||
|
||||
func (le *loadError) Error() string {
|
||||
if le.verifierLog == "" {
|
||||
return fmt.Sprintf("failed to load program: %s", le.cause)
|
||||
// NewProgramFromID returns the program for a given id.
|
||||
//
|
||||
// Returns ErrNotExist, if there is no eBPF program with the given id.
|
||||
func NewProgramFromID(id ProgramID) (*Program, error) {
|
||||
fd, err := bpfObjGetFDByID(_ProgGetFDByID, uint32(id))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return fmt.Sprintf("failed to load program: %s: %s", le.cause, le.verifierLog)
|
||||
|
||||
name, abi, err := newProgramABIFromFd(fd)
|
||||
if err != nil {
|
||||
_ = fd.Close()
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return newProgram(fd, name, abi), nil
|
||||
}
|
||||
|
||||
func (le *loadError) Cause() error {
|
||||
return le.cause
|
||||
}
|
||||
|
||||
// IsNotSupported returns true if an error occurred because
|
||||
// the kernel does not have support for a specific feature.
|
||||
func IsNotSupported(err error) bool {
|
||||
return errors.Cause(err) == errNotSupported
|
||||
// ID returns the systemwide unique ID of the program.
|
||||
func (p *Program) ID() (ProgramID, error) {
|
||||
info, err := bpfGetProgInfoByFD(p.fd)
|
||||
if err != nil {
|
||||
return ProgramID(0), err
|
||||
}
|
||||
return ProgramID(info.id), nil
|
||||
}
|
||||
|
|
|
@ -13,6 +13,11 @@ The library is maintained by [Cloudflare](https://www.cloudflare.com) and [Ciliu
|
|||
The package is production ready, but **the API is explicitly unstable
|
||||
right now**. Expect to update your code if you want to follow along.
|
||||
|
||||
## Requirements
|
||||
|
||||
* A version of Go that is [supported by upstream](https://golang.org/doc/devel/release.html#policy)
|
||||
* Linux 4.9, 4.19 or 5.4 (versions in-between should work, but are not tested)
|
||||
|
||||
## Useful resources
|
||||
|
||||
* [Cilium eBPF documentation](https://cilium.readthedocs.io/en/latest/bpf/#bpf-guide) (recommended)
|
||||
|
|
|
@ -8,24 +8,22 @@ set -o pipefail
|
|||
if [[ "${1:-}" = "--in-vm" ]]; then
|
||||
shift
|
||||
|
||||
readonly home="$(mktemp --directory)"
|
||||
|
||||
mount -t bpf bpf /sys/fs/bpf
|
||||
export CGO_ENABLED=0
|
||||
export HOME="$home"
|
||||
export GOFLAGS=-mod=readonly
|
||||
export GOPATH=/run/go-path
|
||||
export GOPROXY=file:///run/go-root/pkg/mod/cache/download
|
||||
export GOCACHE=/run/go-cache
|
||||
|
||||
echo Running tests...
|
||||
/usr/local/bin/go test -mod=vendor -coverprofile="$1/coverage.txt" -covermode=atomic -v ./...
|
||||
/usr/local/bin/go test -coverprofile="$1/coverage.txt" -covermode=atomic -v ./...
|
||||
touch "$1/success"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Force Go modules, so that vendoring and building are easier.
|
||||
export GO111MODULE=on
|
||||
|
||||
# Pull all dependencies, so that we can run tests without the
|
||||
# vm having network access.
|
||||
go mod vendor
|
||||
go mod download
|
||||
|
||||
# Use sudo if /dev/kvm isn't accessible by the current user.
|
||||
sudo=""
|
||||
|
@ -42,15 +40,19 @@ fi
|
|||
|
||||
readonly kernel="linux-${kernel_version}.bz"
|
||||
readonly output="$(mktemp -d)"
|
||||
readonly tmp_dir="$(mktemp -d)"
|
||||
readonly tmp_dir="${TMPDIR:-$(mktemp -d)}"
|
||||
|
||||
test -e "${tmp_dir}/${kernel}" || {
|
||||
echo Fetching ${kernel}
|
||||
echo Fetching "${kernel}"
|
||||
curl --fail -L "https://github.com/newtools/ci-kernels/blob/master/${kernel}?raw=true" -o "${tmp_dir}/${kernel}"
|
||||
}
|
||||
|
||||
echo Testing on ${kernel_version}
|
||||
$sudo virtme-run --kimg "${tmp_dir}/${kernel}" --memory 256M --pwd --rwdir=/run/output="${output}" --script-sh "$(realpath "$0") --in-vm /run/output"
|
||||
echo Testing on "${kernel_version}"
|
||||
$sudo virtme-run --kimg "${tmp_dir}/${kernel}" --memory 512M --pwd \
|
||||
--rwdir=/run/output="${output}" \
|
||||
--rodir=/run/go-path="$(go env GOPATH)" \
|
||||
--rwdir=/run/go-cache="$(go env GOCACHE)" \
|
||||
--script-sh "$(realpath "$0") --in-vm /run/output"
|
||||
|
||||
if [[ ! -e "${output}/success" ]]; then
|
||||
echo "Test failed on ${kernel_version}"
|
||||
|
|
|
@ -1,88 +1,35 @@
|
|||
package ebpf
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"strings"
|
||||
"unsafe"
|
||||
|
||||
"github.com/cilium/ebpf/internal"
|
||||
"github.com/cilium/ebpf/internal/btf"
|
||||
"github.com/cilium/ebpf/internal/unix"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"golang.org/x/xerrors"
|
||||
)
|
||||
|
||||
var errClosedFd = errors.New("use of closed file descriptor")
|
||||
|
||||
type bpfFD struct {
|
||||
raw int64
|
||||
}
|
||||
|
||||
func newBPFFD(value uint32) *bpfFD {
|
||||
fd := &bpfFD{int64(value)}
|
||||
runtime.SetFinalizer(fd, (*bpfFD).close)
|
||||
return fd
|
||||
}
|
||||
|
||||
func (fd *bpfFD) String() string {
|
||||
return strconv.FormatInt(fd.raw, 10)
|
||||
}
|
||||
|
||||
func (fd *bpfFD) value() (uint32, error) {
|
||||
if fd.raw < 0 {
|
||||
return 0, errClosedFd
|
||||
}
|
||||
|
||||
return uint32(fd.raw), nil
|
||||
}
|
||||
|
||||
func (fd *bpfFD) close() error {
|
||||
if fd.raw < 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
value := int(fd.raw)
|
||||
fd.raw = -1
|
||||
|
||||
fd.forget()
|
||||
return unix.Close(value)
|
||||
}
|
||||
|
||||
func (fd *bpfFD) forget() {
|
||||
runtime.SetFinalizer(fd, nil)
|
||||
}
|
||||
|
||||
func (fd *bpfFD) dup() (*bpfFD, error) {
|
||||
if fd.raw < 0 {
|
||||
return nil, errClosedFd
|
||||
}
|
||||
|
||||
dup, err := unix.FcntlInt(uintptr(fd.raw), unix.F_DUPFD_CLOEXEC, 0)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "can't dup fd")
|
||||
}
|
||||
|
||||
return newBPFFD(uint32(dup)), nil
|
||||
}
|
||||
// Generic errors returned by BPF syscalls.
|
||||
var (
|
||||
ErrNotExist = xerrors.New("requested object does not exit")
|
||||
)
|
||||
|
||||
// bpfObjName is a null-terminated string made up of
|
||||
// 'A-Za-z0-9_' characters.
|
||||
type bpfObjName [unix.BPF_OBJ_NAME_LEN]byte
|
||||
|
||||
// newBPFObjName truncates the result if it is too long.
|
||||
func newBPFObjName(name string) (bpfObjName, error) {
|
||||
idx := strings.IndexFunc(name, invalidBPFObjNameChar)
|
||||
if idx != -1 {
|
||||
return bpfObjName{}, errors.Errorf("invalid character '%c' in name '%s'", name[idx], name)
|
||||
}
|
||||
|
||||
func newBPFObjName(name string) bpfObjName {
|
||||
var result bpfObjName
|
||||
copy(result[:unix.BPF_OBJ_NAME_LEN-1], name)
|
||||
return result, nil
|
||||
return result
|
||||
}
|
||||
|
||||
func invalidBPFObjNameChar(char rune) bool {
|
||||
dotAllowed := objNameAllowsDot() == nil
|
||||
|
||||
switch {
|
||||
case char >= 'A' && char <= 'Z':
|
||||
fallthrough
|
||||
|
@ -90,6 +37,8 @@ func invalidBPFObjNameChar(char rune) bool {
|
|||
fallthrough
|
||||
case char >= '0' && char <= '9':
|
||||
fallthrough
|
||||
case dotAllowed && char == '.':
|
||||
fallthrough
|
||||
case char == '_':
|
||||
return false
|
||||
default:
|
||||
|
@ -98,21 +47,25 @@ func invalidBPFObjNameChar(char rune) bool {
|
|||
}
|
||||
|
||||
type bpfMapCreateAttr struct {
|
||||
mapType MapType
|
||||
keySize uint32
|
||||
valueSize uint32
|
||||
maxEntries uint32
|
||||
flags uint32
|
||||
innerMapFd uint32 // since 4.12 56f668dfe00d
|
||||
numaNode uint32 // since 4.14 96eabe7a40aa
|
||||
mapName bpfObjName // since 4.15 ad5b177bd73f
|
||||
mapType MapType
|
||||
keySize uint32
|
||||
valueSize uint32
|
||||
maxEntries uint32
|
||||
flags uint32
|
||||
innerMapFd uint32 // since 4.12 56f668dfe00d
|
||||
numaNode uint32 // since 4.14 96eabe7a40aa
|
||||
mapName bpfObjName // since 4.15 ad5b177bd73f
|
||||
mapIfIndex uint32
|
||||
btfFd uint32
|
||||
btfKeyTypeID btf.TypeID
|
||||
btfValueTypeID btf.TypeID
|
||||
}
|
||||
|
||||
type bpfMapOpAttr struct {
|
||||
mapFd uint32
|
||||
padding uint32
|
||||
key syscallPtr
|
||||
value syscallPtr
|
||||
key internal.Pointer
|
||||
value internal.Pointer
|
||||
flags uint64
|
||||
}
|
||||
|
||||
|
@ -127,7 +80,7 @@ type bpfMapInfo struct {
|
|||
}
|
||||
|
||||
type bpfPinObjAttr struct {
|
||||
fileName syscallPtr
|
||||
fileName internal.Pointer
|
||||
fd uint32
|
||||
padding uint32
|
||||
}
|
||||
|
@ -135,16 +88,23 @@ type bpfPinObjAttr struct {
|
|||
type bpfProgLoadAttr struct {
|
||||
progType ProgramType
|
||||
insCount uint32
|
||||
instructions syscallPtr
|
||||
license syscallPtr
|
||||
instructions internal.Pointer
|
||||
license internal.Pointer
|
||||
logLevel uint32
|
||||
logSize uint32
|
||||
logBuf syscallPtr
|
||||
logBuf internal.Pointer
|
||||
kernelVersion uint32 // since 4.1 2541517c32be
|
||||
progFlags uint32 // since 4.11 e07b98d9bffe
|
||||
progName bpfObjName // since 4.15 067cae47771c
|
||||
progIfIndex uint32 // since 4.15 1f6f4cb7ba21
|
||||
expectedAttachType AttachType // since 4.17 5e43f899b03a
|
||||
progBTFFd uint32
|
||||
funcInfoRecSize uint32
|
||||
funcInfo internal.Pointer
|
||||
funcInfoCnt uint32
|
||||
lineInfoRecSize uint32
|
||||
lineInfo internal.Pointer
|
||||
lineInfoCnt uint32
|
||||
}
|
||||
|
||||
type bpfProgInfo struct {
|
||||
|
@ -153,12 +113,12 @@ type bpfProgInfo struct {
|
|||
tag [unix.BPF_TAG_SIZE]byte
|
||||
jitedLen uint32
|
||||
xlatedLen uint32
|
||||
jited syscallPtr
|
||||
xlated syscallPtr
|
||||
jited internal.Pointer
|
||||
xlated internal.Pointer
|
||||
loadTime uint64 // since 4.15 cb4d2b3f03d8
|
||||
createdByUID uint32
|
||||
nrMapIDs uint32
|
||||
mapIds syscallPtr
|
||||
mapIds internal.Pointer
|
||||
name bpfObjName
|
||||
}
|
||||
|
||||
|
@ -167,8 +127,8 @@ type bpfProgTestRunAttr struct {
|
|||
retval uint32
|
||||
dataSizeIn uint32
|
||||
dataSizeOut uint32
|
||||
dataIn syscallPtr
|
||||
dataOut syscallPtr
|
||||
dataIn internal.Pointer
|
||||
dataOut internal.Pointer
|
||||
repeat uint32
|
||||
duration uint32
|
||||
}
|
||||
|
@ -183,7 +143,7 @@ type bpfProgAlterAttr struct {
|
|||
type bpfObjGetInfoByFDAttr struct {
|
||||
fd uint32
|
||||
infoLen uint32
|
||||
info syscallPtr // May be either bpfMapInfo or bpfProgInfo
|
||||
info internal.Pointer // May be either bpfMapInfo or bpfProgInfo
|
||||
}
|
||||
|
||||
type bpfGetFDByIDAttr struct {
|
||||
|
@ -191,13 +151,19 @@ type bpfGetFDByIDAttr struct {
|
|||
next uint32
|
||||
}
|
||||
|
||||
func newPtr(ptr unsafe.Pointer) syscallPtr {
|
||||
return syscallPtr{ptr: ptr}
|
||||
type bpfMapFreezeAttr struct {
|
||||
mapFd uint32
|
||||
}
|
||||
|
||||
func bpfProgLoad(attr *bpfProgLoadAttr) (*bpfFD, error) {
|
||||
type bpfObjGetNextIDAttr struct {
|
||||
startID uint32
|
||||
nextID uint32
|
||||
openFlags uint32
|
||||
}
|
||||
|
||||
func bpfProgLoad(attr *bpfProgLoadAttr) (*internal.FD, error) {
|
||||
for {
|
||||
fd, err := bpfCall(_ProgLoad, unsafe.Pointer(attr), unsafe.Sizeof(*attr))
|
||||
fd, err := internal.BPF(_ProgLoad, unsafe.Pointer(attr), unsafe.Sizeof(*attr))
|
||||
// As of ~4.20 the verifier can be interrupted by a signal,
|
||||
// and returns EAGAIN in that case.
|
||||
if err == unix.EAGAIN {
|
||||
|
@ -208,26 +174,71 @@ func bpfProgLoad(attr *bpfProgLoadAttr) (*bpfFD, error) {
|
|||
return nil, err
|
||||
}
|
||||
|
||||
return newBPFFD(uint32(fd)), nil
|
||||
return internal.NewFD(uint32(fd)), nil
|
||||
}
|
||||
}
|
||||
|
||||
func bpfProgAlter(cmd int, attr *bpfProgAlterAttr) error {
|
||||
_, err := bpfCall(cmd, unsafe.Pointer(attr), unsafe.Sizeof(*attr))
|
||||
_, err := internal.BPF(cmd, unsafe.Pointer(attr), unsafe.Sizeof(*attr))
|
||||
return err
|
||||
}
|
||||
|
||||
func bpfMapCreate(attr *bpfMapCreateAttr) (*bpfFD, error) {
|
||||
fd, err := bpfCall(_MapCreate, unsafe.Pointer(attr), unsafe.Sizeof(*attr))
|
||||
func bpfMapCreate(attr *bpfMapCreateAttr) (*internal.FD, error) {
|
||||
fd, err := internal.BPF(_MapCreate, unsafe.Pointer(attr), unsafe.Sizeof(*attr))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return newBPFFD(uint32(fd)), nil
|
||||
return internal.NewFD(uint32(fd)), nil
|
||||
}
|
||||
|
||||
func bpfMapLookupElem(m *bpfFD, key, valueOut syscallPtr) error {
|
||||
fd, err := m.value()
|
||||
var haveNestedMaps = internal.FeatureTest("nested maps", "4.12", func() bool {
|
||||
inner, err := bpfMapCreate(&bpfMapCreateAttr{
|
||||
mapType: Array,
|
||||
keySize: 4,
|
||||
valueSize: 4,
|
||||
maxEntries: 1,
|
||||
})
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
defer inner.Close()
|
||||
|
||||
innerFd, _ := inner.Value()
|
||||
nested, err := bpfMapCreate(&bpfMapCreateAttr{
|
||||
mapType: ArrayOfMaps,
|
||||
keySize: 4,
|
||||
valueSize: 4,
|
||||
maxEntries: 1,
|
||||
innerMapFd: innerFd,
|
||||
})
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
_ = nested.Close()
|
||||
return true
|
||||
})
|
||||
|
||||
var haveMapMutabilityModifiers = internal.FeatureTest("read- and write-only maps", "5.2", func() bool {
|
||||
// This checks BPF_F_RDONLY_PROG and BPF_F_WRONLY_PROG. Since
|
||||
// BPF_MAP_FREEZE appeared in 5.2 as well we don't do a separate check.
|
||||
m, err := bpfMapCreate(&bpfMapCreateAttr{
|
||||
mapType: Array,
|
||||
keySize: 4,
|
||||
valueSize: 4,
|
||||
maxEntries: 1,
|
||||
flags: unix.BPF_F_RDONLY_PROG,
|
||||
})
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
_ = m.Close()
|
||||
return true
|
||||
})
|
||||
|
||||
func bpfMapLookupElem(m *internal.FD, key, valueOut internal.Pointer) error {
|
||||
fd, err := m.Value()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -237,12 +248,27 @@ func bpfMapLookupElem(m *bpfFD, key, valueOut syscallPtr) error {
|
|||
key: key,
|
||||
value: valueOut,
|
||||
}
|
||||
_, err = bpfCall(_MapLookupElem, unsafe.Pointer(&attr), unsafe.Sizeof(attr))
|
||||
return err
|
||||
_, err = internal.BPF(_MapLookupElem, unsafe.Pointer(&attr), unsafe.Sizeof(attr))
|
||||
return wrapMapError(err)
|
||||
}
|
||||
|
||||
func bpfMapUpdateElem(m *bpfFD, key, valueOut syscallPtr, flags uint64) error {
|
||||
fd, err := m.value()
|
||||
func bpfMapLookupAndDelete(m *internal.FD, key, valueOut internal.Pointer) error {
|
||||
fd, err := m.Value()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
attr := bpfMapOpAttr{
|
||||
mapFd: fd,
|
||||
key: key,
|
||||
value: valueOut,
|
||||
}
|
||||
_, err = internal.BPF(_MapLookupAndDeleteElem, unsafe.Pointer(&attr), unsafe.Sizeof(attr))
|
||||
return wrapMapError(err)
|
||||
}
|
||||
|
||||
func bpfMapUpdateElem(m *internal.FD, key, valueOut internal.Pointer, flags uint64) error {
|
||||
fd, err := m.Value()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -253,12 +279,12 @@ func bpfMapUpdateElem(m *bpfFD, key, valueOut syscallPtr, flags uint64) error {
|
|||
value: valueOut,
|
||||
flags: flags,
|
||||
}
|
||||
_, err = bpfCall(_MapUpdateElem, unsafe.Pointer(&attr), unsafe.Sizeof(attr))
|
||||
return err
|
||||
_, err = internal.BPF(_MapUpdateElem, unsafe.Pointer(&attr), unsafe.Sizeof(attr))
|
||||
return wrapMapError(err)
|
||||
}
|
||||
|
||||
func bpfMapDeleteElem(m *bpfFD, key syscallPtr) error {
|
||||
fd, err := m.value()
|
||||
func bpfMapDeleteElem(m *internal.FD, key internal.Pointer) error {
|
||||
fd, err := m.Value()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -267,12 +293,12 @@ func bpfMapDeleteElem(m *bpfFD, key syscallPtr) error {
|
|||
mapFd: fd,
|
||||
key: key,
|
||||
}
|
||||
_, err = bpfCall(_MapDeleteElem, unsafe.Pointer(&attr), unsafe.Sizeof(attr))
|
||||
return err
|
||||
_, err = internal.BPF(_MapDeleteElem, unsafe.Pointer(&attr), unsafe.Sizeof(attr))
|
||||
return wrapMapError(err)
|
||||
}
|
||||
|
||||
func bpfMapGetNextKey(m *bpfFD, key, nextKeyOut syscallPtr) error {
|
||||
fd, err := m.value()
|
||||
func bpfMapGetNextKey(m *internal.FD, key, nextKeyOut internal.Pointer) error {
|
||||
fd, err := m.Value()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -282,46 +308,93 @@ func bpfMapGetNextKey(m *bpfFD, key, nextKeyOut syscallPtr) error {
|
|||
key: key,
|
||||
value: nextKeyOut,
|
||||
}
|
||||
_, err = bpfCall(_MapGetNextKey, unsafe.Pointer(&attr), unsafe.Sizeof(attr))
|
||||
_, err = internal.BPF(_MapGetNextKey, unsafe.Pointer(&attr), unsafe.Sizeof(attr))
|
||||
return wrapMapError(err)
|
||||
}
|
||||
|
||||
func objGetNextID(cmd int, start uint32) (uint32, error) {
|
||||
attr := bpfObjGetNextIDAttr{
|
||||
startID: start,
|
||||
}
|
||||
_, err := internal.BPF(cmd, unsafe.Pointer(&attr), unsafe.Sizeof(attr))
|
||||
return attr.nextID, wrapObjError(err)
|
||||
}
|
||||
|
||||
func wrapObjError(err error) error {
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
if xerrors.Is(err, unix.ENOENT) {
|
||||
return xerrors.Errorf("%w", ErrNotExist)
|
||||
}
|
||||
|
||||
return xerrors.New(err.Error())
|
||||
}
|
||||
|
||||
func wrapMapError(err error) error {
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
if xerrors.Is(err, unix.ENOENT) {
|
||||
return ErrKeyNotExist
|
||||
}
|
||||
|
||||
return xerrors.New(err.Error())
|
||||
}
|
||||
|
||||
func bpfMapFreeze(m *internal.FD) error {
|
||||
fd, err := m.Value()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
attr := bpfMapFreezeAttr{
|
||||
mapFd: fd,
|
||||
}
|
||||
_, err = internal.BPF(_MapFreeze, unsafe.Pointer(&attr), unsafe.Sizeof(attr))
|
||||
return err
|
||||
}
|
||||
|
||||
const bpfFSType = 0xcafe4a11
|
||||
|
||||
func bpfPinObject(fileName string, fd *bpfFD) error {
|
||||
func bpfPinObject(fileName string, fd *internal.FD) error {
|
||||
dirName := filepath.Dir(fileName)
|
||||
var statfs unix.Statfs_t
|
||||
if err := unix.Statfs(dirName, &statfs); err != nil {
|
||||
return err
|
||||
}
|
||||
if uint64(statfs.Type) != bpfFSType {
|
||||
return errors.Errorf("%s is not on a bpf filesystem", fileName)
|
||||
return xerrors.Errorf("%s is not on a bpf filesystem", fileName)
|
||||
}
|
||||
|
||||
value, err := fd.value()
|
||||
value, err := fd.Value()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = bpfCall(_ObjPin, unsafe.Pointer(&bpfPinObjAttr{
|
||||
fileName: newPtr(unsafe.Pointer(&[]byte(fileName)[0])),
|
||||
_, err = internal.BPF(_ObjPin, unsafe.Pointer(&bpfPinObjAttr{
|
||||
fileName: internal.NewStringPointer(fileName),
|
||||
fd: value,
|
||||
}), 16)
|
||||
return errors.Wrapf(err, "pin object %s", fileName)
|
||||
if err != nil {
|
||||
return xerrors.Errorf("pin object %s: %w", fileName, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func bpfGetObject(fileName string) (*bpfFD, error) {
|
||||
ptr, err := bpfCall(_ObjGet, unsafe.Pointer(&bpfPinObjAttr{
|
||||
fileName: newPtr(unsafe.Pointer(&[]byte(fileName)[0])),
|
||||
func bpfGetObject(fileName string) (*internal.FD, error) {
|
||||
ptr, err := internal.BPF(_ObjGet, unsafe.Pointer(&bpfPinObjAttr{
|
||||
fileName: internal.NewStringPointer(fileName),
|
||||
}), 16)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "get object %s", fileName)
|
||||
return nil, xerrors.Errorf("get object %s: %w", fileName, err)
|
||||
}
|
||||
return newBPFFD(uint32(ptr)), nil
|
||||
return internal.NewFD(uint32(ptr)), nil
|
||||
}
|
||||
|
||||
func bpfGetObjectInfoByFD(fd *bpfFD, info unsafe.Pointer, size uintptr) error {
|
||||
value, err := fd.value()
|
||||
func bpfGetObjectInfoByFD(fd *internal.FD, info unsafe.Pointer, size uintptr) error {
|
||||
value, err := fd.Value()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -330,91 +403,76 @@ func bpfGetObjectInfoByFD(fd *bpfFD, info unsafe.Pointer, size uintptr) error {
|
|||
attr := bpfObjGetInfoByFDAttr{
|
||||
fd: value,
|
||||
infoLen: uint32(size),
|
||||
info: newPtr(info),
|
||||
info: internal.NewPointer(info),
|
||||
}
|
||||
_, err = bpfCall(_ObjGetInfoByFD, unsafe.Pointer(&attr), unsafe.Sizeof(attr))
|
||||
return errors.Wrapf(err, "fd %d", value)
|
||||
_, err = internal.BPF(_ObjGetInfoByFD, unsafe.Pointer(&attr), unsafe.Sizeof(attr))
|
||||
if err != nil {
|
||||
return xerrors.Errorf("fd %d: %w", fd, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func bpfGetProgInfoByFD(fd *bpfFD) (*bpfProgInfo, error) {
|
||||
func bpfGetProgInfoByFD(fd *internal.FD) (*bpfProgInfo, error) {
|
||||
var info bpfProgInfo
|
||||
err := bpfGetObjectInfoByFD(fd, unsafe.Pointer(&info), unsafe.Sizeof(info))
|
||||
return &info, errors.Wrap(err, "can't get program info")
|
||||
if err := bpfGetObjectInfoByFD(fd, unsafe.Pointer(&info), unsafe.Sizeof(info)); err != nil {
|
||||
return nil, xerrors.Errorf("can't get program info: %w", err)
|
||||
}
|
||||
return &info, nil
|
||||
}
|
||||
|
||||
func bpfGetMapInfoByFD(fd *bpfFD) (*bpfMapInfo, error) {
|
||||
func bpfGetMapInfoByFD(fd *internal.FD) (*bpfMapInfo, error) {
|
||||
var info bpfMapInfo
|
||||
err := bpfGetObjectInfoByFD(fd, unsafe.Pointer(&info), unsafe.Sizeof(info))
|
||||
return &info, errors.Wrap(err, "can't get map info:")
|
||||
if err != nil {
|
||||
return nil, xerrors.Errorf("can't get map info: %w", err)
|
||||
}
|
||||
return &info, nil
|
||||
}
|
||||
|
||||
var haveObjName = featureTest{
|
||||
Fn: func() bool {
|
||||
name, err := newBPFObjName("feature_test")
|
||||
if err != nil {
|
||||
// This really is a fatal error, but it should be caught
|
||||
// by the unit tests not working.
|
||||
return false
|
||||
}
|
||||
var haveObjName = internal.FeatureTest("object names", "4.15", func() bool {
|
||||
attr := bpfMapCreateAttr{
|
||||
mapType: Array,
|
||||
keySize: 4,
|
||||
valueSize: 4,
|
||||
maxEntries: 1,
|
||||
mapName: newBPFObjName("feature_test"),
|
||||
}
|
||||
|
||||
attr := bpfMapCreateAttr{
|
||||
mapType: Array,
|
||||
keySize: 4,
|
||||
valueSize: 4,
|
||||
maxEntries: 1,
|
||||
mapName: name,
|
||||
}
|
||||
fd, err := bpfMapCreate(&attr)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
fd, err := bpfMapCreate(&attr)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
_ = fd.Close()
|
||||
return true
|
||||
})
|
||||
|
||||
_ = fd.close()
|
||||
return true
|
||||
},
|
||||
}
|
||||
var objNameAllowsDot = internal.FeatureTest("dot in object names", "5.2", func() bool {
|
||||
if err := haveObjName(); err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
func bpfGetMapFDByID(id uint32) (*bpfFD, error) {
|
||||
// available from 4.13
|
||||
attr := bpfMapCreateAttr{
|
||||
mapType: Array,
|
||||
keySize: 4,
|
||||
valueSize: 4,
|
||||
maxEntries: 1,
|
||||
mapName: newBPFObjName(".test"),
|
||||
}
|
||||
|
||||
fd, err := bpfMapCreate(&attr)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
_ = fd.Close()
|
||||
return true
|
||||
})
|
||||
|
||||
func bpfObjGetFDByID(cmd int, id uint32) (*internal.FD, error) {
|
||||
attr := bpfGetFDByIDAttr{
|
||||
id: id,
|
||||
}
|
||||
ptr, err := bpfCall(_MapGetFDByID, unsafe.Pointer(&attr), unsafe.Sizeof(attr))
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "can't get fd for map id %d", id)
|
||||
}
|
||||
return newBPFFD(uint32(ptr)), nil
|
||||
}
|
||||
|
||||
func bpfGetProgramFDByID(id uint32) (*bpfFD, error) {
|
||||
// available from 4.13
|
||||
attr := bpfGetFDByIDAttr{
|
||||
id: id,
|
||||
}
|
||||
ptr, err := bpfCall(_ProgGetFDByID, unsafe.Pointer(&attr), unsafe.Sizeof(attr))
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "can't get fd for program id %d", id)
|
||||
}
|
||||
return newBPFFD(uint32(ptr)), nil
|
||||
}
|
||||
|
||||
func bpfCall(cmd int, attr unsafe.Pointer, size uintptr) (uintptr, error) {
|
||||
r1, _, errNo := unix.Syscall(unix.SYS_BPF, uintptr(cmd), uintptr(attr), size)
|
||||
runtime.KeepAlive(attr)
|
||||
|
||||
var err error
|
||||
if errNo != 0 {
|
||||
err = errNo
|
||||
}
|
||||
|
||||
return r1, err
|
||||
}
|
||||
|
||||
func convertCString(in []byte) string {
|
||||
inLen := bytes.IndexByte(in, 0)
|
||||
if inLen == -1 {
|
||||
return ""
|
||||
}
|
||||
return string(in[:inLen])
|
||||
ptr, err := internal.BPF(cmd, unsafe.Pointer(&attr), unsafe.Sizeof(attr))
|
||||
return internal.NewFD(uint32(ptr)), wrapObjError(err)
|
||||
}
|
||||
|
|
|
@ -57,6 +57,30 @@ const (
|
|||
// HashOfMaps - Each item in the hash map is another map. The inner map mustn't be a map of maps
|
||||
// itself.
|
||||
HashOfMaps
|
||||
// DevMap - Specialized map to store references to network devices.
|
||||
DevMap
|
||||
// SockMap - Specialized map to store references to sockets.
|
||||
SockMap
|
||||
// CPUMap - Specialized map to store references to CPUs.
|
||||
CPUMap
|
||||
// XSKMap - Specialized map for XDP programs to store references to open sockets.
|
||||
XSKMap
|
||||
// SockHash - Specialized hash to store references to sockets.
|
||||
SockHash
|
||||
// CGroupStorage - Special map for CGroups.
|
||||
CGroupStorage
|
||||
// ReusePortSockArray - Specialized map to store references to sockets that can be reused.
|
||||
ReusePortSockArray
|
||||
// PerCPUCGroupStorage - Special per CPU map for CGroups.
|
||||
PerCPUCGroupStorage
|
||||
// Queue - FIFO storage for BPF programs.
|
||||
Queue
|
||||
// Stack - LIFO storage for BPF programs.
|
||||
Stack
|
||||
// SkStorage - Specialized map for local storage at SK for BPF programs.
|
||||
SkStorage
|
||||
// DevMapHash - Hash-based indexing scheme for references to network devices.
|
||||
DevMapHash
|
||||
)
|
||||
|
||||
// hasPerCPUValue returns true if the Map stores a value per CPU.
|
||||
|
@ -84,12 +108,14 @@ const (
|
|||
_ProgGetFDByID
|
||||
_MapGetFDByID
|
||||
_ObjGetInfoByFD
|
||||
)
|
||||
|
||||
const (
|
||||
_Any = iota
|
||||
_NoExist
|
||||
_Exist
|
||||
_ProgQuery
|
||||
_RawTracepointOpen
|
||||
_BTFLoad
|
||||
_BTFGetFDByID
|
||||
_TaskFDQuery
|
||||
_MapLookupAndDeleteElem
|
||||
_MapFreeze
|
||||
_BTFGetNextID
|
||||
)
|
||||
|
||||
// ProgramType of the eBPF program
|
||||
|
@ -149,6 +175,8 @@ const (
|
|||
RawTracepointWritable
|
||||
// CGroupSockopt program
|
||||
CGroupSockopt
|
||||
// Tracing program
|
||||
Tracing
|
||||
)
|
||||
|
||||
// AttachType of the eBPF program, needed to differentiate allowed context accesses in
|
||||
|
@ -183,6 +211,9 @@ const (
|
|||
AttachCGroupUDP6Recvmsg
|
||||
AttachCGroupGetsockopt
|
||||
AttachCGroupSetsockopt
|
||||
AttachTraceRawTp
|
||||
AttachTraceFEntry
|
||||
AttachTraceFExit
|
||||
)
|
||||
|
||||
// AttachFlags of the eBPF program used in BPF_PROG_ATTACH command
|
||||
|
|
|
@ -22,11 +22,23 @@ func _() {
|
|||
_ = x[LPMTrie-11]
|
||||
_ = x[ArrayOfMaps-12]
|
||||
_ = x[HashOfMaps-13]
|
||||
_ = x[DevMap-14]
|
||||
_ = x[SockMap-15]
|
||||
_ = x[CPUMap-16]
|
||||
_ = x[XSKMap-17]
|
||||
_ = x[SockHash-18]
|
||||
_ = x[CGroupStorage-19]
|
||||
_ = x[ReusePortSockArray-20]
|
||||
_ = x[PerCPUCGroupStorage-21]
|
||||
_ = x[Queue-22]
|
||||
_ = x[Stack-23]
|
||||
_ = x[SkStorage-24]
|
||||
_ = x[DevMapHash-25]
|
||||
}
|
||||
|
||||
const _MapType_name = "UnspecifiedMapHashArrayProgramArrayPerfEventArrayPerCPUHashPerCPUArrayStackTraceCGroupArrayLRUHashLRUCPUHashLPMTrieArrayOfMapsHashOfMaps"
|
||||
const _MapType_name = "UnspecifiedMapHashArrayProgramArrayPerfEventArrayPerCPUHashPerCPUArrayStackTraceCGroupArrayLRUHashLRUCPUHashLPMTrieArrayOfMapsHashOfMapsDevMapSockMapCPUMapXSKMapSockHashCGroupStorageReusePortSockArrayPerCPUCGroupStorageQueueStackSkStorageDevMapHash"
|
||||
|
||||
var _MapType_index = [...]uint8{0, 14, 18, 23, 35, 49, 59, 70, 80, 91, 98, 108, 115, 126, 136}
|
||||
var _MapType_index = [...]uint8{0, 14, 18, 23, 35, 49, 59, 70, 80, 91, 98, 108, 115, 126, 136, 142, 149, 155, 161, 169, 182, 200, 219, 224, 229, 238, 248}
|
||||
|
||||
func (i MapType) String() string {
|
||||
if i >= MapType(len(_MapType_index)-1) {
|
||||
|
@ -64,11 +76,12 @@ func _() {
|
|||
_ = x[CGroupSysctl-23]
|
||||
_ = x[RawTracepointWritable-24]
|
||||
_ = x[CGroupSockopt-25]
|
||||
_ = x[Tracing-26]
|
||||
}
|
||||
|
||||
const _ProgramType_name = "UnspecifiedProgramSocketFilterKprobeSchedCLSSchedACTTracePointXDPPerfEventCGroupSKBCGroupSockLWTInLWTOutLWTXmitSockOpsSkSKBCGroupDeviceSkMsgRawTracepointCGroupSockAddrLWTSeg6LocalLircMode2SkReuseportFlowDissectorCGroupSysctlRawTracepointWritableCGroupSockopt"
|
||||
const _ProgramType_name = "UnspecifiedProgramSocketFilterKprobeSchedCLSSchedACTTracePointXDPPerfEventCGroupSKBCGroupSockLWTInLWTOutLWTXmitSockOpsSkSKBCGroupDeviceSkMsgRawTracepointCGroupSockAddrLWTSeg6LocalLircMode2SkReuseportFlowDissectorCGroupSysctlRawTracepointWritableCGroupSockoptTracing"
|
||||
|
||||
var _ProgramType_index = [...]uint16{0, 18, 30, 36, 44, 52, 62, 65, 74, 83, 93, 98, 104, 111, 118, 123, 135, 140, 153, 167, 179, 188, 199, 212, 224, 245, 258}
|
||||
var _ProgramType_index = [...]uint16{0, 18, 30, 36, 44, 52, 62, 65, 74, 83, 93, 98, 104, 111, 118, 123, 135, 140, 153, 167, 179, 188, 199, 212, 224, 245, 258, 265}
|
||||
|
||||
func (i ProgramType) String() string {
|
||||
if i >= ProgramType(len(_ProgramType_index)-1) {
|
||||
|
|
|
@ -1,17 +1,16 @@
|
|||
language: go
|
||||
go:
|
||||
- "1.10.x"
|
||||
- "1.11.x"
|
||||
- "1.12.x"
|
||||
- "1.13.x"
|
||||
|
||||
go_import_path: github.com/containerd/console
|
||||
|
||||
env:
|
||||
- GO111MODULE=on
|
||||
|
||||
install:
|
||||
- go get -d
|
||||
- GOOS=openbsd go get -d
|
||||
- GOOS=solaris go get -d
|
||||
- GOOS=windows go get -d
|
||||
- go get -u github.com/vbatts/git-validation
|
||||
- go get -u github.com/kunalkushwaha/ltag
|
||||
- pushd ..; go get -u github.com/vbatts/git-validation; popd
|
||||
- pushd ..; go get -u github.com/kunalkushwaha/ltag; popd
|
||||
|
||||
before_script:
|
||||
- pushd ..; git clone https://github.com/containerd/project; popd
|
||||
|
@ -19,6 +18,7 @@ before_script:
|
|||
script:
|
||||
- DCO_VERBOSITY=-q ../project/script/validate/dco
|
||||
- ../project/script/validate/fileheader ../project/
|
||||
- travis_wait ../project/script/validate/vendor
|
||||
- go test -race
|
||||
- GOOS=openbsd go build
|
||||
- GOOS=openbsd go test -c
|
||||
|
|
|
@ -24,10 +24,17 @@ import (
|
|||
|
||||
var ErrNotAConsole = errors.New("provided file is not a console")
|
||||
|
||||
type File interface {
|
||||
io.ReadWriteCloser
|
||||
|
||||
// Fd returns its file descriptor
|
||||
Fd() uintptr
|
||||
// Name returns its file name
|
||||
Name() string
|
||||
}
|
||||
|
||||
type Console interface {
|
||||
io.Reader
|
||||
io.Writer
|
||||
io.Closer
|
||||
File
|
||||
|
||||
// Resize resizes the console to the provided window size
|
||||
Resize(WinSize) error
|
||||
|
@ -42,10 +49,6 @@ type Console interface {
|
|||
Reset() error
|
||||
// Size returns the window size of the console
|
||||
Size() (WinSize, error)
|
||||
// Fd returns the console's file descriptor
|
||||
Fd() uintptr
|
||||
// Name returns the console's file name
|
||||
Name() string
|
||||
}
|
||||
|
||||
// WinSize specifies the window size of the console
|
||||
|
@ -70,7 +73,7 @@ func Current() Console {
|
|||
}
|
||||
|
||||
// ConsoleFromFile returns a console using the provided file
|
||||
func ConsoleFromFile(f *os.File) (Console, error) {
|
||||
func ConsoleFromFile(f File) (Console, error) {
|
||||
if err := checkConsole(f); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
|
@ -58,6 +58,7 @@ type Epoller struct {
|
|||
efd int
|
||||
mu sync.Mutex
|
||||
fdMapping map[int]*EpollConsole
|
||||
closeOnce sync.Once
|
||||
}
|
||||
|
||||
// NewEpoller returns an instance of epoller with a valid epoll fd.
|
||||
|
@ -151,7 +152,11 @@ func (e *Epoller) getConsole(sysfd int) *EpollConsole {
|
|||
|
||||
// Close closes the epoll fd
|
||||
func (e *Epoller) Close() error {
|
||||
return unix.Close(e.efd)
|
||||
closeErr := os.ErrClosed // default to "file already closed"
|
||||
e.closeOnce.Do(func() {
|
||||
closeErr = unix.Close(e.efd)
|
||||
})
|
||||
return closeErr
|
||||
}
|
||||
|
||||
// EpollConsole acts like a console but registers its file descriptor with an
|
||||
|
|
|
@ -47,7 +47,7 @@ func NewPty() (Console, string, error) {
|
|||
}
|
||||
|
||||
type master struct {
|
||||
f *os.File
|
||||
f File
|
||||
original *unix.Termios
|
||||
}
|
||||
|
||||
|
@ -122,7 +122,7 @@ func (m *master) Name() string {
|
|||
}
|
||||
|
||||
// checkConsole checks if the provided file is a console
|
||||
func checkConsole(f *os.File) error {
|
||||
func checkConsole(f File) error {
|
||||
var termios unix.Termios
|
||||
if tcget(f.Fd(), &termios) != nil {
|
||||
return ErrNotAConsole
|
||||
|
@ -130,7 +130,7 @@ func checkConsole(f *os.File) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func newMaster(f *os.File) (Console, error) {
|
||||
func newMaster(f File) (Console, error) {
|
||||
m := &master{
|
||||
f: f,
|
||||
}
|
||||
|
|
|
@ -198,7 +198,7 @@ func makeInputRaw(fd windows.Handle, mode uint32) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func checkConsole(f *os.File) error {
|
||||
func checkConsole(f File) error {
|
||||
var mode uint32
|
||||
if err := windows.GetConsoleMode(windows.Handle(f.Fd()), &mode); err != nil {
|
||||
return err
|
||||
|
@ -206,7 +206,7 @@ func checkConsole(f *os.File) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func newMaster(f *os.File) (Console, error) {
|
||||
func newMaster(f File) (Console, error) {
|
||||
if f != os.Stdin && f != os.Stdout && f != os.Stderr {
|
||||
return nil, errors.New("creating a console from a file is not supported on windows")
|
||||
}
|
||||
|
|
|
@ -0,0 +1,8 @@
|
|||
module github.com/containerd/console
|
||||
|
||||
go 1.13
|
||||
|
||||
require (
|
||||
github.com/pkg/errors v0.8.1
|
||||
golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e
|
||||
)
|
|
@ -0,0 +1,4 @@
|
|||
github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
|
||||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e h1:N7DeIrjYszNmSW409R3frPPwglRwMkXSBzwVbkOjLLA=
|
||||
golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
@ -0,0 +1,21 @@
|
|||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2014 Brian Goff
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
|
@ -0,0 +1,14 @@
|
|||
package md2man
|
||||
|
||||
import (
|
||||
"github.com/russross/blackfriday/v2"
|
||||
)
|
||||
|
||||
// Render converts a markdown document into a roff formatted document.
|
||||
func Render(doc []byte) []byte {
|
||||
renderer := NewRoffRenderer()
|
||||
|
||||
return blackfriday.Run(doc,
|
||||
[]blackfriday.Option{blackfriday.WithRenderer(renderer),
|
||||
blackfriday.WithExtensions(renderer.GetExtensions())}...)
|
||||
}
|
|
@ -0,0 +1,345 @@
|
|||
package md2man
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/russross/blackfriday/v2"
|
||||
)
|
||||
|
||||
// roffRenderer implements the blackfriday.Renderer interface for creating
|
||||
// roff format (manpages) from markdown text
|
||||
type roffRenderer struct {
|
||||
extensions blackfriday.Extensions
|
||||
listCounters []int
|
||||
firstHeader bool
|
||||
defineTerm bool
|
||||
listDepth int
|
||||
}
|
||||
|
||||
const (
|
||||
titleHeader = ".TH "
|
||||
topLevelHeader = "\n\n.SH "
|
||||
secondLevelHdr = "\n.SH "
|
||||
otherHeader = "\n.SS "
|
||||
crTag = "\n"
|
||||
emphTag = "\\fI"
|
||||
emphCloseTag = "\\fP"
|
||||
strongTag = "\\fB"
|
||||
strongCloseTag = "\\fP"
|
||||
breakTag = "\n.br\n"
|
||||
paraTag = "\n.PP\n"
|
||||
hruleTag = "\n.ti 0\n\\l'\\n(.lu'\n"
|
||||
linkTag = "\n\\[la]"
|
||||
linkCloseTag = "\\[ra]"
|
||||
codespanTag = "\\fB\\fC"
|
||||
codespanCloseTag = "\\fR"
|
||||
codeTag = "\n.PP\n.RS\n\n.nf\n"
|
||||
codeCloseTag = "\n.fi\n.RE\n"
|
||||
quoteTag = "\n.PP\n.RS\n"
|
||||
quoteCloseTag = "\n.RE\n"
|
||||
listTag = "\n.RS\n"
|
||||
listCloseTag = "\n.RE\n"
|
||||
arglistTag = "\n.TP\n"
|
||||
tableStart = "\n.TS\nallbox;\n"
|
||||
tableEnd = ".TE\n"
|
||||
tableCellStart = "T{\n"
|
||||
tableCellEnd = "\nT}\n"
|
||||
)
|
||||
|
||||
// NewRoffRenderer creates a new blackfriday Renderer for generating roff documents
|
||||
// from markdown
|
||||
func NewRoffRenderer() *roffRenderer { // nolint: golint
|
||||
var extensions blackfriday.Extensions
|
||||
|
||||
extensions |= blackfriday.NoIntraEmphasis
|
||||
extensions |= blackfriday.Tables
|
||||
extensions |= blackfriday.FencedCode
|
||||
extensions |= blackfriday.SpaceHeadings
|
||||
extensions |= blackfriday.Footnotes
|
||||
extensions |= blackfriday.Titleblock
|
||||
extensions |= blackfriday.DefinitionLists
|
||||
return &roffRenderer{
|
||||
extensions: extensions,
|
||||
}
|
||||
}
|
||||
|
||||
// GetExtensions returns the list of extensions used by this renderer implementation
|
||||
func (r *roffRenderer) GetExtensions() blackfriday.Extensions {
|
||||
return r.extensions
|
||||
}
|
||||
|
||||
// RenderHeader handles outputting the header at document start
|
||||
func (r *roffRenderer) RenderHeader(w io.Writer, ast *blackfriday.Node) {
|
||||
// disable hyphenation
|
||||
out(w, ".nh\n")
|
||||
}
|
||||
|
||||
// RenderFooter handles outputting the footer at the document end; the roff
|
||||
// renderer has no footer information
|
||||
func (r *roffRenderer) RenderFooter(w io.Writer, ast *blackfriday.Node) {
|
||||
}
|
||||
|
||||
// RenderNode is called for each node in a markdown document; based on the node
|
||||
// type the equivalent roff output is sent to the writer
|
||||
func (r *roffRenderer) RenderNode(w io.Writer, node *blackfriday.Node, entering bool) blackfriday.WalkStatus {
|
||||
|
||||
var walkAction = blackfriday.GoToNext
|
||||
|
||||
switch node.Type {
|
||||
case blackfriday.Text:
|
||||
r.handleText(w, node, entering)
|
||||
case blackfriday.Softbreak:
|
||||
out(w, crTag)
|
||||
case blackfriday.Hardbreak:
|
||||
out(w, breakTag)
|
||||
case blackfriday.Emph:
|
||||
if entering {
|
||||
out(w, emphTag)
|
||||
} else {
|
||||
out(w, emphCloseTag)
|
||||
}
|
||||
case blackfriday.Strong:
|
||||
if entering {
|
||||
out(w, strongTag)
|
||||
} else {
|
||||
out(w, strongCloseTag)
|
||||
}
|
||||
case blackfriday.Link:
|
||||
if !entering {
|
||||
out(w, linkTag+string(node.LinkData.Destination)+linkCloseTag)
|
||||
}
|
||||
case blackfriday.Image:
|
||||
// ignore images
|
||||
walkAction = blackfriday.SkipChildren
|
||||
case blackfriday.Code:
|
||||
out(w, codespanTag)
|
||||
escapeSpecialChars(w, node.Literal)
|
||||
out(w, codespanCloseTag)
|
||||
case blackfriday.Document:
|
||||
break
|
||||
case blackfriday.Paragraph:
|
||||
// roff .PP markers break lists
|
||||
if r.listDepth > 0 {
|
||||
return blackfriday.GoToNext
|
||||
}
|
||||
if entering {
|
||||
out(w, paraTag)
|
||||
} else {
|
||||
out(w, crTag)
|
||||
}
|
||||
case blackfriday.BlockQuote:
|
||||
if entering {
|
||||
out(w, quoteTag)
|
||||
} else {
|
||||
out(w, quoteCloseTag)
|
||||
}
|
||||
case blackfriday.Heading:
|
||||
r.handleHeading(w, node, entering)
|
||||
case blackfriday.HorizontalRule:
|
||||
out(w, hruleTag)
|
||||
case blackfriday.List:
|
||||
r.handleList(w, node, entering)
|
||||
case blackfriday.Item:
|
||||
r.handleItem(w, node, entering)
|
||||
case blackfriday.CodeBlock:
|
||||
out(w, codeTag)
|
||||
escapeSpecialChars(w, node.Literal)
|
||||
out(w, codeCloseTag)
|
||||
case blackfriday.Table:
|
||||
r.handleTable(w, node, entering)
|
||||
case blackfriday.TableCell:
|
||||
r.handleTableCell(w, node, entering)
|
||||
case blackfriday.TableHead:
|
||||
case blackfriday.TableBody:
|
||||
case blackfriday.TableRow:
|
||||
// no action as cell entries do all the nroff formatting
|
||||
return blackfriday.GoToNext
|
||||
default:
|
||||
fmt.Fprintln(os.Stderr, "WARNING: go-md2man does not handle node type "+node.Type.String())
|
||||
}
|
||||
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) {
|
||||
if entering {
|
||||
switch node.Level {
|
||||
case 1:
|
||||
if !r.firstHeader {
|
||||
out(w, titleHeader)
|
||||
r.firstHeader = true
|
||||
break
|
||||
}
|
||||
out(w, topLevelHeader)
|
||||
case 2:
|
||||
out(w, secondLevelHdr)
|
||||
default:
|
||||
out(w, otherHeader)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (r *roffRenderer) handleList(w io.Writer, node *blackfriday.Node, entering bool) {
|
||||
openTag := listTag
|
||||
closeTag := listCloseTag
|
||||
if node.ListFlags&blackfriday.ListTypeDefinition != 0 {
|
||||
// tags for definition lists handled within Item node
|
||||
openTag = ""
|
||||
closeTag = ""
|
||||
}
|
||||
if entering {
|
||||
r.listDepth++
|
||||
if node.ListFlags&blackfriday.ListTypeOrdered != 0 {
|
||||
r.listCounters = append(r.listCounters, 1)
|
||||
}
|
||||
out(w, openTag)
|
||||
} else {
|
||||
if node.ListFlags&blackfriday.ListTypeOrdered != 0 {
|
||||
r.listCounters = r.listCounters[:len(r.listCounters)-1]
|
||||
}
|
||||
out(w, closeTag)
|
||||
r.listDepth--
|
||||
}
|
||||
}
|
||||
|
||||
func (r *roffRenderer) handleItem(w io.Writer, node *blackfriday.Node, entering bool) {
|
||||
if entering {
|
||||
if node.ListFlags&blackfriday.ListTypeOrdered != 0 {
|
||||
out(w, fmt.Sprintf(".IP \"%3d.\" 5\n", r.listCounters[len(r.listCounters)-1]))
|
||||
r.listCounters[len(r.listCounters)-1]++
|
||||
} else if node.ListFlags&blackfriday.ListTypeDefinition != 0 {
|
||||
// state machine for handling terms and following definitions
|
||||
// since blackfriday does not distinguish them properly, nor
|
||||
// does it seperate them into separate lists as it should
|
||||
if !r.defineTerm {
|
||||
out(w, arglistTag)
|
||||
r.defineTerm = true
|
||||
} else {
|
||||
r.defineTerm = false
|
||||
}
|
||||
} else {
|
||||
out(w, ".IP \\(bu 2\n")
|
||||
}
|
||||
} else {
|
||||
out(w, "\n")
|
||||
}
|
||||
}
|
||||
|
||||
func (r *roffRenderer) handleTable(w io.Writer, node *blackfriday.Node, entering bool) {
|
||||
if entering {
|
||||
out(w, tableStart)
|
||||
//call walker to count cells (and rows?) so format section can be produced
|
||||
columns := countColumns(node)
|
||||
out(w, strings.Repeat("l ", columns)+"\n")
|
||||
out(w, strings.Repeat("l ", columns)+".\n")
|
||||
} else {
|
||||
out(w, tableEnd)
|
||||
}
|
||||
}
|
||||
|
||||
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 node.Prev != nil && node.Prev.Type == blackfriday.TableCell {
|
||||
out(w, "\t"+start)
|
||||
} else {
|
||||
out(w, start)
|
||||
}
|
||||
} else {
|
||||
// need to carriage return if we are at the end of the header row
|
||||
if node.IsHeader && node.Next == nil {
|
||||
end = end + crTag
|
||||
}
|
||||
out(w, end)
|
||||
}
|
||||
}
|
||||
|
||||
// because roff format requires knowing the column count before outputting any table
|
||||
// data we need to walk a table tree and count the columns
|
||||
func countColumns(node *blackfriday.Node) int {
|
||||
var columns int
|
||||
|
||||
node.Walk(func(node *blackfriday.Node, entering bool) blackfriday.WalkStatus {
|
||||
switch node.Type {
|
||||
case blackfriday.TableRow:
|
||||
if !entering {
|
||||
return blackfriday.Terminate
|
||||
}
|
||||
case blackfriday.TableCell:
|
||||
if entering {
|
||||
columns++
|
||||
}
|
||||
default:
|
||||
}
|
||||
return blackfriday.GoToNext
|
||||
})
|
||||
return columns
|
||||
}
|
||||
|
||||
func out(w io.Writer, output string) {
|
||||
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) {
|
||||
for i := 0; i < len(text); i++ {
|
||||
// escape initial apostrophe or period
|
||||
if len(text) >= 1 && (text[0] == '\'' || text[0] == '.') {
|
||||
out(w, "\\&")
|
||||
}
|
||||
|
||||
// directly copy normal characters
|
||||
org := i
|
||||
|
||||
for i < len(text) && !needsBackslash(text[i]) {
|
||||
i++
|
||||
}
|
||||
if i > org {
|
||||
w.Write(text[org:i]) // nolint: errcheck
|
||||
}
|
||||
|
||||
// escape a character
|
||||
if i >= len(text) {
|
||||
break
|
||||
}
|
||||
|
||||
w.Write([]byte{'\\', text[i]}) // nolint: errcheck
|
||||
}
|
||||
}
|
|
@ -27,7 +27,7 @@
|
|||
|
||||
[people.akihirosuda]
|
||||
Name = "Akihiro Suda"
|
||||
Email = "suda.akihiro@lab.ntt.co.jp"
|
||||
Email = "akihiro.suda.cz@hco.ntt.co.jp"
|
||||
GitHub = "AkihiroSuda"
|
||||
|
||||
[people.dnephin]
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
dependencies:
|
||||
post:
|
||||
# install golint
|
||||
- go get github.com/golang/lint/golint
|
||||
- go get golang.org/x/lint/golint
|
||||
|
||||
test:
|
||||
pre:
|
||||
|
|
|
@ -18,7 +18,7 @@ func HumanDuration(d time.Duration) string {
|
|||
return fmt.Sprintf("%d seconds", seconds)
|
||||
} else if minutes := int(d.Minutes()); minutes == 1 {
|
||||
return "About a minute"
|
||||
} else if minutes < 46 {
|
||||
} else if minutes < 60 {
|
||||
return fmt.Sprintf("%d minutes", minutes)
|
||||
} else if hours := int(d.Hours() + 0.5); hours == 1 {
|
||||
return "About an hour"
|
||||
|
|
|
@ -96,8 +96,13 @@ func ParseUlimit(val string) (*Ulimit, error) {
|
|||
return nil, fmt.Errorf("too many limit value arguments - %s, can only have up to two, `soft[:hard]`", parts[1])
|
||||
}
|
||||
|
||||
if soft > *hard {
|
||||
return nil, fmt.Errorf("ulimit soft limit must be less than or equal to hard limit: %d > %d", soft, *hard)
|
||||
if *hard != -1 {
|
||||
if soft == -1 {
|
||||
return nil, fmt.Errorf("ulimit soft limit must be less than or equal to hard limit: soft: -1 (unlimited), hard: %d", *hard)
|
||||
}
|
||||
if soft > *hard {
|
||||
return nil, fmt.Errorf("ulimit soft limit must be less than or equal to hard limit: %d > %d", soft, *hard)
|
||||
}
|
||||
}
|
||||
|
||||
return &Ulimit{Name: parts[0], Soft: soft, Hard: *hard}, nil
|
||||
|
|
|
@ -1,7 +1,4 @@
|
|||
Go support for Protocol Buffers - Google's data interchange format
|
||||
|
||||
Copyright 2010 The Go Authors. All rights reserved.
|
||||
https://github.com/golang/protobuf
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are
|
||||
|
|
|
@ -1,43 +0,0 @@
|
|||
# Go support for Protocol Buffers - Google's data interchange format
|
||||
#
|
||||
# Copyright 2010 The Go Authors. All rights reserved.
|
||||
# https://github.com/golang/protobuf
|
||||
#
|
||||
# Redistribution and use in source and binary forms, with or without
|
||||
# modification, are permitted provided that the following conditions are
|
||||
# met:
|
||||
#
|
||||
# * Redistributions of source code must retain the above copyright
|
||||
# notice, this list of conditions and the following disclaimer.
|
||||
# * Redistributions in binary form must reproduce the above
|
||||
# copyright notice, this list of conditions and the following disclaimer
|
||||
# in the documentation and/or other materials provided with the
|
||||
# distribution.
|
||||
# * Neither the name of Google Inc. nor the names of its
|
||||
# contributors may be used to endorse or promote products derived from
|
||||
# this software without specific prior written permission.
|
||||
#
|
||||
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
install:
|
||||
go install
|
||||
|
||||
test: install generate-test-pbs
|
||||
go test
|
||||
|
||||
|
||||
generate-test-pbs:
|
||||
make install
|
||||
make -C testdata
|
||||
protoc --go_out=Mtestdata/test.proto=github.com/golang/protobuf/proto/testdata,Mgoogle/protobuf/any.proto=github.com/golang/protobuf/ptypes/any:. proto3_proto/proto3.proto
|
||||
make
|
|
@ -35,22 +35,39 @@
|
|||
package proto
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"reflect"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Clone returns a deep copy of a protocol buffer.
|
||||
func Clone(pb Message) Message {
|
||||
in := reflect.ValueOf(pb)
|
||||
func Clone(src Message) Message {
|
||||
in := reflect.ValueOf(src)
|
||||
if in.IsNil() {
|
||||
return pb
|
||||
return src
|
||||
}
|
||||
|
||||
out := reflect.New(in.Type().Elem())
|
||||
// out is empty so a merge is a deep copy.
|
||||
mergeStruct(out.Elem(), in.Elem())
|
||||
return out.Interface().(Message)
|
||||
dst := out.Interface().(Message)
|
||||
Merge(dst, src)
|
||||
return dst
|
||||
}
|
||||
|
||||
// Merger is the interface representing objects that can merge messages of the same type.
|
||||
type Merger interface {
|
||||
// Merge merges src into this message.
|
||||
// Required and optional fields that are set in src will be set to that value in dst.
|
||||
// Elements of repeated fields will be appended.
|
||||
//
|
||||
// Merge may panic if called with a different argument type than the receiver.
|
||||
Merge(src Message)
|
||||
}
|
||||
|
||||
// generatedMerger is the custom merge method that generated protos will have.
|
||||
// We must add this method since a generate Merge method will conflict with
|
||||
// many existing protos that have a Merge data field already defined.
|
||||
type generatedMerger interface {
|
||||
XXX_Merge(src Message)
|
||||
}
|
||||
|
||||
// Merge merges src into dst.
|
||||
|
@ -58,17 +75,24 @@ func Clone(pb Message) Message {
|
|||
// Elements of repeated fields will be appended.
|
||||
// Merge panics if src and dst are not the same type, or if dst is nil.
|
||||
func Merge(dst, src Message) {
|
||||
if m, ok := dst.(Merger); ok {
|
||||
m.Merge(src)
|
||||
return
|
||||
}
|
||||
|
||||
in := reflect.ValueOf(src)
|
||||
out := reflect.ValueOf(dst)
|
||||
if out.IsNil() {
|
||||
panic("proto: nil destination")
|
||||
}
|
||||
if in.Type() != out.Type() {
|
||||
// Explicit test prior to mergeStruct so that mistyped nils will fail
|
||||
panic("proto: type mismatch")
|
||||
panic(fmt.Sprintf("proto.Merge(%T, %T) type mismatch", dst, src))
|
||||
}
|
||||
if in.IsNil() {
|
||||
// Merging nil into non-nil is a quiet no-op
|
||||
return // Merge from nil src is a noop
|
||||
}
|
||||
if m, ok := dst.(generatedMerger); ok {
|
||||
m.XXX_Merge(src)
|
||||
return
|
||||
}
|
||||
mergeStruct(out.Elem(), in.Elem())
|
||||
|
@ -84,7 +108,7 @@ func mergeStruct(out, in reflect.Value) {
|
|||
mergeAny(out.Field(i), in.Field(i), false, sprop.Prop[i])
|
||||
}
|
||||
|
||||
if emIn, ok := extendable(in.Addr().Interface()); ok {
|
||||
if emIn, err := extendable(in.Addr().Interface()); err == nil {
|
||||
emOut, _ := extendable(out.Addr().Interface())
|
||||
mIn, muIn := emIn.extensionsRead()
|
||||
if mIn != nil {
|
||||
|
|
|
@ -39,8 +39,6 @@ import (
|
|||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"reflect"
|
||||
)
|
||||
|
||||
// errOverflow is returned when an integer is too large to be represented.
|
||||
|
@ -50,10 +48,6 @@ var errOverflow = errors.New("proto: integer overflow")
|
|||
// wire type is encountered. It does not get returned to user code.
|
||||
var ErrInternalBadWireType = errors.New("proto: internal error: bad wiretype for oneof")
|
||||
|
||||
// The fundamental decoders that interpret bytes on the wire.
|
||||
// Those that take integer types all return uint64 and are
|
||||
// therefore of type valueDecoder.
|
||||
|
||||
// DecodeVarint reads a varint-encoded integer from the slice.
|
||||
// It returns the integer and the number of bytes consumed, or
|
||||
// zero if there is not enough.
|
||||
|
@ -192,7 +186,6 @@ func (p *Buffer) DecodeVarint() (x uint64, err error) {
|
|||
if b&0x80 == 0 {
|
||||
goto done
|
||||
}
|
||||
// x -= 0x80 << 63 // Always zero.
|
||||
|
||||
return 0, errOverflow
|
||||
|
||||
|
@ -267,9 +260,6 @@ func (p *Buffer) DecodeZigzag32() (x uint64, err error) {
|
|||
return
|
||||
}
|
||||
|
||||
// These are not ValueDecoders: they produce an array of bytes or a string.
|
||||
// bytes, embedded messages
|
||||
|
||||
// DecodeRawBytes reads a count-delimited byte buffer from the Buffer.
|
||||
// This is the format used for the bytes protocol buffer
|
||||
// type and for embedded messages.
|
||||
|
@ -311,81 +301,29 @@ func (p *Buffer) DecodeStringBytes() (s string, err error) {
|
|||
return string(buf), nil
|
||||
}
|
||||
|
||||
// Skip the next item in the buffer. Its wire type is decoded and presented as an argument.
|
||||
// If the protocol buffer has extensions, and the field matches, add it as an extension.
|
||||
// Otherwise, if the XXX_unrecognized field exists, append the skipped data there.
|
||||
func (o *Buffer) skipAndSave(t reflect.Type, tag, wire int, base structPointer, unrecField field) error {
|
||||
oi := o.index
|
||||
|
||||
err := o.skip(t, tag, wire)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !unrecField.IsValid() {
|
||||
return nil
|
||||
}
|
||||
|
||||
ptr := structPointer_Bytes(base, unrecField)
|
||||
|
||||
// Add the skipped field to struct field
|
||||
obuf := o.buf
|
||||
|
||||
o.buf = *ptr
|
||||
o.EncodeVarint(uint64(tag<<3 | wire))
|
||||
*ptr = append(o.buf, obuf[oi:o.index]...)
|
||||
|
||||
o.buf = obuf
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Skip the next item in the buffer. Its wire type is decoded and presented as an argument.
|
||||
func (o *Buffer) skip(t reflect.Type, tag, wire int) error {
|
||||
|
||||
var u uint64
|
||||
var err error
|
||||
|
||||
switch wire {
|
||||
case WireVarint:
|
||||
_, err = o.DecodeVarint()
|
||||
case WireFixed64:
|
||||
_, err = o.DecodeFixed64()
|
||||
case WireBytes:
|
||||
_, err = o.DecodeRawBytes(false)
|
||||
case WireFixed32:
|
||||
_, err = o.DecodeFixed32()
|
||||
case WireStartGroup:
|
||||
for {
|
||||
u, err = o.DecodeVarint()
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
fwire := int(u & 0x7)
|
||||
if fwire == WireEndGroup {
|
||||
break
|
||||
}
|
||||
ftag := int(u >> 3)
|
||||
err = o.skip(t, ftag, fwire)
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
}
|
||||
default:
|
||||
err = fmt.Errorf("proto: can't skip unknown wire type %d for %s", wire, t)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// Unmarshaler is the interface representing objects that can
|
||||
// unmarshal themselves. The method should reset the receiver before
|
||||
// decoding starts. The argument points to data that may be
|
||||
// unmarshal themselves. The argument points to data that may be
|
||||
// overwritten, so implementations should not keep references to the
|
||||
// buffer.
|
||||
// Unmarshal implementations should not clear the receiver.
|
||||
// Any unmarshaled data should be merged into the receiver.
|
||||
// Callers of Unmarshal that do not want to retain existing data
|
||||
// should Reset the receiver before calling Unmarshal.
|
||||
type Unmarshaler interface {
|
||||
Unmarshal([]byte) error
|
||||
}
|
||||
|
||||
// newUnmarshaler is the interface representing objects that can
|
||||
// unmarshal themselves. The semantics are identical to Unmarshaler.
|
||||
//
|
||||
// This exists to support protoc-gen-go generated messages.
|
||||
// The proto package will stop type-asserting to this interface in the future.
|
||||
//
|
||||
// DO NOT DEPEND ON THIS.
|
||||
type newUnmarshaler interface {
|
||||
XXX_Unmarshal([]byte) error
|
||||
}
|
||||
|
||||
// Unmarshal parses the protocol buffer representation in buf and places the
|
||||
// decoded result in pb. If the struct underlying pb does not match
|
||||
// the data in buf, the results can be unpredictable.
|
||||
|
@ -395,7 +333,13 @@ type Unmarshaler interface {
|
|||
// to preserve and append to existing data.
|
||||
func Unmarshal(buf []byte, pb Message) error {
|
||||
pb.Reset()
|
||||
return UnmarshalMerge(buf, pb)
|
||||
if u, ok := pb.(newUnmarshaler); ok {
|
||||
return u.XXX_Unmarshal(buf)
|
||||
}
|
||||
if u, ok := pb.(Unmarshaler); ok {
|
||||
return u.Unmarshal(buf)
|
||||
}
|
||||
return NewBuffer(buf).Unmarshal(pb)
|
||||
}
|
||||
|
||||
// UnmarshalMerge parses the protocol buffer representation in buf and
|
||||
|
@ -405,8 +349,16 @@ func Unmarshal(buf []byte, pb Message) error {
|
|||
// UnmarshalMerge merges into existing data in pb.
|
||||
// Most code should use Unmarshal instead.
|
||||
func UnmarshalMerge(buf []byte, pb Message) error {
|
||||
// If the object can unmarshal itself, let it.
|
||||
if u, ok := pb.(newUnmarshaler); ok {
|
||||
return u.XXX_Unmarshal(buf)
|
||||
}
|
||||
if u, ok := pb.(Unmarshaler); ok {
|
||||
// NOTE: The history of proto have unfortunately been inconsistent
|
||||
// whether Unmarshaler should or should not implicitly clear itself.
|
||||
// Some implementations do, most do not.
|
||||
// Thus, calling this here may or may not do what people want.
|
||||
//
|
||||
// See https://github.com/golang/protobuf/issues/424
|
||||
return u.Unmarshal(buf)
|
||||
}
|
||||
return NewBuffer(buf).Unmarshal(pb)
|
||||
|
@ -422,12 +374,17 @@ func (p *Buffer) DecodeMessage(pb Message) error {
|
|||
}
|
||||
|
||||
// DecodeGroup reads a tag-delimited group from the Buffer.
|
||||
// StartGroup tag is already consumed. This function consumes
|
||||
// EndGroup tag.
|
||||
func (p *Buffer) DecodeGroup(pb Message) error {
|
||||
typ, base, err := getbase(pb)
|
||||
if err != nil {
|
||||
return err
|
||||
b := p.buf[p.index:]
|
||||
x, y := findEndGroup(b)
|
||||
if x < 0 {
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
return p.unmarshalType(typ.Elem(), GetProperties(typ.Elem()), true, base)
|
||||
err := Unmarshal(b[:x], pb)
|
||||
p.index += y
|
||||
return err
|
||||
}
|
||||
|
||||
// Unmarshal parses the protocol buffer representation in the
|
||||
|
@ -438,533 +395,33 @@ func (p *Buffer) DecodeGroup(pb Message) error {
|
|||
// Unlike proto.Unmarshal, this does not reset pb before starting to unmarshal.
|
||||
func (p *Buffer) Unmarshal(pb Message) error {
|
||||
// If the object can unmarshal itself, let it.
|
||||
if u, ok := pb.(newUnmarshaler); ok {
|
||||
err := u.XXX_Unmarshal(p.buf[p.index:])
|
||||
p.index = len(p.buf)
|
||||
return err
|
||||
}
|
||||
if u, ok := pb.(Unmarshaler); ok {
|
||||
// NOTE: The history of proto have unfortunately been inconsistent
|
||||
// whether Unmarshaler should or should not implicitly clear itself.
|
||||
// Some implementations do, most do not.
|
||||
// Thus, calling this here may or may not do what people want.
|
||||
//
|
||||
// See https://github.com/golang/protobuf/issues/424
|
||||
err := u.Unmarshal(p.buf[p.index:])
|
||||
p.index = len(p.buf)
|
||||
return err
|
||||
}
|
||||
|
||||
typ, base, err := getbase(pb)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = p.unmarshalType(typ.Elem(), GetProperties(typ.Elem()), false, base)
|
||||
|
||||
if collectStats {
|
||||
stats.Decode++
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
// unmarshalType does the work of unmarshaling a structure.
|
||||
func (o *Buffer) unmarshalType(st reflect.Type, prop *StructProperties, is_group bool, base structPointer) error {
|
||||
var state errorState
|
||||
required, reqFields := prop.reqCount, uint64(0)
|
||||
|
||||
var err error
|
||||
for err == nil && o.index < len(o.buf) {
|
||||
oi := o.index
|
||||
var u uint64
|
||||
u, err = o.DecodeVarint()
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
wire := int(u & 0x7)
|
||||
if wire == WireEndGroup {
|
||||
if is_group {
|
||||
if required > 0 {
|
||||
// Not enough information to determine the exact field.
|
||||
// (See below.)
|
||||
return &RequiredNotSetError{"{Unknown}"}
|
||||
}
|
||||
return nil // input is satisfied
|
||||
}
|
||||
return fmt.Errorf("proto: %s: wiretype end group for non-group", st)
|
||||
}
|
||||
tag := int(u >> 3)
|
||||
if tag <= 0 {
|
||||
return fmt.Errorf("proto: %s: illegal tag %d (wire type %d)", st, tag, wire)
|
||||
}
|
||||
fieldnum, ok := prop.decoderTags.get(tag)
|
||||
if !ok {
|
||||
// Maybe it's an extension?
|
||||
if prop.extendable {
|
||||
if e, _ := extendable(structPointer_Interface(base, st)); isExtensionField(e, int32(tag)) {
|
||||
if err = o.skip(st, tag, wire); err == nil {
|
||||
extmap := e.extensionsWrite()
|
||||
ext := extmap[int32(tag)] // may be missing
|
||||
ext.enc = append(ext.enc, o.buf[oi:o.index]...)
|
||||
extmap[int32(tag)] = ext
|
||||
}
|
||||
continue
|
||||
}
|
||||
}
|
||||
// Maybe it's a oneof?
|
||||
if prop.oneofUnmarshaler != nil {
|
||||
m := structPointer_Interface(base, st).(Message)
|
||||
// First return value indicates whether tag is a oneof field.
|
||||
ok, err = prop.oneofUnmarshaler(m, tag, wire, o)
|
||||
if err == ErrInternalBadWireType {
|
||||
// Map the error to something more descriptive.
|
||||
// Do the formatting here to save generated code space.
|
||||
err = fmt.Errorf("bad wiretype for oneof field in %T", m)
|
||||
}
|
||||
if ok {
|
||||
continue
|
||||
}
|
||||
}
|
||||
err = o.skipAndSave(st, tag, wire, base, prop.unrecField)
|
||||
continue
|
||||
}
|
||||
p := prop.Prop[fieldnum]
|
||||
|
||||
if p.dec == nil {
|
||||
fmt.Fprintf(os.Stderr, "proto: no protobuf decoder for %s.%s\n", st, st.Field(fieldnum).Name)
|
||||
continue
|
||||
}
|
||||
dec := p.dec
|
||||
if wire != WireStartGroup && wire != p.WireType {
|
||||
if wire == WireBytes && p.packedDec != nil {
|
||||
// a packable field
|
||||
dec = p.packedDec
|
||||
} else {
|
||||
err = fmt.Errorf("proto: bad wiretype for field %s.%s: got wiretype %d, want %d", st, st.Field(fieldnum).Name, wire, p.WireType)
|
||||
continue
|
||||
}
|
||||
}
|
||||
decErr := dec(o, p, base)
|
||||
if decErr != nil && !state.shouldContinue(decErr, p) {
|
||||
err = decErr
|
||||
}
|
||||
if err == nil && p.Required {
|
||||
// Successfully decoded a required field.
|
||||
if tag <= 64 {
|
||||
// use bitmap for fields 1-64 to catch field reuse.
|
||||
var mask uint64 = 1 << uint64(tag-1)
|
||||
if reqFields&mask == 0 {
|
||||
// new required field
|
||||
reqFields |= mask
|
||||
required--
|
||||
}
|
||||
} else {
|
||||
// This is imprecise. It can be fooled by a required field
|
||||
// with a tag > 64 that is encoded twice; that's very rare.
|
||||
// A fully correct implementation would require allocating
|
||||
// a data structure, which we would like to avoid.
|
||||
required--
|
||||
}
|
||||
}
|
||||
}
|
||||
if err == nil {
|
||||
if is_group {
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
if state.err != nil {
|
||||
return state.err
|
||||
}
|
||||
if required > 0 {
|
||||
// Not enough information to determine the exact field. If we use extra
|
||||
// CPU, we could determine the field only if the missing required field
|
||||
// has a tag <= 64 and we check reqFields.
|
||||
return &RequiredNotSetError{"{Unknown}"}
|
||||
}
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// Individual type decoders
|
||||
// For each,
|
||||
// u is the decoded value,
|
||||
// v is a pointer to the field (pointer) in the struct
|
||||
|
||||
// Sizes of the pools to allocate inside the Buffer.
|
||||
// The goal is modest amortization and allocation
|
||||
// on at least 16-byte boundaries.
|
||||
const (
|
||||
boolPoolSize = 16
|
||||
uint32PoolSize = 8
|
||||
uint64PoolSize = 4
|
||||
)
|
||||
|
||||
// Decode a bool.
|
||||
func (o *Buffer) dec_bool(p *Properties, base structPointer) error {
|
||||
u, err := p.valDec(o)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(o.bools) == 0 {
|
||||
o.bools = make([]bool, boolPoolSize)
|
||||
}
|
||||
o.bools[0] = u != 0
|
||||
*structPointer_Bool(base, p.field) = &o.bools[0]
|
||||
o.bools = o.bools[1:]
|
||||
return nil
|
||||
}
|
||||
|
||||
func (o *Buffer) dec_proto3_bool(p *Properties, base structPointer) error {
|
||||
u, err := p.valDec(o)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
*structPointer_BoolVal(base, p.field) = u != 0
|
||||
return nil
|
||||
}
|
||||
|
||||
// Decode an int32.
|
||||
func (o *Buffer) dec_int32(p *Properties, base structPointer) error {
|
||||
u, err := p.valDec(o)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
word32_Set(structPointer_Word32(base, p.field), o, uint32(u))
|
||||
return nil
|
||||
}
|
||||
|
||||
func (o *Buffer) dec_proto3_int32(p *Properties, base structPointer) error {
|
||||
u, err := p.valDec(o)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
word32Val_Set(structPointer_Word32Val(base, p.field), uint32(u))
|
||||
return nil
|
||||
}
|
||||
|
||||
// Decode an int64.
|
||||
func (o *Buffer) dec_int64(p *Properties, base structPointer) error {
|
||||
u, err := p.valDec(o)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
word64_Set(structPointer_Word64(base, p.field), o, u)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (o *Buffer) dec_proto3_int64(p *Properties, base structPointer) error {
|
||||
u, err := p.valDec(o)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
word64Val_Set(structPointer_Word64Val(base, p.field), o, u)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Decode a string.
|
||||
func (o *Buffer) dec_string(p *Properties, base structPointer) error {
|
||||
s, err := o.DecodeStringBytes()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
*structPointer_String(base, p.field) = &s
|
||||
return nil
|
||||
}
|
||||
|
||||
func (o *Buffer) dec_proto3_string(p *Properties, base structPointer) error {
|
||||
s, err := o.DecodeStringBytes()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
*structPointer_StringVal(base, p.field) = s
|
||||
return nil
|
||||
}
|
||||
|
||||
// Decode a slice of bytes ([]byte).
|
||||
func (o *Buffer) dec_slice_byte(p *Properties, base structPointer) error {
|
||||
b, err := o.DecodeRawBytes(true)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
*structPointer_Bytes(base, p.field) = b
|
||||
return nil
|
||||
}
|
||||
|
||||
// Decode a slice of bools ([]bool).
|
||||
func (o *Buffer) dec_slice_bool(p *Properties, base structPointer) error {
|
||||
u, err := p.valDec(o)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
v := structPointer_BoolSlice(base, p.field)
|
||||
*v = append(*v, u != 0)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Decode a slice of bools ([]bool) in packed format.
|
||||
func (o *Buffer) dec_slice_packed_bool(p *Properties, base structPointer) error {
|
||||
v := structPointer_BoolSlice(base, p.field)
|
||||
|
||||
nn, err := o.DecodeVarint()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
nb := int(nn) // number of bytes of encoded bools
|
||||
fin := o.index + nb
|
||||
if fin < o.index {
|
||||
return errOverflow
|
||||
}
|
||||
|
||||
y := *v
|
||||
for o.index < fin {
|
||||
u, err := p.valDec(o)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
y = append(y, u != 0)
|
||||
}
|
||||
|
||||
*v = y
|
||||
return nil
|
||||
}
|
||||
|
||||
// Decode a slice of int32s ([]int32).
|
||||
func (o *Buffer) dec_slice_int32(p *Properties, base structPointer) error {
|
||||
u, err := p.valDec(o)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
structPointer_Word32Slice(base, p.field).Append(uint32(u))
|
||||
return nil
|
||||
}
|
||||
|
||||
// Decode a slice of int32s ([]int32) in packed format.
|
||||
func (o *Buffer) dec_slice_packed_int32(p *Properties, base structPointer) error {
|
||||
v := structPointer_Word32Slice(base, p.field)
|
||||
|
||||
nn, err := o.DecodeVarint()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
nb := int(nn) // number of bytes of encoded int32s
|
||||
|
||||
fin := o.index + nb
|
||||
if fin < o.index {
|
||||
return errOverflow
|
||||
}
|
||||
for o.index < fin {
|
||||
u, err := p.valDec(o)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
v.Append(uint32(u))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Decode a slice of int64s ([]int64).
|
||||
func (o *Buffer) dec_slice_int64(p *Properties, base structPointer) error {
|
||||
u, err := p.valDec(o)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
structPointer_Word64Slice(base, p.field).Append(u)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Decode a slice of int64s ([]int64) in packed format.
|
||||
func (o *Buffer) dec_slice_packed_int64(p *Properties, base structPointer) error {
|
||||
v := structPointer_Word64Slice(base, p.field)
|
||||
|
||||
nn, err := o.DecodeVarint()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
nb := int(nn) // number of bytes of encoded int64s
|
||||
|
||||
fin := o.index + nb
|
||||
if fin < o.index {
|
||||
return errOverflow
|
||||
}
|
||||
for o.index < fin {
|
||||
u, err := p.valDec(o)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
v.Append(u)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Decode a slice of strings ([]string).
|
||||
func (o *Buffer) dec_slice_string(p *Properties, base structPointer) error {
|
||||
s, err := o.DecodeStringBytes()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
v := structPointer_StringSlice(base, p.field)
|
||||
*v = append(*v, s)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Decode a slice of slice of bytes ([][]byte).
|
||||
func (o *Buffer) dec_slice_slice_byte(p *Properties, base structPointer) error {
|
||||
b, err := o.DecodeRawBytes(true)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
v := structPointer_BytesSlice(base, p.field)
|
||||
*v = append(*v, b)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Decode a map field.
|
||||
func (o *Buffer) dec_new_map(p *Properties, base structPointer) error {
|
||||
raw, err := o.DecodeRawBytes(false)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
oi := o.index // index at the end of this map entry
|
||||
o.index -= len(raw) // move buffer back to start of map entry
|
||||
|
||||
mptr := structPointer_NewAt(base, p.field, p.mtype) // *map[K]V
|
||||
if mptr.Elem().IsNil() {
|
||||
mptr.Elem().Set(reflect.MakeMap(mptr.Type().Elem()))
|
||||
}
|
||||
v := mptr.Elem() // map[K]V
|
||||
|
||||
// Prepare addressable doubly-indirect placeholders for the key and value types.
|
||||
// See enc_new_map for why.
|
||||
keyptr := reflect.New(reflect.PtrTo(p.mtype.Key())).Elem() // addressable *K
|
||||
keybase := toStructPointer(keyptr.Addr()) // **K
|
||||
|
||||
var valbase structPointer
|
||||
var valptr reflect.Value
|
||||
switch p.mtype.Elem().Kind() {
|
||||
case reflect.Slice:
|
||||
// []byte
|
||||
var dummy []byte
|
||||
valptr = reflect.ValueOf(&dummy) // *[]byte
|
||||
valbase = toStructPointer(valptr) // *[]byte
|
||||
case reflect.Ptr:
|
||||
// message; valptr is **Msg; need to allocate the intermediate pointer
|
||||
valptr = reflect.New(reflect.PtrTo(p.mtype.Elem())).Elem() // addressable *V
|
||||
valptr.Set(reflect.New(valptr.Type().Elem()))
|
||||
valbase = toStructPointer(valptr)
|
||||
default:
|
||||
// everything else
|
||||
valptr = reflect.New(reflect.PtrTo(p.mtype.Elem())).Elem() // addressable *V
|
||||
valbase = toStructPointer(valptr.Addr()) // **V
|
||||
}
|
||||
|
||||
// Decode.
|
||||
// This parses a restricted wire format, namely the encoding of a message
|
||||
// with two fields. See enc_new_map for the format.
|
||||
for o.index < oi {
|
||||
// tagcode for key and value properties are always a single byte
|
||||
// because they have tags 1 and 2.
|
||||
tagcode := o.buf[o.index]
|
||||
o.index++
|
||||
switch tagcode {
|
||||
case p.mkeyprop.tagcode[0]:
|
||||
if err := p.mkeyprop.dec(o, p.mkeyprop, keybase); err != nil {
|
||||
return err
|
||||
}
|
||||
case p.mvalprop.tagcode[0]:
|
||||
if err := p.mvalprop.dec(o, p.mvalprop, valbase); err != nil {
|
||||
return err
|
||||
}
|
||||
default:
|
||||
// TODO: Should we silently skip this instead?
|
||||
return fmt.Errorf("proto: bad map data tag %d", raw[0])
|
||||
}
|
||||
}
|
||||
keyelem, valelem := keyptr.Elem(), valptr.Elem()
|
||||
if !keyelem.IsValid() {
|
||||
keyelem = reflect.Zero(p.mtype.Key())
|
||||
}
|
||||
if !valelem.IsValid() {
|
||||
valelem = reflect.Zero(p.mtype.Elem())
|
||||
}
|
||||
|
||||
v.SetMapIndex(keyelem, valelem)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Decode a group.
|
||||
func (o *Buffer) dec_struct_group(p *Properties, base structPointer) error {
|
||||
bas := structPointer_GetStructPointer(base, p.field)
|
||||
if structPointer_IsNil(bas) {
|
||||
// allocate new nested message
|
||||
bas = toStructPointer(reflect.New(p.stype))
|
||||
structPointer_SetStructPointer(base, p.field, bas)
|
||||
}
|
||||
return o.unmarshalType(p.stype, p.sprop, true, bas)
|
||||
}
|
||||
|
||||
// Decode an embedded message.
|
||||
func (o *Buffer) dec_struct_message(p *Properties, base structPointer) (err error) {
|
||||
raw, e := o.DecodeRawBytes(false)
|
||||
if e != nil {
|
||||
return e
|
||||
}
|
||||
|
||||
bas := structPointer_GetStructPointer(base, p.field)
|
||||
if structPointer_IsNil(bas) {
|
||||
// allocate new nested message
|
||||
bas = toStructPointer(reflect.New(p.stype))
|
||||
structPointer_SetStructPointer(base, p.field, bas)
|
||||
}
|
||||
|
||||
// If the object can unmarshal itself, let it.
|
||||
if p.isUnmarshaler {
|
||||
iv := structPointer_Interface(bas, p.stype)
|
||||
return iv.(Unmarshaler).Unmarshal(raw)
|
||||
}
|
||||
|
||||
obuf := o.buf
|
||||
oi := o.index
|
||||
o.buf = raw
|
||||
o.index = 0
|
||||
|
||||
err = o.unmarshalType(p.stype, p.sprop, false, bas)
|
||||
o.buf = obuf
|
||||
o.index = oi
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
// Decode a slice of embedded messages.
|
||||
func (o *Buffer) dec_slice_struct_message(p *Properties, base structPointer) error {
|
||||
return o.dec_slice_struct(p, false, base)
|
||||
}
|
||||
|
||||
// Decode a slice of embedded groups.
|
||||
func (o *Buffer) dec_slice_struct_group(p *Properties, base structPointer) error {
|
||||
return o.dec_slice_struct(p, true, base)
|
||||
}
|
||||
|
||||
// Decode a slice of structs ([]*struct).
|
||||
func (o *Buffer) dec_slice_struct(p *Properties, is_group bool, base structPointer) error {
|
||||
v := reflect.New(p.stype)
|
||||
bas := toStructPointer(v)
|
||||
structPointer_StructPointerSlice(base, p.field).Append(bas)
|
||||
|
||||
if is_group {
|
||||
err := o.unmarshalType(p.stype, p.sprop, is_group, bas)
|
||||
return err
|
||||
}
|
||||
|
||||
raw, err := o.DecodeRawBytes(false)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// If the object can unmarshal itself, let it.
|
||||
if p.isUnmarshaler {
|
||||
iv := v.Interface()
|
||||
return iv.(Unmarshaler).Unmarshal(raw)
|
||||
}
|
||||
|
||||
obuf := o.buf
|
||||
oi := o.index
|
||||
o.buf = raw
|
||||
o.index = 0
|
||||
|
||||
err = o.unmarshalType(p.stype, p.sprop, is_group, bas)
|
||||
|
||||
o.buf = obuf
|
||||
o.index = oi
|
||||
|
||||
// Slow workaround for messages that aren't Unmarshalers.
|
||||
// This includes some hand-coded .pb.go files and
|
||||
// bootstrap protos.
|
||||
// TODO: fix all of those and then add Unmarshal to
|
||||
// the Message interface. Then:
|
||||
// The cast above and code below can be deleted.
|
||||
// The old unmarshaler can be deleted.
|
||||
// Clients can call Unmarshal directly (can already do that, actually).
|
||||
var info InternalMessageInfo
|
||||
err := info.Unmarshal(pb, p.buf[p.index:])
|
||||
p.index = len(p.buf)
|
||||
return err
|
||||
}
|
||||
|
|
|
@ -0,0 +1,63 @@
|
|||
// Go support for Protocol Buffers - Google's data interchange format
|
||||
//
|
||||
// Copyright 2018 The Go Authors. All rights reserved.
|
||||
// https://github.com/golang/protobuf
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are
|
||||
// met:
|
||||
//
|
||||
// * Redistributions of source code must retain the above copyright
|
||||
// notice, this list of conditions and the following disclaimer.
|
||||
// * Redistributions in binary form must reproduce the above
|
||||
// copyright notice, this list of conditions and the following disclaimer
|
||||
// in the documentation and/or other materials provided with the
|
||||
// distribution.
|
||||
// * Neither the name of Google Inc. nor the names of its
|
||||
// contributors may be used to endorse or promote products derived from
|
||||
// this software without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
package proto
|
||||
|
||||
import "errors"
|
||||
|
||||
// Deprecated: do not use.
|
||||
type Stats struct{ Emalloc, Dmalloc, Encode, Decode, Chit, Cmiss, Size uint64 }
|
||||
|
||||
// Deprecated: do not use.
|
||||
func GetStats() Stats { return Stats{} }
|
||||
|
||||
// Deprecated: do not use.
|
||||
func MarshalMessageSet(interface{}) ([]byte, error) {
|
||||
return nil, errors.New("proto: not implemented")
|
||||
}
|
||||
|
||||
// Deprecated: do not use.
|
||||
func UnmarshalMessageSet([]byte, interface{}) error {
|
||||
return errors.New("proto: not implemented")
|
||||
}
|
||||
|
||||
// Deprecated: do not use.
|
||||
func MarshalMessageSetJSON(interface{}) ([]byte, error) {
|
||||
return nil, errors.New("proto: not implemented")
|
||||
}
|
||||
|
||||
// Deprecated: do not use.
|
||||
func UnmarshalMessageSetJSON([]byte, interface{}) error {
|
||||
return errors.New("proto: not implemented")
|
||||
}
|
||||
|
||||
// Deprecated: do not use.
|
||||
func RegisterMessageSetType(Message, int32, string) {}
|
|
@ -35,8 +35,14 @@ import (
|
|||
"fmt"
|
||||
"reflect"
|
||||
"strings"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
)
|
||||
|
||||
type generatedDiscarder interface {
|
||||
XXX_DiscardUnknown()
|
||||
}
|
||||
|
||||
// DiscardUnknown recursively discards all unknown fields from this message
|
||||
// and all embedded messages.
|
||||
//
|
||||
|
@ -49,9 +55,202 @@ import (
|
|||
// For proto2 messages, the unknown fields of message extensions are only
|
||||
// discarded from messages that have been accessed via GetExtension.
|
||||
func DiscardUnknown(m Message) {
|
||||
if m, ok := m.(generatedDiscarder); ok {
|
||||
m.XXX_DiscardUnknown()
|
||||
return
|
||||
}
|
||||
// TODO: Dynamically populate a InternalMessageInfo for legacy messages,
|
||||
// but the master branch has no implementation for InternalMessageInfo,
|
||||
// so it would be more work to replicate that approach.
|
||||
discardLegacy(m)
|
||||
}
|
||||
|
||||
// DiscardUnknown recursively discards all unknown fields.
|
||||
func (a *InternalMessageInfo) DiscardUnknown(m Message) {
|
||||
di := atomicLoadDiscardInfo(&a.discard)
|
||||
if di == nil {
|
||||
di = getDiscardInfo(reflect.TypeOf(m).Elem())
|
||||
atomicStoreDiscardInfo(&a.discard, di)
|
||||
}
|
||||
di.discard(toPointer(&m))
|
||||
}
|
||||
|
||||
type discardInfo struct {
|
||||
typ reflect.Type
|
||||
|
||||
initialized int32 // 0: only typ is valid, 1: everything is valid
|
||||
lock sync.Mutex
|
||||
|
||||
fields []discardFieldInfo
|
||||
unrecognized field
|
||||
}
|
||||
|
||||
type discardFieldInfo struct {
|
||||
field field // Offset of field, guaranteed to be valid
|
||||
discard func(src pointer)
|
||||
}
|
||||
|
||||
var (
|
||||
discardInfoMap = map[reflect.Type]*discardInfo{}
|
||||
discardInfoLock sync.Mutex
|
||||
)
|
||||
|
||||
func getDiscardInfo(t reflect.Type) *discardInfo {
|
||||
discardInfoLock.Lock()
|
||||
defer discardInfoLock.Unlock()
|
||||
di := discardInfoMap[t]
|
||||
if di == nil {
|
||||
di = &discardInfo{typ: t}
|
||||
discardInfoMap[t] = di
|
||||
}
|
||||
return di
|
||||
}
|
||||
|
||||
func (di *discardInfo) discard(src pointer) {
|
||||
if src.isNil() {
|
||||
return // Nothing to do.
|
||||
}
|
||||
|
||||
if atomic.LoadInt32(&di.initialized) == 0 {
|
||||
di.computeDiscardInfo()
|
||||
}
|
||||
|
||||
for _, fi := range di.fields {
|
||||
sfp := src.offset(fi.field)
|
||||
fi.discard(sfp)
|
||||
}
|
||||
|
||||
// For proto2 messages, only discard unknown fields in message extensions
|
||||
// that have been accessed via GetExtension.
|
||||
if em, err := extendable(src.asPointerTo(di.typ).Interface()); err == nil {
|
||||
// Ignore lock since DiscardUnknown is not concurrency safe.
|
||||
emm, _ := em.extensionsRead()
|
||||
for _, mx := range emm {
|
||||
if m, ok := mx.value.(Message); ok {
|
||||
DiscardUnknown(m)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if di.unrecognized.IsValid() {
|
||||
*src.offset(di.unrecognized).toBytes() = nil
|
||||
}
|
||||
}
|
||||
|
||||
func (di *discardInfo) computeDiscardInfo() {
|
||||
di.lock.Lock()
|
||||
defer di.lock.Unlock()
|
||||
if di.initialized != 0 {
|
||||
return
|
||||
}
|
||||
t := di.typ
|
||||
n := t.NumField()
|
||||
|
||||
for i := 0; i < n; i++ {
|
||||
f := t.Field(i)
|
||||
if strings.HasPrefix(f.Name, "XXX_") {
|
||||
continue
|
||||
}
|
||||
|
||||
dfi := discardFieldInfo{field: toField(&f)}
|
||||
tf := f.Type
|
||||
|
||||
// Unwrap tf to get its most basic type.
|
||||
var isPointer, isSlice bool
|
||||
if tf.Kind() == reflect.Slice && tf.Elem().Kind() != reflect.Uint8 {
|
||||
isSlice = true
|
||||
tf = tf.Elem()
|
||||
}
|
||||
if tf.Kind() == reflect.Ptr {
|
||||
isPointer = true
|
||||
tf = tf.Elem()
|
||||
}
|
||||
if isPointer && isSlice && tf.Kind() != reflect.Struct {
|
||||
panic(fmt.Sprintf("%v.%s cannot be a slice of pointers to primitive types", t, f.Name))
|
||||
}
|
||||
|
||||
switch tf.Kind() {
|
||||
case reflect.Struct:
|
||||
switch {
|
||||
case !isPointer:
|
||||
panic(fmt.Sprintf("%v.%s cannot be a direct struct value", t, f.Name))
|
||||
case isSlice: // E.g., []*pb.T
|
||||
di := getDiscardInfo(tf)
|
||||
dfi.discard = func(src pointer) {
|
||||
sps := src.getPointerSlice()
|
||||
for _, sp := range sps {
|
||||
if !sp.isNil() {
|
||||
di.discard(sp)
|
||||
}
|
||||
}
|
||||
}
|
||||
default: // E.g., *pb.T
|
||||
di := getDiscardInfo(tf)
|
||||
dfi.discard = func(src pointer) {
|
||||
sp := src.getPointer()
|
||||
if !sp.isNil() {
|
||||
di.discard(sp)
|
||||
}
|
||||
}
|
||||
}
|
||||
case reflect.Map:
|
||||
switch {
|
||||
case isPointer || isSlice:
|
||||
panic(fmt.Sprintf("%v.%s cannot be a pointer to a map or a slice of map values", t, f.Name))
|
||||
default: // E.g., map[K]V
|
||||
if tf.Elem().Kind() == reflect.Ptr { // Proto struct (e.g., *T)
|
||||
dfi.discard = func(src pointer) {
|
||||
sm := src.asPointerTo(tf).Elem()
|
||||
if sm.Len() == 0 {
|
||||
return
|
||||
}
|
||||
for _, key := range sm.MapKeys() {
|
||||
val := sm.MapIndex(key)
|
||||
DiscardUnknown(val.Interface().(Message))
|
||||
}
|
||||
}
|
||||
} else {
|
||||
dfi.discard = func(pointer) {} // Noop
|
||||
}
|
||||
}
|
||||
case reflect.Interface:
|
||||
// Must be oneof field.
|
||||
switch {
|
||||
case isPointer || isSlice:
|
||||
panic(fmt.Sprintf("%v.%s cannot be a pointer to a interface or a slice of interface values", t, f.Name))
|
||||
default: // E.g., interface{}
|
||||
// TODO: Make this faster?
|
||||
dfi.discard = func(src pointer) {
|
||||
su := src.asPointerTo(tf).Elem()
|
||||
if !su.IsNil() {
|
||||
sv := su.Elem().Elem().Field(0)
|
||||
if sv.Kind() == reflect.Ptr && sv.IsNil() {
|
||||
return
|
||||
}
|
||||
switch sv.Type().Kind() {
|
||||
case reflect.Ptr: // Proto struct (e.g., *T)
|
||||
DiscardUnknown(sv.Interface().(Message))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
default:
|
||||
continue
|
||||
}
|
||||
di.fields = append(di.fields, dfi)
|
||||
}
|
||||
|
||||
di.unrecognized = invalidField
|
||||
if f, ok := t.FieldByName("XXX_unrecognized"); ok {
|
||||
if f.Type != reflect.TypeOf([]byte{}) {
|
||||
panic("expected XXX_unrecognized to be of type []byte")
|
||||
}
|
||||
di.unrecognized = toField(&f)
|
||||
}
|
||||
|
||||
atomic.StoreInt32(&di.initialized, 1)
|
||||
}
|
||||
|
||||
func discardLegacy(m Message) {
|
||||
v := reflect.ValueOf(m)
|
||||
if v.Kind() != reflect.Ptr || v.IsNil() {
|
||||
|
@ -139,7 +338,7 @@ func discardLegacy(m Message) {
|
|||
|
||||
// For proto2 messages, only discard unknown fields in message extensions
|
||||
// that have been accessed via GetExtension.
|
||||
if em, ok := extendable(m); ok {
|
||||
if em, err := extendable(m); err == nil {
|
||||
// Ignore lock since discardLegacy is not concurrency safe.
|
||||
emm, _ := em.extensionsRead()
|
||||
for _, mx := range emm {
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -109,15 +109,6 @@ func equalStruct(v1, v2 reflect.Value) bool {
|
|||
// set/unset mismatch
|
||||
return false
|
||||
}
|
||||
b1, ok := f1.Interface().(raw)
|
||||
if ok {
|
||||
b2 := f2.Interface().(raw)
|
||||
// RawMessage
|
||||
if !bytes.Equal(b1.Bytes(), b2.Bytes()) {
|
||||
return false
|
||||
}
|
||||
continue
|
||||
}
|
||||
f1, f2 = f1.Elem(), f2.Elem()
|
||||
}
|
||||
if !equalAny(f1, f2, sprop.Prop[i]) {
|
||||
|
@ -146,11 +137,7 @@ func equalStruct(v1, v2 reflect.Value) bool {
|
|||
|
||||
u1 := uf.Bytes()
|
||||
u2 := v2.FieldByName("XXX_unrecognized").Bytes()
|
||||
if !bytes.Equal(u1, u2) {
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
return bytes.Equal(u1, u2)
|
||||
}
|
||||
|
||||
// v1 and v2 are known to have the same type.
|
||||
|
@ -259,7 +246,17 @@ func equalExtMap(base reflect.Type, em1, em2 map[int32]Extension) bool {
|
|||
return false
|
||||
}
|
||||
|
||||
m1, m2 := e1.value, e2.value
|
||||
m1 := extensionAsLegacyType(e1.value)
|
||||
m2 := extensionAsLegacyType(e2.value)
|
||||
|
||||
if m1 == nil && m2 == nil {
|
||||
// Both have only encoded form.
|
||||
if bytes.Equal(e1.enc, e2.enc) {
|
||||
continue
|
||||
}
|
||||
// The bytes are different, but the extensions might still be
|
||||
// equal. We need to decode them to compare.
|
||||
}
|
||||
|
||||
if m1 != nil && m2 != nil {
|
||||
// Both are unencoded.
|
||||
|
@ -276,8 +273,12 @@ func equalExtMap(base reflect.Type, em1, em2 map[int32]Extension) bool {
|
|||
desc = m[extNum]
|
||||
}
|
||||
if desc == nil {
|
||||
// If both have only encoded form and the bytes are the same,
|
||||
// it is handled above. We get here when the bytes are different.
|
||||
// We don't know how to decode it, so just compare them as byte
|
||||
// slices.
|
||||
log.Printf("proto: don't know how to compare extension %d of %v", extNum, base)
|
||||
continue
|
||||
return false
|
||||
}
|
||||
var err error
|
||||
if m1 == nil {
|
||||
|
|
|
@ -38,6 +38,7 @@ package proto
|
|||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"sync"
|
||||
|
@ -91,14 +92,29 @@ func (n notLocker) Unlock() {}
|
|||
// extendable returns the extendableProto interface for the given generated proto message.
|
||||
// If the proto message has the old extension format, it returns a wrapper that implements
|
||||
// the extendableProto interface.
|
||||
func extendable(p interface{}) (extendableProto, bool) {
|
||||
if ep, ok := p.(extendableProto); ok {
|
||||
return ep, ok
|
||||
func extendable(p interface{}) (extendableProto, error) {
|
||||
switch p := p.(type) {
|
||||
case extendableProto:
|
||||
if isNilPtr(p) {
|
||||
return nil, fmt.Errorf("proto: nil %T is not extendable", p)
|
||||
}
|
||||
return p, nil
|
||||
case extendableProtoV1:
|
||||
if isNilPtr(p) {
|
||||
return nil, fmt.Errorf("proto: nil %T is not extendable", p)
|
||||
}
|
||||
return extensionAdapter{p}, nil
|
||||
}
|
||||
if ep, ok := p.(extendableProtoV1); ok {
|
||||
return extensionAdapter{ep}, ok
|
||||
}
|
||||
return nil, false
|
||||
// Don't allocate a specific error containing %T:
|
||||
// this is the hot path for Clone and MarshalText.
|
||||
return nil, errNotExtendable
|
||||
}
|
||||
|
||||
var errNotExtendable = errors.New("proto: not an extendable proto.Message")
|
||||
|
||||
func isNilPtr(x interface{}) bool {
|
||||
v := reflect.ValueOf(x)
|
||||
return v.Kind() == reflect.Ptr && v.IsNil()
|
||||
}
|
||||
|
||||
// XXX_InternalExtensions is an internal representation of proto extensions.
|
||||
|
@ -143,9 +159,6 @@ func (e *XXX_InternalExtensions) extensionsRead() (map[int32]Extension, sync.Loc
|
|||
return e.p.extensionMap, &e.p.mu
|
||||
}
|
||||
|
||||
var extendableProtoType = reflect.TypeOf((*extendableProto)(nil)).Elem()
|
||||
var extendableProtoV1Type = reflect.TypeOf((*extendableProtoV1)(nil)).Elem()
|
||||
|
||||
// ExtensionDesc represents an extension specification.
|
||||
// Used in generated code from the protocol compiler.
|
||||
type ExtensionDesc struct {
|
||||
|
@ -172,15 +185,31 @@ type Extension struct {
|
|||
// extension will have only enc set. When such an extension is
|
||||
// accessed using GetExtension (or GetExtensions) desc and value
|
||||
// will be set.
|
||||
desc *ExtensionDesc
|
||||
desc *ExtensionDesc
|
||||
|
||||
// value is a concrete value for the extension field. Let the type of
|
||||
// desc.ExtensionType be the "API type" and the type of Extension.value
|
||||
// be the "storage type". The API type and storage type are the same except:
|
||||
// * For scalars (except []byte), the API type uses *T,
|
||||
// while the storage type uses T.
|
||||
// * For repeated fields, the API type uses []T, while the storage type
|
||||
// uses *[]T.
|
||||
//
|
||||
// The reason for the divergence is so that the storage type more naturally
|
||||
// matches what is expected of when retrieving the values through the
|
||||
// protobuf reflection APIs.
|
||||
//
|
||||
// The value may only be populated if desc is also populated.
|
||||
value interface{}
|
||||
enc []byte
|
||||
|
||||
// enc is the raw bytes for the extension field.
|
||||
enc []byte
|
||||
}
|
||||
|
||||
// SetRawExtension is for testing only.
|
||||
func SetRawExtension(base Message, id int32, b []byte) {
|
||||
epb, ok := extendable(base)
|
||||
if !ok {
|
||||
epb, err := extendable(base)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
extmap := epb.extensionsWrite()
|
||||
|
@ -205,7 +234,7 @@ func checkExtensionTypes(pb extendableProto, extension *ExtensionDesc) error {
|
|||
pbi = ea.extendableProtoV1
|
||||
}
|
||||
if a, b := reflect.TypeOf(pbi), reflect.TypeOf(extension.ExtendedType); a != b {
|
||||
return errors.New("proto: bad extended type; " + b.String() + " does not extend " + a.String())
|
||||
return fmt.Errorf("proto: bad extended type; %v does not extend %v", b, a)
|
||||
}
|
||||
// Check the range.
|
||||
if !isExtensionField(pb, extension.Field) {
|
||||
|
@ -250,85 +279,11 @@ func extensionProperties(ed *ExtensionDesc) *Properties {
|
|||
return prop
|
||||
}
|
||||
|
||||
// encode encodes any unmarshaled (unencoded) extensions in e.
|
||||
func encodeExtensions(e *XXX_InternalExtensions) error {
|
||||
m, mu := e.extensionsRead()
|
||||
if m == nil {
|
||||
return nil // fast path
|
||||
}
|
||||
mu.Lock()
|
||||
defer mu.Unlock()
|
||||
return encodeExtensionsMap(m)
|
||||
}
|
||||
|
||||
// encode encodes any unmarshaled (unencoded) extensions in e.
|
||||
func encodeExtensionsMap(m map[int32]Extension) error {
|
||||
for k, e := range m {
|
||||
if e.value == nil || e.desc == nil {
|
||||
// Extension is only in its encoded form.
|
||||
continue
|
||||
}
|
||||
|
||||
// We don't skip extensions that have an encoded form set,
|
||||
// because the extension value may have been mutated after
|
||||
// the last time this function was called.
|
||||
|
||||
et := reflect.TypeOf(e.desc.ExtensionType)
|
||||
props := extensionProperties(e.desc)
|
||||
|
||||
p := NewBuffer(nil)
|
||||
// If e.value has type T, the encoder expects a *struct{ X T }.
|
||||
// Pass a *T with a zero field and hope it all works out.
|
||||
x := reflect.New(et)
|
||||
x.Elem().Set(reflect.ValueOf(e.value))
|
||||
if err := props.enc(p, props, toStructPointer(x)); err != nil {
|
||||
return err
|
||||
}
|
||||
e.enc = p.buf
|
||||
m[k] = e
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func extensionsSize(e *XXX_InternalExtensions) (n int) {
|
||||
m, mu := e.extensionsRead()
|
||||
if m == nil {
|
||||
return 0
|
||||
}
|
||||
mu.Lock()
|
||||
defer mu.Unlock()
|
||||
return extensionsMapSize(m)
|
||||
}
|
||||
|
||||
func extensionsMapSize(m map[int32]Extension) (n int) {
|
||||
for _, e := range m {
|
||||
if e.value == nil || e.desc == nil {
|
||||
// Extension is only in its encoded form.
|
||||
n += len(e.enc)
|
||||
continue
|
||||
}
|
||||
|
||||
// We don't skip extensions that have an encoded form set,
|
||||
// because the extension value may have been mutated after
|
||||
// the last time this function was called.
|
||||
|
||||
et := reflect.TypeOf(e.desc.ExtensionType)
|
||||
props := extensionProperties(e.desc)
|
||||
|
||||
// If e.value has type T, the encoder expects a *struct{ X T }.
|
||||
// Pass a *T with a zero field and hope it all works out.
|
||||
x := reflect.New(et)
|
||||
x.Elem().Set(reflect.ValueOf(e.value))
|
||||
n += props.size(props, toStructPointer(x))
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// HasExtension returns whether the given extension is present in pb.
|
||||
func HasExtension(pb Message, extension *ExtensionDesc) bool {
|
||||
// TODO: Check types, field numbers, etc.?
|
||||
epb, ok := extendable(pb)
|
||||
if !ok {
|
||||
epb, err := extendable(pb)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
extmap, mu := epb.extensionsRead()
|
||||
|
@ -336,15 +291,15 @@ func HasExtension(pb Message, extension *ExtensionDesc) bool {
|
|||
return false
|
||||
}
|
||||
mu.Lock()
|
||||
_, ok = extmap[extension.Field]
|
||||
_, ok := extmap[extension.Field]
|
||||
mu.Unlock()
|
||||
return ok
|
||||
}
|
||||
|
||||
// ClearExtension removes the given extension from pb.
|
||||
func ClearExtension(pb Message, extension *ExtensionDesc) {
|
||||
epb, ok := extendable(pb)
|
||||
if !ok {
|
||||
epb, err := extendable(pb)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
// TODO: Check types, field numbers, etc.?
|
||||
|
@ -352,16 +307,26 @@ func ClearExtension(pb Message, extension *ExtensionDesc) {
|
|||
delete(extmap, extension.Field)
|
||||
}
|
||||
|
||||
// GetExtension parses and returns the given extension of pb.
|
||||
// If the extension is not present and has no default value it returns ErrMissingExtension.
|
||||
// GetExtension retrieves a proto2 extended field from pb.
|
||||
//
|
||||
// If the descriptor is type complete (i.e., ExtensionDesc.ExtensionType is non-nil),
|
||||
// then GetExtension parses the encoded field and returns a Go value of the specified type.
|
||||
// If the field is not present, then the default value is returned (if one is specified),
|
||||
// otherwise ErrMissingExtension is reported.
|
||||
//
|
||||
// If the descriptor is not type complete (i.e., ExtensionDesc.ExtensionType is nil),
|
||||
// then GetExtension returns the raw encoded bytes of the field extension.
|
||||
func GetExtension(pb Message, extension *ExtensionDesc) (interface{}, error) {
|
||||
epb, ok := extendable(pb)
|
||||
if !ok {
|
||||
return nil, errors.New("proto: not an extendable proto")
|
||||
epb, err := extendable(pb)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := checkExtensionTypes(epb, extension); err != nil {
|
||||
return nil, err
|
||||
if extension.ExtendedType != nil {
|
||||
// can only check type if this is a complete descriptor
|
||||
if err := checkExtensionTypes(epb, extension); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
emap, mu := epb.extensionsRead()
|
||||
|
@ -385,7 +350,12 @@ func GetExtension(pb Message, extension *ExtensionDesc) (interface{}, error) {
|
|||
// descriptors with the same field number.
|
||||
return nil, errors.New("proto: descriptor conflict")
|
||||
}
|
||||
return e.value, nil
|
||||
return extensionAsLegacyType(e.value), nil
|
||||
}
|
||||
|
||||
if extension.ExtensionType == nil {
|
||||
// incomplete descriptor
|
||||
return e.enc, nil
|
||||
}
|
||||
|
||||
v, err := decodeExtension(e.enc, extension)
|
||||
|
@ -395,16 +365,21 @@ func GetExtension(pb Message, extension *ExtensionDesc) (interface{}, error) {
|
|||
|
||||
// Remember the decoded version and drop the encoded version.
|
||||
// That way it is safe to mutate what we return.
|
||||
e.value = v
|
||||
e.value = extensionAsStorageType(v)
|
||||
e.desc = extension
|
||||
e.enc = nil
|
||||
emap[extension.Field] = e
|
||||
return e.value, nil
|
||||
return extensionAsLegacyType(e.value), nil
|
||||
}
|
||||
|
||||
// defaultExtensionValue returns the default value for extension.
|
||||
// If no default for an extension is defined ErrMissingExtension is returned.
|
||||
func defaultExtensionValue(extension *ExtensionDesc) (interface{}, error) {
|
||||
if extension.ExtensionType == nil {
|
||||
// incomplete descriptor, so no default
|
||||
return nil, ErrMissingExtension
|
||||
}
|
||||
|
||||
t := reflect.TypeOf(extension.ExtensionType)
|
||||
props := extensionProperties(extension)
|
||||
|
||||
|
@ -439,31 +414,28 @@ func defaultExtensionValue(extension *ExtensionDesc) (interface{}, error) {
|
|||
|
||||
// decodeExtension decodes an extension encoded in b.
|
||||
func decodeExtension(b []byte, extension *ExtensionDesc) (interface{}, error) {
|
||||
o := NewBuffer(b)
|
||||
|
||||
t := reflect.TypeOf(extension.ExtensionType)
|
||||
|
||||
props := extensionProperties(extension)
|
||||
unmarshal := typeUnmarshaler(t, extension.Tag)
|
||||
|
||||
// t is a pointer to a struct, pointer to basic type or a slice.
|
||||
// Allocate a "field" to store the pointer/slice itself; the
|
||||
// pointer/slice will be stored here. We pass
|
||||
// the address of this field to props.dec.
|
||||
// This passes a zero field and a *t and lets props.dec
|
||||
// interpret it as a *struct{ x t }.
|
||||
// Allocate space to store the pointer/slice.
|
||||
value := reflect.New(t).Elem()
|
||||
|
||||
var err error
|
||||
for {
|
||||
// Discard wire type and field number varint. It isn't needed.
|
||||
if _, err := o.DecodeVarint(); err != nil {
|
||||
x, n := decodeVarint(b)
|
||||
if n == 0 {
|
||||
return nil, io.ErrUnexpectedEOF
|
||||
}
|
||||
b = b[n:]
|
||||
wire := int(x) & 7
|
||||
|
||||
b, err = unmarshal(b, valToPointer(value.Addr()), wire)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := props.dec(o, props, toStructPointer(value.Addr())); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if o.index >= len(o.buf) {
|
||||
if len(b) == 0 {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
@ -473,9 +445,9 @@ func decodeExtension(b []byte, extension *ExtensionDesc) (interface{}, error) {
|
|||
// GetExtensions returns a slice of the extensions present in pb that are also listed in es.
|
||||
// The returned slice has the same length as es; missing extensions will appear as nil elements.
|
||||
func GetExtensions(pb Message, es []*ExtensionDesc) (extensions []interface{}, err error) {
|
||||
epb, ok := extendable(pb)
|
||||
if !ok {
|
||||
return nil, errors.New("proto: not an extendable proto")
|
||||
epb, err := extendable(pb)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
extensions = make([]interface{}, len(es))
|
||||
for i, e := range es {
|
||||
|
@ -494,9 +466,9 @@ func GetExtensions(pb Message, es []*ExtensionDesc) (extensions []interface{}, e
|
|||
// For non-registered extensions, ExtensionDescs returns an incomplete descriptor containing
|
||||
// just the Field field, which defines the extension's field number.
|
||||
func ExtensionDescs(pb Message) ([]*ExtensionDesc, error) {
|
||||
epb, ok := extendable(pb)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("proto: %T is not an extendable proto.Message", pb)
|
||||
epb, err := extendable(pb)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
registeredExtensions := RegisteredExtensions(pb)
|
||||
|
||||
|
@ -523,16 +495,16 @@ func ExtensionDescs(pb Message) ([]*ExtensionDesc, error) {
|
|||
|
||||
// SetExtension sets the specified extension of pb to the specified value.
|
||||
func SetExtension(pb Message, extension *ExtensionDesc, value interface{}) error {
|
||||
epb, ok := extendable(pb)
|
||||
if !ok {
|
||||
return errors.New("proto: not an extendable proto")
|
||||
epb, err := extendable(pb)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := checkExtensionTypes(epb, extension); err != nil {
|
||||
return err
|
||||
}
|
||||
typ := reflect.TypeOf(extension.ExtensionType)
|
||||
if typ != reflect.TypeOf(value) {
|
||||
return errors.New("proto: bad extension value type")
|
||||
return fmt.Errorf("proto: bad extension value type. got: %T, want: %T", value, extension.ExtensionType)
|
||||
}
|
||||
// nil extension values need to be caught early, because the
|
||||
// encoder can't distinguish an ErrNil due to a nil extension
|
||||
|
@ -544,14 +516,14 @@ func SetExtension(pb Message, extension *ExtensionDesc, value interface{}) error
|
|||
}
|
||||
|
||||
extmap := epb.extensionsWrite()
|
||||
extmap[extension.Field] = Extension{desc: extension, value: value}
|
||||
extmap[extension.Field] = Extension{desc: extension, value: extensionAsStorageType(value)}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ClearAllExtensions clears all extensions from pb.
|
||||
func ClearAllExtensions(pb Message) {
|
||||
epb, ok := extendable(pb)
|
||||
if !ok {
|
||||
epb, err := extendable(pb)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
m := epb.extensionsWrite()
|
||||
|
@ -585,3 +557,51 @@ func RegisterExtension(desc *ExtensionDesc) {
|
|||
func RegisteredExtensions(pb Message) map[int32]*ExtensionDesc {
|
||||
return extensionMaps[reflect.TypeOf(pb).Elem()]
|
||||
}
|
||||
|
||||
// extensionAsLegacyType converts an value in the storage type as the API type.
|
||||
// See Extension.value.
|
||||
func extensionAsLegacyType(v interface{}) interface{} {
|
||||
switch rv := reflect.ValueOf(v); rv.Kind() {
|
||||
case reflect.Bool, reflect.Int32, reflect.Int64, reflect.Uint32, reflect.Uint64, reflect.Float32, reflect.Float64, reflect.String:
|
||||
// Represent primitive types as a pointer to the value.
|
||||
rv2 := reflect.New(rv.Type())
|
||||
rv2.Elem().Set(rv)
|
||||
v = rv2.Interface()
|
||||
case reflect.Ptr:
|
||||
// Represent slice types as the value itself.
|
||||
switch rv.Type().Elem().Kind() {
|
||||
case reflect.Slice:
|
||||
if rv.IsNil() {
|
||||
v = reflect.Zero(rv.Type().Elem()).Interface()
|
||||
} else {
|
||||
v = rv.Elem().Interface()
|
||||
}
|
||||
}
|
||||
}
|
||||
return v
|
||||
}
|
||||
|
||||
// extensionAsStorageType converts an value in the API type as the storage type.
|
||||
// See Extension.value.
|
||||
func extensionAsStorageType(v interface{}) interface{} {
|
||||
switch rv := reflect.ValueOf(v); rv.Kind() {
|
||||
case reflect.Ptr:
|
||||
// Represent slice types as the value itself.
|
||||
switch rv.Type().Elem().Kind() {
|
||||
case reflect.Bool, reflect.Int32, reflect.Int64, reflect.Uint32, reflect.Uint64, reflect.Float32, reflect.Float64, reflect.String:
|
||||
if rv.IsNil() {
|
||||
v = reflect.Zero(rv.Type().Elem()).Interface()
|
||||
} else {
|
||||
v = rv.Elem().Interface()
|
||||
}
|
||||
}
|
||||
case reflect.Slice:
|
||||
// Represent slice types as a pointer to the value.
|
||||
if rv.Type().Elem().Kind() != reflect.Uint8 {
|
||||
rv2 := reflect.New(rv.Type())
|
||||
rv2.Elem().Set(rv)
|
||||
v = rv2.Interface()
|
||||
}
|
||||
}
|
||||
return v
|
||||
}
|
||||
|
|
|
@ -273,6 +273,67 @@ import (
|
|||
"sync"
|
||||
)
|
||||
|
||||
// RequiredNotSetError is an error type returned by either Marshal or Unmarshal.
|
||||
// Marshal reports this when a required field is not initialized.
|
||||
// Unmarshal reports this when a required field is missing from the wire data.
|
||||
type RequiredNotSetError struct{ field string }
|
||||
|
||||
func (e *RequiredNotSetError) Error() string {
|
||||
if e.field == "" {
|
||||
return fmt.Sprintf("proto: required field not set")
|
||||
}
|
||||
return fmt.Sprintf("proto: required field %q not set", e.field)
|
||||
}
|
||||
func (e *RequiredNotSetError) RequiredNotSet() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
type invalidUTF8Error struct{ field string }
|
||||
|
||||
func (e *invalidUTF8Error) Error() string {
|
||||
if e.field == "" {
|
||||
return "proto: invalid UTF-8 detected"
|
||||
}
|
||||
return fmt.Sprintf("proto: field %q contains invalid UTF-8", e.field)
|
||||
}
|
||||
func (e *invalidUTF8Error) InvalidUTF8() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// errInvalidUTF8 is a sentinel error to identify fields with invalid UTF-8.
|
||||
// This error should not be exposed to the external API as such errors should
|
||||
// be recreated with the field information.
|
||||
var errInvalidUTF8 = &invalidUTF8Error{}
|
||||
|
||||
// isNonFatal reports whether the error is either a RequiredNotSet error
|
||||
// or a InvalidUTF8 error.
|
||||
func isNonFatal(err error) bool {
|
||||
if re, ok := err.(interface{ RequiredNotSet() bool }); ok && re.RequiredNotSet() {
|
||||
return true
|
||||
}
|
||||
if re, ok := err.(interface{ InvalidUTF8() bool }); ok && re.InvalidUTF8() {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
type nonFatal struct{ E error }
|
||||
|
||||
// Merge merges err into nf and reports whether it was successful.
|
||||
// Otherwise it returns false for any fatal non-nil errors.
|
||||
func (nf *nonFatal) Merge(err error) (ok bool) {
|
||||
if err == nil {
|
||||
return true // not an error
|
||||
}
|
||||
if !isNonFatal(err) {
|
||||
return false // fatal error
|
||||
}
|
||||
if nf.E == nil {
|
||||
nf.E = err // store first instance of non-fatal error
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// Message is implemented by generated protocol buffer messages.
|
||||
type Message interface {
|
||||
Reset()
|
||||
|
@ -280,26 +341,6 @@ type Message interface {
|
|||
ProtoMessage()
|
||||
}
|
||||
|
||||
// Stats records allocation details about the protocol buffer encoders
|
||||
// and decoders. Useful for tuning the library itself.
|
||||
type Stats struct {
|
||||
Emalloc uint64 // mallocs in encode
|
||||
Dmalloc uint64 // mallocs in decode
|
||||
Encode uint64 // number of encodes
|
||||
Decode uint64 // number of decodes
|
||||
Chit uint64 // number of cache hits
|
||||
Cmiss uint64 // number of cache misses
|
||||
Size uint64 // number of sizes
|
||||
}
|
||||
|
||||
// Set to true to enable stats collection.
|
||||
const collectStats = false
|
||||
|
||||
var stats Stats
|
||||
|
||||
// GetStats returns a copy of the global Stats structure.
|
||||
func GetStats() Stats { return stats }
|
||||
|
||||
// A Buffer is a buffer manager for marshaling and unmarshaling
|
||||
// protocol buffers. It may be reused between invocations to
|
||||
// reduce memory usage. It is not necessary to use a Buffer;
|
||||
|
@ -309,16 +350,7 @@ type Buffer struct {
|
|||
buf []byte // encode/decode byte stream
|
||||
index int // read point
|
||||
|
||||
// pools of basic types to amortize allocation.
|
||||
bools []bool
|
||||
uint32s []uint32
|
||||
uint64s []uint64
|
||||
|
||||
// extra pools, only used with pointer_reflect.go
|
||||
int32s []int32
|
||||
int64s []int64
|
||||
float32s []float32
|
||||
float64s []float64
|
||||
deterministic bool
|
||||
}
|
||||
|
||||
// NewBuffer allocates a new Buffer and initializes its internal data to
|
||||
|
@ -343,6 +375,30 @@ func (p *Buffer) SetBuf(s []byte) {
|
|||
// Bytes returns the contents of the Buffer.
|
||||
func (p *Buffer) Bytes() []byte { return p.buf }
|
||||
|
||||
// SetDeterministic sets whether to use deterministic serialization.
|
||||
//
|
||||
// Deterministic serialization guarantees that for a given binary, equal
|
||||
// messages will always be serialized to the same bytes. This implies:
|
||||
//
|
||||
// - Repeated serialization of a message will return the same bytes.
|
||||
// - Different processes of the same binary (which may be executing on
|
||||
// different machines) will serialize equal messages to the same bytes.
|
||||
//
|
||||
// Note that the deterministic serialization is NOT canonical across
|
||||
// languages. It is not guaranteed to remain stable over time. It is unstable
|
||||
// across different builds with schema changes due to unknown fields.
|
||||
// Users who need canonical serialization (e.g., persistent storage in a
|
||||
// canonical form, fingerprinting, etc.) should define their own
|
||||
// canonicalization specification and implement their own serializer rather
|
||||
// than relying on this API.
|
||||
//
|
||||
// If deterministic serialization is requested, map entries will be sorted
|
||||
// by keys in lexicographical order. This is an implementation detail and
|
||||
// subject to change.
|
||||
func (p *Buffer) SetDeterministic(deterministic bool) {
|
||||
p.deterministic = deterministic
|
||||
}
|
||||
|
||||
/*
|
||||
* Helper routines for simplifying the creation of optional fields of basic type.
|
||||
*/
|
||||
|
@ -831,22 +887,12 @@ func fieldDefault(ft reflect.Type, prop *Properties) (sf *scalarField, nestedMes
|
|||
return sf, false, nil
|
||||
}
|
||||
|
||||
// mapKeys returns a sort.Interface to be used for sorting the map keys.
|
||||
// Map fields may have key types of non-float scalars, strings and enums.
|
||||
// The easiest way to sort them in some deterministic order is to use fmt.
|
||||
// If this turns out to be inefficient we can always consider other options,
|
||||
// such as doing a Schwartzian transform.
|
||||
|
||||
func mapKeys(vs []reflect.Value) sort.Interface {
|
||||
s := mapKeySorter{
|
||||
vs: vs,
|
||||
// default Less function: textual comparison
|
||||
less: func(a, b reflect.Value) bool {
|
||||
return fmt.Sprint(a.Interface()) < fmt.Sprint(b.Interface())
|
||||
},
|
||||
}
|
||||
s := mapKeySorter{vs: vs}
|
||||
|
||||
// Type specialization per https://developers.google.com/protocol-buffers/docs/proto#maps;
|
||||
// numeric keys are sorted numerically.
|
||||
// Type specialization per https://developers.google.com/protocol-buffers/docs/proto#maps.
|
||||
if len(vs) == 0 {
|
||||
return s
|
||||
}
|
||||
|
@ -855,6 +901,12 @@ func mapKeys(vs []reflect.Value) sort.Interface {
|
|||
s.less = func(a, b reflect.Value) bool { return a.Int() < b.Int() }
|
||||
case reflect.Uint32, reflect.Uint64:
|
||||
s.less = func(a, b reflect.Value) bool { return a.Uint() < b.Uint() }
|
||||
case reflect.Bool:
|
||||
s.less = func(a, b reflect.Value) bool { return !a.Bool() && b.Bool() } // false < true
|
||||
case reflect.String:
|
||||
s.less = func(a, b reflect.Value) bool { return a.String() < b.String() }
|
||||
default:
|
||||
panic(fmt.Sprintf("unsupported map key type: %v", vs[0].Kind()))
|
||||
}
|
||||
|
||||
return s
|
||||
|
@ -888,10 +940,26 @@ func isProto3Zero(v reflect.Value) bool {
|
|||
return false
|
||||
}
|
||||
|
||||
// ProtoPackageIsVersion2 is referenced from generated protocol buffer files
|
||||
// to assert that that code is compatible with this version of the proto package.
|
||||
const ProtoPackageIsVersion2 = true
|
||||
const (
|
||||
// ProtoPackageIsVersion3 is referenced from generated protocol buffer files
|
||||
// to assert that that code is compatible with this version of the proto package.
|
||||
ProtoPackageIsVersion3 = true
|
||||
|
||||
// ProtoPackageIsVersion1 is referenced from generated protocol buffer files
|
||||
// to assert that that code is compatible with this version of the proto package.
|
||||
const ProtoPackageIsVersion1 = true
|
||||
// ProtoPackageIsVersion2 is referenced from generated protocol buffer files
|
||||
// to assert that that code is compatible with this version of the proto package.
|
||||
ProtoPackageIsVersion2 = true
|
||||
|
||||
// ProtoPackageIsVersion1 is referenced from generated protocol buffer files
|
||||
// to assert that that code is compatible with this version of the proto package.
|
||||
ProtoPackageIsVersion1 = true
|
||||
)
|
||||
|
||||
// InternalMessageInfo is a type used internally by generated .pb.go files.
|
||||
// This type is not intended to be used by non-generated code.
|
||||
// This type is not subject to any compatibility guarantee.
|
||||
type InternalMessageInfo struct {
|
||||
marshal *marshalInfo
|
||||
unmarshal *unmarshalInfo
|
||||
merge *mergeInfo
|
||||
discard *discardInfo
|
||||
}
|
||||
|
|
|
@ -36,12 +36,7 @@ package proto
|
|||
*/
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"sort"
|
||||
)
|
||||
|
||||
// errNoMessageTypeID occurs when a protocol buffer does not have a message type ID.
|
||||
|
@ -94,10 +89,7 @@ func (ms *messageSet) find(pb Message) *_MessageSet_Item {
|
|||
}
|
||||
|
||||
func (ms *messageSet) Has(pb Message) bool {
|
||||
if ms.find(pb) != nil {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
return ms.find(pb) != nil
|
||||
}
|
||||
|
||||
func (ms *messageSet) Unmarshal(pb Message) error {
|
||||
|
@ -147,50 +139,9 @@ func skipVarint(buf []byte) []byte {
|
|||
return buf[i+1:]
|
||||
}
|
||||
|
||||
// MarshalMessageSet encodes the extension map represented by m in the message set wire format.
|
||||
// It is called by generated Marshal methods on protocol buffer messages with the message_set_wire_format option.
|
||||
func MarshalMessageSet(exts interface{}) ([]byte, error) {
|
||||
var m map[int32]Extension
|
||||
switch exts := exts.(type) {
|
||||
case *XXX_InternalExtensions:
|
||||
if err := encodeExtensions(exts); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
m, _ = exts.extensionsRead()
|
||||
case map[int32]Extension:
|
||||
if err := encodeExtensionsMap(exts); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
m = exts
|
||||
default:
|
||||
return nil, errors.New("proto: not an extension map")
|
||||
}
|
||||
|
||||
// Sort extension IDs to provide a deterministic encoding.
|
||||
// See also enc_map in encode.go.
|
||||
ids := make([]int, 0, len(m))
|
||||
for id := range m {
|
||||
ids = append(ids, int(id))
|
||||
}
|
||||
sort.Ints(ids)
|
||||
|
||||
ms := &messageSet{Item: make([]*_MessageSet_Item, 0, len(m))}
|
||||
for _, id := range ids {
|
||||
e := m[int32(id)]
|
||||
// Remove the wire type and field number varint, as well as the length varint.
|
||||
msg := skipVarint(skipVarint(e.enc))
|
||||
|
||||
ms.Item = append(ms.Item, &_MessageSet_Item{
|
||||
TypeId: Int32(int32(id)),
|
||||
Message: msg,
|
||||
})
|
||||
}
|
||||
return Marshal(ms)
|
||||
}
|
||||
|
||||
// UnmarshalMessageSet decodes the extension map encoded in buf in the message set wire format.
|
||||
// It is called by generated Unmarshal methods on protocol buffer messages with the message_set_wire_format option.
|
||||
func UnmarshalMessageSet(buf []byte, exts interface{}) error {
|
||||
// unmarshalMessageSet decodes the extension map encoded in buf in the message set wire format.
|
||||
// It is called by Unmarshal methods on protocol buffer messages with the message_set_wire_format option.
|
||||
func unmarshalMessageSet(buf []byte, exts interface{}) error {
|
||||
var m map[int32]Extension
|
||||
switch exts := exts.(type) {
|
||||
case *XXX_InternalExtensions:
|
||||
|
@ -228,84 +179,3 @@ func UnmarshalMessageSet(buf []byte, exts interface{}) error {
|
|||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// MarshalMessageSetJSON encodes the extension map represented by m in JSON format.
|
||||
// It is called by generated MarshalJSON methods on protocol buffer messages with the message_set_wire_format option.
|
||||
func MarshalMessageSetJSON(exts interface{}) ([]byte, error) {
|
||||
var m map[int32]Extension
|
||||
switch exts := exts.(type) {
|
||||
case *XXX_InternalExtensions:
|
||||
m, _ = exts.extensionsRead()
|
||||
case map[int32]Extension:
|
||||
m = exts
|
||||
default:
|
||||
return nil, errors.New("proto: not an extension map")
|
||||
}
|
||||
var b bytes.Buffer
|
||||
b.WriteByte('{')
|
||||
|
||||
// Process the map in key order for deterministic output.
|
||||
ids := make([]int32, 0, len(m))
|
||||
for id := range m {
|
||||
ids = append(ids, id)
|
||||
}
|
||||
sort.Sort(int32Slice(ids)) // int32Slice defined in text.go
|
||||
|
||||
for i, id := range ids {
|
||||
ext := m[id]
|
||||
if i > 0 {
|
||||
b.WriteByte(',')
|
||||
}
|
||||
|
||||
msd, ok := messageSetMap[id]
|
||||
if !ok {
|
||||
// Unknown type; we can't render it, so skip it.
|
||||
continue
|
||||
}
|
||||
fmt.Fprintf(&b, `"[%s]":`, msd.name)
|
||||
|
||||
x := ext.value
|
||||
if x == nil {
|
||||
x = reflect.New(msd.t.Elem()).Interface()
|
||||
if err := Unmarshal(ext.enc, x.(Message)); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
d, err := json.Marshal(x)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
b.Write(d)
|
||||
}
|
||||
b.WriteByte('}')
|
||||
return b.Bytes(), nil
|
||||
}
|
||||
|
||||
// UnmarshalMessageSetJSON decodes the extension map encoded in buf in JSON format.
|
||||
// It is called by generated UnmarshalJSON methods on protocol buffer messages with the message_set_wire_format option.
|
||||
func UnmarshalMessageSetJSON(buf []byte, exts interface{}) error {
|
||||
// Common-case fast path.
|
||||
if len(buf) == 0 || bytes.Equal(buf, []byte("{}")) {
|
||||
return nil
|
||||
}
|
||||
|
||||
// This is fairly tricky, and it's not clear that it is needed.
|
||||
return errors.New("TODO: UnmarshalMessageSetJSON not yet implemented")
|
||||
}
|
||||
|
||||
// A global registry of types that can be used in a MessageSet.
|
||||
|
||||
var messageSetMap = make(map[int32]messageSetDesc)
|
||||
|
||||
type messageSetDesc struct {
|
||||
t reflect.Type // pointer to struct
|
||||
name string
|
||||
}
|
||||
|
||||
// RegisterMessageSetType is called from the generated code.
|
||||
func RegisterMessageSetType(m Message, fieldNum int32, name string) {
|
||||
messageSetMap[fieldNum] = messageSetDesc{
|
||||
t: reflect.TypeOf(m),
|
||||
name: name,
|
||||
}
|
||||
}
|
||||
|
|
|
@ -29,7 +29,7 @@
|
|||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
// +build appengine js
|
||||
// +build purego appengine js
|
||||
|
||||
// This file contains an implementation of proto field accesses using package reflect.
|
||||
// It is slower than the code in pointer_unsafe.go but it avoids package unsafe and can
|
||||
|
@ -38,32 +38,13 @@
|
|||
package proto
|
||||
|
||||
import (
|
||||
"math"
|
||||
"reflect"
|
||||
"sync"
|
||||
)
|
||||
|
||||
// A structPointer is a pointer to a struct.
|
||||
type structPointer struct {
|
||||
v reflect.Value
|
||||
}
|
||||
const unsafeAllowed = false
|
||||
|
||||
// toStructPointer returns a structPointer equivalent to the given reflect value.
|
||||
// The reflect value must itself be a pointer to a struct.
|
||||
func toStructPointer(v reflect.Value) structPointer {
|
||||
return structPointer{v}
|
||||
}
|
||||
|
||||
// IsNil reports whether p is nil.
|
||||
func structPointer_IsNil(p structPointer) bool {
|
||||
return p.v.IsNil()
|
||||
}
|
||||
|
||||
// Interface returns the struct pointer as an interface value.
|
||||
func structPointer_Interface(p structPointer, _ reflect.Type) interface{} {
|
||||
return p.v.Interface()
|
||||
}
|
||||
|
||||
// A field identifies a field in a struct, accessible from a structPointer.
|
||||
// A field identifies a field in a struct, accessible from a pointer.
|
||||
// In this implementation, a field is identified by the sequence of field indices
|
||||
// passed to reflect's FieldByIndex.
|
||||
type field []int
|
||||
|
@ -76,409 +57,304 @@ func toField(f *reflect.StructField) field {
|
|||
// invalidField is an invalid field identifier.
|
||||
var invalidField = field(nil)
|
||||
|
||||
// zeroField is a noop when calling pointer.offset.
|
||||
var zeroField = field([]int{})
|
||||
|
||||
// IsValid reports whether the field identifier is valid.
|
||||
func (f field) IsValid() bool { return f != nil }
|
||||
|
||||
// field returns the given field in the struct as a reflect value.
|
||||
func structPointer_field(p structPointer, f field) reflect.Value {
|
||||
// Special case: an extension map entry with a value of type T
|
||||
// passes a *T to the struct-handling code with a zero field,
|
||||
// expecting that it will be treated as equivalent to *struct{ X T },
|
||||
// which has the same memory layout. We have to handle that case
|
||||
// specially, because reflect will panic if we call FieldByIndex on a
|
||||
// non-struct.
|
||||
if f == nil {
|
||||
return p.v.Elem()
|
||||
// The pointer type is for the table-driven decoder.
|
||||
// The implementation here uses a reflect.Value of pointer type to
|
||||
// create a generic pointer. In pointer_unsafe.go we use unsafe
|
||||
// instead of reflect to implement the same (but faster) interface.
|
||||
type pointer struct {
|
||||
v reflect.Value
|
||||
}
|
||||
|
||||
// toPointer converts an interface of pointer type to a pointer
|
||||
// that points to the same target.
|
||||
func toPointer(i *Message) pointer {
|
||||
return pointer{v: reflect.ValueOf(*i)}
|
||||
}
|
||||
|
||||
// toAddrPointer converts an interface to a pointer that points to
|
||||
// the interface data.
|
||||
func toAddrPointer(i *interface{}, isptr, deref bool) pointer {
|
||||
v := reflect.ValueOf(*i)
|
||||
u := reflect.New(v.Type())
|
||||
u.Elem().Set(v)
|
||||
if deref {
|
||||
u = u.Elem()
|
||||
}
|
||||
|
||||
return p.v.Elem().FieldByIndex(f)
|
||||
return pointer{v: u}
|
||||
}
|
||||
|
||||
// ifield returns the given field in the struct as an interface value.
|
||||
func structPointer_ifield(p structPointer, f field) interface{} {
|
||||
return structPointer_field(p, f).Addr().Interface()
|
||||
// valToPointer converts v to a pointer. v must be of pointer type.
|
||||
func valToPointer(v reflect.Value) pointer {
|
||||
return pointer{v: v}
|
||||
}
|
||||
|
||||
// Bytes returns the address of a []byte field in the struct.
|
||||
func structPointer_Bytes(p structPointer, f field) *[]byte {
|
||||
return structPointer_ifield(p, f).(*[]byte)
|
||||
// offset converts from a pointer to a structure to a pointer to
|
||||
// one of its fields.
|
||||
func (p pointer) offset(f field) pointer {
|
||||
return pointer{v: p.v.Elem().FieldByIndex(f).Addr()}
|
||||
}
|
||||
|
||||
// BytesSlice returns the address of a [][]byte field in the struct.
|
||||
func structPointer_BytesSlice(p structPointer, f field) *[][]byte {
|
||||
return structPointer_ifield(p, f).(*[][]byte)
|
||||
}
|
||||
|
||||
// Bool returns the address of a *bool field in the struct.
|
||||
func structPointer_Bool(p structPointer, f field) **bool {
|
||||
return structPointer_ifield(p, f).(**bool)
|
||||
}
|
||||
|
||||
// BoolVal returns the address of a bool field in the struct.
|
||||
func structPointer_BoolVal(p structPointer, f field) *bool {
|
||||
return structPointer_ifield(p, f).(*bool)
|
||||
}
|
||||
|
||||
// BoolSlice returns the address of a []bool field in the struct.
|
||||
func structPointer_BoolSlice(p structPointer, f field) *[]bool {
|
||||
return structPointer_ifield(p, f).(*[]bool)
|
||||
}
|
||||
|
||||
// String returns the address of a *string field in the struct.
|
||||
func structPointer_String(p structPointer, f field) **string {
|
||||
return structPointer_ifield(p, f).(**string)
|
||||
}
|
||||
|
||||
// StringVal returns the address of a string field in the struct.
|
||||
func structPointer_StringVal(p structPointer, f field) *string {
|
||||
return structPointer_ifield(p, f).(*string)
|
||||
}
|
||||
|
||||
// StringSlice returns the address of a []string field in the struct.
|
||||
func structPointer_StringSlice(p structPointer, f field) *[]string {
|
||||
return structPointer_ifield(p, f).(*[]string)
|
||||
}
|
||||
|
||||
// Extensions returns the address of an extension map field in the struct.
|
||||
func structPointer_Extensions(p structPointer, f field) *XXX_InternalExtensions {
|
||||
return structPointer_ifield(p, f).(*XXX_InternalExtensions)
|
||||
}
|
||||
|
||||
// ExtMap returns the address of an extension map field in the struct.
|
||||
func structPointer_ExtMap(p structPointer, f field) *map[int32]Extension {
|
||||
return structPointer_ifield(p, f).(*map[int32]Extension)
|
||||
}
|
||||
|
||||
// NewAt returns the reflect.Value for a pointer to a field in the struct.
|
||||
func structPointer_NewAt(p structPointer, f field, typ reflect.Type) reflect.Value {
|
||||
return structPointer_field(p, f).Addr()
|
||||
}
|
||||
|
||||
// SetStructPointer writes a *struct field in the struct.
|
||||
func structPointer_SetStructPointer(p structPointer, f field, q structPointer) {
|
||||
structPointer_field(p, f).Set(q.v)
|
||||
}
|
||||
|
||||
// GetStructPointer reads a *struct field in the struct.
|
||||
func structPointer_GetStructPointer(p structPointer, f field) structPointer {
|
||||
return structPointer{structPointer_field(p, f)}
|
||||
}
|
||||
|
||||
// StructPointerSlice the address of a []*struct field in the struct.
|
||||
func structPointer_StructPointerSlice(p structPointer, f field) structPointerSlice {
|
||||
return structPointerSlice{structPointer_field(p, f)}
|
||||
}
|
||||
|
||||
// A structPointerSlice represents the address of a slice of pointers to structs
|
||||
// (themselves messages or groups). That is, v.Type() is *[]*struct{...}.
|
||||
type structPointerSlice struct {
|
||||
v reflect.Value
|
||||
}
|
||||
|
||||
func (p structPointerSlice) Len() int { return p.v.Len() }
|
||||
func (p structPointerSlice) Index(i int) structPointer { return structPointer{p.v.Index(i)} }
|
||||
func (p structPointerSlice) Append(q structPointer) {
|
||||
p.v.Set(reflect.Append(p.v, q.v))
|
||||
}
|
||||
|
||||
var (
|
||||
int32Type = reflect.TypeOf(int32(0))
|
||||
uint32Type = reflect.TypeOf(uint32(0))
|
||||
float32Type = reflect.TypeOf(float32(0))
|
||||
int64Type = reflect.TypeOf(int64(0))
|
||||
uint64Type = reflect.TypeOf(uint64(0))
|
||||
float64Type = reflect.TypeOf(float64(0))
|
||||
)
|
||||
|
||||
// A word32 represents a field of type *int32, *uint32, *float32, or *enum.
|
||||
// That is, v.Type() is *int32, *uint32, *float32, or *enum and v is assignable.
|
||||
type word32 struct {
|
||||
v reflect.Value
|
||||
}
|
||||
|
||||
// IsNil reports whether p is nil.
|
||||
func word32_IsNil(p word32) bool {
|
||||
func (p pointer) isNil() bool {
|
||||
return p.v.IsNil()
|
||||
}
|
||||
|
||||
// Set sets p to point at a newly allocated word with bits set to x.
|
||||
func word32_Set(p word32, o *Buffer, x uint32) {
|
||||
t := p.v.Type().Elem()
|
||||
switch t {
|
||||
case int32Type:
|
||||
if len(o.int32s) == 0 {
|
||||
o.int32s = make([]int32, uint32PoolSize)
|
||||
}
|
||||
o.int32s[0] = int32(x)
|
||||
p.v.Set(reflect.ValueOf(&o.int32s[0]))
|
||||
o.int32s = o.int32s[1:]
|
||||
return
|
||||
case uint32Type:
|
||||
if len(o.uint32s) == 0 {
|
||||
o.uint32s = make([]uint32, uint32PoolSize)
|
||||
}
|
||||
o.uint32s[0] = x
|
||||
p.v.Set(reflect.ValueOf(&o.uint32s[0]))
|
||||
o.uint32s = o.uint32s[1:]
|
||||
return
|
||||
case float32Type:
|
||||
if len(o.float32s) == 0 {
|
||||
o.float32s = make([]float32, uint32PoolSize)
|
||||
}
|
||||
o.float32s[0] = math.Float32frombits(x)
|
||||
p.v.Set(reflect.ValueOf(&o.float32s[0]))
|
||||
o.float32s = o.float32s[1:]
|
||||
return
|
||||
}
|
||||
|
||||
// must be enum
|
||||
p.v.Set(reflect.New(t))
|
||||
p.v.Elem().SetInt(int64(int32(x)))
|
||||
}
|
||||
|
||||
// Get gets the bits pointed at by p, as a uint32.
|
||||
func word32_Get(p word32) uint32 {
|
||||
elem := p.v.Elem()
|
||||
switch elem.Kind() {
|
||||
case reflect.Int32:
|
||||
return uint32(elem.Int())
|
||||
case reflect.Uint32:
|
||||
return uint32(elem.Uint())
|
||||
case reflect.Float32:
|
||||
return math.Float32bits(float32(elem.Float()))
|
||||
}
|
||||
panic("unreachable")
|
||||
}
|
||||
|
||||
// Word32 returns a reference to a *int32, *uint32, *float32, or *enum field in the struct.
|
||||
func structPointer_Word32(p structPointer, f field) word32 {
|
||||
return word32{structPointer_field(p, f)}
|
||||
}
|
||||
|
||||
// A word32Val represents a field of type int32, uint32, float32, or enum.
|
||||
// That is, v.Type() is int32, uint32, float32, or enum and v is assignable.
|
||||
type word32Val struct {
|
||||
v reflect.Value
|
||||
}
|
||||
|
||||
// Set sets *p to x.
|
||||
func word32Val_Set(p word32Val, x uint32) {
|
||||
switch p.v.Type() {
|
||||
case int32Type:
|
||||
p.v.SetInt(int64(x))
|
||||
return
|
||||
case uint32Type:
|
||||
p.v.SetUint(uint64(x))
|
||||
return
|
||||
case float32Type:
|
||||
p.v.SetFloat(float64(math.Float32frombits(x)))
|
||||
return
|
||||
}
|
||||
|
||||
// must be enum
|
||||
p.v.SetInt(int64(int32(x)))
|
||||
}
|
||||
|
||||
// Get gets the bits pointed at by p, as a uint32.
|
||||
func word32Val_Get(p word32Val) uint32 {
|
||||
elem := p.v
|
||||
switch elem.Kind() {
|
||||
case reflect.Int32:
|
||||
return uint32(elem.Int())
|
||||
case reflect.Uint32:
|
||||
return uint32(elem.Uint())
|
||||
case reflect.Float32:
|
||||
return math.Float32bits(float32(elem.Float()))
|
||||
}
|
||||
panic("unreachable")
|
||||
}
|
||||
|
||||
// Word32Val returns a reference to a int32, uint32, float32, or enum field in the struct.
|
||||
func structPointer_Word32Val(p structPointer, f field) word32Val {
|
||||
return word32Val{structPointer_field(p, f)}
|
||||
}
|
||||
|
||||
// A word32Slice is a slice of 32-bit values.
|
||||
// That is, v.Type() is []int32, []uint32, []float32, or []enum.
|
||||
type word32Slice struct {
|
||||
v reflect.Value
|
||||
}
|
||||
|
||||
func (p word32Slice) Append(x uint32) {
|
||||
n, m := p.v.Len(), p.v.Cap()
|
||||
// grow updates the slice s in place to make it one element longer.
|
||||
// s must be addressable.
|
||||
// Returns the (addressable) new element.
|
||||
func grow(s reflect.Value) reflect.Value {
|
||||
n, m := s.Len(), s.Cap()
|
||||
if n < m {
|
||||
p.v.SetLen(n + 1)
|
||||
s.SetLen(n + 1)
|
||||
} else {
|
||||
t := p.v.Type().Elem()
|
||||
p.v.Set(reflect.Append(p.v, reflect.Zero(t)))
|
||||
s.Set(reflect.Append(s, reflect.Zero(s.Type().Elem())))
|
||||
}
|
||||
elem := p.v.Index(n)
|
||||
switch elem.Kind() {
|
||||
case reflect.Int32:
|
||||
elem.SetInt(int64(int32(x)))
|
||||
case reflect.Uint32:
|
||||
elem.SetUint(uint64(x))
|
||||
case reflect.Float32:
|
||||
elem.SetFloat(float64(math.Float32frombits(x)))
|
||||
return s.Index(n)
|
||||
}
|
||||
|
||||
func (p pointer) toInt64() *int64 {
|
||||
return p.v.Interface().(*int64)
|
||||
}
|
||||
func (p pointer) toInt64Ptr() **int64 {
|
||||
return p.v.Interface().(**int64)
|
||||
}
|
||||
func (p pointer) toInt64Slice() *[]int64 {
|
||||
return p.v.Interface().(*[]int64)
|
||||
}
|
||||
|
||||
var int32ptr = reflect.TypeOf((*int32)(nil))
|
||||
|
||||
func (p pointer) toInt32() *int32 {
|
||||
return p.v.Convert(int32ptr).Interface().(*int32)
|
||||
}
|
||||
|
||||
// The toInt32Ptr/Slice methods don't work because of enums.
|
||||
// Instead, we must use set/get methods for the int32ptr/slice case.
|
||||
/*
|
||||
func (p pointer) toInt32Ptr() **int32 {
|
||||
return p.v.Interface().(**int32)
|
||||
}
|
||||
func (p pointer) toInt32Slice() *[]int32 {
|
||||
return p.v.Interface().(*[]int32)
|
||||
}
|
||||
*/
|
||||
func (p pointer) getInt32Ptr() *int32 {
|
||||
if p.v.Type().Elem().Elem() == reflect.TypeOf(int32(0)) {
|
||||
// raw int32 type
|
||||
return p.v.Elem().Interface().(*int32)
|
||||
}
|
||||
// an enum
|
||||
return p.v.Elem().Convert(int32PtrType).Interface().(*int32)
|
||||
}
|
||||
func (p pointer) setInt32Ptr(v int32) {
|
||||
// Allocate value in a *int32. Possibly convert that to a *enum.
|
||||
// Then assign it to a **int32 or **enum.
|
||||
// Note: we can convert *int32 to *enum, but we can't convert
|
||||
// **int32 to **enum!
|
||||
p.v.Elem().Set(reflect.ValueOf(&v).Convert(p.v.Type().Elem()))
|
||||
}
|
||||
|
||||
func (p word32Slice) Len() int {
|
||||
return p.v.Len()
|
||||
}
|
||||
|
||||
func (p word32Slice) Index(i int) uint32 {
|
||||
elem := p.v.Index(i)
|
||||
switch elem.Kind() {
|
||||
case reflect.Int32:
|
||||
return uint32(elem.Int())
|
||||
case reflect.Uint32:
|
||||
return uint32(elem.Uint())
|
||||
case reflect.Float32:
|
||||
return math.Float32bits(float32(elem.Float()))
|
||||
// getInt32Slice copies []int32 from p as a new slice.
|
||||
// This behavior differs from the implementation in pointer_unsafe.go.
|
||||
func (p pointer) getInt32Slice() []int32 {
|
||||
if p.v.Type().Elem().Elem() == reflect.TypeOf(int32(0)) {
|
||||
// raw int32 type
|
||||
return p.v.Elem().Interface().([]int32)
|
||||
}
|
||||
panic("unreachable")
|
||||
// an enum
|
||||
// Allocate a []int32, then assign []enum's values into it.
|
||||
// Note: we can't convert []enum to []int32.
|
||||
slice := p.v.Elem()
|
||||
s := make([]int32, slice.Len())
|
||||
for i := 0; i < slice.Len(); i++ {
|
||||
s[i] = int32(slice.Index(i).Int())
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
// Word32Slice returns a reference to a []int32, []uint32, []float32, or []enum field in the struct.
|
||||
func structPointer_Word32Slice(p structPointer, f field) word32Slice {
|
||||
return word32Slice{structPointer_field(p, f)}
|
||||
}
|
||||
|
||||
// word64 is like word32 but for 64-bit values.
|
||||
type word64 struct {
|
||||
v reflect.Value
|
||||
}
|
||||
|
||||
func word64_Set(p word64, o *Buffer, x uint64) {
|
||||
t := p.v.Type().Elem()
|
||||
switch t {
|
||||
case int64Type:
|
||||
if len(o.int64s) == 0 {
|
||||
o.int64s = make([]int64, uint64PoolSize)
|
||||
}
|
||||
o.int64s[0] = int64(x)
|
||||
p.v.Set(reflect.ValueOf(&o.int64s[0]))
|
||||
o.int64s = o.int64s[1:]
|
||||
return
|
||||
case uint64Type:
|
||||
if len(o.uint64s) == 0 {
|
||||
o.uint64s = make([]uint64, uint64PoolSize)
|
||||
}
|
||||
o.uint64s[0] = x
|
||||
p.v.Set(reflect.ValueOf(&o.uint64s[0]))
|
||||
o.uint64s = o.uint64s[1:]
|
||||
return
|
||||
case float64Type:
|
||||
if len(o.float64s) == 0 {
|
||||
o.float64s = make([]float64, uint64PoolSize)
|
||||
}
|
||||
o.float64s[0] = math.Float64frombits(x)
|
||||
p.v.Set(reflect.ValueOf(&o.float64s[0]))
|
||||
o.float64s = o.float64s[1:]
|
||||
// setInt32Slice copies []int32 into p as a new slice.
|
||||
// This behavior differs from the implementation in pointer_unsafe.go.
|
||||
func (p pointer) setInt32Slice(v []int32) {
|
||||
if p.v.Type().Elem().Elem() == reflect.TypeOf(int32(0)) {
|
||||
// raw int32 type
|
||||
p.v.Elem().Set(reflect.ValueOf(v))
|
||||
return
|
||||
}
|
||||
panic("unreachable")
|
||||
}
|
||||
|
||||
func word64_IsNil(p word64) bool {
|
||||
return p.v.IsNil()
|
||||
}
|
||||
|
||||
func word64_Get(p word64) uint64 {
|
||||
elem := p.v.Elem()
|
||||
switch elem.Kind() {
|
||||
case reflect.Int64:
|
||||
return uint64(elem.Int())
|
||||
case reflect.Uint64:
|
||||
return elem.Uint()
|
||||
case reflect.Float64:
|
||||
return math.Float64bits(elem.Float())
|
||||
// an enum
|
||||
// Allocate a []enum, then assign []int32's values into it.
|
||||
// Note: we can't convert []enum to []int32.
|
||||
slice := reflect.MakeSlice(p.v.Type().Elem(), len(v), cap(v))
|
||||
for i, x := range v {
|
||||
slice.Index(i).SetInt(int64(x))
|
||||
}
|
||||
panic("unreachable")
|
||||
p.v.Elem().Set(slice)
|
||||
}
|
||||
func (p pointer) appendInt32Slice(v int32) {
|
||||
grow(p.v.Elem()).SetInt(int64(v))
|
||||
}
|
||||
|
||||
func structPointer_Word64(p structPointer, f field) word64 {
|
||||
return word64{structPointer_field(p, f)}
|
||||
func (p pointer) toUint64() *uint64 {
|
||||
return p.v.Interface().(*uint64)
|
||||
}
|
||||
func (p pointer) toUint64Ptr() **uint64 {
|
||||
return p.v.Interface().(**uint64)
|
||||
}
|
||||
func (p pointer) toUint64Slice() *[]uint64 {
|
||||
return p.v.Interface().(*[]uint64)
|
||||
}
|
||||
func (p pointer) toUint32() *uint32 {
|
||||
return p.v.Interface().(*uint32)
|
||||
}
|
||||
func (p pointer) toUint32Ptr() **uint32 {
|
||||
return p.v.Interface().(**uint32)
|
||||
}
|
||||
func (p pointer) toUint32Slice() *[]uint32 {
|
||||
return p.v.Interface().(*[]uint32)
|
||||
}
|
||||
func (p pointer) toBool() *bool {
|
||||
return p.v.Interface().(*bool)
|
||||
}
|
||||
func (p pointer) toBoolPtr() **bool {
|
||||
return p.v.Interface().(**bool)
|
||||
}
|
||||
func (p pointer) toBoolSlice() *[]bool {
|
||||
return p.v.Interface().(*[]bool)
|
||||
}
|
||||
func (p pointer) toFloat64() *float64 {
|
||||
return p.v.Interface().(*float64)
|
||||
}
|
||||
func (p pointer) toFloat64Ptr() **float64 {
|
||||
return p.v.Interface().(**float64)
|
||||
}
|
||||
func (p pointer) toFloat64Slice() *[]float64 {
|
||||
return p.v.Interface().(*[]float64)
|
||||
}
|
||||
func (p pointer) toFloat32() *float32 {
|
||||
return p.v.Interface().(*float32)
|
||||
}
|
||||
func (p pointer) toFloat32Ptr() **float32 {
|
||||
return p.v.Interface().(**float32)
|
||||
}
|
||||
func (p pointer) toFloat32Slice() *[]float32 {
|
||||
return p.v.Interface().(*[]float32)
|
||||
}
|
||||
func (p pointer) toString() *string {
|
||||
return p.v.Interface().(*string)
|
||||
}
|
||||
func (p pointer) toStringPtr() **string {
|
||||
return p.v.Interface().(**string)
|
||||
}
|
||||
func (p pointer) toStringSlice() *[]string {
|
||||
return p.v.Interface().(*[]string)
|
||||
}
|
||||
func (p pointer) toBytes() *[]byte {
|
||||
return p.v.Interface().(*[]byte)
|
||||
}
|
||||
func (p pointer) toBytesSlice() *[][]byte {
|
||||
return p.v.Interface().(*[][]byte)
|
||||
}
|
||||
func (p pointer) toExtensions() *XXX_InternalExtensions {
|
||||
return p.v.Interface().(*XXX_InternalExtensions)
|
||||
}
|
||||
func (p pointer) toOldExtensions() *map[int32]Extension {
|
||||
return p.v.Interface().(*map[int32]Extension)
|
||||
}
|
||||
func (p pointer) getPointer() pointer {
|
||||
return pointer{v: p.v.Elem()}
|
||||
}
|
||||
func (p pointer) setPointer(q pointer) {
|
||||
p.v.Elem().Set(q.v)
|
||||
}
|
||||
func (p pointer) appendPointer(q pointer) {
|
||||
grow(p.v.Elem()).Set(q.v)
|
||||
}
|
||||
|
||||
// word64Val is like word32Val but for 64-bit values.
|
||||
type word64Val struct {
|
||||
v reflect.Value
|
||||
// getPointerSlice copies []*T from p as a new []pointer.
|
||||
// This behavior differs from the implementation in pointer_unsafe.go.
|
||||
func (p pointer) getPointerSlice() []pointer {
|
||||
if p.v.IsNil() {
|
||||
return nil
|
||||
}
|
||||
n := p.v.Elem().Len()
|
||||
s := make([]pointer, n)
|
||||
for i := 0; i < n; i++ {
|
||||
s[i] = pointer{v: p.v.Elem().Index(i)}
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
func word64Val_Set(p word64Val, o *Buffer, x uint64) {
|
||||
switch p.v.Type() {
|
||||
case int64Type:
|
||||
p.v.SetInt(int64(x))
|
||||
return
|
||||
case uint64Type:
|
||||
p.v.SetUint(x)
|
||||
return
|
||||
case float64Type:
|
||||
p.v.SetFloat(math.Float64frombits(x))
|
||||
// setPointerSlice copies []pointer into p as a new []*T.
|
||||
// This behavior differs from the implementation in pointer_unsafe.go.
|
||||
func (p pointer) setPointerSlice(v []pointer) {
|
||||
if v == nil {
|
||||
p.v.Elem().Set(reflect.New(p.v.Elem().Type()).Elem())
|
||||
return
|
||||
}
|
||||
panic("unreachable")
|
||||
}
|
||||
|
||||
func word64Val_Get(p word64Val) uint64 {
|
||||
elem := p.v
|
||||
switch elem.Kind() {
|
||||
case reflect.Int64:
|
||||
return uint64(elem.Int())
|
||||
case reflect.Uint64:
|
||||
return elem.Uint()
|
||||
case reflect.Float64:
|
||||
return math.Float64bits(elem.Float())
|
||||
s := reflect.MakeSlice(p.v.Elem().Type(), 0, len(v))
|
||||
for _, p := range v {
|
||||
s = reflect.Append(s, p.v)
|
||||
}
|
||||
panic("unreachable")
|
||||
p.v.Elem().Set(s)
|
||||
}
|
||||
|
||||
func structPointer_Word64Val(p structPointer, f field) word64Val {
|
||||
return word64Val{structPointer_field(p, f)}
|
||||
}
|
||||
|
||||
type word64Slice struct {
|
||||
v reflect.Value
|
||||
}
|
||||
|
||||
func (p word64Slice) Append(x uint64) {
|
||||
n, m := p.v.Len(), p.v.Cap()
|
||||
if n < m {
|
||||
p.v.SetLen(n + 1)
|
||||
} else {
|
||||
t := p.v.Type().Elem()
|
||||
p.v.Set(reflect.Append(p.v, reflect.Zero(t)))
|
||||
}
|
||||
elem := p.v.Index(n)
|
||||
switch elem.Kind() {
|
||||
case reflect.Int64:
|
||||
elem.SetInt(int64(int64(x)))
|
||||
case reflect.Uint64:
|
||||
elem.SetUint(uint64(x))
|
||||
case reflect.Float64:
|
||||
elem.SetFloat(float64(math.Float64frombits(x)))
|
||||
// getInterfacePointer returns a pointer that points to the
|
||||
// interface data of the interface pointed by p.
|
||||
func (p pointer) getInterfacePointer() pointer {
|
||||
if p.v.Elem().IsNil() {
|
||||
return pointer{v: p.v.Elem()}
|
||||
}
|
||||
return pointer{v: p.v.Elem().Elem().Elem().Field(0).Addr()} // *interface -> interface -> *struct -> struct
|
||||
}
|
||||
|
||||
func (p word64Slice) Len() int {
|
||||
return p.v.Len()
|
||||
func (p pointer) asPointerTo(t reflect.Type) reflect.Value {
|
||||
// TODO: check that p.v.Type().Elem() == t?
|
||||
return p.v
|
||||
}
|
||||
|
||||
func (p word64Slice) Index(i int) uint64 {
|
||||
elem := p.v.Index(i)
|
||||
switch elem.Kind() {
|
||||
case reflect.Int64:
|
||||
return uint64(elem.Int())
|
||||
case reflect.Uint64:
|
||||
return uint64(elem.Uint())
|
||||
case reflect.Float64:
|
||||
return math.Float64bits(float64(elem.Float()))
|
||||
}
|
||||
panic("unreachable")
|
||||
func atomicLoadUnmarshalInfo(p **unmarshalInfo) *unmarshalInfo {
|
||||
atomicLock.Lock()
|
||||
defer atomicLock.Unlock()
|
||||
return *p
|
||||
}
|
||||
func atomicStoreUnmarshalInfo(p **unmarshalInfo, v *unmarshalInfo) {
|
||||
atomicLock.Lock()
|
||||
defer atomicLock.Unlock()
|
||||
*p = v
|
||||
}
|
||||
func atomicLoadMarshalInfo(p **marshalInfo) *marshalInfo {
|
||||
atomicLock.Lock()
|
||||
defer atomicLock.Unlock()
|
||||
return *p
|
||||
}
|
||||
func atomicStoreMarshalInfo(p **marshalInfo, v *marshalInfo) {
|
||||
atomicLock.Lock()
|
||||
defer atomicLock.Unlock()
|
||||
*p = v
|
||||
}
|
||||
func atomicLoadMergeInfo(p **mergeInfo) *mergeInfo {
|
||||
atomicLock.Lock()
|
||||
defer atomicLock.Unlock()
|
||||
return *p
|
||||
}
|
||||
func atomicStoreMergeInfo(p **mergeInfo, v *mergeInfo) {
|
||||
atomicLock.Lock()
|
||||
defer atomicLock.Unlock()
|
||||
*p = v
|
||||
}
|
||||
func atomicLoadDiscardInfo(p **discardInfo) *discardInfo {
|
||||
atomicLock.Lock()
|
||||
defer atomicLock.Unlock()
|
||||
return *p
|
||||
}
|
||||
func atomicStoreDiscardInfo(p **discardInfo, v *discardInfo) {
|
||||
atomicLock.Lock()
|
||||
defer atomicLock.Unlock()
|
||||
*p = v
|
||||
}
|
||||
|
||||
func structPointer_Word64Slice(p structPointer, f field) word64Slice {
|
||||
return word64Slice{structPointer_field(p, f)}
|
||||
}
|
||||
var atomicLock sync.Mutex
|
||||
|
|
|
@ -29,7 +29,7 @@
|
|||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
// +build !appengine,!js
|
||||
// +build !purego,!appengine,!js
|
||||
|
||||
// This file contains the implementation of the proto field accesses using package unsafe.
|
||||
|
||||
|
@ -37,38 +37,13 @@ package proto
|
|||
|
||||
import (
|
||||
"reflect"
|
||||
"sync/atomic"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
// NOTE: These type_Foo functions would more idiomatically be methods,
|
||||
// but Go does not allow methods on pointer types, and we must preserve
|
||||
// some pointer type for the garbage collector. We use these
|
||||
// funcs with clunky names as our poor approximation to methods.
|
||||
//
|
||||
// An alternative would be
|
||||
// type structPointer struct { p unsafe.Pointer }
|
||||
// but that does not registerize as well.
|
||||
const unsafeAllowed = true
|
||||
|
||||
// A structPointer is a pointer to a struct.
|
||||
type structPointer unsafe.Pointer
|
||||
|
||||
// toStructPointer returns a structPointer equivalent to the given reflect value.
|
||||
func toStructPointer(v reflect.Value) structPointer {
|
||||
return structPointer(unsafe.Pointer(v.Pointer()))
|
||||
}
|
||||
|
||||
// IsNil reports whether p is nil.
|
||||
func structPointer_IsNil(p structPointer) bool {
|
||||
return p == nil
|
||||
}
|
||||
|
||||
// Interface returns the struct pointer, assumed to have element type t,
|
||||
// as an interface value.
|
||||
func structPointer_Interface(p structPointer, t reflect.Type) interface{} {
|
||||
return reflect.NewAt(t, unsafe.Pointer(p)).Interface()
|
||||
}
|
||||
|
||||
// A field identifies a field in a struct, accessible from a structPointer.
|
||||
// A field identifies a field in a struct, accessible from a pointer.
|
||||
// In this implementation, a field is identified by its byte offset from the start of the struct.
|
||||
type field uintptr
|
||||
|
||||
|
@ -80,191 +55,259 @@ func toField(f *reflect.StructField) field {
|
|||
// invalidField is an invalid field identifier.
|
||||
const invalidField = ^field(0)
|
||||
|
||||
// zeroField is a noop when calling pointer.offset.
|
||||
const zeroField = field(0)
|
||||
|
||||
// IsValid reports whether the field identifier is valid.
|
||||
func (f field) IsValid() bool {
|
||||
return f != ^field(0)
|
||||
return f != invalidField
|
||||
}
|
||||
|
||||
// Bytes returns the address of a []byte field in the struct.
|
||||
func structPointer_Bytes(p structPointer, f field) *[]byte {
|
||||
return (*[]byte)(unsafe.Pointer(uintptr(p) + uintptr(f)))
|
||||
// The pointer type below is for the new table-driven encoder/decoder.
|
||||
// The implementation here uses unsafe.Pointer to create a generic pointer.
|
||||
// In pointer_reflect.go we use reflect instead of unsafe to implement
|
||||
// the same (but slower) interface.
|
||||
type pointer struct {
|
||||
p unsafe.Pointer
|
||||
}
|
||||
|
||||
// BytesSlice returns the address of a [][]byte field in the struct.
|
||||
func structPointer_BytesSlice(p structPointer, f field) *[][]byte {
|
||||
return (*[][]byte)(unsafe.Pointer(uintptr(p) + uintptr(f)))
|
||||
// size of pointer
|
||||
var ptrSize = unsafe.Sizeof(uintptr(0))
|
||||
|
||||
// toPointer converts an interface of pointer type to a pointer
|
||||
// that points to the same target.
|
||||
func toPointer(i *Message) pointer {
|
||||
// Super-tricky - read pointer out of data word of interface value.
|
||||
// Saves ~25ns over the equivalent:
|
||||
// return valToPointer(reflect.ValueOf(*i))
|
||||
return pointer{p: (*[2]unsafe.Pointer)(unsafe.Pointer(i))[1]}
|
||||
}
|
||||
|
||||
// Bool returns the address of a *bool field in the struct.
|
||||
func structPointer_Bool(p structPointer, f field) **bool {
|
||||
return (**bool)(unsafe.Pointer(uintptr(p) + uintptr(f)))
|
||||
}
|
||||
|
||||
// BoolVal returns the address of a bool field in the struct.
|
||||
func structPointer_BoolVal(p structPointer, f field) *bool {
|
||||
return (*bool)(unsafe.Pointer(uintptr(p) + uintptr(f)))
|
||||
}
|
||||
|
||||
// BoolSlice returns the address of a []bool field in the struct.
|
||||
func structPointer_BoolSlice(p structPointer, f field) *[]bool {
|
||||
return (*[]bool)(unsafe.Pointer(uintptr(p) + uintptr(f)))
|
||||
}
|
||||
|
||||
// String returns the address of a *string field in the struct.
|
||||
func structPointer_String(p structPointer, f field) **string {
|
||||
return (**string)(unsafe.Pointer(uintptr(p) + uintptr(f)))
|
||||
}
|
||||
|
||||
// StringVal returns the address of a string field in the struct.
|
||||
func structPointer_StringVal(p structPointer, f field) *string {
|
||||
return (*string)(unsafe.Pointer(uintptr(p) + uintptr(f)))
|
||||
}
|
||||
|
||||
// StringSlice returns the address of a []string field in the struct.
|
||||
func structPointer_StringSlice(p structPointer, f field) *[]string {
|
||||
return (*[]string)(unsafe.Pointer(uintptr(p) + uintptr(f)))
|
||||
}
|
||||
|
||||
// ExtMap returns the address of an extension map field in the struct.
|
||||
func structPointer_Extensions(p structPointer, f field) *XXX_InternalExtensions {
|
||||
return (*XXX_InternalExtensions)(unsafe.Pointer(uintptr(p) + uintptr(f)))
|
||||
}
|
||||
|
||||
func structPointer_ExtMap(p structPointer, f field) *map[int32]Extension {
|
||||
return (*map[int32]Extension)(unsafe.Pointer(uintptr(p) + uintptr(f)))
|
||||
}
|
||||
|
||||
// NewAt returns the reflect.Value for a pointer to a field in the struct.
|
||||
func structPointer_NewAt(p structPointer, f field, typ reflect.Type) reflect.Value {
|
||||
return reflect.NewAt(typ, unsafe.Pointer(uintptr(p)+uintptr(f)))
|
||||
}
|
||||
|
||||
// SetStructPointer writes a *struct field in the struct.
|
||||
func structPointer_SetStructPointer(p structPointer, f field, q structPointer) {
|
||||
*(*structPointer)(unsafe.Pointer(uintptr(p) + uintptr(f))) = q
|
||||
}
|
||||
|
||||
// GetStructPointer reads a *struct field in the struct.
|
||||
func structPointer_GetStructPointer(p structPointer, f field) structPointer {
|
||||
return *(*structPointer)(unsafe.Pointer(uintptr(p) + uintptr(f)))
|
||||
}
|
||||
|
||||
// StructPointerSlice the address of a []*struct field in the struct.
|
||||
func structPointer_StructPointerSlice(p structPointer, f field) *structPointerSlice {
|
||||
return (*structPointerSlice)(unsafe.Pointer(uintptr(p) + uintptr(f)))
|
||||
}
|
||||
|
||||
// A structPointerSlice represents a slice of pointers to structs (themselves submessages or groups).
|
||||
type structPointerSlice []structPointer
|
||||
|
||||
func (v *structPointerSlice) Len() int { return len(*v) }
|
||||
func (v *structPointerSlice) Index(i int) structPointer { return (*v)[i] }
|
||||
func (v *structPointerSlice) Append(p structPointer) { *v = append(*v, p) }
|
||||
|
||||
// A word32 is the address of a "pointer to 32-bit value" field.
|
||||
type word32 **uint32
|
||||
|
||||
// IsNil reports whether *v is nil.
|
||||
func word32_IsNil(p word32) bool {
|
||||
return *p == nil
|
||||
}
|
||||
|
||||
// Set sets *v to point at a newly allocated word set to x.
|
||||
func word32_Set(p word32, o *Buffer, x uint32) {
|
||||
if len(o.uint32s) == 0 {
|
||||
o.uint32s = make([]uint32, uint32PoolSize)
|
||||
// toAddrPointer converts an interface to a pointer that points to
|
||||
// the interface data.
|
||||
func toAddrPointer(i *interface{}, isptr, deref bool) (p pointer) {
|
||||
// Super-tricky - read or get the address of data word of interface value.
|
||||
if isptr {
|
||||
// The interface is of pointer type, thus it is a direct interface.
|
||||
// The data word is the pointer data itself. We take its address.
|
||||
p = pointer{p: unsafe.Pointer(uintptr(unsafe.Pointer(i)) + ptrSize)}
|
||||
} else {
|
||||
// The interface is not of pointer type. The data word is the pointer
|
||||
// to the data.
|
||||
p = pointer{p: (*[2]unsafe.Pointer)(unsafe.Pointer(i))[1]}
|
||||
}
|
||||
o.uint32s[0] = x
|
||||
*p = &o.uint32s[0]
|
||||
o.uint32s = o.uint32s[1:]
|
||||
}
|
||||
|
||||
// Get gets the value pointed at by *v.
|
||||
func word32_Get(p word32) uint32 {
|
||||
return **p
|
||||
}
|
||||
|
||||
// Word32 returns the address of a *int32, *uint32, *float32, or *enum field in the struct.
|
||||
func structPointer_Word32(p structPointer, f field) word32 {
|
||||
return word32((**uint32)(unsafe.Pointer(uintptr(p) + uintptr(f))))
|
||||
}
|
||||
|
||||
// A word32Val is the address of a 32-bit value field.
|
||||
type word32Val *uint32
|
||||
|
||||
// Set sets *p to x.
|
||||
func word32Val_Set(p word32Val, x uint32) {
|
||||
*p = x
|
||||
}
|
||||
|
||||
// Get gets the value pointed at by p.
|
||||
func word32Val_Get(p word32Val) uint32 {
|
||||
return *p
|
||||
}
|
||||
|
||||
// Word32Val returns the address of a *int32, *uint32, *float32, or *enum field in the struct.
|
||||
func structPointer_Word32Val(p structPointer, f field) word32Val {
|
||||
return word32Val((*uint32)(unsafe.Pointer(uintptr(p) + uintptr(f))))
|
||||
}
|
||||
|
||||
// A word32Slice is a slice of 32-bit values.
|
||||
type word32Slice []uint32
|
||||
|
||||
func (v *word32Slice) Append(x uint32) { *v = append(*v, x) }
|
||||
func (v *word32Slice) Len() int { return len(*v) }
|
||||
func (v *word32Slice) Index(i int) uint32 { return (*v)[i] }
|
||||
|
||||
// Word32Slice returns the address of a []int32, []uint32, []float32, or []enum field in the struct.
|
||||
func structPointer_Word32Slice(p structPointer, f field) *word32Slice {
|
||||
return (*word32Slice)(unsafe.Pointer(uintptr(p) + uintptr(f)))
|
||||
}
|
||||
|
||||
// word64 is like word32 but for 64-bit values.
|
||||
type word64 **uint64
|
||||
|
||||
func word64_Set(p word64, o *Buffer, x uint64) {
|
||||
if len(o.uint64s) == 0 {
|
||||
o.uint64s = make([]uint64, uint64PoolSize)
|
||||
if deref {
|
||||
p.p = *(*unsafe.Pointer)(p.p)
|
||||
}
|
||||
o.uint64s[0] = x
|
||||
*p = &o.uint64s[0]
|
||||
o.uint64s = o.uint64s[1:]
|
||||
return p
|
||||
}
|
||||
|
||||
func word64_IsNil(p word64) bool {
|
||||
return *p == nil
|
||||
// valToPointer converts v to a pointer. v must be of pointer type.
|
||||
func valToPointer(v reflect.Value) pointer {
|
||||
return pointer{p: unsafe.Pointer(v.Pointer())}
|
||||
}
|
||||
|
||||
func word64_Get(p word64) uint64 {
|
||||
return **p
|
||||
// offset converts from a pointer to a structure to a pointer to
|
||||
// one of its fields.
|
||||
func (p pointer) offset(f field) pointer {
|
||||
// For safety, we should panic if !f.IsValid, however calling panic causes
|
||||
// this to no longer be inlineable, which is a serious performance cost.
|
||||
/*
|
||||
if !f.IsValid() {
|
||||
panic("invalid field")
|
||||
}
|
||||
*/
|
||||
return pointer{p: unsafe.Pointer(uintptr(p.p) + uintptr(f))}
|
||||
}
|
||||
|
||||
func structPointer_Word64(p structPointer, f field) word64 {
|
||||
return word64((**uint64)(unsafe.Pointer(uintptr(p) + uintptr(f))))
|
||||
func (p pointer) isNil() bool {
|
||||
return p.p == nil
|
||||
}
|
||||
|
||||
// word64Val is like word32Val but for 64-bit values.
|
||||
type word64Val *uint64
|
||||
|
||||
func word64Val_Set(p word64Val, o *Buffer, x uint64) {
|
||||
*p = x
|
||||
func (p pointer) toInt64() *int64 {
|
||||
return (*int64)(p.p)
|
||||
}
|
||||
func (p pointer) toInt64Ptr() **int64 {
|
||||
return (**int64)(p.p)
|
||||
}
|
||||
func (p pointer) toInt64Slice() *[]int64 {
|
||||
return (*[]int64)(p.p)
|
||||
}
|
||||
func (p pointer) toInt32() *int32 {
|
||||
return (*int32)(p.p)
|
||||
}
|
||||
|
||||
func word64Val_Get(p word64Val) uint64 {
|
||||
return *p
|
||||
// See pointer_reflect.go for why toInt32Ptr/Slice doesn't exist.
|
||||
/*
|
||||
func (p pointer) toInt32Ptr() **int32 {
|
||||
return (**int32)(p.p)
|
||||
}
|
||||
func (p pointer) toInt32Slice() *[]int32 {
|
||||
return (*[]int32)(p.p)
|
||||
}
|
||||
*/
|
||||
func (p pointer) getInt32Ptr() *int32 {
|
||||
return *(**int32)(p.p)
|
||||
}
|
||||
func (p pointer) setInt32Ptr(v int32) {
|
||||
*(**int32)(p.p) = &v
|
||||
}
|
||||
|
||||
func structPointer_Word64Val(p structPointer, f field) word64Val {
|
||||
return word64Val((*uint64)(unsafe.Pointer(uintptr(p) + uintptr(f))))
|
||||
// getInt32Slice loads a []int32 from p.
|
||||
// The value returned is aliased with the original slice.
|
||||
// This behavior differs from the implementation in pointer_reflect.go.
|
||||
func (p pointer) getInt32Slice() []int32 {
|
||||
return *(*[]int32)(p.p)
|
||||
}
|
||||
|
||||
// word64Slice is like word32Slice but for 64-bit values.
|
||||
type word64Slice []uint64
|
||||
|
||||
func (v *word64Slice) Append(x uint64) { *v = append(*v, x) }
|
||||
func (v *word64Slice) Len() int { return len(*v) }
|
||||
func (v *word64Slice) Index(i int) uint64 { return (*v)[i] }
|
||||
|
||||
func structPointer_Word64Slice(p structPointer, f field) *word64Slice {
|
||||
return (*word64Slice)(unsafe.Pointer(uintptr(p) + uintptr(f)))
|
||||
// setInt32Slice stores a []int32 to p.
|
||||
// The value set is aliased with the input slice.
|
||||
// This behavior differs from the implementation in pointer_reflect.go.
|
||||
func (p pointer) setInt32Slice(v []int32) {
|
||||
*(*[]int32)(p.p) = v
|
||||
}
|
||||
|
||||
// TODO: Can we get rid of appendInt32Slice and use setInt32Slice instead?
|
||||
func (p pointer) appendInt32Slice(v int32) {
|
||||
s := (*[]int32)(p.p)
|
||||
*s = append(*s, v)
|
||||
}
|
||||
|
||||
func (p pointer) toUint64() *uint64 {
|
||||
return (*uint64)(p.p)
|
||||
}
|
||||
func (p pointer) toUint64Ptr() **uint64 {
|
||||
return (**uint64)(p.p)
|
||||
}
|
||||
func (p pointer) toUint64Slice() *[]uint64 {
|
||||
return (*[]uint64)(p.p)
|
||||
}
|
||||
func (p pointer) toUint32() *uint32 {
|
||||
return (*uint32)(p.p)
|
||||
}
|
||||
func (p pointer) toUint32Ptr() **uint32 {
|
||||
return (**uint32)(p.p)
|
||||
}
|
||||
func (p pointer) toUint32Slice() *[]uint32 {
|
||||
return (*[]uint32)(p.p)
|
||||
}
|
||||
func (p pointer) toBool() *bool {
|
||||
return (*bool)(p.p)
|
||||
}
|
||||
func (p pointer) toBoolPtr() **bool {
|
||||
return (**bool)(p.p)
|
||||
}
|
||||
func (p pointer) toBoolSlice() *[]bool {
|
||||
return (*[]bool)(p.p)
|
||||
}
|
||||
func (p pointer) toFloat64() *float64 {
|
||||
return (*float64)(p.p)
|
||||
}
|
||||
func (p pointer) toFloat64Ptr() **float64 {
|
||||
return (**float64)(p.p)
|
||||
}
|
||||
func (p pointer) toFloat64Slice() *[]float64 {
|
||||
return (*[]float64)(p.p)
|
||||
}
|
||||
func (p pointer) toFloat32() *float32 {
|
||||
return (*float32)(p.p)
|
||||
}
|
||||
func (p pointer) toFloat32Ptr() **float32 {
|
||||
return (**float32)(p.p)
|
||||
}
|
||||
func (p pointer) toFloat32Slice() *[]float32 {
|
||||
return (*[]float32)(p.p)
|
||||
}
|
||||
func (p pointer) toString() *string {
|
||||
return (*string)(p.p)
|
||||
}
|
||||
func (p pointer) toStringPtr() **string {
|
||||
return (**string)(p.p)
|
||||
}
|
||||
func (p pointer) toStringSlice() *[]string {
|
||||
return (*[]string)(p.p)
|
||||
}
|
||||
func (p pointer) toBytes() *[]byte {
|
||||
return (*[]byte)(p.p)
|
||||
}
|
||||
func (p pointer) toBytesSlice() *[][]byte {
|
||||
return (*[][]byte)(p.p)
|
||||
}
|
||||
func (p pointer) toExtensions() *XXX_InternalExtensions {
|
||||
return (*XXX_InternalExtensions)(p.p)
|
||||
}
|
||||
func (p pointer) toOldExtensions() *map[int32]Extension {
|
||||
return (*map[int32]Extension)(p.p)
|
||||
}
|
||||
|
||||
// getPointerSlice loads []*T from p as a []pointer.
|
||||
// The value returned is aliased with the original slice.
|
||||
// This behavior differs from the implementation in pointer_reflect.go.
|
||||
func (p pointer) getPointerSlice() []pointer {
|
||||
// Super-tricky - p should point to a []*T where T is a
|
||||
// message type. We load it as []pointer.
|
||||
return *(*[]pointer)(p.p)
|
||||
}
|
||||
|
||||
// setPointerSlice stores []pointer into p as a []*T.
|
||||
// The value set is aliased with the input slice.
|
||||
// This behavior differs from the implementation in pointer_reflect.go.
|
||||
func (p pointer) setPointerSlice(v []pointer) {
|
||||
// Super-tricky - p should point to a []*T where T is a
|
||||
// message type. We store it as []pointer.
|
||||
*(*[]pointer)(p.p) = v
|
||||
}
|
||||
|
||||
// getPointer loads the pointer at p and returns it.
|
||||
func (p pointer) getPointer() pointer {
|
||||
return pointer{p: *(*unsafe.Pointer)(p.p)}
|
||||
}
|
||||
|
||||
// setPointer stores the pointer q at p.
|
||||
func (p pointer) setPointer(q pointer) {
|
||||
*(*unsafe.Pointer)(p.p) = q.p
|
||||
}
|
||||
|
||||
// append q to the slice pointed to by p.
|
||||
func (p pointer) appendPointer(q pointer) {
|
||||
s := (*[]unsafe.Pointer)(p.p)
|
||||
*s = append(*s, q.p)
|
||||
}
|
||||
|
||||
// getInterfacePointer returns a pointer that points to the
|
||||
// interface data of the interface pointed by p.
|
||||
func (p pointer) getInterfacePointer() pointer {
|
||||
// Super-tricky - read pointer out of data word of interface value.
|
||||
return pointer{p: (*(*[2]unsafe.Pointer)(p.p))[1]}
|
||||
}
|
||||
|
||||
// asPointerTo returns a reflect.Value that is a pointer to an
|
||||
// object of type t stored at p.
|
||||
func (p pointer) asPointerTo(t reflect.Type) reflect.Value {
|
||||
return reflect.NewAt(t, p.p)
|
||||
}
|
||||
|
||||
func atomicLoadUnmarshalInfo(p **unmarshalInfo) *unmarshalInfo {
|
||||
return (*unmarshalInfo)(atomic.LoadPointer((*unsafe.Pointer)(unsafe.Pointer(p))))
|
||||
}
|
||||
func atomicStoreUnmarshalInfo(p **unmarshalInfo, v *unmarshalInfo) {
|
||||
atomic.StorePointer((*unsafe.Pointer)(unsafe.Pointer(p)), unsafe.Pointer(v))
|
||||
}
|
||||
func atomicLoadMarshalInfo(p **marshalInfo) *marshalInfo {
|
||||
return (*marshalInfo)(atomic.LoadPointer((*unsafe.Pointer)(unsafe.Pointer(p))))
|
||||
}
|
||||
func atomicStoreMarshalInfo(p **marshalInfo, v *marshalInfo) {
|
||||
atomic.StorePointer((*unsafe.Pointer)(unsafe.Pointer(p)), unsafe.Pointer(v))
|
||||
}
|
||||
func atomicLoadMergeInfo(p **mergeInfo) *mergeInfo {
|
||||
return (*mergeInfo)(atomic.LoadPointer((*unsafe.Pointer)(unsafe.Pointer(p))))
|
||||
}
|
||||
func atomicStoreMergeInfo(p **mergeInfo, v *mergeInfo) {
|
||||
atomic.StorePointer((*unsafe.Pointer)(unsafe.Pointer(p)), unsafe.Pointer(v))
|
||||
}
|
||||
func atomicLoadDiscardInfo(p **discardInfo) *discardInfo {
|
||||
return (*discardInfo)(atomic.LoadPointer((*unsafe.Pointer)(unsafe.Pointer(p))))
|
||||
}
|
||||
func atomicStoreDiscardInfo(p **discardInfo, v *discardInfo) {
|
||||
atomic.StorePointer((*unsafe.Pointer)(unsafe.Pointer(p)), unsafe.Pointer(v))
|
||||
}
|
||||
|
|
|
@ -38,7 +38,6 @@ package proto
|
|||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"reflect"
|
||||
"sort"
|
||||
"strconv"
|
||||
|
@ -58,42 +57,6 @@ const (
|
|||
WireFixed32 = 5
|
||||
)
|
||||
|
||||
const startSize = 10 // initial slice/string sizes
|
||||
|
||||
// Encoders are defined in encode.go
|
||||
// An encoder outputs the full representation of a field, including its
|
||||
// tag and encoder type.
|
||||
type encoder func(p *Buffer, prop *Properties, base structPointer) error
|
||||
|
||||
// A valueEncoder encodes a single integer in a particular encoding.
|
||||
type valueEncoder func(o *Buffer, x uint64) error
|
||||
|
||||
// Sizers are defined in encode.go
|
||||
// A sizer returns the encoded size of a field, including its tag and encoder
|
||||
// type.
|
||||
type sizer func(prop *Properties, base structPointer) int
|
||||
|
||||
// A valueSizer returns the encoded size of a single integer in a particular
|
||||
// encoding.
|
||||
type valueSizer func(x uint64) int
|
||||
|
||||
// Decoders are defined in decode.go
|
||||
// A decoder creates a value from its wire representation.
|
||||
// Unrecognized subelements are saved in unrec.
|
||||
type decoder func(p *Buffer, prop *Properties, base structPointer) error
|
||||
|
||||
// A valueDecoder decodes a single integer in a particular encoding.
|
||||
type valueDecoder func(o *Buffer) (x uint64, err error)
|
||||
|
||||
// A oneofMarshaler does the marshaling for all oneof fields in a message.
|
||||
type oneofMarshaler func(Message, *Buffer) error
|
||||
|
||||
// A oneofUnmarshaler does the unmarshaling for a oneof field in a message.
|
||||
type oneofUnmarshaler func(Message, int, int, *Buffer) (bool, error)
|
||||
|
||||
// A oneofSizer does the sizing for all oneof fields in a message.
|
||||
type oneofSizer func(Message) int
|
||||
|
||||
// tagMap is an optimization over map[int]int for typical protocol buffer
|
||||
// use-cases. Encoded protocol buffers are often in tag order with small tag
|
||||
// numbers.
|
||||
|
@ -140,13 +103,6 @@ type StructProperties struct {
|
|||
decoderTags tagMap // map from proto tag to struct field number
|
||||
decoderOrigNames map[string]int // map from original name to struct field number
|
||||
order []int // list of struct field numbers in tag order
|
||||
unrecField field // field id of the XXX_unrecognized []byte field
|
||||
extendable bool // is this an extendable proto
|
||||
|
||||
oneofMarshaler oneofMarshaler
|
||||
oneofUnmarshaler oneofUnmarshaler
|
||||
oneofSizer oneofSizer
|
||||
stype reflect.Type
|
||||
|
||||
// OneofTypes contains information about the oneof fields in this message.
|
||||
// It is keyed by the original name of a field.
|
||||
|
@ -182,41 +138,24 @@ type Properties struct {
|
|||
Repeated bool
|
||||
Packed bool // relevant for repeated primitives only
|
||||
Enum string // set for enum types only
|
||||
proto3 bool // whether this is known to be a proto3 field; set for []byte only
|
||||
proto3 bool // whether this is known to be a proto3 field
|
||||
oneof bool // whether this is a oneof field
|
||||
|
||||
Default string // default value
|
||||
HasDefault bool // whether an explicit default was provided
|
||||
def_uint64 uint64
|
||||
|
||||
enc encoder
|
||||
valEnc valueEncoder // set for bool and numeric types only
|
||||
field field
|
||||
tagcode []byte // encoding of EncodeVarint((Tag<<3)|WireType)
|
||||
tagbuf [8]byte
|
||||
stype reflect.Type // set for struct types only
|
||||
sprop *StructProperties // set for struct types only
|
||||
isMarshaler bool
|
||||
isUnmarshaler bool
|
||||
stype reflect.Type // set for struct types only
|
||||
sprop *StructProperties // set for struct types only
|
||||
|
||||
mtype reflect.Type // set for map types only
|
||||
mkeyprop *Properties // set for map types only
|
||||
mvalprop *Properties // set for map types only
|
||||
|
||||
size sizer
|
||||
valSize valueSizer // set for bool and numeric types only
|
||||
|
||||
dec decoder
|
||||
valDec valueDecoder // set for bool and numeric types only
|
||||
|
||||
// If this is a packable field, this will be the decoder for the packed version of the field.
|
||||
packedDec decoder
|
||||
mtype reflect.Type // set for map types only
|
||||
MapKeyProp *Properties // set for map types only
|
||||
MapValProp *Properties // set for map types only
|
||||
}
|
||||
|
||||
// String formats the properties in the protobuf struct field tag style.
|
||||
func (p *Properties) String() string {
|
||||
s := p.Wire
|
||||
s = ","
|
||||
s += ","
|
||||
s += strconv.Itoa(p.Tag)
|
||||
if p.Required {
|
||||
s += ",req"
|
||||
|
@ -254,7 +193,7 @@ func (p *Properties) Parse(s string) {
|
|||
// "bytes,49,opt,name=foo,def=hello!"
|
||||
fields := strings.Split(s, ",") // breaks def=, but handled below.
|
||||
if len(fields) < 2 {
|
||||
fmt.Fprintf(os.Stderr, "proto: tag has too few fields: %q\n", s)
|
||||
log.Printf("proto: tag has too few fields: %q", s)
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -262,34 +201,19 @@ func (p *Properties) Parse(s string) {
|
|||
switch p.Wire {
|
||||
case "varint":
|
||||
p.WireType = WireVarint
|
||||
p.valEnc = (*Buffer).EncodeVarint
|
||||
p.valDec = (*Buffer).DecodeVarint
|
||||
p.valSize = sizeVarint
|
||||
case "fixed32":
|
||||
p.WireType = WireFixed32
|
||||
p.valEnc = (*Buffer).EncodeFixed32
|
||||
p.valDec = (*Buffer).DecodeFixed32
|
||||
p.valSize = sizeFixed32
|
||||
case "fixed64":
|
||||
p.WireType = WireFixed64
|
||||
p.valEnc = (*Buffer).EncodeFixed64
|
||||
p.valDec = (*Buffer).DecodeFixed64
|
||||
p.valSize = sizeFixed64
|
||||
case "zigzag32":
|
||||
p.WireType = WireVarint
|
||||
p.valEnc = (*Buffer).EncodeZigzag32
|
||||
p.valDec = (*Buffer).DecodeZigzag32
|
||||
p.valSize = sizeZigzag32
|
||||
case "zigzag64":
|
||||
p.WireType = WireVarint
|
||||
p.valEnc = (*Buffer).EncodeZigzag64
|
||||
p.valDec = (*Buffer).DecodeZigzag64
|
||||
p.valSize = sizeZigzag64
|
||||
case "bytes", "group":
|
||||
p.WireType = WireBytes
|
||||
// no numeric converter for non-numeric types
|
||||
default:
|
||||
fmt.Fprintf(os.Stderr, "proto: tag has unknown wire type: %q\n", s)
|
||||
log.Printf("proto: tag has unknown wire type: %q", s)
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -299,6 +223,7 @@ func (p *Properties) Parse(s string) {
|
|||
return
|
||||
}
|
||||
|
||||
outer:
|
||||
for i := 2; i < len(fields); i++ {
|
||||
f := fields[i]
|
||||
switch {
|
||||
|
@ -326,256 +251,41 @@ func (p *Properties) Parse(s string) {
|
|||
if i+1 < len(fields) {
|
||||
// Commas aren't escaped, and def is always last.
|
||||
p.Default += "," + strings.Join(fields[i+1:], ",")
|
||||
break
|
||||
break outer
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func logNoSliceEnc(t1, t2 reflect.Type) {
|
||||
fmt.Fprintf(os.Stderr, "proto: no slice oenc for %T = []%T\n", t1, t2)
|
||||
}
|
||||
|
||||
var protoMessageType = reflect.TypeOf((*Message)(nil)).Elem()
|
||||
|
||||
// Initialize the fields for encoding and decoding.
|
||||
func (p *Properties) setEncAndDec(typ reflect.Type, f *reflect.StructField, lockGetProp bool) {
|
||||
p.enc = nil
|
||||
p.dec = nil
|
||||
p.size = nil
|
||||
|
||||
// setFieldProps initializes the field properties for submessages and maps.
|
||||
func (p *Properties) setFieldProps(typ reflect.Type, f *reflect.StructField, lockGetProp bool) {
|
||||
switch t1 := typ; t1.Kind() {
|
||||
default:
|
||||
fmt.Fprintf(os.Stderr, "proto: no coders for %v\n", t1)
|
||||
|
||||
// proto3 scalar types
|
||||
|
||||
case reflect.Bool:
|
||||
p.enc = (*Buffer).enc_proto3_bool
|
||||
p.dec = (*Buffer).dec_proto3_bool
|
||||
p.size = size_proto3_bool
|
||||
case reflect.Int32:
|
||||
p.enc = (*Buffer).enc_proto3_int32
|
||||
p.dec = (*Buffer).dec_proto3_int32
|
||||
p.size = size_proto3_int32
|
||||
case reflect.Uint32:
|
||||
p.enc = (*Buffer).enc_proto3_uint32
|
||||
p.dec = (*Buffer).dec_proto3_int32 // can reuse
|
||||
p.size = size_proto3_uint32
|
||||
case reflect.Int64, reflect.Uint64:
|
||||
p.enc = (*Buffer).enc_proto3_int64
|
||||
p.dec = (*Buffer).dec_proto3_int64
|
||||
p.size = size_proto3_int64
|
||||
case reflect.Float32:
|
||||
p.enc = (*Buffer).enc_proto3_uint32 // can just treat them as bits
|
||||
p.dec = (*Buffer).dec_proto3_int32
|
||||
p.size = size_proto3_uint32
|
||||
case reflect.Float64:
|
||||
p.enc = (*Buffer).enc_proto3_int64 // can just treat them as bits
|
||||
p.dec = (*Buffer).dec_proto3_int64
|
||||
p.size = size_proto3_int64
|
||||
case reflect.String:
|
||||
p.enc = (*Buffer).enc_proto3_string
|
||||
p.dec = (*Buffer).dec_proto3_string
|
||||
p.size = size_proto3_string
|
||||
|
||||
case reflect.Ptr:
|
||||
switch t2 := t1.Elem(); t2.Kind() {
|
||||
default:
|
||||
fmt.Fprintf(os.Stderr, "proto: no encoder function for %v -> %v\n", t1, t2)
|
||||
break
|
||||
case reflect.Bool:
|
||||
p.enc = (*Buffer).enc_bool
|
||||
p.dec = (*Buffer).dec_bool
|
||||
p.size = size_bool
|
||||
case reflect.Int32:
|
||||
p.enc = (*Buffer).enc_int32
|
||||
p.dec = (*Buffer).dec_int32
|
||||
p.size = size_int32
|
||||
case reflect.Uint32:
|
||||
p.enc = (*Buffer).enc_uint32
|
||||
p.dec = (*Buffer).dec_int32 // can reuse
|
||||
p.size = size_uint32
|
||||
case reflect.Int64, reflect.Uint64:
|
||||
p.enc = (*Buffer).enc_int64
|
||||
p.dec = (*Buffer).dec_int64
|
||||
p.size = size_int64
|
||||
case reflect.Float32:
|
||||
p.enc = (*Buffer).enc_uint32 // can just treat them as bits
|
||||
p.dec = (*Buffer).dec_int32
|
||||
p.size = size_uint32
|
||||
case reflect.Float64:
|
||||
p.enc = (*Buffer).enc_int64 // can just treat them as bits
|
||||
p.dec = (*Buffer).dec_int64
|
||||
p.size = size_int64
|
||||
case reflect.String:
|
||||
p.enc = (*Buffer).enc_string
|
||||
p.dec = (*Buffer).dec_string
|
||||
p.size = size_string
|
||||
case reflect.Struct:
|
||||
if t1.Elem().Kind() == reflect.Struct {
|
||||
p.stype = t1.Elem()
|
||||
p.isMarshaler = isMarshaler(t1)
|
||||
p.isUnmarshaler = isUnmarshaler(t1)
|
||||
if p.Wire == "bytes" {
|
||||
p.enc = (*Buffer).enc_struct_message
|
||||
p.dec = (*Buffer).dec_struct_message
|
||||
p.size = size_struct_message
|
||||
} else {
|
||||
p.enc = (*Buffer).enc_struct_group
|
||||
p.dec = (*Buffer).dec_struct_group
|
||||
p.size = size_struct_group
|
||||
}
|
||||
}
|
||||
|
||||
case reflect.Slice:
|
||||
switch t2 := t1.Elem(); t2.Kind() {
|
||||
default:
|
||||
logNoSliceEnc(t1, t2)
|
||||
break
|
||||
case reflect.Bool:
|
||||
if p.Packed {
|
||||
p.enc = (*Buffer).enc_slice_packed_bool
|
||||
p.size = size_slice_packed_bool
|
||||
} else {
|
||||
p.enc = (*Buffer).enc_slice_bool
|
||||
p.size = size_slice_bool
|
||||
}
|
||||
p.dec = (*Buffer).dec_slice_bool
|
||||
p.packedDec = (*Buffer).dec_slice_packed_bool
|
||||
case reflect.Int32:
|
||||
if p.Packed {
|
||||
p.enc = (*Buffer).enc_slice_packed_int32
|
||||
p.size = size_slice_packed_int32
|
||||
} else {
|
||||
p.enc = (*Buffer).enc_slice_int32
|
||||
p.size = size_slice_int32
|
||||
}
|
||||
p.dec = (*Buffer).dec_slice_int32
|
||||
p.packedDec = (*Buffer).dec_slice_packed_int32
|
||||
case reflect.Uint32:
|
||||
if p.Packed {
|
||||
p.enc = (*Buffer).enc_slice_packed_uint32
|
||||
p.size = size_slice_packed_uint32
|
||||
} else {
|
||||
p.enc = (*Buffer).enc_slice_uint32
|
||||
p.size = size_slice_uint32
|
||||
}
|
||||
p.dec = (*Buffer).dec_slice_int32
|
||||
p.packedDec = (*Buffer).dec_slice_packed_int32
|
||||
case reflect.Int64, reflect.Uint64:
|
||||
if p.Packed {
|
||||
p.enc = (*Buffer).enc_slice_packed_int64
|
||||
p.size = size_slice_packed_int64
|
||||
} else {
|
||||
p.enc = (*Buffer).enc_slice_int64
|
||||
p.size = size_slice_int64
|
||||
}
|
||||
p.dec = (*Buffer).dec_slice_int64
|
||||
p.packedDec = (*Buffer).dec_slice_packed_int64
|
||||
case reflect.Uint8:
|
||||
p.dec = (*Buffer).dec_slice_byte
|
||||
if p.proto3 {
|
||||
p.enc = (*Buffer).enc_proto3_slice_byte
|
||||
p.size = size_proto3_slice_byte
|
||||
} else {
|
||||
p.enc = (*Buffer).enc_slice_byte
|
||||
p.size = size_slice_byte
|
||||
}
|
||||
case reflect.Float32, reflect.Float64:
|
||||
switch t2.Bits() {
|
||||
case 32:
|
||||
// can just treat them as bits
|
||||
if p.Packed {
|
||||
p.enc = (*Buffer).enc_slice_packed_uint32
|
||||
p.size = size_slice_packed_uint32
|
||||
} else {
|
||||
p.enc = (*Buffer).enc_slice_uint32
|
||||
p.size = size_slice_uint32
|
||||
}
|
||||
p.dec = (*Buffer).dec_slice_int32
|
||||
p.packedDec = (*Buffer).dec_slice_packed_int32
|
||||
case 64:
|
||||
// can just treat them as bits
|
||||
if p.Packed {
|
||||
p.enc = (*Buffer).enc_slice_packed_int64
|
||||
p.size = size_slice_packed_int64
|
||||
} else {
|
||||
p.enc = (*Buffer).enc_slice_int64
|
||||
p.size = size_slice_int64
|
||||
}
|
||||
p.dec = (*Buffer).dec_slice_int64
|
||||
p.packedDec = (*Buffer).dec_slice_packed_int64
|
||||
default:
|
||||
logNoSliceEnc(t1, t2)
|
||||
break
|
||||
}
|
||||
case reflect.String:
|
||||
p.enc = (*Buffer).enc_slice_string
|
||||
p.dec = (*Buffer).dec_slice_string
|
||||
p.size = size_slice_string
|
||||
case reflect.Ptr:
|
||||
switch t3 := t2.Elem(); t3.Kind() {
|
||||
default:
|
||||
fmt.Fprintf(os.Stderr, "proto: no ptr oenc for %T -> %T -> %T\n", t1, t2, t3)
|
||||
break
|
||||
case reflect.Struct:
|
||||
p.stype = t2.Elem()
|
||||
p.isMarshaler = isMarshaler(t2)
|
||||
p.isUnmarshaler = isUnmarshaler(t2)
|
||||
if p.Wire == "bytes" {
|
||||
p.enc = (*Buffer).enc_slice_struct_message
|
||||
p.dec = (*Buffer).dec_slice_struct_message
|
||||
p.size = size_slice_struct_message
|
||||
} else {
|
||||
p.enc = (*Buffer).enc_slice_struct_group
|
||||
p.dec = (*Buffer).dec_slice_struct_group
|
||||
p.size = size_slice_struct_group
|
||||
}
|
||||
}
|
||||
case reflect.Slice:
|
||||
switch t2.Elem().Kind() {
|
||||
default:
|
||||
fmt.Fprintf(os.Stderr, "proto: no slice elem oenc for %T -> %T -> %T\n", t1, t2, t2.Elem())
|
||||
break
|
||||
case reflect.Uint8:
|
||||
p.enc = (*Buffer).enc_slice_slice_byte
|
||||
p.dec = (*Buffer).dec_slice_slice_byte
|
||||
p.size = size_slice_slice_byte
|
||||
}
|
||||
if t2 := t1.Elem(); t2.Kind() == reflect.Ptr && t2.Elem().Kind() == reflect.Struct {
|
||||
p.stype = t2.Elem()
|
||||
}
|
||||
|
||||
case reflect.Map:
|
||||
p.enc = (*Buffer).enc_new_map
|
||||
p.dec = (*Buffer).dec_new_map
|
||||
p.size = size_new_map
|
||||
|
||||
p.mtype = t1
|
||||
p.mkeyprop = &Properties{}
|
||||
p.mkeyprop.init(reflect.PtrTo(p.mtype.Key()), "Key", f.Tag.Get("protobuf_key"), nil, lockGetProp)
|
||||
p.mvalprop = &Properties{}
|
||||
p.MapKeyProp = &Properties{}
|
||||
p.MapKeyProp.init(reflect.PtrTo(p.mtype.Key()), "Key", f.Tag.Get("protobuf_key"), nil, lockGetProp)
|
||||
p.MapValProp = &Properties{}
|
||||
vtype := p.mtype.Elem()
|
||||
if vtype.Kind() != reflect.Ptr && vtype.Kind() != reflect.Slice {
|
||||
// The value type is not a message (*T) or bytes ([]byte),
|
||||
// so we need encoders for the pointer to this type.
|
||||
vtype = reflect.PtrTo(vtype)
|
||||
}
|
||||
p.mvalprop.init(vtype, "Value", f.Tag.Get("protobuf_val"), nil, lockGetProp)
|
||||
p.MapValProp.init(vtype, "Value", f.Tag.Get("protobuf_val"), nil, lockGetProp)
|
||||
}
|
||||
|
||||
// precalculate tag code
|
||||
wire := p.WireType
|
||||
if p.Packed {
|
||||
wire = WireBytes
|
||||
}
|
||||
x := uint32(p.Tag)<<3 | uint32(wire)
|
||||
i := 0
|
||||
for i = 0; x > 127; i++ {
|
||||
p.tagbuf[i] = 0x80 | uint8(x&0x7F)
|
||||
x >>= 7
|
||||
}
|
||||
p.tagbuf[i] = uint8(x)
|
||||
p.tagcode = p.tagbuf[0 : i+1]
|
||||
|
||||
if p.stype != nil {
|
||||
if lockGetProp {
|
||||
p.sprop = GetProperties(p.stype)
|
||||
|
@ -586,32 +296,9 @@ func (p *Properties) setEncAndDec(typ reflect.Type, f *reflect.StructField, lock
|
|||
}
|
||||
|
||||
var (
|
||||
marshalerType = reflect.TypeOf((*Marshaler)(nil)).Elem()
|
||||
unmarshalerType = reflect.TypeOf((*Unmarshaler)(nil)).Elem()
|
||||
marshalerType = reflect.TypeOf((*Marshaler)(nil)).Elem()
|
||||
)
|
||||
|
||||
// isMarshaler reports whether type t implements Marshaler.
|
||||
func isMarshaler(t reflect.Type) bool {
|
||||
// We're checking for (likely) pointer-receiver methods
|
||||
// so if t is not a pointer, something is very wrong.
|
||||
// The calls above only invoke isMarshaler on pointer types.
|
||||
if t.Kind() != reflect.Ptr {
|
||||
panic("proto: misuse of isMarshaler")
|
||||
}
|
||||
return t.Implements(marshalerType)
|
||||
}
|
||||
|
||||
// isUnmarshaler reports whether type t implements Unmarshaler.
|
||||
func isUnmarshaler(t reflect.Type) bool {
|
||||
// We're checking for (likely) pointer-receiver methods
|
||||
// so if t is not a pointer, something is very wrong.
|
||||
// The calls above only invoke isUnmarshaler on pointer types.
|
||||
if t.Kind() != reflect.Ptr {
|
||||
panic("proto: misuse of isUnmarshaler")
|
||||
}
|
||||
return t.Implements(unmarshalerType)
|
||||
}
|
||||
|
||||
// Init populates the properties from a protocol buffer struct tag.
|
||||
func (p *Properties) Init(typ reflect.Type, name, tag string, f *reflect.StructField) {
|
||||
p.init(typ, name, tag, f, true)
|
||||
|
@ -621,14 +308,11 @@ func (p *Properties) init(typ reflect.Type, name, tag string, f *reflect.StructF
|
|||
// "bytes,49,opt,def=hello!"
|
||||
p.Name = name
|
||||
p.OrigName = name
|
||||
if f != nil {
|
||||
p.field = toField(f)
|
||||
}
|
||||
if tag == "" {
|
||||
return
|
||||
}
|
||||
p.Parse(tag)
|
||||
p.setEncAndDec(typ, f, lockGetProp)
|
||||
p.setFieldProps(typ, f, lockGetProp)
|
||||
}
|
||||
|
||||
var (
|
||||
|
@ -649,9 +333,6 @@ func GetProperties(t reflect.Type) *StructProperties {
|
|||
sprop, ok := propertiesMap[t]
|
||||
propertiesMu.RUnlock()
|
||||
if ok {
|
||||
if collectStats {
|
||||
stats.Chit++
|
||||
}
|
||||
return sprop
|
||||
}
|
||||
|
||||
|
@ -661,26 +342,26 @@ func GetProperties(t reflect.Type) *StructProperties {
|
|||
return sprop
|
||||
}
|
||||
|
||||
type (
|
||||
oneofFuncsIface interface {
|
||||
XXX_OneofFuncs() (func(Message, *Buffer) error, func(Message, int, int, *Buffer) (bool, error), func(Message) int, []interface{})
|
||||
}
|
||||
oneofWrappersIface interface {
|
||||
XXX_OneofWrappers() []interface{}
|
||||
}
|
||||
)
|
||||
|
||||
// getPropertiesLocked requires that propertiesMu is held.
|
||||
func getPropertiesLocked(t reflect.Type) *StructProperties {
|
||||
if prop, ok := propertiesMap[t]; ok {
|
||||
if collectStats {
|
||||
stats.Chit++
|
||||
}
|
||||
return prop
|
||||
}
|
||||
if collectStats {
|
||||
stats.Cmiss++
|
||||
}
|
||||
|
||||
prop := new(StructProperties)
|
||||
// in case of recursive protos, fill this in now.
|
||||
propertiesMap[t] = prop
|
||||
|
||||
// build properties
|
||||
prop.extendable = reflect.PtrTo(t).Implements(extendableProtoType) ||
|
||||
reflect.PtrTo(t).Implements(extendableProtoV1Type)
|
||||
prop.unrecField = invalidField
|
||||
prop.Prop = make([]*Properties, t.NumField())
|
||||
prop.order = make([]int, t.NumField())
|
||||
|
||||
|
@ -690,17 +371,6 @@ func getPropertiesLocked(t reflect.Type) *StructProperties {
|
|||
name := f.Name
|
||||
p.init(f.Type, name, f.Tag.Get("protobuf"), &f, false)
|
||||
|
||||
if f.Name == "XXX_InternalExtensions" { // special case
|
||||
p.enc = (*Buffer).enc_exts
|
||||
p.dec = nil // not needed
|
||||
p.size = size_exts
|
||||
} else if f.Name == "XXX_extensions" { // special case
|
||||
p.enc = (*Buffer).enc_map
|
||||
p.dec = nil // not needed
|
||||
p.size = size_map
|
||||
} else if f.Name == "XXX_unrecognized" { // special case
|
||||
prop.unrecField = toField(&f)
|
||||
}
|
||||
oneof := f.Tag.Get("protobuf_oneof") // special case
|
||||
if oneof != "" {
|
||||
// Oneof fields don't use the traditional protobuf tag.
|
||||
|
@ -715,22 +385,19 @@ func getPropertiesLocked(t reflect.Type) *StructProperties {
|
|||
}
|
||||
print("\n")
|
||||
}
|
||||
if p.enc == nil && !strings.HasPrefix(f.Name, "XXX_") && oneof == "" {
|
||||
fmt.Fprintln(os.Stderr, "proto: no encoder for", f.Name, f.Type.String(), "[GetProperties]")
|
||||
}
|
||||
}
|
||||
|
||||
// Re-order prop.order.
|
||||
sort.Sort(prop)
|
||||
|
||||
type oneofMessage interface {
|
||||
XXX_OneofFuncs() (func(Message, *Buffer) error, func(Message, int, int, *Buffer) (bool, error), func(Message) int, []interface{})
|
||||
var oots []interface{}
|
||||
switch m := reflect.Zero(reflect.PtrTo(t)).Interface().(type) {
|
||||
case oneofFuncsIface:
|
||||
_, _, _, oots = m.XXX_OneofFuncs()
|
||||
case oneofWrappersIface:
|
||||
oots = m.XXX_OneofWrappers()
|
||||
}
|
||||
if om, ok := reflect.Zero(reflect.PtrTo(t)).Interface().(oneofMessage); ok {
|
||||
var oots []interface{}
|
||||
prop.oneofMarshaler, prop.oneofUnmarshaler, prop.oneofSizer, oots = om.XXX_OneofFuncs()
|
||||
prop.stype = t
|
||||
|
||||
if len(oots) > 0 {
|
||||
// Interpret oneof metadata.
|
||||
prop.OneofTypes = make(map[string]*OneofProperties)
|
||||
for _, oot := range oots {
|
||||
|
@ -779,30 +446,6 @@ func getPropertiesLocked(t reflect.Type) *StructProperties {
|
|||
return prop
|
||||
}
|
||||
|
||||
// Return the Properties object for the x[0]'th field of the structure.
|
||||
func propByIndex(t reflect.Type, x []int) *Properties {
|
||||
if len(x) != 1 {
|
||||
fmt.Fprintf(os.Stderr, "proto: field index dimension %d (not 1) for type %s\n", len(x), t)
|
||||
return nil
|
||||
}
|
||||
prop := GetProperties(t)
|
||||
return prop.Prop[x[0]]
|
||||
}
|
||||
|
||||
// Get the address and type of a pointer to a struct from an interface.
|
||||
func getbase(pb Message) (t reflect.Type, b structPointer, err error) {
|
||||
if pb == nil {
|
||||
err = ErrNil
|
||||
return
|
||||
}
|
||||
// get the reflect type of the pointer to the struct.
|
||||
t = reflect.TypeOf(pb)
|
||||
// get the address of the struct.
|
||||
value := reflect.ValueOf(pb)
|
||||
b = toStructPointer(value)
|
||||
return
|
||||
}
|
||||
|
||||
// A global registry of enum types.
|
||||
// The generated code will register the generated maps by calling RegisterEnum.
|
||||
|
||||
|
@ -826,20 +469,42 @@ func EnumValueMap(enumType string) map[string]int32 {
|
|||
// A registry of all linked message types.
|
||||
// The string is a fully-qualified proto name ("pkg.Message").
|
||||
var (
|
||||
protoTypes = make(map[string]reflect.Type)
|
||||
revProtoTypes = make(map[reflect.Type]string)
|
||||
protoTypedNils = make(map[string]Message) // a map from proto names to typed nil pointers
|
||||
protoMapTypes = make(map[string]reflect.Type) // a map from proto names to map types
|
||||
revProtoTypes = make(map[reflect.Type]string)
|
||||
)
|
||||
|
||||
// RegisterType is called from generated code and maps from the fully qualified
|
||||
// proto name to the type (pointer to struct) of the protocol buffer.
|
||||
func RegisterType(x Message, name string) {
|
||||
if _, ok := protoTypes[name]; ok {
|
||||
if _, ok := protoTypedNils[name]; ok {
|
||||
// TODO: Some day, make this a panic.
|
||||
log.Printf("proto: duplicate proto type registered: %s", name)
|
||||
return
|
||||
}
|
||||
t := reflect.TypeOf(x)
|
||||
protoTypes[name] = t
|
||||
if v := reflect.ValueOf(x); v.Kind() == reflect.Ptr && v.Pointer() == 0 {
|
||||
// Generated code always calls RegisterType with nil x.
|
||||
// This check is just for extra safety.
|
||||
protoTypedNils[name] = x
|
||||
} else {
|
||||
protoTypedNils[name] = reflect.Zero(t).Interface().(Message)
|
||||
}
|
||||
revProtoTypes[t] = name
|
||||
}
|
||||
|
||||
// RegisterMapType is called from generated code and maps from the fully qualified
|
||||
// proto name to the native map type of the proto map definition.
|
||||
func RegisterMapType(x interface{}, name string) {
|
||||
if reflect.TypeOf(x).Kind() != reflect.Map {
|
||||
panic(fmt.Sprintf("RegisterMapType(%T, %q); want map", x, name))
|
||||
}
|
||||
if _, ok := protoMapTypes[name]; ok {
|
||||
log.Printf("proto: duplicate proto type registered: %s", name)
|
||||
return
|
||||
}
|
||||
t := reflect.TypeOf(x)
|
||||
protoMapTypes[name] = t
|
||||
revProtoTypes[t] = name
|
||||
}
|
||||
|
||||
|
@ -855,7 +520,14 @@ func MessageName(x Message) string {
|
|||
}
|
||||
|
||||
// MessageType returns the message type (pointer to struct) for a named message.
|
||||
func MessageType(name string) reflect.Type { return protoTypes[name] }
|
||||
// The type is not guaranteed to implement proto.Message if the name refers to a
|
||||
// map entry.
|
||||
func MessageType(name string) reflect.Type {
|
||||
if t, ok := protoTypedNils[name]; ok {
|
||||
return reflect.TypeOf(t)
|
||||
}
|
||||
return protoMapTypes[name]
|
||||
}
|
||||
|
||||
// A registry of all linked proto files.
|
||||
var (
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,654 @@
|
|||
// Go support for Protocol Buffers - Google's data interchange format
|
||||
//
|
||||
// Copyright 2016 The Go Authors. All rights reserved.
|
||||
// https://github.com/golang/protobuf
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are
|
||||
// met:
|
||||
//
|
||||
// * Redistributions of source code must retain the above copyright
|
||||
// notice, this list of conditions and the following disclaimer.
|
||||
// * Redistributions in binary form must reproduce the above
|
||||
// copyright notice, this list of conditions and the following disclaimer
|
||||
// in the documentation and/or other materials provided with the
|
||||
// distribution.
|
||||
// * Neither the name of Google Inc. nor the names of its
|
||||
// contributors may be used to endorse or promote products derived from
|
||||
// this software without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
package proto
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strings"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
)
|
||||
|
||||
// Merge merges the src message into dst.
|
||||
// This assumes that dst and src of the same type and are non-nil.
|
||||
func (a *InternalMessageInfo) Merge(dst, src Message) {
|
||||
mi := atomicLoadMergeInfo(&a.merge)
|
||||
if mi == nil {
|
||||
mi = getMergeInfo(reflect.TypeOf(dst).Elem())
|
||||
atomicStoreMergeInfo(&a.merge, mi)
|
||||
}
|
||||
mi.merge(toPointer(&dst), toPointer(&src))
|
||||
}
|
||||
|
||||
type mergeInfo struct {
|
||||
typ reflect.Type
|
||||
|
||||
initialized int32 // 0: only typ is valid, 1: everything is valid
|
||||
lock sync.Mutex
|
||||
|
||||
fields []mergeFieldInfo
|
||||
unrecognized field // Offset of XXX_unrecognized
|
||||
}
|
||||
|
||||
type mergeFieldInfo struct {
|
||||
field field // Offset of field, guaranteed to be valid
|
||||
|
||||
// isPointer reports whether the value in the field is a pointer.
|
||||
// This is true for the following situations:
|
||||
// * Pointer to struct
|
||||
// * Pointer to basic type (proto2 only)
|
||||
// * Slice (first value in slice header is a pointer)
|
||||
// * String (first value in string header is a pointer)
|
||||
isPointer bool
|
||||
|
||||
// basicWidth reports the width of the field assuming that it is directly
|
||||
// embedded in the struct (as is the case for basic types in proto3).
|
||||
// The possible values are:
|
||||
// 0: invalid
|
||||
// 1: bool
|
||||
// 4: int32, uint32, float32
|
||||
// 8: int64, uint64, float64
|
||||
basicWidth int
|
||||
|
||||
// Where dst and src are pointers to the types being merged.
|
||||
merge func(dst, src pointer)
|
||||
}
|
||||
|
||||
var (
|
||||
mergeInfoMap = map[reflect.Type]*mergeInfo{}
|
||||
mergeInfoLock sync.Mutex
|
||||
)
|
||||
|
||||
func getMergeInfo(t reflect.Type) *mergeInfo {
|
||||
mergeInfoLock.Lock()
|
||||
defer mergeInfoLock.Unlock()
|
||||
mi := mergeInfoMap[t]
|
||||
if mi == nil {
|
||||
mi = &mergeInfo{typ: t}
|
||||
mergeInfoMap[t] = mi
|
||||
}
|
||||
return mi
|
||||
}
|
||||
|
||||
// merge merges src into dst assuming they are both of type *mi.typ.
|
||||
func (mi *mergeInfo) merge(dst, src pointer) {
|
||||
if dst.isNil() {
|
||||
panic("proto: nil destination")
|
||||
}
|
||||
if src.isNil() {
|
||||
return // Nothing to do.
|
||||
}
|
||||
|
||||
if atomic.LoadInt32(&mi.initialized) == 0 {
|
||||
mi.computeMergeInfo()
|
||||
}
|
||||
|
||||
for _, fi := range mi.fields {
|
||||
sfp := src.offset(fi.field)
|
||||
|
||||
// As an optimization, we can avoid the merge function call cost
|
||||
// if we know for sure that the source will have no effect
|
||||
// by checking if it is the zero value.
|
||||
if unsafeAllowed {
|
||||
if fi.isPointer && sfp.getPointer().isNil() { // Could be slice or string
|
||||
continue
|
||||
}
|
||||
if fi.basicWidth > 0 {
|
||||
switch {
|
||||
case fi.basicWidth == 1 && !*sfp.toBool():
|
||||
continue
|
||||
case fi.basicWidth == 4 && *sfp.toUint32() == 0:
|
||||
continue
|
||||
case fi.basicWidth == 8 && *sfp.toUint64() == 0:
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
dfp := dst.offset(fi.field)
|
||||
fi.merge(dfp, sfp)
|
||||
}
|
||||
|
||||
// TODO: Make this faster?
|
||||
out := dst.asPointerTo(mi.typ).Elem()
|
||||
in := src.asPointerTo(mi.typ).Elem()
|
||||
if emIn, err := extendable(in.Addr().Interface()); err == nil {
|
||||
emOut, _ := extendable(out.Addr().Interface())
|
||||
mIn, muIn := emIn.extensionsRead()
|
||||
if mIn != nil {
|
||||
mOut := emOut.extensionsWrite()
|
||||
muIn.Lock()
|
||||
mergeExtension(mOut, mIn)
|
||||
muIn.Unlock()
|
||||
}
|
||||
}
|
||||
|
||||
if mi.unrecognized.IsValid() {
|
||||
if b := *src.offset(mi.unrecognized).toBytes(); len(b) > 0 {
|
||||
*dst.offset(mi.unrecognized).toBytes() = append([]byte(nil), b...)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (mi *mergeInfo) computeMergeInfo() {
|
||||
mi.lock.Lock()
|
||||
defer mi.lock.Unlock()
|
||||
if mi.initialized != 0 {
|
||||
return
|
||||
}
|
||||
t := mi.typ
|
||||
n := t.NumField()
|
||||
|
||||
props := GetProperties(t)
|
||||
for i := 0; i < n; i++ {
|
||||
f := t.Field(i)
|
||||
if strings.HasPrefix(f.Name, "XXX_") {
|
||||
continue
|
||||
}
|
||||
|
||||
mfi := mergeFieldInfo{field: toField(&f)}
|
||||
tf := f.Type
|
||||
|
||||
// As an optimization, we can avoid the merge function call cost
|
||||
// if we know for sure that the source will have no effect
|
||||
// by checking if it is the zero value.
|
||||
if unsafeAllowed {
|
||||
switch tf.Kind() {
|
||||
case reflect.Ptr, reflect.Slice, reflect.String:
|
||||
// As a special case, we assume slices and strings are pointers
|
||||
// since we know that the first field in the SliceSlice or
|
||||
// StringHeader is a data pointer.
|
||||
mfi.isPointer = true
|
||||
case reflect.Bool:
|
||||
mfi.basicWidth = 1
|
||||
case reflect.Int32, reflect.Uint32, reflect.Float32:
|
||||
mfi.basicWidth = 4
|
||||
case reflect.Int64, reflect.Uint64, reflect.Float64:
|
||||
mfi.basicWidth = 8
|
||||
}
|
||||
}
|
||||
|
||||
// Unwrap tf to get at its most basic type.
|
||||
var isPointer, isSlice bool
|
||||
if tf.Kind() == reflect.Slice && tf.Elem().Kind() != reflect.Uint8 {
|
||||
isSlice = true
|
||||
tf = tf.Elem()
|
||||
}
|
||||
if tf.Kind() == reflect.Ptr {
|
||||
isPointer = true
|
||||
tf = tf.Elem()
|
||||
}
|
||||
if isPointer && isSlice && tf.Kind() != reflect.Struct {
|
||||
panic("both pointer and slice for basic type in " + tf.Name())
|
||||
}
|
||||
|
||||
switch tf.Kind() {
|
||||
case reflect.Int32:
|
||||
switch {
|
||||
case isSlice: // E.g., []int32
|
||||
mfi.merge = func(dst, src pointer) {
|
||||
// NOTE: toInt32Slice is not defined (see pointer_reflect.go).
|
||||
/*
|
||||
sfsp := src.toInt32Slice()
|
||||
if *sfsp != nil {
|
||||
dfsp := dst.toInt32Slice()
|
||||
*dfsp = append(*dfsp, *sfsp...)
|
||||
if *dfsp == nil {
|
||||
*dfsp = []int64{}
|
||||
}
|
||||
}
|
||||
*/
|
||||
sfs := src.getInt32Slice()
|
||||
if sfs != nil {
|
||||
dfs := dst.getInt32Slice()
|
||||
dfs = append(dfs, sfs...)
|
||||
if dfs == nil {
|
||||
dfs = []int32{}
|
||||
}
|
||||
dst.setInt32Slice(dfs)
|
||||
}
|
||||
}
|
||||
case isPointer: // E.g., *int32
|
||||
mfi.merge = func(dst, src pointer) {
|
||||
// NOTE: toInt32Ptr is not defined (see pointer_reflect.go).
|
||||
/*
|
||||
sfpp := src.toInt32Ptr()
|
||||
if *sfpp != nil {
|
||||
dfpp := dst.toInt32Ptr()
|
||||
if *dfpp == nil {
|
||||
*dfpp = Int32(**sfpp)
|
||||
} else {
|
||||
**dfpp = **sfpp
|
||||
}
|
||||
}
|
||||
*/
|
||||
sfp := src.getInt32Ptr()
|
||||
if sfp != nil {
|
||||
dfp := dst.getInt32Ptr()
|
||||
if dfp == nil {
|
||||
dst.setInt32Ptr(*sfp)
|
||||
} else {
|
||||
*dfp = *sfp
|
||||
}
|
||||
}
|
||||
}
|
||||
default: // E.g., int32
|
||||
mfi.merge = func(dst, src pointer) {
|
||||
if v := *src.toInt32(); v != 0 {
|
||||
*dst.toInt32() = v
|
||||
}
|
||||
}
|
||||
}
|
||||
case reflect.Int64:
|
||||
switch {
|
||||
case isSlice: // E.g., []int64
|
||||
mfi.merge = func(dst, src pointer) {
|
||||
sfsp := src.toInt64Slice()
|
||||
if *sfsp != nil {
|
||||
dfsp := dst.toInt64Slice()
|
||||
*dfsp = append(*dfsp, *sfsp...)
|
||||
if *dfsp == nil {
|
||||
*dfsp = []int64{}
|
||||
}
|
||||
}
|
||||
}
|
||||
case isPointer: // E.g., *int64
|
||||
mfi.merge = func(dst, src pointer) {
|
||||
sfpp := src.toInt64Ptr()
|
||||
if *sfpp != nil {
|
||||
dfpp := dst.toInt64Ptr()
|
||||
if *dfpp == nil {
|
||||
*dfpp = Int64(**sfpp)
|
||||
} else {
|
||||
**dfpp = **sfpp
|
||||
}
|
||||
}
|
||||
}
|
||||
default: // E.g., int64
|
||||
mfi.merge = func(dst, src pointer) {
|
||||
if v := *src.toInt64(); v != 0 {
|
||||
*dst.toInt64() = v
|
||||
}
|
||||
}
|
||||
}
|
||||
case reflect.Uint32:
|
||||
switch {
|
||||
case isSlice: // E.g., []uint32
|
||||
mfi.merge = func(dst, src pointer) {
|
||||
sfsp := src.toUint32Slice()
|
||||
if *sfsp != nil {
|
||||
dfsp := dst.toUint32Slice()
|
||||
*dfsp = append(*dfsp, *sfsp...)
|
||||
if *dfsp == nil {
|
||||
*dfsp = []uint32{}
|
||||
}
|
||||
}
|
||||
}
|
||||
case isPointer: // E.g., *uint32
|
||||
mfi.merge = func(dst, src pointer) {
|
||||
sfpp := src.toUint32Ptr()
|
||||
if *sfpp != nil {
|
||||
dfpp := dst.toUint32Ptr()
|
||||
if *dfpp == nil {
|
||||
*dfpp = Uint32(**sfpp)
|
||||
} else {
|
||||
**dfpp = **sfpp
|
||||
}
|
||||
}
|
||||
}
|
||||
default: // E.g., uint32
|
||||
mfi.merge = func(dst, src pointer) {
|
||||
if v := *src.toUint32(); v != 0 {
|
||||
*dst.toUint32() = v
|
||||
}
|
||||
}
|
||||
}
|
||||
case reflect.Uint64:
|
||||
switch {
|
||||
case isSlice: // E.g., []uint64
|
||||
mfi.merge = func(dst, src pointer) {
|
||||
sfsp := src.toUint64Slice()
|
||||
if *sfsp != nil {
|
||||
dfsp := dst.toUint64Slice()
|
||||
*dfsp = append(*dfsp, *sfsp...)
|
||||
if *dfsp == nil {
|
||||
*dfsp = []uint64{}
|
||||
}
|
||||
}
|
||||
}
|
||||
case isPointer: // E.g., *uint64
|
||||
mfi.merge = func(dst, src pointer) {
|
||||
sfpp := src.toUint64Ptr()
|
||||
if *sfpp != nil {
|
||||
dfpp := dst.toUint64Ptr()
|
||||
if *dfpp == nil {
|
||||
*dfpp = Uint64(**sfpp)
|
||||
} else {
|
||||
**dfpp = **sfpp
|
||||
}
|
||||
}
|
||||
}
|
||||
default: // E.g., uint64
|
||||
mfi.merge = func(dst, src pointer) {
|
||||
if v := *src.toUint64(); v != 0 {
|
||||
*dst.toUint64() = v
|
||||
}
|
||||
}
|
||||
}
|
||||
case reflect.Float32:
|
||||
switch {
|
||||
case isSlice: // E.g., []float32
|
||||
mfi.merge = func(dst, src pointer) {
|
||||
sfsp := src.toFloat32Slice()
|
||||
if *sfsp != nil {
|
||||
dfsp := dst.toFloat32Slice()
|
||||
*dfsp = append(*dfsp, *sfsp...)
|
||||
if *dfsp == nil {
|
||||
*dfsp = []float32{}
|
||||
}
|
||||
}
|
||||
}
|
||||
case isPointer: // E.g., *float32
|
||||
mfi.merge = func(dst, src pointer) {
|
||||
sfpp := src.toFloat32Ptr()
|
||||
if *sfpp != nil {
|
||||
dfpp := dst.toFloat32Ptr()
|
||||
if *dfpp == nil {
|
||||
*dfpp = Float32(**sfpp)
|
||||
} else {
|
||||
**dfpp = **sfpp
|
||||
}
|
||||
}
|
||||
}
|
||||
default: // E.g., float32
|
||||
mfi.merge = func(dst, src pointer) {
|
||||
if v := *src.toFloat32(); v != 0 {
|
||||
*dst.toFloat32() = v
|
||||
}
|
||||
}
|
||||
}
|
||||
case reflect.Float64:
|
||||
switch {
|
||||
case isSlice: // E.g., []float64
|
||||
mfi.merge = func(dst, src pointer) {
|
||||
sfsp := src.toFloat64Slice()
|
||||
if *sfsp != nil {
|
||||
dfsp := dst.toFloat64Slice()
|
||||
*dfsp = append(*dfsp, *sfsp...)
|
||||
if *dfsp == nil {
|
||||
*dfsp = []float64{}
|
||||
}
|
||||
}
|
||||
}
|
||||
case isPointer: // E.g., *float64
|
||||
mfi.merge = func(dst, src pointer) {
|
||||
sfpp := src.toFloat64Ptr()
|
||||
if *sfpp != nil {
|
||||
dfpp := dst.toFloat64Ptr()
|
||||
if *dfpp == nil {
|
||||
*dfpp = Float64(**sfpp)
|
||||
} else {
|
||||
**dfpp = **sfpp
|
||||
}
|
||||
}
|
||||
}
|
||||
default: // E.g., float64
|
||||
mfi.merge = func(dst, src pointer) {
|
||||
if v := *src.toFloat64(); v != 0 {
|
||||
*dst.toFloat64() = v
|
||||
}
|
||||
}
|
||||
}
|
||||
case reflect.Bool:
|
||||
switch {
|
||||
case isSlice: // E.g., []bool
|
||||
mfi.merge = func(dst, src pointer) {
|
||||
sfsp := src.toBoolSlice()
|
||||
if *sfsp != nil {
|
||||
dfsp := dst.toBoolSlice()
|
||||
*dfsp = append(*dfsp, *sfsp...)
|
||||
if *dfsp == nil {
|
||||
*dfsp = []bool{}
|
||||
}
|
||||
}
|
||||
}
|
||||
case isPointer: // E.g., *bool
|
||||
mfi.merge = func(dst, src pointer) {
|
||||
sfpp := src.toBoolPtr()
|
||||
if *sfpp != nil {
|
||||
dfpp := dst.toBoolPtr()
|
||||
if *dfpp == nil {
|
||||
*dfpp = Bool(**sfpp)
|
||||
} else {
|
||||
**dfpp = **sfpp
|
||||
}
|
||||
}
|
||||
}
|
||||
default: // E.g., bool
|
||||
mfi.merge = func(dst, src pointer) {
|
||||
if v := *src.toBool(); v {
|
||||
*dst.toBool() = v
|
||||
}
|
||||
}
|
||||
}
|
||||
case reflect.String:
|
||||
switch {
|
||||
case isSlice: // E.g., []string
|
||||
mfi.merge = func(dst, src pointer) {
|
||||
sfsp := src.toStringSlice()
|
||||
if *sfsp != nil {
|
||||
dfsp := dst.toStringSlice()
|
||||
*dfsp = append(*dfsp, *sfsp...)
|
||||
if *dfsp == nil {
|
||||
*dfsp = []string{}
|
||||
}
|
||||
}
|
||||
}
|
||||
case isPointer: // E.g., *string
|
||||
mfi.merge = func(dst, src pointer) {
|
||||
sfpp := src.toStringPtr()
|
||||
if *sfpp != nil {
|
||||
dfpp := dst.toStringPtr()
|
||||
if *dfpp == nil {
|
||||
*dfpp = String(**sfpp)
|
||||
} else {
|
||||
**dfpp = **sfpp
|
||||
}
|
||||
}
|
||||
}
|
||||
default: // E.g., string
|
||||
mfi.merge = func(dst, src pointer) {
|
||||
if v := *src.toString(); v != "" {
|
||||
*dst.toString() = v
|
||||
}
|
||||
}
|
||||
}
|
||||
case reflect.Slice:
|
||||
isProto3 := props.Prop[i].proto3
|
||||
switch {
|
||||
case isPointer:
|
||||
panic("bad pointer in byte slice case in " + tf.Name())
|
||||
case tf.Elem().Kind() != reflect.Uint8:
|
||||
panic("bad element kind in byte slice case in " + tf.Name())
|
||||
case isSlice: // E.g., [][]byte
|
||||
mfi.merge = func(dst, src pointer) {
|
||||
sbsp := src.toBytesSlice()
|
||||
if *sbsp != nil {
|
||||
dbsp := dst.toBytesSlice()
|
||||
for _, sb := range *sbsp {
|
||||
if sb == nil {
|
||||
*dbsp = append(*dbsp, nil)
|
||||
} else {
|
||||
*dbsp = append(*dbsp, append([]byte{}, sb...))
|
||||
}
|
||||
}
|
||||
if *dbsp == nil {
|
||||
*dbsp = [][]byte{}
|
||||
}
|
||||
}
|
||||
}
|
||||
default: // E.g., []byte
|
||||
mfi.merge = func(dst, src pointer) {
|
||||
sbp := src.toBytes()
|
||||
if *sbp != nil {
|
||||
dbp := dst.toBytes()
|
||||
if !isProto3 || len(*sbp) > 0 {
|
||||
*dbp = append([]byte{}, *sbp...)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
case reflect.Struct:
|
||||
switch {
|
||||
case !isPointer:
|
||||
panic(fmt.Sprintf("message field %s without pointer", tf))
|
||||
case isSlice: // E.g., []*pb.T
|
||||
mi := getMergeInfo(tf)
|
||||
mfi.merge = func(dst, src pointer) {
|
||||
sps := src.getPointerSlice()
|
||||
if sps != nil {
|
||||
dps := dst.getPointerSlice()
|
||||
for _, sp := range sps {
|
||||
var dp pointer
|
||||
if !sp.isNil() {
|
||||
dp = valToPointer(reflect.New(tf))
|
||||
mi.merge(dp, sp)
|
||||
}
|
||||
dps = append(dps, dp)
|
||||
}
|
||||
if dps == nil {
|
||||
dps = []pointer{}
|
||||
}
|
||||
dst.setPointerSlice(dps)
|
||||
}
|
||||
}
|
||||
default: // E.g., *pb.T
|
||||
mi := getMergeInfo(tf)
|
||||
mfi.merge = func(dst, src pointer) {
|
||||
sp := src.getPointer()
|
||||
if !sp.isNil() {
|
||||
dp := dst.getPointer()
|
||||
if dp.isNil() {
|
||||
dp = valToPointer(reflect.New(tf))
|
||||
dst.setPointer(dp)
|
||||
}
|
||||
mi.merge(dp, sp)
|
||||
}
|
||||
}
|
||||
}
|
||||
case reflect.Map:
|
||||
switch {
|
||||
case isPointer || isSlice:
|
||||
panic("bad pointer or slice in map case in " + tf.Name())
|
||||
default: // E.g., map[K]V
|
||||
mfi.merge = func(dst, src pointer) {
|
||||
sm := src.asPointerTo(tf).Elem()
|
||||
if sm.Len() == 0 {
|
||||
return
|
||||
}
|
||||
dm := dst.asPointerTo(tf).Elem()
|
||||
if dm.IsNil() {
|
||||
dm.Set(reflect.MakeMap(tf))
|
||||
}
|
||||
|
||||
switch tf.Elem().Kind() {
|
||||
case reflect.Ptr: // Proto struct (e.g., *T)
|
||||
for _, key := range sm.MapKeys() {
|
||||
val := sm.MapIndex(key)
|
||||
val = reflect.ValueOf(Clone(val.Interface().(Message)))
|
||||
dm.SetMapIndex(key, val)
|
||||
}
|
||||
case reflect.Slice: // E.g. Bytes type (e.g., []byte)
|
||||
for _, key := range sm.MapKeys() {
|
||||
val := sm.MapIndex(key)
|
||||
val = reflect.ValueOf(append([]byte{}, val.Bytes()...))
|
||||
dm.SetMapIndex(key, val)
|
||||
}
|
||||
default: // Basic type (e.g., string)
|
||||
for _, key := range sm.MapKeys() {
|
||||
val := sm.MapIndex(key)
|
||||
dm.SetMapIndex(key, val)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
case reflect.Interface:
|
||||
// Must be oneof field.
|
||||
switch {
|
||||
case isPointer || isSlice:
|
||||
panic("bad pointer or slice in interface case in " + tf.Name())
|
||||
default: // E.g., interface{}
|
||||
// TODO: Make this faster?
|
||||
mfi.merge = func(dst, src pointer) {
|
||||
su := src.asPointerTo(tf).Elem()
|
||||
if !su.IsNil() {
|
||||
du := dst.asPointerTo(tf).Elem()
|
||||
typ := su.Elem().Type()
|
||||
if du.IsNil() || du.Elem().Type() != typ {
|
||||
du.Set(reflect.New(typ.Elem())) // Initialize interface if empty
|
||||
}
|
||||
sv := su.Elem().Elem().Field(0)
|
||||
if sv.Kind() == reflect.Ptr && sv.IsNil() {
|
||||
return
|
||||
}
|
||||
dv := du.Elem().Elem().Field(0)
|
||||
if dv.Kind() == reflect.Ptr && dv.IsNil() {
|
||||
dv.Set(reflect.New(sv.Type().Elem())) // Initialize proto message if empty
|
||||
}
|
||||
switch sv.Type().Kind() {
|
||||
case reflect.Ptr: // Proto struct (e.g., *T)
|
||||
Merge(dv.Interface().(Message), sv.Interface().(Message))
|
||||
case reflect.Slice: // E.g. Bytes type (e.g., []byte)
|
||||
dv.Set(reflect.ValueOf(append([]byte{}, sv.Bytes()...)))
|
||||
default: // Basic type (e.g., string)
|
||||
dv.Set(sv)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
default:
|
||||
panic(fmt.Sprintf("merger not found for type:%s", tf))
|
||||
}
|
||||
mi.fields = append(mi.fields, mfi)
|
||||
}
|
||||
|
||||
mi.unrecognized = invalidField
|
||||
if f, ok := t.FieldByName("XXX_unrecognized"); ok {
|
||||
if f.Type != reflect.TypeOf([]byte{}) {
|
||||
panic("expected XXX_unrecognized to be of type []byte")
|
||||
}
|
||||
mi.unrecognized = toField(&f)
|
||||
}
|
||||
|
||||
atomic.StoreInt32(&mi.initialized, 1)
|
||||
}
|
File diff suppressed because it is too large
Load Diff
|
@ -50,7 +50,6 @@ import (
|
|||
var (
|
||||
newline = []byte("\n")
|
||||
spaces = []byte(" ")
|
||||
gtNewline = []byte(">\n")
|
||||
endBraceNewline = []byte("}\n")
|
||||
backslashN = []byte{'\\', 'n'}
|
||||
backslashR = []byte{'\\', 'r'}
|
||||
|
@ -170,11 +169,6 @@ func writeName(w *textWriter, props *Properties) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
// raw is the interface satisfied by RawMessage.
|
||||
type raw interface {
|
||||
Bytes() []byte
|
||||
}
|
||||
|
||||
func requiresQuotes(u string) bool {
|
||||
// When type URL contains any characters except [0-9A-Za-z./\-]*, it must be quoted.
|
||||
for _, ch := range u {
|
||||
|
@ -269,6 +263,10 @@ func (tm *TextMarshaler) writeStruct(w *textWriter, sv reflect.Value) error {
|
|||
props := sprops.Prop[i]
|
||||
name := st.Field(i).Name
|
||||
|
||||
if name == "XXX_NoUnkeyedLiteral" {
|
||||
continue
|
||||
}
|
||||
|
||||
if strings.HasPrefix(name, "XXX_") {
|
||||
// There are two XXX_ fields:
|
||||
// XXX_unrecognized []byte
|
||||
|
@ -355,7 +353,7 @@ func (tm *TextMarshaler) writeStruct(w *textWriter, sv reflect.Value) error {
|
|||
return err
|
||||
}
|
||||
}
|
||||
if err := tm.writeAny(w, key, props.mkeyprop); err != nil {
|
||||
if err := tm.writeAny(w, key, props.MapKeyProp); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := w.WriteByte('\n'); err != nil {
|
||||
|
@ -372,7 +370,7 @@ func (tm *TextMarshaler) writeStruct(w *textWriter, sv reflect.Value) error {
|
|||
return err
|
||||
}
|
||||
}
|
||||
if err := tm.writeAny(w, val, props.mvalprop); err != nil {
|
||||
if err := tm.writeAny(w, val, props.MapValProp); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := w.WriteByte('\n'); err != nil {
|
||||
|
@ -436,12 +434,6 @@ func (tm *TextMarshaler) writeStruct(w *textWriter, sv reflect.Value) error {
|
|||
return err
|
||||
}
|
||||
}
|
||||
if b, ok := fv.Interface().(raw); ok {
|
||||
if err := writeRaw(w, b.Bytes()); err != nil {
|
||||
return err
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
// Enums have a String method, so writeAny will work fine.
|
||||
if err := tm.writeAny(w, fv, props); err != nil {
|
||||
|
@ -455,7 +447,7 @@ func (tm *TextMarshaler) writeStruct(w *textWriter, sv reflect.Value) error {
|
|||
|
||||
// Extensions (the XXX_extensions field).
|
||||
pv := sv.Addr()
|
||||
if _, ok := extendable(pv.Interface()); ok {
|
||||
if _, err := extendable(pv.Interface()); err == nil {
|
||||
if err := tm.writeExtensions(w, pv); err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -464,26 +456,7 @@ func (tm *TextMarshaler) writeStruct(w *textWriter, sv reflect.Value) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
// writeRaw writes an uninterpreted raw message.
|
||||
func writeRaw(w *textWriter, b []byte) error {
|
||||
if err := w.WriteByte('<'); err != nil {
|
||||
return err
|
||||
}
|
||||
if !w.compact {
|
||||
if err := w.WriteByte('\n'); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
w.indent()
|
||||
if err := writeUnknownStruct(w, b); err != nil {
|
||||
return err
|
||||
}
|
||||
w.unindent()
|
||||
if err := w.WriteByte('>'); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
var textMarshalerType = reflect.TypeOf((*encoding.TextMarshaler)(nil)).Elem()
|
||||
|
||||
// writeAny writes an arbitrary field.
|
||||
func (tm *TextMarshaler) writeAny(w *textWriter, v reflect.Value, props *Properties) error {
|
||||
|
@ -535,16 +508,34 @@ func (tm *TextMarshaler) writeAny(w *textWriter, v reflect.Value, props *Propert
|
|||
}
|
||||
}
|
||||
w.indent()
|
||||
if etm, ok := v.Interface().(encoding.TextMarshaler); ok {
|
||||
text, err := etm.MarshalText()
|
||||
if v.CanAddr() {
|
||||
// Calling v.Interface on a struct causes the reflect package to
|
||||
// copy the entire struct. This is racy with the new Marshaler
|
||||
// since we atomically update the XXX_sizecache.
|
||||
//
|
||||
// Thus, we retrieve a pointer to the struct if possible to avoid
|
||||
// a race since v.Interface on the pointer doesn't copy the struct.
|
||||
//
|
||||
// If v is not addressable, then we are not worried about a race
|
||||
// since it implies that the binary Marshaler cannot possibly be
|
||||
// mutating this value.
|
||||
v = v.Addr()
|
||||
}
|
||||
if v.Type().Implements(textMarshalerType) {
|
||||
text, err := v.Interface().(encoding.TextMarshaler).MarshalText()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if _, err = w.Write(text); err != nil {
|
||||
return err
|
||||
}
|
||||
} else if err := tm.writeStruct(w, v); err != nil {
|
||||
return err
|
||||
} else {
|
||||
if v.Kind() == reflect.Ptr {
|
||||
v = v.Elem()
|
||||
}
|
||||
if err := tm.writeStruct(w, v); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
w.unindent()
|
||||
if err := w.WriteByte(ket); err != nil {
|
||||
|
|
|
@ -206,7 +206,6 @@ func (p *textParser) advance() {
|
|||
|
||||
var (
|
||||
errBadUTF8 = errors.New("proto: bad UTF-8")
|
||||
errBadHex = errors.New("proto: bad hexadecimal")
|
||||
)
|
||||
|
||||
func unquoteC(s string, quote rune) (string, error) {
|
||||
|
@ -277,60 +276,47 @@ func unescape(s string) (ch string, tail string, err error) {
|
|||
return "?", s, nil // trigraph workaround
|
||||
case '\'', '"', '\\':
|
||||
return string(r), s, nil
|
||||
case '0', '1', '2', '3', '4', '5', '6', '7', 'x', 'X':
|
||||
case '0', '1', '2', '3', '4', '5', '6', '7':
|
||||
if len(s) < 2 {
|
||||
return "", "", fmt.Errorf(`\%c requires 2 following digits`, r)
|
||||
}
|
||||
base := 8
|
||||
ss := s[:2]
|
||||
ss := string(r) + s[:2]
|
||||
s = s[2:]
|
||||
if r == 'x' || r == 'X' {
|
||||
base = 16
|
||||
} else {
|
||||
ss = string(r) + ss
|
||||
}
|
||||
i, err := strconv.ParseUint(ss, base, 8)
|
||||
i, err := strconv.ParseUint(ss, 8, 8)
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
return "", "", fmt.Errorf(`\%s contains non-octal digits`, ss)
|
||||
}
|
||||
return string([]byte{byte(i)}), s, nil
|
||||
case 'u', 'U':
|
||||
n := 4
|
||||
if r == 'U' {
|
||||
case 'x', 'X', 'u', 'U':
|
||||
var n int
|
||||
switch r {
|
||||
case 'x', 'X':
|
||||
n = 2
|
||||
case 'u':
|
||||
n = 4
|
||||
case 'U':
|
||||
n = 8
|
||||
}
|
||||
if len(s) < n {
|
||||
return "", "", fmt.Errorf(`\%c requires %d digits`, r, n)
|
||||
}
|
||||
|
||||
bs := make([]byte, n/2)
|
||||
for i := 0; i < n; i += 2 {
|
||||
a, ok1 := unhex(s[i])
|
||||
b, ok2 := unhex(s[i+1])
|
||||
if !ok1 || !ok2 {
|
||||
return "", "", errBadHex
|
||||
}
|
||||
bs[i/2] = a<<4 | b
|
||||
return "", "", fmt.Errorf(`\%c requires %d following digits`, r, n)
|
||||
}
|
||||
ss := s[:n]
|
||||
s = s[n:]
|
||||
return string(bs), s, nil
|
||||
i, err := strconv.ParseUint(ss, 16, 64)
|
||||
if err != nil {
|
||||
return "", "", fmt.Errorf(`\%c%s contains non-hexadecimal digits`, r, ss)
|
||||
}
|
||||
if r == 'x' || r == 'X' {
|
||||
return string([]byte{byte(i)}), s, nil
|
||||
}
|
||||
if i > utf8.MaxRune {
|
||||
return "", "", fmt.Errorf(`\%c%s is not a valid Unicode code point`, r, ss)
|
||||
}
|
||||
return string(i), s, nil
|
||||
}
|
||||
return "", "", fmt.Errorf(`unknown escape \%c`, r)
|
||||
}
|
||||
|
||||
// Adapted from src/pkg/strconv/quote.go.
|
||||
func unhex(b byte) (v byte, ok bool) {
|
||||
switch {
|
||||
case '0' <= b && b <= '9':
|
||||
return b - '0', true
|
||||
case 'a' <= b && b <= 'f':
|
||||
return b - 'a' + 10, true
|
||||
case 'A' <= b && b <= 'F':
|
||||
return b - 'A' + 10, true
|
||||
}
|
||||
return 0, false
|
||||
}
|
||||
|
||||
// Back off the parser by one token. Can only be done between calls to next().
|
||||
// It makes the next advance() a no-op.
|
||||
func (p *textParser) back() { p.backed = true }
|
||||
|
@ -644,17 +630,17 @@ func (p *textParser) readStruct(sv reflect.Value, terminator string) error {
|
|||
if err := p.consumeToken(":"); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := p.readAny(key, props.mkeyprop); err != nil {
|
||||
if err := p.readAny(key, props.MapKeyProp); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := p.consumeOptionalSeparator(); err != nil {
|
||||
return err
|
||||
}
|
||||
case "value":
|
||||
if err := p.checkForColon(props.mvalprop, dst.Type().Elem()); err != nil {
|
||||
if err := p.checkForColon(props.MapValProp, dst.Type().Elem()); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := p.readAny(val, props.mvalprop); err != nil {
|
||||
if err := p.readAny(val, props.MapValProp); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := p.consumeOptionalSeparator(); err != nil {
|
||||
|
@ -728,6 +714,9 @@ func (p *textParser) consumeExtName() (string, error) {
|
|||
if tok.err != nil {
|
||||
return "", p.errorf("unrecognized type_url or extension name: %s", tok.err)
|
||||
}
|
||||
if p.done && tok.value != "]" {
|
||||
return "", p.errorf("unclosed type_url or extension name")
|
||||
}
|
||||
}
|
||||
return strings.Join(parts, ""), nil
|
||||
}
|
||||
|
@ -865,7 +854,7 @@ func (p *textParser) readAny(v reflect.Value, props *Properties) error {
|
|||
return p.readStruct(fv, terminator)
|
||||
case reflect.Uint32:
|
||||
if x, err := strconv.ParseUint(tok.value, 0, 32); err == nil {
|
||||
fv.SetUint(x)
|
||||
fv.SetUint(uint64(x))
|
||||
return nil
|
||||
}
|
||||
case reflect.Uint64:
|
||||
|
@ -883,13 +872,9 @@ func (p *textParser) readAny(v reflect.Value, props *Properties) error {
|
|||
// UnmarshalText returns *RequiredNotSetError.
|
||||
func UnmarshalText(s string, pb Message) error {
|
||||
if um, ok := pb.(encoding.TextUnmarshaler); ok {
|
||||
err := um.UnmarshalText([]byte(s))
|
||||
return err
|
||||
return um.UnmarshalText([]byte(s))
|
||||
}
|
||||
pb.Reset()
|
||||
v := reflect.ValueOf(pb)
|
||||
if pe := newTextParser(s).readStruct(v.Elem(), ""); pe != nil {
|
||||
return pe
|
||||
}
|
||||
return nil
|
||||
return newTextParser(s).readStruct(v.Elem(), "")
|
||||
}
|
||||
|
|
|
@ -1,11 +1,21 @@
|
|||
package mountinfo
|
||||
|
||||
import "io"
|
||||
|
||||
// GetMounts retrieves a list of mounts for the current running process,
|
||||
// with an optional filter applied (use nil for no filter).
|
||||
func GetMounts(f FilterFunc) ([]*Info, error) {
|
||||
return parseMountTable(f)
|
||||
}
|
||||
|
||||
// GetMountsFromReader retrieves a list of mounts from the
|
||||
// reader provided, with an optional filter applied (use nil
|
||||
// for no filter). This can be useful in tests or benchmarks
|
||||
// that provide a fake mountinfo data.
|
||||
func GetMountsFromReader(reader io.Reader, f FilterFunc) ([]*Info, error) {
|
||||
return parseInfoFile(reader, f)
|
||||
}
|
||||
|
||||
// Mounted determines if a specified mountpoint has been mounted.
|
||||
// On Linux it looks at /proc/self/mountinfo.
|
||||
func Mounted(mountpoint string) (bool, error) {
|
||||
|
|
|
@ -3,10 +3,12 @@ package mountinfo
|
|||
import "strings"
|
||||
|
||||
// FilterFunc is a type defining a callback function for GetMount(),
|
||||
// used to filter out mountinfo entries we're not interested in.
|
||||
// used to filter out mountinfo entries we're not interested in,
|
||||
// and/or stop further processing if we found what we wanted.
|
||||
//
|
||||
// It takes a pointer to the Info struct (not fully populated,
|
||||
// currently only Mountpoint is filled in), and returns two booleans:
|
||||
// currently only Mountpoint, Fstype, Source, and (on Linux)
|
||||
// VfsOpts are filled in), and returns two booleans:
|
||||
//
|
||||
// - skip: true if the entry should be skipped
|
||||
// - stop: true if parsing should be stopped after the entry
|
||||
|
|
|
@ -34,6 +34,7 @@ func parseMountTable(filter FilterFunc) ([]*Info, error) {
|
|||
var skip, stop bool
|
||||
mountinfo.Mountpoint = C.GoString(&entry.f_mntonname[0])
|
||||
mountinfo.Fstype = C.GoString(&entry.f_fstypename[0])
|
||||
mountinfo.Source = C.GoString(&entry.f_mntfromname[0])
|
||||
|
||||
if filter != nil {
|
||||
// filter out entries we're not interested in
|
||||
|
@ -43,8 +44,6 @@ func parseMountTable(filter FilterFunc) ([]*Info, error) {
|
|||
}
|
||||
}
|
||||
|
||||
mountinfo.Source = C.GoString(&entry.f_mntfromname[0])
|
||||
|
||||
out = append(out, &mountinfo)
|
||||
if stop {
|
||||
break
|
||||
|
|
|
@ -71,12 +71,13 @@ func parseInfoFile(r io.Reader, filter FilterFunc) ([]*Info, error) {
|
|||
p := &Info{}
|
||||
|
||||
// Fill in the fields that a filter might check
|
||||
// (currently only Mountpoint and Fstype)
|
||||
p.Mountpoint, err = strconv.Unquote(`"` + fields[4] + `"`)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Parsing '%s' failed: unable to unquote mount point field: %w", fields[4], err)
|
||||
}
|
||||
p.Fstype = fields[sepIdx+1]
|
||||
p.Source = fields[sepIdx+2]
|
||||
p.VfsOpts = fields[sepIdx+3]
|
||||
|
||||
// Run a filter soon so we can skip parsing/adding entries
|
||||
// the caller is not interested in
|
||||
|
@ -117,9 +118,6 @@ func parseInfoFile(r io.Reader, filter FilterFunc) ([]*Info, error) {
|
|||
p.Optional = strings.Join(fields[6:sepIdx-1], " ")
|
||||
}
|
||||
|
||||
p.Source = fields[sepIdx+2]
|
||||
p.VfsOpts = fields[sepIdx+3]
|
||||
|
||||
out = append(out, p)
|
||||
if stop {
|
||||
break
|
||||
|
|
|
@ -4,9 +4,14 @@ package mountinfo
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"runtime"
|
||||
)
|
||||
|
||||
func parseMountTable(f FilterFunc) ([]*Info, error) {
|
||||
func parseMountTable(_ FilterFunc) ([]*Info, error) {
|
||||
return nil, fmt.Errorf("mount.parseMountTable is not implemented on %s/%s", runtime.GOOS, runtime.GOARCH)
|
||||
}
|
||||
|
||||
func parseInfoFile(_ io.Reader, f FilterFunc) ([]*Info, error) {
|
||||
return parseMountTable(f)
|
||||
}
|
||||
|
|
|
@ -1,6 +1,12 @@
|
|||
package mountinfo
|
||||
|
||||
func parseMountTable(f FilterFunc) ([]*Info, error) {
|
||||
import "io"
|
||||
|
||||
func parseMountTable(_ FilterFunc) ([]*Info, error) {
|
||||
// Do NOT return an error!
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func parseInfoFile(_ io.Reader, f FilterFunc) ([]*Info, error) {
|
||||
return parseMountTable(f)
|
||||
}
|
||||
|
|
|
@ -89,6 +89,8 @@ type User struct {
|
|||
UID uint32 `json:"uid" platform:"linux,solaris"`
|
||||
// GID is the group id.
|
||||
GID uint32 `json:"gid" platform:"linux,solaris"`
|
||||
// Umask is the umask for the init process.
|
||||
Umask uint32 `json:"umask,omitempty" platform:"linux,solaris"`
|
||||
// AdditionalGids are additional group ids set for the container's process.
|
||||
AdditionalGids []uint32 `json:"additionalGids,omitempty" platform:"linux,solaris"`
|
||||
// Username is the user name.
|
||||
|
@ -123,13 +125,26 @@ type Hook struct {
|
|||
Timeout *int `json:"timeout,omitempty"`
|
||||
}
|
||||
|
||||
// Hooks specifies a command that is run in the container at a particular event in the lifecycle of a container
|
||||
// Hooks for container setup and teardown
|
||||
type Hooks struct {
|
||||
// Prestart is a list of hooks to be run before the container process is executed.
|
||||
// Prestart is Deprecated. Prestart is a list of hooks to be run before the container process is executed.
|
||||
// It is called in the Runtime Namespace
|
||||
Prestart []Hook `json:"prestart,omitempty"`
|
||||
// CreateRuntime is a list of hooks to be run after the container has been created but before pivot_root or any equivalent operation has been called
|
||||
// It is called in the Runtime Namespace
|
||||
CreateRuntime []Hook `json:"createRuntime,omitempty"`
|
||||
// CreateContainer is a list of hooks to be run after the container has been created but before pivot_root or any equivalent operation has been called
|
||||
// It is called in the Container Namespace
|
||||
CreateContainer []Hook `json:"createContainer,omitempty"`
|
||||
// StartContainer is a list of hooks to be run after the start operation is called but before the container process is started
|
||||
// It is called in the Container Namespace
|
||||
StartContainer []Hook `json:"startContainer,omitempty"`
|
||||
// Poststart is a list of hooks to be run after the container process is started.
|
||||
// It is called in the Runtime Namespace
|
||||
Poststart []Hook `json:"poststart,omitempty"`
|
||||
// Poststop is a list of hooks to be run after the container process exits.
|
||||
// It is called in the Runtime Namespace
|
||||
Poststop []Hook `json:"poststop,omitempty"`
|
||||
}
|
||||
|
||||
|
@ -165,6 +180,8 @@ type Linux struct {
|
|||
// IntelRdt contains Intel Resource Director Technology (RDT) information for
|
||||
// handling resource constraints (e.g., L3 cache, memory bandwidth) for the container
|
||||
IntelRdt *LinuxIntelRdt `json:"intelRdt,omitempty"`
|
||||
// Personality contains configuration for the Linux personality syscall
|
||||
Personality *LinuxPersonality `json:"personality,omitempty"`
|
||||
}
|
||||
|
||||
// LinuxNamespace is the configuration for a Linux namespace
|
||||
|
@ -183,17 +200,17 @@ const (
|
|||
// PIDNamespace for isolating process IDs
|
||||
PIDNamespace LinuxNamespaceType = "pid"
|
||||
// NetworkNamespace for isolating network devices, stacks, ports, etc
|
||||
NetworkNamespace = "network"
|
||||
NetworkNamespace LinuxNamespaceType = "network"
|
||||
// MountNamespace for isolating mount points
|
||||
MountNamespace = "mount"
|
||||
MountNamespace LinuxNamespaceType = "mount"
|
||||
// IPCNamespace for isolating System V IPC, POSIX message queues
|
||||
IPCNamespace = "ipc"
|
||||
IPCNamespace LinuxNamespaceType = "ipc"
|
||||
// UTSNamespace for isolating hostname and NIS domain name
|
||||
UTSNamespace = "uts"
|
||||
UTSNamespace LinuxNamespaceType = "uts"
|
||||
// UserNamespace for isolating user and group IDs
|
||||
UserNamespace = "user"
|
||||
UserNamespace LinuxNamespaceType = "user"
|
||||
// CgroupNamespace for isolating cgroup hierarchies
|
||||
CgroupNamespace = "cgroup"
|
||||
CgroupNamespace LinuxNamespaceType = "cgroup"
|
||||
)
|
||||
|
||||
// LinuxIDMapping specifies UID/GID mappings
|
||||
|
@ -219,6 +236,7 @@ type POSIXRlimit struct {
|
|||
// LinuxHugepageLimit structure corresponds to limiting kernel hugepages
|
||||
type LinuxHugepageLimit struct {
|
||||
// Pagesize is the hugepage size
|
||||
// Format: "<size><unit-prefix>B' (e.g. 64KB, 2MB, 1GB, etc.)
|
||||
Pagesize string `json:"pageSize"`
|
||||
// Limit is the limit of "hugepagesize" hugetlb usage
|
||||
Limit uint64 `json:"limit"`
|
||||
|
@ -290,6 +308,8 @@ type LinuxMemory struct {
|
|||
Swappiness *uint64 `json:"swappiness,omitempty"`
|
||||
// DisableOOMKiller disables the OOM killer for out of memory conditions
|
||||
DisableOOMKiller *bool `json:"disableOOMKiller,omitempty"`
|
||||
// Enables hierarchical memory accounting
|
||||
UseHierarchy *bool `json:"useHierarchy,omitempty"`
|
||||
}
|
||||
|
||||
// LinuxCPU for Linux cgroup 'cpu' resource management
|
||||
|
@ -386,6 +406,28 @@ type LinuxDeviceCgroup struct {
|
|||
Access string `json:"access,omitempty"`
|
||||
}
|
||||
|
||||
// LinuxPersonalityDomain refers to a personality domain.
|
||||
type LinuxPersonalityDomain string
|
||||
|
||||
// LinuxPersonalityFlag refers to an additional personality flag. None are currently defined.
|
||||
type LinuxPersonalityFlag string
|
||||
|
||||
// Define domain and flags for Personality
|
||||
const (
|
||||
// PerLinux is the standard Linux personality
|
||||
PerLinux LinuxPersonalityDomain = "LINUX"
|
||||
// PerLinux32 sets personality to 32 bit
|
||||
PerLinux32 LinuxPersonalityDomain = "LINUX32"
|
||||
)
|
||||
|
||||
// LinuxPersonality represents the Linux personality syscall input
|
||||
type LinuxPersonality struct {
|
||||
// Domain for the personality
|
||||
Domain LinuxPersonalityDomain `json:"domain"`
|
||||
// Additional flags
|
||||
Flags []LinuxPersonalityFlag `json:"flags,omitempty"`
|
||||
}
|
||||
|
||||
// Solaris contains platform-specific configuration for Solaris application containers.
|
||||
type Solaris struct {
|
||||
// SMF FMRI which should go "online" before we start the container process.
|
||||
|
@ -555,12 +597,16 @@ type VMImage struct {
|
|||
type LinuxSeccomp struct {
|
||||
DefaultAction LinuxSeccompAction `json:"defaultAction"`
|
||||
Architectures []Arch `json:"architectures,omitempty"`
|
||||
Flags []LinuxSeccompFlag `json:"flags,omitempty"`
|
||||
Syscalls []LinuxSyscall `json:"syscalls,omitempty"`
|
||||
}
|
||||
|
||||
// Arch used for additional architectures
|
||||
type Arch string
|
||||
|
||||
// LinuxSeccompFlag is a flag to pass to seccomp(2).
|
||||
type LinuxSeccompFlag string
|
||||
|
||||
// Additional architectures permitted to be used for system calls
|
||||
// By default only the native architecture of the kernel is permitted
|
||||
const (
|
||||
|
@ -594,6 +640,7 @@ const (
|
|||
ActErrno LinuxSeccompAction = "SCMP_ACT_ERRNO"
|
||||
ActTrace LinuxSeccompAction = "SCMP_ACT_TRACE"
|
||||
ActAllow LinuxSeccompAction = "SCMP_ACT_ALLOW"
|
||||
ActLog LinuxSeccompAction = "SCMP_ACT_LOG"
|
||||
)
|
||||
|
||||
// LinuxSeccompOperator used to match syscall arguments in Seccomp
|
||||
|
|
|
@ -8,10 +8,10 @@ const (
|
|||
// VersionMinor is for functionality in a backwards-compatible manner
|
||||
VersionMinor = 0
|
||||
// VersionPatch is for backwards-compatible bug fixes
|
||||
VersionPatch = 1
|
||||
VersionPatch = 2
|
||||
|
||||
// VersionDev indicates development branch. Releases will be empty string.
|
||||
VersionDev = "-dev"
|
||||
VersionDev = ""
|
||||
)
|
||||
|
||||
// Version is the specification version that the package types support.
|
||||
|
|
|
@ -1,109 +1,77 @@
|
|||
// +build !selinux !linux
|
||||
|
||||
package label
|
||||
|
||||
// InitLabels returns the process label and file labels to be used within
|
||||
// the container. A list of options can be passed into this function to alter
|
||||
// the labels.
|
||||
func InitLabels(options []string) (string, string, error) {
|
||||
return "", "", nil
|
||||
}
|
||||
import (
|
||||
"github.com/opencontainers/selinux/go-selinux"
|
||||
)
|
||||
|
||||
func ROMountLabel() string {
|
||||
return ""
|
||||
}
|
||||
// Deprecated: use selinux.ROFileLabel
|
||||
var ROMountLabel = selinux.ROFileLabel
|
||||
|
||||
func GenLabels(options string) (string, string, error) {
|
||||
return "", "", nil
|
||||
}
|
||||
// SetProcessLabel takes a process label and tells the kernel to assign the
|
||||
// label to the next program executed by the current process.
|
||||
// Deprecated: use selinux.SetExecLabel
|
||||
var SetProcessLabel = selinux.SetExecLabel
|
||||
|
||||
func FormatMountLabel(src string, mountLabel string) string {
|
||||
return src
|
||||
}
|
||||
// ProcessLabel returns the process label that the kernel will assign
|
||||
// to the next program executed by the current process. If "" is returned
|
||||
// this indicates that the default labeling will happen for the process.
|
||||
// Deprecated: use selinux.ExecLabel
|
||||
var ProcessLabel = selinux.ExecLabel
|
||||
|
||||
func SetProcessLabel(processLabel string) error {
|
||||
return nil
|
||||
}
|
||||
// SetSocketLabel takes a process label and tells the kernel to assign the
|
||||
// label to the next socket that gets created
|
||||
// Deprecated: use selinux.SetSocketLabel
|
||||
var SetSocketLabel = selinux.SetSocketLabel
|
||||
|
||||
func ProcessLabel() (string, error) {
|
||||
return "", nil
|
||||
}
|
||||
// SocketLabel retrieves the current default socket label setting
|
||||
// Deprecated: use selinux.SocketLabel
|
||||
var SocketLabel = selinux.SocketLabel
|
||||
|
||||
func SetSocketLabel(processLabel string) error {
|
||||
return nil
|
||||
}
|
||||
// SetKeyLabel takes a process label and tells the kernel to assign the
|
||||
// label to the next kernel keyring that gets created
|
||||
// Deprecated: use selinux.SetKeyLabel
|
||||
var SetKeyLabel = selinux.SetKeyLabel
|
||||
|
||||
func SocketLabel() (string, error) {
|
||||
return "", nil
|
||||
}
|
||||
// KeyLabel retrieves the current default kernel keyring label setting
|
||||
// Deprecated: use selinux.KeyLabel
|
||||
var KeyLabel = selinux.KeyLabel
|
||||
|
||||
func SetKeyLabel(processLabel string) error {
|
||||
return nil
|
||||
}
|
||||
// FileLabel returns the label for specified path
|
||||
// Deprecated: use selinux.FileLabel
|
||||
var FileLabel = selinux.FileLabel
|
||||
|
||||
func KeyLabel() (string, error) {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
func FileLabel(path string) (string, error) {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
func SetFileLabel(path string, fileLabel string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func SetFileCreateLabel(fileLabel string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func Relabel(path string, fileLabel string, shared bool) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func PidLabel(pid int) (string, error) {
|
||||
return "", nil
|
||||
}
|
||||
// PidLabel will return the label of the process running with the specified pid
|
||||
// Deprecated: use selinux.PidLabel
|
||||
var PidLabel = selinux.PidLabel
|
||||
|
||||
// Init initialises the labeling system
|
||||
func Init() {
|
||||
selinux.GetEnabled()
|
||||
}
|
||||
|
||||
// ClearLabels clears all reserved labels
|
||||
func ClearLabels() {
|
||||
return
|
||||
}
|
||||
// ClearLabels will clear all reserved labels
|
||||
// Deprecated: use selinux.ClearLabels
|
||||
var ClearLabels = selinux.ClearLabels
|
||||
|
||||
// ReserveLabel will record the fact that the MCS label has already been used.
|
||||
// This will prevent InitLabels from using the MCS label in a newly created
|
||||
// container
|
||||
// Deprecated: use selinux.ReserveLabel
|
||||
func ReserveLabel(label string) error {
|
||||
selinux.ReserveLabel(label)
|
||||
return nil
|
||||
}
|
||||
|
||||
// ReleaseLabel will remove the reservation of the MCS label.
|
||||
// This will allow InitLabels to use the MCS label in a newly created
|
||||
// containers
|
||||
// Deprecated: use selinux.ReleaseLabel
|
||||
func ReleaseLabel(label string) error {
|
||||
selinux.ReleaseLabel(label)
|
||||
return nil
|
||||
}
|
||||
|
||||
// DupSecOpt takes a process label and returns security options that
|
||||
// can be used to set duplicate labels on future container processes
|
||||
func DupSecOpt(src string) ([]string, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// DisableSecOpt returns a security opt that can disable labeling
|
||||
// support for future container processes
|
||||
func DisableSecOpt() []string {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Validate checks that the label does not include unexpected options
|
||||
func Validate(label string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// RelabelNeeded checks whether the user requested a relabel
|
||||
func RelabelNeeded(label string) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// IsShared checks that the label includes a "shared" mark
|
||||
func IsShared(label string) bool {
|
||||
return false
|
||||
}
|
||||
// Deprecated: use selinux.DupSecOpt
|
||||
var DupSecOpt = selinux.DupSecOpt
|
||||
|
|
|
@ -9,6 +9,7 @@ import (
|
|||
"strings"
|
||||
|
||||
"github.com/opencontainers/selinux/go-selinux"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
// Valid Label Options
|
||||
|
@ -21,7 +22,7 @@ var validOptions = map[string]bool{
|
|||
"level": true,
|
||||
}
|
||||
|
||||
var ErrIncompatibleLabel = fmt.Errorf("Bad SELinux option z and Z can not be used together")
|
||||
var ErrIncompatibleLabel = errors.New("Bad SELinux option z and Z can not be used together")
|
||||
|
||||
// InitLabels returns the process label and file labels to be used within
|
||||
// the container. A list of options can be passed into this function to alter
|
||||
|
@ -35,7 +36,7 @@ func InitLabels(options []string) (plabel string, mlabel string, Err error) {
|
|||
if processLabel != "" {
|
||||
defer func() {
|
||||
if Err != nil {
|
||||
ReleaseLabel(mountLabel)
|
||||
selinux.ReleaseLabel(mountLabel)
|
||||
}
|
||||
}()
|
||||
pcon, err := selinux.NewContext(processLabel)
|
||||
|
@ -52,11 +53,11 @@ func InitLabels(options []string) (plabel string, mlabel string, Err error) {
|
|||
return "", mountLabel, nil
|
||||
}
|
||||
if i := strings.Index(opt, ":"); i == -1 {
|
||||
return "", "", fmt.Errorf("Bad label option %q, valid options 'disable' or \n'user, role, level, type, filetype' followed by ':' and a value", opt)
|
||||
return "", "", errors.Errorf("Bad label option %q, valid options 'disable' or \n'user, role, level, type, filetype' followed by ':' and a value", opt)
|
||||
}
|
||||
con := strings.SplitN(opt, ":", 2)
|
||||
if !validOptions[con[0]] {
|
||||
return "", "", fmt.Errorf("Bad label option %q, valid options 'disable, user, role, level, type, filetype'", con[0])
|
||||
return "", "", errors.Errorf("Bad label option %q, valid options 'disable, user, role, level, type, filetype'", con[0])
|
||||
|
||||
}
|
||||
if con[0] == "filetype" {
|
||||
|
@ -67,19 +68,16 @@ func InitLabels(options []string) (plabel string, mlabel string, Err error) {
|
|||
mcon[con[0]] = con[1]
|
||||
}
|
||||
}
|
||||
_ = ReleaseLabel(processLabel)
|
||||
selinux.ReleaseLabel(processLabel)
|
||||
processLabel = pcon.Get()
|
||||
mountLabel = mcon.Get()
|
||||
_ = ReserveLabel(processLabel)
|
||||
selinux.ReserveLabel(processLabel)
|
||||
}
|
||||
return processLabel, mountLabel, nil
|
||||
}
|
||||
|
||||
func ROMountLabel() string {
|
||||
return selinux.ROFileLabel()
|
||||
}
|
||||
|
||||
// DEPRECATED: The GenLabels function is only to be used during the transition to the official API.
|
||||
// Deprecated: The GenLabels function is only to be used during the transition
|
||||
// to the official API. Use InitLabels(strings.Fields(options)) instead.
|
||||
func GenLabels(options string) (string, string, error) {
|
||||
return InitLabels(strings.Fields(options))
|
||||
}
|
||||
|
@ -102,71 +100,27 @@ func FormatMountLabel(src, mountLabel string) string {
|
|||
return src
|
||||
}
|
||||
|
||||
// SetProcessLabel takes a process label and tells the kernel to assign the
|
||||
// label to the next program executed by the current process.
|
||||
func SetProcessLabel(processLabel string) error {
|
||||
return selinux.SetExecLabel(processLabel)
|
||||
}
|
||||
|
||||
// SetSocketLabel takes a process label and tells the kernel to assign the
|
||||
// label to the next socket that gets created
|
||||
func SetSocketLabel(processLabel string) error {
|
||||
return selinux.SetSocketLabel(processLabel)
|
||||
}
|
||||
|
||||
// SocketLabel retrieves the current default socket label setting
|
||||
func SocketLabel() (string, error) {
|
||||
return selinux.SocketLabel()
|
||||
}
|
||||
|
||||
// SetKeyLabel takes a process label and tells the kernel to assign the
|
||||
// label to the next kernel keyring that gets created
|
||||
func SetKeyLabel(processLabel string) error {
|
||||
return selinux.SetKeyLabel(processLabel)
|
||||
}
|
||||
|
||||
// KeyLabel retrieves the current default kernel keyring label setting
|
||||
func KeyLabel() (string, error) {
|
||||
return selinux.KeyLabel()
|
||||
}
|
||||
|
||||
// ProcessLabel returns the process label that the kernel will assign
|
||||
// to the next program executed by the current process. If "" is returned
|
||||
// this indicates that the default labeling will happen for the process.
|
||||
func ProcessLabel() (string, error) {
|
||||
return selinux.ExecLabel()
|
||||
}
|
||||
|
||||
// FileLabel returns the label for specified path
|
||||
func FileLabel(path string) (string, error) {
|
||||
return selinux.FileLabel(path)
|
||||
}
|
||||
|
||||
// SetFileLabel modifies the "path" label to the specified file label
|
||||
func SetFileLabel(path string, fileLabel string) error {
|
||||
if selinux.GetEnabled() && fileLabel != "" {
|
||||
return selinux.SetFileLabel(path, fileLabel)
|
||||
if !selinux.GetEnabled() || fileLabel == "" {
|
||||
return nil
|
||||
}
|
||||
return nil
|
||||
return selinux.SetFileLabel(path, fileLabel)
|
||||
}
|
||||
|
||||
// SetFileCreateLabel tells the kernel the label for all files to be created
|
||||
func SetFileCreateLabel(fileLabel string) error {
|
||||
if selinux.GetEnabled() {
|
||||
return selinux.SetFSCreateLabel(fileLabel)
|
||||
if !selinux.GetEnabled() {
|
||||
return nil
|
||||
}
|
||||
return nil
|
||||
return selinux.SetFSCreateLabel(fileLabel)
|
||||
}
|
||||
|
||||
// Relabel changes the label of path to the filelabel string.
|
||||
// It changes the MCS label to s0 if shared is true.
|
||||
// This will allow all containers to share the content.
|
||||
func Relabel(path string, fileLabel string, shared bool) error {
|
||||
if !selinux.GetEnabled() {
|
||||
return nil
|
||||
}
|
||||
|
||||
if fileLabel == "" {
|
||||
if !selinux.GetEnabled() || fileLabel == "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@ -211,7 +165,7 @@ func Relabel(path string, fileLabel string, shared bool) error {
|
|||
path = strings.TrimSuffix(path, "/")
|
||||
}
|
||||
if exclude_paths[path] {
|
||||
return fmt.Errorf("SELinux relabeling of %s is not allowed", path)
|
||||
return errors.Errorf("SELinux relabeling of %s is not allowed", path)
|
||||
}
|
||||
|
||||
if shared {
|
||||
|
@ -229,48 +183,10 @@ func Relabel(path string, fileLabel string, shared bool) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
// PidLabel will return the label of the process running with the specified pid
|
||||
func PidLabel(pid int) (string, error) {
|
||||
return selinux.PidLabel(pid)
|
||||
}
|
||||
|
||||
// Init initialises the labeling system
|
||||
func Init() {
|
||||
selinux.GetEnabled()
|
||||
}
|
||||
|
||||
// ClearLabels will clear all reserved labels
|
||||
func ClearLabels() {
|
||||
selinux.ClearLabels()
|
||||
}
|
||||
|
||||
// ReserveLabel will record the fact that the MCS label has already been used.
|
||||
// This will prevent InitLabels from using the MCS label in a newly created
|
||||
// container
|
||||
func ReserveLabel(label string) error {
|
||||
selinux.ReserveLabel(label)
|
||||
return nil
|
||||
}
|
||||
|
||||
// ReleaseLabel will remove the reservation of the MCS label.
|
||||
// This will allow InitLabels to use the MCS label in a newly created
|
||||
// containers
|
||||
func ReleaseLabel(label string) error {
|
||||
selinux.ReleaseLabel(label)
|
||||
return nil
|
||||
}
|
||||
|
||||
// DupSecOpt takes a process label and returns security options that
|
||||
// can be used to set duplicate labels on future container processes
|
||||
func DupSecOpt(src string) ([]string, error) {
|
||||
return selinux.DupSecOpt(src)
|
||||
}
|
||||
|
||||
// DisableSecOpt returns a security opt that can disable labeling
|
||||
// support for future container processes
|
||||
func DisableSecOpt() []string {
|
||||
return selinux.DisableSecOpt()
|
||||
}
|
||||
// Deprecated: use selinux.DisableSecOpt
|
||||
var DisableSecOpt = selinux.DisableSecOpt
|
||||
|
||||
// Validate checks that the label does not include unexpected options
|
||||
func Validate(label string) error {
|
||||
|
|
54
vendor/github.com/opencontainers/selinux/go-selinux/label/label_stub.go
generated
vendored
Normal file
54
vendor/github.com/opencontainers/selinux/go-selinux/label/label_stub.go
generated
vendored
Normal file
|
@ -0,0 +1,54 @@
|
|||
// +build !selinux !linux
|
||||
|
||||
package label
|
||||
|
||||
// InitLabels returns the process label and file labels to be used within
|
||||
// the container. A list of options can be passed into this function to alter
|
||||
// the labels.
|
||||
func InitLabels(options []string) (string, string, error) {
|
||||
return "", "", nil
|
||||
}
|
||||
|
||||
// Deprecated: The GenLabels function is only to be used during the transition
|
||||
// to the official API. Use InitLabels(strings.Fields(options)) instead.
|
||||
func GenLabels(options string) (string, string, error) {
|
||||
return "", "", nil
|
||||
}
|
||||
|
||||
func FormatMountLabel(src string, mountLabel string) string {
|
||||
return src
|
||||
}
|
||||
|
||||
func SetFileLabel(path string, fileLabel string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func SetFileCreateLabel(fileLabel string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func Relabel(path string, fileLabel string, shared bool) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// DisableSecOpt returns a security opt that can disable labeling
|
||||
// support for future container processes
|
||||
func DisableSecOpt() []string {
|
||||
// TODO the selinux.DisableSecOpt stub returns []string{"disable"} instead of "nil"
|
||||
return nil
|
||||
}
|
||||
|
||||
// Validate checks that the label does not include unexpected options
|
||||
func Validate(label string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// RelabelNeeded checks whether the user requested a relabel
|
||||
func RelabelNeeded(label string) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// IsShared checks that the label includes a "shared" mark
|
||||
func IsShared(label string) bool {
|
||||
return false
|
||||
}
|
|
@ -17,8 +17,8 @@ import (
|
|||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"syscall"
|
||||
|
||||
"github.com/opencontainers/selinux/pkg/pwalk"
|
||||
"github.com/pkg/errors"
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
@ -37,7 +37,6 @@ const (
|
|||
selinuxTypeTag = "SELINUXTYPE"
|
||||
selinuxTag = "SELINUX"
|
||||
xattrNameSelinux = "security.selinux"
|
||||
stRdOnly = 0x01
|
||||
)
|
||||
|
||||
type selinuxState struct {
|
||||
|
@ -103,13 +102,13 @@ func SetDisabled() {
|
|||
}
|
||||
|
||||
func verifySELinuxfsMount(mnt string) bool {
|
||||
var buf syscall.Statfs_t
|
||||
var buf unix.Statfs_t
|
||||
for {
|
||||
err := syscall.Statfs(mnt, &buf)
|
||||
err := unix.Statfs(mnt, &buf)
|
||||
if err == nil {
|
||||
break
|
||||
}
|
||||
if err == syscall.EAGAIN {
|
||||
if err == unix.EAGAIN {
|
||||
continue
|
||||
}
|
||||
return false
|
||||
|
@ -118,7 +117,7 @@ func verifySELinuxfsMount(mnt string) bool {
|
|||
if uint32(buf.Type) != uint32(unix.SELINUX_MAGIC) {
|
||||
return false
|
||||
}
|
||||
if (buf.Flags & stRdOnly) != 0 {
|
||||
if (buf.Flags & unix.ST_RDONLY) != 0 {
|
||||
return false
|
||||
}
|
||||
|
||||
|
@ -251,10 +250,10 @@ func isProcHandle(fh *os.File) error {
|
|||
var buf unix.Statfs_t
|
||||
err := unix.Fstatfs(int(fh.Fd()), &buf)
|
||||
if err != nil {
|
||||
return fmt.Errorf("statfs(%q) failed: %v", fh.Name(), err)
|
||||
return errors.Wrapf(err, "statfs(%q) failed", fh.Name())
|
||||
}
|
||||
if buf.Type != unix.PROC_SUPER_MAGIC {
|
||||
return fmt.Errorf("file %q is not on procfs", fh.Name())
|
||||
return errors.Errorf("file %q is not on procfs", fh.Name())
|
||||
}
|
||||
|
||||
return nil
|
||||
|
@ -282,12 +281,29 @@ func readCon(fpath string) (string, error) {
|
|||
return strings.Trim(retval, "\x00"), nil
|
||||
}
|
||||
|
||||
// ClassIndex returns the int index for an object class in the loaded policy, or -1 and an error
|
||||
func ClassIndex(class string) (int, error) {
|
||||
permpath := fmt.Sprintf("class/%s/index", class)
|
||||
indexpath := filepath.Join(getSelinuxMountPoint(), permpath)
|
||||
|
||||
indexB, err := ioutil.ReadFile(indexpath)
|
||||
if err != nil {
|
||||
return -1, err
|
||||
}
|
||||
index, err := strconv.Atoi(string(indexB))
|
||||
if err != nil {
|
||||
return -1, err
|
||||
}
|
||||
|
||||
return index, nil
|
||||
}
|
||||
|
||||
// SetFileLabel sets the SELinux label for this path or returns an error.
|
||||
func SetFileLabel(fpath string, label string) error {
|
||||
if fpath == "" {
|
||||
return ErrEmptyPath
|
||||
}
|
||||
if err := lsetxattr(fpath, xattrNameSelinux, []byte(label), 0); err != nil {
|
||||
if err := unix.Lsetxattr(fpath, xattrNameSelinux, []byte(label), 0); err != nil {
|
||||
return errors.Wrapf(err, "failed to set file label on %s", fpath)
|
||||
}
|
||||
return nil
|
||||
|
@ -390,7 +406,7 @@ func attrPath(attr string) string {
|
|||
return path.Join(threadSelfPrefix, attr)
|
||||
}
|
||||
|
||||
return path.Join("/proc/self/task/", strconv.Itoa(syscall.Gettid()), "/attr/", attr)
|
||||
return path.Join("/proc/self/task/", strconv.Itoa(unix.Gettid()), "/attr/", attr)
|
||||
}
|
||||
|
||||
func readAttr(attr string) (string, error) {
|
||||
|
@ -410,6 +426,18 @@ func CanonicalizeContext(val string) (string, error) {
|
|||
return readWriteCon(filepath.Join(getSelinuxMountPoint(), "context"), val)
|
||||
}
|
||||
|
||||
/*
|
||||
ComputeCreateContext requests the type transition from source to target for class from the kernel.
|
||||
*/
|
||||
func ComputeCreateContext(source string, target string, class string) (string, error) {
|
||||
classidx, err := ClassIndex(class)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return readWriteCon(filepath.Join(getSelinuxMountPoint(), "create"), fmt.Sprintf("%s %s %d", source, target, classidx))
|
||||
}
|
||||
|
||||
func readWriteCon(fpath string, val string) (string, error) {
|
||||
if fpath == "" {
|
||||
return "", ErrEmptyPath
|
||||
|
@ -461,17 +489,17 @@ func SocketLabel() (string, error) {
|
|||
|
||||
// PeerLabel retrieves the label of the client on the other side of a socket
|
||||
func PeerLabel(fd uintptr) (string, error) {
|
||||
return unix.GetsockoptString(int(fd), syscall.SOL_SOCKET, syscall.SO_PEERSEC)
|
||||
return unix.GetsockoptString(int(fd), unix.SOL_SOCKET, unix.SO_PEERSEC)
|
||||
}
|
||||
|
||||
// SetKeyLabel takes a process label and tells the kernel to assign the
|
||||
// label to the next kernel keyring that gets created
|
||||
func SetKeyLabel(label string) error {
|
||||
err := writeCon("/proc/self/attr/keycreate", label)
|
||||
if os.IsNotExist(err) {
|
||||
if os.IsNotExist(errors.Cause(err)) {
|
||||
return nil
|
||||
}
|
||||
if label == "" && os.IsPermission(err) {
|
||||
if label == "" && os.IsPermission(errors.Cause(err)) {
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
|
@ -772,14 +800,14 @@ func badPrefix(fpath string) error {
|
|||
badPrefixes := []string{"/usr"}
|
||||
for _, prefix := range badPrefixes {
|
||||
if strings.HasPrefix(fpath, prefix) {
|
||||
return fmt.Errorf("relabeling content in %s is not allowed", prefix)
|
||||
return errors.Errorf("relabeling content in %s is not allowed", prefix)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Chcon changes the `fpath` file object to the SELinux label `label`.
|
||||
// If `fpath` is a directory and `recurse`` is true, Chcon will walk the
|
||||
// Chcon changes the fpath file object to the SELinux label label.
|
||||
// If fpath is a directory and recurse is true, Chcon will walk the
|
||||
// directory tree setting the label.
|
||||
func Chcon(fpath string, label string, recurse bool) error {
|
||||
if fpath == "" {
|
||||
|
@ -791,19 +819,19 @@ func Chcon(fpath string, label string, recurse bool) error {
|
|||
if err := badPrefix(fpath); err != nil {
|
||||
return err
|
||||
}
|
||||
callback := func(p string, info os.FileInfo, err error) error {
|
||||
|
||||
if !recurse {
|
||||
return SetFileLabel(fpath, label)
|
||||
}
|
||||
|
||||
return pwalk.Walk(fpath, func(p string, info os.FileInfo, err error) error {
|
||||
e := SetFileLabel(p, label)
|
||||
if os.IsNotExist(e) {
|
||||
// Walk a file tree can race with removal, so ignore ENOENT
|
||||
if os.IsNotExist(errors.Cause(e)) {
|
||||
return nil
|
||||
}
|
||||
return e
|
||||
}
|
||||
|
||||
if recurse {
|
||||
return filepath.Walk(fpath, callback)
|
||||
}
|
||||
|
||||
return SetFileLabel(fpath, label)
|
||||
})
|
||||
}
|
||||
|
||||
// DupSecOpt takes an SELinux process label and returns security options that
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
// +build !selinux
|
||||
// +build !selinux !linux
|
||||
|
||||
package selinux
|
||||
|
||||
|
@ -35,6 +35,11 @@ func GetEnabled() bool {
|
|||
return false
|
||||
}
|
||||
|
||||
// ClassIndex returns the int index for an object class in the loaded policy, or -1 and an error
|
||||
func ClassIndex(class string) (int, error) {
|
||||
return -1, nil
|
||||
}
|
||||
|
||||
// SetFileLabel sets the SELinux label for this path or returns an error.
|
||||
func SetFileLabel(fpath string, label string) error {
|
||||
return nil
|
||||
|
@ -88,6 +93,13 @@ func CanonicalizeContext(val string) (string, error) {
|
|||
return "", nil
|
||||
}
|
||||
|
||||
/*
|
||||
ComputeCreateContext requests the type transition from source to target for class from the kernel.
|
||||
*/
|
||||
func ComputeCreateContext(source string, target string, class string) (string, error) {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
/*
|
||||
SetExecLabel sets the SELinux label that the kernel will use for any programs
|
||||
that are executed by the current process thread, or an error.
|
||||
|
|
|
@ -12,8 +12,8 @@ func lgetxattr(path string, attr string) ([]byte, error) {
|
|||
// Start with a 128 length byte array
|
||||
dest := make([]byte, 128)
|
||||
sz, errno := unix.Lgetxattr(path, attr, dest)
|
||||
if errno == unix.ERANGE {
|
||||
// Buffer too small, get the real size first
|
||||
for errno == unix.ERANGE {
|
||||
// Buffer too small, use zero-sized buffer to get the actual size
|
||||
sz, errno = unix.Lgetxattr(path, attr, []byte{})
|
||||
if errno != nil {
|
||||
return nil, errno
|
||||
|
@ -28,7 +28,3 @@ func lgetxattr(path string, attr string) ([]byte, error) {
|
|||
|
||||
return dest[:sz], nil
|
||||
}
|
||||
|
||||
func lsetxattr(path string, attr string, data []byte, flags int) error {
|
||||
return unix.Lsetxattr(path, attr, data, flags)
|
||||
}
|
||||
|
|
|
@ -0,0 +1,42 @@
|
|||
## pwalk: parallel implementation of filepath.Walk
|
||||
|
||||
This is a wrapper for [filepath.Walk](https://pkg.go.dev/path/filepath?tab=doc#Walk)
|
||||
which may speed it up by calling multiple callback functions (WalkFunc) in parallel,
|
||||
utilizing goroutines.
|
||||
|
||||
By default, it utilizes 2\*runtime.NumCPU() goroutines for callbacks.
|
||||
This can be changed by using WalkN function which has the additional
|
||||
parameter, specifying the number of goroutines (concurrency).
|
||||
|
||||
### Caveats
|
||||
|
||||
Please note the following limitations of this code:
|
||||
|
||||
* Unlike filepath.Walk, the order of calls is non-deterministic;
|
||||
|
||||
* Only primitive error handling is supported:
|
||||
|
||||
* filepath.SkipDir is not supported;
|
||||
|
||||
* no errors are ever passed to WalkFunc;
|
||||
|
||||
* once any error is returned from any WalkFunc instance, no more new calls
|
||||
to WalkFunc are made, and the error is returned to the caller of Walk;
|
||||
|
||||
* if more than one walkFunc instance will return an error, only one
|
||||
of such errors will be propagated and returned by Walk, others
|
||||
will be silently discarded.
|
||||
|
||||
### Documentation
|
||||
|
||||
For the official documentation, see
|
||||
https://pkg.go.dev/github.com/opencontainers/selinux/pkg/pwalk?tab=doc
|
||||
|
||||
### Benchmarks
|
||||
|
||||
For a WalkFunc that consists solely of the return statement, this
|
||||
implementation is about 10% slower than the standard library's
|
||||
filepath.Walk.
|
||||
|
||||
Otherwise (if a WalkFunc is doing something) this is usually faster,
|
||||
except when the WalkN(..., 1) is used.
|
|
@ -0,0 +1,99 @@
|
|||
package pwalk
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"sync"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
type WalkFunc = filepath.WalkFunc
|
||||
|
||||
// Walk is a wrapper for filepath.Walk which can call multiple walkFn
|
||||
// in parallel, allowing to handle each item concurrently. A maximum of
|
||||
// twice the runtime.NumCPU() walkFn will be called at any one time.
|
||||
// If you want to change the maximum, use WalkN instead.
|
||||
//
|
||||
// The order of calls is non-deterministic.
|
||||
//
|
||||
// Note that this implementation only supports primitive error handling:
|
||||
//
|
||||
// * no errors are ever passed to WalkFn
|
||||
//
|
||||
// * once a walkFn returns any error, all further processing stops
|
||||
// and the error is returned to the caller of Walk;
|
||||
//
|
||||
// * filepath.SkipDir is not supported;
|
||||
//
|
||||
// * if more than one walkFn instance will return an error, only one
|
||||
// of such errors will be propagated and returned by Walk, others
|
||||
// will be silently discarded.
|
||||
//
|
||||
func Walk(root string, walkFn WalkFunc) error {
|
||||
return WalkN(root, walkFn, runtime.NumCPU()*2)
|
||||
}
|
||||
|
||||
// WalkN is a wrapper for filepath.Walk which can call multiple walkFn
|
||||
// in parallel, allowing to handle each item concurrently. A maximum of
|
||||
// num walkFn will be called at any one time.
|
||||
func WalkN(root string, walkFn WalkFunc, num int) error {
|
||||
// make sure limit is sensible
|
||||
if num < 1 {
|
||||
return errors.Errorf("walk(%q): num must be > 0", root)
|
||||
}
|
||||
|
||||
files := make(chan *walkArgs, 2*num)
|
||||
errCh := make(chan error, 1) // get the first error, ignore others
|
||||
|
||||
// Start walking a tree asap
|
||||
var err error
|
||||
go func() {
|
||||
err = filepath.Walk(root, func(p string, info os.FileInfo, err error) error {
|
||||
if err != nil {
|
||||
close(files)
|
||||
return err
|
||||
}
|
||||
// add a file to the queue unless a callback sent an error
|
||||
select {
|
||||
case e := <-errCh:
|
||||
close(files)
|
||||
return e
|
||||
default:
|
||||
files <- &walkArgs{path: p, info: &info}
|
||||
return nil
|
||||
}
|
||||
})
|
||||
if err == nil {
|
||||
close(files)
|
||||
}
|
||||
}()
|
||||
|
||||
var wg sync.WaitGroup
|
||||
wg.Add(num)
|
||||
for i := 0; i < num; i++ {
|
||||
go func() {
|
||||
for file := range files {
|
||||
if e := walkFn(file.path, *file.info, nil); e != nil {
|
||||
select {
|
||||
case errCh <- e: // sent ok
|
||||
default: // buffer full
|
||||
}
|
||||
}
|
||||
}
|
||||
wg.Done()
|
||||
}()
|
||||
}
|
||||
|
||||
wg.Wait()
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
// walkArgs holds the arguments that were passed to the Walk or WalkLimit
|
||||
// functions.
|
||||
type walkArgs struct {
|
||||
path string
|
||||
info *os.FileInfo
|
||||
}
|
|
@ -1,15 +1,10 @@
|
|||
language: go
|
||||
go_import_path: github.com/pkg/errors
|
||||
go:
|
||||
- 1.4.x
|
||||
- 1.5.x
|
||||
- 1.6.x
|
||||
- 1.7.x
|
||||
- 1.8.x
|
||||
- 1.9.x
|
||||
- 1.10.x
|
||||
- 1.11.x
|
||||
- 1.12.x
|
||||
- 1.13.x
|
||||
- tip
|
||||
|
||||
script:
|
||||
- go test -v ./...
|
||||
- make check
|
||||
|
|
|
@ -0,0 +1,44 @@
|
|||
PKGS := github.com/pkg/errors
|
||||
SRCDIRS := $(shell go list -f '{{.Dir}}' $(PKGS))
|
||||
GO := go
|
||||
|
||||
check: test vet gofmt misspell unconvert staticcheck ineffassign unparam
|
||||
|
||||
test:
|
||||
$(GO) test $(PKGS)
|
||||
|
||||
vet: | test
|
||||
$(GO) vet $(PKGS)
|
||||
|
||||
staticcheck:
|
||||
$(GO) get honnef.co/go/tools/cmd/staticcheck
|
||||
staticcheck -checks all $(PKGS)
|
||||
|
||||
misspell:
|
||||
$(GO) get github.com/client9/misspell/cmd/misspell
|
||||
misspell \
|
||||
-locale GB \
|
||||
-error \
|
||||
*.md *.go
|
||||
|
||||
unconvert:
|
||||
$(GO) get github.com/mdempsky/unconvert
|
||||
unconvert -v $(PKGS)
|
||||
|
||||
ineffassign:
|
||||
$(GO) get github.com/gordonklaus/ineffassign
|
||||
find $(SRCDIRS) -name '*.go' | xargs ineffassign
|
||||
|
||||
pedantic: check errcheck
|
||||
|
||||
unparam:
|
||||
$(GO) get mvdan.cc/unparam
|
||||
unparam ./...
|
||||
|
||||
errcheck:
|
||||
$(GO) get github.com/kisielk/errcheck
|
||||
errcheck $(PKGS)
|
||||
|
||||
gofmt:
|
||||
@echo Checking code is gofmted
|
||||
@test -z "$(shell gofmt -s -l -d -e $(SRCDIRS) | tee /dev/stderr)"
|
|
@ -41,11 +41,18 @@ default:
|
|||
|
||||
[Read the package documentation for more information](https://godoc.org/github.com/pkg/errors).
|
||||
|
||||
## Roadmap
|
||||
|
||||
With the upcoming [Go2 error proposals](https://go.googlesource.com/proposal/+/master/design/go2draft.md) this package is moving into maintenance mode. The roadmap for a 1.0 release is as follows:
|
||||
|
||||
- 0.9. Remove pre Go 1.9 and Go 1.10 support, address outstanding pull requests (if possible)
|
||||
- 1.0. Final release.
|
||||
|
||||
## Contributing
|
||||
|
||||
We welcome pull requests, bug fixes and issue reports. With that said, the bar for adding new symbols to this package is intentionally set high.
|
||||
Because of the Go2 errors changes, this package is not accepting proposals for new functionality. With that said, we welcome pull requests, bug fixes and issue reports.
|
||||
|
||||
Before proposing a change, please discuss your change by raising an issue.
|
||||
Before sending a PR, please discuss your change by raising an issue.
|
||||
|
||||
## License
|
||||
|
||||
|
|
|
@ -82,7 +82,7 @@
|
|||
//
|
||||
// if err, ok := err.(stackTracer); ok {
|
||||
// for _, f := range err.StackTrace() {
|
||||
// fmt.Printf("%+s:%d", f)
|
||||
// fmt.Printf("%+s:%d\n", f, f)
|
||||
// }
|
||||
// }
|
||||
//
|
||||
|
@ -159,6 +159,9 @@ type withStack struct {
|
|||
|
||||
func (w *withStack) Cause() error { return w.error }
|
||||
|
||||
// Unwrap provides compatibility for Go 1.13 error chains.
|
||||
func (w *withStack) Unwrap() error { return w.error }
|
||||
|
||||
func (w *withStack) Format(s fmt.State, verb rune) {
|
||||
switch verb {
|
||||
case 'v':
|
||||
|
@ -241,6 +244,9 @@ type withMessage struct {
|
|||
func (w *withMessage) Error() string { return w.msg + ": " + w.cause.Error() }
|
||||
func (w *withMessage) Cause() error { return w.cause }
|
||||
|
||||
// Unwrap provides compatibility for Go 1.13 error chains.
|
||||
func (w *withMessage) Unwrap() error { return w.cause }
|
||||
|
||||
func (w *withMessage) Format(s fmt.State, verb rune) {
|
||||
switch verb {
|
||||
case 'v':
|
||||
|
|
|
@ -0,0 +1,38 @@
|
|||
// +build go1.13
|
||||
|
||||
package errors
|
||||
|
||||
import (
|
||||
stderrors "errors"
|
||||
)
|
||||
|
||||
// Is reports whether any error in err's chain matches target.
|
||||
//
|
||||
// The chain consists of err itself followed by the sequence of errors obtained by
|
||||
// repeatedly calling Unwrap.
|
||||
//
|
||||
// An error is considered to match a target if it is equal to that target or if
|
||||
// it implements a method Is(error) bool such that Is(target) returns true.
|
||||
func Is(err, target error) bool { return stderrors.Is(err, target) }
|
||||
|
||||
// As finds the first error in err's chain that matches target, and if so, sets
|
||||
// target to that error value and returns true.
|
||||
//
|
||||
// The chain consists of err itself followed by the sequence of errors obtained by
|
||||
// repeatedly calling Unwrap.
|
||||
//
|
||||
// An error matches target if the error's concrete value is assignable to the value
|
||||
// pointed to by target, or if the error has a method As(interface{}) bool such that
|
||||
// As(target) returns true. In the latter case, the As method is responsible for
|
||||
// setting target.
|
||||
//
|
||||
// As will panic if target is not a non-nil pointer to either a type that implements
|
||||
// error, or to any interface type. As returns false if err is nil.
|
||||
func As(err error, target interface{}) bool { return stderrors.As(err, target) }
|
||||
|
||||
// Unwrap returns the result of calling the Unwrap method on err, if err's
|
||||
// type contains an Unwrap method returning error.
|
||||
// Otherwise, Unwrap returns nil.
|
||||
func Unwrap(err error) error {
|
||||
return stderrors.Unwrap(err)
|
||||
}
|
|
@ -5,10 +5,13 @@ import (
|
|||
"io"
|
||||
"path"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Frame represents a program counter inside a stack frame.
|
||||
// For historical reasons if Frame is interpreted as a uintptr
|
||||
// its value represents the program counter + 1.
|
||||
type Frame uintptr
|
||||
|
||||
// pc returns the program counter for this frame;
|
||||
|
@ -37,6 +40,15 @@ func (f Frame) line() int {
|
|||
return line
|
||||
}
|
||||
|
||||
// name returns the name of this function, if known.
|
||||
func (f Frame) name() string {
|
||||
fn := runtime.FuncForPC(f.pc())
|
||||
if fn == nil {
|
||||
return "unknown"
|
||||
}
|
||||
return fn.Name()
|
||||
}
|
||||
|
||||
// Format formats the frame according to the fmt.Formatter interface.
|
||||
//
|
||||
// %s source file
|
||||
|
@ -54,22 +66,16 @@ func (f Frame) Format(s fmt.State, verb rune) {
|
|||
case 's':
|
||||
switch {
|
||||
case s.Flag('+'):
|
||||
pc := f.pc()
|
||||
fn := runtime.FuncForPC(pc)
|
||||
if fn == nil {
|
||||
io.WriteString(s, "unknown")
|
||||
} else {
|
||||
file, _ := fn.FileLine(pc)
|
||||
fmt.Fprintf(s, "%s\n\t%s", fn.Name(), file)
|
||||
}
|
||||
io.WriteString(s, f.name())
|
||||
io.WriteString(s, "\n\t")
|
||||
io.WriteString(s, f.file())
|
||||
default:
|
||||
io.WriteString(s, path.Base(f.file()))
|
||||
}
|
||||
case 'd':
|
||||
fmt.Fprintf(s, "%d", f.line())
|
||||
io.WriteString(s, strconv.Itoa(f.line()))
|
||||
case 'n':
|
||||
name := runtime.FuncForPC(f.pc()).Name()
|
||||
io.WriteString(s, funcname(name))
|
||||
io.WriteString(s, funcname(f.name()))
|
||||
case 'v':
|
||||
f.Format(s, 's')
|
||||
io.WriteString(s, ":")
|
||||
|
@ -77,6 +83,16 @@ func (f Frame) Format(s fmt.State, verb rune) {
|
|||
}
|
||||
}
|
||||
|
||||
// MarshalText formats a stacktrace Frame as a text string. The output is the
|
||||
// same as that of fmt.Sprintf("%+v", f), but without newlines or tabs.
|
||||
func (f Frame) MarshalText() ([]byte, error) {
|
||||
name := f.name()
|
||||
if name == "unknown" {
|
||||
return []byte(name), nil
|
||||
}
|
||||
return []byte(fmt.Sprintf("%s %s:%d", name, f.file(), f.line())), nil
|
||||
}
|
||||
|
||||
// StackTrace is stack of Frames from innermost (newest) to outermost (oldest).
|
||||
type StackTrace []Frame
|
||||
|
||||
|
@ -94,18 +110,32 @@ func (st StackTrace) Format(s fmt.State, verb rune) {
|
|||
switch {
|
||||
case s.Flag('+'):
|
||||
for _, f := range st {
|
||||
fmt.Fprintf(s, "\n%+v", f)
|
||||
io.WriteString(s, "\n")
|
||||
f.Format(s, verb)
|
||||
}
|
||||
case s.Flag('#'):
|
||||
fmt.Fprintf(s, "%#v", []Frame(st))
|
||||
default:
|
||||
fmt.Fprintf(s, "%v", []Frame(st))
|
||||
st.formatSlice(s, verb)
|
||||
}
|
||||
case 's':
|
||||
fmt.Fprintf(s, "%s", []Frame(st))
|
||||
st.formatSlice(s, verb)
|
||||
}
|
||||
}
|
||||
|
||||
// formatSlice will format this StackTrace into the given buffer as a slice of
|
||||
// Frame, only valid when called with '%s' or '%v'.
|
||||
func (st StackTrace) formatSlice(s fmt.State, verb rune) {
|
||||
io.WriteString(s, "[")
|
||||
for i, f := range st {
|
||||
if i > 0 {
|
||||
io.WriteString(s, " ")
|
||||
}
|
||||
f.Format(s, verb)
|
||||
}
|
||||
io.WriteString(s, "]")
|
||||
}
|
||||
|
||||
// stack represents a stack of program counters.
|
||||
type stack []uintptr
|
||||
|
||||
|
|
|
@ -0,0 +1,8 @@
|
|||
*.out
|
||||
*.swp
|
||||
*.8
|
||||
*.6
|
||||
_obj
|
||||
_test*
|
||||
markdown
|
||||
tags
|
|
@ -0,0 +1,17 @@
|
|||
sudo: false
|
||||
language: go
|
||||
go:
|
||||
- "1.10.x"
|
||||
- "1.11.x"
|
||||
- tip
|
||||
matrix:
|
||||
fast_finish: true
|
||||
allow_failures:
|
||||
- go: tip
|
||||
install:
|
||||
- # Do nothing. This is needed to prevent default install action "go get -t -v ./..." from happening here (we want it to happen inside script step).
|
||||
script:
|
||||
- go get -t -v ./...
|
||||
- diff -u <(echo -n) <(gofmt -d -s .)
|
||||
- go tool vet .
|
||||
- go test -v ./...
|
|
@ -0,0 +1,29 @@
|
|||
Blackfriday is distributed under the Simplified BSD License:
|
||||
|
||||
> Copyright © 2011 Russ Ross
|
||||
> All rights reserved.
|
||||
>
|
||||
> Redistribution and use in source and binary forms, with or without
|
||||
> modification, are permitted provided that the following conditions
|
||||
> are met:
|
||||
>
|
||||
> 1. Redistributions of source code must retain the above copyright
|
||||
> notice, this list of conditions and the following disclaimer.
|
||||
>
|
||||
> 2. Redistributions in binary form must reproduce the above
|
||||
> copyright notice, this list of conditions and the following
|
||||
> disclaimer in the documentation and/or other materials provided with
|
||||
> the distribution.
|
||||
>
|
||||
> THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
> "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
> LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
|
||||
> FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
|
||||
> COPYRIGHT 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.
|
|
@ -0,0 +1,291 @@
|
|||
Blackfriday [![Build Status](https://travis-ci.org/russross/blackfriday.svg?branch=master)](https://travis-ci.org/russross/blackfriday)
|
||||
===========
|
||||
|
||||
Blackfriday is a [Markdown][1] processor implemented in [Go][2]. It
|
||||
is paranoid about its input (so you can safely feed it user-supplied
|
||||
data), it is fast, it supports common extensions (tables, smart
|
||||
punctuation substitutions, etc.), and it is safe for all utf-8
|
||||
(unicode) input.
|
||||
|
||||
HTML output is currently supported, along with Smartypants
|
||||
extensions.
|
||||
|
||||
It started as a translation from C of [Sundown][3].
|
||||
|
||||
|
||||
Installation
|
||||
------------
|
||||
|
||||
Blackfriday is compatible with any modern Go release. With Go 1.7 and git
|
||||
installed:
|
||||
|
||||
go get gopkg.in/russross/blackfriday.v2
|
||||
|
||||
will download, compile, and install the package into your `$GOPATH`
|
||||
directory hierarchy. Alternatively, you can achieve the same if you
|
||||
import it into a project:
|
||||
|
||||
import "gopkg.in/russross/blackfriday.v2"
|
||||
|
||||
and `go get` without parameters.
|
||||
|
||||
|
||||
Versions
|
||||
--------
|
||||
|
||||
Currently maintained and recommended version of Blackfriday is `v2`. It's being
|
||||
developed on its own branch: https://github.com/russross/blackfriday/tree/v2 and the
|
||||
documentation is available at
|
||||
https://godoc.org/gopkg.in/russross/blackfriday.v2.
|
||||
|
||||
It is `go get`-able via via [gopkg.in][6] at `gopkg.in/russross/blackfriday.v2`,
|
||||
but we highly recommend using package management tool like [dep][7] or
|
||||
[Glide][8] and make use of semantic versioning. With package management you
|
||||
should import `github.com/russross/blackfriday` and specify that you're using
|
||||
version 2.0.0.
|
||||
|
||||
Version 2 offers a number of improvements over v1:
|
||||
|
||||
* Cleaned up API
|
||||
* A separate call to [`Parse`][4], which produces an abstract syntax tree for
|
||||
the document
|
||||
* Latest bug fixes
|
||||
* Flexibility to easily add your own rendering extensions
|
||||
|
||||
Potential drawbacks:
|
||||
|
||||
* Our benchmarks show v2 to be slightly slower than v1. Currently in the
|
||||
ballpark of around 15%.
|
||||
* API breakage. If you can't afford modifying your code to adhere to the new API
|
||||
and don't care too much about the new features, v2 is probably not for you.
|
||||
* Several bug fixes are trailing behind and still need to be forward-ported to
|
||||
v2. See issue [#348](https://github.com/russross/blackfriday/issues/348) for
|
||||
tracking.
|
||||
|
||||
Usage
|
||||
-----
|
||||
|
||||
For the most sensible markdown processing, it is as simple as getting your input
|
||||
into a byte slice and calling:
|
||||
|
||||
```go
|
||||
output := blackfriday.Run(input)
|
||||
```
|
||||
|
||||
Your input will be parsed and the output rendered with a set of most popular
|
||||
extensions enabled. If you want the most basic feature set, corresponding with
|
||||
the bare Markdown specification, use:
|
||||
|
||||
```go
|
||||
output := blackfriday.Run(input, blackfriday.WithNoExtensions())
|
||||
```
|
||||
|
||||
### Sanitize untrusted content
|
||||
|
||||
Blackfriday itself does nothing to protect against malicious content. If you are
|
||||
dealing with user-supplied markdown, we recommend running Blackfriday's output
|
||||
through HTML sanitizer such as [Bluemonday][5].
|
||||
|
||||
Here's an example of simple usage of Blackfriday together with Bluemonday:
|
||||
|
||||
```go
|
||||
import (
|
||||
"github.com/microcosm-cc/bluemonday"
|
||||
"github.com/russross/blackfriday"
|
||||
)
|
||||
|
||||
// ...
|
||||
unsafe := blackfriday.Run(input)
|
||||
html := bluemonday.UGCPolicy().SanitizeBytes(unsafe)
|
||||
```
|
||||
|
||||
### Custom options
|
||||
|
||||
If you want to customize the set of options, use `blackfriday.WithExtensions`,
|
||||
`blackfriday.WithRenderer` and `blackfriday.WithRefOverride`.
|
||||
|
||||
You can also check out `blackfriday-tool` for a more complete example
|
||||
of how to use it. Download and install it using:
|
||||
|
||||
go get github.com/russross/blackfriday-tool
|
||||
|
||||
This is a simple command-line tool that allows you to process a
|
||||
markdown file using a standalone program. You can also browse the
|
||||
source directly on github if you are just looking for some example
|
||||
code:
|
||||
|
||||
* <http://github.com/russross/blackfriday-tool>
|
||||
|
||||
Note that if you have not already done so, installing
|
||||
`blackfriday-tool` will be sufficient to download and install
|
||||
blackfriday in addition to the tool itself. The tool binary will be
|
||||
installed in `$GOPATH/bin`. This is a statically-linked binary that
|
||||
can be copied to wherever you need it without worrying about
|
||||
dependencies and library versions.
|
||||
|
||||
|
||||
Features
|
||||
--------
|
||||
|
||||
All features of Sundown are supported, including:
|
||||
|
||||
* **Compatibility**. The Markdown v1.0.3 test suite passes with
|
||||
the `--tidy` option. Without `--tidy`, the differences are
|
||||
mostly in whitespace and entity escaping, where blackfriday is
|
||||
more consistent and cleaner.
|
||||
|
||||
* **Common extensions**, including table support, fenced code
|
||||
blocks, autolinks, strikethroughs, non-strict emphasis, etc.
|
||||
|
||||
* **Safety**. Blackfriday is paranoid when parsing, making it safe
|
||||
to feed untrusted user input without fear of bad things
|
||||
happening. The test suite stress tests this and there are no
|
||||
known inputs that make it crash. If you find one, please let me
|
||||
know and send me the input that does it.
|
||||
|
||||
NOTE: "safety" in this context means *runtime safety only*. In order to
|
||||
protect yourself against JavaScript injection in untrusted content, see
|
||||
[this example](https://github.com/russross/blackfriday#sanitize-untrusted-content).
|
||||
|
||||
* **Fast processing**. It is fast enough to render on-demand in
|
||||
most web applications without having to cache the output.
|
||||
|
||||
* **Thread safety**. You can run multiple parsers in different
|
||||
goroutines without ill effect. There is no dependence on global
|
||||
shared state.
|
||||
|
||||
* **Minimal dependencies**. Blackfriday only depends on standard
|
||||
library packages in Go. The source code is pretty
|
||||
self-contained, so it is easy to add to any project, including
|
||||
Google App Engine projects.
|
||||
|
||||
* **Standards compliant**. Output successfully validates using the
|
||||
W3C validation tool for HTML 4.01 and XHTML 1.0 Transitional.
|
||||
|
||||
|
||||
Extensions
|
||||
----------
|
||||
|
||||
In addition to the standard markdown syntax, this package
|
||||
implements the following extensions:
|
||||
|
||||
* **Intra-word emphasis supression**. The `_` character is
|
||||
commonly used inside words when discussing code, so having
|
||||
markdown interpret it as an emphasis command is usually the
|
||||
wrong thing. Blackfriday lets you treat all emphasis markers as
|
||||
normal characters when they occur inside a word.
|
||||
|
||||
* **Tables**. Tables can be created by drawing them in the input
|
||||
using a simple syntax:
|
||||
|
||||
```
|
||||
Name | Age
|
||||
--------|------
|
||||
Bob | 27
|
||||
Alice | 23
|
||||
```
|
||||
|
||||
* **Fenced code blocks**. In addition to the normal 4-space
|
||||
indentation to mark code blocks, you can explicitly mark them
|
||||
and supply a language (to make syntax highlighting simple). Just
|
||||
mark it like this:
|
||||
|
||||
```go
|
||||
func getTrue() bool {
|
||||
return true
|
||||
}
|
||||
```
|
||||
|
||||
You can use 3 or more backticks to mark the beginning of the
|
||||
block, and the same number to mark the end of the block.
|
||||
|
||||
* **Definition lists**. A simple definition list is made of a single-line
|
||||
term followed by a colon and the definition for that term.
|
||||
|
||||
Cat
|
||||
: Fluffy animal everyone likes
|
||||
|
||||
Internet
|
||||
: Vector of transmission for pictures of cats
|
||||
|
||||
Terms must be separated from the previous definition by a blank line.
|
||||
|
||||
* **Footnotes**. A marker in the text that will become a superscript number;
|
||||
a footnote definition that will be placed in a list of footnotes at the
|
||||
end of the document. A footnote looks like this:
|
||||
|
||||
This is a footnote.[^1]
|
||||
|
||||
[^1]: the footnote text.
|
||||
|
||||
* **Autolinking**. Blackfriday can find URLs that have not been
|
||||
explicitly marked as links and turn them into links.
|
||||
|
||||
* **Strikethrough**. Use two tildes (`~~`) to mark text that
|
||||
should be crossed out.
|
||||
|
||||
* **Hard line breaks**. With this extension enabled newlines in the input
|
||||
translate into line breaks in the output. This extension is off by default.
|
||||
|
||||
* **Smart quotes**. Smartypants-style punctuation substitution is
|
||||
supported, turning normal double- and single-quote marks into
|
||||
curly quotes, etc.
|
||||
|
||||
* **LaTeX-style dash parsing** is an additional option, where `--`
|
||||
is translated into `–`, and `---` is translated into
|
||||
`—`. This differs from most smartypants processors, which
|
||||
turn a single hyphen into an ndash and a double hyphen into an
|
||||
mdash.
|
||||
|
||||
* **Smart fractions**, where anything that looks like a fraction
|
||||
is translated into suitable HTML (instead of just a few special
|
||||
cases like most smartypant processors). For example, `4/5`
|
||||
becomes `<sup>4</sup>⁄<sub>5</sub>`, which renders as
|
||||
<sup>4</sup>⁄<sub>5</sub>.
|
||||
|
||||
|
||||
Other renderers
|
||||
---------------
|
||||
|
||||
Blackfriday is structured to allow alternative rendering engines. Here
|
||||
are a few of note:
|
||||
|
||||
* [github_flavored_markdown](https://godoc.org/github.com/shurcooL/github_flavored_markdown):
|
||||
provides a GitHub Flavored Markdown renderer with fenced code block
|
||||
highlighting, clickable heading anchor links.
|
||||
|
||||
It's not customizable, and its goal is to produce HTML output
|
||||
equivalent to the [GitHub Markdown API endpoint](https://developer.github.com/v3/markdown/#render-a-markdown-document-in-raw-mode),
|
||||
except the rendering is performed locally.
|
||||
|
||||
* [markdownfmt](https://github.com/shurcooL/markdownfmt): like gofmt,
|
||||
but for markdown.
|
||||
|
||||
* [LaTeX output](https://github.com/Ambrevar/Blackfriday-LaTeX):
|
||||
renders output as LaTeX.
|
||||
|
||||
* [Blackfriday-Confluence](https://github.com/kentaro-m/blackfriday-confluence): provides a [Confluence Wiki Markup](https://confluence.atlassian.com/doc/confluence-wiki-markup-251003035.html) renderer.
|
||||
|
||||
|
||||
Todo
|
||||
----
|
||||
|
||||
* More unit testing
|
||||
* Improve unicode support. It does not understand all unicode
|
||||
rules (about what constitutes a letter, a punctuation symbol,
|
||||
etc.), so it may fail to detect word boundaries correctly in
|
||||
some instances. It is safe on all utf-8 input.
|
||||
|
||||
|
||||
License
|
||||
-------
|
||||
|
||||
[Blackfriday is distributed under the Simplified BSD License](LICENSE.txt)
|
||||
|
||||
|
||||
[1]: https://daringfireball.net/projects/markdown/ "Markdown"
|
||||
[2]: https://golang.org/ "Go Language"
|
||||
[3]: https://github.com/vmg/sundown "Sundown"
|
||||
[4]: https://godoc.org/gopkg.in/russross/blackfriday.v2#Parse "Parse func"
|
||||
[5]: https://github.com/microcosm-cc/bluemonday "Bluemonday"
|
||||
[6]: https://labix.org/gopkg.in "gopkg.in"
|
File diff suppressed because it is too large
Load Diff
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue