Assorted OS X build fixes.
[dcpomatic.git] / src / lib / cross.cc
1 /*
2     Copyright (C) 2012-2020 Carl Hetherington <cth@carlh.net>
3
4     This file is part of DCP-o-matic.
5
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.
10
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.
15
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/>.
18
19 */
20
21 #include "cross.h"
22 #include "compose.hpp"
23 #include "log.h"
24 #include "dcpomatic_log.h"
25 #include "config.h"
26 #include "exceptions.h"
27 #include <dcp/raw_convert.h>
28 #include <glib.h>
29 extern "C" {
30 #include <libavformat/avio.h>
31 }
32 #include <boost/algorithm/string.hpp>
33 #include <boost/foreach.hpp>
34 #ifdef DCPOMATIC_LINUX
35 #include <unistd.h>
36 #include <mntent.h>
37 #endif
38 #ifdef DCPOMATIC_WINDOWS
39 #include <windows.h>
40 #include <setupapi.h>
41 #undef DATADIR
42 #include <shlwapi.h>
43 #include <shellapi.h>
44 #include <fcntl.h>
45 #endif
46 #ifdef DCPOMATIC_OSX
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>
52 #endif
53 #ifdef DCPOMATIC_POSIX
54 #include <sys/types.h>
55 #include <ifaddrs.h>
56 #include <netinet/in.h>
57 #include <arpa/inet.h>
58 #endif
59 #include <fstream>
60
61 #include "i18n.h"
62
63 using std::pair;
64 using std::list;
65 using std::ifstream;
66 using std::string;
67 using std::wstring;
68 using std::make_pair;
69 using std::vector;
70 using std::cerr;
71 using std::cout;
72 using std::runtime_error;
73 using boost::shared_ptr;
74 using boost::optional;
75
76 /** @param s Number of seconds to sleep for */
77 void
78 dcpomatic_sleep_seconds (int s)
79 {
80 #ifdef DCPOMATIC_POSIX
81         sleep (s);
82 #endif
83 #ifdef DCPOMATIC_WINDOWS
84         Sleep (s * 1000);
85 #endif
86 }
87
88 void
89 dcpomatic_sleep_milliseconds (int ms)
90 {
91 #ifdef DCPOMATIC_POSIX
92         usleep (ms * 1000);
93 #endif
94 #ifdef DCPOMATIC_WINDOWS
95         Sleep (ms);
96 #endif
97 }
98
99 /** @return A string of CPU information (model name etc.) */
100 string
101 cpu_info ()
102 {
103         string info;
104
105 #ifdef DCPOMATIC_LINUX
106         /* This use of ifstream is ok; the filename can never
107            be non-Latin
108         */
109         ifstream f ("/proc/cpuinfo");
110         while (f.good ()) {
111                 string l;
112                 getline (f, l);
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);
117                         }
118                 }
119         }
120 #endif
121
122 #ifdef DCPOMATIC_OSX
123         char buffer[64];
124         size_t N = sizeof (buffer);
125         if (sysctlbyname ("machdep.cpu.brand_string", buffer, &N, 0, 0) == 0) {
126                 info = buffer;
127         }
128 #endif
129
130 #ifdef DCPOMATIC_WINDOWS
131         HKEY key;
132         if (RegOpenKeyEx (HKEY_LOCAL_MACHINE, L"HARDWARE\\DESCRIPTION\\System\\CentralProcessor\\0", 0, KEY_READ, &key) != ERROR_SUCCESS) {
133                 return info;
134         }
135
136         DWORD type;
137         DWORD data;
138         if (RegQueryValueEx (key, L"ProcessorNameString", 0, &type, 0, &data) != ERROR_SUCCESS) {
139                 return info;
140         }
141
142         if (type != REG_SZ) {
143                 return info;
144         }
145
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) {
148                 RegCloseKey (key);
149                 return info;
150         }
151
152         info = string (value.begin(), value.end());
153
154         RegCloseKey (key);
155
156 #endif
157
158         return info;
159 }
160
161 #ifdef DCPOMATIC_OSX
162 /** @return Path of the Contents directory in the .app */
163 boost::filesystem::path
164 app_contents ()
165 {
166         uint32_t size = 1024;
167         char buffer[size];
168         if (_NSGetExecutablePath (buffer, &size)) {
169                 throw runtime_error ("_NSGetExecutablePath failed");
170         }
171
172         boost::filesystem::path path (buffer);
173         path = boost::filesystem::canonical (path);
174         path = path.parent_path ();
175         path = path.parent_path ();
176         return path;
177 }
178 #endif
179
180 boost::filesystem::path
181 shared_path ()
182 {
183 #ifdef DCPOMATIC_LINUX
184         char const * p = getenv ("DCPOMATIC_LINUX_SHARE_PREFIX");
185         if (p) {
186                 return p;
187         }
188         return boost::filesystem::canonical (LINUX_SHARE_PREFIX);
189 #endif
190 #ifdef DCPOMATIC_WINDOWS
191         wchar_t dir[512];
192         GetModuleFileName (GetModuleHandle (0), dir, sizeof (dir));
193         PathRemoveFileSpec (dir);
194         boost::filesystem::path path = dir;
195         return path.parent_path();
196 #endif
197 #ifdef DCPOMATIC_OSX
198         return app_contents() / "Resources";
199 #endif
200 }
201
202 void
203 run_ffprobe (boost::filesystem::path content, boost::filesystem::path out)
204 {
205 #ifdef DCPOMATIC_WINDOWS
206         SECURITY_ATTRIBUTES security;
207         security.nLength = sizeof (security);
208         security.bInheritHandle = TRUE;
209         security.lpSecurityDescriptor = 0;
210
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)");
215                 return;
216         }
217
218         wchar_t dir[512];
219         GetModuleFileName (GetModuleHandle (0), dir, sizeof (dir));
220         PathRemoveFileSpec (dir);
221         SetCurrentDirectory (dir);
222
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;
228
229         wchar_t command[512];
230         wcscpy (command, L"ffprobe.exe \"");
231
232         wchar_t file[512];
233         MultiByteToWideChar (CP_UTF8, 0, content.string().c_str(), -1, file, sizeof(file));
234         wcscat (command, file);
235
236         wcscat (command, L"\"");
237
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)"));
242                 return;
243         }
244
245         FILE* o = fopen_boost (out, "w");
246         if (!o) {
247                 LOG_ERROR_NC (N_("ffprobe call failed (could not create output file)"));
248                 return;
249         }
250
251         CloseHandle (child_stderr_write);
252
253         while (true) {
254                 char buffer[512];
255                 DWORD read;
256                 if (!ReadFile(child_stderr_read, buffer, sizeof(buffer), &read, 0) || read == 0) {
257                         break;
258                 }
259                 fwrite (buffer, read, 1, o);
260         }
261
262         fclose (o);
263
264         WaitForSingleObject (process_info.hProcess, INFINITE);
265         CloseHandle (process_info.hProcess);
266         CloseHandle (process_info.hThread);
267         CloseHandle (child_stderr_read);
268 #endif
269
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 ());
274 #endif
275
276 #ifdef DCPOMATIC_OSX
277         boost::filesystem::path path = app_contents();
278         path /= "MacOS";
279         path /= "ffprobe";
280
281         string ffprobe = "\"" + path.string() + "\" \"" + content.string() + "\" 2> \"" + out.string() + "\"";
282         LOG_GENERAL (N_("Probing with %1"), ffprobe);
283         system (ffprobe.c_str ());
284 #endif
285 }
286
287 list<pair<string, string> >
288 mount_info ()
289 {
290         list<pair<string, string> > m;
291
292 #ifdef DCPOMATIC_LINUX
293         FILE* f = setmntent ("/etc/mtab", "r");
294         if (!f) {
295                 return m;
296         }
297
298         while (true) {
299                 struct mntent* mnt = getmntent (f);
300                 if (!mnt) {
301                         break;
302                 }
303
304                 m.push_back (make_pair (mnt->mnt_dir, mnt->mnt_type));
305         }
306
307         endmntent (f);
308 #endif
309
310         return m;
311 }
312
313 boost::filesystem::path
314 openssl_path ()
315 {
316 #ifdef DCPOMATIC_WINDOWS
317         wchar_t dir[512];
318         GetModuleFileName (GetModuleHandle (0), dir, sizeof (dir));
319         PathRemoveFileSpec (dir);
320
321         boost::filesystem::path path = dir;
322         path /= "openssl.exe";
323         return path;
324 #endif
325
326 #ifdef DCPOMATIC_OSX
327         boost::filesystem::path path = app_contents();
328         path /= "MacOS";
329         path /= "openssl";
330         return path;
331 #endif
332
333 #ifdef DCPOMATIC_LINUX
334         return "dcpomatic2_openssl";
335 #endif
336
337 }
338
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
341    with this wrapper.
342 */
343 FILE *
344 fopen_boost (boost::filesystem::path p, string t)
345 {
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 ());
350 #else
351         return fopen (p.c_str(), t.c_str ());
352 #endif
353 }
354
355 int
356 dcpomatic_fseek (FILE* stream, int64_t offset, int whence)
357 {
358 #ifdef DCPOMATIC_WINDOWS
359         return _fseeki64 (stream, offset, whence);
360 #else
361         return fseek (stream, offset, whence);
362 #endif
363 }
364
365 void
366 Waker::nudge ()
367 {
368 #ifdef DCPOMATIC_WINDOWS
369         boost::mutex::scoped_lock lm (_mutex);
370         SetThreadExecutionState (ES_SYSTEM_REQUIRED);
371 #endif
372 }
373
374 Waker::Waker ()
375 {
376 #ifdef DCPOMATIC_OSX
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);
382 #endif
383 }
384
385 Waker::~Waker ()
386 {
387 #ifdef DCPOMATIC_OSX
388         boost::mutex::scoped_lock lm (_mutex);
389         IOPMAssertionRelease (_assertion_id);
390 #endif
391 }
392
393 void
394 start_tool (boost::filesystem::path dcpomatic, string executable,
395 #ifdef DCPOMATIC_OSX
396             string app
397 #else
398             string
399 #endif
400         )
401 {
402 #if defined(DCPOMATIC_LINUX) || defined(DCPOMATIC_WINDOWS)
403         boost::filesystem::path batch = dcpomatic.parent_path() / executable;
404 #endif
405
406 #ifdef DCPOMATIC_OSX
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
412         batch /= app;
413         batch /= "Contents";
414         batch /= "MacOS";
415         batch /= executable;
416 #endif
417
418 #if defined(DCPOMATIC_LINUX) || defined(DCPOMATIC_OSX)
419         pid_t pid = fork ();
420         if (pid == 0) {
421                 int const r = system (batch.string().c_str());
422                 exit (WEXITSTATUS (r));
423         }
424 #endif
425
426 #ifdef DCPOMATIC_WINDOWS
427         STARTUPINFO startup_info;
428         ZeroMemory (&startup_info, sizeof (startup_info));
429         startup_info.cb = sizeof (startup_info);
430
431         PROCESS_INFORMATION process_info;
432         ZeroMemory (&process_info, sizeof (process_info));
433
434         wchar_t cmd[512];
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);
437 #endif
438 }
439
440 void
441 start_batch_converter (boost::filesystem::path dcpomatic)
442 {
443         start_tool (dcpomatic, "dcpomatic2_batch", "DCP-o-matic\\ 2\\ Batch\\ Converter.app");
444 }
445
446 void
447 start_player (boost::filesystem::path dcpomatic)
448 {
449         start_tool (dcpomatic, "dcpomatic2_player", "DCP-o-matic\\ 2\\ Player.app");
450 }
451
452 uint64_t
453 thread_id ()
454 {
455 #ifdef DCPOMATIC_WINDOWS
456         return (uint64_t) GetCurrentThreadId ();
457 #else
458         return (uint64_t) pthread_self ();
459 #endif
460 }
461
462 int
463 avio_open_boost (AVIOContext** s, boost::filesystem::path file, int flags)
464 {
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);
470         delete[] utf8;
471         return r;
472 #else
473         return avio_open (s, file.c_str(), flags);
474 #endif
475 }
476
477 #ifdef DCPOMATIC_WINDOWS
478 void
479 maybe_open_console ()
480 {
481         if (Config::instance()->win32_console ()) {
482                 AllocConsole();
483
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);
488                 *stdout = *hf_out;
489
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);
494                 *stdin = *hf_in;
495         }
496 }
497 #endif
498
499 boost::filesystem::path
500 home_directory ()
501 {
502 #if defined(DCPOMATIC_LINUX) || defined(DCPOMATIC_OSX)
503                 return getenv("HOME");
504 #endif
505 #ifdef DCPOMATIC_WINDOWS
506                 return boost::filesystem::path(getenv("HOMEDRIVE")) / boost::filesystem::path(getenv("HOMEPATH"));
507 #endif
508 }
509
510 string
511 command_and_read (string cmd)
512 {
513 #ifdef DCPOMATIC_LINUX
514         FILE* pipe = popen (cmd.c_str(), "r");
515         if (!pipe) {
516                 throw runtime_error ("popen failed");
517         }
518
519         string result;
520         char buffer[128];
521         try {
522                 while (fgets(buffer, sizeof(buffer), pipe)) {
523                         result += buffer;
524                 }
525         } catch (...) {
526                 pclose (pipe);
527                 throw;
528         }
529
530         pclose (pipe);
531         return result;
532 #endif
533
534         return "";
535 }
536
537 /** @return true if this process is a 32-bit one running on a 64-bit-capable OS */
538 bool
539 running_32_on_64 ()
540 {
541 #ifdef DCPOMATIC_WINDOWS
542         BOOL p;
543         IsWow64Process (GetCurrentProcess(), &p);
544         return p;
545 #endif
546         /* XXX: assuming nobody does this on Linux / OS X */
547         return false;
548 }
549
550 #ifdef DCPOMATIC_OSX
551 static void
552 disk_appeared(DADiskRef disk, void* context)
553 {
554         const char* name = DADiskGetBSDName (disk);
555         if (name) {
556                 list<Drive>* drives = reinterpret_cast<list<Drive>*> (context);
557                 drives->push_back (Drive(name, 0, false, optional<string>(), optional<string>()));
558         }
559 }
560 #endif
561
562 vector<Drive>
563 get_drives ()
564 {
565         vector<Drive> drives;
566
567 #ifdef DCPOMATIC_WINDOWS
568
569         const GUID GUID_DEVICE_INTERFACE_DISK = {
570                 0x53F56307L, 0xB6BF, 0x11D0, { 0x94, 0xF2, 0x00, 0xA0, 0xC9, 0x1E, 0xFB, 0x8B }
571         };
572
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");
576                 return drives;
577         }
578
579         int i = 0;
580         while (true) {
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());
585                         return drives;
586                 }
587                 ++i;
588
589                 wchar_t friendly_name_buffer[MAX_PATH];
590                 ZeroMemory (&friendly_name_buffer, sizeof(friendly_name_buffer));
591
592                 bool r = SetupDiGetDeviceRegistryPropertyW (
593                         device_info, &device_info_data, SPDRP_FRIENDLYNAME, 0, static_cast<PBYTE>(wbuffer), sizeof(wbuffer), 0
594                         );
595
596                 if (r) {
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 */
602                         delete[] utf8;
603                 }
604
605                 int j = 0;
606                 while (true) {
607                         SP_DEVICE_INTERFACE_DATA device_interface_data;
608                         device_interface_data.cbSize = sizeof(SP_DEVICE_INTERFACE_DATA);
609
610                         bool r = SetupDiEnumDeviceInterfaces (device_info, &device_info_data, &GUID_DEVICE_INTERFACE_DISK, j, &device_interface_data);
611                         if (!r) {
612                                 DWORD e = GetLastError();
613                                 if (e == ERROR_NO_MORE_ITEMS) {
614                                         break;
615                                 } else {
616                                         LOG_DIST("SetupDiEnumDeviceInterfaces failed (%1)", e);
617                                         return drives;
618                                 }
619                         }
620
621                         DWORD size;
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");
626                                 return drives;
627                         }
628
629                         device_detail_data->cbSize = sizeof(SP_DEVICE_INTERFACE_DETAIL_DATA_W);
630
631                         r = SetupDiGetDeviceInterfaceDetailW (device_info, &device_interface_data, device_detail_data, size, &size, 0);
632                         if (!r) {
633                                 LOG_DIST_NC("SetupDiGetDeviceInterfaceDetailW failed");
634                                 return 1;
635                         }
636
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
641                                 );
642
643                         if (device == INVALID_HANDLE_VALUE) {
644                                 LOG_DIST_NC("CreateFileW failed");
645                                 return drives;
646                         }
647
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
652                                 );
653
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 */
657                         }
658
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
663                                 );
664
665                         if (r) {
666                                 LOG_DIST("GET_DEVICE_NUMBER gives %1", disk_number.DeviceNumber);
667                                 /* Disk number for \\.\PHYSICALDRIVEx is device_number.DeviceNumber */
668                         }
669
670                         ++j;
671                 }
672         }
673 #endif
674
675 #ifdef DCPOMATIC_OSX
676         DASessionRef session = DASessionCreate(kCFAllocatorDefault);
677         if (!session) {
678                 return drives;
679         }
680
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);
687         CFRelease(session);
688 #endif
689
690 #ifdef DCPOMATIC_LINUX
691
692         using namespace boost::filesystem;
693         list<string> mounted_devices;
694         std::ifstream f("/proc/mounts");
695         string line;
696         while (f.good()) {
697                 getline(f, line);
698                 vector<string> bits;
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]);
703                 }
704         }
705
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);
713                 }
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;
717                         if (size == 0) {
718                                 continue;
719                         }
720                         bool mounted = false;
721                         optional<string> vendor;
722                         try {
723                                 vendor = dcp::file_to_string("/sys/block/" + name + "/device/vendor");
724                                 boost::trim(*vendor);
725                         } catch (...) {}
726                         optional<string> model;
727                         try {
728                                 model = dcp::file_to_string("/sys/block/" + name + "/device/model");
729                                 boost::trim(*model);
730                         } catch (...) {}
731                         BOOST_FOREACH (string j, mounted_devices) {
732                                 if (boost::algorithm::starts_with(j, "/dev/" + name)) {
733                                         mounted = true;
734                                 }
735                         }
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]"));
738                 }
739         }
740 #endif
741         return drives;
742 }
743
744 string
745 Drive::description () const
746 {
747         char gb[64];
748         snprintf(gb, 64, "%.1f", _size / 1000000000.0);
749
750         string name;
751         if (_vendor) {
752                 name += *_vendor;
753         }
754         if (_model) {
755                 if (name.size() > 0) {
756                         name += " " + *_model;
757                 }
758         }
759         if (name.size() == 0) {
760                 name = _("Unknown");
761         }
762
763         return String::compose("%1 (%2 GB) [%3]", name, gb, _internal_name);
764 }
765
766 #ifdef DCPOMATIC_LINUX
767 void
768 unprivileged ()
769 {
770         uid_t ruid, euid, suid;
771         if (getresuid(&ruid, &euid, &suid) == -1) {
772                 cerr << "getresuid() failed.\n";
773                 exit (EXIT_FAILURE);
774         }
775         seteuid (ruid);
776 }
777
778 PrivilegeEscalator::~PrivilegeEscalator ()
779 {
780         unprivileged ();
781 }
782
783 PrivilegeEscalator::PrivilegeEscalator ()
784 {
785         seteuid (0);
786 }
787 #endif
788
789 boost::filesystem::path
790 config_path ()
791 {
792         boost::filesystem::path p;
793 #ifdef DCPOMATIC_OSX
794                 p /= g_get_home_dir ();
795                 p /= "Library";
796                 p /= "Preferences";
797                 p /= "com.dcpomatic";
798                 p /= "2";
799 #else
800                 p /= g_get_user_config_dir ();
801                 p /= "dcpomatic2";
802 #endif
803         return p;
804 }