Move pkg/cgroups into common to be shared with buildah
Signed-off-by: Daniel J Walsh <dwalsh@redhat.com>
This commit is contained in:
parent
699e131252
commit
e0c3749251
|
|
@ -7,17 +7,21 @@ require (
|
|||
github.com/containers/image/v5 v5.17.1-0.20211201214147-603ec1341d58
|
||||
github.com/containers/ocicrypt v1.1.2
|
||||
github.com/containers/storage v1.37.1-0.20211119174841-bf170b3ddac0
|
||||
github.com/coreos/go-systemd/v22 v22.3.2
|
||||
github.com/cyphar/filepath-securejoin v0.2.3 // indirect
|
||||
github.com/disiqueira/gotree/v3 v3.0.2
|
||||
github.com/docker/distribution v2.7.1+incompatible
|
||||
github.com/docker/docker v20.10.11+incompatible
|
||||
github.com/docker/go-units v0.4.0
|
||||
github.com/fsnotify/fsnotify v1.5.1 // indirect
|
||||
github.com/ghodss/yaml v1.0.0
|
||||
github.com/godbus/dbus/v5 v5.0.6
|
||||
github.com/gorilla/mux v1.8.0 // indirect
|
||||
github.com/hashicorp/go-multierror v1.1.1
|
||||
github.com/jinzhu/copier v0.3.4
|
||||
github.com/json-iterator/go v1.1.12
|
||||
github.com/mitchellh/mapstructure v1.4.3
|
||||
github.com/moby/term v0.0.0-20201216013528-df9cb8a40635 // indirect
|
||||
github.com/moby/term v0.0.0-20210619224110-3f7ff695adc6 // indirect
|
||||
github.com/onsi/ginkgo v1.16.5
|
||||
github.com/onsi/gomega v1.17.0
|
||||
github.com/opencontainers/go-digest v1.0.0
|
||||
|
|
@ -33,7 +37,8 @@ require (
|
|||
github.com/spf13/pflag v1.0.5
|
||||
github.com/stretchr/testify v1.7.0
|
||||
github.com/syndtr/gocapability v0.0.0-20200815063812-42c35b437635
|
||||
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97 // indirect
|
||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c
|
||||
golang.org/x/sys v0.0.0-20211025201205-69cdffdb9359
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1
|
||||
golang.org/x/term v0.0.0-20210615171337-6886f2dfbf5b
|
||||
)
|
||||
|
|
|
|||
|
|
@ -41,8 +41,9 @@ dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7
|
|||
github.com/14rcole/gopopulate v0.0.0-20180821133914-b175b219e774 h1:SCbEWT58NSt7d2mcFdvxC9uyrdcTfvBbPLThhkDmXzg=
|
||||
github.com/14rcole/gopopulate v0.0.0-20180821133914-b175b219e774/go.mod h1:6/0dYRLLXyJjbkIPeeGyoJ/eKOSI0eU6eTlCBYibgd0=
|
||||
github.com/Azure/azure-sdk-for-go v16.2.1+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc=
|
||||
github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78 h1:w+iIsaOQNcT7OZ575w+acHgRric5iCyQh+xv+KJ4HB8=
|
||||
github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78/go.mod h1:LmzpDX56iTiv29bbRTIsUNlaFfuhWRQBWjQdVyAevI8=
|
||||
github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 h1:UQHMgLO+TxOElx5B5HZ4hJQsoJ/PvUvKRhJHDQXO8P8=
|
||||
github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E=
|
||||
github.com/Azure/go-autorest v10.8.1+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24=
|
||||
github.com/Azure/go-autorest v14.2.0+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24=
|
||||
github.com/Azure/go-autorest/autorest v0.11.1/go.mod h1:JFgpikqFJ/MleTTxwepExTKnFUKKszPS8UavbQYUMuw=
|
||||
|
|
@ -270,8 +271,9 @@ github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7Do
|
|||
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
||||
github.com/creack/pty v1.1.11 h1:07n33Z8lZxZ2qwegKbObQohDhXDQxiMMz1NOUGYlesw=
|
||||
github.com/creack/pty v1.1.11/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
||||
github.com/cyphar/filepath-securejoin v0.2.2 h1:jCwT2GTP+PY5nBz3c/YL5PAIbusElVrPujOBSCj8xRg=
|
||||
github.com/cyphar/filepath-securejoin v0.2.2/go.mod h1:FpkQEhXnPnOthhzymB7CGsFk2G9VLXONKD9G7QGMM+4=
|
||||
github.com/cyphar/filepath-securejoin v0.2.3 h1:YX6ebbZCZP7VkM3scTTokDgBL2TY741X51MTk3ycuNI=
|
||||
github.com/cyphar/filepath-securejoin v0.2.3/go.mod h1:aPGpWjXOXUn2NCNjFvBE6aRxGGx79pTxQpKOJNYHHl4=
|
||||
github.com/d2g/dhcp4 v0.0.0-20170904100407-a1d1b6c41b1c/go.mod h1:Ct2BUK8SB0YC1SMSibvLzxjeJLnrYEVLULFNiHY9YfQ=
|
||||
github.com/d2g/dhcp4client v1.0.0/go.mod h1:j0hNfjhrt2SxUOw55nL0ATM/z4Yt3t2Kd1mW34z5W5s=
|
||||
github.com/d2g/dhcp4server v0.0.0-20181031114812-7d4a0a7f59a5/go.mod h1:Eo87+Kg/IX2hfWJfwxMzLyuSZyxSoAug2nGa1G2QAi8=
|
||||
|
|
@ -331,8 +333,9 @@ github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5Kwzbycv
|
|||
github.com/form3tech-oss/jwt-go v3.2.2+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k=
|
||||
github.com/frankban/quicktest v1.11.3/go.mod h1:wRf/ReqHper53s+kmmSZizM8NamnL3IM0I9ntUbOk+k=
|
||||
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
||||
github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4=
|
||||
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
|
||||
github.com/fsnotify/fsnotify v1.5.1 h1:mZcQUHVQUQWoPXXtuf9yuEXKudkV2sx1E06UadKWpgI=
|
||||
github.com/fsnotify/fsnotify v1.5.1/go.mod h1:T3375wBYaZdLLcVNkcVbzGHY7f1l/uK5T5Ai1i3InKU=
|
||||
github.com/fullsailor/pkcs7 v0.0.0-20190404230743-d7302db945fa/go.mod h1:KnogPXtdwXqoenmZCw6S+25EAm2MkxbG0deNDu4cbSA=
|
||||
github.com/garyburd/redigo v0.0.0-20150301180006-535138d7bcd7/go.mod h1:NR3MbYisc3/PwhQ00EMzDiPmrwpPxAn5GI05/YaO1SY=
|
||||
github.com/ghodss/yaml v0.0.0-20150909031657-73d445a93680/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
|
||||
|
|
@ -366,8 +369,9 @@ github.com/godbus/dbus v0.0.0-20180201030542-885f9cc04c9c/go.mod h1:/YcGZj5zSblf
|
|||
github.com/godbus/dbus v0.0.0-20190422162347-ade71ed3457e h1:BWhy2j3IXJhjCbC68FptL43tDKIq8FladmaTs3Xs7Z8=
|
||||
github.com/godbus/dbus v0.0.0-20190422162347-ade71ed3457e/go.mod h1:bBOAhwG1umN6/6ZUMtDFBMQR8jRg9O75tm9K00oMsK4=
|
||||
github.com/godbus/dbus/v5 v5.0.3/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
|
||||
github.com/godbus/dbus/v5 v5.0.4 h1:9349emZab16e7zQvpmsbtjc18ykshndd8y2PG3sgJbA=
|
||||
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
|
||||
github.com/godbus/dbus/v5 v5.0.6 h1:mkgN1ofwASrYnJ5W6U/BxG15eXXXjirgZc7CLqkcaro=
|
||||
github.com/godbus/dbus/v5 v5.0.6/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
|
||||
github.com/gogo/googleapis v1.2.0/go.mod h1:Njal3psf3qN6dwBtQfUmBZh2ybovJ0tlu3o/AC7HYjU=
|
||||
github.com/gogo/googleapis v1.4.0/go.mod h1:5YRNX2z1oM5gXdAkurHa942MDgEJyk02w4OecKY87+c=
|
||||
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
|
||||
|
|
@ -597,8 +601,8 @@ github.com/moby/sys/mountinfo v0.5.0 h1:2Ks8/r6lopsxWi9m58nlwjaeSzUX9iiL1vj5qB/9
|
|||
github.com/moby/sys/mountinfo v0.5.0/go.mod h1:3bMD3Rg+zkqx8MRYPi7Pyb0Ie97QEBmdxbhnCLlSvSU=
|
||||
github.com/moby/sys/symlink v0.1.0/go.mod h1:GGDODQmbFOjFsXvfLVn3+ZRxkch54RkSiGqsZeMYowQ=
|
||||
github.com/moby/term v0.0.0-20200312100748-672ec06f55cd/go.mod h1:DdlQx2hp0Ss5/fLikoLlEeIYiATotOjgB//nb973jeo=
|
||||
github.com/moby/term v0.0.0-20201216013528-df9cb8a40635 h1:rzf0wL0CHVc8CEsgyygG0Mn9CNCCPZqOPaz8RiiHYQk=
|
||||
github.com/moby/term v0.0.0-20201216013528-df9cb8a40635/go.mod h1:FBS0z0QWA44HXygs7VXDUOGoN/1TV3RuWkLO04am3wc=
|
||||
github.com/moby/term v0.0.0-20210619224110-3f7ff695adc6 h1:dcztxKSvZ4Id8iPpHERQBbIJfabdt4wUm5qy3wOL2Zc=
|
||||
github.com/moby/term v0.0.0-20210619224110-3f7ff695adc6/go.mod h1:E2VnQOmVuvZB6UYnnDB0qG5Nq/1tD9acaOpo6xmt0Kw=
|
||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
|
|
@ -881,8 +885,9 @@ golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8U
|
|||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20200728195943-123391ffb6de/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2 h1:It14KIkyBFYkHkwZ7k45minvA9aorojkyjGk9KJ5B/w=
|
||||
golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
|
||||
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97 h1:/UOmuWzQfxxo9UtlXMwuQU8CMgg1eZXqTRwkSQJWKOI=
|
||||
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
|
||||
|
|
@ -1053,7 +1058,6 @@ golang.org/x/sys v0.0.0-20200622214017-ed371f2e16b4/go.mod h1:h1NjWce9XRLGQEsW7w
|
|||
golang.org/x/sys v0.0.0-20200728102440-3e129f6d46b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200817155316-9781c653f443/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200831180312-196b9ba8737a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/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=
|
||||
|
|
@ -1079,13 +1083,17 @@ golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57/go.mod h1:h1NjWce9XRLGQEsW7w
|
|||
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210426230700-d19ff857e887/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210820121016-41cdb8703e55/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210910150752-751e447fb3d0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20211004093028-2c5d950f24ef/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20211025201205-69cdffdb9359 h1:2B5p2L5IfGiD7+b9BOoRMC6DgObAVZV+Fsp050NqXik=
|
||||
golang.org/x/sys v0.0.0-20211025201205-69cdffdb9359/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1 h1:v+OssWQX+hTHEmOBgwxdZxK4zHq3yOs8F9J7mk0PY8E=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210615171337-6886f2dfbf5b h1:9zKuko04nR4gjZ4+DNjHqRlAJqbJETHwiNKDqTfOjfE=
|
||||
golang.org/x/term v0.0.0-20210615171337-6886f2dfbf5b/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
|
|
|
|||
|
|
@ -0,0 +1,149 @@
|
|||
package cgroups
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
spec "github.com/opencontainers/runtime-spec/specs-go"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
type blkioHandler struct {
|
||||
}
|
||||
|
||||
func getBlkioHandler() *blkioHandler {
|
||||
return &blkioHandler{}
|
||||
}
|
||||
|
||||
// Apply set the specified constraints
|
||||
func (c *blkioHandler) Apply(ctr *CgroupControl, res *spec.LinuxResources) error {
|
||||
if res.BlockIO == nil {
|
||||
return nil
|
||||
}
|
||||
return fmt.Errorf("blkio apply function not implemented yet")
|
||||
}
|
||||
|
||||
// Create the cgroup
|
||||
func (c *blkioHandler) Create(ctr *CgroupControl) (bool, error) {
|
||||
if ctr.cgroup2 {
|
||||
return false, nil
|
||||
}
|
||||
return ctr.createCgroupDirectory(Blkio)
|
||||
}
|
||||
|
||||
// Destroy the cgroup
|
||||
func (c *blkioHandler) Destroy(ctr *CgroupControl) error {
|
||||
return rmDirRecursively(ctr.getCgroupv1Path(Blkio))
|
||||
}
|
||||
|
||||
// Stat fills a metrics structure with usage stats for the controller
|
||||
func (c *blkioHandler) Stat(ctr *CgroupControl, m *Metrics) error {
|
||||
var ioServiceBytesRecursive []BlkIOEntry
|
||||
|
||||
if ctr.cgroup2 {
|
||||
// more details on the io.stat file format:X https://facebookmicrosites.github.io/cgroup2/docs/io-controller.html
|
||||
values, err := readCgroup2MapFile(ctr, "io.stat")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for k, v := range values {
|
||||
d := strings.Split(k, ":")
|
||||
if len(d) != 2 {
|
||||
continue
|
||||
}
|
||||
minor, err := strconv.ParseUint(d[0], 10, 0)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
major, err := strconv.ParseUint(d[1], 10, 0)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, item := range v {
|
||||
d := strings.Split(item, "=")
|
||||
if len(d) != 2 {
|
||||
continue
|
||||
}
|
||||
op := d[0]
|
||||
|
||||
// Accommodate the cgroup v1 naming
|
||||
switch op {
|
||||
case "rbytes":
|
||||
op = "read"
|
||||
case "wbytes":
|
||||
op = "write"
|
||||
}
|
||||
|
||||
value, err := strconv.ParseUint(d[1], 10, 0)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
entry := BlkIOEntry{
|
||||
Op: op,
|
||||
Major: major,
|
||||
Minor: minor,
|
||||
Value: value,
|
||||
}
|
||||
ioServiceBytesRecursive = append(ioServiceBytesRecursive, entry)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
BlkioRoot := ctr.getCgroupv1Path(Blkio)
|
||||
|
||||
p := filepath.Join(BlkioRoot, "blkio.throttle.io_service_bytes_recursive")
|
||||
f, err := os.Open(p)
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
return nil
|
||||
}
|
||||
return errors.Wrapf(err, "open %s", p)
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
scanner := bufio.NewScanner(f)
|
||||
for scanner.Scan() {
|
||||
line := scanner.Text()
|
||||
parts := strings.Fields(line)
|
||||
if len(parts) < 3 {
|
||||
continue
|
||||
}
|
||||
d := strings.Split(parts[0], ":")
|
||||
if len(d) != 2 {
|
||||
continue
|
||||
}
|
||||
minor, err := strconv.ParseUint(d[0], 10, 0)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
major, err := strconv.ParseUint(d[1], 10, 0)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
op := parts[1]
|
||||
|
||||
value, err := strconv.ParseUint(parts[2], 10, 0)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
entry := BlkIOEntry{
|
||||
Op: op,
|
||||
Major: major,
|
||||
Minor: minor,
|
||||
Value: value,
|
||||
}
|
||||
ioServiceBytesRecursive = append(ioServiceBytesRecursive, entry)
|
||||
}
|
||||
if err := scanner.Err(); err != nil {
|
||||
return errors.Wrapf(err, "parse %s", p)
|
||||
}
|
||||
}
|
||||
m.Blkio = BlkioMetrics{IoServiceBytesRecursive: ioServiceBytesRecursive}
|
||||
return nil
|
||||
}
|
||||
|
|
@ -0,0 +1,674 @@
|
|||
package cgroups
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"context"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"math"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/containers/storage/pkg/unshare"
|
||||
systemdDbus "github.com/coreos/go-systemd/v22/dbus"
|
||||
"github.com/godbus/dbus/v5"
|
||||
spec "github.com/opencontainers/runtime-spec/specs-go"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
var (
|
||||
// ErrCgroupDeleted means the cgroup was deleted
|
||||
ErrCgroupDeleted = errors.New("cgroup deleted")
|
||||
// ErrCgroupV1Rootless means the cgroup v1 were attempted to be used in rootless environment
|
||||
ErrCgroupV1Rootless = errors.New("no support for CGroups V1 in rootless environments")
|
||||
ErrStatCgroup = errors.New("no cgroup available for gathering user statistics")
|
||||
)
|
||||
|
||||
// CgroupControl controls a cgroup hierarchy
|
||||
type CgroupControl struct {
|
||||
cgroup2 bool
|
||||
path string
|
||||
systemd bool
|
||||
// List of additional cgroup subsystems joined that
|
||||
// do not have a custom handler.
|
||||
additionalControllers []controller
|
||||
}
|
||||
|
||||
// CPUUsage keeps stats for the CPU usage (unit: nanoseconds)
|
||||
type CPUUsage struct {
|
||||
Kernel uint64
|
||||
Total uint64
|
||||
PerCPU []uint64
|
||||
}
|
||||
|
||||
// MemoryUsage keeps stats for the memory usage
|
||||
type MemoryUsage struct {
|
||||
Usage uint64
|
||||
Limit uint64
|
||||
}
|
||||
|
||||
// CPUMetrics keeps stats for the CPU usage
|
||||
type CPUMetrics struct {
|
||||
Usage CPUUsage
|
||||
}
|
||||
|
||||
// BlkIOEntry describes an entry in the blkio stats
|
||||
type BlkIOEntry struct {
|
||||
Op string
|
||||
Major uint64
|
||||
Minor uint64
|
||||
Value uint64
|
||||
}
|
||||
|
||||
// BlkioMetrics keeps usage stats for the blkio cgroup controller
|
||||
type BlkioMetrics struct {
|
||||
IoServiceBytesRecursive []BlkIOEntry
|
||||
}
|
||||
|
||||
// MemoryMetrics keeps usage stats for the memory cgroup controller
|
||||
type MemoryMetrics struct {
|
||||
Usage MemoryUsage
|
||||
}
|
||||
|
||||
// PidsMetrics keeps usage stats for the pids cgroup controller
|
||||
type PidsMetrics struct {
|
||||
Current uint64
|
||||
}
|
||||
|
||||
// Metrics keeps usage stats for the cgroup controllers
|
||||
type Metrics struct {
|
||||
CPU CPUMetrics
|
||||
Blkio BlkioMetrics
|
||||
Memory MemoryMetrics
|
||||
Pids PidsMetrics
|
||||
}
|
||||
|
||||
type controller struct {
|
||||
name string
|
||||
symlink bool
|
||||
}
|
||||
|
||||
type controllerHandler interface {
|
||||
Create(*CgroupControl) (bool, error)
|
||||
Apply(*CgroupControl, *spec.LinuxResources) error
|
||||
Destroy(*CgroupControl) error
|
||||
Stat(*CgroupControl, *Metrics) error
|
||||
}
|
||||
|
||||
const (
|
||||
cgroupRoot = "/sys/fs/cgroup"
|
||||
// CPU is the cpu controller
|
||||
CPU = "cpu"
|
||||
// CPUAcct is the cpuacct controller
|
||||
CPUAcct = "cpuacct"
|
||||
// CPUset is the cpuset controller
|
||||
CPUset = "cpuset"
|
||||
// Memory is the memory controller
|
||||
Memory = "memory"
|
||||
// Pids is the pids controller
|
||||
Pids = "pids"
|
||||
// Blkio is the blkio controller
|
||||
Blkio = "blkio"
|
||||
)
|
||||
|
||||
var handlers map[string]controllerHandler
|
||||
|
||||
func init() {
|
||||
handlers = make(map[string]controllerHandler)
|
||||
handlers[CPU] = getCPUHandler()
|
||||
handlers[CPUset] = getCpusetHandler()
|
||||
handlers[Memory] = getMemoryHandler()
|
||||
handlers[Pids] = getPidsHandler()
|
||||
handlers[Blkio] = getBlkioHandler()
|
||||
}
|
||||
|
||||
// getAvailableControllers get the available controllers
|
||||
func getAvailableControllers(exclude map[string]controllerHandler, cgroup2 bool) ([]controller, error) {
|
||||
if cgroup2 {
|
||||
controllers := []controller{}
|
||||
controllersFile := cgroupRoot + "/cgroup.controllers"
|
||||
// rootless cgroupv2: check available controllers for current user, systemd or servicescope will inherit
|
||||
if unshare.IsRootless() {
|
||||
userSlice, err := getCgroupPathForCurrentProcess()
|
||||
if err != nil {
|
||||
return controllers, err
|
||||
}
|
||||
// userSlice already contains '/' so not adding here
|
||||
basePath := cgroupRoot + userSlice
|
||||
controllersFile = fmt.Sprintf("%s/cgroup.controllers", basePath)
|
||||
}
|
||||
controllersFileBytes, err := ioutil.ReadFile(controllersFile)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "failed while reading controllers for cgroup v2 from %q", controllersFile)
|
||||
}
|
||||
for _, controllerName := range strings.Fields(string(controllersFileBytes)) {
|
||||
c := controller{
|
||||
name: controllerName,
|
||||
symlink: false,
|
||||
}
|
||||
controllers = append(controllers, c)
|
||||
}
|
||||
return controllers, nil
|
||||
}
|
||||
|
||||
subsystems, _ := cgroupV1GetAllSubsystems()
|
||||
controllers := []controller{}
|
||||
// cgroupv1 and rootless: No subsystem is available: delegation is unsafe.
|
||||
if unshare.IsRootless() {
|
||||
return controllers, nil
|
||||
}
|
||||
|
||||
for _, name := range subsystems {
|
||||
if _, found := exclude[name]; found {
|
||||
continue
|
||||
}
|
||||
fileInfo, err := os.Stat(cgroupRoot + "/" + name)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
c := controller{
|
||||
name: name,
|
||||
symlink: !fileInfo.IsDir(),
|
||||
}
|
||||
controllers = append(controllers, c)
|
||||
}
|
||||
|
||||
return controllers, nil
|
||||
}
|
||||
|
||||
// GetAvailableControllers get string:bool map of all the available controllers
|
||||
func GetAvailableControllers(exclude map[string]controllerHandler, cgroup2 bool) ([]string, error) {
|
||||
availableControllers, err := getAvailableControllers(exclude, cgroup2)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
controllerList := []string{}
|
||||
for _, controller := range availableControllers {
|
||||
controllerList = append(controllerList, controller.name)
|
||||
}
|
||||
|
||||
return controllerList, nil
|
||||
}
|
||||
|
||||
func cgroupV1GetAllSubsystems() ([]string, error) {
|
||||
f, err := os.Open("/proc/cgroups")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
subsystems := []string{}
|
||||
|
||||
s := bufio.NewScanner(f)
|
||||
for s.Scan() {
|
||||
text := s.Text()
|
||||
if text[0] != '#' {
|
||||
parts := strings.Fields(text)
|
||||
if len(parts) >= 4 && parts[3] != "0" {
|
||||
subsystems = append(subsystems, parts[0])
|
||||
}
|
||||
}
|
||||
}
|
||||
if err := s.Err(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return subsystems, nil
|
||||
}
|
||||
|
||||
func getCgroupPathForCurrentProcess() (string, error) {
|
||||
path := fmt.Sprintf("/proc/%d/cgroup", os.Getpid())
|
||||
f, err := os.Open(path)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
cgroupPath := ""
|
||||
s := bufio.NewScanner(f)
|
||||
for s.Scan() {
|
||||
text := s.Text()
|
||||
procEntries := strings.SplitN(text, "::", 2)
|
||||
// set process cgroupPath only if entry is valid
|
||||
if len(procEntries) > 1 {
|
||||
cgroupPath = procEntries[1]
|
||||
}
|
||||
}
|
||||
if err := s.Err(); err != nil {
|
||||
return cgroupPath, err
|
||||
}
|
||||
return cgroupPath, nil
|
||||
}
|
||||
|
||||
// getCgroupv1Path is a helper function to get the cgroup v1 path
|
||||
func (c *CgroupControl) getCgroupv1Path(name string) string {
|
||||
return filepath.Join(cgroupRoot, name, c.path)
|
||||
}
|
||||
|
||||
// createCgroupv2Path creates the cgroupv2 path and enables all the available controllers
|
||||
func createCgroupv2Path(path string) (deferredError error) {
|
||||
if !strings.HasPrefix(path, cgroupRoot+"/") {
|
||||
return fmt.Errorf("invalid cgroup path %s", path)
|
||||
}
|
||||
content, err := ioutil.ReadFile(cgroupRoot + "/cgroup.controllers")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
ctrs := bytes.Fields(content)
|
||||
res := append([]byte("+"), bytes.Join(ctrs, []byte(" +"))...)
|
||||
|
||||
current := "/sys/fs"
|
||||
elements := strings.Split(path, "/")
|
||||
for i, e := range elements[3:] {
|
||||
current = filepath.Join(current, e)
|
||||
if i > 0 {
|
||||
if err := os.Mkdir(current, 0755); err != nil {
|
||||
if !os.IsExist(err) {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
// If the directory was created, be sure it is not left around on errors.
|
||||
defer func() {
|
||||
if deferredError != nil {
|
||||
os.Remove(current)
|
||||
}
|
||||
}()
|
||||
}
|
||||
}
|
||||
// We enable the controllers for all the path components except the last one. It is not allowed to add
|
||||
// PIDs if there are already enabled controllers.
|
||||
if i < len(elements[3:])-1 {
|
||||
if err := ioutil.WriteFile(filepath.Join(current, "cgroup.subtree_control"), res, 0755); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// initialize initializes the specified hierarchy
|
||||
func (c *CgroupControl) initialize() (err error) {
|
||||
createdSoFar := map[string]controllerHandler{}
|
||||
defer func() {
|
||||
if err != nil {
|
||||
for name, ctr := range createdSoFar {
|
||||
if err := ctr.Destroy(c); err != nil {
|
||||
logrus.Warningf("error cleaning up controller %s for %s", name, c.path)
|
||||
}
|
||||
}
|
||||
}
|
||||
}()
|
||||
if c.cgroup2 {
|
||||
if err := createCgroupv2Path(filepath.Join(cgroupRoot, c.path)); err != nil {
|
||||
return errors.Wrapf(err, "error creating cgroup path %s", c.path)
|
||||
}
|
||||
}
|
||||
for name, handler := range handlers {
|
||||
created, err := handler.Create(c)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if created {
|
||||
createdSoFar[name] = handler
|
||||
}
|
||||
}
|
||||
|
||||
if !c.cgroup2 {
|
||||
// We won't need to do this for cgroup v2
|
||||
for _, ctr := range c.additionalControllers {
|
||||
if ctr.symlink {
|
||||
continue
|
||||
}
|
||||
path := c.getCgroupv1Path(ctr.name)
|
||||
if err := os.MkdirAll(path, 0755); err != nil {
|
||||
return errors.Wrapf(err, "error creating cgroup path for %s", ctr.name)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *CgroupControl) createCgroupDirectory(controller string) (bool, error) {
|
||||
cPath := c.getCgroupv1Path(controller)
|
||||
_, err := os.Stat(cPath)
|
||||
if err == nil {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
if !os.IsNotExist(err) {
|
||||
return false, err
|
||||
}
|
||||
|
||||
if err := os.MkdirAll(cPath, 0755); err != nil {
|
||||
return false, errors.Wrapf(err, "error creating cgroup for %s", controller)
|
||||
}
|
||||
return true, nil
|
||||
}
|
||||
|
||||
func readFileAsUint64(path string) (uint64, error) {
|
||||
data, err := ioutil.ReadFile(path)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
v := cleanString(string(data))
|
||||
if v == "max" {
|
||||
return math.MaxUint64, nil
|
||||
}
|
||||
ret, err := strconv.ParseUint(v, 10, 64)
|
||||
if err != nil {
|
||||
return ret, errors.Wrapf(err, "parse %s from %s", v, path)
|
||||
}
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
// New creates a new cgroup control
|
||||
func New(path string, resources *spec.LinuxResources) (*CgroupControl, error) {
|
||||
cgroup2, err := IsCgroup2UnifiedMode()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
control := &CgroupControl{
|
||||
cgroup2: cgroup2,
|
||||
path: path,
|
||||
}
|
||||
|
||||
if !cgroup2 {
|
||||
controllers, err := getAvailableControllers(handlers, false)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
control.additionalControllers = controllers
|
||||
}
|
||||
|
||||
if err := control.initialize(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return control, nil
|
||||
}
|
||||
|
||||
// NewSystemd creates a new cgroup control
|
||||
func NewSystemd(path string) (*CgroupControl, error) {
|
||||
cgroup2, err := IsCgroup2UnifiedMode()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
control := &CgroupControl{
|
||||
cgroup2: cgroup2,
|
||||
path: path,
|
||||
systemd: true,
|
||||
}
|
||||
return control, nil
|
||||
}
|
||||
|
||||
// Load loads an existing cgroup control
|
||||
func Load(path string) (*CgroupControl, error) {
|
||||
cgroup2, err := IsCgroup2UnifiedMode()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
control := &CgroupControl{
|
||||
cgroup2: cgroup2,
|
||||
path: path,
|
||||
systemd: false,
|
||||
}
|
||||
if !cgroup2 {
|
||||
controllers, err := getAvailableControllers(handlers, false)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
control.additionalControllers = controllers
|
||||
}
|
||||
if !cgroup2 {
|
||||
oneExists := false
|
||||
// check that the cgroup exists at least under one controller
|
||||
for name := range handlers {
|
||||
p := control.getCgroupv1Path(name)
|
||||
if _, err := os.Stat(p); err == nil {
|
||||
oneExists = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// if there is no controller at all, raise an error
|
||||
if !oneExists {
|
||||
if unshare.IsRootless() {
|
||||
return nil, ErrCgroupV1Rootless
|
||||
}
|
||||
// compatible with the error code
|
||||
// used by containerd/cgroups
|
||||
return nil, ErrCgroupDeleted
|
||||
}
|
||||
}
|
||||
return control, nil
|
||||
}
|
||||
|
||||
// CreateSystemdUnit creates the systemd cgroup
|
||||
func (c *CgroupControl) CreateSystemdUnit(path string) error {
|
||||
if !c.systemd {
|
||||
return fmt.Errorf("the cgroup controller is not using systemd")
|
||||
}
|
||||
|
||||
conn, err := systemdDbus.NewWithContext(context.TODO())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer conn.Close()
|
||||
|
||||
return systemdCreate(path, conn)
|
||||
}
|
||||
|
||||
// GetUserConnection returns an user connection to D-BUS
|
||||
func GetUserConnection(uid int) (*systemdDbus.Conn, error) {
|
||||
return systemdDbus.NewConnection(func() (*dbus.Conn, error) {
|
||||
return dbusAuthConnection(uid, dbus.SessionBusPrivate)
|
||||
})
|
||||
}
|
||||
|
||||
// CreateSystemdUserUnit creates the systemd cgroup for the specified user
|
||||
func (c *CgroupControl) CreateSystemdUserUnit(path string, uid int) error {
|
||||
if !c.systemd {
|
||||
return fmt.Errorf("the cgroup controller is not using systemd")
|
||||
}
|
||||
|
||||
conn, err := GetUserConnection(uid)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer conn.Close()
|
||||
|
||||
return systemdCreate(path, conn)
|
||||
}
|
||||
|
||||
func dbusAuthConnection(uid int, createBus func(opts ...dbus.ConnOption) (*dbus.Conn, error)) (*dbus.Conn, error) {
|
||||
conn, err := createBus()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
methods := []dbus.Auth{dbus.AuthExternal(strconv.Itoa(uid))}
|
||||
|
||||
err = conn.Auth(methods)
|
||||
if err != nil {
|
||||
conn.Close()
|
||||
return nil, err
|
||||
}
|
||||
if err := conn.Hello(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return conn, nil
|
||||
}
|
||||
|
||||
// Delete cleans a cgroup
|
||||
func (c *CgroupControl) Delete() error {
|
||||
return c.DeleteByPath(c.path)
|
||||
}
|
||||
|
||||
// rmDirRecursively delete recursively a cgroup directory.
|
||||
// It differs from os.RemoveAll as it doesn't attempt to unlink files.
|
||||
// On cgroupfs we are allowed only to rmdir empty directories.
|
||||
func rmDirRecursively(path string) error {
|
||||
if err := os.Remove(path); err == nil || os.IsNotExist(err) {
|
||||
return nil
|
||||
}
|
||||
entries, err := ioutil.ReadDir(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, i := range entries {
|
||||
if i.IsDir() {
|
||||
if err := rmDirRecursively(filepath.Join(path, i.Name())); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
if err := os.Remove(path); err != nil {
|
||||
if !os.IsNotExist(err) {
|
||||
return errors.Wrapf(err, "remove %s", path)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// DeleteByPathConn deletes the specified cgroup path using the specified
|
||||
// dbus connection if needed.
|
||||
func (c *CgroupControl) DeleteByPathConn(path string, conn *systemdDbus.Conn) error {
|
||||
if c.systemd {
|
||||
return systemdDestroyConn(path, conn)
|
||||
}
|
||||
if c.cgroup2 {
|
||||
return rmDirRecursively(filepath.Join(cgroupRoot, c.path))
|
||||
}
|
||||
var lastError error
|
||||
for _, h := range handlers {
|
||||
if err := h.Destroy(c); err != nil {
|
||||
lastError = err
|
||||
}
|
||||
}
|
||||
|
||||
for _, ctr := range c.additionalControllers {
|
||||
if ctr.symlink {
|
||||
continue
|
||||
}
|
||||
p := c.getCgroupv1Path(ctr.name)
|
||||
if err := rmDirRecursively(p); err != nil {
|
||||
lastError = errors.Wrapf(err, "remove %s", p)
|
||||
}
|
||||
}
|
||||
return lastError
|
||||
}
|
||||
|
||||
// DeleteByPath deletes the specified cgroup path
|
||||
func (c *CgroupControl) DeleteByPath(path string) error {
|
||||
if c.systemd {
|
||||
conn, err := systemdDbus.NewWithContext(context.TODO())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer conn.Close()
|
||||
return c.DeleteByPathConn(path, conn)
|
||||
}
|
||||
return c.DeleteByPathConn(path, nil)
|
||||
}
|
||||
|
||||
// Update updates the cgroups
|
||||
func (c *CgroupControl) Update(resources *spec.LinuxResources) error {
|
||||
for _, h := range handlers {
|
||||
if err := h.Apply(c, resources); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// AddPid moves the specified pid to the cgroup
|
||||
func (c *CgroupControl) AddPid(pid int) error {
|
||||
pidString := []byte(fmt.Sprintf("%d\n", pid))
|
||||
|
||||
if c.cgroup2 {
|
||||
p := filepath.Join(cgroupRoot, c.path, "cgroup.procs")
|
||||
if err := ioutil.WriteFile(p, pidString, 0644); err != nil {
|
||||
return errors.Wrapf(err, "write %s", p)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
names := make([]string, 0, len(handlers))
|
||||
for n := range handlers {
|
||||
names = append(names, n)
|
||||
}
|
||||
|
||||
for _, c := range c.additionalControllers {
|
||||
if !c.symlink {
|
||||
names = append(names, c.name)
|
||||
}
|
||||
}
|
||||
|
||||
for _, n := range names {
|
||||
// If we aren't using cgroup2, we won't write correctly to unified hierarchy
|
||||
if !c.cgroup2 && n == "unified" {
|
||||
continue
|
||||
}
|
||||
p := filepath.Join(c.getCgroupv1Path(n), "tasks")
|
||||
if err := ioutil.WriteFile(p, pidString, 0644); err != nil {
|
||||
return errors.Wrapf(err, "write %s", p)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Stat returns usage statistics for the cgroup
|
||||
func (c *CgroupControl) Stat() (*Metrics, error) {
|
||||
m := Metrics{}
|
||||
found := false
|
||||
for _, h := range handlers {
|
||||
if err := h.Stat(c, &m); err != nil {
|
||||
if !os.IsNotExist(errors.Cause(err)) {
|
||||
return nil, err
|
||||
}
|
||||
logrus.Warningf("Failed to retrieve cgroup stats: %v", err)
|
||||
continue
|
||||
}
|
||||
found = true
|
||||
}
|
||||
if !found {
|
||||
return nil, ErrStatCgroup
|
||||
}
|
||||
return &m, nil
|
||||
}
|
||||
|
||||
func readCgroup2MapPath(path string) (map[string][]string, error) {
|
||||
ret := map[string][]string{}
|
||||
f, err := os.Open(path)
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
return ret, nil
|
||||
}
|
||||
return nil, errors.Wrapf(err, "open file %s", path)
|
||||
}
|
||||
defer f.Close()
|
||||
scanner := bufio.NewScanner(f)
|
||||
for scanner.Scan() {
|
||||
line := scanner.Text()
|
||||
parts := strings.Fields(line)
|
||||
if len(parts) < 2 {
|
||||
continue
|
||||
}
|
||||
ret[parts[0]] = parts[1:]
|
||||
}
|
||||
if err := scanner.Err(); err != nil {
|
||||
return nil, errors.Wrapf(err, "parsing file %s", path)
|
||||
}
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
func readCgroup2MapFile(ctr *CgroupControl, name string) (map[string][]string, error) {
|
||||
p := filepath.Join(cgroupRoot, ctr.path, name)
|
||||
|
||||
return readCgroup2MapPath(p)
|
||||
}
|
||||
|
|
@ -0,0 +1,90 @@
|
|||
// +build linux
|
||||
|
||||
package cgroups
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"sync"
|
||||
"syscall"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
var (
|
||||
isUnifiedOnce sync.Once
|
||||
isUnified bool
|
||||
isUnifiedErr error
|
||||
)
|
||||
|
||||
// IsCgroup2UnifiedMode returns whether we are running in cgroup 2 cgroup2 mode.
|
||||
func IsCgroup2UnifiedMode() (bool, error) {
|
||||
isUnifiedOnce.Do(func() {
|
||||
var st syscall.Statfs_t
|
||||
if err := syscall.Statfs("/sys/fs/cgroup", &st); err != nil {
|
||||
isUnified, isUnifiedErr = false, err
|
||||
} else {
|
||||
isUnified, isUnifiedErr = st.Type == unix.CGROUP2_SUPER_MAGIC, nil
|
||||
}
|
||||
})
|
||||
return isUnified, isUnifiedErr
|
||||
}
|
||||
|
||||
// UserOwnsCurrentSystemdCgroup checks whether the current EUID owns the
|
||||
// current cgroup.
|
||||
func UserOwnsCurrentSystemdCgroup() (bool, error) {
|
||||
uid := os.Geteuid()
|
||||
|
||||
cgroup2, err := IsCgroup2UnifiedMode()
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
f, err := os.Open("/proc/self/cgroup")
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
scanner := bufio.NewScanner(f)
|
||||
for scanner.Scan() {
|
||||
line := scanner.Text()
|
||||
parts := strings.SplitN(line, ":", 3)
|
||||
|
||||
if len(parts) < 3 {
|
||||
continue
|
||||
}
|
||||
|
||||
var cgroupPath string
|
||||
|
||||
if cgroup2 {
|
||||
cgroupPath = filepath.Join(cgroupRoot, parts[2])
|
||||
} else {
|
||||
if parts[1] != "name=systemd" {
|
||||
continue
|
||||
}
|
||||
cgroupPath = filepath.Join(cgroupRoot, "systemd", parts[2])
|
||||
}
|
||||
|
||||
st, err := os.Stat(cgroupPath)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
s := st.Sys()
|
||||
if s == nil {
|
||||
return false, fmt.Errorf("error stat cgroup path %s", cgroupPath)
|
||||
}
|
||||
|
||||
if int(s.(*syscall.Stat_t).Uid) != uid {
|
||||
return false, nil
|
||||
}
|
||||
}
|
||||
if err := scanner.Err(); err != nil {
|
||||
return false, errors.Wrapf(err, "parsing file /proc/self/cgroup")
|
||||
}
|
||||
return true, nil
|
||||
}
|
||||
|
|
@ -0,0 +1,32 @@
|
|||
package cgroups
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/containers/storage/pkg/unshare"
|
||||
spec "github.com/opencontainers/runtime-spec/specs-go"
|
||||
)
|
||||
|
||||
func TestCreated(t *testing.T) {
|
||||
// tests only works in rootless mode
|
||||
if unshare.IsRootless() {
|
||||
return
|
||||
}
|
||||
|
||||
var resources spec.LinuxResources
|
||||
cgr, err := New("machine.slice", &resources)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
if err := cgr.Delete(); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
cgr, err = NewSystemd("machine.slice")
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
if err := cgr.Delete(); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
// +build !linux
|
||||
|
||||
package cgroups
|
||||
|
||||
// IsCgroup2UnifiedMode returns whether we are running in cgroup 2 cgroup2 mode.
|
||||
func IsCgroup2UnifiedMode() (bool, error) {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
// UserOwnsCurrentSystemdCgroup checks whether the current EUID owns the
|
||||
// current cgroup.
|
||||
func UserOwnsCurrentSystemdCgroup() (bool, error) {
|
||||
return false, nil
|
||||
}
|
||||
|
|
@ -0,0 +1,160 @@
|
|||
package cgroups
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
spec "github.com/opencontainers/runtime-spec/specs-go"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
type cpuHandler struct {
|
||||
}
|
||||
|
||||
func getCPUHandler() *cpuHandler {
|
||||
return &cpuHandler{}
|
||||
}
|
||||
|
||||
func cleanString(s string) string {
|
||||
return strings.Trim(s, "\n")
|
||||
}
|
||||
|
||||
func readAcct(ctr *CgroupControl, name string) (uint64, error) {
|
||||
p := filepath.Join(ctr.getCgroupv1Path(CPUAcct), name)
|
||||
return readFileAsUint64(p)
|
||||
}
|
||||
|
||||
func readAcctList(ctr *CgroupControl, name string) ([]uint64, error) {
|
||||
p := filepath.Join(ctr.getCgroupv1Path(CPUAcct), name)
|
||||
data, err := ioutil.ReadFile(p)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "reading %s", p)
|
||||
}
|
||||
r := []uint64{}
|
||||
for _, s := range strings.Split(string(data), " ") {
|
||||
s = cleanString(s)
|
||||
if s == "" {
|
||||
break
|
||||
}
|
||||
v, err := strconv.ParseUint(s, 10, 64)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "parsing %s", s)
|
||||
}
|
||||
r = append(r, v)
|
||||
}
|
||||
return r, nil
|
||||
}
|
||||
|
||||
// Apply set the specified constraints
|
||||
func (c *cpuHandler) Apply(ctr *CgroupControl, res *spec.LinuxResources) error {
|
||||
if res.CPU == nil {
|
||||
return nil
|
||||
}
|
||||
return fmt.Errorf("cpu apply not implemented yet")
|
||||
}
|
||||
|
||||
// Create the cgroup
|
||||
func (c *cpuHandler) Create(ctr *CgroupControl) (bool, error) {
|
||||
if ctr.cgroup2 {
|
||||
return false, nil
|
||||
}
|
||||
return ctr.createCgroupDirectory(CPU)
|
||||
}
|
||||
|
||||
// Destroy the cgroup
|
||||
func (c *cpuHandler) Destroy(ctr *CgroupControl) error {
|
||||
return rmDirRecursively(ctr.getCgroupv1Path(CPU))
|
||||
}
|
||||
|
||||
// Stat fills a metrics structure with usage stats for the controller
|
||||
func (c *cpuHandler) Stat(ctr *CgroupControl, m *Metrics) error {
|
||||
var err error
|
||||
usage := CPUUsage{}
|
||||
if ctr.cgroup2 {
|
||||
values, err := readCgroup2MapFile(ctr, "cpu.stat")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if val, found := values["usage_usec"]; found {
|
||||
usage.Total, err = strconv.ParseUint(cleanString(val[0]), 10, 64)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
usage.Kernel *= 1000
|
||||
}
|
||||
if val, found := values["system_usec"]; found {
|
||||
usage.Kernel, err = strconv.ParseUint(cleanString(val[0]), 10, 64)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
usage.Total *= 1000
|
||||
}
|
||||
// FIXME: How to read usage.PerCPU?
|
||||
} else {
|
||||
usage.Total, err = readAcct(ctr, "cpuacct.usage")
|
||||
if err != nil {
|
||||
if !os.IsNotExist(errors.Cause(err)) {
|
||||
return err
|
||||
}
|
||||
usage.Total = 0
|
||||
}
|
||||
usage.Kernel, err = readAcct(ctr, "cpuacct.usage_sys")
|
||||
if err != nil {
|
||||
if !os.IsNotExist(errors.Cause(err)) {
|
||||
return err
|
||||
}
|
||||
usage.Kernel = 0
|
||||
}
|
||||
usage.PerCPU, err = readAcctList(ctr, "cpuacct.usage_percpu")
|
||||
if err != nil {
|
||||
if !os.IsNotExist(errors.Cause(err)) {
|
||||
return err
|
||||
}
|
||||
usage.PerCPU = nil
|
||||
}
|
||||
}
|
||||
m.CPU = CPUMetrics{Usage: usage}
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetSystemCPUUsage returns the system usage for all the cgroups
|
||||
func GetSystemCPUUsage() (uint64, error) {
|
||||
cgroupv2, err := IsCgroup2UnifiedMode()
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if !cgroupv2 {
|
||||
p := filepath.Join(cgroupRoot, CPUAcct, "cpuacct.usage")
|
||||
return readFileAsUint64(p)
|
||||
}
|
||||
|
||||
files, err := ioutil.ReadDir(cgroupRoot)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
var total uint64
|
||||
for _, file := range files {
|
||||
if !file.IsDir() {
|
||||
continue
|
||||
}
|
||||
p := filepath.Join(cgroupRoot, file.Name(), "cpu.stat")
|
||||
|
||||
values, err := readCgroup2MapPath(p)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
if val, found := values["usage_usec"]; found {
|
||||
v, err := strconv.ParseUint(cleanString(val[0]), 10, 64)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
total += v * 1000
|
||||
}
|
||||
}
|
||||
return total, nil
|
||||
}
|
||||
|
|
@ -0,0 +1,85 @@
|
|||
package cgroups
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
spec "github.com/opencontainers/runtime-spec/specs-go"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
type cpusetHandler struct {
|
||||
}
|
||||
|
||||
func cpusetCopyFileFromParent(dir, file string, cgroupv2 bool) ([]byte, error) {
|
||||
if dir == cgroupRoot {
|
||||
return nil, fmt.Errorf("could not find parent to initialize cpuset %s", file)
|
||||
}
|
||||
path := filepath.Join(dir, file)
|
||||
parentPath := path
|
||||
if cgroupv2 {
|
||||
parentPath = fmt.Sprintf("%s.effective", parentPath)
|
||||
}
|
||||
data, err := ioutil.ReadFile(parentPath)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "open %s", path)
|
||||
}
|
||||
if strings.Trim(string(data), "\n") != "" {
|
||||
return data, nil
|
||||
}
|
||||
data, err = cpusetCopyFileFromParent(filepath.Dir(dir), file, cgroupv2)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := ioutil.WriteFile(path, data, 0644); err != nil {
|
||||
return nil, errors.Wrapf(err, "write %s", path)
|
||||
}
|
||||
return data, nil
|
||||
}
|
||||
|
||||
func cpusetCopyFromParent(path string, cgroupv2 bool) error {
|
||||
for _, file := range []string{"cpuset.cpus", "cpuset.mems"} {
|
||||
if _, err := cpusetCopyFileFromParent(path, file, cgroupv2); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func getCpusetHandler() *cpusetHandler {
|
||||
return &cpusetHandler{}
|
||||
}
|
||||
|
||||
// Apply set the specified constraints
|
||||
func (c *cpusetHandler) Apply(ctr *CgroupControl, res *spec.LinuxResources) error {
|
||||
if res.CPU == nil {
|
||||
return nil
|
||||
}
|
||||
return fmt.Errorf("cpuset apply not implemented yet")
|
||||
}
|
||||
|
||||
// Create the cgroup
|
||||
func (c *cpusetHandler) Create(ctr *CgroupControl) (bool, error) {
|
||||
if ctr.cgroup2 {
|
||||
path := filepath.Join(cgroupRoot, ctr.path)
|
||||
return true, cpusetCopyFromParent(path, true)
|
||||
}
|
||||
|
||||
created, err := ctr.createCgroupDirectory(CPUset)
|
||||
if !created || err != nil {
|
||||
return created, err
|
||||
}
|
||||
return true, cpusetCopyFromParent(ctr.getCgroupv1Path(CPUset), false)
|
||||
}
|
||||
|
||||
// Destroy the cgroup
|
||||
func (c *cpusetHandler) Destroy(ctr *CgroupControl) error {
|
||||
return rmDirRecursively(ctr.getCgroupv1Path(CPUset))
|
||||
}
|
||||
|
||||
// Stat fills a metrics structure with usage stats for the controller
|
||||
func (c *cpusetHandler) Stat(ctr *CgroupControl, m *Metrics) error {
|
||||
return nil
|
||||
}
|
||||
|
|
@ -0,0 +1,66 @@
|
|||
package cgroups
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"path/filepath"
|
||||
|
||||
spec "github.com/opencontainers/runtime-spec/specs-go"
|
||||
)
|
||||
|
||||
type memHandler struct {
|
||||
}
|
||||
|
||||
func getMemoryHandler() *memHandler {
|
||||
return &memHandler{}
|
||||
}
|
||||
|
||||
// Apply set the specified constraints
|
||||
func (c *memHandler) Apply(ctr *CgroupControl, res *spec.LinuxResources) error {
|
||||
if res.Memory == nil {
|
||||
return nil
|
||||
}
|
||||
return fmt.Errorf("memory apply not implemented yet")
|
||||
}
|
||||
|
||||
// Create the cgroup
|
||||
func (c *memHandler) Create(ctr *CgroupControl) (bool, error) {
|
||||
if ctr.cgroup2 {
|
||||
return false, nil
|
||||
}
|
||||
return ctr.createCgroupDirectory(Memory)
|
||||
}
|
||||
|
||||
// Destroy the cgroup
|
||||
func (c *memHandler) Destroy(ctr *CgroupControl) error {
|
||||
return rmDirRecursively(ctr.getCgroupv1Path(Memory))
|
||||
}
|
||||
|
||||
// Stat fills a metrics structure with usage stats for the controller
|
||||
func (c *memHandler) Stat(ctr *CgroupControl, m *Metrics) error {
|
||||
var err error
|
||||
usage := MemoryUsage{}
|
||||
|
||||
var memoryRoot string
|
||||
filenames := map[string]string{}
|
||||
|
||||
if ctr.cgroup2 {
|
||||
memoryRoot = filepath.Join(cgroupRoot, ctr.path)
|
||||
filenames["usage"] = "memory.current"
|
||||
filenames["limit"] = "memory.max"
|
||||
} else {
|
||||
memoryRoot = ctr.getCgroupv1Path(Memory)
|
||||
filenames["usage"] = "memory.usage_in_bytes"
|
||||
filenames["limit"] = "memory.limit_in_bytes"
|
||||
}
|
||||
usage.Usage, err = readFileAsUint64(filepath.Join(memoryRoot, filenames["usage"]))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
usage.Limit, err = readFileAsUint64(filepath.Join(memoryRoot, filenames["limit"]))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
m.Memory = MemoryMetrics{Usage: usage}
|
||||
return nil
|
||||
}
|
||||
|
|
@ -0,0 +1,66 @@
|
|||
package cgroups
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"path/filepath"
|
||||
|
||||
spec "github.com/opencontainers/runtime-spec/specs-go"
|
||||
)
|
||||
|
||||
type pidHandler struct {
|
||||
}
|
||||
|
||||
func getPidsHandler() *pidHandler {
|
||||
return &pidHandler{}
|
||||
}
|
||||
|
||||
// Apply set the specified constraints
|
||||
func (c *pidHandler) Apply(ctr *CgroupControl, res *spec.LinuxResources) error {
|
||||
if res.Pids == nil {
|
||||
return nil
|
||||
}
|
||||
var PIDRoot string
|
||||
|
||||
if ctr.cgroup2 {
|
||||
PIDRoot = filepath.Join(cgroupRoot, ctr.path)
|
||||
} else {
|
||||
PIDRoot = ctr.getCgroupv1Path(Pids)
|
||||
}
|
||||
|
||||
p := filepath.Join(PIDRoot, "pids.max")
|
||||
return ioutil.WriteFile(p, []byte(fmt.Sprintf("%d\n", res.Pids.Limit)), 0644)
|
||||
}
|
||||
|
||||
// Create the cgroup
|
||||
func (c *pidHandler) Create(ctr *CgroupControl) (bool, error) {
|
||||
return ctr.createCgroupDirectory(Pids)
|
||||
}
|
||||
|
||||
// Destroy the cgroup
|
||||
func (c *pidHandler) Destroy(ctr *CgroupControl) error {
|
||||
return rmDirRecursively(ctr.getCgroupv1Path(Pids))
|
||||
}
|
||||
|
||||
// Stat fills a metrics structure with usage stats for the controller
|
||||
func (c *pidHandler) Stat(ctr *CgroupControl, m *Metrics) error {
|
||||
if ctr.path == "" {
|
||||
// nothing we can do to retrieve the pids.current path
|
||||
return nil
|
||||
}
|
||||
|
||||
var PIDRoot string
|
||||
if ctr.cgroup2 {
|
||||
PIDRoot = filepath.Join(cgroupRoot, ctr.path)
|
||||
} else {
|
||||
PIDRoot = ctr.getCgroupv1Path(Pids)
|
||||
}
|
||||
|
||||
current, err := readFileAsUint64(filepath.Join(PIDRoot, "pids.current"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
m.Pids = PidsMetrics{Current: current}
|
||||
return nil
|
||||
}
|
||||
|
|
@ -0,0 +1,80 @@
|
|||
package cgroups
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
systemdDbus "github.com/coreos/go-systemd/v22/dbus"
|
||||
"github.com/godbus/dbus/v5"
|
||||
)
|
||||
|
||||
func systemdCreate(path string, c *systemdDbus.Conn) error {
|
||||
slice, name := filepath.Split(path)
|
||||
slice = strings.TrimSuffix(slice, "/")
|
||||
|
||||
var lastError error
|
||||
for i := 0; i < 2; i++ {
|
||||
properties := []systemdDbus.Property{
|
||||
systemdDbus.PropDescription(fmt.Sprintf("cgroup %s", name)),
|
||||
systemdDbus.PropWants(slice),
|
||||
}
|
||||
pMap := map[string]bool{
|
||||
"DefaultDependencies": false,
|
||||
"MemoryAccounting": true,
|
||||
"CPUAccounting": true,
|
||||
"BlockIOAccounting": true,
|
||||
}
|
||||
if i == 0 {
|
||||
pMap["Delegate"] = true
|
||||
}
|
||||
for k, v := range pMap {
|
||||
p := systemdDbus.Property{
|
||||
Name: k,
|
||||
Value: dbus.MakeVariant(v),
|
||||
}
|
||||
properties = append(properties, p)
|
||||
}
|
||||
|
||||
ch := make(chan string)
|
||||
_, err := c.StartTransientUnitContext(context.TODO(), name, "replace", properties, ch)
|
||||
if err != nil {
|
||||
lastError = err
|
||||
continue
|
||||
}
|
||||
<-ch
|
||||
return nil
|
||||
}
|
||||
return lastError
|
||||
}
|
||||
|
||||
/*
|
||||
systemdDestroyConn is copied from containerd/cgroups/systemd.go file, that
|
||||
has the following license:
|
||||
|
||||
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
|
||||
|
||||
https://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.
|
||||
*/
|
||||
func systemdDestroyConn(path string, c *systemdDbus.Conn) error {
|
||||
name := filepath.Base(path)
|
||||
|
||||
ch := make(chan string)
|
||||
_, err := c.StopUnitContext(context.TODO(), name, "replace", ch)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
<-ch
|
||||
return nil
|
||||
}
|
||||
|
|
@ -4,10 +4,12 @@
|
|||
|
||||
language: go
|
||||
go:
|
||||
- 1.7.x
|
||||
- 1.8.x
|
||||
- 1.13.x
|
||||
- 1.16.x
|
||||
- tip
|
||||
|
||||
arch:
|
||||
- AMD64
|
||||
- ppc64le
|
||||
os:
|
||||
- linux
|
||||
- osx
|
||||
|
|
|
|||
|
|
@ -7,6 +7,19 @@ standard library][go#20126]. The purpose of this function is to be a "secure"
|
|||
alternative to `filepath.Join`, and in particular it provides certain
|
||||
guarantees that are not provided by `filepath.Join`.
|
||||
|
||||
> **NOTE**: This code is *only* safe if you are not at risk of other processes
|
||||
> modifying path components after you've used `SecureJoin`. If it is possible
|
||||
> for a malicious process to modify path components of the resolved path, then
|
||||
> you will be vulnerable to some fairly trivial TOCTOU race conditions. [There
|
||||
> are some Linux kernel patches I'm working on which might allow for a better
|
||||
> solution.][lwn-obeneath]
|
||||
>
|
||||
> In addition, with a slightly modified API it might be possible to use
|
||||
> `O_PATH` and verify that the opened path is actually the resolved one -- but
|
||||
> I have not done that yet. I might add it in the future as a helper function
|
||||
> to help users verify the path (we can't just return `/proc/self/fd/<foo>`
|
||||
> because that doesn't always work transparently for all users).
|
||||
|
||||
This is the function prototype:
|
||||
|
||||
```go
|
||||
|
|
@ -16,8 +29,8 @@ func SecureJoin(root, unsafePath string) (string, error)
|
|||
This library **guarantees** the following:
|
||||
|
||||
* If no error is set, the resulting string **must** be a child path of
|
||||
`SecureJoin` and will not contain any symlink path components (they will all
|
||||
be expanded).
|
||||
`root` and will not contain any symlink path components (they will all be
|
||||
expanded).
|
||||
|
||||
* When expanding symlinks, all symlink path components **must** be resolved
|
||||
relative to the provided root. In particular, this can be considered a
|
||||
|
|
@ -25,7 +38,7 @@ This library **guarantees** the following:
|
|||
these symlinks will **not** be expanded lexically (`filepath.Clean` is not
|
||||
called on the input before processing).
|
||||
|
||||
* Non-existant path components are unaffected by `SecureJoin` (similar to
|
||||
* Non-existent path components are unaffected by `SecureJoin` (similar to
|
||||
`filepath.EvalSymlinks`'s semantics).
|
||||
|
||||
* The returned path will always be `filepath.Clean`ed and thus not contain any
|
||||
|
|
@ -57,6 +70,7 @@ func SecureJoin(root, unsafePath string) (string, error) {
|
|||
}
|
||||
```
|
||||
|
||||
[lwn-obeneath]: https://lwn.net/Articles/767547/
|
||||
[go#20126]: https://github.com/golang/go/issues/20126
|
||||
|
||||
### License ###
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
0.2.2
|
||||
0.2.3
|
||||
|
|
|
|||
|
|
@ -0,0 +1,3 @@
|
|||
module github.com/cyphar/filepath-securejoin
|
||||
|
||||
go 1.13
|
||||
|
|
@ -12,39 +12,20 @@ package securejoin
|
|||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"syscall"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
// ErrSymlinkLoop is returned by SecureJoinVFS when too many symlinks have been
|
||||
// evaluated in attempting to securely join the two given paths.
|
||||
var ErrSymlinkLoop = errors.Wrap(syscall.ELOOP, "secure join")
|
||||
|
||||
// IsNotExist tells you if err is an error that implies that either the path
|
||||
// accessed does not exist (or path components don't exist). This is
|
||||
// effectively a more broad version of os.IsNotExist.
|
||||
func IsNotExist(err error) bool {
|
||||
// If it's a bone-fide ENOENT just bail.
|
||||
if os.IsNotExist(errors.Cause(err)) {
|
||||
return true
|
||||
}
|
||||
|
||||
// Check that it's not actually an ENOTDIR, which in some cases is a more
|
||||
// convoluted case of ENOENT (usually involving weird paths).
|
||||
var errno error
|
||||
switch err := errors.Cause(err).(type) {
|
||||
case *os.PathError:
|
||||
errno = err.Err
|
||||
case *os.LinkError:
|
||||
errno = err.Err
|
||||
case *os.SyscallError:
|
||||
errno = err.Err
|
||||
}
|
||||
return errno == syscall.ENOTDIR || errno == syscall.ENOENT
|
||||
return errors.Is(err, os.ErrNotExist) || errors.Is(err, syscall.ENOTDIR) || errors.Is(err, syscall.ENOENT)
|
||||
}
|
||||
|
||||
// SecureJoinVFS joins the two given path components (similar to Join) except
|
||||
|
|
@ -68,7 +49,7 @@ func SecureJoinVFS(root, unsafePath string, vfs VFS) (string, error) {
|
|||
n := 0
|
||||
for unsafePath != "" {
|
||||
if n > 255 {
|
||||
return "", ErrSymlinkLoop
|
||||
return "", &os.PathError{Op: "SecureJoin", Path: root + "/" + unsafePath, Err: syscall.ELOOP}
|
||||
}
|
||||
|
||||
// Next path component, p.
|
||||
|
|
|
|||
|
|
@ -1 +0,0 @@
|
|||
github.com/pkg/errors v0.8.0
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
Chris Howey <howeyc@gmail.com> <chris@howey.me>
|
||||
Nathan Youngman <git@nathany.com> <4566+nathany@users.noreply.github.com>
|
||||
|
|
@ -1,36 +0,0 @@
|
|||
sudo: false
|
||||
language: go
|
||||
|
||||
go:
|
||||
- "stable"
|
||||
- "1.11.x"
|
||||
- "1.10.x"
|
||||
- "1.9.x"
|
||||
|
||||
matrix:
|
||||
include:
|
||||
- go: "stable"
|
||||
env: GOLINT=true
|
||||
allow_failures:
|
||||
- go: tip
|
||||
fast_finish: true
|
||||
|
||||
|
||||
before_install:
|
||||
- if [ ! -z "${GOLINT}" ]; then go get -u golang.org/x/lint/golint; fi
|
||||
|
||||
script:
|
||||
- go test --race ./...
|
||||
|
||||
after_script:
|
||||
- test -z "$(gofmt -s -l -w . | tee /dev/stderr)"
|
||||
- if [ ! -z "${GOLINT}" ]; then echo running golint; golint --set_exit_status ./...; else echo skipping golint; fi
|
||||
- go vet ./...
|
||||
|
||||
os:
|
||||
- linux
|
||||
- osx
|
||||
- windows
|
||||
|
||||
notifications:
|
||||
email: false
|
||||
|
|
@ -4,35 +4,44 @@
|
|||
|
||||
# You can update this list using the following command:
|
||||
#
|
||||
# $ git shortlog -se | awk '{print $2 " " $3 " " $4}'
|
||||
# $ (head -n10 AUTHORS && git shortlog -se | sed -E 's/^\s+[0-9]+\t//') | tee AUTHORS
|
||||
|
||||
# Please keep the list sorted.
|
||||
|
||||
Aaron L <aaron@bettercoder.net>
|
||||
Adrien Bustany <adrien@bustany.org>
|
||||
Alexey Kazakov <alkazako@redhat.com>
|
||||
Amit Krishnan <amit.krishnan@oracle.com>
|
||||
Anmol Sethi <me@anmol.io>
|
||||
Bjørn Erik Pedersen <bjorn.erik.pedersen@gmail.com>
|
||||
Brian Goff <cpuguy83@gmail.com>
|
||||
Bruno Bigras <bigras.bruno@gmail.com>
|
||||
Caleb Spare <cespare@gmail.com>
|
||||
Case Nelson <case@teammating.com>
|
||||
Chris Howey <chris@howey.me> <howeyc@gmail.com>
|
||||
Chris Howey <howeyc@gmail.com>
|
||||
Christoffer Buchholz <christoffer.buchholz@gmail.com>
|
||||
Daniel Wagner-Hall <dawagner@gmail.com>
|
||||
Dave Cheney <dave@cheney.net>
|
||||
Eric Lin <linxiulei@gmail.com>
|
||||
Evan Phoenix <evan@fallingsnow.net>
|
||||
Francisco Souza <f@souza.cc>
|
||||
Gautam Dey <gautam.dey77@gmail.com>
|
||||
Hari haran <hariharan.uno@gmail.com>
|
||||
John C Barstow
|
||||
Ichinose Shogo <shogo82148@gmail.com>
|
||||
Johannes Ebke <johannes@ebke.org>
|
||||
John C Barstow <jbowtie@amathaine.com>
|
||||
Kelvin Fo <vmirage@gmail.com>
|
||||
Ken-ichirou MATSUZAWA <chamas@h4.dion.ne.jp>
|
||||
Matt Layher <mdlayher@gmail.com>
|
||||
Matthias Stone <matthias@bellstone.ca>
|
||||
Nathan Youngman <git@nathany.com>
|
||||
Nickolai Zeldovich <nickolai@csail.mit.edu>
|
||||
Oliver Bristow <evilumbrella+github@gmail.com>
|
||||
Patrick <patrick@dropbox.com>
|
||||
Paul Hammond <paul@paulhammond.org>
|
||||
Pawel Knap <pawelknap88@gmail.com>
|
||||
Pieter Droogendijk <pieter@binky.org.uk>
|
||||
Pratik Shinde <pratikshinde320@gmail.com>
|
||||
Pursuit92 <JoshChase@techpursuit.net>
|
||||
Riku Voipio <riku.voipio@linaro.org>
|
||||
Rob Figueiredo <robfig@gmail.com>
|
||||
|
|
@ -41,6 +50,7 @@ Slawek Ligus <root@ooz.ie>
|
|||
Soge Zhang <zhssoge@gmail.com>
|
||||
Tiffany Jernigan <tiffany.jernigan@intel.com>
|
||||
Tilak Sharma <tilaks@google.com>
|
||||
Tobias Klauser <tobias.klauser@gmail.com>
|
||||
Tom Payne <twpayne@gmail.com>
|
||||
Travis Cline <travis.cline@gmail.com>
|
||||
Tudor Golubenco <tudor.g@gmail.com>
|
||||
|
|
|
|||
|
|
@ -1,6 +1,28 @@
|
|||
# Changelog
|
||||
|
||||
## v1.4.7 / 2018-01-09
|
||||
All notable changes to this project will be documented in this file.
|
||||
|
||||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
## [1.5.1] - 2021-08-24
|
||||
|
||||
* Revert Add AddRaw to not follow symlinks
|
||||
|
||||
## [1.5.0] - 2021-08-20
|
||||
|
||||
* Go: Increase minimum required version to Go 1.12 [#381](https://github.com/fsnotify/fsnotify/pull/381)
|
||||
* Feature: Add AddRaw method which does not follow symlinks when adding a watch [#289](https://github.com/fsnotify/fsnotify/pull/298)
|
||||
* Windows: Follow symlinks by default like on all other systems [#289](https://github.com/fsnotify/fsnotify/pull/289)
|
||||
* CI: Use GitHub Actions for CI and cover go 1.12-1.17
|
||||
[#378](https://github.com/fsnotify/fsnotify/pull/378)
|
||||
[#381](https://github.com/fsnotify/fsnotify/pull/381)
|
||||
[#385](https://github.com/fsnotify/fsnotify/pull/385)
|
||||
* Go 1.14+: Fix unsafe pointer conversion [#325](https://github.com/fsnotify/fsnotify/pull/325)
|
||||
|
||||
## [1.4.7] - 2018-01-09
|
||||
|
||||
* BSD/macOS: Fix possible deadlock on closing the watcher on kqueue (thanks @nhooyr and @glycerine)
|
||||
* Tests: Fix missing verb on format string (thanks @rchiossi)
|
||||
|
|
@ -10,62 +32,62 @@
|
|||
* Linux: Properly handle inotify's IN_Q_OVERFLOW event (thanks @zeldovich)
|
||||
* Docs: replace references to OS X with macOS
|
||||
|
||||
## v1.4.2 / 2016-10-10
|
||||
## [1.4.2] - 2016-10-10
|
||||
|
||||
* Linux: use InotifyInit1 with IN_CLOEXEC to stop leaking a file descriptor to a child process when using fork/exec [#178](https://github.com/fsnotify/fsnotify/pull/178) (thanks @pattyshack)
|
||||
|
||||
## v1.4.1 / 2016-10-04
|
||||
## [1.4.1] - 2016-10-04
|
||||
|
||||
* Fix flaky inotify stress test on Linux [#177](https://github.com/fsnotify/fsnotify/pull/177) (thanks @pattyshack)
|
||||
|
||||
## v1.4.0 / 2016-10-01
|
||||
## [1.4.0] - 2016-10-01
|
||||
|
||||
* add a String() method to Event.Op [#165](https://github.com/fsnotify/fsnotify/pull/165) (thanks @oozie)
|
||||
|
||||
## v1.3.1 / 2016-06-28
|
||||
## [1.3.1] - 2016-06-28
|
||||
|
||||
* Windows: fix for double backslash when watching the root of a drive [#151](https://github.com/fsnotify/fsnotify/issues/151) (thanks @brunoqc)
|
||||
|
||||
## v1.3.0 / 2016-04-19
|
||||
## [1.3.0] - 2016-04-19
|
||||
|
||||
* Support linux/arm64 by [patching](https://go-review.googlesource.com/#/c/21971/) x/sys/unix and switching to to it from syscall (thanks @suihkulokki) [#135](https://github.com/fsnotify/fsnotify/pull/135)
|
||||
|
||||
## v1.2.10 / 2016-03-02
|
||||
## [1.2.10] - 2016-03-02
|
||||
|
||||
* Fix golint errors in windows.go [#121](https://github.com/fsnotify/fsnotify/pull/121) (thanks @tiffanyfj)
|
||||
|
||||
## v1.2.9 / 2016-01-13
|
||||
## [1.2.9] - 2016-01-13
|
||||
|
||||
kqueue: Fix logic for CREATE after REMOVE [#111](https://github.com/fsnotify/fsnotify/pull/111) (thanks @bep)
|
||||
|
||||
## v1.2.8 / 2015-12-17
|
||||
## [1.2.8] - 2015-12-17
|
||||
|
||||
* kqueue: fix race condition in Close [#105](https://github.com/fsnotify/fsnotify/pull/105) (thanks @djui for reporting the issue and @ppknap for writing a failing test)
|
||||
* inotify: fix race in test
|
||||
* enable race detection for continuous integration (Linux, Mac, Windows)
|
||||
|
||||
## v1.2.5 / 2015-10-17
|
||||
## [1.2.5] - 2015-10-17
|
||||
|
||||
* inotify: use epoll_create1 for arm64 support (requires Linux 2.6.27 or later) [#100](https://github.com/fsnotify/fsnotify/pull/100) (thanks @suihkulokki)
|
||||
* inotify: fix path leaks [#73](https://github.com/fsnotify/fsnotify/pull/73) (thanks @chamaken)
|
||||
* kqueue: watch for rename events on subdirectories [#83](https://github.com/fsnotify/fsnotify/pull/83) (thanks @guotie)
|
||||
* kqueue: avoid infinite loops from symlinks cycles [#101](https://github.com/fsnotify/fsnotify/pull/101) (thanks @illicitonion)
|
||||
|
||||
## v1.2.1 / 2015-10-14
|
||||
## [1.2.1] - 2015-10-14
|
||||
|
||||
* kqueue: don't watch named pipes [#98](https://github.com/fsnotify/fsnotify/pull/98) (thanks @evanphx)
|
||||
|
||||
## v1.2.0 / 2015-02-08
|
||||
## [1.2.0] - 2015-02-08
|
||||
|
||||
* inotify: use epoll to wake up readEvents [#66](https://github.com/fsnotify/fsnotify/pull/66) (thanks @PieterD)
|
||||
* inotify: closing watcher should now always shut down goroutine [#63](https://github.com/fsnotify/fsnotify/pull/63) (thanks @PieterD)
|
||||
* kqueue: close kqueue after removing watches, fixes [#59](https://github.com/fsnotify/fsnotify/issues/59)
|
||||
|
||||
## v1.1.1 / 2015-02-05
|
||||
## [1.1.1] - 2015-02-05
|
||||
|
||||
* inotify: Retry read on EINTR [#61](https://github.com/fsnotify/fsnotify/issues/61) (thanks @PieterD)
|
||||
|
||||
## v1.1.0 / 2014-12-12
|
||||
## [1.1.0] - 2014-12-12
|
||||
|
||||
* kqueue: rework internals [#43](https://github.com/fsnotify/fsnotify/pull/43)
|
||||
* add low-level functions
|
||||
|
|
@ -77,22 +99,22 @@ kqueue: Fix logic for CREATE after REMOVE [#111](https://github.com/fsnotify/fsn
|
|||
* kqueue: fix regression in rework causing subdirectories to be watched [#48](https://github.com/fsnotify/fsnotify/issues/48)
|
||||
* kqueue: cleanup internal watch before sending remove event [#51](https://github.com/fsnotify/fsnotify/issues/51)
|
||||
|
||||
## v1.0.4 / 2014-09-07
|
||||
## [1.0.4] - 2014-09-07
|
||||
|
||||
* kqueue: add dragonfly to the build tags.
|
||||
* Rename source code files, rearrange code so exported APIs are at the top.
|
||||
* Add done channel to example code. [#37](https://github.com/fsnotify/fsnotify/pull/37) (thanks @chenyukang)
|
||||
|
||||
## v1.0.3 / 2014-08-19
|
||||
## [1.0.3] - 2014-08-19
|
||||
|
||||
* [Fix] Windows MOVED_TO now translates to Create like on BSD and Linux. [#36](https://github.com/fsnotify/fsnotify/issues/36)
|
||||
|
||||
## v1.0.2 / 2014-08-17
|
||||
## [1.0.2] - 2014-08-17
|
||||
|
||||
* [Fix] Missing create events on macOS. [#14](https://github.com/fsnotify/fsnotify/issues/14) (thanks @zhsso)
|
||||
* [Fix] Make ./path and path equivalent. (thanks @zhsso)
|
||||
|
||||
## v1.0.0 / 2014-08-15
|
||||
## [1.0.0] - 2014-08-15
|
||||
|
||||
* [API] Remove AddWatch on Windows, use Add.
|
||||
* Improve documentation for exported identifiers. [#30](https://github.com/fsnotify/fsnotify/issues/30)
|
||||
|
|
@ -146,51 +168,51 @@ kqueue: Fix logic for CREATE after REMOVE [#111](https://github.com/fsnotify/fsn
|
|||
* no tests for the current implementation
|
||||
* not fully implemented on Windows [#93](https://github.com/howeyc/fsnotify/issues/93#issuecomment-39285195)
|
||||
|
||||
## v0.9.3 / 2014-12-31
|
||||
## [0.9.3] - 2014-12-31
|
||||
|
||||
* kqueue: cleanup internal watch before sending remove event [#51](https://github.com/fsnotify/fsnotify/issues/51)
|
||||
|
||||
## v0.9.2 / 2014-08-17
|
||||
## [0.9.2] - 2014-08-17
|
||||
|
||||
* [Backport] Fix missing create events on macOS. [#14](https://github.com/fsnotify/fsnotify/issues/14) (thanks @zhsso)
|
||||
|
||||
## v0.9.1 / 2014-06-12
|
||||
## [0.9.1] - 2014-06-12
|
||||
|
||||
* Fix data race on kevent buffer (thanks @tilaks) [#98](https://github.com/howeyc/fsnotify/pull/98)
|
||||
|
||||
## v0.9.0 / 2014-01-17
|
||||
## [0.9.0] - 2014-01-17
|
||||
|
||||
* IsAttrib() for events that only concern a file's metadata [#79][] (thanks @abustany)
|
||||
* [Fix] kqueue: fix deadlock [#77][] (thanks @cespare)
|
||||
* [NOTICE] Development has moved to `code.google.com/p/go.exp/fsnotify` in preparation for inclusion in the Go standard library.
|
||||
|
||||
## v0.8.12 / 2013-11-13
|
||||
## [0.8.12] - 2013-11-13
|
||||
|
||||
* [API] Remove FD_SET and friends from Linux adapter
|
||||
|
||||
## v0.8.11 / 2013-11-02
|
||||
## [0.8.11] - 2013-11-02
|
||||
|
||||
* [Doc] Add Changelog [#72][] (thanks @nathany)
|
||||
* [Doc] Spotlight and double modify events on macOS [#62][] (reported by @paulhammond)
|
||||
|
||||
## v0.8.10 / 2013-10-19
|
||||
## [0.8.10] - 2013-10-19
|
||||
|
||||
* [Fix] kqueue: remove file watches when parent directory is removed [#71][] (reported by @mdwhatcott)
|
||||
* [Fix] kqueue: race between Close and readEvents [#70][] (reported by @bernerdschaefer)
|
||||
* [Doc] specify OS-specific limits in README (thanks @debrando)
|
||||
|
||||
## v0.8.9 / 2013-09-08
|
||||
## [0.8.9] - 2013-09-08
|
||||
|
||||
* [Doc] Contributing (thanks @nathany)
|
||||
* [Doc] update package path in example code [#63][] (thanks @paulhammond)
|
||||
* [Doc] GoCI badge in README (Linux only) [#60][]
|
||||
* [Doc] Cross-platform testing with Vagrant [#59][] (thanks @nathany)
|
||||
|
||||
## v0.8.8 / 2013-06-17
|
||||
## [0.8.8] - 2013-06-17
|
||||
|
||||
* [Fix] Windows: handle `ERROR_MORE_DATA` on Windows [#49][] (thanks @jbowtie)
|
||||
|
||||
## v0.8.7 / 2013-06-03
|
||||
## [0.8.7] - 2013-06-03
|
||||
|
||||
* [API] Make syscall flags internal
|
||||
* [Fix] inotify: ignore event changes
|
||||
|
|
@ -198,74 +220,74 @@ kqueue: Fix logic for CREATE after REMOVE [#111](https://github.com/fsnotify/fsn
|
|||
* [Fix] tests on Windows
|
||||
* lower case error messages
|
||||
|
||||
## v0.8.6 / 2013-05-23
|
||||
## [0.8.6] - 2013-05-23
|
||||
|
||||
* kqueue: Use EVT_ONLY flag on Darwin
|
||||
* [Doc] Update README with full example
|
||||
|
||||
## v0.8.5 / 2013-05-09
|
||||
## [0.8.5] - 2013-05-09
|
||||
|
||||
* [Fix] inotify: allow monitoring of "broken" symlinks (thanks @tsg)
|
||||
|
||||
## v0.8.4 / 2013-04-07
|
||||
## [0.8.4] - 2013-04-07
|
||||
|
||||
* [Fix] kqueue: watch all file events [#40][] (thanks @ChrisBuchholz)
|
||||
|
||||
## v0.8.3 / 2013-03-13
|
||||
## [0.8.3] - 2013-03-13
|
||||
|
||||
* [Fix] inoitfy/kqueue memory leak [#36][] (reported by @nbkolchin)
|
||||
* [Fix] kqueue: use fsnFlags for watching a directory [#33][] (reported by @nbkolchin)
|
||||
|
||||
## v0.8.2 / 2013-02-07
|
||||
## [0.8.2] - 2013-02-07
|
||||
|
||||
* [Doc] add Authors
|
||||
* [Fix] fix data races for map access [#29][] (thanks @fsouza)
|
||||
|
||||
## v0.8.1 / 2013-01-09
|
||||
## [0.8.1] - 2013-01-09
|
||||
|
||||
* [Fix] Windows path separators
|
||||
* [Doc] BSD License
|
||||
|
||||
## v0.8.0 / 2012-11-09
|
||||
## [0.8.0] - 2012-11-09
|
||||
|
||||
* kqueue: directory watching improvements (thanks @vmirage)
|
||||
* inotify: add `IN_MOVED_TO` [#25][] (requested by @cpisto)
|
||||
* [Fix] kqueue: deleting watched directory [#24][] (reported by @jakerr)
|
||||
|
||||
## v0.7.4 / 2012-10-09
|
||||
## [0.7.4] - 2012-10-09
|
||||
|
||||
* [Fix] inotify: fixes from https://codereview.appspot.com/5418045/ (ugorji)
|
||||
* [Fix] kqueue: preserve watch flags when watching for delete [#21][] (reported by @robfig)
|
||||
* [Fix] kqueue: watch the directory even if it isn't a new watch (thanks @robfig)
|
||||
* [Fix] kqueue: modify after recreation of file
|
||||
|
||||
## v0.7.3 / 2012-09-27
|
||||
## [0.7.3] - 2012-09-27
|
||||
|
||||
* [Fix] kqueue: watch with an existing folder inside the watched folder (thanks @vmirage)
|
||||
* [Fix] kqueue: no longer get duplicate CREATE events
|
||||
|
||||
## v0.7.2 / 2012-09-01
|
||||
## [0.7.2] - 2012-09-01
|
||||
|
||||
* kqueue: events for created directories
|
||||
|
||||
## v0.7.1 / 2012-07-14
|
||||
## [0.7.1] - 2012-07-14
|
||||
|
||||
* [Fix] for renaming files
|
||||
|
||||
## v0.7.0 / 2012-07-02
|
||||
## [0.7.0] - 2012-07-02
|
||||
|
||||
* [Feature] FSNotify flags
|
||||
* [Fix] inotify: Added file name back to event path
|
||||
|
||||
## v0.6.0 / 2012-06-06
|
||||
## [0.6.0] - 2012-06-06
|
||||
|
||||
* kqueue: watch files after directory created (thanks @tmc)
|
||||
|
||||
## v0.5.1 / 2012-05-22
|
||||
## [0.5.1] - 2012-05-22
|
||||
|
||||
* [Fix] inotify: remove all watches before Close()
|
||||
|
||||
## v0.5.0 / 2012-05-03
|
||||
## [0.5.0] - 2012-05-03
|
||||
|
||||
* [API] kqueue: return errors during watch instead of sending over channel
|
||||
* kqueue: match symlink behavior on Linux
|
||||
|
|
@ -273,22 +295,22 @@ kqueue: Fix logic for CREATE after REMOVE [#111](https://github.com/fsnotify/fsn
|
|||
* [Fix] kqueue: handle EINTR (reported by @robfig)
|
||||
* [Doc] Godoc example [#1][] (thanks @davecheney)
|
||||
|
||||
## v0.4.0 / 2012-03-30
|
||||
## [0.4.0] - 2012-03-30
|
||||
|
||||
* Go 1 released: build with go tool
|
||||
* [Feature] Windows support using winfsnotify
|
||||
* Windows does not have attribute change notifications
|
||||
* Roll attribute notifications into IsModify
|
||||
|
||||
## v0.3.0 / 2012-02-19
|
||||
## [0.3.0] - 2012-02-19
|
||||
|
||||
* kqueue: add files when watch directory
|
||||
|
||||
## v0.2.0 / 2011-12-30
|
||||
## [0.2.0] - 2011-12-30
|
||||
|
||||
* update to latest Go weekly code
|
||||
|
||||
## v0.1.0 / 2011-10-19
|
||||
## [0.1.0] - 2011-10-19
|
||||
|
||||
* kqueue: add watch on file creation to match inotify
|
||||
* kqueue: create file event
|
||||
|
|
|
|||
|
|
@ -12,9 +12,9 @@ Cross platform: Windows, Linux, BSD and macOS.
|
|||
|
||||
| Adapter | OS | Status |
|
||||
| --------------------- | -------------------------------- | ------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| inotify | Linux 2.6.27 or later, Android\* | Supported [](https://travis-ci.org/fsnotify/fsnotify) |
|
||||
| kqueue | BSD, macOS, iOS\* | Supported [](https://travis-ci.org/fsnotify/fsnotify) |
|
||||
| ReadDirectoryChangesW | Windows | Supported [](https://travis-ci.org/fsnotify/fsnotify) |
|
||||
| inotify | Linux 2.6.27 or later, Android\* | Supported |
|
||||
| kqueue | BSD, macOS, iOS\* | Supported |
|
||||
| ReadDirectoryChangesW | Windows | Supported |
|
||||
| FSEvents | macOS | [Planned](https://github.com/fsnotify/fsnotify/issues/11) |
|
||||
| FEN | Solaris 11 | [In Progress](https://github.com/fsnotify/fsnotify/issues/12) |
|
||||
| fanotify | Linux 2.6.37+ | [Planned](https://github.com/fsnotify/fsnotify/issues/114) |
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build solaris
|
||||
// +build solaris
|
||||
|
||||
package fsnotify
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build !plan9
|
||||
// +build !plan9
|
||||
|
||||
// Package fsnotify provides a platform-independent interface for file system notifications.
|
||||
|
|
|
|||
|
|
@ -2,4 +2,6 @@ module github.com/fsnotify/fsnotify
|
|||
|
||||
go 1.13
|
||||
|
||||
require golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9
|
||||
require golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c
|
||||
|
||||
retract v1.5.0
|
||||
|
|
|
|||
|
|
@ -1,2 +1,2 @@
|
|||
golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9 h1:L2auWcuQIvxz9xSEqzESnV/QN/gNRXNApHi3fYwl2w0=
|
||||
golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c h1:F1jZWGFhYfh0Ci55sIpILtKKK8p3i2/krTr0H1rg74I=
|
||||
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build linux
|
||||
// +build linux
|
||||
|
||||
package fsnotify
|
||||
|
|
@ -272,7 +273,7 @@ func (w *Watcher) readEvents() {
|
|||
|
||||
if nameLen > 0 {
|
||||
// Point "bytes" at the first byte of the filename
|
||||
bytes := (*[unix.PathMax]byte)(unsafe.Pointer(&buf[offset+unix.SizeofInotifyEvent]))
|
||||
bytes := (*[unix.PathMax]byte)(unsafe.Pointer(&buf[offset+unix.SizeofInotifyEvent]))[:nameLen:nameLen]
|
||||
// The filename is padded with NULL bytes. TrimRight() gets rid of those.
|
||||
name += "/" + strings.TrimRight(string(bytes[0:nameLen]), "\000")
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build linux
|
||||
// +build linux
|
||||
|
||||
package fsnotify
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build freebsd || openbsd || netbsd || dragonfly || darwin
|
||||
// +build freebsd openbsd netbsd dragonfly darwin
|
||||
|
||||
package fsnotify
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build freebsd || openbsd || netbsd || dragonfly
|
||||
// +build freebsd openbsd netbsd dragonfly
|
||||
|
||||
package fsnotify
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build darwin
|
||||
// +build darwin
|
||||
|
||||
package fsnotify
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build windows
|
||||
// +build windows
|
||||
|
||||
package fsnotify
|
||||
|
|
|
|||
|
|
@ -14,14 +14,12 @@ D-Bus message bus system.
|
|||
|
||||
### Installation
|
||||
|
||||
This packages requires Go 1.7. If you installed it and set up your GOPATH, just run:
|
||||
This packages requires Go 1.12 or later. It can be installed by running the command below:
|
||||
|
||||
```
|
||||
go get github.com/godbus/dbus
|
||||
go get github.com/godbus/dbus/v5
|
||||
```
|
||||
|
||||
If you want to use the subpackages, you can install them the same way.
|
||||
|
||||
### Usage
|
||||
|
||||
The complete package documentation and some simple examples are available at
|
||||
|
|
@ -30,10 +28,12 @@ The complete package documentation and some simple examples are available at
|
|||
gives a short overview over the basic usage.
|
||||
|
||||
#### Projects using godbus
|
||||
- [notify](https://github.com/esiqveland/notify) provides desktop notifications over dbus into a library.
|
||||
- [fyne](https://github.com/fyne-io/fyne) a cross platform GUI in Go inspired by Material Design.
|
||||
- [fynedesk](https://github.com/fyne-io/fynedesk) a full desktop environment for Linux/Unix using Fyne.
|
||||
- [go-bluetooth](https://github.com/muka/go-bluetooth) provides a bluetooth client over bluez dbus API.
|
||||
- [playerbm](https://github.com/altdesktop/playerbm) a bookmark utility for media players.
|
||||
- [iwd](https://github.com/shibumi/iwd) go bindings for the internet wireless daemon "iwd".
|
||||
- [notify](https://github.com/esiqveland/notify) provides desktop notifications over dbus into a library.
|
||||
- [playerbm](https://github.com/altdesktop/playerbm) a bookmark utility for media players.
|
||||
|
||||
Please note that the API is considered unstable for now and may change without
|
||||
further notice.
|
||||
|
|
@ -53,7 +53,7 @@ type Auth interface {
|
|||
// bus. Auth must not be called on shared connections.
|
||||
func (conn *Conn) Auth(methods []Auth) error {
|
||||
if methods == nil {
|
||||
uid := strconv.Itoa(os.Getuid())
|
||||
uid := strconv.Itoa(os.Geteuid())
|
||||
methods = []Auth{AuthExternal(uid), AuthCookieSha1(uid, getHomeDir())}
|
||||
}
|
||||
in := bufio.NewReader(conn.transport)
|
||||
|
|
@ -75,9 +75,9 @@ func (conn *Conn) Auth(methods []Auth) error {
|
|||
s = s[1:]
|
||||
for _, v := range s {
|
||||
for _, m := range methods {
|
||||
if name, data, status := m.FirstData(); bytes.Equal(v, name) {
|
||||
if name, _, status := m.FirstData(); bytes.Equal(v, name) {
|
||||
var ok bool
|
||||
err = authWriteLine(conn.transport, []byte("AUTH"), v, data)
|
||||
err = authWriteLine(conn.transport, []byte("AUTH"), v)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
@ -194,11 +194,14 @@ func (conn *Conn) tryAuth(m Auth, state authState, in *bufio.Reader) (error, boo
|
|||
}
|
||||
conn.uuid = string(s[1])
|
||||
return nil, true
|
||||
case state == waitingForOk && string(s[0]) == "DATA":
|
||||
err = authWriteLine(conn.transport, []byte("DATA"))
|
||||
if err != nil {
|
||||
return err, false
|
||||
}
|
||||
case state == waitingForOk && string(s[0]) == "REJECTED":
|
||||
return nil, false
|
||||
case state == waitingForOk && (string(s[0]) == "DATA" ||
|
||||
string(s[0]) == "ERROR"):
|
||||
|
||||
case state == waitingForOk && string(s[0]) == "ERROR":
|
||||
err = authWriteLine(conn.transport, []byte("CANCEL"))
|
||||
if err != nil {
|
||||
return err, false
|
||||
|
|
|
|||
|
|
@ -73,7 +73,7 @@ func SessionBus() (conn *Conn, err error) {
|
|||
return
|
||||
}
|
||||
|
||||
func getSessionBusAddress() (string, error) {
|
||||
func getSessionBusAddress(autolaunch bool) (string, error) {
|
||||
if address := os.Getenv("DBUS_SESSION_BUS_ADDRESS"); address != "" && address != "autolaunch:" {
|
||||
return address, nil
|
||||
|
||||
|
|
@ -81,12 +81,26 @@ func getSessionBusAddress() (string, error) {
|
|||
os.Setenv("DBUS_SESSION_BUS_ADDRESS", address)
|
||||
return address, nil
|
||||
}
|
||||
if !autolaunch {
|
||||
return "", errors.New("dbus: couldn't determine address of session bus")
|
||||
}
|
||||
return getSessionBusPlatformAddress()
|
||||
}
|
||||
|
||||
// SessionBusPrivate returns a new private connection to the session bus.
|
||||
func SessionBusPrivate(opts ...ConnOption) (*Conn, error) {
|
||||
address, err := getSessionBusAddress()
|
||||
address, err := getSessionBusAddress(true)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return Dial(address, opts...)
|
||||
}
|
||||
|
||||
// SessionBusPrivate returns a new private connection to the session bus. If
|
||||
// the session bus is not already open, do not attempt to launch it.
|
||||
func SessionBusPrivateNoAutoStartup(opts ...ConnOption) (*Conn, error) {
|
||||
address, err := getSessionBusAddress(false)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
@ -121,7 +135,7 @@ func SystemBus() (conn *Conn, err error) {
|
|||
|
||||
// ConnectSessionBus connects to the session bus.
|
||||
func ConnectSessionBus(opts ...ConnOption) (*Conn, error) {
|
||||
address, err := getSessionBusAddress()
|
||||
address, err := getSessionBusAddress(true)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
@ -180,7 +194,7 @@ func Dial(address string, opts ...ConnOption) (*Conn, error) {
|
|||
//
|
||||
// Deprecated: use Dial with options instead.
|
||||
func DialHandler(address string, handler Handler, signalHandler SignalHandler) (*Conn, error) {
|
||||
return Dial(address, WithSignalHandler(signalHandler))
|
||||
return Dial(address, WithHandler(handler), WithSignalHandler(signalHandler))
|
||||
}
|
||||
|
||||
// ConnOption is a connection option.
|
||||
|
|
@ -478,14 +492,24 @@ func (conn *Conn) sendMessageAndIfClosed(msg *Message, ifClosed func()) {
|
|||
conn.outInt(msg)
|
||||
}
|
||||
err := conn.outHandler.sendAndIfClosed(msg, ifClosed)
|
||||
conn.calls.handleSendError(msg, err)
|
||||
if err != nil {
|
||||
conn.serialGen.RetireSerial(msg.serial)
|
||||
conn.handleSendError(msg, err)
|
||||
} else if msg.Type != TypeMethodCall {
|
||||
conn.serialGen.RetireSerial(msg.serial)
|
||||
}
|
||||
}
|
||||
|
||||
func (conn *Conn) handleSendError(msg *Message, err error) {
|
||||
if msg.Type == TypeMethodCall {
|
||||
conn.calls.handleSendError(msg, err)
|
||||
} else if msg.Type == TypeMethodReply {
|
||||
if _, ok := err.(FormatError); ok {
|
||||
conn.sendError(err, msg.Headers[FieldDestination].value.(string), msg.Headers[FieldReplySerial].value.(uint32))
|
||||
}
|
||||
}
|
||||
conn.serialGen.RetireSerial(msg.serial)
|
||||
}
|
||||
|
||||
// Send sends the given message to the message bus. You usually don't need to
|
||||
// use this; use the higher-level equivalents (Call / Go, Emit and Export)
|
||||
// instead. If msg is a method call and NoReplyExpected is not set, a non-nil
|
||||
|
|
|
|||
|
|
@ -10,14 +10,16 @@ type decoder struct {
|
|||
in io.Reader
|
||||
order binary.ByteOrder
|
||||
pos int
|
||||
fds []int
|
||||
}
|
||||
|
||||
// newDecoder returns a new decoder that reads values from in. The input is
|
||||
// expected to be in the given byte order.
|
||||
func newDecoder(in io.Reader, order binary.ByteOrder) *decoder {
|
||||
func newDecoder(in io.Reader, order binary.ByteOrder, fds []int) *decoder {
|
||||
dec := new(decoder)
|
||||
dec.in = in
|
||||
dec.order = order
|
||||
dec.fds = fds
|
||||
return dec
|
||||
}
|
||||
|
||||
|
|
@ -53,7 +55,7 @@ func (dec *decoder) Decode(sig Signature) (vs []interface{}, err error) {
|
|||
vs = make([]interface{}, 0)
|
||||
s := sig.str
|
||||
for s != "" {
|
||||
err, rem := validSingle(s, 0)
|
||||
err, rem := validSingle(s, &depthCounter{})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
@ -150,7 +152,7 @@ func (dec *decoder) decode(s string, depth int) interface{} {
|
|||
if len(sig.str) == 0 {
|
||||
panic(FormatError("variant signature is empty"))
|
||||
}
|
||||
err, rem := validSingle(sig.str, 0)
|
||||
err, rem := validSingle(sig.str, &depthCounter{})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
|
@ -161,7 +163,11 @@ func (dec *decoder) decode(s string, depth int) interface{} {
|
|||
variant.value = dec.decode(sig.str, depth+1)
|
||||
return variant
|
||||
case 'h':
|
||||
return UnixFDIndex(dec.decode("u", depth).(uint32))
|
||||
idx := dec.decode("u", depth).(uint32)
|
||||
if int(idx) < len(dec.fds) {
|
||||
return UnixFD(dec.fds[idx])
|
||||
}
|
||||
return UnixFDIndex(idx)
|
||||
case 'a':
|
||||
if len(s) > 1 && s[1] == '{' {
|
||||
ksig := s[2:3]
|
||||
|
|
@ -219,7 +225,7 @@ func (dec *decoder) decode(s string, depth int) interface{} {
|
|||
v := make([]interface{}, 0)
|
||||
s = s[1 : len(s)-1]
|
||||
for s != "" {
|
||||
err, rem := validSingle(s, 0)
|
||||
err, rem := validSingle(s, &depthCounter{})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,28 +5,33 @@ import (
|
|||
"encoding/binary"
|
||||
"io"
|
||||
"reflect"
|
||||
"strings"
|
||||
"unicode/utf8"
|
||||
)
|
||||
|
||||
// An encoder encodes values to the D-Bus wire format.
|
||||
type encoder struct {
|
||||
out io.Writer
|
||||
fds []int
|
||||
order binary.ByteOrder
|
||||
pos int
|
||||
}
|
||||
|
||||
// NewEncoder returns a new encoder that writes to out in the given byte order.
|
||||
func newEncoder(out io.Writer, order binary.ByteOrder) *encoder {
|
||||
return newEncoderAtOffset(out, 0, order)
|
||||
func newEncoder(out io.Writer, order binary.ByteOrder, fds []int) *encoder {
|
||||
enc := newEncoderAtOffset(out, 0, order, fds)
|
||||
return enc
|
||||
}
|
||||
|
||||
// newEncoderAtOffset returns a new encoder that writes to out in the given
|
||||
// byte order. Specify the offset to initialize pos for proper alignment
|
||||
// computation.
|
||||
func newEncoderAtOffset(out io.Writer, offset int, order binary.ByteOrder) *encoder {
|
||||
func newEncoderAtOffset(out io.Writer, offset int, order binary.ByteOrder, fds []int) *encoder {
|
||||
enc := new(encoder)
|
||||
enc.out = out
|
||||
enc.order = order
|
||||
enc.pos = offset
|
||||
enc.fds = fds
|
||||
return enc
|
||||
}
|
||||
|
||||
|
|
@ -75,6 +80,9 @@ func (enc *encoder) Encode(vs ...interface{}) (err error) {
|
|||
// encode encodes the given value to the writer and panics on error. depth holds
|
||||
// the depth of the container nesting.
|
||||
func (enc *encoder) encode(v reflect.Value, depth int) {
|
||||
if depth > 64 {
|
||||
panic(FormatError("input exceeds depth limitation"))
|
||||
}
|
||||
enc.align(alignment(v.Type()))
|
||||
switch v.Kind() {
|
||||
case reflect.Uint8:
|
||||
|
|
@ -97,7 +105,14 @@ func (enc *encoder) encode(v reflect.Value, depth int) {
|
|||
enc.binwrite(uint16(v.Uint()))
|
||||
enc.pos += 2
|
||||
case reflect.Int, reflect.Int32:
|
||||
enc.binwrite(int32(v.Int()))
|
||||
if v.Type() == unixFDType {
|
||||
fd := v.Int()
|
||||
idx := len(enc.fds)
|
||||
enc.fds = append(enc.fds, int(fd))
|
||||
enc.binwrite(uint32(idx))
|
||||
} else {
|
||||
enc.binwrite(int32(v.Int()))
|
||||
}
|
||||
enc.pos += 4
|
||||
case reflect.Uint, reflect.Uint32:
|
||||
enc.binwrite(uint32(v.Uint()))
|
||||
|
|
@ -112,9 +127,21 @@ func (enc *encoder) encode(v reflect.Value, depth int) {
|
|||
enc.binwrite(v.Float())
|
||||
enc.pos += 8
|
||||
case reflect.String:
|
||||
enc.encode(reflect.ValueOf(uint32(len(v.String()))), depth)
|
||||
str := v.String()
|
||||
if !utf8.ValidString(str) {
|
||||
panic(FormatError("input has a not-utf8 char in string"))
|
||||
}
|
||||
if strings.IndexByte(str, byte(0)) != -1 {
|
||||
panic(FormatError("input has a null char('\\000') in string"))
|
||||
}
|
||||
if v.Type() == objectPathType {
|
||||
if !ObjectPath(str).IsValid() {
|
||||
panic(FormatError("invalid object path"))
|
||||
}
|
||||
}
|
||||
enc.encode(reflect.ValueOf(uint32(len(str))), depth)
|
||||
b := make([]byte, v.Len()+1)
|
||||
copy(b, v.String())
|
||||
copy(b, str)
|
||||
b[len(b)-1] = 0
|
||||
n, err := enc.out.Write(b)
|
||||
if err != nil {
|
||||
|
|
@ -124,20 +151,23 @@ func (enc *encoder) encode(v reflect.Value, depth int) {
|
|||
case reflect.Ptr:
|
||||
enc.encode(v.Elem(), depth)
|
||||
case reflect.Slice, reflect.Array:
|
||||
if depth >= 64 {
|
||||
panic(FormatError("input exceeds container depth limit"))
|
||||
}
|
||||
// Lookahead offset: 4 bytes for uint32 length (with alignment),
|
||||
// plus alignment for elements.
|
||||
n := enc.padding(0, 4) + 4
|
||||
offset := enc.pos + n + enc.padding(n, alignment(v.Type().Elem()))
|
||||
|
||||
var buf bytes.Buffer
|
||||
bufenc := newEncoderAtOffset(&buf, offset, enc.order)
|
||||
bufenc := newEncoderAtOffset(&buf, offset, enc.order, enc.fds)
|
||||
|
||||
for i := 0; i < v.Len(); i++ {
|
||||
bufenc.encode(v.Index(i), depth+1)
|
||||
}
|
||||
|
||||
if buf.Len() > 1<<26 {
|
||||
panic(FormatError("input exceeds array size limitation"))
|
||||
}
|
||||
|
||||
enc.fds = bufenc.fds
|
||||
enc.encode(reflect.ValueOf(uint32(buf.Len())), depth)
|
||||
length := buf.Len()
|
||||
enc.align(alignment(v.Type().Elem()))
|
||||
|
|
@ -146,13 +176,10 @@ func (enc *encoder) encode(v reflect.Value, depth int) {
|
|||
}
|
||||
enc.pos += length
|
||||
case reflect.Struct:
|
||||
if depth >= 64 && v.Type() != signatureType {
|
||||
panic(FormatError("input exceeds container depth limit"))
|
||||
}
|
||||
switch t := v.Type(); t {
|
||||
case signatureType:
|
||||
str := v.Field(0)
|
||||
enc.encode(reflect.ValueOf(byte(str.Len())), depth+1)
|
||||
enc.encode(reflect.ValueOf(byte(str.Len())), depth)
|
||||
b := make([]byte, str.Len()+1)
|
||||
copy(b, str.String())
|
||||
b[len(b)-1] = 0
|
||||
|
|
@ -176,9 +203,6 @@ func (enc *encoder) encode(v reflect.Value, depth int) {
|
|||
case reflect.Map:
|
||||
// Maps are arrays of structures, so they actually increase the depth by
|
||||
// 2.
|
||||
if depth >= 63 {
|
||||
panic(FormatError("input exceeds container depth limit"))
|
||||
}
|
||||
if !isKeyType(v.Type().Key()) {
|
||||
panic(InvalidTypeError{v.Type()})
|
||||
}
|
||||
|
|
@ -189,12 +213,13 @@ func (enc *encoder) encode(v reflect.Value, depth int) {
|
|||
offset := enc.pos + n + enc.padding(n, 8)
|
||||
|
||||
var buf bytes.Buffer
|
||||
bufenc := newEncoderAtOffset(&buf, offset, enc.order)
|
||||
bufenc := newEncoderAtOffset(&buf, offset, enc.order, enc.fds)
|
||||
for _, k := range keys {
|
||||
bufenc.align(8)
|
||||
bufenc.encode(k, depth+2)
|
||||
bufenc.encode(v.MapIndex(k), depth+2)
|
||||
}
|
||||
enc.fds = bufenc.fds
|
||||
enc.encode(reflect.ValueOf(uint32(buf.Len())), depth)
|
||||
length := buf.Len()
|
||||
enc.align(8)
|
||||
|
|
|
|||
|
|
@ -26,6 +26,27 @@ var (
|
|||
}
|
||||
)
|
||||
|
||||
func MakeNoObjectError(path ObjectPath) Error {
|
||||
return Error{
|
||||
"org.freedesktop.DBus.Error.NoSuchObject",
|
||||
[]interface{}{fmt.Sprintf("No such object '%s'", string(path))},
|
||||
}
|
||||
}
|
||||
|
||||
func MakeUnknownMethodError(methodName string) Error {
|
||||
return Error{
|
||||
"org.freedesktop.DBus.Error.UnknownMethod",
|
||||
[]interface{}{fmt.Sprintf("Unknown / invalid method '%s'", methodName)},
|
||||
}
|
||||
}
|
||||
|
||||
func MakeUnknownInterfaceError(ifaceName string) Error {
|
||||
return Error{
|
||||
"org.freedesktop.DBus.Error.UnknownInterface",
|
||||
[]interface{}{fmt.Sprintf("Object does not implement the interface '%s'", ifaceName)},
|
||||
}
|
||||
}
|
||||
|
||||
func MakeFailedError(err error) *Error {
|
||||
return &Error{
|
||||
"org.freedesktop.DBus.Error.Failed",
|
||||
|
|
@ -128,6 +149,11 @@ func (conn *Conn) handleCall(msg *Message) {
|
|||
ifaceName, _ := msg.Headers[FieldInterface].value.(string)
|
||||
sender, hasSender := msg.Headers[FieldSender].value.(string)
|
||||
serial := msg.serial
|
||||
|
||||
if len(name) == 0 {
|
||||
conn.sendError(ErrMsgUnknownMethod, sender, serial)
|
||||
}
|
||||
|
||||
if ifaceName == "org.freedesktop.DBus.Peer" {
|
||||
switch name {
|
||||
case "Ping":
|
||||
|
|
@ -135,29 +161,26 @@ func (conn *Conn) handleCall(msg *Message) {
|
|||
case "GetMachineId":
|
||||
conn.sendReply(sender, serial, conn.uuid)
|
||||
default:
|
||||
conn.sendError(ErrMsgUnknownMethod, sender, serial)
|
||||
conn.sendError(MakeUnknownMethodError(name), sender, serial)
|
||||
}
|
||||
return
|
||||
}
|
||||
if len(name) == 0 {
|
||||
conn.sendError(ErrMsgUnknownMethod, sender, serial)
|
||||
}
|
||||
|
||||
object, ok := conn.handler.LookupObject(path)
|
||||
if !ok {
|
||||
conn.sendError(ErrMsgNoObject, sender, serial)
|
||||
conn.sendError(MakeNoObjectError(path), sender, serial)
|
||||
return
|
||||
}
|
||||
|
||||
iface, exists := object.LookupInterface(ifaceName)
|
||||
if !exists {
|
||||
conn.sendError(ErrMsgUnknownInterface, sender, serial)
|
||||
conn.sendError(MakeUnknownInterfaceError(ifaceName), sender, serial)
|
||||
return
|
||||
}
|
||||
|
||||
m, exists := iface.LookupMethod(name)
|
||||
if !exists {
|
||||
conn.sendError(ErrMsgUnknownMethod, sender, serial)
|
||||
conn.sendError(MakeUnknownMethodError(name), sender, serial)
|
||||
return
|
||||
}
|
||||
args, err := conn.decodeArguments(m, sender, msg)
|
||||
|
|
|
|||
|
|
@ -118,11 +118,7 @@ type header struct {
|
|||
Variant
|
||||
}
|
||||
|
||||
// DecodeMessage tries to decode a single message in the D-Bus wire format
|
||||
// from the given reader. The byte order is figured out from the first byte.
|
||||
// The possibly returned error can be an error of the underlying reader, an
|
||||
// InvalidMessageError or a FormatError.
|
||||
func DecodeMessage(rd io.Reader) (msg *Message, err error) {
|
||||
func DecodeMessageWithFDs(rd io.Reader, fds []int) (msg *Message, err error) {
|
||||
var order binary.ByteOrder
|
||||
var hlength, length uint32
|
||||
var typ, flags, proto byte
|
||||
|
|
@ -142,7 +138,7 @@ func DecodeMessage(rd io.Reader) (msg *Message, err error) {
|
|||
return nil, InvalidMessageError("invalid byte order")
|
||||
}
|
||||
|
||||
dec := newDecoder(rd, order)
|
||||
dec := newDecoder(rd, order, fds)
|
||||
dec.pos = 1
|
||||
|
||||
msg = new(Message)
|
||||
|
|
@ -166,7 +162,7 @@ func DecodeMessage(rd io.Reader) (msg *Message, err error) {
|
|||
if hlength+length+16 > 1<<27 {
|
||||
return nil, InvalidMessageError("message is too long")
|
||||
}
|
||||
dec = newDecoder(io.MultiReader(bytes.NewBuffer(b), rd), order)
|
||||
dec = newDecoder(io.MultiReader(bytes.NewBuffer(b), rd), order, fds)
|
||||
dec.pos = 12
|
||||
vs, err = dec.Decode(Signature{"a(yv)"})
|
||||
if err != nil {
|
||||
|
|
@ -196,7 +192,7 @@ func DecodeMessage(rd io.Reader) (msg *Message, err error) {
|
|||
sig, _ := msg.Headers[FieldSignature].value.(Signature)
|
||||
if sig.str != "" {
|
||||
buf := bytes.NewBuffer(body)
|
||||
dec = newDecoder(buf, order)
|
||||
dec = newDecoder(buf, order, fds)
|
||||
vs, err := dec.Decode(sig)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
|
@ -207,12 +203,32 @@ func DecodeMessage(rd io.Reader) (msg *Message, err error) {
|
|||
return
|
||||
}
|
||||
|
||||
// EncodeTo encodes and sends a message to the given writer. The byte order must
|
||||
// be either binary.LittleEndian or binary.BigEndian. If the message is not
|
||||
// valid or an error occurs when writing, an error is returned.
|
||||
func (msg *Message) EncodeTo(out io.Writer, order binary.ByteOrder) error {
|
||||
// DecodeMessage tries to decode a single message in the D-Bus wire format
|
||||
// from the given reader. The byte order is figured out from the first byte.
|
||||
// The possibly returned error can be an error of the underlying reader, an
|
||||
// InvalidMessageError or a FormatError.
|
||||
func DecodeMessage(rd io.Reader) (msg *Message, err error) {
|
||||
return DecodeMessageWithFDs(rd, make([]int, 0));
|
||||
}
|
||||
|
||||
type nullwriter struct{}
|
||||
|
||||
func (nullwriter) Write(p []byte) (cnt int, err error) {
|
||||
return len(p), nil
|
||||
}
|
||||
|
||||
func (msg *Message) CountFds() (int, error) {
|
||||
if len(msg.Body) == 0 {
|
||||
return 0, nil
|
||||
}
|
||||
enc := newEncoder(nullwriter{}, nativeEndian, make([]int, 0))
|
||||
err := enc.Encode(msg.Body...)
|
||||
return len(enc.fds), err
|
||||
}
|
||||
|
||||
func (msg *Message) EncodeToWithFDs(out io.Writer, order binary.ByteOrder) (fds []int, err error) {
|
||||
if err := msg.IsValid(); err != nil {
|
||||
return err
|
||||
return make([]int, 0), err
|
||||
}
|
||||
var vs [7]interface{}
|
||||
switch order {
|
||||
|
|
@ -221,12 +237,16 @@ func (msg *Message) EncodeTo(out io.Writer, order binary.ByteOrder) error {
|
|||
case binary.BigEndian:
|
||||
vs[0] = byte('B')
|
||||
default:
|
||||
return errors.New("dbus: invalid byte order")
|
||||
return make([]int, 0), errors.New("dbus: invalid byte order")
|
||||
}
|
||||
body := new(bytes.Buffer)
|
||||
enc := newEncoder(body, order)
|
||||
fds = make([]int, 0)
|
||||
enc := newEncoder(body, order, fds)
|
||||
if len(msg.Body) != 0 {
|
||||
enc.Encode(msg.Body...)
|
||||
err = enc.Encode(msg.Body...)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
vs[1] = msg.Type
|
||||
vs[2] = msg.Flags
|
||||
|
|
@ -239,17 +259,28 @@ func (msg *Message) EncodeTo(out io.Writer, order binary.ByteOrder) error {
|
|||
}
|
||||
vs[6] = headers
|
||||
var buf bytes.Buffer
|
||||
enc = newEncoder(&buf, order)
|
||||
enc.Encode(vs[:]...)
|
||||
enc = newEncoder(&buf, order, enc.fds)
|
||||
err = enc.Encode(vs[:]...)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
enc.align(8)
|
||||
body.WriteTo(&buf)
|
||||
if buf.Len() > 1<<27 {
|
||||
return InvalidMessageError("message is too long")
|
||||
return make([]int, 0), InvalidMessageError("message is too long")
|
||||
}
|
||||
if _, err := buf.WriteTo(out); err != nil {
|
||||
return err
|
||||
return make([]int, 0), err
|
||||
}
|
||||
return nil
|
||||
return enc.fds, nil
|
||||
}
|
||||
|
||||
// EncodeTo encodes and sends a message to the given writer. The byte order must
|
||||
// be either binary.LittleEndian or binary.BigEndian. If the message is not
|
||||
// valid or an error occurs when writing, an error is returned.
|
||||
func (msg *Message) EncodeTo(out io.Writer, order binary.ByteOrder) (err error) {
|
||||
_, err = msg.EncodeToWithFDs(out, order)
|
||||
return err
|
||||
}
|
||||
|
||||
// IsValid checks whether msg is a valid message and returns an
|
||||
|
|
|
|||
|
|
@ -34,7 +34,7 @@ type Signature struct {
|
|||
func SignatureOf(vs ...interface{}) Signature {
|
||||
var s string
|
||||
for _, v := range vs {
|
||||
s += getSignature(reflect.TypeOf(v))
|
||||
s += getSignature(reflect.TypeOf(v), &depthCounter{})
|
||||
}
|
||||
return Signature{s}
|
||||
}
|
||||
|
|
@ -42,11 +42,19 @@ func SignatureOf(vs ...interface{}) Signature {
|
|||
// SignatureOfType returns the signature of the given type. It panics if the
|
||||
// type is not representable in D-Bus.
|
||||
func SignatureOfType(t reflect.Type) Signature {
|
||||
return Signature{getSignature(t)}
|
||||
return Signature{getSignature(t, &depthCounter{})}
|
||||
}
|
||||
|
||||
// getSignature returns the signature of the given type and panics on unknown types.
|
||||
func getSignature(t reflect.Type) string {
|
||||
func getSignature(t reflect.Type, depth *depthCounter) (sig string) {
|
||||
if !depth.Valid() {
|
||||
panic("container nesting too deep")
|
||||
}
|
||||
defer func() {
|
||||
if len(sig) > 255 {
|
||||
panic("signature exceeds the length limitation")
|
||||
}
|
||||
}()
|
||||
// handle simple types first
|
||||
switch t.Kind() {
|
||||
case reflect.Uint8:
|
||||
|
|
@ -74,7 +82,7 @@ func getSignature(t reflect.Type) string {
|
|||
case reflect.Float64:
|
||||
return "d"
|
||||
case reflect.Ptr:
|
||||
return getSignature(t.Elem())
|
||||
return getSignature(t.Elem(), depth)
|
||||
case reflect.String:
|
||||
if t == objectPathType {
|
||||
return "o"
|
||||
|
|
@ -90,17 +98,20 @@ func getSignature(t reflect.Type) string {
|
|||
for i := 0; i < t.NumField(); i++ {
|
||||
field := t.Field(i)
|
||||
if field.PkgPath == "" && field.Tag.Get("dbus") != "-" {
|
||||
s += getSignature(t.Field(i).Type)
|
||||
s += getSignature(t.Field(i).Type, depth.EnterStruct())
|
||||
}
|
||||
}
|
||||
if len(s) == 0 {
|
||||
panic("empty struct")
|
||||
}
|
||||
return "(" + s + ")"
|
||||
case reflect.Array, reflect.Slice:
|
||||
return "a" + getSignature(t.Elem())
|
||||
return "a" + getSignature(t.Elem(), depth.EnterArray())
|
||||
case reflect.Map:
|
||||
if !isKeyType(t.Key()) {
|
||||
panic(InvalidTypeError{t})
|
||||
}
|
||||
return "a{" + getSignature(t.Key()) + getSignature(t.Elem()) + "}"
|
||||
return "a{" + getSignature(t.Key(), depth.EnterArray().EnterDictEntry()) + getSignature(t.Elem(), depth.EnterArray().EnterDictEntry()) + "}"
|
||||
case reflect.Interface:
|
||||
return "v"
|
||||
}
|
||||
|
|
@ -118,7 +129,7 @@ func ParseSignature(s string) (sig Signature, err error) {
|
|||
}
|
||||
sig.str = s
|
||||
for err == nil && len(s) != 0 {
|
||||
err, s = validSingle(s, 0)
|
||||
err, s = validSingle(s, &depthCounter{})
|
||||
}
|
||||
if err != nil {
|
||||
sig = Signature{""}
|
||||
|
|
@ -144,7 +155,7 @@ func (s Signature) Empty() bool {
|
|||
|
||||
// Single returns whether the signature represents a single, complete type.
|
||||
func (s Signature) Single() bool {
|
||||
err, r := validSingle(s.str, 0)
|
||||
err, r := validSingle(s.str, &depthCounter{})
|
||||
return err != nil && r == ""
|
||||
}
|
||||
|
||||
|
|
@ -164,15 +175,38 @@ func (e SignatureError) Error() string {
|
|||
return fmt.Sprintf("dbus: invalid signature: %q (%s)", e.Sig, e.Reason)
|
||||
}
|
||||
|
||||
type depthCounter struct {
|
||||
arrayDepth, structDepth, dictEntryDepth int
|
||||
}
|
||||
|
||||
func (cnt *depthCounter) Valid() bool {
|
||||
return cnt.arrayDepth <= 32 && cnt.structDepth <= 32 && cnt.dictEntryDepth <= 32
|
||||
}
|
||||
|
||||
func (cnt depthCounter) EnterArray() *depthCounter {
|
||||
cnt.arrayDepth++
|
||||
return &cnt
|
||||
}
|
||||
|
||||
func (cnt depthCounter) EnterStruct() *depthCounter {
|
||||
cnt.structDepth++
|
||||
return &cnt
|
||||
}
|
||||
|
||||
func (cnt depthCounter) EnterDictEntry() *depthCounter {
|
||||
cnt.dictEntryDepth++
|
||||
return &cnt
|
||||
}
|
||||
|
||||
// Try to read a single type from this string. If it was successful, err is nil
|
||||
// and rem is the remaining unparsed part. Otherwise, err is a non-nil
|
||||
// SignatureError and rem is "". depth is the current recursion depth which may
|
||||
// not be greater than 64 and should be given as 0 on the first call.
|
||||
func validSingle(s string, depth int) (err error, rem string) {
|
||||
func validSingle(s string, depth *depthCounter) (err error, rem string) {
|
||||
if s == "" {
|
||||
return SignatureError{Sig: s, Reason: "empty signature"}, ""
|
||||
}
|
||||
if depth > 64 {
|
||||
if !depth.Valid() {
|
||||
return SignatureError{Sig: s, Reason: "container nesting too deep"}, ""
|
||||
}
|
||||
switch s[0] {
|
||||
|
|
@ -187,10 +221,10 @@ func validSingle(s string, depth int) (err error, rem string) {
|
|||
i++
|
||||
rem = s[i+1:]
|
||||
s = s[2:i]
|
||||
if err, _ = validSingle(s[:1], depth+1); err != nil {
|
||||
if err, _ = validSingle(s[:1], depth.EnterArray().EnterDictEntry()); err != nil {
|
||||
return err, ""
|
||||
}
|
||||
err, nr := validSingle(s[1:], depth+1)
|
||||
err, nr := validSingle(s[1:], depth.EnterArray().EnterDictEntry())
|
||||
if err != nil {
|
||||
return err, ""
|
||||
}
|
||||
|
|
@ -199,7 +233,7 @@ func validSingle(s string, depth int) (err error, rem string) {
|
|||
}
|
||||
return nil, rem
|
||||
}
|
||||
return validSingle(s[1:], depth+1)
|
||||
return validSingle(s[1:], depth.EnterArray())
|
||||
case '(':
|
||||
i := findMatching(s, '(', ')')
|
||||
if i == -1 {
|
||||
|
|
@ -208,7 +242,7 @@ func validSingle(s string, depth int) (err error, rem string) {
|
|||
rem = s[i+1:]
|
||||
s = s[1:i]
|
||||
for err == nil && s != "" {
|
||||
err, s = validSingle(s, depth+1)
|
||||
err, s = validSingle(s, depth.EnterStruct())
|
||||
}
|
||||
if err != nil {
|
||||
rem = ""
|
||||
|
|
@ -236,7 +270,7 @@ func findMatching(s string, left, right rune) int {
|
|||
// typeFor returns the type of the given signature. It ignores any left over
|
||||
// characters and panics if s doesn't start with a valid type signature.
|
||||
func typeFor(s string) (t reflect.Type) {
|
||||
err, _ := validSingle(s, 0)
|
||||
err, _ := validSingle(s, &depthCounter{})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -41,10 +41,12 @@ func (t genericTransport) ReadMessage() (*Message, error) {
|
|||
}
|
||||
|
||||
func (t genericTransport) SendMessage(msg *Message) error {
|
||||
for _, v := range msg.Body {
|
||||
if _, ok := v.(UnixFD); ok {
|
||||
return errors.New("dbus: unix fd passing not enabled")
|
||||
}
|
||||
fds, err := msg.CountFds()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if fds != 0 {
|
||||
return errors.New("dbus: unix fd passing not enabled")
|
||||
}
|
||||
return msg.EncodeTo(t, nativeEndian)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -113,7 +113,7 @@ func (t *unixTransport) ReadMessage() (*Message, error) {
|
|||
if _, err := io.ReadFull(t.rdr, headerdata[4:]); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
dec := newDecoder(bytes.NewBuffer(headerdata), order)
|
||||
dec := newDecoder(bytes.NewBuffer(headerdata), order, make([]int, 0))
|
||||
dec.pos = 12
|
||||
vs, err := dec.Decode(Signature{"a(yv)"})
|
||||
if err != nil {
|
||||
|
|
@ -147,7 +147,7 @@ func (t *unixTransport) ReadMessage() (*Message, error) {
|
|||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
msg, err := DecodeMessage(bytes.NewBuffer(all))
|
||||
msg, err := DecodeMessageWithFDs(bytes.NewBuffer(all), fds)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
@ -179,21 +179,21 @@ func (t *unixTransport) ReadMessage() (*Message, error) {
|
|||
}
|
||||
|
||||
func (t *unixTransport) SendMessage(msg *Message) error {
|
||||
fds := make([]int, 0)
|
||||
for i, v := range msg.Body {
|
||||
if fd, ok := v.(UnixFD); ok {
|
||||
msg.Body[i] = UnixFDIndex(len(fds))
|
||||
fds = append(fds, int(fd))
|
||||
}
|
||||
fdcnt, err := msg.CountFds()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(fds) != 0 {
|
||||
if fdcnt != 0 {
|
||||
if !t.hasUnixFDs {
|
||||
return errors.New("dbus: unix fd passing not enabled")
|
||||
}
|
||||
msg.Headers[FieldUnixFDs] = MakeVariant(uint32(len(fds)))
|
||||
oob := syscall.UnixRights(fds...)
|
||||
msg.Headers[FieldUnixFDs] = MakeVariant(uint32(fdcnt))
|
||||
buf := new(bytes.Buffer)
|
||||
msg.EncodeTo(buf, nativeEndian)
|
||||
fds, err := msg.EncodeToWithFDs(buf, nativeEndian)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
oob := syscall.UnixRights(fds...)
|
||||
n, oobn, err := t.UnixConn.WriteMsgUnix(buf.Bytes(), oob, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
|
|
|
|||
14
common/vendor/github.com/godbus/dbus/v5/transport_unixcred_netbsd.go
generated
vendored
Normal file
14
common/vendor/github.com/godbus/dbus/v5/transport_unixcred_netbsd.go
generated
vendored
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
package dbus
|
||||
|
||||
import "io"
|
||||
|
||||
func (t *unixTransport) SendNullByte() error {
|
||||
n, _, err := t.UnixConn.WriteMsgUnix([]byte{0}, nil, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if n != 1 {
|
||||
return io.ErrShortWrite
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
|
@ -1,5 +1,7 @@
|
|||
# Go terminal/console support
|
||||
|
||||
[](https://pkg.go.dev/golang.org/x/term)
|
||||
|
||||
This repository provides Go terminal and console support packages.
|
||||
|
||||
## Download/Install
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
module golang.org/x/term
|
||||
|
||||
go 1.11
|
||||
go 1.17
|
||||
|
||||
require golang.org/x/sys v0.0.0-20201119102817-f84b799fce68
|
||||
require golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1
|
||||
|
|
|
|||
|
|
@ -1,2 +1,2 @@
|
|||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68 h1:nxC68pudNYkKU6jWhgrqdreuFiOQWj1Fs7T3VrH4Pjw=
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1 h1:SrN+KX8Art/Sf4HNj6Zcz06G7VEz+7w9tdXTPOZ7+l4=
|
||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
|
|
|
|||
|
|
@ -7,11 +7,11 @@
|
|||
//
|
||||
// Putting a terminal into raw mode is the most common requirement:
|
||||
//
|
||||
// oldState, err := terminal.MakeRaw(0)
|
||||
// oldState, err := term.MakeRaw(int(os.Stdin.Fd()))
|
||||
// if err != nil {
|
||||
// panic(err)
|
||||
// }
|
||||
// defer terminal.Restore(0, oldState)
|
||||
// defer term.Restore(int(os.Stdin.Fd()), oldState)
|
||||
package term
|
||||
|
||||
// State contains the state of a terminal.
|
||||
|
|
|
|||
|
|
@ -1,111 +0,0 @@
|
|||
// Copyright 2019 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package term
|
||||
|
||||
import (
|
||||
"io"
|
||||
"syscall"
|
||||
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
// State contains the state of a terminal.
|
||||
type state struct {
|
||||
termios unix.Termios
|
||||
}
|
||||
|
||||
func isTerminal(fd int) bool {
|
||||
_, err := unix.IoctlGetTermio(fd, unix.TCGETA)
|
||||
return err == nil
|
||||
}
|
||||
|
||||
func readPassword(fd int) ([]byte, error) {
|
||||
// see also: http://src.illumos.org/source/xref/illumos-gate/usr/src/lib/libast/common/uwin/getpass.c
|
||||
val, err := unix.IoctlGetTermios(fd, unix.TCGETS)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
oldState := *val
|
||||
|
||||
newState := oldState
|
||||
newState.Lflag &^= syscall.ECHO
|
||||
newState.Lflag |= syscall.ICANON | syscall.ISIG
|
||||
newState.Iflag |= syscall.ICRNL
|
||||
err = unix.IoctlSetTermios(fd, unix.TCSETS, &newState)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
defer unix.IoctlSetTermios(fd, unix.TCSETS, &oldState)
|
||||
|
||||
var buf [16]byte
|
||||
var ret []byte
|
||||
for {
|
||||
n, err := syscall.Read(fd, buf[:])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if n == 0 {
|
||||
if len(ret) == 0 {
|
||||
return nil, io.EOF
|
||||
}
|
||||
break
|
||||
}
|
||||
if buf[n-1] == '\n' {
|
||||
n--
|
||||
}
|
||||
ret = append(ret, buf[:n]...)
|
||||
if n < len(buf) {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
func makeRaw(fd int) (*State, error) {
|
||||
// see http://cr.illumos.org/~webrev/andy_js/1060/
|
||||
termios, err := unix.IoctlGetTermios(fd, unix.TCGETS)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
oldState := State{state{termios: *termios}}
|
||||
|
||||
termios.Iflag &^= unix.IGNBRK | unix.BRKINT | unix.PARMRK | unix.ISTRIP | unix.INLCR | unix.IGNCR | unix.ICRNL | unix.IXON
|
||||
termios.Oflag &^= unix.OPOST
|
||||
termios.Lflag &^= unix.ECHO | unix.ECHONL | unix.ICANON | unix.ISIG | unix.IEXTEN
|
||||
termios.Cflag &^= unix.CSIZE | unix.PARENB
|
||||
termios.Cflag |= unix.CS8
|
||||
termios.Cc[unix.VMIN] = 1
|
||||
termios.Cc[unix.VTIME] = 0
|
||||
|
||||
if err := unix.IoctlSetTermios(fd, unix.TCSETS, termios); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &oldState, nil
|
||||
}
|
||||
|
||||
func restore(fd int, oldState *State) error {
|
||||
return unix.IoctlSetTermios(fd, unix.TCSETS, &oldState.termios)
|
||||
}
|
||||
|
||||
func getState(fd int) (*State, error) {
|
||||
termios, err := unix.IoctlGetTermios(fd, unix.TCGETS)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &State{state{termios: *termios}}, nil
|
||||
}
|
||||
|
||||
func getSize(fd int) (width, height int, err error) {
|
||||
ws, err := unix.IoctlGetWinsize(fd, unix.TIOCGWINSZ)
|
||||
if err != nil {
|
||||
return 0, 0, err
|
||||
}
|
||||
return int(ws.Col), int(ws.Row), nil
|
||||
}
|
||||
|
|
@ -2,7 +2,8 @@
|
|||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// +build aix darwin dragonfly freebsd linux netbsd openbsd zos
|
||||
//go:build aix || darwin || dragonfly || freebsd || linux || netbsd || openbsd || solaris || zos
|
||||
// +build aix darwin dragonfly freebsd linux netbsd openbsd solaris zos
|
||||
|
||||
package term
|
||||
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build darwin || dragonfly || freebsd || netbsd || openbsd
|
||||
// +build darwin dragonfly freebsd netbsd openbsd
|
||||
|
||||
package term
|
||||
|
|
|
|||
|
|
@ -1,10 +0,0 @@
|
|||
// Copyright 2019 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package term
|
||||
|
||||
import "golang.org/x/sys/unix"
|
||||
|
||||
const ioctlReadTermios = unix.TCGETS
|
||||
const ioctlWriteTermios = unix.TCSETS
|
||||
|
|
@ -1,7 +1,10 @@
|
|||
// Copyright 2019 The Go Authors. All rights reserved.
|
||||
// Copyright 2021 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build aix || linux || solaris || zos
|
||||
// +build aix linux solaris zos
|
||||
|
||||
package term
|
||||
|
||||
import "golang.org/x/sys/unix"
|
||||
|
|
@ -1,10 +0,0 @@
|
|||
// Copyright 2020 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package term
|
||||
|
||||
import "golang.org/x/sys/unix"
|
||||
|
||||
const ioctlReadTermios = unix.TCGETS
|
||||
const ioctlWriteTermios = unix.TCSETS
|
||||
|
|
@ -2,6 +2,7 @@
|
|||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build !aix && !darwin && !dragonfly && !freebsd && !linux && !netbsd && !openbsd && !zos && !windows && !solaris && !plan9
|
||||
// +build !aix,!darwin,!dragonfly,!freebsd,!linux,!netbsd,!openbsd,!zos,!windows,!solaris,!plan9
|
||||
|
||||
package term
|
||||
|
|
|
|||
|
|
@ -163,8 +163,10 @@ github.com/containers/storage/pkg/truncindex
|
|||
github.com/containers/storage/pkg/unshare
|
||||
github.com/containers/storage/types
|
||||
# github.com/coreos/go-systemd/v22 v22.3.2
|
||||
## explicit
|
||||
github.com/coreos/go-systemd/v22/dbus
|
||||
# github.com/cyphar/filepath-securejoin v0.2.2
|
||||
# github.com/cyphar/filepath-securejoin v0.2.3
|
||||
## explicit
|
||||
github.com/cyphar/filepath-securejoin
|
||||
# github.com/davecgh/go-spew v1.1.1
|
||||
github.com/davecgh/go-spew/spew
|
||||
|
|
@ -217,12 +219,14 @@ github.com/docker/go-metrics
|
|||
# github.com/docker/go-units v0.4.0
|
||||
## explicit
|
||||
github.com/docker/go-units
|
||||
# github.com/fsnotify/fsnotify v1.4.9
|
||||
# github.com/fsnotify/fsnotify v1.5.1
|
||||
## explicit
|
||||
github.com/fsnotify/fsnotify
|
||||
# github.com/ghodss/yaml v1.0.0
|
||||
## explicit
|
||||
github.com/ghodss/yaml
|
||||
# github.com/godbus/dbus/v5 v5.0.4
|
||||
# github.com/godbus/dbus/v5 v5.0.6
|
||||
## explicit
|
||||
github.com/godbus/dbus/v5
|
||||
# github.com/gogo/protobuf v1.3.2
|
||||
github.com/gogo/protobuf/gogoproto
|
||||
|
|
@ -287,7 +291,7 @@ github.com/mistifyio/go-zfs
|
|||
github.com/mitchellh/mapstructure
|
||||
# github.com/moby/sys/mountinfo v0.5.0
|
||||
github.com/moby/sys/mountinfo
|
||||
# github.com/moby/term v0.0.0-20201216013528-df9cb8a40635
|
||||
# github.com/moby/term v0.0.0-20210619224110-3f7ff695adc6
|
||||
## explicit
|
||||
# github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd
|
||||
github.com/modern-go/concurrent
|
||||
|
|
@ -444,7 +448,8 @@ go.opencensus.io/internal
|
|||
go.opencensus.io/trace
|
||||
go.opencensus.io/trace/internal
|
||||
go.opencensus.io/trace/tracestate
|
||||
# golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2
|
||||
# golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97
|
||||
## explicit
|
||||
golang.org/x/crypto/cast5
|
||||
golang.org/x/crypto/ed25519
|
||||
golang.org/x/crypto/ed25519/internal/edwards25519
|
||||
|
|
@ -479,7 +484,7 @@ golang.org/x/sys/internal/unsafeheader
|
|||
golang.org/x/sys/plan9
|
||||
golang.org/x/sys/unix
|
||||
golang.org/x/sys/windows
|
||||
# golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1
|
||||
# golang.org/x/term v0.0.0-20210615171337-6886f2dfbf5b
|
||||
## explicit
|
||||
golang.org/x/term
|
||||
# golang.org/x/text v0.3.7
|
||||
|
|
|
|||
Loading…
Reference in New Issue