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