はじめに
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プログラミングを実現しましょう!
参考リンク
この記事が役に立ったら、ぜひシェアしてください!
