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
DetectOsRelease(llvm::vfs::FileSystem & VFS)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("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
DetectLsbRelease(llvm::vfs::FileSystem & VFS)50 static 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.startswith("DISTRIB_CODENAME="))
63 Version = llvm::StringSwitch<Distro::DistroType>(Line.substr(17))
64 .Case("hardy", Distro::UbuntuHardy)
65 .Case("intrepid", Distro::UbuntuIntrepid)
66 .Case("jaunty", Distro::UbuntuJaunty)
67 .Case("karmic", Distro::UbuntuKarmic)
68 .Case("lucid", Distro::UbuntuLucid)
69 .Case("maverick", Distro::UbuntuMaverick)
70 .Case("natty", Distro::UbuntuNatty)
71 .Case("oneiric", Distro::UbuntuOneiric)
72 .Case("precise", Distro::UbuntuPrecise)
73 .Case("quantal", Distro::UbuntuQuantal)
74 .Case("raring", Distro::UbuntuRaring)
75 .Case("saucy", Distro::UbuntuSaucy)
76 .Case("trusty", Distro::UbuntuTrusty)
77 .Case("utopic", Distro::UbuntuUtopic)
78 .Case("vivid", Distro::UbuntuVivid)
79 .Case("wily", Distro::UbuntuWily)
80 .Case("xenial", Distro::UbuntuXenial)
81 .Case("yakkety", Distro::UbuntuYakkety)
82 .Case("zesty", Distro::UbuntuZesty)
83 .Case("artful", Distro::UbuntuArtful)
84 .Case("bionic", Distro::UbuntuBionic)
85 .Case("cosmic", Distro::UbuntuCosmic)
86 .Case("disco", Distro::UbuntuDisco)
87 .Case("eoan", Distro::UbuntuEoan)
88 .Case("focal", Distro::UbuntuFocal)
89 .Case("groovy", Distro::UbuntuGroovy)
90 .Case("hirsute", Distro::UbuntuHirsute)
91 .Default(Distro::UnknownDistro);
92 return Version;
93 }
94
DetectDistro(llvm::vfs::FileSystem & VFS)95 static Distro::DistroType DetectDistro(llvm::vfs::FileSystem &VFS) {
96 Distro::DistroType Version = Distro::UnknownDistro;
97
98 // Newer freedesktop.org's compilant systemd-based systems
99 // should provide /etc/os-release or /usr/lib/os-release.
100 Version = DetectOsRelease(VFS);
101 if (Version != Distro::UnknownDistro)
102 return Version;
103
104 // Older systems might provide /etc/lsb-release.
105 Version = DetectLsbRelease(VFS);
106 if (Version != Distro::UnknownDistro)
107 return Version;
108
109 // Otherwise try some distro-specific quirks for RedHat...
110 llvm::ErrorOr<std::unique_ptr<llvm::MemoryBuffer>> File =
111 VFS.getBufferForFile("/etc/redhat-release");
112
113 if (File) {
114 StringRef Data = File.get()->getBuffer();
115 if (Data.startswith("Fedora release"))
116 return Distro::Fedora;
117 if (Data.startswith("Red Hat Enterprise Linux") ||
118 Data.startswith("CentOS") || Data.startswith("Scientific Linux")) {
119 if (Data.find("release 7") != StringRef::npos)
120 return Distro::RHEL7;
121 else if (Data.find("release 6") != StringRef::npos)
122 return Distro::RHEL6;
123 else if (Data.find("release 5") != StringRef::npos)
124 return Distro::RHEL5;
125 }
126 return Distro::UnknownDistro;
127 }
128
129 // ...for Debian
130 File = VFS.getBufferForFile("/etc/debian_version");
131 if (File) {
132 StringRef Data = File.get()->getBuffer();
133 // Contents: < major.minor > or < codename/sid >
134 int MajorVersion;
135 if (!Data.split('.').first.getAsInteger(10, MajorVersion)) {
136 switch (MajorVersion) {
137 case 5:
138 return Distro::DebianLenny;
139 case 6:
140 return Distro::DebianSqueeze;
141 case 7:
142 return Distro::DebianWheezy;
143 case 8:
144 return Distro::DebianJessie;
145 case 9:
146 return Distro::DebianStretch;
147 case 10:
148 return Distro::DebianBuster;
149 case 11:
150 return Distro::DebianBullseye;
151 default:
152 return Distro::UnknownDistro;
153 }
154 }
155 return llvm::StringSwitch<Distro::DistroType>(Data.split("\n").first)
156 .Case("squeeze/sid", Distro::DebianSqueeze)
157 .Case("wheezy/sid", Distro::DebianWheezy)
158 .Case("jessie/sid", Distro::DebianJessie)
159 .Case("stretch/sid", Distro::DebianStretch)
160 .Case("buster/sid", Distro::DebianBuster)
161 .Case("bullseye/sid", Distro::DebianBullseye)
162 .Default(Distro::UnknownDistro);
163 }
164
165 // ...for SUSE
166 File = VFS.getBufferForFile("/etc/SuSE-release");
167 if (File) {
168 StringRef Data = File.get()->getBuffer();
169 SmallVector<StringRef, 8> Lines;
170 Data.split(Lines, "\n");
171 for (const StringRef &Line : Lines) {
172 if (!Line.trim().startswith("VERSION"))
173 continue;
174 std::pair<StringRef, StringRef> SplitLine = Line.split('=');
175 // Old versions have split VERSION and PATCHLEVEL
176 // Newer versions use VERSION = x.y
177 std::pair<StringRef, StringRef> SplitVer =
178 SplitLine.second.trim().split('.');
179 int Version;
180
181 // OpenSUSE/SLES 10 and older are not supported and not compatible
182 // with our rules, so just treat them as Distro::UnknownDistro.
183 if (!SplitVer.first.getAsInteger(10, Version) && Version > 10)
184 return Distro::OpenSUSE;
185 return Distro::UnknownDistro;
186 }
187 return Distro::UnknownDistro;
188 }
189
190 // ...and others.
191 if (VFS.exists("/etc/exherbo-release"))
192 return Distro::Exherbo;
193
194 if (VFS.exists("/etc/alpine-release"))
195 return Distro::AlpineLinux;
196
197 if (VFS.exists("/etc/arch-release"))
198 return Distro::ArchLinux;
199
200 if (VFS.exists("/etc/gentoo-release"))
201 return Distro::Gentoo;
202
203 return Distro::UnknownDistro;
204 }
205
GetDistro(llvm::vfs::FileSystem & VFS,const llvm::Triple & TargetOrHost)206 static 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 few
209 // 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 file
217 // system, no need to check the distro. This is the case where someone
218 // is cross-compiling from BSD or Windows to Linux, and it would be
219 // 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, perform
226 // 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
Distro(llvm::vfs::FileSystem & VFS,const llvm::Triple & TargetOrHost)235 Distro::Distro(llvm::vfs::FileSystem &VFS, const llvm::Triple &TargetOrHost)
236 : DistroVal(GetDistro(VFS, TargetOrHost)) {}
237