为什么 Linux 不执行具有 Java 执行权限的 bash 脚本?

Why won't Linux execute a bash script that has execution permission from Java?

有很多问题看起来与此类似,但其中 none 似乎完全相同,而且他们的所有解决方案都不适合我,所以这里是...

我有一个 Java 程序试图自动更新自身。它是这样工作的:

如果一切正常,bash 脚本现在将启动 Java 主 class。

此列表中的所有内容实际上都有效,除了我尝试在最后执行 bash 脚本的最后一步。

bash 文件权限如下所示(在我 运行 自动更新程序之前和之后):

-rwxr-xr-x

据我所知,运行宁这个文件应该没有权限问题(事实上,我可以 运行 在进程崩溃后立即手动它)。

这是我得到的错误:

java.io.IOException: Cannot run program "/home/xxx/build/image/bin/run": error=13, Permission denied
at java.base/java.lang.ProcessBuilder.start(Unknown Source)
at java.base/java.lang.ProcessBuilder.start(Unknown Source)
at java.base/java.lang.Runtime.exec(Unknown Source)
at java.base/java.lang.Runtime.exec(Unknown Source)
at java.base/java.lang.Runtime.exec(Unknown Source)
at my_mod/my.Main.main(Unknown Source)

我写了一个 "process bomb",它的工作原理类似,但确实有效:

所以我认为这不是某种 Linux 防止像这样的进程炸弹的保护。

但我完全不明白为什么 Linux 在更新后仍然拒绝访问 运行ning 那个 bash 脚本。

我的 OS 是 Ubuntu 19.

我如何找出它拒绝访问的原因,更重要的是,我该如何解决它?

我终于找到了问题的根本原因:是文件权限问题,但是不是错误消息中提到的文件

我解压新应用程序更新的方式是通过在 Java 中编写一个简单的 "unzipper",基本实现如下:

    try (var zip = new ZipInputStream(
            new BufferedInputStream(
                    new FileInputStream(newVersionZipFile), 4096))) {
        var zipEntry = zip.getNextEntry();
        if (zipEntry == null) {
            throw new IllegalStateException("Expected at least one entry in the zip file: " + newVersionZipFile);
        }
        var topEntryName = zipEntry.getName();
        zipEntry = zip.getNextEntry();
        while (zipEntry != null) {
            var file = fileFor(zipEntry, destinationDir, topEntryName);
            if (isDirectory(zipEntry)) {
                var ok = file.mkdir();
                if (!ok) throw new IllegalStateException("Cannot create new directory: " + file);
            } else {
                Files.copy(zip, file.toPath());
            }
            zipEntry = zip.getNextEntry();
        }
    }

这在大多数情况下确实有效,但它有一个大问题:它没有考虑文件权限。我已经注意到这个问题并在解压后将bin目录中的所有内容都设置为可执行文件,但我没有想到的是jlink创建的minimal-JVM中有更多可执行文件。而且因为该应用程序实际上大部分都能正常工作,所以我觉得一切都很好!

使用 tree 命令,我查看了使用 Linux 的 unzip 解压缩后的目录树,并使用我的自定义 Java 解压缩。我注意到,在 unzip 中,除了 bin 目录中的文件外,以下文件也具有执行权限:

├── [drwxrwxr-x]  lib
│   ├── [-rw-rw-r--]  classlist
│   ├── [-rw-rw-r--]  javafx.properties
│   ├── [-rw-rw-r--]  javafx-swt.jar
│   ├── [-rwxrwxr-x]  jexec
│   ├── [-rw-rw-r--]  jrt-fs.jar
│   ├── [-rwxrwxr-x]  jspawnhelper
...

相比之下,Java 解压器解压后的树看起来像这样:

├── [drwxrwxr-x]  lib
│   ├── [-rw-rw-r--]  classlist
│   ├── [-rw-rw-r--]  javafx.properties
│   ├── [-rw-rw-r--]  javafx-swt.jar
│   ├── [-rw-rw-r--]  jexec
│   ├── [-rw-rw-r--]  jrt-fs.jar
│   ├── [-rw-rw-r--]  jspawnhelper
...

注意到两个文件应该具有执行权限:jexecjspawnhelper,这两个文件的名称都强烈暗示它们与执行其他进程有关 :D

为了验证这个假设,我让解压器也将这两个文件的权限更改为可执行,现在一切正常!

我现在的挑战是如何在解压缩文件时真正保留所有文件的文件权限,这是另一个难题,因为没有标准方法从标准 zip 获取文件权限(这就是为什么 Java API for zips 没有该功能)。有大部分工作的外部库,所以我可能最终会使用其中一个...但这是另一个话题。