From a0ed00d8f318090885bfd2c56fb5855b6c200f36 Mon Sep 17 00:00:00 2001 From: Felipe Lalanne Date: Tue, 25 Jan 2022 17:05:04 -0300 Subject: [PATCH 1/2] Perform safeRename on writeFileAtomic This forces a sync of the file as soon as the rename happens to prevent corruption. --- src/lib/fs-utils.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/fs-utils.ts b/src/lib/fs-utils.ts index 99c10cea..51f55581 100644 --- a/src/lib/fs-utils.ts +++ b/src/lib/fs-utils.ts @@ -27,7 +27,7 @@ export async function writeFileAtomic( data: string | Buffer, ): Promise { await writeAndSyncFile(`${pathName}.new`, data); - await fs.rename(`${pathName}.new`, pathName); + await safeRename(`${pathName}.new`, pathName); } export async function safeRename(src: string, dest: string): Promise { From d071cd1507b8424628afa96ea1b1ed6b19225651 Mon Sep 17 00:00:00 2001 From: Felipe Lalanne Date: Tue, 25 Jan 2022 18:34:34 -0300 Subject: [PATCH 2/2] Use `writeAndSync` when writing to config.json `/mnt/boot` is a vfat partition which does not support atomic file rename. The best course of action is to write and sync as fast as possible to prevent corruption (although it still may happen) Change-type: patch --- src/config/configJson.ts | 27 ++++++++++----------------- 1 file changed, 10 insertions(+), 17 deletions(-) diff --git a/src/config/configJson.ts b/src/config/configJson.ts index 6829805d..a6b747e6 100644 --- a/src/config/configJson.ts +++ b/src/config/configJson.ts @@ -4,7 +4,7 @@ import { promises as fs } from 'fs'; import * as path from 'path'; import * as constants from '../lib/constants'; -import { writeAndSyncFile, writeFileAtomic } from '../lib/fs-utils'; +import { writeAndSyncFile } from '../lib/fs-utils'; import * as osRelease from '../lib/os-release'; import log from '../lib/supervisor-console'; @@ -86,21 +86,14 @@ export default class ConfigJsonConfigBackend { }); } - private write(): Promise { - let atomicWritePossible = true; - return this.pathOnHost() - .catch((err) => { - log.error('There was an error detecting the config.json path', err); - atomicWritePossible = false; - return constants.configJsonNonAtomicPath; - }) - .then((configPath) => { - if (atomicWritePossible) { - return writeFileAtomic(configPath, JSON.stringify(this.cache)); - } else { - return writeAndSyncFile(configPath, JSON.stringify(this.cache)); - } - }); + private async write(): Promise { + // We use writeAndSyncFile since /mnt/boot partition is a vfat + // filesystem which dows not provide atomic file renames. The best + // course of action on that case is to write and sync as soon as possible + return writeAndSyncFile( + await this.pathOnHost(), + JSON.stringify(this.cache), + ); } private async read(): Promise { @@ -133,7 +126,7 @@ export default class ConfigJsonConfigBackend { // then we can't do atomic changes (only access to config.json we have is in /boot, // which is assumed to be a file bind mount where rename is impossible). throw new Error( - 'Could not determine config.json path on host, atomic write will not be possible', + `OS version '${osVersion}' does not match any known balenaOS version.`, ); } }