mpd/src/lib/nfs/FileReader.cxx

285 lines
5.4 KiB
C++
Raw Normal View History

/*
2017-01-03 20:48:59 +01:00
* Copyright 2003-2017 The Music Player Daemon Project
* 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"
#include "Base.hxx"
#include "Connection.hxx"
#include "event/Call.hxx"
#include "IOThread.hxx"
#include "util/StringCompare.hxx"
#include <utility>
#include <assert.h>
#include <string.h>
#include <fcntl.h>
2016-03-01 22:08:13 +01:00
#include <sys/stat.h>
NfsFileReader::NfsFileReader()
:DeferredMonitor(io_thread_get()), state(State::INITIAL)
{
}
NfsFileReader::~NfsFileReader()
{
assert(state == State::INITIAL);
}
void
NfsFileReader::Close()
{
if (state == State::INITIAL)
return;
if (state == State::DEFER) {
state = State::INITIAL;
DeferredMonitor::Cancel();
return;
}
/* this cancels State::MOUNT */
connection->RemoveLease(*this);
CancelOrClose();
}
void
NfsFileReader::CancelOrClose()
{
assert(state != State::INITIAL &&
state != State::DEFER);
if (state == State::IDLE)
/* no async operation in progress: can close
immediately */
connection->Close(fh);
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);
state = State::INITIAL;
}
void
NfsFileReader::DeferClose()
{
BlockingCall(io_thread_get(), [this](){ Close(); });
}
void
NfsFileReader::Open(const char *uri)
{
assert(state == State::INITIAL);
if (!StringStartsWith(uri, "nfs://"))
throw std::runtime_error("Malformed nfs:// URI");
uri += 6;
const char *slash = strchr(uri, '/');
if (slash == nullptr)
throw std::runtime_error("Malformed nfs:// URI");
server = std::string(uri, slash);
uri = slash;
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, '/');
if (slash == nullptr || slash[1] == 0)
throw std::runtime_error("Malformed nfs:// URI");
export_name = std::string(uri, slash);
path = slash;
}
state = State::DEFER;
DeferredMonitor::Schedule();
}
void
NfsFileReader::Read(uint64_t offset, size_t size)
{
assert(state == State::IDLE);
connection->Read(fh, offset, size, *this);
state = State::READ;
}
void
NfsFileReader::CancelRead()
{
if (state == State::READ) {
connection->Cancel(*this);
state = State::IDLE;
}
}
void
NfsFileReader::OnNfsConnectionReady()
{
assert(state == State::MOUNT);
try {
connection->Open(path, O_RDONLY, *this);
} catch (...) {
OnNfsFileError(std::current_exception());
return;
}
state = State::OPEN;
}
void
NfsFileReader::OnNfsConnectionFailed(std::exception_ptr e)
{
assert(state == State::MOUNT);
state = State::INITIAL;
OnNfsFileError(std::move(e));
}
void
NfsFileReader::OnNfsConnectionDisconnected(std::exception_ptr e)
{
assert(state > State::MOUNT);
CancelOrClose();
OnNfsFileError(std::move(e));
}
inline void
NfsFileReader::OpenCallback(nfsfh *_fh)
{
assert(state == State::OPEN);
assert(connection != nullptr);
assert(_fh != nullptr);
fh = _fh;
try {
connection->Stat(fh, *this);
} catch (...) {
OnNfsFileError(std::current_exception());
return;
}
state = State::STAT;
}
inline void
NfsFileReader::StatCallback(const struct stat *st)
{
assert(state == State::STAT);
assert(connection != nullptr);
assert(fh != nullptr);
assert(st != nullptr);
if (!S_ISREG(st->st_mode)) {
OnNfsFileError(std::make_exception_ptr(std::runtime_error("Not a regular file")));
return;
}
state = State::IDLE;
OnNfsFileOpen(st->st_size);
}
void
NfsFileReader::OnNfsCallback(unsigned status, void *data)
{
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
NfsFileReader::OnNfsError(std::exception_ptr &&e)
{
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;
}
OnNfsFileError(std::move(e));
}
void
NfsFileReader::RunDeferred()
{
assert(state == State::DEFER);
state = State::MOUNT;
connection = &nfs_get_connection(server.c_str(), export_name.c_str());
connection->AddLease(*this);
}