<commit_before>/*
 * Copyright (C) 2015-2018 Dubalu LLC. All rights reserved.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */

#include "replication.h"

#ifdef XAPIAND_CLUSTERING

#include "io_utils.h"
#include "length.h"

/*  ____            _ _           _   _
 * |  _ \ ___ _ __ | (_) ___ __ _| |_(_) ___  _ __
 * | |_) / _ \ '_ \| | |/ __/ _` | __| |/ _ \| '_ \
 * |  _ <  __/ |_) | | | (_| (_| | |_| | (_) | | | |
 * |_| \_\___| .__/|_|_|\___\__,_|\__|_|\___/|_| |_|
 *           |_|
 */

using dispatch_func = void (Replication::*)(const std::string&);

Replication::Replication(BinaryClient* client_)
	: client(client_)
{
		L_OBJ("CREATED REPLICATION OBJ!");
}

Replication::~Replication()
{
	L_OBJ("DELETED REPLICATION OBJ!");
}

void
Replication::replication_server(ReplicationMessageType type, const std::string &message)
{
	static const dispatch_func dispatch[] = {
		&Replication::msg_get_changesets,
	};
	try {
		if (static_cast<size_t>(type) >= sizeof(dispatch) / sizeof(dispatch[0])) {
			std::string errmsg("Unexpected message type ");
			errmsg += std::to_string(toUType(type));
			THROW(InvalidArgumentError, errmsg);
		}
		(this->*(dispatch[static_cast<int>(type)]))(message);
	} catch (...) {
		client->checkin_database();
		throw;
	}
}

void
Replication::msg_get_changesets(const std::string &)
{
	L_REPLICATION("Replication::msg_get_changesets");

	// Xapian::Database *db_;
	// const char *p = message.c_str();
	// const char *p_end = p + message.size();

	// size_t len = unserialise_length(&p, p_end, true);
	// std::string uuid(p, len);
	// p += len;

	// len = unserialise_length(&p, p_end, true);
	// std::string from_revision(p, len);
	// p += len;

	// len = unserialise_length(&p, p_end, true);
	// std::string index_path(p, len);
	// p += len;

	// // Select endpoints and get database
	// try {
	// 	endpoints.clear();
	// 	Endpoint endpoint(index_path);
	// 	endpoints.add(endpoint);
	// 	db_ = get_db();
	// 	if (!db_)
	// 		THROW(InvalidOperationError, "Server has no open database");
	// } catch (...) {
	// 	throw;
	// }

	// char path[] = "/tmp/xapian_changesets_sent.XXXXXX";
	// int fd = mkstemp(path);
	// try {
	// 	std::string to_revision = databases[db_]->checkout_revision;
	// 	L_REPLICATION("Replication::msg_get_changesets for %s (%s) from rev:%s to rev:%s [%d]", endpoints.as_string(), uuid, repr(from_revision, false), repr(to_revision, false), need_whole_db);

	// 	if (fd == -1) {
	// 		L_ERR("Cannot write to %s (1)", path);
	// 		return;
	// 	}
	// 	// db_->write_changesets_to_fd(fd, from_revision, uuid != db_->get_uuid().to_string());  // FIXME: Implement Replication
	// } catch (...) {
	// 	release_db(db_);
	// 	io::close(fd);
	// 	io::unlink(path);
	// 	throw;
	// }
	// release_db(db_);

	// send_file(fd);

	// io::close(fd);
	// io::unlink(path);
}

void
Replication::replication_client(ReplicationReplyType type, const std::string &message)
{
	static const dispatch_func dispatch[] = {
		&Replication::reply_end_of_changes,
		&Replication::reply_fail,
		&Replication::reply_db_header,
		&Replication::reply_db_filename,
		&Replication::reply_db_filedata,
		&Replication::reply_db_footer,
		&Replication::reply_changeset,
	};
	try {
		if (static_cast<size_t>(type) >= sizeof(dispatch) / sizeof(dispatch[0])) {
			std::string errmsg("Unexpected message type ");
			errmsg += std::to_string(toUType(type));
			THROW(InvalidArgumentError, errmsg);
		}
		(this->*(dispatch[static_cast<int>(type)]))(message);
	} catch (...) {
		client->checkin_database();
		throw;
	}
}

void
Replication::reply_end_of_changes(const std::string &)
{
	L_REPLICATION("Replication::reply_end_of_changes");

	// if (repl_switched_db) {
	// 	XapiandManager::manager->database_pool.switch_db(*endpoints.cbegin());
	// }

	// client->checkin_database();

	// shutdown();
}

void
Replication::reply_fail(const std::string &)
{
	L_REPLICATION("Replication::reply_fail");

	// L_ERR("Replication failure!");
	// client->checkin_database();

	// shutdown();
}

void
Replication::reply_db_header(const std::string &)
{
	L_REPLICATION("Replication::reply_db_header");

	// const char *p = message.data();
	// const char *p_end = p + message.size();
	// size_t length = unserialise_length(&p, p_end, true);
	// repl_db_uuid.assign(p, length);
	// p += length;
	// repl_db_revision = unserialise_length(&p, p_end);
	// repl_db_filename.clear();

	// Endpoint& endpoint = endpoints[0];
	// std::string path_tmp = endpoint.path + "/.tmp";

	// int dir = ::mkdir(path_tmp.c_str(), S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH);
	// if (dir == 0) {
	// 	L_DEBUG("Directory %s created", path_tmp);
	// } else if (errno == EEXIST) {
	// 	delete_files(path_tmp.c_str());
	// 	dir = ::mkdir(path_tmp.c_str(), S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH);
	// 	if (dir == 0) {
	// 		L_DEBUG("Directory %s created", path_tmp);
	// 	}
	// } else {
	// 	L_ERR("Directory %s not created (%s)", path_tmp, strerror(errno));
	// }
}

void
Replication::reply_db_filename(const std::string &)
{
	L_REPLICATION("Replication::reply_db_filename");

	// const char *p = message.data();
	// const char *p_end = p + message.size();
	// repl_db_filename.assign(p, p_end - p);
}

void
Replication::reply_db_filedata(const std::string &)
{
	L_REPLICATION("Replication::reply_db_filedata");

	// const char *p = message.data();
	// const char *p_end = p + message.size();

	// const Endpoint& endpoint = endpoints[0];

	// std::string path = endpoint.path + "/.tmp/";
	// std::string path_filename = path + repl_db_filename;

	// int fd = io::open(path_filename.c_str(), O_WRONLY | O_CREAT | O_EXCL | O_CLOEXEC, 0644);
	// if (fd != -1) {
	// 	L_REPLICATION("path_filename %s", path_filename);
	// 	if (io::write(fd, p, p_end - p) != p_end - p) {
	// 		L_ERR("Cannot write to %s", repl_db_filename);
	// 		return;
	// 	}
	// 	io::close(fd);
	// }
}

void
Replication::reply_db_footer(const std::string &)
{
	L_REPLICATION("Replication::reply_db_footer");

	// // const char *p = message.data();
	// // const char *p_end = p + message.size();
	// // size_t revision = unserialise_length(&p, p_end);
	// // Indicates the end of a DB copy operation, signal switch

	// Endpoints endpoints_tmp;
	// Endpoint& endpoint_tmp = endpoints[0];
	// endpoint_tmp.path.append("/.tmp");
	// endpoints_tmp.insert(endpoint_tmp);

	// if (!repl_database_tmp) {
	// 	try {
	// 		XapiandManager::manager->database_pool.checkout(repl_database_tmp, endpoints_tmp, DB_WRITABLE | DB_VOLATILE);
	// 	} catch (const CheckoutError&)
	// 		L_ERR("Cannot checkout tmp %s", endpoint_tmp.path);
	// 	}
	// }

	// repl_switched_db = true;
	// repl_just_switched_db = true;
}

void
Replication::reply_changeset(const std::string &)
{
	L_REPLICATION("Replication::reply_changeset");

	// Xapian::WritableDatabase *wdb_;
	// if (repl_database_tmp) {
	// 	wdb_ = static_cast<Xapian::WritableDatabase *>(repl_database_tmp->db.get());
	// } else {
	// 	wdb_ = static_cast<Xapian::WritableDatabase *>(database);
	// }

	// char path[] = "/tmp/xapian_changes.XXXXXX";
	// int fd = mkstemp(path);
	// if (fd == -1) {
	// 	L_ERR("Cannot write to %s (1)", path);
	// 	return;
	// }

	// std::string header;
	// header += toUType(ReplicationMessageType::REPLY_CHANGESET);
	// header += serialise_length(message.size());

	// if (io::write(fd, header.data(), header.size()) != static_cast<ssize_t>(header.size())) {
	// 	L_ERR("Cannot write to %s (2)", path);
	// 	return;
	// }

	// if (io::write(fd, message.data(), message.size()) != static_cast<ssize_t>(message.size())) {
	// 	L_ERR("Cannot write to %s (3)", path);
	// 	return;
	// }

	// io::lseek(fd, 0, SEEK_SET);

	// try {
	// 	// wdb_->apply_changeset_from_fd(fd, !repl_just_switched_db);  // FIXME: Implement Replication
	// 	repl_just_switched_db = false;
	// } catch (const MSG_NetworkError& exc) {
	// 	L_EXC("ERROR: %s", exc.get_description());
	// 	io::close(fd);
	// 	io::unlink(path);
	// 	throw;
	// } catch (const Xapian::DatabaseError& exc) {
	// 	L_EXC("ERROR: %s", exc.get_description());
	// 	io::close(fd);
	// 	io::unlink(path);
	// 	throw;
	// } catch (...) {
	// 	io::close(fd);
	// 	io::unlink(path);
	// 	throw;
	// }

	// io::close(fd);
	// io::unlink(path);
}

void
Replication::replication_client_file_done()
{
	L_REPLICATION("Replication::replication_client_file_done");

	char buf[1024];
	const char *p;
	const char *p_end;
	std::string buffer;

	ssize_t size = io::read(client->file_descriptor, buf, sizeof(buf));
	buffer.append(buf, size);
	p = buffer.data();
	p_end = p + buffer.size();
	const char *s = p;

	while (p != p_end) {
		ReplicationReplyType type = static_cast<ReplicationReplyType>(*p++);
		ssize_t len = unserialise_length(&p, p_end);
		size_t pos = p - s;
		while (p_end - p < len || static_cast<size_t>(p_end - p) < sizeof(buf) / 2) {
			size = io::read(client->file_descriptor, buf, sizeof(buf));
			if (!size) break;
			buffer.append(buf, size);
			s = p = buffer.data();
			p_end = p + buffer.size();
			p += pos;
		}
		if (p_end - p < len) {
			THROW(Error, "Replication failure!");
		}
		std::string msg(p, len);
		p += len;

		replication_client(type, msg);

		buffer.erase(0, p - s);
		s = p = buffer.data();
		p_end = p + buffer.size();
	}
}

#endif  /* XAPIAND_CLUSTERING */
<commit_msg>Replication: Added L_CALLs<commit_after>/*
 * Copyright (C) 2015-2018 Dubalu LLC. All rights reserved.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */

#include "replication.h"

#ifdef XAPIAND_CLUSTERING

#include "io_utils.h"
#include "length.h"

/*  ____            _ _           _   _
 * |  _ \ ___ _ __ | (_) ___ __ _| |_(_) ___  _ __
 * | |_) / _ \ '_ \| | |/ __/ _` | __| |/ _ \| '_ \
 * |  _ <  __/ |_) | | | (_| (_| | |_| | (_) | | | |
 * |_| \_\___| .__/|_|_|\___\__,_|\__|_|\___/|_| |_|
 *           |_|
 */

using dispatch_func = void (Replication::*)(const std::string&);

Replication::Replication(BinaryClient* client_)
	: client(client_)
{
	L_OBJ("CREATED REPLICATION OBJ!");
}

Replication::~Replication()
{
	L_OBJ("DELETED REPLICATION OBJ!");
}

void
Replication::replication_server(ReplicationMessageType type, const std::string &message)
{
	L_CALL("Replication::replication_server(%s, <message>)", ReplicationMessageTypeNames[static_cast<int>(type)]);

	static const dispatch_func dispatch[] = {
		&Replication::msg_get_changesets,
	};
	try {
		if (static_cast<size_t>(type) >= sizeof(dispatch) / sizeof(dispatch[0])) {
			std::string errmsg("Unexpected message type ");
			errmsg += std::to_string(toUType(type));
			THROW(InvalidArgumentError, errmsg);
		}
		(this->*(dispatch[static_cast<int>(type)]))(message);
	} catch (...) {
		client->checkin_database();
		throw;
	}
}

void
Replication::msg_get_changesets(const std::string &)
{
	L_CALL("Replication::msg_get_changesets(<message>)");

	L_REPLICATION("Replication::msg_get_changesets");

	// Xapian::Database *db_;
	// const char *p = message.c_str();
	// const char *p_end = p + message.size();

	// size_t len = unserialise_length(&p, p_end, true);
	// std::string uuid(p, len);
	// p += len;

	// len = unserialise_length(&p, p_end, true);
	// std::string from_revision(p, len);
	// p += len;

	// len = unserialise_length(&p, p_end, true);
	// std::string index_path(p, len);
	// p += len;

	// // Select endpoints and get database
	// try {
	// 	endpoints.clear();
	// 	Endpoint endpoint(index_path);
	// 	endpoints.add(endpoint);
	// 	db_ = get_db();
	// 	if (!db_)
	// 		THROW(InvalidOperationError, "Server has no open database");
	// } catch (...) {
	// 	throw;
	// }

	// char path[] = "/tmp/xapian_changesets_sent.XXXXXX";
	// int fd = mkstemp(path);
	// try {
	// 	std::string to_revision = databases[db_]->checkout_revision;
	// 	L_REPLICATION("Replication::msg_get_changesets for %s (%s) from rev:%s to rev:%s [%d]", endpoints.as_string(), uuid, repr(from_revision, false), repr(to_revision, false), need_whole_db);

	// 	if (fd == -1) {
	// 		L_ERR("Cannot write to %s (1)", path);
	// 		return;
	// 	}
	// 	// db_->write_changesets_to_fd(fd, from_revision, uuid != db_->get_uuid().to_string());  // FIXME: Implement Replication
	// } catch (...) {
	// 	release_db(db_);
	// 	io::close(fd);
	// 	io::unlink(path);
	// 	throw;
	// }
	// release_db(db_);

	// send_file(fd);

	// io::close(fd);
	// io::unlink(path);
}

void
Replication::replication_client(ReplicationReplyType type, const std::string &message)
{
	L_CALL("Replication::replication_client(%s, <message>)", ReplicationMessageTypeNames[static_cast<int>(type)]);

	static const dispatch_func dispatch[] = {
		&Replication::reply_end_of_changes,
		&Replication::reply_fail,
		&Replication::reply_db_header,
		&Replication::reply_db_filename,
		&Replication::reply_db_filedata,
		&Replication::reply_db_footer,
		&Replication::reply_changeset,
	};
	try {
		if (static_cast<size_t>(type) >= sizeof(dispatch) / sizeof(dispatch[0])) {
			std::string errmsg("Unexpected message type ");
			errmsg += std::to_string(toUType(type));
			THROW(InvalidArgumentError, errmsg);
		}
		(this->*(dispatch[static_cast<int>(type)]))(message);
	} catch (...) {
		client->checkin_database();
		throw;
	}
}

void
Replication::reply_end_of_changes(const std::string &)
{
	L_CALL("Replication::reply_end_of_changes(<message>)");

	L_REPLICATION("Replication::reply_end_of_changes");

	// if (repl_switched_db) {
	// 	XapiandManager::manager->database_pool.switch_db(*endpoints.cbegin());
	// }

	// client->checkin_database();

	// shutdown();
}

void
Replication::reply_fail(const std::string &)
{
	L_CALL("Replication::reply_fail(<message>)");

	L_REPLICATION("Replication::reply_fail");

	// L_ERR("Replication failure!");
	// client->checkin_database();

	// shutdown();
}

void
Replication::reply_db_header(const std::string &)
{
	L_CALL("Replication::reply_db_header(<message>)");

	L_REPLICATION("Replication::reply_db_header");

	// const char *p = message.data();
	// const char *p_end = p + message.size();
	// size_t length = unserialise_length(&p, p_end, true);
	// repl_db_uuid.assign(p, length);
	// p += length;
	// repl_db_revision = unserialise_length(&p, p_end);
	// repl_db_filename.clear();

	// Endpoint& endpoint = endpoints[0];
	// std::string path_tmp = endpoint.path + "/.tmp";

	// int dir = ::mkdir(path_tmp.c_str(), S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH);
	// if (dir == 0) {
	// 	L_DEBUG("Directory %s created", path_tmp);
	// } else if (errno == EEXIST) {
	// 	delete_files(path_tmp.c_str());
	// 	dir = ::mkdir(path_tmp.c_str(), S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH);
	// 	if (dir == 0) {
	// 		L_DEBUG("Directory %s created", path_tmp);
	// 	}
	// } else {
	// 	L_ERR("Directory %s not created (%s)", path_tmp, strerror(errno));
	// }
}

void
Replication::reply_db_filename(const std::string &)
{
	L_CALL("Replication::reply_db_filename(<message>)");

	L_REPLICATION("Replication::reply_db_filename");

	// const char *p = message.data();
	// const char *p_end = p + message.size();
	// repl_db_filename.assign(p, p_end - p);
}

void
Replication::reply_db_filedata(const std::string &)
{
	L_CALL("Replication::reply_db_filedata(<message>)");

	L_REPLICATION("Replication::reply_db_filedata");

	// const char *p = message.data();
	// const char *p_end = p + message.size();

	// const Endpoint& endpoint = endpoints[0];

	// std::string path = endpoint.path + "/.tmp/";
	// std::string path_filename = path + repl_db_filename;

	// int fd = io::open(path_filename.c_str(), O_WRONLY | O_CREAT | O_EXCL | O_CLOEXEC, 0644);
	// if (fd != -1) {
	// 	L_REPLICATION("path_filename %s", path_filename);
	// 	if (io::write(fd, p, p_end - p) != p_end - p) {
	// 		L_ERR("Cannot write to %s", repl_db_filename);
	// 		return;
	// 	}
	// 	io::close(fd);
	// }
}

void
Replication::reply_db_footer(const std::string &)
{
	L_CALL("Replication::reply_db_footer(<message>)");

	L_REPLICATION("Replication::reply_db_footer");

	// // const char *p = message.data();
	// // const char *p_end = p + message.size();
	// // size_t revision = unserialise_length(&p, p_end);
	// // Indicates the end of a DB copy operation, signal switch

	// Endpoints endpoints_tmp;
	// Endpoint& endpoint_tmp = endpoints[0];
	// endpoint_tmp.path.append("/.tmp");
	// endpoints_tmp.insert(endpoint_tmp);

	// if (!repl_database_tmp) {
	// 	try {
	// 		XapiandManager::manager->database_pool.checkout(repl_database_tmp, endpoints_tmp, DB_WRITABLE | DB_VOLATILE);
	// 	} catch (const CheckoutError&)
	// 		L_ERR("Cannot checkout tmp %s", endpoint_tmp.path);
	// 	}
	// }

	// repl_switched_db = true;
	// repl_just_switched_db = true;
}

void
Replication::reply_changeset(const std::string &)
{
	L_CALL("Replication::reply_changeset(<message>)");

	L_REPLICATION("Replication::reply_changeset");

	// Xapian::WritableDatabase *wdb_;
	// if (repl_database_tmp) {
	// 	wdb_ = static_cast<Xapian::WritableDatabase *>(repl_database_tmp->db.get());
	// } else {
	// 	wdb_ = static_cast<Xapian::WritableDatabase *>(database);
	// }

	// char path[] = "/tmp/xapian_changes.XXXXXX";
	// int fd = mkstemp(path);
	// if (fd == -1) {
	// 	L_ERR("Cannot write to %s (1)", path);
	// 	return;
	// }

	// std::string header;
	// header += toUType(ReplicationMessageType::REPLY_CHANGESET);
	// header += serialise_length(message.size());

	// if (io::write(fd, header.data(), header.size()) != static_cast<ssize_t>(header.size())) {
	// 	L_ERR("Cannot write to %s (2)", path);
	// 	return;
	// }

	// if (io::write(fd, message.data(), message.size()) != static_cast<ssize_t>(message.size())) {
	// 	L_ERR("Cannot write to %s (3)", path);
	// 	return;
	// }

	// io::lseek(fd, 0, SEEK_SET);

	// try {
	// 	// wdb_->apply_changeset_from_fd(fd, !repl_just_switched_db);  // FIXME: Implement Replication
	// 	repl_just_switched_db = false;
	// } catch (const MSG_NetworkError& exc) {
	// 	L_EXC("ERROR: %s", exc.get_description());
	// 	io::close(fd);
	// 	io::unlink(path);
	// 	throw;
	// } catch (const Xapian::DatabaseError& exc) {
	// 	L_EXC("ERROR: %s", exc.get_description());
	// 	io::close(fd);
	// 	io::unlink(path);
	// 	throw;
	// } catch (...) {
	// 	io::close(fd);
	// 	io::unlink(path);
	// 	throw;
	// }

	// io::close(fd);
	// io::unlink(path);
}

void
Replication::replication_client_file_done()
{
	L_CALL("Replication::replication_client_file_done(<message>)");

	L_REPLICATION("Replication::replication_client_file_done");

	char buf[1024];
	const char *p;
	const char *p_end;
	std::string buffer;

	ssize_t size = io::read(client->file_descriptor, buf, sizeof(buf));
	buffer.append(buf, size);
	p = buffer.data();
	p_end = p + buffer.size();
	const char *s = p;

	while (p != p_end) {
		ReplicationReplyType type = static_cast<ReplicationReplyType>(*p++);
		ssize_t len = unserialise_length(&p, p_end);
		size_t pos = p - s;
		while (p_end - p < len || static_cast<size_t>(p_end - p) < sizeof(buf) / 2) {
			size = io::read(client->file_descriptor, buf, sizeof(buf));
			if (!size) break;
			buffer.append(buf, size);
			s = p = buffer.data();
			p_end = p + buffer.size();
			p += pos;
		}
		if (p_end - p < len) {
			THROW(Error, "Replication failure!");
		}
		std::string msg(p, len);
		p += len;

		replication_client(type, msg);

		buffer.erase(0, p - s);
		s = p = buffer.data();
		p_end = p + buffer.size();
	}
}

#endif  /* XAPIAND_CLUSTERING */
<|endoftext|>


<commit_before>// Copyright (c) 2010 Satoshi Nakamoto
// Copyright (c) 2009-2018 The Syscoin Core developers
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.

#include <rpc/client.h>
#include <rpc/protocol.h>
#include <util/system.h>

#include <set>
#include <stdint.h>

class CRPCConvertParam
{
public:
    std::string methodName; //!< method whose params want conversion
    int paramIdx;           //!< 0-based idx of param to convert
    std::string paramName;  //!< parameter name
};

// clang-format off
/**
 * Specify a (method, idx, name) here if the argument is a non-string RPC
 * argument and needs to be converted from JSON.
 *
 * @note Parameter indexes start from 0.
 */
static const CRPCConvertParam vRPCConvertParams[] =
{
    { "setmocktime", 0, "timestamp" },
    { "generate", 0, "nblocks" },
    { "generate", 1, "maxtries" },
    { "generatetoaddress", 0, "nblocks" },
    { "generatetoaddress", 2, "maxtries" },
    { "getnetworkhashps", 0, "nblocks" },
    { "getnetworkhashps", 1, "height" },
    { "sendtoaddress", 1, "amount" },
    { "sendtoaddress", 4, "subtractfeefromamount" },
    { "sendtoaddress", 5 , "replaceable" },
    { "sendtoaddress", 6 , "conf_target" },
    { "settxfee", 0, "amount" },
    { "sethdseed", 0, "newkeypool" },
    { "getreceivedbyaddress", 1, "minconf" },
    { "getreceivedbylabel", 1, "minconf" },
    { "listreceivedbyaddress", 0, "minconf" },
    { "listreceivedbyaddress", 1, "include_empty" },
    { "listreceivedbyaddress", 2, "include_watchonly" },
    { "listreceivedbylabel", 0, "minconf" },
    { "listreceivedbylabel", 1, "include_empty" },
    { "listreceivedbylabel", 2, "include_watchonly" },
    { "getbalance", 1, "minconf" },
    { "getbalance", 2, "include_watchonly" },
    { "getblockhash", 0, "height" },
    { "waitforblockheight", 0, "height" },
    { "waitforblockheight", 1, "timeout" },
    { "waitforblock", 1, "timeout" },
    { "waitfornewblock", 0, "timeout" },
    { "listtransactions", 1, "count" },
    { "listtransactions", 2, "skip" },
    { "listtransactions", 3, "include_watchonly" },
    { "walletpassphrase", 1, "timeout" },
    { "getblocktemplate", 0, "template_request" },
    { "listsinceblock", 1, "target_confirmations" },
    { "listsinceblock", 2, "include_watchonly" },
    { "listsinceblock", 3, "include_removed" },
    { "sendmany", 1, "amounts" },
    { "sendmany", 2, "minconf" },
    { "sendmany", 4, "subtractfeefrom" },
    { "sendmany", 5 , "replaceable" },
    { "sendmany", 6 , "conf_target" },
    { "deriveaddresses", 1, "range" },
    { "scantxoutset", 1, "scanobjects" },
    { "addmultisigaddress", 0, "nrequired" },
    { "addmultisigaddress", 1, "keys" },
    { "createmultisig", 0, "nrequired" },
    { "createmultisig", 1, "keys" },
    { "listunspent", 0, "minconf" },
    { "listunspent", 1, "maxconf" },
    { "listunspent", 2, "addresses" },
    { "listunspent", 3, "include_unsafe" },
    { "listunspent", 4, "query_options" },
    { "getblock", 1, "verbosity" },
    { "getblock", 1, "verbose" },
    { "getblockheader", 1, "verbose" },
    { "getchaintxstats", 0, "nblocks" },
    { "gettransaction", 1, "include_watchonly" },
    { "getrawtransaction", 1, "verbose" },
    { "createrawtransaction", 0, "inputs" },
    { "createrawtransaction", 1, "outputs" },
    { "createrawtransaction", 2, "locktime" },
    { "createrawtransaction", 3, "replaceable" },
    { "decoderawtransaction", 1, "iswitness" },
    { "signrawtransactionwithkey", 1, "privkeys" },
    { "signrawtransactionwithkey", 2, "prevtxs" },
    { "signrawtransactionwithwallet", 1, "prevtxs" },
    { "sendrawtransaction", 1, "allowhighfees" },
    { "sendrawtransaction", 1, "maxfeerate" },
    { "testmempoolaccept", 0, "rawtxs" },
    { "testmempoolaccept", 1, "allowhighfees" },
    { "testmempoolaccept", 1, "maxfeerate" },
    { "combinerawtransaction", 0, "txs" },
    { "fundrawtransaction", 1, "options" },
    { "fundrawtransaction", 2, "iswitness" },
    { "walletcreatefundedpsbt", 0, "inputs" },
    { "walletcreatefundedpsbt", 1, "outputs" },
    { "walletcreatefundedpsbt", 2, "locktime" },
    { "walletcreatefundedpsbt", 3, "options" },
    { "walletcreatefundedpsbt", 4, "bip32derivs" },
    { "walletprocesspsbt", 1, "sign" },
    { "walletprocesspsbt", 3, "bip32derivs" },
    { "createpsbt", 0, "inputs" },
    { "createpsbt", 1, "outputs" },
    { "createpsbt", 2, "locktime" },
    { "createpsbt", 3, "replaceable" },
    { "combinepsbt", 0, "txs"},
    { "joinpsbts", 0, "txs"},
    { "finalizepsbt", 1, "extract"},
    { "converttopsbt", 1, "permitsigdata"},
    { "converttopsbt", 2, "iswitness"},
    { "gettxout", 1, "n" },
    { "gettxout", 2, "include_mempool" },
    { "gettxoutproof", 0, "txids" },
    { "lockunspent", 0, "unlock" },
    { "lockunspent", 1, "transactions" },
    { "importprivkey", 2, "rescan" },
    { "importaddress", 2, "rescan" },
    { "importaddress", 3, "p2sh" },
    { "importpubkey", 2, "rescan" },
    { "importmulti", 0, "requests" },
    { "importmulti", 1, "options" },
    { "verifychain", 0, "checklevel" },
    { "verifychain", 1, "nblocks" },
    { "getblockstats", 0, "hash_or_height" },
    { "getblockstats", 1, "stats" },
    { "pruneblockchain", 0, "height" },
    { "keypoolrefill", 0, "newsize" },
    { "getrawmempool", 0, "verbose" },
    { "estimatesmartfee", 0, "conf_target" },
    { "estimaterawfee", 0, "conf_target" },
    { "estimaterawfee", 1, "threshold" },
    { "prioritisetransaction", 1, "dummy" },
    { "prioritisetransaction", 2, "fee_delta" },
    { "setban", 2, "bantime" },
    { "setban", 3, "absolute" },
    { "setnetworkactive", 0, "state" },
    { "getmempoolancestors", 1, "verbose" },
    { "getmempooldescendants", 1, "verbose" },
    { "bumpfee", 1, "options" },
    { "logging", 0, "include" },
    { "logging", 1, "exclude" },
    { "disconnectnode", 1, "nodeid" },
    // Echo with conversion (For testing only)
    { "echojson", 0, "arg0" },
    { "echojson", 1, "arg1" },
    { "echojson", 2, "arg2" },
    { "echojson", 3, "arg3" },
    { "echojson", 4, "arg4" },
    { "echojson", 5, "arg5" },
    { "echojson", 6, "arg6" },
    { "echojson", 7, "arg7" },
    { "echojson", 8, "arg8" },
    { "echojson", 9, "arg9" },
    { "rescanblockchain", 0, "start_height"},
    { "rescanblockchain", 1, "stop_height"},
    { "createwallet", 1, "disable_private_keys"},
    { "createwallet", 2, "blank"},
    { "getnodeaddresses", 0, "count"},
    { "stop", 0, "wait" },
    { "getsuperblockbudget", 0, "index" },
    { "spork", 1, "value" },
    { "voteraw", 1, "tx_index" },
    { "voteraw", 5, "time" },
    { "syscoinmint", 2, "blocknumber" },
    { "syscointxfund", 2, "output_index" },
    { "assetallocationlock", 0, "asset_guid" },
	{ "assetallocationlock", 3, "output_index" },
    { "assetallocationsend", 0, "asset_guid" },
    { "assetallocationsendmany", 0, "asset_guid" },
    { "assetallocationsendmany", 2, "inputs" },
    { "assetallocationburn", 0, "asset_guid" },
    { "assetallocationmint", 0, "asset_guid" },
    { "assetallocationmint", 3, "blocknumber" },
    { "assetallocationinfo", 0, "asset_guid" },
    { "assetallocationbalance", 0, "asset_guid" },
    { "assetallocationsenderstatus", 0, "asset_guid" },
    { "listassetallocations", 0, "count" },
    { "listassetallocations", 1, "from" },
    { "listassetallocations", 2, "options" },
    { "listassetallocationmempoolbalances", 0, "count" },
    { "listassetallocationmempoolbalances", 1, "from" },
    { "listassetallocationmempoolbalances", 2, "options" },
    { "assetnew", 3, "precision" },
    { "assetnew", 6, "update_flags" },
    { "assetupdate", 0, "asset_guid" },
    { "assetupdate", 4, "update_flags" },
    { "assettransfer", 0, "asset_guid" },
    { "assetsend", 0, "asset_guid" },
    { "assetsendmany", 0, "asset_guid" },
    { "assetsendmany", 1, "inputs" },
    { "assetinfo", 0, "asset_guid" },
    { "sentinelping", 0, "version" },
    { "listassets", 0, "count" },
    { "listassets", 1, "from" },
    { "listassets", 2, "options" },
    { "tpstestadd", 0, "starttime" },
    { "tpstestadd", 1, "rawtxs" },
    { "tpstestsetenabled", 0, "enabled" },
    { "syscoinsetethstatus", 1, "highestBlock" },
    { "syscoinsetethheaders", 0, "headers" },
    { "listassetindex", 0, "page" },
    { "listassetindex", 1, "options" },
};
// clang-format on

class CRPCConvertTable
{
private:
    std::set<std::pair<std::string, int>> members;
    std::set<std::pair<std::string, std::string>> membersByName;

public:
    CRPCConvertTable();

    bool convert(const std::string& method, int idx) {
        return (members.count(std::make_pair(method, idx)) > 0);
    }
    bool convert(const std::string& method, const std::string& name) {
        return (membersByName.count(std::make_pair(method, name)) > 0);
    }
};

CRPCConvertTable::CRPCConvertTable()
{
    const unsigned int n_elem =
        (sizeof(vRPCConvertParams) / sizeof(vRPCConvertParams[0]));

    for (unsigned int i = 0; i < n_elem; i++) {
        members.insert(std::make_pair(vRPCConvertParams[i].methodName,
                                      vRPCConvertParams[i].paramIdx));
        membersByName.insert(std::make_pair(vRPCConvertParams[i].methodName,
                                            vRPCConvertParams[i].paramName));
    }
}

static CRPCConvertTable rpcCvtTable;

/** Non-RFC4627 JSON parser, accepts internal values (such as numbers, true, false, null)
 * as well as objects and arrays.
 */
UniValue ParseNonRFCJSONValue(const std::string& strVal)
{
    UniValue jVal;
    if (!jVal.read(std::string("[")+strVal+std::string("]")) ||
        !jVal.isArray() || jVal.size()!=1)
        throw std::runtime_error(std::string("Error parsing JSON:")+strVal);
    return jVal[0];
}

UniValue RPCConvertValues(const std::string &strMethod, const std::vector<std::string> &strParams)
{
    UniValue params(UniValue::VARR);

    for (unsigned int idx = 0; idx < strParams.size(); idx++) {
        const std::string& strVal = strParams[idx];

        if (!rpcCvtTable.convert(strMethod, idx)) {
            // insert string value directly
            params.push_back(strVal);
        } else {
            // parse string as JSON, insert bool/number/object/etc. value
            params.push_back(ParseNonRFCJSONValue(strVal));
        }
    }

    return params;
}

UniValue RPCConvertNamedValues(const std::string &strMethod, const std::vector<std::string> &strParams)
{
    UniValue params(UniValue::VOBJ);

    for (const std::string &s: strParams) {
        size_t pos = s.find('=');
        if (pos == std::string::npos) {
            throw(std::runtime_error("No '=' in named argument '"+s+"', this needs to be present for every argument (even if it is empty)"));
        }

        std::string name = s.substr(0, pos);
        std::string value = s.substr(pos+1);

        if (!rpcCvtTable.convert(strMethod, name)) {
            // insert string value directly
            params.pushKV(name, value);
        } else {
            // parse string as JSON, insert bool/number/object/etc. value
            params.pushKV(name, ParseNonRFCJSONValue(value));
        }
    }

    return params;
}
<commit_msg>Fixed tab to spaces<commit_after>// Copyright (c) 2010 Satoshi Nakamoto
// Copyright (c) 2009-2018 The Syscoin Core developers
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.

#include <rpc/client.h>
#include <rpc/protocol.h>
#include <util/system.h>

#include <set>
#include <stdint.h>

class CRPCConvertParam
{
public:
    std::string methodName; //!< method whose params want conversion
    int paramIdx;           //!< 0-based idx of param to convert
    std::string paramName;  //!< parameter name
};

// clang-format off
/**
 * Specify a (method, idx, name) here if the argument is a non-string RPC
 * argument and needs to be converted from JSON.
 *
 * @note Parameter indexes start from 0.
 */
static const CRPCConvertParam vRPCConvertParams[] =
{
    { "setmocktime", 0, "timestamp" },
    { "generate", 0, "nblocks" },
    { "generate", 1, "maxtries" },
    { "generatetoaddress", 0, "nblocks" },
    { "generatetoaddress", 2, "maxtries" },
    { "getnetworkhashps", 0, "nblocks" },
    { "getnetworkhashps", 1, "height" },
    { "sendtoaddress", 1, "amount" },
    { "sendtoaddress", 4, "subtractfeefromamount" },
    { "sendtoaddress", 5 , "replaceable" },
    { "sendtoaddress", 6 , "conf_target" },
    { "settxfee", 0, "amount" },
    { "sethdseed", 0, "newkeypool" },
    { "getreceivedbyaddress", 1, "minconf" },
    { "getreceivedbylabel", 1, "minconf" },
    { "listreceivedbyaddress", 0, "minconf" },
    { "listreceivedbyaddress", 1, "include_empty" },
    { "listreceivedbyaddress", 2, "include_watchonly" },
    { "listreceivedbylabel", 0, "minconf" },
    { "listreceivedbylabel", 1, "include_empty" },
    { "listreceivedbylabel", 2, "include_watchonly" },
    { "getbalance", 1, "minconf" },
    { "getbalance", 2, "include_watchonly" },
    { "getblockhash", 0, "height" },
    { "waitforblockheight", 0, "height" },
    { "waitforblockheight", 1, "timeout" },
    { "waitforblock", 1, "timeout" },
    { "waitfornewblock", 0, "timeout" },
    { "listtransactions", 1, "count" },
    { "listtransactions", 2, "skip" },
    { "listtransactions", 3, "include_watchonly" },
    { "walletpassphrase", 1, "timeout" },
    { "getblocktemplate", 0, "template_request" },
    { "listsinceblock", 1, "target_confirmations" },
    { "listsinceblock", 2, "include_watchonly" },
    { "listsinceblock", 3, "include_removed" },
    { "sendmany", 1, "amounts" },
    { "sendmany", 2, "minconf" },
    { "sendmany", 4, "subtractfeefrom" },
    { "sendmany", 5 , "replaceable" },
    { "sendmany", 6 , "conf_target" },
    { "deriveaddresses", 1, "range" },
    { "scantxoutset", 1, "scanobjects" },
    { "addmultisigaddress", 0, "nrequired" },
    { "addmultisigaddress", 1, "keys" },
    { "createmultisig", 0, "nrequired" },
    { "createmultisig", 1, "keys" },
    { "listunspent", 0, "minconf" },
    { "listunspent", 1, "maxconf" },
    { "listunspent", 2, "addresses" },
    { "listunspent", 3, "include_unsafe" },
    { "listunspent", 4, "query_options" },
    { "getblock", 1, "verbosity" },
    { "getblock", 1, "verbose" },
    { "getblockheader", 1, "verbose" },
    { "getchaintxstats", 0, "nblocks" },
    { "gettransaction", 1, "include_watchonly" },
    { "getrawtransaction", 1, "verbose" },
    { "createrawtransaction", 0, "inputs" },
    { "createrawtransaction", 1, "outputs" },
    { "createrawtransaction", 2, "locktime" },
    { "createrawtransaction", 3, "replaceable" },
    { "decoderawtransaction", 1, "iswitness" },
    { "signrawtransactionwithkey", 1, "privkeys" },
    { "signrawtransactionwithkey", 2, "prevtxs" },
    { "signrawtransactionwithwallet", 1, "prevtxs" },
    { "sendrawtransaction", 1, "allowhighfees" },
    { "sendrawtransaction", 1, "maxfeerate" },
    { "testmempoolaccept", 0, "rawtxs" },
    { "testmempoolaccept", 1, "allowhighfees" },
    { "testmempoolaccept", 1, "maxfeerate" },
    { "combinerawtransaction", 0, "txs" },
    { "fundrawtransaction", 1, "options" },
    { "fundrawtransaction", 2, "iswitness" },
    { "walletcreatefundedpsbt", 0, "inputs" },
    { "walletcreatefundedpsbt", 1, "outputs" },
    { "walletcreatefundedpsbt", 2, "locktime" },
    { "walletcreatefundedpsbt", 3, "options" },
    { "walletcreatefundedpsbt", 4, "bip32derivs" },
    { "walletprocesspsbt", 1, "sign" },
    { "walletprocesspsbt", 3, "bip32derivs" },
    { "createpsbt", 0, "inputs" },
    { "createpsbt", 1, "outputs" },
    { "createpsbt", 2, "locktime" },
    { "createpsbt", 3, "replaceable" },
    { "combinepsbt", 0, "txs"},
    { "joinpsbts", 0, "txs"},
    { "finalizepsbt", 1, "extract"},
    { "converttopsbt", 1, "permitsigdata"},
    { "converttopsbt", 2, "iswitness"},
    { "gettxout", 1, "n" },
    { "gettxout", 2, "include_mempool" },
    { "gettxoutproof", 0, "txids" },
    { "lockunspent", 0, "unlock" },
    { "lockunspent", 1, "transactions" },
    { "importprivkey", 2, "rescan" },
    { "importaddress", 2, "rescan" },
    { "importaddress", 3, "p2sh" },
    { "importpubkey", 2, "rescan" },
    { "importmulti", 0, "requests" },
    { "importmulti", 1, "options" },
    { "verifychain", 0, "checklevel" },
    { "verifychain", 1, "nblocks" },
    { "getblockstats", 0, "hash_or_height" },
    { "getblockstats", 1, "stats" },
    { "pruneblockchain", 0, "height" },
    { "keypoolrefill", 0, "newsize" },
    { "getrawmempool", 0, "verbose" },
    { "estimatesmartfee", 0, "conf_target" },
    { "estimaterawfee", 0, "conf_target" },
    { "estimaterawfee", 1, "threshold" },
    { "prioritisetransaction", 1, "dummy" },
    { "prioritisetransaction", 2, "fee_delta" },
    { "setban", 2, "bantime" },
    { "setban", 3, "absolute" },
    { "setnetworkactive", 0, "state" },
    { "getmempoolancestors", 1, "verbose" },
    { "getmempooldescendants", 1, "verbose" },
    { "bumpfee", 1, "options" },
    { "logging", 0, "include" },
    { "logging", 1, "exclude" },
    { "disconnectnode", 1, "nodeid" },
    // Echo with conversion (For testing only)
    { "echojson", 0, "arg0" },
    { "echojson", 1, "arg1" },
    { "echojson", 2, "arg2" },
    { "echojson", 3, "arg3" },
    { "echojson", 4, "arg4" },
    { "echojson", 5, "arg5" },
    { "echojson", 6, "arg6" },
    { "echojson", 7, "arg7" },
    { "echojson", 8, "arg8" },
    { "echojson", 9, "arg9" },
    { "rescanblockchain", 0, "start_height"},
    { "rescanblockchain", 1, "stop_height"},
    { "createwallet", 1, "disable_private_keys"},
    { "createwallet", 2, "blank"},
    { "getnodeaddresses", 0, "count"},
    { "stop", 0, "wait" },
    { "getsuperblockbudget", 0, "index" },
    { "spork", 1, "value" },
    { "voteraw", 1, "tx_index" },
    { "voteraw", 5, "time" },
    { "syscoinmint", 2, "blocknumber" },
    { "syscointxfund", 2, "output_index" },
    { "assetallocationlock", 0, "asset_guid" },
    { "assetallocationlock", 3, "output_index" },
    { "assetallocationsend", 0, "asset_guid" },
    { "assetallocationsendmany", 0, "asset_guid" },
    { "assetallocationsendmany", 2, "inputs" },
    { "assetallocationburn", 0, "asset_guid" },
    { "assetallocationmint", 0, "asset_guid" },
    { "assetallocationmint", 3, "blocknumber" },
    { "assetallocationinfo", 0, "asset_guid" },
    { "assetallocationbalance", 0, "asset_guid" },
    { "assetallocationsenderstatus", 0, "asset_guid" },
    { "listassetallocations", 0, "count" },
    { "listassetallocations", 1, "from" },
    { "listassetallocations", 2, "options" },
    { "listassetallocationmempoolbalances", 0, "count" },
    { "listassetallocationmempoolbalances", 1, "from" },
    { "listassetallocationmempoolbalances", 2, "options" },
    { "assetnew", 3, "precision" },
    { "assetnew", 6, "update_flags" },
    { "assetupdate", 0, "asset_guid" },
    { "assetupdate", 4, "update_flags" },
    { "assettransfer", 0, "asset_guid" },
    { "assetsend", 0, "asset_guid" },
    { "assetsendmany", 0, "asset_guid" },
    { "assetsendmany", 1, "inputs" },
    { "assetinfo", 0, "asset_guid" },
    { "sentinelping", 0, "version" },
    { "listassets", 0, "count" },
    { "listassets", 1, "from" },
    { "listassets", 2, "options" },
    { "tpstestadd", 0, "starttime" },
    { "tpstestadd", 1, "rawtxs" },
    { "tpstestsetenabled", 0, "enabled" },
    { "syscoinsetethstatus", 1, "highestBlock" },
    { "syscoinsetethheaders", 0, "headers" },
    { "listassetindex", 0, "page" },
    { "listassetindex", 1, "options" },
};
// clang-format on

class CRPCConvertTable
{
private:
    std::set<std::pair<std::string, int>> members;
    std::set<std::pair<std::string, std::string>> membersByName;

public:
    CRPCConvertTable();

    bool convert(const std::string& method, int idx) {
        return (members.count(std::make_pair(method, idx)) > 0);
    }
    bool convert(const std::string& method, const std::string& name) {
        return (membersByName.count(std::make_pair(method, name)) > 0);
    }
};

CRPCConvertTable::CRPCConvertTable()
{
    const unsigned int n_elem =
        (sizeof(vRPCConvertParams) / sizeof(vRPCConvertParams[0]));

    for (unsigned int i = 0; i < n_elem; i++) {
        members.insert(std::make_pair(vRPCConvertParams[i].methodName,
                                      vRPCConvertParams[i].paramIdx));
        membersByName.insert(std::make_pair(vRPCConvertParams[i].methodName,
                                            vRPCConvertParams[i].paramName));
    }
}

static CRPCConvertTable rpcCvtTable;

/** Non-RFC4627 JSON parser, accepts internal values (such as numbers, true, false, null)
 * as well as objects and arrays.
 */
UniValue ParseNonRFCJSONValue(const std::string& strVal)
{
    UniValue jVal;
    if (!jVal.read(std::string("[")+strVal+std::string("]")) ||
        !jVal.isArray() || jVal.size()!=1)
        throw std::runtime_error(std::string("Error parsing JSON:")+strVal);
    return jVal[0];
}

UniValue RPCConvertValues(const std::string &strMethod, const std::vector<std::string> &strParams)
{
    UniValue params(UniValue::VARR);

    for (unsigned int idx = 0; idx < strParams.size(); idx++) {
        const std::string& strVal = strParams[idx];

        if (!rpcCvtTable.convert(strMethod, idx)) {
            // insert string value directly
            params.push_back(strVal);
        } else {
            // parse string as JSON, insert bool/number/object/etc. value
            params.push_back(ParseNonRFCJSONValue(strVal));
        }
    }

    return params;
}

UniValue RPCConvertNamedValues(const std::string &strMethod, const std::vector<std::string> &strParams)
{
    UniValue params(UniValue::VOBJ);

    for (const std::string &s: strParams) {
        size_t pos = s.find('=');
        if (pos == std::string::npos) {
            throw(std::runtime_error("No '=' in named argument '"+s+"', this needs to be present for every argument (even if it is empty)"));
        }

        std::string name = s.substr(0, pos);
        std::string value = s.substr(pos+1);

        if (!rpcCvtTable.convert(strMethod, name)) {
            // insert string value directly
            params.pushKV(name, value);
        } else {
            // parse string as JSON, insert bool/number/object/etc. value
            params.pushKV(name, ParseNonRFCJSONValue(value));
        }
    }

    return params;
}
<|endoftext|>


<commit_before>#include <QCoreApplication>
#include <QObject>
#include <QProcess>
#include <QSettings>
#include <QString>

#include "debug.h"
#include "utils.h"

#include "scheduling.h"

#define UPDATED_LAUNCHD_PATH_LONG                                              \
    "The OS X launchd scheduling service contained an out-of-date link to "    \
    "Tarsnap GUI (did you upgrade it recently?).\n\nThis has been updated to " \
    "point to the current Tarsnap GUI."

#define UPDATED_LAUNCHD_PATH_SHORT "Updated launchd path to Tarsnap GUI"

#define UPDATED_LAUNCHD_PATH_ERROR                                             \
    "An error occurred while attempting to "                                   \
    "update the OS X launchd path."

struct cmdinfo
{
    int        exit_code;
    QByteArray stderr_msg;
    QByteArray stdout_msg;
};

static struct cmdinfo runCmd(QString cmd, QStringList args,
                             const QByteArray *stdin_msg = nullptr)
{
    QProcess       proc;
    struct cmdinfo info;
    proc.start(cmd, args);

    // We can want stdin_msg to be empty but non-null; for example if we're
    // disabling the scheduling and therefore writing an empty crontab file.
    if(stdin_msg != nullptr)
    {
        proc.write(*stdin_msg);
        proc.closeWriteChannel();
    }
    proc.waitForFinished(-1);

    // Get exit code, working around QProcess not having a valid exitCode()
    // if there's a crash.
    info.exit_code = proc.exitCode();
    if(proc.exitStatus() != QProcess::NormalExit)
        info.exit_code = -1;

    info.stderr_msg = proc.readAllStandardError();
    info.stdout_msg = proc.readAllStandardOutput();

    return (info);
}

// This is an awkward hack which is an intermediate step towards separating
// the front-end and back-end code.  Return values:
//   0: everything ok
//   1: failed to load
//   2: failed to start
static int launchdLoad()
{
    struct cmdinfo pinfo;
    QString        launchdPlistFileName =
        QDir::homePath() + "/Library/LaunchAgents/com.tarsnap.gui.plist";

    pinfo = runCmd("launchctl", QStringList() << "load" << launchdPlistFileName);
    if(pinfo.exit_code != 0)
        return (1);

    pinfo = runCmd("launchctl", QStringList() << "start"
                                              << "com.tarsnap.gui");
    if(pinfo.exit_code != 0)
        return (2);

    return (0);
}

// Return values:
//   0: everything ok
//   1: failed to unload
static int launchdUnload()
{
    struct cmdinfo pinfo;
    QString        launchdPlistFileName =
        QDir::homePath() + "/Library/LaunchAgents/com.tarsnap.gui.plist";

    pinfo =
        runCmd("launchctl", QStringList() << "unload" << launchdPlistFileName);
    if(pinfo.exit_code != 0)
        return (1);

    return (0);
}

#if defined(Q_OS_OSX)
static bool launchdLoaded()
{
    struct cmdinfo pinfo;

    pinfo = runCmd("launchctl", QStringList() << "list"
                                              << "com.tarsnap.gui");
    if(pinfo.exit_code != 0)
        return (false);

    return (true);
}
#endif

struct scheduleinfo launchdEnable()
{
    struct scheduleinfo info = {SCHEDULE_OK, "", ""};

    QFile launchdPlist(":/com.tarsnap.gui.plist");
    launchdPlist.open(QIODevice::ReadOnly | QIODevice::Text);
    QFile launchdPlistFile(QDir::homePath()
                           + "/Library/LaunchAgents/com.tarsnap.gui.plist");
    if(launchdPlistFile.exists())
    {
        info.status  = SCHEDULE_ERROR;
        info.message = QObject::tr("Looks like scheduling is already enabled."
                                   " Nothing to do.\n\n%1")
                           .arg(CRON_MARKER_HELP);
        return info;
    }
    if(!launchdPlistFile.open(QIODevice::WriteOnly | QIODevice::Text))
    {
        info.status = SCHEDULE_ERROR;
        info.message =
            QObject::tr("Failed to write service file %1. Aborting operation.")
                .arg(launchdPlistFile.fileName());
        return info;
    }
    launchdPlistFile.write(
        launchdPlist.readAll()
            .replace("%1", QCoreApplication::applicationFilePath().toLatin1())
            .replace("%2", QDir::homePath().toLatin1()));
    launchdPlist.close();
    launchdPlistFile.close();

    int ret = launchdLoad();
    if(ret == 1)
    {
        info.status  = SCHEDULE_ERROR;
        info.message = QObject::tr("Failed to load launchd service file.");
        return info;
    }
    else if(ret == 2)
    {
        info.status  = SCHEDULE_ERROR;
        info.message = QObject::tr("Failed to start launchd service file.");
        return info;
    }
    return info;
}

struct scheduleinfo launchdDisable()
{
    struct scheduleinfo info = {SCHEDULE_OK, "", ""};

    QFile launchdPlistFile(QDir::homePath()
                           + "/Library/LaunchAgents/com.tarsnap.gui.plist");
    if(!launchdPlistFile.exists())
    {
        info.status = SCHEDULE_ERROR;
        info.message =
            QObject::tr("Launchd service file not found:\n%1\n Nothing to do.")
                .arg(launchdPlistFile.fileName());
        return info;
    }

    int ret = launchdUnload();
    if(ret == 1)
    {
        info.status  = SCHEDULE_ERROR;
        info.message = QObject::tr("Failed to unload launchd service.");
        return info;
    }

    if(!launchdPlistFile.remove())
    {
        info.status = SCHEDULE_ERROR;
        info.message =
            QObject::tr("Cannot remove service file:\n%1\nAborting operation.")
                .arg(launchdPlistFile.fileName());
        return info;
    }
    return info;
}

struct scheduleinfo cronEnable()
{
    struct scheduleinfo info = {SCHEDULE_OK, "", ""};

    struct cmdinfo pinfo;
    pinfo = runCmd("crontab", QStringList() << "-l");
    if(pinfo.exit_code != 0)
    {
        QString error(pinfo.stderr_msg);
        /* On some distros crontab -l exits with error 1 and message
         * "no crontab for username" if there's no crontab installed
         * for the current user. If parent is the case proceed and don't err.
         */
        if(-1 == error.indexOf(QRegExp("^(crontab: )?no crontab for")))
        {
            info.status = SCHEDULE_ERROR;
            info.message =
                QObject::tr("Failed to list current crontab: %1").arg(error);
            return info;
        }
    }
    QString currentCrontab(pinfo.stdout_msg);

    QRegExp rx(QString("\n?%1.+%2\n?")
                   .arg(QRegExp::escape(CRON_MARKER_BEGIN))
                   .arg(QRegExp::escape(CRON_MARKER_END)));
    rx.setMinimal(true);
    if(-1 != rx.indexIn(currentCrontab))
    {
        info.status = SCHEDULE_ERROR;
        info.message =
            QObject::tr("Looks like scheduling is already enabled for the"
                        " current user's crontab. Nothing to do."
                        "\n%1")
                .arg(CRON_MARKER_HELP);
        return info;
    }

    QString             cronLine(CRON_LINE);
    QProcessEnvironment env = QProcessEnvironment::systemEnvironment();
    cronLine =
        cronLine
            .arg(env.contains("SCREEN") ? "SCREEN=" + env.value("SCREEN") : "")
            .arg(env.contains("DISPLAY") ? "DISPLAY=" + env.value("DISPLAY")
                                         : "")
            .arg(env.contains("XAUTHORITY")
                     ? "XAUTHORITY=" + env.value("XAUTHORITY")
                     : "")
            .arg(QCoreApplication::applicationFilePath());

    QString cronBlock("\n%1\n%2\n%3\n%4\n");
    cronBlock = cronBlock.arg(CRON_MARKER_BEGIN)
                    .arg(CRON_MARKER_HELP)
                    .arg(cronLine)
                    .arg(CRON_MARKER_END);

    info.status  = SCHEDULE_NEED_INFO;
    info.message = cronBlock;
    info.extra   = currentCrontab;

    return info;
}

struct scheduleinfo cronEnable_p2(QString cronBlock, QString currentCrontab)
{
    struct scheduleinfo info = {SCHEDULE_OK, "", ""};
    struct cmdinfo      pinfo;

    currentCrontab.append(cronBlock.toLatin1());
    QByteArray newCrontab = currentCrontab.toLatin1();

    pinfo = runCmd("crontab", QStringList() << "-", &newCrontab);
    if(pinfo.exit_code != 0)
    {
        info.status  = SCHEDULE_ERROR;
        info.message = QObject::tr("Failed to update crontab: %1")
                           .arg(QString(pinfo.stderr_msg));
        return info;
    }
    return info;
}

struct scheduleinfo cronDisable()
{
    struct scheduleinfo info = {SCHEDULE_OK, "", ""};
    struct cmdinfo      pinfo;

    pinfo = runCmd("crontab", QStringList() << "-l");
    if(pinfo.exit_code != 0)
    {
        QString error(pinfo.stderr_msg);
        /* On some distros crontab -l exits with error 1 and message
         * "no crontab for username" if there's no crontab installed
         * for the current user. If parent is the case proceed and don't err.
         */
        if(error.startsWith(QLatin1String("no crontab for")))
        {
            info.status = SCHEDULE_ERROR;
            info.message =
                QObject::tr("There's no crontab for the current user."
                            " Nothing to do.\n\n%1")
                    .arg(CRON_MARKER_HELP);
            return info;
        }
        else
        {
            info.status = SCHEDULE_ERROR;
            info.message =
                QObject::tr("Failed to list current crontab: %1").arg(error);
            return info;
        }
    }
    QString currentCrontab(pinfo.stdout_msg);
    if(currentCrontab.isEmpty())
    {
        info.status = SCHEDULE_ERROR;
        info.message =
            QObject::tr("Looks like the crontab for the current user is"
                        " empty. Nothing to do.\n\n%1")
                .arg(CRON_MARKER_HELP);
        return info;
    }

    QRegExp rx(QString("\n?%1.+%2\n?")
                   .arg(QRegExp::escape(CRON_MARKER_BEGIN))
                   .arg(QRegExp::escape(CRON_MARKER_END)));
    //    rx.setMinimal(true);
    QString linesToRemove;
    int     pos = 0;
    while((pos = rx.indexIn(currentCrontab, pos)) != -1)
    {
        linesToRemove += rx.cap();
        pos += rx.matchedLength();
    }

    if(linesToRemove.isEmpty())
    {
        info.status = SCHEDULE_ERROR;
        info.message =
            QObject::tr("Looks like Job scheduling hasn't been enabled"
                        " yet. Nothing to do. \n\n%1")
                .arg(CRON_MARKER_HELP);
        return info;
    }
    info.status  = SCHEDULE_NEED_INFO;
    info.message = linesToRemove;
    info.extra   = currentCrontab;
    return info;
}

struct scheduleinfo cronDisable_p2(QString linesToRemove, QString currentCrontab)
{
    struct scheduleinfo info = {SCHEDULE_OK, "", ""};
    struct cmdinfo      pinfo;

    currentCrontab.remove(linesToRemove);
    DEBUG << currentCrontab;
    QByteArray newCrontab = currentCrontab.toLatin1();

    pinfo = runCmd("crontab", QStringList() << "-", &newCrontab);
    if(pinfo.exit_code != 0)
    {
        info.status  = SCHEDULE_ERROR;
        info.message = QObject::tr("Failed to update crontab: %1")
                           .arg(QString(pinfo.stderr_msg));
        return info;
    }
    return info;
}

struct scheduleinfo correctedSchedulingPath()
{
    struct scheduleinfo info = {SCHEDULE_NOTHING_HAPPENED, "", ""};
#if defined(Q_OS_OSX)
    QSettings launchdPlist(QDir::homePath()
                               + "/Library/LaunchAgents/com.tarsnap.gui.plist",
                           QSettings::NativeFormat);

    // Bail if the file doesn't exist
    if(!launchdPlist.contains("ProgramArguments"))
    {
        info.status  = SCHEDULE_ERROR;
        info.message = QObject::tr(UPDATED_LAUNCHD_PATH_ERROR);
        return (info);
    }

    // Get path, bail if it still exists (we assume it's still executable)
    QStringList args =
        launchdPlist.value("ProgramArguments").value<QStringList>();
    if(QFile::exists(args.at(0)))
    {
        info.status  = SCHEDULE_ERROR;
        info.message = QObject::tr(UPDATED_LAUNCHD_PATH_ERROR);
        return (info);
    }

    // Update the path
    args.replace(0, QCoreApplication::applicationFilePath().toLatin1());
    launchdPlist.setValue("ProgramArguments", args);
    launchdPlist.sync();

    // Stop launchd script if it's loaded
    if(launchdLoaded())
    {
        if(launchdUnload() != 0)
        {
            info.status  = SCHEDULE_ERROR;
            info.message = QObject::tr(UPDATED_LAUNCHD_PATH_ERROR);
            return (info);
        }
    }

    // Load (and start) new program
    if(launchdLoad() != 0)
    {
        info.status  = SCHEDULE_ERROR;
        info.message = QObject::tr(UPDATED_LAUNCHD_PATH_ERROR);
        return (info);
    }

    info.status  = SCHEDULE_OK;
    info.message = tr(UPDATED_LAUNCHD_PATH_LONG);
    info.extra   = tr(UPDATED_LAUNCHED_PATH_SHORT);
    return (0);
#else
    return (info);
#endif
}
<commit_msg>Scheduling.cpp: fix OSX compile<commit_after>#include <QCoreApplication>
#include <QObject>
#include <QProcess>
#include <QSettings>
#include <QString>

#include "debug.h"
#include "utils.h"

#include "scheduling.h"

#define UPDATED_LAUNCHD_PATH_LONG                                              \
    "The OS X launchd scheduling service contained an out-of-date link to "    \
    "Tarsnap GUI (did you upgrade it recently?).\n\nThis has been updated to " \
    "point to the current Tarsnap GUI."

#define UPDATED_LAUNCHD_PATH_SHORT "Updated launchd path to Tarsnap GUI"

#define UPDATED_LAUNCHD_PATH_ERROR                                             \
    "An error occurred while attempting to "                                   \
    "update the OS X launchd path."

struct cmdinfo
{
    int        exit_code;
    QByteArray stderr_msg;
    QByteArray stdout_msg;
};

static struct cmdinfo runCmd(QString cmd, QStringList args,
                             const QByteArray *stdin_msg = nullptr)
{
    QProcess       proc;
    struct cmdinfo info;
    proc.start(cmd, args);

    // We can want stdin_msg to be empty but non-null; for example if we're
    // disabling the scheduling and therefore writing an empty crontab file.
    if(stdin_msg != nullptr)
    {
        proc.write(*stdin_msg);
        proc.closeWriteChannel();
    }
    proc.waitForFinished(-1);

    // Get exit code, working around QProcess not having a valid exitCode()
    // if there's a crash.
    info.exit_code = proc.exitCode();
    if(proc.exitStatus() != QProcess::NormalExit)
        info.exit_code = -1;

    info.stderr_msg = proc.readAllStandardError();
    info.stdout_msg = proc.readAllStandardOutput();

    return (info);
}

// This is an awkward hack which is an intermediate step towards separating
// the front-end and back-end code.  Return values:
//   0: everything ok
//   1: failed to load
//   2: failed to start
static int launchdLoad()
{
    struct cmdinfo pinfo;
    QString        launchdPlistFileName =
        QDir::homePath() + "/Library/LaunchAgents/com.tarsnap.gui.plist";

    pinfo = runCmd("launchctl", QStringList() << "load" << launchdPlistFileName);
    if(pinfo.exit_code != 0)
        return (1);

    pinfo = runCmd("launchctl", QStringList() << "start"
                                              << "com.tarsnap.gui");
    if(pinfo.exit_code != 0)
        return (2);

    return (0);
}

// Return values:
//   0: everything ok
//   1: failed to unload
static int launchdUnload()
{
    struct cmdinfo pinfo;
    QString        launchdPlistFileName =
        QDir::homePath() + "/Library/LaunchAgents/com.tarsnap.gui.plist";

    pinfo =
        runCmd("launchctl", QStringList() << "unload" << launchdPlistFileName);
    if(pinfo.exit_code != 0)
        return (1);

    return (0);
}

#if defined(Q_OS_OSX)
static bool launchdLoaded()
{
    struct cmdinfo pinfo;

    pinfo = runCmd("launchctl", QStringList() << "list"
                                              << "com.tarsnap.gui");
    if(pinfo.exit_code != 0)
        return (false);

    return (true);
}
#endif

struct scheduleinfo launchdEnable()
{
    struct scheduleinfo info = {SCHEDULE_OK, "", ""};

    QFile launchdPlist(":/com.tarsnap.gui.plist");
    launchdPlist.open(QIODevice::ReadOnly | QIODevice::Text);
    QFile launchdPlistFile(QDir::homePath()
                           + "/Library/LaunchAgents/com.tarsnap.gui.plist");
    if(launchdPlistFile.exists())
    {
        info.status  = SCHEDULE_ERROR;
        info.message = QObject::tr("Looks like scheduling is already enabled."
                                   " Nothing to do.\n\n%1")
                           .arg(CRON_MARKER_HELP);
        return info;
    }
    if(!launchdPlistFile.open(QIODevice::WriteOnly | QIODevice::Text))
    {
        info.status = SCHEDULE_ERROR;
        info.message =
            QObject::tr("Failed to write service file %1. Aborting operation.")
                .arg(launchdPlistFile.fileName());
        return info;
    }
    launchdPlistFile.write(
        launchdPlist.readAll()
            .replace("%1", QCoreApplication::applicationFilePath().toLatin1())
            .replace("%2", QDir::homePath().toLatin1()));
    launchdPlist.close();
    launchdPlistFile.close();

    int ret = launchdLoad();
    if(ret == 1)
    {
        info.status  = SCHEDULE_ERROR;
        info.message = QObject::tr("Failed to load launchd service file.");
        return info;
    }
    else if(ret == 2)
    {
        info.status  = SCHEDULE_ERROR;
        info.message = QObject::tr("Failed to start launchd service file.");
        return info;
    }
    return info;
}

struct scheduleinfo launchdDisable()
{
    struct scheduleinfo info = {SCHEDULE_OK, "", ""};

    QFile launchdPlistFile(QDir::homePath()
                           + "/Library/LaunchAgents/com.tarsnap.gui.plist");
    if(!launchdPlistFile.exists())
    {
        info.status = SCHEDULE_ERROR;
        info.message =
            QObject::tr("Launchd service file not found:\n%1\n Nothing to do.")
                .arg(launchdPlistFile.fileName());
        return info;
    }

    int ret = launchdUnload();
    if(ret == 1)
    {
        info.status  = SCHEDULE_ERROR;
        info.message = QObject::tr("Failed to unload launchd service.");
        return info;
    }

    if(!launchdPlistFile.remove())
    {
        info.status = SCHEDULE_ERROR;
        info.message =
            QObject::tr("Cannot remove service file:\n%1\nAborting operation.")
                .arg(launchdPlistFile.fileName());
        return info;
    }
    return info;
}

struct scheduleinfo cronEnable()
{
    struct scheduleinfo info = {SCHEDULE_OK, "", ""};

    struct cmdinfo pinfo;
    pinfo = runCmd("crontab", QStringList() << "-l");
    if(pinfo.exit_code != 0)
    {
        QString error(pinfo.stderr_msg);
        /* On some distros crontab -l exits with error 1 and message
         * "no crontab for username" if there's no crontab installed
         * for the current user. If parent is the case proceed and don't err.
         */
        if(-1 == error.indexOf(QRegExp("^(crontab: )?no crontab for")))
        {
            info.status = SCHEDULE_ERROR;
            info.message =
                QObject::tr("Failed to list current crontab: %1").arg(error);
            return info;
        }
    }
    QString currentCrontab(pinfo.stdout_msg);

    QRegExp rx(QString("\n?%1.+%2\n?")
                   .arg(QRegExp::escape(CRON_MARKER_BEGIN))
                   .arg(QRegExp::escape(CRON_MARKER_END)));
    rx.setMinimal(true);
    if(-1 != rx.indexIn(currentCrontab))
    {
        info.status = SCHEDULE_ERROR;
        info.message =
            QObject::tr("Looks like scheduling is already enabled for the"
                        " current user's crontab. Nothing to do."
                        "\n%1")
                .arg(CRON_MARKER_HELP);
        return info;
    }

    QString             cronLine(CRON_LINE);
    QProcessEnvironment env = QProcessEnvironment::systemEnvironment();
    cronLine =
        cronLine
            .arg(env.contains("SCREEN") ? "SCREEN=" + env.value("SCREEN") : "")
            .arg(env.contains("DISPLAY") ? "DISPLAY=" + env.value("DISPLAY")
                                         : "")
            .arg(env.contains("XAUTHORITY")
                     ? "XAUTHORITY=" + env.value("XAUTHORITY")
                     : "")
            .arg(QCoreApplication::applicationFilePath());

    QString cronBlock("\n%1\n%2\n%3\n%4\n");
    cronBlock = cronBlock.arg(CRON_MARKER_BEGIN)
                    .arg(CRON_MARKER_HELP)
                    .arg(cronLine)
                    .arg(CRON_MARKER_END);

    info.status  = SCHEDULE_NEED_INFO;
    info.message = cronBlock;
    info.extra   = currentCrontab;

    return info;
}

struct scheduleinfo cronEnable_p2(QString cronBlock, QString currentCrontab)
{
    struct scheduleinfo info = {SCHEDULE_OK, "", ""};
    struct cmdinfo      pinfo;

    currentCrontab.append(cronBlock.toLatin1());
    QByteArray newCrontab = currentCrontab.toLatin1();

    pinfo = runCmd("crontab", QStringList() << "-", &newCrontab);
    if(pinfo.exit_code != 0)
    {
        info.status  = SCHEDULE_ERROR;
        info.message = QObject::tr("Failed to update crontab: %1")
                           .arg(QString(pinfo.stderr_msg));
        return info;
    }
    return info;
}

struct scheduleinfo cronDisable()
{
    struct scheduleinfo info = {SCHEDULE_OK, "", ""};
    struct cmdinfo      pinfo;

    pinfo = runCmd("crontab", QStringList() << "-l");
    if(pinfo.exit_code != 0)
    {
        QString error(pinfo.stderr_msg);
        /* On some distros crontab -l exits with error 1 and message
         * "no crontab for username" if there's no crontab installed
         * for the current user. If parent is the case proceed and don't err.
         */
        if(error.startsWith(QLatin1String("no crontab for")))
        {
            info.status = SCHEDULE_ERROR;
            info.message =
                QObject::tr("There's no crontab for the current user."
                            " Nothing to do.\n\n%1")
                    .arg(CRON_MARKER_HELP);
            return info;
        }
        else
        {
            info.status = SCHEDULE_ERROR;
            info.message =
                QObject::tr("Failed to list current crontab: %1").arg(error);
            return info;
        }
    }
    QString currentCrontab(pinfo.stdout_msg);
    if(currentCrontab.isEmpty())
    {
        info.status = SCHEDULE_ERROR;
        info.message =
            QObject::tr("Looks like the crontab for the current user is"
                        " empty. Nothing to do.\n\n%1")
                .arg(CRON_MARKER_HELP);
        return info;
    }

    QRegExp rx(QString("\n?%1.+%2\n?")
                   .arg(QRegExp::escape(CRON_MARKER_BEGIN))
                   .arg(QRegExp::escape(CRON_MARKER_END)));
    //    rx.setMinimal(true);
    QString linesToRemove;
    int     pos = 0;
    while((pos = rx.indexIn(currentCrontab, pos)) != -1)
    {
        linesToRemove += rx.cap();
        pos += rx.matchedLength();
    }

    if(linesToRemove.isEmpty())
    {
        info.status = SCHEDULE_ERROR;
        info.message =
            QObject::tr("Looks like Job scheduling hasn't been enabled"
                        " yet. Nothing to do. \n\n%1")
                .arg(CRON_MARKER_HELP);
        return info;
    }
    info.status  = SCHEDULE_NEED_INFO;
    info.message = linesToRemove;
    info.extra   = currentCrontab;
    return info;
}

struct scheduleinfo cronDisable_p2(QString linesToRemove, QString currentCrontab)
{
    struct scheduleinfo info = {SCHEDULE_OK, "", ""};
    struct cmdinfo      pinfo;

    currentCrontab.remove(linesToRemove);
    DEBUG << currentCrontab;
    QByteArray newCrontab = currentCrontab.toLatin1();

    pinfo = runCmd("crontab", QStringList() << "-", &newCrontab);
    if(pinfo.exit_code != 0)
    {
        info.status  = SCHEDULE_ERROR;
        info.message = QObject::tr("Failed to update crontab: %1")
                           .arg(QString(pinfo.stderr_msg));
        return info;
    }
    return info;
}

struct scheduleinfo correctedSchedulingPath()
{
    struct scheduleinfo info = {SCHEDULE_NOTHING_HAPPENED, "", ""};
#if defined(Q_OS_OSX)
    QSettings launchdPlist(QDir::homePath()
                               + "/Library/LaunchAgents/com.tarsnap.gui.plist",
                           QSettings::NativeFormat);

    // Bail if the file doesn't exist
    if(!launchdPlist.contains("ProgramArguments"))
    {
        info.status  = SCHEDULE_ERROR;
        info.message = QObject::tr(UPDATED_LAUNCHD_PATH_ERROR);
        return (info);
    }

    // Get path, bail if it still exists (we assume it's still executable)
    QStringList args =
        launchdPlist.value("ProgramArguments").value<QStringList>();
    if(QFile::exists(args.at(0)))
    {
        info.status  = SCHEDULE_ERROR;
        info.message = QObject::tr(UPDATED_LAUNCHD_PATH_ERROR);
        return (info);
    }

    // Update the path
    args.replace(0, QCoreApplication::applicationFilePath().toLatin1());
    launchdPlist.setValue("ProgramArguments", args);
    launchdPlist.sync();

    // Stop launchd script if it's loaded
    if(launchdLoaded())
    {
        if(launchdUnload() != 0)
        {
            info.status  = SCHEDULE_ERROR;
            info.message = QObject::tr(UPDATED_LAUNCHD_PATH_ERROR);
            return (info);
        }
    }

    // Load (and start) new program
    if(launchdLoad() != 0)
    {
        info.status  = SCHEDULE_ERROR;
        info.message = QObject::tr(UPDATED_LAUNCHD_PATH_ERROR);
        return (info);
    }

    info.status  = SCHEDULE_OK;
    info.message = QObject::tr(UPDATED_LAUNCHD_PATH_LONG);
    info.extra   = QObject::tr(UPDATED_LAUNCHD_PATH_SHORT);
    return (info);
#else
    return (info);
#endif
}
<|endoftext|>


<commit_before>#include <cstdlib>
#include <iostream>
#include <boost/bind.hpp>
#include <boost/asio.hpp>
#include <boost/program_options.hpp>
#include <boost/thread/thread.hpp>
#include <rados/librados.hpp>
#include "libzlog.h"
#include "zlog.pb.h"

namespace po = boost::program_options;

class Sequence {
 public:
  Sequence() : seq_(0) {}
  Sequence(uint64_t seq) : seq_(seq) {}

  uint64_t read() {
    return seq_;
  }

  void inc() {
    seq_++;
  }

 private:
  uint64_t seq_;
};

class LogManager {
 public:
  LogManager() {
    thread_ = boost::thread(&LogManager::Run, this);
  }

  /*
   * Read and optionally increment the log sequence number.
   */
  uint64_t ReadSequence(const std::string& pool, const std::string& name,
      uint64_t epoch, bool increment, uint64_t *seq) {

    boost::unique_lock<boost::mutex> g(lock_);

    std::map<std::pair<std::string, std::string>, Log>::iterator it =
      logs_.find(std::make_pair(pool, name));

    if (it == logs_.end()) {
      QueueLogInit(pool, name);
      return -EAGAIN;
    }

    if (epoch < it->second.epoch)
      return -ERANGE;

    if (increment)
      it->second.seq.inc();

    *seq = it->second.seq.read();

    return 0;
  }

 private:
  struct Log {
    Log() {}
    Log(uint64_t pos, uint64_t epoch) : seq(pos), epoch(epoch) {}
    Sequence seq;
    uint64_t epoch;
  };

  /*
   * Prepare the log for this sequencer. After this function runs a new
   * epoch has been allocated, each storage device is sealed with the new
   * epoch, and the maximum position written to prior to the new epoch is
   * found. The sequencer must not be allowed to hand out new positions until
   * these steps are completed successfully.
   */
  int InitLog(const std::string& pool, const std::string& name,
      uint64_t *pepoch, uint64_t *pposition) {

    librados::Rados rados;
    int ret = rados.init(NULL);
    if (ret) {
      std::cerr << "could not initialize rados client" << std::endl;
      return ret;
    }

    rados.conf_read_file(NULL);

    ret = rados.connect();
    if (ret) {
      std::cerr << "rados client could not connect" << std::endl;
      return ret;
    }

    librados::IoCtx ioctx;
    ret = rados.ioctx_create(pool.c_str(), ioctx);
    if (ret) {
      std::cerr << "failed to connect to pool " << pool
        << " ret " << ret << std::endl;
      return ret;
    }

    zlog::Log log;
    ret = zlog::Log::Open(ioctx, name, NULL, log);
    if (ret) {
      std::cerr << "failed to open log " << name << std::endl;
      return ret;
    }

    uint64_t epoch;
    ret = log.SetProjection(&epoch);
    if (ret) {
      std::cerr << "failed to set new projection " << ret << std::endl;
      return ret;
    }

    ret = log.Seal(epoch);
    if (ret) {
      std::cerr << "failed to seal the store " << ret << std::endl;
      return ret;
    }

    uint64_t position;
    ret = log.FindMaxPosition(epoch, &position);
    if (ret) {
      std::cerr << "failed to find max position " << ret << std::endl;
      return ret;
    }

    *pepoch = epoch;
    *pposition = position;

    ioctx.close();
    rados.shutdown();

    return 0;
  }

  /*
   * Queue a log to be initialized.
   */
  void QueueLogInit(const std::string& pool, const std::string& name) {
    pending_logs_.insert(std::make_pair(pool, name));
    cond_.notify_one();
  }

  void Run() {
    for (;;) {
      std::string pool, name;

      {
        boost::unique_lock<boost::mutex> g(lock_);
        while (pending_logs_.empty())
          cond_.wait(g);
        std::set<std::pair<std::string, std::string> >::iterator it =
          pending_logs_.begin();
        assert(it != pending_logs_.end());
        pool = it->first;
        name = it->second;
      }

      uint64_t position, epoch;
      int ret = InitLog(pool, name, &epoch, &position);
      if (ret) {
        boost::unique_lock<boost::mutex> g(lock_);
        pending_logs_.erase(std::make_pair(pool, name));
        std::cerr << "failed to init log" << std::endl;
        continue;
      }

      {
        boost::unique_lock<boost::mutex> g(lock_);
        std::pair<std::string, std::string> key = std::make_pair(pool, name);
        assert(pending_logs_.count(key) == 1);
        pending_logs_.erase(key);
        assert(logs_.count(key) == 0);
        Log log(position, epoch);
        logs_[key] = log;
      }
    }
  }

  boost::thread thread_;
  boost::mutex lock_;
  boost::condition_variable cond_;
  std::map<std::pair<std::string, std::string>, LogManager::Log > logs_;
  std::set<std::pair<std::string, std::string> > pending_logs_;
};

static LogManager *log_mgr;

class Session {
 public:
  Session(boost::asio::io_service& io_service)
    : socket_(io_service)
  {}

  boost::asio::ip::tcp::socket& socket() {
    return socket_;
  }

  void start() {
    boost::asio::async_read(socket_,
        boost::asio::buffer(buffer_, sizeof(uint32_t)),
        boost::bind(&Session::handle_hdr, this,
          boost::asio::placeholders::error,
          boost::asio::placeholders::bytes_transferred));
  }

 private:
  void handle_hdr(const boost::system::error_code& err, size_t size) {
    if (err) {
      delete this;
      return;
    }

    uint32_t msg_size = ntohl(*((uint32_t*)buffer_));

    if (msg_size > sizeof(buffer_)) {
      std::cerr << "message is too large" << std::endl;
      delete this;
      return;
    }

    boost::asio::async_read(socket_,
        boost::asio::buffer(buffer_, msg_size),
        boost::bind(&Session::handle_msg, this,
          boost::asio::placeholders::error,
          boost::asio::placeholders::bytes_transferred));
  }

  void handle_msg(const boost::system::error_code& err, size_t size) {
    if (err) {
      delete this;
      return;
    }

    req_.Clear();

    if (!req_.ParseFromArray(buffer_, size)) {
      std::cerr << "failed to parse message" << std::endl;
      delete this;
      return;
    }

    if (!req_.IsInitialized()) {
      std::cerr << "received incomplete message" << std::endl;
      delete this;
      return;
    }

    reply_.Clear();

    uint64_t seq;

    int ret = log_mgr->ReadSequence(req_.pool(), req_.name(),
        req_.epoch(), req_.next(), &seq);
    if (ret == -EAGAIN)
      reply_.set_status(zlog_proto::MSeqReply::INIT_LOG);
    else if (ret == -ERANGE)
      reply_.set_status(zlog_proto::MSeqReply::STALE_EPOCH);
    else
      assert(!ret);

    reply_.set_position(seq);

    assert(reply_.IsInitialized());

    uint32_t msg_size = reply_.ByteSize();
    assert(msg_size < sizeof(buffer_));
    assert(reply_.SerializeToArray(buffer_, msg_size));

    // scatter/gather buffers
    std::vector<boost::asio::const_buffer> out;
    be_msg_size_ = htonl(msg_size);
    out.push_back(boost::asio::buffer(&be_msg_size_, sizeof(be_msg_size_)));
    out.push_back(boost::asio::buffer(buffer_, msg_size));

    boost::asio::async_write(socket_, out,
        boost::bind(&Session::handle_reply, this,
          boost::asio::placeholders::error,
          boost::asio::placeholders::bytes_transferred));
  }

  void handle_reply(const boost::system::error_code& err, size_t size) {
    if (err) {
      delete this;
      return;
    }

    start();
  }

  boost::asio::ip::tcp::socket socket_;

  char buffer_[1024];
  uint32_t be_msg_size_;

  zlog_proto::MSeqRequest req_;
  zlog_proto::MSeqReply reply_;
};

class Server {
 public:
  Server(boost::asio::io_service& io_service, short port)
    : io_service_(io_service),
    acceptor_(io_service,
        boost::asio::ip::tcp::endpoint(
          boost::asio::ip::tcp::v4(), port))
  {
    start_accept();
  }

 private:
  void start_accept() {
    Session* new_session = new Session(io_service_);
    acceptor_.async_accept(new_session->socket(),
        boost::bind(&Server::handle_accept, this, new_session,
          boost::asio::placeholders::error));
  }

  void handle_accept(Session* new_session,
      const boost::system::error_code& error) {
    if (!error)
      new_session->start();
    else
      delete new_session;
    start_accept();
  }

  boost::asio::io_service& io_service_;
  boost::asio::ip::tcp::acceptor acceptor_;
};

int main(int argc, char* argv[])
{
  int port;
  std::string host;

  po::options_description desc("Allowed options");
  desc.add_options()
    ("port", po::value<int>(&port)->required(), "Server port")
    ("daemon,d", "Run in background")
  ;

  po::variables_map vm;
  po::store(po::parse_command_line(argc, argv, desc), vm);
  po::notify(vm);

  boost::asio::io_service io_service;
  Server s(io_service, port);

  if (vm.count("daemon")) {
    io_service.notify_fork(boost::asio::io_service::fork_prepare);

    pid_t pid = fork();
    if (pid < 0) {
      exit(EXIT_FAILURE);
    }

    if (pid > 0) {
      exit(EXIT_SUCCESS);
    }

    pid_t sid = setsid();
    if (sid < 0) {
      exit(EXIT_FAILURE);
    }

    umask(0);

    close(0);
    close(1);
    close(2);

    io_service.notify_fork(boost::asio::io_service::fork_child);
  }

  log_mgr = new LogManager();

  io_service.run();

  return 0;
}
<commit_msg>seqr: add basic performance monitor<commit_after>#include <cstdlib>
#include <iostream>
#include <boost/bind.hpp>
#include <boost/asio.hpp>
#include <boost/program_options.hpp>
#include <boost/thread/thread.hpp>
#include <rados/librados.hpp>
#include "libzlog.h"
#include "zlog.pb.h"

namespace po = boost::program_options;

static uint64_t get_time(void)
{
  struct timespec ts;
  int ret = clock_gettime(CLOCK_MONOTONIC, &ts);
  assert(ret == 0);
  uint64_t nsec = ((uint64_t)ts.tv_sec) * ((uint64_t)1000000000);
  nsec += ts.tv_nsec;
  return nsec;
}

class Sequence {
 public:
  Sequence() : seq_(0) {}
  Sequence(uint64_t seq) : seq_(seq) {}

  uint64_t read() {
    return seq_;
  }

  void inc() {
    seq_++;
  }

 private:
  uint64_t seq_;
};

class LogManager {
 public:
  LogManager() {
    thread_ = boost::thread(&LogManager::Run, this);
    bench_thread_ = boost::thread(&LogManager::BenchMonitor, this);
  }

  /*
   * Read and optionally increment the log sequence number.
   */
  uint64_t ReadSequence(const std::string& pool, const std::string& name,
      uint64_t epoch, bool increment, uint64_t *seq) {

    boost::unique_lock<boost::mutex> g(lock_);

    std::map<std::pair<std::string, std::string>, Log>::iterator it =
      logs_.find(std::make_pair(pool, name));

    if (it == logs_.end()) {
      QueueLogInit(pool, name);
      return -EAGAIN;
    }

    if (epoch < it->second.epoch)
      return -ERANGE;

    if (increment)
      it->second.seq.inc();

    *seq = it->second.seq.read();

    return 0;
  }

 private:
  struct Log {
    Log() {}
    Log(uint64_t pos, uint64_t epoch) : seq(pos), epoch(epoch) {}
    Sequence seq;
    uint64_t epoch;
  };

  /*
   * Prepare the log for this sequencer. After this function runs a new
   * epoch has been allocated, each storage device is sealed with the new
   * epoch, and the maximum position written to prior to the new epoch is
   * found. The sequencer must not be allowed to hand out new positions until
   * these steps are completed successfully.
   */
  int InitLog(const std::string& pool, const std::string& name,
      uint64_t *pepoch, uint64_t *pposition) {

    librados::Rados rados;
    int ret = rados.init(NULL);
    if (ret) {
      std::cerr << "could not initialize rados client" << std::endl;
      return ret;
    }

    rados.conf_read_file(NULL);

    ret = rados.connect();
    if (ret) {
      std::cerr << "rados client could not connect" << std::endl;
      return ret;
    }

    librados::IoCtx ioctx;
    ret = rados.ioctx_create(pool.c_str(), ioctx);
    if (ret) {
      std::cerr << "failed to connect to pool " << pool
        << " ret " << ret << std::endl;
      return ret;
    }

    zlog::Log log;
    ret = zlog::Log::Open(ioctx, name, NULL, log);
    if (ret) {
      std::cerr << "failed to open log " << name << std::endl;
      return ret;
    }

    uint64_t epoch;
    ret = log.SetProjection(&epoch);
    if (ret) {
      std::cerr << "failed to set new projection " << ret << std::endl;
      return ret;
    }

    ret = log.Seal(epoch);
    if (ret) {
      std::cerr << "failed to seal the store " << ret << std::endl;
      return ret;
    }

    uint64_t position;
    ret = log.FindMaxPosition(epoch, &position);
    if (ret) {
      std::cerr << "failed to find max position " << ret << std::endl;
      return ret;
    }

    *pepoch = epoch;
    *pposition = position;

    ioctx.close();
    rados.shutdown();

    return 0;
  }

  /*
   * Queue a log to be initialized.
   */
  void QueueLogInit(const std::string& pool, const std::string& name) {
    pending_logs_.insert(std::make_pair(pool, name));
    cond_.notify_one();
  }

  /*
   * Monitors the performance of the sequencer.
   *
   * It looks at all the sequence states and then computes how many sequences
   * were handed out over a period of time.
   *
   * FIXME: Note that this currently doesn't take into account new logs that
   * are registered during the sleep period.
   */
  void BenchMonitor() {
    for (;;) {
      uint64_t start_ns;
      uint64_t start_seq;

      // starting state of all the current sequences
      {
        boost::unique_lock<boost::mutex> g(lock_);

        start_ns = get_time();
        start_seq = 0;

        std::map<std::pair<std::string, std::string>, LogManager::Log >::iterator it;
        for (it = logs_.begin(); it != logs_.end(); it++) {
          start_seq += it->second.seq.read();
        }
      }

      sleep(60);

      uint64_t end_ns;
      uint64_t end_seq;
      int num_logs;

      // ending state of all the current sequences
      {
        boost::unique_lock<boost::mutex> g(lock_);

        end_ns = get_time();
        end_seq = 0;
        num_logs = logs_.size();

        std::map<std::pair<std::string, std::string>, LogManager::Log >::iterator it;
        for (it = logs_.begin(); it != logs_.end(); it++) {
          end_seq += it->second.seq.read();
        }
      }

      uint64_t elapsed_ns = end_ns - start_ns;
      uint64_t total_seqs = end_seq - start_seq;
      uint64_t rate = (total_seqs * 1000000000ULL) / elapsed_ns;
      std::cout << "seqr rate = " << rate << " seqs/sec" << std::endl;
    }
  }

  void Run() {
    for (;;) {
      std::string pool, name;

      {
        boost::unique_lock<boost::mutex> g(lock_);
        while (pending_logs_.empty())
          cond_.wait(g);
        std::set<std::pair<std::string, std::string> >::iterator it =
          pending_logs_.begin();
        assert(it != pending_logs_.end());
        pool = it->first;
        name = it->second;
      }

      uint64_t position, epoch;
      int ret = InitLog(pool, name, &epoch, &position);
      if (ret) {
        boost::unique_lock<boost::mutex> g(lock_);
        pending_logs_.erase(std::make_pair(pool, name));
        std::cerr << "failed to init log" << std::endl;
        continue;
      }

      {
        boost::unique_lock<boost::mutex> g(lock_);
        std::pair<std::string, std::string> key = std::make_pair(pool, name);
        assert(pending_logs_.count(key) == 1);
        pending_logs_.erase(key);
        assert(logs_.count(key) == 0);
        Log log(position, epoch);
        logs_[key] = log;
      }
    }
  }

  boost::thread thread_;
  boost::thread bench_thread_;
  boost::mutex lock_;
  boost::condition_variable cond_;
  std::map<std::pair<std::string, std::string>, LogManager::Log > logs_;
  std::set<std::pair<std::string, std::string> > pending_logs_;
};

static LogManager *log_mgr;

class Session {
 public:
  Session(boost::asio::io_service& io_service)
    : socket_(io_service)
  {}

  boost::asio::ip::tcp::socket& socket() {
    return socket_;
  }

  void start() {
    boost::asio::async_read(socket_,
        boost::asio::buffer(buffer_, sizeof(uint32_t)),
        boost::bind(&Session::handle_hdr, this,
          boost::asio::placeholders::error,
          boost::asio::placeholders::bytes_transferred));
  }

 private:
  void handle_hdr(const boost::system::error_code& err, size_t size) {
    if (err) {
      delete this;
      return;
    }

    uint32_t msg_size = ntohl(*((uint32_t*)buffer_));

    if (msg_size > sizeof(buffer_)) {
      std::cerr << "message is too large" << std::endl;
      delete this;
      return;
    }

    boost::asio::async_read(socket_,
        boost::asio::buffer(buffer_, msg_size),
        boost::bind(&Session::handle_msg, this,
          boost::asio::placeholders::error,
          boost::asio::placeholders::bytes_transferred));
  }

  void handle_msg(const boost::system::error_code& err, size_t size) {
    if (err) {
      delete this;
      return;
    }

    req_.Clear();

    if (!req_.ParseFromArray(buffer_, size)) {
      std::cerr << "failed to parse message" << std::endl;
      delete this;
      return;
    }

    if (!req_.IsInitialized()) {
      std::cerr << "received incomplete message" << std::endl;
      delete this;
      return;
    }

    reply_.Clear();

    uint64_t seq;

    int ret = log_mgr->ReadSequence(req_.pool(), req_.name(),
        req_.epoch(), req_.next(), &seq);
    if (ret == -EAGAIN)
      reply_.set_status(zlog_proto::MSeqReply::INIT_LOG);
    else if (ret == -ERANGE)
      reply_.set_status(zlog_proto::MSeqReply::STALE_EPOCH);
    else
      assert(!ret);

    reply_.set_position(seq);

    assert(reply_.IsInitialized());

    uint32_t msg_size = reply_.ByteSize();
    assert(msg_size < sizeof(buffer_));
    assert(reply_.SerializeToArray(buffer_, msg_size));

    // scatter/gather buffers
    std::vector<boost::asio::const_buffer> out;
    be_msg_size_ = htonl(msg_size);
    out.push_back(boost::asio::buffer(&be_msg_size_, sizeof(be_msg_size_)));
    out.push_back(boost::asio::buffer(buffer_, msg_size));

    boost::asio::async_write(socket_, out,
        boost::bind(&Session::handle_reply, this,
          boost::asio::placeholders::error,
          boost::asio::placeholders::bytes_transferred));
  }

  void handle_reply(const boost::system::error_code& err, size_t size) {
    if (err) {
      delete this;
      return;
    }

    start();
  }

  boost::asio::ip::tcp::socket socket_;

  char buffer_[1024];
  uint32_t be_msg_size_;

  zlog_proto::MSeqRequest req_;
  zlog_proto::MSeqReply reply_;
};

class Server {
 public:
  Server(boost::asio::io_service& io_service, short port)
    : io_service_(io_service),
    acceptor_(io_service,
        boost::asio::ip::tcp::endpoint(
          boost::asio::ip::tcp::v4(), port))
  {
    start_accept();
  }

 private:
  void start_accept() {
    Session* new_session = new Session(io_service_);
    acceptor_.async_accept(new_session->socket(),
        boost::bind(&Server::handle_accept, this, new_session,
          boost::asio::placeholders::error));
  }

  void handle_accept(Session* new_session,
      const boost::system::error_code& error) {
    if (!error)
      new_session->start();
    else
      delete new_session;
    start_accept();
  }

  boost::asio::io_service& io_service_;
  boost::asio::ip::tcp::acceptor acceptor_;
};

int main(int argc, char* argv[])
{
  int port;
  std::string host;

  po::options_description desc("Allowed options");
  desc.add_options()
    ("port", po::value<int>(&port)->required(), "Server port")
    ("daemon,d", "Run in background")
  ;

  po::variables_map vm;
  po::store(po::parse_command_line(argc, argv, desc), vm);
  po::notify(vm);

  boost::asio::io_service io_service;
  Server s(io_service, port);

  if (vm.count("daemon")) {
    io_service.notify_fork(boost::asio::io_service::fork_prepare);

    pid_t pid = fork();
    if (pid < 0) {
      exit(EXIT_FAILURE);
    }

    if (pid > 0) {
      exit(EXIT_SUCCESS);
    }

    pid_t sid = setsid();
    if (sid < 0) {
      exit(EXIT_FAILURE);
    }

    umask(0);

    close(0);
    close(1);
    close(2);

    io_service.notify_fork(boost::asio::io_service::fork_child);
  }

  log_mgr = new LogManager();

  io_service.run();

  return 0;
}
<|endoftext|>