diff --git a/lib/cli/diskpart.js b/lib/cli/diskpart.js index 4104f9b7..731c4015 100644 --- a/lib/cli/diskpart.js +++ b/lib/cli/diskpart.js @@ -16,14 +16,71 @@ 'use strict' +const os = require('os') +const fs = require('fs') +const path = require('path') +const crypto = require('crypto') +const childProcess = require('child_process') const debug = require('debug')('etcher:cli:diskpart') const Promise = require('bluebird') const retry = require('bluebird-retry') -const driveClean = require('win-drive-clean') +const TMP_RANDOM_BYTES = 6 const DISKPART_DELAY = 2000 const DISKPART_RETRIES = 5 +/** + * @summary Generate a tmp filename with full path of OS' tmp dir + * @function + * @private + * + * @param {String} extension - temporary file extension + * @returns {String} filename + * + * @example + * const filename = tmpFilename('.sh'); + */ +const tmpFilename = (extension) => { + const random = crypto.randomBytes(TMP_RANDOM_BYTES).toString('hex') + const filename = `etcher-diskpart-${random}${extension}` + return path.join(os.tmpdir(), filename) +} + +/** + * @summary Run a diskpart script + * @param {Array} commands - list of commands to run + * @param {Function} callback - callback(error) + * @example + * runDiskpart(['rescan'], (error) => { + * ... + * }) + */ +const runDiskpart = (commands, callback) => { + if (os.platform() !== 'win32') { + callback() + return + } + + const filename = tmpFilename('') + const script = commands.join('\r\n') + + fs.writeFile(filename, script, { + mode: 0o755 + }, (writeError) => { + debug('write %s:', filename, writeError || 'OK') + + childProcess.exec(`diskpart /s ${filename}`, (execError, stdout, stderr) => { + debug('stdout:', stdout) + debug('stderr:', stderr) + + fs.unlink(filename, (unlinkError) => { + debug('unlink %s:', filename, unlinkError || 'OK') + callback(execError) + }) + }) + }) +} + module.exports = { /** @@ -36,21 +93,32 @@ module.exports = { * @returns {Promise} */ clean (device) { + if (os.platform() !== 'win32') { + return Promise.resolve() + } + debug('clean', device) - // NOTE: This is a noop on Linux & Mac OS at this time and - // will always succeed without doing anything - return retry(() => { - return new Promise((resolve, reject) => { - driveClean(device, (error) => { - return error ? reject(error) : resolve() - }) - }).delay(DISKPART_DELAY) - }, { - /* eslint-disable camelcase */ - max_tries: DISKPART_RETRIES - /* eslint-enable camelcase */ - }) + const pattern = /PHYSICALDRIVE(\d+)/i + + if (pattern.test(device)) { + const deviceId = device.match(pattern).pop() + return retry(() => { + return new Promise((resolve, reject) => { + runDiskpart([ `select disk ${deviceId}`, 'clean', 'rescan' ], (error) => { + return error ? reject(error) : resolve() + }) + }).delay(DISKPART_DELAY) + }, { + /* eslint-disable camelcase */ + max_tries: DISKPART_RETRIES + /* eslint-enable camelcase */ + }).catch((error) => { + throw new Error(`Couldn't clean the drive, ${error.failure.message} (code ${error.failure.code})`) + }) + } + + return Promise.reject(new Error(`Invalid device: "${device}"`)) } } diff --git a/npm-shrinkwrap.json b/npm-shrinkwrap.json index 085551d6..2079bd85 100644 --- a/npm-shrinkwrap.json +++ b/npm-shrinkwrap.json @@ -9013,40 +9013,6 @@ } } }, - "win-drive-clean": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/win-drive-clean/-/win-drive-clean-1.0.1.tgz", - "dependencies": { - "bindings": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.3.0.tgz" - }, - "nan": { - "version": "2.8.0", - "resolved": "https://registry.npmjs.org/nan/-/nan-2.8.0.tgz" - }, - "node-abi": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-2.2.0.tgz" - }, - "prebuild-install": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-2.5.1.tgz" - }, - "pump": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/pump/-/pump-2.0.1.tgz" - }, - "semver": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.5.0.tgz" - }, - "simple-get": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/simple-get/-/simple-get-2.7.0.tgz" - } - } - }, "window-size": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/window-size/-/window-size-0.1.0.tgz", diff --git a/package.json b/package.json index 7a9a9f87..f2007f40 100644 --- a/package.json +++ b/package.json @@ -87,7 +87,6 @@ "unbzip2-stream": "github:resin-io-modules/unbzip2-stream#core-streams", "usb": "github:tessel/node-usb#1.3.0", "uuid": "3.0.1", - "win-drive-clean": "1.0.1", "winusb-driver-generator": "1.1.7", "xml2js": "0.4.17", "yargs": "11.0.0",