2 Copyright (C) 2019-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/>.
21 #include "lib/compose.hpp"
22 #include "lib/cross.h"
23 #include "lib/dcpomatic_log.h"
24 #include "lib/digester.h"
25 #include "lib/disk_writer_messages.h"
26 #include "lib/exceptions.h"
28 #include "lib/file_log.h"
29 #include "lib/nanomsg.h"
30 #include "lib/version.h"
31 #include "lib/warnings.h"
33 #ifdef DCPOMATIC_POSIX
34 #include <sys/ioctl.h>
35 #include <sys/types.h>
40 #include "lib/stdout_log.h"
43 #include <lwext4/file_dev.h>
48 #ifdef DCPOMATIC_LINUX
49 #include <polkit/polkit.h>
53 #ifdef DCPOMATIC_WINDOWS
55 #include <lwext4/file_windows.h>
59 DCPOMATIC_DISABLE_WARNINGS
61 DCPOMATIC_ENABLE_WARNINGS
64 #include <sys/types.h>
65 #include <boost/filesystem.hpp>
66 #include <boost/algorithm/string.hpp>
72 using std::runtime_error;
75 using boost::optional;
78 #define SHORT_TIMEOUT 100
79 #define LONG_TIMEOUT 2000
82 #ifdef DCPOMATIC_LINUX
83 static PolkitAuthority* polkit_authority = 0;
85 static Nanomsg* nanomsg = 0;
89 boost::filesystem::path dcp_path;
91 std::string posix_partition;
94 #ifdef DCPOMATIC_LINUX
97 polkit_callback (GObject *, GAsyncResult* res, gpointer data)
99 Parameters* parameters = reinterpret_cast<Parameters*> (data);
100 PolkitAuthorizationResult* result = polkit_authority_check_authorization_finish (polkit_authority, res, 0);
101 if (result && polkit_authorization_result_get_is_authorized(result)) {
102 dcpomatic::write (parameters->dcp_path, parameters->device, parameters->posix_partition, nanomsg);
106 g_object_unref (result);
116 using namespace boost::algorithm;
118 optional<string> s = nanomsg->receive (0);
123 LOG_DISK("Writer receives command: %1", *s);
125 if (*s == DISK_WRITER_QUIT) {
127 } else if (*s == DISK_WRITER_PING) {
128 nanomsg->send(DISK_WRITER_PONG "\n", LONG_TIMEOUT);
129 } else if (*s == DISK_WRITER_UNMOUNT) {
130 /* XXX: should do Linux polkit stuff here */
131 optional<string> xml_head = nanomsg->receive (LONG_TIMEOUT);
132 optional<string> xml_body = nanomsg->receive (LONG_TIMEOUT);
133 if (!xml_head || !xml_body) {
134 LOG_DISK_NC("Failed to receive unmount request");
135 throw CommunicationFailedError ();
137 bool const success = Drive(*xml_head + *xml_body).unmount();
138 if (!nanomsg->send (success ? (DISK_WRITER_OK "\n") : (DISK_WRITER_ERROR "\n"), LONG_TIMEOUT)) {
139 LOG_DISK_NC("CommunicationFailedError in unmount_finished");
140 throw CommunicationFailedError ();
142 } else if (*s == DISK_WRITER_WRITE) {
143 optional<string> dcp_path = nanomsg->receive (LONG_TIMEOUT);
144 optional<string> device = nanomsg->receive (LONG_TIMEOUT);
145 if (!dcp_path || !device) {
146 LOG_DISK_NC("Failed to receive write request");
147 throw CommunicationFailedError();
150 /* Do some basic sanity checks; this is a bit belt-and-braces but it can't hurt... */
153 if (!starts_with(*device, "/dev/disk")) {
154 LOG_DISK ("Will not write to %1", *device);
155 nanomsg->send(DISK_WRITER_ERROR "\nRefusing to write to this drive\n1\n", LONG_TIMEOUT);
159 #ifdef DCPOMATIC_LINUX
160 if (!starts_with(*device, "/dev/sd") && !starts_with(*device, "/dev/hd")) {
161 LOG_DISK ("Will not write to %1", *device);
162 nanomsg->send(DISK_WRITER_ERROR "\nRefusing to write to this drive\n1\n", LONG_TIMEOUT);
166 #ifdef DCPOMATIC_WINDOWS
167 if (!starts_with(*device, "\\\\.\\PHYSICALDRIVE")) {
168 LOG_DISK ("Will not write to %1", *device);
169 nanomsg->send(DISK_WRITER_ERROR "\nRefusing to write to this drive\n1\n", LONG_TIMEOUT);
174 bool on_drive_list = false;
175 bool mounted = false;
176 for (auto const& i: Drive::get()) {
177 if (i.device() == *device) {
178 on_drive_list = true;
179 mounted = i.mounted();
183 if (!on_drive_list) {
184 LOG_DISK ("Will not write to %1 as it's not recognised as a drive", *device);
185 nanomsg->send(DISK_WRITER_ERROR "\nRefusing to write to this drive\n1\n", LONG_TIMEOUT);
189 LOG_DISK ("Will not write to %1 as it's mounted", *device);
190 nanomsg->send(DISK_WRITER_ERROR "\nRefusing to write to this drive\n1\n", LONG_TIMEOUT);
194 LOG_DISK ("Here we go writing %1 to %2", *dcp_path, *device);
196 #if defined(DCPOMATIC_LINUX)
197 polkit_authority = polkit_authority_get_sync (0, 0);
198 PolkitSubject* subject = polkit_unix_process_new_for_owner (getppid(), 0, -1);
199 Parameters* parameters = new Parameters;
200 parameters->dcp_path = *dcp_path;
201 parameters->device = *device;
202 parameters->posix_partition = *device;
203 /* XXX: don't know if this logic is sensible */
204 if (parameters->posix_partition.size() > 0 && isdigit(parameters->posix_partition[parameters->posix_partition.length() - 1])) {
205 parameters->posix_partition += "p1";
207 parameters->posix_partition += "1";
209 polkit_authority_check_authorization (
210 polkit_authority, subject, "com.dcpomatic.write-drive", 0, POLKIT_CHECK_AUTHORIZATION_FLAGS_ALLOW_USER_INTERACTION, 0, polkit_callback, parameters
212 #elif defined(DCPOMATIC_OSX)
213 string fast_device = boost::algorithm::replace_first_copy (*device, "/dev/disk", "/dev/rdisk");
214 dcpomatic::write (*dcp_path, fast_device, fast_device + "s1", nanomsg);
215 #elif defined(DCPOMATIC_WINDOWS)
216 dcpomatic::write (*dcp_path, *device, "", nanomsg);
221 } catch (exception& e) {
222 LOG_DISK("Exception (from idle): %1", e.what());
230 /* On macOS this is running as root, so config_path() will be somewhere in root's
231 * home. Instead, just write to stdout as the macOS process control stuff will
232 * redirect this to a file in /var/log
234 dcpomatic_log.reset(new StdoutLog(LogEntry::TYPE_DISK));
235 LOG_DISK("dcpomatic_disk_writer %1 started", dcpomatic_git_commit);
237 /* XXX: this is a hack, but I expect we'll need logs and I'm not sure if there's
238 * a better place to put them.
240 dcpomatic_log.reset(new FileLog(config_path() / "disk_writer.log", LogEntry::TYPE_DISK));
241 LOG_DISK_NC("dcpomatic_disk_writer started");
245 /* I *think* this consumes the notifyd event that we used to start the process, so we only
246 * get started once per notification.
248 xpc_set_event_stream_handler("com.apple.notifyd.matching", DISPATCH_TARGET_QUEUE_DEFAULT, ^(xpc_object_t) {});
252 nanomsg = new Nanomsg (false);
253 } catch (runtime_error& e) {
254 LOG_DISK_NC("Could not set up nanomsg socket");
258 Glib::RefPtr<Glib::MainLoop> ml = Glib::MainLoop::create ();
259 Glib::signal_timeout().connect(sigc::ptr_fun(&idle), 500);