📑

Go言語における os.Stat() と os.Lstat() の違い

に公開

はじめに

Go言語において、ファイルやディレクトリの情報を取得する処理をすることがあります。標準ライブラリのosパッケージには、このためにos.Stat()os.Lstat()という2つの関数があります。一見似ているこれらですが、シンボリックリンクを扱う際に重要な違いがあります。

基本的な違い

os.Stat()

  • シンボリックリンクを辿って、リンク先のファイルやディレクトリの情報を返します。
  • リンク先の実際のファイル情報が必要な場合に使用します。
  • シンボリックリンクが壊れている場合(リンク先が存在しない場合)はエラーを返します(os.ErrNotExist)。

os.Lstat()

  • シンボリックリンクを辿らず、リンク自体の情報を返します。
  • シンボリックリンクそのものの情報が必要な場合に使用します。
  • シンボリックリンクが壊れていても、リンク自体の情報を取得できます。

実際の動作の違い

以下のようなファイル構成を考えてみます。

/tmp/test/
├── file           # 通常のファイル
└── link_to_tmp    # /tmp/testへのシンボリックリンク

通常のファイルに対する場合

通常のファイルに対しては、os.Stat()os.Lstat()の結果は同じになります。

// ファイル "file" に対する結果
os.Stat("/tmp/test/file")  // Size: 5, Mode: -rw-r--r--, IsDir: false
os.Lstat("/tmp/test/file") // Size: 5, Mode: -rw-r--r--, IsDir: false

シンボリックリンクに対する場合

シンボリックリンクに対しては、結果が異なります。

// シンボリックリンク "link_to_tmp" に対する結果
os.Stat("/tmp/test/link_to_tmp")  // Size: 480, Mode: dtrwxrwxrwx, IsDir: true
os.Lstat("/tmp/test/link_to_tmp") // Size: 4, Mode:  Lrwxr-xr-x, IsDir: false

解説:

  • os.Stat(): シンボリックリンクを辿り、リンク先であるディレクトリ/tmp/testの情報を返します。
    • Size: ディレクトリのサイズ
    • Mode: ディレクトリのパーミッション
    • IsDir: true(ディレクトリであるため。)
  • os.Lstat(): シンボリックリンク自体の情報を返します。
    • Size: シンボリックリンクのターゲットパスの長さ(この場合は"/tmp/test"
    • Mode: シンボリックリンクのパーミッション(Lが先頭に付きます。)
    • IsDir: false(シンボリックリンクそのものはファイルとして扱われます。)

実装例

以下のようにして、os.Stat()os.Lstat()の違いを確認することができます。

package main

import (
    "fmt"
    "os"
)

func printFileInfo(path string) error {
    // os.Stat()を使用して情報を取得
    statInfo, err := os.Stat(path)
    if err != nil {
        return fmt.Errorf("os.Stat() error: %v", err)
    }
    fmt.Println("os.Stat() の結果:")
    fmt.Printf("Name: %v\nSize: %v\nMode: %v\nIsDir: %v\n\n",
        statInfo.Name(), statInfo.Size(), statInfo.Mode(), statInfo.IsDir())

    // os.Lstat()を使用して情報を取得
    lstatInfo, err := os.Lstat(path)
    if err != nil {
        return fmt.Errorf("os.Lstat() error: %v", err)
    }
    fmt.Println("os.Lstat() の結果:")
    fmt.Printf("Name: %v\nSize: %v\nMode: %v\nIsDir: %v\n",
        lstatInfo.Name(), lstatInfo.Size(), lstatInfo.Mode(), lstatInfo.IsDir())

    return nil
}

func main() {
    path := "/tmp/test/link_to_tmp"
    if err := printFileInfo(path); err != nil {
        fmt.Fprintf(os.Stderr, "Error: %v\n", err)
    }
}

シンボリックリンクの検出

シンボリックリンクかどうかは以下のように判定できます。

info, err := os.Lstat("/tmp/test/link_to_tmp")
if err == nil && info.Mode()&os.ModeSymlink != 0 {
    fmt.Println("これはシンボリックリンクです")
}

使い分けについて

  1. ファイルシステムの走査

    • os.Lstat(): シンボリックリンクによる循環参照を避け、無限ループを防ぐためにリンクを辿らずに走査する場合。
    • os.Stat(): リンク先も含めて完全なファイルシステムの状態を取得したい場合。
  2. パフォーマンス

    • os.Stat()は指定されたパスがシンボリックリンクである場合、そのリンク先のファイル情報を取得します。これにより、リンクを辿るための追加のオーバーヘッドが発生します。リンク先のファイルのメタデータを取得するために、実際のファイルシステムへのアクセスが必要になるため、パフォーマンスの影響を受けることがあります。
    • os.Lstat()はシンボリックリンク自体の情報を取得するため、リンクを辿る必要がありません。このため、os.Lstat()は通常、os.Stat()よりも高速に動作します。特に、シンボリックリンクが多く存在する場合や、リンク先のファイルが大きい場合には、os.Lstat()の方が効率的です。

エラーハンドリング

2つの関数の主なエラーの種類は以下の通りです。

  • ファイルやディレクトリが存在しない場合: エラーは両者ともにos.ErrNotExist
  • アクセス権限がない場合: エラーは両者ともにos.ErrPermission
    • ディレクトリに対する読み取り権限がない場合。
  • 壊れたシンボリックリンクの場合:
    • os.Stat(): os.ErrNotExistエラーが発生する。
    • os.Lstat(): シンボリックリンク自体の情報を返します。

まとめ

Go言語のos.Stat()os.Lstat()は、シンボリックリンクを扱う際に、リンク先の情報が必要か、リンクそのものの情報が必要かによって使い分ける必要があります。
また、ファイルシステムの走査やパフォーマンスの観点でどちらを使うのかを検討するのがいいと思います。

参考文献

Discussion