FileReader.cxx 5.74 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 21
 * 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 "FileReader.hxx"
#include "Glue.hxx"
22
#include "Base.hxx"
23 24
#include "Connection.hxx"
#include "event/Call.hxx"
25
#include "util/ASCII.hxx"
26

27
#include <cassert>
Rosen Penev's avatar
Rosen Penev committed
28
#include <cstring>
29
#include <stdexcept>
30
#include <utility>
31 32 33

#include <fcntl.h>

34
NfsFileReader::NfsFileReader() noexcept
35
	:defer_open(nfs_get_event_loop(), BIND_THIS_METHOD(OnDeferredOpen))
36 37 38
{
}

39
NfsFileReader::~NfsFileReader() noexcept
40 41 42 43 44
{
	assert(state == State::INITIAL);
}

void
45
NfsFileReader::Close() noexcept
46 47 48 49 50 51
{
	if (state == State::INITIAL)
		return;

	if (state == State::DEFER) {
		state = State::INITIAL;
52
		defer_open.Cancel();
53 54 55
		return;
	}

56
	/* this cancels State::MOUNT */
57 58
	connection->RemoveLease(*this);

59 60 61 62
	CancelOrClose();
}

void
63
NfsFileReader::CancelOrClose() noexcept
64 65 66 67
{
	assert(state != State::INITIAL &&
	       state != State::DEFER);

68 69 70
	if (state == State::IDLE)
		/* no async operation in progress: can close
		   immediately */
71
		connection->Close(fh);
72 73 74 75 76 77 78 79
	else if (state > State::OPEN)
		/* one async operation in progress: cancel it and
		   defer the nfs_close_async() call */
		connection->CancelAndClose(fh, *this);
	else if (state > State::MOUNT)
		/* we don't have a file handle yet - just cancel the
		   async operation */
		connection->Cancel(*this);
80 81 82 83 84

	state = State::INITIAL;
}

void
85
NfsFileReader::DeferClose() noexcept
86
{
87
	BlockingCall(GetEventLoop(), [this](){ Close(); });
88 89
}

90 91
void
NfsFileReader::Open(const char *uri)
92 93 94
{
	assert(state == State::INITIAL);

95
	if (!StringStartsWithCaseASCII(uri, "nfs://"))
96
		throw std::runtime_error("Malformed nfs:// URI");
97 98 99

	uri += 6;

Rosen Penev's avatar
Rosen Penev committed
100
	const char *slash = std::strchr(uri, '/');
101 102
	if (slash == nullptr)
		throw std::runtime_error("Malformed nfs:// URI");
103 104 105 106 107

	server = std::string(uri, slash);

	uri = slash;

108 109 110 111 112 113 114
	const char *new_path = nfs_check_base(server.c_str(), uri);
	if (new_path != nullptr) {
		export_name = std::string(uri, new_path);
		if (*new_path == 0)
			new_path = "/";
		path = new_path;
	} else {
Rosen Penev's avatar
Rosen Penev committed
115
		slash = std::strrchr(uri + 1, '/');
116 117
		if (slash == nullptr || slash[1] == 0)
			throw std::runtime_error("Malformed nfs:// URI");
118 119 120 121

		export_name = std::string(uri, slash);
		path = slash;
	}
122 123

	state = State::DEFER;
124
	defer_open.Schedule();
125 126
}

127 128
void
NfsFileReader::Read(uint64_t offset, size_t size)
129 130 131
{
	assert(state == State::IDLE);

132
	connection->Read(fh, offset, size, *this);
133 134 135 136
	state = State::READ;
}

void
137
NfsFileReader::CancelRead() noexcept
138 139 140 141 142 143 144 145
{
	if (state == State::READ) {
		connection->Cancel(*this);
		state = State::IDLE;
	}
}

void
146
NfsFileReader::OnNfsConnectionReady() noexcept
147 148 149
{
	assert(state == State::MOUNT);

150 151 152 153
	try {
		connection->Open(path, O_RDONLY, *this);
	} catch (...) {
		OnNfsFileError(std::current_exception());
154 155 156 157 158 159 160
		return;
	}

	state = State::OPEN;
}

void
161
NfsFileReader::OnNfsConnectionFailed(std::exception_ptr e) noexcept
162 163 164
{
	assert(state == State::MOUNT);

165 166
	state = State::INITIAL;

167
	OnNfsFileError(std::move(e));
168 169 170
}

void
171
NfsFileReader::OnNfsConnectionDisconnected(std::exception_ptr e) noexcept
172 173 174
{
	assert(state > State::MOUNT);

175
	CancelOrClose();
176

177
	OnNfsFileError(std::move(e));
178 179 180
}

inline void
181
NfsFileReader::OpenCallback(nfsfh *_fh) noexcept
182 183 184 185 186 187
{
	assert(connection != nullptr);
	assert(_fh != nullptr);

	fh = _fh;

188 189 190 191
	try {
		connection->Stat(fh, *this);
	} catch (...) {
		OnNfsFileError(std::current_exception());
192 193 194 195 196 197 198
		return;
	}

	state = State::STAT;
}

inline void
199
NfsFileReader::StatCallback(const struct stat *_st) noexcept
200 201 202
{
	assert(connection != nullptr);
	assert(fh != nullptr);
203 204 205 206 207 208 209 210 211 212
	assert(_st != nullptr);

#if defined(_WIN32) && !defined(_WIN64)
	/* on 32-bit Windows, libnfs enables -D_FILE_OFFSET_BITS=64,
	   but MPD (Meson) doesn't - to work around this mismatch, we
	   cast explicitly to "struct stat64" */
	const auto *st = (const struct stat64 *)_st;
#else
	const auto *st = _st;
#endif
213 214

	if (!S_ISREG(st->st_mode)) {
215
		OnNfsFileError(std::make_exception_ptr(std::runtime_error("Not a regular file")));
216 217 218 219 220 221 222
		return;
	}

	OnNfsFileOpen(st->st_size);
}

void
223
NfsFileReader::OnNfsCallback(unsigned status, void *data) noexcept
224
{
225
	switch (std::exchange(state, State::IDLE)) {
226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247
	case State::INITIAL:
	case State::DEFER:
	case State::MOUNT:
	case State::IDLE:
		assert(false);
		gcc_unreachable();

	case State::OPEN:
		OpenCallback((struct nfsfh *)data);
		break;

	case State::STAT:
		StatCallback((const struct stat *)data);
		break;

	case State::READ:
		OnNfsFileRead(data, status);
		break;
	}
}

void
248
NfsFileReader::OnNfsError(std::exception_ptr &&e) noexcept
249
{
250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273
	switch (state) {
	case State::INITIAL:
	case State::DEFER:
	case State::MOUNT:
	case State::IDLE:
		assert(false);
		gcc_unreachable();

	case State::OPEN:
		connection->RemoveLease(*this);
		state = State::INITIAL;
		break;

	case State::STAT:
		connection->RemoveLease(*this);
		connection->Close(fh);
		state = State::INITIAL;
		break;

	case State::READ:
		state = State::IDLE;
		break;
	}

274
	OnNfsFileError(std::move(e));
275 276 277
}

void
278
NfsFileReader::OnDeferredOpen() noexcept
279 280 281 282 283 284 285 286
{
	assert(state == State::DEFER);

	state = State::MOUNT;

	connection = &nfs_get_connection(server.c_str(), export_name.c_str());
	connection->AddLease(*this);
}