From 76424950e373d3b04ac3dd13019151bfba3e8423 Mon Sep 17 00:00:00 2001 From: sumuel Date: Mon, 17 Aug 2026 20:44:55 +0000 Subject: Add the files --- src/detection/os/os.c | 21 +++ src/detection/os/os.h | 19 ++ src/detection/os/os_android.c | 16 ++ src/detection/os/os_apple.m | 105 +++++++++++ src/detection/os/os_haiku.c | 66 +++++++ src/detection/os/os_linux.c | 397 ++++++++++++++++++++++++++++++++++++++++++ src/detection/os/os_nbsd.c | 6 + src/detection/os/os_obsd.c | 6 + src/detection/os/os_sunos.c | 17 ++ src/detection/os/os_windows.c | 92 ++++++++++ 10 files changed, 745 insertions(+) create mode 100644 src/detection/os/os.c create mode 100644 src/detection/os/os.h create mode 100644 src/detection/os/os_android.c create mode 100644 src/detection/os/os_apple.m create mode 100644 src/detection/os/os_haiku.c create mode 100644 src/detection/os/os_linux.c create mode 100644 src/detection/os/os_nbsd.c create mode 100644 src/detection/os/os_obsd.c create mode 100644 src/detection/os/os_sunos.c create mode 100644 src/detection/os/os_windows.c (limited to 'src/detection/os') diff --git a/src/detection/os/os.c b/src/detection/os/os.c new file mode 100644 index 0000000..7f66733 --- /dev/null +++ b/src/detection/os/os.c @@ -0,0 +1,21 @@ +#include "os.h" + +void ffDetectOSImpl(FFOSResult* os); + +const FFOSResult* ffDetectOS(void) { + static FFOSResult result; + if (result.name.chars == NULL) { + ffStrbufInit(&result.name); + ffStrbufInit(&result.prettyName); + ffStrbufInit(&result.id); + ffStrbufInit(&result.version); + ffStrbufInit(&result.versionID); + ffStrbufInit(&result.codename); + ffStrbufInit(&result.buildID); + ffStrbufInit(&result.idLike); + ffStrbufInit(&result.variant); + ffStrbufInit(&result.variantID); + ffDetectOSImpl(&result); + } + return &result; +} diff --git a/src/detection/os/os.h b/src/detection/os/os.h new file mode 100644 index 0000000..5b62aab --- /dev/null +++ b/src/detection/os/os.h @@ -0,0 +1,19 @@ +#pragma once + +#include "fastfetch.h" +#include "modules/os/option.h" + +typedef struct FFOSResult { + FFstrbuf name; + FFstrbuf prettyName; + FFstrbuf id; + FFstrbuf idLike; + FFstrbuf variant; + FFstrbuf variantID; + FFstrbuf version; + FFstrbuf versionID; + FFstrbuf codename; + FFstrbuf buildID; +} FFOSResult; + +const FFOSResult* ffDetectOS(); diff --git a/src/detection/os/os_android.c b/src/detection/os/os_android.c new file mode 100644 index 0000000..47ea40b --- /dev/null +++ b/src/detection/os/os_android.c @@ -0,0 +1,16 @@ +#include "os.h" +#include "common/settings.h" + +void ffDetectOSImpl(FFOSResult* os) { + ffStrbufSetStatic(&os->name, "Android"); + + ffStrbufSetStatic(&os->id, "android"); + + ffSettingsGetAndroidProperty("ro.build.version.release", &os->version); + + ffSettingsGetAndroidProperty("ro.build.version.release", &os->versionID); + + ffSettingsGetAndroidProperty("ro.build.version.codename", &os->codename); + + ffSettingsGetAndroidProperty("ro.build.id", &os->buildID); +} diff --git a/src/detection/os/os_apple.m b/src/detection/os/os_apple.m new file mode 100644 index 0000000..2890fc3 --- /dev/null +++ b/src/detection/os/os_apple.m @@ -0,0 +1,105 @@ +#include "os.h" +#include "common/io.h" +#include "common/sysctl.h" +#include "common/strutil.h" +#include "common/mallocHelper.h" + +#include +#include +#import + +static bool parseSystemVersion(FFOSResult* os) +{ + NSError* error; + NSString* fileName = @"file:///System/Library/CoreServices/SystemVersion.plist"; + NSDictionary* dict = [NSDictionary dictionaryWithContentsOfURL:[NSURL URLWithString:fileName] + error:&error]; + if(error) + return false; + + NSString* value; + + if((value = dict[@"ProductName"])) + ffStrbufInitS(&os->name, value.UTF8String); + if((value = dict[@"ProductUserVisibleVersion"])) + ffStrbufInitS(&os->version, value.UTF8String); + if((value = dict[@"ProductBuildVersion"])) + ffStrbufInitS(&os->buildID, value.UTF8String); + if (ffStrbufStartsWithS(&os->version, "16.")) + { + // macOS 26 Tahoe. #1809 + os->version.chars[0] = '2'; + } + + return true; +} + +static bool detectOSCodeName(FFOSResult* os) +{ + // https://en.wikipedia.org/wiki/MacOS_version_history + char* str_end; + const char* version = os->version.chars; + unsigned long num = strtoul(version, &str_end, 10); + if (str_end == version) return false; + + switch (num) + { + case 26: + case 16: ffStrbufSetStatic(&os->codename, "Tahoe"); return true; + case 15: ffStrbufSetStatic(&os->codename, "Sequoia"); return true; + case 14: ffStrbufSetStatic(&os->codename, "Sonoma"); return true; + case 13: ffStrbufSetStatic(&os->codename, "Ventura"); return true; + case 12: ffStrbufSetStatic(&os->codename, "Monterey"); return true; + case 11: ffStrbufSetStatic(&os->codename, "Big Sur"); return true; + case 10: { + version = str_end + 1; + num = strtoul(version, &str_end, 10); + if (str_end == version) return false; + + switch (num) + { + case 16: ffStrbufSetStatic(&os->codename, "Big Sur"); return true; + case 15: ffStrbufSetStatic(&os->codename, "Catalina"); return true; + case 14: ffStrbufSetStatic(&os->codename, "Mojave"); return true; + case 13: ffStrbufSetStatic(&os->codename, "High Sierra"); return true; + case 12: ffStrbufSetStatic(&os->codename, "Sierra"); return true; + case 11: ffStrbufSetStatic(&os->codename, "El Capitan"); return true; + case 10: ffStrbufSetStatic(&os->codename, "Yosemite"); return true; + case 9: ffStrbufSetStatic(&os->codename, "Mavericks"); return true; + case 8: ffStrbufSetStatic(&os->codename, "Mountain Lion"); return true; + case 7: ffStrbufSetStatic(&os->codename, "Lion"); return true; + case 6: ffStrbufSetStatic(&os->codename, "Snow Leopard"); return true; + case 5: ffStrbufSetStatic(&os->codename, "Leopard"); return true; + case 4: ffStrbufSetStatic(&os->codename, "Tiger"); return true; + case 3: ffStrbufSetStatic(&os->codename, "Panther"); return true; + case 2: ffStrbufSetStatic(&os->codename, "Jaguar"); return true; + case 1: ffStrbufSetStatic(&os->codename, "Puma"); return true; + case 0: ffStrbufSetStatic(&os->codename, "Cheetah"); return true; + } + } + } + + return false; +} + +void ffDetectOSImpl(FFOSResult* os) +{ + parseSystemVersion(os); + + ffStrbufSetStatic(&os->id, "macos"); + + if(__builtin_expect(os->name.length == 0, 0)) + ffStrbufSetStatic(&os->name, "macOS"); + + if(__builtin_expect(os->version.length == 0, 0)) + ffSysctlGetString("kern.osproductversion", &os->version); + + if(__builtin_expect(os->buildID.length == 0, 0)) + ffSysctlGetString("kern.osversion", &os->buildID); + + ffStrbufAppend(&os->versionID, &os->version); + + detectOSCodeName(os); + + ffStrbufSetF(&os->prettyName, "%s %s %s (%s)", os->name.chars, os->codename.chars, os->version.chars, os->buildID.chars); +} diff --git a/src/detection/os/os_haiku.c b/src/detection/os/os_haiku.c new file mode 100644 index 0000000..e8e9f4a --- /dev/null +++ b/src/detection/os/os_haiku.c @@ -0,0 +1,66 @@ +#include "os.h" +#include +#include +#include + +void ffDetectOSImpl(FFOSResult* os) { + ffStrbufSetStatic(&os->name, "Haiku"); + + ffStrbufSetStatic(&os->id, "haiku"); + + image_info image; + int32 cookie = 0; + while (get_next_image_info(B_SYSTEM_TEAM, &cookie, &image) == B_OK) { + int32 ver = image.api_version; + if (ver == 0) { + continue; + } + + // https://github.com/haiku/haiku/blob/e63683b2fb337d2034059a7e053c170eaf978142/headers/os/BeBuild.h#L36 + if (ver < B_HAIKU_VERSION_1_ALPHA_1) { + switch (ver) { + case B_HAIKU_VERSION_BEOS: + ffStrbufSetStatic(&os->version, "BEOS"); + break; + case B_HAIKU_VERSION_BONE: + ffStrbufSetStatic(&os->version, "BONE"); + break; + case B_HAIKU_VERSION_DANO: + ffStrbufSetStatic(&os->version, "DANO"); + break; + } + } else { + int32 relVer = ver / 0x10000; + ver %= 0x10000; + if (ver == 0) { + ffStrbufSetF(&os->version, "R%" PRIi32, relVer); + } else { + relVer++; + + bool isPre = !!(ver & 1); + if (ver < B_HAIKU_VERSION_1_PRE_BETA_1) { + int32 alphaVer = ver / 0x100; + if (isPre) { + ffStrbufSetF(&os->version, "R%" PRId32 "A%" PRIi32 "-", relVer, alphaVer + 1); + } else { + ffStrbufSetF(&os->version, "R%" PRIi32 "A%" PRIi32, relVer, alphaVer); + } + } else if (ver < 0x00010000 /* B_HAIKU_VERSION_1 */) { + int32 betaVer = (ver - B_HAIKU_VERSION_1_ALPHA_4) / 0x100; + if (isPre) { + ffStrbufSetF(&os->version, "R%" PRIi32 "B%" PRIi32 "-", relVer, betaVer + 1); + } else { + ffStrbufSetF(&os->version, "R%" PRIi32 "B%" PRIi32, relVer, betaVer); + } + } + } + } + } + + if (!os->version.length) { + system_info sys; + if (get_system_info(&sys) == B_OK) { + ffStrbufAppendF(&os->version, "R%" PRIx64, sys.kernel_version); + } + } +} diff --git a/src/detection/os/os_linux.c b/src/detection/os/os_linux.c new file mode 100644 index 0000000..fc653a3 --- /dev/null +++ b/src/detection/os/os_linux.c @@ -0,0 +1,397 @@ +#include "os.h" +#include "common/properties.h" +#include "common/parsing.h" +#include "common/io.h" +#include "common/processing.h" +#include "common/strutil.h" + +#include +#include + +#define FF_STR_INDIR(x) #x +#define FF_STR(x) FF_STR_INDIR(x) + +static bool parseLsbRelease(const char* fileName, FFOSResult* result) { + return ffParsePropFileValues(fileName, 4, (FFpropquery[]) { + { "DISTRIB_ID =", &result->id }, + { "DISTRIB_DESCRIPTION =", &result->prettyName }, + { "DISTRIB_RELEASE =", &result->version }, + { "DISTRIB_CODENAME =", &result->codename }, + }); +} + +static bool parseOsRelease(const char* fileName, FFOSResult* result) { + return ffParsePropFileValues(fileName, 11, (FFpropquery[]) { + { "PRETTY_NAME =", &result->prettyName }, + { "NAME =", &result->name }, + { "ID =", &result->id }, + { "ID_LIKE =", &result->idLike }, + { "VARIANT =", &result->variant }, + { "VARIANT_ID =", &result->variantID }, + { "VERSION =", &result->version }, + { "VERSION_ID =", &result->versionID }, + { "VERSION_CODENAME =", &result->codename }, + { "CODENAME =", &result->codename }, + { "BUILD_ID =", &result->buildID }, + }); +} + +// Common logic for detecting Armbian image version +FF_A_UNUSED static bool detectArmbianVersion(FFOSResult* result) { + // Possible values `PRETTY_NAME` starts with on Armbian: + // - `Armbian` for official releases + // - `Armbian_community` for community releases + // - `Armbian_Security` for images with kali repo added + // - `Armbian-unofficial` for an unofficial image built from source, e.g. during development and testing + if (ffStrbufStartsWithS(&result->prettyName, "Armbian")) { + ffStrbufSetStatic(&result->name, "Armbian"); + } else { + return false; + } + ffStrbufSet(&result->idLike, &result->id); + ffStrbufSetS(&result->id, "armbian"); + ffStrbufClear(&result->versionID); + uint32_t versionStart = ffStrbufFirstIndexC(&result->prettyName, ' ') + 1; + uint32_t versionEnd = ffStrbufNextIndexC(&result->prettyName, versionStart, ' '); + ffStrbufSetNS(&result->versionID, versionEnd - versionStart, result->prettyName.chars + versionStart); + return true; +} + +// Returns false if PrettyName should be updated by caller +FF_A_UNUSED static bool getUbuntuFlavour(FFOSResult* result) { + if (detectArmbianVersion(result)) { + return true; + } else if (ffStrbufStartsWithS(&result->prettyName, "Linux Lite ")) { + ffStrbufSetStatic(&result->name, "Linux Lite"); + ffStrbufSetStatic(&result->id, "linuxlite"); + ffStrbufSetStatic(&result->idLike, "ubuntu"); + ffStrbufSetS(&result->versionID, result->prettyName.chars + strlen("Linux Lite ")); + return true; + } else if (ffStrbufStartsWithS(&result->prettyName, "Rhino Linux ")) { + ffStrbufSetStatic(&result->name, "Rhino Linux"); + ffStrbufSetStatic(&result->id, "rhinolinux"); + ffStrbufSetStatic(&result->idLike, "ubuntu"); + ffStrbufSetS(&result->versionID, result->prettyName.chars + strlen("Rhino Linux ")); + return true; + } else if (ffStrbufStartsWithS(&result->prettyName, "VanillaOS ")) { + ffStrbufSetStatic(&result->id, "vanilla"); + ffStrbufSetStatic(&result->idLike, "ubuntu"); + return true; + } + + if (ffPathExists("/usr/bin/lliurex-version", FF_PATHTYPE_FILE)) { + ffStrbufSetStatic(&result->name, "LliureX"); + ffStrbufSetStatic(&result->id, "lliurex"); + ffStrbufClear(&result->version); + if (ffProcessAppendStdOut(&result->version, (char* const[]) { + "/usr/bin/lliurex-version", + NULL, + }) == NULL) { // 8.2.2 + ffStrbufTrimRightSpace(&result->version); + } + ffStrbufSetF(&result->prettyName, "LliureX %s", result->version.chars); + ffStrbufSetStatic(&result->idLike, "ubuntu"); + return true; + } + + // xdgConfigDirs contains plasma only + if (ffPathExists("/var/lib/dpkg/info/ubuntustudio-desktop.list", FF_PATHTYPE_FILE)) { + ffStrbufSetStatic(&result->name, "Ubuntu Studio"); + ffStrbufSetStatic(&result->id, "ubuntu-studio"); + ffStrbufSetStatic(&result->idLike, "ubuntu"); + return false; + } + + const char* xdgConfigDirs = getenv("XDG_CONFIG_DIRS"); + if (!ffStrSet(xdgConfigDirs)) { + return false; + } + + if (ffStrContains(xdgConfigDirs, "kde") || ffStrContains(xdgConfigDirs, "plasma") || ffStrContains(xdgConfigDirs, "kubuntu")) { + ffStrbufSetStatic(&result->name, "Kubuntu"); + ffStrbufSetStatic(&result->id, "kubuntu"); + ffStrbufSetStatic(&result->idLike, "ubuntu"); + return false; + } + + if (ffStrContains(xdgConfigDirs, "xfce") || ffStrContains(xdgConfigDirs, "xubuntu")) { + ffStrbufSetStatic(&result->name, "Xubuntu"); + ffStrbufSetStatic(&result->id, "xubuntu"); + ffStrbufSetStatic(&result->idLike, "ubuntu"); + return false; + } + + if (ffStrContains(xdgConfigDirs, "lxqt") || ffStrContains(xdgConfigDirs, "lubuntu")) { + ffStrbufSetStatic(&result->name, "Lubuntu"); + ffStrbufSetStatic(&result->id, "lubuntu"); + ffStrbufSetStatic(&result->idLike, "ubuntu"); + return false; + } + + if (ffStrContains(xdgConfigDirs, "budgie")) { + ffStrbufSetStatic(&result->name, "Ubuntu Budgie"); + ffStrbufSetStatic(&result->id, "ubuntu-budgie"); + ffStrbufSetStatic(&result->idLike, "ubuntu"); + return false; + } + + if (ffStrContains(xdgConfigDirs, "cinnamon")) { + ffStrbufSetStatic(&result->name, "Ubuntu Cinnamon"); + ffStrbufSetStatic(&result->id, "ubuntu-cinnamon"); + ffStrbufSetStatic(&result->idLike, "ubuntu"); + return false; + } + + if (ffStrContains(xdgConfigDirs, "mate")) { + ffStrbufSetStatic(&result->name, "Ubuntu MATE"); + ffStrbufSetStatic(&result->id, "ubuntu-mate"); + ffStrbufSetStatic(&result->idLike, "ubuntu"); + return false; + } + + if (ffStrContains(xdgConfigDirs, "ukui")) { + ffStrbufSetStatic(&result->name, "Ubuntu Kylin"); + ffStrbufSetStatic(&result->id, "ubuntu-kylin"); + ffStrbufSetStatic(&result->idLike, "ubuntu"); + return false; + } + + if (ffStrContains(xdgConfigDirs, "sway")) { + ffStrbufSetStatic(&result->name, "Ubuntu Sway"); + ffStrbufSetStatic(&result->id, "ubuntu-sway"); + ffStrbufSetStatic(&result->idLike, "ubuntu"); + return false; + } + + if (ffStrContains(xdgConfigDirs, "touch")) { + ffStrbufSetStatic(&result->name, "Ubuntu Touch"); + ffStrbufSetStatic(&result->id, "ubuntu-touch"); + ffStrbufSetStatic(&result->idLike, "ubuntu"); + return false; + } + + if (ffStrContains(xdgConfigDirs, "unity")) { + ffStrbufSetStatic(&result->name, "Ubuntu Unity"); + ffStrbufSetStatic(&result->id, "ubuntu-unity"); + ffStrbufSetStatic(&result->idLike, "ubuntu"); + return false; + } + + return false; +} + +FF_A_UNUSED static void getDebianVersion(FFOSResult* result) { + FF_STRBUF_AUTO_DESTROY debianVersion = ffStrbufCreate(); + ffAppendFileBuffer("/etc/debian_version", &debianVersion); + ffStrbufTrimRightSpace(&debianVersion); + if (!debianVersion.length) { + return; + } + ffStrbufDestroy(&result->versionID); + ffStrbufInitMove(&result->versionID, &debianVersion); + + ffStrbufSetF(&result->prettyName, "%s %s (%s)", result->name.chars, result->versionID.chars, result->codename.chars); +} + +FF_A_UNUSED static bool detectDebianDerived(FFOSResult* result) { + if (detectArmbianVersion(result)) { + return true; + } else if (ffStrbufStartsWithS(&result->name, "Loc-OS")) { + ffStrbufSetStatic(&result->id, "locos"); + ffStrbufSetStatic(&result->idLike, "debian"); + return true; + } else if (ffStrbufEqualS(&result->name, "Parrot Security")) { + // https://github.com/ParrotSec/base-files/blob/c06f6d42ddf8d79564882306576576eddab7d907/etc/os-release + ffStrbufSetS(&result->id, "parrot"); + ffStrbufSetS(&result->idLike, "debian"); + return true; + } else if (ffStrbufStartsWithS(&result->name, "Lilidog GNU/Linux")) { + // https://github.com/fastfetch-cli/fastfetch/issues/1373 + ffStrbufSetStatic(&result->id, "lilidog"); + ffStrbufSetStatic(&result->idLike, "debian"); + return true; + } else if (access("/usr/bin/pveversion", X_OK) == 0) { + ffStrbufSetStatic(&result->id, "pve"); + ffStrbufSetStatic(&result->idLike, "debian"); + ffStrbufSetStatic(&result->name, "Proxmox VE"); + ffStrbufClear(&result->versionID); + if (ffProcessAppendStdOut(&result->versionID, (char* const[]) { + "/usr/bin/dpkg-query", + "--showformat=${version}", + "--show", + "pve-manager", + NULL, + }) == NULL) { // 8.2.2 + ffStrbufTrimRightSpace(&result->versionID); + } + ffStrbufSetF(&result->prettyName, "Proxmox VE %s", result->versionID.chars); + return true; + } else if (ffPathExists("/etc/rpi-issue", FF_PATHTYPE_FILE)) { + // Raspberry Pi OS + ffStrbufSetStatic(&result->id, "raspbian"); + ffStrbufSetStatic(&result->idLike, "debian"); + ffStrbufSetStatic(&result->name, "Raspberry Pi OS"); + getDebianVersion(result); + return true; + } else if (ffPathExists("/boot/dietpi/.version", FF_PATHTYPE_FILE)) { + // DietPi + ffStrbufSetStatic(&result->id, "dietpi"); + ffStrbufSetStatic(&result->name, "DietPi"); + ffStrbufSetStatic(&result->prettyName, "DietPi"); + ffStrbufSetStatic(&result->idLike, "debian"); + FF_STRBUF_AUTO_DESTROY core = ffStrbufCreate(); + FF_STRBUF_AUTO_DESTROY sub = ffStrbufCreate(); + FF_STRBUF_AUTO_DESTROY rc = ffStrbufCreate(); + if (ffParsePropFileValues("/boot/dietpi/.version", 3, (FFpropquery[]) { + { "G_DIETPI_VERSION_CORE=", &core }, + { "G_DIETPI_VERSION_SUB=", &sub }, + { "G_DIETPI_VERSION_RC=", &rc }, + })) { + ffStrbufAppendF(&result->prettyName, " %s.%s.%s", core.chars, sub.chars, rc.chars); + } + return true; + } else if (ffStrbufEndsWithS(&instance.state.platform.sysinfo.release, "+truenas")) { + // TrueNAS Scale + ffStrbufSetStatic(&result->id, "truenas-scale"); + ffStrbufSetStatic(&result->idLike, "debian"); + ffStrbufSetStatic(&result->name, "TrueNAS Scale"); + ffStrbufSetStatic(&result->prettyName, "TrueNAS Scale"); + return true; + } else if (ffPathExists("/usr/bin/emmabuntus_config.sh", FF_PATHTYPE_FILE)) { + // Emmabuntüs + ffStrbufSetStatic(&result->id, "emmabuntus"); + ffStrbufSetStatic(&result->idLike, "debian"); + ffStrbufSetStatic(&result->name, "Emmabuntüs"); + getDebianVersion(result); + return true; + } else { + // Hack for MX Linux. See #847 + FF_STRBUF_AUTO_DESTROY lsbRelease = ffStrbufCreate(); + if (ffAppendFileBuffer("/etc/lsb-release", &lsbRelease) && ffStrbufContainS(&lsbRelease, "DISTRIB_ID=MX")) { + ffStrbufSetStatic(&result->id, "mx"); + ffStrbufSetStatic(&result->idLike, "debian"); + ffStrbufSetStatic(&result->name, "MX"); + + ffStrbufClear(&result->version); + ffParsePropLines(lsbRelease.chars, "DISTRIB_RELEASE=", &result->version); + ffStrbufSet(&result->versionID, &result->version); + + ffStrbufClear(&result->codename); + ffParsePropLines(lsbRelease.chars, "DISTRIB_CODENAME=", &result->codename); + + ffStrbufClear(&result->prettyName); + ffParsePropLines(lsbRelease.chars, "DISTRIB_DESCRIPTION=", &result->prettyName); + return true; + } + } + return false; +} + +FF_A_UNUSED static bool detectFedoraVariant(FFOSResult* result) { + if (ffStrbufEqualS(&result->variantID, "coreos") || ffStrbufEqualS(&result->variantID, "kinoite") || ffStrbufEqualS(&result->variantID, "sericea") || ffStrbufEqualS(&result->variantID, "silverblue")) { + ffStrbufAppendC(&result->id, '-'); + ffStrbufAppend(&result->id, &result->variantID); + ffStrbufSetStatic(&result->idLike, "fedora"); + return true; + } + return false; +} + +FF_A_UNUSED static bool detectBedrock(FFOSResult* os) { + const char* bedrockRestrict = getenv("BEDROCK_RESTRICT"); + if (bedrockRestrict && bedrockRestrict[0] == '1') { + return false; + } + return parseOsRelease(FASTFETCH_TARGET_DIR_ROOT "/bedrock/strata/bedrock/etc/os-release", os); +} + +FF_A_UNUSED static void detectDeepinEnhancement(FFOSResult* result) { + if (ffStrbufContainC(&result->prettyName, '(')) { + return; + } + + FF_STRBUF_AUTO_DESTROY minor = ffStrbufCreate(); + FF_STRBUF_AUTO_DESTROY edition = ffStrbufCreate(); + + if (!ffParsePropFileValues( + FASTFETCH_TARGET_DIR_ETC "/os-version", + 2, + (FFpropquery[]) { + { "MinorVersion=", &minor }, + { "EditionName=", &edition }, + }) || + minor.length == 0) { + return; + } + + ffStrbufSet(&result->versionID, &minor); + + if (edition.length > 0) { + ffStrbufSetF(&result->prettyName, "%s %s (%s)", result->name.chars, minor.chars, edition.chars); + } else { + ffStrbufSetF(&result->prettyName, "%s %s", result->name.chars, minor.chars); + } +} + +static void detectOS(FFOSResult* os) { +#ifdef FF_CUSTOM_OS_RELEASE_PATH + parseOsRelease(FF_STR(FF_CUSTOM_OS_RELEASE_PATH), os); + #ifdef FF_CUSTOM_LSB_RELEASE_PATH + parseLsbRelease(FF_STR(FF_CUSTOM_LSB_RELEASE_PATH), os); + #endif + return; +#endif + +#ifdef __linux__ + if (detectBedrock(os)) { + return; + } +#endif + + // Refer: https://gist.github.com/natefoo/814c5bf936922dad97ff + + parseOsRelease(FASTFETCH_TARGET_DIR_ETC "/os-release", os); + + if (os->id.length == 0 || os->version.length == 0 || os->prettyName.length == 0 || os->codename.length == 0) { + parseLsbRelease(FASTFETCH_TARGET_DIR_ETC "/lsb-release", os); + } + if (os->id.length == 0 || os->name.length == 0 || os->prettyName.length == 0) { + parseOsRelease(FASTFETCH_TARGET_DIR_USR "/lib/os-release", os); + } + if (os->id.length == 0 && os->name.length == 0 && os->prettyName.length == 0) { + // HarmonyOS has no os-release file + if (ffStrbufEqualS(&instance.state.platform.sysinfo.name, "HarmonyOS")) { + ffStrbufSetS(&os->id, "harmonyos"); + ffStrbufSetS(&os->idLike, "harmonyos"); + ffStrbufSetS(&os->name, "HarmonyOS"); + ffStrbufSetS(&os->prettyName, "HarmonyOS"); + } + } +} + +void ffDetectOSImpl(FFOSResult* os) { + detectOS(os); + +#if __linux__ || __GNU__ + if (ffStrbufEqualS(&os->id, "ubuntu")) { + if (!getUbuntuFlavour(os)) { + if (!ffStrbufEndsWithS(&os->prettyName, " (development branch)")) { + ffStrbufSetF(&os->prettyName, "%s %s", os->name.chars, os->version.chars); // os->version contains code name + } + } + } else if (ffStrbufEqualS(&os->id, "debian")) { + if (!detectDebianDerived(os)) { + getDebianVersion(os); + } + } else if (ffStrbufEqualS(&os->id, "fedora")) { + detectFedoraVariant(os); + } else if (ffStrbufEqualS(&os->id, "linuxmint")) { + if (ffStrbufEqualS(&os->name, "LMDE")) { + ffStrbufSetS(&os->id, "lmde"); + ffStrbufSetS(&os->idLike, "linuxmint"); + } + } else if (ffStrbufEqualS(&os->id, "deepin")) { + detectDeepinEnhancement(os); + } +#endif +} diff --git a/src/detection/os/os_nbsd.c b/src/detection/os/os_nbsd.c new file mode 100644 index 0000000..200d0f2 --- /dev/null +++ b/src/detection/os/os_nbsd.c @@ -0,0 +1,6 @@ +#include "os.h" + +void ffDetectOSImpl(FFOSResult* os) { + ffStrbufSetStatic(&os->name, "NetBSD"); + ffStrbufSet(&os->version, &instance.state.platform.sysinfo.release); +} diff --git a/src/detection/os/os_obsd.c b/src/detection/os/os_obsd.c new file mode 100644 index 0000000..4baf185 --- /dev/null +++ b/src/detection/os/os_obsd.c @@ -0,0 +1,6 @@ +#include "os.h" + +void ffDetectOSImpl(FFOSResult* os) { + ffStrbufSetStatic(&os->name, "OpenBSD"); + ffStrbufSet(&os->version, &instance.state.platform.sysinfo.release); +} diff --git a/src/detection/os/os_sunos.c b/src/detection/os/os_sunos.c new file mode 100644 index 0000000..72f9946 --- /dev/null +++ b/src/detection/os/os_sunos.c @@ -0,0 +1,17 @@ +#include "os.h" +#include "common/io.h" + +void ffDetectOSImpl(FFOSResult* os) { + if (!ffReadFileBuffer("/etc/release", &os->prettyName)) { + return; + } + + ffStrbufSubstrBeforeFirstC(&os->prettyName, '\n'); + ffStrbufSubstrBeforeLastC(&os->prettyName, '('); + ffStrbufTrim(&os->prettyName, ' '); + + // OpenIndiana Hipster 2024.04 + uint32_t idx = ffStrbufFirstIndexC(&os->prettyName, ' '); + ffStrbufSetNS(&os->id, idx, os->prettyName.chars); + ffStrbufSetStatic(&os->idLike, "sunos"); +} diff --git a/src/detection/os/os_windows.c b/src/detection/os/os_windows.c new file mode 100644 index 0000000..2f280c8 --- /dev/null +++ b/src/detection/os/os_windows.c @@ -0,0 +1,92 @@ +#include "os.h" +#include "common/strutil.h" +#include "common/windows/registry.h" +#include "common/windows/unicode.h" + +#include + +PWSTR WINAPI BrandingFormatString(PCWSTR format); + +static bool getCodeName(FFOSResult* os) { + FF_AUTO_CLOSE_FD HANDLE hKey = NULL; + if (!ffRegOpenKeyForRead(HKEY_LOCAL_MACHINE, L"SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion", &hKey, NULL)) { + return false; + } + + if (!ffRegReadStrbuf(hKey, L"DisplayVersion", &os->codename, NULL)) { + if (!ffRegReadStrbuf(hKey, L"CSDVersion", &os->codename, NULL)) { // For Windows 7 and Windows 8 + if (!ffRegReadStrbuf(hKey, L"ReleaseId", &os->codename, NULL)) { // For old Windows 10 + return false; + } + } + } + + return true; +} + +void ffDetectOSImpl(FFOSResult* os) { + // https://dennisbabkin.com/blog/?t=how-to-tell-the-real-version-of-windows-your-app-is-running-on#ver_string + const wchar_t* rawName = BrandingFormatString(L"%WINDOWS_LONG%"); + ffStrbufSetWS(&os->variant, rawName); + GlobalFree((HGLOBAL) rawName); + ffStrbufSet(&os->prettyName, &os->variant); + ffStrbufTrimRight(&os->variant, ' '); + + // WMI returns the "Microsoft" prefix while BrandingFormatString doesn't. Make them consistent. + if (ffStrbufStartsWithS(&os->variant, "Microsoft ")) { + ffStrbufSubstrAfter(&os->variant, strlen("Microsoft ") - 1); + } + + if (os->variant.length == 0) // Windows PE? + { + FF_AUTO_CLOSE_FD HANDLE hKey = NULL; + if (ffRegOpenKeyForRead(HKEY_LOCAL_MACHINE, L"SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion", &hKey, NULL)) { + ffRegReadStrbuf(hKey, L"ProductName", &os->variant, NULL); + } + } + + ffStrbufSet(&os->prettyName, &os->variant); + + if (ffStrbufStartsWithS(&os->variant, "Windows ")) { + ffStrbufAppendS(&os->name, "Windows"); + + ffStrbufSubstrAfter(&os->variant, strlen("Windows ") - 1); + + if (ffStrbufStartsWithS(&os->variant, "Server ")) { + ffStrbufAppendS(&os->name, " Server"); + ffStrbufSubstrAfter(&os->variant, strlen(" Server") - 1); + } else if (ffStrbufStartsWithS(&os->variant, "Embedded ")) { + ffStrbufAppendS(&os->name, " Embedded"); + ffStrbufSubstrAfter(&os->variant, strlen(" Embedded") - 1); + } + + if (ffStrbufStartsWithIgnCaseS(&os->variant, "(TM) ")) { + ffStrbufSubstrAfter(&os->variant, strlen(" (TM)") - 1); + } + + uint32_t index = ffStrbufFirstIndexC(&os->variant, ' '); + ffStrbufAppendNS(&os->version, index, os->variant.chars); + ffStrbufSubstrAfter(&os->variant, index); + + // Windows Server 20xx Rx + if (ffStrbufEndsWithC(&os->name, 'r')) { + if (os->variant.chars[0] == 'R' && + ffCharIsDigit(os->variant.chars[1]) && + (os->variant.chars[2] == '\0' || os->variant.chars[2] == ' ')) { + ffStrbufAppendF(&os->version, " R%c", os->variant.chars[1]); + ffStrbufSubstrAfter(&os->variant, strlen("Rx ") - 1); + } + } + } else { + // Unknown Windows name, please report this + ffStrbufAppend(&os->name, &os->variant); + ffStrbufClear(&os->variant); + } + + ffStrbufAppendF(&os->id, "%s %s", os->name.chars, os->version.chars); + ffStrbufSetStatic(&os->idLike, "Windows"); + + if (getCodeName(os) && os->codename.length > 0) { + ffStrbufAppendF(&os->prettyName, " (%s)", os->codename.chars); + } +} -- cgit v1.2.3