Update Red Hat Copyright Notices
[nbdkit.git] / plugins / torrent / torrent.cpp
blobf229b3fe36c2cf93c21d33488dd4381c73f7899c
1 /* nbdkit
2 * Copyright Red Hat
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions are
6 * met:
8 * * Redistributions of source code must retain the above copyright
9 * notice, this list of conditions and the following disclaimer.
11 * * Redistributions in binary form must reproduce the above copyright
12 * notice, this list of conditions and the following disclaimer in the
13 * documentation and/or other materials provided with the distribution.
15 * * Neither the name of Red Hat nor the names of its contributors may be
16 * used to endorse or promote products derived from this software without
17 * specific prior written permission.
19 * THIS SOFTWARE IS PROVIDED BY RED HAT AND CONTRIBUTORS ''AS IS'' AND
20 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
21 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
22 * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL RED HAT OR
23 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
24 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
25 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
26 * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
27 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
28 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
29 * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
30 * SUCH DAMAGE.
33 #include <config.h>
35 #include <cstdlib>
36 #include <iostream>
37 #include <atomic>
39 #include <inttypes.h>
40 #include <assert.h>
42 #include <pthread.h>
44 #define NBDKIT_API_VERSION 2
45 #include <nbdkit-plugin.h>
47 #include <libtorrent/alert.hpp>
48 #include <libtorrent/alert_types.hpp>
49 #include <libtorrent/download_priority.hpp>
50 #include <libtorrent/magnet_uri.hpp>
51 #include <libtorrent/session.hpp>
52 #include <libtorrent/torrent_info.hpp>
53 #include <libtorrent/version.hpp>
55 #include "array-size.h"
56 #include "cleanup.h"
58 static bool seen_torrent = false;
60 static char *cache;
61 static bool clean_cache_on_exit = true;
63 /* This lock protects all the static fields that might be accessed by
64 * the background thread, as well as the condition.
66 static pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;
68 /* Name, index within the torrent, and size of the file that we are
69 * serving. It's called index_ because of the function from
70 * <strings.h>.
72 static char *file;
73 static std::atomic_int index_(-1);
74 static int64_t size = -1;
76 static libtorrent::session *session;
77 static libtorrent::torrent_handle handle;
79 /* Torrent handle settings. */
80 static libtorrent::add_torrent_params params;
81 static libtorrent::settings_pack pack;
83 enum setting_type { BPS, STRING, INT };
84 struct setting {
85 const char *name, *altname;
86 int setting;
87 enum setting_type type;
89 static struct setting settings[] = {
90 { "connections-limit", "connections_limit", pack.connections_limit,
91 INT },
92 { "download-rate-limit", "download_rate_limit", pack.download_rate_limit,
93 BPS },
94 { "listen-interfaces", "listen_interfaces", pack.listen_interfaces,
95 STRING },
96 { "outgoing-interfaces", "outgoing_interfaces", pack.outgoing_interfaces,
97 STRING },
98 { "upload-rate-limit", "upload_rate_limit", pack.upload_rate_limit,
99 BPS },
100 { "user-agent", "user_agent", pack.user_agent,
101 STRING },
103 static libtorrent::alert_category_t alert_cats =
104 libtorrent::alert_category::error
105 | libtorrent::alert_category::piece_progress
106 | libtorrent::alert_category::status
107 | libtorrent::alert_category::storage
110 /* This condition is used to signal the plugin when a piece has been
111 * downloaded.
113 static pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
115 static void
116 torrent_unload (void)
118 libtorrent::remove_flags_t flags;
120 if (session && handle.is_valid()) {
121 if (clean_cache_on_exit)
122 flags = libtorrent::session_handle::delete_files;
123 session->remove_torrent (handle, flags);
126 /* Although in theory libtorrent can remove all the files (see flags
127 * above), we still need to remove the temporary directory that we
128 * created, and we might as well rm -rf it. Quoting: Since we're
129 * only calling this on temporary paths that we generate, we don't
130 * need to quote this. Probably. If someone specifies a stupid
131 * TMPDIR then it could break, but that's controlled by the person
132 * running nbdkit.
134 if (clean_cache_on_exit && cache) {
135 CLEANUP_FREE char *cmd;
136 if (asprintf (&cmd, "rm -rf %s", cache) >= 0) {
137 #pragma GCC diagnostic push
138 #pragma GCC diagnostic ignored "-Wunused-result"
139 system (cmd);
140 #pragma GCC diagnostic pop
144 free (cache);
145 free (file);
147 if (session)
148 delete session;
151 static int
152 torrent_config (const char *key, const char *value)
154 if (strcmp (key, "torrent") == 0) {
155 if (seen_torrent) {
156 nbdkit_error ("torrent cannot be specified more than once");
157 return -1;
159 seen_torrent = true;
161 /* In future we want to support downloading automatically from
162 * URLs using libcurl, so "reserve" a few likely ones here.
164 if (strncmp (value, "http:", 5) == 0 ||
165 strncmp (value, "https:", 6) == 0 ||
166 strncmp (value, "ftp:", 4) == 0 ||
167 strncmp (value, "ftps:", 5) == 0) {
168 nbdkit_error ("downloading torrent files from URLs not yet implemented");
169 return -1;
171 else if (strncmp (value, "file:", 5) == 0) {
172 value += 5;
173 goto is_file;
175 else if (strncmp (value, "magnet:", 7) == 0) {
176 libtorrent::error_code err;
177 parse_magnet_uri (value, params, err);
178 if (err) {
179 nbdkit_error ("parsing magnet uri failed: %s",
180 err.message().c_str());
181 return -1;
184 else {
185 is_file:
186 CLEANUP_FREE char *torrent_file = nbdkit_realpath (value);
187 libtorrent::error_code err;
188 if (torrent_file == NULL)
189 return -1;
190 params.ti = std::make_shared<libtorrent::torrent_info> (torrent_file,
191 std::ref (err));
192 if (err) {
193 nbdkit_error ("parsing torrent metadata failed: %s",
194 err.message().c_str());
195 return -1;
198 return 0;
201 else if (strcmp (key, "file") == 0) {
202 file = strdup (value);
203 if (file == NULL) {
204 nbdkit_error ("strdup: %m");
205 return -1;
207 return 0;
210 else if (strcmp (key, "cache") == 0) {
211 free (cache);
212 cache = nbdkit_realpath (value);
213 if (cache == NULL)
214 return -1;
215 clean_cache_on_exit = false;
216 return 0;
219 /* Settings. */
220 else {
221 for (size_t i = 0; i < ARRAY_SIZE (settings); ++i) {
222 if (strcmp (key, settings[i].name) == 0 ||
223 (settings[i].altname && strcmp (key, settings[i].altname) == 0)) {
224 switch (settings[i].type) {
225 int vi;
226 int64_t vbps;
227 case BPS:
228 vbps = nbdkit_parse_size (value);
229 if (vbps == -1)
230 return -1;
231 pack.set_int (settings[i].setting, int (vbps / 8));
232 break;
233 case INT:
234 if (nbdkit_parse_int (key, value, &vi) == -1)
235 return -1;
236 pack.set_int (settings[i].setting, vi);
237 break;
238 case STRING:
239 pack.set_str (settings[i].setting, value);
240 break;
242 return 0;
247 nbdkit_error ("unknown parameter '%s'", key);
248 return -1;
251 static int
252 torrent_config_complete (void)
254 if (!seen_torrent) {
255 nbdkit_error ("you must specify a torrent or magnet link");
256 return -1;
259 /* If no cache was given, create a temporary directory under $TMPDIR. */
260 if (!cache) {
261 const char *tmpdir = getenv ("TMPDIR") ? : LARGE_TMPDIR;
263 if (asprintf (&cache, "%s/torrentXXXXXX", tmpdir) == -1) {
264 nbdkit_error ("asprintf: %m");
265 return -1;
268 if (mkdtemp (cache) == NULL) {
269 nbdkit_error ("mkdtemp: %m");
270 return -1;
273 nbdkit_debug ("torrent: cache directory: %s%s",
274 cache, clean_cache_on_exit ? " (cleaned up on exit)" : "");
275 params.save_path = cache;
277 pack.set_str (pack.dht_bootstrap_nodes,
278 "router.bittorrent.com:6881,"
279 "router.utorrent.com:6881,"
280 "dht.transmissionbt.com:6881");
281 pack.set_bool (pack.auto_sequential, true);
282 pack.set_bool (pack.strict_end_game_mode, false);
283 pack.set_bool (pack.announce_to_all_trackers, true);
284 pack.set_bool (pack.announce_to_all_tiers, true);
285 pack.set_int (pack.alert_mask, alert_cats);
287 return 0;
290 #define torrent_config_help \
291 "torrent=<TORRENT> (required) Torrent or magnet link\n" \
292 "file=DISK.iso File to serve within torrent\n" \
293 "cache=DIR Set directory to store partial downloads\n" \
294 "connections-limit=N Set limit on peer connections (dflt: 200)\n" \
295 "download-rate-limit=BPS Set download rate limit (bps)\n" \
296 "listen-interfaces=IP:PORT,... Set listening ports\n" \
297 "outgoing-interfaces=IP,IP,... Set outgoing IP addresses\n" \
298 "upload-rate-limit=BPS Set upload rate limit (bps)\n" \
299 "user-agent=STRING Set the user-agent"
301 /* We got the metadata. */
302 static void
303 got_metadata (void)
305 ACQUIRE_LOCK_FOR_CURRENT_SCOPE (&lock);
306 auto ti = handle.torrent_file();
307 int i, num_files = ti->num_files();
309 if (num_files == 0) {
310 nbdkit_error ("torrent: no files in the torrent");
311 exit (EXIT_FAILURE);
314 /* If the file parameter was not set, pick the largest file. */
315 if (!file) {
316 int64_t largest = 0;
318 nbdkit_debug ("torrent: number of files: %d", num_files);
320 for (i = 0; i < num_files; ++i) {
321 std::string path = ti->files().file_path(i);
322 int64_t sz = ti->files().file_size(i);
324 nbdkit_debug ("torrent: file[%d]: %s (size %" PRIi64 ")",
325 i, path.c_str(), sz);
326 if (sz > largest) {
327 file = strdup (path.c_str());
328 largest = sz;
332 if (!file) {
333 nbdkit_debug ("torrent: no file could be found to serve");
334 exit (EXIT_FAILURE);
337 /* We should have a file to serve now, so find its index. */
338 for (i = 0; i < num_files; ++i) {
339 if (ti->files().file_path(i) == file) {
340 index_ = i;
341 size = ti->files().file_size(i);
342 break;
346 if (index_ == -1) {
347 nbdkit_error ("cannot find file ā€˜%sā€™ in the torrent", file);
348 exit (EXIT_FAILURE);
351 nbdkit_debug ("torrent: serving file index %d: %s",
352 index_.load(), file);
355 static void
356 handle_alert (libtorrent::alert *alert)
358 using namespace libtorrent;
360 nbdkit_debug ("torrent: %s", alert->message().c_str());
362 if (metadata_received_alert *p1 =
363 alert_cast<metadata_received_alert>(alert)) {
364 handle = p1->handle;
365 got_metadata ();
368 else if (add_torrent_alert *p2 = alert_cast<add_torrent_alert>(alert)) {
369 handle = p2->handle;
370 if (handle.status().has_metadata)
371 got_metadata ();
374 else if (/*piece_finished_alert *p = */
375 alert_cast<piece_finished_alert>(alert)) {
376 pthread_cond_broadcast (&cond);
379 /* We just ignore any other alerts we don't know about, but
380 * they are all logged above.
384 static void *
385 alerts_thread (void *arg)
387 for (;;) {
388 if (!session->wait_for_alert (libtorrent::seconds (5)))
389 continue;
391 std::vector<libtorrent::alert*> alerts;
392 session->pop_alerts (&alerts);
393 for (std::vector<libtorrent::alert*>::iterator i = alerts.begin();
394 i != alerts.end();
395 ++i) {
396 handle_alert (*i);
401 /* Create the libtorrent session (which creates an implicit thread).
402 * Also start our own background thread to handle libtorrent alerts.
404 * We must do all of this after any forking because otherwise the
405 * threads will be stranded by fork.
407 static int
408 torrent_after_fork (void)
410 int err;
411 pthread_t thread;
413 /* Create the session. */
414 session = new libtorrent::session (pack);
415 if (!session) {
416 /* for what reason? XXX */
417 nbdkit_error ("could not create libtorrent session");
418 return -1;
420 session->async_add_torrent (params);
422 err = pthread_create (&thread, NULL, alerts_thread, NULL);
423 if (err) {
424 errno = err;
425 nbdkit_error ("pthread_create: %m");
426 return -1;
429 return 0;
432 static int
433 torrent_preconnect (int readonly)
435 if (index_ == -1) {
436 /* Wait for a piece to be downloaded, which implicitly waits for
437 * metadata.
439 ACQUIRE_LOCK_FOR_CURRENT_SCOPE (&lock);
440 pthread_cond_wait (&cond, &lock);
443 /* I believe this should never happen, but can't prove it. If it
444 * does happen let's investigate further.
446 assert (index_ >= 0);
448 return 0;
451 struct handle {
452 int fd;
455 static void *
456 torrent_open (int readonly)
458 CLEANUP_FREE char *path = NULL;
459 int fd = -1;
460 struct handle *h;
462 if (asprintf (&path, "%s/%s", cache, file) == -1) {
463 nbdkit_error ("asprintf: %m");
464 return NULL;
467 /* The file may not exist until at least one piece has been
468 * downloaded, so we may need to loop here.
470 while ((fd = open (path, O_RDONLY | O_CLOEXEC)) == -1) {
471 if (errno != ENOENT) {
472 nbdkit_error ("open: %s: %m", path);
473 return NULL;
476 /* Wait for a piece to be downloaded. */
477 ACQUIRE_LOCK_FOR_CURRENT_SCOPE (&lock);
478 pthread_cond_wait (&cond, &lock);
481 h = (struct handle *) calloc (1, sizeof *h);
482 if (h == NULL) {
483 nbdkit_error ("calloc: %m");
484 return NULL;
486 h->fd = fd;
488 return h;
491 static void
492 torrent_close (void *hv)
494 struct handle *h = (struct handle *) hv;
496 close (h->fd);
497 free (h);
500 #define THREAD_MODEL NBDKIT_THREAD_MODEL_PARALLEL
502 /* Get the file size. */
503 static int64_t
504 torrent_get_size (void *hv)
506 return size;
509 /* Read data from the file. */
510 static int
511 torrent_pread (void *hv, void *buf, uint32_t count, uint64_t offset,
512 uint32_t flags)
514 struct handle *h = (struct handle *) hv;
515 auto ti = handle.torrent_file();
517 while (count > 0) {
518 libtorrent::peer_request part =
519 ti->map_file (index_.load(), offset, (int) count);
521 part.length = std::min (ti->piece_size (part.piece) - part.start,
522 part.length);
524 while (! handle.have_piece (part.piece)) {
525 /* Tell the picker that we want this piece sooner. */
526 handle.piece_priority (part.piece, libtorrent::top_priority);
528 /* Wait for a piece to be downloaded. */
529 ACQUIRE_LOCK_FOR_CURRENT_SCOPE (&lock);
530 pthread_cond_wait (&cond, &lock);
533 /* We've got this piece in full (on disk), so we can copy it to
534 * the buffer.
536 if (pread (h->fd, buf, part.length, offset) == -1) {
537 nbdkit_error ("pread: %m");
538 return -1;
541 count -= part.length;
542 offset += part.length;
543 buf = (int8_t *)buf + part.length;
546 return 0;
549 /* https://bugzilla.redhat.com/show_bug.cgi?id=1418328#c9 */
550 namespace {
551 nbdkit_plugin create_plugin() {
552 nbdkit_plugin plugin = nbdkit_plugin ();
553 plugin.name = "torrent";
554 plugin.longname = "nbdkit bittorrent plugin";
555 plugin.version = PACKAGE_VERSION;
556 plugin.unload = torrent_unload;
557 plugin.config = torrent_config;
558 plugin.config_complete = torrent_config_complete;
559 plugin.config_help = torrent_config_help;
560 plugin.magic_config_key = "torrent";
561 plugin.after_fork = torrent_after_fork;
562 plugin.preconnect = torrent_preconnect;
563 plugin.open = torrent_open;
564 plugin.close = torrent_close;
565 plugin.get_size = torrent_get_size;
566 plugin.pread = torrent_pread;
567 return plugin;
570 static struct nbdkit_plugin plugin = create_plugin ();
572 NBDKIT_REGISTER_PLUGIN(plugin)