Distro.cpp 9.0 KB

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