From f0c4e8cd113e761958ab387f4b0237f4d8797335 Mon Sep 17 00:00:00 2001 From: renkei <30912473+renkei@users.noreply.github.com> Date: Tue, 10 May 2022 00:19:28 +0200 Subject: [PATCH] fix(bootstrap): prevent overriding existing node addon file (#1611) Fixes: #1589 Co-authored-by: Keimling, Rene --- prelude/bootstrap.js | 63 +++++++++++++++++++++++++++++++++++++------- 1 file changed, 53 insertions(+), 10 deletions(-) diff --git a/prelude/bootstrap.js b/prelude/bootstrap.js index 9c2ad55ac..8627ea5eb 100644 --- a/prelude/bootstrap.js +++ b/prelude/bootstrap.js @@ -181,28 +181,71 @@ function copyInChunks( // TODO: replace this with fs.cpSync when we drop Node < 16 function copyFolderRecursiveSync(source, target) { - let files = []; - - // Check if folder needs to be created or integrated + // Build target folder const targetFolder = path.join(target, path.basename(source)); + + // Check if target folder needs to be created or integrated if (!fs.existsSync(targetFolder)) { fs.mkdirSync(targetFolder); } // Copy if (fs.lstatSync(source).isDirectory()) { - files = fs.readdirSync(source); - files.forEach((file) => { + const files = fs.readdirSync(source); + + for (const file of files) { + // Build source name const curSource = path.join(source, file); + + // Call this function recursively as long as source is a directory if (fs.lstatSync(curSource).isDirectory()) { copyFolderRecursiveSync(curSource, targetFolder); } else { - fs.copyFileSync( - curSource, - path.join(targetFolder, path.basename(curSource)) - ); + // Current source is a file, it must be available on the real filesystem + // instead of the virtual snapshot file system to load it by process.dlopen. + // + // Before we try to copy we do some checks. + // See https://github.com/vercel/pkg/issues/1589 for more details. + + // Build target file name + const curTarget = path.join(targetFolder, path.basename(curSource)); + + if (fs.existsSync(curTarget)) { + // Target file already exists, read source and target file... + const curSourceContent = fs.readFileSync(curSource, { + encoding: 'binary', + }); + const curTargetContent = fs.readFileSync(curTarget, { + encoding: 'binary', + }); + + // ...and calculate checksum from source and target file + const curSourceHash = createHash('sha256') + .update(curSourceContent) + .digest('hex'); + const curTargetHash = createHash('sha256') + .update(curTargetContent) + .digest('hex'); + + // If checksums are equal then there is nothing to do here + // ==> target already exists and is up-to-date + if (curSourceHash === curTargetHash) { + continue; + } + } + + // Target must be copied because it either does not exist or is outdated. + // Due to the possibility that mutliple instances of this app start simultaneously, + // the copy action might fail. Only one starting instance gets write access. + // + // We don't catch any error here because it does not make sense to go ahead and to + // try to load the file while another instance has not yet finished the copy action. + // If the app start fails then the user should try to start the app later again. + // Unfortunately, we cannot implement delayed retries ourselves because process.dlopen + // is a synchronous function, promises are not supported. + fs.copyFileSync(curSource, curTarget); } - }); + } } }