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
|
||||
*.html text
|
||||
*.json text
|
||||
*.cpp text
|
||||
*.h text
|
||||
*.gyp text
|
||||
LICENSE text
|
||||
Makefile text
|
||||
*.md text
|
||||
|
2
.gitignore
vendored
2
.gitignore
vendored
@ -21,7 +21,7 @@ coverage
|
||||
.lock-wscript
|
||||
|
||||
# Compiled binary addons (http://nodejs.org/api/addons.html)
|
||||
build/Release
|
||||
build
|
||||
|
||||
# Dependency directory
|
||||
# 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
|
||||
./scripts/build/docker/run-command.sh -r "${TARGET_ARCH}" -s "${PWD}" -c "make info && make electron-develop";
|
||||
else
|
||||
pip install codespell==1.9.2 awscli;
|
||||
pip install codespell==1.9.2 awscli cpplint;
|
||||
brew install afsctool jq;
|
||||
make info;
|
||||
travis_wait make electron-develop;
|
||||
|
38
Makefile
38
Makefile
@ -174,40 +174,40 @@ $(BUILD_DIRECTORY):
|
||||
$(BUILD_TEMPORARY_DIRECTORY): | $(BUILD_DIRECTORY)
|
||||
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)
|
||||
mkdir $@
|
||||
|
||||
$(BUILD_DIRECTORY)/electron-$(TARGET_PLATFORM)-$(TARGET_ARCH)-dependencies/node_modules: package.json npm-shrinkwrap.json \
|
||||
| $(BUILD_DIRECTORY)/electron-$(TARGET_PLATFORM)-$(TARGET_ARCH)-dependencies
|
||||
$(BUILD_DIRECTORY)/electron-$(TARGET_PLATFORM)-$(TARGET_ARCH)-dependencies: package.json npm-shrinkwrap.json \
|
||||
| $(BUILD_DIRECTORY)
|
||||
mkdir $@
|
||||
cp -rf src $@
|
||||
./scripts/build/dependencies-npm.sh -p \
|
||||
-r "$(TARGET_ARCH)" \
|
||||
-v "$(ELECTRON_VERSION)" \
|
||||
-x $| \
|
||||
-x $@ \
|
||||
-t electron \
|
||||
-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
|
||||
$(BUILD_DIRECTORY)/node-$(TARGET_PLATFORM)-$(TARGET_ARCH)-dependencies: package.json npm-shrinkwrap.json \
|
||||
| $(BUILD_DIRECTORY)
|
||||
mkdir $@
|
||||
cp -rf src $@
|
||||
./scripts/build/dependencies-npm.sh -p -f \
|
||||
-r "$(TARGET_ARCH)" \
|
||||
-v "$(NODE_VERSION)" \
|
||||
-x $| \
|
||||
-x $@ \
|
||||
-t node \
|
||||
-s "$(TARGET_PLATFORM)"
|
||||
rm -rf $@/src
|
||||
|
||||
$(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)
|
||||
./scripts/build/electron-create-resources-app.sh -s . -o $@ \
|
||||
-v $(APPLICATION_VERSION) \
|
||||
-f "$(APPLICATION_FILES)"
|
||||
cp -RLf $< $@
|
||||
cp -RLf $</* $@
|
||||
|
||||
ifdef ANALYTICS_SENTRY_TOKEN
|
||||
./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: \
|
||||
package.json lib \
|
||||
$(BUILD_DIRECTORY)/node-$(TARGET_PLATFORM)-$(TARGET_ARCH)-dependencies/node_modules \
|
||||
$(BUILD_DIRECTORY)/node-$(TARGET_PLATFORM)-$(TARGET_ARCH)-dependencies \
|
||||
| $(BUILD_DIRECTORY)
|
||||
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)-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 $@
|
||||
|
||||
$(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) $(BUILD_TEMPORARY_DIRECTORY)
|
||||
./scripts/build/node-package-cli.sh -o $@ -l $< \
|
||||
./scripts/build/node-package-cli.sh -o $@ -l $</node_modules \
|
||||
-n $(APPLICATION_NAME) \
|
||||
-e $(word 2,$^) \
|
||||
-r $(TARGET_ARCH) \
|
||||
|
@ -32,7 +32,7 @@ install:
|
||||
- set PATH=C:\Program Files (x86)\NSIS;%PATH%
|
||||
- set PATH=C:\MinGW\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 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/)
|
||||
- [Codespell](https://github.com/lucasdemarchi/codespell)
|
||||
- [curl](https://curl.haxx.se/)
|
||||
- [cpplint](https://github.com/cpplint/cpplint)
|
||||
|
||||
### 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';
|
||||
|
||||
const os = require('os');
|
||||
const bindings = require('./bindings');
|
||||
const Bluebird = require('bluebird');
|
||||
const childProcess = Bluebird.promisifyAll(require('child_process'));
|
||||
const sudoPrompt = Bluebird.promisifyAll(require('sudo-prompt'));
|
||||
@ -145,22 +146,13 @@ exports.elevateCommand = (command, options) => {
|
||||
const prefixedCommand = _.concat(exports.getEnvironmentCommandPrefix(options.environment), command);
|
||||
|
||||
if (os.platform() === 'win32') {
|
||||
const elevator = Bluebird.promisifyAll(require('elevator'));
|
||||
|
||||
return elevator.executeAsync(prefixedCommand, {
|
||||
hidden: true,
|
||||
terminating: true,
|
||||
doNotPushdCurrentDirectory: true,
|
||||
waitForTermination: true
|
||||
}).then(() => {
|
||||
const elevator = Bluebird.promisifyAll(bindings.load('elevator'));
|
||||
return elevator.elevateAsync(_.concat([
|
||||
'cmd.exe',
|
||||
'/c'
|
||||
], prefixedCommand)).then((results) => {
|
||||
return {
|
||||
cancelled: false
|
||||
};
|
||||
}).catch({
|
||||
code: 'ELEVATE_CANCELLED'
|
||||
}, () => {
|
||||
return {
|
||||
cancelled: true
|
||||
cancelled: results.cancelled
|
||||
};
|
||||
});
|
||||
}
|
||||
|
15
package.json
15
package.json
@ -9,6 +9,7 @@
|
||||
"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.",
|
||||
"homepage": "https://github.com/resin-io/etcher",
|
||||
"gypfile": true,
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git@github.com:resin-io/etcher.git"
|
||||
@ -16,14 +17,18 @@
|
||||
"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",
|
||||
"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",
|
||||
"sasslint": "sass-lint lib/gui/scss",
|
||||
"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",
|
||||
"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",
|
||||
"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>",
|
||||
"license": "Apache-2.0",
|
||||
@ -61,8 +66,7 @@
|
||||
}
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"electron-installer-debian": "0.5.1",
|
||||
"elevator": "2.2.3"
|
||||
"electron-installer-debian": "0.5.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"angular": "1.6.3",
|
||||
@ -72,6 +76,7 @@
|
||||
"angular-seconds-to-date": "^1.0.0",
|
||||
"angular-ui-bootstrap": "^2.5.0",
|
||||
"angular-ui-router": "^0.4.2",
|
||||
"bindings": "^1.2.1",
|
||||
"bluebird": "^3.0.5",
|
||||
"bootstrap-sass": "^3.3.5",
|
||||
"chalk": "^1.1.3",
|
||||
@ -86,6 +91,7 @@
|
||||
"lzma-native": "^1.5.2",
|
||||
"mime-types": "^2.1.15",
|
||||
"mountutils": "^1.0.6",
|
||||
"nan": "^2.3.5",
|
||||
"node-ipc": "^8.9.2",
|
||||
"node-stream-zip": "^1.3.4",
|
||||
"path-is-inside": "^1.0.2",
|
||||
@ -120,6 +126,7 @@
|
||||
"html-angular-validate": "^0.1.9",
|
||||
"mochainon": "^1.0.0",
|
||||
"nock": "^9.0.9",
|
||||
"node-gyp": "^3.5.0",
|
||||
"node-sass": "^3.8.0",
|
||||
"sass-lint": "^1.10.2",
|
||||
"tmp": "0.0.31",
|
||||
|
@ -129,12 +129,17 @@ if [ -n "$ARGV_PREFIX" ]; then
|
||||
cp "$PWD/npm-shrinkwrap.json" "$ARGV_PREFIX/npm-shrinkwrap.json"
|
||||
fi
|
||||
|
||||
if [ -f "$PWD/binding.gyp" ]; then
|
||||
cp "$PWD/binding.gyp" "$ARGV_PREFIX/binding.gyp"
|
||||
fi
|
||||
|
||||
pushd "$ARGV_PREFIX"
|
||||
run_install
|
||||
popd
|
||||
|
||||
rm -f "$ARGV_PREFIX/package.json"
|
||||
rm -f "$ARGV_PREFIX/npm-shrinkwrap.json"
|
||||
rm -f "$ARGV_PREFIX/binding.gyp"
|
||||
else
|
||||
run_install
|
||||
fi
|
||||
|
@ -36,4 +36,4 @@ RUN curl -sL https://deb.nodesource.com/setup_6.x | bash - \
|
||||
RUN npm config set spin=false
|
||||
|
||||
# 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
|
||||
|
||||
# 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
|
||||
|
||||
# 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