From f410d4092c6596d0ab0e4b90b303eabdd48b76d3 Mon Sep 17 00:00:00 2001 From: Adrien Foulon <6115458+Tofandel@users.noreply.github.com> Date: Mon, 28 Oct 2024 17:24:52 +0100 Subject: [PATCH] Detect libc from ldd file and cache result of libc --- lib/current-env.js | 44 +++++++++++++++++------ test/check-platform.js | 79 +++++++++++++++++++++++++++--------------- 2 files changed, 84 insertions(+), 39 deletions(-) diff --git a/lib/current-env.js b/lib/current-env.js index 9babde1..96b4de0 100644 --- a/lib/current-env.js +++ b/lib/current-env.js @@ -1,10 +1,10 @@ const process = require('node:process') const nodeOs = require('node:os') +const fs = require('node:fs') function isMusl (file) { return file.includes('libc.musl-') || file.includes('ld-musl-') } - function os () { return process.platform } @@ -13,20 +13,42 @@ function cpu () { return process.arch } +const LDD_PATH = '/usr/bin/ldd' +function getFamilyFromFilesystem () { + try { + const content = fs.readFileSync(LDD_PATH, 'utf-8') + if (content.includes('musl')) { + return 'musl' + } + if (content.includes('GNU C Library')) { + return 'glibc' + } + return null + } catch { + return undefined + } +} + +let family function libc (osName) { - // this is to make it faster on non linux machines if (osName !== 'linux') { return undefined } - let family - const originalExclude = process.report.excludeNetwork - process.report.excludeNetwork = true - const report = process.report.getReport() - process.report.excludeNetwork = originalExclude - if (report.header?.glibcVersionRuntime) { - family = 'glibc' - } else if (Array.isArray(report.sharedObjects) && report.sharedObjects.some(isMusl)) { - family = 'musl' + if (family === undefined) { + family = getFamilyFromFilesystem() + if (family === undefined) { + const originalExclude = process.report.excludeNetwork + process.report.excludeNetwork = true + const report = process.report.getReport() + process.report.excludeNetwork = originalExclude + if (report.header?.glibcVersionRuntime) { + family = 'glibc' + } else if (Array.isArray(report.sharedObjects) && report.sharedObjects.some(isMusl)) { + family = 'musl' + } else { + family = null + } + } } return family } diff --git a/test/check-platform.js b/test/check-platform.js index 3d262d6..93df8f7 100644 --- a/test/check-platform.js +++ b/test/check-platform.js @@ -94,30 +94,42 @@ t.test('wrong libc with overridden libc', async t => }), { code: 'EBADPLATFORM' })) t.test('libc', (t) => { - let PLATFORM = '' - - const _processPlatform = Object.getOwnPropertyDescriptor(process, 'platform') - Object.defineProperty(process, 'platform', { - enumerable: true, - configurable: true, - get: () => PLATFORM, - }) - + let noCacheChckPtfm + let PLATFORM = 'linux' let REPORT = {} - const _processReport = process.report.getReport - process.report.getReport = () => REPORT - - t.teardown(() => { - Object.defineProperty(process, 'platform', _processPlatform) - process.report.getReport = _processReport - }) + let readFileSync + + function withoutLibcCache () { + readFileSync = () => { + throw new Error('File not found') + } + noCacheChckPtfm = (...args) => { + const original = t.mock('..', { + '../lib/current-env': t.mock('../lib/current-env', { + 'node:fs': { + readFileSync, + }, + 'node:process': { + platform: PLATFORM, + report: { + getReport: () => REPORT, + }, + }, + }), + }).checkPlatform + withoutLibcCache() + return original(...args) + } + } + + withoutLibcCache() t.test('fails when not in linux', (t) => { PLATFORM = 'darwin' - t.throws(() => checkPlatform({ libc: 'glibc' }), { code: 'EBADPLATFORM' }, + t.throws(() => noCacheChckPtfm({ libc: 'glibc' }), { code: 'EBADPLATFORM' }, 'fails for glibc when not in linux') - t.throws(() => checkPlatform({ libc: 'musl' }), { code: 'EBADPLATFORM' }, + t.throws(() => noCacheChckPtfm({ libc: 'musl' }), { code: 'EBADPLATFORM' }, 'fails for musl when not in linux') t.end() }) @@ -125,17 +137,25 @@ t.test('libc', (t) => { t.test('glibc', (t) => { PLATFORM = 'linux' + readFileSync = () => 'this ldd file contains GNU C Library' + t.doesNotThrow(() => noCacheChckPtfm({ libc: 'glibc' }), 'allows glibc on glibc from ldd file') + REPORT = {} - t.throws(() => checkPlatform({ libc: 'glibc' }), { code: 'EBADPLATFORM' }, + t.throws(() => noCacheChckPtfm({ libc: 'glibc' }), { code: 'EBADPLATFORM' }, 'fails when report is missing header property') REPORT = { header: {} } - t.throws(() => checkPlatform({ libc: 'glibc' }), { code: 'EBADPLATFORM' }, + t.throws(() => noCacheChckPtfm({ libc: 'glibc' }), { code: 'EBADPLATFORM' }, 'fails when header is missing glibcVersionRuntime property') REPORT = { header: { glibcVersionRuntime: '1' } } - t.doesNotThrow(() => checkPlatform({ libc: 'glibc' }), 'allows glibc on glibc') - t.throws(() => checkPlatform({ libc: 'musl' }), { code: 'EBADPLATFORM' }, + t.doesNotThrow(() => noCacheChckPtfm({ libc: 'glibc' }), 'allows glibc on glibc') + + readFileSync = () => 'this ldd file is unsupported' + t.throws(() => noCacheChckPtfm({ libc: 'glibc' }), { code: 'EBADPLATFORM' }, + 'fails when ldd file exists but is not something known') + + t.throws(() => noCacheChckPtfm({ libc: 'musl' }), { code: 'EBADPLATFORM' }, 'does not allow musl on glibc') t.end() @@ -144,25 +164,28 @@ t.test('libc', (t) => { t.test('musl', (t) => { PLATFORM = 'linux' + readFileSync = () => 'this ldd file contains musl' + t.doesNotThrow(() => noCacheChckPtfm({ libc: 'musl' }), 'allows musl on musl from ldd file') + REPORT = {} - t.throws(() => checkPlatform({ libc: 'musl' }), { code: 'EBADPLATFORM' }, + t.throws(() => noCacheChckPtfm({ libc: 'musl' }), { code: 'EBADPLATFORM' }, 'fails when report is missing sharedObjects property') REPORT = { sharedObjects: {} } - t.throws(() => checkPlatform({ libc: 'musl' }), { code: 'EBADPLATFORM' }, + t.throws(() => noCacheChckPtfm({ libc: 'musl' }), { code: 'EBADPLATFORM' }, 'fails when sharedObjects property is not an array') REPORT = { sharedObjects: [] } - t.throws(() => checkPlatform({ libc: 'musl' }), { code: 'EBADPLATFORM' }, + t.throws(() => noCacheChckPtfm({ libc: 'musl' }), { code: 'EBADPLATFORM' }, 'fails when sharedObjects does not contain musl') REPORT = { sharedObjects: ['ld-musl-foo'] } - t.doesNotThrow(() => checkPlatform({ libc: 'musl' }), 'allows musl on musl as ld-musl-') + t.doesNotThrow(() => noCacheChckPtfm({ libc: 'musl' }), 'allows musl on musl as ld-musl-') REPORT = { sharedObjects: ['libc.musl-'] } - t.doesNotThrow(() => checkPlatform({ libc: 'musl' }), 'allows musl on musl as libc.musl-') + t.doesNotThrow(() => noCacheChckPtfm({ libc: 'musl' }), 'allows musl on musl as libc.musl-') - t.throws(() => checkPlatform({ libc: 'glibc' }), { code: 'EBADPLATFORM' }, + t.throws(() => noCacheChckPtfm({ libc: 'glibc' }), { code: 'EBADPLATFORM' }, 'does not allow glibc on musl') t.end()