mirror of
https://github.com/opencontainers/runc.git
synced 2026-07-11 06:03:57 +08:00
update vendor
Signed-off-by: Akihiro Suda <akihiro.suda.cz@hco.ntt.co.jp>
This commit is contained in:
@@ -3,25 +3,24 @@ module github.com/opencontainers/runc
|
|||||||
go 1.14
|
go 1.14
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/checkpoint-restore/go-criu v0.0.0-20181120144056-17b0214f6c48
|
github.com/checkpoint-restore/go-criu v0.0.0-20191125063657-fcdcd07065c5
|
||||||
github.com/cilium/ebpf v0.0.0-20191025125908-95b36a581eed
|
github.com/cilium/ebpf v0.0.0-20200319110858-a7172c01168f
|
||||||
github.com/containerd/console v0.0.0-20181022165439-0650fd9eeb50
|
github.com/containerd/console v1.0.0
|
||||||
github.com/coreos/go-systemd/v22 v22.0.0
|
github.com/coreos/go-systemd/v22 v22.0.0
|
||||||
github.com/cyphar/filepath-securejoin v0.2.2
|
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/godbus/dbus/v5 v5.0.3
|
||||||
github.com/golang/protobuf v1.0.0
|
github.com/golang/protobuf v1.3.5
|
||||||
github.com/moby/sys/mountinfo v0.1.0
|
github.com/moby/sys/mountinfo v0.1.3
|
||||||
github.com/mrunalp/fileutils v0.0.0-20171103030105-7d4729fb3618
|
github.com/mrunalp/fileutils v0.0.0-20171103030105-7d4729fb3618
|
||||||
github.com/opencontainers/runtime-spec v1.0.2-0.20190207185410-29686dbc5559
|
github.com/opencontainers/runtime-spec v1.0.2
|
||||||
github.com/opencontainers/selinux v1.3.3
|
github.com/opencontainers/selinux v1.4.0
|
||||||
github.com/pkg/errors v0.8.1
|
github.com/pkg/errors v0.9.1
|
||||||
github.com/seccomp/libseccomp-golang 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/syndtr/gocapability v0.0.0-20180916011248-d98352740cb2
|
||||||
github.com/urfave/cli v1.20.0
|
// NOTE: urfave/cli must be <= v1.22.1 due to a regression: https://github.com/urfave/cli/issues/1092
|
||||||
github.com/vishvananda/netlink v0.0.0-20150820014904-1e2e08e8a2dc
|
github.com/urfave/cli v1.22.1
|
||||||
github.com/vishvananda/netns v0.0.0-20191106174202-0a2b9b5464df // indirect
|
github.com/vishvananda/netlink v1.1.0
|
||||||
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e // indirect
|
golang.org/x/sys v0.0.0-20200327173247-9dae0f8f5775
|
||||||
golang.org/x/sys v0.0.0-20191115151921-52ab43148777
|
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,53 +1,65 @@
|
|||||||
github.com/checkpoint-restore/go-criu v0.0.0-20181120144056-17b0214f6c48 h1:AQMF0Xixllgf29MKlx/TGEhRk7bEDX5kxz8Ui8lOvEs=
|
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||||
github.com/checkpoint-restore/go-criu v0.0.0-20181120144056-17b0214f6c48/go.mod h1:TrMrLQfeENAPYPRsJuq3jsqdlRh3lvi6trTZJG8+tho=
|
github.com/checkpoint-restore/go-criu v0.0.0-20191125063657-fcdcd07065c5 h1:950diauOSoCNaQfvLE0WaFadyI/ilKIjb6jEUQnm+hE=
|
||||||
github.com/cilium/ebpf v0.0.0-20191025125908-95b36a581eed h1:/UgmF+cZTm9kp4uJ122y/9cVhczNJCgAgAeH2FfzPeg=
|
github.com/checkpoint-restore/go-criu v0.0.0-20191125063657-fcdcd07065c5/go.mod h1:TrMrLQfeENAPYPRsJuq3jsqdlRh3lvi6trTZJG8+tho=
|
||||||
github.com/cilium/ebpf v0.0.0-20191025125908-95b36a581eed/go.mod h1:MA5e5Lr8slmEg9bt0VpxxWqJlO4iwu3FBdHUzV7wQVg=
|
github.com/cilium/ebpf v0.0.0-20200319110858-a7172c01168f h1:W1RQPz3nR8RxUw/Uqk71GU3JlZ7pNa1pXrHs98h0o9U=
|
||||||
github.com/containerd/console v0.0.0-20181022165439-0650fd9eeb50 h1:WMpHmC6AxwWb9hMqhudkqG7A/p14KiMnl6d3r1iUMjU=
|
github.com/cilium/ebpf v0.0.0-20200319110858-a7172c01168f/go.mod h1:XT+cAw5wfvsodedcijoh1l9cf7v1x9FlFB/3VmF/O8s=
|
||||||
github.com/containerd/console v0.0.0-20181022165439-0650fd9eeb50/go.mod h1:Tj/on1eG8kiEhd0+fhSDzsPAFESxzBBvdyEgyryXffw=
|
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 h1:XJIw/+VlJ+87J+doOxznsAWIdmWuViOVhkQamW5YV28=
|
||||||
github.com/coreos/go-systemd/v22 v22.0.0/go.mod h1:xO0FLkIi5MaZafQlIrOotqXZ90ih+1atmu1JpKERPPk=
|
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 h1:jCwT2GTP+PY5nBz3c/YL5PAIbusElVrPujOBSCj8xRg=
|
||||||
github.com/cyphar/filepath-securejoin v0.2.2/go.mod h1:FpkQEhXnPnOthhzymB7CGsFk2G9VLXONKD9G7QGMM+4=
|
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 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
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.4.0 h1:3uh0PgVws3nIA0Q+MwDC8yjEPf9zjRfZZWXZYDct3Tw=
|
||||||
github.com/docker/go-units v0.3.3/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
|
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 h1:ZqHaoEF7TBzh4jzPmqVhE/5A1z9of6orkAe5uHoAeME=
|
||||||
github.com/godbus/dbus/v5 v5.0.3/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
|
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.3.5 h1:F768QJ1E9tib+q5Sc8MkdJi1RxLTbRcTf8LJV56aRls=
|
||||||
github.com/golang/protobuf v1.0.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
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 h1:mweAR1A6xJ3oS2pRaGiHgQ4OO8tzTaLawm8vnODuwDk=
|
||||||
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
github.com/konsorten/go-windows-terminal-sequences v1.0.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.3 h1:KIrhRO14+AkwKvG/g2yIpNMOUVZ02xNhOw8KY1WsLOI=
|
||||||
github.com/moby/sys/mountinfo v0.1.0/go.mod h1:w2t2Avltqx8vE7gX5l+QiBKxODu2TX0+Syr3h52Tw4o=
|
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 h1:7InQ7/zrOh6SlFjaXFubv0xX0HsuC9qJsdqm7bNQpYM=
|
||||||
github.com/mrunalp/fileutils v0.0.0-20171103030105-7d4729fb3618/go.mod h1:x8F1gnqOkIEiO4rqoeEEEqQbo7HjGMTvyoq3gej4iT0=
|
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 h1:UfAcuLBJB9Coz72x1hgl8O5RVzTdNiaglX6v2DM6FI0=
|
||||||
github.com/opencontainers/runtime-spec v1.0.2-0.20190207185410-29686dbc5559/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0=
|
github.com/opencontainers/runtime-spec v1.0.2/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0=
|
||||||
github.com/opencontainers/selinux v1.3.3 h1:RX0wAeqtvVSYQcr017X3pFXPkLEtB6V4NjRD7gVQgg4=
|
github.com/opencontainers/selinux v1.4.0 h1:cpiX/2wWIju/6My60T6/z9CxNG7c8xTQyEmA9fChpUo=
|
||||||
github.com/opencontainers/selinux v1.3.3/go.mod h1:yTcKuYAh6R95iDpefGLQaPaRwJFwyzAJufJyiTt7s0g=
|
github.com/opencontainers/selinux v1.4.0/go.mod h1:yTcKuYAh6R95iDpefGLQaPaRwJFwyzAJufJyiTt7s0g=
|
||||||
github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
|
|
||||||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||||
|
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||||
|
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
|
github.com/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 h1:NJjM5DNFOs0s3kYE1WUOr6G8V97sdt46rlXTMfXGWBo=
|
||||||
github.com/seccomp/libseccomp-golang v0.9.1/go.mod h1:GbW5+tmTXfcxTToHLXlScSlAvWlF4P2Ca7zGrPiEpWo=
|
github.com/seccomp/libseccomp-golang v0.9.1/go.mod h1:GbW5+tmTXfcxTToHLXlScSlAvWlF4P2Ca7zGrPiEpWo=
|
||||||
github.com/sirupsen/logrus v1.4.1 h1:GL2rEmy6nsikmW0r8opw9JIRScdMF5hA8cOYLH7In1k=
|
github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo=
|
||||||
github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q=
|
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
|
||||||
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
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 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w=
|
||||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
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 h1:b6uOv7YOFK0TYG7HtkIgExQo+2RdLuwRft63jn2HWj8=
|
||||||
github.com/syndtr/gocapability v0.0.0-20180916011248-d98352740cb2/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww=
|
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.22.1 h1:+mkCCcOFKPnCmVYVcURKps1Xe+3zP90gSYGNfRkjoIY=
|
||||||
github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA=
|
github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
|
||||||
github.com/vishvananda/netlink v0.0.0-20150820014904-1e2e08e8a2dc h1:0HAHLwEY4k1VqaO1SzBi4XxT0KA06Cv+QW2LXknBk9g=
|
github.com/vishvananda/netlink v1.1.0 h1:1iyaYNBLmP6L0220aDnYQpo1QEV4t4hJ+xEEhhJH8j0=
|
||||||
github.com/vishvananda/netlink v0.0.0-20150820014904-1e2e08e8a2dc/go.mod h1:+SR5DhBJrl6ZM7CoCKvpw5BKroDKQ+PJqOg65H/2ktk=
|
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 h1:OviZH7qLw/7ZovXvuNyL3XQl8UFofeikI1NW1Gypu7k=
|
||||||
github.com/vishvananda/netns v0.0.0-20191106174202-0a2b9b5464df/go.mod h1:JP3t17pCcGlemwknint6hfoeCVQrEMVwxRLRjXpq+BU=
|
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/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sys v0.0.0-20190606203320-7fc4e5ec1444/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
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-20191115151921-52ab43148777/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=
|
||||||
|
|||||||
+146
-122
@@ -1,143 +1,90 @@
|
|||||||
package ebpf
|
package ebpf
|
||||||
|
|
||||||
import (
|
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.
|
// MapABI are the attributes of a Map which are available across all supported kernels.
|
||||||
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.
|
|
||||||
type MapABI struct {
|
type MapABI struct {
|
||||||
Type MapType
|
Type MapType
|
||||||
KeySize uint32
|
KeySize uint32
|
||||||
ValueSize uint32
|
ValueSize uint32
|
||||||
MaxEntries uint32
|
MaxEntries uint32
|
||||||
InnerMap *MapABI
|
Flags uint32
|
||||||
}
|
}
|
||||||
|
|
||||||
func newMapABIFromSpec(spec *MapSpec) *MapABI {
|
func newMapABIFromSpec(spec *MapSpec) *MapABI {
|
||||||
var inner *MapABI
|
|
||||||
if spec.InnerMap != nil {
|
|
||||||
inner = newMapABIFromSpec(spec.InnerMap)
|
|
||||||
}
|
|
||||||
|
|
||||||
return &MapABI{
|
return &MapABI{
|
||||||
spec.Type,
|
spec.Type,
|
||||||
spec.KeySize,
|
spec.KeySize,
|
||||||
spec.ValueSize,
|
spec.ValueSize,
|
||||||
spec.MaxEntries,
|
spec.MaxEntries,
|
||||||
inner,
|
spec.Flags,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func newMapABIFromFd(fd *bpfFD) (*MapABI, error) {
|
func newMapABIFromFd(fd *internal.FD) (string, *MapABI, error) {
|
||||||
info, err := bpfGetMapInfoByFD(fd)
|
info, err := bpfGetMapInfoByFD(fd)
|
||||||
if err != nil {
|
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)
|
return "", &MapABI{
|
||||||
if mapType == ArrayOfMaps || mapType == HashOfMaps {
|
MapType(info.mapType),
|
||||||
return nil, errors.New("can't get map info for nested maps")
|
|
||||||
}
|
|
||||||
|
|
||||||
return &MapABI{
|
|
||||||
mapType,
|
|
||||||
info.keySize,
|
info.keySize,
|
||||||
info.valueSize,
|
info.valueSize,
|
||||||
info.maxEntries,
|
info.maxEntries,
|
||||||
nil,
|
info.flags,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check verifies that a Map conforms to the ABI.
|
func newMapABIFromProc(fd *internal.FD) (*MapABI, error) {
|
||||||
//
|
var abi MapABI
|
||||||
// Members of ABI which have the zero value of their type are not checked.
|
err := scanFdInfo(fd, map[string]interface{}{
|
||||||
func (abi *MapABI) Check(m *Map) error {
|
"map_type": &abi.Type,
|
||||||
return abi.check(&m.abi)
|
"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 {
|
// Equal returns true if two ABIs have the same values.
|
||||||
if abi.Type != UnspecifiedMap && other.Type != abi.Type {
|
func (abi *MapABI) Equal(other *MapABI) bool {
|
||||||
return errors.Errorf("expected map type %s, have %s", abi.Type, other.Type)
|
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.
|
// ProgramABI are the attributes of a Program which are available across all supported kernels.
|
||||||
//
|
|
||||||
// Use it to assert that a Program matches what your code expects.
|
|
||||||
type ProgramABI struct {
|
type ProgramABI struct {
|
||||||
Type ProgramType
|
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)
|
info, err := bpfGetProgInfoByFD(fd)
|
||||||
if err != nil {
|
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 name, &ProgramABI{
|
||||||
return &ProgramABI{
|
|
||||||
Type: ProgramType(info.progType),
|
Type: ProgramType(info.progType),
|
||||||
}
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check verifies that a Program conforms to the ABI.
|
func newProgramABIFromProc(fd *internal.FD) (string, *ProgramABI, error) {
|
||||||
//
|
var (
|
||||||
// Members which have the zero value of their type
|
abi ProgramABI
|
||||||
// are not checked.
|
name string
|
||||||
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)
|
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
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func checkUint32(name string, want, have uint32) error {
|
// Equal returns true if two ABIs have the same values.
|
||||||
if want != 0 && have != want {
|
func (abi *ProgramABI) Equal(other *ProgramABI) bool {
|
||||||
return errors.Errorf("expected %s to be %d, have %d", name, want, have)
|
switch {
|
||||||
|
case abi.Type != other.Type:
|
||||||
|
return false
|
||||||
|
default:
|
||||||
|
return true
|
||||||
}
|
}
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|||||||
+63
-19
@@ -7,7 +7,7 @@ import (
|
|||||||
"math"
|
"math"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/pkg/errors"
|
"golang.org/x/xerrors"
|
||||||
)
|
)
|
||||||
|
|
||||||
// InstructionSize is the size of a BPF instruction in bytes
|
// 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
|
var bi2 bpfInstruction
|
||||||
if err := binary.Read(r, bo, &bi2); err != nil {
|
if err := binary.Read(r, bo, &bi2); err != nil {
|
||||||
// No Wrap, to avoid io.EOF clash
|
// 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 {
|
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)))
|
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.
|
// Marshal encodes a BPF instruction.
|
||||||
func (ins Instruction) Marshal(w io.Writer, bo binary.ByteOrder) (uint64, error) {
|
func (ins Instruction) Marshal(w io.Writer, bo binary.ByteOrder) (uint64, error) {
|
||||||
if ins.OpCode == InvalidOpCode {
|
if ins.OpCode == InvalidOpCode {
|
||||||
return 0, errors.New("invalid opcode")
|
return 0, xerrors.New("invalid opcode")
|
||||||
}
|
}
|
||||||
|
|
||||||
isDWordLoad := ins.OpCode.isDWordLoad()
|
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.
|
// RewriteMapPtr changes an instruction to use a new map fd.
|
||||||
//
|
//
|
||||||
// Returns an error if the fd is invalid, or the instruction
|
// Returns an error if the instruction doesn't load a map.
|
||||||
// is incorrect.
|
|
||||||
func (ins *Instruction) RewriteMapPtr(fd int) error {
|
func (ins *Instruction) RewriteMapPtr(fd int) error {
|
||||||
if !ins.OpCode.isDWordLoad() {
|
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 {
|
if ins.Src != PseudoMapFD && ins.Src != PseudoMapValue {
|
||||||
return errors.New("invalid fd")
|
return xerrors.New("not a load from a map")
|
||||||
}
|
}
|
||||||
|
|
||||||
ins.Src = R1
|
// Preserve the offset value for direct map loads.
|
||||||
ins.Constant = int64(fd)
|
offset := uint64(ins.Constant) & (math.MaxUint32 << 32)
|
||||||
|
rawFd := uint64(uint32(fd))
|
||||||
|
ins.Constant = int64(offset | rawFd)
|
||||||
return nil
|
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.
|
// Format implements fmt.Formatter.
|
||||||
func (ins Instruction) Format(f fmt.State, c rune) {
|
func (ins Instruction) Format(f fmt.State, c rune) {
|
||||||
if c != 'v' {
|
if c != 'v' {
|
||||||
@@ -139,6 +169,19 @@ func (ins Instruction) Format(f fmt.State, c rune) {
|
|||||||
return
|
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)
|
fmt.Fprintf(f, "%v ", op)
|
||||||
switch cls := op.Class(); cls {
|
switch cls := op.Class(); cls {
|
||||||
case LdClass, LdXClass, StClass, StXClass:
|
case LdClass, LdXClass, StClass, StXClass:
|
||||||
@@ -166,7 +209,7 @@ func (ins Instruction) Format(f fmt.State, c rune) {
|
|||||||
case JumpClass:
|
case JumpClass:
|
||||||
switch jop := op.JumpOp(); jop {
|
switch jop := op.JumpOp(); jop {
|
||||||
case Call:
|
case Call:
|
||||||
if ins.Src == R1 {
|
if ins.Src == PseudoCall {
|
||||||
// bpf-to-bpf call
|
// bpf-to-bpf call
|
||||||
fmt.Fprint(f, ins.Constant)
|
fmt.Fprint(f, ins.Constant)
|
||||||
} else {
|
} else {
|
||||||
@@ -183,6 +226,7 @@ func (ins Instruction) Format(f fmt.State, c rune) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ref:
|
||||||
if ins.Reference != "" {
|
if ins.Reference != "" {
|
||||||
fmt.Fprintf(f, " <%s>", 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.
|
// Returns an error if the symbol isn't used, see IsUnreferencedSymbol.
|
||||||
func (insns Instructions) RewriteMapPtr(symbol string, fd int) error {
|
func (insns Instructions) RewriteMapPtr(symbol string, fd int) error {
|
||||||
if symbol == "" {
|
if symbol == "" {
|
||||||
return errors.New("empty symbol")
|
return xerrors.New("empty symbol")
|
||||||
}
|
}
|
||||||
|
|
||||||
found := false
|
found := false
|
||||||
@@ -235,7 +279,7 @@ func (insns Instructions) SymbolOffsets() (map[string]int, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if _, ok := offsets[ins.Symbol]; ok {
|
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
|
offsets[ins.Symbol] = i
|
||||||
@@ -273,7 +317,7 @@ func (insns Instructions) marshalledOffsets() (map[string]int, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if _, ok := symbols[ins.Symbol]; ok {
|
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
|
symbols[ins.Symbol] = currentPos
|
||||||
@@ -350,11 +394,11 @@ func (insns Instructions) Marshal(w io.Writer, bo binary.ByteOrder) error {
|
|||||||
num := 0
|
num := 0
|
||||||
for i, ins := range insns {
|
for i, ins := range insns {
|
||||||
switch {
|
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
|
// Rewrite bpf to bpf call
|
||||||
offset, ok := absoluteOffsets[ins.Reference]
|
offset, ok := absoluteOffsets[ins.Reference]
|
||||||
if !ok {
|
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)
|
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
|
// Rewrite jump to label
|
||||||
offset, ok := absoluteOffsets[ins.Reference]
|
offset, ok := absoluteOffsets[ins.Reference]
|
||||||
if !ok {
|
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)
|
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)
|
n, err := ins.Marshal(w, bo)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.Wrapf(err, "instruction %d", i)
|
return xerrors.Errorf("instruction %d: %w", i, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
num += int(n / InstructionSize)
|
num += int(n / InstructionSize)
|
||||||
|
|||||||
+1
-1
@@ -95,7 +95,7 @@ func (op JumpOp) Label(label string) Instruction {
|
|||||||
if op == Call {
|
if op == Call {
|
||||||
return Instruction{
|
return Instruction{
|
||||||
OpCode: OpCode(JumpClass).SetJumpOp(Call),
|
OpCode: OpCode(JumpClass).SetJumpOp(Call),
|
||||||
Src: R1,
|
Src: PseudoCall,
|
||||||
Constant: -1,
|
Constant: -1,
|
||||||
Reference: label,
|
Reference: label,
|
||||||
}
|
}
|
||||||
|
|||||||
+16
-1
@@ -110,11 +110,26 @@ func LoadMapPtr(dst Register, fd int) Instruction {
|
|||||||
return Instruction{
|
return Instruction{
|
||||||
OpCode: LoadImmOp(DWord),
|
OpCode: LoadImmOp(DWord),
|
||||||
Dst: dst,
|
Dst: dst,
|
||||||
Src: R1,
|
Src: PseudoMapFD,
|
||||||
Constant: int64(fd),
|
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.
|
// LoadIndOp returns the OpCode for loading a value of given size from an sk_buff.
|
||||||
func LoadIndOp(size Size) OpCode {
|
func LoadIndOp(size Size) OpCode {
|
||||||
return OpCode(LdClass).SetMode(IndMode).SetSize(size)
|
return OpCode(LdClass).SetMode(IndMode).SetSize(size)
|
||||||
|
|||||||
+7
@@ -33,6 +33,13 @@ const (
|
|||||||
RFP = R10
|
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 {
|
func (r Register) String() string {
|
||||||
v := uint8(r)
|
v := uint8(r)
|
||||||
if v == 10 {
|
if v == 10 {
|
||||||
|
|||||||
+161
-16
@@ -1,8 +1,12 @@
|
|||||||
package ebpf
|
package ebpf
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"math"
|
||||||
|
|
||||||
"github.com/cilium/ebpf/asm"
|
"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.
|
// CollectionOptions control loading a collection into the kernel.
|
||||||
@@ -38,6 +42,89 @@ func (cs *CollectionSpec) Copy() *CollectionSpec {
|
|||||||
return &cpy
|
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
|
// Collection is a collection of Programs and Maps associated
|
||||||
// with their symbols
|
// with their symbols
|
||||||
type Collection struct {
|
type Collection struct {
|
||||||
@@ -55,45 +142,103 @@ func NewCollection(spec *CollectionSpec) (*Collection, error) {
|
|||||||
// NewCollectionWithOptions creates a Collection from a specification.
|
// NewCollectionWithOptions creates a Collection from a specification.
|
||||||
//
|
//
|
||||||
// Only maps referenced by at least one of the programs are initialized.
|
// Only maps referenced by at least one of the programs are initialized.
|
||||||
func NewCollectionWithOptions(spec *CollectionSpec, opts CollectionOptions) (*Collection, error) {
|
func NewCollectionWithOptions(spec *CollectionSpec, opts CollectionOptions) (coll *Collection, err error) {
|
||||||
maps := make(map[string]*Map)
|
var (
|
||||||
for mapName, mapSpec := range spec.Maps {
|
maps = make(map[string]*Map)
|
||||||
m, err := NewMap(mapSpec)
|
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 {
|
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
|
maps[mapName] = m
|
||||||
}
|
}
|
||||||
|
|
||||||
progs := make(map[string]*Program)
|
|
||||||
for progName, origProgSpec := range spec.Programs {
|
for progName, origProgSpec := range spec.Programs {
|
||||||
progSpec := origProgSpec.Copy()
|
progSpec := origProgSpec.Copy()
|
||||||
|
|
||||||
// Rewrite any reference to a valid map.
|
// Rewrite any reference to a valid map.
|
||||||
for i := range progSpec.Instructions {
|
for i := range progSpec.Instructions {
|
||||||
var (
|
ins := &progSpec.Instructions[i]
|
||||||
ins = &progSpec.Instructions[i]
|
|
||||||
m = maps[ins.Reference]
|
|
||||||
)
|
|
||||||
|
|
||||||
if ins.Reference == "" || m == nil {
|
if ins.OpCode != asm.LoadImmOp(asm.DWord) || ins.Reference == "" {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
if ins.Src == asm.R1 {
|
if uint32(ins.Constant) != math.MaxUint32 {
|
||||||
// Don't overwrite maps already rewritten, users can
|
// Don't overwrite maps already rewritten, users can
|
||||||
// rewrite programs in the spec themselves
|
// rewrite programs in the spec themselves
|
||||||
continue
|
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 {
|
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 {
|
if err != nil {
|
||||||
return nil, errors.Wrapf(err, "program %s", progName)
|
return nil, xerrors.Errorf("program %s: %w", progName, err)
|
||||||
}
|
}
|
||||||
progs[progName] = prog
|
progs[progName] = prog
|
||||||
}
|
}
|
||||||
|
|||||||
+448
-131
@@ -4,20 +4,25 @@ import (
|
|||||||
"bytes"
|
"bytes"
|
||||||
"debug/elf"
|
"debug/elf"
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
"fmt"
|
|
||||||
"io"
|
"io"
|
||||||
|
"math"
|
||||||
"os"
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/cilium/ebpf/asm"
|
"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 {
|
type elfCode struct {
|
||||||
*elf.File
|
*elf.File
|
||||||
symbols []elf.Symbol
|
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.
|
// LoadCollectionSpec parses an ELF file into a CollectionSpec.
|
||||||
@@ -29,12 +34,15 @@ func LoadCollectionSpec(file string) (*CollectionSpec, error) {
|
|||||||
defer f.Close()
|
defer f.Close()
|
||||||
|
|
||||||
spec, err := LoadCollectionSpecFromReader(f)
|
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.
|
// LoadCollectionSpecFromReader parses an ELF file into a CollectionSpec.
|
||||||
func LoadCollectionSpecFromReader(code io.ReaderAt) (*CollectionSpec, error) {
|
func LoadCollectionSpecFromReader(rd io.ReaderAt) (*CollectionSpec, error) {
|
||||||
f, err := elf.NewFile(code)
|
f, err := elf.NewFile(rd)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -42,15 +50,21 @@ func LoadCollectionSpecFromReader(code io.ReaderAt) (*CollectionSpec, error) {
|
|||||||
|
|
||||||
symbols, err := f.Symbols()
|
symbols, err := f.Symbols()
|
||||||
if err != nil {
|
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 {
|
for i, sec := range ec.Sections {
|
||||||
switch {
|
switch {
|
||||||
case strings.HasPrefix(sec.Name, "license"):
|
case strings.HasPrefix(sec.Name, "license"):
|
||||||
@@ -59,15 +73,19 @@ func LoadCollectionSpecFromReader(code io.ReaderAt) (*CollectionSpec, error) {
|
|||||||
versionSection = sec
|
versionSection = sec
|
||||||
case strings.HasPrefix(sec.Name, "maps"):
|
case strings.HasPrefix(sec.Name, "maps"):
|
||||||
mapSections[elf.SectionIndex(i)] = sec
|
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:
|
case sec.Type == elf.SHT_REL:
|
||||||
if int(sec.Info) >= len(ec.Sections) {
|
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
|
// Store relocations under the section index of the target
|
||||||
idx := elf.SectionIndex(sec.Info)
|
idx := elf.SectionIndex(sec.Info)
|
||||||
if relSections[idx] != nil {
|
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
|
relSections[idx] = sec
|
||||||
case sec.Type == elf.SHT_PROGBITS && (sec.Flags&elf.SHF_EXECINSTR) != 0 && sec.Size > 0:
|
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 {
|
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 {
|
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 {
|
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)
|
maps := make(map[string]*MapSpec)
|
||||||
if err != nil {
|
if err := ec.loadMaps(maps, mapSections); err != nil {
|
||||||
return nil, errors.Wrap(err, "load programs")
|
return nil, xerrors.Errorf("load maps: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(libs) > 0 {
|
if len(btfMaps) > 0 {
|
||||||
for name, prog := range progs {
|
if err := ec.loadBTFMaps(maps, btfMaps, btfSpec); err != nil {
|
||||||
prog.Instructions, err = link(prog.Instructions, libs...)
|
return nil, xerrors.Errorf("load BTF maps: %w", err)
|
||||||
if err != nil {
|
|
||||||
return nil, errors.Wrapf(err, "program %s", name)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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
|
return &CollectionSpec{maps, progs}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func loadLicense(sec *elf.Section) (string, error) {
|
func loadLicense(sec *elf.Section) (string, error) {
|
||||||
if sec == nil {
|
if sec == nil {
|
||||||
return "", errors.Errorf("missing license section")
|
return "", xerrors.New("missing license section")
|
||||||
}
|
}
|
||||||
data, err := sec.Data()
|
data, err := sec.Data()
|
||||||
if err != nil {
|
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
|
return string(bytes.TrimRight(data, "\000")), nil
|
||||||
}
|
}
|
||||||
@@ -124,92 +155,226 @@ func loadVersion(sec *elf.Section, bo binary.ByteOrder) (uint32, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var version uint32
|
var version uint32
|
||||||
err := binary.Read(sec.Open(), bo, &version)
|
if err := binary.Read(sec.Open(), bo, &version); err != nil {
|
||||||
return version, errors.Wrapf(err, "section %s", sec.Name)
|
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 (
|
var (
|
||||||
progs = make(map[string]*ProgramSpec)
|
progs []*ProgramSpec
|
||||||
libs []asm.Instructions
|
libs []*ProgramSpec
|
||||||
)
|
)
|
||||||
for idx, prog := range progSections {
|
|
||||||
|
for idx, sec := range progSections {
|
||||||
syms := ec.symbolsPerSection[idx]
|
syms := ec.symbolsPerSection[idx]
|
||||||
if len(syms) == 0 {
|
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]
|
funcSym, ok := syms[0]
|
||||||
if funcSym == "" {
|
if !ok {
|
||||||
return nil, nil, errors.Errorf("section %v: no label at start", prog.Name)
|
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 {
|
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)
|
progType, attachType := getProgType(sec.Name)
|
||||||
if err != nil {
|
|
||||||
return nil, nil, errors.Wrapf(err, "program %s: can't unmarshal instructions", funcSym)
|
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,
|
// There is no single name we can use for "library" sections,
|
||||||
// since they may contain multiple functions. We'll decode the
|
// since they may contain multiple functions. We'll decode the
|
||||||
// labels they contain later on, and then link sections that way.
|
// labels they contain later on, and then link sections that way.
|
||||||
libs = append(libs, insns)
|
libs = append(libs, spec)
|
||||||
} else {
|
} else {
|
||||||
progs[funcSym] = &ProgramSpec{
|
progs = append(progs, spec)
|
||||||
Name: funcSym,
|
|
||||||
Type: progType,
|
|
||||||
AttachType: attachType,
|
|
||||||
License: license,
|
|
||||||
KernelVersion: version,
|
|
||||||
Instructions: insns,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
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 (
|
var (
|
||||||
r = section.Open()
|
r = section.Open()
|
||||||
insns asm.Instructions
|
insns asm.Instructions
|
||||||
ins asm.Instruction
|
|
||||||
offset uint64
|
offset uint64
|
||||||
)
|
)
|
||||||
for {
|
for {
|
||||||
|
var ins asm.Instruction
|
||||||
n, err := ins.Unmarshal(r, ec.ByteOrder)
|
n, err := ins.Unmarshal(r, ec.ByteOrder)
|
||||||
if err == io.EOF {
|
if err == io.EOF {
|
||||||
return insns, nil
|
return insns, offset, nil
|
||||||
}
|
}
|
||||||
if err != 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.Symbol = symbols[offset].Name
|
||||||
ins.Reference = relocations[offset]
|
|
||||||
|
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)
|
insns = append(insns, ins)
|
||||||
offset += n
|
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 (
|
var (
|
||||||
maps = make(map[string]*MapSpec)
|
typ = elf.ST_TYPE(rel.Info)
|
||||||
b = make([]byte, 1)
|
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 {
|
for idx, sec := range mapSections {
|
||||||
syms := ec.symbolsPerSection[idx]
|
syms := ec.symbolsPerSection[idx]
|
||||||
if len(syms) == 0 {
|
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 {
|
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 (
|
var (
|
||||||
@@ -217,66 +382,212 @@ func (ec *elfCode) loadMaps(mapSections map[elf.SectionIndex]*elf.Section) (map[
|
|||||||
size = sec.Size / uint64(len(syms))
|
size = sec.Size / uint64(len(syms))
|
||||||
)
|
)
|
||||||
for i, offset := 0, uint64(0); i < len(syms); i, offset = i+1, offset+size {
|
for i, offset := 0, uint64(0); i < len(syms); i, offset = i+1, offset+size {
|
||||||
mapSym := syms[offset]
|
mapSym, ok := syms[offset]
|
||||||
if mapSym == "" {
|
if !ok {
|
||||||
fmt.Println(syms)
|
return xerrors.Errorf("section %s: missing symbol for map at offset %d", sec.Name, offset)
|
||||||
return nil, errors.Errorf("section %s: missing symbol for map at offset %d", sec.Name, offset)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if maps[mapSym] != nil {
|
if maps[mapSym.Name] != nil {
|
||||||
return nil, errors.Errorf("section %v: map %v already exists", sec.Name, mapSym)
|
return xerrors.Errorf("section %v: map %v already exists", sec.Name, mapSym)
|
||||||
}
|
}
|
||||||
|
|
||||||
lr := io.LimitReader(r, int64(size))
|
lr := io.LimitReader(r, int64(size))
|
||||||
|
|
||||||
var spec MapSpec
|
spec := MapSpec{
|
||||||
|
Name: SanitizeName(mapSym.Name, -1),
|
||||||
|
}
|
||||||
switch {
|
switch {
|
||||||
case binary.Read(lr, ec.ByteOrder, &spec.Type) != nil:
|
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:
|
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:
|
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:
|
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:
|
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 {
|
if _, err := io.Copy(internal.DiscardZeroes{}, lr); err != nil {
|
||||||
_, err := lr.Read(b)
|
return xerrors.Errorf("map %v: unknown and non-zero fields in definition", mapSym)
|
||||||
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)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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) {
|
func getProgType(v string) (ProgramType, AttachType) {
|
||||||
types := map[string]ProgramType{
|
types := map[string]ProgramType{
|
||||||
// From https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/tools/lib/bpf/libbpf.c#n3568
|
// From https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/tools/lib/bpf/libbpf.c#n3568
|
||||||
"socket": SocketFilter,
|
"socket": SocketFilter,
|
||||||
"seccomp": SocketFilter,
|
"seccomp": SocketFilter,
|
||||||
"kprobe/": Kprobe,
|
"kprobe/": Kprobe,
|
||||||
"kretprobe/": Kprobe,
|
"uprobe/": Kprobe,
|
||||||
"tracepoint/": TracePoint,
|
"kretprobe/": Kprobe,
|
||||||
"xdp": XDP,
|
"uretprobe/": Kprobe,
|
||||||
"perf_event": PerfEvent,
|
"tracepoint/": TracePoint,
|
||||||
"sockops": SockOps,
|
"raw_tracepoint/": RawTracepoint,
|
||||||
"sk_skb": SkSKB,
|
"xdp": XDP,
|
||||||
"sk_msg": SkMsg,
|
"perf_event": PerfEvent,
|
||||||
"lirc_mode2": LircMode2,
|
"lwt_in": LWTIn,
|
||||||
"flow_dissector": FlowDissector,
|
"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_skb/": CGroupSKB,
|
||||||
"cgroup/dev": CGroupDevice,
|
"cgroup/dev": CGroupDevice,
|
||||||
@@ -333,38 +644,40 @@ func getProgType(v string) (ProgramType, AttachType) {
|
|||||||
return UnspecifiedProgram, AttachNone
|
return UnspecifiedProgram, AttachNone
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ec *elfCode) loadRelocations(sec *elf.Section) (map[uint64]string, error) {
|
func (ec *elfCode) loadRelocations(sections map[elf.SectionIndex]*elf.Section) (map[elf.SectionIndex]map[uint64]elf.Symbol, error) {
|
||||||
rels := make(map[uint64]string)
|
result := make(map[elf.SectionIndex]map[uint64]elf.Symbol)
|
||||||
if sec == nil {
|
for idx, sec := range sections {
|
||||||
return rels, nil
|
rels := make(map[uint64]elf.Symbol)
|
||||||
}
|
|
||||||
|
|
||||||
if sec.Entsize < 16 {
|
if sec.Entsize < 16 {
|
||||||
return nil, errors.New("rels are less than 16 bytes")
|
return nil, xerrors.Errorf("section %s: relocations are less than 16 bytes", sec.Name)
|
||||||
}
|
|
||||||
|
|
||||||
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)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
symNo := int(elf.R_SYM64(rel.Info) - 1)
|
r := sec.Open()
|
||||||
if symNo >= len(ec.symbols) {
|
for off := uint64(0); off < sec.Size; off += sec.Entsize {
|
||||||
return nil, errors.Errorf("relocation at offset %d: symbol %v doesnt exist", off, symNo)
|
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 {
|
func symbolsPerSection(symbols []elf.Symbol) map[elf.SectionIndex]map[uint64]elf.Symbol {
|
||||||
result := make(map[elf.SectionIndex]map[uint64]string)
|
result := make(map[elf.SectionIndex]map[uint64]elf.Symbol)
|
||||||
for i, sym := range symbols {
|
for _, sym := range symbols {
|
||||||
switch elf.ST_TYPE(sym.Info) {
|
switch elf.ST_TYPE(sym.Info) {
|
||||||
case elf.STT_NOTYPE:
|
case elf.STT_NOTYPE:
|
||||||
// Older versions of LLVM doesn't tag
|
// Older versions of LLVM doesn't tag
|
||||||
@@ -378,15 +691,19 @@ func symbolsPerSection(symbols []elf.Symbol) map[elf.SectionIndex]map[uint64]str
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if sym.Section == elf.SHN_UNDEF || sym.Section >= elf.SHN_LORESERVE {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
if sym.Name == "" {
|
if sym.Name == "" {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
idx := sym.Section
|
idx := sym.Section
|
||||||
if _, ok := result[idx]; !ok {
|
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
|
return result
|
||||||
}
|
}
|
||||||
|
|||||||
-19
@@ -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
|
|
||||||
}
|
|
||||||
+2
-2
@@ -3,6 +3,6 @@ module github.com/cilium/ebpf
|
|||||||
go 1.12
|
go 1.12
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/pkg/errors v0.8.1
|
golang.org/x/sys v0.0.0-20200124204421-9fbb57f87de9
|
||||||
golang.org/x/sys v0.0.0-20191022100944-742c48ecaeb7
|
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543
|
||||||
)
|
)
|
||||||
|
|||||||
+4
-2
@@ -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 h1:HmbHVPwrPEKPGLAcHSrMe6+hqSUlvZU0rab6x5EXfGU=
|
||||||
golang.org/x/sys v0.0.0-20191022100944-742c48ecaeb7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
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=
|
||||||
|
|||||||
+613
@@ -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)
|
||||||
|
})
|
||||||
+238
@@ -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})
|
||||||
|
}
|
||||||
|
}
|
||||||
+8
@@ -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
|
||||||
+182
@@ -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,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+60
@@ -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
|
||||||
|
}
|
||||||
+586
@@ -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
|
||||||
|
}
|
||||||
+29
-31
@@ -2,10 +2,9 @@ package internal
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"io/ioutil"
|
||||||
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"github.com/pkg/errors"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var sysCPU struct {
|
var sysCPU struct {
|
||||||
@@ -18,45 +17,44 @@ var sysCPU struct {
|
|||||||
// Logical CPU numbers must be of the form 0-n
|
// Logical CPU numbers must be of the form 0-n
|
||||||
func PossibleCPUs() (int, error) {
|
func PossibleCPUs() (int, error) {
|
||||||
sysCPU.once.Do(func() {
|
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
|
return sysCPU.num, sysCPU.err
|
||||||
}
|
}
|
||||||
|
|
||||||
var onlineCPU struct {
|
func parseCPUsFromFile(path string) (int, error) {
|
||||||
once sync.Once
|
spec, err := ioutil.ReadFile(path)
|
||||||
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)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, err
|
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
|
var low, high int
|
||||||
n, _ := fmt.Fscanf(file, "%d-%d", &low, &high)
|
n, err := fmt.Sscanf(spec, "%d-%d\n", &low, &high)
|
||||||
if n < 1 || low != 0 {
|
if n != 2 || err != nil {
|
||||||
return 0, errors.Wrapf(err, "%s has unknown format", path)
|
return 0, fmt.Errorf("invalid format: %s", spec)
|
||||||
}
|
}
|
||||||
if n == 1 {
|
if low != 0 {
|
||||||
high = low
|
return 0, fmt.Errorf("CPU spec doesn't start at zero: %s", spec)
|
||||||
}
|
}
|
||||||
|
|
||||||
// cpus is 0 indexed
|
// cpus is 0 indexed
|
||||||
|
|||||||
+47
@@ -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])
|
||||||
|
}
|
||||||
+63
@@ -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
|
||||||
|
}
|
||||||
+93
@@ -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
|
||||||
|
}
|
||||||
+16
@@ -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
|
||||||
|
}
|
||||||
+30
@@ -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])}
|
||||||
|
}
|
||||||
Generated
Vendored
+3
-3
@@ -1,14 +1,14 @@
|
|||||||
// +build armbe mips mips64p32
|
// +build armbe mips mips64p32
|
||||||
|
|
||||||
package ebpf
|
package internal
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"unsafe"
|
"unsafe"
|
||||||
)
|
)
|
||||||
|
|
||||||
// ptr wraps an unsafe.Pointer to be 64bit to
|
// Pointer wraps an unsafe.Pointer to be 64bit to
|
||||||
// conform to the syscall specification.
|
// conform to the syscall specification.
|
||||||
type syscallPtr struct {
|
type Pointer struct {
|
||||||
pad uint32
|
pad uint32
|
||||||
ptr unsafe.Pointer
|
ptr unsafe.Pointer
|
||||||
}
|
}
|
||||||
Generated
Vendored
+3
-3
@@ -1,14 +1,14 @@
|
|||||||
// +build 386 amd64p32 arm mipsle mips64p32le
|
// +build 386 amd64p32 arm mipsle mips64p32le
|
||||||
|
|
||||||
package ebpf
|
package internal
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"unsafe"
|
"unsafe"
|
||||||
)
|
)
|
||||||
|
|
||||||
// ptr wraps an unsafe.Pointer to be 64bit to
|
// Pointer wraps an unsafe.Pointer to be 64bit to
|
||||||
// conform to the syscall specification.
|
// conform to the syscall specification.
|
||||||
type syscallPtr struct {
|
type Pointer struct {
|
||||||
ptr unsafe.Pointer
|
ptr unsafe.Pointer
|
||||||
pad uint32
|
pad uint32
|
||||||
}
|
}
|
||||||
Generated
Vendored
+3
-3
@@ -1,14 +1,14 @@
|
|||||||
// +build !386,!amd64p32,!arm,!mipsle,!mips64p32le
|
// +build !386,!amd64p32,!arm,!mipsle,!mips64p32le
|
||||||
// +build !armbe,!mips,!mips64p32
|
// +build !armbe,!mips,!mips64p32
|
||||||
|
|
||||||
package ebpf
|
package internal
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"unsafe"
|
"unsafe"
|
||||||
)
|
)
|
||||||
|
|
||||||
// ptr wraps an unsafe.Pointer to be 64bit to
|
// Pointer wraps an unsafe.Pointer to be 64bit to
|
||||||
// conform to the syscall specification.
|
// conform to the syscall specification.
|
||||||
type syscallPtr struct {
|
type Pointer struct {
|
||||||
ptr unsafe.Pointer
|
ptr unsafe.Pointer
|
||||||
}
|
}
|
||||||
+23
@@ -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
|
||||||
|
}
|
||||||
+29
@@ -14,6 +14,11 @@ const (
|
|||||||
ENOSPC = linux.ENOSPC
|
ENOSPC = linux.ENOSPC
|
||||||
EINVAL = linux.EINVAL
|
EINVAL = linux.EINVAL
|
||||||
EPOLLIN = linux.EPOLLIN
|
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_OBJ_NAME_LEN = linux.BPF_OBJ_NAME_LEN
|
||||||
BPF_TAG_SIZE = linux.BPF_TAG_SIZE
|
BPF_TAG_SIZE = linux.BPF_TAG_SIZE
|
||||||
SYS_BPF = linux.SYS_BPF
|
SYS_BPF = linux.SYS_BPF
|
||||||
@@ -30,6 +35,7 @@ const (
|
|||||||
PerfBitWatermark = linux.PerfBitWatermark
|
PerfBitWatermark = linux.PerfBitWatermark
|
||||||
PERF_SAMPLE_RAW = linux.PERF_SAMPLE_RAW
|
PERF_SAMPLE_RAW = linux.PERF_SAMPLE_RAW
|
||||||
PERF_FLAG_FD_CLOEXEC = linux.PERF_FLAG_FD_CLOEXEC
|
PERF_FLAG_FD_CLOEXEC = linux.PERF_FLAG_FD_CLOEXEC
|
||||||
|
RLIM_INFINITY = linux.RLIM_INFINITY
|
||||||
)
|
)
|
||||||
|
|
||||||
// Statfs_t is a wrapper
|
// 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) {
|
func PerfEventOpen(attr *PerfEventAttr, pid int, cpu int, groupFd int, flags int) (fd int, err error) {
|
||||||
return linux.PerfEventOpen(attr, pid, cpu, groupFd, flags)
|
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)
|
||||||
|
}
|
||||||
|
|||||||
+31
@@ -15,6 +15,11 @@ const (
|
|||||||
EAGAIN = syscall.EAGAIN
|
EAGAIN = syscall.EAGAIN
|
||||||
ENOSPC = syscall.ENOSPC
|
ENOSPC = syscall.ENOSPC
|
||||||
EINVAL = syscall.EINVAL
|
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_OBJ_NAME_LEN = 0x10
|
||||||
BPF_TAG_SIZE = 0x8
|
BPF_TAG_SIZE = 0x8
|
||||||
SYS_BPF = 321
|
SYS_BPF = 321
|
||||||
@@ -32,6 +37,7 @@ const (
|
|||||||
PerfBitWatermark = 0x4000
|
PerfBitWatermark = 0x4000
|
||||||
PERF_SAMPLE_RAW = 0x400
|
PERF_SAMPLE_RAW = 0x400
|
||||||
PERF_FLAG_FD_CLOEXEC = 0x8
|
PERF_FLAG_FD_CLOEXEC = 0x8
|
||||||
|
RLIM_INFINITY = 0x7fffffffffffffff
|
||||||
)
|
)
|
||||||
|
|
||||||
// Statfs_t is a wrapper
|
// 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) {
|
func PerfEventOpen(attr *PerfEventAttr, pid int, cpu int, groupFd int, flags int) (fd int, err error) {
|
||||||
return 0, errNonLinux
|
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
|
||||||
|
}
|
||||||
|
|||||||
+46
-18
@@ -2,30 +2,59 @@ package ebpf
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/cilium/ebpf/asm"
|
"github.com/cilium/ebpf/asm"
|
||||||
|
"github.com/cilium/ebpf/internal/btf"
|
||||||
|
|
||||||
|
"golang.org/x/xerrors"
|
||||||
)
|
)
|
||||||
|
|
||||||
// link resolves bpf-to-bpf calls.
|
// link resolves bpf-to-bpf calls.
|
||||||
//
|
//
|
||||||
// Each section may contain multiple functions / labels, and is only linked
|
// Each library may contain multiple functions / labels, and is only linked
|
||||||
// if the program being edited references one of these functions.
|
// if prog references one of these functions.
|
||||||
//
|
//
|
||||||
// Sections must not require linking themselves.
|
// Libraries also linked.
|
||||||
func link(insns asm.Instructions, sections ...asm.Instructions) (asm.Instructions, error) {
|
func link(prog *ProgramSpec, libs []*ProgramSpec) error {
|
||||||
for _, section := range sections {
|
var (
|
||||||
var err error
|
linked = make(map[*ProgramSpec]bool)
|
||||||
insns, err = linkSection(insns, section)
|
pending = []asm.Instructions{prog.Instructions}
|
||||||
if err != nil {
|
insns asm.Instructions
|
||||||
return nil, err
|
)
|
||||||
|
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.
|
// A map of symbols to the libraries which contain them.
|
||||||
symbols, err := section.SymbolOffsets()
|
symbols, err := section.SymbolOffsets()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return false, err
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, ins := range insns {
|
for _, ins := range insns {
|
||||||
@@ -33,7 +62,7 @@ func linkSection(insns, section asm.Instructions) (asm.Instructions, error) {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
if ins.OpCode.JumpOp() != asm.Call || ins.Src != asm.R1 {
|
if ins.OpCode.JumpOp() != asm.Call || ins.Src != asm.PseudoCall {
|
||||||
continue
|
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
|
// At this point we know that at least one function in the
|
||||||
// library is called from insns. Merge the two sections.
|
// library is called from insns, so we have to link it.
|
||||||
// The rewrite of ins.Constant happens in asm.Instruction.Marshal.
|
return true, nil
|
||||||
return append(insns, section...), nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// None of the functions in the section are called. Do nothing.
|
// None of the functions in the section are called.
|
||||||
return insns, nil
|
return false, nil
|
||||||
}
|
}
|
||||||
|
|||||||
+311
-112
@@ -2,14 +2,24 @@ package ebpf
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"unsafe"
|
"strings"
|
||||||
|
|
||||||
"github.com/cilium/ebpf/internal"
|
"github.com/cilium/ebpf/internal"
|
||||||
|
"github.com/cilium/ebpf/internal/btf"
|
||||||
"github.com/cilium/ebpf/internal/unix"
|
"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.
|
// MapSpec defines a Map.
|
||||||
type MapSpec struct {
|
type MapSpec struct {
|
||||||
// Name is passed to the kernel as a debug aid. Must only contain
|
// Name is passed to the kernel as a debug aid. Must only contain
|
||||||
@@ -20,8 +30,18 @@ type MapSpec struct {
|
|||||||
ValueSize uint32
|
ValueSize uint32
|
||||||
MaxEntries uint32
|
MaxEntries uint32
|
||||||
Flags 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 is used as a template for ArrayOfMaps and HashOfMaps
|
||||||
InnerMap *MapSpec
|
InnerMap *MapSpec
|
||||||
|
|
||||||
|
// The BTF associated with this map.
|
||||||
|
BTF *btf.Map
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ms *MapSpec) String() string {
|
func (ms *MapSpec) String() string {
|
||||||
@@ -29,16 +49,26 @@ func (ms *MapSpec) String() string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Copy returns a copy of the spec.
|
// Copy returns a copy of the spec.
|
||||||
|
//
|
||||||
|
// MapSpec.Contents is a shallow copy.
|
||||||
func (ms *MapSpec) Copy() *MapSpec {
|
func (ms *MapSpec) Copy() *MapSpec {
|
||||||
if ms == nil {
|
if ms == nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
cpy := *ms
|
cpy := *ms
|
||||||
|
cpy.Contents = make([]MapKV, len(ms.Contents))
|
||||||
|
copy(cpy.Contents, ms.Contents)
|
||||||
cpy.InnerMap = ms.InnerMap.Copy()
|
cpy.InnerMap = ms.InnerMap.Copy()
|
||||||
return &cpy
|
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.
|
// Map represents a Map file descriptor.
|
||||||
//
|
//
|
||||||
// It is not safe to close a map which is used by other goroutines.
|
// 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
|
// Implement encoding.BinaryMarshaler or encoding.BinaryUnmarshaler
|
||||||
// if you require custom encoding.
|
// if you require custom encoding.
|
||||||
type Map struct {
|
type Map struct {
|
||||||
fd *bpfFD
|
name string
|
||||||
abi MapABI
|
fd *internal.FD
|
||||||
|
abi MapABI
|
||||||
// Per CPU maps return values larger than the size in the spec
|
// Per CPU maps return values larger than the size in the spec
|
||||||
fullValueSize int
|
fullValueSize int
|
||||||
}
|
}
|
||||||
@@ -60,16 +91,16 @@ type Map struct {
|
|||||||
// You should not use fd after calling this function.
|
// You should not use fd after calling this function.
|
||||||
func NewMapFromFD(fd int) (*Map, error) {
|
func NewMapFromFD(fd int) (*Map, error) {
|
||||||
if fd < 0 {
|
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 {
|
if err != nil {
|
||||||
bpfFd.forget()
|
bpfFd.Forget()
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return newMap(bpfFd, abi)
|
return newMap(bpfFd, name, abi)
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewMap creates a new Map.
|
// 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
|
// Creating a map for the first time will perform feature detection
|
||||||
// by creating small, temporary maps.
|
// by creating small, temporary maps.
|
||||||
func NewMap(spec *MapSpec) (*Map, error) {
|
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 {
|
if spec.Type != ArrayOfMaps && spec.Type != HashOfMaps {
|
||||||
return createMap(spec, nil)
|
return createMap(spec, nil, handle)
|
||||||
}
|
}
|
||||||
|
|
||||||
if spec.InnerMap == nil {
|
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 {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
defer template.Close()
|
defer template.Close()
|
||||||
|
|
||||||
return createMap(spec, template.fd)
|
return createMap(spec, template.fd, handle)
|
||||||
}
|
}
|
||||||
|
|
||||||
func createMap(spec *MapSpec, inner *bpfFD) (*Map, error) {
|
func createMap(spec *MapSpec, inner *internal.FD, handle *btf.Handle) (*Map, error) {
|
||||||
spec = spec.Copy()
|
abi := newMapABIFromSpec(spec)
|
||||||
|
|
||||||
switch spec.Type {
|
switch spec.Type {
|
||||||
case ArrayOfMaps:
|
case ArrayOfMaps:
|
||||||
fallthrough
|
fallthrough
|
||||||
case HashOfMaps:
|
case HashOfMaps:
|
||||||
if spec.ValueSize != 0 && spec.ValueSize != 4 {
|
if err := haveNestedMaps(); err != nil {
|
||||||
return nil, errors.Errorf("ValueSize must be zero or four for map of map")
|
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:
|
case PerfEventArray:
|
||||||
if spec.KeySize != 0 {
|
if abi.KeySize != 0 && abi.KeySize != 4 {
|
||||||
return nil, errors.Errorf("KeySize must be zero for perf event array")
|
return nil, xerrors.New("KeySize must be zero or four 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)
|
|
||||||
}
|
}
|
||||||
|
abi.KeySize = 4
|
||||||
|
|
||||||
spec.KeySize = 4
|
if abi.ValueSize != 0 && abi.ValueSize != 4 {
|
||||||
spec.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{
|
attr := bpfMapCreateAttr{
|
||||||
mapType: spec.Type,
|
mapType: abi.Type,
|
||||||
keySize: spec.KeySize,
|
keySize: abi.KeySize,
|
||||||
valueSize: spec.ValueSize,
|
valueSize: abi.ValueSize,
|
||||||
maxEntries: spec.MaxEntries,
|
maxEntries: abi.MaxEntries,
|
||||||
flags: spec.Flags,
|
flags: abi.Flags,
|
||||||
}
|
}
|
||||||
|
|
||||||
if inner != nil {
|
if inner != nil {
|
||||||
var err error
|
var err error
|
||||||
attr.innerMapFd, err = inner.value()
|
attr.innerMapFd, err = inner.Value()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.Wrap(err, "map create")
|
return nil, xerrors.Errorf("map create: %w", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
name, err := newBPFObjName(spec.Name)
|
if handle != nil && spec.BTF != nil {
|
||||||
if err != nil {
|
attr.btfFd = uint32(handle.FD())
|
||||||
return nil, errors.Wrap(err, "map create")
|
attr.btfKeyTypeID = btf.MapKey(spec.BTF).ID()
|
||||||
|
attr.btfValueTypeID = btf.MapValue(spec.BTF).ID()
|
||||||
}
|
}
|
||||||
|
|
||||||
if haveObjName.Result() {
|
if haveObjName() == nil {
|
||||||
attr.mapName = name
|
attr.mapName = newBPFObjName(spec.Name)
|
||||||
}
|
}
|
||||||
|
|
||||||
fd, err := bpfMapCreate(&attr)
|
fd, err := bpfMapCreate(&attr)
|
||||||
if err != nil {
|
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{
|
m := &Map{
|
||||||
|
name,
|
||||||
fd,
|
fd,
|
||||||
*abi,
|
*abi,
|
||||||
int(abi.ValueSize),
|
int(abi.ValueSize),
|
||||||
@@ -179,7 +253,10 @@ func newMap(fd *bpfFD, abi *MapABI) (*Map, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (m *Map) String() string {
|
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
|
// ABI gets the ABI of the Map
|
||||||
@@ -219,9 +296,9 @@ func (m *Map) Lookup(key, valueOut interface{}) error {
|
|||||||
*value = m
|
*value = m
|
||||||
return nil
|
return nil
|
||||||
case *Map:
|
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:
|
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:
|
case **Program:
|
||||||
p, err := unmarshalProgram(valueBytes)
|
p, err := unmarshalProgram(valueBytes)
|
||||||
@@ -233,38 +310,58 @@ func (m *Map) Lookup(key, valueOut interface{}) error {
|
|||||||
*value = p
|
*value = p
|
||||||
return nil
|
return nil
|
||||||
case *Program:
|
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:
|
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:
|
default:
|
||||||
return unmarshalBytes(valueOut, valueBytes)
|
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.
|
// LookupBytes gets a value from Map.
|
||||||
//
|
//
|
||||||
// Returns a nil value if a key doesn't exist.
|
// Returns a nil value if a key doesn't exist.
|
||||||
func (m *Map) LookupBytes(key interface{}) ([]byte, error) {
|
func (m *Map) LookupBytes(key interface{}) ([]byte, error) {
|
||||||
valueBytes := make([]byte, m.fullValueSize)
|
valueBytes := make([]byte, m.fullValueSize)
|
||||||
valuePtr := newPtr(unsafe.Pointer(&valueBytes[0]))
|
valuePtr := internal.NewSlicePointer(valueBytes)
|
||||||
|
|
||||||
err := m.lookup(key, valuePtr)
|
err := m.lookup(key, valuePtr)
|
||||||
if IsNotExist(err) {
|
if xerrors.Is(err, ErrKeyNotExist) {
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
return valueBytes, err
|
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))
|
keyPtr, err := marshalPtr(key, int(m.abi.KeySize))
|
||||||
if err != nil {
|
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)
|
if err = bpfMapLookupElem(m.fd, keyPtr, valueOut); err != nil {
|
||||||
return errors.WithMessage(err, "lookup failed")
|
return xerrors.Errorf("lookup failed: %w", err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// MapUpdateFlags controls the behaviour of the Map.Update call.
|
// 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 {
|
func (m *Map) Update(key, value interface{}, flags MapUpdateFlags) error {
|
||||||
keyPtr, err := marshalPtr(key, int(m.abi.KeySize))
|
keyPtr, err := marshalPtr(key, int(m.abi.KeySize))
|
||||||
if err != nil {
|
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() {
|
if m.abi.Type.hasPerCPUValue() {
|
||||||
valuePtr, err = marshalPerCPUValue(value, int(m.abi.ValueSize))
|
valuePtr, err = marshalPerCPUValue(value, int(m.abi.ValueSize))
|
||||||
} else {
|
} else {
|
||||||
valuePtr, err = marshalPtr(value, int(m.abi.ValueSize))
|
valuePtr, err = marshalPtr(value, int(m.abi.ValueSize))
|
||||||
}
|
}
|
||||||
if err != nil {
|
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.
|
// 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 {
|
func (m *Map) Delete(key interface{}) error {
|
||||||
keyPtr, err := marshalPtr(key, int(m.abi.KeySize))
|
keyPtr, err := marshalPtr(key, int(m.abi.KeySize))
|
||||||
if err != nil {
|
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)
|
if err = bpfMapDeleteElem(m.fd, keyPtr); err != nil {
|
||||||
return errors.WithMessage(err, "can't delete key")
|
return xerrors.Errorf("delete failed: %w", err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// NextKey finds the key following an initial key.
|
// NextKey finds the key following an initial key.
|
||||||
//
|
//
|
||||||
// See NextKeyBytes for details.
|
// See NextKeyBytes for details.
|
||||||
|
//
|
||||||
|
// Returns ErrKeyNotExist if there is no next key.
|
||||||
func (m *Map) NextKey(key, nextKeyOut interface{}) error {
|
func (m *Map) NextKey(key, nextKeyOut interface{}) error {
|
||||||
nextKeyPtr, nextKeyBytes := makeBuffer(nextKeyOut, int(m.abi.KeySize))
|
nextKeyPtr, nextKeyBytes := makeBuffer(nextKeyOut, int(m.abi.KeySize))
|
||||||
|
|
||||||
@@ -335,8 +440,10 @@ func (m *Map) NextKey(key, nextKeyOut interface{}) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
err := unmarshalBytes(nextKeyOut, nextKeyBytes)
|
if err := unmarshalBytes(nextKeyOut, nextKeyBytes); err != nil {
|
||||||
return errors.WithMessage(err, "can't unmarshal next key")
|
return xerrors.Errorf("can't unmarshal next key: %w", err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// NextKeyBytes returns the key following an initial key as a byte slice.
|
// 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.
|
// Passing nil will return the first key.
|
||||||
//
|
//
|
||||||
// Use Iterate if you want to traverse all entries in the map.
|
// 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) {
|
func (m *Map) NextKeyBytes(key interface{}) ([]byte, error) {
|
||||||
nextKey := make([]byte, m.abi.KeySize)
|
nextKey := make([]byte, m.abi.KeySize)
|
||||||
nextKeyPtr := newPtr(unsafe.Pointer(&nextKey[0]))
|
nextKeyPtr := internal.NewSlicePointer(nextKey)
|
||||||
|
|
||||||
err := m.nextKey(key, nextKeyPtr)
|
err := m.nextKey(key, nextKeyPtr)
|
||||||
if IsNotExist(err) {
|
if xerrors.Is(err, ErrKeyNotExist) {
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
return nextKey, err
|
return nextKey, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *Map) nextKey(key interface{}, nextKeyOut syscallPtr) error {
|
func (m *Map) nextKey(key interface{}, nextKeyOut internal.Pointer) error {
|
||||||
var (
|
var (
|
||||||
keyPtr syscallPtr
|
keyPtr internal.Pointer
|
||||||
err error
|
err error
|
||||||
)
|
)
|
||||||
|
|
||||||
if key != nil {
|
if key != nil {
|
||||||
keyPtr, err = marshalPtr(key, int(m.abi.KeySize))
|
keyPtr, err = marshalPtr(key, int(m.abi.KeySize))
|
||||||
if err != nil {
|
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)
|
if err = bpfMapGetNextKey(m.fd, keyPtr, nextKeyOut); err != nil {
|
||||||
return errors.WithMessage(err, "can't get next key")
|
return xerrors.Errorf("next key failed: %w", err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Iterate traverses a map.
|
// Iterate traverses a map.
|
||||||
@@ -391,14 +502,14 @@ func (m *Map) Close() error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
return m.fd.close()
|
return m.fd.Close()
|
||||||
}
|
}
|
||||||
|
|
||||||
// FD gets the file descriptor of the Map.
|
// FD gets the file descriptor of the Map.
|
||||||
//
|
//
|
||||||
// Calling this function is invalid after Close has been called.
|
// Calling this function is invalid after Close has been called.
|
||||||
func (m *Map) FD() int {
|
func (m *Map) FD() int {
|
||||||
fd, err := m.fd.value()
|
fd, err := m.fd.Value()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// Best effort: -1 is the number most likely to be an
|
// Best effort: -1 is the number most likely to be an
|
||||||
// invalid file descriptor.
|
// invalid file descriptor.
|
||||||
@@ -419,12 +530,12 @@ func (m *Map) Clone() (*Map, error) {
|
|||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
dup, err := m.fd.dup()
|
dup, err := m.fd.Dup()
|
||||||
if err != nil {
|
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.
|
// 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)
|
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.
|
// LoadPinnedMap load a Map from a BPF file.
|
||||||
//
|
//
|
||||||
// Requires at least Linux 4.13, and is not compatible with
|
// The function is not compatible with nested maps.
|
||||||
// nested maps. Use LoadPinnedMapExplicit in these situations.
|
// Use LoadPinnedMapExplicit in these situations.
|
||||||
func LoadPinnedMap(fileName string) (*Map, error) {
|
func LoadPinnedMap(fileName string) (*Map, error) {
|
||||||
fd, err := bpfGetObject(fileName)
|
fd, err := bpfGetObject(fileName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
abi, err := newMapABIFromFd(fd)
|
name, abi, err := newMapABIFromFd(fd)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
_ = fd.close()
|
_ = fd.Close()
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return newMap(fd, abi)
|
return newMap(fd, name, abi)
|
||||||
}
|
}
|
||||||
|
|
||||||
// LoadPinnedMapExplicit loads a map with explicit parameters.
|
// LoadPinnedMapExplicit loads a map with explicit parameters.
|
||||||
@@ -457,34 +591,23 @@ func LoadPinnedMapExplicit(fileName string, abi *MapABI) (*Map, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return newMap(fd, abi)
|
return newMap(fd, "", abi)
|
||||||
}
|
}
|
||||||
|
|
||||||
func unmarshalMap(buf []byte) (*Map, error) {
|
func unmarshalMap(buf []byte) (*Map, error) {
|
||||||
if len(buf) != 4 {
|
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,
|
// Looking up an entry in a nested map or prog array returns an id,
|
||||||
// not an fd.
|
// not an fd.
|
||||||
id := internal.NativeEndian.Uint32(buf)
|
id := internal.NativeEndian.Uint32(buf)
|
||||||
fd, err := bpfGetMapFDByID(id)
|
return NewMapFromID(MapID(id))
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
abi, err := newMapABIFromFd(fd)
|
|
||||||
if err != nil {
|
|
||||||
_ = fd.close()
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return newMap(fd, abi)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// MarshalBinary implements BinaryMarshaler.
|
// MarshalBinary implements BinaryMarshaler.
|
||||||
func (m *Map) MarshalBinary() ([]byte, error) {
|
func (m *Map) MarshalBinary() ([]byte, error) {
|
||||||
fd, err := m.fd.value()
|
fd, err := m.fd.Value()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -494,6 +617,60 @@ func (m *Map) MarshalBinary() ([]byte, error) {
|
|||||||
return buf, nil
|
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.
|
// MapIterator iterates a Map.
|
||||||
//
|
//
|
||||||
// See Map.Iterate.
|
// 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.
|
// Next decodes the next key and value.
|
||||||
//
|
//
|
||||||
// Iterating a hash map from which keys are being deleted is not
|
// 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.prevKey = mi.prevBytes
|
||||||
|
|
||||||
mi.err = mi.target.Lookup(nextBytes, valueOut)
|
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
|
// Even though the key should be valid, we couldn't look up
|
||||||
// its value. If we're iterating a hash map this is probably
|
// its value. If we're iterating a hash map this is probably
|
||||||
// because a concurrent delete removed the value before we
|
// 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
|
return mi.err == nil
|
||||||
}
|
}
|
||||||
|
|
||||||
mi.err = errIterationAborted
|
mi.err = xerrors.Errorf("%w", ErrIterationAborted)
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
// Err returns any encountered error.
|
// Err returns any encountered error.
|
||||||
//
|
//
|
||||||
// The method must be called after Next returns nil.
|
// 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 {
|
func (mi *MapIterator) Err() error {
|
||||||
return mi.err
|
return mi.err
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsNotExist returns true if the error indicates that a
|
// MapGetNextID returns the ID of the next eBPF map.
|
||||||
// key doesn't exist.
|
//
|
||||||
func IsNotExist(err error) bool {
|
// Returns ErrNotExist, if there is no next eBPF map.
|
||||||
return errors.Cause(err) == unix.ENOENT
|
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.
|
// Returns ErrNotExist, if there is no eBPF map with the given id.
|
||||||
func IsIterationAborted(err error) bool {
|
func NewMapFromID(id MapID) (*Map, error) {
|
||||||
return errors.Cause(err) == errIterationAborted
|
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
|
||||||
}
|
}
|
||||||
|
|||||||
+35
-24
@@ -10,20 +10,27 @@ import (
|
|||||||
|
|
||||||
"github.com/cilium/ebpf/internal"
|
"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 {
|
if ptr, ok := data.(unsafe.Pointer); ok {
|
||||||
return newPtr(ptr), nil
|
return internal.NewPointer(ptr), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
buf, err := marshalBytes(data, length)
|
buf, err := marshalBytes(data, length)
|
||||||
if err != nil {
|
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) {
|
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:
|
case []byte:
|
||||||
buf = value
|
buf = value
|
||||||
case unsafe.Pointer:
|
case unsafe.Pointer:
|
||||||
err = errors.New("can't marshal from unsafe.Pointer")
|
err = xerrors.New("can't marshal from unsafe.Pointer")
|
||||||
default:
|
default:
|
||||||
var wr bytes.Buffer
|
var wr bytes.Buffer
|
||||||
err = binary.Write(&wr, internal.NativeEndian, value)
|
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()
|
buf = wr.Bytes()
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -47,18 +56,18 @@ func marshalBytes(data interface{}, length int) (buf []byte, err error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if len(buf) != length {
|
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
|
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 {
|
if ptr, ok := dst.(unsafe.Pointer); ok {
|
||||||
return newPtr(ptr), nil
|
return internal.NewPointer(ptr), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
buf := make([]byte, length)
|
buf := make([]byte, length)
|
||||||
return newPtr(unsafe.Pointer(&buf[0])), buf
|
return internal.NewSlicePointer(buf), buf
|
||||||
}
|
}
|
||||||
|
|
||||||
func unmarshalBytes(data interface{}, buf []byte) error {
|
func unmarshalBytes(data interface{}, buf []byte) error {
|
||||||
@@ -83,13 +92,15 @@ func unmarshalBytes(data interface{}, buf []byte) error {
|
|||||||
*value = buf
|
*value = buf
|
||||||
return nil
|
return nil
|
||||||
case string:
|
case string:
|
||||||
return errors.New("require pointer to string")
|
return xerrors.New("require pointer to string")
|
||||||
case []byte:
|
case []byte:
|
||||||
return errors.New("require pointer to []byte")
|
return xerrors.New("require pointer to []byte")
|
||||||
default:
|
default:
|
||||||
rd := bytes.NewReader(buf)
|
rd := bytes.NewReader(buf)
|
||||||
err := binary.Read(rd, internal.NativeEndian, value)
|
if err := binary.Read(rd, internal.NativeEndian, value); err != nil {
|
||||||
return errors.Wrapf(err, "decoding %T", value)
|
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.
|
// Values are initialized to zero if the slice has less elements than CPUs.
|
||||||
//
|
//
|
||||||
// slice must have a type like []elementType.
|
// 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)
|
sliceType := reflect.TypeOf(slice)
|
||||||
if sliceType.Kind() != reflect.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()
|
possibleCPUs, err := internal.PossibleCPUs()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return syscallPtr{}, err
|
return internal.Pointer{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
sliceValue := reflect.ValueOf(slice)
|
sliceValue := reflect.ValueOf(slice)
|
||||||
sliceLen := sliceValue.Len()
|
sliceLen := sliceValue.Len()
|
||||||
if sliceLen > possibleCPUs {
|
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)
|
alignedElemLength := align(elemLength, 8)
|
||||||
@@ -123,14 +134,14 @@ func marshalPerCPUValue(slice interface{}, elemLength int) (syscallPtr, error) {
|
|||||||
elem := sliceValue.Index(i).Interface()
|
elem := sliceValue.Index(i).Interface()
|
||||||
elemBytes, err := marshalBytes(elem, elemLength)
|
elemBytes, err := marshalBytes(elem, elemLength)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return syscallPtr{}, err
|
return internal.Pointer{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
offset := i * alignedElemLength
|
offset := i * alignedElemLength
|
||||||
copy(buf[offset:offset+elemLength], elemBytes)
|
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
|
// 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 {
|
func unmarshalPerCPUValue(slicePtr interface{}, elemLength int, buf []byte) error {
|
||||||
slicePtrType := reflect.TypeOf(slicePtr)
|
slicePtrType := reflect.TypeOf(slicePtr)
|
||||||
if slicePtrType.Kind() != reflect.Ptr || slicePtrType.Elem().Kind() != reflect.Slice {
|
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()
|
possibleCPUs, err := internal.PossibleCPUs()
|
||||||
@@ -159,7 +170,7 @@ func unmarshalPerCPUValue(slicePtr interface{}, elemLength int, buf []byte) erro
|
|||||||
|
|
||||||
step := len(buf) / possibleCPUs
|
step := len(buf) / possibleCPUs
|
||||||
if step < elemLength {
|
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++ {
|
for i := 0; i < possibleCPUs; i++ {
|
||||||
var elem interface{}
|
var elem interface{}
|
||||||
@@ -177,7 +188,7 @@ func unmarshalPerCPUValue(slicePtr interface{}, elemLength int, buf []byte) erro
|
|||||||
|
|
||||||
err := unmarshalBytes(elem, elemBytes)
|
err := unmarshalBytes(elem, elemBytes)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.Wrapf(err, "cpu %d", i)
|
return xerrors.Errorf("cpu %d: %w", i, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
buf = buf[step:]
|
buf = buf[step:]
|
||||||
|
|||||||
+186
-145
@@ -4,21 +4,23 @@ import (
|
|||||||
"bytes"
|
"bytes"
|
||||||
"fmt"
|
"fmt"
|
||||||
"math"
|
"math"
|
||||||
"path/filepath"
|
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
"unsafe"
|
"unsafe"
|
||||||
|
|
||||||
"github.com/cilium/ebpf/asm"
|
"github.com/cilium/ebpf/asm"
|
||||||
"github.com/cilium/ebpf/internal"
|
"github.com/cilium/ebpf/internal"
|
||||||
|
"github.com/cilium/ebpf/internal/btf"
|
||||||
"github.com/cilium/ebpf/internal/unix"
|
"github.com/cilium/ebpf/internal/unix"
|
||||||
|
|
||||||
"github.com/pkg/errors"
|
"golang.org/x/xerrors"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
// ErrNotSupported is returned whenever the kernel doesn't support a feature.
|
||||||
errNotSupported = errors.New("ebpf: not supported by kernel")
|
var ErrNotSupported = internal.ErrNotSupported
|
||||||
)
|
|
||||||
|
// ProgramID represents the unique ID of an eBPF program
|
||||||
|
type ProgramID uint32
|
||||||
|
|
||||||
const (
|
const (
|
||||||
// Number of bytes to pad the output buffer for BPF_PROG_TEST_RUN.
|
// Number of bytes to pad the output buffer for BPF_PROG_TEST_RUN.
|
||||||
@@ -51,6 +53,11 @@ type ProgramSpec struct {
|
|||||||
Instructions asm.Instructions
|
Instructions asm.Instructions
|
||||||
License string
|
License string
|
||||||
KernelVersion uint32
|
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.
|
// Copy returns a copy of the spec.
|
||||||
@@ -73,7 +80,7 @@ type Program struct {
|
|||||||
// otherwise it is empty.
|
// otherwise it is empty.
|
||||||
VerifierLog string
|
VerifierLog string
|
||||||
|
|
||||||
fd *bpfFD
|
fd *internal.FD
|
||||||
name string
|
name string
|
||||||
abi ProgramABI
|
abi ProgramABI
|
||||||
}
|
}
|
||||||
@@ -91,7 +98,20 @@ func NewProgram(spec *ProgramSpec) (*Program, error) {
|
|||||||
// Loading a program for the first time will perform
|
// Loading a program for the first time will perform
|
||||||
// feature detection by loading small, temporary programs.
|
// feature detection by loading small, temporary programs.
|
||||||
func NewProgramWithOptions(spec *ProgramSpec, opts ProgramOptions) (*Program, error) {
|
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 {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -106,62 +126,52 @@ func NewProgramWithOptions(spec *ProgramSpec, opts ProgramOptions) (*Program, er
|
|||||||
logBuf = make([]byte, logSize)
|
logBuf = make([]byte, logSize)
|
||||||
attr.logLevel = opts.LogLevel
|
attr.logLevel = opts.LogLevel
|
||||||
attr.logSize = uint32(len(logBuf))
|
attr.logSize = uint32(len(logBuf))
|
||||||
attr.logBuf = newPtr(unsafe.Pointer(&logBuf[0]))
|
attr.logBuf = internal.NewSlicePointer(logBuf)
|
||||||
}
|
}
|
||||||
|
|
||||||
fd, err := bpfProgLoad(attr)
|
fd, err := bpfProgLoad(attr)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
prog := newProgram(fd, spec.Name, &ProgramABI{spec.Type})
|
prog := newProgram(fd, spec.Name, &ProgramABI{spec.Type})
|
||||||
prog.VerifierLog = convertCString(logBuf)
|
prog.VerifierLog = internal.CString(logBuf)
|
||||||
return prog, nil
|
return prog, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
truncated := errors.Cause(err) == unix.ENOSPC
|
logErr := err
|
||||||
if opts.LogLevel == 0 {
|
if opts.LogLevel == 0 {
|
||||||
// Re-run with the verifier enabled to get better error messages.
|
// Re-run with the verifier enabled to get better error messages.
|
||||||
logBuf = make([]byte, logSize)
|
logBuf = make([]byte, logSize)
|
||||||
attr.logLevel = 1
|
attr.logLevel = 1
|
||||||
attr.logSize = uint32(len(logBuf))
|
attr.logSize = uint32(len(logBuf))
|
||||||
attr.logBuf = newPtr(unsafe.Pointer(&logBuf[0]))
|
attr.logBuf = internal.NewSlicePointer(logBuf)
|
||||||
|
|
||||||
_, nerr := bpfProgLoad(attr)
|
_, logErr = bpfProgLoad(attr)
|
||||||
truncated = errors.Cause(nerr) == unix.ENOSPC
|
|
||||||
}
|
}
|
||||||
|
|
||||||
logs := convertCString(logBuf)
|
err = internal.ErrorWithLog(err, logBuf, logErr)
|
||||||
if truncated {
|
return nil, xerrors.Errorf("can't load program: %w", err)
|
||||||
logs += "\n(truncated...)"
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil, &loadError{err, logs}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewProgramFromFD creates a program from a raw fd.
|
// NewProgramFromFD creates a program from a raw fd.
|
||||||
//
|
//
|
||||||
// You should not use fd after calling this function.
|
// You should not use fd after calling this function.
|
||||||
|
//
|
||||||
|
// Requires at least Linux 4.11.
|
||||||
func NewProgramFromFD(fd int) (*Program, error) {
|
func NewProgramFromFD(fd int) (*Program, error) {
|
||||||
if fd < 0 {
|
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 {
|
if err != nil {
|
||||||
bpfFd.forget()
|
bpfFd.Forget()
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
var name string
|
return newProgram(bpfFd, name, abi), nil
|
||||||
if bpfName := convertCString(info.name[:]); bpfName != "" {
|
|
||||||
name = bpfName
|
|
||||||
} else {
|
|
||||||
name = convertCString(info.tag[:])
|
|
||||||
}
|
|
||||||
|
|
||||||
return newProgram(bpfFd, name, newProgramABIFromInfo(info)), nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func newProgram(fd *bpfFD, name string, abi *ProgramABI) *Program {
|
func newProgram(fd *internal.FD, name string, abi *ProgramABI) *Program {
|
||||||
return &Program{
|
return &Program{
|
||||||
name: name,
|
name: name,
|
||||||
fd: fd,
|
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 {
|
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 {
|
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))
|
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()
|
bytecode := buf.Bytes()
|
||||||
insCount := uint32(len(bytecode) / asm.InstructionSize)
|
insCount := uint32(len(bytecode) / asm.InstructionSize)
|
||||||
lic := []byte(spec.License)
|
|
||||||
attr := &bpfProgLoadAttr{
|
attr := &bpfProgLoadAttr{
|
||||||
progType: spec.Type,
|
progType: spec.Type,
|
||||||
expectedAttachType: spec.AttachType,
|
expectedAttachType: spec.AttachType,
|
||||||
insCount: insCount,
|
insCount: insCount,
|
||||||
instructions: newPtr(unsafe.Pointer(&bytecode[0])),
|
instructions: internal.NewSlicePointer(bytecode),
|
||||||
license: newPtr(unsafe.Pointer(&lic[0])),
|
license: internal.NewStringPointer(spec.License),
|
||||||
|
kernelVersion: spec.KernelVersion,
|
||||||
}
|
}
|
||||||
|
|
||||||
name, err := newBPFObjName(spec.Name)
|
if haveObjName() == nil {
|
||||||
if err != nil {
|
attr.progName = newBPFObjName(spec.Name)
|
||||||
return nil, err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if includeName {
|
if handle != nil && spec.BTF != nil {
|
||||||
attr.progName = name
|
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
|
return attr, nil
|
||||||
@@ -209,9 +234,9 @@ func convertProgramSpec(spec *ProgramSpec, includeName bool) (*bpfProgLoadAttr,
|
|||||||
|
|
||||||
func (p *Program) String() string {
|
func (p *Program) String() string {
|
||||||
if p.name != "" {
|
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
|
// 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.
|
// It is invalid to call this function after Close has been called.
|
||||||
func (p *Program) FD() int {
|
func (p *Program) FD() int {
|
||||||
fd, err := p.fd.value()
|
fd, err := p.fd.Value()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// Best effort: -1 is the number most likely to be an
|
// Best effort: -1 is the number most likely to be an
|
||||||
// invalid file descriptor.
|
// invalid file descriptor.
|
||||||
@@ -243,9 +268,9 @@ func (p *Program) Clone() (*Program, error) {
|
|||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
dup, err := p.fd.dup()
|
dup, err := p.fd.Dup()
|
||||||
if err != nil {
|
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
|
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
|
// 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 {
|
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.
|
// Close unloads the program from the kernel.
|
||||||
@@ -264,7 +292,7 @@ func (p *Program) Close() error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
return p.fd.close()
|
return p.fd.Close()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Test runs the Program in the kernel with the given input and returns the
|
// 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.
|
// This function requires at least Linux 4.12.
|
||||||
func (p *Program) Test(in []byte) (uint32, []byte, error) {
|
func (p *Program) Test(in []byte) (uint32, []byte, error) {
|
||||||
ret, out, _, err := p.testRun(in, 1)
|
ret, out, _, err := p.testRun(in, 1, nil)
|
||||||
return ret, out, err
|
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
|
// Benchmark runs the Program with the given input for a number of times
|
||||||
// and returns the time taken per iteration.
|
// and returns the time taken per iteration.
|
||||||
//
|
//
|
||||||
// The returned value is the return value of the last execution of
|
// Returns the result of the last execution of the program and the time per
|
||||||
// the program.
|
// 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.
|
// This function requires at least Linux 4.12.
|
||||||
func (p *Program) Benchmark(in []byte, repeat int) (uint32, time.Duration, error) {
|
func (p *Program) Benchmark(in []byte, repeat int, reset func()) (uint32, time.Duration, error) {
|
||||||
ret, _, total, err := p.testRun(in, repeat)
|
ret, _, total, err := p.testRun(in, repeat, reset)
|
||||||
return ret, total, err
|
if err != nil {
|
||||||
|
return ret, total, xerrors.Errorf("can't benchmark program: %w", err)
|
||||||
|
}
|
||||||
|
return ret, total, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
var noProgTestRun = featureTest{
|
var haveProgTestRun = internal.FeatureTest("BPF_PROG_TEST_RUN", "4.12", func() bool {
|
||||||
Fn: func() bool {
|
prog, err := NewProgram(&ProgramSpec{
|
||||||
prog, err := NewProgram(&ProgramSpec{
|
Type: SocketFilter,
|
||||||
Type: SocketFilter,
|
Instructions: asm.Instructions{
|
||||||
Instructions: asm.Instructions{
|
asm.LoadImm(asm.R0, 0, asm.DWord),
|
||||||
asm.LoadImm(asm.R0, 0, asm.DWord),
|
asm.Return(),
|
||||||
asm.Return(),
|
},
|
||||||
},
|
License: "MIT",
|
||||||
License: "MIT",
|
})
|
||||||
})
|
if err != nil {
|
||||||
if err != nil {
|
// This may be because we lack sufficient permissions, etc.
|
||||||
// This may be because we lack sufficient permissions, etc.
|
return false
|
||||||
return false
|
}
|
||||||
}
|
defer prog.Close()
|
||||||
defer prog.Close()
|
|
||||||
|
|
||||||
fd, err := prog.fd.value()
|
fd, err := prog.fd.Value()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
// Programs require at least 14 bytes input
|
// Programs require at least 14 bytes input
|
||||||
in := make([]byte, 14)
|
in := make([]byte, 14)
|
||||||
attr := bpfProgTestRunAttr{
|
attr := bpfProgTestRunAttr{
|
||||||
fd: fd,
|
fd: fd,
|
||||||
dataSizeIn: uint32(len(in)),
|
dataSizeIn: uint32(len(in)),
|
||||||
dataIn: newPtr(unsafe.Pointer(&in[0])),
|
dataIn: internal.NewSlicePointer(in),
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err = bpfCall(_ProgTestRun, unsafe.Pointer(&attr), unsafe.Sizeof(attr))
|
_, err = internal.BPF(_ProgTestRun, unsafe.Pointer(&attr), unsafe.Sizeof(attr))
|
||||||
return errors.Cause(err) == unix.EINVAL
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
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 {
|
if uint(repeat) > math.MaxUint32 {
|
||||||
return 0, nil, 0, fmt.Errorf("repeat is too high")
|
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")
|
return 0, nil, 0, fmt.Errorf("input is too long")
|
||||||
}
|
}
|
||||||
|
|
||||||
if noProgTestRun.Result() {
|
if err := haveProgTestRun(); err != nil {
|
||||||
return 0, nil, 0, errNotSupported
|
return 0, nil, 0, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Older kernels ignore the dataSizeOut argument when copying to user space.
|
// 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/
|
// See https://patchwork.ozlabs.org/cover/1006822/
|
||||||
out := make([]byte, len(in)+outputPad)
|
out := make([]byte, len(in)+outputPad)
|
||||||
|
|
||||||
fd, err := p.fd.value()
|
fd, err := p.fd.Value()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, nil, 0, err
|
return 0, nil, 0, err
|
||||||
}
|
}
|
||||||
@@ -358,14 +397,25 @@ func (p *Program) testRun(in []byte, repeat int) (uint32, []byte, time.Duration,
|
|||||||
fd: fd,
|
fd: fd,
|
||||||
dataSizeIn: uint32(len(in)),
|
dataSizeIn: uint32(len(in)),
|
||||||
dataSizeOut: uint32(len(out)),
|
dataSizeOut: uint32(len(out)),
|
||||||
dataIn: newPtr(unsafe.Pointer(&in[0])),
|
dataIn: internal.NewSlicePointer(in),
|
||||||
dataOut: newPtr(unsafe.Pointer(&out[0])),
|
dataOut: internal.NewSlicePointer(out),
|
||||||
repeat: uint32(repeat),
|
repeat: uint32(repeat),
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err = bpfCall(_ProgTestRun, unsafe.Pointer(&attr), unsafe.Sizeof(attr))
|
for {
|
||||||
if err != nil {
|
_, err = internal.BPF(_ProgTestRun, unsafe.Pointer(&attr), unsafe.Sizeof(attr))
|
||||||
return 0, nil, 0, errors.Wrap(err, "can't run test")
|
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) {
|
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) {
|
func unmarshalProgram(buf []byte) (*Program, error) {
|
||||||
if len(buf) != 4 {
|
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,
|
// Looking up an entry in a nested map or prog array returns an id,
|
||||||
// not an fd.
|
// not an fd.
|
||||||
id := internal.NativeEndian.Uint32(buf)
|
id := internal.NativeEndian.Uint32(buf)
|
||||||
fd, err := bpfGetProgramFDByID(id)
|
return NewProgramFromID(ProgramID(id))
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
abi, err := newProgramABIFromFd(fd)
|
|
||||||
if err != nil {
|
|
||||||
_ = fd.close()
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return newProgram(fd, "", abi), nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// MarshalBinary implements BinaryMarshaler.
|
// MarshalBinary implements BinaryMarshaler.
|
||||||
func (p *Program) MarshalBinary() ([]byte, error) {
|
func (p *Program) MarshalBinary() ([]byte, error) {
|
||||||
value, err := p.fd.value()
|
value, err := p.fd.Value()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -416,10 +455,10 @@ func (p *Program) MarshalBinary() ([]byte, error) {
|
|||||||
// Attach a Program to a container object fd
|
// Attach a Program to a container object fd
|
||||||
func (p *Program) Attach(fd int, typ AttachType, flags AttachFlags) error {
|
func (p *Program) Attach(fd int, typ AttachType, flags AttachFlags) error {
|
||||||
if fd < 0 {
|
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 {
|
if err != nil {
|
||||||
return err
|
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
|
// Detach a Program from a container object fd
|
||||||
func (p *Program) Detach(fd int, typ AttachType, flags AttachFlags) error {
|
func (p *Program) Detach(fd int, typ AttachType, flags AttachFlags) error {
|
||||||
if fd < 0 {
|
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 {
|
if err != nil {
|
||||||
return err
|
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.
|
// LoadPinnedProgram loads a Program from a BPF file.
|
||||||
//
|
//
|
||||||
// Requires at least Linux 4.13, use LoadPinnedProgramExplicit on
|
// Requires at least Linux 4.11.
|
||||||
// earlier versions.
|
|
||||||
func LoadPinnedProgram(fileName string) (*Program, error) {
|
func LoadPinnedProgram(fileName string) (*Program, error) {
|
||||||
fd, err := bpfGetObject(fileName)
|
fd, err := bpfGetObject(fileName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
abi, err := newProgramABIFromFd(fd)
|
name, abi, err := newProgramABIFromFd(fd)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
_ = fd.close()
|
_ = fd.Close()
|
||||||
return nil, err
|
return nil, xerrors.Errorf("can't get ABI for %s: %w", fileName, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return newProgram(fd, filepath.Base(fileName), abi), nil
|
return newProgram(fd, name, 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
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// SanitizeName replaces all invalid characters in name.
|
// SanitizeName replaces all invalid characters in name.
|
||||||
@@ -500,24 +528,37 @@ func SanitizeName(name string, replacement rune) string {
|
|||||||
}, name)
|
}, name)
|
||||||
}
|
}
|
||||||
|
|
||||||
type loadError struct {
|
// ProgramGetNextID returns the ID of the next eBPF program.
|
||||||
cause error
|
//
|
||||||
verifierLog string
|
// 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 {
|
// NewProgramFromID returns the program for a given id.
|
||||||
if le.verifierLog == "" {
|
//
|
||||||
return fmt.Sprintf("failed to load program: %s", le.cause)
|
// 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 {
|
// ID returns the systemwide unique ID of the program.
|
||||||
return le.cause
|
func (p *Program) ID() (ProgramID, error) {
|
||||||
}
|
info, err := bpfGetProgInfoByFD(p.fd)
|
||||||
|
if err != nil {
|
||||||
// IsNotSupported returns true if an error occurred because
|
return ProgramID(0), err
|
||||||
// the kernel does not have support for a specific feature.
|
}
|
||||||
func IsNotSupported(err error) bool {
|
return ProgramID(info.id), nil
|
||||||
return errors.Cause(err) == errNotSupported
|
|
||||||
}
|
}
|
||||||
|
|||||||
+5
@@ -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
|
The package is production ready, but **the API is explicitly unstable
|
||||||
right now**. Expect to update your code if you want to follow along.
|
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
|
## Useful resources
|
||||||
|
|
||||||
* [Cilium eBPF documentation](https://cilium.readthedocs.io/en/latest/bpf/#bpf-guide) (recommended)
|
* [Cilium eBPF documentation](https://cilium.readthedocs.io/en/latest/bpf/#bpf-guide) (recommended)
|
||||||
|
|||||||
+14
-12
@@ -8,24 +8,22 @@ set -o pipefail
|
|||||||
if [[ "${1:-}" = "--in-vm" ]]; then
|
if [[ "${1:-}" = "--in-vm" ]]; then
|
||||||
shift
|
shift
|
||||||
|
|
||||||
readonly home="$(mktemp --directory)"
|
|
||||||
|
|
||||||
mount -t bpf bpf /sys/fs/bpf
|
mount -t bpf bpf /sys/fs/bpf
|
||||||
export CGO_ENABLED=0
|
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...
|
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"
|
touch "$1/success"
|
||||||
exit 0
|
exit 0
|
||||||
fi
|
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
|
# Pull all dependencies, so that we can run tests without the
|
||||||
# vm having network access.
|
# vm having network access.
|
||||||
go mod vendor
|
go mod download
|
||||||
|
|
||||||
# Use sudo if /dev/kvm isn't accessible by the current user.
|
# Use sudo if /dev/kvm isn't accessible by the current user.
|
||||||
sudo=""
|
sudo=""
|
||||||
@@ -42,15 +40,19 @@ fi
|
|||||||
|
|
||||||
readonly kernel="linux-${kernel_version}.bz"
|
readonly kernel="linux-${kernel_version}.bz"
|
||||||
readonly output="$(mktemp -d)"
|
readonly output="$(mktemp -d)"
|
||||||
readonly tmp_dir="$(mktemp -d)"
|
readonly tmp_dir="${TMPDIR:-$(mktemp -d)}"
|
||||||
|
|
||||||
test -e "${tmp_dir}/${kernel}" || {
|
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}"
|
curl --fail -L "https://github.com/newtools/ci-kernels/blob/master/${kernel}?raw=true" -o "${tmp_dir}/${kernel}"
|
||||||
}
|
}
|
||||||
|
|
||||||
echo Testing on ${kernel_version}
|
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"
|
$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
|
if [[ ! -e "${output}/success" ]]; then
|
||||||
echo "Test failed on ${kernel_version}"
|
echo "Test failed on ${kernel_version}"
|
||||||
|
|||||||
+249
-191
@@ -1,88 +1,35 @@
|
|||||||
package ebpf
|
package ebpf
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"runtime"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
"unsafe"
|
"unsafe"
|
||||||
|
|
||||||
|
"github.com/cilium/ebpf/internal"
|
||||||
|
"github.com/cilium/ebpf/internal/btf"
|
||||||
"github.com/cilium/ebpf/internal/unix"
|
"github.com/cilium/ebpf/internal/unix"
|
||||||
|
|
||||||
"github.com/pkg/errors"
|
"golang.org/x/xerrors"
|
||||||
)
|
)
|
||||||
|
|
||||||
var errClosedFd = errors.New("use of closed file descriptor")
|
// Generic errors returned by BPF syscalls.
|
||||||
|
var (
|
||||||
type bpfFD struct {
|
ErrNotExist = xerrors.New("requested object does not exit")
|
||||||
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
|
|
||||||
}
|
|
||||||
|
|
||||||
// bpfObjName is a null-terminated string made up of
|
// bpfObjName is a null-terminated string made up of
|
||||||
// 'A-Za-z0-9_' characters.
|
// 'A-Za-z0-9_' characters.
|
||||||
type bpfObjName [unix.BPF_OBJ_NAME_LEN]byte
|
type bpfObjName [unix.BPF_OBJ_NAME_LEN]byte
|
||||||
|
|
||||||
// newBPFObjName truncates the result if it is too long.
|
// newBPFObjName truncates the result if it is too long.
|
||||||
func newBPFObjName(name string) (bpfObjName, error) {
|
func newBPFObjName(name string) bpfObjName {
|
||||||
idx := strings.IndexFunc(name, invalidBPFObjNameChar)
|
|
||||||
if idx != -1 {
|
|
||||||
return bpfObjName{}, errors.Errorf("invalid character '%c' in name '%s'", name[idx], name)
|
|
||||||
}
|
|
||||||
|
|
||||||
var result bpfObjName
|
var result bpfObjName
|
||||||
copy(result[:unix.BPF_OBJ_NAME_LEN-1], name)
|
copy(result[:unix.BPF_OBJ_NAME_LEN-1], name)
|
||||||
return result, nil
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
func invalidBPFObjNameChar(char rune) bool {
|
func invalidBPFObjNameChar(char rune) bool {
|
||||||
|
dotAllowed := objNameAllowsDot() == nil
|
||||||
|
|
||||||
switch {
|
switch {
|
||||||
case char >= 'A' && char <= 'Z':
|
case char >= 'A' && char <= 'Z':
|
||||||
fallthrough
|
fallthrough
|
||||||
@@ -90,6 +37,8 @@ func invalidBPFObjNameChar(char rune) bool {
|
|||||||
fallthrough
|
fallthrough
|
||||||
case char >= '0' && char <= '9':
|
case char >= '0' && char <= '9':
|
||||||
fallthrough
|
fallthrough
|
||||||
|
case dotAllowed && char == '.':
|
||||||
|
fallthrough
|
||||||
case char == '_':
|
case char == '_':
|
||||||
return false
|
return false
|
||||||
default:
|
default:
|
||||||
@@ -98,21 +47,25 @@ func invalidBPFObjNameChar(char rune) bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type bpfMapCreateAttr struct {
|
type bpfMapCreateAttr struct {
|
||||||
mapType MapType
|
mapType MapType
|
||||||
keySize uint32
|
keySize uint32
|
||||||
valueSize uint32
|
valueSize uint32
|
||||||
maxEntries uint32
|
maxEntries uint32
|
||||||
flags uint32
|
flags uint32
|
||||||
innerMapFd uint32 // since 4.12 56f668dfe00d
|
innerMapFd uint32 // since 4.12 56f668dfe00d
|
||||||
numaNode uint32 // since 4.14 96eabe7a40aa
|
numaNode uint32 // since 4.14 96eabe7a40aa
|
||||||
mapName bpfObjName // since 4.15 ad5b177bd73f
|
mapName bpfObjName // since 4.15 ad5b177bd73f
|
||||||
|
mapIfIndex uint32
|
||||||
|
btfFd uint32
|
||||||
|
btfKeyTypeID btf.TypeID
|
||||||
|
btfValueTypeID btf.TypeID
|
||||||
}
|
}
|
||||||
|
|
||||||
type bpfMapOpAttr struct {
|
type bpfMapOpAttr struct {
|
||||||
mapFd uint32
|
mapFd uint32
|
||||||
padding uint32
|
padding uint32
|
||||||
key syscallPtr
|
key internal.Pointer
|
||||||
value syscallPtr
|
value internal.Pointer
|
||||||
flags uint64
|
flags uint64
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -127,7 +80,7 @@ type bpfMapInfo struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type bpfPinObjAttr struct {
|
type bpfPinObjAttr struct {
|
||||||
fileName syscallPtr
|
fileName internal.Pointer
|
||||||
fd uint32
|
fd uint32
|
||||||
padding uint32
|
padding uint32
|
||||||
}
|
}
|
||||||
@@ -135,16 +88,23 @@ type bpfPinObjAttr struct {
|
|||||||
type bpfProgLoadAttr struct {
|
type bpfProgLoadAttr struct {
|
||||||
progType ProgramType
|
progType ProgramType
|
||||||
insCount uint32
|
insCount uint32
|
||||||
instructions syscallPtr
|
instructions internal.Pointer
|
||||||
license syscallPtr
|
license internal.Pointer
|
||||||
logLevel uint32
|
logLevel uint32
|
||||||
logSize uint32
|
logSize uint32
|
||||||
logBuf syscallPtr
|
logBuf internal.Pointer
|
||||||
kernelVersion uint32 // since 4.1 2541517c32be
|
kernelVersion uint32 // since 4.1 2541517c32be
|
||||||
progFlags uint32 // since 4.11 e07b98d9bffe
|
progFlags uint32 // since 4.11 e07b98d9bffe
|
||||||
progName bpfObjName // since 4.15 067cae47771c
|
progName bpfObjName // since 4.15 067cae47771c
|
||||||
progIfIndex uint32 // since 4.15 1f6f4cb7ba21
|
progIfIndex uint32 // since 4.15 1f6f4cb7ba21
|
||||||
expectedAttachType AttachType // since 4.17 5e43f899b03a
|
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 {
|
type bpfProgInfo struct {
|
||||||
@@ -153,12 +113,12 @@ type bpfProgInfo struct {
|
|||||||
tag [unix.BPF_TAG_SIZE]byte
|
tag [unix.BPF_TAG_SIZE]byte
|
||||||
jitedLen uint32
|
jitedLen uint32
|
||||||
xlatedLen uint32
|
xlatedLen uint32
|
||||||
jited syscallPtr
|
jited internal.Pointer
|
||||||
xlated syscallPtr
|
xlated internal.Pointer
|
||||||
loadTime uint64 // since 4.15 cb4d2b3f03d8
|
loadTime uint64 // since 4.15 cb4d2b3f03d8
|
||||||
createdByUID uint32
|
createdByUID uint32
|
||||||
nrMapIDs uint32
|
nrMapIDs uint32
|
||||||
mapIds syscallPtr
|
mapIds internal.Pointer
|
||||||
name bpfObjName
|
name bpfObjName
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -167,8 +127,8 @@ type bpfProgTestRunAttr struct {
|
|||||||
retval uint32
|
retval uint32
|
||||||
dataSizeIn uint32
|
dataSizeIn uint32
|
||||||
dataSizeOut uint32
|
dataSizeOut uint32
|
||||||
dataIn syscallPtr
|
dataIn internal.Pointer
|
||||||
dataOut syscallPtr
|
dataOut internal.Pointer
|
||||||
repeat uint32
|
repeat uint32
|
||||||
duration uint32
|
duration uint32
|
||||||
}
|
}
|
||||||
@@ -183,7 +143,7 @@ type bpfProgAlterAttr struct {
|
|||||||
type bpfObjGetInfoByFDAttr struct {
|
type bpfObjGetInfoByFDAttr struct {
|
||||||
fd uint32
|
fd uint32
|
||||||
infoLen uint32
|
infoLen uint32
|
||||||
info syscallPtr // May be either bpfMapInfo or bpfProgInfo
|
info internal.Pointer // May be either bpfMapInfo or bpfProgInfo
|
||||||
}
|
}
|
||||||
|
|
||||||
type bpfGetFDByIDAttr struct {
|
type bpfGetFDByIDAttr struct {
|
||||||
@@ -191,13 +151,19 @@ type bpfGetFDByIDAttr struct {
|
|||||||
next uint32
|
next uint32
|
||||||
}
|
}
|
||||||
|
|
||||||
func newPtr(ptr unsafe.Pointer) syscallPtr {
|
type bpfMapFreezeAttr struct {
|
||||||
return syscallPtr{ptr: ptr}
|
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 {
|
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,
|
// As of ~4.20 the verifier can be interrupted by a signal,
|
||||||
// and returns EAGAIN in that case.
|
// and returns EAGAIN in that case.
|
||||||
if err == unix.EAGAIN {
|
if err == unix.EAGAIN {
|
||||||
@@ -208,26 +174,71 @@ func bpfProgLoad(attr *bpfProgLoadAttr) (*bpfFD, error) {
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return newBPFFD(uint32(fd)), nil
|
return internal.NewFD(uint32(fd)), nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func bpfProgAlter(cmd int, attr *bpfProgAlterAttr) error {
|
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
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func bpfMapCreate(attr *bpfMapCreateAttr) (*bpfFD, error) {
|
func bpfMapCreate(attr *bpfMapCreateAttr) (*internal.FD, error) {
|
||||||
fd, err := bpfCall(_MapCreate, unsafe.Pointer(attr), unsafe.Sizeof(*attr))
|
fd, err := internal.BPF(_MapCreate, unsafe.Pointer(attr), unsafe.Sizeof(*attr))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return newBPFFD(uint32(fd)), nil
|
return internal.NewFD(uint32(fd)), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func bpfMapLookupElem(m *bpfFD, key, valueOut syscallPtr) error {
|
var haveNestedMaps = internal.FeatureTest("nested maps", "4.12", func() bool {
|
||||||
fd, err := m.value()
|
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 {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -237,12 +248,27 @@ func bpfMapLookupElem(m *bpfFD, key, valueOut syscallPtr) error {
|
|||||||
key: key,
|
key: key,
|
||||||
value: valueOut,
|
value: valueOut,
|
||||||
}
|
}
|
||||||
_, err = bpfCall(_MapLookupElem, unsafe.Pointer(&attr), unsafe.Sizeof(attr))
|
_, err = internal.BPF(_MapLookupElem, unsafe.Pointer(&attr), unsafe.Sizeof(attr))
|
||||||
return err
|
return wrapMapError(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
func bpfMapUpdateElem(m *bpfFD, key, valueOut syscallPtr, flags uint64) error {
|
func bpfMapLookupAndDelete(m *internal.FD, key, valueOut internal.Pointer) error {
|
||||||
fd, err := m.value()
|
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 {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -253,12 +279,12 @@ func bpfMapUpdateElem(m *bpfFD, key, valueOut syscallPtr, flags uint64) error {
|
|||||||
value: valueOut,
|
value: valueOut,
|
||||||
flags: flags,
|
flags: flags,
|
||||||
}
|
}
|
||||||
_, err = bpfCall(_MapUpdateElem, unsafe.Pointer(&attr), unsafe.Sizeof(attr))
|
_, err = internal.BPF(_MapUpdateElem, unsafe.Pointer(&attr), unsafe.Sizeof(attr))
|
||||||
return err
|
return wrapMapError(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
func bpfMapDeleteElem(m *bpfFD, key syscallPtr) error {
|
func bpfMapDeleteElem(m *internal.FD, key internal.Pointer) error {
|
||||||
fd, err := m.value()
|
fd, err := m.Value()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -267,12 +293,12 @@ func bpfMapDeleteElem(m *bpfFD, key syscallPtr) error {
|
|||||||
mapFd: fd,
|
mapFd: fd,
|
||||||
key: key,
|
key: key,
|
||||||
}
|
}
|
||||||
_, err = bpfCall(_MapDeleteElem, unsafe.Pointer(&attr), unsafe.Sizeof(attr))
|
_, err = internal.BPF(_MapDeleteElem, unsafe.Pointer(&attr), unsafe.Sizeof(attr))
|
||||||
return err
|
return wrapMapError(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
func bpfMapGetNextKey(m *bpfFD, key, nextKeyOut syscallPtr) error {
|
func bpfMapGetNextKey(m *internal.FD, key, nextKeyOut internal.Pointer) error {
|
||||||
fd, err := m.value()
|
fd, err := m.Value()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -282,46 +308,93 @@ func bpfMapGetNextKey(m *bpfFD, key, nextKeyOut syscallPtr) error {
|
|||||||
key: key,
|
key: key,
|
||||||
value: nextKeyOut,
|
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
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
const bpfFSType = 0xcafe4a11
|
const bpfFSType = 0xcafe4a11
|
||||||
|
|
||||||
func bpfPinObject(fileName string, fd *bpfFD) error {
|
func bpfPinObject(fileName string, fd *internal.FD) error {
|
||||||
dirName := filepath.Dir(fileName)
|
dirName := filepath.Dir(fileName)
|
||||||
var statfs unix.Statfs_t
|
var statfs unix.Statfs_t
|
||||||
if err := unix.Statfs(dirName, &statfs); err != nil {
|
if err := unix.Statfs(dirName, &statfs); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if uint64(statfs.Type) != bpfFSType {
|
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 {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err = bpfCall(_ObjPin, unsafe.Pointer(&bpfPinObjAttr{
|
_, err = internal.BPF(_ObjPin, unsafe.Pointer(&bpfPinObjAttr{
|
||||||
fileName: newPtr(unsafe.Pointer(&[]byte(fileName)[0])),
|
fileName: internal.NewStringPointer(fileName),
|
||||||
fd: value,
|
fd: value,
|
||||||
}), 16)
|
}), 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) {
|
func bpfGetObject(fileName string) (*internal.FD, error) {
|
||||||
ptr, err := bpfCall(_ObjGet, unsafe.Pointer(&bpfPinObjAttr{
|
ptr, err := internal.BPF(_ObjGet, unsafe.Pointer(&bpfPinObjAttr{
|
||||||
fileName: newPtr(unsafe.Pointer(&[]byte(fileName)[0])),
|
fileName: internal.NewStringPointer(fileName),
|
||||||
}), 16)
|
}), 16)
|
||||||
if err != nil {
|
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 {
|
func bpfGetObjectInfoByFD(fd *internal.FD, info unsafe.Pointer, size uintptr) error {
|
||||||
value, err := fd.value()
|
value, err := fd.Value()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -330,91 +403,76 @@ func bpfGetObjectInfoByFD(fd *bpfFD, info unsafe.Pointer, size uintptr) error {
|
|||||||
attr := bpfObjGetInfoByFDAttr{
|
attr := bpfObjGetInfoByFDAttr{
|
||||||
fd: value,
|
fd: value,
|
||||||
infoLen: uint32(size),
|
infoLen: uint32(size),
|
||||||
info: newPtr(info),
|
info: internal.NewPointer(info),
|
||||||
}
|
}
|
||||||
_, err = bpfCall(_ObjGetInfoByFD, unsafe.Pointer(&attr), unsafe.Sizeof(attr))
|
_, err = internal.BPF(_ObjGetInfoByFD, unsafe.Pointer(&attr), unsafe.Sizeof(attr))
|
||||||
return errors.Wrapf(err, "fd %d", value)
|
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
|
var info bpfProgInfo
|
||||||
err := bpfGetObjectInfoByFD(fd, unsafe.Pointer(&info), unsafe.Sizeof(info))
|
if err := bpfGetObjectInfoByFD(fd, unsafe.Pointer(&info), unsafe.Sizeof(info)); err != nil {
|
||||||
return &info, errors.Wrap(err, "can't get program info")
|
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
|
var info bpfMapInfo
|
||||||
err := bpfGetObjectInfoByFD(fd, unsafe.Pointer(&info), unsafe.Sizeof(info))
|
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{
|
var haveObjName = internal.FeatureTest("object names", "4.15", func() bool {
|
||||||
Fn: func() bool {
|
attr := bpfMapCreateAttr{
|
||||||
name, err := newBPFObjName("feature_test")
|
mapType: Array,
|
||||||
if err != nil {
|
keySize: 4,
|
||||||
// This really is a fatal error, but it should be caught
|
valueSize: 4,
|
||||||
// by the unit tests not working.
|
maxEntries: 1,
|
||||||
return false
|
mapName: newBPFObjName("feature_test"),
|
||||||
}
|
}
|
||||||
|
|
||||||
attr := bpfMapCreateAttr{
|
fd, err := bpfMapCreate(&attr)
|
||||||
mapType: Array,
|
if err != nil {
|
||||||
keySize: 4,
|
return false
|
||||||
valueSize: 4,
|
}
|
||||||
maxEntries: 1,
|
|
||||||
mapName: name,
|
|
||||||
}
|
|
||||||
|
|
||||||
fd, err := bpfMapCreate(&attr)
|
_ = fd.Close()
|
||||||
if err != nil {
|
return true
|
||||||
return false
|
})
|
||||||
}
|
|
||||||
|
|
||||||
_ = fd.close()
|
var objNameAllowsDot = internal.FeatureTest("dot in object names", "5.2", func() bool {
|
||||||
return true
|
if err := haveObjName(); err != nil {
|
||||||
},
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
func bpfGetMapFDByID(id uint32) (*bpfFD, error) {
|
attr := bpfMapCreateAttr{
|
||||||
// available from 4.13
|
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{
|
attr := bpfGetFDByIDAttr{
|
||||||
id: id,
|
id: id,
|
||||||
}
|
}
|
||||||
ptr, err := bpfCall(_MapGetFDByID, unsafe.Pointer(&attr), unsafe.Sizeof(attr))
|
ptr, err := internal.BPF(cmd, unsafe.Pointer(&attr), unsafe.Sizeof(attr))
|
||||||
if err != nil {
|
return internal.NewFD(uint32(ptr)), wrapObjError(err)
|
||||||
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])
|
|
||||||
}
|
}
|
||||||
|
|||||||
+37
-6
@@ -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
|
// HashOfMaps - Each item in the hash map is another map. The inner map mustn't be a map of maps
|
||||||
// itself.
|
// itself.
|
||||||
HashOfMaps
|
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.
|
// hasPerCPUValue returns true if the Map stores a value per CPU.
|
||||||
@@ -84,12 +108,14 @@ const (
|
|||||||
_ProgGetFDByID
|
_ProgGetFDByID
|
||||||
_MapGetFDByID
|
_MapGetFDByID
|
||||||
_ObjGetInfoByFD
|
_ObjGetInfoByFD
|
||||||
)
|
_ProgQuery
|
||||||
|
_RawTracepointOpen
|
||||||
const (
|
_BTFLoad
|
||||||
_Any = iota
|
_BTFGetFDByID
|
||||||
_NoExist
|
_TaskFDQuery
|
||||||
_Exist
|
_MapLookupAndDeleteElem
|
||||||
|
_MapFreeze
|
||||||
|
_BTFGetNextID
|
||||||
)
|
)
|
||||||
|
|
||||||
// ProgramType of the eBPF program
|
// ProgramType of the eBPF program
|
||||||
@@ -149,6 +175,8 @@ const (
|
|||||||
RawTracepointWritable
|
RawTracepointWritable
|
||||||
// CGroupSockopt program
|
// CGroupSockopt program
|
||||||
CGroupSockopt
|
CGroupSockopt
|
||||||
|
// Tracing program
|
||||||
|
Tracing
|
||||||
)
|
)
|
||||||
|
|
||||||
// AttachType of the eBPF program, needed to differentiate allowed context accesses in
|
// AttachType of the eBPF program, needed to differentiate allowed context accesses in
|
||||||
@@ -183,6 +211,9 @@ const (
|
|||||||
AttachCGroupUDP6Recvmsg
|
AttachCGroupUDP6Recvmsg
|
||||||
AttachCGroupGetsockopt
|
AttachCGroupGetsockopt
|
||||||
AttachCGroupSetsockopt
|
AttachCGroupSetsockopt
|
||||||
|
AttachTraceRawTp
|
||||||
|
AttachTraceFEntry
|
||||||
|
AttachTraceFExit
|
||||||
)
|
)
|
||||||
|
|
||||||
// AttachFlags of the eBPF program used in BPF_PROG_ATTACH command
|
// AttachFlags of the eBPF program used in BPF_PROG_ATTACH command
|
||||||
|
|||||||
+17
-4
@@ -22,11 +22,23 @@ func _() {
|
|||||||
_ = x[LPMTrie-11]
|
_ = x[LPMTrie-11]
|
||||||
_ = x[ArrayOfMaps-12]
|
_ = x[ArrayOfMaps-12]
|
||||||
_ = x[HashOfMaps-13]
|
_ = 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 {
|
func (i MapType) String() string {
|
||||||
if i >= MapType(len(_MapType_index)-1) {
|
if i >= MapType(len(_MapType_index)-1) {
|
||||||
@@ -64,11 +76,12 @@ func _() {
|
|||||||
_ = x[CGroupSysctl-23]
|
_ = x[CGroupSysctl-23]
|
||||||
_ = x[RawTracepointWritable-24]
|
_ = x[RawTracepointWritable-24]
|
||||||
_ = x[CGroupSockopt-25]
|
_ = 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 {
|
func (i ProgramType) String() string {
|
||||||
if i >= ProgramType(len(_ProgramType_index)-1) {
|
if i >= ProgramType(len(_ProgramType_index)-1) {
|
||||||
|
|||||||
+8
-8
@@ -1,17 +1,16 @@
|
|||||||
language: go
|
language: go
|
||||||
go:
|
go:
|
||||||
- "1.10.x"
|
- "1.12.x"
|
||||||
- "1.11.x"
|
- "1.13.x"
|
||||||
|
|
||||||
go_import_path: github.com/containerd/console
|
go_import_path: github.com/containerd/console
|
||||||
|
|
||||||
|
env:
|
||||||
|
- GO111MODULE=on
|
||||||
|
|
||||||
install:
|
install:
|
||||||
- go get -d
|
- pushd ..; go get -u github.com/vbatts/git-validation; popd
|
||||||
- GOOS=openbsd go get -d
|
- pushd ..; go get -u github.com/kunalkushwaha/ltag; popd
|
||||||
- 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
|
|
||||||
|
|
||||||
before_script:
|
before_script:
|
||||||
- pushd ..; git clone https://github.com/containerd/project; popd
|
- pushd ..; git clone https://github.com/containerd/project; popd
|
||||||
@@ -19,6 +18,7 @@ before_script:
|
|||||||
script:
|
script:
|
||||||
- DCO_VERBOSITY=-q ../project/script/validate/dco
|
- DCO_VERBOSITY=-q ../project/script/validate/dco
|
||||||
- ../project/script/validate/fileheader ../project/
|
- ../project/script/validate/fileheader ../project/
|
||||||
|
- travis_wait ../project/script/validate/vendor
|
||||||
- go test -race
|
- go test -race
|
||||||
- GOOS=openbsd go build
|
- GOOS=openbsd go build
|
||||||
- GOOS=openbsd go test -c
|
- GOOS=openbsd go test -c
|
||||||
|
|||||||
+11
-8
@@ -24,10 +24,17 @@ import (
|
|||||||
|
|
||||||
var ErrNotAConsole = errors.New("provided file is not a console")
|
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 {
|
type Console interface {
|
||||||
io.Reader
|
File
|
||||||
io.Writer
|
|
||||||
io.Closer
|
|
||||||
|
|
||||||
// Resize resizes the console to the provided window size
|
// Resize resizes the console to the provided window size
|
||||||
Resize(WinSize) error
|
Resize(WinSize) error
|
||||||
@@ -42,10 +49,6 @@ type Console interface {
|
|||||||
Reset() error
|
Reset() error
|
||||||
// Size returns the window size of the console
|
// Size returns the window size of the console
|
||||||
Size() (WinSize, error)
|
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
|
// WinSize specifies the window size of the console
|
||||||
@@ -70,7 +73,7 @@ func Current() Console {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ConsoleFromFile returns a console using the provided file
|
// 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 {
|
if err := checkConsole(f); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|||||||
+6
-1
@@ -58,6 +58,7 @@ type Epoller struct {
|
|||||||
efd int
|
efd int
|
||||||
mu sync.Mutex
|
mu sync.Mutex
|
||||||
fdMapping map[int]*EpollConsole
|
fdMapping map[int]*EpollConsole
|
||||||
|
closeOnce sync.Once
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewEpoller returns an instance of epoller with a valid epoll fd.
|
// 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
|
// Close closes the epoll fd
|
||||||
func (e *Epoller) Close() error {
|
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
|
// EpollConsole acts like a console but registers its file descriptor with an
|
||||||
|
|||||||
+3
-3
@@ -47,7 +47,7 @@ func NewPty() (Console, string, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type master struct {
|
type master struct {
|
||||||
f *os.File
|
f File
|
||||||
original *unix.Termios
|
original *unix.Termios
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -122,7 +122,7 @@ func (m *master) Name() string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// checkConsole checks if the provided file is a console
|
// checkConsole checks if the provided file is a console
|
||||||
func checkConsole(f *os.File) error {
|
func checkConsole(f File) error {
|
||||||
var termios unix.Termios
|
var termios unix.Termios
|
||||||
if tcget(f.Fd(), &termios) != nil {
|
if tcget(f.Fd(), &termios) != nil {
|
||||||
return ErrNotAConsole
|
return ErrNotAConsole
|
||||||
@@ -130,7 +130,7 @@ func checkConsole(f *os.File) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func newMaster(f *os.File) (Console, error) {
|
func newMaster(f File) (Console, error) {
|
||||||
m := &master{
|
m := &master{
|
||||||
f: f,
|
f: f,
|
||||||
}
|
}
|
||||||
|
|||||||
+2
-2
@@ -198,7 +198,7 @@ func makeInputRaw(fd windows.Handle, mode uint32) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func checkConsole(f *os.File) error {
|
func checkConsole(f File) error {
|
||||||
var mode uint32
|
var mode uint32
|
||||||
if err := windows.GetConsoleMode(windows.Handle(f.Fd()), &mode); err != nil {
|
if err := windows.GetConsoleMode(windows.Handle(f.Fd()), &mode); err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -206,7 +206,7 @@ func checkConsole(f *os.File) error {
|
|||||||
return nil
|
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 {
|
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")
|
return nil, errors.New("creating a console from a file is not supported on windows")
|
||||||
}
|
}
|
||||||
|
|||||||
+8
@@ -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
|
||||||
|
)
|
||||||
+4
@@ -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=
|
||||||
+21
@@ -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.
|
||||||
+14
@@ -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())}...)
|
||||||
|
}
|
||||||
+345
@@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
+1
-1
@@ -27,7 +27,7 @@
|
|||||||
|
|
||||||
[people.akihirosuda]
|
[people.akihirosuda]
|
||||||
Name = "Akihiro Suda"
|
Name = "Akihiro Suda"
|
||||||
Email = "suda.akihiro@lab.ntt.co.jp"
|
Email = "akihiro.suda.cz@hco.ntt.co.jp"
|
||||||
GitHub = "AkihiroSuda"
|
GitHub = "AkihiroSuda"
|
||||||
|
|
||||||
[people.dnephin]
|
[people.dnephin]
|
||||||
|
|||||||
+1
-1
@@ -1,7 +1,7 @@
|
|||||||
dependencies:
|
dependencies:
|
||||||
post:
|
post:
|
||||||
# install golint
|
# install golint
|
||||||
- go get github.com/golang/lint/golint
|
- go get golang.org/x/lint/golint
|
||||||
|
|
||||||
test:
|
test:
|
||||||
pre:
|
pre:
|
||||||
|
|||||||
+1
-1
@@ -18,7 +18,7 @@ func HumanDuration(d time.Duration) string {
|
|||||||
return fmt.Sprintf("%d seconds", seconds)
|
return fmt.Sprintf("%d seconds", seconds)
|
||||||
} else if minutes := int(d.Minutes()); minutes == 1 {
|
} else if minutes := int(d.Minutes()); minutes == 1 {
|
||||||
return "About a minute"
|
return "About a minute"
|
||||||
} else if minutes < 46 {
|
} else if minutes < 60 {
|
||||||
return fmt.Sprintf("%d minutes", minutes)
|
return fmt.Sprintf("%d minutes", minutes)
|
||||||
} else if hours := int(d.Hours() + 0.5); hours == 1 {
|
} else if hours := int(d.Hours() + 0.5); hours == 1 {
|
||||||
return "About an hour"
|
return "About an hour"
|
||||||
|
|||||||
+7
-2
@@ -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])
|
return nil, fmt.Errorf("too many limit value arguments - %s, can only have up to two, `soft[:hard]`", parts[1])
|
||||||
}
|
}
|
||||||
|
|
||||||
if soft > *hard {
|
if *hard != -1 {
|
||||||
return nil, fmt.Errorf("ulimit soft limit must be less than or equal to hard limit: %d > %d", soft, *hard)
|
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
|
return &Ulimit{Name: parts[0], Soft: soft, Hard: *hard}, nil
|
||||||
|
|||||||
-3
@@ -1,7 +1,4 @@
|
|||||||
Go support for Protocol Buffers - Google's data interchange format
|
|
||||||
|
|
||||||
Copyright 2010 The Go Authors. All rights reserved.
|
Copyright 2010 The Go Authors. All rights reserved.
|
||||||
https://github.com/golang/protobuf
|
|
||||||
|
|
||||||
Redistribution and use in source and binary forms, with or without
|
Redistribution and use in source and binary forms, with or without
|
||||||
modification, are permitted provided that the following conditions are
|
modification, are permitted provided that the following conditions are
|
||||||
|
|||||||
-43
@@ -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
-11
@@ -35,22 +35,39 @@
|
|||||||
package proto
|
package proto
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
"reflect"
|
"reflect"
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Clone returns a deep copy of a protocol buffer.
|
// Clone returns a deep copy of a protocol buffer.
|
||||||
func Clone(pb Message) Message {
|
func Clone(src Message) Message {
|
||||||
in := reflect.ValueOf(pb)
|
in := reflect.ValueOf(src)
|
||||||
if in.IsNil() {
|
if in.IsNil() {
|
||||||
return pb
|
return src
|
||||||
}
|
}
|
||||||
|
|
||||||
out := reflect.New(in.Type().Elem())
|
out := reflect.New(in.Type().Elem())
|
||||||
// out is empty so a merge is a deep copy.
|
dst := out.Interface().(Message)
|
||||||
mergeStruct(out.Elem(), in.Elem())
|
Merge(dst, src)
|
||||||
return out.Interface().(Message)
|
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.
|
// Merge merges src into dst.
|
||||||
@@ -58,17 +75,24 @@ func Clone(pb Message) Message {
|
|||||||
// Elements of repeated fields will be appended.
|
// Elements of repeated fields will be appended.
|
||||||
// Merge panics if src and dst are not the same type, or if dst is nil.
|
// Merge panics if src and dst are not the same type, or if dst is nil.
|
||||||
func Merge(dst, src Message) {
|
func Merge(dst, src Message) {
|
||||||
|
if m, ok := dst.(Merger); ok {
|
||||||
|
m.Merge(src)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
in := reflect.ValueOf(src)
|
in := reflect.ValueOf(src)
|
||||||
out := reflect.ValueOf(dst)
|
out := reflect.ValueOf(dst)
|
||||||
if out.IsNil() {
|
if out.IsNil() {
|
||||||
panic("proto: nil destination")
|
panic("proto: nil destination")
|
||||||
}
|
}
|
||||||
if in.Type() != out.Type() {
|
if in.Type() != out.Type() {
|
||||||
// Explicit test prior to mergeStruct so that mistyped nils will fail
|
panic(fmt.Sprintf("proto.Merge(%T, %T) type mismatch", dst, src))
|
||||||
panic("proto: type mismatch")
|
|
||||||
}
|
}
|
||||||
if in.IsNil() {
|
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
|
return
|
||||||
}
|
}
|
||||||
mergeStruct(out.Elem(), in.Elem())
|
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])
|
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())
|
emOut, _ := extendable(out.Addr().Interface())
|
||||||
mIn, muIn := emIn.extensionsRead()
|
mIn, muIn := emIn.extensionsRead()
|
||||||
if mIn != nil {
|
if mIn != nil {
|
||||||
|
|||||||
+63
-606
@@ -39,8 +39,6 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"os"
|
|
||||||
"reflect"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// errOverflow is returned when an integer is too large to be represented.
|
// 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.
|
// wire type is encountered. It does not get returned to user code.
|
||||||
var ErrInternalBadWireType = errors.New("proto: internal error: bad wiretype for oneof")
|
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.
|
// DecodeVarint reads a varint-encoded integer from the slice.
|
||||||
// It returns the integer and the number of bytes consumed, or
|
// It returns the integer and the number of bytes consumed, or
|
||||||
// zero if there is not enough.
|
// zero if there is not enough.
|
||||||
@@ -192,7 +186,6 @@ func (p *Buffer) DecodeVarint() (x uint64, err error) {
|
|||||||
if b&0x80 == 0 {
|
if b&0x80 == 0 {
|
||||||
goto done
|
goto done
|
||||||
}
|
}
|
||||||
// x -= 0x80 << 63 // Always zero.
|
|
||||||
|
|
||||||
return 0, errOverflow
|
return 0, errOverflow
|
||||||
|
|
||||||
@@ -267,9 +260,6 @@ func (p *Buffer) DecodeZigzag32() (x uint64, err error) {
|
|||||||
return
|
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.
|
// DecodeRawBytes reads a count-delimited byte buffer from the Buffer.
|
||||||
// This is the format used for the bytes protocol buffer
|
// This is the format used for the bytes protocol buffer
|
||||||
// type and for embedded messages.
|
// type and for embedded messages.
|
||||||
@@ -311,81 +301,29 @@ func (p *Buffer) DecodeStringBytes() (s string, err error) {
|
|||||||
return string(buf), nil
|
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
|
// Unmarshaler is the interface representing objects that can
|
||||||
// unmarshal themselves. The method should reset the receiver before
|
// unmarshal themselves. The argument points to data that may be
|
||||||
// decoding starts. The argument points to data that may be
|
|
||||||
// overwritten, so implementations should not keep references to the
|
// overwritten, so implementations should not keep references to the
|
||||||
// buffer.
|
// 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 {
|
type Unmarshaler interface {
|
||||||
Unmarshal([]byte) error
|
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
|
// Unmarshal parses the protocol buffer representation in buf and places the
|
||||||
// decoded result in pb. If the struct underlying pb does not match
|
// decoded result in pb. If the struct underlying pb does not match
|
||||||
// the data in buf, the results can be unpredictable.
|
// the data in buf, the results can be unpredictable.
|
||||||
@@ -395,7 +333,13 @@ type Unmarshaler interface {
|
|||||||
// to preserve and append to existing data.
|
// to preserve and append to existing data.
|
||||||
func Unmarshal(buf []byte, pb Message) error {
|
func Unmarshal(buf []byte, pb Message) error {
|
||||||
pb.Reset()
|
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
|
// 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.
|
// UnmarshalMerge merges into existing data in pb.
|
||||||
// Most code should use Unmarshal instead.
|
// Most code should use Unmarshal instead.
|
||||||
func UnmarshalMerge(buf []byte, pb Message) error {
|
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 {
|
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 u.Unmarshal(buf)
|
||||||
}
|
}
|
||||||
return NewBuffer(buf).Unmarshal(pb)
|
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.
|
// 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 {
|
func (p *Buffer) DecodeGroup(pb Message) error {
|
||||||
typ, base, err := getbase(pb)
|
b := p.buf[p.index:]
|
||||||
if err != nil {
|
x, y := findEndGroup(b)
|
||||||
return err
|
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
|
// 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.
|
// Unlike proto.Unmarshal, this does not reset pb before starting to unmarshal.
|
||||||
func (p *Buffer) Unmarshal(pb Message) error {
|
func (p *Buffer) Unmarshal(pb Message) error {
|
||||||
// If the object can unmarshal itself, let it.
|
// 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 {
|
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:])
|
err := u.Unmarshal(p.buf[p.index:])
|
||||||
p.index = len(p.buf)
|
p.index = len(p.buf)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
typ, base, err := getbase(pb)
|
// Slow workaround for messages that aren't Unmarshalers.
|
||||||
if err != nil {
|
// This includes some hand-coded .pb.go files and
|
||||||
return err
|
// bootstrap protos.
|
||||||
}
|
// TODO: fix all of those and then add Unmarshal to
|
||||||
|
// the Message interface. Then:
|
||||||
err = p.unmarshalType(typ.Elem(), GetProperties(typ.Elem()), false, base)
|
// The cast above and code below can be deleted.
|
||||||
|
// The old unmarshaler can be deleted.
|
||||||
if collectStats {
|
// Clients can call Unmarshal directly (can already do that, actually).
|
||||||
stats.Decode++
|
var info InternalMessageInfo
|
||||||
}
|
err := info.Unmarshal(pb, p.buf[p.index:])
|
||||||
|
p.index = len(p.buf)
|
||||||
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
|
|
||||||
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|||||||
+63
@@ -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) {}
|
||||||
+200
-1
@@ -35,8 +35,14 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"reflect"
|
"reflect"
|
||||||
"strings"
|
"strings"
|
||||||
|
"sync"
|
||||||
|
"sync/atomic"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type generatedDiscarder interface {
|
||||||
|
XXX_DiscardUnknown()
|
||||||
|
}
|
||||||
|
|
||||||
// DiscardUnknown recursively discards all unknown fields from this message
|
// DiscardUnknown recursively discards all unknown fields from this message
|
||||||
// and all embedded messages.
|
// and all embedded messages.
|
||||||
//
|
//
|
||||||
@@ -49,9 +55,202 @@ import (
|
|||||||
// For proto2 messages, the unknown fields of message extensions are only
|
// For proto2 messages, the unknown fields of message extensions are only
|
||||||
// discarded from messages that have been accessed via GetExtension.
|
// discarded from messages that have been accessed via GetExtension.
|
||||||
func DiscardUnknown(m Message) {
|
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)
|
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) {
|
func discardLegacy(m Message) {
|
||||||
v := reflect.ValueOf(m)
|
v := reflect.ValueOf(m)
|
||||||
if v.Kind() != reflect.Ptr || v.IsNil() {
|
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
|
// For proto2 messages, only discard unknown fields in message extensions
|
||||||
// that have been accessed via GetExtension.
|
// 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.
|
// Ignore lock since discardLegacy is not concurrency safe.
|
||||||
emm, _ := em.extensionsRead()
|
emm, _ := em.extensionsRead()
|
||||||
for _, mx := range emm {
|
for _, mx := range emm {
|
||||||
|
|||||||
+24
-1183
File diff suppressed because it is too large
Load Diff
+17
-16
@@ -109,15 +109,6 @@ func equalStruct(v1, v2 reflect.Value) bool {
|
|||||||
// set/unset mismatch
|
// set/unset mismatch
|
||||||
return false
|
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()
|
f1, f2 = f1.Elem(), f2.Elem()
|
||||||
}
|
}
|
||||||
if !equalAny(f1, f2, sprop.Prop[i]) {
|
if !equalAny(f1, f2, sprop.Prop[i]) {
|
||||||
@@ -146,11 +137,7 @@ func equalStruct(v1, v2 reflect.Value) bool {
|
|||||||
|
|
||||||
u1 := uf.Bytes()
|
u1 := uf.Bytes()
|
||||||
u2 := v2.FieldByName("XXX_unrecognized").Bytes()
|
u2 := v2.FieldByName("XXX_unrecognized").Bytes()
|
||||||
if !bytes.Equal(u1, u2) {
|
return bytes.Equal(u1, u2)
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
return true
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// v1 and v2 are known to have the same type.
|
// 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
|
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 {
|
if m1 != nil && m2 != nil {
|
||||||
// Both are unencoded.
|
// Both are unencoded.
|
||||||
@@ -276,8 +273,12 @@ func equalExtMap(base reflect.Type, em1, em2 map[int32]Extension) bool {
|
|||||||
desc = m[extNum]
|
desc = m[extNum]
|
||||||
}
|
}
|
||||||
if desc == nil {
|
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)
|
log.Printf("proto: don't know how to compare extension %d of %v", extNum, base)
|
||||||
continue
|
return false
|
||||||
}
|
}
|
||||||
var err error
|
var err error
|
||||||
if m1 == nil {
|
if m1 == nil {
|
||||||
|
|||||||
+153
-133
@@ -38,6 +38,7 @@ package proto
|
|||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io"
|
||||||
"reflect"
|
"reflect"
|
||||||
"strconv"
|
"strconv"
|
||||||
"sync"
|
"sync"
|
||||||
@@ -91,14 +92,29 @@ func (n notLocker) Unlock() {}
|
|||||||
// extendable returns the extendableProto interface for the given generated proto message.
|
// 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
|
// If the proto message has the old extension format, it returns a wrapper that implements
|
||||||
// the extendableProto interface.
|
// the extendableProto interface.
|
||||||
func extendable(p interface{}) (extendableProto, bool) {
|
func extendable(p interface{}) (extendableProto, error) {
|
||||||
if ep, ok := p.(extendableProto); ok {
|
switch p := p.(type) {
|
||||||
return ep, ok
|
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 {
|
// Don't allocate a specific error containing %T:
|
||||||
return extensionAdapter{ep}, ok
|
// this is the hot path for Clone and MarshalText.
|
||||||
}
|
return nil, errNotExtendable
|
||||||
return nil, false
|
}
|
||||||
|
|
||||||
|
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.
|
// 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
|
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.
|
// ExtensionDesc represents an extension specification.
|
||||||
// Used in generated code from the protocol compiler.
|
// Used in generated code from the protocol compiler.
|
||||||
type ExtensionDesc struct {
|
type ExtensionDesc struct {
|
||||||
@@ -172,15 +185,31 @@ type Extension struct {
|
|||||||
// extension will have only enc set. When such an extension is
|
// extension will have only enc set. When such an extension is
|
||||||
// accessed using GetExtension (or GetExtensions) desc and value
|
// accessed using GetExtension (or GetExtensions) desc and value
|
||||||
// will be set.
|
// 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{}
|
value interface{}
|
||||||
enc []byte
|
|
||||||
|
// enc is the raw bytes for the extension field.
|
||||||
|
enc []byte
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetRawExtension is for testing only.
|
// SetRawExtension is for testing only.
|
||||||
func SetRawExtension(base Message, id int32, b []byte) {
|
func SetRawExtension(base Message, id int32, b []byte) {
|
||||||
epb, ok := extendable(base)
|
epb, err := extendable(base)
|
||||||
if !ok {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
extmap := epb.extensionsWrite()
|
extmap := epb.extensionsWrite()
|
||||||
@@ -205,7 +234,7 @@ func checkExtensionTypes(pb extendableProto, extension *ExtensionDesc) error {
|
|||||||
pbi = ea.extendableProtoV1
|
pbi = ea.extendableProtoV1
|
||||||
}
|
}
|
||||||
if a, b := reflect.TypeOf(pbi), reflect.TypeOf(extension.ExtendedType); a != b {
|
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.
|
// Check the range.
|
||||||
if !isExtensionField(pb, extension.Field) {
|
if !isExtensionField(pb, extension.Field) {
|
||||||
@@ -250,85 +279,11 @@ func extensionProperties(ed *ExtensionDesc) *Properties {
|
|||||||
return prop
|
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.
|
// HasExtension returns whether the given extension is present in pb.
|
||||||
func HasExtension(pb Message, extension *ExtensionDesc) bool {
|
func HasExtension(pb Message, extension *ExtensionDesc) bool {
|
||||||
// TODO: Check types, field numbers, etc.?
|
// TODO: Check types, field numbers, etc.?
|
||||||
epb, ok := extendable(pb)
|
epb, err := extendable(pb)
|
||||||
if !ok {
|
if err != nil {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
extmap, mu := epb.extensionsRead()
|
extmap, mu := epb.extensionsRead()
|
||||||
@@ -336,15 +291,15 @@ func HasExtension(pb Message, extension *ExtensionDesc) bool {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
mu.Lock()
|
mu.Lock()
|
||||||
_, ok = extmap[extension.Field]
|
_, ok := extmap[extension.Field]
|
||||||
mu.Unlock()
|
mu.Unlock()
|
||||||
return ok
|
return ok
|
||||||
}
|
}
|
||||||
|
|
||||||
// ClearExtension removes the given extension from pb.
|
// ClearExtension removes the given extension from pb.
|
||||||
func ClearExtension(pb Message, extension *ExtensionDesc) {
|
func ClearExtension(pb Message, extension *ExtensionDesc) {
|
||||||
epb, ok := extendable(pb)
|
epb, err := extendable(pb)
|
||||||
if !ok {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
// TODO: Check types, field numbers, etc.?
|
// TODO: Check types, field numbers, etc.?
|
||||||
@@ -352,16 +307,26 @@ func ClearExtension(pb Message, extension *ExtensionDesc) {
|
|||||||
delete(extmap, extension.Field)
|
delete(extmap, extension.Field)
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetExtension parses and returns the given extension of pb.
|
// GetExtension retrieves a proto2 extended field from pb.
|
||||||
// If the extension is not present and has no default value it returns ErrMissingExtension.
|
//
|
||||||
|
// 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) {
|
func GetExtension(pb Message, extension *ExtensionDesc) (interface{}, error) {
|
||||||
epb, ok := extendable(pb)
|
epb, err := extendable(pb)
|
||||||
if !ok {
|
if err != nil {
|
||||||
return nil, errors.New("proto: not an extendable proto")
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := checkExtensionTypes(epb, extension); err != nil {
|
if extension.ExtendedType != nil {
|
||||||
return nil, err
|
// can only check type if this is a complete descriptor
|
||||||
|
if err := checkExtensionTypes(epb, extension); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
emap, mu := epb.extensionsRead()
|
emap, mu := epb.extensionsRead()
|
||||||
@@ -385,7 +350,12 @@ func GetExtension(pb Message, extension *ExtensionDesc) (interface{}, error) {
|
|||||||
// descriptors with the same field number.
|
// descriptors with the same field number.
|
||||||
return nil, errors.New("proto: descriptor conflict")
|
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)
|
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.
|
// Remember the decoded version and drop the encoded version.
|
||||||
// That way it is safe to mutate what we return.
|
// That way it is safe to mutate what we return.
|
||||||
e.value = v
|
e.value = extensionAsStorageType(v)
|
||||||
e.desc = extension
|
e.desc = extension
|
||||||
e.enc = nil
|
e.enc = nil
|
||||||
emap[extension.Field] = e
|
emap[extension.Field] = e
|
||||||
return e.value, nil
|
return extensionAsLegacyType(e.value), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// defaultExtensionValue returns the default value for extension.
|
// defaultExtensionValue returns the default value for extension.
|
||||||
// If no default for an extension is defined ErrMissingExtension is returned.
|
// If no default for an extension is defined ErrMissingExtension is returned.
|
||||||
func defaultExtensionValue(extension *ExtensionDesc) (interface{}, error) {
|
func defaultExtensionValue(extension *ExtensionDesc) (interface{}, error) {
|
||||||
|
if extension.ExtensionType == nil {
|
||||||
|
// incomplete descriptor, so no default
|
||||||
|
return nil, ErrMissingExtension
|
||||||
|
}
|
||||||
|
|
||||||
t := reflect.TypeOf(extension.ExtensionType)
|
t := reflect.TypeOf(extension.ExtensionType)
|
||||||
props := extensionProperties(extension)
|
props := extensionProperties(extension)
|
||||||
|
|
||||||
@@ -439,31 +414,28 @@ func defaultExtensionValue(extension *ExtensionDesc) (interface{}, error) {
|
|||||||
|
|
||||||
// decodeExtension decodes an extension encoded in b.
|
// decodeExtension decodes an extension encoded in b.
|
||||||
func decodeExtension(b []byte, extension *ExtensionDesc) (interface{}, error) {
|
func decodeExtension(b []byte, extension *ExtensionDesc) (interface{}, error) {
|
||||||
o := NewBuffer(b)
|
|
||||||
|
|
||||||
t := reflect.TypeOf(extension.ExtensionType)
|
t := reflect.TypeOf(extension.ExtensionType)
|
||||||
|
unmarshal := typeUnmarshaler(t, extension.Tag)
|
||||||
props := extensionProperties(extension)
|
|
||||||
|
|
||||||
// t is a pointer to a struct, pointer to basic type or a slice.
|
// t is a pointer to a struct, pointer to basic type or a slice.
|
||||||
// Allocate a "field" to store the pointer/slice itself; the
|
// Allocate space to store the pointer/slice.
|
||||||
// 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 }.
|
|
||||||
value := reflect.New(t).Elem()
|
value := reflect.New(t).Elem()
|
||||||
|
|
||||||
|
var err error
|
||||||
for {
|
for {
|
||||||
// Discard wire type and field number varint. It isn't needed.
|
x, n := decodeVarint(b)
|
||||||
if _, err := o.DecodeVarint(); err != nil {
|
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
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := props.dec(o, props, toStructPointer(value.Addr())); err != nil {
|
if len(b) == 0 {
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if o.index >= len(o.buf) {
|
|
||||||
break
|
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.
|
// 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.
|
// 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) {
|
func GetExtensions(pb Message, es []*ExtensionDesc) (extensions []interface{}, err error) {
|
||||||
epb, ok := extendable(pb)
|
epb, err := extendable(pb)
|
||||||
if !ok {
|
if err != nil {
|
||||||
return nil, errors.New("proto: not an extendable proto")
|
return nil, err
|
||||||
}
|
}
|
||||||
extensions = make([]interface{}, len(es))
|
extensions = make([]interface{}, len(es))
|
||||||
for i, e := range 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
|
// For non-registered extensions, ExtensionDescs returns an incomplete descriptor containing
|
||||||
// just the Field field, which defines the extension's field number.
|
// just the Field field, which defines the extension's field number.
|
||||||
func ExtensionDescs(pb Message) ([]*ExtensionDesc, error) {
|
func ExtensionDescs(pb Message) ([]*ExtensionDesc, error) {
|
||||||
epb, ok := extendable(pb)
|
epb, err := extendable(pb)
|
||||||
if !ok {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("proto: %T is not an extendable proto.Message", pb)
|
return nil, err
|
||||||
}
|
}
|
||||||
registeredExtensions := RegisteredExtensions(pb)
|
registeredExtensions := RegisteredExtensions(pb)
|
||||||
|
|
||||||
@@ -523,16 +495,16 @@ func ExtensionDescs(pb Message) ([]*ExtensionDesc, error) {
|
|||||||
|
|
||||||
// SetExtension sets the specified extension of pb to the specified value.
|
// SetExtension sets the specified extension of pb to the specified value.
|
||||||
func SetExtension(pb Message, extension *ExtensionDesc, value interface{}) error {
|
func SetExtension(pb Message, extension *ExtensionDesc, value interface{}) error {
|
||||||
epb, ok := extendable(pb)
|
epb, err := extendable(pb)
|
||||||
if !ok {
|
if err != nil {
|
||||||
return errors.New("proto: not an extendable proto")
|
return err
|
||||||
}
|
}
|
||||||
if err := checkExtensionTypes(epb, extension); err != nil {
|
if err := checkExtensionTypes(epb, extension); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
typ := reflect.TypeOf(extension.ExtensionType)
|
typ := reflect.TypeOf(extension.ExtensionType)
|
||||||
if typ != reflect.TypeOf(value) {
|
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
|
// nil extension values need to be caught early, because the
|
||||||
// encoder can't distinguish an ErrNil due to a nil extension
|
// 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 := epb.extensionsWrite()
|
||||||
extmap[extension.Field] = Extension{desc: extension, value: value}
|
extmap[extension.Field] = Extension{desc: extension, value: extensionAsStorageType(value)}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// ClearAllExtensions clears all extensions from pb.
|
// ClearAllExtensions clears all extensions from pb.
|
||||||
func ClearAllExtensions(pb Message) {
|
func ClearAllExtensions(pb Message) {
|
||||||
epb, ok := extendable(pb)
|
epb, err := extendable(pb)
|
||||||
if !ok {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
m := epb.extensionsWrite()
|
m := epb.extensionsWrite()
|
||||||
@@ -585,3 +557,51 @@ func RegisterExtension(desc *ExtensionDesc) {
|
|||||||
func RegisteredExtensions(pb Message) map[int32]*ExtensionDesc {
|
func RegisteredExtensions(pb Message) map[int32]*ExtensionDesc {
|
||||||
return extensionMaps[reflect.TypeOf(pb).Elem()]
|
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
|
||||||
|
}
|
||||||
|
|||||||
+117
-49
@@ -273,6 +273,67 @@ import (
|
|||||||
"sync"
|
"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.
|
// Message is implemented by generated protocol buffer messages.
|
||||||
type Message interface {
|
type Message interface {
|
||||||
Reset()
|
Reset()
|
||||||
@@ -280,26 +341,6 @@ type Message interface {
|
|||||||
ProtoMessage()
|
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
|
// A Buffer is a buffer manager for marshaling and unmarshaling
|
||||||
// protocol buffers. It may be reused between invocations to
|
// protocol buffers. It may be reused between invocations to
|
||||||
// reduce memory usage. It is not necessary to use a Buffer;
|
// reduce memory usage. It is not necessary to use a Buffer;
|
||||||
@@ -309,16 +350,7 @@ type Buffer struct {
|
|||||||
buf []byte // encode/decode byte stream
|
buf []byte // encode/decode byte stream
|
||||||
index int // read point
|
index int // read point
|
||||||
|
|
||||||
// pools of basic types to amortize allocation.
|
deterministic bool
|
||||||
bools []bool
|
|
||||||
uint32s []uint32
|
|
||||||
uint64s []uint64
|
|
||||||
|
|
||||||
// extra pools, only used with pointer_reflect.go
|
|
||||||
int32s []int32
|
|
||||||
int64s []int64
|
|
||||||
float32s []float32
|
|
||||||
float64s []float64
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewBuffer allocates a new Buffer and initializes its internal data to
|
// 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.
|
// Bytes returns the contents of the Buffer.
|
||||||
func (p *Buffer) Bytes() []byte { return p.buf }
|
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.
|
* 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
|
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.
|
// 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 {
|
func mapKeys(vs []reflect.Value) sort.Interface {
|
||||||
s := mapKeySorter{
|
s := mapKeySorter{vs: vs}
|
||||||
vs: vs,
|
|
||||||
// default Less function: textual comparison
|
|
||||||
less: func(a, b reflect.Value) bool {
|
|
||||||
return fmt.Sprint(a.Interface()) < fmt.Sprint(b.Interface())
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
// Type specialization per https://developers.google.com/protocol-buffers/docs/proto#maps;
|
// Type specialization per https://developers.google.com/protocol-buffers/docs/proto#maps.
|
||||||
// numeric keys are sorted numerically.
|
|
||||||
if len(vs) == 0 {
|
if len(vs) == 0 {
|
||||||
return s
|
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() }
|
s.less = func(a, b reflect.Value) bool { return a.Int() < b.Int() }
|
||||||
case reflect.Uint32, reflect.Uint64:
|
case reflect.Uint32, reflect.Uint64:
|
||||||
s.less = func(a, b reflect.Value) bool { return a.Uint() < b.Uint() }
|
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
|
return s
|
||||||
@@ -888,10 +940,26 @@ func isProto3Zero(v reflect.Value) bool {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
// ProtoPackageIsVersion2 is referenced from generated protocol buffer files
|
const (
|
||||||
// to assert that that code is compatible with this version of the proto package.
|
// ProtoPackageIsVersion3 is referenced from generated protocol buffer files
|
||||||
const ProtoPackageIsVersion2 = true
|
// to assert that that code is compatible with this version of the proto package.
|
||||||
|
ProtoPackageIsVersion3 = true
|
||||||
|
|
||||||
// ProtoPackageIsVersion1 is referenced from generated protocol buffer files
|
// ProtoPackageIsVersion2 is referenced from generated protocol buffer files
|
||||||
// to assert that that code is compatible with this version of the proto package.
|
// to assert that that code is compatible with this version of the proto package.
|
||||||
const ProtoPackageIsVersion1 = true
|
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
|
||||||
|
}
|
||||||
|
|||||||
+4
-134
@@ -36,12 +36,7 @@ package proto
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
|
||||||
"encoding/json"
|
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
|
||||||
"reflect"
|
|
||||||
"sort"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// errNoMessageTypeID occurs when a protocol buffer does not have a message type ID.
|
// 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 {
|
func (ms *messageSet) Has(pb Message) bool {
|
||||||
if ms.find(pb) != nil {
|
return ms.find(pb) != nil
|
||||||
return true
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ms *messageSet) Unmarshal(pb Message) error {
|
func (ms *messageSet) Unmarshal(pb Message) error {
|
||||||
@@ -147,50 +139,9 @@ func skipVarint(buf []byte) []byte {
|
|||||||
return buf[i+1:]
|
return buf[i+1:]
|
||||||
}
|
}
|
||||||
|
|
||||||
// MarshalMessageSet encodes the extension map represented by m in the message set wire format.
|
// unmarshalMessageSet decodes the extension map encoded in buf in the message set wire format.
|
||||||
// It is called by generated Marshal methods on protocol buffer messages with the message_set_wire_format option.
|
// It is called by Unmarshal methods on protocol buffer messages with the message_set_wire_format option.
|
||||||
func MarshalMessageSet(exts interface{}) ([]byte, error) {
|
func unmarshalMessageSet(buf []byte, exts interface{}) 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 {
|
|
||||||
var m map[int32]Extension
|
var m map[int32]Extension
|
||||||
switch exts := exts.(type) {
|
switch exts := exts.(type) {
|
||||||
case *XXX_InternalExtensions:
|
case *XXX_InternalExtensions:
|
||||||
@@ -228,84 +179,3 @@ func UnmarshalMessageSet(buf []byte, exts interface{}) error {
|
|||||||
}
|
}
|
||||||
return nil
|
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,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
+262
-386
@@ -29,7 +29,7 @@
|
|||||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
// 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.
|
// 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
|
// It is slower than the code in pointer_unsafe.go but it avoids package unsafe and can
|
||||||
@@ -38,32 +38,13 @@
|
|||||||
package proto
|
package proto
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"math"
|
|
||||||
"reflect"
|
"reflect"
|
||||||
|
"sync"
|
||||||
)
|
)
|
||||||
|
|
||||||
// A structPointer is a pointer to a struct.
|
const unsafeAllowed = false
|
||||||
type structPointer struct {
|
|
||||||
v reflect.Value
|
|
||||||
}
|
|
||||||
|
|
||||||
// toStructPointer returns a structPointer equivalent to the given reflect value.
|
// A field identifies a field in a struct, accessible from a pointer.
|
||||||
// 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.
|
|
||||||
// In this implementation, a field is identified by the sequence of field indices
|
// In this implementation, a field is identified by the sequence of field indices
|
||||||
// passed to reflect's FieldByIndex.
|
// passed to reflect's FieldByIndex.
|
||||||
type field []int
|
type field []int
|
||||||
@@ -76,409 +57,304 @@ func toField(f *reflect.StructField) field {
|
|||||||
// invalidField is an invalid field identifier.
|
// invalidField is an invalid field identifier.
|
||||||
var invalidField = field(nil)
|
var invalidField = field(nil)
|
||||||
|
|
||||||
|
// zeroField is a noop when calling pointer.offset.
|
||||||
|
var zeroField = field([]int{})
|
||||||
|
|
||||||
// IsValid reports whether the field identifier is valid.
|
// IsValid reports whether the field identifier is valid.
|
||||||
func (f field) IsValid() bool { return f != nil }
|
func (f field) IsValid() bool { return f != nil }
|
||||||
|
|
||||||
// field returns the given field in the struct as a reflect value.
|
// The pointer type is for the table-driven decoder.
|
||||||
func structPointer_field(p structPointer, f field) reflect.Value {
|
// The implementation here uses a reflect.Value of pointer type to
|
||||||
// Special case: an extension map entry with a value of type T
|
// create a generic pointer. In pointer_unsafe.go we use unsafe
|
||||||
// passes a *T to the struct-handling code with a zero field,
|
// instead of reflect to implement the same (but faster) interface.
|
||||||
// expecting that it will be treated as equivalent to *struct{ X T },
|
type pointer struct {
|
||||||
// which has the same memory layout. We have to handle that case
|
v reflect.Value
|
||||||
// specially, because reflect will panic if we call FieldByIndex on a
|
}
|
||||||
// non-struct.
|
|
||||||
if f == nil {
|
// toPointer converts an interface of pointer type to a pointer
|
||||||
return p.v.Elem()
|
// 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 pointer{v: u}
|
||||||
return p.v.Elem().FieldByIndex(f)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ifield returns the given field in the struct as an interface value.
|
// valToPointer converts v to a pointer. v must be of pointer type.
|
||||||
func structPointer_ifield(p structPointer, f field) interface{} {
|
func valToPointer(v reflect.Value) pointer {
|
||||||
return structPointer_field(p, f).Addr().Interface()
|
return pointer{v: v}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Bytes returns the address of a []byte field in the struct.
|
// offset converts from a pointer to a structure to a pointer to
|
||||||
func structPointer_Bytes(p structPointer, f field) *[]byte {
|
// one of its fields.
|
||||||
return structPointer_ifield(p, f).(*[]byte)
|
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 (p pointer) isNil() bool {
|
||||||
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 {
|
|
||||||
return p.v.IsNil()
|
return p.v.IsNil()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set sets p to point at a newly allocated word with bits set to x.
|
// grow updates the slice s in place to make it one element longer.
|
||||||
func word32_Set(p word32, o *Buffer, x uint32) {
|
// s must be addressable.
|
||||||
t := p.v.Type().Elem()
|
// Returns the (addressable) new element.
|
||||||
switch t {
|
func grow(s reflect.Value) reflect.Value {
|
||||||
case int32Type:
|
n, m := s.Len(), s.Cap()
|
||||||
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()
|
|
||||||
if n < m {
|
if n < m {
|
||||||
p.v.SetLen(n + 1)
|
s.SetLen(n + 1)
|
||||||
} else {
|
} else {
|
||||||
t := p.v.Type().Elem()
|
s.Set(reflect.Append(s, reflect.Zero(s.Type().Elem())))
|
||||||
p.v.Set(reflect.Append(p.v, reflect.Zero(t)))
|
|
||||||
}
|
}
|
||||||
elem := p.v.Index(n)
|
return s.Index(n)
|
||||||
switch elem.Kind() {
|
}
|
||||||
case reflect.Int32:
|
|
||||||
elem.SetInt(int64(int32(x)))
|
func (p pointer) toInt64() *int64 {
|
||||||
case reflect.Uint32:
|
return p.v.Interface().(*int64)
|
||||||
elem.SetUint(uint64(x))
|
}
|
||||||
case reflect.Float32:
|
func (p pointer) toInt64Ptr() **int64 {
|
||||||
elem.SetFloat(float64(math.Float32frombits(x)))
|
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 {
|
// getInt32Slice copies []int32 from p as a new slice.
|
||||||
return p.v.Len()
|
// 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)) {
|
||||||
func (p word32Slice) Index(i int) uint32 {
|
// raw int32 type
|
||||||
elem := p.v.Index(i)
|
return p.v.Elem().Interface().([]int32)
|
||||||
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")
|
// 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.
|
// setInt32Slice copies []int32 into p as a new slice.
|
||||||
func structPointer_Word32Slice(p structPointer, f field) word32Slice {
|
// This behavior differs from the implementation in pointer_unsafe.go.
|
||||||
return word32Slice{structPointer_field(p, f)}
|
func (p pointer) setInt32Slice(v []int32) {
|
||||||
}
|
if p.v.Type().Elem().Elem() == reflect.TypeOf(int32(0)) {
|
||||||
|
// raw int32 type
|
||||||
// word64 is like word32 but for 64-bit values.
|
p.v.Elem().Set(reflect.ValueOf(v))
|
||||||
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:]
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
panic("unreachable")
|
// an enum
|
||||||
}
|
// Allocate a []enum, then assign []int32's values into it.
|
||||||
|
// Note: we can't convert []enum to []int32.
|
||||||
func word64_IsNil(p word64) bool {
|
slice := reflect.MakeSlice(p.v.Type().Elem(), len(v), cap(v))
|
||||||
return p.v.IsNil()
|
for i, x := range v {
|
||||||
}
|
slice.Index(i).SetInt(int64(x))
|
||||||
|
|
||||||
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())
|
|
||||||
}
|
}
|
||||||
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 {
|
func (p pointer) toUint64() *uint64 {
|
||||||
return word64{structPointer_field(p, f)}
|
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.
|
// getPointerSlice copies []*T from p as a new []pointer.
|
||||||
type word64Val struct {
|
// This behavior differs from the implementation in pointer_unsafe.go.
|
||||||
v reflect.Value
|
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) {
|
// setPointerSlice copies []pointer into p as a new []*T.
|
||||||
switch p.v.Type() {
|
// This behavior differs from the implementation in pointer_unsafe.go.
|
||||||
case int64Type:
|
func (p pointer) setPointerSlice(v []pointer) {
|
||||||
p.v.SetInt(int64(x))
|
if v == nil {
|
||||||
return
|
p.v.Elem().Set(reflect.New(p.v.Elem().Type()).Elem())
|
||||||
case uint64Type:
|
|
||||||
p.v.SetUint(x)
|
|
||||||
return
|
|
||||||
case float64Type:
|
|
||||||
p.v.SetFloat(math.Float64frombits(x))
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
panic("unreachable")
|
s := reflect.MakeSlice(p.v.Elem().Type(), 0, len(v))
|
||||||
}
|
for _, p := range v {
|
||||||
|
s = reflect.Append(s, p.v)
|
||||||
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())
|
|
||||||
}
|
}
|
||||||
panic("unreachable")
|
p.v.Elem().Set(s)
|
||||||
}
|
}
|
||||||
|
|
||||||
func structPointer_Word64Val(p structPointer, f field) word64Val {
|
// getInterfacePointer returns a pointer that points to the
|
||||||
return word64Val{structPointer_field(p, f)}
|
// interface data of the interface pointed by p.
|
||||||
}
|
func (p pointer) getInterfacePointer() pointer {
|
||||||
|
if p.v.Elem().IsNil() {
|
||||||
type word64Slice struct {
|
return pointer{v: p.v.Elem()}
|
||||||
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)))
|
|
||||||
}
|
}
|
||||||
|
return pointer{v: p.v.Elem().Elem().Elem().Field(0).Addr()} // *interface -> interface -> *struct -> struct
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p word64Slice) Len() int {
|
func (p pointer) asPointerTo(t reflect.Type) reflect.Value {
|
||||||
return p.v.Len()
|
// TODO: check that p.v.Type().Elem() == t?
|
||||||
|
return p.v
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p word64Slice) Index(i int) uint64 {
|
func atomicLoadUnmarshalInfo(p **unmarshalInfo) *unmarshalInfo {
|
||||||
elem := p.v.Index(i)
|
atomicLock.Lock()
|
||||||
switch elem.Kind() {
|
defer atomicLock.Unlock()
|
||||||
case reflect.Int64:
|
return *p
|
||||||
return uint64(elem.Int())
|
}
|
||||||
case reflect.Uint64:
|
func atomicStoreUnmarshalInfo(p **unmarshalInfo, v *unmarshalInfo) {
|
||||||
return uint64(elem.Uint())
|
atomicLock.Lock()
|
||||||
case reflect.Float64:
|
defer atomicLock.Unlock()
|
||||||
return math.Float64bits(float64(elem.Float()))
|
*p = v
|
||||||
}
|
}
|
||||||
panic("unreachable")
|
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 {
|
var atomicLock sync.Mutex
|
||||||
return word64Slice{structPointer_field(p, f)}
|
|
||||||
}
|
|
||||||
|
|||||||
+235
-192
@@ -29,7 +29,7 @@
|
|||||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
// 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.
|
// This file contains the implementation of the proto field accesses using package unsafe.
|
||||||
|
|
||||||
@@ -37,38 +37,13 @@ package proto
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"reflect"
|
"reflect"
|
||||||
|
"sync/atomic"
|
||||||
"unsafe"
|
"unsafe"
|
||||||
)
|
)
|
||||||
|
|
||||||
// NOTE: These type_Foo functions would more idiomatically be methods,
|
const unsafeAllowed = true
|
||||||
// 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.
|
|
||||||
|
|
||||||
// A structPointer is a pointer to a struct.
|
// A field identifies a field in a struct, accessible from a pointer.
|
||||||
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.
|
|
||||||
// In this implementation, a field is identified by its byte offset from the start of the struct.
|
// In this implementation, a field is identified by its byte offset from the start of the struct.
|
||||||
type field uintptr
|
type field uintptr
|
||||||
|
|
||||||
@@ -80,191 +55,259 @@ func toField(f *reflect.StructField) field {
|
|||||||
// invalidField is an invalid field identifier.
|
// invalidField is an invalid field identifier.
|
||||||
const invalidField = ^field(0)
|
const invalidField = ^field(0)
|
||||||
|
|
||||||
|
// zeroField is a noop when calling pointer.offset.
|
||||||
|
const zeroField = field(0)
|
||||||
|
|
||||||
// IsValid reports whether the field identifier is valid.
|
// IsValid reports whether the field identifier is valid.
|
||||||
func (f field) IsValid() bool {
|
func (f field) IsValid() bool {
|
||||||
return f != ^field(0)
|
return f != invalidField
|
||||||
}
|
}
|
||||||
|
|
||||||
// Bytes returns the address of a []byte field in the struct.
|
// The pointer type below is for the new table-driven encoder/decoder.
|
||||||
func structPointer_Bytes(p structPointer, f field) *[]byte {
|
// The implementation here uses unsafe.Pointer to create a generic pointer.
|
||||||
return (*[]byte)(unsafe.Pointer(uintptr(p) + uintptr(f)))
|
// 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.
|
// size of pointer
|
||||||
func structPointer_BytesSlice(p structPointer, f field) *[][]byte {
|
var ptrSize = unsafe.Sizeof(uintptr(0))
|
||||||
return (*[][]byte)(unsafe.Pointer(uintptr(p) + uintptr(f)))
|
|
||||||
|
// 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.
|
// toAddrPointer converts an interface to a pointer that points to
|
||||||
func structPointer_Bool(p structPointer, f field) **bool {
|
// the interface data.
|
||||||
return (**bool)(unsafe.Pointer(uintptr(p) + uintptr(f)))
|
func toAddrPointer(i *interface{}, isptr, deref bool) (p pointer) {
|
||||||
}
|
// Super-tricky - read or get the address of data word of interface value.
|
||||||
|
if isptr {
|
||||||
// BoolVal returns the address of a bool field in the struct.
|
// The interface is of pointer type, thus it is a direct interface.
|
||||||
func structPointer_BoolVal(p structPointer, f field) *bool {
|
// The data word is the pointer data itself. We take its address.
|
||||||
return (*bool)(unsafe.Pointer(uintptr(p) + uintptr(f)))
|
p = pointer{p: unsafe.Pointer(uintptr(unsafe.Pointer(i)) + ptrSize)}
|
||||||
}
|
} else {
|
||||||
|
// The interface is not of pointer type. The data word is the pointer
|
||||||
// BoolSlice returns the address of a []bool field in the struct.
|
// to the data.
|
||||||
func structPointer_BoolSlice(p structPointer, f field) *[]bool {
|
p = pointer{p: (*[2]unsafe.Pointer)(unsafe.Pointer(i))[1]}
|
||||||
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)
|
|
||||||
}
|
}
|
||||||
o.uint32s[0] = x
|
if deref {
|
||||||
*p = &o.uint32s[0]
|
p.p = *(*unsafe.Pointer)(p.p)
|
||||||
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)
|
|
||||||
}
|
}
|
||||||
o.uint64s[0] = x
|
return p
|
||||||
*p = &o.uint64s[0]
|
|
||||||
o.uint64s = o.uint64s[1:]
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func word64_IsNil(p word64) bool {
|
// valToPointer converts v to a pointer. v must be of pointer type.
|
||||||
return *p == nil
|
func valToPointer(v reflect.Value) pointer {
|
||||||
|
return pointer{p: unsafe.Pointer(v.Pointer())}
|
||||||
}
|
}
|
||||||
|
|
||||||
func word64_Get(p word64) uint64 {
|
// offset converts from a pointer to a structure to a pointer to
|
||||||
return **p
|
// 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 {
|
func (p pointer) isNil() bool {
|
||||||
return word64((**uint64)(unsafe.Pointer(uintptr(p) + uintptr(f))))
|
return p.p == nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// word64Val is like word32Val but for 64-bit values.
|
func (p pointer) toInt64() *int64 {
|
||||||
type word64Val *uint64
|
return (*int64)(p.p)
|
||||||
|
}
|
||||||
func word64Val_Set(p word64Val, o *Buffer, x uint64) {
|
func (p pointer) toInt64Ptr() **int64 {
|
||||||
*p = x
|
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 {
|
// See pointer_reflect.go for why toInt32Ptr/Slice doesn't exist.
|
||||||
return *p
|
/*
|
||||||
|
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 {
|
// getInt32Slice loads a []int32 from p.
|
||||||
return word64Val((*uint64)(unsafe.Pointer(uintptr(p) + uintptr(f))))
|
// 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.
|
// setInt32Slice stores a []int32 to p.
|
||||||
type word64Slice []uint64
|
// The value set is aliased with the input slice.
|
||||||
|
// This behavior differs from the implementation in pointer_reflect.go.
|
||||||
func (v *word64Slice) Append(x uint64) { *v = append(*v, x) }
|
func (p pointer) setInt32Slice(v []int32) {
|
||||||
func (v *word64Slice) Len() int { return len(*v) }
|
*(*[]int32)(p.p) = v
|
||||||
func (v *word64Slice) Index(i int) uint64 { return (*v)[i] }
|
}
|
||||||
|
|
||||||
func structPointer_Word64Slice(p structPointer, f field) *word64Slice {
|
// TODO: Can we get rid of appendInt32Slice and use setInt32Slice instead?
|
||||||
return (*word64Slice)(unsafe.Pointer(uintptr(p) + uintptr(f)))
|
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))
|
||||||
}
|
}
|
||||||
|
|||||||
+72
-400
@@ -38,7 +38,6 @@ package proto
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
"os"
|
|
||||||
"reflect"
|
"reflect"
|
||||||
"sort"
|
"sort"
|
||||||
"strconv"
|
"strconv"
|
||||||
@@ -58,42 +57,6 @@ const (
|
|||||||
WireFixed32 = 5
|
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
|
// 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
|
// use-cases. Encoded protocol buffers are often in tag order with small tag
|
||||||
// numbers.
|
// numbers.
|
||||||
@@ -140,13 +103,6 @@ type StructProperties struct {
|
|||||||
decoderTags tagMap // map from proto tag to struct field number
|
decoderTags tagMap // map from proto tag to struct field number
|
||||||
decoderOrigNames map[string]int // map from original name 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
|
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.
|
// OneofTypes contains information about the oneof fields in this message.
|
||||||
// It is keyed by the original name of a field.
|
// It is keyed by the original name of a field.
|
||||||
@@ -182,41 +138,24 @@ type Properties struct {
|
|||||||
Repeated bool
|
Repeated bool
|
||||||
Packed bool // relevant for repeated primitives only
|
Packed bool // relevant for repeated primitives only
|
||||||
Enum string // set for enum types 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
|
oneof bool // whether this is a oneof field
|
||||||
|
|
||||||
Default string // default value
|
Default string // default value
|
||||||
HasDefault bool // whether an explicit default was provided
|
HasDefault bool // whether an explicit default was provided
|
||||||
def_uint64 uint64
|
|
||||||
|
|
||||||
enc encoder
|
stype reflect.Type // set for struct types only
|
||||||
valEnc valueEncoder // set for bool and numeric types only
|
sprop *StructProperties // set for struct 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
|
|
||||||
|
|
||||||
mtype reflect.Type // set for map types only
|
mtype reflect.Type // set for map types only
|
||||||
mkeyprop *Properties // set for map types only
|
MapKeyProp *Properties // set for map types only
|
||||||
mvalprop *Properties // set for map types only
|
MapValProp *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
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// String formats the properties in the protobuf struct field tag style.
|
// String formats the properties in the protobuf struct field tag style.
|
||||||
func (p *Properties) String() string {
|
func (p *Properties) String() string {
|
||||||
s := p.Wire
|
s := p.Wire
|
||||||
s = ","
|
s += ","
|
||||||
s += strconv.Itoa(p.Tag)
|
s += strconv.Itoa(p.Tag)
|
||||||
if p.Required {
|
if p.Required {
|
||||||
s += ",req"
|
s += ",req"
|
||||||
@@ -254,7 +193,7 @@ func (p *Properties) Parse(s string) {
|
|||||||
// "bytes,49,opt,name=foo,def=hello!"
|
// "bytes,49,opt,name=foo,def=hello!"
|
||||||
fields := strings.Split(s, ",") // breaks def=, but handled below.
|
fields := strings.Split(s, ",") // breaks def=, but handled below.
|
||||||
if len(fields) < 2 {
|
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
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -262,34 +201,19 @@ func (p *Properties) Parse(s string) {
|
|||||||
switch p.Wire {
|
switch p.Wire {
|
||||||
case "varint":
|
case "varint":
|
||||||
p.WireType = WireVarint
|
p.WireType = WireVarint
|
||||||
p.valEnc = (*Buffer).EncodeVarint
|
|
||||||
p.valDec = (*Buffer).DecodeVarint
|
|
||||||
p.valSize = sizeVarint
|
|
||||||
case "fixed32":
|
case "fixed32":
|
||||||
p.WireType = WireFixed32
|
p.WireType = WireFixed32
|
||||||
p.valEnc = (*Buffer).EncodeFixed32
|
|
||||||
p.valDec = (*Buffer).DecodeFixed32
|
|
||||||
p.valSize = sizeFixed32
|
|
||||||
case "fixed64":
|
case "fixed64":
|
||||||
p.WireType = WireFixed64
|
p.WireType = WireFixed64
|
||||||
p.valEnc = (*Buffer).EncodeFixed64
|
|
||||||
p.valDec = (*Buffer).DecodeFixed64
|
|
||||||
p.valSize = sizeFixed64
|
|
||||||
case "zigzag32":
|
case "zigzag32":
|
||||||
p.WireType = WireVarint
|
p.WireType = WireVarint
|
||||||
p.valEnc = (*Buffer).EncodeZigzag32
|
|
||||||
p.valDec = (*Buffer).DecodeZigzag32
|
|
||||||
p.valSize = sizeZigzag32
|
|
||||||
case "zigzag64":
|
case "zigzag64":
|
||||||
p.WireType = WireVarint
|
p.WireType = WireVarint
|
||||||
p.valEnc = (*Buffer).EncodeZigzag64
|
|
||||||
p.valDec = (*Buffer).DecodeZigzag64
|
|
||||||
p.valSize = sizeZigzag64
|
|
||||||
case "bytes", "group":
|
case "bytes", "group":
|
||||||
p.WireType = WireBytes
|
p.WireType = WireBytes
|
||||||
// no numeric converter for non-numeric types
|
// no numeric converter for non-numeric types
|
||||||
default:
|
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
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -299,6 +223,7 @@ func (p *Properties) Parse(s string) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
outer:
|
||||||
for i := 2; i < len(fields); i++ {
|
for i := 2; i < len(fields); i++ {
|
||||||
f := fields[i]
|
f := fields[i]
|
||||||
switch {
|
switch {
|
||||||
@@ -326,256 +251,41 @@ func (p *Properties) Parse(s string) {
|
|||||||
if i+1 < len(fields) {
|
if i+1 < len(fields) {
|
||||||
// Commas aren't escaped, and def is always last.
|
// Commas aren't escaped, and def is always last.
|
||||||
p.Default += "," + strings.Join(fields[i+1:], ",")
|
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()
|
var protoMessageType = reflect.TypeOf((*Message)(nil)).Elem()
|
||||||
|
|
||||||
// Initialize the fields for encoding and decoding.
|
// setFieldProps initializes the field properties for submessages and maps.
|
||||||
func (p *Properties) setEncAndDec(typ reflect.Type, f *reflect.StructField, lockGetProp bool) {
|
func (p *Properties) setFieldProps(typ reflect.Type, f *reflect.StructField, lockGetProp bool) {
|
||||||
p.enc = nil
|
|
||||||
p.dec = nil
|
|
||||||
p.size = nil
|
|
||||||
|
|
||||||
switch t1 := typ; t1.Kind() {
|
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:
|
case reflect.Ptr:
|
||||||
switch t2 := t1.Elem(); t2.Kind() {
|
if t1.Elem().Kind() == reflect.Struct {
|
||||||
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:
|
|
||||||
p.stype = t1.Elem()
|
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:
|
case reflect.Slice:
|
||||||
switch t2 := t1.Elem(); t2.Kind() {
|
if t2 := t1.Elem(); t2.Kind() == reflect.Ptr && t2.Elem().Kind() == reflect.Struct {
|
||||||
default:
|
p.stype = t2.Elem()
|
||||||
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
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
case reflect.Map:
|
case reflect.Map:
|
||||||
p.enc = (*Buffer).enc_new_map
|
|
||||||
p.dec = (*Buffer).dec_new_map
|
|
||||||
p.size = size_new_map
|
|
||||||
|
|
||||||
p.mtype = t1
|
p.mtype = t1
|
||||||
p.mkeyprop = &Properties{}
|
p.MapKeyProp = &Properties{}
|
||||||
p.mkeyprop.init(reflect.PtrTo(p.mtype.Key()), "Key", f.Tag.Get("protobuf_key"), nil, lockGetProp)
|
p.MapKeyProp.init(reflect.PtrTo(p.mtype.Key()), "Key", f.Tag.Get("protobuf_key"), nil, lockGetProp)
|
||||||
p.mvalprop = &Properties{}
|
p.MapValProp = &Properties{}
|
||||||
vtype := p.mtype.Elem()
|
vtype := p.mtype.Elem()
|
||||||
if vtype.Kind() != reflect.Ptr && vtype.Kind() != reflect.Slice {
|
if vtype.Kind() != reflect.Ptr && vtype.Kind() != reflect.Slice {
|
||||||
// The value type is not a message (*T) or bytes ([]byte),
|
// The value type is not a message (*T) or bytes ([]byte),
|
||||||
// so we need encoders for the pointer to this type.
|
// so we need encoders for the pointer to this type.
|
||||||
vtype = reflect.PtrTo(vtype)
|
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 p.stype != nil {
|
||||||
if lockGetProp {
|
if lockGetProp {
|
||||||
p.sprop = GetProperties(p.stype)
|
p.sprop = GetProperties(p.stype)
|
||||||
@@ -586,32 +296,9 @@ func (p *Properties) setEncAndDec(typ reflect.Type, f *reflect.StructField, lock
|
|||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
marshalerType = reflect.TypeOf((*Marshaler)(nil)).Elem()
|
marshalerType = reflect.TypeOf((*Marshaler)(nil)).Elem()
|
||||||
unmarshalerType = reflect.TypeOf((*Unmarshaler)(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.
|
// Init populates the properties from a protocol buffer struct tag.
|
||||||
func (p *Properties) Init(typ reflect.Type, name, tag string, f *reflect.StructField) {
|
func (p *Properties) Init(typ reflect.Type, name, tag string, f *reflect.StructField) {
|
||||||
p.init(typ, name, tag, f, true)
|
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!"
|
// "bytes,49,opt,def=hello!"
|
||||||
p.Name = name
|
p.Name = name
|
||||||
p.OrigName = name
|
p.OrigName = name
|
||||||
if f != nil {
|
|
||||||
p.field = toField(f)
|
|
||||||
}
|
|
||||||
if tag == "" {
|
if tag == "" {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
p.Parse(tag)
|
p.Parse(tag)
|
||||||
p.setEncAndDec(typ, f, lockGetProp)
|
p.setFieldProps(typ, f, lockGetProp)
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@@ -649,9 +333,6 @@ func GetProperties(t reflect.Type) *StructProperties {
|
|||||||
sprop, ok := propertiesMap[t]
|
sprop, ok := propertiesMap[t]
|
||||||
propertiesMu.RUnlock()
|
propertiesMu.RUnlock()
|
||||||
if ok {
|
if ok {
|
||||||
if collectStats {
|
|
||||||
stats.Chit++
|
|
||||||
}
|
|
||||||
return sprop
|
return sprop
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -661,26 +342,26 @@ func GetProperties(t reflect.Type) *StructProperties {
|
|||||||
return sprop
|
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.
|
// getPropertiesLocked requires that propertiesMu is held.
|
||||||
func getPropertiesLocked(t reflect.Type) *StructProperties {
|
func getPropertiesLocked(t reflect.Type) *StructProperties {
|
||||||
if prop, ok := propertiesMap[t]; ok {
|
if prop, ok := propertiesMap[t]; ok {
|
||||||
if collectStats {
|
|
||||||
stats.Chit++
|
|
||||||
}
|
|
||||||
return prop
|
return prop
|
||||||
}
|
}
|
||||||
if collectStats {
|
|
||||||
stats.Cmiss++
|
|
||||||
}
|
|
||||||
|
|
||||||
prop := new(StructProperties)
|
prop := new(StructProperties)
|
||||||
// in case of recursive protos, fill this in now.
|
// in case of recursive protos, fill this in now.
|
||||||
propertiesMap[t] = prop
|
propertiesMap[t] = prop
|
||||||
|
|
||||||
// build properties
|
// build properties
|
||||||
prop.extendable = reflect.PtrTo(t).Implements(extendableProtoType) ||
|
|
||||||
reflect.PtrTo(t).Implements(extendableProtoV1Type)
|
|
||||||
prop.unrecField = invalidField
|
|
||||||
prop.Prop = make([]*Properties, t.NumField())
|
prop.Prop = make([]*Properties, t.NumField())
|
||||||
prop.order = make([]int, t.NumField())
|
prop.order = make([]int, t.NumField())
|
||||||
|
|
||||||
@@ -690,17 +371,6 @@ func getPropertiesLocked(t reflect.Type) *StructProperties {
|
|||||||
name := f.Name
|
name := f.Name
|
||||||
p.init(f.Type, name, f.Tag.Get("protobuf"), &f, false)
|
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
|
oneof := f.Tag.Get("protobuf_oneof") // special case
|
||||||
if oneof != "" {
|
if oneof != "" {
|
||||||
// Oneof fields don't use the traditional protobuf tag.
|
// Oneof fields don't use the traditional protobuf tag.
|
||||||
@@ -715,22 +385,19 @@ func getPropertiesLocked(t reflect.Type) *StructProperties {
|
|||||||
}
|
}
|
||||||
print("\n")
|
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.
|
// Re-order prop.order.
|
||||||
sort.Sort(prop)
|
sort.Sort(prop)
|
||||||
|
|
||||||
type oneofMessage interface {
|
var oots []interface{}
|
||||||
XXX_OneofFuncs() (func(Message, *Buffer) error, func(Message, int, int, *Buffer) (bool, error), func(Message) int, []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 {
|
if len(oots) > 0 {
|
||||||
var oots []interface{}
|
|
||||||
prop.oneofMarshaler, prop.oneofUnmarshaler, prop.oneofSizer, oots = om.XXX_OneofFuncs()
|
|
||||||
prop.stype = t
|
|
||||||
|
|
||||||
// Interpret oneof metadata.
|
// Interpret oneof metadata.
|
||||||
prop.OneofTypes = make(map[string]*OneofProperties)
|
prop.OneofTypes = make(map[string]*OneofProperties)
|
||||||
for _, oot := range oots {
|
for _, oot := range oots {
|
||||||
@@ -779,30 +446,6 @@ func getPropertiesLocked(t reflect.Type) *StructProperties {
|
|||||||
return prop
|
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.
|
// A global registry of enum types.
|
||||||
// The generated code will register the generated maps by calling RegisterEnum.
|
// 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.
|
// A registry of all linked message types.
|
||||||
// The string is a fully-qualified proto name ("pkg.Message").
|
// The string is a fully-qualified proto name ("pkg.Message").
|
||||||
var (
|
var (
|
||||||
protoTypes = make(map[string]reflect.Type)
|
protoTypedNils = make(map[string]Message) // a map from proto names to typed nil pointers
|
||||||
revProtoTypes = make(map[reflect.Type]string)
|
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
|
// RegisterType is called from generated code and maps from the fully qualified
|
||||||
// proto name to the type (pointer to struct) of the protocol buffer.
|
// proto name to the type (pointer to struct) of the protocol buffer.
|
||||||
func RegisterType(x Message, name string) {
|
func RegisterType(x Message, name string) {
|
||||||
if _, ok := protoTypes[name]; ok {
|
if _, ok := protoTypedNils[name]; ok {
|
||||||
// TODO: Some day, make this a panic.
|
// TODO: Some day, make this a panic.
|
||||||
log.Printf("proto: duplicate proto type registered: %s", name)
|
log.Printf("proto: duplicate proto type registered: %s", name)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
t := reflect.TypeOf(x)
|
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
|
revProtoTypes[t] = name
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -855,7 +520,14 @@ func MessageName(x Message) string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// MessageType returns the message type (pointer to struct) for a named message.
|
// 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.
|
// A registry of all linked proto files.
|
||||||
var (
|
var (
|
||||||
|
|||||||
+2776
File diff suppressed because it is too large
Load Diff
+654
@@ -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)
|
||||||
|
}
|
||||||
+2053
File diff suppressed because it is too large
Load Diff
+30
-39
@@ -50,7 +50,6 @@ import (
|
|||||||
var (
|
var (
|
||||||
newline = []byte("\n")
|
newline = []byte("\n")
|
||||||
spaces = []byte(" ")
|
spaces = []byte(" ")
|
||||||
gtNewline = []byte(">\n")
|
|
||||||
endBraceNewline = []byte("}\n")
|
endBraceNewline = []byte("}\n")
|
||||||
backslashN = []byte{'\\', 'n'}
|
backslashN = []byte{'\\', 'n'}
|
||||||
backslashR = []byte{'\\', 'r'}
|
backslashR = []byte{'\\', 'r'}
|
||||||
@@ -170,11 +169,6 @@ func writeName(w *textWriter, props *Properties) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// raw is the interface satisfied by RawMessage.
|
|
||||||
type raw interface {
|
|
||||||
Bytes() []byte
|
|
||||||
}
|
|
||||||
|
|
||||||
func requiresQuotes(u string) bool {
|
func requiresQuotes(u string) bool {
|
||||||
// When type URL contains any characters except [0-9A-Za-z./\-]*, it must be quoted.
|
// When type URL contains any characters except [0-9A-Za-z./\-]*, it must be quoted.
|
||||||
for _, ch := range u {
|
for _, ch := range u {
|
||||||
@@ -269,6 +263,10 @@ func (tm *TextMarshaler) writeStruct(w *textWriter, sv reflect.Value) error {
|
|||||||
props := sprops.Prop[i]
|
props := sprops.Prop[i]
|
||||||
name := st.Field(i).Name
|
name := st.Field(i).Name
|
||||||
|
|
||||||
|
if name == "XXX_NoUnkeyedLiteral" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
if strings.HasPrefix(name, "XXX_") {
|
if strings.HasPrefix(name, "XXX_") {
|
||||||
// There are two XXX_ fields:
|
// There are two XXX_ fields:
|
||||||
// XXX_unrecognized []byte
|
// XXX_unrecognized []byte
|
||||||
@@ -355,7 +353,7 @@ func (tm *TextMarshaler) writeStruct(w *textWriter, sv reflect.Value) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if err := tm.writeAny(w, key, props.mkeyprop); err != nil {
|
if err := tm.writeAny(w, key, props.MapKeyProp); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if err := w.WriteByte('\n'); err != nil {
|
if err := w.WriteByte('\n'); err != nil {
|
||||||
@@ -372,7 +370,7 @@ func (tm *TextMarshaler) writeStruct(w *textWriter, sv reflect.Value) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if err := tm.writeAny(w, val, props.mvalprop); err != nil {
|
if err := tm.writeAny(w, val, props.MapValProp); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if err := w.WriteByte('\n'); err != nil {
|
if err := w.WriteByte('\n'); err != nil {
|
||||||
@@ -436,12 +434,6 @@ func (tm *TextMarshaler) writeStruct(w *textWriter, sv reflect.Value) error {
|
|||||||
return err
|
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.
|
// Enums have a String method, so writeAny will work fine.
|
||||||
if err := tm.writeAny(w, fv, props); err != nil {
|
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).
|
// Extensions (the XXX_extensions field).
|
||||||
pv := sv.Addr()
|
pv := sv.Addr()
|
||||||
if _, ok := extendable(pv.Interface()); ok {
|
if _, err := extendable(pv.Interface()); err == nil {
|
||||||
if err := tm.writeExtensions(w, pv); err != nil {
|
if err := tm.writeExtensions(w, pv); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -464,26 +456,7 @@ func (tm *TextMarshaler) writeStruct(w *textWriter, sv reflect.Value) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// writeRaw writes an uninterpreted raw message.
|
var textMarshalerType = reflect.TypeOf((*encoding.TextMarshaler)(nil)).Elem()
|
||||||
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
|
|
||||||
}
|
|
||||||
|
|
||||||
// writeAny writes an arbitrary field.
|
// writeAny writes an arbitrary field.
|
||||||
func (tm *TextMarshaler) writeAny(w *textWriter, v reflect.Value, props *Properties) error {
|
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()
|
w.indent()
|
||||||
if etm, ok := v.Interface().(encoding.TextMarshaler); ok {
|
if v.CanAddr() {
|
||||||
text, err := etm.MarshalText()
|
// 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 {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if _, err = w.Write(text); err != nil {
|
if _, err = w.Write(text); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
} else if err := tm.writeStruct(w, v); err != nil {
|
} else {
|
||||||
return err
|
if v.Kind() == reflect.Ptr {
|
||||||
|
v = v.Elem()
|
||||||
|
}
|
||||||
|
if err := tm.writeStruct(w, v); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
w.unindent()
|
w.unindent()
|
||||||
if err := w.WriteByte(ket); err != nil {
|
if err := w.WriteByte(ket); err != nil {
|
||||||
|
|||||||
+34
-49
@@ -206,7 +206,6 @@ func (p *textParser) advance() {
|
|||||||
|
|
||||||
var (
|
var (
|
||||||
errBadUTF8 = errors.New("proto: bad UTF-8")
|
errBadUTF8 = errors.New("proto: bad UTF-8")
|
||||||
errBadHex = errors.New("proto: bad hexadecimal")
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func unquoteC(s string, quote rune) (string, error) {
|
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
|
return "?", s, nil // trigraph workaround
|
||||||
case '\'', '"', '\\':
|
case '\'', '"', '\\':
|
||||||
return string(r), s, nil
|
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 {
|
if len(s) < 2 {
|
||||||
return "", "", fmt.Errorf(`\%c requires 2 following digits`, r)
|
return "", "", fmt.Errorf(`\%c requires 2 following digits`, r)
|
||||||
}
|
}
|
||||||
base := 8
|
ss := string(r) + s[:2]
|
||||||
ss := s[:2]
|
|
||||||
s = s[2:]
|
s = s[2:]
|
||||||
if r == 'x' || r == 'X' {
|
i, err := strconv.ParseUint(ss, 8, 8)
|
||||||
base = 16
|
|
||||||
} else {
|
|
||||||
ss = string(r) + ss
|
|
||||||
}
|
|
||||||
i, err := strconv.ParseUint(ss, base, 8)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", "", err
|
return "", "", fmt.Errorf(`\%s contains non-octal digits`, ss)
|
||||||
}
|
}
|
||||||
return string([]byte{byte(i)}), s, nil
|
return string([]byte{byte(i)}), s, nil
|
||||||
case 'u', 'U':
|
case 'x', 'X', 'u', 'U':
|
||||||
n := 4
|
var n int
|
||||||
if r == 'U' {
|
switch r {
|
||||||
|
case 'x', 'X':
|
||||||
|
n = 2
|
||||||
|
case 'u':
|
||||||
|
n = 4
|
||||||
|
case 'U':
|
||||||
n = 8
|
n = 8
|
||||||
}
|
}
|
||||||
if len(s) < n {
|
if len(s) < n {
|
||||||
return "", "", fmt.Errorf(`\%c requires %d digits`, r, n)
|
return "", "", fmt.Errorf(`\%c requires %d following 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
|
|
||||||
}
|
}
|
||||||
|
ss := s[:n]
|
||||||
s = 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)
|
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().
|
// Back off the parser by one token. Can only be done between calls to next().
|
||||||
// It makes the next advance() a no-op.
|
// It makes the next advance() a no-op.
|
||||||
func (p *textParser) back() { p.backed = true }
|
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 {
|
if err := p.consumeToken(":"); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if err := p.readAny(key, props.mkeyprop); err != nil {
|
if err := p.readAny(key, props.MapKeyProp); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if err := p.consumeOptionalSeparator(); err != nil {
|
if err := p.consumeOptionalSeparator(); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
case "value":
|
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
|
return err
|
||||||
}
|
}
|
||||||
if err := p.readAny(val, props.mvalprop); err != nil {
|
if err := p.readAny(val, props.MapValProp); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if err := p.consumeOptionalSeparator(); err != nil {
|
if err := p.consumeOptionalSeparator(); err != nil {
|
||||||
@@ -728,6 +714,9 @@ func (p *textParser) consumeExtName() (string, error) {
|
|||||||
if tok.err != nil {
|
if tok.err != nil {
|
||||||
return "", p.errorf("unrecognized type_url or extension name: %s", tok.err)
|
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
|
return strings.Join(parts, ""), nil
|
||||||
}
|
}
|
||||||
@@ -865,7 +854,7 @@ func (p *textParser) readAny(v reflect.Value, props *Properties) error {
|
|||||||
return p.readStruct(fv, terminator)
|
return p.readStruct(fv, terminator)
|
||||||
case reflect.Uint32:
|
case reflect.Uint32:
|
||||||
if x, err := strconv.ParseUint(tok.value, 0, 32); err == nil {
|
if x, err := strconv.ParseUint(tok.value, 0, 32); err == nil {
|
||||||
fv.SetUint(x)
|
fv.SetUint(uint64(x))
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
case reflect.Uint64:
|
case reflect.Uint64:
|
||||||
@@ -883,13 +872,9 @@ func (p *textParser) readAny(v reflect.Value, props *Properties) error {
|
|||||||
// UnmarshalText returns *RequiredNotSetError.
|
// UnmarshalText returns *RequiredNotSetError.
|
||||||
func UnmarshalText(s string, pb Message) error {
|
func UnmarshalText(s string, pb Message) error {
|
||||||
if um, ok := pb.(encoding.TextUnmarshaler); ok {
|
if um, ok := pb.(encoding.TextUnmarshaler); ok {
|
||||||
err := um.UnmarshalText([]byte(s))
|
return um.UnmarshalText([]byte(s))
|
||||||
return err
|
|
||||||
}
|
}
|
||||||
pb.Reset()
|
pb.Reset()
|
||||||
v := reflect.ValueOf(pb)
|
v := reflect.ValueOf(pb)
|
||||||
if pe := newTextParser(s).readStruct(v.Elem(), ""); pe != nil {
|
return newTextParser(s).readStruct(v.Elem(), "")
|
||||||
return pe
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|||||||
+10
@@ -1,11 +1,21 @@
|
|||||||
package mountinfo
|
package mountinfo
|
||||||
|
|
||||||
|
import "io"
|
||||||
|
|
||||||
// GetMounts retrieves a list of mounts for the current running process,
|
// GetMounts retrieves a list of mounts for the current running process,
|
||||||
// with an optional filter applied (use nil for no filter).
|
// with an optional filter applied (use nil for no filter).
|
||||||
func GetMounts(f FilterFunc) ([]*Info, error) {
|
func GetMounts(f FilterFunc) ([]*Info, error) {
|
||||||
return parseMountTable(f)
|
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.
|
// Mounted determines if a specified mountpoint has been mounted.
|
||||||
// On Linux it looks at /proc/self/mountinfo.
|
// On Linux it looks at /proc/self/mountinfo.
|
||||||
func Mounted(mountpoint string) (bool, error) {
|
func Mounted(mountpoint string) (bool, error) {
|
||||||
|
|||||||
+4
-2
@@ -3,10 +3,12 @@ package mountinfo
|
|||||||
import "strings"
|
import "strings"
|
||||||
|
|
||||||
// FilterFunc is a type defining a callback function for GetMount(),
|
// 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,
|
// 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
|
// - skip: true if the entry should be skipped
|
||||||
// - stop: true if parsing should be stopped after the entry
|
// - stop: true if parsing should be stopped after the entry
|
||||||
|
|||||||
+1
-2
@@ -34,6 +34,7 @@ func parseMountTable(filter FilterFunc) ([]*Info, error) {
|
|||||||
var skip, stop bool
|
var skip, stop bool
|
||||||
mountinfo.Mountpoint = C.GoString(&entry.f_mntonname[0])
|
mountinfo.Mountpoint = C.GoString(&entry.f_mntonname[0])
|
||||||
mountinfo.Fstype = C.GoString(&entry.f_fstypename[0])
|
mountinfo.Fstype = C.GoString(&entry.f_fstypename[0])
|
||||||
|
mountinfo.Source = C.GoString(&entry.f_mntfromname[0])
|
||||||
|
|
||||||
if filter != nil {
|
if filter != nil {
|
||||||
// filter out entries we're not interested in
|
// 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)
|
out = append(out, &mountinfo)
|
||||||
if stop {
|
if stop {
|
||||||
break
|
break
|
||||||
|
|||||||
+2
-4
@@ -71,12 +71,13 @@ func parseInfoFile(r io.Reader, filter FilterFunc) ([]*Info, error) {
|
|||||||
p := &Info{}
|
p := &Info{}
|
||||||
|
|
||||||
// Fill in the fields that a filter might check
|
// Fill in the fields that a filter might check
|
||||||
// (currently only Mountpoint and Fstype)
|
|
||||||
p.Mountpoint, err = strconv.Unquote(`"` + fields[4] + `"`)
|
p.Mountpoint, err = strconv.Unquote(`"` + fields[4] + `"`)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("Parsing '%s' failed: unable to unquote mount point field: %w", fields[4], err)
|
return nil, fmt.Errorf("Parsing '%s' failed: unable to unquote mount point field: %w", fields[4], err)
|
||||||
}
|
}
|
||||||
p.Fstype = fields[sepIdx+1]
|
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
|
// Run a filter soon so we can skip parsing/adding entries
|
||||||
// the caller is not interested in
|
// 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.Optional = strings.Join(fields[6:sepIdx-1], " ")
|
||||||
}
|
}
|
||||||
|
|
||||||
p.Source = fields[sepIdx+2]
|
|
||||||
p.VfsOpts = fields[sepIdx+3]
|
|
||||||
|
|
||||||
out = append(out, p)
|
out = append(out, p)
|
||||||
if stop {
|
if stop {
|
||||||
break
|
break
|
||||||
|
|||||||
+6
-1
@@ -4,9 +4,14 @@ package mountinfo
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io"
|
||||||
"runtime"
|
"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)
|
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)
|
||||||
|
}
|
||||||
|
|||||||
+7
-1
@@ -1,6 +1,12 @@
|
|||||||
package mountinfo
|
package mountinfo
|
||||||
|
|
||||||
func parseMountTable(f FilterFunc) ([]*Info, error) {
|
import "io"
|
||||||
|
|
||||||
|
func parseMountTable(_ FilterFunc) ([]*Info, error) {
|
||||||
// Do NOT return an error!
|
// Do NOT return an error!
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func parseInfoFile(_ io.Reader, f FilterFunc) ([]*Info, error) {
|
||||||
|
return parseMountTable(f)
|
||||||
|
}
|
||||||
|
|||||||
+54
-7
@@ -89,6 +89,8 @@ type User struct {
|
|||||||
UID uint32 `json:"uid" platform:"linux,solaris"`
|
UID uint32 `json:"uid" platform:"linux,solaris"`
|
||||||
// GID is the group id.
|
// GID is the group id.
|
||||||
GID uint32 `json:"gid" platform:"linux,solaris"`
|
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 are additional group ids set for the container's process.
|
||||||
AdditionalGids []uint32 `json:"additionalGids,omitempty" platform:"linux,solaris"`
|
AdditionalGids []uint32 `json:"additionalGids,omitempty" platform:"linux,solaris"`
|
||||||
// Username is the user name.
|
// Username is the user name.
|
||||||
@@ -123,13 +125,26 @@ type Hook struct {
|
|||||||
Timeout *int `json:"timeout,omitempty"`
|
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
|
// Hooks for container setup and teardown
|
||||||
type Hooks struct {
|
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"`
|
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.
|
// 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"`
|
Poststart []Hook `json:"poststart,omitempty"`
|
||||||
// Poststop is a list of hooks to be run after the container process exits.
|
// 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"`
|
Poststop []Hook `json:"poststop,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -165,6 +180,8 @@ type Linux struct {
|
|||||||
// IntelRdt contains Intel Resource Director Technology (RDT) information for
|
// IntelRdt contains Intel Resource Director Technology (RDT) information for
|
||||||
// handling resource constraints (e.g., L3 cache, memory bandwidth) for the container
|
// handling resource constraints (e.g., L3 cache, memory bandwidth) for the container
|
||||||
IntelRdt *LinuxIntelRdt `json:"intelRdt,omitempty"`
|
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
|
// LinuxNamespace is the configuration for a Linux namespace
|
||||||
@@ -183,17 +200,17 @@ const (
|
|||||||
// PIDNamespace for isolating process IDs
|
// PIDNamespace for isolating process IDs
|
||||||
PIDNamespace LinuxNamespaceType = "pid"
|
PIDNamespace LinuxNamespaceType = "pid"
|
||||||
// NetworkNamespace for isolating network devices, stacks, ports, etc
|
// NetworkNamespace for isolating network devices, stacks, ports, etc
|
||||||
NetworkNamespace = "network"
|
NetworkNamespace LinuxNamespaceType = "network"
|
||||||
// MountNamespace for isolating mount points
|
// MountNamespace for isolating mount points
|
||||||
MountNamespace = "mount"
|
MountNamespace LinuxNamespaceType = "mount"
|
||||||
// IPCNamespace for isolating System V IPC, POSIX message queues
|
// IPCNamespace for isolating System V IPC, POSIX message queues
|
||||||
IPCNamespace = "ipc"
|
IPCNamespace LinuxNamespaceType = "ipc"
|
||||||
// UTSNamespace for isolating hostname and NIS domain name
|
// UTSNamespace for isolating hostname and NIS domain name
|
||||||
UTSNamespace = "uts"
|
UTSNamespace LinuxNamespaceType = "uts"
|
||||||
// UserNamespace for isolating user and group IDs
|
// UserNamespace for isolating user and group IDs
|
||||||
UserNamespace = "user"
|
UserNamespace LinuxNamespaceType = "user"
|
||||||
// CgroupNamespace for isolating cgroup hierarchies
|
// CgroupNamespace for isolating cgroup hierarchies
|
||||||
CgroupNamespace = "cgroup"
|
CgroupNamespace LinuxNamespaceType = "cgroup"
|
||||||
)
|
)
|
||||||
|
|
||||||
// LinuxIDMapping specifies UID/GID mappings
|
// LinuxIDMapping specifies UID/GID mappings
|
||||||
@@ -219,6 +236,7 @@ type POSIXRlimit struct {
|
|||||||
// LinuxHugepageLimit structure corresponds to limiting kernel hugepages
|
// LinuxHugepageLimit structure corresponds to limiting kernel hugepages
|
||||||
type LinuxHugepageLimit struct {
|
type LinuxHugepageLimit struct {
|
||||||
// Pagesize is the hugepage size
|
// Pagesize is the hugepage size
|
||||||
|
// Format: "<size><unit-prefix>B' (e.g. 64KB, 2MB, 1GB, etc.)
|
||||||
Pagesize string `json:"pageSize"`
|
Pagesize string `json:"pageSize"`
|
||||||
// Limit is the limit of "hugepagesize" hugetlb usage
|
// Limit is the limit of "hugepagesize" hugetlb usage
|
||||||
Limit uint64 `json:"limit"`
|
Limit uint64 `json:"limit"`
|
||||||
@@ -290,6 +308,8 @@ type LinuxMemory struct {
|
|||||||
Swappiness *uint64 `json:"swappiness,omitempty"`
|
Swappiness *uint64 `json:"swappiness,omitempty"`
|
||||||
// DisableOOMKiller disables the OOM killer for out of memory conditions
|
// DisableOOMKiller disables the OOM killer for out of memory conditions
|
||||||
DisableOOMKiller *bool `json:"disableOOMKiller,omitempty"`
|
DisableOOMKiller *bool `json:"disableOOMKiller,omitempty"`
|
||||||
|
// Enables hierarchical memory accounting
|
||||||
|
UseHierarchy *bool `json:"useHierarchy,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// LinuxCPU for Linux cgroup 'cpu' resource management
|
// LinuxCPU for Linux cgroup 'cpu' resource management
|
||||||
@@ -386,6 +406,28 @@ type LinuxDeviceCgroup struct {
|
|||||||
Access string `json:"access,omitempty"`
|
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.
|
// Solaris contains platform-specific configuration for Solaris application containers.
|
||||||
type Solaris struct {
|
type Solaris struct {
|
||||||
// SMF FMRI which should go "online" before we start the container process.
|
// SMF FMRI which should go "online" before we start the container process.
|
||||||
@@ -555,12 +597,16 @@ type VMImage struct {
|
|||||||
type LinuxSeccomp struct {
|
type LinuxSeccomp struct {
|
||||||
DefaultAction LinuxSeccompAction `json:"defaultAction"`
|
DefaultAction LinuxSeccompAction `json:"defaultAction"`
|
||||||
Architectures []Arch `json:"architectures,omitempty"`
|
Architectures []Arch `json:"architectures,omitempty"`
|
||||||
|
Flags []LinuxSeccompFlag `json:"flags,omitempty"`
|
||||||
Syscalls []LinuxSyscall `json:"syscalls,omitempty"`
|
Syscalls []LinuxSyscall `json:"syscalls,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Arch used for additional architectures
|
// Arch used for additional architectures
|
||||||
type Arch string
|
type Arch string
|
||||||
|
|
||||||
|
// LinuxSeccompFlag is a flag to pass to seccomp(2).
|
||||||
|
type LinuxSeccompFlag string
|
||||||
|
|
||||||
// Additional architectures permitted to be used for system calls
|
// Additional architectures permitted to be used for system calls
|
||||||
// By default only the native architecture of the kernel is permitted
|
// By default only the native architecture of the kernel is permitted
|
||||||
const (
|
const (
|
||||||
@@ -594,6 +640,7 @@ const (
|
|||||||
ActErrno LinuxSeccompAction = "SCMP_ACT_ERRNO"
|
ActErrno LinuxSeccompAction = "SCMP_ACT_ERRNO"
|
||||||
ActTrace LinuxSeccompAction = "SCMP_ACT_TRACE"
|
ActTrace LinuxSeccompAction = "SCMP_ACT_TRACE"
|
||||||
ActAllow LinuxSeccompAction = "SCMP_ACT_ALLOW"
|
ActAllow LinuxSeccompAction = "SCMP_ACT_ALLOW"
|
||||||
|
ActLog LinuxSeccompAction = "SCMP_ACT_LOG"
|
||||||
)
|
)
|
||||||
|
|
||||||
// LinuxSeccompOperator used to match syscall arguments in Seccomp
|
// LinuxSeccompOperator used to match syscall arguments in Seccomp
|
||||||
|
|||||||
+2
-2
@@ -8,10 +8,10 @@ const (
|
|||||||
// VersionMinor is for functionality in a backwards-compatible manner
|
// VersionMinor is for functionality in a backwards-compatible manner
|
||||||
VersionMinor = 0
|
VersionMinor = 0
|
||||||
// VersionPatch is for backwards-compatible bug fixes
|
// VersionPatch is for backwards-compatible bug fixes
|
||||||
VersionPatch = 1
|
VersionPatch = 2
|
||||||
|
|
||||||
// VersionDev indicates development branch. Releases will be empty string.
|
// VersionDev indicates development branch. Releases will be empty string.
|
||||||
VersionDev = "-dev"
|
VersionDev = ""
|
||||||
)
|
)
|
||||||
|
|
||||||
// Version is the specification version that the package types support.
|
// Version is the specification version that the package types support.
|
||||||
|
|||||||
+51
-83
@@ -1,109 +1,77 @@
|
|||||||
// +build !selinux !linux
|
|
||||||
|
|
||||||
package label
|
package label
|
||||||
|
|
||||||
// InitLabels returns the process label and file labels to be used within
|
import (
|
||||||
// the container. A list of options can be passed into this function to alter
|
"github.com/opencontainers/selinux/go-selinux"
|
||||||
// the labels.
|
)
|
||||||
func InitLabels(options []string) (string, string, error) {
|
|
||||||
return "", "", nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func ROMountLabel() string {
|
// Deprecated: use selinux.ROFileLabel
|
||||||
return ""
|
var ROMountLabel = selinux.ROFileLabel
|
||||||
}
|
|
||||||
|
|
||||||
func GenLabels(options string) (string, string, error) {
|
// SetProcessLabel takes a process label and tells the kernel to assign the
|
||||||
return "", "", nil
|
// 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 {
|
// ProcessLabel returns the process label that the kernel will assign
|
||||||
return src
|
// 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 {
|
// SetSocketLabel takes a process label and tells the kernel to assign the
|
||||||
return nil
|
// label to the next socket that gets created
|
||||||
}
|
// Deprecated: use selinux.SetSocketLabel
|
||||||
|
var SetSocketLabel = selinux.SetSocketLabel
|
||||||
|
|
||||||
func ProcessLabel() (string, error) {
|
// SocketLabel retrieves the current default socket label setting
|
||||||
return "", nil
|
// Deprecated: use selinux.SocketLabel
|
||||||
}
|
var SocketLabel = selinux.SocketLabel
|
||||||
|
|
||||||
func SetSocketLabel(processLabel string) error {
|
// SetKeyLabel takes a process label and tells the kernel to assign the
|
||||||
return nil
|
// label to the next kernel keyring that gets created
|
||||||
}
|
// Deprecated: use selinux.SetKeyLabel
|
||||||
|
var SetKeyLabel = selinux.SetKeyLabel
|
||||||
|
|
||||||
func SocketLabel() (string, error) {
|
// KeyLabel retrieves the current default kernel keyring label setting
|
||||||
return "", nil
|
// Deprecated: use selinux.KeyLabel
|
||||||
}
|
var KeyLabel = selinux.KeyLabel
|
||||||
|
|
||||||
func SetKeyLabel(processLabel string) error {
|
// FileLabel returns the label for specified path
|
||||||
return nil
|
// Deprecated: use selinux.FileLabel
|
||||||
}
|
var FileLabel = selinux.FileLabel
|
||||||
|
|
||||||
func KeyLabel() (string, error) {
|
// PidLabel will return the label of the process running with the specified pid
|
||||||
return "", nil
|
// Deprecated: use selinux.PidLabel
|
||||||
}
|
var PidLabel = selinux.PidLabel
|
||||||
|
|
||||||
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
|
|
||||||
}
|
|
||||||
|
|
||||||
|
// Init initialises the labeling system
|
||||||
func Init() {
|
func Init() {
|
||||||
|
selinux.GetEnabled()
|
||||||
}
|
}
|
||||||
|
|
||||||
// ClearLabels clears all reserved labels
|
// ClearLabels will clear all reserved labels
|
||||||
func ClearLabels() {
|
// Deprecated: use selinux.ClearLabels
|
||||||
return
|
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 {
|
func ReserveLabel(label string) error {
|
||||||
|
selinux.ReserveLabel(label)
|
||||||
return nil
|
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 {
|
func ReleaseLabel(label string) error {
|
||||||
|
selinux.ReleaseLabel(label)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// DupSecOpt takes a process label and returns security options that
|
// DupSecOpt takes a process label and returns security options that
|
||||||
// can be used to set duplicate labels on future container processes
|
// can be used to set duplicate labels on future container processes
|
||||||
func DupSecOpt(src string) ([]string, error) {
|
// Deprecated: use selinux.DupSecOpt
|
||||||
return nil, nil
|
var DupSecOpt = selinux.DupSecOpt
|
||||||
}
|
|
||||||
|
|
||||||
// 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
|
|
||||||
}
|
|
||||||
|
|||||||
+19
-103
@@ -9,6 +9,7 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/opencontainers/selinux/go-selinux"
|
"github.com/opencontainers/selinux/go-selinux"
|
||||||
|
"github.com/pkg/errors"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Valid Label Options
|
// Valid Label Options
|
||||||
@@ -21,7 +22,7 @@ var validOptions = map[string]bool{
|
|||||||
"level": true,
|
"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
|
// 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 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 != "" {
|
if processLabel != "" {
|
||||||
defer func() {
|
defer func() {
|
||||||
if Err != nil {
|
if Err != nil {
|
||||||
ReleaseLabel(mountLabel)
|
selinux.ReleaseLabel(mountLabel)
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
pcon, err := selinux.NewContext(processLabel)
|
pcon, err := selinux.NewContext(processLabel)
|
||||||
@@ -52,11 +53,11 @@ func InitLabels(options []string) (plabel string, mlabel string, Err error) {
|
|||||||
return "", mountLabel, nil
|
return "", mountLabel, nil
|
||||||
}
|
}
|
||||||
if i := strings.Index(opt, ":"); i == -1 {
|
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)
|
con := strings.SplitN(opt, ":", 2)
|
||||||
if !validOptions[con[0]] {
|
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" {
|
if con[0] == "filetype" {
|
||||||
@@ -67,19 +68,16 @@ func InitLabels(options []string) (plabel string, mlabel string, Err error) {
|
|||||||
mcon[con[0]] = con[1]
|
mcon[con[0]] = con[1]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_ = ReleaseLabel(processLabel)
|
selinux.ReleaseLabel(processLabel)
|
||||||
processLabel = pcon.Get()
|
processLabel = pcon.Get()
|
||||||
mountLabel = mcon.Get()
|
mountLabel = mcon.Get()
|
||||||
_ = ReserveLabel(processLabel)
|
selinux.ReserveLabel(processLabel)
|
||||||
}
|
}
|
||||||
return processLabel, mountLabel, nil
|
return processLabel, mountLabel, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func ROMountLabel() string {
|
// Deprecated: The GenLabels function is only to be used during the transition
|
||||||
return selinux.ROFileLabel()
|
// to the official API. Use InitLabels(strings.Fields(options)) instead.
|
||||||
}
|
|
||||||
|
|
||||||
// DEPRECATED: The GenLabels function is only to be used during the transition to the official API.
|
|
||||||
func GenLabels(options string) (string, string, error) {
|
func GenLabels(options string) (string, string, error) {
|
||||||
return InitLabels(strings.Fields(options))
|
return InitLabels(strings.Fields(options))
|
||||||
}
|
}
|
||||||
@@ -102,71 +100,27 @@ func FormatMountLabel(src, mountLabel string) string {
|
|||||||
return src
|
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
|
// SetFileLabel modifies the "path" label to the specified file label
|
||||||
func SetFileLabel(path string, fileLabel string) error {
|
func SetFileLabel(path string, fileLabel string) error {
|
||||||
if selinux.GetEnabled() && fileLabel != "" {
|
if !selinux.GetEnabled() || fileLabel == "" {
|
||||||
return selinux.SetFileLabel(path, fileLabel)
|
return nil
|
||||||
}
|
}
|
||||||
return nil
|
return selinux.SetFileLabel(path, fileLabel)
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetFileCreateLabel tells the kernel the label for all files to be created
|
// SetFileCreateLabel tells the kernel the label for all files to be created
|
||||||
func SetFileCreateLabel(fileLabel string) error {
|
func SetFileCreateLabel(fileLabel string) error {
|
||||||
if selinux.GetEnabled() {
|
if !selinux.GetEnabled() {
|
||||||
return selinux.SetFSCreateLabel(fileLabel)
|
return nil
|
||||||
}
|
}
|
||||||
return nil
|
return selinux.SetFSCreateLabel(fileLabel)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Relabel changes the label of path to the filelabel string.
|
// Relabel changes the label of path to the filelabel string.
|
||||||
// It changes the MCS label to s0 if shared is true.
|
// It changes the MCS label to s0 if shared is true.
|
||||||
// This will allow all containers to share the content.
|
// This will allow all containers to share the content.
|
||||||
func Relabel(path string, fileLabel string, shared bool) error {
|
func Relabel(path string, fileLabel string, shared bool) error {
|
||||||
if !selinux.GetEnabled() {
|
if !selinux.GetEnabled() || fileLabel == "" {
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if fileLabel == "" {
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -211,7 +165,7 @@ func Relabel(path string, fileLabel string, shared bool) error {
|
|||||||
path = strings.TrimSuffix(path, "/")
|
path = strings.TrimSuffix(path, "/")
|
||||||
}
|
}
|
||||||
if exclude_paths[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 {
|
if shared {
|
||||||
@@ -229,48 +183,10 @@ func Relabel(path string, fileLabel string, shared bool) error {
|
|||||||
return nil
|
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
|
// DisableSecOpt returns a security opt that can disable labeling
|
||||||
// support for future container processes
|
// support for future container processes
|
||||||
func DisableSecOpt() []string {
|
// Deprecated: use selinux.DisableSecOpt
|
||||||
return selinux.DisableSecOpt()
|
var DisableSecOpt = selinux.DisableSecOpt
|
||||||
}
|
|
||||||
|
|
||||||
// Validate checks that the label does not include unexpected options
|
// Validate checks that the label does not include unexpected options
|
||||||
func Validate(label string) error {
|
func Validate(label string) error {
|
||||||
|
|||||||
+54
@@ -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
|
||||||
|
}
|
||||||
+53
-25
@@ -17,8 +17,8 @@ import (
|
|||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"syscall"
|
|
||||||
|
|
||||||
|
"github.com/opencontainers/selinux/pkg/pwalk"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
"golang.org/x/sys/unix"
|
"golang.org/x/sys/unix"
|
||||||
)
|
)
|
||||||
@@ -37,7 +37,6 @@ const (
|
|||||||
selinuxTypeTag = "SELINUXTYPE"
|
selinuxTypeTag = "SELINUXTYPE"
|
||||||
selinuxTag = "SELINUX"
|
selinuxTag = "SELINUX"
|
||||||
xattrNameSelinux = "security.selinux"
|
xattrNameSelinux = "security.selinux"
|
||||||
stRdOnly = 0x01
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type selinuxState struct {
|
type selinuxState struct {
|
||||||
@@ -103,13 +102,13 @@ func SetDisabled() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func verifySELinuxfsMount(mnt string) bool {
|
func verifySELinuxfsMount(mnt string) bool {
|
||||||
var buf syscall.Statfs_t
|
var buf unix.Statfs_t
|
||||||
for {
|
for {
|
||||||
err := syscall.Statfs(mnt, &buf)
|
err := unix.Statfs(mnt, &buf)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
if err == syscall.EAGAIN {
|
if err == unix.EAGAIN {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
@@ -118,7 +117,7 @@ func verifySELinuxfsMount(mnt string) bool {
|
|||||||
if uint32(buf.Type) != uint32(unix.SELINUX_MAGIC) {
|
if uint32(buf.Type) != uint32(unix.SELINUX_MAGIC) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
if (buf.Flags & stRdOnly) != 0 {
|
if (buf.Flags & unix.ST_RDONLY) != 0 {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -251,10 +250,10 @@ func isProcHandle(fh *os.File) error {
|
|||||||
var buf unix.Statfs_t
|
var buf unix.Statfs_t
|
||||||
err := unix.Fstatfs(int(fh.Fd()), &buf)
|
err := unix.Fstatfs(int(fh.Fd()), &buf)
|
||||||
if err != nil {
|
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 {
|
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
|
return nil
|
||||||
@@ -282,12 +281,29 @@ func readCon(fpath string) (string, error) {
|
|||||||
return strings.Trim(retval, "\x00"), nil
|
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.
|
// SetFileLabel sets the SELinux label for this path or returns an error.
|
||||||
func SetFileLabel(fpath string, label string) error {
|
func SetFileLabel(fpath string, label string) error {
|
||||||
if fpath == "" {
|
if fpath == "" {
|
||||||
return ErrEmptyPath
|
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 errors.Wrapf(err, "failed to set file label on %s", fpath)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
@@ -390,7 +406,7 @@ func attrPath(attr string) string {
|
|||||||
return path.Join(threadSelfPrefix, attr)
|
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) {
|
func readAttr(attr string) (string, error) {
|
||||||
@@ -410,6 +426,18 @@ func CanonicalizeContext(val string) (string, error) {
|
|||||||
return readWriteCon(filepath.Join(getSelinuxMountPoint(), "context"), val)
|
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) {
|
func readWriteCon(fpath string, val string) (string, error) {
|
||||||
if fpath == "" {
|
if fpath == "" {
|
||||||
return "", ErrEmptyPath
|
return "", ErrEmptyPath
|
||||||
@@ -461,17 +489,17 @@ func SocketLabel() (string, error) {
|
|||||||
|
|
||||||
// PeerLabel retrieves the label of the client on the other side of a socket
|
// PeerLabel retrieves the label of the client on the other side of a socket
|
||||||
func PeerLabel(fd uintptr) (string, error) {
|
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
|
// SetKeyLabel takes a process label and tells the kernel to assign the
|
||||||
// label to the next kernel keyring that gets created
|
// label to the next kernel keyring that gets created
|
||||||
func SetKeyLabel(label string) error {
|
func SetKeyLabel(label string) error {
|
||||||
err := writeCon("/proc/self/attr/keycreate", label)
|
err := writeCon("/proc/self/attr/keycreate", label)
|
||||||
if os.IsNotExist(err) {
|
if os.IsNotExist(errors.Cause(err)) {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
if label == "" && os.IsPermission(err) {
|
if label == "" && os.IsPermission(errors.Cause(err)) {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
return err
|
return err
|
||||||
@@ -772,14 +800,14 @@ func badPrefix(fpath string) error {
|
|||||||
badPrefixes := []string{"/usr"}
|
badPrefixes := []string{"/usr"}
|
||||||
for _, prefix := range badPrefixes {
|
for _, prefix := range badPrefixes {
|
||||||
if strings.HasPrefix(fpath, prefix) {
|
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
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Chcon changes the `fpath` file object to the SELinux label `label`.
|
// Chcon changes the fpath file object to the SELinux label label.
|
||||||
// If `fpath` is a directory and `recurse`` is true, Chcon will walk the
|
// If fpath is a directory and recurse is true, Chcon will walk the
|
||||||
// directory tree setting the label.
|
// directory tree setting the label.
|
||||||
func Chcon(fpath string, label string, recurse bool) error {
|
func Chcon(fpath string, label string, recurse bool) error {
|
||||||
if fpath == "" {
|
if fpath == "" {
|
||||||
@@ -791,19 +819,19 @@ func Chcon(fpath string, label string, recurse bool) error {
|
|||||||
if err := badPrefix(fpath); err != nil {
|
if err := badPrefix(fpath); err != nil {
|
||||||
return err
|
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)
|
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 nil
|
||||||
}
|
}
|
||||||
return e
|
return e
|
||||||
}
|
})
|
||||||
|
|
||||||
if recurse {
|
|
||||||
return filepath.Walk(fpath, callback)
|
|
||||||
}
|
|
||||||
|
|
||||||
return SetFileLabel(fpath, label)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// DupSecOpt takes an SELinux process label and returns security options that
|
// DupSecOpt takes an SELinux process label and returns security options that
|
||||||
|
|||||||
+13
-1
@@ -1,4 +1,4 @@
|
|||||||
// +build !selinux
|
// +build !selinux !linux
|
||||||
|
|
||||||
package selinux
|
package selinux
|
||||||
|
|
||||||
@@ -35,6 +35,11 @@ func GetEnabled() bool {
|
|||||||
return false
|
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.
|
// SetFileLabel sets the SELinux label for this path or returns an error.
|
||||||
func SetFileLabel(fpath string, label string) error {
|
func SetFileLabel(fpath string, label string) error {
|
||||||
return nil
|
return nil
|
||||||
@@ -88,6 +93,13 @@ func CanonicalizeContext(val string) (string, error) {
|
|||||||
return "", nil
|
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
|
SetExecLabel sets the SELinux label that the kernel will use for any programs
|
||||||
that are executed by the current process thread, or an error.
|
that are executed by the current process thread, or an error.
|
||||||
|
|||||||
+2
-6
@@ -12,8 +12,8 @@ func lgetxattr(path string, attr string) ([]byte, error) {
|
|||||||
// Start with a 128 length byte array
|
// Start with a 128 length byte array
|
||||||
dest := make([]byte, 128)
|
dest := make([]byte, 128)
|
||||||
sz, errno := unix.Lgetxattr(path, attr, dest)
|
sz, errno := unix.Lgetxattr(path, attr, dest)
|
||||||
if errno == unix.ERANGE {
|
for errno == unix.ERANGE {
|
||||||
// Buffer too small, get the real size first
|
// Buffer too small, use zero-sized buffer to get the actual size
|
||||||
sz, errno = unix.Lgetxattr(path, attr, []byte{})
|
sz, errno = unix.Lgetxattr(path, attr, []byte{})
|
||||||
if errno != nil {
|
if errno != nil {
|
||||||
return nil, errno
|
return nil, errno
|
||||||
@@ -28,7 +28,3 @@ func lgetxattr(path string, attr string) ([]byte, error) {
|
|||||||
|
|
||||||
return dest[:sz], nil
|
return dest[:sz], nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func lsetxattr(path string, attr string, data []byte, flags int) error {
|
|
||||||
return unix.Lsetxattr(path, attr, data, flags)
|
|
||||||
}
|
|
||||||
|
|||||||
+42
@@ -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.
|
||||||
+99
@@ -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
|
||||||
|
}
|
||||||
+3
-8
@@ -1,15 +1,10 @@
|
|||||||
language: go
|
language: go
|
||||||
go_import_path: github.com/pkg/errors
|
go_import_path: github.com/pkg/errors
|
||||||
go:
|
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.11.x
|
||||||
|
- 1.12.x
|
||||||
|
- 1.13.x
|
||||||
- tip
|
- tip
|
||||||
|
|
||||||
script:
|
script:
|
||||||
- go test -v ./...
|
- make check
|
||||||
|
|||||||
+44
@@ -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)"
|
||||||
+9
-2
@@ -41,11 +41,18 @@ default:
|
|||||||
|
|
||||||
[Read the package documentation for more information](https://godoc.org/github.com/pkg/errors).
|
[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
|
## 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
|
## License
|
||||||
|
|
||||||
|
|||||||
+7
-1
@@ -82,7 +82,7 @@
|
|||||||
//
|
//
|
||||||
// if err, ok := err.(stackTracer); ok {
|
// if err, ok := err.(stackTracer); ok {
|
||||||
// for _, f := range err.StackTrace() {
|
// 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 }
|
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) {
|
func (w *withStack) Format(s fmt.State, verb rune) {
|
||||||
switch verb {
|
switch verb {
|
||||||
case 'v':
|
case 'v':
|
||||||
@@ -241,6 +244,9 @@ type withMessage struct {
|
|||||||
func (w *withMessage) Error() string { return w.msg + ": " + w.cause.Error() }
|
func (w *withMessage) Error() string { return w.msg + ": " + w.cause.Error() }
|
||||||
func (w *withMessage) Cause() error { return w.cause }
|
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) {
|
func (w *withMessage) Format(s fmt.State, verb rune) {
|
||||||
switch verb {
|
switch verb {
|
||||||
case 'v':
|
case 'v':
|
||||||
|
|||||||
+38
@@ -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)
|
||||||
|
}
|
||||||
+44
-14
@@ -5,10 +5,13 @@ import (
|
|||||||
"io"
|
"io"
|
||||||
"path"
|
"path"
|
||||||
"runtime"
|
"runtime"
|
||||||
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Frame represents a program counter inside a stack frame.
|
// 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
|
type Frame uintptr
|
||||||
|
|
||||||
// pc returns the program counter for this frame;
|
// pc returns the program counter for this frame;
|
||||||
@@ -37,6 +40,15 @@ func (f Frame) line() int {
|
|||||||
return line
|
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.
|
// Format formats the frame according to the fmt.Formatter interface.
|
||||||
//
|
//
|
||||||
// %s source file
|
// %s source file
|
||||||
@@ -54,22 +66,16 @@ func (f Frame) Format(s fmt.State, verb rune) {
|
|||||||
case 's':
|
case 's':
|
||||||
switch {
|
switch {
|
||||||
case s.Flag('+'):
|
case s.Flag('+'):
|
||||||
pc := f.pc()
|
io.WriteString(s, f.name())
|
||||||
fn := runtime.FuncForPC(pc)
|
io.WriteString(s, "\n\t")
|
||||||
if fn == nil {
|
io.WriteString(s, f.file())
|
||||||
io.WriteString(s, "unknown")
|
|
||||||
} else {
|
|
||||||
file, _ := fn.FileLine(pc)
|
|
||||||
fmt.Fprintf(s, "%s\n\t%s", fn.Name(), file)
|
|
||||||
}
|
|
||||||
default:
|
default:
|
||||||
io.WriteString(s, path.Base(f.file()))
|
io.WriteString(s, path.Base(f.file()))
|
||||||
}
|
}
|
||||||
case 'd':
|
case 'd':
|
||||||
fmt.Fprintf(s, "%d", f.line())
|
io.WriteString(s, strconv.Itoa(f.line()))
|
||||||
case 'n':
|
case 'n':
|
||||||
name := runtime.FuncForPC(f.pc()).Name()
|
io.WriteString(s, funcname(f.name()))
|
||||||
io.WriteString(s, funcname(name))
|
|
||||||
case 'v':
|
case 'v':
|
||||||
f.Format(s, 's')
|
f.Format(s, 's')
|
||||||
io.WriteString(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).
|
// StackTrace is stack of Frames from innermost (newest) to outermost (oldest).
|
||||||
type StackTrace []Frame
|
type StackTrace []Frame
|
||||||
|
|
||||||
@@ -94,18 +110,32 @@ func (st StackTrace) Format(s fmt.State, verb rune) {
|
|||||||
switch {
|
switch {
|
||||||
case s.Flag('+'):
|
case s.Flag('+'):
|
||||||
for _, f := range st {
|
for _, f := range st {
|
||||||
fmt.Fprintf(s, "\n%+v", f)
|
io.WriteString(s, "\n")
|
||||||
|
f.Format(s, verb)
|
||||||
}
|
}
|
||||||
case s.Flag('#'):
|
case s.Flag('#'):
|
||||||
fmt.Fprintf(s, "%#v", []Frame(st))
|
fmt.Fprintf(s, "%#v", []Frame(st))
|
||||||
default:
|
default:
|
||||||
fmt.Fprintf(s, "%v", []Frame(st))
|
st.formatSlice(s, verb)
|
||||||
}
|
}
|
||||||
case 's':
|
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.
|
// stack represents a stack of program counters.
|
||||||
type stack []uintptr
|
type stack []uintptr
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,8 @@
|
|||||||
|
*.out
|
||||||
|
*.swp
|
||||||
|
*.8
|
||||||
|
*.6
|
||||||
|
_obj
|
||||||
|
_test*
|
||||||
|
markdown
|
||||||
|
tags
|
||||||
+17
@@ -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 ./...
|
||||||
+29
@@ -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.
|
||||||
+291
@@ -0,0 +1,291 @@
|
|||||||
|
Blackfriday [](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"
|
||||||
+1590
File diff suppressed because it is too large
Load Diff
+18
@@ -0,0 +1,18 @@
|
|||||||
|
// Package blackfriday is a markdown processor.
|
||||||
|
//
|
||||||
|
// It translates plain text with simple formatting rules into an AST, which can
|
||||||
|
// then be further processed to HTML (provided by Blackfriday itself) or other
|
||||||
|
// formats (provided by the community).
|
||||||
|
//
|
||||||
|
// The simplest way to invoke Blackfriday is to call the Run function. It will
|
||||||
|
// take a text input and produce a text output in HTML (or other format).
|
||||||
|
//
|
||||||
|
// A slightly more sophisticated way to use Blackfriday is to create a Markdown
|
||||||
|
// processor and to call Parse, which returns a syntax tree for the input
|
||||||
|
// document. You can leverage Blackfriday's parsing for content extraction from
|
||||||
|
// markdown documents. You can assign a custom renderer and set various options
|
||||||
|
// to the Markdown processor.
|
||||||
|
//
|
||||||
|
// If you're interested in calling Blackfriday from command line, see
|
||||||
|
// https://github.com/russross/blackfriday-tool.
|
||||||
|
package blackfriday
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user