在Java中执行外部程序将返回“拒绝访问”或停止执行

时间:2014-07-10 11:25:23

标签: java sqlcipher

更新3

进展!我写了一个CMD脚本,它有三个参数:

  1. sqlite3cipher.exe
  2. 的绝对路径
  3. testdb
  4. 的绝对路径
  5. statements.sql
  6. 的绝对路径

    所有脚本都执行命令

    path\to\sqlite3cipher.exe path\to\testdb < path\to\statements.sql
    

    从我的Java代码中调用此脚本。它就像一个魅力!至少所有文件都驻留在我的主目录中的文件夹中。将文件放在C:\Program Files子文件夹中并不起作用,因此可能存在我必须与管理员协商的权限问题。


    更新2

    我在我的主目录中创建了一个虚拟SQL文件。代码现在应该从中读取SQL文件并将数据库文件写入我的主目录。

    再次省略命令中的cmd /c会从SQL文件中读取和写入第一行,但在此之后停止(整个程序的执行停止)。 执行包含cmd /c的命令读取和写入SQL文件的每一行但是一旦达到EOF(当然没有任何内容会被写入)我再次收到Access denied消息。


    更新1

    skarist的评论考虑在内以下命令(省略cmd /c,因为执行exe文件不是必须的)

    String cmdForRuntime = "\"" + prog.getAbsolutePath() + "\" \"" + fDb.getAbsolutePath() + "\"";
    
    拼写出来的

    看起来像

    "C:\Program Files\Apache Software Foundation\bin\sqlite3cipher.exe" "C:\Program Files\Apache Software Foundation\pages\dbdir\testdb"
    

    不会产生任何输出。代码也停止执行,所以我认为I / O有问题吗?


    问题

    为了编码数据库,我想在Java命令行上执行sqlite3cipher.exe

    我遇到了各种方法的相当不同的结果。首先是设置(注意这是一个测试环境,现在没有任何关于文件所在位置的更改):

    • sqlite3cipher.exe位于C:\Program Files\Apache Software Foundation\bin\

    • 空文件testdb位于C:\Program Files\Apache Software Foundation\pages\dbdir\

    • 包含SQL语句statements.sql的文件与testdb位于同一文件夹中

    在命令行上执行sqlite3cipher.exe -help打印用法。这是一个exerpt:

    C:\Program Files\Apache Software Foundation\bin\sqlite3cipher.exe [OPTIONS] FILENAME [SQL] 
    FILENAME is the name of an SQLite database. A new database is created if the file does not previously exist.
    

    在命令行上执行"C:\Program Files\Apache Software Foundation\bin\sqlite3cipher.exe" "C:\Program Files\Apache Software Foundation\pages\dbdir\testdb" < "C:\Program Files\Apache Software Foundation\pages\dbdir\statements.sql"具有所需的效果:testdb现在是根据通过statements.sql放入的SQL语句的加密数据库。

    现在在Java中,我的代码如下所示:

    String cmdForRuntime = "cmd /C \"" + prog.getAbsolutePath() + "\" \"" + fDb.getAbsolutePath() + "\" < \"" + fSqlScript.getAbsolutePath() + "\"";
    Process process = Runtime.getRuntime().exec( cmdForRuntime );
    
    BufferedReader stdIn = new BufferedReader(new InputStreamReader(process.getInputStream()));
    String s;
    
    while ((s = stdIn.readLine()) != null) {
        logger.info("OUTPUT FROM PROG: " + s);
    }
    
    if (process.waitFor() == 0) {
        logger.info("Process finished");
    }
    else {
        logger.error("Error!");
    
        // Get input streams
        BufferedReader stdError = new BufferedReader(new InputStreamReader(process.getErrorStream()));
    
        System.out.println("Standard error: ");
        while ((s = stdError.readLine()) != null) {
            System.out.println(s);
        }
    }
    

    其中progfDbfSqlScript的类型为File,分别代表sqlite3cipher.exetestdbstatements.sql

    我对Process.execute()并不精通,但我从阅读中得到的是cmd /c作为命令的一部分在Windows上是必需的(测试服务器运行的是Windows 8)为了启动命令解释器。

    执行时,此代码在命令行上返回Access denied(通过进程的流)。在命令行上执行此确切命令(包括cmd /c)会直接返回相同的结果:Access denied。即使我以管理员身份启动命令行。

    cmd /c离开命令(因为它之前没有在命令行上工作),我得到的信息是too many options。 经过进一步调查,我了解到这个问题是由<运算符引起的,该运算符在命令中用于重定向输入。 exec()方法无法将<识别为运营商,建议您使用该流程OutputStream。所以我将代码更改为:

    String cmdForRuntime = "cmd /c \"" + prog.getAbsolutePath() + "\" \"" + fDb.getAbsolutePath() + "\"";
    Process process = Runtime.getRuntime().exec( cmdForRuntime );
    
    BufferedWriter bufferedwriter = new BufferedWriter(new OutputStreamWriter(process.getOutputStream()));
    BufferedReader br = new BufferedReader(new FileReader(fSqlScript.getAbsolutePath()));
    
    BufferedReader stdIn = new BufferedReader(new InputStreamReader(process.getInputStream()));
    String s;
    String fileInput = br.readLine();
    
    while (fileInput != null) {
    
        bufferedwriter.write(fileInput);
    
        while ((s = stdIn.readLine()) != null) {
            logger.info("OUTPUT FROM PROG: " + s);
        }
    
        fileInput = br.readLine();
    }
    
    if (process.waitFor() == 0) {
        logger.info("Process finished");
    }
    else {
        logger.error("Error!");
    
        // Get input streams
        BufferedReader stdError = new BufferedReader(new InputStreamReader(process.getErrorStream()));
    
        System.out.println("Standard error: ");
        while ((s = stdError.readLine()) != null) {
            System.out.println(s);
        }
    }
    

    据我所知,阅读外部程序打印的内容非常重要,因此它不会阻止或停止工作。这就是为什么我从statements.sql(通过BufferedReader br)读取一行并将其写入流程OutputStream的原因。然后我读了程序将打印出来的内容(记录该输出)。如果没有(左)阅读,我会阅读statements.sql的下一行。这将重复进行,直到没有任何内容可以从所述文件中读取。

    如果我执行此代码,我的记录器将记录以下内容:

    INFO  Executing command: cmd /c "C:\Program Files\Apache Software Foundation\bin\sqlite3cipher.exe" "C:\Program Files\Apache Software Foundation\pages\dbdir\testdb"
    ERROR Error!
    Standard error: 
    Access denied
    

    在这种情况下,代码至少会终止,从而导致空文件testdb。在另一种方法中,我在命令中保留cmd但跳过了/c,导致以下输出:

    INFO  Executing command: cmd "C:\Program Files\Apache Software Foundation\bin\sqlite3cipher.exe" "C:\Program Files\Apache Software Foundation\pages\dbdir\testdb"
    INFO  OUTPUT FROM PROG: Microsoft Windows [Version 6.3.9600]
    INFO  OUTPUT FROM PROG: (c) 2013 Microsoft Corporation. Alle Rechte vorbehalten.
    INFO  OUTPUT FROM PROG: 
    

    输出与在命令行中键入cmd时得到的输出相同。执行停止的地方。


    TL;博士

    • 在命令行上执行直接像魅力一样
    • 从Java执行的完全相同的命令不起作用
    • 命令由cmd /c作为前缀(显然应该是这样),并且流程InputStreamOutputStream的使用不起作用:Access denied

    我不确定下一步该做什么,所以我真诚地希望有人可以帮助我。

2 个答案:

答案 0 :(得分:1)

所以我用更新3中提到的解决方法解决了这个问题。

我的Java代码现在看起来像这样(除了cmdForRuntime变量之外,它应该与开头时的代码几乎相同,当我问这个问题时):

try {
        File fDb = new File(this.inputDirectory + File.separator + dbName);

        String cmdForRuntime =  "\"" + cmdScript.getAbsolutePath() + "\"" + " " +       // the script to be executed 
                                "\"" + prog.getAbsolutePath() + "\"" + " " +            // sqlite3cipher.exe
                                "\"" + fDb.getAbsolutePath() + "\"" + " " +             // testdb
                                "\"" + fSqlScript.getAbsolutePath() + "\"";             // statements.sql

        logger.info("Executing command: " + cmdForRuntime );

        Process process = Runtime.getRuntime().exec( cmdForRuntime );

        BufferedReader stdIn = new BufferedReader(new InputStreamReader(process.getInputStream()));
        String s;

        while ((s = stdIn.readLine()) != null) {
            logger.info("OUTPUT FROM PROG: " + s);
        }

        if (process.waitFor() == 0) {
            logger.info("Process finished");
        }
        else {
            logger.error("!!!! error !!!!");
            // Get input streams
            BufferedReader stdError = new BufferedReader(new InputStreamReader(process.getErrorStream()));

            System.out.println("Standard error: ");
            while ((s = stdError.readLine()) != null) {
                System.out.println(s);
            }
        }
    }
    catch(Exception ioe) {
        ioe.printStackTrace();
    }       

cmdForRuntime会读到这样的内容(我会省略路径,唯一会增加混淆):

"SQLiteCipher.cmd" "sqlite3cipher.exe" "testdb" "statements.sql"

脚本的内容如下所示:

%1 %2 < %3

这会调用带有参数sqlite3cipher.exe的{​​{1}}和来自testdb的重定向输入。

严重的是脚本的位置以及exe的位置不是系统文件夹。

如果满足所有这些标准,一切都像魅力一样。

答案 1 :(得分:0)

您正在User Account Control issue

程序似乎在非提升模式下运行,以便更改应用程序需要在提升模式下运行的系统目录。

以提升模式运行程序应该可以解决问题。