详解 WordPress `wp_is_writable()` 函数源码:如何判断文件或目录可写,以及在更新时的应用。

各位听众,晚上好!今天咱们来聊聊 WordPress 里一个看似简单,实则暗藏玄机的函数:wp_is_writable()。别看它名字平平无奇,在 WordPress 更新、插件安装、主题切换等核心操作中,它可是个关键角色。今天,咱们就扒开它的源码,看看它到底是怎么判断文件或目录是否可写的,以及在实际更新过程中又是如何应用的。

开场白:权限这档子事儿

在开始深入代码之前,咱们先来回顾一下文件权限的基础知识。在 Linux/Unix 系统中,每个文件和目录都有权限,决定了哪些用户可以读取、写入和执行它们。这些权限通常用三个数字表示,比如 755 或 644。

  • 第一个数字:代表文件所有者的权限。
  • 第二个数字:代表文件所属用户组的权限。
  • 第三个数字:代表其他用户的权限。

每个数字都可以用 0-7 之间的值表示,每个数字实际上是三个二进制位的组合,分别代表:

  • 4: 读取权限 (r)
  • 2: 写入权限 (w)
  • 1: 执行权限 (x)

举个例子,权限 755 表示:

  • 所有者拥有读、写和执行权限 (4+2+1=7)
  • 所属用户组拥有读和执行权限 (4+0+1=5)
  • 其他用户拥有读和执行权限 (4+0+1=5)

而权限 644 表示:

  • 所有者拥有读和写权限 (4+2+0=6)
  • 所属用户组拥有读权限 (4+0+0=4)
  • 其他用户拥有读权限 (4+0+0=4)

了解了这些基础知识,咱们才能更好地理解 wp_is_writable() 函数的工作原理。

wp_is_writable() 源码剖析:一次深入的探险

好了,废话不多说,咱们直接上代码。wp_is_writable() 函数的定义通常位于 WordPress 的 wp-includes/functions.php 文件中。

<?php

/**
 * Tests for file writability.
 *
 * @since 2.0.0
 *
 * @param string $path Path to test for writability.
 * @return bool True if the path is writable, false otherwise.
 */
function wp_is_writable( $path ) {
    if ( 'WIN' === strtoupper( substr( PHP_OS, 0, 3 ) ) ) {
        // Windows' "is_writable" always reports correct file status.
        return is_writable( $path );
    } else {
        // For other platforms, we need to work around certain quirks.
        if ( is_dir( $path ) ) {
            // If it's a directory, check if a file can be created inside it.
            $path = trailingslashit( $path ) . uniqid( mt_rand() ) . '.tmp';

            if ( @file_put_contents( $path, ' ' ) !== false ) {
                @unlink( $path );
                return true;
            } else {
                return false;
            }
        } else {
            // If it's a file, check if it can be opened for writing.
            $realpath = realpath( $path );
            if ( false === $realpath ) {
                return false;
            }
            if ( @is_writable( $realpath ) ) {
                return true;
            }
            // Check if the file exists and has the correct user and group ownership.
            if ( file_exists( $realpath ) ) {
                $file_owner = @fileowner( $realpath );
                $file_group = @filegroup( $realpath );
                if ( function_exists( 'posix_getuid' ) && function_exists( 'posix_getgid' ) ) {
                    $current_user_id = posix_getuid();
                    $current_group_id = posix_getgid();

                    if ( $file_owner === $current_user_id || $file_group === $current_group_id ) {
                        return true;
                    }
                }
            }
            return false;
        }
    }
}

咱们来逐行解读一下这段代码:

  1. 操作系统判断
if ( 'WIN' === strtoupper( substr( PHP_OS, 0, 3 ) ) ) {
    // Windows' "is_writable" always reports correct file status.
    return is_writable( $path );
}

这段代码首先判断操作系统是不是 Windows。如果是 Windows,就直接调用 PHP 的 is_writable() 函数来判断文件或目录是否可写。 在 Windows 下,is_writable() 通常能够准确地报告文件的可写状态,所以 WordPress 没必要再费力气自己实现了。

  1. 目录可写性判断
else {
    // For other platforms, we need to work around certain quirks.
    if ( is_dir( $path ) ) {
        // If it's a directory, check if a file can be created inside it.
        $path = trailingslashit( $path ) . uniqid( mt_rand() ) . '.tmp';

        if ( @file_put_contents( $path, ' ' ) !== false ) {
            @unlink( $path );
            return true;
        } else {
            return false;
        }
    }

如果操作系统不是 Windows,并且 $path 指向的是一个目录,那么 WordPress 会尝试在这个目录下创建一个临时文件。

  • trailingslashit( $path ): 这个函数确保 $path 以斜杠 / 结尾,避免路径拼接错误。
  • uniqid( mt_rand() ) . '.tmp': 生成一个唯一的临时文件名,避免文件名冲突。
  • @file_put_contents( $path, ' ' ): 尝试将一个空格写入这个临时文件。@ 符号在这里的作用是抑制错误显示,因为如果目录不可写,这个函数会产生一个警告。
  • @unlink( $path ): 如果文件成功创建,则立即删除它。
  • 如果文件创建成功,函数返回 true,表示目录可写;否则返回 false

这种方法比简单地检查目录权限更可靠,因为它实际上测试了 PHP 进程是否有权限在目录中创建文件。

  1. 文件可写性判断
else {
    // If it's a file, check if it can be opened for writing.
    $realpath = realpath( $path );
    if ( false === $realpath ) {
        return false;
    }
    if ( @is_writable( $realpath ) ) {
        return true;
    }
    // Check if the file exists and has the correct user and group ownership.
    if ( file_exists( $realpath ) ) {
        $file_owner = @fileowner( $realpath );
        $file_group = @filegroup( $realpath );
        if ( function_exists( 'posix_getuid' ) && function_exists( 'posix_getgid' ) ) {
            $current_user_id = posix_getuid();
            $current_group_id = posix_getgid();

            if ( $file_owner === $current_user_id || $file_group === $current_group_id ) {
                return true;
            }
        }
    }
    return false;
}

如果 $path 指向的是一个文件,那么 WordPress 会执行以下步骤:

  • $realpath = realpath( $path ): 获取文件的真实路径。这可以解决符号链接的问题。如果 $path 指向的是一个不存在的文件,realpath() 会返回 false,函数也会直接返回 false
  • @is_writable( $realpath ): 再次调用 PHP 的 is_writable() 函数来判断文件是否可写。同样,@ 符号用于抑制错误显示。如果 is_writable() 返回 true,函数就直接返回 true
  • 所有者和用户组检查:如果 is_writable() 返回 false,WordPress 还会进一步检查文件的所有者和用户组是否与当前 PHP 进程的用户和用户组匹配。
    • file_exists( $realpath ): 确保文件存在。
    • $file_owner = @fileowner( $realpath ): 获取文件的所有者 ID。
    • $file_group = @filegroup( $realpath ): 获取文件的用户组 ID。
    • function_exists( 'posix_getuid' ) && function_exists( 'posix_getgid' ): 检查 posix_getuid()posix_getgid() 函数是否存在。这两个函数用于获取当前 PHP 进程的用户 ID 和用户组 ID。注意:这两个函数在某些服务器环境中可能被禁用。
    • $current_user_id = posix_getuid(): 获取当前 PHP 进程的用户 ID。
    • $current_group_id = posix_getgid(): 获取当前 PHP 进程的用户组 ID。
    • if ( $file_owner === $current_user_id || $file_group === $current_group_id ): 如果文件的所有者或用户组与当前 PHP 进程的用户或用户组匹配,那么函数返回 true

为什么要进行所有者和用户组检查?

在某些共享主机环境中,PHP 进程可能以与文件所有者不同的用户身份运行。即使文件的权限设置为允许所有者写入,PHP 进程也可能无法写入该文件。通过检查所有者和用户组,wp_is_writable() 可以更准确地判断文件是否可写。

wp_is_writable() 在 WordPress 更新中的应用:一场权限的保卫战

现在咱们来看看 wp_is_writable() 在 WordPress 更新过程中是如何发挥作用的。WordPress 在进行自动更新、插件更新、主题更新等操作之前,都会先检查相关文件和目录是否可写。如果发现不可写,就会给出警告或者阻止更新,以避免更新失败或者更严重的问题。

以下是一些 wp_is_writable() 在更新过程中应用的场景:

  • 检查 wp-config.php 是否可写:在 WordPress 尝试自动更新数据库连接设置时,会先检查 wp-config.php 文件是否可写。如果不可写,更新将被阻止,并提示用户手动修改文件。
if ( ! wp_is_writable( ABSPATH . 'wp-config.php' ) ) {
    // 显示错误信息,提示用户修改 wp-config.php 权限
    wp_die( __( 'Your wp-config.php file is not writable.' ) );
}
  • 检查 wp-content 目录及其子目录是否可写:在安装或更新插件和主题时,WordPress 需要将文件写入 wp-content 目录及其子目录。如果这些目录不可写,安装或更新将失败。
if ( ! wp_is_writable( WP_CONTENT_DIR ) ) {
    // 显示错误信息,提示用户修改 wp-content 目录权限
    wp_die( __( 'Your wp-content directory is not writable.' ) );
}
  • 检查 uploads 目录是否可写:在上传媒体文件时,WordPress 需要将文件写入 uploads 目录。如果该目录不可写,上传将失败。
if ( ! wp_is_writable( WP_CONTENT_DIR . '/uploads' ) ) {
    // 显示错误信息,提示用户修改 uploads 目录权限
    wp_die( __( 'Your uploads directory is not writable.' ) );
}

表格总结:wp_is_writable() 的行为

为了更清晰地理解 wp_is_writable() 的行为,咱们用一个表格来总结一下:

操作系统 $path 类型 判断逻辑 返回值
Windows 文件或目录 直接调用 is_writable() 函数。 truefalse
非 Windows 目录 尝试在目录下创建一个临时文件,如果成功则删除并返回 true,否则返回 false truefalse
非 Windows 文件 1. 获取文件的真实路径 (realpath())。 2. 调用 is_writable() 函数。 3. 如果 is_writable() 返回 false,则检查文件的所有者和用户组是否与当前 PHP 进程的用户和用户组匹配,如果匹配则返回 true,否则返回 false truefalse

wp_is_writable() 的局限性:并非万能钥匙

虽然 wp_is_writable() 在大多数情况下能够准确地判断文件或目录是否可写,但它也存在一些局限性:

  • SELinux 和其他安全模块:SELinux (Security-Enhanced Linux) 和其他安全模块可能会限制 PHP 进程的权限,即使 wp_is_writable() 返回 true,PHP 进程仍然可能无法写入文件。
  • NFS 文件系统:在使用 NFS (Network File System) 时,文件权限可能会出现问题,导致 wp_is_writable() 的判断结果不准确。
  • 基于用户的权限管理:在一些高级的权限管理系统中,可能会根据用户的角色和权限来限制文件访问,wp_is_writable() 无法考虑到这些因素。

总结:权限管理,永无止境

好了,今天的讲座就到这里。咱们深入剖析了 WordPress 的 wp_is_writable() 函数的源码,了解了它的工作原理和在更新过程中的应用。虽然 wp_is_writable() 并非万能钥匙,但它仍然是 WordPress 中一个非常重要的函数,帮助咱们在更新和维护网站时避免许多权限问题。记住,权限管理是一个复杂而重要的课题,需要咱们不断学习和实践。希望今天的分享能够帮助大家更好地理解 WordPress 的内部机制,并在实际工作中更加得心应手。谢谢大家!

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注