mirror of
https://github.com/balena-io/etcher.git
synced 2025-07-23 11:16:39 +00:00
feat(GUI): implement Windows elevation using a native module (#1366)
Sentry error reports showcase that elevation errors on Windows are one of the most frequent Windows errors. In order to perform Windows elevation, we ship compiled EXEs of a third party CLI elevation application (http://code.kliu.org/misc/elevate/) that has several limitations: - We have the scan the output of the script to determine if a user cancelled the elevation request, which causes all sorts of issues on computers where English is not the main language - The application displays a `cmd.exe` window for some milliseconds, which is bad UX, that we have to workaround by distributing a patched version of the tool - The CLI application has to be spawned, which seems to be problematic if users have anti-virus software, leading to hard to debug issues - We don't have any control if something goes wrong For these reasons, we decided to implement our own elevation mechanism in C++ as a Node.js add-on, based on the `elevate.exe` code we where previously using. Misc changes: - Introduce a `lib/shared/bindings.js` module to easily require local native add-ons - Install `cpplint` and configure it to lint C++ files Note that for practical reasons, the C++ code lives in this repository rather than in a separate module. We will release this functionality in a more accessible way in the future as part of the Etcher SDK project. Change-Type: patch Changelog-Entry: Fix uncaught errors when cancelling elevation requests on Windows when the system's language is not English. Signed-off-by: Juan Cruz Viotti <jviotti@openmailbox.org>
This commit is contained in:
parent
9881364e1d
commit
b75dfd3ece
3
.gitattributes
vendored
3
.gitattributes
vendored
@ -13,6 +13,9 @@ etcher text
|
|||||||
.git* text
|
.git* text
|
||||||
*.html text
|
*.html text
|
||||||
*.json text
|
*.json text
|
||||||
|
*.cpp text
|
||||||
|
*.h text
|
||||||
|
*.gyp text
|
||||||
LICENSE text
|
LICENSE text
|
||||||
Makefile text
|
Makefile text
|
||||||
*.md text
|
*.md text
|
||||||
|
2
.gitignore
vendored
2
.gitignore
vendored
@ -21,7 +21,7 @@ coverage
|
|||||||
.lock-wscript
|
.lock-wscript
|
||||||
|
|
||||||
# Compiled binary addons (http://nodejs.org/api/addons.html)
|
# Compiled binary addons (http://nodejs.org/api/addons.html)
|
||||||
build/Release
|
build
|
||||||
|
|
||||||
# Dependency directory
|
# Dependency directory
|
||||||
# https://docs.npmjs.com/misc/faq#should-i-check-my-node-modules-folder-into-git
|
# https://docs.npmjs.com/misc/faq#should-i-check-my-node-modules-folder-into-git
|
||||||
|
@ -40,7 +40,7 @@ install:
|
|||||||
- if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then
|
- if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then
|
||||||
./scripts/build/docker/run-command.sh -r "${TARGET_ARCH}" -s "${PWD}" -c "make info && make electron-develop";
|
./scripts/build/docker/run-command.sh -r "${TARGET_ARCH}" -s "${PWD}" -c "make info && make electron-develop";
|
||||||
else
|
else
|
||||||
pip install codespell==1.9.2 awscli;
|
pip install codespell==1.9.2 awscli cpplint;
|
||||||
brew install afsctool jq;
|
brew install afsctool jq;
|
||||||
make info;
|
make info;
|
||||||
travis_wait make electron-develop;
|
travis_wait make electron-develop;
|
||||||
|
38
Makefile
38
Makefile
@ -174,40 +174,40 @@ $(BUILD_DIRECTORY):
|
|||||||
$(BUILD_TEMPORARY_DIRECTORY): | $(BUILD_DIRECTORY)
|
$(BUILD_TEMPORARY_DIRECTORY): | $(BUILD_DIRECTORY)
|
||||||
mkdir $@
|
mkdir $@
|
||||||
|
|
||||||
$(BUILD_DIRECTORY)/electron-$(TARGET_PLATFORM)-$(TARGET_ARCH)-dependencies: | $(BUILD_DIRECTORY)
|
|
||||||
mkdir $@
|
|
||||||
|
|
||||||
$(BUILD_DIRECTORY)/node-$(TARGET_PLATFORM)-$(TARGET_ARCH)-dependencies: | $(BUILD_DIRECTORY)
|
|
||||||
mkdir $@
|
|
||||||
|
|
||||||
$(BUILD_OUTPUT_DIRECTORY): | $(BUILD_DIRECTORY)
|
$(BUILD_OUTPUT_DIRECTORY): | $(BUILD_DIRECTORY)
|
||||||
mkdir $@
|
mkdir $@
|
||||||
|
|
||||||
$(BUILD_DIRECTORY)/electron-$(TARGET_PLATFORM)-$(TARGET_ARCH)-dependencies/node_modules: package.json npm-shrinkwrap.json \
|
$(BUILD_DIRECTORY)/electron-$(TARGET_PLATFORM)-$(TARGET_ARCH)-dependencies: package.json npm-shrinkwrap.json \
|
||||||
| $(BUILD_DIRECTORY)/electron-$(TARGET_PLATFORM)-$(TARGET_ARCH)-dependencies
|
| $(BUILD_DIRECTORY)
|
||||||
|
mkdir $@
|
||||||
|
cp -rf src $@
|
||||||
./scripts/build/dependencies-npm.sh -p \
|
./scripts/build/dependencies-npm.sh -p \
|
||||||
-r "$(TARGET_ARCH)" \
|
-r "$(TARGET_ARCH)" \
|
||||||
-v "$(ELECTRON_VERSION)" \
|
-v "$(ELECTRON_VERSION)" \
|
||||||
-x $| \
|
-x $@ \
|
||||||
-t electron \
|
-t electron \
|
||||||
-s "$(TARGET_PLATFORM)"
|
-s "$(TARGET_PLATFORM)"
|
||||||
|
rm -rf $@/src
|
||||||
|
|
||||||
$(BUILD_DIRECTORY)/node-$(TARGET_PLATFORM)-$(TARGET_ARCH)-dependencies/node_modules: package.json npm-shrinkwrap.json \
|
$(BUILD_DIRECTORY)/node-$(TARGET_PLATFORM)-$(TARGET_ARCH)-dependencies: package.json npm-shrinkwrap.json \
|
||||||
| $(BUILD_DIRECTORY)/node-$(TARGET_PLATFORM)-$(TARGET_ARCH)-dependencies
|
| $(BUILD_DIRECTORY)
|
||||||
|
mkdir $@
|
||||||
|
cp -rf src $@
|
||||||
./scripts/build/dependencies-npm.sh -p -f \
|
./scripts/build/dependencies-npm.sh -p -f \
|
||||||
-r "$(TARGET_ARCH)" \
|
-r "$(TARGET_ARCH)" \
|
||||||
-v "$(NODE_VERSION)" \
|
-v "$(NODE_VERSION)" \
|
||||||
-x $| \
|
-x $@ \
|
||||||
-t node \
|
-t node \
|
||||||
-s "$(TARGET_PLATFORM)"
|
-s "$(TARGET_PLATFORM)"
|
||||||
|
rm -rf $@/src
|
||||||
|
|
||||||
$(BUILD_DIRECTORY)/electron-$(TARGET_PLATFORM)-$(APPLICATION_VERSION)-$(TARGET_ARCH)-app: \
|
$(BUILD_DIRECTORY)/electron-$(TARGET_PLATFORM)-$(APPLICATION_VERSION)-$(TARGET_ARCH)-app: \
|
||||||
$(BUILD_DIRECTORY)/electron-$(TARGET_PLATFORM)-$(TARGET_ARCH)-dependencies/node_modules \
|
$(BUILD_DIRECTORY)/electron-$(TARGET_PLATFORM)-$(TARGET_ARCH)-dependencies \
|
||||||
| $(BUILD_DIRECTORY) $(BUILD_TEMPORARY_DIRECTORY)
|
| $(BUILD_DIRECTORY) $(BUILD_TEMPORARY_DIRECTORY)
|
||||||
./scripts/build/electron-create-resources-app.sh -s . -o $@ \
|
./scripts/build/electron-create-resources-app.sh -s . -o $@ \
|
||||||
-v $(APPLICATION_VERSION) \
|
-v $(APPLICATION_VERSION) \
|
||||||
-f "$(APPLICATION_FILES)"
|
-f "$(APPLICATION_FILES)"
|
||||||
cp -RLf $< $@
|
cp -RLf $</* $@
|
||||||
|
|
||||||
ifdef ANALYTICS_SENTRY_TOKEN
|
ifdef ANALYTICS_SENTRY_TOKEN
|
||||||
./scripts/build/jq-insert.sh \
|
./scripts/build/jq-insert.sh \
|
||||||
@ -240,10 +240,12 @@ $(BUILD_DIRECTORY)/electron-$(ELECTRON_VERSION)-$(TARGET_PLATFORM)-$(TARGET_ARCH
|
|||||||
|
|
||||||
$(BUILD_DIRECTORY)/$(APPLICATION_NAME)-cli-$(TARGET_PLATFORM)-$(APPLICATION_VERSION)-$(TARGET_ARCH)-app: \
|
$(BUILD_DIRECTORY)/$(APPLICATION_NAME)-cli-$(TARGET_PLATFORM)-$(APPLICATION_VERSION)-$(TARGET_ARCH)-app: \
|
||||||
package.json lib \
|
package.json lib \
|
||||||
$(BUILD_DIRECTORY)/node-$(TARGET_PLATFORM)-$(TARGET_ARCH)-dependencies/node_modules \
|
$(BUILD_DIRECTORY)/node-$(TARGET_PLATFORM)-$(TARGET_ARCH)-dependencies \
|
||||||
| $(BUILD_DIRECTORY)
|
| $(BUILD_DIRECTORY)
|
||||||
mkdir $@
|
mkdir $@
|
||||||
$(foreach prerequisite,$^,$(call execute-command,cp -rf $(prerequisite) $@))
|
cp $(word 1,$^) $@
|
||||||
|
cp $(word 2,$^) $@
|
||||||
|
cp -rf $(word 3,$^)/* $@
|
||||||
|
|
||||||
$(BUILD_DIRECTORY)/$(APPLICATION_NAME)-cli-$(TARGET_PLATFORM)-$(APPLICATION_VERSION)-$(TARGET_ARCH).js: \
|
$(BUILD_DIRECTORY)/$(APPLICATION_NAME)-cli-$(TARGET_PLATFORM)-$(APPLICATION_VERSION)-$(TARGET_ARCH).js: \
|
||||||
$(BUILD_DIRECTORY)/$(APPLICATION_NAME)-cli-$(TARGET_PLATFORM)-$(APPLICATION_VERSION)-$(TARGET_ARCH)-app \
|
$(BUILD_DIRECTORY)/$(APPLICATION_NAME)-cli-$(TARGET_PLATFORM)-$(APPLICATION_VERSION)-$(TARGET_ARCH)-app \
|
||||||
@ -251,10 +253,10 @@ $(BUILD_DIRECTORY)/$(APPLICATION_NAME)-cli-$(TARGET_PLATFORM)-$(APPLICATION_VERS
|
|||||||
./scripts/build/concatenate-javascript.sh -e $</lib/cli/etcher.js -o $@
|
./scripts/build/concatenate-javascript.sh -e $</lib/cli/etcher.js -o $@
|
||||||
|
|
||||||
$(BUILD_DIRECTORY)/$(APPLICATION_NAME)-cli-$(APPLICATION_VERSION)-$(TARGET_PLATFORM)-$(TARGET_ARCH): \
|
$(BUILD_DIRECTORY)/$(APPLICATION_NAME)-cli-$(APPLICATION_VERSION)-$(TARGET_PLATFORM)-$(TARGET_ARCH): \
|
||||||
$(BUILD_DIRECTORY)/node-$(TARGET_PLATFORM)-$(TARGET_ARCH)-dependencies/node_modules \
|
$(BUILD_DIRECTORY)/node-$(TARGET_PLATFORM)-$(TARGET_ARCH)-dependencies \
|
||||||
$(BUILD_DIRECTORY)/$(APPLICATION_NAME)-cli-$(TARGET_PLATFORM)-$(APPLICATION_VERSION)-$(TARGET_ARCH).js \
|
$(BUILD_DIRECTORY)/$(APPLICATION_NAME)-cli-$(TARGET_PLATFORM)-$(APPLICATION_VERSION)-$(TARGET_ARCH).js \
|
||||||
| $(BUILD_DIRECTORY) $(BUILD_TEMPORARY_DIRECTORY)
|
| $(BUILD_DIRECTORY) $(BUILD_TEMPORARY_DIRECTORY)
|
||||||
./scripts/build/node-package-cli.sh -o $@ -l $< \
|
./scripts/build/node-package-cli.sh -o $@ -l $</node_modules \
|
||||||
-n $(APPLICATION_NAME) \
|
-n $(APPLICATION_NAME) \
|
||||||
-e $(word 2,$^) \
|
-e $(word 2,$^) \
|
||||||
-r $(TARGET_ARCH) \
|
-r $(TARGET_ARCH) \
|
||||||
|
@ -32,7 +32,7 @@ install:
|
|||||||
- set PATH=C:\Program Files (x86)\NSIS;%PATH%
|
- set PATH=C:\Program Files (x86)\NSIS;%PATH%
|
||||||
- set PATH=C:\MinGW\bin;%PATH%
|
- set PATH=C:\MinGW\bin;%PATH%
|
||||||
- set PATH=C:\MinGW\msys\1.0\bin;%PATH%
|
- set PATH=C:\MinGW\msys\1.0\bin;%PATH%
|
||||||
- pip install codespell==1.9.2 awscli
|
- pip install codespell==1.9.2 awscli cpplint
|
||||||
- make info
|
- make info
|
||||||
- make electron-develop
|
- make electron-develop
|
||||||
|
|
||||||
|
25
binding.gyp
Normal file
25
binding.gyp
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
{
|
||||||
|
"targets": [
|
||||||
|
{
|
||||||
|
"target_name": "elevator",
|
||||||
|
"include_dirs" : [
|
||||||
|
"src",
|
||||||
|
"<!(node -e \"require('nan')\")"
|
||||||
|
],
|
||||||
|
'conditions': [
|
||||||
|
|
||||||
|
[ 'OS=="win"', {
|
||||||
|
"sources": [
|
||||||
|
"src/utils/v8utils.cpp",
|
||||||
|
"src/os/elevate.cpp",
|
||||||
|
"src/elevator_init.cpp",
|
||||||
|
],
|
||||||
|
"libraries": [
|
||||||
|
"-lShell32.lib",
|
||||||
|
],
|
||||||
|
} ]
|
||||||
|
|
||||||
|
],
|
||||||
|
}
|
||||||
|
],
|
||||||
|
}
|
@ -14,6 +14,7 @@ Prerequisites
|
|||||||
- [jq](https://stedolan.github.io/jq/)
|
- [jq](https://stedolan.github.io/jq/)
|
||||||
- [Codespell](https://github.com/lucasdemarchi/codespell)
|
- [Codespell](https://github.com/lucasdemarchi/codespell)
|
||||||
- [curl](https://curl.haxx.se/)
|
- [curl](https://curl.haxx.se/)
|
||||||
|
- [cpplint](https://github.com/cpplint/cpplint)
|
||||||
|
|
||||||
### Windows
|
### Windows
|
||||||
|
|
||||||
|
44
lib/shared/bindings.js
Normal file
44
lib/shared/bindings.js
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2017 resin.io
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
const path = require('path');
|
||||||
|
const bindings = require('bindings');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @summary Load a native module
|
||||||
|
* @function
|
||||||
|
* @public
|
||||||
|
*
|
||||||
|
* @param {String} moduleName - native module name
|
||||||
|
* @returns {Object} native module
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* const elevator = bindings.load('elevator');
|
||||||
|
*/
|
||||||
|
exports.load = (moduleName) => {
|
||||||
|
return bindings({
|
||||||
|
bindings: moduleName,
|
||||||
|
|
||||||
|
/* eslint-disable camelcase */
|
||||||
|
|
||||||
|
module_root: path.join(__dirname, '..', '..')
|
||||||
|
|
||||||
|
/* eslint-enable camelcase */
|
||||||
|
|
||||||
|
});
|
||||||
|
};
|
@ -17,6 +17,7 @@
|
|||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
const os = require('os');
|
const os = require('os');
|
||||||
|
const bindings = require('./bindings');
|
||||||
const Bluebird = require('bluebird');
|
const Bluebird = require('bluebird');
|
||||||
const childProcess = Bluebird.promisifyAll(require('child_process'));
|
const childProcess = Bluebird.promisifyAll(require('child_process'));
|
||||||
const sudoPrompt = Bluebird.promisifyAll(require('sudo-prompt'));
|
const sudoPrompt = Bluebird.promisifyAll(require('sudo-prompt'));
|
||||||
@ -145,22 +146,13 @@ exports.elevateCommand = (command, options) => {
|
|||||||
const prefixedCommand = _.concat(exports.getEnvironmentCommandPrefix(options.environment), command);
|
const prefixedCommand = _.concat(exports.getEnvironmentCommandPrefix(options.environment), command);
|
||||||
|
|
||||||
if (os.platform() === 'win32') {
|
if (os.platform() === 'win32') {
|
||||||
const elevator = Bluebird.promisifyAll(require('elevator'));
|
const elevator = Bluebird.promisifyAll(bindings.load('elevator'));
|
||||||
|
return elevator.elevateAsync(_.concat([
|
||||||
return elevator.executeAsync(prefixedCommand, {
|
'cmd.exe',
|
||||||
hidden: true,
|
'/c'
|
||||||
terminating: true,
|
], prefixedCommand)).then((results) => {
|
||||||
doNotPushdCurrentDirectory: true,
|
|
||||||
waitForTermination: true
|
|
||||||
}).then(() => {
|
|
||||||
return {
|
return {
|
||||||
cancelled: false
|
cancelled: results.cancelled
|
||||||
};
|
|
||||||
}).catch({
|
|
||||||
code: 'ELEVATE_CANCELLED'
|
|
||||||
}, () => {
|
|
||||||
return {
|
|
||||||
cancelled: true
|
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
15
package.json
15
package.json
@ -9,6 +9,7 @@
|
|||||||
"description": "Flash OS images to SD cards & USB drives, safely and easily.",
|
"description": "Flash OS images to SD cards & USB drives, safely and easily.",
|
||||||
"productDescription": "Etcher is a powerful OS image flasher built with web technologies to ensure flashing an SDCard or USB drive is a pleasant and safe experience. It protects you from accidentally writing to your hard-drives, ensures every byte of data was written correctly and much more.",
|
"productDescription": "Etcher is a powerful OS image flasher built with web technologies to ensure flashing an SDCard or USB drive is a pleasant and safe experience. It protects you from accidentally writing to your hard-drives, ensures every byte of data was written correctly and much more.",
|
||||||
"homepage": "https://github.com/resin-io/etcher",
|
"homepage": "https://github.com/resin-io/etcher",
|
||||||
|
"gypfile": true,
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "git@github.com:resin-io/etcher.git"
|
"url": "git@github.com:resin-io/etcher.git"
|
||||||
@ -16,14 +17,18 @@
|
|||||||
"scripts": {
|
"scripts": {
|
||||||
"test": "npm run lint && electron-mocha --recursive --renderer tests/gui -R spec && electron-mocha --recursive tests/shared tests/child-writer tests/image-stream -R spec",
|
"test": "npm run lint && electron-mocha --recursive --renderer tests/gui -R spec && electron-mocha --recursive tests/shared tests/child-writer tests/image-stream -R spec",
|
||||||
"sass": "node-sass ./lib/gui/scss/main.scss > ./lib/gui/css/main.css",
|
"sass": "node-sass ./lib/gui/scss/main.scss > ./lib/gui/css/main.css",
|
||||||
|
"cpplint": "cpplint --recursive src",
|
||||||
"jslint": "eslint lib tests scripts bin versionist.conf.js",
|
"jslint": "eslint lib tests scripts bin versionist.conf.js",
|
||||||
"sasslint": "sass-lint lib/gui/scss",
|
"sasslint": "sass-lint lib/gui/scss",
|
||||||
"htmllint": "node scripts/html-lint.js",
|
"htmllint": "node scripts/html-lint.js",
|
||||||
"codespell": "codespell.py --skip *.gz,*.bz2,*.xz,*.zip,*.img,*.dmg,*.iso,.DS_Store lib tests docs scripts Makefile *.md LICENSE",
|
"codespell": "codespell.py --skip *.gz,*.bz2,*.xz,*.zip,*.img,*.dmg,*.iso,.DS_Store lib tests docs scripts Makefile *.md LICENSE",
|
||||||
"lint": "npm run jslint && npm run sasslint && npm run codespell && npm run htmllint",
|
"lint": "npm run jslint && npm run sasslint && npm run cpplint && npm run codespell && npm run htmllint",
|
||||||
"changelog": "versionist",
|
"changelog": "versionist",
|
||||||
"start": "electron lib/start.js",
|
"start": "electron lib/start.js",
|
||||||
"preshrinkwrap": "node ./scripts/clean-shrinkwrap.js"
|
"preshrinkwrap": "node ./scripts/clean-shrinkwrap.js",
|
||||||
|
"configure": "node-gyp configure",
|
||||||
|
"build": "node-gyp build",
|
||||||
|
"install": "node-gyp rebuild"
|
||||||
},
|
},
|
||||||
"author": "Juan Cruz Viotti <juan@resin.io>",
|
"author": "Juan Cruz Viotti <juan@resin.io>",
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
@ -61,8 +66,7 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"optionalDependencies": {
|
"optionalDependencies": {
|
||||||
"electron-installer-debian": "0.5.1",
|
"electron-installer-debian": "0.5.1"
|
||||||
"elevator": "2.2.3"
|
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"angular": "1.6.3",
|
"angular": "1.6.3",
|
||||||
@ -72,6 +76,7 @@
|
|||||||
"angular-seconds-to-date": "^1.0.0",
|
"angular-seconds-to-date": "^1.0.0",
|
||||||
"angular-ui-bootstrap": "^2.5.0",
|
"angular-ui-bootstrap": "^2.5.0",
|
||||||
"angular-ui-router": "^0.4.2",
|
"angular-ui-router": "^0.4.2",
|
||||||
|
"bindings": "^1.2.1",
|
||||||
"bluebird": "^3.0.5",
|
"bluebird": "^3.0.5",
|
||||||
"bootstrap-sass": "^3.3.5",
|
"bootstrap-sass": "^3.3.5",
|
||||||
"chalk": "^1.1.3",
|
"chalk": "^1.1.3",
|
||||||
@ -86,6 +91,7 @@
|
|||||||
"lzma-native": "^1.5.2",
|
"lzma-native": "^1.5.2",
|
||||||
"mime-types": "^2.1.15",
|
"mime-types": "^2.1.15",
|
||||||
"mountutils": "^1.0.6",
|
"mountutils": "^1.0.6",
|
||||||
|
"nan": "^2.3.5",
|
||||||
"node-ipc": "^8.9.2",
|
"node-ipc": "^8.9.2",
|
||||||
"node-stream-zip": "^1.3.4",
|
"node-stream-zip": "^1.3.4",
|
||||||
"path-is-inside": "^1.0.2",
|
"path-is-inside": "^1.0.2",
|
||||||
@ -120,6 +126,7 @@
|
|||||||
"html-angular-validate": "^0.1.9",
|
"html-angular-validate": "^0.1.9",
|
||||||
"mochainon": "^1.0.0",
|
"mochainon": "^1.0.0",
|
||||||
"nock": "^9.0.9",
|
"nock": "^9.0.9",
|
||||||
|
"node-gyp": "^3.5.0",
|
||||||
"node-sass": "^3.8.0",
|
"node-sass": "^3.8.0",
|
||||||
"sass-lint": "^1.10.2",
|
"sass-lint": "^1.10.2",
|
||||||
"tmp": "0.0.31",
|
"tmp": "0.0.31",
|
||||||
|
@ -129,12 +129,17 @@ if [ -n "$ARGV_PREFIX" ]; then
|
|||||||
cp "$PWD/npm-shrinkwrap.json" "$ARGV_PREFIX/npm-shrinkwrap.json"
|
cp "$PWD/npm-shrinkwrap.json" "$ARGV_PREFIX/npm-shrinkwrap.json"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
if [ -f "$PWD/binding.gyp" ]; then
|
||||||
|
cp "$PWD/binding.gyp" "$ARGV_PREFIX/binding.gyp"
|
||||||
|
fi
|
||||||
|
|
||||||
pushd "$ARGV_PREFIX"
|
pushd "$ARGV_PREFIX"
|
||||||
run_install
|
run_install
|
||||||
popd
|
popd
|
||||||
|
|
||||||
rm -f "$ARGV_PREFIX/package.json"
|
rm -f "$ARGV_PREFIX/package.json"
|
||||||
rm -f "$ARGV_PREFIX/npm-shrinkwrap.json"
|
rm -f "$ARGV_PREFIX/npm-shrinkwrap.json"
|
||||||
|
rm -f "$ARGV_PREFIX/binding.gyp"
|
||||||
else
|
else
|
||||||
run_install
|
run_install
|
||||||
fi
|
fi
|
||||||
|
@ -36,4 +36,4 @@ RUN curl -sL https://deb.nodesource.com/setup_6.x | bash - \
|
|||||||
RUN npm config set spin=false
|
RUN npm config set spin=false
|
||||||
|
|
||||||
# Python
|
# Python
|
||||||
RUN pip install codespell==1.9.2 awscli
|
RUN pip install codespell==1.9.2 awscli cpplint
|
||||||
|
@ -36,4 +36,4 @@ RUN curl -sL https://deb.nodesource.com/setup_6.x | bash - \
|
|||||||
RUN npm config set spin=false
|
RUN npm config set spin=false
|
||||||
|
|
||||||
# Python
|
# Python
|
||||||
RUN pip install codespell==1.9.2 awscli
|
RUN pip install codespell==1.9.2 awscli cpplint
|
||||||
|
@ -36,4 +36,4 @@ RUN curl -sL https://deb.nodesource.com/setup_6.x | bash - \
|
|||||||
RUN npm config set spin=false
|
RUN npm config set spin=false
|
||||||
|
|
||||||
# Python
|
# Python
|
||||||
RUN pip install codespell==1.9.2 awscli
|
RUN pip install codespell==1.9.2 awscli cpplint
|
||||||
|
61
src/elevator_init.cpp
Normal file
61
src/elevator_init.cpp
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2017 resin.io
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include "os/elevate.h"
|
||||||
|
#include "utils/v8utils.h"
|
||||||
|
|
||||||
|
NAN_METHOD(Elevate) {
|
||||||
|
if (!info[0]->IsArray()) {
|
||||||
|
return Nan::ThrowError("This function expects an array");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!info[1]->IsFunction()) {
|
||||||
|
return Nan::ThrowError("Callback must be a function");
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<std::string> arguments =
|
||||||
|
etcher::v8utils::GetArguments(info[0].As<v8::Array>());
|
||||||
|
v8::Local<v8::Function> callback = info[1].As<v8::Function>();
|
||||||
|
|
||||||
|
etcher::ELEVATE_RESULT result = etcher::Elevate(
|
||||||
|
arguments.front(),
|
||||||
|
std::vector<std::string>(arguments.begin() + 1, arguments.end()));
|
||||||
|
|
||||||
|
// Create results object
|
||||||
|
v8::Isolate *isolate = v8::Isolate::GetCurrent();
|
||||||
|
v8::Local<v8::Object> results = v8::Object::New(isolate);
|
||||||
|
|
||||||
|
switch (result) {
|
||||||
|
case etcher::ELEVATE_RESULT::ELEVATE_SUCCESS:
|
||||||
|
results->Set(v8::String::NewFromUtf8(isolate, "cancelled"), Nan::False());
|
||||||
|
YIELD_OBJECT(callback, results);
|
||||||
|
break;
|
||||||
|
case etcher::ELEVATE_RESULT::ELEVATE_CANCELLED:
|
||||||
|
results->Set(v8::String::NewFromUtf8(isolate, "cancelled"), Nan::True());
|
||||||
|
YIELD_OBJECT(callback, results);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
std::string details = etcher::ElevateResultToString(result);
|
||||||
|
YIELD_ERROR(callback, details.c_str());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
NAN_MODULE_INIT(ElevatorInit) { NAN_SET_FUNCTION("elevate", Elevate); }
|
||||||
|
|
||||||
|
NODE_MODULE(elevator, ElevatorInit)
|
156
src/os/elevate.cpp
Normal file
156
src/os/elevate.cpp
Normal file
@ -0,0 +1,156 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2017 resin.io
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "os/elevate.h"
|
||||||
|
|
||||||
|
static std::string JoinArguments(std::vector<std::string> arguments) {
|
||||||
|
std::ostringstream result;
|
||||||
|
|
||||||
|
std::copy(arguments.begin(), arguments.end(),
|
||||||
|
std::ostream_iterator<std::string>(result, " "));
|
||||||
|
|
||||||
|
return result.str();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make sure to delete the result after you're done
|
||||||
|
// with it by calling `delete[] result;`.
|
||||||
|
// See http://stackoverflow.com/a/1201471
|
||||||
|
static LPCTSTR ConvertStringToLPCTSTR(const std::string &string) {
|
||||||
|
char *result = new char[string.size() + 1];
|
||||||
|
std::copy(string.begin(), string.end(), result);
|
||||||
|
result[string.size()] = 0;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
etcher::ELEVATE_RESULT etcher::Elevate(const std::string &command,
|
||||||
|
std::vector<std::string> arguments) {
|
||||||
|
// Initialize the SHELLEXECUTEINFO structure. We zero it out
|
||||||
|
// in order to be on the safe side, and set cbSize to the size
|
||||||
|
// of the structure as recommend by MSDN
|
||||||
|
// See: https://msdn.microsoft.com/en-us/library/windows/desktop/bb759784(v=vs.85).aspx
|
||||||
|
SHELLEXECUTEINFO shellExecuteInfo;
|
||||||
|
ZeroMemory(&shellExecuteInfo, sizeof(shellExecuteInfo));
|
||||||
|
shellExecuteInfo.cbSize = sizeof(SHELLEXECUTEINFO);
|
||||||
|
|
||||||
|
// Flags that indicate the content and validity of the other structure member.
|
||||||
|
shellExecuteInfo.fMask =
|
||||||
|
|
||||||
|
// Used to indicate that the hProcess member receives the process handle.
|
||||||
|
// This handle is typically used to allow an application to find out
|
||||||
|
// when a process created with ShellExecuteEx terminates.
|
||||||
|
SEE_MASK_NOCLOSEPROCESS |
|
||||||
|
|
||||||
|
// Wait for the execute operation to complete before returning.
|
||||||
|
SEE_MASK_NOASYNC |
|
||||||
|
|
||||||
|
// Do not display an error message box if an error occurs.
|
||||||
|
SEE_MASK_FLAG_NO_UI;
|
||||||
|
|
||||||
|
// The action to be performed.
|
||||||
|
shellExecuteInfo.lpVerb = TEXT("runas");
|
||||||
|
|
||||||
|
// Run the file in the background
|
||||||
|
shellExecuteInfo.nShow = SW_HIDE;
|
||||||
|
|
||||||
|
// Use the current directory as the working directory
|
||||||
|
shellExecuteInfo.lpDirectory = NULL;
|
||||||
|
|
||||||
|
// Set file and parameters
|
||||||
|
// We can't just assign the result of `.c_str()`, since
|
||||||
|
// that pointer is owned by the `std::string` instance,
|
||||||
|
// and will not be safe after the instance is destroyed.
|
||||||
|
LPCTSTR file = ConvertStringToLPCTSTR(command);
|
||||||
|
LPCTSTR argv = ConvertStringToLPCTSTR(JoinArguments(arguments));
|
||||||
|
shellExecuteInfo.lpFile = file;
|
||||||
|
shellExecuteInfo.lpParameters = argv;
|
||||||
|
|
||||||
|
BOOL executeResult = ShellExecuteEx(&shellExecuteInfo);
|
||||||
|
|
||||||
|
delete[] file;
|
||||||
|
delete[] argv;
|
||||||
|
|
||||||
|
// Finally, let's try to elevate the command
|
||||||
|
if (!executeResult) {
|
||||||
|
DWORD executeError = GetLastError();
|
||||||
|
|
||||||
|
switch (executeError) {
|
||||||
|
case ERROR_FILE_NOT_FOUND:
|
||||||
|
return etcher::ELEVATE_RESULT::ELEVATE_FILE_NOT_FOUND;
|
||||||
|
case ERROR_PATH_NOT_FOUND:
|
||||||
|
return etcher::ELEVATE_RESULT::ELEVATE_PATH_NOT_FOUND;
|
||||||
|
case ERROR_DDE_FAIL:
|
||||||
|
return etcher::ELEVATE_RESULT::ELEVATE_DDE_FAIL;
|
||||||
|
case ERROR_NO_ASSOCIATION:
|
||||||
|
return etcher::ELEVATE_RESULT::ELEVATE_NO_ASSOCIATION;
|
||||||
|
case ERROR_ACCESS_DENIED:
|
||||||
|
return etcher::ELEVATE_RESULT::ELEVATE_ACCESS_DENIED;
|
||||||
|
case ERROR_DLL_NOT_FOUND:
|
||||||
|
return etcher::ELEVATE_RESULT::ELEVATE_DLL_NOT_FOUND;
|
||||||
|
case ERROR_CANCELLED:
|
||||||
|
return etcher::ELEVATE_RESULT::ELEVATE_CANCELLED;
|
||||||
|
case ERROR_NOT_ENOUGH_MEMORY:
|
||||||
|
return etcher::ELEVATE_RESULT::ELEVATE_NOT_ENOUGH_MEMORY;
|
||||||
|
case ERROR_SHARING_VIOLATION:
|
||||||
|
return etcher::ELEVATE_RESULT::ELEVATE_SHARING_VIOLATION;
|
||||||
|
default:
|
||||||
|
return etcher::ELEVATE_RESULT::ELEVATE_UNKNOWN_ERROR;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Since we passed SEE_MASK_NOCLOSEPROCESS, the
|
||||||
|
// process handle is accessible from hProcess.
|
||||||
|
if (shellExecuteInfo.hProcess) {
|
||||||
|
// Wait for the process to exit before continuing.
|
||||||
|
// See: https://msdn.microsoft.com/en-us/library/windows/desktop/ms687032(v=vs.85).aspx
|
||||||
|
WaitForSingleObject(shellExecuteInfo.hProcess, INFINITE);
|
||||||
|
|
||||||
|
if (!CloseHandle(shellExecuteInfo.hProcess)) {
|
||||||
|
return etcher::ELEVATE_RESULT::ELEVATE_UNKNOWN_ERROR;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return etcher::ELEVATE_RESULT::ELEVATE_SUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string
|
||||||
|
etcher::ElevateResultToString(const etcher::ELEVATE_RESULT &result) {
|
||||||
|
switch (result) {
|
||||||
|
case etcher::ELEVATE_RESULT::ELEVATE_SUCCESS:
|
||||||
|
return "Success";
|
||||||
|
case etcher::ELEVATE_RESULT::ELEVATE_CANCELLED:
|
||||||
|
return "The user cancelled the elevation request";
|
||||||
|
case etcher::ELEVATE_RESULT::ELEVATE_FILE_NOT_FOUND:
|
||||||
|
return "The specified file was not found";
|
||||||
|
case etcher::ELEVATE_RESULT::ELEVATE_PATH_NOT_FOUND:
|
||||||
|
return "The specified path was not found";
|
||||||
|
case etcher::ELEVATE_RESULT::ELEVATE_DDE_FAIL:
|
||||||
|
return "The Dynamic Data Exchange (DDE) transaction failed";
|
||||||
|
case etcher::ELEVATE_RESULT::ELEVATE_NO_ASSOCIATION:
|
||||||
|
return "There is no application associated with the "
|
||||||
|
"specified file name extension";
|
||||||
|
case etcher::ELEVATE_RESULT::ELEVATE_ACCESS_DENIED:
|
||||||
|
return "Access to the specified file is denied";
|
||||||
|
case etcher::ELEVATE_RESULT::ELEVATE_DLL_NOT_FOUND:
|
||||||
|
return "One of the library files necessary to run the "
|
||||||
|
"application can't be found";
|
||||||
|
case etcher::ELEVATE_RESULT::ELEVATE_NOT_ENOUGH_MEMORY:
|
||||||
|
return "There is not enough memory to perform the specified action";
|
||||||
|
case etcher::ELEVATE_RESULT::ELEVATE_SHARING_VIOLATION:
|
||||||
|
return "A sharing violation occurred";
|
||||||
|
default:
|
||||||
|
return "Unknown error";
|
||||||
|
}
|
||||||
|
}
|
59
src/os/elevate.h
Normal file
59
src/os/elevate.h
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
#ifndef SRC_OS_ELEVATE_H_
|
||||||
|
#define SRC_OS_ELEVATE_H_
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Copyright 2017 resin.io
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Fix winsock.h redefinition errors
|
||||||
|
#ifndef WIN32_LEAN_AND_MEAN
|
||||||
|
#define WIN32_LEAN_AND_MEAN
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// Note that windows.h has to be included before any
|
||||||
|
// other Windows library to avoid declaration issues
|
||||||
|
#include <windows.h>
|
||||||
|
#include <shellapi.h>
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <iterator>
|
||||||
|
#include <sstream>
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace etcher {
|
||||||
|
|
||||||
|
enum class ELEVATE_RESULT {
|
||||||
|
ELEVATE_SUCCESS,
|
||||||
|
ELEVATE_FILE_NOT_FOUND,
|
||||||
|
ELEVATE_PATH_NOT_FOUND,
|
||||||
|
ELEVATE_DDE_FAIL,
|
||||||
|
ELEVATE_NO_ASSOCIATION,
|
||||||
|
ELEVATE_ACCESS_DENIED,
|
||||||
|
ELEVATE_DLL_NOT_FOUND,
|
||||||
|
ELEVATE_CANCELLED,
|
||||||
|
ELEVATE_NOT_ENOUGH_MEMORY,
|
||||||
|
ELEVATE_SHARING_VIOLATION,
|
||||||
|
ELEVATE_UNKNOWN_ERROR
|
||||||
|
};
|
||||||
|
|
||||||
|
ELEVATE_RESULT Elevate(const std::string &command,
|
||||||
|
std::vector<std::string> arguments);
|
||||||
|
|
||||||
|
std::string ElevateResultToString(const ELEVATE_RESULT &result);
|
||||||
|
|
||||||
|
} // namespace etcher
|
||||||
|
|
||||||
|
#endif // SRC_OS_ELEVATE_H_
|
30
src/utils/v8utils.cpp
Normal file
30
src/utils/v8utils.cpp
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2017 resin.io
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "utils/v8utils.h"
|
||||||
|
|
||||||
|
std::vector<std::string>
|
||||||
|
etcher::v8utils::GetArguments(v8::Local<v8::Array> arguments) {
|
||||||
|
std::vector<std::string> result(0);
|
||||||
|
|
||||||
|
for (uint32_t index = 0; index < arguments->Length(); index++) {
|
||||||
|
std::string argument(
|
||||||
|
*v8::String::Utf8Value(arguments->Get(index)->ToString()));
|
||||||
|
result.push_back(argument);
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
54
src/utils/v8utils.h
Normal file
54
src/utils/v8utils.h
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
#ifndef SRC_UTILS_V8UTILS_H_
|
||||||
|
#define SRC_UTILS_V8UTILS_H_
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Copyright 2017 resin.io
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <nan.h>
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace etcher {
|
||||||
|
namespace v8utils {
|
||||||
|
std::vector<std::string> GetArguments(v8::Local<v8::Array> arguments);
|
||||||
|
} // namespace v8utils
|
||||||
|
} // namespace etcher
|
||||||
|
|
||||||
|
#define YIELD_ERROR(CALLBACK, ERROR) \
|
||||||
|
{ \
|
||||||
|
v8::Local<v8::Value> argv[1] = {Nan::Error((ERROR))}; \
|
||||||
|
Nan::MakeCallback(Nan::GetCurrentContext()->Global(), (CALLBACK), 1, \
|
||||||
|
argv); \
|
||||||
|
} \
|
||||||
|
return;
|
||||||
|
|
||||||
|
#define YIELD_OBJECT(CALLBACK, OBJECT) \
|
||||||
|
{ \
|
||||||
|
v8::Local<v8::Value> argv[2] = {Nan::Null(), (OBJECT)}; \
|
||||||
|
Nan::MakeCallback(Nan::GetCurrentContext()->Global(), (CALLBACK), 2, \
|
||||||
|
argv); \
|
||||||
|
} \
|
||||||
|
return;
|
||||||
|
|
||||||
|
#define YIELD_NOTHING(CALLBACK) \
|
||||||
|
Nan::MakeCallback(Nan::GetCurrentContext()->Global(), (CALLBACK), 0, 0);
|
||||||
|
|
||||||
|
#define NAN_SET_FUNCTION(JSSYMBOL, FUNCTION) \
|
||||||
|
Nan::Set(target, Nan::New((JSSYMBOL)).ToLocalChecked(), \
|
||||||
|
Nan::GetFunction(Nan::New<v8::FunctionTemplate>((FUNCTION))) \
|
||||||
|
.ToLocalChecked());
|
||||||
|
|
||||||
|
#endif // SRC_UTILS_V8UTILS_H_
|
Loading…
x
Reference in New Issue
Block a user