diff --git a/CHANGES b/CHANGES index 8cae86a49..18e4c5633 100644 --- a/CHANGES +++ b/CHANGES @@ -2805,3 +2805,5 @@ * backups: fix issue with s3 backend where files missing in remote was not detected correctly * provision: redirect to correct task (setup/restore/activation) +[8.0.2] +* tgz: fix unhandled promise error handler diff --git a/src/backupformat/tgz.js b/src/backupformat/tgz.js index 12b7ca446..116cd29de 100644 --- a/src/backupformat/tgz.js +++ b/src/backupformat/tgz.js @@ -34,7 +34,7 @@ function getBackupFilePath(backupConfig, remotePath) { return path.join(rootPath, remotePath + fileType); } -function addToPack(pack, header, options) { +function addEntryToPack(pack, header, options) { assert.strictEqual(typeof pack, 'object'); assert.strictEqual(typeof header, 'object'); assert.strictEqual(typeof options, 'object'); // { input } @@ -56,6 +56,52 @@ function addToPack(pack, header, options) { }); } +async function addPathToPack(pack, localPath, dataLayout) { + assert.strictEqual(typeof pack, 'object'); + assert(dataLayout instanceof DataLayout, 'dataLayout must be a DataLayout'); + assert.strictEqual(typeof localPath, 'string'); + + const queue = [ localPath ]; + while (queue.length) { + // if (pack.destroyed || outStream.destroyed) break; + const dir = queue.shift(); + debug(`tarPack: processing ${dir}`); + const [readdirError, entries] = await safe(fs.promises.readdir(dir, { withFileTypes: true })); + if (!entries) { + debug(`tarPack: skipping directory ${dir}: ${readdirError.message}`); + continue; + } + const subdirs = []; + for (const entry of entries) { + const abspath = path.join(dir, entry.name); + const headerName = dataLayout.toRemotePath(abspath); + if (entry.isFile()) { + const [openError, handle] = await safe(fs.promises.open(abspath, 'r')); + if (!handle) { debug(`tarPack: skipping file, could not open ${abspath}: ${openError.message}`); continue; } + const [statError, stat] = await safe(handle.stat()); + if (!stat) { debug(`tarPack: skipping file, could not stat ${abspath}: ${statError.message}`); continue; } + const header = { name: headerName, type: 'file', mode: stat.mode, size: stat.size, uid: process.getuid(), gid: process.getgid() }; + if (stat.size > 8589934590 || entry.name.length > 99) header.pax = { size: stat.size }; + const input = handle.createReadStream({ autoClose: true }); + await addEntryToPack(pack, header, { input }); + } else if (entry.isDirectory()) { + const header = { name: headerName, type: 'directory', uid: process.getuid(), gid: process.getgid() }; + subdirs.push(abspath); + await addEntryToPack(pack, header, { /* options */ }); + } else if (entry.isSymbolicLink()) { + const [readlinkError, target] = await safe(fs.promises.readlink(abspath)); + if (!target) { debug(`tarPack: skipping link, could not readlink ${abspath}: ${readlinkError.message}`); continue; } + const header = { name: headerName, type: 'symlink', linkname: target, uid: process.getuid(), gid: process.getgid() }; + await addEntryToPack(pack, header, { /* options */ }); + } else { + debug(`tarPack: ignoring unknown type ${entry.name} ${entry.type}`); + } + } + + queue.unshift(...subdirs); // add to front of queue and in order of readdir listing + } +} + async function tarPack(dataLayout, encryption, uploader, progressCallback) { assert(dataLayout instanceof DataLayout, 'dataLayout must be a DataLayout'); assert.strictEqual(typeof encryption, 'object'); @@ -81,48 +127,11 @@ async function tarPack(dataLayout, encryption, uploader, progressCallback) { } for (const localPath of dataLayout.localPaths()) { - const queue = [ localPath ]; - while (queue.length) { - // if (pack.destroyed || outStream.destroyed) break; - const dir = queue.shift(); - debug(`tarPack: processing ${dir}`); - const [readdirError, entries] = await safe(fs.promises.readdir(dir, { withFileTypes: true })); - if (!entries) { - debug(`tarPack: skipping directory ${dir}: ${readdirError.message}`); - continue; - } - const subdirs = []; - for (const entry of entries) { - const abspath = path.join(dir, entry.name); - const headerName = dataLayout.toRemotePath(abspath); - if (entry.isFile()) { - const [openError, handle] = await safe(fs.promises.open(abspath, 'r')); - if (!handle) { debug(`tarPack: skipping file, could not open ${abspath}: ${openError.message}`); continue; } - const [statError, stat] = await safe(handle.stat()); - if (!stat) { debug(`tarPack: skipping file, could not stat ${abspath}: ${statError.message}`); continue; } - const header = { name: headerName, type: 'file', mode: stat.mode, size: stat.size, uid: process.getuid(), gid: process.getgid() }; - if (stat.size > 8589934590 || entry.name.length > 99) header.pax = { size: stat.size }; - const input = handle.createReadStream({ autoClose: true }); - await addToPack(pack, header, { input }); - } else if (entry.isDirectory()) { - const header = { name: headerName, type: 'directory', uid: process.getuid(), gid: process.getgid() }; - await addToPack(pack, header); - subdirs.push(abspath); - } else if (entry.isSymbolicLink()) { - const [readlinkError, target] = await safe(fs.promises.readlink(abspath)); - if (!target) { debug(`tarPack: skipping link, could not readlink ${abspath}: ${readlinkError.message}`); continue; } - const header = { name: headerName, type: 'symlink', linkname: target, uid: process.getuid(), gid: process.getgid() }; - await addToPack(pack, header); - } else { - debug(`tarPack: ignoring unknown type ${entry.name} ${entry.type}`); - } - } - - queue.unshift(...subdirs); // add to front of queue and in order of readdir listing - } + const [error] = await safe(addPathToPack(pack, localPath, dataLayout), { debug }); + if (error) break; // the pipeline will error and we will retry the whole packing all over } - pack.finalize(); + pack.finalize(); // harmless to call if already in error state const [error] = await pipeline; // already wrapped in safe() if (error) throw new BoxError(BoxError.EXTERNAL_ERROR, `tarPack pipeline error: ${error.message}`);