All Downloads are FREE. Search and download functionalities are using the official Maven repository.

oshi.software.os.linux.LinuxOperatingSystem Maven / Gradle / Ivy

There is a newer version: 6.6.5
Show newest version
/*
 * Copyright 2016-2024 The OSHI Project Contributors
 * SPDX-License-Identifier: MIT
 */
package oshi.software.os.linux;

import static oshi.software.os.OSService.State.RUNNING;
import static oshi.software.os.OSService.State.STOPPED;

import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Properties;
import java.util.Set;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.sun.jna.Native;
import com.sun.jna.platform.linux.LibC;
import com.sun.jna.platform.linux.Udev;

import oshi.annotation.concurrent.ThreadSafe;
import oshi.driver.linux.Who;
import oshi.driver.linux.proc.Auxv;
import oshi.driver.linux.proc.CpuStat;
import oshi.driver.linux.proc.ProcessStat;
import oshi.driver.linux.proc.UpTime;
import oshi.jna.Struct.CloseableSysinfo;
import oshi.jna.platform.linux.LinuxLibc;
import oshi.software.common.AbstractOperatingSystem;
import oshi.software.os.FileSystem;
import oshi.software.os.InternetProtocolStats;
import oshi.software.os.NetworkParams;
import oshi.software.os.OSProcess;
import oshi.software.os.OSProcess.State;
import oshi.software.os.OSService;
import oshi.software.os.OSSession;
import oshi.software.os.OSThread;
import oshi.util.Constants;
import oshi.util.ExecutingCommand;
import oshi.util.FileUtil;
import oshi.util.GlobalConfig;
import oshi.util.ParseUtil;
import oshi.util.platform.linux.ProcPath;
import oshi.util.tuples.Pair;
import oshi.util.tuples.Triplet;

/**
 * Linux is a family of open source Unix-like operating systems based on the Linux kernel, an operating system kernel
 * first released on September 17, 1991, by Linus Torvalds. Linux is typically packaged in a Linux distribution.
 */
@ThreadSafe
public class LinuxOperatingSystem extends AbstractOperatingSystem {

    private static final Logger LOG = LoggerFactory.getLogger(LinuxOperatingSystem.class);

    private static final String OS_RELEASE_LOG = "os-release: {}";
    private static final String LSB_RELEASE_A_LOG = "lsb_release -a: {}";
    private static final String LSB_RELEASE_LOG = "lsb-release: {}";
    private static final String RELEASE_DELIM = " release ";
    private static final String DOUBLE_QUOTES = "(?:^\")|(?:\"$)";
    private static final String FILENAME_PROPERTIES = "oshi.linux.filename.properties";

    /** This static field identifies if the udev library can be loaded. */
    public static final boolean HAS_UDEV;
    /** This static field identifies if the gettid function is in the c library. */
    public static final boolean HAS_GETTID;
    /** This static field identifies if the syscall for gettid returns sane results. */
    public static final boolean HAS_SYSCALL_GETTID;

    static {
        boolean hasUdev = false;
        boolean hasGettid = false;
        boolean hasSyscallGettid = false;
        try {
            if (GlobalConfig.get(GlobalConfig.OSHI_OS_LINUX_ALLOWUDEV, true)) {
                try {
                    @SuppressWarnings("unused")
                    Udev lib = Udev.INSTANCE;
                    hasUdev = true;
                } catch (UnsatisfiedLinkError e) {
                    LOG.warn("Did not find udev library in operating system. Some features may not work.");
                }
            } else {
                LOG.info("Loading of udev not allowed by configuration. Some features may not work.");
            }

            try {
                LinuxLibc.INSTANCE.gettid();
                hasGettid = true;
            } catch (UnsatisfiedLinkError e) {
                LOG.debug("Did not find gettid function in operating system. Using fallbacks.");
            }

            hasSyscallGettid = hasGettid;
            if (!hasGettid) {
                try {
                    hasSyscallGettid = LinuxLibc.INSTANCE.syscall(LinuxLibc.SYS_GETTID).intValue() > 0;
                } catch (UnsatisfiedLinkError e) {
                    LOG.debug("Did not find working syscall gettid function in operating system. Using procfs");
                }
            }
        } catch (NoClassDefFoundError e) {
            LOG.error("Did not JNA classes. Investigate incompatible version or missing native dll.");
        }
        HAS_UDEV = hasUdev;
        HAS_GETTID = hasGettid;
        HAS_SYSCALL_GETTID = hasSyscallGettid;
    }

    /**
     * Jiffies per second, used for process time counters.
     */
    private static final long USER_HZ;
    private static final long PAGE_SIZE;
    static {
        Map auxv = Auxv.queryAuxv();
        long hz = auxv.getOrDefault(Auxv.AT_CLKTCK, 0L);
        if (hz > 0) {
            USER_HZ = hz;
        } else {
            USER_HZ = ParseUtil.parseLongOrDefault(ExecutingCommand.getFirstAnswer("getconf CLK_TCK"), 100L);
        }
        long pagesz = Auxv.queryAuxv().getOrDefault(Auxv.AT_PAGESZ, 0L);
        if (pagesz > 0) {
            PAGE_SIZE = pagesz;
        } else {
            PAGE_SIZE = ParseUtil.parseLongOrDefault(ExecutingCommand.getFirstAnswer("getconf PAGE_SIZE"), 4096L);
        }
    }

    /**
     * OS Name for manufacturer
     */
    private static final String OS_NAME = ExecutingCommand.getFirstAnswer("uname -o");

    // Package private for access from LinuxOSProcess
    static final long BOOTTIME;
    static {
        long tempBT = CpuStat.getBootTime();
        // If above fails, current time minus uptime.
        if (tempBT == 0) {
            tempBT = System.currentTimeMillis() / 1000L - (long) UpTime.getSystemUptimeSeconds();
        }
        BOOTTIME = tempBT;
    }

    // PPID is 4th numeric value in proc pid stat; subtract 1 for 0-index
    private static final int[] PPID_INDEX = { 3 };

    /**
     * 

* Constructor for LinuxOperatingSystem. *

*/ public LinuxOperatingSystem() { super.getVersionInfo(); } @Override public String queryManufacturer() { return OS_NAME; } @Override public Pair queryFamilyVersionInfo() { Triplet familyVersionCodename = queryFamilyVersionCodenameFromReleaseFiles(); String buildNumber = null; List procVersion = FileUtil.readFile(ProcPath.VERSION); if (!procVersion.isEmpty()) { String[] split = ParseUtil.whitespaces.split(procVersion.get(0)); for (String s : split) { if (!"Linux".equals(s) && !"version".equals(s)) { buildNumber = s; break; } } } OSVersionInfo versionInfo = new OSVersionInfo(familyVersionCodename.getB(), familyVersionCodename.getC(), buildNumber); return new Pair<>(familyVersionCodename.getA(), versionInfo); } @Override protected int queryBitness(int jvmBitness) { if (jvmBitness < 64 && !ExecutingCommand.getFirstAnswer("uname -m").contains("64")) { return jvmBitness; } return 64; } @Override public FileSystem getFileSystem() { return new LinuxFileSystem(); } @Override public InternetProtocolStats getInternetProtocolStats() { return new LinuxInternetProtocolStats(); } @Override public List getSessions() { return USE_WHO_COMMAND ? super.getSessions() : Who.queryUtxent(); } @Override public OSProcess getProcess(int pid) { OSProcess proc = new LinuxOSProcess(pid, this); if (!proc.getState().equals(State.INVALID)) { return proc; } return null; } @Override public List queryAllProcesses() { return queryChildProcesses(-1); } @Override public List queryChildProcesses(int parentPid) { File[] pidFiles = ProcessStat.getPidFiles(); if (parentPid >= 0) { // Only return descendants return queryProcessList(getChildrenOrDescendants(getParentPidsFromProcFiles(pidFiles), parentPid, false)); } Set descendantPids = new HashSet<>(); // Put everything in the "descendant" set for (File procFile : pidFiles) { int pid = ParseUtil.parseIntOrDefault(procFile.getName(), -2); if (pid != -2) { descendantPids.add(pid); } } return queryProcessList(descendantPids); } @Override public List queryDescendantProcesses(int parentPid) { File[] pidFiles = ProcessStat.getPidFiles(); return queryProcessList(getChildrenOrDescendants(getParentPidsFromProcFiles(pidFiles), parentPid, true)); } private List queryProcessList(Set descendantPids) { List procs = new ArrayList<>(); for (int pid : descendantPids) { OSProcess proc = new LinuxOSProcess(pid, this); if (!proc.getState().equals(State.INVALID)) { procs.add(proc); } } return procs; } private static Map getParentPidsFromProcFiles(File[] pidFiles) { Map parentPidMap = new HashMap<>(); for (File procFile : pidFiles) { int pid = ParseUtil.parseIntOrDefault(procFile.getName(), 0); parentPidMap.put(pid, getParentPidFromProcFile(pid)); } return parentPidMap; } private static int getParentPidFromProcFile(int pid) { String stat = FileUtil.getStringFromFile(String.format(Locale.ROOT, "/proc/%d/stat", pid)); // A race condition may leave us with an empty string if (stat.isEmpty()) { return 0; } // Grab PPID long[] statArray = ParseUtil.parseStringToLongArray(stat, PPID_INDEX, ProcessStat.PROC_PID_STAT_LENGTH, ' '); return (int) statArray[0]; } @Override public int getProcessId() { return LinuxLibc.INSTANCE.getpid(); } @Override public int getProcessCount() { return ProcessStat.getPidFiles().length; } @Override public int getThreadId() { if (HAS_SYSCALL_GETTID) { return HAS_GETTID ? LinuxLibc.INSTANCE.gettid() : LinuxLibc.INSTANCE.syscall(LinuxLibc.SYS_GETTID).intValue(); } try { return ParseUtil.parseIntOrDefault( Files.readSymbolicLink(new File(ProcPath.THREAD_SELF).toPath()).getFileName().toString(), 0); } catch (IOException e) { return 0; } } @Override public OSThread getCurrentThread() { return new LinuxOSThread(getProcessId(), getThreadId()); } @Override public int getThreadCount() { try (CloseableSysinfo info = new CloseableSysinfo()) { if (0 != LibC.INSTANCE.sysinfo(info)) { LOG.error("Failed to get process thread count. Error code: {}", Native.getLastError()); return 0; } return info.procs; } catch (UnsatisfiedLinkError | NoClassDefFoundError e) { LOG.error("Failed to get procs from sysinfo. {}", e.getMessage()); } return 0; } @Override public long getSystemUptime() { return (long) UpTime.getSystemUptimeSeconds(); } @Override public long getSystemBootTime() { return BOOTTIME; } @Override public NetworkParams getNetworkParams() { return new LinuxNetworkParams(); } private static Triplet queryFamilyVersionCodenameFromReleaseFiles() { Triplet familyVersionCodename; // There are two competing options for family/version information. // Newer systems are adopting a standard /etc/os-release file: // https://www.freedesktop.org/software/systemd/man/os-release.html // // Some systems are still using the lsb standard which parses a // variety of /etc/*-release files and is most easily accessed via // the commandline lsb_release -a, see here: // https://linux.die.net/man/1/lsb_release // In this case, the /etc/lsb-release file (if it exists) has // optional overrides to the information in the /etc/distrib-release // files, which show: "Distributor release x.x (Codename)" // Attempt to read /etc/system-release which has more details than // os-release on (CentOS and Fedora) if ((familyVersionCodename = readDistribRelease("/etc/system-release")) != null) { // If successful, we're done. this.family has been set and // possibly the versionID and codeName return familyVersionCodename; } // Attempt to read /etc/os-release file. if ((familyVersionCodename = readOsRelease()) != null) { // If successful, we're done. this.family has been set and // possibly the versionID and codeName return familyVersionCodename; } // Attempt to execute the `lsb_release` command if ((familyVersionCodename = execLsbRelease()) != null) { // If successful, we're done. this.family has been set and // possibly the versionID and codeName return familyVersionCodename; } // The above two options should hopefully work on most // distributions. If not, we keep having fun. // Attempt to read /etc/lsb-release file if ((familyVersionCodename = readLsbRelease()) != null) { // If successful, we're done. this.family has been set and // possibly the versionID and codeName return familyVersionCodename; } // If we're still looking, we search for any /etc/*-release (or // similar) filename, for which the first line should be of the // "Distributor release x.x (Codename)" format or possibly a // "Distributor VERSION x.x (Codename)" format String etcDistribRelease = getReleaseFilename(); if ((familyVersionCodename = readDistribRelease(etcDistribRelease)) != null) { // If successful, we're done. this.family has been set and // possibly the versionID and codeName return familyVersionCodename; } // If we've gotten this far with no match, use the distrib-release // filename (defaults will eventually give "Unknown") String family = filenameToFamily(etcDistribRelease.replace("/etc/", "").replace("release", "") .replace("version", "").replace("-", "").replace("_", "")); return new Triplet<>(family, Constants.UNKNOWN, Constants.UNKNOWN); } /** * Attempts to read /etc/os-release * * @return a triplet with the parsed family, versionID and codeName if file successfully read and NAME= found, null * otherwise */ private static Triplet readOsRelease() { String family = null; String versionId = Constants.UNKNOWN; String codeName = Constants.UNKNOWN; List osRelease = FileUtil.readFile("/etc/os-release"); // Search for NAME= for (String line : osRelease) { if (line.startsWith("VERSION=")) { LOG.debug(OS_RELEASE_LOG, line); // remove beginning and ending '"' characters, etc from // VERSION="14.04.4 LTS, Trusty Tahr" (Ubuntu style) // or VERSION="17 (Beefy Miracle)" (os-release doc style) line = line.replace("VERSION=", "").replaceAll(DOUBLE_QUOTES, "").trim(); String[] split = line.split("[()]"); if (split.length <= 1) { // If no parentheses, check for Ubuntu's comma format split = line.split(", "); } if (split.length > 0) { versionId = split[0].trim(); } if (split.length > 1) { codeName = split[1].trim(); } } else if (line.startsWith("NAME=") && family == null) { LOG.debug(OS_RELEASE_LOG, line); // remove beginning and ending '"' characters, etc from // NAME="Ubuntu" family = line.replace("NAME=", "").replaceAll(DOUBLE_QUOTES, "").trim(); } else if (line.startsWith("VERSION_ID=") && versionId.equals(Constants.UNKNOWN)) { LOG.debug(OS_RELEASE_LOG, line); // remove beginning and ending '"' characters, etc from // VERSION_ID="14.04" versionId = line.replace("VERSION_ID=", "").replaceAll(DOUBLE_QUOTES, "").trim(); } } return family == null ? null : new Triplet<>(family, versionId, codeName); } /** * Attempts to execute `lsb_release -a` * * @return a triplet with the parsed family, versionID and codeName if the command successfully executed and * Distributor ID: or Description: found, null otherwise */ private static Triplet execLsbRelease() { String family = null; String versionId = Constants.UNKNOWN; String codeName = Constants.UNKNOWN; // If description is of the format Distrib release x.x (Codename) // that is primary, otherwise use Distributor ID: which returns the // distribution concatenated, e.g., RedHat instead of Red Hat for (String line : ExecutingCommand.runNative("lsb_release -a")) { if (line.startsWith("Description:")) { LOG.debug(LSB_RELEASE_A_LOG, line); line = line.replace("Description:", "").trim(); if (line.contains(RELEASE_DELIM)) { Triplet triplet = parseRelease(line, RELEASE_DELIM); family = triplet.getA(); if (versionId.equals(Constants.UNKNOWN)) { versionId = triplet.getB(); } if (codeName.equals(Constants.UNKNOWN)) { codeName = triplet.getC(); } } } else if (line.startsWith("Distributor ID:") && family == null) { LOG.debug(LSB_RELEASE_A_LOG, line); family = line.replace("Distributor ID:", "").trim(); } else if (line.startsWith("Release:") && versionId.equals(Constants.UNKNOWN)) { LOG.debug(LSB_RELEASE_A_LOG, line); versionId = line.replace("Release:", "").trim(); } else if (line.startsWith("Codename:") && codeName.equals(Constants.UNKNOWN)) { LOG.debug(LSB_RELEASE_A_LOG, line); codeName = line.replace("Codename:", "").trim(); } } return family == null ? null : new Triplet<>(family, versionId, codeName); } /** * Attempts to read /etc/lsb-release * * @return a triplet with the parsed family, versionID and codeName if file successfully read and and DISTRIB_ID or * DISTRIB_DESCRIPTION, null otherwise */ private static Triplet readLsbRelease() { String family = null; String versionId = Constants.UNKNOWN; String codeName = Constants.UNKNOWN; List osRelease = FileUtil.readFile("/etc/lsb-release"); // Search for NAME= for (String line : osRelease) { if (line.startsWith("DISTRIB_DESCRIPTION=")) { LOG.debug(LSB_RELEASE_LOG, line); line = line.replace("DISTRIB_DESCRIPTION=", "").replaceAll(DOUBLE_QUOTES, "").trim(); if (line.contains(RELEASE_DELIM)) { Triplet triplet = parseRelease(line, RELEASE_DELIM); family = triplet.getA(); if (versionId.equals(Constants.UNKNOWN)) { versionId = triplet.getB(); } if (codeName.equals(Constants.UNKNOWN)) { codeName = triplet.getC(); } } } else if (line.startsWith("DISTRIB_ID=") && family == null) { LOG.debug(LSB_RELEASE_LOG, line); family = line.replace("DISTRIB_ID=", "").replaceAll(DOUBLE_QUOTES, "").trim(); } else if (line.startsWith("DISTRIB_RELEASE=") && versionId.equals(Constants.UNKNOWN)) { LOG.debug(LSB_RELEASE_LOG, line); versionId = line.replace("DISTRIB_RELEASE=", "").replaceAll(DOUBLE_QUOTES, "").trim(); } else if (line.startsWith("DISTRIB_CODENAME=") && codeName.equals(Constants.UNKNOWN)) { LOG.debug(LSB_RELEASE_LOG, line); codeName = line.replace("DISTRIB_CODENAME=", "").replaceAll(DOUBLE_QUOTES, "").trim(); } } return family == null ? null : new Triplet<>(family, versionId, codeName); } /** * Attempts to read /etc/distrib-release (for some value of distrib) * * @param filename The /etc/distrib-release file * @return a triplet with the parsed family, versionID and codeName if file successfully read and " release " or " * VERSION " found, null otherwise */ private static Triplet readDistribRelease(String filename) { if (new File(filename).exists()) { List osRelease = FileUtil.readFile(filename); // Search for Distrib release x.x (Codename) for (String line : osRelease) { LOG.debug("{}: {}", filename, line); if (line.contains(RELEASE_DELIM)) { // If this parses properly we're done return parseRelease(line, RELEASE_DELIM); } else if (line.contains(" VERSION ")) { // If this parses properly we're done return parseRelease(line, " VERSION "); } } } return null; } /** * Helper method to parse version description line style * * @param line a String of the form "Distributor release x.x (Codename)" * @param splitLine A regex to split on, e.g. " release " * @return a triplet with the parsed family, versionID and codeName */ private static Triplet parseRelease(String line, String splitLine) { String[] split = line.split(splitLine); String family = split[0].trim(); String versionId = Constants.UNKNOWN; String codeName = Constants.UNKNOWN; if (split.length > 1) { split = split[1].split("[()]"); if (split.length > 0) { versionId = split[0].trim(); } if (split.length > 1) { codeName = split[1].trim(); } } return new Triplet<>(family, versionId, codeName); } /** * Looks for a collection of possible distrib-release filenames * * @return The first valid matching filename */ protected static String getReleaseFilename() { // Look for any /etc/*-release, *-version, and variants File etc = new File("/etc"); // Find any *_input files in that path File[] matchingFiles = etc.listFiles(// f -> (f.getName().endsWith("-release") || // f.getName().endsWith("-version") || // f.getName().endsWith("_release") || // f.getName().endsWith("_version")) // && !(f.getName().endsWith("os-release") || // f.getName().endsWith("lsb-release") || // f.getName().endsWith("system-release"))); if (matchingFiles != null && matchingFiles.length > 0) { return matchingFiles[0].getPath(); } if (new File("/etc/release").exists()) { return "/etc/release"; } // If all else fails, try this return "/etc/issue"; } /** * Converts a portion of a filename (e.g. the 'redhat' in /etc/redhat-release) to a mixed case string representing * the family (e.g., Red Hat) * * @param name Stripped version of filename after removing /etc and -release * @return Mixed case family */ private static String filenameToFamily(String name) { if (name.isEmpty()) { return "Solaris"; } else if ("issue".equalsIgnoreCase(name)) { // /etc/issue will end up here return "Unknown"; } else { Properties filenameProps = FileUtil.readPropertiesFromFilename(FILENAME_PROPERTIES); String family = filenameProps.getProperty(name.toLowerCase(Locale.ROOT)); return family != null ? family : name.substring(0, 1).toUpperCase(Locale.ROOT) + name.substring(1); } } @Override public List getServices() { // Get running services List services = new ArrayList<>(); Set running = new HashSet<>(); for (OSProcess p : getChildProcesses(1, ProcessFiltering.ALL_PROCESSES, ProcessSorting.PID_ASC, 0)) { OSService s = new OSService(p.getName(), p.getProcessID(), RUNNING); services.add(s); running.add(p.getName()); } boolean systemctlFound = false; List systemctl = ExecutingCommand.runNative("systemctl list-unit-files"); for (String str : systemctl) { String[] split = ParseUtil.whitespaces.split(str); if (split.length >= 2 && split[0].endsWith(".service") && "enabled".equals(split[1])) { // remove .service extension String name = split[0].substring(0, split[0].length() - 8); int index = name.lastIndexOf('.'); String shortName = (index < 0 || index > name.length() - 2) ? name : name.substring(index + 1); if (!running.contains(name) && !running.contains(shortName)) { OSService s = new OSService(name, 0, STOPPED); services.add(s); systemctlFound = true; } } } if (!systemctlFound) { // Get Directories for stopped services File dir = new File("/etc/init"); if (dir.exists() && dir.isDirectory()) { for (File f : dir.listFiles((f, name) -> name.toLowerCase(Locale.ROOT).endsWith(".conf"))) { // remove .conf extension String name = f.getName().substring(0, f.getName().length() - 5); int index = name.lastIndexOf('.'); String shortName = (index < 0 || index > name.length() - 2) ? name : name.substring(index + 1); if (!running.contains(name) && !running.contains(shortName)) { OSService s = new OSService(name, 0, STOPPED); services.add(s); } } } else { LOG.error("Directory: /etc/init does not exist"); } } return services; } /** * Gets Jiffies per second, useful for converting ticks to milliseconds and vice versa. * * @return Jiffies per second. */ public static long getHz() { return USER_HZ; } /** * Gets Page Size, for converting memory stats from pages to bytes * * @return Page Size */ public static long getPageSize() { return PAGE_SIZE; } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy