//===--- Distro.cpp - Linux distribution detection support ------*- C++ -*-===// // // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. // See https://llvm.org/LICENSE.txt for license information. // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception // //===----------------------------------------------------------------------===// #include "clang/Driver/Distro.h" #include "clang/Basic/LLVM.h" #include "llvm/ADT/SmallVector.h" #include "llvm/ADT/StringRef.h" #include "llvm/ADT/StringSwitch.h" #include "llvm/ADT/Triple.h" #include "llvm/Support/ErrorOr.h" #include "llvm/Support/Host.h" #include "llvm/Support/MemoryBuffer.h" #include "llvm/Support/Threading.h" using namespace clang::driver; using namespace clang; static Distro::DistroType DetectOsRelease(llvm::vfs::FileSystem &VFS) { llvm::ErrorOr> File = VFS.getBufferForFile("/etc/os-release"); if (!File) File = VFS.getBufferForFile("/usr/lib/os-release"); if (!File) return Distro::UnknownDistro; SmallVector Lines; File.get()->getBuffer().split(Lines, "\n"); Distro::DistroType Version = Distro::UnknownDistro; // Obviously this can be improved a lot. for (StringRef Line : Lines) if (Version == Distro::UnknownDistro && Line.startswith("ID=")) Version = llvm::StringSwitch(Line.substr(3)) .Case("alpine", Distro::AlpineLinux) .Case("fedora", Distro::Fedora) .Case("gentoo", Distro::Gentoo) .Case("arch", Distro::ArchLinux) // On SLES, /etc/os-release was introduced in SLES 11. .Case("sles", Distro::OpenSUSE) .Case("opensuse", Distro::OpenSUSE) .Case("exherbo", Distro::Exherbo) .Default(Distro::UnknownDistro); return Version; } static Distro::DistroType DetectLsbRelease(llvm::vfs::FileSystem &VFS) { llvm::ErrorOr> File = VFS.getBufferForFile("/etc/lsb-release"); if (!File) return Distro::UnknownDistro; SmallVector Lines; File.get()->getBuffer().split(Lines, "\n"); Distro::DistroType Version = Distro::UnknownDistro; for (StringRef Line : Lines) if (Version == Distro::UnknownDistro && Line.startswith("DISTRIB_CODENAME=")) Version = llvm::StringSwitch(Line.substr(17)) .Case("hardy", Distro::UbuntuHardy) .Case("intrepid", Distro::UbuntuIntrepid) .Case("jaunty", Distro::UbuntuJaunty) .Case("karmic", Distro::UbuntuKarmic) .Case("lucid", Distro::UbuntuLucid) .Case("maverick", Distro::UbuntuMaverick) .Case("natty", Distro::UbuntuNatty) .Case("oneiric", Distro::UbuntuOneiric) .Case("precise", Distro::UbuntuPrecise) .Case("quantal", Distro::UbuntuQuantal) .Case("raring", Distro::UbuntuRaring) .Case("saucy", Distro::UbuntuSaucy) .Case("trusty", Distro::UbuntuTrusty) .Case("utopic", Distro::UbuntuUtopic) .Case("vivid", Distro::UbuntuVivid) .Case("wily", Distro::UbuntuWily) .Case("xenial", Distro::UbuntuXenial) .Case("yakkety", Distro::UbuntuYakkety) .Case("zesty", Distro::UbuntuZesty) .Case("artful", Distro::UbuntuArtful) .Case("bionic", Distro::UbuntuBionic) .Case("cosmic", Distro::UbuntuCosmic) .Case("disco", Distro::UbuntuDisco) .Case("eoan", Distro::UbuntuEoan) .Case("focal", Distro::UbuntuFocal) .Case("groovy", Distro::UbuntuGroovy) .Case("hirsute", Distro::UbuntuHirsute) .Case("impish", Distro::UbuntuImpish) .Case("jammy", Distro::UbuntuJammy) .Default(Distro::UnknownDistro); return Version; } static Distro::DistroType DetectDistro(llvm::vfs::FileSystem &VFS) { Distro::DistroType Version = Distro::UnknownDistro; // Newer freedesktop.org's compilant systemd-based systems // should provide /etc/os-release or /usr/lib/os-release. Version = DetectOsRelease(VFS); if (Version != Distro::UnknownDistro) return Version; // Older systems might provide /etc/lsb-release. Version = DetectLsbRelease(VFS); if (Version != Distro::UnknownDistro) return Version; // Otherwise try some distro-specific quirks for RedHat... llvm::ErrorOr> File = VFS.getBufferForFile("/etc/redhat-release"); if (File) { StringRef Data = File.get()->getBuffer(); if (Data.startswith("Fedora release")) return Distro::Fedora; if (Data.startswith("Red Hat Enterprise Linux") || Data.startswith("CentOS") || Data.startswith("Scientific Linux")) { if (Data.contains("release 7")) return Distro::RHEL7; else if (Data.contains("release 6")) return Distro::RHEL6; else if (Data.contains("release 5")) return Distro::RHEL5; } return Distro::UnknownDistro; } // ...for Debian File = VFS.getBufferForFile("/etc/debian_version"); if (File) { StringRef Data = File.get()->getBuffer(); // Contents: < major.minor > or < codename/sid > int MajorVersion; if (!Data.split('.').first.getAsInteger(10, MajorVersion)) { switch (MajorVersion) { case 5: return Distro::DebianLenny; case 6: return Distro::DebianSqueeze; case 7: return Distro::DebianWheezy; case 8: return Distro::DebianJessie; case 9: return Distro::DebianStretch; case 10: return Distro::DebianBuster; case 11: return Distro::DebianBullseye; case 12: return Distro::DebianBookworm; default: return Distro::UnknownDistro; } } return llvm::StringSwitch(Data.split("\n").first) .Case("squeeze/sid", Distro::DebianSqueeze) .Case("wheezy/sid", Distro::DebianWheezy) .Case("jessie/sid", Distro::DebianJessie) .Case("stretch/sid", Distro::DebianStretch) .Case("buster/sid", Distro::DebianBuster) .Case("bullseye/sid", Distro::DebianBullseye) .Case("bookworm/sid", Distro::DebianBookworm) .Default(Distro::UnknownDistro); } // ...for SUSE File = VFS.getBufferForFile("/etc/SuSE-release"); if (File) { StringRef Data = File.get()->getBuffer(); SmallVector Lines; Data.split(Lines, "\n"); for (const StringRef &Line : Lines) { if (!Line.trim().startswith("VERSION")) continue; std::pair SplitLine = Line.split('='); // Old versions have split VERSION and PATCHLEVEL // Newer versions use VERSION = x.y std::pair SplitVer = SplitLine.second.trim().split('.'); int Version; // OpenSUSE/SLES 10 and older are not supported and not compatible // with our rules, so just treat them as Distro::UnknownDistro. if (!SplitVer.first.getAsInteger(10, Version) && Version > 10) return Distro::OpenSUSE; return Distro::UnknownDistro; } return Distro::UnknownDistro; } // ...and others. if (VFS.exists("/etc/gentoo-release")) return Distro::Gentoo; return Distro::UnknownDistro; } static Distro::DistroType GetDistro(llvm::vfs::FileSystem &VFS, const llvm::Triple &TargetOrHost) { // If we don't target Linux, no need to check the distro. This saves a few // OS calls. if (!TargetOrHost.isOSLinux()) return Distro::UnknownDistro; // True if we're backed by a real file system. const bool onRealFS = (llvm::vfs::getRealFileSystem() == &VFS); // If the host is not running Linux, and we're backed by a real file // system, no need to check the distro. This is the case where someone // is cross-compiling from BSD or Windows to Linux, and it would be // meaningless to try to figure out the "distro" of the non-Linux host. llvm::Triple HostTriple(llvm::sys::getProcessTriple()); if (!HostTriple.isOSLinux() && onRealFS) return Distro::UnknownDistro; if (onRealFS) { // If we're backed by a real file system, perform // the detection only once and save the result. static Distro::DistroType LinuxDistro = DetectDistro(VFS); return LinuxDistro; } // This is mostly for passing tests which uses llvm::vfs::InMemoryFileSystem, // which is not "real". return DetectDistro(VFS); } Distro::Distro(llvm::vfs::FileSystem &VFS, const llvm::Triple &TargetOrHost) : DistroVal(GetDistro(VFS, TargetOrHost)) {}