2 Copyright (C) 2019-2021 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 "lib/compose.hpp"
23 #include "lib/cross.h"
24 #include "lib/dcpomatic_log.h"
25 #include "lib/digester.h"
26 #include "lib/disk_writer_messages.h"
27 #include "lib/exceptions.h"
29 #include "lib/file_log.h"
30 #include "lib/state.h"
31 #include "lib/nanomsg.h"
33 #include "lib/version.h"
34 #include <dcp/warnings.h>
36 #ifdef DCPOMATIC_POSIX
37 #include <sys/ioctl.h>
38 #include <sys/types.h>
43 #include "lib/stdout_log.h"
46 #include <lwext4/file_dev.h>
52 #ifdef DCPOMATIC_LINUX
53 #include <polkit/polkit.h>
57 #ifdef DCPOMATIC_WINDOWS
59 #include <lwext4/file_windows.h>
63 LIBDCP_DISABLE_WARNINGS
65 LIBDCP_ENABLE_WARNINGS
68 #include <sys/types.h>
69 #include <boost/filesystem.hpp>
70 #include <boost/algorithm/string.hpp>
77 using std::runtime_error;
80 using boost::optional;
83 #define SHORT_TIMEOUT 100
84 #define LONG_TIMEOUT 2000
87 #ifdef DCPOMATIC_LINUX
88 static PolkitAuthority* polkit_authority = nullptr;
90 static Nanomsg* nanomsg = nullptr;
93 #ifdef DCPOMATIC_LINUX
95 polkit_callback (GObject *, GAsyncResult* res, gpointer data)
97 auto parameters = reinterpret_cast<std::pair<std::function<void ()>, std::function<void ()>>*> (data);
98 GError* error = nullptr;
99 auto result = polkit_authority_check_authorization_finish (polkit_authority, res, &error);
103 LOG_DISK("polkit authority check failed (check_authorization_finish failed with %1)", error->message);
106 if (polkit_authorization_result_get_is_authorized(result)) {
110 if (polkit_authorization_result_get_is_challenge(result)) {
111 LOG_DISK_NC("polkit authority check failed (challenge)");
113 LOG_DISK_NC("polkit authority check failed (not authorized)");
119 parameters->second();
125 g_object_unref (result);
131 #ifdef DCPOMATIC_LINUX
132 void request_privileges (string action, std::function<void ()> granted, std::function<void ()> denied)
134 void request_privileges (string, std::function<void ()> granted, std::function<void ()>)
137 #ifdef DCPOMATIC_LINUX
138 polkit_authority = polkit_authority_get_sync (0, 0);
139 auto subject = polkit_unix_process_new_for_owner (getppid(), 0, -1);
141 auto parameters = new std::pair<std::function<void ()>, std::function<void ()>>(granted, denied);
142 polkit_authority_check_authorization (
143 polkit_authority, subject, action.c_str(), 0, POLKIT_CHECK_AUTHORIZATION_FLAGS_ALLOW_USER_INTERACTION, 0, polkit_callback, parameters
155 using namespace boost::algorithm;
157 auto s = nanomsg->receive (0);
162 LOG_DISK("Writer receives command: %1", *s);
164 if (*s == DISK_WRITER_QUIT) {
166 } else if (*s == DISK_WRITER_PING) {
167 nanomsg->send(DISK_WRITER_PONG "\n", LONG_TIMEOUT);
168 } else if (*s == DISK_WRITER_UNMOUNT) {
169 auto xml_head = nanomsg->receive (LONG_TIMEOUT);
170 auto xml_body = nanomsg->receive (LONG_TIMEOUT);
171 if (!xml_head || !xml_body) {
172 LOG_DISK_NC("Failed to receive unmount request");
173 throw CommunicationFailedError ();
175 auto xml = *xml_head + *xml_body;
177 "com.dcpomatic.write-drive",
179 bool const success = Drive(xml).unmount();
180 bool sent_reply = false;
182 sent_reply = nanomsg->send(DISK_WRITER_OK "\n", LONG_TIMEOUT);
184 sent_reply = nanomsg->send(DISK_WRITER_ERROR "\nCould not unmount drive\n1\n", LONG_TIMEOUT);
187 LOG_DISK_NC("CommunicationFailedError in unmount_finished");
188 throw CommunicationFailedError ();
192 if (!nanomsg->send(DISK_WRITER_ERROR "\nCould not get permission to unmount drive\n1\n", LONG_TIMEOUT)) {
193 LOG_DISK_NC("CommunicationFailedError in unmount_finished");
194 throw CommunicationFailedError ();
197 } else if (*s == DISK_WRITER_WRITE) {
198 auto device_opt = nanomsg->receive (LONG_TIMEOUT);
200 LOG_DISK_NC("Failed to receive write request");
201 throw CommunicationFailedError();
203 auto device = *device_opt;
205 vector<boost::filesystem::path> dcp_paths;
207 auto dcp_path_opt = nanomsg->receive (LONG_TIMEOUT);
209 LOG_DISK_NC("Failed to receive write request");
210 throw CommunicationFailedError();
212 if (*dcp_path_opt != "") {
213 dcp_paths.push_back(*dcp_path_opt);
219 /* Do some basic sanity checks; this is a bit belt-and-braces but it can't hurt... */
222 if (!starts_with(device, "/dev/disk")) {
223 LOG_DISK ("Will not write to %1", device);
224 nanomsg->send(DISK_WRITER_ERROR "\nRefusing to write to this drive\n1\n", LONG_TIMEOUT);
228 #ifdef DCPOMATIC_LINUX
229 if (!starts_with(device, "/dev/sd") && !starts_with(device, "/dev/hd")) {
230 LOG_DISK ("Will not write to %1", device);
231 nanomsg->send(DISK_WRITER_ERROR "\nRefusing to write to this drive\n1\n", LONG_TIMEOUT);
235 #ifdef DCPOMATIC_WINDOWS
236 if (!starts_with(device, "\\\\.\\PHYSICALDRIVE")) {
237 LOG_DISK ("Will not write to %1", device);
238 nanomsg->send(DISK_WRITER_ERROR "\nRefusing to write to this drive\n1\n", LONG_TIMEOUT);
243 bool on_drive_list = false;
244 bool mounted = false;
245 for (auto const& i: Drive::get()) {
246 if (i.device() == device) {
247 on_drive_list = true;
248 mounted = i.mounted();
252 if (!on_drive_list) {
253 LOG_DISK ("Will not write to %1 as it's not recognised as a drive", device);
254 nanomsg->send(DISK_WRITER_ERROR "\nRefusing to write to this drive\n1\n", LONG_TIMEOUT);
258 LOG_DISK ("Will not write to %1 as it's mounted", device);
259 nanomsg->send(DISK_WRITER_ERROR "\nRefusing to write to this drive\n1\n", LONG_TIMEOUT);
263 LOG_DISK("Here we go writing these to %1", device);
264 for (auto dcp: dcp_paths) {
265 LOG_DISK(" %1", dcp);
269 "com.dcpomatic.write-drive",
270 [dcp_paths, device]() {
271 #if defined(DCPOMATIC_LINUX)
272 auto posix_partition = device;
273 /* XXX: don't know if this logic is sensible */
274 if (posix_partition.size() > 0 && isdigit(posix_partition[posix_partition.length() - 1])) {
275 posix_partition += "p1";
277 posix_partition += "1";
279 dcpomatic::write (dcp_paths, device, posix_partition, nanomsg);
280 #elif defined(DCPOMATIC_OSX)
281 auto fast_device = boost::algorithm::replace_first_copy (device, "/dev/disk", "/dev/rdisk");
282 dcpomatic::write (dcp_paths, fast_device, fast_device + "s1", nanomsg);
283 #elif defined(DCPOMATIC_WINDOWS)
284 dcpomatic::write (dcp_paths, device, "", nanomsg);
289 nanomsg->send(DISK_WRITER_ERROR "\nCould not obtain authorization to write to the drive\n1\n", LONG_TIMEOUT);
295 } catch (exception& e) {
296 LOG_DISK("Exception (from idle): %1", e.what());
304 /* On macOS this is running as root, so config_path() will be somewhere in root's
305 * home. Instead, just write to stdout as the macOS process control stuff will
306 * redirect this to a file in /var/log
308 dcpomatic_log.reset(new StdoutLog(LogEntry::TYPE_DISK));
309 LOG_DISK("dcpomatic_disk_writer %1 started uid=%2 euid=%3", dcpomatic_git_commit, getuid(), geteuid());
311 /* XXX: this is a hack, but I expect we'll need logs and I'm not sure if there's
312 * a better place to put them.
314 dcpomatic_log.reset(new FileLog(State::write_path("disk_writer.log"), LogEntry::TYPE_DISK));
315 LOG_DISK_NC("dcpomatic_disk_writer started");
319 /* I *think* this consumes the notifyd event that we used to start the process, so we only
320 * get started once per notification.
322 xpc_set_event_stream_handler("com.apple.notifyd.matching", DISPATCH_TARGET_QUEUE_DEFAULT, ^(xpc_object_t) {});
326 nanomsg = new Nanomsg (false);
327 } catch (runtime_error& e) {
328 LOG_DISK_NC("Could not set up nanomsg socket");
332 LOG_DISK_NC("Entering main loop");
333 auto ml = Glib::MainLoop::create ();
334 Glib::signal_timeout().connect(sigc::ptr_fun(&idle), 500);