detect-libc.js 6.1 KB

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