FileReader.cxx 5.54 KB
Newer Older
1
/*
Max Kellermann's avatar
Max Kellermann committed
2
 * Copyright 2003-2017 The Music Player Daemon Project
3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
 * 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 "config.h"
#include "FileReader.hxx"
#include "Glue.hxx"
23
#include "Base.hxx"
24 25
#include "Connection.hxx"
#include "event/Call.hxx"
26
#include "util/StringCompare.hxx"
27 28 29 30 31 32

#include <utility>

#include <assert.h>
#include <string.h>
#include <fcntl.h>
33
#include <sys/stat.h>
34

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

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

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

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

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

60 61 62 63
	CancelOrClose();
}

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

69 70 71
	if (state == State::IDLE)
		/* no async operation in progress: can close
		   immediately */
72
		connection->Close(fh);
73 74 75 76 77 78 79 80
	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);
81 82 83 84 85

	state = State::INITIAL;
}

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

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

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

	uri += 6;

	const char *slash = strchr(uri, '/');
102 103
	if (slash == nullptr)
		throw std::runtime_error("Malformed nfs:// URI");
104 105 106 107 108

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

	uri = slash;

109 110 111 112 113 114 115 116
	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 {
		slash = strrchr(uri + 1, '/');
117 118
		if (slash == nullptr || slash[1] == 0)
			throw std::runtime_error("Malformed nfs:// URI");
119 120 121 122

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

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

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

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

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

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

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

	state = State::OPEN;
}

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

166 167
	state = State::INITIAL;

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

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

176
	CancelOrClose();
177

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

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

	fh = _fh;

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

	state = State::STAT;
}

inline void
201
NfsFileReader::StatCallback(const struct stat *st) noexcept
202 203 204 205 206 207 208
{
	assert(state == State::STAT);
	assert(connection != nullptr);
	assert(fh != nullptr);
	assert(st != nullptr);

	if (!S_ISREG(st->st_mode)) {
209
		OnNfsFileError(std::make_exception_ptr(std::runtime_error("Not a regular file")));
210 211 212 213 214 215 216 217 218
		return;
	}

	state = State::IDLE;

	OnNfsFileOpen(st->st_size);
}

void
219
NfsFileReader::OnNfsCallback(unsigned status, void *data) noexcept
220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244
{
	switch (state) {
	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:
		state = State::IDLE;
		OnNfsFileRead(data, status);
		break;
	}
}

void
245
NfsFileReader::OnNfsError(std::exception_ptr &&e) noexcept
246
{
247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270
	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;
	}

271
	OnNfsFileError(std::move(e));
272 273 274
}

void
275
NfsFileReader::OnDeferredOpen() noexcept
276 277 278 279 280 281 282 283
{
	assert(state == State::DEFER);

	state = State::MOUNT;

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