こんにちは!今回は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.)
