Home | History | Annotate | Line # | Download | only in Driver
      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-exception
      6 //
      7 //===----------------------------------------------------------------------===//
      8 
      9 #include "clang/Driver/Distro.h"
     10 #include "clang/Basic/LLVM.h"
     11 #include "llvm/ADT/SmallVector.h"
     12 #include "llvm/ADT/StringRef.h"
     13 #include "llvm/ADT/StringSwitch.h"
     14 #include "llvm/ADT/Triple.h"
     15 #include "llvm/Support/ErrorOr.h"
     16 #include "llvm/Support/Host.h"
     17 #include "llvm/Support/MemoryBuffer.h"
     18 #include "llvm/Support/Threading.h"
     19 
     20 using namespace clang::driver;
     21 using namespace clang;
     22 
     23 static Distro::DistroType DetectOsRelease(llvm::vfs::FileSystem &VFS) {
     24   llvm::ErrorOr<std::unique_ptr<llvm::MemoryBuffer>> File =
     25       VFS.getBufferForFile("/etc/os-release");
     26   if (!File)
     27     File = VFS.getBufferForFile("/usr/lib/os-release");
     28   if (!File)
     29     return Distro::UnknownDistro;
     30 
     31   SmallVector<StringRef, 16> Lines;
     32   File.get()->getBuffer().split(Lines, "\n");
     33   Distro::DistroType Version = Distro::UnknownDistro;
     34 
     35   // Obviously this can be improved a lot.
     36   for (StringRef Line : Lines)
     37     if (Version == Distro::UnknownDistro && Line.startswith("ID="))
     38       Version = llvm::StringSwitch<Distro::DistroType>(Line.substr(3))
     39                     .Case("alpine", Distro::AlpineLinux)
     40                     .Case("fedora", Distro::Fedora)
     41                     .Case("gentoo", Distro::Gentoo)
     42                     .Case("arch", Distro::ArchLinux)
     43                     // On SLES, /etc/os-release was introduced in SLES 11.
     44                     .Case("sles", Distro::OpenSUSE)
     45                     .Case("opensuse", Distro::OpenSUSE)
     46                     .Case("exherbo", Distro::Exherbo)
     47                     .Default(Distro::UnknownDistro);
     48   return Version;
     49 }
     50 
     51 static Distro::DistroType DetectLsbRelease(llvm::vfs::FileSystem &VFS) {
     52   llvm::ErrorOr<std::unique_ptr<llvm::MemoryBuffer>> File =
     53       VFS.getBufferForFile("/etc/lsb-release");
     54   if (!File)
     55     return Distro::UnknownDistro;
     56 
     57   SmallVector<StringRef, 16> Lines;
     58   File.get()->getBuffer().split(Lines, "\n");
     59   Distro::DistroType Version = Distro::UnknownDistro;
     60 
     61   for (StringRef Line : Lines)
     62     if (Version == Distro::UnknownDistro &&
     63         Line.startswith("DISTRIB_CODENAME="))
     64       Version = llvm::StringSwitch<Distro::DistroType>(Line.substr(17))
     65                     .Case("hardy", Distro::UbuntuHardy)
     66                     .Case("intrepid", Distro::UbuntuIntrepid)
     67                     .Case("jaunty", Distro::UbuntuJaunty)
     68                     .Case("karmic", Distro::UbuntuKarmic)
     69                     .Case("lucid", Distro::UbuntuLucid)
     70                     .Case("maverick", Distro::UbuntuMaverick)
     71                     .Case("natty", Distro::UbuntuNatty)
     72                     .Case("oneiric", Distro::UbuntuOneiric)
     73                     .Case("precise", Distro::UbuntuPrecise)
     74                     .Case("quantal", Distro::UbuntuQuantal)
     75                     .Case("raring", Distro::UbuntuRaring)
     76                     .Case("saucy", Distro::UbuntuSaucy)
     77                     .Case("trusty", Distro::UbuntuTrusty)
     78                     .Case("utopic", Distro::UbuntuUtopic)
     79                     .Case("vivid", Distro::UbuntuVivid)
     80                     .Case("wily", Distro::UbuntuWily)
     81                     .Case("xenial", Distro::UbuntuXenial)
     82                     .Case("yakkety", Distro::UbuntuYakkety)
     83                     .Case("zesty", Distro::UbuntuZesty)
     84                     .Case("artful", Distro::UbuntuArtful)
     85                     .Case("bionic", Distro::UbuntuBionic)
     86                     .Case("cosmic", Distro::UbuntuCosmic)
     87                     .Case("disco", Distro::UbuntuDisco)
     88                     .Case("eoan", Distro::UbuntuEoan)
     89                     .Case("focal", Distro::UbuntuFocal)
     90                     .Case("groovy", Distro::UbuntuGroovy)
     91                     .Case("hirsute", Distro::UbuntuHirsute)
     92                     .Case("impish", Distro::UbuntuImpish)
     93                     .Default(Distro::UnknownDistro);
     94   return Version;
     95 }
     96 
     97 static Distro::DistroType DetectDistro(llvm::vfs::FileSystem &VFS) {
     98   Distro::DistroType Version = Distro::UnknownDistro;
     99 
    100   // Newer freedesktop.org's compilant systemd-based systems
    101   // should provide /etc/os-release or /usr/lib/os-release.
    102   Version = DetectOsRelease(VFS);
    103   if (Version != Distro::UnknownDistro)
    104     return Version;
    105 
    106   // Older systems might provide /etc/lsb-release.
    107   Version = DetectLsbRelease(VFS);
    108   if (Version != Distro::UnknownDistro)
    109     return Version;
    110 
    111   // Otherwise try some distro-specific quirks for RedHat...
    112   llvm::ErrorOr<std::unique_ptr<llvm::MemoryBuffer>> File =
    113       VFS.getBufferForFile("/etc/redhat-release");
    114 
    115   if (File) {
    116     StringRef Data = File.get()->getBuffer();
    117     if (Data.startswith("Fedora release"))
    118       return Distro::Fedora;
    119     if (Data.startswith("Red Hat Enterprise Linux") ||
    120         Data.startswith("CentOS") || Data.startswith("Scientific Linux")) {
    121       if (Data.find("release 7") != StringRef::npos)
    122         return Distro::RHEL7;
    123       else if (Data.find("release 6") != StringRef::npos)
    124         return Distro::RHEL6;
    125       else if (Data.find("release 5") != StringRef::npos)
    126         return Distro::RHEL5;
    127     }
    128     return Distro::UnknownDistro;
    129   }
    130 
    131   // ...for Debian
    132   File = VFS.getBufferForFile("/etc/debian_version");
    133   if (File) {
    134     StringRef Data = File.get()->getBuffer();
    135     // Contents: < major.minor > or < codename/sid >
    136     int MajorVersion;
    137     if (!Data.split('.').first.getAsInteger(10, MajorVersion)) {
    138       switch (MajorVersion) {
    139       case 5:
    140         return Distro::DebianLenny;
    141       case 6:
    142         return Distro::DebianSqueeze;
    143       case 7:
    144         return Distro::DebianWheezy;
    145       case 8:
    146         return Distro::DebianJessie;
    147       case 9:
    148         return Distro::DebianStretch;
    149       case 10:
    150         return Distro::DebianBuster;
    151       case 11:
    152         return Distro::DebianBullseye;
    153       default:
    154         return Distro::UnknownDistro;
    155       }
    156     }
    157     return llvm::StringSwitch<Distro::DistroType>(Data.split("\n").first)
    158         .Case("squeeze/sid", Distro::DebianSqueeze)
    159         .Case("wheezy/sid", Distro::DebianWheezy)
    160         .Case("jessie/sid", Distro::DebianJessie)
    161         .Case("stretch/sid", Distro::DebianStretch)
    162         .Case("buster/sid", Distro::DebianBuster)
    163         .Case("bullseye/sid", Distro::DebianBullseye)
    164         .Default(Distro::UnknownDistro);
    165   }
    166 
    167   // ...for SUSE
    168   File = VFS.getBufferForFile("/etc/SuSE-release");
    169   if (File) {
    170     StringRef Data = File.get()->getBuffer();
    171     SmallVector<StringRef, 8> Lines;
    172     Data.split(Lines, "\n");
    173     for (const StringRef &Line : Lines) {
    174       if (!Line.trim().startswith("VERSION"))
    175         continue;
    176       std::pair<StringRef, StringRef> SplitLine = Line.split('=');
    177       // Old versions have split VERSION and PATCHLEVEL
    178       // Newer versions use VERSION = x.y
    179       std::pair<StringRef, StringRef> SplitVer =
    180           SplitLine.second.trim().split('.');
    181       int Version;
    182 
    183       // OpenSUSE/SLES 10 and older are not supported and not compatible
    184       // with our rules, so just treat them as Distro::UnknownDistro.
    185       if (!SplitVer.first.getAsInteger(10, Version) && Version > 10)
    186         return Distro::OpenSUSE;
    187       return Distro::UnknownDistro;
    188     }
    189     return Distro::UnknownDistro;
    190   }
    191 
    192   // ...and others.
    193   if (VFS.exists("/etc/gentoo-release"))
    194     return Distro::Gentoo;
    195 
    196   return Distro::UnknownDistro;
    197 }
    198 
    199 static Distro::DistroType GetDistro(llvm::vfs::FileSystem &VFS,
    200                                     const llvm::Triple &TargetOrHost) {
    201   // If we don't target Linux, no need to check the distro. This saves a few
    202   // OS calls.
    203   if (!TargetOrHost.isOSLinux())
    204     return Distro::UnknownDistro;
    205 
    206   // True if we're backed by a real file system.
    207   const bool onRealFS = (llvm::vfs::getRealFileSystem() == &VFS);
    208 
    209   // If the host is not running Linux, and we're backed by a real file
    210   // system, no need to check the distro. This is the case where someone
    211   // is cross-compiling from BSD or Windows to Linux, and it would be
    212   // meaningless to try to figure out the "distro" of the non-Linux host.
    213   llvm::Triple HostTriple(llvm::sys::getProcessTriple());
    214   if (!HostTriple.isOSLinux() && onRealFS)
    215     return Distro::UnknownDistro;
    216 
    217   if (onRealFS) {
    218     // If we're backed by a real file system, perform
    219     // the detection only once and save the result.
    220     static Distro::DistroType LinuxDistro = DetectDistro(VFS);
    221     return LinuxDistro;
    222   }
    223   // This is mostly for passing tests which uses llvm::vfs::InMemoryFileSystem,
    224   // which is not "real".
    225   return DetectDistro(VFS);
    226 }
    227 
    228 Distro::Distro(llvm::vfs::FileSystem &VFS, const llvm::Triple &TargetOrHost)
    229     : DistroVal(GetDistro(VFS, TargetOrHost)) {}
    230