[PHP]posix_errno関数の使い方を完全解説!POSIXエラー番号を取得する方法

PHP

はじめに

PHPでPOSIX関数を使用していると、処理が失敗することがあります。その際、「なぜ失敗したのか?」を詳しく知りたいと思ったことはありませんか?

通常のPHP関数は例外やエラーメッセージで失敗理由を教えてくれますが、POSIX関数の多くはfalseを返すだけです。しかし実は、失敗の詳細な理由はerrnoという番号として保存されています。

そんなエラー番号を取得するのが**posix_errno関数**です。この関数とposix_strerrorを組み合わせることで、POSIX関数の失敗理由を詳しく知ることができます。

この記事では、posix_errnoの基本から実践的なエラーハンドリング方法まで、詳しく解説します。

posix_errnoとは?

posix_errnoは、最後に失敗したPOSIX関数のエラー番号(errno)を返す関数です。

基本構文

posix_errno(): int

パラメータ

この関数はパラメータを取りません。

戻り値

  • 整数: 最後に失敗したPOSIX関数のエラー番号
  • 0: エラーなし、または前回の操作が成功

対応環境

  • POSIX準拠システム(Linux、Unix、macOS)
  • Windows では利用不可
  • POSIX拡張モジュールが必要

対応バージョン

  • PHP 4.2.0 以降で使用可能

基本的な使い方

エラー番号の取得

<?php
// ファイルを開こうとする(存在しないファイル)
$fd = posix_open('/nonexistent/file.txt', O_RDONLY);

if ($fd === false) {
    $errno = posix_errno();
    echo "エラー番号: {$errno}\n";
    
    // エラーメッセージを取得
    $error_message = posix_strerror($errno);
    echo "エラーメッセージ: {$error_message}\n";
}

// 出力例:
// エラー番号: 2
// エラーメッセージ: No such file or directory
?>

posix_get_last_errorとの関係

<?php
// posix_errnoとposix_get_last_errorは同じ値を返す
$result = posix_kill(99999, SIGTERM); // 存在しないプロセス

if ($result === false) {
    $errno1 = posix_errno();
    $errno2 = posix_get_last_error();
    
    echo "posix_errno(): {$errno1}\n";
    echo "posix_get_last_error(): {$errno2}\n";
    echo "同じ値: " . ($errno1 === $errno2 ? 'はい' : 'いいえ') . "\n";
}

// 注意: posix_get_last_errorはPHP 4.2.0で追加されたエイリアス
?>

主なエラー番号(errno)

一般的なエラー番号

<?php
// よく遭遇するエラー番号の定数
$common_errors = [
    EPERM => 'EPERM (1) - 操作が許可されていません',
    ENOENT => 'ENOENT (2) - ファイルまたはディレクトリが存在しません',
    ESRCH => 'ESRCH (3) - プロセスが存在しません',
    EINTR => 'EINTR (4) - システムコールが中断されました',
    EIO => 'EIO (5) - 入出力エラー',
    ENXIO => 'ENXIO (6) - デバイスが存在しません',
    EACCES => 'EACCES (13) - アクセスが拒否されました',
    EEXIST => 'EEXIST (17) - ファイルが既に存在します',
    ENODEV => 'ENODEV (19) - デバイスが存在しません',
    ENOTDIR => 'ENOTDIR (20) - ディレクトリではありません',
    EISDIR => 'EISDIR (21) - ディレクトリです',
    EINVAL => 'EINVAL (22) - 無効な引数',
    EMFILE => 'EMFILE (24) - 開いているファイルが多すぎます',
    ENOSPC => 'ENOSPC (28) - デバイスに空き容量がありません',
    EROFS => 'EROFS (30) - 読み取り専用ファイルシステム',
];

echo "=== よく使われるエラー番号 ===\n\n";
foreach ($common_errors as $errno => $description) {
    echo "{$description}\n";
}
?>

実践的な使用例

例1: 詳細なエラーハンドリング

<?php
class PosixErrorHandler {
    public static function handleError($operation, $result) {
        if ($result === false) {
            $errno = posix_errno();
            $errmsg = posix_strerror($errno);
            
            throw new RuntimeException(
                "POSIX操作失敗: {$operation}\n" .
                "エラー番号: {$errno}\n" .
                "エラーメッセージ: {$errmsg}"
            );
        }
        
        return $result;
    }
    
    public static function tryOperation($operation, callable $callback) {
        try {
            $result = $callback();
            return self::handleError($operation, $result);
        } catch (RuntimeException $e) {
            return [
                'success' => false,
                'error' => $e->getMessage(),
                'errno' => posix_errno()
            ];
        }
    }
}

// 使用例
$result = PosixErrorHandler::tryOperation(
    'ファイルを開く',
    fn() => posix_open('/nonexistent/file.txt', O_RDONLY)
);

if ($result['success'] === false) {
    echo "エラーが発生しました:\n";
    echo $result['error'] . "\n";
}
?>

例2: エラー番号による分岐処理

<?php
function createDirectory($path, $mode = 0755) {
    $result = @mkdir($path, $mode);
    
    if (!$result) {
        $errno = posix_errno();
        
        switch ($errno) {
            case EEXIST:
                // 既に存在する場合
                echo "ディレクトリは既に存在します: {$path}\n";
                return true; // エラーとして扱わない
                
            case EACCES:
                // アクセス拒否
                throw new Exception(
                    "ディレクトリ作成の権限がありません: {$path}\n" .
                    "親ディレクトリの権限を確認してください"
                );
                
            case ENOENT:
                // 親ディレクトリが存在しない
                $parent = dirname($path);
                throw new Exception(
                    "親ディレクトリが存在しません: {$parent}\n" .
                    "mkdir -p を使用するか、親ディレクトリを先に作成してください"
                );
                
            case ENOSPC:
                // ディスク容量不足
                throw new Exception(
                    "ディスク容量が不足しています\n" .
                    "空き容量を確保してください"
                );
                
            default:
                $errmsg = posix_strerror($errno);
                throw new Exception(
                    "ディレクトリ作成に失敗しました: {$path}\n" .
                    "エラー: {$errmsg} (errno: {$errno})"
                );
        }
    }
    
    return true;
}

// 使用例
try {
    createDirectory('/tmp/test/subdir');
    echo "ディレクトリを作成しました\n";
} catch (Exception $e) {
    echo "エラー: " . $e->getMessage() . "\n";
}
?>

例3: ファイル操作のエラー診断

<?php
class FileOperationDiagnostics {
    public static function diagnoseFileAccess($filepath) {
        echo "=== ファイルアクセス診断 ===\n";
        echo "対象: {$filepath}\n\n";
        
        $issues = [];
        
        // 1. 存在確認
        if (!file_exists($filepath)) {
            $errno = ENOENT;
            $issues[] = [
                'errno' => $errno,
                'message' => posix_strerror($errno),
                'severity' => 'error',
                'suggestion' => 'ファイルパスを確認してください'
            ];
        }
        
        // 2. 読み取り権限
        if (file_exists($filepath)) {
            $result = posix_access($filepath, POSIX_R_OK);
            
            if (!$result) {
                $errno = posix_errno();
                $issues[] = [
                    'errno' => $errno,
                    'message' => '読み取り権限がありません',
                    'severity' => 'error',
                    'suggestion' => 'chmod +r またはchownでファイルの権限を変更'
                ];
            }
        }
        
        // 3. 書き込み権限
        if (file_exists($filepath)) {
            $result = posix_access($filepath, POSIX_W_OK);
            
            if (!$result) {
                $errno = posix_errno();
                $issues[] = [
                    'errno' => $errno,
                    'message' => '書き込み権限がありません',
                    'severity' => 'warning',
                    'suggestion' => 'chmod +w で書き込み権限を付与'
                ];
            }
        }
        
        // 4. ディレクトリかファイルか
        if (file_exists($filepath) && is_dir($filepath)) {
            $issues[] = [
                'errno' => EISDIR,
                'message' => posix_strerror(EISDIR),
                'severity' => 'error',
                'suggestion' => 'ディレクトリではなくファイルを指定してください'
            ];
        }
        
        // 結果表示
        if (empty($issues)) {
            echo "✓ 問題は見つかりませんでした\n";
            return true;
        } else {
            echo "問題が見つかりました:\n\n";
            foreach ($issues as $i => $issue) {
                $mark = match($issue['severity']) {
                    'error' => '✗',
                    'warning' => '⚠',
                    default => 'ℹ'
                };
                
                echo ($i + 1) . ". {$mark} [{$issue['severity']}] {$issue['message']}\n";
                echo "   errno: {$issue['errno']}\n";
                echo "   対処: {$issue['suggestion']}\n\n";
            }
            return false;
        }
    }
}

// 使用例
FileOperationDiagnostics::diagnoseFileAccess('/etc/shadow');
?>

例4: プロセス操作のエラーハンドリング

<?php
function sendSignalWithErrorHandling($pid, $signal) {
    echo "プロセス {$pid} にシグナル {$signal} を送信...\n";
    
    $result = posix_kill($pid, $signal);
    
    if ($result === false) {
        $errno = posix_errno();
        $errmsg = posix_strerror($errno);
        
        echo "シグナル送信失敗\n\n";
        
        switch ($errno) {
            case ESRCH:
                echo "✗ プロセスが存在しません (errno: {$errno})\n";
                echo "   プロセスは既に終了しているか、PIDが間違っています\n";
                echo "   確認: ps aux | grep {$pid}\n";
                break;
                
            case EPERM:
                echo "✗ 権限がありません (errno: {$errno})\n";
                echo "   このプロセスにシグナルを送る権限がありません\n";
                
                // 現在のユーザーとプロセスの所有者を確認
                $current_user = posix_getpwuid(posix_getuid());
                echo "   現在のユーザー: {$current_user['name']}\n";
                
                // プロセス情報を取得(存在する場合)
                $ps_info = shell_exec("ps -p {$pid} -o user=");
                if ($ps_info) {
                    echo "   プロセスの所有者: " . trim($ps_info) . "\n";
                    echo "   対処: sudo を使用するか、所有者として実行\n";
                }
                break;
                
            case EINVAL:
                echo "✗ 無効なシグナル (errno: {$errno})\n";
                echo "   シグナル番号が無効です: {$signal}\n";
                echo "   利用可能なシグナル: SIGTERM(15), SIGKILL(9), SIGHUP(1) など\n";
                break;
                
            default:
                echo "✗ 予期しないエラー (errno: {$errno})\n";
                echo "   {$errmsg}\n";
        }
        
        return false;
    }
    
    echo "✓ シグナルを送信しました\n";
    return true;
}

// 使用例
sendSignalWithErrorHandling(getmypid(), SIGTERM); // 自分自身に送信(成功)
sendSignalWithErrorHandling(99999, SIGTERM);      // 存在しないPID(失敗)
sendSignalWithErrorHandling(1, SIGKILL);          // initプロセス(権限エラー)
?>

例5: リソース制限のエラー処理

<?php
function checkResourceLimits() {
    echo "=== リソース制限チェック ===\n\n";
    
    // ファイル記述子の制限
    $limits = posix_getrlimit();
    
    if ($limits === false) {
        $errno = posix_errno();
        echo "✗ リソース制限の取得に失敗\n";
        echo "   errno: {$errno}\n";
        echo "   " . posix_strerror($errno) . "\n";
        return;
    }
    
    echo "ファイル記述子の制限:\n";
    echo "  ソフトリミット: {$limits['soft openfiles']}\n";
    echo "  ハードリミット: {$limits['hard openfiles']}\n\n";
    
    // 多数のファイルを開いてみる
    $files = [];
    $max_test = min(1000, $limits['soft openfiles'] - 10);
    
    for ($i = 0; $i < $max_test; $i++) {
        $fd = @fopen('php://temp', 'r');
        
        if ($fd === false) {
            $errno = posix_errno();
            
            if ($errno === EMFILE) {
                echo "⚠ ファイル記述子の上限に達しました\n";
                echo "   開いているファイル数: {$i}\n";
                echo "   errno: {$errno} (EMFILE)\n";
                echo "   " . posix_strerror($errno) . "\n";
                echo "\n対処方法:\n";
                echo "   1. ulimit -n で制限を増やす\n";
                echo "   2. 不要なファイルをcloseする\n";
                echo "   3. /etc/security/limits.conf を編集\n";
                break;
            }
        }
        
        $files[] = $fd;
    }
    
    // クリーンアップ
    foreach ($files as $fd) {
        if ($fd) fclose($fd);
    }
    
    echo "\n✓ リソースチェック完了\n";
}

checkResourceLimits();
?>

エラーメッセージの取得

posix_strerrorとの連携

<?php
function getDetailedError($context = '') {
    $errno = posix_errno();
    
    if ($errno === 0) {
        return null; // エラーなし
    }
    
    $errmsg = posix_strerror($errno);
    
    // 日本語の説明を追加
    $description = match($errno) {
        EPERM => '操作に必要な権限がありません',
        ENOENT => 'ファイルまたはディレクトリが見つかりません',
        ESRCH => '指定されたプロセスが存在しません',
        EACCES => 'アクセス権限がありません',
        EEXIST => 'ファイルが既に存在します',
        ENOTDIR => 'ディレクトリではありません',
        EISDIR => 'ディレクトリです(ファイルとして扱えません)',
        EINVAL => '引数が無効です',
        EMFILE => '開いているファイルが多すぎます',
        ENOSPC => 'ディスク容量が不足しています',
        EROFS => '読み取り専用ファイルシステムです',
        default => $errmsg
    };
    
    return [
        'errno' => $errno,
        'message' => $errmsg,
        'description' => $description,
        'context' => $context
    ];
}

// 使用例
$result = posix_kill(99999, SIGTERM);

if ($result === false) {
    $error = getDetailedError('プロセスへのシグナル送信');
    
    echo "エラーが発生しました\n";
    echo "コンテキスト: {$error['context']}\n";
    echo "エラー番号: {$error['errno']}\n";
    echo "システムメッセージ: {$error['message']}\n";
    echo "説明: {$error['description']}\n";
}
?>

デバッグとトラブルシューティング

エラー履歴の記録

<?php
class PosixErrorLogger {
    private static $errors = [];
    
    public static function logError($operation) {
        $errno = posix_errno();
        
        if ($errno === 0) {
            return; // エラーなし
        }
        
        self::$errors[] = [
            'timestamp' => microtime(true),
            'datetime' => date('Y-m-d H:i:s'),
            'operation' => $operation,
            'errno' => $errno,
            'message' => posix_strerror($errno),
            'backtrace' => debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 3)
        ];
    }
    
    public static function getErrors() {
        return self::$errors;
    }
    
    public static function displayErrors() {
        if (empty(self::$errors)) {
            echo "記録されたエラーはありません\n";
            return;
        }
        
        echo "=== POSIXエラーログ ===\n\n";
        
        foreach (self::$errors as $i => $error) {
            echo ($i + 1) . ". {$error['datetime']}\n";
            echo "   操作: {$error['operation']}\n";
            echo "   errno: {$error['errno']}\n";
            echo "   メッセージ: {$error['message']}\n";
            
            if (!empty($error['backtrace'])) {
                $caller = $error['backtrace'][0];
                echo "   場所: {$caller['file']}:{$caller['line']}\n";
            }
            
            echo "\n";
        }
    }
    
    public static function clear() {
        self::$errors = [];
    }
}

// 使用例
posix_kill(99999, SIGTERM);
PosixErrorLogger::logError('存在しないプロセスへのシグナル送信');

posix_access('/root/.ssh/id_rsa', POSIX_R_OK);
PosixErrorLogger::logError('秘密鍵ファイルへのアクセス');

PosixErrorLogger::displayErrors();
?>

ベストプラクティス

1. エラーチェックの実装

<?php
function safePosixOperation($operation_name, callable $operation) {
    $result = $operation();
    
    if ($result === false) {
        $errno = posix_errno();
        $errmsg = posix_strerror($errno);
        
        error_log(sprintf(
            "[POSIX Error] %s failed: %s (errno: %d)",
            $operation_name,
            $errmsg,
            $errno
        ));
        
        throw new RuntimeException(
            "{$operation_name} failed: {$errmsg}",
            $errno
        );
    }
    
    return $result;
}

// 使用例
try {
    $result = safePosixOperation(
        'プロセスへのシグナル送信',
        fn() => posix_kill($pid, SIGTERM)
    );
    echo "成功\n";
} catch (RuntimeException $e) {
    echo "エラー: " . $e->getMessage() . "\n";
    echo "エラー番号: " . $e->getCode() . "\n";
}
?>

2. カスタム例外の作成

<?php
class PosixException extends RuntimeException {
    private $errno;
    private $posix_message;
    
    public function __construct($operation, $errno = null) {
        $this->errno = $errno ?? posix_errno();
        $this->posix_message = posix_strerror($this->errno);
        
        $message = "{$operation} failed: {$this->posix_message} (errno: {$this->errno})";
        parent::__construct($message, $this->errno);
    }
    
    public function getErrno() {
        return $this->errno;
    }
    
    public function getPosixMessage() {
        return $this->posix_message;
    }
}

// 使用例
function killProcess($pid, $signal) {
    $result = posix_kill($pid, $signal);
    
    if ($result === false) {
        throw new PosixException("プロセス {$pid} へのシグナル送信");
    }
    
    return true;
}

try {
    killProcess(99999, SIGTERM);
} catch (PosixException $e) {
    echo "エラー: " . $e->getMessage() . "\n";
    echo "errno: " . $e->getErrno() . "\n";
}
?>

まとめ

posix_errnoは、POSIX関数のエラー番号を取得するための重要な関数です。

主な特徴:

  • ✅ 最後に失敗したPOSIX関数のエラー番号を返す
  • posix_strerrorと組み合わせて詳細なエラーメッセージを取得
  • ✅ エラー番号で条件分岐してきめ細かいエラーハンドリングが可能
  • ✅ デバッグとトラブルシューティングに不可欠

よく使うエラー番号:

  • EPERM (1) – 権限エラー
  • ENOENT (2) – ファイルが存在しない
  • ESRCH (3) – プロセスが存在しない
  • EACCES (13) – アクセス拒否
  • EEXIST (17) – ファイルが既に存在
  • EMFILE (24) – ファイル記述子の上限

関連関数:

  • posix_strerror() – エラー番号からメッセージを取得
  • posix_get_last_error() – posix_errno()のエイリアス

ベストプラクティス:

  • POSIX関数の戻り値を常にチェック
  • エラー時は必ずerrnoを確認
  • カスタム例外を作成して統一的なエラーハンドリング
  • エラーログに詳細情報を記録

この関数を理解して、より堅牢なPOSIXプログラミングを実現しましょう!

参考リンク


この記事が役に立ったら、ぜひシェアしてください!

タイトルとURLをコピーしました