X-Git-Url: https://main.carlh.net/gitweb/?p=dcpomatic.git;a=blobdiff_plain;f=src%2Flib%2Fcross_windows.cc;h=ac92aa7eb730b8364673b8b8725b862453b7975f;hp=f55e0a9c23cddaeea6f72553819c93ea207705b1;hb=da44da6f31f97d39ca91c35955e573e76371f2c2;hpb=ddccef68ae80bac6b4aa8583dd8e0a7c943b535c diff --git a/src/lib/cross_windows.cc b/src/lib/cross_windows.cc index f55e0a9c2..ac92aa7eb 100644 --- a/src/lib/cross_windows.cc +++ b/src/lib/cross_windows.cc @@ -1,5 +1,5 @@ /* - Copyright (C) 2012-2020 Carl Hetherington + Copyright (C) 2012-2021 Carl Hetherington This file is part of DCP-o-matic. @@ -18,6 +18,7 @@ */ + #include "cross.h" #include "compose.hpp" #include "log.h" @@ -25,40 +26,48 @@ #include "config.h" #include "exceptions.h" #include "dcpomatic_assert.h" +#include "util.h" #include #include extern "C" { #include } #include -#include #include #include #include #include #include #include +#include #undef DATADIR #include #include #include #include +#include #include "i18n.h" + using std::pair; -using std::list; -using std::ifstream; -using std::string; -using std::wstring; -using std::make_pair; -using std::vector; using std::cerr; using std::cout; +using std::ifstream; +using std::list; +using std::make_pair; +using std::map; using std::runtime_error; -using boost::shared_ptr; +using std::shared_ptr; +using std::string; +using std::vector; +using std::wstring; using boost::optional; + +static std::vector> locked_volumes; + + /** @param s Number of seconds to sleep for */ void dcpomatic_sleep_seconds (int s) @@ -66,12 +75,14 @@ dcpomatic_sleep_seconds (int s) Sleep (s * 1000); } + void dcpomatic_sleep_milliseconds (int ms) { Sleep (ms); } + /** @return A string of CPU information (model name etc.) */ string cpu_info () @@ -106,6 +117,7 @@ cpu_info () return info; } + void run_ffprobe (boost::filesystem::path content, boost::filesystem::path out) { @@ -122,9 +134,7 @@ run_ffprobe (boost::filesystem::path content, boost::filesystem::path out) } wchar_t dir[512]; - GetModuleFileName (GetModuleHandle (0), dir, sizeof (dir)); - PathRemoveFileSpec (dir); - SetCurrentDirectory (dir); + MultiByteToWideChar (CP_UTF8, 0, directory_containing_executable().string().c_str(), -1, dir, sizeof(dir)); STARTUPINFO startup_info; ZeroMemory (&startup_info, sizeof (startup_info)); @@ -143,12 +153,12 @@ run_ffprobe (boost::filesystem::path content, boost::filesystem::path out) PROCESS_INFORMATION process_info; ZeroMemory (&process_info, sizeof (process_info)); - if (!CreateProcess (0, command, 0, 0, TRUE, CREATE_NO_WINDOW, 0, 0, &startup_info, &process_info)) { + if (!CreateProcess (0, command, 0, 0, TRUE, CREATE_NO_WINDOW, 0, dir, &startup_info, &process_info)) { LOG_ERROR_NC (N_("ffprobe call failed (could not CreateProcess)")); return; } - FILE* o = fopen_boost (out, "w"); + auto o = fopen_boost (out, "w"); if (!o) { LOG_ERROR_NC (N_("ffprobe call failed (could not create output file)")); return; @@ -173,39 +183,93 @@ run_ffprobe (boost::filesystem::path content, boost::filesystem::path out) CloseHandle (child_stderr_read); } -list > + +list> mount_info () { - list > m; - return m; + return {}; } -static boost::filesystem::path -executable_path () + +boost::filesystem::path +directory_containing_executable () { return boost::dll::program_location().parent_path(); } + boost::filesystem::path -shared_path () +resources_path () { - return executable_path().parent_path(); + return directory_containing_executable().parent_path(); } + +boost::filesystem::path +xsd_path () +{ + return directory_containing_executable().parent_path() / "xsd"; +} + + +boost::filesystem::path +tags_path () +{ + return directory_containing_executable().parent_path() / "tags"; +} + + boost::filesystem::path openssl_path () { - return executable_path() / "openssl.exe"; + return directory_containing_executable() / "openssl.exe"; } + #ifdef DCPOMATIC_DISK boost::filesystem::path disk_writer_path () { - return executable_path() / "dcpomatic2_disk_writer.exe"; + return directory_containing_executable() / "dcpomatic2_disk_writer.exe"; } #endif + +/** Windows can't "by default" cope with paths longer than 260 characters, so if you pass such a path to + * any boost::filesystem method it will fail. There is a "fix" for this, which is to prepend + * the string \\?\ to the path. This will make it work, so long as: + * - the path is absolute. + * - the path only uses backslashes. + * - individual path components are "short enough" (probably less than 255 characters) + * + * See https://www.boost.org/doc/libs/1_57_0/libs/filesystem/doc/reference.html under + * "Warning: Long paths on Windows" for some details. + * + * Our fopen_boost uses this method to get this fix, but any other calls to boost::filesystem + * will not unless this method is explicitly called to pre-process the pathname. + */ +boost::filesystem::path +fix_long_path (boost::filesystem::path long_path) +{ + using namespace boost::filesystem; + + path fixed = "\\\\?\\"; + if (boost::algorithm::starts_with(long_path.string(), fixed.string())) { + return long_path; + } + + /* We have to make the path canonical but we can't call canonical() on the long path + * as it will fail. So we'll sort of do it ourselves (possibly badly). + */ + if (long_path.is_absolute()) { + fixed += long_path.make_preferred(); + } else { + fixed += boost::filesystem::current_path() / long_path.make_preferred(); + } + return fixed; +} + + /* Apparently there is no way to create an ofstream using a UTF-8 filename under Windows. We are hence reduced to using fopen with this wrapper. @@ -214,16 +278,18 @@ FILE * fopen_boost (boost::filesystem::path p, string t) { wstring w (t.begin(), t.end()); - /* c_str() here should give a UTF-16 string */ - return _wfopen (p.c_str(), w.c_str ()); + /* c_str() on fixed here should give a UTF-16 string */ + return _wfopen (fix_long_path(p).c_str(), w.c_str()); } + int dcpomatic_fseek (FILE* stream, int64_t offset, int whence) { return _fseeki64 (stream, offset, whence); } + void Waker::nudge () { @@ -231,20 +297,23 @@ Waker::nudge () SetThreadExecutionState (ES_SYSTEM_REQUIRED); } + Waker::Waker () { } + Waker::~Waker () { } + void -start_tool (boost::filesystem::path dcpomatic, string executable, string) +start_tool (string executable) { - boost::filesystem::path batch = dcpomatic.parent_path() / executable; + auto batch = directory_containing_executable() / executable; STARTUPINFO startup_info; ZeroMemory (&startup_info, sizeof (startup_info)); @@ -258,24 +327,28 @@ start_tool (boost::filesystem::path dcpomatic, string executable, string) CreateProcess (0, cmd, 0, 0, FALSE, 0, 0, 0, &startup_info, &process_info); } + void -start_batch_converter (boost::filesystem::path dcpomatic) +start_batch_converter () { - start_tool (dcpomatic, "dcpomatic2_batch", "DCP-o-matic\\ 2\\ Batch\\ Converter.app"); + start_tool ("dcpomatic2_batch"); } + void -start_player (boost::filesystem::path dcpomatic) +start_player () { - start_tool (dcpomatic, "dcpomatic2_player", "DCP-o-matic\\ 2\\ Player.app"); + start_tool ("dcpomatic2_player"); } + uint64_t thread_id () { return (uint64_t) GetCurrentThreadId (); } + static string wchar_to_utf8 (wchar_t const * s) { @@ -287,43 +360,41 @@ wchar_to_utf8 (wchar_t const * s) return u; } + int avio_open_boost (AVIOContext** s, boost::filesystem::path file, int flags) { return avio_open (s, wchar_to_utf8(file.c_str()).c_str(), flags); } + void maybe_open_console () { if (Config::instance()->win32_console ()) { AllocConsole(); - HANDLE handle_out = GetStdHandle(STD_OUTPUT_HANDLE); + auto handle_out = GetStdHandle(STD_OUTPUT_HANDLE); int hCrt = _open_osfhandle((intptr_t) handle_out, _O_TEXT); - FILE* hf_out = _fdopen(hCrt, "w"); + auto hf_out = _fdopen(hCrt, "w"); setvbuf(hf_out, NULL, _IONBF, 1); *stdout = *hf_out; - HANDLE handle_in = GetStdHandle(STD_INPUT_HANDLE); + auto handle_in = GetStdHandle(STD_INPUT_HANDLE); hCrt = _open_osfhandle((intptr_t) handle_in, _O_TEXT); - FILE* hf_in = _fdopen(hCrt, "r"); + auto hf_in = _fdopen(hCrt, "r"); setvbuf(hf_in, NULL, _IONBF, 128); *stdin = *hf_in; } } + boost::filesystem::path home_directory () { return boost::filesystem::path(getenv("HOMEDRIVE")) / boost::filesystem::path(getenv("HOMEPATH")); } -string -command_and_read (string) -{ - return ""; -} /** @return true if this process is a 32-bit one running on a 64-bit-capable OS */ bool @@ -334,6 +405,7 @@ running_32_on_64 () return p; } + static optional get_friendly_name (HDEVINFO device_info, SP_DEVINFO_DATA* device_info_data) { @@ -348,10 +420,12 @@ get_friendly_name (HDEVINFO device_info, SP_DEVINFO_DATA* device_info_data) return wchar_to_utf8 (buffer); } + static const GUID GUID_DEVICE_INTERFACE_DISK = { 0x53F56307L, 0xB6BF, 0x11D0, { 0x94, 0xF2, 0x00, 0xA0, 0xC9, 0x1E, 0xFB, 0x8B } }; + static optional get_device_number (HDEVINFO device_info, SP_DEVINFO_DATA* device_info_data) { @@ -360,7 +434,7 @@ get_device_number (HDEVINFO device_info, SP_DEVINFO_DATA* device_info_data) SP_DEVICE_INTERFACE_DATA device_interface_data; device_interface_data.cbSize = sizeof(SP_DEVICE_INTERFACE_DATA); - BOOL r = SetupDiEnumDeviceInterfaces (device_info, device_info_data, &GUID_DEVICE_INTERFACE_DISK, 0, &device_interface_data); + auto r = SetupDiEnumDeviceInterfaces (device_info, device_info_data, &GUID_DEVICE_INTERFACE_DISK, 0, &device_interface_data); if (!r) { LOG_DISK("SetupDiEnumDeviceInterfaces failed (%1)", GetLastError()); return optional(); @@ -388,7 +462,7 @@ get_device_number (HDEVINFO device_info, SP_DEVINFO_DATA* device_info_data) /* Open it. We would not be allowed GENERIC_READ access here but specifying 0 for dwDesiredAccess allows us to query some metadata. */ - HANDLE device = CreateFileW ( + auto device = CreateFileW ( device_detail_data->DevicePath, 0, FILE_SHARE_READ | FILE_SHARE_WRITE, 0, OPEN_EXISTING, 0, 0 @@ -411,24 +485,42 @@ get_device_number (HDEVINFO device_info, SP_DEVINFO_DATA* device_info_data) CloseHandle (device); if (!r) { - return optional(); + return {}; } return device_number.DeviceNumber; } + +typedef map> MountPoints; + + /** Take a volume path (with a trailing \) and add any disk numbers related to that volume * to @ref disks. */ static void -add_volume_disk_number (wchar_t* volume, vector& disks) +add_volume_mount_points (wchar_t* volume, MountPoints& mount_points) { + LOG_DISK("Looking at %1", wchar_to_utf8(volume)); + + wchar_t volume_path_names[512]; + vector mp; + DWORD returned; + if (GetVolumePathNamesForVolumeNameW(volume, volume_path_names, sizeof(volume_path_names) / sizeof(wchar_t), &returned)) { + wchar_t* p = volume_path_names; + while (*p != L'\0') { + mp.push_back (wchar_to_utf8(p)); + LOG_DISK ("Found mount point %1", wchar_to_utf8(p)); + p += wcslen(p) + 1; + } + } + /* Strip trailing \ */ size_t const len = wcslen (volume); DCPOMATIC_ASSERT (len > 0); volume[len - 1] = L'\0'; - HANDLE handle = CreateFileW ( + auto handle = CreateFileW ( volume, 0, FILE_SHARE_READ | FILE_SHARE_WRITE, 0, OPEN_EXISTING, 0, 0 @@ -444,44 +536,44 @@ add_volume_disk_number (wchar_t* volume, vector& disks) return; } DCPOMATIC_ASSERT (extents.NumberOfDiskExtents == 1); - return disks.push_back (extents.Extents[0].DiskNumber); + + mount_points[extents.Extents[0].DiskNumber] = mp; } -/* Return a list of disk numbers that contain volumes; i.e. a list of disk numbers that should - * not be offered as targets to write to as they are "mounted" (whatever that means on Windows). - */ -vector -disk_numbers_with_volumes () + +MountPoints +find_mount_points () { - vector disks; + MountPoints mount_points; wchar_t volume_name[512]; - HANDLE volume = FindFirstVolumeW (volume_name, sizeof(volume_name) / sizeof(wchar_t)); + auto volume = FindFirstVolumeW (volume_name, sizeof(volume_name) / sizeof(wchar_t)); if (volume == INVALID_HANDLE_VALUE) { - return disks; + return MountPoints(); } - add_volume_disk_number (volume_name, disks); + add_volume_mount_points (volume_name, mount_points); while (true) { if (!FindNextVolumeW(volume, volume_name, sizeof(volume_name) / sizeof(wchar_t))) { break; } - add_volume_disk_number (volume_name, disks); + add_volume_mount_points (volume_name, mount_points); } FindVolumeClose (volume); - return disks; + return mount_points; } + vector -get_drives () +Drive::get () { vector drives; - vector disks_to_ignore = disk_numbers_with_volumes (); + auto mount_points = find_mount_points (); /* Get a `device information set' containing information about all disks */ - HDEVINFO device_info = SetupDiGetClassDevsA (&GUID_DEVICE_INTERFACE_DISK, 0, 0, DIGCF_PRESENT | DIGCF_DEVICEINTERFACE); + auto device_info = SetupDiGetClassDevsA (&GUID_DEVICE_INTERFACE_DISK, 0, 0, DIGCF_PRESENT | DIGCF_DEVICEINTERFACE); if (device_info == INVALID_HANDLE_VALUE) { LOG_DISK_NC ("SetupDiClassDevsA failed"); return drives; @@ -501,8 +593,8 @@ get_drives () } ++i; - optional const friendly_name = get_friendly_name (device_info, &device_info_data); - optional device_number = get_device_number (device_info, &device_info_data); + auto const friendly_name = get_friendly_name (device_info, &device_info_data); + auto device_number = get_device_number (device_info, &device_info_data); if (!device_number) { continue; } @@ -527,9 +619,18 @@ get_drives () &geom, sizeof(geom), &returned, 0 ); - if (r && find(disks_to_ignore.begin(), disks_to_ignore.end(), *device_number) == disks_to_ignore.end()) { + LOG_DISK("Having a look through %1 locked volumes", locked_volumes.size()); + bool locked = false; + for (auto const& i: locked_volumes) { + if (i.second == physical_drive) { + locked = true; + } + } + + if (r) { uint64_t const disk_size = geom.Cylinders.QuadPart * geom.TracksPerCylinder * geom.SectorsPerTrack * geom.BytesPerSector; - drives.push_back (Drive(physical_drive, disk_size, false, friendly_name, optional())); + drives.push_back (Drive(physical_drive, locked ? vector() : mount_points[*device_number], disk_size, friendly_name, optional())); + LOG_DISK("Added drive %1%2", drives.back().log_summary(), locked ? "(locked by us)" : ""); } CloseHandle (device); @@ -538,6 +639,36 @@ get_drives () return drives; } + +bool +Drive::unmount () +{ + LOG_DISK("Unmounting %1 with %2 mount points", _device, _mount_points.size()); + DCPOMATIC_ASSERT (_mount_points.size() == 1); + string const device_name = String::compose ("\\\\.\\%1", _mount_points.front()); + string const truncated = device_name.substr (0, device_name.length() - 1); + //LOG_DISK("Actually opening %1", _device); + //HANDLE device = CreateFileA (_device.c_str(), (GENERIC_READ | GENERIC_WRITE), FILE_SHARE_READ | FILE_SHARE_WRITE, 0, OPEN_EXISTING, 0, 0); + LOG_DISK("Actually opening %1", truncated); + HANDLE device = CreateFileA (truncated.c_str(), (GENERIC_READ | GENERIC_WRITE), FILE_SHARE_READ | FILE_SHARE_WRITE, 0, OPEN_EXISTING, 0, 0); + if (device == INVALID_HANDLE_VALUE) { + LOG_DISK("Could not open %1 for unmount (%2)", truncated, GetLastError()); + return false; + } + DWORD returned; + BOOL r = DeviceIoControl (device, FSCTL_LOCK_VOLUME, 0, 0, 0, 0, &returned, 0); + if (!r) { + LOG_DISK("Unmount of %1 failed (%2)", truncated, GetLastError()); + return false; + } + + LOG_DISK("Unmount of %1 succeeded", _device); + locked_volumes.push_back (make_pair(device, _device)); + + return true; +} + + boost::filesystem::path config_path () { @@ -546,3 +677,20 @@ config_path () p /= "dcpomatic2"; return p; } + + +void +disk_write_finished () +{ + for (auto const& i: locked_volumes) { + CloseHandle (i.first); + } +} + + +string +dcpomatic::get_process_id () +{ + return dcp::raw_convert(GetCurrentProcessId()); +} +