Compare commits

...

35 Commits

Author SHA1 Message Date
renovate-bot f3943d25d1 chore(deps): update otel/weaver docker tag to v0.24.2 2026-06-23 18:00:57 +00:00
renovate-bot 9859ff46f5 chore(deps): update otel/weaver docker tag to v0.24.1 2026-06-21 21:01:00 +00:00
renovate-bot 3109a0da82 chore(deps): update otel/weaver docker tag to v0.24.0 2026-06-20 00:01:07 +00:00
renovate-bot b1146a707e chore(deps): update nginx docker tag to v1.31.2 2026-06-17 23:01:19 +00:00
Weblate 6d13d14d75 chore: update translation files
Updated by "Update PO files to match POT (msgmerge)" add-on in Weblate.

Translation: Co-op Cloud/abra
Translate-URL: https://translate.coopcloud.tech/projects/co-op-cloud/abra/
2026-06-16 17:41:06 +00:00
Linus Gasser 427ec22cab Running i18n 2026-06-16 17:40:58 +00:00
Linus Gasser 1111b69f12 Use docker login credentials from host
I had a lot of failures for pulling the docker images lately,
so I was looking for a way to connect using docker login.
This PR sends the docker login credentials from the host to
the swarm server.
2026-06-16 17:40:58 +00:00
Linus Gasser 1541e6aa6a Also use xgettext-go@latest for .drone.yml 2026-06-14 18:37:34 +02:00
Linus Gasser 14ee80582a Use go run for xgettext-go
Before: on a mac, the Makefile downloaded a linux file, which did not work
for updating the i18n.

Now: use 'go run' to run the xgettext-go file. go caches it, so it compiles only
once.
2026-06-14 18:28:45 +02:00
renovate-bot e5c65b8fa0 chore(deps): update alpine docker tag to v3.24 2026-06-09 21:00:57 +00:00
Weblate e1b10f6020 chore: update translation files
Updated by "Update PO files to match POT (msgmerge)" add-on in Weblate.

Translation: Co-op Cloud/abra
Translate-URL: https://translate.coopcloud.tech/projects/co-op-cloud/abra/
2026-06-09 14:36:32 +00:00
moritz 8e4ed7b689 chore: make i18n 2026-06-09 16:30:08 +02:00
moritz a44fde2df2 fix(new): checkout given recipeVersion before generating env closes #862 2026-06-09 16:09:58 +02:00
renovate-bot e623f55852 chore(deps): update golang docker tag 2026-06-08 17:01:04 +00:00
renovate-bot 11e6f28a60 chore(deps): update module golang.org/x/term to v0.44.0 2026-06-08 16:01:46 +00:00
renovate-bot acec067d76 chore(deps): update golang docker tag 2026-06-08 14:01:12 +00:00
renovate-bot f50a57af7a chore(deps): update module golang.org/x/sys to v0.46.0 2026-06-08 13:01:32 +00:00
renovate-bot 776693acc0 chore(deps): update golang docker tag 2026-05-22 23:01:07 +00:00
renovate-bot 5dea5f7746 chore(deps): update module golang.org/x/sys to v0.45.0 2026-05-22 22:01:49 +00:00
renovate-bot 1d9a289888 chore(deps): update nginx docker tag to v1.31.1 2026-05-22 21:00:50 +00:00
devydave c7bd55e371 feat: adds nix flake 2026-05-20 22:29:47 +00:00
renovate-bot 4276337b0f chore(deps): update golang docker tag 2026-05-18 22:01:06 +00:00
renovate-bot 90ca856b64 chore(deps): update module github.com/go-git/go-git/v5 to v5.19.1 2026-05-18 21:01:33 +00:00
renovate-bot f2dd65491d chore(deps): update golang docker tag 2026-05-15 16:01:27 +00:00
renovate-bot 0e902ed897 chore(deps): update coopcloud.tech/tagcmp digest to c26951b 2026-05-15 14:01:42 +00:00
renovate-bot db001c1ba4 chore(deps): update tonistiigi/xx docker tag to v1.9.0 2026-05-15 00:00:59 +00:00
renovate-bot e4215c09aa chore(deps): update nginx docker tag to v1.31.0 2026-05-14 23:04:17 +00:00
renovate-bot e0e6dcb710 chore(deps): update otel/weaver docker tag to v0.23.0 2026-05-14 23:00:58 +00:00
renovate-bot e7ddb74a08 chore(deps): update module golang.org/x/term to v0.43.0 2026-05-14 22:04:37 +00:00
renovate-bot 24a5e6334f chore(deps): update golang docker tag 2026-05-14 22:00:58 +00:00
renovate-bot 9d8eb2317e chore(deps): update module golang.org/x/sys to v0.44.0 2026-05-14 21:01:45 +00:00
renovate-bot 5945ea8e1b chore(deps): update module github.com/go-git/go-git/v5 to v5.19.0 2026-05-14 20:01:30 +00:00
renovate-bot e170d1c971 chore(deps): update alpine docker tag to v3.23 2026-05-14 19:04:12 +00:00
renovate-bot 5eba3abb1b chore(deps): update golang docker tag 2026-05-14 19:01:12 +00:00
renovate-bot df5a38e887 chore(deps): update module github.com/decentral1se/cobra to v1.10.2 2026-05-14 18:01:50 +00:00
159 changed files with 5808 additions and 1617 deletions
+11 -6
View File
@@ -8,12 +8,17 @@ steps:
- make check - make check
- name: xgettext-go - name: xgettext-go
image: git.coopcloud.tech/toolshed/drone-xgettext-go:latest image: golang:1.26
settings: environment:
keyword: i18n.G GOPRIVATE: coopcloud.tech
keyword_ctx: i18n.GC commands:
out: pkg/i18n/locales/abra.pot - go run git.coopcloud.tech/toolshed/xgettext-go@latest
comments_tag: translators -o pkg/i18n/locales/abra.pot
--keyword=i18n.G
--keyword-ctx=i18n.GC
--sort-output
--add-comments-tag="translators"
$(find . -name "*.go" -not -path "*vendor*" | sort)
depends_on: depends_on:
- make check - make check
when: when:
+1 -1
View File
@@ -15,7 +15,7 @@ WORKDIR /app
RUN CGO_ENABLED=0 make build RUN CGO_ENABLED=0 make build
FROM alpine:3.22 FROM alpine:3.24
RUN apk add --no-cache \ RUN apk add --no-cache \
ca-certificates \ ca-certificates \
+3 -8
View File
@@ -1,5 +1,5 @@
ABRA := ./cmd/abra ABRA := ./cmd/abra
XGETTEXT := ./bin/xgettext-go XGETTEXT := go run git.coopcloud.tech/toolshed/xgettext-go@latest
COMMIT := $(shell git rev-list -1 HEAD) COMMIT := $(shell git rev-list -1 HEAD)
GOPATH := $(shell go env GOPATH) GOPATH := $(shell go env GOPATH)
GOVERSION := 1.26 GOVERSION := 1.26
@@ -62,8 +62,8 @@ update-po:
done done
.PHONY: update-pot .PHONY: update-pot
update-pot: $(XGETTEXT) update-pot:
@${XGETTEXT} \ @$(XGETTEXT) \
-o pkg/i18n/locales/$(DOMAIN).pot \ -o pkg/i18n/locales/$(DOMAIN).pot \
--keyword=i18n.G \ --keyword=i18n.G \
--keyword-ctx=i18n.GC \ --keyword-ctx=i18n.GC \
@@ -71,11 +71,6 @@ update-pot: $(XGETTEXT)
--add-comments-tag="translators" \ --add-comments-tag="translators" \
$$(find . -name "*.go" -not -path "*vendor*" | sort) $$(find . -name "*.go" -not -path "*vendor*" | sort)
${XGETTEXT}:
@mkdir -p ./bin && \
wget -O ./bin/xgettext-go https://git.coopcloud.tech/toolshed/xgettext-go/raw/branch/main/xgettext-go && \
chmod +x ./bin/xgettext-go
.PHONY: update-pot-po-metadata .PHONY: update-pot-po-metadata
update-pot-po-metadata: update-pot-po-metadata:
@sed -i "s/charset=CHARSET/charset=UTF-8/g" pkg/i18n/locales/*.po pkg/i18n/locales/*.pot @sed -i "s/charset=CHARSET/charset=UTF-8/g" pkg/i18n/locales/*.po pkg/i18n/locales/*.pot
+6 -5
View File
@@ -151,11 +151,12 @@ checkout as-is. Recipe commit hashes are also supported as values for
stackName := app.StackName() stackName := app.StackName()
deployOpts := stack.Deploy{ deployOpts := stack.Deploy{
Composefiles: composeFiles, Composefiles: composeFiles,
Namespace: stackName, Namespace: stackName,
Prune: false, Prune: false,
ResolveImage: stack.ResolveImageAlways, ResolveImage: stack.ResolveImageAlways,
Detach: false, Detach: false,
SendRegistryAuth: true,
} }
compose, err := appPkg.GetAppComposeConfig(app.Name, deployOpts, app.Env) compose, err := appPkg.GetAppComposeConfig(app.Name, deployOpts, app.Env)
if err != nil { if err != nil {
+5 -1
View File
@@ -112,7 +112,11 @@ var AppNewCommand = &cobra.Command{
} }
} }
if len(recipeVersions) > 0 { if recipeVersion != "" {
if _, err := recipe.EnsureVersion(recipeVersion); err != nil {
log.Fatal(err)
}
} else if len(recipeVersions) > 0 {
latest := recipeVersions[len(recipeVersions)-1] latest := recipeVersions[len(recipeVersions)-1]
for tag := range latest { for tag := range latest {
recipeVersion = tag recipeVersion = tag
+6 -5
View File
@@ -166,11 +166,12 @@ beforehand. See "abra app backup" for more.`),
stackName := app.StackName() stackName := app.StackName()
deployOpts := stack.Deploy{ deployOpts := stack.Deploy{
Composefiles: composeFiles, Composefiles: composeFiles,
Namespace: stackName, Namespace: stackName,
Prune: false, Prune: false,
ResolveImage: stack.ResolveImageAlways, ResolveImage: stack.ResolveImageAlways,
Detach: false, Detach: false,
SendRegistryAuth: true,
} }
compose, err := appPkg.GetAppComposeConfig(app.Name, deployOpts, app.Env) compose, err := appPkg.GetAppComposeConfig(app.Name, deployOpts, app.Env)
+6 -5
View File
@@ -178,11 +178,12 @@ beforehand. See "abra app backup" for more.`),
stackName := app.StackName() stackName := app.StackName()
deployOpts := stack.Deploy{ deployOpts := stack.Deploy{
Composefiles: composeFiles, Composefiles: composeFiles,
Namespace: stackName, Namespace: stackName,
Prune: false, Prune: false,
ResolveImage: stack.ResolveImageAlways, ResolveImage: stack.ResolveImageAlways,
Detach: false, Detach: false,
SendRegistryAuth: true,
} }
compose, err := appPkg.GetAppComposeConfig(app.Name, deployOpts, app.Env) compose, err := appPkg.GetAppComposeConfig(app.Name, deployOpts, app.Env)
Generated
+61
View File
@@ -0,0 +1,61 @@
{
"nodes": {
"flake-utils": {
"inputs": {
"systems": "systems"
},
"locked": {
"lastModified": 1731533236,
"narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=",
"owner": "numtide",
"repo": "flake-utils",
"rev": "11707dc2f618dd54ca8739b309ec4fc024de578b",
"type": "github"
},
"original": {
"owner": "numtide",
"repo": "flake-utils",
"type": "github"
}
},
"nixpkgs": {
"locked": {
"lastModified": 1778443072,
"narHash": "sha256-zi7/fsqM/kFdNuED//4WOCUtezGtKKqRNORjMvfwjnA=",
"owner": "nixos",
"repo": "nixpkgs",
"rev": "da5ad661ba4e5ef59ba743f0d112cbc30e474f32",
"type": "github"
},
"original": {
"owner": "nixos",
"ref": "nixos-unstable",
"repo": "nixpkgs",
"type": "github"
}
},
"root": {
"inputs": {
"flake-utils": "flake-utils",
"nixpkgs": "nixpkgs"
}
},
"systems": {
"locked": {
"lastModified": 1681028828,
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
"owner": "nix-systems",
"repo": "default",
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
"type": "github"
},
"original": {
"owner": "nix-systems",
"repo": "default",
"type": "github"
}
}
},
"root": "root",
"version": 7
}
+37
View File
@@ -0,0 +1,37 @@
{
description = "The Co-op Cloud utility belt";
inputs = {
nixpkgs.url = "github:nixos/nixpkgs?ref=nixos-unstable";
flake-utils.url = "github:numtide/flake-utils";
};
outputs =
{
self,
nixpkgs,
flake-utils,
}:
flake-utils.lib.eachDefaultSystem (
system:
let
pkgs = nixpkgs.legacyPackages.${system};
in
{
packages = rec {
abra = pkgs.callPackage ./package.nix { };
default = abra;
};
apps = rec {
abra = flake-utils.lib.mkApp { drv = self.packages.${system}.abra; };
default = abra;
};
devShells.default = pkgs.mkShell {
packages = with pkgs; [
go_1_26
gnumake
];
};
}
);
}
+11 -11
View File
@@ -3,7 +3,7 @@ module coopcloud.tech/abra
go 1.26.0 go 1.26.0
require ( require (
coopcloud.tech/tagcmp v0.0.0-20250818180036-0ec1b205b5ca coopcloud.tech/tagcmp v0.0.0-20260515102403-c26951b55977
git.coopcloud.tech/toolshed/godotenv v1.5.2-0.20250103171850-4d0ca41daa5c git.coopcloud.tech/toolshed/godotenv v1.5.2-0.20250103171850-4d0ca41daa5c
github.com/AlecAivazis/survey/v2 v2.3.7 github.com/AlecAivazis/survey/v2 v2.3.7
github.com/charmbracelet/bubbletea v1.3.10 github.com/charmbracelet/bubbletea v1.3.10
@@ -13,14 +13,14 @@ require (
github.com/docker/cli v28.4.0+incompatible github.com/docker/cli v28.4.0+incompatible
github.com/docker/docker v28.5.2+incompatible github.com/docker/docker v28.5.2+incompatible
github.com/docker/go-units v0.5.0 github.com/docker/go-units v0.5.0
github.com/go-git/go-git/v5 v5.17.2 github.com/go-git/go-git/v5 v5.19.1
github.com/google/go-cmp v0.7.0 github.com/google/go-cmp v0.7.0
github.com/leonelquinteros/gotext v1.7.2 github.com/leonelquinteros/gotext v1.7.2
github.com/moby/sys/signal v0.7.1 github.com/moby/sys/signal v0.7.1
github.com/moby/term v0.5.2 github.com/moby/term v0.5.2
github.com/pkg/errors v0.9.1 github.com/pkg/errors v0.9.1
github.com/schollz/progressbar/v3 v3.19.0 github.com/schollz/progressbar/v3 v3.19.0
golang.org/x/term v0.41.0 golang.org/x/term v0.44.0
gopkg.in/yaml.v3 v3.0.1 gopkg.in/yaml.v3 v3.0.1
gotest.tools/v3 v3.5.2 gotest.tools/v3 v3.5.2
) )
@@ -60,7 +60,7 @@ require (
github.com/felixge/httpsnoop v1.0.4 // indirect github.com/felixge/httpsnoop v1.0.4 // indirect
github.com/ghodss/yaml v1.0.0 // indirect github.com/ghodss/yaml v1.0.0 // indirect
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect
github.com/go-git/go-billy/v5 v5.8.0 // indirect github.com/go-git/go-billy/v5 v5.9.0 // indirect
github.com/go-logfmt/logfmt v0.6.1 // indirect github.com/go-logfmt/logfmt v0.6.1 // indirect
github.com/go-logr/logr v1.4.3 // indirect github.com/go-logr/logr v1.4.3 // indirect
github.com/go-logr/stdr v1.2.2 // indirect github.com/go-logr/stdr v1.2.2 // indirect
@@ -96,7 +96,7 @@ require (
github.com/opencontainers/go-digest v1.0.0 // indirect github.com/opencontainers/go-digest v1.0.0 // indirect
github.com/opencontainers/runc v1.1.13 // indirect github.com/opencontainers/runc v1.1.13 // indirect
github.com/opencontainers/runtime-spec v1.1.0 // indirect github.com/opencontainers/runtime-spec v1.1.0 // indirect
github.com/pjbgf/sha1cd v0.5.0 // indirect github.com/pjbgf/sha1cd v0.6.0 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/prometheus/client_model v0.6.2 // indirect github.com/prometheus/client_model v0.6.2 // indirect
github.com/prometheus/common v0.67.5 // indirect github.com/prometheus/common v0.67.5 // indirect
@@ -124,10 +124,10 @@ require (
go.opentelemetry.io/proto/otlp v1.10.0 // indirect go.opentelemetry.io/proto/otlp v1.10.0 // indirect
go.yaml.in/yaml/v2 v2.4.4 // indirect go.yaml.in/yaml/v2 v2.4.4 // indirect
go.yaml.in/yaml/v3 v3.0.4 // indirect go.yaml.in/yaml/v3 v3.0.4 // indirect
golang.org/x/crypto v0.49.0 // indirect golang.org/x/crypto v0.50.0 // indirect
golang.org/x/exp v0.0.0-20260312153236-7ab1446f8b90 // indirect golang.org/x/exp v0.0.0-20260410095643-746e56fc9e2f // indirect
golang.org/x/net v0.52.0 // indirect golang.org/x/net v0.53.0 // indirect
golang.org/x/text v0.35.0 // indirect golang.org/x/text v0.36.0 // indirect
golang.org/x/time v0.15.0 // indirect golang.org/x/time v0.15.0 // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20260401024825-9d38bb4040a9 // indirect google.golang.org/genproto/googleapis/api v0.0.0-20260401024825-9d38bb4040a9 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20260401024825-9d38bb4040a9 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20260401024825-9d38bb4040a9 // indirect
@@ -155,9 +155,9 @@ require (
github.com/stretchr/testify v1.11.1 github.com/stretchr/testify v1.11.1
github.com/theupdateframework/notary v0.7.0 // indirect github.com/theupdateframework/notary v0.7.0 // indirect
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect
golang.org/x/sys v0.42.0 golang.org/x/sys v0.46.0
) )
replace github.com/docker/cli v28.4.0+incompatible => git.coopcloud.tech/toolshed/docker-cli v28.5.3-0.20260202112816-30df2d0b3a00+incompatible replace github.com/docker/cli v28.4.0+incompatible => git.coopcloud.tech/toolshed/docker-cli v28.5.3-0.20260202112816-30df2d0b3a00+incompatible
replace github.com/spf13/cobra => github.com/decentral1se/cobra v1.10.2-i18n replace github.com/spf13/cobra => github.com/decentral1se/cobra v1.10.2
+22 -22
View File
@@ -22,8 +22,8 @@ cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIA
cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw=
cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos= cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos=
cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk=
coopcloud.tech/tagcmp v0.0.0-20250818180036-0ec1b205b5ca h1:gSD53tBAsbIGq4SnFfq+mEep6foekQ2a5ea7b38qkm0= coopcloud.tech/tagcmp v0.0.0-20260515102403-c26951b55977 h1:J7I0HFjwVAj/kkX6lwSTHmlXDRjQRsdIFNUUqu55ADY=
coopcloud.tech/tagcmp v0.0.0-20250818180036-0ec1b205b5ca/go.mod h1:ESVm0wQKcbcFi06jItF3rI7enf4Jt2PvbkWpDDHk1DQ= coopcloud.tech/tagcmp v0.0.0-20260515102403-c26951b55977/go.mod h1:ESVm0wQKcbcFi06jItF3rI7enf4Jt2PvbkWpDDHk1DQ=
dario.cat/mergo v1.0.2 h1:85+piFYR1tMbRrLcDwR18y4UKJ3aH1Tbzi24VRW1TK8= dario.cat/mergo v1.0.2 h1:85+piFYR1tMbRrLcDwR18y4UKJ3aH1Tbzi24VRW1TK8=
dario.cat/mergo v1.0.2/go.mod h1:E/hbnu0NxMFBjpMIE34DRGLWqDy0g5FuKDhCb31ngxA= dario.cat/mergo v1.0.2/go.mod h1:E/hbnu0NxMFBjpMIE34DRGLWqDy0g5FuKDhCb31ngxA=
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
@@ -308,8 +308,8 @@ github.com/d2g/hardwareaddr v0.0.0-20190221164911-e7d9fbe030e4/go.mod h1:bMl4RjI
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/decentral1se/cobra v1.10.2-i18n h1:XR+6AHHfnf4k5NM9f09oLMrEVwz3rkQIAIcqgL8R08g= github.com/decentral1se/cobra v1.10.2 h1:MZ8Ifi/jRels9sZrpSccDbUlK++3b2HlBODfv0Bh6x0=
github.com/decentral1se/cobra v1.10.2-i18n/go.mod h1:7C1pvHqHw5A4vrJfjNwvOdzYu0Gml16OCs2GRiTUUS4= github.com/decentral1se/cobra v1.10.2/go.mod h1:7C1pvHqHw5A4vrJfjNwvOdzYu0Gml16OCs2GRiTUUS4=
github.com/decentral1se/passgen v1.0.1 h1:j2AxK/kHKxDHWZZfkJj8Wgae9+O+DYEqR5sjKthIYKA= github.com/decentral1se/passgen v1.0.1 h1:j2AxK/kHKxDHWZZfkJj8Wgae9+O+DYEqR5sjKthIYKA=
github.com/decentral1se/passgen v1.0.1/go.mod h1:530V+lNoPhKtkrX2fIVsIfLhkl47CuiOM7HRgi7C+SU= github.com/decentral1se/passgen v1.0.1/go.mod h1:530V+lNoPhKtkrX2fIVsIfLhkl47CuiOM7HRgi7C+SU=
github.com/denisenkom/go-mssqldb v0.0.0-20191128021309-1d7a30a10f73/go.mod h1:xbL0rPBG9cCiLr28tMa8zpbdarY27NDyej4t/EjAShU= github.com/denisenkom/go-mssqldb v0.0.0-20191128021309-1d7a30a10f73/go.mod h1:xbL0rPBG9cCiLr28tMa8zpbdarY27NDyej4t/EjAShU=
@@ -389,12 +389,12 @@ github.com/gliderlabs/ssh v0.3.8 h1:a4YXD1V7xMF9g5nTkdfnja3Sxy1PVDCj1Zg4Wb8vY6c=
github.com/gliderlabs/ssh v0.3.8/go.mod h1:xYoytBv1sV0aL3CavoDuJIQNURXkkfPA/wxQ1pL1fAU= github.com/gliderlabs/ssh v0.3.8/go.mod h1:xYoytBv1sV0aL3CavoDuJIQNURXkkfPA/wxQ1pL1fAU=
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 h1:+zs/tPmkDkHx3U66DAb0lQFJrpS6731Oaa12ikc+DiI= github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 h1:+zs/tPmkDkHx3U66DAb0lQFJrpS6731Oaa12ikc+DiI=
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376/go.mod h1:an3vInlBmSxCcxctByoQdvwPiA7DTK7jaaFDBTtu0ic= github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376/go.mod h1:an3vInlBmSxCcxctByoQdvwPiA7DTK7jaaFDBTtu0ic=
github.com/go-git/go-billy/v5 v5.8.0 h1:I8hjc3LbBlXTtVuFNJuwYuMiHvQJDq1AT6u4DwDzZG0= github.com/go-git/go-billy/v5 v5.9.0 h1:jItGXszUDRtR/AlferWPTMN4j38BQ88XnXKbilmmBPA=
github.com/go-git/go-billy/v5 v5.8.0/go.mod h1:RpvI/rw4Vr5QA+Z60c6d6LXH0rYJo0uD5SqfmrrheCY= github.com/go-git/go-billy/v5 v5.9.0/go.mod h1:jCnQMLj9eUgGU7+ludSTYoZL/GGmii14RxKFj7ROgHw=
github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399 h1:eMje31YglSBqCdIqdhKBW8lokaMrL3uTkpGYlE2OOT4= github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399 h1:eMje31YglSBqCdIqdhKBW8lokaMrL3uTkpGYlE2OOT4=
github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399/go.mod h1:1OCfN199q1Jm3HZlxleg+Dw/mwps2Wbk9frAWm+4FII= github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399/go.mod h1:1OCfN199q1Jm3HZlxleg+Dw/mwps2Wbk9frAWm+4FII=
github.com/go-git/go-git/v5 v5.17.2 h1:B+nkdlxdYrvyFK4GPXVU8w1U+YkbsgciIR7f2sZJ104= github.com/go-git/go-git/v5 v5.19.1 h1:nX27AnaU43/K5bKktKwgBmR9lawoYVe1Ckg0rgzzN00=
github.com/go-git/go-git/v5 v5.17.2/go.mod h1:pW/VmeqkanRFqR6AljLcs7EA7FbZaN5MQqO7oZADXpo= github.com/go-git/go-git/v5 v5.19.1/go.mod h1:Pb1v0c7/g8aGQJwx9Us09W85yGoyvSwuhEGMH7zjDKQ=
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
@@ -753,8 +753,8 @@ github.com/opencontainers/selinux v1.10.0/go.mod h1:2i0OySw99QjzBBQByd1Gr9gSjvuh
github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o=
github.com/pelletier/go-toml v1.8.1/go.mod h1:T2/BmBdy8dvIRq1a/8aqjN41wvWlN4lrapLU/GW4pbc= github.com/pelletier/go-toml v1.8.1/go.mod h1:T2/BmBdy8dvIRq1a/8aqjN41wvWlN4lrapLU/GW4pbc=
github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU= github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU=
github.com/pjbgf/sha1cd v0.5.0 h1:a+UkboSi1znleCDUNT3M5YxjOnN1fz2FhN48FlwCxs0= github.com/pjbgf/sha1cd v0.6.0 h1:3WJ8Wz8gvDz29quX1OcEmkAlUg9diU4GxJHqs0/XiwU=
github.com/pjbgf/sha1cd v0.5.0/go.mod h1:lhpGlyHLpQZoxMv8HcgXvZEhcGs0PG/vsZnEJ7H0iCM= github.com/pjbgf/sha1cd v0.6.0/go.mod h1:lhpGlyHLpQZoxMv8HcgXvZEhcGs0PG/vsZnEJ7H0iCM=
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.8.1-0.20171018195549-f15c970de5b7/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.1-0.20171018195549-f15c970de5b7/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
@@ -966,8 +966,8 @@ golang.org/x/crypto v0.0.0-20201117144127-c1f2f97bffc9/go.mod h1:jdWPYTVW3xRLrWP
golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/crypto v0.49.0 h1:+Ng2ULVvLHnJ/ZFEq4KdcDd/cfjrrjjNSXNzxg0Y4U4= golang.org/x/crypto v0.50.0 h1:zO47/JPrL6vsNkINmLoo/PH1gcxpls50DNogFvB5ZGI=
golang.org/x/crypto v0.49.0/go.mod h1:ErX4dUh2UM+CFYiXZRTcMpEcN8b/1gxEuv3nODoYtCA= golang.org/x/crypto v0.50.0/go.mod h1:3muZ7vA7PBCE6xgPX7nkzzjiUq87kRItoJQM1Yo8S+Q=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
@@ -978,8 +978,8 @@ golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u0
golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM=
golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=
golang.org/x/exp v0.0.0-20260312153236-7ab1446f8b90 h1:jiDhWWeC7jfWqR9c/uplMOqJ0sbNlNWv0UkzE0vX1MA= golang.org/x/exp v0.0.0-20260410095643-746e56fc9e2f h1:W3F4c+6OLc6H2lb//N1q4WpJkhzJCK5J6kUi1NTVXfM=
golang.org/x/exp v0.0.0-20260312153236-7ab1446f8b90/go.mod h1:xE1HEv6b+1SCZ5/uscMRjUBKtIxworgEcEi+/n9NQDQ= golang.org/x/exp v0.0.0-20260410095643-746e56fc9e2f/go.mod h1:J1xhfL/vlindoeF/aINzNzt2Bket5bjo9sdOYzOsU80=
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
@@ -1043,8 +1043,8 @@ golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96b
golang.org/x/net v0.0.0-20210825183410-e898025ed96a/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20210825183410-e898025ed96a/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.52.0 h1:He/TN1l0e4mmR3QqHMT2Xab3Aj3L9qjbhRm78/6jrW0= golang.org/x/net v0.53.0 h1:d+qAbo5L0orcWAr0a9JweQpjXF19LMXJE8Ey7hwOdUA=
golang.org/x/net v0.52.0/go.mod h1:R1MAz7uMZxVMualyPXb+VaqGSa3LIaUqk0eEt3w36Sw= golang.org/x/net v0.53.0/go.mod h1:JvMuJH7rrdiCfbeHoo3fCQU24Lf5JJwT9W3sJFulfgs=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
@@ -1140,13 +1140,13 @@ golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.42.0 h1:omrd2nAlyT5ESRdCLYdm3+fMfNFE/+Rf4bDIQImRJeo= golang.org/x/sys v0.46.0 h1:noSf2Fq6F8DBgS+LysIkx7rIExoNHJsxOAtPp4rthXw=
golang.org/x/sys v0.42.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= golang.org/x/sys v0.46.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= 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.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.41.0 h1:QCgPso/Q3RTJx2Th4bDLqML4W6iJiaXFq2/ftQF13YU= golang.org/x/term v0.44.0 h1:0rLvDRCtNj0gZkyIXhCyOb2OAzEhLVqc4B+hrsBhrmc=
golang.org/x/term v0.41.0/go.mod h1:3pfBgksrReYfZ5lvYM0kSO0LIkAl4Yl2bXOkKP7Ec2A= golang.org/x/term v0.44.0/go.mod h1:7ze4MdzUzLXpSAoFP1H0bOI9aXDqveSvatT5vKcFh2Y=
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
@@ -1156,8 +1156,8 @@ golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= 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.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.35.0 h1:JOVx6vVDFokkpaq1AEptVzLTpDe9KGpj5tR4/X+ybL8= golang.org/x/text v0.36.0 h1:JfKh3XmcRPqZPKevfXVpI1wXPTqbkE5f7JA92a55Yxg=
golang.org/x/text v0.35.0/go.mod h1:khi/HExzZJ2pGnjenulevKNX1W67CUy0AsXcNubPGCA= golang.org/x/text v0.36.0/go.mod h1:NIdBknypM8iqVmPiuco0Dh6P5Jcdk8lJL0CUebqK164=
golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
+54
View File
@@ -0,0 +1,54 @@
{
buildGo126Module,
fetchgit,
lib,
installShellFiles,
}:
buildGo126Module rec {
pname = "abra";
version = "0.13.0-beta";
rev = "06a57ded025a43c80f94d4e65299add8a31830dc";
src = fetchgit {
url = "https://git.coopcloud.tech/toolshed/abra.git";
tag = version;
hash = "sha256-rgoK0TY0WLSQ39lPvVM80zW/qJF40VFBSxYDOaKXZQo=";
};
vendorHash = null;
nativeBuildInputs = [
installShellFiles
];
env.CGO_ENABLED = 0;
buildPhase = ''
runHook preBuild
go build -ldflags="-s -w -X 'main.Commit=${rev}' -X 'main.Version=${version}'" ./cmd/abra
runHook postBuild
'';
installPhase = ''
runHook preInstall
install -D abra $out/bin/abra
runHook postInstall
'';
postInstall = ''
export ABRA_DIR="$out"
$out/bin/abra autocomplete bash >abra.bash
$out/bin/abra autocomplete fish >abra.fish
$out/bin/abra autocomplete zsh >abra.zsh
installShellCompletion abra.{bash,fish,zsh}
'';
meta = with lib; {
description = "The Co-op Cloud utility belt";
homepage = "https://docs.coopcloud.tech/abra";
changelog = "https://git.coopcloud.tech/toolshed/abra/releases/tag/${version}";
mainProgram = "abra";
license = licenses.gpl3Plus;
maintainers = "devydave";
};
}
+83 -83
View File
@@ -7,7 +7,7 @@
msgid "" msgid ""
msgstr "Project-Id-Version: \n" msgstr "Project-Id-Version: \n"
"Report-Msgid-Bugs-To: EMAIL\n" "Report-Msgid-Bugs-To: EMAIL\n"
"POT-Creation-Date: 2026-04-11 11:34+0200\n" "POT-Creation-Date: 2026-06-14 17:56+0200\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n" "Language-Team: LANGUAGE <LL@li.org>\n"
@@ -214,7 +214,7 @@ msgstr ""
msgid "%s already exists?" msgid "%s already exists?"
msgstr "" msgstr ""
#: ./cli/app/new.go:202 #: ./cli/app/new.go:206
#, c-format #, c-format
msgid "%s created (version: %s)" msgid "%s created (version: %s)"
msgstr "" msgstr ""
@@ -299,7 +299,7 @@ msgstr ""
msgid "%s has no published versions?" msgid "%s has no published versions?"
msgstr "" msgstr ""
#: ./cli/app/new.go:311 #: ./cli/app/new.go:315
#, c-format #, c-format
msgid "%s has no secrets to generate, skipping..." msgid "%s has no secrets to generate, skipping..."
msgstr "" msgstr ""
@@ -339,17 +339,17 @@ msgstr ""
msgid "%s is missing the TYPE env var?" msgid "%s is missing the TYPE env var?"
msgstr "" msgstr ""
#: ./cli/app/rollback.go:309 ./cli/app/rollback.go:313 #: ./cli/app/rollback.go:310 ./cli/app/rollback.go:314
#, c-format #, c-format
msgid "%s is not a downgrade for %s?" msgid "%s is not a downgrade for %s?"
msgstr "" msgstr ""
#: ./cli/app/upgrade.go:429 ./cli/app/upgrade.go:433 #: ./cli/app/upgrade.go:430 ./cli/app/upgrade.go:434
#, c-format #, c-format
msgid "%s is not an upgrade for %s?" msgid "%s is not an upgrade for %s?"
msgstr "" msgstr ""
#: ./cli/app/env.go:146 ./cli/app/logs.go:65 ./cli/app/ps.go:62 ./cli/app/restart.go:100 ./cli/app/services.go:55 ./cli/app/undeploy.go:66 ./cli/app/upgrade.go:450 #: ./cli/app/env.go:146 ./cli/app/logs.go:65 ./cli/app/ps.go:62 ./cli/app/restart.go:100 ./cli/app/services.go:55 ./cli/app/undeploy.go:66 ./cli/app/upgrade.go:451
#, c-format #, c-format
msgid "%s is not deployed?" msgid "%s is not deployed?"
msgstr "" msgstr ""
@@ -369,7 +369,7 @@ msgstr ""
msgid "%s missing context, run \"abra server add %s\"?" msgid "%s missing context, run \"abra server add %s\"?"
msgstr "" msgstr ""
#: ./cli/app/deploy.go:194 ./cli/app/upgrade.go:210 #: ./cli/app/deploy.go:195 ./cli/app/upgrade.go:211
#, c-format #, c-format
msgid "%s missing from %s.env" msgid "%s missing from %s.env"
msgstr "" msgstr ""
@@ -404,17 +404,17 @@ msgstr ""
msgid "%s removed from pass store" msgid "%s removed from pass store"
msgstr "" msgstr ""
#: ./cli/app/new.go:220 #: ./cli/app/new.go:224
#, c-format #, c-format
msgid "%s requires secret generation before deploy, run \"abra app secret generate %s --all\"" msgid "%s requires secret generation before deploy, run \"abra app secret generate %s --all\""
msgstr "" msgstr ""
#: ./cli/app/new.go:224 #: ./cli/app/new.go:228
#, c-format #, c-format
msgid "%s requires secret insertion before deploy (#generate=false)" msgid "%s requires secret insertion before deploy (#generate=false)"
msgstr "" msgstr ""
#: ./cli/app/new.go:151 #: ./cli/app/new.go:155
#, c-format #, c-format
msgid "%s sanitised as %s for new app" msgid "%s sanitised as %s for new app"
msgstr "" msgstr ""
@@ -549,12 +549,12 @@ msgstr ""
msgid "%s: waiting %d seconds before next retry" msgid "%s: waiting %d seconds before next retry"
msgstr "" msgstr ""
#: ./cli/app/upgrade.go:424 #: ./cli/app/upgrade.go:425
#, c-format #, c-format
msgid "'%s' is not a known version" msgid "'%s' is not a known version"
msgstr "" msgstr ""
#: ./cli/app/rollback.go:304 ./cli/app/upgrade.go:419 #: ./cli/app/rollback.go:305 ./cli/app/upgrade.go:420
#, c-format #, c-format
msgid "'%s' is not a known version for %s" msgid "'%s' is not a known version for %s"
msgstr "" msgstr ""
@@ -626,7 +626,7 @@ msgstr ""
msgid "Both local recipe and live deployment labels are shown." msgid "Both local recipe and live deployment labels are shown."
msgstr "" msgstr ""
#: ./cli/app/backup.go:319 ./cli/app/backup.go:335 ./cli/app/check.go:95 ./cli/app/cmd.go:285 ./cli/app/cp.go:385 ./cli/app/deploy.go:414 ./cli/app/labels.go:143 ./cli/app/list.go:335 ./cli/app/new.go:407 ./cli/app/ps.go:213 ./cli/app/restart.go:163 ./cli/app/restore.go:138 ./cli/app/secret.go:569 ./cli/app/secret.go:609 ./cli/app/secret.go:633 ./cli/app/secret.go:641 ./cli/catalogue/catalogue.go:318 ./cli/recipe/lint.go:137 #: ./cli/app/backup.go:319 ./cli/app/backup.go:335 ./cli/app/check.go:95 ./cli/app/cmd.go:285 ./cli/app/cp.go:385 ./cli/app/deploy.go:415 ./cli/app/labels.go:143 ./cli/app/list.go:335 ./cli/app/new.go:411 ./cli/app/ps.go:213 ./cli/app/restart.go:163 ./cli/app/restore.go:138 ./cli/app/secret.go:569 ./cli/app/secret.go:609 ./cli/app/secret.go:633 ./cli/app/secret.go:641 ./cli/catalogue/catalogue.go:318 ./cli/recipe/lint.go:137
msgid "C" msgid "C"
msgstr "" msgstr ""
@@ -767,7 +767,7 @@ msgid "Creates a new app from a default recipe.\n"
"on your $PATH." "on your $PATH."
msgstr "" msgstr ""
#: ./cli/app/deploy.go:430 ./cli/app/new.go:383 ./cli/app/rollback.go:361 ./cli/app/upgrade.go:470 #: ./cli/app/deploy.go:431 ./cli/app/new.go:387 ./cli/app/rollback.go:362 ./cli/app/upgrade.go:471
msgid "D" msgid "D"
msgstr "" msgstr ""
@@ -878,7 +878,7 @@ msgid "Generate a report of all managed apps.\n"
"Use \"--status/-S\" flag to query all servers for the live deployment status." "Use \"--status/-S\" flag to query all servers for the live deployment status."
msgstr "" msgstr ""
#: ./cli/app/new.go:317 #: ./cli/app/new.go:321
msgid "Generate app secrets?" msgid "Generate app secrets?"
msgstr "" msgstr ""
@@ -1257,7 +1257,7 @@ msgstr ""
msgid "Run app commands" msgid "Run app commands"
msgstr "" msgstr ""
#: ./cli/app/backup.go:303 ./cli/app/list.go:296 ./cli/app/logs.go:109 ./cli/app/new.go:399 #: ./cli/app/backup.go:303 ./cli/app/list.go:296 ./cli/app/logs.go:109 ./cli/app/new.go:403
msgid "S" msgid "S"
msgstr "" msgstr ""
@@ -1265,7 +1265,7 @@ msgstr ""
msgid "SECRETS" msgid "SECRETS"
msgstr "" msgstr ""
#: ./cli/app/new.go:234 #: ./cli/app/new.go:238
msgid "SECRETS OVERVIEW" msgid "SECRETS OVERVIEW"
msgstr "" msgstr ""
@@ -1298,7 +1298,7 @@ msgstr ""
msgid "STATUS" msgid "STATUS"
msgstr "" msgstr ""
#: ./cli/app/new.go:342 #: ./cli/app/new.go:346
msgid "Select app server:" msgid "Select app server:"
msgstr "" msgstr ""
@@ -1330,7 +1330,7 @@ msgstr ""
msgid "Specify a server name" msgid "Specify a server name"
msgstr "" msgstr ""
#: ./cli/app/new.go:293 #: ./cli/app/new.go:297
msgid "Specify app domain" msgid "Specify app domain"
msgstr "" msgstr ""
@@ -1462,7 +1462,7 @@ msgid "To load completions:\n"
" # and source this file from your PowerShell profile." " # and source this file from your PowerShell profile."
msgstr "" msgstr ""
#: ./cli/app/deploy.go:454 ./cli/app/rollback.go:377 ./cli/app/upgrade.go:494 #: ./cli/app/deploy.go:455 ./cli/app/rollback.go:378 ./cli/app/upgrade.go:495
msgid "U" msgid "U"
msgstr "" msgstr ""
@@ -1835,7 +1835,7 @@ msgstr ""
msgid "attempting to run %s" msgid "attempting to run %s"
msgstr "" msgstr ""
#: ./cli/app/deploy.go:283 ./cli/app/upgrade.go:296 #: ./cli/app/deploy.go:284 ./cli/app/upgrade.go:297
#, c-format #, c-format
msgid "attempting to run post deploy commands, saw: %s" msgid "attempting to run post deploy commands, saw: %s"
msgstr "" msgstr ""
@@ -1865,7 +1865,7 @@ msgstr ""
msgid "autocomplete failed: %s" msgid "autocomplete failed: %s"
msgstr "" msgstr ""
#: ./cli/app/new.go:401 #: ./cli/app/new.go:405
msgid "automatically generate secrets" msgid "automatically generate secrets"
msgstr "" msgstr ""
@@ -1915,7 +1915,7 @@ msgstr ""
#. no spaces in between #. no spaces in between
#. translators: `abra app cp` aliases. use a comma separated list of aliases with #. translators: `abra app cp` aliases. use a comma separated list of aliases with
#. no spaces in between #. no spaces in between
#: ./cli/app/backup.go:148 ./cli/app/cp.go:30 ./cli/app/deploy.go:438 ./cli/app/rollback.go:369 ./cli/app/upgrade.go:478 #: ./cli/app/backup.go:148 ./cli/app/cp.go:30 ./cli/app/deploy.go:439 ./cli/app/rollback.go:370 ./cli/app/upgrade.go:479
msgid "c" msgid "c"
msgstr "" msgstr ""
@@ -1971,7 +1971,7 @@ msgstr ""
msgid "cannot redeploy previous chaos version (%s), did you mean to use \"--chaos\"?" msgid "cannot redeploy previous chaos version (%s), did you mean to use \"--chaos\"?"
msgstr "" msgstr ""
#: ./cli/app/deploy.go:388 #: ./cli/app/deploy.go:389
#, c-format #, c-format
msgid "cannot redeploy previous chaos version (%s), did you mean to use \"--chaos\"?\n" msgid "cannot redeploy previous chaos version (%s), did you mean to use \"--chaos\"?\n"
" to return to a regular release, specify a release tag, commit SHA or use \"--latest\"" " to return to a regular release, specify a release tag, commit SHA or use \"--latest\""
@@ -1990,7 +1990,7 @@ msgstr ""
msgid "cannot use '[secret] [version]' and '--all' together" msgid "cannot use '[secret] [version]' and '--all' together"
msgstr "" msgstr ""
#: ./cli/app/deploy.go:330 #: ./cli/app/deploy.go:331
msgid "cannot use --chaos and --latest together" msgid "cannot use --chaos and --latest together"
msgstr "" msgstr ""
@@ -2014,11 +2014,11 @@ msgstr ""
msgid "cannot use [service] and --all-services/-a together" msgid "cannot use [service] and --all-services/-a together"
msgstr "" msgstr ""
#: ./cli/app/deploy.go:322 ./cli/app/new.go:80 #: ./cli/app/deploy.go:323 ./cli/app/new.go:80
msgid "cannot use [version] and --chaos together" msgid "cannot use [version] and --chaos together"
msgstr "" msgstr ""
#: ./cli/app/deploy.go:326 #: ./cli/app/deploy.go:327
msgid "cannot use [version] and --latest together" msgid "cannot use [version] and --latest together"
msgstr "" msgstr ""
@@ -2054,7 +2054,7 @@ msgstr ""
msgid "cfg" msgid "cfg"
msgstr "" msgstr ""
#: ./cli/app/backup.go:318 ./cli/app/backup.go:334 ./cli/app/check.go:94 ./cli/app/cmd.go:284 ./cli/app/cp.go:384 ./cli/app/deploy.go:413 ./cli/app/labels.go:142 ./cli/app/list.go:334 ./cli/app/new.go:406 ./cli/app/ps.go:212 ./cli/app/restart.go:162 ./cli/app/restore.go:137 ./cli/app/secret.go:568 ./cli/app/secret.go:608 ./cli/app/secret.go:632 ./cli/app/secret.go:640 ./cli/catalogue/catalogue.go:317 ./cli/recipe/lint.go:136 #: ./cli/app/backup.go:318 ./cli/app/backup.go:334 ./cli/app/check.go:94 ./cli/app/cmd.go:284 ./cli/app/cp.go:384 ./cli/app/deploy.go:414 ./cli/app/labels.go:142 ./cli/app/list.go:334 ./cli/app/new.go:410 ./cli/app/ps.go:212 ./cli/app/restart.go:162 ./cli/app/restore.go:137 ./cli/app/secret.go:568 ./cli/app/secret.go:608 ./cli/app/secret.go:632 ./cli/app/secret.go:640 ./cli/catalogue/catalogue.go:317 ./cli/recipe/lint.go:136
msgid "chaos" msgid "chaos"
msgstr "" msgstr ""
@@ -2068,7 +2068,7 @@ msgstr ""
msgid "check <domain> [flags]" msgid "check <domain> [flags]"
msgstr "" msgstr ""
#: ./cli/app/deploy.go:95 ./cli/app/undeploy.go:58 ./cli/app/upgrade.go:442 #: ./cli/app/deploy.go:95 ./cli/app/undeploy.go:58 ./cli/app/upgrade.go:443
#, c-format #, c-format
msgid "checking whether %s is already deployed" msgid "checking whether %s is already deployed"
msgstr "" msgstr ""
@@ -2371,7 +2371,7 @@ msgstr ""
msgid "critical errors present in %s config" msgid "critical errors present in %s config"
msgstr "" msgstr ""
#: ./cli/app/rollback.go:299 #: ./cli/app/rollback.go:300
#, c-format #, c-format
msgid "current deployment '%s' is not a known version for %s" msgid "current deployment '%s' is not a known version for %s"
msgstr "" msgstr ""
@@ -2422,7 +2422,7 @@ msgstr ""
msgid "deploy labels stanza present" msgid "deploy labels stanza present"
msgstr "" msgstr ""
#: ./cli/app/deploy.go:448 #: ./cli/app/deploy.go:449
msgid "deploy latest recipe version" msgid "deploy latest recipe version"
msgstr "" msgstr ""
@@ -2524,11 +2524,11 @@ msgstr ""
msgid "dirty: %v, " msgid "dirty: %v, "
msgstr "" msgstr ""
#: ./cli/app/deploy.go:440 ./cli/app/rollback.go:371 ./cli/app/upgrade.go:480 #: ./cli/app/deploy.go:441 ./cli/app/rollback.go:372 ./cli/app/upgrade.go:481
msgid "disable converge logic checks" msgid "disable converge logic checks"
msgstr "" msgstr ""
#: ./cli/app/deploy.go:432 ./cli/app/rollback.go:363 ./cli/app/upgrade.go:472 #: ./cli/app/deploy.go:433 ./cli/app/rollback.go:364 ./cli/app/upgrade.go:473
msgid "disable public DNS checks" msgid "disable public DNS checks"
msgstr "" msgstr ""
@@ -2544,11 +2544,11 @@ msgstr ""
msgid "docker: is the daemon running / your user has docker permissions?" msgid "docker: is the daemon running / your user has docker permissions?"
msgstr "" msgstr ""
#: ./cli/app/new.go:382 #: ./cli/app/new.go:386
msgid "domain" msgid "domain"
msgstr "" msgstr ""
#: ./cli/app/new.go:385 #: ./cli/app/new.go:389
msgid "domain name for app" msgid "domain name for app"
msgstr "" msgstr ""
@@ -2737,7 +2737,7 @@ msgstr ""
#. translators: `abra recipe fetch` aliases. use a comma separated list of aliases #. translators: `abra recipe fetch` aliases. use a comma separated list of aliases
#. with no spaces in between #. with no spaces in between
#: ./cli/app/deploy.go:422 ./cli/app/env.go:325 ./cli/app/remove.go:163 ./cli/app/rollback.go:353 ./cli/app/secret.go:593 ./cli/app/upgrade.go:462 ./cli/app/volume.go:217 ./cli/recipe/fetch.go:20 ./cli/recipe/fetch.go:138 #: ./cli/app/deploy.go:423 ./cli/app/env.go:325 ./cli/app/remove.go:163 ./cli/app/rollback.go:354 ./cli/app/secret.go:593 ./cli/app/upgrade.go:463 ./cli/app/volume.go:217 ./cli/recipe/fetch.go:20 ./cli/recipe/fetch.go:138
msgid "f" msgid "f"
msgstr "" msgstr ""
@@ -2898,7 +2898,7 @@ msgstr ""
msgid "failed to resize tty, using default size" msgid "failed to resize tty, using default size"
msgstr "" msgstr ""
#: ./cli/app/new.go:134 #: ./cli/app/new.go:138
#, c-format #, c-format
msgid "failed to retrieve latest commit for %s: %s" msgid "failed to retrieve latest commit for %s: %s"
msgstr "" msgstr ""
@@ -2988,7 +2988,7 @@ msgstr ""
msgid "final merged env values for %s are: %s" msgid "final merged env values for %s are: %s"
msgstr "" msgstr ""
#: ./cli/app/deploy.go:421 ./cli/app/env.go:324 ./cli/app/remove.go:162 ./cli/app/rollback.go:352 ./cli/app/upgrade.go:461 ./cli/app/volume.go:216 ./cli/recipe/fetch.go:137 #: ./cli/app/deploy.go:422 ./cli/app/env.go:324 ./cli/app/remove.go:162 ./cli/app/rollback.go:353 ./cli/app/upgrade.go:462 ./cli/app/volume.go:216 ./cli/recipe/fetch.go:137
msgid "force" msgid "force"
msgstr "" msgstr ""
@@ -3202,7 +3202,7 @@ msgstr ""
msgid "id: %s, " msgid "id: %s, "
msgstr "" msgstr ""
#: ./cli/app/backup.go:321 ./cli/app/backup.go:337 ./cli/app/check.go:97 ./cli/app/cmd.go:287 ./cli/app/cp.go:387 ./cli/app/deploy.go:416 ./cli/app/labels.go:145 ./cli/app/list.go:337 ./cli/app/new.go:409 ./cli/app/ps.go:215 ./cli/app/restart.go:165 ./cli/app/restore.go:140 ./cli/app/secret.go:571 ./cli/app/secret.go:611 ./cli/app/secret.go:635 ./cli/app/secret.go:643 ./cli/catalogue/catalogue.go:320 ./cli/recipe/lint.go:139 #: ./cli/app/backup.go:321 ./cli/app/backup.go:337 ./cli/app/check.go:97 ./cli/app/cmd.go:287 ./cli/app/cp.go:387 ./cli/app/deploy.go:417 ./cli/app/labels.go:145 ./cli/app/list.go:337 ./cli/app/new.go:413 ./cli/app/ps.go:215 ./cli/app/restart.go:165 ./cli/app/restore.go:140 ./cli/app/secret.go:571 ./cli/app/secret.go:611 ./cli/app/secret.go:635 ./cli/app/secret.go:643 ./cli/catalogue/catalogue.go:320 ./cli/recipe/lint.go:139
msgid "ignore uncommitted recipes changes" msgid "ignore uncommitted recipes changes"
msgstr "" msgstr ""
@@ -3400,7 +3400,7 @@ msgstr ""
#. no spaces in between #. no spaces in between
#. translators: `abra recipe lint` aliases. use a comma separated list of #. translators: `abra recipe lint` aliases. use a comma separated list of
#. aliases with no spaces in between #. aliases with no spaces in between
#: ./cli/app/cmd.go:261 ./cli/app/deploy.go:446 ./cli/app/logs.go:20 ./cli/recipe/lint.go:17 ./cli/server/add.go:207 #: ./cli/app/cmd.go:261 ./cli/app/deploy.go:447 ./cli/app/logs.go:20 ./cli/recipe/lint.go:17 ./cli/server/add.go:207
msgid "l" msgid "l"
msgstr "" msgstr ""
@@ -3415,7 +3415,7 @@ msgstr ""
msgid "labels <domain> [flags]" msgid "labels <domain> [flags]"
msgstr "" msgstr ""
#: ./cli/app/deploy.go:445 ./cli/app/list.go:186 #: ./cli/app/deploy.go:446 ./cli/app/list.go:186
msgid "latest" msgid "latest"
msgstr "" msgstr ""
@@ -3752,7 +3752,7 @@ msgstr ""
msgid "no containers matching the %v filter found?" msgid "no containers matching the %v filter found?"
msgstr "" msgstr ""
#: ./cli/app/new.go:302 ./cli/internal/validate.go:129 #: ./cli/app/new.go:306 ./cli/internal/validate.go:129
msgid "no domain provided" msgid "no domain provided"
msgstr "" msgstr ""
@@ -3779,7 +3779,7 @@ msgstr ""
msgid "no recipe name provided" msgid "no recipe name provided"
msgstr "" msgstr ""
#: ./cli/app/upgrade.go:241 #: ./cli/app/upgrade.go:242
#, c-format #, c-format
msgid "no release notes for upgrading from %s to %s" msgid "no release notes for upgrading from %s to %s"
msgstr "" msgstr ""
@@ -3810,7 +3810,7 @@ msgstr ""
msgid "no secrets to remove?" msgid "no secrets to remove?"
msgstr "" msgstr ""
#: ./cli/app/new.go:351 ./cli/internal/validate.go:167 #: ./cli/app/new.go:355 ./cli/internal/validate.go:167
msgid "no server provided" msgid "no server provided"
msgstr "" msgstr ""
@@ -3868,11 +3868,11 @@ msgstr ""
msgid "no volumes to remove" msgid "no volumes to remove"
msgstr "" msgstr ""
#: ./cli/app/deploy.go:437 ./cli/app/rollback.go:368 ./cli/app/upgrade.go:477 #: ./cli/app/deploy.go:438 ./cli/app/rollback.go:369 ./cli/app/upgrade.go:478
msgid "no-converge-checks" msgid "no-converge-checks"
msgstr "" msgstr ""
#: ./cli/app/deploy.go:429 ./cli/app/rollback.go:360 ./cli/app/upgrade.go:469 #: ./cli/app/deploy.go:430 ./cli/app/rollback.go:361 ./cli/app/upgrade.go:470
msgid "no-domain-checks" msgid "no-domain-checks"
msgstr "" msgstr ""
@@ -3940,7 +3940,7 @@ msgstr ""
msgid "only show errors" msgid "only show errors"
msgstr "" msgstr ""
#: ./cli/app/upgrade.go:488 #: ./cli/app/upgrade.go:489
msgid "only show release notes" msgid "only show release notes"
msgstr "" msgstr ""
@@ -3952,7 +3952,7 @@ msgstr ""
#. with no spaces in between #. with no spaces in between
#. translators: `abra server prune` aliases. use a comma separated list of #. translators: `abra server prune` aliases. use a comma separated list of
#. aliases with no spaces in between #. aliases with no spaces in between
#: ./cli/app/backup.go:295 ./cli/app/new.go:391 ./cli/app/ps.go:29 ./cli/app/secret.go:561 ./cli/app/secret.go:585 ./cli/app/secret.go:625 ./cli/app/undeploy.go:169 ./cli/catalogue/catalogue.go:294 ./cli/recipe/list.go:112 ./cli/server/prune.go:18 #: ./cli/app/backup.go:295 ./cli/app/new.go:395 ./cli/app/ps.go:29 ./cli/app/secret.go:561 ./cli/app/secret.go:585 ./cli/app/secret.go:625 ./cli/app/undeploy.go:169 ./cli/catalogue/catalogue.go:294 ./cli/recipe/list.go:112 ./cli/server/prune.go:18
msgid "p" msgid "p"
msgstr "" msgstr ""
@@ -3971,27 +3971,27 @@ msgstr ""
msgid "parsed following command arguments: %s" msgid "parsed following command arguments: %s"
msgstr "" msgstr ""
#: ./cli/app/upgrade.go:345 #: ./cli/app/upgrade.go:346
#, c-format #, c-format
msgid "parsing chosen upgrade version failed: %s" msgid "parsing chosen upgrade version failed: %s"
msgstr "" msgstr ""
#: ./cli/app/upgrade.go:389 #: ./cli/app/upgrade.go:390
#, c-format #, c-format
msgid "parsing deployed version failed: %s" msgid "parsing deployed version failed: %s"
msgstr "" msgstr ""
#: ./cli/app/upgrade.go:350 #: ./cli/app/upgrade.go:351
#, c-format #, c-format
msgid "parsing deployment version failed: %s" msgid "parsing deployment version failed: %s"
msgstr "" msgstr ""
#: ./cli/app/upgrade.go:356 ./cli/app/upgrade.go:395 #: ./cli/app/upgrade.go:357 ./cli/app/upgrade.go:396
#, c-format #, c-format
msgid "parsing recipe version failed: %s" msgid "parsing recipe version failed: %s"
msgstr "" msgstr ""
#: ./cli/app/new.go:390 ./cli/app/secret.go:560 ./cli/app/secret.go:584 ./cli/app/secret.go:624 #: ./cli/app/new.go:394 ./cli/app/secret.go:560 ./cli/app/secret.go:584 ./cli/app/secret.go:624
msgid "pass" msgid "pass"
msgstr "" msgstr ""
@@ -4011,7 +4011,7 @@ msgstr ""
msgid "pattern" msgid "pattern"
msgstr "" msgstr ""
#: ./cli/app/deploy.go:424 ./cli/app/env.go:327 ./cli/app/remove.go:165 ./cli/app/rollback.go:355 ./cli/app/upgrade.go:464 ./cli/app/volume.go:219 #: ./cli/app/deploy.go:425 ./cli/app/env.go:327 ./cli/app/remove.go:165 ./cli/app/rollback.go:356 ./cli/app/upgrade.go:465 ./cli/app/volume.go:219
msgid "perform action without further prompt" msgid "perform action without further prompt"
msgstr "" msgstr ""
@@ -4021,22 +4021,22 @@ msgstr ""
msgid "pl,p" msgid "pl,p"
msgstr "" msgstr ""
#: ./cli/app/rollback.go:267 #: ./cli/app/rollback.go:268
#, c-format #, c-format
msgid "please select a downgrade (version: %s):" msgid "please select a downgrade (version: %s):"
msgstr "" msgstr ""
#: ./cli/app/rollback.go:272 #: ./cli/app/rollback.go:273
#, c-format #, c-format
msgid "please select a downgrade (version: %s, chaos: %s):" msgid "please select a downgrade (version: %s, chaos: %s):"
msgstr "" msgstr ""
#: ./cli/app/upgrade.go:312 #: ./cli/app/upgrade.go:313
#, c-format #, c-format
msgid "please select an upgrade (version: %s):" msgid "please select an upgrade (version: %s):"
msgstr "" msgstr ""
#: ./cli/app/upgrade.go:317 #: ./cli/app/upgrade.go:318
#, c-format #, c-format
msgid "please select an upgrade (version: %s, chaos: %s):" msgid "please select an upgrade (version: %s, chaos: %s):"
msgstr "" msgstr ""
@@ -4119,7 +4119,7 @@ msgstr ""
#. with no spaces in between #. with no spaces in between
#. translators: `abra recipe` aliases. use a comma separated list of aliases #. translators: `abra recipe` aliases. use a comma separated list of aliases
#. with no spaces in between #. with no spaces in between
#: ./cli/app/backup.go:327 ./cli/app/list.go:304 ./cli/app/move.go:350 ./cli/app/run.go:23 ./cli/app/upgrade.go:486 ./cli/catalogue/catalogue.go:302 ./cli/recipe/recipe.go:12 ./cli/recipe/release.go:624 #: ./cli/app/backup.go:327 ./cli/app/list.go:304 ./cli/app/move.go:350 ./cli/app/run.go:23 ./cli/app/upgrade.go:487 ./cli/catalogue/catalogue.go:302 ./cli/recipe/recipe.go:12 ./cli/recipe/release.go:624
msgid "r" msgid "r"
msgstr "" msgstr ""
@@ -4239,7 +4239,7 @@ msgstr ""
msgid "release failed. any changes made have been reverted" msgid "release failed. any changes made have been reverted"
msgstr "" msgstr ""
#: ./cli/app/upgrade.go:485 #: ./cli/app/upgrade.go:486
msgid "releasenotes" msgid "releasenotes"
msgstr "" msgstr ""
@@ -4543,7 +4543,7 @@ msgstr ""
msgid "run command locally" msgid "run command locally"
msgstr "" msgstr ""
#: ./cli/app/deploy.go:281 ./cli/app/upgrade.go:293 #: ./cli/app/deploy.go:282 ./cli/app/upgrade.go:294
#, c-format #, c-format
msgid "run the following post-deploy commands: %s" msgid "run the following post-deploy commands: %s"
msgstr "" msgstr ""
@@ -4584,7 +4584,7 @@ msgstr ""
#. no spaces in between #. no spaces in between
#. translators: `abra server` aliases. use a comma separated list of aliases #. translators: `abra server` aliases. use a comma separated list of aliases
#. with no spaces in between #. with no spaces in between
#: ./cli/app/backup.go:198 ./cli/app/backup.go:263 ./cli/app/backup.go:287 ./cli/app/env.go:333 ./cli/app/list.go:327 ./cli/app/logs.go:101 ./cli/app/new.go:368 ./cli/app/restore.go:114 ./cli/app/secret.go:535 ./cli/catalogue/catalogue.go:27 ./cli/catalogue/catalogue.go:310 ./cli/recipe/fetch.go:130 ./cli/server/server.go:12 #: ./cli/app/backup.go:198 ./cli/app/backup.go:263 ./cli/app/backup.go:287 ./cli/app/env.go:333 ./cli/app/list.go:327 ./cli/app/logs.go:101 ./cli/app/new.go:372 ./cli/app/restore.go:114 ./cli/app/secret.go:535 ./cli/catalogue/catalogue.go:27 ./cli/catalogue/catalogue.go:310 ./cli/recipe/fetch.go:130 ./cli/server/server.go:12
msgid "s" msgid "s"
msgstr "" msgstr ""
@@ -4626,12 +4626,12 @@ msgstr ""
msgid "secret not found: %s" msgid "secret not found: %s"
msgstr "" msgstr ""
#: ./cli/app/deploy.go:358 #: ./cli/app/deploy.go:359
#, c-format #, c-format
msgid "secret not generated: %s" msgid "secret not generated: %s"
msgstr "" msgstr ""
#: ./cli/app/deploy.go:356 #: ./cli/app/deploy.go:357
#, c-format #, c-format
msgid "secret not inserted (#generate=false): %s" msgid "secret not inserted (#generate=false): %s"
msgstr "" msgstr ""
@@ -4641,27 +4641,27 @@ msgstr ""
msgid "secret: %s removed" msgid "secret: %s removed"
msgstr "" msgstr ""
#: ./cli/app/backup.go:302 ./cli/app/new.go:398 #: ./cli/app/backup.go:302 ./cli/app/new.go:402
msgid "secrets" msgid "secrets"
msgstr "" msgstr ""
#: ./cli/app/new.go:238 #: ./cli/app/new.go:242
#, c-format #, c-format
msgid "secrets are %s shown again, please save them %s" msgid "secrets are %s shown again, please save them %s"
msgstr "" msgstr ""
#: ./cli/app/deploy.go:306 #: ./cli/app/deploy.go:307
#, c-format #, c-format
msgid "selected latest recipe version: %s (from %d available versions)" msgid "selected latest recipe version: %s (from %d available versions)"
msgstr "" msgstr ""
#: ./cli/app/new.go:121 #: ./cli/app/new.go:125
#, c-format #, c-format
msgid "selected recipe version: %s (from %d available versions)" msgid "selected recipe version: %s (from %d available versions)"
msgstr "" msgstr ""
#. translators: `abra server` command for autocompletion #. translators: `abra server` command for autocompletion
#: ./cli/app/env.go:332 ./cli/app/env.go:339 ./cli/app/list.go:326 ./cli/app/list.go:341 ./cli/app/new.go:367 ./cli/app/new.go:374 ./cli/run.go:101 #: ./cli/app/env.go:332 ./cli/app/env.go:339 ./cli/app/list.go:326 ./cli/app/list.go:341 ./cli/app/new.go:371 ./cli/app/new.go:378 ./cli/run.go:101
msgid "server" msgid "server"
msgstr "" msgstr ""
@@ -4775,7 +4775,7 @@ msgstr ""
msgid "severity" msgid "severity"
msgstr "" msgstr ""
#: ./cli/app/deploy.go:456 ./cli/app/rollback.go:379 ./cli/app/upgrade.go:496 #: ./cli/app/deploy.go:457 ./cli/app/rollback.go:380 ./cli/app/upgrade.go:497
msgid "show all configs & images, including unchanged ones" msgid "show all configs & images, including unchanged ones"
msgstr "" msgstr ""
@@ -4799,7 +4799,7 @@ msgstr ""
msgid "show debug messages" msgid "show debug messages"
msgstr "" msgstr ""
#: ./cli/app/deploy.go:453 ./cli/app/rollback.go:376 ./cli/app/upgrade.go:493 #: ./cli/app/deploy.go:454 ./cli/app/rollback.go:377 ./cli/app/upgrade.go:494
msgid "show-unchanged" msgid "show-unchanged"
msgstr "" msgstr ""
@@ -4807,7 +4807,7 @@ msgstr ""
msgid "since" msgid "since"
msgstr "" msgstr ""
#: ./cli/app/new.go:336 #: ./cli/app/new.go:340
#, c-format #, c-format
msgid "single server detected, choosing %s automatically" msgid "single server detected, choosing %s automatically"
msgstr "" msgstr ""
@@ -4847,11 +4847,11 @@ msgstr ""
msgid "skipping converge logic checks" msgid "skipping converge logic checks"
msgstr "" msgstr ""
#: ./cli/app/deploy.go:208 #: ./cli/app/deploy.go:209
msgid "skipping domain checks" msgid "skipping domain checks"
msgstr "" msgstr ""
#: ./cli/app/deploy.go:205 #: ./cli/app/deploy.go:206
msgid "skipping domain checks, no DOMAIN=... configured" msgid "skipping domain checks, no DOMAIN=... configured"
msgstr "" msgstr ""
@@ -4907,7 +4907,7 @@ msgstr ""
msgid "specify secret value" msgid "specify secret value"
msgstr "" msgstr ""
#: ./cli/app/new.go:370 #: ./cli/app/new.go:374
msgid "specify server for new app" msgid "specify server for new app"
msgstr "" msgstr ""
@@ -4965,7 +4965,7 @@ msgstr ""
msgid "store generated secrets in a local pass store" msgid "store generated secrets in a local pass store"
msgstr "" msgstr ""
#: ./cli/app/new.go:393 #: ./cli/app/new.go:397
msgid "store secrets in a local pass store" msgid "store secrets in a local pass store"
msgstr "" msgstr ""
@@ -5115,7 +5115,7 @@ msgstr ""
msgid "trim input" msgid "trim input"
msgstr "" msgstr ""
#: ./cli/app/new.go:263 ./pkg/app/app.go:141 #: ./cli/app/new.go:267 ./pkg/app/app.go:141
#, c-format #, c-format
msgid "trimming %s to %s to avoid runtime limits" msgid "trimming %s to %s to avoid runtime limits"
msgstr "" msgstr ""
@@ -5653,27 +5653,27 @@ msgstr ""
msgid "version wiped from %s.env" msgid "version wiped from %s.env"
msgstr "" msgstr ""
#: ./cli/app/deploy.go:372 #: ./cli/app/deploy.go:373
#, c-format #, c-format
msgid "version: taking chaos version: %s" msgid "version: taking chaos version: %s"
msgstr "" msgstr ""
#: ./cli/app/deploy.go:398 #: ./cli/app/deploy.go:399
#, c-format #, c-format
msgid "version: taking deployed version: %s" msgid "version: taking deployed version: %s"
msgstr "" msgstr ""
#: ./cli/app/deploy.go:403 #: ./cli/app/deploy.go:404
#, c-format #, c-format
msgid "version: taking new recipe version: %s" msgid "version: taking new recipe version: %s"
msgstr "" msgstr ""
#: ./cli/app/deploy.go:392 #: ./cli/app/deploy.go:393
#, c-format #, c-format
msgid "version: taking version from .env file: %s" msgid "version: taking version from .env file: %s"
msgstr "" msgstr ""
#: ./cli/app/deploy.go:378 #: ./cli/app/deploy.go:379
#, c-format #, c-format
msgid "version: taking version from cli arg: %s" msgid "version: taking version from cli arg: %s"
msgstr "" msgstr ""
@@ -5801,7 +5801,7 @@ msgstr ""
msgid "writer: %v, " msgid "writer: %v, "
msgstr "" msgstr ""
#: ./cli/app/deploy.go:288 ./cli/app/new.go:251 ./cli/app/rollback.go:256 ./cli/app/undeploy.go:120 ./cli/app/upgrade.go:301 #: ./cli/app/deploy.go:289 ./cli/app/new.go:255 ./cli/app/rollback.go:257 ./cli/app/undeploy.go:120 ./cli/app/upgrade.go:302
#, c-format #, c-format
msgid "writing recipe version failed: %s" msgid "writing recipe version failed: %s"
msgstr "" msgstr ""
+92 -92
View File
@@ -2,7 +2,7 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: \n" "Project-Id-Version: \n"
"Report-Msgid-Bugs-To: EMAIL\n" "Report-Msgid-Bugs-To: EMAIL\n"
"POT-Creation-Date: 2026-04-11 11:34+0200\n" "POT-Creation-Date: 2026-06-14 17:56+0200\n"
"PO-Revision-Date: 2026-02-28 13:52+0000\n" "PO-Revision-Date: 2026-02-28 13:52+0000\n"
"Last-Translator: chasqui <chasqui@cryptolab.net>\n" "Last-Translator: chasqui <chasqui@cryptolab.net>\n"
"Language-Team: Spanish <https://translate.coopcloud.tech/projects/co-op-cloud/abra/es/>\n" "Language-Team: Spanish <https://translate.coopcloud.tech/projects/co-op-cloud/abra/es/>\n"
@@ -318,7 +318,7 @@ msgstr "%s ya existe"
msgid "%s already exists?" msgid "%s already exists?"
msgstr "%s ¿ya existe?" msgstr "%s ¿ya existe?"
#: cli/app/new.go:202 #: cli/app/new.go:206
#, c-format #, c-format
msgid "%s created (version: %s)" msgid "%s created (version: %s)"
msgstr "%s creado (versión: %s)" msgstr "%s creado (versión: %s)"
@@ -403,7 +403,7 @@ msgstr "¿%s no tiene una aplicación principal?"
msgid "%s has no published versions?" msgid "%s has no published versions?"
msgstr "¿%s no tiene versiones publicadas?" msgstr "¿%s no tiene versiones publicadas?"
#: cli/app/new.go:311 #: cli/app/new.go:315
#, c-format #, c-format
msgid "%s has no secrets to generate, skipping..." msgid "%s has no secrets to generate, skipping..."
msgstr "%s no tiene secretos para generar, omitiendo..." msgstr "%s no tiene secretos para generar, omitiendo..."
@@ -443,19 +443,19 @@ msgstr "¿%s no tiene un archivo compose.yml o compose.*.yml?"
msgid "%s is missing the TYPE env var?" msgid "%s is missing the TYPE env var?"
msgstr "¿A %s le falta la variable de entorno TYPE?" msgstr "¿A %s le falta la variable de entorno TYPE?"
#: cli/app/rollback.go:309 cli/app/rollback.go:313 #: cli/app/rollback.go:310 cli/app/rollback.go:314
#, c-format #, c-format
msgid "%s is not a downgrade for %s?" msgid "%s is not a downgrade for %s?"
msgstr "¿%s no es un downgrade para %s?" msgstr "¿%s no es un downgrade para %s?"
#: cli/app/upgrade.go:429 cli/app/upgrade.go:433 #: cli/app/upgrade.go:430 cli/app/upgrade.go:434
#, c-format #, c-format
msgid "%s is not an upgrade for %s?" msgid "%s is not an upgrade for %s?"
msgstr "¿%s no es una actualización para %s?" msgstr "¿%s no es una actualización para %s?"
#: cli/app/env.go:146 cli/app/logs.go:65 cli/app/ps.go:62 #: cli/app/env.go:146 cli/app/logs.go:65 cli/app/ps.go:62
#: cli/app/restart.go:100 cli/app/services.go:55 cli/app/undeploy.go:66 #: cli/app/restart.go:100 cli/app/services.go:55 cli/app/undeploy.go:66
#: cli/app/upgrade.go:450 #: cli/app/upgrade.go:451
#, c-format #, c-format
msgid "%s is not deployed?" msgid "%s is not deployed?"
msgstr "¿%s no está desplegado?" msgstr "¿%s no está desplegado?"
@@ -475,7 +475,7 @@ msgstr "%s todavía está desplegado. Ejecuta \"abra aplicacion plegar %s\""
msgid "%s missing context, run \"abra server add %s\"?" msgid "%s missing context, run \"abra server add %s\"?"
msgstr "falta contexto %s, ¿ejecutar \"abra servidor agregar %s\"?" msgstr "falta contexto %s, ¿ejecutar \"abra servidor agregar %s\"?"
#: cli/app/deploy.go:194 cli/app/upgrade.go:210 #: cli/app/deploy.go:195 cli/app/upgrade.go:211
#, c-format #, c-format
msgid "%s missing from %s.env" msgid "%s missing from %s.env"
msgstr "falta %s de %s.env" msgstr "falta %s de %s.env"
@@ -510,17 +510,17 @@ msgstr "notas de la versión %s:"
msgid "%s removed from pass store" msgid "%s removed from pass store"
msgstr "%s eliminado del almacén de contraseñas" msgstr "%s eliminado del almacén de contraseñas"
#: cli/app/new.go:220 #: cli/app/new.go:224
#, c-format #, c-format
msgid "%s requires secret generation before deploy, run \"abra app secret generate %s --all\"" msgid "%s requires secret generation before deploy, run \"abra app secret generate %s --all\""
msgstr "%s requiere generación de secretos antes del despliegue, ejecuta \"abra aplicacion secreto generar %s --all\"" msgstr "%s requiere generación de secretos antes del despliegue, ejecuta \"abra aplicacion secreto generar %s --all\""
#: cli/app/new.go:224 #: cli/app/new.go:228
#, c-format #, c-format
msgid "%s requires secret insertion before deploy (#generate=false)" msgid "%s requires secret insertion before deploy (#generate=false)"
msgstr "%s requiere inserción de secretos antes del despliegue (#generate=false)" msgstr "%s requiere inserción de secretos antes del despliegue (#generate=false)"
#: cli/app/new.go:151 #: cli/app/new.go:155
#, c-format #, c-format
msgid "%s sanitised as %s for new app" msgid "%s sanitised as %s for new app"
msgstr "%s sanitisado como %s para nueva aplicación" msgstr "%s sanitisado como %s para nueva aplicación"
@@ -655,12 +655,12 @@ msgstr "%s: no se puede resolver la dirección IPv4: %s"
msgid "%s: waiting %d seconds before next retry" msgid "%s: waiting %d seconds before next retry"
msgstr "%s: esperando %d segundos antes del siguiente reintento" msgstr "%s: esperando %d segundos antes del siguiente reintento"
#: cli/app/upgrade.go:424 #: cli/app/upgrade.go:425
#, c-format #, c-format
msgid "'%s' is not a known version" msgid "'%s' is not a known version"
msgstr "'%s' no es una versión conocida" msgstr "'%s' no es una versión conocida"
#: cli/app/rollback.go:304 cli/app/upgrade.go:419 #: cli/app/rollback.go:305 cli/app/upgrade.go:420
#, c-format #, c-format
msgid "'%s' is not a known version for %s" msgid "'%s' is not a known version for %s"
msgstr "'%s' no es una versión conocida para %s" msgstr "'%s' no es una versión conocida para %s"
@@ -759,8 +759,8 @@ msgid "Both local recipe and live deployment labels are shown."
msgstr "Se muestran tanto la receta local como las etiquetas del despliegue en vivo." msgstr "Se muestran tanto la receta local como las etiquetas del despliegue en vivo."
#: cli/app/backup.go:319 cli/app/backup.go:335 cli/app/check.go:95 #: cli/app/backup.go:319 cli/app/backup.go:335 cli/app/check.go:95
#: cli/app/cmd.go:285 cli/app/cp.go:385 cli/app/deploy.go:414 #: cli/app/cmd.go:285 cli/app/cp.go:385 cli/app/deploy.go:415
#: cli/app/labels.go:143 cli/app/list.go:335 cli/app/new.go:407 #: cli/app/labels.go:143 cli/app/list.go:335 cli/app/new.go:411
#: cli/app/ps.go:213 cli/app/restart.go:163 cli/app/restore.go:138 #: cli/app/ps.go:213 cli/app/restart.go:163 cli/app/restore.go:138
#: cli/app/secret.go:569 cli/app/secret.go:609 cli/app/secret.go:633 #: cli/app/secret.go:569 cli/app/secret.go:609 cli/app/secret.go:633
#: cli/app/secret.go:641 cli/catalogue/catalogue.go:318 cli/recipe/lint.go:137 #: cli/app/secret.go:641 cli/catalogue/catalogue.go:318 cli/recipe/lint.go:137
@@ -959,8 +959,8 @@ msgstr ""
"en un almacén pass (ver passwordstore.org). El comando pass debe estar \n" "en un almacén pass (ver passwordstore.org). El comando pass debe estar \n"
"disponible en tu $PATH." "disponible en tu $PATH."
#: cli/app/deploy.go:430 cli/app/new.go:383 cli/app/rollback.go:361 #: cli/app/deploy.go:431 cli/app/new.go:387 cli/app/rollback.go:362
#: cli/app/upgrade.go:470 #: cli/app/upgrade.go:471
msgid "D" msgid "D"
msgstr "" msgstr ""
@@ -1112,7 +1112,7 @@ msgstr ""
"\n" "\n"
"Usa la opción \"--estado/-S\" para consultar en todos los servidores sobre el estado de despliegue." "Usa la opción \"--estado/-S\" para consultar en todos los servidores sobre el estado de despliegue."
#: cli/app/new.go:317 #: cli/app/new.go:321
msgid "Generate app secrets?" msgid "Generate app secrets?"
msgstr "¿Generar secretos de la aplicación?" msgstr "¿Generar secretos de la aplicación?"
@@ -1579,7 +1579,7 @@ msgid "Run app commands"
msgstr "Ejecutar 💻 comando dentro una 🚀aplicación" msgstr "Ejecutar 💻 comando dentro una 🚀aplicación"
#: cli/app/backup.go:303 cli/app/list.go:296 cli/app/logs.go:109 #: cli/app/backup.go:303 cli/app/list.go:296 cli/app/logs.go:109
#: cli/app/new.go:399 #: cli/app/new.go:403
msgid "S" msgid "S"
msgstr "" msgstr ""
@@ -1587,7 +1587,7 @@ msgstr ""
msgid "SECRETS" msgid "SECRETS"
msgstr "🥷 SECRETOS" msgstr "🥷 SECRETOS"
#: cli/app/new.go:234 #: cli/app/new.go:238
msgid "SECRETS OVERVIEW" msgid "SECRETS OVERVIEW"
msgstr "RESUMEN DE LOS 🥷 SECRETOS" msgstr "RESUMEN DE LOS 🥷 SECRETOS"
@@ -1621,7 +1621,7 @@ msgstr ""
msgid "STATUS" msgid "STATUS"
msgstr "🩻 ESTADO" msgstr "🩻 ESTADO"
#: cli/app/new.go:342 #: cli/app/new.go:346
msgid "Select app server:" msgid "Select app server:"
msgstr "Seleccionar servidor para la aplicación:" msgstr "Seleccionar servidor para la aplicación:"
@@ -1659,7 +1659,7 @@ msgstr "Especifica un 🌐 nombre de dominio (DNS)"
msgid "Specify a server name" msgid "Specify a server name"
msgstr "Especifica un nombre de servidor" msgstr "Especifica un nombre de servidor"
#: cli/app/new.go:293 #: cli/app/new.go:297
msgid "Specify app domain" msgid "Specify app domain"
msgstr "Especifica un 🌐 nombre de dominio (DNS) para la aplicación" msgstr "Especifica un 🌐 nombre de dominio (DNS) para la aplicación"
@@ -1881,7 +1881,7 @@ msgstr ""
" PS> abra autocomplete powershell > abra.ps1\n" " PS> abra autocomplete powershell > abra.ps1\n"
" # y fuente (source) ese archivo desde tu perfil de PowerShell." " # y fuente (source) ese archivo desde tu perfil de PowerShell."
#: cli/app/deploy.go:454 cli/app/rollback.go:377 cli/app/upgrade.go:494 #: cli/app/deploy.go:455 cli/app/rollback.go:378 cli/app/upgrade.go:495
msgid "U" msgid "U"
msgstr "" msgstr ""
@@ -2368,7 +2368,7 @@ msgstr "Intentando generar y almacenar %s en %s"
msgid "attempting to run %s" msgid "attempting to run %s"
msgstr "Intentando ejecutar %s" msgstr "Intentando ejecutar %s"
#: cli/app/deploy.go:283 cli/app/upgrade.go:296 #: cli/app/deploy.go:284 cli/app/upgrade.go:297
#, c-format #, c-format
msgid "attempting to run post deploy commands, saw: %s" msgid "attempting to run post deploy commands, saw: %s"
msgstr "Intentando ejecutar los comandos posteriores al despliegue: %s" msgstr "Intentando ejecutar los comandos posteriores al despliegue: %s"
@@ -2404,7 +2404,7 @@ msgstr "autocompletar [bash|zsh|fish|powershell]"
msgid "autocomplete failed: %s" msgid "autocomplete failed: %s"
msgstr "autocompletar falló: %s" msgstr "autocompletar falló: %s"
#: cli/app/new.go:401 #: cli/app/new.go:405
msgid "automatically generate secrets" msgid "automatically generate secrets"
msgstr "generar secretos automáticamente" msgstr "generar secretos automáticamente"
@@ -2454,8 +2454,8 @@ msgstr "enlace symlink roto en tus carpetas de configuración de abra: %s"
#. no spaces in between #. no spaces in between
#. translators: `abra app cp` aliases. use a comma separated list of aliases with #. translators: `abra app cp` aliases. use a comma separated list of aliases with
#. no spaces in between #. no spaces in between
#: cli/app/backup.go:148 cli/app/cp.go:30 cli/app/deploy.go:438 #: cli/app/backup.go:148 cli/app/cp.go:30 cli/app/deploy.go:439
#: cli/app/rollback.go:369 cli/app/upgrade.go:478 #: cli/app/rollback.go:370 cli/app/upgrade.go:479
msgid "c" msgid "c"
msgstr "" msgstr ""
@@ -2511,7 +2511,7 @@ msgstr "no se puede obtener la etiqueta %s para %s"
msgid "cannot redeploy previous chaos version (%s), did you mean to use \"--chaos\"?" msgid "cannot redeploy previous chaos version (%s), did you mean to use \"--chaos\"?"
msgstr "no se puede redeplegar la versión anterior de caos (%s), ¿Era tu intención usar \"--caos\"?" msgstr "no se puede redeplegar la versión anterior de caos (%s), ¿Era tu intención usar \"--caos\"?"
#: cli/app/deploy.go:388 #: cli/app/deploy.go:389
#, c-format #, c-format
msgid "" msgid ""
"cannot redeploy previous chaos version (%s), did you mean to use \"--chaos\"?\n" "cannot redeploy previous chaos version (%s), did you mean to use \"--chaos\"?\n"
@@ -2533,7 +2533,7 @@ msgstr "no se puede especificar una etiqueta y un tipo de incremento al mismo ti
msgid "cannot use '[secret] [version]' and '--all' together" msgid "cannot use '[secret] [version]' and '--all' together"
msgstr "no se puede usar '[secreto] [versión]' y '--todos' juntos" msgstr "no se puede usar '[secreto] [versión]' y '--todos' juntos"
#: cli/app/deploy.go:330 #: cli/app/deploy.go:331
msgid "cannot use --chaos and --latest together" msgid "cannot use --chaos and --latest together"
msgstr "no se puede usar --caos y --latest juntos" msgstr "no se puede usar --caos y --latest juntos"
@@ -2557,11 +2557,11 @@ msgstr "no se puede usar [server] y --local al mismo tiempo"
msgid "cannot use [service] and --all-services/-a together" msgid "cannot use [service] and --all-services/-a together"
msgstr "no se puede usar [servicio] y --todos-los-servicios/-a juntos" msgstr "no se puede usar [servicio] y --todos-los-servicios/-a juntos"
#: cli/app/deploy.go:322 cli/app/new.go:80 #: cli/app/deploy.go:323 cli/app/new.go:80
msgid "cannot use [version] and --chaos together" msgid "cannot use [version] and --chaos together"
msgstr "no se puede usar [versión] y --caos juntos" msgstr "no se puede usar [versión] y --caos juntos"
#: cli/app/deploy.go:326 #: cli/app/deploy.go:327
msgid "cannot use [version] and --latest together" msgid "cannot use [version] and --latest together"
msgstr "no se puede usar [versión] y --latest juntos" msgstr "no se puede usar [versión] y --latest juntos"
@@ -2598,8 +2598,8 @@ msgid "cfg"
msgstr "" msgstr ""
#: cli/app/backup.go:318 cli/app/backup.go:334 cli/app/check.go:94 #: cli/app/backup.go:318 cli/app/backup.go:334 cli/app/check.go:94
#: cli/app/cmd.go:284 cli/app/cp.go:384 cli/app/deploy.go:413 #: cli/app/cmd.go:284 cli/app/cp.go:384 cli/app/deploy.go:414
#: cli/app/labels.go:142 cli/app/list.go:334 cli/app/new.go:406 #: cli/app/labels.go:142 cli/app/list.go:334 cli/app/new.go:410
#: cli/app/ps.go:212 cli/app/restart.go:162 cli/app/restore.go:137 #: cli/app/ps.go:212 cli/app/restart.go:162 cli/app/restore.go:137
#: cli/app/secret.go:568 cli/app/secret.go:608 cli/app/secret.go:632 #: cli/app/secret.go:568 cli/app/secret.go:608 cli/app/secret.go:632
#: cli/app/secret.go:640 cli/catalogue/catalogue.go:317 cli/recipe/lint.go:136 #: cli/app/secret.go:640 cli/catalogue/catalogue.go:317 cli/recipe/lint.go:136
@@ -2616,7 +2616,7 @@ msgstr ""
msgid "check <domain> [flags]" msgid "check <domain> [flags]"
msgstr "verificar <aplicacion> [opciones]" msgstr "verificar <aplicacion> [opciones]"
#: cli/app/deploy.go:95 cli/app/undeploy.go:58 cli/app/upgrade.go:442 #: cli/app/deploy.go:95 cli/app/undeploy.go:58 cli/app/upgrade.go:443
#, c-format #, c-format
msgid "checking whether %s is already deployed" msgid "checking whether %s is already deployed"
msgstr "verificando si %s ya está desplegado" msgstr "verificando si %s ya está desplegado"
@@ -2919,7 +2919,7 @@ msgstr "crítico"
msgid "critical errors present in %s config" msgid "critical errors present in %s config"
msgstr "errores críticos presentes en la configuración de %s" msgstr "errores críticos presentes en la configuración de %s"
#: cli/app/rollback.go:299 #: cli/app/rollback.go:300
#, c-format #, c-format
msgid "current deployment '%s' is not a known version for %s" msgid "current deployment '%s' is not a known version for %s"
msgstr "el despliegue actual '%s' no es una versión conocida para %s" msgstr "el despliegue actual '%s' no es una versión conocida para %s"
@@ -2971,7 +2971,7 @@ msgstr "despliegue en proceso 🟠"
msgid "deploy labels stanza present" msgid "deploy labels stanza present"
msgstr "stanza de etiquetas de despliegue presente" msgstr "stanza de etiquetas de despliegue presente"
#: cli/app/deploy.go:448 #: cli/app/deploy.go:449
msgid "deploy latest recipe version" msgid "deploy latest recipe version"
msgstr "desplegar la última versión de la receta" msgstr "desplegar la última versión de la receta"
@@ -3073,11 +3073,11 @@ msgstr "el directorio está vacío: %s"
msgid "dirty: %v, " msgid "dirty: %v, "
msgstr "" msgstr ""
#: cli/app/deploy.go:440 cli/app/rollback.go:371 cli/app/upgrade.go:480 #: cli/app/deploy.go:441 cli/app/rollback.go:372 cli/app/upgrade.go:481
msgid "disable converge logic checks" msgid "disable converge logic checks"
msgstr "desactivar comprobaciones de lógica de convergencia" msgstr "desactivar comprobaciones de lógica de convergencia"
#: cli/app/deploy.go:432 cli/app/rollback.go:363 cli/app/upgrade.go:472 #: cli/app/deploy.go:433 cli/app/rollback.go:364 cli/app/upgrade.go:473
msgid "disable public DNS checks" msgid "disable public DNS checks"
msgstr "desactivar comprobaciones de DNS público" msgstr "desactivar comprobaciones de DNS público"
@@ -3093,11 +3093,11 @@ msgstr "no solicitar un TTY"
msgid "docker: is the daemon running / your user has docker permissions?" msgid "docker: is the daemon running / your user has docker permissions?"
msgstr "docker: ¿está corriendo el daemon / tu usuario tiene permisos de Docker?" msgstr "docker: ¿está corriendo el daemon / tu usuario tiene permisos de Docker?"
#: cli/app/new.go:382 #: cli/app/new.go:386
msgid "domain" msgid "domain"
msgstr "dominio" msgstr "dominio"
#: cli/app/new.go:385 #: cli/app/new.go:389
msgid "domain name for app" msgid "domain name for app"
msgstr "nombre de dominio para la aplicación" msgstr "nombre de dominio para la aplicación"
@@ -3288,8 +3288,8 @@ msgstr "extrayendo secreto %s en %s"
#. translators: `abra recipe fetch` aliases. use a comma separated list of aliases #. translators: `abra recipe fetch` aliases. use a comma separated list of aliases
#. with no spaces in between #. with no spaces in between
#: cli/app/deploy.go:422 cli/app/env.go:325 cli/app/remove.go:163 #: cli/app/deploy.go:423 cli/app/env.go:325 cli/app/remove.go:163
#: cli/app/rollback.go:353 cli/app/secret.go:593 cli/app/upgrade.go:462 #: cli/app/rollback.go:354 cli/app/secret.go:593 cli/app/upgrade.go:463
#: cli/app/volume.go:217 cli/recipe/fetch.go:20 cli/recipe/fetch.go:138 #: cli/app/volume.go:217 cli/recipe/fetch.go:20 cli/recipe/fetch.go:138
msgid "f" msgid "f"
msgstr "" msgstr ""
@@ -3451,7 +3451,7 @@ msgstr "🛑 No se pudo eliminar algunos recursos del stock: %s"
msgid "failed to resize tty, using default size" msgid "failed to resize tty, using default size"
msgstr "🛑 No se pudo redimensionar el TTY; se usará el tamaño predeterminado" msgstr "🛑 No se pudo redimensionar el TTY; se usará el tamaño predeterminado"
#: cli/app/new.go:134 #: cli/app/new.go:138
#, c-format #, c-format
msgid "failed to retrieve latest commit for %s: %s" msgid "failed to retrieve latest commit for %s: %s"
msgstr "🛑 No se pudo obtener el último commit de %s: %s" msgstr "🛑 No se pudo obtener el último commit de %s: %s"
@@ -3541,8 +3541,8 @@ msgstr "Filtrar por receta"
msgid "final merged env values for %s are: %s" msgid "final merged env values for %s are: %s"
msgstr "Los valores finales combinados de entorno para %s son: %s" msgstr "Los valores finales combinados de entorno para %s son: %s"
#: cli/app/deploy.go:421 cli/app/env.go:324 cli/app/remove.go:162 #: cli/app/deploy.go:422 cli/app/env.go:324 cli/app/remove.go:162
#: cli/app/rollback.go:352 cli/app/upgrade.go:461 cli/app/volume.go:216 #: cli/app/rollback.go:353 cli/app/upgrade.go:462 cli/app/volume.go:216
#: cli/recipe/fetch.go:137 #: cli/recipe/fetch.go:137
msgid "force" msgid "force"
msgstr "forzar" msgstr "forzar"
@@ -3758,8 +3758,8 @@ msgid "id: %s, "
msgstr "" msgstr ""
#: cli/app/backup.go:321 cli/app/backup.go:337 cli/app/check.go:97 #: cli/app/backup.go:321 cli/app/backup.go:337 cli/app/check.go:97
#: cli/app/cmd.go:287 cli/app/cp.go:387 cli/app/deploy.go:416 #: cli/app/cmd.go:287 cli/app/cp.go:387 cli/app/deploy.go:417
#: cli/app/labels.go:145 cli/app/list.go:337 cli/app/new.go:409 #: cli/app/labels.go:145 cli/app/list.go:337 cli/app/new.go:413
#: cli/app/ps.go:215 cli/app/restart.go:165 cli/app/restore.go:140 #: cli/app/ps.go:215 cli/app/restart.go:165 cli/app/restore.go:140
#: cli/app/secret.go:571 cli/app/secret.go:611 cli/app/secret.go:635 #: cli/app/secret.go:571 cli/app/secret.go:611 cli/app/secret.go:635
#: cli/app/secret.go:643 cli/catalogue/catalogue.go:320 cli/recipe/lint.go:139 #: cli/app/secret.go:643 cli/catalogue/catalogue.go:320 cli/recipe/lint.go:139
@@ -3960,7 +3960,7 @@ msgstr "versión %s especificada inválida"
#. no spaces in between #. no spaces in between
#. translators: `abra recipe lint` aliases. use a comma separated list of #. translators: `abra recipe lint` aliases. use a comma separated list of
#. aliases with no spaces in between #. aliases with no spaces in between
#: cli/app/cmd.go:261 cli/app/deploy.go:446 cli/app/logs.go:20 #: cli/app/cmd.go:261 cli/app/deploy.go:447 cli/app/logs.go:20
#: cli/recipe/lint.go:17 cli/server/add.go:207 #: cli/recipe/lint.go:17 cli/server/add.go:207
msgid "l" msgid "l"
msgstr "" msgstr ""
@@ -3976,7 +3976,7 @@ msgstr ""
msgid "labels <domain> [flags]" msgid "labels <domain> [flags]"
msgstr "etiquetas <aplicacion> [opciones]" msgstr "etiquetas <aplicacion> [opciones]"
#: cli/app/deploy.go:445 cli/app/list.go:186 #: cli/app/deploy.go:446 cli/app/list.go:186
msgid "latest" msgid "latest"
msgstr "ultima" msgstr "ultima"
@@ -4325,7 +4325,7 @@ msgstr "no hay configuraciones para eliminar"
msgid "no containers matching the %v filter found?" msgid "no containers matching the %v filter found?"
msgstr "no se encontraron contenedores que coincidan con el filtro %v?" msgstr "no se encontraron contenedores que coincidan con el filtro %v?"
#: cli/app/new.go:302 cli/internal/validate.go:129 #: cli/app/new.go:306 cli/internal/validate.go:129
msgid "no domain provided" msgid "no domain provided"
msgstr "no se proporcionó dominio" msgstr "no se proporcionó dominio"
@@ -4352,7 +4352,7 @@ msgstr "¿no existe la receta '%s'?"
msgid "no recipe name provided" msgid "no recipe name provided"
msgstr "no se proporcionó el nombre de la receta" msgstr "no se proporcionó el nombre de la receta"
#: cli/app/upgrade.go:241 #: cli/app/upgrade.go:242
#, fuzzy, c-format #, fuzzy, c-format
msgid "no release notes for upgrading from %s to %s" msgid "no release notes for upgrading from %s to %s"
msgstr "ejecución de prueba: mover la nota de la versión de 'next' a %s" msgstr "ejecución de prueba: mover la nota de la versión de 'next' a %s"
@@ -4383,7 +4383,7 @@ msgstr "no hay secretos para eliminar"
msgid "no secrets to remove?" msgid "no secrets to remove?"
msgstr "¿No hay secretos para eliminar?" msgstr "¿No hay secretos para eliminar?"
#: cli/app/new.go:351 cli/internal/validate.go:167 #: cli/app/new.go:355 cli/internal/validate.go:167
msgid "no server provided" msgid "no server provided"
msgstr "no se proporcionó servidor" msgstr "no se proporcionó servidor"
@@ -4441,11 +4441,11 @@ msgstr "no se eliminaron volúmenes"
msgid "no volumes to remove" msgid "no volumes to remove"
msgstr "no hay volúmenes para eliminar" msgstr "no hay volúmenes para eliminar"
#: cli/app/deploy.go:437 cli/app/rollback.go:368 cli/app/upgrade.go:477 #: cli/app/deploy.go:438 cli/app/rollback.go:369 cli/app/upgrade.go:478
msgid "no-converge-checks" msgid "no-converge-checks"
msgstr "sin-verificaciones-de-convergencia" msgstr "sin-verificaciones-de-convergencia"
#: cli/app/deploy.go:429 cli/app/rollback.go:360 cli/app/upgrade.go:469 #: cli/app/deploy.go:430 cli/app/rollback.go:361 cli/app/upgrade.go:470
msgid "no-domain-checks" msgid "no-domain-checks"
msgstr "sin-verificaciones-de-dominio" msgstr "sin-verificaciones-de-dominio"
@@ -4513,7 +4513,7 @@ msgstr "solo se usaron etiquetas anotadas para la versión de la receta"
msgid "only show errors" msgid "only show errors"
msgstr "mostrar solo errores" msgstr "mostrar solo errores"
#: cli/app/upgrade.go:488 #: cli/app/upgrade.go:489
msgid "only show release notes" msgid "only show release notes"
msgstr "solo mostrar notas de la versión" msgstr "solo mostrar notas de la versión"
@@ -4525,7 +4525,7 @@ msgstr "solo cola de stderr"
#. with no spaces in between #. with no spaces in between
#. translators: `abra server prune` aliases. use a comma separated list of #. translators: `abra server prune` aliases. use a comma separated list of
#. aliases with no spaces in between #. aliases with no spaces in between
#: cli/app/backup.go:295 cli/app/new.go:391 cli/app/ps.go:29 #: cli/app/backup.go:295 cli/app/new.go:395 cli/app/ps.go:29
#: cli/app/secret.go:561 cli/app/secret.go:585 cli/app/secret.go:625 #: cli/app/secret.go:561 cli/app/secret.go:585 cli/app/secret.go:625
#: cli/app/undeploy.go:169 cli/catalogue/catalogue.go:294 #: cli/app/undeploy.go:169 cli/catalogue/catalogue.go:294
#: cli/recipe/list.go:112 cli/server/prune.go:18 #: cli/recipe/list.go:112 cli/server/prune.go:18
@@ -4547,27 +4547,27 @@ msgstr "parseado %s de %s"
msgid "parsed following command arguments: %s" msgid "parsed following command arguments: %s"
msgstr "parseados los siguientes argumentos del comando: %s" msgstr "parseados los siguientes argumentos del comando: %s"
#: cli/app/upgrade.go:345 #: cli/app/upgrade.go:346
#, c-format #, c-format
msgid "parsing chosen upgrade version failed: %s" msgid "parsing chosen upgrade version failed: %s"
msgstr "falló la actualización de versión elegida para el parsing: %s" msgstr "falló la actualización de versión elegida para el parsing: %s"
#: cli/app/upgrade.go:389 #: cli/app/upgrade.go:390
#, c-format #, c-format
msgid "parsing deployed version failed: %s" msgid "parsing deployed version failed: %s"
msgstr "parsing fallido de la versión desplegada: %s" msgstr "parsing fallido de la versión desplegada: %s"
#: cli/app/upgrade.go:350 #: cli/app/upgrade.go:351
#, c-format #, c-format
msgid "parsing deployment version failed: %s" msgid "parsing deployment version failed: %s"
msgstr "parsing fallido de la versión de despliegue: %s" msgstr "parsing fallido de la versión de despliegue: %s"
#: cli/app/upgrade.go:356 cli/app/upgrade.go:395 #: cli/app/upgrade.go:357 cli/app/upgrade.go:396
#, c-format #, c-format
msgid "parsing recipe version failed: %s" msgid "parsing recipe version failed: %s"
msgstr "parsing fallido de la versión de la receta: %s" msgstr "parsing fallido de la versión de la receta: %s"
#: cli/app/new.go:390 cli/app/secret.go:560 cli/app/secret.go:584 #: cli/app/new.go:394 cli/app/secret.go:560 cli/app/secret.go:584
#: cli/app/secret.go:624 #: cli/app/secret.go:624
msgid "pass" msgid "pass"
msgstr "" msgstr ""
@@ -4590,8 +4590,8 @@ msgstr "ruta"
msgid "pattern" msgid "pattern"
msgstr "patrón" msgstr "patrón"
#: cli/app/deploy.go:424 cli/app/env.go:327 cli/app/remove.go:165 #: cli/app/deploy.go:425 cli/app/env.go:327 cli/app/remove.go:165
#: cli/app/rollback.go:355 cli/app/upgrade.go:464 cli/app/volume.go:219 #: cli/app/rollback.go:356 cli/app/upgrade.go:465 cli/app/volume.go:219
msgid "perform action without further prompt" msgid "perform action without further prompt"
msgstr "realizar acción sin más avisos" msgstr "realizar acción sin más avisos"
@@ -4601,22 +4601,22 @@ msgstr "realizar acción sin más avisos"
msgid "pl,p" msgid "pl,p"
msgstr "" msgstr ""
#: cli/app/rollback.go:267 #: cli/app/rollback.go:268
#, c-format #, c-format
msgid "please select a downgrade (version: %s):" msgid "please select a downgrade (version: %s):"
msgstr "por favor, selecciona una versión anterior (downgrade) (versión: %s):" msgstr "por favor, selecciona una versión anterior (downgrade) (versión: %s):"
#: cli/app/rollback.go:272 #: cli/app/rollback.go:273
#, c-format #, c-format
msgid "please select a downgrade (version: %s, chaos: %s):" msgid "please select a downgrade (version: %s, chaos: %s):"
msgstr "por favor, selecciona una versión anterior (downgrade) (versión: %s, caos: %s):" msgstr "por favor, selecciona una versión anterior (downgrade) (versión: %s, caos: %s):"
#: cli/app/upgrade.go:312 #: cli/app/upgrade.go:313
#, c-format #, c-format
msgid "please select an upgrade (version: %s):" msgid "please select an upgrade (version: %s):"
msgstr "por favor, selecciona una actualización (versión: %s):" msgstr "por favor, selecciona una actualización (versión: %s):"
#: cli/app/upgrade.go:317 #: cli/app/upgrade.go:318
#, c-format #, c-format
msgid "please select an upgrade (version: %s, chaos: %s):" msgid "please select an upgrade (version: %s, chaos: %s):"
msgstr "por favor, selecciona una actualización (versión: %s, caos: %s):" msgstr "por favor, selecciona una actualización (versión: %s, caos: %s):"
@@ -4702,7 +4702,7 @@ msgstr "consultando servidores remotos..."
#. translators: `abra recipe` aliases. use a comma separated list of aliases #. translators: `abra recipe` aliases. use a comma separated list of aliases
#. with no spaces in between #. with no spaces in between
#: cli/app/backup.go:327 cli/app/list.go:304 cli/app/move.go:350 #: cli/app/backup.go:327 cli/app/list.go:304 cli/app/move.go:350
#: cli/app/run.go:23 cli/app/upgrade.go:486 cli/catalogue/catalogue.go:302 #: cli/app/run.go:23 cli/app/upgrade.go:487 cli/catalogue/catalogue.go:302
#: cli/recipe/recipe.go:12 cli/recipe/release.go:624 #: cli/recipe/recipe.go:12 cli/recipe/release.go:624
msgid "r" msgid "r"
msgstr "" msgstr ""
@@ -4827,7 +4827,7 @@ msgstr "publicar <receta> [version] [opciones]"
msgid "release failed. any changes made have been reverted" msgid "release failed. any changes made have been reverted"
msgstr "" msgstr ""
#: cli/app/upgrade.go:485 #: cli/app/upgrade.go:486
msgid "releasenotes" msgid "releasenotes"
msgstr "notas de la versión" msgstr "notas de la versión"
@@ -5132,7 +5132,7 @@ msgstr "ejecutar comando como usuario"
msgid "run command locally" msgid "run command locally"
msgstr "ejecutar comando localmente" msgstr "ejecutar comando localmente"
#: cli/app/deploy.go:281 cli/app/upgrade.go:293 #: cli/app/deploy.go:282 cli/app/upgrade.go:294
#, c-format #, c-format
msgid "run the following post-deploy commands: %s" msgid "run the following post-deploy commands: %s"
msgstr "Ejecutar los siguientes comandos después del despliegue: %s" msgstr "Ejecutar los siguientes comandos después del despliegue: %s"
@@ -5175,7 +5175,7 @@ msgstr "ejecutando el comando posterior '%s %s' en el contenedor %s"
#. with no spaces in between #. with no spaces in between
#: cli/app/backup.go:198 cli/app/backup.go:263 cli/app/backup.go:287 #: cli/app/backup.go:198 cli/app/backup.go:263 cli/app/backup.go:287
#: cli/app/env.go:333 cli/app/list.go:327 cli/app/logs.go:101 #: cli/app/env.go:333 cli/app/list.go:327 cli/app/logs.go:101
#: cli/app/new.go:368 cli/app/restore.go:114 cli/app/secret.go:535 #: cli/app/new.go:372 cli/app/restore.go:114 cli/app/secret.go:535
#: cli/catalogue/catalogue.go:27 cli/catalogue/catalogue.go:310 #: cli/catalogue/catalogue.go:27 cli/catalogue/catalogue.go:310
#: cli/recipe/fetch.go:130 cli/server/server.go:12 #: cli/recipe/fetch.go:130 cli/server/server.go:12
msgid "s" msgid "s"
@@ -5219,12 +5219,12 @@ msgstr "datos del secreto no proporcionados en la línea de comandos o stdin, so
msgid "secret not found: %s" msgid "secret not found: %s"
msgstr "secreto no encontrado: %s" msgstr "secreto no encontrado: %s"
#: cli/app/deploy.go:358 #: cli/app/deploy.go:359
#, c-format #, c-format
msgid "secret not generated: %s" msgid "secret not generated: %s"
msgstr "secreto no generado: %s" msgstr "secreto no generado: %s"
#: cli/app/deploy.go:356 #: cli/app/deploy.go:357
#, c-format #, c-format
msgid "secret not inserted (#generate=false): %s" msgid "secret not inserted (#generate=false): %s"
msgstr "secreto no insertado (#generate=false): %s" msgstr "secreto no insertado (#generate=false): %s"
@@ -5234,28 +5234,28 @@ msgstr "secreto no insertado (#generate=false): %s"
msgid "secret: %s removed" msgid "secret: %s removed"
msgstr "secreto: %s eliminado" msgstr "secreto: %s eliminado"
#: cli/app/backup.go:302 cli/app/new.go:398 #: cli/app/backup.go:302 cli/app/new.go:402
msgid "secrets" msgid "secrets"
msgstr "secretos" msgstr "secretos"
#: cli/app/new.go:238 #: cli/app/new.go:242
#, c-format #, c-format
msgid "secrets are %s shown again, please save them %s" msgid "secrets are %s shown again, please save them %s"
msgstr "los secretos se muestran %s nuevamente, por favor guárdalos %s" msgstr "los secretos se muestran %s nuevamente, por favor guárdalos %s"
#: cli/app/deploy.go:306 #: cli/app/deploy.go:307
#, c-format #, c-format
msgid "selected latest recipe version: %s (from %d available versions)" msgid "selected latest recipe version: %s (from %d available versions)"
msgstr "versión de receta más reciente seleccionada: %s (de %d versiones disponibles)" msgstr "versión de receta más reciente seleccionada: %s (de %d versiones disponibles)"
#: cli/app/new.go:121 #: cli/app/new.go:125
#, c-format #, c-format
msgid "selected recipe version: %s (from %d available versions)" msgid "selected recipe version: %s (from %d available versions)"
msgstr "versión de receta seleccionada: %s (de %d versiones disponibles)" msgstr "versión de receta seleccionada: %s (de %d versiones disponibles)"
#. translators: `abra server` command for autocompletion #. translators: `abra server` command for autocompletion
#: cli/app/env.go:332 cli/app/env.go:339 cli/app/list.go:326 #: cli/app/env.go:332 cli/app/env.go:339 cli/app/list.go:326
#: cli/app/list.go:341 cli/app/new.go:367 cli/app/new.go:374 cli/run.go:101 #: cli/app/list.go:341 cli/app/new.go:371 cli/app/new.go:378 cli/run.go:101
msgid "server" msgid "server"
msgstr "servidor" msgstr "servidor"
@@ -5369,7 +5369,7 @@ msgstr "establecer referencia: %s"
msgid "severity" msgid "severity"
msgstr "gravedad" msgstr "gravedad"
#: cli/app/deploy.go:456 cli/app/rollback.go:379 cli/app/upgrade.go:496 #: cli/app/deploy.go:457 cli/app/rollback.go:380 cli/app/upgrade.go:497
msgid "show all configs & images, including unchanged ones" msgid "show all configs & images, including unchanged ones"
msgstr "mostrar todas las configuraciones e imágenes, incluidas las no cambiadas" msgstr "mostrar todas las configuraciones e imágenes, incluidas las no cambiadas"
@@ -5393,7 +5393,7 @@ msgstr "mostrar aplicaciones de un servidor específico"
msgid "show debug messages" msgid "show debug messages"
msgstr "mostrar mensajes para el debugeo" msgstr "mostrar mensajes para el debugeo"
#: cli/app/deploy.go:453 cli/app/rollback.go:376 cli/app/upgrade.go:493 #: cli/app/deploy.go:454 cli/app/rollback.go:377 cli/app/upgrade.go:494
msgid "show-unchanged" msgid "show-unchanged"
msgstr "mostrar-sin-cambios" msgstr "mostrar-sin-cambios"
@@ -5401,7 +5401,7 @@ msgstr "mostrar-sin-cambios"
msgid "since" msgid "since"
msgstr "desde" msgstr "desde"
#: cli/app/new.go:336 #: cli/app/new.go:340
#, c-format #, c-format
msgid "single server detected, choosing %s automatically" msgid "single server detected, choosing %s automatically"
msgstr "se ha detectado un único servidor, eligiendo %s automáticamente" msgstr "se ha detectado un único servidor, eligiendo %s automáticamente"
@@ -5441,11 +5441,11 @@ msgstr "omitiendo según lo solicitado, plegado aún está en progreso 🟠"
msgid "skipping converge logic checks" msgid "skipping converge logic checks"
msgstr "omitiendo comprobaciones de lógica de convergencia" msgstr "omitiendo comprobaciones de lógica de convergencia"
#: cli/app/deploy.go:208 #: cli/app/deploy.go:209
msgid "skipping domain checks" msgid "skipping domain checks"
msgstr "omitiendo comprobaciones de dominio" msgstr "omitiendo comprobaciones de dominio"
#: cli/app/deploy.go:205 #: cli/app/deploy.go:206
msgid "skipping domain checks, no DOMAIN=... configured" msgid "skipping domain checks, no DOMAIN=... configured"
msgstr "omitiendo comprobaciones de dominio, no DOMAIN=... configurado" msgstr "omitiendo comprobaciones de dominio, no DOMAIN=... configurado"
@@ -5501,7 +5501,7 @@ msgstr "especificar archivo de secreto"
msgid "specify secret value" msgid "specify secret value"
msgstr "especificar valor de secreto" msgstr "especificar valor de secreto"
#: cli/app/new.go:370 #: cli/app/new.go:374
msgid "specify server for new app" msgid "specify server for new app"
msgstr "especificar servidor para la nueva aplicación" msgstr "especificar servidor para la nueva aplicación"
@@ -5559,7 +5559,7 @@ msgstr ""
msgid "store generated secrets in a local pass store" msgid "store generated secrets in a local pass store"
msgstr "almacenar secretos generados en un almacén de contraseñas local" msgstr "almacenar secretos generados en un almacén de contraseñas local"
#: cli/app/new.go:393 #: cli/app/new.go:397
msgid "store secrets in a local pass store" msgid "store secrets in a local pass store"
msgstr "almacenar secretos en un almacén de contraseñas local" msgstr "almacenar secretos en un almacén de contraseñas local"
@@ -5710,7 +5710,7 @@ msgstr "recortar"
msgid "trim input" msgid "trim input"
msgstr "recortar entrada" msgstr "recortar entrada"
#: cli/app/new.go:263 pkg/app/app.go:141 #: cli/app/new.go:267 pkg/app/app.go:141
#, c-format #, c-format
msgid "trimming %s to %s to avoid runtime limits" msgid "trimming %s to %s to avoid runtime limits"
msgstr "recortando %s a %s para evitar límites de tiempo de ejecución" msgstr "recortando %s a %s para evitar límites de tiempo de ejecución"
@@ -6255,27 +6255,27 @@ msgstr "la versión parece inválida: %s"
msgid "version wiped from %s.env" msgid "version wiped from %s.env"
msgstr "versión eliminada de %s.env" msgstr "versión eliminada de %s.env"
#: cli/app/deploy.go:372 #: cli/app/deploy.go:373
#, c-format #, c-format
msgid "version: taking chaos version: %s" msgid "version: taking chaos version: %s"
msgstr "versión: tomando la versión de caos: %s" msgstr "versión: tomando la versión de caos: %s"
#: cli/app/deploy.go:398 #: cli/app/deploy.go:399
#, c-format #, c-format
msgid "version: taking deployed version: %s" msgid "version: taking deployed version: %s"
msgstr "versión: tomando la versión desplegada: %s" msgstr "versión: tomando la versión desplegada: %s"
#: cli/app/deploy.go:403 #: cli/app/deploy.go:404
#, c-format #, c-format
msgid "version: taking new recipe version: %s" msgid "version: taking new recipe version: %s"
msgstr "versión: tomando la nueva versión de la receta: %s" msgstr "versión: tomando la nueva versión de la receta: %s"
#: cli/app/deploy.go:392 #: cli/app/deploy.go:393
#, c-format #, c-format
msgid "version: taking version from .env file: %s" msgid "version: taking version from .env file: %s"
msgstr "versión: tomando la versión desde el archivo .env: %s" msgstr "versión: tomando la versión desde el archivo .env: %s"
#: cli/app/deploy.go:378 #: cli/app/deploy.go:379
#, c-format #, c-format
msgid "version: taking version from cli arg: %s" msgid "version: taking version from cli arg: %s"
msgstr "versión: tomando la versión desde el argumento de la línea de comandos: %s" msgstr "versión: tomando la versión desde el argumento de la línea de comandos: %s"
@@ -6406,8 +6406,8 @@ msgstr "el directorio de trabajo no está limpio en %s, abortando"
msgid "writer: %v, " msgid "writer: %v, "
msgstr "" msgstr ""
#: cli/app/deploy.go:288 cli/app/new.go:251 cli/app/rollback.go:256 #: cli/app/deploy.go:289 cli/app/new.go:255 cli/app/rollback.go:257
#: cli/app/undeploy.go:120 cli/app/upgrade.go:301 #: cli/app/undeploy.go:120 cli/app/upgrade.go:302
#, c-format #, c-format
msgid "writing recipe version failed: %s" msgid "writing recipe version failed: %s"
msgstr "escritura de la versión de la receta fallida: %s" msgstr "escritura de la versión de la receta fallida: %s"
+1 -1
View File
@@ -3,7 +3,7 @@ version: "3.8"
services: services:
app: app:
image: nginx:1.21.0 image: nginx:1.31.2
secrets: secrets:
- test_pass_one - test_pass_one
- test_pass_two - test_pass_two
+20
View File
@@ -61,6 +61,16 @@ teardown(){
run grep -q "TYPE=$TEST_RECIPE:0.3.0+1.21.0" \ run grep -q "TYPE=$TEST_RECIPE:0.3.0+1.21.0" \
"$ABRA_DIR/servers/$TEST_SERVER/$TEST_APP_DOMAIN.env" "$ABRA_DIR/servers/$TEST_SERVER/$TEST_APP_DOMAIN.env"
assert_success assert_success
# 0.3.0-only
run grep -q "NETWORK_WITH_COMMENT=BAZ" \
"$ABRA_DIR/servers/$TEST_SERVER/$TEST_APP_DOMAIN.env"
assert_success
# latest-only
run grep -q "TIMEOUT=120" \
"$ABRA_DIR/servers/$TEST_SERVER/$TEST_APP_DOMAIN.env"
assert_failure
} }
@test "ensure recipe is up-to-date" { @test "ensure recipe is up-to-date" {
@@ -100,6 +110,16 @@ teardown(){
run grep -q "TYPE=$TEST_RECIPE:${tagHash}" \ run grep -q "TYPE=$TEST_RECIPE:${tagHash}" \
"$ABRA_DIR/servers/$TEST_SERVER/$TEST_APP_DOMAIN.env" "$ABRA_DIR/servers/$TEST_SERVER/$TEST_APP_DOMAIN.env"
assert_success assert_success
# 0.3.0-only
run grep -q "NETWORK_WITH_COMMENT=BAZ" \
"$ABRA_DIR/servers/$TEST_SERVER/$TEST_APP_DOMAIN.env"
assert_success
# latest-only
run grep -q "TIMEOUT=120" \
"$ABRA_DIR/servers/$TEST_SERVER/$TEST_APP_DOMAIN.env"
assert_failure
} }
@test "does not overwrite existing env files" { @test "does not overwrite existing env files" {
+1 -1
View File
@@ -3,7 +3,7 @@ version: "3.8"
services: services:
app: app:
image: nginx:1.29.0 image: nginx:1.31.2
networks: networks:
- proxy - proxy
deploy: deploy:
+3 -3
View File
@@ -3,16 +3,16 @@ kind: pipeline
name: coopcloud.tech/tagcmp name: coopcloud.tech/tagcmp
steps: steps:
- name: gofmt - name: gofmt
image: golang:1.21 image: golang:1.26
commands: commands:
- test -z "$(gofmt -l .)" - test -z "$(gofmt -l .)"
- name: go build - name: go build
image: golang:1.21 image: golang:1.26
commands: commands:
- go build -v . - go build -v .
- name: go test - name: go test
image: golang:1.21 image: golang:1.26
commands: commands:
- go test . -cover - go test . -cover
+6
View File
@@ -0,0 +1,6 @@
{
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
"extends": [
"config:recommended"
]
}
+1 -1
View File
@@ -1,7 +1,7 @@
# syntax=docker/dockerfile:1 # syntax=docker/dockerfile:1
ARG GO_VERSION=1.20.8 ARG GO_VERSION=1.20.8
ARG ALPINE_VERSION=3.18 ARG ALPINE_VERSION=3.24
ARG XX_VERSION=1.2.1 ARG XX_VERSION=1.2.1
FROM --platform=$BUILDPLATFORM tonistiigi/xx:${XX_VERSION} AS xx FROM --platform=$BUILDPLATFORM tonistiigi/xx:${XX_VERSION} AS xx
+4
View File
@@ -5,6 +5,10 @@ Billy implements an interface based on the `os` standard library, allowing to de
Billy was born as part of [go-git/go-git](https://github.com/go-git/go-git) project. Billy was born as part of [go-git/go-git](https://github.com/go-git/go-git) project.
## Version support
go-billy v5 is in maintenance mode. Users should upgrade to [go-billy v6](https://pkg.go.dev/github.com/go-git/go-billy/v6) where possible.
## Installation ## Installation
```go ```go
+198 -10
View File
@@ -3,19 +3,25 @@ package chroot
import ( import (
"errors" "errors"
"os" "os"
"path"
"path/filepath" "path/filepath"
"strings" "strings"
"syscall"
"github.com/go-git/go-billy/v5" "github.com/go-git/go-billy/v5"
"github.com/go-git/go-billy/v5/helper/polyfill" "github.com/go-git/go-billy/v5/helper/polyfill"
) )
// ChrootHelper is a helper to implement billy.Chroot. // ChrootHelper is a helper to implement billy.Chroot.
// It is not a security boundary, callers that need containment should use a
// filesystem implementation that enforces paths at the OS boundary instead.
type ChrootHelper struct { type ChrootHelper struct {
underlying billy.Filesystem underlying billy.Filesystem
base string base string
} }
const maxFollowedSymlinks = 8 // Aligns with POSIX_SYMLOOP_MAX
// New creates a new filesystem wrapping up the given 'fs'. // New creates a new filesystem wrapping up the given 'fs'.
// The created filesystem has its base in the given ChrootHelperectory of the // The created filesystem has its base in the given ChrootHelperectory of the
// underlying filesystem. // underlying filesystem.
@@ -34,15 +40,184 @@ func (fs *ChrootHelper) underlyingPath(filename string) (string, error) {
return fs.Join(fs.Root(), filename), nil return fs.Join(fs.Root(), filename), nil
} }
func isCrossBoundaries(path string) bool { func (fs *ChrootHelper) followedPath(filename string, followFinal bool, op string) (string, error) {
path = filepath.ToSlash(path) fullpath, err := fs.underlyingPath(filename)
path = filepath.Clean(path) if err != nil {
return "", err
}
return strings.HasPrefix(path, ".."+string(filepath.Separator)) sl, ok := fs.underlying.(billy.Symlink)
if !ok {
return fullpath, nil
}
rel, err := fs.relativeToRoot(fullpath)
if err != nil {
return "", err
}
fullpath, err = fs.resolveFollowedPath(rel, followFinal, op, sl)
if errors.Is(err, billy.ErrNotSupported) {
return fs.underlyingPath(filename)
}
return fullpath, err
}
func (fs *ChrootHelper) resolveFollowedPath(rel string, followFinal bool, op string, sl billy.Symlink) (string, error) {
if rel == "" {
return fs.resolveFollowedRoot(followFinal, op, sl)
}
parts := splitRelativePath(rel)
resolved := ""
followed := 0
for len(parts) > 0 {
part := parts[0]
parts = parts[1:]
currentRel := joinRelativePath(resolved, part)
currentPath := fs.Join(fs.Root(), currentRel)
if len(parts) == 0 && !followFinal {
return currentPath, nil
}
fi, err := sl.Lstat(currentPath)
if err != nil {
if os.IsNotExist(err) {
return fs.Join(fs.Root(), joinRelativePath(append([]string{currentRel}, parts...)...)), nil
}
return "", err
}
if fi.Mode()&os.ModeSymlink == 0 {
resolved = currentRel
continue
}
followed++
if followed > maxFollowedSymlinks {
return "", symlinkLoopError(op, currentPath)
}
target, err := sl.Readlink(currentPath)
if err != nil {
return "", err
}
targetRel, err := fs.linkTargetRel(currentPath, target)
if err != nil {
return "", err
}
if targetRel == currentRel {
return "", symlinkLoopError(op, currentPath)
}
parts = append(splitRelativePath(targetRel), parts...)
resolved = ""
}
return fs.Join(fs.Root(), resolved), nil
}
func symlinkLoopError(op, path string) error {
return &os.PathError{Op: op, Path: path, Err: syscall.ELOOP}
}
func (fs *ChrootHelper) resolveFollowedRoot(followFinal bool, op string, sl billy.Symlink) (string, error) {
root := fs.Join(fs.Root(), "")
if !followFinal {
return root, nil
}
fi, err := sl.Lstat(root)
if err != nil {
if os.IsNotExist(err) {
return root, nil
}
return "", err
}
if fi.Mode()&os.ModeSymlink == 0 {
return root, nil
}
target, err := sl.Readlink(root)
if err != nil {
return "", err
}
targetRel, err := fs.linkTargetRel(root, target)
if err != nil {
return root, err
}
if targetRel == "" {
return "", symlinkLoopError(op, root)
}
return fs.resolveFollowedPath(targetRel, followFinal, op, sl)
}
func (fs *ChrootHelper) relativeToRoot(filename string) (string, error) {
rel, err := filepath.Rel(filepath.Clean(fs.Root()), filepath.Clean(filename))
if err != nil || isCrossBoundaries(rel) {
return "", billy.ErrCrossedBoundary
}
if rel == "." {
return "", nil
}
return rel, nil
}
func (fs *ChrootHelper) linkTargetRel(linkPath, target string) (string, error) {
target = filepath.FromSlash(target)
if filepath.IsAbs(target) || strings.HasPrefix(target, string(filepath.Separator)) {
return fs.relativeToRoot(target)
}
return fs.relativeToRoot(fs.Join(filepath.Dir(linkPath), target))
}
func splitRelativePath(filename string) []string {
filename = filepath.Clean(filename)
if filename == "" || filename == "." {
return nil
}
return strings.Split(filepath.ToSlash(filename), "/")
}
func joinRelativePath(elem ...string) string {
parts := make([]string, 0, len(elem))
for _, part := range elem {
if part == "" || part == "." {
continue
}
parts = append(parts, part)
}
if len(parts) == 0 {
return ""
}
return filepath.Join(parts...)
}
func isCreateExclusive(flag int) bool {
return flag&os.O_CREATE != 0 && flag&os.O_EXCL != 0
}
func isCrossBoundaries(name string) bool {
name = filepath.ToSlash(name)
name = strings.TrimLeft(name, "/")
name = path.Clean(name)
return name == ".." || strings.HasPrefix(name, "../")
} }
func (fs *ChrootHelper) Create(filename string) (billy.File, error) { func (fs *ChrootHelper) Create(filename string) (billy.File, error) {
fullpath, err := fs.underlyingPath(filename) fullpath, err := fs.followedPath(filename, true, "create")
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -56,7 +231,7 @@ func (fs *ChrootHelper) Create(filename string) (billy.File, error) {
} }
func (fs *ChrootHelper) Open(filename string) (billy.File, error) { func (fs *ChrootHelper) Open(filename string) (billy.File, error) {
fullpath, err := fs.underlyingPath(filename) fullpath, err := fs.followedPath(filename, true, "open")
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -70,7 +245,7 @@ func (fs *ChrootHelper) Open(filename string) (billy.File, error) {
} }
func (fs *ChrootHelper) OpenFile(filename string, flag int, mode os.FileMode) (billy.File, error) { func (fs *ChrootHelper) OpenFile(filename string, flag int, mode os.FileMode) (billy.File, error) {
fullpath, err := fs.underlyingPath(filename) fullpath, err := fs.followedPath(filename, !isCreateExclusive(flag), "open")
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -84,12 +259,16 @@ func (fs *ChrootHelper) OpenFile(filename string, flag int, mode os.FileMode) (b
} }
func (fs *ChrootHelper) Stat(filename string) (os.FileInfo, error) { func (fs *ChrootHelper) Stat(filename string) (os.FileInfo, error) {
fullpath, err := fs.underlyingPath(filename) fullpath, err := fs.followedPath(filename, true, "stat")
if err != nil { if err != nil {
return nil, err return nil, err
} }
return fs.underlying.Stat(fullpath) fi, err := fs.underlying.Stat(fullpath)
if err != nil {
return nil, err
}
return fileInfo{FileInfo: fi, name: filepath.Base(filename)}, nil
} }
func (fs *ChrootHelper) Rename(from, to string) error { func (fs *ChrootHelper) Rename(from, to string) error {
@@ -135,7 +314,7 @@ func (fs *ChrootHelper) TempFile(dir, prefix string) (billy.File, error) {
} }
func (fs *ChrootHelper) ReadDir(path string) ([]os.FileInfo, error) { func (fs *ChrootHelper) ReadDir(path string) ([]os.FileInfo, error) {
fullpath, err := fs.underlyingPath(path) fullpath, err := fs.followedPath(path, true, "readdir")
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -241,6 +420,11 @@ type file struct {
name string name string
} }
type fileInfo struct {
os.FileInfo
name string
}
func newFile(fs billy.Filesystem, f billy.File, filename string) billy.File { func newFile(fs billy.Filesystem, f billy.File, filename string) billy.File {
filename = fs.Join(fs.Root(), filename) filename = fs.Join(fs.Root(), filename)
filename, _ = filepath.Rel(fs.Root(), filename) filename, _ = filepath.Rel(fs.Root(), filename)
@@ -254,3 +438,7 @@ func newFile(fs billy.Filesystem, f billy.File, filename string) billy.File {
func (f *file) Name() string { func (f *file) Name() string {
return f.name return f.name
} }
func (fi fileInfo) Name() string {
return fi.name
}
+5
View File
@@ -24,6 +24,9 @@ var Default = &ChrootOS{}
// New returns a new OS filesystem. // New returns a new OS filesystem.
// By default paths are deduplicated, but still enforced // By default paths are deduplicated, but still enforced
// under baseDir. For more info refer to WithDeduplicatePath. // under baseDir. For more info refer to WithDeduplicatePath.
//
// New returns ChrootOS by default for v5 compatibility. Users should prefer
// New with WithBoundOS.
func New(baseDir string, opts ...Option) billy.Filesystem { func New(baseDir string, opts ...Option) billy.Filesystem {
o := &options{ o := &options{
deduplicatePath: true, deduplicatePath: true,
@@ -47,6 +50,8 @@ func WithBoundOS() Option {
} }
// WithChrootOS returns the option of using a Chroot filesystem OS. // WithChrootOS returns the option of using a Chroot filesystem OS.
//
// Deprecated: use WithBoundOS instead.
func WithChrootOS() Option { func WithChrootOS() Option {
return func(o *options) { return func(o *options) {
o.Type = ChrootOSFS o.Type = ChrootOSFS
+85 -15
View File
@@ -20,6 +20,7 @@
package osfs package osfs
import ( import (
"errors"
"fmt" "fmt"
"os" "os"
"path/filepath" "path/filepath"
@@ -29,6 +30,31 @@ import (
"github.com/go-git/go-billy/v5" "github.com/go-git/go-billy/v5"
) )
var (
// ErrBaseDirCannotBeRemoved is returned when removing the BoundOS base dir.
ErrBaseDirCannotBeRemoved = errors.New("base dir cannot be removed")
// ErrBaseDirCannotBeRenamed is returned when renaming the BoundOS base dir.
ErrBaseDirCannotBeRenamed = errors.New("base dir cannot be renamed")
dotPrefixes = dotPathPrefixes()
dotSeparators = dotPathSeparators()
)
func dotPathPrefixes() []string {
if filepath.Separator == '\\' {
return []string{"./", ".\\"}
}
return []string{"./"}
}
func dotPathSeparators() string {
if filepath.Separator == '\\' {
return `/\`
}
return `/`
}
// BoundOS is a fs implementation based on the OS filesystem which is bound to // BoundOS is a fs implementation based on the OS filesystem which is bound to
// a base dir. // a base dir.
// Prefer this fs implementation over ChrootOS. // Prefer this fs implementation over ChrootOS.
@@ -54,6 +80,7 @@ func (fs *BoundOS) Create(filename string) (billy.File, error) {
} }
func (fs *BoundOS) OpenFile(filename string, flag int, perm os.FileMode) (billy.File, error) { func (fs *BoundOS) OpenFile(filename string, flag int, perm os.FileMode) (billy.File, error) {
filename = fs.expandDot(filename)
fn, err := fs.abs(filename) fn, err := fs.abs(filename)
if err != nil { if err != nil {
return nil, err return nil, err
@@ -62,6 +89,7 @@ func (fs *BoundOS) OpenFile(filename string, flag int, perm os.FileMode) (billy.
} }
func (fs *BoundOS) ReadDir(path string) ([]os.FileInfo, error) { func (fs *BoundOS) ReadDir(path string) ([]os.FileInfo, error) {
path = fs.expandDot(path)
dir, err := fs.abs(path) dir, err := fs.abs(path)
if err != nil { if err != nil {
return nil, err return nil, err
@@ -71,6 +99,12 @@ func (fs *BoundOS) ReadDir(path string) ([]os.FileInfo, error) {
} }
func (fs *BoundOS) Rename(from, to string) error { func (fs *BoundOS) Rename(from, to string) error {
if fs.isBaseDir(from) {
return ErrBaseDirCannotBeRenamed
}
from = fs.expandDot(from)
to = fs.expandDot(to)
f, err := fs.abs(from) f, err := fs.abs(from)
if err != nil { if err != nil {
return err return err
@@ -89,6 +123,7 @@ func (fs *BoundOS) Rename(from, to string) error {
} }
func (fs *BoundOS) MkdirAll(path string, perm os.FileMode) error { func (fs *BoundOS) MkdirAll(path string, perm os.FileMode) error {
path = fs.expandDot(path)
dir, err := fs.abs(path) dir, err := fs.abs(path)
if err != nil { if err != nil {
return err return err
@@ -101,6 +136,7 @@ func (fs *BoundOS) Open(filename string) (billy.File, error) {
} }
func (fs *BoundOS) Stat(filename string) (os.FileInfo, error) { func (fs *BoundOS) Stat(filename string) (os.FileInfo, error) {
filename = fs.expandDot(filename)
filename, err := fs.abs(filename) filename, err := fs.abs(filename)
if err != nil { if err != nil {
return nil, err return nil, err
@@ -109,6 +145,11 @@ func (fs *BoundOS) Stat(filename string) (os.FileInfo, error) {
} }
func (fs *BoundOS) Remove(filename string) error { func (fs *BoundOS) Remove(filename string) error {
if fs.isBaseDir(filename) {
return ErrBaseDirCannotBeRemoved
}
filename = fs.expandDot(filename)
fn, err := fs.abs(filename) fn, err := fs.abs(filename)
if err != nil { if err != nil {
return err return err
@@ -122,6 +163,7 @@ func (fs *BoundOS) Remove(filename string) error {
func (fs *BoundOS) TempFile(dir, prefix string) (billy.File, error) { func (fs *BoundOS) TempFile(dir, prefix string) (billy.File, error) {
if dir != "" { if dir != "" {
var err error var err error
dir = fs.expandDot(dir)
dir, err = fs.abs(dir) dir, err = fs.abs(dir)
if err != nil { if err != nil {
return nil, err return nil, err
@@ -144,6 +186,11 @@ func (fs *BoundOS) Join(elem ...string) string {
} }
func (fs *BoundOS) RemoveAll(path string) error { func (fs *BoundOS) RemoveAll(path string) error {
if fs.isBaseDir(path) {
return ErrBaseDirCannotBeRemoved
}
path = fs.expandDot(path)
dir, err := fs.abs(path) dir, err := fs.abs(path)
if err != nil { if err != nil {
return err return err
@@ -152,6 +199,7 @@ func (fs *BoundOS) RemoveAll(path string) error {
} }
func (fs *BoundOS) Symlink(target, link string) error { func (fs *BoundOS) Symlink(target, link string) error {
link = fs.expandDot(link)
ln, err := fs.abs(link) ln, err := fs.abs(link)
if err != nil { if err != nil {
return err return err
@@ -164,6 +212,7 @@ func (fs *BoundOS) Symlink(target, link string) error {
} }
func (fs *BoundOS) Lstat(filename string) (os.FileInfo, error) { func (fs *BoundOS) Lstat(filename string) (os.FileInfo, error) {
filename = fs.expandDot(filename)
filename = filepath.Clean(filename) filename = filepath.Clean(filename)
if !filepath.IsAbs(filename) { if !filepath.IsAbs(filename) {
filename = filepath.Join(fs.baseDir, filename) filename = filepath.Join(fs.baseDir, filename)
@@ -175,6 +224,7 @@ func (fs *BoundOS) Lstat(filename string) (os.FileInfo, error) {
} }
func (fs *BoundOS) Readlink(link string) (string, error) { func (fs *BoundOS) Readlink(link string) (string, error) {
link = fs.expandDot(link)
if !filepath.IsAbs(link) { if !filepath.IsAbs(link) {
link = filepath.Clean(filepath.Join(fs.baseDir, link)) link = filepath.Clean(filepath.Join(fs.baseDir, link))
} }
@@ -185,6 +235,7 @@ func (fs *BoundOS) Readlink(link string) (string, error) {
} }
func (fs *BoundOS) Chmod(path string, mode os.FileMode) error { func (fs *BoundOS) Chmod(path string, mode os.FileMode) error {
path = fs.expandDot(path)
abspath, err := fs.abs(path) abspath, err := fs.abs(path)
if err != nil { if err != nil {
return err return err
@@ -199,7 +250,7 @@ func (fs *BoundOS) Chroot(path string) (billy.Filesystem, error) {
if err != nil { if err != nil {
return nil, err return nil, err
} }
return New(joined), nil return New(joined, WithBoundOS()), nil
} }
// Root returns the current base dir of the billy.Filesystem. // Root returns the current base dir of the billy.Filesystem.
@@ -220,6 +271,37 @@ func (fs *BoundOS) createDir(fullpath string) error {
return nil return nil
} }
func (fs *BoundOS) expandDot(path string) string {
if path == "." {
return fs.baseDir
}
for _, prefix := range dotPrefixes {
if strings.HasPrefix(path, prefix) {
path = strings.TrimLeft(strings.TrimPrefix(path, prefix), dotSeparators)
if path == "" {
return fs.baseDir
}
return path
}
}
return path
}
func (fs *BoundOS) isBaseDir(path string) bool {
if path == "" || filepath.Clean(path) == "." {
return true
}
path = fs.expandDot(path)
if filepath.Clean(path) == filepath.Clean(fs.baseDir) {
return true
}
abspath, err := fs.abs(path)
if err != nil {
return false
}
return filepath.Clean(abspath) == filepath.Clean(fs.baseDir)
}
// abs transforms filename to an absolute path, taking into account the base dir. // abs transforms filename to an absolute path, taking into account the base dir.
// Relative paths won't be allowed to ascend the base dir, so `../file` will become // Relative paths won't be allowed to ascend the base dir, so `../file` will become
// `/working-dir/file`. // `/working-dir/file`.
@@ -233,7 +315,7 @@ func (fs *BoundOS) abs(filename string) (string, error) {
path, err := securejoin.SecureJoin(fs.baseDir, filename) path, err := securejoin.SecureJoin(fs.baseDir, filename)
if err != nil { if err != nil {
return "", nil return "", err
} }
if fs.deduplicatePath { if fs.deduplicatePath {
@@ -246,24 +328,12 @@ func (fs *BoundOS) abs(filename string) (string, error) {
return path, nil return path, nil
} }
// insideBaseDir checks whether filename is located within
// the fs.baseDir.
func (fs *BoundOS) insideBaseDir(filename string) (bool, error) {
if filename == fs.baseDir {
return true, nil
}
if !strings.HasPrefix(filename, fs.baseDir+string(filepath.Separator)) {
return false, fmt.Errorf("path outside base dir")
}
return true, nil
}
// insideBaseDirEval checks whether filename is contained within // insideBaseDirEval checks whether filename is contained within
// a dir that is within the fs.baseDir, by first evaluating any symlinks // a dir that is within the fs.baseDir, by first evaluating any symlinks
// that either filename or fs.baseDir may contain. // that either filename or fs.baseDir may contain.
func (fs *BoundOS) insideBaseDirEval(filename string) (bool, error) { func (fs *BoundOS) insideBaseDirEval(filename string) (bool, error) {
// "/" contains all others. // "/" contains all others.
if fs.baseDir == "/" { if fs.baseDir == "/" || fs.baseDir == filename {
return true, nil return true, nil
} }
dir, err := filepath.EvalSymlinks(filepath.Dir(filename)) dir, err := filepath.EvalSymlinks(filepath.Dir(filename))
+10
View File
@@ -14,6 +14,8 @@ import (
// ChrootOS is a legacy filesystem based on a "soft chroot" of the os filesystem. // ChrootOS is a legacy filesystem based on a "soft chroot" of the os filesystem.
// Although this is still the default os filesystem, consider using BoundOS instead. // Although this is still the default os filesystem, consider using BoundOS instead.
// //
// Deprecated: use New with WithBoundOS instead.
//
// Behaviours of note: // Behaviours of note:
// 1. A "soft chroot" translates the base dir to "/" for the purposes of the // 1. A "soft chroot" translates the base dir to "/" for the purposes of the
// fs abstraction. // fs abstraction.
@@ -24,6 +26,14 @@ import (
type ChrootOS struct{} type ChrootOS struct{}
func newChrootOS(baseDir string) billy.Filesystem { func newChrootOS(baseDir string) billy.Filesystem {
if baseDir != "" {
resolved, err := filepath.EvalSymlinks(baseDir)
if err != nil {
return chroot.New(&ChrootOS{}, baseDir)
}
baseDir = resolved
}
return chroot.New(&ChrootOS{}, baseDir) return chroot.New(&ChrootOS{}, baseDir)
} }
+19 -24
View File
@@ -16,8 +16,6 @@ import (
// can but returns the first error it encounters. If the path does not exist, // can but returns the first error it encounters. If the path does not exist,
// RemoveAll returns nil (no error). // RemoveAll returns nil (no error).
func RemoveAll(fs billy.Basic, path string) error { func RemoveAll(fs billy.Basic, path string) error {
fs, path = getUnderlyingAndPath(fs, path)
if r, ok := fs.(removerAll); ok { if r, ok := fs.(removerAll); ok {
return r.RemoveAll(path) return r.RemoveAll(path)
} }
@@ -39,7 +37,7 @@ func removeAll(fs billy.Basic, path string) error {
} }
// Otherwise, is this a directory we need to recurse into? // Otherwise, is this a directory we need to recurse into?
dir, serr := fs.Stat(path) dir, serr := lstat(fs, path)
if serr != nil { if serr != nil {
if errors.Is(serr, os.ErrNotExist) { if errors.Is(serr, os.ErrNotExist) {
return nil return nil
@@ -48,8 +46,8 @@ func removeAll(fs billy.Basic, path string) error {
return serr return serr
} }
if !dir.IsDir() { if dir.Mode()&os.ModeSymlink != 0 || !dir.IsDir() {
// Not a directory; return the error from Remove. // Not a directory we should recurse into; return the error from Remove.
return err return err
} }
@@ -62,7 +60,7 @@ func removeAll(fs billy.Basic, path string) error {
fis, err := dirfs.ReadDir(path) fis, err := dirfs.ReadDir(path)
if err != nil { if err != nil {
if errors.Is(err, os.ErrNotExist) { if errors.Is(err, os.ErrNotExist) {
// Race. It was deleted between the Lstat and Open. // Race. It was deleted between the Lstat and ReadDir.
// Return nil per RemoveAll's docs. // Return nil per RemoveAll's docs.
return nil return nil
} }
@@ -91,7 +89,18 @@ func removeAll(fs billy.Basic, path string) error {
} }
return err return err
}
func lstat(filesystem billy.Basic, path string) (os.FileInfo, error) {
if sl, ok := filesystem.(billy.Symlink); ok {
// Avoid following a symlink substituted after the initial Remove fails.
fi, err := sl.Lstat(path)
if err == nil || !errors.Is(err, billy.ErrNotSupported) {
return fi, err
}
}
return filesystem.Stat(path)
} }
// WriteFile writes data to a file named by filename in the given filesystem. // WriteFile writes data to a file named by filename in the given filesystem.
@@ -123,8 +132,10 @@ func WriteFile(fs billy.Basic, filename string, data []byte, perm os.FileMode) (
// We generate random temporary file names so that there's a good // We generate random temporary file names so that there's a good
// chance the file doesn't exist yet - keeps the number of tries in // chance the file doesn't exist yet - keeps the number of tries in
// TempFile to a minimum. // TempFile to a minimum.
var rand uint32 var (
var randmu sync.Mutex rand uint32
randmu sync.Mutex
)
func reseed() uint32 { func reseed() uint32 {
return uint32(time.Now().UnixNano() + int64(os.Getpid())) return uint32(time.Now().UnixNano() + int64(os.Getpid()))
@@ -220,22 +231,6 @@ func getTempDir(fs billy.Basic) string {
return ".tmp" return ".tmp"
} }
type underlying interface {
Underlying() billy.Basic
}
func getUnderlyingAndPath(fs billy.Basic, path string) (billy.Basic, string) {
u, ok := fs.(underlying)
if !ok {
return fs, path
}
if ch, ok := fs.(billy.Chroot); ok {
path = fs.Join(ch.Root(), path)
}
return u.Underlying(), path
}
// ReadFile reads the named file and returns the contents from the given filesystem. // ReadFile reads the named file and returns the contents from the given filesystem.
// A successful call returns err == nil, not err == EOF. // A successful call returns err == nil, not err == EOF.
// Because ReadFile reads the whole file, it does not treat an EOF from Read // Because ReadFile reads the whole file, it does not treat an EOF from Read
+30 -1
View File
@@ -61,6 +61,16 @@ type Config struct {
CommentChar string CommentChar string
// RepositoryFormatVersion identifies the repository format and layout version. // RepositoryFormatVersion identifies the repository format and layout version.
RepositoryFormatVersion format.RepositoryFormatVersion RepositoryFormatVersion format.RepositoryFormatVersion
// ProtectNTFS controls whether NTFS-specific path protections are
// applied (e.g. rejecting .git trailing spaces/periods, alternate
// data streams, 8.3 short names). When unset, defaults to true on
// Windows.
ProtectNTFS OptBool
// ProtectHFS controls whether HFS+-specific path protections are
// applied (e.g. rejecting .git with Unicode zero-width or
// directional characters that HFS+ would normalize away).
// When unset, defaults to true on macOS.
ProtectHFS OptBool
} }
User struct { User struct {
@@ -266,6 +276,8 @@ const (
repositoryFormatVersionKey = "repositoryformatversion" repositoryFormatVersionKey = "repositoryformatversion"
objectFormat = "objectformat" objectFormat = "objectformat"
mirrorKey = "mirror" mirrorKey = "mirror"
protectNTFSKey = "protectNTFS"
protectHFSKey = "protectHFS"
// DefaultPackWindow holds the number of previous objects used to // DefaultPackWindow holds the number of previous objects used to
// generate deltas. The value 10 is the same used by git command. // generate deltas. The value 10 is the same used by git command.
@@ -309,6 +321,14 @@ func (c *Config) unmarshalCore() {
c.Core.Worktree = s.Options.Get(worktreeKey) c.Core.Worktree = s.Options.Get(worktreeKey)
c.Core.CommentChar = s.Options.Get(commentCharKey) c.Core.CommentChar = s.Options.Get(commentCharKey)
if parsed := parseConfigBool(s.Options.Get(protectNTFSKey)); parsed.IsSet() {
c.Core.ProtectNTFS = parsed
}
if parsed := parseConfigBool(s.Options.Get(protectHFSKey)); parsed.IsSet() {
c.Core.ProtectHFS = parsed
}
} }
func (c *Config) unmarshalUser() { func (c *Config) unmarshalUser() {
@@ -379,7 +399,8 @@ func unmarshalSubmodules(fc *format.Config, submodules map[string]*Submodule) {
m := &Submodule{} m := &Submodule{}
m.unmarshal(sub) m.unmarshal(sub)
if m.Validate() == ErrModuleBadPath { if err := m.Validate(); errors.Is(err, ErrModuleBadPath) ||
errors.Is(err, ErrModuleBadName) {
continue continue
} }
@@ -436,6 +457,14 @@ func (c *Config) marshalCore() {
if c.Core.Worktree != "" { if c.Core.Worktree != "" {
s.SetOption(worktreeKey, c.Core.Worktree) s.SetOption(worktreeKey, c.Core.Worktree)
} }
if c.Core.ProtectNTFS.IsSet() {
s.SetOption(protectNTFSKey, c.Core.ProtectNTFS.FormatBool())
}
if c.Core.ProtectHFS.IsSet() {
s.SetOption(protectHFSKey, c.Core.ProtectHFS.FormatBool())
}
} }
func (c *Config) marshalExtensions() { func (c *Config) marshalExtensions() {
+52
View File
@@ -3,8 +3,11 @@ package config
import ( import (
"bytes" "bytes"
"errors" "errors"
"fmt"
"regexp" "regexp"
"strings"
"github.com/go-git/go-git/v5/internal/pathutil"
format "github.com/go-git/go-git/v5/plumbing/format/config" format "github.com/go-git/go-git/v5/plumbing/format/config"
) )
@@ -12,6 +15,7 @@ var (
ErrModuleEmptyURL = errors.New("module config: empty URL") ErrModuleEmptyURL = errors.New("module config: empty URL")
ErrModuleEmptyPath = errors.New("module config: empty path") ErrModuleEmptyPath = errors.New("module config: empty path")
ErrModuleBadPath = errors.New("submodule has an invalid path") ErrModuleBadPath = errors.New("submodule has an invalid path")
ErrModuleBadName = errors.New("ignoring suspicious submodule name")
) )
var ( var (
@@ -94,6 +98,10 @@ type Submodule struct {
// Validate validates the fields and sets the default values. // Validate validates the fields and sets the default values.
func (m *Submodule) Validate() error { func (m *Submodule) Validate() error {
if err := validSubmoduleName(m.Name); err != nil {
return fmt.Errorf("%w: %q", ErrModuleBadName, m.Name)
}
if m.Path == "" { if m.Path == "" {
return ErrModuleEmptyPath return ErrModuleEmptyPath
} }
@@ -109,6 +117,50 @@ func (m *Submodule) Validate() error {
return nil return nil
} }
// validSubmoduleName mirrors canonical Git's check_submodule_name in
// submodule-config.c [1]: reject empty names and any name with a ".."
// path component, using both '/' and '\\' as separators so the rule
// is consistent across platforms. The component check is delegated to
// `pathutil.IsHFSDot` and `pathutil.IsNTFSDot` with `.` as the needle,
// which both cover the bare ".." case and reject components that
// resolve to ".." after HFS+ Unicode normalisation (ignored code
// points, e.g. `.<U+200C>.`) or NTFS trailing-space/dot/ADS
// canonicalisation (e.g. `.. `, `..::$INDEX_ALLOCATION`).
// `.gitmodules` is attacker-controlled by definition, so both checks
// run unconditionally regardless of host OS.
//
// The additional checks (bare ".", NUL byte, leading or trailing
// separator, drive-letter prefix) close go-git-specific edge cases
// the canonical loop does not exercise: canonical Git treats names
// as opaque C strings, while Go strings carry NULs through and the
// billy filesystem layer is path-aware in ways Git's working storage
// is not.
//
// [1]: https://github.com/git/git/blob/v2.54.0/submodule-config.c#L214-L237
func validSubmoduleName(name string) error {
if name == "" || name == "." {
return ErrModuleBadName
}
for _, seg := range strings.FieldsFunc(name, isPathSep) {
if pathutil.IsHFSDot(seg, ".") || pathutil.IsNTFSDot(seg, ".", "") {
return ErrModuleBadName
}
}
// go-git-specific defensive checks beyond canonical Git.
if strings.ContainsRune(name, 0) {
return ErrModuleBadName
}
if isPathSep(rune(name[0])) || isPathSep(rune(name[len(name)-1])) {
return ErrModuleBadName
}
if len(name) >= 2 && name[1] == ':' {
return ErrModuleBadName
}
return nil
}
func isPathSep(r rune) bool { return r == '/' || r == '\\' }
func (m *Submodule) unmarshal(s *format.Subsection) { func (m *Submodule) unmarshal(s *format.Subsection) {
m.raw = s m.raw = s
+82
View File
@@ -0,0 +1,82 @@
package config
import (
"strconv"
"strings"
)
// OptBool is a tri-state boolean: unset, explicitly false, or explicitly true.
// Its zero value (OptBoolUnset) means the setting was not specified, which
// allows merge logic based on reflect.Value.IsZero to skip unset fields while
// still letting an explicit "false" override a previously set "true".
type OptBool byte
const (
// OptBoolUnset indicates the setting was not specified.
OptBoolUnset OptBool = iota
// OptBoolFalse indicates the setting was explicitly set to false.
OptBoolFalse
// OptBoolTrue indicates the setting was explicitly set to true.
OptBoolTrue
)
// NewOptBool converts a plain bool into an OptBool.
func NewOptBool(v bool) OptBool {
if v {
return OptBoolTrue
}
return OptBoolFalse
}
// IsTrue returns whether the value is explicitly true.
func (o OptBool) IsTrue() bool { return o == OptBoolTrue }
// IsSet returns whether the value was explicitly specified (true or false).
func (o OptBool) IsSet() bool { return o != OptBoolUnset }
func (o OptBool) String() string {
switch o {
case OptBoolTrue:
return "true"
case OptBoolFalse:
return "false"
default:
return "unset"
}
}
// FormatBool returns the strconv-formatted value. Only meaningful when IsSet.
func (o OptBool) FormatBool() string {
return strconv.FormatBool(o.IsTrue())
}
// parseConfigBool mirrors upstream Git's git_parse_maybe_bool: it
// accepts true/yes/on (→ OptBoolTrue) and false/no/off (→
// OptBoolFalse) case-insensitively, plus any decimal integer (zero
// → OptBoolFalse, non-zero → OptBoolTrue). Empty or otherwise
// unrecognised values return OptBoolUnset, leaving the caller's
// platform default in place. The empty-string handling is the only
// intentional divergence from upstream, which returns false for
// empty: in our unmarshalCore caller, an empty value means the key
// is unset and the platform default should apply.
//
// Reference: upstream Git git_parse_maybe_bool_text at parse.c
// L157-L173 and git_parse_maybe_bool at parse.c L174-L182 in tag
// v2.54.0[1].
//
// [1]: https://github.com/git/git/blob/v2.54.0/parse.c#L157-L182
func parseConfigBool(v string) OptBool {
switch strings.ToLower(v) {
case "true", "yes", "on":
return OptBoolTrue
case "false", "no", "off":
return OptBoolFalse
}
if i, err := strconv.Atoi(v); err == nil {
if i != 0 {
return OptBoolTrue
}
return OptBoolFalse
}
return OptBoolUnset
}
+21
View File
@@ -0,0 +1,21 @@
package pathutil
import "strings"
// IsDotGitName reports whether name is `.git` or its 8.3 NTFS short
// alias `git~1`, case-insensitively. Both are forbidden as path
// components (and as submodule names) because they refer to the
// repository's own metadata directory.
//
// File names that do not conform to the 8.3 format (up to eight
// characters for the basename, three for the file extension) are
// associated with a so-called "short name" on NTFS — at least on
// the `C:` drive by default — which means that `git~1/` is a valid
// way to refer to `.git/`.
func IsDotGitName(name string) bool {
switch strings.ToLower(name) {
case ".git", "git~1":
return true
}
return false
}
+99
View File
@@ -0,0 +1,99 @@
package pathutil
import "unicode"
// hfsIgnoredCodepoints contains Unicode code points that HFS+ ignores
// during path normalization. A path component containing these
// characters between the bytes of ".git" (or ".gitmodules", etc.)
// will be treated as that name by HFS+, so they have to be filtered
// out before comparison.
//
// See upstream Git utf8.c next_hfs_char in tag v2.54.0[1].
//
// [1]: https://github.com/git/git/blob/v2.54.0/utf8.c#L703-L740
var hfsIgnoredCodepoints = map[rune]struct{}{
0x200c: {}, // ZERO WIDTH NON-JOINER
0x200d: {}, // ZERO WIDTH JOINER
0x200e: {}, // LEFT-TO-RIGHT MARK
0x200f: {}, // RIGHT-TO-LEFT MARK
0x202a: {}, // LEFT-TO-RIGHT EMBEDDING
0x202b: {}, // RIGHT-TO-LEFT EMBEDDING
0x202c: {}, // POP DIRECTIONAL FORMATTING
0x202d: {}, // LEFT-TO-RIGHT OVERRIDE
0x202e: {}, // RIGHT-TO-LEFT OVERRIDE
0x206a: {}, // INHIBIT SYMMETRIC SWAPPING
0x206b: {}, // ACTIVATE SYMMETRIC SWAPPING
0x206c: {}, // INHIBIT ARABIC FORM SHAPING
0x206d: {}, // ACTIVATE ARABIC FORM SHAPING
0x206e: {}, // NATIONAL DIGIT SHAPES
0x206f: {}, // NOMINAL DIGIT SHAPES
0xfeff: {}, // ZERO WIDTH NO-BREAK SPACE
}
// IsHFSDot reports whether part would be treated as ".<needle>" on an
// HFS+ filesystem after stripping ignored Unicode code points and
// folding ASCII to lower case. The needle is the lowercase ASCII
// suffix without the leading dot (e.g. "git", "gitmodules"). It
// mirrors upstream Git's is_hfs_dot_generic and is the building
// block of IsHFSDotGit / IsHFSDotGitmodules.
//
// Reference: upstream Git utf8.c is_hfs_dot_generic at L741-L774 and
// the dotgit family at L784-L809 in tag v2.54.0[1].
//
// [1]: https://github.com/git/git/blob/v2.54.0/utf8.c#L741-L809
func IsHFSDot(part, needle string) bool {
runes := []rune(part)
i := 0
// skip ignored code points, then expect '.'
for i < len(runes) {
if _, ok := hfsIgnoredCodepoints[runes[i]]; !ok {
break
}
i++
}
if i >= len(runes) || runes[i] != '.' {
return false
}
i++
// match needle case-insensitively, skipping ignored code points
for _, expected := range needle {
for i < len(runes) {
if _, ok := hfsIgnoredCodepoints[runes[i]]; !ok {
break
}
i++
}
if i >= len(runes) {
return false
}
r := runes[i]
if r > 127 {
return false
}
if unicode.ToLower(r) != expected {
return false
}
i++
}
// skip trailing ignored code points
for i < len(runes) {
if _, ok := hfsIgnoredCodepoints[runes[i]]; !ok {
break
}
i++
}
// must be at end of component
return i == len(runes)
}
// IsHFSDotGit reports whether part is an HFS+ equivalent of ".git".
func IsHFSDotGit(part string) bool { return IsHFSDot(part, "git") }
// IsHFSDotGitmodules reports whether part is an HFS+ equivalent of
// ".gitmodules", catching attempts to plant the file via Unicode
// code points that HFS+ would strip during normalisation.
func IsHFSDotGitmodules(part string) bool { return IsHFSDot(part, "gitmodules") }
+187
View File
@@ -0,0 +1,187 @@
package pathutil
import "strings"
// IsNTFSDotGit ports upstream Git's is_ntfs_dotgit. It detects path
// components that NTFS would resolve to ".git": the canonical name
// itself and its 8.3 short-name alias "git~1", each followed by any
// number of trailing spaces or periods (which NTFS silently trims)
// and an optional Alternate Data Stream suffix (":<stream>"). The
// bare strings ".git" and "git~1" also match, mirroring upstream.
//
// Reference: upstream Git path.c is_ntfs_dotgit at L1415-L1449
// in tag v2.54.0[1].
//
// [1]: https://github.com/git/git/blob/v2.54.0/path.c#L1415-L1449
func IsNTFSDotGit(part string) bool {
var i int
switch {
case len(part) >= 4 && part[0] == '.' &&
asciiToLower(part[1]) == 'g' &&
asciiToLower(part[2]) == 'i' &&
asciiToLower(part[3]) == 't':
i = 4
case len(part) >= 5 &&
asciiToLower(part[0]) == 'g' &&
asciiToLower(part[1]) == 'i' &&
asciiToLower(part[2]) == 't' &&
part[3] == '~' && part[4] == '1':
i = 5
default:
return false
}
for ; i < len(part); i++ {
c := part[i]
if c == ':' {
return true
}
if c != '.' && c != ' ' {
return false
}
}
return true
}
// WindowsValidPath reports whether part is a valid Windows / NTFS
// path component for the worktree filesystem abstraction. It rejects
// NTFS-disguised variants of `.git` and `git~1` (trailing spaces,
// periods, Alternate Data Streams) and Windows reserved device
// names. Bare `.git` and `git~1` are allowed at this layer; the
// caller decides whether they are permissible at the current path
// position.
func WindowsValidPath(part string) bool {
if IsNTFSDotGit(part) && !IsDotGitName(part) {
return false
}
return !isWindowsReservedName(part)
}
// windowsReservedNames lists the Windows reserved device names.
// A path component is reserved if its base name (ignoring trailing
// spaces, extensions, and NTFS Alternate Data Streams) matches one of
// these case-insensitively.
//
// See upstream Git compat/mingw.c is_valid_win32_path().
var windowsReservedNames = []string{
"CON", "PRN", "AUX", "NUL",
"COM1", "COM2", "COM3", "COM4", "COM5", "COM6", "COM7", "COM8", "COM9",
"LPT1", "LPT2", "LPT3", "LPT4", "LPT5", "LPT6", "LPT7", "LPT8", "LPT9",
"CONIN$", "CONOUT$",
}
func isWindowsReservedName(part string) bool {
for _, name := range windowsReservedNames {
if len(part) < len(name) {
continue
}
if !strings.EqualFold(part[:len(name)], name) {
continue
}
// Exact match or followed by space, dot, colon (ADS), or separator.
if len(part) == len(name) {
return true
}
switch part[len(name)] {
case ' ', '.', ':':
return true
}
}
return false
}
// IsNTFSDot ports upstream Git's is_ntfs_dot_generic. It detects NTFS
// path-component variants of a dotfile name that attackers can use to
// bypass case-insensitive comparisons against the canonical name on
// Windows. The dotgit parameter is the lowercase name without the
// leading dot (e.g. "gitmodules"); shortnamePrefix is the canonical
// 6-character NTFS short-name prefix used as a fall-back match
// (e.g. "gi7eba" for ".gitmodules").
//
// Reference: upstream Git path.c is_ntfs_dot_generic at L1451-L1507
// in tag v2.54.0[1].
//
// [1]: https://github.com/git/git/blob/v2.54.0/path.c#L1451-L1507
func IsNTFSDot(name, dotgit, shortnamePrefix string) bool {
// onlySpacesAndPeriods returns true when the suffix from start
// onwards consists only of trailing spaces and periods, possibly
// terminated by a NTFS Alternate Data Stream colon. Mirrors the
// only_spaces_and_periods label in upstream's is_ntfs_dot_generic.
onlySpacesAndPeriods := func(start int) bool {
for i := start; i < len(name); i++ {
c := name[i]
if c == ':' {
return true
}
if c != ' ' && c != '.' {
return false
}
}
return true
}
// Pattern 1: ".<dotgit>" prefix + trailing spaces / periods / ADS.
if len(name) >= len(dotgit)+1 && name[0] == '.' &&
strings.EqualFold(name[1:1+len(dotgit)], dotgit) {
if onlySpacesAndPeriods(len(dotgit) + 1) {
return true
}
}
// Pattern 2: standard NTFS short name <dotgit[:6]>~[1-4].
if len(dotgit) >= 6 && len(name) >= 8 &&
strings.EqualFold(name[:6], dotgit[:6]) &&
name[6] == '~' && name[7] >= '1' && name[7] <= '4' {
if onlySpacesAndPeriods(8) {
return true
}
}
// Pattern 3: fall-back NTFS short name keyed by shortnamePrefix.
if len(shortnamePrefix) < 6 || len(name) < 8 {
return false
}
sawTilde := false
i := 0
for i < 8 {
c := name[i]
switch {
case sawTilde:
if c < '0' || c > '9' {
return false
}
case c == '~':
i++
if i >= len(name) || name[i] < '1' || name[i] > '9' {
return false
}
sawTilde = true
case i >= 6:
return false
case c&0x80 != 0:
return false
default:
if asciiToLower(c) != shortnamePrefix[i] {
return false
}
}
i++
}
return onlySpacesAndPeriods(8)
}
// IsNTFSDotGitmodules reports whether part is an NTFS-equivalent of
// ".gitmodules" — the file name (or any of its variants that NTFS
// would resolve to it) that attackers can use to plant submodule
// configuration disguised as a symlink. The 6-character canonical
// short-name prefix "gi7eba" mirrors upstream Git's is_ntfs_dotgitmodules.
func IsNTFSDotGitmodules(part string) bool {
return IsNTFSDot(part, "gitmodules", "gi7eba")
}
func asciiToLower(c byte) byte {
if c >= 'A' && c <= 'Z' {
return c + ('a' - 'A')
}
return c
}
+66
View File
@@ -0,0 +1,66 @@
package pathutil
import (
"fmt"
"path/filepath"
"strings"
)
// ErrInvalidPath is returned by ValidTreePath when its argument is
// not a safe path to materialise into the worktree.
var ErrInvalidPath = fmt.Errorf("invalid path")
// ValidTreePath rejects path strings that, if materialised into a
// worktree, would let an attacker-controlled tree entry escape the
// worktree or rewrite repository metadata. It rejects:
//
// - control characters (< 0x20, 0x7f);
// - empty paths and "." / ".." components;
// - Windows volume name prefixes (e.g. C:);
// - .git, its 8.3 NTFS short-name git~1, plus their HFS+ and NTFS
// variants — at every position, not just the root.
//
// HFS+/NTFS variants of `.git` are always rejected at this layer
// regardless of runtime config: tree paths are canonical UTF-8 with
// no zero-width characters or NTFS short-name forms, so an entry
// that looks like a disguised `.git` is suspicious anywhere. Windows
// reserved device names (CON, NUL, etc.) are not policed here — they
// are legitimate filenames on non-Windows filesystems and upstream
// Git accepts them. The wrapper layer (validPath in package git)
// rejects them at materialisation time when core.protectNTFS is on.
//
// Mirrors upstream Git's verify_path_internal at read-cache.c#L987
// in tag v2.54.0[1] with protect_hfs / protect_ntfs treated as
// always-on for `.git`-disguise detection (tree paths are not
// application-supplied) and is_valid_win32_path left to the wrapper.
//
// [1]: https://github.com/git/git/blob/v2.54.0/read-cache.c#L987
func ValidTreePath(p string) error {
for i := 0; i < len(p); i++ {
if p[i] < 0x20 || p[i] == 0x7f {
return fmt.Errorf("%w %q: contains control character", ErrInvalidPath, p)
}
}
parts := strings.FieldsFunc(p, func(r rune) bool { return r == '\\' || r == '/' })
if len(parts) == 0 {
return fmt.Errorf("%w: %q", ErrInvalidPath, p)
}
// Volume names are not supported, in both formats: \\ and <DRIVE_LETTER>:.
if vol := filepath.VolumeName(p); vol != "" {
return fmt.Errorf("%w: %q", ErrInvalidPath, p)
}
for _, part := range parts {
if part == "." || part == ".." {
return fmt.Errorf("%w %q: cannot use %q", ErrInvalidPath, p, part)
}
if IsDotGitName(part) || IsHFSDotGit(part) || IsNTFSDotGit(part) {
return fmt.Errorf("%w component: %q", ErrInvalidPath, p)
}
}
return nil
}
+35 -2
View File
@@ -2,12 +2,14 @@ package url
import ( import (
"regexp" "regexp"
"runtime"
"strings"
) )
var ( var (
isSchemeRegExp = regexp.MustCompile(`^[^:]+://`) isSchemeRegExp = regexp.MustCompile(`^[^:]+://`)
// Ref: https://github.com/git/git/blob/master/Documentation/urls.txt#L37 // Ref: https://github.com/git/git/blob/v2.54.0/Documentation/urls.adoc#L41-L48
scpLikeUrlRegExp = regexp.MustCompile(`^(?:(?P<user>[^@]+)@)?(?P<host>[^:\s]+):(?:(?P<port>[0-9]{1,5}):)?(?P<path>[^\\].*)$`) scpLikeUrlRegExp = regexp.MustCompile(`^(?:(?P<user>[^@]+)@)?(?P<host>[^:\s]+):(?:(?P<port>[0-9]{1,5}):)?(?P<path>[^\\].*)$`)
) )
@@ -20,7 +22,38 @@ func MatchesScheme(url string) bool {
// MatchesScpLike returns true if the given string matches an SCP-like // MatchesScpLike returns true if the given string matches an SCP-like
// format scheme. // format scheme.
func MatchesScpLike(url string) bool { func MatchesScpLike(url string) bool {
return scpLikeUrlRegExp.MatchString(url) if !scpLikeUrlRegExp.MatchString(url) {
return false
}
// Mirror canonical Git's url_is_local_not_ssh in connect.c[1] for
// the cases the regex above cannot disambiguate by itself: a URL
// is treated as a local path (not SCP-style SSH) when a `/`
// precedes the first `:` (e.g. `./relative:path`,
// `/abs/with:colon/file`), or — on Windows only — when it has a
// DOS drive prefix like `C:foo` where the host is a single
// ASCII letter.
//
// [1]: https://github.com/git/git/blob/v2.54.0/connect.c#L710-L716
if before, _, _ := strings.Cut(url, ":"); strings.Contains(before, "/") {
return false
}
if runtime.GOOS == "windows" && hasDosDrivePrefix(url) {
return false
}
return true
}
// hasDosDrivePrefix reports whether s begins with `<letter>:` (a
// Windows drive prefix such as `C:` or `c:`). Mirrors canonical Git's
// win32_has_dos_drive_prefix[1].
//
// [1]: https://github.com/git/git/blob/v2.54.0/compat/win32/path-utils.c#L20-L29
func hasDosDrivePrefix(s string) bool {
if len(s) < 2 || s[1] != ':' {
return false
}
c := s[0]
return ('A' <= c && c <= 'Z') || ('a' <= c && c <= 'z')
} }
// FindScpLikeComponents returns the user, host, port and path of the // FindScpLikeComponents returns the user, host, port and path of the
+159 -5
View File
@@ -7,6 +7,7 @@ import (
"errors" "errors"
"fmt" "fmt"
"io" "io"
"io/fs"
"github.com/go-git/go-git/v5/plumbing/hash" "github.com/go-git/go-git/v5/plumbing/hash"
"github.com/go-git/go-git/v5/utils/binary" "github.com/go-git/go-git/v5/utils/binary"
@@ -25,35 +26,88 @@ const (
objectIDLength = hash.Size objectIDLength = hash.Size
) )
// Byte sizes of the idx v2 layout elements, used by the size formula
// in [validateIdxV2Size]. See [gitformat-pack] for the canonical
// layout.
//
// [gitformat-pack]: https://git-scm.com/docs/gitformat-pack
const (
headerLen = 8 // magic + version
fanoutLen = fanout * 4 // uint32 per bucket
crc32Len = 4 // CRC32 per object
offset32Len = 4 // 32-bit offset per object
offset64Len = 8 // 64-bit overflow offset
trailerHashes = 2 // pack checksum + idx checksum, each hashsz
)
// statInput is the optional shape the [Decoder] probes for at the
// start of [Decoder.Decode] to learn the on-disk length of the idx
// blob, which it uses to validate the canonical-Git size formula
// before any allocations driven by the fanout table. Callers that
// pass an [*os.File] or a `billy.File` backed by an `*os.File`
// (the production call sites in `storage/filesystem`) satisfy it
// directly; arbitrary [io.Reader]s do not, and decode for them
// retains the pre-existing behaviour of erroring out at the
// truncated-payload boundary instead.
//
// The interface is intentionally unexported so the public
// [NewDecoder] signature stays compatible with v5.
type statInput interface {
Stat() (fs.FileInfo, error)
}
// Decoder reads and decodes idx files from an input stream. // Decoder reads and decodes idx files from an input stream.
type Decoder struct { type Decoder struct {
io.Reader io.Reader
h hash.Hash src io.Reader
h hash.Hash
} }
// NewDecoder builds a new idx stream decoder, that reads from r. // NewDecoder builds a new idx stream decoder, that reads from r.
func NewDecoder(r io.Reader) *Decoder { func NewDecoder(r io.Reader) *Decoder {
h := hash.New(crypto.SHA1) h := hash.New(crypto.SHA1)
tr := io.TeeReader(r, h) tr := io.TeeReader(r, h)
return &Decoder{tr, h} return &Decoder{tr, r, h}
} }
// Decode reads from the stream and decode the content into the MemoryIndex struct. // Decode reads from the stream and decode the content into the MemoryIndex struct.
func (d *Decoder) Decode(idx *MemoryIndex) error { func (d *Decoder) Decode(idx *MemoryIndex) error {
idxSize := int64(-1)
if in, ok := d.src.(statInput); ok {
fi, err := in.Stat()
if err != nil {
return fmt.Errorf("%w: stat input: %w", ErrMalformedIdxFile, err)
}
idxSize = fi.Size()
}
if err := validateHeader(d); err != nil { if err := validateHeader(d); err != nil {
return err return err
} }
flow := []func(*MemoryIndex, io.Reader) error{ headerFlow := []func(*MemoryIndex, io.Reader) error{
readVersion, readVersion,
readFanout, readFanout,
}
for _, f := range headerFlow {
if err := f(idx, d); err != nil {
return err
}
}
if idxSize >= 0 {
if err := validateIdxV2Size(idx, idxSize); err != nil {
return err
}
}
bodyFlow := []func(*MemoryIndex, io.Reader) error{
readObjectNames, readObjectNames,
readCRC32, readCRC32,
readOffsets, readOffsets,
readPackChecksum, readPackChecksum,
} }
for _, f := range bodyFlow {
for _, f := range flow {
if err := f(idx, d); err != nil { if err := f(idx, d); err != nil {
return err return err
} }
@@ -199,3 +253,103 @@ func readIdxChecksum(idx *MemoryIndex, r io.Reader) error {
return nil return nil
} }
// validateIdxV2Size enforces the size formula used by canonical Git
// load_idx for idx v2 files: the on-disk length must lie within
// [minSize, maxSize] where
//
// perObject = hashsz + crc32Len + offset32Len
// minSize = headerLen + fanoutLen + trailerHashes*hashsz + nr*perObject
// maxSize = minSize + (nr-1)*offset64Len when nr > 0
//
// with nr taken from the last fanout entry and hashsz fixed at
// [objectIDLength] (SHA-1 in v5). Multiplications use a self-checking
// overflow guard so inputs whose claimed object count overflows the
// formula are rejected rather than wrapping into a smaller value.
func validateIdxV2Size(idx *MemoryIndex, idxSize int64) error {
nr := int64(idx.Fanout[fanout-1])
hashsz := int64(objectIDLength)
minSize := minIdxV2Size(nr, hashsz)
maxSize := maxIdxV2Size(nr, hashsz)
if minSize < 0 || maxSize < 0 {
return fmt.Errorf("%w: object count %d is inconsistent with file size", ErrMalformedIdxFile, nr)
}
if idxSize < minSize || idxSize > maxSize {
return fmt.Errorf("%w: file size %d is inconsistent with object count %d", ErrMalformedIdxFile, idxSize, nr)
}
return nil
}
// minIdxV2Size returns the minimum on-disk size of an idx v2 file
// holding nr objects with the given hash size, mirroring the
// computation in canonical Git load_idx. Returns -1 when any
// intermediate multiplication or addition would overflow int64.
func minIdxV2Size(nr, hashsz int64) int64 {
perObject := hashsz + crc32Len + offset32Len
fixed := int64(headerLen+fanoutLen) + trailerHashes*hashsz
objects, ok := mulInt64(nr, perObject)
if !ok {
return -1
}
sum, ok := addInt64(fixed, objects)
if !ok {
return -1
}
return sum
}
// maxIdxV2Size returns the maximum on-disk size of an idx v2 file
// holding nr objects with the given hash size, mirroring the
// computation in canonical Git load_idx. Returns -1 on overflow.
func maxIdxV2Size(nr, hashsz int64) int64 {
minSize := minIdxV2Size(nr, hashsz)
if minSize < 0 {
return -1
}
if nr == 0 {
return minSize
}
overflow, ok := mulInt64(nr-1, offset64Len)
if !ok {
return -1
}
sum, ok := addInt64(minSize, overflow)
if !ok {
return -1
}
return sum
}
// mulInt64 returns a*b and whether the result fits in an int64 without
// overflow. Negative operands or overflow yield ok=false. The overflow
// check uses the standard self-inverse identity: a*b/b == a only when
// the multiplication did not wrap.
func mulInt64(a, b int64) (int64, bool) {
if a < 0 || b < 0 {
return 0, false
}
if a == 0 || b == 0 {
return 0, true
}
c := a * b
if c/b != a {
return 0, false
}
return c, true
}
// addInt64 returns a+b and whether the result fits in an int64 without
// overflow. Negative operands or overflow yield ok=false.
func addInt64(a, b int64) (int64, bool) {
if a < 0 || b < 0 {
return 0, false
}
c := a + b
if c < a {
return 0, false
}
return c, true
}
+21 -8
View File
@@ -2,6 +2,7 @@ package idxfile
import ( import (
"bytes" "bytes"
"fmt"
"io" "io"
"sort" "sort"
"sync" "sync"
@@ -126,7 +127,10 @@ func (idx *MemoryIndex) FindOffset(h plumbing.Hash) (int64, error) {
return 0, plumbing.ErrObjectNotFound return 0, plumbing.ErrObjectNotFound
} }
offset := idx.getOffset(k, i) offset, err := idx.getOffset(k, i)
if err != nil {
return 0, err
}
// Save the offset for reverse lookup // Save the offset for reverse lookup
idx.mu.Lock() idx.mu.Lock()
@@ -141,17 +145,19 @@ func (idx *MemoryIndex) FindOffset(h plumbing.Hash) (int64, error) {
const isO64Mask = uint64(1) << 31 const isO64Mask = uint64(1) << 31
func (idx *MemoryIndex) getOffset(firstLevel, secondLevel int) uint64 { func (idx *MemoryIndex) getOffset(firstLevel, secondLevel int) (uint64, error) {
offset := secondLevel << 2 offset := secondLevel << 2
ofs := encbin.BigEndian.Uint32(idx.Offset32[firstLevel][offset : offset+4]) ofs := encbin.BigEndian.Uint32(idx.Offset32[firstLevel][offset : offset+4])
if (uint64(ofs) & isO64Mask) != 0 { if (uint64(ofs) & isO64Mask) != 0 {
offset := 8 * (uint64(ofs) & ^isO64Mask) offset := 8 * (uint64(ofs) & ^isO64Mask)
n := encbin.BigEndian.Uint64(idx.Offset64[offset : offset+8]) if l := uint64(len(idx.Offset64)); l < 8 || offset > l-8 {
return n return 0, fmt.Errorf("%w: offset64 index out of range", ErrMalformedIdxFile)
}
return encbin.BigEndian.Uint64(idx.Offset64[offset : offset+8]), nil
} }
return uint64(ofs) return uint64(ofs), nil
} }
// FindCRC32 implements the Index interface. // FindCRC32 implements the Index interface.
@@ -209,8 +215,11 @@ func (idx *MemoryIndex) genOffsetHash() error {
mappedFirstLevel := idx.FanoutMapping[firstLevel] mappedFirstLevel := idx.FanoutMapping[firstLevel]
for secondLevel := uint32(0); i < fanoutValue; i++ { for secondLevel := uint32(0); i < fanoutValue; i++ {
copy(hash[:], idx.Names[mappedFirstLevel][secondLevel*objectIDLength:]) copy(hash[:], idx.Names[mappedFirstLevel][secondLevel*objectIDLength:])
offset := int64(idx.getOffset(mappedFirstLevel, int(secondLevel))) off, err := idx.getOffset(mappedFirstLevel, int(secondLevel))
offsetHash[offset] = hash if err != nil {
return err
}
offsetHash[int64(off)] = hash
secondLevel++ secondLevel++
} }
} }
@@ -291,7 +300,11 @@ func (i *idxfileEntryIter) Next() (*Entry, error) {
mappedFirstLevel := i.idx.FanoutMapping[i.firstLevel] mappedFirstLevel := i.idx.FanoutMapping[i.firstLevel]
entry := new(Entry) entry := new(Entry)
copy(entry.Hash[:], i.idx.Names[mappedFirstLevel][i.secondLevel*objectIDLength:]) copy(entry.Hash[:], i.idx.Names[mappedFirstLevel][i.secondLevel*objectIDLength:])
entry.Offset = i.idx.getOffset(mappedFirstLevel, i.secondLevel) var err error
entry.Offset, err = i.idx.getOffset(mappedFirstLevel, i.secondLevel)
if err != nil {
return nil, err
}
entry.CRC32 = i.idx.getCRC32(mappedFirstLevel, i.secondLevel) entry.CRC32 = i.idx.getCRC32(mappedFirstLevel, i.secondLevel)
i.secondLevel++ i.secondLevel++
+15 -3
View File
@@ -11,9 +11,10 @@ import (
) )
var ( var (
ErrClosed = errors.New("objfile: already closed") ErrClosed = errors.New("objfile: already closed")
ErrHeader = errors.New("objfile: invalid header") ErrHeader = errors.New("objfile: invalid header")
ErrNegativeSize = errors.New("objfile: negative object size") ErrHeaderNotRead = errors.New("objfile: Header must be called before Read")
ErrNegativeSize = errors.New("objfile: negative object size")
) )
// Reader reads and decodes compressed objfile data from a provided io.Reader. // Reader reads and decodes compressed objfile data from a provided io.Reader.
@@ -100,12 +101,23 @@ func (r *Reader) prepareForRead(t plumbing.ObjectType, size int64) {
// //
// If Read encounters the end of the data stream it will return err == io.EOF, // If Read encounters the end of the data stream it will return err == io.EOF,
// either in the current call if n > 0 or in a subsequent call. // either in the current call if n > 0 or in a subsequent call.
//
// Read returns ErrHeaderNotRead if Header has not been called successfully.
func (r *Reader) Read(p []byte) (n int, err error) { func (r *Reader) Read(p []byte) (n int, err error) {
if r.multi == nil {
return 0, ErrHeaderNotRead
}
return r.multi.Read(p) return r.multi.Read(p)
} }
// Hash returns the hash of the object data stream that has been read so far. // Hash returns the hash of the object data stream that has been read so far.
// It returns the zero plumbing.Hash if Header has not been called
// successfully — guarding against the nil hasher that prepareForRead has
// not yet allocated.
func (r *Reader) Hash() plumbing.Hash { func (r *Reader) Hash() plumbing.Hash {
if r.multi == nil {
return plumbing.ZeroHash
}
return r.hasher.Sum() return r.hasher.Sum()
} }
@@ -19,9 +19,6 @@ const (
// https://github.com/git/git/blob/f7466e94375b3be27f229c78873f0acf8301c0a5/diff-delta.c#L428 // https://github.com/git/git/blob/f7466e94375b3be27f229c78873f0acf8301c0a5/diff-delta.c#L428
// Max size of a copy operation (64KB). // Max size of a copy operation (64KB).
maxCopySize = 64 * 1024 maxCopySize = 64 * 1024
// Min size of a copy operation.
minCopySize = 4
) )
// GetDelta returns an EncodedObject of type OFSDeltaObject. Base and Target object, // GetDelta returns an EncodedObject of type OFSDeltaObject. Base and Target object,
+7 -1
View File
@@ -78,7 +78,13 @@ func (o *FSObject) Reader() (io.ReadCloser, error) {
_ = f.Close() _ = f.Close()
return nil, err return nil, err
} }
return ioutil.NewReadCloserWithCloser(r, f.Close), nil // Cap the lazy stream at the resolved object size: well-formed
// content reaches EOF inside the bound, an inflated stream that
// runs past surfaces ErrInflatedSizeMismatch on the byte just
// past the limit. For delta-resolved objects o.size is the
// expanded size, which is what the caller is reading here.
bounded := newBoundedReadCloser(r, o.size)
return ioutil.NewReadCloserWithCloser(bounded, f.Close), nil
} }
r, err := p.getObjectContent(o.offset) r, err := p.getObjectContent(o.offset)
if err != nil { if err != nil {
+15 -6
View File
@@ -126,11 +126,17 @@ func (p *Packfile) nextObjectHeader() (*ObjectHeader, error) {
return h, err return h, err
} }
func (p *Packfile) getDeltaObjectSize(buf *bytes.Buffer) int64 { func (p *Packfile) getDeltaObjectSize(buf *bytes.Buffer) (int64, error) {
delta := buf.Bytes() delta := buf.Bytes()
_, delta = decodeLEB128(delta) // skip src size _, delta, err := decodeLEB128(delta) // skip src size
sz, _ := decodeLEB128(delta) if err != nil {
return int64(sz) return 0, err
}
sz, _, err := decodeLEB128(delta)
if err != nil {
return 0, err
}
return int64(sz), nil
} }
func (p *Packfile) getObjectSize(h *ObjectHeader) (int64, error) { func (p *Packfile) getObjectSize(h *ObjectHeader) (int64, error) {
@@ -145,7 +151,7 @@ func (p *Packfile) getObjectSize(h *ObjectHeader) (int64, error) {
return 0, err return 0, err
} }
return p.getDeltaObjectSize(buf), nil return p.getDeltaObjectSize(buf)
default: default:
return 0, ErrInvalidObject.AddDetails("type %q", h.Type) return 0, ErrInvalidObject.AddDetails("type %q", h.Type)
} }
@@ -233,7 +239,10 @@ func (p *Packfile) getNextObject(h *ObjectHeader, hash plumbing.Hash) (plumbing.
return nil, err return nil, err
} }
size = p.getDeltaObjectSize(buf) size, err = p.getDeltaObjectSize(buf)
if err != nil {
return nil, err
}
if size <= smallObjectThreshold { if size <= smallObjectThreshold {
var obj = new(plumbing.MemoryObject) var obj = new(plumbing.MemoryObject)
obj.SetSize(size) obj.SetSize(size)
+65 -7
View File
@@ -26,6 +26,45 @@ var (
ErrDeltaNotCached = errors.New("delta could not be found in cache") ErrDeltaNotCached = errors.New("delta could not be found in cache")
) )
// maxObjectPreallocBytes caps the up-front size hint passed to
// bytes.Buffer.Grow when staging an object's contents, so a malformed length
// cannot trigger a huge or out-of-range allocation. The buffer still grows
// dynamically as data is written; this is purely a hint cap.
const maxObjectPreallocBytes = 1 << 30 // 1 GiB
// maxObjectsPrealloc caps the up-front capacity reserved from the pack's
// declared object count, so a header advertising an absurd quantity cannot
// trigger a multi-gigabyte allocation. The slice and maps still grow
// organically beyond this hint.
const maxObjectsPrealloc = 1 << 16 // 64 Ki entries
// Match upstream Git's pack depth ceiling: pack-objects.h OE_DEPTH_BITS,
// enforced in builtin/pack-objects.c as (1 << OE_DEPTH_BITS) - 1.
const maxDeltaChainDepth = 4095
// growHint returns a non-negative int64 size, clamped to a sane upper bound,
// suitable for passing to bytes.Buffer.Grow.
func growHint(n int64) int {
switch {
case n <= 0:
return 0
case n > maxObjectPreallocBytes:
return maxObjectPreallocBytes
default:
return int(n)
}
}
// objectsHint returns a non-negative count, clamped to maxObjectsPrealloc,
// suitable for passing to make() as the capacity hint for slices or maps
// sized from a pack's declared object count.
func objectsHint(n uint32) int {
if n > maxObjectsPrealloc {
return maxObjectsPrealloc
}
return int(n)
}
// Observer interface is implemented by index encoders. // Observer interface is implemented by index encoders.
type Observer interface { type Observer interface {
// OnHeader is called when a new packfile is opened. // OnHeader is called when a new packfile is opened.
@@ -166,9 +205,10 @@ func (p *Parser) init() error {
} }
p.count = c p.count = c
p.oiByHash = make(map[plumbing.Hash]*objectInfo, p.count) hint := objectsHint(p.count)
p.oiByOffset = make(map[int64]*objectInfo, p.count) p.oiByHash = make(map[plumbing.Hash]*objectInfo, hint)
p.oi = make([]*objectInfo, p.count) p.oiByOffset = make(map[int64]*objectInfo, hint)
p.oi = make([]*objectInfo, 0, hint)
return nil return nil
} }
@@ -261,7 +301,7 @@ func (p *Parser) indexObjects() error {
} }
if delta && !p.scanner.IsSeekable { if delta && !p.scanner.IsSeekable {
buf.Reset() buf.Reset()
buf.Grow(int(oh.Length)) buf.Grow(growHint(oh.Length))
writers = append(writers, buf) writers = append(writers, buf)
} }
@@ -306,7 +346,7 @@ func (p *Parser) indexObjects() error {
} }
p.oiByOffset[oh.Offset] = ota p.oiByOffset[oh.Offset] = ota
p.oi[i] = ota p.oi = append(p.oi, ota)
} }
return nil return nil
@@ -317,8 +357,12 @@ func (p *Parser) resolveDeltas() error {
defer sync.PutBytesBuffer(buf) defer sync.PutBytesBuffer(buf)
for _, obj := range p.oi { for _, obj := range p.oi {
if err := checkDeltaChainDepth(obj); err != nil {
return err
}
buf.Reset() buf.Reset()
buf.Grow(int(obj.Length)) buf.Grow(growHint(obj.Length))
err := p.get(obj, buf) err := p.get(obj, buf)
if err != nil { if err != nil {
return err return err
@@ -337,6 +381,9 @@ func (p *Parser) resolveDeltas() error {
// create it once and reuse across all children. // create it once and reuse across all children.
r := bytes.NewReader(buf.Bytes()) r := bytes.NewReader(buf.Bytes())
for _, child := range obj.Children { for _, child := range obj.Children {
if err := checkDeltaChainDepth(child); err != nil {
return err
}
// Even though we are discarding the output, we still need to read it to // Even though we are discarding the output, we still need to read it to
// so that the scanner can advance to the next object, and the SHA1 can be // so that the scanner can advance to the next object, and the SHA1 can be
// calculated. // calculated.
@@ -356,6 +403,17 @@ func (p *Parser) resolveDeltas() error {
return nil return nil
} }
func checkDeltaChainDepth(o *objectInfo) error {
var depth int
for current := o; current != nil && current.DiskType.IsDelta(); current = current.Parent {
depth++
if depth > maxDeltaChainDepth {
return fmt.Errorf("%w: delta chain depth exceeds %d", ErrMalformedPackFile, maxDeltaChainDepth)
}
}
return nil
}
func (p *Parser) resolveExternalRef(o *objectInfo) { func (p *Parser) resolveExternalRef(o *objectInfo) {
if ref, ok := p.oiByHash[o.SHA1]; ok && ref.ExternalRef { if ref, ok := p.oiByHash[o.SHA1]; ok && ref.ExternalRef {
p.oiByHash[o.SHA1] = o p.oiByHash[o.SHA1] = o
@@ -405,7 +463,7 @@ func (p *Parser) get(o *objectInfo, buf *bytes.Buffer) (err error) {
if o.DiskType.IsDelta() { if o.DiskType.IsDelta() {
b := sync.GetBytesBuffer() b := sync.GetBytesBuffer()
defer sync.PutBytesBuffer(b) defer sync.PutBytesBuffer(b)
buf.Grow(int(o.Length)) buf.Grow(growHint(o.Length))
err := p.get(o.Parent, b) err := p.get(o.Parent, b)
if err != nil { if err != nil {
return err return err
+72 -41
View File
@@ -31,10 +31,15 @@ const (
// premptively made available for a patch operation. // premptively made available for a patch operation.
maxPatchPreemptionSize uint = 65536 maxPatchPreemptionSize uint = 65536
// minDeltaSize defines the smallest size for a delta. // minDeltaSize is the smallest valid delta: a 1-byte srcSz LEB128
minDeltaSize = 4 // header followed by a 1-byte targetSz LEB128 header (the
// shortest case being targetSz=0 with no operations).
minDeltaSize = 2
) )
// uintBits is the bit width of uint on the current platform (32 or 64).
const uintBits = 32 << (^uint(0) >> 63)
type offset struct { type offset struct {
mask byte mask byte
shift uint shift uint
@@ -142,7 +147,7 @@ func ReaderFromDelta(base plumbing.EncodedObject, deltaRC io.Reader) (io.ReadClo
baseBuf := bufio.NewReader(baseRd) baseBuf := bufio.NewReader(baseRd)
basePos := uint(0) basePos := uint(0)
for { for remainingTargetSz > 0 {
cmd, err := deltaBuf.ReadByte() cmd, err := deltaBuf.ReadByte()
if err == io.EOF { if err == io.EOF {
_ = dstWr.CloseWithError(ErrInvalidDelta) _ = dstWr.CloseWithError(ErrInvalidDelta)
@@ -166,9 +171,9 @@ func ReaderFromDelta(base plumbing.EncodedObject, deltaRC io.Reader) (io.ReadClo
return return
} }
if invalidSize(sz, targetSz) || if invalidSize(sz, remainingTargetSz) ||
invalidOffsetSize(offset, sz, srcSz) { invalidOffsetSize(offset, sz, srcSz) {
_ = dstWr.Close() _ = dstWr.CloseWithError(ErrInvalidDelta)
return return
} }
@@ -210,7 +215,7 @@ func ReaderFromDelta(base plumbing.EncodedObject, deltaRC io.Reader) (io.ReadClo
case isCopyFromDelta(cmd): case isCopyFromDelta(cmd):
sz := uint(cmd) // cmd is the size itself sz := uint(cmd) // cmd is the size itself
if invalidSize(sz, targetSz) { if invalidSize(sz, remainingTargetSz) {
_ = dstWr.CloseWithError(ErrInvalidDelta) _ = dstWr.CloseWithError(ErrInvalidDelta)
return return
} }
@@ -225,40 +230,48 @@ func ReaderFromDelta(base plumbing.EncodedObject, deltaRC io.Reader) (io.ReadClo
_ = dstWr.CloseWithError(ErrDeltaCmd) _ = dstWr.CloseWithError(ErrDeltaCmd)
return return
} }
if remainingTargetSz <= 0 {
_ = dstWr.Close()
return
}
} }
// Mirror upstream's `data != top` post-loop check: every byte
// of the delta payload must be consumed.
if _, err := deltaBuf.ReadByte(); err == nil {
_ = dstWr.CloseWithError(ErrInvalidDelta)
return
} else if err != io.EOF {
_ = dstWr.CloseWithError(err)
return
}
_ = dstWr.Close()
}() }()
return dstRd, nil return dstRd, nil
} }
func patchDelta(dst *bytes.Buffer, src, delta []byte) error { func patchDelta(dst *bytes.Buffer, src, delta []byte) error {
if len(delta) < minCopySize { srcSz, delta, err := decodeLEB128(delta)
return ErrInvalidDelta if err != nil {
return fmt.Errorf("%w: %w", ErrInvalidDelta, err)
} }
srcSz, delta := decodeLEB128(delta)
if srcSz != uint(len(src)) { if srcSz != uint(len(src)) {
return ErrInvalidDelta return ErrInvalidDelta
} }
targetSz, delta := decodeLEB128(delta) targetSz, delta, err := decodeLEB128(delta)
if err != nil {
return fmt.Errorf("%w: %w", ErrInvalidDelta, err)
}
remainingTargetSz := targetSz remainingTargetSz := targetSz
var cmd byte
growSz := min(targetSz, maxPatchPreemptionSize) growSz := min(targetSz, maxPatchPreemptionSize)
dst.Grow(int(growSz)) dst.Grow(int(growSz))
for {
for remainingTargetSz > 0 {
if len(delta) == 0 { if len(delta) == 0 {
return ErrInvalidDelta return ErrInvalidDelta
} }
cmd = delta[0] cmd := delta[0]
delta = delta[1:] delta = delta[1:]
switch { switch {
@@ -275,16 +288,16 @@ func patchDelta(dst *bytes.Buffer, src, delta []byte) error {
return err return err
} }
if invalidSize(sz, targetSz) || if invalidSize(sz, remainingTargetSz) ||
invalidOffsetSize(offset, sz, srcSz) { invalidOffsetSize(offset, sz, srcSz) {
break return ErrInvalidDelta
} }
dst.Write(src[offset : offset+sz]) dst.Write(src[offset : offset+sz])
remainingTargetSz -= sz remainingTargetSz -= sz
case isCopyFromDelta(cmd): case isCopyFromDelta(cmd):
sz := uint(cmd) // cmd is the size itself sz := uint(cmd) // cmd is the size itself
if invalidSize(sz, targetSz) { if invalidSize(sz, remainingTargetSz) {
return ErrInvalidDelta return ErrInvalidDelta
} }
@@ -299,10 +312,12 @@ func patchDelta(dst *bytes.Buffer, src, delta []byte) error {
default: default:
return ErrDeltaCmd return ErrDeltaCmd
} }
}
if remainingTargetSz <= 0 { // Mirror upstream's `data != top` post-loop check: every byte of
break // the delta payload must be consumed.
} if len(delta) != 0 {
return ErrInvalidDelta
} }
return nil return nil
@@ -354,7 +369,7 @@ func patchDeltaWriter(dst io.Writer, base io.ReaderAt, delta io.Reader,
baselr := io.LimitReader(sr, 0).(*io.LimitedReader) baselr := io.LimitReader(sr, 0).(*io.LimitedReader)
deltalr := io.LimitReader(deltaBuf, 0).(*io.LimitedReader) deltalr := io.LimitReader(deltaBuf, 0).(*io.LimitedReader)
for { for remainingTargetSz > 0 {
buf := *bufp buf := *bufp
cmd, err := deltaBuf.ReadByte() cmd, err := deltaBuf.ReadByte()
if err == io.EOF { if err == io.EOF {
@@ -374,9 +389,9 @@ func patchDeltaWriter(dst io.Writer, base io.ReaderAt, delta io.Reader,
return 0, plumbing.ZeroHash, err return 0, plumbing.ZeroHash, err
} }
if invalidSize(sz, targetSz) || if invalidSize(sz, remainingTargetSz) ||
invalidOffsetSize(offset, sz, srcSz) { invalidOffsetSize(offset, sz, srcSz) {
return 0, plumbing.ZeroHash, err return 0, plumbing.ZeroHash, ErrInvalidDelta
} }
if _, err := sr.Seek(int64(offset), io.SeekStart); err != nil { if _, err := sr.Seek(int64(offset), io.SeekStart); err != nil {
@@ -389,7 +404,7 @@ func patchDeltaWriter(dst io.Writer, base io.ReaderAt, delta io.Reader,
remainingTargetSz -= sz remainingTargetSz -= sz
} else if isCopyFromDelta(cmd) { } else if isCopyFromDelta(cmd) {
sz := uint(cmd) // cmd is the size itself sz := uint(cmd) // cmd is the size itself
if invalidSize(sz, targetSz) { if invalidSize(sz, remainingTargetSz) {
return 0, plumbing.ZeroHash, ErrInvalidDelta return 0, plumbing.ZeroHash, ErrInvalidDelta
} }
deltalr.N = int64(sz) deltalr.N = int64(sz)
@@ -399,30 +414,41 @@ func patchDeltaWriter(dst io.Writer, base io.ReaderAt, delta io.Reader,
remainingTargetSz -= sz remainingTargetSz -= sz
} else { } else {
return 0, plumbing.ZeroHash, err return 0, plumbing.ZeroHash, ErrDeltaCmd
}
if remainingTargetSz <= 0 {
break
} }
} }
// Mirror upstream's `data != top` post-loop check: every byte of
// the delta payload must be consumed.
if _, err := deltaBuf.ReadByte(); err == nil {
return 0, plumbing.ZeroHash, ErrInvalidDelta
} else if err != io.EOF {
return 0, plumbing.ZeroHash, err
}
return targetSz, hasher.Sum(), nil return targetSz, hasher.Sum(), nil
} }
// Decodes a number encoded as an unsigned LEB128 at the start of some // Decodes a number encoded as an unsigned LEB128 at the start of some
// binary data and returns the decoded number and the rest of the // binary data and returns the decoded number, the rest of the stream,
// stream. // and an error if the encoded value does not fit in a uint.
// //
// This must be called twice on the delta data buffer, first to get the // This must be called twice on the delta data buffer, first to get the
// expected source buffer size, and again to get the target buffer size. // expected source buffer size, and again to get the target buffer size.
func decodeLEB128(input []byte) (uint, []byte) { func decodeLEB128(input []byte) (uint, []byte, error) {
if len(input) == 0 { if len(input) == 0 {
return 0, input return 0, input, nil
} }
var num, sz uint var num, sz uint
var b byte var b byte
for { for {
// A continuation byte at shift > uintBits-7 cannot contribute
// without overflowing the accumulator.
if sz*7 > uintBits-7 {
return 0, input, ErrLengthOverflow
}
b = input[sz] b = input[sz]
num |= (uint(b) & payload) << (sz * 7) // concats 7 bits chunks num |= (uint(b) & payload) << (sz * 7) // concats 7 bits chunks
sz++ sz++
@@ -432,12 +458,16 @@ func decodeLEB128(input []byte) (uint, []byte) {
} }
} }
return num, input[sz:] return num, input[sz:], nil
} }
func decodeLEB128ByteReader(input io.ByteReader) (uint, error) { func decodeLEB128ByteReader(input io.ByteReader) (uint, error) {
var num, sz uint var num, sz uint
for { for {
if sz*7 > uintBits-7 {
return 0, ErrLengthOverflow
}
b, err := input.ReadByte() b, err := input.ReadByte()
if err != nil { if err != nil {
return 0, err return 0, err
@@ -529,8 +559,9 @@ func decodeSize(cmd byte, delta []byte) (uint, []byte, error) {
return sz, delta, nil return sz, delta, nil
} }
func invalidSize(sz, targetSz uint) bool { // invalidSize reports whether sz exceeds the remaining target size.
return sz > targetSz func invalidSize(sz, remaining uint) bool {
return sz > remaining
} }
func invalidOffsetSize(offset, sz, srcSz uint) bool { func invalidOffsetSize(offset, sz, srcSz uint) bool {
+145 -9
View File
@@ -29,8 +29,100 @@ var (
ErrSeekNotSupported = NewError("not seek support") ErrSeekNotSupported = NewError("not seek support")
// ErrMalformedPackFile is returned by the parser when the pack file is corrupted. // ErrMalformedPackFile is returned by the parser when the pack file is corrupted.
ErrMalformedPackFile = errors.New("malformed PACK file") ErrMalformedPackFile = errors.New("malformed PACK file")
// ErrLengthOverflow is returned when a variable-length integer would not
// fit into its accumulator because the input declares more continuation
// bytes than the type can hold.
ErrLengthOverflow = errors.New("variable-length integer overflow")
// ErrInflatedSizeMismatch is returned when a packfile object inflates to
// more bytes than the size declared in its object header. A well-formed
// packfile never produces more data than the declared size; exceeding it
// indicates a structurally invalid entry.
ErrInflatedSizeMismatch = errors.New("packfile: inflated object exceeds declared size")
) )
// boundedWriter passes writes through to w up to limit bytes total, then
// returns ErrInflatedSizeMismatch. It is used to enforce that a packfile
// object's inflated length does not exceed the size declared in its header.
type boundedWriter struct {
w io.Writer
limit int64
n int64
}
// Write forwards p to the underlying writer while keeping the running total
// at or below limit. On overrun it forwards the legal prefix and reports
// the number of bytes actually consumed alongside ErrInflatedSizeMismatch,
// matching the contract in io.Writer. A write error from the underlying
// writer during overrun-handling is joined with ErrInflatedSizeMismatch so
// it is not silently dropped.
func (b *boundedWriter) Write(p []byte) (int, error) {
if b.n+int64(len(p)) > b.limit {
remain := int(b.limit - b.n)
err := error(ErrInflatedSizeMismatch)
if remain > 0 {
n, werr := b.w.Write(p[:remain])
b.n += int64(n)
if werr != nil {
err = errors.Join(ErrInflatedSizeMismatch, werr)
}
return n, err
}
return 0, err
}
n, err := b.w.Write(p)
b.n += int64(n)
return n, err
}
// boundedReadCloser wraps a ReadCloser and reports ErrInflatedSizeMismatch
// once more than limit bytes have been read. It is used by the on-demand
// object reader returned from FSObject.Reader so that a lazy Read of a
// packfile object cannot stream past its declared inflated size.
//
// The implementation builds on io.LimitedReader with the standard
// overrun-detection trick: request limit+1 bytes from the underlying so
// that the moment the sentinel byte materializes (LimitedReader.N drops
// to zero) we know the source produced more than limit bytes.
type boundedReadCloser struct {
lr io.LimitedReader
closer io.Closer
overrun bool
}
// newBoundedReadCloser wraps rc so that the cumulative bytes returned from
// Read never exceed limit. The first call that would have returned a byte
// past limit instead returns ErrInflatedSizeMismatch; subsequent calls
// keep returning the same error. A negative limit is treated as zero, so
// the first byte produced by rc surfaces ErrInflatedSizeMismatch.
func newBoundedReadCloser(rc io.ReadCloser, limit int64) *boundedReadCloser {
if limit < 0 {
limit = 0
}
return &boundedReadCloser{
lr: io.LimitedReader{R: rc, N: limit + 1},
closer: rc,
}
}
// Read forwards Read up to the configured byte limit. When the underlying
// stream produces the limit+1 sentinel byte, the legal prefix is returned
// alongside ErrInflatedSizeMismatch; on subsequent calls only the error
// is returned.
func (b *boundedReadCloser) Read(p []byte) (int, error) {
if b.overrun {
return 0, ErrInflatedSizeMismatch
}
n, err := b.lr.Read(p)
if b.lr.N == 0 {
b.overrun = true
return n - 1, ErrInflatedSizeMismatch
}
return n, err
}
// Close closes the underlying ReadCloser.
func (b *boundedReadCloser) Close() error { return b.closer.Close() }
// ObjectHeader contains the information related to the object, this information // ObjectHeader contains the information related to the object, this information
// is collected from the previous bytes to the content of the object. // is collected from the previous bytes to the content of the object.
type ObjectHeader struct { type ObjectHeader struct {
@@ -220,6 +312,13 @@ func (s *Scanner) nextObjectHeader() (*ObjectHeader, error) {
return nil, err return nil, err
} }
// An OFS-delta references a base object that appears earlier
// in the pack; the negative offset must be strictly positive
// and not larger than the current object's offset.
if no <= 0 || no > h.Offset {
return nil, fmt.Errorf("%w: invalid OFS delta offset", ErrMalformedPackFile)
}
h.OffsetReference = h.Offset - no h.OffsetReference = h.Offset - no
case plumbing.REFDeltaObject: case plumbing.REFDeltaObject:
var err error var err error
@@ -303,6 +402,13 @@ func (s *Scanner) readLength(first byte) (int64, error) {
shift := firstLengthBits shift := firstLengthBits
var err error var err error
for c&maskContinue > 0 { for c&maskContinue > 0 {
// Mirrors unpack_object_header_buffer in canonical Git's
// packfile.c: a continuation byte at shift > 64-7 cannot
// contribute without overflowing an int64.
if shift > 64-lengthBits {
return 0, fmt.Errorf("%w: %w", ErrMalformedPackFile, ErrLengthOverflow)
}
if c, err = s.r.ReadByte(); err != nil { if c, err = s.r.ReadByte(); err != nil {
return 0, err return 0, err
} }
@@ -315,10 +421,18 @@ func (s *Scanner) readLength(first byte) (int64, error) {
} }
// NextObject writes the content of the next object into the reader, returns // NextObject writes the content of the next object into the reader, returns
// the number of bytes written, the CRC32 of the content and an error, if any // the number of bytes written, the CRC32 of the content and an error, if any.
//
// When a prior NextObjectHeader has stashed the object header in
// pendingObject, the inflated stream is bounded by the header's declared
// length and surfaces ErrInflatedSizeMismatch on overrun.
func (s *Scanner) NextObject(w io.Writer) (written int64, crc32 uint32, err error) { func (s *Scanner) NextObject(w io.Writer) (written int64, crc32 uint32, err error) {
declaredSize := int64(-1)
if s.pendingObject != nil {
declaredSize = s.pendingObject.Length
}
s.pendingObject = nil s.pendingObject = nil
written, err = s.copyObject(w) written, err = s.copyObject(w, declaredSize)
s.r.Flush() s.r.Flush()
crc32 = s.crc.Sum32() crc32 = s.crc.Sum32()
@@ -327,23 +441,39 @@ func (s *Scanner) NextObject(w io.Writer) (written int64, crc32 uint32, err erro
return return
} }
// ReadObject returns a reader for the object content and an error // ReadObject returns a reader for the object content and an error.
//
// When a prior NextObjectHeader has stashed the object header in
// pendingObject, the returned reader is bounded by the header's declared
// length so callers cannot stream past the declared inflated size; an
// overrun surfaces ErrInflatedSizeMismatch on the byte just past the
// limit.
func (s *Scanner) ReadObject() (io.ReadCloser, error) { func (s *Scanner) ReadObject() (io.ReadCloser, error) {
declaredSize := int64(-1)
if s.pendingObject != nil {
declaredSize = s.pendingObject.Length
}
s.pendingObject = nil s.pendingObject = nil
zr, err := sync.GetZlibReader(s.r) zr, err := sync.GetZlibReader(s.r)
if err != nil { if err != nil {
return nil, fmt.Errorf("zlib reset error: %s", err) return nil, fmt.Errorf("zlib reset error: %s", err)
} }
return ioutil.NewReadCloserWithCloser(zr.Reader, func() error { rc := ioutil.NewReadCloserWithCloser(zr.Reader, func() error {
sync.PutZlibReader(zr) sync.PutZlibReader(zr)
return nil return nil
}), nil })
if declaredSize >= 0 {
return newBoundedReadCloser(rc, declaredSize), nil
}
return rc, nil
} }
// ReadRegularObject reads and write a non-deltified object // copyObject inflates a non-deltified object's zlib stream into w. When
// from it zlib stream in an object entry in the packfile. // declaredSize is non-negative, the write sink is wrapped in a
func (s *Scanner) copyObject(w io.Writer) (n int64, err error) { // boundedWriter so an overrun surfaces ErrInflatedSizeMismatch instead
// of being silently appended.
func (s *Scanner) copyObject(w io.Writer, declaredSize int64) (n int64, err error) {
zr, err := sync.GetZlibReader(s.r) zr, err := sync.GetZlibReader(s.r)
defer sync.PutZlibReader(zr) defer sync.PutZlibReader(zr)
@@ -352,8 +482,14 @@ func (s *Scanner) copyObject(w io.Writer) (n int64, err error) {
} }
defer ioutil.CheckClose(zr.Reader, &err) defer ioutil.CheckClose(zr.Reader, &err)
sink := w
if declaredSize >= 0 {
sink = &boundedWriter{w: w, limit: declaredSize}
}
buf := sync.GetByteSlice() buf := sync.GetByteSlice()
n, err = io.CopyBuffer(w, zr.Reader, *buf) n, err = io.CopyBuffer(sink, zr.Reader, *buf)
sync.PutByteSlice(buf) sync.PutByteSlice(buf)
return return
} }
+113 -94
View File
@@ -5,7 +5,7 @@ import (
"context" "context"
"errors" "errors"
"fmt" "fmt"
"io" "slices"
"strings" "strings"
"github.com/ProtonMail/go-crypto/openpgp" "github.com/ProtonMail/go-crypto/openpgp"
@@ -20,6 +20,7 @@ const (
beginpgp string = "-----BEGIN PGP SIGNATURE-----" beginpgp string = "-----BEGIN PGP SIGNATURE-----"
endpgp string = "-----END PGP SIGNATURE-----" endpgp string = "-----END PGP SIGNATURE-----"
headerpgp string = "gpgsig" headerpgp string = "gpgsig"
headerpgp256 string = "gpgsig-sha256"
headerencoding string = "encoding" headerencoding string = "encoding"
// https://github.com/git/git/blob/bcb6cae2966cc407ca1afc77413b3ef11103c175/Documentation/gitformat-signature.txt#L153 // https://github.com/git/git/blob/bcb6cae2966cc407ca1afc77413b3ef11103c175/Documentation/gitformat-signature.txt#L153
@@ -41,6 +42,11 @@ type MessageEncoding string
// in time, such as a timestamp, the author of the changes since the last // in time, such as a timestamp, the author of the changes since the last
// commit, a pointer to the previous commit(s), etc. // commit, a pointer to the previous commit(s), etc.
// http://shafiulazam.com/gitbook/1_the_git_object_model.html // http://shafiulazam.com/gitbook/1_the_git_object_model.html
//
// When a Commit is populated by Decode it retains a reference to the source
// plumbing.EncodedObject so that EncodeWithoutSignature can reproduce the
// exact bytes the signature was computed over. Refer to EncodeWithoutSignature
// for more information.
type Commit struct { type Commit struct {
// Hash of the commit object. // Hash of the commit object.
Hash plumbing.Hash Hash plumbing.Hash
@@ -66,6 +72,9 @@ type Commit struct {
ExtraHeaders []ExtraHeader ExtraHeaders []ExtraHeader
s storer.EncodedObjectStorer s storer.EncodedObjectStorer
// src holds the encoded object this Commit was decoded from, used by
// EncodeWithoutSignature to recover the canonical signed bytes.
src plumbing.EncodedObject
} }
// ExtraHeader holds any non-standard header // ExtraHeader holds any non-standard header
@@ -98,8 +107,8 @@ func (h ExtraHeader) Format(f fmt.State, verb rune) {
func parseExtraHeader(line []byte) (ExtraHeader, bool) { func parseExtraHeader(line []byte) (ExtraHeader, bool) {
split := bytes.SplitN(line, []byte{' '}, 2) split := bytes.SplitN(line, []byte{' '}, 2)
out := ExtraHeader { out := ExtraHeader{
Key: string(bytes.TrimRight(split[0], "\n")), Key: string(bytes.TrimRight(split[0], "\n")),
Value: "", Value: "",
} }
@@ -181,6 +190,11 @@ func (c *Commit) NumParents() int {
var ErrParentNotFound = errors.New("commit parent not found") var ErrParentNotFound = errors.New("commit parent not found")
// ErrMalformedCommit is returned when a commit object cannot be decoded
// because its standard headers (tree, parent, author, committer) are missing,
// duplicated, or out of order.
var ErrMalformedCommit = errors.New("malformed commit")
// Parent returns the ith parent of a commit. // Parent returns the ith parent of a commit.
func (c *Commit) Parent(i int) (*Commit, error) { func (c *Commit) Parent(i int) (*Commit, error) {
if len(c.ParentHashes) == 0 || i > len(c.ParentHashes)-1 { if len(c.ParentHashes) == 0 || i > len(c.ParentHashes)-1 {
@@ -227,14 +241,23 @@ func (c *Commit) Type() plumbing.ObjectType {
return plumbing.CommitObject return plumbing.CommitObject
} }
func (c *Commit) reset() {
storer := c.s
*c = Commit{
Encoding: defaultUtf8CommitMessageEncoding,
s: storer,
}
}
// Decode transforms a plumbing.EncodedObject into a Commit struct. // Decode transforms a plumbing.EncodedObject into a Commit struct.
func (c *Commit) Decode(o plumbing.EncodedObject) (err error) { func (c *Commit) Decode(o plumbing.EncodedObject) (err error) {
if o.Type() != plumbing.CommitObject { if o.Type() != plumbing.CommitObject {
return ErrUnsupportedObject return ErrUnsupportedObject
} }
c.reset()
c.Hash = o.Hash() c.Hash = o.Hash()
c.Encoding = defaultUtf8CommitMessageEncoding c.src = o
reader, err := o.Reader() reader, err := o.Reader()
if err != nil { if err != nil {
@@ -245,97 +268,17 @@ func (c *Commit) Decode(o plumbing.EncodedObject) (err error) {
r := sync.GetBufioReader(reader) r := sync.GetBufioReader(reader)
defer sync.PutBufioReader(r) defer sync.PutBufioReader(r)
var message bool s := &commitScanner{r: r, c: c}
var mergetag bool for state := scanTree; state != nil; {
var pgpsig bool state, err = state(s)
var msgbuf bytes.Buffer if err != nil {
var extraheader *ExtraHeader = nil
for {
line, err := r.ReadBytes('\n')
if err != nil && err != io.EOF {
return err return err
} }
if mergetag {
if len(line) > 0 && line[0] == ' ' {
line = bytes.TrimLeft(line, " ")
c.MergeTag += string(line)
continue
} else {
mergetag = false
}
}
if pgpsig {
if len(line) > 0 && line[0] == ' ' {
line = bytes.TrimLeft(line, " ")
c.PGPSignature += string(line)
continue
} else {
pgpsig = false
}
}
if extraheader != nil {
if len(line) > 0 && line[0] == ' ' {
extraheader.Value += string(line[1:])
continue
} else {
extraheader.Value = strings.TrimRight(extraheader.Value, "\n")
c.ExtraHeaders = append(c.ExtraHeaders, *extraheader)
extraheader = nil
}
}
if !message {
original_line := line
line = bytes.TrimSpace(line)
if len(line) == 0 {
message = true
continue
}
split := bytes.SplitN(line, []byte{' '}, 2)
var data []byte
if len(split) == 2 {
data = split[1]
}
switch string(split[0]) {
case "tree":
c.TreeHash = plumbing.NewHash(string(data))
case "parent":
c.ParentHashes = append(c.ParentHashes, plumbing.NewHash(string(data)))
case "author":
c.Author.Decode(data)
case "committer":
c.Committer.Decode(data)
case headermergetag:
c.MergeTag += string(data) + "\n"
mergetag = true
case headerencoding:
c.Encoding = MessageEncoding(data)
case headerpgp:
c.PGPSignature += string(data) + "\n"
pgpsig = true
default:
h, maybecontinued := parseExtraHeader(original_line)
if maybecontinued {
extraheader = &h
} else {
c.ExtraHeaders = append(c.ExtraHeaders, h)
}
}
} else {
msgbuf.Write(line)
}
if err == io.EOF {
break
}
} }
c.Message = msgbuf.String() if !s.sawTree {
return fmt.Errorf("%w: missing tree header", ErrMalformedCommit)
}
c.Message = s.msgbuf.String()
return nil return nil
} }
@@ -344,11 +287,73 @@ func (c *Commit) Encode(o plumbing.EncodedObject) error {
return c.encode(o, true) return c.encode(o, true)
} }
// EncodeWithoutSignature export a Commit into a plumbing.EncodedObject without the signature (correspond to the payload of the PGP signature). // EncodeWithoutSignature exports a Commit into a plumbing.EncodedObject
// without any signature headers, producing the payload that PGP/GPG
// signatures are computed over.
//
// Behaviour depends on how the Commit was created:
//
// - For Commits populated by Decode whose exported fields still match the
// source object, the payload is streamed from the raw source bytes with
// gpgsig and gpgsig-sha256 headers (and their continuation lines)
// stripped verbatim. This preserves the exact bytes the signature was
// computed over, regardless of any normalization performed by Decode.
//
// - For Commits constructed in memory, or for decoded Commits whose
// exported fields have been mutated, the payload is derived from the
// current struct fields. Mutation is detected by re-decoding the source
// object and comparing exported fields; if any differ, the in-memory
// representation prevails.
func (c *Commit) EncodeWithoutSignature(o plumbing.EncodedObject) error { func (c *Commit) EncodeWithoutSignature(o plumbing.EncodedObject) error {
if c.matchesSource() {
return stripObjectSignatures(o, c.src, plumbing.CommitObject)
}
return c.encode(o, false) return c.encode(o, false)
} }
// matchesSource reports whether c.src is set and re-decoding it produces a
// Commit whose payload-affecting exported fields are identical to those of
// c. It is the auto-detection used by EncodeWithoutSignature to decide
// between the raw bytes and the struct-encoded payload.
//
// PGPSignature is intentionally excluded from the comparison: neither path
// emits it, so mutating it must not trigger a switch to struct-encode (which
// would change the byte layout the caller is trying to verify against).
func (c *Commit) matchesSource() bool {
if c.src == nil {
return false
}
fresh := &Commit{}
if err := fresh.Decode(c.src); err != nil {
return false
}
return c.Hash == fresh.Hash &&
signatureEqual(c.Author, fresh.Author) &&
signatureEqual(c.Committer, fresh.Committer) &&
c.MergeTag == fresh.MergeTag &&
c.Message == fresh.Message &&
c.TreeHash == fresh.TreeHash &&
c.Encoding == fresh.Encoding &&
slices.Equal(c.ParentHashes, fresh.ParentHashes) &&
slices.Equal(c.ExtraHeaders, fresh.ExtraHeaders)
}
func signatureEqual(a, b Signature) bool {
return a.Name == b.Name &&
a.Email == b.Email &&
a.When.Unix() == b.When.Unix() &&
a.When.Format("-0700") == b.When.Format("-0700")
}
func isStandardHeader(key string) bool {
switch key {
case "tree", "parent", "author", "committer",
headerencoding, headermergetag, headerpgp, headerpgp256:
return true
}
return false
}
func (c *Commit) encode(o plumbing.EncodedObject, includeSig bool) (err error) { func (c *Commit) encode(o plumbing.EncodedObject, includeSig bool) (err error) {
o.SetType(plumbing.CommitObject) o.SetType(plumbing.CommitObject)
w, err := o.Writer() w, err := o.Writer()
@@ -407,7 +412,9 @@ func (c *Commit) encode(o plumbing.EncodedObject, includeSig bool) (err error) {
} }
for _, header := range c.ExtraHeaders { for _, header := range c.ExtraHeaders {
if isStandardHeader(header.Key) {
continue
}
if _, err = fmt.Fprintf(w, "\n%s", header); err != nil { if _, err = fmt.Fprintf(w, "\n%s", header); err != nil {
return err return err
} }
@@ -478,9 +485,21 @@ func (c *Commit) String() string {
) )
} }
// ErrMultipleSignatures is returned by Verify when the commit carries more
// than one armored signature block. Mirrors upstream's parse_gpg_output
// rejection of GOODSIG/BADSIG status lines after the first
// (gpg-interface.c:257-269): multi-signature commits are intentionally
// unsupported because their provenance cannot be reduced to a single
// authoritative signer.
var ErrMultipleSignatures = errors.New("commit has multiple signatures")
// Verify performs PGP verification of the commit with a provided armored // Verify performs PGP verification of the commit with a provided armored
// keyring and returns openpgp.Entity associated with verifying key on success. // keyring and returns openpgp.Entity associated with verifying key on success.
func (c *Commit) Verify(armoredKeyRing string) (*openpgp.Entity, error) { func (c *Commit) Verify(armoredKeyRing string) (*openpgp.Entity, error) {
if countSignatureBlocks([]byte(c.PGPSignature)) > 1 {
return nil, ErrMultipleSignatures
}
keyRingReader := strings.NewReader(armoredKeyRing) keyRingReader := strings.NewReader(armoredKeyRing)
keyring, err := openpgp.ReadArmoredKeyRing(keyRingReader) keyring, err := openpgp.ReadArmoredKeyRing(keyRingReader)
if err != nil { if err != nil {
+377
View File
@@ -0,0 +1,377 @@
package object
import (
"bufio"
"bytes"
"fmt"
"io"
"strings"
"github.com/go-git/go-git/v5/plumbing"
)
// commitScanner holds the working state of the commit decoder driven by the
// stateFn loop in (*Commit).Decode. Each commitState reads one or more lines
// from r, updates the in-progress *Commit and the scanner's bookkeeping, and
// returns the state that should run next (or nil to stop).
type commitScanner struct {
r *bufio.Reader
c *Commit
msgbuf bytes.Buffer
// pending holds a line that was read but the current state decided to
// hand back to the next state, paired with the io.EOF flag that was
// returned when the line was originally read.
pending []byte
pendingErr error
// First-occurrence tracking: once the corresponding field has been
// decoded, subsequent occurrences are silently dropped (matches
// upstream's find_commit_header / first-wins semantics).
//
// gpgsig is not tracked here: upstream's parse_buffer_signed_by_header
// (commit.c:1186) accumulates every occurrence into one signature buffer,
// so we do the same on the scanner side to keep verification payloads
// byte-aligned. gpgsig-sha256 is recognized and skipped without exposing a
// new field in v5.
sawTree, sawAuthor, sawCommitter bool
sawEncoding, sawMergetag bool
// extra is the multi-line ExtraHeader currently being assembled.
extra *ExtraHeader
}
// commitState is one step of the decoder state machine. Each function reads
// the lines it needs, mutates *Commit via s.c, and returns the next state to
// run (or nil to terminate the loop).
type commitState func(*commitScanner) (commitState, error)
// readLine returns the next line from the buffer, transparently consuming any
// line that was previously pushed back by a state that decided not to handle
// it.
func (s *commitScanner) readLine() ([]byte, error) {
if s.pending != nil {
line, err := s.pending, s.pendingErr
s.pending, s.pendingErr = nil, nil
return line, err
}
line, err := s.r.ReadBytes('\n')
if err != nil && err != io.EOF {
return line, err
}
return line, err
}
// pushBack stashes an unconsumed line so the next state's readLine call sees
// it. Only one line can be pushed back at a time.
func (s *commitScanner) pushBack(line []byte, err error) {
s.pending = line
s.pendingErr = err
}
// scanTree expects the first non-empty header to be `tree HASH`. Anything
// else (or an empty buffer) is rejected with ErrMalformedCommit, matching
// upstream's `bogus commit object` check.
func scanTree(s *commitScanner) (commitState, error) {
line, err := s.readLine()
if err != nil && err != io.EOF {
return nil, err
}
if len(line) == 0 || isBlankLine(line) {
return nil, fmt.Errorf("%w: missing tree header", ErrMalformedCommit)
}
key, data := splitHeader(line)
if key != "tree" {
return nil, fmt.Errorf("%w: tree header must be first", ErrMalformedCommit)
}
h, herr := parseObjectIDHex(data, ErrMalformedCommit, "tree")
if herr != nil {
return nil, herr
}
s.c.TreeHash = h
s.sawTree = true
if err == io.EOF {
return nil, nil
}
return scanParents, nil
}
// scanParents consumes contiguous `parent HASH` lines. The first non-parent
// line ends the parent block and is handed off to scanAuthor; any later
// `parent` line is silently dropped (matches upstream's parse_commit_buffer
// exiting its parent loop at the first non-parent line and
// read_commit_extra_header_lines filtering `parent` out of extras).
func scanParents(s *commitScanner) (commitState, error) {
line, err := s.readLine()
if err != nil && err != io.EOF {
return nil, err
}
if len(line) == 0 {
return nil, nil
}
if isBlankLine(line) {
return scanMessage, nil
}
key, data := splitHeader(line)
if key == "parent" {
h, herr := parseObjectIDHex(data, ErrMalformedCommit, "parent")
if herr != nil {
return nil, herr
}
s.c.ParentHashes = append(s.c.ParentHashes, h)
if err == io.EOF {
return nil, nil
}
return scanParents, nil
}
s.pushBack(line, err)
return scanAuthor, nil
}
// scanAuthor accepts an `author` line at its canonical position immediately
// after the parent block. Any other header here is pushed back for
// scanCommitter; an out-of-place author is therefore silently dropped.
// Mirrors upstream's parse_commit_date func.
func scanAuthor(s *commitScanner) (commitState, error) {
line, err := s.readLine()
if err != nil && err != io.EOF {
return nil, err
}
if len(line) == 0 {
return nil, nil
}
if isBlankLine(line) {
return scanMessage, nil
}
key, data := splitHeader(line)
if key == "author" {
s.c.Author.Decode(data)
s.sawAuthor = true
if err == io.EOF {
return nil, nil
}
return scanCommitter, nil
}
s.pushBack(line, err)
return scanCommitter, nil
}
// scanCommitter accepts a `committer` line at its canonical position
// immediately after the author. Any other header is pushed back for
// scanHeaders. Same upstream rationale as scanAuthor.
func scanCommitter(s *commitScanner) (commitState, error) {
line, err := s.readLine()
if err != nil && err != io.EOF {
return nil, err
}
if len(line) == 0 {
return nil, nil
}
if isBlankLine(line) {
return scanMessage, nil
}
key, data := splitHeader(line)
if key == "committer" {
s.c.Committer.Decode(data)
s.sawCommitter = true
if err == io.EOF {
return nil, nil
}
return scanHeaders, nil
}
s.pushBack(line, err)
return scanHeaders, nil
}
// scanHeaders dispatches one header line. Continuation-bearing headers
// (mergetag, gpgsig, gpgsig-sha256, and unknown extras whose value is
// continued on subsequent lines) hand off to a dedicated continuation state
// that handles the `<space>...` lines and then returns here.
func scanHeaders(s *commitScanner) (commitState, error) {
line, err := s.readLine()
if err != nil && err != io.EOF {
return nil, err
}
if len(line) == 0 {
return nil, nil
}
if isBlankLine(line) {
return scanMessage, nil
}
originalLine := line
key, data := splitHeader(line)
var next commitState = scanHeaders
switch key {
case "tree", "parent", "author", "committer":
// Anything reaching scanHeaders with one of these keys is out of
// canonical position: duplicate tree, parent past the contiguous
// block, or author/committer not at their expected slot. Drop them
// the same way upstream's standard_header_field filter excludes
// them from the extras list (read_commit_extra_header_lines,
// commit.c:1520-1522).
case headerencoding:
if !s.sawEncoding {
s.c.Encoding = MessageEncoding(data)
s.sawEncoding = true
}
case headermergetag:
if s.sawMergetag {
next = scanSkipCont
} else {
s.c.MergeTag += string(data) + "\n"
s.sawMergetag = true
next = scanMergetagCont
}
case headerpgp:
s.c.PGPSignature += string(data) + "\n"
next = scanPgpCont
case headerpgp256:
next = scanSkipCont
default:
h, multiline := parseExtraHeader(originalLine)
if multiline {
s.extra = &h
next = scanExtraCont
} else {
s.c.ExtraHeaders = append(s.c.ExtraHeaders, h)
}
}
if err == io.EOF {
return nil, nil
}
return next, nil
}
// scanMergetagCont accumulates continuation lines for the first mergetag
// header. Continuations strip exactly one leading space, mirroring upstream's
// `line + 1` (commit.c:1509). The first non-continuation line is pushed back
// so scanHeaders can dispatch it.
func scanMergetagCont(s *commitScanner) (commitState, error) {
return continuationCont(s, &s.c.MergeTag, scanMergetagCont)
}
// scanPgpCont accumulates continuation lines for a signature header.
// Continuations strip exactly one leading space, mirroring upstream's
// `line + 1` (commit.c:1509). The first non-continuation line is pushed back
// so scanHeaders can dispatch it. Repeat occurrences of the same signature
// header land back here and concatenate, matching upstream's
// parse_buffer_signed_by_header (commit.c:1186).
func scanPgpCont(s *commitScanner) (commitState, error) {
return continuationCont(s, &s.c.PGPSignature, scanPgpCont)
}
func continuationCont(s *commitScanner, dst *string, self commitState) (commitState, error) {
line, err := s.readLine()
if err != nil && err != io.EOF {
return nil, err
}
if len(line) > 0 && line[0] == ' ' {
*dst += string(line[1:])
if err == io.EOF {
return nil, nil
}
return self, nil
}
if len(line) > 0 {
s.pushBack(line, err)
}
return scanHeaders, nil
}
// scanSkipCont discards continuation lines that belong to a header scanHeaders
// chose to drop.
func scanSkipCont(s *commitScanner) (commitState, error) {
line, err := s.readLine()
if err != nil && err != io.EOF {
return nil, err
}
if len(line) > 0 && line[0] == ' ' {
if err == io.EOF {
return nil, nil
}
return scanSkipCont, nil
}
if len(line) > 0 {
s.pushBack(line, err)
}
return scanHeaders, nil
}
// scanExtraCont accumulates continuation lines for an unknown ExtraHeader
// whose value spans multiple lines, then finalises the entry once the
// continuation block ends.
func scanExtraCont(s *commitScanner) (commitState, error) {
line, err := s.readLine()
if err != nil && err != io.EOF {
return nil, err
}
if len(line) > 0 && line[0] == ' ' {
s.extra.Value += string(line[1:])
if err == io.EOF {
s.finaliseExtra()
return nil, nil
}
return scanExtraCont, nil
}
s.finaliseExtra()
if len(line) > 0 {
s.pushBack(line, err)
}
return scanHeaders, nil
}
func (s *commitScanner) finaliseExtra() {
s.extra.Value = strings.TrimRight(s.extra.Value, "\n")
s.c.ExtraHeaders = append(s.c.ExtraHeaders, *s.extra)
s.extra = nil
}
// scanMessage drains the remaining bytes into the message buffer.
func scanMessage(s *commitScanner) (commitState, error) {
for {
line, err := s.readLine()
if err != nil && err != io.EOF {
return nil, err
}
if len(line) > 0 {
s.msgbuf.Write(line)
}
if err == io.EOF {
return nil, nil
}
}
}
// isBlankLine reports whether line is the canonical header/body separator:
// a single newline. Mirrors upstream's `*line == '\n'` test in
// read_commit_extra_header_lines (commit.c:1502).
func isBlankLine(line []byte) bool {
return len(line) == 1 && line[0] == '\n'
}
// splitHeader returns the header keyword (everything before the first space)
// and the value (everything after, with the trailing newline stripped). If
// the header has no value the returned data is nil.
func splitHeader(line []byte) (string, []byte) {
trimmed := bytes.TrimRight(line, "\n")
key, value, ok := bytes.Cut(trimmed, []byte{' '})
if !ok {
return string(trimmed), nil
}
return string(key), value
}
func parseObjectIDHex(data []byte, malformedErr error, header string) (plumbing.Hash, error) {
id := string(data)
if !plumbing.IsHash(id) {
return plumbing.ZeroHash, fmt.Errorf("%w: bad %s hash", malformedErr, header)
}
return plumbing.NewHash(id), nil
}
+121 -1
View File
@@ -1,6 +1,13 @@
package object package object
import "bytes" import (
"bytes"
"io"
"github.com/go-git/go-git/v5/plumbing"
"github.com/go-git/go-git/v5/utils/ioutil"
"github.com/go-git/go-git/v5/utils/sync"
)
const ( const (
signatureTypeUnknown signatureType = iota signatureTypeUnknown signatureType = iota
@@ -100,3 +107,116 @@ func parseSignedBytes(b []byte) (int, signatureType) {
} }
return match, t return match, t
} }
// countSignatureBlocks reports how many distinct armored signature blocks
// start at a line boundary in b. Used by verification paths to reject
// multi-signature payloads, matching upstream's check in gpg-interface.c
// where parse_gpg_output bails out the first time it sees a second
// exclusive status line (a second GOODSIG/BADSIG/etc.).
func countSignatureBlocks(b []byte) int {
n, count := 0, 0
for n < len(b) {
i := b[n:]
if typeForSignature(i) != signatureTypeUnknown {
count++
}
if eol := bytes.IndexByte(i, '\n'); eol >= 0 {
n += eol + 1
continue
}
break
}
return count
}
// isSignatureHeader reports whether line is a canonical "gpgsig "/
// "gpgsig-sha256 " header line. Other "gpgsig"-prefixed extra headers
// are intentionally not matched.
func isSignatureHeader(line []byte) bool {
return bytes.HasPrefix(line, []byte(headerpgp+" ")) ||
bytes.HasPrefix(line, []byte(headerpgp256+" "))
}
// stripObjectSignatures streams src into dst, producing the byte sequence
// over which a PGP/GPG signature is computed:
//
// - Canonical "gpgsig" and "gpgsig-sha256" headers (and their
// continuation lines) are dropped, mirroring upstream's
// remove_signature in commit.c.
// - For tag objects, the inline trailing PGP signature is additionally
// truncated, mirroring upstream's parse_signature in gpg-interface.c
// used by gpg_verify_tag.
//
// The returned object's type is set to objType. Used by both
// Commit.EncodeWithoutSignature and Tag.EncodeWithoutSignature to
// reproduce the exact bytes the signature was computed over.
func stripObjectSignatures(dst, src plumbing.EncodedObject, objType plumbing.ObjectType) (err error) {
dst.SetType(objType)
r, err := src.Reader()
if err != nil {
return err
}
defer ioutil.CheckClose(r, &err)
var input io.Reader = r
if objType == plumbing.TagObject {
raw, err := io.ReadAll(r)
if err != nil {
return err
}
if sm, _ := parseSignedBytes(raw); sm >= 0 {
raw = raw[:sm]
}
input = bytes.NewReader(raw)
}
w, err := dst.Writer()
if err != nil {
return err
}
defer ioutil.CheckClose(w, &err)
return stripHeaderSignatures(w, input)
}
// stripHeaderSignatures copies r to w, dropping canonical signature header
// lines (gpgsig and gpgsig-sha256) and their continuation lines. Lines
// past the blank line that closes the header block are copied verbatim.
func stripHeaderSignatures(w io.Writer, r io.Reader) error {
br := sync.GetBufioReader(r)
defer sync.PutBufioReader(br)
var inBody, skipping bool
for {
line, rerr := br.ReadBytes('\n')
if rerr != nil && rerr != io.EOF {
return rerr
}
write := true
if !inBody {
switch {
case skipping && len(line) > 0 && line[0] == ' ':
write = false
case isSignatureHeader(line):
skipping = true
write = false
case len(line) == 1 && line[0] == '\n':
skipping = false
inBody = true
default:
skipping = false
}
}
if write && len(line) > 0 {
if _, werr := w.Write(line); werr != nil {
return werr
}
}
if rerr == io.EOF {
return nil
}
}
}
+90 -45
View File
@@ -1,9 +1,8 @@
package object package object
import ( import (
"bytes" "errors"
"fmt" "fmt"
"io"
"strings" "strings"
"github.com/ProtonMail/go-crypto/openpgp" "github.com/ProtonMail/go-crypto/openpgp"
@@ -13,6 +12,10 @@ import (
"github.com/go-git/go-git/v5/utils/sync" "github.com/go-git/go-git/v5/utils/sync"
) )
// ErrMalformedTag is returned when a tag object cannot be decoded because
// its required headers (object, type, tag) are missing or out of order.
var ErrMalformedTag = errors.New("malformed tag")
// Tag represents an annotated tag object. It points to a single git object of // Tag represents an annotated tag object. It points to a single git object of
// any type, but tags typically are applied to commit or blob objects. It // any type, but tags typically are applied to commit or blob objects. It
// provides a reference that associates the target with a tag name. It also // provides a reference that associates the target with a tag name. It also
@@ -39,6 +42,9 @@ type Tag struct {
Target plumbing.Hash Target plumbing.Hash
s storer.EncodedObjectStorer s storer.EncodedObjectStorer
// src holds the encoded object this Tag was decoded from, used by
// EncodeWithoutSignature to recover the canonical signed bytes.
src plumbing.EncodedObject
} }
// GetTag gets a tag from an object storer and decodes it. // GetTag gets a tag from an object storer and decodes it.
@@ -77,13 +83,20 @@ func (t *Tag) Type() plumbing.ObjectType {
return plumbing.TagObject return plumbing.TagObject
} }
func (t *Tag) reset() {
storer := t.s
*t = Tag{s: storer}
}
// Decode transforms a plumbing.EncodedObject into a Tag struct. // Decode transforms a plumbing.EncodedObject into a Tag struct.
func (t *Tag) Decode(o plumbing.EncodedObject) (err error) { func (t *Tag) Decode(o plumbing.EncodedObject) (err error) {
if o.Type() != plumbing.TagObject { if o.Type() != plumbing.TagObject {
return ErrUnsupportedObject return ErrUnsupportedObject
} }
t.reset()
t.Hash = o.Hash() t.Hash = o.Hash()
t.src = o
reader, err := o.Reader() reader, err := o.Reader()
if err != nil { if err != nil {
@@ -94,42 +107,15 @@ func (t *Tag) Decode(o plumbing.EncodedObject) (err error) {
r := sync.GetBufioReader(reader) r := sync.GetBufioReader(reader)
defer sync.PutBufioReader(r) defer sync.PutBufioReader(r)
for { scanner := &tagScanner{r: r, t: t}
var line []byte for state := scanTagObject; state != nil; {
line, err = r.ReadBytes('\n') state, err = state(scanner)
if err != nil && err != io.EOF { if err != nil {
return err return err
} }
line = bytes.TrimSpace(line)
if len(line) == 0 {
break // Start of message
}
split := bytes.SplitN(line, []byte{' '}, 2)
switch string(split[0]) {
case "object":
t.Target = plumbing.NewHash(string(split[1]))
case "type":
t.TargetType, err = plumbing.ParseObjectType(string(split[1]))
if err != nil {
return err
}
case "tag":
t.Name = string(split[1])
case "tagger":
t.Tagger.Decode(split[1])
}
if err == io.EOF {
return nil
}
} }
data, err := io.ReadAll(r) data := scanner.msgbuf.Bytes()
if err != nil {
return err
}
if sm, _ := parseSignedBytes(data); sm >= 0 { if sm, _ := parseSignedBytes(data); sm >= 0 {
t.PGPSignature = string(data[sm:]) t.PGPSignature = string(data[sm:])
data = data[:sm] data = data[:sm]
@@ -144,11 +130,54 @@ func (t *Tag) Encode(o plumbing.EncodedObject) error {
return t.encode(o, true) return t.encode(o, true)
} }
// EncodeWithoutSignature export a Tag into a plumbing.EncodedObject without the signature (correspond to the payload of the PGP signature). // EncodeWithoutSignature exports a Tag into a plumbing.EncodedObject without
// any signature data, producing the payload that PGP/GPG signatures are
// computed over.
//
// Behaviour mirrors Commit.EncodeWithoutSignature:
//
// - For Tags populated by Decode whose exported fields still match the
// source object, the payload is streamed from the raw source bytes with
// the inline trailing signature truncated and gpgsig/gpgsig-sha256
// headers (and their continuation lines) stripped verbatim. This
// preserves the exact bytes the signature was computed over, regardless
// of any normalization performed by Decode.
//
// - For Tags constructed in memory, or for decoded Tags whose exported
// fields have been mutated, the payload is derived from the current
// struct fields. Mutation is detected by re-decoding the source object
// and comparing exported fields; if any differ, the in-memory
// representation prevails.
func (t *Tag) EncodeWithoutSignature(o plumbing.EncodedObject) error { func (t *Tag) EncodeWithoutSignature(o plumbing.EncodedObject) error {
if t.matchesSource() {
return stripObjectSignatures(o, t.src, plumbing.TagObject)
}
return t.encode(o, false) return t.encode(o, false)
} }
// matchesSource reports whether t.src is set and re-decoding it produces a
// Tag whose payload-affecting exported fields are identical to those of t.
//
// PGPSignature is intentionally excluded from the comparison: neither path
// emits it as part of the verification payload, so mutating it must not
// trigger a switch to struct-encode (which would change the byte layout the
// caller is trying to verify against).
func (t *Tag) matchesSource() bool {
if t.src == nil {
return false
}
fresh := &Tag{}
if err := fresh.Decode(t.src); err != nil {
return false
}
return t.Hash == fresh.Hash &&
t.Name == fresh.Name &&
signatureEqual(t.Tagger, fresh.Tagger) &&
t.Message == fresh.Message &&
t.TargetType == fresh.TargetType &&
t.Target == fresh.Target
}
func (t *Tag) encode(o plumbing.EncodedObject, includeSig bool) (err error) { func (t *Tag) encode(o plumbing.EncodedObject, includeSig bool) (err error) {
o.SetType(plumbing.TagObject) o.SetType(plumbing.TagObject)
w, err := o.Writer() w, err := o.Writer()
@@ -158,16 +187,26 @@ func (t *Tag) encode(o plumbing.EncodedObject, includeSig bool) (err error) {
defer ioutil.CheckClose(w, &err) defer ioutil.CheckClose(w, &err)
if _, err = fmt.Fprintf(w, if _, err = fmt.Fprintf(w,
"object %s\ntype %s\ntag %s\ntagger ", "object %s\ntype %s\ntag %s\n",
t.Target.String(), t.TargetType.Bytes(), t.Name); err != nil { t.Target.String(), t.TargetType.Bytes(), t.Name); err != nil {
return err return err
} }
if err = t.Tagger.Encode(w); err != nil { if !isZeroSignature(t.Tagger) {
return err if _, err = fmt.Fprint(w, "tagger "); err != nil {
return err
}
if err = t.Tagger.Encode(w); err != nil {
return err
}
if _, err = fmt.Fprint(w, "\n"); err != nil {
return err
}
} }
if _, err = fmt.Fprint(w, "\n\n"); err != nil { if _, err = fmt.Fprint(w, "\n"); err != nil {
return err return err
} }
@@ -175,11 +214,12 @@ func (t *Tag) encode(o plumbing.EncodedObject, includeSig bool) (err error) {
return err return err
} }
// Note that this is highly sensitive to what it sent along in the message. // Note that this is highly sensitive to what is sent along in the
// Message *always* needs to end with a newline, or else the message and the // message. Message *always* needs to end with a newline, or else the
// signature will be concatenated into a corrupt object. Since this is a // message and the trailing signature will be concatenated into a
// lower-level method, we assume you know what you are doing and have already // corrupt object. Since this is a lower-level method, we assume you
// done the needful on the message in the caller. // know what you are doing and have already done the needful on the
// message in the caller.
if includeSig { if includeSig {
if _, err = fmt.Fprint(w, t.PGPSignature); err != nil { if _, err = fmt.Fprint(w, t.PGPSignature); err != nil {
return err return err
@@ -189,6 +229,10 @@ func (t *Tag) encode(o plumbing.EncodedObject, includeSig bool) (err error) {
return err return err
} }
func isZeroSignature(s Signature) bool {
return s.Name == "" && s.Email == "" && s.When.IsZero()
}
// Commit returns the commit pointed to by the tag. If the tag points to a // Commit returns the commit pointed to by the tag. If the tag points to a
// different type of object ErrUnsupportedObject will be returned. // different type of object ErrUnsupportedObject will be returned.
func (t *Tag) Commit() (*Commit, error) { func (t *Tag) Commit() (*Commit, error) {
@@ -256,7 +300,8 @@ func (t *Tag) String() string {
} }
// Verify performs PGP verification of the tag with a provided armored // Verify performs PGP verification of the tag with a provided armored
// keyring and returns openpgp.Entity associated with verifying key on success. // keyring and returns openpgp.Entity associated with verifying key on
// success.
func (t *Tag) Verify(armoredKeyRing string) (*openpgp.Entity, error) { func (t *Tag) Verify(armoredKeyRing string) (*openpgp.Entity, error) {
keyRingReader := strings.NewReader(armoredKeyRing) keyRingReader := strings.NewReader(armoredKeyRing)
keyring, err := openpgp.ReadArmoredKeyRing(keyRingReader) keyring, err := openpgp.ReadArmoredKeyRing(keyRingReader)
+237
View File
@@ -0,0 +1,237 @@
package object
import (
"bufio"
"bytes"
"fmt"
"io"
"github.com/go-git/go-git/v5/plumbing"
)
// tagScanner holds the working state of the tag decoder driven by the
// stateFn loop in (*Tag).Decode. Each tagState reads one or more lines
// from r, updates the in-progress *Tag and the scanner's bookkeeping,
// and returns the state that should run next (or nil to stop).
type tagScanner struct {
r *bufio.Reader
t *Tag
msgbuf bytes.Buffer
// pending holds a line that was read but the current state decided to
// hand back to the next state, paired with the io.EOF flag returned
// when the line was originally read.
pending []byte
pendingErr error
// First-occurrence tracking: once the corresponding canonical
// header has been decoded at its expected position, subsequent
// occurrences (or out-of-position lines) are silently dropped,
// matching the strict layout enforced by upstream's
// parse_tag_buffer (tag.c:130).
//
// gpgsig-sha256 is recognized and skipped without exposing a new field
// in v5.
sawObject, sawType, sawName, sawTagger bool
}
// tagState is one step of the decoder state machine. Each function reads
// the lines it needs, mutates *Tag via s.t, and returns the next state
// to run (or nil to terminate the loop).
type tagState func(*tagScanner) (tagState, error)
// readLine returns the next line from the buffer, transparently
// consuming any line that was previously pushed back by a state that
// decided not to handle it.
func (s *tagScanner) readLine() ([]byte, error) {
if s.pending != nil {
line, err := s.pending, s.pendingErr
s.pending, s.pendingErr = nil, nil
return line, err
}
return s.r.ReadBytes('\n')
}
// pushBack stashes an unconsumed line so the next state's readLine call
// sees it. Only one line can be pushed back at a time.
func (s *tagScanner) pushBack(line []byte, err error) {
s.pending = line
s.pendingErr = err
}
// scanTagObject requires the first line to be `object HASH`, mirroring
// upstream's strict parse_tag_buffer (tag.c:151-156). Anything else
// returns ErrMalformedTag.
func scanTagObject(s *tagScanner) (tagState, error) {
line, err := s.readLine()
if err != nil && err != io.EOF {
return nil, err
}
if len(line) == 0 || isBlankLine(line) {
return nil, fmt.Errorf("%w: missing object header", ErrMalformedTag)
}
key, data := splitHeader(line)
if key != "object" {
return nil, fmt.Errorf("%w: object header must be first", ErrMalformedTag)
}
h, herr := parseObjectIDHex(data, ErrMalformedTag, "object")
if herr != nil {
return nil, herr
}
s.t.Target = h
s.sawObject = true
if err == io.EOF {
return nil, nil
}
return scanTagType, nil
}
// scanTagType requires a `type` line immediately after the object header,
// mirroring upstream's parse_tag_buffer (tag.c:158-166).
func scanTagType(s *tagScanner) (tagState, error) {
line, err := s.readLine()
if err != nil && err != io.EOF {
return nil, err
}
if len(line) == 0 || isBlankLine(line) {
return nil, fmt.Errorf("%w: missing type header", ErrMalformedTag)
}
key, data := splitHeader(line)
if key != "type" {
return nil, fmt.Errorf("%w: type header must follow object", ErrMalformedTag)
}
ot, perr := plumbing.ParseObjectType(string(data))
if perr != nil {
return nil, perr
}
s.t.TargetType = ot
s.sawType = true
if err == io.EOF {
return nil, nil
}
return scanTagName, nil
}
// scanTagName requires a `tag` line immediately after the type header,
// mirroring upstream's parse_tag_buffer (tag.c:186-194).
func scanTagName(s *tagScanner) (tagState, error) {
line, err := s.readLine()
if err != nil && err != io.EOF {
return nil, err
}
if len(line) == 0 || isBlankLine(line) {
return nil, fmt.Errorf("%w: missing tag header", ErrMalformedTag)
}
key, data := splitHeader(line)
if key != "tag" {
return nil, fmt.Errorf("%w: tag header must follow type", ErrMalformedTag)
}
s.t.Name = string(data)
s.sawName = true
if err == io.EOF {
return nil, nil
}
return scanTagTagger, nil
}
// scanTagTagger accepts a `tagger` line at its canonical position. Any
// other header is pushed back for scanTagHeaders.
func scanTagTagger(s *tagScanner) (tagState, error) {
line, err := s.readLine()
if err != nil && err != io.EOF {
return nil, err
}
if len(line) == 0 {
return nil, nil
}
if isBlankLine(line) {
return scanTagMessage, nil
}
key, data := splitHeader(line)
if key == "tagger" {
s.t.Tagger.Decode(data)
s.sawTagger = true
if err == io.EOF {
return nil, nil
}
return scanTagHeaders, nil
}
s.pushBack(line, err)
return scanTagHeaders, nil
}
// scanTagHeaders dispatches one header line. gpgsig-sha256 hands off to
// scanTagSkipCont so the continuation block can be consumed; out-of-position
// canonical fields and unknown headers are silently dropped.
func scanTagHeaders(s *tagScanner) (tagState, error) {
line, err := s.readLine()
if err != nil && err != io.EOF {
return nil, err
}
if len(line) == 0 {
return nil, nil
}
if isBlankLine(line) {
return scanTagMessage, nil
}
key, _ := splitHeader(line)
next := scanTagHeaders
switch key {
case "object", "type", "tag", "tagger":
// Out-of-canonical-position duplicates are dropped, mirroring the
// strict ordering of upstream's parse_tag_buffer.
case headerpgp256:
next = scanTagSkipCont
default:
// Unknown header: silently dropped (the Tag struct does not
// expose ExtraHeaders).
}
if err == io.EOF {
return nil, nil
}
return next, nil
}
// scanTagSkipCont discards continuation lines for a header scanTagHeaders chose
// to drop. The first non-continuation line is pushed back so scanTagHeaders can
// dispatch it.
func scanTagSkipCont(s *tagScanner) (tagState, error) {
line, err := s.readLine()
if err != nil && err != io.EOF {
return nil, err
}
if len(line) > 0 && line[0] == ' ' {
if err == io.EOF {
return nil, nil
}
return scanTagSkipCont, nil
}
if len(line) > 0 {
s.pushBack(line, err)
}
return scanTagHeaders, nil
}
// scanTagMessage drains the remaining bytes into the message buffer.
// (*Tag).Decode then runs parseSignedBytes over those bytes to peel off
// the optional inline trailing PGP signature.
func scanTagMessage(s *tagScanner) (tagState, error) {
for {
line, err := s.readLine()
if err != nil && err != io.EOF {
return nil, err
}
if len(line) > 0 {
s.msgbuf.Write(line)
}
if err == io.EOF {
return nil, nil
}
}
}
+118 -33
View File
@@ -10,6 +10,7 @@ import (
"sort" "sort"
"strings" "strings"
"github.com/go-git/go-git/v5/internal/pathutil"
"github.com/go-git/go-git/v5/plumbing" "github.com/go-git/go-git/v5/plumbing"
"github.com/go-git/go-git/v5/plumbing/filemode" "github.com/go-git/go-git/v5/plumbing/filemode"
"github.com/go-git/go-git/v5/plumbing/storer" "github.com/go-git/go-git/v5/plumbing/storer"
@@ -29,6 +30,7 @@ var (
ErrDirectoryNotFound = errors.New("directory not found") ErrDirectoryNotFound = errors.New("directory not found")
ErrEntryNotFound = errors.New("entry not found") ErrEntryNotFound = errors.New("entry not found")
ErrEntriesNotSorted = errors.New("entries in tree are not sorted") ErrEntriesNotSorted = errors.New("entries in tree are not sorted")
ErrMalformedTree = errors.New("malformed tree")
) )
// Tree is basically like a directory - it references a bunch of other trees // Tree is basically like a directory - it references a bunch of other trees
@@ -37,9 +39,9 @@ type Tree struct {
Entries []TreeEntry Entries []TreeEntry
Hash plumbing.Hash Hash plumbing.Hash
s storer.EncodedObjectStorer s storer.EncodedObjectStorer
m map[string]*TreeEntry t map[string]*Tree // tree path cache
t map[string]*Tree // tree path cache entriesSorted bool
} }
// GetTree gets a tree from an object storer and decodes it. // GetTree gets a tree from an object storer and decodes it.
@@ -117,7 +119,16 @@ func (t *Tree) Tree(path string) (*Tree, error) {
} }
// TreeEntryFile returns the *File for a given *TreeEntry. // TreeEntryFile returns the *File for a given *TreeEntry.
//
// The entry's name is validated against pathutil.ValidTreePath for
// the same reason FindEntry validates: TreeEntryFile is a boundary
// where attacker-controlled tree data leaves the trusted store as a
// *File whose Name a caller can hand to filesystem ops.
func (t *Tree) TreeEntryFile(e *TreeEntry) (*File, error) { func (t *Tree) TreeEntryFile(e *TreeEntry) (*File, error) {
if err := pathutil.ValidTreePath(e.Name); err != nil {
return nil, err
}
blob, err := GetBlob(t.s, e.Hash) blob, err := GetBlob(t.s, e.Hash)
if err != nil { if err != nil {
return nil, err return nil, err
@@ -127,7 +138,16 @@ func (t *Tree) TreeEntryFile(e *TreeEntry) (*File, error) {
} }
// FindEntry search a TreeEntry in this tree or any subtree. // FindEntry search a TreeEntry in this tree or any subtree.
//
// The lookup path is validated against pathutil.ValidTreePath to
// prevent attacker-controlled tree contents from leaking past this
// boundary as `.git`-shaped or path-traversal-shaped names. Callers
// that legitimately need to look up unsafe paths should walk the
// tree manually.
func (t *Tree) FindEntry(path string) (*TreeEntry, error) { func (t *Tree) FindEntry(path string) (*TreeEntry, error) {
if err := pathutil.ValidTreePath(path); err != nil {
return nil, err
}
if t.t == nil { if t.t == nil {
t.t = make(map[string]*Tree) t.t = make(map[string]*Tree)
} }
@@ -182,16 +202,43 @@ func (t *Tree) dir(baseName string) (*Tree, error) {
} }
func (t *Tree) entry(baseName string) (*TreeEntry, error) { func (t *Tree) entry(baseName string) (*TreeEntry, error) {
if t.m == nil { if t.entriesSorted {
t.buildMap() if entry := t.searchEntry(baseName); entry != nil {
} return entry, nil
}
entry, ok := t.m[baseName]
if !ok {
return nil, ErrEntryNotFound return nil, ErrEntryNotFound
} }
return entry, nil pastName := baseName + "/"
for i := range t.Entries {
entry := &t.Entries[i]
if entry.Name == baseName {
return entry, nil
}
if treeEntrySortName(entry) > pastName {
break
}
}
return nil, ErrEntryNotFound
}
func (t *Tree) searchEntry(baseName string) *TreeEntry {
if i := t.searchEntryIndex(baseName); i < len(t.Entries) && t.Entries[i].Name == baseName {
return &t.Entries[i]
}
if i := t.searchEntryIndex(baseName + "/"); i < len(t.Entries) && t.Entries[i].Name == baseName {
return &t.Entries[i]
}
return nil
}
func (t *Tree) searchEntryIndex(name string) int {
return sort.Search(len(t.Entries), func(i int) bool {
return treeEntrySortName(&t.Entries[i]) >= name
})
} }
// Files returns a FileIter allowing to iterate over the Tree // Files returns a FileIter allowing to iterate over the Tree
@@ -212,20 +259,25 @@ func (t *Tree) Type() plumbing.ObjectType {
return plumbing.TreeObject return plumbing.TreeObject
} }
func (t *Tree) reset() {
storer := t.s
*t = Tree{s: storer}
}
// Decode transform an plumbing.EncodedObject into a Tree struct // Decode transform an plumbing.EncodedObject into a Tree struct
func (t *Tree) Decode(o plumbing.EncodedObject) (err error) { func (t *Tree) Decode(o plumbing.EncodedObject) (err error) {
if o.Type() != plumbing.TreeObject { if o.Type() != plumbing.TreeObject {
return ErrUnsupportedObject return ErrUnsupportedObject
} }
t.reset()
t.Hash = o.Hash() t.Hash = o.Hash()
// assume tree is sorted as a valid tree should always be sorted.
t.entriesSorted = true
if o.Size() == 0 { if o.Size() == 0 {
return nil return nil
} }
t.Entries = nil
t.m = nil
reader, err := o.Reader() reader, err := o.Reader()
if err != nil { if err != nil {
return err return err
@@ -235,10 +287,14 @@ func (t *Tree) Decode(o plumbing.EncodedObject) (err error) {
r := sync.GetBufioReader(reader) r := sync.GetBufioReader(reader)
defer sync.PutBufioReader(r) defer sync.PutBufioReader(r)
var prevSortName string
for { for {
str, err := r.ReadString(' ') str, err := r.ReadString(' ')
if err != nil { if err != nil {
if err == io.EOF { if err == io.EOF {
if len(str) != 0 {
return fmt.Errorf("%w: missing mode terminator", ErrMalformedTree)
}
break break
} }
@@ -248,25 +304,41 @@ func (t *Tree) Decode(o plumbing.EncodedObject) (err error) {
mode, err := filemode.New(str) mode, err := filemode.New(str)
if err != nil { if err != nil {
return err return fmt.Errorf("%w: malformed mode", ErrMalformedTree)
} }
mode = canonicalTreeMode(mode)
name, err := r.ReadString(0) name, err := r.ReadString(0)
if err != nil && err != io.EOF { if err != nil {
if err == io.EOF {
return fmt.Errorf("%w: missing filename terminator", ErrMalformedTree)
}
return err return err
} }
if len(name) == 1 {
return fmt.Errorf("%w: empty filename", ErrMalformedTree)
}
var hash plumbing.Hash var hash plumbing.Hash
if _, err = io.ReadFull(r, hash[:]); err != nil { if _, err = io.ReadFull(r, hash[:]); err != nil {
if errors.Is(err, io.EOF) || errors.Is(err, io.ErrUnexpectedEOF) {
return fmt.Errorf("%w: truncated object id", ErrMalformedTree)
}
return err return err
} }
baseName := name[:len(name)-1] baseName := name[:len(name)-1]
t.Entries = append(t.Entries, TreeEntry{ entry := TreeEntry{
Hash: hash, Hash: hash,
Mode: mode, Mode: mode,
Name: baseName, Name: baseName,
}) }
sortName := treeEntrySortName(&entry)
if len(t.Entries) != 0 && prevSortName > sortName {
t.entriesSorted = false
}
prevSortName = sortName
t.Entries = append(t.Entries, entry)
} }
return nil return nil
@@ -279,21 +351,37 @@ func (s TreeEntrySorter) Len() int {
} }
func (s TreeEntrySorter) Less(i, j int) bool { func (s TreeEntrySorter) Less(i, j int) bool {
name1 := s[i].Name return treeEntrySortName(&s[i]) < treeEntrySortName(&s[j])
name2 := s[j].Name
if s[i].Mode == filemode.Dir {
name1 += "/"
}
if s[j].Mode == filemode.Dir {
name2 += "/"
}
return name1 < name2
} }
func (s TreeEntrySorter) Swap(i, j int) { func (s TreeEntrySorter) Swap(i, j int) {
s[i], s[j] = s[j], s[i] s[i], s[j] = s[j], s[i]
} }
// Git compares tree entries as if directory names had a trailing slash.
func treeEntrySortName(e *TreeEntry) string {
if e.Mode == filemode.Dir {
return e.Name + "/"
}
return e.Name
}
func canonicalTreeMode(mode filemode.FileMode) filemode.FileMode {
switch mode & 0o170000 {
case 0o040000:
return filemode.Dir
case 0o100000:
if mode&0o111 != 0 {
return filemode.Executable
}
return filemode.Regular
case 0o120000:
return filemode.Symlink
default:
return filemode.Submodule
}
}
// Encode transforms a Tree into a plumbing.EncodedObject. // Encode transforms a Tree into a plumbing.EncodedObject.
// The tree entries must be sorted by name. // The tree entries must be sorted by name.
func (t *Tree) Encode(o plumbing.EncodedObject) (err error) { func (t *Tree) Encode(o plumbing.EncodedObject) (err error) {
@@ -329,13 +417,6 @@ func (t *Tree) Encode(o plumbing.EncodedObject) (err error) {
return err return err
} }
func (t *Tree) buildMap() {
t.m = make(map[string]*TreeEntry)
for i := 0; i < len(t.Entries); i++ {
t.m[t.Entries[i].Name] = &t.Entries[i]
}
}
// Diff returns a list of changes between this tree and the provided one // Diff returns a list of changes between this tree and the provided one
func (t *Tree) Diff(to *Tree) (Changes, error) { func (t *Tree) Diff(to *Tree) (Changes, error) {
return t.DiffContext(context.Background(), to) return t.DiffContext(context.Background(), to)
@@ -455,6 +536,10 @@ func (w *TreeWalker) Next() (name string, entry TreeEntry, err error) {
continue continue
} }
if err := pathutil.ValidTreePath(entry.Name); err != nil {
return name, entry, err
}
if entry.Mode == filemode.Dir { if entry.Mode == filemode.Dir {
obj, err = GetTree(w.s, entry.Hash) obj, err = GetTree(w.s, entry.Hash)
} }
+147 -21
View File
@@ -7,7 +7,6 @@ import (
"crypto/tls" "crypto/tls"
"crypto/x509" "crypto/x509"
"fmt" "fmt"
"net"
"net/http" "net/http"
"net/url" "net/url"
"reflect" "reflect"
@@ -24,6 +23,33 @@ import (
"github.com/go-git/go-git/v5/utils/ioutil" "github.com/go-git/go-git/v5/utils/ioutil"
) )
type contextKey int
const initialRequestKey contextKey = iota
// RedirectPolicy controls how the HTTP transport follows redirects.
//
// The values mirror Git's http.followRedirects config:
// "true" follows redirects for all requests, "false" treats redirects as
// errors, and "initial" follows redirects only for the initial
// /info/refs discovery request. The zero value defaults to "initial".
type RedirectPolicy string
const (
FollowInitialRedirects RedirectPolicy = "initial"
FollowRedirects RedirectPolicy = "true"
NoFollowRedirects RedirectPolicy = "false"
)
func withInitialRequest(ctx context.Context) context.Context {
return context.WithValue(ctx, initialRequestKey, true)
}
func isInitialRequest(req *http.Request) bool {
v, _ := req.Context().Value(initialRequestKey).(bool)
return v
}
// it requires a bytes.Buffer, because we need to know the length // it requires a bytes.Buffer, because we need to know the length
func applyHeadersToRequest(req *http.Request, content *bytes.Buffer, host string, requestType string) { func applyHeadersToRequest(req *http.Request, content *bytes.Buffer, host string, requestType string) {
req.Header.Add("User-Agent", capability.DefaultAgent()) req.Header.Add("User-Agent", capability.DefaultAgent())
@@ -54,12 +80,15 @@ func advertisedReferences(ctx context.Context, s *session, serviceName string) (
s.ApplyAuthToRequest(req) s.ApplyAuthToRequest(req)
applyHeadersToRequest(req, nil, s.endpoint.Host, serviceName) applyHeadersToRequest(req, nil, s.endpoint.Host, serviceName)
res, err := s.client.Do(req.WithContext(ctx)) res, err := s.client.Do(req.WithContext(withInitialRequest(ctx)))
if err != nil { if err != nil {
return nil, err return nil, err
} }
s.ModifyEndpointIfRedirect(res) if err := s.ModifyEndpointIfRedirect(res); err != nil {
_ = res.Body.Close()
return nil, err
}
defer ioutil.CheckClose(res.Body, &err) defer ioutil.CheckClose(res.Body, &err)
if err = NewErr(res); err != nil { if err = NewErr(res); err != nil {
@@ -96,6 +125,7 @@ type client struct {
client *http.Client client *http.Client
transports *lru.Cache transports *lru.Cache
mutex sync.RWMutex mutex sync.RWMutex
follow RedirectPolicy
} }
// ClientOptions holds user configurable options for the client. // ClientOptions holds user configurable options for the client.
@@ -106,6 +136,11 @@ type ClientOptions struct {
// size, will result in the least recently used transport getting deleted // size, will result in the least recently used transport getting deleted
// before the provided transport is added to the cache. // before the provided transport is added to the cache.
CacheMaxEntries int CacheMaxEntries int
// RedirectPolicy controls redirect handling. Supported values are
// "true", "false", and "initial". The zero value defaults to
// "initial", matching Git's http.followRedirects default.
RedirectPolicy RedirectPolicy
} }
var ( var (
@@ -150,12 +185,16 @@ func NewClientWithOptions(c *http.Client, opts *ClientOptions) transport.Transpo
} }
cl := &client{ cl := &client{
client: c, client: c,
follow: FollowInitialRedirects,
} }
if opts != nil { if opts != nil {
if opts.CacheMaxEntries > 0 { if opts.CacheMaxEntries > 0 {
cl.transports = lru.New(opts.CacheMaxEntries) cl.transports = lru.New(opts.CacheMaxEntries)
} }
if opts.RedirectPolicy != "" {
cl.follow = opts.RedirectPolicy
}
} }
return cl return cl
} }
@@ -289,14 +328,9 @@ func newSession(c *client, ep *transport.Endpoint, auth transport.AuthMethod) (*
} }
} }
httpClient = &http.Client{ httpClient = c.cloneHTTPClient(transport)
Transport: transport,
CheckRedirect: c.client.CheckRedirect,
Jar: c.client.Jar,
Timeout: c.client.Timeout,
}
} else { } else {
httpClient = c.client httpClient = c.cloneHTTPClient(c.client.Transport)
} }
s := &session{ s := &session{
@@ -324,30 +358,122 @@ func (s *session) ApplyAuthToRequest(req *http.Request) {
s.auth.SetAuth(req) s.auth.SetAuth(req)
} }
func (s *session) ModifyEndpointIfRedirect(res *http.Response) { func (s *session) ModifyEndpointIfRedirect(res *http.Response) error {
if res.Request == nil { if res.Request == nil {
return return nil
}
if s.endpoint == nil {
return fmt.Errorf("http redirect: nil endpoint")
} }
r := res.Request r := res.Request
if !strings.HasSuffix(r.URL.Path, infoRefsPath) { if !strings.HasSuffix(r.URL.Path, infoRefsPath) {
return return fmt.Errorf("http redirect: target %q does not end with %s", r.URL.Path, infoRefsPath)
}
if r.URL.Scheme != "http" && r.URL.Scheme != "https" {
return fmt.Errorf("http redirect: unsupported scheme %q", r.URL.Scheme)
}
if r.URL.Scheme != s.endpoint.Protocol &&
!(s.endpoint.Protocol == "http" && r.URL.Scheme == "https") {
return fmt.Errorf("http redirect: changes scheme from %q to %q", s.endpoint.Protocol, r.URL.Scheme)
} }
h, p, err := net.SplitHostPort(r.URL.Host) host := endpointHost(r.URL.Hostname())
port, err := endpointPort(r.URL.Port())
if err != nil { if err != nil {
h = r.URL.Host return err
} }
if p != "" {
port, err := strconv.Atoi(p) if host != s.endpoint.Host || effectivePort(r.URL.Scheme, port) != effectivePort(s.endpoint.Protocol, s.endpoint.Port) {
if err == nil { s.endpoint.User = ""
s.endpoint.Port = port s.endpoint.Password = ""
} s.auth = nil
} }
s.endpoint.Host = h
s.endpoint.Host = host
s.endpoint.Port = port
s.endpoint.Protocol = r.URL.Scheme s.endpoint.Protocol = r.URL.Scheme
s.endpoint.Path = r.URL.Path[:len(r.URL.Path)-len(infoRefsPath)] s.endpoint.Path = r.URL.Path[:len(r.URL.Path)-len(infoRefsPath)]
return nil
}
func endpointHost(host string) string {
if strings.Contains(host, ":") {
return "[" + host + "]"
}
return host
}
func endpointPort(port string) (int, error) {
if port == "" {
return 0, nil
}
parsed, err := strconv.Atoi(port)
if err != nil {
return 0, fmt.Errorf("http redirect: invalid port %q", port)
}
return parsed, nil
}
func effectivePort(scheme string, port int) int {
if port != 0 {
return port
}
switch strings.ToLower(scheme) {
case "http":
return 80
case "https":
return 443
default:
return 0
}
}
func (c *client) cloneHTTPClient(transport http.RoundTripper) *http.Client {
return &http.Client{
Transport: transport,
CheckRedirect: wrapCheckRedirect(c.follow, c.client.CheckRedirect),
Jar: c.client.Jar,
Timeout: c.client.Timeout,
}
}
func wrapCheckRedirect(policy RedirectPolicy, next func(*http.Request, []*http.Request) error) func(*http.Request, []*http.Request) error {
return func(req *http.Request, via []*http.Request) error {
if err := checkRedirect(req, via, policy); err != nil {
return err
}
if next != nil {
return next(req, via)
}
return nil
}
}
func checkRedirect(req *http.Request, via []*http.Request, policy RedirectPolicy) error {
switch policy {
case FollowRedirects:
case NoFollowRedirects:
return fmt.Errorf("http redirect: redirects disabled to %s", req.URL)
case "", FollowInitialRedirects:
if !isInitialRequest(req) {
return fmt.Errorf("http redirect: redirect on non-initial request to %s", req.URL)
}
default:
return fmt.Errorf("http redirect: invalid redirect policy %q", policy)
}
if req.URL.Scheme != "http" && req.URL.Scheme != "https" {
return fmt.Errorf("http redirect: unsupported scheme %q", req.URL.Scheme)
}
if len(via) >= 10 {
return fmt.Errorf("http redirect: too many redirects")
}
return nil
} }
func (*session) Close() error { func (*session) Close() error {
+33 -1
View File
@@ -252,7 +252,39 @@ func (c *command) setAuthFromEndpoint() error {
} }
func endpointToCommand(cmd string, ep *transport.Endpoint) string { func endpointToCommand(cmd string, ep *transport.Endpoint) string {
return fmt.Sprintf("%s '%s'", cmd, ep.Path) var b strings.Builder
b.WriteString(cmd)
b.WriteByte(' ')
writeShellQuote(&b, ep.Path)
return b.String()
}
// writeShellQuote writes s to b, wrapped in single quotes with
// embedded single quotes and exclamation marks escaped using the
// POSIX close-escape-reopen idiom:
//
// ' becomes '\''
// ! becomes '\!'
//
// It is a direct port of canonical Git's sq_quote_buf (quote.c).
// The bang escape keeps the result safe when re-evaluated under
// csh-derived shells that perform history expansion. The output is
// safe to pass as a single argument through any POSIX shell and
// round-trips through git-shell's sq_dequote_to_argv.
func writeShellQuote(b *strings.Builder, s string) {
b.Grow(len(s) + 2)
b.WriteByte('\'')
for i := 0; i < len(s); i++ {
c := s[i]
if c == '\'' || c == '!' {
b.WriteString(`'\`)
b.WriteByte(c)
b.WriteByte('\'')
continue
}
b.WriteByte(c)
}
b.WriteByte('\'')
} }
func overrideConfig(overrides *ssh.ClientConfig, c *ssh.ClientConfig) { func overrideConfig(overrides *ssh.ClientConfig, c *ssh.ClientConfig) {
+12 -1
View File
@@ -1530,7 +1530,18 @@ func (r *Repository) Worktree() (*Worktree, error) {
return nil, ErrIsBareRepository return nil, ErrIsBareRepository
} }
return &Worktree{r: r, Filesystem: r.wt}, nil protectNTFS := defaultProtectNTFS()
protectHFS := defaultProtectHFS()
if cfg, err := r.Config(); err == nil {
if cfg.Core.ProtectNTFS.IsSet() {
protectNTFS = cfg.Core.ProtectNTFS.IsTrue()
}
if cfg.Core.ProtectHFS.IsSet() {
protectHFS = cfg.Core.ProtectHFS.IsTrue()
}
}
return &Worktree{r: r, Filesystem: newWorktreeFilesystem(r.wt, protectNTFS, protectHFS)}, nil
} }
func expand_ref(s storer.ReferenceStorer, ref plumbing.ReferenceName) (*plumbing.Reference, error) { func expand_ref(s storer.ReferenceStorer, ref plumbing.ReferenceName) (*plumbing.Reference, error) {
+17 -2
View File
@@ -75,6 +75,10 @@ var (
// ErrEmptyRefFile is returned when a reference file is attempted to be read, // ErrEmptyRefFile is returned when a reference file is attempted to be read,
// but the file is empty // but the file is empty
ErrEmptyRefFile = errors.New("ref file is empty") ErrEmptyRefFile = errors.New("ref file is empty")
// ErrModuleNameEscape is returned when a submodule name would
// resolve outside the modules/ subtree, mirroring canonical Git's
// "ignoring suspicious submodule name" defence.
ErrModuleNameEscape = errors.New("submodule name escapes modules/ directory")
) )
// Options holds configuration for the storage. // Options holds configuration for the storage.
@@ -1127,9 +1131,20 @@ func (d *DotGit) PackRefs() (err error) {
return nil return nil
} }
// Module return a billy.Filesystem pointing to the module folder // Module returns a billy.Filesystem pointing to the module folder.
//
// As a defence in depth against submodule name path traversal,
// refuse names whose joined path leaves the modules/ subtree once
// cleaned. The config-layer parser also validates submodule names,
// but Module may be reached from any caller that constructs a
// Submodule struct programmatically and so bypasses the parser.
func (d *DotGit) Module(name string) (billy.Filesystem, error) { func (d *DotGit) Module(name string) (billy.Filesystem, error) {
return d.fs.Chroot(d.fs.Join(modulePath, name)) p := d.fs.Join(modulePath, name)
cleaned := path.Clean(filepath.ToSlash(p))
if cleaned != modulePath && !strings.HasPrefix(cleaned, modulePath+"/") {
return nil, ErrModuleNameEscape
}
return d.fs.Chroot(p)
} }
func (d *DotGit) AddAlternate(remote string) error { func (d *DotGit) AddAlternate(remote string) error {
+74 -8
View File
@@ -6,9 +6,12 @@ import (
"errors" "errors"
"fmt" "fmt"
"path" "path"
"path/filepath"
"github.com/go-git/go-billy/v5" "github.com/go-git/go-billy/v5"
"github.com/go-git/go-git/v5/config" "github.com/go-git/go-git/v5/config"
"github.com/go-git/go-git/v5/internal/pathutil"
giturl "github.com/go-git/go-git/v5/internal/url"
"github.com/go-git/go-git/v5/plumbing" "github.com/go-git/go-git/v5/plumbing"
"github.com/go-git/go-git/v5/plumbing/format/index" "github.com/go-git/go-git/v5/plumbing/format/index"
"github.com/go-git/go-git/v5/plumbing/transport" "github.com/go-git/go-git/v5/plumbing/transport"
@@ -119,6 +122,16 @@ func (s *Submodule) Repository() (*Repository, error) {
exists = true exists = true
} }
// s.c.Path is sourced from the worktree's .gitmodules and is
// therefore tree-controlled. Apply the strict tree-path validator
// before chroot — the wrapper's tolerant validPath would let a
// final-position .git component through (e.g. "submodule/.git"),
// which a malicious .gitmodules could use to chroot the submodule
// worktree into the repository's actual .git directory.
if err := pathutil.ValidTreePath(s.c.Path); err != nil {
return nil, err
}
var worktree billy.Filesystem var worktree billy.Filesystem
if worktree, err = s.w.Filesystem.Chroot(s.c.Path); err != nil { if worktree, err = s.w.Filesystem.Chroot(s.c.Path); err != nil {
return nil, err return nil, err
@@ -138,18 +151,25 @@ func (s *Submodule) Repository() (*Repository, error) {
return nil, err return nil, err
} }
if !path.IsAbs(moduleEndpoint.Path) && moduleEndpoint.Protocol == "file" { // A relative submodule URL such as "../X.git" must resolve against
remotes, err := s.w.r.Remotes() // the parent repository's remote URL, not against the process CWD.
// Detect relativity from the raw configured URL because
// transport.NewEndpoint normalizes local paths to absolute form via
// filepath.Abs, which would otherwise mask the relative form here.
if giturl.IsLocalEndpoint(s.c.URL) &&
!path.IsAbs(s.c.URL) && !filepath.IsAbs(s.c.URL) {
base, err := defaultRemote(s.w.r)
if err != nil {
return nil, fmt.Errorf("resolving relative submodule URL: %w", err)
}
rootEndpoint, err := transport.NewEndpoint(base.URLs[0])
if err != nil { if err != nil {
return nil, err return nil, err
} }
rootEndpoint, err := transport.NewEndpoint(remotes[0].c.URLs[0]) rootEndpoint.Path = path.Join(rootEndpoint.Path, s.c.URL)
if err != nil {
return nil, err
}
rootEndpoint.Path = path.Join(rootEndpoint.Path, moduleEndpoint.Path)
*moduleEndpoint = *rootEndpoint *moduleEndpoint = *rootEndpoint
} }
@@ -161,6 +181,52 @@ func (s *Submodule) Repository() (*Repository, error) {
return r, err return r, err
} }
// defaultRemote returns the remote that relative submodule URLs are
// resolved against, mirroring canonical Git's repo_default_remote
// (remote.c) and resolve_relative_url (builtin/submodule--helper.c):
//
// 1. if HEAD is on a branch with branch.<name>.remote configured,
// use that remote;
// 2. else if exactly one remote is configured, use it;
// 3. otherwise fall back to DefaultRemoteName ("origin").
//
// Each rule falls through unconditionally: a branch lookup that
// finds the branch but with an empty Remote does not short-circuit
// rule (2). Returns an error when the chosen remote is not configured.
func defaultRemote(r *Repository) (*config.RemoteConfig, error) {
cfg, err := r.Config()
if err != nil {
return nil, err
}
if ref, err := r.Reference(plumbing.HEAD, false); err == nil &&
ref.Type() == plumbing.SymbolicReference &&
ref.Target().IsBranch() {
if b, ok := cfg.Branches[ref.Target().Short()]; ok && b.Remote != "" {
return lookupRemote(cfg, b.Remote)
}
}
if len(cfg.Remotes) == 1 {
for name := range cfg.Remotes {
return lookupRemote(cfg, name)
}
}
return lookupRemote(cfg, DefaultRemoteName)
}
func lookupRemote(cfg *config.Config, name string) (*config.RemoteConfig, error) {
rc, ok := cfg.Remotes[name]
if !ok {
return nil, fmt.Errorf("remote %q not found", name)
}
if len(rc.URLs) == 0 {
return nil, fmt.Errorf("remote %q has no configured URL", name)
}
return rc, nil
}
// Update the registered submodule to match what the superproject expects, the // Update the registered submodule to match what the superproject expects, the
// submodule should be initialized first calling the Init method or setting in // submodule should be initialized first calling the Init method or setting in
// the options SubmoduleUpdateOptions.Init equals true // the options SubmoduleUpdateOptions.Init equals true
+15
View File
@@ -5,11 +5,18 @@ package binary
import ( import (
"bufio" "bufio"
"encoding/binary" "encoding/binary"
"errors"
"io" "io"
"math"
"github.com/go-git/go-git/v5/plumbing" "github.com/go-git/go-git/v5/plumbing"
) )
// ErrIntegerOverflow is returned when a Git-format variable-width integer
// would not fit into an int64 because the input declares more continuation
// bytes than the type can hold.
var ErrIntegerOverflow = errors.New("variable-width integer overflow")
// Read reads structured binary data from r into data. Bytes are read and // Read reads structured binary data from r into data. Bytes are read and
// decoded in BigEndian order // decoded in BigEndian order
// https://golang.org/pkg/encoding/binary/#Read // https://golang.org/pkg/encoding/binary/#Read
@@ -92,6 +99,14 @@ func ReadVariableWidthInt(r io.Reader) (int64, error) {
var v = int64(c & maskLength) var v = int64(c & maskLength)
for c&maskContinue > 0 { for c&maskContinue > 0 {
// Reject input that, after the v++ and shift below, would
// not fit in an int64. With v < (MaxInt64-127)>>7, the
// post-increment v is at most (MaxInt64-127)>>7 and the
// final (v << 7) + (c & 0x7F) stays within int64.
if v >= (math.MaxInt64-int64(maskLength))>>lengthBits {
return 0, ErrIntegerOverflow
}
v++ v++
if err := Read(r, &c); err != nil { if err := Read(r, &c); err != nil {
return 0, err return 0, err
+4 -111
View File
@@ -7,7 +7,6 @@ import (
"io" "io"
"os" "os"
"path/filepath" "path/filepath"
"runtime"
"strings" "strings"
"github.com/go-git/go-billy/v5" "github.com/go-git/go-billy/v5"
@@ -458,10 +457,6 @@ func (w *Worktree) resetWorktree(t *object.Tree, files []string) error {
filesMap := buildFilePathMap(files) filesMap := buildFilePathMap(files)
for _, ch := range changes { for _, ch := range changes {
if err := w.validChange(ch); err != nil {
return err
}
if len(files) > 0 { if len(files) > 0 {
file := "" file := ""
if ch.From != nil { if ch.From != nil {
@@ -489,108 +484,6 @@ func (w *Worktree) resetWorktree(t *object.Tree, files []string) error {
return w.r.Storer.SetIndex(idx) return w.r.Storer.SetIndex(idx)
} }
// worktreeDeny is a list of paths that are not allowed
// to be used when resetting the worktree.
var worktreeDeny = map[string]struct{}{
// .git
GitDirName: {},
// For other historical reasons, file names that do not conform to the 8.3
// format (up to eight characters for the basename, three for the file
// extension, certain characters not allowed such as `+`, etc) are associated
// with a so-called "short name", at least on the `C:` drive by default.
// Which means that `git~1/` is a valid way to refer to `.git/`.
"git~1": {},
}
// validPath checks whether paths are valid.
// The rules around invalid paths could differ from upstream based on how
// filesystems are managed within go-git, but they are largely the same.
//
// For upstream rules:
// https://github.com/git/git/blob/564d0252ca632e0264ed670534a51d18a689ef5d/read-cache.c#L946
// https://github.com/git/git/blob/564d0252ca632e0264ed670534a51d18a689ef5d/path.c#L1383
func validPath(paths ...string) error {
for _, p := range paths {
parts := strings.FieldsFunc(p, func(r rune) bool { return (r == '\\' || r == '/') })
if len(parts) == 0 {
return fmt.Errorf("invalid path: %q", p)
}
if _, denied := worktreeDeny[strings.ToLower(parts[0])]; denied {
return fmt.Errorf("invalid path prefix: %q", p)
}
if runtime.GOOS == "windows" {
// Volume names are not supported, in both formats: \\ and <DRIVE_LETTER>:.
if vol := filepath.VolumeName(p); vol != "" {
return fmt.Errorf("invalid path: %q", p)
}
if !windowsValidPath(parts[0]) {
return fmt.Errorf("invalid path: %q", p)
}
}
for _, part := range parts {
if part == ".." {
return fmt.Errorf("invalid path %q: cannot use '..'", p)
}
}
}
return nil
}
// windowsPathReplacer defines the chars that need to be replaced
// as part of windowsValidPath.
var windowsPathReplacer *strings.Replacer
func init() {
windowsPathReplacer = strings.NewReplacer(" ", "", ".", "")
}
func windowsValidPath(part string) bool {
if len(part) > 3 && strings.EqualFold(part[:4], GitDirName) {
// For historical reasons, file names that end in spaces or periods are
// automatically trimmed. Therefore, `.git . . ./` is a valid way to refer
// to `.git/`.
if windowsPathReplacer.Replace(part[4:]) == "" {
return false
}
// For yet other historical reasons, NTFS supports so-called "Alternate Data
// Streams", i.e. metadata associated with a given file, referred to via
// `<filename>:<stream-name>:<stream-type>`. There exists a default stream
// type for directories, allowing `.git/` to be accessed via
// `.git::$INDEX_ALLOCATION/`.
//
// For performance reasons, _all_ Alternate Data Streams of `.git/` are
// forbidden, not just `::$INDEX_ALLOCATION`.
if len(part) > 4 && part[4:5] == ":" {
return false
}
}
return true
}
func (w *Worktree) validChange(ch merkletrie.Change) error {
action, err := ch.Action()
if err != nil {
return nil
}
switch action {
case merkletrie.Delete:
return validPath(ch.From.String())
case merkletrie.Insert:
return validPath(ch.To.String())
case merkletrie.Modify:
return validPath(ch.From.String(), ch.To.String())
}
return nil
}
func (w *Worktree) checkoutChange(ch merkletrie.Change, t *object.Tree, idx *indexBuilder) error { func (w *Worktree) checkoutChange(ch merkletrie.Change, t *object.Tree, idx *indexBuilder) error {
a, err := ch.Action() a, err := ch.Action()
if err != nil { if err != nil {
@@ -763,10 +656,10 @@ func (w *Worktree) checkoutFile(f *object.File) (err error) {
} }
func (w *Worktree) checkoutFileSymlink(f *object.File) (err error) { func (w *Worktree) checkoutFileSymlink(f *object.File) (err error) {
// https://github.com/git/git/commit/10ecfa76491e4923988337b2e2243b05376b40de // .gitmodules symlink rejection (and its NTFS / HFS variants) is
if strings.EqualFold(f.Name, gitmodulesFile) { // enforced by the worktreeFilesystem wrapper's Symlink method via
return ErrGitModulesSymlink // validSymlinkName. See https://github.com/git/git/commit/10ecfa7
} // for the upstream rationale.
from, err := f.Reader() from, err := f.Reader()
if err != nil { if err != nil {
+264
View File
@@ -0,0 +1,264 @@
package git
import (
"errors"
"fmt"
"os"
"path/filepath"
"runtime"
"strings"
"github.com/go-git/go-billy/v5"
"github.com/go-git/go-git/v5/internal/pathutil"
)
// defaultProtectHFS returns the default value for core.protectHFS
// when not explicitly configured. Matches upstream Git's
// PROTECT_HFS_DEFAULT[1], which the Makefile sets to 1 on Darwin
// and leaves at 0 on every other platform.
//
// [1]: https://github.com/git/git/blob/v2.54.0/config.mak.uname#L146
func defaultProtectHFS() bool {
return runtime.GOOS == "darwin"
}
// defaultProtectNTFS returns the default value for core.protectNTFS
// when not explicitly configured. Matches upstream Git's
// PROTECT_NTFS_DEFAULT, which has been 1 on every platform since
// 9102f958ee5 (CVE-2019-1353)[1]: WSL allows Linux processes to
// reach NTFS-mounted worktrees on Windows hosts, so the
// is_ntfs_dotgit guard cannot safely be gated on the runtime OS.
//
// [1]: https://github.com/git/git/commit/9102f958ee5
func defaultProtectNTFS() bool {
return true
}
// worktreeFilesystem wraps a billy.Filesystem and validates every path passed
// to a mutating operation. This prevents writing to, or deleting from,
// dangerous locations (e.g. .git/*, ../) regardless of which worktree
// code path triggers the operation.
type worktreeFilesystem struct {
billy.Filesystem
protectNTFS bool
protectHFS bool
}
func newWorktreeFilesystem(fs billy.Filesystem, protectNTFS, protectHFS bool) *worktreeFilesystem {
return &worktreeFilesystem{Filesystem: fs, protectNTFS: protectNTFS, protectHFS: protectHFS}
}
func (sfs *worktreeFilesystem) Create(filename string) (billy.File, error) {
if err := sfs.validPath(filename); err != nil {
return nil, fmt.Errorf("create: %w", err)
}
return sfs.Filesystem.Create(filename)
}
func (sfs *worktreeFilesystem) Open(filename string) (billy.File, error) {
if err := sfs.validReadPath(filename); err != nil {
return nil, fmt.Errorf("open: %w", err)
}
return sfs.Filesystem.Open(filename)
}
func (sfs *worktreeFilesystem) OpenFile(filename string, flag int, perm os.FileMode) (billy.File, error) {
if err := sfs.validPath(filename); err != nil {
return nil, fmt.Errorf("openfile: %w", err)
}
return sfs.Filesystem.OpenFile(filename, flag, perm)
}
func (sfs *worktreeFilesystem) Stat(filename string) (os.FileInfo, error) {
if err := sfs.validReadPath(filename); err != nil {
return nil, fmt.Errorf("stat: %w", err)
}
return sfs.Filesystem.Stat(filename)
}
func (sfs *worktreeFilesystem) Remove(filename string) error {
if err := sfs.validPath(filename); err != nil {
return fmt.Errorf("remove: %w", err)
}
return sfs.Filesystem.Remove(filename)
}
func (sfs *worktreeFilesystem) Rename(from, to string) error {
if err := sfs.validPath(from, to); err != nil {
return fmt.Errorf("rename: %w", err)
}
return sfs.Filesystem.Rename(from, to)
}
func (sfs *worktreeFilesystem) ReadDir(path string) ([]os.FileInfo, error) {
if err := sfs.validReadPath(path); err != nil {
return nil, fmt.Errorf("readdir: %w", err)
}
return sfs.Filesystem.ReadDir(path)
}
func (sfs *worktreeFilesystem) Lstat(filename string) (os.FileInfo, error) {
if err := sfs.validReadPath(filename); err != nil {
return nil, fmt.Errorf("lstat: %w", err)
}
return sfs.Filesystem.Lstat(filename)
}
func (sfs *worktreeFilesystem) Symlink(target, link string) error {
if err := sfs.validPath(link); err != nil {
return fmt.Errorf("symlink: %w", err)
}
if err := sfs.validSymlinkName(link); err != nil {
return fmt.Errorf("symlink: %w", err)
}
return sfs.Filesystem.Symlink(target, link)
}
func (sfs *worktreeFilesystem) Readlink(link string) (string, error) {
if err := sfs.validReadPath(link); err != nil {
return "", fmt.Errorf("readlink: %w", err)
}
return sfs.Filesystem.Readlink(link)
}
func (sfs *worktreeFilesystem) MkdirAll(path string, perm os.FileMode) error {
// MkdirAll on the worktree root is a no-op: the root always exists,
// so there is nothing to materialise. Mirroring the tolerance that
// validReadPath gives to read-side operations avoids breaking callers
// that walk a directory tree and pass the relative-to-root prefix
// ("") through to the worktree FS.
if path == "" || path == "." || path == "/" {
return nil
}
if err := sfs.validPath(path); err != nil {
return fmt.Errorf("mkdirall: %w", err)
}
return sfs.Filesystem.MkdirAll(path, perm)
}
func (sfs *worktreeFilesystem) TempFile(_, _ string) (billy.File, error) {
return nil, fmt.Errorf("tempfile: %w", errUnsupportedOperation)
}
func (sfs *worktreeFilesystem) Chroot(path string) (billy.Filesystem, error) {
if err := sfs.validReadPath(path); err != nil {
return nil, fmt.Errorf("chroot: %w", err)
}
return sfs.Filesystem.Chroot(path)
}
// validReadPath is like validPath but treats the empty string and "." as
// valid references to the worktree root. Read-side operations on the root
// (e.g. ReadDir(""), Lstat(".")) are legitimate; mutating the root itself
// is not, so write-side operations continue to use validPath directly.
func (sfs *worktreeFilesystem) validReadPath(p string) error {
if p == "" || p == "." || p == "/" {
return nil
}
return sfs.validPath(p)
}
var errUnsupportedOperation = errors.New("unsupported operation")
// isDotGitVariant reports whether part is .git, git~1, or an HFS+
// equivalent of .git (when protectHFS is true). NTFS variants of .git
// (e.g. ".git " with trailing space, ".git::$INDEX_ALLOCATION") are
// detected separately by pathutil.WindowsValidPath, which applies
// regardless of position in the path. Both validators reuse this
// helper.
func isDotGitVariant(part string, protectHFS bool) bool {
if pathutil.IsDotGitName(part) {
return true
}
if protectHFS && pathutil.IsHFSDotGit(part) {
return true
}
return false
}
// validPath checks whether paths are valid for the worktree
// filesystem abstraction. It is intentionally tolerant of .git as
// the final path component of a multi-component path
// (e.g. "submodule/.git"), so that legitimate gitlink pointer files
// can still be Stat'd, Read, and Removed via the wrapper during
// submodule cleanup. Attacker-controlled tree-entry paths are
// validated separately by pathutil.ValidTreePath at the boundaries
// where data leaves the trusted store (Tree.FindEntry, the explicit
// callers in CherryPick and Submodule.Repository).
//
// For upstream rules:
// https://github.com/git/git/blob/v2.54.0/read-cache.c#L987
// https://github.com/git/git/blob/v2.54.0/path.c#L1419
func (sfs *worktreeFilesystem) validPath(paths ...string) error {
for _, p := range paths {
for i := 0; i < len(p); i++ {
if p[i] < 0x20 || p[i] == 0x7f {
return fmt.Errorf("invalid path %q: contains control character", p)
}
}
parts := strings.FieldsFunc(p, func(r rune) bool { return (r == '\\' || r == '/') })
if len(parts) == 0 {
return fmt.Errorf("invalid path: %q", p)
}
if sfs.protectNTFS {
// Volume names are not supported, in both formats: \\ and <DRIVE_LETTER>:.
if vol := filepath.VolumeName(p); vol != "" {
return fmt.Errorf("invalid path: %q", p)
}
}
for i, part := range parts {
if part == "." || part == ".." {
return fmt.Errorf("invalid path %q: cannot use %q", p, part)
}
// Reject .git (and equivalents) as a path component when it is
// either the first component (root-level .git) or a non-final
// component (traversal into a .git directory, e.g. "a/.git/config").
// A final non-first .git component (e.g. "submodule/.git") is
// allowed because submodule worktrees contain a .git pointer file.
if isDotGitVariant(part, sfs.protectHFS) && (i == 0 || i < len(parts)-1) {
return fmt.Errorf("invalid path component: %q", p)
}
if sfs.protectNTFS && !pathutil.WindowsValidPath(part) {
return fmt.Errorf("invalid path: %q", p)
}
}
}
return nil
}
// validSymlinkName checks the per-component name of a symlink for
// dotfile names that attackers can use to trick a checkout into
// writing a dangerous symlink. Each path component is compared
// against .gitmodules case-insensitively, against its NTFS variants
// (e.g. ".gitmodules .", ".gitmodules::$INDEX_ALLOCATION", or 8.3
// short-name forms) when protectNTFS is on, and against its HFS+
// variants (Unicode ignored code points folded into ".gitmodules")
// when protectHFS is on.
//
// Reference: upstream Git verify_path_internal at read-cache.c#L1004-L1024
// in tag v2.54.0[1].
//
// [1]: https://github.com/git/git/blob/v2.54.0/read-cache.c#L1004-L1024
func (sfs *worktreeFilesystem) validSymlinkName(name string) error {
parts := strings.FieldsFunc(name, func(r rune) bool {
return r == '/' || r == '\\'
})
for _, part := range parts {
if strings.EqualFold(part, gitmodulesFile) {
return ErrGitModulesSymlink
}
if sfs.protectNTFS && pathutil.IsNTFSDotGitmodules(part) {
return ErrGitModulesSymlink
}
if sfs.protectHFS && pathutil.IsHFSDotGitmodules(part) {
return ErrGitModulesSymlink
}
}
return nil
}
+9
View File
@@ -10,6 +10,7 @@ import (
"strings" "strings"
"github.com/go-git/go-billy/v5/util" "github.com/go-git/go-billy/v5/util"
"github.com/go-git/go-git/v5/internal/pathutil"
"github.com/go-git/go-git/v5/plumbing" "github.com/go-git/go-git/v5/plumbing"
"github.com/go-git/go-git/v5/plumbing/filemode" "github.com/go-git/go-git/v5/plumbing/filemode"
"github.com/go-git/go-git/v5/plumbing/format/gitignore" "github.com/go-git/go-git/v5/plumbing/format/gitignore"
@@ -545,6 +546,14 @@ func (w *Worktree) addOrUpdateFileToIndex(idx *index.Index, filename string, h p
} }
func (w *Worktree) doAddFileToIndex(idx *index.Index, filename string, h plumbing.Hash) error { func (w *Worktree) doAddFileToIndex(idx *index.Index, filename string, h plumbing.Hash) error {
// Mirror upstream's Index.Add gate at the v5 caller boundary: the
// index feeds future trees, so a name that the tree-side
// pathutil.ValidTreePath gate would reject must not enter the
// index in the first place. v5 keeps Index.Add's existing signature
// for API compatibility, so the validation happens here.
if err := pathutil.ValidTreePath(filename); err != nil {
return err
}
return w.doUpdateFileToIndex(idx.Add(filename), filename, h) return w.doUpdateFileToIndex(idx.Add(filename), filename, h)
} }
+1 -1
View File
@@ -1,4 +1,4 @@
FROM golang:1.24@sha256:14fd8a55e59a560704e5fc44970b301d00d344e45d6b914dda228e09f359a088 FROM golang:1.26@sha256:68cb6d68bed024785b69195b89af7ac7a444f27791435f98647edff595aa0479
ENV GOOS=linux ENV GOOS=linux
ENV GOARCH=arm ENV GOARCH=arm
+1 -1
View File
@@ -1,4 +1,4 @@
FROM golang:1.24@sha256:14fd8a55e59a560704e5fc44970b301d00d344e45d6b914dda228e09f359a088 FROM golang:1.26@sha256:68cb6d68bed024785b69195b89af7ac7a444f27791435f98647edff595aa0479
ENV GOOS=linux ENV GOOS=linux
ENV GOARCH=arm64 ENV GOARCH=arm64
-5
View File
@@ -12,7 +12,6 @@ package sha1cd
// Original: https://github.com/golang/go/blob/master/src/crypto/sha1/sha1.go // Original: https://github.com/golang/go/blob/master/src/crypto/sha1/sha1.go
import ( import (
"crypto"
"encoding/binary" "encoding/binary"
"errors" "errors"
"hash" "hash"
@@ -20,10 +19,6 @@ import (
shared "github.com/pjbgf/sha1cd/internal" shared "github.com/pjbgf/sha1cd/internal"
) )
func init() {
crypto.RegisterHash(crypto.SHA1, New)
}
// The size of a SHA-1 checksum in bytes. // The size of a SHA-1 checksum in bytes.
const Size = shared.Size const Size = shared.Size
+2 -2
View File
@@ -37,9 +37,9 @@ func block(dig *digest, p []byte) {
chunk := p[:shared.Chunk] chunk := p[:shared.Chunk]
blockAMD64(dig.h[:], chunk, m1[:], cs[:]) blockAMD64(dig.h[:], chunk, m1[:], cs[:])
rectifyCompressionState(m1, &cs) rectifyCompressionState(&m1, &cs)
col := checkCollision(m1, cs, dig.h) col := checkCollision(&m1, &cs, &dig.h)
if col { if col {
dig.col = true dig.col = true
+4 -4
View File
@@ -11,11 +11,11 @@
// Reference implementations: // Reference implementations:
// - https://github.com/golang/go/blob/master/src/crypto/sha1/sha1block_amd64.s // - https://github.com/golang/go/blob/master/src/crypto/sha1/sha1block_amd64.s
// Reverse the dword order in abcd via PSHUFD then store the 16 bytes in one
// move, instead of issuing four VPEXTRD's that each go through the store port.
#define LOADCS(abcd, e, index, target) \ #define LOADCS(abcd, e, index, target) \
VPEXTRD $3, abcd, ((index*20)+0)(target); \ VPSHUFD $0x1B, abcd, X8; \
VPEXTRD $2, abcd, ((index*20)+4)(target); \ VMOVDQU X8, ((index*20)+0)(target); \
VPEXTRD $1, abcd, ((index*20)+8)(target); \
VPEXTRD $0, abcd, ((index*20)+12)(target); \
MOVL e, ((index*20)+16)(target); MOVL e, ((index*20)+16)(target);
#define LOADM1(m1, index, target) \ #define LOADM1(m1, index, target) \
+2 -2
View File
@@ -34,8 +34,8 @@ func block(dig *digest, p []byte) {
blockARM64(dig.h[:], chunk, m1[:], cs[:]) blockARM64(dig.h[:], chunk, m1[:], cs[:])
rectifyCompressionState(m1, &cs) rectifyCompressionState(&m1, &cs)
col := checkCollision(m1, cs, dig.h) col := checkCollision(&m1, &cs, &dig.h)
if col { if col {
dig.col = true dig.col = true
+13 -12
View File
@@ -127,7 +127,8 @@ func blockGeneric(dig *digest, p []byte) {
} }
if hi == 1 { if hi == 1 {
col := checkCollision(m1, cs, [shared.WordBuffers]uint32{h0, h1, h2, h3, h4}) h := [shared.WordBuffers]uint32{h0, h1, h2, h3, h4}
col := checkCollision(&m1, &cs, &h)
if col { if col {
dig.col = true dig.col = true
hi++ hi++
@@ -143,23 +144,23 @@ func blockGeneric(dig *digest, p []byte) {
//go:noinline //go:noinline
func checkCollision( func checkCollision(
m1 [shared.Rounds]uint32, m1 *[shared.Rounds]uint32,
cs [shared.PreStepState][shared.WordBuffers]uint32, cs *[shared.PreStepState][shared.WordBuffers]uint32,
h [shared.WordBuffers]uint32, h *[shared.WordBuffers]uint32,
) bool { ) bool {
if mask := ubc.CalculateDvMask(m1); mask != 0 { if mask := ubc.CalculateDvMask(m1); mask != 0 {
dvs := ubc.SHA1_dvs() dvs := ubc.SHA1_dvs()
for i := 0; dvs[i].DvType != 0; i++ { for i := 0; dvs[i].DvType != 0; i++ {
if (mask & ((uint32)(1) << uint32(dvs[i].MaskB))) != 0 { if (mask & ((uint32)(1) << uint32(dvs[i].MaskB))) != 0 {
var csState [shared.WordBuffers]uint32 var csState *[shared.WordBuffers]uint32
switch dvs[i].TestT { switch dvs[i].TestT {
case 58: case 58:
csState = cs[1] csState = &cs[1]
case 65: case 65:
csState = cs[2] csState = &cs[2]
case 0: case 0:
csState = cs[0] csState = &cs[0]
default: default:
panic(fmt.Sprintf("dvs data is trying to use a testT that isn't available: %d", dvs[i].TestT)) panic(fmt.Sprintf("dvs data is trying to use a testT that isn't available: %d", dvs[i].TestT))
} }
@@ -168,7 +169,7 @@ func checkCollision(
dvs[i].TestT, // testT is the step number dvs[i].TestT, // testT is the step number
// m2 is a secondary message created XORing with // m2 is a secondary message created XORing with
// ubc's DM prior to the SHA recompression step. // ubc's DM prior to the SHA recompression step.
m1, dvs[i].Dm, m1, &dvs[i].Dm,
csState, csState,
h) h)
@@ -182,8 +183,8 @@ func checkCollision(
} }
//go:nosplit //go:nosplit
func hasCollided(step uint32, m1, dm [shared.Rounds]uint32, func hasCollided(step uint32, m1, dm *[shared.Rounds]uint32,
state [shared.WordBuffers]uint32, h [shared.WordBuffers]uint32) bool { state *[shared.WordBuffers]uint32, h *[shared.WordBuffers]uint32) bool {
// Intermediary Hash Value. // Intermediary Hash Value.
ihv := [shared.WordBuffers]uint32{} ihv := [shared.WordBuffers]uint32{}
@@ -282,7 +283,7 @@ func hasCollided(step uint32, m1, dm [shared.Rounds]uint32,
// //
//go:nosplit //go:nosplit
func rectifyCompressionState( func rectifyCompressionState(
m1 [shared.Rounds]uint32, m1 *[shared.Rounds]uint32,
cs *[shared.PreStepState][shared.WordBuffers]uint32, cs *[shared.PreStepState][shared.WordBuffers]uint32,
) { ) {
if cs == nil { if cs == nil {
+4 -1
View File
@@ -29,7 +29,10 @@ type DvInfo struct {
// bitconditions for that DV have been met. // bitconditions for that DV have been met.
// //
//go:nosplit //go:nosplit
func CalculateDvMask(W [80]uint32) uint32 { func CalculateDvMask(W *[80]uint32) uint32 {
if W == nil {
return 0
}
mask := uint32(0xFFFFFFFF) mask := uint32(0xFFFFFFFF)
mask &= (((((W[44] ^ W[45]) >> 29) & 1) - 1) | ^(DV_I_48_0_bit | DV_I_51_0_bit | DV_I_52_0_bit | DV_II_45_0_bit | DV_II_46_0_bit | DV_II_50_0_bit | DV_II_51_0_bit)) mask &= (((((W[44] ^ W[45]) >> 29) & 1) - 1) | ^(DV_I_48_0_bit | DV_I_51_0_bit | DV_I_52_0_bit | DV_II_45_0_bit | DV_II_46_0_bit | DV_II_50_0_bit | DV_II_51_0_bit))
mask &= (((((W[49] ^ W[50]) >> 29) & 1) - 1) | ^(DV_I_46_0_bit | DV_II_45_0_bit | DV_II_50_0_bit | DV_II_51_0_bit | DV_II_55_0_bit | DV_II_56_0_bit)) mask &= (((((W[49] ^ W[50]) >> 29) & 1) - 1) | ^(DV_I_46_0_bit | DV_II_45_0_bit | DV_II_50_0_bit | DV_II_51_0_bit | DV_II_55_0_bit | DV_II_56_0_bit))
+5 -11
View File
@@ -925,15 +925,10 @@ func (c *Command) execute(a []string) (err error) {
// Also say we need help if the command isn't runnable. // Also say we need help if the command isn't runnable.
helpVal, err := c.Flags().GetBool(helpFlagName) helpVal, err := c.Flags().GetBool(helpFlagName)
if err != nil { if err != nil {
// NOTE(d1): temporarily hardcoding "ayuda" as a replacement for "help" // should be impossible to get here as we always declare a help
// source of the pain: https://github.com/spf13/cobra/issues/2359 // flag in InitDefaultHelpFlag()
helpVal, err = c.Flags().GetBool("ayuda") c.Println("\"help\" flag declared as non-bool. Please correct your code")
if err != nil { return err
// should be impossible to get here as we always declare a help
// flag in InitDefaultHelpFlag()
c.Println("\"help\" flag declared as non-bool. Please correct your code")
return err
}
} }
if helpVal { if helpVal {
@@ -1231,8 +1226,7 @@ func (c *Command) InitDefaultHelpFlag() {
} else { } else {
usage += name usage += name
} }
// NOTE(d1): do not assume "help" exists in the context of translation c.Flags().BoolP(helpFlagName, "h", false, usage)
// c.Flags().BoolP(helpFlagName, "h", false, usage)
_ = c.Flags().SetAnnotation(helpFlagName, FlagSetByCobraAnnotation, []string{"true"}) _ = c.Flags().SetAnnotation(helpFlagName, FlagSetByCobraAnnotation, []string{"true"})
} }
} }
+1 -1
View File
@@ -1,4 +1,4 @@
# This is a renovate-friendly source of Docker images. # This is a renovate-friendly source of Docker images.
FROM python:3.13.6-slim-bullseye@sha256:e98b521460ee75bca92175c16247bdf7275637a8faaeb2bcfa19d879ae5c4b9a AS python FROM python:3.13.6-slim-bullseye@sha256:e98b521460ee75bca92175c16247bdf7275637a8faaeb2bcfa19d879ae5c4b9a AS python
FROM otel/weaver:v0.21.2@sha256:2401de985c38bdb98b43918e2f43aa36b2afed4aa5669ac1c1de0a17301cd36d AS weaver FROM otel/weaver:v0.24.2@sha256:d1fb16d279f39810c340fbbf1cf9e5e995a3a9cefa531938e9012437e3bc00c1 AS weaver
FROM avtodev/markdown-lint:v1@sha256:6aeedc2f49138ce7a1cd0adffc1b1c0321b841dc2102408967d9301c031949ee AS markdown FROM avtodev/markdown-lint:v1@sha256:6aeedc2f49138ce7a1cd0adffc1b1c0321b841dc2102408967d9301c031949ee AS markdown
+1 -1
View File
@@ -586,7 +586,7 @@ func (c *cbcCipher) writeCipherPacket(seqNum uint32, w io.Writer, rand io.Reader
// Length of encrypted portion of the packet (header, payload, padding). // Length of encrypted portion of the packet (header, payload, padding).
// Enforce minimum padding and packet size. // Enforce minimum padding and packet size.
encLength := maxUInt32(prefixLen+len(packet)+cbcMinPaddingSize, cbcMinPaddingSize) encLength := maxUInt32(prefixLen+len(packet)+cbcMinPaddingSize, cbcMinPacketSize)
// Enforce block size. // Enforce block size.
encLength = (encLength + effectiveBlockSize - 1) / effectiveBlockSize * effectiveBlockSize encLength = (encLength + effectiveBlockSize - 1) / effectiveBlockSize * effectiveBlockSize
+7 -3
View File
@@ -274,10 +274,14 @@ func pickSignatureAlgorithm(signer Signer, extensions map[string][]byte) (MultiA
} }
// Filter algorithms based on those supported by MultiAlgorithmSigner. // Filter algorithms based on those supported by MultiAlgorithmSigner.
// Iterate over the signer's algorithms first to preserve its preference order.
supportedKeyAlgos := algorithmsForKeyFormat(keyFormat)
var keyAlgos []string var keyAlgos []string
for _, algo := range algorithmsForKeyFormat(keyFormat) { for _, signerAlgo := range as.Algorithms() {
if slices.Contains(as.Algorithms(), underlyingAlgo(algo)) { if idx := slices.IndexFunc(supportedKeyAlgos, func(algo string) bool {
keyAlgos = append(keyAlgos, algo) return underlyingAlgo(algo) == signerAlgo
}); idx >= 0 {
keyAlgos = append(keyAlgos, supportedKeyAlgos[idx])
} }
} }
+10 -3
View File
@@ -6,6 +6,7 @@ package hpack
import ( import (
"fmt" "fmt"
"strings"
) )
// headerFieldTable implements a list of HeaderFields. // headerFieldTable implements a list of HeaderFields.
@@ -54,10 +55,16 @@ func (t *headerFieldTable) len() int {
// addEntry adds a new entry. // addEntry adds a new entry.
func (t *headerFieldTable) addEntry(f HeaderField) { func (t *headerFieldTable) addEntry(f HeaderField) {
// Prevent f from escaping to the heap.
f2 := HeaderField{
Name: strings.Clone(f.Name),
Value: strings.Clone(f.Value),
Sensitive: f.Sensitive,
}
id := uint64(t.len()) + t.evictCount + 1 id := uint64(t.len()) + t.evictCount + 1
t.byName[f.Name] = id t.byName[f2.Name] = id
t.byNameValue[pairNameValue{f.Name, f.Value}] = id t.byNameValue[pairNameValue{f2.Name, f2.Value}] = id
t.ents = append(t.ents, f) t.ents = append(t.ents, f2)
} }
// evictOldest evicts the n oldest entries in the table. // evictOldest evicts the n oldest entries in the table.
+3 -6
View File
@@ -718,9 +718,6 @@ func canRetryError(err error) bool {
} }
func (t *Transport) dialClientConn(ctx context.Context, addr string, singleUse bool) (*ClientConn, error) { func (t *Transport) dialClientConn(ctx context.Context, addr string, singleUse bool) (*ClientConn, error) {
if t.transportTestHooks != nil {
return t.newClientConn(nil, singleUse, nil)
}
host, _, err := net.SplitHostPort(addr) host, _, err := net.SplitHostPort(addr)
if err != nil { if err != nil {
return nil, err return nil, err
@@ -2861,6 +2858,9 @@ func (rl *clientConnReadLoop) processSettingsNoWrite(f *SettingsFrame) error {
var seenMaxConcurrentStreams bool var seenMaxConcurrentStreams bool
err := f.ForeachSetting(func(s Setting) error { err := f.ForeachSetting(func(s Setting) error {
if err := s.Valid(); err != nil {
return err
}
switch s.ID { switch s.ID {
case SettingMaxFrameSize: case SettingMaxFrameSize:
cc.maxFrameSize = s.Val cc.maxFrameSize = s.Val
@@ -2892,9 +2892,6 @@ func (rl *clientConnReadLoop) processSettingsNoWrite(f *SettingsFrame) error {
cc.henc.SetMaxDynamicTableSize(s.Val) cc.henc.SetMaxDynamicTableSize(s.Val)
cc.peerMaxHeaderTableSize = s.Val cc.peerMaxHeaderTableSize = s.Val
case SettingEnableConnectProtocol: case SettingEnableConnectProtocol:
if err := s.Valid(); err != nil {
return err
}
// If the peer wants to send us SETTINGS_ENABLE_CONNECT_PROTOCOL, // If the peer wants to send us SETTINGS_ENABLE_CONNECT_PROTOCOL,
// we require that it do so in the first SETTINGS frame. // we require that it do so in the first SETTINGS frame.
// //
+12 -7
View File
@@ -152,13 +152,17 @@ var ARM struct {
// The booleans in Loong64 contain the correspondingly named cpu feature bit. // The booleans in Loong64 contain the correspondingly named cpu feature bit.
// The struct is padded to avoid false sharing. // The struct is padded to avoid false sharing.
var Loong64 struct { var Loong64 struct {
_ CacheLinePad _ CacheLinePad
HasLSX bool // support 128-bit vector extension HasLSX bool // support 128-bit vector extension
HasLASX bool // support 256-bit vector extension HasLASX bool // support 256-bit vector extension
HasCRC32 bool // support CRC instruction HasCRC32 bool // support CRC instruction
HasLAM_BH bool // support AM{SWAP/ADD}[_DB].{B/H} instruction HasLAMCAS bool // support AMCAS[_DB].{B/H/W/D}
HasLAMCAS bool // support AMCAS[_DB].{B/H/W/D} instruction HasLAM_BH bool // support AM{SWAP/ADD}[_DB].{B/H} instruction
_ CacheLinePad HasLLACQ_SCREL bool // support LLACQ.{W/D}, SCREL.{W/D} instruction
HasSCQ bool // support SC.Q instruction
HasDBAR_HINTS bool // supports finer-grained DBAR hints
_ CacheLinePad
} }
// MIPS64X contains the supported CPU features of the current mips64/mips64le // MIPS64X contains the supported CPU features of the current mips64/mips64le
@@ -232,6 +236,7 @@ var RISCV64 struct {
HasZba bool // Address generation instructions extension HasZba bool // Address generation instructions extension
HasZbb bool // Basic bit-manipulation extension HasZbb bool // Basic bit-manipulation extension
HasZbs bool // Single-bit instructions extension HasZbs bool // Single-bit instructions extension
HasZbc bool // Carryless multiplication extension
HasZvbb bool // Vector Basic Bit-manipulation HasZvbb bool // Vector Basic Bit-manipulation
HasZvbc bool // Vector Carryless Multiplication HasZvbc bool // Vector Carryless Multiplication
HasZvkb bool // Vector Cryptography Bit-manipulation HasZvkb bool // Vector Cryptography Bit-manipulation
+2
View File
@@ -6,6 +6,8 @@
package cpu package cpu
import "runtime"
func doinit() { func doinit() {
setMinimalFeatures() setMinimalFeatures()
+2
View File
@@ -58,6 +58,7 @@ const (
riscv_HWPROBE_EXT_ZBA = 0x8 riscv_HWPROBE_EXT_ZBA = 0x8
riscv_HWPROBE_EXT_ZBB = 0x10 riscv_HWPROBE_EXT_ZBB = 0x10
riscv_HWPROBE_EXT_ZBS = 0x20 riscv_HWPROBE_EXT_ZBS = 0x20
riscv_HWPROBE_EXT_ZBC = 0x80
riscv_HWPROBE_EXT_ZVBB = 0x20000 riscv_HWPROBE_EXT_ZVBB = 0x20000
riscv_HWPROBE_EXT_ZVBC = 0x40000 riscv_HWPROBE_EXT_ZVBC = 0x40000
riscv_HWPROBE_EXT_ZVKB = 0x80000 riscv_HWPROBE_EXT_ZVKB = 0x80000
@@ -108,6 +109,7 @@ func doinit() {
RISCV64.HasZba = isSet(v, riscv_HWPROBE_EXT_ZBA) RISCV64.HasZba = isSet(v, riscv_HWPROBE_EXT_ZBA)
RISCV64.HasZbb = isSet(v, riscv_HWPROBE_EXT_ZBB) RISCV64.HasZbb = isSet(v, riscv_HWPROBE_EXT_ZBB)
RISCV64.HasZbs = isSet(v, riscv_HWPROBE_EXT_ZBS) RISCV64.HasZbs = isSet(v, riscv_HWPROBE_EXT_ZBS)
RISCV64.HasZbc = isSet(v, riscv_HWPROBE_EXT_ZBC)
RISCV64.HasZvbb = isSet(v, riscv_HWPROBE_EXT_ZVBB) RISCV64.HasZvbb = isSet(v, riscv_HWPROBE_EXT_ZVBB)
RISCV64.HasZvbc = isSet(v, riscv_HWPROBE_EXT_ZVBC) RISCV64.HasZvbc = isSet(v, riscv_HWPROBE_EXT_ZVBC)
RISCV64.HasZvkb = isSet(v, riscv_HWPROBE_EXT_ZVKB) RISCV64.HasZvkb = isSet(v, riscv_HWPROBE_EXT_ZVKB)
+14 -2
View File
@@ -15,8 +15,13 @@ const (
cpucfg1_CRC32 = 1 << 25 cpucfg1_CRC32 = 1 << 25
// CPUCFG2 bits // CPUCFG2 bits
cpucfg2_LAM_BH = 1 << 27 cpucfg2_LAM_BH = 1 << 27
cpucfg2_LAMCAS = 1 << 28 cpucfg2_LAMCAS = 1 << 28
cpucfg2_LLACQ_SCREL = 1 << 29
cpucfg2_SCQ = 1 << 30
// CPUCFG3 bits
cpucfg3_DBAR_HINTS = 1 << 17
) )
func initOptions() { func initOptions() {
@@ -26,6 +31,9 @@ func initOptions() {
{Name: "crc32", Feature: &Loong64.HasCRC32}, {Name: "crc32", Feature: &Loong64.HasCRC32},
{Name: "lam_bh", Feature: &Loong64.HasLAM_BH}, {Name: "lam_bh", Feature: &Loong64.HasLAM_BH},
{Name: "lamcas", Feature: &Loong64.HasLAMCAS}, {Name: "lamcas", Feature: &Loong64.HasLAMCAS},
{Name: "llacq_screl", Feature: &Loong64.HasLLACQ_SCREL},
{Name: "scq", Feature: &Loong64.HasSCQ},
{Name: "dbar_hints", Feature: &Loong64.HasDBAR_HINTS},
} }
// The CPUCFG data on Loong64 only reflects the hardware capabilities, // The CPUCFG data on Loong64 only reflects the hardware capabilities,
@@ -37,10 +45,14 @@ func initOptions() {
// through CPUCFG // through CPUCFG
cfg1 := get_cpucfg(1) cfg1 := get_cpucfg(1)
cfg2 := get_cpucfg(2) cfg2 := get_cpucfg(2)
cfg3 := get_cpucfg(3)
Loong64.HasCRC32 = cfgIsSet(cfg1, cpucfg1_CRC32) Loong64.HasCRC32 = cfgIsSet(cfg1, cpucfg1_CRC32)
Loong64.HasLAMCAS = cfgIsSet(cfg2, cpucfg2_LAMCAS) Loong64.HasLAMCAS = cfgIsSet(cfg2, cpucfg2_LAMCAS)
Loong64.HasLAM_BH = cfgIsSet(cfg2, cpucfg2_LAM_BH) Loong64.HasLAM_BH = cfgIsSet(cfg2, cpucfg2_LAM_BH)
Loong64.HasLLACQ_SCREL = cfgIsSet(cfg2, cpucfg2_LLACQ_SCREL)
Loong64.HasSCQ = cfgIsSet(cfg2, cpucfg2_SCQ)
Loong64.HasDBAR_HINTS = cfgIsSet(cfg3, cpucfg3_DBAR_HINTS)
} }
func get_cpucfg(reg uint32) uint32 func get_cpucfg(reg uint32) uint32
+1
View File
@@ -16,6 +16,7 @@ func initOptions() {
{Name: "zba", Feature: &RISCV64.HasZba}, {Name: "zba", Feature: &RISCV64.HasZba},
{Name: "zbb", Feature: &RISCV64.HasZbb}, {Name: "zbb", Feature: &RISCV64.HasZbb},
{Name: "zbs", Feature: &RISCV64.HasZbs}, {Name: "zbs", Feature: &RISCV64.HasZbs},
{Name: "zbc", Feature: &RISCV64.HasZbc},
// RISC-V Cryptography Extensions // RISC-V Cryptography Extensions
{Name: "zvbb", Feature: &RISCV64.HasZvbb}, {Name: "zvbb", Feature: &RISCV64.HasZvbb},
{Name: "zvbc", Feature: &RISCV64.HasZvbc}, {Name: "zvbc", Feature: &RISCV64.HasZvbc},
+26
View File
@@ -0,0 +1,26 @@
// Copyright 2026 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package cpu
//go:generate go run golang.org/x/sys/windows/mkwinsyscall -systemdll=false -output zcpu_windows.go cpu_windows.go
//sys isProcessorFeaturePresent(ProcessorFeature uint32) (ret bool) = kernel32.IsProcessorFeaturePresent
// The processor features to be tested for IsProcessorFeaturePresent, see
// https://learn.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-isprocessorfeaturepresent
const (
_PF_ARM_V8_CRYPTO_INSTRUCTIONS_AVAILABLE = 30
_PF_ARM_V8_CRC32_INSTRUCTIONS_AVAILABLE = 31
_PF_ARM_V81_ATOMIC_INSTRUCTIONS_AVAILABLE = 34
_PF_ARM_V82_DP_INSTRUCTIONS_AVAILABLE = 43
_PF_ARM_V83_JSCVT_INSTRUCTIONS_AVAILABLE = 44
_PF_ARM_V83_LRCPC_INSTRUCTIONS_AVAILABLE = 45
_PF_ARM_SVE_INSTRUCTIONS_AVAILABLE = 46
_PF_ARM_SVE2_INSTRUCTIONS_AVAILABLE = 47
_PF_ARM_SHA3_INSTRUCTIONS_AVAILABLE = 64
_PF_ARM_SHA512_INSTRUCTIONS_AVAILABLE = 65
)
+10 -14
View File
@@ -4,10 +4,6 @@
package cpu package cpu
import (
"golang.org/x/sys/windows"
)
func doinit() { func doinit() {
// set HasASIMD and HasFP to true as per // set HasASIMD and HasFP to true as per
// https://learn.microsoft.com/en-us/cpp/build/arm64-windows-abi-conventions?view=msvc-170#base-requirements // https://learn.microsoft.com/en-us/cpp/build/arm64-windows-abi-conventions?view=msvc-170#base-requirements
@@ -18,25 +14,25 @@ func doinit() {
ARM64.HasASIMD = true ARM64.HasASIMD = true
ARM64.HasFP = true ARM64.HasFP = true
if windows.IsProcessorFeaturePresent(windows.PF_ARM_V8_CRYPTO_INSTRUCTIONS_AVAILABLE) { if isProcessorFeaturePresent(_PF_ARM_V8_CRYPTO_INSTRUCTIONS_AVAILABLE) {
ARM64.HasAES = true ARM64.HasAES = true
ARM64.HasPMULL = true ARM64.HasPMULL = true
ARM64.HasSHA1 = true ARM64.HasSHA1 = true
ARM64.HasSHA2 = true ARM64.HasSHA2 = true
} }
ARM64.HasSHA3 = windows.IsProcessorFeaturePresent(windows.PF_ARM_SHA3_INSTRUCTIONS_AVAILABLE) ARM64.HasSHA3 = isProcessorFeaturePresent(_PF_ARM_SHA3_INSTRUCTIONS_AVAILABLE)
ARM64.HasCRC32 = windows.IsProcessorFeaturePresent(windows.PF_ARM_V8_CRC32_INSTRUCTIONS_AVAILABLE) ARM64.HasCRC32 = isProcessorFeaturePresent(_PF_ARM_V8_CRC32_INSTRUCTIONS_AVAILABLE)
ARM64.HasSHA512 = windows.IsProcessorFeaturePresent(windows.PF_ARM_SHA512_INSTRUCTIONS_AVAILABLE) ARM64.HasSHA512 = isProcessorFeaturePresent(_PF_ARM_SHA512_INSTRUCTIONS_AVAILABLE)
ARM64.HasATOMICS = windows.IsProcessorFeaturePresent(windows.PF_ARM_V81_ATOMIC_INSTRUCTIONS_AVAILABLE) ARM64.HasATOMICS = isProcessorFeaturePresent(_PF_ARM_V81_ATOMIC_INSTRUCTIONS_AVAILABLE)
if windows.IsProcessorFeaturePresent(windows.PF_ARM_V82_DP_INSTRUCTIONS_AVAILABLE) { if isProcessorFeaturePresent(_PF_ARM_V82_DP_INSTRUCTIONS_AVAILABLE) {
ARM64.HasASIMDDP = true ARM64.HasASIMDDP = true
ARM64.HasASIMDRDM = true ARM64.HasASIMDRDM = true
} }
if windows.IsProcessorFeaturePresent(windows.PF_ARM_V83_LRCPC_INSTRUCTIONS_AVAILABLE) { if isProcessorFeaturePresent(_PF_ARM_V83_LRCPC_INSTRUCTIONS_AVAILABLE) {
ARM64.HasLRCPC = true ARM64.HasLRCPC = true
ARM64.HasSM3 = true ARM64.HasSM3 = true
} }
ARM64.HasSVE = windows.IsProcessorFeaturePresent(windows.PF_ARM_SVE_INSTRUCTIONS_AVAILABLE) ARM64.HasSVE = isProcessorFeaturePresent(_PF_ARM_SVE_INSTRUCTIONS_AVAILABLE)
ARM64.HasSVE2 = windows.IsProcessorFeaturePresent(windows.PF_ARM_SVE2_INSTRUCTIONS_AVAILABLE) ARM64.HasSVE2 = isProcessorFeaturePresent(_PF_ARM_SVE2_INSTRUCTIONS_AVAILABLE)
ARM64.HasJSCVT = windows.IsProcessorFeaturePresent(windows.PF_ARM_V83_JSCVT_INSTRUCTIONS_AVAILABLE) ARM64.HasJSCVT = isProcessorFeaturePresent(_PF_ARM_V83_JSCVT_INSTRUCTIONS_AVAILABLE)
} }
+48
View File
@@ -0,0 +1,48 @@
// Code generated by 'go generate'; DO NOT EDIT.
package cpu
import (
"syscall"
"unsafe"
)
var _ unsafe.Pointer
// Do the interface allocations only once for common
// Errno values.
const (
errnoERROR_IO_PENDING = 997
)
var (
errERROR_IO_PENDING error = syscall.Errno(errnoERROR_IO_PENDING)
errERROR_EINVAL error = syscall.EINVAL
)
// errnoErr returns common boxed Errno values, to prevent
// allocations at runtime.
func errnoErr(e syscall.Errno) error {
switch e {
case 0:
return errERROR_EINVAL
case errnoERROR_IO_PENDING:
return errERROR_IO_PENDING
}
// TODO: add more here, after collecting data on the common
// error values see on Windows. (perhaps when running
// all.bat?)
return e
}
var (
modkernel32 = syscall.NewLazyDLL("kernel32.dll")
procIsProcessorFeaturePresent = modkernel32.NewProc("IsProcessorFeaturePresent")
)
func isProcessorFeaturePresent(ProcessorFeature uint32) (ret bool) {
r0, _, _ := syscall.SyscallN(procIsProcessorFeaturePresent.Addr(), uintptr(ProcessorFeature))
ret = r0 != 0
return
}
+112 -16
View File
@@ -13,11 +13,19 @@ import (
const cpuSetSize = _CPU_SETSIZE / _NCPUBITS const cpuSetSize = _CPU_SETSIZE / _NCPUBITS
// CPUSet represents a CPU affinity mask. // CPUSet represents a bit mask of CPUs, to be used with [SchedGetaffinity], [SchedSetaffinity],
// and [SetMemPolicy].
//
// Note this type can only represent CPU IDs 0 through 1023.
// Use [CPUSetDynamic]/[NewCPUSet] instead to avoid this limit.
type CPUSet [cpuSetSize]cpuMask type CPUSet [cpuSetSize]cpuMask
func schedAffinity(trap uintptr, pid int, set *CPUSet) error { // CPUSetDynamic represents a bit mask of CPUs, to be used with [SchedGetaffinityDynamic],
_, _, e := RawSyscall(trap, uintptr(pid), uintptr(unsafe.Sizeof(*set)), uintptr(unsafe.Pointer(set))) // [SchedSetaffinityDynamic], and [SetMemPolicyDynamic]. Use [NewCPUSet] to allocate.
type CPUSetDynamic []cpuMask
func schedAffinity(trap uintptr, pid int, size uintptr, ptr unsafe.Pointer) error {
_, _, e := RawSyscall(trap, uintptr(pid), uintptr(size), uintptr(ptr))
if e != 0 { if e != 0 {
return errnoErr(e) return errnoErr(e)
} }
@@ -27,13 +35,13 @@ func schedAffinity(trap uintptr, pid int, set *CPUSet) error {
// SchedGetaffinity gets the CPU affinity mask of the thread specified by pid. // SchedGetaffinity gets the CPU affinity mask of the thread specified by pid.
// If pid is 0 the calling thread is used. // If pid is 0 the calling thread is used.
func SchedGetaffinity(pid int, set *CPUSet) error { func SchedGetaffinity(pid int, set *CPUSet) error {
return schedAffinity(SYS_SCHED_GETAFFINITY, pid, set) return schedAffinity(SYS_SCHED_GETAFFINITY, pid, unsafe.Sizeof(*set), unsafe.Pointer(set))
} }
// SchedSetaffinity sets the CPU affinity mask of the thread specified by pid. // SchedSetaffinity sets the CPU affinity mask of the thread specified by pid.
// If pid is 0 the calling thread is used. // If pid is 0 the calling thread is used.
func SchedSetaffinity(pid int, set *CPUSet) error { func SchedSetaffinity(pid int, set *CPUSet) error {
return schedAffinity(SYS_SCHED_SETAFFINITY, pid, set) return schedAffinity(SYS_SCHED_SETAFFINITY, pid, unsafe.Sizeof(*set), unsafe.Pointer(set))
} }
// Zero clears the set s, so that it contains no CPUs. // Zero clears the set s, so that it contains no CPUs.
@@ -45,9 +53,7 @@ func (s *CPUSet) Zero() {
// will silently ignore any invalid CPU bits in [CPUSet] so this is an // will silently ignore any invalid CPU bits in [CPUSet] so this is an
// efficient way of resetting the CPU affinity of a process. // efficient way of resetting the CPU affinity of a process.
func (s *CPUSet) Fill() { func (s *CPUSet) Fill() {
for i := range s { cpuMaskFill(s[:])
s[i] = ^cpuMask(0)
}
} }
func cpuBitsIndex(cpu int) int { func cpuBitsIndex(cpu int) int {
@@ -58,24 +64,27 @@ func cpuBitsMask(cpu int) cpuMask {
return cpuMask(1 << (uint(cpu) % _NCPUBITS)) return cpuMask(1 << (uint(cpu) % _NCPUBITS))
} }
// Set adds cpu to the set s. func cpuMaskFill(s []cpuMask) {
func (s *CPUSet) Set(cpu int) { for i := range s {
s[i] = ^cpuMask(0)
}
}
func cpuMaskSet(s []cpuMask, cpu int) {
i := cpuBitsIndex(cpu) i := cpuBitsIndex(cpu)
if i < len(s) { if i < len(s) {
s[i] |= cpuBitsMask(cpu) s[i] |= cpuBitsMask(cpu)
} }
} }
// Clear removes cpu from the set s. func cpuMaskClear(s []cpuMask, cpu int) {
func (s *CPUSet) Clear(cpu int) {
i := cpuBitsIndex(cpu) i := cpuBitsIndex(cpu)
if i < len(s) { if i < len(s) {
s[i] &^= cpuBitsMask(cpu) s[i] &^= cpuBitsMask(cpu)
} }
} }
// IsSet reports whether cpu is in the set s. func cpuMaskIsSet(s []cpuMask, cpu int) bool {
func (s *CPUSet) IsSet(cpu int) bool {
i := cpuBitsIndex(cpu) i := cpuBitsIndex(cpu)
if i < len(s) { if i < len(s) {
return s[i]&cpuBitsMask(cpu) != 0 return s[i]&cpuBitsMask(cpu) != 0
@@ -83,11 +92,98 @@ func (s *CPUSet) IsSet(cpu int) bool {
return false return false
} }
// Count returns the number of CPUs in the set s. func cpuMaskCount(s []cpuMask) int {
func (s *CPUSet) Count() int {
c := 0 c := 0
for _, b := range s { for _, b := range s {
c += bits.OnesCount64(uint64(b)) c += bits.OnesCount64(uint64(b))
} }
return c return c
} }
// Set adds cpu to the set s. If cpu is out of bounds for s, no action is taken.
func (s *CPUSet) Set(cpu int) {
cpuMaskSet(s[:], cpu)
}
// Clear removes cpu from the set s. If cpu is out of bounds for s, no action is taken.
func (s *CPUSet) Clear(cpu int) {
cpuMaskClear(s[:], cpu)
}
// IsSet reports whether cpu is in the set s.
func (s *CPUSet) IsSet(cpu int) bool {
return cpuMaskIsSet(s[:], cpu)
}
// Count returns the number of CPUs in the set s.
func (s *CPUSet) Count() int {
return cpuMaskCount(s[:])
}
// NewCPUSet creates a CPU affinity mask capable of representing CPU IDs
// up to maxCPU (exclusive).
func NewCPUSet(maxCPU int) CPUSetDynamic {
numMasks := (maxCPU + _NCPUBITS - 1) / _NCPUBITS
if numMasks == 0 {
numMasks = 1
}
return make(CPUSetDynamic, numMasks)
}
// Zero clears the set s, so that it contains no CPUs.
func (s CPUSetDynamic) Zero() {
clear(s)
}
// Fill adds all possible CPU bits to the set s. On Linux, [SchedSetaffinityDynamic]
// will silently ignore any invalid CPU bits in [CPUSetDynamic] so this is an
// efficient way of resetting the CPU affinity of a process.
func (s CPUSetDynamic) Fill() {
cpuMaskFill(s)
}
// Set adds cpu to the set s. If cpu is out of bounds for s, no action is taken.
func (s CPUSetDynamic) Set(cpu int) {
cpuMaskSet(s, cpu)
}
// Clear removes cpu from the set s. If cpu is out of bounds for s, no action is taken.
func (s CPUSetDynamic) Clear(cpu int) {
cpuMaskClear(s, cpu)
}
// IsSet reports whether cpu is in the set s.
func (s CPUSetDynamic) IsSet(cpu int) bool {
return cpuMaskIsSet(s, cpu)
}
// Count returns the number of CPUs in the set s.
func (s CPUSetDynamic) Count() int {
return cpuMaskCount(s)
}
func (s CPUSetDynamic) size() uintptr {
return uintptr(len(s)) * unsafe.Sizeof(cpuMask(0))
}
func (s CPUSetDynamic) pointer() unsafe.Pointer {
if len(s) == 0 {
return nil
}
return unsafe.Pointer(&s[0])
}
// SchedGetaffinityDynamic gets the CPU affinity mask of the thread specified by pid.
// If pid is 0 the calling thread is used.
//
// If the set is smaller than the size of the affinity mask used by the kernel,
// [EINVAL] is returned.
func SchedGetaffinityDynamic(pid int, set CPUSetDynamic) error {
return schedAffinity(SYS_SCHED_GETAFFINITY, pid, set.size(), set.pointer())
}
// SchedSetaffinityDynamic sets the CPU affinity mask of the thread specified by pid.
// If pid is 0 the calling thread is used.
func SchedSetaffinityDynamic(pid int, set CPUSetDynamic) error {
return schedAffinity(SYS_SCHED_SETAFFINITY, pid, set.size(), set.pointer())
}
+1 -1
View File
@@ -51,7 +51,7 @@ if [[ "$GOOS" = "linux" ]]; then
# Files generated through docker (use $cmd so you can Ctl-C the build or run) # Files generated through docker (use $cmd so you can Ctl-C the build or run)
set -e set -e
$cmd docker build --tag generate:$GOOS $GOOS $cmd docker build --tag generate:$GOOS $GOOS
$cmd docker run --interactive --tty --volume $(cd -- "$(dirname -- "$0")/.." && pwd):/build generate:$GOOS $cmd docker run --rm --interactive --tty --volume $(cd -- "$(dirname -- "$0")/.." && pwd):/build generate:$GOOS
exit exit
fi fi
+3
View File
@@ -354,6 +354,9 @@ struct ltchars {
// Renamed in v6.16, commit c6d732c38f93 ("net: ethtool: remove duplicate defines for family info") // Renamed in v6.16, commit c6d732c38f93 ("net: ethtool: remove duplicate defines for family info")
#define ETHTOOL_FAMILY_NAME ETHTOOL_GENL_NAME #define ETHTOOL_FAMILY_NAME ETHTOOL_GENL_NAME
#define ETHTOOL_FAMILY_VERSION ETHTOOL_GENL_VERSION #define ETHTOOL_FAMILY_VERSION ETHTOOL_GENL_VERSION
// Removed in v6.17, commit 760e6f7befba ("futex: Remove support for IMMUTABLE")
#define PR_FUTEX_HASH_GET_IMMUTABLE 3
' '
includes_NetBSD=' includes_NetBSD='
+103
View File
@@ -0,0 +1,103 @@
// Copyright 2026 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
//go:build darwin || linux || openbsd
package unix
import "unsafe"
// minIovec is the size of the small initial allocation used by
// Readv, Writev, etc.
//
// This small allocation gets stack allocated, which lets the
// common use case of len(iovs) <= minIovec avoid more expensive
// heap allocations.
const minIovec = 8
// appendBytes converts bs to Iovecs and appends them to vecs.
func appendBytes(vecs []Iovec, bs [][]byte) []Iovec {
for _, b := range bs {
var v Iovec
v.SetLen(len(b))
if len(b) > 0 {
v.Base = &b[0]
} else {
v.Base = (*byte)(unsafe.Pointer(&_zero))
}
vecs = append(vecs, v)
}
return vecs
}
// writevRaceDetect tells the race detector that the program
// has read the first n bytes stored in iovecs.
func writevRaceDetect(iovecs []Iovec, n int) {
if !raceenabled {
return
}
for i := 0; n > 0 && i < len(iovecs); i++ {
m := min(int(iovecs[i].Len), n)
n -= m
if m > 0 {
raceReadRange(unsafe.Pointer(iovecs[i].Base), m)
}
}
}
// readvRaceDetect tells the race detector that the program
// has written to the first n bytes stored in iovecs.
func readvRaceDetect(iovecs []Iovec, n int, err error) {
if !raceenabled {
return
}
for i := 0; n > 0 && i < len(iovecs); i++ {
m := min(int(iovecs[i].Len), n)
n -= m
if m > 0 {
raceWriteRange(unsafe.Pointer(iovecs[i].Base), m)
}
}
if err == nil {
raceAcquire(unsafe.Pointer(&ioSync))
}
}
func Readv(fd int, iovs [][]byte) (n int, err error) {
iovecs := make([]Iovec, 0, minIovec)
iovecs = appendBytes(iovecs, iovs)
n, err = readv(fd, iovecs)
readvRaceDetect(iovecs, n, err)
return n, err
}
func Preadv(fd int, iovs [][]byte, offset int64) (n int, err error) {
iovecs := make([]Iovec, 0, minIovec)
iovecs = appendBytes(iovecs, iovs)
n, err = preadv(fd, iovecs, offset)
readvRaceDetect(iovecs, n, err)
return n, err
}
func Writev(fd int, iovs [][]byte) (n int, err error) {
iovecs := make([]Iovec, 0, minIovec)
iovecs = appendBytes(iovecs, iovs)
if raceenabled {
raceReleaseMerge(unsafe.Pointer(&ioSync))
}
n, err = writev(fd, iovecs)
writevRaceDetect(iovecs, n)
return n, err
}
func Pwritev(fd int, iovs [][]byte, offset int64) (n int, err error) {
iovecs := make([]Iovec, 0, minIovec)
iovecs = appendBytes(iovecs, iovs)
if raceenabled {
raceReleaseMerge(unsafe.Pointer(&ioSync))
}
n, err = pwritev(fd, iovecs, offset)
writevRaceDetect(iovecs, n)
return n, err
}
-89
View File
@@ -602,95 +602,6 @@ func Connectx(fd int, srcIf uint32, srcAddr, dstAddr Sockaddr, associd SaeAssocI
return return
} }
const minIovec = 8
func Readv(fd int, iovs [][]byte) (n int, err error) {
iovecs := make([]Iovec, 0, minIovec)
iovecs = appendBytes(iovecs, iovs)
n, err = readv(fd, iovecs)
readvRacedetect(iovecs, n, err)
return n, err
}
func Preadv(fd int, iovs [][]byte, offset int64) (n int, err error) {
iovecs := make([]Iovec, 0, minIovec)
iovecs = appendBytes(iovecs, iovs)
n, err = preadv(fd, iovecs, offset)
readvRacedetect(iovecs, n, err)
return n, err
}
func Writev(fd int, iovs [][]byte) (n int, err error) {
iovecs := make([]Iovec, 0, minIovec)
iovecs = appendBytes(iovecs, iovs)
if raceenabled {
raceReleaseMerge(unsafe.Pointer(&ioSync))
}
n, err = writev(fd, iovecs)
writevRacedetect(iovecs, n)
return n, err
}
func Pwritev(fd int, iovs [][]byte, offset int64) (n int, err error) {
iovecs := make([]Iovec, 0, minIovec)
iovecs = appendBytes(iovecs, iovs)
if raceenabled {
raceReleaseMerge(unsafe.Pointer(&ioSync))
}
n, err = pwritev(fd, iovecs, offset)
writevRacedetect(iovecs, n)
return n, err
}
func appendBytes(vecs []Iovec, bs [][]byte) []Iovec {
for _, b := range bs {
var v Iovec
v.SetLen(len(b))
if len(b) > 0 {
v.Base = &b[0]
} else {
v.Base = (*byte)(unsafe.Pointer(&_zero))
}
vecs = append(vecs, v)
}
return vecs
}
func writevRacedetect(iovecs []Iovec, n int) {
if !raceenabled {
return
}
for i := 0; n > 0 && i < len(iovecs); i++ {
m := int(iovecs[i].Len)
if m > n {
m = n
}
n -= m
if m > 0 {
raceReadRange(unsafe.Pointer(iovecs[i].Base), m)
}
}
}
func readvRacedetect(iovecs []Iovec, n int, err error) {
if !raceenabled {
return
}
for i := 0; n > 0 && i < len(iovecs); i++ {
m := int(iovecs[i].Len)
if m > n {
m = n
}
n -= m
if m > 0 {
raceWriteRange(unsafe.Pointer(iovecs[i].Base), m)
}
}
if err == nil {
raceAcquire(unsafe.Pointer(&ioSync))
}
}
//sys connectx(fd int, endpoints *SaEndpoints, associd SaeAssocID, flags uint32, iov []Iovec, n *uintptr, connid *SaeConnID) (err error) //sys connectx(fd int, endpoints *SaEndpoints, associd SaeAssocID, flags uint32, iov []Iovec, n *uintptr, connid *SaeConnID) (err error)
//sys sendfile(infd int, outfd int, offset int64, len *int64, hdtr unsafe.Pointer, flags int) (err error) //sys sendfile(infd int, outfd int, offset int64, len *int64, hdtr unsafe.Pointer, flags int) (err error)
+18 -96
View File
@@ -2150,33 +2150,10 @@ func Signalfd(fd int, sigmask *Sigset_t, flags int) (newfd int, err error) {
//sys exitThread(code int) (err error) = SYS_EXIT //sys exitThread(code int) (err error) = SYS_EXIT
//sys readv(fd int, iovs []Iovec) (n int, err error) = SYS_READV //sys readv(fd int, iovs []Iovec) (n int, err error) = SYS_READV
//sys writev(fd int, iovs []Iovec) (n int, err error) = SYS_WRITEV //sys writev(fd int, iovs []Iovec) (n int, err error) = SYS_WRITEV
//sys preadv(fd int, iovs []Iovec, offs_l uintptr, offs_h uintptr) (n int, err error) = SYS_PREADV //sys preadvSyscall(fd int, iovs []Iovec, offs_l uintptr, offs_h uintptr) (n int, err error) = SYS_PREADV
//sys pwritev(fd int, iovs []Iovec, offs_l uintptr, offs_h uintptr) (n int, err error) = SYS_PWRITEV //sys pwritevSyscall(fd int, iovs []Iovec, offs_l uintptr, offs_h uintptr) (n int, err error) = SYS_PWRITEV
//sys preadv2(fd int, iovs []Iovec, offs_l uintptr, offs_h uintptr, flags int) (n int, err error) = SYS_PREADV2 //sys preadv2Syscall(fd int, iovs []Iovec, offs_l uintptr, offs_h uintptr, flags int) (n int, err error) = SYS_PREADV2
//sys pwritev2(fd int, iovs []Iovec, offs_l uintptr, offs_h uintptr, flags int) (n int, err error) = SYS_PWRITEV2 //sys pwritev2Syscall(fd int, iovs []Iovec, offs_l uintptr, offs_h uintptr, flags int) (n int, err error) = SYS_PWRITEV2
// minIovec is the size of the small initial allocation used by
// Readv, Writev, etc.
//
// This small allocation gets stack allocated, which lets the
// common use case of len(iovs) <= minIovs avoid more expensive
// heap allocations.
const minIovec = 8
// appendBytes converts bs to Iovecs and appends them to vecs.
func appendBytes(vecs []Iovec, bs [][]byte) []Iovec {
for _, b := range bs {
var v Iovec
v.SetLen(len(b))
if len(b) > 0 {
v.Base = &b[0]
} else {
v.Base = (*byte)(unsafe.Pointer(&_zero))
}
vecs = append(vecs, v)
}
return vecs
}
// offs2lohi splits offs into its low and high order bits. // offs2lohi splits offs into its low and high order bits.
func offs2lohi(offs int64) (lo, hi uintptr) { func offs2lohi(offs int64) (lo, hi uintptr) {
@@ -2184,69 +2161,23 @@ func offs2lohi(offs int64) (lo, hi uintptr) {
return uintptr(offs), uintptr(uint64(offs) >> (longBits - 1) >> 1) // two shifts to avoid false positive in vet return uintptr(offs), uintptr(uint64(offs) >> (longBits - 1) >> 1) // two shifts to avoid false positive in vet
} }
func Readv(fd int, iovs [][]byte) (n int, err error) { func preadv(fd int, iovecs []Iovec, offset int64) (n int, err error) {
iovecs := make([]Iovec, 0, minIovec)
iovecs = appendBytes(iovecs, iovs)
n, err = readv(fd, iovecs)
readvRacedetect(iovecs, n, err)
return n, err
}
func Preadv(fd int, iovs [][]byte, offset int64) (n int, err error) {
iovecs := make([]Iovec, 0, minIovec)
iovecs = appendBytes(iovecs, iovs)
lo, hi := offs2lohi(offset) lo, hi := offs2lohi(offset)
n, err = preadv(fd, iovecs, lo, hi) return preadvSyscall(fd, iovecs, lo, hi)
readvRacedetect(iovecs, n, err)
return n, err
} }
func Preadv2(fd int, iovs [][]byte, offset int64, flags int) (n int, err error) { func Preadv2(fd int, iovs [][]byte, offset int64, flags int) (n int, err error) {
iovecs := make([]Iovec, 0, minIovec) iovecs := make([]Iovec, 0, minIovec)
iovecs = appendBytes(iovecs, iovs) iovecs = appendBytes(iovecs, iovs)
lo, hi := offs2lohi(offset) lo, hi := offs2lohi(offset)
n, err = preadv2(fd, iovecs, lo, hi, flags) n, err = preadv2Syscall(fd, iovecs, lo, hi, flags)
readvRacedetect(iovecs, n, err) readvRaceDetect(iovecs, n, err)
return n, err return n, err
} }
func readvRacedetect(iovecs []Iovec, n int, err error) { func pwritev(fd int, iovecs []Iovec, offset int64) (n int, err error) {
if !raceenabled {
return
}
for i := 0; n > 0 && i < len(iovecs); i++ {
m := min(int(iovecs[i].Len), n)
n -= m
if m > 0 {
raceWriteRange(unsafe.Pointer(iovecs[i].Base), m)
}
}
if err == nil {
raceAcquire(unsafe.Pointer(&ioSync))
}
}
func Writev(fd int, iovs [][]byte) (n int, err error) {
iovecs := make([]Iovec, 0, minIovec)
iovecs = appendBytes(iovecs, iovs)
if raceenabled {
raceReleaseMerge(unsafe.Pointer(&ioSync))
}
n, err = writev(fd, iovecs)
writevRacedetect(iovecs, n)
return n, err
}
func Pwritev(fd int, iovs [][]byte, offset int64) (n int, err error) {
iovecs := make([]Iovec, 0, minIovec)
iovecs = appendBytes(iovecs, iovs)
if raceenabled {
raceReleaseMerge(unsafe.Pointer(&ioSync))
}
lo, hi := offs2lohi(offset) lo, hi := offs2lohi(offset)
n, err = pwritev(fd, iovecs, lo, hi) return pwritevSyscall(fd, iovecs, lo, hi)
writevRacedetect(iovecs, n)
return n, err
} }
func Pwritev2(fd int, iovs [][]byte, offset int64, flags int) (n int, err error) { func Pwritev2(fd int, iovs [][]byte, offset int64, flags int) (n int, err error) {
@@ -2256,24 +2187,11 @@ func Pwritev2(fd int, iovs [][]byte, offset int64, flags int) (n int, err error)
raceReleaseMerge(unsafe.Pointer(&ioSync)) raceReleaseMerge(unsafe.Pointer(&ioSync))
} }
lo, hi := offs2lohi(offset) lo, hi := offs2lohi(offset)
n, err = pwritev2(fd, iovecs, lo, hi, flags) n, err = pwritev2Syscall(fd, iovecs, lo, hi, flags)
writevRacedetect(iovecs, n) writevRaceDetect(iovecs, n)
return n, err return n, err
} }
func writevRacedetect(iovecs []Iovec, n int) {
if !raceenabled {
return
}
for i := 0; n > 0 && i < len(iovecs); i++ {
m := min(int(iovecs[i].Len), n)
n -= m
if m > 0 {
raceReadRange(unsafe.Pointer(iovecs[i].Base), m)
}
}
}
// mmap varies by architecture; see syscall_linux_*.go. // mmap varies by architecture; see syscall_linux_*.go.
//sys munmap(addr uintptr, length uintptr) (err error) //sys munmap(addr uintptr, length uintptr) (err error)
//sys mremap(oldaddr uintptr, oldlength uintptr, newlength uintptr, flags int, newaddr uintptr) (xaddr uintptr, err error) //sys mremap(oldaddr uintptr, oldlength uintptr, newlength uintptr, flags int, newaddr uintptr) (xaddr uintptr, err error)
@@ -2644,8 +2562,12 @@ func SchedGetAttr(pid int, flags uint) (*SchedAttr, error) {
//sys Cachestat(fd uint, crange *CachestatRange, cstat *Cachestat_t, flags uint) (err error) //sys Cachestat(fd uint, crange *CachestatRange, cstat *Cachestat_t, flags uint) (err error)
//sys Mseal(b []byte, flags uint) (err error) //sys Mseal(b []byte, flags uint) (err error)
//sys setMemPolicy(mode int, mask *CPUSet, size int) (err error) = SYS_SET_MEMPOLICY //sys setMemPolicy(mode int, mask unsafe.Pointer, size uintptr) (err error) = SYS_SET_MEMPOLICY
func SetMemPolicy(mode int, mask *CPUSet) error { func SetMemPolicy(mode int, mask *CPUSet) error {
return setMemPolicy(mode, mask, _CPU_SETSIZE) return setMemPolicy(mode, unsafe.Pointer(mask), _CPU_SETSIZE)
}
func SetMemPolicyDynamic(mode int, mask CPUSetDynamic) error {
return setMemPolicy(mode, mask.pointer(), mask.size())
} }
+3
View File
@@ -82,6 +82,9 @@ func Time(t *Time_t) (Time_t, error) {
} }
func Utime(path string, buf *Utimbuf) error { func Utime(path string, buf *Utimbuf) error {
if buf == nil {
return Utimes(path, nil)
}
tv := []Timeval{ tv := []Timeval{
{Sec: buf.Actime}, {Sec: buf.Actime},
{Sec: buf.Modtime}, {Sec: buf.Modtime},
+3
View File
@@ -113,6 +113,9 @@ func Time(t *Time_t) (Time_t, error) {
} }
func Utime(path string, buf *Utimbuf) error { func Utime(path string, buf *Utimbuf) error {
if buf == nil {
return Utimes(path, nil)
}
tv := []Timeval{ tv := []Timeval{
{Sec: buf.Actime}, {Sec: buf.Actime},
{Sec: buf.Modtime}, {Sec: buf.Modtime},
+3
View File
@@ -150,6 +150,9 @@ func Time(t *Time_t) (Time_t, error) {
} }
func Utime(path string, buf *Utimbuf) error { func Utime(path string, buf *Utimbuf) error {
if buf == nil {
return Utimes(path, nil)
}
tv := []Timeval{ tv := []Timeval{
{Sec: buf.Actime}, {Sec: buf.Actime},
{Sec: buf.Modtime}, {Sec: buf.Modtime},
+3
View File
@@ -112,6 +112,9 @@ func Time(t *Time_t) (Time_t, error) {
} }
func Utime(path string, buf *Utimbuf) error { func Utime(path string, buf *Utimbuf) error {
if buf == nil {
return Utimes(path, nil)
}
tv := []Timeval{ tv := []Timeval{
{Sec: buf.Actime}, {Sec: buf.Actime},
{Sec: buf.Modtime}, {Sec: buf.Modtime},
+4
View File
@@ -300,6 +300,10 @@ func Uname(uname *Utsname) error {
//sys Pathconf(path string, name int) (val int, err error) //sys Pathconf(path string, name int) (val int, err error)
//sys pread(fd int, p []byte, offset int64) (n int, err error) //sys pread(fd int, p []byte, offset int64) (n int, err error)
//sys pwrite(fd int, p []byte, offset int64) (n int, err error) //sys pwrite(fd int, p []byte, offset int64) (n int, err error)
//sys readv(fd int, iovecs []Iovec) (n int, err error)
//sys writev(fd int, iovecs []Iovec) (n int, err error)
//sys preadv(fd int, iovecs []Iovec, offset int64) (n int, err error)
//sys pwritev(fd int, iovecs []Iovec, offset int64) (n int, err error)
//sys read(fd int, p []byte) (n int, err error) //sys read(fd int, p []byte) (n int, err error)
//sys Readlink(path string, buf []byte) (n int, err error) //sys Readlink(path string, buf []byte) (n int, err error)
//sys Readlinkat(dirfd int, path string, buf []byte) (n int, err error) //sys Readlinkat(dirfd int, path string, buf []byte) (n int, err error)
+59 -2
View File
@@ -353,8 +353,10 @@ const (
AUDIT_MAC_IPSEC_EVENT = 0x587 AUDIT_MAC_IPSEC_EVENT = 0x587
AUDIT_MAC_MAP_ADD = 0x581 AUDIT_MAC_MAP_ADD = 0x581
AUDIT_MAC_MAP_DEL = 0x582 AUDIT_MAC_MAP_DEL = 0x582
AUDIT_MAC_OBJ_CONTEXTS = 0x592
AUDIT_MAC_POLICY_LOAD = 0x57b AUDIT_MAC_POLICY_LOAD = 0x57b
AUDIT_MAC_STATUS = 0x57c AUDIT_MAC_STATUS = 0x57c
AUDIT_MAC_TASK_CONTEXTS = 0x591
AUDIT_MAC_UNLBL_ALLOW = 0x57e AUDIT_MAC_UNLBL_ALLOW = 0x57e
AUDIT_MAC_UNLBL_STCADD = 0x588 AUDIT_MAC_UNLBL_STCADD = 0x588
AUDIT_MAC_UNLBL_STCDEL = 0x589 AUDIT_MAC_UNLBL_STCDEL = 0x589
@@ -591,8 +593,13 @@ const (
CAN_CTRLMODE_LOOPBACK = 0x1 CAN_CTRLMODE_LOOPBACK = 0x1
CAN_CTRLMODE_ONE_SHOT = 0x8 CAN_CTRLMODE_ONE_SHOT = 0x8
CAN_CTRLMODE_PRESUME_ACK = 0x40 CAN_CTRLMODE_PRESUME_ACK = 0x40
CAN_CTRLMODE_RESTRICTED = 0x800
CAN_CTRLMODE_TDC_AUTO = 0x200 CAN_CTRLMODE_TDC_AUTO = 0x200
CAN_CTRLMODE_TDC_MANUAL = 0x400 CAN_CTRLMODE_TDC_MANUAL = 0x400
CAN_CTRLMODE_XL = 0x1000
CAN_CTRLMODE_XL_TDC_AUTO = 0x2000
CAN_CTRLMODE_XL_TDC_MANUAL = 0x4000
CAN_CTRLMODE_XL_TMS = 0x8000
CAN_EFF_FLAG = 0x80000000 CAN_EFF_FLAG = 0x80000000
CAN_EFF_ID_BITS = 0x1d CAN_EFF_ID_BITS = 0x1d
CAN_EFF_MASK = 0x1fffffff CAN_EFF_MASK = 0x1fffffff
@@ -800,6 +807,8 @@ const (
DEVLINK_PORT_FN_CAP_IPSEC_PACKET = 0x8 DEVLINK_PORT_FN_CAP_IPSEC_PACKET = 0x8
DEVLINK_PORT_FN_CAP_MIGRATABLE = 0x2 DEVLINK_PORT_FN_CAP_MIGRATABLE = 0x2
DEVLINK_PORT_FN_CAP_ROCE = 0x1 DEVLINK_PORT_FN_CAP_ROCE = 0x1
DEVLINK_RATE_TCS_MAX = 0x8
DEVLINK_RATE_TC_INDEX_MAX = 0x7
DEVLINK_SB_THRESHOLD_TO_ALPHA_MAX = 0x14 DEVLINK_SB_THRESHOLD_TO_ALPHA_MAX = 0x14
DEVLINK_SUPPORTED_FLASH_OVERWRITE_SECTIONS = 0x3 DEVLINK_SUPPORTED_FLASH_OVERWRITE_SECTIONS = 0x3
DEVMEM_MAGIC = 0x454d444d DEVMEM_MAGIC = 0x454d444d
@@ -1186,6 +1195,7 @@ const (
ETH_P_MPLS_UC = 0x8847 ETH_P_MPLS_UC = 0x8847
ETH_P_MRP = 0x88e3 ETH_P_MRP = 0x88e3
ETH_P_MVRP = 0x88f5 ETH_P_MVRP = 0x88f5
ETH_P_MXLGSW = 0x88c3
ETH_P_NCSI = 0x88f8 ETH_P_NCSI = 0x88f8
ETH_P_NSH = 0x894f ETH_P_NSH = 0x894f
ETH_P_PAE = 0x888e ETH_P_PAE = 0x888e
@@ -1218,6 +1228,7 @@ const (
ETH_P_WCCP = 0x883e ETH_P_WCCP = 0x883e
ETH_P_X25 = 0x805 ETH_P_X25 = 0x805
ETH_P_XDSA = 0xf8 ETH_P_XDSA = 0xf8
ETH_P_YT921X = 0x9988
ET_CORE = 0x4 ET_CORE = 0x4
ET_DYN = 0x3 ET_DYN = 0x3
ET_EXEC = 0x2 ET_EXEC = 0x2
@@ -1258,6 +1269,7 @@ const (
FALLOC_FL_NO_HIDE_STALE = 0x4 FALLOC_FL_NO_HIDE_STALE = 0x4
FALLOC_FL_PUNCH_HOLE = 0x2 FALLOC_FL_PUNCH_HOLE = 0x2
FALLOC_FL_UNSHARE_RANGE = 0x40 FALLOC_FL_UNSHARE_RANGE = 0x40
FALLOC_FL_WRITE_ZEROES = 0x80
FALLOC_FL_ZERO_RANGE = 0x10 FALLOC_FL_ZERO_RANGE = 0x10
FANOTIFY_METADATA_VERSION = 0x3 FANOTIFY_METADATA_VERSION = 0x3
FAN_ACCESS = 0x1 FAN_ACCESS = 0x1
@@ -1477,6 +1489,7 @@ const (
GRND_INSECURE = 0x4 GRND_INSECURE = 0x4
GRND_NONBLOCK = 0x1 GRND_NONBLOCK = 0x1
GRND_RANDOM = 0x2 GRND_RANDOM = 0x2
GUEST_MEMFD_MAGIC = 0x474d454d
HDIO_DRIVE_CMD = 0x31f HDIO_DRIVE_CMD = 0x31f
HDIO_DRIVE_CMD_AEB = 0x31e HDIO_DRIVE_CMD_AEB = 0x31e
HDIO_DRIVE_CMD_HDR_SIZE = 0x4 HDIO_DRIVE_CMD_HDR_SIZE = 0x4
@@ -1517,6 +1530,7 @@ const (
HDIO_SET_XFER = 0x306 HDIO_SET_XFER = 0x306
HDIO_TRISTATE_HWIF = 0x31b HDIO_TRISTATE_HWIF = 0x31b
HDIO_UNREGISTER_HWIF = 0x32a HDIO_UNREGISTER_HWIF = 0x32a
HIDIOCTL_LAST = 0xd
HID_MAX_DESCRIPTOR_SIZE = 0x1000 HID_MAX_DESCRIPTOR_SIZE = 0x1000
HOSTFS_SUPER_MAGIC = 0xc0ffee HOSTFS_SUPER_MAGIC = 0xc0ffee
HPFS_SUPER_MAGIC = 0xf995e849 HPFS_SUPER_MAGIC = 0xf995e849
@@ -1809,6 +1823,8 @@ const (
KEXEC_ARCH_X86_64 = 0x3e0000 KEXEC_ARCH_X86_64 = 0x3e0000
KEXEC_CRASH_HOTPLUG_SUPPORT = 0x8 KEXEC_CRASH_HOTPLUG_SUPPORT = 0x8
KEXEC_FILE_DEBUG = 0x8 KEXEC_FILE_DEBUG = 0x8
KEXEC_FILE_FORCE_DTB = 0x20
KEXEC_FILE_NO_CMA = 0x10
KEXEC_FILE_NO_INITRAMFS = 0x4 KEXEC_FILE_NO_INITRAMFS = 0x4
KEXEC_FILE_ON_CRASH = 0x2 KEXEC_FILE_ON_CRASH = 0x2
KEXEC_FILE_UNLOAD = 0x1 KEXEC_FILE_UNLOAD = 0x1
@@ -1905,6 +1921,7 @@ const (
LANDLOCK_RESTRICT_SELF_LOG_NEW_EXEC_ON = 0x2 LANDLOCK_RESTRICT_SELF_LOG_NEW_EXEC_ON = 0x2
LANDLOCK_RESTRICT_SELF_LOG_SAME_EXEC_OFF = 0x1 LANDLOCK_RESTRICT_SELF_LOG_SAME_EXEC_OFF = 0x1
LANDLOCK_RESTRICT_SELF_LOG_SUBDOMAINS_OFF = 0x4 LANDLOCK_RESTRICT_SELF_LOG_SUBDOMAINS_OFF = 0x4
LANDLOCK_RESTRICT_SELF_TSYNC = 0x8
LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET = 0x1 LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET = 0x1
LANDLOCK_SCOPE_SIGNAL = 0x2 LANDLOCK_SCOPE_SIGNAL = 0x2
LINUX_REBOOT_CMD_CAD_OFF = 0x0 LINUX_REBOOT_CMD_CAD_OFF = 0x0
@@ -2412,6 +2429,7 @@ const (
NN_PRXFPREG = "LINUX" NN_PRXFPREG = "LINUX"
NN_RISCV_CSR = "LINUX" NN_RISCV_CSR = "LINUX"
NN_RISCV_TAGGED_ADDR_CTRL = "LINUX" NN_RISCV_TAGGED_ADDR_CTRL = "LINUX"
NN_RISCV_USER_CFI = "LINUX"
NN_RISCV_VECTOR = "LINUX" NN_RISCV_VECTOR = "LINUX"
NN_S390_CTRS = "LINUX" NN_S390_CTRS = "LINUX"
NN_S390_GS_BC = "LINUX" NN_S390_GS_BC = "LINUX"
@@ -2493,6 +2511,7 @@ const (
NT_PRXFPREG = 0x46e62b7f NT_PRXFPREG = 0x46e62b7f
NT_RISCV_CSR = 0x900 NT_RISCV_CSR = 0x900
NT_RISCV_TAGGED_ADDR_CTRL = 0x902 NT_RISCV_TAGGED_ADDR_CTRL = 0x902
NT_RISCV_USER_CFI = 0x903
NT_RISCV_VECTOR = 0x901 NT_RISCV_VECTOR = 0x901
NT_S390_CTRS = 0x304 NT_S390_CTRS = 0x304
NT_S390_GS_BC = 0x30c NT_S390_GS_BC = 0x30c
@@ -2515,6 +2534,7 @@ const (
NT_X86_SHSTK = 0x204 NT_X86_SHSTK = 0x204
NT_X86_XSAVE_LAYOUT = 0x205 NT_X86_XSAVE_LAYOUT = 0x205
NT_X86_XSTATE = 0x202 NT_X86_XSTATE = 0x202
NULL_FS_MAGIC = 0x4e554c4c
OCFS2_SUPER_MAGIC = 0x7461636f OCFS2_SUPER_MAGIC = 0x7461636f
OCRNL = 0x8 OCRNL = 0x8
OFDEL = 0x80 OFDEL = 0x80
@@ -2594,6 +2614,7 @@ const (
PERF_ATTR_SIZE_VER6 = 0x78 PERF_ATTR_SIZE_VER6 = 0x78
PERF_ATTR_SIZE_VER7 = 0x80 PERF_ATTR_SIZE_VER7 = 0x80
PERF_ATTR_SIZE_VER8 = 0x88 PERF_ATTR_SIZE_VER8 = 0x88
PERF_ATTR_SIZE_VER9 = 0x90
PERF_AUX_FLAG_COLLISION = 0x8 PERF_AUX_FLAG_COLLISION = 0x8
PERF_AUX_FLAG_CORESIGHT_FORMAT_CORESIGHT = 0x0 PERF_AUX_FLAG_CORESIGHT_FORMAT_CORESIGHT = 0x0
PERF_AUX_FLAG_CORESIGHT_FORMAT_RAW = 0x100 PERF_AUX_FLAG_CORESIGHT_FORMAT_RAW = 0x100
@@ -2629,6 +2650,7 @@ const (
PERF_MEM_LVLNUM_ANY_CACHE = 0xb PERF_MEM_LVLNUM_ANY_CACHE = 0xb
PERF_MEM_LVLNUM_CXL = 0x9 PERF_MEM_LVLNUM_CXL = 0x9
PERF_MEM_LVLNUM_IO = 0xa PERF_MEM_LVLNUM_IO = 0xa
PERF_MEM_LVLNUM_L0 = 0x7
PERF_MEM_LVLNUM_L1 = 0x1 PERF_MEM_LVLNUM_L1 = 0x1
PERF_MEM_LVLNUM_L2 = 0x2 PERF_MEM_LVLNUM_L2 = 0x2
PERF_MEM_LVLNUM_L2_MHB = 0x5 PERF_MEM_LVLNUM_L2_MHB = 0x5
@@ -2662,6 +2684,23 @@ const (
PERF_MEM_OP_PFETCH = 0x8 PERF_MEM_OP_PFETCH = 0x8
PERF_MEM_OP_SHIFT = 0x0 PERF_MEM_OP_SHIFT = 0x0
PERF_MEM_OP_STORE = 0x4 PERF_MEM_OP_STORE = 0x4
PERF_MEM_REGION_L_NON_SHARE = 0x3
PERF_MEM_REGION_L_SHARE = 0x2
PERF_MEM_REGION_MEM0 = 0x8
PERF_MEM_REGION_MEM1 = 0x9
PERF_MEM_REGION_MEM2 = 0xa
PERF_MEM_REGION_MEM3 = 0xb
PERF_MEM_REGION_MEM4 = 0xc
PERF_MEM_REGION_MEM5 = 0xd
PERF_MEM_REGION_MEM6 = 0xe
PERF_MEM_REGION_MEM7 = 0xf
PERF_MEM_REGION_MMIO = 0x7
PERF_MEM_REGION_NA = 0x0
PERF_MEM_REGION_O_IO = 0x4
PERF_MEM_REGION_O_NON_SHARE = 0x6
PERF_MEM_REGION_O_SHARE = 0x5
PERF_MEM_REGION_RSVD = 0x1
PERF_MEM_REGION_SHIFT = 0x2e
PERF_MEM_REMOTE_REMOTE = 0x1 PERF_MEM_REMOTE_REMOTE = 0x1
PERF_MEM_REMOTE_SHIFT = 0x25 PERF_MEM_REMOTE_SHIFT = 0x25
PERF_MEM_SNOOPX_FWD = 0x1 PERF_MEM_SNOOPX_FWD = 0x1
@@ -2776,6 +2815,10 @@ const (
PR_CAP_AMBIENT_IS_SET = 0x1 PR_CAP_AMBIENT_IS_SET = 0x1
PR_CAP_AMBIENT_LOWER = 0x3 PR_CAP_AMBIENT_LOWER = 0x3
PR_CAP_AMBIENT_RAISE = 0x2 PR_CAP_AMBIENT_RAISE = 0x2
PR_CFI_BRANCH_LANDING_PADS = 0x0
PR_CFI_DISABLE = 0x2
PR_CFI_ENABLE = 0x1
PR_CFI_LOCK = 0x4
PR_ENDIAN_BIG = 0x0 PR_ENDIAN_BIG = 0x0
PR_ENDIAN_LITTLE = 0x1 PR_ENDIAN_LITTLE = 0x1
PR_ENDIAN_PPC_LITTLE = 0x2 PR_ENDIAN_PPC_LITTLE = 0x2
@@ -2798,6 +2841,7 @@ const (
PR_FUTEX_HASH_GET_SLOTS = 0x2 PR_FUTEX_HASH_GET_SLOTS = 0x2
PR_FUTEX_HASH_SET_SLOTS = 0x1 PR_FUTEX_HASH_SET_SLOTS = 0x1
PR_GET_AUXV = 0x41555856 PR_GET_AUXV = 0x41555856
PR_GET_CFI = 0x50
PR_GET_CHILD_SUBREAPER = 0x25 PR_GET_CHILD_SUBREAPER = 0x25
PR_GET_DUMPABLE = 0x3 PR_GET_DUMPABLE = 0x3
PR_GET_ENDIAN = 0x13 PR_GET_ENDIAN = 0x13
@@ -2834,6 +2878,7 @@ const (
PR_MDWE_REFUSE_EXEC_GAIN = 0x1 PR_MDWE_REFUSE_EXEC_GAIN = 0x1
PR_MPX_DISABLE_MANAGEMENT = 0x2c PR_MPX_DISABLE_MANAGEMENT = 0x2c
PR_MPX_ENABLE_MANAGEMENT = 0x2b PR_MPX_ENABLE_MANAGEMENT = 0x2b
PR_MTE_STORE_ONLY = 0x80000
PR_MTE_TAG_MASK = 0x7fff8 PR_MTE_TAG_MASK = 0x7fff8
PR_MTE_TAG_SHIFT = 0x3 PR_MTE_TAG_SHIFT = 0x3
PR_MTE_TCF_ASYNC = 0x4 PR_MTE_TCF_ASYNC = 0x4
@@ -2877,6 +2922,10 @@ const (
PR_RISCV_V_VSTATE_CTRL_NEXT_MASK = 0xc PR_RISCV_V_VSTATE_CTRL_NEXT_MASK = 0xc
PR_RISCV_V_VSTATE_CTRL_OFF = 0x1 PR_RISCV_V_VSTATE_CTRL_OFF = 0x1
PR_RISCV_V_VSTATE_CTRL_ON = 0x2 PR_RISCV_V_VSTATE_CTRL_ON = 0x2
PR_RSEQ_SLICE_EXTENSION = 0x4f
PR_RSEQ_SLICE_EXTENSION_GET = 0x1
PR_RSEQ_SLICE_EXTENSION_SET = 0x2
PR_RSEQ_SLICE_EXT_ENABLE = 0x1
PR_SCHED_CORE = 0x3e PR_SCHED_CORE = 0x3e
PR_SCHED_CORE_CREATE = 0x1 PR_SCHED_CORE_CREATE = 0x1
PR_SCHED_CORE_GET = 0x0 PR_SCHED_CORE_GET = 0x0
@@ -2886,6 +2935,7 @@ const (
PR_SCHED_CORE_SCOPE_THREAD_GROUP = 0x1 PR_SCHED_CORE_SCOPE_THREAD_GROUP = 0x1
PR_SCHED_CORE_SHARE_FROM = 0x3 PR_SCHED_CORE_SHARE_FROM = 0x3
PR_SCHED_CORE_SHARE_TO = 0x2 PR_SCHED_CORE_SHARE_TO = 0x2
PR_SET_CFI = 0x51
PR_SET_CHILD_SUBREAPER = 0x24 PR_SET_CHILD_SUBREAPER = 0x24
PR_SET_DUMPABLE = 0x4 PR_SET_DUMPABLE = 0x4
PR_SET_ENDIAN = 0x14 PR_SET_ENDIAN = 0x14
@@ -2951,11 +3001,14 @@ const (
PR_SVE_SET_VL_ONEXEC = 0x40000 PR_SVE_SET_VL_ONEXEC = 0x40000
PR_SVE_VL_INHERIT = 0x20000 PR_SVE_VL_INHERIT = 0x20000
PR_SVE_VL_LEN_MASK = 0xffff PR_SVE_VL_LEN_MASK = 0xffff
PR_SYS_DISPATCH_EXCLUSIVE_ON = 0x1
PR_SYS_DISPATCH_INCLUSIVE_ON = 0x2
PR_SYS_DISPATCH_OFF = 0x0 PR_SYS_DISPATCH_OFF = 0x0
PR_SYS_DISPATCH_ON = 0x1 PR_SYS_DISPATCH_ON = 0x1
PR_TAGGED_ADDR_ENABLE = 0x1 PR_TAGGED_ADDR_ENABLE = 0x1
PR_TASK_PERF_EVENTS_DISABLE = 0x1f PR_TASK_PERF_EVENTS_DISABLE = 0x1f
PR_TASK_PERF_EVENTS_ENABLE = 0x20 PR_TASK_PERF_EVENTS_ENABLE = 0x20
PR_THP_DISABLE_EXCEPT_ADVISED = 0x2
PR_TIMER_CREATE_RESTORE_IDS = 0x4d PR_TIMER_CREATE_RESTORE_IDS = 0x4d
PR_TIMER_CREATE_RESTORE_IDS_GET = 0x2 PR_TIMER_CREATE_RESTORE_IDS_GET = 0x2
PR_TIMER_CREATE_RESTORE_IDS_OFF = 0x0 PR_TIMER_CREATE_RESTORE_IDS_OFF = 0x0
@@ -2987,8 +3040,10 @@ const (
PTP_STRICT_FLAGS = 0x8 PTP_STRICT_FLAGS = 0x8
PTP_SYS_OFFSET_EXTENDED = 0xc4c03d09 PTP_SYS_OFFSET_EXTENDED = 0xc4c03d09
PTP_SYS_OFFSET_EXTENDED2 = 0xc4c03d12 PTP_SYS_OFFSET_EXTENDED2 = 0xc4c03d12
PTP_SYS_OFFSET_EXTENDED_CYCLES = 0xc4c03d16
PTP_SYS_OFFSET_PRECISE = 0xc0403d08 PTP_SYS_OFFSET_PRECISE = 0xc0403d08
PTP_SYS_OFFSET_PRECISE2 = 0xc0403d11 PTP_SYS_OFFSET_PRECISE2 = 0xc0403d11
PTP_SYS_OFFSET_PRECISE_CYCLES = 0xc0403d15
PTRACE_ATTACH = 0x10 PTRACE_ATTACH = 0x10
PTRACE_CONT = 0x7 PTRACE_CONT = 0x7
PTRACE_DETACH = 0x11 PTRACE_DETACH = 0x11
@@ -3330,8 +3385,9 @@ const (
RWF_DSYNC = 0x2 RWF_DSYNC = 0x2
RWF_HIPRI = 0x1 RWF_HIPRI = 0x1
RWF_NOAPPEND = 0x20 RWF_NOAPPEND = 0x20
RWF_NOSIGNAL = 0x100
RWF_NOWAIT = 0x8 RWF_NOWAIT = 0x8
RWF_SUPPORTED = 0xff RWF_SUPPORTED = 0x1ff
RWF_SYNC = 0x4 RWF_SYNC = 0x4
RWF_WRITE_LIFE_NOT_SET = 0x0 RWF_WRITE_LIFE_NOT_SET = 0x0
SCHED_BATCH = 0x3 SCHED_BATCH = 0x3
@@ -3714,7 +3770,7 @@ const (
TASKSTATS_GENL_NAME = "TASKSTATS" TASKSTATS_GENL_NAME = "TASKSTATS"
TASKSTATS_GENL_VERSION = 0x1 TASKSTATS_GENL_VERSION = 0x1
TASKSTATS_TYPE_MAX = 0x6 TASKSTATS_TYPE_MAX = 0x6
TASKSTATS_VERSION = 0x10 TASKSTATS_VERSION = 0x11
TCIFLUSH = 0x0 TCIFLUSH = 0x0
TCIOFF = 0x2 TCIOFF = 0x2
TCIOFLUSH = 0x2 TCIOFLUSH = 0x2
@@ -4052,6 +4108,7 @@ const (
XDP_FLAGS_REPLACE = 0x10 XDP_FLAGS_REPLACE = 0x10
XDP_FLAGS_SKB_MODE = 0x2 XDP_FLAGS_SKB_MODE = 0x2
XDP_FLAGS_UPDATE_IF_NOEXIST = 0x1 XDP_FLAGS_UPDATE_IF_NOEXIST = 0x1
XDP_MAX_TX_SKB_BUDGET = 0x9
XDP_MMAP_OFFSETS = 0x1 XDP_MMAP_OFFSETS = 0x1
XDP_OPTIONS = 0x8 XDP_OPTIONS = 0x8
XDP_OPTIONS_ZEROCOPY = 0x1 XDP_OPTIONS_ZEROCOPY = 0x1
+6 -1
View File
@@ -159,6 +159,7 @@ const (
NFDBITS = 0x20 NFDBITS = 0x20
NLDLY = 0x100 NLDLY = 0x100
NOFLSH = 0x80 NOFLSH = 0x80
NS_GET_ID = 0x8008b70d
NS_GET_MNTNS_ID = 0x8008b705 NS_GET_MNTNS_ID = 0x8008b705
NS_GET_NSTYPE = 0xb703 NS_GET_NSTYPE = 0xb703
NS_GET_OWNER_UID = 0xb704 NS_GET_OWNER_UID = 0xb704
@@ -305,6 +306,7 @@ const (
RTC_WKALM_SET = 0x4028700f RTC_WKALM_SET = 0x4028700f
SCM_DEVMEM_DMABUF = 0x4f SCM_DEVMEM_DMABUF = 0x4f
SCM_DEVMEM_LINEAR = 0x4e SCM_DEVMEM_LINEAR = 0x4e
SCM_INQ = 0x54
SCM_TIMESTAMPING = 0x25 SCM_TIMESTAMPING = 0x25
SCM_TIMESTAMPING_OPT_STATS = 0x36 SCM_TIMESTAMPING_OPT_STATS = 0x36
SCM_TIMESTAMPING_PKTINFO = 0x3a SCM_TIMESTAMPING_PKTINFO = 0x3a
@@ -352,6 +354,7 @@ const (
SO_ERROR = 0x4 SO_ERROR = 0x4
SO_INCOMING_CPU = 0x31 SO_INCOMING_CPU = 0x31
SO_INCOMING_NAPI_ID = 0x38 SO_INCOMING_NAPI_ID = 0x38
SO_INQ = 0x54
SO_KEEPALIVE = 0x9 SO_KEEPALIVE = 0x9
SO_LINGER = 0xd SO_LINGER = 0xd
SO_LOCK_FILTER = 0x2c SO_LOCK_FILTER = 0x2c
@@ -596,6 +599,8 @@ const (
EDESTADDRREQ = syscall.Errno(0x59) EDESTADDRREQ = syscall.Errno(0x59)
EDOTDOT = syscall.Errno(0x49) EDOTDOT = syscall.Errno(0x49)
EDQUOT = syscall.Errno(0x7a) EDQUOT = syscall.Errno(0x7a)
EFSBADCRC = syscall.Errno(0x4a)
EFSCORRUPTED = syscall.Errno(0x75)
EHOSTDOWN = syscall.Errno(0x70) EHOSTDOWN = syscall.Errno(0x70)
EHOSTUNREACH = syscall.Errno(0x71) EHOSTUNREACH = syscall.Errno(0x71)
EHWPOISON = syscall.Errno(0x85) EHWPOISON = syscall.Errno(0x85)
@@ -819,7 +824,7 @@ var errorList = [...]struct {
{114, "EALREADY", "operation already in progress"}, {114, "EALREADY", "operation already in progress"},
{115, "EINPROGRESS", "operation now in progress"}, {115, "EINPROGRESS", "operation now in progress"},
{116, "ESTALE", "stale file handle"}, {116, "ESTALE", "stale file handle"},
{117, "EUCLEAN", "structure needs cleaning"}, {117, "EFSCORRUPTED", "structure needs cleaning"},
{118, "ENOTNAM", "not a XENIX named type file"}, {118, "ENOTNAM", "not a XENIX named type file"},
{119, "ENAVAIL", "no XENIX semaphores available"}, {119, "ENAVAIL", "no XENIX semaphores available"},
{120, "EISNAM", "is a named type file"}, {120, "EISNAM", "is a named type file"},
+6 -1
View File
@@ -159,6 +159,7 @@ const (
NFDBITS = 0x40 NFDBITS = 0x40
NLDLY = 0x100 NLDLY = 0x100
NOFLSH = 0x80 NOFLSH = 0x80
NS_GET_ID = 0x8008b70d
NS_GET_MNTNS_ID = 0x8008b705 NS_GET_MNTNS_ID = 0x8008b705
NS_GET_NSTYPE = 0xb703 NS_GET_NSTYPE = 0xb703
NS_GET_OWNER_UID = 0xb704 NS_GET_OWNER_UID = 0xb704
@@ -306,6 +307,7 @@ const (
RTC_WKALM_SET = 0x4028700f RTC_WKALM_SET = 0x4028700f
SCM_DEVMEM_DMABUF = 0x4f SCM_DEVMEM_DMABUF = 0x4f
SCM_DEVMEM_LINEAR = 0x4e SCM_DEVMEM_LINEAR = 0x4e
SCM_INQ = 0x54
SCM_TIMESTAMPING = 0x25 SCM_TIMESTAMPING = 0x25
SCM_TIMESTAMPING_OPT_STATS = 0x36 SCM_TIMESTAMPING_OPT_STATS = 0x36
SCM_TIMESTAMPING_PKTINFO = 0x3a SCM_TIMESTAMPING_PKTINFO = 0x3a
@@ -353,6 +355,7 @@ const (
SO_ERROR = 0x4 SO_ERROR = 0x4
SO_INCOMING_CPU = 0x31 SO_INCOMING_CPU = 0x31
SO_INCOMING_NAPI_ID = 0x38 SO_INCOMING_NAPI_ID = 0x38
SO_INQ = 0x54
SO_KEEPALIVE = 0x9 SO_KEEPALIVE = 0x9
SO_LINGER = 0xd SO_LINGER = 0xd
SO_LOCK_FILTER = 0x2c SO_LOCK_FILTER = 0x2c
@@ -596,6 +599,8 @@ const (
EDESTADDRREQ = syscall.Errno(0x59) EDESTADDRREQ = syscall.Errno(0x59)
EDOTDOT = syscall.Errno(0x49) EDOTDOT = syscall.Errno(0x49)
EDQUOT = syscall.Errno(0x7a) EDQUOT = syscall.Errno(0x7a)
EFSBADCRC = syscall.Errno(0x4a)
EFSCORRUPTED = syscall.Errno(0x75)
EHOSTDOWN = syscall.Errno(0x70) EHOSTDOWN = syscall.Errno(0x70)
EHOSTUNREACH = syscall.Errno(0x71) EHOSTUNREACH = syscall.Errno(0x71)
EHWPOISON = syscall.Errno(0x85) EHWPOISON = syscall.Errno(0x85)
@@ -819,7 +824,7 @@ var errorList = [...]struct {
{114, "EALREADY", "operation already in progress"}, {114, "EALREADY", "operation already in progress"},
{115, "EINPROGRESS", "operation now in progress"}, {115, "EINPROGRESS", "operation now in progress"},
{116, "ESTALE", "stale file handle"}, {116, "ESTALE", "stale file handle"},
{117, "EUCLEAN", "structure needs cleaning"}, {117, "EFSCORRUPTED", "structure needs cleaning"},
{118, "ENOTNAM", "not a XENIX named type file"}, {118, "ENOTNAM", "not a XENIX named type file"},
{119, "ENAVAIL", "no XENIX semaphores available"}, {119, "ENAVAIL", "no XENIX semaphores available"},
{120, "EISNAM", "is a named type file"}, {120, "EISNAM", "is a named type file"},
+6 -1
View File
@@ -156,6 +156,7 @@ const (
NFDBITS = 0x20 NFDBITS = 0x20
NLDLY = 0x100 NLDLY = 0x100
NOFLSH = 0x80 NOFLSH = 0x80
NS_GET_ID = 0x8008b70d
NS_GET_MNTNS_ID = 0x8008b705 NS_GET_MNTNS_ID = 0x8008b705
NS_GET_NSTYPE = 0xb703 NS_GET_NSTYPE = 0xb703
NS_GET_OWNER_UID = 0xb704 NS_GET_OWNER_UID = 0xb704
@@ -311,6 +312,7 @@ const (
RTC_WKALM_SET = 0x4028700f RTC_WKALM_SET = 0x4028700f
SCM_DEVMEM_DMABUF = 0x4f SCM_DEVMEM_DMABUF = 0x4f
SCM_DEVMEM_LINEAR = 0x4e SCM_DEVMEM_LINEAR = 0x4e
SCM_INQ = 0x54
SCM_TIMESTAMPING = 0x25 SCM_TIMESTAMPING = 0x25
SCM_TIMESTAMPING_OPT_STATS = 0x36 SCM_TIMESTAMPING_OPT_STATS = 0x36
SCM_TIMESTAMPING_PKTINFO = 0x3a SCM_TIMESTAMPING_PKTINFO = 0x3a
@@ -358,6 +360,7 @@ const (
SO_ERROR = 0x4 SO_ERROR = 0x4
SO_INCOMING_CPU = 0x31 SO_INCOMING_CPU = 0x31
SO_INCOMING_NAPI_ID = 0x38 SO_INCOMING_NAPI_ID = 0x38
SO_INQ = 0x54
SO_KEEPALIVE = 0x9 SO_KEEPALIVE = 0x9
SO_LINGER = 0xd SO_LINGER = 0xd
SO_LOCK_FILTER = 0x2c SO_LOCK_FILTER = 0x2c
@@ -601,6 +604,8 @@ const (
EDESTADDRREQ = syscall.Errno(0x59) EDESTADDRREQ = syscall.Errno(0x59)
EDOTDOT = syscall.Errno(0x49) EDOTDOT = syscall.Errno(0x49)
EDQUOT = syscall.Errno(0x7a) EDQUOT = syscall.Errno(0x7a)
EFSBADCRC = syscall.Errno(0x4a)
EFSCORRUPTED = syscall.Errno(0x75)
EHOSTDOWN = syscall.Errno(0x70) EHOSTDOWN = syscall.Errno(0x70)
EHOSTUNREACH = syscall.Errno(0x71) EHOSTUNREACH = syscall.Errno(0x71)
EHWPOISON = syscall.Errno(0x85) EHWPOISON = syscall.Errno(0x85)
@@ -824,7 +829,7 @@ var errorList = [...]struct {
{114, "EALREADY", "operation already in progress"}, {114, "EALREADY", "operation already in progress"},
{115, "EINPROGRESS", "operation now in progress"}, {115, "EINPROGRESS", "operation now in progress"},
{116, "ESTALE", "stale file handle"}, {116, "ESTALE", "stale file handle"},
{117, "EUCLEAN", "structure needs cleaning"}, {117, "EFSCORRUPTED", "structure needs cleaning"},
{118, "ENOTNAM", "not a XENIX named type file"}, {118, "ENOTNAM", "not a XENIX named type file"},
{119, "ENAVAIL", "no XENIX semaphores available"}, {119, "ENAVAIL", "no XENIX semaphores available"},
{120, "EISNAM", "is a named type file"}, {120, "EISNAM", "is a named type file"},
+6 -1
View File
@@ -161,6 +161,7 @@ const (
NFDBITS = 0x40 NFDBITS = 0x40
NLDLY = 0x100 NLDLY = 0x100
NOFLSH = 0x80 NOFLSH = 0x80
NS_GET_ID = 0x8008b70d
NS_GET_MNTNS_ID = 0x8008b705 NS_GET_MNTNS_ID = 0x8008b705
NS_GET_NSTYPE = 0xb703 NS_GET_NSTYPE = 0xb703
NS_GET_OWNER_UID = 0xb704 NS_GET_OWNER_UID = 0xb704
@@ -304,6 +305,7 @@ const (
RTC_WKALM_SET = 0x4028700f RTC_WKALM_SET = 0x4028700f
SCM_DEVMEM_DMABUF = 0x4f SCM_DEVMEM_DMABUF = 0x4f
SCM_DEVMEM_LINEAR = 0x4e SCM_DEVMEM_LINEAR = 0x4e
SCM_INQ = 0x54
SCM_TIMESTAMPING = 0x25 SCM_TIMESTAMPING = 0x25
SCM_TIMESTAMPING_OPT_STATS = 0x36 SCM_TIMESTAMPING_OPT_STATS = 0x36
SCM_TIMESTAMPING_PKTINFO = 0x3a SCM_TIMESTAMPING_PKTINFO = 0x3a
@@ -351,6 +353,7 @@ const (
SO_ERROR = 0x4 SO_ERROR = 0x4
SO_INCOMING_CPU = 0x31 SO_INCOMING_CPU = 0x31
SO_INCOMING_NAPI_ID = 0x38 SO_INCOMING_NAPI_ID = 0x38
SO_INQ = 0x54
SO_KEEPALIVE = 0x9 SO_KEEPALIVE = 0x9
SO_LINGER = 0xd SO_LINGER = 0xd
SO_LOCK_FILTER = 0x2c SO_LOCK_FILTER = 0x2c
@@ -598,6 +601,8 @@ const (
EDESTADDRREQ = syscall.Errno(0x59) EDESTADDRREQ = syscall.Errno(0x59)
EDOTDOT = syscall.Errno(0x49) EDOTDOT = syscall.Errno(0x49)
EDQUOT = syscall.Errno(0x7a) EDQUOT = syscall.Errno(0x7a)
EFSBADCRC = syscall.Errno(0x4a)
EFSCORRUPTED = syscall.Errno(0x75)
EHOSTDOWN = syscall.Errno(0x70) EHOSTDOWN = syscall.Errno(0x70)
EHOSTUNREACH = syscall.Errno(0x71) EHOSTUNREACH = syscall.Errno(0x71)
EHWPOISON = syscall.Errno(0x85) EHWPOISON = syscall.Errno(0x85)
@@ -821,7 +826,7 @@ var errorList = [...]struct {
{114, "EALREADY", "operation already in progress"}, {114, "EALREADY", "operation already in progress"},
{115, "EINPROGRESS", "operation now in progress"}, {115, "EINPROGRESS", "operation now in progress"},
{116, "ESTALE", "stale file handle"}, {116, "ESTALE", "stale file handle"},
{117, "EUCLEAN", "structure needs cleaning"}, {117, "EFSCORRUPTED", "structure needs cleaning"},
{118, "ENOTNAM", "not a XENIX named type file"}, {118, "ENOTNAM", "not a XENIX named type file"},
{119, "ENAVAIL", "no XENIX semaphores available"}, {119, "ENAVAIL", "no XENIX semaphores available"},
{120, "EISNAM", "is a named type file"}, {120, "EISNAM", "is a named type file"},
+6 -1
View File
@@ -160,6 +160,7 @@ const (
NFDBITS = 0x40 NFDBITS = 0x40
NLDLY = 0x100 NLDLY = 0x100
NOFLSH = 0x80 NOFLSH = 0x80
NS_GET_ID = 0x8008b70d
NS_GET_MNTNS_ID = 0x8008b705 NS_GET_MNTNS_ID = 0x8008b705
NS_GET_NSTYPE = 0xb703 NS_GET_NSTYPE = 0xb703
NS_GET_OWNER_UID = 0xb704 NS_GET_OWNER_UID = 0xb704
@@ -298,6 +299,7 @@ const (
RTC_WKALM_SET = 0x4028700f RTC_WKALM_SET = 0x4028700f
SCM_DEVMEM_DMABUF = 0x4f SCM_DEVMEM_DMABUF = 0x4f
SCM_DEVMEM_LINEAR = 0x4e SCM_DEVMEM_LINEAR = 0x4e
SCM_INQ = 0x54
SCM_TIMESTAMPING = 0x25 SCM_TIMESTAMPING = 0x25
SCM_TIMESTAMPING_OPT_STATS = 0x36 SCM_TIMESTAMPING_OPT_STATS = 0x36
SCM_TIMESTAMPING_PKTINFO = 0x3a SCM_TIMESTAMPING_PKTINFO = 0x3a
@@ -345,6 +347,7 @@ const (
SO_ERROR = 0x4 SO_ERROR = 0x4
SO_INCOMING_CPU = 0x31 SO_INCOMING_CPU = 0x31
SO_INCOMING_NAPI_ID = 0x38 SO_INCOMING_NAPI_ID = 0x38
SO_INQ = 0x54
SO_KEEPALIVE = 0x9 SO_KEEPALIVE = 0x9
SO_LINGER = 0xd SO_LINGER = 0xd
SO_LOCK_FILTER = 0x2c SO_LOCK_FILTER = 0x2c
@@ -588,6 +591,8 @@ const (
EDESTADDRREQ = syscall.Errno(0x59) EDESTADDRREQ = syscall.Errno(0x59)
EDOTDOT = syscall.Errno(0x49) EDOTDOT = syscall.Errno(0x49)
EDQUOT = syscall.Errno(0x7a) EDQUOT = syscall.Errno(0x7a)
EFSBADCRC = syscall.Errno(0x4a)
EFSCORRUPTED = syscall.Errno(0x75)
EHOSTDOWN = syscall.Errno(0x70) EHOSTDOWN = syscall.Errno(0x70)
EHOSTUNREACH = syscall.Errno(0x71) EHOSTUNREACH = syscall.Errno(0x71)
EHWPOISON = syscall.Errno(0x85) EHWPOISON = syscall.Errno(0x85)
@@ -811,7 +816,7 @@ var errorList = [...]struct {
{114, "EALREADY", "operation already in progress"}, {114, "EALREADY", "operation already in progress"},
{115, "EINPROGRESS", "operation now in progress"}, {115, "EINPROGRESS", "operation now in progress"},
{116, "ESTALE", "stale file handle"}, {116, "ESTALE", "stale file handle"},
{117, "EUCLEAN", "structure needs cleaning"}, {117, "EFSCORRUPTED", "structure needs cleaning"},
{118, "ENOTNAM", "not a XENIX named type file"}, {118, "ENOTNAM", "not a XENIX named type file"},
{119, "ENAVAIL", "no XENIX semaphores available"}, {119, "ENAVAIL", "no XENIX semaphores available"},
{120, "EISNAM", "is a named type file"}, {120, "EISNAM", "is a named type file"},
+6 -1
View File
@@ -156,6 +156,7 @@ const (
NFDBITS = 0x20 NFDBITS = 0x20
NLDLY = 0x100 NLDLY = 0x100
NOFLSH = 0x80 NOFLSH = 0x80
NS_GET_ID = 0x4008b70d
NS_GET_MNTNS_ID = 0x4008b705 NS_GET_MNTNS_ID = 0x4008b705
NS_GET_NSTYPE = 0x2000b703 NS_GET_NSTYPE = 0x2000b703
NS_GET_OWNER_UID = 0x2000b704 NS_GET_OWNER_UID = 0x2000b704
@@ -304,6 +305,7 @@ const (
RTC_WKALM_SET = 0x8028700f RTC_WKALM_SET = 0x8028700f
SCM_DEVMEM_DMABUF = 0x4f SCM_DEVMEM_DMABUF = 0x4f
SCM_DEVMEM_LINEAR = 0x4e SCM_DEVMEM_LINEAR = 0x4e
SCM_INQ = 0x54
SCM_TIMESTAMPING = 0x25 SCM_TIMESTAMPING = 0x25
SCM_TIMESTAMPING_OPT_STATS = 0x36 SCM_TIMESTAMPING_OPT_STATS = 0x36
SCM_TIMESTAMPING_PKTINFO = 0x3a SCM_TIMESTAMPING_PKTINFO = 0x3a
@@ -351,6 +353,7 @@ const (
SO_ERROR = 0x1007 SO_ERROR = 0x1007
SO_INCOMING_CPU = 0x31 SO_INCOMING_CPU = 0x31
SO_INCOMING_NAPI_ID = 0x38 SO_INCOMING_NAPI_ID = 0x38
SO_INQ = 0x54
SO_KEEPALIVE = 0x8 SO_KEEPALIVE = 0x8
SO_LINGER = 0x80 SO_LINGER = 0x80
SO_LOCK_FILTER = 0x2c SO_LOCK_FILTER = 0x2c
@@ -597,6 +600,8 @@ const (
EDESTADDRREQ = syscall.Errno(0x60) EDESTADDRREQ = syscall.Errno(0x60)
EDOTDOT = syscall.Errno(0x49) EDOTDOT = syscall.Errno(0x49)
EDQUOT = syscall.Errno(0x46d) EDQUOT = syscall.Errno(0x46d)
EFSBADCRC = syscall.Errno(0x4d)
EFSCORRUPTED = syscall.Errno(0x87)
EHOSTDOWN = syscall.Errno(0x93) EHOSTDOWN = syscall.Errno(0x93)
EHOSTUNREACH = syscall.Errno(0x94) EHOSTUNREACH = syscall.Errno(0x94)
EHWPOISON = syscall.Errno(0xa8) EHWPOISON = syscall.Errno(0xa8)
@@ -814,7 +819,7 @@ var errorList = [...]struct {
{132, "ENOBUFS", "no buffer space available"}, {132, "ENOBUFS", "no buffer space available"},
{133, "EISCONN", "transport endpoint is already connected"}, {133, "EISCONN", "transport endpoint is already connected"},
{134, "ENOTCONN", "transport endpoint is not connected"}, {134, "ENOTCONN", "transport endpoint is not connected"},
{135, "EUCLEAN", "structure needs cleaning"}, {135, "EFSCORRUPTED", "structure needs cleaning"},
{137, "ENOTNAM", "not a XENIX named type file"}, {137, "ENOTNAM", "not a XENIX named type file"},
{138, "ENAVAIL", "no XENIX semaphores available"}, {138, "ENAVAIL", "no XENIX semaphores available"},
{139, "EISNAM", "is a named type file"}, {139, "EISNAM", "is a named type file"},
+6 -1
View File
@@ -156,6 +156,7 @@ const (
NFDBITS = 0x40 NFDBITS = 0x40
NLDLY = 0x100 NLDLY = 0x100
NOFLSH = 0x80 NOFLSH = 0x80
NS_GET_ID = 0x4008b70d
NS_GET_MNTNS_ID = 0x4008b705 NS_GET_MNTNS_ID = 0x4008b705
NS_GET_NSTYPE = 0x2000b703 NS_GET_NSTYPE = 0x2000b703
NS_GET_OWNER_UID = 0x2000b704 NS_GET_OWNER_UID = 0x2000b704
@@ -304,6 +305,7 @@ const (
RTC_WKALM_SET = 0x8028700f RTC_WKALM_SET = 0x8028700f
SCM_DEVMEM_DMABUF = 0x4f SCM_DEVMEM_DMABUF = 0x4f
SCM_DEVMEM_LINEAR = 0x4e SCM_DEVMEM_LINEAR = 0x4e
SCM_INQ = 0x54
SCM_TIMESTAMPING = 0x25 SCM_TIMESTAMPING = 0x25
SCM_TIMESTAMPING_OPT_STATS = 0x36 SCM_TIMESTAMPING_OPT_STATS = 0x36
SCM_TIMESTAMPING_PKTINFO = 0x3a SCM_TIMESTAMPING_PKTINFO = 0x3a
@@ -351,6 +353,7 @@ const (
SO_ERROR = 0x1007 SO_ERROR = 0x1007
SO_INCOMING_CPU = 0x31 SO_INCOMING_CPU = 0x31
SO_INCOMING_NAPI_ID = 0x38 SO_INCOMING_NAPI_ID = 0x38
SO_INQ = 0x54
SO_KEEPALIVE = 0x8 SO_KEEPALIVE = 0x8
SO_LINGER = 0x80 SO_LINGER = 0x80
SO_LOCK_FILTER = 0x2c SO_LOCK_FILTER = 0x2c
@@ -597,6 +600,8 @@ const (
EDESTADDRREQ = syscall.Errno(0x60) EDESTADDRREQ = syscall.Errno(0x60)
EDOTDOT = syscall.Errno(0x49) EDOTDOT = syscall.Errno(0x49)
EDQUOT = syscall.Errno(0x46d) EDQUOT = syscall.Errno(0x46d)
EFSBADCRC = syscall.Errno(0x4d)
EFSCORRUPTED = syscall.Errno(0x87)
EHOSTDOWN = syscall.Errno(0x93) EHOSTDOWN = syscall.Errno(0x93)
EHOSTUNREACH = syscall.Errno(0x94) EHOSTUNREACH = syscall.Errno(0x94)
EHWPOISON = syscall.Errno(0xa8) EHWPOISON = syscall.Errno(0xa8)
@@ -814,7 +819,7 @@ var errorList = [...]struct {
{132, "ENOBUFS", "no buffer space available"}, {132, "ENOBUFS", "no buffer space available"},
{133, "EISCONN", "transport endpoint is already connected"}, {133, "EISCONN", "transport endpoint is already connected"},
{134, "ENOTCONN", "transport endpoint is not connected"}, {134, "ENOTCONN", "transport endpoint is not connected"},
{135, "EUCLEAN", "structure needs cleaning"}, {135, "EFSCORRUPTED", "structure needs cleaning"},
{137, "ENOTNAM", "not a XENIX named type file"}, {137, "ENOTNAM", "not a XENIX named type file"},
{138, "ENAVAIL", "no XENIX semaphores available"}, {138, "ENAVAIL", "no XENIX semaphores available"},
{139, "EISNAM", "is a named type file"}, {139, "EISNAM", "is a named type file"},
+6 -1
View File
@@ -156,6 +156,7 @@ const (
NFDBITS = 0x40 NFDBITS = 0x40
NLDLY = 0x100 NLDLY = 0x100
NOFLSH = 0x80 NOFLSH = 0x80
NS_GET_ID = 0x4008b70d
NS_GET_MNTNS_ID = 0x4008b705 NS_GET_MNTNS_ID = 0x4008b705
NS_GET_NSTYPE = 0x2000b703 NS_GET_NSTYPE = 0x2000b703
NS_GET_OWNER_UID = 0x2000b704 NS_GET_OWNER_UID = 0x2000b704
@@ -304,6 +305,7 @@ const (
RTC_WKALM_SET = 0x8028700f RTC_WKALM_SET = 0x8028700f
SCM_DEVMEM_DMABUF = 0x4f SCM_DEVMEM_DMABUF = 0x4f
SCM_DEVMEM_LINEAR = 0x4e SCM_DEVMEM_LINEAR = 0x4e
SCM_INQ = 0x54
SCM_TIMESTAMPING = 0x25 SCM_TIMESTAMPING = 0x25
SCM_TIMESTAMPING_OPT_STATS = 0x36 SCM_TIMESTAMPING_OPT_STATS = 0x36
SCM_TIMESTAMPING_PKTINFO = 0x3a SCM_TIMESTAMPING_PKTINFO = 0x3a
@@ -351,6 +353,7 @@ const (
SO_ERROR = 0x1007 SO_ERROR = 0x1007
SO_INCOMING_CPU = 0x31 SO_INCOMING_CPU = 0x31
SO_INCOMING_NAPI_ID = 0x38 SO_INCOMING_NAPI_ID = 0x38
SO_INQ = 0x54
SO_KEEPALIVE = 0x8 SO_KEEPALIVE = 0x8
SO_LINGER = 0x80 SO_LINGER = 0x80
SO_LOCK_FILTER = 0x2c SO_LOCK_FILTER = 0x2c
@@ -597,6 +600,8 @@ const (
EDESTADDRREQ = syscall.Errno(0x60) EDESTADDRREQ = syscall.Errno(0x60)
EDOTDOT = syscall.Errno(0x49) EDOTDOT = syscall.Errno(0x49)
EDQUOT = syscall.Errno(0x46d) EDQUOT = syscall.Errno(0x46d)
EFSBADCRC = syscall.Errno(0x4d)
EFSCORRUPTED = syscall.Errno(0x87)
EHOSTDOWN = syscall.Errno(0x93) EHOSTDOWN = syscall.Errno(0x93)
EHOSTUNREACH = syscall.Errno(0x94) EHOSTUNREACH = syscall.Errno(0x94)
EHWPOISON = syscall.Errno(0xa8) EHWPOISON = syscall.Errno(0xa8)
@@ -814,7 +819,7 @@ var errorList = [...]struct {
{132, "ENOBUFS", "no buffer space available"}, {132, "ENOBUFS", "no buffer space available"},
{133, "EISCONN", "transport endpoint is already connected"}, {133, "EISCONN", "transport endpoint is already connected"},
{134, "ENOTCONN", "transport endpoint is not connected"}, {134, "ENOTCONN", "transport endpoint is not connected"},
{135, "EUCLEAN", "structure needs cleaning"}, {135, "EFSCORRUPTED", "structure needs cleaning"},
{137, "ENOTNAM", "not a XENIX named type file"}, {137, "ENOTNAM", "not a XENIX named type file"},
{138, "ENAVAIL", "no XENIX semaphores available"}, {138, "ENAVAIL", "no XENIX semaphores available"},
{139, "EISNAM", "is a named type file"}, {139, "EISNAM", "is a named type file"},

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