Connection.cxx 14.7 KB
Newer Older
1
/*
Max Kellermann's avatar
Max Kellermann committed
2
 * Copyright 2003-2020 The Music Player Daemon Project
3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
 * http://www.musicpd.org
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License along
 * with this program; if not, write to the Free Software Foundation, Inc.,
 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 */

#include "Connection.hxx"
21
#include "Error.hxx"
22 23
#include "Lease.hxx"
#include "Callback.hxx"
24
#include "event/Loop.hxx"
25
#include "net/SocketDescriptor.hxx"
26
#include "util/RuntimeError.hxx"
27 28 29 30 31 32 33

extern "C" {
#include <nfsc/libnfs.h>
}

#include <utility>

34 35 36
#ifdef _WIN32
#include <winsock2.h>
#else
37
#include <poll.h> /* for POLLIN, POLLOUT */
38
#endif
39

40
static constexpr Event::Duration NFS_MOUNT_TIMEOUT =
41
	std::chrono::minutes(1);
42

43
inline void
44
NfsConnection::CancellableCallback::Stat(nfs_context *ctx,
45
					 const char *path)
46
{
47 48
	assert(connection.GetEventLoop().IsInside());

49
	int result = nfs_stat64_async(ctx, path, Callback, this);
50
	if (result < 0)
51
		throw FormatRuntimeError("nfs_stat64_async() failed: %s",
52
					 nfs_get_error(ctx));
53 54
}

55 56 57 58 59 60 61 62 63 64 65 66
inline void
NfsConnection::CancellableCallback::Lstat(nfs_context *ctx,
					  const char *path)
{
	assert(connection.GetEventLoop().IsInside());

	int result = nfs_lstat64_async(ctx, path, Callback, this);
	if (result < 0)
		throw FormatRuntimeError("nfs_lstat64_async() failed: %s",
					 nfs_get_error(ctx));
}

67
inline void
68
NfsConnection::CancellableCallback::OpenDirectory(nfs_context *ctx,
69
						  const char *path)
70
{
71 72
	assert(connection.GetEventLoop().IsInside());

73
	int result = nfs_opendir_async(ctx, path, Callback, this);
74 75 76
	if (result < 0)
		throw FormatRuntimeError("nfs_opendir_async() failed: %s",
					 nfs_get_error(ctx));
77 78
}

79
inline void
80
NfsConnection::CancellableCallback::Open(nfs_context *ctx,
81
					 const char *path, int flags)
82
{
83 84
	assert(connection.GetEventLoop().IsInside());

85 86
	int result = nfs_open_async(ctx, path, flags,
				    Callback, this);
87 88 89
	if (result < 0)
		throw FormatRuntimeError("nfs_open_async() failed: %s",
					 nfs_get_error(ctx));
90 91
}

92
inline void
93
NfsConnection::CancellableCallback::Stat(nfs_context *ctx,
94
					 struct nfsfh *fh)
95
{
96 97
	assert(connection.GetEventLoop().IsInside());

98
	int result = nfs_fstat_async(ctx, fh, Callback, this);
99 100 101
	if (result < 0)
		throw FormatRuntimeError("nfs_fstat_async() failed: %s",
					 nfs_get_error(ctx));
102 103
}

104
inline void
105
NfsConnection::CancellableCallback::Read(nfs_context *ctx, struct nfsfh *fh,
106
					 uint64_t offset, size_t size)
107
{
108 109
	assert(connection.GetEventLoop().IsInside());

110
	int result = nfs_pread_async(ctx, fh, offset, size, Callback, this);
111 112 113
	if (result < 0)
		throw FormatRuntimeError("nfs_pread_async() failed: %s",
					 nfs_get_error(ctx));
114 115
}

116
inline void
117
NfsConnection::CancellableCallback::CancelAndScheduleClose(struct nfsfh *fh) noexcept
118
{
119
	assert(connection.GetEventLoop().IsInside());
120 121 122 123 124 125 126 127
	assert(!open);
	assert(close_fh == nullptr);
	assert(fh != nullptr);

	close_fh = fh;
	Cancel();
}

128
inline void
129
NfsConnection::CancellableCallback::PrepareDestroyContext() noexcept
130 131 132 133 134 135 136 137 138
{
	assert(IsCancelled());

	if (close_fh != nullptr) {
		connection.InternalClose(close_fh);
		close_fh = nullptr;
	}
}

139
inline void
140
NfsConnection::CancellableCallback::Callback(int err, void *data) noexcept
141
{
142 143
	assert(connection.GetEventLoop().IsInside());

144
	if (!IsCancelled()) {
145 146
		assert(close_fh == nullptr);

147 148 149 150 151 152 153
		NfsCallback &cb = Get();

		connection.callbacks.Remove(*this);

		if (err >= 0)
			cb.OnNfsCallback((unsigned)err, data);
		else
154
			cb.OnNfsError(std::make_exception_ptr(NfsClientError(-err, (const char *)data)));
155
	} else {
156 157 158 159
		if (open) {
			/* a nfs_open_async() call was cancelled - to
			   avoid a memory leak, close the newly
			   allocated file handle immediately */
160 161
			assert(close_fh == nullptr);

162
			if (err >= 0) {
Max Kellermann's avatar
Max Kellermann committed
163
				auto *fh = (struct nfsfh *)data;
164 165
				connection.Close(fh);
			}
166 167
		} else if (close_fh != nullptr)
			connection.DeferClose(close_fh);
168

169 170 171 172 173 174
		connection.callbacks.Remove(*this);
	}
}

void
NfsConnection::CancellableCallback::Callback(int err,
Rosen Penev's avatar
Rosen Penev committed
175
					     [[maybe_unused]] struct nfs_context *nfs,
176 177
					     void *data,
					     void *private_data) noexcept
178 179 180 181 182 183
{
	CancellableCallback &c = *(CancellableCallback *)private_data;
	c.Callback(err, data);
}

static constexpr unsigned
184
libnfs_to_events(int i) noexcept
185
{
186 187
	return ((i & POLLIN) ? SocketEvent::READ : 0) |
		((i & POLLOUT) ? SocketEvent::WRITE : 0);
188 189 190
}

static constexpr int
191
events_to_libnfs(unsigned i) noexcept
192
{
193 194 195 196
	return ((i & SocketEvent::READ) ? POLLIN : 0) |
		((i & SocketEvent::WRITE) ? POLLOUT : 0) |
		((i & SocketEvent::HANGUP) ? POLLHUP : 0) |
		((i & SocketEvent::ERROR) ? POLLERR : 0);
197 198
}

199
NfsConnection::~NfsConnection() noexcept
200
{
201
	assert(GetEventLoop().IsInside());
202 203 204
	assert(new_leases.empty());
	assert(active_leases.empty());
	assert(callbacks.IsEmpty());
205
	assert(deferred_close.empty());
206 207

	if (context != nullptr)
208
		DestroyContext();
209 210 211
}

void
212
NfsConnection::AddLease(NfsLease &lease) noexcept
213
{
214 215 216
	assert(GetEventLoop().IsInside());

	new_leases.push_back(&lease);
217

218
	defer_new_lease.Schedule();
219 220 221
}

void
222
NfsConnection::RemoveLease(NfsLease &lease) noexcept
223
{
224
	assert(GetEventLoop().IsInside());
225 226 227 228 229

	new_leases.remove(&lease);
	active_leases.remove(&lease);
}

230 231
void
NfsConnection::Stat(const char *path, NfsCallback &callback)
232
{
233
	assert(GetEventLoop().IsInside());
234 235 236
	assert(!callbacks.Contains(callback));

	auto &c = callbacks.Add(callback, *this, false);
237 238 239
	try {
		c.Stat(context, path);
	} catch (...) {
240
		callbacks.Remove(c);
241
		throw;
242 243 244 245 246
	}

	ScheduleSocket();
}

247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263
void
NfsConnection::Lstat(const char *path, NfsCallback &callback)
{
	assert(GetEventLoop().IsInside());
	assert(!callbacks.Contains(callback));

	auto &c = callbacks.Add(callback, *this, false);
	try {
		c.Lstat(context, path);
	} catch (...) {
		callbacks.Remove(c);
		throw;
	}

	ScheduleSocket();
}

264 265
void
NfsConnection::OpenDirectory(const char *path, NfsCallback &callback)
266
{
267
	assert(GetEventLoop().IsInside());
268 269 270
	assert(!callbacks.Contains(callback));

	auto &c = callbacks.Add(callback, *this, true);
271 272 273
	try {
		c.OpenDirectory(context, path);
	} catch (...) {
274
		callbacks.Remove(c);
275
		throw;
276 277 278 279 280 281
	}

	ScheduleSocket();
}

const struct nfsdirent *
282
NfsConnection::ReadDirectory(struct nfsdir *dir) noexcept
283
{
284 285
	assert(GetEventLoop().IsInside());

286 287 288 289
	return nfs_readdir(context, dir);
}

void
290
NfsConnection::CloseDirectory(struct nfsdir *dir) noexcept
291
{
292 293
	assert(GetEventLoop().IsInside());

294 295 296
	return nfs_closedir(context, dir);
}

297 298
void
NfsConnection::Open(const char *path, int flags, NfsCallback &callback)
299
{
300
	assert(GetEventLoop().IsInside());
301 302
	assert(!callbacks.Contains(callback));

303
	auto &c = callbacks.Add(callback, *this, true);
304 305 306
	try {
		c.Open(context, path, flags);
	} catch (...) {
307
		callbacks.Remove(c);
308
		throw;
309 310 311 312 313
	}

	ScheduleSocket();
}

314 315
void
NfsConnection::Stat(struct nfsfh *fh, NfsCallback &callback)
316
{
317
	assert(GetEventLoop().IsInside());
318 319
	assert(!callbacks.Contains(callback));

320
	auto &c = callbacks.Add(callback, *this, false);
321 322 323
	try {
		c.Stat(context, fh);
	} catch (...) {
324
		callbacks.Remove(c);
325
		throw;
326 327 328 329 330
	}

	ScheduleSocket();
}

331
void
332
NfsConnection::Read(struct nfsfh *fh, uint64_t offset, size_t size,
333
		    NfsCallback &callback)
334
{
335
	assert(GetEventLoop().IsInside());
336 337
	assert(!callbacks.Contains(callback));

338
	auto &c = callbacks.Add(callback, *this, false);
339 340 341
	try {
		c.Read(context, fh, offset, size);
	} catch (...) {
342
		callbacks.Remove(c);
343
		throw;
344 345 346 347 348 349
	}

	ScheduleSocket();
}

void
350
NfsConnection::Cancel(NfsCallback &callback) noexcept
351 352 353 354 355
{
	callbacks.Cancel(callback);
}

static void
356
DummyCallback(int, struct nfs_context *, void *, void *) noexcept
357 358 359
{
}

360
inline void
361
NfsConnection::InternalClose(struct nfsfh *fh) noexcept
362 363 364 365 366 367 368 369
{
	assert(GetEventLoop().IsInside());
	assert(context != nullptr);
	assert(fh != nullptr);

	nfs_close_async(context, fh, DummyCallback, nullptr);
}

370
void
371
NfsConnection::Close(struct nfsfh *fh) noexcept
372
{
373 374
	assert(GetEventLoop().IsInside());

375
	InternalClose(fh);
376 377 378
	ScheduleSocket();
}

379
void
380
NfsConnection::CancelAndClose(struct nfsfh *fh, NfsCallback &callback) noexcept
381 382 383 384 385
{
	CancellableCallback &cancel = callbacks.Get(callback);
	cancel.CancelAndScheduleClose(fh);
}

386
void
387
NfsConnection::DestroyContext() noexcept
388
{
389
	assert(GetEventLoop().IsInside());
390 391
	assert(context != nullptr);

392 393 394 395 396
#ifndef NDEBUG
	assert(!in_destroy);
	in_destroy = true;
#endif

397
	if (!mount_finished) {
398 399
		assert(mount_timeout_event.IsActive());
		mount_timeout_event.Cancel();
400 401
	}

402
	/* cancel pending DeferEvent that was scheduled to notify
403
	   new leases */
404
	defer_new_lease.Cancel();
405

406 407
	if (socket_event.IsDefined())
		socket_event.Steal();
408

409 410 411 412
	callbacks.ForEach([](CancellableCallback &c){
			c.PrepareDestroyContext();
		});

413 414 415 416
	nfs_destroy_context(context);
	context = nullptr;
}

417
inline void
418
NfsConnection::DeferClose(struct nfsfh *fh) noexcept
419
{
420
	assert(GetEventLoop().IsInside());
421 422
	assert(in_event);
	assert(in_service);
423 424
	assert(context != nullptr);
	assert(fh != nullptr);
425 426 427 428

	deferred_close.push_front(fh);
}

429
void
430
NfsConnection::ScheduleSocket() noexcept
431
{
432
	assert(GetEventLoop().IsInside());
433 434
	assert(context != nullptr);

435 436
	const int which_events = nfs_which_events(context);

437
	if (which_events == POLLOUT && socket_event.IsDefined())
438 439 440 441 442 443
		/* kludge: if libnfs asks only for POLLOUT, it means
		   that it is currently waiting for the connect() to
		   finish - rpc_reconnect_requeue() may have been
		   called from inside nfs_service(); we must now
		   unregister the old socket and register the new one
		   instead */
444
		socket_event.Steal();
445

446
	if (!socket_event.IsDefined()) {
447 448
		SocketDescriptor _fd(nfs_get_fd(context));
		if (!_fd.IsDefined())
449 450
			return;

451
		_fd.EnableCloseOnExec();
452
		socket_event.Open(_fd);
453 454
	}

455
	socket_event.Schedule(libnfs_to_events(which_events));
456 457
}

458
inline int
459
NfsConnection::Service(unsigned flags) noexcept
460
{
461
	assert(GetEventLoop().IsInside());
462
	assert(context != nullptr);
463

464
#ifndef NDEBUG
465 466 467 468 469
	assert(!in_event);
	in_event = true;

	assert(!in_service);
	in_service = true;
470
#endif
471 472 473

	int result = nfs_service(context, events_to_libnfs(flags));

474
#ifndef NDEBUG
475 476 477
	assert(context != nullptr);
	assert(in_service);
	in_service = false;
478
#endif
479

480 481 482
	return result;
}

483
void
484
NfsConnection::OnSocketReady(unsigned flags) noexcept
485 486 487 488 489
{
	assert(GetEventLoop().IsInside());
	assert(deferred_close.empty());

	const bool was_mounted = mount_finished;
490
	if (!mount_finished || (flags & SocketEvent::HANGUP) != 0)
491 492 493
		/* until the mount is finished, the NFS client may use
		   various sockets, therefore we unregister and
		   re-register it each time */
494 495 496 497
		/* also re-register the socket if we got a HANGUP,
		   which is a sure sign that libnfs will close the
		   socket, which can lead to a race condition if
		   epoll_ctl() is called later */
498
		socket_event.Steal();
499 500 501

	const int result = Service(flags);

502
	while (!deferred_close.empty()) {
503
		InternalClose(deferred_close.front());
504 505 506
		deferred_close.pop_front();
	}

507
	if (!was_mounted && mount_finished) {
508
		if (postponed_mount_error) {
509 510 511 512 513 514 515
			DestroyContext();
			BroadcastMountError(std::move(postponed_mount_error));
		} else if (result == 0)
			BroadcastMountSuccess();
	} else if (result < 0) {
		/* the connection has failed */

516 517 518
		auto e = FormatRuntimeError("NFS connection has failed: %s",
					    nfs_get_error(context));
		BroadcastError(std::make_exception_ptr(e));
519

520
		DestroyContext();
521
	} else if (nfs_get_fd(context) < 0) {
522
		/* this happens when rpc_reconnect_requeue() is called
523
		   after the connection broke, but autoreconnect was
524
		   disabled - nfs_service() returns 0 */
525

526 527
		const char *msg = nfs_get_error(context);
		if (msg == nullptr)
528 529
			msg = "<unknown>";
		auto e = FormatRuntimeError("NFS socket disappeared: %s", msg);
530

531
		BroadcastError(std::make_exception_ptr(e));
532

533
		DestroyContext();
534 535
	}

536 537
	assert(context == nullptr || nfs_get_fd(context) >= 0);

538
#ifndef NDEBUG
539 540
	assert(in_event);
	in_event = false;
541
#endif
542 543 544 545 546 547

	if (context != nullptr)
		ScheduleSocket();
}

inline void
Rosen Penev's avatar
Rosen Penev committed
548 549
NfsConnection::MountCallback(int status, [[maybe_unused]] nfs_context *nfs,
			     [[maybe_unused]] void *data) noexcept
550
{
551
	assert(GetEventLoop().IsInside());
552 553 554 555
	assert(context == nfs);

	mount_finished = true;

556 557
	assert(mount_timeout_event.IsActive() || in_destroy);
	mount_timeout_event.Cancel();
558

559
	if (status < 0) {
560 561 562
		auto e = FormatRuntimeError("nfs_mount_async() failed: %s",
					    nfs_get_error(context));
		postponed_mount_error = std::make_exception_ptr(e);
563 564 565 566 567 568
		return;
	}
}

void
NfsConnection::MountCallback(int status, nfs_context *nfs, void *data,
569
			     void *private_data) noexcept
570
{
Max Kellermann's avatar
Max Kellermann committed
571
	auto *c = (NfsConnection *)private_data;
572 573 574 575

	c->MountCallback(status, nfs, data);
}

576 577
inline void
NfsConnection::MountInternal()
578
{
579
	assert(GetEventLoop().IsInside());
580
	assert(context == nullptr);
581 582

	context = nfs_init_context();
583 584
	if (context == nullptr)
		throw std::runtime_error("nfs_init_context() failed");
585

586
	postponed_mount_error = std::exception_ptr();
587
	mount_finished = false;
588

589
	mount_timeout_event.Schedule(NFS_MOUNT_TIMEOUT);
590

591
#ifndef NDEBUG
592 593
	in_service = false;
	in_event = false;
594
	in_destroy = false;
595
#endif
596 597 598

	if (nfs_mount_async(context, server.c_str(), export_name.c_str(),
			    MountCallback, this) != 0) {
599 600
		auto e = FormatRuntimeError("nfs_mount_async() failed: %s",
					    nfs_get_error(context));
601 602
		nfs_destroy_context(context);
		context = nullptr;
603
		throw e;
604 605 606 607 608 609
	}

	ScheduleSocket();
}

void
610
NfsConnection::BroadcastMountSuccess() noexcept
611
{
612 613
	assert(GetEventLoop().IsInside());

614 615 616 617 618 619 620 621
	while (!new_leases.empty()) {
		auto i = new_leases.begin();
		active_leases.splice(active_leases.end(), new_leases, i);
		(*i)->OnNfsConnectionReady();
	}
}

void
622
NfsConnection::BroadcastMountError(std::exception_ptr &&e) noexcept
623
{
624 625
	assert(GetEventLoop().IsInside());

626 627 628
	while (!new_leases.empty()) {
		auto l = new_leases.front();
		new_leases.pop_front();
629
		l->OnNfsConnectionFailed(e);
630 631
	}

632
	OnNfsConnectionError(std::move(e));
633 634 635
}

void
636
NfsConnection::BroadcastError(std::exception_ptr &&e) noexcept
637
{
638 639
	assert(GetEventLoop().IsInside());

640 641 642
	while (!active_leases.empty()) {
		auto l = active_leases.front();
		active_leases.pop_front();
643
		l->OnNfsConnectionDisconnected(e);
644 645
	}

646
	BroadcastMountError(std::move(e));
647 648
}

649
void
650
NfsConnection::OnMountTimeout() noexcept
651 652 653 654 655 656 657
{
	assert(GetEventLoop().IsInside());
	assert(!mount_finished);

	mount_finished = true;
	DestroyContext();

658
	BroadcastMountError(std::make_exception_ptr(std::runtime_error("Mount timeout")));
659 660
}

661
void
662
NfsConnection::RunDeferred() noexcept
663
{
664 665
	assert(GetEventLoop().IsInside());

666
	if (context == nullptr) {
667 668 669 670
		try {
			MountInternal();
		} catch (...) {
			BroadcastMountError(std::current_exception());
671 672 673 674
			return;
		}
	}

675
	if (mount_finished)
676 677
		BroadcastMountSuccess();
}