Merge pull request #24447 from Luap99/vendor

vendor latest c/{buildah,common,image,storage}
This commit is contained in:
openshift-merge-bot[bot] 2024-11-07 13:20:46 +00:00 • committed by GitHub
commit 6598a36e6b
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
158 changed files with 6405 additions and 3079 deletions

27
go.mod
View file

@ -13,15 +13,15 @@ require (
github.com/checkpoint-restore/checkpointctl v1.3.0
github.com/checkpoint-restore/go-criu/v7 v7.2.0
github.com/containernetworking/plugins v1.5.1
github.com/containers/buildah v1.37.1-0.20241018144937-2551c8f3d110
github.com/containers/common v0.60.1-0.20241018183244-7e6f2b4d6de7
github.com/containers/buildah v1.37.1-0.20241104213057-0dc5f958934a
github.com/containers/common v0.60.1-0.20241106101802-8130e31c0832
github.com/containers/conmon v2.0.20+incompatible
github.com/containers/gvisor-tap-vsock v0.8.0
github.com/containers/image/v5 v5.32.3-0.20241016192323-a66152c1cdf6
github.com/containers/image/v5 v5.32.3-0.20241105183637-59417aed3db1
github.com/containers/libhvee v0.7.1
github.com/containers/ocicrypt v1.2.0
github.com/containers/psgo v1.9.0
github.com/containers/storage v1.55.1-0.20241017155235-4db236377c55
github.com/containers/storage v1.55.2-0.20241104201357-ad5f2a48e9e9
github.com/containers/winquit v1.1.0
github.com/coreos/go-systemd/v22 v22.5.1-0.20231103132048-7d375ecc2b09
github.com/coreos/stream-metadata-go v0.4.4
@ -89,7 +89,7 @@ require (
require (
dario.cat/mergo v1.0.1 // indirect
github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 // indirect
github.com/Microsoft/hcsshim v0.12.7 // indirect
github.com/Microsoft/hcsshim v0.12.9 // indirect
github.com/VividCortex/ewma v1.2.0 // indirect
github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d // indirect
github.com/aead/serpent v0.0.0-20160714141033-fba169763ea6 // indirect
@ -99,7 +99,8 @@ require (
github.com/chenzhuoyu/iasm v0.9.1 // indirect
github.com/chzyer/readline v1.5.1 // indirect
github.com/containerd/cgroups/v3 v3.0.3 // indirect
github.com/containerd/errdefs v0.1.0 // indirect
github.com/containerd/errdefs v0.3.0 // indirect
github.com/containerd/errdefs/pkg v0.3.0 // indirect
github.com/containerd/log v0.1.0 // indirect
github.com/containerd/platforms v0.2.1 // indirect
github.com/containerd/stargz-snapshotter/estargz v0.15.1 // indirect
@ -117,7 +118,7 @@ require (
github.com/docker/docker-credential-helpers v0.8.2 // indirect
github.com/ebitengine/purego v0.8.1 // indirect
github.com/felixge/httpsnoop v1.0.4 // indirect
github.com/fsnotify/fsnotify v1.7.0 // indirect
github.com/fsnotify/fsnotify v1.8.0 // indirect
github.com/fsouza/go-dockerclient v1.12.0 // indirect
github.com/gabriel-vasile/mimetype v1.4.3 // indirect
github.com/gin-contrib/sse v0.1.0 // indirect
@ -170,11 +171,11 @@ require (
github.com/miekg/pkcs11 v1.1.1 // indirect
github.com/mistifyio/go-zfs/v3 v3.0.1 // indirect
github.com/mitchellh/mapstructure v1.5.0 // indirect
github.com/moby/buildkit v0.16.0 // indirect
github.com/moby/buildkit v0.17.0 // indirect
github.com/moby/docker-image-spec v1.3.1 // indirect
github.com/moby/patternmatcher v0.6.0 // indirect
github.com/moby/sys/mountinfo v0.7.2 // indirect
github.com/moby/sys/sequential v0.5.0 // indirect
github.com/moby/sys/sequential v0.6.0 // indirect
github.com/moby/sys/userns v0.1.0 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
@ -184,7 +185,8 @@ require (
github.com/ostreedev/ostree-go v0.0.0-20210805093236-719684c64e4f // indirect
github.com/pelletier/go-toml/v2 v2.2.2 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/pkg/sftp v1.13.6 // indirect
github.com/pkg/sftp v1.13.7 // indirect
github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 // indirect
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // indirect
github.com/proglottis/gpgme v0.1.3 // indirect
@ -195,6 +197,7 @@ require (
github.com/sigstore/fulcio v1.6.4 // indirect
github.com/sigstore/rekor v1.3.6 // indirect
github.com/sigstore/sigstore v1.8.9 // indirect
github.com/skeema/knownhosts v1.3.0 // indirect
github.com/skratchdot/open-golang v0.0.0-20200116055534-eef842397966 // indirect
github.com/stefanberger/go-pkcs11uri v0.0.0-20230803200340-78284954bff6 // indirect
github.com/sylabs/sif/v2 v2.19.1 // indirect
@ -222,8 +225,8 @@ require (
golang.org/x/oauth2 v0.23.0 // indirect
golang.org/x/time v0.6.0 // indirect
golang.org/x/tools v0.26.0 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20240823204242-4ba0660f739c // indirect
google.golang.org/grpc v1.66.0 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20240903143218-8af14fe29dc1 // indirect
google.golang.org/grpc v1.67.0 // indirect
gopkg.in/natefinch/lumberjack.v2 v2.2.1 // indirect
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 // indirect
tags.cncf.io/container-device-interface/specs-go v0.8.0 // indirect

61
go.sum
View file

@ -12,8 +12,8 @@ github.com/BurntSushi/toml v1.4.0 h1:kuoIxZQy2WRRk1pttg9asf+WVv6tWQuBNVmK8+nqPr0
github.com/BurntSushi/toml v1.4.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho=
github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY=
github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU=
github.com/Microsoft/hcsshim v0.12.7 h1:MP6R1spmjxTE4EU4J3YsrTxn8CjvN9qwjTKJXldFaRg=
github.com/Microsoft/hcsshim v0.12.7/go.mod h1:HPbAuJ9BvQYYZbB4yEQcyGIsTP5L4yHKeO9XO149AEM=
github.com/Microsoft/hcsshim v0.12.9 h1:2zJy5KA+l0loz1HzEGqyNnjd3fyZA31ZBCGKacp6lLg=
github.com/Microsoft/hcsshim v0.12.9/go.mod h1:fJ0gkFAna6ukt0bLdKB8djt4XIJhF/vEPuoIWYVvZ8Y=
github.com/Netflix/go-expect v0.0.0-20220104043353-73e0943537d2 h1:+vx7roKuyA63nhn5WAunQHLTznkw5W8b1Xc0dNjp83s=
github.com/Netflix/go-expect v0.0.0-20220104043353-73e0943537d2/go.mod h1:HBCaDeC1lPdgDeDbhX8XFpy1jqjK0IBG8W5K+xYqA0w=
github.com/VividCortex/ewma v1.2.0 h1:f58SaIzcDXrSy3kWaHNvuJgJ3Nmz59Zji6XoJR/q1ow=
@ -63,8 +63,10 @@ github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDk
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
github.com/containerd/cgroups/v3 v3.0.3 h1:S5ByHZ/h9PMe5IOQoN7E+nMc2UcLEM/V48DGDJ9kip0=
github.com/containerd/cgroups/v3 v3.0.3/go.mod h1:8HBe7V3aWGLFPd/k03swSIsGjZhHI2WzJmticMgVuz0=
github.com/containerd/errdefs v0.1.0 h1:m0wCRBiu1WJT/Fr+iOoQHMQS/eP5myQ8lCv4Dz5ZURM=
github.com/containerd/errdefs v0.1.0/go.mod h1:YgWiiHtLmSeBrvpw+UfPijzbLaB77mEG1WwJTDETIV0=
github.com/containerd/errdefs v0.3.0 h1:FSZgGOeK4yuT/+DnF07/Olde/q4KBoMsaamhXxIMDp4=
github.com/containerd/errdefs v0.3.0/go.mod h1:+YBYIdtsnF4Iw6nWZhJcqGSg/dwvV7tyJ/kCkyJ2k+M=
github.com/containerd/errdefs/pkg v0.3.0 h1:9IKJ06FvyNlexW690DXuQNx2KA2cUJXx151Xdx3ZPPE=
github.com/containerd/errdefs/pkg v0.3.0/go.mod h1:NJw6s9HwNuRhnjJhM7pylWwMyAkmCQvQ4GpJHEqRLVk=
github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I=
github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo=
github.com/containerd/platforms v0.2.1 h1:zvwtM3rz2YHPQsF2CHYM8+KtB5dvhISiXh5ZpSBQv6A=
@ -77,16 +79,16 @@ github.com/containernetworking/cni v1.2.3 h1:hhOcjNVUQTnzdRJ6alC5XF+wd9mfGIUaj8F
github.com/containernetworking/cni v1.2.3/go.mod h1:DuLgF+aPd3DzcTQTtp/Nvl1Kim23oFKdm2okJzBQA5M=
github.com/containernetworking/plugins v1.5.1 h1:T5ji+LPYjjgW0QM+KyrigZbLsZ8jaX+E5J/EcKOE4gQ=
github.com/containernetworking/plugins v1.5.1/go.mod h1:MIQfgMayGuHYs0XdNudf31cLLAC+i242hNm6KuDGqCM=
github.com/containers/buildah v1.37.1-0.20241018144937-2551c8f3d110 h1:YcrjUM1CwDTEnaPHgpVmjid/R3zAVXRRHcgknQsajlI=
github.com/containers/buildah v1.37.1-0.20241018144937-2551c8f3d110/go.mod h1:SVyERMThmMXGTdle/9MdRuX2Ae7eVY9qDVartYXIx7E=
github.com/containers/common v0.60.1-0.20241018183244-7e6f2b4d6de7 h1:EFzq3sjwy0vBr9RoZPzTbtoGZR4hoZsmgxuIs5Uc5FA=
github.com/containers/common v0.60.1-0.20241018183244-7e6f2b4d6de7/go.mod h1:T8vpUWd7AspK7CMELf/c+NCZB6bKrRkLriRCspdFGyQ=
github.com/containers/buildah v1.37.1-0.20241104213057-0dc5f958934a h1:0ZkxeT5b46pyfvwnplL3yxI4XscQXFoXWVpn619qIYA=
github.com/containers/buildah v1.37.1-0.20241104213057-0dc5f958934a/go.mod h1:zyzg/OC40RZUl6NxvPkBHjaPHKFNGsAMyd8JrbseNwE=
github.com/containers/common v0.60.1-0.20241106101802-8130e31c0832 h1:bu0QHPZns2JbbaEArh+Khs9ajAoX8/biSdBYWAD+/Ec=
github.com/containers/common v0.60.1-0.20241106101802-8130e31c0832/go.mod h1:7MOGFZ12UTBYSPxvQpBBJHTbHho0qizdMR6MlDZwGBA=
github.com/containers/conmon v2.0.20+incompatible h1:YbCVSFSCqFjjVwHTPINGdMX1F6JXHGTUje2ZYobNrkg=
github.com/containers/conmon v2.0.20+incompatible/go.mod h1:hgwZ2mtuDrppv78a/cOBNiCm6O0UMWGx1mu7P00nu5I=
github.com/containers/gvisor-tap-vsock v0.8.0 h1:Z8ZEWb+Lio0d+lXexONdUWT4rm9lF91vH0g3ARnMy7o=
github.com/containers/gvisor-tap-vsock v0.8.0/go.mod h1:LVwnMiNvhxyGfhaMEQcXKJhNnN4h8woB9U3wf8rYOPc=
github.com/containers/image/v5 v5.32.3-0.20241016192323-a66152c1cdf6 h1:kDsMVMhEFmWFLN6QEn0ul0MbpXCxLiIL5pqxADOqB8g=
github.com/containers/image/v5 v5.32.3-0.20241016192323-a66152c1cdf6/go.mod h1:Ulwf/jQO4757C/uOJyNiZ10dRiXRwVnyhF9wYFno3GQ=
github.com/containers/image/v5 v5.32.3-0.20241105183637-59417aed3db1 h1:QOxFuqs21puYp9EnMaAEMgygtSORUYKn0felPS4rXa0=
github.com/containers/image/v5 v5.32.3-0.20241105183637-59417aed3db1/go.mod h1:qsONoo3ZvAnrwa2+49bY6m3ewtJO5+A1AaELCtGuDKc=
github.com/containers/libhvee v0.7.1 h1:dWGF5GLq9DZvXo3P8aDp3cNieL5eCaSell4UmeA/jY4=
github.com/containers/libhvee v0.7.1/go.mod h1:fRKB3AyIqHMvq6xaeYhTpckM2cdoq0oecolyoiuLP7M=
github.com/containers/libtrust v0.0.0-20230121012942-c1716e8a8d01 h1:Qzk5C6cYglewc+UyGf6lc8Mj2UaPTHy/iF2De0/77CA=
@ -97,8 +99,8 @@ github.com/containers/ocicrypt v1.2.0 h1:X14EgRK3xNFvJEfI5O4Qn4T3E25ANudSOZz/sir
github.com/containers/ocicrypt v1.2.0/go.mod h1:ZNviigQajtdlxIZGibvblVuIFBKIuUI2M0QM12SD31U=
github.com/containers/psgo v1.9.0 h1:eJ74jzSaCHnWt26OlKZROSyUyRcGDf+gYBdXnxrMW4g=
github.com/containers/psgo v1.9.0/go.mod h1:0YoluUm43Mz2UnBIh1P+6V6NWcbpTL5uRtXyOcH0B5A=
github.com/containers/storage v1.55.1-0.20241017155235-4db236377c55 h1:lqNa07muv5Gkfyw8aivrLwy8cCZgewHN2OxB0cuNNyY=
github.com/containers/storage v1.55.1-0.20241017155235-4db236377c55/go.mod h1:iq56tOFXnj8kA8DAytN28fhUm77eKLWrashQrImaqBs=
github.com/containers/storage v1.55.2-0.20241104201357-ad5f2a48e9e9 h1:qB+KgM08F5GEGzdzrWLUHs41NU6d/+TUMEP2xMeyjGs=
github.com/containers/storage v1.55.2-0.20241104201357-ad5f2a48e9e9/go.mod h1:c6WKowcAlED/DkWGNuL9bvGYqIWCVy7isRMdCSKWNjk=
github.com/containers/winquit v1.1.0 h1:jArun04BNDQvt2W0Y78kh9TazN2EIEMG5Im6/JY7+pE=
github.com/containers/winquit v1.1.0/go.mod h1:PsPeZlnbkmGGIToMPHF1zhWjBUkd8aHjMOr/vFcPxw8=
github.com/coreos/go-oidc/v3 v3.11.0 h1:Ia3MxdwpSw702YW0xgfmP1GVCMA9aEFWu12XUZ3/OtI=
@ -161,8 +163,8 @@ github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4Nij
github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw=
github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA=
github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM=
github.com/fsnotify/fsnotify v1.8.0 h1:dAwr6QBTBZIkG8roQaJjGof0pp0EeF+tNV7YBP3F/8M=
github.com/fsnotify/fsnotify v1.8.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
github.com/fsouza/go-dockerclient v1.12.0 h1:S2f2crEUbBNCFiF06kR/GvioEB8EMsb3Td/bpawD+aU=
github.com/fsouza/go-dockerclient v1.12.0/go.mod h1:YWUtjg8japrqD/80L98nTtCoxQFp5B5wrSsnyeB5lFo=
github.com/gabriel-vasile/mimetype v1.4.3 h1:in2uUcidCuFcDKtdcBxlR0rJ1+fsokWf+uqxgUFjbI0=
@ -359,8 +361,8 @@ github.com/mistifyio/go-zfs/v3 v3.0.1 h1:YaoXgBePoMA12+S1u/ddkv+QqxcfiZK4prI6HPn
github.com/mistifyio/go-zfs/v3 v3.0.1/go.mod h1:CzVgeB0RvF2EGzQnytKVvVSDwmKJXxkOTUGbNrTja/k=
github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
github.com/moby/buildkit v0.16.0 h1:wOVBj1o5YNVad/txPQNXUXdelm7Hs/i0PUFjzbK0VKE=
github.com/moby/buildkit v0.16.0/go.mod h1:Xqx/5GlrqE1yIRORk0NSCVDFpQAU1WjlT6KHYZdisIQ=
github.com/moby/buildkit v0.17.0 h1:ZA/4AxwBbve1f3ZaNNJQiCBtTV62R6YweWNwq4A+sTc=
github.com/moby/buildkit v0.17.0/go.mod h1:ru8NFyDHD8HbuKaLXJIjK9nr3x6FZR+IWjtF07S+wdM=
github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0=
github.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo=
github.com/moby/patternmatcher v0.6.0 h1:GmP9lR19aU5GqSSFko+5pRqHi+Ohk1O69aFiKkVGiPk=
@ -369,8 +371,8 @@ github.com/moby/sys/capability v0.3.0 h1:kEP+y6te0gEXIaeQhIi0s7vKs/w0RPoH1qPa6jR
github.com/moby/sys/capability v0.3.0/go.mod h1:4g9IK291rVkms3LKCDOoYlnV8xKwoDTpIrNEE35Wq0I=
github.com/moby/sys/mountinfo v0.7.2 h1:1shs6aH5s4o5H2zQLn796ADW1wMrIwHsyJ2v9KouLrg=
github.com/moby/sys/mountinfo v0.7.2/go.mod h1:1YOa8w8Ih7uW0wALDUgT1dTTSBrZ+HiBLGws92L2RU4=
github.com/moby/sys/sequential v0.5.0 h1:OPvI35Lzn9K04PBbCLW0g4LcFAJgHsvXsRyewg5lXtc=
github.com/moby/sys/sequential v0.5.0/go.mod h1:tH2cOOs5V9MlPiXcQzRC+eEyab644PWKGRYaaV5ZZlo=
github.com/moby/sys/sequential v0.6.0 h1:qrx7XFUd/5DxtqcoH1h438hF5TmOvzC/lspjy7zgvCU=
github.com/moby/sys/sequential v0.6.0/go.mod h1:uyv8EUTrca5PnDsdMGXhZe6CCe8U/UiTWd+lL+7b/Ko=
github.com/moby/sys/user v0.3.0 h1:9ni5DlcW5an3SvRSx4MouotOygvzaXbaSrc/wGDFWPo=
github.com/moby/sys/user v0.3.0/go.mod h1:bG+tYYYJgaMtRKgEmuueC0hJEAZWwtIbZTB+85uoHjs=
github.com/moby/sys/userns v0.1.0 h1:tVLXkFOxVu9A64/yh59slHVv9ahO9UIev4JZusOLG/g=
@ -418,8 +420,10 @@ github.com/pierrec/lz4/v4 v4.1.21 h1:yOVMLb6qSIDP67pl/5F7RepeKYu/VmTyEXvuMI5d9mQ
github.com/pierrec/lz4/v4 v4.1.21/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/sftp v1.13.6 h1:JFZT4XbOU7l77xGSpOdW+pwIMqP044IyjXX6FGyEKFo=
github.com/pkg/sftp v1.13.6/go.mod h1:tz1ryNURKu77RL+GuCzmoJYxQczL3wLNNpPWagdg4Qk=
github.com/pkg/sftp v1.13.7 h1:uv+I3nNJvlKZIQGSr8JVQLNHFU9YhhNpvC14Y6KgmSM=
github.com/pkg/sftp v1.13.7/go.mod h1:KMKI0t3T6hfA+lTR/ssZdunHo+uwq7ghoN09/FSu3DY=
github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 h1:GFCKgmp0tecUJ0sJuv4pzYCqS9+RGSn52M3FUwPs+uo=
github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10/go.mod h1:t/avpk3KcrXxUnYOhZhMXJlSEyie6gQbtLq5NM3loB8=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
@ -464,6 +468,8 @@ github.com/sigstore/sigstore v1.8.9 h1:NiUZIVWywgYuVTxXmRoTT4O4QAGiTEKup4N1wdxFa
github.com/sigstore/sigstore v1.8.9/go.mod h1:d9ZAbNDs8JJfxJrYmulaTazU3Pwr8uLL9+mii4BNR3w=
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
github.com/skeema/knownhosts v1.3.0 h1:AM+y0rI04VksttfwjkSTNQorvGqmwATnvnAHpSgc0LY=
github.com/skeema/knownhosts v1.3.0/go.mod h1:sPINvnADmT/qYH1kfv+ePMmOBTH6Tbl7b5LvTDjFK7M=
github.com/skratchdot/open-golang v0.0.0-20200116055534-eef842397966 h1:JIAuq3EEf9cgbU6AtGPK4CTG3Zf6CKMNqf0MHTggAUA=
github.com/skratchdot/open-golang v0.0.0-20200116055534-eef842397966/go.mod h1:sUM3LWHvSMaG192sy56D9F7CNvL7jUJVXoqM1QKLnog=
github.com/spf13/cobra v1.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM=
@ -573,7 +579,7 @@ golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACk
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.1.0/go.mod h1:RecgLatLF4+eUMCP1PoPZQb+cVrJcOPbHkTkbkB9sbw=
golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4=
golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
golang.org/x/crypto v0.28.0 h1:GBDwsMXVQi34v5CCYUm2jkJvu4cbtru2U4TN2PSyQnw=
golang.org/x/crypto v0.28.0/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7U=
@ -602,7 +608,6 @@ golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwY
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco=
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
golang.org/x/net v0.30.0 h1:AcW1SDZMkb8IpzCdQUaIq2sP4sZ4zw+55h6ynffypl4=
@ -646,21 +651,21 @@ golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo=
golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
golang.org/x/term v0.15.0/go.mod h1:BDl952bC7+uMoWR75FIrCDx79TPU9oHkTZ9yRbYOrX0=
golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk=
golang.org/x/term v0.25.0 h1:WtHI/ltw4NvSUig5KARz9h521QvRC8RmF/cuYqifU24=
golang.org/x/term v0.25.0/go.mod h1:RPyXicDX+6vLxogjjRxjgD2TKtmAO6NZBsBRfrOLu7M=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
@ -693,15 +698,15 @@ google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEY
google.golang.org/genproto v0.0.0-20240823204242-4ba0660f739c h1:TYOEhrQMrNDTAd2rX9m+WgGr8Ku6YNuj1D7OX6rWSok=
google.golang.org/genproto/googleapis/api v0.0.0-20240814211410-ddb44dafa142 h1:wKguEg1hsxI2/L3hUYrpo1RVi48K+uTyzKqprwLXsb8=
google.golang.org/genproto/googleapis/api v0.0.0-20240814211410-ddb44dafa142/go.mod h1:d6be+8HhtEtucleCbxpPW9PA9XwISACu8nvpPqF0BVo=
google.golang.org/genproto/googleapis/rpc v0.0.0-20240823204242-4ba0660f739c h1:Kqjm4WpoWvwhMPcrAczoTyMySQmYa9Wy2iL6Con4zn8=
google.golang.org/genproto/googleapis/rpc v0.0.0-20240823204242-4ba0660f739c/go.mod h1:UqMtugtsSgubUsoxbuAoiCXvqvErP7Gf0so0mK9tHxU=
google.golang.org/genproto/googleapis/rpc v0.0.0-20240903143218-8af14fe29dc1 h1:pPJltXNxVzT4pK9yD8vR9X75DaWYYmLGMsEvBfFQZzQ=
google.golang.org/genproto/googleapis/rpc v0.0.0-20240903143218-8af14fe29dc1/go.mod h1:UqMtugtsSgubUsoxbuAoiCXvqvErP7Gf0so0mK9tHxU=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc=
google.golang.org/grpc v1.66.0 h1:DibZuoBznOxbDQxRINckZcUvnCEvrW9pcWIE2yF9r1c=
google.golang.org/grpc v1.66.0/go.mod h1:s3/l6xSSCURdVfAnL+TqCNMyTDAGN6+lZeVxnZR128Y=
google.golang.org/grpc v1.67.0 h1:IdH9y6PF5MPSdAntIcpjQ+tXO41pcQsfZV2RxtQgVcw=
google.golang.org/grpc v1.67.0/go.mod h1:1gLDyUQU7CTLJI90u3nXZ9ekeghjeM7pTDZlqFNg2AA=
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=

View file

@ -177,11 +177,13 @@ if [[ -n $do_checkout ]]; then
# the set of patches
(set -x;git tag $BASE_TAG)
# Build buildah and the copy helper
# Build buildah and the copy and inet helpers
failhint="error building buildah. This should never happen."
(set -x;make bin/buildah)
failhint="error building buildah's copy helper. This should never happen."
(set -x;make bin/copy)
failhint="error building buildah's inet helper. This should never happen."
(set -x;make bin/inet)
# The upcoming patch may fail. Before we try it, create a helper script
# for a developer to push a new set of diffs to podman-land.
@ -259,5 +261,6 @@ review the test failure and double-check your changes.
REMOTE=$REMOTE \
BUILDAH_BINARY=$(pwd)/bin/buildah \
COPY_BINARY=$(pwd)/bin/copy \
INET_BINARY=$(pwd)/bin/inet \
bats "${bats_filter[@]}" tests/bud.bats)
fi

View file

@ -534,6 +534,11 @@ json-file | f
expect="$output"
TZ=Pacific/Chatham run_podman run --rm --tz=local $IMAGE date -Iseconds -r $testfile
is "$output" "$expect" "podman run with --tz=local, matches host"
# Force a TZDIR env as local should not try to use the TZDIR at all, #23550.
# This used to fail with: stat /usr/share/zoneinfo/local: no such file or directory.
TZDIR=/usr/share/zoneinfo run_podman run --rm --tz=local $IMAGE date -Iseconds -r $testfile
is "$output" "$expect" "podman run with --tz=local ignored TZDIR"
}
# bats test_tags=ci:parallel

View file

@ -29,7 +29,7 @@ const (
)
func (es EndpointState) String() string {
return [...]string{"Uninitialized", "Attached", "AttachedSharing", "Detached", "Degraded", "Destroyed"}[es]
return [...]string{"Uninitialized", "Created", "Attached", "AttachedSharing", "Detached", "Degraded", "Destroyed"}[es]
}
// HNSEndpoint represents a network endpoint in HNS

View file

@ -188,7 +188,7 @@ func Open(ctx context.Context, options *Options) (_ *JobObject, err error) {
return nil, winapi.RtlNtStatusToDosError(status)
}
} else {
jobHandle, err = winapi.OpenJobObject(winapi.JOB_OBJECT_ALL_ACCESS, 0, unicodeJobName.Buffer)
jobHandle, err = winapi.OpenJobObject(winapi.JOB_OBJECT_ALL_ACCESS, false, unicodeJobName.Buffer)
if err != nil {
return nil, err
}
@ -523,12 +523,9 @@ func (job *JobObject) ApplyFileBinding(root, target string, readOnly bool) error
func isJobSilo(h windows.Handle) bool {
// None of the information from the structure that this info class expects will be used, this is just used as
// the call will fail if the job hasn't been upgraded to a silo so we can use this to tell when we open a job
// if it's a silo or not. Because none of the info matters simply define a dummy struct with the size that the call
// expects which is 16 bytes.
type isSiloObj struct {
_ [16]byte
}
var siloInfo isSiloObj
// if it's a silo or not. We still need to define the struct layout as expected by Win32, else the struct
// alignment might be different and the call will fail.
var siloInfo winapi.SILOOBJECT_BASIC_INFORMATION
err := winapi.QueryInformationJobObject(
h,
winapi.JobObjectSiloBasicInformation,

View file

@ -6,7 +6,7 @@ import (
"net"
"os"
"github.com/containerd/errdefs"
errdefs "github.com/containerd/errdefs/pkg/errgrpc"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
)

View file

@ -28,7 +28,7 @@ const (
// https://docs.microsoft.com/en-us/windows/win32/procthread/job-object-security-and-access-rights
const (
JOB_OBJECT_QUERY = 0x0004
JOB_OBJECT_ALL_ACCESS = 0x1F001F
JOB_OBJECT_ALL_ACCESS = 0x1F003F
)
// IO limit flags
@ -160,6 +160,21 @@ type JOBOBJECT_ASSOCIATE_COMPLETION_PORT struct {
CompletionPort windows.Handle
}
// typedef struct _SILOOBJECT_BASIC_INFORMATION {
// DWORD SiloId;
// DWORD SiloParentId;
// DWORD NumberOfProcesses;
// BOOLEAN IsInServerSilo;
// BYTE Reserved[3];
// } SILOOBJECT_BASIC_INFORMATION, *PSILOOBJECT_BASIC_INFORMATION;
type SILOOBJECT_BASIC_INFORMATION struct {
SiloID uint32
SiloParentID uint32
NumberOfProcesses uint32
IsInServerSilo bool
Reserved [3]uint8
}
// BOOL IsProcessInJob(
// HANDLE ProcessHandle,
// HANDLE JobHandle,
@ -184,7 +199,7 @@ type JOBOBJECT_ASSOCIATE_COMPLETION_PORT struct {
// LPCWSTR lpName
// );
//
//sys OpenJobObject(desiredAccess uint32, inheritHandle int32, lpName *uint16) (handle windows.Handle, err error) = kernel32.OpenJobObjectW
//sys OpenJobObject(desiredAccess uint32, inheritHandle bool, lpName *uint16) (handle windows.Handle, err error) = kernel32.OpenJobObjectW
// DWORD SetIoRateControlInformationJobObject(
// HANDLE hJob,

View file

@ -470,8 +470,12 @@ func LocalFree(ptr uintptr) {
return
}
func OpenJobObject(desiredAccess uint32, inheritHandle int32, lpName *uint16) (handle windows.Handle, err error) {
r0, _, e1 := syscall.SyscallN(procOpenJobObjectW.Addr(), uintptr(desiredAccess), uintptr(inheritHandle), uintptr(unsafe.Pointer(lpName)))
func OpenJobObject(desiredAccess uint32, inheritHandle bool, lpName *uint16) (handle windows.Handle, err error) {
var _p0 uint32
if inheritHandle {
_p0 = 1
}
r0, _, e1 := syscall.SyscallN(procOpenJobObjectW.Addr(), uintptr(desiredAccess), uintptr(_p0), uintptr(unsafe.Pointer(lpName)))
handle = windows.Handle(r0)
if handle == 0 {
err = errnoErr(e1)

View file

@ -21,9 +21,6 @@
//
// To detect an error class, use the IsXXX functions to tell whether an error
// is of a certain type.
//
// The functions ToGRPC and FromGRPC can be used to map server-side and
// client-side errors to the correct types.
package errdefs
import (
@ -36,57 +33,411 @@ import (
// Packages should return errors of these types when they want to instruct a
// client to take a particular action.
//
// For the most part, we just try to provide local grpc errors. Most conditions
// map very well to those defined by grpc.
// These errors map closely to grpc errors.
var (
ErrUnknown = errors.New("unknown") // used internally to represent a missed mapping.
ErrInvalidArgument = errors.New("invalid argument")
ErrNotFound = errors.New("not found")
ErrAlreadyExists = errors.New("already exists")
ErrFailedPrecondition = errors.New("failed precondition")
ErrUnavailable = errors.New("unavailable")
ErrNotImplemented = errors.New("not implemented") // represents not supported and unimplemented
ErrUnknown = errUnknown{}
ErrInvalidArgument = errInvalidArgument{}
ErrNotFound = errNotFound{}
ErrAlreadyExists = errAlreadyExists{}
ErrPermissionDenied = errPermissionDenied{}
ErrResourceExhausted = errResourceExhausted{}
ErrFailedPrecondition = errFailedPrecondition{}
ErrConflict = errConflict{}
ErrNotModified = errNotModified{}
ErrAborted = errAborted{}
ErrOutOfRange = errOutOfRange{}
ErrNotImplemented = errNotImplemented{}
ErrInternal = errInternal{}
ErrUnavailable = errUnavailable{}
ErrDataLoss = errDataLoss{}
ErrUnauthenticated = errUnauthorized{}
)
// IsInvalidArgument returns true if the error is due to an invalid argument
func IsInvalidArgument(err error) bool {
return errors.Is(err, ErrInvalidArgument)
}
// IsNotFound returns true if the error is due to a missing object
func IsNotFound(err error) bool {
return errors.Is(err, ErrNotFound)
}
// IsAlreadyExists returns true if the error is due to an already existing
// metadata item
func IsAlreadyExists(err error) bool {
return errors.Is(err, ErrAlreadyExists)
}
// IsFailedPrecondition returns true if an operation could not proceed to the
// lack of a particular condition
func IsFailedPrecondition(err error) bool {
return errors.Is(err, ErrFailedPrecondition)
}
// IsUnavailable returns true if the error is due to a resource being unavailable
func IsUnavailable(err error) bool {
return errors.Is(err, ErrUnavailable)
}
// IsNotImplemented returns true if the error is due to not being implemented
func IsNotImplemented(err error) bool {
return errors.Is(err, ErrNotImplemented)
// cancelled maps to Moby's "ErrCancelled"
type cancelled interface {
Cancelled()
}
// IsCanceled returns true if the error is due to `context.Canceled`.
func IsCanceled(err error) bool {
return errors.Is(err, context.Canceled)
return errors.Is(err, context.Canceled) || isInterface[cancelled](err)
}
type errUnknown struct{}
func (errUnknown) Error() string { return "unknown" }
func (errUnknown) Unknown() {}
func (e errUnknown) WithMessage(msg string) error {
return customMessage{e, msg}
}
// unknown maps to Moby's "ErrUnknown"
type unknown interface {
Unknown()
}
// IsUnknown returns true if the error is due to an unknown error,
// unhandled condition or unexpected response.
func IsUnknown(err error) bool {
return errors.Is(err, errUnknown{}) || isInterface[unknown](err)
}
type errInvalidArgument struct{}
func (errInvalidArgument) Error() string { return "invalid argument" }
func (errInvalidArgument) InvalidParameter() {}
func (e errInvalidArgument) WithMessage(msg string) error {
return customMessage{e, msg}
}
// invalidParameter maps to Moby's "ErrInvalidParameter"
type invalidParameter interface {
InvalidParameter()
}
// IsInvalidArgument returns true if the error is due to an invalid argument
func IsInvalidArgument(err error) bool {
return errors.Is(err, ErrInvalidArgument) || isInterface[invalidParameter](err)
}
// deadlineExceed maps to Moby's "ErrDeadline"
type deadlineExceeded interface {
DeadlineExceeded()
}
// IsDeadlineExceeded returns true if the error is due to
// `context.DeadlineExceeded`.
func IsDeadlineExceeded(err error) bool {
return errors.Is(err, context.DeadlineExceeded)
return errors.Is(err, context.DeadlineExceeded) || isInterface[deadlineExceeded](err)
}
type errNotFound struct{}
func (errNotFound) Error() string { return "not found" }
func (errNotFound) NotFound() {}
func (e errNotFound) WithMessage(msg string) error {
return customMessage{e, msg}
}
// notFound maps to Moby's "ErrNotFound"
type notFound interface {
NotFound()
}
// IsNotFound returns true if the error is due to a missing object
func IsNotFound(err error) bool {
return errors.Is(err, ErrNotFound) || isInterface[notFound](err)
}
type errAlreadyExists struct{}
func (errAlreadyExists) Error() string { return "already exists" }
func (errAlreadyExists) AlreadyExists() {}
func (e errAlreadyExists) WithMessage(msg string) error {
return customMessage{e, msg}
}
type alreadyExists interface {
AlreadyExists()
}
// IsAlreadyExists returns true if the error is due to an already existing
// metadata item
func IsAlreadyExists(err error) bool {
return errors.Is(err, ErrAlreadyExists) || isInterface[alreadyExists](err)
}
type errPermissionDenied struct{}
func (errPermissionDenied) Error() string { return "permission denied" }
func (errPermissionDenied) Forbidden() {}
func (e errPermissionDenied) WithMessage(msg string) error {
return customMessage{e, msg}
}
// forbidden maps to Moby's "ErrForbidden"
type forbidden interface {
Forbidden()
}
// IsPermissionDenied returns true if the error is due to permission denied
// or forbidden (403) response
func IsPermissionDenied(err error) bool {
return errors.Is(err, ErrPermissionDenied) || isInterface[forbidden](err)
}
type errResourceExhausted struct{}
func (errResourceExhausted) Error() string { return "resource exhausted" }
func (errResourceExhausted) ResourceExhausted() {}
func (e errResourceExhausted) WithMessage(msg string) error {
return customMessage{e, msg}
}
type resourceExhausted interface {
ResourceExhausted()
}
// IsResourceExhausted returns true if the error is due to
// a lack of resources or too many attempts.
func IsResourceExhausted(err error) bool {
return errors.Is(err, errResourceExhausted{}) || isInterface[resourceExhausted](err)
}
type errFailedPrecondition struct{}
func (e errFailedPrecondition) Error() string { return "failed precondition" }
func (errFailedPrecondition) FailedPrecondition() {}
func (e errFailedPrecondition) WithMessage(msg string) error {
return customMessage{e, msg}
}
type failedPrecondition interface {
FailedPrecondition()
}
// IsFailedPrecondition returns true if an operation could not proceed due to
// the lack of a particular condition
func IsFailedPrecondition(err error) bool {
return errors.Is(err, errFailedPrecondition{}) || isInterface[failedPrecondition](err)
}
type errConflict struct{}
func (errConflict) Error() string { return "conflict" }
func (errConflict) Conflict() {}
func (e errConflict) WithMessage(msg string) error {
return customMessage{e, msg}
}
// conflict maps to Moby's "ErrConflict"
type conflict interface {
Conflict()
}
// IsConflict returns true if an operation could not proceed due to
// a conflict.
func IsConflict(err error) bool {
return errors.Is(err, errConflict{}) || isInterface[conflict](err)
}
type errNotModified struct{}
func (errNotModified) Error() string { return "not modified" }
func (errNotModified) NotModified() {}
func (e errNotModified) WithMessage(msg string) error {
return customMessage{e, msg}
}
// notModified maps to Moby's "ErrNotModified"
type notModified interface {
NotModified()
}
// IsNotModified returns true if an operation could not proceed due
// to an object not modified from a previous state.
func IsNotModified(err error) bool {
return errors.Is(err, errNotModified{}) || isInterface[notModified](err)
}
type errAborted struct{}
func (errAborted) Error() string { return "aborted" }
func (errAborted) Aborted() {}
func (e errAborted) WithMessage(msg string) error {
return customMessage{e, msg}
}
type aborted interface {
Aborted()
}
// IsAborted returns true if an operation was aborted.
func IsAborted(err error) bool {
return errors.Is(err, errAborted{}) || isInterface[aborted](err)
}
type errOutOfRange struct{}
func (errOutOfRange) Error() string { return "out of range" }
func (errOutOfRange) OutOfRange() {}
func (e errOutOfRange) WithMessage(msg string) error {
return customMessage{e, msg}
}
type outOfRange interface {
OutOfRange()
}
// IsOutOfRange returns true if an operation could not proceed due
// to data being out of the expected range.
func IsOutOfRange(err error) bool {
return errors.Is(err, errOutOfRange{}) || isInterface[outOfRange](err)
}
type errNotImplemented struct{}
func (errNotImplemented) Error() string { return "not implemented" }
func (errNotImplemented) NotImplemented() {}
func (e errNotImplemented) WithMessage(msg string) error {
return customMessage{e, msg}
}
// notImplemented maps to Moby's "ErrNotImplemented"
type notImplemented interface {
NotImplemented()
}
// IsNotImplemented returns true if the error is due to not being implemented
func IsNotImplemented(err error) bool {
return errors.Is(err, errNotImplemented{}) || isInterface[notImplemented](err)
}
type errInternal struct{}
func (errInternal) Error() string { return "internal" }
func (errInternal) System() {}
func (e errInternal) WithMessage(msg string) error {
return customMessage{e, msg}
}
// system maps to Moby's "ErrSystem"
type system interface {
System()
}
// IsInternal returns true if the error returns to an internal or system error
func IsInternal(err error) bool {
return errors.Is(err, errInternal{}) || isInterface[system](err)
}
type errUnavailable struct{}
func (errUnavailable) Error() string { return "unavailable" }
func (errUnavailable) Unavailable() {}
func (e errUnavailable) WithMessage(msg string) error {
return customMessage{e, msg}
}
// unavailable maps to Moby's "ErrUnavailable"
type unavailable interface {
Unavailable()
}
// IsUnavailable returns true if the error is due to a resource being unavailable
func IsUnavailable(err error) bool {
return errors.Is(err, errUnavailable{}) || isInterface[unavailable](err)
}
type errDataLoss struct{}
func (errDataLoss) Error() string { return "data loss" }
func (errDataLoss) DataLoss() {}
func (e errDataLoss) WithMessage(msg string) error {
return customMessage{e, msg}
}
// dataLoss maps to Moby's "ErrDataLoss"
type dataLoss interface {
DataLoss()
}
// IsDataLoss returns true if data during an operation was lost or corrupted
func IsDataLoss(err error) bool {
return errors.Is(err, errDataLoss{}) || isInterface[dataLoss](err)
}
type errUnauthorized struct{}
func (errUnauthorized) Error() string { return "unauthorized" }
func (errUnauthorized) Unauthorized() {}
func (e errUnauthorized) WithMessage(msg string) error {
return customMessage{e, msg}
}
// unauthorized maps to Moby's "ErrUnauthorized"
type unauthorized interface {
Unauthorized()
}
// IsUnauthorized returns true if the error indicates that the user was
// unauthenticated or unauthorized.
func IsUnauthorized(err error) bool {
return errors.Is(err, errUnauthorized{}) || isInterface[unauthorized](err)
}
func isInterface[T any](err error) bool {
for {
switch x := err.(type) {
case T:
return true
case customMessage:
err = x.err
case interface{ Unwrap() error }:
err = x.Unwrap()
if err == nil {
return false
}
case interface{ Unwrap() []error }:
for _, err := range x.Unwrap() {
if isInterface[T](err) {
return true
}
}
return false
default:
return false
}
}
}
// customMessage is used to provide a defined error with a custom message.
// The message is not wrapped but can be compared by the `Is(error) bool` interface.
type customMessage struct {
err error
msg string
}
func (c customMessage) Is(err error) bool {
return c.err == err
}
func (c customMessage) As(target any) bool {
return errors.As(c.err, target)
}
func (c customMessage) Error() string {
return c.msg
}

View file

@ -1,147 +0,0 @@
/*
Copyright The containerd Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package errdefs
import (
"context"
"fmt"
"strings"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
)
// ToGRPC will attempt to map the backend containerd error into a grpc error,
// using the original error message as a description.
//
// Further information may be extracted from certain errors depending on their
// type.
//
// If the error is unmapped, the original error will be returned to be handled
// by the regular grpc error handling stack.
func ToGRPC(err error) error {
if err == nil {
return nil
}
if isGRPCError(err) {
// error has already been mapped to grpc
return err
}
switch {
case IsInvalidArgument(err):
return status.Errorf(codes.InvalidArgument, err.Error())
case IsNotFound(err):
return status.Errorf(codes.NotFound, err.Error())
case IsAlreadyExists(err):
return status.Errorf(codes.AlreadyExists, err.Error())
case IsFailedPrecondition(err):
return status.Errorf(codes.FailedPrecondition, err.Error())
case IsUnavailable(err):
return status.Errorf(codes.Unavailable, err.Error())
case IsNotImplemented(err):
return status.Errorf(codes.Unimplemented, err.Error())
case IsCanceled(err):
return status.Errorf(codes.Canceled, err.Error())
case IsDeadlineExceeded(err):
return status.Errorf(codes.DeadlineExceeded, err.Error())
}
return err
}
// ToGRPCf maps the error to grpc error codes, assembling the formatting string
// and combining it with the target error string.
//
// This is equivalent to errdefs.ToGRPC(fmt.Errorf("%s: %w", fmt.Sprintf(format, args...), err))
func ToGRPCf(err error, format string, args ...interface{}) error {
return ToGRPC(fmt.Errorf("%s: %w", fmt.Sprintf(format, args...), err))
}
// FromGRPC returns the underlying error from a grpc service based on the grpc error code
func FromGRPC(err error) error {
if err == nil {
return nil
}
var cls error // divide these into error classes, becomes the cause
switch code(err) {
case codes.InvalidArgument:
cls = ErrInvalidArgument
case codes.AlreadyExists:
cls = ErrAlreadyExists
case codes.NotFound:
cls = ErrNotFound
case codes.Unavailable:
cls = ErrUnavailable
case codes.FailedPrecondition:
cls = ErrFailedPrecondition
case codes.Unimplemented:
cls = ErrNotImplemented
case codes.Canceled:
cls = context.Canceled
case codes.DeadlineExceeded:
cls = context.DeadlineExceeded
default:
cls = ErrUnknown
}
msg := rebaseMessage(cls, err)
if msg != "" {
err = fmt.Errorf("%s: %w", msg, cls)
} else {
err = cls
}
return err
}
// rebaseMessage removes the repeats for an error at the end of an error
// string. This will happen when taking an error over grpc then remapping it.
//
// Effectively, we just remove the string of cls from the end of err if it
// appears there.
func rebaseMessage(cls error, err error) string {
desc := errDesc(err)
clss := cls.Error()
if desc == clss {
return ""
}
return strings.TrimSuffix(desc, ": "+clss)
}
func isGRPCError(err error) bool {
_, ok := status.FromError(err)
return ok
}
func code(err error) codes.Code {
if s, ok := status.FromError(err); ok {
return s.Code()
}
return codes.Unknown
}
func errDesc(err error) string {
if s, ok := status.FromError(err); ok {
return s.Message()
}
return err.Error()
}

191
vendor/github.com/containerd/errdefs/pkg/LICENSE generated vendored Normal file
View file

@ -0,0 +1,191 @@
Apache License
Version 2.0, January 2004
https://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
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.

View file

@ -0,0 +1,353 @@
/*
Copyright The containerd Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
// Package errgrpc provides utility functions for translating errors to
// and from a gRPC context.
//
// The functions ToGRPC and ToNative can be used to map server-side and
// client-side errors to the correct types.
package errgrpc
import (
"context"
"errors"
"fmt"
"reflect"
"strconv"
"strings"
spb "google.golang.org/genproto/googleapis/rpc/status"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
"google.golang.org/protobuf/proto"
"google.golang.org/protobuf/protoadapt"
"google.golang.org/protobuf/types/known/anypb"
"github.com/containerd/typeurl/v2"
"github.com/containerd/errdefs"
"github.com/containerd/errdefs/pkg/internal/cause"
"github.com/containerd/errdefs/pkg/internal/types"
)
// ToGRPC will attempt to map the error into a grpc error, from the error types
// defined in the the errdefs package and attempign to preserve the original
// description. Any type which does not resolve to a defined error type will
// be assigned the unknown error code.
//
// Further information may be extracted from certain errors depending on their
// type. The grpc error details will be used to attempt to preserve as much of
// the error structures and types as possible.
//
// Errors which can be marshaled using protobuf or typeurl will be considered
// for including as GRPC error details.
// Additionally, use the following interfaces in errors to preserve custom types:
//
// WrapError(error) error - Used to wrap the previous error
// JoinErrors(...error) error - Used to join all previous errors
// CollapseError() - Used for errors which carry information but
// should not have their error message shown.
func ToGRPC(err error) error {
if err == nil {
return nil
}
if _, ok := status.FromError(err); ok {
// error has already been mapped to grpc
return err
}
st := statusFromError(err)
if st != nil {
if details := errorDetails(err, false); len(details) > 0 {
if ds, _ := st.WithDetails(details...); ds != nil {
st = ds
}
}
err = st.Err()
}
return err
}
func statusFromError(err error) *status.Status {
switch errdefs.Resolve(err) {
case errdefs.ErrInvalidArgument:
return status.New(codes.InvalidArgument, err.Error())
case errdefs.ErrNotFound:
return status.New(codes.NotFound, err.Error())
case errdefs.ErrAlreadyExists:
return status.New(codes.AlreadyExists, err.Error())
case errdefs.ErrPermissionDenied:
return status.New(codes.PermissionDenied, err.Error())
case errdefs.ErrResourceExhausted:
return status.New(codes.ResourceExhausted, err.Error())
case errdefs.ErrFailedPrecondition, errdefs.ErrConflict, errdefs.ErrNotModified:
return status.New(codes.FailedPrecondition, err.Error())
case errdefs.ErrAborted:
return status.New(codes.Aborted, err.Error())
case errdefs.ErrOutOfRange:
return status.New(codes.OutOfRange, err.Error())
case errdefs.ErrNotImplemented:
return status.New(codes.Unimplemented, err.Error())
case errdefs.ErrInternal:
return status.New(codes.Internal, err.Error())
case errdefs.ErrUnavailable:
return status.New(codes.Unavailable, err.Error())
case errdefs.ErrDataLoss:
return status.New(codes.DataLoss, err.Error())
case errdefs.ErrUnauthenticated:
return status.New(codes.Unauthenticated, err.Error())
case context.DeadlineExceeded:
return status.New(codes.DeadlineExceeded, err.Error())
case context.Canceled:
return status.New(codes.Canceled, err.Error())
case errdefs.ErrUnknown:
return status.New(codes.Unknown, err.Error())
}
return nil
}
// errorDetails returns an array of errors which make up the provided error.
// If firstIncluded is true, then all encodable errors will be used, otherwise
// the first error in an error list will be not be used, to account for the
// the base status error which details are added to via wrap or join.
//
// The errors are ordered in way that they can be applied in order by either
// wrapping or joining the errors to recreate an error with the same structure
// when `WrapError` and `JoinErrors` interfaces are used.
//
// The intent is that when re-applying the errors to create a single error, the
// results of calls to `Error()`, `errors.Is`, `errors.As`, and "%+v" formatting
// is the same as the original error.
func errorDetails(err error, firstIncluded bool) []protoadapt.MessageV1 {
switch uerr := err.(type) {
case interface{ Unwrap() error }:
details := errorDetails(uerr.Unwrap(), firstIncluded)
// If the type is able to wrap, then include if proto
if _, ok := err.(interface{ WrapError(error) error }); ok {
// Get proto message
if protoErr := toProtoMessage(err); protoErr != nil {
details = append(details, protoErr)
}
}
return details
case interface{ Unwrap() []error }:
var details []protoadapt.MessageV1
for i, e := range uerr.Unwrap() {
details = append(details, errorDetails(e, firstIncluded || i > 0)...)
}
if _, ok := err.(interface{ JoinErrors(...error) error }); ok {
// Get proto message
if protoErr := toProtoMessage(err); protoErr != nil {
details = append(details, protoErr)
}
}
return details
}
if firstIncluded {
if protoErr := toProtoMessage(err); protoErr != nil {
return []protoadapt.MessageV1{protoErr}
}
if gs, ok := status.FromError(ToGRPC(err)); ok {
return []protoadapt.MessageV1{gs.Proto()}
}
// TODO: Else include unknown extra error type?
}
return nil
}
func toProtoMessage(err error) protoadapt.MessageV1 {
// Do not double encode proto messages, otherwise use Any
if pm, ok := err.(protoadapt.MessageV1); ok {
return pm
}
if pm, ok := err.(proto.Message); ok {
return protoadapt.MessageV1Of(pm)
}
if reflect.TypeOf(err).Kind() == reflect.Ptr {
a, aerr := typeurl.MarshalAny(err)
if aerr == nil {
return &anypb.Any{
TypeUrl: a.GetTypeUrl(),
Value: a.GetValue(),
}
}
}
return nil
}
// ToGRPCf maps the error to grpc error codes, assembling the formatting string
// and combining it with the target error string.
//
// This is equivalent to grpc.ToGRPC(fmt.Errorf("%s: %w", fmt.Sprintf(format, args...), err))
func ToGRPCf(err error, format string, args ...interface{}) error {
return ToGRPC(fmt.Errorf("%s: %w", fmt.Sprintf(format, args...), err))
}
// ToNative returns the underlying error from a grpc service based on the grpc
// error code. The grpc details are used to add wrap the error in more context
// or support multiple errors.
func ToNative(err error) error {
if err == nil {
return nil
}
s, isGRPC := status.FromError(err)
var (
desc string
code codes.Code
)
if isGRPC {
desc = s.Message()
code = s.Code()
} else {
desc = err.Error()
code = codes.Unknown
}
var cls error // divide these into error classes, becomes the cause
switch code {
case codes.InvalidArgument:
cls = errdefs.ErrInvalidArgument
case codes.AlreadyExists:
cls = errdefs.ErrAlreadyExists
case codes.NotFound:
cls = errdefs.ErrNotFound
case codes.Unavailable:
cls = errdefs.ErrUnavailable
case codes.FailedPrecondition:
// TODO: Has suffix is not sufficient for conflict and not modified
// Message should start with ": " or be at beginning of a line
// Message should end with ": " or be at the end of a line
// Compile a regex
if desc == errdefs.ErrConflict.Error() || strings.HasSuffix(desc, ": "+errdefs.ErrConflict.Error()) {
cls = errdefs.ErrConflict
} else if desc == errdefs.ErrNotModified.Error() || strings.HasSuffix(desc, ": "+errdefs.ErrNotModified.Error()) {
cls = errdefs.ErrNotModified
} else {
cls = errdefs.ErrFailedPrecondition
}
case codes.Unimplemented:
cls = errdefs.ErrNotImplemented
case codes.Canceled:
cls = context.Canceled
case codes.DeadlineExceeded:
cls = context.DeadlineExceeded
case codes.Aborted:
cls = errdefs.ErrAborted
case codes.Unauthenticated:
cls = errdefs.ErrUnauthenticated
case codes.PermissionDenied:
cls = errdefs.ErrPermissionDenied
case codes.Internal:
cls = errdefs.ErrInternal
case codes.DataLoss:
cls = errdefs.ErrDataLoss
case codes.OutOfRange:
cls = errdefs.ErrOutOfRange
case codes.ResourceExhausted:
cls = errdefs.ErrResourceExhausted
default:
if idx := strings.LastIndex(desc, cause.UnexpectedStatusPrefix); idx > 0 {
if status, uerr := strconv.Atoi(desc[idx+len(cause.UnexpectedStatusPrefix):]); uerr == nil && status >= 200 && status < 600 {
cls = cause.ErrUnexpectedStatus{Status: status}
}
}
if cls == nil {
cls = errdefs.ErrUnknown
}
}
msg := rebaseMessage(cls, desc)
if msg == "" {
err = cls
} else if msg != desc {
err = fmt.Errorf("%s: %w", msg, cls)
} else if wm, ok := cls.(interface{ WithMessage(string) error }); ok {
err = wm.WithMessage(msg)
} else {
err = fmt.Errorf("%s: %w", msg, cls)
}
if isGRPC {
errs := []error{err}
for _, a := range s.Details() {
var derr error
// First decode error if needed
if s, ok := a.(*spb.Status); ok {
derr = ToNative(status.ErrorProto(s))
} else if e, ok := a.(error); ok {
derr = e
} else if dany, ok := a.(typeurl.Any); ok {
i, uerr := typeurl.UnmarshalAny(dany)
if uerr == nil {
if e, ok = i.(error); ok {
derr = e
} else {
derr = fmt.Errorf("non-error unmarshalled detail: %v", i)
}
} else {
derr = fmt.Errorf("error of type %q with failure to unmarshal: %v", dany.GetTypeUrl(), uerr)
}
} else {
derr = fmt.Errorf("non-error detail: %v", a)
}
switch werr := derr.(type) {
case interface{ WrapError(error) error }:
errs[len(errs)-1] = werr.WrapError(errs[len(errs)-1])
case interface{ JoinErrors(...error) error }:
// TODO: Consider whether this should support joining a subset
errs[0] = werr.JoinErrors(errs...)
case interface{ CollapseError() }:
errs[len(errs)-1] = types.CollapsedError(errs[len(errs)-1], derr)
default:
errs = append(errs, derr)
}
}
if len(errs) > 1 {
err = errors.Join(errs...)
} else {
err = errs[0]
}
}
return err
}
// rebaseMessage removes the repeats for an error at the end of an error
// string. This will happen when taking an error over grpc then remapping it.
//
// Effectively, we just remove the string of cls from the end of err if it
// appears there.
func rebaseMessage(cls error, desc string) string {
clss := cls.Error()
if desc == clss {
return ""
}
return strings.TrimSuffix(desc, ": "+clss)
}

View file

@ -0,0 +1,33 @@
/*
Copyright The containerd Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
// Package cause is used to define root causes for errors
// common to errors packages like grpc and http.
package cause
import "fmt"
type ErrUnexpectedStatus struct {
Status int
}
const UnexpectedStatusPrefix = "unexpected status "
func (e ErrUnexpectedStatus) Error() string {
return fmt.Sprintf("%s%d", UnexpectedStatusPrefix, e.Status)
}
func (ErrUnexpectedStatus) Unknown() {}

View file

@ -0,0 +1,57 @@
/*
Copyright The containerd Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package types
import "fmt"
// CollapsibleError indicates the error should be collapsed
type CollapsibleError interface {
CollapseError()
}
// CollapsedError returns a new error with the collapsed
// error returned on unwrapped or when formatted with "%+v"
func CollapsedError(err error, collapsed ...error) error {
return collapsedError{err, collapsed}
}
type collapsedError struct {
error
collapsed []error
}
func (c collapsedError) Unwrap() []error {
return append([]error{c.error}, c.collapsed...)
}
func (c collapsedError) Format(s fmt.State, verb rune) {
switch verb {
case 'v':
if s.Flag('+') {
fmt.Fprintf(s, "%+v", c.error)
for _, err := range c.collapsed {
fmt.Fprintf(s, "\n%+v", err)
}
return
}
fallthrough
case 's':
fmt.Fprint(s, c.Error())
case 'q':
fmt.Fprintf(s, "%q", c.Error())
}
}

147
vendor/github.com/containerd/errdefs/resolve.go generated vendored Normal file
View file

@ -0,0 +1,147 @@
/*
Copyright The containerd Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package errdefs
import "context"
// Resolve returns the first error found in the error chain which matches an
// error defined in this package or context error. A raw, unwrapped error is
// returned or ErrUnknown if no matching error is found.
//
// This is useful for determining a response code based on the outermost wrapped
// error rather than the original cause. For example, a not found error deep
// in the code may be wrapped as an invalid argument. When determining status
// code from Is* functions, the depth or ordering of the error is not
// considered.
//
// The search order is depth first, a wrapped error returned from any part of
// the chain from `Unwrap() error` will be returned before any joined errors
// as returned by `Unwrap() []error`.
func Resolve(err error) error {
if err == nil {
return nil
}
err = firstError(err)
if err == nil {
err = ErrUnknown
}
return err
}
func firstError(err error) error {
for {
switch err {
case ErrUnknown,
ErrInvalidArgument,
ErrNotFound,
ErrAlreadyExists,
ErrPermissionDenied,
ErrResourceExhausted,
ErrFailedPrecondition,
ErrConflict,
ErrNotModified,
ErrAborted,
ErrOutOfRange,
ErrNotImplemented,
ErrInternal,
ErrUnavailable,
ErrDataLoss,
ErrUnauthenticated,
context.DeadlineExceeded,
context.Canceled:
return err
}
switch e := err.(type) {
case customMessage:
err = e.err
case unknown:
return ErrUnknown
case invalidParameter:
return ErrInvalidArgument
case notFound:
return ErrNotFound
case alreadyExists:
return ErrAlreadyExists
case forbidden:
return ErrPermissionDenied
case resourceExhausted:
return ErrResourceExhausted
case failedPrecondition:
return ErrFailedPrecondition
case conflict:
return ErrConflict
case notModified:
return ErrNotModified
case aborted:
return ErrAborted
case errOutOfRange:
return ErrOutOfRange
case notImplemented:
return ErrNotImplemented
case system:
return ErrInternal
case unavailable:
return ErrUnavailable
case dataLoss:
return ErrDataLoss
case unauthorized:
return ErrUnauthenticated
case deadlineExceeded:
return context.DeadlineExceeded
case cancelled:
return context.Canceled
case interface{ Unwrap() error }:
err = e.Unwrap()
if err == nil {
return nil
}
case interface{ Unwrap() []error }:
for _, ue := range e.Unwrap() {
if fe := firstError(ue); fe != nil {
return fe
}
}
return nil
case interface{ Is(error) bool }:
for _, target := range []error{ErrUnknown,
ErrInvalidArgument,
ErrNotFound,
ErrAlreadyExists,
ErrPermissionDenied,
ErrResourceExhausted,
ErrFailedPrecondition,
ErrConflict,
ErrNotModified,
ErrAborted,
ErrOutOfRange,
ErrNotImplemented,
ErrInternal,
ErrUnavailable,
ErrDataLoss,
ErrUnauthenticated,
context.DeadlineExceeded,
context.Canceled} {
if e.Is(target) {
return target
}
}
return nil
default:
return nil
}
}
}

View file

@ -53,7 +53,7 @@ endif
# Note: Uses the -N -l go compiler options to disable compiler optimizations
# and inlining. Using these build options allows you to subsequently
# use source debugging tools like delve.
all: bin/buildah bin/imgtype bin/copy bin/tutorial docs
all: bin/buildah bin/imgtype bin/copy bin/inet bin/tutorial docs
# Update nix/nixpkgs.json its latest stable commit
.PHONY: nixpkgs
@ -110,6 +110,9 @@ bin/copy: $(SOURCES) tests/copy/copy.go
bin/tutorial: $(SOURCES) tests/tutorial/tutorial.go
$(GO_BUILD) $(BUILDAH_LDFLAGS) -o $@ $(BUILDFLAGS) ./tests/tutorial/tutorial.go
bin/inet: tests/inet/inet.go
$(GO_BUILD) $(BUILDAH_LDFLAGS) -o $@ $(BUILDFLAGS) ./tests/inet/inet.go
.PHONY: clean
clean:
$(RM) -r bin tests/testreport/testreport

View file

@ -30,6 +30,7 @@ import (
"github.com/containers/storage/pkg/regexp"
"github.com/docker/go-connections/tlsconfig"
"github.com/hashicorp/go-multierror"
"github.com/moby/sys/userns"
digest "github.com/opencontainers/go-digest"
"github.com/opencontainers/runtime-spec/specs-go"
"github.com/sirupsen/logrus"
@ -545,7 +546,7 @@ func (b *Builder) Add(destination string, extract bool, options AddAndCopyOption
ChmodDirs: nil,
ChownFiles: nil,
ChmodFiles: nil,
IgnoreDevices: runningInUserNS(),
IgnoreDevices: userns.RunningInUserNS(),
}
putErr = copier.Put(extractDirectory, extractDirectory, putOptions, io.TeeReader(pipeReader, hasher))
}
@ -686,7 +687,7 @@ func (b *Builder) Add(destination string, extract bool, options AddAndCopyOption
ChmodDirs: nil,
ChownFiles: nil,
ChmodFiles: nil,
IgnoreDevices: runningInUserNS(),
IgnoreDevices: userns.RunningInUserNS(),
}
putErr = copier.Put(extractDirectory, extractDirectory, putOptions, io.TeeReader(pipeReader, hasher))
}

View file

@ -1,7 +0,0 @@
//go:build !linux
package buildah
func runningInUserNS() bool {
return false
}

View file

@ -1,9 +0,0 @@
package buildah
import (
"github.com/opencontainers/runc/libcontainer/userns"
)
func runningInUserNS() bool {
return userns.RunningInUserNS()
}

View file

@ -498,7 +498,7 @@ func runUsingChroot(spec *specs.Spec, bundlePath string, ctty *os.File, stdin io
BundlePath: bundlePath,
})
if conferr != nil {
fmt.Fprintf(os.Stderr, "error re-encoding configuration for %q", runUsingChrootExecCommand)
fmt.Fprintf(os.Stderr, "error re-encoding configuration for %q\n", runUsingChrootExecCommand)
os.Exit(1)
}
@ -567,7 +567,7 @@ func runUsingChroot(spec *specs.Spec, bundlePath string, ctty *os.File, stdin io
}
}
}
fmt.Fprintf(os.Stderr, "process exited with error: %v", err)
fmt.Fprintf(os.Stderr, "process exited with error: %v\n", err)
os.Exit(1)
}
@ -695,7 +695,7 @@ func runUsingChrootExecMain() {
}
logrus.Debugf("setting supplemental groups")
if err = syscall.Setgroups(gids); err != nil {
fmt.Fprintf(os.Stderr, "error setting supplemental groups list: %v", err)
fmt.Fprintf(os.Stderr, "error setting supplemental groups list: %v\n", err)
os.Exit(1)
}
} else {
@ -703,7 +703,7 @@ func runUsingChrootExecMain() {
if strings.Trim(string(setgroups), "\n") != "deny" {
logrus.Debugf("clearing supplemental groups")
if err = syscall.Setgroups([]int{}); err != nil {
fmt.Fprintf(os.Stderr, "error clearing supplemental groups list: %v", err)
fmt.Fprintf(os.Stderr, "error clearing supplemental groups list: %v\n", err)
os.Exit(1)
}
}
@ -711,7 +711,7 @@ func runUsingChrootExecMain() {
logrus.Debugf("setting gid")
if err = unix.Setresgid(int(user.GID), int(user.GID), int(user.GID)); err != nil {
fmt.Fprintf(os.Stderr, "error setting GID: %v", err)
fmt.Fprintf(os.Stderr, "error setting GID: %v\n", err)
os.Exit(1)
}
@ -732,7 +732,7 @@ func runUsingChrootExecMain() {
logrus.Debugf("setting uid")
if err = unix.Setresuid(int(user.UID), int(user.UID), int(user.UID)); err != nil {
fmt.Fprintf(os.Stderr, "error setting UID: %v", err)
fmt.Fprintf(os.Stderr, "error setting UID: %v\n", err)
os.Exit(1)
}
@ -745,7 +745,7 @@ func runUsingChrootExecMain() {
logrus.Debugf("Running %#v (PATH = %q)", cmd, os.Getenv("PATH"))
interrupted := make(chan os.Signal, 100)
if err = cmd.Start(); err != nil {
fmt.Fprintf(os.Stderr, "process failed to start with error: %v", err)
fmt.Fprintf(os.Stderr, "process failed to start with error: %v\n", err)
}
go func() {
for range interrupted {
@ -772,7 +772,7 @@ func runUsingChrootExecMain() {
}
}
}
fmt.Fprintf(os.Stderr, "process exited with error: %v", err)
fmt.Fprintf(os.Stderr, "process exited with error: %v\n", err)
os.Exit(1)
}
}

View file

@ -531,6 +531,7 @@ func GetVolumes(ctx *types.SystemContext, store storage.Store, volumes []string,
// getMounts takes user-provided input from the --mount flag and creates OCI
// spec mounts.
// buildah run --mount type=bind,src=/etc/resolv.conf,target=/etc/resolv.conf ...
// buildah run --mount type=cache,target=/var/cache ...
// buildah run --mount type=tmpfs,target=/dev/shm ...
//
// If this function succeeds, the caller must unlock the returned *lockfile.LockFile s if any (when??).
@ -590,7 +591,7 @@ func getMounts(ctx *types.SystemContext, store storage.Store, mounts []string, c
}
finalMounts[mount.Destination] = mount
case TypeTmpfs:
mount, err := GetTmpfsMount(tokens)
mount, err := GetTmpfsMount(tokens, workDir)
if err != nil {
return nil, mountedImages, nil, err
}
@ -608,7 +609,7 @@ func getMounts(ctx *types.SystemContext, store storage.Store, mounts []string, c
}
// GetTmpfsMount parses a single tmpfs mount entry from the --mount flag
func GetTmpfsMount(args []string) (specs.Mount, error) {
func GetTmpfsMount(args []string, workDir string) (specs.Mount, error) {
newMount := specs.Mount{
Type: TypeTmpfs,
Source: TypeTmpfs,
@ -646,10 +647,14 @@ func GetTmpfsMount(args []string) (specs.Mount, error) {
if !hasArgValue {
return newMount, fmt.Errorf("%v: %w", argName, errBadOptionArg)
}
if err := parse.ValidateVolumeCtrDir(argValue); err != nil {
targetPath := argValue
if !path.IsAbs(targetPath) {
targetPath = filepath.Join(workDir, targetPath)
}
if err := parse.ValidateVolumeCtrDir(targetPath); err != nil {
return newMount, err
}
newMount.Destination = argValue
newMount.Destination = targetPath
setDest = true
default:
return newMount, fmt.Errorf("%v: %w", argName, errBadMntOption)

View file

@ -1605,7 +1605,7 @@ func (b *Builder) runSetupRunMounts(mountPoint string, mounts []string, sources
mountImages = append(mountImages, image)
}
case "tmpfs":
mountSpec, err = b.getTmpfsMount(tokens, idMaps)
mountSpec, err = b.getTmpfsMount(tokens, idMaps, sources.WorkDir)
if err != nil {
return nil, nil, err
}
@ -1665,9 +1665,9 @@ func (b *Builder) getBindMount(tokens []string, context *imageTypes.SystemContex
return &volumes[0], image, nil
}
func (b *Builder) getTmpfsMount(tokens []string, idMaps IDMaps) (*specs.Mount, error) {
func (b *Builder) getTmpfsMount(tokens []string, idMaps IDMaps, workDir string) (*specs.Mount, error) {
var optionMounts []specs.Mount
mount, err := volumes.GetTmpfsMount(tokens)
mount, err := volumes.GetTmpfsMount(tokens, workDir)
if err != nil {
return nil, err
}

View file

@ -124,10 +124,16 @@ func (b *Builder) Run(command []string, options RunOptions) error {
return err
}
workDir := b.WorkDir()
if options.WorkingDir != "" {
g.SetProcessCwd(options.WorkingDir)
workDir = options.WorkingDir
} else if b.WorkDir() != "" {
g.SetProcessCwd(b.WorkDir())
workDir = b.WorkDir()
}
if workDir == "" {
workDir = string(os.PathSeparator)
}
mountPoint, err := b.Mount(b.MountLabel)
if err != nil {
@ -249,6 +255,7 @@ func (b *Builder) Run(command []string, options RunOptions) error {
}
runMountInfo := runMountInfo{
WorkDir: workDir,
ContextDir: options.ContextDir,
Secrets: options.Secrets,
SSHSources: options.SSHSources,

View file

@ -237,6 +237,10 @@ func (b *Builder) Run(command []string, options RunOptions) error {
workDir = options.WorkingDir
} else if b.WorkDir() != "" {
g.SetProcessCwd(b.WorkDir())
workDir = b.WorkDir()
}
if workDir == "" {
workDir = string(os.PathSeparator)
}
setupSelinux(g, b.ProcessLabel, b.MountLabel)
mountPoint, err := b.Mount(b.MountLabel)

View file

@ -175,8 +175,8 @@ type Copier struct {
// newCopier creates a Copier based on a runtime's system context.
// Note that fields in options *may* overwrite the counterparts of
// the specified system context. Please make sure to call `(*Copier).Close()`.
func (r *Runtime) newCopier(options *CopyOptions) (*Copier, error) {
return NewCopier(options, r.SystemContext())
func (r *Runtime) newCopier(options *CopyOptions, reportResolvedReference *types.ImageReference) (*Copier, error) {
return NewCopier(options, r.SystemContext(), reportResolvedReference)
}
// storageAllowedPolicyScopes overrides the policy for local storage
@ -223,7 +223,7 @@ func getDockerAuthConfig(name, passwd, creds, idToken string) (*types.DockerAuth
// NewCopier creates a Copier based on a provided system context.
// Note that fields in options *may* overwrite the counterparts of
// the specified system context. Please make sure to call `(*Copier).Close()`.
func NewCopier(options *CopyOptions, sc *types.SystemContext) (*Copier, error) {
func NewCopier(options *CopyOptions, sc *types.SystemContext, reportResolvedReference *types.ImageReference) (*Copier, error) {
c := Copier{extendTimeoutSocket: options.extendTimeoutSocket}
sysContextCopy := *sc
c.systemContext = &sysContextCopy
@ -330,6 +330,7 @@ func NewCopier(options *CopyOptions, sc *types.SystemContext) (*Copier, error) {
c.imageCopyOptions.SignBySigstorePrivateKeyFile = options.SignBySigstorePrivateKeyFile
c.imageCopyOptions.SignSigstorePrivateKeyPassphrase = options.SignSigstorePrivateKeyPassphrase
c.imageCopyOptions.ReportWriter = options.Writer
c.imageCopyOptions.ReportResolvedReference = reportResolvedReference
defaultContainerConfig, err := config.Default()
if err != nil {

View file

@ -104,7 +104,7 @@ func (r *Runtime) Import(ctx context.Context, path string, options *ImportOption
return "", err
}
c, err := r.newCopier(&options.CopyOptions)
c, err := r.newCopier(&options.CopyOptions, nil)
if err != nil {
return "", err
}

View file

@ -792,7 +792,7 @@ func (m *ManifestList) Push(ctx context.Context, destination string, options *Ma
// NOTE: we're using the logic in copier to create a proper
// types.SystemContext. This prevents us from having an error prone
// code duplicate here.
copier, err := m.image.runtime.newCopier(&options.CopyOptions)
copier, err := m.image.runtime.newCopier(&options.CopyOptions, nil)
if err != nil {
return "", err
}

View file

@ -17,15 +17,14 @@ import (
dockerArchiveTransport "github.com/containers/image/v5/docker/archive"
dockerDaemonTransport "github.com/containers/image/v5/docker/daemon"
"github.com/containers/image/v5/docker/reference"
"github.com/containers/image/v5/manifest"
ociArchiveTransport "github.com/containers/image/v5/oci/archive"
ociTransport "github.com/containers/image/v5/oci/layout"
"github.com/containers/image/v5/pkg/shortnames"
storageTransport "github.com/containers/image/v5/storage"
"github.com/containers/image/v5/transports"
"github.com/containers/image/v5/transports/alltransports"
"github.com/containers/image/v5/types"
"github.com/containers/storage"
digest "github.com/opencontainers/go-digest"
ociSpec "github.com/opencontainers/image-spec/specs-go/v1"
"github.com/sirupsen/logrus"
)
@ -231,7 +230,7 @@ func nameFromAnnotations(annotations map[string]string) string {
// copyFromDefault is the default copier for a number of transports. Other
// transports require some specific dancing, sometimes Yoga.
func (r *Runtime) copyFromDefault(ctx context.Context, ref types.ImageReference, options *CopyOptions) ([]string, error) {
c, err := r.newCopier(options)
c, err := r.newCopier(options, nil)
if err != nil {
return nil, err
}
@ -387,7 +386,7 @@ func (r *Runtime) copyFromDockerArchive(ctx context.Context, ref types.ImageRefe
// copyFromDockerArchiveReaderReference copies the specified readerRef from reader.
func (r *Runtime) copyFromDockerArchiveReaderReference(ctx context.Context, reader *dockerArchiveTransport.Reader, readerRef types.ImageReference, options *CopyOptions) ([]string, error) {
c, err := r.newCopier(options)
c, err := r.newCopier(options, nil)
if err != nil {
return nil, err
}
@ -421,7 +420,11 @@ func (r *Runtime) copyFromRegistry(ctx context.Context, ref types.ImageReference
}
if !options.AllTags {
return r.copySingleImageFromRegistry(ctx, inputName, pullPolicy, options)
pulled, err := r.copySingleImageFromRegistry(ctx, inputName, pullPolicy, options)
if err != nil {
return nil, err
}
return []string{pulled}, nil
}
// Copy all tags
@ -447,68 +450,19 @@ func (r *Runtime) copyFromRegistry(ctx context.Context, ref types.ImageReference
if err != nil {
return nil, err
}
pulledIDs = append(pulledIDs, pulled...)
pulledIDs = append(pulledIDs, pulled)
}
return pulledIDs, nil
}
// imageIDsForManifest() parses the manifest of the copied image and then looks
// up the IDs of the matching image. There's a small slice of time, between
// when we copy the image into local storage and when we go to look for it
// using the name that we gave it when we copied it, when the name we wanted to
// assign to the image could have been moved, but the image's ID will remain
// the same until it is deleted.
func (r *Runtime) imagesIDsForManifest(manifestBytes []byte, sys *types.SystemContext) ([]string, error) {
var imageDigest digest.Digest
manifestType := manifest.GuessMIMEType(manifestBytes)
if manifest.MIMETypeIsMultiImage(manifestType) {
list, err := manifest.ListFromBlob(manifestBytes, manifestType)
if err != nil {
return nil, fmt.Errorf("parsing manifest list: %w", err)
}
d, err := list.ChooseInstance(sys)
if err != nil {
return nil, fmt.Errorf("choosing instance from manifest list: %w", err)
}
imageDigest = d
} else {
d, err := manifest.Digest(manifestBytes)
if err != nil {
return nil, errors.New("digesting manifest")
}
imageDigest = d
}
images, err := r.store.ImagesByDigest(imageDigest)
if err != nil {
return nil, fmt.Errorf("listing images by manifest digest: %w", err)
}
// If you have additionStores defined and the same image stored in
// both storage and additional store, it can be output twice.
// Fixes github.com/containers/podman/issues/18647
results := []string{}
imageMap := map[string]bool{}
for _, image := range images {
if imageMap[image.ID] {
continue
}
imageMap[image.ID] = true
results = append(results, image.ID)
}
if len(results) == 0 {
return nil, fmt.Errorf("identifying new image by manifest digest: %w", storage.ErrImageUnknown)
}
return results, nil
}
// copySingleImageFromRegistry pulls the specified, possibly unqualified, name
// from a registry. On successful pull it returns the ID of the image in local
// storage.
func (r *Runtime) copySingleImageFromRegistry(ctx context.Context, imageName string, pullPolicy config.PullPolicy, options *PullOptions) ([]string, error) { //nolint:gocyclo
// storage (or, FIXME, a name/ID? that could be resolved in local storage)
func (r *Runtime) copySingleImageFromRegistry(ctx context.Context, imageName string, pullPolicy config.PullPolicy, options *PullOptions) (string, error) { //nolint:gocyclo
// Sanity check.
if err := pullPolicy.Validate(); err != nil {
return nil, err
return "", err
}
var (
@ -533,6 +487,14 @@ func (r *Runtime) copySingleImageFromRegistry(ctx context.Context, imageName str
if options.OS != runtime.GOOS {
lookupImageOptions.OS = options.OS
}
// FIXME: We sometimes return resolvedImageName from this function.
// The function documentation says this returns an image ID, resolvedImageName is frequently not an image ID.
//
// Ultimately Runtime.Pull looks up the returned name... again, possibly finding some other match
// than we did.
//
// This should be restructured so that the image we found here is returned to the caller of Pull
// directly, without another image -> name -> image round-trip and possible inconsistency.
localImage, resolvedImageName, err = r.LookupImage(imageName, lookupImageOptions)
if err != nil && !errors.Is(err, storage.ErrImageUnknown) {
logrus.Errorf("Looking up %s in local storage: %v", imageName, err)
@ -563,23 +525,23 @@ func (r *Runtime) copySingleImageFromRegistry(ctx context.Context, imageName str
if pullPolicy == config.PullPolicyNever {
if localImage != nil {
logrus.Debugf("Pull policy %q and %s resolved to local image %s", pullPolicy, imageName, resolvedImageName)
return []string{resolvedImageName}, nil
return resolvedImageName, nil
}
logrus.Debugf("Pull policy %q but no local image has been found for %s", pullPolicy, imageName)
return nil, fmt.Errorf("%s: %w", imageName, storage.ErrImageUnknown)
return "", fmt.Errorf("%s: %w", imageName, storage.ErrImageUnknown)
}
if pullPolicy == config.PullPolicyMissing && localImage != nil {
return []string{resolvedImageName}, nil
return resolvedImageName, nil
}
// If we looked up the image by ID, we cannot really pull from anywhere.
if localImage != nil && strings.HasPrefix(localImage.ID(), imageName) {
switch pullPolicy {
case config.PullPolicyAlways:
return nil, fmt.Errorf("pull policy is always but image has been referred to by ID (%s)", imageName)
return "", fmt.Errorf("pull policy is always but image has been referred to by ID (%s)", imageName)
default:
return []string{resolvedImageName}, nil
return resolvedImageName, nil
}
}
@ -604,9 +566,9 @@ func (r *Runtime) copySingleImageFromRegistry(ctx context.Context, imageName str
resolved, err := shortnames.Resolve(sys, imageName)
if err != nil {
if localImage != nil && pullPolicy == config.PullPolicyNewer {
return []string{resolvedImageName}, nil
return resolvedImageName, nil
}
return nil, err
return "", err
}
// NOTE: Below we print the description from the short-name resolution.
@ -636,9 +598,10 @@ func (r *Runtime) copySingleImageFromRegistry(ctx context.Context, imageName str
if socketPath, ok := os.LookupEnv("NOTIFY_SOCKET"); ok {
options.extendTimeoutSocket = socketPath
}
c, err := r.newCopier(&options.CopyOptions)
var resolvedReference types.ImageReference
c, err := r.newCopier(&options.CopyOptions, &resolvedReference)
if err != nil {
return nil, err
return "", err
}
defer c.Close()
@ -648,7 +611,7 @@ func (r *Runtime) copySingleImageFromRegistry(ctx context.Context, imageName str
logrus.Debugf("Attempting to pull candidate %s for %s", candidateString, imageName)
srcRef, err := registryTransport.NewReference(candidate.Value)
if err != nil {
return nil, err
return "", err
}
if pullPolicy == config.PullPolicyNewer && localImage != nil {
@ -666,19 +629,18 @@ func (r *Runtime) copySingleImageFromRegistry(ctx context.Context, imageName str
destRef, err := storageTransport.Transport.ParseStoreReference(r.store, candidate.Value.String())
if err != nil {
return nil, err
return "", err
}
if err := writeDesc(); err != nil {
return nil, err
return "", err
}
if options.Writer != nil {
if _, err := io.WriteString(options.Writer, fmt.Sprintf("Trying to pull %s...\n", candidateString)); err != nil {
return nil, err
return "", err
}
}
var manifestBytes []byte
if manifestBytes, err = c.Copy(ctx, srcRef, destRef); err != nil {
if _, err := c.Copy(ctx, srcRef, destRef); err != nil {
logrus.Debugf("Error pulling candidate %s: %v", candidateString, err)
pullErrors = append(pullErrors, err)
continue
@ -691,19 +653,23 @@ func (r *Runtime) copySingleImageFromRegistry(ctx context.Context, imageName str
}
logrus.Debugf("Pulled candidate %s successfully", candidateString)
if ids, err := r.imagesIDsForManifest(manifestBytes, sys); err == nil {
return ids, nil
if resolvedReference == nil { // resolvedReference should always be set for storageTransport destinations
return "", fmt.Errorf("internal error: After pulling %s, resolvedReference is nil", candidateString)
}
return []string{candidate.Value.String()}, nil
_, image, err := storageTransport.ResolveReference(resolvedReference)
if err != nil {
return "", fmt.Errorf("resolving an already-resolved reference %q to the pulled image: %w", transports.ImageName(resolvedReference), err)
}
return image.ID, nil
}
if localImage != nil && pullPolicy == config.PullPolicyNewer {
return []string{resolvedImageName}, nil
return resolvedImageName, nil
}
if len(pullErrors) == 0 {
return nil, fmt.Errorf("internal error: no image pulled (pull policy %s)", pullPolicy)
return "", fmt.Errorf("internal error: no image pulled (pull policy %s)", pullPolicy)
}
return nil, resolved.FormatPullErrors(pullErrors)
return "", resolved.FormatPullErrors(pullErrors)
}

View file

@ -109,7 +109,7 @@ func (r *Runtime) Push(ctx context.Context, source, destination string, options
}
}
c, err := r.newCopier(&options.CopyOptions)
c, err := r.newCopier(&options.CopyOptions, nil)
if err != nil {
return nil, err
}

View file

@ -119,7 +119,7 @@ func (r *Runtime) saveSingleImage(ctx context.Context, name, format, path string
return err
}
c, err := r.newCopier(&options.CopyOptions)
c, err := r.newCopier(&options.CopyOptions, nil)
if err != nil {
return err
}
@ -204,7 +204,7 @@ func (r *Runtime) saveDockerArchive(ctx context.Context, names []string, path st
copyOpts := options.CopyOptions
copyOpts.dockerArchiveAdditionalTags = local.tags
c, err := r.newCopier(&copyOpts)
c, err := r.newCopier(&copyOpts, nil)
if err != nil {
return err
}

View file

@ -201,7 +201,7 @@ func (n *Netns) setupPasta(nsPath string) error {
Netns: nsPath,
ExtraOptions: []string{"--pid", pidPath},
}
res, err := pasta.Setup2(&pastaOpts)
res, err := pasta.Setup(&pastaOpts)
if err != nil {
return fmt.Errorf("setting up Pasta: %w", err)
}

View file

@ -50,11 +50,6 @@ type SetupOptions struct {
ExtraOptions []string
}
// Setup2 alias for Setup()
func Setup2(opts *SetupOptions) (*SetupResult, error) {
return Setup(opts)
}
// Setup start the pasta process for the given netns.
// The pasta binary is looked up in the HelperBinariesDir and $PATH.
// Note that there is no need for any special cleanup logic, the pasta

View file

@ -16,14 +16,20 @@ import (
"strings"
"time"
// We are using skeema/knownhosts rather than
// golang.org/x/crypto/ssh/knownhosts because the
// latter has an issue when the first key returned
// by the server doesn't match the one in known_hosts:
// https://github.com/golang/go/issues/29286
// https://github.com/containers/podman/issues/23575
"github.com/containers/common/pkg/config"
"github.com/containers/storage/pkg/fileutils"
"github.com/containers/storage/pkg/homedir"
"github.com/pkg/sftp"
"github.com/sirupsen/logrus"
"github.com/skeema/knownhosts"
"golang.org/x/crypto/ssh"
"golang.org/x/crypto/ssh/agent"
"golang.org/x/crypto/ssh/knownhosts"
"golang.org/x/exp/maps"
)
@ -301,46 +307,44 @@ func ValidateAndConfigure(uri *url.URL, iden string, insecureIsMachineConnection
return nil, err
}
keyFilePath := filepath.Join(homedir.Get(), ".ssh", "known_hosts")
known, err := knownhosts.NewDB(keyFilePath)
if err != nil {
if !errors.Is(err, os.ErrNotExist) {
return nil, err
}
keyDir := path.Dir(keyFilePath)
if err := fileutils.Exists(keyDir); errors.Is(err, os.ErrNotExist) {
if err := os.Mkdir(keyDir, 0o700); err != nil {
return nil, err
}
}
k, err := os.OpenFile(keyFilePath, os.O_RDWR|os.O_CREATE, 0o600)
if err != nil {
return nil, err
}
k.Close()
known, err = knownhosts.NewDB(keyFilePath)
if err != nil {
return nil, err
}
}
var callback ssh.HostKeyCallback
if insecureIsMachineConnection {
callback = ssh.InsecureIgnoreHostKey()
} else {
callback = ssh.HostKeyCallback(func(host string, remote net.Addr, pubKey ssh.PublicKey) error {
keyFilePath := filepath.Join(homedir.Get(), ".ssh", "known_hosts")
known, err := knownhosts.New(keyFilePath)
if err != nil {
if !errors.Is(err, os.ErrNotExist) {
return err
}
keyDir := path.Dir(keyFilePath)
if err := fileutils.Exists(keyDir); errors.Is(err, os.ErrNotExist) {
if err := os.Mkdir(keyDir, 0o700); err != nil {
return err
}
}
k, err := os.OpenFile(keyFilePath, os.O_RDWR|os.O_CREATE, 0o600)
if err != nil {
return err
}
k.Close()
known, err = knownhosts.New(keyFilePath)
if err != nil {
return err
}
}
// we need to check if there is an error from reading known hosts for this public key and if there is an error, what is it, and why is it happening?
// if it is a key mismatch we want to error since we know the host using another key
// however, if it is a general error not because of a known key, we want to add our key to the known_hosts file
hErr := known(host, remote, pubKey)
var keyErr *knownhosts.KeyError
// if keyErr.Want is not empty, we are receiving a different key meaning the host is known but we are using the wrong key
as := errors.As(hErr, &keyErr)
hErr := known.HostKeyCallback()(host, remote, pubKey)
switch {
case as && len(keyErr.Want) > 0:
case knownhosts.IsHostKeyChanged(hErr):
logrus.Warnf("ssh host key mismatch for host %s, got key %s of type %s", host, ssh.FingerprintSHA256(pubKey), pubKey.Type())
return keyErr
return hErr
// if keyErr.Want is empty that just means we do not know this host yet, add it.
case as && len(keyErr.Want) == 0:
case knownhosts.IsHostUnknown(hErr):
// write to known_hosts
err := addKnownHostsEntry(host, pubKey)
if err != nil {
@ -358,10 +362,11 @@ func ValidateAndConfigure(uri *url.URL, iden string, insecureIsMachineConnection
}
cfg := &ssh.ClientConfig{
User: uri.User.Username(),
Auth: authMethods,
HostKeyCallback: callback,
Timeout: tick,
User: uri.User.Username(),
Auth: authMethods,
HostKeyCallback: callback,
Timeout: tick,
HostKeyAlgorithms: known.HostKeyAlgorithms(uri.Host),
}
return cfg, nil
}

View file

@ -23,18 +23,20 @@ func ConfigureContainerTimeZone(timezone, containerRunDir, mountPoint, etcPath,
switch {
case timezone == "":
return "", nil
case os.Getenv("TZDIR") != "":
// Allow using TZDIR per:
// https://sourceware.org/git/?p=glibc.git;a=blob;f=time/tzfile.c;h=8a923d0cccc927a106dc3e3c641be310893bab4e;hb=HEAD#l149
timezonePath = filepath.Join(os.Getenv("TZDIR"), timezone)
case timezone == "local":
timezonePath, err = filepath.EvalSymlinks("/etc/localtime")
if err != nil {
return "", fmt.Errorf("finding local timezone for container %s: %w", containerID, err)
}
default:
timezonePath = filepath.Join("/usr/share/zoneinfo", timezone)
// Allow using TZDIR per:
// https://sourceware.org/git/?p=glibc.git;a=blob;f=time/tzfile.c;h=8a923d0cccc927a106dc3e3c641be310893bab4e;hb=HEAD#l149
zoneinfo := os.Getenv("TZDIR")
if zoneinfo == "" {
// default zoneinfo location
zoneinfo = "/usr/share/zoneinfo"
}
timezonePath = filepath.Join(zoneinfo, timezone)
}
etcFd, err := openDirectory(etcPath)

View file

@ -137,6 +137,17 @@ type Options struct {
// DestinationCtx.CompressionFormat is used exclusively, and blobs of other
// compression algorithms are not reused.
ForceCompressionFormat bool
// ReportResolvedReference, if set, asks the destination transport to store
// a “resolved” (more detailed) reference to the created image
// into the value this option points to.
// What “resolved” means is transport-specific.
// Most transports don’t support this, and cause the value to be set to nil.
//
// For the containers-storage: transport, the reference contains an image ID,
// so that storage.ResolveReference returns exactly the created image.
// WARNING: It is unspecified whether the reference also contains a reference.Named element.
ReportResolvedReference *types.ImageReference
}
// OptionCompressionVariant allows to supply information about
@ -337,7 +348,13 @@ func Image(ctx context.Context, policyContext *signature.PolicyContext, destRef,
}
}
if err := c.dest.Commit(ctx, c.unparsedToplevel); err != nil {
if options.ReportResolvedReference != nil {
*options.ReportResolvedReference = nil // The default outcome, if not specifically supported by the transport.
}
if err := c.dest.CommitWithOptions(ctx, private.CommitOptions{
UnparsedToplevel: c.unparsedToplevel,
ReportResolvedReference: options.ReportResolvedReference,
}); err != nil {
return nil, fmt.Errorf("committing the finished image: %w", err)
}

View file

@ -828,7 +828,7 @@ func (ic *imageCopier) copyLayer(ctx context.Context, srcInfo types.BlobInfo, to
return false, types.BlobInfo{}, err
}()
if err != nil {
return types.BlobInfo{}, "", fmt.Errorf("reading blob %s: %w", srcInfo.Digest, err)
return types.BlobInfo{}, "", fmt.Errorf("partial pull of blob %s: %w", srcInfo.Digest, err)
}
if reused {
return blobInfo, cachedDiffID, nil

View file

@ -251,14 +251,11 @@ func (d *dirImageDestination) PutSignaturesWithFormat(ctx context.Context, signa
return nil
}
// Commit marks the process of storing the image as successful and asks for the image to be persisted.
// unparsedToplevel contains data about the top-level manifest of the source (which may be a single-arch image or a manifest list
// if PutManifest was only called for the single-arch image with instanceDigest == nil), primarily to allow lookups by the
// original manifest list digest, if desired.
// CommitWithOptions marks the process of storing the image as successful and asks for the image to be persisted.
// WARNING: This does not have any transactional semantics:
// - Uploaded data MAY be visible to others before Commit() is called
// - Uploaded data MAY be removed or MAY remain around if Close() is called without Commit() (i.e. rollback is allowed but not guaranteed)
func (d *dirImageDestination) Commit(context.Context, types.UnparsedImage) error {
// - Uploaded data MAY be visible to others before CommitWithOptions() is called
// - Uploaded data MAY be removed or MAY remain around if Close() is called without CommitWithOptions() (i.e. rollback is allowed but not guaranteed)
func (d *dirImageDestination) CommitWithOptions(ctx context.Context, options private.CommitOptions) error {
return nil
}

View file

@ -34,16 +34,17 @@ func newImageDestination(sys *types.SystemContext, ref archiveReference) (privat
writer = w
closeWriter = true
}
tarDest := tarfile.NewDestination(sys, writer.archive, ref.Transport().Name(), ref.ref)
if sys != nil && sys.DockerArchiveAdditionalTags != nil {
tarDest.AddRepoTags(sys.DockerArchiveAdditionalTags)
}
return &archiveImageDestination{
Destination: tarDest,
d := &archiveImageDestination{
ref: ref,
writer: writer,
closeWriter: closeWriter,
}, nil
}
tarDest := tarfile.NewDestination(sys, writer.archive, ref.Transport().Name(), ref.ref, d.CommitWithOptions)
if sys != nil && sys.DockerArchiveAdditionalTags != nil {
tarDest.AddRepoTags(sys.DockerArchiveAdditionalTags)
}
d.Destination = tarDest
return d, nil
}
// Reference returns the reference used to set up this destination. Note that this should directly correspond to user's intent,
@ -60,14 +61,11 @@ func (d *archiveImageDestination) Close() error {
return nil
}
// Commit marks the process of storing the image as successful and asks for the image to be persisted.
// unparsedToplevel contains data about the top-level manifest of the source (which may be a single-arch image or a manifest list
// if PutManifest was only called for the single-arch image with instanceDigest == nil), primarily to allow lookups by the
// original manifest list digest, if desired.
// CommitWithOptions marks the process of storing the image as successful and asks for the image to be persisted.
// WARNING: This does not have any transactional semantics:
// - Uploaded data MAY be visible to others before Commit() is called
// - Uploaded data MAY be removed or MAY remain around if Close() is called without Commit() (i.e. rollback is allowed but not guaranteed)
func (d *archiveImageDestination) Commit(ctx context.Context, unparsedToplevel types.UnparsedImage) error {
// - Uploaded data MAY be visible to others before CommitWithOptions() is called
// - Uploaded data MAY be removed or MAY remain around if Close() is called without CommitWithOptions() (i.e. rollback is allowed but not guaranteed)
func (d *archiveImageDestination) CommitWithOptions(ctx context.Context, options private.CommitOptions) error {
d.writer.imageCommitted()
if d.closeWriter {
// We could do this only in .Close(), but failures in .Close() are much more likely to be

View file

@ -197,7 +197,7 @@ func (br *bodyReader) Read(p []byte) (int, error) {
consumedBody = true
br.body = res.Body
br.lastRetryOffset = br.offset
br.lastRetryTime = time.Time{}
br.lastRetryTime = time.Now()
return n, nil
default:

View file

@ -56,16 +56,17 @@ func newImageDestination(ctx context.Context, sys *types.SystemContext, ref daem
goroutineContext, goroutineCancel := context.WithCancel(ctx)
go imageLoadGoroutine(goroutineContext, c, reader, statusChannel)
return &daemonImageDestination{
d := &daemonImageDestination{
ref: ref,
mustMatchRuntimeOS: mustMatchRuntimeOS,
Destination: tarfile.NewDestination(sys, archive, ref.Transport().Name(), namedTaggedRef),
archive: archive,
goroutineCancel: goroutineCancel,
statusChannel: statusChannel,
writer: writer,
committed: false,
}, nil
}
d.Destination = tarfile.NewDestination(sys, archive, ref.Transport().Name(), namedTaggedRef, d.CommitWithOptions)
return d, nil
}
// imageLoadGoroutine accepts tar stream on reader, sends it to c, and reports error or success by writing to statusChannel
@ -146,7 +147,7 @@ func (d *daemonImageDestination) Close() error {
// immediately, and hopefully, through terminating the sending which uses "Transfer-Encoding: chunked"" without sending
// the terminating zero-length chunk, prevent the docker daemon from processing the tar stream at all.
// Whether that works or not, closing the PipeWriter seems desirable in any case.
if err := d.writer.CloseWithError(errors.New("Aborting upload, daemonImageDestination closed without a previous .Commit()")); err != nil {
if err := d.writer.CloseWithError(errors.New("Aborting upload, daemonImageDestination closed without a previous .CommitWithOptions()")); err != nil {
return err
}
}
@ -159,14 +160,11 @@ func (d *daemonImageDestination) Reference() types.ImageReference {
return d.ref
}
// Commit marks the process of storing the image as successful and asks for the image to be persisted.
// unparsedToplevel contains data about the top-level manifest of the source (which may be a single-arch image or a manifest list
// if PutManifest was only called for the single-arch image with instanceDigest == nil), primarily to allow lookups by the
// original manifest list digest, if desired.
// CommitWithOptions marks the process of storing the image as successful and asks for the image to be persisted.
// WARNING: This does not have any transactional semantics:
// - Uploaded data MAY be visible to others before Commit() is called
// - Uploaded data MAY be removed or MAY remain around if Close() is called without Commit() (i.e. rollback is allowed but not guaranteed)
func (d *daemonImageDestination) Commit(ctx context.Context, unparsedToplevel types.UnparsedImage) error {
// - Uploaded data MAY be visible to others before CommitWithOptions() is called
// - Uploaded data MAY be removed or MAY remain around if Close() is called without CommitWithOptions() (i.e. rollback is allowed but not guaranteed)
func (d *daemonImageDestination) CommitWithOptions(ctx context.Context, options private.CommitOptions) error {
logrus.Debugf("docker-daemon: Closing tar stream")
if err := d.archive.Close(); err != nil {
return err

View file

@ -923,13 +923,10 @@ func (d *dockerImageDestination) putSignaturesToAPIExtension(ctx context.Context
return nil
}
// Commit marks the process of storing the image as successful and asks for the image to be persisted.
// unparsedToplevel contains data about the top-level manifest of the source (which may be a single-arch image or a manifest list
// if PutManifest was only called for the single-arch image with instanceDigest == nil), primarily to allow lookups by the
// original manifest list digest, if desired.
// CommitWithOptions marks the process of storing the image as successful and asks for the image to be persisted.
// WARNING: This does not have any transactional semantics:
// - Uploaded data MAY be visible to others before Commit() is called
// - Uploaded data MAY be removed or MAY remain around if Close() is called without Commit() (i.e. rollback is allowed but not guaranteed)
func (d *dockerImageDestination) Commit(context.Context, types.UnparsedImage) error {
// - Uploaded data MAY be visible to others before CommitWithOptions() is called
// - Uploaded data MAY be removed or MAY remain around if Close() is called without CommitWithOptions() (i.e. rollback is allowed but not guaranteed)
func (d *dockerImageDestination) CommitWithOptions(ctx context.Context, options private.CommitOptions) error {
return nil
}

View file

@ -20,22 +20,25 @@ import (
"github.com/sirupsen/logrus"
)
// Destination is a partial implementation of private.ImageDestination for writing to an io.Writer.
// Destination is a partial implementation of private.ImageDestination for writing to a Writer.
type Destination struct {
impl.Compat
impl.PropertyMethodsInitialize
stubs.NoPutBlobPartialInitialize
stubs.NoSignaturesInitialize
archive *Writer
repoTags []reference.NamedTagged
archive *Writer
commitWithOptions func(ctx context.Context, options private.CommitOptions) error
repoTags []reference.NamedTagged
// Other state.
config []byte
sysCtx *types.SystemContext
}
// NewDestination returns a tarfile.Destination adding images to the specified Writer.
func NewDestination(sys *types.SystemContext, archive *Writer, transportName string, ref reference.NamedTagged) *Destination {
// commitWithOptions implements ImageDestination.CommitWithOptions.
func NewDestination(sys *types.SystemContext, archive *Writer, transportName string, ref reference.NamedTagged,
commitWithOptions func(ctx context.Context, options private.CommitOptions) error) *Destination {
repoTags := []reference.NamedTagged{}
if ref != nil {
repoTags = append(repoTags, ref)
@ -57,9 +60,10 @@ func NewDestination(sys *types.SystemContext, archive *Writer, transportName str
NoPutBlobPartialInitialize: stubs.NoPutBlobPartialRaw(transportName),
NoSignaturesInitialize: stubs.NoSignatures("Storing signatures for docker tar files is not supported"),
archive: archive,
repoTags: repoTags,
sysCtx: sys,
archive: archive,
commitWithOptions: commitWithOptions,
repoTags: repoTags,
sysCtx: sys,
}
dest.Compat = impl.AddCompat(dest)
return dest
@ -179,3 +183,13 @@ func (d *Destination) PutManifest(ctx context.Context, m []byte, instanceDigest
return d.archive.ensureManifestItemLocked(man.LayersDescriptors, man.ConfigDescriptor.Digest, d.repoTags)
}
// CommitWithOptions marks the process of storing the image as successful and asks for the image to be persisted.
// WARNING: This does not have any transactional semantics:
// - Uploaded data MAY be visible to others before CommitWithOptions() is called
// - Uploaded data MAY be removed or MAY remain around if Close() is called without CommitWithOptions() (i.e. rollback is allowed but not guaranteed)
func (d *Destination) CommitWithOptions(ctx context.Context, options private.CommitOptions) error {
// This indirection exists because impl.Compat expects all ImageDestinationInternalOnly methods
// to be implemented in one place.
return d.commitWithOptions(ctx, options)
}

View file

@ -99,3 +99,16 @@ func (c *Compat) PutSignatures(ctx context.Context, signatures [][]byte, instanc
}
return c.dest.PutSignaturesWithFormat(ctx, withFormat, instanceDigest)
}
// Commit marks the process of storing the image as successful and asks for the image to be persisted.
// unparsedToplevel contains data about the top-level manifest of the source (which may be a single-arch image or a manifest list
// if PutManifest was only called for the single-arch image with instanceDigest == nil), primarily to allow lookups by the
// original manifest list digest, if desired.
// WARNING: This does not have any transactional semantics:
// - Uploaded data MAY be visible to others before Commit() is called
// - Uploaded data MAY be removed or MAY remain around if Close() is called without Commit() (i.e. rollback is allowed but not guaranteed)
func (c *Compat) Commit(ctx context.Context, unparsedToplevel types.UnparsedImage) error {
return c.dest.CommitWithOptions(ctx, private.CommitOptions{
UnparsedToplevel: unparsedToplevel,
})
}

View file

@ -97,3 +97,11 @@ func (w *wrapped) PutSignaturesWithFormat(ctx context.Context, signatures []sign
}
return w.PutSignatures(ctx, simpleSigs, instanceDigest)
}
// CommitWithOptions marks the process of storing the image as successful and asks for the image to be persisted.
// WARNING: This does not have any transactional semantics:
// - Uploaded data MAY be visible to others before CommitWithOptions() is called
// - Uploaded data MAY be removed or MAY remain around if Close() is called without CommitWithOptions() (i.e. rollback is allowed but not guaranteed)
func (w *wrapped) CommitWithOptions(ctx context.Context, options private.CommitOptions) error {
return w.Commit(ctx, options.UnparsedToplevel)
}

View file

@ -70,6 +70,12 @@ type ImageDestinationInternalOnly interface {
// (when the primary manifest is a manifest list); this should always be nil if the primary manifest is not a manifest list.
// MUST be called after PutManifest (signatures may reference manifest contents).
PutSignaturesWithFormat(ctx context.Context, signatures []signature.Signature, instanceDigest *digest.Digest) error
// CommitWithOptions marks the process of storing the image as successful and asks for the image to be persisted.
// WARNING: This does not have any transactional semantics:
// - Uploaded data MAY be visible to others before CommitWithOptions() is called
// - Uploaded data MAY be removed or MAY remain around if Close() is called without CommitWithOptions() (i.e. rollback is allowed but not guaranteed)
CommitWithOptions(ctx context.Context, options CommitOptions) error
}
// ImageDestination is an internal extension to the types.ImageDestination
@ -146,6 +152,19 @@ type ReusedBlob struct {
MatchedByTOCDigest bool // Whether the layer was reused/matched by TOC digest. Used only for UI purposes.
}
// CommitOptions are used in CommitWithOptions
type CommitOptions struct {
// UnparsedToplevel contains data about the top-level manifest of the source (which may be a single-arch image or a manifest list
// if PutManifest was only called for the single-arch image with instanceDigest == nil), primarily to allow lookups by the
// original manifest list digest, if desired.
UnparsedToplevel types.UnparsedImage
// ReportResolvedReference, if set, asks the transport to store a “resolved” (more detailed) reference to the created image
// into the value this option points to.
// What “resolved” means is transport-specific.
// Transports which don’t support reporting resolved references can ignore the field; the generic copy code writes "nil" into the value.
ReportResolvedReference *types.ImageReference
}
// ImageSourceChunk is a portion of a blob.
// This API is experimental and can be changed without bumping the major version number.
type ImageSourceChunk struct {

View file

@ -150,13 +150,12 @@ func (d *ociArchiveImageDestination) PutSignaturesWithFormat(ctx context.Context
return d.unpackedDest.PutSignaturesWithFormat(ctx, signatures, instanceDigest)
}
// Commit marks the process of storing the image as successful and asks for the image to be persisted
// unparsedToplevel contains data about the top-level manifest of the source (which may be a single-arch image or a manifest list
// if PutManifest was only called for the single-arch image with instanceDigest == nil), primarily to allow lookups by the
// original manifest list digest, if desired.
// after the directory is made, it is tarred up into a file and the directory is deleted
func (d *ociArchiveImageDestination) Commit(ctx context.Context, unparsedToplevel types.UnparsedImage) error {
if err := d.unpackedDest.Commit(ctx, unparsedToplevel); err != nil {
// CommitWithOptions marks the process of storing the image as successful and asks for the image to be persisted.
// WARNING: This does not have any transactional semantics:
// - Uploaded data MAY be visible to others before CommitWithOptions() is called
// - Uploaded data MAY be removed or MAY remain around if Close() is called without CommitWithOptions() (i.e. rollback is allowed but not guaranteed)
func (d *ociArchiveImageDestination) CommitWithOptions(ctx context.Context, options private.CommitOptions) error {
if err := d.unpackedDest.CommitWithOptions(ctx, options); err != nil {
return fmt.Errorf("storing image %q: %w", d.ref.image, err)
}

View file

@ -278,14 +278,11 @@ func (d *ociImageDestination) addManifest(desc *imgspecv1.Descriptor) {
d.index.Manifests = append(slices.Clone(d.index.Manifests), *desc)
}
// Commit marks the process of storing the image as successful and asks for the image to be persisted.
// unparsedToplevel contains data about the top-level manifest of the source (which may be a single-arch image or a manifest list
// if PutManifest was only called for the single-arch image with instanceDigest == nil), primarily to allow lookups by the
// original manifest list digest, if desired.
// CommitWithOptions marks the process of storing the image as successful and asks for the image to be persisted.
// WARNING: This does not have any transactional semantics:
// - Uploaded data MAY be visible to others before Commit() is called
// - Uploaded data MAY be removed or MAY remain around if Close() is called without Commit() (i.e. rollback is allowed but not guaranteed)
func (d *ociImageDestination) Commit(context.Context, types.UnparsedImage) error {
// - Uploaded data MAY be visible to others before CommitWithOptions() is called
// - Uploaded data MAY be removed or MAY remain around if Close() is called without CommitWithOptions() (i.e. rollback is allowed but not guaranteed)
func (d *ociImageDestination) CommitWithOptions(ctx context.Context, options private.CommitOptions) error {
layoutBytes, err := json.Marshal(imgspecv1.ImageLayout{
Version: imgspecv1.ImageLayoutVersion,
})

View file

@ -236,13 +236,10 @@ func (d *openshiftImageDestination) PutSignaturesWithFormat(ctx context.Context,
return nil
}
// Commit marks the process of storing the image as successful and asks for the image to be persisted.
// unparsedToplevel contains data about the top-level manifest of the source (which may be a single-arch image or a manifest list
// if PutManifest was only called for the single-arch image with instanceDigest == nil), primarily to allow lookups by the
// original manifest list digest, if desired.
// CommitWithOptions marks the process of storing the image as successful and asks for the image to be persisted.
// WARNING: This does not have any transactional semantics:
// - Uploaded data MAY be visible to others before Commit() is called
// - Uploaded data MAY be removed or MAY remain around if Close() is called without Commit() (i.e. rollback is allowed but not guaranteed)
func (d *openshiftImageDestination) Commit(ctx context.Context, unparsedToplevel types.UnparsedImage) error {
return d.docker.Commit(ctx, unparsedToplevel)
// - Uploaded data MAY be visible to others before CommitWithOptions() is called
// - Uploaded data MAY be removed or MAY remain around if Close() is called without CommitWithOptions() (i.e. rollback is allowed but not guaranteed)
func (d *openshiftImageDestination) CommitWithOptions(ctx context.Context, options private.CommitOptions) error {
return d.docker.CommitWithOptions(ctx, options)
}

View file

@ -435,7 +435,11 @@ func (d *ostreeImageDestination) PutSignaturesWithFormat(ctx context.Context, si
return nil
}
func (d *ostreeImageDestination) Commit(context.Context, types.UnparsedImage) error {
// CommitWithOptions marks the process of storing the image as successful and asks for the image to be persisted.
// WARNING: This does not have any transactional semantics:
// - Uploaded data MAY be visible to others before CommitWithOptions() is called
// - Uploaded data MAY be removed or MAY remain around if Close() is called without CommitWithOptions() (i.e. rollback is allowed but not guaranteed)
func (d *ostreeImageDestination) CommitWithOptions(ctx context.Context, options private.CommitOptions) error {
runtime.LockOSThread()
defer runtime.UnlockOSThread()

View file

@ -307,6 +307,10 @@ func (d *blobCacheDestination) PutSignaturesWithFormat(ctx context.Context, sign
return d.destination.PutSignaturesWithFormat(ctx, signatures, instanceDigest)
}
func (d *blobCacheDestination) Commit(ctx context.Context, unparsedToplevel types.UnparsedImage) error {
return d.destination.Commit(ctx, unparsedToplevel)
// CommitWithOptions marks the process of storing the image as successful and asks for the image to be persisted.
// WARNING: This does not have any transactional semantics:
// - Uploaded data MAY be visible to others before CommitWithOptions() is called
// - Uploaded data MAY be removed or MAY remain around if Close() is called without CommitWithOptions() (i.e. rollback is allowed but not guaranteed)
func (d *blobCacheDestination) CommitWithOptions(ctx context.Context, options private.CommitOptions) error {
return d.destination.CommitWithOptions(ctx, options)
}

View file

@ -99,8 +99,18 @@ func CompressStream(dest io.Writer, algo Algorithm, level *int) (io.WriteCloser,
return internal.AlgorithmCompressor(algo)(dest, m, level)
}
// CompressStreamWithMetadata returns the compressor by its name. If the compression
// generates any metadata, it is written to the provided metadata map.
// CompressStreamWithMetadata returns the compressor by its name.
//
// Compressing a stream may create integrity data that allows consuming the compressed byte stream
// while only using subsets of the compressed data (if the compressed data is seekable and most
// of the uncompressed data is already present via other means), while still protecting integrity
// of the compressed stream against unwanted modification. (In OCI container images, this metadata
// is usually carried in manifest annotations.)
//
// Such a partial decompression is not implemented by this package; it is consumed e.g. by
// github.com/containers/storage/pkg/chunked .
//
// If the compression generates such metadata, it is written to the provided metadata map.
func CompressStreamWithMetadata(dest io.Writer, metadata map[string]string, algo Algorithm, level *int) (io.WriteCloser, error) {
return internal.AlgorithmCompressor(algo)(dest, metadata, level)
}

View file

@ -3,6 +3,15 @@ package internal
import "io"
// CompressorFunc writes the compressed stream to the given writer using the specified compression level.
//
// Compressing a stream may create integrity data that allows consuming the compressed byte stream
// while only using subsets of the compressed data (if the compressed data is seekable and most
// of the uncompressed data is already present via other means), while still protecting integrity
// of the compressed stream against unwanted modification. (In OCI container images, this metadata
// is usually carried in manifest annotations.)
//
// If the compression generates such metadata, it is written to the provided metadata map.
//
// The caller must call Close() on the stream (even if the input stream does not need closing!).
type CompressorFunc func(io.Writer, map[string]string, *int) (io.WriteCloser, error)

View file

@ -335,7 +335,7 @@ func (s *storageImageDestination) PutBlobPartial(ctx context.Context, chunkAcces
out, err := s.imageRef.transport.store.PrepareStagedLayer(nil, differ)
if err != nil {
return private.UploadedBlob{}, err
return private.UploadedBlob{}, fmt.Errorf("staging a partially-pulled layer: %w", err)
}
succeeded := false
defer func() {
@ -930,7 +930,7 @@ func (s *storageImageDestination) createNewLayer(index int, layerDigest digest.D
flags := make(map[string]interface{})
if untrustedUncompressedDigest != "" {
flags[expectedLayerDiffIDFlag] = untrustedUncompressedDigest
flags[expectedLayerDiffIDFlag] = untrustedUncompressedDigest.String()
logrus.Debugf("Setting uncompressed digest to %q for layer %q", untrustedUncompressedDigest, newLayerID)
}
@ -1124,26 +1124,23 @@ func (s *storageImageDestination) untrustedLayerDiffID(layerIndex int) (digest.D
return s.untrustedDiffIDValues[layerIndex], nil
}
// Commit marks the process of storing the image as successful and asks for the image to be persisted.
// unparsedToplevel contains data about the top-level manifest of the source (which may be a single-arch image or a manifest list
// if PutManifest was only called for the single-arch image with instanceDigest == nil), primarily to allow lookups by the
// original manifest list digest, if desired.
// CommitWithOptions marks the process of storing the image as successful and asks for the image to be persisted.
// WARNING: This does not have any transactional semantics:
// - Uploaded data MAY be visible to others before Commit() is called
// - Uploaded data MAY be removed or MAY remain around if Close() is called without Commit() (i.e. rollback is allowed but not guaranteed)
func (s *storageImageDestination) Commit(ctx context.Context, unparsedToplevel types.UnparsedImage) error {
// - Uploaded data MAY be visible to others before CommitWithOptions() is called
// - Uploaded data MAY be removed or MAY remain around if Close() is called without CommitWithOptions() (i.e. rollback is allowed but not guaranteed)
func (s *storageImageDestination) CommitWithOptions(ctx context.Context, options private.CommitOptions) error {
// This function is outside of the scope of HasThreadSafePutBlob, so we don’t need to hold s.lock.
if len(s.manifest) == 0 {
return errors.New("Internal error: storageImageDestination.Commit() called without PutManifest()")
return errors.New("Internal error: storageImageDestination.CommitWithOptions() called without PutManifest()")
}
toplevelManifest, _, err := unparsedToplevel.Manifest(ctx)
toplevelManifest, _, err := options.UnparsedToplevel.Manifest(ctx)
if err != nil {
return fmt.Errorf("retrieving top-level manifest: %w", err)
}
// If the name we're saving to includes a digest, then check that the
// manifests that we're about to save all either match the one from the
// unparsedToplevel, or match the digest in the name that we're using.
// options.UnparsedToplevel, or match the digest in the name that we're using.
if s.imageRef.named != nil {
if digested, ok := s.imageRef.named.(reference.Digested); ok {
matches, err := manifest.MatchesDigest(s.manifest, digested.Digest())
@ -1176,24 +1173,24 @@ func (s *storageImageDestination) Commit(ctx context.Context, unparsedToplevel t
}, blob.Size); err != nil {
return err
} else if stopQueue {
return fmt.Errorf("Internal error: storageImageDestination.Commit(): commitLayer() not ready to commit for layer %q", blob.Digest)
return fmt.Errorf("Internal error: storageImageDestination.CommitWithOptions(): commitLayer() not ready to commit for layer %q", blob.Digest)
}
}
var lastLayer string
if len(layerBlobs) > 0 { // Zero-layer images rarely make sense, but it is technically possible, and may happen for non-image artifacts.
prev, ok := s.indexToStorageID[len(layerBlobs)-1]
if !ok {
return fmt.Errorf("Internal error: storageImageDestination.Commit(): previous layer %d hasn't been committed (lastLayer == nil)", len(layerBlobs)-1)
return fmt.Errorf("Internal error: storageImageDestination.CommitWithOptions(): previous layer %d hasn't been committed (lastLayer == nil)", len(layerBlobs)-1)
}
lastLayer = prev
}
// If one of those blobs was a configuration blob, then we can try to dig out the date when the image
// was originally created, in case we're just copying it. If not, no harm done.
options := &storage.ImageOptions{}
imgOptions := &storage.ImageOptions{}
if inspect, err := man.Inspect(s.getConfigBlob); err == nil && inspect.Created != nil {
logrus.Debugf("setting image creation date to %s", inspect.Created)
options.CreationDate = *inspect.Created
imgOptions.CreationDate = *inspect.Created
}
// Set up to save the non-layer blobs as data items. Since we only share layers, they should all be in files, so
@ -1210,13 +1207,13 @@ func (s *storageImageDestination) Commit(ctx context.Context, unparsedToplevel t
if err != nil {
return fmt.Errorf("copying non-layer blob %q to image: %w", blob, err)
}
options.BigData = append(options.BigData, storage.ImageBigDataOption{
imgOptions.BigData = append(imgOptions.BigData, storage.ImageBigDataOption{
Key: blob.String(),
Data: v,
Digest: digest.Canonical.FromBytes(v),
})
}
// Set up to save the unparsedToplevel's manifest if it differs from
// Set up to save the options.UnparsedToplevel's manifest if it differs from
// the per-platform one, which is saved below.
if len(toplevelManifest) != 0 && !bytes.Equal(toplevelManifest, s.manifest) {
manifestDigest, err := manifest.Digest(toplevelManifest)
@ -1227,7 +1224,7 @@ func (s *storageImageDestination) Commit(ctx context.Context, unparsedToplevel t
if err != nil {
return err
}
options.BigData = append(options.BigData, storage.ImageBigDataOption{
imgOptions.BigData = append(imgOptions.BigData, storage.ImageBigDataOption{
Key: key,
Data: toplevelManifest,
Digest: manifestDigest,
@ -1240,19 +1237,19 @@ func (s *storageImageDestination) Commit(ctx context.Context, unparsedToplevel t
if err != nil {
return err
}
options.BigData = append(options.BigData, storage.ImageBigDataOption{
imgOptions.BigData = append(imgOptions.BigData, storage.ImageBigDataOption{
Key: key,
Data: s.manifest,
Digest: s.manifestDigest,
})
options.BigData = append(options.BigData, storage.ImageBigDataOption{
imgOptions.BigData = append(imgOptions.BigData, storage.ImageBigDataOption{
Key: storage.ImageDigestBigDataKey,
Data: s.manifest,
Digest: s.manifestDigest,
})
// Set up to save the signatures, if we have any.
if len(s.signatures) > 0 {
options.BigData = append(options.BigData, storage.ImageBigDataOption{
imgOptions.BigData = append(imgOptions.BigData, storage.ImageBigDataOption{
Key: "signatures",
Data: s.signatures,
Digest: digest.Canonical.FromBytes(s.signatures),
@ -1263,7 +1260,7 @@ func (s *storageImageDestination) Commit(ctx context.Context, unparsedToplevel t
if err != nil {
return err
}
options.BigData = append(options.BigData, storage.ImageBigDataOption{
imgOptions.BigData = append(imgOptions.BigData, storage.ImageBigDataOption{
Key: key,
Data: signatures,
Digest: digest.Canonical.FromBytes(signatures),
@ -1276,7 +1273,7 @@ func (s *storageImageDestination) Commit(ctx context.Context, unparsedToplevel t
return fmt.Errorf("encoding metadata for image: %w", err)
}
if len(metadata) != 0 {
options.Metadata = string(metadata)
imgOptions.Metadata = string(metadata)
}
// Create the image record, pointing to the most-recently added layer.
@ -1288,7 +1285,7 @@ func (s *storageImageDestination) Commit(ctx context.Context, unparsedToplevel t
}
}
oldNames := []string{}
img, err := s.imageRef.transport.store.CreateImage(intendedID, nil, lastLayer, "", options)
img, err := s.imageRef.transport.store.CreateImage(intendedID, nil, lastLayer, "", imgOptions)
if err != nil {
if !errors.Is(err, storage.ErrDuplicateID) {
logrus.Debugf("error creating image: %q", err)
@ -1309,21 +1306,21 @@ func (s *storageImageDestination) Commit(ctx context.Context, unparsedToplevel t
// sizes (tracked in the metadata) which might have already
// been present with new values, when ideally we'd find a way
// to merge them since they all apply to the same image
for _, data := range options.BigData {
for _, data := range imgOptions.BigData {
if err := s.imageRef.transport.store.SetImageBigData(img.ID, data.Key, data.Data, manifest.Digest); err != nil {
logrus.Debugf("error saving big data %q for image %q: %v", data.Key, img.ID, err)
return fmt.Errorf("saving big data %q for image %q: %w", data.Key, img.ID, err)
}
}
if options.Metadata != "" {
if err := s.imageRef.transport.store.SetMetadata(img.ID, options.Metadata); err != nil {
if imgOptions.Metadata != "" {
if err := s.imageRef.transport.store.SetMetadata(img.ID, imgOptions.Metadata); err != nil {
logrus.Debugf("error saving metadata for image %q: %v", img.ID, err)
return fmt.Errorf("saving metadata for image %q: %w", img.ID, err)
}
logrus.Debugf("saved image metadata %q", options.Metadata)
logrus.Debugf("saved image metadata %q", imgOptions.Metadata)
}
} else {
logrus.Debugf("created new image ID %q with metadata %q", img.ID, options.Metadata)
logrus.Debugf("created new image ID %q with metadata %q", img.ID, imgOptions.Metadata)
}
// Clean up the unfinished image on any error.
@ -1346,6 +1343,21 @@ func (s *storageImageDestination) Commit(ctx context.Context, unparsedToplevel t
}
logrus.Debugf("added name %q to image %q", name, img.ID)
}
if options.ReportResolvedReference != nil {
// FIXME? This is using nil for the named reference.
// It would be better to also use s.imageRef.named, because that allows us to resolve to the right
// digest / manifest (and corresponding signatures).
// The problem with that is that resolving such a reference fails if the s.imageRef.named name is moved to a different image
// (because it is a tag that moved, or because we have pulled “the same” image for a different architecture).
// Right now (2024-11), ReportResolvedReference is only used in c/common/libimage, where the caller only extracts the image ID,
// so the name does not matter; to give us options, copy.Options.ReportResolvedReference is explicitly refusing to document
// whether the value contains a name.
resolved, err := newReference(s.imageRef.transport, nil, intendedID)
if err != nil {
return fmt.Errorf("creating a resolved reference for (%s, %s): %w", s.imageRef.StringWithinTransport(), intendedID, err)
}
*options.ReportResolvedReference = resolved
}
commitSucceeded = true
return nil

View file

@ -3,7 +3,6 @@ package storage
import (
"errors"
"fmt"
"maps"
"os"
"path/filepath"
"slices"
@ -164,17 +163,17 @@ type containerStore struct {
func copyContainer(c *Container) *Container {
return &Container{
ID: c.ID,
Names: copyStringSlice(c.Names),
Names: copySlicePreferringNil(c.Names),
ImageID: c.ImageID,
LayerID: c.LayerID,
Metadata: c.Metadata,
BigDataNames: copyStringSlice(c.BigDataNames),
BigDataSizes: maps.Clone(c.BigDataSizes),
BigDataDigests: maps.Clone(c.BigDataDigests),
BigDataNames: copySlicePreferringNil(c.BigDataNames),
BigDataSizes: copyMapPreferringNil(c.BigDataSizes),
BigDataDigests: copyMapPreferringNil(c.BigDataDigests),
Created: c.Created,
UIDMap: copyIDMap(c.UIDMap),
GIDMap: copyIDMap(c.GIDMap),
Flags: maps.Clone(c.Flags),
UIDMap: copySlicePreferringNil(c.UIDMap),
GIDMap: copySlicePreferringNil(c.GIDMap),
Flags: copyMapPreferringNil(c.Flags),
volatileStore: c.volatileStore,
}
}
@ -692,9 +691,9 @@ func (r *containerStore) create(id string, names []string, image, layer string,
BigDataSizes: make(map[string]int64),
BigDataDigests: make(map[string]digest.Digest),
Created: time.Now().UTC(),
Flags: copyStringInterfaceMap(options.Flags),
UIDMap: copyIDMap(options.UIDMap),
GIDMap: copyIDMap(options.GIDMap),
Flags: newMapFrom(options.Flags),
UIDMap: copySlicePreferringNil(options.UIDMap),
GIDMap: copySlicePreferringNil(options.GIDMap),
volatileStore: options.Volatile,
}
if options.MountOpts != nil {
@ -906,7 +905,7 @@ func (r *containerStore) BigDataNames(id string) ([]string, error) {
if !ok {
return nil, ErrContainerUnknown
}
return copyStringSlice(c.BigDataNames), nil
return copySlicePreferringNil(c.BigDataNames), nil
}
// Requires startWriting.

View file

@ -16,7 +16,6 @@ import (
"errors"
"fmt"
"io"
"net"
"os"
"path/filepath"
"strings"
@ -199,11 +198,9 @@ func DirCopy(srcDir, dstDir string, copyMode Mode, copyXattrs bool) error {
}
case mode&os.ModeSocket != 0:
s, err := net.Listen("unix", dstPath)
if err != nil {
if err := unix.Mknod(dstPath, stat.Mode, int(stat.Rdev)); err != nil {
return err
}
s.Close()
case mode&os.ModeDevice != 0:
if unshare.IsRootless() {

View file

@ -128,6 +128,7 @@ func (gdw *NaiveDiffDriver) Changes(id string, idMappings *idtools.IDMappings, p
options := MountOpts{
MountLabel: mountLabel,
Options: []string{"ro"},
}
layerFs, err := driver.Get(id, options)
if err != nil {
@ -138,10 +139,6 @@ func (gdw *NaiveDiffDriver) Changes(id string, idMappings *idtools.IDMappings, p
parentFs := ""
if parent != "" {
options := MountOpts{
MountLabel: mountLabel,
Options: []string{"ro"},
}
parentFs, err = driver.Get(parent, options)
if err != nil {
return nil, err

View file

@ -12,6 +12,7 @@ import (
"path/filepath"
"strings"
"sync"
"sync/atomic"
"github.com/containers/storage/pkg/chunked/dump"
"github.com/containers/storage/pkg/fsverity"
@ -24,6 +25,10 @@ var (
composeFsHelperOnce sync.Once
composeFsHelperPath string
composeFsHelperErr error
// skipMountViaFile is used to avoid trying to mount EROFS directly via the file if we already know the current kernel
// does not support it. Mounting directly via a file will be supported in kernel 6.12.
skipMountViaFile atomic.Bool
)
func getComposeFsHelper() (string, error) {
@ -136,17 +141,15 @@ func hasACL(path string) (bool, error) {
return binary.LittleEndian.Uint32(flags)&LCFS_EROFS_FLAGS_HAS_ACL != 0, nil
}
func openComposefsMount(dataDir string) (int, error) {
blobFile := getComposefsBlob(dataDir)
loop, err := loopback.AttachLoopDeviceRO(blobFile)
if err != nil {
return -1, err
}
defer loop.Close()
func openBlobFile(blobFile string, hasACL, useLoopDevice bool) (int, error) {
if useLoopDevice {
loop, err := loopback.AttachLoopDeviceRO(blobFile)
if err != nil {
return -1, err
}
defer loop.Close()
hasACL, err := hasACL(blobFile)
if err != nil {
return -1, err
blobFile = loop.Name()
}
fsfd, err := unix.Fsopen("erofs", 0)
@ -155,7 +158,7 @@ func openComposefsMount(dataDir string) (int, error) {
}
defer unix.Close(fsfd)
if err := unix.FsconfigSetString(fsfd, "source", loop.Name()); err != nil {
if err := unix.FsconfigSetString(fsfd, "source", blobFile); err != nil {
return -1, fmt.Errorf("failed to set source for erofs filesystem: %w", err)
}
@ -172,7 +175,7 @@ func openComposefsMount(dataDir string) (int, error) {
if err := unix.FsconfigCreate(fsfd); err != nil {
buffer := make([]byte, 4096)
if n, _ := unix.Read(fsfd, buffer); n > 0 {
return -1, fmt.Errorf("failed to create erofs filesystem: %s: %w", string(buffer[:n]), err)
return -1, fmt.Errorf("failed to create erofs filesystem: %s: %w", strings.TrimSuffix(string(buffer[:n]), "\n"), err)
}
return -1, fmt.Errorf("failed to create erofs filesystem: %w", err)
}
@ -188,6 +191,26 @@ func openComposefsMount(dataDir string) (int, error) {
return mfd, nil
}
func openComposefsMount(dataDir string) (int, error) {
blobFile := getComposefsBlob(dataDir)
hasACL, err := hasACL(blobFile)
if err != nil {
return -1, err
}
if !skipMountViaFile.Load() {
fd, err := openBlobFile(blobFile, hasACL, false)
if err == nil || !errors.Is(err, unix.ENOTBLK) {
return fd, err
}
logrus.Debugf("The current kernel doesn't support mounting EROFS directly from a file, fallback to a loopback device")
skipMountViaFile.Store(true)
}
return openBlobFile(blobFile, hasACL, true)
}
func mountComposefsBlob(dataDir, mountPoint string) error {
mfd, err := openComposefsMount(dataDir)
if err != nil {
@ -196,7 +219,7 @@ func mountComposefsBlob(dataDir, mountPoint string) error {
defer unix.Close(mfd)
if err := unix.MoveMount(mfd, "", unix.AT_FDCWD, mountPoint, unix.MOVE_MOUNT_F_EMPTY_PATH); err != nil {
return fmt.Errorf("failed to move mount: %w", err)
return fmt.Errorf("failed to move mount to %q: %w", mountPoint, err)
}
return nil
}

View file

@ -589,7 +589,7 @@ func parseOptions(options []string) (*overlayOptions, error) {
m := os.FileMode(mask)
o.forceMask = &m
default:
return nil, fmt.Errorf("overlay: Unknown option %s", key)
return nil, fmt.Errorf("overlay: unknown option %s", key)
}
}
return o, nil
@ -1579,7 +1579,6 @@ func (d *Driver) get(id string, disableShifting bool, options graphdriver.MountO
}()
composeFsLayers := []string{}
composeFsLayersDir := filepath.Join(dir, "composefs-layers")
maybeAddComposefsMount := func(lowerID string, i int, readWrite bool) (string, error) {
composefsBlob := d.getComposefsData(lowerID)
if err := fileutils.Exists(composefsBlob); err != nil {
@ -1594,7 +1593,7 @@ func (d *Driver) get(id string, disableShifting bool, options graphdriver.MountO
return "", fmt.Errorf("cannot mount a composefs layer as writeable")
}
dest := filepath.Join(composeFsLayersDir, strconv.Itoa(i))
dest := d.getStorePrivateDirectory(id, dir, fmt.Sprintf("composefs-layers/%d", i), inAdditionalStore)
if err := os.MkdirAll(dest, 0o700); err != nil {
return "", err
}
@ -1878,6 +1877,16 @@ func (d *Driver) get(id string, disableShifting bool, options graphdriver.MountO
return mergedDir, nil
}
// getStorePrivateDirectory returns a directory path for storing data that requires exclusive access.
// If 'inAdditionalStore' is true, the path will be under the rundir, otherwise it will be placed in
// the primary store.
func (d *Driver) getStorePrivateDirectory(id, layerDir, subdir string, inAdditionalStore bool) string {
if inAdditionalStore {
return path.Join(d.runhome, id, subdir)
}
return path.Join(layerDir, subdir)
}
// getMergedDir returns the directory path that should be used as the mount point for the overlayfs.
func (d *Driver) getMergedDir(id, dir string, inAdditionalStore bool) string {
// Ordinarily, .Get() (layer mounting) callers are supposed to guarantee exclusion.
@ -1897,10 +1906,7 @@ func (d *Driver) getMergedDir(id, dir string, inAdditionalStore bool) string {
// TO DO: LOCKING BUG: the .DiffSize operation does not currently hold an exclusive lock on the primary store.
// (_Some_ of the callers might be better ported to use a metadata-only size computation instead of DiffSize,
// but DiffSize probably needs to remain for computing sizes of container’s RW layers.)
if inAdditionalStore {
return path.Join(d.runhome, id, "merged")
}
return path.Join(dir, "merged")
return d.getStorePrivateDirectory(id, dir, "merged", inAdditionalStore)
}
// Put unmounts the mount path created for the give id.

View file

@ -2,7 +2,6 @@ package storage
import (
"fmt"
"maps"
"os"
"path/filepath"
"slices"
@ -183,18 +182,18 @@ func copyImage(i *Image) *Image {
return &Image{
ID: i.ID,
Digest: i.Digest,
Digests: copyDigestSlice(i.Digests),
Names: copyStringSlice(i.Names),
NamesHistory: copyStringSlice(i.NamesHistory),
Digests: copySlicePreferringNil(i.Digests),
Names: copySlicePreferringNil(i.Names),
NamesHistory: copySlicePreferringNil(i.NamesHistory),
TopLayer: i.TopLayer,
MappedTopLayers: copyStringSlice(i.MappedTopLayers),
MappedTopLayers: copySlicePreferringNil(i.MappedTopLayers),
Metadata: i.Metadata,
BigDataNames: copyStringSlice(i.BigDataNames),
BigDataSizes: maps.Clone(i.BigDataSizes),
BigDataDigests: maps.Clone(i.BigDataDigests),
BigDataNames: copySlicePreferringNil(i.BigDataNames),
BigDataSizes: copyMapPreferringNil(i.BigDataSizes),
BigDataDigests: copyMapPreferringNil(i.BigDataDigests),
Created: i.Created,
ReadOnly: i.ReadOnly,
Flags: maps.Clone(i.Flags),
Flags: copyMapPreferringNil(i.Flags),
}
}
@ -718,14 +717,14 @@ func (r *imageStore) create(id string, names []string, layer string, options Ima
Digest: options.Digest,
Digests: dedupeDigests(options.Digests),
Names: names,
NamesHistory: copyStringSlice(options.NamesHistory),
NamesHistory: copySlicePreferringNil(options.NamesHistory),
TopLayer: layer,
Metadata: options.Metadata,
BigDataNames: []string{},
BigDataSizes: make(map[string]int64),
BigDataDigests: make(map[string]digest.Digest),
Created: options.CreationDate,
Flags: copyStringInterfaceMap(options.Flags),
Flags: newMapFrom(options.Flags),
}
if image.Created.IsZero() {
image.Created = time.Now().UTC()
@ -967,7 +966,7 @@ func (r *imageStore) BigDataNames(id string) ([]string, error) {
if !ok {
return nil, fmt.Errorf("locating image with ID %q: %w", id, ErrImageUnknown)
}
return copyStringSlice(image.BigDataNames), nil
return copySlicePreferringNil(image.BigDataNames), nil
}
// Requires startWriting.

View file

@ -439,7 +439,7 @@ func layerLocation(l *Layer) layerLocations {
func copyLayer(l *Layer) *Layer {
return &Layer{
ID: l.ID,
Names: copyStringSlice(l.Names),
Names: copySlicePreferringNil(l.Names),
Parent: l.Parent,
Metadata: l.Metadata,
MountLabel: l.MountLabel,
@ -454,12 +454,12 @@ func copyLayer(l *Layer) *Layer {
CompressionType: l.CompressionType,
ReadOnly: l.ReadOnly,
volatileStore: l.volatileStore,
BigDataNames: copyStringSlice(l.BigDataNames),
Flags: maps.Clone(l.Flags),
UIDMap: copyIDMap(l.UIDMap),
GIDMap: copyIDMap(l.GIDMap),
UIDs: copyUint32Slice(l.UIDs),
GIDs: copyUint32Slice(l.GIDs),
BigDataNames: copySlicePreferringNil(l.BigDataNames),
Flags: copyMapPreferringNil(l.Flags),
UIDMap: copySlicePreferringNil(l.UIDMap),
GIDMap: copySlicePreferringNil(l.GIDMap),
UIDs: copySlicePreferringNil(l.UIDs),
GIDs: copySlicePreferringNil(l.GIDs),
}
}
@ -1406,9 +1406,9 @@ func (r *layerStore) create(id string, parentLayer *Layer, names []string, mount
CompressionType: templateCompressionType,
UIDs: templateUIDs,
GIDs: templateGIDs,
Flags: copyStringInterfaceMap(moreOptions.Flags),
UIDMap: copyIDMap(moreOptions.UIDMap),
GIDMap: copyIDMap(moreOptions.GIDMap),
Flags: newMapFrom(moreOptions.Flags),
UIDMap: copySlicePreferringNil(moreOptions.UIDMap),
GIDMap: copySlicePreferringNil(moreOptions.GIDMap),
BigDataNames: []string{},
volatileStore: moreOptions.Volatile,
}
@ -1843,7 +1843,7 @@ func (r *layerStore) BigDataNames(id string) ([]string, error) {
if !ok {
return nil, fmt.Errorf("locating layer with ID %q to retrieve bigdata names: %w", id, ErrImageUnknown)
}
return copyStringSlice(layer.BigDataNames), nil
return copySlicePreferringNil(layer.BigDataNames), nil
}
// Requires startReading or startWriting.

View file

@ -149,62 +149,90 @@ func (c *chunkedDiffer) convertTarToZstdChunked(destDirectory string, payload *o
func GetDiffer(ctx context.Context, store storage.Store, blobDigest digest.Digest, blobSize int64, annotations map[string]string, iss ImageSourceSeekable) (graphdriver.Differ, error) {
pullOptions := store.PullOptions()
if !parseBooleanPullOption(pullOptions, "enable_partial_images", true) {
if !parseBooleanPullOption(pullOptions, "enable_partial_images", false) {
// If convertImages is set, the two options disagree whether fallback is permissible.
// Right now, we enable it, but that’s not a promise; rather, such a configuration should ideally be rejected.
return nil, newErrFallbackToOrdinaryLayerDownload(errors.New("partial images are disabled"))
}
// convertImages also serves as a “must not fallback to non-partial pull” option (?!)
convertImages := parseBooleanPullOption(pullOptions, "convert_images", false)
graphDriver, err := store.GraphDriver()
if err != nil {
return nil, err
}
if _, partialSupported := graphDriver.(graphdriver.DriverWithDiffer); !partialSupported {
if convertImages {
return nil, fmt.Errorf("graph driver %s does not support partial pull but convert_images requires that", graphDriver.String())
}
return nil, newErrFallbackToOrdinaryLayerDownload(fmt.Errorf("graph driver %s does not support partial pull", graphDriver.String()))
}
differ, canFallback, err := getProperDiffer(store, blobDigest, blobSize, annotations, iss, pullOptions)
if err != nil {
if !canFallback {
return nil, err
}
// If convert_images is enabled, always attempt to convert it instead of returning an error or falling back to a different method.
if convertImages {
logrus.Debugf("Created differ to convert blob %q", blobDigest)
return makeConvertFromRawDiffer(store, blobDigest, blobSize, iss, pullOptions)
}
return nil, newErrFallbackToOrdinaryLayerDownload(err)
}
return differ, nil
}
// getProperDiffer is an implementation detail of GetDiffer.
// It returns a “proper” differ (not a convert_images one) if possible.
// On error, the second parameter is true if a fallback to an alternative (either the makeConverToRaw differ, or a non-partial pull)
// is permissible.
func getProperDiffer(store storage.Store, blobDigest digest.Digest, blobSize int64, annotations map[string]string, iss ImageSourceSeekable, pullOptions map[string]string) (graphdriver.Differ, bool, error) {
zstdChunkedTOCDigestString, hasZstdChunkedTOC := annotations[internal.ManifestChecksumKey]
estargzTOCDigestString, hasEstargzTOC := annotations[estargz.TOCJSONDigestAnnotation]
if hasZstdChunkedTOC && hasEstargzTOC {
return nil, errors.New("both zstd:chunked and eStargz TOC found")
}
switch {
case hasZstdChunkedTOC && hasEstargzTOC:
return nil, false, errors.New("both zstd:chunked and eStargz TOC found")
convertImages := parseBooleanPullOption(pullOptions, "convert_images", false)
if !hasZstdChunkedTOC && !hasEstargzTOC && !convertImages {
return nil, newErrFallbackToOrdinaryLayerDownload(errors.New("no TOC found and convert_images is not configured"))
}
var err error
var differ graphdriver.Differ
// At this point one of hasZstdChunkedTOC, hasEstargzTOC or convertImages is true.
if hasZstdChunkedTOC {
zstdChunkedTOCDigest, err2 := digest.Parse(zstdChunkedTOCDigestString)
if err2 != nil {
return nil, err2
case hasZstdChunkedTOC:
zstdChunkedTOCDigest, err := digest.Parse(zstdChunkedTOCDigestString)
if err != nil {
return nil, false, err
}
differ, err = makeZstdChunkedDiffer(store, blobSize, zstdChunkedTOCDigest, annotations, iss, pullOptions)
if err == nil {
logrus.Debugf("Created zstd:chunked differ for blob %q", blobDigest)
return differ, err
differ, err := makeZstdChunkedDiffer(store, blobSize, zstdChunkedTOCDigest, annotations, iss, pullOptions)
if err != nil {
logrus.Debugf("Could not create zstd:chunked differ for blob %q: %v", blobDigest, err)
// If the error is a bad request to the server, then signal to the caller that it can try a different method.
var badRequestErr ErrBadRequest
return nil, errors.As(err, &badRequestErr), err
}
} else if hasEstargzTOC {
estargzTOCDigest, err2 := digest.Parse(estargzTOCDigestString)
if err2 != nil {
return nil, err
}
differ, err = makeEstargzChunkedDiffer(store, blobSize, estargzTOCDigest, iss, pullOptions)
if err == nil {
logrus.Debugf("Created eStargz differ for blob %q", blobDigest)
return differ, err
}
}
// If convert_images is enabled, always attempt to convert it instead of returning an error or falling back to a different method.
if convertImages {
logrus.Debugf("Created differ to convert blob %q", blobDigest)
return makeConvertFromRawDiffer(store, blobDigest, blobSize, iss, pullOptions)
}
logrus.Debugf("Created zstd:chunked differ for blob %q", blobDigest)
return differ, false, nil
logrus.Debugf("Could not create differ for blob %q: %v", blobDigest, err)
case hasEstargzTOC:
estargzTOCDigest, err := digest.Parse(estargzTOCDigestString)
if err != nil {
return nil, false, err
}
differ, err := makeEstargzChunkedDiffer(store, blobSize, estargzTOCDigest, iss, pullOptions)
if err != nil {
logrus.Debugf("Could not create estargz differ for blob %q: %v", blobDigest, err)
// If the error is a bad request to the server, then signal to the caller that it can try a different method.
var badRequestErr ErrBadRequest
return nil, errors.As(err, &badRequestErr), err
}
logrus.Debugf("Created eStargz differ for blob %q", blobDigest)
return differ, false, nil
// If the error is a bad request to the server, then signal to the caller that it can try a different method. This can be done
// only when convert_images is disabled.
var badRequestErr ErrBadRequest
if errors.As(err, &badRequestErr) {
err = newErrFallbackToOrdinaryLayerDownload(err)
default: // no TOC
convertImages := parseBooleanPullOption(pullOptions, "convert_images", false)
if !convertImages {
return nil, true, errors.New("no TOC found and convert_images is not configured")
}
return nil, true, errors.New("no TOC found")
}
return nil, err
}
func makeConvertFromRawDiffer(store storage.Store, blobDigest digest.Digest, blobSize int64, iss ImageSourceSeekable, pullOptions map[string]string) (*chunkedDiffer, error) {
@ -1139,6 +1167,7 @@ func (c *chunkedDiffer) copyAllBlobToFile(destination *os.File) (digest.Digest,
if payload == nil {
return "", errors.New("invalid stream returned")
}
defer payload.Close()
originalRawDigester := digest.Canonical.Digester()

View file

@ -8,12 +8,12 @@
# /usr/containers/storage.conf
# /etc/containers/storage.conf
# $HOME/.config/containers/storage.conf
# $XDG_CONFIG_HOME/containers/storage.conf (If XDG_CONFIG_HOME is set)
# $XDG_CONFIG_HOME/containers/storage.conf (if XDG_CONFIG_HOME is set)
# See man 5 containers-storage.conf for more information
# The "container storage" table contains all of the server options.
# The "storage" table contains all of the server options.
[storage]
# Default Storage Driver, Must be set for proper operation.
# Default storage driver, must be set for proper operation.
driver = "overlay"
# Temporary storage location
@ -24,8 +24,8 @@ runroot = "/run/containers/storage"
# driver_priority = ["overlay", "btrfs"]
# Primary Read/Write location of container storage
# When changing the graphroot location on an SELINUX system, you must
# ensure the labeling matches the default locations labels with the
# When changing the graphroot location on an SELinux system, you must
# ensure the labeling matches the default location's labels with the
# following commands:
# semanage fcontext -a -e /var/lib/containers/storage /NEWSTORAGEPATH
# restorecon -R -v /NEWSTORAGEPATH
@ -54,14 +54,14 @@ graphroot = "/var/lib/containers/storage"
additionalimagestores = [
]
# Options controlling how storage is populated when pulling images.
# Options controlling how storage is populated when pulling images.
[storage.options.pull_options]
# Enable the "zstd:chunked" feature, which allows partial pulls, reusing
# content that already exists on the system. This is enabled by default,
# but can be explicitly disabled. For more on zstd:chunked, see
# content that already exists on the system. This is disabled by default,
# and must be explicitly enabled to be used. For more on zstd:chunked, see
# https://github.com/containers/storage/blob/main/docs/containers-storage-zstd-chunked.md
# This is a "string bool": "false" | "true" (cannot be native TOML boolean)
# enable_partial_images = "true"
# enable_partial_images = "false"
# Tells containers/storage to use hard links rather then create new files in
# the image, if an identical file already existed in storage.

View file

@ -877,8 +877,8 @@ func GetStore(options types.StoreOptions) (Store, error) {
graphOptions: options.GraphDriverOptions,
imageStoreDir: options.ImageStore,
pullOptions: options.PullOptions,
uidMap: copyIDMap(options.UIDMap),
gidMap: copyIDMap(options.GIDMap),
uidMap: copySlicePreferringNil(options.UIDMap),
gidMap: copySlicePreferringNil(options.GIDMap),
autoUsernsUser: options.RootAutoNsUser,
autoNsMinSize: autoNsMinSize,
autoNsMaxSize: autoNsMaxSize,
@ -897,30 +897,6 @@ func GetStore(options types.StoreOptions) (Store, error) {
return s, nil
}
func copyUint32Slice(slice []uint32) []uint32 {
m := []uint32{}
if slice != nil {
m = make([]uint32, len(slice))
copy(m, slice)
}
if len(m) > 0 {
return m[:]
}
return nil
}
func copyIDMap(idmap []idtools.IDMap) []idtools.IDMap {
m := []idtools.IDMap{}
if idmap != nil {
m = make([]idtools.IDMap, len(idmap))
copy(m, idmap)
}
if len(m) > 0 {
return m[:]
}
return nil
}
func (s *store) RunRoot() string {
return s.runRoot
}
@ -952,11 +928,11 @@ func (s *store) PullOptions() map[string]string {
}
func (s *store) UIDMap() []idtools.IDMap {
return copyIDMap(s.uidMap)
return copySlicePreferringNil(s.uidMap)
}
func (s *store) GIDMap() []idtools.IDMap {
return copyIDMap(s.gidMap)
return copySlicePreferringNil(s.gidMap)
}
// This must only be called when constructing store; it writes to fields that are assumed to be constant after construction.
@ -1469,8 +1445,8 @@ func (s *store) putLayer(rlstore rwLayerStore, rlstores []roLayerStore, id, pare
var options LayerOptions
if lOptions != nil {
options = *lOptions
options.BigData = copyLayerBigDataOptionSlice(lOptions.BigData)
options.Flags = maps.Clone(lOptions.Flags)
options.BigData = slices.Clone(lOptions.BigData)
options.Flags = copyMapPreferringNil(lOptions.Flags)
}
if options.HostUIDMapping {
options.UIDMap = nil
@ -1541,8 +1517,8 @@ func (s *store) putLayer(rlstore rwLayerStore, rlstores []roLayerStore, id, pare
options.IDMappingOptions = types.IDMappingOptions{
HostUIDMapping: options.HostUIDMapping,
HostGIDMapping: options.HostGIDMapping,
UIDMap: copyIDMap(uidMap),
GIDMap: copyIDMap(gidMap),
UIDMap: copySlicePreferringNil(uidMap),
GIDMap: copySlicePreferringNil(gidMap),
}
}
return rlstore.create(id, parentLayer, names, mountLabel, nil, &options, writeable, diff, slo)
@ -1610,8 +1586,8 @@ func (s *store) CreateImage(id string, names []string, layer, metadata string, i
Metadata: i.Metadata,
CreationDate: i.Created,
Digest: i.Digest,
Digests: copyDigestSlice(i.Digests),
NamesHistory: copyStringSlice(i.NamesHistory),
Digests: copySlicePreferringNil(i.Digests),
NamesHistory: copySlicePreferringNil(i.NamesHistory),
}
for _, key := range i.BigDataNames {
data, err := store.BigData(id, key)
@ -1760,8 +1736,8 @@ func (s *store) imageTopLayerForMapping(image *Image, ristore roImageStore, rlst
layerOptions.IDMappingOptions = types.IDMappingOptions{
HostUIDMapping: options.HostUIDMapping,
HostGIDMapping: options.HostGIDMapping,
UIDMap: copyIDMap(options.UIDMap),
GIDMap: copyIDMap(options.GIDMap),
UIDMap: copySlicePreferringNil(options.UIDMap),
GIDMap: copySlicePreferringNil(options.GIDMap),
}
}
layerOptions.TemplateLayer = layer.ID
@ -1783,12 +1759,12 @@ func (s *store) CreateContainer(id string, names []string, image, layer, metadat
var options ContainerOptions
if cOptions != nil {
options = *cOptions
options.IDMappingOptions.UIDMap = copyIDMap(cOptions.IDMappingOptions.UIDMap)
options.IDMappingOptions.GIDMap = copyIDMap(cOptions.IDMappingOptions.GIDMap)
options.LabelOpts = copyStringSlice(cOptions.LabelOpts)
options.Flags = maps.Clone(cOptions.Flags)
options.MountOpts = copyStringSlice(cOptions.MountOpts)
options.StorageOpt = copyStringStringMap(cOptions.StorageOpt)
options.IDMappingOptions.UIDMap = copySlicePreferringNil(cOptions.IDMappingOptions.UIDMap)
options.IDMappingOptions.GIDMap = copySlicePreferringNil(cOptions.IDMappingOptions.GIDMap)
options.LabelOpts = copySlicePreferringNil(cOptions.LabelOpts)
options.Flags = copyMapPreferringNil(cOptions.Flags)
options.MountOpts = copySlicePreferringNil(cOptions.MountOpts)
options.StorageOpt = copyMapPreferringNil(cOptions.StorageOpt)
options.BigData = copyContainerBigDataOptionSlice(cOptions.BigData)
}
if options.HostUIDMapping {
@ -1913,8 +1889,8 @@ func (s *store) CreateContainer(id string, names []string, image, layer, metadat
layerOptions.IDMappingOptions = types.IDMappingOptions{
HostUIDMapping: idMappingsOptions.HostUIDMapping,
HostGIDMapping: idMappingsOptions.HostGIDMapping,
UIDMap: copyIDMap(uidMap),
GIDMap: copyIDMap(gidMap),
UIDMap: copySlicePreferringNil(uidMap),
GIDMap: copySlicePreferringNil(gidMap),
}
}
if options.Flags == nil {
@ -1951,8 +1927,8 @@ func (s *store) CreateContainer(id string, names []string, image, layer, metadat
options.IDMappingOptions = types.IDMappingOptions{
HostUIDMapping: len(options.UIDMap) == 0,
HostGIDMapping: len(options.GIDMap) == 0,
UIDMap: copyIDMap(options.UIDMap),
GIDMap: copyIDMap(options.GIDMap),
UIDMap: copySlicePreferringNil(options.UIDMap),
GIDMap: copySlicePreferringNil(options.GIDMap),
}
container, err := s.containerStore.create(id, names, imageID, layer, &options)
if err != nil || container == nil {
@ -2450,10 +2426,10 @@ func (s *store) updateNames(id string, names []string, op updateNameOperation) e
options := ImageOptions{
CreationDate: i.Created,
Digest: i.Digest,
Digests: copyDigestSlice(i.Digests),
Digests: copySlicePreferringNil(i.Digests),
Metadata: i.Metadata,
NamesHistory: copyStringSlice(i.NamesHistory),
Flags: copyStringInterfaceMap(i.Flags),
NamesHistory: copySlicePreferringNil(i.NamesHistory),
Flags: copyMapPreferringNil(i.Flags),
}
for _, key := range i.BigDataNames {
data, err := store.BigData(id, key)
@ -3674,58 +3650,42 @@ func makeBigDataBaseName(key string) string {
}
func stringSliceWithoutValue(slice []string, value string) []string {
modified := make([]string, 0, len(slice))
for _, v := range slice {
if v == value {
continue
}
modified = append(modified, v)
}
return modified
return slices.DeleteFunc(slices.Clone(slice), func(v string) bool {
return v == value
})
}
func copyStringSlice(slice []string) []string {
if len(slice) == 0 {
// copySlicePreferringNil returns a copy of the slice.
// If s is empty, a nil is returned.
func copySlicePreferringNil[S ~[]E, E any](s S) S {
if len(s) == 0 {
return nil
}
ret := make([]string, len(slice))
copy(ret, slice)
return ret
return slices.Clone(s)
}
func copyStringStringMap(m map[string]string) map[string]string {
ret := make(map[string]string, len(m))
// copyMapPreferringNil returns a shallow clone of map m.
// If m is empty, a nil is returned.
//
// (As of, e.g., Go 1.23, maps.Clone preserves nil, but that’s not a documented promise;
// and this function turns even non-nil empty maps into nil.)
func copyMapPreferringNil[K comparable, V any](m map[K]V) map[K]V {
if len(m) == 0 {
return nil
}
return maps.Clone(m)
}
// newMapFrom returns a shallow clone of map m.
// If m is empty, an empty map is allocated and returned.
func newMapFrom[K comparable, V any](m map[K]V) map[K]V {
ret := make(map[K]V, len(m))
for k, v := range m {
ret[k] = v
}
return ret
}
func copyDigestSlice(slice []digest.Digest) []digest.Digest {
if len(slice) == 0 {
return nil
}
ret := make([]digest.Digest, len(slice))
copy(ret, slice)
return ret
}
// copyStringInterfaceMap still forces us to assume that the interface{} is
// a non-pointer scalar value
func copyStringInterfaceMap(m map[string]interface{}) map[string]interface{} {
ret := make(map[string]interface{}, len(m))
for k, v := range m {
ret[k] = v
}
return ret
}
func copyLayerBigDataOptionSlice(slice []LayerBigDataOption) []LayerBigDataOption {
ret := make([]LayerBigDataOption, len(slice))
copy(ret, slice)
return ret
}
func copyImageBigDataOptionSlice(slice []ImageBigDataOption) []ImageBigDataOption {
ret := make([]ImageBigDataOption, len(slice))
for i := range slice {

View file

@ -1,7 +1,7 @@
freebsd_task:
name: 'FreeBSD'
freebsd_instance:
image_family: freebsd-13-2
image_family: freebsd-14-1
install_script:
- pkg update -f
- pkg install -y go
@ -9,5 +9,6 @@ freebsd_task:
# run tests as user "cirrus" instead of root
- pw useradd cirrus -m
- chown -R cirrus:cirrus .
- FSNOTIFY_BUFFER=4096 sudo --preserve-env=FSNOTIFY_BUFFER -u cirrus go test -parallel 1 -race ./...
- sudo --preserve-env=FSNOTIFY_BUFFER -u cirrus go test -parallel 1 -race ./...
- FSNOTIFY_BUFFER=4096 sudo --preserve-env=FSNOTIFY_BUFFER -u cirrus go test -parallel 1 -race ./...
- sudo --preserve-env=FSNOTIFY_BUFFER -u cirrus go test -parallel 1 -race ./...
- FSNOTIFY_DEBUG=1 sudo --preserve-env=FSNOTIFY_BUFFER -u cirrus go test -parallel 1 -race -v ./...

View file

@ -1,12 +0,0 @@
root = true
[*.go]
indent_style = tab
indent_size = 4
insert_final_newline = true
[*.{yml,yaml}]
indent_style = space
indent_size = 2
insert_final_newline = true
trim_trailing_whitespace = true

View file

@ -1 +0,0 @@
go.sum linguist-generated

View file

@ -5,3 +5,6 @@
# Output of go build ./cmd/fsnotify
/fsnotify
/fsnotify.exe
/test/kqueue
/test/a.out

View file

@ -1,8 +1,36 @@
# Changelog
Unreleased
----------
Nothing yet.
1.8.0 2023-10-31
----------------
### Additions
- all: add `FSNOTIFY_DEBUG` to print debug logs to stderr ([#619])
### Changes and fixes
- windows: fix behaviour of `WatchList()` to be consistent with other platforms ([#610])
- kqueue: ignore events with Ident=0 ([#590])
- kqueue: set O_CLOEXEC to prevent passing file descriptors to children ([#617])
- kqueue: emit events as "/path/dir/file" instead of "path/link/file" when watching a symlink ([#625])
- inotify: don't send event for IN_DELETE_SELF when also watching the parent ([#620])
- inotify: fix panic when calling Remove() in a goroutine ([#650])
- fen: allow watching subdirectories of watched directories ([#621])
[#590]: https://github.com/fsnotify/fsnotify/pull/590
[#610]: https://github.com/fsnotify/fsnotify/pull/610
[#617]: https://github.com/fsnotify/fsnotify/pull/617
[#619]: https://github.com/fsnotify/fsnotify/pull/619
[#620]: https://github.com/fsnotify/fsnotify/pull/620
[#621]: https://github.com/fsnotify/fsnotify/pull/621
[#625]: https://github.com/fsnotify/fsnotify/pull/625
[#650]: https://github.com/fsnotify/fsnotify/pull/650
1.7.0 - 2023-10-22
------------------

View file

@ -1,7 +1,7 @@
Thank you for your interest in contributing to fsnotify! We try to review and
merge PRs in a reasonable timeframe, but please be aware that:
- To avoid "wasted" work, please discus changes on the issue tracker first. You
- To avoid "wasted" work, please discuss changes on the issue tracker first. You
can just send PRs, but they may end up being rejected for one reason or the
other.
@ -20,6 +20,124 @@ platforms. Testing different platforms locally can be done with something like
Use the `-short` flag to make the "stress test" run faster.
Writing new tests
-----------------
Scripts in the testdata directory allow creating test cases in a "shell-like"
syntax. The basic format is:
script
Output:
desired output
For example:
# Create a new empty file with some data.
watch /
echo data >/file
Output:
create /file
write /file
Just create a new file to add a new test; select which tests to run with
`-run TestScript/[path]`.
script
------
The script is a "shell-like" script:
cmd arg arg
Comments are supported with `#`:
# Comment
cmd arg arg # Comment
All operations are done in a temp directory; a path like "/foo" is rewritten to
"/tmp/TestFoo/foo".
Arguments can be quoted with `"` or `'`; there are no escapes and they're
functionally identical right now, but this may change in the future, so best to
assume shell-like rules.
touch "/file with spaces"
End-of-line escapes with `\` are not supported.
### Supported commands
watch path [ops] # Watch the path, reporting events for it. Nothing is
# watched by default. Optionally a list of ops can be
# given, as with AddWith(path, WithOps(...)).
unwatch path # Stop watching the path.
watchlist n # Assert watchlist length.
stop # Stop running the script; for debugging.
debug [yes/no] # Enable/disable FSNOTIFY_DEBUG (tests are run in
parallel by default, so -parallel=1 is probably a good
idea).
touch path
mkdir [-p] dir
ln -s target link # Only ln -s supported.
mkfifo path
mknod dev path
mv src dst
rm [-r] path
chmod mode path # Octal only
sleep time-in-ms
cat path # Read path (does nothing with the data; just reads it).
echo str >>path # Append "str" to "path".
echo str >path # Truncate "path" and write "str".
require reason # Skip the test if "reason" is true; "skip" and
skip reason # "require" behave identical; it supports both for
# readability. Possible reasons are:
#
# always Always skip this test.
# symlink Symlinks are supported (requires admin
# permissions on Windows).
# mkfifo Platform doesn't support FIFO named sockets.
# mknod Platform doesn't support device nodes.
output
------
After `Output:` the desired output is given; this is indented by convention, but
that's not required.
The format of that is:
# Comment
event path # Comment
system:
event path
system2:
event path
Every event is one line, and any whitespace between the event and path are
ignored. The path can optionally be surrounded in ". Anything after a "#" is
ignored.
Platform-specific tests can be added after GOOS; for example:
watch /
touch /file
Output:
# Tested if nothing else matches
create /file
# Windows-specific test.
windows:
write /file
You can specify multiple platforms with a comma (e.g. "windows, linux:").
"kqueue" is a shortcut for all kqueue systems (BSD, macOS).
[goon]: https://github.com/arp242/goon
[Vagrant]: https://www.vagrantup.com/

View file

@ -1,8 +1,8 @@
//go:build solaris
// +build solaris
// Note: the documentation on the Watcher type and methods is generated from
// mkdoc.zsh
// FEN backend for illumos (supported) and Solaris (untested, but should work).
//
// See port_create(3c) etc. for docs. https://www.illumos.org/man/3C/port_create
package fsnotify
@ -12,150 +12,33 @@ import (
"os"
"path/filepath"
"sync"
"time"
"github.com/fsnotify/fsnotify/internal"
"golang.org/x/sys/unix"
)
// Watcher watches a set of paths, delivering events on a channel.
//
// A watcher should not be copied (e.g. pass it by pointer, rather than by
// value).
//
// # Linux notes
//
// When a file is removed a Remove event won't be emitted until all file
// descriptors are closed, and deletes will always emit a Chmod. For example:
//
// fp := os.Open("file")
// os.Remove("file") // Triggers Chmod
// fp.Close() // Triggers Remove
//
// This is the event that inotify sends, so not much can be changed about this.
//
// The fs.inotify.max_user_watches sysctl variable specifies the upper limit
// for the number of watches per user, and fs.inotify.max_user_instances
// specifies the maximum number of inotify instances per user. Every Watcher you
// create is an "instance", and every path you add is a "watch".
//
// These are also exposed in /proc as /proc/sys/fs/inotify/max_user_watches and
// /proc/sys/fs/inotify/max_user_instances
//
// To increase them you can use sysctl or write the value to the /proc file:
//
// # Default values on Linux 5.18
// sysctl fs.inotify.max_user_watches=124983
// sysctl fs.inotify.max_user_instances=128
//
// To make the changes persist on reboot edit /etc/sysctl.conf or
// /usr/lib/sysctl.d/50-default.conf (details differ per Linux distro; check
// your distro's documentation):
//
// fs.inotify.max_user_watches=124983
// fs.inotify.max_user_instances=128
//
// Reaching the limit will result in a "no space left on device" or "too many open
// files" error.
//
// # kqueue notes (macOS, BSD)
//
// kqueue requires opening a file descriptor for every file that's being watched;
// so if you're watching a directory with five files then that's six file
// descriptors. You will run in to your system's "max open files" limit faster on
// these platforms.
//
// The sysctl variables kern.maxfiles and kern.maxfilesperproc can be used to
// control the maximum number of open files, as well as /etc/login.conf on BSD
// systems.
//
// # Windows notes
//
// Paths can be added as "C:\path\to\dir", but forward slashes
// ("C:/path/to/dir") will also work.
//
// When a watched directory is removed it will always send an event for the
// directory itself, but may not send events for all files in that directory.
// Sometimes it will send events for all times, sometimes it will send no
// events, and often only for some files.
//
// The default ReadDirectoryChangesW() buffer size is 64K, which is the largest
// value that is guaranteed to work with SMB filesystems. If you have many
// events in quick succession this may not be enough, and you will have to use
// [WithBufferSize] to increase the value.
type Watcher struct {
// Events sends the filesystem change events.
//
// fsnotify can send the following events; a "path" here can refer to a
// file, directory, symbolic link, or special file like a FIFO.
//
// fsnotify.Create A new path was created; this may be followed by one
// or more Write events if data also gets written to a
// file.
//
// fsnotify.Remove A path was removed.
//
// fsnotify.Rename A path was renamed. A rename is always sent with the
// old path as Event.Name, and a Create event will be
// sent with the new name. Renames are only sent for
// paths that are currently watched; e.g. moving an
// unmonitored file into a monitored directory will
// show up as just a Create. Similarly, renaming a file
// to outside a monitored directory will show up as
// only a Rename.
//
// fsnotify.Write A file or named pipe was written to. A Truncate will
// also trigger a Write. A single "write action"
// initiated by the user may show up as one or multiple
// writes, depending on when the system syncs things to
// disk. For example when compiling a large Go program
// you may get hundreds of Write events, and you may
// want to wait until you've stopped receiving them
// (see the dedup example in cmd/fsnotify).
//
// Some systems may send Write event for directories
// when the directory content changes.
//
// fsnotify.Chmod Attributes were changed. On Linux this is also sent
// when a file is removed (or more accurately, when a
// link to an inode is removed). On kqueue it's sent
// when a file is truncated. On Windows it's never
// sent.
type fen struct {
Events chan Event
// Errors sends any errors.
//
// ErrEventOverflow is used to indicate there are too many events:
//
// - inotify: There are too many queued events (fs.inotify.max_queued_events sysctl)
// - windows: The buffer size is too small; WithBufferSize() can be used to increase it.
// - kqueue, fen: Not used.
Errors chan error
mu sync.Mutex
port *unix.EventPort
done chan struct{} // Channel for sending a "quit message" to the reader goroutine
dirs map[string]struct{} // Explicitly watched directories
watches map[string]struct{} // Explicitly watched non-directories
done chan struct{} // Channel for sending a "quit message" to the reader goroutine
dirs map[string]Op // Explicitly watched directories
watches map[string]Op // Explicitly watched non-directories
}
// NewWatcher creates a new Watcher.
func NewWatcher() (*Watcher, error) {
return NewBufferedWatcher(0)
func newBackend(ev chan Event, errs chan error) (backend, error) {
return newBufferedBackend(0, ev, errs)
}
// NewBufferedWatcher creates a new Watcher with a buffered Watcher.Events
// channel.
//
// The main use case for this is situations with a very large number of events
// where the kernel buffer size can't be increased (e.g. due to lack of
// permissions). An unbuffered Watcher will perform better for almost all use
// cases, and whenever possible you will be better off increasing the kernel
// buffers instead of adding a large userspace buffer.
func NewBufferedWatcher(sz uint) (*Watcher, error) {
w := &Watcher{
Events: make(chan Event, sz),
Errors: make(chan error),
dirs: make(map[string]struct{}),
watches: make(map[string]struct{}),
func newBufferedBackend(sz uint, ev chan Event, errs chan error) (backend, error) {
w := &fen{
Events: ev,
Errors: errs,
dirs: make(map[string]Op),
watches: make(map[string]Op),
done: make(chan struct{}),
}
@ -171,27 +54,30 @@ func NewBufferedWatcher(sz uint) (*Watcher, error) {
// sendEvent attempts to send an event to the user, returning true if the event
// was put in the channel successfully and false if the watcher has been closed.
func (w *Watcher) sendEvent(name string, op Op) (sent bool) {
func (w *fen) sendEvent(name string, op Op) (sent bool) {
select {
case w.Events <- Event{Name: name, Op: op}:
return true
case <-w.done:
return false
case w.Events <- Event{Name: name, Op: op}:
return true
}
}
// sendError attempts to send an error to the user, returning true if the error
// was put in the channel successfully and false if the watcher has been closed.
func (w *Watcher) sendError(err error) (sent bool) {
select {
case w.Errors <- err:
func (w *fen) sendError(err error) (sent bool) {
if err == nil {
return true
}
select {
case <-w.done:
return false
case w.Errors <- err:
return true
}
}
func (w *Watcher) isClosed() bool {
func (w *fen) isClosed() bool {
select {
case <-w.done:
return true
@ -200,8 +86,7 @@ func (w *Watcher) isClosed() bool {
}
}
// Close removes all watches and closes the Events channel.
func (w *Watcher) Close() error {
func (w *fen) Close() error {
// Take the lock used by associateFile to prevent lingering events from
// being processed after the close
w.mu.Lock()
@ -213,60 +98,21 @@ func (w *Watcher) Close() error {
return w.port.Close()
}
// Add starts monitoring the path for changes.
//
// A path can only be watched once; watching it more than once is a no-op and will
// not return an error. Paths that do not yet exist on the filesystem cannot be
// watched.
//
// A watch will be automatically removed if the watched path is deleted or
// renamed. The exception is the Windows backend, which doesn't remove the
// watcher on renames.
//
// Notifications on network filesystems (NFS, SMB, FUSE, etc.) or special
// filesystems (/proc, /sys, etc.) generally don't work.
//
// Returns [ErrClosed] if [Watcher.Close] was called.
//
// See [Watcher.AddWith] for a version that allows adding options.
//
// # Watching directories
//
// All files in a directory are monitored, including new files that are created
// after the watcher is started. Subdirectories are not watched (i.e. it's
// non-recursive).
//
// # Watching files
//
// Watching individual files (rather than directories) is generally not
// recommended as many programs (especially editors) update files atomically: it
// will write to a temporary file which is then moved to to destination,
// overwriting the original (or some variant thereof). The watcher on the
// original file is now lost, as that no longer exists.
//
// The upshot of this is that a power failure or crash won't leave a
// half-written file.
//
// Watch the parent directory and use Event.Name to filter out files you're not
// interested in. There is an example of this in cmd/fsnotify/file.go.
func (w *Watcher) Add(name string) error { return w.AddWith(name) }
func (w *fen) Add(name string) error { return w.AddWith(name) }
// AddWith is like [Watcher.Add], but allows adding options. When using Add()
// the defaults described below are used.
//
// Possible options are:
//
// - [WithBufferSize] sets the buffer size for the Windows backend; no-op on
// other platforms. The default is 64K (65536 bytes).
func (w *Watcher) AddWith(name string, opts ...addOpt) error {
func (w *fen) AddWith(name string, opts ...addOpt) error {
if w.isClosed() {
return ErrClosed
}
if w.port.PathIsWatched(name) {
return nil
if debug {
fmt.Fprintf(os.Stderr, "FSNOTIFY_DEBUG: %s AddWith(%q)\n",
time.Now().Format("15:04:05.000000000"), name)
}
_ = getOptions(opts...)
with := getOptions(opts...)
if !w.xSupports(with.op) {
return fmt.Errorf("%w: %s", xErrUnsupported, with.op)
}
// Currently we resolve symlinks that were explicitly requested to be
// watched. Otherwise we would use LStat here.
@ -283,7 +129,7 @@ func (w *Watcher) AddWith(name string, opts ...addOpt) error {
}
w.mu.Lock()
w.dirs[name] = struct{}{}
w.dirs[name] = with.op
w.mu.Unlock()
return nil
}
@ -294,26 +140,22 @@ func (w *Watcher) AddWith(name string, opts ...addOpt) error {
}
w.mu.Lock()
w.watches[name] = struct{}{}
w.watches[name] = with.op
w.mu.Unlock()
return nil
}
// Remove stops monitoring the path for changes.
//
// Directories are always removed non-recursively. For example, if you added
// /tmp/dir and /tmp/dir/subdir then you will need to remove both.
//
// Removing a path that has not yet been added returns [ErrNonExistentWatch].
//
// Returns nil if [Watcher.Close] was called.
func (w *Watcher) Remove(name string) error {
func (w *fen) Remove(name string) error {
if w.isClosed() {
return nil
}
if !w.port.PathIsWatched(name) {
return fmt.Errorf("%w: %s", ErrNonExistentWatch, name)
}
if debug {
fmt.Fprintf(os.Stderr, "FSNOTIFY_DEBUG: %s Remove(%q)\n",
time.Now().Format("15:04:05.000000000"), name)
}
// The user has expressed an intent. Immediately remove this name from
// whichever watch list it might be in. If it's not in there the delete
@ -346,7 +188,7 @@ func (w *Watcher) Remove(name string) error {
}
// readEvents contains the main loop that runs in a goroutine watching for events.
func (w *Watcher) readEvents() {
func (w *fen) readEvents() {
// If this function returns, the watcher has been closed and we can close
// these channels
defer func() {
@ -382,17 +224,19 @@ func (w *Watcher) readEvents() {
continue
}
if debug {
internal.Debug(pevent.Path, pevent.Events)
}
err = w.handleEvent(&pevent)
if err != nil {
if !w.sendError(err) {
return
}
if !w.sendError(err) {
return
}
}
}
}
func (w *Watcher) handleDirectory(path string, stat os.FileInfo, follow bool, handler func(string, os.FileInfo, bool) error) error {
func (w *fen) handleDirectory(path string, stat os.FileInfo, follow bool, handler func(string, os.FileInfo, bool) error) error {
files, err := os.ReadDir(path)
if err != nil {
return err
@ -418,7 +262,7 @@ func (w *Watcher) handleDirectory(path string, stat os.FileInfo, follow bool, ha
// bitmap matches more than one event type (e.g. the file was both modified and
// had the attributes changed between when the association was created and the
// when event was returned)
func (w *Watcher) handleEvent(event *unix.PortEvent) error {
func (w *fen) handleEvent(event *unix.PortEvent) error {
var (
events = event.Events
path = event.Path
@ -510,15 +354,9 @@ func (w *Watcher) handleEvent(event *unix.PortEvent) error {
}
if events&unix.FILE_MODIFIED != 0 {
if fmode.IsDir() {
if watchedDir {
if err := w.updateDirectory(path); err != nil {
return err
}
} else {
if !w.sendEvent(path, Write) {
return nil
}
if fmode.IsDir() && watchedDir {
if err := w.updateDirectory(path); err != nil {
return err
}
} else {
if !w.sendEvent(path, Write) {
@ -543,7 +381,7 @@ func (w *Watcher) handleEvent(event *unix.PortEvent) error {
return nil
}
func (w *Watcher) updateDirectory(path string) error {
func (w *fen) updateDirectory(path string) error {
// The directory was modified, so we must find unwatched entities and watch
// them. If something was removed from the directory, nothing will happen,
// as everything else should still be watched.
@ -563,10 +401,8 @@ func (w *Watcher) updateDirectory(path string) error {
return err
}
err = w.associateFile(path, finfo, false)
if err != nil {
if !w.sendError(err) {
return nil
}
if !w.sendError(err) {
return nil
}
if !w.sendEvent(path, Create) {
return nil
@ -575,7 +411,7 @@ func (w *Watcher) updateDirectory(path string) error {
return nil
}
func (w *Watcher) associateFile(path string, stat os.FileInfo, follow bool) error {
func (w *fen) associateFile(path string, stat os.FileInfo, follow bool) error {
if w.isClosed() {
return ErrClosed
}
@ -593,34 +429,34 @@ func (w *Watcher) associateFile(path string, stat os.FileInfo, follow bool) erro
// cleared up that discrepancy. The most likely cause is that the event
// has fired but we haven't processed it yet.
err := w.port.DissociatePath(path)
if err != nil && err != unix.ENOENT {
if err != nil && !errors.Is(err, unix.ENOENT) {
return err
}
}
// FILE_NOFOLLOW means we watch symlinks themselves rather than their
// targets.
events := unix.FILE_MODIFIED | unix.FILE_ATTRIB | unix.FILE_NOFOLLOW
if follow {
// We *DO* follow symlinks for explicitly watched entries.
events = unix.FILE_MODIFIED | unix.FILE_ATTRIB
var events int
if !follow {
// Watch symlinks themselves rather than their targets unless this entry
// is explicitly watched.
events |= unix.FILE_NOFOLLOW
}
return w.port.AssociatePath(path, stat,
events,
stat.Mode())
if true { // TODO: implement withOps()
events |= unix.FILE_MODIFIED
}
if true {
events |= unix.FILE_ATTRIB
}
return w.port.AssociatePath(path, stat, events, stat.Mode())
}
func (w *Watcher) dissociateFile(path string, stat os.FileInfo, unused bool) error {
func (w *fen) dissociateFile(path string, stat os.FileInfo, unused bool) error {
if !w.port.PathIsWatched(path) {
return nil
}
return w.port.DissociatePath(path)
}
// WatchList returns all paths explicitly added with [Watcher.Add] (and are not
// yet removed).
//
// Returns nil if [Watcher.Close] was called.
func (w *Watcher) WatchList() []string {
func (w *fen) WatchList() []string {
if w.isClosed() {
return nil
}
@ -638,3 +474,11 @@ func (w *Watcher) WatchList() []string {
return entries
}
func (w *fen) xSupports(op Op) bool {
if op.Has(xUnportableOpen) || op.Has(xUnportableRead) ||
op.Has(xUnportableCloseWrite) || op.Has(xUnportableCloseRead) {
return false
}
return true
}

View file

@ -1,8 +1,4 @@
//go:build linux && !appengine
// +build linux,!appengine
// Note: the documentation on the Watcher type and methods is generated from
// mkdoc.zsh
package fsnotify
@ -10,127 +6,20 @@ import (
"errors"
"fmt"
"io"
"io/fs"
"os"
"path/filepath"
"strings"
"sync"
"time"
"unsafe"
"github.com/fsnotify/fsnotify/internal"
"golang.org/x/sys/unix"
)
// Watcher watches a set of paths, delivering events on a channel.
//
// A watcher should not be copied (e.g. pass it by pointer, rather than by
// value).
//
// # Linux notes
//
// When a file is removed a Remove event won't be emitted until all file
// descriptors are closed, and deletes will always emit a Chmod. For example:
//
// fp := os.Open("file")
// os.Remove("file") // Triggers Chmod
// fp.Close() // Triggers Remove
//
// This is the event that inotify sends, so not much can be changed about this.
//
// The fs.inotify.max_user_watches sysctl variable specifies the upper limit
// for the number of watches per user, and fs.inotify.max_user_instances
// specifies the maximum number of inotify instances per user. Every Watcher you
// create is an "instance", and every path you add is a "watch".
//
// These are also exposed in /proc as /proc/sys/fs/inotify/max_user_watches and
// /proc/sys/fs/inotify/max_user_instances
//
// To increase them you can use sysctl or write the value to the /proc file:
//
// # Default values on Linux 5.18
// sysctl fs.inotify.max_user_watches=124983
// sysctl fs.inotify.max_user_instances=128
//
// To make the changes persist on reboot edit /etc/sysctl.conf or
// /usr/lib/sysctl.d/50-default.conf (details differ per Linux distro; check
// your distro's documentation):
//
// fs.inotify.max_user_watches=124983
// fs.inotify.max_user_instances=128
//
// Reaching the limit will result in a "no space left on device" or "too many open
// files" error.
//
// # kqueue notes (macOS, BSD)
//
// kqueue requires opening a file descriptor for every file that's being watched;
// so if you're watching a directory with five files then that's six file
// descriptors. You will run in to your system's "max open files" limit faster on
// these platforms.
//
// The sysctl variables kern.maxfiles and kern.maxfilesperproc can be used to
// control the maximum number of open files, as well as /etc/login.conf on BSD
// systems.
//
// # Windows notes
//
// Paths can be added as "C:\path\to\dir", but forward slashes
// ("C:/path/to/dir") will also work.
//
// When a watched directory is removed it will always send an event for the
// directory itself, but may not send events for all files in that directory.
// Sometimes it will send events for all times, sometimes it will send no
// events, and often only for some files.
//
// The default ReadDirectoryChangesW() buffer size is 64K, which is the largest
// value that is guaranteed to work with SMB filesystems. If you have many
// events in quick succession this may not be enough, and you will have to use
// [WithBufferSize] to increase the value.
type Watcher struct {
// Events sends the filesystem change events.
//
// fsnotify can send the following events; a "path" here can refer to a
// file, directory, symbolic link, or special file like a FIFO.
//
// fsnotify.Create A new path was created; this may be followed by one
// or more Write events if data also gets written to a
// file.
//
// fsnotify.Remove A path was removed.
//
// fsnotify.Rename A path was renamed. A rename is always sent with the
// old path as Event.Name, and a Create event will be
// sent with the new name. Renames are only sent for
// paths that are currently watched; e.g. moving an
// unmonitored file into a monitored directory will
// show up as just a Create. Similarly, renaming a file
// to outside a monitored directory will show up as
// only a Rename.
//
// fsnotify.Write A file or named pipe was written to. A Truncate will
// also trigger a Write. A single "write action"
// initiated by the user may show up as one or multiple
// writes, depending on when the system syncs things to
// disk. For example when compiling a large Go program
// you may get hundreds of Write events, and you may
// want to wait until you've stopped receiving them
// (see the dedup example in cmd/fsnotify).
//
// Some systems may send Write event for directories
// when the directory content changes.
//
// fsnotify.Chmod Attributes were changed. On Linux this is also sent
// when a file is removed (or more accurately, when a
// link to an inode is removed). On kqueue it's sent
// when a file is truncated. On Windows it's never
// sent.
type inotify struct {
Events chan Event
// Errors sends any errors.
//
// ErrEventOverflow is used to indicate there are too many events:
//
// - inotify: There are too many queued events (fs.inotify.max_queued_events sysctl)
// - windows: The buffer size is too small; WithBufferSize() can be used to increase it.
// - kqueue, fen: Not used.
Errors chan error
// Store fd here as os.File.Read() will no longer return on close after
@ -139,8 +28,26 @@ type Watcher struct {
inotifyFile *os.File
watches *watches
done chan struct{} // Channel for sending a "quit message" to the reader goroutine
closeMu sync.Mutex
doneMu sync.Mutex
doneResp chan struct{} // Channel to respond to Close
// Store rename cookies in an array, with the index wrapping to 0. Almost
// all of the time what we get is a MOVED_FROM to set the cookie and the
// next event inotify sends will be MOVED_TO to read it. However, this is
// not guaranteed – as described in inotify(7) – and we may get other events
// between the two MOVED_* events (including other MOVED_* ones).
//
// A second issue is that moving a file outside the watched directory will
// trigger a MOVED_FROM to set the cookie, but we never see the MOVED_TO to
// read and delete it. So just storing it in a map would slowly leak memory.
//
// Doing it like this gives us a simple fast LRU-cache that won't allocate.
// Ten items should be more than enough for our purpose, and a loop over
// such a short array is faster than a map access anyway (not that it hugely
// matters since we're talking about hundreds of ns at the most, but still).
cookies [10]koekje
cookieIndex uint8
cookiesMu sync.Mutex
}
type (
@ -150,9 +57,14 @@ type (
path map[string]uint32 // pathname → wd
}
watch struct {
wd uint32 // Watch descriptor (as returned by the inotify_add_watch() syscall)
flags uint32 // inotify flags of this watch (see inotify(7) for the list of valid flags)
path string // Watch path.
wd uint32 // Watch descriptor (as returned by the inotify_add_watch() syscall)
flags uint32 // inotify flags of this watch (see inotify(7) for the list of valid flags)
path string // Watch path.
recurse bool // Recursion with ./...?
}
koekje struct {
cookie uint32
path string
}
)
@ -179,23 +91,45 @@ func (w *watches) add(ww *watch) {
func (w *watches) remove(wd uint32) {
w.mu.Lock()
defer w.mu.Unlock()
delete(w.path, w.wd[wd].path)
watch := w.wd[wd] // Could have had Remove() called. See #616.
if watch == nil {
return
}
delete(w.path, watch.path)
delete(w.wd, wd)
}
func (w *watches) removePath(path string) (uint32, bool) {
func (w *watches) removePath(path string) ([]uint32, error) {
w.mu.Lock()
defer w.mu.Unlock()
path, recurse := recursivePath(path)
wd, ok := w.path[path]
if !ok {
return 0, false
return nil, fmt.Errorf("%w: %s", ErrNonExistentWatch, path)
}
watch := w.wd[wd]
if recurse && !watch.recurse {
return nil, fmt.Errorf("can't use /... with non-recursive watch %q", path)
}
delete(w.path, path)
delete(w.wd, wd)
if !watch.recurse {
return []uint32{wd}, nil
}
return wd, true
wds := make([]uint32, 0, 8)
wds = append(wds, wd)
for p, rwd := range w.path {
if filepath.HasPrefix(p, path) {
delete(w.path, p)
delete(w.wd, rwd)
wds = append(wds, rwd)
}
}
return wds, nil
}
func (w *watches) byPath(path string) *watch {
@ -236,20 +170,11 @@ func (w *watches) updatePath(path string, f func(*watch) (*watch, error)) error
return nil
}
// NewWatcher creates a new Watcher.
func NewWatcher() (*Watcher, error) {
return NewBufferedWatcher(0)
func newBackend(ev chan Event, errs chan error) (backend, error) {
return newBufferedBackend(0, ev, errs)
}
// NewBufferedWatcher creates a new Watcher with a buffered Watcher.Events
// channel.
//
// The main use case for this is situations with a very large number of events
// where the kernel buffer size can't be increased (e.g. due to lack of
// permissions). An unbuffered Watcher will perform better for almost all use
// cases, and whenever possible you will be better off increasing the kernel
// buffers instead of adding a large userspace buffer.
func NewBufferedWatcher(sz uint) (*Watcher, error) {
func newBufferedBackend(sz uint, ev chan Event, errs chan error) (backend, error) {
// Need to set nonblocking mode for SetDeadline to work, otherwise blocking
// I/O operations won't terminate on close.
fd, errno := unix.InotifyInit1(unix.IN_CLOEXEC | unix.IN_NONBLOCK)
@ -257,12 +182,12 @@ func NewBufferedWatcher(sz uint) (*Watcher, error) {
return nil, errno
}
w := &Watcher{
w := &inotify{
Events: ev,
Errors: errs,
fd: fd,
inotifyFile: os.NewFile(uintptr(fd), ""),
watches: newWatches(),
Events: make(chan Event, sz),
Errors: make(chan error),
done: make(chan struct{}),
doneResp: make(chan struct{}),
}
@ -272,26 +197,29 @@ func NewBufferedWatcher(sz uint) (*Watcher, error) {
}
// Returns true if the event was sent, or false if watcher is closed.
func (w *Watcher) sendEvent(e Event) bool {
func (w *inotify) sendEvent(e Event) bool {
select {
case w.Events <- e:
return true
case <-w.done:
return false
case w.Events <- e:
return true
}
}
// Returns true if the error was sent, or false if watcher is closed.
func (w *Watcher) sendError(err error) bool {
select {
case w.Errors <- err:
func (w *inotify) sendError(err error) bool {
if err == nil {
return true
}
select {
case <-w.done:
return false
case w.Errors <- err:
return true
}
}
func (w *Watcher) isClosed() bool {
func (w *inotify) isClosed() bool {
select {
case <-w.done:
return true
@ -300,15 +228,14 @@ func (w *Watcher) isClosed() bool {
}
}
// Close removes all watches and closes the Events channel.
func (w *Watcher) Close() error {
w.closeMu.Lock()
func (w *inotify) Close() error {
w.doneMu.Lock()
if w.isClosed() {
w.closeMu.Unlock()
w.doneMu.Unlock()
return nil
}
close(w.done)
w.closeMu.Unlock()
w.doneMu.Unlock()
// Causes any blocking reads to return with an error, provided the file
// still supports deadline operations.
@ -323,78 +250,104 @@ func (w *Watcher) Close() error {
return nil
}
// Add starts monitoring the path for changes.
//
// A path can only be watched once; watching it more than once is a no-op and will
// not return an error. Paths that do not yet exist on the filesystem cannot be
// watched.
//
// A watch will be automatically removed if the watched path is deleted or
// renamed. The exception is the Windows backend, which doesn't remove the
// watcher on renames.
//
// Notifications on network filesystems (NFS, SMB, FUSE, etc.) or special
// filesystems (/proc, /sys, etc.) generally don't work.
//
// Returns [ErrClosed] if [Watcher.Close] was called.
//
// See [Watcher.AddWith] for a version that allows adding options.
//
// # Watching directories
//
// All files in a directory are monitored, including new files that are created
// after the watcher is started. Subdirectories are not watched (i.e. it's
// non-recursive).
//
// # Watching files
//
// Watching individual files (rather than directories) is generally not
// recommended as many programs (especially editors) update files atomically: it
// will write to a temporary file which is then moved to to destination,
// overwriting the original (or some variant thereof). The watcher on the
// original file is now lost, as that no longer exists.
//
// The upshot of this is that a power failure or crash won't leave a
// half-written file.
//
// Watch the parent directory and use Event.Name to filter out files you're not
// interested in. There is an example of this in cmd/fsnotify/file.go.
func (w *Watcher) Add(name string) error { return w.AddWith(name) }
func (w *inotify) Add(name string) error { return w.AddWith(name) }
// AddWith is like [Watcher.Add], but allows adding options. When using Add()
// the defaults described below are used.
//
// Possible options are:
//
// - [WithBufferSize] sets the buffer size for the Windows backend; no-op on
// other platforms. The default is 64K (65536 bytes).
func (w *Watcher) AddWith(name string, opts ...addOpt) error {
func (w *inotify) AddWith(path string, opts ...addOpt) error {
if w.isClosed() {
return ErrClosed
}
if debug {
fmt.Fprintf(os.Stderr, "FSNOTIFY_DEBUG: %s AddWith(%q)\n",
time.Now().Format("15:04:05.000000000"), path)
}
name = filepath.Clean(name)
_ = getOptions(opts...)
with := getOptions(opts...)
if !w.xSupports(with.op) {
return fmt.Errorf("%w: %s", xErrUnsupported, with.op)
}
var flags uint32 = unix.IN_MOVED_TO | unix.IN_MOVED_FROM |
unix.IN_CREATE | unix.IN_ATTRIB | unix.IN_MODIFY |
unix.IN_MOVE_SELF | unix.IN_DELETE | unix.IN_DELETE_SELF
path, recurse := recursivePath(path)
if recurse {
return filepath.WalkDir(path, func(root string, d fs.DirEntry, err error) error {
if err != nil {
return err
}
if !d.IsDir() {
if root == path {
return fmt.Errorf("fsnotify: not a directory: %q", path)
}
return nil
}
return w.watches.updatePath(name, func(existing *watch) (*watch, error) {
// Send a Create event when adding new directory from a recursive
// watch; this is for "mkdir -p one/two/three". Usually all those
// directories will be created before we can set up watchers on the
// subdirectories, so only "one" would be sent as a Create event and
// not "one/two" and "one/two/three" (inotifywait -r has the same
// problem).
if with.sendCreate && root != path {
w.sendEvent(Event{Name: root, Op: Create})
}
return w.add(root, with, true)
})
}
return w.add(path, with, false)
}
func (w *inotify) add(path string, with withOpts, recurse bool) error {
var flags uint32
if with.noFollow {
flags |= unix.IN_DONT_FOLLOW
}
if with.op.Has(Create) {
flags |= unix.IN_CREATE
}
if with.op.Has(Write) {
flags |= unix.IN_MODIFY
}
if with.op.Has(Remove) {
flags |= unix.IN_DELETE | unix.IN_DELETE_SELF
}
if with.op.Has(Rename) {
flags |= unix.IN_MOVED_TO | unix.IN_MOVED_FROM | unix.IN_MOVE_SELF
}
if with.op.Has(Chmod) {
flags |= unix.IN_ATTRIB
}
if with.op.Has(xUnportableOpen) {
flags |= unix.IN_OPEN
}
if with.op.Has(xUnportableRead) {
flags |= unix.IN_ACCESS
}
if with.op.Has(xUnportableCloseWrite) {
flags |= unix.IN_CLOSE_WRITE
}
if with.op.Has(xUnportableCloseRead) {
flags |= unix.IN_CLOSE_NOWRITE
}
return w.register(path, flags, recurse)
}
func (w *inotify) register(path string, flags uint32, recurse bool) error {
return w.watches.updatePath(path, func(existing *watch) (*watch, error) {
if existing != nil {
flags |= existing.flags | unix.IN_MASK_ADD
}
wd, err := unix.InotifyAddWatch(w.fd, name, flags)
wd, err := unix.InotifyAddWatch(w.fd, path, flags)
if wd == -1 {
return nil, err
}
if existing == nil {
return &watch{
wd: uint32(wd),
path: name,
flags: flags,
wd: uint32(wd),
path: path,
flags: flags,
recurse: recurse,
}, nil
}
@ -404,49 +357,44 @@ func (w *Watcher) AddWith(name string, opts ...addOpt) error {
})
}
// Remove stops monitoring the path for changes.
//
// Directories are always removed non-recursively. For example, if you added
// /tmp/dir and /tmp/dir/subdir then you will need to remove both.
//
// Removing a path that has not yet been added returns [ErrNonExistentWatch].
//
// Returns nil if [Watcher.Close] was called.
func (w *Watcher) Remove(name string) error {
func (w *inotify) Remove(name string) error {
if w.isClosed() {
return nil
}
if debug {
fmt.Fprintf(os.Stderr, "FSNOTIFY_DEBUG: %s Remove(%q)\n",
time.Now().Format("15:04:05.000000000"), name)
}
return w.remove(filepath.Clean(name))
}
func (w *Watcher) remove(name string) error {
wd, ok := w.watches.removePath(name)
if !ok {
return fmt.Errorf("%w: %s", ErrNonExistentWatch, name)
func (w *inotify) remove(name string) error {
wds, err := w.watches.removePath(name)
if err != nil {
return err
}
success, errno := unix.InotifyRmWatch(w.fd, wd)
if success == -1 {
// TODO: Perhaps it's not helpful to return an error here in every case;
// The only two possible errors are:
//
// - EBADF, which happens when w.fd is not a valid file descriptor
// of any kind.
// - EINVAL, which is when fd is not an inotify descriptor or wd
// is not a valid watch descriptor. Watch descriptors are
// invalidated when they are removed explicitly or implicitly;
// explicitly by inotify_rm_watch, implicitly when the file they
// are watching is deleted.
return errno
for _, wd := range wds {
_, err := unix.InotifyRmWatch(w.fd, wd)
if err != nil {
// TODO: Perhaps it's not helpful to return an error here in every
// case; the only two possible errors are:
//
// EBADF, which happens when w.fd is not a valid file descriptor of
// any kind.
//
// EINVAL, which is when fd is not an inotify descriptor or wd is
// not a valid watch descriptor. Watch descriptors are invalidated
// when they are removed explicitly or implicitly; explicitly by
// inotify_rm_watch, implicitly when the file they are watching is
// deleted.
return err
}
}
return nil
}
// WatchList returns all paths explicitly added with [Watcher.Add] (and are not
// yet removed).
//
// Returns nil if [Watcher.Close] was called.
func (w *Watcher) WatchList() []string {
func (w *inotify) WatchList() []string {
if w.isClosed() {
return nil
}
@ -463,7 +411,7 @@ func (w *Watcher) WatchList() []string {
// readEvents reads from the inotify file descriptor, converts the
// received events into Event objects and sends them via the Events channel
func (w *Watcher) readEvents() {
func (w *inotify) readEvents() {
defer func() {
close(w.doneResp)
close(w.Errors)
@ -506,15 +454,17 @@ func (w *Watcher) readEvents() {
continue
}
var offset uint32
// We don't know how many events we just read into the buffer
// While the offset points to at least one whole event...
var offset uint32
for offset <= uint32(n-unix.SizeofInotifyEvent) {
var (
// Point "raw" to the event in the buffer
raw = (*unix.InotifyEvent)(unsafe.Pointer(&buf[offset]))
mask = uint32(raw.Mask)
nameLen = uint32(raw.Len)
// Move to the next event in the buffer
next = func() { offset += unix.SizeofInotifyEvent + nameLen }
)
if mask&unix.IN_Q_OVERFLOW != 0 {
@ -523,21 +473,53 @@ func (w *Watcher) readEvents() {
}
}
// If the event happened to the watched directory or the watched file, the kernel
// doesn't append the filename to the event, but we would like to always fill the
// the "Name" field with a valid filename. We retrieve the path of the watch from
// the "paths" map.
/// If the event happened to the watched directory or the watched
/// file, the kernel doesn't append the filename to the event, but
/// we would like to always fill the the "Name" field with a valid
/// filename. We retrieve the path of the watch from the "paths"
/// map.
watch := w.watches.byWd(uint32(raw.Wd))
/// Can be nil if Remove() was called in another goroutine for this
/// path inbetween reading the events from the kernel and reading
/// the internal state. Not much we can do about it, so just skip.
/// See #616.
if watch == nil {
next()
continue
}
name := watch.path
if nameLen > 0 {
/// Point "bytes" at the first byte of the filename
bytes := (*[unix.PathMax]byte)(unsafe.Pointer(&buf[offset+unix.SizeofInotifyEvent]))[:nameLen:nameLen]
/// The filename is padded with NULL bytes. TrimRight() gets rid of those.
name += "/" + strings.TrimRight(string(bytes[0:nameLen]), "\000")
}
if debug {
internal.Debug(name, raw.Mask, raw.Cookie)
}
if mask&unix.IN_IGNORED != 0 { //&& event.Op != 0
next()
continue
}
// inotify will automatically remove the watch on deletes; just need
// to clean our state here.
if watch != nil && mask&unix.IN_DELETE_SELF == unix.IN_DELETE_SELF {
if mask&unix.IN_DELETE_SELF == unix.IN_DELETE_SELF {
w.watches.remove(watch.wd)
}
// We can't really update the state when a watched path is moved;
// only IN_MOVE_SELF is sent and not IN_MOVED_{FROM,TO}. So remove
// the watch.
if watch != nil && mask&unix.IN_MOVE_SELF == unix.IN_MOVE_SELF {
if mask&unix.IN_MOVE_SELF == unix.IN_MOVE_SELF {
if watch.recurse {
next() // Do nothing
continue
}
err := w.remove(watch.path)
if err != nil && !errors.Is(err, ErrNonExistentWatch) {
if !w.sendError(err) {
@ -546,34 +528,69 @@ func (w *Watcher) readEvents() {
}
}
var name string
if watch != nil {
name = watch.path
}
if nameLen > 0 {
// Point "bytes" at the first byte of the filename
bytes := (*[unix.PathMax]byte)(unsafe.Pointer(&buf[offset+unix.SizeofInotifyEvent]))[:nameLen:nameLen]
// The filename is padded with NULL bytes. TrimRight() gets rid of those.
name += "/" + strings.TrimRight(string(bytes[0:nameLen]), "\000")
}
event := w.newEvent(name, mask)
// Send the events that are not ignored on the events channel
if mask&unix.IN_IGNORED == 0 {
if !w.sendEvent(event) {
return
/// Skip if we're watching both this path and the parent; the parent
/// will already send a delete so no need to do it twice.
if mask&unix.IN_DELETE_SELF != 0 {
if _, ok := w.watches.path[filepath.Dir(watch.path)]; ok {
next()
continue
}
}
// Move to the next event in the buffer
offset += unix.SizeofInotifyEvent + nameLen
ev := w.newEvent(name, mask, raw.Cookie)
// Need to update watch path for recurse.
if watch.recurse {
isDir := mask&unix.IN_ISDIR == unix.IN_ISDIR
/// New directory created: set up watch on it.
if isDir && ev.Has(Create) {
err := w.register(ev.Name, watch.flags, true)
if !w.sendError(err) {
return
}
// This was a directory rename, so we need to update all
// the children.
//
// TODO: this is of course pretty slow; we should use a
// better data structure for storing all of this, e.g. store
// children in the watch. I have some code for this in my
// kqueue refactor we can use in the future. For now I'm
// okay with this as it's not publicly available.
// Correctness first, performance second.
if ev.renamedFrom != "" {
w.watches.mu.Lock()
for k, ww := range w.watches.wd {
if k == watch.wd || ww.path == ev.Name {
continue
}
if strings.HasPrefix(ww.path, ev.renamedFrom) {
ww.path = strings.Replace(ww.path, ev.renamedFrom, ev.Name, 1)
w.watches.wd[k] = ww
}
}
w.watches.mu.Unlock()
}
}
}
/// Send the events that are not ignored on the events channel
if !w.sendEvent(ev) {
return
}
next()
}
}
}
// newEvent returns an platform-independent Event based on an inotify mask.
func (w *Watcher) newEvent(name string, mask uint32) Event {
func (w *inotify) isRecursive(path string) bool {
ww := w.watches.byPath(path)
if ww == nil { // path could be a file, so also check the Dir.
ww = w.watches.byPath(filepath.Dir(path))
}
return ww != nil && ww.recurse
}
func (w *inotify) newEvent(name string, mask, cookie uint32) Event {
e := Event{Name: name}
if mask&unix.IN_CREATE == unix.IN_CREATE || mask&unix.IN_MOVED_TO == unix.IN_MOVED_TO {
e.Op |= Create
@ -584,11 +601,58 @@ func (w *Watcher) newEvent(name string, mask uint32) Event {
if mask&unix.IN_MODIFY == unix.IN_MODIFY {
e.Op |= Write
}
if mask&unix.IN_OPEN == unix.IN_OPEN {
e.Op |= xUnportableOpen
}
if mask&unix.IN_ACCESS == unix.IN_ACCESS {
e.Op |= xUnportableRead
}
if mask&unix.IN_CLOSE_WRITE == unix.IN_CLOSE_WRITE {
e.Op |= xUnportableCloseWrite
}
if mask&unix.IN_CLOSE_NOWRITE == unix.IN_CLOSE_NOWRITE {
e.Op |= xUnportableCloseRead
}
if mask&unix.IN_MOVE_SELF == unix.IN_MOVE_SELF || mask&unix.IN_MOVED_FROM == unix.IN_MOVED_FROM {
e.Op |= Rename
}
if mask&unix.IN_ATTRIB == unix.IN_ATTRIB {
e.Op |= Chmod
}
if cookie != 0 {
if mask&unix.IN_MOVED_FROM == unix.IN_MOVED_FROM {
w.cookiesMu.Lock()
w.cookies[w.cookieIndex] = koekje{cookie: cookie, path: e.Name}
w.cookieIndex++
if w.cookieIndex > 9 {
w.cookieIndex = 0
}
w.cookiesMu.Unlock()
} else if mask&unix.IN_MOVED_TO == unix.IN_MOVED_TO {
w.cookiesMu.Lock()
var prev string
for _, c := range w.cookies {
if c.cookie == cookie {
prev = c.path
break
}
}
w.cookiesMu.Unlock()
e.renamedFrom = prev
}
}
return e
}
func (w *inotify) xSupports(op Op) bool {
return true // Supports everything.
}
func (w *inotify) state() {
w.watches.mu.Lock()
defer w.watches.mu.Unlock()
for wd, ww := range w.watches.wd {
fmt.Fprintf(os.Stderr, "%4d: recurse=%t %q\n", wd, ww.recurse, ww.path)
}
}

File diff suppressed because it is too large Load diff

View file

@ -1,205 +1,23 @@
//go:build appengine || (!darwin && !dragonfly && !freebsd && !openbsd && !linux && !netbsd && !solaris && !windows)
// +build appengine !darwin,!dragonfly,!freebsd,!openbsd,!linux,!netbsd,!solaris,!windows
// Note: the documentation on the Watcher type and methods is generated from
// mkdoc.zsh
package fsnotify
import "errors"
// Watcher watches a set of paths, delivering events on a channel.
//
// A watcher should not be copied (e.g. pass it by pointer, rather than by
// value).
//
// # Linux notes
//
// When a file is removed a Remove event won't be emitted until all file
// descriptors are closed, and deletes will always emit a Chmod. For example:
//
// fp := os.Open("file")
// os.Remove("file") // Triggers Chmod
// fp.Close() // Triggers Remove
//
// This is the event that inotify sends, so not much can be changed about this.
//
// The fs.inotify.max_user_watches sysctl variable specifies the upper limit
// for the number of watches per user, and fs.inotify.max_user_instances
// specifies the maximum number of inotify instances per user. Every Watcher you
// create is an "instance", and every path you add is a "watch".
//
// These are also exposed in /proc as /proc/sys/fs/inotify/max_user_watches and
// /proc/sys/fs/inotify/max_user_instances
//
// To increase them you can use sysctl or write the value to the /proc file:
//
// # Default values on Linux 5.18
// sysctl fs.inotify.max_user_watches=124983
// sysctl fs.inotify.max_user_instances=128
//
// To make the changes persist on reboot edit /etc/sysctl.conf or
// /usr/lib/sysctl.d/50-default.conf (details differ per Linux distro; check
// your distro's documentation):
//
// fs.inotify.max_user_watches=124983
// fs.inotify.max_user_instances=128
//
// Reaching the limit will result in a "no space left on device" or "too many open
// files" error.
//
// # kqueue notes (macOS, BSD)
//
// kqueue requires opening a file descriptor for every file that's being watched;
// so if you're watching a directory with five files then that's six file
// descriptors. You will run in to your system's "max open files" limit faster on
// these platforms.
//
// The sysctl variables kern.maxfiles and kern.maxfilesperproc can be used to
// control the maximum number of open files, as well as /etc/login.conf on BSD
// systems.
//
// # Windows notes
//
// Paths can be added as "C:\path\to\dir", but forward slashes
// ("C:/path/to/dir") will also work.
//
// When a watched directory is removed it will always send an event for the
// directory itself, but may not send events for all files in that directory.
// Sometimes it will send events for all times, sometimes it will send no
// events, and often only for some files.
//
// The default ReadDirectoryChangesW() buffer size is 64K, which is the largest
// value that is guaranteed to work with SMB filesystems. If you have many
// events in quick succession this may not be enough, and you will have to use
// [WithBufferSize] to increase the value.
type Watcher struct {
// Events sends the filesystem change events.
//
// fsnotify can send the following events; a "path" here can refer to a
// file, directory, symbolic link, or special file like a FIFO.
//
// fsnotify.Create A new path was created; this may be followed by one
// or more Write events if data also gets written to a
// file.
//
// fsnotify.Remove A path was removed.
//
// fsnotify.Rename A path was renamed. A rename is always sent with the
// old path as Event.Name, and a Create event will be
// sent with the new name. Renames are only sent for
// paths that are currently watched; e.g. moving an
// unmonitored file into a monitored directory will
// show up as just a Create. Similarly, renaming a file
// to outside a monitored directory will show up as
// only a Rename.
//
// fsnotify.Write A file or named pipe was written to. A Truncate will
// also trigger a Write. A single "write action"
// initiated by the user may show up as one or multiple
// writes, depending on when the system syncs things to
// disk. For example when compiling a large Go program
// you may get hundreds of Write events, and you may
// want to wait until you've stopped receiving them
// (see the dedup example in cmd/fsnotify).
//
// Some systems may send Write event for directories
// when the directory content changes.
//
// fsnotify.Chmod Attributes were changed. On Linux this is also sent
// when a file is removed (or more accurately, when a
// link to an inode is removed). On kqueue it's sent
// when a file is truncated. On Windows it's never
// sent.
type other struct {
Events chan Event
// Errors sends any errors.
//
// ErrEventOverflow is used to indicate there are too many events:
//
// - inotify: There are too many queued events (fs.inotify.max_queued_events sysctl)
// - windows: The buffer size is too small; WithBufferSize() can be used to increase it.
// - kqueue, fen: Not used.
Errors chan error
}
// NewWatcher creates a new Watcher.
func NewWatcher() (*Watcher, error) {
func newBackend(ev chan Event, errs chan error) (backend, error) {
return nil, errors.New("fsnotify not supported on the current platform")
}
// NewBufferedWatcher creates a new Watcher with a buffered Watcher.Events
// channel.
//
// The main use case for this is situations with a very large number of events
// where the kernel buffer size can't be increased (e.g. due to lack of
// permissions). An unbuffered Watcher will perform better for almost all use
// cases, and whenever possible you will be better off increasing the kernel
// buffers instead of adding a large userspace buffer.
func NewBufferedWatcher(sz uint) (*Watcher, error) { return NewWatcher() }
// Close removes all watches and closes the Events channel.
func (w *Watcher) Close() error { return nil }
// WatchList returns all paths explicitly added with [Watcher.Add] (and are not
// yet removed).
//
// Returns nil if [Watcher.Close] was called.
func (w *Watcher) WatchList() []string { return nil }
// Add starts monitoring the path for changes.
//
// A path can only be watched once; watching it more than once is a no-op and will
// not return an error. Paths that do not yet exist on the filesystem cannot be
// watched.
//
// A watch will be automatically removed if the watched path is deleted or
// renamed. The exception is the Windows backend, which doesn't remove the
// watcher on renames.
//
// Notifications on network filesystems (NFS, SMB, FUSE, etc.) or special
// filesystems (/proc, /sys, etc.) generally don't work.
//
// Returns [ErrClosed] if [Watcher.Close] was called.
//
// See [Watcher.AddWith] for a version that allows adding options.
//
// # Watching directories
//
// All files in a directory are monitored, including new files that are created
// after the watcher is started. Subdirectories are not watched (i.e. it's
// non-recursive).
//
// # Watching files
//
// Watching individual files (rather than directories) is generally not
// recommended as many programs (especially editors) update files atomically: it
// will write to a temporary file which is then moved to to destination,
// overwriting the original (or some variant thereof). The watcher on the
// original file is now lost, as that no longer exists.
//
// The upshot of this is that a power failure or crash won't leave a
// half-written file.
//
// Watch the parent directory and use Event.Name to filter out files you're not
// interested in. There is an example of this in cmd/fsnotify/file.go.
func (w *Watcher) Add(name string) error { return nil }
// AddWith is like [Watcher.Add], but allows adding options. When using Add()
// the defaults described below are used.
//
// Possible options are:
//
// - [WithBufferSize] sets the buffer size for the Windows backend; no-op on
// other platforms. The default is 64K (65536 bytes).
func (w *Watcher) AddWith(name string, opts ...addOpt) error { return nil }
// Remove stops monitoring the path for changes.
//
// Directories are always removed non-recursively. For example, if you added
// /tmp/dir and /tmp/dir/subdir then you will need to remove both.
//
// Removing a path that has not yet been added returns [ErrNonExistentWatch].
//
// Returns nil if [Watcher.Close] was called.
func (w *Watcher) Remove(name string) error { return nil }
func newBufferedBackend(sz uint, ev chan Event, errs chan error) (backend, error) {
return newBackend(ev, errs)
}
func (w *other) Close() error { return nil }
func (w *other) WatchList() []string { return nil }
func (w *other) Add(name string) error { return nil }
func (w *other) AddWith(name string, opts ...addOpt) error { return nil }
func (w *other) Remove(name string) error { return nil }
func (w *other) xSupports(op Op) bool { return false }

View file

@ -1,12 +1,8 @@
//go:build windows
// +build windows
// Windows backend based on ReadDirectoryChangesW()
//
// https://learn.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-readdirectorychangesw
//
// Note: the documentation on the Watcher type and methods is generated from
// mkdoc.zsh
package fsnotify
@ -19,123 +15,15 @@ import (
"runtime"
"strings"
"sync"
"time"
"unsafe"
"github.com/fsnotify/fsnotify/internal"
"golang.org/x/sys/windows"
)
// Watcher watches a set of paths, delivering events on a channel.
//
// A watcher should not be copied (e.g. pass it by pointer, rather than by
// value).
//
// # Linux notes
//
// When a file is removed a Remove event won't be emitted until all file
// descriptors are closed, and deletes will always emit a Chmod. For example:
//
// fp := os.Open("file")
// os.Remove("file") // Triggers Chmod
// fp.Close() // Triggers Remove
//
// This is the event that inotify sends, so not much can be changed about this.
//
// The fs.inotify.max_user_watches sysctl variable specifies the upper limit
// for the number of watches per user, and fs.inotify.max_user_instances
// specifies the maximum number of inotify instances per user. Every Watcher you
// create is an "instance", and every path you add is a "watch".
//
// These are also exposed in /proc as /proc/sys/fs/inotify/max_user_watches and
// /proc/sys/fs/inotify/max_user_instances
//
// To increase them you can use sysctl or write the value to the /proc file:
//
// # Default values on Linux 5.18
// sysctl fs.inotify.max_user_watches=124983
// sysctl fs.inotify.max_user_instances=128
//
// To make the changes persist on reboot edit /etc/sysctl.conf or
// /usr/lib/sysctl.d/50-default.conf (details differ per Linux distro; check
// your distro's documentation):
//
// fs.inotify.max_user_watches=124983
// fs.inotify.max_user_instances=128
//
// Reaching the limit will result in a "no space left on device" or "too many open
// files" error.
//
// # kqueue notes (macOS, BSD)
//
// kqueue requires opening a file descriptor for every file that's being watched;
// so if you're watching a directory with five files then that's six file
// descriptors. You will run in to your system's "max open files" limit faster on
// these platforms.
//
// The sysctl variables kern.maxfiles and kern.maxfilesperproc can be used to
// control the maximum number of open files, as well as /etc/login.conf on BSD
// systems.
//
// # Windows notes
//
// Paths can be added as "C:\path\to\dir", but forward slashes
// ("C:/path/to/dir") will also work.
//
// When a watched directory is removed it will always send an event for the
// directory itself, but may not send events for all files in that directory.
// Sometimes it will send events for all times, sometimes it will send no
// events, and often only for some files.
//
// The default ReadDirectoryChangesW() buffer size is 64K, which is the largest
// value that is guaranteed to work with SMB filesystems. If you have many
// events in quick succession this may not be enough, and you will have to use
// [WithBufferSize] to increase the value.
type Watcher struct {
// Events sends the filesystem change events.
//
// fsnotify can send the following events; a "path" here can refer to a
// file, directory, symbolic link, or special file like a FIFO.
//
// fsnotify.Create A new path was created; this may be followed by one
// or more Write events if data also gets written to a
// file.
//
// fsnotify.Remove A path was removed.
//
// fsnotify.Rename A path was renamed. A rename is always sent with the
// old path as Event.Name, and a Create event will be
// sent with the new name. Renames are only sent for
// paths that are currently watched; e.g. moving an
// unmonitored file into a monitored directory will
// show up as just a Create. Similarly, renaming a file
// to outside a monitored directory will show up as
// only a Rename.
//
// fsnotify.Write A file or named pipe was written to. A Truncate will
// also trigger a Write. A single "write action"
// initiated by the user may show up as one or multiple
// writes, depending on when the system syncs things to
// disk. For example when compiling a large Go program
// you may get hundreds of Write events, and you may
// want to wait until you've stopped receiving them
// (see the dedup example in cmd/fsnotify).
//
// Some systems may send Write event for directories
// when the directory content changes.
//
// fsnotify.Chmod Attributes were changed. On Linux this is also sent
// when a file is removed (or more accurately, when a
// link to an inode is removed). On kqueue it's sent
// when a file is truncated. On Windows it's never
// sent.
type readDirChangesW struct {
Events chan Event
// Errors sends any errors.
//
// ErrEventOverflow is used to indicate there are too many events:
//
// - inotify: There are too many queued events (fs.inotify.max_queued_events sysctl)
// - windows: The buffer size is too small; WithBufferSize() can be used to increase it.
// - kqueue, fen: Not used.
Errors chan error
port windows.Handle // Handle to completion port
@ -147,48 +35,40 @@ type Watcher struct {
closed bool // Set to true when Close() is first called
}
// NewWatcher creates a new Watcher.
func NewWatcher() (*Watcher, error) {
return NewBufferedWatcher(50)
func newBackend(ev chan Event, errs chan error) (backend, error) {
return newBufferedBackend(50, ev, errs)
}
// NewBufferedWatcher creates a new Watcher with a buffered Watcher.Events
// channel.
//
// The main use case for this is situations with a very large number of events
// where the kernel buffer size can't be increased (e.g. due to lack of
// permissions). An unbuffered Watcher will perform better for almost all use
// cases, and whenever possible you will be better off increasing the kernel
// buffers instead of adding a large userspace buffer.
func NewBufferedWatcher(sz uint) (*Watcher, error) {
func newBufferedBackend(sz uint, ev chan Event, errs chan error) (backend, error) {
port, err := windows.CreateIoCompletionPort(windows.InvalidHandle, 0, 0, 0)
if err != nil {
return nil, os.NewSyscallError("CreateIoCompletionPort", err)
}
w := &Watcher{
w := &readDirChangesW{
Events: ev,
Errors: errs,
port: port,
watches: make(watchMap),
input: make(chan *input, 1),
Events: make(chan Event, sz),
Errors: make(chan error),
quit: make(chan chan<- error, 1),
}
go w.readEvents()
return w, nil
}
func (w *Watcher) isClosed() bool {
func (w *readDirChangesW) isClosed() bool {
w.mu.Lock()
defer w.mu.Unlock()
return w.closed
}
func (w *Watcher) sendEvent(name string, mask uint64) bool {
func (w *readDirChangesW) sendEvent(name, renamedFrom string, mask uint64) bool {
if mask == 0 {
return false
}
event := w.newEvent(name, uint32(mask))
event.renamedFrom = renamedFrom
select {
case ch := <-w.quit:
w.quit <- ch
@ -198,17 +78,19 @@ func (w *Watcher) sendEvent(name string, mask uint64) bool {
}
// Returns true if the error was sent, or false if watcher is closed.
func (w *Watcher) sendError(err error) bool {
func (w *readDirChangesW) sendError(err error) bool {
if err == nil {
return true
}
select {
case w.Errors <- err:
return true
case <-w.quit:
return false
}
return false
}
// Close removes all watches and closes the Events channel.
func (w *Watcher) Close() error {
func (w *readDirChangesW) Close() error {
if w.isClosed() {
return nil
}
@ -226,57 +108,21 @@ func (w *Watcher) Close() error {
return <-ch
}
// Add starts monitoring the path for changes.
//
// A path can only be watched once; watching it more than once is a no-op and will
// not return an error. Paths that do not yet exist on the filesystem cannot be
// watched.
//
// A watch will be automatically removed if the watched path is deleted or
// renamed. The exception is the Windows backend, which doesn't remove the
// watcher on renames.
//
// Notifications on network filesystems (NFS, SMB, FUSE, etc.) or special
// filesystems (/proc, /sys, etc.) generally don't work.
//
// Returns [ErrClosed] if [Watcher.Close] was called.
//
// See [Watcher.AddWith] for a version that allows adding options.
//
// # Watching directories
//
// All files in a directory are monitored, including new files that are created
// after the watcher is started. Subdirectories are not watched (i.e. it's
// non-recursive).
//
// # Watching files
//
// Watching individual files (rather than directories) is generally not
// recommended as many programs (especially editors) update files atomically: it
// will write to a temporary file which is then moved to to destination,
// overwriting the original (or some variant thereof). The watcher on the
// original file is now lost, as that no longer exists.
//
// The upshot of this is that a power failure or crash won't leave a
// half-written file.
//
// Watch the parent directory and use Event.Name to filter out files you're not
// interested in. There is an example of this in cmd/fsnotify/file.go.
func (w *Watcher) Add(name string) error { return w.AddWith(name) }
func (w *readDirChangesW) Add(name string) error { return w.AddWith(name) }
// AddWith is like [Watcher.Add], but allows adding options. When using Add()
// the defaults described below are used.
//
// Possible options are:
//
// - [WithBufferSize] sets the buffer size for the Windows backend; no-op on
// other platforms. The default is 64K (65536 bytes).
func (w *Watcher) AddWith(name string, opts ...addOpt) error {
func (w *readDirChangesW) AddWith(name string, opts ...addOpt) error {
if w.isClosed() {
return ErrClosed
}
if debug {
fmt.Fprintf(os.Stderr, "FSNOTIFY_DEBUG: %s AddWith(%q)\n",
time.Now().Format("15:04:05.000000000"), filepath.ToSlash(name))
}
with := getOptions(opts...)
if !w.xSupports(with.op) {
return fmt.Errorf("%w: %s", xErrUnsupported, with.op)
}
if with.bufsize < 4096 {
return fmt.Errorf("fsnotify.WithBufferSize: buffer size cannot be smaller than 4096 bytes")
}
@ -295,18 +141,14 @@ func (w *Watcher) AddWith(name string, opts ...addOpt) error {
return <-in.reply
}
// Remove stops monitoring the path for changes.
//
// Directories are always removed non-recursively. For example, if you added
// /tmp/dir and /tmp/dir/subdir then you will need to remove both.
//
// Removing a path that has not yet been added returns [ErrNonExistentWatch].
//
// Returns nil if [Watcher.Close] was called.
func (w *Watcher) Remove(name string) error {
func (w *readDirChangesW) Remove(name string) error {
if w.isClosed() {
return nil
}
if debug {
fmt.Fprintf(os.Stderr, "FSNOTIFY_DEBUG: %s Remove(%q)\n",
time.Now().Format("15:04:05.000000000"), filepath.ToSlash(name))
}
in := &input{
op: opRemoveWatch,
@ -320,11 +162,7 @@ func (w *Watcher) Remove(name string) error {
return <-in.reply
}
// WatchList returns all paths explicitly added with [Watcher.Add] (and are not
// yet removed).
//
// Returns nil if [Watcher.Close] was called.
func (w *Watcher) WatchList() []string {
func (w *readDirChangesW) WatchList() []string {
if w.isClosed() {
return nil
}
@ -335,7 +173,13 @@ func (w *Watcher) WatchList() []string {
entries := make([]string, 0, len(w.watches))
for _, entry := range w.watches {
for _, watchEntry := range entry {
entries = append(entries, watchEntry.path)
for name := range watchEntry.names {
entries = append(entries, filepath.Join(watchEntry.path, name))
}
// the directory itself is being watched
if watchEntry.mask != 0 {
entries = append(entries, watchEntry.path)
}
}
}
@ -361,7 +205,7 @@ const (
sysFSIGNORED = 0x8000
)
func (w *Watcher) newEvent(name string, mask uint32) Event {
func (w *readDirChangesW) newEvent(name string, mask uint32) Event {
e := Event{Name: name}
if mask&sysFSCREATE == sysFSCREATE || mask&sysFSMOVEDTO == sysFSMOVEDTO {
e.Op |= Create
@ -417,7 +261,7 @@ type (
watchMap map[uint32]indexMap
)
func (w *Watcher) wakeupReader() error {
func (w *readDirChangesW) wakeupReader() error {
err := windows.PostQueuedCompletionStatus(w.port, 0, 0, nil)
if err != nil {
return os.NewSyscallError("PostQueuedCompletionStatus", err)
@ -425,7 +269,7 @@ func (w *Watcher) wakeupReader() error {
return nil
}
func (w *Watcher) getDir(pathname string) (dir string, err error) {
func (w *readDirChangesW) getDir(pathname string) (dir string, err error) {
attr, err := windows.GetFileAttributes(windows.StringToUTF16Ptr(pathname))
if err != nil {
return "", os.NewSyscallError("GetFileAttributes", err)
@ -439,7 +283,7 @@ func (w *Watcher) getDir(pathname string) (dir string, err error) {
return
}
func (w *Watcher) getIno(path string) (ino *inode, err error) {
func (w *readDirChangesW) getIno(path string) (ino *inode, err error) {
h, err := windows.CreateFile(windows.StringToUTF16Ptr(path),
windows.FILE_LIST_DIRECTORY,
windows.FILE_SHARE_READ|windows.FILE_SHARE_WRITE|windows.FILE_SHARE_DELETE,
@ -482,9 +326,8 @@ func (m watchMap) set(ino *inode, watch *watch) {
}
// Must run within the I/O thread.
func (w *Watcher) addWatch(pathname string, flags uint64, bufsize int) error {
//pathname, recurse := recursivePath(pathname)
recurse := false
func (w *readDirChangesW) addWatch(pathname string, flags uint64, bufsize int) error {
pathname, recurse := recursivePath(pathname)
dir, err := w.getDir(pathname)
if err != nil {
@ -538,7 +381,7 @@ func (w *Watcher) addWatch(pathname string, flags uint64, bufsize int) error {
}
// Must run within the I/O thread.
func (w *Watcher) remWatch(pathname string) error {
func (w *readDirChangesW) remWatch(pathname string) error {
pathname, recurse := recursivePath(pathname)
dir, err := w.getDir(pathname)
@ -566,11 +409,11 @@ func (w *Watcher) remWatch(pathname string) error {
return fmt.Errorf("%w: %s", ErrNonExistentWatch, pathname)
}
if pathname == dir {
w.sendEvent(watch.path, watch.mask&sysFSIGNORED)
w.sendEvent(watch.path, "", watch.mask&sysFSIGNORED)
watch.mask = 0
} else {
name := filepath.Base(pathname)
w.sendEvent(filepath.Join(watch.path, name), watch.names[name]&sysFSIGNORED)
w.sendEvent(filepath.Join(watch.path, name), "", watch.names[name]&sysFSIGNORED)
delete(watch.names, name)
}
@ -578,23 +421,23 @@ func (w *Watcher) remWatch(pathname string) error {
}
// Must run within the I/O thread.
func (w *Watcher) deleteWatch(watch *watch) {
func (w *readDirChangesW) deleteWatch(watch *watch) {
for name, mask := range watch.names {
if mask&provisional == 0 {
w.sendEvent(filepath.Join(watch.path, name), mask&sysFSIGNORED)
w.sendEvent(filepath.Join(watch.path, name), "", mask&sysFSIGNORED)
}
delete(watch.names, name)
}
if watch.mask != 0 {
if watch.mask&provisional == 0 {
w.sendEvent(watch.path, watch.mask&sysFSIGNORED)
w.sendEvent(watch.path, "", watch.mask&sysFSIGNORED)
}
watch.mask = 0
}
}
// Must run within the I/O thread.
func (w *Watcher) startRead(watch *watch) error {
func (w *readDirChangesW) startRead(watch *watch) error {
err := windows.CancelIo(watch.ino.handle)
if err != nil {
w.sendError(os.NewSyscallError("CancelIo", err))
@ -624,7 +467,7 @@ func (w *Watcher) startRead(watch *watch) error {
err := os.NewSyscallError("ReadDirectoryChanges", rdErr)
if rdErr == windows.ERROR_ACCESS_DENIED && watch.mask&provisional == 0 {
// Watched directory was probably removed
w.sendEvent(watch.path, watch.mask&sysFSDELETESELF)
w.sendEvent(watch.path, "", watch.mask&sysFSDELETESELF)
err = nil
}
w.deleteWatch(watch)
@ -637,7 +480,7 @@ func (w *Watcher) startRead(watch *watch) error {
// readEvents reads from the I/O completion port, converts the
// received events into Event objects and sends them via the Events channel.
// Entry point to the I/O thread.
func (w *Watcher) readEvents() {
func (w *readDirChangesW) readEvents() {
var (
n uint32
key uintptr
@ -700,7 +543,7 @@ func (w *Watcher) readEvents() {
}
case windows.ERROR_ACCESS_DENIED:
// Watched directory was probably removed
w.sendEvent(watch.path, watch.mask&sysFSDELETESELF)
w.sendEvent(watch.path, "", watch.mask&sysFSDELETESELF)
w.deleteWatch(watch)
w.startRead(watch)
continue
@ -733,6 +576,10 @@ func (w *Watcher) readEvents() {
name := windows.UTF16ToString(buf)
fullname := filepath.Join(watch.path, name)
if debug {
internal.Debug(fullname, raw.Action)
}
var mask uint64
switch raw.Action {
case windows.FILE_ACTION_REMOVED:
@ -761,21 +608,22 @@ func (w *Watcher) readEvents() {
}
}
sendNameEvent := func() {
w.sendEvent(fullname, watch.names[name]&mask)
}
if raw.Action != windows.FILE_ACTION_RENAMED_NEW_NAME {
sendNameEvent()
w.sendEvent(fullname, "", watch.names[name]&mask)
}
if raw.Action == windows.FILE_ACTION_REMOVED {
w.sendEvent(fullname, watch.names[name]&sysFSIGNORED)
w.sendEvent(fullname, "", watch.names[name]&sysFSIGNORED)
delete(watch.names, name)
}
w.sendEvent(fullname, watch.mask&w.toFSnotifyFlags(raw.Action))
if watch.rename != "" && raw.Action == windows.FILE_ACTION_RENAMED_NEW_NAME {
w.sendEvent(fullname, filepath.Join(watch.path, watch.rename), watch.mask&w.toFSnotifyFlags(raw.Action))
} else {
w.sendEvent(fullname, "", watch.mask&w.toFSnotifyFlags(raw.Action))
}
if raw.Action == windows.FILE_ACTION_RENAMED_NEW_NAME {
fullname = filepath.Join(watch.path, watch.rename)
sendNameEvent()
w.sendEvent(filepath.Join(watch.path, watch.rename), "", watch.names[name]&mask)
}
// Move to the next event in the buffer
@ -787,8 +635,7 @@ func (w *Watcher) readEvents() {
// Error!
if offset >= n {
//lint:ignore ST1005 Windows should be capitalized
w.sendError(errors.New(
"Windows system assumed buffer larger than it is, events have likely been missed"))
w.sendError(errors.New("Windows system assumed buffer larger than it is, events have likely been missed"))
break
}
}
@ -799,7 +646,7 @@ func (w *Watcher) readEvents() {
}
}
func (w *Watcher) toWindowsFlags(mask uint64) uint32 {
func (w *readDirChangesW) toWindowsFlags(mask uint64) uint32 {
var m uint32
if mask&sysFSMODIFY != 0 {
m |= windows.FILE_NOTIFY_CHANGE_LAST_WRITE
@ -810,7 +657,7 @@ func (w *Watcher) toWindowsFlags(mask uint64) uint32 {
return m
}
func (w *Watcher) toFSnotifyFlags(action uint32) uint64 {
func (w *readDirChangesW) toFSnotifyFlags(action uint32) uint64 {
switch action {
case windows.FILE_ACTION_ADDED:
return sysFSCREATE
@ -825,3 +672,11 @@ func (w *Watcher) toFSnotifyFlags(action uint32) uint64 {
}
return 0
}
func (w *readDirChangesW) xSupports(op Op) bool {
if op.Has(xUnportableOpen) || op.Has(xUnportableRead) ||
op.Has(xUnportableCloseWrite) || op.Has(xUnportableCloseRead) {
return false
}
return true
}

View file

@ -3,19 +3,146 @@
//
// Currently supported systems:
//
// Linux 2.6.32+ via inotify
// BSD, macOS via kqueue
// Windows via ReadDirectoryChangesW
// illumos via FEN
// - Linux via inotify
// - BSD, macOS via kqueue
// - Windows via ReadDirectoryChangesW
// - illumos via FEN
//
// # FSNOTIFY_DEBUG
//
// Set the FSNOTIFY_DEBUG environment variable to "1" to print debug messages to
// stderr. This can be useful to track down some problems, especially in cases
// where fsnotify is used as an indirect dependency.
//
// Every event will be printed as soon as there's something useful to print,
// with as little processing from fsnotify.
//
// Example output:
//
// FSNOTIFY_DEBUG: 11:34:23.633087586 256:IN_CREATE → "/tmp/file-1"
// FSNOTIFY_DEBUG: 11:34:23.633202319 4:IN_ATTRIB → "/tmp/file-1"
// FSNOTIFY_DEBUG: 11:34:28.989728764 512:IN_DELETE → "/tmp/file-1"
package fsnotify
import (
"errors"
"fmt"
"os"
"path/filepath"
"strings"
)
// Watcher watches a set of paths, delivering events on a channel.
//
// A watcher should not be copied (e.g. pass it by pointer, rather than by
// value).
//
// # Linux notes
//
// When a file is removed a Remove event won't be emitted until all file
// descriptors are closed, and deletes will always emit a Chmod. For example:
//
// fp := os.Open("file")
// os.Remove("file") // Triggers Chmod
// fp.Close() // Triggers Remove
//
// This is the event that inotify sends, so not much can be changed about this.
//
// The fs.inotify.max_user_watches sysctl variable specifies the upper limit
// for the number of watches per user, and fs.inotify.max_user_instances
// specifies the maximum number of inotify instances per user. Every Watcher you
// create is an "instance", and every path you add is a "watch".
//
// These are also exposed in /proc as /proc/sys/fs/inotify/max_user_watches and
// /proc/sys/fs/inotify/max_user_instances
//
// To increase them you can use sysctl or write the value to the /proc file:
//
// # Default values on Linux 5.18
// sysctl fs.inotify.max_user_watches=124983
// sysctl fs.inotify.max_user_instances=128
//
// To make the changes persist on reboot edit /etc/sysctl.conf or
// /usr/lib/sysctl.d/50-default.conf (details differ per Linux distro; check
// your distro's documentation):
//
// fs.inotify.max_user_watches=124983
// fs.inotify.max_user_instances=128
//
// Reaching the limit will result in a "no space left on device" or "too many open
// files" error.
//
// # kqueue notes (macOS, BSD)
//
// kqueue requires opening a file descriptor for every file that's being watched;
// so if you're watching a directory with five files then that's six file
// descriptors. You will run in to your system's "max open files" limit faster on
// these platforms.
//
// The sysctl variables kern.maxfiles and kern.maxfilesperproc can be used to
// control the maximum number of open files, as well as /etc/login.conf on BSD
// systems.
//
// # Windows notes
//
// Paths can be added as "C:\\path\\to\\dir", but forward slashes
// ("C:/path/to/dir") will also work.
//
// When a watched directory is removed it will always send an event for the
// directory itself, but may not send events for all files in that directory.
// Sometimes it will send events for all files, sometimes it will send no
// events, and often only for some files.
//
// The default ReadDirectoryChangesW() buffer size is 64K, which is the largest
// value that is guaranteed to work with SMB filesystems. If you have many
// events in quick succession this may not be enough, and you will have to use
// [WithBufferSize] to increase the value.
type Watcher struct {
b backend
// Events sends the filesystem change events.
//
// fsnotify can send the following events; a "path" here can refer to a
// file, directory, symbolic link, or special file like a FIFO.
//
// fsnotify.Create A new path was created; this may be followed by one
// or more Write events if data also gets written to a
// file.
//
// fsnotify.Remove A path was removed.
//
// fsnotify.Rename A path was renamed. A rename is always sent with the
// old path as Event.Name, and a Create event will be
// sent with the new name. Renames are only sent for
// paths that are currently watched; e.g. moving an
// unmonitored file into a monitored directory will
// show up as just a Create. Similarly, renaming a file
// to outside a monitored directory will show up as
// only a Rename.
//
// fsnotify.Write A file or named pipe was written to. A Truncate will
// also trigger a Write. A single "write action"
// initiated by the user may show up as one or multiple
// writes, depending on when the system syncs things to
// disk. For example when compiling a large Go program
// you may get hundreds of Write events, and you may
// want to wait until you've stopped receiving them
// (see the dedup example in cmd/fsnotify).
//
// Some systems may send Write event for directories
// when the directory content changes.
//
// fsnotify.Chmod Attributes were changed. On Linux this is also sent
// when a file is removed (or more accurately, when a
// link to an inode is removed). On kqueue it's sent
// when a file is truncated. On Windows it's never
// sent.
Events chan Event
// Errors sends any errors.
Errors chan error
}
// Event represents a file system notification.
type Event struct {
// Path to the file or directory.
@ -30,6 +157,16 @@ type Event struct {
// This is a bitmask and some systems may send multiple operations at once.
// Use the Event.Has() method instead of comparing with ==.
Op Op
// Create events will have this set to the old path if it's a rename. This
// only works when both the source and destination are watched. It's not
// reliable when watching individual files, only directories.
//
// For example "mv /tmp/file /tmp/rename" will emit:
//
// Event{Op: Rename, Name: "/tmp/file"}
// Event{Op: Create, Name: "/tmp/rename", RenamedFrom: "/tmp/file"}
renamedFrom string
}
// Op describes a set of file operations.
@ -50,7 +187,7 @@ const (
// example "remove to trash" is often a rename).
Remove
// The path was renamed to something else; any watched on it will be
// The path was renamed to something else; any watches on it will be
// removed.
Rename
@ -60,15 +197,155 @@ const (
// get triggered very frequently by some software. For example, Spotlight
// indexing on macOS, anti-virus software, backup software, etc.
Chmod
// File descriptor was opened.
//
// Only works on Linux and FreeBSD.
xUnportableOpen
// File was read from.
//
// Only works on Linux and FreeBSD.
xUnportableRead
// File opened for writing was closed.
//
// Only works on Linux and FreeBSD.
//
// The advantage of using this over Write is that it's more reliable than
// waiting for Write events to stop. It's also faster (if you're not
// listening to Write events): copying a file of a few GB can easily
// generate tens of thousands of Write events in a short span of time.
xUnportableCloseWrite
// File opened for reading was closed.
//
// Only works on Linux and FreeBSD.
xUnportableCloseRead
)
// Common errors that can be reported.
var (
// ErrNonExistentWatch is used when Remove() is called on a path that's not
// added.
ErrNonExistentWatch = errors.New("fsnotify: can't remove non-existent watch")
ErrEventOverflow = errors.New("fsnotify: queue or buffer overflow")
ErrClosed = errors.New("fsnotify: watcher already closed")
// ErrClosed is used when trying to operate on a closed Watcher.
ErrClosed = errors.New("fsnotify: watcher already closed")
// ErrEventOverflow is reported from the Errors channel when there are too
// many events:
//
// - inotify: inotify returns IN_Q_OVERFLOW – because there are too
// many queued events (the fs.inotify.max_queued_events
// sysctl can be used to increase this).
// - windows: The buffer size is too small; WithBufferSize() can be used to increase it.
// - kqueue, fen: Not used.
ErrEventOverflow = errors.New("fsnotify: queue or buffer overflow")
// ErrUnsupported is returned by AddWith() when WithOps() specified an
// Unportable event that's not supported on this platform.
xErrUnsupported = errors.New("fsnotify: not supported with this backend")
)
// NewWatcher creates a new Watcher.
func NewWatcher() (*Watcher, error) {
ev, errs := make(chan Event), make(chan error)
b, err := newBackend(ev, errs)
if err != nil {
return nil, err
}
return &Watcher{b: b, Events: ev, Errors: errs}, nil
}
// NewBufferedWatcher creates a new Watcher with a buffered Watcher.Events
// channel.
//
// The main use case for this is situations with a very large number of events
// where the kernel buffer size can't be increased (e.g. due to lack of
// permissions). An unbuffered Watcher will perform better for almost all use
// cases, and whenever possible you will be better off increasing the kernel
// buffers instead of adding a large userspace buffer.
func NewBufferedWatcher(sz uint) (*Watcher, error) {
ev, errs := make(chan Event), make(chan error)
b, err := newBufferedBackend(sz, ev, errs)
if err != nil {
return nil, err
}
return &Watcher{b: b, Events: ev, Errors: errs}, nil
}
// Add starts monitoring the path for changes.
//
// A path can only be watched once; watching it more than once is a no-op and will
// not return an error. Paths that do not yet exist on the filesystem cannot be
// watched.
//
// A watch will be automatically removed if the watched path is deleted or
// renamed. The exception is the Windows backend, which doesn't remove the
// watcher on renames.
//
// Notifications on network filesystems (NFS, SMB, FUSE, etc.) or special
// filesystems (/proc, /sys, etc.) generally don't work.
//
// Returns [ErrClosed] if [Watcher.Close] was called.
//
// See [Watcher.AddWith] for a version that allows adding options.
//
// # Watching directories
//
// All files in a directory are monitored, including new files that are created
// after the watcher is started. Subdirectories are not watched (i.e. it's
// non-recursive).
//
// # Watching files
//
// Watching individual files (rather than directories) is generally not
// recommended as many programs (especially editors) update files atomically: it
// will write to a temporary file which is then moved to destination,
// overwriting the original (or some variant thereof). The watcher on the
// original file is now lost, as that no longer exists.
//
// The upshot of this is that a power failure or crash won't leave a
// half-written file.
//
// Watch the parent directory and use Event.Name to filter out files you're not
// interested in. There is an example of this in cmd/fsnotify/file.go.
func (w *Watcher) Add(path string) error { return w.b.Add(path) }
// AddWith is like [Watcher.Add], but allows adding options. When using Add()
// the defaults described below are used.
//
// Possible options are:
//
// - [WithBufferSize] sets the buffer size for the Windows backend; no-op on
// other platforms. The default is 64K (65536 bytes).
func (w *Watcher) AddWith(path string, opts ...addOpt) error { return w.b.AddWith(path, opts...) }
// Remove stops monitoring the path for changes.
//
// Directories are always removed non-recursively. For example, if you added
// /tmp/dir and /tmp/dir/subdir then you will need to remove both.
//
// Removing a path that has not yet been added returns [ErrNonExistentWatch].
//
// Returns nil if [Watcher.Close] was called.
func (w *Watcher) Remove(path string) error { return w.b.Remove(path) }
// Close removes all watches and closes the Events channel.
func (w *Watcher) Close() error { return w.b.Close() }
// WatchList returns all paths explicitly added with [Watcher.Add] (and are not
// yet removed).
//
// Returns nil if [Watcher.Close] was called.
func (w *Watcher) WatchList() []string { return w.b.WatchList() }
// Supports reports if all the listed operations are supported by this platform.
//
// Create, Write, Remove, Rename, and Chmod are always supported. It can only
// return false for an Op starting with Unportable.
func (w *Watcher) xSupports(op Op) bool { return w.b.xSupports(op) }
func (o Op) String() string {
var b strings.Builder
if o.Has(Create) {
@ -80,6 +357,18 @@ func (o Op) String() string {
if o.Has(Write) {
b.WriteString("|WRITE")
}
if o.Has(xUnportableOpen) {
b.WriteString("|OPEN")
}
if o.Has(xUnportableRead) {
b.WriteString("|READ")
}
if o.Has(xUnportableCloseWrite) {
b.WriteString("|CLOSE_WRITE")
}
if o.Has(xUnportableCloseRead) {
b.WriteString("|CLOSE_READ")
}
if o.Has(Rename) {
b.WriteString("|RENAME")
}
@ -100,24 +389,48 @@ func (e Event) Has(op Op) bool { return e.Op.Has(op) }
// String returns a string representation of the event with their path.
func (e Event) String() string {
if e.renamedFrom != "" {
return fmt.Sprintf("%-13s %q ← %q", e.Op.String(), e.Name, e.renamedFrom)
}
return fmt.Sprintf("%-13s %q", e.Op.String(), e.Name)
}
type (
backend interface {
Add(string) error
AddWith(string, ...addOpt) error
Remove(string) error
WatchList() []string
Close() error
xSupports(Op) bool
}
addOpt func(opt *withOpts)
withOpts struct {
bufsize int
bufsize int
op Op
noFollow bool
sendCreate bool
}
)
var debug = func() bool {
// Check for exactly "1" (rather than mere existence) so we can add
// options/flags in the future. I don't know if we ever want that, but it's
// nice to leave the option open.
return os.Getenv("FSNOTIFY_DEBUG") == "1"
}()
var defaultOpts = withOpts{
bufsize: 65536, // 64K
op: Create | Write | Remove | Rename | Chmod,
}
func getOptions(opts ...addOpt) withOpts {
with := defaultOpts
for _, o := range opts {
o(&with)
if o != nil {
o(&with)
}
}
return with
}
@ -136,9 +449,44 @@ func WithBufferSize(bytes int) addOpt {
return func(opt *withOpts) { opt.bufsize = bytes }
}
// WithOps sets which operations to listen for. The default is [Create],
// [Write], [Remove], [Rename], and [Chmod].
//
// Excluding operations you're not interested in can save quite a bit of CPU
// time; in some use cases there may be hundreds of thousands of useless Write
// or Chmod operations per second.
//
// This can also be used to add unportable operations not supported by all
// platforms; unportable operations all start with "Unportable":
// [UnportableOpen], [UnportableRead], [UnportableCloseWrite], and
// [UnportableCloseRead].
//
// AddWith returns an error when using an unportable operation that's not
// supported. Use [Watcher.Support] to check for support.
func withOps(op Op) addOpt {
return func(opt *withOpts) { opt.op = op }
}
// WithNoFollow disables following symlinks, so the symlinks themselves are
// watched.
func withNoFollow() addOpt {
return func(opt *withOpts) { opt.noFollow = true }
}
// "Internal" option for recursive watches on inotify.
func withCreate() addOpt {
return func(opt *withOpts) { opt.sendCreate = true }
}
var enableRecurse = false
// Check if this path is recursive (ends with "/..." or "\..."), and return the
// path with the /... stripped.
func recursivePath(path string) (string, bool) {
path = filepath.Clean(path)
if !enableRecurse { // Only enabled in tests for now.
return path, false
}
if filepath.Base(path) == "..." {
return filepath.Dir(path), true
}

39
vendor/github.com/fsnotify/fsnotify/internal/darwin.go generated vendored Normal file
View file

@ -0,0 +1,39 @@
//go:build darwin
package internal
import (
"syscall"
"golang.org/x/sys/unix"
)
var (
SyscallEACCES = syscall.EACCES
UnixEACCES = unix.EACCES
)
var maxfiles uint64
// Go 1.19 will do this automatically: https://go-review.googlesource.com/c/go/+/393354/
func SetRlimit() {
var l syscall.Rlimit
err := syscall.Getrlimit(syscall.RLIMIT_NOFILE, &l)
if err == nil && l.Cur != l.Max {
l.Cur = l.Max
syscall.Setrlimit(syscall.RLIMIT_NOFILE, &l)
}
maxfiles = l.Cur
if n, err := syscall.SysctlUint32("kern.maxfiles"); err == nil && uint64(n) < maxfiles {
maxfiles = uint64(n)
}
if n, err := syscall.SysctlUint32("kern.maxfilesperproc"); err == nil && uint64(n) < maxfiles {
maxfiles = uint64(n)
}
}
func Maxfiles() uint64 { return maxfiles }
func Mkfifo(path string, mode uint32) error { return unix.Mkfifo(path, mode) }
func Mknod(path string, mode uint32, dev int) error { return unix.Mknod(path, mode, dev) }

View file

@ -0,0 +1,57 @@
package internal
import "golang.org/x/sys/unix"
var names = []struct {
n string
m uint32
}{
{"NOTE_ABSOLUTE", unix.NOTE_ABSOLUTE},
{"NOTE_ATTRIB", unix.NOTE_ATTRIB},
{"NOTE_BACKGROUND", unix.NOTE_BACKGROUND},
{"NOTE_CHILD", unix.NOTE_CHILD},
{"NOTE_CRITICAL", unix.NOTE_CRITICAL},
{"NOTE_DELETE", unix.NOTE_DELETE},
{"NOTE_EXEC", unix.NOTE_EXEC},
{"NOTE_EXIT", unix.NOTE_EXIT},
{"NOTE_EXITSTATUS", unix.NOTE_EXITSTATUS},
{"NOTE_EXIT_CSERROR", unix.NOTE_EXIT_CSERROR},
{"NOTE_EXIT_DECRYPTFAIL", unix.NOTE_EXIT_DECRYPTFAIL},
{"NOTE_EXIT_DETAIL", unix.NOTE_EXIT_DETAIL},
{"NOTE_EXIT_DETAIL_MASK", unix.NOTE_EXIT_DETAIL_MASK},
{"NOTE_EXIT_MEMORY", unix.NOTE_EXIT_MEMORY},
{"NOTE_EXIT_REPARENTED", unix.NOTE_EXIT_REPARENTED},
{"NOTE_EXTEND", unix.NOTE_EXTEND},
{"NOTE_FFAND", unix.NOTE_FFAND},
{"NOTE_FFCOPY", unix.NOTE_FFCOPY},
{"NOTE_FFCTRLMASK", unix.NOTE_FFCTRLMASK},
{"NOTE_FFLAGSMASK", unix.NOTE_FFLAGSMASK},
{"NOTE_FFNOP", unix.NOTE_FFNOP},
{"NOTE_FFOR", unix.NOTE_FFOR},
{"NOTE_FORK", unix.NOTE_FORK},
{"NOTE_FUNLOCK", unix.NOTE_FUNLOCK},
{"NOTE_LEEWAY", unix.NOTE_LEEWAY},
{"NOTE_LINK", unix.NOTE_LINK},
{"NOTE_LOWAT", unix.NOTE_LOWAT},
{"NOTE_MACHTIME", unix.NOTE_MACHTIME},
{"NOTE_MACH_CONTINUOUS_TIME", unix.NOTE_MACH_CONTINUOUS_TIME},
{"NOTE_NONE", unix.NOTE_NONE},
{"NOTE_NSECONDS", unix.NOTE_NSECONDS},
{"NOTE_OOB", unix.NOTE_OOB},
//{"NOTE_PCTRLMASK", unix.NOTE_PCTRLMASK}, -0x100000 (?!)
{"NOTE_PDATAMASK", unix.NOTE_PDATAMASK},
{"NOTE_REAP", unix.NOTE_REAP},
{"NOTE_RENAME", unix.NOTE_RENAME},
{"NOTE_REVOKE", unix.NOTE_REVOKE},
{"NOTE_SECONDS", unix.NOTE_SECONDS},
{"NOTE_SIGNAL", unix.NOTE_SIGNAL},
{"NOTE_TRACK", unix.NOTE_TRACK},
{"NOTE_TRACKERR", unix.NOTE_TRACKERR},
{"NOTE_TRIGGER", unix.NOTE_TRIGGER},
{"NOTE_USECONDS", unix.NOTE_USECONDS},
{"NOTE_VM_ERROR", unix.NOTE_VM_ERROR},
{"NOTE_VM_PRESSURE", unix.NOTE_VM_PRESSURE},
{"NOTE_VM_PRESSURE_SUDDEN_TERMINATE", unix.NOTE_VM_PRESSURE_SUDDEN_TERMINATE},
{"NOTE_VM_PRESSURE_TERMINATE", unix.NOTE_VM_PRESSURE_TERMINATE},
{"NOTE_WRITE", unix.NOTE_WRITE},
}

View file

@ -0,0 +1,33 @@
package internal
import "golang.org/x/sys/unix"
var names = []struct {
n string
m uint32
}{
{"NOTE_ATTRIB", unix.NOTE_ATTRIB},
{"NOTE_CHILD", unix.NOTE_CHILD},
{"NOTE_DELETE", unix.NOTE_DELETE},
{"NOTE_EXEC", unix.NOTE_EXEC},
{"NOTE_EXIT", unix.NOTE_EXIT},
{"NOTE_EXTEND", unix.NOTE_EXTEND},
{"NOTE_FFAND", unix.NOTE_FFAND},
{"NOTE_FFCOPY", unix.NOTE_FFCOPY},
{"NOTE_FFCTRLMASK", unix.NOTE_FFCTRLMASK},
{"NOTE_FFLAGSMASK", unix.NOTE_FFLAGSMASK},
{"NOTE_FFNOP", unix.NOTE_FFNOP},
{"NOTE_FFOR", unix.NOTE_FFOR},
{"NOTE_FORK", unix.NOTE_FORK},
{"NOTE_LINK", unix.NOTE_LINK},
{"NOTE_LOWAT", unix.NOTE_LOWAT},
{"NOTE_OOB", unix.NOTE_OOB},
{"NOTE_PCTRLMASK", unix.NOTE_PCTRLMASK},
{"NOTE_PDATAMASK", unix.NOTE_PDATAMASK},
{"NOTE_RENAME", unix.NOTE_RENAME},
{"NOTE_REVOKE", unix.NOTE_REVOKE},
{"NOTE_TRACK", unix.NOTE_TRACK},
{"NOTE_TRACKERR", unix.NOTE_TRACKERR},
{"NOTE_TRIGGER", unix.NOTE_TRIGGER},
{"NOTE_WRITE", unix.NOTE_WRITE},
}

View file

@ -0,0 +1,42 @@
package internal
import "golang.org/x/sys/unix"
var names = []struct {
n string
m uint32
}{
{"NOTE_ABSTIME", unix.NOTE_ABSTIME},
{"NOTE_ATTRIB", unix.NOTE_ATTRIB},
{"NOTE_CHILD", unix.NOTE_CHILD},
{"NOTE_CLOSE", unix.NOTE_CLOSE},
{"NOTE_CLOSE_WRITE", unix.NOTE_CLOSE_WRITE},
{"NOTE_DELETE", unix.NOTE_DELETE},
{"NOTE_EXEC", unix.NOTE_EXEC},
{"NOTE_EXIT", unix.NOTE_EXIT},
{"NOTE_EXTEND", unix.NOTE_EXTEND},
{"NOTE_FFAND", unix.NOTE_FFAND},
{"NOTE_FFCOPY", unix.NOTE_FFCOPY},
{"NOTE_FFCTRLMASK", unix.NOTE_FFCTRLMASK},
{"NOTE_FFLAGSMASK", unix.NOTE_FFLAGSMASK},
{"NOTE_FFNOP", unix.NOTE_FFNOP},
{"NOTE_FFOR", unix.NOTE_FFOR},
{"NOTE_FILE_POLL", unix.NOTE_FILE_POLL},
{"NOTE_FORK", unix.NOTE_FORK},
{"NOTE_LINK", unix.NOTE_LINK},
{"NOTE_LOWAT", unix.NOTE_LOWAT},
{"NOTE_MSECONDS", unix.NOTE_MSECONDS},
{"NOTE_NSECONDS", unix.NOTE_NSECONDS},
{"NOTE_OPEN", unix.NOTE_OPEN},
{"NOTE_PCTRLMASK", unix.NOTE_PCTRLMASK},
{"NOTE_PDATAMASK", unix.NOTE_PDATAMASK},
{"NOTE_READ", unix.NOTE_READ},
{"NOTE_RENAME", unix.NOTE_RENAME},
{"NOTE_REVOKE", unix.NOTE_REVOKE},
{"NOTE_SECONDS", unix.NOTE_SECONDS},
{"NOTE_TRACK", unix.NOTE_TRACK},
{"NOTE_TRACKERR", unix.NOTE_TRACKERR},
{"NOTE_TRIGGER", unix.NOTE_TRIGGER},
{"NOTE_USECONDS", unix.NOTE_USECONDS},
{"NOTE_WRITE", unix.NOTE_WRITE},
}

View file

@ -0,0 +1,32 @@
//go:build freebsd || openbsd || netbsd || dragonfly || darwin
package internal
import (
"fmt"
"os"
"strings"
"time"
"golang.org/x/sys/unix"
)
func Debug(name string, kevent *unix.Kevent_t) {
mask := uint32(kevent.Fflags)
var (
l []string
unknown = mask
)
for _, n := range names {
if mask&n.m == n.m {
l = append(l, n.n)
unknown ^= n.m
}
}
if unknown > 0 {
l = append(l, fmt.Sprintf("0x%x", unknown))
}
fmt.Fprintf(os.Stderr, "FSNOTIFY_DEBUG: %s %10d:%-60s → %q\n",
time.Now().Format("15:04:05.000000000"), mask, strings.Join(l, " | "), name)
}

View file

@ -0,0 +1,56 @@
package internal
import (
"fmt"
"os"
"strings"
"time"
"golang.org/x/sys/unix"
)
func Debug(name string, mask, cookie uint32) {
names := []struct {
n string
m uint32
}{
{"IN_ACCESS", unix.IN_ACCESS},
{"IN_ATTRIB", unix.IN_ATTRIB},
{"IN_CLOSE", unix.IN_CLOSE},
{"IN_CLOSE_NOWRITE", unix.IN_CLOSE_NOWRITE},
{"IN_CLOSE_WRITE", unix.IN_CLOSE_WRITE},
{"IN_CREATE", unix.IN_CREATE},
{"IN_DELETE", unix.IN_DELETE},
{"IN_DELETE_SELF", unix.IN_DELETE_SELF},
{"IN_IGNORED", unix.IN_IGNORED},
{"IN_ISDIR", unix.IN_ISDIR},
{"IN_MODIFY", unix.IN_MODIFY},
{"IN_MOVE", unix.IN_MOVE},
{"IN_MOVED_FROM", unix.IN_MOVED_FROM},
{"IN_MOVED_TO", unix.IN_MOVED_TO},
{"IN_MOVE_SELF", unix.IN_MOVE_SELF},
{"IN_OPEN", unix.IN_OPEN},
{"IN_Q_OVERFLOW", unix.IN_Q_OVERFLOW},
{"IN_UNMOUNT", unix.IN_UNMOUNT},
}
var (
l []string
unknown = mask
)
for _, n := range names {
if mask&n.m == n.m {
l = append(l, n.n)
unknown ^= n.m
}
}
if unknown > 0 {
l = append(l, fmt.Sprintf("0x%x", unknown))
}
var c string
if cookie > 0 {
c = fmt.Sprintf("(cookie: %d) ", cookie)
}
fmt.Fprintf(os.Stderr, "FSNOTIFY_DEBUG: %s %-30s → %s%q\n",
time.Now().Format("15:04:05.000000000"), strings.Join(l, "|"), c, name)
}

View file

@ -0,0 +1,25 @@
package internal
import "golang.org/x/sys/unix"
var names = []struct {
n string
m uint32
}{
{"NOTE_ATTRIB", unix.NOTE_ATTRIB},
{"NOTE_CHILD", unix.NOTE_CHILD},
{"NOTE_DELETE", unix.NOTE_DELETE},
{"NOTE_EXEC", unix.NOTE_EXEC},
{"NOTE_EXIT", unix.NOTE_EXIT},
{"NOTE_EXTEND", unix.NOTE_EXTEND},
{"NOTE_FORK", unix.NOTE_FORK},
{"NOTE_LINK", unix.NOTE_LINK},
{"NOTE_LOWAT", unix.NOTE_LOWAT},
{"NOTE_PCTRLMASK", unix.NOTE_PCTRLMASK},
{"NOTE_PDATAMASK", unix.NOTE_PDATAMASK},
{"NOTE_RENAME", unix.NOTE_RENAME},
{"NOTE_REVOKE", unix.NOTE_REVOKE},
{"NOTE_TRACK", unix.NOTE_TRACK},
{"NOTE_TRACKERR", unix.NOTE_TRACKERR},
{"NOTE_WRITE", unix.NOTE_WRITE},
}

View file

@ -0,0 +1,28 @@
package internal
import "golang.org/x/sys/unix"
var names = []struct {
n string
m uint32
}{
{"NOTE_ATTRIB", unix.NOTE_ATTRIB},
// {"NOTE_CHANGE", unix.NOTE_CHANGE}, // Not on 386?
{"NOTE_CHILD", unix.NOTE_CHILD},
{"NOTE_DELETE", unix.NOTE_DELETE},
{"NOTE_EOF", unix.NOTE_EOF},
{"NOTE_EXEC", unix.NOTE_EXEC},
{"NOTE_EXIT", unix.NOTE_EXIT},
{"NOTE_EXTEND", unix.NOTE_EXTEND},
{"NOTE_FORK", unix.NOTE_FORK},
{"NOTE_LINK", unix.NOTE_LINK},
{"NOTE_LOWAT", unix.NOTE_LOWAT},
{"NOTE_PCTRLMASK", unix.NOTE_PCTRLMASK},
{"NOTE_PDATAMASK", unix.NOTE_PDATAMASK},
{"NOTE_RENAME", unix.NOTE_RENAME},
{"NOTE_REVOKE", unix.NOTE_REVOKE},
{"NOTE_TRACK", unix.NOTE_TRACK},
{"NOTE_TRACKERR", unix.NOTE_TRACKERR},
{"NOTE_TRUNCATE", unix.NOTE_TRUNCATE},
{"NOTE_WRITE", unix.NOTE_WRITE},
}

View file

@ -0,0 +1,45 @@
package internal
import (
"fmt"
"os"
"strings"
"time"
"golang.org/x/sys/unix"
)
func Debug(name string, mask int32) {
names := []struct {
n string
m int32
}{
{"FILE_ACCESS", unix.FILE_ACCESS},
{"FILE_MODIFIED", unix.FILE_MODIFIED},
{"FILE_ATTRIB", unix.FILE_ATTRIB},
{"FILE_TRUNC", unix.FILE_TRUNC},
{"FILE_NOFOLLOW", unix.FILE_NOFOLLOW},
{"FILE_DELETE", unix.FILE_DELETE},
{"FILE_RENAME_TO", unix.FILE_RENAME_TO},
{"FILE_RENAME_FROM", unix.FILE_RENAME_FROM},
{"UNMOUNTED", unix.UNMOUNTED},
{"MOUNTEDOVER", unix.MOUNTEDOVER},
{"FILE_EXCEPTION", unix.FILE_EXCEPTION},
}
var (
l []string
unknown = mask
)
for _, n := range names {
if mask&n.m == n.m {
l = append(l, n.n)
unknown ^= n.m
}
}
if unknown > 0 {
l = append(l, fmt.Sprintf("0x%x", unknown))
}
fmt.Fprintf(os.Stderr, "FSNOTIFY_DEBUG: %s %10d:%-30s → %q\n",
time.Now().Format("15:04:05.000000000"), mask, strings.Join(l, " | "), name)
}

View file

@ -0,0 +1,40 @@
package internal
import (
"fmt"
"os"
"path/filepath"
"strings"
"time"
"golang.org/x/sys/windows"
)
func Debug(name string, mask uint32) {
names := []struct {
n string
m uint32
}{
{"FILE_ACTION_ADDED", windows.FILE_ACTION_ADDED},
{"FILE_ACTION_REMOVED", windows.FILE_ACTION_REMOVED},
{"FILE_ACTION_MODIFIED", windows.FILE_ACTION_MODIFIED},
{"FILE_ACTION_RENAMED_OLD_NAME", windows.FILE_ACTION_RENAMED_OLD_NAME},
{"FILE_ACTION_RENAMED_NEW_NAME", windows.FILE_ACTION_RENAMED_NEW_NAME},
}
var (
l []string
unknown = mask
)
for _, n := range names {
if mask&n.m == n.m {
l = append(l, n.n)
unknown ^= n.m
}
}
if unknown > 0 {
l = append(l, fmt.Sprintf("0x%x", unknown))
}
fmt.Fprintf(os.Stderr, "FSNOTIFY_DEBUG: %s %-65s → %q\n",
time.Now().Format("15:04:05.000000000"), strings.Join(l, " | "), filepath.ToSlash(name))
}

View file

@ -0,0 +1,31 @@
//go:build freebsd
package internal
import (
"syscall"
"golang.org/x/sys/unix"
)
var (
SyscallEACCES = syscall.EACCES
UnixEACCES = unix.EACCES
)
var maxfiles uint64
func SetRlimit() {
// Go 1.19 will do this automatically: https://go-review.googlesource.com/c/go/+/393354/
var l syscall.Rlimit
err := syscall.Getrlimit(syscall.RLIMIT_NOFILE, &l)
if err == nil && l.Cur != l.Max {
l.Cur = l.Max
syscall.Setrlimit(syscall.RLIMIT_NOFILE, &l)
}
maxfiles = uint64(l.Cur)
}
func Maxfiles() uint64 { return maxfiles }
func Mkfifo(path string, mode uint32) error { return unix.Mkfifo(path, mode) }
func Mknod(path string, mode uint32, dev int) error { return unix.Mknod(path, mode, uint64(dev)) }

View file

@ -0,0 +1,2 @@
// Package internal contains some helpers.
package internal

31
vendor/github.com/fsnotify/fsnotify/internal/unix.go generated vendored Normal file
View file

@ -0,0 +1,31 @@
//go:build !windows && !darwin && !freebsd
package internal
import (
"syscall"
"golang.org/x/sys/unix"
)
var (
SyscallEACCES = syscall.EACCES
UnixEACCES = unix.EACCES
)
var maxfiles uint64
func SetRlimit() {
// Go 1.19 will do this automatically: https://go-review.googlesource.com/c/go/+/393354/
var l syscall.Rlimit
err := syscall.Getrlimit(syscall.RLIMIT_NOFILE, &l)
if err == nil && l.Cur != l.Max {
l.Cur = l.Max
syscall.Setrlimit(syscall.RLIMIT_NOFILE, &l)
}
maxfiles = uint64(l.Cur)
}
func Maxfiles() uint64 { return maxfiles }
func Mkfifo(path string, mode uint32) error { return unix.Mkfifo(path, mode) }
func Mknod(path string, mode uint32, dev int) error { return unix.Mknod(path, mode, dev) }

View file

@ -0,0 +1,7 @@
//go:build !windows
package internal
func HasPrivilegesForSymlink() bool {
return true
}

View file

@ -0,0 +1,41 @@
//go:build windows
package internal
import (
"errors"
"golang.org/x/sys/windows"
)
// Just a dummy.
var (
SyscallEACCES = errors.New("dummy")
UnixEACCES = errors.New("dummy")
)
func SetRlimit() {}
func Maxfiles() uint64 { return 1<<64 - 1 }
func Mkfifo(path string, mode uint32) error { return errors.New("no FIFOs on Windows") }
func Mknod(path string, mode uint32, dev int) error { return errors.New("no device nodes on Windows") }
func HasPrivilegesForSymlink() bool {
var sid *windows.SID
err := windows.AllocateAndInitializeSid(
&windows.SECURITY_NT_AUTHORITY,
2,
windows.SECURITY_BUILTIN_DOMAIN_RID,
windows.DOMAIN_ALIAS_RID_ADMINS,
0, 0, 0, 0, 0, 0,
&sid)
if err != nil {
return false
}
defer windows.FreeSid(sid)
token := windows.Token(0)
member, err := token.IsMember(sid)
if err != nil {
return false
}
return member || token.IsElevated()
}

View file

@ -1,259 +0,0 @@
#!/usr/bin/env zsh
[ "${ZSH_VERSION:-}" = "" ] && echo >&2 "Only works with zsh" && exit 1
setopt err_exit no_unset pipefail extended_glob
# Simple script to update the godoc comments on all watchers so you don't need
# to update the same comment 5 times.
watcher=$(<<EOF
// Watcher watches a set of paths, delivering events on a channel.
//
// A watcher should not be copied (e.g. pass it by pointer, rather than by
// value).
//
// # Linux notes
//
// When a file is removed a Remove event won't be emitted until all file
// descriptors are closed, and deletes will always emit a Chmod. For example:
//
// fp := os.Open("file")
// os.Remove("file") // Triggers Chmod
// fp.Close() // Triggers Remove
//
// This is the event that inotify sends, so not much can be changed about this.
//
// The fs.inotify.max_user_watches sysctl variable specifies the upper limit
// for the number of watches per user, and fs.inotify.max_user_instances
// specifies the maximum number of inotify instances per user. Every Watcher you
// create is an "instance", and every path you add is a "watch".
//
// These are also exposed in /proc as /proc/sys/fs/inotify/max_user_watches and
// /proc/sys/fs/inotify/max_user_instances
//
// To increase them you can use sysctl or write the value to the /proc file:
//
// # Default values on Linux 5.18
// sysctl fs.inotify.max_user_watches=124983
// sysctl fs.inotify.max_user_instances=128
//
// To make the changes persist on reboot edit /etc/sysctl.conf or
// /usr/lib/sysctl.d/50-default.conf (details differ per Linux distro; check
// your distro's documentation):
//
// fs.inotify.max_user_watches=124983
// fs.inotify.max_user_instances=128
//
// Reaching the limit will result in a "no space left on device" or "too many open
// files" error.
//
// # kqueue notes (macOS, BSD)
//
// kqueue requires opening a file descriptor for every file that's being watched;
// so if you're watching a directory with five files then that's six file
// descriptors. You will run in to your system's "max open files" limit faster on
// these platforms.
//
// The sysctl variables kern.maxfiles and kern.maxfilesperproc can be used to
// control the maximum number of open files, as well as /etc/login.conf on BSD
// systems.
//
// # Windows notes
//
// Paths can be added as "C:\\path\\to\\dir", but forward slashes
// ("C:/path/to/dir") will also work.
//
// When a watched directory is removed it will always send an event for the
// directory itself, but may not send events for all files in that directory.
// Sometimes it will send events for all times, sometimes it will send no
// events, and often only for some files.
//
// The default ReadDirectoryChangesW() buffer size is 64K, which is the largest
// value that is guaranteed to work with SMB filesystems. If you have many
// events in quick succession this may not be enough, and you will have to use
// [WithBufferSize] to increase the value.
EOF
)
new=$(<<EOF
// NewWatcher creates a new Watcher.
EOF
)
newbuffered=$(<<EOF
// NewBufferedWatcher creates a new Watcher with a buffered Watcher.Events
// channel.
//
// The main use case for this is situations with a very large number of events
// where the kernel buffer size can't be increased (e.g. due to lack of
// permissions). An unbuffered Watcher will perform better for almost all use
// cases, and whenever possible you will be better off increasing the kernel
// buffers instead of adding a large userspace buffer.
EOF
)
add=$(<<EOF
// Add starts monitoring the path for changes.
//
// A path can only be watched once; watching it more than once is a no-op and will
// not return an error. Paths that do not yet exist on the filesystem cannot be
// watched.
//
// A watch will be automatically removed if the watched path is deleted or
// renamed. The exception is the Windows backend, which doesn't remove the
// watcher on renames.
//
// Notifications on network filesystems (NFS, SMB, FUSE, etc.) or special
// filesystems (/proc, /sys, etc.) generally don't work.
//
// Returns [ErrClosed] if [Watcher.Close] was called.
//
// See [Watcher.AddWith] for a version that allows adding options.
//
// # Watching directories
//
// All files in a directory are monitored, including new files that are created
// after the watcher is started. Subdirectories are not watched (i.e. it's
// non-recursive).
//
// # Watching files
//
// Watching individual files (rather than directories) is generally not
// recommended as many programs (especially editors) update files atomically: it
// will write to a temporary file which is then moved to to destination,
// overwriting the original (or some variant thereof). The watcher on the
// original file is now lost, as that no longer exists.
//
// The upshot of this is that a power failure or crash won't leave a
// half-written file.
//
// Watch the parent directory and use Event.Name to filter out files you're not
// interested in. There is an example of this in cmd/fsnotify/file.go.
EOF
)
addwith=$(<<EOF
// AddWith is like [Watcher.Add], but allows adding options. When using Add()
// the defaults described below are used.
//
// Possible options are:
//
// - [WithBufferSize] sets the buffer size for the Windows backend; no-op on
// other platforms. The default is 64K (65536 bytes).
EOF
)
remove=$(<<EOF
// Remove stops monitoring the path for changes.
//
// Directories are always removed non-recursively. For example, if you added
// /tmp/dir and /tmp/dir/subdir then you will need to remove both.
//
// Removing a path that has not yet been added returns [ErrNonExistentWatch].
//
// Returns nil if [Watcher.Close] was called.
EOF
)
close=$(<<EOF
// Close removes all watches and closes the Events channel.
EOF
)
watchlist=$(<<EOF
// WatchList returns all paths explicitly added with [Watcher.Add] (and are not
// yet removed).
//
// Returns nil if [Watcher.Close] was called.
EOF
)
events=$(<<EOF
// Events sends the filesystem change events.
//
// fsnotify can send the following events; a "path" here can refer to a
// file, directory, symbolic link, or special file like a FIFO.
//
// fsnotify.Create A new path was created; this may be followed by one
// or more Write events if data also gets written to a
// file.
//
// fsnotify.Remove A path was removed.
//
// fsnotify.Rename A path was renamed. A rename is always sent with the
// old path as Event.Name, and a Create event will be
// sent with the new name. Renames are only sent for
// paths that are currently watched; e.g. moving an
// unmonitored file into a monitored directory will
// show up as just a Create. Similarly, renaming a file
// to outside a monitored directory will show up as
// only a Rename.
//
// fsnotify.Write A file or named pipe was written to. A Truncate will
// also trigger a Write. A single "write action"
// initiated by the user may show up as one or multiple
// writes, depending on when the system syncs things to
// disk. For example when compiling a large Go program
// you may get hundreds of Write events, and you may
// want to wait until you've stopped receiving them
// (see the dedup example in cmd/fsnotify).
//
// Some systems may send Write event for directories
// when the directory content changes.
//
// fsnotify.Chmod Attributes were changed. On Linux this is also sent
// when a file is removed (or more accurately, when a
// link to an inode is removed). On kqueue it's sent
// when a file is truncated. On Windows it's never
// sent.
EOF
)
errors=$(<<EOF
// Errors sends any errors.
//
// ErrEventOverflow is used to indicate there are too many events:
//
// - inotify: There are too many queued events (fs.inotify.max_queued_events sysctl)
// - windows: The buffer size is too small; WithBufferSize() can be used to increase it.
// - kqueue, fen: Not used.
EOF
)
set-cmt() {
local pat=$1
local cmt=$2
IFS=$'\n' local files=($(grep -n $pat backend_*~*_test.go))
for f in $files; do
IFS=':' local fields=($=f)
local file=$fields[1]
local end=$(( $fields[2] - 1 ))
# Find start of comment.
local start=0
IFS=$'\n' local lines=($(head -n$end $file))
for (( i = 1; i <= $#lines; i++ )); do
local line=$lines[-$i]
if ! grep -q '^[[:space:]]*//' <<<$line; then
start=$(( end - (i - 2) ))
break
fi
done
head -n $(( start - 1 )) $file >/tmp/x
print -r -- $cmt >>/tmp/x
tail -n+$(( end + 1 )) $file >>/tmp/x
mv /tmp/x $file
done
}
set-cmt '^type Watcher struct ' $watcher
set-cmt '^func NewWatcher(' $new
set-cmt '^func NewBufferedWatcher(' $newbuffered
set-cmt '^func (w \*Watcher) Add(' $add
set-cmt '^func (w \*Watcher) AddWith(' $addwith
set-cmt '^func (w \*Watcher) Remove(' $remove
set-cmt '^func (w \*Watcher) Close(' $close
set-cmt '^func (w \*Watcher) WatchList(' $watchlist
set-cmt '^[[:space:]]*Events *chan Event$' $events
set-cmt '^[[:space:]]*Errors *chan error$' $errors

View file

@ -1,5 +1,4 @@
//go:build freebsd || openbsd || netbsd || dragonfly
// +build freebsd openbsd netbsd dragonfly
package fsnotify

View file

@ -1,5 +1,4 @@
//go:build darwin
// +build darwin
package fsnotify

View file

@ -34,12 +34,24 @@ func withLocation(err error, start, end int) error {
// WithLocation extends an error with a source code location
func WithLocation(err error, location []Range) error {
return setLocation(err, location, true)
}
func SetLocation(err error, location []Range) error {
return setLocation(err, location, false)
}
func setLocation(err error, location []Range, add bool) error {
if err == nil {
return nil
}
var el *ErrorLocation
if errors.As(err, &el) {
el.Locations = append(el.Locations, location)
if add {
el.Locations = append(el.Locations, location)
} else {
el.Locations = [][]Range{location}
}
return err
}
return stack.Enable(&ErrorLocation{

View file

@ -167,16 +167,17 @@ func (d *directives) setEscapeToken(s string) error {
// possibleParserDirective looks for parser directives, eg '# escapeToken=<char>'.
// Parser directives must precede any builder instruction or other comments,
// and cannot be repeated.
func (d *directives) possibleParserDirective(line []byte) error {
// and cannot be repeated. Returns true if a parser directive was found.
func (d *directives) possibleParserDirective(line []byte) (bool, error) {
directive, err := d.parser.ParseLine(line)
if err != nil {
return err
return false, err
}
if directive != nil && directive.Name == keyEscape {
return d.setEscapeToken(directive.Value)
err := d.setEscapeToken(directive.Value)
return err == nil, err
}
return nil
return directive != nil, nil
}
// newDefaultDirectives returns a new directives structure with the default escapeToken token
@ -300,7 +301,13 @@ func Parse(rwc io.Reader) (*Result, error) {
comments = append(comments, comment)
}
}
bytesRead, err = processLine(d, bytesRead, true)
var directiveOk bool
bytesRead, directiveOk, err = processLine(d, bytesRead, true)
// If the line is a directive, strip it from the comments
// so it doesn't get added to the AST.
if directiveOk {
comments = comments[:len(comments)-1]
}
if err != nil {
return nil, withLocation(err, currentLine, 0)
}
@ -316,7 +323,7 @@ func Parse(rwc io.Reader) (*Result, error) {
var hasEmptyContinuationLine bool
for !isEndOfLine && scanner.Scan() {
bytesRead, err := processLine(d, scanner.Bytes(), false)
bytesRead, _, err := processLine(d, scanner.Bytes(), false)
if err != nil {
return nil, withLocation(err, currentLine, 0)
}
@ -527,12 +534,13 @@ func trimContinuationCharacter(line []byte, d *directives) ([]byte, bool) {
// TODO: remove stripLeftWhitespace after deprecation period. It seems silly
// to preserve whitespace on continuation lines. Why is that done?
func processLine(d *directives, token []byte, stripLeftWhitespace bool) ([]byte, error) {
func processLine(d *directives, token []byte, stripLeftWhitespace bool) ([]byte, bool, error) {
token = trimNewline(token)
if stripLeftWhitespace {
token = trimLeadingWhitespace(token)
}
return trimComments(token), d.possibleParserDirective(token)
directiveOk, err := d.possibleParserDirective(token)
return trimComments(token), directiveOk, err
}
// Variation of bufio.ScanLines that preserves the line endings

View file

@ -1,3 +0,0 @@
package stack
//go:generate protoc -I=. -I=../../vendor/ --go_out=. --go_opt=paths=source_relative --go_opt=Mstack.proto=/util/stack stack.proto

View file

@ -1,8 +1,8 @@
// Code generated by protoc-gen-go. DO NOT EDIT.
// versions:
// protoc-gen-go v1.33.0
// protoc-gen-go v1.35.1
// protoc v3.11.4
// source: stack.proto
// source: github.com/moby/buildkit/util/stack/stack.proto
package stack
@ -34,11 +34,9 @@ type Stack struct {
func (x *Stack) Reset() {
*x = Stack{}
if protoimpl.UnsafeEnabled {
mi := &file_stack_proto_msgTypes[0]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
mi := &file_github_com_moby_buildkit_util_stack_stack_proto_msgTypes[0]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *Stack) String() string {
@ -48,8 +46,8 @@ func (x *Stack) String() string {
func (*Stack) ProtoMessage() {}
func (x *Stack) ProtoReflect() protoreflect.Message {
mi := &file_stack_proto_msgTypes[0]
if protoimpl.UnsafeEnabled && x != nil {
mi := &file_github_com_moby_buildkit_util_stack_stack_proto_msgTypes[0]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
@ -61,7 +59,7 @@ func (x *Stack) ProtoReflect() protoreflect.Message {
// Deprecated: Use Stack.ProtoReflect.Descriptor instead.
func (*Stack) Descriptor() ([]byte, []int) {
return file_stack_proto_rawDescGZIP(), []int{0}
return file_github_com_moby_buildkit_util_stack_stack_proto_rawDescGZIP(), []int{0}
}
func (x *Stack) GetFrames() []*Frame {
@ -111,11 +109,9 @@ type Frame struct {
func (x *Frame) Reset() {
*x = Frame{}
if protoimpl.UnsafeEnabled {
mi := &file_stack_proto_msgTypes[1]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
mi := &file_github_com_moby_buildkit_util_stack_stack_proto_msgTypes[1]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *Frame) String() string {
@ -125,8 +121,8 @@ func (x *Frame) String() string {
func (*Frame) ProtoMessage() {}
func (x *Frame) ProtoReflect() protoreflect.Message {
mi := &file_stack_proto_msgTypes[1]
if protoimpl.UnsafeEnabled && x != nil {
mi := &file_github_com_moby_buildkit_util_stack_stack_proto_msgTypes[1]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
@ -138,7 +134,7 @@ func (x *Frame) ProtoReflect() protoreflect.Message {
// Deprecated: Use Frame.ProtoReflect.Descriptor instead.
func (*Frame) Descriptor() ([]byte, []int) {
return file_stack_proto_rawDescGZIP(), []int{1}
return file_github_com_moby_buildkit_util_stack_stack_proto_rawDescGZIP(), []int{1}
}
func (x *Frame) GetName() string {
@ -162,45 +158,49 @@ func (x *Frame) GetLine() int32 {
return 0
}
var File_stack_proto protoreflect.FileDescriptor
var File_github_com_moby_buildkit_util_stack_stack_proto protoreflect.FileDescriptor
var file_stack_proto_rawDesc = []byte{
0x0a, 0x0b, 0x73, 0x74, 0x61, 0x63, 0x6b, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x05, 0x73,
0x74, 0x61, 0x63, 0x6b, 0x22, 0x8f, 0x01, 0x0a, 0x05, 0x53, 0x74, 0x61, 0x63, 0x6b, 0x12, 0x24,
0x0a, 0x06, 0x66, 0x72, 0x61, 0x6d, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x0c,
0x2e, 0x73, 0x74, 0x61, 0x63, 0x6b, 0x2e, 0x46, 0x72, 0x61, 0x6d, 0x65, 0x52, 0x06, 0x66, 0x72,
0x61, 0x6d, 0x65, 0x73, 0x12, 0x18, 0x0a, 0x07, 0x63, 0x6d, 0x64, 0x6c, 0x69, 0x6e, 0x65, 0x18,
0x02, 0x20, 0x03, 0x28, 0x09, 0x52, 0x07, 0x63, 0x6d, 0x64, 0x6c, 0x69, 0x6e, 0x65, 0x12, 0x10,
0x0a, 0x03, 0x70, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x05, 0x52, 0x03, 0x70, 0x69, 0x64,
0x12, 0x18, 0x0a, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x04, 0x20, 0x01, 0x28,
0x09, 0x52, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x1a, 0x0a, 0x08, 0x72, 0x65,
0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x72, 0x65,
0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x22, 0x43, 0x0a, 0x05, 0x46, 0x72, 0x61, 0x6d, 0x65, 0x12,
0x12, 0x0a, 0x04, 0x4e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x4e,
0x61, 0x6d, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x46, 0x69, 0x6c, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28,
0x09, 0x52, 0x04, 0x46, 0x69, 0x6c, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x4c, 0x69, 0x6e, 0x65, 0x18,
0x03, 0x20, 0x01, 0x28, 0x05, 0x52, 0x04, 0x4c, 0x69, 0x6e, 0x65, 0x62, 0x06, 0x70, 0x72, 0x6f,
0x74, 0x6f, 0x33,
var file_github_com_moby_buildkit_util_stack_stack_proto_rawDesc = []byte{
0x0a, 0x2f, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x6d, 0x6f, 0x62,
0x79, 0x2f, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x6b, 0x69, 0x74, 0x2f, 0x75, 0x74, 0x69, 0x6c, 0x2f,
0x73, 0x74, 0x61, 0x63, 0x6b, 0x2f, 0x73, 0x74, 0x61, 0x63, 0x6b, 0x2e, 0x70, 0x72, 0x6f, 0x74,
0x6f, 0x12, 0x05, 0x73, 0x74, 0x61, 0x63, 0x6b, 0x22, 0x8f, 0x01, 0x0a, 0x05, 0x53, 0x74, 0x61,
0x63, 0x6b, 0x12, 0x24, 0x0a, 0x06, 0x66, 0x72, 0x61, 0x6d, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03,
0x28, 0x0b, 0x32, 0x0c, 0x2e, 0x73, 0x74, 0x61, 0x63, 0x6b, 0x2e, 0x46, 0x72, 0x61, 0x6d, 0x65,
0x52, 0x06, 0x66, 0x72, 0x61, 0x6d, 0x65, 0x73, 0x12, 0x18, 0x0a, 0x07, 0x63, 0x6d, 0x64, 0x6c,
0x69, 0x6e, 0x65, 0x18, 0x02, 0x20, 0x03, 0x28, 0x09, 0x52, 0x07, 0x63, 0x6d, 0x64, 0x6c, 0x69,
0x6e, 0x65, 0x12, 0x10, 0x0a, 0x03, 0x70, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x05, 0x52,
0x03, 0x70, 0x69, 0x64, 0x12, 0x18, 0x0a, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18,
0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x1a,
0x0a, 0x08, 0x72, 0x65, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09,
0x52, 0x08, 0x72, 0x65, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x22, 0x43, 0x0a, 0x05, 0x46, 0x72,
0x61, 0x6d, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x4e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28,
0x09, 0x52, 0x04, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x46, 0x69, 0x6c, 0x65, 0x18,
0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x46, 0x69, 0x6c, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x4c,
0x69, 0x6e, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x05, 0x52, 0x04, 0x4c, 0x69, 0x6e, 0x65, 0x42,
0x25, 0x5a, 0x23, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x6d, 0x6f,
0x62, 0x79, 0x2f, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x6b, 0x69, 0x74, 0x2f, 0x75, 0x74, 0x69, 0x6c,
0x2f, 0x73, 0x74, 0x61, 0x63, 0x6b, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
}
var (
file_stack_proto_rawDescOnce sync.Once
file_stack_proto_rawDescData = file_stack_proto_rawDesc
file_github_com_moby_buildkit_util_stack_stack_proto_rawDescOnce sync.Once
file_github_com_moby_buildkit_util_stack_stack_proto_rawDescData = file_github_com_moby_buildkit_util_stack_stack_proto_rawDesc
)
func file_stack_proto_rawDescGZIP() []byte {
file_stack_proto_rawDescOnce.Do(func() {
file_stack_proto_rawDescData = protoimpl.X.CompressGZIP(file_stack_proto_rawDescData)
func file_github_com_moby_buildkit_util_stack_stack_proto_rawDescGZIP() []byte {
file_github_com_moby_buildkit_util_stack_stack_proto_rawDescOnce.Do(func() {
file_github_com_moby_buildkit_util_stack_stack_proto_rawDescData = protoimpl.X.CompressGZIP(file_github_com_moby_buildkit_util_stack_stack_proto_rawDescData)
})
return file_stack_proto_rawDescData
return file_github_com_moby_buildkit_util_stack_stack_proto_rawDescData
}
var file_stack_proto_msgTypes = make([]protoimpl.MessageInfo, 2)
var file_stack_proto_goTypes = []interface{}{
var file_github_com_moby_buildkit_util_stack_stack_proto_msgTypes = make([]protoimpl.MessageInfo, 2)
var file_github_com_moby_buildkit_util_stack_stack_proto_goTypes = []any{
(*Stack)(nil), // 0: stack.Stack
(*Frame)(nil), // 1: stack.Frame
}
var file_stack_proto_depIdxs = []int32{
var file_github_com_moby_buildkit_util_stack_stack_proto_depIdxs = []int32{
1, // 0: stack.Stack.frames:type_name -> stack.Frame
1, // [1:1] is the sub-list for method output_type
1, // [1:1] is the sub-list for method input_type
@ -209,53 +209,27 @@ var file_stack_proto_depIdxs = []int32{
0, // [0:1] is the sub-list for field type_name
}
func init() { file_stack_proto_init() }
func file_stack_proto_init() {
if File_stack_proto != nil {
func init() { file_github_com_moby_buildkit_util_stack_stack_proto_init() }
func file_github_com_moby_buildkit_util_stack_stack_proto_init() {
if File_github_com_moby_buildkit_util_stack_stack_proto != nil {
return
}
if !protoimpl.UnsafeEnabled {
file_stack_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*Stack); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_stack_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*Frame); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
}
type x struct{}
out := protoimpl.TypeBuilder{
File: protoimpl.DescBuilder{
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
RawDescriptor: file_stack_proto_rawDesc,
RawDescriptor: file_github_com_moby_buildkit_util_stack_stack_proto_rawDesc,
NumEnums: 0,
NumMessages: 2,
NumExtensions: 0,
NumServices: 0,
},
GoTypes: file_stack_proto_goTypes,
DependencyIndexes: file_stack_proto_depIdxs,
MessageInfos: file_stack_proto_msgTypes,
GoTypes: file_github_com_moby_buildkit_util_stack_stack_proto_goTypes,
DependencyIndexes: file_github_com_moby_buildkit_util_stack_stack_proto_depIdxs,
MessageInfos: file_github_com_moby_buildkit_util_stack_stack_proto_msgTypes,
}.Build()
File_stack_proto = out.File
file_stack_proto_rawDesc = nil
file_stack_proto_goTypes = nil
file_stack_proto_depIdxs = nil
File_github_com_moby_buildkit_util_stack_stack_proto = out.File
file_github_com_moby_buildkit_util_stack_stack_proto_rawDesc = nil
file_github_com_moby_buildkit_util_stack_stack_proto_goTypes = nil
file_github_com_moby_buildkit_util_stack_stack_proto_depIdxs = nil
}

View file

@ -2,6 +2,8 @@ syntax = "proto3";
package stack;
option go_package = "github.com/moby/buildkit/util/stack";
message Stack {
repeated Frame frames = 1;
repeated string cmdline = 2;
@ -14,4 +16,4 @@ message Frame {
string Name = 1;
string File = 2;
int32 Line = 3;
}
}

View file

@ -0,0 +1,660 @@
// Code generated by protoc-gen-go-vtproto. DO NOT EDIT.
// protoc-gen-go-vtproto version: v0.6.1-0.20240319094008-0393e58bdf10
// source: github.com/moby/buildkit/util/stack/stack.proto
package stack
import (
fmt "fmt"
protohelpers "github.com/planetscale/vtprotobuf/protohelpers"
proto "google.golang.org/protobuf/proto"
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
io "io"
)
const (
// Verify that this generated code is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
// Verify that runtime/protoimpl is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
)
func (m *Stack) CloneVT() *Stack {
if m == nil {
return (*Stack)(nil)
}
r := new(Stack)
r.Pid = m.Pid
r.Version = m.Version
r.Revision = m.Revision
if rhs := m.Frames; rhs != nil {
tmpContainer := make([]*Frame, len(rhs))
for k, v := range rhs {
tmpContainer[k] = v.CloneVT()
}
r.Frames = tmpContainer
}
if rhs := m.Cmdline; rhs != nil {
tmpContainer := make([]string, len(rhs))
copy(tmpContainer, rhs)
r.Cmdline = tmpContainer
}
if len(m.unknownFields) > 0 {
r.unknownFields = make([]byte, len(m.unknownFields))
copy(r.unknownFields, m.unknownFields)
}
return r
}
func (m *Stack) CloneMessageVT() proto.Message {
return m.CloneVT()
}
func (m *Frame) CloneVT() *Frame {
if m == nil {
return (*Frame)(nil)
}
r := new(Frame)
r.Name = m.Name
r.File = m.File
r.Line = m.Line
if len(m.unknownFields) > 0 {
r.unknownFields = make([]byte, len(m.unknownFields))
copy(r.unknownFields, m.unknownFields)
}
return r
}
func (m *Frame) CloneMessageVT() proto.Message {
return m.CloneVT()
}
func (this *Stack) EqualVT(that *Stack) bool {
if this == that {
return true
} else if this == nil || that == nil {
return false
}
if len(this.Frames) != len(that.Frames) {
return false
}
for i, vx := range this.Frames {
vy := that.Frames[i]
if p, q := vx, vy; p != q {
if p == nil {
p = &Frame{}
}
if q == nil {
q = &Frame{}
}
if !p.EqualVT(q) {
return false
}
}
}
if len(this.Cmdline) != len(that.Cmdline) {
return false
}
for i, vx := range this.Cmdline {
vy := that.Cmdline[i]
if vx != vy {
return false
}
}
if this.Pid != that.Pid {
return false
}
if this.Version != that.Version {
return false
}
if this.Revision != that.Revision {
return false
}
return string(this.unknownFields) == string(that.unknownFields)
}
func (this *Stack) EqualMessageVT(thatMsg proto.Message) bool {
that, ok := thatMsg.(*Stack)
if !ok {
return false
}
return this.EqualVT(that)
}
func (this *Frame) EqualVT(that *Frame) bool {
if this == that {
return true
} else if this == nil || that == nil {
return false
}
if this.Name != that.Name {
return false
}
if this.File != that.File {
return false
}
if this.Line != that.Line {
return false
}
return string(this.unknownFields) == string(that.unknownFields)
}
func (this *Frame) EqualMessageVT(thatMsg proto.Message) bool {
that, ok := thatMsg.(*Frame)
if !ok {
return false
}
return this.EqualVT(that)
}
func (m *Stack) MarshalVT() (dAtA []byte, err error) {
if m == nil {
return nil, nil
}
size := m.SizeVT()
dAtA = make([]byte, size)
n, err := m.MarshalToSizedBufferVT(dAtA[:size])
if err != nil {
return nil, err
}
return dAtA[:n], nil
}
func (m *Stack) MarshalToVT(dAtA []byte) (int, error) {
size := m.SizeVT()
return m.MarshalToSizedBufferVT(dAtA[:size])
}
func (m *Stack) MarshalToSizedBufferVT(dAtA []byte) (int, error) {
if m == nil {
return 0, nil
}
i := len(dAtA)
_ = i
var l int
_ = l
if m.unknownFields != nil {
i -= len(m.unknownFields)
copy(dAtA[i:], m.unknownFields)
}
if len(m.Revision) > 0 {
i -= len(m.Revision)
copy(dAtA[i:], m.Revision)
i = protohelpers.EncodeVarint(dAtA, i, uint64(len(m.Revision)))
i--
dAtA[i] = 0x2a
}
if len(m.Version) > 0 {
i -= len(m.Version)
copy(dAtA[i:], m.Version)
i = protohelpers.EncodeVarint(dAtA, i, uint64(len(m.Version)))
i--
dAtA[i] = 0x22
}
if m.Pid != 0 {
i = protohelpers.EncodeVarint(dAtA, i, uint64(m.Pid))
i--
dAtA[i] = 0x18
}
if len(m.Cmdline) > 0 {
for iNdEx := len(m.Cmdline) - 1; iNdEx >= 0; iNdEx-- {
i -= len(m.Cmdline[iNdEx])
copy(dAtA[i:], m.Cmdline[iNdEx])
i = protohelpers.EncodeVarint(dAtA, i, uint64(len(m.Cmdline[iNdEx])))
i--
dAtA[i] = 0x12
}
}
if len(m.Frames) > 0 {
for iNdEx := len(m.Frames) - 1; iNdEx >= 0; iNdEx-- {
size, err := m.Frames[iNdEx].MarshalToSizedBufferVT(dAtA[:i])
if err != nil {
return 0, err
}
i -= size
i = protohelpers.EncodeVarint(dAtA, i, uint64(size))
i--
dAtA[i] = 0xa
}
}
return len(dAtA) - i, nil
}
func (m *Frame) MarshalVT() (dAtA []byte, err error) {
if m == nil {
return nil, nil
}
size := m.SizeVT()
dAtA = make([]byte, size)
n, err := m.MarshalToSizedBufferVT(dAtA[:size])
if err != nil {
return nil, err
}
return dAtA[:n], nil
}
func (m *Frame) MarshalToVT(dAtA []byte) (int, error) {
size := m.SizeVT()
return m.MarshalToSizedBufferVT(dAtA[:size])
}
func (m *Frame) MarshalToSizedBufferVT(dAtA []byte) (int, error) {
if m == nil {
return 0, nil
}
i := len(dAtA)
_ = i
var l int
_ = l
if m.unknownFields != nil {
i -= len(m.unknownFields)
copy(dAtA[i:], m.unknownFields)
}
if m.Line != 0 {
i = protohelpers.EncodeVarint(dAtA, i, uint64(m.Line))
i--
dAtA[i] = 0x18
}
if len(m.File) > 0 {
i -= len(m.File)
copy(dAtA[i:], m.File)
i = protohelpers.EncodeVarint(dAtA, i, uint64(len(m.File)))
i--
dAtA[i] = 0x12
}
if len(m.Name) > 0 {
i -= len(m.Name)
copy(dAtA[i:], m.Name)
i = protohelpers.EncodeVarint(dAtA, i, uint64(len(m.Name)))
i--
dAtA[i] = 0xa
}
return len(dAtA) - i, nil
}
func (m *Stack) SizeVT() (n int) {
if m == nil {
return 0
}
var l int
_ = l
if len(m.Frames) > 0 {
for _, e := range m.Frames {
l = e.SizeVT()
n += 1 + l + protohelpers.SizeOfVarint(uint64(l))
}
}
if len(m.Cmdline) > 0 {
for _, s := range m.Cmdline {
l = len(s)
n += 1 + l + protohelpers.SizeOfVarint(uint64(l))
}
}
if m.Pid != 0 {
n += 1 + protohelpers.SizeOfVarint(uint64(m.Pid))
}
l = len(m.Version)
if l > 0 {
n += 1 + l + protohelpers.SizeOfVarint(uint64(l))
}
l = len(m.Revision)
if l > 0 {
n += 1 + l + protohelpers.SizeOfVarint(uint64(l))
}
n += len(m.unknownFields)
return n
}
func (m *Frame) SizeVT() (n int) {
if m == nil {
return 0
}
var l int
_ = l
l = len(m.Name)
if l > 0 {
n += 1 + l + protohelpers.SizeOfVarint(uint64(l))
}
l = len(m.File)
if l > 0 {
n += 1 + l + protohelpers.SizeOfVarint(uint64(l))
}
if m.Line != 0 {
n += 1 + protohelpers.SizeOfVarint(uint64(m.Line))
}
n += len(m.unknownFields)
return n
}
func (m *Stack) UnmarshalVT(dAtA []byte) error {
l := len(dAtA)
iNdEx := 0
for iNdEx < l {
preIndex := iNdEx
var wire uint64
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return protohelpers.ErrIntOverflow
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
wire |= uint64(b&0x7F) << shift
if b < 0x80 {
break
}
}
fieldNum := int32(wire >> 3)
wireType := int(wire & 0x7)
if wireType == 4 {
return fmt.Errorf("proto: Stack: wiretype end group for non-group")
}
if fieldNum <= 0 {
return fmt.Errorf("proto: Stack: illegal tag %d (wire type %d)", fieldNum, wire)
}
switch fieldNum {
case 1:
if wireType != 2 {
return fmt.Errorf("proto: wrong wireType = %d for field Frames", wireType)
}
var msglen int
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return protohelpers.ErrIntOverflow
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
msglen |= int(b&0x7F) << shift
if b < 0x80 {
break
}
}
if msglen < 0 {
return protohelpers.ErrInvalidLength
}
postIndex := iNdEx + msglen
if postIndex < 0 {
return protohelpers.ErrInvalidLength
}
if postIndex > l {
return io.ErrUnexpectedEOF
}
m.Frames = append(m.Frames, &Frame{})
if err := m.Frames[len(m.Frames)-1].UnmarshalVT(dAtA[iNdEx:postIndex]); err != nil {
return err
}
iNdEx = postIndex
case 2:
if wireType != 2 {
return fmt.Errorf("proto: wrong wireType = %d for field Cmdline", wireType)
}
var stringLen uint64
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return protohelpers.ErrIntOverflow
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
stringLen |= uint64(b&0x7F) << shift
if b < 0x80 {
break
}
}
intStringLen := int(stringLen)
if intStringLen < 0 {
return protohelpers.ErrInvalidLength
}
postIndex := iNdEx + intStringLen
if postIndex < 0 {
return protohelpers.ErrInvalidLength
}
if postIndex > l {
return io.ErrUnexpectedEOF
}
m.Cmdline = append(m.Cmdline, string(dAtA[iNdEx:postIndex]))
iNdEx = postIndex
case 3:
if wireType != 0 {
return fmt.Errorf("proto: wrong wireType = %d for field Pid", wireType)
}
m.Pid = 0
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return protohelpers.ErrIntOverflow
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
m.Pid |= int32(b&0x7F) << shift
if b < 0x80 {
break
}
}
case 4:
if wireType != 2 {
return fmt.Errorf("proto: wrong wireType = %d for field Version", wireType)
}
var stringLen uint64
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return protohelpers.ErrIntOverflow
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
stringLen |= uint64(b&0x7F) << shift
if b < 0x80 {
break
}
}
intStringLen := int(stringLen)
if intStringLen < 0 {
return protohelpers.ErrInvalidLength
}
postIndex := iNdEx + intStringLen
if postIndex < 0 {
return protohelpers.ErrInvalidLength
}
if postIndex > l {
return io.ErrUnexpectedEOF
}
m.Version = string(dAtA[iNdEx:postIndex])
iNdEx = postIndex
case 5:
if wireType != 2 {
return fmt.Errorf("proto: wrong wireType = %d for field Revision", wireType)
}
var stringLen uint64
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return protohelpers.ErrIntOverflow
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
stringLen |= uint64(b&0x7F) << shift
if b < 0x80 {
break
}
}
intStringLen := int(stringLen)
if intStringLen < 0 {
return protohelpers.ErrInvalidLength
}
postIndex := iNdEx + intStringLen
if postIndex < 0 {
return protohelpers.ErrInvalidLength
}
if postIndex > l {
return io.ErrUnexpectedEOF
}
m.Revision = string(dAtA[iNdEx:postIndex])
iNdEx = postIndex
default:
iNdEx = preIndex
skippy, err := protohelpers.Skip(dAtA[iNdEx:])
if err != nil {
return err
}
if (skippy < 0) || (iNdEx+skippy) < 0 {
return protohelpers.ErrInvalidLength
}
if (iNdEx + skippy) > l {
return io.ErrUnexpectedEOF
}
m.unknownFields = append(m.unknownFields, dAtA[iNdEx:iNdEx+skippy]...)
iNdEx += skippy
}
}
if iNdEx > l {
return io.ErrUnexpectedEOF
}
return nil
}
func (m *Frame) UnmarshalVT(dAtA []byte) error {
l := len(dAtA)
iNdEx := 0
for iNdEx < l {
preIndex := iNdEx
var wire uint64
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return protohelpers.ErrIntOverflow
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
wire |= uint64(b&0x7F) << shift
if b < 0x80 {
break
}
}
fieldNum := int32(wire >> 3)
wireType := int(wire & 0x7)
if wireType == 4 {
return fmt.Errorf("proto: Frame: wiretype end group for non-group")
}
if fieldNum <= 0 {
return fmt.Errorf("proto: Frame: illegal tag %d (wire type %d)", fieldNum, wire)
}
switch fieldNum {
case 1:
if wireType != 2 {
return fmt.Errorf("proto: wrong wireType = %d for field Name", wireType)
}
var stringLen uint64
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return protohelpers.ErrIntOverflow
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
stringLen |= uint64(b&0x7F) << shift
if b < 0x80 {
break
}
}
intStringLen := int(stringLen)
if intStringLen < 0 {
return protohelpers.ErrInvalidLength
}
postIndex := iNdEx + intStringLen
if postIndex < 0 {
return protohelpers.ErrInvalidLength
}
if postIndex > l {
return io.ErrUnexpectedEOF
}
m.Name = string(dAtA[iNdEx:postIndex])
iNdEx = postIndex
case 2:
if wireType != 2 {
return fmt.Errorf("proto: wrong wireType = %d for field File", wireType)
}
var stringLen uint64
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return protohelpers.ErrIntOverflow
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
stringLen |= uint64(b&0x7F) << shift
if b < 0x80 {
break
}
}
intStringLen := int(stringLen)
if intStringLen < 0 {
return protohelpers.ErrInvalidLength
}
postIndex := iNdEx + intStringLen
if postIndex < 0 {
return protohelpers.ErrInvalidLength
}
if postIndex > l {
return io.ErrUnexpectedEOF
}
m.File = string(dAtA[iNdEx:postIndex])
iNdEx = postIndex
case 3:
if wireType != 0 {
return fmt.Errorf("proto: wrong wireType = %d for field Line", wireType)
}
m.Line = 0
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return protohelpers.ErrIntOverflow
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
m.Line |= int32(b&0x7F) << shift
if b < 0x80 {
break
}
}
default:
iNdEx = preIndex
skippy, err := protohelpers.Skip(dAtA[iNdEx:])
if err != nil {
return err
}
if (skippy < 0) || (iNdEx+skippy) < 0 {
return protohelpers.ErrInvalidLength
}
if (iNdEx + skippy) > l {
return io.ErrUnexpectedEOF
}
m.unknownFields = append(m.unknownFields, dAtA[iNdEx:iNdEx+skippy]...)
iNdEx += skippy
}
}
if iNdEx > l {
return io.ErrUnexpectedEOF
}
return nil
}

Some files were not shown because too many files have changed in this diff Show more