|
|
|
|
|
|
|
|
|
|
|
'use strict'; |
|
|
|
|
|
const childProcess = require('child_process'); |
|
|
const { isLinux, getReport } = require('./process'); |
|
|
const { LDD_PATH, SELF_PATH, readFile, readFileSync } = require('./filesystem'); |
|
|
const { interpreterPath } = require('./elf'); |
|
|
|
|
|
let cachedFamilyInterpreter; |
|
|
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; |
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const GLIBC = 'glibc'; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const RE_GLIBC_VERSION = /LIBC[a-z0-9 \-).]*?(\d+\.\d+)/i; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const MUSL = 'musl'; |
|
|
|
|
|
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 familyFromInterpreterPath = (path) => { |
|
|
if (path) { |
|
|
if (path.includes('/ld-musl-')) { |
|
|
return MUSL; |
|
|
} else if (path.includes('/ld-linux-')) { |
|
|
return GLIBC; |
|
|
} |
|
|
} |
|
|
return null; |
|
|
}; |
|
|
|
|
|
const getFamilyFromLddContent = (content) => { |
|
|
content = content.toString(); |
|
|
if (content.includes('musl')) { |
|
|
return MUSL; |
|
|
} |
|
|
if (content.includes('GNU C Library')) { |
|
|
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; |
|
|
}; |
|
|
|
|
|
const familyFromInterpreter = async () => { |
|
|
if (cachedFamilyInterpreter !== undefined) { |
|
|
return cachedFamilyInterpreter; |
|
|
} |
|
|
cachedFamilyInterpreter = null; |
|
|
try { |
|
|
const selfContent = await readFile(SELF_PATH); |
|
|
const path = interpreterPath(selfContent); |
|
|
cachedFamilyInterpreter = familyFromInterpreterPath(path); |
|
|
} catch (e) {} |
|
|
return cachedFamilyInterpreter; |
|
|
}; |
|
|
|
|
|
const familyFromInterpreterSync = () => { |
|
|
if (cachedFamilyInterpreter !== undefined) { |
|
|
return cachedFamilyInterpreter; |
|
|
} |
|
|
cachedFamilyInterpreter = null; |
|
|
try { |
|
|
const selfContent = readFileSync(SELF_PATH); |
|
|
const path = interpreterPath(selfContent); |
|
|
cachedFamilyInterpreter = familyFromInterpreterPath(path); |
|
|
} catch (e) {} |
|
|
return cachedFamilyInterpreter; |
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const family = async () => { |
|
|
let family = null; |
|
|
if (isLinux()) { |
|
|
family = await familyFromInterpreter(); |
|
|
if (!family) { |
|
|
family = await familyFromFilesystem(); |
|
|
if (!family) { |
|
|
family = familyFromReport(); |
|
|
} |
|
|
if (!family) { |
|
|
const out = await safeCommand(); |
|
|
family = familyFromCommand(out); |
|
|
} |
|
|
} |
|
|
} |
|
|
return family; |
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const familySync = () => { |
|
|
let family = null; |
|
|
if (isLinux()) { |
|
|
family = familyFromInterpreterSync(); |
|
|
if (!family) { |
|
|
family = familyFromFilesystemSync(); |
|
|
if (!family) { |
|
|
family = familyFromReport(); |
|
|
} |
|
|
if (!family) { |
|
|
const out = safeCommandSync(); |
|
|
family = familyFromCommand(out); |
|
|
} |
|
|
} |
|
|
} |
|
|
return family; |
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const isNonGlibcLinux = async () => isLinux() && await family() !== GLIBC; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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; |
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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; |
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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 |
|
|
}; |
|
|
|