没有不值得去解决的问题,也没有不值得去学习的技术!

Fixing the chmod(): Operation Not Permitted Error When Creating Multi-Level Directories With FileHelper in Yii 2.0

Decided to switch to development mode (dev) and upload the topic materials again to view more detailed error information. The error reported was: chmod(): Operation not permitted

1. When uploading topic materials, an internal server error occurred. See Figure 1.

When uploading topic materials, an internal server error occurred.
Figure 1
JSON

{
  "name": "Internal Server Error",
  "message": "服务器内部错误。",
  "code": 0,
  "status": 500
}

2. I decided to switch to development mode (dev) and upload the topic materials again to view more detailed error information. The error reported was: chmod(): Operation not permitted. See Figure 2.

Decided to switch to development mode (dev) and upload the topic materials again to view more detailed error information. The error reported was: chmod(): Operation not permitted
Figure 2
JSON

{
  "name": "Exception",
  "message": "Failed to change permissions for directory \"/webtv/wangjie/pcs-api/tmp/2019/12/05\": chmod(): Operation not permitted",
  "code": 2,
  "type": "yii\\base\\Exception",
  "file": "/mcloud/www/pcs-api/vendor/yiisoft/yii2/helpers/BaseFileHelper.php",
  "line": 635,
  "stack-trace": [
    "#0 /mcloud/www/pcs-api/common/services/AssetService.php(77): yii\\helpers\\BaseFileHelper::createDirectory('/webtv/wangjie/...')",
    "#1 /mcloud/www/pcs-api/api/rests/asset/UploadAction.php(93): common\\services\\AssetService::uploadTempAssets(Array)",
    "#2 [internal function]: api\\rests\\asset\\UploadAction->run()",
    "#3 /mcloud/www/pcs-api/vendor/yiisoft/yii2/base/Action.php(94): call_user_func_array(Array, Array)",
    "#4 /mcloud/www/pcs-api/vendor/yiisoft/yii2/base/Controller.php(157): yii\\base\\Action->runWithParams(Array)",
    "#5 /mcloud/www/pcs-api/vendor/yiisoft/yii2/base/Module.php(528): yii\\base\\Controller->runAction('upload', Array)",
    "#6 /mcloud/www/pcs-api/vendor/yiisoft/yii2/web/Application.php(103): yii\\base\\Module->runAction('v1/asset/upload', Array)",
    "#7 /mcloud/www/pcs-api/vendor/yiisoft/yii2/base/Application.php(386): yii\\web\\Application->handleRequest(Object(yii\\web\\Request))",
    "#8 /mcloud/www/pcs-api/api/web/index.php(17): yii\\base\\Application->run()",
    "#9 {main}"
  ],
  "previous": {
    "name": "PHP Warning",
    "message": "chmod(): Operation not permitted",
    "code": 2,
    "type": "yii\\base\\ErrorException",
    "file": "/mcloud/www/pcs-api/vendor/yiisoft/yii2/helpers/BaseFileHelper.php",
    "line": 633,
    "stack-trace": [
      "#0 [internal function]: yii\\base\\ErrorHandler->handleError(2, 'chmod(): Operat...', '/mcloud/www/pcs...', 633, Array)",
      "#1 /mcloud/www/pcs-api/vendor/yiisoft/yii2/helpers/BaseFileHelper.php(633): chmod('/webtv/wangjie/...', 509)",
      "#2 /mcloud/www/pcs-api/common/services/AssetService.php(77): yii\\helpers\\BaseFileHelper::createDirectory('/webtv/wangjie/...')",
      "#3 /mcloud/www/pcs-api/api/rests/asset/UploadAction.php(93): common\\services\\AssetService::uploadTempAssets(Array)",
      "#4 [internal function]: api\\rests\\asset\\UploadAction->run()",
      "#5 /mcloud/www/pcs-api/vendor/yiisoft/yii2/base/Action.php(94): call_user_func_array(Array, Array)",
      "#6 /mcloud/www/pcs-api/vendor/yiisoft/yii2/base/Controller.php(157): yii\\base\\Action->runWithParams(Array)",
      "#7 /mcloud/www/pcs-api/vendor/yiisoft/yii2/base/Module.php(528): yii\\base\\Controller->runAction('upload', Array)",
      "#8 /mcloud/www/pcs-api/vendor/yiisoft/yii2/web/Application.php(103): yii\\base\\Module->runAction('v1/asset/upload', Array)",
      "#9 /mcloud/www/pcs-api/vendor/yiisoft/yii2/base/Application.php(386): yii\\web\\Application->handleRequest(Object(yii\\web\\Request))",
      "#10 /mcloud/www/pcs-api/api/web/index.php(17): yii\\base\\Application->run()",
      "#11 {main}"
    ]
  }
}

3. I checked the mounted directory, /webtv/wangjie/pcs-api/tmp/2019/12/05. I uploaded twice in succession: the first time generated the directory 12, and the second time generated the directory 05. See Figure 3.

Checked the mounted directory, /webtv/wangjie/pcs-api/tmp/2019/12/05. Uploaded twice in succession: the first time generated the directory 12, and the second time generated the directory 05
Figure 3
Plaintext

[root@d48dedd9fceb pcs-api]# ls
2019  tmp
[root@d48dedd9fceb pcs-api]# cd tmp
[root@d48dedd9fceb tmp]# ls
2019
[root@d48dedd9fceb tmp]# cd 2019
[root@d48dedd9fceb 2019]# ls
10  11  12
[root@d48dedd9fceb 2019]# cd 12
[root@d48dedd9fceb 12]# ls
05
[root@d48dedd9fceb 12]# cd 05
[root@d48dedd9fceb 05]# ls
[root@d48dedd9fceb 05]# pwd
/webtv/wangjie/pcs-api/tmp/2019/12/05
[root@d48dedd9fceb 05]# ls -l
total 0
[root@d48dedd9fceb 05]# cd ..
[root@d48dedd9fceb 12]# ls -l
total 0
drwxrwxrwx 2 root root 0 Dec  5 15:28 05
[root@d48dedd9fceb 12]#

4. Analyzing the cause: the current user is nginx. I checked the directory user in the development environment, whereas in the environment where the error occurred, the directory user was root with the mode set to 0777. The current user refers to the user executing PHP. This is likely not the same as the usual shell or FTP user. On most systems, the file mode can only be changed by the user who owns the file. See Figure 4.

Analyzing the cause: the current user is nginx. Checked the directory user in the development environment, whereas in the environment where the error occurred, the directory user was root with the mode set to 0777. The current user refers to the user executing PHP. This is likely not the same as the usual shell or FTP user. On most systems, the file mode can only be changed by the user who owns the file.
Figure 4
Plaintext

[root@685b4b7fd197 /]# cd /webtv/wangjiedev/pcs-api/
[root@685b4b7fd197 pcs-api]# ls
2019  php-fpm1.conf  php-fpm.conf  tmp
[root@685b4b7fd197 pcs-api]# cd tmp
[root@685b4b7fd197 tmp]# ls -l
total 0
drwxrwxr-x 9 nginx nginx 101 Dec  2 16:26 2019

5. I ultimately decided to extend the class yii\helpers\FileHelper and override the static method createDirectory($path, $mode = 0775, $recursive = true). When chmod() fails to change the file mode, it returns true instead of throwing an exception, allowing subsequent execution to continue. The new class created is \common\helpers\FileHelper.php.

PHP
<?php
/**
 * Created by PhpStorm.
 * User: Qiang Wang
 * Date: 2019/12/05
 * Time: 16:45
 */
 
namespace common\helpers;
 
/**
 * File system helper.
 *
 * @author Qiang Wang <shuijingwanwq@163.com>
 * @since 1.0
 */
class FileHelper extends \yii\helpers\FileHelper
{
    /**
     * Creates a new directory.
     *
     * This method is similar to the PHP `mkdir()` function except that
     * it uses `chmod()` to set the permission of the created directory
     * in order to avoid the impact of the `umask` setting.
     *
     * @param string $path path of the directory to be created.
     * @param int $mode the permission to be set for the created directory.
     * @param bool $recursive whether to create parent directories if they do not exist.
     * @return bool whether the directory is created successfully
     * @throws \yii\base\Exception if the directory could not be created (i.e. php error due to parallel changes)
     */
    public static function createDirectory($path, $mode = 0775, $recursive = true)
    {
        if (is_dir($path)) {
            return true;
        }
        $parentDir = dirname($path);
        // recurse if parent dir does not exist and we are not at the root of the file system.
        if ($recursive && !is_dir($parentDir) && $parentDir !== $path) {
            static::createDirectory($parentDir, $mode, true);
        }
        try {
            if (!mkdir($path, $mode)) {
                return false;
            }
        } catch (\Exception $e) {
            if (!is_dir($path)) {// https://github.com/yiisoft/yii2/issues/9288
                throw new \yii\base\Exception("Failed to create directory \"$path\": " . $e->getMessage(), $e->getCode(), $e);
            }
        }
        try {
            return @chmod($path, $mode);
        } catch (\Exception $e) {
            throw new \yii\base\Exception("Failed to change permissions for directory \"$path\": " . $e->getMessage(), $e->getCode(), $e);
        }
    }
}

6. For subsequent directory creation, I referenced the newly created class common\helpers\FileHelper everywhere. I entered the environment where the error occurred and manually deleted the directory /webtv/wangjie/pcs-api/tmp/2019/12. See Figure 5.

For subsequent directory creation, referenced the newly created class common\helpers\FileHelper everywhere. Entered the environment where the error occurred and manually deleted the directory /webtv/wangjie/pcs-api/tmp/2019/12
Figure 5

7. When uploading the topic materials again, an error occurred: “Failed to create directory: /webtv/wangjie/pcs-api/tmp/2019/12/05”. See Figure 6.

When uploading the topic materials again, an error occurred: "Failed to create directory: /webtv/wangjie/pcs-api/tmp/2019/12/05"
Figure 6
JSON

{
  "name": "Internal Server Error",
  "message": "创建目录:/webtv/wangjie/pcs-api/tmp/2019/12/05,失败",
  "code": 202002,
  "status": 500,
  "type": "yii\\web\\ServerErrorHttpException"
}

8. I checked the mounted directory, /webtv/wangjie/pcs-api/tmp/2019/12/05. It was successfully created on the first attempt. I confirmed that the issue of being unable to create multiple directories at once has been resolved. See Figure 7.

Checked the mounted directory, /webtv/wangjie/pcs-api/tmp/2019/12/05. It was successfully created on the first attempt. Confirmed that the issue of being unable to create multiple directories at once has been resolved
Figure 7

9. The cause of the analysis is that the static method createDirectory($path, $mode = 0775, $recursive = true) did not return true, which led to the assumption during the check that directory creation had failed, triggering an exception. Therefore, it should return true regardless of the chmod result.

PHP
<?php
/**
 * Created by PhpStorm.
 * User: Qiang Wang
 * Date: 2019/12/05
 * Time: 16:45
 */
 
namespace common\helpers;
 
/**
 * File system helper.
 *
 * @author Qiang Wang <shuijingwanwq@163.com>
 * @since 1.0
 */
class FileHelper extends \yii\helpers\FileHelper
{
    /**
     * Creates a new directory.
     *
     * This method is similar to the PHP `mkdir()` function except that
     * it uses `chmod()` to set the permission of the created directory
     * in order to avoid the impact of the `umask` setting.
     *
     * @param string $path path of the directory to be created.
     * @param int $mode the permission to be set for the created directory.
     * @param bool $recursive whether to create parent directories if they do not exist.
     * @return bool whether the directory is created successfully
     * @throws \yii\base\Exception if the directory could not be created (i.e. php error due to parallel changes)
     */
    public static function createDirectory($path, $mode = 0775, $recursive = true)
    {
        if (is_dir($path)) {
            return true;
        }
        $parentDir = dirname($path);
        // recurse if parent dir does not exist and we are not at the root of the file system.
        if ($recursive && !is_dir($parentDir) && $parentDir !== $path) {
            static::createDirectory($parentDir, $mode, true);
        }
        try {
            if (!mkdir($path, $mode)) {
                return false;
            }
        } catch (\Exception $e) {
            if (!is_dir($path)) {// https://github.com/yiisoft/yii2/issues/9288
                throw new \yii\base\Exception("Failed to create directory \"$path\": " . $e->getMessage(), $e->getCode(), $e);
            }
        }
        try {
            // return chmod($path, $mode);
            @chmod($path, $mode);
            return true;
        } catch (\Exception $e) {
            throw new \yii\base\Exception("Failed to change permissions for directory \"$path\": " . $e->getMessage(), $e->getCode(), $e);
        }
    }
}

10. I manually deleted the directory /webtv/wangjie/pcs-api/tmp/2019/12 and uploaded the topic materials again. The resource upload was successful, as expected. See Figure 8.

Manually deleted the directory /webtv/wangjie/pcs-api/tmp/2019/12 and uploaded the topic materials again. The resource upload was successful, as expected
Figure 8

11. I checked the mounted directory; /webtv/wangjie/pcs-api/tmp/2019/12/05/1575537845.5862.1232301337.jpg already exists, as expected. See Figure 9.

Checked the mounted directory; /webtv/wangjie/pcs-api/tmp/2019/12/05/1575537845.5862.1232301337.jpg already exists, as expected
Figure 9

关于作者 · 持续学习与技术实践

我是一名拥有 15 年以上开发经验的技术从业者,自 2013 年起持续运营个人技术博客,记录工作与个人项目中遇到的真实问题,以及解决问题过程中的探索、实践和思考。

除了技术写作,我也在持续开发和维护自己的独立项目,探索新技术和新工具在实际场景中的应用。我始终相信,没有不值得去解决的问题,也没有不值得去学习的技术。

如果这篇文章对你有所帮助,欢迎浏览博客中的其他文章,也欢迎交流技术经验与想法。

关于我  ·  GitHub  ·  邮件联系