| ... | ... |
@@ -24,10 +24,7 @@ |
| 24 | 24 |
# the case. Therefore, you don't have to disable it anymore. |
| 25 | 25 |
# |
| 26 | 26 |
|
| 27 |
-FROM golang:1.10.4 AS base |
|
| 28 |
-# FIXME(vdemeester) this is kept for other script depending on it to not fail right away |
|
| 29 |
-# Remove this once the other scripts uses something else to detect the version |
|
| 30 |
-ENV GO_VERSION 1.10.4 |
|
| 27 |
+FROM golang:1.11.0 AS base |
|
| 31 | 28 |
# allow replacing httpredir or deb mirror |
| 32 | 29 |
ARG APT_MIRROR=deb.debian.org |
| 33 | 30 |
RUN sed -ri "s/(httpredir|deb).debian.org/$APT_MIRROR/g" /etc/apt/sources.list |
| ... | ... |
@@ -5,7 +5,7 @@ |
| 5 | 5 |
|
| 6 | 6 |
# This represents the bare minimum required to build and test Docker. |
| 7 | 7 |
|
| 8 |
-FROM debian:stretch |
|
| 8 |
+FROM golang:1.11.0-stretch |
|
| 9 | 9 |
|
| 10 | 10 |
# allow replacing httpredir or deb mirror |
| 11 | 11 |
ARG APT_MIRROR=deb.debian.org |
| ... | ... |
@@ -37,18 +37,6 @@ RUN apt-get update && apt-get install -y --no-install-recommends \ |
| 37 | 37 |
vim-common \ |
| 38 | 38 |
&& rm -rf /var/lib/apt/lists/* |
| 39 | 39 |
|
| 40 |
-# Install Go |
|
| 41 |
-# IMPORTANT: If the version of Go is updated, the Windows to Linux CI machines |
|
| 42 |
-# will need updating, to avoid errors. Ping #docker-maintainers on IRC |
|
| 43 |
-# with a heads-up. |
|
| 44 |
-# IMPORTANT: When updating this please note that stdlib archive/tar pkg is vendored |
|
| 45 |
-ENV GO_VERSION 1.10.4 |
|
| 46 |
-RUN curl -fsSL "https://golang.org/dl/go${GO_VERSION}.linux-amd64.tar.gz" \
|
|
| 47 |
- | tar -xzC /usr/local |
|
| 48 |
-ENV PATH /go/bin:/usr/local/go/bin:$PATH |
|
| 49 |
-ENV GOPATH /go |
|
| 50 |
-ENV CGO_LDFLAGS -L/lib |
|
| 51 |
- |
|
| 52 | 40 |
# Install runc, containerd, tini and docker-proxy |
| 53 | 41 |
# Please edit hack/dockerfile/install/<name>.installer to update them. |
| 54 | 42 |
COPY hack/dockerfile/install hack/dockerfile/install |
| ... | ... |
@@ -161,7 +161,7 @@ SHELL ["powershell", "-Command", "$ErrorActionPreference = 'Stop'; $ProgressPref |
| 161 | 161 |
# Environment variable notes: |
| 162 | 162 |
# - GO_VERSION must be consistent with 'Dockerfile' used by Linux. |
| 163 | 163 |
# - FROM_DOCKERFILE is used for detection of building within a container. |
| 164 |
-ENV GO_VERSION=1.10.4 ` |
|
| 164 |
+ENV GO_VERSION=1.11 ` |
|
| 165 | 165 |
GIT_VERSION=2.11.1 ` |
| 166 | 166 |
GOPATH=C:\go ` |
| 167 | 167 |
FROM_DOCKERFILE=1 |
| ... | ... |
@@ -29,8 +29,8 @@ func TestStrSliceMarshalJSON(t *testing.T) {
|
| 29 | 29 |
|
| 30 | 30 |
func TestStrSliceUnmarshalJSON(t *testing.T) {
|
| 31 | 31 |
parts := map[string][]string{
|
| 32 |
- "": {"default", "values"},
|
|
| 33 |
- "[]": {},
|
|
| 32 |
+ "": {"default", "values"},
|
|
| 33 |
+ "[]": {},
|
|
| 34 | 34 |
`["/bin/sh","-c","echo"]`: {"/bin/sh", "-c", "echo"},
|
| 35 | 35 |
} |
| 36 | 36 |
for json, expectedParts := range parts {
|
| ... | ... |
@@ -788,7 +788,7 @@ func NewDaemon(ctx context.Context, config *config.Config, pluginStore *plugin.S |
| 788 | 788 |
|
| 789 | 789 |
for operatingSystem, gd := range d.graphDrivers {
|
| 790 | 790 |
layerStores[operatingSystem], err = layer.NewStoreFromOptions(layer.StoreOptions{
|
| 791 |
- Root: config.Root, |
|
| 791 |
+ Root: config.Root, |
|
| 792 | 792 |
MetadataStorePathTemplate: filepath.Join(config.Root, "image", "%s", "layerdb"), |
| 793 | 793 |
GraphDriver: gd, |
| 794 | 794 |
GraphDriverOptions: config.GraphOptions, |
| ... | ... |
@@ -30,10 +30,10 @@ func TestValidateLogOpt(t *testing.T) {
|
| 30 | 30 |
splunkVerifyConnectionKey: "true", |
| 31 | 31 |
splunkGzipCompressionKey: "true", |
| 32 | 32 |
splunkGzipCompressionLevelKey: "1", |
| 33 |
- envKey: "a", |
|
| 34 |
- envRegexKey: "^foo", |
|
| 35 |
- labelsKey: "b", |
|
| 36 |
- tagKey: "c", |
|
| 33 |
+ envKey: "a", |
|
| 34 |
+ envRegexKey: "^foo", |
|
| 35 |
+ labelsKey: "b", |
|
| 36 |
+ tagKey: "c", |
|
| 37 | 37 |
}) |
| 38 | 38 |
if err != nil {
|
| 39 | 39 |
t.Fatal(err) |
| ... | ... |
@@ -251,9 +251,9 @@ func TestInlineFormatWithNonDefaultOptions(t *testing.T) {
|
| 251 | 251 |
splunkIndexKey: "myindex", |
| 252 | 252 |
splunkFormatKey: splunkFormatInline, |
| 253 | 253 |
splunkGzipCompressionKey: "true", |
| 254 |
- tagKey: "{{.ImageName}}/{{.Name}}",
|
|
| 255 |
- labelsKey: "a", |
|
| 256 |
- envRegexKey: "^foo", |
|
| 254 |
+ tagKey: "{{.ImageName}}/{{.Name}}",
|
|
| 255 |
+ labelsKey: "a", |
|
| 256 |
+ envRegexKey: "^foo", |
|
| 257 | 257 |
}, |
| 258 | 258 |
ContainerID: "containeriid", |
| 259 | 259 |
ContainerName: "/container_name", |
| ... | ... |
@@ -130,7 +130,7 @@ Function Check-InContainer() {
|
| 130 | 130 |
# outside of a container where it may be out of date with master. |
| 131 | 131 |
Function Verify-GoVersion() {
|
| 132 | 132 |
Try {
|
| 133 |
- $goVersionDockerfile=(Select-String -Path ".\Dockerfile" -Pattern "^FROM golang:").ToString().Split(" ")[1].SubString(7)
|
|
| 133 |
+ $goVersionDockerfile=(Select-String -Path ".\Dockerfile" -Pattern "^FROM golang:").ToString().Split(" ")[1].SubString(7) -replace '\.0$',''
|
|
| 134 | 134 |
$goVersionInstalled=(go version).ToString().Split(" ")[2].SubString(2)
|
| 135 | 135 |
} |
| 136 | 136 |
Catch [Exception] {
|
| ... | ... |
@@ -9,11 +9,7 @@ validate_vendor_diff(){
|
| 9 | 9 |
unset IFS |
| 10 | 10 |
|
| 11 | 11 |
if [ ${#files[@]} -gt 0 ]; then
|
| 12 |
- # Remove vendor/ first so that anything not included in vendor.conf will |
|
| 13 |
- # cause the validation to fail. archive/tar is a special case, see vendor.conf |
|
| 14 |
- # for details. |
|
| 15 |
- ls -d vendor/* | grep -v vendor/archive | xargs rm -rf |
|
| 16 |
- # run vndr to recreate vendor/ |
|
| 12 |
+ # recreate vendor/ |
|
| 17 | 13 |
vndr |
| 18 | 14 |
# check if any files have changed |
| 19 | 15 |
diffs="$(git status --porcelain -- vendor 2>/dev/null)" |
| ... | ... |
@@ -656,7 +656,7 @@ func (s *DockerSuite) TestBuildCopyWildcard(c *check.C) {
|
| 656 | 656 |
"file2.txt": "test2", |
| 657 | 657 |
"dir/nested_file": "nested file", |
| 658 | 658 |
"dir/nested_dir/nest_nest_file": "2 times nested", |
| 659 |
- "dirt": "dirty", |
|
| 659 |
+ "dirt": "dirty", |
|
| 660 | 660 |
})) |
| 661 | 661 |
defer ctx.Close() |
| 662 | 662 |
|
| ... | ... |
@@ -37,8 +37,8 @@ func TestBuildWithRemoveAndForceRemove(t *testing.T) {
|
| 37 | 37 |
RUN exit 0 |
| 38 | 38 |
RUN exit 0`, |
| 39 | 39 |
numberOfIntermediateContainers: 2, |
| 40 |
- rm: false, |
|
| 41 |
- forceRm: false, |
|
| 40 |
+ rm: false, |
|
| 41 |
+ forceRm: false, |
|
| 42 | 42 |
}, |
| 43 | 43 |
{
|
| 44 | 44 |
name: "successful build with remove", |
| ... | ... |
@@ -46,8 +46,8 @@ func TestBuildWithRemoveAndForceRemove(t *testing.T) {
|
| 46 | 46 |
RUN exit 0 |
| 47 | 47 |
RUN exit 0`, |
| 48 | 48 |
numberOfIntermediateContainers: 0, |
| 49 |
- rm: true, |
|
| 50 |
- forceRm: false, |
|
| 49 |
+ rm: true, |
|
| 50 |
+ forceRm: false, |
|
| 51 | 51 |
}, |
| 52 | 52 |
{
|
| 53 | 53 |
name: "successful build with remove and force remove", |
| ... | ... |
@@ -55,8 +55,8 @@ func TestBuildWithRemoveAndForceRemove(t *testing.T) {
|
| 55 | 55 |
RUN exit 0 |
| 56 | 56 |
RUN exit 0`, |
| 57 | 57 |
numberOfIntermediateContainers: 0, |
| 58 |
- rm: true, |
|
| 59 |
- forceRm: true, |
|
| 58 |
+ rm: true, |
|
| 59 |
+ forceRm: true, |
|
| 60 | 60 |
}, |
| 61 | 61 |
{
|
| 62 | 62 |
name: "failed build with no removal", |
| ... | ... |
@@ -64,8 +64,8 @@ func TestBuildWithRemoveAndForceRemove(t *testing.T) {
|
| 64 | 64 |
RUN exit 0 |
| 65 | 65 |
RUN exit 1`, |
| 66 | 66 |
numberOfIntermediateContainers: 2, |
| 67 |
- rm: false, |
|
| 68 |
- forceRm: false, |
|
| 67 |
+ rm: false, |
|
| 68 |
+ forceRm: false, |
|
| 69 | 69 |
}, |
| 70 | 70 |
{
|
| 71 | 71 |
name: "failed build with remove", |
| ... | ... |
@@ -73,8 +73,8 @@ func TestBuildWithRemoveAndForceRemove(t *testing.T) {
|
| 73 | 73 |
RUN exit 0 |
| 74 | 74 |
RUN exit 1`, |
| 75 | 75 |
numberOfIntermediateContainers: 1, |
| 76 |
- rm: true, |
|
| 77 |
- forceRm: false, |
|
| 76 |
+ rm: true, |
|
| 77 |
+ forceRm: false, |
|
| 78 | 78 |
}, |
| 79 | 79 |
{
|
| 80 | 80 |
name: "failed build with remove and force remove", |
| ... | ... |
@@ -82,8 +82,8 @@ func TestBuildWithRemoveAndForceRemove(t *testing.T) {
|
| 82 | 82 |
RUN exit 0 |
| 83 | 83 |
RUN exit 1`, |
| 84 | 84 |
numberOfIntermediateContainers: 0, |
| 85 |
- rm: true, |
|
| 86 |
- forceRm: true, |
|
| 85 |
+ rm: true, |
|
| 86 |
+ forceRm: true, |
|
| 87 | 87 |
}, |
| 88 | 88 |
} |
| 89 | 89 |
|
| ... | ... |
@@ -168,9 +168,9 @@ func (c *client) Create(_ context.Context, id string, spec *specs.Spec, runtimeO |
| 168 | 168 |
func (c *client) createWindows(id string, spec *specs.Spec, runtimeOptions interface{}) error {
|
| 169 | 169 |
logger := c.logger.WithField("container", id)
|
| 170 | 170 |
configuration := &hcsshim.ContainerConfig{
|
| 171 |
- SystemType: "Container", |
|
| 172 |
- Name: id, |
|
| 173 |
- Owner: defaultOwner, |
|
| 171 |
+ SystemType: "Container", |
|
| 172 |
+ Name: id, |
|
| 173 |
+ Owner: defaultOwner, |
|
| 174 | 174 |
IgnoreFlushesDuringBoot: spec.Windows.IgnoreFlushesDuringBoot, |
| 175 | 175 |
HostName: spec.Hostname, |
| 176 | 176 |
HvPartition: false, |
| ... | ... |
@@ -377,11 +377,11 @@ func (c *client) createLinux(id string, spec *specs.Spec, runtimeOptions interfa |
| 377 | 377 |
} |
| 378 | 378 |
|
| 379 | 379 |
configuration := &hcsshim.ContainerConfig{
|
| 380 |
- HvPartition: true, |
|
| 381 |
- Name: id, |
|
| 382 |
- SystemType: "container", |
|
| 383 |
- ContainerType: "linux", |
|
| 384 |
- Owner: defaultOwner, |
|
| 380 |
+ HvPartition: true, |
|
| 381 |
+ Name: id, |
|
| 382 |
+ SystemType: "container", |
|
| 383 |
+ ContainerType: "linux", |
|
| 384 |
+ Owner: defaultOwner, |
|
| 385 | 385 |
TerminateOnLastHandleClosed: true, |
| 386 | 386 |
} |
| 387 | 387 |
|
| ... | ... |
@@ -40,9 +40,9 @@ func TestMigrateRefs(t *testing.T) {
|
| 40 | 40 |
} |
| 41 | 41 |
|
| 42 | 42 |
expected := map[string]string{
|
| 43 |
- "docker.io/library/busybox:latest": "sha256:fcde2b2edba56bf408601fb721fe9b5c338d10ee429ea04fae5511b68fbf8fb9", |
|
| 43 |
+ "docker.io/library/busybox:latest": "sha256:fcde2b2edba56bf408601fb721fe9b5c338d10ee429ea04fae5511b68fbf8fb9", |
|
| 44 | 44 |
"docker.io/library/busybox@sha256:16a2a52884c2a9481ed267c2d46483eac7693b813a63132368ab098a71303f8a": "sha256:fcde2b2edba56bf408601fb721fe9b5c338d10ee429ea04fae5511b68fbf8fb9", |
| 45 |
- "docker.io/library/registry:2": "sha256:2c26b46b68ffc68ff99b453c1d30413413422d706483bfa0f98a5e886266e7ae", |
|
| 45 |
+ "docker.io/library/registry:2": "sha256:2c26b46b68ffc68ff99b453c1d30413413422d706483bfa0f98a5e886266e7ae", |
|
| 46 | 46 |
} |
| 47 | 47 |
|
| 48 | 48 |
if !reflect.DeepEqual(expected, ta.refs) {
|
| ... | ... |
@@ -69,18 +69,18 @@ func TestParseDockerDaemonHost(t *testing.T) {
|
| 69 | 69 |
"[::1]:5555/path": "tcp://[::1]:5555/path", |
| 70 | 70 |
"[0:0:0:0:0:0:0:1]:": "tcp://[0:0:0:0:0:0:0:1]:2375", |
| 71 | 71 |
"[0:0:0:0:0:0:0:1]:5555/path": "tcp://[0:0:0:0:0:0:0:1]:5555/path", |
| 72 |
- ":6666": fmt.Sprintf("tcp://%s:6666", DefaultHTTPHost),
|
|
| 73 |
- ":6666/path": fmt.Sprintf("tcp://%s:6666/path", DefaultHTTPHost),
|
|
| 74 |
- "tcp://": DefaultTCPHost, |
|
| 75 |
- "tcp://:7777": fmt.Sprintf("tcp://%s:7777", DefaultHTTPHost),
|
|
| 76 |
- "tcp://:7777/path": fmt.Sprintf("tcp://%s:7777/path", DefaultHTTPHost),
|
|
| 77 |
- "unix:///run/docker.sock": "unix:///run/docker.sock", |
|
| 78 |
- "unix://": "unix://" + DefaultUnixSocket, |
|
| 79 |
- "fd://": "fd://", |
|
| 80 |
- "fd://something": "fd://something", |
|
| 81 |
- "localhost:": "tcp://localhost:2375", |
|
| 82 |
- "localhost:5555": "tcp://localhost:5555", |
|
| 83 |
- "localhost:5555/path": "tcp://localhost:5555/path", |
|
| 72 |
+ ":6666": fmt.Sprintf("tcp://%s:6666", DefaultHTTPHost),
|
|
| 73 |
+ ":6666/path": fmt.Sprintf("tcp://%s:6666/path", DefaultHTTPHost),
|
|
| 74 |
+ "tcp://": DefaultTCPHost, |
|
| 75 |
+ "tcp://:7777": fmt.Sprintf("tcp://%s:7777", DefaultHTTPHost),
|
|
| 76 |
+ "tcp://:7777/path": fmt.Sprintf("tcp://%s:7777/path", DefaultHTTPHost),
|
|
| 77 |
+ "unix:///run/docker.sock": "unix:///run/docker.sock", |
|
| 78 |
+ "unix://": "unix://" + DefaultUnixSocket, |
|
| 79 |
+ "fd://": "fd://", |
|
| 80 |
+ "fd://something": "fd://something", |
|
| 81 |
+ "localhost:": "tcp://localhost:2375", |
|
| 82 |
+ "localhost:5555": "tcp://localhost:5555", |
|
| 83 |
+ "localhost:5555/path": "tcp://localhost:5555/path", |
|
| 84 | 84 |
} |
| 85 | 85 |
for invalidAddr, expectedError := range invalids {
|
| 86 | 86 |
if addr, err := parseDaemonHost(invalidAddr); err == nil || err.Error() != expectedError {
|
| ... | ... |
@@ -144,7 +144,7 @@ func TestDrainBody(t *testing.T) {
|
| 144 | 144 |
length int // length is the message length send to drainBody |
| 145 | 145 |
expectedBodyLength int // expectedBodyLength is the expected body length after drainBody is called |
| 146 | 146 |
}{
|
| 147 |
- {10, 10}, // Small message size
|
|
| 147 |
+ {10, 10}, // Small message size
|
|
| 148 | 148 |
{maxBodySize - 1, maxBodySize - 1}, // Max message size
|
| 149 | 149 |
{maxBodySize * 2, 0}, // Large message size (skip copying body)
|
| 150 | 150 |
|
| ... | ... |
@@ -16,11 +16,11 @@ import ( |
| 16 | 16 |
|
| 17 | 17 |
var ( |
| 18 | 18 |
saveLoadTestCases = map[string]digest.Digest{
|
| 19 |
- "registry:5000/foobar:HEAD": "sha256:470022b8af682154f57a2163d030eb369549549cba00edc69e1b99b46bb924d6", |
|
| 20 |
- "registry:5000/foobar:alternate": "sha256:ae300ebc4a4f00693702cfb0a5e0b7bc527b353828dc86ad09fb95c8a681b793", |
|
| 21 |
- "registry:5000/foobar:latest": "sha256:6153498b9ac00968d71b66cca4eac37e990b5f9eb50c26877eb8799c8847451b", |
|
| 22 |
- "registry:5000/foobar:master": "sha256:6c9917af4c4e05001b346421959d7ea81b6dc9d25718466a37a6add865dfd7fc", |
|
| 23 |
- "jess/hollywood:latest": "sha256:ae7a5519a0a55a2d4ef20ddcbd5d0ca0888a1f7ab806acc8e2a27baf46f529fe", |
|
| 19 |
+ "registry:5000/foobar:HEAD": "sha256:470022b8af682154f57a2163d030eb369549549cba00edc69e1b99b46bb924d6", |
|
| 20 |
+ "registry:5000/foobar:alternate": "sha256:ae300ebc4a4f00693702cfb0a5e0b7bc527b353828dc86ad09fb95c8a681b793", |
|
| 21 |
+ "registry:5000/foobar:latest": "sha256:6153498b9ac00968d71b66cca4eac37e990b5f9eb50c26877eb8799c8847451b", |
|
| 22 |
+ "registry:5000/foobar:master": "sha256:6c9917af4c4e05001b346421959d7ea81b6dc9d25718466a37a6add865dfd7fc", |
|
| 23 |
+ "jess/hollywood:latest": "sha256:ae7a5519a0a55a2d4ef20ddcbd5d0ca0888a1f7ab806acc8e2a27baf46f529fe", |
|
| 24 | 24 |
"registry@sha256:367eb40fd0330a7e464777121e39d2f5b3e8e23a1e159342e53ab05c9e4d94e6": "sha256:24126a56805beb9711be5f4590cc2eb55ab8d4a85ebd618eed72bb19fc50631c", |
| 25 | 25 |
"busybox:latest": "sha256:91e54dfb11794fad694460162bf0cb0a4fa710cfa3f60979c177d920813e267c", |
| 26 | 26 |
} |
| ... | ... |
@@ -57,7 +57,7 @@ func (s *DefaultService) lookupV2Endpoints(hostname string) (endpoints []APIEndp |
| 57 | 57 |
Scheme: "https", |
| 58 | 58 |
Host: hostname, |
| 59 | 59 |
}, |
| 60 |
- Version: APIVersion2, |
|
| 60 |
+ Version: APIVersion2, |
|
| 61 | 61 |
AllowNondistributableArtifacts: ana, |
| 62 | 62 |
TrimHostname: true, |
| 63 | 63 |
TLSConfig: tlsConfig, |
| ... | ... |
@@ -70,7 +70,7 @@ func (s *DefaultService) lookupV2Endpoints(hostname string) (endpoints []APIEndp |
| 70 | 70 |
Scheme: "http", |
| 71 | 71 |
Host: hostname, |
| 72 | 72 |
}, |
| 73 |
- Version: APIVersion2, |
|
| 73 |
+ Version: APIVersion2, |
|
| 74 | 74 |
AllowNondistributableArtifacts: ana, |
| 75 | 75 |
TrimHostname: true, |
| 76 | 76 |
// used to check if supposed to be secure via InsecureSkipVerify |
| ... | ... |
@@ -22,20 +22,20 @@ func TestNetworkModeTest(t *testing.T) {
|
| 22 | 22 |
"something:weird": {true, false, false, false, false, false},
|
| 23 | 23 |
"bridge": {true, true, false, false, false, false},
|
| 24 | 24 |
DefaultDaemonNetworkMode(): {true, true, false, false, false, false},
|
| 25 |
- "host": {false, false, true, false, false, false},
|
|
| 26 |
- "container:name": {false, false, false, true, false, false},
|
|
| 27 |
- "none": {true, false, false, false, true, false},
|
|
| 28 |
- "default": {true, false, false, false, false, true},
|
|
| 25 |
+ "host": {false, false, true, false, false, false},
|
|
| 26 |
+ "container:name": {false, false, false, true, false, false},
|
|
| 27 |
+ "none": {true, false, false, false, true, false},
|
|
| 28 |
+ "default": {true, false, false, false, false, true},
|
|
| 29 | 29 |
} |
| 30 | 30 |
networkModeNames := map[container.NetworkMode]string{
|
| 31 | 31 |
"": "", |
| 32 | 32 |
"something:weird": "something:weird", |
| 33 | 33 |
"bridge": "bridge", |
| 34 | 34 |
DefaultDaemonNetworkMode(): "bridge", |
| 35 |
- "host": "host", |
|
| 36 |
- "container:name": "container", |
|
| 37 |
- "none": "none", |
|
| 38 |
- "default": "default", |
|
| 35 |
+ "host": "host", |
|
| 36 |
+ "container:name": "container", |
|
| 37 |
+ "none": "none", |
|
| 38 |
+ "default": "default", |
|
| 39 | 39 |
} |
| 40 | 40 |
for networkMode, state := range networkModes {
|
| 41 | 41 |
if networkMode.IsPrivate() != state[0] {
|
| ... | ... |
@@ -57,7 +57,8 @@ github.com/samuel/go-zookeeper d0e0d8e11f318e000a8cc434616d69e329edc374 |
| 57 | 57 |
github.com/deckarep/golang-set ef32fa3046d9f249d399f98ebaf9be944430fd1d |
| 58 | 58 |
github.com/coreos/etcd v3.2.1 |
| 59 | 59 |
github.com/coreos/go-semver v0.2.0 |
| 60 |
-github.com/ugorji/go f1f1a805ed361a0e078bb537e4ea78cd37dcf065 |
|
| 60 |
+# fix for go vet (https://github.com/kolyshkin/ugorji-go/commit/1cf431c13dec46596) |
|
| 61 |
+github.com/ugorji/go go111 https://github.com/kolyshkin/ugorji-go |
|
| 61 | 62 |
github.com/hashicorp/consul v0.5.2 |
| 62 | 63 |
github.com/boltdb/bolt fff57c100f4dea1905678da7e90d92429dff2904 |
| 63 | 64 |
github.com/miekg/dns v1.0.7 |
| ... | ... |
@@ -116,7 +117,7 @@ google.golang.org/genproto 694d95ba50e67b2e363f3483057db5d4910c18f9 |
| 116 | 116 |
# containerd |
| 117 | 117 |
github.com/containerd/containerd v1.2.0-beta.2 |
| 118 | 118 |
github.com/containerd/fifo 3d5202aec260678c48179c56f40e6f38a095738c |
| 119 |
-github.com/containerd/continuity d3c23511c1bf5851696cba83143d9cbcd666869b |
|
| 119 |
+github.com/containerd/continuity c7c5070e6f6e090ab93b0a61eb921f2196fc3383 |
|
| 120 | 120 |
github.com/containerd/cgroups 5e610833b72089b37d0e615de9a92dfc043757c2 |
| 121 | 121 |
github.com/containerd/console c12b1e7919c14469339a5d38f2f8ed9b64a9de23 |
| 122 | 122 |
github.com/containerd/go-runc edcf3de1f4971445c42d61f20d506b30612aa031 |
| ... | ... |
@@ -156,9 +157,3 @@ github.com/Nvveen/Gotty a8b993ba6abdb0e0c12b0125c603323a71c7790c https://github. |
| 156 | 156 |
github.com/docker/go-metrics d466d4f6fd960e01820085bd7e1a24426ee7ef18 |
| 157 | 157 |
|
| 158 | 158 |
github.com/opencontainers/selinux b29023b86e4a69d1b46b7e7b4e2b6fda03f0b9cd |
| 159 |
- |
|
| 160 |
- |
|
| 161 |
-# archive/tar (for Go 1.10, see https://github.com/golang/go/issues/24787) |
|
| 162 |
-# mkdir -p ./vendor/archive |
|
| 163 |
-# git clone -b go-1.10 --depth=1 git@github.com:kolyshkin/go-tar.git ./vendor/archive/tar |
|
| 164 |
-# vndr # to clean up test files |
| 165 | 159 |
deleted file mode 100644 |
| ... | ... |
@@ -1,27 +0,0 @@ |
| 1 |
-Copyright (c) 2009 The Go Authors. All rights reserved. |
|
| 2 |
- |
|
| 3 |
-Redistribution and use in source and binary forms, with or without |
|
| 4 |
-modification, are permitted provided that the following conditions are |
|
| 5 |
-met: |
|
| 6 |
- |
|
| 7 |
- * Redistributions of source code must retain the above copyright |
|
| 8 |
-notice, this list of conditions and the following disclaimer. |
|
| 9 |
- * Redistributions in binary form must reproduce the above |
|
| 10 |
-copyright notice, this list of conditions and the following disclaimer |
|
| 11 |
-in the documentation and/or other materials provided with the |
|
| 12 |
-distribution. |
|
| 13 |
- * Neither the name of Google Inc. nor the names of its |
|
| 14 |
-contributors may be used to endorse or promote products derived from |
|
| 15 |
-this software without specific prior written permission. |
|
| 16 |
- |
|
| 17 |
-THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
|
| 18 |
-"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
|
| 19 |
-LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
|
| 20 |
-A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
|
| 21 |
-OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
|
| 22 |
-SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
|
| 23 |
-LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
|
| 24 |
-DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
|
| 25 |
-THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
|
| 26 |
-(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
|
| 27 |
-OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| 28 | 1 |
deleted file mode 100644 |
| ... | ... |
@@ -1,27 +0,0 @@ |
| 1 |
-This is a fork of Go 1.10 `archive/tar` package from the official |
|
| 2 |
-[repo](https://github.com/golang/go/tree/release-branch.go1.10/src/archive/tar), |
|
| 3 |
-with a partial [revert](https://github.com/kolyshkin/go-tar/commit/d651d6e45972363e9bb62b8e9d876df440b31628) |
|
| 4 |
-of upstream [commit 0564e304a6ea](https://github.com/golang/go/commit/0564e304a6ea394a42929060c588469dbd6f32af). |
|
| 5 |
-It is suggested as a replacement to the original package included with Go 1.10 |
|
| 6 |
-in case you want to build a static Linux/glibc binary that works, and |
|
| 7 |
-can't afford to use `CGO_ENABLED=0`. |
|
| 8 |
- |
|
| 9 |
-## Details |
|
| 10 |
- |
|
| 11 |
-Using Go 1.10 [archive/tar](https://golang.org/pkg/archive/tar/) from a static binary |
|
| 12 |
-compiled with glibc on Linux can result in a panic upon calling |
|
| 13 |
-[`tar.FileInfoHeader()`](https://golang.org/pkg/archive/tar/#FileInfoHeader). |
|
| 14 |
-This is a major regression in Go 1.10, filed as |
|
| 15 |
-[Go issue #24787](https://github.com/golang/go/issues/24787). |
|
| 16 |
- |
|
| 17 |
-The above issue is caused by an unfortunate combination of: |
|
| 18 |
-1. glibc way of dynamic loading of nss libraries even for a static build; |
|
| 19 |
-2. Go `os/user` package hard-coded reliance on libc to resolve user/group IDs to names (unless CGO is disabled). |
|
| 20 |
- |
|
| 21 |
-While glibc can probably not be fixed and is not considered a bug per se, |
|
| 22 |
-the `os/user` issue is documented (see [Go issue #23265](https://github.com/golang/go/issues/23265)) |
|
| 23 |
-and already fixed by [Go commit 62f0127d81](https://github.com/golang/go/commit/62f0127d8104d8266d9a3fb5a87e2f09ec8b6f5b). |
|
| 24 |
-The fix is expected to make its way to Go 1.11, and requires `osusergo` build tag |
|
| 25 |
-to be used for a static build. |
|
| 26 |
- |
|
| 27 |
-This repository serves as a temporary workaround until the above fix is available. |
| 28 | 1 |
deleted file mode 100644 |
| ... | ... |
@@ -1,720 +0,0 @@ |
| 1 |
-// Copyright 2009 The Go Authors. All rights reserved. |
|
| 2 |
-// Use of this source code is governed by a BSD-style |
|
| 3 |
-// license that can be found in the LICENSE file. |
|
| 4 |
- |
|
| 5 |
-// Package tar implements access to tar archives. |
|
| 6 |
-// |
|
| 7 |
-// Tape archives (tar) are a file format for storing a sequence of files that |
|
| 8 |
-// can be read and written in a streaming manner. |
|
| 9 |
-// This package aims to cover most variations of the format, |
|
| 10 |
-// including those produced by GNU and BSD tar tools. |
|
| 11 |
-package tar |
|
| 12 |
- |
|
| 13 |
-import ( |
|
| 14 |
- "errors" |
|
| 15 |
- "fmt" |
|
| 16 |
- "math" |
|
| 17 |
- "os" |
|
| 18 |
- "path" |
|
| 19 |
- "reflect" |
|
| 20 |
- "strconv" |
|
| 21 |
- "strings" |
|
| 22 |
- "time" |
|
| 23 |
-) |
|
| 24 |
- |
|
| 25 |
-// BUG: Use of the Uid and Gid fields in Header could overflow on 32-bit |
|
| 26 |
-// architectures. If a large value is encountered when decoding, the result |
|
| 27 |
-// stored in Header will be the truncated version. |
|
| 28 |
- |
|
| 29 |
-var ( |
|
| 30 |
- ErrHeader = errors.New("archive/tar: invalid tar header")
|
|
| 31 |
- ErrWriteTooLong = errors.New("archive/tar: write too long")
|
|
| 32 |
- ErrFieldTooLong = errors.New("archive/tar: header field too long")
|
|
| 33 |
- ErrWriteAfterClose = errors.New("archive/tar: write after close")
|
|
| 34 |
- errMissData = errors.New("archive/tar: sparse file references non-existent data")
|
|
| 35 |
- errUnrefData = errors.New("archive/tar: sparse file contains unreferenced data")
|
|
| 36 |
- errWriteHole = errors.New("archive/tar: write non-NUL byte in sparse hole")
|
|
| 37 |
-) |
|
| 38 |
- |
|
| 39 |
-type headerError []string |
|
| 40 |
- |
|
| 41 |
-func (he headerError) Error() string {
|
|
| 42 |
- const prefix = "archive/tar: cannot encode header" |
|
| 43 |
- var ss []string |
|
| 44 |
- for _, s := range he {
|
|
| 45 |
- if s != "" {
|
|
| 46 |
- ss = append(ss, s) |
|
| 47 |
- } |
|
| 48 |
- } |
|
| 49 |
- if len(ss) == 0 {
|
|
| 50 |
- return prefix |
|
| 51 |
- } |
|
| 52 |
- return fmt.Sprintf("%s: %v", prefix, strings.Join(ss, "; and "))
|
|
| 53 |
-} |
|
| 54 |
- |
|
| 55 |
-// Type flags for Header.Typeflag. |
|
| 56 |
-const ( |
|
| 57 |
- // Type '0' indicates a regular file. |
|
| 58 |
- TypeReg = '0' |
|
| 59 |
- TypeRegA = '\x00' // For legacy support; use TypeReg instead |
|
| 60 |
- |
|
| 61 |
- // Type '1' to '6' are header-only flags and may not have a data body. |
|
| 62 |
- TypeLink = '1' // Hard link |
|
| 63 |
- TypeSymlink = '2' // Symbolic link |
|
| 64 |
- TypeChar = '3' // Character device node |
|
| 65 |
- TypeBlock = '4' // Block device node |
|
| 66 |
- TypeDir = '5' // Directory |
|
| 67 |
- TypeFifo = '6' // FIFO node |
|
| 68 |
- |
|
| 69 |
- // Type '7' is reserved. |
|
| 70 |
- TypeCont = '7' |
|
| 71 |
- |
|
| 72 |
- // Type 'x' is used by the PAX format to store key-value records that |
|
| 73 |
- // are only relevant to the next file. |
|
| 74 |
- // This package transparently handles these types. |
|
| 75 |
- TypeXHeader = 'x' |
|
| 76 |
- |
|
| 77 |
- // Type 'g' is used by the PAX format to store key-value records that |
|
| 78 |
- // are relevant to all subsequent files. |
|
| 79 |
- // This package only supports parsing and composing such headers, |
|
| 80 |
- // but does not currently support persisting the global state across files. |
|
| 81 |
- TypeXGlobalHeader = 'g' |
|
| 82 |
- |
|
| 83 |
- // Type 'S' indicates a sparse file in the GNU format. |
|
| 84 |
- TypeGNUSparse = 'S' |
|
| 85 |
- |
|
| 86 |
- // Types 'L' and 'K' are used by the GNU format for a meta file |
|
| 87 |
- // used to store the path or link name for the next file. |
|
| 88 |
- // This package transparently handles these types. |
|
| 89 |
- TypeGNULongName = 'L' |
|
| 90 |
- TypeGNULongLink = 'K' |
|
| 91 |
-) |
|
| 92 |
- |
|
| 93 |
-// Keywords for PAX extended header records. |
|
| 94 |
-const ( |
|
| 95 |
- paxNone = "" // Indicates that no PAX key is suitable |
|
| 96 |
- paxPath = "path" |
|
| 97 |
- paxLinkpath = "linkpath" |
|
| 98 |
- paxSize = "size" |
|
| 99 |
- paxUid = "uid" |
|
| 100 |
- paxGid = "gid" |
|
| 101 |
- paxUname = "uname" |
|
| 102 |
- paxGname = "gname" |
|
| 103 |
- paxMtime = "mtime" |
|
| 104 |
- paxAtime = "atime" |
|
| 105 |
- paxCtime = "ctime" // Removed from later revision of PAX spec, but was valid |
|
| 106 |
- paxCharset = "charset" // Currently unused |
|
| 107 |
- paxComment = "comment" // Currently unused |
|
| 108 |
- |
|
| 109 |
- paxSchilyXattr = "SCHILY.xattr." |
|
| 110 |
- |
|
| 111 |
- // Keywords for GNU sparse files in a PAX extended header. |
|
| 112 |
- paxGNUSparse = "GNU.sparse." |
|
| 113 |
- paxGNUSparseNumBlocks = "GNU.sparse.numblocks" |
|
| 114 |
- paxGNUSparseOffset = "GNU.sparse.offset" |
|
| 115 |
- paxGNUSparseNumBytes = "GNU.sparse.numbytes" |
|
| 116 |
- paxGNUSparseMap = "GNU.sparse.map" |
|
| 117 |
- paxGNUSparseName = "GNU.sparse.name" |
|
| 118 |
- paxGNUSparseMajor = "GNU.sparse.major" |
|
| 119 |
- paxGNUSparseMinor = "GNU.sparse.minor" |
|
| 120 |
- paxGNUSparseSize = "GNU.sparse.size" |
|
| 121 |
- paxGNUSparseRealSize = "GNU.sparse.realsize" |
|
| 122 |
-) |
|
| 123 |
- |
|
| 124 |
-// basicKeys is a set of the PAX keys for which we have built-in support. |
|
| 125 |
-// This does not contain "charset" or "comment", which are both PAX-specific, |
|
| 126 |
-// so adding them as first-class features of Header is unlikely. |
|
| 127 |
-// Users can use the PAXRecords field to set it themselves. |
|
| 128 |
-var basicKeys = map[string]bool{
|
|
| 129 |
- paxPath: true, paxLinkpath: true, paxSize: true, paxUid: true, paxGid: true, |
|
| 130 |
- paxUname: true, paxGname: true, paxMtime: true, paxAtime: true, paxCtime: true, |
|
| 131 |
-} |
|
| 132 |
- |
|
| 133 |
-// A Header represents a single header in a tar archive. |
|
| 134 |
-// Some fields may not be populated. |
|
| 135 |
-// |
|
| 136 |
-// For forward compatibility, users that retrieve a Header from Reader.Next, |
|
| 137 |
-// mutate it in some ways, and then pass it back to Writer.WriteHeader |
|
| 138 |
-// should do so by creating a new Header and copying the fields |
|
| 139 |
-// that they are interested in preserving. |
|
| 140 |
-type Header struct {
|
|
| 141 |
- Typeflag byte // Type of header entry (should be TypeReg for most files) |
|
| 142 |
- |
|
| 143 |
- Name string // Name of file entry |
|
| 144 |
- Linkname string // Target name of link (valid for TypeLink or TypeSymlink) |
|
| 145 |
- |
|
| 146 |
- Size int64 // Logical file size in bytes |
|
| 147 |
- Mode int64 // Permission and mode bits |
|
| 148 |
- Uid int // User ID of owner |
|
| 149 |
- Gid int // Group ID of owner |
|
| 150 |
- Uname string // User name of owner |
|
| 151 |
- Gname string // Group name of owner |
|
| 152 |
- |
|
| 153 |
- // If the Format is unspecified, then Writer.WriteHeader rounds ModTime |
|
| 154 |
- // to the nearest second and ignores the AccessTime and ChangeTime fields. |
|
| 155 |
- // |
|
| 156 |
- // To use AccessTime or ChangeTime, specify the Format as PAX or GNU. |
|
| 157 |
- // To use sub-second resolution, specify the Format as PAX. |
|
| 158 |
- ModTime time.Time // Modification time |
|
| 159 |
- AccessTime time.Time // Access time (requires either PAX or GNU support) |
|
| 160 |
- ChangeTime time.Time // Change time (requires either PAX or GNU support) |
|
| 161 |
- |
|
| 162 |
- Devmajor int64 // Major device number (valid for TypeChar or TypeBlock) |
|
| 163 |
- Devminor int64 // Minor device number (valid for TypeChar or TypeBlock) |
|
| 164 |
- |
|
| 165 |
- // Xattrs stores extended attributes as PAX records under the |
|
| 166 |
- // "SCHILY.xattr." namespace. |
|
| 167 |
- // |
|
| 168 |
- // The following are semantically equivalent: |
|
| 169 |
- // h.Xattrs[key] = value |
|
| 170 |
- // h.PAXRecords["SCHILY.xattr."+key] = value |
|
| 171 |
- // |
|
| 172 |
- // When Writer.WriteHeader is called, the contents of Xattrs will take |
|
| 173 |
- // precedence over those in PAXRecords. |
|
| 174 |
- // |
|
| 175 |
- // Deprecated: Use PAXRecords instead. |
|
| 176 |
- Xattrs map[string]string |
|
| 177 |
- |
|
| 178 |
- // PAXRecords is a map of PAX extended header records. |
|
| 179 |
- // |
|
| 180 |
- // User-defined records should have keys of the following form: |
|
| 181 |
- // VENDOR.keyword |
|
| 182 |
- // Where VENDOR is some namespace in all uppercase, and keyword may |
|
| 183 |
- // not contain the '=' character (e.g., "GOLANG.pkg.version"). |
|
| 184 |
- // The key and value should be non-empty UTF-8 strings. |
|
| 185 |
- // |
|
| 186 |
- // When Writer.WriteHeader is called, PAX records derived from the |
|
| 187 |
- // the other fields in Header take precedence over PAXRecords. |
|
| 188 |
- PAXRecords map[string]string |
|
| 189 |
- |
|
| 190 |
- // Format specifies the format of the tar header. |
|
| 191 |
- // |
|
| 192 |
- // This is set by Reader.Next as a best-effort guess at the format. |
|
| 193 |
- // Since the Reader liberally reads some non-compliant files, |
|
| 194 |
- // it is possible for this to be FormatUnknown. |
|
| 195 |
- // |
|
| 196 |
- // If the format is unspecified when Writer.WriteHeader is called, |
|
| 197 |
- // then it uses the first format (in the order of USTAR, PAX, GNU) |
|
| 198 |
- // capable of encoding this Header (see Format). |
|
| 199 |
- Format Format |
|
| 200 |
-} |
|
| 201 |
- |
|
| 202 |
-// sparseEntry represents a Length-sized fragment at Offset in the file. |
|
| 203 |
-type sparseEntry struct{ Offset, Length int64 }
|
|
| 204 |
- |
|
| 205 |
-func (s sparseEntry) endOffset() int64 { return s.Offset + s.Length }
|
|
| 206 |
- |
|
| 207 |
-// A sparse file can be represented as either a sparseDatas or a sparseHoles. |
|
| 208 |
-// As long as the total size is known, they are equivalent and one can be |
|
| 209 |
-// converted to the other form and back. The various tar formats with sparse |
|
| 210 |
-// file support represent sparse files in the sparseDatas form. That is, they |
|
| 211 |
-// specify the fragments in the file that has data, and treat everything else as |
|
| 212 |
-// having zero bytes. As such, the encoding and decoding logic in this package |
|
| 213 |
-// deals with sparseDatas. |
|
| 214 |
-// |
|
| 215 |
-// However, the external API uses sparseHoles instead of sparseDatas because the |
|
| 216 |
-// zero value of sparseHoles logically represents a normal file (i.e., there are |
|
| 217 |
-// no holes in it). On the other hand, the zero value of sparseDatas implies |
|
| 218 |
-// that the file has no data in it, which is rather odd. |
|
| 219 |
-// |
|
| 220 |
-// As an example, if the underlying raw file contains the 10-byte data: |
|
| 221 |
-// var compactFile = "abcdefgh" |
|
| 222 |
-// |
|
| 223 |
-// And the sparse map has the following entries: |
|
| 224 |
-// var spd sparseDatas = []sparseEntry{
|
|
| 225 |
-// {Offset: 2, Length: 5}, // Data fragment for 2..6
|
|
| 226 |
-// {Offset: 18, Length: 3}, // Data fragment for 18..20
|
|
| 227 |
-// } |
|
| 228 |
-// var sph sparseHoles = []sparseEntry{
|
|
| 229 |
-// {Offset: 0, Length: 2}, // Hole fragment for 0..1
|
|
| 230 |
-// {Offset: 7, Length: 11}, // Hole fragment for 7..17
|
|
| 231 |
-// {Offset: 21, Length: 4}, // Hole fragment for 21..24
|
|
| 232 |
-// } |
|
| 233 |
-// |
|
| 234 |
-// Then the content of the resulting sparse file with a Header.Size of 25 is: |
|
| 235 |
-// var sparseFile = "\x00"*2 + "abcde" + "\x00"*11 + "fgh" + "\x00"*4 |
|
| 236 |
-type ( |
|
| 237 |
- sparseDatas []sparseEntry |
|
| 238 |
- sparseHoles []sparseEntry |
|
| 239 |
-) |
|
| 240 |
- |
|
| 241 |
-// validateSparseEntries reports whether sp is a valid sparse map. |
|
| 242 |
-// It does not matter whether sp represents data fragments or hole fragments. |
|
| 243 |
-func validateSparseEntries(sp []sparseEntry, size int64) bool {
|
|
| 244 |
- // Validate all sparse entries. These are the same checks as performed by |
|
| 245 |
- // the BSD tar utility. |
|
| 246 |
- if size < 0 {
|
|
| 247 |
- return false |
|
| 248 |
- } |
|
| 249 |
- var pre sparseEntry |
|
| 250 |
- for _, cur := range sp {
|
|
| 251 |
- switch {
|
|
| 252 |
- case cur.Offset < 0 || cur.Length < 0: |
|
| 253 |
- return false // Negative values are never okay |
|
| 254 |
- case cur.Offset > math.MaxInt64-cur.Length: |
|
| 255 |
- return false // Integer overflow with large length |
|
| 256 |
- case cur.endOffset() > size: |
|
| 257 |
- return false // Region extends beyond the actual size |
|
| 258 |
- case pre.endOffset() > cur.Offset: |
|
| 259 |
- return false // Regions cannot overlap and must be in order |
|
| 260 |
- } |
|
| 261 |
- pre = cur |
|
| 262 |
- } |
|
| 263 |
- return true |
|
| 264 |
-} |
|
| 265 |
- |
|
| 266 |
-// alignSparseEntries mutates src and returns dst where each fragment's |
|
| 267 |
-// starting offset is aligned up to the nearest block edge, and each |
|
| 268 |
-// ending offset is aligned down to the nearest block edge. |
|
| 269 |
-// |
|
| 270 |
-// Even though the Go tar Reader and the BSD tar utility can handle entries |
|
| 271 |
-// with arbitrary offsets and lengths, the GNU tar utility can only handle |
|
| 272 |
-// offsets and lengths that are multiples of blockSize. |
|
| 273 |
-func alignSparseEntries(src []sparseEntry, size int64) []sparseEntry {
|
|
| 274 |
- dst := src[:0] |
|
| 275 |
- for _, s := range src {
|
|
| 276 |
- pos, end := s.Offset, s.endOffset() |
|
| 277 |
- pos += blockPadding(+pos) // Round-up to nearest blockSize |
|
| 278 |
- if end != size {
|
|
| 279 |
- end -= blockPadding(-end) // Round-down to nearest blockSize |
|
| 280 |
- } |
|
| 281 |
- if pos < end {
|
|
| 282 |
- dst = append(dst, sparseEntry{Offset: pos, Length: end - pos})
|
|
| 283 |
- } |
|
| 284 |
- } |
|
| 285 |
- return dst |
|
| 286 |
-} |
|
| 287 |
- |
|
| 288 |
-// invertSparseEntries converts a sparse map from one form to the other. |
|
| 289 |
-// If the input is sparseHoles, then it will output sparseDatas and vice-versa. |
|
| 290 |
-// The input must have been already validated. |
|
| 291 |
-// |
|
| 292 |
-// This function mutates src and returns a normalized map where: |
|
| 293 |
-// * adjacent fragments are coalesced together |
|
| 294 |
-// * only the last fragment may be empty |
|
| 295 |
-// * the endOffset of the last fragment is the total size |
|
| 296 |
-func invertSparseEntries(src []sparseEntry, size int64) []sparseEntry {
|
|
| 297 |
- dst := src[:0] |
|
| 298 |
- var pre sparseEntry |
|
| 299 |
- for _, cur := range src {
|
|
| 300 |
- if cur.Length == 0 {
|
|
| 301 |
- continue // Skip empty fragments |
|
| 302 |
- } |
|
| 303 |
- pre.Length = cur.Offset - pre.Offset |
|
| 304 |
- if pre.Length > 0 {
|
|
| 305 |
- dst = append(dst, pre) // Only add non-empty fragments |
|
| 306 |
- } |
|
| 307 |
- pre.Offset = cur.endOffset() |
|
| 308 |
- } |
|
| 309 |
- pre.Length = size - pre.Offset // Possibly the only empty fragment |
|
| 310 |
- return append(dst, pre) |
|
| 311 |
-} |
|
| 312 |
- |
|
| 313 |
-// fileState tracks the number of logical (includes sparse holes) and physical |
|
| 314 |
-// (actual in tar archive) bytes remaining for the current file. |
|
| 315 |
-// |
|
| 316 |
-// Invariant: LogicalRemaining >= PhysicalRemaining |
|
| 317 |
-type fileState interface {
|
|
| 318 |
- LogicalRemaining() int64 |
|
| 319 |
- PhysicalRemaining() int64 |
|
| 320 |
-} |
|
| 321 |
- |
|
| 322 |
-// allowedFormats determines which formats can be used. |
|
| 323 |
-// The value returned is the logical OR of multiple possible formats. |
|
| 324 |
-// If the value is FormatUnknown, then the input Header cannot be encoded |
|
| 325 |
-// and an error is returned explaining why. |
|
| 326 |
-// |
|
| 327 |
-// As a by-product of checking the fields, this function returns paxHdrs, which |
|
| 328 |
-// contain all fields that could not be directly encoded. |
|
| 329 |
-// A value receiver ensures that this method does not mutate the source Header. |
|
| 330 |
-func (h Header) allowedFormats() (format Format, paxHdrs map[string]string, err error) {
|
|
| 331 |
- format = FormatUSTAR | FormatPAX | FormatGNU |
|
| 332 |
- paxHdrs = make(map[string]string) |
|
| 333 |
- |
|
| 334 |
- var whyNoUSTAR, whyNoPAX, whyNoGNU string |
|
| 335 |
- var preferPAX bool // Prefer PAX over USTAR |
|
| 336 |
- verifyString := func(s string, size int, name, paxKey string) {
|
|
| 337 |
- // NUL-terminator is optional for path and linkpath. |
|
| 338 |
- // Technically, it is required for uname and gname, |
|
| 339 |
- // but neither GNU nor BSD tar checks for it. |
|
| 340 |
- tooLong := len(s) > size |
|
| 341 |
- allowLongGNU := paxKey == paxPath || paxKey == paxLinkpath |
|
| 342 |
- if hasNUL(s) || (tooLong && !allowLongGNU) {
|
|
| 343 |
- whyNoGNU = fmt.Sprintf("GNU cannot encode %s=%q", name, s)
|
|
| 344 |
- format.mustNotBe(FormatGNU) |
|
| 345 |
- } |
|
| 346 |
- if !isASCII(s) || tooLong {
|
|
| 347 |
- canSplitUSTAR := paxKey == paxPath |
|
| 348 |
- if _, _, ok := splitUSTARPath(s); !canSplitUSTAR || !ok {
|
|
| 349 |
- whyNoUSTAR = fmt.Sprintf("USTAR cannot encode %s=%q", name, s)
|
|
| 350 |
- format.mustNotBe(FormatUSTAR) |
|
| 351 |
- } |
|
| 352 |
- if paxKey == paxNone {
|
|
| 353 |
- whyNoPAX = fmt.Sprintf("PAX cannot encode %s=%q", name, s)
|
|
| 354 |
- format.mustNotBe(FormatPAX) |
|
| 355 |
- } else {
|
|
| 356 |
- paxHdrs[paxKey] = s |
|
| 357 |
- } |
|
| 358 |
- } |
|
| 359 |
- if v, ok := h.PAXRecords[paxKey]; ok && v == s {
|
|
| 360 |
- paxHdrs[paxKey] = v |
|
| 361 |
- } |
|
| 362 |
- } |
|
| 363 |
- verifyNumeric := func(n int64, size int, name, paxKey string) {
|
|
| 364 |
- if !fitsInBase256(size, n) {
|
|
| 365 |
- whyNoGNU = fmt.Sprintf("GNU cannot encode %s=%d", name, n)
|
|
| 366 |
- format.mustNotBe(FormatGNU) |
|
| 367 |
- } |
|
| 368 |
- if !fitsInOctal(size, n) {
|
|
| 369 |
- whyNoUSTAR = fmt.Sprintf("USTAR cannot encode %s=%d", name, n)
|
|
| 370 |
- format.mustNotBe(FormatUSTAR) |
|
| 371 |
- if paxKey == paxNone {
|
|
| 372 |
- whyNoPAX = fmt.Sprintf("PAX cannot encode %s=%d", name, n)
|
|
| 373 |
- format.mustNotBe(FormatPAX) |
|
| 374 |
- } else {
|
|
| 375 |
- paxHdrs[paxKey] = strconv.FormatInt(n, 10) |
|
| 376 |
- } |
|
| 377 |
- } |
|
| 378 |
- if v, ok := h.PAXRecords[paxKey]; ok && v == strconv.FormatInt(n, 10) {
|
|
| 379 |
- paxHdrs[paxKey] = v |
|
| 380 |
- } |
|
| 381 |
- } |
|
| 382 |
- verifyTime := func(ts time.Time, size int, name, paxKey string) {
|
|
| 383 |
- if ts.IsZero() {
|
|
| 384 |
- return // Always okay |
|
| 385 |
- } |
|
| 386 |
- if !fitsInBase256(size, ts.Unix()) {
|
|
| 387 |
- whyNoGNU = fmt.Sprintf("GNU cannot encode %s=%v", name, ts)
|
|
| 388 |
- format.mustNotBe(FormatGNU) |
|
| 389 |
- } |
|
| 390 |
- isMtime := paxKey == paxMtime |
|
| 391 |
- fitsOctal := fitsInOctal(size, ts.Unix()) |
|
| 392 |
- if (isMtime && !fitsOctal) || !isMtime {
|
|
| 393 |
- whyNoUSTAR = fmt.Sprintf("USTAR cannot encode %s=%v", name, ts)
|
|
| 394 |
- format.mustNotBe(FormatUSTAR) |
|
| 395 |
- } |
|
| 396 |
- needsNano := ts.Nanosecond() != 0 |
|
| 397 |
- if !isMtime || !fitsOctal || needsNano {
|
|
| 398 |
- preferPAX = true // USTAR may truncate sub-second measurements |
|
| 399 |
- if paxKey == paxNone {
|
|
| 400 |
- whyNoPAX = fmt.Sprintf("PAX cannot encode %s=%v", name, ts)
|
|
| 401 |
- format.mustNotBe(FormatPAX) |
|
| 402 |
- } else {
|
|
| 403 |
- paxHdrs[paxKey] = formatPAXTime(ts) |
|
| 404 |
- } |
|
| 405 |
- } |
|
| 406 |
- if v, ok := h.PAXRecords[paxKey]; ok && v == formatPAXTime(ts) {
|
|
| 407 |
- paxHdrs[paxKey] = v |
|
| 408 |
- } |
|
| 409 |
- } |
|
| 410 |
- |
|
| 411 |
- // Check basic fields. |
|
| 412 |
- var blk block |
|
| 413 |
- v7 := blk.V7() |
|
| 414 |
- ustar := blk.USTAR() |
|
| 415 |
- gnu := blk.GNU() |
|
| 416 |
- verifyString(h.Name, len(v7.Name()), "Name", paxPath) |
|
| 417 |
- verifyString(h.Linkname, len(v7.LinkName()), "Linkname", paxLinkpath) |
|
| 418 |
- verifyString(h.Uname, len(ustar.UserName()), "Uname", paxUname) |
|
| 419 |
- verifyString(h.Gname, len(ustar.GroupName()), "Gname", paxGname) |
|
| 420 |
- verifyNumeric(h.Mode, len(v7.Mode()), "Mode", paxNone) |
|
| 421 |
- verifyNumeric(int64(h.Uid), len(v7.UID()), "Uid", paxUid) |
|
| 422 |
- verifyNumeric(int64(h.Gid), len(v7.GID()), "Gid", paxGid) |
|
| 423 |
- verifyNumeric(h.Size, len(v7.Size()), "Size", paxSize) |
|
| 424 |
- verifyNumeric(h.Devmajor, len(ustar.DevMajor()), "Devmajor", paxNone) |
|
| 425 |
- verifyNumeric(h.Devminor, len(ustar.DevMinor()), "Devminor", paxNone) |
|
| 426 |
- verifyTime(h.ModTime, len(v7.ModTime()), "ModTime", paxMtime) |
|
| 427 |
- verifyTime(h.AccessTime, len(gnu.AccessTime()), "AccessTime", paxAtime) |
|
| 428 |
- verifyTime(h.ChangeTime, len(gnu.ChangeTime()), "ChangeTime", paxCtime) |
|
| 429 |
- |
|
| 430 |
- // Check for header-only types. |
|
| 431 |
- var whyOnlyPAX, whyOnlyGNU string |
|
| 432 |
- switch h.Typeflag {
|
|
| 433 |
- case TypeReg, TypeChar, TypeBlock, TypeFifo, TypeGNUSparse: |
|
| 434 |
- // Exclude TypeLink and TypeSymlink, since they may reference directories. |
|
| 435 |
- if strings.HasSuffix(h.Name, "/") {
|
|
| 436 |
- return FormatUnknown, nil, headerError{"filename may not have trailing slash"}
|
|
| 437 |
- } |
|
| 438 |
- case TypeXHeader, TypeGNULongName, TypeGNULongLink: |
|
| 439 |
- return FormatUnknown, nil, headerError{"cannot manually encode TypeXHeader, TypeGNULongName, or TypeGNULongLink headers"}
|
|
| 440 |
- case TypeXGlobalHeader: |
|
| 441 |
- h2 := Header{Name: h.Name, Typeflag: h.Typeflag, Xattrs: h.Xattrs, PAXRecords: h.PAXRecords, Format: h.Format}
|
|
| 442 |
- if !reflect.DeepEqual(h, h2) {
|
|
| 443 |
- return FormatUnknown, nil, headerError{"only PAXRecords should be set for TypeXGlobalHeader"}
|
|
| 444 |
- } |
|
| 445 |
- whyOnlyPAX = "only PAX supports TypeXGlobalHeader" |
|
| 446 |
- format.mayOnlyBe(FormatPAX) |
|
| 447 |
- } |
|
| 448 |
- if !isHeaderOnlyType(h.Typeflag) && h.Size < 0 {
|
|
| 449 |
- return FormatUnknown, nil, headerError{"negative size on header-only type"}
|
|
| 450 |
- } |
|
| 451 |
- |
|
| 452 |
- // Check PAX records. |
|
| 453 |
- if len(h.Xattrs) > 0 {
|
|
| 454 |
- for k, v := range h.Xattrs {
|
|
| 455 |
- paxHdrs[paxSchilyXattr+k] = v |
|
| 456 |
- } |
|
| 457 |
- whyOnlyPAX = "only PAX supports Xattrs" |
|
| 458 |
- format.mayOnlyBe(FormatPAX) |
|
| 459 |
- } |
|
| 460 |
- if len(h.PAXRecords) > 0 {
|
|
| 461 |
- for k, v := range h.PAXRecords {
|
|
| 462 |
- switch _, exists := paxHdrs[k]; {
|
|
| 463 |
- case exists: |
|
| 464 |
- continue // Do not overwrite existing records |
|
| 465 |
- case h.Typeflag == TypeXGlobalHeader: |
|
| 466 |
- paxHdrs[k] = v // Copy all records |
|
| 467 |
- case !basicKeys[k] && !strings.HasPrefix(k, paxGNUSparse): |
|
| 468 |
- paxHdrs[k] = v // Ignore local records that may conflict |
|
| 469 |
- } |
|
| 470 |
- } |
|
| 471 |
- whyOnlyPAX = "only PAX supports PAXRecords" |
|
| 472 |
- format.mayOnlyBe(FormatPAX) |
|
| 473 |
- } |
|
| 474 |
- for k, v := range paxHdrs {
|
|
| 475 |
- if !validPAXRecord(k, v) {
|
|
| 476 |
- return FormatUnknown, nil, headerError{fmt.Sprintf("invalid PAX record: %q", k+" = "+v)}
|
|
| 477 |
- } |
|
| 478 |
- } |
|
| 479 |
- |
|
| 480 |
- // TODO(dsnet): Re-enable this when adding sparse support. |
|
| 481 |
- // See https://golang.org/issue/22735 |
|
| 482 |
- /* |
|
| 483 |
- // Check sparse files. |
|
| 484 |
- if len(h.SparseHoles) > 0 || h.Typeflag == TypeGNUSparse {
|
|
| 485 |
- if isHeaderOnlyType(h.Typeflag) {
|
|
| 486 |
- return FormatUnknown, nil, headerError{"header-only type cannot be sparse"}
|
|
| 487 |
- } |
|
| 488 |
- if !validateSparseEntries(h.SparseHoles, h.Size) {
|
|
| 489 |
- return FormatUnknown, nil, headerError{"invalid sparse holes"}
|
|
| 490 |
- } |
|
| 491 |
- if h.Typeflag == TypeGNUSparse {
|
|
| 492 |
- whyOnlyGNU = "only GNU supports TypeGNUSparse" |
|
| 493 |
- format.mayOnlyBe(FormatGNU) |
|
| 494 |
- } else {
|
|
| 495 |
- whyNoGNU = "GNU supports sparse files only with TypeGNUSparse" |
|
| 496 |
- format.mustNotBe(FormatGNU) |
|
| 497 |
- } |
|
| 498 |
- whyNoUSTAR = "USTAR does not support sparse files" |
|
| 499 |
- format.mustNotBe(FormatUSTAR) |
|
| 500 |
- } |
|
| 501 |
- */ |
|
| 502 |
- |
|
| 503 |
- // Check desired format. |
|
| 504 |
- if wantFormat := h.Format; wantFormat != FormatUnknown {
|
|
| 505 |
- if wantFormat.has(FormatPAX) && !preferPAX {
|
|
| 506 |
- wantFormat.mayBe(FormatUSTAR) // PAX implies USTAR allowed too |
|
| 507 |
- } |
|
| 508 |
- format.mayOnlyBe(wantFormat) // Set union of formats allowed and format wanted |
|
| 509 |
- } |
|
| 510 |
- if format == FormatUnknown {
|
|
| 511 |
- switch h.Format {
|
|
| 512 |
- case FormatUSTAR: |
|
| 513 |
- err = headerError{"Format specifies USTAR", whyNoUSTAR, whyOnlyPAX, whyOnlyGNU}
|
|
| 514 |
- case FormatPAX: |
|
| 515 |
- err = headerError{"Format specifies PAX", whyNoPAX, whyOnlyGNU}
|
|
| 516 |
- case FormatGNU: |
|
| 517 |
- err = headerError{"Format specifies GNU", whyNoGNU, whyOnlyPAX}
|
|
| 518 |
- default: |
|
| 519 |
- err = headerError{whyNoUSTAR, whyNoPAX, whyNoGNU, whyOnlyPAX, whyOnlyGNU}
|
|
| 520 |
- } |
|
| 521 |
- } |
|
| 522 |
- return format, paxHdrs, err |
|
| 523 |
-} |
|
| 524 |
- |
|
| 525 |
-// FileInfo returns an os.FileInfo for the Header. |
|
| 526 |
-func (h *Header) FileInfo() os.FileInfo {
|
|
| 527 |
- return headerFileInfo{h}
|
|
| 528 |
-} |
|
| 529 |
- |
|
| 530 |
-// headerFileInfo implements os.FileInfo. |
|
| 531 |
-type headerFileInfo struct {
|
|
| 532 |
- h *Header |
|
| 533 |
-} |
|
| 534 |
- |
|
| 535 |
-func (fi headerFileInfo) Size() int64 { return fi.h.Size }
|
|
| 536 |
-func (fi headerFileInfo) IsDir() bool { return fi.Mode().IsDir() }
|
|
| 537 |
-func (fi headerFileInfo) ModTime() time.Time { return fi.h.ModTime }
|
|
| 538 |
-func (fi headerFileInfo) Sys() interface{} { return fi.h }
|
|
| 539 |
- |
|
| 540 |
-// Name returns the base name of the file. |
|
| 541 |
-func (fi headerFileInfo) Name() string {
|
|
| 542 |
- if fi.IsDir() {
|
|
| 543 |
- return path.Base(path.Clean(fi.h.Name)) |
|
| 544 |
- } |
|
| 545 |
- return path.Base(fi.h.Name) |
|
| 546 |
-} |
|
| 547 |
- |
|
| 548 |
-// Mode returns the permission and mode bits for the headerFileInfo. |
|
| 549 |
-func (fi headerFileInfo) Mode() (mode os.FileMode) {
|
|
| 550 |
- // Set file permission bits. |
|
| 551 |
- mode = os.FileMode(fi.h.Mode).Perm() |
|
| 552 |
- |
|
| 553 |
- // Set setuid, setgid and sticky bits. |
|
| 554 |
- if fi.h.Mode&c_ISUID != 0 {
|
|
| 555 |
- mode |= os.ModeSetuid |
|
| 556 |
- } |
|
| 557 |
- if fi.h.Mode&c_ISGID != 0 {
|
|
| 558 |
- mode |= os.ModeSetgid |
|
| 559 |
- } |
|
| 560 |
- if fi.h.Mode&c_ISVTX != 0 {
|
|
| 561 |
- mode |= os.ModeSticky |
|
| 562 |
- } |
|
| 563 |
- |
|
| 564 |
- // Set file mode bits; clear perm, setuid, setgid, and sticky bits. |
|
| 565 |
- switch m := os.FileMode(fi.h.Mode) &^ 07777; m {
|
|
| 566 |
- case c_ISDIR: |
|
| 567 |
- mode |= os.ModeDir |
|
| 568 |
- case c_ISFIFO: |
|
| 569 |
- mode |= os.ModeNamedPipe |
|
| 570 |
- case c_ISLNK: |
|
| 571 |
- mode |= os.ModeSymlink |
|
| 572 |
- case c_ISBLK: |
|
| 573 |
- mode |= os.ModeDevice |
|
| 574 |
- case c_ISCHR: |
|
| 575 |
- mode |= os.ModeDevice |
|
| 576 |
- mode |= os.ModeCharDevice |
|
| 577 |
- case c_ISSOCK: |
|
| 578 |
- mode |= os.ModeSocket |
|
| 579 |
- } |
|
| 580 |
- |
|
| 581 |
- switch fi.h.Typeflag {
|
|
| 582 |
- case TypeSymlink: |
|
| 583 |
- mode |= os.ModeSymlink |
|
| 584 |
- case TypeChar: |
|
| 585 |
- mode |= os.ModeDevice |
|
| 586 |
- mode |= os.ModeCharDevice |
|
| 587 |
- case TypeBlock: |
|
| 588 |
- mode |= os.ModeDevice |
|
| 589 |
- case TypeDir: |
|
| 590 |
- mode |= os.ModeDir |
|
| 591 |
- case TypeFifo: |
|
| 592 |
- mode |= os.ModeNamedPipe |
|
| 593 |
- } |
|
| 594 |
- |
|
| 595 |
- return mode |
|
| 596 |
-} |
|
| 597 |
- |
|
| 598 |
-// sysStat, if non-nil, populates h from system-dependent fields of fi. |
|
| 599 |
-var sysStat func(fi os.FileInfo, h *Header) error |
|
| 600 |
- |
|
| 601 |
-const ( |
|
| 602 |
- // Mode constants from the USTAR spec: |
|
| 603 |
- // See http://pubs.opengroup.org/onlinepubs/9699919799/utilities/pax.html#tag_20_92_13_06 |
|
| 604 |
- c_ISUID = 04000 // Set uid |
|
| 605 |
- c_ISGID = 02000 // Set gid |
|
| 606 |
- c_ISVTX = 01000 // Save text (sticky bit) |
|
| 607 |
- |
|
| 608 |
- // Common Unix mode constants; these are not defined in any common tar standard. |
|
| 609 |
- // Header.FileInfo understands these, but FileInfoHeader will never produce these. |
|
| 610 |
- c_ISDIR = 040000 // Directory |
|
| 611 |
- c_ISFIFO = 010000 // FIFO |
|
| 612 |
- c_ISREG = 0100000 // Regular file |
|
| 613 |
- c_ISLNK = 0120000 // Symbolic link |
|
| 614 |
- c_ISBLK = 060000 // Block special file |
|
| 615 |
- c_ISCHR = 020000 // Character special file |
|
| 616 |
- c_ISSOCK = 0140000 // Socket |
|
| 617 |
-) |
|
| 618 |
- |
|
| 619 |
-// FileInfoHeader creates a partially-populated Header from fi. |
|
| 620 |
-// If fi describes a symlink, FileInfoHeader records link as the link target. |
|
| 621 |
-// If fi describes a directory, a slash is appended to the name. |
|
| 622 |
-// |
|
| 623 |
-// Since os.FileInfo's Name method only returns the base name of |
|
| 624 |
-// the file it describes, it may be necessary to modify Header.Name |
|
| 625 |
-// to provide the full path name of the file. |
|
| 626 |
-func FileInfoHeader(fi os.FileInfo, link string) (*Header, error) {
|
|
| 627 |
- if fi == nil {
|
|
| 628 |
- return nil, errors.New("archive/tar: FileInfo is nil")
|
|
| 629 |
- } |
|
| 630 |
- fm := fi.Mode() |
|
| 631 |
- h := &Header{
|
|
| 632 |
- Name: fi.Name(), |
|
| 633 |
- ModTime: fi.ModTime(), |
|
| 634 |
- Mode: int64(fm.Perm()), // or'd with c_IS* constants later |
|
| 635 |
- } |
|
| 636 |
- switch {
|
|
| 637 |
- case fm.IsRegular(): |
|
| 638 |
- h.Typeflag = TypeReg |
|
| 639 |
- h.Size = fi.Size() |
|
| 640 |
- case fi.IsDir(): |
|
| 641 |
- h.Typeflag = TypeDir |
|
| 642 |
- h.Name += "/" |
|
| 643 |
- case fm&os.ModeSymlink != 0: |
|
| 644 |
- h.Typeflag = TypeSymlink |
|
| 645 |
- h.Linkname = link |
|
| 646 |
- case fm&os.ModeDevice != 0: |
|
| 647 |
- if fm&os.ModeCharDevice != 0 {
|
|
| 648 |
- h.Typeflag = TypeChar |
|
| 649 |
- } else {
|
|
| 650 |
- h.Typeflag = TypeBlock |
|
| 651 |
- } |
|
| 652 |
- case fm&os.ModeNamedPipe != 0: |
|
| 653 |
- h.Typeflag = TypeFifo |
|
| 654 |
- case fm&os.ModeSocket != 0: |
|
| 655 |
- return nil, fmt.Errorf("archive/tar: sockets not supported")
|
|
| 656 |
- default: |
|
| 657 |
- return nil, fmt.Errorf("archive/tar: unknown file mode %v", fm)
|
|
| 658 |
- } |
|
| 659 |
- if fm&os.ModeSetuid != 0 {
|
|
| 660 |
- h.Mode |= c_ISUID |
|
| 661 |
- } |
|
| 662 |
- if fm&os.ModeSetgid != 0 {
|
|
| 663 |
- h.Mode |= c_ISGID |
|
| 664 |
- } |
|
| 665 |
- if fm&os.ModeSticky != 0 {
|
|
| 666 |
- h.Mode |= c_ISVTX |
|
| 667 |
- } |
|
| 668 |
- // If possible, populate additional fields from OS-specific |
|
| 669 |
- // FileInfo fields. |
|
| 670 |
- if sys, ok := fi.Sys().(*Header); ok {
|
|
| 671 |
- // This FileInfo came from a Header (not the OS). Use the |
|
| 672 |
- // original Header to populate all remaining fields. |
|
| 673 |
- h.Uid = sys.Uid |
|
| 674 |
- h.Gid = sys.Gid |
|
| 675 |
- h.Uname = sys.Uname |
|
| 676 |
- h.Gname = sys.Gname |
|
| 677 |
- h.AccessTime = sys.AccessTime |
|
| 678 |
- h.ChangeTime = sys.ChangeTime |
|
| 679 |
- if sys.Xattrs != nil {
|
|
| 680 |
- h.Xattrs = make(map[string]string) |
|
| 681 |
- for k, v := range sys.Xattrs {
|
|
| 682 |
- h.Xattrs[k] = v |
|
| 683 |
- } |
|
| 684 |
- } |
|
| 685 |
- if sys.Typeflag == TypeLink {
|
|
| 686 |
- // hard link |
|
| 687 |
- h.Typeflag = TypeLink |
|
| 688 |
- h.Size = 0 |
|
| 689 |
- h.Linkname = sys.Linkname |
|
| 690 |
- } |
|
| 691 |
- if sys.PAXRecords != nil {
|
|
| 692 |
- h.PAXRecords = make(map[string]string) |
|
| 693 |
- for k, v := range sys.PAXRecords {
|
|
| 694 |
- h.PAXRecords[k] = v |
|
| 695 |
- } |
|
| 696 |
- } |
|
| 697 |
- } |
|
| 698 |
- if sysStat != nil {
|
|
| 699 |
- return h, sysStat(fi, h) |
|
| 700 |
- } |
|
| 701 |
- return h, nil |
|
| 702 |
-} |
|
| 703 |
- |
|
| 704 |
-// isHeaderOnlyType checks if the given type flag is of the type that has no |
|
| 705 |
-// data section even if a size is specified. |
|
| 706 |
-func isHeaderOnlyType(flag byte) bool {
|
|
| 707 |
- switch flag {
|
|
| 708 |
- case TypeLink, TypeSymlink, TypeChar, TypeBlock, TypeDir, TypeFifo: |
|
| 709 |
- return true |
|
| 710 |
- default: |
|
| 711 |
- return false |
|
| 712 |
- } |
|
| 713 |
-} |
|
| 714 |
- |
|
| 715 |
-func min(a, b int64) int64 {
|
|
| 716 |
- if a < b {
|
|
| 717 |
- return a |
|
| 718 |
- } |
|
| 719 |
- return b |
|
| 720 |
-} |
| 721 | 1 |
deleted file mode 100644 |
| ... | ... |
@@ -1,303 +0,0 @@ |
| 1 |
-// Copyright 2016 The Go Authors. All rights reserved. |
|
| 2 |
-// Use of this source code is governed by a BSD-style |
|
| 3 |
-// license that can be found in the LICENSE file. |
|
| 4 |
- |
|
| 5 |
-package tar |
|
| 6 |
- |
|
| 7 |
-import "strings" |
|
| 8 |
- |
|
| 9 |
-// Format represents the tar archive format. |
|
| 10 |
-// |
|
| 11 |
-// The original tar format was introduced in Unix V7. |
|
| 12 |
-// Since then, there have been multiple competing formats attempting to |
|
| 13 |
-// standardize or extend the V7 format to overcome its limitations. |
|
| 14 |
-// The most common formats are the USTAR, PAX, and GNU formats, |
|
| 15 |
-// each with their own advantages and limitations. |
|
| 16 |
-// |
|
| 17 |
-// The following table captures the capabilities of each format: |
|
| 18 |
-// |
|
| 19 |
-// | USTAR | PAX | GNU |
|
| 20 |
-// ------------------+--------+-----------+---------- |
|
| 21 |
-// Name | 256B | unlimited | unlimited |
|
| 22 |
-// Linkname | 100B | unlimited | unlimited |
|
| 23 |
-// Size | uint33 | unlimited | uint89 |
|
| 24 |
-// Mode | uint21 | uint21 | uint57 |
|
| 25 |
-// Uid/Gid | uint21 | unlimited | uint57 |
|
| 26 |
-// Uname/Gname | 32B | unlimited | 32B |
|
| 27 |
-// ModTime | uint33 | unlimited | int89 |
|
| 28 |
-// AccessTime | n/a | unlimited | int89 |
|
| 29 |
-// ChangeTime | n/a | unlimited | int89 |
|
| 30 |
-// Devmajor/Devminor | uint21 | uint21 | uint57 |
|
| 31 |
-// ------------------+--------+-----------+---------- |
|
| 32 |
-// string encoding | ASCII | UTF-8 | binary |
|
| 33 |
-// sub-second times | no | yes | no |
|
| 34 |
-// sparse files | no | yes | yes |
|
| 35 |
-// |
|
| 36 |
-// The table's upper portion shows the Header fields, where each format reports |
|
| 37 |
-// the maximum number of bytes allowed for each string field and |
|
| 38 |
-// the integer type used to store each numeric field |
|
| 39 |
-// (where timestamps are stored as the number of seconds since the Unix epoch). |
|
| 40 |
-// |
|
| 41 |
-// The table's lower portion shows specialized features of each format, |
|
| 42 |
-// such as supported string encodings, support for sub-second timestamps, |
|
| 43 |
-// or support for sparse files. |
|
| 44 |
-// |
|
| 45 |
-// The Writer currently provides no support for sparse files. |
|
| 46 |
-type Format int |
|
| 47 |
- |
|
| 48 |
-// Constants to identify various tar formats. |
|
| 49 |
-const ( |
|
| 50 |
- // Deliberately hide the meaning of constants from public API. |
|
| 51 |
- _ Format = (1 << iota) / 4 // Sequence of 0, 0, 1, 2, 4, 8, etc... |
|
| 52 |
- |
|
| 53 |
- // FormatUnknown indicates that the format is unknown. |
|
| 54 |
- FormatUnknown |
|
| 55 |
- |
|
| 56 |
- // The format of the original Unix V7 tar tool prior to standardization. |
|
| 57 |
- formatV7 |
|
| 58 |
- |
|
| 59 |
- // FormatUSTAR represents the USTAR header format defined in POSIX.1-1988. |
|
| 60 |
- // |
|
| 61 |
- // While this format is compatible with most tar readers, |
|
| 62 |
- // the format has several limitations making it unsuitable for some usages. |
|
| 63 |
- // Most notably, it cannot support sparse files, files larger than 8GiB, |
|
| 64 |
- // filenames larger than 256 characters, and non-ASCII filenames. |
|
| 65 |
- // |
|
| 66 |
- // Reference: |
|
| 67 |
- // http://pubs.opengroup.org/onlinepubs/9699919799/utilities/pax.html#tag_20_92_13_06 |
|
| 68 |
- FormatUSTAR |
|
| 69 |
- |
|
| 70 |
- // FormatPAX represents the PAX header format defined in POSIX.1-2001. |
|
| 71 |
- // |
|
| 72 |
- // PAX extends USTAR by writing a special file with Typeflag TypeXHeader |
|
| 73 |
- // preceding the original header. This file contains a set of key-value |
|
| 74 |
- // records, which are used to overcome USTAR's shortcomings, in addition to |
|
| 75 |
- // providing the ability to have sub-second resolution for timestamps. |
|
| 76 |
- // |
|
| 77 |
- // Some newer formats add their own extensions to PAX by defining their |
|
| 78 |
- // own keys and assigning certain semantic meaning to the associated values. |
|
| 79 |
- // For example, sparse file support in PAX is implemented using keys |
|
| 80 |
- // defined by the GNU manual (e.g., "GNU.sparse.map"). |
|
| 81 |
- // |
|
| 82 |
- // Reference: |
|
| 83 |
- // http://pubs.opengroup.org/onlinepubs/009695399/utilities/pax.html |
|
| 84 |
- FormatPAX |
|
| 85 |
- |
|
| 86 |
- // FormatGNU represents the GNU header format. |
|
| 87 |
- // |
|
| 88 |
- // The GNU header format is older than the USTAR and PAX standards and |
|
| 89 |
- // is not compatible with them. The GNU format supports |
|
| 90 |
- // arbitrary file sizes, filenames of arbitrary encoding and length, |
|
| 91 |
- // sparse files, and other features. |
|
| 92 |
- // |
|
| 93 |
- // It is recommended that PAX be chosen over GNU unless the target |
|
| 94 |
- // application can only parse GNU formatted archives. |
|
| 95 |
- // |
|
| 96 |
- // Reference: |
|
| 97 |
- // http://www.gnu.org/software/tar/manual/html_node/Standard.html |
|
| 98 |
- FormatGNU |
|
| 99 |
- |
|
| 100 |
- // Schily's tar format, which is incompatible with USTAR. |
|
| 101 |
- // This does not cover STAR extensions to the PAX format; these fall under |
|
| 102 |
- // the PAX format. |
|
| 103 |
- formatSTAR |
|
| 104 |
- |
|
| 105 |
- formatMax |
|
| 106 |
-) |
|
| 107 |
- |
|
| 108 |
-func (f Format) has(f2 Format) bool { return f&f2 != 0 }
|
|
| 109 |
-func (f *Format) mayBe(f2 Format) { *f |= f2 }
|
|
| 110 |
-func (f *Format) mayOnlyBe(f2 Format) { *f &= f2 }
|
|
| 111 |
-func (f *Format) mustNotBe(f2 Format) { *f &^= f2 }
|
|
| 112 |
- |
|
| 113 |
-var formatNames = map[Format]string{
|
|
| 114 |
- formatV7: "V7", FormatUSTAR: "USTAR", FormatPAX: "PAX", FormatGNU: "GNU", formatSTAR: "STAR", |
|
| 115 |
-} |
|
| 116 |
- |
|
| 117 |
-func (f Format) String() string {
|
|
| 118 |
- var ss []string |
|
| 119 |
- for f2 := Format(1); f2 < formatMax; f2 <<= 1 {
|
|
| 120 |
- if f.has(f2) {
|
|
| 121 |
- ss = append(ss, formatNames[f2]) |
|
| 122 |
- } |
|
| 123 |
- } |
|
| 124 |
- switch len(ss) {
|
|
| 125 |
- case 0: |
|
| 126 |
- return "<unknown>" |
|
| 127 |
- case 1: |
|
| 128 |
- return ss[0] |
|
| 129 |
- default: |
|
| 130 |
- return "(" + strings.Join(ss, " | ") + ")"
|
|
| 131 |
- } |
|
| 132 |
-} |
|
| 133 |
- |
|
| 134 |
-// Magics used to identify various formats. |
|
| 135 |
-const ( |
|
| 136 |
- magicGNU, versionGNU = "ustar ", " \x00" |
|
| 137 |
- magicUSTAR, versionUSTAR = "ustar\x00", "00" |
|
| 138 |
- trailerSTAR = "tar\x00" |
|
| 139 |
-) |
|
| 140 |
- |
|
| 141 |
-// Size constants from various tar specifications. |
|
| 142 |
-const ( |
|
| 143 |
- blockSize = 512 // Size of each block in a tar stream |
|
| 144 |
- nameSize = 100 // Max length of the name field in USTAR format |
|
| 145 |
- prefixSize = 155 // Max length of the prefix field in USTAR format |
|
| 146 |
-) |
|
| 147 |
- |
|
| 148 |
-// blockPadding computes the number of bytes needed to pad offset up to the |
|
| 149 |
-// nearest block edge where 0 <= n < blockSize. |
|
| 150 |
-func blockPadding(offset int64) (n int64) {
|
|
| 151 |
- return -offset & (blockSize - 1) |
|
| 152 |
-} |
|
| 153 |
- |
|
| 154 |
-var zeroBlock block |
|
| 155 |
- |
|
| 156 |
-type block [blockSize]byte |
|
| 157 |
- |
|
| 158 |
-// Convert block to any number of formats. |
|
| 159 |
-func (b *block) V7() *headerV7 { return (*headerV7)(b) }
|
|
| 160 |
-func (b *block) GNU() *headerGNU { return (*headerGNU)(b) }
|
|
| 161 |
-func (b *block) STAR() *headerSTAR { return (*headerSTAR)(b) }
|
|
| 162 |
-func (b *block) USTAR() *headerUSTAR { return (*headerUSTAR)(b) }
|
|
| 163 |
-func (b *block) Sparse() sparseArray { return (sparseArray)(b[:]) }
|
|
| 164 |
- |
|
| 165 |
-// GetFormat checks that the block is a valid tar header based on the checksum. |
|
| 166 |
-// It then attempts to guess the specific format based on magic values. |
|
| 167 |
-// If the checksum fails, then FormatUnknown is returned. |
|
| 168 |
-func (b *block) GetFormat() Format {
|
|
| 169 |
- // Verify checksum. |
|
| 170 |
- var p parser |
|
| 171 |
- value := p.parseOctal(b.V7().Chksum()) |
|
| 172 |
- chksum1, chksum2 := b.ComputeChecksum() |
|
| 173 |
- if p.err != nil || (value != chksum1 && value != chksum2) {
|
|
| 174 |
- return FormatUnknown |
|
| 175 |
- } |
|
| 176 |
- |
|
| 177 |
- // Guess the magic values. |
|
| 178 |
- magic := string(b.USTAR().Magic()) |
|
| 179 |
- version := string(b.USTAR().Version()) |
|
| 180 |
- trailer := string(b.STAR().Trailer()) |
|
| 181 |
- switch {
|
|
| 182 |
- case magic == magicUSTAR && trailer == trailerSTAR: |
|
| 183 |
- return formatSTAR |
|
| 184 |
- case magic == magicUSTAR: |
|
| 185 |
- return FormatUSTAR | FormatPAX |
|
| 186 |
- case magic == magicGNU && version == versionGNU: |
|
| 187 |
- return FormatGNU |
|
| 188 |
- default: |
|
| 189 |
- return formatV7 |
|
| 190 |
- } |
|
| 191 |
-} |
|
| 192 |
- |
|
| 193 |
-// SetFormat writes the magic values necessary for specified format |
|
| 194 |
-// and then updates the checksum accordingly. |
|
| 195 |
-func (b *block) SetFormat(format Format) {
|
|
| 196 |
- // Set the magic values. |
|
| 197 |
- switch {
|
|
| 198 |
- case format.has(formatV7): |
|
| 199 |
- // Do nothing. |
|
| 200 |
- case format.has(FormatGNU): |
|
| 201 |
- copy(b.GNU().Magic(), magicGNU) |
|
| 202 |
- copy(b.GNU().Version(), versionGNU) |
|
| 203 |
- case format.has(formatSTAR): |
|
| 204 |
- copy(b.STAR().Magic(), magicUSTAR) |
|
| 205 |
- copy(b.STAR().Version(), versionUSTAR) |
|
| 206 |
- copy(b.STAR().Trailer(), trailerSTAR) |
|
| 207 |
- case format.has(FormatUSTAR | FormatPAX): |
|
| 208 |
- copy(b.USTAR().Magic(), magicUSTAR) |
|
| 209 |
- copy(b.USTAR().Version(), versionUSTAR) |
|
| 210 |
- default: |
|
| 211 |
- panic("invalid format")
|
|
| 212 |
- } |
|
| 213 |
- |
|
| 214 |
- // Update checksum. |
|
| 215 |
- // This field is special in that it is terminated by a NULL then space. |
|
| 216 |
- var f formatter |
|
| 217 |
- field := b.V7().Chksum() |
|
| 218 |
- chksum, _ := b.ComputeChecksum() // Possible values are 256..128776 |
|
| 219 |
- f.formatOctal(field[:7], chksum) // Never fails since 128776 < 262143 |
|
| 220 |
- field[7] = ' ' |
|
| 221 |
-} |
|
| 222 |
- |
|
| 223 |
-// ComputeChecksum computes the checksum for the header block. |
|
| 224 |
-// POSIX specifies a sum of the unsigned byte values, but the Sun tar used |
|
| 225 |
-// signed byte values. |
|
| 226 |
-// We compute and return both. |
|
| 227 |
-func (b *block) ComputeChecksum() (unsigned, signed int64) {
|
|
| 228 |
- for i, c := range b {
|
|
| 229 |
- if 148 <= i && i < 156 {
|
|
| 230 |
- c = ' ' // Treat the checksum field itself as all spaces. |
|
| 231 |
- } |
|
| 232 |
- unsigned += int64(c) |
|
| 233 |
- signed += int64(int8(c)) |
|
| 234 |
- } |
|
| 235 |
- return unsigned, signed |
|
| 236 |
-} |
|
| 237 |
- |
|
| 238 |
-// Reset clears the block with all zeros. |
|
| 239 |
-func (b *block) Reset() {
|
|
| 240 |
- *b = block{}
|
|
| 241 |
-} |
|
| 242 |
- |
|
| 243 |
-type headerV7 [blockSize]byte |
|
| 244 |
- |
|
| 245 |
-func (h *headerV7) Name() []byte { return h[000:][:100] }
|
|
| 246 |
-func (h *headerV7) Mode() []byte { return h[100:][:8] }
|
|
| 247 |
-func (h *headerV7) UID() []byte { return h[108:][:8] }
|
|
| 248 |
-func (h *headerV7) GID() []byte { return h[116:][:8] }
|
|
| 249 |
-func (h *headerV7) Size() []byte { return h[124:][:12] }
|
|
| 250 |
-func (h *headerV7) ModTime() []byte { return h[136:][:12] }
|
|
| 251 |
-func (h *headerV7) Chksum() []byte { return h[148:][:8] }
|
|
| 252 |
-func (h *headerV7) TypeFlag() []byte { return h[156:][:1] }
|
|
| 253 |
-func (h *headerV7) LinkName() []byte { return h[157:][:100] }
|
|
| 254 |
- |
|
| 255 |
-type headerGNU [blockSize]byte |
|
| 256 |
- |
|
| 257 |
-func (h *headerGNU) V7() *headerV7 { return (*headerV7)(h) }
|
|
| 258 |
-func (h *headerGNU) Magic() []byte { return h[257:][:6] }
|
|
| 259 |
-func (h *headerGNU) Version() []byte { return h[263:][:2] }
|
|
| 260 |
-func (h *headerGNU) UserName() []byte { return h[265:][:32] }
|
|
| 261 |
-func (h *headerGNU) GroupName() []byte { return h[297:][:32] }
|
|
| 262 |
-func (h *headerGNU) DevMajor() []byte { return h[329:][:8] }
|
|
| 263 |
-func (h *headerGNU) DevMinor() []byte { return h[337:][:8] }
|
|
| 264 |
-func (h *headerGNU) AccessTime() []byte { return h[345:][:12] }
|
|
| 265 |
-func (h *headerGNU) ChangeTime() []byte { return h[357:][:12] }
|
|
| 266 |
-func (h *headerGNU) Sparse() sparseArray { return (sparseArray)(h[386:][:24*4+1]) }
|
|
| 267 |
-func (h *headerGNU) RealSize() []byte { return h[483:][:12] }
|
|
| 268 |
- |
|
| 269 |
-type headerSTAR [blockSize]byte |
|
| 270 |
- |
|
| 271 |
-func (h *headerSTAR) V7() *headerV7 { return (*headerV7)(h) }
|
|
| 272 |
-func (h *headerSTAR) Magic() []byte { return h[257:][:6] }
|
|
| 273 |
-func (h *headerSTAR) Version() []byte { return h[263:][:2] }
|
|
| 274 |
-func (h *headerSTAR) UserName() []byte { return h[265:][:32] }
|
|
| 275 |
-func (h *headerSTAR) GroupName() []byte { return h[297:][:32] }
|
|
| 276 |
-func (h *headerSTAR) DevMajor() []byte { return h[329:][:8] }
|
|
| 277 |
-func (h *headerSTAR) DevMinor() []byte { return h[337:][:8] }
|
|
| 278 |
-func (h *headerSTAR) Prefix() []byte { return h[345:][:131] }
|
|
| 279 |
-func (h *headerSTAR) AccessTime() []byte { return h[476:][:12] }
|
|
| 280 |
-func (h *headerSTAR) ChangeTime() []byte { return h[488:][:12] }
|
|
| 281 |
-func (h *headerSTAR) Trailer() []byte { return h[508:][:4] }
|
|
| 282 |
- |
|
| 283 |
-type headerUSTAR [blockSize]byte |
|
| 284 |
- |
|
| 285 |
-func (h *headerUSTAR) V7() *headerV7 { return (*headerV7)(h) }
|
|
| 286 |
-func (h *headerUSTAR) Magic() []byte { return h[257:][:6] }
|
|
| 287 |
-func (h *headerUSTAR) Version() []byte { return h[263:][:2] }
|
|
| 288 |
-func (h *headerUSTAR) UserName() []byte { return h[265:][:32] }
|
|
| 289 |
-func (h *headerUSTAR) GroupName() []byte { return h[297:][:32] }
|
|
| 290 |
-func (h *headerUSTAR) DevMajor() []byte { return h[329:][:8] }
|
|
| 291 |
-func (h *headerUSTAR) DevMinor() []byte { return h[337:][:8] }
|
|
| 292 |
-func (h *headerUSTAR) Prefix() []byte { return h[345:][:155] }
|
|
| 293 |
- |
|
| 294 |
-type sparseArray []byte |
|
| 295 |
- |
|
| 296 |
-func (s sparseArray) Entry(i int) sparseElem { return (sparseElem)(s[i*24:]) }
|
|
| 297 |
-func (s sparseArray) IsExtended() []byte { return s[24*s.MaxEntries():][:1] }
|
|
| 298 |
-func (s sparseArray) MaxEntries() int { return len(s) / 24 }
|
|
| 299 |
- |
|
| 300 |
-type sparseElem []byte |
|
| 301 |
- |
|
| 302 |
-func (s sparseElem) Offset() []byte { return s[00:][:12] }
|
|
| 303 |
-func (s sparseElem) Length() []byte { return s[12:][:12] }
|
| 304 | 1 |
deleted file mode 100644 |
| ... | ... |
@@ -1,855 +0,0 @@ |
| 1 |
-// Copyright 2009 The Go Authors. All rights reserved. |
|
| 2 |
-// Use of this source code is governed by a BSD-style |
|
| 3 |
-// license that can be found in the LICENSE file. |
|
| 4 |
- |
|
| 5 |
-package tar |
|
| 6 |
- |
|
| 7 |
-import ( |
|
| 8 |
- "bytes" |
|
| 9 |
- "io" |
|
| 10 |
- "io/ioutil" |
|
| 11 |
- "strconv" |
|
| 12 |
- "strings" |
|
| 13 |
- "time" |
|
| 14 |
-) |
|
| 15 |
- |
|
| 16 |
-// Reader provides sequential access to the contents of a tar archive. |
|
| 17 |
-// Reader.Next advances to the next file in the archive (including the first), |
|
| 18 |
-// and then Reader can be treated as an io.Reader to access the file's data. |
|
| 19 |
-type Reader struct {
|
|
| 20 |
- r io.Reader |
|
| 21 |
- pad int64 // Amount of padding (ignored) after current file entry |
|
| 22 |
- curr fileReader // Reader for current file entry |
|
| 23 |
- blk block // Buffer to use as temporary local storage |
|
| 24 |
- |
|
| 25 |
- // err is a persistent error. |
|
| 26 |
- // It is only the responsibility of every exported method of Reader to |
|
| 27 |
- // ensure that this error is sticky. |
|
| 28 |
- err error |
|
| 29 |
-} |
|
| 30 |
- |
|
| 31 |
-type fileReader interface {
|
|
| 32 |
- io.Reader |
|
| 33 |
- fileState |
|
| 34 |
- |
|
| 35 |
- WriteTo(io.Writer) (int64, error) |
|
| 36 |
-} |
|
| 37 |
- |
|
| 38 |
-// NewReader creates a new Reader reading from r. |
|
| 39 |
-func NewReader(r io.Reader) *Reader {
|
|
| 40 |
- return &Reader{r: r, curr: ®FileReader{r, 0}}
|
|
| 41 |
-} |
|
| 42 |
- |
|
| 43 |
-// Next advances to the next entry in the tar archive. |
|
| 44 |
-// The Header.Size determines how many bytes can be read for the next file. |
|
| 45 |
-// Any remaining data in the current file is automatically discarded. |
|
| 46 |
-// |
|
| 47 |
-// io.EOF is returned at the end of the input. |
|
| 48 |
-func (tr *Reader) Next() (*Header, error) {
|
|
| 49 |
- if tr.err != nil {
|
|
| 50 |
- return nil, tr.err |
|
| 51 |
- } |
|
| 52 |
- hdr, err := tr.next() |
|
| 53 |
- tr.err = err |
|
| 54 |
- return hdr, err |
|
| 55 |
-} |
|
| 56 |
- |
|
| 57 |
-func (tr *Reader) next() (*Header, error) {
|
|
| 58 |
- var paxHdrs map[string]string |
|
| 59 |
- var gnuLongName, gnuLongLink string |
|
| 60 |
- |
|
| 61 |
- // Externally, Next iterates through the tar archive as if it is a series of |
|
| 62 |
- // files. Internally, the tar format often uses fake "files" to add meta |
|
| 63 |
- // data that describes the next file. These meta data "files" should not |
|
| 64 |
- // normally be visible to the outside. As such, this loop iterates through |
|
| 65 |
- // one or more "header files" until it finds a "normal file". |
|
| 66 |
- format := FormatUSTAR | FormatPAX | FormatGNU |
|
| 67 |
-loop: |
|
| 68 |
- for {
|
|
| 69 |
- // Discard the remainder of the file and any padding. |
|
| 70 |
- if err := discard(tr.r, tr.curr.PhysicalRemaining()); err != nil {
|
|
| 71 |
- return nil, err |
|
| 72 |
- } |
|
| 73 |
- if _, err := tryReadFull(tr.r, tr.blk[:tr.pad]); err != nil {
|
|
| 74 |
- return nil, err |
|
| 75 |
- } |
|
| 76 |
- tr.pad = 0 |
|
| 77 |
- |
|
| 78 |
- hdr, rawHdr, err := tr.readHeader() |
|
| 79 |
- if err != nil {
|
|
| 80 |
- return nil, err |
|
| 81 |
- } |
|
| 82 |
- if err := tr.handleRegularFile(hdr); err != nil {
|
|
| 83 |
- return nil, err |
|
| 84 |
- } |
|
| 85 |
- format.mayOnlyBe(hdr.Format) |
|
| 86 |
- |
|
| 87 |
- // Check for PAX/GNU special headers and files. |
|
| 88 |
- switch hdr.Typeflag {
|
|
| 89 |
- case TypeXHeader, TypeXGlobalHeader: |
|
| 90 |
- format.mayOnlyBe(FormatPAX) |
|
| 91 |
- paxHdrs, err = parsePAX(tr) |
|
| 92 |
- if err != nil {
|
|
| 93 |
- return nil, err |
|
| 94 |
- } |
|
| 95 |
- if hdr.Typeflag == TypeXGlobalHeader {
|
|
| 96 |
- mergePAX(hdr, paxHdrs) |
|
| 97 |
- return &Header{
|
|
| 98 |
- Name: hdr.Name, |
|
| 99 |
- Typeflag: hdr.Typeflag, |
|
| 100 |
- Xattrs: hdr.Xattrs, |
|
| 101 |
- PAXRecords: hdr.PAXRecords, |
|
| 102 |
- Format: format, |
|
| 103 |
- }, nil |
|
| 104 |
- } |
|
| 105 |
- continue loop // This is a meta header affecting the next header |
|
| 106 |
- case TypeGNULongName, TypeGNULongLink: |
|
| 107 |
- format.mayOnlyBe(FormatGNU) |
|
| 108 |
- realname, err := ioutil.ReadAll(tr) |
|
| 109 |
- if err != nil {
|
|
| 110 |
- return nil, err |
|
| 111 |
- } |
|
| 112 |
- |
|
| 113 |
- var p parser |
|
| 114 |
- switch hdr.Typeflag {
|
|
| 115 |
- case TypeGNULongName: |
|
| 116 |
- gnuLongName = p.parseString(realname) |
|
| 117 |
- case TypeGNULongLink: |
|
| 118 |
- gnuLongLink = p.parseString(realname) |
|
| 119 |
- } |
|
| 120 |
- continue loop // This is a meta header affecting the next header |
|
| 121 |
- default: |
|
| 122 |
- // The old GNU sparse format is handled here since it is technically |
|
| 123 |
- // just a regular file with additional attributes. |
|
| 124 |
- |
|
| 125 |
- if err := mergePAX(hdr, paxHdrs); err != nil {
|
|
| 126 |
- return nil, err |
|
| 127 |
- } |
|
| 128 |
- if gnuLongName != "" {
|
|
| 129 |
- hdr.Name = gnuLongName |
|
| 130 |
- } |
|
| 131 |
- if gnuLongLink != "" {
|
|
| 132 |
- hdr.Linkname = gnuLongLink |
|
| 133 |
- } |
|
| 134 |
- if hdr.Typeflag == TypeRegA && strings.HasSuffix(hdr.Name, "/") {
|
|
| 135 |
- hdr.Typeflag = TypeDir // Legacy archives use trailing slash for directories |
|
| 136 |
- } |
|
| 137 |
- |
|
| 138 |
- // The extended headers may have updated the size. |
|
| 139 |
- // Thus, setup the regFileReader again after merging PAX headers. |
|
| 140 |
- if err := tr.handleRegularFile(hdr); err != nil {
|
|
| 141 |
- return nil, err |
|
| 142 |
- } |
|
| 143 |
- |
|
| 144 |
- // Sparse formats rely on being able to read from the logical data |
|
| 145 |
- // section; there must be a preceding call to handleRegularFile. |
|
| 146 |
- if err := tr.handleSparseFile(hdr, rawHdr); err != nil {
|
|
| 147 |
- return nil, err |
|
| 148 |
- } |
|
| 149 |
- |
|
| 150 |
- // Set the final guess at the format. |
|
| 151 |
- if format.has(FormatUSTAR) && format.has(FormatPAX) {
|
|
| 152 |
- format.mayOnlyBe(FormatUSTAR) |
|
| 153 |
- } |
|
| 154 |
- hdr.Format = format |
|
| 155 |
- return hdr, nil // This is a file, so stop |
|
| 156 |
- } |
|
| 157 |
- } |
|
| 158 |
-} |
|
| 159 |
- |
|
| 160 |
-// handleRegularFile sets up the current file reader and padding such that it |
|
| 161 |
-// can only read the following logical data section. It will properly handle |
|
| 162 |
-// special headers that contain no data section. |
|
| 163 |
-func (tr *Reader) handleRegularFile(hdr *Header) error {
|
|
| 164 |
- nb := hdr.Size |
|
| 165 |
- if isHeaderOnlyType(hdr.Typeflag) {
|
|
| 166 |
- nb = 0 |
|
| 167 |
- } |
|
| 168 |
- if nb < 0 {
|
|
| 169 |
- return ErrHeader |
|
| 170 |
- } |
|
| 171 |
- |
|
| 172 |
- tr.pad = blockPadding(nb) |
|
| 173 |
- tr.curr = ®FileReader{r: tr.r, nb: nb}
|
|
| 174 |
- return nil |
|
| 175 |
-} |
|
| 176 |
- |
|
| 177 |
-// handleSparseFile checks if the current file is a sparse format of any type |
|
| 178 |
-// and sets the curr reader appropriately. |
|
| 179 |
-func (tr *Reader) handleSparseFile(hdr *Header, rawHdr *block) error {
|
|
| 180 |
- var spd sparseDatas |
|
| 181 |
- var err error |
|
| 182 |
- if hdr.Typeflag == TypeGNUSparse {
|
|
| 183 |
- spd, err = tr.readOldGNUSparseMap(hdr, rawHdr) |
|
| 184 |
- } else {
|
|
| 185 |
- spd, err = tr.readGNUSparsePAXHeaders(hdr) |
|
| 186 |
- } |
|
| 187 |
- |
|
| 188 |
- // If sp is non-nil, then this is a sparse file. |
|
| 189 |
- // Note that it is possible for len(sp) == 0. |
|
| 190 |
- if err == nil && spd != nil {
|
|
| 191 |
- if isHeaderOnlyType(hdr.Typeflag) || !validateSparseEntries(spd, hdr.Size) {
|
|
| 192 |
- return ErrHeader |
|
| 193 |
- } |
|
| 194 |
- sph := invertSparseEntries(spd, hdr.Size) |
|
| 195 |
- tr.curr = &sparseFileReader{tr.curr, sph, 0}
|
|
| 196 |
- } |
|
| 197 |
- return err |
|
| 198 |
-} |
|
| 199 |
- |
|
| 200 |
-// readGNUSparsePAXHeaders checks the PAX headers for GNU sparse headers. |
|
| 201 |
-// If they are found, then this function reads the sparse map and returns it. |
|
| 202 |
-// This assumes that 0.0 headers have already been converted to 0.1 headers |
|
| 203 |
-// by the the PAX header parsing logic. |
|
| 204 |
-func (tr *Reader) readGNUSparsePAXHeaders(hdr *Header) (sparseDatas, error) {
|
|
| 205 |
- // Identify the version of GNU headers. |
|
| 206 |
- var is1x0 bool |
|
| 207 |
- major, minor := hdr.PAXRecords[paxGNUSparseMajor], hdr.PAXRecords[paxGNUSparseMinor] |
|
| 208 |
- switch {
|
|
| 209 |
- case major == "0" && (minor == "0" || minor == "1"): |
|
| 210 |
- is1x0 = false |
|
| 211 |
- case major == "1" && minor == "0": |
|
| 212 |
- is1x0 = true |
|
| 213 |
- case major != "" || minor != "": |
|
| 214 |
- return nil, nil // Unknown GNU sparse PAX version |
|
| 215 |
- case hdr.PAXRecords[paxGNUSparseMap] != "": |
|
| 216 |
- is1x0 = false // 0.0 and 0.1 did not have explicit version records, so guess |
|
| 217 |
- default: |
|
| 218 |
- return nil, nil // Not a PAX format GNU sparse file. |
|
| 219 |
- } |
|
| 220 |
- hdr.Format.mayOnlyBe(FormatPAX) |
|
| 221 |
- |
|
| 222 |
- // Update hdr from GNU sparse PAX headers. |
|
| 223 |
- if name := hdr.PAXRecords[paxGNUSparseName]; name != "" {
|
|
| 224 |
- hdr.Name = name |
|
| 225 |
- } |
|
| 226 |
- size := hdr.PAXRecords[paxGNUSparseSize] |
|
| 227 |
- if size == "" {
|
|
| 228 |
- size = hdr.PAXRecords[paxGNUSparseRealSize] |
|
| 229 |
- } |
|
| 230 |
- if size != "" {
|
|
| 231 |
- n, err := strconv.ParseInt(size, 10, 64) |
|
| 232 |
- if err != nil {
|
|
| 233 |
- return nil, ErrHeader |
|
| 234 |
- } |
|
| 235 |
- hdr.Size = n |
|
| 236 |
- } |
|
| 237 |
- |
|
| 238 |
- // Read the sparse map according to the appropriate format. |
|
| 239 |
- if is1x0 {
|
|
| 240 |
- return readGNUSparseMap1x0(tr.curr) |
|
| 241 |
- } |
|
| 242 |
- return readGNUSparseMap0x1(hdr.PAXRecords) |
|
| 243 |
-} |
|
| 244 |
- |
|
| 245 |
-// mergePAX merges paxHdrs into hdr for all relevant fields of Header. |
|
| 246 |
-func mergePAX(hdr *Header, paxHdrs map[string]string) (err error) {
|
|
| 247 |
- for k, v := range paxHdrs {
|
|
| 248 |
- if v == "" {
|
|
| 249 |
- continue // Keep the original USTAR value |
|
| 250 |
- } |
|
| 251 |
- var id64 int64 |
|
| 252 |
- switch k {
|
|
| 253 |
- case paxPath: |
|
| 254 |
- hdr.Name = v |
|
| 255 |
- case paxLinkpath: |
|
| 256 |
- hdr.Linkname = v |
|
| 257 |
- case paxUname: |
|
| 258 |
- hdr.Uname = v |
|
| 259 |
- case paxGname: |
|
| 260 |
- hdr.Gname = v |
|
| 261 |
- case paxUid: |
|
| 262 |
- id64, err = strconv.ParseInt(v, 10, 64) |
|
| 263 |
- hdr.Uid = int(id64) // Integer overflow possible |
|
| 264 |
- case paxGid: |
|
| 265 |
- id64, err = strconv.ParseInt(v, 10, 64) |
|
| 266 |
- hdr.Gid = int(id64) // Integer overflow possible |
|
| 267 |
- case paxAtime: |
|
| 268 |
- hdr.AccessTime, err = parsePAXTime(v) |
|
| 269 |
- case paxMtime: |
|
| 270 |
- hdr.ModTime, err = parsePAXTime(v) |
|
| 271 |
- case paxCtime: |
|
| 272 |
- hdr.ChangeTime, err = parsePAXTime(v) |
|
| 273 |
- case paxSize: |
|
| 274 |
- hdr.Size, err = strconv.ParseInt(v, 10, 64) |
|
| 275 |
- default: |
|
| 276 |
- if strings.HasPrefix(k, paxSchilyXattr) {
|
|
| 277 |
- if hdr.Xattrs == nil {
|
|
| 278 |
- hdr.Xattrs = make(map[string]string) |
|
| 279 |
- } |
|
| 280 |
- hdr.Xattrs[k[len(paxSchilyXattr):]] = v |
|
| 281 |
- } |
|
| 282 |
- } |
|
| 283 |
- if err != nil {
|
|
| 284 |
- return ErrHeader |
|
| 285 |
- } |
|
| 286 |
- } |
|
| 287 |
- hdr.PAXRecords = paxHdrs |
|
| 288 |
- return nil |
|
| 289 |
-} |
|
| 290 |
- |
|
| 291 |
-// parsePAX parses PAX headers. |
|
| 292 |
-// If an extended header (type 'x') is invalid, ErrHeader is returned |
|
| 293 |
-func parsePAX(r io.Reader) (map[string]string, error) {
|
|
| 294 |
- buf, err := ioutil.ReadAll(r) |
|
| 295 |
- if err != nil {
|
|
| 296 |
- return nil, err |
|
| 297 |
- } |
|
| 298 |
- sbuf := string(buf) |
|
| 299 |
- |
|
| 300 |
- // For GNU PAX sparse format 0.0 support. |
|
| 301 |
- // This function transforms the sparse format 0.0 headers into format 0.1 |
|
| 302 |
- // headers since 0.0 headers were not PAX compliant. |
|
| 303 |
- var sparseMap []string |
|
| 304 |
- |
|
| 305 |
- paxHdrs := make(map[string]string) |
|
| 306 |
- for len(sbuf) > 0 {
|
|
| 307 |
- key, value, residual, err := parsePAXRecord(sbuf) |
|
| 308 |
- if err != nil {
|
|
| 309 |
- return nil, ErrHeader |
|
| 310 |
- } |
|
| 311 |
- sbuf = residual |
|
| 312 |
- |
|
| 313 |
- switch key {
|
|
| 314 |
- case paxGNUSparseOffset, paxGNUSparseNumBytes: |
|
| 315 |
- // Validate sparse header order and value. |
|
| 316 |
- if (len(sparseMap)%2 == 0 && key != paxGNUSparseOffset) || |
|
| 317 |
- (len(sparseMap)%2 == 1 && key != paxGNUSparseNumBytes) || |
|
| 318 |
- strings.Contains(value, ",") {
|
|
| 319 |
- return nil, ErrHeader |
|
| 320 |
- } |
|
| 321 |
- sparseMap = append(sparseMap, value) |
|
| 322 |
- default: |
|
| 323 |
- paxHdrs[key] = value |
|
| 324 |
- } |
|
| 325 |
- } |
|
| 326 |
- if len(sparseMap) > 0 {
|
|
| 327 |
- paxHdrs[paxGNUSparseMap] = strings.Join(sparseMap, ",") |
|
| 328 |
- } |
|
| 329 |
- return paxHdrs, nil |
|
| 330 |
-} |
|
| 331 |
- |
|
| 332 |
-// readHeader reads the next block header and assumes that the underlying reader |
|
| 333 |
-// is already aligned to a block boundary. It returns the raw block of the |
|
| 334 |
-// header in case further processing is required. |
|
| 335 |
-// |
|
| 336 |
-// The err will be set to io.EOF only when one of the following occurs: |
|
| 337 |
-// * Exactly 0 bytes are read and EOF is hit. |
|
| 338 |
-// * Exactly 1 block of zeros is read and EOF is hit. |
|
| 339 |
-// * At least 2 blocks of zeros are read. |
|
| 340 |
-func (tr *Reader) readHeader() (*Header, *block, error) {
|
|
| 341 |
- // Two blocks of zero bytes marks the end of the archive. |
|
| 342 |
- if _, err := io.ReadFull(tr.r, tr.blk[:]); err != nil {
|
|
| 343 |
- return nil, nil, err // EOF is okay here; exactly 0 bytes read |
|
| 344 |
- } |
|
| 345 |
- if bytes.Equal(tr.blk[:], zeroBlock[:]) {
|
|
| 346 |
- if _, err := io.ReadFull(tr.r, tr.blk[:]); err != nil {
|
|
| 347 |
- return nil, nil, err // EOF is okay here; exactly 1 block of zeros read |
|
| 348 |
- } |
|
| 349 |
- if bytes.Equal(tr.blk[:], zeroBlock[:]) {
|
|
| 350 |
- return nil, nil, io.EOF // normal EOF; exactly 2 block of zeros read |
|
| 351 |
- } |
|
| 352 |
- return nil, nil, ErrHeader // Zero block and then non-zero block |
|
| 353 |
- } |
|
| 354 |
- |
|
| 355 |
- // Verify the header matches a known format. |
|
| 356 |
- format := tr.blk.GetFormat() |
|
| 357 |
- if format == FormatUnknown {
|
|
| 358 |
- return nil, nil, ErrHeader |
|
| 359 |
- } |
|
| 360 |
- |
|
| 361 |
- var p parser |
|
| 362 |
- hdr := new(Header) |
|
| 363 |
- |
|
| 364 |
- // Unpack the V7 header. |
|
| 365 |
- v7 := tr.blk.V7() |
|
| 366 |
- hdr.Typeflag = v7.TypeFlag()[0] |
|
| 367 |
- hdr.Name = p.parseString(v7.Name()) |
|
| 368 |
- hdr.Linkname = p.parseString(v7.LinkName()) |
|
| 369 |
- hdr.Size = p.parseNumeric(v7.Size()) |
|
| 370 |
- hdr.Mode = p.parseNumeric(v7.Mode()) |
|
| 371 |
- hdr.Uid = int(p.parseNumeric(v7.UID())) |
|
| 372 |
- hdr.Gid = int(p.parseNumeric(v7.GID())) |
|
| 373 |
- hdr.ModTime = time.Unix(p.parseNumeric(v7.ModTime()), 0) |
|
| 374 |
- |
|
| 375 |
- // Unpack format specific fields. |
|
| 376 |
- if format > formatV7 {
|
|
| 377 |
- ustar := tr.blk.USTAR() |
|
| 378 |
- hdr.Uname = p.parseString(ustar.UserName()) |
|
| 379 |
- hdr.Gname = p.parseString(ustar.GroupName()) |
|
| 380 |
- hdr.Devmajor = p.parseNumeric(ustar.DevMajor()) |
|
| 381 |
- hdr.Devminor = p.parseNumeric(ustar.DevMinor()) |
|
| 382 |
- |
|
| 383 |
- var prefix string |
|
| 384 |
- switch {
|
|
| 385 |
- case format.has(FormatUSTAR | FormatPAX): |
|
| 386 |
- hdr.Format = format |
|
| 387 |
- ustar := tr.blk.USTAR() |
|
| 388 |
- prefix = p.parseString(ustar.Prefix()) |
|
| 389 |
- |
|
| 390 |
- // For Format detection, check if block is properly formatted since |
|
| 391 |
- // the parser is more liberal than what USTAR actually permits. |
|
| 392 |
- notASCII := func(r rune) bool { return r >= 0x80 }
|
|
| 393 |
- if bytes.IndexFunc(tr.blk[:], notASCII) >= 0 {
|
|
| 394 |
- hdr.Format = FormatUnknown // Non-ASCII characters in block. |
|
| 395 |
- } |
|
| 396 |
- nul := func(b []byte) bool { return int(b[len(b)-1]) == 0 }
|
|
| 397 |
- if !(nul(v7.Size()) && nul(v7.Mode()) && nul(v7.UID()) && nul(v7.GID()) && |
|
| 398 |
- nul(v7.ModTime()) && nul(ustar.DevMajor()) && nul(ustar.DevMinor())) {
|
|
| 399 |
- hdr.Format = FormatUnknown // Numeric fields must end in NUL |
|
| 400 |
- } |
|
| 401 |
- case format.has(formatSTAR): |
|
| 402 |
- star := tr.blk.STAR() |
|
| 403 |
- prefix = p.parseString(star.Prefix()) |
|
| 404 |
- hdr.AccessTime = time.Unix(p.parseNumeric(star.AccessTime()), 0) |
|
| 405 |
- hdr.ChangeTime = time.Unix(p.parseNumeric(star.ChangeTime()), 0) |
|
| 406 |
- case format.has(FormatGNU): |
|
| 407 |
- hdr.Format = format |
|
| 408 |
- var p2 parser |
|
| 409 |
- gnu := tr.blk.GNU() |
|
| 410 |
- if b := gnu.AccessTime(); b[0] != 0 {
|
|
| 411 |
- hdr.AccessTime = time.Unix(p2.parseNumeric(b), 0) |
|
| 412 |
- } |
|
| 413 |
- if b := gnu.ChangeTime(); b[0] != 0 {
|
|
| 414 |
- hdr.ChangeTime = time.Unix(p2.parseNumeric(b), 0) |
|
| 415 |
- } |
|
| 416 |
- |
|
| 417 |
- // Prior to Go1.8, the Writer had a bug where it would output |
|
| 418 |
- // an invalid tar file in certain rare situations because the logic |
|
| 419 |
- // incorrectly believed that the old GNU format had a prefix field. |
|
| 420 |
- // This is wrong and leads to an output file that mangles the |
|
| 421 |
- // atime and ctime fields, which are often left unused. |
|
| 422 |
- // |
|
| 423 |
- // In order to continue reading tar files created by former, buggy |
|
| 424 |
- // versions of Go, we skeptically parse the atime and ctime fields. |
|
| 425 |
- // If we are unable to parse them and the prefix field looks like |
|
| 426 |
- // an ASCII string, then we fallback on the pre-Go1.8 behavior |
|
| 427 |
- // of treating these fields as the USTAR prefix field. |
|
| 428 |
- // |
|
| 429 |
- // Note that this will not use the fallback logic for all possible |
|
| 430 |
- // files generated by a pre-Go1.8 toolchain. If the generated file |
|
| 431 |
- // happened to have a prefix field that parses as valid |
|
| 432 |
- // atime and ctime fields (e.g., when they are valid octal strings), |
|
| 433 |
- // then it is impossible to distinguish between an valid GNU file |
|
| 434 |
- // and an invalid pre-Go1.8 file. |
|
| 435 |
- // |
|
| 436 |
- // See https://golang.org/issues/12594 |
|
| 437 |
- // See https://golang.org/issues/21005 |
|
| 438 |
- if p2.err != nil {
|
|
| 439 |
- hdr.AccessTime, hdr.ChangeTime = time.Time{}, time.Time{}
|
|
| 440 |
- ustar := tr.blk.USTAR() |
|
| 441 |
- if s := p.parseString(ustar.Prefix()); isASCII(s) {
|
|
| 442 |
- prefix = s |
|
| 443 |
- } |
|
| 444 |
- hdr.Format = FormatUnknown // Buggy file is not GNU |
|
| 445 |
- } |
|
| 446 |
- } |
|
| 447 |
- if len(prefix) > 0 {
|
|
| 448 |
- hdr.Name = prefix + "/" + hdr.Name |
|
| 449 |
- } |
|
| 450 |
- } |
|
| 451 |
- return hdr, &tr.blk, p.err |
|
| 452 |
-} |
|
| 453 |
- |
|
| 454 |
-// readOldGNUSparseMap reads the sparse map from the old GNU sparse format. |
|
| 455 |
-// The sparse map is stored in the tar header if it's small enough. |
|
| 456 |
-// If it's larger than four entries, then one or more extension headers are used |
|
| 457 |
-// to store the rest of the sparse map. |
|
| 458 |
-// |
|
| 459 |
-// The Header.Size does not reflect the size of any extended headers used. |
|
| 460 |
-// Thus, this function will read from the raw io.Reader to fetch extra headers. |
|
| 461 |
-// This method mutates blk in the process. |
|
| 462 |
-func (tr *Reader) readOldGNUSparseMap(hdr *Header, blk *block) (sparseDatas, error) {
|
|
| 463 |
- // Make sure that the input format is GNU. |
|
| 464 |
- // Unfortunately, the STAR format also has a sparse header format that uses |
|
| 465 |
- // the same type flag but has a completely different layout. |
|
| 466 |
- if blk.GetFormat() != FormatGNU {
|
|
| 467 |
- return nil, ErrHeader |
|
| 468 |
- } |
|
| 469 |
- hdr.Format.mayOnlyBe(FormatGNU) |
|
| 470 |
- |
|
| 471 |
- var p parser |
|
| 472 |
- hdr.Size = p.parseNumeric(blk.GNU().RealSize()) |
|
| 473 |
- if p.err != nil {
|
|
| 474 |
- return nil, p.err |
|
| 475 |
- } |
|
| 476 |
- s := blk.GNU().Sparse() |
|
| 477 |
- spd := make(sparseDatas, 0, s.MaxEntries()) |
|
| 478 |
- for {
|
|
| 479 |
- for i := 0; i < s.MaxEntries(); i++ {
|
|
| 480 |
- // This termination condition is identical to GNU and BSD tar. |
|
| 481 |
- if s.Entry(i).Offset()[0] == 0x00 {
|
|
| 482 |
- break // Don't return, need to process extended headers (even if empty) |
|
| 483 |
- } |
|
| 484 |
- offset := p.parseNumeric(s.Entry(i).Offset()) |
|
| 485 |
- length := p.parseNumeric(s.Entry(i).Length()) |
|
| 486 |
- if p.err != nil {
|
|
| 487 |
- return nil, p.err |
|
| 488 |
- } |
|
| 489 |
- spd = append(spd, sparseEntry{Offset: offset, Length: length})
|
|
| 490 |
- } |
|
| 491 |
- |
|
| 492 |
- if s.IsExtended()[0] > 0 {
|
|
| 493 |
- // There are more entries. Read an extension header and parse its entries. |
|
| 494 |
- if _, err := mustReadFull(tr.r, blk[:]); err != nil {
|
|
| 495 |
- return nil, err |
|
| 496 |
- } |
|
| 497 |
- s = blk.Sparse() |
|
| 498 |
- continue |
|
| 499 |
- } |
|
| 500 |
- return spd, nil // Done |
|
| 501 |
- } |
|
| 502 |
-} |
|
| 503 |
- |
|
| 504 |
-// readGNUSparseMap1x0 reads the sparse map as stored in GNU's PAX sparse format |
|
| 505 |
-// version 1.0. The format of the sparse map consists of a series of |
|
| 506 |
-// newline-terminated numeric fields. The first field is the number of entries |
|
| 507 |
-// and is always present. Following this are the entries, consisting of two |
|
| 508 |
-// fields (offset, length). This function must stop reading at the end |
|
| 509 |
-// boundary of the block containing the last newline. |
|
| 510 |
-// |
|
| 511 |
-// Note that the GNU manual says that numeric values should be encoded in octal |
|
| 512 |
-// format. However, the GNU tar utility itself outputs these values in decimal. |
|
| 513 |
-// As such, this library treats values as being encoded in decimal. |
|
| 514 |
-func readGNUSparseMap1x0(r io.Reader) (sparseDatas, error) {
|
|
| 515 |
- var ( |
|
| 516 |
- cntNewline int64 |
|
| 517 |
- buf bytes.Buffer |
|
| 518 |
- blk block |
|
| 519 |
- ) |
|
| 520 |
- |
|
| 521 |
- // feedTokens copies data in blocks from r into buf until there are |
|
| 522 |
- // at least cnt newlines in buf. It will not read more blocks than needed. |
|
| 523 |
- feedTokens := func(n int64) error {
|
|
| 524 |
- for cntNewline < n {
|
|
| 525 |
- if _, err := mustReadFull(r, blk[:]); err != nil {
|
|
| 526 |
- return err |
|
| 527 |
- } |
|
| 528 |
- buf.Write(blk[:]) |
|
| 529 |
- for _, c := range blk {
|
|
| 530 |
- if c == '\n' {
|
|
| 531 |
- cntNewline++ |
|
| 532 |
- } |
|
| 533 |
- } |
|
| 534 |
- } |
|
| 535 |
- return nil |
|
| 536 |
- } |
|
| 537 |
- |
|
| 538 |
- // nextToken gets the next token delimited by a newline. This assumes that |
|
| 539 |
- // at least one newline exists in the buffer. |
|
| 540 |
- nextToken := func() string {
|
|
| 541 |
- cntNewline-- |
|
| 542 |
- tok, _ := buf.ReadString('\n')
|
|
| 543 |
- return strings.TrimRight(tok, "\n") |
|
| 544 |
- } |
|
| 545 |
- |
|
| 546 |
- // Parse for the number of entries. |
|
| 547 |
- // Use integer overflow resistant math to check this. |
|
| 548 |
- if err := feedTokens(1); err != nil {
|
|
| 549 |
- return nil, err |
|
| 550 |
- } |
|
| 551 |
- numEntries, err := strconv.ParseInt(nextToken(), 10, 0) // Intentionally parse as native int |
|
| 552 |
- if err != nil || numEntries < 0 || int(2*numEntries) < int(numEntries) {
|
|
| 553 |
- return nil, ErrHeader |
|
| 554 |
- } |
|
| 555 |
- |
|
| 556 |
- // Parse for all member entries. |
|
| 557 |
- // numEntries is trusted after this since a potential attacker must have |
|
| 558 |
- // committed resources proportional to what this library used. |
|
| 559 |
- if err := feedTokens(2 * numEntries); err != nil {
|
|
| 560 |
- return nil, err |
|
| 561 |
- } |
|
| 562 |
- spd := make(sparseDatas, 0, numEntries) |
|
| 563 |
- for i := int64(0); i < numEntries; i++ {
|
|
| 564 |
- offset, err1 := strconv.ParseInt(nextToken(), 10, 64) |
|
| 565 |
- length, err2 := strconv.ParseInt(nextToken(), 10, 64) |
|
| 566 |
- if err1 != nil || err2 != nil {
|
|
| 567 |
- return nil, ErrHeader |
|
| 568 |
- } |
|
| 569 |
- spd = append(spd, sparseEntry{Offset: offset, Length: length})
|
|
| 570 |
- } |
|
| 571 |
- return spd, nil |
|
| 572 |
-} |
|
| 573 |
- |
|
| 574 |
-// readGNUSparseMap0x1 reads the sparse map as stored in GNU's PAX sparse format |
|
| 575 |
-// version 0.1. The sparse map is stored in the PAX headers. |
|
| 576 |
-func readGNUSparseMap0x1(paxHdrs map[string]string) (sparseDatas, error) {
|
|
| 577 |
- // Get number of entries. |
|
| 578 |
- // Use integer overflow resistant math to check this. |
|
| 579 |
- numEntriesStr := paxHdrs[paxGNUSparseNumBlocks] |
|
| 580 |
- numEntries, err := strconv.ParseInt(numEntriesStr, 10, 0) // Intentionally parse as native int |
|
| 581 |
- if err != nil || numEntries < 0 || int(2*numEntries) < int(numEntries) {
|
|
| 582 |
- return nil, ErrHeader |
|
| 583 |
- } |
|
| 584 |
- |
|
| 585 |
- // There should be two numbers in sparseMap for each entry. |
|
| 586 |
- sparseMap := strings.Split(paxHdrs[paxGNUSparseMap], ",") |
|
| 587 |
- if len(sparseMap) == 1 && sparseMap[0] == "" {
|
|
| 588 |
- sparseMap = sparseMap[:0] |
|
| 589 |
- } |
|
| 590 |
- if int64(len(sparseMap)) != 2*numEntries {
|
|
| 591 |
- return nil, ErrHeader |
|
| 592 |
- } |
|
| 593 |
- |
|
| 594 |
- // Loop through the entries in the sparse map. |
|
| 595 |
- // numEntries is trusted now. |
|
| 596 |
- spd := make(sparseDatas, 0, numEntries) |
|
| 597 |
- for len(sparseMap) >= 2 {
|
|
| 598 |
- offset, err1 := strconv.ParseInt(sparseMap[0], 10, 64) |
|
| 599 |
- length, err2 := strconv.ParseInt(sparseMap[1], 10, 64) |
|
| 600 |
- if err1 != nil || err2 != nil {
|
|
| 601 |
- return nil, ErrHeader |
|
| 602 |
- } |
|
| 603 |
- spd = append(spd, sparseEntry{Offset: offset, Length: length})
|
|
| 604 |
- sparseMap = sparseMap[2:] |
|
| 605 |
- } |
|
| 606 |
- return spd, nil |
|
| 607 |
-} |
|
| 608 |
- |
|
| 609 |
-// Read reads from the current file in the tar archive. |
|
| 610 |
-// It returns (0, io.EOF) when it reaches the end of that file, |
|
| 611 |
-// until Next is called to advance to the next file. |
|
| 612 |
-// |
|
| 613 |
-// If the current file is sparse, then the regions marked as a hole |
|
| 614 |
-// are read back as NUL-bytes. |
|
| 615 |
-// |
|
| 616 |
-// Calling Read on special types like TypeLink, TypeSymlink, TypeChar, |
|
| 617 |
-// TypeBlock, TypeDir, and TypeFifo returns (0, io.EOF) regardless of what |
|
| 618 |
-// the Header.Size claims. |
|
| 619 |
-func (tr *Reader) Read(b []byte) (int, error) {
|
|
| 620 |
- if tr.err != nil {
|
|
| 621 |
- return 0, tr.err |
|
| 622 |
- } |
|
| 623 |
- n, err := tr.curr.Read(b) |
|
| 624 |
- if err != nil && err != io.EOF {
|
|
| 625 |
- tr.err = err |
|
| 626 |
- } |
|
| 627 |
- return n, err |
|
| 628 |
-} |
|
| 629 |
- |
|
| 630 |
-// writeTo writes the content of the current file to w. |
|
| 631 |
-// The bytes written matches the number of remaining bytes in the current file. |
|
| 632 |
-// |
|
| 633 |
-// If the current file is sparse and w is an io.WriteSeeker, |
|
| 634 |
-// then writeTo uses Seek to skip past holes defined in Header.SparseHoles, |
|
| 635 |
-// assuming that skipped regions are filled with NULs. |
|
| 636 |
-// This always writes the last byte to ensure w is the right size. |
|
| 637 |
-// |
|
| 638 |
-// TODO(dsnet): Re-export this when adding sparse file support. |
|
| 639 |
-// See https://golang.org/issue/22735 |
|
| 640 |
-func (tr *Reader) writeTo(w io.Writer) (int64, error) {
|
|
| 641 |
- if tr.err != nil {
|
|
| 642 |
- return 0, tr.err |
|
| 643 |
- } |
|
| 644 |
- n, err := tr.curr.WriteTo(w) |
|
| 645 |
- if err != nil {
|
|
| 646 |
- tr.err = err |
|
| 647 |
- } |
|
| 648 |
- return n, err |
|
| 649 |
-} |
|
| 650 |
- |
|
| 651 |
-// regFileReader is a fileReader for reading data from a regular file entry. |
|
| 652 |
-type regFileReader struct {
|
|
| 653 |
- r io.Reader // Underlying Reader |
|
| 654 |
- nb int64 // Number of remaining bytes to read |
|
| 655 |
-} |
|
| 656 |
- |
|
| 657 |
-func (fr *regFileReader) Read(b []byte) (n int, err error) {
|
|
| 658 |
- if int64(len(b)) > fr.nb {
|
|
| 659 |
- b = b[:fr.nb] |
|
| 660 |
- } |
|
| 661 |
- if len(b) > 0 {
|
|
| 662 |
- n, err = fr.r.Read(b) |
|
| 663 |
- fr.nb -= int64(n) |
|
| 664 |
- } |
|
| 665 |
- switch {
|
|
| 666 |
- case err == io.EOF && fr.nb > 0: |
|
| 667 |
- return n, io.ErrUnexpectedEOF |
|
| 668 |
- case err == nil && fr.nb == 0: |
|
| 669 |
- return n, io.EOF |
|
| 670 |
- default: |
|
| 671 |
- return n, err |
|
| 672 |
- } |
|
| 673 |
-} |
|
| 674 |
- |
|
| 675 |
-func (fr *regFileReader) WriteTo(w io.Writer) (int64, error) {
|
|
| 676 |
- return io.Copy(w, struct{ io.Reader }{fr})
|
|
| 677 |
-} |
|
| 678 |
- |
|
| 679 |
-func (fr regFileReader) LogicalRemaining() int64 {
|
|
| 680 |
- return fr.nb |
|
| 681 |
-} |
|
| 682 |
- |
|
| 683 |
-func (fr regFileReader) PhysicalRemaining() int64 {
|
|
| 684 |
- return fr.nb |
|
| 685 |
-} |
|
| 686 |
- |
|
| 687 |
-// sparseFileReader is a fileReader for reading data from a sparse file entry. |
|
| 688 |
-type sparseFileReader struct {
|
|
| 689 |
- fr fileReader // Underlying fileReader |
|
| 690 |
- sp sparseHoles // Normalized list of sparse holes |
|
| 691 |
- pos int64 // Current position in sparse file |
|
| 692 |
-} |
|
| 693 |
- |
|
| 694 |
-func (sr *sparseFileReader) Read(b []byte) (n int, err error) {
|
|
| 695 |
- finished := int64(len(b)) >= sr.LogicalRemaining() |
|
| 696 |
- if finished {
|
|
| 697 |
- b = b[:sr.LogicalRemaining()] |
|
| 698 |
- } |
|
| 699 |
- |
|
| 700 |
- b0 := b |
|
| 701 |
- endPos := sr.pos + int64(len(b)) |
|
| 702 |
- for endPos > sr.pos && err == nil {
|
|
| 703 |
- var nf int // Bytes read in fragment |
|
| 704 |
- holeStart, holeEnd := sr.sp[0].Offset, sr.sp[0].endOffset() |
|
| 705 |
- if sr.pos < holeStart { // In a data fragment
|
|
| 706 |
- bf := b[:min(int64(len(b)), holeStart-sr.pos)] |
|
| 707 |
- nf, err = tryReadFull(sr.fr, bf) |
|
| 708 |
- } else { // In a hole fragment
|
|
| 709 |
- bf := b[:min(int64(len(b)), holeEnd-sr.pos)] |
|
| 710 |
- nf, err = tryReadFull(zeroReader{}, bf)
|
|
| 711 |
- } |
|
| 712 |
- b = b[nf:] |
|
| 713 |
- sr.pos += int64(nf) |
|
| 714 |
- if sr.pos >= holeEnd && len(sr.sp) > 1 {
|
|
| 715 |
- sr.sp = sr.sp[1:] // Ensure last fragment always remains |
|
| 716 |
- } |
|
| 717 |
- } |
|
| 718 |
- |
|
| 719 |
- n = len(b0) - len(b) |
|
| 720 |
- switch {
|
|
| 721 |
- case err == io.EOF: |
|
| 722 |
- return n, errMissData // Less data in dense file than sparse file |
|
| 723 |
- case err != nil: |
|
| 724 |
- return n, err |
|
| 725 |
- case sr.LogicalRemaining() == 0 && sr.PhysicalRemaining() > 0: |
|
| 726 |
- return n, errUnrefData // More data in dense file than sparse file |
|
| 727 |
- case finished: |
|
| 728 |
- return n, io.EOF |
|
| 729 |
- default: |
|
| 730 |
- return n, nil |
|
| 731 |
- } |
|
| 732 |
-} |
|
| 733 |
- |
|
| 734 |
-func (sr *sparseFileReader) WriteTo(w io.Writer) (n int64, err error) {
|
|
| 735 |
- ws, ok := w.(io.WriteSeeker) |
|
| 736 |
- if ok {
|
|
| 737 |
- if _, err := ws.Seek(0, io.SeekCurrent); err != nil {
|
|
| 738 |
- ok = false // Not all io.Seeker can really seek |
|
| 739 |
- } |
|
| 740 |
- } |
|
| 741 |
- if !ok {
|
|
| 742 |
- return io.Copy(w, struct{ io.Reader }{sr})
|
|
| 743 |
- } |
|
| 744 |
- |
|
| 745 |
- var writeLastByte bool |
|
| 746 |
- pos0 := sr.pos |
|
| 747 |
- for sr.LogicalRemaining() > 0 && !writeLastByte && err == nil {
|
|
| 748 |
- var nf int64 // Size of fragment |
|
| 749 |
- holeStart, holeEnd := sr.sp[0].Offset, sr.sp[0].endOffset() |
|
| 750 |
- if sr.pos < holeStart { // In a data fragment
|
|
| 751 |
- nf = holeStart - sr.pos |
|
| 752 |
- nf, err = io.CopyN(ws, sr.fr, nf) |
|
| 753 |
- } else { // In a hole fragment
|
|
| 754 |
- nf = holeEnd - sr.pos |
|
| 755 |
- if sr.PhysicalRemaining() == 0 {
|
|
| 756 |
- writeLastByte = true |
|
| 757 |
- nf-- |
|
| 758 |
- } |
|
| 759 |
- _, err = ws.Seek(nf, io.SeekCurrent) |
|
| 760 |
- } |
|
| 761 |
- sr.pos += nf |
|
| 762 |
- if sr.pos >= holeEnd && len(sr.sp) > 1 {
|
|
| 763 |
- sr.sp = sr.sp[1:] // Ensure last fragment always remains |
|
| 764 |
- } |
|
| 765 |
- } |
|
| 766 |
- |
|
| 767 |
- // If the last fragment is a hole, then seek to 1-byte before EOF, and |
|
| 768 |
- // write a single byte to ensure the file is the right size. |
|
| 769 |
- if writeLastByte && err == nil {
|
|
| 770 |
- _, err = ws.Write([]byte{0})
|
|
| 771 |
- sr.pos++ |
|
| 772 |
- } |
|
| 773 |
- |
|
| 774 |
- n = sr.pos - pos0 |
|
| 775 |
- switch {
|
|
| 776 |
- case err == io.EOF: |
|
| 777 |
- return n, errMissData // Less data in dense file than sparse file |
|
| 778 |
- case err != nil: |
|
| 779 |
- return n, err |
|
| 780 |
- case sr.LogicalRemaining() == 0 && sr.PhysicalRemaining() > 0: |
|
| 781 |
- return n, errUnrefData // More data in dense file than sparse file |
|
| 782 |
- default: |
|
| 783 |
- return n, nil |
|
| 784 |
- } |
|
| 785 |
-} |
|
| 786 |
- |
|
| 787 |
-func (sr sparseFileReader) LogicalRemaining() int64 {
|
|
| 788 |
- return sr.sp[len(sr.sp)-1].endOffset() - sr.pos |
|
| 789 |
-} |
|
| 790 |
-func (sr sparseFileReader) PhysicalRemaining() int64 {
|
|
| 791 |
- return sr.fr.PhysicalRemaining() |
|
| 792 |
-} |
|
| 793 |
- |
|
| 794 |
-type zeroReader struct{}
|
|
| 795 |
- |
|
| 796 |
-func (zeroReader) Read(b []byte) (int, error) {
|
|
| 797 |
- for i := range b {
|
|
| 798 |
- b[i] = 0 |
|
| 799 |
- } |
|
| 800 |
- return len(b), nil |
|
| 801 |
-} |
|
| 802 |
- |
|
| 803 |
-// mustReadFull is like io.ReadFull except it returns |
|
| 804 |
-// io.ErrUnexpectedEOF when io.EOF is hit before len(b) bytes are read. |
|
| 805 |
-func mustReadFull(r io.Reader, b []byte) (int, error) {
|
|
| 806 |
- n, err := tryReadFull(r, b) |
|
| 807 |
- if err == io.EOF {
|
|
| 808 |
- err = io.ErrUnexpectedEOF |
|
| 809 |
- } |
|
| 810 |
- return n, err |
|
| 811 |
-} |
|
| 812 |
- |
|
| 813 |
-// tryReadFull is like io.ReadFull except it returns |
|
| 814 |
-// io.EOF when it is hit before len(b) bytes are read. |
|
| 815 |
-func tryReadFull(r io.Reader, b []byte) (n int, err error) {
|
|
| 816 |
- for len(b) > n && err == nil {
|
|
| 817 |
- var nn int |
|
| 818 |
- nn, err = r.Read(b[n:]) |
|
| 819 |
- n += nn |
|
| 820 |
- } |
|
| 821 |
- if len(b) == n && err == io.EOF {
|
|
| 822 |
- err = nil |
|
| 823 |
- } |
|
| 824 |
- return n, err |
|
| 825 |
-} |
|
| 826 |
- |
|
| 827 |
-// discard skips n bytes in r, reporting an error if unable to do so. |
|
| 828 |
-func discard(r io.Reader, n int64) error {
|
|
| 829 |
- // If possible, Seek to the last byte before the end of the data section. |
|
| 830 |
- // Do this because Seek is often lazy about reporting errors; this will mask |
|
| 831 |
- // the fact that the stream may be truncated. We can rely on the |
|
| 832 |
- // io.CopyN done shortly afterwards to trigger any IO errors. |
|
| 833 |
- var seekSkipped int64 // Number of bytes skipped via Seek |
|
| 834 |
- if sr, ok := r.(io.Seeker); ok && n > 1 {
|
|
| 835 |
- // Not all io.Seeker can actually Seek. For example, os.Stdin implements |
|
| 836 |
- // io.Seeker, but calling Seek always returns an error and performs |
|
| 837 |
- // no action. Thus, we try an innocent seek to the current position |
|
| 838 |
- // to see if Seek is really supported. |
|
| 839 |
- pos1, err := sr.Seek(0, io.SeekCurrent) |
|
| 840 |
- if pos1 >= 0 && err == nil {
|
|
| 841 |
- // Seek seems supported, so perform the real Seek. |
|
| 842 |
- pos2, err := sr.Seek(n-1, io.SeekCurrent) |
|
| 843 |
- if pos2 < 0 || err != nil {
|
|
| 844 |
- return err |
|
| 845 |
- } |
|
| 846 |
- seekSkipped = pos2 - pos1 |
|
| 847 |
- } |
|
| 848 |
- } |
|
| 849 |
- |
|
| 850 |
- copySkipped, err := io.CopyN(ioutil.Discard, r, n-seekSkipped) |
|
| 851 |
- if err == io.EOF && seekSkipped+copySkipped < n {
|
|
| 852 |
- err = io.ErrUnexpectedEOF |
|
| 853 |
- } |
|
| 854 |
- return err |
|
| 855 |
-} |
| 856 | 1 |
deleted file mode 100644 |
| ... | ... |
@@ -1,20 +0,0 @@ |
| 1 |
-// Copyright 2012 The Go Authors. All rights reserved. |
|
| 2 |
-// Use of this source code is governed by a BSD-style |
|
| 3 |
-// license that can be found in the LICENSE file. |
|
| 4 |
- |
|
| 5 |
-// +build linux dragonfly openbsd solaris |
|
| 6 |
- |
|
| 7 |
-package tar |
|
| 8 |
- |
|
| 9 |
-import ( |
|
| 10 |
- "syscall" |
|
| 11 |
- "time" |
|
| 12 |
-) |
|
| 13 |
- |
|
| 14 |
-func statAtime(st *syscall.Stat_t) time.Time {
|
|
| 15 |
- return time.Unix(st.Atim.Unix()) |
|
| 16 |
-} |
|
| 17 |
- |
|
| 18 |
-func statCtime(st *syscall.Stat_t) time.Time {
|
|
| 19 |
- return time.Unix(st.Ctim.Unix()) |
|
| 20 |
-} |
| 21 | 1 |
deleted file mode 100644 |
| ... | ... |
@@ -1,20 +0,0 @@ |
| 1 |
-// Copyright 2012 The Go Authors. All rights reserved. |
|
| 2 |
-// Use of this source code is governed by a BSD-style |
|
| 3 |
-// license that can be found in the LICENSE file. |
|
| 4 |
- |
|
| 5 |
-// +build darwin freebsd netbsd |
|
| 6 |
- |
|
| 7 |
-package tar |
|
| 8 |
- |
|
| 9 |
-import ( |
|
| 10 |
- "syscall" |
|
| 11 |
- "time" |
|
| 12 |
-) |
|
| 13 |
- |
|
| 14 |
-func statAtime(st *syscall.Stat_t) time.Time {
|
|
| 15 |
- return time.Unix(st.Atimespec.Unix()) |
|
| 16 |
-} |
|
| 17 |
- |
|
| 18 |
-func statCtime(st *syscall.Stat_t) time.Time {
|
|
| 19 |
- return time.Unix(st.Ctimespec.Unix()) |
|
| 20 |
-} |
| 21 | 1 |
deleted file mode 100644 |
| ... | ... |
@@ -1,76 +0,0 @@ |
| 1 |
-// Copyright 2012 The Go Authors. All rights reserved. |
|
| 2 |
-// Use of this source code is governed by a BSD-style |
|
| 3 |
-// license that can be found in the LICENSE file. |
|
| 4 |
- |
|
| 5 |
-// +build linux darwin dragonfly freebsd openbsd netbsd solaris |
|
| 6 |
- |
|
| 7 |
-package tar |
|
| 8 |
- |
|
| 9 |
-import ( |
|
| 10 |
- "os" |
|
| 11 |
- "runtime" |
|
| 12 |
- "syscall" |
|
| 13 |
-) |
|
| 14 |
- |
|
| 15 |
-func init() {
|
|
| 16 |
- sysStat = statUnix |
|
| 17 |
-} |
|
| 18 |
- |
|
| 19 |
-func statUnix(fi os.FileInfo, h *Header) error {
|
|
| 20 |
- sys, ok := fi.Sys().(*syscall.Stat_t) |
|
| 21 |
- if !ok {
|
|
| 22 |
- return nil |
|
| 23 |
- } |
|
| 24 |
- h.Uid = int(sys.Uid) |
|
| 25 |
- h.Gid = int(sys.Gid) |
|
| 26 |
- |
|
| 27 |
- // TODO(bradfitz): populate username & group. os/user |
|
| 28 |
- // doesn't cache LookupId lookups, and lacks group |
|
| 29 |
- // lookup functions. |
|
| 30 |
- h.AccessTime = statAtime(sys) |
|
| 31 |
- h.ChangeTime = statCtime(sys) |
|
| 32 |
- |
|
| 33 |
- // Best effort at populating Devmajor and Devminor. |
|
| 34 |
- if h.Typeflag == TypeChar || h.Typeflag == TypeBlock {
|
|
| 35 |
- dev := uint64(sys.Rdev) // May be int32 or uint32 |
|
| 36 |
- switch runtime.GOOS {
|
|
| 37 |
- case "linux": |
|
| 38 |
- // Copied from golang.org/x/sys/unix/dev_linux.go. |
|
| 39 |
- major := uint32((dev & 0x00000000000fff00) >> 8) |
|
| 40 |
- major |= uint32((dev & 0xfffff00000000000) >> 32) |
|
| 41 |
- minor := uint32((dev & 0x00000000000000ff) >> 0) |
|
| 42 |
- minor |= uint32((dev & 0x00000ffffff00000) >> 12) |
|
| 43 |
- h.Devmajor, h.Devminor = int64(major), int64(minor) |
|
| 44 |
- case "darwin": |
|
| 45 |
- // Copied from golang.org/x/sys/unix/dev_darwin.go. |
|
| 46 |
- major := uint32((dev >> 24) & 0xff) |
|
| 47 |
- minor := uint32(dev & 0xffffff) |
|
| 48 |
- h.Devmajor, h.Devminor = int64(major), int64(minor) |
|
| 49 |
- case "dragonfly": |
|
| 50 |
- // Copied from golang.org/x/sys/unix/dev_dragonfly.go. |
|
| 51 |
- major := uint32((dev >> 8) & 0xff) |
|
| 52 |
- minor := uint32(dev & 0xffff00ff) |
|
| 53 |
- h.Devmajor, h.Devminor = int64(major), int64(minor) |
|
| 54 |
- case "freebsd": |
|
| 55 |
- // Copied from golang.org/x/sys/unix/dev_freebsd.go. |
|
| 56 |
- major := uint32((dev >> 8) & 0xff) |
|
| 57 |
- minor := uint32(dev & 0xffff00ff) |
|
| 58 |
- h.Devmajor, h.Devminor = int64(major), int64(minor) |
|
| 59 |
- case "netbsd": |
|
| 60 |
- // Copied from golang.org/x/sys/unix/dev_netbsd.go. |
|
| 61 |
- major := uint32((dev & 0x000fff00) >> 8) |
|
| 62 |
- minor := uint32((dev & 0x000000ff) >> 0) |
|
| 63 |
- minor |= uint32((dev & 0xfff00000) >> 12) |
|
| 64 |
- h.Devmajor, h.Devminor = int64(major), int64(minor) |
|
| 65 |
- case "openbsd": |
|
| 66 |
- // Copied from golang.org/x/sys/unix/dev_openbsd.go. |
|
| 67 |
- major := uint32((dev & 0x0000ff00) >> 8) |
|
| 68 |
- minor := uint32((dev & 0x000000ff) >> 0) |
|
| 69 |
- minor |= uint32((dev & 0xffff0000) >> 8) |
|
| 70 |
- h.Devmajor, h.Devminor = int64(major), int64(minor) |
|
| 71 |
- default: |
|
| 72 |
- // TODO: Implement solaris (see https://golang.org/issue/8106) |
|
| 73 |
- } |
|
| 74 |
- } |
|
| 75 |
- return nil |
|
| 76 |
-} |
| 77 | 1 |
deleted file mode 100644 |
| ... | ... |
@@ -1,326 +0,0 @@ |
| 1 |
-// Copyright 2016 The Go Authors. All rights reserved. |
|
| 2 |
-// Use of this source code is governed by a BSD-style |
|
| 3 |
-// license that can be found in the LICENSE file. |
|
| 4 |
- |
|
| 5 |
-package tar |
|
| 6 |
- |
|
| 7 |
-import ( |
|
| 8 |
- "bytes" |
|
| 9 |
- "fmt" |
|
| 10 |
- "strconv" |
|
| 11 |
- "strings" |
|
| 12 |
- "time" |
|
| 13 |
-) |
|
| 14 |
- |
|
| 15 |
-// hasNUL reports whether the NUL character exists within s. |
|
| 16 |
-func hasNUL(s string) bool {
|
|
| 17 |
- return strings.IndexByte(s, 0) >= 0 |
|
| 18 |
-} |
|
| 19 |
- |
|
| 20 |
-// isASCII reports whether the input is an ASCII C-style string. |
|
| 21 |
-func isASCII(s string) bool {
|
|
| 22 |
- for _, c := range s {
|
|
| 23 |
- if c >= 0x80 || c == 0x00 {
|
|
| 24 |
- return false |
|
| 25 |
- } |
|
| 26 |
- } |
|
| 27 |
- return true |
|
| 28 |
-} |
|
| 29 |
- |
|
| 30 |
-// toASCII converts the input to an ASCII C-style string. |
|
| 31 |
-// This a best effort conversion, so invalid characters are dropped. |
|
| 32 |
-func toASCII(s string) string {
|
|
| 33 |
- if isASCII(s) {
|
|
| 34 |
- return s |
|
| 35 |
- } |
|
| 36 |
- b := make([]byte, 0, len(s)) |
|
| 37 |
- for _, c := range s {
|
|
| 38 |
- if c < 0x80 && c != 0x00 {
|
|
| 39 |
- b = append(b, byte(c)) |
|
| 40 |
- } |
|
| 41 |
- } |
|
| 42 |
- return string(b) |
|
| 43 |
-} |
|
| 44 |
- |
|
| 45 |
-type parser struct {
|
|
| 46 |
- err error // Last error seen |
|
| 47 |
-} |
|
| 48 |
- |
|
| 49 |
-type formatter struct {
|
|
| 50 |
- err error // Last error seen |
|
| 51 |
-} |
|
| 52 |
- |
|
| 53 |
-// parseString parses bytes as a NUL-terminated C-style string. |
|
| 54 |
-// If a NUL byte is not found then the whole slice is returned as a string. |
|
| 55 |
-func (*parser) parseString(b []byte) string {
|
|
| 56 |
- if i := bytes.IndexByte(b, 0); i >= 0 {
|
|
| 57 |
- return string(b[:i]) |
|
| 58 |
- } |
|
| 59 |
- return string(b) |
|
| 60 |
-} |
|
| 61 |
- |
|
| 62 |
-// formatString copies s into b, NUL-terminating if possible. |
|
| 63 |
-func (f *formatter) formatString(b []byte, s string) {
|
|
| 64 |
- if len(s) > len(b) {
|
|
| 65 |
- f.err = ErrFieldTooLong |
|
| 66 |
- } |
|
| 67 |
- copy(b, s) |
|
| 68 |
- if len(s) < len(b) {
|
|
| 69 |
- b[len(s)] = 0 |
|
| 70 |
- } |
|
| 71 |
- |
|
| 72 |
- // Some buggy readers treat regular files with a trailing slash |
|
| 73 |
- // in the V7 path field as a directory even though the full path |
|
| 74 |
- // recorded elsewhere (e.g., via PAX record) contains no trailing slash. |
|
| 75 |
- if len(s) > len(b) && b[len(b)-1] == '/' {
|
|
| 76 |
- n := len(strings.TrimRight(s[:len(b)], "/")) |
|
| 77 |
- b[n] = 0 // Replace trailing slash with NUL terminator |
|
| 78 |
- } |
|
| 79 |
-} |
|
| 80 |
- |
|
| 81 |
-// fitsInBase256 reports whether x can be encoded into n bytes using base-256 |
|
| 82 |
-// encoding. Unlike octal encoding, base-256 encoding does not require that the |
|
| 83 |
-// string ends with a NUL character. Thus, all n bytes are available for output. |
|
| 84 |
-// |
|
| 85 |
-// If operating in binary mode, this assumes strict GNU binary mode; which means |
|
| 86 |
-// that the first byte can only be either 0x80 or 0xff. Thus, the first byte is |
|
| 87 |
-// equivalent to the sign bit in two's complement form. |
|
| 88 |
-func fitsInBase256(n int, x int64) bool {
|
|
| 89 |
- binBits := uint(n-1) * 8 |
|
| 90 |
- return n >= 9 || (x >= -1<<binBits && x < 1<<binBits) |
|
| 91 |
-} |
|
| 92 |
- |
|
| 93 |
-// parseNumeric parses the input as being encoded in either base-256 or octal. |
|
| 94 |
-// This function may return negative numbers. |
|
| 95 |
-// If parsing fails or an integer overflow occurs, err will be set. |
|
| 96 |
-func (p *parser) parseNumeric(b []byte) int64 {
|
|
| 97 |
- // Check for base-256 (binary) format first. |
|
| 98 |
- // If the first bit is set, then all following bits constitute a two's |
|
| 99 |
- // complement encoded number in big-endian byte order. |
|
| 100 |
- if len(b) > 0 && b[0]&0x80 != 0 {
|
|
| 101 |
- // Handling negative numbers relies on the following identity: |
|
| 102 |
- // -a-1 == ^a |
|
| 103 |
- // |
|
| 104 |
- // If the number is negative, we use an inversion mask to invert the |
|
| 105 |
- // data bytes and treat the value as an unsigned number. |
|
| 106 |
- var inv byte // 0x00 if positive or zero, 0xff if negative |
|
| 107 |
- if b[0]&0x40 != 0 {
|
|
| 108 |
- inv = 0xff |
|
| 109 |
- } |
|
| 110 |
- |
|
| 111 |
- var x uint64 |
|
| 112 |
- for i, c := range b {
|
|
| 113 |
- c ^= inv // Inverts c only if inv is 0xff, otherwise does nothing |
|
| 114 |
- if i == 0 {
|
|
| 115 |
- c &= 0x7f // Ignore signal bit in first byte |
|
| 116 |
- } |
|
| 117 |
- if (x >> 56) > 0 {
|
|
| 118 |
- p.err = ErrHeader // Integer overflow |
|
| 119 |
- return 0 |
|
| 120 |
- } |
|
| 121 |
- x = x<<8 | uint64(c) |
|
| 122 |
- } |
|
| 123 |
- if (x >> 63) > 0 {
|
|
| 124 |
- p.err = ErrHeader // Integer overflow |
|
| 125 |
- return 0 |
|
| 126 |
- } |
|
| 127 |
- if inv == 0xff {
|
|
| 128 |
- return ^int64(x) |
|
| 129 |
- } |
|
| 130 |
- return int64(x) |
|
| 131 |
- } |
|
| 132 |
- |
|
| 133 |
- // Normal case is base-8 (octal) format. |
|
| 134 |
- return p.parseOctal(b) |
|
| 135 |
-} |
|
| 136 |
- |
|
| 137 |
-// formatNumeric encodes x into b using base-8 (octal) encoding if possible. |
|
| 138 |
-// Otherwise it will attempt to use base-256 (binary) encoding. |
|
| 139 |
-func (f *formatter) formatNumeric(b []byte, x int64) {
|
|
| 140 |
- if fitsInOctal(len(b), x) {
|
|
| 141 |
- f.formatOctal(b, x) |
|
| 142 |
- return |
|
| 143 |
- } |
|
| 144 |
- |
|
| 145 |
- if fitsInBase256(len(b), x) {
|
|
| 146 |
- for i := len(b) - 1; i >= 0; i-- {
|
|
| 147 |
- b[i] = byte(x) |
|
| 148 |
- x >>= 8 |
|
| 149 |
- } |
|
| 150 |
- b[0] |= 0x80 // Highest bit indicates binary format |
|
| 151 |
- return |
|
| 152 |
- } |
|
| 153 |
- |
|
| 154 |
- f.formatOctal(b, 0) // Last resort, just write zero |
|
| 155 |
- f.err = ErrFieldTooLong |
|
| 156 |
-} |
|
| 157 |
- |
|
| 158 |
-func (p *parser) parseOctal(b []byte) int64 {
|
|
| 159 |
- // Because unused fields are filled with NULs, we need |
|
| 160 |
- // to skip leading NULs. Fields may also be padded with |
|
| 161 |
- // spaces or NULs. |
|
| 162 |
- // So we remove leading and trailing NULs and spaces to |
|
| 163 |
- // be sure. |
|
| 164 |
- b = bytes.Trim(b, " \x00") |
|
| 165 |
- |
|
| 166 |
- if len(b) == 0 {
|
|
| 167 |
- return 0 |
|
| 168 |
- } |
|
| 169 |
- x, perr := strconv.ParseUint(p.parseString(b), 8, 64) |
|
| 170 |
- if perr != nil {
|
|
| 171 |
- p.err = ErrHeader |
|
| 172 |
- } |
|
| 173 |
- return int64(x) |
|
| 174 |
-} |
|
| 175 |
- |
|
| 176 |
-func (f *formatter) formatOctal(b []byte, x int64) {
|
|
| 177 |
- if !fitsInOctal(len(b), x) {
|
|
| 178 |
- x = 0 // Last resort, just write zero |
|
| 179 |
- f.err = ErrFieldTooLong |
|
| 180 |
- } |
|
| 181 |
- |
|
| 182 |
- s := strconv.FormatInt(x, 8) |
|
| 183 |
- // Add leading zeros, but leave room for a NUL. |
|
| 184 |
- if n := len(b) - len(s) - 1; n > 0 {
|
|
| 185 |
- s = strings.Repeat("0", n) + s
|
|
| 186 |
- } |
|
| 187 |
- f.formatString(b, s) |
|
| 188 |
-} |
|
| 189 |
- |
|
| 190 |
-// fitsInOctal reports whether the integer x fits in a field n-bytes long |
|
| 191 |
-// using octal encoding with the appropriate NUL terminator. |
|
| 192 |
-func fitsInOctal(n int, x int64) bool {
|
|
| 193 |
- octBits := uint(n-1) * 3 |
|
| 194 |
- return x >= 0 && (n >= 22 || x < 1<<octBits) |
|
| 195 |
-} |
|
| 196 |
- |
|
| 197 |
-// parsePAXTime takes a string of the form %d.%d as described in the PAX |
|
| 198 |
-// specification. Note that this implementation allows for negative timestamps, |
|
| 199 |
-// which is allowed for by the PAX specification, but not always portable. |
|
| 200 |
-func parsePAXTime(s string) (time.Time, error) {
|
|
| 201 |
- const maxNanoSecondDigits = 9 |
|
| 202 |
- |
|
| 203 |
- // Split string into seconds and sub-seconds parts. |
|
| 204 |
- ss, sn := s, "" |
|
| 205 |
- if pos := strings.IndexByte(s, '.'); pos >= 0 {
|
|
| 206 |
- ss, sn = s[:pos], s[pos+1:] |
|
| 207 |
- } |
|
| 208 |
- |
|
| 209 |
- // Parse the seconds. |
|
| 210 |
- secs, err := strconv.ParseInt(ss, 10, 64) |
|
| 211 |
- if err != nil {
|
|
| 212 |
- return time.Time{}, ErrHeader
|
|
| 213 |
- } |
|
| 214 |
- if len(sn) == 0 {
|
|
| 215 |
- return time.Unix(secs, 0), nil // No sub-second values |
|
| 216 |
- } |
|
| 217 |
- |
|
| 218 |
- // Parse the nanoseconds. |
|
| 219 |
- if strings.Trim(sn, "0123456789") != "" {
|
|
| 220 |
- return time.Time{}, ErrHeader
|
|
| 221 |
- } |
|
| 222 |
- if len(sn) < maxNanoSecondDigits {
|
|
| 223 |
- sn += strings.Repeat("0", maxNanoSecondDigits-len(sn)) // Right pad
|
|
| 224 |
- } else {
|
|
| 225 |
- sn = sn[:maxNanoSecondDigits] // Right truncate |
|
| 226 |
- } |
|
| 227 |
- nsecs, _ := strconv.ParseInt(sn, 10, 64) // Must succeed |
|
| 228 |
- if len(ss) > 0 && ss[0] == '-' {
|
|
| 229 |
- return time.Unix(secs, -1*nsecs), nil // Negative correction |
|
| 230 |
- } |
|
| 231 |
- return time.Unix(secs, nsecs), nil |
|
| 232 |
-} |
|
| 233 |
- |
|
| 234 |
-// formatPAXTime converts ts into a time of the form %d.%d as described in the |
|
| 235 |
-// PAX specification. This function is capable of negative timestamps. |
|
| 236 |
-func formatPAXTime(ts time.Time) (s string) {
|
|
| 237 |
- secs, nsecs := ts.Unix(), ts.Nanosecond() |
|
| 238 |
- if nsecs == 0 {
|
|
| 239 |
- return strconv.FormatInt(secs, 10) |
|
| 240 |
- } |
|
| 241 |
- |
|
| 242 |
- // If seconds is negative, then perform correction. |
|
| 243 |
- sign := "" |
|
| 244 |
- if secs < 0 {
|
|
| 245 |
- sign = "-" // Remember sign |
|
| 246 |
- secs = -(secs + 1) // Add a second to secs |
|
| 247 |
- nsecs = -(nsecs - 1E9) // Take that second away from nsecs |
|
| 248 |
- } |
|
| 249 |
- return strings.TrimRight(fmt.Sprintf("%s%d.%09d", sign, secs, nsecs), "0")
|
|
| 250 |
-} |
|
| 251 |
- |
|
| 252 |
-// parsePAXRecord parses the input PAX record string into a key-value pair. |
|
| 253 |
-// If parsing is successful, it will slice off the currently read record and |
|
| 254 |
-// return the remainder as r. |
|
| 255 |
-func parsePAXRecord(s string) (k, v, r string, err error) {
|
|
| 256 |
- // The size field ends at the first space. |
|
| 257 |
- sp := strings.IndexByte(s, ' ') |
|
| 258 |
- if sp == -1 {
|
|
| 259 |
- return "", "", s, ErrHeader |
|
| 260 |
- } |
|
| 261 |
- |
|
| 262 |
- // Parse the first token as a decimal integer. |
|
| 263 |
- n, perr := strconv.ParseInt(s[:sp], 10, 0) // Intentionally parse as native int |
|
| 264 |
- if perr != nil || n < 5 || int64(len(s)) < n {
|
|
| 265 |
- return "", "", s, ErrHeader |
|
| 266 |
- } |
|
| 267 |
- |
|
| 268 |
- // Extract everything between the space and the final newline. |
|
| 269 |
- rec, nl, rem := s[sp+1:n-1], s[n-1:n], s[n:] |
|
| 270 |
- if nl != "\n" {
|
|
| 271 |
- return "", "", s, ErrHeader |
|
| 272 |
- } |
|
| 273 |
- |
|
| 274 |
- // The first equals separates the key from the value. |
|
| 275 |
- eq := strings.IndexByte(rec, '=') |
|
| 276 |
- if eq == -1 {
|
|
| 277 |
- return "", "", s, ErrHeader |
|
| 278 |
- } |
|
| 279 |
- k, v = rec[:eq], rec[eq+1:] |
|
| 280 |
- |
|
| 281 |
- if !validPAXRecord(k, v) {
|
|
| 282 |
- return "", "", s, ErrHeader |
|
| 283 |
- } |
|
| 284 |
- return k, v, rem, nil |
|
| 285 |
-} |
|
| 286 |
- |
|
| 287 |
-// formatPAXRecord formats a single PAX record, prefixing it with the |
|
| 288 |
-// appropriate length. |
|
| 289 |
-func formatPAXRecord(k, v string) (string, error) {
|
|
| 290 |
- if !validPAXRecord(k, v) {
|
|
| 291 |
- return "", ErrHeader |
|
| 292 |
- } |
|
| 293 |
- |
|
| 294 |
- const padding = 3 // Extra padding for ' ', '=', and '\n' |
|
| 295 |
- size := len(k) + len(v) + padding |
|
| 296 |
- size += len(strconv.Itoa(size)) |
|
| 297 |
- record := strconv.Itoa(size) + " " + k + "=" + v + "\n" |
|
| 298 |
- |
|
| 299 |
- // Final adjustment if adding size field increased the record size. |
|
| 300 |
- if len(record) != size {
|
|
| 301 |
- size = len(record) |
|
| 302 |
- record = strconv.Itoa(size) + " " + k + "=" + v + "\n" |
|
| 303 |
- } |
|
| 304 |
- return record, nil |
|
| 305 |
-} |
|
| 306 |
- |
|
| 307 |
-// validPAXRecord reports whether the key-value pair is valid where each |
|
| 308 |
-// record is formatted as: |
|
| 309 |
-// "%d %s=%s\n" % (size, key, value) |
|
| 310 |
-// |
|
| 311 |
-// Keys and values should be UTF-8, but the number of bad writers out there |
|
| 312 |
-// forces us to be a more liberal. |
|
| 313 |
-// Thus, we only reject all keys with NUL, and only reject NULs in values |
|
| 314 |
-// for the PAX version of the USTAR string fields. |
|
| 315 |
-// The key must not contain an '=' character. |
|
| 316 |
-func validPAXRecord(k, v string) bool {
|
|
| 317 |
- if k == "" || strings.IndexByte(k, '=') >= 0 {
|
|
| 318 |
- return false |
|
| 319 |
- } |
|
| 320 |
- switch k {
|
|
| 321 |
- case paxPath, paxLinkpath, paxUname, paxGname: |
|
| 322 |
- return !hasNUL(v) |
|
| 323 |
- default: |
|
| 324 |
- return !hasNUL(k) |
|
| 325 |
- } |
|
| 326 |
-} |
| 327 | 1 |
deleted file mode 100644 |
| ... | ... |
@@ -1,644 +0,0 @@ |
| 1 |
-// Copyright 2009 The Go Authors. All rights reserved. |
|
| 2 |
-// Use of this source code is governed by a BSD-style |
|
| 3 |
-// license that can be found in the LICENSE file. |
|
| 4 |
- |
|
| 5 |
-package tar |
|
| 6 |
- |
|
| 7 |
-import ( |
|
| 8 |
- "bytes" |
|
| 9 |
- "fmt" |
|
| 10 |
- "io" |
|
| 11 |
- "path" |
|
| 12 |
- "sort" |
|
| 13 |
- "strings" |
|
| 14 |
- "time" |
|
| 15 |
-) |
|
| 16 |
- |
|
| 17 |
-// Writer provides sequential writing of a tar archive. |
|
| 18 |
-// Write.WriteHeader begins a new file with the provided Header, |
|
| 19 |
-// and then Writer can be treated as an io.Writer to supply that file's data. |
|
| 20 |
-type Writer struct {
|
|
| 21 |
- w io.Writer |
|
| 22 |
- pad int64 // Amount of padding to write after current file entry |
|
| 23 |
- curr fileWriter // Writer for current file entry |
|
| 24 |
- hdr Header // Shallow copy of Header that is safe for mutations |
|
| 25 |
- blk block // Buffer to use as temporary local storage |
|
| 26 |
- |
|
| 27 |
- // err is a persistent error. |
|
| 28 |
- // It is only the responsibility of every exported method of Writer to |
|
| 29 |
- // ensure that this error is sticky. |
|
| 30 |
- err error |
|
| 31 |
-} |
|
| 32 |
- |
|
| 33 |
-// NewWriter creates a new Writer writing to w. |
|
| 34 |
-func NewWriter(w io.Writer) *Writer {
|
|
| 35 |
- return &Writer{w: w, curr: ®FileWriter{w, 0}}
|
|
| 36 |
-} |
|
| 37 |
- |
|
| 38 |
-type fileWriter interface {
|
|
| 39 |
- io.Writer |
|
| 40 |
- fileState |
|
| 41 |
- |
|
| 42 |
- ReadFrom(io.Reader) (int64, error) |
|
| 43 |
-} |
|
| 44 |
- |
|
| 45 |
-// Flush finishes writing the current file's block padding. |
|
| 46 |
-// The current file must be fully written before Flush can be called. |
|
| 47 |
-// |
|
| 48 |
-// This is unnecessary as the next call to WriteHeader or Close |
|
| 49 |
-// will implicitly flush out the file's padding. |
|
| 50 |
-func (tw *Writer) Flush() error {
|
|
| 51 |
- if tw.err != nil {
|
|
| 52 |
- return tw.err |
|
| 53 |
- } |
|
| 54 |
- if nb := tw.curr.LogicalRemaining(); nb > 0 {
|
|
| 55 |
- return fmt.Errorf("archive/tar: missed writing %d bytes", nb)
|
|
| 56 |
- } |
|
| 57 |
- if _, tw.err = tw.w.Write(zeroBlock[:tw.pad]); tw.err != nil {
|
|
| 58 |
- return tw.err |
|
| 59 |
- } |
|
| 60 |
- tw.pad = 0 |
|
| 61 |
- return nil |
|
| 62 |
-} |
|
| 63 |
- |
|
| 64 |
-// WriteHeader writes hdr and prepares to accept the file's contents. |
|
| 65 |
-// The Header.Size determines how many bytes can be written for the next file. |
|
| 66 |
-// If the current file is not fully written, then this returns an error. |
|
| 67 |
-// This implicitly flushes any padding necessary before writing the header. |
|
| 68 |
-func (tw *Writer) WriteHeader(hdr *Header) error {
|
|
| 69 |
- if err := tw.Flush(); err != nil {
|
|
| 70 |
- return err |
|
| 71 |
- } |
|
| 72 |
- tw.hdr = *hdr // Shallow copy of Header |
|
| 73 |
- |
|
| 74 |
- // Round ModTime and ignore AccessTime and ChangeTime unless |
|
| 75 |
- // the format is explicitly chosen. |
|
| 76 |
- // This ensures nominal usage of WriteHeader (without specifying the format) |
|
| 77 |
- // does not always result in the PAX format being chosen, which |
|
| 78 |
- // causes a 1KiB increase to every header. |
|
| 79 |
- if tw.hdr.Format == FormatUnknown {
|
|
| 80 |
- tw.hdr.ModTime = tw.hdr.ModTime.Round(time.Second) |
|
| 81 |
- tw.hdr.AccessTime = time.Time{}
|
|
| 82 |
- tw.hdr.ChangeTime = time.Time{}
|
|
| 83 |
- } |
|
| 84 |
- |
|
| 85 |
- allowedFormats, paxHdrs, err := tw.hdr.allowedFormats() |
|
| 86 |
- switch {
|
|
| 87 |
- case allowedFormats.has(FormatUSTAR): |
|
| 88 |
- tw.err = tw.writeUSTARHeader(&tw.hdr) |
|
| 89 |
- return tw.err |
|
| 90 |
- case allowedFormats.has(FormatPAX): |
|
| 91 |
- tw.err = tw.writePAXHeader(&tw.hdr, paxHdrs) |
|
| 92 |
- return tw.err |
|
| 93 |
- case allowedFormats.has(FormatGNU): |
|
| 94 |
- tw.err = tw.writeGNUHeader(&tw.hdr) |
|
| 95 |
- return tw.err |
|
| 96 |
- default: |
|
| 97 |
- return err // Non-fatal error |
|
| 98 |
- } |
|
| 99 |
-} |
|
| 100 |
- |
|
| 101 |
-func (tw *Writer) writeUSTARHeader(hdr *Header) error {
|
|
| 102 |
- // Check if we can use USTAR prefix/suffix splitting. |
|
| 103 |
- var namePrefix string |
|
| 104 |
- if prefix, suffix, ok := splitUSTARPath(hdr.Name); ok {
|
|
| 105 |
- namePrefix, hdr.Name = prefix, suffix |
|
| 106 |
- } |
|
| 107 |
- |
|
| 108 |
- // Pack the main header. |
|
| 109 |
- var f formatter |
|
| 110 |
- blk := tw.templateV7Plus(hdr, f.formatString, f.formatOctal) |
|
| 111 |
- f.formatString(blk.USTAR().Prefix(), namePrefix) |
|
| 112 |
- blk.SetFormat(FormatUSTAR) |
|
| 113 |
- if f.err != nil {
|
|
| 114 |
- return f.err // Should never happen since header is validated |
|
| 115 |
- } |
|
| 116 |
- return tw.writeRawHeader(blk, hdr.Size, hdr.Typeflag) |
|
| 117 |
-} |
|
| 118 |
- |
|
| 119 |
-func (tw *Writer) writePAXHeader(hdr *Header, paxHdrs map[string]string) error {
|
|
| 120 |
- realName, realSize := hdr.Name, hdr.Size |
|
| 121 |
- |
|
| 122 |
- // TODO(dsnet): Re-enable this when adding sparse support. |
|
| 123 |
- // See https://golang.org/issue/22735 |
|
| 124 |
- /* |
|
| 125 |
- // Handle sparse files. |
|
| 126 |
- var spd sparseDatas |
|
| 127 |
- var spb []byte |
|
| 128 |
- if len(hdr.SparseHoles) > 0 {
|
|
| 129 |
- sph := append([]sparseEntry{}, hdr.SparseHoles...) // Copy sparse map
|
|
| 130 |
- sph = alignSparseEntries(sph, hdr.Size) |
|
| 131 |
- spd = invertSparseEntries(sph, hdr.Size) |
|
| 132 |
- |
|
| 133 |
- // Format the sparse map. |
|
| 134 |
- hdr.Size = 0 // Replace with encoded size |
|
| 135 |
- spb = append(strconv.AppendInt(spb, int64(len(spd)), 10), '\n') |
|
| 136 |
- for _, s := range spd {
|
|
| 137 |
- hdr.Size += s.Length |
|
| 138 |
- spb = append(strconv.AppendInt(spb, s.Offset, 10), '\n') |
|
| 139 |
- spb = append(strconv.AppendInt(spb, s.Length, 10), '\n') |
|
| 140 |
- } |
|
| 141 |
- pad := blockPadding(int64(len(spb))) |
|
| 142 |
- spb = append(spb, zeroBlock[:pad]...) |
|
| 143 |
- hdr.Size += int64(len(spb)) // Accounts for encoded sparse map |
|
| 144 |
- |
|
| 145 |
- // Add and modify appropriate PAX records. |
|
| 146 |
- dir, file := path.Split(realName) |
|
| 147 |
- hdr.Name = path.Join(dir, "GNUSparseFile.0", file) |
|
| 148 |
- paxHdrs[paxGNUSparseMajor] = "1" |
|
| 149 |
- paxHdrs[paxGNUSparseMinor] = "0" |
|
| 150 |
- paxHdrs[paxGNUSparseName] = realName |
|
| 151 |
- paxHdrs[paxGNUSparseRealSize] = strconv.FormatInt(realSize, 10) |
|
| 152 |
- paxHdrs[paxSize] = strconv.FormatInt(hdr.Size, 10) |
|
| 153 |
- delete(paxHdrs, paxPath) // Recorded by paxGNUSparseName |
|
| 154 |
- } |
|
| 155 |
- */ |
|
| 156 |
- _ = realSize |
|
| 157 |
- |
|
| 158 |
- // Write PAX records to the output. |
|
| 159 |
- isGlobal := hdr.Typeflag == TypeXGlobalHeader |
|
| 160 |
- if len(paxHdrs) > 0 || isGlobal {
|
|
| 161 |
- // Sort keys for deterministic ordering. |
|
| 162 |
- var keys []string |
|
| 163 |
- for k := range paxHdrs {
|
|
| 164 |
- keys = append(keys, k) |
|
| 165 |
- } |
|
| 166 |
- sort.Strings(keys) |
|
| 167 |
- |
|
| 168 |
- // Write each record to a buffer. |
|
| 169 |
- var buf bytes.Buffer |
|
| 170 |
- for _, k := range keys {
|
|
| 171 |
- rec, err := formatPAXRecord(k, paxHdrs[k]) |
|
| 172 |
- if err != nil {
|
|
| 173 |
- return err |
|
| 174 |
- } |
|
| 175 |
- buf.WriteString(rec) |
|
| 176 |
- } |
|
| 177 |
- |
|
| 178 |
- // Write the extended header file. |
|
| 179 |
- var name string |
|
| 180 |
- var flag byte |
|
| 181 |
- if isGlobal {
|
|
| 182 |
- name = realName |
|
| 183 |
- if name == "" {
|
|
| 184 |
- name = "GlobalHead.0.0" |
|
| 185 |
- } |
|
| 186 |
- flag = TypeXGlobalHeader |
|
| 187 |
- } else {
|
|
| 188 |
- dir, file := path.Split(realName) |
|
| 189 |
- name = path.Join(dir, "PaxHeaders.0", file) |
|
| 190 |
- flag = TypeXHeader |
|
| 191 |
- } |
|
| 192 |
- data := buf.String() |
|
| 193 |
- if err := tw.writeRawFile(name, data, flag, FormatPAX); err != nil || isGlobal {
|
|
| 194 |
- return err // Global headers return here |
|
| 195 |
- } |
|
| 196 |
- } |
|
| 197 |
- |
|
| 198 |
- // Pack the main header. |
|
| 199 |
- var f formatter // Ignore errors since they are expected |
|
| 200 |
- fmtStr := func(b []byte, s string) { f.formatString(b, toASCII(s)) }
|
|
| 201 |
- blk := tw.templateV7Plus(hdr, fmtStr, f.formatOctal) |
|
| 202 |
- blk.SetFormat(FormatPAX) |
|
| 203 |
- if err := tw.writeRawHeader(blk, hdr.Size, hdr.Typeflag); err != nil {
|
|
| 204 |
- return err |
|
| 205 |
- } |
|
| 206 |
- |
|
| 207 |
- // TODO(dsnet): Re-enable this when adding sparse support. |
|
| 208 |
- // See https://golang.org/issue/22735 |
|
| 209 |
- /* |
|
| 210 |
- // Write the sparse map and setup the sparse writer if necessary. |
|
| 211 |
- if len(spd) > 0 {
|
|
| 212 |
- // Use tw.curr since the sparse map is accounted for in hdr.Size. |
|
| 213 |
- if _, err := tw.curr.Write(spb); err != nil {
|
|
| 214 |
- return err |
|
| 215 |
- } |
|
| 216 |
- tw.curr = &sparseFileWriter{tw.curr, spd, 0}
|
|
| 217 |
- } |
|
| 218 |
- */ |
|
| 219 |
- return nil |
|
| 220 |
-} |
|
| 221 |
- |
|
| 222 |
-func (tw *Writer) writeGNUHeader(hdr *Header) error {
|
|
| 223 |
- // Use long-link files if Name or Linkname exceeds the field size. |
|
| 224 |
- const longName = "././@LongLink" |
|
| 225 |
- if len(hdr.Name) > nameSize {
|
|
| 226 |
- data := hdr.Name + "\x00" |
|
| 227 |
- if err := tw.writeRawFile(longName, data, TypeGNULongName, FormatGNU); err != nil {
|
|
| 228 |
- return err |
|
| 229 |
- } |
|
| 230 |
- } |
|
| 231 |
- if len(hdr.Linkname) > nameSize {
|
|
| 232 |
- data := hdr.Linkname + "\x00" |
|
| 233 |
- if err := tw.writeRawFile(longName, data, TypeGNULongLink, FormatGNU); err != nil {
|
|
| 234 |
- return err |
|
| 235 |
- } |
|
| 236 |
- } |
|
| 237 |
- |
|
| 238 |
- // Pack the main header. |
|
| 239 |
- var f formatter // Ignore errors since they are expected |
|
| 240 |
- var spd sparseDatas |
|
| 241 |
- var spb []byte |
|
| 242 |
- blk := tw.templateV7Plus(hdr, f.formatString, f.formatNumeric) |
|
| 243 |
- if !hdr.AccessTime.IsZero() {
|
|
| 244 |
- f.formatNumeric(blk.GNU().AccessTime(), hdr.AccessTime.Unix()) |
|
| 245 |
- } |
|
| 246 |
- if !hdr.ChangeTime.IsZero() {
|
|
| 247 |
- f.formatNumeric(blk.GNU().ChangeTime(), hdr.ChangeTime.Unix()) |
|
| 248 |
- } |
|
| 249 |
- // TODO(dsnet): Re-enable this when adding sparse support. |
|
| 250 |
- // See https://golang.org/issue/22735 |
|
| 251 |
- /* |
|
| 252 |
- if hdr.Typeflag == TypeGNUSparse {
|
|
| 253 |
- sph := append([]sparseEntry{}, hdr.SparseHoles...) // Copy sparse map
|
|
| 254 |
- sph = alignSparseEntries(sph, hdr.Size) |
|
| 255 |
- spd = invertSparseEntries(sph, hdr.Size) |
|
| 256 |
- |
|
| 257 |
- // Format the sparse map. |
|
| 258 |
- formatSPD := func(sp sparseDatas, sa sparseArray) sparseDatas {
|
|
| 259 |
- for i := 0; len(sp) > 0 && i < sa.MaxEntries(); i++ {
|
|
| 260 |
- f.formatNumeric(sa.Entry(i).Offset(), sp[0].Offset) |
|
| 261 |
- f.formatNumeric(sa.Entry(i).Length(), sp[0].Length) |
|
| 262 |
- sp = sp[1:] |
|
| 263 |
- } |
|
| 264 |
- if len(sp) > 0 {
|
|
| 265 |
- sa.IsExtended()[0] = 1 |
|
| 266 |
- } |
|
| 267 |
- return sp |
|
| 268 |
- } |
|
| 269 |
- sp2 := formatSPD(spd, blk.GNU().Sparse()) |
|
| 270 |
- for len(sp2) > 0 {
|
|
| 271 |
- var spHdr block |
|
| 272 |
- sp2 = formatSPD(sp2, spHdr.Sparse()) |
|
| 273 |
- spb = append(spb, spHdr[:]...) |
|
| 274 |
- } |
|
| 275 |
- |
|
| 276 |
- // Update size fields in the header block. |
|
| 277 |
- realSize := hdr.Size |
|
| 278 |
- hdr.Size = 0 // Encoded size; does not account for encoded sparse map |
|
| 279 |
- for _, s := range spd {
|
|
| 280 |
- hdr.Size += s.Length |
|
| 281 |
- } |
|
| 282 |
- copy(blk.V7().Size(), zeroBlock[:]) // Reset field |
|
| 283 |
- f.formatNumeric(blk.V7().Size(), hdr.Size) |
|
| 284 |
- f.formatNumeric(blk.GNU().RealSize(), realSize) |
|
| 285 |
- } |
|
| 286 |
- */ |
|
| 287 |
- blk.SetFormat(FormatGNU) |
|
| 288 |
- if err := tw.writeRawHeader(blk, hdr.Size, hdr.Typeflag); err != nil {
|
|
| 289 |
- return err |
|
| 290 |
- } |
|
| 291 |
- |
|
| 292 |
- // Write the extended sparse map and setup the sparse writer if necessary. |
|
| 293 |
- if len(spd) > 0 {
|
|
| 294 |
- // Use tw.w since the sparse map is not accounted for in hdr.Size. |
|
| 295 |
- if _, err := tw.w.Write(spb); err != nil {
|
|
| 296 |
- return err |
|
| 297 |
- } |
|
| 298 |
- tw.curr = &sparseFileWriter{tw.curr, spd, 0}
|
|
| 299 |
- } |
|
| 300 |
- return nil |
|
| 301 |
-} |
|
| 302 |
- |
|
| 303 |
-type ( |
|
| 304 |
- stringFormatter func([]byte, string) |
|
| 305 |
- numberFormatter func([]byte, int64) |
|
| 306 |
-) |
|
| 307 |
- |
|
| 308 |
-// templateV7Plus fills out the V7 fields of a block using values from hdr. |
|
| 309 |
-// It also fills out fields (uname, gname, devmajor, devminor) that are |
|
| 310 |
-// shared in the USTAR, PAX, and GNU formats using the provided formatters. |
|
| 311 |
-// |
|
| 312 |
-// The block returned is only valid until the next call to |
|
| 313 |
-// templateV7Plus or writeRawFile. |
|
| 314 |
-func (tw *Writer) templateV7Plus(hdr *Header, fmtStr stringFormatter, fmtNum numberFormatter) *block {
|
|
| 315 |
- tw.blk.Reset() |
|
| 316 |
- |
|
| 317 |
- modTime := hdr.ModTime |
|
| 318 |
- if modTime.IsZero() {
|
|
| 319 |
- modTime = time.Unix(0, 0) |
|
| 320 |
- } |
|
| 321 |
- |
|
| 322 |
- v7 := tw.blk.V7() |
|
| 323 |
- v7.TypeFlag()[0] = hdr.Typeflag |
|
| 324 |
- fmtStr(v7.Name(), hdr.Name) |
|
| 325 |
- fmtStr(v7.LinkName(), hdr.Linkname) |
|
| 326 |
- fmtNum(v7.Mode(), hdr.Mode) |
|
| 327 |
- fmtNum(v7.UID(), int64(hdr.Uid)) |
|
| 328 |
- fmtNum(v7.GID(), int64(hdr.Gid)) |
|
| 329 |
- fmtNum(v7.Size(), hdr.Size) |
|
| 330 |
- fmtNum(v7.ModTime(), modTime.Unix()) |
|
| 331 |
- |
|
| 332 |
- ustar := tw.blk.USTAR() |
|
| 333 |
- fmtStr(ustar.UserName(), hdr.Uname) |
|
| 334 |
- fmtStr(ustar.GroupName(), hdr.Gname) |
|
| 335 |
- fmtNum(ustar.DevMajor(), hdr.Devmajor) |
|
| 336 |
- fmtNum(ustar.DevMinor(), hdr.Devminor) |
|
| 337 |
- |
|
| 338 |
- return &tw.blk |
|
| 339 |
-} |
|
| 340 |
- |
|
| 341 |
-// writeRawFile writes a minimal file with the given name and flag type. |
|
| 342 |
-// It uses format to encode the header format and will write data as the body. |
|
| 343 |
-// It uses default values for all of the other fields (as BSD and GNU tar does). |
|
| 344 |
-func (tw *Writer) writeRawFile(name, data string, flag byte, format Format) error {
|
|
| 345 |
- tw.blk.Reset() |
|
| 346 |
- |
|
| 347 |
- // Best effort for the filename. |
|
| 348 |
- name = toASCII(name) |
|
| 349 |
- if len(name) > nameSize {
|
|
| 350 |
- name = name[:nameSize] |
|
| 351 |
- } |
|
| 352 |
- name = strings.TrimRight(name, "/") |
|
| 353 |
- |
|
| 354 |
- var f formatter |
|
| 355 |
- v7 := tw.blk.V7() |
|
| 356 |
- v7.TypeFlag()[0] = flag |
|
| 357 |
- f.formatString(v7.Name(), name) |
|
| 358 |
- f.formatOctal(v7.Mode(), 0) |
|
| 359 |
- f.formatOctal(v7.UID(), 0) |
|
| 360 |
- f.formatOctal(v7.GID(), 0) |
|
| 361 |
- f.formatOctal(v7.Size(), int64(len(data))) // Must be < 8GiB |
|
| 362 |
- f.formatOctal(v7.ModTime(), 0) |
|
| 363 |
- tw.blk.SetFormat(format) |
|
| 364 |
- if f.err != nil {
|
|
| 365 |
- return f.err // Only occurs if size condition is violated |
|
| 366 |
- } |
|
| 367 |
- |
|
| 368 |
- // Write the header and data. |
|
| 369 |
- if err := tw.writeRawHeader(&tw.blk, int64(len(data)), flag); err != nil {
|
|
| 370 |
- return err |
|
| 371 |
- } |
|
| 372 |
- _, err := io.WriteString(tw, data) |
|
| 373 |
- return err |
|
| 374 |
-} |
|
| 375 |
- |
|
| 376 |
-// writeRawHeader writes the value of blk, regardless of its value. |
|
| 377 |
-// It sets up the Writer such that it can accept a file of the given size. |
|
| 378 |
-// If the flag is a special header-only flag, then the size is treated as zero. |
|
| 379 |
-func (tw *Writer) writeRawHeader(blk *block, size int64, flag byte) error {
|
|
| 380 |
- if err := tw.Flush(); err != nil {
|
|
| 381 |
- return err |
|
| 382 |
- } |
|
| 383 |
- if _, err := tw.w.Write(blk[:]); err != nil {
|
|
| 384 |
- return err |
|
| 385 |
- } |
|
| 386 |
- if isHeaderOnlyType(flag) {
|
|
| 387 |
- size = 0 |
|
| 388 |
- } |
|
| 389 |
- tw.curr = ®FileWriter{tw.w, size}
|
|
| 390 |
- tw.pad = blockPadding(size) |
|
| 391 |
- return nil |
|
| 392 |
-} |
|
| 393 |
- |
|
| 394 |
-// splitUSTARPath splits a path according to USTAR prefix and suffix rules. |
|
| 395 |
-// If the path is not splittable, then it will return ("", "", false).
|
|
| 396 |
-func splitUSTARPath(name string) (prefix, suffix string, ok bool) {
|
|
| 397 |
- length := len(name) |
|
| 398 |
- if length <= nameSize || !isASCII(name) {
|
|
| 399 |
- return "", "", false |
|
| 400 |
- } else if length > prefixSize+1 {
|
|
| 401 |
- length = prefixSize + 1 |
|
| 402 |
- } else if name[length-1] == '/' {
|
|
| 403 |
- length-- |
|
| 404 |
- } |
|
| 405 |
- |
|
| 406 |
- i := strings.LastIndex(name[:length], "/") |
|
| 407 |
- nlen := len(name) - i - 1 // nlen is length of suffix |
|
| 408 |
- plen := i // plen is length of prefix |
|
| 409 |
- if i <= 0 || nlen > nameSize || nlen == 0 || plen > prefixSize {
|
|
| 410 |
- return "", "", false |
|
| 411 |
- } |
|
| 412 |
- return name[:i], name[i+1:], true |
|
| 413 |
-} |
|
| 414 |
- |
|
| 415 |
-// Write writes to the current file in the tar archive. |
|
| 416 |
-// Write returns the error ErrWriteTooLong if more than |
|
| 417 |
-// Header.Size bytes are written after WriteHeader. |
|
| 418 |
-// |
|
| 419 |
-// Calling Write on special types like TypeLink, TypeSymlink, TypeChar, |
|
| 420 |
-// TypeBlock, TypeDir, and TypeFifo returns (0, ErrWriteTooLong) regardless |
|
| 421 |
-// of what the Header.Size claims. |
|
| 422 |
-func (tw *Writer) Write(b []byte) (int, error) {
|
|
| 423 |
- if tw.err != nil {
|
|
| 424 |
- return 0, tw.err |
|
| 425 |
- } |
|
| 426 |
- n, err := tw.curr.Write(b) |
|
| 427 |
- if err != nil && err != ErrWriteTooLong {
|
|
| 428 |
- tw.err = err |
|
| 429 |
- } |
|
| 430 |
- return n, err |
|
| 431 |
-} |
|
| 432 |
- |
|
| 433 |
-// readFrom populates the content of the current file by reading from r. |
|
| 434 |
-// The bytes read must match the number of remaining bytes in the current file. |
|
| 435 |
-// |
|
| 436 |
-// If the current file is sparse and r is an io.ReadSeeker, |
|
| 437 |
-// then readFrom uses Seek to skip past holes defined in Header.SparseHoles, |
|
| 438 |
-// assuming that skipped regions are all NULs. |
|
| 439 |
-// This always reads the last byte to ensure r is the right size. |
|
| 440 |
-// |
|
| 441 |
-// TODO(dsnet): Re-export this when adding sparse file support. |
|
| 442 |
-// See https://golang.org/issue/22735 |
|
| 443 |
-func (tw *Writer) readFrom(r io.Reader) (int64, error) {
|
|
| 444 |
- if tw.err != nil {
|
|
| 445 |
- return 0, tw.err |
|
| 446 |
- } |
|
| 447 |
- n, err := tw.curr.ReadFrom(r) |
|
| 448 |
- if err != nil && err != ErrWriteTooLong {
|
|
| 449 |
- tw.err = err |
|
| 450 |
- } |
|
| 451 |
- return n, err |
|
| 452 |
-} |
|
| 453 |
- |
|
| 454 |
-// Close closes the tar archive by flushing the padding, and writing the footer. |
|
| 455 |
-// If the current file (from a prior call to WriteHeader) is not fully written, |
|
| 456 |
-// then this returns an error. |
|
| 457 |
-func (tw *Writer) Close() error {
|
|
| 458 |
- if tw.err == ErrWriteAfterClose {
|
|
| 459 |
- return nil |
|
| 460 |
- } |
|
| 461 |
- if tw.err != nil {
|
|
| 462 |
- return tw.err |
|
| 463 |
- } |
|
| 464 |
- |
|
| 465 |
- // Trailer: two zero blocks. |
|
| 466 |
- err := tw.Flush() |
|
| 467 |
- for i := 0; i < 2 && err == nil; i++ {
|
|
| 468 |
- _, err = tw.w.Write(zeroBlock[:]) |
|
| 469 |
- } |
|
| 470 |
- |
|
| 471 |
- // Ensure all future actions are invalid. |
|
| 472 |
- tw.err = ErrWriteAfterClose |
|
| 473 |
- return err // Report IO errors |
|
| 474 |
-} |
|
| 475 |
- |
|
| 476 |
-// regFileWriter is a fileWriter for writing data to a regular file entry. |
|
| 477 |
-type regFileWriter struct {
|
|
| 478 |
- w io.Writer // Underlying Writer |
|
| 479 |
- nb int64 // Number of remaining bytes to write |
|
| 480 |
-} |
|
| 481 |
- |
|
| 482 |
-func (fw *regFileWriter) Write(b []byte) (n int, err error) {
|
|
| 483 |
- overwrite := int64(len(b)) > fw.nb |
|
| 484 |
- if overwrite {
|
|
| 485 |
- b = b[:fw.nb] |
|
| 486 |
- } |
|
| 487 |
- if len(b) > 0 {
|
|
| 488 |
- n, err = fw.w.Write(b) |
|
| 489 |
- fw.nb -= int64(n) |
|
| 490 |
- } |
|
| 491 |
- switch {
|
|
| 492 |
- case err != nil: |
|
| 493 |
- return n, err |
|
| 494 |
- case overwrite: |
|
| 495 |
- return n, ErrWriteTooLong |
|
| 496 |
- default: |
|
| 497 |
- return n, nil |
|
| 498 |
- } |
|
| 499 |
-} |
|
| 500 |
- |
|
| 501 |
-func (fw *regFileWriter) ReadFrom(r io.Reader) (int64, error) {
|
|
| 502 |
- return io.Copy(struct{ io.Writer }{fw}, r)
|
|
| 503 |
-} |
|
| 504 |
- |
|
| 505 |
-func (fw regFileWriter) LogicalRemaining() int64 {
|
|
| 506 |
- return fw.nb |
|
| 507 |
-} |
|
| 508 |
-func (fw regFileWriter) PhysicalRemaining() int64 {
|
|
| 509 |
- return fw.nb |
|
| 510 |
-} |
|
| 511 |
- |
|
| 512 |
-// sparseFileWriter is a fileWriter for writing data to a sparse file entry. |
|
| 513 |
-type sparseFileWriter struct {
|
|
| 514 |
- fw fileWriter // Underlying fileWriter |
|
| 515 |
- sp sparseDatas // Normalized list of data fragments |
|
| 516 |
- pos int64 // Current position in sparse file |
|
| 517 |
-} |
|
| 518 |
- |
|
| 519 |
-func (sw *sparseFileWriter) Write(b []byte) (n int, err error) {
|
|
| 520 |
- overwrite := int64(len(b)) > sw.LogicalRemaining() |
|
| 521 |
- if overwrite {
|
|
| 522 |
- b = b[:sw.LogicalRemaining()] |
|
| 523 |
- } |
|
| 524 |
- |
|
| 525 |
- b0 := b |
|
| 526 |
- endPos := sw.pos + int64(len(b)) |
|
| 527 |
- for endPos > sw.pos && err == nil {
|
|
| 528 |
- var nf int // Bytes written in fragment |
|
| 529 |
- dataStart, dataEnd := sw.sp[0].Offset, sw.sp[0].endOffset() |
|
| 530 |
- if sw.pos < dataStart { // In a hole fragment
|
|
| 531 |
- bf := b[:min(int64(len(b)), dataStart-sw.pos)] |
|
| 532 |
- nf, err = zeroWriter{}.Write(bf)
|
|
| 533 |
- } else { // In a data fragment
|
|
| 534 |
- bf := b[:min(int64(len(b)), dataEnd-sw.pos)] |
|
| 535 |
- nf, err = sw.fw.Write(bf) |
|
| 536 |
- } |
|
| 537 |
- b = b[nf:] |
|
| 538 |
- sw.pos += int64(nf) |
|
| 539 |
- if sw.pos >= dataEnd && len(sw.sp) > 1 {
|
|
| 540 |
- sw.sp = sw.sp[1:] // Ensure last fragment always remains |
|
| 541 |
- } |
|
| 542 |
- } |
|
| 543 |
- |
|
| 544 |
- n = len(b0) - len(b) |
|
| 545 |
- switch {
|
|
| 546 |
- case err == ErrWriteTooLong: |
|
| 547 |
- return n, errMissData // Not possible; implies bug in validation logic |
|
| 548 |
- case err != nil: |
|
| 549 |
- return n, err |
|
| 550 |
- case sw.LogicalRemaining() == 0 && sw.PhysicalRemaining() > 0: |
|
| 551 |
- return n, errUnrefData // Not possible; implies bug in validation logic |
|
| 552 |
- case overwrite: |
|
| 553 |
- return n, ErrWriteTooLong |
|
| 554 |
- default: |
|
| 555 |
- return n, nil |
|
| 556 |
- } |
|
| 557 |
-} |
|
| 558 |
- |
|
| 559 |
-func (sw *sparseFileWriter) ReadFrom(r io.Reader) (n int64, err error) {
|
|
| 560 |
- rs, ok := r.(io.ReadSeeker) |
|
| 561 |
- if ok {
|
|
| 562 |
- if _, err := rs.Seek(0, io.SeekCurrent); err != nil {
|
|
| 563 |
- ok = false // Not all io.Seeker can really seek |
|
| 564 |
- } |
|
| 565 |
- } |
|
| 566 |
- if !ok {
|
|
| 567 |
- return io.Copy(struct{ io.Writer }{sw}, r)
|
|
| 568 |
- } |
|
| 569 |
- |
|
| 570 |
- var readLastByte bool |
|
| 571 |
- pos0 := sw.pos |
|
| 572 |
- for sw.LogicalRemaining() > 0 && !readLastByte && err == nil {
|
|
| 573 |
- var nf int64 // Size of fragment |
|
| 574 |
- dataStart, dataEnd := sw.sp[0].Offset, sw.sp[0].endOffset() |
|
| 575 |
- if sw.pos < dataStart { // In a hole fragment
|
|
| 576 |
- nf = dataStart - sw.pos |
|
| 577 |
- if sw.PhysicalRemaining() == 0 {
|
|
| 578 |
- readLastByte = true |
|
| 579 |
- nf-- |
|
| 580 |
- } |
|
| 581 |
- _, err = rs.Seek(nf, io.SeekCurrent) |
|
| 582 |
- } else { // In a data fragment
|
|
| 583 |
- nf = dataEnd - sw.pos |
|
| 584 |
- nf, err = io.CopyN(sw.fw, rs, nf) |
|
| 585 |
- } |
|
| 586 |
- sw.pos += nf |
|
| 587 |
- if sw.pos >= dataEnd && len(sw.sp) > 1 {
|
|
| 588 |
- sw.sp = sw.sp[1:] // Ensure last fragment always remains |
|
| 589 |
- } |
|
| 590 |
- } |
|
| 591 |
- |
|
| 592 |
- // If the last fragment is a hole, then seek to 1-byte before EOF, and |
|
| 593 |
- // read a single byte to ensure the file is the right size. |
|
| 594 |
- if readLastByte && err == nil {
|
|
| 595 |
- _, err = mustReadFull(rs, []byte{0})
|
|
| 596 |
- sw.pos++ |
|
| 597 |
- } |
|
| 598 |
- |
|
| 599 |
- n = sw.pos - pos0 |
|
| 600 |
- switch {
|
|
| 601 |
- case err == io.EOF: |
|
| 602 |
- return n, io.ErrUnexpectedEOF |
|
| 603 |
- case err == ErrWriteTooLong: |
|
| 604 |
- return n, errMissData // Not possible; implies bug in validation logic |
|
| 605 |
- case err != nil: |
|
| 606 |
- return n, err |
|
| 607 |
- case sw.LogicalRemaining() == 0 && sw.PhysicalRemaining() > 0: |
|
| 608 |
- return n, errUnrefData // Not possible; implies bug in validation logic |
|
| 609 |
- default: |
|
| 610 |
- return n, ensureEOF(rs) |
|
| 611 |
- } |
|
| 612 |
-} |
|
| 613 |
- |
|
| 614 |
-func (sw sparseFileWriter) LogicalRemaining() int64 {
|
|
| 615 |
- return sw.sp[len(sw.sp)-1].endOffset() - sw.pos |
|
| 616 |
-} |
|
| 617 |
-func (sw sparseFileWriter) PhysicalRemaining() int64 {
|
|
| 618 |
- return sw.fw.PhysicalRemaining() |
|
| 619 |
-} |
|
| 620 |
- |
|
| 621 |
-// zeroWriter may only be written with NULs, otherwise it returns errWriteHole. |
|
| 622 |
-type zeroWriter struct{}
|
|
| 623 |
- |
|
| 624 |
-func (zeroWriter) Write(b []byte) (int, error) {
|
|
| 625 |
- for i, c := range b {
|
|
| 626 |
- if c != 0 {
|
|
| 627 |
- return i, errWriteHole |
|
| 628 |
- } |
|
| 629 |
- } |
|
| 630 |
- return len(b), nil |
|
| 631 |
-} |
|
| 632 |
- |
|
| 633 |
-// ensureEOF checks whether r is at EOF, reporting ErrWriteTooLong if not so. |
|
| 634 |
-func ensureEOF(r io.Reader) error {
|
|
| 635 |
- n, err := tryReadFull(r, []byte{0})
|
|
| 636 |
- switch {
|
|
| 637 |
- case n > 0: |
|
| 638 |
- return ErrWriteTooLong |
|
| 639 |
- case err == io.EOF: |
|
| 640 |
- return nil |
|
| 641 |
- default: |
|
| 642 |
- return err |
|
| 643 |
- } |
|
| 644 |
-} |
| ... | ... |
@@ -6,7 +6,6 @@ import ( |
| 6 | 6 |
"errors" |
| 7 | 7 |
"fmt" |
| 8 | 8 |
"os" |
| 9 |
- "path/filepath" |
|
| 10 | 9 |
"sort" |
| 11 | 10 |
|
| 12 | 11 |
"github.com/containerd/continuity/devices" |
| ... | ... |
@@ -26,18 +25,6 @@ func (d *driver) Mkfifo(path string, mode os.FileMode) error {
|
| 26 | 26 |
return devices.Mknod(path, mode, 0, 0) |
| 27 | 27 |
} |
| 28 | 28 |
|
| 29 |
-// Lchmod changes the mode of an file not following symlinks. |
|
| 30 |
-func (d *driver) Lchmod(path string, mode os.FileMode) (err error) {
|
|
| 31 |
- if !filepath.IsAbs(path) {
|
|
| 32 |
- path, err = filepath.Abs(path) |
|
| 33 |
- if err != nil {
|
|
| 34 |
- return |
|
| 35 |
- } |
|
| 36 |
- } |
|
| 37 |
- |
|
| 38 |
- return sysx.Fchmodat(0, path, uint32(mode), sysx.AtSymlinkNofollow) |
|
| 39 |
-} |
|
| 40 |
- |
|
| 41 | 29 |
// Getxattr returns all of the extended attributes for the file at path p. |
| 42 | 30 |
func (d *driver) Getxattr(p string) (map[string][]byte, error) {
|
| 43 | 31 |
xattrs, err := sysx.Listxattr(p) |
| 44 | 32 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,19 @@ |
| 0 |
+package driver |
|
| 1 |
+ |
|
| 2 |
+import ( |
|
| 3 |
+ "os" |
|
| 4 |
+ |
|
| 5 |
+ "golang.org/x/sys/unix" |
|
| 6 |
+) |
|
| 7 |
+ |
|
| 8 |
+// Lchmod changes the mode of a file not following symlinks. |
|
| 9 |
+func (d *driver) Lchmod(path string, mode os.FileMode) error {
|
|
| 10 |
+ // On Linux, file mode is not supported for symlinks, |
|
| 11 |
+ // and fchmodat() does not support AT_SYMLINK_NOFOLLOW, |
|
| 12 |
+ // so symlinks need to be skipped entirely. |
|
| 13 |
+ if st, err := os.Stat(path); err == nil && st.Mode()&os.ModeSymlink != 0 {
|
|
| 14 |
+ return nil |
|
| 15 |
+ } |
|
| 16 |
+ |
|
| 17 |
+ return unix.Fchmodat(unix.AT_FDCWD, path, uint32(mode), 0) |
|
| 18 |
+} |
| 0 | 19 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,14 @@ |
| 0 |
+// +build darwin freebsd solaris |
|
| 1 |
+ |
|
| 2 |
+package driver |
|
| 3 |
+ |
|
| 4 |
+import ( |
|
| 5 |
+ "os" |
|
| 6 |
+ |
|
| 7 |
+ "golang.org/x/sys/unix" |
|
| 8 |
+) |
|
| 9 |
+ |
|
| 10 |
+// Lchmod changes the mode of a file not following symlinks. |
|
| 11 |
+func (d *driver) Lchmod(path string, mode os.FileMode) error {
|
|
| 12 |
+ return unix.Fchmodat(unix.AT_FDCWD, path, uint32(mode), unix.AT_SYMLINK_NOFOLLOW) |
|
| 13 |
+} |
| ... | ... |
@@ -10,8 +10,8 @@ type Usage struct {
|
| 10 | 10 |
|
| 11 | 11 |
// DiskUsage counts the number of inodes and disk usage for the resources under |
| 12 | 12 |
// path. |
| 13 |
-func DiskUsage(roots ...string) (Usage, error) {
|
|
| 14 |
- return diskUsage(roots...) |
|
| 13 |
+func DiskUsage(ctx context.Context, roots ...string) (Usage, error) {
|
|
| 14 |
+ return diskUsage(ctx, roots...) |
|
| 15 | 15 |
} |
| 16 | 16 |
|
| 17 | 17 |
// DiffUsage counts the numbers of inodes and disk usage in the |
| ... | ... |
@@ -24,7 +24,7 @@ func newInode(stat *syscall.Stat_t) inode {
|
| 24 | 24 |
} |
| 25 | 25 |
} |
| 26 | 26 |
|
| 27 |
-func diskUsage(roots ...string) (Usage, error) {
|
|
| 27 |
+func diskUsage(ctx context.Context, roots ...string) (Usage, error) {
|
|
| 28 | 28 |
|
| 29 | 29 |
var ( |
| 30 | 30 |
size int64 |
| ... | ... |
@@ -37,6 +37,12 @@ func diskUsage(roots ...string) (Usage, error) {
|
| 37 | 37 |
return err |
| 38 | 38 |
} |
| 39 | 39 |
|
| 40 |
+ select {
|
|
| 41 |
+ case <-ctx.Done(): |
|
| 42 |
+ return ctx.Err() |
|
| 43 |
+ default: |
|
| 44 |
+ } |
|
| 45 |
+ |
|
| 40 | 46 |
inoKey := newInode(fi.Sys().(*syscall.Stat_t)) |
| 41 | 47 |
if _, ok := inodes[inoKey]; !ok {
|
| 42 | 48 |
inodes[inoKey] = struct{}{}
|
| ... | ... |
@@ -8,7 +8,7 @@ import ( |
| 8 | 8 |
"path/filepath" |
| 9 | 9 |
) |
| 10 | 10 |
|
| 11 |
-func diskUsage(roots ...string) (Usage, error) {
|
|
| 11 |
+func diskUsage(ctx context.Context, roots ...string) (Usage, error) {
|
|
| 12 | 12 |
var ( |
| 13 | 13 |
size int64 |
| 14 | 14 |
) |
| ... | ... |
@@ -21,6 +21,12 @@ func diskUsage(roots ...string) (Usage, error) {
|
| 21 | 21 |
return err |
| 22 | 22 |
} |
| 23 | 23 |
|
| 24 |
+ select {
|
|
| 25 |
+ case <-ctx.Done(): |
|
| 26 |
+ return ctx.Err() |
|
| 27 |
+ default: |
|
| 28 |
+ } |
|
| 29 |
+ |
|
| 24 | 30 |
size += fi.Size() |
| 25 | 31 |
return nil |
| 26 | 32 |
}); err != nil {
|
| 0 | 3 |
deleted file mode 100644 |
| ... | ... |
@@ -1,10 +0,0 @@ |
| 1 |
-// Copyright 2014 The Go Authors. All rights reserved. |
|
| 2 |
-// Use of this source code is governed by a BSD-style |
|
| 3 |
-// license that can be found in the LICENSE file. |
|
| 4 |
- |
|
| 5 |
-// +build !gccgo |
|
| 6 |
- |
|
| 7 |
-#include "textflag.h" |
|
| 8 |
- |
|
| 9 |
-TEXT ·use(SB),NOSPLIT,$0 |
|
| 10 |
- RET |
| 11 | 1 |
deleted file mode 100644 |
| ... | ... |
@@ -1,18 +0,0 @@ |
| 1 |
-package sysx |
|
| 2 |
- |
|
| 3 |
-const ( |
|
| 4 |
- // AtSymlinkNoFollow defined from AT_SYMLINK_NOFOLLOW in <sys/fcntl.h> |
|
| 5 |
- AtSymlinkNofollow = 0x20 |
|
| 6 |
-) |
|
| 7 |
- |
|
| 8 |
-const ( |
|
| 9 |
- |
|
| 10 |
- // SYS_FCHMODAT defined from golang.org/sys/unix |
|
| 11 |
- SYS_FCHMODAT = 467 |
|
| 12 |
-) |
|
| 13 |
- |
|
| 14 |
-// These functions will be generated by generate.sh |
|
| 15 |
-// $ GOOS=darwin GOARCH=386 ./generate.sh chmod |
|
| 16 |
-// $ GOOS=darwin GOARCH=amd64 ./generate.sh chmod |
|
| 17 |
- |
|
| 18 |
-//sys Fchmodat(dirfd int, path string, mode uint32, flags int) (err error) |
| 19 | 1 |
deleted file mode 100644 |
| ... | ... |
@@ -1,25 +0,0 @@ |
| 1 |
-// mksyscall.pl -l32 chmod_darwin.go |
|
| 2 |
-// MACHINE GENERATED BY THE COMMAND ABOVE; DO NOT EDIT |
|
| 3 |
- |
|
| 4 |
-package sysx |
|
| 5 |
- |
|
| 6 |
-import ( |
|
| 7 |
- "syscall" |
|
| 8 |
- "unsafe" |
|
| 9 |
-) |
|
| 10 |
- |
|
| 11 |
-// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT |
|
| 12 |
- |
|
| 13 |
-func Fchmodat(dirfd int, path string, mode uint32, flags int) (err error) {
|
|
| 14 |
- var _p0 *byte |
|
| 15 |
- _p0, err = syscall.BytePtrFromString(path) |
|
| 16 |
- if err != nil {
|
|
| 17 |
- return |
|
| 18 |
- } |
|
| 19 |
- _, _, e1 := syscall.Syscall6(SYS_FCHMODAT, uintptr(dirfd), uintptr(unsafe.Pointer(_p0)), uintptr(mode), uintptr(flags), 0, 0) |
|
| 20 |
- use(unsafe.Pointer(_p0)) |
|
| 21 |
- if e1 != 0 {
|
|
| 22 |
- err = errnoErr(e1) |
|
| 23 |
- } |
|
| 24 |
- return |
|
| 25 |
-} |
| 26 | 1 |
deleted file mode 100644 |
| ... | ... |
@@ -1,25 +0,0 @@ |
| 1 |
-// mksyscall.pl chmod_darwin.go |
|
| 2 |
-// MACHINE GENERATED BY THE COMMAND ABOVE; DO NOT EDIT |
|
| 3 |
- |
|
| 4 |
-package sysx |
|
| 5 |
- |
|
| 6 |
-import ( |
|
| 7 |
- "syscall" |
|
| 8 |
- "unsafe" |
|
| 9 |
-) |
|
| 10 |
- |
|
| 11 |
-// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT |
|
| 12 |
- |
|
| 13 |
-func Fchmodat(dirfd int, path string, mode uint32, flags int) (err error) {
|
|
| 14 |
- var _p0 *byte |
|
| 15 |
- _p0, err = syscall.BytePtrFromString(path) |
|
| 16 |
- if err != nil {
|
|
| 17 |
- return |
|
| 18 |
- } |
|
| 19 |
- _, _, e1 := syscall.Syscall6(SYS_FCHMODAT, uintptr(dirfd), uintptr(unsafe.Pointer(_p0)), uintptr(mode), uintptr(flags), 0, 0) |
|
| 20 |
- use(unsafe.Pointer(_p0)) |
|
| 21 |
- if e1 != 0 {
|
|
| 22 |
- err = errnoErr(e1) |
|
| 23 |
- } |
|
| 24 |
- return |
|
| 25 |
-} |
| 26 | 1 |
deleted file mode 100644 |
| ... | ... |
@@ -1,17 +0,0 @@ |
| 1 |
-package sysx |
|
| 2 |
- |
|
| 3 |
-const ( |
|
| 4 |
- // AtSymlinkNoFollow defined from AT_SYMLINK_NOFOLLOW in <sys/fcntl.h> |
|
| 5 |
- AtSymlinkNofollow = 0x200 |
|
| 6 |
-) |
|
| 7 |
- |
|
| 8 |
-const ( |
|
| 9 |
- |
|
| 10 |
- // SYS_FCHMODAT defined from golang.org/sys/unix |
|
| 11 |
- SYS_FCHMODAT = 490 |
|
| 12 |
-) |
|
| 13 |
- |
|
| 14 |
-// These functions will be generated by generate.sh |
|
| 15 |
-// $ GOOS=freebsd GOARCH=amd64 ./generate.sh chmod |
|
| 16 |
- |
|
| 17 |
-//sys Fchmodat(dirfd int, path string, mode uint32, flags int) (err error) |
| 18 | 1 |
deleted file mode 100644 |
| ... | ... |
@@ -1,25 +0,0 @@ |
| 1 |
-// mksyscall.pl chmod_freebsd.go |
|
| 2 |
-// MACHINE GENERATED BY THE COMMAND ABOVE; DO NOT EDIT |
|
| 3 |
- |
|
| 4 |
-package sysx |
|
| 5 |
- |
|
| 6 |
-import ( |
|
| 7 |
- "syscall" |
|
| 8 |
- "unsafe" |
|
| 9 |
-) |
|
| 10 |
- |
|
| 11 |
-// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT |
|
| 12 |
- |
|
| 13 |
-func Fchmodat(dirfd int, path string, mode uint32, flags int) (err error) {
|
|
| 14 |
- var _p0 *byte |
|
| 15 |
- _p0, err = syscall.BytePtrFromString(path) |
|
| 16 |
- if err != nil {
|
|
| 17 |
- return |
|
| 18 |
- } |
|
| 19 |
- _, _, e1 := syscall.Syscall6(SYS_FCHMODAT, uintptr(dirfd), uintptr(unsafe.Pointer(_p0)), uintptr(mode), uintptr(flags), 0, 0) |
|
| 20 |
- use(unsafe.Pointer(_p0)) |
|
| 21 |
- if e1 != 0 {
|
|
| 22 |
- err = errnoErr(e1) |
|
| 23 |
- } |
|
| 24 |
- return |
|
| 25 |
-} |
| 26 | 1 |
deleted file mode 100644 |
| ... | ... |
@@ -1,12 +0,0 @@ |
| 1 |
-package sysx |
|
| 2 |
- |
|
| 3 |
-import "syscall" |
|
| 4 |
- |
|
| 5 |
-const ( |
|
| 6 |
- // AtSymlinkNoFollow defined from AT_SYMLINK_NOFOLLOW in /usr/include/linux/fcntl.h |
|
| 7 |
- AtSymlinkNofollow = 0x100 |
|
| 8 |
-) |
|
| 9 |
- |
|
| 10 |
-func Fchmodat(dirfd int, path string, mode uint32, flags int) error {
|
|
| 11 |
- return syscall.Fchmodat(dirfd, path, mode, flags) |
|
| 12 |
-} |
| 13 | 1 |
deleted file mode 100644 |
| ... | ... |
@@ -1,11 +0,0 @@ |
| 1 |
-package sysx |
|
| 2 |
- |
|
| 3 |
-import "golang.org/x/sys/unix" |
|
| 4 |
- |
|
| 5 |
-const ( |
|
| 6 |
- AtSymlinkNofollow = unix.AT_SYMLINK_NOFOLLOW |
|
| 7 |
-) |
|
| 8 |
- |
|
| 9 |
-func Fchmodat(dirfd int, path string, mode uint32, flags int) error {
|
|
| 10 |
- return unix.Fchmodat(dirfd, path, mode, flags) |
|
| 11 |
-} |
| 12 | 1 |
deleted file mode 100644 |
| ... | ... |
@@ -1,37 +0,0 @@ |
| 1 |
-package sysx |
|
| 2 |
- |
|
| 3 |
-import ( |
|
| 4 |
- "syscall" |
|
| 5 |
- "unsafe" |
|
| 6 |
-) |
|
| 7 |
- |
|
| 8 |
-var _zero uintptr |
|
| 9 |
- |
|
| 10 |
-// use is a no-op, but the compiler cannot see that it is. |
|
| 11 |
-// Calling use(p) ensures that p is kept live until that point. |
|
| 12 |
-//go:noescape |
|
| 13 |
-func use(p unsafe.Pointer) |
|
| 14 |
- |
|
| 15 |
-// Do the interface allocations only once for common |
|
| 16 |
-// Errno values. |
|
| 17 |
-var ( |
|
| 18 |
- errEAGAIN error = syscall.EAGAIN |
|
| 19 |
- errEINVAL error = syscall.EINVAL |
|
| 20 |
- errENOENT error = syscall.ENOENT |
|
| 21 |
-) |
|
| 22 |
- |
|
| 23 |
-// errnoErr returns common boxed Errno values, to prevent |
|
| 24 |
-// allocations at runtime. |
|
| 25 |
-func errnoErr(e syscall.Errno) error {
|
|
| 26 |
- switch e {
|
|
| 27 |
- case 0: |
|
| 28 |
- return nil |
|
| 29 |
- case syscall.EAGAIN: |
|
| 30 |
- return errEAGAIN |
|
| 31 |
- case syscall.EINVAL: |
|
| 32 |
- return errEINVAL |
|
| 33 |
- case syscall.ENOENT: |
|
| 34 |
- return errENOENT |
|
| 35 |
- } |
|
| 36 |
- return e |
|
| 37 |
-} |
| ... | ... |
@@ -1,14 +1,56 @@ |
| 1 |
+// +build linux darwin |
|
| 2 |
+ |
|
| 1 | 3 |
package sysx |
| 2 | 4 |
|
| 3 | 5 |
import ( |
| 4 | 6 |
"bytes" |
| 5 |
- "fmt" |
|
| 6 | 7 |
"syscall" |
| 8 |
+ |
|
| 9 |
+ "golang.org/x/sys/unix" |
|
| 7 | 10 |
) |
| 8 | 11 |
|
| 9 |
-const defaultXattrBufferSize = 5 |
|
| 12 |
+// Listxattr calls syscall listxattr and reads all content |
|
| 13 |
+// and returns a string array |
|
| 14 |
+func Listxattr(path string) ([]string, error) {
|
|
| 15 |
+ return listxattrAll(path, unix.Listxattr) |
|
| 16 |
+} |
|
| 17 |
+ |
|
| 18 |
+// Removexattr calls syscall removexattr |
|
| 19 |
+func Removexattr(path string, attr string) (err error) {
|
|
| 20 |
+ return unix.Removexattr(path, attr) |
|
| 21 |
+} |
|
| 22 |
+ |
|
| 23 |
+// Setxattr calls syscall setxattr |
|
| 24 |
+func Setxattr(path string, attr string, data []byte, flags int) (err error) {
|
|
| 25 |
+ return unix.Setxattr(path, attr, data, flags) |
|
| 26 |
+} |
|
| 27 |
+ |
|
| 28 |
+// Getxattr calls syscall getxattr |
|
| 29 |
+func Getxattr(path, attr string) ([]byte, error) {
|
|
| 30 |
+ return getxattrAll(path, attr, unix.Getxattr) |
|
| 31 |
+} |
|
| 10 | 32 |
|
| 11 |
-var ErrNotSupported = fmt.Errorf("not supported")
|
|
| 33 |
+// LListxattr lists xattrs, not following symlinks |
|
| 34 |
+func LListxattr(path string) ([]string, error) {
|
|
| 35 |
+ return listxattrAll(path, unix.Llistxattr) |
|
| 36 |
+} |
|
| 37 |
+ |
|
| 38 |
+// LRemovexattr removes an xattr, not following symlinks |
|
| 39 |
+func LRemovexattr(path string, attr string) (err error) {
|
|
| 40 |
+ return unix.Lremovexattr(path, attr) |
|
| 41 |
+} |
|
| 42 |
+ |
|
| 43 |
+// LSetxattr sets an xattr, not following symlinks |
|
| 44 |
+func LSetxattr(path string, attr string, data []byte, flags int) (err error) {
|
|
| 45 |
+ return unix.Lsetxattr(path, attr, data, flags) |
|
| 46 |
+} |
|
| 47 |
+ |
|
| 48 |
+// LGetxattr gets an xattr, not following symlinks |
|
| 49 |
+func LGetxattr(path, attr string) ([]byte, error) {
|
|
| 50 |
+ return getxattrAll(path, attr, unix.Lgetxattr) |
|
| 51 |
+} |
|
| 52 |
+ |
|
| 53 |
+const defaultXattrBufferSize = 5 |
|
| 12 | 54 |
|
| 13 | 55 |
type listxattrFunc func(path string, dest []byte) (int, error) |
| 14 | 56 |
|
| 15 | 57 |
deleted file mode 100644 |
| ... | ... |
@@ -1,71 +0,0 @@ |
| 1 |
-package sysx |
|
| 2 |
- |
|
| 3 |
-// These functions will be generated by generate.sh |
|
| 4 |
-// $ GOOS=darwin GOARCH=386 ./generate.sh xattr |
|
| 5 |
-// $ GOOS=darwin GOARCH=amd64 ./generate.sh xattr |
|
| 6 |
- |
|
| 7 |
-//sys getxattr(path string, attr string, dest []byte, pos int, options int) (sz int, err error) |
|
| 8 |
-//sys setxattr(path string, attr string, data []byte, flags int) (err error) |
|
| 9 |
-//sys removexattr(path string, attr string, options int) (err error) |
|
| 10 |
-//sys listxattr(path string, dest []byte, options int) (sz int, err error) |
|
| 11 |
-//sys Fchmodat(dirfd int, path string, mode uint32, flags int) (err error) |
|
| 12 |
- |
|
| 13 |
-const ( |
|
| 14 |
- xattrNoFollow = 0x01 |
|
| 15 |
-) |
|
| 16 |
- |
|
| 17 |
-func listxattrFollow(path string, dest []byte) (sz int, err error) {
|
|
| 18 |
- return listxattr(path, dest, 0) |
|
| 19 |
-} |
|
| 20 |
- |
|
| 21 |
-// Listxattr calls syscall getxattr |
|
| 22 |
-func Listxattr(path string) ([]string, error) {
|
|
| 23 |
- return listxattrAll(path, listxattrFollow) |
|
| 24 |
-} |
|
| 25 |
- |
|
| 26 |
-// Removexattr calls syscall getxattr |
|
| 27 |
-func Removexattr(path string, attr string) (err error) {
|
|
| 28 |
- return removexattr(path, attr, 0) |
|
| 29 |
-} |
|
| 30 |
- |
|
| 31 |
-// Setxattr calls syscall setxattr |
|
| 32 |
-func Setxattr(path string, attr string, data []byte, flags int) (err error) {
|
|
| 33 |
- return setxattr(path, attr, data, flags) |
|
| 34 |
-} |
|
| 35 |
- |
|
| 36 |
-func getxattrFollow(path, attr string, dest []byte) (sz int, err error) {
|
|
| 37 |
- return getxattr(path, attr, dest, 0, 0) |
|
| 38 |
-} |
|
| 39 |
- |
|
| 40 |
-// Getxattr calls syscall getxattr |
|
| 41 |
-func Getxattr(path, attr string) ([]byte, error) {
|
|
| 42 |
- return getxattrAll(path, attr, getxattrFollow) |
|
| 43 |
-} |
|
| 44 |
- |
|
| 45 |
-func listxattrNoFollow(path string, dest []byte) (sz int, err error) {
|
|
| 46 |
- return listxattr(path, dest, xattrNoFollow) |
|
| 47 |
-} |
|
| 48 |
- |
|
| 49 |
-// LListxattr calls syscall listxattr with XATTR_NOFOLLOW |
|
| 50 |
-func LListxattr(path string) ([]string, error) {
|
|
| 51 |
- return listxattrAll(path, listxattrNoFollow) |
|
| 52 |
-} |
|
| 53 |
- |
|
| 54 |
-// LRemovexattr calls syscall removexattr with XATTR_NOFOLLOW |
|
| 55 |
-func LRemovexattr(path string, attr string) (err error) {
|
|
| 56 |
- return removexattr(path, attr, xattrNoFollow) |
|
| 57 |
-} |
|
| 58 |
- |
|
| 59 |
-// Setxattr calls syscall setxattr with XATTR_NOFOLLOW |
|
| 60 |
-func LSetxattr(path string, attr string, data []byte, flags int) (err error) {
|
|
| 61 |
- return setxattr(path, attr, data, flags|xattrNoFollow) |
|
| 62 |
-} |
|
| 63 |
- |
|
| 64 |
-func getxattrNoFollow(path, attr string, dest []byte) (sz int, err error) {
|
|
| 65 |
- return getxattr(path, attr, dest, 0, xattrNoFollow) |
|
| 66 |
-} |
|
| 67 |
- |
|
| 68 |
-// LGetxattr calls syscall getxattr with XATTR_NOFOLLOW |
|
| 69 |
-func LGetxattr(path, attr string) ([]byte, error) {
|
|
| 70 |
- return getxattrAll(path, attr, getxattrNoFollow) |
|
| 71 |
-} |
| 72 | 1 |
deleted file mode 100644 |
| ... | ... |
@@ -1,111 +0,0 @@ |
| 1 |
-// mksyscall.pl -l32 xattr_darwin.go |
|
| 2 |
-// MACHINE GENERATED BY THE COMMAND ABOVE; DO NOT EDIT |
|
| 3 |
- |
|
| 4 |
-package sysx |
|
| 5 |
- |
|
| 6 |
-import ( |
|
| 7 |
- "syscall" |
|
| 8 |
- "unsafe" |
|
| 9 |
-) |
|
| 10 |
- |
|
| 11 |
-// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT |
|
| 12 |
- |
|
| 13 |
-func getxattr(path string, attr string, dest []byte, pos int, options int) (sz int, err error) {
|
|
| 14 |
- var _p0 *byte |
|
| 15 |
- _p0, err = syscall.BytePtrFromString(path) |
|
| 16 |
- if err != nil {
|
|
| 17 |
- return |
|
| 18 |
- } |
|
| 19 |
- var _p1 *byte |
|
| 20 |
- _p1, err = syscall.BytePtrFromString(attr) |
|
| 21 |
- if err != nil {
|
|
| 22 |
- return |
|
| 23 |
- } |
|
| 24 |
- var _p2 unsafe.Pointer |
|
| 25 |
- if len(dest) > 0 {
|
|
| 26 |
- _p2 = unsafe.Pointer(&dest[0]) |
|
| 27 |
- } else {
|
|
| 28 |
- _p2 = unsafe.Pointer(&_zero) |
|
| 29 |
- } |
|
| 30 |
- r0, _, e1 := syscall.Syscall6(syscall.SYS_GETXATTR, uintptr(unsafe.Pointer(_p0)), uintptr(unsafe.Pointer(_p1)), uintptr(_p2), uintptr(len(dest)), uintptr(pos), uintptr(options)) |
|
| 31 |
- use(unsafe.Pointer(_p0)) |
|
| 32 |
- use(unsafe.Pointer(_p1)) |
|
| 33 |
- sz = int(r0) |
|
| 34 |
- if e1 != 0 {
|
|
| 35 |
- err = errnoErr(e1) |
|
| 36 |
- } |
|
| 37 |
- return |
|
| 38 |
-} |
|
| 39 |
- |
|
| 40 |
-// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT |
|
| 41 |
- |
|
| 42 |
-func setxattr(path string, attr string, data []byte, flags int) (err error) {
|
|
| 43 |
- var _p0 *byte |
|
| 44 |
- _p0, err = syscall.BytePtrFromString(path) |
|
| 45 |
- if err != nil {
|
|
| 46 |
- return |
|
| 47 |
- } |
|
| 48 |
- var _p1 *byte |
|
| 49 |
- _p1, err = syscall.BytePtrFromString(attr) |
|
| 50 |
- if err != nil {
|
|
| 51 |
- return |
|
| 52 |
- } |
|
| 53 |
- var _p2 unsafe.Pointer |
|
| 54 |
- if len(data) > 0 {
|
|
| 55 |
- _p2 = unsafe.Pointer(&data[0]) |
|
| 56 |
- } else {
|
|
| 57 |
- _p2 = unsafe.Pointer(&_zero) |
|
| 58 |
- } |
|
| 59 |
- _, _, e1 := syscall.Syscall6(syscall.SYS_SETXATTR, uintptr(unsafe.Pointer(_p0)), uintptr(unsafe.Pointer(_p1)), uintptr(_p2), uintptr(len(data)), uintptr(flags), 0) |
|
| 60 |
- use(unsafe.Pointer(_p0)) |
|
| 61 |
- use(unsafe.Pointer(_p1)) |
|
| 62 |
- if e1 != 0 {
|
|
| 63 |
- err = errnoErr(e1) |
|
| 64 |
- } |
|
| 65 |
- return |
|
| 66 |
-} |
|
| 67 |
- |
|
| 68 |
-// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT |
|
| 69 |
- |
|
| 70 |
-func removexattr(path string, attr string, options int) (err error) {
|
|
| 71 |
- var _p0 *byte |
|
| 72 |
- _p0, err = syscall.BytePtrFromString(path) |
|
| 73 |
- if err != nil {
|
|
| 74 |
- return |
|
| 75 |
- } |
|
| 76 |
- var _p1 *byte |
|
| 77 |
- _p1, err = syscall.BytePtrFromString(attr) |
|
| 78 |
- if err != nil {
|
|
| 79 |
- return |
|
| 80 |
- } |
|
| 81 |
- _, _, e1 := syscall.Syscall(syscall.SYS_REMOVEXATTR, uintptr(unsafe.Pointer(_p0)), uintptr(unsafe.Pointer(_p1)), uintptr(options)) |
|
| 82 |
- use(unsafe.Pointer(_p0)) |
|
| 83 |
- use(unsafe.Pointer(_p1)) |
|
| 84 |
- if e1 != 0 {
|
|
| 85 |
- err = errnoErr(e1) |
|
| 86 |
- } |
|
| 87 |
- return |
|
| 88 |
-} |
|
| 89 |
- |
|
| 90 |
-// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT |
|
| 91 |
- |
|
| 92 |
-func listxattr(path string, dest []byte, options int) (sz int, err error) {
|
|
| 93 |
- var _p0 *byte |
|
| 94 |
- _p0, err = syscall.BytePtrFromString(path) |
|
| 95 |
- if err != nil {
|
|
| 96 |
- return |
|
| 97 |
- } |
|
| 98 |
- var _p1 unsafe.Pointer |
|
| 99 |
- if len(dest) > 0 {
|
|
| 100 |
- _p1 = unsafe.Pointer(&dest[0]) |
|
| 101 |
- } else {
|
|
| 102 |
- _p1 = unsafe.Pointer(&_zero) |
|
| 103 |
- } |
|
| 104 |
- r0, _, e1 := syscall.Syscall6(syscall.SYS_LISTXATTR, uintptr(unsafe.Pointer(_p0)), uintptr(_p1), uintptr(len(dest)), uintptr(options), 0, 0) |
|
| 105 |
- use(unsafe.Pointer(_p0)) |
|
| 106 |
- sz = int(r0) |
|
| 107 |
- if e1 != 0 {
|
|
| 108 |
- err = errnoErr(e1) |
|
| 109 |
- } |
|
| 110 |
- return |
|
| 111 |
-} |
| 112 | 1 |
deleted file mode 100644 |
| ... | ... |
@@ -1,111 +0,0 @@ |
| 1 |
-// mksyscall.pl xattr_darwin.go |
|
| 2 |
-// MACHINE GENERATED BY THE COMMAND ABOVE; DO NOT EDIT |
|
| 3 |
- |
|
| 4 |
-package sysx |
|
| 5 |
- |
|
| 6 |
-import ( |
|
| 7 |
- "syscall" |
|
| 8 |
- "unsafe" |
|
| 9 |
-) |
|
| 10 |
- |
|
| 11 |
-// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT |
|
| 12 |
- |
|
| 13 |
-func getxattr(path string, attr string, dest []byte, pos int, options int) (sz int, err error) {
|
|
| 14 |
- var _p0 *byte |
|
| 15 |
- _p0, err = syscall.BytePtrFromString(path) |
|
| 16 |
- if err != nil {
|
|
| 17 |
- return |
|
| 18 |
- } |
|
| 19 |
- var _p1 *byte |
|
| 20 |
- _p1, err = syscall.BytePtrFromString(attr) |
|
| 21 |
- if err != nil {
|
|
| 22 |
- return |
|
| 23 |
- } |
|
| 24 |
- var _p2 unsafe.Pointer |
|
| 25 |
- if len(dest) > 0 {
|
|
| 26 |
- _p2 = unsafe.Pointer(&dest[0]) |
|
| 27 |
- } else {
|
|
| 28 |
- _p2 = unsafe.Pointer(&_zero) |
|
| 29 |
- } |
|
| 30 |
- r0, _, e1 := syscall.Syscall6(syscall.SYS_GETXATTR, uintptr(unsafe.Pointer(_p0)), uintptr(unsafe.Pointer(_p1)), uintptr(_p2), uintptr(len(dest)), uintptr(pos), uintptr(options)) |
|
| 31 |
- use(unsafe.Pointer(_p0)) |
|
| 32 |
- use(unsafe.Pointer(_p1)) |
|
| 33 |
- sz = int(r0) |
|
| 34 |
- if e1 != 0 {
|
|
| 35 |
- err = errnoErr(e1) |
|
| 36 |
- } |
|
| 37 |
- return |
|
| 38 |
-} |
|
| 39 |
- |
|
| 40 |
-// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT |
|
| 41 |
- |
|
| 42 |
-func setxattr(path string, attr string, data []byte, flags int) (err error) {
|
|
| 43 |
- var _p0 *byte |
|
| 44 |
- _p0, err = syscall.BytePtrFromString(path) |
|
| 45 |
- if err != nil {
|
|
| 46 |
- return |
|
| 47 |
- } |
|
| 48 |
- var _p1 *byte |
|
| 49 |
- _p1, err = syscall.BytePtrFromString(attr) |
|
| 50 |
- if err != nil {
|
|
| 51 |
- return |
|
| 52 |
- } |
|
| 53 |
- var _p2 unsafe.Pointer |
|
| 54 |
- if len(data) > 0 {
|
|
| 55 |
- _p2 = unsafe.Pointer(&data[0]) |
|
| 56 |
- } else {
|
|
| 57 |
- _p2 = unsafe.Pointer(&_zero) |
|
| 58 |
- } |
|
| 59 |
- _, _, e1 := syscall.Syscall6(syscall.SYS_SETXATTR, uintptr(unsafe.Pointer(_p0)), uintptr(unsafe.Pointer(_p1)), uintptr(_p2), uintptr(len(data)), uintptr(flags), 0) |
|
| 60 |
- use(unsafe.Pointer(_p0)) |
|
| 61 |
- use(unsafe.Pointer(_p1)) |
|
| 62 |
- if e1 != 0 {
|
|
| 63 |
- err = errnoErr(e1) |
|
| 64 |
- } |
|
| 65 |
- return |
|
| 66 |
-} |
|
| 67 |
- |
|
| 68 |
-// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT |
|
| 69 |
- |
|
| 70 |
-func removexattr(path string, attr string, options int) (err error) {
|
|
| 71 |
- var _p0 *byte |
|
| 72 |
- _p0, err = syscall.BytePtrFromString(path) |
|
| 73 |
- if err != nil {
|
|
| 74 |
- return |
|
| 75 |
- } |
|
| 76 |
- var _p1 *byte |
|
| 77 |
- _p1, err = syscall.BytePtrFromString(attr) |
|
| 78 |
- if err != nil {
|
|
| 79 |
- return |
|
| 80 |
- } |
|
| 81 |
- _, _, e1 := syscall.Syscall(syscall.SYS_REMOVEXATTR, uintptr(unsafe.Pointer(_p0)), uintptr(unsafe.Pointer(_p1)), uintptr(options)) |
|
| 82 |
- use(unsafe.Pointer(_p0)) |
|
| 83 |
- use(unsafe.Pointer(_p1)) |
|
| 84 |
- if e1 != 0 {
|
|
| 85 |
- err = errnoErr(e1) |
|
| 86 |
- } |
|
| 87 |
- return |
|
| 88 |
-} |
|
| 89 |
- |
|
| 90 |
-// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT |
|
| 91 |
- |
|
| 92 |
-func listxattr(path string, dest []byte, options int) (sz int, err error) {
|
|
| 93 |
- var _p0 *byte |
|
| 94 |
- _p0, err = syscall.BytePtrFromString(path) |
|
| 95 |
- if err != nil {
|
|
| 96 |
- return |
|
| 97 |
- } |
|
| 98 |
- var _p1 unsafe.Pointer |
|
| 99 |
- if len(dest) > 0 {
|
|
| 100 |
- _p1 = unsafe.Pointer(&dest[0]) |
|
| 101 |
- } else {
|
|
| 102 |
- _p1 = unsafe.Pointer(&_zero) |
|
| 103 |
- } |
|
| 104 |
- r0, _, e1 := syscall.Syscall6(syscall.SYS_LISTXATTR, uintptr(unsafe.Pointer(_p0)), uintptr(_p1), uintptr(len(dest)), uintptr(options), 0, 0) |
|
| 105 |
- use(unsafe.Pointer(_p0)) |
|
| 106 |
- sz = int(r0) |
|
| 107 |
- if e1 != 0 {
|
|
| 108 |
- err = errnoErr(e1) |
|
| 109 |
- } |
|
| 110 |
- return |
|
| 111 |
-} |
| 112 | 1 |
deleted file mode 100644 |
| ... | ... |
@@ -1,12 +0,0 @@ |
| 1 |
-package sysx |
|
| 2 |
- |
|
| 3 |
-import ( |
|
| 4 |
- "errors" |
|
| 5 |
-) |
|
| 6 |
- |
|
| 7 |
-// Initial stub version for FreeBSD. FreeBSD has a different |
|
| 8 |
-// syscall API from Darwin and Linux for extended attributes; |
|
| 9 |
-// it is also not widely used. It is not exposed at all by the |
|
| 10 |
-// Go syscall package, so we need to implement directly eventually. |
|
| 11 |
- |
|
| 12 |
-var unsupported = errors.New("extended attributes unsupported on FreeBSD")
|
| 13 | 1 |
deleted file mode 100644 |
| ... | ... |
@@ -1,44 +0,0 @@ |
| 1 |
-package sysx |
|
| 2 |
- |
|
| 3 |
-import "golang.org/x/sys/unix" |
|
| 4 |
- |
|
| 5 |
-// Listxattr calls syscall listxattr and reads all content |
|
| 6 |
-// and returns a string array |
|
| 7 |
-func Listxattr(path string) ([]string, error) {
|
|
| 8 |
- return listxattrAll(path, unix.Listxattr) |
|
| 9 |
-} |
|
| 10 |
- |
|
| 11 |
-// Removexattr calls syscall removexattr |
|
| 12 |
-func Removexattr(path string, attr string) (err error) {
|
|
| 13 |
- return unix.Removexattr(path, attr) |
|
| 14 |
-} |
|
| 15 |
- |
|
| 16 |
-// Setxattr calls syscall setxattr |
|
| 17 |
-func Setxattr(path string, attr string, data []byte, flags int) (err error) {
|
|
| 18 |
- return unix.Setxattr(path, attr, data, flags) |
|
| 19 |
-} |
|
| 20 |
- |
|
| 21 |
-// Getxattr calls syscall getxattr |
|
| 22 |
-func Getxattr(path, attr string) ([]byte, error) {
|
|
| 23 |
- return getxattrAll(path, attr, unix.Getxattr) |
|
| 24 |
-} |
|
| 25 |
- |
|
| 26 |
-// LListxattr lists xattrs, not following symlinks |
|
| 27 |
-func LListxattr(path string) ([]string, error) {
|
|
| 28 |
- return listxattrAll(path, unix.Llistxattr) |
|
| 29 |
-} |
|
| 30 |
- |
|
| 31 |
-// LRemovexattr removes an xattr, not following symlinks |
|
| 32 |
-func LRemovexattr(path string, attr string) (err error) {
|
|
| 33 |
- return unix.Lremovexattr(path, attr) |
|
| 34 |
-} |
|
| 35 |
- |
|
| 36 |
-// LSetxattr sets an xattr, not following symlinks |
|
| 37 |
-func LSetxattr(path string, attr string, data []byte, flags int) (err error) {
|
|
| 38 |
- return unix.Lsetxattr(path, attr, data, flags) |
|
| 39 |
-} |
|
| 40 |
- |
|
| 41 |
-// LGetxattr gets an xattr, not following symlinks |
|
| 42 |
-func LGetxattr(path, attr string) ([]byte, error) {
|
|
| 43 |
- return getxattrAll(path, attr, unix.Lgetxattr) |
|
| 44 |
-} |
| 8 | 1 |
deleted file mode 100644 |
| ... | ... |
@@ -1,12 +0,0 @@ |
| 1 |
-package sysx |
|
| 2 |
- |
|
| 3 |
-import ( |
|
| 4 |
- "errors" |
|
| 5 |
-) |
|
| 6 |
- |
|
| 7 |
-// Initial stub version for Solaris. Solaris has a different |
|
| 8 |
-// syscall API from Darwin and Linux for extended attributes; |
|
| 9 |
-// it is also not widely used. It is not exposed at all by the |
|
| 10 |
-// Go syscall package, so we need to implement directly eventually. |
|
| 11 |
- |
|
| 12 |
-var unsupported = errors.New("extended attributes unsupported on Solaris")
|
| ... | ... |
@@ -1,7 +1,14 @@ |
| 1 |
-// +build freebsd openbsd solaris |
|
| 1 |
+// +build !linux,!darwin |
|
| 2 | 2 |
|
| 3 | 3 |
package sysx |
| 4 | 4 |
|
| 5 |
+import ( |
|
| 6 |
+ "errors" |
|
| 7 |
+ "runtime" |
|
| 8 |
+) |
|
| 9 |
+ |
|
| 10 |
+var unsupported = errors.New("extended attributes unsupported on " + runtime.GOOS)
|
|
| 11 |
+ |
|
| 5 | 12 |
// Listxattr calls syscall listxattr and reads all content |
| 6 | 13 |
// and returns a string array |
| 7 | 14 |
func Listxattr(path string) ([]string, error) {
|
| ... | ... |
@@ -10,4 +10,4 @@ github.com/spf13/pflag 4c012f6dcd9546820e378d0bdda4d8fc772cdfea |
| 10 | 10 |
golang.org/x/crypto 9f005a07e0d31d45e6656d241bb5c0f2efd4bc94 |
| 11 | 11 |
golang.org/x/net a337091b0525af65de94df2eb7e98bd9962dcbe2 |
| 12 | 12 |
golang.org/x/sync 450f422ab23cf9881c94e2db30cac0eb1b7cf80c |
| 13 |
-golang.org/x/sys 665f6529cca930e27b831a0d1dafffbe1c172924 |
|
| 13 |
+golang.org/x/sys 77b0e4315053a57ed2962443614bdb28db152054 |
| 5 | 5 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,197 @@ |
| 0 |
+// Copyright 2012 The Go Authors. All rights reserved. |
|
| 1 |
+// Use of this source code is governed by a BSD-style |
|
| 2 |
+// license that can be found in the LICENSE file. |
|
| 3 |
+ |
|
| 4 |
+// This code can be compiled and used to test the otr package against libotr. |
|
| 5 |
+// See otr_test.go. |
|
| 6 |
+ |
|
| 7 |
+// +build ignore |
|
| 8 |
+ |
|
| 9 |
+#include <stdio.h> |
|
| 10 |
+#include <stdlib.h> |
|
| 11 |
+#include <unistd.h> |
|
| 12 |
+ |
|
| 13 |
+#include <proto.h> |
|
| 14 |
+#include <message.h> |
|
| 15 |
+#include <privkey.h> |
|
| 16 |
+ |
|
| 17 |
+static int g_session_established = 0; |
|
| 18 |
+ |
|
| 19 |
+OtrlPolicy policy(void *opdata, ConnContext *context) {
|
|
| 20 |
+ return OTRL_POLICY_ALWAYS; |
|
| 21 |
+} |
|
| 22 |
+ |
|
| 23 |
+int is_logged_in(void *opdata, const char *accountname, const char *protocol, |
|
| 24 |
+ const char *recipient) {
|
|
| 25 |
+ return 1; |
|
| 26 |
+} |
|
| 27 |
+ |
|
| 28 |
+void inject_message(void *opdata, const char *accountname, const char *protocol, |
|
| 29 |
+ const char *recipient, const char *message) {
|
|
| 30 |
+ printf("%s\n", message);
|
|
| 31 |
+ fflush(stdout); |
|
| 32 |
+ fprintf(stderr, "libotr helper sent: %s\n", message); |
|
| 33 |
+} |
|
| 34 |
+ |
|
| 35 |
+void update_context_list(void *opdata) {}
|
|
| 36 |
+ |
|
| 37 |
+void new_fingerprint(void *opdata, OtrlUserState us, const char *accountname, |
|
| 38 |
+ const char *protocol, const char *username, |
|
| 39 |
+ unsigned char fingerprint[20]) {
|
|
| 40 |
+ fprintf(stderr, "NEW FINGERPRINT\n"); |
|
| 41 |
+ g_session_established = 1; |
|
| 42 |
+} |
|
| 43 |
+ |
|
| 44 |
+void write_fingerprints(void *opdata) {}
|
|
| 45 |
+ |
|
| 46 |
+void gone_secure(void *opdata, ConnContext *context) {}
|
|
| 47 |
+ |
|
| 48 |
+void gone_insecure(void *opdata, ConnContext *context) {}
|
|
| 49 |
+ |
|
| 50 |
+void still_secure(void *opdata, ConnContext *context, int is_reply) {}
|
|
| 51 |
+ |
|
| 52 |
+int max_message_size(void *opdata, ConnContext *context) { return 99999; }
|
|
| 53 |
+ |
|
| 54 |
+const char *account_name(void *opdata, const char *account, |
|
| 55 |
+ const char *protocol) {
|
|
| 56 |
+ return "ACCOUNT"; |
|
| 57 |
+} |
|
| 58 |
+ |
|
| 59 |
+void account_name_free(void *opdata, const char *account_name) {}
|
|
| 60 |
+ |
|
| 61 |
+const char *error_message(void *opdata, ConnContext *context, |
|
| 62 |
+ OtrlErrorCode err_code) {
|
|
| 63 |
+ return "ERR"; |
|
| 64 |
+} |
|
| 65 |
+ |
|
| 66 |
+void error_message_free(void *opdata, const char *msg) {}
|
|
| 67 |
+ |
|
| 68 |
+void resent_msg_prefix_free(void *opdata, const char *prefix) {}
|
|
| 69 |
+ |
|
| 70 |
+void handle_smp_event(void *opdata, OtrlSMPEvent smp_event, |
|
| 71 |
+ ConnContext *context, unsigned short progress_event, |
|
| 72 |
+ char *question) {}
|
|
| 73 |
+ |
|
| 74 |
+void handle_msg_event(void *opdata, OtrlMessageEvent msg_event, |
|
| 75 |
+ ConnContext *context, const char *message, |
|
| 76 |
+ gcry_error_t err) {
|
|
| 77 |
+ fprintf(stderr, "msg event: %d %s\n", msg_event, message); |
|
| 78 |
+} |
|
| 79 |
+ |
|
| 80 |
+OtrlMessageAppOps uiops = {
|
|
| 81 |
+ policy, |
|
| 82 |
+ NULL, |
|
| 83 |
+ is_logged_in, |
|
| 84 |
+ inject_message, |
|
| 85 |
+ update_context_list, |
|
| 86 |
+ new_fingerprint, |
|
| 87 |
+ write_fingerprints, |
|
| 88 |
+ gone_secure, |
|
| 89 |
+ gone_insecure, |
|
| 90 |
+ still_secure, |
|
| 91 |
+ max_message_size, |
|
| 92 |
+ account_name, |
|
| 93 |
+ account_name_free, |
|
| 94 |
+ NULL, /* received_symkey */ |
|
| 95 |
+ error_message, |
|
| 96 |
+ error_message_free, |
|
| 97 |
+ NULL, /* resent_msg_prefix */ |
|
| 98 |
+ resent_msg_prefix_free, |
|
| 99 |
+ handle_smp_event, |
|
| 100 |
+ handle_msg_event, |
|
| 101 |
+ NULL /* create_instag */, |
|
| 102 |
+ NULL /* convert_msg */, |
|
| 103 |
+ NULL /* convert_free */, |
|
| 104 |
+ NULL /* timer_control */, |
|
| 105 |
+}; |
|
| 106 |
+ |
|
| 107 |
+static const char kPrivateKeyData[] = |
|
| 108 |
+ "(privkeys (account (name \"account\") (protocol proto) (private-key (dsa " |
|
| 109 |
+ "(p " |
|
| 110 |
+ "#00FC07ABCF0DC916AFF6E9AE47BEF60C7AB9B4D6B2469E436630E36F8A489BE812486A09F" |
|
| 111 |
+ "30B71224508654940A835301ACC525A4FF133FC152CC53DCC59D65C30A54F1993FE13FE63E" |
|
| 112 |
+ "5823D4C746DB21B90F9B9C00B49EC7404AB1D929BA7FBA12F2E45C6E0A651689750E8528AB" |
|
| 113 |
+ "8C031D3561FECEE72EBB4A090D450A9B7A857#) (q " |
|
| 114 |
+ "#00997BD266EF7B1F60A5C23F3A741F2AEFD07A2081#) (g " |
|
| 115 |
+ "#535E360E8A95EBA46A4F7DE50AD6E9B2A6DB785A66B64EB9F20338D2A3E8FB0E94725848F" |
|
| 116 |
+ "1AA6CC567CB83A1CC517EC806F2E92EAE71457E80B2210A189B91250779434B41FC8A8873F" |
|
| 117 |
+ "6DB94BEA7D177F5D59E7E114EE10A49CFD9CEF88AE43387023B672927BA74B04EB6BBB5E57" |
|
| 118 |
+ "597766A2F9CE3857D7ACE3E1E3BC1FC6F26#) (y " |
|
| 119 |
+ "#0AC8670AD767D7A8D9D14CC1AC6744CD7D76F993B77FFD9E39DF01E5A6536EF65E775FCEF" |
|
| 120 |
+ "2A983E2A19BD6415500F6979715D9FD1257E1FE2B6F5E1E74B333079E7C880D39868462A93" |
|
| 121 |
+ "454B41877BE62E5EF0A041C2EE9C9E76BD1E12AE25D9628DECB097025DD625EF49C3258A1A" |
|
| 122 |
+ "3C0FF501E3DC673B76D7BABF349009B6ECF#) (x " |
|
| 123 |
+ "#14D0345A3562C480A039E3C72764F72D79043216#)))))\n"; |
|
| 124 |
+ |
|
| 125 |
+int main() {
|
|
| 126 |
+ OTRL_INIT; |
|
| 127 |
+ |
|
| 128 |
+ // We have to write the private key information to a file because the libotr |
|
| 129 |
+ // API demands a filename to read from. |
|
| 130 |
+ const char *tmpdir = "/tmp"; |
|
| 131 |
+ if (getenv("TMP")) {
|
|
| 132 |
+ tmpdir = getenv("TMP");
|
|
| 133 |
+ } |
|
| 134 |
+ |
|
| 135 |
+ char private_key_file[256]; |
|
| 136 |
+ snprintf(private_key_file, sizeof(private_key_file), |
|
| 137 |
+ "%s/libotr_test_helper_privatekeys-XXXXXX", tmpdir); |
|
| 138 |
+ int fd = mkstemp(private_key_file); |
|
| 139 |
+ if (fd == -1) {
|
|
| 140 |
+ perror("creating temp file");
|
|
| 141 |
+ } |
|
| 142 |
+ write(fd, kPrivateKeyData, sizeof(kPrivateKeyData) - 1); |
|
| 143 |
+ close(fd); |
|
| 144 |
+ |
|
| 145 |
+ OtrlUserState userstate = otrl_userstate_create(); |
|
| 146 |
+ otrl_privkey_read(userstate, private_key_file); |
|
| 147 |
+ unlink(private_key_file); |
|
| 148 |
+ |
|
| 149 |
+ fprintf(stderr, "libotr helper started\n"); |
|
| 150 |
+ |
|
| 151 |
+ char buf[4096]; |
|
| 152 |
+ |
|
| 153 |
+ for (;;) {
|
|
| 154 |
+ char *message = fgets(buf, sizeof(buf), stdin); |
|
| 155 |
+ if (strlen(message) == 0) {
|
|
| 156 |
+ break; |
|
| 157 |
+ } |
|
| 158 |
+ message[strlen(message) - 1] = 0; |
|
| 159 |
+ fprintf(stderr, "libotr helper got: %s\n", message); |
|
| 160 |
+ |
|
| 161 |
+ char *newmessage = NULL; |
|
| 162 |
+ OtrlTLV *tlvs; |
|
| 163 |
+ int ignore_message = otrl_message_receiving( |
|
| 164 |
+ userstate, &uiops, NULL, "account", "proto", "peer", message, |
|
| 165 |
+ &newmessage, &tlvs, NULL, NULL, NULL); |
|
| 166 |
+ if (tlvs) {
|
|
| 167 |
+ otrl_tlv_free(tlvs); |
|
| 168 |
+ } |
|
| 169 |
+ |
|
| 170 |
+ if (newmessage != NULL) {
|
|
| 171 |
+ fprintf(stderr, "libotr got: %s\n", newmessage); |
|
| 172 |
+ otrl_message_free(newmessage); |
|
| 173 |
+ |
|
| 174 |
+ gcry_error_t err; |
|
| 175 |
+ char *newmessage = NULL; |
|
| 176 |
+ |
|
| 177 |
+ err = otrl_message_sending(userstate, &uiops, NULL, "account", "proto", |
|
| 178 |
+ "peer", 0, "test message", NULL, &newmessage, |
|
| 179 |
+ OTRL_FRAGMENT_SEND_SKIP, NULL, NULL, NULL); |
|
| 180 |
+ if (newmessage == NULL) {
|
|
| 181 |
+ fprintf(stderr, "libotr didn't encrypt message\n"); |
|
| 182 |
+ return 1; |
|
| 183 |
+ } |
|
| 184 |
+ write(1, newmessage, strlen(newmessage)); |
|
| 185 |
+ write(1, "\n", 1); |
|
| 186 |
+ fprintf(stderr, "libotr sent: %s\n", newmessage); |
|
| 187 |
+ otrl_message_free(newmessage); |
|
| 188 |
+ |
|
| 189 |
+ g_session_established = 0; |
|
| 190 |
+ write(1, "?OTRv2?\n", 8); |
|
| 191 |
+ fprintf(stderr, "libotr sent: ?OTRv2\n"); |
|
| 192 |
+ } |
|
| 193 |
+ } |
|
| 194 |
+ |
|
| 195 |
+ return 0; |
|
| 196 |
+} |
| 0 | 197 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,1415 @@ |
| 0 |
+// Copyright 2012 The Go Authors. All rights reserved. |
|
| 1 |
+// Use of this source code is governed by a BSD-style |
|
| 2 |
+// license that can be found in the LICENSE file. |
|
| 3 |
+ |
|
| 4 |
+// Package otr implements the Off The Record protocol as specified in |
|
| 5 |
+// http://www.cypherpunks.ca/otr/Protocol-v2-3.1.0.html |
|
| 6 |
+package otr // import "golang.org/x/crypto/otr" |
|
| 7 |
+ |
|
| 8 |
+import ( |
|
| 9 |
+ "bytes" |
|
| 10 |
+ "crypto/aes" |
|
| 11 |
+ "crypto/cipher" |
|
| 12 |
+ "crypto/dsa" |
|
| 13 |
+ "crypto/hmac" |
|
| 14 |
+ "crypto/rand" |
|
| 15 |
+ "crypto/sha1" |
|
| 16 |
+ "crypto/sha256" |
|
| 17 |
+ "crypto/subtle" |
|
| 18 |
+ "encoding/base64" |
|
| 19 |
+ "encoding/hex" |
|
| 20 |
+ "errors" |
|
| 21 |
+ "hash" |
|
| 22 |
+ "io" |
|
| 23 |
+ "math/big" |
|
| 24 |
+ "strconv" |
|
| 25 |
+) |
|
| 26 |
+ |
|
| 27 |
+// SecurityChange describes a change in the security state of a Conversation. |
|
| 28 |
+type SecurityChange int |
|
| 29 |
+ |
|
| 30 |
+const ( |
|
| 31 |
+ NoChange SecurityChange = iota |
|
| 32 |
+ // NewKeys indicates that a key exchange has completed. This occurs |
|
| 33 |
+ // when a conversation first becomes encrypted, and when the keys are |
|
| 34 |
+ // renegotiated within an encrypted conversation. |
|
| 35 |
+ NewKeys |
|
| 36 |
+ // SMPSecretNeeded indicates that the peer has started an |
|
| 37 |
+ // authentication and that we need to supply a secret. Call SMPQuestion |
|
| 38 |
+ // to get the optional, human readable challenge and then Authenticate |
|
| 39 |
+ // to supply the matching secret. |
|
| 40 |
+ SMPSecretNeeded |
|
| 41 |
+ // SMPComplete indicates that an authentication completed. The identity |
|
| 42 |
+ // of the peer has now been confirmed. |
|
| 43 |
+ SMPComplete |
|
| 44 |
+ // SMPFailed indicates that an authentication failed. |
|
| 45 |
+ SMPFailed |
|
| 46 |
+ // ConversationEnded indicates that the peer ended the secure |
|
| 47 |
+ // conversation. |
|
| 48 |
+ ConversationEnded |
|
| 49 |
+) |
|
| 50 |
+ |
|
| 51 |
+// QueryMessage can be sent to a peer to start an OTR conversation. |
|
| 52 |
+var QueryMessage = "?OTRv2?" |
|
| 53 |
+ |
|
| 54 |
+// ErrorPrefix can be used to make an OTR error by appending an error message |
|
| 55 |
+// to it. |
|
| 56 |
+var ErrorPrefix = "?OTR Error:" |
|
| 57 |
+ |
|
| 58 |
+var ( |
|
| 59 |
+ fragmentPartSeparator = []byte(",")
|
|
| 60 |
+ fragmentPrefix = []byte("?OTR,")
|
|
| 61 |
+ msgPrefix = []byte("?OTR:")
|
|
| 62 |
+ queryMarker = []byte("?OTR")
|
|
| 63 |
+) |
|
| 64 |
+ |
|
| 65 |
+// isQuery attempts to parse an OTR query from msg and returns the greatest |
|
| 66 |
+// common version, or 0 if msg is not an OTR query. |
|
| 67 |
+func isQuery(msg []byte) (greatestCommonVersion int) {
|
|
| 68 |
+ pos := bytes.Index(msg, queryMarker) |
|
| 69 |
+ if pos == -1 {
|
|
| 70 |
+ return 0 |
|
| 71 |
+ } |
|
| 72 |
+ for i, c := range msg[pos+len(queryMarker):] {
|
|
| 73 |
+ if i == 0 {
|
|
| 74 |
+ if c == '?' {
|
|
| 75 |
+ // Indicates support for version 1, but we don't |
|
| 76 |
+ // implement that. |
|
| 77 |
+ continue |
|
| 78 |
+ } |
|
| 79 |
+ |
|
| 80 |
+ if c != 'v' {
|
|
| 81 |
+ // Invalid message |
|
| 82 |
+ return 0 |
|
| 83 |
+ } |
|
| 84 |
+ |
|
| 85 |
+ continue |
|
| 86 |
+ } |
|
| 87 |
+ |
|
| 88 |
+ if c == '?' {
|
|
| 89 |
+ // End of message |
|
| 90 |
+ return |
|
| 91 |
+ } |
|
| 92 |
+ |
|
| 93 |
+ if c == ' ' || c == '\t' {
|
|
| 94 |
+ // Probably an invalid message |
|
| 95 |
+ return 0 |
|
| 96 |
+ } |
|
| 97 |
+ |
|
| 98 |
+ if c == '2' {
|
|
| 99 |
+ greatestCommonVersion = 2 |
|
| 100 |
+ } |
|
| 101 |
+ } |
|
| 102 |
+ |
|
| 103 |
+ return 0 |
|
| 104 |
+} |
|
| 105 |
+ |
|
| 106 |
+const ( |
|
| 107 |
+ statePlaintext = iota |
|
| 108 |
+ stateEncrypted |
|
| 109 |
+ stateFinished |
|
| 110 |
+) |
|
| 111 |
+ |
|
| 112 |
+const ( |
|
| 113 |
+ authStateNone = iota |
|
| 114 |
+ authStateAwaitingDHKey |
|
| 115 |
+ authStateAwaitingRevealSig |
|
| 116 |
+ authStateAwaitingSig |
|
| 117 |
+) |
|
| 118 |
+ |
|
| 119 |
+const ( |
|
| 120 |
+ msgTypeDHCommit = 2 |
|
| 121 |
+ msgTypeData = 3 |
|
| 122 |
+ msgTypeDHKey = 10 |
|
| 123 |
+ msgTypeRevealSig = 17 |
|
| 124 |
+ msgTypeSig = 18 |
|
| 125 |
+) |
|
| 126 |
+ |
|
| 127 |
+const ( |
|
| 128 |
+ // If the requested fragment size is less than this, it will be ignored. |
|
| 129 |
+ minFragmentSize = 18 |
|
| 130 |
+ // Messages are padded to a multiple of this number of bytes. |
|
| 131 |
+ paddingGranularity = 256 |
|
| 132 |
+ // The number of bytes in a Diffie-Hellman private value (320-bits). |
|
| 133 |
+ dhPrivateBytes = 40 |
|
| 134 |
+ // The number of bytes needed to represent an element of the DSA |
|
| 135 |
+ // subgroup (160-bits). |
|
| 136 |
+ dsaSubgroupBytes = 20 |
|
| 137 |
+ // The number of bytes of the MAC that are sent on the wire (160-bits). |
|
| 138 |
+ macPrefixBytes = 20 |
|
| 139 |
+) |
|
| 140 |
+ |
|
| 141 |
+// These are the global, common group parameters for OTR. |
|
| 142 |
+var ( |
|
| 143 |
+ p *big.Int // group prime |
|
| 144 |
+ g *big.Int // group generator |
|
| 145 |
+ q *big.Int // group order |
|
| 146 |
+ pMinus2 *big.Int |
|
| 147 |
+) |
|
| 148 |
+ |
|
| 149 |
+func init() {
|
|
| 150 |
+ p, _ = new(big.Int).SetString("FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7EDEE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3DC2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F83655D23DCA3AD961C62F356208552BB9ED529077096966D670C354E4ABC9804F1746C08CA237327FFFFFFFFFFFFFFFF", 16)
|
|
| 151 |
+ q, _ = new(big.Int).SetString("7FFFFFFFFFFFFFFFE487ED5110B4611A62633145C06E0E68948127044533E63A0105DF531D89CD9128A5043CC71A026EF7CA8CD9E69D218D98158536F92F8A1BA7F09AB6B6A8E122F242DABB312F3F637A262174D31BF6B585FFAE5B7A035BF6F71C35FDAD44CFD2D74F9208BE258FF324943328F6722D9EE1003E5C50B1DF82CC6D241B0E2AE9CD348B1FD47E9267AFC1B2AE91EE51D6CB0E3179AB1042A95DCF6A9483B84B4B36B3861AA7255E4C0278BA36046511B993FFFFFFFFFFFFFFFF", 16)
|
|
| 152 |
+ g = new(big.Int).SetInt64(2) |
|
| 153 |
+ pMinus2 = new(big.Int).Sub(p, g) |
|
| 154 |
+} |
|
| 155 |
+ |
|
| 156 |
+// Conversation represents a relation with a peer. The zero value is a valid |
|
| 157 |
+// Conversation, although PrivateKey must be set. |
|
| 158 |
+// |
|
| 159 |
+// When communicating with a peer, all inbound messages should be passed to |
|
| 160 |
+// Conversation.Receive and all outbound messages to Conversation.Send. The |
|
| 161 |
+// Conversation will take care of maintaining the encryption state and |
|
| 162 |
+// negotiating encryption as needed. |
|
| 163 |
+type Conversation struct {
|
|
| 164 |
+ // PrivateKey contains the private key to use to sign key exchanges. |
|
| 165 |
+ PrivateKey *PrivateKey |
|
| 166 |
+ |
|
| 167 |
+ // Rand can be set to override the entropy source. Otherwise, |
|
| 168 |
+ // crypto/rand will be used. |
|
| 169 |
+ Rand io.Reader |
|
| 170 |
+ // If FragmentSize is set, all messages produced by Receive and Send |
|
| 171 |
+ // will be fragmented into messages of, at most, this number of bytes. |
|
| 172 |
+ FragmentSize int |
|
| 173 |
+ |
|
| 174 |
+ // Once Receive has returned NewKeys once, the following fields are |
|
| 175 |
+ // valid. |
|
| 176 |
+ SSID [8]byte |
|
| 177 |
+ TheirPublicKey PublicKey |
|
| 178 |
+ |
|
| 179 |
+ state, authState int |
|
| 180 |
+ |
|
| 181 |
+ r [16]byte |
|
| 182 |
+ x, y *big.Int |
|
| 183 |
+ gx, gy *big.Int |
|
| 184 |
+ gxBytes []byte |
|
| 185 |
+ digest [sha256.Size]byte |
|
| 186 |
+ |
|
| 187 |
+ revealKeys, sigKeys akeKeys |
|
| 188 |
+ |
|
| 189 |
+ myKeyId uint32 |
|
| 190 |
+ myCurrentDHPub *big.Int |
|
| 191 |
+ myCurrentDHPriv *big.Int |
|
| 192 |
+ myLastDHPub *big.Int |
|
| 193 |
+ myLastDHPriv *big.Int |
|
| 194 |
+ |
|
| 195 |
+ theirKeyId uint32 |
|
| 196 |
+ theirCurrentDHPub *big.Int |
|
| 197 |
+ theirLastDHPub *big.Int |
|
| 198 |
+ |
|
| 199 |
+ keySlots [4]keySlot |
|
| 200 |
+ |
|
| 201 |
+ myCounter [8]byte |
|
| 202 |
+ theirLastCtr [8]byte |
|
| 203 |
+ oldMACs []byte |
|
| 204 |
+ |
|
| 205 |
+ k, n int // fragment state |
|
| 206 |
+ frag []byte |
|
| 207 |
+ |
|
| 208 |
+ smp smpState |
|
| 209 |
+} |
|
| 210 |
+ |
|
| 211 |
+// A keySlot contains key material for a specific (their keyid, my keyid) pair. |
|
| 212 |
+type keySlot struct {
|
|
| 213 |
+ // used is true if this slot is valid. If false, it's free for reuse. |
|
| 214 |
+ used bool |
|
| 215 |
+ theirKeyId uint32 |
|
| 216 |
+ myKeyId uint32 |
|
| 217 |
+ sendAESKey, recvAESKey []byte |
|
| 218 |
+ sendMACKey, recvMACKey []byte |
|
| 219 |
+ theirLastCtr [8]byte |
|
| 220 |
+} |
|
| 221 |
+ |
|
| 222 |
+// akeKeys are generated during key exchange. There's one set for the reveal |
|
| 223 |
+// signature message and another for the signature message. In the protocol |
|
| 224 |
+// spec the latter are indicated with a prime mark. |
|
| 225 |
+type akeKeys struct {
|
|
| 226 |
+ c [16]byte |
|
| 227 |
+ m1, m2 [32]byte |
|
| 228 |
+} |
|
| 229 |
+ |
|
| 230 |
+func (c *Conversation) rand() io.Reader {
|
|
| 231 |
+ if c.Rand != nil {
|
|
| 232 |
+ return c.Rand |
|
| 233 |
+ } |
|
| 234 |
+ return rand.Reader |
|
| 235 |
+} |
|
| 236 |
+ |
|
| 237 |
+func (c *Conversation) randMPI(buf []byte) *big.Int {
|
|
| 238 |
+ _, err := io.ReadFull(c.rand(), buf) |
|
| 239 |
+ if err != nil {
|
|
| 240 |
+ panic("otr: short read from random source")
|
|
| 241 |
+ } |
|
| 242 |
+ |
|
| 243 |
+ return new(big.Int).SetBytes(buf) |
|
| 244 |
+} |
|
| 245 |
+ |
|
| 246 |
+// tlv represents the type-length value from the protocol. |
|
| 247 |
+type tlv struct {
|
|
| 248 |
+ typ, length uint16 |
|
| 249 |
+ data []byte |
|
| 250 |
+} |
|
| 251 |
+ |
|
| 252 |
+const ( |
|
| 253 |
+ tlvTypePadding = 0 |
|
| 254 |
+ tlvTypeDisconnected = 1 |
|
| 255 |
+ tlvTypeSMP1 = 2 |
|
| 256 |
+ tlvTypeSMP2 = 3 |
|
| 257 |
+ tlvTypeSMP3 = 4 |
|
| 258 |
+ tlvTypeSMP4 = 5 |
|
| 259 |
+ tlvTypeSMPAbort = 6 |
|
| 260 |
+ tlvTypeSMP1WithQuestion = 7 |
|
| 261 |
+) |
|
| 262 |
+ |
|
| 263 |
+// Receive handles a message from a peer. It returns a human readable message, |
|
| 264 |
+// an indicator of whether that message was encrypted, a hint about the |
|
| 265 |
+// encryption state and zero or more messages to send back to the peer. |
|
| 266 |
+// These messages do not need to be passed to Send before transmission. |
|
| 267 |
+func (c *Conversation) Receive(in []byte) (out []byte, encrypted bool, change SecurityChange, toSend [][]byte, err error) {
|
|
| 268 |
+ if bytes.HasPrefix(in, fragmentPrefix) {
|
|
| 269 |
+ in, err = c.processFragment(in) |
|
| 270 |
+ if in == nil || err != nil {
|
|
| 271 |
+ return |
|
| 272 |
+ } |
|
| 273 |
+ } |
|
| 274 |
+ |
|
| 275 |
+ if bytes.HasPrefix(in, msgPrefix) && in[len(in)-1] == '.' {
|
|
| 276 |
+ in = in[len(msgPrefix) : len(in)-1] |
|
| 277 |
+ } else if version := isQuery(in); version > 0 {
|
|
| 278 |
+ c.authState = authStateAwaitingDHKey |
|
| 279 |
+ c.reset() |
|
| 280 |
+ toSend = c.encode(c.generateDHCommit()) |
|
| 281 |
+ return |
|
| 282 |
+ } else {
|
|
| 283 |
+ // plaintext message |
|
| 284 |
+ out = in |
|
| 285 |
+ return |
|
| 286 |
+ } |
|
| 287 |
+ |
|
| 288 |
+ msg := make([]byte, base64.StdEncoding.DecodedLen(len(in))) |
|
| 289 |
+ msgLen, err := base64.StdEncoding.Decode(msg, in) |
|
| 290 |
+ if err != nil {
|
|
| 291 |
+ err = errors.New("otr: invalid base64 encoding in message")
|
|
| 292 |
+ return |
|
| 293 |
+ } |
|
| 294 |
+ msg = msg[:msgLen] |
|
| 295 |
+ |
|
| 296 |
+ // The first two bytes are the protocol version (2) |
|
| 297 |
+ if len(msg) < 3 || msg[0] != 0 || msg[1] != 2 {
|
|
| 298 |
+ err = errors.New("otr: invalid OTR message")
|
|
| 299 |
+ return |
|
| 300 |
+ } |
|
| 301 |
+ |
|
| 302 |
+ msgType := int(msg[2]) |
|
| 303 |
+ msg = msg[3:] |
|
| 304 |
+ |
|
| 305 |
+ switch msgType {
|
|
| 306 |
+ case msgTypeDHCommit: |
|
| 307 |
+ switch c.authState {
|
|
| 308 |
+ case authStateNone: |
|
| 309 |
+ c.authState = authStateAwaitingRevealSig |
|
| 310 |
+ if err = c.processDHCommit(msg); err != nil {
|
|
| 311 |
+ return |
|
| 312 |
+ } |
|
| 313 |
+ c.reset() |
|
| 314 |
+ toSend = c.encode(c.generateDHKey()) |
|
| 315 |
+ return |
|
| 316 |
+ case authStateAwaitingDHKey: |
|
| 317 |
+ // This is a 'SYN-crossing'. The greater digest wins. |
|
| 318 |
+ var cmp int |
|
| 319 |
+ if cmp, err = c.compareToDHCommit(msg); err != nil {
|
|
| 320 |
+ return |
|
| 321 |
+ } |
|
| 322 |
+ if cmp > 0 {
|
|
| 323 |
+ // We win. Retransmit DH commit. |
|
| 324 |
+ toSend = c.encode(c.serializeDHCommit()) |
|
| 325 |
+ return |
|
| 326 |
+ } else {
|
|
| 327 |
+ // They win. We forget about our DH commit. |
|
| 328 |
+ c.authState = authStateAwaitingRevealSig |
|
| 329 |
+ if err = c.processDHCommit(msg); err != nil {
|
|
| 330 |
+ return |
|
| 331 |
+ } |
|
| 332 |
+ c.reset() |
|
| 333 |
+ toSend = c.encode(c.generateDHKey()) |
|
| 334 |
+ return |
|
| 335 |
+ } |
|
| 336 |
+ case authStateAwaitingRevealSig: |
|
| 337 |
+ if err = c.processDHCommit(msg); err != nil {
|
|
| 338 |
+ return |
|
| 339 |
+ } |
|
| 340 |
+ toSend = c.encode(c.serializeDHKey()) |
|
| 341 |
+ case authStateAwaitingSig: |
|
| 342 |
+ if err = c.processDHCommit(msg); err != nil {
|
|
| 343 |
+ return |
|
| 344 |
+ } |
|
| 345 |
+ c.reset() |
|
| 346 |
+ toSend = c.encode(c.generateDHKey()) |
|
| 347 |
+ c.authState = authStateAwaitingRevealSig |
|
| 348 |
+ default: |
|
| 349 |
+ panic("bad state")
|
|
| 350 |
+ } |
|
| 351 |
+ case msgTypeDHKey: |
|
| 352 |
+ switch c.authState {
|
|
| 353 |
+ case authStateAwaitingDHKey: |
|
| 354 |
+ var isSame bool |
|
| 355 |
+ if isSame, err = c.processDHKey(msg); err != nil {
|
|
| 356 |
+ return |
|
| 357 |
+ } |
|
| 358 |
+ if isSame {
|
|
| 359 |
+ err = errors.New("otr: unexpected duplicate DH key")
|
|
| 360 |
+ return |
|
| 361 |
+ } |
|
| 362 |
+ toSend = c.encode(c.generateRevealSig()) |
|
| 363 |
+ c.authState = authStateAwaitingSig |
|
| 364 |
+ case authStateAwaitingSig: |
|
| 365 |
+ var isSame bool |
|
| 366 |
+ if isSame, err = c.processDHKey(msg); err != nil {
|
|
| 367 |
+ return |
|
| 368 |
+ } |
|
| 369 |
+ if isSame {
|
|
| 370 |
+ toSend = c.encode(c.serializeDHKey()) |
|
| 371 |
+ } |
|
| 372 |
+ } |
|
| 373 |
+ case msgTypeRevealSig: |
|
| 374 |
+ if c.authState != authStateAwaitingRevealSig {
|
|
| 375 |
+ return |
|
| 376 |
+ } |
|
| 377 |
+ if err = c.processRevealSig(msg); err != nil {
|
|
| 378 |
+ return |
|
| 379 |
+ } |
|
| 380 |
+ toSend = c.encode(c.generateSig()) |
|
| 381 |
+ c.authState = authStateNone |
|
| 382 |
+ c.state = stateEncrypted |
|
| 383 |
+ change = NewKeys |
|
| 384 |
+ case msgTypeSig: |
|
| 385 |
+ if c.authState != authStateAwaitingSig {
|
|
| 386 |
+ return |
|
| 387 |
+ } |
|
| 388 |
+ if err = c.processSig(msg); err != nil {
|
|
| 389 |
+ return |
|
| 390 |
+ } |
|
| 391 |
+ c.authState = authStateNone |
|
| 392 |
+ c.state = stateEncrypted |
|
| 393 |
+ change = NewKeys |
|
| 394 |
+ case msgTypeData: |
|
| 395 |
+ if c.state != stateEncrypted {
|
|
| 396 |
+ err = errors.New("otr: encrypted message received without encrypted session established")
|
|
| 397 |
+ return |
|
| 398 |
+ } |
|
| 399 |
+ var tlvs []tlv |
|
| 400 |
+ out, tlvs, err = c.processData(msg) |
|
| 401 |
+ encrypted = true |
|
| 402 |
+ |
|
| 403 |
+ EachTLV: |
|
| 404 |
+ for _, inTLV := range tlvs {
|
|
| 405 |
+ switch inTLV.typ {
|
|
| 406 |
+ case tlvTypeDisconnected: |
|
| 407 |
+ change = ConversationEnded |
|
| 408 |
+ c.state = stateFinished |
|
| 409 |
+ break EachTLV |
|
| 410 |
+ case tlvTypeSMP1, tlvTypeSMP2, tlvTypeSMP3, tlvTypeSMP4, tlvTypeSMPAbort, tlvTypeSMP1WithQuestion: |
|
| 411 |
+ var reply tlv |
|
| 412 |
+ var complete bool |
|
| 413 |
+ reply, complete, err = c.processSMP(inTLV) |
|
| 414 |
+ if err == smpSecretMissingError {
|
|
| 415 |
+ err = nil |
|
| 416 |
+ change = SMPSecretNeeded |
|
| 417 |
+ c.smp.saved = &inTLV |
|
| 418 |
+ return |
|
| 419 |
+ } |
|
| 420 |
+ if err == smpFailureError {
|
|
| 421 |
+ err = nil |
|
| 422 |
+ change = SMPFailed |
|
| 423 |
+ } else if complete {
|
|
| 424 |
+ change = SMPComplete |
|
| 425 |
+ } |
|
| 426 |
+ if reply.typ != 0 {
|
|
| 427 |
+ toSend = c.encode(c.generateData(nil, &reply)) |
|
| 428 |
+ } |
|
| 429 |
+ break EachTLV |
|
| 430 |
+ default: |
|
| 431 |
+ // skip unknown TLVs |
|
| 432 |
+ } |
|
| 433 |
+ } |
|
| 434 |
+ default: |
|
| 435 |
+ err = errors.New("otr: unknown message type " + strconv.Itoa(msgType))
|
|
| 436 |
+ } |
|
| 437 |
+ |
|
| 438 |
+ return |
|
| 439 |
+} |
|
| 440 |
+ |
|
| 441 |
+// Send takes a human readable message from the local user, possibly encrypts |
|
| 442 |
+// it and returns zero one or more messages to send to the peer. |
|
| 443 |
+func (c *Conversation) Send(msg []byte) ([][]byte, error) {
|
|
| 444 |
+ switch c.state {
|
|
| 445 |
+ case statePlaintext: |
|
| 446 |
+ return [][]byte{msg}, nil
|
|
| 447 |
+ case stateEncrypted: |
|
| 448 |
+ return c.encode(c.generateData(msg, nil)), nil |
|
| 449 |
+ case stateFinished: |
|
| 450 |
+ return nil, errors.New("otr: cannot send message because secure conversation has finished")
|
|
| 451 |
+ } |
|
| 452 |
+ |
|
| 453 |
+ return nil, errors.New("otr: cannot send message in current state")
|
|
| 454 |
+} |
|
| 455 |
+ |
|
| 456 |
+// SMPQuestion returns the human readable challenge question from the peer. |
|
| 457 |
+// It's only valid after Receive has returned SMPSecretNeeded. |
|
| 458 |
+func (c *Conversation) SMPQuestion() string {
|
|
| 459 |
+ return c.smp.question |
|
| 460 |
+} |
|
| 461 |
+ |
|
| 462 |
+// Authenticate begins an authentication with the peer. Authentication involves |
|
| 463 |
+// an optional challenge message and a shared secret. The authentication |
|
| 464 |
+// proceeds until either Receive returns SMPComplete, SMPSecretNeeded (which |
|
| 465 |
+// indicates that a new authentication is happening and thus this one was |
|
| 466 |
+// aborted) or SMPFailed. |
|
| 467 |
+func (c *Conversation) Authenticate(question string, mutualSecret []byte) (toSend [][]byte, err error) {
|
|
| 468 |
+ if c.state != stateEncrypted {
|
|
| 469 |
+ err = errors.New("otr: can't authenticate a peer without a secure conversation established")
|
|
| 470 |
+ return |
|
| 471 |
+ } |
|
| 472 |
+ |
|
| 473 |
+ if c.smp.saved != nil {
|
|
| 474 |
+ c.calcSMPSecret(mutualSecret, false /* they started it */) |
|
| 475 |
+ |
|
| 476 |
+ var out tlv |
|
| 477 |
+ var complete bool |
|
| 478 |
+ out, complete, err = c.processSMP(*c.smp.saved) |
|
| 479 |
+ if complete {
|
|
| 480 |
+ panic("SMP completed on the first message")
|
|
| 481 |
+ } |
|
| 482 |
+ c.smp.saved = nil |
|
| 483 |
+ if out.typ != 0 {
|
|
| 484 |
+ toSend = c.encode(c.generateData(nil, &out)) |
|
| 485 |
+ } |
|
| 486 |
+ return |
|
| 487 |
+ } |
|
| 488 |
+ |
|
| 489 |
+ c.calcSMPSecret(mutualSecret, true /* we started it */) |
|
| 490 |
+ outs := c.startSMP(question) |
|
| 491 |
+ for _, out := range outs {
|
|
| 492 |
+ toSend = append(toSend, c.encode(c.generateData(nil, &out))...) |
|
| 493 |
+ } |
|
| 494 |
+ return |
|
| 495 |
+} |
|
| 496 |
+ |
|
| 497 |
+// End ends a secure conversation by generating a termination message for |
|
| 498 |
+// the peer and switches to unencrypted communication. |
|
| 499 |
+func (c *Conversation) End() (toSend [][]byte) {
|
|
| 500 |
+ switch c.state {
|
|
| 501 |
+ case statePlaintext: |
|
| 502 |
+ return nil |
|
| 503 |
+ case stateEncrypted: |
|
| 504 |
+ c.state = statePlaintext |
|
| 505 |
+ return c.encode(c.generateData(nil, &tlv{typ: tlvTypeDisconnected}))
|
|
| 506 |
+ case stateFinished: |
|
| 507 |
+ c.state = statePlaintext |
|
| 508 |
+ return nil |
|
| 509 |
+ } |
|
| 510 |
+ panic("unreachable")
|
|
| 511 |
+} |
|
| 512 |
+ |
|
| 513 |
+// IsEncrypted returns true if a message passed to Send would be encrypted |
|
| 514 |
+// before transmission. This result remains valid until the next call to |
|
| 515 |
+// Receive or End, which may change the state of the Conversation. |
|
| 516 |
+func (c *Conversation) IsEncrypted() bool {
|
|
| 517 |
+ return c.state == stateEncrypted |
|
| 518 |
+} |
|
| 519 |
+ |
|
| 520 |
+var fragmentError = errors.New("otr: invalid OTR fragment")
|
|
| 521 |
+ |
|
| 522 |
+// processFragment processes a fragmented OTR message and possibly returns a |
|
| 523 |
+// complete message. Fragmented messages look like "?OTR,k,n,msg," where k is |
|
| 524 |
+// the fragment number (starting from 1), n is the number of fragments in this |
|
| 525 |
+// message and msg is a substring of the base64 encoded message. |
|
| 526 |
+func (c *Conversation) processFragment(in []byte) (out []byte, err error) {
|
|
| 527 |
+ in = in[len(fragmentPrefix):] // remove "?OTR," |
|
| 528 |
+ parts := bytes.Split(in, fragmentPartSeparator) |
|
| 529 |
+ if len(parts) != 4 || len(parts[3]) != 0 {
|
|
| 530 |
+ return nil, fragmentError |
|
| 531 |
+ } |
|
| 532 |
+ |
|
| 533 |
+ k, err := strconv.Atoi(string(parts[0])) |
|
| 534 |
+ if err != nil {
|
|
| 535 |
+ return nil, fragmentError |
|
| 536 |
+ } |
|
| 537 |
+ |
|
| 538 |
+ n, err := strconv.Atoi(string(parts[1])) |
|
| 539 |
+ if err != nil {
|
|
| 540 |
+ return nil, fragmentError |
|
| 541 |
+ } |
|
| 542 |
+ |
|
| 543 |
+ if k < 1 || n < 1 || k > n {
|
|
| 544 |
+ return nil, fragmentError |
|
| 545 |
+ } |
|
| 546 |
+ |
|
| 547 |
+ if k == 1 {
|
|
| 548 |
+ c.frag = append(c.frag[:0], parts[2]...) |
|
| 549 |
+ c.k, c.n = k, n |
|
| 550 |
+ } else if n == c.n && k == c.k+1 {
|
|
| 551 |
+ c.frag = append(c.frag, parts[2]...) |
|
| 552 |
+ c.k++ |
|
| 553 |
+ } else {
|
|
| 554 |
+ c.frag = c.frag[:0] |
|
| 555 |
+ c.n, c.k = 0, 0 |
|
| 556 |
+ } |
|
| 557 |
+ |
|
| 558 |
+ if c.n > 0 && c.k == c.n {
|
|
| 559 |
+ c.n, c.k = 0, 0 |
|
| 560 |
+ return c.frag, nil |
|
| 561 |
+ } |
|
| 562 |
+ |
|
| 563 |
+ return nil, nil |
|
| 564 |
+} |
|
| 565 |
+ |
|
| 566 |
+func (c *Conversation) generateDHCommit() []byte {
|
|
| 567 |
+ _, err := io.ReadFull(c.rand(), c.r[:]) |
|
| 568 |
+ if err != nil {
|
|
| 569 |
+ panic("otr: short read from random source")
|
|
| 570 |
+ } |
|
| 571 |
+ |
|
| 572 |
+ var xBytes [dhPrivateBytes]byte |
|
| 573 |
+ c.x = c.randMPI(xBytes[:]) |
|
| 574 |
+ c.gx = new(big.Int).Exp(g, c.x, p) |
|
| 575 |
+ c.gy = nil |
|
| 576 |
+ c.gxBytes = appendMPI(nil, c.gx) |
|
| 577 |
+ |
|
| 578 |
+ h := sha256.New() |
|
| 579 |
+ h.Write(c.gxBytes) |
|
| 580 |
+ h.Sum(c.digest[:0]) |
|
| 581 |
+ |
|
| 582 |
+ aesCipher, err := aes.NewCipher(c.r[:]) |
|
| 583 |
+ if err != nil {
|
|
| 584 |
+ panic(err.Error()) |
|
| 585 |
+ } |
|
| 586 |
+ |
|
| 587 |
+ var iv [aes.BlockSize]byte |
|
| 588 |
+ ctr := cipher.NewCTR(aesCipher, iv[:]) |
|
| 589 |
+ ctr.XORKeyStream(c.gxBytes, c.gxBytes) |
|
| 590 |
+ |
|
| 591 |
+ return c.serializeDHCommit() |
|
| 592 |
+} |
|
| 593 |
+ |
|
| 594 |
+func (c *Conversation) serializeDHCommit() []byte {
|
|
| 595 |
+ var ret []byte |
|
| 596 |
+ ret = appendU16(ret, 2) // protocol version |
|
| 597 |
+ ret = append(ret, msgTypeDHCommit) |
|
| 598 |
+ ret = appendData(ret, c.gxBytes) |
|
| 599 |
+ ret = appendData(ret, c.digest[:]) |
|
| 600 |
+ return ret |
|
| 601 |
+} |
|
| 602 |
+ |
|
| 603 |
+func (c *Conversation) processDHCommit(in []byte) error {
|
|
| 604 |
+ var ok1, ok2 bool |
|
| 605 |
+ c.gxBytes, in, ok1 = getData(in) |
|
| 606 |
+ digest, in, ok2 := getData(in) |
|
| 607 |
+ if !ok1 || !ok2 || len(in) > 0 {
|
|
| 608 |
+ return errors.New("otr: corrupt DH commit message")
|
|
| 609 |
+ } |
|
| 610 |
+ copy(c.digest[:], digest) |
|
| 611 |
+ return nil |
|
| 612 |
+} |
|
| 613 |
+ |
|
| 614 |
+func (c *Conversation) compareToDHCommit(in []byte) (int, error) {
|
|
| 615 |
+ _, in, ok1 := getData(in) |
|
| 616 |
+ digest, in, ok2 := getData(in) |
|
| 617 |
+ if !ok1 || !ok2 || len(in) > 0 {
|
|
| 618 |
+ return 0, errors.New("otr: corrupt DH commit message")
|
|
| 619 |
+ } |
|
| 620 |
+ return bytes.Compare(c.digest[:], digest), nil |
|
| 621 |
+} |
|
| 622 |
+ |
|
| 623 |
+func (c *Conversation) generateDHKey() []byte {
|
|
| 624 |
+ var yBytes [dhPrivateBytes]byte |
|
| 625 |
+ c.y = c.randMPI(yBytes[:]) |
|
| 626 |
+ c.gy = new(big.Int).Exp(g, c.y, p) |
|
| 627 |
+ return c.serializeDHKey() |
|
| 628 |
+} |
|
| 629 |
+ |
|
| 630 |
+func (c *Conversation) serializeDHKey() []byte {
|
|
| 631 |
+ var ret []byte |
|
| 632 |
+ ret = appendU16(ret, 2) // protocol version |
|
| 633 |
+ ret = append(ret, msgTypeDHKey) |
|
| 634 |
+ ret = appendMPI(ret, c.gy) |
|
| 635 |
+ return ret |
|
| 636 |
+} |
|
| 637 |
+ |
|
| 638 |
+func (c *Conversation) processDHKey(in []byte) (isSame bool, err error) {
|
|
| 639 |
+ gy, in, ok := getMPI(in) |
|
| 640 |
+ if !ok {
|
|
| 641 |
+ err = errors.New("otr: corrupt DH key message")
|
|
| 642 |
+ return |
|
| 643 |
+ } |
|
| 644 |
+ if gy.Cmp(g) < 0 || gy.Cmp(pMinus2) > 0 {
|
|
| 645 |
+ err = errors.New("otr: DH value out of range")
|
|
| 646 |
+ return |
|
| 647 |
+ } |
|
| 648 |
+ if c.gy != nil {
|
|
| 649 |
+ isSame = c.gy.Cmp(gy) == 0 |
|
| 650 |
+ return |
|
| 651 |
+ } |
|
| 652 |
+ c.gy = gy |
|
| 653 |
+ return |
|
| 654 |
+} |
|
| 655 |
+ |
|
| 656 |
+func (c *Conversation) generateEncryptedSignature(keys *akeKeys, xFirst bool) ([]byte, []byte) {
|
|
| 657 |
+ var xb []byte |
|
| 658 |
+ xb = c.PrivateKey.PublicKey.Serialize(xb) |
|
| 659 |
+ |
|
| 660 |
+ var verifyData []byte |
|
| 661 |
+ if xFirst {
|
|
| 662 |
+ verifyData = appendMPI(verifyData, c.gx) |
|
| 663 |
+ verifyData = appendMPI(verifyData, c.gy) |
|
| 664 |
+ } else {
|
|
| 665 |
+ verifyData = appendMPI(verifyData, c.gy) |
|
| 666 |
+ verifyData = appendMPI(verifyData, c.gx) |
|
| 667 |
+ } |
|
| 668 |
+ verifyData = append(verifyData, xb...) |
|
| 669 |
+ verifyData = appendU32(verifyData, c.myKeyId) |
|
| 670 |
+ |
|
| 671 |
+ mac := hmac.New(sha256.New, keys.m1[:]) |
|
| 672 |
+ mac.Write(verifyData) |
|
| 673 |
+ mb := mac.Sum(nil) |
|
| 674 |
+ |
|
| 675 |
+ xb = appendU32(xb, c.myKeyId) |
|
| 676 |
+ xb = append(xb, c.PrivateKey.Sign(c.rand(), mb)...) |
|
| 677 |
+ |
|
| 678 |
+ aesCipher, err := aes.NewCipher(keys.c[:]) |
|
| 679 |
+ if err != nil {
|
|
| 680 |
+ panic(err.Error()) |
|
| 681 |
+ } |
|
| 682 |
+ var iv [aes.BlockSize]byte |
|
| 683 |
+ ctr := cipher.NewCTR(aesCipher, iv[:]) |
|
| 684 |
+ ctr.XORKeyStream(xb, xb) |
|
| 685 |
+ |
|
| 686 |
+ mac = hmac.New(sha256.New, keys.m2[:]) |
|
| 687 |
+ encryptedSig := appendData(nil, xb) |
|
| 688 |
+ mac.Write(encryptedSig) |
|
| 689 |
+ |
|
| 690 |
+ return encryptedSig, mac.Sum(nil) |
|
| 691 |
+} |
|
| 692 |
+ |
|
| 693 |
+func (c *Conversation) generateRevealSig() []byte {
|
|
| 694 |
+ s := new(big.Int).Exp(c.gy, c.x, p) |
|
| 695 |
+ c.calcAKEKeys(s) |
|
| 696 |
+ c.myKeyId++ |
|
| 697 |
+ |
|
| 698 |
+ encryptedSig, mac := c.generateEncryptedSignature(&c.revealKeys, true /* gx comes first */) |
|
| 699 |
+ |
|
| 700 |
+ c.myCurrentDHPub = c.gx |
|
| 701 |
+ c.myCurrentDHPriv = c.x |
|
| 702 |
+ c.rotateDHKeys() |
|
| 703 |
+ incCounter(&c.myCounter) |
|
| 704 |
+ |
|
| 705 |
+ var ret []byte |
|
| 706 |
+ ret = appendU16(ret, 2) |
|
| 707 |
+ ret = append(ret, msgTypeRevealSig) |
|
| 708 |
+ ret = appendData(ret, c.r[:]) |
|
| 709 |
+ ret = append(ret, encryptedSig...) |
|
| 710 |
+ ret = append(ret, mac[:20]...) |
|
| 711 |
+ return ret |
|
| 712 |
+} |
|
| 713 |
+ |
|
| 714 |
+func (c *Conversation) processEncryptedSig(encryptedSig, theirMAC []byte, keys *akeKeys, xFirst bool) error {
|
|
| 715 |
+ mac := hmac.New(sha256.New, keys.m2[:]) |
|
| 716 |
+ mac.Write(appendData(nil, encryptedSig)) |
|
| 717 |
+ myMAC := mac.Sum(nil)[:20] |
|
| 718 |
+ |
|
| 719 |
+ if len(myMAC) != len(theirMAC) || subtle.ConstantTimeCompare(myMAC, theirMAC) == 0 {
|
|
| 720 |
+ return errors.New("bad signature MAC in encrypted signature")
|
|
| 721 |
+ } |
|
| 722 |
+ |
|
| 723 |
+ aesCipher, err := aes.NewCipher(keys.c[:]) |
|
| 724 |
+ if err != nil {
|
|
| 725 |
+ panic(err.Error()) |
|
| 726 |
+ } |
|
| 727 |
+ var iv [aes.BlockSize]byte |
|
| 728 |
+ ctr := cipher.NewCTR(aesCipher, iv[:]) |
|
| 729 |
+ ctr.XORKeyStream(encryptedSig, encryptedSig) |
|
| 730 |
+ |
|
| 731 |
+ sig := encryptedSig |
|
| 732 |
+ sig, ok1 := c.TheirPublicKey.Parse(sig) |
|
| 733 |
+ keyId, sig, ok2 := getU32(sig) |
|
| 734 |
+ if !ok1 || !ok2 {
|
|
| 735 |
+ return errors.New("otr: corrupt encrypted signature")
|
|
| 736 |
+ } |
|
| 737 |
+ |
|
| 738 |
+ var verifyData []byte |
|
| 739 |
+ if xFirst {
|
|
| 740 |
+ verifyData = appendMPI(verifyData, c.gx) |
|
| 741 |
+ verifyData = appendMPI(verifyData, c.gy) |
|
| 742 |
+ } else {
|
|
| 743 |
+ verifyData = appendMPI(verifyData, c.gy) |
|
| 744 |
+ verifyData = appendMPI(verifyData, c.gx) |
|
| 745 |
+ } |
|
| 746 |
+ verifyData = c.TheirPublicKey.Serialize(verifyData) |
|
| 747 |
+ verifyData = appendU32(verifyData, keyId) |
|
| 748 |
+ |
|
| 749 |
+ mac = hmac.New(sha256.New, keys.m1[:]) |
|
| 750 |
+ mac.Write(verifyData) |
|
| 751 |
+ mb := mac.Sum(nil) |
|
| 752 |
+ |
|
| 753 |
+ sig, ok1 = c.TheirPublicKey.Verify(mb, sig) |
|
| 754 |
+ if !ok1 {
|
|
| 755 |
+ return errors.New("bad signature in encrypted signature")
|
|
| 756 |
+ } |
|
| 757 |
+ if len(sig) > 0 {
|
|
| 758 |
+ return errors.New("corrupt encrypted signature")
|
|
| 759 |
+ } |
|
| 760 |
+ |
|
| 761 |
+ c.theirKeyId = keyId |
|
| 762 |
+ zero(c.theirLastCtr[:]) |
|
| 763 |
+ return nil |
|
| 764 |
+} |
|
| 765 |
+ |
|
| 766 |
+func (c *Conversation) processRevealSig(in []byte) error {
|
|
| 767 |
+ r, in, ok1 := getData(in) |
|
| 768 |
+ encryptedSig, in, ok2 := getData(in) |
|
| 769 |
+ theirMAC := in |
|
| 770 |
+ if !ok1 || !ok2 || len(theirMAC) != 20 {
|
|
| 771 |
+ return errors.New("otr: corrupt reveal signature message")
|
|
| 772 |
+ } |
|
| 773 |
+ |
|
| 774 |
+ aesCipher, err := aes.NewCipher(r) |
|
| 775 |
+ if err != nil {
|
|
| 776 |
+ return errors.New("otr: cannot create AES cipher from reveal signature message: " + err.Error())
|
|
| 777 |
+ } |
|
| 778 |
+ var iv [aes.BlockSize]byte |
|
| 779 |
+ ctr := cipher.NewCTR(aesCipher, iv[:]) |
|
| 780 |
+ ctr.XORKeyStream(c.gxBytes, c.gxBytes) |
|
| 781 |
+ h := sha256.New() |
|
| 782 |
+ h.Write(c.gxBytes) |
|
| 783 |
+ digest := h.Sum(nil) |
|
| 784 |
+ if len(digest) != len(c.digest) || subtle.ConstantTimeCompare(digest, c.digest[:]) == 0 {
|
|
| 785 |
+ return errors.New("otr: bad commit MAC in reveal signature message")
|
|
| 786 |
+ } |
|
| 787 |
+ var rest []byte |
|
| 788 |
+ c.gx, rest, ok1 = getMPI(c.gxBytes) |
|
| 789 |
+ if !ok1 || len(rest) > 0 {
|
|
| 790 |
+ return errors.New("otr: gx corrupt after decryption")
|
|
| 791 |
+ } |
|
| 792 |
+ if c.gx.Cmp(g) < 0 || c.gx.Cmp(pMinus2) > 0 {
|
|
| 793 |
+ return errors.New("otr: DH value out of range")
|
|
| 794 |
+ } |
|
| 795 |
+ s := new(big.Int).Exp(c.gx, c.y, p) |
|
| 796 |
+ c.calcAKEKeys(s) |
|
| 797 |
+ |
|
| 798 |
+ if err := c.processEncryptedSig(encryptedSig, theirMAC, &c.revealKeys, true /* gx comes first */); err != nil {
|
|
| 799 |
+ return errors.New("otr: in reveal signature message: " + err.Error())
|
|
| 800 |
+ } |
|
| 801 |
+ |
|
| 802 |
+ c.theirCurrentDHPub = c.gx |
|
| 803 |
+ c.theirLastDHPub = nil |
|
| 804 |
+ |
|
| 805 |
+ return nil |
|
| 806 |
+} |
|
| 807 |
+ |
|
| 808 |
+func (c *Conversation) generateSig() []byte {
|
|
| 809 |
+ c.myKeyId++ |
|
| 810 |
+ |
|
| 811 |
+ encryptedSig, mac := c.generateEncryptedSignature(&c.sigKeys, false /* gy comes first */) |
|
| 812 |
+ |
|
| 813 |
+ c.myCurrentDHPub = c.gy |
|
| 814 |
+ c.myCurrentDHPriv = c.y |
|
| 815 |
+ c.rotateDHKeys() |
|
| 816 |
+ incCounter(&c.myCounter) |
|
| 817 |
+ |
|
| 818 |
+ var ret []byte |
|
| 819 |
+ ret = appendU16(ret, 2) |
|
| 820 |
+ ret = append(ret, msgTypeSig) |
|
| 821 |
+ ret = append(ret, encryptedSig...) |
|
| 822 |
+ ret = append(ret, mac[:macPrefixBytes]...) |
|
| 823 |
+ return ret |
|
| 824 |
+} |
|
| 825 |
+ |
|
| 826 |
+func (c *Conversation) processSig(in []byte) error {
|
|
| 827 |
+ encryptedSig, in, ok1 := getData(in) |
|
| 828 |
+ theirMAC := in |
|
| 829 |
+ if !ok1 || len(theirMAC) != macPrefixBytes {
|
|
| 830 |
+ return errors.New("otr: corrupt signature message")
|
|
| 831 |
+ } |
|
| 832 |
+ |
|
| 833 |
+ if err := c.processEncryptedSig(encryptedSig, theirMAC, &c.sigKeys, false /* gy comes first */); err != nil {
|
|
| 834 |
+ return errors.New("otr: in signature message: " + err.Error())
|
|
| 835 |
+ } |
|
| 836 |
+ |
|
| 837 |
+ c.theirCurrentDHPub = c.gy |
|
| 838 |
+ c.theirLastDHPub = nil |
|
| 839 |
+ |
|
| 840 |
+ return nil |
|
| 841 |
+} |
|
| 842 |
+ |
|
| 843 |
+func (c *Conversation) rotateDHKeys() {
|
|
| 844 |
+ // evict slots using our retired key id |
|
| 845 |
+ for i := range c.keySlots {
|
|
| 846 |
+ slot := &c.keySlots[i] |
|
| 847 |
+ if slot.used && slot.myKeyId == c.myKeyId-1 {
|
|
| 848 |
+ slot.used = false |
|
| 849 |
+ c.oldMACs = append(c.oldMACs, slot.recvMACKey...) |
|
| 850 |
+ } |
|
| 851 |
+ } |
|
| 852 |
+ |
|
| 853 |
+ c.myLastDHPriv = c.myCurrentDHPriv |
|
| 854 |
+ c.myLastDHPub = c.myCurrentDHPub |
|
| 855 |
+ |
|
| 856 |
+ var xBytes [dhPrivateBytes]byte |
|
| 857 |
+ c.myCurrentDHPriv = c.randMPI(xBytes[:]) |
|
| 858 |
+ c.myCurrentDHPub = new(big.Int).Exp(g, c.myCurrentDHPriv, p) |
|
| 859 |
+ c.myKeyId++ |
|
| 860 |
+} |
|
| 861 |
+ |
|
| 862 |
+func (c *Conversation) processData(in []byte) (out []byte, tlvs []tlv, err error) {
|
|
| 863 |
+ origIn := in |
|
| 864 |
+ flags, in, ok1 := getU8(in) |
|
| 865 |
+ theirKeyId, in, ok2 := getU32(in) |
|
| 866 |
+ myKeyId, in, ok3 := getU32(in) |
|
| 867 |
+ y, in, ok4 := getMPI(in) |
|
| 868 |
+ counter, in, ok5 := getNBytes(in, 8) |
|
| 869 |
+ encrypted, in, ok6 := getData(in) |
|
| 870 |
+ macedData := origIn[:len(origIn)-len(in)] |
|
| 871 |
+ theirMAC, in, ok7 := getNBytes(in, macPrefixBytes) |
|
| 872 |
+ _, in, ok8 := getData(in) |
|
| 873 |
+ if !ok1 || !ok2 || !ok3 || !ok4 || !ok5 || !ok6 || !ok7 || !ok8 || len(in) > 0 {
|
|
| 874 |
+ err = errors.New("otr: corrupt data message")
|
|
| 875 |
+ return |
|
| 876 |
+ } |
|
| 877 |
+ |
|
| 878 |
+ ignoreErrors := flags&1 != 0 |
|
| 879 |
+ |
|
| 880 |
+ slot, err := c.calcDataKeys(myKeyId, theirKeyId) |
|
| 881 |
+ if err != nil {
|
|
| 882 |
+ if ignoreErrors {
|
|
| 883 |
+ err = nil |
|
| 884 |
+ } |
|
| 885 |
+ return |
|
| 886 |
+ } |
|
| 887 |
+ |
|
| 888 |
+ mac := hmac.New(sha1.New, slot.recvMACKey) |
|
| 889 |
+ mac.Write([]byte{0, 2, 3})
|
|
| 890 |
+ mac.Write(macedData) |
|
| 891 |
+ myMAC := mac.Sum(nil) |
|
| 892 |
+ if len(myMAC) != len(theirMAC) || subtle.ConstantTimeCompare(myMAC, theirMAC) == 0 {
|
|
| 893 |
+ if !ignoreErrors {
|
|
| 894 |
+ err = errors.New("otr: bad MAC on data message")
|
|
| 895 |
+ } |
|
| 896 |
+ return |
|
| 897 |
+ } |
|
| 898 |
+ |
|
| 899 |
+ if bytes.Compare(counter, slot.theirLastCtr[:]) <= 0 {
|
|
| 900 |
+ err = errors.New("otr: counter regressed")
|
|
| 901 |
+ return |
|
| 902 |
+ } |
|
| 903 |
+ copy(slot.theirLastCtr[:], counter) |
|
| 904 |
+ |
|
| 905 |
+ var iv [aes.BlockSize]byte |
|
| 906 |
+ copy(iv[:], counter) |
|
| 907 |
+ aesCipher, err := aes.NewCipher(slot.recvAESKey) |
|
| 908 |
+ if err != nil {
|
|
| 909 |
+ panic(err.Error()) |
|
| 910 |
+ } |
|
| 911 |
+ ctr := cipher.NewCTR(aesCipher, iv[:]) |
|
| 912 |
+ ctr.XORKeyStream(encrypted, encrypted) |
|
| 913 |
+ decrypted := encrypted |
|
| 914 |
+ |
|
| 915 |
+ if myKeyId == c.myKeyId {
|
|
| 916 |
+ c.rotateDHKeys() |
|
| 917 |
+ } |
|
| 918 |
+ if theirKeyId == c.theirKeyId {
|
|
| 919 |
+ // evict slots using their retired key id |
|
| 920 |
+ for i := range c.keySlots {
|
|
| 921 |
+ slot := &c.keySlots[i] |
|
| 922 |
+ if slot.used && slot.theirKeyId == theirKeyId-1 {
|
|
| 923 |
+ slot.used = false |
|
| 924 |
+ c.oldMACs = append(c.oldMACs, slot.recvMACKey...) |
|
| 925 |
+ } |
|
| 926 |
+ } |
|
| 927 |
+ |
|
| 928 |
+ c.theirLastDHPub = c.theirCurrentDHPub |
|
| 929 |
+ c.theirKeyId++ |
|
| 930 |
+ c.theirCurrentDHPub = y |
|
| 931 |
+ } |
|
| 932 |
+ |
|
| 933 |
+ if nulPos := bytes.IndexByte(decrypted, 0); nulPos >= 0 {
|
|
| 934 |
+ out = decrypted[:nulPos] |
|
| 935 |
+ tlvData := decrypted[nulPos+1:] |
|
| 936 |
+ for len(tlvData) > 0 {
|
|
| 937 |
+ var t tlv |
|
| 938 |
+ var ok1, ok2, ok3 bool |
|
| 939 |
+ |
|
| 940 |
+ t.typ, tlvData, ok1 = getU16(tlvData) |
|
| 941 |
+ t.length, tlvData, ok2 = getU16(tlvData) |
|
| 942 |
+ t.data, tlvData, ok3 = getNBytes(tlvData, int(t.length)) |
|
| 943 |
+ if !ok1 || !ok2 || !ok3 {
|
|
| 944 |
+ err = errors.New("otr: corrupt tlv data")
|
|
| 945 |
+ return |
|
| 946 |
+ } |
|
| 947 |
+ tlvs = append(tlvs, t) |
|
| 948 |
+ } |
|
| 949 |
+ } else {
|
|
| 950 |
+ out = decrypted |
|
| 951 |
+ } |
|
| 952 |
+ |
|
| 953 |
+ return |
|
| 954 |
+} |
|
| 955 |
+ |
|
| 956 |
+func (c *Conversation) generateData(msg []byte, extra *tlv) []byte {
|
|
| 957 |
+ slot, err := c.calcDataKeys(c.myKeyId-1, c.theirKeyId) |
|
| 958 |
+ if err != nil {
|
|
| 959 |
+ panic("otr: failed to generate sending keys: " + err.Error())
|
|
| 960 |
+ } |
|
| 961 |
+ |
|
| 962 |
+ var plaintext []byte |
|
| 963 |
+ plaintext = append(plaintext, msg...) |
|
| 964 |
+ plaintext = append(plaintext, 0) |
|
| 965 |
+ |
|
| 966 |
+ padding := paddingGranularity - ((len(plaintext) + 4) % paddingGranularity) |
|
| 967 |
+ plaintext = appendU16(plaintext, tlvTypePadding) |
|
| 968 |
+ plaintext = appendU16(plaintext, uint16(padding)) |
|
| 969 |
+ for i := 0; i < padding; i++ {
|
|
| 970 |
+ plaintext = append(plaintext, 0) |
|
| 971 |
+ } |
|
| 972 |
+ |
|
| 973 |
+ if extra != nil {
|
|
| 974 |
+ plaintext = appendU16(plaintext, extra.typ) |
|
| 975 |
+ plaintext = appendU16(plaintext, uint16(len(extra.data))) |
|
| 976 |
+ plaintext = append(plaintext, extra.data...) |
|
| 977 |
+ } |
|
| 978 |
+ |
|
| 979 |
+ encrypted := make([]byte, len(plaintext)) |
|
| 980 |
+ |
|
| 981 |
+ var iv [aes.BlockSize]byte |
|
| 982 |
+ copy(iv[:], c.myCounter[:]) |
|
| 983 |
+ aesCipher, err := aes.NewCipher(slot.sendAESKey) |
|
| 984 |
+ if err != nil {
|
|
| 985 |
+ panic(err.Error()) |
|
| 986 |
+ } |
|
| 987 |
+ ctr := cipher.NewCTR(aesCipher, iv[:]) |
|
| 988 |
+ ctr.XORKeyStream(encrypted, plaintext) |
|
| 989 |
+ |
|
| 990 |
+ var ret []byte |
|
| 991 |
+ ret = appendU16(ret, 2) |
|
| 992 |
+ ret = append(ret, msgTypeData) |
|
| 993 |
+ ret = append(ret, 0 /* flags */) |
|
| 994 |
+ ret = appendU32(ret, c.myKeyId-1) |
|
| 995 |
+ ret = appendU32(ret, c.theirKeyId) |
|
| 996 |
+ ret = appendMPI(ret, c.myCurrentDHPub) |
|
| 997 |
+ ret = append(ret, c.myCounter[:]...) |
|
| 998 |
+ ret = appendData(ret, encrypted) |
|
| 999 |
+ |
|
| 1000 |
+ mac := hmac.New(sha1.New, slot.sendMACKey) |
|
| 1001 |
+ mac.Write(ret) |
|
| 1002 |
+ ret = append(ret, mac.Sum(nil)[:macPrefixBytes]...) |
|
| 1003 |
+ ret = appendData(ret, c.oldMACs) |
|
| 1004 |
+ c.oldMACs = nil |
|
| 1005 |
+ incCounter(&c.myCounter) |
|
| 1006 |
+ |
|
| 1007 |
+ return ret |
|
| 1008 |
+} |
|
| 1009 |
+ |
|
| 1010 |
+func incCounter(counter *[8]byte) {
|
|
| 1011 |
+ for i := 7; i >= 0; i-- {
|
|
| 1012 |
+ counter[i]++ |
|
| 1013 |
+ if counter[i] > 0 {
|
|
| 1014 |
+ break |
|
| 1015 |
+ } |
|
| 1016 |
+ } |
|
| 1017 |
+} |
|
| 1018 |
+ |
|
| 1019 |
+// calcDataKeys computes the keys used to encrypt a data message given the key |
|
| 1020 |
+// IDs. |
|
| 1021 |
+func (c *Conversation) calcDataKeys(myKeyId, theirKeyId uint32) (slot *keySlot, err error) {
|
|
| 1022 |
+ // Check for a cache hit. |
|
| 1023 |
+ for i := range c.keySlots {
|
|
| 1024 |
+ slot = &c.keySlots[i] |
|
| 1025 |
+ if slot.used && slot.theirKeyId == theirKeyId && slot.myKeyId == myKeyId {
|
|
| 1026 |
+ return |
|
| 1027 |
+ } |
|
| 1028 |
+ } |
|
| 1029 |
+ |
|
| 1030 |
+ // Find an empty slot to write into. |
|
| 1031 |
+ slot = nil |
|
| 1032 |
+ for i := range c.keySlots {
|
|
| 1033 |
+ if !c.keySlots[i].used {
|
|
| 1034 |
+ slot = &c.keySlots[i] |
|
| 1035 |
+ break |
|
| 1036 |
+ } |
|
| 1037 |
+ } |
|
| 1038 |
+ if slot == nil {
|
|
| 1039 |
+ return nil, errors.New("otr: internal error: no more key slots")
|
|
| 1040 |
+ } |
|
| 1041 |
+ |
|
| 1042 |
+ var myPriv, myPub, theirPub *big.Int |
|
| 1043 |
+ |
|
| 1044 |
+ if myKeyId == c.myKeyId {
|
|
| 1045 |
+ myPriv = c.myCurrentDHPriv |
|
| 1046 |
+ myPub = c.myCurrentDHPub |
|
| 1047 |
+ } else if myKeyId == c.myKeyId-1 {
|
|
| 1048 |
+ myPriv = c.myLastDHPriv |
|
| 1049 |
+ myPub = c.myLastDHPub |
|
| 1050 |
+ } else {
|
|
| 1051 |
+ err = errors.New("otr: peer requested keyid " + strconv.FormatUint(uint64(myKeyId), 10) + " when I'm on " + strconv.FormatUint(uint64(c.myKeyId), 10))
|
|
| 1052 |
+ return |
|
| 1053 |
+ } |
|
| 1054 |
+ |
|
| 1055 |
+ if theirKeyId == c.theirKeyId {
|
|
| 1056 |
+ theirPub = c.theirCurrentDHPub |
|
| 1057 |
+ } else if theirKeyId == c.theirKeyId-1 && c.theirLastDHPub != nil {
|
|
| 1058 |
+ theirPub = c.theirLastDHPub |
|
| 1059 |
+ } else {
|
|
| 1060 |
+ err = errors.New("otr: peer requested keyid " + strconv.FormatUint(uint64(myKeyId), 10) + " when they're on " + strconv.FormatUint(uint64(c.myKeyId), 10))
|
|
| 1061 |
+ return |
|
| 1062 |
+ } |
|
| 1063 |
+ |
|
| 1064 |
+ var sendPrefixByte, recvPrefixByte [1]byte |
|
| 1065 |
+ |
|
| 1066 |
+ if myPub.Cmp(theirPub) > 0 {
|
|
| 1067 |
+ // we're the high end |
|
| 1068 |
+ sendPrefixByte[0], recvPrefixByte[0] = 1, 2 |
|
| 1069 |
+ } else {
|
|
| 1070 |
+ // we're the low end |
|
| 1071 |
+ sendPrefixByte[0], recvPrefixByte[0] = 2, 1 |
|
| 1072 |
+ } |
|
| 1073 |
+ |
|
| 1074 |
+ s := new(big.Int).Exp(theirPub, myPriv, p) |
|
| 1075 |
+ sBytes := appendMPI(nil, s) |
|
| 1076 |
+ |
|
| 1077 |
+ h := sha1.New() |
|
| 1078 |
+ h.Write(sendPrefixByte[:]) |
|
| 1079 |
+ h.Write(sBytes) |
|
| 1080 |
+ slot.sendAESKey = h.Sum(slot.sendAESKey[:0])[:16] |
|
| 1081 |
+ |
|
| 1082 |
+ h.Reset() |
|
| 1083 |
+ h.Write(slot.sendAESKey) |
|
| 1084 |
+ slot.sendMACKey = h.Sum(slot.sendMACKey[:0]) |
|
| 1085 |
+ |
|
| 1086 |
+ h.Reset() |
|
| 1087 |
+ h.Write(recvPrefixByte[:]) |
|
| 1088 |
+ h.Write(sBytes) |
|
| 1089 |
+ slot.recvAESKey = h.Sum(slot.recvAESKey[:0])[:16] |
|
| 1090 |
+ |
|
| 1091 |
+ h.Reset() |
|
| 1092 |
+ h.Write(slot.recvAESKey) |
|
| 1093 |
+ slot.recvMACKey = h.Sum(slot.recvMACKey[:0]) |
|
| 1094 |
+ |
|
| 1095 |
+ slot.theirKeyId = theirKeyId |
|
| 1096 |
+ slot.myKeyId = myKeyId |
|
| 1097 |
+ slot.used = true |
|
| 1098 |
+ |
|
| 1099 |
+ zero(slot.theirLastCtr[:]) |
|
| 1100 |
+ return |
|
| 1101 |
+} |
|
| 1102 |
+ |
|
| 1103 |
+func (c *Conversation) calcAKEKeys(s *big.Int) {
|
|
| 1104 |
+ mpi := appendMPI(nil, s) |
|
| 1105 |
+ h := sha256.New() |
|
| 1106 |
+ |
|
| 1107 |
+ var cBytes [32]byte |
|
| 1108 |
+ hashWithPrefix(c.SSID[:], 0, mpi, h) |
|
| 1109 |
+ |
|
| 1110 |
+ hashWithPrefix(cBytes[:], 1, mpi, h) |
|
| 1111 |
+ copy(c.revealKeys.c[:], cBytes[:16]) |
|
| 1112 |
+ copy(c.sigKeys.c[:], cBytes[16:]) |
|
| 1113 |
+ |
|
| 1114 |
+ hashWithPrefix(c.revealKeys.m1[:], 2, mpi, h) |
|
| 1115 |
+ hashWithPrefix(c.revealKeys.m2[:], 3, mpi, h) |
|
| 1116 |
+ hashWithPrefix(c.sigKeys.m1[:], 4, mpi, h) |
|
| 1117 |
+ hashWithPrefix(c.sigKeys.m2[:], 5, mpi, h) |
|
| 1118 |
+} |
|
| 1119 |
+ |
|
| 1120 |
+func hashWithPrefix(out []byte, prefix byte, in []byte, h hash.Hash) {
|
|
| 1121 |
+ h.Reset() |
|
| 1122 |
+ var p [1]byte |
|
| 1123 |
+ p[0] = prefix |
|
| 1124 |
+ h.Write(p[:]) |
|
| 1125 |
+ h.Write(in) |
|
| 1126 |
+ if len(out) == h.Size() {
|
|
| 1127 |
+ h.Sum(out[:0]) |
|
| 1128 |
+ } else {
|
|
| 1129 |
+ digest := h.Sum(nil) |
|
| 1130 |
+ copy(out, digest) |
|
| 1131 |
+ } |
|
| 1132 |
+} |
|
| 1133 |
+ |
|
| 1134 |
+func (c *Conversation) encode(msg []byte) [][]byte {
|
|
| 1135 |
+ b64 := make([]byte, base64.StdEncoding.EncodedLen(len(msg))+len(msgPrefix)+1) |
|
| 1136 |
+ base64.StdEncoding.Encode(b64[len(msgPrefix):], msg) |
|
| 1137 |
+ copy(b64, msgPrefix) |
|
| 1138 |
+ b64[len(b64)-1] = '.' |
|
| 1139 |
+ |
|
| 1140 |
+ if c.FragmentSize < minFragmentSize || len(b64) <= c.FragmentSize {
|
|
| 1141 |
+ // We can encode this in a single fragment. |
|
| 1142 |
+ return [][]byte{b64}
|
|
| 1143 |
+ } |
|
| 1144 |
+ |
|
| 1145 |
+ // We have to fragment this message. |
|
| 1146 |
+ var ret [][]byte |
|
| 1147 |
+ bytesPerFragment := c.FragmentSize - minFragmentSize |
|
| 1148 |
+ numFragments := (len(b64) + bytesPerFragment) / bytesPerFragment |
|
| 1149 |
+ |
|
| 1150 |
+ for i := 0; i < numFragments; i++ {
|
|
| 1151 |
+ frag := []byte("?OTR," + strconv.Itoa(i+1) + "," + strconv.Itoa(numFragments) + ",")
|
|
| 1152 |
+ todo := bytesPerFragment |
|
| 1153 |
+ if todo > len(b64) {
|
|
| 1154 |
+ todo = len(b64) |
|
| 1155 |
+ } |
|
| 1156 |
+ frag = append(frag, b64[:todo]...) |
|
| 1157 |
+ b64 = b64[todo:] |
|
| 1158 |
+ frag = append(frag, ',') |
|
| 1159 |
+ ret = append(ret, frag) |
|
| 1160 |
+ } |
|
| 1161 |
+ |
|
| 1162 |
+ return ret |
|
| 1163 |
+} |
|
| 1164 |
+ |
|
| 1165 |
+func (c *Conversation) reset() {
|
|
| 1166 |
+ c.myKeyId = 0 |
|
| 1167 |
+ |
|
| 1168 |
+ for i := range c.keySlots {
|
|
| 1169 |
+ c.keySlots[i].used = false |
|
| 1170 |
+ } |
|
| 1171 |
+} |
|
| 1172 |
+ |
|
| 1173 |
+type PublicKey struct {
|
|
| 1174 |
+ dsa.PublicKey |
|
| 1175 |
+} |
|
| 1176 |
+ |
|
| 1177 |
+func (pk *PublicKey) Parse(in []byte) ([]byte, bool) {
|
|
| 1178 |
+ var ok bool |
|
| 1179 |
+ var pubKeyType uint16 |
|
| 1180 |
+ |
|
| 1181 |
+ if pubKeyType, in, ok = getU16(in); !ok || pubKeyType != 0 {
|
|
| 1182 |
+ return nil, false |
|
| 1183 |
+ } |
|
| 1184 |
+ if pk.P, in, ok = getMPI(in); !ok {
|
|
| 1185 |
+ return nil, false |
|
| 1186 |
+ } |
|
| 1187 |
+ if pk.Q, in, ok = getMPI(in); !ok {
|
|
| 1188 |
+ return nil, false |
|
| 1189 |
+ } |
|
| 1190 |
+ if pk.G, in, ok = getMPI(in); !ok {
|
|
| 1191 |
+ return nil, false |
|
| 1192 |
+ } |
|
| 1193 |
+ if pk.Y, in, ok = getMPI(in); !ok {
|
|
| 1194 |
+ return nil, false |
|
| 1195 |
+ } |
|
| 1196 |
+ |
|
| 1197 |
+ return in, true |
|
| 1198 |
+} |
|
| 1199 |
+ |
|
| 1200 |
+func (pk *PublicKey) Serialize(in []byte) []byte {
|
|
| 1201 |
+ in = appendU16(in, 0) |
|
| 1202 |
+ in = appendMPI(in, pk.P) |
|
| 1203 |
+ in = appendMPI(in, pk.Q) |
|
| 1204 |
+ in = appendMPI(in, pk.G) |
|
| 1205 |
+ in = appendMPI(in, pk.Y) |
|
| 1206 |
+ return in |
|
| 1207 |
+} |
|
| 1208 |
+ |
|
| 1209 |
+// Fingerprint returns the 20-byte, binary fingerprint of the PublicKey. |
|
| 1210 |
+func (pk *PublicKey) Fingerprint() []byte {
|
|
| 1211 |
+ b := pk.Serialize(nil) |
|
| 1212 |
+ h := sha1.New() |
|
| 1213 |
+ h.Write(b[2:]) |
|
| 1214 |
+ return h.Sum(nil) |
|
| 1215 |
+} |
|
| 1216 |
+ |
|
| 1217 |
+func (pk *PublicKey) Verify(hashed, sig []byte) ([]byte, bool) {
|
|
| 1218 |
+ if len(sig) != 2*dsaSubgroupBytes {
|
|
| 1219 |
+ return nil, false |
|
| 1220 |
+ } |
|
| 1221 |
+ r := new(big.Int).SetBytes(sig[:dsaSubgroupBytes]) |
|
| 1222 |
+ s := new(big.Int).SetBytes(sig[dsaSubgroupBytes:]) |
|
| 1223 |
+ ok := dsa.Verify(&pk.PublicKey, hashed, r, s) |
|
| 1224 |
+ return sig[dsaSubgroupBytes*2:], ok |
|
| 1225 |
+} |
|
| 1226 |
+ |
|
| 1227 |
+type PrivateKey struct {
|
|
| 1228 |
+ PublicKey |
|
| 1229 |
+ dsa.PrivateKey |
|
| 1230 |
+} |
|
| 1231 |
+ |
|
| 1232 |
+func (priv *PrivateKey) Sign(rand io.Reader, hashed []byte) []byte {
|
|
| 1233 |
+ r, s, err := dsa.Sign(rand, &priv.PrivateKey, hashed) |
|
| 1234 |
+ if err != nil {
|
|
| 1235 |
+ panic(err.Error()) |
|
| 1236 |
+ } |
|
| 1237 |
+ rBytes := r.Bytes() |
|
| 1238 |
+ sBytes := s.Bytes() |
|
| 1239 |
+ if len(rBytes) > dsaSubgroupBytes || len(sBytes) > dsaSubgroupBytes {
|
|
| 1240 |
+ panic("DSA signature too large")
|
|
| 1241 |
+ } |
|
| 1242 |
+ |
|
| 1243 |
+ out := make([]byte, 2*dsaSubgroupBytes) |
|
| 1244 |
+ copy(out[dsaSubgroupBytes-len(rBytes):], rBytes) |
|
| 1245 |
+ copy(out[len(out)-len(sBytes):], sBytes) |
|
| 1246 |
+ return out |
|
| 1247 |
+} |
|
| 1248 |
+ |
|
| 1249 |
+func (priv *PrivateKey) Serialize(in []byte) []byte {
|
|
| 1250 |
+ in = priv.PublicKey.Serialize(in) |
|
| 1251 |
+ in = appendMPI(in, priv.PrivateKey.X) |
|
| 1252 |
+ return in |
|
| 1253 |
+} |
|
| 1254 |
+ |
|
| 1255 |
+func (priv *PrivateKey) Parse(in []byte) ([]byte, bool) {
|
|
| 1256 |
+ in, ok := priv.PublicKey.Parse(in) |
|
| 1257 |
+ if !ok {
|
|
| 1258 |
+ return in, ok |
|
| 1259 |
+ } |
|
| 1260 |
+ priv.PrivateKey.PublicKey = priv.PublicKey.PublicKey |
|
| 1261 |
+ priv.PrivateKey.X, in, ok = getMPI(in) |
|
| 1262 |
+ return in, ok |
|
| 1263 |
+} |
|
| 1264 |
+ |
|
| 1265 |
+func (priv *PrivateKey) Generate(rand io.Reader) {
|
|
| 1266 |
+ if err := dsa.GenerateParameters(&priv.PrivateKey.PublicKey.Parameters, rand, dsa.L1024N160); err != nil {
|
|
| 1267 |
+ panic(err.Error()) |
|
| 1268 |
+ } |
|
| 1269 |
+ if err := dsa.GenerateKey(&priv.PrivateKey, rand); err != nil {
|
|
| 1270 |
+ panic(err.Error()) |
|
| 1271 |
+ } |
|
| 1272 |
+ priv.PublicKey.PublicKey = priv.PrivateKey.PublicKey |
|
| 1273 |
+} |
|
| 1274 |
+ |
|
| 1275 |
+func notHex(r rune) bool {
|
|
| 1276 |
+ if r >= '0' && r <= '9' || |
|
| 1277 |
+ r >= 'a' && r <= 'f' || |
|
| 1278 |
+ r >= 'A' && r <= 'F' {
|
|
| 1279 |
+ return false |
|
| 1280 |
+ } |
|
| 1281 |
+ |
|
| 1282 |
+ return true |
|
| 1283 |
+} |
|
| 1284 |
+ |
|
| 1285 |
+// Import parses the contents of a libotr private key file. |
|
| 1286 |
+func (priv *PrivateKey) Import(in []byte) bool {
|
|
| 1287 |
+ mpiStart := []byte(" #")
|
|
| 1288 |
+ |
|
| 1289 |
+ mpis := make([]*big.Int, 5) |
|
| 1290 |
+ |
|
| 1291 |
+ for i := 0; i < len(mpis); i++ {
|
|
| 1292 |
+ start := bytes.Index(in, mpiStart) |
|
| 1293 |
+ if start == -1 {
|
|
| 1294 |
+ return false |
|
| 1295 |
+ } |
|
| 1296 |
+ in = in[start+len(mpiStart):] |
|
| 1297 |
+ end := bytes.IndexFunc(in, notHex) |
|
| 1298 |
+ if end == -1 {
|
|
| 1299 |
+ return false |
|
| 1300 |
+ } |
|
| 1301 |
+ hexBytes := in[:end] |
|
| 1302 |
+ in = in[end:] |
|
| 1303 |
+ |
|
| 1304 |
+ if len(hexBytes)&1 != 0 {
|
|
| 1305 |
+ return false |
|
| 1306 |
+ } |
|
| 1307 |
+ |
|
| 1308 |
+ mpiBytes := make([]byte, len(hexBytes)/2) |
|
| 1309 |
+ if _, err := hex.Decode(mpiBytes, hexBytes); err != nil {
|
|
| 1310 |
+ return false |
|
| 1311 |
+ } |
|
| 1312 |
+ |
|
| 1313 |
+ mpis[i] = new(big.Int).SetBytes(mpiBytes) |
|
| 1314 |
+ } |
|
| 1315 |
+ |
|
| 1316 |
+ for _, mpi := range mpis {
|
|
| 1317 |
+ if mpi.Sign() <= 0 {
|
|
| 1318 |
+ return false |
|
| 1319 |
+ } |
|
| 1320 |
+ } |
|
| 1321 |
+ |
|
| 1322 |
+ priv.PrivateKey.P = mpis[0] |
|
| 1323 |
+ priv.PrivateKey.Q = mpis[1] |
|
| 1324 |
+ priv.PrivateKey.G = mpis[2] |
|
| 1325 |
+ priv.PrivateKey.Y = mpis[3] |
|
| 1326 |
+ priv.PrivateKey.X = mpis[4] |
|
| 1327 |
+ priv.PublicKey.PublicKey = priv.PrivateKey.PublicKey |
|
| 1328 |
+ |
|
| 1329 |
+ a := new(big.Int).Exp(priv.PrivateKey.G, priv.PrivateKey.X, priv.PrivateKey.P) |
|
| 1330 |
+ return a.Cmp(priv.PrivateKey.Y) == 0 |
|
| 1331 |
+} |
|
| 1332 |
+ |
|
| 1333 |
+func getU8(in []byte) (uint8, []byte, bool) {
|
|
| 1334 |
+ if len(in) < 1 {
|
|
| 1335 |
+ return 0, in, false |
|
| 1336 |
+ } |
|
| 1337 |
+ return in[0], in[1:], true |
|
| 1338 |
+} |
|
| 1339 |
+ |
|
| 1340 |
+func getU16(in []byte) (uint16, []byte, bool) {
|
|
| 1341 |
+ if len(in) < 2 {
|
|
| 1342 |
+ return 0, in, false |
|
| 1343 |
+ } |
|
| 1344 |
+ r := uint16(in[0])<<8 | uint16(in[1]) |
|
| 1345 |
+ return r, in[2:], true |
|
| 1346 |
+} |
|
| 1347 |
+ |
|
| 1348 |
+func getU32(in []byte) (uint32, []byte, bool) {
|
|
| 1349 |
+ if len(in) < 4 {
|
|
| 1350 |
+ return 0, in, false |
|
| 1351 |
+ } |
|
| 1352 |
+ r := uint32(in[0])<<24 | uint32(in[1])<<16 | uint32(in[2])<<8 | uint32(in[3]) |
|
| 1353 |
+ return r, in[4:], true |
|
| 1354 |
+} |
|
| 1355 |
+ |
|
| 1356 |
+func getMPI(in []byte) (*big.Int, []byte, bool) {
|
|
| 1357 |
+ l, in, ok := getU32(in) |
|
| 1358 |
+ if !ok || uint32(len(in)) < l {
|
|
| 1359 |
+ return nil, in, false |
|
| 1360 |
+ } |
|
| 1361 |
+ r := new(big.Int).SetBytes(in[:l]) |
|
| 1362 |
+ return r, in[l:], true |
|
| 1363 |
+} |
|
| 1364 |
+ |
|
| 1365 |
+func getData(in []byte) ([]byte, []byte, bool) {
|
|
| 1366 |
+ l, in, ok := getU32(in) |
|
| 1367 |
+ if !ok || uint32(len(in)) < l {
|
|
| 1368 |
+ return nil, in, false |
|
| 1369 |
+ } |
|
| 1370 |
+ return in[:l], in[l:], true |
|
| 1371 |
+} |
|
| 1372 |
+ |
|
| 1373 |
+func getNBytes(in []byte, n int) ([]byte, []byte, bool) {
|
|
| 1374 |
+ if len(in) < n {
|
|
| 1375 |
+ return nil, in, false |
|
| 1376 |
+ } |
|
| 1377 |
+ return in[:n], in[n:], true |
|
| 1378 |
+} |
|
| 1379 |
+ |
|
| 1380 |
+func appendU16(out []byte, v uint16) []byte {
|
|
| 1381 |
+ out = append(out, byte(v>>8), byte(v)) |
|
| 1382 |
+ return out |
|
| 1383 |
+} |
|
| 1384 |
+ |
|
| 1385 |
+func appendU32(out []byte, v uint32) []byte {
|
|
| 1386 |
+ out = append(out, byte(v>>24), byte(v>>16), byte(v>>8), byte(v)) |
|
| 1387 |
+ return out |
|
| 1388 |
+} |
|
| 1389 |
+ |
|
| 1390 |
+func appendData(out, v []byte) []byte {
|
|
| 1391 |
+ out = appendU32(out, uint32(len(v))) |
|
| 1392 |
+ out = append(out, v...) |
|
| 1393 |
+ return out |
|
| 1394 |
+} |
|
| 1395 |
+ |
|
| 1396 |
+func appendMPI(out []byte, v *big.Int) []byte {
|
|
| 1397 |
+ vBytes := v.Bytes() |
|
| 1398 |
+ out = appendU32(out, uint32(len(vBytes))) |
|
| 1399 |
+ out = append(out, vBytes...) |
|
| 1400 |
+ return out |
|
| 1401 |
+} |
|
| 1402 |
+ |
|
| 1403 |
+func appendMPIs(out []byte, mpis ...*big.Int) []byte {
|
|
| 1404 |
+ for _, mpi := range mpis {
|
|
| 1405 |
+ out = appendMPI(out, mpi) |
|
| 1406 |
+ } |
|
| 1407 |
+ return out |
|
| 1408 |
+} |
|
| 1409 |
+ |
|
| 1410 |
+func zero(b []byte) {
|
|
| 1411 |
+ for i := range b {
|
|
| 1412 |
+ b[i] = 0 |
|
| 1413 |
+ } |
|
| 1414 |
+} |
| 0 | 1415 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,572 @@ |
| 0 |
+// Copyright 2012 The Go Authors. All rights reserved. |
|
| 1 |
+// Use of this source code is governed by a BSD-style |
|
| 2 |
+// license that can be found in the LICENSE file. |
|
| 3 |
+ |
|
| 4 |
+// This file implements the Socialist Millionaires Protocol as described in |
|
| 5 |
+// http://www.cypherpunks.ca/otr/Protocol-v2-3.1.0.html. The protocol |
|
| 6 |
+// specification is required in order to understand this code and, where |
|
| 7 |
+// possible, the variable names in the code match up with the spec. |
|
| 8 |
+ |
|
| 9 |
+package otr |
|
| 10 |
+ |
|
| 11 |
+import ( |
|
| 12 |
+ "bytes" |
|
| 13 |
+ "crypto/sha256" |
|
| 14 |
+ "errors" |
|
| 15 |
+ "hash" |
|
| 16 |
+ "math/big" |
|
| 17 |
+) |
|
| 18 |
+ |
|
| 19 |
+type smpFailure string |
|
| 20 |
+ |
|
| 21 |
+func (s smpFailure) Error() string {
|
|
| 22 |
+ return string(s) |
|
| 23 |
+} |
|
| 24 |
+ |
|
| 25 |
+var smpFailureError = smpFailure("otr: SMP protocol failed")
|
|
| 26 |
+var smpSecretMissingError = smpFailure("otr: mutual secret needed")
|
|
| 27 |
+ |
|
| 28 |
+const smpVersion = 1 |
|
| 29 |
+ |
|
| 30 |
+const ( |
|
| 31 |
+ smpState1 = iota |
|
| 32 |
+ smpState2 |
|
| 33 |
+ smpState3 |
|
| 34 |
+ smpState4 |
|
| 35 |
+) |
|
| 36 |
+ |
|
| 37 |
+type smpState struct {
|
|
| 38 |
+ state int |
|
| 39 |
+ a2, a3, b2, b3, pb, qb *big.Int |
|
| 40 |
+ g2a, g3a *big.Int |
|
| 41 |
+ g2, g3 *big.Int |
|
| 42 |
+ g3b, papb, qaqb, ra *big.Int |
|
| 43 |
+ saved *tlv |
|
| 44 |
+ secret *big.Int |
|
| 45 |
+ question string |
|
| 46 |
+} |
|
| 47 |
+ |
|
| 48 |
+func (c *Conversation) startSMP(question string) (tlvs []tlv) {
|
|
| 49 |
+ if c.smp.state != smpState1 {
|
|
| 50 |
+ tlvs = append(tlvs, c.generateSMPAbort()) |
|
| 51 |
+ } |
|
| 52 |
+ tlvs = append(tlvs, c.generateSMP1(question)) |
|
| 53 |
+ c.smp.question = "" |
|
| 54 |
+ c.smp.state = smpState2 |
|
| 55 |
+ return |
|
| 56 |
+} |
|
| 57 |
+ |
|
| 58 |
+func (c *Conversation) resetSMP() {
|
|
| 59 |
+ c.smp.state = smpState1 |
|
| 60 |
+ c.smp.secret = nil |
|
| 61 |
+ c.smp.question = "" |
|
| 62 |
+} |
|
| 63 |
+ |
|
| 64 |
+func (c *Conversation) processSMP(in tlv) (out tlv, complete bool, err error) {
|
|
| 65 |
+ data := in.data |
|
| 66 |
+ |
|
| 67 |
+ switch in.typ {
|
|
| 68 |
+ case tlvTypeSMPAbort: |
|
| 69 |
+ if c.smp.state != smpState1 {
|
|
| 70 |
+ err = smpFailureError |
|
| 71 |
+ } |
|
| 72 |
+ c.resetSMP() |
|
| 73 |
+ return |
|
| 74 |
+ case tlvTypeSMP1WithQuestion: |
|
| 75 |
+ // We preprocess this into a SMP1 message. |
|
| 76 |
+ nulPos := bytes.IndexByte(data, 0) |
|
| 77 |
+ if nulPos == -1 {
|
|
| 78 |
+ err = errors.New("otr: SMP message with question didn't contain a NUL byte")
|
|
| 79 |
+ return |
|
| 80 |
+ } |
|
| 81 |
+ c.smp.question = string(data[:nulPos]) |
|
| 82 |
+ data = data[nulPos+1:] |
|
| 83 |
+ } |
|
| 84 |
+ |
|
| 85 |
+ numMPIs, data, ok := getU32(data) |
|
| 86 |
+ if !ok || numMPIs > 20 {
|
|
| 87 |
+ err = errors.New("otr: corrupt SMP message")
|
|
| 88 |
+ return |
|
| 89 |
+ } |
|
| 90 |
+ |
|
| 91 |
+ mpis := make([]*big.Int, numMPIs) |
|
| 92 |
+ for i := range mpis {
|
|
| 93 |
+ var ok bool |
|
| 94 |
+ mpis[i], data, ok = getMPI(data) |
|
| 95 |
+ if !ok {
|
|
| 96 |
+ err = errors.New("otr: corrupt SMP message")
|
|
| 97 |
+ return |
|
| 98 |
+ } |
|
| 99 |
+ } |
|
| 100 |
+ |
|
| 101 |
+ switch in.typ {
|
|
| 102 |
+ case tlvTypeSMP1, tlvTypeSMP1WithQuestion: |
|
| 103 |
+ if c.smp.state != smpState1 {
|
|
| 104 |
+ c.resetSMP() |
|
| 105 |
+ out = c.generateSMPAbort() |
|
| 106 |
+ return |
|
| 107 |
+ } |
|
| 108 |
+ if c.smp.secret == nil {
|
|
| 109 |
+ err = smpSecretMissingError |
|
| 110 |
+ return |
|
| 111 |
+ } |
|
| 112 |
+ if err = c.processSMP1(mpis); err != nil {
|
|
| 113 |
+ return |
|
| 114 |
+ } |
|
| 115 |
+ c.smp.state = smpState3 |
|
| 116 |
+ out = c.generateSMP2() |
|
| 117 |
+ case tlvTypeSMP2: |
|
| 118 |
+ if c.smp.state != smpState2 {
|
|
| 119 |
+ c.resetSMP() |
|
| 120 |
+ out = c.generateSMPAbort() |
|
| 121 |
+ return |
|
| 122 |
+ } |
|
| 123 |
+ if out, err = c.processSMP2(mpis); err != nil {
|
|
| 124 |
+ out = c.generateSMPAbort() |
|
| 125 |
+ return |
|
| 126 |
+ } |
|
| 127 |
+ c.smp.state = smpState4 |
|
| 128 |
+ case tlvTypeSMP3: |
|
| 129 |
+ if c.smp.state != smpState3 {
|
|
| 130 |
+ c.resetSMP() |
|
| 131 |
+ out = c.generateSMPAbort() |
|
| 132 |
+ return |
|
| 133 |
+ } |
|
| 134 |
+ if out, err = c.processSMP3(mpis); err != nil {
|
|
| 135 |
+ return |
|
| 136 |
+ } |
|
| 137 |
+ c.smp.state = smpState1 |
|
| 138 |
+ c.smp.secret = nil |
|
| 139 |
+ complete = true |
|
| 140 |
+ case tlvTypeSMP4: |
|
| 141 |
+ if c.smp.state != smpState4 {
|
|
| 142 |
+ c.resetSMP() |
|
| 143 |
+ out = c.generateSMPAbort() |
|
| 144 |
+ return |
|
| 145 |
+ } |
|
| 146 |
+ if err = c.processSMP4(mpis); err != nil {
|
|
| 147 |
+ out = c.generateSMPAbort() |
|
| 148 |
+ return |
|
| 149 |
+ } |
|
| 150 |
+ c.smp.state = smpState1 |
|
| 151 |
+ c.smp.secret = nil |
|
| 152 |
+ complete = true |
|
| 153 |
+ default: |
|
| 154 |
+ panic("unknown SMP message")
|
|
| 155 |
+ } |
|
| 156 |
+ |
|
| 157 |
+ return |
|
| 158 |
+} |
|
| 159 |
+ |
|
| 160 |
+func (c *Conversation) calcSMPSecret(mutualSecret []byte, weStarted bool) {
|
|
| 161 |
+ h := sha256.New() |
|
| 162 |
+ h.Write([]byte{smpVersion})
|
|
| 163 |
+ if weStarted {
|
|
| 164 |
+ h.Write(c.PrivateKey.PublicKey.Fingerprint()) |
|
| 165 |
+ h.Write(c.TheirPublicKey.Fingerprint()) |
|
| 166 |
+ } else {
|
|
| 167 |
+ h.Write(c.TheirPublicKey.Fingerprint()) |
|
| 168 |
+ h.Write(c.PrivateKey.PublicKey.Fingerprint()) |
|
| 169 |
+ } |
|
| 170 |
+ h.Write(c.SSID[:]) |
|
| 171 |
+ h.Write(mutualSecret) |
|
| 172 |
+ c.smp.secret = new(big.Int).SetBytes(h.Sum(nil)) |
|
| 173 |
+} |
|
| 174 |
+ |
|
| 175 |
+func (c *Conversation) generateSMP1(question string) tlv {
|
|
| 176 |
+ var randBuf [16]byte |
|
| 177 |
+ c.smp.a2 = c.randMPI(randBuf[:]) |
|
| 178 |
+ c.smp.a3 = c.randMPI(randBuf[:]) |
|
| 179 |
+ g2a := new(big.Int).Exp(g, c.smp.a2, p) |
|
| 180 |
+ g3a := new(big.Int).Exp(g, c.smp.a3, p) |
|
| 181 |
+ h := sha256.New() |
|
| 182 |
+ |
|
| 183 |
+ r2 := c.randMPI(randBuf[:]) |
|
| 184 |
+ r := new(big.Int).Exp(g, r2, p) |
|
| 185 |
+ c2 := new(big.Int).SetBytes(hashMPIs(h, 1, r)) |
|
| 186 |
+ d2 := new(big.Int).Mul(c.smp.a2, c2) |
|
| 187 |
+ d2.Sub(r2, d2) |
|
| 188 |
+ d2.Mod(d2, q) |
|
| 189 |
+ if d2.Sign() < 0 {
|
|
| 190 |
+ d2.Add(d2, q) |
|
| 191 |
+ } |
|
| 192 |
+ |
|
| 193 |
+ r3 := c.randMPI(randBuf[:]) |
|
| 194 |
+ r.Exp(g, r3, p) |
|
| 195 |
+ c3 := new(big.Int).SetBytes(hashMPIs(h, 2, r)) |
|
| 196 |
+ d3 := new(big.Int).Mul(c.smp.a3, c3) |
|
| 197 |
+ d3.Sub(r3, d3) |
|
| 198 |
+ d3.Mod(d3, q) |
|
| 199 |
+ if d3.Sign() < 0 {
|
|
| 200 |
+ d3.Add(d3, q) |
|
| 201 |
+ } |
|
| 202 |
+ |
|
| 203 |
+ var ret tlv |
|
| 204 |
+ if len(question) > 0 {
|
|
| 205 |
+ ret.typ = tlvTypeSMP1WithQuestion |
|
| 206 |
+ ret.data = append(ret.data, question...) |
|
| 207 |
+ ret.data = append(ret.data, 0) |
|
| 208 |
+ } else {
|
|
| 209 |
+ ret.typ = tlvTypeSMP1 |
|
| 210 |
+ } |
|
| 211 |
+ ret.data = appendU32(ret.data, 6) |
|
| 212 |
+ ret.data = appendMPIs(ret.data, g2a, c2, d2, g3a, c3, d3) |
|
| 213 |
+ return ret |
|
| 214 |
+} |
|
| 215 |
+ |
|
| 216 |
+func (c *Conversation) processSMP1(mpis []*big.Int) error {
|
|
| 217 |
+ if len(mpis) != 6 {
|
|
| 218 |
+ return errors.New("otr: incorrect number of arguments in SMP1 message")
|
|
| 219 |
+ } |
|
| 220 |
+ g2a := mpis[0] |
|
| 221 |
+ c2 := mpis[1] |
|
| 222 |
+ d2 := mpis[2] |
|
| 223 |
+ g3a := mpis[3] |
|
| 224 |
+ c3 := mpis[4] |
|
| 225 |
+ d3 := mpis[5] |
|
| 226 |
+ h := sha256.New() |
|
| 227 |
+ |
|
| 228 |
+ r := new(big.Int).Exp(g, d2, p) |
|
| 229 |
+ s := new(big.Int).Exp(g2a, c2, p) |
|
| 230 |
+ r.Mul(r, s) |
|
| 231 |
+ r.Mod(r, p) |
|
| 232 |
+ t := new(big.Int).SetBytes(hashMPIs(h, 1, r)) |
|
| 233 |
+ if c2.Cmp(t) != 0 {
|
|
| 234 |
+ return errors.New("otr: ZKP c2 incorrect in SMP1 message")
|
|
| 235 |
+ } |
|
| 236 |
+ r.Exp(g, d3, p) |
|
| 237 |
+ s.Exp(g3a, c3, p) |
|
| 238 |
+ r.Mul(r, s) |
|
| 239 |
+ r.Mod(r, p) |
|
| 240 |
+ t.SetBytes(hashMPIs(h, 2, r)) |
|
| 241 |
+ if c3.Cmp(t) != 0 {
|
|
| 242 |
+ return errors.New("otr: ZKP c3 incorrect in SMP1 message")
|
|
| 243 |
+ } |
|
| 244 |
+ |
|
| 245 |
+ c.smp.g2a = g2a |
|
| 246 |
+ c.smp.g3a = g3a |
|
| 247 |
+ return nil |
|
| 248 |
+} |
|
| 249 |
+ |
|
| 250 |
+func (c *Conversation) generateSMP2() tlv {
|
|
| 251 |
+ var randBuf [16]byte |
|
| 252 |
+ b2 := c.randMPI(randBuf[:]) |
|
| 253 |
+ c.smp.b3 = c.randMPI(randBuf[:]) |
|
| 254 |
+ r2 := c.randMPI(randBuf[:]) |
|
| 255 |
+ r3 := c.randMPI(randBuf[:]) |
|
| 256 |
+ r4 := c.randMPI(randBuf[:]) |
|
| 257 |
+ r5 := c.randMPI(randBuf[:]) |
|
| 258 |
+ r6 := c.randMPI(randBuf[:]) |
|
| 259 |
+ |
|
| 260 |
+ g2b := new(big.Int).Exp(g, b2, p) |
|
| 261 |
+ g3b := new(big.Int).Exp(g, c.smp.b3, p) |
|
| 262 |
+ |
|
| 263 |
+ r := new(big.Int).Exp(g, r2, p) |
|
| 264 |
+ h := sha256.New() |
|
| 265 |
+ c2 := new(big.Int).SetBytes(hashMPIs(h, 3, r)) |
|
| 266 |
+ d2 := new(big.Int).Mul(b2, c2) |
|
| 267 |
+ d2.Sub(r2, d2) |
|
| 268 |
+ d2.Mod(d2, q) |
|
| 269 |
+ if d2.Sign() < 0 {
|
|
| 270 |
+ d2.Add(d2, q) |
|
| 271 |
+ } |
|
| 272 |
+ |
|
| 273 |
+ r.Exp(g, r3, p) |
|
| 274 |
+ c3 := new(big.Int).SetBytes(hashMPIs(h, 4, r)) |
|
| 275 |
+ d3 := new(big.Int).Mul(c.smp.b3, c3) |
|
| 276 |
+ d3.Sub(r3, d3) |
|
| 277 |
+ d3.Mod(d3, q) |
|
| 278 |
+ if d3.Sign() < 0 {
|
|
| 279 |
+ d3.Add(d3, q) |
|
| 280 |
+ } |
|
| 281 |
+ |
|
| 282 |
+ c.smp.g2 = new(big.Int).Exp(c.smp.g2a, b2, p) |
|
| 283 |
+ c.smp.g3 = new(big.Int).Exp(c.smp.g3a, c.smp.b3, p) |
|
| 284 |
+ c.smp.pb = new(big.Int).Exp(c.smp.g3, r4, p) |
|
| 285 |
+ c.smp.qb = new(big.Int).Exp(g, r4, p) |
|
| 286 |
+ r.Exp(c.smp.g2, c.smp.secret, p) |
|
| 287 |
+ c.smp.qb.Mul(c.smp.qb, r) |
|
| 288 |
+ c.smp.qb.Mod(c.smp.qb, p) |
|
| 289 |
+ |
|
| 290 |
+ s := new(big.Int) |
|
| 291 |
+ s.Exp(c.smp.g2, r6, p) |
|
| 292 |
+ r.Exp(g, r5, p) |
|
| 293 |
+ s.Mul(r, s) |
|
| 294 |
+ s.Mod(s, p) |
|
| 295 |
+ r.Exp(c.smp.g3, r5, p) |
|
| 296 |
+ cp := new(big.Int).SetBytes(hashMPIs(h, 5, r, s)) |
|
| 297 |
+ |
|
| 298 |
+ // D5 = r5 - r4 cP mod q and D6 = r6 - y cP mod q |
|
| 299 |
+ |
|
| 300 |
+ s.Mul(r4, cp) |
|
| 301 |
+ r.Sub(r5, s) |
|
| 302 |
+ d5 := new(big.Int).Mod(r, q) |
|
| 303 |
+ if d5.Sign() < 0 {
|
|
| 304 |
+ d5.Add(d5, q) |
|
| 305 |
+ } |
|
| 306 |
+ |
|
| 307 |
+ s.Mul(c.smp.secret, cp) |
|
| 308 |
+ r.Sub(r6, s) |
|
| 309 |
+ d6 := new(big.Int).Mod(r, q) |
|
| 310 |
+ if d6.Sign() < 0 {
|
|
| 311 |
+ d6.Add(d6, q) |
|
| 312 |
+ } |
|
| 313 |
+ |
|
| 314 |
+ var ret tlv |
|
| 315 |
+ ret.typ = tlvTypeSMP2 |
|
| 316 |
+ ret.data = appendU32(ret.data, 11) |
|
| 317 |
+ ret.data = appendMPIs(ret.data, g2b, c2, d2, g3b, c3, d3, c.smp.pb, c.smp.qb, cp, d5, d6) |
|
| 318 |
+ return ret |
|
| 319 |
+} |
|
| 320 |
+ |
|
| 321 |
+func (c *Conversation) processSMP2(mpis []*big.Int) (out tlv, err error) {
|
|
| 322 |
+ if len(mpis) != 11 {
|
|
| 323 |
+ err = errors.New("otr: incorrect number of arguments in SMP2 message")
|
|
| 324 |
+ return |
|
| 325 |
+ } |
|
| 326 |
+ g2b := mpis[0] |
|
| 327 |
+ c2 := mpis[1] |
|
| 328 |
+ d2 := mpis[2] |
|
| 329 |
+ g3b := mpis[3] |
|
| 330 |
+ c3 := mpis[4] |
|
| 331 |
+ d3 := mpis[5] |
|
| 332 |
+ pb := mpis[6] |
|
| 333 |
+ qb := mpis[7] |
|
| 334 |
+ cp := mpis[8] |
|
| 335 |
+ d5 := mpis[9] |
|
| 336 |
+ d6 := mpis[10] |
|
| 337 |
+ h := sha256.New() |
|
| 338 |
+ |
|
| 339 |
+ r := new(big.Int).Exp(g, d2, p) |
|
| 340 |
+ s := new(big.Int).Exp(g2b, c2, p) |
|
| 341 |
+ r.Mul(r, s) |
|
| 342 |
+ r.Mod(r, p) |
|
| 343 |
+ s.SetBytes(hashMPIs(h, 3, r)) |
|
| 344 |
+ if c2.Cmp(s) != 0 {
|
|
| 345 |
+ err = errors.New("otr: ZKP c2 failed in SMP2 message")
|
|
| 346 |
+ return |
|
| 347 |
+ } |
|
| 348 |
+ |
|
| 349 |
+ r.Exp(g, d3, p) |
|
| 350 |
+ s.Exp(g3b, c3, p) |
|
| 351 |
+ r.Mul(r, s) |
|
| 352 |
+ r.Mod(r, p) |
|
| 353 |
+ s.SetBytes(hashMPIs(h, 4, r)) |
|
| 354 |
+ if c3.Cmp(s) != 0 {
|
|
| 355 |
+ err = errors.New("otr: ZKP c3 failed in SMP2 message")
|
|
| 356 |
+ return |
|
| 357 |
+ } |
|
| 358 |
+ |
|
| 359 |
+ c.smp.g2 = new(big.Int).Exp(g2b, c.smp.a2, p) |
|
| 360 |
+ c.smp.g3 = new(big.Int).Exp(g3b, c.smp.a3, p) |
|
| 361 |
+ |
|
| 362 |
+ r.Exp(g, d5, p) |
|
| 363 |
+ s.Exp(c.smp.g2, d6, p) |
|
| 364 |
+ r.Mul(r, s) |
|
| 365 |
+ s.Exp(qb, cp, p) |
|
| 366 |
+ r.Mul(r, s) |
|
| 367 |
+ r.Mod(r, p) |
|
| 368 |
+ |
|
| 369 |
+ s.Exp(c.smp.g3, d5, p) |
|
| 370 |
+ t := new(big.Int).Exp(pb, cp, p) |
|
| 371 |
+ s.Mul(s, t) |
|
| 372 |
+ s.Mod(s, p) |
|
| 373 |
+ t.SetBytes(hashMPIs(h, 5, s, r)) |
|
| 374 |
+ if cp.Cmp(t) != 0 {
|
|
| 375 |
+ err = errors.New("otr: ZKP cP failed in SMP2 message")
|
|
| 376 |
+ return |
|
| 377 |
+ } |
|
| 378 |
+ |
|
| 379 |
+ var randBuf [16]byte |
|
| 380 |
+ r4 := c.randMPI(randBuf[:]) |
|
| 381 |
+ r5 := c.randMPI(randBuf[:]) |
|
| 382 |
+ r6 := c.randMPI(randBuf[:]) |
|
| 383 |
+ r7 := c.randMPI(randBuf[:]) |
|
| 384 |
+ |
|
| 385 |
+ pa := new(big.Int).Exp(c.smp.g3, r4, p) |
|
| 386 |
+ r.Exp(c.smp.g2, c.smp.secret, p) |
|
| 387 |
+ qa := new(big.Int).Exp(g, r4, p) |
|
| 388 |
+ qa.Mul(qa, r) |
|
| 389 |
+ qa.Mod(qa, p) |
|
| 390 |
+ |
|
| 391 |
+ r.Exp(g, r5, p) |
|
| 392 |
+ s.Exp(c.smp.g2, r6, p) |
|
| 393 |
+ r.Mul(r, s) |
|
| 394 |
+ r.Mod(r, p) |
|
| 395 |
+ |
|
| 396 |
+ s.Exp(c.smp.g3, r5, p) |
|
| 397 |
+ cp.SetBytes(hashMPIs(h, 6, s, r)) |
|
| 398 |
+ |
|
| 399 |
+ r.Mul(r4, cp) |
|
| 400 |
+ d5 = new(big.Int).Sub(r5, r) |
|
| 401 |
+ d5.Mod(d5, q) |
|
| 402 |
+ if d5.Sign() < 0 {
|
|
| 403 |
+ d5.Add(d5, q) |
|
| 404 |
+ } |
|
| 405 |
+ |
|
| 406 |
+ r.Mul(c.smp.secret, cp) |
|
| 407 |
+ d6 = new(big.Int).Sub(r6, r) |
|
| 408 |
+ d6.Mod(d6, q) |
|
| 409 |
+ if d6.Sign() < 0 {
|
|
| 410 |
+ d6.Add(d6, q) |
|
| 411 |
+ } |
|
| 412 |
+ |
|
| 413 |
+ r.ModInverse(qb, p) |
|
| 414 |
+ qaqb := new(big.Int).Mul(qa, r) |
|
| 415 |
+ qaqb.Mod(qaqb, p) |
|
| 416 |
+ |
|
| 417 |
+ ra := new(big.Int).Exp(qaqb, c.smp.a3, p) |
|
| 418 |
+ r.Exp(qaqb, r7, p) |
|
| 419 |
+ s.Exp(g, r7, p) |
|
| 420 |
+ cr := new(big.Int).SetBytes(hashMPIs(h, 7, s, r)) |
|
| 421 |
+ |
|
| 422 |
+ r.Mul(c.smp.a3, cr) |
|
| 423 |
+ d7 := new(big.Int).Sub(r7, r) |
|
| 424 |
+ d7.Mod(d7, q) |
|
| 425 |
+ if d7.Sign() < 0 {
|
|
| 426 |
+ d7.Add(d7, q) |
|
| 427 |
+ } |
|
| 428 |
+ |
|
| 429 |
+ c.smp.g3b = g3b |
|
| 430 |
+ c.smp.qaqb = qaqb |
|
| 431 |
+ |
|
| 432 |
+ r.ModInverse(pb, p) |
|
| 433 |
+ c.smp.papb = new(big.Int).Mul(pa, r) |
|
| 434 |
+ c.smp.papb.Mod(c.smp.papb, p) |
|
| 435 |
+ c.smp.ra = ra |
|
| 436 |
+ |
|
| 437 |
+ out.typ = tlvTypeSMP3 |
|
| 438 |
+ out.data = appendU32(out.data, 8) |
|
| 439 |
+ out.data = appendMPIs(out.data, pa, qa, cp, d5, d6, ra, cr, d7) |
|
| 440 |
+ return |
|
| 441 |
+} |
|
| 442 |
+ |
|
| 443 |
+func (c *Conversation) processSMP3(mpis []*big.Int) (out tlv, err error) {
|
|
| 444 |
+ if len(mpis) != 8 {
|
|
| 445 |
+ err = errors.New("otr: incorrect number of arguments in SMP3 message")
|
|
| 446 |
+ return |
|
| 447 |
+ } |
|
| 448 |
+ pa := mpis[0] |
|
| 449 |
+ qa := mpis[1] |
|
| 450 |
+ cp := mpis[2] |
|
| 451 |
+ d5 := mpis[3] |
|
| 452 |
+ d6 := mpis[4] |
|
| 453 |
+ ra := mpis[5] |
|
| 454 |
+ cr := mpis[6] |
|
| 455 |
+ d7 := mpis[7] |
|
| 456 |
+ h := sha256.New() |
|
| 457 |
+ |
|
| 458 |
+ r := new(big.Int).Exp(g, d5, p) |
|
| 459 |
+ s := new(big.Int).Exp(c.smp.g2, d6, p) |
|
| 460 |
+ r.Mul(r, s) |
|
| 461 |
+ s.Exp(qa, cp, p) |
|
| 462 |
+ r.Mul(r, s) |
|
| 463 |
+ r.Mod(r, p) |
|
| 464 |
+ |
|
| 465 |
+ s.Exp(c.smp.g3, d5, p) |
|
| 466 |
+ t := new(big.Int).Exp(pa, cp, p) |
|
| 467 |
+ s.Mul(s, t) |
|
| 468 |
+ s.Mod(s, p) |
|
| 469 |
+ t.SetBytes(hashMPIs(h, 6, s, r)) |
|
| 470 |
+ if t.Cmp(cp) != 0 {
|
|
| 471 |
+ err = errors.New("otr: ZKP cP failed in SMP3 message")
|
|
| 472 |
+ return |
|
| 473 |
+ } |
|
| 474 |
+ |
|
| 475 |
+ r.ModInverse(c.smp.qb, p) |
|
| 476 |
+ qaqb := new(big.Int).Mul(qa, r) |
|
| 477 |
+ qaqb.Mod(qaqb, p) |
|
| 478 |
+ |
|
| 479 |
+ r.Exp(qaqb, d7, p) |
|
| 480 |
+ s.Exp(ra, cr, p) |
|
| 481 |
+ r.Mul(r, s) |
|
| 482 |
+ r.Mod(r, p) |
|
| 483 |
+ |
|
| 484 |
+ s.Exp(g, d7, p) |
|
| 485 |
+ t.Exp(c.smp.g3a, cr, p) |
|
| 486 |
+ s.Mul(s, t) |
|
| 487 |
+ s.Mod(s, p) |
|
| 488 |
+ t.SetBytes(hashMPIs(h, 7, s, r)) |
|
| 489 |
+ if t.Cmp(cr) != 0 {
|
|
| 490 |
+ err = errors.New("otr: ZKP cR failed in SMP3 message")
|
|
| 491 |
+ return |
|
| 492 |
+ } |
|
| 493 |
+ |
|
| 494 |
+ var randBuf [16]byte |
|
| 495 |
+ r7 := c.randMPI(randBuf[:]) |
|
| 496 |
+ rb := new(big.Int).Exp(qaqb, c.smp.b3, p) |
|
| 497 |
+ |
|
| 498 |
+ r.Exp(qaqb, r7, p) |
|
| 499 |
+ s.Exp(g, r7, p) |
|
| 500 |
+ cr = new(big.Int).SetBytes(hashMPIs(h, 8, s, r)) |
|
| 501 |
+ |
|
| 502 |
+ r.Mul(c.smp.b3, cr) |
|
| 503 |
+ d7 = new(big.Int).Sub(r7, r) |
|
| 504 |
+ d7.Mod(d7, q) |
|
| 505 |
+ if d7.Sign() < 0 {
|
|
| 506 |
+ d7.Add(d7, q) |
|
| 507 |
+ } |
|
| 508 |
+ |
|
| 509 |
+ out.typ = tlvTypeSMP4 |
|
| 510 |
+ out.data = appendU32(out.data, 3) |
|
| 511 |
+ out.data = appendMPIs(out.data, rb, cr, d7) |
|
| 512 |
+ |
|
| 513 |
+ r.ModInverse(c.smp.pb, p) |
|
| 514 |
+ r.Mul(pa, r) |
|
| 515 |
+ r.Mod(r, p) |
|
| 516 |
+ s.Exp(ra, c.smp.b3, p) |
|
| 517 |
+ if r.Cmp(s) != 0 {
|
|
| 518 |
+ err = smpFailureError |
|
| 519 |
+ } |
|
| 520 |
+ |
|
| 521 |
+ return |
|
| 522 |
+} |
|
| 523 |
+ |
|
| 524 |
+func (c *Conversation) processSMP4(mpis []*big.Int) error {
|
|
| 525 |
+ if len(mpis) != 3 {
|
|
| 526 |
+ return errors.New("otr: incorrect number of arguments in SMP4 message")
|
|
| 527 |
+ } |
|
| 528 |
+ rb := mpis[0] |
|
| 529 |
+ cr := mpis[1] |
|
| 530 |
+ d7 := mpis[2] |
|
| 531 |
+ h := sha256.New() |
|
| 532 |
+ |
|
| 533 |
+ r := new(big.Int).Exp(c.smp.qaqb, d7, p) |
|
| 534 |
+ s := new(big.Int).Exp(rb, cr, p) |
|
| 535 |
+ r.Mul(r, s) |
|
| 536 |
+ r.Mod(r, p) |
|
| 537 |
+ |
|
| 538 |
+ s.Exp(g, d7, p) |
|
| 539 |
+ t := new(big.Int).Exp(c.smp.g3b, cr, p) |
|
| 540 |
+ s.Mul(s, t) |
|
| 541 |
+ s.Mod(s, p) |
|
| 542 |
+ t.SetBytes(hashMPIs(h, 8, s, r)) |
|
| 543 |
+ if t.Cmp(cr) != 0 {
|
|
| 544 |
+ return errors.New("otr: ZKP cR failed in SMP4 message")
|
|
| 545 |
+ } |
|
| 546 |
+ |
|
| 547 |
+ r.Exp(rb, c.smp.a3, p) |
|
| 548 |
+ if r.Cmp(c.smp.papb) != 0 {
|
|
| 549 |
+ return smpFailureError |
|
| 550 |
+ } |
|
| 551 |
+ |
|
| 552 |
+ return nil |
|
| 553 |
+} |
|
| 554 |
+ |
|
| 555 |
+func (c *Conversation) generateSMPAbort() tlv {
|
|
| 556 |
+ return tlv{typ: tlvTypeSMPAbort}
|
|
| 557 |
+} |
|
| 558 |
+ |
|
| 559 |
+func hashMPIs(h hash.Hash, magic byte, mpis ...*big.Int) []byte {
|
|
| 560 |
+ if h != nil {
|
|
| 561 |
+ h.Reset() |
|
| 562 |
+ } else {
|
|
| 563 |
+ h = sha256.New() |
|
| 564 |
+ } |
|
| 565 |
+ |
|
| 566 |
+ h.Write([]byte{magic})
|
|
| 567 |
+ for _, mpi := range mpis {
|
|
| 568 |
+ h.Write(appendMPI(nil, mpi)) |
|
| 569 |
+ } |
|
| 570 |
+ return h.Sum(nil) |
|
| 571 |
+} |
| 0 | 572 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,7 @@ |
| 0 |
+// Copyright 2012 The Go Authors. All rights reserved. |
|
| 1 |
+// Use of this source code is governed by a BSD-style |
|
| 2 |
+// license that can be found in the LICENSE file. |
|
| 3 |
+ |
|
| 4 |
+// Package test contains integration tests for the |
|
| 5 |
+// golang.org/x/crypto/ssh package. |
|
| 6 |
+package test // import "golang.org/x/crypto/ssh/test" |
| 0 | 7 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,173 @@ |
| 0 |
+// Copyright 2017 The Go Authors. All rights reserved. |
|
| 1 |
+// Use of this source code is governed by a BSD-style |
|
| 2 |
+// license that can be found in the LICENSE file. |
|
| 3 |
+ |
|
| 4 |
+// sshd_test_pw.c |
|
| 5 |
+// Wrapper to inject test password data for sshd PAM authentication |
|
| 6 |
+// |
|
| 7 |
+// This wrapper implements custom versions of getpwnam, getpwnam_r, |
|
| 8 |
+// getspnam and getspnam_r. These functions first call their real |
|
| 9 |
+// libc versions, then check if the requested user matches test user |
|
| 10 |
+// specified in env variable TEST_USER and if so replace the password |
|
| 11 |
+// with crypted() value of TEST_PASSWD env variable. |
|
| 12 |
+// |
|
| 13 |
+// Compile: |
|
| 14 |
+// gcc -Wall -shared -o sshd_test_pw.so -fPIC sshd_test_pw.c |
|
| 15 |
+// |
|
| 16 |
+// Compile with debug: |
|
| 17 |
+// gcc -DVERBOSE -Wall -shared -o sshd_test_pw.so -fPIC sshd_test_pw.c |
|
| 18 |
+// |
|
| 19 |
+// Run sshd: |
|
| 20 |
+// LD_PRELOAD="sshd_test_pw.so" TEST_USER="..." TEST_PASSWD="..." sshd ... |
|
| 21 |
+ |
|
| 22 |
+// +build ignore |
|
| 23 |
+ |
|
| 24 |
+#define _GNU_SOURCE |
|
| 25 |
+#include <string.h> |
|
| 26 |
+#include <pwd.h> |
|
| 27 |
+#include <shadow.h> |
|
| 28 |
+#include <dlfcn.h> |
|
| 29 |
+#include <stdlib.h> |
|
| 30 |
+#include <unistd.h> |
|
| 31 |
+#include <stdio.h> |
|
| 32 |
+ |
|
| 33 |
+#ifdef VERBOSE |
|
| 34 |
+#define DEBUG(X...) fprintf(stderr, X) |
|
| 35 |
+#else |
|
| 36 |
+#define DEBUG(X...) while (0) { }
|
|
| 37 |
+#endif |
|
| 38 |
+ |
|
| 39 |
+/* crypt() password */ |
|
| 40 |
+static char * |
|
| 41 |
+pwhash(char *passwd) {
|
|
| 42 |
+ return strdup(crypt(passwd, "$6$")); |
|
| 43 |
+} |
|
| 44 |
+ |
|
| 45 |
+/* Pointers to real functions in libc */ |
|
| 46 |
+static struct passwd * (*real_getpwnam)(const char *) = NULL; |
|
| 47 |
+static int (*real_getpwnam_r)(const char *, struct passwd *, char *, size_t, struct passwd **) = NULL; |
|
| 48 |
+static struct spwd * (*real_getspnam)(const char *) = NULL; |
|
| 49 |
+static int (*real_getspnam_r)(const char *, struct spwd *, char *, size_t, struct spwd **) = NULL; |
|
| 50 |
+ |
|
| 51 |
+/* Cached test user and test password */ |
|
| 52 |
+static char *test_user = NULL; |
|
| 53 |
+static char *test_passwd_hash = NULL; |
|
| 54 |
+ |
|
| 55 |
+static void |
|
| 56 |
+init(void) {
|
|
| 57 |
+ /* Fetch real libc function pointers */ |
|
| 58 |
+ real_getpwnam = dlsym(RTLD_NEXT, "getpwnam"); |
|
| 59 |
+ real_getpwnam_r = dlsym(RTLD_NEXT, "getpwnam_r"); |
|
| 60 |
+ real_getspnam = dlsym(RTLD_NEXT, "getspnam"); |
|
| 61 |
+ real_getspnam_r = dlsym(RTLD_NEXT, "getspnam_r"); |
|
| 62 |
+ |
|
| 63 |
+ /* abort if env variables are not defined */ |
|
| 64 |
+ if (getenv("TEST_USER") == NULL || getenv("TEST_PASSWD") == NULL) {
|
|
| 65 |
+ fprintf(stderr, "env variables TEST_USER and TEST_PASSWD are missing\n"); |
|
| 66 |
+ abort(); |
|
| 67 |
+ } |
|
| 68 |
+ |
|
| 69 |
+ /* Fetch test user and test password from env */ |
|
| 70 |
+ test_user = strdup(getenv("TEST_USER"));
|
|
| 71 |
+ test_passwd_hash = pwhash(getenv("TEST_PASSWD"));
|
|
| 72 |
+ |
|
| 73 |
+ DEBUG("sshd_test_pw init():\n");
|
|
| 74 |
+ DEBUG("\treal_getpwnam: %p\n", real_getpwnam);
|
|
| 75 |
+ DEBUG("\treal_getpwnam_r: %p\n", real_getpwnam_r);
|
|
| 76 |
+ DEBUG("\treal_getspnam: %p\n", real_getspnam);
|
|
| 77 |
+ DEBUG("\treal_getspnam_r: %p\n", real_getspnam_r);
|
|
| 78 |
+ DEBUG("\tTEST_USER: '%s'\n", test_user);
|
|
| 79 |
+ DEBUG("\tTEST_PASSWD: '%s'\n", getenv("TEST_PASSWD"));
|
|
| 80 |
+ DEBUG("\tTEST_PASSWD_HASH: '%s'\n", test_passwd_hash);
|
|
| 81 |
+} |
|
| 82 |
+ |
|
| 83 |
+static int |
|
| 84 |
+is_test_user(const char *name) {
|
|
| 85 |
+ if (test_user != NULL && strcmp(test_user, name) == 0) |
|
| 86 |
+ return 1; |
|
| 87 |
+ return 0; |
|
| 88 |
+} |
|
| 89 |
+ |
|
| 90 |
+/* getpwnam */ |
|
| 91 |
+ |
|
| 92 |
+struct passwd * |
|
| 93 |
+getpwnam(const char *name) {
|
|
| 94 |
+ struct passwd *pw; |
|
| 95 |
+ |
|
| 96 |
+ DEBUG("sshd_test_pw getpwnam(%s)\n", name);
|
|
| 97 |
+ |
|
| 98 |
+ if (real_getpwnam == NULL) |
|
| 99 |
+ init(); |
|
| 100 |
+ if ((pw = real_getpwnam(name)) == NULL) |
|
| 101 |
+ return NULL; |
|
| 102 |
+ |
|
| 103 |
+ if (is_test_user(name)) |
|
| 104 |
+ pw->pw_passwd = strdup(test_passwd_hash); |
|
| 105 |
+ |
|
| 106 |
+ return pw; |
|
| 107 |
+} |
|
| 108 |
+ |
|
| 109 |
+/* getpwnam_r */ |
|
| 110 |
+ |
|
| 111 |
+int |
|
| 112 |
+getpwnam_r(const char *name, |
|
| 113 |
+ struct passwd *pwd, |
|
| 114 |
+ char *buf, |
|
| 115 |
+ size_t buflen, |
|
| 116 |
+ struct passwd **result) {
|
|
| 117 |
+ int r; |
|
| 118 |
+ |
|
| 119 |
+ DEBUG("sshd_test_pw getpwnam_r(%s)\n", name);
|
|
| 120 |
+ |
|
| 121 |
+ if (real_getpwnam_r == NULL) |
|
| 122 |
+ init(); |
|
| 123 |
+ if ((r = real_getpwnam_r(name, pwd, buf, buflen, result)) != 0 || *result == NULL) |
|
| 124 |
+ return r; |
|
| 125 |
+ |
|
| 126 |
+ if (is_test_user(name)) |
|
| 127 |
+ pwd->pw_passwd = strdup(test_passwd_hash); |
|
| 128 |
+ |
|
| 129 |
+ return 0; |
|
| 130 |
+} |
|
| 131 |
+ |
|
| 132 |
+/* getspnam */ |
|
| 133 |
+ |
|
| 134 |
+struct spwd * |
|
| 135 |
+getspnam(const char *name) {
|
|
| 136 |
+ struct spwd *sp; |
|
| 137 |
+ |
|
| 138 |
+ DEBUG("sshd_test_pw getspnam(%s)\n", name);
|
|
| 139 |
+ |
|
| 140 |
+ if (real_getspnam == NULL) |
|
| 141 |
+ init(); |
|
| 142 |
+ if ((sp = real_getspnam(name)) == NULL) |
|
| 143 |
+ return NULL; |
|
| 144 |
+ |
|
| 145 |
+ if (is_test_user(name)) |
|
| 146 |
+ sp->sp_pwdp = strdup(test_passwd_hash); |
|
| 147 |
+ |
|
| 148 |
+ return sp; |
|
| 149 |
+} |
|
| 150 |
+ |
|
| 151 |
+/* getspnam_r */ |
|
| 152 |
+ |
|
| 153 |
+int |
|
| 154 |
+getspnam_r(const char *name, |
|
| 155 |
+ struct spwd *spbuf, |
|
| 156 |
+ char *buf, |
|
| 157 |
+ size_t buflen, |
|
| 158 |
+ struct spwd **spbufp) {
|
|
| 159 |
+ int r; |
|
| 160 |
+ |
|
| 161 |
+ DEBUG("sshd_test_pw getspnam_r(%s)\n", name);
|
|
| 162 |
+ |
|
| 163 |
+ if (real_getspnam_r == NULL) |
|
| 164 |
+ init(); |
|
| 165 |
+ if ((r = real_getspnam_r(name, spbuf, buf, buflen, spbufp)) != 0) |
|
| 166 |
+ return r; |
|
| 167 |
+ |
|
| 168 |
+ if (is_test_user(name)) |
|
| 169 |
+ spbuf->sp_pwdp = strdup(test_passwd_hash); |
|
| 170 |
+ |
|
| 171 |
+ return r; |
|
| 172 |
+} |
| 0 | 173 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,38 @@ |
| 0 |
+// Copyright 2018 The Go Authors. All rights reserved. |
|
| 1 |
+// Use of this source code is governed by a BSD-style |
|
| 2 |
+// license that can be found in the LICENSE file. |
|
| 3 |
+ |
|
| 4 |
+// Package cpu implements processor feature detection for |
|
| 5 |
+// various CPU architectures. |
|
| 6 |
+package cpu |
|
| 7 |
+ |
|
| 8 |
+// CacheLinePad is used to pad structs to avoid false sharing. |
|
| 9 |
+type CacheLinePad struct{ _ [cacheLineSize]byte }
|
|
| 10 |
+ |
|
| 11 |
+// X86 contains the supported CPU features of the |
|
| 12 |
+// current X86/AMD64 platform. If the current platform |
|
| 13 |
+// is not X86/AMD64 then all feature flags are false. |
|
| 14 |
+// |
|
| 15 |
+// X86 is padded to avoid false sharing. Further the HasAVX |
|
| 16 |
+// and HasAVX2 are only set if the OS supports XMM and YMM |
|
| 17 |
+// registers in addition to the CPUID feature bit being set. |
|
| 18 |
+var X86 struct {
|
|
| 19 |
+ _ CacheLinePad |
|
| 20 |
+ HasAES bool // AES hardware implementation (AES NI) |
|
| 21 |
+ HasADX bool // Multi-precision add-carry instruction extensions |
|
| 22 |
+ HasAVX bool // Advanced vector extension |
|
| 23 |
+ HasAVX2 bool // Advanced vector extension 2 |
|
| 24 |
+ HasBMI1 bool // Bit manipulation instruction set 1 |
|
| 25 |
+ HasBMI2 bool // Bit manipulation instruction set 2 |
|
| 26 |
+ HasERMS bool // Enhanced REP for MOVSB and STOSB |
|
| 27 |
+ HasFMA bool // Fused-multiply-add instructions |
|
| 28 |
+ HasOSXSAVE bool // OS supports XSAVE/XRESTOR for saving/restoring XMM registers. |
|
| 29 |
+ HasPCLMULQDQ bool // PCLMULQDQ instruction - most often used for AES-GCM |
|
| 30 |
+ HasPOPCNT bool // Hamming weight instruction POPCNT. |
|
| 31 |
+ HasSSE2 bool // Streaming SIMD extension 2 (always available on amd64) |
|
| 32 |
+ HasSSE3 bool // Streaming SIMD extension 3 |
|
| 33 |
+ HasSSSE3 bool // Supplemental streaming SIMD extension 3 |
|
| 34 |
+ HasSSE41 bool // Streaming SIMD extension 4 and 4.1 |
|
| 35 |
+ HasSSE42 bool // Streaming SIMD extension 4 and 4.2 |
|
| 36 |
+ _ CacheLinePad |
|
| 37 |
+} |
| 0 | 7 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,16 @@ |
| 0 |
+// Copyright 2018 The Go Authors. All rights reserved. |
|
| 1 |
+// Use of this source code is governed by a BSD-style |
|
| 2 |
+// license that can be found in the LICENSE file. |
|
| 3 |
+ |
|
| 4 |
+// +build 386 amd64 amd64p32 |
|
| 5 |
+// +build !gccgo |
|
| 6 |
+ |
|
| 7 |
+package cpu |
|
| 8 |
+ |
|
| 9 |
+// cpuid is implemented in cpu_x86.s for gc compiler |
|
| 10 |
+// and in cpu_gccgo.c for gccgo. |
|
| 11 |
+func cpuid(eaxArg, ecxArg uint32) (eax, ebx, ecx, edx uint32) |
|
| 12 |
+ |
|
| 13 |
+// xgetbv with ecx = 0 is implemented in cpu_x86.s for gc compiler |
|
| 14 |
+// and in cpu_gccgo.c for gccgo. |
|
| 15 |
+func xgetbv() (eax, edx uint32) |
| 0 | 16 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,43 @@ |
| 0 |
+// Copyright 2018 The Go Authors. All rights reserved. |
|
| 1 |
+// Use of this source code is governed by a BSD-style |
|
| 2 |
+// license that can be found in the LICENSE file. |
|
| 3 |
+ |
|
| 4 |
+// +build 386 amd64 amd64p32 |
|
| 5 |
+// +build gccgo |
|
| 6 |
+ |
|
| 7 |
+#include <cpuid.h> |
|
| 8 |
+#include <stdint.h> |
|
| 9 |
+ |
|
| 10 |
+// Need to wrap __get_cpuid_count because it's declared as static. |
|
| 11 |
+int |
|
| 12 |
+gccgoGetCpuidCount(uint32_t leaf, uint32_t subleaf, |
|
| 13 |
+ uint32_t *eax, uint32_t *ebx, |
|
| 14 |
+ uint32_t *ecx, uint32_t *edx) |
|
| 15 |
+{
|
|
| 16 |
+ return __get_cpuid_count(leaf, subleaf, eax, ebx, ecx, edx); |
|
| 17 |
+} |
|
| 18 |
+ |
|
| 19 |
+// xgetbv reads the contents of an XCR (Extended Control Register) |
|
| 20 |
+// specified in the ECX register into registers EDX:EAX. |
|
| 21 |
+// Currently, the only supported value for XCR is 0. |
|
| 22 |
+// |
|
| 23 |
+// TODO: Replace with a better alternative: |
|
| 24 |
+// |
|
| 25 |
+// #include <xsaveintrin.h> |
|
| 26 |
+// |
|
| 27 |
+// #pragma GCC target("xsave")
|
|
| 28 |
+// |
|
| 29 |
+// void gccgoXgetbv(uint32_t *eax, uint32_t *edx) {
|
|
| 30 |
+// unsigned long long x = _xgetbv(0); |
|
| 31 |
+// *eax = x & 0xffffffff; |
|
| 32 |
+// *edx = (x >> 32) & 0xffffffff; |
|
| 33 |
+// } |
|
| 34 |
+// |
|
| 35 |
+// Note that _xgetbv is defined starting with GCC 8. |
|
| 36 |
+void |
|
| 37 |
+gccgoXgetbv(uint32_t *eax, uint32_t *edx) |
|
| 38 |
+{
|
|
| 39 |
+ __asm(" xorl %%ecx, %%ecx\n"
|
|
| 40 |
+ " xgetbv" |
|
| 41 |
+ : "=a"(*eax), "=d"(*edx)); |
|
| 42 |
+} |
| 0 | 43 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,26 @@ |
| 0 |
+// Copyright 2018 The Go Authors. All rights reserved. |
|
| 1 |
+// Use of this source code is governed by a BSD-style |
|
| 2 |
+// license that can be found in the LICENSE file. |
|
| 3 |
+ |
|
| 4 |
+// +build 386 amd64 amd64p32 |
|
| 5 |
+// +build gccgo |
|
| 6 |
+ |
|
| 7 |
+package cpu |
|
| 8 |
+ |
|
| 9 |
+//extern gccgoGetCpuidCount |
|
| 10 |
+func gccgoGetCpuidCount(eaxArg, ecxArg uint32, eax, ebx, ecx, edx *uint32) |
|
| 11 |
+ |
|
| 12 |
+func cpuid(eaxArg, ecxArg uint32) (eax, ebx, ecx, edx uint32) {
|
|
| 13 |
+ var a, b, c, d uint32 |
|
| 14 |
+ gccgoGetCpuidCount(eaxArg, ecxArg, &a, &b, &c, &d) |
|
| 15 |
+ return a, b, c, d |
|
| 16 |
+} |
|
| 17 |
+ |
|
| 18 |
+//extern gccgoXgetbv |
|
| 19 |
+func gccgoXgetbv(eax, edx *uint32) |
|
| 20 |
+ |
|
| 21 |
+func xgetbv() (eax, edx uint32) {
|
|
| 22 |
+ var a, d uint32 |
|
| 23 |
+ gccgoXgetbv(&a, &d) |
|
| 24 |
+ return a, d |
|
| 25 |
+} |
| 0 | 26 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,9 @@ |
| 0 |
+// Copyright 2018 The Go Authors. All rights reserved. |
|
| 1 |
+// Use of this source code is governed by a BSD-style |
|
| 2 |
+// license that can be found in the LICENSE file. |
|
| 3 |
+ |
|
| 4 |
+// +build mips64 mips64le |
|
| 5 |
+ |
|
| 6 |
+package cpu |
|
| 7 |
+ |
|
| 8 |
+const cacheLineSize = 32 |
| 0 | 7 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,55 @@ |
| 0 |
+// Copyright 2018 The Go Authors. All rights reserved. |
|
| 1 |
+// Use of this source code is governed by a BSD-style |
|
| 2 |
+// license that can be found in the LICENSE file. |
|
| 3 |
+ |
|
| 4 |
+// +build 386 amd64 amd64p32 |
|
| 5 |
+ |
|
| 6 |
+package cpu |
|
| 7 |
+ |
|
| 8 |
+const cacheLineSize = 64 |
|
| 9 |
+ |
|
| 10 |
+func init() {
|
|
| 11 |
+ maxID, _, _, _ := cpuid(0, 0) |
|
| 12 |
+ |
|
| 13 |
+ if maxID < 1 {
|
|
| 14 |
+ return |
|
| 15 |
+ } |
|
| 16 |
+ |
|
| 17 |
+ _, _, ecx1, edx1 := cpuid(1, 0) |
|
| 18 |
+ X86.HasSSE2 = isSet(26, edx1) |
|
| 19 |
+ |
|
| 20 |
+ X86.HasSSE3 = isSet(0, ecx1) |
|
| 21 |
+ X86.HasPCLMULQDQ = isSet(1, ecx1) |
|
| 22 |
+ X86.HasSSSE3 = isSet(9, ecx1) |
|
| 23 |
+ X86.HasFMA = isSet(12, ecx1) |
|
| 24 |
+ X86.HasSSE41 = isSet(19, ecx1) |
|
| 25 |
+ X86.HasSSE42 = isSet(20, ecx1) |
|
| 26 |
+ X86.HasPOPCNT = isSet(23, ecx1) |
|
| 27 |
+ X86.HasAES = isSet(25, ecx1) |
|
| 28 |
+ X86.HasOSXSAVE = isSet(27, ecx1) |
|
| 29 |
+ |
|
| 30 |
+ osSupportsAVX := false |
|
| 31 |
+ // For XGETBV, OSXSAVE bit is required and sufficient. |
|
| 32 |
+ if X86.HasOSXSAVE {
|
|
| 33 |
+ eax, _ := xgetbv() |
|
| 34 |
+ // Check if XMM and YMM registers have OS support. |
|
| 35 |
+ osSupportsAVX = isSet(1, eax) && isSet(2, eax) |
|
| 36 |
+ } |
|
| 37 |
+ |
|
| 38 |
+ X86.HasAVX = isSet(28, ecx1) && osSupportsAVX |
|
| 39 |
+ |
|
| 40 |
+ if maxID < 7 {
|
|
| 41 |
+ return |
|
| 42 |
+ } |
|
| 43 |
+ |
|
| 44 |
+ _, ebx7, _, _ := cpuid(7, 0) |
|
| 45 |
+ X86.HasBMI1 = isSet(3, ebx7) |
|
| 46 |
+ X86.HasAVX2 = isSet(5, ebx7) && osSupportsAVX |
|
| 47 |
+ X86.HasBMI2 = isSet(8, ebx7) |
|
| 48 |
+ X86.HasERMS = isSet(9, ebx7) |
|
| 49 |
+ X86.HasADX = isSet(19, ebx7) |
|
| 50 |
+} |
|
| 51 |
+ |
|
| 52 |
+func isSet(bitpos uint, value uint32) bool {
|
|
| 53 |
+ return value&(1<<bitpos) != 0 |
|
| 54 |
+} |
| 0 | 55 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,27 @@ |
| 0 |
+// Copyright 2018 The Go Authors. All rights reserved. |
|
| 1 |
+// Use of this source code is governed by a BSD-style |
|
| 2 |
+// license that can be found in the LICENSE file. |
|
| 3 |
+ |
|
| 4 |
+// +build 386 amd64 amd64p32 |
|
| 5 |
+// +build !gccgo |
|
| 6 |
+ |
|
| 7 |
+#include "textflag.h" |
|
| 8 |
+ |
|
| 9 |
+// func cpuid(eaxArg, ecxArg uint32) (eax, ebx, ecx, edx uint32) |
|
| 10 |
+TEXT ·cpuid(SB), NOSPLIT, $0-24 |
|
| 11 |
+ MOVL eaxArg+0(FP), AX |
|
| 12 |
+ MOVL ecxArg+4(FP), CX |
|
| 13 |
+ CPUID |
|
| 14 |
+ MOVL AX, eax+8(FP) |
|
| 15 |
+ MOVL BX, ebx+12(FP) |
|
| 16 |
+ MOVL CX, ecx+16(FP) |
|
| 17 |
+ MOVL DX, edx+20(FP) |
|
| 18 |
+ RET |
|
| 19 |
+ |
|
| 20 |
+// func xgetbv() (eax, edx uint32) |
|
| 21 |
+TEXT ·xgetbv(SB),NOSPLIT,$0-8 |
|
| 22 |
+ MOVL $0, CX |
|
| 23 |
+ XGETBV |
|
| 24 |
+ MOVL AX, eax+0(FP) |
|
| 25 |
+ MOVL DX, edx+4(FP) |
|
| 26 |
+ RET |