diff --git a/prelude/bootstrap.js b/prelude/bootstrap.js index 094ff60e5..6704873e4 100644 --- a/prelude/bootstrap.js +++ b/prelude/bootstrap.js @@ -158,19 +158,28 @@ function createMountpoint(interior, exterior) { mountpoints.push({ interior, exterior }); } -function copyFileSync(source, target) { - let targetFile = target; - - // If target is a directory, a new file with the same name will be created - if (fs.existsSync(target)) { - if (fs.lstatSync(target).isDirectory()) { - targetFile = path.join(target, path.basename(source)); - } +const DEFAULT_COPY_CHUNK_SIZE = 10 * 1024 * 1024; // 10 MB +function copyInChunks( + source, + target, + chunkSize = DEFAULT_COPY_CHUNK_SIZE, + fs_ = fs +) { + const sourceFile = fs_.openSync(source, 'r'); + const targetFile = fs_.openSync(target, 'w'); + + let bytesRead = 1; + while (bytesRead > 0) { + const buffer = Buffer.alloc(chunkSize); + bytesRead = fs_.readSync(sourceFile, buffer, 0, chunkSize); + fs_.writeSync(targetFile, buffer, 0, bytesRead); } - fs.writeFileSync(targetFile, fs.readFileSync(source)); + fs_.closeSync(sourceFile); + fs_.closeSync(targetFile); } +// TODO: replace this with fs.cpSync when we drop Node < 16 function copyFolderRecursiveSync(source, target) { let files = []; @@ -188,7 +197,10 @@ function copyFolderRecursiveSync(source, target) { if (fs.lstatSync(curSource).isDirectory()) { copyFolderRecursiveSync(curSource, targetFolder); } else { - copyFileSync(curSource, targetFolder); + fs.copyFileSync( + curSource, + path.join(targetFolder, path.basename(curSource)) + ); } }); } @@ -572,6 +584,8 @@ function payloadFileSync(pointer) { mkdirSync: fs.mkdirSync, mkdir: fs.mkdir, createReadStream: fs.createReadStream, + copyFileSync: fs.copyFileSync, + copyFile: fs.copyFile, }; ancestor.realpathSync.native = fs.realpathSync; @@ -1066,6 +1080,67 @@ function payloadFileSync(pointer) { }); }; + fs.copyFile = function copyFile(src, dest, flags, callback) { + if (!insideSnapshot(path.resolve(src))) { + ancestor.copyFile(src, dest, flags, callback); + return; + } + if (typeof flags === 'function') { + callback = flags; + flags = 0; + } else if (typeof callback !== 'function') { + throw new TypeError('Callback must be a function'); + } + + function _streamCopy() { + fs.createReadStream(src) + .on('error', callback) + .pipe(fs.createWriteStream(dest)) + .on('error', callback) + .on('finish', callback); + } + + if (flags & fs.constants.COPYFILE_EXCL) { + fs.stat(dest, (statError) => { + if (!statError) { + callback( + Object.assign(new Error('File already exists'), { + code: 'EEXIST', + }) + ); + return; + } + if (statError.code !== 'ENOENT') { + callback(statError); + return; + } + _streamCopy(); + }); + } else { + _streamCopy(); + } + }; + + fs.copyFileSync = function copyFileSync(src, dest, flags) { + if (!insideSnapshot(path.resolve(src))) { + ancestor.copyFileSync(src, dest, flags); + return; + } + + if (flags & fs.constants.COPYFILE_EXCL) { + try { + fs.statSync(dest); + } catch (statError) { + if (statError.code !== 'ENOENT') throw statError; + copyInChunks(src, dest, DEFAULT_COPY_CHUNK_SIZE, fs); + return; + } + + throw Object.assign(new Error('File already exists'), { code: 'EEXIST' }); + } + copyInChunks(src, dest, DEFAULT_COPY_CHUNK_SIZE, fs); + }; + // /////////////////////////////////////////////////////////////// // writeFile ///////////////////////////////////////////////////// // /////////////////////////////////////////////////////////////// @@ -1556,6 +1631,7 @@ function payloadFileSync(pointer) { lstat: fs.promises.lstat, fstat: fs.promises.fstat, access: fs.promises.access, + copyFile: fs.promises.copyFile, }; fs.promises.open = async function open(path_) { @@ -1602,6 +1678,7 @@ function payloadFileSync(pointer) { // this one use promisify on purpose fs.promises.readdir = util.promisify(fs.readdir); + fs.promises.copyFile = util.promisify(fs.copyFile); /* fs.promises.read = util.promisify(fs.read); @@ -2093,6 +2170,8 @@ function payloadFileSync(pointer) { // Example: /tmp/pkg/ const tmpFolder = path.join(tmpdir(), 'pkg', hash); + createDirRecursively(tmpFolder); + // Example: moduleFolder = /snapshot/appname/node_modules/sharp/build/Release const parts = moduleFolder.split(path.sep); const mIndex = parts.indexOf('node_modules') + 1; @@ -2110,14 +2189,12 @@ function payloadFileSync(pointer) { // here we copy all files from the snapshot module folder to temporary folder // we keep the module folder structure to prevent issues with modules that are statically // linked using relative paths (Fix #1075) - createDirRecursively(tmpFolder); copyFolderRecursiveSync(modulePkgFolder, tmpFolder); // Example: /tmp/pkg//sharp/build/Release/sharp.node newPath = path.join(tmpFolder, modulePackagePath, moduleBaseName); } else { - createDirRecursively(tmpFolder); - copyFileSync(modulePath, tmpFolder); + fs.copyFileSync(modulePath, path.join(tmpFolder, moduleBaseName)); // load the copied file in the temporary folder newPath = path.join(tmpFolder, moduleBaseName); diff --git a/test/test-420-copy-from-snapshot/.gitignore b/test/test-420-copy-from-snapshot/.gitignore new file mode 100644 index 000000000..b7482ccf8 --- /dev/null +++ b/test/test-420-copy-from-snapshot/.gitignore @@ -0,0 +1,3 @@ +output +sync.json +async.json diff --git a/test/test-420-copy-from-snapshot/copy.js b/test/test-420-copy-from-snapshot/copy.js new file mode 100644 index 000000000..0a4f23e6a --- /dev/null +++ b/test/test-420-copy-from-snapshot/copy.js @@ -0,0 +1,19 @@ +#!/usr/bin/env node + +'use strict'; + +const fs = require('fs'); +const path = require('path'); + +const testPath = path.resolve(__dirname, 'input/test.json'); +console.log(fs.readFileSync(testPath, 'utf8')); + +const syncPath = path.resolve(process.cwd(), 'output/sync.json'); +fs.copyFileSync(testPath, syncPath); +console.log(fs.readFileSync(syncPath, 'utf8')); + +const asyncPath = path.resolve(process.cwd(), 'output/async.json'); +fs.copyFile(testPath, asyncPath, (err) => { + if (err) throw err; + console.log(fs.readFileSync(asyncPath, 'utf8')); +}); diff --git a/test/test-420-copy-from-snapshot/input/test.json b/test/test-420-copy-from-snapshot/input/test.json new file mode 100644 index 000000000..7a9e86441 --- /dev/null +++ b/test/test-420-copy-from-snapshot/input/test.json @@ -0,0 +1,3 @@ +{ + "key": "value" +} diff --git a/test/test-420-copy-from-snapshot/main.js b/test/test-420-copy-from-snapshot/main.js new file mode 100644 index 000000000..0a04ea41f --- /dev/null +++ b/test/test-420-copy-from-snapshot/main.js @@ -0,0 +1,30 @@ +#!/usr/bin/env node + +'use strict'; + +const path = require('path'); +const assert = require('assert'); +const utils = require('../utils.js'); + +assert(!module.parent); +assert(__dirname === process.cwd()); + +const target = process.argv[2] || 'host'; +const input = './copy.js'; +const output = './output/test-output.exe'; + +utils.mkdirp.sync(path.dirname(output)); +utils.pkg.sync(['--target', target, '--output', output, '.']); + +let left, right; +left = utils.spawn.sync('node', [path.basename(input)], { + cwd: path.dirname(input), +}); + +right = utils.spawn.sync(output, [], { + cwd: path.dirname(input), +}); + +assert.strictEqual(left, right); +utils.vacuum.sync(path.dirname(output)); +utils.vacuum.sync(path.join(__dirname, '/*sync.json')); diff --git a/test/test-420-copy-from-snapshot/package.json b/test/test-420-copy-from-snapshot/package.json new file mode 100644 index 000000000..8ce379bda --- /dev/null +++ b/test/test-420-copy-from-snapshot/package.json @@ -0,0 +1,8 @@ +{ + "bin": "copy.js", + "pkg": { + "assets": [ + "input/**/*" + ] + } +}