为啥 PHP 认为它正在写入文件,但事实并非如此?

Posted

技术标签:

【中文标题】为啥 PHP 认为它正在写入文件,但事实并非如此?【英文标题】:Why does PHP think it's writing to a file but it isn't?为什么 PHP 认为它正在写入文件,但事实并非如此? 【发布时间】:2020-07-10 08:19:33 【问题描述】:

我有一个带有 php pgm 的 html 表单,用于将结果写入文件。我基本上是从网上复制的。这不是火箭手术。它在具有最新 Raspbian 的 Raspberry Pi 上运行 PHP 7。 PHP pgm 报告该文件是可写的,它成功了,等等,但该文件仍然是空的。我已将权限更改为 777(用于测试),但这并没有帮助。我难住了。这是 HTML:

<!DOCTYPE html>
<html>
<head>
<title>Update</title>
</head>
<body>
<FORM NAME ="form1" METHOD ="POST" action="filewrite.php">
<INPUT TYPE = "TEXT" VALUE ="memory" NAME="memory">
<INPUT TYPE = "TEXT" VALUE ="desc"   NAME="desc">
<INPUT TYPE = "TEXT" VALUE ="comm"   NAME="comm">
<INPUT TYPE = "TEXT" VALUE ="reset"  NAME="reset">
<INPUT TYPE = "Submit" Name = "Submit1" VALUE = "Create">
</FORM>
</body>
</html>

这是 PHP(在 filewrite.php 中):

<?php
ini_set('display_errors', true);

if(isset($_POST['memory']) && isset($_POST['desc']) && isset($_POST['comm']) && isset($_POST['reset']))

#    $data = $_POST['memory'] . '\t' . $_POST['desc']  . '\t' . $_POST['comm']  . '\t' . $_POST['reset'] . "\n";
    $data = "testing";
    $file = "/tmp/data.txt";

var_dump($_POST);

    if (is_writable($file)) 
       echo nl2br("\n\nThe file is writable\n\n");
     else 
       echo nl2br("The file is not writable\n\n");
    

    touch($file);
    $ret = file_put_contents($file, $data, LOCK_EX);
    if($ret === false)
    
        die('There was an error writing this file');
    
    else
    
        echo nl2br("$ret bytes written to file\n");
        echo nl2br("data = $data\n");
        echo nl2br("file = $file\n");
    

else

   die('no post data to process');


?>

这是我得到的输出:

array(5)  ["memory"]=> string(4) "fred" ["desc"]=> string(6) "barney" ["comm"]=> string(5) "wilma" ["reset"]=> string(5) "betty" ["Submit1"]=> string(6) "Create" 

The file is writable

7 bytes written to file
data = testing
file = /tmp/data.txt

这是文件:

-rwxrwxrwx 1 pi pi 0 Mar 29 16:43 /tmp/data.txt

我在没有文件存在的情况下尝试了它,并且在那里有它。起初我试图将表单数据写入文件,但后来切换到只是尝试编写“测试”。我已经尝试了该文件的其他目录,但它们(如预期的那样)由于权限而失败。

编辑: 当我尝试将文件写入 /home/pi 时(我预计会失败,因为网络服务器是 www-data),我在 /var/log/apache2/error.log 中收到此错误:

[Sun Mar 29 17:04:24.648150 2020] [php7:warn] [pid 12075] [client fe80::cceb:ba3c:da1d:6b2a:53052] PHP Warning:  file_put_contents(/home/pi/data.txt): failed to open stream: Permission denied in /var/www/html/filewrite.php on line 19, referer: http://devberry-pi.local/rwupdate.html

将其设置回 /tmp/data.txt 清除了该错误(并且不会生成其他错误。)

编辑 2: 我将一个 6k 文件复制到 /tmp 以确保有足够的空间并且工作正常。我现在只写 7 个字节(“测试”),并且真的只想写

编辑 3: 全新的卡,全新的安装,同样的结果。 8^( 我认为我的下一步是使用不同的 Raspberry Pi,尽管我不确定硬件可能是什么问题。注意:我可以从命令行在 /tmp 中创建文件。

哦,新开发 -- 我创建了一个新文件夹 /home/www,将其权限设置为 777,它确实成功地在其中创建了文件。 /tmp 的权限是 drwxrwxrwt,它由 root/root 拥有。 webserver/php 用户是 www-data。我可以将其用作解决方法,但在我的书中并不理想。有没有比 /tmp 更好的地方来放置不同用户/pgms 需要访问的东西?

有什么想法吗?我应该看什么?

【问题讨论】:

您好:您的 PHP 错误日志中有什么内容? /var/log/apache2/error.log 中没有任何内容(除了我之前尝试各种疯狂的事情时)。是否还有其他日志文件要检查? /tmp FS 已满?尝试手动将文件写入/tmp/file.txt,cat &gt; /tmp/file.txt ... 我检查了 /tmp 目录 -- 我将一个 6k 文件复制到 /tmp 并且很好...我只是(当前)尝试写入 7 个字节(“测试”)。 检查您的 Pi 的 SD 卡。我有一个表现出类似行为的人,结果发现卡文件系统已损坏。具体来说,似乎所有写入都在工作,但在重新启动后,根本没有写入任何更改。 【参考方案1】:

您可以使用 PHP 函数 file_put_contents 获取您希望写入的文件名和数据并将其写入文件。在您的情况下,这可能是发送的某种形式的 HTML POST 数据。在下面的解决方案中,我将值编码为 JSON,因为它很好地表示了与此类表单相关联的键/值对。这可以以适合您需要的任何其他形式完成。

我已选择将文件写入与包含表单的 PHP 文件相同的文件夹。这在生产环境中是不现实的,但它表明 PHP 可以保存文件。

如果文件的位置是问题所在,那么权限就是您应该查看的位置,因为在使用以下方法时,这应该是导致文件无法写入的唯一原因。我已在表单字段中输入值以便于测试,但占位符应保留在生产环境中并删除值。

在任何环境中都应该同时进行客户端和服务器端验证。为了简单起见,我已经删除了验证,并且它是题外话,但每个值都应该在进行任何其他操作之前进行测试和过滤。无论如何,这是合并到一个文件中的工作示例:

<?php
/**
 * Check for submission of form.
 */
$nl="<br>";
if (isset($_POST['memory']) && isset($_POST['desc']) && isset($_POST['comm']) && isset($_POST['reset'])) 
    try 
        $data = json_encode($_POST, JSON_PRETTY_PRINT);
        $file = "data.txt";
        if ( file_put_contents ($file, $data) ) 
            $msg = "File successfully saved.";
         else 
            $msg = "Error saving file";
                    
     catch (Exception $e) 
        $msg = "Error saving file:" .$nl.$nl . $e->getMessage() . $nl.$nl . $e-getTrace();
    

?>
<!doctype html>
<html lang="en_US">
<head>
<meta charset='utf-8'>
<title>Example: Save File</title>
</head>
<body>
<?php if (isset($msg)) echo '< style="color:#100">'.$msg.'</p>';  ?>
<form name="form1" method="post" enctype="multipart/form-data">
<input type="text" placeholder="memory" name="memory" value="mem">
<input type="text" placeholder="desc"   name="desc" value="something">
<input type="text" placeholder="comm"   name="comm" value="more data">
<input type="text" placeholder="reset"  name="reset" value="reset">
<input type="Submit" Name = "Submit1" value="Create">
</form>
</body>
</html>

此外,提交表单时,您应该在表单标签上设置 enctype 属性。如果您要上传文件&lt;input type="file"&gt;,则必须将其设置为multipart/form-data。这允许通过 HTTP Post 二进制发送数据。

<form enctype="multipart/form-data" action="..." id="....">

此外,正如 MDN 在其文档中指出的那样,您可以将属性添加到 &lt;input&gt;button 元素。:

“...或&lt;input&gt;&lt;button&gt; 元素的formenctype 属性。”

在所有情况下,您都应该指定表单的 enctype,包括在使用 XMLHttpRequest 的异步请求期间。

【讨论】:

以上是关于为啥 PHP 认为它正在写入文件,但事实并非如此?的主要内容,如果未能解决你的问题,请参考以下文章

如何将图像写入文件?

为啥 save() 第一次不写入文件?

为啥 SIMD 比蛮力慢

为啥 AVURLAsset 不加载文件?

为啥 proguard 不混淆方法体?

Docker 守护进程写入 syslog 但为啥