2 Copyright (C) 2012-2020 Carl Hetherington <cth@carlh.net>
4 This file is part of DCP-o-matic.
6 DCP-o-matic is free software; you can redistribute it and/or modify
7 it under the terms of the GNU General Public License as published by
8 the Free Software Foundation; either version 2 of the License, or
9 (at your option) any later version.
11 DCP-o-matic is distributed in the hope that it will be useful,
12 but WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 GNU General Public License for more details.
16 You should have received a copy of the GNU General Public License
17 along with DCP-o-matic. If not, see <http://www.gnu.org/licenses/>.
22 #include "compose.hpp"
24 #include "dcpomatic_log.h"
26 #include "exceptions.h"
27 #include <dcp/raw_convert.h>
30 #include <libavformat/avio.h>
32 #include <boost/algorithm/string.hpp>
33 #include <boost/foreach.hpp>
34 #ifdef DCPOMATIC_LINUX
38 #ifdef DCPOMATIC_WINDOWS
47 #include <sys/sysctl.h>
48 #include <mach-o/dyld.h>
49 #include <IOKit/pwr_mgt/IOPMLib.h>
50 #include <DiskArbitration/DADisk.h>
51 #include <DiskArbitration/DiskArbitration.h>
53 #ifdef DCPOMATIC_POSIX
54 #include <sys/types.h>
56 #include <netinet/in.h>
57 #include <arpa/inet.h>
72 using std::runtime_error;
73 using boost::shared_ptr;
74 using boost::optional;
76 /** @param s Number of seconds to sleep for */
78 dcpomatic_sleep_seconds (int s)
80 #ifdef DCPOMATIC_POSIX
83 #ifdef DCPOMATIC_WINDOWS
89 dcpomatic_sleep_milliseconds (int ms)
91 #ifdef DCPOMATIC_POSIX
94 #ifdef DCPOMATIC_WINDOWS
99 /** @return A string of CPU information (model name etc.) */
105 #ifdef DCPOMATIC_LINUX
106 /* This use of ifstream is ok; the filename can never
109 ifstream f ("/proc/cpuinfo");
113 if (boost::algorithm::starts_with (l, "model name")) {
114 string::size_type const c = l.find (':');
115 if (c != string::npos) {
116 info = l.substr (c + 2);
124 size_t N = sizeof (buffer);
125 if (sysctlbyname ("machdep.cpu.brand_string", buffer, &N, 0, 0) == 0) {
130 #ifdef DCPOMATIC_WINDOWS
132 if (RegOpenKeyEx (HKEY_LOCAL_MACHINE, L"HARDWARE\\DESCRIPTION\\System\\CentralProcessor\\0", 0, KEY_READ, &key) != ERROR_SUCCESS) {
138 if (RegQueryValueEx (key, L"ProcessorNameString", 0, &type, 0, &data) != ERROR_SUCCESS) {
142 if (type != REG_SZ) {
146 wstring value (data / sizeof (wchar_t), L'\0');
147 if (RegQueryValueEx (key, L"ProcessorNameString", 0, 0, reinterpret_cast<LPBYTE> (&value[0]), &data) != ERROR_SUCCESS) {
152 info = string (value.begin(), value.end());
162 /** @return Path of the Contents directory in the .app */
163 boost::filesystem::path
166 uint32_t size = 1024;
168 if (_NSGetExecutablePath (buffer, &size)) {
169 throw runtime_error ("_NSGetExecutablePath failed");
172 boost::filesystem::path path (buffer);
173 path = boost::filesystem::canonical (path);
174 path = path.parent_path ();
175 path = path.parent_path ();
180 boost::filesystem::path
183 #ifdef DCPOMATIC_LINUX
184 char const * p = getenv ("DCPOMATIC_LINUX_SHARE_PREFIX");
188 return boost::filesystem::canonical (LINUX_SHARE_PREFIX);
190 #ifdef DCPOMATIC_WINDOWS
192 GetModuleFileName (GetModuleHandle (0), dir, sizeof (dir));
193 PathRemoveFileSpec (dir);
194 boost::filesystem::path path = dir;
195 return path.parent_path();
198 return app_contents() / "Resources";
203 run_ffprobe (boost::filesystem::path content, boost::filesystem::path out)
205 #ifdef DCPOMATIC_WINDOWS
206 SECURITY_ATTRIBUTES security;
207 security.nLength = sizeof (security);
208 security.bInheritHandle = TRUE;
209 security.lpSecurityDescriptor = 0;
211 HANDLE child_stderr_read;
212 HANDLE child_stderr_write;
213 if (!CreatePipe (&child_stderr_read, &child_stderr_write, &security, 0)) {
214 LOG_ERROR_NC ("ffprobe call failed (could not CreatePipe)");
219 GetModuleFileName (GetModuleHandle (0), dir, sizeof (dir));
220 PathRemoveFileSpec (dir);
221 SetCurrentDirectory (dir);
223 STARTUPINFO startup_info;
224 ZeroMemory (&startup_info, sizeof (startup_info));
225 startup_info.cb = sizeof (startup_info);
226 startup_info.hStdError = child_stderr_write;
227 startup_info.dwFlags |= STARTF_USESTDHANDLES;
229 wchar_t command[512];
230 wcscpy (command, L"ffprobe.exe \"");
233 MultiByteToWideChar (CP_UTF8, 0, content.string().c_str(), -1, file, sizeof(file));
234 wcscat (command, file);
236 wcscat (command, L"\"");
238 PROCESS_INFORMATION process_info;
239 ZeroMemory (&process_info, sizeof (process_info));
240 if (!CreateProcess (0, command, 0, 0, TRUE, CREATE_NO_WINDOW, 0, 0, &startup_info, &process_info)) {
241 LOG_ERROR_NC (N_("ffprobe call failed (could not CreateProcess)"));
245 FILE* o = fopen_boost (out, "w");
247 LOG_ERROR_NC (N_("ffprobe call failed (could not create output file)"));
251 CloseHandle (child_stderr_write);
256 if (!ReadFile(child_stderr_read, buffer, sizeof(buffer), &read, 0) || read == 0) {
259 fwrite (buffer, read, 1, o);
264 WaitForSingleObject (process_info.hProcess, INFINITE);
265 CloseHandle (process_info.hProcess);
266 CloseHandle (process_info.hThread);
267 CloseHandle (child_stderr_read);
270 #ifdef DCPOMATIC_LINUX
271 string ffprobe = "ffprobe \"" + content.string() + "\" 2> \"" + out.string() + "\"";
272 LOG_GENERAL (N_("Probing with %1"), ffprobe);
273 system (ffprobe.c_str ());
277 boost::filesystem::path path = app_contents();
281 string ffprobe = "\"" + path.string() + "\" \"" + content.string() + "\" 2> \"" + out.string() + "\"";
282 LOG_GENERAL (N_("Probing with %1"), ffprobe);
283 system (ffprobe.c_str ());
287 list<pair<string, string> >
290 list<pair<string, string> > m;
292 #ifdef DCPOMATIC_LINUX
293 FILE* f = setmntent ("/etc/mtab", "r");
299 struct mntent* mnt = getmntent (f);
304 m.push_back (make_pair (mnt->mnt_dir, mnt->mnt_type));
313 boost::filesystem::path
316 #ifdef DCPOMATIC_WINDOWS
318 GetModuleFileName (GetModuleHandle (0), dir, sizeof (dir));
319 PathRemoveFileSpec (dir);
321 boost::filesystem::path path = dir;
322 path /= "openssl.exe";
327 boost::filesystem::path path = app_contents();
333 #ifdef DCPOMATIC_LINUX
334 return "dcpomatic2_openssl";
339 /* Apparently there is no way to create an ofstream using a UTF-8
340 filename under Windows. We are hence reduced to using fopen
344 fopen_boost (boost::filesystem::path p, string t)
346 #ifdef DCPOMATIC_WINDOWS
347 wstring w (t.begin(), t.end());
348 /* c_str() here should give a UTF-16 string */
349 return _wfopen (p.c_str(), w.c_str ());
351 return fopen (p.c_str(), t.c_str ());
356 dcpomatic_fseek (FILE* stream, int64_t offset, int whence)
358 #ifdef DCPOMATIC_WINDOWS
359 return _fseeki64 (stream, offset, whence);
361 return fseek (stream, offset, whence);
368 #ifdef DCPOMATIC_WINDOWS
369 boost::mutex::scoped_lock lm (_mutex);
370 SetThreadExecutionState (ES_SYSTEM_REQUIRED);
377 boost::mutex::scoped_lock lm (_mutex);
378 /* We should use this */
379 // IOPMAssertionCreateWithName (kIOPMAssertionTypeNoIdleSleep, kIOPMAssertionLevelOn, CFSTR ("Encoding DCP"), &_assertion_id);
380 /* but it's not available on 10.5, so we use this */
381 IOPMAssertionCreate (kIOPMAssertionTypeNoIdleSleep, kIOPMAssertionLevelOn, &_assertion_id);
388 boost::mutex::scoped_lock lm (_mutex);
389 IOPMAssertionRelease (_assertion_id);
394 start_tool (boost::filesystem::path dcpomatic, string executable,
402 #if defined(DCPOMATIC_LINUX) || defined(DCPOMATIC_WINDOWS)
403 boost::filesystem::path batch = dcpomatic.parent_path() / executable;
407 boost::filesystem::path batch = dcpomatic.parent_path ();
408 batch = batch.parent_path (); // MacOS
409 batch = batch.parent_path (); // Contents
410 batch = batch.parent_path (); // DCP-o-matic.app
411 batch = batch.parent_path (); // Applications
418 #if defined(DCPOMATIC_LINUX) || defined(DCPOMATIC_OSX)
421 int const r = system (batch.string().c_str());
422 exit (WEXITSTATUS (r));
426 #ifdef DCPOMATIC_WINDOWS
427 STARTUPINFO startup_info;
428 ZeroMemory (&startup_info, sizeof (startup_info));
429 startup_info.cb = sizeof (startup_info);
431 PROCESS_INFORMATION process_info;
432 ZeroMemory (&process_info, sizeof (process_info));
435 MultiByteToWideChar (CP_UTF8, 0, batch.string().c_str(), -1, cmd, sizeof(cmd));
436 CreateProcess (0, cmd, 0, 0, FALSE, 0, 0, 0, &startup_info, &process_info);
441 start_batch_converter (boost::filesystem::path dcpomatic)
443 start_tool (dcpomatic, "dcpomatic2_batch", "DCP-o-matic\\ 2\\ Batch\\ Converter.app");
447 start_player (boost::filesystem::path dcpomatic)
449 start_tool (dcpomatic, "dcpomatic2_player", "DCP-o-matic\\ 2\\ Player.app");
455 #ifdef DCPOMATIC_WINDOWS
456 return (uint64_t) GetCurrentThreadId ();
458 return (uint64_t) pthread_self ();
463 avio_open_boost (AVIOContext** s, boost::filesystem::path file, int flags)
465 #ifdef DCPOMATIC_WINDOWS
466 int const length = (file.string().length() + 1) * 2;
467 char* utf8 = new char[length];
468 WideCharToMultiByte (CP_UTF8, 0, file.c_str(), -1, utf8, length, 0, 0);
469 int const r = avio_open (s, utf8, flags);
473 return avio_open (s, file.c_str(), flags);
477 #ifdef DCPOMATIC_WINDOWS
479 maybe_open_console ()
481 if (Config::instance()->win32_console ()) {
484 HANDLE handle_out = GetStdHandle(STD_OUTPUT_HANDLE);
485 int hCrt = _open_osfhandle((intptr_t) handle_out, _O_TEXT);
486 FILE* hf_out = _fdopen(hCrt, "w");
487 setvbuf(hf_out, NULL, _IONBF, 1);
490 HANDLE handle_in = GetStdHandle(STD_INPUT_HANDLE);
491 hCrt = _open_osfhandle((intptr_t) handle_in, _O_TEXT);
492 FILE* hf_in = _fdopen(hCrt, "r");
493 setvbuf(hf_in, NULL, _IONBF, 128);
499 boost::filesystem::path
502 #if defined(DCPOMATIC_LINUX) || defined(DCPOMATIC_OSX)
503 return getenv("HOME");
505 #ifdef DCPOMATIC_WINDOWS
506 return boost::filesystem::path(getenv("HOMEDRIVE")) / boost::filesystem::path(getenv("HOMEPATH"));
511 command_and_read (string cmd)
513 #ifdef DCPOMATIC_LINUX
514 FILE* pipe = popen (cmd.c_str(), "r");
516 throw runtime_error ("popen failed");
522 while (fgets(buffer, sizeof(buffer), pipe)) {
537 /** @return true if this process is a 32-bit one running on a 64-bit-capable OS */
541 #ifdef DCPOMATIC_WINDOWS
543 IsWow64Process (GetCurrentProcess(), &p);
546 /* XXX: assuming nobody does this on Linux / OS X */
552 disk_appeared(DADiskRef disk, void* context)
554 const char* name = DADiskGetBSDName (disk);
556 list<Drive>* drives = reinterpret_cast<list<Drive>*> (context);
557 drives->push_back (Drive(name, 0, false, optional<string>(), optional<string>()));
565 vector<Drive> drives;
567 #ifdef DCPOMATIC_WINDOWS
569 const GUID GUID_DEVICE_INTERFACE_DISK = {
570 0x53F56307L, 0xB6BF, 0x11D0, { 0x94, 0xF2, 0x00, 0xA0, 0xC9, 0x1E, 0xFB, 0x8B }
573 HDEVINFO device_info = SetupDiGetClassDevsA (&GUID_DEVICE_INTERFACE_DISK, 0, 0, DIGCF_PRESENT | DIGCF_DEVICEINTERFACE);
574 if (device_info == INVALID_HANDLE_VALUE) {
575 LOG_DIST_NC("SetupDiClassDevsA failed");
581 SP_DEVINFO_DATA device_info_data;
582 device_info_data.cbSize = sizeof(SP_DEVINFO_DATA);
583 if (!SetupDiEnumDeviceInfo(device_info, i, &device_info_data)) {
584 LOG_DIST ("SetupDiEnumDeviceInfo failed (%1)", GetLastError());
589 wchar_t friendly_name_buffer[MAX_PATH];
590 ZeroMemory (&friendly_name_buffer, sizeof(friendly_name_buffer));
592 bool r = SetupDiGetDeviceRegistryPropertyW (
593 device_info, &device_info_data, SPDRP_FRIENDLYNAME, 0, static_cast<PBYTE>(wbuffer), sizeof(wbuffer), 0
597 int const length = (wcslen(friendly_name_buffer) + 1) * 2;
598 char* utf8 = new char[length];
599 /* XXX: this is used in a few places in this file; should be abstracted out */
600 WideCharToMultiByte (CP_UTF8, 0, friendly_name_buffer, -1, utf8, length, 0, 0);
601 /* XXX: utf8 contains a user-readable name */
607 SP_DEVICE_INTERFACE_DATA device_interface_data;
608 device_interface_data.cbSize = sizeof(SP_DEVICE_INTERFACE_DATA);
610 bool r = SetupDiEnumDeviceInterfaces (device_info, &device_info_data, &GUID_DEVICE_INTERFACE_DISK, j, &device_interface_data);
612 DWORD e = GetLastError();
613 if (e == ERROR_NO_MORE_ITEMS) {
616 LOG_DIST("SetupDiEnumDeviceInterfaces failed (%1)", e);
622 r = SetupDiGetDeviceInterfaceDetailW(device_info, &device_interface_data, 0, 0, &size, 0);
623 PSP_DEVICE_INTERFACE_DETAIL_DATA_W device_detail_data = static_cast<PSP_DEVICE_INTERFACE_DETAIL_DATA_W> (malloc(size));
624 if (!device_detail_data) {
625 LOG_DIST_NC("malloc failed");
629 device_detail_data->cbSize = sizeof(SP_DEVICE_INTERFACE_DETAIL_DATA_W);
631 r = SetupDiGetDeviceInterfaceDetailW (device_info, &device_interface_data, device_detail_data, size, &size, 0);
633 LOG_DIST_NC("SetupDiGetDeviceInterfaceDetailW failed");
637 HANDLE device = CreateFileW (
638 device_detail_data->DevicePath, 0,
639 FILE_SHARE_READ | FILE_SHARE_WRITE, 0,
640 OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0
643 if (device == INVALID_HANDLE_VALUE) {
644 LOG_DIST_NC("CreateFileW failed");
648 VOLUME_DISK_EXTENTS disk_extents;
649 r = DeviceIoControl (
650 device, IOCTL_VOLUME_GET_VOLUME_DISK_EXTENTS, 0, 0,
651 &disk_extents, sizeof(VOLUME_DISK_EXTENTS), &size, 0
654 if (r && disk_extents.NumberOfDiskExtents > 0) {
655 LOG_DIST("GET_VOLUME_DISK_EXTENTS gives %1", disk_extents.Extents[0].DiskNumber);
656 /* Disk number for \\.\PHYSICALDRIVEx is disk disk_extents.Extents[0].DiskNumber */
659 STORAGE_DEVICE_NUMBER device_number;
660 r = DeviceIoControl (
661 device, IOCTL_STORAGE_GET_DEVICE_NUMBER, 0, 0,
662 &device_number, sizeof(device_number), &size, 0
666 LOG_DIST("GET_DEVICE_NUMBER gives %1", disk_number.DeviceNumber);
667 /* Disk number for \\.\PHYSICALDRIVEx is device_number.DeviceNumber */
676 DASessionRef session = DASessionCreate(kCFAllocatorDefault);
681 DARegisterDiskAppearedCallback (session, NULL, disk_appeared, &drives);
682 CFRunLoopRef run_loop = CFRunLoopGetCurrent ();
683 DASessionScheduleWithRunLoop (session, run_loop, kCFRunLoopDefaultMode);
684 CFRunLoopStop (run_loop);
685 CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0.05, 0);
686 DAUnregisterCallback(session, (void *) disk_appeared, &drives);
690 #ifdef DCPOMATIC_LINUX
692 using namespace boost::filesystem;
693 list<string> mounted_devices;
694 std::ifstream f("/proc/mounts");
699 boost::algorithm::split (bits, line, boost::is_any_of(" "));
700 if (bits.size() > 0 && boost::algorithm::starts_with(bits[0], "/dev/")) {
701 mounted_devices.push_back(bits[0]);
702 LOG_DIST("Mounted device %1", bits[0]);
706 for (directory_iterator i = directory_iterator("/sys/block"); i != directory_iterator(); ++i) {
707 string const name = i->path().filename().string();
708 path device_type_file("/sys/block/" + name + "/device/type");
709 optional<string> device_type;
710 if (exists(device_type_file)) {
711 device_type = dcp::file_to_string (device_type_file);
712 boost::trim(*device_type);
714 /* Device type 5 is "SCSI_TYPE_ROM" in blkdev.h; seems usually to be a CD/DVD drive */
715 if (!boost::algorithm::starts_with(name, "loop") && (!device_type || *device_type != "5")) {
716 uint64_t const size = dcp::raw_convert<uint64_t>(dcp::file_to_string(*i / "size")) * 512;
720 bool mounted = false;
721 optional<string> vendor;
723 vendor = dcp::file_to_string("/sys/block/" + name + "/device/vendor");
724 boost::trim(*vendor);
726 optional<string> model;
728 model = dcp::file_to_string("/sys/block/" + name + "/device/model");
731 BOOST_FOREACH (string j, mounted_devices) {
732 if (boost::algorithm::starts_with(j, "/dev/" + name)) {
736 drives.push_back(Drive(i->path().filename().string(), size, mounted, vendor, model));
737 LOG_DIST("Block device %1 size %2 %3 vendor %4 model %5", name, size, mounted ? "mounted" : "not mounted", vendor.get_value_or("[none]"), model.get_value_or("[none]"));
745 Drive::description () const
748 snprintf(gb, 64, "%.1f", _size / 1000000000.0);
755 if (name.size() > 0) {
756 name += " " + *_model;
759 if (name.size() == 0) {
763 return String::compose("%1 (%2 GB) [%3]", name, gb, _internal_name);
766 #ifdef DCPOMATIC_LINUX
770 uid_t ruid, euid, suid;
771 if (getresuid(&ruid, &euid, &suid) == -1) {
772 cerr << "getresuid() failed.\n";
778 PrivilegeEscalator::~PrivilegeEscalator ()
783 PrivilegeEscalator::PrivilegeEscalator ()
789 boost::filesystem::path
792 boost::filesystem::path p;
794 p /= g_get_home_dir ();
797 p /= "com.dcpomatic";
800 p /= g_get_user_config_dir ();