4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions are
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
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"
58 static bool seen_torrent
= false;
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
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
};
85 const char *name
, *altname
;
87 enum setting_type type
;
89 static struct setting settings
[] = {
90 { "connections-limit", "connections_limit", pack
.connections_limit
,
92 { "download-rate-limit", "download_rate_limit", pack
.download_rate_limit
,
94 { "listen-interfaces", "listen_interfaces", pack
.listen_interfaces
,
96 { "outgoing-interfaces", "outgoing_interfaces", pack
.outgoing_interfaces
,
98 { "upload-rate-limit", "upload_rate_limit", pack
.upload_rate_limit
,
100 { "user-agent", "user_agent", pack
.user_agent
,
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
113 static pthread_cond_t cond
= PTHREAD_COND_INITIALIZER
;
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
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"
140 #pragma GCC diagnostic pop
152 torrent_config (const char *key
, const char *value
)
154 if (strcmp (key
, "torrent") == 0) {
156 nbdkit_error ("torrent cannot be specified more than once");
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");
171 else if (strncmp (value
, "file:", 5) == 0) {
175 else if (strncmp (value
, "magnet:", 7) == 0) {
176 libtorrent::error_code err
;
177 parse_magnet_uri (value
, params
, err
);
179 nbdkit_error ("parsing magnet uri failed: %s",
180 err
.message().c_str());
186 CLEANUP_FREE
char *torrent_file
= nbdkit_realpath (value
);
187 libtorrent::error_code err
;
188 if (torrent_file
== NULL
)
190 params
.ti
= std::make_shared
<libtorrent::torrent_info
> (torrent_file
,
193 nbdkit_error ("parsing torrent metadata failed: %s",
194 err
.message().c_str());
201 else if (strcmp (key
, "file") == 0) {
202 file
= strdup (value
);
204 nbdkit_error ("strdup: %m");
210 else if (strcmp (key
, "cache") == 0) {
212 cache
= nbdkit_realpath (value
);
215 clean_cache_on_exit
= false;
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
) {
228 vbps
= nbdkit_parse_size (value
);
231 pack
.set_int (settings
[i
].setting
, int (vbps
/ 8));
234 if (nbdkit_parse_int (key
, value
, &vi
) == -1)
236 pack
.set_int (settings
[i
].setting
, vi
);
239 pack
.set_str (settings
[i
].setting
, value
);
247 nbdkit_error ("unknown parameter '%s'", key
);
252 torrent_config_complete (void)
255 nbdkit_error ("you must specify a torrent or magnet link");
259 /* If no cache was given, create a temporary directory under $TMPDIR. */
261 const char *tmpdir
= getenv ("TMPDIR") ? : LARGE_TMPDIR
;
263 if (asprintf (&cache
, "%s/torrentXXXXXX", tmpdir
) == -1) {
264 nbdkit_error ("asprintf: %m");
268 if (mkdtemp (cache
) == NULL
) {
269 nbdkit_error ("mkdtemp: %m");
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
);
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. */
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");
314 /* If the file parameter was not set, pick the largest file. */
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
);
327 file
= strdup (path
.c_str());
333 nbdkit_debug ("torrent: no file could be found to serve");
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
) {
341 size
= ti
->files().file_size(i
);
347 nbdkit_error ("cannot find file ā%sā in the torrent", file
);
351 nbdkit_debug ("torrent: serving file index %d: %s",
352 index_
.load(), file
);
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
)) {
368 else if (add_torrent_alert
*p2
= alert_cast
<add_torrent_alert
>(alert
)) {
370 if (handle
.status().has_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.
385 alerts_thread (void *arg
)
388 if (!session
->wait_for_alert (libtorrent::seconds (5)))
391 std::vector
<libtorrent::alert
*> alerts
;
392 session
->pop_alerts (&alerts
);
393 for (std::vector
<libtorrent::alert
*>::iterator i
= alerts
.begin();
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.
408 torrent_after_fork (void)
413 /* Create the session. */
414 session
= new libtorrent::session (pack
);
416 /* for what reason? XXX */
417 nbdkit_error ("could not create libtorrent session");
420 session
->async_add_torrent (params
);
422 err
= pthread_create (&thread
, NULL
, alerts_thread
, NULL
);
425 nbdkit_error ("pthread_create: %m");
433 torrent_preconnect (int readonly
)
436 /* Wait for a piece to be downloaded, which implicitly waits for
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);
456 torrent_open (int readonly
)
458 CLEANUP_FREE
char *path
= NULL
;
462 if (asprintf (&path
, "%s/%s", cache
, file
) == -1) {
463 nbdkit_error ("asprintf: %m");
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
);
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
);
483 nbdkit_error ("calloc: %m");
492 torrent_close (void *hv
)
494 struct handle
*h
= (struct handle
*) hv
;
500 #define THREAD_MODEL NBDKIT_THREAD_MODEL_PARALLEL
502 /* Get the file size. */
504 torrent_get_size (void *hv
)
509 /* Read data from the file. */
511 torrent_pread (void *hv
, void *buf
, uint32_t count
, uint64_t offset
,
514 struct handle
*h
= (struct handle
*) hv
;
515 auto ti
= handle
.torrent_file();
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
,
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
536 if (pread (h
->fd
, buf
, part
.length
, offset
) == -1) {
537 nbdkit_error ("pread: %m");
541 count
-= part
.length
;
542 offset
+= part
.length
;
543 buf
= (int8_t *)buf
+ part
.length
;
549 /* https://bugzilla.redhat.com/show_bug.cgi?id=1418328#c9 */
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
;
570 static struct nbdkit_plugin plugin
= create_plugin ();
572 NBDKIT_REGISTER_PLUGIN(plugin
)