mod_perl对system()、exec()或反引号调用的奇怪行为

3
我开发了一个简单的Perl脚本,可以验证USB键是否已挂载并最终卸载它。
我不确定问题是与编程还是配置有关,如果我错了,请告诉我,我将重新在服务器配置相关堆栈上发布我的问题。
基本问题是:当我在终端中使用Perl执行这些脚本时,它完美地工作,但是当我在浏览器中使用mod_perl时,它会显示一些相当奇怪的行为。
来源 panelmin.pl
此程序仅输出一个消息,指示系统设备/dev/sda1是否已在系统中挂载。
#!/usr/bin/perl
use strict;
use warnings;

print "Content-type:text/html\n\n";
print "<html><head><title>USB test</title>";
print "</head><body>";

my $mounted = `df -h | grep /dev/sda1`;

if ($mounted eq '') {
        print '<h1>USB device not connected</h1>';
        print $mounted;
}
else {
        print '<h1>Device is connected</h1>';
}
print '</body></html>';

umount.pl

该程序用于将系统中的/dev/sda1设备卸载。

#!/usr/bin/perl
print "Content-type:text/html\n\n";
print "<html><head><title>Umount</title></head><body>";
system("sudo", "umount", "/dev/sda1");
print "</body></html>";

测试场景

  1. Manually mounting the device /dev/sda1, (it is declared in fstab):

    $ mount -a
    

    Making sure the device is mounted in the system:

    $ mount
    /dev/sda1 on /mnt/usbstick type vfat (rw,relatime,uid=1000,gid=1000,fmask=0137,dmask=0027,codepage=437,iocharset=ascii,shortname=mixed,utf8,errors=remount-ro)
    
  2. Executing panelmin.pl in the web browser (I will use curl for the purpose of clean output):

    $ curl http://localhost/cgi-bin/admin/Q/panel/panelmin.pl
    <html><head><title>USB test</title></head><body><h1>Device is connected</h1></body></html>
    

    As we can see, the output is correct. It detected the through linux command df -h that the device /dev/sda1 is mounted in the system.

  3. Executing umount.pl in the web browser in order to umount the device:

    $ curl http://localhost/cgi-bin/admin/Q/panel/umount.pl
    <html><head><title>Umount</title></head><body></body></html>
    
  4. Verifying if the device is umounted using both panelmin.pl script in the web browser and linux command line.

    $ curl http://localhost/cgi-bin/admin/Q/panel/panelmin.pl
    <html><head><title>USB test</title></head><body><h1>USB device not connected</h1></body></html>
    

    Seems to be correct, but let's verify it manually with df -h command:

    $ df -h | grep /dev/sda1
    /dev/sda1        15G  366M   15G   3% /mnt/usbstick
    

    As we can see the device is still mounted in the system.

  5. Let's retry the whole process but this time instead of executing scripts in the browser we will launch them manually with Perl in the terminal. First lets umount the device. This will also show that the user is in sudoers and script can umount it.

    $ sudo umount /dev/sda1
    $ df -h | grep /dev/sda1
    

    Let's repeat the process.

    $ mount -a (as superuser)
    $ df -h | grep /dev/sda1
    /dev/sda1        15G  366M   15G   3% /mnt/usbstick
    

    And finally the test:

    $ perl panelmin.pl 
    Content-type:text/html
    <html><head><title>USB test</title></head><body><h1>Device is connected</h1></body>
    
    $ perl umount.pl 
    Content-type:text/html
    <html><head><title>Umount</title></head><body></body></html>
    
    $ perl panelmin.pl 
    Content-type:text/html
    <html><head><title>USB test</title></head><body><h1>USB device not connected</h1></body></html>
    
    $ df -h | grep /dev/sda1
    

    Now df -h | grep /dev/sda1 returned empty string, it has proven that umount.pl managed to umount the device from the system, but only if executed in the shell with Perl.

解决问题的尝试

  1. 确保在umount.pl中执行sudo umount /dev/sda1的用户有权执行此操作,我通过在脚本中执行whoami并将其打印在输出中进行了验证。

  2. 尝试不同语法的system(),例如分离参数或作为一个命令执行。

  3. 我验证了我的mod_perl是否以taint mode模式执行脚本,因为我听说它可能会影响执行外部进程。实际上,在我的httpd.conf中没有PerlTaintCheck On,如果我理解正确,它会影响使用动态参数执行(出于安全原因),对于我的脚本来说,每次都是相同的命令。

  4. 我尝试使用不同的函数,用Exec或反引号替换System等。

  5. 我尝试使用Apache2::SubProcess,就像他们在这里所做的那样,但我必须承认,我不确定我是否做得正确,我是Perl的新手,这(以及所有其他可用的)示例都太糟糕了,例如他们的代码中从未初始化$r变量,我只是将其声明为Apache2::RequestRec对象,但它没有起作用。

  6. 我尝试按照这里的说明更改环境变量,在子节10.2.6. Starting a Long-Running External Program中进行了说明。

  7. 我验证了Apache目录中的日志,发现错误,但与这些脚本无关,我没有在此处发布它们,因为我不希望它变得太混乱。

解释

事实上,它在Perl中运行而在mod_perl中不运行并不令人惊讶,我认为最奇怪的是,在使用其system()启动的panelmin.pl脚本之后,执行umount.pl后,它不再正确工作。目前我已经没有主意了,我认为这种行为很奇怪,我指望你们。希望有人知道我该怎么做。谢谢。

要求的结果

  1. Capturing the output of System in umount.pl.

    #!/usr/bin/perl
    print "Content-type:text/html\n\n";
    print "<html><head><title>Umount</title></head><body>";
    my $res = `sudo umount /dev/sda1`;
    print "$res</body></html>";
    

    And the result is void:

    $ curl http://localhost/cgi-bin/admin/Q/panel/umount.pl
    <html><head><title>Umount</title></head><body></body></html>
    

我可以建议捕获“umount”命令的输出吗? 我猜mod_perl调用是以不同的权限运行的,可能无法使用sudo。 - Sobrique
检查 system() 的返回值或使用 use autodie qw(:all); - mpapec
编译autodie及其所有依赖项花费了一些时间(我在树莓派上工作),所以我把它留到了晚上。现在我刚刚验证了你建议的方法,不幸的是它仍然无法卸载设备。 - Marek
从您的外壳命令中捕获stderr的输出。 sudo umount /dev/sda1 2>1 - Miller
谢谢Miller。我在命令的末尾添加了2>1,用反引号捕获输出,输出与其他情况一样为空,并且我还使用Perl从shell执行了脚本并执行了命令本身,但没有输出。 - Marek
1个回答

0

原来问题并非与编程有关,而是与服务器配置有关。

这是Apache的问题。它在多线程/多进程环境中工作。我不确定为什么,但它使他无法处理system()函数。当我将其限制为单进程模式时,问题得到解决。

即使知道问题与编程无关,我认为它应该留在这里,因为当出现此问题时,可能很难确定问题的来源,我想对于大多数初学者来说,StackOverflow应该是首先要寻找答案的地方。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接