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