Qt/QML edition

This commit is contained in:
Floris Bos 2020-03-04 16:55:40 +01:00
commit d7b361ba44
2168 changed files with 721948 additions and 0 deletions

View 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);
}

View 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);
}

View 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)

View 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_

View 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);
}

View 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();
}

View 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();
}