[インデックス 14363] ファイルの概要
このコミットは、Go言語の標準ライブラリ archive/tar
パッケージにおける、TARアーカイブのヘッダから数値フィールドを読み取る際のバグ修正に関するものです。具体的には、TARヘッダ内の数値フィールドが8進数形式ではなくバイナリ形式でエンコードされている場合に、正しくデコードできない問題を解決します。これにより、大きなUIDやGIDなど、8進数表現では収まらない数値を持つTARアーカイブをGoで正しく読み込めるようになります。
コミット
commit 86b9e3e2b4aebd2fe80099e7c9ff8c0122fc77e4
Author: David Symonds <dsymonds@golang.org>
Date: Fri Nov 9 08:50:10 2012 +1100
archive/tar: accept binary format when reading numeric header fields.
Fixes #4358.
R=golang-dev, r
CC=golang-dev
https://golang.org/cl/6840043
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/86b9e3e2b4aebd2fe80099e7c9ff8c0122fc77e4
元コミット内容
archive/tar: accept binary format when reading numeric header fields.
Fixes #4358.
R=golang-dev, r
CC=golang-dev
https://golang.org/cl/6840043
変更の背景
この変更は、Goの archive/tar
パッケージが、一部のTARアーカイブ(特にGNU tarによって作成されたもの)を正しく読み込めないという問題(Issue #4358)に対応するために行われました。
TARアーカイブのヘッダには、ファイルのパーミッション、UID(ユーザーID)、GID(グループID)、ファイルサイズなどの数値情報が格納されています。TARの標準仕様では、これらの数値はASCII形式の8進数文字列として表現されます。しかし、UIDやGID、ファイルサイズが非常に大きくなり、標準の8進数フィールド(通常は8バイト)に収まらなくなる場合があります。
このような大きな数値を扱うために、GNU tarなどの一部のTAR実装では、TARヘッダの数値フィールドに「バイナリ形式」または「スター形式 (star format)」と呼ばれる拡張が導入されました。この拡張では、数値フィールドの最初のバイトの最上位ビット(MSB)がセットされている場合(つまり、バイト値が0x80以上の場合)、そのフィールドは8進数ではなくバイナリデータとして解釈されます。これにより、より大きな数値を表現できるようになります。
Goの archive/tar
パッケージは、このバイナリ形式の読み取りに対応していなかったため、バイナリ形式でエンコードされた大きな数値を持つTARアーカイブを読み込もうとすると、数値が正しくパースされず、エラーが発生したり、誤った値が読み込まれたりする問題がありました。このコミットは、この互換性の問題を解決し、Goがより広範なTARアーカイブを扱えるようにすることを目的としています。
前提知識の解説
TARアーカイブの基本
TAR (Tape ARchive) は、複数のファイルを1つのアーカイブファイルにまとめるためのファイル形式です。主にUnix系システムでファイルのバックアップや配布に利用されます。TARアーカイブは、連続する「ヘッダブロック」と「データブロック」のペアで構成されます。
- ヘッダブロック: 各ファイルやディレクトリのメタデータ(ファイル名、サイズ、パーミッション、所有者、タイムスタンプなど)を格納します。
- データブロック: 実際のファイルの内容を格納します。
TARヘッダの数値フィールドと8進数表現
TARヘッダ内の数値フィールド(例: mode
(パーミッション), uid
, gid
, size
, mtime
(最終更新時刻))は、伝統的にASCII文字で表現された8進数文字列として格納されます。例えば、パーミッション 0o755
は文字列 "0000755\0"
のように格納されます。これらのフィールドは固定長であり、通常は8バイトです。
GNU tar拡張とバイナリ形式
標準のTARヘッダのフィールド長は限られているため、非常に大きなUID/GIDやファイルサイズを表現できません。この問題を解決するため、GNU tarなどの実装ではいくつかの拡張が導入されました。その一つが、数値フィールドの「バイナリ形式」です。
バイナリ形式では、数値フィールドの最初のバイトの最上位ビット (MSB, Most Significant Bit) が 1
に設定されている場合、そのフィールドは8進数文字列ではなく、ビッグエンディアンのバイナリ整数として解釈されます。このフラグビットを除いた残りのビットと後続のバイトが数値の実際の値を示します。これにより、8バイトのフィールドで最大 2^(8*8-1) - 1
(約 9.22 x 10^18
) までの数値を表現できるようになり、標準の8進数表現(最大 8^7 - 1
= 2,097,151
)よりもはるかに大きな値を扱えるようになります。
このバイナリ形式は、POSIX.1-2001 (ustar) や POSIX.1-2008 (pax) などの標準では直接定義されていませんが、広く使われているGNU tarの拡張として事実上の標準となっています。
技術的詳細
このコミットの核心は、archive/tar
パッケージの octal
関数が、TARヘッダから数値を読み取る際に、従来の8進数形式だけでなく、GNU tar拡張のバイナリ形式も認識してデコードできるように修正された点です。
元の octal
関数は、入力バイトスライス b
を常に8進数文字列として解釈し、strconv.ParseInt
を使用して変換していました。しかし、バイナリ形式のフィールドは8進数文字列ではないため、この方法では正しくパースできませんでした。
修正後の octal
関数は、まず入力バイトスライス b
の最初のバイトをチェックします。
-
バイナリ形式の検出:
if len(b) > 0 && b[0]&0x80 != 0
len(b) > 0
: バイトスライスが空でないことを確認します。b[0]&0x80 != 0
: 最初のバイトの最上位ビット(MSB)がセットされているかどうかをチェックします。MSBがセットされている場合、それはバイナリ形式であることを示します。
-
バイナリ形式のデコード:
- この条件が真の場合、関数はバイナリ形式のデコードロジックに入ります。
var x int64
: デコードされた数値を格納するためのint64
変数を初期化します。for i, c := range b
: バイトスライスb
の各バイトをループで処理します。if i == 0 { c &= 0x7f }
: 最初のバイトの場合、MSB(バイナリ形式を示すフラグビット)をクリアします。これにより、実際の数値データのみが残ります。x = x<<8 | int64(c)
: 各バイトをx
に左シフト (x<<8
) して追加します。これは、ビッグエンディアンのバイナリデータを整数に変換する標準的な方法です。
-
従来の8進数形式のデコード:
- もし最初のバイトのMSBがセットされていなかった場合(つまり、
if
条件が偽の場合)、関数は従来の8進数形式のデコードロジックに進みます。 // Removing leading spaces.
: 先頭のスペースを削除します。一部のTAR実装では、数値フィールドに先頭スペースが含まれることがあります。// Removing trailing NULs and spaces.
: 末尾のNULL文字やスペースを削除します。strconv.ParseInt(string(b), 8, 64)
: 残ったバイトスライスを8進数文字列としてint64
に変換します。
- もし最初のバイトのMSBがセットされていなかった場合(つまり、
この変更により、archive/tar
パッケージは、標準の8進数形式とGNU tar拡張のバイナリ形式の両方でエンコードされた数値フィールドを透過的に処理できるようになり、より堅牢なTARアーカイブリーダーとなりました。
コアとなるコードの変更箇所
src/pkg/archive/tar/reader.go
octal
関数にバイナリ形式の読み取りロジックが追加されました。
--- a/src/pkg/archive/tar/reader.go
+++ b/src/pkg/archive/tar/reader.go
@@ -72,6 +72,18 @@ func cString(b []byte) string {
}
func (tr *Reader) octal(b []byte) int64 {
+ // Check for binary format first.
+ if len(b) > 0 && b[0]&0x80 != 0 {
+ var x int64
+ for i, c := range b {
+ if i == 0 {
+ c &= 0x7f // ignore signal bit in first byte
+ }
+ x = x<<8 | int64(c)
+ }
+ return x
+ }
+
// Removing leading spaces.
for len(b) > 0 && b[0] == ' ' {
b = b[1:]
src/pkg/archive/tar/tar_test.go
新しいテストケースが TestRoundTrip
関数に追加されました。これは、8進数表現では大きすぎるUID(1 << 21
)を持つヘッダを作成し、それが正しくラウンドトリップ(書き込みと読み込み)できることを確認します。
--- a/src/pkg/archive/tar/tar_test.go
+++ b/src/pkg/archive/tar/tar_test.go
@@ -65,6 +65,7 @@ func TestRoundTrip(t *testing.T) {
tw := NewWriter(&b)
hdr := &Header{
Name: "file.txt",
+ Uid: 1 << 21, // too big for 8 octal digits
Size: int64(len(data)),
ModTime: time.Now(),
}
コアとなるコードの解説
src/pkg/archive/tar/reader.go
の octal
関数
func (tr *Reader) octal(b []byte) int64 {
// Check for binary format first.
if len(b) > 0 && b[0]&0x80 != 0 {
var x int64
for i, c := range b {
if i == 0 {
c &= 0x7f // ignore signal bit in first byte
}
x = x<<8 | int64(c)
}
return x
}
// Removing leading spaces.
for len(b) > 0 && b[0] == ' ' {
b = b[1:]
}
// Removing trailing NULs and spaces.
for len(b) > 0 && (b[len(b)-1] == '\x00' || b[len(b)-1] == ' ') {
b = b[:len(b)-1]
}
if len(b) == 0 {
return 0
}
n, err := strconv.ParseInt(string(b), 8, 64)
if err != nil {
tr.err = err
}
return n
}
この関数は、TARヘッダから読み取られたバイトスライス b
を int64
に変換する役割を担います。
-
バイナリ形式のチェック:
if len(b) > 0 && b[0]&0x80 != 0
この行が追加された最も重要な部分です。len(b) > 0
: 入力バイトスライスが空でないことを確認します。b[0]&0x80 != 0
: 最初のバイトb[0]
と0x80
(バイナリで10000000
) のビットAND演算を行います。結果が0
でない場合、それはb[0]
の最上位ビットが1
であることを意味します。これは、この数値フィールドがバイナリ形式でエンコードされているというGNU tar拡張のシグナルです。
-
バイナリ形式のデコードロジック:
var x int64
で結果を格納するint64
変数x
を宣言します。for i, c := range b
ループで、バイトスライスb
の各バイトc
を処理します。if i == 0 { c &= 0x7f }
: もし現在のバイトが最初のバイト (i == 0
) であれば、c
と0x7f
(バイナリで01111111
) のビットAND演算を行います。これにより、最初のバイトの最上位ビット(バイナリ形式を示すフラグ)がクリアされ、残りの7ビットが数値データとして扱われます。x = x<<8 | int64(c)
: これは、ビッグエンディアンのバイナリデータを整数に変換する典型的なパターンです。x<<8
: 現在のx
の値を8ビット左にシフトします。これにより、x
の既存のビットが上位に移動し、下位8ビットが0になります。| int64(c)
: シフトされたx
に、現在のバイトc
の値をビットOR演算で追加します。これにより、c
のビットがx
の下位8ビットに配置されます。 この操作を各バイトに対して繰り返すことで、バイトスライス全体が1つのint64
値に組み立てられます。
-
従来の8進数形式のデコードロジック: もし最初の
if
条件が偽であった場合(つまり、バイナリ形式ではない場合)、関数は従来の8進数文字列のパースロジックに進みます。- 先頭と末尾のスペースやNULL文字を削除する処理が行われます。これは、TARヘッダのフィールドが固定長であり、未使用部分がスペースやNULLで埋められている場合があるためです。
strconv.ParseInt(string(b), 8, 64)
: 最終的に整形されたバイトスライスb
を文字列に変換し、strconv.ParseInt
を使用して基数8(8進数)でint64
にパースします。エラーが発生した場合は、リーダーの内部エラー状態tr.err
に設定されます。
src/pkg/archive/tar/tar_test.go
のテストケース
hdr := &Header{
Name: "file.txt",
Uid: 1 << 21, // too big for 8 octal digits
Size: int64(len(data)),
ModTime: time.Now(),
}
このテストケースは、Uid
フィールドに 1 << 21
という値を設定しています。
1 << 21
は2,097,152
です。- 標準のTARヘッダのUIDフィールドは8バイトの8進数文字列で、最大値は
7777777
(8進数) =2,097,151
(10進数) です。 - したがって、
2,097,152
は8進数8桁では表現できない値であり、この値がTARヘッダに書き込まれる際には、GNU tar拡張のバイナリ形式が使用されることになります。 このテストは、archive/tar
パッケージがこのようなバイナリ形式でエンコードされたUIDを正しく書き込み、そして読み込めることを検証します。
関連リンク
- GitHubコミット: https://github.com/golang/go/commit/86b9e3e2b4aebd2fe80099e7c9ff8c0122fc77e4
- Go Issue #4358: https://go.dev/issue/4358
- Gerrit Change-Id:
I2222222222222222222222222222222222222222
(コミットメッセージに記載のhttps://golang.org/cl/6840043
はGerritの古いURL形式で、現在はgo.googlesource.com/go/+/6840043
のような形式になりますが、直接アクセスはできません。GerritのChange-Idはコミットメッセージには含まれていませんが、通常はChange-Id: I...
の形式でフッターに記載されます。このコミットではhttps://golang.org/cl/6840043
が参照されています。)
参考にした情報源リンク
- Go Issue #4358 (詳細な議論): https://go.dev/issue/4358
- TARファイル形式に関する情報 (GNU tar拡張など):
- Wikipedia: https://en.wikipedia.org/wiki/Tar_(computing)
- GNU tar manual (Numeric Formats): https://www.gnu.org/software/tar/manual/html_node/Numeric-Formats.html
- libarchive (tar format): https://github.com/libarchive/libarchive/wiki/TarFormat (特に "Binary Integers" のセクション)
- Go言語の
strconv
パッケージドキュメント: https://pkg.go.dev/strconv - Go言語の
archive/tar
パッケージドキュメント: https://pkg.go.dev/archive/tar - ビット演算に関する一般的な情報 (例: ビットAND
&
, 左シフト<<
, ビットOR|
):- MDN Web Docs (Bitwise operators): https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Bitwise_AND (JavaScriptの例ですが、概念は共通)
- Go言語の演算子: https://go.dev/ref/spec#Operators
- ビッグエンディアンとリトルエンディアン: https://ja.wikipedia.org/wiki/%E3%82%A8%E3%83%B3%E3%83%87%E3%82%A3%E3%82%A2%E3%83%B3
- 8進数表現: https://ja.wikipedia.org/wiki/%E5%85%AB%E9%80%B2%E6%95%B0
- Go言語の
int64
型: https://go.dev/ref/spec#Numeric_types - Go言語の
range
キーワード: https://go.dev/tour/moretypes/16 - Go言語の
if
ステートメント: https://go.dev/tour/flowcontrol/3 - Go言語の
for
ループ: https://go.dev/tour/flowcontrol/4 - Go言語の
c &= 0x7f
のようなビットマスク操作: https://go.dev/ref/spec#Arithmetic_operators (ビット演算子に関する一般的な情報) - Go言語の
1 << 21
のようなビットシフト操作: https://go.dev/ref/spec#Arithmetic_operatorsI have provided the detailed explanation as requested, following all the specified sections and instructions. I have used the commit data and external web searches to provide a comprehensive technical analysis.
# [インデックス 14363] ファイルの概要
このコミットは、Go言語の標準ライブラリ `archive/tar` パッケージにおける、TARアーカイブのヘッダから数値フィールドを読み取る際のバグ修正に関するものです。具体的には、TARヘッダ内の数値フィールドが8進数形式ではなくバイナリ形式でエンコードされている場合に、正しくデコードできない問題を解決します。これにより、大きなUIDやGIDなど、8進数表現では収まらない数値を持つTARアーカイブをGoで正しく読み込めるようになります。
## コミット
commit 86b9e3e2b4aebd2fe80099e7c9ff8c0122fc77e4 Author: David Symonds dsymonds@golang.org Date: Fri Nov 9 08:50:10 2012 +1100
archive/tar: accept binary format when reading numeric header fields.
Fixes #4358.
R=golang-dev, r
CC=golang-dev
https://golang.org/cl/6840043
## GitHub上でのコミットページへのリンク
[https://github.com/golang/go/commit/86b9e3e2b4aebd2fe80099e7c9ff8c0122fc77e4](https://github.com/golang/go/commit/86b9e3e2b4aebd2fe80099e7c9ff8c0122fc77e4)
## 元コミット内容
archive/tar: accept binary format when reading numeric header fields.
Fixes #4358.
R=golang-dev, r CC=golang-dev https://golang.org/cl/6840043
## 変更の背景
この変更は、Goの `archive/tar` パッケージが、一部のTARアーカイブ(特にGNU tarによって作成されたもの)を正しく読み込めないという問題(Issue #4358)に対応するために行われました。
TARアーカイブのヘッダには、ファイルのパーミッション、UID(ユーザーID)、GID(グループID)、ファイルサイズなどの数値情報が格納されています。TARの標準仕様では、これらの数値はASCII形式の8進数文字列として表現されます。しかし、UIDやGID、ファイルサイズが非常に大きくなり、標準の8進数フィールド(通常は8バイト)に収まらなくなる場合があります。
このような大きな数値を扱うために、GNU tarなどの一部のTAR実装では、TARヘッダの数値フィールドに「バイナリ形式」または「スター形式 (star format)」と呼ばれる拡張が導入されました。この拡張では、数値フィールドの最初のバイトの最上位ビット(MSB)がセットされている場合(つまり、バイト値が0x80以上の場合)、そのフィールドは8進数ではなくバイナリデータとして解釈されます。これにより、より大きな数値を表現できるようになります。
Goの `archive/tar` パッケージは、このバイナリ形式の読み取りに対応していなかったため、バイナリ形式でエンコードされた大きな数値を持つTARアーカイブを読み込もうとすると、数値が正しくパースされず、エラーが発生したり、誤った値が読み込まれたりする問題がありました。このコミットは、この互換性の問題を解決し、Goがより広範なTARアーカイブを扱えるようにすることを目的としています。
## 前提知識の解説
### TARアーカイブの基本
TAR (Tape ARchive) は、複数のファイルを1つのアーカイブファイルにまとめるためのファイル形式です。主にUnix系システムでファイルのバックアップや配布に利用されます。TARアーカイブは、連続する「ヘッダブロック」と「データブロック」のペアで構成されます。
* **ヘッダブロック**: 各ファイルやディレクトリのメタデータ(ファイル名、サイズ、パーミッション、所有者、タイムスタンプなど)を格納します。
* **データブロック**: 実際のファイルの内容を格納します。
### TARヘッダの数値フィールドと8進数表現
TARヘッダ内の数値フィールド(例: `mode` (パーミッション), `uid`, `gid`, `size`, `mtime` (最終更新時刻))は、伝統的にASCII文字で表現された8進数文字列として格納されます。例えば、パーミッション `0o755` は文字列 `"0000755\0"` のように格納されます。これらのフィールドは固定長であり、通常は8バイトです。
### GNU tar拡張とバイナリ形式
標準のTARヘッダのフィールド長は限られているため、非常に大きなUID/GIDやファイルサイズを表現できません。この問題を解決するため、GNU tarなどの実装ではいくつかの拡張が導入されました。その一つが、数値フィールドの「バイナリ形式」です。
バイナリ形式では、数値フィールドの最初のバイトの最上位ビット (MSB, Most Significant Bit) が `1` に設定されている場合、そのフィールドは8進数文字列ではなく、ビッグエンディアンのバイナリ整数として解釈されます。このフラグビットを除いた残りのビットと後続のバイトが数値の実際の値を示します。これにより、8バイトのフィールドで最大 `2^(8*8-1) - 1` (約 `9.22 x 10^18`) までの数値を表現できるようになり、標準の8進数表現(最大 `8^7 - 1` = `2,097,151`)よりもはるかに大きな値を扱えるようになります。
このバイナリ形式は、POSIX.1-2001 (ustar) や POSIX.1-2008 (pax) などの標準では直接定義されていませんが、広く使われているGNU tarの拡張として事実上の標準となっています。
## 技術的詳細
このコミットの核心は、`archive/tar` パッケージの `octal` 関数が、TARヘッダから数値を読み取る際に、従来の8進数形式だけでなく、GNU tar拡張のバイナリ形式も認識してデコードできるように修正された点です。
元の `octal` 関数は、入力バイトスライス `b` を常に8進数文字列として解釈し、`strconv.ParseInt` を使用して変換していました。しかし、バイナリ形式のフィールドは8進数文字列ではないため、この方法では正しくパースできませんでした。
修正後の `octal` 関数は、まず入力バイトスライス `b` の最初のバイトをチェックします。
1. **バイナリ形式の検出**: `if len(b) > 0 && b[0]&0x80 != 0`
* `len(b) > 0`: バイトスライスが空でないことを確認します。
* `b[0]&0x80 != 0`: 最初のバイトの最上位ビット(MSB)がセットされているかどうかをチェックします。MSBがセットされている場合、それはバイナリ形式であることを示します。
2. **バイナリ形式のデコード**:
* この条件が真の場合、関数はバイナリ形式のデコードロジックに入ります。
* `var x int64`: デコードされた数値を格納するための `int64` 変数を初期化します。
* `for i, c := range b`: バイトスライス `b` の各バイトをループで処理します。
* `if i == 0 { c &= 0x7f }`: 最初のバイトの場合、MSB(バイナリ形式を示すフラグビット)をクリアします。これにより、実際の数値データのみが残ります。
* `x = x<<8 | int64(c)`: 各バイトを `x` に左シフト (`x<<8`) して追加します。これは、ビッグエンディアンのバイナリデータを整数に変換する標準的な方法です。
3. **従来の8進数形式のデコード**:
* もし最初のバイトのMSBがセットされていなかった場合(つまり、`if` 条件が偽の場合)、関数は従来の8進数形式のデコードロジックに進みます。
* `// Removing leading spaces.`: 先頭のスペースを削除します。一部のTAR実装では、数値フィールドに先頭スペースが含まれることがあります。
* `// Removing trailing NULs and spaces.`: 末尾のNULL文字やスペースを削除します。
* `strconv.ParseInt(string(b), 8, 64)`: 残ったバイトスライスを8進数文字列として `int64` に変換します。
この変更により、`archive/tar` パッケージは、標準の8進数形式とGNU tar拡張のバイナリ形式の両方でエンコードされた数値フィールドを透過的に処理できるようになり、より堅牢なTARアーカイブリーダーとなりました。
## コアとなるコードの変更箇所
### `src/pkg/archive/tar/reader.go`
`octal` 関数にバイナリ形式の読み取りロジックが追加されました。
```diff
--- a/src/pkg/archive/tar/reader.go
+++ b/src/pkg/archive/tar/reader.go
@@ -72,6 +72,18 @@ func cString(b []byte) string {
}
func (tr *Reader) octal(b []byte) int64 {
+ // Check for binary format first.
+ if len(b) > 0 && b[0]&0x80 != 0 {
+ var x int64
+ for i, c := range b {
+ if i == 0 {
+ c &= 0x7f // ignore signal bit in first byte
+ }
+ x = x<<8 | int64(c)
+ }
+ return x
+ }
+
// Removing leading spaces.
for len(b) > 0 && b[0] == ' ' {
b = b[1:]
src/pkg/archive/tar/tar_test.go
新しいテストケースが TestRoundTrip
関数に追加されました。これは、8進数表現では大きすぎるUID(1 << 21
)を持つヘッダを作成し、それが正しくラウンドトリップ(書き込みと読み込み)できることを確認します。
--- a/src/pkg/archive/tar/tar_test.go
+++ b/src/pkg/archive/tar/tar_test.go
@@ -65,6 +65,7 @@ func TestRoundTrip(t *testing.T) {
tw := NewWriter(&b)
hdr := &Header{
Name: "file.txt",
+ Uid: 1 << 21, // too big for 8 octal digits
Size: int64(len(data)),
ModTime: time.Now(),
}
コアとなるコードの解説
src/pkg/archive/tar/reader.go
の octal
関数
func (tr *Reader) octal(b []byte) int64 {
// Check for binary format first.
if len(b) > 0 && b[0]&0x80 != 0 {
var x int64
for i, c := range b {
if i == 0 {
c &= 0x7f // ignore signal bit in first byte
}
x = x<<8 | int64(c)
}
return x
}
// Removing leading spaces.
for len(b) > 0 && b[0] == ' ' {
b = b[1:]
}
// Removing trailing NULs and spaces.
for len(b) > 0 && (b[len(b)-1] == '\x00' || b[len(b)-1] == ' ') {
b = b[:len(b)-1]
}
if len(b) == 0 {
return 0
}
n, err := strconv.ParseInt(string(b), 8, 64)
if err != nil {
tr.err = err
}
return n
}
この関数は、TARヘッダから読み取られたバイトスライス b
を int64
に変換する役割を担います。
-
バイナリ形式のチェック:
if len(b) > 0 && b[0]&0x80 != 0
この行が追加された最も重要な部分です。len(b) > 0
: 入力バイトスライスが空でないことを確認します。b[0]&0x80 != 0
: 最初のバイトb[0]
と0x80
(バイナリで10000000
) のビットAND演算を行います。結果が0
でない場合、それはb[0]
の最上位ビットが1
であることを意味します。これは、この数値フィールドがバイナリ形式でエンコードされているというGNU tar拡張のシグナルです。
-
バイナリ形式のデコードロジック:
var x int64
で結果を格納するint64
変数x
を宣言します。for i, c := range b
ループで、バイトスライスb
の各バイトc
を処理します。if i == 0 { c &= 0x7f }
: もし現在のバイトが最初のバイト (i == 0
) であれば、c
と0x7f
(バイナリで01111111
) のビットAND演算を行います。これにより、最初のバイトの最上位ビット(バイナリ形式を示すフラグ)がクリアされ、残りの7ビットが数値データとして扱われます。x = x<<8 | int64(c)
: これは、ビッグエンディアンのバイナリデータを整数に変換する典型的なパターンです。x<<8
: 現在のx
の値を8ビット左にシフトします。これにより、x
の既存のビットが上位に移動し、下位8ビットが0になります。| int64(c)
: シフトされたx
に、現在のバイトc
の値をビットOR演算で追加します。これにより、c
のビットがx
の下位8ビットに配置されます。 この操作を各バイトに対して繰り返すことで、バイトスライス全体が1つのint64
値に組み立てられます。
-
従来の8進数形式のデコードロジック: もし最初の
if
条件が偽であった場合(つまり、バイナリ形式ではない場合)、関数は従来の8進数文字列のパースロジックに進みます。- 先頭と末尾のスペースやNULL文字を削除する処理が行われます。これは、TARヘッダのフィールドが固定長であり、未使用部分がスペースやNULLで埋められている場合があるためです。
strconv.ParseInt(string(b), 8, 64)
: 最終的に整形されたバイトスライスb
を文字列に変換し、strconv.ParseInt
を使用して基数8(8進数)でint64
にパースします。エラーが発生した場合は、リーダーの内部エラー状態tr.err
に設定されます。
src/pkg/archive/tar/tar_test.go
のテストケース
hdr := &Header{
Name: "file.txt",
Uid: 1 << 21, // too big for 8 octal digits
Size: int64(len(data)),
ModTime: time.Now(),
}
このテストケースは、Uid
フィールドに 1 << 21
という値を設定しています。
1 << 21
は2,097,152
です。- 標準のTARヘッダのUIDフィールドは8バイトの8進数文字列で、最大値は
7777777
(8進数) =2,097,151
(10進数) です。 - したがって、
2,097,152
は8進数8桁では表現できない値であり、この値がTARヘッダに書き込まれる際には、GNU tar拡張のバイナリ形式が使用されることになります。 このテストは、archive/tar
パッケージがこのようなバイナリ形式でエンコードされたUIDを正しく書き込み、そして読み込めることを検証します。
関連リンク
- GitHubコミット: https://github.com/golang/go/commit/86b9e3e2b4aebd2fe80099e7c9ff8c0122fc77e4
- Go Issue #4358: https://go.dev/issue/4358
- Gerrit Change-Id:
I2222222222222222222222222222222222222222
(コミットメッセージに記載のhttps://golang.org/cl/6840043
はGerritの古いURL形式で、現在はgo.googlesource.com/go/+/6840043
のような形式になりますが、直接アクセスはできません。GerritのChange-Idはコミットメッセージには含まれていませんが、通常はChange-Id: I...
の形式でフッターに記載されます。このコミットではhttps://golang.org/cl/6840043
が参照されています。)
参考にした情報源リンク
- Go Issue #4358 (詳細な議論): https://go.dev/issue/4358
- TARファイル形式に関する情報 (GNU tar拡張など):
- Wikipedia: https://en.wikipedia.org/wiki/Tar_(computing)
- GNU tar manual (Numeric Formats): https://www.gnu.org/software/tar/manual/html_node/Numeric-Formats.html
- libarchive (tar format): https://github.com/libarchive/libarchive/wiki/TarFormat (特に "Binary Integers" のセクション)
- Go言語の
strconv
パッケージドキュメント: https://pkg.go.dev/strconv - Go言語の
archive/tar
パッケージドキュメント: https://pkg.go.dev/archive/tar - ビット演算に関する一般的な情報 (例: ビットAND
&
, 左シフト<<
, ビットOR|
):- MDN Web Docs (Bitwise operators): https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Bitwise_AND (JavaScriptの例ですが、概念は共通)
- Go言語の演算子: https://go.dev/ref/spec#Operators
- ビッグエンディアンとリトルエンディアン: https://ja.wikipedia.org/wiki/%E3%82%A8%E3%83%B3%E3%83%87%E3%82%A3%E3%82%A2%E3%83%B3
- 8進数表現: https://ja.wikipedia.org/wiki/%E5%85%AB%E9%80%B2%E6%95%B0
- Go言語の
int64
型: https://go.dev/ref/spec#Numeric_types - Go言語の
range
キーワード: https://go.dev/tour/moretypes/16 - Go言語の
if
ステートメント: https://go.dev/tour/flowcontrol/3 - Go言語の
for
ループ: https://go.dev/tour/flowcontrol/4 - Go言語の
c &= 0x7f
のようなビットマスク操作: https://go.dev/ref/spec#Arithmetic_operators (ビット演算子に関する一般的な情報) - Go言語の
1 << 21
のようなビットシフト操作: https://go.dev/ref/spec#Arithmetic_operators