the-forest/client/node_modules/detect-libc/lib/detect-libc.js
2024-09-17 20:35:18 -04:00

280 lines
6.3 KiB
JavaScript

// Copyright 2017 Lovell Fuller and others.
// SPDX-License-Identifier: Apache-2.0
'use strict';
const childProcess = require('child_process');
const { isLinux, getReport } = require('./process');
const { LDD_PATH, readFile, readFileSync } = require('./filesystem');
let cachedFamilyFilesystem;
let cachedVersionFilesystem;
const command = 'getconf GNU_LIBC_VERSION 2>&1 || true; ldd --version 2>&1 || true';
let commandOut = '';
const safeCommand = () => {
if (!commandOut) {
return new Promise((resolve) => {
childProcess.exec(command, (err, out) => {
commandOut = err ? ' ' : out;
resolve(commandOut);
});
});
}
return commandOut;
};
const safeCommandSync = () => {
if (!commandOut) {
try {
commandOut = childProcess.execSync(command, { encoding: 'utf8' });
} catch (_err) {
commandOut = ' ';
}
}
return commandOut;
};
/**
* A String constant containing the value `glibc`.
* @type {string}
* @public
*/
const GLIBC = 'glibc';
/**
* A Regexp constant to get the GLIBC Version.
* @type {string}
*/
const RE_GLIBC_VERSION = /GLIBC\s(\d+\.\d+)/;
/**
* A String constant containing the value `musl`.
* @type {string}
* @public
*/
const MUSL = 'musl';
/**
* This string is used to find if the {@link LDD_PATH} is GLIBC
* @type {string}
*/
const GLIBC_ON_LDD = GLIBC.toUpperCase();
/**
* This string is used to find if the {@link LDD_PATH} is musl
* @type {string}
*/
const MUSL_ON_LDD = MUSL.toLowerCase();
const isFileMusl = (f) => f.includes('libc.musl-') || f.includes('ld-musl-');
const familyFromReport = () => {
const report = getReport();
if (report.header && report.header.glibcVersionRuntime) {
return GLIBC;
}
if (Array.isArray(report.sharedObjects)) {
if (report.sharedObjects.some(isFileMusl)) {
return MUSL;
}
}
return null;
};
const familyFromCommand = (out) => {
const [getconf, ldd1] = out.split(/[\r\n]+/);
if (getconf && getconf.includes(GLIBC)) {
return GLIBC;
}
if (ldd1 && ldd1.includes(MUSL)) {
return MUSL;
}
return null;
};
const getFamilyFromLddContent = (content) => {
if (content.includes(MUSL_ON_LDD)) {
return MUSL;
}
if (content.includes(GLIBC_ON_LDD)) {
return GLIBC;
}
return null;
};
const familyFromFilesystem = async () => {
if (cachedFamilyFilesystem !== undefined) {
return cachedFamilyFilesystem;
}
cachedFamilyFilesystem = null;
try {
const lddContent = await readFile(LDD_PATH);
cachedFamilyFilesystem = getFamilyFromLddContent(lddContent);
} catch (e) {}
return cachedFamilyFilesystem;
};
const familyFromFilesystemSync = () => {
if (cachedFamilyFilesystem !== undefined) {
return cachedFamilyFilesystem;
}
cachedFamilyFilesystem = null;
try {
const lddContent = readFileSync(LDD_PATH);
cachedFamilyFilesystem = getFamilyFromLddContent(lddContent);
} catch (e) {}
return cachedFamilyFilesystem;
};
/**
* Resolves with the libc family when it can be determined, `null` otherwise.
* @returns {Promise<?string>}
*/
const family = async () => {
let family = null;
if (isLinux()) {
family = await familyFromFilesystem();
if (!family) {
family = familyFromReport();
}
if (!family) {
const out = await safeCommand();
family = familyFromCommand(out);
}
}
return family;
};
/**
* Returns the libc family when it can be determined, `null` otherwise.
* @returns {?string}
*/
const familySync = () => {
let family = null;
if (isLinux()) {
family = familyFromFilesystemSync();
if (!family) {
family = familyFromReport();
}
if (!family) {
const out = safeCommandSync();
family = familyFromCommand(out);
}
}
return family;
};
/**
* Resolves `true` only when the platform is Linux and the libc family is not `glibc`.
* @returns {Promise<boolean>}
*/
const isNonGlibcLinux = async () => isLinux() && await family() !== GLIBC;
/**
* Returns `true` only when the platform is Linux and the libc family is not `glibc`.
* @returns {boolean}
*/
const isNonGlibcLinuxSync = () => isLinux() && familySync() !== GLIBC;
const versionFromFilesystem = async () => {
if (cachedVersionFilesystem !== undefined) {
return cachedVersionFilesystem;
}
cachedVersionFilesystem = null;
try {
const lddContent = await readFile(LDD_PATH);
const versionMatch = lddContent.match(RE_GLIBC_VERSION);
if (versionMatch) {
cachedVersionFilesystem = versionMatch[1];
}
} catch (e) {}
return cachedVersionFilesystem;
};
const versionFromFilesystemSync = () => {
if (cachedVersionFilesystem !== undefined) {
return cachedVersionFilesystem;
}
cachedVersionFilesystem = null;
try {
const lddContent = readFileSync(LDD_PATH);
const versionMatch = lddContent.match(RE_GLIBC_VERSION);
if (versionMatch) {
cachedVersionFilesystem = versionMatch[1];
}
} catch (e) {}
return cachedVersionFilesystem;
};
const versionFromReport = () => {
const report = getReport();
if (report.header && report.header.glibcVersionRuntime) {
return report.header.glibcVersionRuntime;
}
return null;
};
const versionSuffix = (s) => s.trim().split(/\s+/)[1];
const versionFromCommand = (out) => {
const [getconf, ldd1, ldd2] = out.split(/[\r\n]+/);
if (getconf && getconf.includes(GLIBC)) {
return versionSuffix(getconf);
}
if (ldd1 && ldd2 && ldd1.includes(MUSL)) {
return versionSuffix(ldd2);
}
return null;
};
/**
* Resolves with the libc version when it can be determined, `null` otherwise.
* @returns {Promise<?string>}
*/
const version = async () => {
let version = null;
if (isLinux()) {
version = await versionFromFilesystem();
if (!version) {
version = versionFromReport();
}
if (!version) {
const out = await safeCommand();
version = versionFromCommand(out);
}
}
return version;
};
/**
* Returns the libc version when it can be determined, `null` otherwise.
* @returns {?string}
*/
const versionSync = () => {
let version = null;
if (isLinux()) {
version = versionFromFilesystemSync();
if (!version) {
version = versionFromReport();
}
if (!version) {
const out = safeCommandSync();
version = versionFromCommand(out);
}
}
return version;
};
module.exports = {
GLIBC,
MUSL,
family,
familySync,
isNonGlibcLinux,
isNonGlibcLinuxSync,
version,
versionSync
};