[インデックス 12591] ファイルの概要
このコミットは、Go言語の標準ライブラリ os パッケージにおける os.IsExist() 関数のWindows環境での挙動を修正し、より正確にファイルやディレクトリの存在を示すエラーを判定できるようにするためのものです。特に、WindowsがPOSIX標準に完全に準拠していないことに起因する問題を解決し、os.IsExist() が syscall.ERROR_ALREADY_EXISTS や syscall.ERROR_FILE_EXISTS といったWindows固有のエラーコードも適切に処理するように拡張しています。また、この変更に伴い、Windows固有の実装を分離するためのビルドタグの調整と、新しいテストケースの追加が行われています。
コミット
commit 0238cec02144991036dadb7ee58e8c9a2de2b0de
Author: Shenghou Ma <minux.ma@gmail.com>
Date: Tue Mar 13 12:50:04 2012 +1100
os, syscall: windows really isn't posix compliant, fix os.IsExist()
R=golang-dev, rsc, bradfitz, alex.brainman
CC=golang-dev
https://golang.org/cl/5754083
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/0238cec02144991036dadb7ee58e8c9a2de2b0de
元コミット内容
os, syscall: windows really isn't posix compliant, fix os.IsExist()
R=golang-dev, rsc, bradfitz, alex.brainman
CC=golang-dev
https://golang.org/cl/5754083
変更の背景
Go言語の os パッケージには、ファイル操作で発生したエラーが特定の意味を持つかどうかを判定するためのヘルパー関数群(os.IsExist, os.IsNotExist, os.IsPermission など)が提供されています。これらの関数は、異なるオペレーティングシステム(OS)間で一貫したエラーハンドリングを提供することを目的としています。
しかし、Windowsオペレーティングシステムは、UNIX系のOSが準拠するPOSIX(Portable Operating System Interface)標準に完全に準拠していません。特に、ファイルやディレクトリが既に存在する場合に返されるエラーコードが、POSIX準拠のシステム(例: Linux, macOS)とWindowsでは異なることが問題となっていました。
従来の os.IsExist() は、主にPOSIXシステムで使われる syscall.EEXIST エラーコードに基づいて存在エラーを判定していました。しかし、Windowsではファイルが既に存在する場合に syscall.EEXIST ではなく、ERROR_ALREADY_EXISTS や ERROR_FILE_EXISTS といった異なるエラーコードを返すことがありました。この不一致により、Windows環境で os.OpenFile の os.O_EXCL フラグ(ファイルが既に存在する場合はエラーを返す)などを使用した場合に、os.IsExist() が期待通りに true を返さないというバグが発生していました。
このコミットは、このWindows固有のエラーコードを os.IsExist() が認識できるように拡張し、クロスプラットフォームでの os.IsExist() の信頼性を向上させることを目的としています。
前提知識の解説
POSIX (Portable Operating System Interface)
POSIXは、UNIX系オペレーティングシステムのAPIに関する標準規格群です。これにより、異なるUNIX系OS間でソフトウェアの互換性を高めることができます。ファイルシステム操作、プロセス管理、スレッド、ネットワークなど、多岐にわたるAPIが定義されています。多くのUNIX系OS(Linux、macOS、FreeBSDなど)はPOSIXに準拠していますが、Windowsは歴史的に独自のAPIセット(Win32 API)を持っており、POSIXに完全には準拠していません。このため、同じ操作でもOSによって異なるエラーコードや挙動を示すことがあります。
os.IsExist() 関数
Go言語の os パッケージで提供される func IsExist(err error) bool は、与えられたエラーが「ファイルまたはディレクトリが既に存在すること」を示すものである場合に true を返します。これは、ファイル作成時に os.O_EXCL フラグを指定して、ファイルが既に存在する場合にエラーを検出する際などに利用されます。この関数は、OS固有のエラーコードを抽象化し、開発者がプラットフォームに依存しないエラーハンドリングを記述できるようにするためのものです。
syscall パッケージ
Go言語の syscall パッケージは、オペレーティングシステムの低レベルなシステムコールへのインターフェースを提供します。これにより、GoプログラムからOSのカーネル機能に直接アクセスできます。ファイル操作、プロセス管理、ネットワーク通信など、OSの基本的な機能はシステムコールを通じて行われます。syscall パッケージはOSごとに異なる実装を持ち、各OS固有のエラーコードや定数を定義しています。
PathError 型
Go言語の os パッケージでは、ファイルパスに関連する操作でエラーが発生した場合に、*os.PathError 型のエラーが返されることがあります。PathError は、エラーが発生した操作(Op)、関連するファイルパス(Path)、そして元のシステムコールエラー(Err)を含む構造体です。os.IsExist() などのヘルパー関数は、この PathError の内部にある Err フィールドを調べて、実際のエラーコードを判定します。
Windows固有のエラーコード
Windows APIでは、操作が失敗した場合に特定の数値エラーコードを返します。このコミットで特に重要となるのは以下のエラーコードです。
ERROR_ALREADY_EXISTS(183): オブジェクト(ファイル、ディレクトリ、ミューテックスなど)が既に存在する場合に返されるエラーコードです。例えば、CreateDirectory関数で既に存在するディレクトリを作成しようとした場合などに発生します。ERROR_FILE_EXISTS(80): ファイルが既に存在する場合に返されるエラーコードです。例えば、CreateFile関数でCREATE_NEWフラグを指定して、既に存在するファイルを作成しようとした場合などに発生します。
これらのエラーコードは、POSIXの EEXIST に相当するWindowsのエラーです。
ビルドタグ (Build Tags)
Go言語では、ソースファイルの先頭に // +build <tag> の形式でビルドタグを記述することで、特定の環境でのみそのファイルをコンパイルするように制御できます。これにより、OS固有のコードやアーキテクチャ固有のコードを分離し、クロスプラットフォーム対応のアプリケーションを容易に開発できます。例えば、// +build windows と書かれたファイルはWindows環境でのみコンパイルされ、// +build linux と書かれたファイルはLinux環境でのみコンパイルされます。
技術的詳細
このコミットの核心は、Windows環境における os.IsExist() の挙動を、POSIX準拠のシステムと整合させることです。
従来のGoの os.IsExist() は、内部的に syscall.EEXIST をチェックしていました。これはUNIX系システムでは適切ですが、Windowsではファイルやディレクトリが既に存在する場合に syscall.EEXIST 以外のエラーコード(ERROR_ALREADY_EXISTS や ERROR_FILE_EXISTS)が返されることがありました。このため、Windowsで os.OpenFile の os.O_EXCL フラグを使ってファイル作成を試み、ファイルが既に存在した際に返されるエラーが os.IsExist() で正しく判定されないという問題がありました。
この修正では、以下の変更が行われました。
-
os/error_posix.goからWindowsを除外:src/pkg/os/error_posix.goは、POSIX準拠のシステム(darwin, freebsd, linux, netbsd, openbsd)向けにos.IsExist()などのヘルパー関数を定義していました。このファイルからwindowsビルドタグが削除され、Windows固有の実装が分離されることになりました。 -
os/error_windows.goの新規追加: Windows固有のos.IsExist(),os.IsNotExist(),os.IsPermission()の実装を含むsrc/pkg/os/error_windows.goが新しく追加されました。このファイルは+build windowsタグを持つため、Windows環境でのみコンパイルされます。 この新しいos.IsExist()実装では、syscall.EEXISTに加えて、Windows固有のエラーコードであるsyscall.ERROR_ALREADY_EXISTSとsyscall.ERROR_FILE_EXISTSも存在エラーとして認識するように拡張されました。これにより、Windows上でのファイル存在チェックの信頼性が向上します。 -
syscall/ztypes_windows.goへのWindowsエラーコードの追加:src/pkg/syscall/ztypes_windows.goは、Windowsのシステムコールで使用される定数や型を定義するファイルです。このファイルに、ERROR_FILE_EXISTS(80) とERROR_ALREADY_EXISTS(183) の定数が追加されました。これにより、GoプログラムからこれらのWindows固有のエラーコードをsyscallパッケージを通じて参照できるようになります。 -
os/error_test.goの新規追加:os.IsExist()の挙動を検証するための新しいテストファイルsrc/pkg/os/error_test.goが追加されました。このテストは、ioutil.TempFileを使って一時ファイルを作成し、そのファイルが既に存在する状態でos.OpenFileをos.O_RDWR|os.O_CREATE|os.O_EXCLフラグで開こうとすることでエラーを意図的に発生させます。そして、そのエラーがos.IsExist()によって正しくtrueと判定されることを確認します。このテストは、Windows環境での修正が正しく機能するかを検証する上で非常に重要です。
これらの変更により、Goの os.IsExist() 関数は、Windowsを含むすべてのサポート対象OSで、ファイルやディレクトリの存在を示すエラーをより正確に判定できるようになりました。
コアとなるコードの変更箇所
このコミットによる主要なコード変更は以下の4つのファイルにわたります。
-
src/pkg/os/error_posix.go--- a/src/pkg/os/error_posix.go +++ b/src/pkg/os/error_posix.go @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -// +build darwin freebsd linux netbsd openbsd windows +// +build darwin freebsd linux netbsd openbsd package os- ビルドタグから
windowsが削除されました。これにより、このファイルで定義されているIsExistなどの関数はWindowsではコンパイルされなくなります。
- ビルドタグから
-
src/pkg/os/error_test.go(新規ファイル)// Copyright 2012 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package os_test import ( "io/ioutil" "os" "testing" ) func TestErrIsExist(t *testing.T) { f, err := ioutil.TempFile("", "_Go_ErrIsExist") if err != nil { t.Fatalf("open ErrIsExist tempfile: %s", err) return } defer os.Remove(f.Name()) defer f.Close() f2, err := os.OpenFile(f.Name(), os.O_RDWR|os.O_CREATE|os.O_EXCL, 0600) if err == nil { f2.Close() t.Fatal("Open should have failed") return } if !os.IsExist(err) { t.Fatalf("os.IsExist does not work as expected for %#v", err) return } }os.IsExist()の動作を検証するための新しいテストケースが追加されました。
-
src/pkg/os/error_windows.go(新規ファイル)// Copyright 2012 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package os import "syscall" // IsExist returns whether the error is known to report that a file already exists. // It is satisfied by ErrExist as well as some syscall errors. func IsExist(err error) bool { if pe, ok := err.(*PathError); ok { err = pe.Err } return err == syscall.EEXIST || err == syscall.ERROR_ALREADY_EXISTS || err == syscall.ERROR_FILE_EXISTS || err == ErrExist } // IsNotExist returns whether the error is known to report that a file does not exist. // It is satisfied by ErrNotExist as well as some syscall errors. func IsNotExist(err error) bool { if pe, ok := err.(*PathError); ok { err = pe.Err } return err == syscall.ENOENT || err == ErrNotExist } // IsPermission returns whether the error is known to report that permission is denied. // It is satisfied by ErrPermission as well as some syscall errors. func IsPermission(err error) bool { if pe, ok := err.(*PathError); ok { err = pe.Err } return err == syscall.EACCES || err == syscall.EPERM || err == ErrPermission }- Windows環境でのみコンパイルされる
os.IsExist,os.IsNotExist,os.IsPermissionの実装が追加されました。 IsExist関数がsyscall.ERROR_ALREADY_EXISTSとsyscall.ERROR_FILE_EXISTSをチェックするようになりました。
- Windows環境でのみコンパイルされる
-
src/pkg/syscall/ztypes_windows.go--- a/src/pkg/syscall/ztypes_windows.go +++ b/src/pkg/syscall/ztypes_windows.go @@ -10,11 +10,13 @@ const ( ERROR_PATH_NOT_FOUND Errno = 3 ERROR_ACCESS_DENIED Errno = 5 ERROR_NO_MORE_FILES Errno = 18 + ERROR_FILE_EXISTS Errno = 80 ERROR_BROKEN_PIPE Errno = 109 ERROR_BUFFER_OVERFLOW Errno = 111 ERROR_INSUFFICIENT_BUFFER Errno = 122 ERROR_MOD_NOT_FOUND Errno = 126 ERROR_PROC_NOT_FOUND Errno = 127 + ERROR_ALREADY_EXISTS Errno = 183 ERROR_ENVVAR_NOT_FOUND Errno = 203 ERROR_OPERATION_ABORTED Errno = 995 ERROR_IO_PENDING Errno = 997- Windows固有のエラーコード
ERROR_FILE_EXISTS(80) とERROR_ALREADY_EXISTS(183) がsyscall.Errno型の定数として追加されました。
- Windows固有のエラーコード
コアとなるコードの解説
src/pkg/os/error_posix.go の変更
このファイルのビルドタグから windows が削除されたことで、error_posix.go で定義されていた IsExist などの関数は、Windows環境ではコンパイルされなくなりました。これは、WindowsがPOSIXに完全に準拠していないため、Windows固有のエラーハンドリングロジックを別途用意する必要があるという設計判断に基づいています。これにより、各OSに最適化されたエラー判定ロジックを提供するための基盤が整いました。
src/pkg/os/error_test.go の新規追加
TestErrIsExist 関数は、os.IsExist() の動作を検証するための統合テストです。
ioutil.TempFile("", "_Go_ErrIsExist")で一時ファイルを新規作成し、そのファイルハンドルfとエラーerrを取得します。これにより、テスト対象のファイルが確実に存在するようにします。defer os.Remove(f.Name())とdefer f.Close()で、テスト終了後に一時ファイルをクリーンアップし、ファイルハンドルを閉じます。os.OpenFile(f.Name(), os.O_RDWR|os.O_CREATE|os.O_EXCL, 0600)を呼び出します。f.Name(): 既に存在する一時ファイルのパスを指定します。os.O_RDWR: 読み書きモードで開きます。os.O_CREATE: ファイルが存在しない場合に作成します。os.O_EXCL:os.O_CREATEと同時に使用された場合、ファイルが既に存在するとエラーを返します。このフラグがこのテストの肝であり、意図的に「ファイルが存在する」というエラーを発生させます。0600: ファイルのパーミッションを設定します。
if err == nilで、os.OpenFileがエラーを返さなかった場合にテストを失敗させます。これは、os.O_EXCLフラグにより、ファイルが既に存在するためエラーが返されるべきだからです。if !os.IsExist(err)で、発生したエラーerrがos.IsExist()によって「ファイルが存在する」エラーとして正しく判定されるかを検証します。もしos.IsExist(err)がfalseを返した場合、テストは失敗し、os.IsExistが期待通りに動作していないことを示します。
このテストは、特にWindows環境において、os.OpenFile が ERROR_ALREADY_EXISTS や ERROR_FILE_EXISTS などのエラーを返した場合でも、os.IsExist() がそれらを正しく処理できることを保証します。
src/pkg/os/error_windows.go の新規追加
このファイルは、Windows環境でのみコンパイルされる os パッケージのエラーヘルパー関数の実装を提供します。
-
IsExist(err error) bool:- まず、エラーが
*PathError型であるかをチェックし、もしそうであれば内部のpe.Errを取り出します。これは、osパッケージの多くの関数がPathErrorを返すため、元のシステムコールエラーを抽出するためです。 - 次に、抽出されたエラーが
syscall.EEXIST、syscall.ERROR_ALREADY_EXISTS、syscall.ERROR_FILE_EXISTS、またはGoの内部エラー定数ErrExistのいずれかと一致するかをチェックします。 - この変更により、Windowsが返す可能性のある複数の「既に存在する」エラーコードを
os.IsExist()が適切に処理できるようになり、クロスプラットフォームでの一貫性が向上しました。
- まず、エラーが
-
IsNotExist(err error) boolとIsPermission(err error) bool:- これらの関数も同様に、
PathErrorから元のエラーを抽出し、Windows固有のsyscallエラーコード(syscall.ENOENTやsyscall.EACCES,syscall.EPERM)とGoの内部エラー定数(ErrNotExist,ErrPermission)を組み合わせてチェックするように実装されています。これにより、Windows環境での「ファイルが存在しない」エラーや「パーミッション拒否」エラーの判定も正確に行えるようになります。
- これらの関数も同様に、
src/pkg/syscall/ztypes_windows.go の変更
このファイルは、Windowsのシステムコールで使用される定数を定義しています。
const (ブロック内に、ERROR_FILE_EXISTS(値: 80) とERROR_ALREADY_EXISTS(値: 183) の2つのErrno型の定数が追加されました。- これらの定数が追加されたことで、Goの
syscallパッケージを通じてWindows固有のエラーコードを直接参照できるようになり、os/error_windows.goのIsExist関数でこれらのエラーコードを比較することが可能になりました。これは、Windows APIが返す具体的なエラーコードをGoの型システムにマッピングする重要なステップです。
これらの変更全体として、Go言語がWindows環境でファイル操作のエラーをより正確かつ堅牢に処理できるようになり、開発者がプラットフォームの違いを意識することなく、一貫したエラーハンドリングロジックを記述できる基盤が強化されました。
関連リンク
- Go言語の
osパッケージドキュメント: https://pkg.go.dev/os - Go言語の
syscallパッケージドキュメント: https://pkg.go.dev/syscall - Go言語の
os.IsExist関数: https://pkg.go.dev/os#IsExist - Go言語の
os.OpenFile関数: https://pkg.go.dev/os#OpenFile - Go言語のビルド制約 (Build Constraints): https://pkg.go.dev/cmd/go#hdr-Build_constraints
参考にした情報源リンク
- Windows System Error Codes: https://learn.microsoft.com/en-us/windows/win32/debug/system-error-codes
ERROR_ALREADY_EXISTS(183): https://learn.microsoft.com/en-us/windows/win32/debug/system-error-codes--0-499-#_183ERROR_FILE_EXISTS(80): https://learn.microsoft.com/en-us/windows/win32/debug/system-error-codes--0-499-#_80
- POSIX (Portable Operating System Interface): https://ja.wikipedia.org/wiki/POSIX
- Go issue related to this commit (likely the original issue that led to this fix): https://github.com/golang/go/issues/2002 (This is an educated guess based on the commit message and typical Go development workflow, as the CL link is internal.)
- Go Code Review (CL) 5754083: https://golang.org/cl/5754083 (Note: This link points to the Go project's internal code review system, which might not be publicly accessible or might redirect to a different URL now.)