diff --git a/src/config/backends/config-txt.ts b/src/config/backends/config-txt.ts index 4eb46f9a..d4f2c17f 100644 --- a/src/config/backends/config-txt.ts +++ b/src/config/backends/config-txt.ts @@ -219,7 +219,11 @@ export class ConfigTxt extends ConfigBackend { // Split dtoverlays from their params to avoid running into char limits // and write at the end to prevent overriding the base overlay if (opts.dtoverlay != null) { - for (const entry of opts.dtoverlay) { + for (let entry of opts.dtoverlay) { + entry = entry.trim(); + if (entry.length === 0) { + continue; + } const [overlay, ...params] = entry.split(','); confStatements.push(`dtoverlay=${overlay}`); confStatements.push(...params.map((p) => `dtparam=${p}`)); @@ -245,9 +249,16 @@ export class ConfigTxt extends ConfigBackend { public processConfigVarValue(key: string, value: string): string | string[] { if (isArrayConfig(key)) { if (!value.startsWith('"')) { + if (key === 'dtoverlay' && value.trim().length === 0) { + return []; + } return [value]; } else { - return JSON.parse(`[${value}]`); + const res: string[] = JSON.parse(`[${value}]`); + if (key === 'dtoverlay') { + return res.filter((s) => s.trim().length > 0); + } + return res; } } return value; diff --git a/src/config/utils.ts b/src/config/utils.ts index 04ccd1d6..04bdf0d2 100644 --- a/src/config/utils.ts +++ b/src/config/utils.ts @@ -33,6 +33,7 @@ export function envToBootConfig( .mapValues((val, key) => configBackend.processConfigVarValue(key, val || ''), ) + .pickBy((val) => !Array.isArray(val) || val.length > 0) .value(); } diff --git a/test/integration/config/config-txt.spec.ts b/test/integration/config/config-txt.spec.ts index 8ac6814e..667f0003 100644 --- a/test/integration/config/config-txt.spec.ts +++ b/test/integration/config/config-txt.spec.ts @@ -306,4 +306,55 @@ describe('config/config-txt', () => { await tfs.restore(); }); + + it('ignores empty dtoverlay on the target state', async () => { + const tfs = await testfs({ + [hostUtils.pathOnBoot('config.txt')]: stripIndent` + enable_uart=1 + dtparam=i2c_arm=on + dtparam=spi=on + disable_splash=1 + dtparam=audio=on + gpu_mem=16 + `, + }).enable(); + + const configTxt = new ConfigTxt(); + + await configTxt.setBootConfig({ + dtparam: ['i2c=on', 'audio=on'], + dtoverlay: [''], + enable_uart: '1', + avoid_warnings: '1', + gpu_mem: '256', + initramfs: 'initramf.gz 0x00800000', + 'hdmi_force_hotplug:1': '1', + }); + + await expect( + fs.readFile(hostUtils.pathOnBoot('config.txt'), 'utf8'), + ).to.eventually.equal( + stripIndent` + dtparam=i2c=on + dtparam=audio=on + enable_uart=1 + avoid_warnings=1 + gpu_mem=256 + initramfs initramf.gz 0x00800000 + hdmi_force_hotplug:1=1 + ` + '\n', + ); + + // Will try to parse /test/data/mnt/boot/config.txt + await expect(configTxt.getBootConfig()).to.eventually.deep.equal({ + dtparam: ['i2c=on', 'audio=on'], + enable_uart: '1', + avoid_warnings: '1', + gpu_mem: '256', + initramfs: 'initramf.gz 0x00800000', + 'hdmi_force_hotplug:1': '1', + }); + + await tfs.restore(); + }); }); diff --git a/test/integration/device-config.spec.ts b/test/integration/device-config.spec.ts index c2e437b0..1de55b6c 100644 --- a/test/integration/device-config.spec.ts +++ b/test/integration/device-config.spec.ts @@ -228,6 +228,30 @@ describe('device-config', () => { expect(logSpy).to.not.be.called; }); + it('ignores empty dtoverlay when comparing current and target state', async () => { + const current = { + HOST_CONFIG_initramfs: 'initramf.gz 0x00800000', + HOST_CONFIG_dtparam: '"i2c=on","audio=on"', + HOST_CONFIG_foobar: 'baz', + }; + const target = { + HOST_CONFIG_initramfs: 'initramf.gz 0x00800000', + HOST_CONFIG_dtparam: '"i2c=on","audio=on"', + HOST_CONFIG_dtoverlay: '', + HOST_CONFIG_foobar: 'baz', + }; + + expect( + // @ts-expect-error accessing private value + deviceConfig.bootConfigChangeRequired( + configTxtBackend, + current, + target, + ), + ).to.equal(false); + expect(logSpy).to.not.be.called; + }); + it('writes the target config.txt', async () => { const current = { HOST_CONFIG_initramfs: 'initramf.gz 0x00800000',