10b57cec5SDimitry Andric //===--- Distro.cpp - Linux distribution detection support ------*- C++ -*-===//
20b57cec5SDimitry Andric //
30b57cec5SDimitry Andric // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
40b57cec5SDimitry Andric // See https://llvm.org/LICENSE.txt for license information.
50b57cec5SDimitry Andric // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
60b57cec5SDimitry Andric //
70b57cec5SDimitry Andric //===----------------------------------------------------------------------===//
80b57cec5SDimitry Andric 
90b57cec5SDimitry Andric #include "clang/Driver/Distro.h"
100b57cec5SDimitry Andric #include "clang/Basic/LLVM.h"
110b57cec5SDimitry Andric #include "llvm/ADT/SmallVector.h"
120b57cec5SDimitry Andric #include "llvm/ADT/StringRef.h"
130b57cec5SDimitry Andric #include "llvm/ADT/StringSwitch.h"
145ffd83dbSDimitry Andric #include "llvm/Support/ErrorOr.h"
155ffd83dbSDimitry Andric #include "llvm/Support/MemoryBuffer.h"
16e8d8bef9SDimitry Andric #include "llvm/Support/Threading.h"
1706c3fb27SDimitry Andric #include "llvm/TargetParser/Host.h"
1806c3fb27SDimitry Andric #include "llvm/TargetParser/Triple.h"
190b57cec5SDimitry Andric 
200b57cec5SDimitry Andric using namespace clang::driver;
210b57cec5SDimitry Andric using namespace clang;
220b57cec5SDimitry Andric 
DetectOsRelease(llvm::vfs::FileSystem & VFS)23e8d8bef9SDimitry Andric static Distro::DistroType DetectOsRelease(llvm::vfs::FileSystem &VFS) {
24e8d8bef9SDimitry Andric   llvm::ErrorOr<std::unique_ptr<llvm::MemoryBuffer>> File =
25e8d8bef9SDimitry Andric       VFS.getBufferForFile("/etc/os-release");
26e8d8bef9SDimitry Andric   if (!File)
27e8d8bef9SDimitry Andric     File = VFS.getBufferForFile("/usr/lib/os-release");
28e8d8bef9SDimitry Andric   if (!File)
29480093f4SDimitry Andric     return Distro::UnknownDistro;
30480093f4SDimitry Andric 
31e8d8bef9SDimitry Andric   SmallVector<StringRef, 16> Lines;
32e8d8bef9SDimitry Andric   File.get()->getBuffer().split(Lines, "\n");
33e8d8bef9SDimitry Andric   Distro::DistroType Version = Distro::UnknownDistro;
34480093f4SDimitry Andric 
35e8d8bef9SDimitry Andric   // Obviously this can be improved a lot.
36e8d8bef9SDimitry Andric   for (StringRef Line : Lines)
375f757f3fSDimitry Andric     if (Version == Distro::UnknownDistro && Line.starts_with("ID="))
38e8d8bef9SDimitry Andric       Version = llvm::StringSwitch<Distro::DistroType>(Line.substr(3))
39fe6060f1SDimitry Andric                     .Case("alpine", Distro::AlpineLinux)
40e8d8bef9SDimitry Andric                     .Case("fedora", Distro::Fedora)
41e8d8bef9SDimitry Andric                     .Case("gentoo", Distro::Gentoo)
42e8d8bef9SDimitry Andric                     .Case("arch", Distro::ArchLinux)
43e8d8bef9SDimitry Andric                     // On SLES, /etc/os-release was introduced in SLES 11.
44e8d8bef9SDimitry Andric                     .Case("sles", Distro::OpenSUSE)
45e8d8bef9SDimitry Andric                     .Case("opensuse", Distro::OpenSUSE)
46e8d8bef9SDimitry Andric                     .Case("exherbo", Distro::Exherbo)
47e8d8bef9SDimitry Andric                     .Default(Distro::UnknownDistro);
48e8d8bef9SDimitry Andric   return Version;
49e8d8bef9SDimitry Andric }
50e8d8bef9SDimitry Andric 
DetectLsbRelease(llvm::vfs::FileSystem & VFS)51e8d8bef9SDimitry Andric static Distro::DistroType DetectLsbRelease(llvm::vfs::FileSystem &VFS) {
520b57cec5SDimitry Andric   llvm::ErrorOr<std::unique_ptr<llvm::MemoryBuffer>> File =
530b57cec5SDimitry Andric       VFS.getBufferForFile("/etc/lsb-release");
54e8d8bef9SDimitry Andric   if (!File)
55e8d8bef9SDimitry Andric     return Distro::UnknownDistro;
56e8d8bef9SDimitry Andric 
570b57cec5SDimitry Andric   SmallVector<StringRef, 16> Lines;
58e8d8bef9SDimitry Andric   File.get()->getBuffer().split(Lines, "\n");
590b57cec5SDimitry Andric   Distro::DistroType Version = Distro::UnknownDistro;
60e8d8bef9SDimitry Andric 
610b57cec5SDimitry Andric   for (StringRef Line : Lines)
62e8d8bef9SDimitry Andric     if (Version == Distro::UnknownDistro &&
635f757f3fSDimitry Andric         Line.starts_with("DISTRIB_CODENAME="))
640b57cec5SDimitry Andric       Version = llvm::StringSwitch<Distro::DistroType>(Line.substr(17))
650b57cec5SDimitry Andric                     .Case("hardy", Distro::UbuntuHardy)
660b57cec5SDimitry Andric                     .Case("intrepid", Distro::UbuntuIntrepid)
670b57cec5SDimitry Andric                     .Case("jaunty", Distro::UbuntuJaunty)
680b57cec5SDimitry Andric                     .Case("karmic", Distro::UbuntuKarmic)
690b57cec5SDimitry Andric                     .Case("lucid", Distro::UbuntuLucid)
700b57cec5SDimitry Andric                     .Case("maverick", Distro::UbuntuMaverick)
710b57cec5SDimitry Andric                     .Case("natty", Distro::UbuntuNatty)
720b57cec5SDimitry Andric                     .Case("oneiric", Distro::UbuntuOneiric)
730b57cec5SDimitry Andric                     .Case("precise", Distro::UbuntuPrecise)
740b57cec5SDimitry Andric                     .Case("quantal", Distro::UbuntuQuantal)
750b57cec5SDimitry Andric                     .Case("raring", Distro::UbuntuRaring)
760b57cec5SDimitry Andric                     .Case("saucy", Distro::UbuntuSaucy)
770b57cec5SDimitry Andric                     .Case("trusty", Distro::UbuntuTrusty)
780b57cec5SDimitry Andric                     .Case("utopic", Distro::UbuntuUtopic)
790b57cec5SDimitry Andric                     .Case("vivid", Distro::UbuntuVivid)
800b57cec5SDimitry Andric                     .Case("wily", Distro::UbuntuWily)
810b57cec5SDimitry Andric                     .Case("xenial", Distro::UbuntuXenial)
820b57cec5SDimitry Andric                     .Case("yakkety", Distro::UbuntuYakkety)
830b57cec5SDimitry Andric                     .Case("zesty", Distro::UbuntuZesty)
840b57cec5SDimitry Andric                     .Case("artful", Distro::UbuntuArtful)
850b57cec5SDimitry Andric                     .Case("bionic", Distro::UbuntuBionic)
860b57cec5SDimitry Andric                     .Case("cosmic", Distro::UbuntuCosmic)
870b57cec5SDimitry Andric                     .Case("disco", Distro::UbuntuDisco)
880b57cec5SDimitry Andric                     .Case("eoan", Distro::UbuntuEoan)
89480093f4SDimitry Andric                     .Case("focal", Distro::UbuntuFocal)
905ffd83dbSDimitry Andric                     .Case("groovy", Distro::UbuntuGroovy)
91e8d8bef9SDimitry Andric                     .Case("hirsute", Distro::UbuntuHirsute)
92fe6060f1SDimitry Andric                     .Case("impish", Distro::UbuntuImpish)
93349cc55cSDimitry Andric                     .Case("jammy", Distro::UbuntuJammy)
9481ad6265SDimitry Andric                     .Case("kinetic", Distro::UbuntuKinetic)
95bdd1243dSDimitry Andric                     .Case("lunar", Distro::UbuntuLunar)
9606c3fb27SDimitry Andric                     .Case("mantic", Distro::UbuntuMantic)
975f757f3fSDimitry Andric                     .Case("noble", Distro::UbuntuNoble)
980b57cec5SDimitry Andric                     .Default(Distro::UnknownDistro);
990b57cec5SDimitry Andric   return Version;
1000b57cec5SDimitry Andric }
1010b57cec5SDimitry Andric 
DetectDistro(llvm::vfs::FileSystem & VFS)102e8d8bef9SDimitry Andric static Distro::DistroType DetectDistro(llvm::vfs::FileSystem &VFS) {
103e8d8bef9SDimitry Andric   Distro::DistroType Version = Distro::UnknownDistro;
104e8d8bef9SDimitry Andric 
105e8d8bef9SDimitry Andric   // Newer freedesktop.org's compilant systemd-based systems
106e8d8bef9SDimitry Andric   // should provide /etc/os-release or /usr/lib/os-release.
107e8d8bef9SDimitry Andric   Version = DetectOsRelease(VFS);
108e8d8bef9SDimitry Andric   if (Version != Distro::UnknownDistro)
109e8d8bef9SDimitry Andric     return Version;
110e8d8bef9SDimitry Andric 
111e8d8bef9SDimitry Andric   // Older systems might provide /etc/lsb-release.
112e8d8bef9SDimitry Andric   Version = DetectLsbRelease(VFS);
113e8d8bef9SDimitry Andric   if (Version != Distro::UnknownDistro)
114e8d8bef9SDimitry Andric     return Version;
115e8d8bef9SDimitry Andric 
116e8d8bef9SDimitry Andric   // Otherwise try some distro-specific quirks for Red Hat...
117e8d8bef9SDimitry Andric   llvm::ErrorOr<std::unique_ptr<llvm::MemoryBuffer>> File =
118e8d8bef9SDimitry Andric       VFS.getBufferForFile("/etc/redhat-release");
119e8d8bef9SDimitry Andric 
1200b57cec5SDimitry Andric   if (File) {
1210b57cec5SDimitry Andric     StringRef Data = File.get()->getBuffer();
1225f757f3fSDimitry Andric     if (Data.starts_with("Fedora release"))
1230b57cec5SDimitry Andric       return Distro::Fedora;
1245f757f3fSDimitry Andric     if (Data.starts_with("Red Hat Enterprise Linux") ||
1255f757f3fSDimitry Andric         Data.starts_with("CentOS") || Data.starts_with("Scientific Linux")) {
126349cc55cSDimitry Andric       if (Data.contains("release 7"))
1270b57cec5SDimitry Andric         return Distro::RHEL7;
128349cc55cSDimitry Andric       else if (Data.contains("release 6"))
1290b57cec5SDimitry Andric         return Distro::RHEL6;
130349cc55cSDimitry Andric       else if (Data.contains("release 5"))
1310b57cec5SDimitry Andric         return Distro::RHEL5;
1320b57cec5SDimitry Andric     }
1330b57cec5SDimitry Andric     return Distro::UnknownDistro;
1340b57cec5SDimitry Andric   }
1350b57cec5SDimitry Andric 
136e8d8bef9SDimitry Andric   // ...for Debian
1370b57cec5SDimitry Andric   File = VFS.getBufferForFile("/etc/debian_version");
1380b57cec5SDimitry Andric   if (File) {
1390b57cec5SDimitry Andric     StringRef Data = File.get()->getBuffer();
1400b57cec5SDimitry Andric     // Contents: < major.minor > or < codename/sid >
1410b57cec5SDimitry Andric     int MajorVersion;
1420b57cec5SDimitry Andric     if (!Data.split('.').first.getAsInteger(10, MajorVersion)) {
1430b57cec5SDimitry Andric       switch (MajorVersion) {
1440b57cec5SDimitry Andric       case 5:
1450b57cec5SDimitry Andric         return Distro::DebianLenny;
1460b57cec5SDimitry Andric       case 6:
1470b57cec5SDimitry Andric         return Distro::DebianSqueeze;
1480b57cec5SDimitry Andric       case 7:
1490b57cec5SDimitry Andric         return Distro::DebianWheezy;
1500b57cec5SDimitry Andric       case 8:
1510b57cec5SDimitry Andric         return Distro::DebianJessie;
1520b57cec5SDimitry Andric       case 9:
1530b57cec5SDimitry Andric         return Distro::DebianStretch;
1540b57cec5SDimitry Andric       case 10:
1550b57cec5SDimitry Andric         return Distro::DebianBuster;
1560b57cec5SDimitry Andric       case 11:
1570b57cec5SDimitry Andric         return Distro::DebianBullseye;
158349cc55cSDimitry Andric       case 12:
159349cc55cSDimitry Andric         return Distro::DebianBookworm;
16081ad6265SDimitry Andric       case 13:
16181ad6265SDimitry Andric         return Distro::DebianTrixie;
1620b57cec5SDimitry Andric       default:
1630b57cec5SDimitry Andric         return Distro::UnknownDistro;
1640b57cec5SDimitry Andric       }
1650b57cec5SDimitry Andric     }
1660b57cec5SDimitry Andric     return llvm::StringSwitch<Distro::DistroType>(Data.split("\n").first)
1670b57cec5SDimitry Andric         .Case("squeeze/sid", Distro::DebianSqueeze)
1680b57cec5SDimitry Andric         .Case("wheezy/sid", Distro::DebianWheezy)
1690b57cec5SDimitry Andric         .Case("jessie/sid", Distro::DebianJessie)
1700b57cec5SDimitry Andric         .Case("stretch/sid", Distro::DebianStretch)
1710b57cec5SDimitry Andric         .Case("buster/sid", Distro::DebianBuster)
1720b57cec5SDimitry Andric         .Case("bullseye/sid", Distro::DebianBullseye)
173349cc55cSDimitry Andric         .Case("bookworm/sid", Distro::DebianBookworm)
17406c3fb27SDimitry Andric         .Case("trixie/sid", Distro::DebianTrixie)
1750b57cec5SDimitry Andric         .Default(Distro::UnknownDistro);
1760b57cec5SDimitry Andric   }
1770b57cec5SDimitry Andric 
178e8d8bef9SDimitry Andric   // ...for SUSE
1790b57cec5SDimitry Andric   File = VFS.getBufferForFile("/etc/SuSE-release");
1800b57cec5SDimitry Andric   if (File) {
1810b57cec5SDimitry Andric     StringRef Data = File.get()->getBuffer();
1820b57cec5SDimitry Andric     SmallVector<StringRef, 8> Lines;
1830b57cec5SDimitry Andric     Data.split(Lines, "\n");
1840b57cec5SDimitry Andric     for (const StringRef &Line : Lines) {
1855f757f3fSDimitry Andric       if (!Line.trim().starts_with("VERSION"))
1860b57cec5SDimitry Andric         continue;
1870b57cec5SDimitry Andric       std::pair<StringRef, StringRef> SplitLine = Line.split('=');
1880b57cec5SDimitry Andric       // Old versions have split VERSION and PATCHLEVEL
1890b57cec5SDimitry Andric       // Newer versions use VERSION = x.y
190e8d8bef9SDimitry Andric       std::pair<StringRef, StringRef> SplitVer =
191e8d8bef9SDimitry Andric           SplitLine.second.trim().split('.');
1920b57cec5SDimitry Andric       int Version;
1930b57cec5SDimitry Andric 
1940b57cec5SDimitry Andric       // OpenSUSE/SLES 10 and older are not supported and not compatible
1950b57cec5SDimitry Andric       // with our rules, so just treat them as Distro::UnknownDistro.
1960b57cec5SDimitry Andric       if (!SplitVer.first.getAsInteger(10, Version) && Version > 10)
1970b57cec5SDimitry Andric         return Distro::OpenSUSE;
1980b57cec5SDimitry Andric       return Distro::UnknownDistro;
1990b57cec5SDimitry Andric     }
2000b57cec5SDimitry Andric     return Distro::UnknownDistro;
2010b57cec5SDimitry Andric   }
2020b57cec5SDimitry Andric 
203e8d8bef9SDimitry Andric   // ...and others.
2040b57cec5SDimitry Andric   if (VFS.exists("/etc/gentoo-release"))
2050b57cec5SDimitry Andric     return Distro::Gentoo;
2060b57cec5SDimitry Andric 
2070b57cec5SDimitry Andric   return Distro::UnknownDistro;
2080b57cec5SDimitry Andric }
2090b57cec5SDimitry Andric 
GetDistro(llvm::vfs::FileSystem & VFS,const llvm::Triple & TargetOrHost)210e8d8bef9SDimitry Andric static Distro::DistroType GetDistro(llvm::vfs::FileSystem &VFS,
211e8d8bef9SDimitry Andric                                     const llvm::Triple &TargetOrHost) {
212e8d8bef9SDimitry Andric   // If we don't target Linux, no need to check the distro. This saves a few
213e8d8bef9SDimitry Andric   // OS calls.
214e8d8bef9SDimitry Andric   if (!TargetOrHost.isOSLinux())
215e8d8bef9SDimitry Andric     return Distro::UnknownDistro;
216e8d8bef9SDimitry Andric 
217e8d8bef9SDimitry Andric   // True if we're backed by a real file system.
218e8d8bef9SDimitry Andric   const bool onRealFS = (llvm::vfs::getRealFileSystem() == &VFS);
219e8d8bef9SDimitry Andric 
220e8d8bef9SDimitry Andric   // If the host is not running Linux, and we're backed by a real file
221e8d8bef9SDimitry Andric   // system, no need to check the distro. This is the case where someone
222e8d8bef9SDimitry Andric   // is cross-compiling from BSD or Windows to Linux, and it would be
223e8d8bef9SDimitry Andric   // meaningless to try to figure out the "distro" of the non-Linux host.
224e8d8bef9SDimitry Andric   llvm::Triple HostTriple(llvm::sys::getProcessTriple());
225e8d8bef9SDimitry Andric   if (!HostTriple.isOSLinux() && onRealFS)
226e8d8bef9SDimitry Andric     return Distro::UnknownDistro;
227e8d8bef9SDimitry Andric 
228e8d8bef9SDimitry Andric   if (onRealFS) {
229e8d8bef9SDimitry Andric     // If we're backed by a real file system, perform
230e8d8bef9SDimitry Andric     // the detection only once and save the result.
231e8d8bef9SDimitry Andric     static Distro::DistroType LinuxDistro = DetectDistro(VFS);
232e8d8bef9SDimitry Andric     return LinuxDistro;
233e8d8bef9SDimitry Andric   }
234e8d8bef9SDimitry Andric   // This is mostly for passing tests which uses llvm::vfs::InMemoryFileSystem,
235e8d8bef9SDimitry Andric   // which is not "real".
236e8d8bef9SDimitry Andric   return DetectDistro(VFS);
237e8d8bef9SDimitry Andric }
238e8d8bef9SDimitry Andric 
Distro(llvm::vfs::FileSystem & VFS,const llvm::Triple & TargetOrHost)239480093f4SDimitry Andric Distro::Distro(llvm::vfs::FileSystem &VFS, const llvm::Triple &TargetOrHost)
240e8d8bef9SDimitry Andric     : DistroVal(GetDistro(VFS, TargetOrHost)) {}
241