在调用Runtime exec工作的系统命令时,ProcessBuilder失败

时间:2017-03-30 13:56:49

标签: java gwt tomcat8 processbuilder

我在使用ProcessBuilder时没有在服务器上运行命令。

在我的项目早期,我使用Runtime.exec()只是为了从一个正常运行的程序中检索输出:

private List<SatelliteCode> getSatelliteCodes() {
    List<SatelliteCode> codes = new ArrayList<>();

    Runtime runtime = Runtime.getRuntime();
    String[] commands = { "w_scan", "-s?" };
    Process process;

    try {
        process = runtime.exec(commands);

        BufferedReader error = new BufferedReader(new InputStreamReader(process.getErrorStream()));

        String s = error.readLine(); // discard first line

        while ((s = error.readLine()) != null) {
            s = s.trim();
            int i = s.indexOf('\t'); // separated by a tab!?!?
            codes.add(new SatelliteCode(s.substring(0, i), s.substring(i)));
        }
    } catch (IOException e) {
        e.printStackTrace();
    }

    return codes;
}

在终端中运行它可以正常工作,我得到了我需要的所有输出:

w_scan -fs -cGB -sS19E2 > channels.conf

但是,服务器需要从“进程”中获取正在进行的输出.getErrorStream()&#39;在Web界面中显示。实际发生的是ProcessBuilder失败并返回1的退出代码。

初始化ProcessBuilder并开始扫描运行的函数是[EDIT 1]:

private static StringBuilder scan_error_output = null;

@Override
public boolean startSatelliteScan(String user, String country_code, String satellite_code) {
    UserAccountPermissions perm = validateUserEdit(user);
    if (perm == null) return false;

    Shared.writeUserLog(user, Shared.getTimeStamp() + 
            ": DVB satellite scan started " + 
            country_code + " - " + satellite_code + 
            System.lineSeparator() + System.lineSeparator());

    scan_error_output = new StringBuilder();
    new ScanThread(country_code, satellite_code).start();

    // write out country code and satellite code to prefs file

    BufferedWriter bw = null;
    try {
        bw = new BufferedWriter(new FileWriter(satellite_last_scan_codes));

        bw.write(country_code); bw.newLine();
        bw.write(satellite_code); bw.newLine();

        bw.close();
    } catch (IOException e) {
        e.printStackTrace();
    }

    return true;
}

然后在服务器上运行另外两个线程,一个将自己运行扫描并等待它完成,以便它可以获得最终的扫描数据。另一个不断更新std错误流的输出,然后从客户端浏览器定期轮询。这很像显示终端的持续输出。

扫描线程(无法启动进程)[编辑1]:

private static class ScanThread extends Thread {
    private String cc, sc;

    public ScanThread(String country_code, String satellite_code) {
        cc = country_code;
        sc = satellite_code;
    }

    public void run() {
        ProcessBuilder pb = new ProcessBuilder("/usr/bin/w_scan", 
                "-fs", "-c" + cc, "-s" + sc);
        pb.redirectOutput(new File(satellite_scan_file));

        Process process;
        try {
            System.out.println("Scan thread started");

            process = pb.start();

            IOScanErrorOutputHandler error_output_handler = new IOScanErrorOutputHandler(process.getErrorStream());
            error_output_handler.start();

            int result = process.waitFor();
            System.out.println(cc + " - " + sc + " - " + 
                    "Process.waitFor() result " + result);

        } catch (IOException e) {
            System.out.println(e.getMessage());
            e.printStackTrace();
        } catch (InterruptedException e) {
            System.out.println(e.getMessage());
            e.printStackTrace();
        }

        System.out.println("Scan thread finished");
    }
}

错误输出流线程捕获显然由于扫描线程失败而无法启动的输出:

private static class IOScanErrorOutputHandler extends Thread {
    private InputStream inputStream;

    IOScanErrorOutputHandler(InputStream inputStream) {
        this.inputStream = inputStream;
    }

    public void run() {
        Scanner br = null;
        try {
            System.out.println("Scan thread Error IO capture running");
            br = new Scanner(new InputStreamReader(inputStream));
            String line = null;
            while (br.hasNextLine()) {
                line = br.nextLine();
                scan_error_output.append(line + System.getProperty("line.separator"));
            }
        } finally {
            br.close();
        }
        System.out.println("Scan thread Error IO capture finished");

        scan_error_output = null;
    }
}

返回std错误输出进度的服务器函数:

@Override
public String pollScanResult(String user) {
    if (validateUserEdit(user) == null) return null;
    StringBuilder sb = scan_error_output; // grab instance
    if (sb == null) return null;
    return sb.toString();
}

如上所述,Runtime.exec()工作正常,但ProcessBuilder失败。

注意:我在Linux Mint 18.1上使用Apache Tomcat 8作为服务器,在Eclipse Neon中使用Linux默认JDK 8和GWT 2.7 [来自2.8的更正]。

谁能看到我做错了什么?

非常感谢...

[编辑1]

虽然在DVB-T的另一台机器Linux Mint 17.2,JDK 8和Apache Tomcat 7上开发了这个,但这种方法运行良好,并且在客户端的浏览器中显示了扫描输出的轮询。

ProcessBuilder.start仍然返回1,并为输出扫描文件创建一个空文件。

[编辑2]

似乎ProcessBuilder失败的原因是因为用户&#39; tomcat8&#39;没有权限运行&#39; w_scan&#39;。 &#39; w_scan&#39;从终端工作,但不从tomcat服务器工作。不知怎的,我现在必须解决这个问题。

[解决方案]

在被VGR放入正确的方向以获取ProcessBuilder的错误流后,我开始进一步挖掘并发现我得到了:

main:3909: FATAL: failed to open '/dev/dvb/adapter0/frontend0': 13 Permission denied

Apache tomcat 8没有权限访问DVB-S前端来运行扫描。这有两种解决方法:

1 - 03catalina.policy我添加了额外的权限(他们是否有所作为我不知道)。

grant codeBase "file:/dev/dvb/-" {
    permission java.io.FilePermission "file:/dev/dvb/-", "read, write";
    permission java.security.AllPermission;
};

2 - dvb前端属于&#39;视频&#39;组。所以我需要将用户tomcat8添加到该组。

usermod -a -G video tomcat8

现在一切正常......

1 个答案:

答案 0 :(得分:4)

你对Runtime.exe所做的ProcessBuilder没有做同样的事情,所以我不知道为什么你认为ProcessBuilder就是问题。

关于如何将命令的输出写入文件,您遇到了一些问题。

首先,ProcessBuilder命令中">", satellite_scan_temp_file的存在是不正确的。输出重定向不是任何命令的一部分;它由shell处理。但是当您使用Runtime.exec或ProcessBuilder运行时,您没有在shell中运行,而是直接执行该过程。 w_scan和任何其他命令都不会将>视为特殊字符。

重定向到文件的正确方法是使用redirectOutput方法:

ProcessBuilder pb = new ProcessBuilder(
    "/usr/bin/w_scan", "-fs", "-s" + satellite_code, "-c" + country_code);

pb.redirectOutput(new File(satellite_scan_temp_file));

其次,您的ScanThread代码忽略了(当前不正确的)重定向,并且正在尝试读取命令的输出。但是没有输出,因为你将它全部重定向到一个文件。

将输出正确地重定向到文件后,可以完全删除BufferedReader和BufferedWriter循环。

最后,值得注意的是,您捕获的错误输出可能告诉您>不是w_scan进程的有效参数。