detect-libc.js 7.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313
  1. // Copyright 2017 Lovell Fuller and others.
  2. // SPDX-License-Identifier: Apache-2.0
  3. 'use strict';
  4. const childProcess = require('child_process');
  5. const { isLinux, getReport } = require('./process');
  6. const { LDD_PATH, SELF_PATH, readFile, readFileSync } = require('./filesystem');
  7. const { interpreterPath } = require('./elf');
  8. let cachedFamilyInterpreter;
  9. let cachedFamilyFilesystem;
  10. let cachedVersionFilesystem;
  11. const command = 'getconf GNU_LIBC_VERSION 2>&1 || true; ldd --version 2>&1 || true';
  12. let commandOut = '';
  13. const safeCommand = () => {
  14. if (!commandOut) {
  15. return new Promise((resolve) => {
  16. childProcess.exec(command, (err, out) => {
  17. commandOut = err ? ' ' : out;
  18. resolve(commandOut);
  19. });
  20. });
  21. }
  22. return commandOut;
  23. };
  24. const safeCommandSync = () => {
  25. if (!commandOut) {
  26. try {
  27. commandOut = childProcess.execSync(command, { encoding: 'utf8' });
  28. } catch (_err) {
  29. commandOut = ' ';
  30. }
  31. }
  32. return commandOut;
  33. };
  34. /**
  35. * A String constant containing the value `glibc`.
  36. * @type {string}
  37. * @public
  38. */
  39. const GLIBC = 'glibc';
  40. /**
  41. * A Regexp constant to get the GLIBC Version.
  42. * @type {string}
  43. */
  44. const RE_GLIBC_VERSION = /LIBC[a-z0-9 \-).]*?(\d+\.\d+)/i;
  45. /**
  46. * A String constant containing the value `musl`.
  47. * @type {string}
  48. * @public
  49. */
  50. const MUSL = 'musl';
  51. const isFileMusl = (f) => f.includes('libc.musl-') || f.includes('ld-musl-');
  52. const familyFromReport = () => {
  53. const report = getReport();
  54. if (report.header && report.header.glibcVersionRuntime) {
  55. return GLIBC;
  56. }
  57. if (Array.isArray(report.sharedObjects)) {
  58. if (report.sharedObjects.some(isFileMusl)) {
  59. return MUSL;
  60. }
  61. }
  62. return null;
  63. };
  64. const familyFromCommand = (out) => {
  65. const [getconf, ldd1] = out.split(/[\r\n]+/);
  66. if (getconf && getconf.includes(GLIBC)) {
  67. return GLIBC;
  68. }
  69. if (ldd1 && ldd1.includes(MUSL)) {
  70. return MUSL;
  71. }
  72. return null;
  73. };
  74. const familyFromInterpreterPath = (path) => {
  75. if (path) {
  76. if (path.includes('/ld-musl-')) {
  77. return MUSL;
  78. } else if (path.includes('/ld-linux-')) {
  79. return GLIBC;
  80. }
  81. }
  82. return null;
  83. };
  84. const getFamilyFromLddContent = (content) => {
  85. content = content.toString();
  86. if (content.includes('musl')) {
  87. return MUSL;
  88. }
  89. if (content.includes('GNU C Library')) {
  90. return GLIBC;
  91. }
  92. return null;
  93. };
  94. const familyFromFilesystem = async () => {
  95. if (cachedFamilyFilesystem !== undefined) {
  96. return cachedFamilyFilesystem;
  97. }
  98. cachedFamilyFilesystem = null;
  99. try {
  100. const lddContent = await readFile(LDD_PATH);
  101. cachedFamilyFilesystem = getFamilyFromLddContent(lddContent);
  102. } catch (e) {}
  103. return cachedFamilyFilesystem;
  104. };
  105. const familyFromFilesystemSync = () => {
  106. if (cachedFamilyFilesystem !== undefined) {
  107. return cachedFamilyFilesystem;
  108. }
  109. cachedFamilyFilesystem = null;
  110. try {
  111. const lddContent = readFileSync(LDD_PATH);
  112. cachedFamilyFilesystem = getFamilyFromLddContent(lddContent);
  113. } catch (e) {}
  114. return cachedFamilyFilesystem;
  115. };
  116. const familyFromInterpreter = async () => {
  117. if (cachedFamilyInterpreter !== undefined) {
  118. return cachedFamilyInterpreter;
  119. }
  120. cachedFamilyInterpreter = null;
  121. try {
  122. const selfContent = await readFile(SELF_PATH);
  123. const path = interpreterPath(selfContent);
  124. cachedFamilyInterpreter = familyFromInterpreterPath(path);
  125. } catch (e) {}
  126. return cachedFamilyInterpreter;
  127. };
  128. const familyFromInterpreterSync = () => {
  129. if (cachedFamilyInterpreter !== undefined) {
  130. return cachedFamilyInterpreter;
  131. }
  132. cachedFamilyInterpreter = null;
  133. try {
  134. const selfContent = readFileSync(SELF_PATH);
  135. const path = interpreterPath(selfContent);
  136. cachedFamilyInterpreter = familyFromInterpreterPath(path);
  137. } catch (e) {}
  138. return cachedFamilyInterpreter;
  139. };
  140. /**
  141. * Resolves with the libc family when it can be determined, `null` otherwise.
  142. * @returns {Promise<?string>}
  143. */
  144. const family = async () => {
  145. let family = null;
  146. if (isLinux()) {
  147. family = await familyFromInterpreter();
  148. if (!family) {
  149. family = await familyFromFilesystem();
  150. if (!family) {
  151. family = familyFromReport();
  152. }
  153. if (!family) {
  154. const out = await safeCommand();
  155. family = familyFromCommand(out);
  156. }
  157. }
  158. }
  159. return family;
  160. };
  161. /**
  162. * Returns the libc family when it can be determined, `null` otherwise.
  163. * @returns {?string}
  164. */
  165. const familySync = () => {
  166. let family = null;
  167. if (isLinux()) {
  168. family = familyFromInterpreterSync();
  169. if (!family) {
  170. family = familyFromFilesystemSync();
  171. if (!family) {
  172. family = familyFromReport();
  173. }
  174. if (!family) {
  175. const out = safeCommandSync();
  176. family = familyFromCommand(out);
  177. }
  178. }
  179. }
  180. return family;
  181. };
  182. /**
  183. * Resolves `true` only when the platform is Linux and the libc family is not `glibc`.
  184. * @returns {Promise<boolean>}
  185. */
  186. const isNonGlibcLinux = async () => isLinux() && await family() !== GLIBC;
  187. /**
  188. * Returns `true` only when the platform is Linux and the libc family is not `glibc`.
  189. * @returns {boolean}
  190. */
  191. const isNonGlibcLinuxSync = () => isLinux() && familySync() !== GLIBC;
  192. const versionFromFilesystem = async () => {
  193. if (cachedVersionFilesystem !== undefined) {
  194. return cachedVersionFilesystem;
  195. }
  196. cachedVersionFilesystem = null;
  197. try {
  198. const lddContent = await readFile(LDD_PATH);
  199. const versionMatch = lddContent.match(RE_GLIBC_VERSION);
  200. if (versionMatch) {
  201. cachedVersionFilesystem = versionMatch[1];
  202. }
  203. } catch (e) {}
  204. return cachedVersionFilesystem;
  205. };
  206. const versionFromFilesystemSync = () => {
  207. if (cachedVersionFilesystem !== undefined) {
  208. return cachedVersionFilesystem;
  209. }
  210. cachedVersionFilesystem = null;
  211. try {
  212. const lddContent = readFileSync(LDD_PATH);
  213. const versionMatch = lddContent.match(RE_GLIBC_VERSION);
  214. if (versionMatch) {
  215. cachedVersionFilesystem = versionMatch[1];
  216. }
  217. } catch (e) {}
  218. return cachedVersionFilesystem;
  219. };
  220. const versionFromReport = () => {
  221. const report = getReport();
  222. if (report.header && report.header.glibcVersionRuntime) {
  223. return report.header.glibcVersionRuntime;
  224. }
  225. return null;
  226. };
  227. const versionSuffix = (s) => s.trim().split(/\s+/)[1];
  228. const versionFromCommand = (out) => {
  229. const [getconf, ldd1, ldd2] = out.split(/[\r\n]+/);
  230. if (getconf && getconf.includes(GLIBC)) {
  231. return versionSuffix(getconf);
  232. }
  233. if (ldd1 && ldd2 && ldd1.includes(MUSL)) {
  234. return versionSuffix(ldd2);
  235. }
  236. return null;
  237. };
  238. /**
  239. * Resolves with the libc version when it can be determined, `null` otherwise.
  240. * @returns {Promise<?string>}
  241. */
  242. const version = async () => {
  243. let version = null;
  244. if (isLinux()) {
  245. version = await versionFromFilesystem();
  246. if (!version) {
  247. version = versionFromReport();
  248. }
  249. if (!version) {
  250. const out = await safeCommand();
  251. version = versionFromCommand(out);
  252. }
  253. }
  254. return version;
  255. };
  256. /**
  257. * Returns the libc version when it can be determined, `null` otherwise.
  258. * @returns {?string}
  259. */
  260. const versionSync = () => {
  261. let version = null;
  262. if (isLinux()) {
  263. version = versionFromFilesystemSync();
  264. if (!version) {
  265. version = versionFromReport();
  266. }
  267. if (!version) {
  268. const out = safeCommandSync();
  269. version = versionFromCommand(out);
  270. }
  271. }
  272. return version;
  273. };
  274. module.exports = {
  275. GLIBC,
  276. MUSL,
  277. family,
  278. familySync,
  279. isNonGlibcLinux,
  280. isNonGlibcLinuxSync,
  281. version,
  282. versionSync
  283. };