1 /*
2 * Copyright 2016-2022 The OSHI Project Contributors
3 * SPDX-License-Identifier: MIT
4 */
5 package oshi.util;
6
7 import java.io.BufferedReader;
8 import java.io.IOException;
9 import java.io.InputStreamReader;
10 import java.nio.charset.Charset;
11 import java.util.ArrayList;
12 import java.util.Arrays;
13 import java.util.Collections;
14 import java.util.List;
15
16 import org.slf4j.Logger;
17 import org.slf4j.LoggerFactory;
18
19 import com.sun.jna.Platform;
20
21 import oshi.annotation.concurrent.ThreadSafe;
22
23 /**
24 * A class for executing on the command line and returning the result of execution.
25 */
26 @ThreadSafe
27 public final class ExecutingCommand {
28
29 private static final Logger LOG = LoggerFactory.getLogger(ExecutingCommand.class);
30
31 private static final String[] DEFAULT_ENV = getDefaultEnv();
32
33 private ExecutingCommand() {
34 }
35
36 private static String[] getDefaultEnv() {
37 if (Platform.isWindows()) {
38 return new String[] { "LANGUAGE=C" };
39 } else {
40 return new String[] { "LC_ALL=C" };
41 }
42 }
43
44 /**
45 * Executes a command on the native command line and returns the result. This is a convenience method to call
46 * {@link java.lang.Runtime#exec(String)} and capture the resulting output in a list of Strings. On Windows,
47 * built-in commands not associated with an executable program may require {@code cmd.exe /c} to be prepended to the
48 * command.
49 *
50 * @param cmdToRun Command to run
51 * @return A list of Strings representing the result of the command, or empty string if the command failed
52 */
53 public static List<String> runNative(String cmdToRun) {
54 String[] cmd = cmdToRun.split(" ");
55 return runNative(cmd);
56 }
57
58 /**
59 * Executes a command on the native command line and returns the result line by line. This is a convenience method
60 * to call {@link java.lang.Runtime#exec(String[])} and capture the resulting output in a list of Strings. On
61 * Windows, built-in commands not associated with an executable program may require the strings {@code cmd.exe} and
62 * {@code /c} to be prepended to the array.
63 *
64 * @param cmdToRunWithArgs Command to run and args, in an array
65 * @return A list of Strings representing the result of the command, or empty string if the command failed
66 */
67 public static List<String> runNative(String[] cmdToRunWithArgs) {
68 return runNative(cmdToRunWithArgs, DEFAULT_ENV);
69 }
70
71 /**
72 * Executes a command on the native command line and returns the result line by line. This is a convenience method
73 * to call {@link java.lang.Runtime#exec(String[])} and capture the resulting output in a list of Strings. On
74 * Windows, built-in commands not associated with an executable program may require the strings {@code cmd.exe} and
75 * {@code /c} to be prepended to the array.
76 *
77 * @param cmdToRunWithArgs Command to run and args, in an array
78 * @param envp array of strings, each element of which has environment variable settings in the format
79 * name=value, or null if the subprocess should inherit the environment of the current
80 * process.
81 * @return A list of Strings representing the result of the command, or empty string if the command failed
82 */
83 public static List<String> runNative(String[] cmdToRunWithArgs, String[] envp) {
84 Process p = null;
85 try {
86 p = Runtime.getRuntime().exec(cmdToRunWithArgs, envp);
87 return getProcessOutput(p, cmdToRunWithArgs);
88 } catch (SecurityException | IOException e) {
89 LOG.trace("Couldn't run command {}: {}", Arrays.toString(cmdToRunWithArgs), e.getMessage());
90 } finally {
91 // Ensure all resources are released
92 if (p != null) {
93 // Windows and Solaris don't close descriptors on destroy,
94 // so we must handle separately
95 if (Platform.isWindows() || Platform.isSolaris()) {
96 try {
97 p.getOutputStream().close();
98 } catch (IOException e) {
99 // do nothing on failure
100 }
101 try {
102 p.getInputStream().close();
103 } catch (IOException e) {
104 // do nothing on failure
105 }
106 try {
107 p.getErrorStream().close();
108 } catch (IOException e) {
109 // do nothing on failure
110 }
111 }
112 p.destroy();
113 }
114 }
115 return Collections.emptyList();
116 }
117
118 private static List<String> getProcessOutput(Process p, String[] cmd) {
119 ArrayList<String> sa = new ArrayList<>();
120 try (BufferedReader reader = new BufferedReader(
121 new InputStreamReader(p.getInputStream(), Charset.defaultCharset()))) {
122 String line;
123 while ((line = reader.readLine()) != null) {
124 sa.add(line);
125 }
126 p.waitFor();
127 } catch (IOException e) {
128 LOG.trace("Problem reading output from {}: {}", Arrays.toString(cmd), e.getMessage());
129 } catch (InterruptedException ie) {
130 LOG.trace("Interrupted while reading output from {}: {}", Arrays.toString(cmd), ie.getMessage());
131 Thread.currentThread().interrupt();
132 }
133 return sa;
134 }
135
136 /**
137 * Return first line of response for selected command.
138 *
139 * @param cmd2launch String command to be launched
140 * @return String or empty string if command failed
141 */
142 public static String getFirstAnswer(String cmd2launch) {
143 return getAnswerAt(cmd2launch, 0);
144 }
145
146 /**
147 * Return response on selected line index (0-based) after running selected command.
148 *
149 * @param cmd2launch String command to be launched
150 * @param answerIdx int index of line in response of the command
151 * @return String whole line in response or empty string if invalid index or running of command fails
152 */
153 public static String getAnswerAt(String cmd2launch, int answerIdx) {
154 List<String> sa = ExecutingCommand.runNative(cmd2launch);
155
156 if (answerIdx >= 0 && answerIdx < sa.size()) {
157 return sa.get(answerIdx);
158 }
159 return "";
160 }
161
162 }