Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

[インデックス 10709] ファイルの概要

このコミットは、Go言語の標準ライブラリ archive/zip パッケージにおいて、ZIPアーカイブ内のファイルのパーミッション情報をGoの os.FileMode 型で適切に扱うように変更するものです。これにより、基盤となるOSのファイルモードへの暗黙的な依存関係が修正され、クロスプラットフォームでのZIPファイルの互換性と正確性が向上します。

コミット

  • コミットハッシュ: 2cb1aa468108598d5d3fab1c6ff9b6ba1217bf80
  • Author: Roger Peppe rogpeppe@gmail.com
  • Date: Mon Dec 12 15:22:55 2011 -0500

GitHub上でのコミットページへのリンク

https://github.com/golang/go/commit/2cb1aa468108598d5d3fab1c6ff9b6ba1217bf80

元コミット内容

    archive/zip: make zip understand os.FileMode.
    Fixes implicit dependency on underlying os file modes.
    
    R=rsc, r, n13m3y3r, gustavo, adg
    CC=golang-dev
    https://golang.org/cl/5440130

変更の背景

ZIPファイルフォーマットは、アーカイブ内の各ファイルに対して「外部ファイル属性 (External File Attributes)」というフィールドを持ち、これにはファイルパーミッションやその他のOS固有の属性が格納されます。以前の archive/zip パッケージの実装では、この外部属性の解釈が、ZIPファイルが作成されたOS(特にUnix系システム)のファイルモード表現に暗黙的に依存していました。

具体的には、ZIPヘッダーの ExternalAttrs フィールドからファイルモードを抽出する際に、単にビットシフトして uint32 型として扱っていました。これは、ZIPファイルがUnix系システムで作成された場合に、その ExternalAttrs の上位16ビットにUnixのファイルモードが格納されるという慣習に基づいています。しかし、Windowsなどの他のOSで作成されたZIPファイルの場合、ExternalAttrs の意味合いが異なり、MS-DOS属性が格納されることが一般的です。

この暗黙的な依存関係により、異なるOSで作成されたZIPファイルをGoの archive/zip パッケージで読み込んだ際に、ファイルパーミッションが正しく解釈されない、あるいは設定できないという問題が発生していました。例えば、Windowsで作成されたZIPファイル内のファイルの読み取り専用属性がGoで認識されない、またはUnixで作成されたZIPファイル内の実行可能属性がWindowsで正しく扱われない、といった状況が考えられます。

このコミットは、この問題を解決するために、Goの os.FileMode 型を導入し、ZIPファイルの ExternalAttrs を、作成元のOS(CreatorVersion フィールドで識別される)に応じて適切に os.FileMode に変換・設定するように変更します。これにより、archive/zip パッケージがより堅牢になり、クロスプラットフォームでのZIPファイルの取り扱いが改善されます。

前提知識の解説

1. os.FileMode (Go言語)

Go言語の os パッケージには、ファイルやディレクトリのパーミッションと種類を表す FileMode 型が定義されています。これは uint32 のエイリアスですが、ビットフラグとして特定の意味を持ちます。

  • パーミッションビット: 下位9ビット(0o777)は、Unix系のファイルパーミッション(所有者、グループ、その他の読み取り/書き込み/実行権限)を表します。
  • モードビット: 上位ビットは、ファイルの種類(ディレクトリ、シンボリックリンク、デバイスファイルなど)や特殊なパーミッション(Setuid, Setgid, スティッキービット)を表します。
    • os.ModeDir: ディレクトリ
    • os.ModeAppend: 追記専用ファイル
    • os.ModeExclusive: 排他ロックファイル
    • os.ModeTemporary: 一時ファイル
    • os.ModeSymlink: シンボリックリンク
    • os.ModeDevice: デバイスファイル
    • os.ModeNamedPipe: 名前付きパイプ
    • os.ModeSocket: ソケット
    • os.ModeSetuid: Setuidビット
    • os.ModeSetgid: Setgidビット
    • os.ModeCharDevice: 文字デバイス
    • os.ModeSticky: スティッキービット
    • os.ModeIrregular: 不規則なファイル

os.FileMode は、GoプログラムがOSに依存せずにファイルパーミッションを抽象的に扱うための重要なメカニズムです。

2. ZIPファイルフォーマットとファイル属性

ZIPファイルフォーマットは、各エントリ(ファイルまたはディレクトリ)のメタデータを「ローカルファイルヘッダー」と「中央ディレクトリファイルヘッダー」に格納します。このメタデータの一部として、ファイル属性に関する情報が含まれます。

  • CreatorVersion: このフィールドは、ZIPエントリを作成したアプリケーションやOSのバージョン情報を含みます。特に重要なのは、上位バイトが「作成元OS」を示すコードであることです。
    • 0 (FAT): MS-DOS, Windows (FAT/FAT32ファイルシステム)
    • 3 (Unix): Unix系OS
    • 11 (NTFS): Windows NT/XP/Vista/7 (NTFSファイルシステム)
    • 14 (VFAT): Windows (VFATファイルシステム)
    • 19 (MacOSX): macOS
  • ExternalAttrs: このフィールドは、ファイルが作成されたOSに固有のファイル属性を格納します。
    • Unix系OSの場合: ExternalAttrs の上位16ビット(ExternalAttrs >> 16)にUnixのファイルモード(st_mode)が格納されることが一般的です。これにはファイルの種類(通常ファイル、ディレクトリなど)とパーミッションビットが含まれます。
      • S_IFMT (0xf000): ファイルタイプマスク
      • S_IFDIR (0x4000): ディレクトリ
      • S_IFREG (0x8000): 通常ファイル
      • S_ISUID (0x800): Setuidビット
      • S_ISGID (0x400): Setgidビット
    • MS-DOS/Windowsの場合: ExternalAttrs の下位バイトにMS-DOSのファイル属性が格納されます。
      • msdosDir (0x10): ディレクトリ属性
      • msdosReadOnly (0x01): 読み取り専用属性

このコミットは、CreatorVersion を参照して ExternalAttrs の解釈を切り替えることで、異なるOSで作成されたZIPファイルのファイルモードを正確に os.FileMode に変換できるようにします。

技術的詳細

このコミットの主要な変更は、src/pkg/archive/zip/struct.go ファイルに集中しています。

  1. os パッケージのインポート: import ("os") が追加され、os.FileMode 型が利用可能になります。

  2. CreatorVersion 定数の追加: ZIPファイルの CreatorVersion フィールドで使われる、作成元OSを示す新しい定数が追加されました。

    • creatorFAT = 0
    • creatorUnix = 3 (既存)
    • creatorNTFS = 11
    • creatorVFAT = 14
    • creatorMacOSX = 19 これにより、ZIPエントリがどのOSで作成されたかをより正確に識別できるようになります。
  3. Unix定数の定義: Unixのファイルモードに関連する定数(s_IFMT, s_IFDIR, s_IFREG, s_ISUID, s_ISGID)が定義されました。これらは、ZIPの ExternalAttrs からUnixモードを解釈する際に使用されます。

  4. MS-DOS属性定数の定義: MS-DOSのファイル属性に関連する定数(msdosDir, msdosReadOnly)が定義されました。これらは、MS-DOS/Windowsで作成されたZIPファイルの ExternalAttrs を解釈する際に使用されます。

  5. FileHeader.Mode() メソッドの変更:

    • 戻り値の型が uint32 から os.FileMode に変更されました。
    • h.CreatorVersion >> 8 の値(作成元OS)に基づいて、ExternalAttrs の解釈を分岐する switch ステートメントが導入されました。
      • creatorUnix, creatorMacOSX の場合: unixModeToFileMode(h.ExternalAttrs >> 16) を呼び出してUnixモードを os.FileMode に変換します。
      • creatorNTFS, creatorVFAT, creatorFAT の場合: msdosModeToFileMode(h.ExternalAttrs) を呼び出してMS-DOS属性を os.FileMode に変換します。
    • ファイル名が / で終わる場合(ディレクトリを示す)は、os.ModeDir フラグが mode に追加されます。
    • エラーを返す代わりに、常に os.FileMode を返すようになりました。
  6. FileHeader.SetMode() メソッドの変更:

    • 引数の型が uint32 から os.FileMode に変更されました。
    • h.CreatorVersion の作成元OSを creatorUnix に設定します。これは、GoがZIPファイルを作成する際に、Unix形式のファイルモードを ExternalAttrs に書き込むことを意図しています。
    • h.ExternalAttrs には、fileModeToUnixMode(mode) << 16 を使って os.FileMode をUnix形式のモードに変換し、上位16ビットに設定します。
    • さらに、MS-DOS属性も設定されるようになりました。os.ModeDir が設定されていれば msdosDir を、読み取り専用(実行権限がない)であれば msdosReadOnlyExternalAttrs に追加します。これは、オリジナルのZIPツールがUnixモードとMS-DOS属性の両方を設定する慣習に合わせたものです。
  7. 新しいヘルパー関数の追加:

    • msdosModeToFileMode(m uint32) os.FileMode: MS-DOS属性(ExternalAttrs の下位バイト)を os.FileMode に変換します。ディレクトリ属性や読み取り専用属性を考慮します。
    • fileModeToUnixMode(mode os.FileMode) uint32: os.FileMode をUnix形式のファイルモード(st_mode)に変換します。ファイルの種類(ディレクトリ、通常ファイル)と特殊なパーミッション(Setuid, Setgid)を考慮します。
    • unixModeToFileMode(m uint32) os.FileMode: Unix形式のファイルモード(st_mode)を os.FileMode に変換します。ファイルの種類と特殊なパーミッションを考慮します。

これらの変更により、archive/zip パッケージは、ZIPファイルの ExternalAttrs フィールドを、作成元OSに応じて適切に解釈し、Goの os.FileMode 型との間で正確に変換できるようになりました。

テストファイル (reader_test.go, writer_test.go) も、ZipTestFile および WriteTest 構造体の Mode フィールドの型が uint32 から os.FileMode に変更され、新しい os.FileMode の値(例: 0644, 0666, os.ModeDir | 0777, 0755 | os.ModeSetuid)がテストケースに追加されています。また、readTestFile 関数内の mtime の比較ロジックが改善され、ft.Mtime が空文字列の場合は比較をスキップするようになりました。

コアとなるコードの変更箇所

src/pkg/archive/zip/struct.go

--- a/src/pkg/archive/zip/struct.go
+++ b/src/pkg/archive/zip/struct.go
@@ -12,7 +12,7 @@ This package does not support ZIP64 or disk spanning.\n package zip
 
 import (
-\t"errors"\n+\t"os"\n \t"time"\n )
 
@@ -32,7 +32,11 @@ const (
 	dataDescriptorLen        = 12
 
 	// Constants for the first byte in CreatorVersion
-\tcreatorUnix = 3\n+\tcreatorFAT    = 0\n+\tcreatorUnix   = 3\n+\tcreatorNTFS   = 11\n+\tcreatorVFAT   = 14\n+\tcreatorMacOSX = 19\n )
 
 type FileHeader struct {
@@ -98,17 +102,85 @@ func (h *FileHeader) ModTime() time.Time {
 	return msDosTimeToTime(h.ModifiedDate, h.ModifiedTime)\n }\n \n+// traditional names for Unix constants\n+const (\n+\ts_IFMT  = 0xf000\n+\ts_IFDIR = 0x4000\n+\ts_IFREG = 0x8000\n+\ts_ISUID = 0x800\n+\ts_ISGID = 0x400\n+\n+\tmsdosDir      = 0x10\n+\tmsdosReadOnly = 0x01\n+)\n+\n // Mode returns the permission and mode bits for the FileHeader.\n // An error is returned in case the information is not available.\n-func (h *FileHeader) Mode() (mode uint32, err error) {\n-\tif h.CreatorVersion>>8 == creatorUnix {\n-\t\treturn h.ExternalAttrs >> 16, nil\n+func (h *FileHeader) Mode() (mode os.FileMode, err error) {\n+\tswitch h.CreatorVersion >> 8 {\n+\tcase creatorUnix, creatorMacOSX:\n+\t\tmode = unixModeToFileMode(h.ExternalAttrs >> 16)\n+\tcase creatorNTFS, creatorVFAT, creatorFAT:\n+\t\tmode = msdosModeToFileMode(h.ExternalAttrs)\n \t}\n-\treturn 0, errors.New(\"file mode not available\")\n+\tif len(h.Name) > 0 && h.Name[len(h.Name)-1] == \'/\' {\n+\t\tmode |= os.ModeDir\n+\t}\n+\treturn mode, nil\n }\n \n // SetMode changes the permission and mode bits for the FileHeader.\n-func (h *FileHeader) SetMode(mode uint32) {\n+func (h *FileHeader) SetMode(mode os.FileMode) {\n \th.CreatorVersion = h.CreatorVersion&0xff | creatorUnix<<8\n-\th.ExternalAttrs = mode << 16\n+\th.ExternalAttrs = fileModeToUnixMode(mode) << 16\n+\n+\t// set MSDOS attributes too, as the original zip does.\n+\tif mode&os.ModeDir != 0 {\n+\t\th.ExternalAttrs |= msdosDir\n+\t}\n+\tif mode&0200 == 0 {\n+\t\th.ExternalAttrs |= msdosReadOnly\n+\t}\n+}\n+\n+func msdosModeToFileMode(m uint32) (mode os.FileMode) {\n+\tif m&msdosDir != 0 {\n+\t\tmode = os.ModeDir | 0777\n+\t} else {\n+\t\tmode = 0666\n+\t}\n+\tif m&msdosReadOnly != 0 {\n+\t\tmode &^= 0222\n+\t}\n+\treturn mode\n+}\n+\n+func fileModeToUnixMode(mode os.FileMode) uint32 {\n+\tvar m uint32\n+\tif mode&os.ModeDir != 0 {\n+\t\tm = s_IFDIR\n+\t} else {\n+\t\tm = s_IFREG\n+\t}\n+\tif mode&os.ModeSetuid != 0 {\n+\t\tm |= s_ISUID\n+\t}\n+\tif mode&os.ModeSetgid != 0 {\n+\t\tm |= s_ISGID\n+\t}\n+\treturn m | uint32(mode&0777)\n+}\n+\n+func unixModeToFileMode(m uint32) os.FileMode {\n+\tvar mode os.FileMode\n+\tif m&s_IFMT == s_IFDIR {\n+\t\tmode |= os.ModeDir\n+\t}\n+\tif m&s_ISGID != 0 {\n+\t\tmode |= os.ModeSetgid\n+\t}\n+\tif m&s_ISUID != 0 {\n+\t\tmode |= os.ModeSetuid\n+\t}\n+\treturn mode | os.FileMode(m&0777)\n }\n```

### `src/pkg/archive/zip/reader_test.go`

```diff
--- a/src/pkg/archive/zip/reader_test.go
+++ b/src/pkg/archive/zip/reader_test.go
@@ -25,7 +26,7 @@ type ZipTestFile struct {
 	Content []byte // if blank, will attempt to compare against File\n 	File    string // name of file to compare to (relative to testdata/)\n 	Mtime   string // modified time in format "mm-dd-yy hh:mm:ss"\n-\tMode    uint32\n+\tMode    os.FileMode\n }\n \n // Caution: The Mtime values found for the test files should correspond to\n@@ -47,13 +48,13 @@ var tests = []ZipTest{\n \t\t\t\tName:    "test.txt",\n \t\t\t\tContent: []byte("This is a test text file.\\n"),\n \t\t\t\tMtime:   "09-05-10 12:12:02",\n-\t\t\t\tMode:    0x81a4,\n+\t\t\t\tMode:    0644,\n \t\t\t},\n \t\t\t{\n \t\t\t\tName:  "gophercolor16x16.png",\n \t\t\t\tFile:  "gophercolor16x16.png",\n \t\t\t\tMtime: "09-05-10 15:52:58",\n-\t\t\t\tMode:  0x81a4,\n+\t\t\t\tMode:  0644,\n \t\t\t},\n \t\t},\n \t},\n@@ -64,6 +65,7 @@ var tests = []ZipTest{\n \t\t\t\tName:  "r/r.zip",\n \t\t\t\tFile:  "r.zip",\n \t\t\t\tMtime: "03-04-10 00:24:16",\n+\t\t\t\tMode:  0666,\n \t\t\t},\n \t\t},\n \t},\n@@ -76,9 +78,43 @@ var tests = []ZipTest{\n \t\t\t\tName:    "filename",\n \t\t\t\tContent: []byte("This is a test textfile.\\n"),\n \t\t\t\tMtime:   "02-02-11 13:06:20",\n+\t\t\t\tMode:    0666,\n \t\t\t},\n \t\t},\n \t},\n+\t{\n+\t\t// created in windows XP file manager.\n+\t\tName: "winxp.zip",\n+\t\tFile: crossPlatform,\n+\t},\n+\t{\n+\t\t// created by Zip 3.0 under Linux\n+\t\tName: "unix.zip",\n+\t\tFile: crossPlatform,\n+\t},\n+}\n+\n+var crossPlatform = []ZipTestFile{\n+\t{\n+\t\tName:    "hello",\n+\t\tContent: []byte("world \\r\\n"),\n+\t\tMode:    0666,\n+\t},\n+\t{\n+\t\tName:    "dir/bar",\n+\t\tContent: []byte("foo \\r\\n"),\n+\t\tMode:    0666,\n+\t},\n+\t{\n+\t\tName:    "dir/empty/",\n+\t\tContent: []byte{},\n+\t\tMode:    os.ModeDir | 0777,\n+\t},\n+\t{\n+\t\tName:    "readonly",\n+\t\tContent: []byte("important \\r\\n"),\n+\t\tMode:    0444,\n+\t},\n }\n \n func TestReader(t *testing.T) {\n@@ -159,13 +195,15 @@ func readTestFile(t *testing.T, ft ZipTestFile, f *File) {\n \t\tt.Errorf("name=%q, want %q", f.Name, ft.Name)\n \t}\n \n-\tmtime, err := time.Parse("01-02-06 15:04:05", ft.Mtime)\n-\tif err != nil {\n-\t\tt.Error(err)\n-\t\treturn\n-\t}\n-\tif ft := f.ModTime(); !ft.Equal(mtime) {\n-\t\tt.Errorf("%s: mtime=%s, want %s", f.Name, ft, mtime)\n+\tif ft.Mtime != "" {\n+\t\tmtime, err := time.Parse("01-02-06 15:04:05", ft.Mtime)\n+\t\tif err != nil {\n+\t\t\tt.Error(err)\n+\t\t\treturn\n+\t\t}\n+\t\tif ft := f.ModTime(); !ft.Equal(mtime) {\n+\t\t\tt.Errorf("%s: mtime=%s, want %s", f.Name, ft, mtime)\n+\t\t}\n \t}\n \n \ttestFileMode(t, f, ft.Mode)\n@@ -191,7 +229,7 @@ func readTestFile(t *testing.T, ft ZipTestFile, f *File) {\n \tr.Close()\n \n \tvar c []byte\n-\tif len(ft.Content) != 0 {\n+\tif ft.Content != nil {\n \t\tc = ft.Content\n \t} else if c, err = ioutil.ReadFile("testdata/" + ft.File); err != nil {\n \t\tt.Error(err)\n@@ -211,7 +249,7 @@ func readTestFile(t *testing.T, f *File, want uint32) {\n \t}\n }\n \n-func testFileMode(t *testing.T, f *File, want uint32) {\n+func testFileMode(t *testing.T, f *File, want os.FileMode) {\n \tmode, err := f.Mode()\n \tif want == 0 {\n \t\tif err == nil {\n@@ -220,7 +258,7 @@ func testFileMode(t *testing.T, f *File, want uint32) {\n \t} else if err != nil {\n \t\tt.Errorf("%s mode: %s", f.Name, err)\n \t} else if mode != want {\n-\t\tt.Errorf("%s mode: want 0x%x, got 0x%x", f.Name, want, mode)\n+\t\tt.Errorf("%s mode: want %v, got %v", f.Name, want, mode)\n \t}\n }\n```

### `src/pkg/archive/zip/writer_test.go`

```diff
--- a/src/pkg/archive/zip/writer_test.go
+++ b/src/pkg/archive/zip/writer_test.go
@@ -8,6 +8,7 @@ import (
 	"bytes"\n 	"io/ioutil"\n 	"math/rand"\n+\t"os"\n 	"testing"\n )\n \n@@ -17,7 +18,7 @@ type WriteTest struct {
 	Name   string\n 	Data   []byte\n 	Method uint16\n-\tMode   uint32\n+\tMode   os.FileMode\n }\n \n var writeTests = []WriteTest{\n@@ -25,12 +26,31 @@ var writeTests = []WriteTest{\n \t\tName:   "foo",\n \t\tData:   []byte("Rabbits, guinea pigs, gophers, marsupial rats, and quolls."),\n \t\tMethod: Store,\n+\t\tMode:   0666,\n \t},\n \t{\n \t\tName:   "bar",\n \t\tData:   nil, // large data set in the test\n \t\tMethod: Deflate,\n-\t\tMode:   0x81ed,\n+\t\tMode:   0644,\n+\t},\n+\t{\n+\t\tName:   "setuid",\n+\t\tData:   []byte("setuid file"),\n+\t\tMethod: Deflate,\n+\t\tMode:   0755 | os.ModeSetuid,\n+\t},\n+\t{\n+\t\tName:   "setgid",\n+\t\tData:   []byte("setgid file"),\n+\t\tMethod: Deflate,\n+\t\tMode:   0755 | os.ModeSetgid,\n+\t},\n+\t{\n+\t\tName:   "setgid",\n+\t\tData:   []byte("setgid file"),\n+\t\tMethod: Deflate,\n+\t\tMode:   0755 | os.ModeSetgid,\n \t},\n }\n```

## コアとなるコードの解説

### `FileHeader.Mode() (mode os.FileMode, err error)`

このメソッドは、ZIPエントリの `FileHeader` からファイルモードを抽出し、Goの `os.FileMode` 型として返します。

-   **`switch h.CreatorVersion >> 8`**: ZIPエントリを作成したOSを識別します。`CreatorVersion` の上位バイトがOSコードを示します。
-   **`case creatorUnix, creatorMacOSX:`**: 作成元がUnix系OSまたはmacOSの場合、`ExternalAttrs` の上位16ビットにUnixのファイルモードが格納されていると仮定し、`unixModeToFileMode` 関数を使って `os.FileMode` に変換します。
-   **`case creatorNTFS, creatorVFAT, creatorFAT:`**: 作成元がWindows系OSの場合、`ExternalAttrs` の下位バイトにMS-DOS属性が格納されていると仮定し、`msdosModeToFileMode` 関数を使って `os.FileMode` に変換します。
-   **`if len(h.Name) > 0 && h.Name[len(h.Name)-1] == '/'`**: ファイル名がスラッシュで終わる場合、それはディレクトリであることを示唆するため、`os.ModeDir` フラグを `mode` に追加します。これにより、ディレクトリが正しく識別されます。

### `FileHeader.SetMode(mode os.FileMode)`

このメソッドは、Goの `os.FileMode` をZIPエントリの `FileHeader` に設定します。

-   **`h.CreatorVersion = h.CreatorVersion&0xff | creatorUnix<<8`**: ZIPエントリの作成元OSを `creatorUnix` に設定します。これは、GoがZIPファイルを作成する際に、Unix形式のファイルモードを `ExternalAttrs` に書き込むことを標準とするためです。
-   **`h.ExternalAttrs = fileModeToUnixMode(mode) << 16`**: `os.FileMode` を `fileModeToUnixMode` 関数でUnix形式のファイルモードに変換し、それを `ExternalAttrs` の上位16ビットに設定します。
-   **MS-DOS属性の設定**:
    -   **`if mode&os.ModeDir != 0 { h.ExternalAttrs |= msdosDir }`**: `os.FileMode` がディレクトリを示す場合、MS-DOSのディレクトリ属性 `msdosDir` を `ExternalAttrs` に追加します。
    -   **`if mode&0200 == 0 { h.ExternalAttrs |= msdosReadOnly }`**: `os.FileMode` が書き込み権限を持たない場合(`0200` は所有者の書き込み権限)、MS-DOSの読み取り専用属性 `msdosReadOnly` を `ExternalAttrs` に追加します。これは、オリジナルのZIPツールがUnixモードとMS-DOS属性の両方を設定する慣習に合わせたもので、クロスプラットフォームでの互換性を高めます。

### `msdosModeToFileMode(m uint32) (mode os.FileMode)`

この関数は、MS-DOS属性(`ExternalAttrs` の下位バイト)を `os.FileMode` に変換します。

-   **`if m&msdosDir != 0 { mode = os.ModeDir | 0777 } else { mode = 0666 }`**: MS-DOSのディレクトリ属性が設定されていれば、`os.ModeDir` とデフォルトのパーミッション `0777` を設定します。そうでなければ、デフォルトのファイルパーミッション `0666` を設定します。
-   **`if m&msdosReadOnly != 0 { mode &^= 0222 }`**: MS-DOSの読み取り専用属性が設定されていれば、書き込み権限(`0222`)をクリアします。

### `fileModeToUnixMode(mode os.FileMode) uint32`

この関数は、`os.FileMode` をUnix形式のファイルモード(`st_mode`)に変換します。

-   **`if mode&os.ModeDir != 0 { m = s_IFDIR } else { m = s_IFREG }`**: `os.FileMode` がディレクトリであれば `s_IFDIR` を、そうでなければ `s_IFREG`(通常ファイル)を設定します。
-   **`if mode&os.ModeSetuid != 0 { m |= s_ISUID }`**: Setuidビットが設定されていれば `s_ISUID` を追加します。
-   **`if mode&os.ModeSetgid != 0 { m |= s_ISGID }`**: Setgidビットが設定されていれば `s_ISGID` を追加します。
-   **`return m | uint32(mode&0777)`**: ファイルの種類と特殊なパーミッションに、`os.FileMode` の下位9ビット(通常のパーミッション)を結合して返します。

### `unixModeToFileMode(m uint32) os.FileMode`

この関数は、Unix形式のファイルモード(`st_mode`)を `os.FileMode` に変換します。

-   **`if m&s_IFMT == s_IFDIR { mode |= os.ModeDir }`**: ファイルタイプマスク `s_IFMT` を使って、モードがディレクトリであれば `os.ModeDir` を設定します。
-   **`if m&s_ISGID != 0 { mode |= os.ModeSetgid }`**: Setgidビットが設定されていれば `os.ModeSetgid` を設定します。
-   **`if m&s_ISUID != 0 { mode |= os.ModeSetuid }`**: Setuidビットが設定されていれば `os.ModeSetuid` を設定します。
-   **`return mode | os.FileMode(m&0777)`**: ファイルの種類と特殊なパーミッションに、Unixモードの下位9ビット(通常のパーミッション)を結合して返します。

これらの変更により、`archive/zip` パッケージは、ZIPファイルのファイルモードをより正確かつクロスプラットフォーム互換性のある方法で扱うことができるようになりました。

## 関連リンク

-   [https://golang.org/cl/5440130](https://golang.org/cl/5440130)

## 参考にした情報源リンク

-   Go言語 `os` パッケージのドキュメント: `os.FileMode`
-   ZIPファイルフォーマットの仕様 (例: PKWARE's APPNOTE.TXT)
-   Unixファイルパーミッションに関する一般的な知識