PHP umask() 函数

定义和用法

umask() 函数用于设置或获取当前进程的文件权限掩码(umask)。权限掩码用于控制在创建新文件或目录时应用的默认权限。

umask 是一个八进制数,表示要从默认权限中"减去"的权限。它通过屏蔽(mask)某些权限位来工作,确保新创建的文件和目录具有安全的默认权限。

注意: umask 只影响当前进程和该进程创建的子进程。它不会影响系统其他进程或已经存在的文件。

语法

umask ([ int $mask ] ) : int

参数说明:

  • $mask:新的权限掩码(可选参数)
  • 如果不提供参数,则返回当前的权限掩码
  • 如果提供参数,则设置新的权限掩码并返回旧的权限掩码

参数详解

参数 描述
mask

新的权限掩码。可选参数。

  • 八进制数,通常以0开头(如 022)
  • 如果不指定,函数返回当前umask而不修改
  • 如果指定,设置新的umask并返回旧的umask
  • 有效的umask值范围是0-0777(八进制)
  • 常见的值:022、002、027、077等

返回值

  • 不提供参数时:返回当前的权限掩码
  • 提供参数时:设置新的权限掩码,并返回旧的权限掩码
注意: umask() 函数没有错误返回值。如果传入无效的参数,可能会产生不可预测的结果。

工作原理

权限计算规则

umask 通过从默认权限中屏蔽某些位来工作:

项目 默认权限 实际权限计算
普通文件 0666 (rw-rw-rw-) 0666 & ~umask
目录 0777 (rwxrwxrwx) 0777 & ~umask
权限位说明
八进制权限表示:
0 = --- (无权限)
1 = --x (执行)
2 = -w- (写)
3 = -wx (写+执行)
4 = r-- (读)
5 = r-x (读+执行)
6 = rw- (读+写)
7 = rwx (读+写+执行)

三位数分别表示:
第一位: 所有者权限
第二位: 组权限
第三位: 其他用户权限

例如:
755 = rwxr-xr-x
644 = rw-r--r--
755 = 所有者:读+写+执行, 组:读+执行, 其他:读+执行
计算示例
<?php
// 示例: umask 022 时的权限计算

// 默认文件权限: 0666 (rw-rw-rw-)
// 默认目录权限: 0777 (rwxrwxrwx)

// umask 022 (八进制) = 二进制 000010010
// ~022 = 二进制 111101101 (八进制 755)

// 文件实际权限: 0666 & ~022 = 0644 (rw-r--r--)
// 目录实际权限: 0777 & ~022 = 0755 (rwxr-xr-x)

echo "umask 022 时:<br>";
echo "新文件权限: 0644 (rw-r--r--)<br>";
echo "新目录权限: 0755 (rwxr-xr-x)<br>";

// 另一个示例: umask 077
// ~077 = 0700

// 文件实际权限: 0666 & ~077 = 0600 (rw-------)
// 目录实际权限: 0777 & ~077 = 0700 (rwx------)
?>

示例

示例 1:获取当前 umask

查看当前进程的 umask 值:

<?php
// 获取当前 umask
$current_umask = umask();

// 以八进制显示
echo "当前 umask: " . sprintf("%04o", $current_umask) . "<br>";
echo "十进制值: " . $current_umask . "<br>";

// 解释umask的含义
function explain_umask($umask) {
    $file_perm = 0666 & ~$umask;
    $dir_perm = 0777 & ~$umask;

    echo "umask " . sprintf("%03o", $umask) . " 意味着:<br>";
    echo "新文件权限: " . sprintf("%03o", $file_perm) . " (" . decoct($file_perm) . ")<br>";
    echo "新目录权限: " . sprintf("%03o", $dir_perm) . " (" . decoct($dir_perm) . ")<br>";
}

explain_umask($current_umask);
?>
示例 2:设置新的 umask

修改 umask 并观察效果:

<?php
// 保存旧的 umask
$old_umask = umask();

echo "旧的 umask: " . sprintf("%04o", $old_umask) . "<br>";

// 设置新的 umask 为 027 (更严格的权限)
$previous_umask = umask(027);

echo "设置时的 previous umask: " . sprintf("%04o", $previous_umask) . "<br>";
echo "新的 umask: " . sprintf("%04o", umask()) . "<br>";

// 创建文件和目录来验证
$test_file = "test_umask_file.txt";
$test_dir = "test_umask_dir";

// 创建文件
touch($test_file);
// 创建目录
mkdir($test_dir);

// 获取实际权限
$file_perms = fileperms($test_file);
$dir_perms = fileperms($test_dir);

// 显示权限(去掉前导的0)
echo "<br>创建的文件权限: " . substr(sprintf('%o', $file_perms), -4) . "<br>";
echo "创建的目录权限: " . substr(sprintf('%o', $dir_perms), -4) . "<br>";

// 清理
unlink($test_file);
rmdir($test_dir);

// 恢复原来的 umask
umask($old_umask);
echo "<br>恢复后的 umask: " . sprintf("%04o", umask());
?>
示例 3:不同 umask 值的比较

比较不同 umask 值对权限的影响:

<?php
function test_umask_values() {
    $umask_values = [0, 022, 027, 077, 002];
    $results = [];

    foreach ($umask_values as $umask) {
        // 设置 umask
        $old_umask = umask($umask);

        // 计算预期的权限
        $expected_file = 0666 & ~$umask;
        $expected_dir = 0777 & ~$umask;

        // 创建测试文件
        $test_file = "test_file_" . $umask . ".txt";
        $test_dir = "test_dir_" . $umask;

        touch($test_file);
        mkdir($test_dir);

        // 获取实际权限
        $actual_file = fileperms($test_file) & 0777;
        $actual_dir = fileperms($test_dir) & 0777;

        // 清理
        unlink($test_file);
        rmdir($test_dir);

        // 恢复原来的 umask
        umask($old_umask);

        $results[] = [
            'umask' => sprintf("%03o", $umask),
            'expected_file' => sprintf("%03o", $expected_file),
            'actual_file' => sprintf("%03o", $actual_file),
            'expected_dir' => sprintf("%03o", $expected_dir),
            'actual_dir' => sprintf("%03o", $actual_dir),
            'file_match' => ($expected_file === $actual_file),
            'dir_match' => ($expected_dir === $actual_dir)
        ];
    }

    return $results;
}

echo "不同 umask 值的效果比较:<br><br>";

$results = test_umask_values();

echo "<table border='1' cellpadding='5'>";
echo "<tr><th>umask</th><th>预期文件权限</th><th>实际文件权限</th><th>预期目录权限</th><th>实际目录权限</th><th>文件匹配</th><th>目录匹配</th></tr>";

foreach ($results as $row) {
    echo "<tr>";
    echo "<td>" . $row['umask'] . "</td>";
    echo "<td>" . $row['expected_file'] . "</td>";
    echo "<td>" . $row['actual_file'] . "</td>";
    echo "<td>" . $row['expected_dir'] . "</td>";
    echo "<td>" . $row['actual_dir'] . "</td>";
    echo "<td>" . ($row['file_match'] ? '✓' : '✗') . "</td>";
    echo "<td>" . ($row['dir_match'] ? '✓' : '✗') . "</td>";
    echo "</tr>";
}

echo "</table>";
?>
示例 4:安全的文件创建

使用 umask 确保安全地创建文件:

<?php
class SecureFileCreator {
    private $original_umask;

    public function __construct() {
        // 保存当前的 umask
        $this->original_umask = umask();
    }

    public function createSecureFile($filename, $content = '', $directory = '.') {
        // 设置安全的 umask (只允许所有者读写)
        umask(077);

        $full_path = $directory . '/' . $filename;

        try {
            // 创建文件
            if (file_put_contents($full_path, $content) === false) {
                throw new Exception("无法创建文件: $filename");
            }

            // 验证权限
            $perms = fileperms($full_path) & 0777;
            $expected_perms = 0600; // rw-------

            if ($perms !== $expected_perms) {
                // 如果权限不正确,尝试修复
                chmod($full_path, $expected_perms);
                $perms = fileperms($full_path) & 0777;

                if ($perms !== $expected_perms) {
                    throw new Exception("无法设置正确的文件权限");
                }
            }

            echo "安全创建文件: $full_path<br>";
            echo "文件权限: " . substr(sprintf('%o', $perms), -4) . "<br>";

            return $full_path;
        } catch (Exception $e) {
            // 清理
            if (file_exists($full_path)) {
                unlink($full_path);
            }
            throw $e;
        } finally {
            // 恢复原来的 umask
            umask($this->original_umask);
        }
    }

    public function __destruct() {
        // 确保恢复 umask
        umask($this->original_umask);
    }
}

// 使用示例
try {
    $creator = new SecureFileCreator();
    $file = $creator->createSecureFile('secret_data.txt', '这是敏感数据');

    // 检查文件权限
    $perms = fileperms($file) & 0777;
    echo "最终文件权限: " . substr(sprintf('%o', $perms), -4) . "<br>";

    // 清理
    unlink($file);
} catch (Exception $e) {
    echo "错误: " . $e->getMessage();
}
?>
示例 5:临时修改 umask

使用临时修改 umask 的模式:

<?php
// 使用 withUmask 模式临时修改 umask
function withUmask($new_umask, $callback) {
    $old_umask = umask();

    try {
        // 设置新的 umask
        umask($new_umask);

        // 执行回调函数
        return $callback();
    } finally {
        // 恢复原来的 umask
        umask($old_umask);
    }
}

// 使用示例
$result = withUmask(027, function() {
    // 在这个函数中,umask 是 027

    $files = ['file1.txt', 'file2.txt', 'file3.txt'];
    $created = [];

    foreach ($files as $file) {
        touch($file);
        $perms = fileperms($file) & 0777;
        echo "创建 $file, 权限: " . substr(sprintf('%o', $perms), -4) . "<br>";
        $created[] = $file;
    }

    // 返回创建的文件列表
    return $created;
});

echo "创建的文件数量: " . count($result) . "<br>";

// 验证 umask 已恢复
$current_umask = umask();
echo "恢复后的 umask: " . sprintf("%04o", $current_umask) . "<br>";

// 清理文件
foreach ($result as $file) {
    if (file_exists($file)) {
        unlink($file);
    }
}
?>

常见 umask 值及其含义

umask 值 文件权限 目录权限 描述和使用场景
022 644 (rw-r--r--) 755 (rwxr-xr-x) 最常用的设置。所有者可读写,其他用户只读。适用于大多数Web应用。
027 640 (rw-r-----) 750 (rwxr-x---) 更安全。所有者可读写,组用户只读,其他用户无权限。适用于共享主机。
077 600 (rw-------) 700 (rwx------) 最严格。只有所有者有权限。适用于敏感数据、配置文件。
002 664 (rw-rw-r--) 775 (rwxrwxr-x) 宽松的设置。所有者和组用户可读写。适用于开发环境或协作项目。
000 666 (rw-rw-rw-) 777 (rwxrwxrwx) 最宽松。所有用户都有完全权限。一般不推荐使用。
033 644 (rw-r--r--) 744 (rwxr--r--) 特殊设置。文件与022相同,但目录的组和其他用户没有执行权限。
推荐设置
  • 生产环境: 推荐使用 027 或 022
  • 开发环境: 可以使用 002 方便协作
  • 敏感数据: 使用 077
  • Web服务器: 通常使用 022 或 027
  • 共享主机: 建议使用 027

注意事项和最佳实践

重要提示
  • 作用范围: umask 只影响当前进程和其创建的子进程
  • 继承性: 子进程会继承父进程的 umask
  • 临时性: 进程结束后,umask 设置不会保留
  • 权限计算: 实际权限 = 默认权限 & ~umask
  • 现有文件: umask 不影响已存在的文件
最佳实践
<?php
// 1. 总是保存和恢复原来的 umask
function safeUmaskOperation() {
    $old_umask = umask(); // 保存原来的 umask

    try {
        umask(027); // 设置新的 umask

        // 执行文件操作
        touch('secure_file.txt');

    } finally {
        umask($old_umask); // 恢复原来的 umask
    }
}

// 2. 在类的构造和析构函数中管理 umask
class SecureFileHandler {
    private $original_umask;

    public function __construct() {
        $this->original_umask = umask();
        umask(077); // 设置严格的 umask
    }

    public function __destruct() {
        umask($this->original_umask); // 恢复原来的 umask
    }
}

// 3. 验证 umask 设置
function verifyUmask($expected_umask) {
    $actual_umask = umask();
    umask($actual_umask); // 重新设置以获取当前值

    if ($actual_umask !== $expected_umask) {
        throw new RuntimeException("umask 设置不正确");
    }
}

// 4. 使用适合场景的 umask 值
function getRecommendedUmask() {
    if (PHP_SAPI === 'cli') {
        return 022; // 命令行环境
    } else {
        return 027; // Web 环境
    }
}
?>
常见错误
  • 忘记恢复: 修改 umask 后忘记恢复原来的值
  • 错误的值: 使用十进制数而不是八进制数
  • 作用域误解: 认为 umask 会影响系统全局或其他进程
  • 权限覆盖: 使用 chmod() 等函数时会覆盖 umask 的效果
  • 测试不足: 在不同环境下没有测试 umask 的效果

实际应用场景

场景1:Web应用文件上传
<?php
class SecureUploadHandler {
    private $uploadDir;

    public function __construct($uploadDir) {
        $this->uploadDir = $uploadDir;

        // 确保上传目录存在且安全
        if (!is_dir($uploadDir)) {
            // 使用安全的 umask 创建目录
            $old_umask = umask(077);
            mkdir($uploadDir, 0755, true);
            umask($old_umask);
        }
    }

    public function handleUpload($fileData) {
        // 生成安全的文件名
        $filename = $this->generateSafeFilename($fileData['name']);
        $filepath = $this->uploadDir . '/' . $filename;

        // 使用安全的 umask 创建文件
        $old_umask = umask(077);

        try {
            // 移动上传的文件
            if (move_uploaded_file($fileData['tmp_name'], $filepath)) {
                // 设置适当的权限(根据文件类型)
                $this->setFilePermissions($filepath, $fileData['type']);

                return [
                    'success' => true,
                    'filename' => $filename,
                    'path' => $filepath,
                    'permissions' => substr(sprintf('%o', fileperms($filepath)), -4)
                ];
            } else {
                return ['success' => false, 'error' => '文件移动失败'];
            }
        } finally {
            // 恢复原来的 umask
            umask($old_umask);
        }
    }

    private function generateSafeFilename($originalName) {
        // 生成安全的文件名(防止路径遍历等攻击)
        $ext = pathinfo($originalName, PATHINFO_EXTENSION);
        return uniqid('upload_', true) . '.' . $ext;
    }

    private function setFilePermissions($filepath, $mimeType) {
        // 根据文件类型设置不同的权限
        if (strpos($mimeType, 'image/') === 0) {
            // 图片文件:所有者读写,其他用户只读
            chmod($filepath, 0644);
        } else {
            // 其他文件:更严格的权限
            chmod($filepath, 0600);
        }
    }
}

// 使用示例
$uploadHandler = new SecureUploadHandler('/var/www/uploads');
// 处理上传...
?>
场景2:临时文件生成器
<?php
class TempFileGenerator {
    private $tempDir;
    private $defaultUmask;

    public function __construct($tempDir = null) {
        $this->tempDir = $tempDir ?: sys_get_temp_dir();
        $this->defaultUmask = umask();
    }

    public function createTempFile($content = '', $secure = true) {
        // 生成唯一文件名
        $filename = uniqid('temp_', true) . '.tmp';
        $filepath = $this->tempDir . '/' . $filename;

        // 根据安全需求设置 umask
        $old_umask = umask($secure ? 077 : 022);

        try {
            // 创建文件
            if (file_put_contents($filepath, $content) === false) {
                throw new RuntimeException("无法创建临时文件");
            }

            return [
                'path' => $filepath,
                'name' => $filename,
                'secure' => $secure,
                'permissions' => substr(sprintf('%o', fileperms($filepath)), -4)
            ];
        } finally {
            // 恢复原来的 umask
            umask($old_umask);
        }
    }

    public function cleanupOldFiles($maxAge = 3600) {
        // 清理过期的临时文件
        $files = glob($this->tempDir . '/temp_*.tmp');
        $cleaned = 0;

        foreach ($files as $file) {
            if (filemtime($file) < time() - $maxAge) {
                unlink($file);
                $cleaned++;
            }
        }

        return $cleaned;
    }
}

// 使用示例
$generator = new TempFileGenerator();

// 创建安全的临时文件(只有所有者可读写)
$secureFile = $generator->createTempFile('敏感数据', true);
echo "安全文件: " . $secureFile['path'] . " (权限: " . $secureFile['permissions'] . ")<br>";

// 创建普通的临时文件
$normalFile = $generator->createTempFile('普通数据', false);
echo "普通文件: " . $normalFile['path'] . " (权限: " . $normalFile['permissions'] . ")<br>";

// 清理
unlink($secureFile['path']);
unlink($normalFile['path']);
?>