[PHP]posix_strerror関数とは?エラーメッセージ取得を徹底解説

PHP

こんにちは!今回はPHPのPOSIX拡張モジュールに含まれる「posix_strerror」関数について、詳しく解説していきます。この関数は、POSIX関数のエラーを人間が読める形式で取得するための重要なデバッグツールです。

posix_strerrorとは何か?

posix_strerrorは、エラー番号(errno)を人間が読めるエラーメッセージに変換する関数です。POSIX関数が失敗したときに、具体的な原因を知るために使用します。

基本的な構文

posix_strerror(int $error_number): string

パラメータ:

  • $error_number: エラー番号(errno)

戻り値:

  • エラー番号に対応するエラーメッセージ文字列

基本的な使用例

例1: 最もシンプルな使い方

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

if ($fd === false) {
    // エラー番号を取得
    $errno = posix_get_last_error();

    // エラーメッセージを取得
    $error_message = posix_strerror($errno);

    echo "エラーが発生しました\n";
    echo "エラー番号: {$errno}\n";
    echo "エラーメッセージ: {$error_message}\n";
}

// 出力例:
// エラーが発生しました
// エラー番号: 2
// エラーメッセージ: No such file or directory
?>

例2: posix_get_last_errorと組み合わせる

<?php
// ユーザーIDを変更しようとする(権限がない場合)
if (!posix_setuid(0)) {
    $errno = posix_get_last_error();
    $error = posix_strerror($errno);

    echo "ユーザーID変更に失敗しました\n";
    echo "理由: {$error} (errno: {$errno})\n";
}

// 出力例:
// ユーザーID変更に失敗しました
// 理由: Operation not permitted (errno: 1)
?>

主なエラー番号とメッセージ

PHPでよく遭遇するPOSIXエラーの一覧です:

<?php
// 一般的なエラー番号とメッセージ
$commonErrors = [
    1 => 'EPERM',      // Operation not permitted
    2 => 'ENOENT',     // No such file or directory
    3 => 'ESRCH',      // No such process
    4 => 'EINTR',      // Interrupted system call
    5 => 'EIO',        // Input/output error
    9 => 'EBADF',      // Bad file descriptor
    12 => 'ENOMEM',    // Cannot allocate memory
    13 => 'EACCES',    // Permission denied
    14 => 'EFAULT',    // Bad address
    17 => 'EEXIST',    // File exists
    20 => 'ENOTDIR',   // Not a directory
    21 => 'EISDIR',    // Is a directory
    22 => 'EINVAL',    // Invalid argument
    24 => 'EMFILE',    // Too many open files
    28 => 'ENOSPC',    // No space left on device
    30 => 'EROFS',     // Read-only file system
];

echo "=== 一般的なPOSIXエラー ===\n\n";
foreach ($commonErrors as $errno => $name) {
    $message = posix_strerror($errno);
    echo "errno {$errno} ({$name}): {$message}\n";
}
?>

実践的な使用例

例1: エラーハンドリングクラス

<?php
class PosixErrorHandler {
    private $errors = [];

    public function handleError($operation, $errno = null) {
        // エラー番号が指定されていない場合は最後のエラーを取得
        if ($errno === null) {
            $errno = posix_get_last_error();
        }

        $error = [
            'operation' => $operation,
            'errno' => $errno,
            'message' => posix_strerror($errno),
            'timestamp' => time()
        ];

        $this->errors[] = $error;

        return $error;
    }

    public function getLastError() {
        return end($this->errors);
    }

    public function getAllErrors() {
        return $this->errors;
    }

    public function clearErrors() {
        $this->errors = [];
    }

    public function printError($error) {
        $time = date('Y-m-d H:i:s', $error['timestamp']);
        echo "[{$time}] {$error['operation']} 失敗\n";
        echo "  エラー: {$error['message']} (errno: {$error['errno']})\n";
    }

    public function logError($error, $logFile) {
        $time = date('Y-m-d H:i:s', $error['timestamp']);
        $log = "[{$time}] {$error['operation']}: {$error['message']} (errno: {$error['errno']})\n";
        file_put_contents($logFile, $log, FILE_APPEND);
    }
}

// 使用例
$handler = new PosixErrorHandler();

// ファイル操作
$fd = posix_open('/protected/file.txt', O_RDONLY);
if ($fd === false) {
    $error = $handler->handleError('posix_open');
    $handler->printError($error);
}

// ユーザー変更
if (!posix_setuid(0)) {
    $error = $handler->handleError('posix_setuid');
    $handler->printError($error);
    $handler->logError($error, '/var/log/app.log');
}

// すべてのエラーを表示
echo "\n=== すべてのエラー ===\n";
foreach ($handler->getAllErrors() as $error) {
    $handler->printError($error);
}
?>

例2: 詳細なエラー診断システム

<?php
class PosixDiagnostics {
    private static $errorCategories = [
        'permission' => [1, 13, 30],      // EPERM, EACCES, EROFS
        'not_found' => [2, 3],            // ENOENT, ESRCH
        'resource' => [12, 24, 28],       // ENOMEM, EMFILE, ENOSPC
        'invalid' => [9, 14, 22],         // EBADF, EFAULT, EINVAL
        'filesystem' => [5, 17, 20, 21],  // EIO, EEXIST, ENOTDIR, EISDIR
    ];

    public static function diagnose($errno) {
        $message = posix_strerror($errno);
        $category = self::categorizeError($errno);
        $suggestion = self::getSuggestion($errno);

        return [
            'errno' => $errno,
            'message' => $message,
            'category' => $category,
            'suggestion' => $suggestion
        ];
    }

    private static function categorizeError($errno) {
        foreach (self::$errorCategories as $category => $errors) {
            if (in_array($errno, $errors)) {
                return $category;
            }
        }
        return 'other';
    }

    private static function getSuggestion($errno) {
        $suggestions = [
            1 => '管理者権限で実行するか、sudoを使用してください',
            2 => 'ファイルまたはディレクトリのパスを確認してください',
            3 => 'プロセスが存在するか確認してください',
            5 => 'ディスクやファイルシステムの状態を確認してください',
            9 => 'ファイルディスクリプタが有効か確認してください',
            12 => 'メモリ使用量を確認し、不要なプロセスを終了してください',
            13 => 'ファイルやディレクトリのパーミッションを確認してください',
            14 => 'メモリアドレスが有効か確認してください',
            17 => 'ファイルが既に存在します。別の名前を使用するか削除してください',
            20 => 'パスがディレクトリでないファイルを指しています',
            21 => 'ディレクトリに対して無効な操作を実行しようとしています',
            22 => '引数が無効です。パラメータを確認してください',
            24 => '開いているファイル数が上限に達しています。ファイルを閉じてください',
            28 => 'ディスク容量が不足しています。空き容量を確保してください',
            30 => '読み取り専用のファイルシステムに書き込もうとしています',
        ];

        return $suggestions[$errno] ?? '詳細なエラー情報を確認してください';
    }

    public static function printDiagnosis($errno) {
        $diag = self::diagnose($errno);

        echo "=== エラー診断 ===\n";
        echo "エラー番号: {$diag['errno']}\n";
        echo "カテゴリ: {$diag['category']}\n";
        echo "メッセージ: {$diag['message']}\n";
        echo "対処法: {$diag['suggestion']}\n";
    }
}

// 使用例
if (!posix_setuid(0)) {
    $errno = posix_get_last_error();
    PosixDiagnostics::printDiagnosis($errno);
}

// 出力例:
// === エラー診断 ===
// エラー番号: 1
// カテゴリ: permission
// メッセージ: Operation not permitted
// 対処法: 管理者権限で実行するか、sudoを使用してください
?>

例3: リトライ機能付きエラーハンドラー

<?php
class RetryableOperation {
    private $maxRetries;
    private $retryDelay;
    private $retryableErrors;

    public function __construct($maxRetries = 3, $retryDelay = 1) {
        $this->maxRetries = $maxRetries;
        $this->retryDelay = $retryDelay;

        // リトライ可能なエラー番号
        $this->retryableErrors = [
            4,  // EINTR - Interrupted system call
            5,  // EIO - Input/output error
            11, // EAGAIN - Resource temporarily unavailable
        ];
    }

    public function execute(callable $operation, $operationName = 'Operation') {
        $attempt = 0;

        while ($attempt < $this->maxRetries) {
            $attempt++;

            echo "{$operationName} 実行中 (試行 {$attempt}/{$this->maxRetries})...\n";

            $result = $operation();

            if ($result !== false) {
                echo "{$operationName} 成功\n";
                return $result;
            }

            // エラー情報を取得
            $errno = posix_get_last_error();
            $error = posix_strerror($errno);

            echo "{$operationName} 失敗: {$error} (errno: {$errno})\n";

            // リトライ可能なエラーか確認
            if (!$this->isRetryable($errno)) {
                echo "リトライ不可能なエラーです\n";
                return false;
            }

            if ($attempt < $this->maxRetries) {
                echo "{$this->retryDelay}秒後にリトライします...\n";
                sleep($this->retryDelay);
            }
        }

        echo "{$operationName} 失敗: 最大試行回数に達しました\n";
        return false;
    }

    private function isRetryable($errno) {
        return in_array($errno, $this->retryableErrors);
    }
}

// 使用例
$retry = new RetryableOperation(3, 2);

// ファイルオープンをリトライ
$result = $retry->execute(function() {
    return posix_open('/tmp/lock.file', O_RDWR | O_CREAT);
}, 'ファイルオープン');

if ($result !== false) {
    echo "ファイルディスクリプタ: {$result}\n";
}
?>

例4: 包括的なPOSIX関数ラッパー

<?php
class SafePosix {
    private $logFile;
    private $throwExceptions;

    public function __construct($logFile = null, $throwExceptions = false) {
        $this->logFile = $logFile;
        $this->throwExceptions = $throwExceptions;
    }

    public function setuid($uid) {
        $result = posix_setuid($uid);

        if (!$result) {
            $this->handleError('posix_setuid', ['uid' => $uid]);
        }

        return $result;
    }

    public function setgid($gid) {
        $result = posix_setgid($gid);

        if (!$result) {
            $this->handleError('posix_setgid', ['gid' => $gid]);
        }

        return $result;
    }

    public function kill($pid, $sig) {
        $result = posix_kill($pid, $sig);

        if (!$result) {
            $this->handleError('posix_kill', ['pid' => $pid, 'signal' => $sig]);
        }

        return $result;
    }

    public function open($path, $flags, $mode = 0666) {
        $result = posix_open($path, $flags, $mode);

        if ($result === false) {
            $this->handleError('posix_open', [
                'path' => $path,
                'flags' => $flags,
                'mode' => decoct($mode)
            ]);
        }

        return $result;
    }

    public function mkfifo($pathname, $mode) {
        $result = posix_mkfifo($pathname, $mode);

        if (!$result) {
            $this->handleError('posix_mkfifo', [
                'pathname' => $pathname,
                'mode' => decoct($mode)
            ]);
        }

        return $result;
    }

    private function handleError($function, $params = []) {
        $errno = posix_get_last_error();
        $error = posix_strerror($errno);

        $errorInfo = [
            'function' => $function,
            'errno' => $errno,
            'message' => $error,
            'params' => $params,
            'timestamp' => date('Y-m-d H:i:s')
        ];

        // ログに記録
        if ($this->logFile) {
            $this->logError($errorInfo);
        }

        // 標準エラー出力に表示
        $this->printError($errorInfo);

        // 例外をスロー(オプション)
        if ($this->throwExceptions) {
            throw new RuntimeException(
                "{$function} failed: {$error} (errno: {$errno})"
            );
        }
    }

    private function logError($errorInfo) {
        $params = json_encode($errorInfo['params']);
        $log = "[{$errorInfo['timestamp']}] {$errorInfo['function']}({$params}): " .
               "{$errorInfo['message']} (errno: {$errorInfo['errno']})\n";

        file_put_contents($this->logFile, $log, FILE_APPEND);
    }

    private function printError($errorInfo) {
        fprintf(STDERR, "ERROR: %s failed\n", $errorInfo['function']);
        fprintf(STDERR, "  Message: %s\n", $errorInfo['message']);
        fprintf(STDERR, "  Errno: %d\n", $errorInfo['errno']);

        if (!empty($errorInfo['params'])) {
            fprintf(STDERR, "  Parameters:\n");
            foreach ($errorInfo['params'] as $key => $value) {
                fprintf(STDERR, "    %s: %s\n", $key, $value);
            }
        }
    }
}

// 使用例1: ログのみ
$posix = new SafePosix('/var/log/posix_errors.log');

if ($posix->setuid(0)) {
    echo "ユーザーID変更成功\n";
}

// 使用例2: 例外をスロー
$posix = new SafePosix('/var/log/posix_errors.log', true);

try {
    $fd = $posix->open('/nonexistent/file.txt', O_RDONLY);
} catch (RuntimeException $e) {
    echo "例外がキャッチされました: " . $e->getMessage() . "\n";
}
?>

例5: デバッグ用のエラートレーサー

<?php
class PosixErrorTracer {
    private $traces = [];
    private $enabled = true;

    public function enable() {
        $this->enabled = true;
    }

    public function disable() {
        $this->enabled = false;
    }

    public function trace($operation, $result) {
        if (!$this->enabled) {
            return $result;
        }

        $trace = [
            'operation' => $operation,
            'result' => $result,
            'success' => $result !== false,
            'timestamp' => microtime(true),
            'backtrace' => debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 3)
        ];

        if (!$trace['success']) {
            $errno = posix_get_last_error();
            $trace['errno'] = $errno;
            $trace['error'] = posix_strerror($errno);
        }

        $this->traces[] = $trace;

        return $result;
    }

    public function getTraces() {
        return $this->traces;
    }

    public function getErrorTraces() {
        return array_filter($this->traces, function($trace) {
            return !$trace['success'];
        });
    }

    public function printReport() {
        echo "=== POSIX操作レポート ===\n\n";

        $total = count($this->traces);
        $errors = count($this->getErrorTraces());
        $success = $total - $errors;

        echo "総操作数: {$total}\n";
        echo "成功: {$success}\n";
        echo "失敗: {$errors}\n\n";

        if ($errors > 0) {
            echo "=== エラー詳細 ===\n";
            foreach ($this->getErrorTraces() as $i => $trace) {
                echo "\nエラー #" . ($i + 1) . ":\n";
                echo "  操作: {$trace['operation']}\n";
                echo "  エラー: {$trace['error']} (errno: {$trace['errno']})\n";
                echo "  発生場所: {$trace['backtrace'][1]['file']}:" .
                     "{$trace['backtrace'][1]['line']}\n";
            }
        }
    }

    public function exportToJson($filename) {
        $export = [
            'total_operations' => count($this->traces),
            'errors' => count($this->getErrorTraces()),
            'traces' => $this->traces
        ];

        file_put_contents($filename, json_encode($export, JSON_PRETTY_PRINT));
    }

    public function clear() {
        $this->traces = [];
    }
}

// 使用例
$tracer = new PosixErrorTracer();

// 操作をトレース
$tracer->trace('posix_setuid(0)', posix_setuid(0));
$tracer->trace('posix_setgid(0)', posix_setgid(0));
$tracer->trace('posix_kill(99999, 15)', posix_kill(99999, 15));

// レポートを表示
$tracer->printReport();

// JSONにエクスポート
$tracer->exportToJson('/tmp/posix_trace.json');
?>

エラーメッセージの国際化

<?php
class LocalizedPosixError {
    private $locale;
    private $translations;

    public function __construct($locale = 'ja_JP') {
        $this->locale = $locale;
        $this->loadTranslations();
    }

    private function loadTranslations() {
        // 日本語の翻訳
        $this->translations['ja_JP'] = [
            1 => '操作が許可されていません',
            2 => 'ファイルまたはディレクトリが見つかりません',
            3 => 'プロセスが見つかりません',
            4 => 'システムコールが中断されました',
            5 => '入出力エラーが発生しました',
            9 => '無効なファイルディスクリプタです',
            12 => 'メモリを割り当てられません',
            13 => 'アクセスが拒否されました',
            14 => '無効なアドレスです',
            17 => 'ファイルが既に存在します',
            20 => 'ディレクトリではありません',
            21 => 'ディレクトリです',
            22 => '無効な引数です',
            24 => '開いているファイルが多すぎます',
            28 => 'ディスク容量が不足しています',
            30 => '読み取り専用のファイルシステムです',
        ];
    }

    public function getErrorMessage($errno) {
        // 英語のメッセージを取得
        $englishMessage = posix_strerror($errno);

        // ローカライズされたメッセージがあれば使用
        if (isset($this->translations[$this->locale][$errno])) {
            return $this->translations[$this->locale][$errno];
        }

        return $englishMessage;
    }

    public function formatError($errno, $includeEnglish = true) {
        $localMessage = $this->getErrorMessage($errno);

        if ($includeEnglish && isset($this->translations[$this->locale][$errno])) {
            $englishMessage = posix_strerror($errno);
            return "{$localMessage} ({$englishMessage})";
        }

        return $localMessage;
    }
}

// 使用例
$error = new LocalizedPosixError('ja_JP');

if (!posix_setuid(0)) {
    $errno = posix_get_last_error();
    echo "エラー: " . $error->formatError($errno) . "\n";
}

// 出力例:
// エラー: 操作が許可されていません (Operation not permitted)
?>

よくある使用パターン

パターン1: 簡潔なエラー出力

<?php
function posix_error() {
    $errno = posix_get_last_error();
    return posix_strerror($errno) . " (errno: {$errno})";
}

// 使用例
if (!posix_setuid(1000)) {
    die("UID変更失敗: " . posix_error() . "\n");
}
?>

パターン2: デバッグ情報付きエラー

<?php
function posix_debug_error($operation, $context = []) {
    $errno = posix_get_last_error();
    $error = posix_strerror($errno);

    $debug = [
        'operation' => $operation,
        'errno' => $errno,
        'error' => $error,
        'context' => $context,
        'file' => debug_backtrace()[0]['file'],
        'line' => debug_backtrace()[0]['line'],
    ];

    error_log(json_encode($debug));

    return $error;
}

// 使用例
$uid = 1000;
if (!posix_setuid($uid)) {
    echo posix_debug_error('posix_setuid', ['uid' => $uid]) . "\n";
}
?>

パターン3: アサーション付きエラー

<?php
function posix_assert($result, $operation) {
    if ($result === false) {
        $errno = posix_get_last_error();
        $error = posix_strerror($errno);

        throw new AssertionError(
            "{$operation} failed: {$error} (errno: {$errno})"
        );
    }

    return $result;
}

// 使用例
try {
    posix_assert(posix_setuid(0), 'posix_setuid');
    posix_assert(posix_setgid(0), 'posix_setgid');
} catch (AssertionError $e) {
    echo "アサーション失敗: " . $e->getMessage() . "\n";
}
?>

まとめ

posix_strerrorは、POSIX関数のエラーを理解するための必須のデバッグツールです。

重要なポイント:

  • エラー番号を人間が読めるメッセージに変換
  • posix_get_last_error()と組み合わせて使用
  • ロギングとデバッグに不可欠
  • 多言語対応も可能

主な用途:

  • エラーメッセージの表示
  • ログファイルへの記録
  • デバッグ情報の収集
  • ユーザーへのフィードバック

ベストプラクティス:

  • 常にエラーチェックを行う
  • エラーメッセージを適切にログに記録
  • ユーザーフレンドリーなエラー表示を心がける
  • 開発環境では詳細なエラー情報を出力

POSIX関数を使用する際は、必ずposix_strerrorでエラーを確認する習慣をつけましょう。適切なエラーハンドリングにより、問題の早期発見と解決が可能になります!


関連記事:

  • posix_get_last_error(): 最後のエラー番号を取得
  • error_get_last(): PHPの最後のエラーを取得
  • error_log(): エラーメッセージをログに記録
  • その他のPOSIX関数(setuid, setgid, kill, etc.)
タイトルとURLをコピーしました