mirror of
https://github.com/cmclark00/retro-imager.git
synced 2025-05-19 00:15:21 +01:00
Qt/QML edition
This commit is contained in:
commit
d7b361ba44
2168 changed files with 721948 additions and 0 deletions
13
dependencies/mountutils/.editorconfig
vendored
Normal file
13
dependencies/mountutils/.editorconfig
vendored
Normal file
|
@ -0,0 +1,13 @@
|
|||
# editorconfig.org
|
||||
root = true
|
||||
|
||||
[*]
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
end_of_line = lf
|
||||
charset = utf-8
|
||||
trim_trailing_whitespace = true
|
||||
insert_final_newline = true
|
||||
|
||||
[*.md]
|
||||
trim_trailing_whitespace = false
|
477
dependencies/mountutils/.eslintrc.yml
vendored
Normal file
477
dependencies/mountutils/.eslintrc.yml
vendored
Normal file
|
@ -0,0 +1,477 @@
|
|||
env:
|
||||
browser: true
|
||||
commonjs: true
|
||||
es6: true
|
||||
node: true
|
||||
mocha: true
|
||||
extends: 'eslint:recommended'
|
||||
rules:
|
||||
|
||||
# Possible Errors
|
||||
|
||||
comma-dangle:
|
||||
- error
|
||||
- never
|
||||
no-cond-assign:
|
||||
- error
|
||||
no-console:
|
||||
- off
|
||||
no-constant-condition:
|
||||
- error
|
||||
no-control-regex:
|
||||
- error
|
||||
no-debugger:
|
||||
- error
|
||||
no-dupe-args:
|
||||
- error
|
||||
no-dupe-keys:
|
||||
- error
|
||||
no-duplicate-case:
|
||||
- error
|
||||
no-empty:
|
||||
- error
|
||||
no-empty-character-class:
|
||||
- error
|
||||
no-ex-assign:
|
||||
- error
|
||||
no-extra-boolean-cast:
|
||||
- error
|
||||
no-extra-parens:
|
||||
- error
|
||||
no-extra-semi:
|
||||
- error
|
||||
no-func-assign:
|
||||
- error
|
||||
no-inner-declarations:
|
||||
- error
|
||||
- both
|
||||
no-invalid-regexp:
|
||||
- error
|
||||
no-irregular-whitespace:
|
||||
- error
|
||||
no-negated-in-lhs:
|
||||
- error
|
||||
no-obj-calls:
|
||||
- error
|
||||
no-prototype-builtins:
|
||||
- error
|
||||
no-regex-spaces:
|
||||
- error
|
||||
no-sparse-arrays:
|
||||
- error
|
||||
no-unexpected-multiline:
|
||||
- error
|
||||
no-unreachable:
|
||||
- error
|
||||
no-unsafe-finally:
|
||||
- error
|
||||
use-isnan:
|
||||
- error
|
||||
valid-jsdoc:
|
||||
- error
|
||||
- requireReturn: false
|
||||
requireReturnDescription: false
|
||||
requireReturnType: true
|
||||
requireParamDescription: true
|
||||
preferType:
|
||||
boolean: "Boolean"
|
||||
number: "Number"
|
||||
object: "Object"
|
||||
string: "String"
|
||||
array: "Array"
|
||||
prefer:
|
||||
arg: "param"
|
||||
return: "returns"
|
||||
valid-typeof:
|
||||
- error
|
||||
|
||||
# Best Practices
|
||||
|
||||
accessor-pairs:
|
||||
- error
|
||||
array-callback-return:
|
||||
- error
|
||||
block-scoped-var:
|
||||
- error
|
||||
complexity:
|
||||
- off
|
||||
curly:
|
||||
- error
|
||||
default-case:
|
||||
- error
|
||||
dot-location:
|
||||
- error
|
||||
- property
|
||||
dot-notation:
|
||||
- error
|
||||
eqeqeq:
|
||||
- error
|
||||
guard-for-in:
|
||||
- error
|
||||
no-alert:
|
||||
- error
|
||||
no-caller:
|
||||
- error
|
||||
no-case-declarations:
|
||||
- error
|
||||
no-div-regex:
|
||||
- error
|
||||
no-else-return:
|
||||
- error
|
||||
no-empty-function:
|
||||
- error
|
||||
no-empty-pattern:
|
||||
- error
|
||||
no-eq-null:
|
||||
- error
|
||||
no-eval:
|
||||
- error
|
||||
no-extend-native:
|
||||
- error
|
||||
no-extra-bind:
|
||||
- error
|
||||
no-extra-label:
|
||||
- error
|
||||
no-fallthrough:
|
||||
- error
|
||||
no-floating-decimal:
|
||||
- error
|
||||
no-implicit-coercion:
|
||||
- error
|
||||
no-implicit-globals:
|
||||
- error
|
||||
no-implied-eval:
|
||||
- error
|
||||
no-iterator:
|
||||
- error
|
||||
no-labels:
|
||||
- error
|
||||
no-lone-blocks:
|
||||
- error
|
||||
no-loop-func:
|
||||
- error
|
||||
no-multi-spaces:
|
||||
- error
|
||||
no-multi-str:
|
||||
- error
|
||||
no-native-reassign:
|
||||
- error
|
||||
no-new:
|
||||
- error
|
||||
no-new-func:
|
||||
- error
|
||||
no-new-wrappers:
|
||||
- error
|
||||
no-octal:
|
||||
- error
|
||||
no-octal-escape:
|
||||
- error
|
||||
no-proto:
|
||||
- error
|
||||
no-redeclare:
|
||||
- error
|
||||
no-return-assign:
|
||||
- error
|
||||
no-script-url:
|
||||
- error
|
||||
no-self-assign:
|
||||
- error
|
||||
no-self-compare:
|
||||
- error
|
||||
no-sequences:
|
||||
- error
|
||||
no-throw-literal:
|
||||
- error
|
||||
no-unmodified-loop-condition:
|
||||
- error
|
||||
no-unused-labels:
|
||||
- error
|
||||
no-useless-call:
|
||||
- error
|
||||
no-useless-concat:
|
||||
- error
|
||||
no-useless-escape:
|
||||
- error
|
||||
no-void:
|
||||
- error
|
||||
no-warning-comments:
|
||||
- off
|
||||
no-with:
|
||||
- error
|
||||
radix:
|
||||
- error
|
||||
vars-on-top:
|
||||
- off
|
||||
wrap-iife:
|
||||
- error
|
||||
- outside
|
||||
yoda:
|
||||
- error
|
||||
|
||||
# Strict mode
|
||||
|
||||
strict:
|
||||
- error
|
||||
- global
|
||||
|
||||
# Variables
|
||||
|
||||
no-catch-shadow:
|
||||
- error
|
||||
no-delete-var:
|
||||
- error
|
||||
no-label-var:
|
||||
- error
|
||||
no-shadow:
|
||||
- error
|
||||
no-shadow-restricted-names:
|
||||
- error
|
||||
no-undef:
|
||||
- error
|
||||
no-undef-init:
|
||||
- error
|
||||
no-unused-vars:
|
||||
- error
|
||||
no-use-before-define:
|
||||
- error
|
||||
|
||||
# NodeJS and CommonJS
|
||||
|
||||
callback-return:
|
||||
- error
|
||||
global-require:
|
||||
- off
|
||||
handle-callback-err:
|
||||
- error
|
||||
no-mixed-requires:
|
||||
- error
|
||||
no-new-require:
|
||||
- error
|
||||
no-path-concat:
|
||||
- error
|
||||
no-process-env:
|
||||
- off
|
||||
no-process-exit:
|
||||
- off
|
||||
no-sync:
|
||||
- off
|
||||
|
||||
# Stylistic Issues
|
||||
|
||||
array-bracket-spacing:
|
||||
- error
|
||||
- always
|
||||
block-spacing:
|
||||
- error
|
||||
brace-style:
|
||||
- error
|
||||
- 1tbs
|
||||
camelcase:
|
||||
- error
|
||||
comma-spacing:
|
||||
- error
|
||||
- before: false
|
||||
after: true
|
||||
comma-style:
|
||||
- error
|
||||
- last
|
||||
computed-property-spacing:
|
||||
- error
|
||||
- never
|
||||
consistent-this:
|
||||
- error
|
||||
- self
|
||||
eol-last:
|
||||
- error
|
||||
func-names:
|
||||
- error
|
||||
- never
|
||||
func-style:
|
||||
- error
|
||||
- expression
|
||||
id-blacklist:
|
||||
- error
|
||||
indent:
|
||||
- error
|
||||
- 2
|
||||
- SwitchCase: 1
|
||||
key-spacing:
|
||||
- error
|
||||
- beforeColon: false
|
||||
afterColon: true
|
||||
mode: strict
|
||||
keyword-spacing:
|
||||
- error
|
||||
- before: true
|
||||
after: true
|
||||
lines-around-comment:
|
||||
- error
|
||||
- beforeBlockComment: true
|
||||
afterBlockComment: false
|
||||
beforeLineComment: true
|
||||
afterLineComment: false
|
||||
allowBlockStart: true
|
||||
allowBlockEnd: false
|
||||
allowObjectStart: true
|
||||
allowObjectEnd: false
|
||||
allowArrayStart: true
|
||||
allowArrayEnd: false
|
||||
max-len:
|
||||
- error
|
||||
- code: 130
|
||||
comments: 150
|
||||
ignoreComments: false
|
||||
ignoreTrailingComments: false
|
||||
ignoreUrls: true
|
||||
max-statements-per-line:
|
||||
- error
|
||||
- max: 1
|
||||
new-cap:
|
||||
- error
|
||||
new-parens:
|
||||
- error
|
||||
no-array-constructor:
|
||||
- error
|
||||
no-bitwise:
|
||||
- error
|
||||
no-continue:
|
||||
- error
|
||||
no-inline-comments:
|
||||
- error
|
||||
no-mixed-operators:
|
||||
- error
|
||||
no-mixed-spaces-and-tabs:
|
||||
- error
|
||||
no-multiple-empty-lines:
|
||||
- error
|
||||
- max: 1
|
||||
maxEOF: 1
|
||||
maxBOF: 0
|
||||
no-negated-condition:
|
||||
- error
|
||||
no-nested-ternary:
|
||||
- error
|
||||
no-new-object:
|
||||
- error
|
||||
no-plusplus:
|
||||
- error
|
||||
no-spaced-func:
|
||||
- error
|
||||
no-trailing-spaces:
|
||||
- error
|
||||
no-underscore-dangle:
|
||||
- error
|
||||
- allowAfterThis: false
|
||||
no-unneeded-ternary:
|
||||
- error
|
||||
no-whitespace-before-property:
|
||||
- error
|
||||
object-curly-newline:
|
||||
- error
|
||||
- minProperties: 1
|
||||
object-curly-spacing:
|
||||
- error
|
||||
- always
|
||||
object-property-newline:
|
||||
- error
|
||||
one-var:
|
||||
- error
|
||||
- never
|
||||
operator-assignment:
|
||||
- error
|
||||
- always
|
||||
operator-linebreak:
|
||||
- error
|
||||
- before
|
||||
quote-props:
|
||||
- error
|
||||
- as-needed
|
||||
quotes:
|
||||
- error
|
||||
- single
|
||||
require-jsdoc:
|
||||
- error
|
||||
- require:
|
||||
FunctionDeclaration: true
|
||||
ClassDeclaration: true
|
||||
MethodDefinition: true
|
||||
semi:
|
||||
- error
|
||||
- always
|
||||
semi-spacing:
|
||||
- error
|
||||
- before: false
|
||||
after: true
|
||||
space-before-blocks:
|
||||
- error
|
||||
space-before-function-paren:
|
||||
- error
|
||||
- never
|
||||
space-in-parens:
|
||||
- error
|
||||
- never
|
||||
space-infix-ops:
|
||||
- error
|
||||
spaced-comment:
|
||||
- error
|
||||
- always
|
||||
unicode-bom:
|
||||
- error
|
||||
|
||||
# ECMAScript 6
|
||||
|
||||
arrow-body-style:
|
||||
- error
|
||||
- always
|
||||
arrow-parens:
|
||||
- error
|
||||
- always
|
||||
arrow-spacing:
|
||||
- error
|
||||
- before: true
|
||||
after: true
|
||||
constructor-super:
|
||||
- error
|
||||
generator-star-spacing:
|
||||
- error
|
||||
- before: true
|
||||
after: false
|
||||
no-class-assign:
|
||||
- error
|
||||
no-confusing-arrow:
|
||||
- error
|
||||
no-const-assign:
|
||||
- error
|
||||
no-dupe-class-members:
|
||||
- error
|
||||
no-duplicate-imports:
|
||||
- error
|
||||
no-new-symbol:
|
||||
- error
|
||||
no-this-before-super:
|
||||
- error
|
||||
no-useless-computed-key:
|
||||
- error
|
||||
no-useless-constructor:
|
||||
- error
|
||||
no-useless-rename:
|
||||
- error
|
||||
no-var:
|
||||
- error
|
||||
prefer-const:
|
||||
- error
|
||||
prefer-reflect:
|
||||
- error
|
||||
prefer-spread:
|
||||
- error
|
||||
require-yield:
|
||||
- error
|
||||
rest-spread-spacing:
|
||||
- error
|
||||
template-curly-spacing:
|
||||
- error
|
||||
- never
|
||||
yield-star-spacing:
|
||||
- error
|
||||
- before: true
|
||||
after: false
|
3
dependencies/mountutils/.gitignore
vendored
Normal file
3
dependencies/mountutils/.gitignore
vendored
Normal file
|
@ -0,0 +1,3 @@
|
|||
node_modules
|
||||
build/
|
||||
prebuilds
|
14
dependencies/mountutils/.npmignore
vendored
Normal file
14
dependencies/mountutils/.npmignore
vendored
Normal file
|
@ -0,0 +1,14 @@
|
|||
# Files
|
||||
.*
|
||||
*.md
|
||||
*.log
|
||||
*.yml
|
||||
|
||||
# Directories
|
||||
doc
|
||||
tests
|
||||
build
|
||||
example
|
||||
benchmark
|
||||
prebuilds
|
||||
scripts
|
46
dependencies/mountutils/.travis.yml
vendored
Normal file
46
dependencies/mountutils/.travis.yml
vendored
Normal file
|
@ -0,0 +1,46 @@
|
|||
language: node_js
|
||||
node_js:
|
||||
- 6
|
||||
- 8
|
||||
- 10
|
||||
- 12
|
||||
os:
|
||||
- linux
|
||||
- osx
|
||||
before_install:
|
||||
- if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then
|
||||
pip install --user -r requirements.txt;
|
||||
else
|
||||
pip2 install -r requirements.txt;
|
||||
fi
|
||||
script:
|
||||
- npm test
|
||||
compiler: clang-4.0
|
||||
env:
|
||||
global:
|
||||
- CCACHE_TEMPDIR=/tmp/.ccache-temp
|
||||
- CCACHE_COMPRESS=1
|
||||
- CC="clang"
|
||||
- CXX="clang++"
|
||||
matrix:
|
||||
- TARGET_ARCH=x64
|
||||
- TARGET_ARCH=x86
|
||||
matrix:
|
||||
exclude:
|
||||
- os: osx
|
||||
env: TARGET_ARCH=x86
|
||||
addons:
|
||||
apt:
|
||||
sources:
|
||||
- ubuntu-toolchain-r-test
|
||||
packages:
|
||||
- libstdc++-5-dev
|
||||
notifications:
|
||||
email: false
|
||||
deploy:
|
||||
skip_cleanup: true
|
||||
provider: script
|
||||
script: scripts/prebuild-publish.sh
|
||||
on:
|
||||
tags: true
|
||||
branch: master
|
155
dependencies/mountutils/CHANGELOG.md
vendored
Normal file
155
dependencies/mountutils/CHANGELOG.md
vendored
Normal file
|
@ -0,0 +1,155 @@
|
|||
# Change Log
|
||||
|
||||
All notable changes to this project will be documented in this file
|
||||
automatically by Versionist. DO NOT EDIT THIS FILE MANUALLY!
|
||||
This project adheres to [Semantic Versioning](http://semver.org/).
|
||||
|
||||
## 1.3.19 - 2019-05-23
|
||||
|
||||
* Housekeeping: add CODEOWNERS [Gergely Imreh]
|
||||
|
||||
## 1.3.18 - 2019-05-23
|
||||
|
||||
* Ci: update Appveyor prebuild-publish script for batch scripting fixes [Gergely Imreh]
|
||||
* Scripts: remove linting from the testing step [Gergely Imreh]
|
||||
* Ci: update appveyor and travis setting for extra Node versions [Gergely Imreh]
|
||||
* Dependencies: Update code to be ready for Node 12 [Gergely Imreh]
|
||||
|
||||
## 1.3.17 - 2019-03-12
|
||||
|
||||
* Remove useless sleeps and wrong debug "Retrying" messages [Alexis Svinartchouk]
|
||||
|
||||
## 1.3.16 - 2019-01-11
|
||||
|
||||
* Fix build on Xcode 10.1 [Alexis Svinartchouk]
|
||||
|
||||
## v1.3.15 - 2018-07-06
|
||||
|
||||
* Fix(linux): Use threadsafe getmntent() #69 [Jonas Hermsmeier]
|
||||
|
||||
## v1.3.14 - 2018-06-19
|
||||
|
||||
* Fix(test): Add python requirements.txt #68 [Jonas Hermsmeier]
|
||||
|
||||
## v1.3.13 - 2018-06-08
|
||||
|
||||
* Upgrade(package): Bump depenencies #67 [Jonas Hermsmeier]
|
||||
|
||||
## v1.3.12 - 2018-05-25
|
||||
|
||||
* Fix(src): Fix nan callback->call deprecation #66 [Jonas Hermsmeier]
|
||||
|
||||
## v1.3.11 - 2018-04-02
|
||||
|
||||
* Upgrade(package): Bump dependencies #65 [Jonas Hermsmeier]
|
||||
|
||||
## v1.3.10 - 2018-01-20
|
||||
|
||||
* Fix(linux): Fix partial unmounts on Linux #64 [Jonas Hermsmeier]
|
||||
|
||||
## v1.3.9 - 2018-01-18
|
||||
|
||||
* Fix(windows): Link to appropriate libraries #63 [Jonas Hermsmeier]
|
||||
|
||||
## v1.3.8 - 2017-12-21
|
||||
|
||||
* Upgrade(package): Bump dependencies #61 [Jonas Hermsmeier]
|
||||
|
||||
## v1.3.7 - 2017-12-14
|
||||
|
||||
* Fix(package): Fix prebuild script being run on build #59 [Jonas Hermsmeier]
|
||||
|
||||
## v1.3.6 - 2017-12-12
|
||||
|
||||
* Test(ci): Fix pip -> pip2 on Travis CI #60 [Jonas Hermsmeier]
|
||||
|
||||
## v1.3.5 - 2017-10-19
|
||||
|
||||
* Chore(scripts): Run CI on x86 as well #58 [Jonas Hermsmeier]
|
||||
|
||||
## v1.3.4 - 2017-10-18
|
||||
|
||||
* Chore(scripts): Also prebuild x86 on Windows and Linux #57 [Jonas Hermsmeier]
|
||||
|
||||
## v1.3.3 - 2017-10-17
|
||||
|
||||
* Upgrade(package): Bump dependencies #56 [Jonas Hermsmeier]
|
||||
|
||||
## v1.3.2 - 2017-10-15
|
||||
|
||||
* Make publishing scripts executable ...hopefully this will fix deploy errors #55 [Andrew Scheller]
|
||||
|
||||
## v1.3.1 - 2017-10-13
|
||||
|
||||
* Fix(scripts): Ensure the Travis box finds bash #52 [Jonas Hermsmeier]
|
||||
* Chore(CHANGELOG): Fix changelog formatting #52 [Jonas Hermsmeier]
|
||||
|
||||
## v1.3.0 - 2017-10-13
|
||||
|
||||
* Chore(ci): Add CI deployment scripts [Jonas Hermsmeier]
|
||||
* Feat(package): Add prebuilds for native bindings [Jonas Hermsmeier]
|
||||
* Chore(appveyor): Use VS2015 base image [Jonas Hermsmeier]
|
||||
* Upgrade(package): Update dependencies [Jonas Hermsmeier]
|
||||
|
||||
## v1.2.2 - 2017-08-28
|
||||
|
||||
### Changed
|
||||
|
||||
- Retry ejection various times before giving up on Windows
|
||||
|
||||
## v1.2.1 - 2017-07-23
|
||||
|
||||
### Changed
|
||||
|
||||
- Retry the unmount operation if the loop times out in macOS
|
||||
|
||||
## v1.2.0 - 2017-05-30
|
||||
|
||||
### Changed
|
||||
|
||||
- Use `Nan::AsyncWorker` to avoid blocking the main thread
|
||||
- Fix global state on Mac OS causing improper return values
|
||||
|
||||
## v1.1.0 - 2017-05-11
|
||||
|
||||
### Changed
|
||||
|
||||
- Add `.eject()` method to unmount & eject a device
|
||||
|
||||
## v1.0.6 - 2017-04-21
|
||||
|
||||
### Changed
|
||||
|
||||
- Fix "Unmount failed" error on Windows when there are network drives.
|
||||
|
||||
## v1.0.5 - 2017-04-14
|
||||
|
||||
### Added
|
||||
|
||||
- Add extra logging when setting `MOUNTUTILS_DEBUG`.
|
||||
|
||||
## v1.0.4 - 2017-04-12
|
||||
|
||||
### Changed
|
||||
|
||||
- Don't throw "invalid drive" for Windows drives that have no logical volumes
|
||||
attached.
|
||||
|
||||
## v1.0.3 - 2017-04-07
|
||||
|
||||
### Changed
|
||||
|
||||
- Make `bindings` correctly resolve the node add-on location even when
|
||||
concatenated with Browserify.
|
||||
|
||||
## v1.0.2 - 2017-03-30
|
||||
|
||||
### Changed
|
||||
|
||||
- Don't include build directory in npm package
|
||||
|
||||
## v1.0.1 - 2017-03-27
|
||||
|
||||
### Changed
|
||||
|
||||
- Fix Windows build failures when installing from npm
|
9
dependencies/mountutils/MANUAL_TESTS.md
vendored
Normal file
9
dependencies/mountutils/MANUAL_TESTS.md
vendored
Normal file
|
@ -0,0 +1,9 @@
|
|||
Manual Tests
|
||||
============
|
||||
|
||||
Windows:
|
||||
|
||||
- [x] SD Card reader
|
||||
- [x] USB Drive
|
||||
- [ ] USB HDD (does nothing)
|
||||
- [x] Multi SD Card reader
|
116
dependencies/mountutils/README.md
vendored
Normal file
116
dependencies/mountutils/README.md
vendored
Normal file
|
@ -0,0 +1,116 @@
|
|||
mountutils
|
||||
==========
|
||||
|
||||
[](https://npmjs.com/package/mountutils)
|
||||
[](https://npmjs.com/package/mountutils)
|
||||
[](https://npmjs.com/package/mountutils)
|
||||
[](https://travis-ci.org/resin-io-modules/mountutils/branches)
|
||||
[](https://ci.appveyor.com/project/resin-io/mountutils/branch/master)
|
||||
|
||||
> Cross platform mount related utilities
|
||||
|
||||
Installation
|
||||
------------
|
||||
|
||||
Install `mountutils` by running:
|
||||
|
||||
```sh
|
||||
$ npm install --save mountutils
|
||||
```
|
||||
|
||||
Debug mode
|
||||
----------
|
||||
|
||||
You can enable debug mode by setting the `MOUNTUTILS_DEBUG` environment
|
||||
variable.
|
||||
|
||||
Documentation
|
||||
-------------
|
||||
|
||||
<a name="module_mountutils.unmountDisk"></a>
|
||||
|
||||
### mountutils.unmountDisk(device, callback)
|
||||
**Kind**: static method of <code>[mountutils](#module_mountutils)</code>
|
||||
**Summary**: Unmount a whole disk
|
||||
**Access**: public
|
||||
|
||||
| Param | Type | Description |
|
||||
| --- | --- | --- |
|
||||
| device | <code>String</code> | device |
|
||||
| callback | <code>function</code> | callback (error) |
|
||||
|
||||
**Example**
|
||||
```js
|
||||
// macOS
|
||||
const drive = '/dev/disk2';
|
||||
|
||||
// GNU/Linux
|
||||
const drive = '/dev/sdb';
|
||||
|
||||
// Windows
|
||||
const drive = '\\\\.\\PHYSICALDRIVE2';
|
||||
|
||||
mountutils.unmountDisk(drive, (error) => {
|
||||
if (error) {
|
||||
throw error;
|
||||
}
|
||||
|
||||
console.log('Done!');
|
||||
});
|
||||
```
|
||||
|
||||
Support
|
||||
-------
|
||||
|
||||
If you're having any problem, please [raise an issue][newissue] on GitHub and
|
||||
the Resin.io team will be happy to help.
|
||||
|
||||
Tests
|
||||
-----
|
||||
|
||||
Run the test suite by doing:
|
||||
|
||||
```sh
|
||||
$ npm test
|
||||
```
|
||||
|
||||
Troubleshooting
|
||||
---------------
|
||||
|
||||
### `error C2373: '__pfnDliNotifyHook2': redefinition`
|
||||
|
||||
This error indicates that the version of npm you're running is too old. Upgrade
|
||||
by running:
|
||||
|
||||
```sh
|
||||
npm install -g npm@latest
|
||||
```
|
||||
|
||||
See the [following `node-gyp` issue](https://github.com/nodejs/node-gyp/issues/972) for more details.
|
||||
|
||||
Contribute
|
||||
----------
|
||||
|
||||
- Issue Tracker: [github.com/resin-io-modules/mountutils/issues][issues]
|
||||
- Source Code: [github.com/resin-io-modules/mountutils][source]
|
||||
|
||||
### Dependencies
|
||||
|
||||
- [cpplint][cpplint]
|
||||
|
||||
Before submitting a PR, please make sure that you include tests, and that
|
||||
linters run without any warning:
|
||||
|
||||
```sh
|
||||
$ npm run lint
|
||||
```
|
||||
|
||||
License
|
||||
-------
|
||||
|
||||
The project is licensed under the Apache 2.0 license.
|
||||
|
||||
[issues]: https://github.com/resin-io-modules/mountutils/issues
|
||||
[newissue]: https://github.com/resin-io-modules/mountutils/issues/new
|
||||
[source]: https://github.com/resin-io-modules/mountutils
|
||||
[cpplint]: https://github.com/cpplint/cpplint
|
40
dependencies/mountutils/appveyor.yml
vendored
Normal file
40
dependencies/mountutils/appveyor.yml
vendored
Normal file
|
@ -0,0 +1,40 @@
|
|||
# appveyor file
|
||||
# http://www.appveyor.com/docs/appveyor-yml
|
||||
image:
|
||||
- Visual Studio 2015
|
||||
|
||||
init:
|
||||
- git config --global core.autocrlf input
|
||||
|
||||
cache:
|
||||
- C:\Users\appveyor\.node-gyp
|
||||
- '%AppData%\npm-cache'
|
||||
|
||||
environment:
|
||||
matrix:
|
||||
- nodejs_version: 6
|
||||
- nodejs_version: 8
|
||||
- nodejs_version: 10
|
||||
- nodejs_version: 12
|
||||
|
||||
platform:
|
||||
- x86
|
||||
- x64
|
||||
|
||||
install:
|
||||
# https://www.appveyor.com/docs/lang/nodejs-iojs/#installing-any-version-of-nodejs-or-iojs
|
||||
- ps: Update-NodeJsInstallation (Get-NodeJsLatestBuild $env:nodejs_version) $env:PLATFORM
|
||||
- npm -g install npm@latest
|
||||
- set PATH=%APPDATA%\npm;%PATH%
|
||||
- npm install --build-from-source
|
||||
- pip install -r requirements.txt
|
||||
|
||||
build: off
|
||||
|
||||
test_script:
|
||||
- node --version
|
||||
- npm --version
|
||||
- cmd: npm test
|
||||
|
||||
deploy_script:
|
||||
- scripts\prebuild-publish.bat
|
66
dependencies/mountutils/binding.gyp
vendored
Normal file
66
dependencies/mountutils/binding.gyp
vendored
Normal file
|
@ -0,0 +1,66 @@
|
|||
{
|
||||
"targets": [
|
||||
{
|
||||
"target_name": "MountUtils",
|
||||
"include_dirs" : [
|
||||
"<!(node -e \"require('nan')\")"
|
||||
],
|
||||
"sources": [
|
||||
"src/mountutils.cpp",
|
||||
"src/worker-unmount.cpp",
|
||||
"src/worker-eject.cpp"
|
||||
],
|
||||
"msvs_settings": {
|
||||
"VCLinkerTool": {
|
||||
"SetChecksum": "true"
|
||||
},
|
||||
"VCCLCompilerTool": {
|
||||
"ExceptionHandling": 1,
|
||||
"AdditionalOptions": [
|
||||
"/EHsc"
|
||||
]
|
||||
}
|
||||
},
|
||||
'conditions': [
|
||||
|
||||
[ 'OS=="linux"', {
|
||||
"sources": [
|
||||
"src/linux/functions.cpp"
|
||||
],
|
||||
} ],
|
||||
|
||||
[ 'OS=="win"', {
|
||||
"sources": [
|
||||
"src/windows/functions.cpp"
|
||||
],
|
||||
"libraries": [
|
||||
"-lKernel32.lib",
|
||||
"-lSetupAPI.lib",
|
||||
"-lCfgmgr32.lib"
|
||||
],
|
||||
} ],
|
||||
|
||||
[ 'OS=="mac"', {
|
||||
"xcode_settings": {
|
||||
"OTHER_CPLUSPLUSFLAGS": [
|
||||
"-stdlib=libc++"
|
||||
],
|
||||
"OTHER_LDFLAGS": [
|
||||
"-stdlib=libc++"
|
||||
]
|
||||
},
|
||||
"sources": [
|
||||
"src/darwin/functions.cpp"
|
||||
],
|
||||
"link_settings": {
|
||||
"libraries": [
|
||||
"DiskArbitration.framework",
|
||||
"Foundation.framework",
|
||||
],
|
||||
},
|
||||
} ],
|
||||
|
||||
],
|
||||
}
|
||||
],
|
||||
}
|
91
dependencies/mountutils/docs/README.hbs
vendored
Normal file
91
dependencies/mountutils/docs/README.hbs
vendored
Normal file
|
@ -0,0 +1,91 @@
|
|||
mountutils
|
||||
==========
|
||||
|
||||
[](https://npmjs.com/package/mountutils)
|
||||
[](https://npmjs.com/package/mountutils)
|
||||
[](https://npmjs.com/package/mountutils)
|
||||
[](https://travis-ci.org/resin-io-modules/mountutils/branches)
|
||||
[](https://ci.appveyor.com/project/resin-io/mountutils/branch/master)
|
||||
|
||||
> Cross platform mount related utilities
|
||||
|
||||
Installation
|
||||
------------
|
||||
|
||||
Install `mountutils` by running:
|
||||
|
||||
```sh
|
||||
$ npm install --save mountutils
|
||||
```
|
||||
|
||||
Debug mode
|
||||
----------
|
||||
|
||||
You can enable debug mode by setting the `MOUNTUTILS_DEBUG` environment
|
||||
variable.
|
||||
|
||||
Documentation
|
||||
-------------
|
||||
|
||||
{{#module name="mountutils"}}
|
||||
{{>body~}}
|
||||
{{>member-index~}}
|
||||
{{>separator~}}
|
||||
{{>members~}}
|
||||
{{/module}}
|
||||
|
||||
Support
|
||||
-------
|
||||
|
||||
If you're having any problem, please [raise an issue][newissue] on GitHub and
|
||||
the Resin.io team will be happy to help.
|
||||
|
||||
Tests
|
||||
-----
|
||||
|
||||
Run the test suite by doing:
|
||||
|
||||
```sh
|
||||
$ npm test
|
||||
```
|
||||
|
||||
Troubleshooting
|
||||
---------------
|
||||
|
||||
### `error C2373: '__pfnDliNotifyHook2': redefinition`
|
||||
|
||||
This error indicates that the version of npm you're running is too old. Upgrade
|
||||
by running:
|
||||
|
||||
```sh
|
||||
npm install -g npm@latest
|
||||
```
|
||||
|
||||
See the [following `node-gyp` issue](https://github.com/nodejs/node-gyp/issues/972) for more details.
|
||||
|
||||
Contribute
|
||||
----------
|
||||
|
||||
- Issue Tracker: [github.com/resin-io-modules/mountutils/issues][issues]
|
||||
- Source Code: [github.com/resin-io-modules/mountutils][source]
|
||||
|
||||
### Dependencies
|
||||
|
||||
- [cpplint][cpplint]
|
||||
|
||||
Before submitting a PR, please make sure that you include tests, and that
|
||||
linters run without any warning:
|
||||
|
||||
```sh
|
||||
$ npm run lint
|
||||
```
|
||||
|
||||
License
|
||||
-------
|
||||
|
||||
The project is licensed under the Apache 2.0 license.
|
||||
|
||||
[issues]: https://github.com/resin-io-modules/mountutils/issues
|
||||
[newissue]: https://github.com/resin-io-modules/mountutils/issues/new
|
||||
[source]: https://github.com/resin-io-modules/mountutils
|
||||
[cpplint]: https://github.com/cpplint/cpplint
|
36
dependencies/mountutils/example/eject.js
vendored
Normal file
36
dependencies/mountutils/example/eject.js
vendored
Normal file
|
@ -0,0 +1,36 @@
|
|||
#!/usr/bin/env node
|
||||
/*
|
||||
* 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 mountUtils = require('..');
|
||||
|
||||
let argv = process.argv.slice(2);
|
||||
let disk = argv.shift();
|
||||
|
||||
if (!disk) {
|
||||
console.error(`Usage: node example/eject <disk>`);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
process.env.MOUNTUTILS_DEBUG = true;
|
||||
|
||||
console.log('Ejecting', disk, '...');
|
||||
|
||||
mountUtils.eject(disk, function(error) {
|
||||
console.log(error || 'OK');
|
||||
});
|
36
dependencies/mountutils/example/unmount.js
vendored
Normal file
36
dependencies/mountutils/example/unmount.js
vendored
Normal file
|
@ -0,0 +1,36 @@
|
|||
#!/usr/bin/env node
|
||||
/*
|
||||
* 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 mountUtils = require('..');
|
||||
|
||||
let argv = process.argv.slice(2);
|
||||
let disk = argv.shift();
|
||||
|
||||
if (!disk) {
|
||||
console.error(`Usage: node example/unmount <disk>`);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
process.env.MOUNTUTILS_DEBUG = true;
|
||||
|
||||
console.log('Unmounting', disk, '...');
|
||||
|
||||
mountUtils.unmountDisk(disk, function(error) {
|
||||
console.log(error || 'OK');
|
||||
});
|
57
dependencies/mountutils/index.js
vendored
Normal file
57
dependencies/mountutils/index.js
vendored
Normal file
|
@ -0,0 +1,57 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @module mountutils
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* @summary Unmount a whole disk
|
||||
* @function
|
||||
* @public
|
||||
* @static
|
||||
* @name unmountDisk
|
||||
*
|
||||
* @param {String} device - device
|
||||
* @param {Function} callback - callback (error)
|
||||
*
|
||||
* @example
|
||||
* // macOS
|
||||
* const drive = '/dev/disk2';
|
||||
*
|
||||
* // GNU/Linux
|
||||
* const drive = '/dev/sdb';
|
||||
*
|
||||
* // Windows
|
||||
* const drive = '\\\\.\\PHYSICALDRIVE2';
|
||||
*
|
||||
* mountutils.unmountDisk(drive, (error) => {
|
||||
* if (error) {
|
||||
* throw error;
|
||||
* }
|
||||
*
|
||||
* console.log('Done!');
|
||||
* });
|
||||
*/
|
||||
|
||||
module.exports = require('bindings')({
|
||||
bindings: 'MountUtils',
|
||||
/* eslint-disable camelcase */
|
||||
module_root: __dirname
|
||||
/* eslint-enable camelcase */
|
||||
});
|
52
dependencies/mountutils/package.json
vendored
Normal file
52
dependencies/mountutils/package.json
vendored
Normal file
|
@ -0,0 +1,52 @@
|
|||
{
|
||||
"name": "mountutils",
|
||||
"version": "1.3.19",
|
||||
"main": "index.js",
|
||||
"description": "Cross platform mount related utilities",
|
||||
"homepage": "https://github.com/resin-io-modules/mountutils",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git://github.com/resin-io-modules/mountutils.git"
|
||||
},
|
||||
"keywords": [
|
||||
"mount",
|
||||
"utils",
|
||||
"unmount",
|
||||
"unmounDisk",
|
||||
"cross",
|
||||
"platform"
|
||||
],
|
||||
"directories": {
|
||||
"test": "tests"
|
||||
},
|
||||
"scripts": {
|
||||
"configure": "node-gyp configure",
|
||||
"build": "node-gyp build",
|
||||
"rebuild": "node-gyp rebuild",
|
||||
"readme": "jsdoc2md --template docs/README.hbs index.js > README.md",
|
||||
"lint-js": "eslint *.js",
|
||||
"lint-cpp": "cpplint --recursive src",
|
||||
"lint": "npm run lint-js && npm run lint-cpp",
|
||||
"test": "mocha tests -R spec",
|
||||
"install": "prebuild-install || node-gyp rebuild",
|
||||
"prebuild-release": "prebuild --all --strip"
|
||||
},
|
||||
"author": "Juan Cruz Viotti <juan@resin.io>",
|
||||
"license": "Apache-2.0",
|
||||
"devDependencies": {
|
||||
"chai": "^4.1.2",
|
||||
"eslint": "^4.19.1",
|
||||
"jsdoc-to-markdown": "^4.0.1",
|
||||
"mocha": "^5.2.0",
|
||||
"prebuild": "^7.6.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"bindings": "^1.3.0",
|
||||
"nan": "^2.14.0",
|
||||
"prebuild-install": "^4.0.0"
|
||||
},
|
||||
"gypfile": true,
|
||||
"bugs": {
|
||||
"url": "https://github.com/resin-io-modules/mountutils/issues"
|
||||
}
|
||||
}
|
1
dependencies/mountutils/requirements.txt
vendored
Normal file
1
dependencies/mountutils/requirements.txt
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
cpplint==1.3.0
|
7
dependencies/mountutils/scripts/prebuild-publish.bat
vendored
Normal file
7
dependencies/mountutils/scripts/prebuild-publish.bat
vendored
Normal file
|
@ -0,0 +1,7 @@
|
|||
@echo off
|
||||
|
||||
IF "%APPVEYOR_REPO_BRANCH%"=="master" (
|
||||
IF "%GITHUB_TOKEN%" NEQ "" (
|
||||
npm run prebuild-release -- -u %GITHUB_TOKEN%
|
||||
)
|
||||
)
|
5
dependencies/mountutils/scripts/prebuild-publish.sh
vendored
Normal file
5
dependencies/mountutils/scripts/prebuild-publish.sh
vendored
Normal file
|
@ -0,0 +1,5 @@
|
|||
#!/bin/bash
|
||||
|
||||
if [[ $GITHUB_TOKEN ]]; then
|
||||
npm run prebuild-release -- -u "$GITHUB_TOKEN"
|
||||
fi
|
177
dependencies/mountutils/src/darwin/functions.cpp
vendored
Normal file
177
dependencies/mountutils/src/darwin/functions.cpp
vendored
Normal file
|
@ -0,0 +1,177 @@
|
|||
/*
|
||||
* 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 <DiskArbitration/DiskArbitration.h>
|
||||
#include "../mountutils.hpp"
|
||||
|
||||
struct RunLoopContext {
|
||||
MOUNTUTILS_RESULT code = MOUNTUTILS_SUCCESS;
|
||||
};
|
||||
|
||||
MOUNTUTILS_RESULT translate_dissenter(DADissenterRef dissenter) {
|
||||
if (dissenter) {
|
||||
DAReturn status = DADissenterGetStatus(dissenter);
|
||||
if (status == kDAReturnBadArgument ||
|
||||
status == kDAReturnNotFound) {
|
||||
return MOUNTUTILS_ERROR_INVALID_DRIVE;
|
||||
} else if (status == kDAReturnNotPermitted ||
|
||||
status == kDAReturnNotPrivileged) {
|
||||
return MOUNTUTILS_ERROR_ACCESS_DENIED;
|
||||
} else {
|
||||
MountUtilsLog("Unknown dissenter status");
|
||||
return MOUNTUTILS_ERROR_GENERAL;
|
||||
}
|
||||
} else {
|
||||
return MOUNTUTILS_SUCCESS;
|
||||
}
|
||||
}
|
||||
|
||||
MOUNTUTILS_RESULT
|
||||
run_cb(const char* device, DADiskUnmountCallback callback, size_t times) {
|
||||
RunLoopContext context;
|
||||
void *ctx = &context;
|
||||
|
||||
// Create a session object
|
||||
MountUtilsLog("Creating DA session");
|
||||
DASessionRef session = DASessionCreate(kCFAllocatorDefault);
|
||||
|
||||
if (session == NULL) {
|
||||
MountUtilsLog("Session couldn't be created");
|
||||
return MOUNTUTILS_ERROR_GENERAL;
|
||||
}
|
||||
|
||||
// Get a disk object from the disk path
|
||||
MountUtilsLog("Getting disk object");
|
||||
DADiskRef disk = DADiskCreateFromBSDName(kCFAllocatorDefault,
|
||||
session, device);
|
||||
|
||||
// Unmount, and then eject from the unmount callback
|
||||
MountUtilsLog("Unmounting");
|
||||
DADiskUnmount(disk,
|
||||
kDADiskUnmountOptionWhole | kDADiskUnmountOptionForce,
|
||||
callback,
|
||||
ctx);
|
||||
|
||||
// Schedule a disk arbitration session
|
||||
MountUtilsLog("Schedule session on run loop");
|
||||
DASessionScheduleWithRunLoop(session,
|
||||
CFRunLoopGetCurrent(),
|
||||
kCFRunLoopDefaultMode);
|
||||
|
||||
// Start the run loop: Run with a timeout of 500ms (0.5s),
|
||||
// and don't terminate after only handling one resource.
|
||||
// NOTE: As the unmount callback gets called *before* the runloop can
|
||||
// be started here when there's no device to be unmounted or
|
||||
// the device has already been unmounted, the loop would
|
||||
// hang indefinitely until stopped manually otherwise.
|
||||
// Here we repeatedly run the loop for a given time, and stop
|
||||
// it at some point if it hasn't gotten anywhere, or if there's
|
||||
// nothing to be unmounted, or a dissent has been caught before the run.
|
||||
// This way we don't have to manage state across callbacks.
|
||||
MountUtilsLog("Starting run loop");
|
||||
|
||||
bool done = false;
|
||||
unsigned int loop_count = 0;
|
||||
|
||||
while (!done) {
|
||||
loop_count++;
|
||||
// See https://developer.apple.com/reference/corefoundation/1541988-cfrunloopruninmode
|
||||
SInt32 status = CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0.5, false);
|
||||
// Stop starting the runloop once it's been manually stopped
|
||||
if ((status == kCFRunLoopRunStopped) || (status == kCFRunLoopRunFinished)) {
|
||||
done = true;
|
||||
}
|
||||
// Bail out if DADiskUnmount caught a dissent and
|
||||
// thus returned before the runloop even started
|
||||
if (context.code != MOUNTUTILS_SUCCESS) {
|
||||
MountUtilsLog("Runloop dry");
|
||||
done = true;
|
||||
}
|
||||
// Bail out if the runloop is timing out, but not getting anywhere
|
||||
if (loop_count > 10) {
|
||||
MountUtilsLog("Runloop stall");
|
||||
context.code = MOUNTUTILS_ERROR_AGAIN;
|
||||
done = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Clean up the session
|
||||
MountUtilsLog("Releasing session & disk object");
|
||||
DASessionUnscheduleFromRunLoop(session,
|
||||
CFRunLoopGetCurrent(),
|
||||
kCFRunLoopDefaultMode);
|
||||
CFRelease(session);
|
||||
|
||||
if (context.code == MOUNTUTILS_ERROR_AGAIN && times < 5) {
|
||||
MountUtilsLog("Retrying...");
|
||||
return run_cb(device, callback, times + 1);
|
||||
}
|
||||
|
||||
MOUNTUTILS_RESULT result = context.code;
|
||||
return result;
|
||||
}
|
||||
|
||||
void _unmount_cb(DADiskRef disk, DADissenterRef dissenter, void *ctx) {
|
||||
MountUtilsLog("[unmount]: Unmount callback");
|
||||
RunLoopContext *context = reinterpret_cast<RunLoopContext*>(ctx);
|
||||
if (dissenter) {
|
||||
MountUtilsLog("[unmount]: Unmount dissenter");
|
||||
context->code = translate_dissenter(dissenter);
|
||||
} else {
|
||||
MountUtilsLog("[unmount]: Unmount success");
|
||||
context->code = MOUNTUTILS_SUCCESS;
|
||||
}
|
||||
CFRunLoopStop(CFRunLoopGetCurrent());
|
||||
}
|
||||
|
||||
void _eject_cb(DADiskRef disk, DADissenterRef dissenter, void *ctx) {
|
||||
MountUtilsLog("[eject]: Eject callback");
|
||||
RunLoopContext *context = reinterpret_cast<RunLoopContext*>(ctx);
|
||||
if (dissenter) {
|
||||
MountUtilsLog("[eject]: Eject dissenter");
|
||||
context->code = translate_dissenter(dissenter);
|
||||
} else {
|
||||
MountUtilsLog("[eject]: Eject success");
|
||||
context->code = MOUNTUTILS_SUCCESS;
|
||||
}
|
||||
CFRunLoopStop(CFRunLoopGetCurrent());
|
||||
}
|
||||
|
||||
void _eject_unmount_cb(DADiskRef disk, DADissenterRef dissenter, void *ctx) {
|
||||
MountUtilsLog("[eject]: Unmount callback");
|
||||
RunLoopContext *context = reinterpret_cast<RunLoopContext*>(ctx);
|
||||
if (dissenter) {
|
||||
MountUtilsLog("[eject]: Unmount dissenter");
|
||||
context->code = translate_dissenter(dissenter);
|
||||
CFRunLoopStop(CFRunLoopGetCurrent());
|
||||
} else {
|
||||
MountUtilsLog("[eject]: Unmount success");
|
||||
MountUtilsLog("[eject]: Ejecting...");
|
||||
context->code = MOUNTUTILS_SUCCESS;
|
||||
DADiskEject(disk,
|
||||
kDADiskEjectOptionDefault,
|
||||
_eject_cb,
|
||||
ctx);
|
||||
}
|
||||
}
|
||||
|
||||
MOUNTUTILS_RESULT unmount_disk(const char* device) {
|
||||
return run_cb(device, _unmount_cb, 0);
|
||||
}
|
||||
|
||||
MOUNTUTILS_RESULT eject_disk(const char* device) {
|
||||
return run_cb(device, _eject_unmount_cb, 0);
|
||||
}
|
148
dependencies/mountutils/src/linux/functions.cpp
vendored
Normal file
148
dependencies/mountutils/src/linux/functions.cpp
vendored
Normal file
|
@ -0,0 +1,148 @@
|
|||
/*
|
||||
* 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 <sys/stat.h>
|
||||
#include <sys/mount.h>
|
||||
#include <mntent.h>
|
||||
#include <errno.h>
|
||||
#include "../mountutils.hpp"
|
||||
|
||||
MOUNTUTILS_RESULT unmount_disk(const char *device_path) {
|
||||
const char *mount_path = NULL;
|
||||
std::vector<std::string> mount_dirs = {};
|
||||
|
||||
// Stat the device to make sure it exists
|
||||
struct stat stats;
|
||||
|
||||
if (stat(device_path, &stats) != 0) {
|
||||
MountUtilsLog("Stat failed");
|
||||
|
||||
// TODO(jhermsmeier): See TODO below
|
||||
// v8::Local<v8::Value> argv[1] = {
|
||||
// Nan::ErrnoException(errno, "stat", NULL, device_path)
|
||||
// };
|
||||
// Nan::MakeCallback(Nan::GetCurrentContext()->Global(), callback, 1, argv);
|
||||
return MOUNTUTILS_ERROR_GENERAL;
|
||||
} else if (S_ISDIR(stats.st_mode)) {
|
||||
MountUtilsLog("Device is a directory");
|
||||
|
||||
// TODO(jhermsmeier): See TODO below
|
||||
// v8::Local<v8::Value> argv[1] = {
|
||||
// Nan::Error("Invalid device, path is a directory")
|
||||
// };
|
||||
// Nan::MakeCallback(Nan::GetCurrentContext()->Global(), callback, 1, argv);
|
||||
return MOUNTUTILS_ERROR_INVALID_DRIVE;
|
||||
}
|
||||
|
||||
// Get mountpaths from the device path, as `umount(device)`
|
||||
// has been removed in Linux 2.3+
|
||||
struct mntent *mnt_p, data;
|
||||
// See https://github.com/RasPlex/aufs-utils/commit/2d1a37468cdc1f9c779cbf22267c5ae491a44f8e
|
||||
char mnt_buf[4096 + 1024];
|
||||
FILE *proc_mounts;
|
||||
|
||||
MountUtilsLog("Reading /proc/mounts");
|
||||
proc_mounts = setmntent("/proc/mounts", "r");
|
||||
|
||||
if (proc_mounts == NULL) {
|
||||
MountUtilsLog("Couldn't read /proc/mounts");
|
||||
// TODO(jhermsmeier): Refactor MOUNTUTILS_RESULT into a struct
|
||||
// with error_msg, errno, error_code etc. and set the respective
|
||||
// values on the struct and move creation of proper errors with
|
||||
// the right errno messages etc. into the AsyncWorkers (even better:
|
||||
// create a function which creates the proper error from a
|
||||
// MOUNTUTILS_RESULT struct).
|
||||
// v8::Local<v8::Value> argv[1] = {
|
||||
// Nan::ErrnoException(errno, "setmntent", NULL, "/proc/mounts")
|
||||
// };
|
||||
// Nan::MakeCallback(Nan::GetCurrentContext()->Global(), callback, 1, argv);
|
||||
return MOUNTUTILS_ERROR_GENERAL;
|
||||
}
|
||||
|
||||
while ((mnt_p = getmntent_r(proc_mounts, &data, mnt_buf, sizeof(mnt_buf)))) {
|
||||
mount_path = mnt_p->mnt_fsname;
|
||||
if (strncmp(mount_path, device_path, strlen(device_path)) == 0) {
|
||||
MountUtilsLog("Mount point " + std::string(mount_path) +
|
||||
" belongs to drive " + std::string(device_path));
|
||||
mount_dirs.push_back(std::string(mnt_p->mnt_dir));
|
||||
}
|
||||
}
|
||||
|
||||
MountUtilsLog("Closing /proc/mounts");
|
||||
endmntent(proc_mounts);
|
||||
|
||||
// Use umount2() with the MNT_DETACH flag, which performs a lazy unmount;
|
||||
// making the mount point unavailable for new accesses,
|
||||
// and only actually unmounting when the mount point ceases to be busy
|
||||
// TODO(jhermsmeier): See TODO above
|
||||
// v8::Local<v8::Value> argv[1] = {
|
||||
// Nan::ErrnoException(errno, "umount2", NULL, mnt_p->mnt_dir)
|
||||
// };
|
||||
// v8::Local<v8::Object> ctx = Nan::GetCurrentContext()->Global();
|
||||
// Nan::MakeCallback(ctx, callback, 1, argv);
|
||||
|
||||
size_t unmounts = 0;
|
||||
MOUNTUTILS_RESULT result_code = MOUNTUTILS_SUCCESS;
|
||||
|
||||
for (std::string mount_dir : mount_dirs) {
|
||||
MountUtilsLog("Unmounting " + mount_dir + "...");
|
||||
|
||||
mount_path = mount_dir.c_str();
|
||||
|
||||
if (umount2(mount_path, MNT_EXPIRE) != 0) {
|
||||
MountUtilsLog("Unmount MNT_EXPIRE " + mount_dir + ": EAGAIN");
|
||||
if (umount2(mount_path, MNT_EXPIRE) != 0) {
|
||||
MountUtilsLog("Unmount MNT_EXPIRE " + mount_dir + " failed: " +
|
||||
std::string(strerror(errno)));
|
||||
} else {
|
||||
MountUtilsLog("Unmount " + mount_dir + ": success");
|
||||
unmounts++;
|
||||
continue;
|
||||
}
|
||||
} else {
|
||||
MountUtilsLog("Unmount " + mount_dir + ": success");
|
||||
unmounts++;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (umount2(mount_path, MNT_DETACH) != 0) {
|
||||
MountUtilsLog("Unmount MNT_DETACH " + mount_dir + " failed: " +
|
||||
std::string(strerror(errno)));
|
||||
} else {
|
||||
MountUtilsLog("Unmount " + mount_dir + ": success");
|
||||
unmounts++;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (umount2(mount_path, MNT_FORCE) != 0) {
|
||||
MountUtilsLog("Unmount MNT_FORCE " + mount_dir + " failed: " +
|
||||
std::string(strerror(errno)));
|
||||
} else {
|
||||
MountUtilsLog("Unmount " + mount_dir + ": success");
|
||||
unmounts++;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
return unmounts == mount_dirs.size() ?
|
||||
MOUNTUTILS_SUCCESS : MOUNTUTILS_ERROR_GENERAL;
|
||||
}
|
||||
|
||||
// FIXME: This is just a stub copy of `UnmountDisk()`,
|
||||
// and needs implementation!
|
||||
MOUNTUTILS_RESULT eject_disk(const char *device) {
|
||||
return unmount_disk(device);
|
||||
}
|
33
dependencies/mountutils/src/mountutils.cpp
vendored
Normal file
33
dependencies/mountutils/src/mountutils.cpp
vendored
Normal file
|
@ -0,0 +1,33 @@
|
|||
/*
|
||||
* 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 <cstdlib>
|
||||
#include <iostream>
|
||||
#include "mountutils.hpp"
|
||||
|
||||
NAN_MODULE_INIT(MountUtilsInit) {
|
||||
NAN_EXPORT(target, unmountDisk);
|
||||
NAN_EXPORT(target, eject);
|
||||
}
|
||||
|
||||
void MountUtilsLog(std::string string) {
|
||||
const char* debug = std::getenv("MOUNTUTILS_DEBUG");
|
||||
if (debug != NULL) {
|
||||
std::cout << "[mountutils] " << string << std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
NODE_MODULE(MountUtils, MountUtilsInit)
|
39
dependencies/mountutils/src/mountutils.hpp
vendored
Normal file
39
dependencies/mountutils/src/mountutils.hpp
vendored
Normal file
|
@ -0,0 +1,39 @@
|
|||
#ifndef SRC_MOUNTUTILS_HPP_
|
||||
#define SRC_MOUNTUTILS_HPP_
|
||||
|
||||
/*
|
||||
* 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>
|
||||
|
||||
enum MOUNTUTILS_RESULT {
|
||||
MOUNTUTILS_SUCCESS,
|
||||
MOUNTUTILS_ERROR_INVALID_DRIVE,
|
||||
MOUNTUTILS_ERROR_ACCESS_DENIED,
|
||||
MOUNTUTILS_ERROR_AGAIN,
|
||||
MOUNTUTILS_ERROR_GENERAL
|
||||
};
|
||||
|
||||
void MountUtilsLog(std::string string);
|
||||
|
||||
MOUNTUTILS_RESULT unmount_disk(const char *device);
|
||||
MOUNTUTILS_RESULT eject_disk(const char *device);
|
||||
|
||||
NAN_METHOD(unmountDisk);
|
||||
NAN_METHOD(eject);
|
||||
|
||||
#endif // SRC_MOUNTUTILS_HPP_
|
557
dependencies/mountutils/src/windows/functions.cpp
vendored
Normal file
557
dependencies/mountutils/src/windows/functions.cpp
vendored
Normal file
|
@ -0,0 +1,557 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
// Adapted from https://support.microsoft.com/en-us/kb/165721
|
||||
|
||||
#ifndef WIN32_LEAN_AND_MEAN
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
#endif
|
||||
|
||||
#include <windows.h>
|
||||
#include <winioctl.h>
|
||||
#include <tchar.h>
|
||||
#include <stdio.h>
|
||||
#include <cfgmgr32.h>
|
||||
#include <setupapi.h>
|
||||
#include "../mountutils.hpp"
|
||||
|
||||
HANDLE CreateVolumeHandleFromDevicePath(LPCTSTR devicePath, DWORD flags) {
|
||||
return CreateFile(devicePath,
|
||||
flags,
|
||||
FILE_SHARE_READ | FILE_SHARE_WRITE,
|
||||
NULL,
|
||||
OPEN_EXISTING,
|
||||
0,
|
||||
NULL);
|
||||
}
|
||||
|
||||
HANDLE CreateVolumeHandleFromDriveLetter(TCHAR driveLetter, DWORD flags) {
|
||||
TCHAR devicePath[8];
|
||||
sprintf_s(devicePath, "\\\\.\\%c:", driveLetter);
|
||||
return CreateVolumeHandleFromDevicePath(devicePath, flags);
|
||||
}
|
||||
|
||||
ULONG GetDeviceNumberFromVolumeHandle(HANDLE volume) {
|
||||
STORAGE_DEVICE_NUMBER storageDeviceNumber;
|
||||
DWORD bytesReturned;
|
||||
|
||||
BOOL result = DeviceIoControl(volume,
|
||||
IOCTL_STORAGE_GET_DEVICE_NUMBER,
|
||||
NULL, 0,
|
||||
&storageDeviceNumber,
|
||||
sizeof(storageDeviceNumber),
|
||||
&bytesReturned,
|
||||
NULL);
|
||||
|
||||
if (!result) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return storageDeviceNumber.DeviceNumber;
|
||||
}
|
||||
|
||||
BOOL IsDriveFixed(TCHAR driveLetter) {
|
||||
TCHAR rootName[5];
|
||||
sprintf_s(rootName, "%c:\\", driveLetter);
|
||||
return GetDriveType(rootName) == DRIVE_FIXED;
|
||||
}
|
||||
|
||||
BOOL LockVolume(HANDLE volume) {
|
||||
DWORD bytesReturned;
|
||||
|
||||
for (size_t tries = 0; tries < 20; tries++) {
|
||||
if (DeviceIoControl(volume,
|
||||
FSCTL_LOCK_VOLUME,
|
||||
NULL, 0,
|
||||
NULL, 0,
|
||||
&bytesReturned,
|
||||
NULL)) {
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
Sleep(500);
|
||||
}
|
||||
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
// Adapted from https://www.codeproject.com/articles/13839/how-to-prepare-a-usb-drive-for-safe-removal
|
||||
// which is licensed under "The Code Project Open License (CPOL) 1.02"
|
||||
// https://www.codeproject.com/info/cpol10.aspx
|
||||
DEVINST GetDeviceInstanceFromDeviceNumber(ULONG deviceNumber) {
|
||||
const GUID* guid = reinterpret_cast<const GUID *>(&GUID_DEVINTERFACE_DISK);
|
||||
|
||||
// Get device interface info set handle for all devices attached to system
|
||||
DWORD deviceInformationFlags = DIGCF_PRESENT | DIGCF_DEVICEINTERFACE;
|
||||
HDEVINFO deviceInformation = SetupDiGetClassDevs(guid,
|
||||
NULL, NULL,
|
||||
deviceInformationFlags);
|
||||
|
||||
if (deviceInformation == INVALID_HANDLE_VALUE) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
DWORD memberIndex = 0;
|
||||
BYTE buffer[1024];
|
||||
|
||||
PSP_DEVICE_INTERFACE_DETAIL_DATA deviceInterfaceDetailData =
|
||||
(PSP_DEVICE_INTERFACE_DETAIL_DATA)buffer;
|
||||
|
||||
SP_DEVINFO_DATA deviceInformationData;
|
||||
DWORD requiredSize;
|
||||
|
||||
SP_DEVICE_INTERFACE_DATA deviceInterfaceData;
|
||||
deviceInterfaceData.cbSize = sizeof(SP_DEVICE_INTERFACE_DATA);
|
||||
|
||||
while (true) {
|
||||
if (!SetupDiEnumDeviceInterfaces(deviceInformation,
|
||||
NULL,
|
||||
guid,
|
||||
memberIndex,
|
||||
&deviceInterfaceData)) {
|
||||
break;
|
||||
}
|
||||
|
||||
requiredSize = 0;
|
||||
SetupDiGetDeviceInterfaceDetail(deviceInformation,
|
||||
&deviceInterfaceData,
|
||||
NULL, 0,
|
||||
&requiredSize, NULL);
|
||||
|
||||
if (requiredSize == 0 || requiredSize > sizeof(buffer)) {
|
||||
memberIndex++;
|
||||
continue;
|
||||
}
|
||||
|
||||
deviceInterfaceDetailData->cbSize = sizeof(SP_DEVICE_INTERFACE_DETAIL_DATA);
|
||||
|
||||
ZeroMemory((PVOID)&deviceInformationData, sizeof(SP_DEVINFO_DATA));
|
||||
deviceInformationData.cbSize = sizeof(SP_DEVINFO_DATA);
|
||||
|
||||
BOOL result = SetupDiGetDeviceInterfaceDetail(deviceInformation,
|
||||
&deviceInterfaceData,
|
||||
deviceInterfaceDetailData,
|
||||
sizeof(buffer),
|
||||
&requiredSize,
|
||||
&deviceInformationData);
|
||||
|
||||
if (!result) {
|
||||
memberIndex++;
|
||||
continue;
|
||||
}
|
||||
|
||||
LPCTSTR devicePath = deviceInterfaceDetailData->DevicePath;
|
||||
HANDLE driveHandle = CreateVolumeHandleFromDevicePath(devicePath, 0);
|
||||
|
||||
if (driveHandle == INVALID_HANDLE_VALUE) {
|
||||
memberIndex++;
|
||||
continue;
|
||||
}
|
||||
|
||||
ULONG currentDriveDeviceNumber =
|
||||
GetDeviceNumberFromVolumeHandle(driveHandle);
|
||||
|
||||
CloseHandle(driveHandle);
|
||||
|
||||
if (!currentDriveDeviceNumber) {
|
||||
memberIndex++;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (deviceNumber == currentDriveDeviceNumber) {
|
||||
SetupDiDestroyDeviceInfoList(deviceInformation);
|
||||
return deviceInformationData.DevInst;
|
||||
}
|
||||
|
||||
memberIndex++;
|
||||
}
|
||||
|
||||
SetupDiDestroyDeviceInfoList(deviceInformation);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
BOOL UnlockVolume(HANDLE volume) {
|
||||
DWORD bytesReturned;
|
||||
|
||||
return DeviceIoControl(volume,
|
||||
FSCTL_UNLOCK_VOLUME,
|
||||
NULL, 0,
|
||||
NULL, 0,
|
||||
&bytesReturned,
|
||||
NULL);
|
||||
}
|
||||
|
||||
BOOL DismountVolume(HANDLE volume) {
|
||||
DWORD bytesReturned;
|
||||
|
||||
return DeviceIoControl(volume,
|
||||
FSCTL_DISMOUNT_VOLUME,
|
||||
NULL, 0,
|
||||
NULL, 0,
|
||||
&bytesReturned,
|
||||
NULL);
|
||||
}
|
||||
|
||||
BOOL IsVolumeMounted(HANDLE volume) {
|
||||
DWORD bytesReturned;
|
||||
|
||||
return DeviceIoControl(volume,
|
||||
FSCTL_IS_VOLUME_MOUNTED,
|
||||
NULL, 0,
|
||||
NULL, 0,
|
||||
&bytesReturned,
|
||||
NULL);
|
||||
}
|
||||
|
||||
BOOL EjectRemovableVolume(HANDLE volume) {
|
||||
DWORD bytesReturned;
|
||||
PREVENT_MEDIA_REMOVAL buffer;
|
||||
buffer.PreventMediaRemoval = FALSE;
|
||||
BOOL result = DeviceIoControl(volume,
|
||||
IOCTL_STORAGE_MEDIA_REMOVAL,
|
||||
&buffer, sizeof(PREVENT_MEDIA_REMOVAL),
|
||||
NULL, 0,
|
||||
&bytesReturned,
|
||||
NULL);
|
||||
|
||||
if (!result) {
|
||||
MountUtilsLog("Couldn't prevent media removal");
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
for (size_t tries = 0; tries < 5; tries++) {
|
||||
if (tries != 0) {
|
||||
MountUtilsLog("Retrying ejection");
|
||||
Sleep(500);
|
||||
}
|
||||
const BOOL result = DeviceIoControl(volume,
|
||||
IOCTL_STORAGE_EJECT_MEDIA,
|
||||
NULL, 0,
|
||||
NULL, 0,
|
||||
&bytesReturned,
|
||||
NULL);
|
||||
if (result) {
|
||||
MountUtilsLog("Volume ejected");
|
||||
return TRUE;
|
||||
}
|
||||
}
|
||||
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
MOUNTUTILS_RESULT EjectFixedDriveByDeviceNumber(ULONG deviceNumber) {
|
||||
DEVINST deviceInstance = GetDeviceInstanceFromDeviceNumber(deviceNumber);
|
||||
if (!deviceInstance) {
|
||||
MountUtilsLog("Couldn't get instance from device number");
|
||||
return MOUNTUTILS_ERROR_GENERAL;
|
||||
}
|
||||
|
||||
CONFIGRET status;
|
||||
PNP_VETO_TYPE vetoType = PNP_VetoTypeUnknown;
|
||||
char vetoName[MAX_PATH];
|
||||
|
||||
// It's often seen that the removal fails on the first
|
||||
// attempt but works on the second attempt.
|
||||
// See https://www.codeproject.com/articles/13839/how-to-prepare-a-usb-drive-for-safe-removal
|
||||
for (size_t tries = 0; tries < 3; tries++) {
|
||||
if (tries != 0) {
|
||||
MountUtilsLog("Retrying");
|
||||
Sleep(500);
|
||||
}
|
||||
MountUtilsLog("Ejecting device instance");
|
||||
status = CM_Request_Device_Eject(deviceInstance,
|
||||
&vetoType,
|
||||
vetoName,
|
||||
MAX_PATH,
|
||||
0);
|
||||
|
||||
if (status == CR_SUCCESS) {
|
||||
MountUtilsLog("Ejected device instance successfully");
|
||||
return MOUNTUTILS_SUCCESS;
|
||||
}
|
||||
|
||||
MountUtilsLog("Ejecting was vetoed");
|
||||
|
||||
// We use this as an indicator that the device driver
|
||||
// is not setting the `SurpriseRemovalOK` capability.
|
||||
// See https://msdn.microsoft.com/en-us/library/windows/hardware/ff539722(v=vs.85).aspx
|
||||
if (status == CR_REMOVE_VETOED &&
|
||||
vetoType == PNP_VetoIllegalDeviceRequest) {
|
||||
MountUtilsLog("Removing subtree");
|
||||
status = CM_Query_And_Remove_SubTree(deviceInstance,
|
||||
&vetoType,
|
||||
vetoName,
|
||||
MAX_PATH,
|
||||
|
||||
// We have to add the `CM_REMOVE_NO_RESTART` flag because
|
||||
// otherwise the just-removed device may be immediately
|
||||
// redetected, which might happen on XP and Vista.
|
||||
// See https://www.codeproject.com/articles/13839/how-to-prepare-a-usb-drive-for-safe-removal
|
||||
|
||||
CM_REMOVE_NO_RESTART);
|
||||
|
||||
if (status == CR_ACCESS_DENIED) {
|
||||
return MOUNTUTILS_ERROR_ACCESS_DENIED;
|
||||
}
|
||||
|
||||
if (status == CR_SUCCESS) {
|
||||
return MOUNTUTILS_SUCCESS;
|
||||
}
|
||||
|
||||
MountUtilsLog("Couldn't eject device instance");
|
||||
return MOUNTUTILS_ERROR_GENERAL;
|
||||
}
|
||||
}
|
||||
|
||||
return MOUNTUTILS_ERROR_GENERAL;
|
||||
}
|
||||
|
||||
MOUNTUTILS_RESULT EjectDriveLetter(TCHAR driveLetter) {
|
||||
DWORD volumeFlags = GENERIC_READ | GENERIC_WRITE;
|
||||
HANDLE volumeHandle = CreateVolumeHandleFromDriveLetter(driveLetter,
|
||||
volumeFlags);
|
||||
|
||||
MountUtilsLog("Creating volume handle");
|
||||
|
||||
if (volumeHandle == INVALID_HANDLE_VALUE) {
|
||||
MountUtilsLog("Couldn't create volume handle");
|
||||
return MOUNTUTILS_ERROR_INVALID_DRIVE;
|
||||
}
|
||||
|
||||
// Don't proceed if the volume is not mounted
|
||||
if (!IsVolumeMounted(volumeHandle)) {
|
||||
MountUtilsLog("Volume is not mounted");
|
||||
|
||||
if (!CloseHandle(volumeHandle)) {
|
||||
MountUtilsLog("Couldn't close volume handle");
|
||||
return MOUNTUTILS_ERROR_GENERAL;
|
||||
}
|
||||
|
||||
return MOUNTUTILS_SUCCESS;
|
||||
}
|
||||
|
||||
if (IsDriveFixed(driveLetter)) {
|
||||
MountUtilsLog("Drive is fixed");
|
||||
|
||||
ULONG deviceNumber = GetDeviceNumberFromVolumeHandle(volumeHandle);
|
||||
if (!deviceNumber) {
|
||||
MountUtilsLog("Couldn't get device number from volume handle");
|
||||
CloseHandle(volumeHandle);
|
||||
return MOUNTUTILS_ERROR_GENERAL;
|
||||
}
|
||||
|
||||
if (!CloseHandle(volumeHandle)) {
|
||||
MountUtilsLog("Couldn't close volume handle");
|
||||
return MOUNTUTILS_ERROR_GENERAL;
|
||||
}
|
||||
|
||||
MountUtilsLog("Ejecting fixed drive");
|
||||
return EjectFixedDriveByDeviceNumber(deviceNumber);
|
||||
}
|
||||
|
||||
MountUtilsLog("Locking volume");
|
||||
|
||||
if (!LockVolume(volumeHandle)) {
|
||||
MountUtilsLog("Couldn't lock volume");
|
||||
CloseHandle(volumeHandle);
|
||||
return MOUNTUTILS_ERROR_GENERAL;
|
||||
}
|
||||
|
||||
MountUtilsLog("Dismounting volume");
|
||||
|
||||
if (!DismountVolume(volumeHandle)) {
|
||||
MountUtilsLog("Couldn't dismount volume");
|
||||
CloseHandle(volumeHandle);
|
||||
return MOUNTUTILS_ERROR_GENERAL;
|
||||
}
|
||||
|
||||
MountUtilsLog("Ejecting volume");
|
||||
|
||||
if (!EjectRemovableVolume(volumeHandle)) {
|
||||
MountUtilsLog("Couldn't eject volume");
|
||||
CloseHandle(volumeHandle);
|
||||
return MOUNTUTILS_ERROR_GENERAL;
|
||||
}
|
||||
|
||||
MountUtilsLog("Unlocking volume");
|
||||
|
||||
if (!UnlockVolume(volumeHandle)) {
|
||||
MountUtilsLog("Couldn't unlock volume");
|
||||
CloseHandle(volumeHandle);
|
||||
return MOUNTUTILS_ERROR_GENERAL;
|
||||
}
|
||||
|
||||
MountUtilsLog("Closing volume handle");
|
||||
|
||||
if (!CloseHandle(volumeHandle)) {
|
||||
MountUtilsLog("Couldn't close volume handle");
|
||||
return MOUNTUTILS_ERROR_GENERAL;
|
||||
}
|
||||
|
||||
return MOUNTUTILS_SUCCESS;
|
||||
}
|
||||
|
||||
BOOL IsDriveEjectable(TCHAR driveLetter) {
|
||||
TCHAR devicePath[8];
|
||||
sprintf_s(devicePath, "%c:\\", driveLetter);
|
||||
|
||||
MountUtilsLog("Checking whether drive is ejectable: "
|
||||
+ std::string(1, driveLetter));
|
||||
|
||||
switch (GetDriveType(devicePath)) {
|
||||
case DRIVE_NO_ROOT_DIR:
|
||||
MountUtilsLog("The drive doesn't exist");
|
||||
return FALSE;
|
||||
case DRIVE_REMOVABLE:
|
||||
MountUtilsLog("The drive is removable");
|
||||
return TRUE;
|
||||
case DRIVE_FIXED:
|
||||
MountUtilsLog("The drive is fixed");
|
||||
return TRUE;
|
||||
case DRIVE_REMOTE:
|
||||
MountUtilsLog("The drive is remote");
|
||||
return FALSE;
|
||||
case DRIVE_CDROM:
|
||||
MountUtilsLog("The drive is a CDROM");
|
||||
return FALSE;
|
||||
case DRIVE_RAMDISK:
|
||||
MountUtilsLog("The drive is a RAM disk");
|
||||
return FALSE;
|
||||
default:
|
||||
MountUtilsLog("The drive type is unknown");
|
||||
return FALSE;
|
||||
}
|
||||
}
|
||||
|
||||
MOUNTUTILS_RESULT Eject(ULONG deviceNumber) {
|
||||
DWORD logicalDrivesMask = GetLogicalDrives();
|
||||
TCHAR currentDriveLetter = 'A';
|
||||
|
||||
if (logicalDrivesMask == 0) {
|
||||
MountUtilsLog("Couldn't get logical drives");
|
||||
return MOUNTUTILS_ERROR_GENERAL;
|
||||
}
|
||||
|
||||
while (logicalDrivesMask) {
|
||||
if (logicalDrivesMask & 1 && IsDriveEjectable(currentDriveLetter)) {
|
||||
MountUtilsLog("Opening drive letter handle: "
|
||||
+ std::string(1, currentDriveLetter));
|
||||
|
||||
HANDLE driveHandle =
|
||||
CreateVolumeHandleFromDriveLetter(currentDriveLetter, 0);
|
||||
|
||||
if (driveHandle == INVALID_HANDLE_VALUE) {
|
||||
MountUtilsLog("Couldn't open drive letter handle");
|
||||
return MOUNTUTILS_ERROR_GENERAL;
|
||||
}
|
||||
|
||||
ULONG currentDeviceNumber = GetDeviceNumberFromVolumeHandle(driveHandle);
|
||||
|
||||
MountUtilsLog("Closing drive letter handle");
|
||||
|
||||
if (!CloseHandle(driveHandle)) {
|
||||
MountUtilsLog("Couldn't close drive letter handle");
|
||||
return MOUNTUTILS_ERROR_GENERAL;
|
||||
}
|
||||
|
||||
if (currentDeviceNumber == deviceNumber) {
|
||||
MountUtilsLog("Drive letter device matches");
|
||||
MOUNTUTILS_RESULT result;
|
||||
|
||||
// Retry ejecting 3 times, since I've seen that in some systems
|
||||
// the filesystem is ejected, but the drive letter remains assigned,
|
||||
// which gets fixed if you retry again.
|
||||
for (size_t times = 0; times < 3; times++) {
|
||||
if (times != 0) {
|
||||
MountUtilsLog("Retrying");
|
||||
Sleep(500);
|
||||
}
|
||||
MountUtilsLog("Ejecting drive letter");
|
||||
result = EjectDriveLetter(currentDriveLetter);
|
||||
|
||||
// Abort the loop if we couldn't open a handle on the drive letter
|
||||
// after previous attempts worked, since this means the drive was
|
||||
// completely ejected, and that we don't have to keep retrying.
|
||||
if (times > 0 && result == MOUNTUTILS_ERROR_INVALID_DRIVE) {
|
||||
MountUtilsLog("Drive letter has already been ejected");
|
||||
break;
|
||||
}
|
||||
|
||||
if (result != MOUNTUTILS_SUCCESS) {
|
||||
MountUtilsLog("Couldn't eject drive letter");
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
MountUtilsLog("Continuing with the next available letter");
|
||||
}
|
||||
|
||||
currentDriveLetter++;
|
||||
logicalDrivesMask >>= 1;
|
||||
}
|
||||
|
||||
return MOUNTUTILS_SUCCESS;
|
||||
}
|
||||
|
||||
// From http://stackoverflow.com/a/12923949
|
||||
MOUNTUTILS_RESULT stringToInteger(char *string, int *out) {
|
||||
if (string[0] == '\0' || isspace((unsigned char) string[0])) {
|
||||
return MOUNTUTILS_ERROR_GENERAL;
|
||||
}
|
||||
|
||||
char *end;
|
||||
errno = 0;
|
||||
int result = strtol(string, &end, 10);
|
||||
|
||||
if (result > INT_MAX || result < INT_MIN ||
|
||||
(errno == ERANGE && result == LONG_MAX) ||
|
||||
(errno == ERANGE && result == LONG_MIN)) {
|
||||
return MOUNTUTILS_ERROR_GENERAL;
|
||||
}
|
||||
|
||||
if (*end != '\0') {
|
||||
return MOUNTUTILS_ERROR_GENERAL;
|
||||
}
|
||||
|
||||
*out = result;
|
||||
|
||||
return MOUNTUTILS_SUCCESS;
|
||||
}
|
||||
|
||||
MOUNTUTILS_RESULT unmount_disk(const char *device) {
|
||||
int deviceId;
|
||||
char prefix[18];
|
||||
|
||||
MountUtilsLog(std::string(device));
|
||||
|
||||
int result = sscanf(device, "%17s%i", prefix, &deviceId);
|
||||
|
||||
// Return value of `sscanf` is the number of receiving arguments
|
||||
// successfully assigned; and `0` or `EOF` in case of failure
|
||||
if (result != 2 || result == EOF) {
|
||||
return MOUNTUTILS_ERROR_INVALID_DRIVE;
|
||||
}
|
||||
|
||||
return Eject(deviceId);
|
||||
}
|
||||
|
||||
// FIXME: This is just a stub copy of `UnmountDisk()`,
|
||||
// and needs implementation!
|
||||
MOUNTUTILS_RESULT eject_disk(const char *device) {
|
||||
return unmount_disk(device);
|
||||
}
|
74
dependencies/mountutils/src/worker-eject.cpp
vendored
Normal file
74
dependencies/mountutils/src/worker-eject.cpp
vendored
Normal file
|
@ -0,0 +1,74 @@
|
|||
/*
|
||||
* 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 "mountutils.hpp"
|
||||
|
||||
class EjectWorker : public Nan::AsyncWorker {
|
||||
public:
|
||||
EjectWorker(Nan::Callback *callback, std::string device)
|
||||
: Nan::AsyncWorker(callback) {
|
||||
device_path = device;
|
||||
}
|
||||
|
||||
~EjectWorker() {}
|
||||
|
||||
void Execute() {
|
||||
MOUNTUTILS_RESULT result = eject_disk(device_path.c_str());
|
||||
|
||||
MountUtilsLog("Eject complete");
|
||||
|
||||
if (result != MOUNTUTILS_SUCCESS) {
|
||||
switch (result) {
|
||||
case MOUNTUTILS_ERROR_ACCESS_DENIED:
|
||||
SetErrorMessage("Eject failed, access denied");
|
||||
break;
|
||||
case MOUNTUTILS_ERROR_INVALID_DRIVE:
|
||||
SetErrorMessage("Eject failed, invalid drive");
|
||||
break;
|
||||
default:
|
||||
SetErrorMessage("Eject failed");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void HandleOKCallback() {
|
||||
Nan::HandleScope scope;
|
||||
v8::Local<v8::Value> argv[] = { Nan::Null() };
|
||||
callback->Call(1, argv, async_resource);
|
||||
}
|
||||
|
||||
private:
|
||||
std::string device_path;
|
||||
};
|
||||
|
||||
NAN_METHOD(eject) {
|
||||
if (!info[1]->IsFunction()) {
|
||||
return Nan::ThrowError("Callback must be a function");
|
||||
}
|
||||
|
||||
if (!info[0]->IsString()) {
|
||||
return Nan::ThrowError("Device must be a string");
|
||||
}
|
||||
|
||||
Nan::Utf8String device(Nan::To<v8::String>(info[0]).ToLocalChecked());
|
||||
std::string device_path(*device);
|
||||
Nan::Callback *callback = new Nan::Callback(info[1].As<v8::Function>());
|
||||
|
||||
Nan::AsyncQueueWorker(new EjectWorker(callback, device_path));
|
||||
|
||||
info.GetReturnValue().SetUndefined();
|
||||
}
|
74
dependencies/mountutils/src/worker-unmount.cpp
vendored
Normal file
74
dependencies/mountutils/src/worker-unmount.cpp
vendored
Normal file
|
@ -0,0 +1,74 @@
|
|||
/*
|
||||
* 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 "mountutils.hpp"
|
||||
|
||||
class UnmountWorker : public Nan::AsyncWorker {
|
||||
public:
|
||||
UnmountWorker(Nan::Callback *callback, std::string device)
|
||||
: Nan::AsyncWorker(callback) {
|
||||
device_path = device;
|
||||
}
|
||||
|
||||
~UnmountWorker() {}
|
||||
|
||||
void Execute() {
|
||||
MOUNTUTILS_RESULT result = unmount_disk(device_path.c_str());
|
||||
|
||||
MountUtilsLog("Unmount complete");
|
||||
|
||||
if (result != MOUNTUTILS_SUCCESS) {
|
||||
switch (result) {
|
||||
case MOUNTUTILS_ERROR_ACCESS_DENIED:
|
||||
SetErrorMessage("Unmount failed, access denied");
|
||||
break;
|
||||
case MOUNTUTILS_ERROR_INVALID_DRIVE:
|
||||
SetErrorMessage("Unmount failed, invalid drive");
|
||||
break;
|
||||
default:
|
||||
SetErrorMessage("Unmount failed");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void HandleOKCallback() {
|
||||
Nan::HandleScope scope;
|
||||
v8::Local<v8::Value> argv[] = { Nan::Null() };
|
||||
callback->Call(1, argv, async_resource);
|
||||
}
|
||||
|
||||
private:
|
||||
std::string device_path;
|
||||
};
|
||||
|
||||
NAN_METHOD(unmountDisk) {
|
||||
if (!info[1]->IsFunction()) {
|
||||
return Nan::ThrowError("Callback must be a function");
|
||||
}
|
||||
|
||||
if (!info[0]->IsString()) {
|
||||
return Nan::ThrowError("Device must be a string");
|
||||
}
|
||||
|
||||
Nan::Utf8String device(Nan::To<v8::String>(info[0]).ToLocalChecked());
|
||||
std::string device_path(*device);
|
||||
Nan::Callback *callback = new Nan::Callback(info[1].As<v8::Function>());
|
||||
|
||||
Nan::AsyncQueueWorker(new UnmountWorker(callback, device_path));
|
||||
|
||||
info.GetReturnValue().SetUndefined();
|
||||
}
|
106
dependencies/mountutils/tests/mountutils.spec.js
vendored
Normal file
106
dependencies/mountutils/tests/mountutils.spec.js
vendored
Normal file
|
@ -0,0 +1,106 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
const chai = require('chai');
|
||||
const mountutils = require('../');
|
||||
|
||||
describe('MountUtils', function() {
|
||||
|
||||
describe('.unmountDisk()', function() {
|
||||
|
||||
it('should be a function', function() {
|
||||
chai.expect(mountutils.unmountDisk).to.be.a('function');
|
||||
});
|
||||
|
||||
context('missing / wrong arguments', function() {
|
||||
|
||||
specify('throws on missing device', function() {
|
||||
chai.expect(function() {
|
||||
mountutils.unmountDisk(null, function() {});
|
||||
}).to.throw(/must be a string/i);
|
||||
});
|
||||
|
||||
specify('throws on missing callback', function() {
|
||||
chai.expect(function() {
|
||||
mountutils.unmountDisk('novalue');
|
||||
}).to.throw(/must be a function/i);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
context('invalid device', function() {
|
||||
|
||||
specify('device is a directory', function( done ) {
|
||||
mountutils.unmountDisk( __dirname, function( error ) {
|
||||
chai.expect(error.message).to.match(/Unmount failed/);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
specify('device is an empty string', function( done ) {
|
||||
mountutils.unmountDisk( '', function( error ) {
|
||||
chai.expect(error.message).to.match(/Unmount failed/);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('.eject()', function() {
|
||||
|
||||
it('should be a function', function() {
|
||||
chai.expect(mountutils.eject).to.be.a('function');
|
||||
});
|
||||
|
||||
context('missing / wrong arguments', function() {
|
||||
|
||||
specify('throws on missing device', function() {
|
||||
chai.expect(function() {
|
||||
mountutils.eject(null, function() {});
|
||||
}).to.throw(/must be a string/i);
|
||||
});
|
||||
|
||||
specify('throws on missing callback', function() {
|
||||
chai.expect(function() {
|
||||
mountutils.eject('novalue');
|
||||
}).to.throw(/must be a function/i);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
context('invalid device', function() {
|
||||
|
||||
specify('device is a directory', function( done ) {
|
||||
mountutils.eject( __dirname, function( error ) {
|
||||
chai.expect(error.message).to.match(/Eject failed/);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
specify('device is an empty string', function( done ) {
|
||||
mountutils.eject( '', function( error ) {
|
||||
chai.expect(error.message).to.match(/Eject failed/);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
});
|
Loading…
Add table
Add a link
Reference in a new issue