[PHP]posix_mknod関数とは?特殊ファイルを作成する方法を徹底解説

PHP

こんにちは!今回はPHPのPOSIX関数の中から、posix_mknod関数について詳しく解説していきます。通常のファイルだけでなく、特殊なファイルタイプを作成できる上級者向けの関数を、実践的なサンプルコードと共にお伝えします。

posix_mknod関数とは?

posix_mknodは、**特殊ファイル(special files)**を作成する関数です。通常のファイルやディレクトリだけでなく、デバイスファイル、名前付きパイプ(FIFO)などを作成できます。

基本構文

posix_mknod(string $filename, int $flags, int $major = 0, int $minor = 0): bool
  • 第1引数: ファイルパス
  • 第2引数: モードとファイルタイプのフラグ
  • 第3引数: メジャーデバイス番号(デバイスファイルの場合)
  • 第4引数: マイナーデバイス番号(デバイスファイルの場合)
  • 戻り値: 成功時にtrue、失敗時にfalse

ファイルタイプとフラグ

主要なファイルタイプ定数

Unix/Linuxシステムには様々なファイルタイプが存在します。

<?php
// ファイルタイプ定数
// S_IFREG  - 通常のファイル
// S_IFCHR  - キャラクタデバイス
// S_IFBLK  - ブロックデバイス
// S_IFIFO  - FIFO (名前付きパイプ)
// S_IFSOCK - ソケット

// パーミッションビット
// 0644 - rw-r--r-- (ファイル)
// 0755 - rwxr-xr-x (実行可能)
// 0666 - rw-rw-rw- (全ユーザー読み書き可能)
?>

フラグの組み合わせ

<?php
// フラグはファイルタイプとパーミッションを組み合わせる
$mode = S_IFIFO | 0666;  // FIFO、rw-rw-rw-
$mode = S_IFCHR | 0660;  // キャラクタデバイス、rw-rw----
?>

実践的な使い方

例1: 基本的なFIFOの作成

<?php
// posix_mkfifoと同等の機能
$fifo_path = '/tmp/myfifo';

// FIFOを作成 (S_IFIFO | パーミッション)
$result = posix_mknod($fifo_path, S_IFIFO | 0666);

if ($result) {
    echo "FIFOを作成しました: {$fifo_path}\n";
    
    // ファイルタイプを確認
    $type = filetype($fifo_path);
    echo "ファイルタイプ: {$type}\n"; // "fifo"
    
    // パーミッションを確認
    $perms = fileperms($fifo_path);
    echo "パーミッション: " . decoct($perms & 0777) . "\n";
    
    // クリーンアップ
    unlink($fifo_path);
} else {
    $error = posix_get_last_error();
    echo "エラー: " . posix_strerror($error) . "\n";
}
?>

例2: ファイルタイプ判定と作成

<?php
class SpecialFileManager {
    public function createFIFO($path, $perms = 0666) {
        if (file_exists($path)) {
            throw new Exception("ファイルは既に存在します: {$path}");
        }
        
        $mode = S_IFIFO | $perms;
        
        if (!posix_mknod($path, $mode)) {
            $error = posix_strerror(posix_get_last_error());
            throw new Exception("FIFO作成失敗: {$error}");
        }
        
        return true;
    }
    
    public function createRegularFile($path, $perms = 0644) {
        // 通常ファイルの作成
        // 注意: posix_mknodではS_IFREGをサポートしない実装が多い
        // 通常はfopen/touchを使用する
        if (touch($path)) {
            chmod($path, $perms);
            return true;
        }
        return false;
    }
    
    public function getFileInfo($path) {
        if (!file_exists($path)) {
            throw new Exception("ファイルが見つかりません: {$path}");
        }
        
        $stat = stat($path);
        $mode = $stat['mode'];
        
        // ファイルタイプを判定
        $type = 'unknown';
        if (($mode & S_IFIFO) === S_IFIFO) {
            $type = 'FIFO';
        } elseif (($mode & S_IFCHR) === S_IFCHR) {
            $type = 'Character Device';
        } elseif (($mode & S_IFBLK) === S_IFBLK) {
            $type = 'Block Device';
        } elseif (($mode & S_IFSOCK) === S_IFSOCK) {
            $type = 'Socket';
        } elseif (is_file($path)) {
            $type = 'Regular File';
        } elseif (is_dir($path)) {
            $type = 'Directory';
        } elseif (is_link($path)) {
            $type = 'Symbolic Link';
        }
        
        return [
            'path' => $path,
            'type' => $type,
            'mode' => decoct($mode & 0777),
            'size' => $stat['size'],
            'uid' => $stat['uid'],
            'gid' => $stat['gid'],
        ];
    }
    
    public function remove($path) {
        if (!file_exists($path)) {
            return true;
        }
        
        return unlink($path);
    }
}

// 使用例
$manager = new SpecialFileManager();

try {
    // FIFOを作成
    $manager->createFIFO('/tmp/test_fifo', 0666);
    
    // ファイル情報を取得
    $info = $manager->getFileInfo('/tmp/test_fifo');
    
    echo "=== ファイル情報 ===\n";
    foreach ($info as $key => $value) {
        echo "{$key}: {$value}\n";
    }
    
    // 削除
    $manager->remove('/tmp/test_fifo');
    echo "\nファイルを削除しました\n";
    
} catch (Exception $e) {
    echo "エラー: " . $e->getMessage() . "\n";
}
?>

例3: 複数のFIFOを管理するシステム

<?php
class FIFOPool {
    private $fifos = [];
    private $base_path;
    
    public function __construct($base_path = '/tmp/fifo_pool') {
        $this->base_path = $base_path;
        
        // ベースディレクトリを作成
        if (!is_dir($base_path)) {
            mkdir($base_path, 0755, true);
        }
    }
    
    public function create($name, $perms = 0666) {
        $path = $this->base_path . '/' . $name;
        
        if (isset($this->fifos[$name])) {
            throw new Exception("FIFO '{$name}' は既に存在します");
        }
        
        if (file_exists($path)) {
            throw new Exception("パスは既に使用されています: {$path}");
        }
        
        $mode = S_IFIFO | $perms;
        
        if (!posix_mknod($path, $mode)) {
            $error = posix_strerror(posix_get_last_error());
            throw new Exception("FIFO作成失敗: {$error}");
        }
        
        $this->fifos[$name] = [
            'path' => $path,
            'created_at' => time(),
            'readers' => [],
            'writers' => []
        ];
        
        echo "FIFO '{$name}' を作成しました: {$path}\n";
        return $path;
    }
    
    public function get($name) {
        if (!isset($this->fifos[$name])) {
            throw new Exception("FIFO '{$name}' が見つかりません");
        }
        
        return $this->fifos[$name]['path'];
    }
    
    public function openReader($name) {
        $path = $this->get($name);
        
        $fp = fopen($path, 'r');
        if ($fp) {
            $this->fifos[$name]['readers'][] = $fp;
            return $fp;
        }
        
        return false;
    }
    
    public function openWriter($name) {
        $path = $this->get($name);
        
        $fp = fopen($path, 'w');
        if ($fp) {
            $this->fifos[$name]['writers'][] = $fp;
            return $fp;
        }
        
        return false;
    }
    
    public function remove($name) {
        if (!isset($this->fifos[$name])) {
            return false;
        }
        
        $fifo_info = $this->fifos[$name];
        
        // 開いているストリームを閉じる
        foreach ($fifo_info['readers'] as $fp) {
            if (is_resource($fp)) {
                fclose($fp);
            }
        }
        
        foreach ($fifo_info['writers'] as $fp) {
            if (is_resource($fp)) {
                fclose($fp);
            }
        }
        
        // FIFOを削除
        if (file_exists($fifo_info['path'])) {
            unlink($fifo_info['path']);
        }
        
        unset($this->fifos[$name]);
        echo "FIFO '{$name}' を削除しました\n";
        return true;
    }
    
    public function removeAll() {
        foreach (array_keys($this->fifos) as $name) {
            $this->remove($name);
        }
    }
    
    public function list() {
        $result = [];
        
        foreach ($this->fifos as $name => $info) {
            $result[$name] = [
                'path' => $info['path'],
                'created_at' => date('Y-m-d H:i:s', $info['created_at']),
                'readers' => count($info['readers']),
                'writers' => count($info['writers']),
            ];
        }
        
        return $result;
    }
    
    public function __destruct() {
        $this->removeAll();
    }
}

// 使用例
$pool = new FIFOPool();

// 複数のFIFOを作成
$pool->create('input');
$pool->create('output');
$pool->create('control', 0660);

// FIFOのリストを表示
echo "\n=== FIFO一覧 ===\n";
print_r($pool->list());

// データの送受信
$pid = pcntl_fork();

if ($pid == 0) {
    // 子プロセス: データを送信
    sleep(1);
    $writer = $pool->openWriter('input');
    if ($writer) {
        fwrite($writer, "Hello from child process\n");
        fclose($writer);
    }
    exit(0);
} else {
    // 親プロセス: データを受信
    $reader = $pool->openReader('input');
    if ($reader) {
        $data = fgets($reader);
        echo "\n受信したデータ: {$data}";
        fclose($reader);
    }
    
    pcntl_wait($status);
}

// クリーンアップ
$pool->removeAll();
?>

例4: パーミッションとumaskの管理

<?php
class SecureFileCreator {
    private $old_umask;
    
    public function __construct() {
        // 現在のumaskを保存
        $this->old_umask = umask();
    }
    
    public function createWithExactPermissions($path, $type, $perms) {
        // umaskを0に設定して、指定したパーミッションを正確に適用
        umask(0);
        
        $mode = $type | $perms;
        $result = posix_mknod($path, $mode);
        
        // umaskを元に戻す
        umask($this->old_umask);
        
        if ($result) {
            echo "ファイルを作成: {$path}\n";
            echo "パーミッション: " . decoct($perms) . "\n";
            
            // 実際のパーミッションを確認
            $actual = fileperms($path) & 0777;
            echo "実際のパーミッション: " . decoct($actual) . "\n";
            
            return true;
        }
        
        return false;
    }
    
    public function createPrivateFIFO($path) {
        // 所有者のみアクセス可能なFIFO
        return $this->createWithExactPermissions($path, S_IFIFO, 0600);
    }
    
    public function createGroupFIFO($path) {
        // 所有者とグループがアクセス可能なFIFO
        return $this->createWithExactPermissions($path, S_IFIFO, 0660);
    }
    
    public function createPublicFIFO($path) {
        // 全ユーザーがアクセス可能なFIFO
        return $this->createWithExactPermissions($path, S_IFIFO, 0666);
    }
    
    public function demonstrateUmaskEffect() {
        $test_path = '/tmp/umask_test_';
        $counter = 0;
        
        echo "\n=== umaskの影響を確認 ===\n";
        
        // umaskなしで作成
        echo "\n1. umask=0で作成\n";
        umask(0);
        $path1 = $test_path . $counter++;
        posix_mknod($path1, S_IFIFO | 0666);
        echo "   パーミッション: " . decoct(fileperms($path1) & 0777) . "\n";
        unlink($path1);
        
        // umask=022で作成
        echo "\n2. umask=022で作成\n";
        umask(0022);
        $path2 = $test_path . $counter++;
        posix_mknod($path2, S_IFIFO | 0666);
        echo "   パーミッション: " . decoct(fileperms($path2) & 0777) . "\n";
        echo "   (0666 & ~022 = 0644)\n";
        unlink($path2);
        
        // umask=077で作成
        echo "\n3. umask=077で作成\n";
        umask(0077);
        $path3 = $test_path . $counter++;
        posix_mknod($path3, S_IFIFO | 0666);
        echo "   パーミッション: " . decoct(fileperms($path3) & 0777) . "\n";
        echo "   (0666 & ~077 = 0600)\n";
        unlink($path3);
        
        // umaskを元に戻す
        umask($this->old_umask);
    }
    
    public function __destruct() {
        // umaskを元に戻す
        umask($this->old_umask);
    }
}

// 使用例
$creator = new SecureFileCreator();

// 異なるセキュリティレベルのFIFOを作成
echo "=== セキュアなFIFO作成 ===\n";

$creator->createPrivateFIFO('/tmp/private.fifo');
echo "\n";

$creator->createGroupFIFO('/tmp/group.fifo');
echo "\n";

$creator->createPublicFIFO('/tmp/public.fifo');
echo "\n";

// umaskの影響を実演
$creator->demonstrateUmaskEffect();

// クリーンアップ
unlink('/tmp/private.fifo');
unlink('/tmp/group.fifo');
unlink('/tmp/public.fifo');
?>

例5: デバイスファイルの情報取得(参考)

<?php
// 注意: デバイスファイルの作成にはroot権限が必要
// この例は情報取得のみを行います

class DeviceFileInspector {
    public function inspect($path) {
        if (!file_exists($path)) {
            throw new Exception("ファイルが見つかりません: {$path}");
        }
        
        $stat = stat($path);
        $mode = $stat['mode'];
        
        $info = [
            'path' => $path,
            'type' => $this->getFileType($mode),
            'mode' => sprintf('%04o', $mode & 0777),
            'uid' => $stat['uid'],
            'gid' => $stat['gid'],
            'size' => $stat['size'],
        ];
        
        // デバイスファイルの場合、デバイス番号を取得
        if ($this->isDeviceFile($mode)) {
            $info['device'] = $stat['rdev'];
            $info['major'] = $this->getMajor($stat['rdev']);
            $info['minor'] = $this->getMinor($stat['rdev']);
        }
        
        return $info;
    }
    
    private function getFileType($mode) {
        if (($mode & 0170000) === 0010000) return 'FIFO';
        if (($mode & 0170000) === 0020000) return 'Character Device';
        if (($mode & 0170000) === 0060000) return 'Block Device';
        if (($mode & 0170000) === 0140000) return 'Socket';
        if (($mode & 0170000) === 0100000) return 'Regular File';
        if (($mode & 0170000) === 0040000) return 'Directory';
        if (($mode & 0170000) === 0120000) return 'Symbolic Link';
        return 'Unknown';
    }
    
    private function isDeviceFile($mode) {
        $type = $mode & 0170000;
        return $type === 0020000 || $type === 0060000;
    }
    
    private function getMajor($dev) {
        return ($dev >> 8) & 0xff;
    }
    
    private function getMinor($dev) {
        return $dev & 0xff;
    }
    
    public function listSystemDevices($dir = '/dev') {
        $devices = [];
        
        $files = scandir($dir);
        foreach ($files as $file) {
            if ($file === '.' || $file === '..') {
                continue;
            }
            
            $path = $dir . '/' . $file;
            
            if (is_dir($path)) {
                continue;
            }
            
            try {
                $info = $this->inspect($path);
                if ($info['type'] === 'Character Device' || 
                    $info['type'] === 'Block Device') {
                    $devices[] = $info;
                }
            } catch (Exception $e) {
                // アクセス権限がない場合はスキップ
                continue;
            }
            
            // 最初の10個だけ表示(例として)
            if (count($devices) >= 10) {
                break;
            }
        }
        
        return $devices;
    }
}

// 使用例
$inspector = new DeviceFileInspector();

// FIFOを作成して検査
$test_fifo = '/tmp/inspect_test.fifo';
posix_mknod($test_fifo, S_IFIFO | 0644);

echo "=== FIFO情報 ===\n";
$info = $inspector->inspect($test_fifo);
foreach ($info as $key => $value) {
    echo "{$key}: {$value}\n";
}

unlink($test_fifo);

// システムデバイスの情報を取得(読み取り権限がある場合)
echo "\n=== システムデバイス(抜粋) ===\n";
try {
    $devices = $inspector->listSystemDevices('/dev');
    foreach ($devices as $device) {
        echo "\n{$device['path']}:\n";
        echo "  Type: {$device['type']}\n";
        if (isset($device['major'])) {
            echo "  Major: {$device['major']}, Minor: {$device['minor']}\n";
        }
    }
} catch (Exception $e) {
    echo "デバイス情報の取得に失敗: " . $e->getMessage() . "\n";
}
?>

注意点とトラブルシューティング

root権限が必要な場合

<?php
// デバイスファイルの作成にはroot権限が必要
if (posix_getuid() !== 0) {
    die("警告: デバイスファイルの作成にはroot権限が必要です\n");
}

// キャラクタデバイスを作成(例: /dev/null のような)
// 注意: 実際のシステムデバイスは作成しないでください
$result = posix_mknod('/tmp/test_device', S_IFCHR | 0666, 1, 3);

if (!$result) {
    $error = posix_strerror(posix_get_last_error());
    echo "エラー: {$error}\n";
}
?>

posix_mkfifoとの使い分け

<?php
// FIFOを作成する場合、以下は同等

// 方法1: posix_mkfifo(シンプル)
posix_mkfifo('/tmp/fifo1', 0666);

// 方法2: posix_mknod(より汎用的)
posix_mknod('/tmp/fifo2', S_IFIFO | 0666);

// FIFOのみを作成する場合はposix_mkfifoが推奨
// 他のファイルタイプも扱う場合はposix_mknodを使用
?>

エラーハンドリング

<?php
function safeCreateSpecialFile($path, $mode, $major = 0, $minor = 0) {
    // 既存ファイルチェック
    if (file_exists($path)) {
        throw new Exception("ファイルは既に存在します: {$path}");
    }
    
    // ディレクトリの存在確認
    $dir = dirname($path);
    if (!is_dir($dir)) {
        throw new Exception("ディレクトリが存在しません: {$dir}");
    }
    
    // 書き込み権限チェック
    if (!is_writable($dir)) {
        throw new Exception("ディレクトリに書き込み権限がありません: {$dir}");
    }
    
    // ファイル作成
    $result = posix_mknod($path, $mode, $major, $minor);
    
    if (!$result) {
        $error_num = posix_get_last_error();
        $error_msg = posix_strerror($error_num);
        throw new Exception("ファイル作成失敗: {$error_msg} (errno: {$error_num})");
    }
    
    return true;
}

// 使用例
try {
    safeCreateSpecialFile('/tmp/safe_fifo', S_IFIFO | 0666);
    echo "ファイルを安全に作成しました\n";
} catch (Exception $e) {
    echo "エラー: " . $e->getMessage() . "\n";
}
?>

Windows環境での制限

<?php
if (!function_exists('posix_mknod')) {
    die("posix_mknodはこの環境では使用できません\n" .
        "Unix/Linux/macOS環境で実行してください\n");
}

// プラットフォーム固有の機能をラップ
class CrossPlatformFileCreator {
    public function createFIFO($path, $perms = 0666) {
        if (function_exists('posix_mknod')) {
            return posix_mknod($path, S_IFIFO | $perms);
        } elseif (function_exists('posix_mkfifo')) {
            return posix_mkfifo($path, $perms);
        } else {
            throw new Exception("この環境ではFIFOを作成できません");
        }
    }
}
?>

関連する便利な関数

ファイルシステム関数との組み合わせ

<?php
// ファイル情報の取得
$path = '/tmp/test.fifo';
posix_mknod($path, S_IFIFO | 0666);

// stat()で詳細情報を取得
$stat = stat($path);
echo "inode: {$stat['ino']}\n";
echo "mode: " . decoct($stat['mode']) . "\n";
echo "links: {$stat['nlink']}\n";

// filetype()でタイプを確認
echo "type: " . filetype($path) . "\n"; // "fifo"

// is_*系関数
var_dump(is_file($path));  // false
var_dump(is_dir($path));   // false
var_dump(is_link($path));  // false

// パーミッション操作
chmod($path, 0644);
echo "新しいパーミッション: " . decoct(fileperms($path) & 0777) . "\n";

unlink($path);
?>

主要な関連関数

  • posix_mkfifo(): FIFOのみを作成(簡易版)
  • filetype(): ファイルタイプを取得
  • stat(): ファイルの詳細情報を取得
  • fileperms(): パーミッションを取得
  • chmod(): パーミッションを変更
  • unlink(): ファイルを削除

まとめ

posix_mknod関数は以下の特徴があります。

✅ 特殊ファイルを作成する汎用関数 ✅ FIFO、デバイスファイルなどに対応 ✅ パーミッションを細かく制御可能 ✅ posix_mkfifoより汎用的 ✅ システムプログラミングに必須

posix_mknodは低レベルのシステムプログラミングで使用される関数です。通常のアプリケーション開発ではFIFO作成にposix_mkfifoを使用することが多いですが、より高度な制御が必要な場合にposix_mknodが役立ちます。

ベストプラクティス

  1. FIFOには専用関数: 単純にFIFOを作るならposix_mkfifo()
  2. 権限の適切な設定: umaskを考慮してパーミッション設定
  3. エラーハンドリング: 失敗時の詳細なエラー情報を取得
  4. クリーンアップ: 使用後は必ずunlink()で削除
  5. root権限の注意: デバイスファイル作成時の権限管理

セキュリティの考慮事項

  • パーミッション: 最小限のアクセス権を設定
  • パスの検証: ユーザー入力をパスに使用する場合は検証
  • 一時ファイル: /tmpディレクトリ使用時の競合状態に注意
  • 所有権: 適切なユーザー/グループ権限の設定

安全なファイル操作を! この記事が役に立ったら、ぜひシェアしてください。PHPのシステムプログラミングについて、他にも知りたいことがあればコメントで教えてくださいね。

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