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/Support/ErrorOr.h"
15 #include "llvm/Support/MemoryBuffer.h"
16 #include "llvm/Support/Threading.h"
17 #include "llvm/TargetParser/Host.h"
18 #include "llvm/TargetParser/Triple.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                     .Case("jammy", Distro::UbuntuJammy)
94                     .Case("kinetic", Distro::UbuntuKinetic)
95                     .Case("lunar", Distro::UbuntuLunar)
96                     .Case("mantic", Distro::UbuntuMantic)
97                     .Default(Distro::UnknownDistro);
98   return Version;
99 }
100 
101 static Distro::DistroType DetectDistro(llvm::vfs::FileSystem &VFS) {
102   Distro::DistroType Version = Distro::UnknownDistro;
103 
104   // Newer freedesktop.org's compilant systemd-based systems
105   // should provide /etc/os-release or /usr/lib/os-release.
106   Version = DetectOsRelease(VFS);
107   if (Version != Distro::UnknownDistro)
108     return Version;
109 
110   // Older systems might provide /etc/lsb-release.
111   Version = DetectLsbRelease(VFS);
112   if (Version != Distro::UnknownDistro)
113     return Version;
114 
115   // Otherwise try some distro-specific quirks for RedHat...
116   llvm::ErrorOr<std::unique_ptr<llvm::MemoryBuffer>> File =
117       VFS.getBufferForFile("/etc/redhat-release");
118 
119   if (File) {
120     StringRef Data = File.get()->getBuffer();
121     if (Data.startswith("Fedora release"))
122       return Distro::Fedora;
123     if (Data.startswith("Red Hat Enterprise Linux") ||
124         Data.startswith("CentOS") || Data.startswith("Scientific Linux")) {
125       if (Data.contains("release 7"))
126         return Distro::RHEL7;
127       else if (Data.contains("release 6"))
128         return Distro::RHEL6;
129       else if (Data.contains("release 5"))
130         return Distro::RHEL5;
131     }
132     return Distro::UnknownDistro;
133   }
134 
135   // ...for Debian
136   File = VFS.getBufferForFile("/etc/debian_version");
137   if (File) {
138     StringRef Data = File.get()->getBuffer();
139     // Contents: < major.minor > or < codename/sid >
140     int MajorVersion;
141     if (!Data.split('.').first.getAsInteger(10, MajorVersion)) {
142       switch (MajorVersion) {
143       case 5:
144         return Distro::DebianLenny;
145       case 6:
146         return Distro::DebianSqueeze;
147       case 7:
148         return Distro::DebianWheezy;
149       case 8:
150         return Distro::DebianJessie;
151       case 9:
152         return Distro::DebianStretch;
153       case 10:
154         return Distro::DebianBuster;
155       case 11:
156         return Distro::DebianBullseye;
157       case 12:
158         return Distro::DebianBookworm;
159       case 13:
160         return Distro::DebianTrixie;
161       default:
162         return Distro::UnknownDistro;
163       }
164     }
165     return llvm::StringSwitch<Distro::DistroType>(Data.split("\n").first)
166         .Case("squeeze/sid", Distro::DebianSqueeze)
167         .Case("wheezy/sid", Distro::DebianWheezy)
168         .Case("jessie/sid", Distro::DebianJessie)
169         .Case("stretch/sid", Distro::DebianStretch)
170         .Case("buster/sid", Distro::DebianBuster)
171         .Case("bullseye/sid", Distro::DebianBullseye)
172         .Case("bookworm/sid", Distro::DebianBookworm)
173         .Case("trixie/sid", Distro::DebianTrixie)
174         .Default(Distro::UnknownDistro);
175   }
176 
177   // ...for SUSE
178   File = VFS.getBufferForFile("/etc/SuSE-release");
179   if (File) {
180     StringRef Data = File.get()->getBuffer();
181     SmallVector<StringRef, 8> Lines;
182     Data.split(Lines, "\n");
183     for (const StringRef &Line : Lines) {
184       if (!Line.trim().startswith("VERSION"))
185         continue;
186       std::pair<StringRef, StringRef> SplitLine = Line.split('=');
187       // Old versions have split VERSION and PATCHLEVEL
188       // Newer versions use VERSION = x.y
189       std::pair<StringRef, StringRef> SplitVer =
190           SplitLine.second.trim().split('.');
191       int Version;
192 
193       // OpenSUSE/SLES 10 and older are not supported and not compatible
194       // with our rules, so just treat them as Distro::UnknownDistro.
195       if (!SplitVer.first.getAsInteger(10, Version) && Version > 10)
196         return Distro::OpenSUSE;
197       return Distro::UnknownDistro;
198     }
199     return Distro::UnknownDistro;
200   }
201 
202   // ...and others.
203   if (VFS.exists("/etc/gentoo-release"))
204     return Distro::Gentoo;
205 
206   return Distro::UnknownDistro;
207 }
208 
209 static Distro::DistroType GetDistro(llvm::vfs::FileSystem &VFS,
210                                     const llvm::Triple &TargetOrHost) {
211   // If we don't target Linux, no need to check the distro. This saves a few
212   // OS calls.
213   if (!TargetOrHost.isOSLinux())
214     return Distro::UnknownDistro;
215 
216   // True if we're backed by a real file system.
217   const bool onRealFS = (llvm::vfs::getRealFileSystem() == &VFS);
218 
219   // If the host is not running Linux, and we're backed by a real file
220   // system, no need to check the distro. This is the case where someone
221   // is cross-compiling from BSD or Windows to Linux, and it would be
222   // meaningless to try to figure out the "distro" of the non-Linux host.
223   llvm::Triple HostTriple(llvm::sys::getProcessTriple());
224   if (!HostTriple.isOSLinux() && onRealFS)
225     return Distro::UnknownDistro;
226 
227   if (onRealFS) {
228     // If we're backed by a real file system, perform
229     // the detection only once and save the result.
230     static Distro::DistroType LinuxDistro = DetectDistro(VFS);
231     return LinuxDistro;
232   }
233   // This is mostly for passing tests which uses llvm::vfs::InMemoryFileSystem,
234   // which is not "real".
235   return DetectDistro(VFS);
236 }
237 
238 Distro::Distro(llvm::vfs::FileSystem &VFS, const llvm::Triple &TargetOrHost)
239     : DistroVal(GetDistro(VFS, TargetOrHost)) {}
240