deps: update runc to v1.0.0-rc93
includes updates for breaking changes in runc v1.0.0-rc93
This commit is contained in:
parent
d8d3faef39
commit
5a182e1d03
|
@ -758,7 +758,7 @@ func newLibcontainerConfig(command *ExecCommand) (*lconfigs.Config, error) {
|
|||
Version: "1.0.0",
|
||||
}
|
||||
for _, device := range specconv.AllowedDevices {
|
||||
cfg.Cgroups.Resources.Devices = append(cfg.Cgroups.Resources.Devices, &device.DeviceRule)
|
||||
cfg.Cgroups.Resources.Devices = append(cfg.Cgroups.Resources.Devices, &device.Rule)
|
||||
}
|
||||
|
||||
if err := configureCapabilities(cfg, command); err != nil {
|
||||
|
|
|
@ -21,6 +21,7 @@ import (
|
|||
tu "github.com/hashicorp/nomad/testutil"
|
||||
"github.com/opencontainers/runc/libcontainer/cgroups"
|
||||
lconfigs "github.com/opencontainers/runc/libcontainer/configs"
|
||||
"github.com/opencontainers/runc/libcontainer/devices"
|
||||
"github.com/stretchr/testify/require"
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
@ -597,8 +598,8 @@ func TestExecutor_cmdDevices(t *testing.T) {
|
|||
},
|
||||
}
|
||||
|
||||
expected := &lconfigs.Device{
|
||||
DeviceRule: lconfigs.DeviceRule{
|
||||
expected := &devices.Device{
|
||||
Rule: devices.Rule{
|
||||
Type: 99,
|
||||
Major: 1,
|
||||
Minor: 3,
|
||||
|
|
|
@ -74,7 +74,7 @@ func (e *UniversalExecutor) configureResourceContainer(pid int) error {
|
|||
},
|
||||
}
|
||||
for _, device := range specconv.AllowedDevices {
|
||||
cfg.Cgroups.Resources.Devices = append(cfg.Cgroups.Resources.Devices, &device.DeviceRule)
|
||||
cfg.Cgroups.Resources.Devices = append(cfg.Cgroups.Resources.Devices, &device.Rule)
|
||||
}
|
||||
|
||||
err := configureBasicCgroups(cfg)
|
||||
|
|
11
go.mod
11
go.mod
|
@ -45,9 +45,9 @@ require (
|
|||
github.com/fatih/color v1.9.0
|
||||
github.com/fsnotify/fsnotify v1.4.9 // indirect
|
||||
github.com/fsouza/go-dockerclient v1.6.5
|
||||
github.com/golang/protobuf v1.4.2
|
||||
github.com/golang/protobuf v1.4.3
|
||||
github.com/golang/snappy v0.0.1
|
||||
github.com/google/go-cmp v0.4.0
|
||||
github.com/google/go-cmp v0.5.2
|
||||
github.com/gorilla/websocket v1.4.2
|
||||
github.com/grpc-ecosystem/go-grpc-middleware v1.2.1-0.20200228141219-3ce3d519df39
|
||||
github.com/hashicorp/consul v1.7.8
|
||||
|
@ -103,7 +103,7 @@ require (
|
|||
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e // indirect
|
||||
github.com/oklog/run v1.0.1-0.20180308005104-6934b124db28 // indirect
|
||||
github.com/onsi/gomega v1.9.0 // indirect
|
||||
github.com/opencontainers/runc v1.0.0-rc92
|
||||
github.com/opencontainers/runc v1.0.0-rc93
|
||||
github.com/pkg/errors v0.9.1
|
||||
github.com/posener/complete v1.2.3
|
||||
github.com/prometheus/client_golang v1.4.0
|
||||
|
@ -117,7 +117,7 @@ require (
|
|||
github.com/skratchdot/open-golang v0.0.0-20160302144031-75fb7ed4208c
|
||||
github.com/stretchr/objx v0.2.0 // indirect
|
||||
github.com/stretchr/testify v1.6.1
|
||||
github.com/syndtr/gocapability v0.0.0-20180916011248-d98352740cb2
|
||||
github.com/syndtr/gocapability v0.0.0-20200815063812-42c35b437635
|
||||
github.com/tklauser/go-sysconf v0.3.5 // indirect
|
||||
github.com/zclconf/go-cty v1.4.1
|
||||
github.com/zclconf/go-cty-yaml v1.0.2
|
||||
|
@ -126,10 +126,9 @@ require (
|
|||
golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136 // indirect
|
||||
golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f // indirect
|
||||
golang.org/x/mod v0.3.0 // indirect
|
||||
golang.org/x/net v0.0.0-20200602114024-627f9648deb9
|
||||
golang.org/x/net v0.0.0-20201224014010-6772e930b67b
|
||||
golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a
|
||||
golang.org/x/sys v0.0.0-20210316164454-77fc1eacc6aa
|
||||
golang.org/x/text v0.3.3-0.20200306154105-06d492aade88 // indirect
|
||||
golang.org/x/time v0.0.0-20191024005414-555d28b269f0
|
||||
golang.org/x/tools v0.0.0-20200522201501-cb1345f3a375 // indirect
|
||||
google.golang.org/api v0.13.0 // indirect
|
||||
|
|
60
go.sum
60
go.sum
|
@ -133,8 +133,8 @@ github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XL
|
|||
github.com/checkpoint-restore/go-criu/v4 v4.1.0 h1:WW2B2uxx9KWF6bGlHqhm8Okiafwwx7Y2kcpn8lCpjgo=
|
||||
github.com/checkpoint-restore/go-criu/v4 v4.1.0/go.mod h1:xUQBLp4RLc5zJtWY++yjOoMoB5lihDt7fai+75m+rGw=
|
||||
github.com/cheggaaa/pb v1.0.27/go.mod h1:pQciLPpbU0oxA0h+VJYYLxO+XeDQb5pZijXscXHm81s=
|
||||
github.com/cilium/ebpf v0.0.0-20200702112145-1c8d4c9ef775 h1:cHzBGGVew0ezFsq2grfy2RsB8hO/eNyBgOLHBCqfR1U=
|
||||
github.com/cilium/ebpf v0.0.0-20200702112145-1c8d4c9ef775/go.mod h1:7cR51M8ViRLIdUjrmSXlK9pkrsDlLHbO8jiB8X8JnOc=
|
||||
github.com/cilium/ebpf v0.2.0 h1:Fv93L3KKckEcEHR3oApXVzyBTDA8WAm6VXhPE00N3f8=
|
||||
github.com/cilium/ebpf v0.2.0/go.mod h1:To2CFviqOWL/M0gIMsvSMlqe7em/l1ALkX1PyjrX2Qs=
|
||||
github.com/circonus-labs/circonus-gometrics v2.3.1+incompatible h1:C29Ae4G5GtYyYMm1aztcyj/J5ckgJm2zwdDajFbx1NY=
|
||||
github.com/circonus-labs/circonus-gometrics v2.3.1+incompatible/go.mod h1:nmEj6Dob7S7YxXgwXpfOuvO54S+tGdZdw9fuRZt25Ag=
|
||||
github.com/circonus-labs/circonusllhist v0.1.3 h1:TJH+oke8D16535+jHExHj4nQvzlZrj7ug5D7I/orNUA=
|
||||
|
@ -145,8 +145,8 @@ github.com/container-storage-interface/spec v1.4.0 h1:ozAshSKxpJnYUfmkpZCTYyF/4M
|
|||
github.com/container-storage-interface/spec v1.4.0/go.mod h1:6URME8mwIBbpVyZV93Ce5St17xBiQJQY67NDsuohiy4=
|
||||
github.com/containerd/cgroups v0.0.0-20190919134610-bf292b21730f/go.mod h1:OApqhQ4XNSNC13gXIwDjhOQxjWa/NxkwZXJ1EvqT0ko=
|
||||
github.com/containerd/console v0.0.0-20180822173158-c12b1e7919c1/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/containerd/console v1.0.1 h1:u7SFAJyRqWcG6ogaMAx3KjSTy1e3hT9QxqX7Jco7dRc=
|
||||
github.com/containerd/console v1.0.1/go.mod h1:XUsP6YE/mKtz6bxc+I8UiKKTP04qjQL4qcS3XoQ5xkw=
|
||||
github.com/containerd/containerd v1.3.0-beta.2.0.20190828155532-0293cbd26c69/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA=
|
||||
github.com/containerd/containerd v1.3.0/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA=
|
||||
github.com/containerd/containerd v1.3.2 h1:ForxmXkA6tPIvffbrDAcPUIB32QgXkt2XFj+F0UxetA=
|
||||
|
@ -294,8 +294,9 @@ github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ
|
|||
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
|
||||
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/google/go-cmp v0.4.0 h1:xsAVV57WRhGj6kEIi8ReJzQlHHqcBYCElAvkovg3B/4=
|
||||
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.2 h1:X2ev0eStA3AbceY54o37/0PQ/UWqKEiiO2dKL5OPaFM=
|
||||
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-querystring v0.0.0-20170111101155-53e6ce116135 h1:zLTLjkaOFEFIOxY5BWLFLwh+cL8vOBW4XJ2aqLE/Tf0=
|
||||
github.com/google/go-querystring v0.0.0-20170111101155-53e6ce116135/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck=
|
||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
|
@ -483,8 +484,6 @@ github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+o
|
|||
github.com/klauspost/compress v1.11.2 h1:MiK62aErc3gIiVEtyzKfeOHgW7atJb5g/KNX5m3c2nQ=
|
||||
github.com/klauspost/compress v1.11.2/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs=
|
||||
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||
github.com/konsorten/go-windows-terminal-sequences v1.0.3 h1:CE8S1cTafDpPvMhIxNJKvHsGVBgn1xWYf1NbHQhywc8=
|
||||
github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
|
||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||
github.com/kr/pretty v0.2.0 h1:s5hAObm+yFO5uHYt5dYjxi2rXrsnmRpJx4OYvIWUaQs=
|
||||
|
@ -553,16 +552,16 @@ github.com/mitchellh/osext v0.0.0-20151018003038-5e2d6d41470f/go.mod h1:OkQIRizQ
|
|||
github.com/mitchellh/reflectwalk v1.0.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
|
||||
github.com/mitchellh/reflectwalk v1.0.1 h1:FVzMWA5RllMAKIdUSC8mdWo3XtwoecrH79BY70sEEpE=
|
||||
github.com/mitchellh/reflectwalk v1.0.1/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
|
||||
github.com/moby/sys/mountinfo v0.1.3 h1:KIrhRO14+AkwKvG/g2yIpNMOUVZ02xNhOw8KY1WsLOI=
|
||||
github.com/moby/sys/mountinfo v0.1.3/go.mod h1:w2t2Avltqx8vE7gX5l+QiBKxODu2TX0+Syr3h52Tw4o=
|
||||
github.com/moby/sys/mountinfo v0.4.0 h1:1KInV3Huv18akCu58V7lzNlt+jFmqlu1EaErnEHE/VM=
|
||||
github.com/moby/sys/mountinfo v0.4.0/go.mod h1:rEr8tzG/lsIZHBtN/JjGG+LMYx9eXgW2JI+6q0qou+A=
|
||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
||||
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
||||
github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A=
|
||||
github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc=
|
||||
github.com/mrunalp/fileutils v0.0.0-20200520151820-abd8a0e76976 h1:aZQToFSLH8ejFeSkTc3r3L4dPImcj7Ib/KgmkQqbGGg=
|
||||
github.com/mrunalp/fileutils v0.0.0-20200520151820-abd8a0e76976/go.mod h1:x8F1gnqOkIEiO4rqoeEEEqQbo7HjGMTvyoq3gej4iT0=
|
||||
github.com/mrunalp/fileutils v0.5.0 h1:NKzVxiH7eSk+OQ4M+ZYW1K6h27RUV3MI6NUTsHhU6Z4=
|
||||
github.com/mrunalp/fileutils v0.5.0/go.mod h1:M1WthSahJixYnrXQl/DFQuteStB1weuxD2QJNHXfbSQ=
|
||||
github.com/munnerz/goautoneg v0.0.0-20120707110453-a547fc61f48d/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
|
||||
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
|
||||
github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw=
|
||||
|
@ -592,14 +591,14 @@ github.com/opencontainers/image-spec v1.0.1 h1:JMemWkRwHx4Zj+fVxWoMCFm/8sYGGrUVo
|
|||
github.com/opencontainers/image-spec v1.0.1/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0=
|
||||
github.com/opencontainers/runc v0.0.0-20190115041553-12f6a991201f/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59PVA73FjuZG0U=
|
||||
github.com/opencontainers/runc v0.1.1/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59PVA73FjuZG0U=
|
||||
github.com/opencontainers/runc v1.0.0-rc92 h1:+IczUKCRzDzFDnw99O/PAqrcBBCoRp9xN3cB1SYSNS4=
|
||||
github.com/opencontainers/runc v1.0.0-rc92/go.mod h1:X1zlU4p7wOlX4+WRCz+hvlRv8phdL7UqbYD+vQwNMmE=
|
||||
github.com/opencontainers/runc v1.0.0-rc93 h1:x2UMpOOVf3kQ8arv/EsDGwim8PTNqzL1/EYDr/+scOM=
|
||||
github.com/opencontainers/runc v1.0.0-rc93/go.mod h1:3NOsor4w32B2tC0Zbl8Knk4Wg84SM2ImC1fxBuqJ/H0=
|
||||
github.com/opencontainers/runtime-spec v0.1.2-0.20190507144316-5b71a03e2700/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0=
|
||||
github.com/opencontainers/runtime-spec v1.0.3-0.20200728170252-4d89ac9fbff6 h1:NhsM2gc769rVWDqJvapK37r+7+CBXI8xHhnfnt8uQsg=
|
||||
github.com/opencontainers/runtime-spec v1.0.3-0.20200728170252-4d89ac9fbff6/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0=
|
||||
github.com/opencontainers/runtime-spec v1.0.3-0.20200929063507-e6143ca7d51d h1:pNa8metDkwZjb9g4T8s+krQ+HRgZAkqnXml+wNir/+s=
|
||||
github.com/opencontainers/runtime-spec v1.0.3-0.20200929063507-e6143ca7d51d/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0=
|
||||
github.com/opencontainers/runtime-tools v0.0.0-20181011054405-1d69bd0f9c39/go.mod h1:r3f7wjNzSs2extwzU3Y+6pKfobzPh+kKFJ3ofN+3nfs=
|
||||
github.com/opencontainers/selinux v1.6.0 h1:+bIAS/Za3q5FTwWym4fTB0vObnfCf3G/NC7K6Jx62mY=
|
||||
github.com/opencontainers/selinux v1.6.0/go.mod h1:VVGKuOLlE7v4PJyT6h7mNWvq1rzqiriPsEqVhc+svHE=
|
||||
github.com/opencontainers/selinux v1.8.0 h1:+77ba4ar4jsCbL1GLbFL8fFM57w6suPfSS9PDLDY7KM=
|
||||
github.com/opencontainers/selinux v1.8.0/go.mod h1:RScLhm78qiWa2gbVCcGkC7tCGdgk3ogry1nUQF8Evvo=
|
||||
github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o=
|
||||
github.com/packethost/packngo v0.1.1-0.20180711074735-b9cb5096f54c h1:vwpFWvAO8DeIZfFeqASzZfsxuWPno9ncAebBEP0N3uE=
|
||||
github.com/packethost/packngo v0.1.1-0.20180711074735-b9cb5096f54c/go.mod h1:otzZQXgoO96RTzDB/Hycg0qZcXZsWJGJRSXbmEIJ+4M=
|
||||
|
@ -676,8 +675,8 @@ github.com/sirupsen/logrus v1.0.6/go.mod h1:pMByvHTf9Beacp5x1UXfOR9xyW/9antXMhjM
|
|||
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
|
||||
github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q=
|
||||
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
|
||||
github.com/sirupsen/logrus v1.6.0 h1:UBcNElsrwanuuMsnGSlYmtmgbb23qDR5dG+6X6Oo89I=
|
||||
github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88=
|
||||
github.com/sirupsen/logrus v1.7.0 h1:ShrD1U9pZB12TX0cVy0DtePoCH97K8EtX+mg7ZARUtM=
|
||||
github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
|
||||
github.com/skratchdot/open-golang v0.0.0-20160302144031-75fb7ed4208c h1:fyKiXKO1/I/B6Y2U8T7WdQGWzwehOuGIrljPtt7YTTI=
|
||||
github.com/skratchdot/open-golang v0.0.0-20160302144031-75fb7ed4208c/go.mod h1:sUM3LWHvSMaG192sy56D9F7CNvL7jUJVXoqM1QKLnog=
|
||||
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
|
||||
|
@ -703,8 +702,8 @@ github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5
|
|||
github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0=
|
||||
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/syndtr/gocapability v0.0.0-20170704070218-db04d3cc01c8/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww=
|
||||
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-20200815063812-42c35b437635 h1:kdXcSzyDtseVEc4yCz2qF8ZrQvIDBJLl4S1c3GCXmoI=
|
||||
github.com/syndtr/gocapability v0.0.0-20200815063812-42c35b437635/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww=
|
||||
github.com/tencentcloud/tencentcloud-sdk-go v3.0.83+incompatible h1:8uRvJleFpqLsO77WaAh2UrasMOzd8MxXrNj20e7El+Q=
|
||||
github.com/tencentcloud/tencentcloud-sdk-go v3.0.83+incompatible/go.mod h1:0PfYow01SHPMhKY31xa+EFz2RStxIqj6JFAJS+IkCi4=
|
||||
github.com/tklauser/go-sysconf v0.3.4/go.mod h1:Cl2c8ZRWfHD5IrfHo9VN+FX9kCFjIOyVklgXycLB6ek=
|
||||
|
@ -727,8 +726,8 @@ github.com/vmihailenco/msgpack v3.3.3+incompatible h1:wapg9xDUZDzGCNFlwc5SqI1rvc
|
|||
github.com/vmihailenco/msgpack v3.3.3+incompatible/go.mod h1:fy3FlTQTDXWkZ7Bh6AcGMlsjHatGryHQYUTf1ShIgkk=
|
||||
github.com/vmware/govmomi v0.18.0 h1:f7QxSmP7meCtoAmiKZogvVbLInT+CZx6Px6K5rYsJZo=
|
||||
github.com/vmware/govmomi v0.18.0/go.mod h1:URlwyTFZX72RmxtxuaFL2Uj3fD1JTvZdx59bHWk6aFU=
|
||||
github.com/willf/bitset v1.1.11-0.20200630133818-d5bec3311243 h1:R43TdZy32XXSXjJn7M/HhALJ9imq6ztLnChfYJpVDnM=
|
||||
github.com/willf/bitset v1.1.11-0.20200630133818-d5bec3311243/go.mod h1:RjeCKbqT1RxIR/KWY6phxZiaY1IyutSBfGjNPySAYV4=
|
||||
github.com/willf/bitset v1.1.11 h1:N7Z7E9UvjW+sGsEl7k/SJrvY2reP1A07MrGuCjIOjRE=
|
||||
github.com/willf/bitset v1.1.11/go.mod h1:83CECat5yLh5zVOf4P1ErAgKA5UDvKtgyUABdr3+MjI=
|
||||
github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU=
|
||||
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ=
|
||||
github.com/xeipuuv/gojsonschema v0.0.0-20180618132009-1d523034197f/go.mod h1:5yf86TLmAcydyeJq5YvxkGPE2fm/u4myDekKRoLuqhs=
|
||||
|
@ -814,8 +813,8 @@ golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLL
|
|||
golang.org/x/net v0.0.0-20191004110552-13f9640d40b9/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200602114024-627f9648deb9 h1:pNX+40auqi2JqRfOP1akLGtYcn15TUbkhwuCO3foqqM=
|
||||
golang.org/x/net v0.0.0-20200602114024-627f9648deb9/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||
golang.org/x/net v0.0.0-20201224014010-6772e930b67b h1:iFwSg7t5GZmB/Q5TjiEAsdoLDrdJRC1RiF2WhuV29Qw=
|
||||
golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45 h1:SVwTIAaPC2U/AvvLNZ2a7OVsmBpC8L5BlwK1whH3hm0=
|
||||
|
@ -861,23 +860,24 @@ golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7w
|
|||
golang.org/x/sys v0.0.0-20191022100944-742c48ecaeb7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/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-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200124204421-9fbb57f87de9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200728102440-3e129f6d46b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200909081042-eff7692f9009/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200916030750-2334cc1a136f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210217105451-b926d437f341/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210316164454-77fc1eacc6aa h1:ZYxPR6aca/uhfRJyaOAtflSHjJYiktO7QnJC5ut7iY4=
|
||||
golang.org/x/sys v0.0.0-20210316164454-77fc1eacc6aa/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/text v0.0.0-20160726164857-2910a502d2bf/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.1-0.20181227161524-e6919f6577db/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||
golang.org/x/text v0.3.3-0.20200306154105-06d492aade88 h1:FrVQfHlSD67oSu7MiglkpXjapTEe2ZF/wS9JaYQX/xA=
|
||||
golang.org/x/text v0.3.3-0.20200306154105-06d492aade88/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20191024005414-555d28b269f0 h1:/5xXl8Y5W96D+TtHSlonuFqGHIWVuyCkGJLwGh9JJFs=
|
||||
|
|
|
@ -0,0 +1,17 @@
|
|||
---
|
||||
Language: Cpp
|
||||
BasedOnStyle: LLVM
|
||||
AlignAfterOpenBracket: DontAlign
|
||||
AlignConsecutiveAssignments: true
|
||||
AlignEscapedNewlines: DontAlign
|
||||
AlwaysBreakBeforeMultilineStrings: true
|
||||
AlwaysBreakTemplateDeclarations: false
|
||||
AllowAllParametersOfDeclarationOnNextLine: false
|
||||
AllowShortFunctionsOnASingleLine: false
|
||||
BreakBeforeBraces: Attach
|
||||
IndentWidth: 4
|
||||
KeepEmptyLinesAtTheStartOfBlocks: false
|
||||
TabWidth: 4
|
||||
UseTab: ForContinuationAndIndentation
|
||||
ColumnLimit: 1000
|
||||
...
|
|
@ -4,6 +4,7 @@
|
|||
*.dll
|
||||
*.so
|
||||
*.dylib
|
||||
*.o
|
||||
|
||||
# Test binary, build with `go test -c`
|
||||
*.test
|
||||
|
|
|
@ -88,12 +88,6 @@ type ProgramABI struct {
|
|||
Type ProgramType
|
||||
}
|
||||
|
||||
func newProgramABIFromSpec(spec *ProgramSpec) *ProgramABI {
|
||||
return &ProgramABI{
|
||||
spec.Type,
|
||||
}
|
||||
}
|
||||
|
||||
func newProgramABIFromFd(fd *internal.FD) (string, *ProgramABI, error) {
|
||||
info, err := bpfGetProgInfoByFD(fd)
|
||||
if err != nil {
|
||||
|
|
|
@ -12,6 +12,14 @@ import (
|
|||
// InstructionSize is the size of a BPF instruction in bytes
|
||||
const InstructionSize = 8
|
||||
|
||||
// RawInstructionOffset is an offset in units of raw BPF instructions.
|
||||
type RawInstructionOffset uint64
|
||||
|
||||
// Bytes returns the offset of an instruction in bytes.
|
||||
func (rio RawInstructionOffset) Bytes() uint64 {
|
||||
return uint64(rio) * InstructionSize
|
||||
}
|
||||
|
||||
// Instruction is a single eBPF instruction.
|
||||
type Instruction struct {
|
||||
OpCode OpCode
|
||||
|
@ -155,6 +163,13 @@ func (ins *Instruction) isLoadFromMap() bool {
|
|||
return ins.OpCode == LoadImmOp(DWord) && (ins.Src == PseudoMapFD || ins.Src == PseudoMapValue)
|
||||
}
|
||||
|
||||
// IsFunctionCall returns true if the instruction calls another BPF function.
|
||||
//
|
||||
// This is not the same thing as a BPF helper call.
|
||||
func (ins *Instruction) IsFunctionCall() bool {
|
||||
return ins.OpCode.JumpOp() == Call && ins.Src == PseudoCall
|
||||
}
|
||||
|
||||
// Format implements fmt.Formatter.
|
||||
func (ins Instruction) Format(f fmt.State, c rune) {
|
||||
if c != 'v' {
|
||||
|
@ -310,28 +325,6 @@ func (insns Instructions) ReferenceOffsets() map[string][]int {
|
|||
return offsets
|
||||
}
|
||||
|
||||
func (insns Instructions) marshalledOffsets() (map[string]int, error) {
|
||||
symbols := make(map[string]int)
|
||||
|
||||
marshalledPos := 0
|
||||
for _, ins := range insns {
|
||||
currentPos := marshalledPos
|
||||
marshalledPos += ins.OpCode.marshalledInstructions()
|
||||
|
||||
if ins.Symbol == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
if _, ok := symbols[ins.Symbol]; ok {
|
||||
return nil, fmt.Errorf("duplicate symbol %s", ins.Symbol)
|
||||
}
|
||||
|
||||
symbols[ins.Symbol] = currentPos
|
||||
}
|
||||
|
||||
return symbols, nil
|
||||
}
|
||||
|
||||
// Format implements fmt.Formatter.
|
||||
//
|
||||
// You can control indentation of symbols by
|
||||
|
@ -370,21 +363,17 @@ func (insns Instructions) Format(f fmt.State, c rune) {
|
|||
symIndent = strings.Repeat(" ", symPadding)
|
||||
}
|
||||
|
||||
// Figure out how many digits we need to represent the highest
|
||||
// offset.
|
||||
highestOffset := 0
|
||||
for _, ins := range insns {
|
||||
highestOffset += ins.OpCode.marshalledInstructions()
|
||||
}
|
||||
// Guess how many digits we need at most, by assuming that all instructions
|
||||
// are double wide.
|
||||
highestOffset := len(insns) * 2
|
||||
offsetWidth := int(math.Ceil(math.Log10(float64(highestOffset))))
|
||||
|
||||
offset := 0
|
||||
for _, ins := range insns {
|
||||
if ins.Symbol != "" {
|
||||
fmt.Fprintf(f, "%s%s:\n", symIndent, ins.Symbol)
|
||||
iter := insns.Iterate()
|
||||
for iter.Next() {
|
||||
if iter.Ins.Symbol != "" {
|
||||
fmt.Fprintf(f, "%s%s:\n", symIndent, iter.Ins.Symbol)
|
||||
}
|
||||
fmt.Fprintf(f, "%s%*d: %v\n", indent, offsetWidth, offset, ins)
|
||||
offset += ins.OpCode.marshalledInstructions()
|
||||
fmt.Fprintf(f, "%s%*d: %v\n", indent, offsetWidth, iter.Offset, iter.Ins)
|
||||
}
|
||||
|
||||
return
|
||||
|
@ -392,43 +381,49 @@ func (insns Instructions) Format(f fmt.State, c rune) {
|
|||
|
||||
// Marshal encodes a BPF program into the kernel format.
|
||||
func (insns Instructions) Marshal(w io.Writer, bo binary.ByteOrder) error {
|
||||
absoluteOffsets, err := insns.marshalledOffsets()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
num := 0
|
||||
for i, ins := range insns {
|
||||
switch {
|
||||
case ins.OpCode.JumpOp() == Call && ins.Src == PseudoCall && ins.Constant == -1:
|
||||
// Rewrite bpf to bpf call
|
||||
offset, ok := absoluteOffsets[ins.Reference]
|
||||
if !ok {
|
||||
return fmt.Errorf("instruction %d: reference to missing symbol %s", i, ins.Reference)
|
||||
}
|
||||
|
||||
ins.Constant = int64(offset - num - 1)
|
||||
|
||||
case ins.OpCode.Class() == JumpClass && ins.Offset == -1:
|
||||
// Rewrite jump to label
|
||||
offset, ok := absoluteOffsets[ins.Reference]
|
||||
if !ok {
|
||||
return fmt.Errorf("instruction %d: reference to missing symbol %s", i, ins.Reference)
|
||||
}
|
||||
|
||||
ins.Offset = int16(offset - num - 1)
|
||||
}
|
||||
|
||||
n, err := ins.Marshal(w, bo)
|
||||
_, err := ins.Marshal(w, bo)
|
||||
if err != nil {
|
||||
return fmt.Errorf("instruction %d: %w", i, err)
|
||||
}
|
||||
|
||||
num += int(n / InstructionSize)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Iterate allows iterating a BPF program while keeping track of
|
||||
// various offsets.
|
||||
//
|
||||
// Modifying the instruction slice will lead to undefined behaviour.
|
||||
func (insns Instructions) Iterate() *InstructionIterator {
|
||||
return &InstructionIterator{insns: insns}
|
||||
}
|
||||
|
||||
// InstructionIterator iterates over a BPF program.
|
||||
type InstructionIterator struct {
|
||||
insns Instructions
|
||||
// The instruction in question.
|
||||
Ins *Instruction
|
||||
// The index of the instruction in the original instruction slice.
|
||||
Index int
|
||||
// The offset of the instruction in raw BPF instructions. This accounts
|
||||
// for double-wide instructions.
|
||||
Offset RawInstructionOffset
|
||||
}
|
||||
|
||||
// Next returns true as long as there are any instructions remaining.
|
||||
func (iter *InstructionIterator) Next() bool {
|
||||
if len(iter.insns) == 0 {
|
||||
return false
|
||||
}
|
||||
|
||||
if iter.Ins != nil {
|
||||
iter.Offset += RawInstructionOffset(iter.Ins.OpCode.rawInstructions())
|
||||
}
|
||||
iter.Ins = &iter.insns[0]
|
||||
iter.insns = iter.insns[1:]
|
||||
return true
|
||||
}
|
||||
|
||||
type bpfInstruction struct {
|
||||
OpCode OpCode
|
||||
Registers bpfRegisters
|
||||
|
|
|
@ -66,10 +66,10 @@ type OpCode uint8
|
|||
// InvalidOpCode is returned by setters on OpCode
|
||||
const InvalidOpCode OpCode = 0xff
|
||||
|
||||
// marshalledInstructions returns the number of BPF instructions required
|
||||
// rawInstructions returns the number of BPF instructions required
|
||||
// to encode this opcode.
|
||||
func (op OpCode) marshalledInstructions() int {
|
||||
if op == LoadImmOp(DWord) {
|
||||
func (op OpCode) rawInstructions() int {
|
||||
if op.isDWordLoad() {
|
||||
return 2
|
||||
}
|
||||
return 1
|
||||
|
|
|
@ -4,6 +4,8 @@ import (
|
|||
"errors"
|
||||
"fmt"
|
||||
"math"
|
||||
"reflect"
|
||||
"strings"
|
||||
|
||||
"github.com/cilium/ebpf/asm"
|
||||
"github.com/cilium/ebpf/internal"
|
||||
|
@ -11,7 +13,10 @@ import (
|
|||
)
|
||||
|
||||
// CollectionOptions control loading a collection into the kernel.
|
||||
//
|
||||
// Maps and Programs are passed to NewMapWithOptions and NewProgramsWithOptions.
|
||||
type CollectionOptions struct {
|
||||
Maps MapOptions
|
||||
Programs ProgramOptions
|
||||
}
|
||||
|
||||
|
@ -126,6 +131,65 @@ func (cs *CollectionSpec) RewriteConstants(consts map[string]interface{}) error
|
|||
return nil
|
||||
}
|
||||
|
||||
// Assign the contents of a collection spec to a struct.
|
||||
//
|
||||
// This function is a short-cut to manually checking the presence
|
||||
// of maps and programs in a collection spec.
|
||||
//
|
||||
// The argument to must be a pointer to a struct. A field of the
|
||||
// struct is updated with values from Programs or Maps if it
|
||||
// has an `ebpf` tag and its type is *ProgramSpec or *MapSpec.
|
||||
// The tag gives the name of the program or map as found in
|
||||
// the CollectionSpec.
|
||||
//
|
||||
// struct {
|
||||
// Foo *ebpf.ProgramSpec `ebpf:"xdp_foo"`
|
||||
// Bar *ebpf.MapSpec `ebpf:"bar_map"`
|
||||
// Ignored int
|
||||
// }
|
||||
//
|
||||
// Returns an error if any of the fields can't be found, or
|
||||
// if the same map or program is assigned multiple times.
|
||||
func (cs *CollectionSpec) Assign(to interface{}) error {
|
||||
valueOf := func(typ reflect.Type, name string) (reflect.Value, error) {
|
||||
switch typ {
|
||||
case reflect.TypeOf((*ProgramSpec)(nil)):
|
||||
p := cs.Programs[name]
|
||||
if p == nil {
|
||||
return reflect.Value{}, fmt.Errorf("missing program %q", name)
|
||||
}
|
||||
return reflect.ValueOf(p), nil
|
||||
case reflect.TypeOf((*MapSpec)(nil)):
|
||||
m := cs.Maps[name]
|
||||
if m == nil {
|
||||
return reflect.Value{}, fmt.Errorf("missing map %q", name)
|
||||
}
|
||||
return reflect.ValueOf(m), nil
|
||||
default:
|
||||
return reflect.Value{}, fmt.Errorf("unsupported type %s", typ)
|
||||
}
|
||||
}
|
||||
|
||||
return assignValues(to, valueOf)
|
||||
}
|
||||
|
||||
// LoadAndAssign creates a collection from a spec, and assigns it to a struct.
|
||||
//
|
||||
// See Collection.Assign for details.
|
||||
func (cs *CollectionSpec) LoadAndAssign(to interface{}, opts *CollectionOptions) error {
|
||||
if opts == nil {
|
||||
opts = &CollectionOptions{}
|
||||
}
|
||||
|
||||
coll, err := NewCollectionWithOptions(cs, *opts)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer coll.Close()
|
||||
|
||||
return coll.Assign(to)
|
||||
}
|
||||
|
||||
// Collection is a collection of Programs and Maps associated
|
||||
// with their symbols
|
||||
type Collection struct {
|
||||
|
@ -191,7 +255,7 @@ func NewCollectionWithOptions(spec *CollectionSpec, opts CollectionOptions) (col
|
|||
}
|
||||
}
|
||||
|
||||
m, err := newMapWithBTF(mapSpec, handle)
|
||||
m, err := newMapWithBTF(mapSpec, handle, opts.Maps)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("map %s: %w", mapName, err)
|
||||
}
|
||||
|
@ -292,3 +356,105 @@ func (coll *Collection) DetachProgram(name string) *Program {
|
|||
delete(coll.Programs, name)
|
||||
return p
|
||||
}
|
||||
|
||||
// Assign the contents of a collection to a struct.
|
||||
//
|
||||
// `to` must be a pointer to a struct like the following:
|
||||
//
|
||||
// struct {
|
||||
// Foo *ebpf.Program `ebpf:"xdp_foo"`
|
||||
// Bar *ebpf.Map `ebpf:"bar_map"`
|
||||
// Ignored int
|
||||
// }
|
||||
//
|
||||
// See CollectionSpec.Assign for the semantics of this function.
|
||||
//
|
||||
// DetachMap and DetachProgram is invoked for all assigned elements
|
||||
// if the function is successful.
|
||||
func (coll *Collection) Assign(to interface{}) error {
|
||||
assignedMaps := make(map[string]struct{})
|
||||
assignedPrograms := make(map[string]struct{})
|
||||
valueOf := func(typ reflect.Type, name string) (reflect.Value, error) {
|
||||
switch typ {
|
||||
case reflect.TypeOf((*Program)(nil)):
|
||||
p := coll.Programs[name]
|
||||
if p == nil {
|
||||
return reflect.Value{}, fmt.Errorf("missing program %q", name)
|
||||
}
|
||||
assignedPrograms[name] = struct{}{}
|
||||
return reflect.ValueOf(p), nil
|
||||
case reflect.TypeOf((*Map)(nil)):
|
||||
m := coll.Maps[name]
|
||||
if m == nil {
|
||||
return reflect.Value{}, fmt.Errorf("missing map %q", name)
|
||||
}
|
||||
assignedMaps[name] = struct{}{}
|
||||
return reflect.ValueOf(m), nil
|
||||
default:
|
||||
return reflect.Value{}, fmt.Errorf("unsupported type %s", typ)
|
||||
}
|
||||
}
|
||||
|
||||
if err := assignValues(to, valueOf); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for name := range assignedPrograms {
|
||||
coll.DetachProgram(name)
|
||||
}
|
||||
|
||||
for name := range assignedMaps {
|
||||
coll.DetachMap(name)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func assignValues(to interface{}, valueOf func(reflect.Type, string) (reflect.Value, error)) error {
|
||||
v := reflect.ValueOf(to)
|
||||
if v.Kind() != reflect.Ptr || v.Elem().Kind() != reflect.Struct {
|
||||
return fmt.Errorf("%T is not a pointer to a struct", to)
|
||||
}
|
||||
|
||||
type elem struct {
|
||||
typ reflect.Type
|
||||
name string
|
||||
}
|
||||
|
||||
var (
|
||||
s = v.Elem()
|
||||
sT = s.Type()
|
||||
assignedTo = make(map[elem]string)
|
||||
)
|
||||
for i := 0; i < sT.NumField(); i++ {
|
||||
field := sT.Field(i)
|
||||
|
||||
name := field.Tag.Get("ebpf")
|
||||
if name == "" {
|
||||
continue
|
||||
}
|
||||
if strings.Contains(name, ",") {
|
||||
return fmt.Errorf("field %s: ebpf tag contains a comma", field.Name)
|
||||
}
|
||||
|
||||
e := elem{field.Type, name}
|
||||
if assignedField := assignedTo[e]; assignedField != "" {
|
||||
return fmt.Errorf("field %s: %q was already assigned to %s", field.Name, name, assignedField)
|
||||
}
|
||||
|
||||
value, err := valueOf(field.Type, name)
|
||||
if err != nil {
|
||||
return fmt.Errorf("field %s: %w", field.Name, err)
|
||||
}
|
||||
|
||||
fieldValue := s.Field(i)
|
||||
if !fieldValue.CanSet() {
|
||||
return fmt.Errorf("can't set value of field %s", field.Name)
|
||||
}
|
||||
|
||||
fieldValue.Set(value)
|
||||
assignedTo[e] = field.Name
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -12,6 +12,5 @@
|
|||
// eBPF code should be compiled ahead of time using clang, and shipped with
|
||||
// your application as any other resource.
|
||||
//
|
||||
// This package doesn't include code required to attach eBPF to Linux
|
||||
// subsystems, since this varies per subsystem.
|
||||
// Use the link subpackage to attach a loaded program to a hook in the kernel.
|
||||
package ebpf
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package ebpf
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"debug/elf"
|
||||
"encoding/binary"
|
||||
|
@ -236,7 +237,7 @@ func (ec *elfCode) loadPrograms(progSections map[elf.SectionIndex]*elf.Section,
|
|||
|
||||
func (ec *elfCode) loadInstructions(section *elf.Section, symbols, relocations map[uint64]elf.Symbol) (asm.Instructions, uint64, error) {
|
||||
var (
|
||||
r = section.Open()
|
||||
r = bufio.NewReader(section.Open())
|
||||
insns asm.Instructions
|
||||
offset uint64
|
||||
)
|
||||
|
@ -389,7 +390,7 @@ func (ec *elfCode) loadMaps(maps map[string]*MapSpec, mapSections map[elf.Sectio
|
|||
}
|
||||
|
||||
var (
|
||||
r = sec.Open()
|
||||
r = bufio.NewReader(sec.Open())
|
||||
size = sec.Size / uint64(len(syms))
|
||||
)
|
||||
for i, offset := 0, uint64(0); i < len(syms); i, offset = i+1, offset+size {
|
||||
|
@ -482,6 +483,7 @@ func mapSpecFromBTF(spec *btf.Spec, name string) (*MapSpec, error) {
|
|||
|
||||
var (
|
||||
mapType, flags, maxEntries uint32
|
||||
pinType PinType
|
||||
)
|
||||
for _, member := range btfMapMembers {
|
||||
switch member.Name {
|
||||
|
@ -529,9 +531,7 @@ func mapSpecFromBTF(spec *btf.Spec, name string) (*MapSpec, error) {
|
|||
return nil, fmt.Errorf("can't get pinning: %w", err)
|
||||
}
|
||||
|
||||
if pinning != 0 {
|
||||
return nil, fmt.Errorf("'pinning' attribute not supported: %w", ErrNotSupported)
|
||||
}
|
||||
pinType = PinType(pinning)
|
||||
|
||||
case "key", "value":
|
||||
default:
|
||||
|
@ -540,12 +540,14 @@ func mapSpecFromBTF(spec *btf.Spec, name string) (*MapSpec, error) {
|
|||
}
|
||||
|
||||
return &MapSpec{
|
||||
Name: SanitizeName(name, -1),
|
||||
Type: MapType(mapType),
|
||||
KeySize: keySize,
|
||||
ValueSize: valueSize,
|
||||
MaxEntries: maxEntries,
|
||||
Flags: flags,
|
||||
BTF: btfMap,
|
||||
Pinning: pinType,
|
||||
}, nil
|
||||
}
|
||||
|
||||
|
@ -636,6 +638,8 @@ func getProgType(sectionName string) (ProgramType, AttachType, string) {
|
|||
"lirc_mode2": {LircMode2, AttachLircMode2},
|
||||
"flow_dissector": {FlowDissector, AttachFlowDissector},
|
||||
"iter/": {Tracing, AttachTraceIter},
|
||||
"sk_lookup/": {SkLookup, AttachSkLookup},
|
||||
"lsm/": {LSM, AttachLSMMac},
|
||||
|
||||
"cgroup_skb/ingress": {CGroupSKB, AttachCGroupInetIngress},
|
||||
"cgroup_skb/egress": {CGroupSKB, AttachCGroupInetEgress},
|
||||
|
@ -684,7 +688,7 @@ func (ec *elfCode) loadRelocations(sections map[elf.SectionIndex]*elf.Section) (
|
|||
return nil, nil, fmt.Errorf("section %s: relocations are less than 16 bytes", sec.Name)
|
||||
}
|
||||
|
||||
r := sec.Open()
|
||||
r := bufio.NewReader(sec.Open())
|
||||
for off := uint64(0); off < sec.Size; off += sec.Entsize {
|
||||
ent := io.LimitReader(r, int64(sec.Entsize))
|
||||
|
||||
|
|
|
@ -1,5 +1,8 @@
|
|||
module github.com/cilium/ebpf
|
||||
|
||||
go 1.13
|
||||
go 1.14
|
||||
|
||||
require golang.org/x/sys v0.0.0-20200124204421-9fbb57f87de9
|
||||
require (
|
||||
github.com/google/go-cmp v0.5.2
|
||||
golang.org/x/sys v0.0.0-20200124204421-9fbb57f87de9
|
||||
)
|
||||
|
|
|
@ -1,2 +1,6 @@
|
|||
github.com/google/go-cmp v0.5.2 h1:X2ev0eStA3AbceY54o37/0PQ/UWqKEiiO2dKL5OPaFM=
|
||||
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
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=
|
||||
|
|
|
@ -31,7 +31,7 @@ var (
|
|||
type Spec struct {
|
||||
rawTypes []rawType
|
||||
strings stringTable
|
||||
types map[string][]Type
|
||||
types map[string][]namedType
|
||||
funcInfos map[string]extInfo
|
||||
lineInfos map[string]extInfo
|
||||
byteOrder binary.ByteOrder
|
||||
|
@ -59,29 +59,9 @@ func LoadSpecFromReader(rd io.ReaderAt) (*Spec, error) {
|
|||
}
|
||||
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, fmt.Errorf("section %s exceeds maximum size", sec.Name)
|
||||
}
|
||||
|
||||
sectionSizes[sec.Name] = uint32(sec.Size)
|
||||
}
|
||||
btfSection, btfExtSection, sectionSizes, err := findBtfSections(file)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if btfSection == nil {
|
||||
|
@ -129,6 +109,51 @@ func LoadSpecFromReader(rd io.ReaderAt) (*Spec, error) {
|
|||
return spec, nil
|
||||
}
|
||||
|
||||
func findBtfSections(file *elf.File) (*elf.Section, *elf.Section, map[string]uint32, error) {
|
||||
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, nil, nil, fmt.Errorf("section %s exceeds maximum size", sec.Name)
|
||||
}
|
||||
|
||||
sectionSizes[sec.Name] = uint32(sec.Size)
|
||||
}
|
||||
}
|
||||
return btfSection, btfExtSection, sectionSizes, nil
|
||||
}
|
||||
|
||||
func loadSpecFromVmlinux(rd io.ReaderAt) (*Spec, error) {
|
||||
file, err := elf.NewFile(rd)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
btfSection, _, _, err := findBtfSections(file)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf(".BTF ELF section: %s", err)
|
||||
}
|
||||
if btfSection == nil {
|
||||
return nil, fmt.Errorf("unable to find .BTF ELF section")
|
||||
}
|
||||
return loadNakedSpec(btfSection.Open(), file.ByteOrder, nil, nil)
|
||||
}
|
||||
|
||||
func loadNakedSpec(btf io.ReadSeeker, bo binary.ByteOrder, sectionSizes map[string]uint32, variableOffsets map[variable]uint32) (*Spec, error) {
|
||||
rawTypes, rawStrings, err := parseBTF(btf, bo)
|
||||
if err != nil {
|
||||
|
@ -176,16 +201,43 @@ func LoadKernelSpec() (*Spec, error) {
|
|||
}
|
||||
|
||||
func loadKernelSpec() (*Spec, error) {
|
||||
fh, err := os.Open("/sys/kernel/btf/vmlinux")
|
||||
if os.IsNotExist(err) {
|
||||
return nil, fmt.Errorf("can't open kernel BTF at /sys/kernel/btf/vmlinux: %w", ErrNotFound)
|
||||
}
|
||||
release, err := unix.KernelRelease()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("can't read kernel BTF: %s", err)
|
||||
return nil, fmt.Errorf("can't read kernel release number: %w", err)
|
||||
}
|
||||
defer fh.Close()
|
||||
|
||||
return loadNakedSpec(fh, internal.NativeEndian, nil, nil)
|
||||
fh, err := os.Open("/sys/kernel/btf/vmlinux")
|
||||
if err == nil {
|
||||
defer fh.Close()
|
||||
|
||||
return loadNakedSpec(fh, internal.NativeEndian, nil, nil)
|
||||
}
|
||||
|
||||
// use same list of locations as libbpf
|
||||
// https://github.com/libbpf/libbpf/blob/9a3a42608dbe3731256a5682a125ac1e23bced8f/src/btf.c#L3114-L3122
|
||||
locations := []string{
|
||||
"/boot/vmlinux-%s",
|
||||
"/lib/modules/%s/vmlinux-%[1]s",
|
||||
"/lib/modules/%s/build/vmlinux",
|
||||
"/usr/lib/modules/%s/kernel/vmlinux",
|
||||
"/usr/lib/debug/boot/vmlinux-%s",
|
||||
"/usr/lib/debug/boot/vmlinux-%s.debug",
|
||||
"/usr/lib/debug/lib/modules/%s/vmlinux",
|
||||
}
|
||||
|
||||
for _, loc := range locations {
|
||||
path := fmt.Sprintf(loc, release)
|
||||
|
||||
fh, err := os.Open(path)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
defer fh.Close()
|
||||
|
||||
return loadSpecFromVmlinux(fh)
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("no BTF for kernel version %s: %w", release, internal.ErrNotSupported)
|
||||
}
|
||||
|
||||
func parseBTF(btf io.ReadSeeker, bo binary.ByteOrder) ([]rawType, stringTable, error) {
|
||||
|
@ -402,9 +454,19 @@ func (s *Spec) Map(name string) (*Map, []Member, error) {
|
|||
switch member.Name {
|
||||
case "key":
|
||||
key = member.Type
|
||||
if pk, isPtr := key.(*Pointer); !isPtr {
|
||||
return nil, nil, fmt.Errorf("key type is not a pointer: %T", key)
|
||||
} else {
|
||||
key = pk.Target
|
||||
}
|
||||
|
||||
case "value":
|
||||
value = member.Type
|
||||
if vk, isPtr := value.(*Pointer); !isPtr {
|
||||
return nil, nil, fmt.Errorf("value type is not a pointer: %T", value)
|
||||
} else {
|
||||
value = vk.Target
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -441,11 +503,16 @@ func (s *Spec) FindType(name string, typ Type) error {
|
|||
candidate Type
|
||||
)
|
||||
|
||||
for _, typ := range s.types[name] {
|
||||
for _, typ := range s.types[essentialName(name)] {
|
||||
if reflect.TypeOf(typ) != wanted {
|
||||
continue
|
||||
}
|
||||
|
||||
// Match against the full name, not just the essential one.
|
||||
if typ.name() != name {
|
||||
continue
|
||||
}
|
||||
|
||||
if candidate != nil {
|
||||
return fmt.Errorf("type %s: multiple candidates for %T", name, typ)
|
||||
}
|
||||
|
@ -621,9 +688,7 @@ type bpfLoadBTFAttr struct {
|
|||
}
|
||||
|
||||
func bpfLoadBTF(attr *bpfLoadBTFAttr) (*internal.FD, error) {
|
||||
const _BTFLoad = 18
|
||||
|
||||
fd, err := internal.BPF(_BTFLoad, unsafe.Pointer(attr), unsafe.Sizeof(*attr))
|
||||
fd, err := internal.BPF(internal.BPF_BTF_LOAD, unsafe.Pointer(attr), unsafe.Sizeof(*attr))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
|
@ -40,10 +40,12 @@ const (
|
|||
)
|
||||
|
||||
const (
|
||||
btfTypeKindShift = 24
|
||||
btfTypeKindLen = 4
|
||||
btfTypeVlenShift = 0
|
||||
btfTypeVlenMask = 16
|
||||
btfTypeKindShift = 24
|
||||
btfTypeKindLen = 4
|
||||
btfTypeVlenShift = 0
|
||||
btfTypeVlenMask = 16
|
||||
btfTypeKindFlagShift = 31
|
||||
btfTypeKindFlagMask = 1
|
||||
)
|
||||
|
||||
// btfType is equivalent to struct btf_type in Documentation/bpf/btf.rst.
|
||||
|
@ -136,6 +138,10 @@ func (bt *btfType) SetVlen(vlen int) {
|
|||
bt.setInfo(uint32(vlen), btfTypeVlenMask, btfTypeVlenShift)
|
||||
}
|
||||
|
||||
func (bt *btfType) KindFlag() bool {
|
||||
return bt.info(btfTypeKindFlagMask, btfTypeKindFlagShift) == 1
|
||||
}
|
||||
|
||||
func (bt *btfType) Linkage() btfFuncLinkage {
|
||||
return btfFuncLinkage(bt.info(btfTypeVlenMask, btfTypeVlenShift))
|
||||
}
|
||||
|
@ -257,3 +263,7 @@ func readTypes(r io.Reader, bo binary.ByteOrder) ([]rawType, error) {
|
|||
types = append(types, rawType{header, data})
|
||||
}
|
||||
}
|
||||
|
||||
func intEncoding(raw uint32) (IntEncoding, uint32, byte) {
|
||||
return IntEncoding((raw & 0x0f000000) >> 24), (raw & 0x00ff0000) >> 16, byte(raw & 0x000000ff)
|
||||
}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package btf
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
|
@ -58,7 +59,8 @@ func parseExtInfos(r io.ReadSeeker, bo binary.ByteOrder, strings stringTable) (f
|
|||
return nil, nil, fmt.Errorf("can't seek to function info section: %v", err)
|
||||
}
|
||||
|
||||
funcInfo, err = parseExtInfo(io.LimitReader(r, int64(header.FuncInfoLen)), bo, strings)
|
||||
buf := bufio.NewReader(io.LimitReader(r, int64(header.FuncInfoLen)))
|
||||
funcInfo, err = parseExtInfo(buf, bo, strings)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("function info: %w", err)
|
||||
}
|
||||
|
@ -67,7 +69,8 @@ func parseExtInfos(r io.ReadSeeker, bo binary.ByteOrder, strings stringTable) (f
|
|||
return nil, nil, fmt.Errorf("can't seek to line info section: %v", err)
|
||||
}
|
||||
|
||||
lineInfo, err = parseExtInfo(io.LimitReader(r, int64(header.LineInfoLen)), bo, strings)
|
||||
buf = bufio.NewReader(io.LimitReader(r, int64(header.LineInfoLen)))
|
||||
lineInfo, err = parseExtInfo(buf, bo, strings)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("line info: %w", err)
|
||||
}
|
||||
|
@ -127,6 +130,8 @@ func (ei extInfo) MarshalBinary() ([]byte, error) {
|
|||
}
|
||||
|
||||
func parseExtInfo(r io.Reader, bo binary.ByteOrder, strings stringTable) (map[string]extInfo, error) {
|
||||
const maxRecordSize = 256
|
||||
|
||||
var recordSize uint32
|
||||
if err := binary.Read(r, bo, &recordSize); err != nil {
|
||||
return nil, fmt.Errorf("can't read record size: %v", err)
|
||||
|
@ -136,6 +141,9 @@ func parseExtInfo(r io.Reader, bo binary.ByteOrder, strings stringTable) (map[st
|
|||
// Need at least insnOff
|
||||
return nil, errors.New("record size too short")
|
||||
}
|
||||
if recordSize > maxRecordSize {
|
||||
return nil, fmt.Errorf("record size %v exceeds %v", recordSize, maxRecordSize)
|
||||
}
|
||||
|
||||
result := make(map[string]extInfo)
|
||||
for {
|
||||
|
|
|
@ -0,0 +1,49 @@
|
|||
// +build gofuzz
|
||||
|
||||
// Use with https://github.com/dvyukov/go-fuzz
|
||||
|
||||
package btf
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
|
||||
"github.com/cilium/ebpf/internal"
|
||||
)
|
||||
|
||||
func FuzzSpec(data []byte) int {
|
||||
if len(data) < binary.Size(btfHeader{}) {
|
||||
return -1
|
||||
}
|
||||
|
||||
spec, err := loadNakedSpec(bytes.NewReader(data), internal.NativeEndian, nil, nil)
|
||||
if err != nil {
|
||||
if spec != nil {
|
||||
panic("spec is not nil")
|
||||
}
|
||||
return 0
|
||||
}
|
||||
if spec == nil {
|
||||
panic("spec is nil")
|
||||
}
|
||||
return 1
|
||||
}
|
||||
|
||||
func FuzzExtInfo(data []byte) int {
|
||||
if len(data) < binary.Size(btfExtHeader{}) {
|
||||
return -1
|
||||
}
|
||||
|
||||
table := stringTable("\x00foo\x00barfoo\x00")
|
||||
info, err := parseExtInfo(bytes.NewReader(data), internal.NativeEndian, table)
|
||||
if err != nil {
|
||||
if info != nil {
|
||||
panic("info is not nil")
|
||||
}
|
||||
return 0
|
||||
}
|
||||
if info == nil {
|
||||
panic("info is nil")
|
||||
}
|
||||
return 1
|
||||
}
|
|
@ -4,6 +4,7 @@ import (
|
|||
"errors"
|
||||
"fmt"
|
||||
"math"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const maxTypeDepth = 32
|
||||
|
@ -23,9 +24,19 @@ type Type interface {
|
|||
// Make a copy of the type, without copying Type members.
|
||||
copy() Type
|
||||
|
||||
// Enumerate all nested Types. Repeated calls must visit nested
|
||||
// types in the same order.
|
||||
walk(*copyStack)
|
||||
}
|
||||
|
||||
// namedType is a type with a name.
|
||||
//
|
||||
// Most named types simply embed Name.
|
||||
type namedType interface {
|
||||
Type
|
||||
name() string
|
||||
}
|
||||
|
||||
// Name identifies a type.
|
||||
//
|
||||
// Anonymous types have an empty name.
|
||||
|
@ -43,15 +54,30 @@ func (v *Void) size() uint32 { return 0 }
|
|||
func (v *Void) copy() Type { return (*Void)(nil) }
|
||||
func (v *Void) walk(*copyStack) {}
|
||||
|
||||
type IntEncoding byte
|
||||
|
||||
const (
|
||||
Signed IntEncoding = 1 << iota
|
||||
Char
|
||||
Bool
|
||||
)
|
||||
|
||||
// Int is an integer of a given length.
|
||||
type Int struct {
|
||||
TypeID
|
||||
Name
|
||||
|
||||
// The size of the integer in bytes.
|
||||
Size uint32
|
||||
Size uint32
|
||||
Encoding IntEncoding
|
||||
// Offset is the starting bit offset. Currently always 0.
|
||||
// See https://www.kernel.org/doc/html/latest/bpf/btf.html#btf-kind-int
|
||||
Offset uint32
|
||||
Bits byte
|
||||
}
|
||||
|
||||
var _ namedType = (*Int)(nil)
|
||||
|
||||
func (i *Int) size() uint32 { return i.Size }
|
||||
func (i *Int) walk(*copyStack) {}
|
||||
func (i *Int) copy() Type {
|
||||
|
@ -109,6 +135,10 @@ func (s *Struct) copy() Type {
|
|||
return &cpy
|
||||
}
|
||||
|
||||
func (s *Struct) members() []Member {
|
||||
return s.Members
|
||||
}
|
||||
|
||||
// Union is a compound type where members occupy the same memory.
|
||||
type Union struct {
|
||||
TypeID
|
||||
|
@ -133,25 +163,51 @@ func (u *Union) copy() Type {
|
|||
return &cpy
|
||||
}
|
||||
|
||||
func (u *Union) members() []Member {
|
||||
return u.Members
|
||||
}
|
||||
|
||||
type composite interface {
|
||||
members() []Member
|
||||
}
|
||||
|
||||
var (
|
||||
_ composite = (*Struct)(nil)
|
||||
_ composite = (*Union)(nil)
|
||||
)
|
||||
|
||||
// Member is part of a Struct or Union.
|
||||
//
|
||||
// It is not a valid Type.
|
||||
type Member struct {
|
||||
Name
|
||||
Type Type
|
||||
Offset uint32
|
||||
Type Type
|
||||
// Offset is the bit offset of this member
|
||||
Offset uint32
|
||||
BitfieldSize uint32
|
||||
}
|
||||
|
||||
// Enum lists possible values.
|
||||
type Enum struct {
|
||||
TypeID
|
||||
Name
|
||||
Values []EnumValue
|
||||
}
|
||||
|
||||
// EnumValue is part of an Enum
|
||||
//
|
||||
// Is is not a valid Type
|
||||
type EnumValue struct {
|
||||
Name
|
||||
Value int32
|
||||
}
|
||||
|
||||
func (e *Enum) size() uint32 { return 4 }
|
||||
func (e *Enum) walk(*copyStack) {}
|
||||
func (e *Enum) copy() Type {
|
||||
cpy := *e
|
||||
cpy.Values = make([]EnumValue, len(e.Values))
|
||||
copy(cpy.Values, e.Values)
|
||||
return &cpy
|
||||
}
|
||||
|
||||
|
@ -180,36 +236,39 @@ func (td *Typedef) copy() Type {
|
|||
return &cpy
|
||||
}
|
||||
|
||||
// Volatile is a modifier.
|
||||
// Volatile is a qualifier.
|
||||
type Volatile struct {
|
||||
TypeID
|
||||
Type Type
|
||||
}
|
||||
|
||||
func (v *Volatile) qualify() Type { return v.Type }
|
||||
func (v *Volatile) walk(cs *copyStack) { cs.push(&v.Type) }
|
||||
func (v *Volatile) copy() Type {
|
||||
cpy := *v
|
||||
return &cpy
|
||||
}
|
||||
|
||||
// Const is a modifier.
|
||||
// Const is a qualifier.
|
||||
type Const struct {
|
||||
TypeID
|
||||
Type Type
|
||||
}
|
||||
|
||||
func (c *Const) qualify() Type { return c.Type }
|
||||
func (c *Const) walk(cs *copyStack) { cs.push(&c.Type) }
|
||||
func (c *Const) copy() Type {
|
||||
cpy := *c
|
||||
return &cpy
|
||||
}
|
||||
|
||||
// Restrict is a modifier.
|
||||
// Restrict is a qualifier.
|
||||
type Restrict struct {
|
||||
TypeID
|
||||
Type Type
|
||||
}
|
||||
|
||||
func (r *Restrict) qualify() Type { return r.Type }
|
||||
func (r *Restrict) walk(cs *copyStack) { cs.push(&r.Type) }
|
||||
func (r *Restrict) copy() Type {
|
||||
cpy := *r
|
||||
|
@ -233,15 +292,28 @@ func (f *Func) copy() Type {
|
|||
type FuncProto struct {
|
||||
TypeID
|
||||
Return Type
|
||||
// Parameters not supported yet
|
||||
Params []FuncParam
|
||||
}
|
||||
|
||||
func (fp *FuncProto) walk(cs *copyStack) {
|
||||
cs.push(&fp.Return)
|
||||
for i := range fp.Params {
|
||||
cs.push(&fp.Params[i].Type)
|
||||
}
|
||||
}
|
||||
|
||||
func (fp *FuncProto) walk(cs *copyStack) { cs.push(&fp.Return) }
|
||||
func (fp *FuncProto) copy() Type {
|
||||
cpy := *fp
|
||||
cpy.Params = make([]FuncParam, len(fp.Params))
|
||||
copy(cpy.Params, fp.Params)
|
||||
return &cpy
|
||||
}
|
||||
|
||||
type FuncParam struct {
|
||||
Name
|
||||
Type Type
|
||||
}
|
||||
|
||||
// Var is a global variable.
|
||||
type Var struct {
|
||||
TypeID
|
||||
|
@ -298,6 +370,16 @@ var (
|
|||
_ sizer = (*Datasec)(nil)
|
||||
)
|
||||
|
||||
type qualifier interface {
|
||||
qualify() Type
|
||||
}
|
||||
|
||||
var (
|
||||
_ qualifier = (*Const)(nil)
|
||||
_ qualifier = (*Restrict)(nil)
|
||||
_ qualifier = (*Volatile)(nil)
|
||||
)
|
||||
|
||||
// Sizeof returns the size of a type in bytes.
|
||||
//
|
||||
// Returns an error if the size can't be computed.
|
||||
|
@ -326,14 +408,9 @@ func Sizeof(typ Type) (int, error) {
|
|||
case *Typedef:
|
||||
typ = v.Type
|
||||
continue
|
||||
case *Volatile:
|
||||
typ = v.Type
|
||||
continue
|
||||
case *Const:
|
||||
typ = v.Type
|
||||
continue
|
||||
case *Restrict:
|
||||
typ = v.Type
|
||||
|
||||
case qualifier:
|
||||
typ = v.qualify()
|
||||
continue
|
||||
|
||||
default:
|
||||
|
@ -383,7 +460,7 @@ func copyType(typ Type) Type {
|
|||
}
|
||||
|
||||
// copyStack keeps track of pointers to types which still
|
||||
// need to be copied.
|
||||
// need to be visited.
|
||||
type copyStack []*Type
|
||||
|
||||
// push adds a type to the stack.
|
||||
|
@ -403,19 +480,13 @@ func (cs *copyStack) pop() *Type {
|
|||
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) {
|
||||
func inflateRawTypes(rawTypes []rawType, rawStrings stringTable) (namedTypes map[string][]namedType, err error) {
|
||||
type fixupDef struct {
|
||||
id TypeID
|
||||
expectedKind btfKind
|
||||
|
@ -427,7 +498,7 @@ func inflateRawTypes(rawTypes []rawType, rawStrings stringTable) (namedTypes map
|
|||
fixups = append(fixups, fixupDef{id, expectedKind, typ})
|
||||
}
|
||||
|
||||
convertMembers := func(raw []btfMember) ([]Member, error) {
|
||||
convertMembers := func(raw []btfMember, kindFlag bool) ([]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))
|
||||
|
@ -436,10 +507,15 @@ func inflateRawTypes(rawTypes []rawType, rawStrings stringTable) (namedTypes map
|
|||
if err != nil {
|
||||
return nil, fmt.Errorf("can't get name for member %d: %w", i, err)
|
||||
}
|
||||
members = append(members, Member{
|
||||
m := Member{
|
||||
Name: name,
|
||||
Offset: btfMember.Offset,
|
||||
})
|
||||
}
|
||||
if kindFlag {
|
||||
m.BitfieldSize = btfMember.Offset >> 24
|
||||
m.Offset &= 0xffffff
|
||||
}
|
||||
members = append(members, m)
|
||||
}
|
||||
for i := range members {
|
||||
fixup(raw[i].Type, kindUnknown, &members[i].Type)
|
||||
|
@ -449,7 +525,7 @@ func inflateRawTypes(rawTypes []rawType, rawStrings stringTable) (namedTypes map
|
|||
|
||||
types := make([]Type, 0, len(rawTypes))
|
||||
types = append(types, (*Void)(nil))
|
||||
namedTypes = make(map[string][]Type)
|
||||
namedTypes = make(map[string][]namedType)
|
||||
|
||||
for i, raw := range rawTypes {
|
||||
var (
|
||||
|
@ -466,7 +542,8 @@ func inflateRawTypes(rawTypes []rawType, rawStrings stringTable) (namedTypes map
|
|||
|
||||
switch raw.Kind() {
|
||||
case kindInt:
|
||||
typ = &Int{id, name, raw.Size()}
|
||||
encoding, offset, bits := intEncoding(*raw.data.(*uint32))
|
||||
typ = &Int{id, name, raw.Size(), encoding, offset, bits}
|
||||
|
||||
case kindPointer:
|
||||
ptr := &Pointer{id, nil}
|
||||
|
@ -483,21 +560,33 @@ func inflateRawTypes(rawTypes []rawType, rawStrings stringTable) (namedTypes map
|
|||
typ = arr
|
||||
|
||||
case kindStruct:
|
||||
members, err := convertMembers(raw.data.([]btfMember))
|
||||
members, err := convertMembers(raw.data.([]btfMember), raw.KindFlag())
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("struct %s (id %d): %w", name, id, err)
|
||||
}
|
||||
typ = &Struct{id, name, raw.Size(), members}
|
||||
|
||||
case kindUnion:
|
||||
members, err := convertMembers(raw.data.([]btfMember))
|
||||
members, err := convertMembers(raw.data.([]btfMember), raw.KindFlag())
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("union %s (id %d): %w", name, id, err)
|
||||
}
|
||||
typ = &Union{id, name, raw.Size(), members}
|
||||
|
||||
case kindEnum:
|
||||
typ = &Enum{id, name}
|
||||
rawvals := raw.data.([]btfEnum)
|
||||
vals := make([]EnumValue, 0, len(rawvals))
|
||||
for i, btfVal := range rawvals {
|
||||
name, err := rawStrings.LookupName(btfVal.NameOff)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("can't get name for enum value %d: %s", i, err)
|
||||
}
|
||||
vals = append(vals, EnumValue{
|
||||
Name: name,
|
||||
Value: btfVal.Val,
|
||||
})
|
||||
}
|
||||
typ = &Enum{id, name, vals}
|
||||
|
||||
case kindForward:
|
||||
typ = &Fwd{id, name}
|
||||
|
@ -528,7 +617,22 @@ func inflateRawTypes(rawTypes []rawType, rawStrings stringTable) (namedTypes map
|
|||
typ = fn
|
||||
|
||||
case kindFuncProto:
|
||||
fp := &FuncProto{id, nil}
|
||||
rawparams := raw.data.([]btfParam)
|
||||
params := make([]FuncParam, 0, len(rawparams))
|
||||
for i, param := range rawparams {
|
||||
name, err := rawStrings.LookupName(param.NameOff)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("can't get name for func proto parameter %d: %s", i, err)
|
||||
}
|
||||
params = append(params, FuncParam{
|
||||
Name: name,
|
||||
})
|
||||
}
|
||||
for i := range params {
|
||||
fixup(rawparams[i].Type, kindUnknown, ¶ms[i].Type)
|
||||
}
|
||||
|
||||
fp := &FuncProto{id, nil, params}
|
||||
fixup(raw.Type(), kindUnknown, &fp.Return)
|
||||
typ = fp
|
||||
|
||||
|
@ -557,9 +661,9 @@ func inflateRawTypes(rawTypes []rawType, rawStrings stringTable) (namedTypes map
|
|||
|
||||
types = append(types, typ)
|
||||
|
||||
if namer, ok := typ.(namer); ok {
|
||||
if name := namer.name(); name != "" {
|
||||
namedTypes[name] = append(namedTypes[name], typ)
|
||||
if named, ok := typ.(namedType); ok {
|
||||
if name := essentialName(named.name()); name != "" {
|
||||
namedTypes[name] = append(namedTypes[name], named)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -585,3 +689,12 @@ func inflateRawTypes(rawTypes []rawType, rawStrings stringTable) (namedTypes map
|
|||
|
||||
return namedTypes, nil
|
||||
}
|
||||
|
||||
// essentialName returns name without a ___ suffix.
|
||||
func essentialName(name string) string {
|
||||
lastIdx := strings.LastIndex(name, "___")
|
||||
if lastIdx > 0 {
|
||||
return name[:lastIdx]
|
||||
}
|
||||
return name
|
||||
}
|
||||
|
|
|
@ -20,6 +20,9 @@ type UnsupportedFeatureError struct {
|
|||
}
|
||||
|
||||
func (ufe *UnsupportedFeatureError) Error() string {
|
||||
if ufe.MinimumVersion.Unspecified() {
|
||||
return fmt.Sprintf("%s not supported", ufe.Name)
|
||||
}
|
||||
return fmt.Sprintf("%s not supported (requires >= %s)", ufe.Name, ufe.MinimumVersion)
|
||||
}
|
||||
|
||||
|
@ -120,3 +123,8 @@ func (v Version) Less(other Version) bool {
|
|||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// Unspecified returns true if the version is all zero.
|
||||
func (v Version) Unspecified() bool {
|
||||
return v[0] == 0 && v[1] == 0 && v[2] == 0
|
||||
}
|
||||
|
|
|
@ -137,3 +137,30 @@ func BPFObjGet(fileName string) (*FD, error) {
|
|||
}
|
||||
return NewFD(uint32(ptr)), nil
|
||||
}
|
||||
|
||||
type bpfObjGetInfoByFDAttr struct {
|
||||
fd uint32
|
||||
infoLen uint32
|
||||
info Pointer
|
||||
}
|
||||
|
||||
// BPFObjGetInfoByFD wraps BPF_OBJ_GET_INFO_BY_FD.
|
||||
//
|
||||
// Available from 4.13.
|
||||
func BPFObjGetInfoByFD(fd *FD, info unsafe.Pointer, size uintptr) error {
|
||||
value, err := fd.Value()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
attr := bpfObjGetInfoByFDAttr{
|
||||
fd: value,
|
||||
infoLen: uint32(size),
|
||||
info: NewPointer(info),
|
||||
}
|
||||
_, err = BPF(BPF_OBJ_GET_INFO_BY_FD, unsafe.Pointer(&attr), unsafe.Sizeof(attr))
|
||||
if err != nil {
|
||||
return fmt.Errorf("fd %v: %w", fd, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
package unix
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"syscall"
|
||||
|
||||
linux "golang.org/x/sys/unix"
|
||||
|
@ -19,6 +20,7 @@ const (
|
|||
EPERM = linux.EPERM
|
||||
ESRCH = linux.ESRCH
|
||||
ENODEV = linux.ENODEV
|
||||
BPF_F_NUMA_NODE = linux.BPF_F_NUMA_NODE
|
||||
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
|
||||
|
@ -148,3 +150,15 @@ func Gettid() int {
|
|||
func Tgkill(tgid int, tid int, sig syscall.Signal) (err error) {
|
||||
return linux.Tgkill(tgid, tid, sig)
|
||||
}
|
||||
|
||||
func KernelRelease() (string, error) {
|
||||
var uname Utsname
|
||||
err := Uname(&uname)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
end := bytes.IndexByte(uname.Release[:], 0)
|
||||
release := string(uname.Release[:end])
|
||||
return release, nil
|
||||
}
|
||||
|
|
|
@ -20,6 +20,7 @@ const (
|
|||
EPERM = syscall.EPERM
|
||||
ESRCH = syscall.ESRCH
|
||||
ENODEV = syscall.ENODEV
|
||||
BPF_F_NUMA_NODE = 0
|
||||
BPF_F_RDONLY_PROG = 0
|
||||
BPF_F_WRONLY_PROG = 0
|
||||
BPF_OBJ_NAME_LEN = 0x10
|
||||
|
@ -215,3 +216,7 @@ func Gettid() int {
|
|||
func Tgkill(tgid int, tid int, sig syscall.Signal) (err error) {
|
||||
return errNonLinux
|
||||
}
|
||||
|
||||
func KernelRelease() (string, error) {
|
||||
return "", errNonLinux
|
||||
}
|
||||
|
|
|
@ -84,3 +84,50 @@ func needSection(insns, section asm.Instructions) (bool, error) {
|
|||
// None of the functions in the section are called.
|
||||
return false, nil
|
||||
}
|
||||
|
||||
func fixupJumpsAndCalls(insns asm.Instructions) error {
|
||||
symbolOffsets := make(map[string]asm.RawInstructionOffset)
|
||||
iter := insns.Iterate()
|
||||
for iter.Next() {
|
||||
ins := iter.Ins
|
||||
|
||||
if ins.Symbol == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
if _, ok := symbolOffsets[ins.Symbol]; ok {
|
||||
return fmt.Errorf("duplicate symbol %s", ins.Symbol)
|
||||
}
|
||||
|
||||
symbolOffsets[ins.Symbol] = iter.Offset
|
||||
}
|
||||
|
||||
iter = insns.Iterate()
|
||||
for iter.Next() {
|
||||
i := iter.Index
|
||||
offset := iter.Offset
|
||||
ins := iter.Ins
|
||||
|
||||
switch {
|
||||
case ins.IsFunctionCall() && ins.Constant == -1:
|
||||
// Rewrite bpf to bpf call
|
||||
callOffset, ok := symbolOffsets[ins.Reference]
|
||||
if !ok {
|
||||
return fmt.Errorf("instruction %d: reference to missing symbol %s", i, ins.Reference)
|
||||
}
|
||||
|
||||
ins.Constant = int64(callOffset - offset - 1)
|
||||
|
||||
case ins.OpCode.Class() == asm.JumpClass && ins.Offset == -1:
|
||||
// Rewrite jump to label
|
||||
jumpOffset, ok := symbolOffsets[ins.Reference]
|
||||
if !ok {
|
||||
return fmt.Errorf("instruction %d: reference to missing symbol %s", i, ins.Reference)
|
||||
}
|
||||
|
||||
ins.Offset = int16(jumpOffset - offset - 1)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -3,6 +3,8 @@ package ebpf
|
|||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/cilium/ebpf/internal"
|
||||
|
@ -17,6 +19,14 @@ var (
|
|||
ErrIterationAborted = errors.New("iteration aborted")
|
||||
)
|
||||
|
||||
// MapOptions control loading a map into the kernel.
|
||||
type MapOptions struct {
|
||||
// The base path to pin maps in if requested via PinByName.
|
||||
// Existing maps will be re-used if they are compatible, otherwise an
|
||||
// error is returned.
|
||||
PinPath string
|
||||
}
|
||||
|
||||
// MapID represents the unique ID of an eBPF map
|
||||
type MapID uint32
|
||||
|
||||
|
@ -31,6 +41,15 @@ type MapSpec struct {
|
|||
MaxEntries uint32
|
||||
Flags uint32
|
||||
|
||||
// Automatically pin and load a map from MapOptions.PinPath.
|
||||
// Generates an error if an existing pinned map is incompatible with the MapSpec.
|
||||
Pinning PinType
|
||||
|
||||
// Specify numa node during map creation
|
||||
// (effective only if unix.BPF_F_NUMA_NODE flag is set,
|
||||
// which can be imported from golang.org/x/sys/unix)
|
||||
NumaNode uint32
|
||||
|
||||
// The initial contents of the map. May be nil.
|
||||
Contents []MapKV
|
||||
|
||||
|
@ -69,6 +88,26 @@ type MapKV struct {
|
|||
Value interface{}
|
||||
}
|
||||
|
||||
func (ms *MapSpec) checkCompatibility(m *Map) error {
|
||||
switch {
|
||||
case m.abi.Type != ms.Type:
|
||||
return fmt.Errorf("expected type %v, got %v", ms.Type, m.abi.Type)
|
||||
|
||||
case m.abi.KeySize != ms.KeySize:
|
||||
return fmt.Errorf("expected key size %v, got %v", ms.KeySize, m.abi.KeySize)
|
||||
|
||||
case m.abi.ValueSize != ms.ValueSize:
|
||||
return fmt.Errorf("expected value size %v, got %v", ms.ValueSize, m.abi.ValueSize)
|
||||
|
||||
case m.abi.MaxEntries != ms.MaxEntries:
|
||||
return fmt.Errorf("expected max entries %v, got %v", ms.MaxEntries, m.abi.MaxEntries)
|
||||
|
||||
case m.abi.Flags != ms.Flags:
|
||||
return fmt.Errorf("expected flags %v, got %v", ms.Flags, m.abi.Flags)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Map represents a Map file descriptor.
|
||||
//
|
||||
// It is not safe to close a map which is used by other goroutines.
|
||||
|
@ -105,15 +144,22 @@ func NewMapFromFD(fd int) (*Map, error) {
|
|||
|
||||
// NewMap creates a new Map.
|
||||
//
|
||||
// It's equivalent to calling NewMapWithOptions with default options.
|
||||
func NewMap(spec *MapSpec) (*Map, error) {
|
||||
return NewMapWithOptions(spec, MapOptions{})
|
||||
}
|
||||
|
||||
// NewMapWithOptions creates a new Map.
|
||||
//
|
||||
// Creating a map for the first time will perform feature detection
|
||||
// by creating small, temporary maps.
|
||||
//
|
||||
// The caller is responsible for ensuring the process' rlimit is set
|
||||
// sufficiently high for locking memory during map creation. This can be done
|
||||
// by calling unix.Setrlimit with unix.RLIMIT_MEMLOCK prior to calling NewMap.
|
||||
func NewMap(spec *MapSpec) (*Map, error) {
|
||||
// by calling unix.Setrlimit with unix.RLIMIT_MEMLOCK prior to calling NewMapWithOptions.
|
||||
func NewMapWithOptions(spec *MapSpec, opts MapOptions) (*Map, error) {
|
||||
if spec.BTF == nil {
|
||||
return newMapWithBTF(spec, nil)
|
||||
return newMapWithBTF(spec, nil, opts)
|
||||
}
|
||||
|
||||
handle, err := btf.NewHandle(btf.MapSpec(spec.BTF))
|
||||
|
@ -121,28 +167,75 @@ func NewMap(spec *MapSpec) (*Map, error) {
|
|||
return nil, fmt.Errorf("can't load BTF: %w", err)
|
||||
}
|
||||
|
||||
return newMapWithBTF(spec, handle)
|
||||
return newMapWithBTF(spec, handle, opts)
|
||||
}
|
||||
|
||||
func newMapWithBTF(spec *MapSpec, handle *btf.Handle) (*Map, error) {
|
||||
if spec.Type != ArrayOfMaps && spec.Type != HashOfMaps {
|
||||
return createMap(spec, nil, handle)
|
||||
func newMapWithBTF(spec *MapSpec, handle *btf.Handle, opts MapOptions) (*Map, error) {
|
||||
switch spec.Pinning {
|
||||
case PinByName:
|
||||
if spec.Name == "" || opts.PinPath == "" {
|
||||
return nil, fmt.Errorf("pin by name: missing Name or PinPath")
|
||||
}
|
||||
|
||||
m, err := LoadPinnedMap(filepath.Join(opts.PinPath, spec.Name))
|
||||
if errors.Is(err, unix.ENOENT) {
|
||||
break
|
||||
}
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("load pinned map: %s", err)
|
||||
}
|
||||
|
||||
if err := spec.checkCompatibility(m); err != nil {
|
||||
m.Close()
|
||||
return nil, fmt.Errorf("use pinned map %s: %s", spec.Name, err)
|
||||
}
|
||||
|
||||
return m, nil
|
||||
|
||||
case PinNone:
|
||||
// Nothing to do here
|
||||
|
||||
default:
|
||||
return nil, fmt.Errorf("unsupported pin type %d", int(spec.Pinning))
|
||||
}
|
||||
|
||||
if spec.InnerMap == nil {
|
||||
return nil, fmt.Errorf("%s requires InnerMap", spec.Type)
|
||||
var innerFd *internal.FD
|
||||
if spec.Type == ArrayOfMaps || spec.Type == HashOfMaps {
|
||||
if spec.InnerMap == nil {
|
||||
return nil, fmt.Errorf("%s requires InnerMap", spec.Type)
|
||||
}
|
||||
|
||||
template, err := createMap(spec.InnerMap, nil, handle, opts)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer template.Close()
|
||||
|
||||
innerFd = template.fd
|
||||
}
|
||||
|
||||
template, err := createMap(spec.InnerMap, nil, handle)
|
||||
m, err := createMap(spec, innerFd, handle, opts)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer template.Close()
|
||||
|
||||
return createMap(spec, template.fd, handle)
|
||||
if spec.Pinning == PinByName {
|
||||
if err := m.Pin(filepath.Join(opts.PinPath, spec.Name)); err != nil {
|
||||
m.Close()
|
||||
return nil, fmt.Errorf("pin map: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
return m, nil
|
||||
}
|
||||
|
||||
func createMap(spec *MapSpec, inner *internal.FD, handle *btf.Handle) (*Map, error) {
|
||||
func createMap(spec *MapSpec, inner *internal.FD, handle *btf.Handle, opts MapOptions) (_ *Map, err error) {
|
||||
closeOnError := func(closer io.Closer) {
|
||||
if err != nil {
|
||||
closer.Close()
|
||||
}
|
||||
}
|
||||
|
||||
abi := newMapABIFromSpec(spec)
|
||||
|
||||
switch spec.Type {
|
||||
|
@ -190,6 +283,7 @@ func createMap(spec *MapSpec, inner *internal.FD, handle *btf.Handle) (*Map, err
|
|||
valueSize: abi.ValueSize,
|
||||
maxEntries: abi.MaxEntries,
|
||||
flags: abi.Flags,
|
||||
numaNode: spec.NumaNode,
|
||||
}
|
||||
|
||||
if inner != nil {
|
||||
|
@ -214,6 +308,7 @@ func createMap(spec *MapSpec, inner *internal.FD, handle *btf.Handle) (*Map, err
|
|||
if err != nil {
|
||||
return nil, fmt.Errorf("map create: %w", err)
|
||||
}
|
||||
defer closeOnError(fd)
|
||||
|
||||
m, err := newMap(fd, spec.Name, abi)
|
||||
if err != nil {
|
||||
|
@ -221,13 +316,11 @@ func createMap(spec *MapSpec, inner *internal.FD, handle *btf.Handle) (*Map, err
|
|||
}
|
||||
|
||||
if err := m.populate(spec.Contents); err != nil {
|
||||
m.Close()
|
||||
return nil, fmt.Errorf("map create: can't set initial contents: %w", err)
|
||||
}
|
||||
|
||||
if spec.Freeze {
|
||||
if err := m.Freeze(); err != nil {
|
||||
m.Close()
|
||||
return nil, fmt.Errorf("can't freeze map: %w", err)
|
||||
}
|
||||
}
|
||||
|
@ -263,7 +356,34 @@ func (m *Map) String() string {
|
|||
return fmt.Sprintf("%s#%v", m.abi.Type, m.fd)
|
||||
}
|
||||
|
||||
// ABI gets the ABI of the Map
|
||||
// Type returns the underlying type of the map.
|
||||
func (m *Map) Type() MapType {
|
||||
return m.abi.Type
|
||||
}
|
||||
|
||||
// KeySize returns the size of the map key in bytes.
|
||||
func (m *Map) KeySize() uint32 {
|
||||
return m.abi.KeySize
|
||||
}
|
||||
|
||||
// ValueSize returns the size of the map value in bytes.
|
||||
func (m *Map) ValueSize() uint32 {
|
||||
return m.abi.ValueSize
|
||||
}
|
||||
|
||||
// MaxEntries returns the maximum number of elements the map can hold.
|
||||
func (m *Map) MaxEntries() uint32 {
|
||||
return m.abi.MaxEntries
|
||||
}
|
||||
|
||||
// Flags returns the flags of the map.
|
||||
func (m *Map) Flags() uint32 {
|
||||
return m.abi.Flags
|
||||
}
|
||||
|
||||
// ABI gets the ABI of the Map.
|
||||
//
|
||||
// Deprecated: use Type, KeySize, ValueSize, MaxEntries and Flags instead.
|
||||
func (m *Map) ABI() MapABI {
|
||||
return m.abi
|
||||
}
|
||||
|
@ -544,7 +664,7 @@ func (m *Map) Clone() (*Map, error) {
|
|||
|
||||
// Pin persists the map past the lifetime of the process that created it.
|
||||
//
|
||||
// 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 https://docs.cilium.io/en/k8s-doc/admin/#admin-mount-bpffs
|
||||
func (m *Map) Pin(fileName string) error {
|
||||
return internal.BPFObjPin(fileName, m.fd)
|
||||
}
|
||||
|
@ -789,6 +909,8 @@ func NewMapFromID(id MapID) (*Map, error) {
|
|||
}
|
||||
|
||||
// ID returns the systemwide unique ID of the map.
|
||||
//
|
||||
// Requires at least Linux 4.13.
|
||||
func (m *Map) ID() (MapID, error) {
|
||||
info, err := bpfGetMapInfoByFD(m.fd)
|
||||
if err != nil {
|
||||
|
|
|
@ -208,8 +208,15 @@ func convertProgramSpec(spec *ProgramSpec, handle *btf.Handle) (*bpfProgLoadAttr
|
|||
return nil, fmt.Errorf("can't load %s program on %s", spec.ByteOrder, internal.NativeEndian)
|
||||
}
|
||||
|
||||
insns := make(asm.Instructions, len(spec.Instructions))
|
||||
copy(insns, spec.Instructions)
|
||||
|
||||
if err := fixupJumpsAndCalls(insns); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
buf := bytes.NewBuffer(make([]byte, 0, len(spec.Instructions)*asm.InstructionSize))
|
||||
err := spec.Instructions.Marshal(buf, internal.NativeEndian)
|
||||
err := insns.Marshal(buf, internal.NativeEndian)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -269,7 +276,14 @@ func (p *Program) String() string {
|
|||
return fmt.Sprintf("%s#%v", p.abi.Type, p.fd)
|
||||
}
|
||||
|
||||
// ABI gets the ABI of the Program
|
||||
// Type returns the underlying type of the program.
|
||||
func (p *Program) Type() ProgramType {
|
||||
return p.abi.Type
|
||||
}
|
||||
|
||||
// ABI gets the ABI of the Program.
|
||||
//
|
||||
// Deprecated: use Type instead.
|
||||
func (p *Program) ABI() ProgramABI {
|
||||
return p.abi
|
||||
}
|
||||
|
@ -308,7 +322,7 @@ func (p *Program) Clone() (*Program, error) {
|
|||
|
||||
// Pin persists the Program past the lifetime of the process that created it
|
||||
//
|
||||
// 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 https://docs.cilium.io/en/k8s-doc/admin/#admin-mount-bpffs
|
||||
func (p *Program) Pin(fileName string) error {
|
||||
if err := internal.BPFObjPin(fileName, p.fd); err != nil {
|
||||
return fmt.Errorf("can't pin program: %w", err)
|
||||
|
@ -595,12 +609,16 @@ func (p *Program) ID() (ProgramID, error) {
|
|||
return ProgramID(info.id), nil
|
||||
}
|
||||
|
||||
func resolveBTFType(name string, progType ProgramType, attachType AttachType) (btf.Type, error) {
|
||||
func findKernelType(name string, typ btf.Type) error {
|
||||
kernel, err := btf.LoadKernelSpec()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("can't resolve BTF type %s: %w", name, err)
|
||||
return fmt.Errorf("can't load kernel spec: %w", err)
|
||||
}
|
||||
|
||||
return kernel.FindType(name, typ)
|
||||
}
|
||||
|
||||
func resolveBTFType(name string, progType ProgramType, attachType AttachType) (btf.Type, error) {
|
||||
type match struct {
|
||||
p ProgramType
|
||||
a AttachType
|
||||
|
@ -608,10 +626,30 @@ func resolveBTFType(name string, progType ProgramType, attachType AttachType) (b
|
|||
|
||||
target := match{progType, attachType}
|
||||
switch target {
|
||||
case match{LSM, AttachLSMMac}:
|
||||
var target btf.Func
|
||||
err := findKernelType("bpf_lsm_"+name, &target)
|
||||
if errors.Is(err, btf.ErrNotFound) {
|
||||
return nil, &internal.UnsupportedFeatureError{
|
||||
Name: name + " LSM hook",
|
||||
}
|
||||
}
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("resolve BTF for LSM hook %s: %w", name, err)
|
||||
}
|
||||
|
||||
return &target, nil
|
||||
|
||||
case match{Tracing, AttachTraceIter}:
|
||||
var target btf.Func
|
||||
if err := kernel.FindType("bpf_iter_"+name, &target); err != nil {
|
||||
return nil, fmt.Errorf("can't resolve BTF for iterator %s: %w", name, err)
|
||||
err := findKernelType("bpf_iter_"+name, &target)
|
||||
if errors.Is(err, btf.ErrNotFound) {
|
||||
return nil, &internal.UnsupportedFeatureError{
|
||||
Name: name + " iterator",
|
||||
}
|
||||
}
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("resolve BTF for iterator %s: %w", name, err)
|
||||
}
|
||||
|
||||
return &target, nil
|
||||
|
|
|
@ -1,10 +1,13 @@
|
|||
eBPF
|
||||
-------
|
||||
[![](https://godoc.org/github.com/cilium/ebpf?status.svg)](https://godoc.org/github.com/cilium/ebpf)
|
||||
[![PkgGoDev](https://pkg.go.dev/badge/github.com/cilium/ebpf)](https://pkg.go.dev/github.com/cilium/ebpf)
|
||||
|
||||
eBPF is a pure Go library that provides utilities for loading, compiling, and debugging eBPF programs. It has minimal external dependencies and is intended to be used in long running processes.
|
||||
|
||||
[ebpf/asm](https://godoc.org/github.com/cilium/ebpf/asm) contains a basic assembler.
|
||||
* [asm](https://pkg.go.dev/github.com/cilium/ebpf/asm) contains a basic assembler.
|
||||
* [link](https://pkg.go.dev/github.com/cilium/ebpf/link) allows attaching eBPF to various hooks.
|
||||
* [perf](https://pkg.go.dev/github.com/cilium/ebpf/perf) allows reading from a PERF_EVENT_ARRAY.
|
||||
* [cmd/bpf2go](https://pkg.go.dev/github.com/cilium/ebpf/cmd/bpf2go) allows embedding eBPF in Go.
|
||||
|
||||
The library is maintained by [Cloudflare](https://www.cloudflare.com) and [Cilium](https://www.cilium.io). Feel free to [join](https://cilium.herokuapp.com/) the [libbpf-go](https://cilium.slack.com/messages/libbpf-go) channel on Slack.
|
||||
|
||||
|
@ -20,6 +23,7 @@ right now**. Expect to update your code if you want to follow along.
|
|||
|
||||
## Useful resources
|
||||
|
||||
* [Cilium eBPF documentation](https://cilium.readthedocs.io/en/latest/bpf/#bpf-guide) (recommended)
|
||||
* [eBPF.io](https://ebpf.io) (recommended)
|
||||
* [Cilium eBPF documentation](https://docs.cilium.io/en/latest/bpf/#bpf-guide) (recommended)
|
||||
* [Linux documentation on BPF](http://elixir.free-electrons.com/linux/latest/source/Documentation/networking/filter.txt)
|
||||
* [eBPF features by Linux version](https://github.com/iovisor/bcc/blob/master/docs/kernel-versions.md)
|
||||
|
|
|
@ -12,7 +12,8 @@ if [[ "${1:-}" = "--in-vm" ]]; then
|
|||
export CGO_ENABLED=0
|
||||
export GOFLAGS=-mod=readonly
|
||||
export GOPATH=/run/go-path
|
||||
export GOPROXY=file:///run/go-root/pkg/mod/cache/download
|
||||
export GOPROXY=file:///run/go-path/pkg/mod/cache/download
|
||||
export GOSUMDB=off
|
||||
export GOCACHE=/run/go-cache
|
||||
|
||||
elfs=""
|
||||
|
@ -21,7 +22,9 @@ if [[ "${1:-}" = "--in-vm" ]]; then
|
|||
fi
|
||||
|
||||
echo Running tests...
|
||||
/usr/local/bin/go test -coverprofile="$1/coverage.txt" -covermode=atomic -v -elfs "$elfs" ./...
|
||||
# TestLibBPFCompat runs separately to pass the "-elfs" flag only for it: https://github.com/cilium/ebpf/pull/119
|
||||
go test -v -count 1 -run TestLibBPFCompat -elfs "$elfs"
|
||||
go test -v -count 1 ./...
|
||||
touch "$1/success"
|
||||
exit 0
|
||||
fi
|
||||
|
@ -66,11 +69,12 @@ fi
|
|||
|
||||
echo Testing on "${kernel_version}"
|
||||
$sudo virtme-run --kimg "${tmp_dir}/${kernel}" --memory 512M --pwd \
|
||||
--rw \
|
||||
--rwdir=/run/input="${input}" \
|
||||
--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" \
|
||||
--script-sh "PATH=\"$PATH\" $(realpath "$0") --in-vm /run/output" \
|
||||
--qemu-opts -smp 2 # need at least two CPUs for some tests
|
||||
|
||||
if [[ ! -e "${output}/success" ]]; then
|
||||
|
@ -78,11 +82,11 @@ if [[ ! -e "${output}/success" ]]; then
|
|||
exit 1
|
||||
else
|
||||
echo "Test successful on ${kernel_version}"
|
||||
if [[ -v CODECOV_TOKEN ]]; then
|
||||
curl --fail -s https://codecov.io/bash > "${tmp_dir}/codecov.sh"
|
||||
chmod +x "${tmp_dir}/codecov.sh"
|
||||
"${tmp_dir}/codecov.sh" -f "${output}/coverage.txt"
|
||||
fi
|
||||
# if [[ -v CODECOV_TOKEN ]]; then
|
||||
# curl --fail -s https://codecov.io/bash > "${tmp_dir}/codecov.sh"
|
||||
# chmod +x "${tmp_dir}/codecov.sh"
|
||||
# "${tmp_dir}/codecov.sh" -f "${output}/coverage.txt"
|
||||
# fi
|
||||
fi
|
||||
|
||||
$sudo rm -r "${input}"
|
||||
|
|
|
@ -129,12 +129,6 @@ type bpfProgTestRunAttr struct {
|
|||
duration uint32
|
||||
}
|
||||
|
||||
type bpfObjGetInfoByFDAttr struct {
|
||||
fd uint32
|
||||
infoLen uint32
|
||||
info internal.Pointer // May be either bpfMapInfo or bpfProgInfo
|
||||
}
|
||||
|
||||
type bpfGetFDByIDAttr struct {
|
||||
id uint32
|
||||
next uint32
|
||||
|
@ -353,28 +347,9 @@ func bpfMapFreeze(m *internal.FD) error {
|
|||
return err
|
||||
}
|
||||
|
||||
func bpfGetObjectInfoByFD(fd *internal.FD, info unsafe.Pointer, size uintptr) error {
|
||||
value, err := fd.Value()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// available from 4.13
|
||||
attr := bpfObjGetInfoByFDAttr{
|
||||
fd: value,
|
||||
infoLen: uint32(size),
|
||||
info: internal.NewPointer(info),
|
||||
}
|
||||
_, err = internal.BPF(internal.BPF_OBJ_GET_INFO_BY_FD, unsafe.Pointer(&attr), unsafe.Sizeof(attr))
|
||||
if err != nil {
|
||||
return fmt.Errorf("fd %d: %w", fd, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func bpfGetProgInfoByFD(fd *internal.FD) (*bpfProgInfo, error) {
|
||||
var info bpfProgInfo
|
||||
if err := bpfGetObjectInfoByFD(fd, unsafe.Pointer(&info), unsafe.Sizeof(info)); err != nil {
|
||||
if err := internal.BPFObjGetInfoByFD(fd, unsafe.Pointer(&info), unsafe.Sizeof(info)); err != nil {
|
||||
return nil, fmt.Errorf("can't get program info: %w", err)
|
||||
}
|
||||
return &info, nil
|
||||
|
@ -382,7 +357,7 @@ func bpfGetProgInfoByFD(fd *internal.FD) (*bpfProgInfo, error) {
|
|||
|
||||
func bpfGetMapInfoByFD(fd *internal.FD) (*bpfMapInfo, error) {
|
||||
var info bpfMapInfo
|
||||
err := bpfGetObjectInfoByFD(fd, unsafe.Pointer(&info), unsafe.Sizeof(info))
|
||||
err := internal.BPFObjGetInfoByFD(fd, unsafe.Pointer(&info), unsafe.Sizeof(info))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("can't get map info: %w", err)
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
package ebpf
|
||||
|
||||
//go:generate stringer -output types_string.go -type=MapType,ProgramType,AttachType
|
||||
//go:generate stringer -output types_string.go -type=MapType,ProgramType,AttachType,PinType
|
||||
|
||||
// MapType indicates the type map structure
|
||||
// that will be initialized in the kernel.
|
||||
|
@ -96,60 +96,37 @@ type ProgramType uint32
|
|||
|
||||
// eBPF program types
|
||||
const (
|
||||
// Unrecognized program type
|
||||
UnspecifiedProgram ProgramType = iota
|
||||
// SocketFilter socket or seccomp filter
|
||||
SocketFilter
|
||||
// Kprobe program
|
||||
Kprobe
|
||||
// SchedCLS traffic control shaper
|
||||
SchedCLS
|
||||
// SchedACT routing control shaper
|
||||
SchedACT
|
||||
// TracePoint program
|
||||
TracePoint
|
||||
// XDP program
|
||||
XDP
|
||||
// PerfEvent program
|
||||
PerfEvent
|
||||
// CGroupSKB program
|
||||
CGroupSKB
|
||||
// CGroupSock program
|
||||
CGroupSock
|
||||
// LWTIn program
|
||||
LWTIn
|
||||
// LWTOut program
|
||||
LWTOut
|
||||
// LWTXmit program
|
||||
LWTXmit
|
||||
// SockOps program
|
||||
SockOps
|
||||
// SkSKB program
|
||||
SkSKB
|
||||
// CGroupDevice program
|
||||
CGroupDevice
|
||||
// SkMsg program
|
||||
SkMsg
|
||||
// RawTracepoint program
|
||||
RawTracepoint
|
||||
// CGroupSockAddr program
|
||||
CGroupSockAddr
|
||||
// LWTSeg6Local program
|
||||
LWTSeg6Local
|
||||
// LircMode2 program
|
||||
LircMode2
|
||||
// SkReuseport program
|
||||
SkReuseport
|
||||
// FlowDissector program
|
||||
FlowDissector
|
||||
// CGroupSysctl program
|
||||
CGroupSysctl
|
||||
// RawTracepointWritable program
|
||||
RawTracepointWritable
|
||||
// CGroupSockopt program
|
||||
CGroupSockopt
|
||||
// Tracing program
|
||||
Tracing
|
||||
StructOps
|
||||
Extension
|
||||
LSM
|
||||
SkLookup
|
||||
)
|
||||
|
||||
// AttachType of the eBPF program, needed to differentiate allowed context accesses in
|
||||
|
@ -190,7 +167,28 @@ const (
|
|||
AttachModifyReturn
|
||||
AttachLSMMac
|
||||
AttachTraceIter
|
||||
AttachCgroupInet4GetPeername
|
||||
AttachCgroupInet6GetPeername
|
||||
AttachCgroupInet4GetSockname
|
||||
AttachCgroupInet6GetSockname
|
||||
AttachXDPDevMap
|
||||
AttachCgroupInetSockRelease
|
||||
AttachXDPCPUMap
|
||||
AttachSkLookup
|
||||
AttachXDP
|
||||
)
|
||||
|
||||
// AttachFlags of the eBPF program used in BPF_PROG_ATTACH command
|
||||
type AttachFlags uint32
|
||||
|
||||
// PinType determines whether a map is pinned into a BPFFS.
|
||||
type PinType int
|
||||
|
||||
// Valid pin types.
|
||||
//
|
||||
// Mirrors enum libbpf_pin_type.
|
||||
const (
|
||||
PinNone PinType = iota
|
||||
// Pin an object by using its name as the filename.
|
||||
PinByName
|
||||
)
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
// Code generated by "stringer -output types_string.go -type=MapType,ProgramType,AttachType"; DO NOT EDIT.
|
||||
// Code generated by "stringer -output types_string.go -type=MapType,ProgramType,AttachType,PinType"; DO NOT EDIT.
|
||||
|
||||
package ebpf
|
||||
|
||||
|
@ -77,11 +77,15 @@ func _() {
|
|||
_ = x[RawTracepointWritable-24]
|
||||
_ = x[CGroupSockopt-25]
|
||||
_ = x[Tracing-26]
|
||||
_ = x[StructOps-27]
|
||||
_ = x[Extension-28]
|
||||
_ = x[LSM-29]
|
||||
_ = x[SkLookup-30]
|
||||
}
|
||||
|
||||
const _ProgramType_name = "UnspecifiedProgramSocketFilterKprobeSchedCLSSchedACTTracePointXDPPerfEventCGroupSKBCGroupSockLWTInLWTOutLWTXmitSockOpsSkSKBCGroupDeviceSkMsgRawTracepointCGroupSockAddrLWTSeg6LocalLircMode2SkReuseportFlowDissectorCGroupSysctlRawTracepointWritableCGroupSockoptTracing"
|
||||
const _ProgramType_name = "UnspecifiedProgramSocketFilterKprobeSchedCLSSchedACTTracePointXDPPerfEventCGroupSKBCGroupSockLWTInLWTOutLWTXmitSockOpsSkSKBCGroupDeviceSkMsgRawTracepointCGroupSockAddrLWTSeg6LocalLircMode2SkReuseportFlowDissectorCGroupSysctlRawTracepointWritableCGroupSockoptTracingStructOpsExtensionLSMSkLookup"
|
||||
|
||||
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}
|
||||
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, 274, 283, 286, 294}
|
||||
|
||||
func (i ProgramType) String() string {
|
||||
if i >= ProgramType(len(_ProgramType_index)-1) {
|
||||
|
@ -123,11 +127,20 @@ func _() {
|
|||
_ = x[AttachModifyReturn-26]
|
||||
_ = x[AttachLSMMac-27]
|
||||
_ = x[AttachTraceIter-28]
|
||||
_ = x[AttachCgroupInet4GetPeername-29]
|
||||
_ = x[AttachCgroupInet6GetPeername-30]
|
||||
_ = x[AttachCgroupInet4GetSockname-31]
|
||||
_ = x[AttachCgroupInet6GetSockname-32]
|
||||
_ = x[AttachXDPDevMap-33]
|
||||
_ = x[AttachCgroupInetSockRelease-34]
|
||||
_ = x[AttachXDPCPUMap-35]
|
||||
_ = x[AttachSkLookup-36]
|
||||
_ = x[AttachXDP-37]
|
||||
}
|
||||
|
||||
const _AttachType_name = "AttachNoneAttachCGroupInetEgressAttachCGroupInetSockCreateAttachCGroupSockOpsAttachSkSKBStreamParserAttachSkSKBStreamVerdictAttachCGroupDeviceAttachSkMsgVerdictAttachCGroupInet4BindAttachCGroupInet6BindAttachCGroupInet4ConnectAttachCGroupInet6ConnectAttachCGroupInet4PostBindAttachCGroupInet6PostBindAttachCGroupUDP4SendmsgAttachCGroupUDP6SendmsgAttachLircMode2AttachFlowDissectorAttachCGroupSysctlAttachCGroupUDP4RecvmsgAttachCGroupUDP6RecvmsgAttachCGroupGetsockoptAttachCGroupSetsockoptAttachTraceRawTpAttachTraceFEntryAttachTraceFExitAttachModifyReturnAttachLSMMacAttachTraceIter"
|
||||
const _AttachType_name = "AttachNoneAttachCGroupInetEgressAttachCGroupInetSockCreateAttachCGroupSockOpsAttachSkSKBStreamParserAttachSkSKBStreamVerdictAttachCGroupDeviceAttachSkMsgVerdictAttachCGroupInet4BindAttachCGroupInet6BindAttachCGroupInet4ConnectAttachCGroupInet6ConnectAttachCGroupInet4PostBindAttachCGroupInet6PostBindAttachCGroupUDP4SendmsgAttachCGroupUDP6SendmsgAttachLircMode2AttachFlowDissectorAttachCGroupSysctlAttachCGroupUDP4RecvmsgAttachCGroupUDP6RecvmsgAttachCGroupGetsockoptAttachCGroupSetsockoptAttachTraceRawTpAttachTraceFEntryAttachTraceFExitAttachModifyReturnAttachLSMMacAttachTraceIterAttachCgroupInet4GetPeernameAttachCgroupInet6GetPeernameAttachCgroupInet4GetSocknameAttachCgroupInet6GetSocknameAttachXDPDevMapAttachCgroupInetSockReleaseAttachXDPCPUMapAttachSkLookupAttachXDP"
|
||||
|
||||
var _AttachType_index = [...]uint16{0, 10, 32, 58, 77, 100, 124, 142, 160, 181, 202, 226, 250, 275, 300, 323, 346, 361, 380, 398, 421, 444, 466, 488, 504, 521, 537, 555, 567, 582}
|
||||
var _AttachType_index = [...]uint16{0, 10, 32, 58, 77, 100, 124, 142, 160, 181, 202, 226, 250, 275, 300, 323, 346, 361, 380, 398, 421, 444, 466, 488, 504, 521, 537, 555, 567, 582, 610, 638, 666, 694, 709, 736, 751, 765, 774}
|
||||
|
||||
func (i AttachType) String() string {
|
||||
if i >= AttachType(len(_AttachType_index)-1) {
|
||||
|
@ -135,3 +148,21 @@ func (i AttachType) String() string {
|
|||
}
|
||||
return _AttachType_name[_AttachType_index[i]:_AttachType_index[i+1]]
|
||||
}
|
||||
func _() {
|
||||
// An "invalid array index" compiler error signifies that the constant values have changed.
|
||||
// Re-run the stringer command to generate them again.
|
||||
var x [1]struct{}
|
||||
_ = x[PinNone-0]
|
||||
_ = x[PinByName-1]
|
||||
}
|
||||
|
||||
const _PinType_name = "PinNonePinByName"
|
||||
|
||||
var _PinType_index = [...]uint8{0, 7, 16}
|
||||
|
||||
func (i PinType) String() string {
|
||||
if i < 0 || i >= PinType(len(_PinType_index)-1) {
|
||||
return "PinType(" + strconv.FormatInt(int64(i), 10) + ")"
|
||||
}
|
||||
return _PinType_name[_PinType_index[i]:_PinType_index[i+1]]
|
||||
}
|
||||
|
|
|
@ -0,0 +1,20 @@
|
|||
linters:
|
||||
enable:
|
||||
- structcheck
|
||||
- varcheck
|
||||
- staticcheck
|
||||
- unconvert
|
||||
- gofmt
|
||||
- goimports
|
||||
- golint
|
||||
- ineffassign
|
||||
- vet
|
||||
- unused
|
||||
- misspell
|
||||
disable:
|
||||
- errcheck
|
||||
|
||||
run:
|
||||
timeout: 3m
|
||||
skip-dirs:
|
||||
- vendor
|
|
@ -1,27 +0,0 @@
|
|||
language: go
|
||||
go:
|
||||
- "1.12.x"
|
||||
- "1.13.x"
|
||||
|
||||
go_import_path: github.com/containerd/console
|
||||
|
||||
env:
|
||||
- GO111MODULE=on
|
||||
|
||||
install:
|
||||
- pushd ..; go get -u github.com/vbatts/git-validation; popd
|
||||
- pushd ..; go get -u github.com/kunalkushwaha/ltag; popd
|
||||
|
||||
before_script:
|
||||
- pushd ..; git clone https://github.com/containerd/project; popd
|
||||
|
||||
script:
|
||||
- DCO_VERBOSITY=-q ../project/script/validate/dco
|
||||
- ../project/script/validate/fileheader ../project/
|
||||
- travis_wait ../project/script/validate/vendor
|
||||
- go test -race
|
||||
- GOOS=openbsd go build
|
||||
- GOOS=openbsd go test -c
|
||||
- GOOS=solaris go build
|
||||
- GOOS=solaris go test -c
|
||||
- GOOS=windows go test
|
|
@ -61,18 +61,24 @@ type WinSize struct {
|
|||
y uint16
|
||||
}
|
||||
|
||||
// Current returns the current processes console
|
||||
func Current() Console {
|
||||
c, err := ConsoleFromFile(os.Stdin)
|
||||
if err != nil {
|
||||
// stdin should always be a console for the design
|
||||
// of this function
|
||||
panic(err)
|
||||
// Current returns the current process' console
|
||||
func Current() (c Console) {
|
||||
var err error
|
||||
// Usually all three streams (stdin, stdout, and stderr)
|
||||
// are open to the same console, but some might be redirected,
|
||||
// so try all three.
|
||||
for _, s := range []*os.File{os.Stderr, os.Stdout, os.Stdin} {
|
||||
if c, err = ConsoleFromFile(s); err == nil {
|
||||
return c
|
||||
}
|
||||
}
|
||||
return c
|
||||
// One of the std streams should always be a console
|
||||
// for the design of this function.
|
||||
panic(err)
|
||||
}
|
||||
|
||||
// ConsoleFromFile returns a console using the provided file
|
||||
// nolint:golint
|
||||
func ConsoleFromFile(f File) (Console, error) {
|
||||
if err := checkConsole(f); err != nil {
|
||||
return nil, err
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
// +build darwin freebsd linux openbsd solaris
|
||||
// +build darwin freebsd linux netbsd openbsd solaris
|
||||
|
||||
/*
|
||||
Copyright The containerd Authors.
|
||||
|
|
|
@ -3,6 +3,6 @@ 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
|
||||
github.com/pkg/errors v0.9.1
|
||||
golang.org/x/sys v0.0.0-20200916030750-2334cc1a136f
|
||||
)
|
||||
|
|
|
@ -1,4 +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=
|
||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
golang.org/x/sys v0.0.0-20200916030750-2334cc1a136f h1:6Sc1XOXTulBN6imkqo6XoAXDEzoQ4/ro6xy7Vn8+rOM=
|
||||
golang.org/x/sys v0.0.0-20200916030750-2334cc1a136f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
|
|
|
@ -19,7 +19,6 @@ package console
|
|||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"unsafe"
|
||||
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
@ -29,18 +28,10 @@ const (
|
|||
cmdTcSet = unix.TIOCSETA
|
||||
)
|
||||
|
||||
func ioctl(fd, flag, data uintptr) error {
|
||||
if _, _, err := unix.Syscall(unix.SYS_IOCTL, fd, flag, data); err != 0 {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// unlockpt unlocks the slave pseudoterminal device corresponding to the master pseudoterminal referred to by f.
|
||||
// unlockpt should be called before opening the slave side of a pty.
|
||||
func unlockpt(f *os.File) error {
|
||||
var u int32
|
||||
return ioctl(f.Fd(), unix.TIOCPTYUNLK, uintptr(unsafe.Pointer(&u)))
|
||||
return unix.IoctlSetPointerInt(int(f.Fd()), unix.TIOCPTYUNLK, 0)
|
||||
}
|
||||
|
||||
// ptsname retrieves the name of the first available pts for the given master.
|
||||
|
|
|
@ -19,7 +19,6 @@ package console
|
|||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"unsafe"
|
||||
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
@ -32,17 +31,13 @@ const (
|
|||
// unlockpt unlocks the slave pseudoterminal device corresponding to the master pseudoterminal referred to by f.
|
||||
// unlockpt should be called before opening the slave side of a pty.
|
||||
func unlockpt(f *os.File) error {
|
||||
var u int32
|
||||
if _, _, err := unix.Syscall(unix.SYS_IOCTL, f.Fd(), unix.TIOCSPTLCK, uintptr(unsafe.Pointer(&u))); err != 0 {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
return unix.IoctlSetPointerInt(int(f.Fd()), unix.TIOCSPTLCK, 0)
|
||||
}
|
||||
|
||||
// ptsname retrieves the name of the first available pts for the given master.
|
||||
func ptsname(f *os.File) (string, error) {
|
||||
var u uint32
|
||||
if _, _, err := unix.Syscall(unix.SYS_IOCTL, f.Fd(), unix.TIOCGPTN, uintptr(unsafe.Pointer(&u))); err != 0 {
|
||||
u, err := unix.IoctlGetInt(int(f.Fd()), unix.TIOCGPTN)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return fmt.Sprintf("/dev/pts/%d", u), nil
|
||||
|
|
|
@ -0,0 +1,45 @@
|
|||
/*
|
||||
Copyright The containerd Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package console
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"os"
|
||||
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
const (
|
||||
cmdTcGet = unix.TIOCGETA
|
||||
cmdTcSet = unix.TIOCSETA
|
||||
)
|
||||
|
||||
// unlockpt unlocks the slave pseudoterminal device corresponding to the master pseudoterminal referred to by f.
|
||||
// unlockpt should be called before opening the slave side of a pty.
|
||||
// This does not exist on NetBSD, it does not allocate controlling terminals on open
|
||||
func unlockpt(f *os.File) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// ptsname retrieves the name of the first available pts for the given master.
|
||||
func ptsname(f *os.File) (string, error) {
|
||||
ptm, err := unix.IoctlGetPtmget(int(f.Fd()), unix.TIOCPTSNAME)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return string(ptm.Sn[:bytes.IndexByte(ptm.Sn[:], 0)]), nil
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
// +build darwin freebsd linux openbsd solaris
|
||||
// +build darwin freebsd linux netbsd openbsd solaris
|
||||
|
||||
/*
|
||||
Copyright The containerd Authors.
|
||||
|
|
|
@ -151,6 +151,6 @@ func areConcreteErrors(x, y interface{}) bool {
|
|||
func compareErrors(x, y interface{}) bool {
|
||||
xe := x.(error)
|
||||
ye := y.(error)
|
||||
// TODO: Use errors.Is when go1.13 is the minimally supported version of Go.
|
||||
// TODO(≥go1.13): Use standard definition of errors.Is.
|
||||
return xerrors.Is(xe, ye) || xerrors.Is(ye, xe)
|
||||
}
|
||||
|
|
|
@ -14,14 +14,13 @@ import (
|
|||
"github.com/google/go-cmp/cmp/internal/function"
|
||||
)
|
||||
|
||||
// IgnoreFields returns an Option that ignores exported fields of the
|
||||
// given names on a single struct type.
|
||||
// IgnoreFields returns an Option that ignores fields of the
|
||||
// given names on a single struct type. It respects the names of exported fields
|
||||
// that are forwarded due to struct embedding.
|
||||
// The struct type is specified by passing in a value of that type.
|
||||
//
|
||||
// The name may be a dot-delimited string (e.g., "Foo.Bar") to ignore a
|
||||
// specific sub-field that is embedded or nested within the parent struct.
|
||||
//
|
||||
// This does not handle unexported fields; use IgnoreUnexported instead.
|
||||
func IgnoreFields(typ interface{}, names ...string) cmp.Option {
|
||||
sf := newStructFilter(typ, names...)
|
||||
return cmp.FilterPath(sf.filter, cmp.Ignore())
|
||||
|
@ -129,7 +128,7 @@ func newUnexportedFilter(typs ...interface{}) unexportedFilter {
|
|||
for _, typ := range typs {
|
||||
t := reflect.TypeOf(typ)
|
||||
if t == nil || t.Kind() != reflect.Struct {
|
||||
panic(fmt.Sprintf("invalid struct type: %T", typ))
|
||||
panic(fmt.Sprintf("%T must be a non-pointer struct", typ))
|
||||
}
|
||||
ux.m[t] = true
|
||||
}
|
||||
|
|
|
@ -42,7 +42,7 @@ func newStructFilter(typ interface{}, names ...string) structFilter {
|
|||
|
||||
t := reflect.TypeOf(typ)
|
||||
if t == nil || t.Kind() != reflect.Struct {
|
||||
panic(fmt.Sprintf("%T must be a struct", typ))
|
||||
panic(fmt.Sprintf("%T must be a non-pointer struct", typ))
|
||||
}
|
||||
var ft fieldTree
|
||||
for _, name := range names {
|
||||
|
@ -160,14 +160,19 @@ func canonicalName(t reflect.Type, sel string) ([]string, error) {
|
|||
|
||||
// Find the canonical name for this current field name.
|
||||
// If the field exists in an embedded struct, then it will be expanded.
|
||||
sf, _ := t.FieldByName(name)
|
||||
if !isExported(name) {
|
||||
// Disallow unexported fields:
|
||||
// * To discourage people from actually touching unexported fields
|
||||
// * FieldByName is buggy (https://golang.org/issue/4876)
|
||||
return []string{name}, fmt.Errorf("name must be exported")
|
||||
// Avoid using reflect.Type.FieldByName for unexported fields due to
|
||||
// buggy behavior with regard to embeddeding and unexported fields.
|
||||
// See https://golang.org/issue/4876 for details.
|
||||
sf = reflect.StructField{}
|
||||
for i := 0; i < t.NumField() && sf.Name == ""; i++ {
|
||||
if t.Field(i).Name == name {
|
||||
sf = t.Field(i)
|
||||
}
|
||||
}
|
||||
}
|
||||
sf, ok := t.FieldByName(name)
|
||||
if !ok {
|
||||
if sf.Name == "" {
|
||||
return []string{name}, fmt.Errorf("does not exist")
|
||||
}
|
||||
var ss []string
|
||||
|
|
|
@ -6,6 +6,10 @@
|
|||
//
|
||||
// This package is intended to be a more powerful and safer alternative to
|
||||
// reflect.DeepEqual for comparing whether two values are semantically equal.
|
||||
// It is intended to only be used in tests, as performance is not a goal and
|
||||
// it may panic if it cannot compare the values. Its propensity towards
|
||||
// panicking means that its unsuitable for production environments where a
|
||||
// spurious panic may be fatal.
|
||||
//
|
||||
// The primary features of cmp are:
|
||||
//
|
||||
|
@ -86,6 +90,52 @@ import (
|
|||
// If there is a cycle, then the pointed at values are considered equal
|
||||
// only if both addresses were previously visited in the same path step.
|
||||
func Equal(x, y interface{}, opts ...Option) bool {
|
||||
s := newState(opts)
|
||||
s.compareAny(rootStep(x, y))
|
||||
return s.result.Equal()
|
||||
}
|
||||
|
||||
// Diff returns a human-readable report of the differences between two values:
|
||||
// y - x. It returns an empty string if and only if Equal returns true for the
|
||||
// same input values and options.
|
||||
//
|
||||
// The output is displayed as a literal in pseudo-Go syntax.
|
||||
// At the start of each line, a "-" prefix indicates an element removed from y,
|
||||
// a "+" prefix to indicates an element added to y, and the lack of a prefix
|
||||
// indicates an element common to both x and y. If possible, the output
|
||||
// uses fmt.Stringer.String or error.Error methods to produce more humanly
|
||||
// readable outputs. In such cases, the string is prefixed with either an
|
||||
// 's' or 'e' character, respectively, to indicate that the method was called.
|
||||
//
|
||||
// Do not depend on this output being stable. If you need the ability to
|
||||
// programmatically interpret the difference, consider using a custom Reporter.
|
||||
func Diff(x, y interface{}, opts ...Option) string {
|
||||
s := newState(opts)
|
||||
|
||||
// Optimization: If there are no other reporters, we can optimize for the
|
||||
// common case where the result is equal (and thus no reported difference).
|
||||
// This avoids the expensive construction of a difference tree.
|
||||
if len(s.reporters) == 0 {
|
||||
s.compareAny(rootStep(x, y))
|
||||
if s.result.Equal() {
|
||||
return ""
|
||||
}
|
||||
s.result = diff.Result{} // Reset results
|
||||
}
|
||||
|
||||
r := new(defaultReporter)
|
||||
s.reporters = append(s.reporters, reporter{r})
|
||||
s.compareAny(rootStep(x, y))
|
||||
d := r.String()
|
||||
if (d == "") != s.result.Equal() {
|
||||
panic("inconsistent difference and equality results")
|
||||
}
|
||||
return d
|
||||
}
|
||||
|
||||
// rootStep constructs the first path step. If x and y have differing types,
|
||||
// then they are stored within an empty interface type.
|
||||
func rootStep(x, y interface{}) PathStep {
|
||||
vx := reflect.ValueOf(x)
|
||||
vy := reflect.ValueOf(y)
|
||||
|
||||
|
@ -108,33 +158,7 @@ func Equal(x, y interface{}, opts ...Option) bool {
|
|||
t = vx.Type()
|
||||
}
|
||||
|
||||
s := newState(opts)
|
||||
s.compareAny(&pathStep{t, vx, vy})
|
||||
return s.result.Equal()
|
||||
}
|
||||
|
||||
// Diff returns a human-readable report of the differences between two values.
|
||||
// It returns an empty string if and only if Equal returns true for the same
|
||||
// input values and options.
|
||||
//
|
||||
// The output is displayed as a literal in pseudo-Go syntax.
|
||||
// At the start of each line, a "-" prefix indicates an element removed from x,
|
||||
// a "+" prefix to indicates an element added to y, and the lack of a prefix
|
||||
// indicates an element common to both x and y. If possible, the output
|
||||
// uses fmt.Stringer.String or error.Error methods to produce more humanly
|
||||
// readable outputs. In such cases, the string is prefixed with either an
|
||||
// 's' or 'e' character, respectively, to indicate that the method was called.
|
||||
//
|
||||
// Do not depend on this output being stable. If you need the ability to
|
||||
// programmatically interpret the difference, consider using a custom Reporter.
|
||||
func Diff(x, y interface{}, opts ...Option) string {
|
||||
r := new(defaultReporter)
|
||||
eq := Equal(x, y, Options(opts), Reporter(r))
|
||||
d := r.String()
|
||||
if (d == "") != eq {
|
||||
panic("inconsistent difference and equality results")
|
||||
}
|
||||
return d
|
||||
return &pathStep{t, vx, vy}
|
||||
}
|
||||
|
||||
type state struct {
|
||||
|
@ -352,7 +376,7 @@ func detectRaces(c chan<- reflect.Value, f reflect.Value, vs ...reflect.Value) {
|
|||
// assuming that T is assignable to R.
|
||||
// Otherwise, it returns the input value as is.
|
||||
func sanitizeValue(v reflect.Value, t reflect.Type) reflect.Value {
|
||||
// TODO(dsnet): Workaround for reflect bug (https://golang.org/issue/22143).
|
||||
// TODO(≥go1.10): Workaround for reflect bug (https://golang.org/issue/22143).
|
||||
if !flags.AtLeastGo110 {
|
||||
if v.Kind() == reflect.Interface && v.IsNil() && v.Type() != t {
|
||||
return reflect.New(t).Elem()
|
||||
|
@ -362,6 +386,7 @@ func sanitizeValue(v reflect.Value, t reflect.Type) reflect.Value {
|
|||
}
|
||||
|
||||
func (s *state) compareStruct(t reflect.Type, vx, vy reflect.Value) {
|
||||
var addr bool
|
||||
var vax, vay reflect.Value // Addressable versions of vx and vy
|
||||
|
||||
var mayForce, mayForceInit bool
|
||||
|
@ -383,6 +408,7 @@ func (s *state) compareStruct(t reflect.Type, vx, vy reflect.Value) {
|
|||
// For retrieveUnexportedField to work, the parent struct must
|
||||
// be addressable. Create a new copy of the values if
|
||||
// necessary to make them addressable.
|
||||
addr = vx.CanAddr() || vy.CanAddr()
|
||||
vax = makeAddressable(vx)
|
||||
vay = makeAddressable(vy)
|
||||
}
|
||||
|
@ -393,6 +419,7 @@ func (s *state) compareStruct(t reflect.Type, vx, vy reflect.Value) {
|
|||
mayForceInit = true
|
||||
}
|
||||
step.mayForce = mayForce
|
||||
step.paddr = addr
|
||||
step.pvx = vax
|
||||
step.pvy = vay
|
||||
step.field = t.Field(i)
|
||||
|
|
|
@ -10,6 +10,6 @@ import "reflect"
|
|||
|
||||
const supportExporters = false
|
||||
|
||||
func retrieveUnexportedField(reflect.Value, reflect.StructField) reflect.Value {
|
||||
func retrieveUnexportedField(reflect.Value, reflect.StructField, bool) reflect.Value {
|
||||
panic("no support for forcibly accessing unexported fields")
|
||||
}
|
||||
|
|
|
@ -17,9 +17,19 @@ const supportExporters = true
|
|||
// a struct such that the value has read-write permissions.
|
||||
//
|
||||
// The parent struct, v, must be addressable, while f must be a StructField
|
||||
// describing the field to retrieve.
|
||||
func retrieveUnexportedField(v reflect.Value, f reflect.StructField) reflect.Value {
|
||||
// See https://github.com/google/go-cmp/issues/167 for discussion of the
|
||||
// following expression.
|
||||
return reflect.NewAt(f.Type, unsafe.Pointer(uintptr(unsafe.Pointer(v.UnsafeAddr()))+f.Offset)).Elem()
|
||||
// describing the field to retrieve. If addr is false,
|
||||
// then the returned value will be shallowed copied to be non-addressable.
|
||||
func retrieveUnexportedField(v reflect.Value, f reflect.StructField, addr bool) reflect.Value {
|
||||
ve := reflect.NewAt(f.Type, unsafe.Pointer(uintptr(unsafe.Pointer(v.UnsafeAddr()))+f.Offset)).Elem()
|
||||
if !addr {
|
||||
// A field is addressable if and only if the struct is addressable.
|
||||
// If the original parent value was not addressable, shallow copy the
|
||||
// value to make it non-addressable to avoid leaking an implementation
|
||||
// detail of how forcibly exporting a field works.
|
||||
if ve.Kind() == reflect.Interface && ve.IsNil() {
|
||||
return reflect.Zero(f.Type)
|
||||
}
|
||||
return reflect.ValueOf(ve.Interface()).Convert(f.Type)
|
||||
}
|
||||
return ve
|
||||
}
|
||||
|
|
|
@ -12,6 +12,13 @@
|
|||
// is more important than obtaining a minimal Levenshtein distance.
|
||||
package diff
|
||||
|
||||
import (
|
||||
"math/rand"
|
||||
"time"
|
||||
|
||||
"github.com/google/go-cmp/cmp/internal/flags"
|
||||
)
|
||||
|
||||
// EditType represents a single operation within an edit-script.
|
||||
type EditType uint8
|
||||
|
||||
|
@ -112,6 +119,8 @@ func (r Result) Similar() bool {
|
|||
return r.NumSame+1 >= r.NumDiff
|
||||
}
|
||||
|
||||
var randInt = rand.New(rand.NewSource(time.Now().Unix())).Intn(2)
|
||||
|
||||
// Difference reports whether two lists of lengths nx and ny are equal
|
||||
// given the definition of equality provided as f.
|
||||
//
|
||||
|
@ -159,6 +168,17 @@ func Difference(nx, ny int, f EqualFunc) (es EditScript) {
|
|||
// A vertical edge is equivalent to inserting a symbol from list Y.
|
||||
// A diagonal edge is equivalent to a matching symbol between both X and Y.
|
||||
|
||||
// To ensure flexibility in changing the algorithm in the future,
|
||||
// introduce some degree of deliberate instability.
|
||||
// This is achieved by fiddling the zigzag iterator to start searching
|
||||
// the graph starting from the bottom-right versus than the top-left.
|
||||
// The result may differ depending on the starting search location,
|
||||
// but still produces a valid edit script.
|
||||
zigzagInit := randInt // either 0 or 1
|
||||
if flags.Deterministic {
|
||||
zigzagInit = 0
|
||||
}
|
||||
|
||||
// Invariants:
|
||||
// • 0 ≤ fwdPath.X ≤ (fwdFrontier.X, revFrontier.X) ≤ revPath.X ≤ nx
|
||||
// • 0 ≤ fwdPath.Y ≤ (fwdFrontier.Y, revFrontier.Y) ≤ revPath.Y ≤ ny
|
||||
|
@ -209,7 +229,7 @@ func Difference(nx, ny int, f EqualFunc) (es EditScript) {
|
|||
if fwdFrontier.X >= revFrontier.X || fwdFrontier.Y >= revFrontier.Y || searchBudget == 0 {
|
||||
break
|
||||
}
|
||||
for stop1, stop2, i := false, false, 0; !(stop1 && stop2) && searchBudget > 0; i++ {
|
||||
for stop1, stop2, i := false, false, zigzagInit; !(stop1 && stop2) && searchBudget > 0; i++ {
|
||||
// Search in a diagonal pattern for a match.
|
||||
z := zigzag(i)
|
||||
p := point{fwdFrontier.X + z, fwdFrontier.Y - z}
|
||||
|
|
|
@ -0,0 +1,157 @@
|
|||
// Copyright 2020, The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE.md file.
|
||||
|
||||
package value
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
// TypeString is nearly identical to reflect.Type.String,
|
||||
// but has an additional option to specify that full type names be used.
|
||||
func TypeString(t reflect.Type, qualified bool) string {
|
||||
return string(appendTypeName(nil, t, qualified, false))
|
||||
}
|
||||
|
||||
func appendTypeName(b []byte, t reflect.Type, qualified, elideFunc bool) []byte {
|
||||
// BUG: Go reflection provides no way to disambiguate two named types
|
||||
// of the same name and within the same package,
|
||||
// but declared within the namespace of different functions.
|
||||
|
||||
// Named type.
|
||||
if t.Name() != "" {
|
||||
if qualified && t.PkgPath() != "" {
|
||||
b = append(b, '"')
|
||||
b = append(b, t.PkgPath()...)
|
||||
b = append(b, '"')
|
||||
b = append(b, '.')
|
||||
b = append(b, t.Name()...)
|
||||
} else {
|
||||
b = append(b, t.String()...)
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
// Unnamed type.
|
||||
switch k := t.Kind(); k {
|
||||
case reflect.Bool, reflect.String, reflect.UnsafePointer,
|
||||
reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64,
|
||||
reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr,
|
||||
reflect.Float32, reflect.Float64, reflect.Complex64, reflect.Complex128:
|
||||
b = append(b, k.String()...)
|
||||
case reflect.Chan:
|
||||
if t.ChanDir() == reflect.RecvDir {
|
||||
b = append(b, "<-"...)
|
||||
}
|
||||
b = append(b, "chan"...)
|
||||
if t.ChanDir() == reflect.SendDir {
|
||||
b = append(b, "<-"...)
|
||||
}
|
||||
b = append(b, ' ')
|
||||
b = appendTypeName(b, t.Elem(), qualified, false)
|
||||
case reflect.Func:
|
||||
if !elideFunc {
|
||||
b = append(b, "func"...)
|
||||
}
|
||||
b = append(b, '(')
|
||||
for i := 0; i < t.NumIn(); i++ {
|
||||
if i > 0 {
|
||||
b = append(b, ", "...)
|
||||
}
|
||||
if i == t.NumIn()-1 && t.IsVariadic() {
|
||||
b = append(b, "..."...)
|
||||
b = appendTypeName(b, t.In(i).Elem(), qualified, false)
|
||||
} else {
|
||||
b = appendTypeName(b, t.In(i), qualified, false)
|
||||
}
|
||||
}
|
||||
b = append(b, ')')
|
||||
switch t.NumOut() {
|
||||
case 0:
|
||||
// Do nothing
|
||||
case 1:
|
||||
b = append(b, ' ')
|
||||
b = appendTypeName(b, t.Out(0), qualified, false)
|
||||
default:
|
||||
b = append(b, " ("...)
|
||||
for i := 0; i < t.NumOut(); i++ {
|
||||
if i > 0 {
|
||||
b = append(b, ", "...)
|
||||
}
|
||||
b = appendTypeName(b, t.Out(i), qualified, false)
|
||||
}
|
||||
b = append(b, ')')
|
||||
}
|
||||
case reflect.Struct:
|
||||
b = append(b, "struct{ "...)
|
||||
for i := 0; i < t.NumField(); i++ {
|
||||
if i > 0 {
|
||||
b = append(b, "; "...)
|
||||
}
|
||||
sf := t.Field(i)
|
||||
if !sf.Anonymous {
|
||||
if qualified && sf.PkgPath != "" {
|
||||
b = append(b, '"')
|
||||
b = append(b, sf.PkgPath...)
|
||||
b = append(b, '"')
|
||||
b = append(b, '.')
|
||||
}
|
||||
b = append(b, sf.Name...)
|
||||
b = append(b, ' ')
|
||||
}
|
||||
b = appendTypeName(b, sf.Type, qualified, false)
|
||||
if sf.Tag != "" {
|
||||
b = append(b, ' ')
|
||||
b = strconv.AppendQuote(b, string(sf.Tag))
|
||||
}
|
||||
}
|
||||
if b[len(b)-1] == ' ' {
|
||||
b = b[:len(b)-1]
|
||||
} else {
|
||||
b = append(b, ' ')
|
||||
}
|
||||
b = append(b, '}')
|
||||
case reflect.Slice, reflect.Array:
|
||||
b = append(b, '[')
|
||||
if k == reflect.Array {
|
||||
b = strconv.AppendUint(b, uint64(t.Len()), 10)
|
||||
}
|
||||
b = append(b, ']')
|
||||
b = appendTypeName(b, t.Elem(), qualified, false)
|
||||
case reflect.Map:
|
||||
b = append(b, "map["...)
|
||||
b = appendTypeName(b, t.Key(), qualified, false)
|
||||
b = append(b, ']')
|
||||
b = appendTypeName(b, t.Elem(), qualified, false)
|
||||
case reflect.Ptr:
|
||||
b = append(b, '*')
|
||||
b = appendTypeName(b, t.Elem(), qualified, false)
|
||||
case reflect.Interface:
|
||||
b = append(b, "interface{ "...)
|
||||
for i := 0; i < t.NumMethod(); i++ {
|
||||
if i > 0 {
|
||||
b = append(b, "; "...)
|
||||
}
|
||||
m := t.Method(i)
|
||||
if qualified && m.PkgPath != "" {
|
||||
b = append(b, '"')
|
||||
b = append(b, m.PkgPath...)
|
||||
b = append(b, '"')
|
||||
b = append(b, '.')
|
||||
}
|
||||
b = append(b, m.Name...)
|
||||
b = appendTypeName(b, m.Type, qualified, true)
|
||||
}
|
||||
if b[len(b)-1] == ' ' {
|
||||
b = b[:len(b)-1]
|
||||
} else {
|
||||
b = append(b, ' ')
|
||||
}
|
||||
b = append(b, '}')
|
||||
default:
|
||||
panic("invalid kind: " + k.String())
|
||||
}
|
||||
return b
|
||||
}
|
|
@ -21,3 +21,13 @@ func PointerOf(v reflect.Value) Pointer {
|
|||
// assumes that the GC implementation does not use a moving collector.
|
||||
return Pointer{v.Pointer(), v.Type()}
|
||||
}
|
||||
|
||||
// IsNil reports whether the pointer is nil.
|
||||
func (p Pointer) IsNil() bool {
|
||||
return p.p == 0
|
||||
}
|
||||
|
||||
// Uintptr returns the pointer as a uintptr.
|
||||
func (p Pointer) Uintptr() uintptr {
|
||||
return p.p
|
||||
}
|
||||
|
|
|
@ -24,3 +24,13 @@ func PointerOf(v reflect.Value) Pointer {
|
|||
// which is necessary if the GC ever uses a moving collector.
|
||||
return Pointer{unsafe.Pointer(v.Pointer()), v.Type()}
|
||||
}
|
||||
|
||||
// IsNil reports whether the pointer is nil.
|
||||
func (p Pointer) IsNil() bool {
|
||||
return p.p == nil
|
||||
}
|
||||
|
||||
// Uintptr returns the pointer as a uintptr.
|
||||
func (p Pointer) Uintptr() uintptr {
|
||||
return uintptr(p.p)
|
||||
}
|
||||
|
|
|
@ -225,11 +225,14 @@ func (validator) apply(s *state, vx, vy reflect.Value) {
|
|||
|
||||
// Unable to Interface implies unexported field without visibility access.
|
||||
if !vx.CanInterface() || !vy.CanInterface() {
|
||||
const help = "consider using a custom Comparer; if you control the implementation of type, you can also consider using an Exporter, AllowUnexported, or cmpopts.IgnoreUnexported"
|
||||
help := "consider using a custom Comparer; if you control the implementation of type, you can also consider using an Exporter, AllowUnexported, or cmpopts.IgnoreUnexported"
|
||||
var name string
|
||||
if t := s.curPath.Index(-2).Type(); t.Name() != "" {
|
||||
// Named type with unexported fields.
|
||||
name = fmt.Sprintf("%q.%v", t.PkgPath(), t.Name()) // e.g., "path/to/package".MyType
|
||||
if _, ok := reflect.New(t).Interface().(error); ok {
|
||||
help = "consider using cmpopts.EquateErrors to compare error values"
|
||||
}
|
||||
} else {
|
||||
// Unnamed type with unexported fields. Derive PkgPath from field.
|
||||
var pkgPath string
|
||||
|
|
|
@ -177,7 +177,8 @@ type structField struct {
|
|||
// pvx, pvy, and field are only valid if unexported is true.
|
||||
unexported bool
|
||||
mayForce bool // Forcibly allow visibility
|
||||
pvx, pvy reflect.Value // Parent values
|
||||
paddr bool // Was parent addressable?
|
||||
pvx, pvy reflect.Value // Parent values (always addressible)
|
||||
field reflect.StructField // Field information
|
||||
}
|
||||
|
||||
|
@ -189,8 +190,8 @@ func (sf StructField) Values() (vx, vy reflect.Value) {
|
|||
|
||||
// Forcibly obtain read-write access to an unexported struct field.
|
||||
if sf.mayForce {
|
||||
vx = retrieveUnexportedField(sf.pvx, sf.field)
|
||||
vy = retrieveUnexportedField(sf.pvy, sf.field)
|
||||
vx = retrieveUnexportedField(sf.pvx, sf.field, sf.paddr)
|
||||
vy = retrieveUnexportedField(sf.pvy, sf.field, sf.paddr)
|
||||
return vx, vy // CanInterface reports true
|
||||
}
|
||||
return sf.vx, sf.vy // CanInterface reports false
|
||||
|
|
|
@ -41,7 +41,10 @@ func (r *defaultReporter) String() string {
|
|||
if r.root.NumDiff == 0 {
|
||||
return ""
|
||||
}
|
||||
return formatOptions{}.FormatDiff(r.root).String()
|
||||
ptrs := new(pointerReferences)
|
||||
text := formatOptions{}.FormatDiff(r.root, ptrs)
|
||||
resolveReferences(text)
|
||||
return text.String()
|
||||
}
|
||||
|
||||
func assert(ok bool) {
|
||||
|
|
|
@ -11,14 +11,6 @@ import (
|
|||
"github.com/google/go-cmp/cmp/internal/value"
|
||||
)
|
||||
|
||||
// TODO: Enforce limits?
|
||||
// * Enforce maximum number of records to print per node?
|
||||
// * Enforce maximum size in bytes allowed?
|
||||
// * As a heuristic, use less verbosity for equal nodes than unequal nodes.
|
||||
// TODO: Enforce unique outputs?
|
||||
// * Avoid Stringer methods if it results in same output?
|
||||
// * Print pointer address if outputs still equal?
|
||||
|
||||
// numContextRecords is the number of surrounding equal records to print.
|
||||
const numContextRecords = 2
|
||||
|
||||
|
@ -71,24 +63,66 @@ func (opts formatOptions) WithTypeMode(t typeMode) formatOptions {
|
|||
opts.TypeMode = t
|
||||
return opts
|
||||
}
|
||||
func (opts formatOptions) WithVerbosity(level int) formatOptions {
|
||||
opts.VerbosityLevel = level
|
||||
opts.LimitVerbosity = true
|
||||
return opts
|
||||
}
|
||||
func (opts formatOptions) verbosity() uint {
|
||||
switch {
|
||||
case opts.VerbosityLevel < 0:
|
||||
return 0
|
||||
case opts.VerbosityLevel > 16:
|
||||
return 16 // some reasonable maximum to avoid shift overflow
|
||||
default:
|
||||
return uint(opts.VerbosityLevel)
|
||||
}
|
||||
}
|
||||
|
||||
const maxVerbosityPreset = 3
|
||||
|
||||
// verbosityPreset modifies the verbosity settings given an index
|
||||
// between 0 and maxVerbosityPreset, inclusive.
|
||||
func verbosityPreset(opts formatOptions, i int) formatOptions {
|
||||
opts.VerbosityLevel = int(opts.verbosity()) + 2*i
|
||||
if i > 0 {
|
||||
opts.AvoidStringer = true
|
||||
}
|
||||
if i >= maxVerbosityPreset {
|
||||
opts.PrintAddresses = true
|
||||
opts.QualifiedNames = true
|
||||
}
|
||||
return opts
|
||||
}
|
||||
|
||||
// FormatDiff converts a valueNode tree into a textNode tree, where the later
|
||||
// is a textual representation of the differences detected in the former.
|
||||
func (opts formatOptions) FormatDiff(v *valueNode) textNode {
|
||||
func (opts formatOptions) FormatDiff(v *valueNode, ptrs *pointerReferences) (out textNode) {
|
||||
if opts.DiffMode == diffIdentical {
|
||||
opts = opts.WithVerbosity(1)
|
||||
} else {
|
||||
opts = opts.WithVerbosity(3)
|
||||
}
|
||||
|
||||
// Check whether we have specialized formatting for this node.
|
||||
// This is not necessary, but helpful for producing more readable outputs.
|
||||
if opts.CanFormatDiffSlice(v) {
|
||||
return opts.FormatDiffSlice(v)
|
||||
}
|
||||
|
||||
var parentKind reflect.Kind
|
||||
if v.parent != nil && v.parent.TransformerName == "" {
|
||||
parentKind = v.parent.Type.Kind()
|
||||
}
|
||||
|
||||
// For leaf nodes, format the value based on the reflect.Values alone.
|
||||
if v.MaxDepth == 0 {
|
||||
switch opts.DiffMode {
|
||||
case diffUnknown, diffIdentical:
|
||||
// Format Equal.
|
||||
if v.NumDiff == 0 {
|
||||
outx := opts.FormatValue(v.ValueX, visitedPointers{})
|
||||
outy := opts.FormatValue(v.ValueY, visitedPointers{})
|
||||
outx := opts.FormatValue(v.ValueX, parentKind, ptrs)
|
||||
outy := opts.FormatValue(v.ValueY, parentKind, ptrs)
|
||||
if v.NumIgnored > 0 && v.NumSame == 0 {
|
||||
return textEllipsis
|
||||
} else if outx.Len() < outy.Len() {
|
||||
|
@ -101,8 +135,13 @@ func (opts formatOptions) FormatDiff(v *valueNode) textNode {
|
|||
// Format unequal.
|
||||
assert(opts.DiffMode == diffUnknown)
|
||||
var list textList
|
||||
outx := opts.WithTypeMode(elideType).FormatValue(v.ValueX, visitedPointers{})
|
||||
outy := opts.WithTypeMode(elideType).FormatValue(v.ValueY, visitedPointers{})
|
||||
outx := opts.WithTypeMode(elideType).FormatValue(v.ValueX, parentKind, ptrs)
|
||||
outy := opts.WithTypeMode(elideType).FormatValue(v.ValueY, parentKind, ptrs)
|
||||
for i := 0; i <= maxVerbosityPreset && outx != nil && outy != nil && outx.Equal(outy); i++ {
|
||||
opts2 := verbosityPreset(opts, i).WithTypeMode(elideType)
|
||||
outx = opts2.FormatValue(v.ValueX, parentKind, ptrs)
|
||||
outy = opts2.FormatValue(v.ValueY, parentKind, ptrs)
|
||||
}
|
||||
if outx != nil {
|
||||
list = append(list, textRecord{Diff: '-', Value: outx})
|
||||
}
|
||||
|
@ -111,34 +150,57 @@ func (opts formatOptions) FormatDiff(v *valueNode) textNode {
|
|||
}
|
||||
return opts.WithTypeMode(emitType).FormatType(v.Type, list)
|
||||
case diffRemoved:
|
||||
return opts.FormatValue(v.ValueX, visitedPointers{})
|
||||
return opts.FormatValue(v.ValueX, parentKind, ptrs)
|
||||
case diffInserted:
|
||||
return opts.FormatValue(v.ValueY, visitedPointers{})
|
||||
return opts.FormatValue(v.ValueY, parentKind, ptrs)
|
||||
default:
|
||||
panic("invalid diff mode")
|
||||
}
|
||||
}
|
||||
|
||||
// Register slice element to support cycle detection.
|
||||
if parentKind == reflect.Slice {
|
||||
ptrRefs := ptrs.PushPair(v.ValueX, v.ValueY, opts.DiffMode, true)
|
||||
defer ptrs.Pop()
|
||||
defer func() { out = wrapTrunkReferences(ptrRefs, out) }()
|
||||
}
|
||||
|
||||
// Descend into the child value node.
|
||||
if v.TransformerName != "" {
|
||||
out := opts.WithTypeMode(emitType).FormatDiff(v.Value)
|
||||
out = textWrap{"Inverse(" + v.TransformerName + ", ", out, ")"}
|
||||
out := opts.WithTypeMode(emitType).FormatDiff(v.Value, ptrs)
|
||||
out = &textWrap{Prefix: "Inverse(" + v.TransformerName + ", ", Value: out, Suffix: ")"}
|
||||
return opts.FormatType(v.Type, out)
|
||||
} else {
|
||||
switch k := v.Type.Kind(); k {
|
||||
case reflect.Struct, reflect.Array, reflect.Slice, reflect.Map:
|
||||
return opts.FormatType(v.Type, opts.formatDiffList(v.Records, k))
|
||||
case reflect.Struct, reflect.Array, reflect.Slice:
|
||||
out = opts.formatDiffList(v.Records, k, ptrs)
|
||||
out = opts.FormatType(v.Type, out)
|
||||
case reflect.Map:
|
||||
// Register map to support cycle detection.
|
||||
ptrRefs := ptrs.PushPair(v.ValueX, v.ValueY, opts.DiffMode, false)
|
||||
defer ptrs.Pop()
|
||||
|
||||
out = opts.formatDiffList(v.Records, k, ptrs)
|
||||
out = wrapTrunkReferences(ptrRefs, out)
|
||||
out = opts.FormatType(v.Type, out)
|
||||
case reflect.Ptr:
|
||||
return textWrap{"&", opts.FormatDiff(v.Value), ""}
|
||||
// Register pointer to support cycle detection.
|
||||
ptrRefs := ptrs.PushPair(v.ValueX, v.ValueY, opts.DiffMode, false)
|
||||
defer ptrs.Pop()
|
||||
|
||||
out = opts.FormatDiff(v.Value, ptrs)
|
||||
out = wrapTrunkReferences(ptrRefs, out)
|
||||
out = &textWrap{Prefix: "&", Value: out}
|
||||
case reflect.Interface:
|
||||
return opts.WithTypeMode(emitType).FormatDiff(v.Value)
|
||||
out = opts.WithTypeMode(emitType).FormatDiff(v.Value, ptrs)
|
||||
default:
|
||||
panic(fmt.Sprintf("%v cannot have children", k))
|
||||
}
|
||||
return out
|
||||
}
|
||||
}
|
||||
|
||||
func (opts formatOptions) formatDiffList(recs []reportRecord, k reflect.Kind) textNode {
|
||||
func (opts formatOptions) formatDiffList(recs []reportRecord, k reflect.Kind, ptrs *pointerReferences) textNode {
|
||||
// Derive record name based on the data structure kind.
|
||||
var name string
|
||||
var formatKey func(reflect.Value) string
|
||||
|
@ -154,7 +216,17 @@ func (opts formatOptions) formatDiffList(recs []reportRecord, k reflect.Kind) te
|
|||
case reflect.Map:
|
||||
name = "entry"
|
||||
opts = opts.WithTypeMode(elideType)
|
||||
formatKey = formatMapKey
|
||||
formatKey = func(v reflect.Value) string { return formatMapKey(v, false, ptrs) }
|
||||
}
|
||||
|
||||
maxLen := -1
|
||||
if opts.LimitVerbosity {
|
||||
if opts.DiffMode == diffIdentical {
|
||||
maxLen = ((1 << opts.verbosity()) >> 1) << 2 // 0, 4, 8, 16, 32, etc...
|
||||
} else {
|
||||
maxLen = (1 << opts.verbosity()) << 1 // 2, 4, 8, 16, 32, 64, etc...
|
||||
}
|
||||
opts.VerbosityLevel--
|
||||
}
|
||||
|
||||
// Handle unification.
|
||||
|
@ -163,6 +235,11 @@ func (opts formatOptions) formatDiffList(recs []reportRecord, k reflect.Kind) te
|
|||
var list textList
|
||||
var deferredEllipsis bool // Add final "..." to indicate records were dropped
|
||||
for _, r := range recs {
|
||||
if len(list) == maxLen {
|
||||
deferredEllipsis = true
|
||||
break
|
||||
}
|
||||
|
||||
// Elide struct fields that are zero value.
|
||||
if k == reflect.Struct {
|
||||
var isZero bool
|
||||
|
@ -186,23 +263,31 @@ func (opts formatOptions) formatDiffList(recs []reportRecord, k reflect.Kind) te
|
|||
}
|
||||
continue
|
||||
}
|
||||
if out := opts.FormatDiff(r.Value); out != nil {
|
||||
if out := opts.FormatDiff(r.Value, ptrs); out != nil {
|
||||
list = append(list, textRecord{Key: formatKey(r.Key), Value: out})
|
||||
}
|
||||
}
|
||||
if deferredEllipsis {
|
||||
list.AppendEllipsis(diffStats{})
|
||||
}
|
||||
return textWrap{"{", list, "}"}
|
||||
return &textWrap{Prefix: "{", Value: list, Suffix: "}"}
|
||||
case diffUnknown:
|
||||
default:
|
||||
panic("invalid diff mode")
|
||||
}
|
||||
|
||||
// Handle differencing.
|
||||
var numDiffs int
|
||||
var list textList
|
||||
var keys []reflect.Value // invariant: len(list) == len(keys)
|
||||
groups := coalesceAdjacentRecords(name, recs)
|
||||
maxGroup := diffStats{Name: name}
|
||||
for i, ds := range groups {
|
||||
if maxLen >= 0 && numDiffs >= maxLen {
|
||||
maxGroup = maxGroup.Append(ds)
|
||||
continue
|
||||
}
|
||||
|
||||
// Handle equal records.
|
||||
if ds.NumDiff() == 0 {
|
||||
// Compute the number of leading and trailing records to print.
|
||||
|
@ -226,16 +311,21 @@ func (opts formatOptions) formatDiffList(recs []reportRecord, k reflect.Kind) te
|
|||
|
||||
// Format the equal values.
|
||||
for _, r := range recs[:numLo] {
|
||||
out := opts.WithDiffMode(diffIdentical).FormatDiff(r.Value)
|
||||
out := opts.WithDiffMode(diffIdentical).FormatDiff(r.Value, ptrs)
|
||||
list = append(list, textRecord{Key: formatKey(r.Key), Value: out})
|
||||
keys = append(keys, r.Key)
|
||||
}
|
||||
if numEqual > numLo+numHi {
|
||||
ds.NumIdentical -= numLo + numHi
|
||||
list.AppendEllipsis(ds)
|
||||
for len(keys) < len(list) {
|
||||
keys = append(keys, reflect.Value{})
|
||||
}
|
||||
}
|
||||
for _, r := range recs[numEqual-numHi : numEqual] {
|
||||
out := opts.WithDiffMode(diffIdentical).FormatDiff(r.Value)
|
||||
out := opts.WithDiffMode(diffIdentical).FormatDiff(r.Value, ptrs)
|
||||
list = append(list, textRecord{Key: formatKey(r.Key), Value: out})
|
||||
keys = append(keys, r.Key)
|
||||
}
|
||||
recs = recs[numEqual:]
|
||||
continue
|
||||
|
@ -247,24 +337,70 @@ func (opts formatOptions) formatDiffList(recs []reportRecord, k reflect.Kind) te
|
|||
case opts.CanFormatDiffSlice(r.Value):
|
||||
out := opts.FormatDiffSlice(r.Value)
|
||||
list = append(list, textRecord{Key: formatKey(r.Key), Value: out})
|
||||
keys = append(keys, r.Key)
|
||||
case r.Value.NumChildren == r.Value.MaxDepth:
|
||||
outx := opts.WithDiffMode(diffRemoved).FormatDiff(r.Value)
|
||||
outy := opts.WithDiffMode(diffInserted).FormatDiff(r.Value)
|
||||
outx := opts.WithDiffMode(diffRemoved).FormatDiff(r.Value, ptrs)
|
||||
outy := opts.WithDiffMode(diffInserted).FormatDiff(r.Value, ptrs)
|
||||
for i := 0; i <= maxVerbosityPreset && outx != nil && outy != nil && outx.Equal(outy); i++ {
|
||||
opts2 := verbosityPreset(opts, i)
|
||||
outx = opts2.WithDiffMode(diffRemoved).FormatDiff(r.Value, ptrs)
|
||||
outy = opts2.WithDiffMode(diffInserted).FormatDiff(r.Value, ptrs)
|
||||
}
|
||||
if outx != nil {
|
||||
list = append(list, textRecord{Diff: diffRemoved, Key: formatKey(r.Key), Value: outx})
|
||||
keys = append(keys, r.Key)
|
||||
}
|
||||
if outy != nil {
|
||||
list = append(list, textRecord{Diff: diffInserted, Key: formatKey(r.Key), Value: outy})
|
||||
keys = append(keys, r.Key)
|
||||
}
|
||||
default:
|
||||
out := opts.FormatDiff(r.Value)
|
||||
out := opts.FormatDiff(r.Value, ptrs)
|
||||
list = append(list, textRecord{Key: formatKey(r.Key), Value: out})
|
||||
keys = append(keys, r.Key)
|
||||
}
|
||||
}
|
||||
recs = recs[ds.NumDiff():]
|
||||
numDiffs += ds.NumDiff()
|
||||
}
|
||||
assert(len(recs) == 0)
|
||||
return textWrap{"{", list, "}"}
|
||||
if maxGroup.IsZero() {
|
||||
assert(len(recs) == 0)
|
||||
} else {
|
||||
list.AppendEllipsis(maxGroup)
|
||||
for len(keys) < len(list) {
|
||||
keys = append(keys, reflect.Value{})
|
||||
}
|
||||
}
|
||||
assert(len(list) == len(keys))
|
||||
|
||||
// For maps, the default formatting logic uses fmt.Stringer which may
|
||||
// produce ambiguous output. Avoid calling String to disambiguate.
|
||||
if k == reflect.Map {
|
||||
var ambiguous bool
|
||||
seenKeys := map[string]reflect.Value{}
|
||||
for i, currKey := range keys {
|
||||
if currKey.IsValid() {
|
||||
strKey := list[i].Key
|
||||
prevKey, seen := seenKeys[strKey]
|
||||
if seen && prevKey.CanInterface() && currKey.CanInterface() {
|
||||
ambiguous = prevKey.Interface() != currKey.Interface()
|
||||
if ambiguous {
|
||||
break
|
||||
}
|
||||
}
|
||||
seenKeys[strKey] = currKey
|
||||
}
|
||||
}
|
||||
if ambiguous {
|
||||
for i, k := range keys {
|
||||
if k.IsValid() {
|
||||
list[i].Key = formatMapKey(k, true, ptrs)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return &textWrap{Prefix: "{", Value: list, Suffix: "}"}
|
||||
}
|
||||
|
||||
// coalesceAdjacentRecords coalesces the list of records into groups of
|
||||
|
|
|
@ -0,0 +1,264 @@
|
|||
// Copyright 2020, The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE.md file.
|
||||
|
||||
package cmp
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strings"
|
||||
|
||||
"github.com/google/go-cmp/cmp/internal/flags"
|
||||
"github.com/google/go-cmp/cmp/internal/value"
|
||||
)
|
||||
|
||||
const (
|
||||
pointerDelimPrefix = "⟪"
|
||||
pointerDelimSuffix = "⟫"
|
||||
)
|
||||
|
||||
// formatPointer prints the address of the pointer.
|
||||
func formatPointer(p value.Pointer, withDelims bool) string {
|
||||
v := p.Uintptr()
|
||||
if flags.Deterministic {
|
||||
v = 0xdeadf00f // Only used for stable testing purposes
|
||||
}
|
||||
if withDelims {
|
||||
return pointerDelimPrefix + formatHex(uint64(v)) + pointerDelimSuffix
|
||||
}
|
||||
return formatHex(uint64(v))
|
||||
}
|
||||
|
||||
// pointerReferences is a stack of pointers visited so far.
|
||||
type pointerReferences [][2]value.Pointer
|
||||
|
||||
func (ps *pointerReferences) PushPair(vx, vy reflect.Value, d diffMode, deref bool) (pp [2]value.Pointer) {
|
||||
if deref && vx.IsValid() {
|
||||
vx = vx.Addr()
|
||||
}
|
||||
if deref && vy.IsValid() {
|
||||
vy = vy.Addr()
|
||||
}
|
||||
switch d {
|
||||
case diffUnknown, diffIdentical:
|
||||
pp = [2]value.Pointer{value.PointerOf(vx), value.PointerOf(vy)}
|
||||
case diffRemoved:
|
||||
pp = [2]value.Pointer{value.PointerOf(vx), value.Pointer{}}
|
||||
case diffInserted:
|
||||
pp = [2]value.Pointer{value.Pointer{}, value.PointerOf(vy)}
|
||||
}
|
||||
*ps = append(*ps, pp)
|
||||
return pp
|
||||
}
|
||||
|
||||
func (ps *pointerReferences) Push(v reflect.Value) (p value.Pointer, seen bool) {
|
||||
p = value.PointerOf(v)
|
||||
for _, pp := range *ps {
|
||||
if p == pp[0] || p == pp[1] {
|
||||
return p, true
|
||||
}
|
||||
}
|
||||
*ps = append(*ps, [2]value.Pointer{p, p})
|
||||
return p, false
|
||||
}
|
||||
|
||||
func (ps *pointerReferences) Pop() {
|
||||
*ps = (*ps)[:len(*ps)-1]
|
||||
}
|
||||
|
||||
// trunkReferences is metadata for a textNode indicating that the sub-tree
|
||||
// represents the value for either pointer in a pair of references.
|
||||
type trunkReferences struct{ pp [2]value.Pointer }
|
||||
|
||||
// trunkReference is metadata for a textNode indicating that the sub-tree
|
||||
// represents the value for the given pointer reference.
|
||||
type trunkReference struct{ p value.Pointer }
|
||||
|
||||
// leafReference is metadata for a textNode indicating that the value is
|
||||
// truncated as it refers to another part of the tree (i.e., a trunk).
|
||||
type leafReference struct{ p value.Pointer }
|
||||
|
||||
func wrapTrunkReferences(pp [2]value.Pointer, s textNode) textNode {
|
||||
switch {
|
||||
case pp[0].IsNil():
|
||||
return &textWrap{Value: s, Metadata: trunkReference{pp[1]}}
|
||||
case pp[1].IsNil():
|
||||
return &textWrap{Value: s, Metadata: trunkReference{pp[0]}}
|
||||
case pp[0] == pp[1]:
|
||||
return &textWrap{Value: s, Metadata: trunkReference{pp[0]}}
|
||||
default:
|
||||
return &textWrap{Value: s, Metadata: trunkReferences{pp}}
|
||||
}
|
||||
}
|
||||
func wrapTrunkReference(p value.Pointer, printAddress bool, s textNode) textNode {
|
||||
var prefix string
|
||||
if printAddress {
|
||||
prefix = formatPointer(p, true)
|
||||
}
|
||||
return &textWrap{Prefix: prefix, Value: s, Metadata: trunkReference{p}}
|
||||
}
|
||||
func makeLeafReference(p value.Pointer, printAddress bool) textNode {
|
||||
out := &textWrap{Prefix: "(", Value: textEllipsis, Suffix: ")"}
|
||||
var prefix string
|
||||
if printAddress {
|
||||
prefix = formatPointer(p, true)
|
||||
}
|
||||
return &textWrap{Prefix: prefix, Value: out, Metadata: leafReference{p}}
|
||||
}
|
||||
|
||||
// resolveReferences walks the textNode tree searching for any leaf reference
|
||||
// metadata and resolves each against the corresponding trunk references.
|
||||
// Since pointer addresses in memory are not particularly readable to the user,
|
||||
// it replaces each pointer value with an arbitrary and unique reference ID.
|
||||
func resolveReferences(s textNode) {
|
||||
var walkNodes func(textNode, func(textNode))
|
||||
walkNodes = func(s textNode, f func(textNode)) {
|
||||
f(s)
|
||||
switch s := s.(type) {
|
||||
case *textWrap:
|
||||
walkNodes(s.Value, f)
|
||||
case textList:
|
||||
for _, r := range s {
|
||||
walkNodes(r.Value, f)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Collect all trunks and leaves with reference metadata.
|
||||
var trunks, leaves []*textWrap
|
||||
walkNodes(s, func(s textNode) {
|
||||
if s, ok := s.(*textWrap); ok {
|
||||
switch s.Metadata.(type) {
|
||||
case leafReference:
|
||||
leaves = append(leaves, s)
|
||||
case trunkReference, trunkReferences:
|
||||
trunks = append(trunks, s)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
// No leaf references to resolve.
|
||||
if len(leaves) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
// Collect the set of all leaf references to resolve.
|
||||
leafPtrs := make(map[value.Pointer]bool)
|
||||
for _, leaf := range leaves {
|
||||
leafPtrs[leaf.Metadata.(leafReference).p] = true
|
||||
}
|
||||
|
||||
// Collect the set of trunk pointers that are always paired together.
|
||||
// This allows us to assign a single ID to both pointers for brevity.
|
||||
// If a pointer in a pair ever occurs by itself or as a different pair,
|
||||
// then the pair is broken.
|
||||
pairedTrunkPtrs := make(map[value.Pointer]value.Pointer)
|
||||
unpair := func(p value.Pointer) {
|
||||
if !pairedTrunkPtrs[p].IsNil() {
|
||||
pairedTrunkPtrs[pairedTrunkPtrs[p]] = value.Pointer{} // invalidate other half
|
||||
}
|
||||
pairedTrunkPtrs[p] = value.Pointer{} // invalidate this half
|
||||
}
|
||||
for _, trunk := range trunks {
|
||||
switch p := trunk.Metadata.(type) {
|
||||
case trunkReference:
|
||||
unpair(p.p) // standalone pointer cannot be part of a pair
|
||||
case trunkReferences:
|
||||
p0, ok0 := pairedTrunkPtrs[p.pp[0]]
|
||||
p1, ok1 := pairedTrunkPtrs[p.pp[1]]
|
||||
switch {
|
||||
case !ok0 && !ok1:
|
||||
// Register the newly seen pair.
|
||||
pairedTrunkPtrs[p.pp[0]] = p.pp[1]
|
||||
pairedTrunkPtrs[p.pp[1]] = p.pp[0]
|
||||
case ok0 && ok1 && p0 == p.pp[1] && p1 == p.pp[0]:
|
||||
// Exact pair already seen; do nothing.
|
||||
default:
|
||||
// Pair conflicts with some other pair; break all pairs.
|
||||
unpair(p.pp[0])
|
||||
unpair(p.pp[1])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Correlate each pointer referenced by leaves to a unique identifier,
|
||||
// and print the IDs for each trunk that matches those pointers.
|
||||
var nextID uint
|
||||
ptrIDs := make(map[value.Pointer]uint)
|
||||
newID := func() uint {
|
||||
id := nextID
|
||||
nextID++
|
||||
return id
|
||||
}
|
||||
for _, trunk := range trunks {
|
||||
switch p := trunk.Metadata.(type) {
|
||||
case trunkReference:
|
||||
if print := leafPtrs[p.p]; print {
|
||||
id, ok := ptrIDs[p.p]
|
||||
if !ok {
|
||||
id = newID()
|
||||
ptrIDs[p.p] = id
|
||||
}
|
||||
trunk.Prefix = updateReferencePrefix(trunk.Prefix, formatReference(id))
|
||||
}
|
||||
case trunkReferences:
|
||||
print0 := leafPtrs[p.pp[0]]
|
||||
print1 := leafPtrs[p.pp[1]]
|
||||
if print0 || print1 {
|
||||
id0, ok0 := ptrIDs[p.pp[0]]
|
||||
id1, ok1 := ptrIDs[p.pp[1]]
|
||||
isPair := pairedTrunkPtrs[p.pp[0]] == p.pp[1] && pairedTrunkPtrs[p.pp[1]] == p.pp[0]
|
||||
if isPair {
|
||||
var id uint
|
||||
assert(ok0 == ok1) // must be seen together or not at all
|
||||
if ok0 {
|
||||
assert(id0 == id1) // must have the same ID
|
||||
id = id0
|
||||
} else {
|
||||
id = newID()
|
||||
ptrIDs[p.pp[0]] = id
|
||||
ptrIDs[p.pp[1]] = id
|
||||
}
|
||||
trunk.Prefix = updateReferencePrefix(trunk.Prefix, formatReference(id))
|
||||
} else {
|
||||
if print0 && !ok0 {
|
||||
id0 = newID()
|
||||
ptrIDs[p.pp[0]] = id0
|
||||
}
|
||||
if print1 && !ok1 {
|
||||
id1 = newID()
|
||||
ptrIDs[p.pp[1]] = id1
|
||||
}
|
||||
switch {
|
||||
case print0 && print1:
|
||||
trunk.Prefix = updateReferencePrefix(trunk.Prefix, formatReference(id0)+","+formatReference(id1))
|
||||
case print0:
|
||||
trunk.Prefix = updateReferencePrefix(trunk.Prefix, formatReference(id0))
|
||||
case print1:
|
||||
trunk.Prefix = updateReferencePrefix(trunk.Prefix, formatReference(id1))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Update all leaf references with the unique identifier.
|
||||
for _, leaf := range leaves {
|
||||
if id, ok := ptrIDs[leaf.Metadata.(leafReference).p]; ok {
|
||||
leaf.Prefix = updateReferencePrefix(leaf.Prefix, formatReference(id))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func formatReference(id uint) string {
|
||||
return fmt.Sprintf("ref#%d", id)
|
||||
}
|
||||
|
||||
func updateReferencePrefix(prefix, ref string) string {
|
||||
if prefix == "" {
|
||||
return pointerDelimPrefix + ref + pointerDelimSuffix
|
||||
}
|
||||
suffix := strings.TrimPrefix(prefix, pointerDelimPrefix)
|
||||
return pointerDelimPrefix + ref + ": " + suffix
|
||||
}
|
|
@ -5,13 +5,14 @@
|
|||
package cmp
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"strings"
|
||||
"unicode"
|
||||
"unicode/utf8"
|
||||
|
||||
"github.com/google/go-cmp/cmp/internal/flags"
|
||||
"github.com/google/go-cmp/cmp/internal/value"
|
||||
)
|
||||
|
||||
|
@ -20,14 +21,22 @@ type formatValueOptions struct {
|
|||
// methods like error.Error or fmt.Stringer.String.
|
||||
AvoidStringer bool
|
||||
|
||||
// ShallowPointers controls whether to avoid descending into pointers.
|
||||
// Useful when printing map keys, where pointer comparison is performed
|
||||
// on the pointer address rather than the pointed-at value.
|
||||
ShallowPointers bool
|
||||
|
||||
// PrintAddresses controls whether to print the address of all pointers,
|
||||
// slice elements, and maps.
|
||||
PrintAddresses bool
|
||||
|
||||
// QualifiedNames controls whether FormatType uses the fully qualified name
|
||||
// (including the full package path as opposed to just the package name).
|
||||
QualifiedNames bool
|
||||
|
||||
// VerbosityLevel controls the amount of output to produce.
|
||||
// A higher value produces more output. A value of zero or lower produces
|
||||
// no output (represented using an ellipsis).
|
||||
// If LimitVerbosity is false, then the level is treated as infinite.
|
||||
VerbosityLevel int
|
||||
|
||||
// LimitVerbosity specifies that formatting should respect VerbosityLevel.
|
||||
LimitVerbosity bool
|
||||
}
|
||||
|
||||
// FormatType prints the type as if it were wrapping s.
|
||||
|
@ -44,12 +53,15 @@ func (opts formatOptions) FormatType(t reflect.Type, s textNode) textNode {
|
|||
default:
|
||||
return s
|
||||
}
|
||||
if opts.DiffMode == diffIdentical {
|
||||
return s // elide type for identical nodes
|
||||
}
|
||||
case elideType:
|
||||
return s
|
||||
}
|
||||
|
||||
// Determine the type label, applying special handling for unnamed types.
|
||||
typeName := t.String()
|
||||
typeName := value.TypeString(t, opts.QualifiedNames)
|
||||
if t.Name() == "" {
|
||||
// According to Go grammar, certain type literals contain symbols that
|
||||
// do not strongly bind to the next lexicographical token (e.g., *T).
|
||||
|
@ -57,39 +69,77 @@ func (opts formatOptions) FormatType(t reflect.Type, s textNode) textNode {
|
|||
case reflect.Chan, reflect.Func, reflect.Ptr:
|
||||
typeName = "(" + typeName + ")"
|
||||
}
|
||||
typeName = strings.Replace(typeName, "struct {", "struct{", -1)
|
||||
typeName = strings.Replace(typeName, "interface {", "interface{", -1)
|
||||
}
|
||||
return &textWrap{Prefix: typeName, Value: wrapParens(s)}
|
||||
}
|
||||
|
||||
// Avoid wrap the value in parenthesis if unnecessary.
|
||||
if s, ok := s.(textWrap); ok {
|
||||
hasParens := strings.HasPrefix(s.Prefix, "(") && strings.HasSuffix(s.Suffix, ")")
|
||||
hasBraces := strings.HasPrefix(s.Prefix, "{") && strings.HasSuffix(s.Suffix, "}")
|
||||
// wrapParens wraps s with a set of parenthesis, but avoids it if the
|
||||
// wrapped node itself is already surrounded by a pair of parenthesis or braces.
|
||||
// It handles unwrapping one level of pointer-reference nodes.
|
||||
func wrapParens(s textNode) textNode {
|
||||
var refNode *textWrap
|
||||
if s2, ok := s.(*textWrap); ok {
|
||||
// Unwrap a single pointer reference node.
|
||||
switch s2.Metadata.(type) {
|
||||
case leafReference, trunkReference, trunkReferences:
|
||||
refNode = s2
|
||||
if s3, ok := refNode.Value.(*textWrap); ok {
|
||||
s2 = s3
|
||||
}
|
||||
}
|
||||
|
||||
// Already has delimiters that make parenthesis unnecessary.
|
||||
hasParens := strings.HasPrefix(s2.Prefix, "(") && strings.HasSuffix(s2.Suffix, ")")
|
||||
hasBraces := strings.HasPrefix(s2.Prefix, "{") && strings.HasSuffix(s2.Suffix, "}")
|
||||
if hasParens || hasBraces {
|
||||
return textWrap{typeName, s, ""}
|
||||
return s
|
||||
}
|
||||
}
|
||||
return textWrap{typeName + "(", s, ")"}
|
||||
if refNode != nil {
|
||||
refNode.Value = &textWrap{Prefix: "(", Value: refNode.Value, Suffix: ")"}
|
||||
return s
|
||||
}
|
||||
return &textWrap{Prefix: "(", Value: s, Suffix: ")"}
|
||||
}
|
||||
|
||||
// FormatValue prints the reflect.Value, taking extra care to avoid descending
|
||||
// into pointers already in m. As pointers are visited, m is also updated.
|
||||
func (opts formatOptions) FormatValue(v reflect.Value, m visitedPointers) (out textNode) {
|
||||
// into pointers already in ptrs. As pointers are visited, ptrs is also updated.
|
||||
func (opts formatOptions) FormatValue(v reflect.Value, parentKind reflect.Kind, ptrs *pointerReferences) (out textNode) {
|
||||
if !v.IsValid() {
|
||||
return nil
|
||||
}
|
||||
t := v.Type()
|
||||
|
||||
// Check slice element for cycles.
|
||||
if parentKind == reflect.Slice {
|
||||
ptrRef, visited := ptrs.Push(v.Addr())
|
||||
if visited {
|
||||
return makeLeafReference(ptrRef, false)
|
||||
}
|
||||
defer ptrs.Pop()
|
||||
defer func() { out = wrapTrunkReference(ptrRef, false, out) }()
|
||||
}
|
||||
|
||||
// Check whether there is an Error or String method to call.
|
||||
if !opts.AvoidStringer && v.CanInterface() {
|
||||
// Avoid calling Error or String methods on nil receivers since many
|
||||
// implementations crash when doing so.
|
||||
if (t.Kind() != reflect.Ptr && t.Kind() != reflect.Interface) || !v.IsNil() {
|
||||
switch v := v.Interface().(type) {
|
||||
case error:
|
||||
return textLine("e" + formatString(v.Error()))
|
||||
case fmt.Stringer:
|
||||
return textLine("s" + formatString(v.String()))
|
||||
var prefix, strVal string
|
||||
func() {
|
||||
// Swallow and ignore any panics from String or Error.
|
||||
defer func() { recover() }()
|
||||
switch v := v.Interface().(type) {
|
||||
case error:
|
||||
strVal = v.Error()
|
||||
prefix = "e"
|
||||
case fmt.Stringer:
|
||||
strVal = v.String()
|
||||
prefix = "s"
|
||||
}
|
||||
}()
|
||||
if prefix != "" {
|
||||
return opts.formatString(prefix, strVal)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -102,94 +152,140 @@ func (opts formatOptions) FormatValue(v reflect.Value, m visitedPointers) (out t
|
|||
}
|
||||
}()
|
||||
|
||||
var ptr string
|
||||
switch t.Kind() {
|
||||
case reflect.Bool:
|
||||
return textLine(fmt.Sprint(v.Bool()))
|
||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||
return textLine(fmt.Sprint(v.Int()))
|
||||
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
|
||||
// Unnamed uints are usually bytes or words, so use hexadecimal.
|
||||
if t.PkgPath() == "" || t.Kind() == reflect.Uintptr {
|
||||
case reflect.Uint, reflect.Uint16, reflect.Uint32, reflect.Uint64:
|
||||
return textLine(fmt.Sprint(v.Uint()))
|
||||
case reflect.Uint8:
|
||||
if parentKind == reflect.Slice || parentKind == reflect.Array {
|
||||
return textLine(formatHex(v.Uint()))
|
||||
}
|
||||
return textLine(fmt.Sprint(v.Uint()))
|
||||
case reflect.Uintptr:
|
||||
return textLine(formatHex(v.Uint()))
|
||||
case reflect.Float32, reflect.Float64:
|
||||
return textLine(fmt.Sprint(v.Float()))
|
||||
case reflect.Complex64, reflect.Complex128:
|
||||
return textLine(fmt.Sprint(v.Complex()))
|
||||
case reflect.String:
|
||||
return textLine(formatString(v.String()))
|
||||
return opts.formatString("", v.String())
|
||||
case reflect.UnsafePointer, reflect.Chan, reflect.Func:
|
||||
return textLine(formatPointer(v))
|
||||
return textLine(formatPointer(value.PointerOf(v), true))
|
||||
case reflect.Struct:
|
||||
var list textList
|
||||
v := makeAddressable(v) // needed for retrieveUnexportedField
|
||||
maxLen := v.NumField()
|
||||
if opts.LimitVerbosity {
|
||||
maxLen = ((1 << opts.verbosity()) >> 1) << 2 // 0, 4, 8, 16, 32, etc...
|
||||
opts.VerbosityLevel--
|
||||
}
|
||||
for i := 0; i < v.NumField(); i++ {
|
||||
vv := v.Field(i)
|
||||
if value.IsZero(vv) {
|
||||
continue // Elide fields with zero values
|
||||
}
|
||||
s := opts.WithTypeMode(autoType).FormatValue(vv, m)
|
||||
list = append(list, textRecord{Key: t.Field(i).Name, Value: s})
|
||||
if len(list) == maxLen {
|
||||
list.AppendEllipsis(diffStats{})
|
||||
break
|
||||
}
|
||||
sf := t.Field(i)
|
||||
if supportExporters && !isExported(sf.Name) {
|
||||
vv = retrieveUnexportedField(v, sf, true)
|
||||
}
|
||||
s := opts.WithTypeMode(autoType).FormatValue(vv, t.Kind(), ptrs)
|
||||
list = append(list, textRecord{Key: sf.Name, Value: s})
|
||||
}
|
||||
return textWrap{"{", list, "}"}
|
||||
return &textWrap{Prefix: "{", Value: list, Suffix: "}"}
|
||||
case reflect.Slice:
|
||||
if v.IsNil() {
|
||||
return textNil
|
||||
}
|
||||
if opts.PrintAddresses {
|
||||
ptr = formatPointer(v)
|
||||
|
||||
// Check whether this is a []byte of text data.
|
||||
if t.Elem() == reflect.TypeOf(byte(0)) {
|
||||
b := v.Bytes()
|
||||
isPrintSpace := func(r rune) bool { return unicode.IsPrint(r) && unicode.IsSpace(r) }
|
||||
if len(b) > 0 && utf8.Valid(b) && len(bytes.TrimFunc(b, isPrintSpace)) == 0 {
|
||||
out = opts.formatString("", string(b))
|
||||
return opts.WithTypeMode(emitType).FormatType(t, out)
|
||||
}
|
||||
}
|
||||
|
||||
fallthrough
|
||||
case reflect.Array:
|
||||
maxLen := v.Len()
|
||||
if opts.LimitVerbosity {
|
||||
maxLen = ((1 << opts.verbosity()) >> 1) << 2 // 0, 4, 8, 16, 32, etc...
|
||||
opts.VerbosityLevel--
|
||||
}
|
||||
var list textList
|
||||
for i := 0; i < v.Len(); i++ {
|
||||
vi := v.Index(i)
|
||||
if vi.CanAddr() { // Check for cyclic elements
|
||||
p := vi.Addr()
|
||||
if m.Visit(p) {
|
||||
var out textNode
|
||||
out = textLine(formatPointer(p))
|
||||
out = opts.WithTypeMode(emitType).FormatType(p.Type(), out)
|
||||
out = textWrap{"*", out, ""}
|
||||
list = append(list, textRecord{Value: out})
|
||||
continue
|
||||
}
|
||||
if len(list) == maxLen {
|
||||
list.AppendEllipsis(diffStats{})
|
||||
break
|
||||
}
|
||||
s := opts.WithTypeMode(elideType).FormatValue(vi, m)
|
||||
s := opts.WithTypeMode(elideType).FormatValue(v.Index(i), t.Kind(), ptrs)
|
||||
list = append(list, textRecord{Value: s})
|
||||
}
|
||||
return textWrap{ptr + "{", list, "}"}
|
||||
|
||||
out = &textWrap{Prefix: "{", Value: list, Suffix: "}"}
|
||||
if t.Kind() == reflect.Slice && opts.PrintAddresses {
|
||||
header := fmt.Sprintf("ptr:%v, len:%d, cap:%d", formatPointer(value.PointerOf(v), false), v.Len(), v.Cap())
|
||||
out = &textWrap{Prefix: pointerDelimPrefix + header + pointerDelimSuffix, Value: out}
|
||||
}
|
||||
return out
|
||||
case reflect.Map:
|
||||
if v.IsNil() {
|
||||
return textNil
|
||||
}
|
||||
if m.Visit(v) {
|
||||
return textLine(formatPointer(v))
|
||||
}
|
||||
|
||||
// Check pointer for cycles.
|
||||
ptrRef, visited := ptrs.Push(v)
|
||||
if visited {
|
||||
return makeLeafReference(ptrRef, opts.PrintAddresses)
|
||||
}
|
||||
defer ptrs.Pop()
|
||||
|
||||
maxLen := v.Len()
|
||||
if opts.LimitVerbosity {
|
||||
maxLen = ((1 << opts.verbosity()) >> 1) << 2 // 0, 4, 8, 16, 32, etc...
|
||||
opts.VerbosityLevel--
|
||||
}
|
||||
var list textList
|
||||
for _, k := range value.SortKeys(v.MapKeys()) {
|
||||
sk := formatMapKey(k)
|
||||
sv := opts.WithTypeMode(elideType).FormatValue(v.MapIndex(k), m)
|
||||
if len(list) == maxLen {
|
||||
list.AppendEllipsis(diffStats{})
|
||||
break
|
||||
}
|
||||
sk := formatMapKey(k, false, ptrs)
|
||||
sv := opts.WithTypeMode(elideType).FormatValue(v.MapIndex(k), t.Kind(), ptrs)
|
||||
list = append(list, textRecord{Key: sk, Value: sv})
|
||||
}
|
||||
if opts.PrintAddresses {
|
||||
ptr = formatPointer(v)
|
||||
}
|
||||
return textWrap{ptr + "{", list, "}"}
|
||||
|
||||
out = &textWrap{Prefix: "{", Value: list, Suffix: "}"}
|
||||
out = wrapTrunkReference(ptrRef, opts.PrintAddresses, out)
|
||||
return out
|
||||
case reflect.Ptr:
|
||||
if v.IsNil() {
|
||||
return textNil
|
||||
}
|
||||
if m.Visit(v) || opts.ShallowPointers {
|
||||
return textLine(formatPointer(v))
|
||||
}
|
||||
if opts.PrintAddresses {
|
||||
ptr = formatPointer(v)
|
||||
|
||||
// Check pointer for cycles.
|
||||
ptrRef, visited := ptrs.Push(v)
|
||||
if visited {
|
||||
out = makeLeafReference(ptrRef, opts.PrintAddresses)
|
||||
return &textWrap{Prefix: "&", Value: out}
|
||||
}
|
||||
defer ptrs.Pop()
|
||||
|
||||
skipType = true // Let the underlying value print the type instead
|
||||
return textWrap{"&" + ptr, opts.FormatValue(v.Elem(), m), ""}
|
||||
out = opts.FormatValue(v.Elem(), t.Kind(), ptrs)
|
||||
out = wrapTrunkReference(ptrRef, opts.PrintAddresses, out)
|
||||
out = &textWrap{Prefix: "&", Value: out}
|
||||
return out
|
||||
case reflect.Interface:
|
||||
if v.IsNil() {
|
||||
return textNil
|
||||
|
@ -197,19 +293,65 @@ func (opts formatOptions) FormatValue(v reflect.Value, m visitedPointers) (out t
|
|||
// Interfaces accept different concrete types,
|
||||
// so configure the underlying value to explicitly print the type.
|
||||
skipType = true // Print the concrete type instead
|
||||
return opts.WithTypeMode(emitType).FormatValue(v.Elem(), m)
|
||||
return opts.WithTypeMode(emitType).FormatValue(v.Elem(), t.Kind(), ptrs)
|
||||
default:
|
||||
panic(fmt.Sprintf("%v kind not handled", v.Kind()))
|
||||
}
|
||||
}
|
||||
|
||||
func (opts formatOptions) formatString(prefix, s string) textNode {
|
||||
maxLen := len(s)
|
||||
maxLines := strings.Count(s, "\n") + 1
|
||||
if opts.LimitVerbosity {
|
||||
maxLen = (1 << opts.verbosity()) << 5 // 32, 64, 128, 256, etc...
|
||||
maxLines = (1 << opts.verbosity()) << 2 // 4, 8, 16, 32, 64, etc...
|
||||
}
|
||||
|
||||
// For multiline strings, use the triple-quote syntax,
|
||||
// but only use it when printing removed or inserted nodes since
|
||||
// we only want the extra verbosity for those cases.
|
||||
lines := strings.Split(strings.TrimSuffix(s, "\n"), "\n")
|
||||
isTripleQuoted := len(lines) >= 4 && (opts.DiffMode == '-' || opts.DiffMode == '+')
|
||||
for i := 0; i < len(lines) && isTripleQuoted; i++ {
|
||||
lines[i] = strings.TrimPrefix(strings.TrimSuffix(lines[i], "\r"), "\r") // trim leading/trailing carriage returns for legacy Windows endline support
|
||||
isPrintable := func(r rune) bool {
|
||||
return unicode.IsPrint(r) || r == '\t' // specially treat tab as printable
|
||||
}
|
||||
line := lines[i]
|
||||
isTripleQuoted = !strings.HasPrefix(strings.TrimPrefix(line, prefix), `"""`) && !strings.HasPrefix(line, "...") && strings.TrimFunc(line, isPrintable) == "" && len(line) <= maxLen
|
||||
}
|
||||
if isTripleQuoted {
|
||||
var list textList
|
||||
list = append(list, textRecord{Diff: opts.DiffMode, Value: textLine(prefix + `"""`), ElideComma: true})
|
||||
for i, line := range lines {
|
||||
if numElided := len(lines) - i; i == maxLines-1 && numElided > 1 {
|
||||
comment := commentString(fmt.Sprintf("%d elided lines", numElided))
|
||||
list = append(list, textRecord{Diff: opts.DiffMode, Value: textEllipsis, ElideComma: true, Comment: comment})
|
||||
break
|
||||
}
|
||||
list = append(list, textRecord{Diff: opts.DiffMode, Value: textLine(line), ElideComma: true})
|
||||
}
|
||||
list = append(list, textRecord{Diff: opts.DiffMode, Value: textLine(prefix + `"""`), ElideComma: true})
|
||||
return &textWrap{Prefix: "(", Value: list, Suffix: ")"}
|
||||
}
|
||||
|
||||
// Format the string as a single-line quoted string.
|
||||
if len(s) > maxLen+len(textEllipsis) {
|
||||
return textLine(prefix + formatString(s[:maxLen]) + string(textEllipsis))
|
||||
}
|
||||
return textLine(prefix + formatString(s))
|
||||
}
|
||||
|
||||
// formatMapKey formats v as if it were a map key.
|
||||
// The result is guaranteed to be a single line.
|
||||
func formatMapKey(v reflect.Value) string {
|
||||
func formatMapKey(v reflect.Value, disambiguate bool, ptrs *pointerReferences) string {
|
||||
var opts formatOptions
|
||||
opts.DiffMode = diffIdentical
|
||||
opts.TypeMode = elideType
|
||||
opts.ShallowPointers = true
|
||||
s := opts.FormatValue(v, visitedPointers{}).String()
|
||||
opts.PrintAddresses = disambiguate
|
||||
opts.AvoidStringer = disambiguate
|
||||
opts.QualifiedNames = disambiguate
|
||||
s := opts.FormatValue(v, reflect.Map, ptrs).String()
|
||||
return strings.TrimSpace(s)
|
||||
}
|
||||
|
||||
|
@ -227,7 +369,7 @@ func formatString(s string) string {
|
|||
rawInvalid := func(r rune) bool {
|
||||
return r == '`' || r == '\n' || !(unicode.IsPrint(r) || r == '\t')
|
||||
}
|
||||
if strings.IndexFunc(s, rawInvalid) < 0 {
|
||||
if utf8.ValidString(s) && strings.IndexFunc(s, rawInvalid) < 0 {
|
||||
return "`" + s + "`"
|
||||
}
|
||||
return qs
|
||||
|
@ -256,23 +398,3 @@ func formatHex(u uint64) string {
|
|||
}
|
||||
return fmt.Sprintf(f, u)
|
||||
}
|
||||
|
||||
// formatPointer prints the address of the pointer.
|
||||
func formatPointer(v reflect.Value) string {
|
||||
p := v.Pointer()
|
||||
if flags.Deterministic {
|
||||
p = 0xdeadf00f // Only used for stable testing purposes
|
||||
}
|
||||
return fmt.Sprintf("⟪0x%x⟫", p)
|
||||
}
|
||||
|
||||
type visitedPointers map[value.Pointer]struct{}
|
||||
|
||||
// Visit inserts pointer v into the visited map and reports whether it had
|
||||
// already been visited before.
|
||||
func (m visitedPointers) Visit(v reflect.Value) bool {
|
||||
p := value.PointerOf(v)
|
||||
_, visited := m[p]
|
||||
m[p] = struct{}{}
|
||||
return visited
|
||||
}
|
||||
|
|
|
@ -8,6 +8,7 @@ import (
|
|||
"bytes"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"strings"
|
||||
"unicode"
|
||||
"unicode/utf8"
|
||||
|
@ -23,11 +24,25 @@ func (opts formatOptions) CanFormatDiffSlice(v *valueNode) bool {
|
|||
return false // Must be formatting in diff mode
|
||||
case v.NumDiff == 0:
|
||||
return false // No differences detected
|
||||
case v.NumIgnored+v.NumCompared+v.NumTransformed > 0:
|
||||
// TODO: Handle the case where someone uses bytes.Equal on a large slice.
|
||||
return false // Some custom option was used to determined equality
|
||||
case !v.ValueX.IsValid() || !v.ValueY.IsValid():
|
||||
return false // Both values must be valid
|
||||
case v.Type.Kind() == reflect.Slice && (v.ValueX.Len() == 0 || v.ValueY.Len() == 0):
|
||||
return false // Both slice values have to be non-empty
|
||||
case v.NumIgnored > 0:
|
||||
return false // Some ignore option was used
|
||||
case v.NumTransformed > 0:
|
||||
return false // Some transform option was used
|
||||
case v.NumCompared > 1:
|
||||
return false // More than one comparison was used
|
||||
case v.NumCompared == 1 && v.Type.Name() != "":
|
||||
// The need for cmp to check applicability of options on every element
|
||||
// in a slice is a significant performance detriment for large []byte.
|
||||
// The workaround is to specify Comparer(bytes.Equal),
|
||||
// which enables cmp to compare []byte more efficiently.
|
||||
// If they differ, we still want to provide batched diffing.
|
||||
// The logic disallows named types since they tend to have their own
|
||||
// String method, with nicer formatting than what this provides.
|
||||
return false
|
||||
}
|
||||
|
||||
switch t := v.Type; t.Kind() {
|
||||
|
@ -82,7 +97,7 @@ func (opts formatOptions) FormatDiffSlice(v *valueNode) textNode {
|
|||
}
|
||||
if isText || isBinary {
|
||||
var numLines, lastLineIdx, maxLineLen int
|
||||
isBinary = false
|
||||
isBinary = !utf8.ValidString(sx) || !utf8.ValidString(sy)
|
||||
for i, r := range sx + sy {
|
||||
if !(unicode.IsPrint(r) || unicode.IsSpace(r)) || r == utf8.RuneError {
|
||||
isBinary = true
|
||||
|
@ -97,7 +112,7 @@ func (opts formatOptions) FormatDiffSlice(v *valueNode) textNode {
|
|||
}
|
||||
}
|
||||
isText = !isBinary
|
||||
isLinedText = isText && numLines >= 4 && maxLineLen <= 256
|
||||
isLinedText = isText && numLines >= 4 && maxLineLen <= 1024
|
||||
}
|
||||
|
||||
// Format the string into printable records.
|
||||
|
@ -117,6 +132,83 @@ func (opts formatOptions) FormatDiffSlice(v *valueNode) textNode {
|
|||
},
|
||||
)
|
||||
delim = "\n"
|
||||
|
||||
// If possible, use a custom triple-quote (""") syntax for printing
|
||||
// differences in a string literal. This format is more readable,
|
||||
// but has edge-cases where differences are visually indistinguishable.
|
||||
// This format is avoided under the following conditions:
|
||||
// • A line starts with `"""`
|
||||
// • A line starts with "..."
|
||||
// • A line contains non-printable characters
|
||||
// • Adjacent different lines differ only by whitespace
|
||||
//
|
||||
// For example:
|
||||
// """
|
||||
// ... // 3 identical lines
|
||||
// foo
|
||||
// bar
|
||||
// - baz
|
||||
// + BAZ
|
||||
// """
|
||||
isTripleQuoted := true
|
||||
prevRemoveLines := map[string]bool{}
|
||||
prevInsertLines := map[string]bool{}
|
||||
var list2 textList
|
||||
list2 = append(list2, textRecord{Value: textLine(`"""`), ElideComma: true})
|
||||
for _, r := range list {
|
||||
if !r.Value.Equal(textEllipsis) {
|
||||
line, _ := strconv.Unquote(string(r.Value.(textLine)))
|
||||
line = strings.TrimPrefix(strings.TrimSuffix(line, "\r"), "\r") // trim leading/trailing carriage returns for legacy Windows endline support
|
||||
normLine := strings.Map(func(r rune) rune {
|
||||
if unicode.IsSpace(r) {
|
||||
return -1 // drop whitespace to avoid visually indistinguishable output
|
||||
}
|
||||
return r
|
||||
}, line)
|
||||
isPrintable := func(r rune) bool {
|
||||
return unicode.IsPrint(r) || r == '\t' // specially treat tab as printable
|
||||
}
|
||||
isTripleQuoted = !strings.HasPrefix(line, `"""`) && !strings.HasPrefix(line, "...") && strings.TrimFunc(line, isPrintable) == ""
|
||||
switch r.Diff {
|
||||
case diffRemoved:
|
||||
isTripleQuoted = isTripleQuoted && !prevInsertLines[normLine]
|
||||
prevRemoveLines[normLine] = true
|
||||
case diffInserted:
|
||||
isTripleQuoted = isTripleQuoted && !prevRemoveLines[normLine]
|
||||
prevInsertLines[normLine] = true
|
||||
}
|
||||
if !isTripleQuoted {
|
||||
break
|
||||
}
|
||||
r.Value = textLine(line)
|
||||
r.ElideComma = true
|
||||
}
|
||||
if !(r.Diff == diffRemoved || r.Diff == diffInserted) { // start a new non-adjacent difference group
|
||||
prevRemoveLines = map[string]bool{}
|
||||
prevInsertLines = map[string]bool{}
|
||||
}
|
||||
list2 = append(list2, r)
|
||||
}
|
||||
if r := list2[len(list2)-1]; r.Diff == diffIdentical && len(r.Value.(textLine)) == 0 {
|
||||
list2 = list2[:len(list2)-1] // elide single empty line at the end
|
||||
}
|
||||
list2 = append(list2, textRecord{Value: textLine(`"""`), ElideComma: true})
|
||||
if isTripleQuoted {
|
||||
var out textNode = &textWrap{Prefix: "(", Value: list2, Suffix: ")"}
|
||||
switch t.Kind() {
|
||||
case reflect.String:
|
||||
if t != reflect.TypeOf(string("")) {
|
||||
out = opts.FormatType(t, out)
|
||||
}
|
||||
case reflect.Slice:
|
||||
// Always emit type for slices since the triple-quote syntax
|
||||
// looks like a string (not a slice).
|
||||
opts = opts.WithTypeMode(emitType)
|
||||
out = opts.FormatType(t, out)
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// If the text appears to be single-lined text,
|
||||
// then perform differencing in approximately fixed-sized chunks.
|
||||
// The output is printed as quoted strings.
|
||||
|
@ -129,6 +221,7 @@ func (opts formatOptions) FormatDiffSlice(v *valueNode) textNode {
|
|||
},
|
||||
)
|
||||
delim = ""
|
||||
|
||||
// If the text appears to be binary data,
|
||||
// then perform differencing in approximately fixed-sized chunks.
|
||||
// The output is inspired by hexdump.
|
||||
|
@ -145,6 +238,7 @@ func (opts formatOptions) FormatDiffSlice(v *valueNode) textNode {
|
|||
return textRecord{Diff: d, Value: textLine(s), Comment: comment}
|
||||
},
|
||||
)
|
||||
|
||||
// For all other slices of primitive types,
|
||||
// then perform differencing in approximately fixed-sized chunks.
|
||||
// The size of each chunk depends on the width of the element kind.
|
||||
|
@ -172,7 +266,9 @@ func (opts formatOptions) FormatDiffSlice(v *valueNode) textNode {
|
|||
switch t.Elem().Kind() {
|
||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||
ss = append(ss, fmt.Sprint(v.Index(i).Int()))
|
||||
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
|
||||
case reflect.Uint, reflect.Uint16, reflect.Uint32, reflect.Uint64:
|
||||
ss = append(ss, fmt.Sprint(v.Index(i).Uint()))
|
||||
case reflect.Uint8, reflect.Uintptr:
|
||||
ss = append(ss, formatHex(v.Index(i).Uint()))
|
||||
case reflect.Bool, reflect.Float32, reflect.Float64, reflect.Complex64, reflect.Complex128:
|
||||
ss = append(ss, fmt.Sprint(v.Index(i).Interface()))
|
||||
|
@ -185,7 +281,7 @@ func (opts formatOptions) FormatDiffSlice(v *valueNode) textNode {
|
|||
}
|
||||
|
||||
// Wrap the output with appropriate type information.
|
||||
var out textNode = textWrap{"{", list, "}"}
|
||||
var out textNode = &textWrap{Prefix: "{", Value: list, Suffix: "}"}
|
||||
if !isText {
|
||||
// The "{...}" byte-sequence literal is not valid Go syntax for strings.
|
||||
// Emit the type for extra clarity (e.g. "string{...}").
|
||||
|
@ -196,12 +292,12 @@ func (opts formatOptions) FormatDiffSlice(v *valueNode) textNode {
|
|||
}
|
||||
switch t.Kind() {
|
||||
case reflect.String:
|
||||
out = textWrap{"strings.Join(", out, fmt.Sprintf(", %q)", delim)}
|
||||
out = &textWrap{Prefix: "strings.Join(", Value: out, Suffix: fmt.Sprintf(", %q)", delim)}
|
||||
if t != reflect.TypeOf(string("")) {
|
||||
out = opts.FormatType(t, out)
|
||||
}
|
||||
case reflect.Slice:
|
||||
out = textWrap{"bytes.Join(", out, fmt.Sprintf(", %q)", delim)}
|
||||
out = &textWrap{Prefix: "bytes.Join(", Value: out, Suffix: fmt.Sprintf(", %q)", delim)}
|
||||
if t != reflect.TypeOf([]byte(nil)) {
|
||||
out = opts.FormatType(t, out)
|
||||
}
|
||||
|
@ -242,9 +338,22 @@ func (opts formatOptions) formatDiffSlice(
|
|||
return n0 - v.Len()
|
||||
}
|
||||
|
||||
var numDiffs int
|
||||
maxLen := -1
|
||||
if opts.LimitVerbosity {
|
||||
maxLen = (1 << opts.verbosity()) << 2 // 4, 8, 16, 32, 64, etc...
|
||||
opts.VerbosityLevel--
|
||||
}
|
||||
|
||||
groups := coalesceAdjacentEdits(name, es)
|
||||
groups = coalesceInterveningIdentical(groups, chunkSize/4)
|
||||
maxGroup := diffStats{Name: name}
|
||||
for i, ds := range groups {
|
||||
if maxLen >= 0 && numDiffs >= maxLen {
|
||||
maxGroup = maxGroup.Append(ds)
|
||||
continue
|
||||
}
|
||||
|
||||
// Print equal.
|
||||
if ds.NumDiff() == 0 {
|
||||
// Compute the number of leading and trailing equal bytes to print.
|
||||
|
@ -273,12 +382,18 @@ func (opts formatOptions) formatDiffSlice(
|
|||
}
|
||||
|
||||
// Print unequal.
|
||||
len0 := len(list)
|
||||
nx := appendChunks(vx.Slice(0, ds.NumIdentical+ds.NumRemoved+ds.NumModified), diffRemoved)
|
||||
vx = vx.Slice(nx, vx.Len())
|
||||
ny := appendChunks(vy.Slice(0, ds.NumIdentical+ds.NumInserted+ds.NumModified), diffInserted)
|
||||
vy = vy.Slice(ny, vy.Len())
|
||||
numDiffs += len(list) - len0
|
||||
}
|
||||
if maxGroup.IsZero() {
|
||||
assert(vx.Len() == 0 && vy.Len() == 0)
|
||||
} else {
|
||||
list.AppendEllipsis(maxGroup)
|
||||
}
|
||||
assert(vx.Len() == 0 && vy.Len() == 0)
|
||||
return list
|
||||
}
|
||||
|
||||
|
|
|
@ -10,12 +10,15 @@ import (
|
|||
"math/rand"
|
||||
"strings"
|
||||
"time"
|
||||
"unicode/utf8"
|
||||
|
||||
"github.com/google/go-cmp/cmp/internal/flags"
|
||||
)
|
||||
|
||||
var randBool = rand.New(rand.NewSource(time.Now().Unix())).Intn(2) == 0
|
||||
|
||||
const maxColumnLength = 80
|
||||
|
||||
type indentMode int
|
||||
|
||||
func (n indentMode) appendIndent(b []byte, d diffMode) []byte {
|
||||
|
@ -91,21 +94,22 @@ type textNode interface {
|
|||
// textWrap is a wrapper that concatenates a prefix and/or a suffix
|
||||
// to the underlying node.
|
||||
type textWrap struct {
|
||||
Prefix string // e.g., "bytes.Buffer{"
|
||||
Value textNode // textWrap | textList | textLine
|
||||
Suffix string // e.g., "}"
|
||||
Prefix string // e.g., "bytes.Buffer{"
|
||||
Value textNode // textWrap | textList | textLine
|
||||
Suffix string // e.g., "}"
|
||||
Metadata interface{} // arbitrary metadata; has no effect on formatting
|
||||
}
|
||||
|
||||
func (s textWrap) Len() int {
|
||||
func (s *textWrap) Len() int {
|
||||
return len(s.Prefix) + s.Value.Len() + len(s.Suffix)
|
||||
}
|
||||
func (s1 textWrap) Equal(s2 textNode) bool {
|
||||
if s2, ok := s2.(textWrap); ok {
|
||||
func (s1 *textWrap) Equal(s2 textNode) bool {
|
||||
if s2, ok := s2.(*textWrap); ok {
|
||||
return s1.Prefix == s2.Prefix && s1.Value.Equal(s2.Value) && s1.Suffix == s2.Suffix
|
||||
}
|
||||
return false
|
||||
}
|
||||
func (s textWrap) String() string {
|
||||
func (s *textWrap) String() string {
|
||||
var d diffMode
|
||||
var n indentMode
|
||||
_, s2 := s.formatCompactTo(nil, d)
|
||||
|
@ -114,7 +118,7 @@ func (s textWrap) String() string {
|
|||
b = append(b, '\n') // Trailing newline
|
||||
return string(b)
|
||||
}
|
||||
func (s textWrap) formatCompactTo(b []byte, d diffMode) ([]byte, textNode) {
|
||||
func (s *textWrap) formatCompactTo(b []byte, d diffMode) ([]byte, textNode) {
|
||||
n0 := len(b) // Original buffer length
|
||||
b = append(b, s.Prefix...)
|
||||
b, s.Value = s.Value.formatCompactTo(b, d)
|
||||
|
@ -124,7 +128,7 @@ func (s textWrap) formatCompactTo(b []byte, d diffMode) ([]byte, textNode) {
|
|||
}
|
||||
return b, s
|
||||
}
|
||||
func (s textWrap) formatExpandedTo(b []byte, d diffMode, n indentMode) []byte {
|
||||
func (s *textWrap) formatExpandedTo(b []byte, d diffMode, n indentMode) []byte {
|
||||
b = append(b, s.Prefix...)
|
||||
b = s.Value.formatExpandedTo(b, d, n)
|
||||
b = append(b, s.Suffix...)
|
||||
|
@ -136,22 +140,23 @@ func (s textWrap) formatExpandedTo(b []byte, d diffMode, n indentMode) []byte {
|
|||
// of the textList.formatCompactTo method.
|
||||
type textList []textRecord
|
||||
type textRecord struct {
|
||||
Diff diffMode // e.g., 0 or '-' or '+'
|
||||
Key string // e.g., "MyField"
|
||||
Value textNode // textWrap | textLine
|
||||
Comment fmt.Stringer // e.g., "6 identical fields"
|
||||
Diff diffMode // e.g., 0 or '-' or '+'
|
||||
Key string // e.g., "MyField"
|
||||
Value textNode // textWrap | textLine
|
||||
ElideComma bool // avoid trailing comma
|
||||
Comment fmt.Stringer // e.g., "6 identical fields"
|
||||
}
|
||||
|
||||
// AppendEllipsis appends a new ellipsis node to the list if none already
|
||||
// exists at the end. If cs is non-zero it coalesces the statistics with the
|
||||
// previous diffStats.
|
||||
func (s *textList) AppendEllipsis(ds diffStats) {
|
||||
hasStats := ds != diffStats{}
|
||||
hasStats := !ds.IsZero()
|
||||
if len(*s) == 0 || !(*s)[len(*s)-1].Value.Equal(textEllipsis) {
|
||||
if hasStats {
|
||||
*s = append(*s, textRecord{Value: textEllipsis, Comment: ds})
|
||||
*s = append(*s, textRecord{Value: textEllipsis, ElideComma: true, Comment: ds})
|
||||
} else {
|
||||
*s = append(*s, textRecord{Value: textEllipsis})
|
||||
*s = append(*s, textRecord{Value: textEllipsis, ElideComma: true})
|
||||
}
|
||||
return
|
||||
}
|
||||
|
@ -191,7 +196,7 @@ func (s1 textList) Equal(s2 textNode) bool {
|
|||
}
|
||||
|
||||
func (s textList) String() string {
|
||||
return textWrap{"{", s, "}"}.String()
|
||||
return (&textWrap{Prefix: "{", Value: s, Suffix: "}"}).String()
|
||||
}
|
||||
|
||||
func (s textList) formatCompactTo(b []byte, d diffMode) ([]byte, textNode) {
|
||||
|
@ -221,7 +226,7 @@ func (s textList) formatCompactTo(b []byte, d diffMode) ([]byte, textNode) {
|
|||
}
|
||||
// Force multi-lined output when printing a removed/inserted node that
|
||||
// is sufficiently long.
|
||||
if (d == diffInserted || d == diffRemoved) && len(b[n0:]) > 80 {
|
||||
if (d == diffInserted || d == diffRemoved) && len(b[n0:]) > maxColumnLength {
|
||||
multiLine = true
|
||||
}
|
||||
if !multiLine {
|
||||
|
@ -236,16 +241,50 @@ func (s textList) formatExpandedTo(b []byte, d diffMode, n indentMode) []byte {
|
|||
_, isLine := r.Value.(textLine)
|
||||
return r.Key == "" || !isLine
|
||||
},
|
||||
func(r textRecord) int { return len(r.Key) },
|
||||
func(r textRecord) int { return utf8.RuneCountInString(r.Key) },
|
||||
)
|
||||
alignValueLens := s.alignLens(
|
||||
func(r textRecord) bool {
|
||||
_, isLine := r.Value.(textLine)
|
||||
return !isLine || r.Value.Equal(textEllipsis) || r.Comment == nil
|
||||
},
|
||||
func(r textRecord) int { return len(r.Value.(textLine)) },
|
||||
func(r textRecord) int { return utf8.RuneCount(r.Value.(textLine)) },
|
||||
)
|
||||
|
||||
// Format lists of simple lists in a batched form.
|
||||
// If the list is sequence of only textLine values,
|
||||
// then batch multiple values on a single line.
|
||||
var isSimple bool
|
||||
for _, r := range s {
|
||||
_, isLine := r.Value.(textLine)
|
||||
isSimple = r.Diff == 0 && r.Key == "" && isLine && r.Comment == nil
|
||||
if !isSimple {
|
||||
break
|
||||
}
|
||||
}
|
||||
if isSimple {
|
||||
n++
|
||||
var batch []byte
|
||||
emitBatch := func() {
|
||||
if len(batch) > 0 {
|
||||
b = n.appendIndent(append(b, '\n'), d)
|
||||
b = append(b, bytes.TrimRight(batch, " ")...)
|
||||
batch = batch[:0]
|
||||
}
|
||||
}
|
||||
for _, r := range s {
|
||||
line := r.Value.(textLine)
|
||||
if len(batch)+len(line)+len(", ") > maxColumnLength {
|
||||
emitBatch()
|
||||
}
|
||||
batch = append(batch, line...)
|
||||
batch = append(batch, ", "...)
|
||||
}
|
||||
emitBatch()
|
||||
n--
|
||||
return n.appendIndent(append(b, '\n'), d)
|
||||
}
|
||||
|
||||
// Format the list as a multi-lined output.
|
||||
n++
|
||||
for i, r := range s {
|
||||
|
@ -256,7 +295,7 @@ func (s textList) formatExpandedTo(b []byte, d diffMode, n indentMode) []byte {
|
|||
b = alignKeyLens[i].appendChar(b, ' ')
|
||||
|
||||
b = r.Value.formatExpandedTo(b, d|r.Diff, n)
|
||||
if !r.Value.Equal(textEllipsis) {
|
||||
if !r.ElideComma {
|
||||
b = append(b, ',')
|
||||
}
|
||||
b = alignValueLens[i].appendChar(b, ' ')
|
||||
|
@ -332,6 +371,11 @@ type diffStats struct {
|
|||
NumModified int
|
||||
}
|
||||
|
||||
func (s diffStats) IsZero() bool {
|
||||
s.Name = ""
|
||||
return s == diffStats{}
|
||||
}
|
||||
|
||||
func (s diffStats) NumDiff() int {
|
||||
return s.NumRemoved + s.NumInserted + s.NumModified
|
||||
}
|
||||
|
|
|
@ -1,9 +0,0 @@
|
|||
(The MIT License)
|
||||
|
||||
Copyright (c) 2017 marvin + konsorten GmbH (open-source@konsorten.de)
|
||||
|
||||
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.
|
|
@ -1,42 +0,0 @@
|
|||
# Windows Terminal Sequences
|
||||
|
||||
This library allow for enabling Windows terminal color support for Go.
|
||||
|
||||
See [Console Virtual Terminal Sequences](https://docs.microsoft.com/en-us/windows/console/console-virtual-terminal-sequences) for details.
|
||||
|
||||
## Usage
|
||||
|
||||
```go
|
||||
import (
|
||||
"syscall"
|
||||
|
||||
sequences "github.com/konsorten/go-windows-terminal-sequences"
|
||||
)
|
||||
|
||||
func main() {
|
||||
sequences.EnableVirtualTerminalProcessing(syscall.Stdout, true)
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
## Authors
|
||||
|
||||
The tool is sponsored by the [marvin + konsorten GmbH](http://www.konsorten.de).
|
||||
|
||||
We thank all the authors who provided code to this library:
|
||||
|
||||
* Felix Kollmann
|
||||
* Nicolas Perraut
|
||||
* @dirty49374
|
||||
|
||||
## License
|
||||
|
||||
(The MIT License)
|
||||
|
||||
Copyright (c) 2018 marvin + konsorten GmbH (open-source@konsorten.de)
|
||||
|
||||
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.
|
|
@ -1 +0,0 @@
|
|||
module github.com/konsorten/go-windows-terminal-sequences
|
|
@ -1,35 +0,0 @@
|
|||
// +build windows
|
||||
|
||||
package sequences
|
||||
|
||||
import (
|
||||
"syscall"
|
||||
)
|
||||
|
||||
var (
|
||||
kernel32Dll *syscall.LazyDLL = syscall.NewLazyDLL("Kernel32.dll")
|
||||
setConsoleMode *syscall.LazyProc = kernel32Dll.NewProc("SetConsoleMode")
|
||||
)
|
||||
|
||||
func EnableVirtualTerminalProcessing(stream syscall.Handle, enable bool) error {
|
||||
const ENABLE_VIRTUAL_TERMINAL_PROCESSING uint32 = 0x4
|
||||
|
||||
var mode uint32
|
||||
err := syscall.GetConsoleMode(syscall.Stdout, &mode)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if enable {
|
||||
mode |= ENABLE_VIRTUAL_TERMINAL_PROCESSING
|
||||
} else {
|
||||
mode &^= ENABLE_VIRTUAL_TERMINAL_PROCESSING
|
||||
}
|
||||
|
||||
ret, _, err := setConsoleMode.Call(uintptr(stream), uintptr(mode))
|
||||
if ret == 0 {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
|
@ -1,11 +0,0 @@
|
|||
// +build linux darwin
|
||||
|
||||
package sequences
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
func EnableVirtualTerminalProcessing(stream uintptr, enable bool) error {
|
||||
return fmt.Errorf("windows only package")
|
||||
}
|
|
@ -0,0 +1,44 @@
|
|||
// Package mountinfo provides a set of functions to retrieve information about OS mounts.
|
||||
//
|
||||
// Currently it supports Linux. For historical reasons, there is also some support for FreeBSD and OpenBSD,
|
||||
// and a shallow implementation for Windows, but in general this is Linux-only package, so
|
||||
// the rest of the document only applies to Linux, unless explicitly specified otherwise.
|
||||
//
|
||||
// In Linux, information about mounts seen by the current process is available from
|
||||
// /proc/self/mountinfo. Note that due to mount namespaces, different processes can
|
||||
// see different mounts. A per-process mountinfo table is available from /proc/<PID>/mountinfo,
|
||||
// where <PID> is a numerical process identifier.
|
||||
//
|
||||
// In general, /proc is not a very efficient interface, and mountinfo is not an exception.
|
||||
// For example, there is no way to get information about a specific mount point (i.e. it
|
||||
// is all-or-nothing). This package tries to hide the /proc ineffectiveness by using
|
||||
// parse filters while reading mountinfo. A filter can skip some entries, or stop
|
||||
// processing the rest of the file once the needed information is found.
|
||||
//
|
||||
// For mountinfo filters that accept path as an argument, the path must be absolute,
|
||||
// having all symlinks resolved, and being cleaned (i.e. no extra slashes or dots).
|
||||
// One way to achieve all of the above is to employ filepath.Abs followed by
|
||||
// filepath.EvalSymlinks (the latter calls filepath.Clean on the result so
|
||||
// there is no need to explicitly call filepath.Clean).
|
||||
//
|
||||
// NOTE that in many cases there is no need to consult mountinfo at all. Here are some
|
||||
// of the cases where mountinfo should not be parsed:
|
||||
//
|
||||
// 1. Before performing a mount. Usually, this is not needed, but if required (say to
|
||||
// prevent over-mounts), to check whether a directory is mounted, call os.Lstat
|
||||
// on it and its parent directory, and compare their st.Sys().(*syscall.Stat_t).Dev
|
||||
// fields -- if they differ, then the directory is the mount point. NOTE this does
|
||||
// not work for bind mounts. Optionally, the filesystem type can also be checked
|
||||
// by calling unix.Statfs and checking the Type field (i.e. filesystem type).
|
||||
//
|
||||
// 2. After performing a mount. If there is no error returned, the mount succeeded;
|
||||
// checking the mount table for a new mount is redundant and expensive.
|
||||
//
|
||||
// 3. Before performing an unmount. It is more efficient to do an unmount and ignore
|
||||
// a specific error (EINVAL) which tells the directory is not mounted.
|
||||
//
|
||||
// 4. After performing an unmount. If there is no error returned, the unmount succeeded.
|
||||
//
|
||||
// 5. To find the mount point root of a specific directory. You can perform os.Stat()
|
||||
// on the directory and traverse up until the Dev field of a parent directory differs.
|
||||
package mountinfo
|
|
@ -1,3 +1,5 @@
|
|||
module github.com/moby/sys/mountinfo
|
||||
|
||||
go 1.14
|
||||
|
||||
require golang.org/x/sys v0.0.0-20200909081042-eff7692f9009
|
||||
|
|
|
@ -0,0 +1,2 @@
|
|||
golang.org/x/sys v0.0.0-20200909081042-eff7692f9009 h1:W0lCpv29Hv0UaM1LXb9QlBHLNP8UFfcKjblhVCWftOM=
|
||||
golang.org/x/sys v0.0.0-20200909081042-eff7692f9009/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
@ -0,0 +1,58 @@
|
|||
package mountinfo
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
// mountedByOpenat2 is a method of detecting a mount that works for all kinds
|
||||
// of mounts (incl. bind mounts), but requires a recent (v5.6+) linux kernel.
|
||||
func mountedByOpenat2(path string) (bool, error) {
|
||||
dir, last := filepath.Split(path)
|
||||
|
||||
dirfd, err := unix.Openat2(unix.AT_FDCWD, dir, &unix.OpenHow{
|
||||
Flags: unix.O_PATH | unix.O_CLOEXEC,
|
||||
})
|
||||
if err != nil {
|
||||
if err == unix.ENOENT { // not a mount
|
||||
return false, nil
|
||||
}
|
||||
return false, &os.PathError{Op: "openat2", Path: dir, Err: err}
|
||||
}
|
||||
fd, err := unix.Openat2(dirfd, last, &unix.OpenHow{
|
||||
Flags: unix.O_PATH | unix.O_CLOEXEC | unix.O_NOFOLLOW,
|
||||
Resolve: unix.RESOLVE_NO_XDEV,
|
||||
})
|
||||
_ = unix.Close(dirfd)
|
||||
switch err {
|
||||
case nil: // definitely not a mount
|
||||
_ = unix.Close(fd)
|
||||
return false, nil
|
||||
case unix.EXDEV: // definitely a mount
|
||||
return true, nil
|
||||
case unix.ENOENT: // not a mount
|
||||
return false, nil
|
||||
}
|
||||
// not sure
|
||||
return false, &os.PathError{Op: "openat2", Path: path, Err: err}
|
||||
}
|
||||
|
||||
func mounted(path string) (bool, error) {
|
||||
// Try a fast path, using openat2() with RESOLVE_NO_XDEV.
|
||||
mounted, err := mountedByOpenat2(path)
|
||||
if err == nil {
|
||||
return mounted, nil
|
||||
}
|
||||
// Another fast path: compare st.st_dev fields.
|
||||
mounted, err = mountedByStat(path)
|
||||
// This does not work for bind mounts, so false negative
|
||||
// is possible, therefore only trust if return is true.
|
||||
if mounted && err == nil {
|
||||
return mounted, nil
|
||||
}
|
||||
|
||||
// Fallback to parsing mountinfo
|
||||
return mountedByMountinfo(path)
|
||||
}
|
|
@ -0,0 +1,66 @@
|
|||
// +build linux freebsd,cgo openbsd,cgo
|
||||
|
||||
package mountinfo
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
func mountedByStat(path string) (bool, error) {
|
||||
var st unix.Stat_t
|
||||
|
||||
if err := unix.Lstat(path, &st); err != nil {
|
||||
if err == unix.ENOENT {
|
||||
// Treat ENOENT as "not mounted".
|
||||
return false, nil
|
||||
}
|
||||
return false, &os.PathError{Op: "stat", Path: path, Err: err}
|
||||
}
|
||||
dev := st.Dev
|
||||
parent := filepath.Dir(path)
|
||||
if err := unix.Lstat(parent, &st); err != nil {
|
||||
return false, &os.PathError{Op: "stat", Path: parent, Err: err}
|
||||
}
|
||||
if dev != st.Dev {
|
||||
// Device differs from that of parent,
|
||||
// so definitely a mount point.
|
||||
return true, nil
|
||||
}
|
||||
// NB: this does not detect bind mounts on Linux.
|
||||
return false, nil
|
||||
}
|
||||
|
||||
func normalizePath(path string) (realPath string, err error) {
|
||||
if realPath, err = filepath.Abs(path); err != nil {
|
||||
return "", fmt.Errorf("unable to get absolute path for %q: %w", path, err)
|
||||
}
|
||||
if realPath, err = filepath.EvalSymlinks(realPath); err != nil {
|
||||
return "", fmt.Errorf("failed to canonicalise path for %q: %w", path, err)
|
||||
}
|
||||
if _, err := os.Stat(realPath); err != nil {
|
||||
return "", fmt.Errorf("failed to stat target of %q: %w", path, err)
|
||||
}
|
||||
return realPath, nil
|
||||
}
|
||||
|
||||
func mountedByMountinfo(path string) (bool, error) {
|
||||
path, err := normalizePath(path)
|
||||
if err != nil {
|
||||
if errors.Is(err, unix.ENOENT) {
|
||||
// treat ENOENT as "not mounted"
|
||||
return false, nil
|
||||
}
|
||||
return false, err
|
||||
}
|
||||
entries, err := GetMounts(SingleEntryFilter(path))
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
return len(entries) > 0, nil
|
||||
}
|
|
@ -1,6 +1,8 @@
|
|||
package mountinfo
|
||||
|
||||
import "io"
|
||||
import (
|
||||
"os"
|
||||
)
|
||||
|
||||
// GetMounts retrieves a list of mounts for the current running process,
|
||||
// with an optional filter applied (use nil for no filter).
|
||||
|
@ -8,23 +10,17 @@ func GetMounts(f FilterFunc) ([]*Info, error) {
|
|||
return parseMountTable(f)
|
||||
}
|
||||
|
||||
// GetMountsFromReader retrieves a list of mounts from the
|
||||
// reader provided, with an optional filter applied (use nil
|
||||
// for no filter). This can be useful in tests or benchmarks
|
||||
// that provide a fake mountinfo data.
|
||||
func GetMountsFromReader(reader io.Reader, f FilterFunc) ([]*Info, error) {
|
||||
return parseInfoFile(reader, f)
|
||||
}
|
||||
|
||||
// Mounted determines if a specified mountpoint has been mounted.
|
||||
// On Linux it looks at /proc/self/mountinfo.
|
||||
func Mounted(mountpoint string) (bool, error) {
|
||||
entries, err := GetMounts(SingleEntryFilter(mountpoint))
|
||||
if err != nil {
|
||||
return false, err
|
||||
// Mounted determines if a specified path is a mount point.
|
||||
//
|
||||
// The argument must be an absolute path, with all symlinks resolved, and clean.
|
||||
// One way to ensure it is to process the path using filepath.Abs followed by
|
||||
// filepath.EvalSymlinks before calling this function.
|
||||
func Mounted(path string) (bool, error) {
|
||||
// root is always mounted
|
||||
if path == string(os.PathSeparator) {
|
||||
return true, nil
|
||||
}
|
||||
|
||||
return len(entries) > 0, nil
|
||||
return mounted(path)
|
||||
}
|
||||
|
||||
// Info reveals information about a particular mounted filesystem. This
|
||||
|
@ -50,18 +46,18 @@ type Info struct {
|
|||
// Mountpoint indicates the mount point relative to the process's root.
|
||||
Mountpoint string
|
||||
|
||||
// Opts represents mount-specific options.
|
||||
Opts string
|
||||
// Options represents mount-specific options.
|
||||
Options string
|
||||
|
||||
// Optional represents optional fields.
|
||||
Optional string
|
||||
|
||||
// Fstype indicates the type of filesystem, such as EXT3.
|
||||
Fstype string
|
||||
// FSType indicates the type of filesystem, such as EXT3.
|
||||
FSType string
|
||||
|
||||
// Source indicates filesystem specific information or "none".
|
||||
Source string
|
||||
|
||||
// VfsOpts represents per super block options.
|
||||
VfsOpts string
|
||||
// VFSOptions represents per super block options.
|
||||
VFSOptions string
|
||||
}
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
// +build freebsd,cgo openbsd,cgo
|
||||
|
||||
package mountinfo
|
||||
|
||||
/*
|
||||
|
@ -33,7 +35,7 @@ func parseMountTable(filter FilterFunc) ([]*Info, error) {
|
|||
var mountinfo Info
|
||||
var skip, stop bool
|
||||
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 {
|
||||
|
@ -51,3 +53,15 @@ func parseMountTable(filter FilterFunc) ([]*Info, error) {
|
|||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func mounted(path string) (bool, error) {
|
||||
// Fast path: compare st.st_dev fields.
|
||||
// This should always work for FreeBSD and OpenBSD.
|
||||
mounted, err := mountedByStat(path)
|
||||
if err == nil {
|
||||
return mounted, nil
|
||||
}
|
||||
|
||||
// Fallback to parsing mountinfo
|
||||
return mountedByMountinfo(path)
|
||||
}
|
|
@ -6,16 +6,16 @@ import "strings"
|
|||
// used to filter out mountinfo entries we're not interested in,
|
||||
// and/or stop further processing if we found what we wanted.
|
||||
//
|
||||
// It takes a pointer to the Info struct (not fully populated,
|
||||
// currently only Mountpoint, Fstype, Source, and (on Linux)
|
||||
// VfsOpts are filled in), and returns two booleans:
|
||||
// It takes a pointer to the Info struct (fully populated with all available
|
||||
// fields on the GOOS platform), and returns two booleans:
|
||||
//
|
||||
// - skip: true if the entry should be skipped
|
||||
// - stop: true if parsing should be stopped after the entry
|
||||
// skip: true if the entry should be skipped;
|
||||
//
|
||||
// stop: true if parsing should be stopped after the entry.
|
||||
type FilterFunc func(*Info) (skip, stop bool)
|
||||
|
||||
// PrefixFilter discards all entries whose mount points
|
||||
// do not start with a specific prefix
|
||||
// do not start with a specific prefix.
|
||||
func PrefixFilter(prefix string) FilterFunc {
|
||||
return func(m *Info) (bool, bool) {
|
||||
skip := !strings.HasPrefix(m.Mountpoint, prefix)
|
||||
|
@ -23,7 +23,7 @@ func PrefixFilter(prefix string) FilterFunc {
|
|||
}
|
||||
}
|
||||
|
||||
// SingleEntryFilter looks for a specific entry
|
||||
// SingleEntryFilter looks for a specific entry.
|
||||
func SingleEntryFilter(mp string) FilterFunc {
|
||||
return func(m *Info) (bool, bool) {
|
||||
if m.Mountpoint == mp {
|
||||
|
@ -36,8 +36,8 @@ func SingleEntryFilter(mp string) FilterFunc {
|
|||
// ParentsFilter returns all entries whose mount points
|
||||
// can be parents of a path specified, discarding others.
|
||||
//
|
||||
// For example, given `/var/lib/docker/something`, entries
|
||||
// like `/var/lib/docker`, `/var` and `/` are returned.
|
||||
// For example, given /var/lib/docker/something, entries
|
||||
// like /var/lib/docker, /var and / are returned.
|
||||
func ParentsFilter(path string) FilterFunc {
|
||||
return func(m *Info) (bool, bool) {
|
||||
skip := !strings.HasPrefix(path, m.Mountpoint)
|
||||
|
@ -45,12 +45,12 @@ func ParentsFilter(path string) FilterFunc {
|
|||
}
|
||||
}
|
||||
|
||||
// FstypeFilter returns all entries that match provided fstype(s).
|
||||
func FstypeFilter(fstype ...string) FilterFunc {
|
||||
// FSTypeFilter returns all entries that match provided fstype(s).
|
||||
func FSTypeFilter(fstype ...string) FilterFunc {
|
||||
return func(m *Info) (bool, bool) {
|
||||
for _, t := range fstype {
|
||||
if m.Fstype == t {
|
||||
return false, false // don't skeep, keep going
|
||||
if m.FSType == t {
|
||||
return false, false // don't skip, keep going
|
||||
}
|
||||
}
|
||||
return true, false // skip, keep going
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
// +build go1.13
|
||||
|
||||
package mountinfo
|
||||
|
||||
import (
|
||||
|
@ -11,14 +9,18 @@ import (
|
|||
"strings"
|
||||
)
|
||||
|
||||
func parseInfoFile(r io.Reader, filter FilterFunc) ([]*Info, error) {
|
||||
// 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.
|
||||
//
|
||||
// This function is Linux-specific.
|
||||
func GetMountsFromReader(r io.Reader, filter FilterFunc) ([]*Info, error) {
|
||||
s := bufio.NewScanner(r)
|
||||
out := []*Info{}
|
||||
var err error
|
||||
for s.Scan() {
|
||||
if err = s.Err(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var err error
|
||||
|
||||
/*
|
||||
See http://man7.org/linux/man-pages/man5/proc.5.html
|
||||
|
||||
|
@ -70,26 +72,19 @@ func parseInfoFile(r io.Reader, filter FilterFunc) ([]*Info, error) {
|
|||
|
||||
p := &Info{}
|
||||
|
||||
// Fill in the fields that a filter might check
|
||||
p.Mountpoint, err = strconv.Unquote(`"` + fields[4] + `"`)
|
||||
p.Mountpoint, err = unescape(fields[4])
|
||||
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: mount point: %w", fields[4], err)
|
||||
}
|
||||
p.Fstype = fields[sepIdx+1]
|
||||
p.Source = fields[sepIdx+2]
|
||||
p.VfsOpts = fields[sepIdx+3]
|
||||
|
||||
// Run a filter soon so we can skip parsing/adding entries
|
||||
// the caller is not interested in
|
||||
var skip, stop bool
|
||||
if filter != nil {
|
||||
skip, stop = filter(p)
|
||||
if skip {
|
||||
continue
|
||||
}
|
||||
p.FSType, err = unescape(fields[sepIdx+1])
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Parsing '%s' failed: fstype: %w", fields[sepIdx+1], err)
|
||||
}
|
||||
|
||||
// Fill in the rest of the fields
|
||||
p.Source, err = unescape(fields[sepIdx+2])
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Parsing '%s' failed: source: %w", fields[sepIdx+2], err)
|
||||
}
|
||||
p.VFSOptions = fields[sepIdx+3]
|
||||
|
||||
// ignore any numbers parsing errors, as there should not be any
|
||||
p.ID, _ = strconv.Atoi(fields[0])
|
||||
|
@ -101,12 +96,12 @@ func parseInfoFile(r io.Reader, filter FilterFunc) ([]*Info, error) {
|
|||
p.Major, _ = strconv.Atoi(mm[0])
|
||||
p.Minor, _ = strconv.Atoi(mm[1])
|
||||
|
||||
p.Root, err = strconv.Unquote(`"` + fields[3] + `"`)
|
||||
p.Root, err = unescape(fields[3])
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Parsing '%s' failed: unable to unquote root field: %w", fields[3], err)
|
||||
return nil, fmt.Errorf("Parsing '%s' failed: root: %w", fields[3], err)
|
||||
}
|
||||
|
||||
p.Opts = fields[5]
|
||||
p.Options = fields[5]
|
||||
|
||||
// zero or more optional fields
|
||||
switch {
|
||||
|
@ -118,11 +113,23 @@ func parseInfoFile(r io.Reader, filter FilterFunc) ([]*Info, error) {
|
|||
p.Optional = strings.Join(fields[6:sepIdx-1], " ")
|
||||
}
|
||||
|
||||
// Run the filter after parsing all of the fields.
|
||||
var skip, stop bool
|
||||
if filter != nil {
|
||||
skip, stop = filter(p)
|
||||
if skip {
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
out = append(out, p)
|
||||
if stop {
|
||||
break
|
||||
}
|
||||
}
|
||||
if err := s.Err(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
|
@ -135,12 +142,17 @@ func parseMountTable(filter FilterFunc) ([]*Info, error) {
|
|||
}
|
||||
defer f.Close()
|
||||
|
||||
return parseInfoFile(f, filter)
|
||||
return GetMountsFromReader(f, filter)
|
||||
}
|
||||
|
||||
// PidMountInfo collects the mounts for a specific process ID. If the process
|
||||
// ID is unknown, it is better to use `GetMounts` which will inspect
|
||||
// "/proc/self/mountinfo" instead.
|
||||
// PidMountInfo retrieves the list of mounts from a given process' mount
|
||||
// namespace. Unless there is a need to get mounts from a mount namespace
|
||||
// different from that of a calling process, use GetMounts.
|
||||
//
|
||||
// This function is Linux-specific.
|
||||
//
|
||||
// Deprecated: this will be removed before v1; use GetMountsFromReader with
|
||||
// opened /proc/<pid>/mountinfo as an argument instead.
|
||||
func PidMountInfo(pid int) ([]*Info, error) {
|
||||
f, err := os.Open(fmt.Sprintf("/proc/%d/mountinfo", pid))
|
||||
if err != nil {
|
||||
|
@ -148,5 +160,63 @@ func PidMountInfo(pid int) ([]*Info, error) {
|
|||
}
|
||||
defer f.Close()
|
||||
|
||||
return parseInfoFile(f, nil)
|
||||
return GetMountsFromReader(f, nil)
|
||||
}
|
||||
|
||||
// A few specific characters in mountinfo path entries (root and mountpoint)
|
||||
// are escaped using a backslash followed by a character's ascii code in octal.
|
||||
//
|
||||
// space -- as \040
|
||||
// tab (aka \t) -- as \011
|
||||
// newline (aka \n) -- as \012
|
||||
// backslash (aka \\) -- as \134
|
||||
//
|
||||
// This function converts path from mountinfo back, i.e. it unescapes the above sequences.
|
||||
func unescape(path string) (string, error) {
|
||||
// try to avoid copying
|
||||
if strings.IndexByte(path, '\\') == -1 {
|
||||
return path, nil
|
||||
}
|
||||
|
||||
// The following code is UTF-8 transparent as it only looks for some
|
||||
// specific characters (backslash and 0..7) with values < utf8.RuneSelf,
|
||||
// and everything else is passed through as is.
|
||||
buf := make([]byte, len(path))
|
||||
bufLen := 0
|
||||
for i := 0; i < len(path); i++ {
|
||||
if path[i] != '\\' {
|
||||
buf[bufLen] = path[i]
|
||||
bufLen++
|
||||
continue
|
||||
}
|
||||
s := path[i:]
|
||||
if len(s) < 4 {
|
||||
// too short
|
||||
return "", fmt.Errorf("bad escape sequence %q: too short", s)
|
||||
}
|
||||
c := s[1]
|
||||
switch c {
|
||||
case '0', '1', '2', '3', '4', '5', '6', '7':
|
||||
v := c - '0'
|
||||
for j := 2; j < 4; j++ { // one digit already; two more
|
||||
if s[j] < '0' || s[j] > '7' {
|
||||
return "", fmt.Errorf("bad escape sequence %q: not a digit", s[:3])
|
||||
}
|
||||
x := s[j] - '0'
|
||||
v = (v << 3) | x
|
||||
}
|
||||
if v > 255 {
|
||||
return "", fmt.Errorf("bad escape sequence %q: out of range" + s[:3])
|
||||
}
|
||||
buf[bufLen] = v
|
||||
bufLen++
|
||||
i += 3
|
||||
continue
|
||||
default:
|
||||
return "", fmt.Errorf("bad escape sequence %q: not a digit" + s[:3])
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
return string(buf[:bufLen]), nil
|
||||
}
|
||||
|
|
|
@ -1,17 +1,18 @@
|
|||
// +build !windows,!linux,!freebsd freebsd,!cgo
|
||||
// +build !windows,!linux,!freebsd,!openbsd freebsd,!cgo openbsd,!cgo
|
||||
|
||||
package mountinfo
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"runtime"
|
||||
)
|
||||
|
||||
var errNotImplemented = fmt.Errorf("not implemented on %s/%s", runtime.GOOS, runtime.GOARCH)
|
||||
|
||||
func parseMountTable(_ FilterFunc) ([]*Info, error) {
|
||||
return nil, fmt.Errorf("mount.parseMountTable is not implemented on %s/%s", runtime.GOOS, runtime.GOARCH)
|
||||
return nil, errNotImplemented
|
||||
}
|
||||
|
||||
func parseInfoFile(_ io.Reader, f FilterFunc) ([]*Info, error) {
|
||||
return parseMountTable(f)
|
||||
func mounted(path string) (bool, error) {
|
||||
return false, errNotImplemented
|
||||
}
|
||||
|
|
|
@ -1,12 +1,10 @@
|
|||
package mountinfo
|
||||
|
||||
import "io"
|
||||
|
||||
func parseMountTable(_ FilterFunc) ([]*Info, error) {
|
||||
// Do NOT return an error!
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func parseInfoFile(_ io.Reader, f FilterFunc) ([]*Info, error) {
|
||||
return parseMountTable(f)
|
||||
func mounted(_ string) (bool, error) {
|
||||
return false, nil
|
||||
}
|
||||
|
|
|
@ -22,7 +22,7 @@ func CopyFile(source string, dest string) error {
|
|||
|
||||
uid := int(st.Uid)
|
||||
gid := int(st.Gid)
|
||||
modeType := si.Mode()&os.ModeType
|
||||
modeType := si.Mode() & os.ModeType
|
||||
|
||||
// Handle symlinks
|
||||
if modeType == os.ModeSymlink {
|
||||
|
@ -52,19 +52,7 @@ func CopyFile(source string, dest string) error {
|
|||
|
||||
// Handle regular files
|
||||
if si.Mode().IsRegular() {
|
||||
sf, err := os.Open(source)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer sf.Close()
|
||||
|
||||
df, err := os.Create(dest)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer df.Close()
|
||||
|
||||
_, err = io.Copy(df, sf)
|
||||
err = copyInternal(source, dest)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -85,6 +73,28 @@ func CopyFile(source string, dest string) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func copyInternal(source, dest string) (retErr error) {
|
||||
sf, err := os.Open(source)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer sf.Close()
|
||||
|
||||
df, err := os.Create(dest)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer func() {
|
||||
err := df.Close()
|
||||
if retErr == nil {
|
||||
retErr = err
|
||||
}
|
||||
}()
|
||||
|
||||
_, err = io.Copy(df, sf)
|
||||
return err
|
||||
}
|
||||
|
||||
// CopyDirectory copies the files under the source directory
|
||||
// to dest directory. The dest directory is created if it
|
||||
// does not exist.
|
||||
|
|
|
@ -0,0 +1,3 @@
|
|||
module github.com/mrunalp/fileutils
|
||||
|
||||
go 1.13
|
|
@ -60,87 +60,87 @@ defaultMountFlags := unix.MS_NOEXEC | unix.MS_NOSUID | unix.MS_NODEV
|
|||
config := &configs.Config{
|
||||
Rootfs: "/your/path/to/rootfs",
|
||||
Capabilities: &configs.Capabilities{
|
||||
Bounding: []string{
|
||||
"CAP_CHOWN",
|
||||
"CAP_DAC_OVERRIDE",
|
||||
"CAP_FSETID",
|
||||
"CAP_FOWNER",
|
||||
"CAP_MKNOD",
|
||||
"CAP_NET_RAW",
|
||||
"CAP_SETGID",
|
||||
"CAP_SETUID",
|
||||
"CAP_SETFCAP",
|
||||
"CAP_SETPCAP",
|
||||
"CAP_NET_BIND_SERVICE",
|
||||
"CAP_SYS_CHROOT",
|
||||
"CAP_KILL",
|
||||
"CAP_AUDIT_WRITE",
|
||||
},
|
||||
Effective: []string{
|
||||
"CAP_CHOWN",
|
||||
"CAP_DAC_OVERRIDE",
|
||||
"CAP_FSETID",
|
||||
"CAP_FOWNER",
|
||||
"CAP_MKNOD",
|
||||
"CAP_NET_RAW",
|
||||
"CAP_SETGID",
|
||||
"CAP_SETUID",
|
||||
"CAP_SETFCAP",
|
||||
"CAP_SETPCAP",
|
||||
"CAP_NET_BIND_SERVICE",
|
||||
"CAP_SYS_CHROOT",
|
||||
"CAP_KILL",
|
||||
"CAP_AUDIT_WRITE",
|
||||
},
|
||||
Inheritable: []string{
|
||||
"CAP_CHOWN",
|
||||
"CAP_DAC_OVERRIDE",
|
||||
"CAP_FSETID",
|
||||
"CAP_FOWNER",
|
||||
"CAP_MKNOD",
|
||||
"CAP_NET_RAW",
|
||||
"CAP_SETGID",
|
||||
"CAP_SETUID",
|
||||
"CAP_SETFCAP",
|
||||
"CAP_SETPCAP",
|
||||
"CAP_NET_BIND_SERVICE",
|
||||
"CAP_SYS_CHROOT",
|
||||
"CAP_KILL",
|
||||
"CAP_AUDIT_WRITE",
|
||||
},
|
||||
Permitted: []string{
|
||||
"CAP_CHOWN",
|
||||
"CAP_DAC_OVERRIDE",
|
||||
"CAP_FSETID",
|
||||
"CAP_FOWNER",
|
||||
"CAP_MKNOD",
|
||||
"CAP_NET_RAW",
|
||||
"CAP_SETGID",
|
||||
"CAP_SETUID",
|
||||
"CAP_SETFCAP",
|
||||
"CAP_SETPCAP",
|
||||
"CAP_NET_BIND_SERVICE",
|
||||
"CAP_SYS_CHROOT",
|
||||
"CAP_KILL",
|
||||
"CAP_AUDIT_WRITE",
|
||||
},
|
||||
Ambient: []string{
|
||||
"CAP_CHOWN",
|
||||
"CAP_DAC_OVERRIDE",
|
||||
"CAP_FSETID",
|
||||
"CAP_FOWNER",
|
||||
"CAP_MKNOD",
|
||||
"CAP_NET_RAW",
|
||||
"CAP_SETGID",
|
||||
"CAP_SETUID",
|
||||
"CAP_SETFCAP",
|
||||
"CAP_SETPCAP",
|
||||
"CAP_NET_BIND_SERVICE",
|
||||
"CAP_SYS_CHROOT",
|
||||
"CAP_KILL",
|
||||
"CAP_AUDIT_WRITE",
|
||||
},
|
||||
},
|
||||
Bounding: []string{
|
||||
"CAP_CHOWN",
|
||||
"CAP_DAC_OVERRIDE",
|
||||
"CAP_FSETID",
|
||||
"CAP_FOWNER",
|
||||
"CAP_MKNOD",
|
||||
"CAP_NET_RAW",
|
||||
"CAP_SETGID",
|
||||
"CAP_SETUID",
|
||||
"CAP_SETFCAP",
|
||||
"CAP_SETPCAP",
|
||||
"CAP_NET_BIND_SERVICE",
|
||||
"CAP_SYS_CHROOT",
|
||||
"CAP_KILL",
|
||||
"CAP_AUDIT_WRITE",
|
||||
},
|
||||
Effective: []string{
|
||||
"CAP_CHOWN",
|
||||
"CAP_DAC_OVERRIDE",
|
||||
"CAP_FSETID",
|
||||
"CAP_FOWNER",
|
||||
"CAP_MKNOD",
|
||||
"CAP_NET_RAW",
|
||||
"CAP_SETGID",
|
||||
"CAP_SETUID",
|
||||
"CAP_SETFCAP",
|
||||
"CAP_SETPCAP",
|
||||
"CAP_NET_BIND_SERVICE",
|
||||
"CAP_SYS_CHROOT",
|
||||
"CAP_KILL",
|
||||
"CAP_AUDIT_WRITE",
|
||||
},
|
||||
Inheritable: []string{
|
||||
"CAP_CHOWN",
|
||||
"CAP_DAC_OVERRIDE",
|
||||
"CAP_FSETID",
|
||||
"CAP_FOWNER",
|
||||
"CAP_MKNOD",
|
||||
"CAP_NET_RAW",
|
||||
"CAP_SETGID",
|
||||
"CAP_SETUID",
|
||||
"CAP_SETFCAP",
|
||||
"CAP_SETPCAP",
|
||||
"CAP_NET_BIND_SERVICE",
|
||||
"CAP_SYS_CHROOT",
|
||||
"CAP_KILL",
|
||||
"CAP_AUDIT_WRITE",
|
||||
},
|
||||
Permitted: []string{
|
||||
"CAP_CHOWN",
|
||||
"CAP_DAC_OVERRIDE",
|
||||
"CAP_FSETID",
|
||||
"CAP_FOWNER",
|
||||
"CAP_MKNOD",
|
||||
"CAP_NET_RAW",
|
||||
"CAP_SETGID",
|
||||
"CAP_SETUID",
|
||||
"CAP_SETFCAP",
|
||||
"CAP_SETPCAP",
|
||||
"CAP_NET_BIND_SERVICE",
|
||||
"CAP_SYS_CHROOT",
|
||||
"CAP_KILL",
|
||||
"CAP_AUDIT_WRITE",
|
||||
},
|
||||
Ambient: []string{
|
||||
"CAP_CHOWN",
|
||||
"CAP_DAC_OVERRIDE",
|
||||
"CAP_FSETID",
|
||||
"CAP_FOWNER",
|
||||
"CAP_MKNOD",
|
||||
"CAP_NET_RAW",
|
||||
"CAP_SETGID",
|
||||
"CAP_SETUID",
|
||||
"CAP_SETFCAP",
|
||||
"CAP_SETPCAP",
|
||||
"CAP_NET_BIND_SERVICE",
|
||||
"CAP_SYS_CHROOT",
|
||||
"CAP_KILL",
|
||||
"CAP_AUDIT_WRITE",
|
||||
},
|
||||
},
|
||||
Namespaces: configs.Namespaces([]configs.Namespace{
|
||||
{Type: configs.NEWNS},
|
||||
{Type: configs.NEWUTS},
|
||||
|
|
|
@ -1,8 +1,7 @@
|
|||
// +build apparmor,linux
|
||||
|
||||
package apparmor
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
|
@ -12,11 +11,9 @@ import (
|
|||
|
||||
// IsEnabled returns true if apparmor is enabled for the host.
|
||||
func IsEnabled() bool {
|
||||
if _, err := os.Stat("/sys/kernel/security/apparmor"); err == nil && os.Getenv("container") == "" {
|
||||
if _, err = os.Stat("/sbin/apparmor_parser"); err == nil {
|
||||
buf, err := ioutil.ReadFile("/sys/module/apparmor/parameters/enabled")
|
||||
return err == nil && len(buf) > 1 && buf[0] == 'Y'
|
||||
}
|
||||
if _, err := os.Stat("/sys/kernel/security/apparmor"); err == nil {
|
||||
buf, err := ioutil.ReadFile("/sys/module/apparmor/parameters/enabled")
|
||||
return err == nil && bytes.HasPrefix(buf, []byte("Y"))
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
@ -24,9 +21,7 @@ func IsEnabled() bool {
|
|||
func setProcAttr(attr, value string) error {
|
||||
// Under AppArmor you can only change your own attr, so use /proc/self/
|
||||
// instead of /proc/<tid>/ like libapparmor does
|
||||
path := fmt.Sprintf("/proc/self/attr/%s", attr)
|
||||
|
||||
f, err := os.OpenFile(path, os.O_WRONLY, 0)
|
||||
f, err := os.OpenFile("/proc/self/attr/"+attr, os.O_WRONLY, 0)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -36,14 +31,13 @@ func setProcAttr(attr, value string) error {
|
|||
return err
|
||||
}
|
||||
|
||||
_, err = fmt.Fprintf(f, "%s", value)
|
||||
_, err = f.WriteString(value)
|
||||
return err
|
||||
}
|
||||
|
||||
// changeOnExec reimplements aa_change_onexec from libapparmor in Go
|
||||
func changeOnExec(name string) error {
|
||||
value := "exec " + name
|
||||
if err := setProcAttr("exec", value); err != nil {
|
||||
if err := setProcAttr("exec", "exec "+name); err != nil {
|
||||
return fmt.Errorf("apparmor failed to apply profile: %s", err)
|
||||
}
|
||||
return nil
|
|
@ -1,4 +1,4 @@
|
|||
// +build !apparmor !linux
|
||||
// +build !linux
|
||||
|
||||
package apparmor
|
||||
|
96
vendor/github.com/opencontainers/runc/libcontainer/capabilities/capabilities.go
generated
vendored
Normal file
96
vendor/github.com/opencontainers/runc/libcontainer/capabilities/capabilities.go
generated
vendored
Normal file
|
@ -0,0 +1,96 @@
|
|||
// +build linux
|
||||
|
||||
package capabilities
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/opencontainers/runc/libcontainer/configs"
|
||||
"github.com/syndtr/gocapability/capability"
|
||||
)
|
||||
|
||||
const allCapabilityTypes = capability.CAPS | capability.BOUNDS | capability.AMBS
|
||||
|
||||
var capabilityMap map[string]capability.Cap
|
||||
|
||||
func init() {
|
||||
capabilityMap = make(map[string]capability.Cap, capability.CAP_LAST_CAP+1)
|
||||
for _, c := range capability.List() {
|
||||
if c > capability.CAP_LAST_CAP {
|
||||
continue
|
||||
}
|
||||
capabilityMap["CAP_"+strings.ToUpper(c.String())] = c
|
||||
}
|
||||
}
|
||||
|
||||
// New creates a new Caps from the given Capabilities config.
|
||||
func New(capConfig *configs.Capabilities) (*Caps, error) {
|
||||
var (
|
||||
err error
|
||||
caps Caps
|
||||
)
|
||||
|
||||
if caps.bounding, err = capSlice(capConfig.Bounding); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if caps.effective, err = capSlice(capConfig.Effective); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if caps.inheritable, err = capSlice(capConfig.Inheritable); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if caps.permitted, err = capSlice(capConfig.Permitted); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if caps.ambient, err = capSlice(capConfig.Ambient); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if caps.pid, err = capability.NewPid2(0); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err = caps.pid.Load(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &caps, nil
|
||||
}
|
||||
|
||||
func capSlice(caps []string) ([]capability.Cap, error) {
|
||||
out := make([]capability.Cap, len(caps))
|
||||
for i, c := range caps {
|
||||
v, ok := capabilityMap[c]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("unknown capability %q", c)
|
||||
}
|
||||
out[i] = v
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
// Caps holds the capabilities for a container.
|
||||
type Caps struct {
|
||||
pid capability.Capabilities
|
||||
bounding []capability.Cap
|
||||
effective []capability.Cap
|
||||
inheritable []capability.Cap
|
||||
permitted []capability.Cap
|
||||
ambient []capability.Cap
|
||||
}
|
||||
|
||||
// ApplyBoundingSet sets the capability bounding set to those specified in the whitelist.
|
||||
func (c *Caps) ApplyBoundingSet() error {
|
||||
c.pid.Clear(capability.BOUNDS)
|
||||
c.pid.Set(capability.BOUNDS, c.bounding...)
|
||||
return c.pid.Apply(capability.BOUNDS)
|
||||
}
|
||||
|
||||
// Apply sets all the capabilities for the current process in the config.
|
||||
func (c *Caps) ApplyCaps() error {
|
||||
c.pid.Clear(allCapabilityTypes)
|
||||
c.pid.Set(capability.BOUNDS, c.bounding...)
|
||||
c.pid.Set(capability.PERMITTED, c.permitted...)
|
||||
c.pid.Set(capability.INHERITABLE, c.inheritable...)
|
||||
c.pid.Set(capability.EFFECTIVE, c.effective...)
|
||||
c.pid.Set(capability.AMBIENT, c.ambient...)
|
||||
return c.pid.Apply(allCapabilityTypes)
|
||||
}
|
3
vendor/github.com/opencontainers/runc/libcontainer/capabilities/capabilities_unsupported.go
generated
vendored
Normal file
3
vendor/github.com/opencontainers/runc/libcontainer/capabilities/capabilities_unsupported.go
generated
vendored
Normal file
|
@ -0,0 +1,3 @@
|
|||
// +build !linux
|
||||
|
||||
package capabilities
|
|
@ -1,117 +0,0 @@
|
|||
// +build linux
|
||||
|
||||
package libcontainer
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/opencontainers/runc/libcontainer/configs"
|
||||
"github.com/syndtr/gocapability/capability"
|
||||
)
|
||||
|
||||
const allCapabilityTypes = capability.CAPS | capability.BOUNDS | capability.AMBS
|
||||
|
||||
var capabilityMap map[string]capability.Cap
|
||||
|
||||
func init() {
|
||||
capabilityMap = make(map[string]capability.Cap)
|
||||
last := capability.CAP_LAST_CAP
|
||||
// workaround for RHEL6 which has no /proc/sys/kernel/cap_last_cap
|
||||
if last == capability.Cap(63) {
|
||||
last = capability.CAP_BLOCK_SUSPEND
|
||||
}
|
||||
for _, cap := range capability.List() {
|
||||
if cap > last {
|
||||
continue
|
||||
}
|
||||
capKey := fmt.Sprintf("CAP_%s", strings.ToUpper(cap.String()))
|
||||
capabilityMap[capKey] = cap
|
||||
}
|
||||
}
|
||||
|
||||
func newContainerCapList(capConfig *configs.Capabilities) (*containerCapabilities, error) {
|
||||
bounding := []capability.Cap{}
|
||||
for _, c := range capConfig.Bounding {
|
||||
v, ok := capabilityMap[c]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("unknown capability %q", c)
|
||||
}
|
||||
bounding = append(bounding, v)
|
||||
}
|
||||
effective := []capability.Cap{}
|
||||
for _, c := range capConfig.Effective {
|
||||
v, ok := capabilityMap[c]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("unknown capability %q", c)
|
||||
}
|
||||
effective = append(effective, v)
|
||||
}
|
||||
inheritable := []capability.Cap{}
|
||||
for _, c := range capConfig.Inheritable {
|
||||
v, ok := capabilityMap[c]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("unknown capability %q", c)
|
||||
}
|
||||
inheritable = append(inheritable, v)
|
||||
}
|
||||
permitted := []capability.Cap{}
|
||||
for _, c := range capConfig.Permitted {
|
||||
v, ok := capabilityMap[c]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("unknown capability %q", c)
|
||||
}
|
||||
permitted = append(permitted, v)
|
||||
}
|
||||
ambient := []capability.Cap{}
|
||||
for _, c := range capConfig.Ambient {
|
||||
v, ok := capabilityMap[c]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("unknown capability %q", c)
|
||||
}
|
||||
ambient = append(ambient, v)
|
||||
}
|
||||
pid, err := capability.NewPid2(0)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = pid.Load()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &containerCapabilities{
|
||||
bounding: bounding,
|
||||
effective: effective,
|
||||
inheritable: inheritable,
|
||||
permitted: permitted,
|
||||
ambient: ambient,
|
||||
pid: pid,
|
||||
}, nil
|
||||
}
|
||||
|
||||
type containerCapabilities struct {
|
||||
pid capability.Capabilities
|
||||
bounding []capability.Cap
|
||||
effective []capability.Cap
|
||||
inheritable []capability.Cap
|
||||
permitted []capability.Cap
|
||||
ambient []capability.Cap
|
||||
}
|
||||
|
||||
// ApplyBoundingSet sets the capability bounding set to those specified in the whitelist.
|
||||
func (c *containerCapabilities) ApplyBoundingSet() error {
|
||||
c.pid.Clear(capability.BOUNDS)
|
||||
c.pid.Set(capability.BOUNDS, c.bounding...)
|
||||
return c.pid.Apply(capability.BOUNDS)
|
||||
}
|
||||
|
||||
// Apply sets all the capabilities for the current process in the config.
|
||||
func (c *containerCapabilities) ApplyCaps() error {
|
||||
c.pid.Clear(allCapabilityTypes)
|
||||
c.pid.Set(capability.BOUNDS, c.bounding...)
|
||||
c.pid.Set(capability.PERMITTED, c.permitted...)
|
||||
c.pid.Set(capability.INHERITABLE, c.inheritable...)
|
||||
c.pid.Set(capability.EFFECTIVE, c.effective...)
|
||||
c.pid.Set(capability.AMBIENT, c.ambient...)
|
||||
return c.pid.Apply(allCapabilityTypes)
|
||||
}
|
50
vendor/github.com/opencontainers/runc/libcontainer/cgroups/devices/devices_emulator.go
generated
vendored
50
vendor/github.com/opencontainers/runc/libcontainer/cgroups/devices/devices_emulator.go
generated
vendored
|
@ -27,29 +27,29 @@ import (
|
|||
"sort"
|
||||
"strconv"
|
||||
|
||||
"github.com/opencontainers/runc/libcontainer/configs"
|
||||
"github.com/opencontainers/runc/libcontainer/devices"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
// deviceMeta is a DeviceRule without the Allow or Permissions fields, and no
|
||||
// deviceMeta is a Rule without the Allow or Permissions fields, and no
|
||||
// wildcard-type support. It's effectively the "match" portion of a metadata
|
||||
// rule, for the purposes of our emulation.
|
||||
type deviceMeta struct {
|
||||
node configs.DeviceType
|
||||
node devices.Type
|
||||
major int64
|
||||
minor int64
|
||||
}
|
||||
|
||||
// deviceRule is effectively the tuple (deviceMeta, DevicePermissions).
|
||||
// deviceRule is effectively the tuple (deviceMeta, Permissions).
|
||||
type deviceRule struct {
|
||||
meta deviceMeta
|
||||
perms configs.DevicePermissions
|
||||
perms devices.Permissions
|
||||
}
|
||||
|
||||
// deviceRules is a mapping of device metadata rules to the associated
|
||||
// permissions in the ruleset.
|
||||
type deviceRules map[deviceMeta]configs.DevicePermissions
|
||||
type deviceRules map[deviceMeta]devices.Permissions
|
||||
|
||||
func (r deviceRules) orderedEntries() []deviceRule {
|
||||
var rules []deviceRule
|
||||
|
@ -103,9 +103,9 @@ func parseLine(line string) (*deviceRule, error) {
|
|||
// TODO: Double-check that the entire file is "a *:* rwm".
|
||||
return nil, nil
|
||||
case "b":
|
||||
rule.meta.node = configs.BlockDevice
|
||||
rule.meta.node = devices.BlockDevice
|
||||
case "c":
|
||||
rule.meta.node = configs.CharDevice
|
||||
rule.meta.node = devices.CharDevice
|
||||
default:
|
||||
// Should never happen!
|
||||
return nil, errors.Errorf("unknown device type %q", node)
|
||||
|
@ -113,7 +113,7 @@ func parseLine(line string) (*deviceRule, error) {
|
|||
|
||||
// Parse the major number.
|
||||
if major == "*" {
|
||||
rule.meta.major = configs.Wildcard
|
||||
rule.meta.major = devices.Wildcard
|
||||
} else {
|
||||
val, err := strconv.ParseUint(major, 10, 32)
|
||||
if err != nil {
|
||||
|
@ -124,7 +124,7 @@ func parseLine(line string) (*deviceRule, error) {
|
|||
|
||||
// Parse the minor number.
|
||||
if minor == "*" {
|
||||
rule.meta.minor = configs.Wildcard
|
||||
rule.meta.minor = devices.Wildcard
|
||||
} else {
|
||||
val, err := strconv.ParseUint(minor, 10, 32)
|
||||
if err != nil {
|
||||
|
@ -134,7 +134,7 @@ func parseLine(line string) (*deviceRule, error) {
|
|||
}
|
||||
|
||||
// Parse the access permissions.
|
||||
rule.perms = configs.DevicePermissions(perms)
|
||||
rule.perms = devices.Permissions(perms)
|
||||
if !rule.perms.IsValid() || rule.perms.IsEmpty() {
|
||||
// Should never happen!
|
||||
return nil, errors.Errorf("parse access mode: contained unknown modes or is empty: %q", perms)
|
||||
|
@ -144,7 +144,7 @@ func parseLine(line string) (*deviceRule, error) {
|
|||
|
||||
func (e *Emulator) addRule(rule deviceRule) error {
|
||||
if e.rules == nil {
|
||||
e.rules = make(map[deviceMeta]configs.DevicePermissions)
|
||||
e.rules = make(map[deviceMeta]devices.Permissions)
|
||||
}
|
||||
|
||||
// Merge with any pre-existing permissions.
|
||||
|
@ -169,9 +169,9 @@ func (e *Emulator) rmRule(rule deviceRule) error {
|
|||
// to mention it'd be really slow (the kernel side is implemented as a
|
||||
// linked-list of exceptions).
|
||||
for _, partialMeta := range []deviceMeta{
|
||||
{node: rule.meta.node, major: configs.Wildcard, minor: rule.meta.minor},
|
||||
{node: rule.meta.node, major: rule.meta.major, minor: configs.Wildcard},
|
||||
{node: rule.meta.node, major: configs.Wildcard, minor: configs.Wildcard},
|
||||
{node: rule.meta.node, major: devices.Wildcard, minor: rule.meta.minor},
|
||||
{node: rule.meta.node, major: rule.meta.major, minor: devices.Wildcard},
|
||||
{node: rule.meta.node, major: devices.Wildcard, minor: devices.Wildcard},
|
||||
} {
|
||||
// This wildcard rule is equivalent to the requested rule, so skip it.
|
||||
if rule.meta == partialMeta {
|
||||
|
@ -202,7 +202,7 @@ func (e *Emulator) rmRule(rule deviceRule) error {
|
|||
func (e *Emulator) allow(rule *deviceRule) error {
|
||||
// This cgroup is configured as a black-list. Reset the entire emulator,
|
||||
// and put is into black-list mode.
|
||||
if rule == nil || rule.meta.node == configs.WildcardDevice {
|
||||
if rule == nil || rule.meta.node == devices.WildcardDevice {
|
||||
*e = Emulator{
|
||||
defaultAllow: true,
|
||||
rules: nil,
|
||||
|
@ -222,7 +222,7 @@ func (e *Emulator) allow(rule *deviceRule) error {
|
|||
func (e *Emulator) deny(rule *deviceRule) error {
|
||||
// This cgroup is configured as a white-list. Reset the entire emulator,
|
||||
// and put is into white-list mode.
|
||||
if rule == nil || rule.meta.node == configs.WildcardDevice {
|
||||
if rule == nil || rule.meta.node == devices.WildcardDevice {
|
||||
*e = Emulator{
|
||||
defaultAllow: false,
|
||||
rules: nil,
|
||||
|
@ -239,7 +239,7 @@ func (e *Emulator) deny(rule *deviceRule) error {
|
|||
return err
|
||||
}
|
||||
|
||||
func (e *Emulator) Apply(rule configs.DeviceRule) error {
|
||||
func (e *Emulator) Apply(rule devices.Rule) error {
|
||||
if !rule.Type.CanCgroup() {
|
||||
return errors.Errorf("cannot add rule [%#v] with non-cgroup type %q", rule, rule.Type)
|
||||
}
|
||||
|
@ -252,7 +252,7 @@ func (e *Emulator) Apply(rule configs.DeviceRule) error {
|
|||
},
|
||||
perms: rule.Permissions,
|
||||
}
|
||||
if innerRule.meta.node == configs.WildcardDevice {
|
||||
if innerRule.meta.node == devices.WildcardDevice {
|
||||
innerRule = nil
|
||||
}
|
||||
|
||||
|
@ -307,8 +307,8 @@ func EmulatorFromList(list io.Reader) (*Emulator, error) {
|
|||
// This function is the sole reason for all of Emulator -- to allow us
|
||||
// to figure out how to update a containers' cgroups without causing spurrious
|
||||
// device errors (if possible).
|
||||
func (source *Emulator) Transition(target *Emulator) ([]*configs.DeviceRule, error) {
|
||||
var transitionRules []*configs.DeviceRule
|
||||
func (source *Emulator) Transition(target *Emulator) ([]*devices.Rule, error) {
|
||||
var transitionRules []*devices.Rule
|
||||
oldRules := source.rules
|
||||
|
||||
// If the default policy doesn't match, we need to include a "disruptive"
|
||||
|
@ -319,11 +319,11 @@ func (source *Emulator) Transition(target *Emulator) ([]*configs.DeviceRule, err
|
|||
// deny rules are in place in a black-list cgroup. Thus if the source is a
|
||||
// black-list we also have to include a disruptive rule.
|
||||
if source.IsBlacklist() || source.defaultAllow != target.defaultAllow {
|
||||
transitionRules = append(transitionRules, &configs.DeviceRule{
|
||||
transitionRules = append(transitionRules, &devices.Rule{
|
||||
Type: 'a',
|
||||
Major: -1,
|
||||
Minor: -1,
|
||||
Permissions: configs.DevicePermissions("rwm"),
|
||||
Permissions: devices.Permissions("rwm"),
|
||||
Allow: target.defaultAllow,
|
||||
})
|
||||
// The old rules are only relevant if we aren't starting out with a
|
||||
|
@ -342,7 +342,7 @@ func (source *Emulator) Transition(target *Emulator) ([]*configs.DeviceRule, err
|
|||
newPerms := target.rules[meta]
|
||||
droppedPerms := oldPerms.Difference(newPerms)
|
||||
if !droppedPerms.IsEmpty() {
|
||||
transitionRules = append(transitionRules, &configs.DeviceRule{
|
||||
transitionRules = append(transitionRules, &devices.Rule{
|
||||
Type: meta.node,
|
||||
Major: meta.major,
|
||||
Minor: meta.minor,
|
||||
|
@ -360,7 +360,7 @@ func (source *Emulator) Transition(target *Emulator) ([]*configs.DeviceRule, err
|
|||
oldPerms := oldRules[meta]
|
||||
gainedPerms := newPerms.Difference(oldPerms)
|
||||
if !gainedPerms.IsEmpty() {
|
||||
transitionRules = append(transitionRules, &configs.DeviceRule{
|
||||
transitionRules = append(transitionRules, &devices.Rule{
|
||||
Type: meta.node,
|
||||
Major: meta.major,
|
||||
Minor: meta.minor,
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
// Package devicefilter containes eBPF device filter program
|
||||
// Package devicefilter contains eBPF device filter program
|
||||
//
|
||||
// The implementation is based on https://github.com/containers/crun/blob/0.10.2/src/libcrun/ebpf.c
|
||||
//
|
||||
|
@ -7,11 +7,11 @@
|
|||
package devicefilter
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math"
|
||||
"strconv"
|
||||
|
||||
"github.com/cilium/ebpf/asm"
|
||||
"github.com/opencontainers/runc/libcontainer/configs"
|
||||
"github.com/opencontainers/runc/libcontainer/devices"
|
||||
"github.com/pkg/errors"
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
@ -22,7 +22,7 @@ const (
|
|||
)
|
||||
|
||||
// DeviceFilter returns eBPF device filter program and its license string
|
||||
func DeviceFilter(devices []*configs.DeviceRule) (asm.Instructions, string, error) {
|
||||
func DeviceFilter(devices []*devices.Rule) (asm.Instructions, string, error) {
|
||||
p := &program{}
|
||||
p.init()
|
||||
for i := len(devices) - 1; i >= 0; i-- {
|
||||
|
@ -68,7 +68,7 @@ func (p *program) init() {
|
|||
}
|
||||
|
||||
// appendDevice needs to be called from the last element of OCI linux.resources.devices to the head element.
|
||||
func (p *program) appendDevice(dev *configs.DeviceRule) error {
|
||||
func (p *program) appendDevice(dev *devices.Rule) error {
|
||||
if p.blockID < 0 {
|
||||
return errors.New("the program is finalized")
|
||||
}
|
||||
|
@ -88,7 +88,7 @@ func (p *program) appendDevice(dev *configs.DeviceRule) error {
|
|||
hasType = false
|
||||
default:
|
||||
// if not specified in OCI json, typ is set to DeviceTypeAll
|
||||
return errors.Errorf("invalid DeviceType %q", string(dev.Type))
|
||||
return errors.Errorf("invalid Type %q", string(dev.Type))
|
||||
}
|
||||
if dev.Major > math.MaxUint32 {
|
||||
return errors.Errorf("invalid major %d", dev.Major)
|
||||
|
@ -114,9 +114,11 @@ func (p *program) appendDevice(dev *configs.DeviceRule) error {
|
|||
// If the access is rwm, skip the check.
|
||||
hasAccess := bpfAccess != (unix.BPF_DEVCG_ACC_READ | unix.BPF_DEVCG_ACC_WRITE | unix.BPF_DEVCG_ACC_MKNOD)
|
||||
|
||||
blockSym := fmt.Sprintf("block-%d", p.blockID)
|
||||
nextBlockSym := fmt.Sprintf("block-%d", p.blockID+1)
|
||||
prevBlockLastIdx := len(p.insts) - 1
|
||||
var (
|
||||
blockSym = "block-" + strconv.Itoa(p.blockID)
|
||||
nextBlockSym = "block-" + strconv.Itoa(p.blockID+1)
|
||||
prevBlockLastIdx = len(p.insts) - 1
|
||||
)
|
||||
if hasType {
|
||||
p.insts = append(p.insts,
|
||||
// if (R2 != bpfType) goto next
|
||||
|
@ -158,7 +160,7 @@ func (p *program) finalize() (asm.Instructions, error) {
|
|||
// acceptBlock with asm.Return() is already inserted
|
||||
return p.insts, nil
|
||||
}
|
||||
blockSym := fmt.Sprintf("block-%d", p.blockID)
|
||||
blockSym := "block-" + strconv.Itoa(p.blockID)
|
||||
p.insts = append(p.insts,
|
||||
// R0 <- 0
|
||||
asm.Mov.Imm32(asm.R0, 0).Sym(blockSym),
|
||||
|
|
|
@ -6,7 +6,6 @@ import (
|
|||
"bufio"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
|
@ -105,9 +104,9 @@ func splitBlkioStatLine(r rune) bool {
|
|||
return r == ' ' || r == ':'
|
||||
}
|
||||
|
||||
func getBlkioStat(path string) ([]cgroups.BlkioStatEntry, error) {
|
||||
func getBlkioStat(dir, file string) ([]cgroups.BlkioStatEntry, error) {
|
||||
var blkioStats []cgroups.BlkioStatEntry
|
||||
f, err := os.Open(path)
|
||||
f, err := fscommon.OpenFile(dir, file, os.O_RDONLY)
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
return blkioStats, nil
|
||||
|
@ -125,7 +124,7 @@ func getBlkioStat(path string) ([]cgroups.BlkioStatEntry, error) {
|
|||
// skip total line
|
||||
continue
|
||||
} else {
|
||||
return nil, fmt.Errorf("Invalid line found while parsing %s: %s", path, sc.Text())
|
||||
return nil, fmt.Errorf("Invalid line found while parsing %s/%s: %s", dir, file, sc.Text())
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -158,73 +157,134 @@ func getBlkioStat(path string) ([]cgroups.BlkioStatEntry, error) {
|
|||
}
|
||||
|
||||
func (s *BlkioGroup) GetStats(path string, stats *cgroups.Stats) error {
|
||||
// Try to read CFQ stats available on all CFQ enabled kernels first
|
||||
if blkioStats, err := getBlkioStat(filepath.Join(path, "blkio.io_serviced_recursive")); err == nil && blkioStats != nil {
|
||||
return getCFQStats(path, stats)
|
||||
type blkioStatInfo struct {
|
||||
filename string
|
||||
blkioStatEntriesPtr *[]cgroups.BlkioStatEntry
|
||||
}
|
||||
var bfqDebugStats = []blkioStatInfo{
|
||||
{
|
||||
filename: "blkio.bfq.sectors_recursive",
|
||||
blkioStatEntriesPtr: &stats.BlkioStats.SectorsRecursive,
|
||||
},
|
||||
{
|
||||
filename: "blkio.bfq.io_service_time_recursive",
|
||||
blkioStatEntriesPtr: &stats.BlkioStats.IoServiceTimeRecursive,
|
||||
},
|
||||
{
|
||||
filename: "blkio.bfq.io_wait_time_recursive",
|
||||
blkioStatEntriesPtr: &stats.BlkioStats.IoWaitTimeRecursive,
|
||||
},
|
||||
{
|
||||
filename: "blkio.bfq.io_merged_recursive",
|
||||
blkioStatEntriesPtr: &stats.BlkioStats.IoMergedRecursive,
|
||||
},
|
||||
{
|
||||
filename: "blkio.bfq.io_queued_recursive",
|
||||
blkioStatEntriesPtr: &stats.BlkioStats.IoQueuedRecursive,
|
||||
},
|
||||
{
|
||||
filename: "blkio.bfq.time_recursive",
|
||||
blkioStatEntriesPtr: &stats.BlkioStats.IoTimeRecursive,
|
||||
},
|
||||
{
|
||||
filename: "blkio.bfq.io_serviced_recursive",
|
||||
blkioStatEntriesPtr: &stats.BlkioStats.IoServicedRecursive,
|
||||
},
|
||||
{
|
||||
filename: "blkio.bfq.io_service_bytes_recursive",
|
||||
blkioStatEntriesPtr: &stats.BlkioStats.IoServiceBytesRecursive,
|
||||
},
|
||||
}
|
||||
var bfqStats = []blkioStatInfo{
|
||||
{
|
||||
filename: "blkio.bfq.io_serviced_recursive",
|
||||
blkioStatEntriesPtr: &stats.BlkioStats.IoServicedRecursive,
|
||||
},
|
||||
{
|
||||
filename: "blkio.bfq.io_service_bytes_recursive",
|
||||
blkioStatEntriesPtr: &stats.BlkioStats.IoServiceBytesRecursive,
|
||||
},
|
||||
}
|
||||
var cfqStats = []blkioStatInfo{
|
||||
{
|
||||
filename: "blkio.sectors_recursive",
|
||||
blkioStatEntriesPtr: &stats.BlkioStats.SectorsRecursive,
|
||||
},
|
||||
{
|
||||
filename: "blkio.io_service_time_recursive",
|
||||
blkioStatEntriesPtr: &stats.BlkioStats.IoServiceTimeRecursive,
|
||||
},
|
||||
{
|
||||
filename: "blkio.io_wait_time_recursive",
|
||||
blkioStatEntriesPtr: &stats.BlkioStats.IoWaitTimeRecursive,
|
||||
},
|
||||
{
|
||||
filename: "blkio.io_merged_recursive",
|
||||
blkioStatEntriesPtr: &stats.BlkioStats.IoMergedRecursive,
|
||||
},
|
||||
{
|
||||
filename: "blkio.io_queued_recursive",
|
||||
blkioStatEntriesPtr: &stats.BlkioStats.IoQueuedRecursive,
|
||||
},
|
||||
{
|
||||
filename: "blkio.time_recursive",
|
||||
blkioStatEntriesPtr: &stats.BlkioStats.IoTimeRecursive,
|
||||
},
|
||||
{
|
||||
filename: "blkio.io_serviced_recursive",
|
||||
blkioStatEntriesPtr: &stats.BlkioStats.IoServicedRecursive,
|
||||
},
|
||||
{
|
||||
filename: "blkio.io_service_bytes_recursive",
|
||||
blkioStatEntriesPtr: &stats.BlkioStats.IoServiceBytesRecursive,
|
||||
},
|
||||
}
|
||||
var throttleRecursiveStats = []blkioStatInfo{
|
||||
{
|
||||
filename: "blkio.throttle.io_serviced_recursive",
|
||||
blkioStatEntriesPtr: &stats.BlkioStats.IoServicedRecursive,
|
||||
},
|
||||
{
|
||||
filename: "blkio.throttle.io_service_bytes_recursive",
|
||||
blkioStatEntriesPtr: &stats.BlkioStats.IoServiceBytesRecursive,
|
||||
},
|
||||
}
|
||||
var baseStats = []blkioStatInfo{
|
||||
{
|
||||
filename: "blkio.throttle.io_serviced",
|
||||
blkioStatEntriesPtr: &stats.BlkioStats.IoServicedRecursive,
|
||||
},
|
||||
{
|
||||
filename: "blkio.throttle.io_service_bytes",
|
||||
blkioStatEntriesPtr: &stats.BlkioStats.IoServiceBytesRecursive,
|
||||
},
|
||||
}
|
||||
var orderedStats = [][]blkioStatInfo{
|
||||
bfqDebugStats,
|
||||
bfqStats,
|
||||
cfqStats,
|
||||
throttleRecursiveStats,
|
||||
baseStats,
|
||||
}
|
||||
return getStats(path, stats) // Use generic stats as fallback
|
||||
}
|
||||
|
||||
func getCFQStats(path string, stats *cgroups.Stats) error {
|
||||
var blkioStats []cgroups.BlkioStatEntry
|
||||
var err error
|
||||
|
||||
if blkioStats, err = getBlkioStat(filepath.Join(path, "blkio.sectors_recursive")); err != nil {
|
||||
return err
|
||||
for _, statGroup := range orderedStats {
|
||||
for i, statInfo := range statGroup {
|
||||
if blkioStats, err = getBlkioStat(path, statInfo.filename); err != nil || blkioStats == nil {
|
||||
// if error occurs on first file, move to next group
|
||||
if i == 0 {
|
||||
break
|
||||
}
|
||||
return err
|
||||
}
|
||||
*statInfo.blkioStatEntriesPtr = blkioStats
|
||||
//finish if all stats are gathered
|
||||
if i == len(statGroup)-1 {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
stats.BlkioStats.SectorsRecursive = blkioStats
|
||||
|
||||
if blkioStats, err = getBlkioStat(filepath.Join(path, "blkio.io_service_bytes_recursive")); err != nil {
|
||||
return err
|
||||
}
|
||||
stats.BlkioStats.IoServiceBytesRecursive = blkioStats
|
||||
|
||||
if blkioStats, err = getBlkioStat(filepath.Join(path, "blkio.io_serviced_recursive")); err != nil {
|
||||
return err
|
||||
}
|
||||
stats.BlkioStats.IoServicedRecursive = blkioStats
|
||||
|
||||
if blkioStats, err = getBlkioStat(filepath.Join(path, "blkio.io_queued_recursive")); err != nil {
|
||||
return err
|
||||
}
|
||||
stats.BlkioStats.IoQueuedRecursive = blkioStats
|
||||
|
||||
if blkioStats, err = getBlkioStat(filepath.Join(path, "blkio.io_service_time_recursive")); err != nil {
|
||||
return err
|
||||
}
|
||||
stats.BlkioStats.IoServiceTimeRecursive = blkioStats
|
||||
|
||||
if blkioStats, err = getBlkioStat(filepath.Join(path, "blkio.io_wait_time_recursive")); err != nil {
|
||||
return err
|
||||
}
|
||||
stats.BlkioStats.IoWaitTimeRecursive = blkioStats
|
||||
|
||||
if blkioStats, err = getBlkioStat(filepath.Join(path, "blkio.io_merged_recursive")); err != nil {
|
||||
return err
|
||||
}
|
||||
stats.BlkioStats.IoMergedRecursive = blkioStats
|
||||
|
||||
if blkioStats, err = getBlkioStat(filepath.Join(path, "blkio.time_recursive")); err != nil {
|
||||
return err
|
||||
}
|
||||
stats.BlkioStats.IoTimeRecursive = blkioStats
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func getStats(path string, stats *cgroups.Stats) error {
|
||||
var blkioStats []cgroups.BlkioStatEntry
|
||||
var err error
|
||||
|
||||
if blkioStats, err = getBlkioStat(filepath.Join(path, "blkio.throttle.io_service_bytes")); err != nil {
|
||||
return err
|
||||
}
|
||||
stats.BlkioStats.IoServiceBytesRecursive = blkioStats
|
||||
|
||||
if blkioStats, err = getBlkioStat(filepath.Join(path, "blkio.throttle.io_serviced")); err != nil {
|
||||
return err
|
||||
}
|
||||
stats.BlkioStats.IoServicedRecursive = blkioStats
|
||||
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -6,7 +6,6 @@ import (
|
|||
"bufio"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
|
||||
"github.com/opencontainers/runc/libcontainer/cgroups"
|
||||
|
@ -87,7 +86,7 @@ func (s *CpuGroup) Set(path string, cgroup *configs.Cgroup) error {
|
|||
}
|
||||
|
||||
func (s *CpuGroup) GetStats(path string, stats *cgroups.Stats) error {
|
||||
f, err := os.Open(filepath.Join(path, "cpu.stat"))
|
||||
f, err := fscommon.OpenFile(path, "cpu.stat", os.O_RDONLY)
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
return nil
|
||||
|
|
|
@ -5,7 +5,6 @@ package fs
|
|||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
|
@ -83,8 +82,7 @@ func (s *CpuacctGroup) GetStats(path string, stats *cgroups.Stats) error {
|
|||
|
||||
// Returns user and kernel usage breakdown in nanoseconds.
|
||||
func getCpuUsageBreakdown(path string) (uint64, uint64, error) {
|
||||
userModeUsage := uint64(0)
|
||||
kernelModeUsage := uint64(0)
|
||||
var userModeUsage, kernelModeUsage uint64
|
||||
const (
|
||||
userField = "user"
|
||||
systemField = "system"
|
||||
|
@ -93,11 +91,11 @@ func getCpuUsageBreakdown(path string) (uint64, uint64, error) {
|
|||
// Expected format:
|
||||
// user <usage in ticks>
|
||||
// system <usage in ticks>
|
||||
data, err := ioutil.ReadFile(filepath.Join(path, cgroupCpuacctStat))
|
||||
data, err := fscommon.ReadFile(path, cgroupCpuacctStat)
|
||||
if err != nil {
|
||||
return 0, 0, err
|
||||
}
|
||||
fields := strings.Fields(string(data))
|
||||
fields := strings.Fields(data)
|
||||
if len(fields) < 4 {
|
||||
return 0, 0, fmt.Errorf("failure - %s is expected to have at least 4 fields", filepath.Join(path, cgroupCpuacctStat))
|
||||
}
|
||||
|
@ -119,11 +117,11 @@ func getCpuUsageBreakdown(path string) (uint64, uint64, error) {
|
|||
|
||||
func getPercpuUsage(path string) ([]uint64, error) {
|
||||
percpuUsage := []uint64{}
|
||||
data, err := ioutil.ReadFile(filepath.Join(path, "cpuacct.usage_percpu"))
|
||||
data, err := fscommon.ReadFile(path, "cpuacct.usage_percpu")
|
||||
if err != nil {
|
||||
return percpuUsage, err
|
||||
}
|
||||
for _, value := range strings.Fields(string(data)) {
|
||||
for _, value := range strings.Fields(data) {
|
||||
value, err := strconv.ParseUint(value, 10, 64)
|
||||
if err != nil {
|
||||
return percpuUsage, fmt.Errorf("Unable to convert param value to uint64: %s", err)
|
||||
|
@ -137,7 +135,7 @@ func getPercpuUsageInModes(path string) ([]uint64, []uint64, error) {
|
|||
usageKernelMode := []uint64{}
|
||||
usageUserMode := []uint64{}
|
||||
|
||||
file, err := os.Open(filepath.Join(path, cgroupCpuacctUsageAll))
|
||||
file, err := fscommon.OpenFile(path, cgroupCpuacctUsageAll, os.O_RDONLY)
|
||||
if os.IsNotExist(err) {
|
||||
return usageKernelMode, usageUserMode, nil
|
||||
} else if err != nil {
|
||||
|
|
|
@ -3,17 +3,17 @@
|
|||
package fs
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io/ioutil"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/moby/sys/mountinfo"
|
||||
"github.com/opencontainers/runc/libcontainer/cgroups"
|
||||
"github.com/opencontainers/runc/libcontainer/cgroups/fscommon"
|
||||
"github.com/opencontainers/runc/libcontainer/configs"
|
||||
libcontainerUtils "github.com/opencontainers/runc/libcontainer/utils"
|
||||
"github.com/pkg/errors"
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
type CpusetGroup struct {
|
||||
|
@ -41,30 +41,107 @@ func (s *CpusetGroup) Set(path string, cgroup *configs.Cgroup) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func (s *CpusetGroup) GetStats(path string, stats *cgroups.Stats) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Get the source mount point of directory passed in as argument.
|
||||
func getMount(dir string) (string, error) {
|
||||
mi, err := mountinfo.GetMounts(mountinfo.ParentsFilter(dir))
|
||||
func getCpusetStat(path string, filename string) ([]uint16, error) {
|
||||
var extracted []uint16
|
||||
fileContent, err := fscommon.GetCgroupParamString(path, filename)
|
||||
if err != nil {
|
||||
return "", err
|
||||
return extracted, err
|
||||
}
|
||||
if len(mi) < 1 {
|
||||
return "", errors.Errorf("Can't find mount point of %s", dir)
|
||||
if len(fileContent) == 0 {
|
||||
return extracted, fmt.Errorf("%s found to be empty", filepath.Join(path, filename))
|
||||
}
|
||||
|
||||
// find the longest mount point
|
||||
var idx, maxlen int
|
||||
for i := range mi {
|
||||
if len(mi[i].Mountpoint) > maxlen {
|
||||
maxlen = len(mi[i].Mountpoint)
|
||||
idx = i
|
||||
for _, s := range strings.Split(fileContent, ",") {
|
||||
splitted := strings.SplitN(s, "-", 3)
|
||||
switch len(splitted) {
|
||||
case 3:
|
||||
return extracted, fmt.Errorf("invalid values in %s", filepath.Join(path, filename))
|
||||
case 2:
|
||||
min, err := strconv.ParseUint(splitted[0], 10, 16)
|
||||
if err != nil {
|
||||
return extracted, err
|
||||
}
|
||||
max, err := strconv.ParseUint(splitted[1], 10, 16)
|
||||
if err != nil {
|
||||
return extracted, err
|
||||
}
|
||||
if min > max {
|
||||
return extracted, fmt.Errorf("invalid values in %s", filepath.Join(path, filename))
|
||||
}
|
||||
for i := min; i <= max; i++ {
|
||||
extracted = append(extracted, uint16(i))
|
||||
}
|
||||
case 1:
|
||||
value, err := strconv.ParseUint(s, 10, 16)
|
||||
if err != nil {
|
||||
return extracted, err
|
||||
}
|
||||
extracted = append(extracted, uint16(value))
|
||||
}
|
||||
}
|
||||
|
||||
return mi[idx].Mountpoint, nil
|
||||
return extracted, nil
|
||||
}
|
||||
|
||||
func (s *CpusetGroup) GetStats(path string, stats *cgroups.Stats) error {
|
||||
var err error
|
||||
|
||||
stats.CPUSetStats.CPUs, err = getCpusetStat(path, "cpuset.cpus")
|
||||
if err != nil && !errors.Is(err, os.ErrNotExist) {
|
||||
return err
|
||||
}
|
||||
|
||||
stats.CPUSetStats.CPUExclusive, err = fscommon.GetCgroupParamUint(path, "cpuset.cpu_exclusive")
|
||||
if err != nil && !errors.Is(err, os.ErrNotExist) {
|
||||
return err
|
||||
}
|
||||
|
||||
stats.CPUSetStats.Mems, err = getCpusetStat(path, "cpuset.mems")
|
||||
if err != nil && !errors.Is(err, os.ErrNotExist) {
|
||||
return err
|
||||
}
|
||||
|
||||
stats.CPUSetStats.MemHardwall, err = fscommon.GetCgroupParamUint(path, "cpuset.mem_hardwall")
|
||||
if err != nil && !errors.Is(err, os.ErrNotExist) {
|
||||
return err
|
||||
}
|
||||
|
||||
stats.CPUSetStats.MemExclusive, err = fscommon.GetCgroupParamUint(path, "cpuset.mem_exclusive")
|
||||
if err != nil && !errors.Is(err, os.ErrNotExist) {
|
||||
return err
|
||||
}
|
||||
|
||||
stats.CPUSetStats.MemoryMigrate, err = fscommon.GetCgroupParamUint(path, "cpuset.memory_migrate")
|
||||
if err != nil && !errors.Is(err, os.ErrNotExist) {
|
||||
return err
|
||||
}
|
||||
|
||||
stats.CPUSetStats.MemorySpreadPage, err = fscommon.GetCgroupParamUint(path, "cpuset.memory_spread_page")
|
||||
if err != nil && !errors.Is(err, os.ErrNotExist) {
|
||||
return err
|
||||
}
|
||||
|
||||
stats.CPUSetStats.MemorySpreadSlab, err = fscommon.GetCgroupParamUint(path, "cpuset.memory_spread_slab")
|
||||
if err != nil && !errors.Is(err, os.ErrNotExist) {
|
||||
return err
|
||||
}
|
||||
|
||||
stats.CPUSetStats.MemoryPressure, err = fscommon.GetCgroupParamUint(path, "cpuset.memory_pressure")
|
||||
if err != nil && !errors.Is(err, os.ErrNotExist) {
|
||||
return err
|
||||
}
|
||||
|
||||
stats.CPUSetStats.SchedLoadBalance, err = fscommon.GetCgroupParamUint(path, "cpuset.sched_load_balance")
|
||||
if err != nil && !errors.Is(err, os.ErrNotExist) {
|
||||
return err
|
||||
}
|
||||
|
||||
stats.CPUSetStats.SchedRelaxDomainLevel, err = fscommon.GetCgroupParamInt(path, "cpuset.sched_relax_domain_level")
|
||||
if err != nil && !errors.Is(err, os.ErrNotExist) {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *CpusetGroup) ApplyDir(dir string, cgroup *configs.Cgroup, pid int) error {
|
||||
|
@ -73,18 +150,13 @@ func (s *CpusetGroup) ApplyDir(dir string, cgroup *configs.Cgroup, pid int) erro
|
|||
if dir == "" {
|
||||
return nil
|
||||
}
|
||||
root, err := getMount(dir)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
root = filepath.Dir(root)
|
||||
// 'ensureParent' start with parent because we don't want to
|
||||
// explicitly inherit from parent, it could conflict with
|
||||
// 'cpuset.cpu_exclusive'.
|
||||
if err := s.ensureParent(filepath.Dir(dir), root); err != nil {
|
||||
if err := cpusetEnsureParent(filepath.Dir(dir)); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := os.MkdirAll(dir, 0755); err != nil {
|
||||
if err := os.Mkdir(dir, 0755); err != nil && !os.IsExist(err) {
|
||||
return err
|
||||
}
|
||||
// We didn't inherit cpuset configs from parent, but we have
|
||||
|
@ -103,59 +175,61 @@ func (s *CpusetGroup) ApplyDir(dir string, cgroup *configs.Cgroup, pid int) erro
|
|||
return cgroups.WriteCgroupProc(dir, pid)
|
||||
}
|
||||
|
||||
func (s *CpusetGroup) getSubsystemSettings(parent string) (cpus []byte, mems []byte, err error) {
|
||||
if cpus, err = ioutil.ReadFile(filepath.Join(parent, "cpuset.cpus")); err != nil {
|
||||
func getCpusetSubsystemSettings(parent string) (cpus, mems string, err error) {
|
||||
if cpus, err = fscommon.ReadFile(parent, "cpuset.cpus"); err != nil {
|
||||
return
|
||||
}
|
||||
if mems, err = ioutil.ReadFile(filepath.Join(parent, "cpuset.mems")); err != nil {
|
||||
if mems, err = fscommon.ReadFile(parent, "cpuset.mems"); err != nil {
|
||||
return
|
||||
}
|
||||
return cpus, mems, nil
|
||||
}
|
||||
|
||||
// ensureParent makes sure that the parent directory of current is created
|
||||
// and populated with the proper cpus and mems files copied from
|
||||
// it's parent.
|
||||
func (s *CpusetGroup) ensureParent(current, root string) error {
|
||||
// cpusetEnsureParent makes sure that the parent directories of current
|
||||
// are created and populated with the proper cpus and mems files copied
|
||||
// from their respective parent. It does that recursively, starting from
|
||||
// the top of the cpuset hierarchy (i.e. cpuset cgroup mount point).
|
||||
func cpusetEnsureParent(current string) error {
|
||||
var st unix.Statfs_t
|
||||
|
||||
parent := filepath.Dir(current)
|
||||
if libcontainerUtils.CleanPath(parent) == root {
|
||||
err := unix.Statfs(parent, &st)
|
||||
if err == nil && st.Type != unix.CGROUP_SUPER_MAGIC {
|
||||
return nil
|
||||
}
|
||||
// Avoid infinite recursion.
|
||||
if parent == current {
|
||||
return errors.New("cpuset: cgroup parent path outside cgroup root")
|
||||
// Treat non-existing directory as cgroupfs as it will be created,
|
||||
// and the root cpuset directory obviously exists.
|
||||
if err != nil && err != unix.ENOENT {
|
||||
return &os.PathError{Op: "statfs", Path: parent, Err: err}
|
||||
}
|
||||
if err := s.ensureParent(parent, root); err != nil {
|
||||
|
||||
if err := cpusetEnsureParent(parent); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := os.MkdirAll(current, 0755); err != nil {
|
||||
if err := os.Mkdir(current, 0755); err != nil && !os.IsExist(err) {
|
||||
return err
|
||||
}
|
||||
return s.copyIfNeeded(current, parent)
|
||||
return cpusetCopyIfNeeded(current, parent)
|
||||
}
|
||||
|
||||
// copyIfNeeded copies the cpuset.cpus and cpuset.mems from the parent
|
||||
// cpusetCopyIfNeeded copies the cpuset.cpus and cpuset.mems from the parent
|
||||
// directory to the current directory if the file's contents are 0
|
||||
func (s *CpusetGroup) copyIfNeeded(current, parent string) error {
|
||||
var (
|
||||
err error
|
||||
currentCpus, currentMems []byte
|
||||
parentCpus, parentMems []byte
|
||||
)
|
||||
|
||||
if currentCpus, currentMems, err = s.getSubsystemSettings(current); err != nil {
|
||||
func cpusetCopyIfNeeded(current, parent string) error {
|
||||
currentCpus, currentMems, err := getCpusetSubsystemSettings(current)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if parentCpus, parentMems, err = s.getSubsystemSettings(parent); err != nil {
|
||||
parentCpus, parentMems, err := getCpusetSubsystemSettings(parent)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if s.isEmpty(currentCpus) {
|
||||
if isEmptyCpuset(currentCpus) {
|
||||
if err := fscommon.WriteFile(current, "cpuset.cpus", string(parentCpus)); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if s.isEmpty(currentMems) {
|
||||
if isEmptyCpuset(currentMems) {
|
||||
if err := fscommon.WriteFile(current, "cpuset.mems", string(parentMems)); err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -163,13 +237,13 @@ func (s *CpusetGroup) copyIfNeeded(current, parent string) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func (s *CpusetGroup) isEmpty(b []byte) bool {
|
||||
return len(bytes.Trim(b, "\n")) == 0
|
||||
func isEmptyCpuset(str string) bool {
|
||||
return str == "" || str == "\n"
|
||||
}
|
||||
|
||||
func (s *CpusetGroup) ensureCpusAndMems(path string, cgroup *configs.Cgroup) error {
|
||||
if err := s.Set(path, cgroup); err != nil {
|
||||
return err
|
||||
}
|
||||
return s.copyIfNeeded(path, filepath.Dir(path))
|
||||
return cpusetCopyIfNeeded(path, filepath.Dir(path))
|
||||
}
|
||||
|
|
|
@ -8,9 +8,10 @@ import (
|
|||
"reflect"
|
||||
|
||||
"github.com/opencontainers/runc/libcontainer/cgroups"
|
||||
"github.com/opencontainers/runc/libcontainer/cgroups/devices"
|
||||
cgroupdevices "github.com/opencontainers/runc/libcontainer/cgroups/devices"
|
||||
"github.com/opencontainers/runc/libcontainer/cgroups/fscommon"
|
||||
"github.com/opencontainers/runc/libcontainer/configs"
|
||||
"github.com/opencontainers/runc/libcontainer/devices"
|
||||
"github.com/opencontainers/runc/libcontainer/system"
|
||||
)
|
||||
|
||||
|
@ -34,17 +35,17 @@ func (s *DevicesGroup) Apply(path string, d *cgroupData) error {
|
|||
return join(path, d.pid)
|
||||
}
|
||||
|
||||
func loadEmulator(path string) (*devices.Emulator, error) {
|
||||
func loadEmulator(path string) (*cgroupdevices.Emulator, error) {
|
||||
list, err := fscommon.ReadFile(path, "devices.list")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return devices.EmulatorFromList(bytes.NewBufferString(list))
|
||||
return cgroupdevices.EmulatorFromList(bytes.NewBufferString(list))
|
||||
}
|
||||
|
||||
func buildEmulator(rules []*configs.DeviceRule) (*devices.Emulator, error) {
|
||||
func buildEmulator(rules []*devices.Rule) (*cgroupdevices.Emulator, error) {
|
||||
// This defaults to a white-list -- which is what we want!
|
||||
emu := &devices.Emulator{}
|
||||
emu := &cgroupdevices.Emulator{}
|
||||
for _, rule := range rules {
|
||||
if err := emu.Apply(*rule); err != nil {
|
||||
return nil, err
|
||||
|
|
|
@ -28,33 +28,54 @@ func (s *FreezerGroup) Apply(path string, d *cgroupData) error {
|
|||
|
||||
func (s *FreezerGroup) Set(path string, cgroup *configs.Cgroup) error {
|
||||
switch cgroup.Resources.Freezer {
|
||||
case configs.Frozen, configs.Thawed:
|
||||
for {
|
||||
// In case this loop does not exit because it doesn't get the expected
|
||||
// state, let's write again this state, hoping it's going to be properly
|
||||
// set this time. Otherwise, this loop could run infinitely, waiting for
|
||||
// a state change that would never happen.
|
||||
if err := fscommon.WriteFile(path, "freezer.state", string(cgroup.Resources.Freezer)); err != nil {
|
||||
case configs.Frozen:
|
||||
// As per older kernel docs (freezer-subsystem.txt before
|
||||
// kernel commit ef9fe980c6fcc1821), if FREEZING is seen,
|
||||
// userspace should either retry or thaw. While current
|
||||
// kernel cgroup v1 docs no longer mention a need to retry,
|
||||
// the kernel (tested on v5.4, Ubuntu 20.04) can't reliably
|
||||
// freeze a cgroup while new processes keep appearing in it
|
||||
// (either via fork/clone or by writing new PIDs to
|
||||
// cgroup.procs).
|
||||
//
|
||||
// The number of retries below is chosen to have a decent
|
||||
// chance to succeed even in the worst case scenario (runc
|
||||
// pause/unpause with parallel runc exec).
|
||||
//
|
||||
// Adding any amount of sleep in between retries did not
|
||||
// increase the chances of successful freeze.
|
||||
for i := 0; i < 1000; i++ {
|
||||
if err := fscommon.WriteFile(path, "freezer.state", string(configs.Frozen)); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
state, err := s.GetState(path)
|
||||
state, err := fscommon.ReadFile(path, "freezer.state")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if state == cgroup.Resources.Freezer {
|
||||
break
|
||||
state = strings.TrimSpace(state)
|
||||
switch state {
|
||||
case "FREEZING":
|
||||
continue
|
||||
case string(configs.Frozen):
|
||||
return nil
|
||||
default:
|
||||
// should never happen
|
||||
return fmt.Errorf("unexpected state %s while freezing", strings.TrimSpace(state))
|
||||
}
|
||||
|
||||
time.Sleep(1 * time.Millisecond)
|
||||
}
|
||||
// Despite our best efforts, it got stuck in FREEZING.
|
||||
// Leaving it in this state is bad and dangerous, so
|
||||
// let's (try to) thaw it back and error out.
|
||||
_ = fscommon.WriteFile(path, "freezer.state", string(configs.Thawed))
|
||||
return errors.New("unable to freeze")
|
||||
case configs.Thawed:
|
||||
return fscommon.WriteFile(path, "freezer.state", string(configs.Thawed))
|
||||
case configs.Undefined:
|
||||
return nil
|
||||
default:
|
||||
return fmt.Errorf("Invalid argument '%s' to freezer.state", string(cgroup.Resources.Freezer))
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *FreezerGroup) GetStats(path string, stats *cgroups.Stats) error {
|
||||
|
|
|
@ -3,11 +3,9 @@
|
|||
package fs
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/opencontainers/runc/libcontainer/cgroups"
|
||||
|
@ -133,46 +131,19 @@ func getCgroupRoot() (string, error) {
|
|||
return cgroupRoot, nil
|
||||
}
|
||||
|
||||
// slow path: parse mountinfo, find the first mount where fs=cgroup
|
||||
// (e.g. "/sys/fs/cgroup/memory"), use its parent.
|
||||
f, err := os.Open("/proc/self/mountinfo")
|
||||
// slow path: parse mountinfo
|
||||
mi, err := cgroups.GetCgroupMounts(false)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
var root string
|
||||
scanner := bufio.NewScanner(f)
|
||||
for scanner.Scan() {
|
||||
text := scanner.Text()
|
||||
fields := strings.Split(text, " ")
|
||||
// Safe as mountinfo encodes mountpoints with spaces as \040.
|
||||
index := strings.Index(text, " - ")
|
||||
postSeparatorFields := strings.Fields(text[index+3:])
|
||||
numPostFields := len(postSeparatorFields)
|
||||
|
||||
// This is an error as we can't detect if the mount is for "cgroup"
|
||||
if numPostFields == 0 {
|
||||
return "", fmt.Errorf("mountinfo: found no fields post '-' in %q", text)
|
||||
}
|
||||
|
||||
if postSeparatorFields[0] == "cgroup" {
|
||||
// Check that the mount is properly formatted.
|
||||
if numPostFields < 3 {
|
||||
return "", fmt.Errorf("Error found less than 3 fields post '-' in %q", text)
|
||||
}
|
||||
|
||||
root = filepath.Dir(fields[4])
|
||||
break
|
||||
}
|
||||
}
|
||||
if err := scanner.Err(); err != nil {
|
||||
return "", err
|
||||
}
|
||||
if root == "" {
|
||||
if len(mi) < 1 {
|
||||
return "", errors.New("no cgroup mount found in mountinfo")
|
||||
}
|
||||
|
||||
// Get the first cgroup mount (e.g. "/sys/fs/cgroup/memory"),
|
||||
// use its parent directory.
|
||||
root := filepath.Dir(mi[0].Mountpoint)
|
||||
|
||||
if _, err := os.Stat(root); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
@ -218,28 +189,31 @@ func (m *manager) Apply(pid int) (err error) {
|
|||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
|
||||
var c = m.cgroups
|
||||
|
||||
d, err := getCgroupData(m.cgroups, pid)
|
||||
if err != nil {
|
||||
return err
|
||||
c := m.cgroups
|
||||
if c.Resources.Unified != nil {
|
||||
return cgroups.ErrV1NoUnified
|
||||
}
|
||||
|
||||
m.paths = make(map[string]string)
|
||||
if c.Paths != nil {
|
||||
cgMap, err := cgroups.ParseCgroupFile("/proc/self/cgroup")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for name, path := range c.Paths {
|
||||
_, err := d.path(name)
|
||||
if err != nil {
|
||||
if cgroups.IsNotFound(err) {
|
||||
continue
|
||||
}
|
||||
return err
|
||||
// XXX(kolyshkin@): why this check is needed?
|
||||
if _, ok := cgMap[name]; ok {
|
||||
m.paths[name] = path
|
||||
}
|
||||
m.paths[name] = path
|
||||
}
|
||||
return cgroups.EnterPid(m.paths, pid)
|
||||
}
|
||||
|
||||
d, err := getCgroupData(m.cgroups, pid)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, sys := range subsystems {
|
||||
p, err := d.path(sys.Name())
|
||||
if err != nil {
|
||||
|
@ -274,11 +248,7 @@ func (m *manager) Destroy() error {
|
|||
}
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
if err := cgroups.RemovePaths(m.paths); err != nil {
|
||||
return err
|
||||
}
|
||||
m.paths = make(map[string]string)
|
||||
return nil
|
||||
return cgroups.RemovePaths(m.paths)
|
||||
}
|
||||
|
||||
func (m *manager) Path(subsys string) string {
|
||||
|
@ -313,6 +283,9 @@ func (m *manager) Set(container *configs.Config) error {
|
|||
if m.cgroups != nil && m.cgroups.Paths != nil {
|
||||
return nil
|
||||
}
|
||||
if container.Cgroups.Resources.Unified != nil {
|
||||
return cgroups.ErrV1NoUnified
|
||||
}
|
||||
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
|
@ -425,16 +398,6 @@ func join(path string, pid int) error {
|
|||
return cgroups.WriteCgroupProc(path, pid)
|
||||
}
|
||||
|
||||
func removePath(p string, err error) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if p != "" {
|
||||
return os.RemoveAll(p)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *manager) GetPaths() map[string]string {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
|
|
|
@ -5,7 +5,6 @@ package fs
|
|||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/opencontainers/runc/libcontainer/cgroups"
|
||||
"github.com/opencontainers/runc/libcontainer/cgroups/fscommon"
|
||||
|
@ -25,7 +24,7 @@ func (s *HugetlbGroup) Apply(path string, d *cgroupData) error {
|
|||
|
||||
func (s *HugetlbGroup) Set(path string, cgroup *configs.Cgroup) error {
|
||||
for _, hugetlb := range cgroup.Resources.HugetlbLimit {
|
||||
if err := fscommon.WriteFile(path, strings.Join([]string{"hugetlb", hugetlb.Pagesize, "limit_in_bytes"}, "."), strconv.FormatUint(hugetlb.Limit, 10)); err != nil {
|
||||
if err := fscommon.WriteFile(path, "hugetlb."+hugetlb.Pagesize+".limit_in_bytes", strconv.FormatUint(hugetlb.Limit, 10)); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
@ -39,21 +38,21 @@ func (s *HugetlbGroup) GetStats(path string, stats *cgroups.Stats) error {
|
|||
return nil
|
||||
}
|
||||
for _, pageSize := range HugePageSizes {
|
||||
usage := strings.Join([]string{"hugetlb", pageSize, "usage_in_bytes"}, ".")
|
||||
usage := "hugetlb." + pageSize + ".usage_in_bytes"
|
||||
value, err := fscommon.GetCgroupParamUint(path, usage)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to parse %s - %v", usage, err)
|
||||
}
|
||||
hugetlbStats.Usage = value
|
||||
|
||||
maxUsage := strings.Join([]string{"hugetlb", pageSize, "max_usage_in_bytes"}, ".")
|
||||
maxUsage := "hugetlb." + pageSize + ".max_usage_in_bytes"
|
||||
value, err = fscommon.GetCgroupParamUint(path, maxUsage)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to parse %s - %v", maxUsage, err)
|
||||
}
|
||||
hugetlbStats.MaxUsage = value
|
||||
|
||||
failcnt := strings.Join([]string{"hugetlb", pageSize, "failcnt"}, ".")
|
||||
failcnt := "hugetlb." + pageSize + ".failcnt"
|
||||
value, err = fscommon.GetCgroupParamUint(path, failcnt)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to parse %s - %v", failcnt, err)
|
||||
|
|
|
@ -5,11 +5,11 @@ package fs
|
|||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
|
||||
"github.com/opencontainers/runc/libcontainer/cgroups"
|
||||
"github.com/opencontainers/runc/libcontainer/cgroups/fscommon"
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
|
@ -42,7 +42,7 @@ func setKernelMemory(path string, kernelMemoryLimit int64) error {
|
|||
// doesn't support it we *must* error out.
|
||||
return errors.New("kernel memory accounting not supported by this kernel")
|
||||
}
|
||||
if err := ioutil.WriteFile(filepath.Join(path, cgroupKernelMemoryLimit), []byte(strconv.FormatInt(kernelMemoryLimit, 10)), 0700); err != nil {
|
||||
if err := fscommon.WriteFile(path, cgroupKernelMemoryLimit, strconv.FormatInt(kernelMemoryLimit, 10)); err != nil {
|
||||
// Check if the error number returned by the syscall is "EBUSY"
|
||||
// The EBUSY signal is returned on attempts to write to the
|
||||
// memory.kmem.limit_in_bytes file if the cgroup has children or
|
||||
|
@ -50,7 +50,7 @@ func setKernelMemory(path string, kernelMemoryLimit int64) error {
|
|||
if errors.Is(err, unix.EBUSY) {
|
||||
return fmt.Errorf("failed to set %s, because either tasks have already joined this cgroup or it has children", cgroupKernelMemoryLimit)
|
||||
}
|
||||
return fmt.Errorf("failed to write %v to %v: %v", kernelMemoryLimit, cgroupKernelMemoryLimit, err)
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -7,7 +7,6 @@ import (
|
|||
"fmt"
|
||||
"math"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
@ -18,16 +17,8 @@ import (
|
|||
)
|
||||
|
||||
const (
|
||||
numaNodeSymbol = "N"
|
||||
numaStatColumnSeparator = " "
|
||||
numaStatKeyValueSeparator = "="
|
||||
numaStatMaxColumns = math.MaxUint8 + 1
|
||||
numaStatValueIndex = 1
|
||||
numaStatTypeIndex = 0
|
||||
numaStatColumnSliceLength = 2
|
||||
cgroupMemorySwapLimit = "memory.memsw.limit_in_bytes"
|
||||
cgroupMemoryLimit = "memory.limit_in_bytes"
|
||||
cgroupMemoryPagesByNuma = "memory.numa_stat"
|
||||
cgroupMemorySwapLimit = "memory.memsw.limit_in_bytes"
|
||||
cgroupMemoryLimit = "memory.limit_in_bytes"
|
||||
)
|
||||
|
||||
type MemoryGroup struct {
|
||||
|
@ -160,7 +151,7 @@ func (s *MemoryGroup) Set(path string, cgroup *configs.Cgroup) error {
|
|||
|
||||
func (s *MemoryGroup) GetStats(path string, stats *cgroups.Stats) error {
|
||||
// Set stats from memory.stat.
|
||||
statsFile, err := os.Open(filepath.Join(path, "memory.stat"))
|
||||
statsFile, err := fscommon.OpenFile(path, "memory.stat", os.O_RDONLY)
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
return nil
|
||||
|
@ -200,8 +191,7 @@ func (s *MemoryGroup) GetStats(path string, stats *cgroups.Stats) error {
|
|||
}
|
||||
stats.MemoryStats.KernelTCPUsage = kernelTCPUsage
|
||||
|
||||
useHierarchy := strings.Join([]string{"memory", "use_hierarchy"}, ".")
|
||||
value, err := fscommon.GetCgroupParamUint(path, useHierarchy)
|
||||
value, err := fscommon.GetCgroupParamUint(path, "memory.use_hierarchy")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -233,12 +223,14 @@ func getMemoryData(path, name string) (cgroups.MemoryData, error) {
|
|||
|
||||
moduleName := "memory"
|
||||
if name != "" {
|
||||
moduleName = strings.Join([]string{"memory", name}, ".")
|
||||
moduleName = "memory." + name
|
||||
}
|
||||
usage := strings.Join([]string{moduleName, "usage_in_bytes"}, ".")
|
||||
maxUsage := strings.Join([]string{moduleName, "max_usage_in_bytes"}, ".")
|
||||
failcnt := strings.Join([]string{moduleName, "failcnt"}, ".")
|
||||
limit := strings.Join([]string{moduleName, "limit_in_bytes"}, ".")
|
||||
var (
|
||||
usage = moduleName + ".usage_in_bytes"
|
||||
maxUsage = moduleName + ".max_usage_in_bytes"
|
||||
failcnt = moduleName + ".failcnt"
|
||||
limit = moduleName + ".limit_in_bytes"
|
||||
)
|
||||
|
||||
value, err := fscommon.GetCgroupParamUint(path, usage)
|
||||
if err != nil {
|
||||
|
@ -277,47 +269,81 @@ func getMemoryData(path, name string) (cgroups.MemoryData, error) {
|
|||
}
|
||||
|
||||
func getPageUsageByNUMA(cgroupPath string) (cgroups.PageUsageByNUMA, error) {
|
||||
const (
|
||||
maxColumns = math.MaxUint8 + 1
|
||||
filename = "memory.numa_stat"
|
||||
)
|
||||
stats := cgroups.PageUsageByNUMA{}
|
||||
|
||||
file, err := os.Open(path.Join(cgroupPath, cgroupMemoryPagesByNuma))
|
||||
file, err := fscommon.OpenFile(cgroupPath, filename, os.O_RDONLY)
|
||||
if os.IsNotExist(err) {
|
||||
return stats, nil
|
||||
} else if err != nil {
|
||||
return stats, err
|
||||
}
|
||||
|
||||
// File format is documented in linux/Documentation/cgroup-v1/memory.txt
|
||||
// and it looks like this:
|
||||
//
|
||||
// total=<total pages> N0=<node 0 pages> N1=<node 1 pages> ...
|
||||
// file=<total file pages> N0=<node 0 pages> N1=<node 1 pages> ...
|
||||
// anon=<total anon pages> N0=<node 0 pages> N1=<node 1 pages> ...
|
||||
// unevictable=<total anon pages> N0=<node 0 pages> N1=<node 1 pages> ...
|
||||
// hierarchical_<counter>=<counter pages> N0=<node 0 pages> N1=<node 1 pages> ...
|
||||
|
||||
scanner := bufio.NewScanner(file)
|
||||
for scanner.Scan() {
|
||||
var statsType string
|
||||
statsByType := cgroups.PageStats{Nodes: map[uint8]uint64{}}
|
||||
columns := strings.SplitN(scanner.Text(), numaStatColumnSeparator, numaStatMaxColumns)
|
||||
var field *cgroups.PageStats
|
||||
|
||||
for _, column := range columns {
|
||||
pagesByNode := strings.SplitN(column, numaStatKeyValueSeparator, numaStatColumnSliceLength)
|
||||
line := scanner.Text()
|
||||
columns := strings.SplitN(line, " ", maxColumns)
|
||||
for i, column := range columns {
|
||||
byNode := strings.SplitN(column, "=", 2)
|
||||
// Some custom kernels have non-standard fields, like
|
||||
// numa_locality 0 0 0 0 0 0 0 0 0 0
|
||||
// numa_exectime 0
|
||||
if len(byNode) < 2 {
|
||||
if i == 0 {
|
||||
// Ignore/skip those.
|
||||
break
|
||||
} else {
|
||||
// The first column was already validated,
|
||||
// so be strict to the rest.
|
||||
return stats, fmt.Errorf("malformed line %q in %s",
|
||||
line, filename)
|
||||
}
|
||||
}
|
||||
key, val := byNode[0], byNode[1]
|
||||
if i == 0 { // First column: key is name, val is total.
|
||||
field = getNUMAField(&stats, key)
|
||||
if field == nil { // unknown field (new kernel?)
|
||||
break
|
||||
}
|
||||
field.Total, err = strconv.ParseUint(val, 0, 64)
|
||||
if err != nil {
|
||||
return stats, err
|
||||
}
|
||||
field.Nodes = map[uint8]uint64{}
|
||||
} else { // Subsequent columns: key is N<id>, val is usage.
|
||||
if len(key) < 2 || key[0] != 'N' {
|
||||
// This is definitely an error.
|
||||
return stats, fmt.Errorf("malformed line %q in %s",
|
||||
line, filename)
|
||||
}
|
||||
|
||||
if strings.HasPrefix(pagesByNode[numaStatTypeIndex], numaNodeSymbol) {
|
||||
nodeID, err := strconv.ParseUint(pagesByNode[numaStatTypeIndex][1:], 10, 8)
|
||||
n, err := strconv.ParseUint(key[1:], 10, 8)
|
||||
if err != nil {
|
||||
return cgroups.PageUsageByNUMA{}, err
|
||||
}
|
||||
|
||||
statsByType.Nodes[uint8(nodeID)], err = strconv.ParseUint(pagesByNode[numaStatValueIndex], 0, 64)
|
||||
if err != nil {
|
||||
return cgroups.PageUsageByNUMA{}, err
|
||||
}
|
||||
} else {
|
||||
statsByType.Total, err = strconv.ParseUint(pagesByNode[numaStatValueIndex], 0, 64)
|
||||
usage, err := strconv.ParseUint(val, 10, 64)
|
||||
if err != nil {
|
||||
return cgroups.PageUsageByNUMA{}, err
|
||||
}
|
||||
|
||||
statsType = pagesByNode[numaStatTypeIndex]
|
||||
field.Nodes[uint8(n)] = usage
|
||||
}
|
||||
|
||||
err := addNUMAStatsByType(&stats, statsByType, statsType)
|
||||
if err != nil {
|
||||
return cgroups.PageUsageByNUMA{}, err
|
||||
}
|
||||
}
|
||||
}
|
||||
err = scanner.Err()
|
||||
|
@ -328,26 +354,24 @@ func getPageUsageByNUMA(cgroupPath string) (cgroups.PageUsageByNUMA, error) {
|
|||
return stats, nil
|
||||
}
|
||||
|
||||
func addNUMAStatsByType(stats *cgroups.PageUsageByNUMA, byTypeStats cgroups.PageStats, statsType string) error {
|
||||
switch statsType {
|
||||
func getNUMAField(stats *cgroups.PageUsageByNUMA, name string) *cgroups.PageStats {
|
||||
switch name {
|
||||
case "total":
|
||||
stats.Total = byTypeStats
|
||||
return &stats.Total
|
||||
case "file":
|
||||
stats.File = byTypeStats
|
||||
return &stats.File
|
||||
case "anon":
|
||||
stats.Anon = byTypeStats
|
||||
return &stats.Anon
|
||||
case "unevictable":
|
||||
stats.Unevictable = byTypeStats
|
||||
return &stats.Unevictable
|
||||
case "hierarchical_total":
|
||||
stats.Hierarchical.Total = byTypeStats
|
||||
return &stats.Hierarchical.Total
|
||||
case "hierarchical_file":
|
||||
stats.Hierarchical.File = byTypeStats
|
||||
return &stats.Hierarchical.File
|
||||
case "hierarchical_anon":
|
||||
stats.Hierarchical.Anon = byTypeStats
|
||||
return &stats.Hierarchical.Anon
|
||||
case "hierarchical_unevictable":
|
||||
stats.Hierarchical.Unevictable = byTypeStats
|
||||
default:
|
||||
return fmt.Errorf("unsupported NUMA page type found: %s", statsType)
|
||||
return &stats.Hierarchical.Unevictable
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -19,7 +19,7 @@ func (s *NameGroup) Name() string {
|
|||
func (s *NameGroup) Apply(path string, d *cgroupData) error {
|
||||
if s.Join {
|
||||
// ignore errors if the named cgroup does not exist
|
||||
join(path, d.pid)
|
||||
_ = join(path, d.pid)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -5,7 +5,6 @@ package fs2
|
|||
import (
|
||||
"bufio"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
|
||||
"github.com/opencontainers/runc/libcontainer/cgroups"
|
||||
|
@ -50,7 +49,7 @@ func setCpu(dirPath string, cgroup *configs.Cgroup) error {
|
|||
return nil
|
||||
}
|
||||
func statCpu(dirPath string, stats *cgroups.Stats) error {
|
||||
f, err := os.Open(filepath.Join(dirPath, "cpu.stat"))
|
||||
f, err := fscommon.OpenFile(dirPath, "cpu.stat", os.O_RDONLY)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
|
@ -1,19 +1,17 @@
|
|||
package fs2
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/opencontainers/runc/libcontainer/cgroups/fscommon"
|
||||
"github.com/opencontainers/runc/libcontainer/configs"
|
||||
)
|
||||
|
||||
func supportedControllers(cgroup *configs.Cgroup) ([]byte, error) {
|
||||
const file = UnifiedMountpoint + "/cgroup.controllers"
|
||||
return ioutil.ReadFile(file)
|
||||
func supportedControllers(cgroup *configs.Cgroup) (string, error) {
|
||||
return fscommon.ReadFile(UnifiedMountpoint, "/cgroup.controllers")
|
||||
}
|
||||
|
||||
// needAnyControllers returns whether we enable some supported controllers or not,
|
||||
|
@ -31,7 +29,7 @@ func needAnyControllers(cgroup *configs.Cgroup) (bool, error) {
|
|||
return false, err
|
||||
}
|
||||
avail := make(map[string]struct{})
|
||||
for _, ctr := range strings.Fields(string(content)) {
|
||||
for _, ctr := range strings.Fields(content) {
|
||||
avail[ctr] = struct{}{}
|
||||
}
|
||||
|
||||
|
@ -81,8 +79,12 @@ func CreateCgroupPath(path string, c *configs.Cgroup) (Err error) {
|
|||
return err
|
||||
}
|
||||
|
||||
ctrs := bytes.Fields(content)
|
||||
res := append([]byte("+"), bytes.Join(ctrs, []byte(" +"))...)
|
||||
const (
|
||||
cgTypeFile = "cgroup.type"
|
||||
cgStCtlFile = "cgroup.subtree_control"
|
||||
)
|
||||
ctrs := strings.Fields(content)
|
||||
res := "+" + strings.Join(ctrs, " +")
|
||||
|
||||
elements := strings.Split(path, "/")
|
||||
elements = elements[3:]
|
||||
|
@ -103,9 +105,9 @@ func CreateCgroupPath(path string, c *configs.Cgroup) (Err error) {
|
|||
}
|
||||
}()
|
||||
}
|
||||
cgTypeFile := filepath.Join(current, "cgroup.type")
|
||||
cgType, _ := ioutil.ReadFile(cgTypeFile)
|
||||
switch strings.TrimSpace(string(cgType)) {
|
||||
cgType, _ := fscommon.ReadFile(current, cgTypeFile)
|
||||
cgType = strings.TrimSpace(cgType)
|
||||
switch cgType {
|
||||
// If the cgroup is in an invalid mode (usually this means there's an internal
|
||||
// process in the cgroup tree, because we created a cgroup under an
|
||||
// already-populated-by-other-processes cgroup), then we have to error out if
|
||||
|
@ -120,7 +122,7 @@ func CreateCgroupPath(path string, c *configs.Cgroup) (Err error) {
|
|||
// since that means we're a properly delegated cgroup subtree) but in
|
||||
// this case there's not much we can do and it's better than giving an
|
||||
// error.
|
||||
_ = ioutil.WriteFile(cgTypeFile, []byte("threaded"), 0644)
|
||||
_ = fscommon.WriteFile(current, cgTypeFile, "threaded")
|
||||
}
|
||||
// If the cgroup is in (threaded) or (domain threaded) mode, we can only use thread-aware controllers
|
||||
// (and you cannot usually take a cgroup out of threaded mode).
|
||||
|
@ -128,18 +130,17 @@ func CreateCgroupPath(path string, c *configs.Cgroup) (Err error) {
|
|||
fallthrough
|
||||
case "threaded":
|
||||
if containsDomainController(c) {
|
||||
return fmt.Errorf("cannot enter cgroupv2 %q with domain controllers -- it is in %s mode", current, strings.TrimSpace(string(cgType)))
|
||||
return fmt.Errorf("cannot enter cgroupv2 %q with domain controllers -- it is in %s mode", current, cgType)
|
||||
}
|
||||
}
|
||||
}
|
||||
// enable all supported controllers
|
||||
if i < len(elements)-1 {
|
||||
file := filepath.Join(current, "cgroup.subtree_control")
|
||||
if err := ioutil.WriteFile(file, res, 0644); err != nil {
|
||||
if err := fscommon.WriteFile(current, cgStCtlFile, res); err != nil {
|
||||
// try write one by one
|
||||
allCtrs := bytes.Split(res, []byte(" "))
|
||||
allCtrs := strings.Split(res, " ")
|
||||
for _, ctr := range allCtrs {
|
||||
_ = ioutil.WriteFile(file, ctr, 0644)
|
||||
_ = fscommon.WriteFile(current, cgStCtlFile, ctr)
|
||||
}
|
||||
}
|
||||
// Some controllers might not be enabled when rootless or containerized,
|
||||
|
|
|
@ -6,11 +6,12 @@ import (
|
|||
"github.com/opencontainers/runc/libcontainer/cgroups/ebpf"
|
||||
"github.com/opencontainers/runc/libcontainer/cgroups/ebpf/devicefilter"
|
||||
"github.com/opencontainers/runc/libcontainer/configs"
|
||||
"github.com/opencontainers/runc/libcontainer/devices"
|
||||
"github.com/pkg/errors"
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
func isRWM(perms configs.DevicePermissions) bool {
|
||||
func isRWM(perms devices.Permissions) bool {
|
||||
var r, w, m bool
|
||||
for _, perm := range perms {
|
||||
switch perm {
|
||||
|
@ -61,7 +62,7 @@ func setDevices(dirPath string, cgroup *configs.Cgroup) error {
|
|||
//
|
||||
// The real issue is that BPF_F_ALLOW_MULTI makes it hard to have a
|
||||
// race-free blacklist because it acts as a whitelist by default, and
|
||||
// having a deny-everything program cannot be overriden by other
|
||||
// having a deny-everything program cannot be overridden by other
|
||||
// programs. You could temporarily insert a deny-everything program
|
||||
// but that would result in spurrious failures during updates.
|
||||
if _, err := ebpf.LoadAttachCgroupDeviceFilter(insts, license, dirFD); err != nil {
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue