07a70cc0b871a1920d6fdfef9facc753c4a7aec2
[dcpomatic.git] / src / lib / cross_linux.cc
1 /*
2     Copyright (C) 2012-2021 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
22 #include "compose.hpp"
23 #include "config.h"
24 #include "cross.h"
25 #include "dcpomatic_log.h"
26 #include "dcpomatic_log.h"
27 #include "exceptions.h"
28 #include "log.h"
29 #include "warnings.h"
30 #include <dcp/raw_convert.h>
31 #include <glib.h>
32 DCPOMATIC_DISABLE_WARNINGS
33 extern "C" {
34 #include <libavformat/avio.h>
35 }
36 DCPOMATIC_ENABLE_WARNINGS
37 #include <boost/algorithm/string.hpp>
38 #if BOOST_VERSION >= 106100
39 #include <boost/dll/runtime_symbol_info.hpp>
40 #endif
41 #include <unistd.h>
42 #include <mntent.h>
43 #include <sys/types.h>
44 #include <sys/mount.h>
45 #include <ifaddrs.h>
46 #include <netinet/in.h>
47 #include <arpa/inet.h>
48 #include <fstream>
49
50 #include "i18n.h"
51
52
53 using std::cerr;
54 using std::cout;
55 using std::ifstream;
56 using std::list;
57 using std::make_pair;
58 using std::pair;
59 using std::string;
60 using std::vector;
61 using boost::optional;
62
63
64 /** @param s Number of seconds to sleep for */
65 void
66 dcpomatic_sleep_seconds (int s)
67 {
68         sleep (s);
69 }
70
71
72 void
73 dcpomatic_sleep_milliseconds (int ms)
74 {
75         usleep (ms * 1000);
76 }
77
78
79 /** @return A string of CPU information (model name etc.) */
80 string
81 cpu_info ()
82 {
83         string info;
84
85         /* This use of ifstream is ok; the filename can never
86            be non-Latin
87         */
88         ifstream f ("/proc/cpuinfo");
89         while (f.good ()) {
90                 string l;
91                 getline (f, l);
92                 if (boost::algorithm::starts_with (l, "model name")) {
93                         string::size_type const c = l.find (':');
94                         if (c != string::npos) {
95                                 info = l.substr (c + 2);
96                         }
97                 }
98         }
99
100         return info;
101 }
102
103
104 boost::filesystem::path
105 resources_path ()
106 {
107         return directory_containing_executable().parent_path() / "share" / "dcpomatic2";
108 }
109
110
111 boost::filesystem::path
112 xsd_path ()
113 {
114         if (auto appdir = getenv("APPDIR")) {
115                 return boost::filesystem::path(appdir) / "usr" / "share" / "libdcp" / "xsd";
116         }
117         return boost::filesystem::canonical(LINUX_SHARE_PREFIX) / "libdcp" / "xsd";
118 }
119
120
121 boost::filesystem::path
122 tags_path ()
123 {
124         if (auto appdir = getenv("APPDIR")) {
125                 return boost::filesystem::path(appdir) / "usr" / "share" / "libdcp" / "tags";
126         }
127         return boost::filesystem::canonical(LINUX_SHARE_PREFIX) / "libdcp" / "tags";
128 }
129
130
131 void
132 run_ffprobe (boost::filesystem::path content, boost::filesystem::path out)
133 {
134         string ffprobe = "ffprobe \"" + content.string() + "\" 2> \"" + out.string() + "\"";
135         LOG_GENERAL (N_("Probing with %1"), ffprobe);
136         int const r = system (ffprobe.c_str());
137         if (r == -1 || (WIFEXITED(r) && WEXITSTATUS(r) != 0)) {
138                 LOG_GENERAL (N_("Could not run ffprobe (system returned %1"), r);
139         }
140 }
141
142
143 list<pair<string, string>>
144 mount_info ()
145 {
146         list<pair<string, string>> m;
147
148         auto f = setmntent ("/etc/mtab", "r");
149         if (!f) {
150                 return m;
151         }
152
153         while (true) {
154                 struct mntent* mnt = getmntent (f);
155                 if (!mnt) {
156                         break;
157                 }
158
159                 m.push_back (make_pair (mnt->mnt_dir, mnt->mnt_type));
160         }
161
162         endmntent (f);
163
164         return m;
165 }
166
167
168 boost::filesystem::path
169 directory_containing_executable ()
170 {
171 #if BOOST_VERSION >= 106100
172         return boost::dll::program_location().parent_path();
173 #else
174         char buffer[PATH_MAX];
175         ssize_t N = readlink ("/proc/self/exe", buffer, PATH_MAX);
176         return boost::filesystem::path(string(buffer, N)).parent_path();
177 #endif
178 }
179
180
181 boost::filesystem::path
182 openssl_path ()
183 {
184         auto p = directory_containing_executable() / "dcpomatic2_openssl";
185         if (boost::filesystem::is_regular_file(p)) {
186                 return p;
187         }
188
189         return "dcpomatic2_openssl";
190 }
191
192
193 #ifdef DCPOMATIC_DISK
194 boost::filesystem::path
195 disk_writer_path ()
196 {
197         return directory_containing_executable() / "dcpomatic2_disk_writer";
198 }
199 #endif
200
201
202 /* Apparently there is no way to create an ofstream using a UTF-8
203    filename under Windows.  We are hence reduced to using fopen
204    with this wrapper.
205 */
206 FILE *
207 fopen_boost (boost::filesystem::path p, string t)
208 {
209         return fopen(p.c_str(), t.c_str());
210 }
211
212
213 int
214 dcpomatic_fseek (FILE* stream, int64_t offset, int whence)
215 {
216         return fseek (stream, offset, whence);
217 }
218
219
220 void
221 Waker::nudge ()
222 {
223
224 }
225
226
227 Waker::Waker ()
228 {
229
230 }
231
232
233 Waker::~Waker ()
234 {
235
236 }
237
238
239 void
240 start_tool (string executable)
241 {
242         auto batch = directory_containing_executable() / executable;
243
244         pid_t pid = fork ();
245         if (pid == 0) {
246                 int const r = system (batch.string().c_str());
247                 exit (WEXITSTATUS (r));
248         }
249 }
250
251
252 void
253 start_batch_converter ()
254 {
255         start_tool ("dcpomatic2_batch");
256 }
257
258
259 void
260 start_player ()
261 {
262         start_tool ("dcpomatic2_player");
263 }
264
265
266 uint64_t
267 thread_id ()
268 {
269         return (uint64_t) pthread_self ();
270 }
271
272
273 int
274 avio_open_boost (AVIOContext** s, boost::filesystem::path file, int flags)
275 {
276         return avio_open (s, file.c_str(), flags);
277 }
278
279
280 boost::filesystem::path
281 home_directory ()
282 {
283         return getenv("HOME");
284 }
285
286
287 /** @return true if this process is a 32-bit one running on a 64-bit-capable OS */
288 bool
289 running_32_on_64 ()
290 {
291         /* I'm assuming nobody does this on Linux */
292         return false;
293 }
294
295
296 static
297 vector<pair<string, string>>
298 get_mounts (string prefix)
299 {
300         vector<pair<string, string>> mounts;
301
302         std::ifstream f("/proc/mounts");
303         string line;
304         while (f.good()) {
305                 getline(f, line);
306                 vector<string> bits;
307                 boost::algorithm::split (bits, line, boost::is_any_of(" "));
308                 if (bits.size() > 1 && boost::algorithm::starts_with(bits[0], prefix)) {
309                         boost::algorithm::replace_all (bits[1], "\\040", " ");
310                         mounts.push_back(make_pair(bits[0], bits[1]));
311                         LOG_DISK("Found mounted device %1 from prefix %2", bits[0], prefix);
312                 }
313         }
314
315         return mounts;
316 }
317
318
319 vector<Drive>
320 Drive::get ()
321 {
322         vector<Drive> drives;
323
324         using namespace boost::filesystem;
325         auto mounted_devices = get_mounts("/dev/");
326
327         for (auto i: directory_iterator("/sys/block")) {
328                 string const name = i.path().filename().string();
329                 path device_type_file("/sys/block/" + name + "/device/type");
330                 optional<string> device_type;
331                 if (exists(device_type_file)) {
332                         device_type = dcp::file_to_string (device_type_file);
333                         boost::trim(*device_type);
334                 }
335                 /* Device type 5 is "SCSI_TYPE_ROM" in blkdev.h; seems usually to be a CD/DVD drive */
336                 if (!boost::algorithm::starts_with(name, "loop") && (!device_type || *device_type != "5")) {
337                         uint64_t const size = dcp::raw_convert<uint64_t>(dcp::file_to_string(i / "size")) * 512;
338                         if (size == 0) {
339                                 continue;
340                         }
341                         optional<string> vendor;
342                         try {
343                                 vendor = dcp::file_to_string("/sys/block/" + name + "/device/vendor");
344                                 boost::trim(*vendor);
345                         } catch (...) {}
346                         optional<string> model;
347                         try {
348                                 model = dcp::file_to_string("/sys/block/" + name + "/device/model");
349                                 boost::trim(*model);
350                         } catch (...) {}
351                         vector<boost::filesystem::path> mount_points;
352                         for (auto const& j: mounted_devices) {
353                                 if (boost::algorithm::starts_with(j.first, "/dev/" + name)) {
354                                         mount_points.push_back (j.second);
355                                 }
356                         }
357                         drives.push_back(Drive("/dev/" + name, mount_points, size, vendor, model));
358                         LOG_DISK_NC(drives.back().log_summary());
359                 }
360         }
361
362         return drives;
363 }
364
365
366 bool
367 Drive::unmount ()
368 {
369         for (auto i: _mount_points) {
370                 int const r = umount(i.string().c_str());
371                 LOG_DISK("Tried to unmount %1 and got %2 and %3", i.string(), r, errno);
372                 if (r == -1) {
373                         return false;
374                 }
375         }
376         return true;
377 }
378
379
380 boost::filesystem::path
381 config_path (optional<string> version)
382 {
383         boost::filesystem::path p;
384         p /= g_get_user_config_dir ();
385         p /= "dcpomatic2";
386         if (version) {
387                 p /= *version;
388         }
389         return p;
390 }
391
392
393 void
394 disk_write_finished ()
395 {
396
397 }
398
399
400 string
401 dcpomatic::get_process_id ()
402 {
403         return dcp::raw_convert<string>(getpid());
404 }
405
406
407 boost::filesystem::path
408 fix_long_path (boost::filesystem::path path)
409 {
410         return path;
411 }
412
413
414 bool
415 show_in_file_manager (boost::filesystem::path dir, boost::filesystem::path)
416 {
417         int r = system ("which nautilus");
418         if (WEXITSTATUS(r) == 0) {
419                 r = system (String::compose("nautilus \"%1\"", dir.string()).c_str());
420                 return static_cast<bool>(WEXITSTATUS(r));
421         } else {
422                 int r = system ("which konqueror");
423                 if (WEXITSTATUS(r) == 0) {
424                         r = system (String::compose("konqueror \"%1\"", dir.string()).c_str());
425                         return static_cast<bool>(WEXITSTATUS(r));
426                 }
427         }
428
429         return true;
430 }
431