237 lines · cpp
1//===--- Distro.cpp - Linux distribution detection support ------*- C++ -*-===//2//3// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.4// See https://llvm.org/LICENSE.txt for license information.5// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception6//7//===----------------------------------------------------------------------===//8 9#include "clang/Driver/Distro.h"10#include "clang/Basic/LLVM.h"11#include "llvm/ADT/StringRef.h"12#include "llvm/ADT/StringSwitch.h"13#include "llvm/Support/ErrorOr.h"14#include "llvm/Support/MemoryBuffer.h"15#include "llvm/Support/Threading.h"16#include "llvm/TargetParser/Host.h"17#include "llvm/TargetParser/Triple.h"18 19using namespace clang::driver;20using namespace clang;21 22static Distro::DistroType DetectOsRelease(llvm::vfs::FileSystem &VFS) {23 llvm::ErrorOr<std::unique_ptr<llvm::MemoryBuffer>> File =24 VFS.getBufferForFile("/etc/os-release");25 if (!File)26 File = VFS.getBufferForFile("/usr/lib/os-release");27 if (!File)28 return Distro::UnknownDistro;29 30 SmallVector<StringRef, 16> Lines;31 File.get()->getBuffer().split(Lines, "\n");32 Distro::DistroType Version = Distro::UnknownDistro;33 34 // Obviously this can be improved a lot.35 for (StringRef Line : Lines)36 if (Version == Distro::UnknownDistro && Line.starts_with("ID="))37 Version = llvm::StringSwitch<Distro::DistroType>(Line.substr(3))38 .Case("alpine", Distro::AlpineLinux)39 .Case("fedora", Distro::Fedora)40 .Case("gentoo", Distro::Gentoo)41 .Case("arch", Distro::ArchLinux)42 // On SLES, /etc/os-release was introduced in SLES 11.43 .Case("sles", Distro::OpenSUSE)44 .Case("opensuse", Distro::OpenSUSE)45 .Case("exherbo", Distro::Exherbo)46 .Default(Distro::UnknownDistro);47 return Version;48}49 50static Distro::DistroType DetectLsbRelease(llvm::vfs::FileSystem &VFS) {51 llvm::ErrorOr<std::unique_ptr<llvm::MemoryBuffer>> File =52 VFS.getBufferForFile("/etc/lsb-release");53 if (!File)54 return Distro::UnknownDistro;55 56 SmallVector<StringRef, 16> Lines;57 File.get()->getBuffer().split(Lines, "\n");58 Distro::DistroType Version = Distro::UnknownDistro;59 60 for (StringRef Line : Lines)61 if (Version == Distro::UnknownDistro &&62 Line.starts_with("DISTRIB_CODENAME="))63 Version = llvm::StringSwitch<Distro::DistroType>(Line.substr(17))64 .Case("quantal", Distro::UbuntuQuantal)65 .Case("raring", Distro::UbuntuRaring)66 .Case("saucy", Distro::UbuntuSaucy)67 .Case("trusty", Distro::UbuntuTrusty)68 .Case("utopic", Distro::UbuntuUtopic)69 .Case("vivid", Distro::UbuntuVivid)70 .Case("wily", Distro::UbuntuWily)71 .Case("xenial", Distro::UbuntuXenial)72 .Case("yakkety", Distro::UbuntuYakkety)73 .Case("zesty", Distro::UbuntuZesty)74 .Case("artful", Distro::UbuntuArtful)75 .Case("bionic", Distro::UbuntuBionic)76 .Case("cosmic", Distro::UbuntuCosmic)77 .Case("disco", Distro::UbuntuDisco)78 .Case("eoan", Distro::UbuntuEoan)79 .Case("focal", Distro::UbuntuFocal)80 .Case("groovy", Distro::UbuntuGroovy)81 .Case("hirsute", Distro::UbuntuHirsute)82 .Case("impish", Distro::UbuntuImpish)83 .Case("jammy", Distro::UbuntuJammy)84 .Case("kinetic", Distro::UbuntuKinetic)85 .Case("lunar", Distro::UbuntuLunar)86 .Case("mantic", Distro::UbuntuMantic)87 .Case("noble", Distro::UbuntuNoble)88 .Case("oracular", Distro::UbuntuOracular)89 .Case("plucky", Distro::UbuntuPlucky)90 .Case("questing", Distro::UbuntuQuesting)91 .Case("resolute", Distro::UbuntuResolute)92 .Default(Distro::UnknownDistro);93 return Version;94}95 96static Distro::DistroType DetectDistro(llvm::vfs::FileSystem &VFS) {97 Distro::DistroType Version = Distro::UnknownDistro;98 99 // Newer freedesktop.org's compilant systemd-based systems100 // should provide /etc/os-release or /usr/lib/os-release.101 Version = DetectOsRelease(VFS);102 if (Version != Distro::UnknownDistro)103 return Version;104 105 // Older systems might provide /etc/lsb-release.106 Version = DetectLsbRelease(VFS);107 if (Version != Distro::UnknownDistro)108 return Version;109 110 // Otherwise try some distro-specific quirks for Red Hat...111 llvm::ErrorOr<std::unique_ptr<llvm::MemoryBuffer>> File =112 VFS.getBufferForFile("/etc/redhat-release");113 114 if (File) {115 StringRef Data = File.get()->getBuffer();116 if (Data.starts_with("Fedora release"))117 return Distro::Fedora;118 if (Data.starts_with("Red Hat Enterprise Linux") ||119 Data.starts_with("CentOS") || Data.starts_with("AlmaLinux") ||120 Data.starts_with("Rocky Linux") ||121 Data.starts_with("Scientific Linux")) {122 if (Data.contains("release 10"))123 return Distro::RHEL10;124 if (Data.contains("release 9"))125 return Distro::RHEL9;126 if (Data.contains("release 8"))127 return Distro::RHEL8;128 if (Data.contains("release 7"))129 return Distro::RHEL7;130 }131 return Distro::UnknownDistro;132 }133 134 // ...for Debian135 File = VFS.getBufferForFile("/etc/debian_version");136 if (File) {137 StringRef Data = File.get()->getBuffer();138 // Contents: < major.minor > or < codename/sid >139 int MajorVersion;140 if (!Data.split('.').first.getAsInteger(10, MajorVersion)) {141 switch (MajorVersion) {142 case 8:143 return Distro::DebianJessie;144 case 9:145 return Distro::DebianStretch;146 case 10:147 return Distro::DebianBuster;148 case 11:149 return Distro::DebianBullseye;150 case 12:151 return Distro::DebianBookworm;152 case 13:153 return Distro::DebianTrixie;154 case 14:155 return Distro::DebianForky;156 case 15:157 return Distro::DebianDuke;158 default:159 return Distro::UnknownDistro;160 }161 }162 return llvm::StringSwitch<Distro::DistroType>(Data.split("\n").first)163 .Case("jessie/sid", Distro::DebianJessie)164 .Case("stretch/sid", Distro::DebianStretch)165 .Case("buster/sid", Distro::DebianBuster)166 .Case("bullseye/sid", Distro::DebianBullseye)167 .Case("bookworm/sid", Distro::DebianBookworm)168 .Case("trixie/sid", Distro::DebianTrixie)169 .Case("forky/sid", Distro::DebianForky)170 .Case("duke/sid", Distro::DebianDuke)171 .Default(Distro::UnknownDistro);172 }173 174 // ...for SUSE175 File = VFS.getBufferForFile("/etc/SuSE-release");176 if (File) {177 StringRef Data = File.get()->getBuffer();178 SmallVector<StringRef, 8> Lines;179 Data.split(Lines, "\n");180 for (const StringRef &Line : Lines) {181 if (!Line.trim().starts_with("VERSION"))182 continue;183 std::pair<StringRef, StringRef> SplitLine = Line.split('=');184 // Old versions have split VERSION and PATCHLEVEL185 // Newer versions use VERSION = x.y186 std::pair<StringRef, StringRef> SplitVer =187 SplitLine.second.trim().split('.');188 int Version;189 190 // OpenSUSE/SLES 10 and older are not supported and not compatible191 // with our rules, so just treat them as Distro::UnknownDistro.192 if (!SplitVer.first.getAsInteger(10, Version) && Version > 10)193 return Distro::OpenSUSE;194 return Distro::UnknownDistro;195 }196 return Distro::UnknownDistro;197 }198 199 // ...and others.200 if (VFS.exists("/etc/gentoo-release"))201 return Distro::Gentoo;202 203 return Distro::UnknownDistro;204}205 206static Distro::DistroType GetDistro(llvm::vfs::FileSystem &VFS,207 const llvm::Triple &TargetOrHost) {208 // If we don't target Linux, no need to check the distro. This saves a few209 // OS calls.210 if (!TargetOrHost.isOSLinux())211 return Distro::UnknownDistro;212 213 // True if we're backed by a real file system.214 const bool onRealFS = (llvm::vfs::getRealFileSystem() == &VFS);215 216 // If the host is not running Linux, and we're backed by a real file217 // system, no need to check the distro. This is the case where someone218 // is cross-compiling from BSD or Windows to Linux, and it would be219 // meaningless to try to figure out the "distro" of the non-Linux host.220 llvm::Triple HostTriple(llvm::sys::getProcessTriple());221 if (!HostTriple.isOSLinux() && onRealFS)222 return Distro::UnknownDistro;223 224 if (onRealFS) {225 // If we're backed by a real file system, perform226 // the detection only once and save the result.227 static Distro::DistroType LinuxDistro = DetectDistro(VFS);228 return LinuxDistro;229 }230 // This is mostly for passing tests which uses llvm::vfs::InMemoryFileSystem,231 // which is not "real".232 return DetectDistro(VFS);233}234 235Distro::Distro(llvm::vfs::FileSystem &VFS, const llvm::Triple &TargetOrHost)236 : DistroVal(GetDistro(VFS, TargetOrHost)) {}237