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

{
"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.

{
"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.

[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.

[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
/**
* 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.

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.

{
"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.

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
/**
* 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.

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.

