java - 为什么 Linux 不执行具有 Java 执行权限的 bash 脚本?
问题描述
有很多看起来与此类似的问题,但似乎没有一个问题完全相同,而且他们的所有解决方案都对我不起作用,所以这里......
我有一个尝试自动更新自身的 Java 程序。它是这样工作的:
- 启动时,bash 脚本会检查某个位置是否存在 zip 文件。
- 如果存在,它不会运行“真正的”主类,而是运行更新程序 Java 类。
- 此类将在临时位置解压缩 zip,然后从该临时位置在单独的进程中重新启动自身。
- 一旦新进程开始,旧进程就会退出。
- 新进程尝试再次将 zip 解压缩到之前的位置(现在没有任何运行,因此可以替换)。
- 成功后,它会删除 zip 文件并再次运行原始 bash 脚本。
如果一切正常,bash 脚本现在将启动 Java 主类。
此列表中的所有内容实际上都有效,除了最后一步我尝试在最后执行 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)
我写了一个类似的“过程炸弹”,但它有效:
- 首先运行一个启动主类的 bash 脚本
主类使用以下代码再次启动 bash 脚本:
// DO NOT RUN THIS AT HOME! // IT WILL START LOTS OF PROCESSES INFINITELY // if you do want to try it, have "killall -9 java" ready! try { Runtime.getRuntime().exec( path ) System.exit(0); } catch ( Exception e ) { e.printStackTrace(); }
所以我认为这不是某种针对此类进程炸弹的 Linux 保护。
但我完全不明白为什么 Linux 在更新后仍然拒绝访问运行该 bash 脚本。
我的操作系统是 Ubuntu 19。
我怎样才能找出它拒绝访问的原因,更重要的是,我该如何解决它?
解决方案
我终于找到了问题的根本原因:它是由于文件权限,而不是错误消息提到的文件!
我解压新应用程序更新的方法是用 Java 编写一个简单的“解压缩器”,它具有以下基本实现:
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 创建的最小 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
...
注意到两个文件应该具有执行权限:jexec
和jspawnhelper
,这两个文件的名称都强烈暗示它们与执行其他进程有关:D
为了验证这个假设,我让解压缩器也将这两个文件的权限更改为可执行,现在一切正常!
我的挑战现在转向如何在解压缩文件时实际保留所有文件的文件权限,这是另一个兔子洞,因为没有从标准 zip 获取文件权限的标准方法(这就是为什么用于 zips 的 Java API 没有该功能)。有一些外部库大部分都可以工作,所以我最终可能会使用其中一个......但这是另一个话题。
推荐阅读
- javascript - 如何验证多个输入的 IP 地址?
- javascript - 无需刷新页面即可获取 sessionStorage 数据
- powerbi - 移动 Web 浏览器中的 Power BI RS Web 嵌入
- angular - ionic3中令牌过期时如何注销?
- angular6 - 弗罗拉
- 列表项未出现
- gstreamer - Gstreamer:如何使用 spca561 驱动程序从 USB 摄像头播放视频?
- java - 为什么下载管理器完成后广播接收器不调用?
- abap - 功能不可用:功能等效可用
- octave - 如何减少八度图例功能中的空白
- php - Shopify 在 shopify 上发送带有选项的产品