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

source code of clang/lib/Driver/Distro.cpp