From c9da451cf517e89267f0a9142fcf22efa1fc933e 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 1/2] Detect libc from ldd file and cache result of libc --- lib/current-env.js | 43 +++++++++++++++++------ test/check-platform.js | 79 +++++++++++++++++++++++++++--------------- 2 files changed, 84 insertions(+), 38 deletions(-) diff --git a/lib/current-env.js b/lib/current-env.js index 9babde1..72f6206 100644 --- a/lib/current-env.js +++ b/lib/current-env.js @@ -1,5 +1,6 @@ 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-') @@ -13,20 +14,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() From 2475952614fed0ff7e47024edd0b9ae6c8763b46 Mon Sep 17 00:00:00 2001 From: Tofandel Date: Thu, 21 Nov 2024 08:32:39 +0100 Subject: [PATCH 2/2] Cache coverage --- lib/current-env.js | 27 ++++++++++-------- test/check-platform.js | 64 +++++++++++++++++++++++++++++++----------- 2 files changed, 63 insertions(+), 28 deletions(-) diff --git a/lib/current-env.js b/lib/current-env.js index 72f6206..31f154a 100644 --- a/lib/current-env.js +++ b/lib/current-env.js @@ -30,6 +30,21 @@ function getFamilyFromFilesystem () { } } +function getFamilyFromReport () { + 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 +} + let family function libc (osName) { if (osName !== 'linux') { @@ -38,17 +53,7 @@ function libc (osName) { 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 - } + family = getFamilyFromReport() } } return family diff --git a/test/check-platform.js b/test/check-platform.js index 93df8f7..09c41e6 100644 --- a/test/check-platform.js +++ b/test/check-platform.js @@ -98,27 +98,44 @@ t.test('libc', (t) => { let PLATFORM = 'linux' let REPORT = {} let readFileSync + let noCache = true + + function withCache (cb) { + noCache = false + cb() + noCache = true + withoutLibcCache() + } 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, + const original = t.mock('..', { + '../lib/current-env': t.mock('../lib/current-env', { + 'node:fs': { + readFileSync: () => { + return readFileSync() }, - 'node:process': { - platform: PLATFORM, - report: { - getReport: () => REPORT, - }, + }, + 'node:process': Object.defineProperty({ + report: { + getReport: () => REPORT, }, + }, 'platform', { + enumerable: true, + get: () => PLATFORM, }), - }).checkPlatform - withoutLibcCache() - return original(...args) + }), + }).checkPlatform + noCacheChckPtfm = (...args) => { + try { + original(...args) + } finally { + if (noCache) { + withoutLibcCache() + } + } } } @@ -137,8 +154,16 @@ 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') + withCache(() => { + readFileSync = () => 'this ldd file contains GNU C Library' + t.doesNotThrow(() => noCacheChckPtfm({ libc: 'glibc' }), + 'allows glibc on glibc from ldd file') + + readFileSync = () => { + throw new Error('File not found') + } + t.doesNotThrow(() => noCacheChckPtfm({ libc: 'glibc' }), 'allows glibc from ldd file cache') + }) REPORT = {} t.throws(() => noCacheChckPtfm({ libc: 'glibc' }), { code: 'EBADPLATFORM' }, @@ -148,8 +173,13 @@ t.test('libc', (t) => { t.throws(() => noCacheChckPtfm({ libc: 'glibc' }), { code: 'EBADPLATFORM' }, 'fails when header is missing glibcVersionRuntime property') - REPORT = { header: { glibcVersionRuntime: '1' } } - t.doesNotThrow(() => noCacheChckPtfm({ libc: 'glibc' }), 'allows glibc on glibc') + withCache(() => { + REPORT = { header: { glibcVersionRuntime: '1' } } + t.doesNotThrow(() => noCacheChckPtfm({ libc: 'glibc' }), 'allows glibc on glibc') + + REPORT = {} + t.doesNotThrow(() => noCacheChckPtfm({ libc: 'glibc' }), 'allows glibc from report cache') + }) readFileSync = () => 'this ldd file is unsupported' t.throws(() => noCacheChckPtfm({ libc: 'glibc' }), { code: 'EBADPLATFORM' },