summaryrefslogtreecommitdiffstats
path: root/src/detection/os
diff options
context:
space:
mode:
authorsumuel <samuel@yakubos.org>2026-08-17 20:44:55 +0000
committersumuel <samuel@yakubos.org>2026-08-17 20:44:55 +0000
commit76424950e373d3b04ac3dd13019151bfba3e8423 (patch)
tree4c3cfdbda039e592b9186be3e28f8d7cfd439e8a /src/detection/os
Add the files
Diffstat (limited to 'src/detection/os')
-rw-r--r--src/detection/os/os.c21
-rw-r--r--src/detection/os/os.h19
-rw-r--r--src/detection/os/os_android.c16
-rw-r--r--src/detection/os/os_apple.m105
-rw-r--r--src/detection/os/os_haiku.c66
-rw-r--r--src/detection/os/os_linux.c397
-rw-r--r--src/detection/os/os_nbsd.c6
-rw-r--r--src/detection/os/os_obsd.c6
-rw-r--r--src/detection/os/os_sunos.c17
-rw-r--r--src/detection/os/os_windows.c92
10 files changed, 745 insertions, 0 deletions
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 <stdlib.h>
+#include <string.h>
+#import <Foundation/Foundation.h>
+
+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 <OS.h>
+#include <image.h>
+#include <inttypes.h>
+
+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 <string.h>
+#include <stdlib.h>
+
+#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 <windows.h>
+
+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);
+ }
+}