View Javadoc
1   /*
2    * Copyright 2016-2024 The OSHI Project Contributors
3    * SPDX-License-Identifier: MIT
4    */
5   package oshi.util;
6   
7   import java.io.BufferedReader;
8   import java.io.ByteArrayOutputStream;
9   import java.io.File;
10  import java.io.IOException;
11  import java.io.InputStream;
12  import java.io.InputStreamReader;
13  import java.io.Reader;
14  import java.net.URL;
15  import java.nio.ByteBuffer;
16  import java.nio.ByteOrder;
17  import java.nio.charset.CharsetDecoder;
18  import java.nio.charset.StandardCharsets;
19  import java.nio.file.Files;
20  import java.nio.file.Path;
21  import java.nio.file.Paths;
22  import java.util.ArrayList;
23  import java.util.Arrays;
24  import java.util.Collections;
25  import java.util.HashMap;
26  import java.util.LinkedHashSet;
27  import java.util.List;
28  import java.util.Map;
29  import java.util.Properties;
30  import java.util.stream.Collectors;
31  import java.util.stream.Stream;
32  
33  import org.slf4j.Logger;
34  import org.slf4j.LoggerFactory;
35  
36  import com.sun.jna.Native;
37  import com.sun.jna.NativeLong;
38  import com.sun.jna.Pointer;
39  import com.sun.jna.platform.unix.LibCAPI.size_t;
40  
41  import oshi.annotation.concurrent.ThreadSafe;
42  
43  /**
44   * File reading methods
45   */
46  @ThreadSafe
47  public final class FileUtil {
48  
49      private static final Logger LOG = LoggerFactory.getLogger(FileUtil.class);
50  
51      private static final String READING_LOG = "Reading file {}";
52      private static final String READ_LOG = "Read {}";
53  
54      private static final int BUFFER_SIZE = 1024;
55  
56      private FileUtil() {
57      }
58  
59      /**
60       * Read an entire file at one time. Intended primarily for Linux /proc filesystem to avoid recalculating file
61       * contents on iterative reads.
62       *
63       * @param filename The file to read
64       * @return A list of Strings representing each line of the file, or an empty list if file could not be read or is
65       *         empty
66       */
67      public static List<String> readFile(String filename) {
68          return readFile(filename, true);
69      }
70  
71      /**
72       * Read an entire file at one time. Intended primarily for Linux /proc filesystem to avoid recalculating file
73       * contents on iterative reads.
74       *
75       * @param filename    The file to read
76       * @param reportError Whether to log errors reading the file
77       * @return A list of Strings representing each line of the file, or an empty list if file could not be read or is
78       *         empty
79       */
80      public static List<String> readFile(String filename, boolean reportError) {
81          if (new File(filename).canRead()) {
82              if (LOG.isDebugEnabled()) {
83                  LOG.debug(READING_LOG, filename);
84              }
85              try {
86                  return Files.readAllLines(Paths.get(filename), StandardCharsets.UTF_8);
87              } catch (IOException e) {
88                  if (reportError) {
89                      LOG.error("Error reading file {}. {}", filename, e.getMessage());
90                  } else {
91                      LOG.debug("Error reading file {}. {}", filename, e.getMessage());
92                  }
93              }
94          } else if (reportError) {
95              LOG.warn("File not found or not readable: {}", filename);
96          }
97          return Collections.emptyList();
98      }
99  
100     /**
101      * Read count lines from a file. Intended primarily for Linux /proc filesystem to avoid recalculating file contents
102      * on iterative reads.
103      *
104      * @param filename The file to read
105      * @param count    The number of lines to read
106      * @return A list of Strings representing the first count lines of the file, or an empty list if file could not be
107      *         read or is empty
108      */
109     public static List<String> readLines(String filename, int count) {
110         return readLines(filename, count, true);
111     }
112 
113     /**
114      * Read count lines from a file. Intended primarily for Linux /proc filesystem to avoid recalculating file contents
115      * on iterative reads.
116      *
117      * @param filename    The file to read
118      * @param count       The number of lines to read
119      * @param reportError Whether to log errors reading the file
120      * @return A list of Strings representing the first count lines of the file, or an empty list if file could not be
121      *         read or is empty
122      */
123     public static List<String> readLines(String filename, int count, boolean reportError) {
124         Path file = Paths.get(filename);
125         if (Files.isReadable(file)) {
126             if (LOG.isDebugEnabled()) {
127                 LOG.debug(READING_LOG, filename);
128             }
129             CharsetDecoder decoder = StandardCharsets.UTF_8.newDecoder();
130             try (Reader isr = new InputStreamReader(Files.newInputStream(file), decoder);
131                     BufferedReader reader = new BufferedReader(isr, BUFFER_SIZE)) {
132                 List<String> lines = new ArrayList<>(count);
133                 for (int i = 0; i < count; ++i) {
134                     String line = reader.readLine();
135                     if (line == null) {
136                         break;
137                     }
138                     lines.add(line);
139                 }
140                 return lines;
141             } catch (IOException e) {
142                 if (reportError) {
143                     LOG.error("Error reading file {}. {}", filename, e.getMessage());
144                 } else {
145                     LOG.debug("Error reading file {}. {}", filename, e.getMessage());
146                 }
147             }
148         } else if (reportError) {
149             LOG.warn("File not found or not readable: {}", filename);
150         }
151         return Collections.emptyList();
152     }
153 
154     /**
155      * Read an entire file at one time. Intended primarily for Linux /proc filesystem to avoid recalculating file
156      * contents on iterative reads.
157      *
158      * @param filename    The file to read
159      * @param reportError Whether to log errors reading the file
160      * @return A byte array representing the file
161      */
162     public static byte[] readAllBytes(String filename, boolean reportError) {
163         if (new File(filename).canRead()) {
164             if (LOG.isDebugEnabled()) {
165                 LOG.debug(READING_LOG, filename);
166             }
167             try {
168                 return Files.readAllBytes(Paths.get(filename));
169             } catch (IOException e) {
170                 if (reportError) {
171                     LOG.error("Error reading file {}. {}", filename, e.getMessage());
172                 } else {
173                     LOG.debug("Error reading file {}. {}", filename, e.getMessage());
174                 }
175             }
176         } else if (reportError) {
177             LOG.warn("File not found or not readable: {}", filename);
178         }
179         return new byte[0];
180     }
181 
182     /**
183      * Read an entire file at one time. Intended for unix /proc binary files to avoid reading file contents on iterative
184      * reads.
185      *
186      *
187      * @param filename The file to read
188      * @return A bytebuffer representing the file if read was successful; null otherwise
189      */
190     public static ByteBuffer readAllBytesAsBuffer(String filename) {
191         byte[] bytes = readAllBytes(filename, false);
192         ByteBuffer buff = ByteBuffer.allocate(bytes.length);
193         buff.order(ByteOrder.nativeOrder());
194         for (byte b : bytes) {
195             buff.put(b);
196         }
197         buff.flip();
198         return buff;
199     }
200 
201     /**
202      * Reads a byte value from a ByteBuffer
203      *
204      * @param buff The bytebuffer to read from
205      * @return The next byte value
206      */
207     public static byte readByteFromBuffer(ByteBuffer buff) {
208         if (buff.position() < buff.limit()) {
209             return buff.get();
210         }
211         return 0;
212     }
213 
214     /**
215      * Reads a short value from a ByteBuffer
216      *
217      * @param buff The bytebuffer to read from
218      * @return The next short value
219      */
220     public static short readShortFromBuffer(ByteBuffer buff) {
221         if (buff.position() <= buff.limit() - 2) {
222             return buff.getShort();
223         }
224         return 0;
225     }
226 
227     /**
228      * Reads an int value from a ByteBuffer
229      *
230      * @param buff The bytebuffer to read from
231      * @return The next int value
232      */
233     public static int readIntFromBuffer(ByteBuffer buff) {
234         if (buff.position() <= buff.limit() - 4) {
235             return buff.getInt();
236         }
237         return 0;
238     }
239 
240     /**
241      * Reads a long value from a ByteBuffer
242      *
243      * @param buff The bytebuffer to read from
244      * @return The next long value
245      */
246     public static long readLongFromBuffer(ByteBuffer buff) {
247         if (buff.position() <= buff.limit() - 8) {
248             return buff.getLong();
249         }
250         return 0L;
251     }
252 
253     /**
254      * Reads a NativeLong value from a ByteBuffer
255      *
256      * @param buff The bytebuffer to read from
257      * @return The next value
258      */
259     public static NativeLong readNativeLongFromBuffer(ByteBuffer buff) {
260         return new NativeLong(Native.LONG_SIZE == 4 ? readIntFromBuffer(buff) : readLongFromBuffer(buff));
261     }
262 
263     /**
264      * Reads a size_t value from a ByteBuffer
265      *
266      * @param buff The bytebuffer to read from
267      * @return The next value
268      */
269     public static size_t readSizeTFromBuffer(ByteBuffer buff) {
270         return new size_t(Native.SIZE_T_SIZE == 4 ? readIntFromBuffer(buff) : readLongFromBuffer(buff));
271     }
272 
273     /**
274      * Reads a byte array value from a ByteBuffer
275      *
276      * @param buff  The bytebuffer to read from
277      * @param array The array into which to read the data
278      */
279     public static void readByteArrayFromBuffer(ByteBuffer buff, byte[] array) {
280         if (buff.position() <= buff.limit() - array.length) {
281             buff.get(array);
282         }
283     }
284 
285     /**
286      * Reads a Pointer value from a ByteBuffer
287      *
288      * @param buff The bytebuffer to read from
289      * @return The next value
290      */
291     public static Pointer readPointerFromBuffer(ByteBuffer buff) {
292         if (buff.position() <= buff.limit() - Native.POINTER_SIZE) {
293             return Native.POINTER_SIZE == 4 ? new Pointer(buff.getInt()) : new Pointer(buff.getLong());
294         }
295         return Pointer.NULL;
296     }
297 
298     /**
299      * Read a file and return the long value contained therein. Intended primarily for Linux /sys filesystem
300      *
301      * @param filename The file to read
302      * @return The value contained in the file, if any; otherwise zero
303      */
304     public static long getLongFromFile(String filename) {
305         if (LOG.isDebugEnabled()) {
306             LOG.debug(READING_LOG, filename);
307         }
308         List<String> read = FileUtil.readLines(filename, 1, false);
309         if (!read.isEmpty()) {
310             if (LOG.isTraceEnabled()) {
311                 LOG.trace(READ_LOG, read.get(0));
312             }
313             return ParseUtil.parseLongOrDefault(read.get(0), 0L);
314         }
315         return 0L;
316     }
317 
318     /**
319      * Read a file and return the unsigned long value contained therein as a long. Intended primarily for Linux /sys
320      * filesystem
321      *
322      * @param filename The file to read
323      * @return The value contained in the file, if any; otherwise zero
324      */
325     public static long getUnsignedLongFromFile(String filename) {
326         if (LOG.isDebugEnabled()) {
327             LOG.debug(READING_LOG, filename);
328         }
329         List<String> read = FileUtil.readLines(filename, 1, false);
330         if (!read.isEmpty()) {
331             if (LOG.isTraceEnabled()) {
332                 LOG.trace(READ_LOG, read.get(0));
333             }
334             return ParseUtil.parseUnsignedLongOrDefault(read.get(0), 0L);
335         }
336         return 0L;
337     }
338 
339     /**
340      * Read a file and return the int value contained therein. Intended primarily for Linux /sys filesystem
341      *
342      * @param filename The file to read
343      * @return The value contained in the file, if any; otherwise zero
344      */
345     public static int getIntFromFile(String filename) {
346         if (LOG.isDebugEnabled()) {
347             LOG.debug(READING_LOG, filename);
348         }
349         try {
350             List<String> read = FileUtil.readLines(filename, 1, false);
351             if (!read.isEmpty()) {
352                 if (LOG.isTraceEnabled()) {
353                     LOG.trace(READ_LOG, read.get(0));
354                 }
355                 return ParseUtil.parseIntOrDefault(read.get(0), 0);
356             }
357         } catch (NumberFormatException ex) {
358             LOG.warn("Unable to read value from {}. {}", filename, ex.getMessage());
359         }
360         return 0;
361     }
362 
363     /**
364      * Read a file and return the String value contained therein. Intended primarily for Linux /sys filesystem
365      *
366      * @param filename The file to read
367      * @return The value contained in the file, if any; otherwise empty string
368      */
369     public static String getStringFromFile(String filename) {
370         if (LOG.isDebugEnabled()) {
371             LOG.debug(READING_LOG, filename);
372         }
373         List<String> read = FileUtil.readLines(filename, 1, false);
374         if (!read.isEmpty()) {
375             if (LOG.isTraceEnabled()) {
376                 LOG.trace(READ_LOG, read.get(0));
377             }
378             return read.get(0);
379         }
380         return "";
381     }
382 
383     /**
384      * Read a file and return a map of string keys to string values contained therein. Intended primarily for Linux
385      * {@code /proc/[pid]} files to provide more detailed or accurate information not available in the API.
386      *
387      * @param filename  The file to read
388      * @param separator Character(s) in each line of the file that separate the key and the value.
389      *
390      * @return The map contained in the file, delimited by the separator, with the value whitespace trimmed. If keys and
391      *         values are not parsed, an empty map is returned.
392      */
393     public static Map<String, String> getKeyValueMapFromFile(String filename, String separator) {
394         Map<String, String> map = new HashMap<>();
395         if (LOG.isDebugEnabled()) {
396             LOG.debug(READING_LOG, filename);
397         }
398         List<String> lines = FileUtil.readFile(filename, false);
399         for (String line : lines) {
400             String[] parts = line.split(separator);
401             if (parts.length == 2) {
402                 map.put(parts[0], parts[1].trim());
403             }
404         }
405         return map;
406     }
407 
408     /**
409      * Read a configuration file from the sequence of context classloader, system classloader and classloader of the
410      * current class, and return its properties
411      *
412      * @param propsFilename The filename
413      * @return A {@link Properties} object containing the properties.
414      */
415     public static Properties readPropertiesFromFilename(String propsFilename) {
416         Properties archProps = new Properties();
417         // Load the configuration file from at least one of multiple possible
418         // ClassLoaders, evaluated in order, eliminating duplicates
419         for (ClassLoader loader : Stream.of(Thread.currentThread().getContextClassLoader(),
420                 ClassLoader.getSystemClassLoader(), FileUtil.class.getClassLoader())
421                 .collect(Collectors.toCollection(LinkedHashSet::new))) {
422             if (readPropertiesFromClassLoader(propsFilename, archProps, loader)) {
423                 return archProps;
424             }
425         }
426         LOG.warn("Failed to load configuration file from classloader: {}", propsFilename);
427         return archProps;
428     }
429 
430     private static boolean readPropertiesFromClassLoader(String propsFilename, Properties archProps,
431             ClassLoader loader) {
432         if (loader == null) {
433             return false;
434         }
435         // Load the configuration file from the classLoader
436         try {
437             List<URL> resources = Collections.list(loader.getResources(propsFilename));
438             if (resources.isEmpty()) {
439                 LOG.debug("No {} file found from ClassLoader {}", propsFilename, loader);
440                 return false;
441             }
442             if (resources.size() > 1) {
443                 // Compare content, only warn if different
444                 byte[] propsAsBytes = readFileAsBytes(resources.get(0));
445                 for (int i = 1; i < resources.size(); i++) {
446                     if (!Arrays.equals(propsAsBytes, readFileAsBytes(resources.get(i)))) {
447                         LOG.warn("Configuration conflict: there is more than one {} file on the classpath: {}",
448                                 propsFilename, resources);
449                         break;
450                     }
451                 }
452             }
453             try (InputStream in = resources.get(0).openStream()) {
454                 if (in != null) {
455                     archProps.load(in);
456                 }
457             }
458             return true;
459         } catch (IOException e) {
460             return false;
461         }
462     }
463 
464     /**
465      * Reads a URL into a byte array
466      *
467      * @param url The URL of the file to read
468      * @return the byte content of the file
469      * @throws IOException on file reading failure
470      */
471     public static byte[] readFileAsBytes(URL url) throws IOException {
472         try (InputStream in = url.openStream()) {
473             ByteArrayOutputStream buf = new ByteArrayOutputStream();
474             byte[] data = new byte[1024];
475             int bytesRead;
476             while ((bytesRead = in.read(data, 0, data.length)) != -1) {
477                 buf.write(data, 0, bytesRead);
478             }
479             buf.flush();
480             return buf.toByteArray();
481         }
482     }
483 
484     /**
485      * Reads the target of a symbolic link
486      *
487      * @param file The file to read
488      * @return The symlink name, or null if the read failed
489      */
490     public static String readSymlinkTarget(File file) {
491         try {
492             return Files.readSymbolicLink(Paths.get(file.getAbsolutePath())).toString();
493         } catch (IOException e) {
494             return null;
495         }
496     }
497 }