diff --git a/lib/cli/device/index.js b/lib/cli/device/index.js index e8de5323..9678dc87 100644 --- a/lib/cli/device/index.js +++ b/lib/cli/device/index.js @@ -39,6 +39,12 @@ module.exports.builder = function(yargs) { , type: 'boolean' , default: false }) + .option('cleanup-folder', { + describe: 'Whether to remove pre-defined folder during cleanup. ' + + 'eg. "--cleanup-folder /data/local /tmp"' + , type: 'array' + , default: [] + }) .option('connect-port', { describe: 'Port allocated to adb connections.' , type: 'number' @@ -201,6 +207,7 @@ module.exports.handler = function(argv) { , cleanup: argv.cleanup , cleanupDisableBluetooth: argv.cleanupDisableBluetooth , cleanupBluetoothBonds: argv.cleanupBluetoothBonds + , cleanupFolder: argv.cleanupFolder , screenReset: argv.screenReset }) } diff --git a/lib/cli/local/index.js b/lib/cli/local/index.js index 43e3659e..d31a649d 100644 --- a/lib/cli/local/index.js +++ b/lib/cli/local/index.js @@ -119,6 +119,12 @@ module.exports.builder = function(yargs) { , type: 'boolean' , default: false }) + .option('cleanup-folder', { + describe: 'Whether to remove pre-defined folder during cleanup. ' + + 'eg. "--cleanup-folder /data/local /tmp"' + , type: 'array' + , default: [] + }) .option('group-timeout', { alias: 't' , describe: 'Timeout in seconds for automatic release of inactive devices.' @@ -318,6 +324,7 @@ module.exports.handler = function(argv) { .concat(!argv.cleanup ? ['--no-cleanup'] : []) .concat(argv.cleanupDisableBluetooth ? ['--cleanup-disable-bluetooth'] : []) .concat(argv.cleanupBluetoothBonds ? ['--cleanup-bluetooth-bonds'] : []) + .concat(argv.cleanupFolder ? ['--cleanup-folder'].concat(argv.cleanupFolder) : []) .concat(!argv.screenReset ? ['--no-screen-reset'] : []) .concat(argv.serial)) diff --git a/lib/cli/provider/index.js b/lib/cli/provider/index.js index bc329634..de1c49fb 100644 --- a/lib/cli/provider/index.js +++ b/lib/cli/provider/index.js @@ -53,6 +53,12 @@ module.exports.builder = function(yargs) { , type: 'boolean' , default: false }) + .option('cleanup-folder', { + describe: 'Whether to remove pre-defined folder during cleanup. ' + + 'eg. "--cleanup-folder /data/local /tmp"' + , type: 'array' + , default: [] + }) .option('connect-push', { alias: 'p' , describe: 'Device-side ZeroMQ PULL endpoint to connect to.' @@ -235,6 +241,7 @@ module.exports.handler = function(argv) { .concat(!argv.cleanup ? ['--no-cleanup'] : []) .concat(argv.cleanupDisableBluetooth ? ['--cleanup-disable-bluetooth'] : []) .concat(argv.cleanupBluetoothBonds ? ['--cleanup-bluetooth-bonds'] : []) + .concat(argv.cleanupFolder ? ['--cleanup-folder'].concat(argv.cleanupFolder) : []) .concat(!argv.screenReset ? ['--no-screen-reset'] : []) return fork(cli, args) diff --git a/lib/units/device/plugins/cleanup.js b/lib/units/device/plugins/cleanup.js index b85d8efd..24eba33e 100644 --- a/lib/units/device/plugins/cleanup.js +++ b/lib/units/device/plugins/cleanup.js @@ -3,6 +3,8 @@ var Promise = require('bluebird') var _ = require('lodash') var logger = require('../../../util/logger') +const util = require('util') +const adbkit = require('@devicefarmer/adbkit') module.exports = syrup.serial() .dependency(require('../support/adb')) @@ -12,11 +14,32 @@ module.exports = syrup.serial() .define(function(options, adb, stfservice, group, service) { var log = logger.createLogger('device:plugins:cleanup') var plugin = Object.create(null) - if (!options.cleanup) { return plugin } + // ignore system folders. This is to prevent accidental deletion of system files. + // It's admin's responsibility to set correct cleanup folders. + var systemFolders = [ + '/system' + , '/boot' + , '/proc' + , '/vendor' + , '/dev' + , '/sys' + ] + // if folder starts with system folder, throw an error and stop provider. + // this is to prevent accidental deletion of system files. + options.cleanupFolder.forEach(function(folder) { + if (systemFolders.some(function(systemFolder) { + return folder.startsWith(systemFolder) + })) { + log.warn('Warning, Tried to clean system folder. Ignoring: %s', folder) + throw new Error(util.format('Cleanup %s is not allowed!', folder)) + } + }) + log.debug('Cleanup folders: %j', options.cleanupFolder) + function listPackages() { return adb.getPackages(options.serial) } @@ -29,6 +52,60 @@ module.exports = syrup.serial() return true }) } + function removeFile(filename) { + return adb + // get file size + .shell(options.serial, util.format('du -h "%s"', filename)) + .then(adbkit.util.readAll) + .then(function(output) { + // output is in format: size filename. extract size; + var size = output.toString().split('\t')[0] + log.info('Removing %s (%s)', filename, size) + return adb + .shell(options.serial, util.format('rm -rf "%s"', filename)) + .then(adbkit.util.readAll) + }) + .catch(function(err) { + log.warn(util.format('Unable to clean %s folder', filename), err) + }) + } + function listFiles(folder, ignoreFiles = []) { + return adb.readdir(options.serial, folder) + .then(function(files) { + // drop . and .. from list + return files.filter(function(file) { + return file.name !== '.' && file.name !== '..' + }) + }) + .then(function(files) { + return files.map(function(file) { + return util.format('%s/%s', folder, file.name) + }) + }) + .then(function(files) { + return files.filter(function(file) { + return !ignoreFiles.includes(file) + }) + }) + } + function cleanFolder(folder) { + log.info('Cleanup %s folder', folder) + // ignore STF service files + var ignoreServiceFiles = [ + '/data/local/tmp/minicap.apk' + , '/data/local/tmp/minicap' + , '/data/local/tmp/minicap.so' + , '/data/local/tmp/minitouch' + , '/data/local/tmp/minirev' + ] + return listFiles(folder, ignoreServiceFiles) + .then(function(files) { + return Promise.map(files, removeFile) + }) + .catch(function(err) { + log.warn('Unable to clean %s folder', folder, err) + }) + } return listPackages() .then(function(initialPackages) { @@ -61,11 +138,22 @@ module.exports = syrup.serial() return service.cleanBluetoothBonds() } + plugin.cleanFolders = function() { + return Promise.each(options.cleanupFolder, cleanFolder) + } + group.on('leave', function() { - Promise.all([ - plugin.removePackages() - , plugin.cleanBluetoothBonds() - , plugin.disableBluetooth()]) + Promise.each([ + plugin.removePackages + , plugin.cleanBluetoothBonds + , plugin.disableBluetooth + , plugin.cleanFolders + , function() { + log.debug('Cleanup done') + } + ], function(fn) { + return fn() + }) }) }) .return(plugin)