/* * Copyright (c) [2012-2015] Novell, Inc. * Copyright (c) [2016-2025] SUSE LLC * * All Rights Reserved. * * This program is free software; you can redistribute it and/or modify it * under the terms of version 2 of the GNU General Public License as published * by the Free Software Foundation. * * 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, contact Novell, Inc. * * To contact Novell about this file by physical or electronic mail, you may * find current contact information at www.novell.com. */ #include "config.h" #include #include #include #include #include #include #include "Types.h" #include "Client.h" #include "MetaSnapper.h" #include "Background.h" boost::shared_mutex big_mutex; Client::Client(const string& name, uid_t uid, const Clients& clients) : name(name), uid(uid), clients(clients) { } Client::~Client() { method_call_thread.interrupt(); files_transfer_thread.interrupt(); if (method_call_thread.joinable()) method_call_thread.join(); if (files_transfer_thread.joinable()) files_transfer_thread.join(); for (Comparison& comparison : comparisons) { delete_comparison(comparison); } for (map, unsigned int>::iterator it1 = mounts.begin(); it1 != mounts.end(); ++it1) { string config_name = it1->first.first; unsigned int number = it1->first.second; unsigned int use_count = it1->second; MetaSnappers::iterator it2 = meta_snappers.find(config_name); Snapper* snapper = it2->getSnapper(); Snapshots& snapshots = snapper->getSnapshots(); Snapshots::iterator snap = snapshots.find(number); if (snap != snapshots.end()) for (unsigned int i = 0; i < use_count; ++i) snap->umountFilesystemSnapshot(false); } } list::iterator Client::find_comparison(Snapper* snapper, Snapshots::const_iterator snapshot1, Snapshots::const_iterator snapshot2) { for (list::iterator it = comparisons.begin(); it != comparisons.end(); ++it) { if (it->getSnapper() == snapper && it->getSnapshot1() == snapshot1 && it->getSnapshot2() == snapshot2) return it; } SN_THROW(NoComparison()); __builtin_unreachable(); } list::iterator Client::find_comparison(Snapper* snapper, unsigned int number1, unsigned int number2) { Snapshots& snapshots = snapper->getSnapshots(); Snapshots::const_iterator snapshot1 = snapshots.find(number1); Snapshots::const_iterator snapshot2 = snapshots.find(number2); return find_comparison(snapper, snapshot1, snapshot2); } void Client::delete_comparison(Comparison& comparison) { // TODO: function name is misleading // TODO: a class MetaComparison with integrated RefCounter would be cool to avoid this // function entirely const Snapper* snapper = comparison.getSnapper(); for (MetaSnapper& meta_snapper : meta_snappers) { if (meta_snapper.is_equal(snapper)) meta_snapper.dec_use_count(); } } void Client::add_lock(const string& config_name) { locks[config_name]++; } void Client::remove_lock(const string& config_name) { map::iterator it = locks.find(config_name); if (it != locks.end() && --it->second == 0) locks.erase(it); } bool Client::has_lock(const string& config_name) const { return locks.find(config_name) != locks.end(); } void Client::add_mount(const string& config_name, unsigned int number) { mounts[make_pair(config_name, number)]++; } void Client::remove_mount(const string& config_name, unsigned int number) { map, unsigned int>::iterator it = mounts.find(make_pair(config_name, number)); if (it != mounts.end()) { if (it->second > 1) it->second--; else mounts.erase(it); } } void Client::introspect(DBus::Connection& conn, DBus::Message& msg) { const char* introspect = DBUS_INTROSPECT_1_0_XML_DOCTYPE_DECL_NODE "\n" "\n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" "\n"; y2deb("Introspect"); DBus::MessageMethodReturn reply(msg); DBus::Marshaller marshaller(reply); marshaller << introspect; conn.send(reply); } struct Permissions : public Exception { explicit Permissions() : Exception("no permissions") {} }; void Client::check_permission(DBus::Connection& conn, DBus::Message& msg) const { // Check if the uid of the dbus-user is root. if (uid == 0) return; SN_THROW(Permissions()); } void Client::check_permission(DBus::Connection& conn, DBus::Message& msg, const MetaSnapper& meta_snapper) const { // Check if the uid of the dbus-user is root. if (uid == 0) return; // Check if the uid of the dbus-user is included in the allowed uids. if (contains(meta_snapper.get_allowed_uids(), uid)) return; string username; gid_t gid; if (get_uid_username_gid(uid, username, gid)) { // Check if the primary gid of the dbus-user is included in the allowed gids. if (contains(meta_snapper.get_allowed_gids(), gid)) return; vector gids = getgrouplist(username.c_str(), gid); // Check if any (primary or secondary) gid of the dbus-user is included in the allowed // gids. for (vector::const_iterator it = gids.begin(); it != gids.end(); ++it) if (contains(meta_snapper.get_allowed_gids(), *it)) return; } SN_THROW(Permissions()); } struct Lock : public Exception { explicit Lock() : Exception("locked") {} }; void Client::check_lock(DBus::Connection& conn, DBus::Message& msg, const string& config_name) const { for (Clients::const_iterator it = clients.begin(); it != clients.end(); ++it) { if (it->zombie || &*it == this) continue; if (it->has_lock(config_name)) SN_THROW(Lock()); } } struct ConfigInUse : public Exception { explicit ConfigInUse() : Exception("config in use") {} }; struct SnapshotInUse : public Exception { explicit SnapshotInUse() : Exception("snapshot in use") {} }; void Client::check_config_in_use(const MetaSnapper& meta_snapper) const { if (meta_snapper.use_count() != 0) SN_THROW(ConfigInUse()); } void Client::check_snapshot_in_use(const MetaSnapper& meta_snapper, unsigned int number) const { for (Clients::const_iterator it1 = clients.begin(); it1 != clients.end(); ++it1) { map, unsigned int>::const_iterator it2 = it1->mounts.find(make_pair(meta_snapper.configName(), number)); if (it2 != it1->mounts.end()) SN_THROW(SnapshotInUse()); } } void Client::signal_config_created(DBus::Connection& conn, const string& config_name) { DBus::MessageSignal msg(PATH, INTERFACE, "ConfigCreated"); DBus::Marshaller marshaller(msg); marshaller << config_name; conn.send(msg); } void Client::signal_config_modified(DBus::Connection& conn, const string& config_name) { DBus::MessageSignal msg(PATH, INTERFACE, "ConfigModified"); DBus::Marshaller marshaller(msg); marshaller << config_name; conn.send(msg); } void Client::signal_config_deleted(DBus::Connection& conn, const string& config_name) { DBus::MessageSignal msg(PATH, INTERFACE, "ConfigDeleted"); DBus::Marshaller marshaller(msg); marshaller << config_name; conn.send(msg); } void Client::signal_snapshot_created(DBus::Connection& conn, const string& config_name, unsigned int num) { DBus::MessageSignal msg(PATH, INTERFACE, "SnapshotCreated"); DBus::Marshaller marshaller(msg); marshaller << config_name << num; conn.send(msg); } void Client::signal_snapshot_modified(DBus::Connection& conn, const string& config_name, unsigned int num) { DBus::MessageSignal msg(PATH, INTERFACE, "SnapshotModified"); DBus::Marshaller marshaller(msg); marshaller << config_name << num; conn.send(msg); } void Client::signal_snapshots_deleted(DBus::Connection& conn, const string& config_name, const vector& nums) { DBus::MessageSignal msg(PATH, INTERFACE, "SnapshotsDeleted"); DBus::Marshaller marshaller(msg); marshaller << config_name << nums; conn.send(msg); } void Client::list_configs(DBus::Connection& conn, DBus::Message& msg) { y2deb("ListConfigs"); boost::shared_lock lock(big_mutex); DBus::MessageMethodReturn reply(msg); DBus::Marshaller marshaller(reply); marshaller.open_array(DBus::TypeInfo::signature); for (const MetaSnapper& meta_snapper : meta_snappers) marshaller << meta_snapper.getConfigInfo(); marshaller.close_array(); conn.send(reply); } void Client::get_config(DBus::Connection& conn, DBus::Message& msg) { string config_name; DBus::Unmarshaller unmarshaller(msg); unmarshaller >> config_name; y2deb("GetConfig config_name:" << config_name); boost::shared_lock lock(big_mutex); MetaSnappers::const_iterator it = meta_snappers.find(config_name); check_permission(conn, msg, *it); DBus::MessageMethodReturn reply(msg); DBus::Marshaller marshaller(reply); marshaller << it->getConfigInfo(); conn.send(reply); } void Client::set_config(DBus::Connection& conn, DBus::Message& msg) { string config_name; DBus::Unmarshaller unmarshaller(msg); map raw; unmarshaller >> config_name >> raw; y2deb("SetConfig config_name:" << config_name << " raw:" << raw); boost::shared_lock lock(big_mutex); MetaSnappers::iterator it = meta_snappers.find(config_name); check_permission(conn, msg); it->setConfigInfo(raw); DBus::MessageMethodReturn reply(msg); conn.send(reply); signal_config_modified(conn, config_name); } void Client::create_config(DBus::Connection& conn, DBus::Message& msg) { string config_name; string subvolume; string fstype; string template_name; DBus::Unmarshaller unmarshaller(msg); unmarshaller >> config_name >> subvolume >> fstype >> template_name; y2deb("CreateConfig config_name:" << config_name << " subvolume:" << subvolume << " fstype:" << fstype << " template_name:" << template_name); boost::unique_lock lock(big_mutex); check_permission(conn, msg); meta_snappers.createConfig(config_name, subvolume, fstype, template_name, report); DBus::MessageMethodReturn reply(msg); conn.send(reply); signal_config_created(conn, config_name); } void Client::delete_config(DBus::Connection& conn, DBus::Message& msg) { string config_name; DBus::Unmarshaller unmarshaller(msg); unmarshaller >> config_name; y2deb("DeleteConfig config_name:" << config_name); boost::unique_lock lock(big_mutex); MetaSnappers::iterator it = meta_snappers.find(config_name); check_permission(conn, msg); check_lock(conn, msg, config_name); check_config_in_use(*it); meta_snappers.deleteConfig(it, report); DBus::MessageMethodReturn reply(msg); conn.send(reply); signal_config_deleted(conn, config_name); } void Client::lock_config(DBus::Connection& conn, DBus::Message& msg) { string config_name; DBus::Unmarshaller unmarshaller(msg); unmarshaller >> config_name; y2deb("LockConfig config_name:" << config_name); boost::unique_lock lock(big_mutex); MetaSnappers::iterator it = meta_snappers.find(config_name); check_permission(conn, msg, *it); add_lock(config_name); DBus::MessageMethodReturn reply(msg); conn.send(reply); } void Client::unlock_config(DBus::Connection& conn, DBus::Message& msg) { string config_name; DBus::Unmarshaller unmarshaller(msg); unmarshaller >> config_name; y2deb("UnlockConfig config_name:" << config_name); boost::unique_lock lock(big_mutex); MetaSnappers::iterator it = meta_snappers.find(config_name); check_permission(conn, msg, *it); remove_lock(config_name); DBus::MessageMethodReturn reply(msg); conn.send(reply); } void Client::list_snapshots(DBus::Connection& conn, DBus::Message& msg) { string config_name; DBus::Unmarshaller unmarshaller(msg); unmarshaller >> config_name; y2deb("ListSnapshots config_name:" << config_name); boost::unique_lock lock(big_mutex); MetaSnappers::iterator it = meta_snappers.find(config_name); check_permission(conn, msg, *it); Snapper* snapper = it->getSnapper(); Snapshots& snapshots = snapper->getSnapshots(); DBus::MessageMethodReturn reply(msg); DBus::Marshaller marshaller(reply); marshaller << snapshots; conn.send(reply); } void Client::list_snapshots_at_time(DBus::Connection& conn, DBus::Message& msg) { string config_name; time_t begin, end; DBus::Unmarshaller unmarshaller(msg); unmarshaller >> config_name >> begin >> end; y2deb("ListSnapshotsAtTime config_name:" << config_name << " begin:" << begin << " end:" << end); boost::unique_lock lock(big_mutex); MetaSnappers::iterator it = meta_snappers.find(config_name); check_permission(conn, msg, *it); Snapper* snapper = it->getSnapper(); Snapshots& snapshots = snapper->getSnapshots(); DBus::MessageMethodReturn reply(msg); DBus::Marshaller marshaller(reply); marshaller.open_array(DBus::TypeInfo::signature); for (const Snapshot& snapshot : snapshots) { if (snapshot.getDate() >= begin && snapshot.getDate() <= end) marshaller << snapshot; } marshaller.close_array(); conn.send(reply); } void Client::get_snapshot(DBus::Connection& conn, DBus::Message& msg) { string config_name; dbus_uint32_t num; DBus::Unmarshaller unmarshaller(msg); unmarshaller >> config_name >> num; y2deb("GetSnapshot config_name:" << config_name << " num:" << num); boost::unique_lock lock(big_mutex); MetaSnappers::iterator it = meta_snappers.find(config_name); check_permission(conn, msg, *it); Snapper* snapper = it->getSnapper(); Snapshots& snapshots = snapper->getSnapshots(); Snapshots::iterator snap = snapshots.find(num); if (snap == snapshots.end()) SN_THROW(IllegalSnapshotException()); DBus::MessageMethodReturn reply(msg); DBus::Marshaller marshaller(reply); marshaller << *snap; conn.send(reply); } void Client::set_snapshot(DBus::Connection& conn, DBus::Message& msg) { string config_name; dbus_uint32_t num; SMD smd; DBus::Unmarshaller unmarshaller(msg); unmarshaller >> config_name >> num >> smd.description >> smd.cleanup >> smd.userdata; y2deb("SetSnapshot config_name:" << config_name << " num:" << num); boost::unique_lock lock(big_mutex); MetaSnappers::iterator it = meta_snappers.find(config_name); check_permission(conn, msg, *it); Snapper* snapper = it->getSnapper(); Snapshots& snapshots = snapper->getSnapshots(); Snapshots::iterator snap = snapshots.find(num); if (snap == snapshots.end()) SN_THROW(IllegalSnapshotException()); snapper->modifySnapshot(snap, smd, report); DBus::MessageMethodReturn reply(msg); conn.send(reply); signal_snapshot_modified(conn, config_name, snap->getNum()); } void Client::create_single_snapshot(DBus::Connection& conn, DBus::Message& msg) { string config_name; SCD scd; DBus::Unmarshaller unmarshaller(msg); unmarshaller >> config_name >> scd.description >> scd.cleanup >> scd.userdata; y2deb("CreateSingleSnapshot config_name:" << config_name << " description:" << scd.description << " cleanup:" << scd.cleanup); boost::unique_lock lock(big_mutex); MetaSnappers::iterator it = meta_snappers.find(config_name); check_permission(conn, msg, *it); scd.uid = uid; Snapper* snapper = it->getSnapper(); Snapshots::iterator snap1 = snapper->createSingleSnapshot(scd, report); DBus::MessageMethodReturn reply(msg); DBus::Marshaller marshaller(reply); marshaller << snap1->getNum(); conn.send(reply); signal_snapshot_created(conn, config_name, snap1->getNum()); } void Client::create_single_snapshot_v2(DBus::Connection& conn, DBus::Message& msg) { string config_name; unsigned int parent_num; SCD scd; DBus::Unmarshaller unmarshaller(msg); unmarshaller >> config_name >> parent_num >> scd.read_only >> scd.description >> scd.cleanup >> scd.userdata; y2deb("CreateSingleSnapshotV2 config_name:" << config_name << " parent_num:" << parent_num << " read_only:" << scd.read_only << " description:" << scd.description << " cleanup:" << scd.cleanup); boost::unique_lock lock(big_mutex); MetaSnappers::iterator it = meta_snappers.find(config_name); check_permission(conn, msg, *it); scd.uid = uid; Snapper* snapper = it->getSnapper(); Snapshots& snapshots = snapper->getSnapshots(); Snapshots::iterator parent = snapshots.find(parent_num); Snapshots::iterator snap2 = snapper->createSingleSnapshot(parent, scd, report); DBus::MessageMethodReturn reply(msg); DBus::Marshaller marshaller(reply); marshaller << snap2->getNum(); conn.send(reply); signal_snapshot_created(conn, config_name, snap2->getNum()); } void Client::create_single_snapshot_of_default(DBus::Connection& conn, DBus::Message& msg) { string config_name; SCD scd; DBus::Unmarshaller unmarshaller(msg); unmarshaller >> config_name >> scd.read_only >> scd.description >> scd.cleanup >> scd.userdata; y2deb("CreateSingleSnapshotOfDefault config_name:" << config_name << " read_only:" << scd.read_only << " description:" << scd.description << " cleanup:" << scd.cleanup); boost::unique_lock lock(big_mutex); MetaSnappers::iterator it = meta_snappers.find(config_name); check_permission(conn, msg, *it); scd.uid = uid; Snapper* snapper = it->getSnapper(); Snapshots::iterator snap = snapper->createSingleSnapshotOfDefault(scd, report); DBus::MessageMethodReturn reply(msg); DBus::Marshaller marshaller(reply); marshaller << snap->getNum(); conn.send(reply); signal_snapshot_created(conn, config_name, snap->getNum()); } void Client::create_pre_snapshot(DBus::Connection& conn, DBus::Message& msg) { string config_name; SCD scd; DBus::Unmarshaller unmarshaller(msg); unmarshaller >> config_name >> scd.description >> scd.cleanup >> scd.userdata; y2deb("CreatePreSnapshot config_name:" << config_name << " description:" << scd.description << " cleanup:" << scd.cleanup); boost::unique_lock lock(big_mutex); MetaSnappers::iterator it = meta_snappers.find(config_name); check_permission(conn, msg, *it); scd.uid = uid; Snapper* snapper = it->getSnapper(); Snapshots::iterator snap1 = snapper->createPreSnapshot(scd, report); DBus::MessageMethodReturn reply(msg); DBus::Marshaller marshaller(reply); marshaller << snap1->getNum(); conn.send(reply); signal_snapshot_created(conn, config_name, snap1->getNum()); } void Client::create_post_snapshot(DBus::Connection& conn, DBus::Message& msg) { string config_name; unsigned int pre_num; SCD scd; DBus::Unmarshaller unmarshaller(msg); unmarshaller >> config_name >> pre_num >> scd.description >> scd.cleanup >> scd.userdata; y2deb("CreatePostSnapshot config_name:" << config_name << " pre_num:" << pre_num << " description:" << scd.description << " cleanup:" << scd.cleanup); boost::unique_lock lock(big_mutex); MetaSnappers::iterator it = meta_snappers.find(config_name); check_permission(conn, msg, *it); scd.uid = uid; Snapper* snapper = it->getSnapper(); Snapshots& snapshots = snapper->getSnapshots(); Snapshots::iterator snap1 = snapshots.find(pre_num); Snapshots::iterator snap2 = snapper->createPostSnapshot(snap1, scd, report); bool background_comparison = true; it->getConfigInfo().get_value("BACKGROUND_COMPARISON", background_comparison); if (background_comparison) { // TODO isReadOnly is wrong if read-only is not supported by file system if (snap1->isReadOnly() && snap2->isReadOnly()) clients.backgrounds().add_task(it, snap1, snap2); } DBus::MessageMethodReturn reply(msg); DBus::Marshaller marshaller(reply); marshaller << snap2->getNum(); conn.send(reply); signal_snapshot_created(conn, config_name, snap2->getNum()); } void Client::delete_snapshots(DBus::Connection& conn, DBus::Message& msg) { string config_name; vector nums; DBus::Unmarshaller unmarshaller(msg); unmarshaller >> config_name >> nums; y2deb("DeleteSnapshots config_name:" << config_name << " nums:" << nums); boost::unique_lock lock(big_mutex); MetaSnappers::iterator it1 = meta_snappers.find(config_name); check_permission(conn, msg, *it1); check_lock(conn, msg, config_name); check_config_in_use(*it1); Snapper* snapper = it1->getSnapper(); Snapshots& snapshots = snapper->getSnapshots(); for (vector::const_iterator it2 = nums.begin(); it2 != nums.end(); ++it2) { check_snapshot_in_use(*it1, *it2); Snapshots::iterator snap = snapshots.find(*it2); snapper->deleteSnapshot(snap, report); } DBus::MessageMethodReturn reply(msg); conn.send(reply); signal_snapshots_deleted(conn, config_name, nums); } void Client::is_snapshot_read_only(DBus::Connection& conn, DBus::Message& msg) { string config_name; dbus_uint32_t num; DBus::Unmarshaller unmarshaller(msg); unmarshaller >> config_name >> num; y2deb("IsSnapshotReadOnly config_name:" << config_name << " num:" << num); boost::unique_lock lock(big_mutex); MetaSnappers::iterator it = meta_snappers.find(config_name); check_permission(conn, msg, *it); Snapper* snapper = it->getSnapper(); Snapshots& snapshots = snapper->getSnapshots(); Snapshots::iterator snap = snapshots.find(num); if (snap == snapshots.end()) SN_THROW(IllegalSnapshotException()); bool read_only = snap->isReadOnly(); DBus::MessageMethodReturn reply(msg); DBus::Marshaller marshaller(reply); marshaller << read_only; conn.send(reply); } void Client::set_snapshot_read_only(DBus::Connection& conn, DBus::Message& msg) { string config_name; dbus_uint32_t num; bool read_only; DBus::Unmarshaller unmarshaller(msg); unmarshaller >> config_name >> num >> read_only; y2deb("SetSnapshotReadOnly config_name:" << config_name << " num:" << num << " read_only:" << read_only); boost::unique_lock lock(big_mutex); MetaSnappers::iterator it = meta_snappers.find(config_name); check_permission(conn, msg, *it); Snapper* snapper = it->getSnapper(); Snapshots& snapshots = snapper->getSnapshots(); Snapshots::iterator snap = snapshots.find(num); if (snap == snapshots.end()) SN_THROW(IllegalSnapshotException()); snap->setReadOnly(read_only, report); DBus::MessageMethodReturn reply(msg); conn.send(reply); } void Client::get_default_snapshot(DBus::Connection& conn, DBus::Message& msg) { string config_name; DBus::Unmarshaller unmarshaller(msg); unmarshaller >> config_name; y2deb("GetDefaultSnapshot config_name:" << config_name); boost::unique_lock lock(big_mutex); MetaSnappers::iterator it = meta_snappers.find(config_name); check_permission(conn, msg, *it); Snapper* snapper = it->getSnapper(); Snapshots& snapshots = snapper->getSnapshots(); Snapshots::const_iterator tmp = snapshots.getDefault(); DBus::MessageMethodReturn reply(msg); DBus::Marshaller marshaller(reply); if (tmp != snapshots.end()) marshaller << true << tmp->getNum(); else marshaller << false << (unsigned int)(0); conn.send(reply); } void Client::get_active_snapshot(DBus::Connection& conn, DBus::Message& msg) { string config_name; DBus::Unmarshaller unmarshaller(msg); unmarshaller >> config_name; y2deb("GetActiveSnapshot config_name:" << config_name); boost::unique_lock lock(big_mutex); MetaSnappers::iterator it = meta_snappers.find(config_name); check_permission(conn, msg, *it); Snapper* snapper = it->getSnapper(); Snapshots& snapshots = snapper->getSnapshots(); Snapshots::const_iterator tmp = snapshots.getActive(); DBus::MessageMethodReturn reply(msg); DBus::Marshaller marshaller(reply); if (tmp != snapshots.end()) marshaller << true << tmp->getNum(); else marshaller << false << (unsigned int)(0); conn.send(reply); } void Client::calculate_used_space(DBus::Connection& conn, DBus::Message& msg) { string config_name; DBus::Unmarshaller unmarshaller(msg); unmarshaller >> config_name; y2deb("CalculateUsedSpace config_name:" << config_name); boost::unique_lock lock(big_mutex); MetaSnappers::iterator it = meta_snappers.find(config_name); check_permission(conn, msg, *it); Snapper* snapper = it->getSnapper(); RefHolder ref_holder(*it); lock.unlock(); snapper->calculateUsedSpace(); lock.lock(); DBus::MessageMethodReturn reply(msg); conn.send(reply); } void Client::get_used_space(DBus::Connection& conn, DBus::Message& msg) { string config_name; dbus_uint32_t num; DBus::Unmarshaller unmarshaller(msg); unmarshaller >> config_name >> num; y2deb("GetUsedSpace config_name:" << config_name << " num:" << num); boost::unique_lock lock(big_mutex); MetaSnappers::iterator it = meta_snappers.find(config_name); check_permission(conn, msg, *it); Snapper* snapper = it->getSnapper(); Snapshots& snapshots = snapper->getSnapshots(); Snapshots::iterator snap = snapshots.find(num); if (snap == snapshots.end()) SN_THROW(IllegalSnapshotException()); uint64_t used_space = snap->getUsedSpace(); DBus::MessageMethodReturn reply(msg); DBus::Marshaller marshaller(reply); marshaller << used_space; conn.send(reply); } void Client::mount_snapshot(DBus::Connection& conn, DBus::Message& msg) { string config_name; dbus_uint32_t num; bool user_request; DBus::Unmarshaller unmarshaller(msg); unmarshaller >> config_name >> num >> user_request; y2deb("MountSnapshot config_name:" << config_name << " num:" << num << " user_request:" << user_request); boost::unique_lock lock(big_mutex); MetaSnappers::iterator it = meta_snappers.find(config_name); check_permission(conn, msg, *it); Snapper* snapper = it->getSnapper(); Snapshots& snapshots = snapper->getSnapshots(); Snapshots::iterator snap = snapshots.find(num); if (snap == snapshots.end()) SN_THROW(IllegalSnapshotException()); snap->mountFilesystemSnapshot(user_request); if (!user_request) add_mount(config_name, num); string mount_point = snap->snapshotDir(); DBus::MessageMethodReturn reply(msg); DBus::Marshaller marshaller(reply); marshaller << mount_point; conn.send(reply); } void Client::umount_snapshot(DBus::Connection& conn, DBus::Message& msg) { string config_name; dbus_uint32_t num; bool user_request; DBus::Unmarshaller unmarshaller(msg); unmarshaller >> config_name >> num >> user_request; y2deb("UmountSnapshot config_name:" << config_name << " num:" << num << " user_request:" << user_request); boost::unique_lock lock(big_mutex); MetaSnappers::iterator it = meta_snappers.find(config_name); check_permission(conn, msg, *it); Snapper* snapper = it->getSnapper(); Snapshots& snapshots = snapper->getSnapshots(); Snapshots::iterator snap = snapshots.find(num); if (snap == snapshots.end()) SN_THROW(IllegalSnapshotException()); snap->umountFilesystemSnapshot(user_request); if (!user_request) remove_mount(config_name, num); DBus::MessageMethodReturn reply(msg); conn.send(reply); } void Client::get_mount_point(DBus::Connection& conn, DBus::Message& msg) { string config_name; dbus_uint32_t num; DBus::Unmarshaller unmarshaller(msg); unmarshaller >> config_name >> num; y2deb("GetMountPoint config_name:" << config_name << " num:" << num); boost::unique_lock lock(big_mutex); MetaSnappers::iterator it = meta_snappers.find(config_name); check_permission(conn, msg, *it); Snapper* snapper = it->getSnapper(); Snapshots& snapshots = snapper->getSnapshots(); Snapshots::iterator snap = snapshots.find(num); if (snap == snapshots.end()) SN_THROW(IllegalSnapshotException()); string mount_point = snap->snapshotDir(); DBus::MessageMethodReturn reply(msg); DBus::Marshaller marshaller(reply); marshaller << mount_point; conn.send(reply); } void Client::create_comparison(DBus::Connection& conn, DBus::Message& msg) { string config_name; dbus_uint32_t num1, num2; DBus::Unmarshaller unmarshaller(msg); unmarshaller >> config_name >> num1 >> num2; y2deb("CreateComparison config_name:" << config_name << " num1:" << num1 << " num2:" << num2); boost::unique_lock lock(big_mutex); MetaSnappers::iterator it = meta_snappers.find(config_name); check_permission(conn, msg, *it); Snapper* snapper = it->getSnapper(); Snapshots& snapshots = snapper->getSnapshots(); Snapshots::const_iterator snapshot1 = snapshots.find(num1); Snapshots::const_iterator snapshot2 = snapshots.find(num2); RefHolder ref_holder(*it); lock.unlock(); Comparison comparison(snapper, snapshot1, snapshot2, false); dbus_uint32_t num_files = comparison.getFiles().size(); lock.lock(); comparisons.push_back(std::move(comparison)); it->inc_use_count(); DBus::MessageMethodReturn reply(msg); DBus::Marshaller marshaller(reply); marshaller << num_files; conn.send(reply); } void Client::delete_comparison(DBus::Connection& conn, DBus::Message& msg) { string config_name; dbus_uint32_t num1, num2; DBus::Unmarshaller unmarshaller(msg); unmarshaller >> config_name >> num1 >> num2; y2deb("DeleteComparison config_name:" << config_name << " num1:" << num1 << " num2:" << num2); boost::unique_lock lock(big_mutex); MetaSnappers::iterator it = meta_snappers.find(config_name); check_permission(conn, msg, *it); list::iterator it2 = find_comparison(it->getSnapper(), num1, num2); delete_comparison(*it2); comparisons.erase(it2); DBus::MessageMethodReturn reply(msg); conn.send(reply); } void Client::get_files(DBus::Connection& conn, DBus::Message& msg) { string config_name; dbus_uint32_t num1, num2; DBus::Unmarshaller unmarshaller(msg); unmarshaller >> config_name >> num1 >> num2; y2deb("GetFiles config_name:" << config_name << " num1:" << num1 << " num2:" << num2); boost::unique_lock lock(big_mutex); MetaSnappers::iterator it = meta_snappers.find(config_name); check_permission(conn, msg, *it); list::iterator it2 = find_comparison(it->getSnapper(), num1, num2); const Files& files = it2->getFiles(); DBus::MessageMethodReturn reply(msg); DBus::Marshaller marshaller(reply); marshaller << files; conn.send(reply); } void Client::get_files_by_pipe(DBus::Connection& conn, DBus::Message& msg) { string config_name; dbus_uint32_t num1, num2; DBus::Unmarshaller unmarshaller(msg); unmarshaller >> config_name >> num1 >> num2; y2deb("GetFilesByPipe config_name:" << config_name << " num1:" << num1 << " num2:" << num2); boost::unique_lock lock(big_mutex); MetaSnappers::iterator it = meta_snappers.find(config_name); check_permission(conn, msg, *it); list::iterator it2 = find_comparison(it->getSnapper(), num1, num2); const Files& files = it2->getFiles(); DBus::MessageMethodReturn reply(msg); DBus::Marshaller marshaller(reply); shared_ptr files_transfer_task = make_shared(files); marshaller << files_transfer_task->get_read_end(); conn.send(reply); files_transfer_task->get_read_end().close(); add_files_transfer_task(files_transfer_task); } void Client::setup_quota(DBus::Connection& conn, DBus::Message& msg) { string config_name; DBus::Unmarshaller unmarshaller(msg); unmarshaller >> config_name; y2deb("SetupQuota config_name:" << config_name); boost::unique_lock lock(big_mutex); MetaSnappers::iterator it = meta_snappers.find(config_name); check_permission(conn, msg, *it); Snapper* snapper = it->getSnapper(); snapper->setupQuota(); it->updateConfigInfo("QGROUP"); DBus::MessageMethodReturn reply(msg); conn.send(reply); } void Client::prepare_quota(DBus::Connection& conn, DBus::Message& msg) { string config_name; DBus::Unmarshaller unmarshaller(msg); unmarshaller >> config_name; y2deb("PrepareQuota config_name:" << config_name); boost::unique_lock lock(big_mutex); MetaSnappers::iterator it = meta_snappers.find(config_name); check_permission(conn, msg, *it); Snapper* snapper = it->getSnapper(); snapper->prepareQuota(); DBus::MessageMethodReturn reply(msg); conn.send(reply); } void Client::query_quota(DBus::Connection& conn, DBus::Message& msg) { string config_name; DBus::Unmarshaller unmarshaller(msg); unmarshaller >> config_name; y2deb("QueryQuota config_name:" << config_name); boost::unique_lock lock(big_mutex); MetaSnappers::iterator it = meta_snappers.find(config_name); check_permission(conn, msg, *it); Snapper* snapper = it->getSnapper(); RefHolder ref_holder(*it); lock.unlock(); QuotaData quota_data = snapper->queryQuotaData(); lock.lock(); DBus::MessageMethodReturn reply(msg); DBus::Marshaller marshaller(reply); marshaller << quota_data; conn.send(reply); } void Client::query_free_space(DBus::Connection& conn, DBus::Message& msg) { string config_name; DBus::Unmarshaller unmarshaller(msg); unmarshaller >> config_name; y2deb("QueryFreeSpace config_name:" << config_name); boost::unique_lock lock(big_mutex); MetaSnappers::iterator it = meta_snappers.find(config_name); check_permission(conn, msg, *it); Snapper* snapper = it->getSnapper(); FreeSpaceData free_space_data = snapper->queryFreeSpaceData(); DBus::MessageMethodReturn reply(msg); DBus::Marshaller marshaller(reply); marshaller << free_space_data; conn.send(reply); } void Client::sync(DBus::Connection& conn, DBus::Message& msg) { string config_name; DBus::Unmarshaller unmarshaller(msg); unmarshaller >> config_name; y2deb("Sync config_name:" << config_name); boost::unique_lock lock(big_mutex); MetaSnappers::iterator it = meta_snappers.find(config_name); check_permission(conn, msg, *it); Snapper* snapper = it->getSnapper(); snapper->syncFilesystem(); DBus::MessageMethodReturn reply(msg); conn.send(reply); } void Client::get_plugins_report(DBus::Connection& conn, DBus::Message& msg) { y2deb("GetPluginsReport"); boost::unique_lock lock(big_mutex); // No permission check here: The report belongs to the client. DBus::MessageMethodReturn reply(msg); DBus::Marshaller marshaller(reply); marshaller << report.entries; conn.send(reply); } void Client::clear_plugins_report(DBus::Connection& conn, DBus::Message& msg) { y2deb("ClearPluginsReport"); boost::unique_lock lock(big_mutex); // No permission check here: The report belongs to the client. DBus::MessageMethodReturn reply(msg); report.clear(); DBus::Marshaller marshaller(reply); conn.send(reply); } void Client::debug(DBus::Connection& conn, DBus::Message& msg) { y2deb("Debug"); boost::shared_lock lock(big_mutex); check_permission(conn, msg); DBus::MessageMethodReturn reply(msg); DBus::Marshaller marshaller(reply); marshaller.open_array("s"); marshaller << "server:"; { std::ostringstream s; s << " pid:" << getpid(); marshaller << s.str(); } marshaller << "clients:"; for (const Client& client : clients) { std::ostringstream s; s << " name:'" << client.name << "', uid:" << client.uid; if (&client == this) s << ", myself"; if (client.zombie) s << ", zombie"; if (!client.locks.empty()) s << ", locks " << client.locks.size(); if (!client.comparisons.empty()) s << ", comparisons " << client.comparisons.size(); marshaller << s.str(); } marshaller << "backgrounds:"; for (const Backgrounds::Task& task : clients.backgrounds()) { std::ostringstream s; s << " name:'" << task.meta_snapper->configName() << "'"; marshaller << s.str(); } marshaller << "meta-snappers:"; for (const MetaSnapper& meta_snapper : meta_snappers) { std::ostringstream s; s << " name:'" << meta_snapper.configName() << "'"; if (meta_snapper.is_loaded()) { s << ", loaded"; if (meta_snapper.is_locked(clients)) s << ", locked"; if (meta_snapper.use_count() == 0) s << ", unused for " << duration_cast(meta_snapper.unused_for()).count() << "ms"; else s << ", use count " << meta_snapper.use_count(); } marshaller << s.str(); } marshaller << "compile options:"; marshaller << " version " + string(Snapper::compileVersion()); marshaller << " libversion " LIBSNAPPER_MAJOR "." LIBSNAPPER_MINOR "." LIBSNAPPER_PATCHLEVEL; marshaller << " flags " + string(Snapper::compileFlags()); marshaller << " CONF_DIR " CONF_DIR; marshaller.close_array(); conn.send(reply); } void Client::dispatch(DBus::Connection& conn, DBus::Message& msg) { using method_fnc = void (Client ::*)(DBus::Connection& conn, DBus::Message& msg); static const vector> method_registry = { { "ListConfigs", &Client::list_configs }, { "CreateConfig", &Client::create_config }, { "GetConfig", &Client::get_config }, { "SetConfig", &Client::set_config }, { "DeleteConfig", &Client::delete_config }, { "LockConfig", &Client::lock_config }, { "UnlockConfig", &Client::unlock_config }, { "ListSnapshots", &Client::list_snapshots }, { "ListSnapshotsAtTime", &Client::list_snapshots_at_time }, { "GetSnapshot", &Client::get_snapshot }, { "SetSnapshot", &Client::set_snapshot }, { "CreateSingleSnapshot", &Client::create_single_snapshot }, { "CreateSingleSnapshotV2", &Client::create_single_snapshot_v2 }, { "CreateSingleSnapshotOfDefault", &Client::create_single_snapshot_of_default }, { "CreatePreSnapshot", &Client::create_pre_snapshot }, { "CreatePostSnapshot", &Client::create_post_snapshot }, { "DeleteSnapshots", &Client::delete_snapshots }, { "IsSnapshotReadOnly", &Client::is_snapshot_read_only }, { "SetSnapshotReadOnly", &Client::set_snapshot_read_only }, { "GetDefaultSnapshot", &Client::get_default_snapshot }, { "GetActiveSnapshot", &Client::get_active_snapshot }, { "CalculateUsedSpace", &Client::calculate_used_space }, { "GetUsedSpace", &Client::get_used_space }, { "MountSnapshot", &Client::mount_snapshot }, { "UmountSnapshot", &Client::umount_snapshot }, { "GetMountPoint", &Client::get_mount_point }, { "CreateComparison", &Client::create_comparison }, { "DeleteComparison", &Client::delete_comparison }, { "GetFiles", &Client::get_files }, { "GetFilesByPipe", &Client::get_files_by_pipe }, { "SetupQuota", &Client::setup_quota }, { "PrepareQuota", &Client::prepare_quota }, { "QueryQuota", &Client::query_quota }, { "QueryFreeSpace", &Client::query_free_space }, { "Sync", &Client::sync }, { "GetPluginsReport", &Client::get_plugins_report }, { "ClearPluginsReport", &Client::clear_plugins_report }, { "Debug", &Client::debug } }; try { for (const vector>::value_type& tmp : method_registry) { if (msg.is_method_call(INTERFACE, tmp.first)) { (*this.*tmp.second)(conn, msg); return; } } DBus::MessageError reply(msg, "error.unknown_method", DBUS_ERROR_FAILED); conn.send(reply); return; } catch (const boost::thread_interrupted&) { throw; } catch (const DBus::MarshallingException& e) { SN_CAUGHT(e); DBus::MessageError reply(msg, "error.dbus.marshalling", DBUS_ERROR_FAILED); conn.send(reply); } catch (const DBus::FatalException& e) { SN_CAUGHT(e); DBus::MessageError reply(msg, "error.dbus.fatal", DBUS_ERROR_FAILED); conn.send(reply); } catch (const UnknownConfig& e) { SN_CAUGHT(e); DBus::MessageError reply(msg, "error.unknown_config", DBUS_ERROR_FAILED); conn.send(reply); } catch (const ListConfigsFailedException& e) { SN_CAUGHT(e); DBus::MessageError reply(msg, "error.list_configs_failed", e.what()); conn.send(reply); } catch (const CreateConfigFailedException& e) { SN_CAUGHT(e); DBus::MessageError reply(msg, "error.create_config_failed", e.what()); conn.send(reply); } catch (const DeleteConfigFailedException& e) { SN_CAUGHT(e); DBus::MessageError reply(msg, "error.delete_config_failed", e.what()); conn.send(reply); } catch (const Permissions& e) { SN_CAUGHT(e); DBus::MessageError reply(msg, "error.no_permissions", DBUS_ERROR_FAILED); conn.send(reply); } catch (const Lock& e) { SN_CAUGHT(e); DBus::MessageError reply(msg, "error.config_locked", DBUS_ERROR_FAILED); conn.send(reply); } catch (const ConfigInUse& e) { SN_CAUGHT(e); DBus::MessageError reply(msg, "error.config_in_use", DBUS_ERROR_FAILED); conn.send(reply); } catch (const SnapshotInUse& e) { SN_CAUGHT(e); DBus::MessageError reply(msg, "error.snapshot_in_use", DBUS_ERROR_FAILED); conn.send(reply); } catch (const NoComparison& e) { SN_CAUGHT(e); DBus::MessageError reply(msg, "error.no_comparisons", DBUS_ERROR_FAILED); conn.send(reply); } catch (const IllegalSnapshotException& e) { SN_CAUGHT(e); DBus::MessageError reply(msg, "error.illegal_snapshot", DBUS_ERROR_FAILED); conn.send(reply); } catch (const CreateSnapshotFailedException& e) { SN_CAUGHT(e); DBus::MessageError reply(msg, "error.create_snapshot_failed", DBUS_ERROR_FAILED); conn.send(reply); } catch (const DeleteSnapshotFailedException& e) { SN_CAUGHT(e); DBus::MessageError reply(msg, "error.delete_snapshot_failed", DBUS_ERROR_FAILED); conn.send(reply); } catch (const InvalidConfigException& e) { SN_CAUGHT(e); DBus::MessageError reply(msg, "error.invalid_config", DBUS_ERROR_FAILED); conn.send(reply); } catch (const InvalidConfigdataException& e) { SN_CAUGHT(e); DBus::MessageError reply(msg, "error.invalid_configdata", DBUS_ERROR_FAILED); conn.send(reply); } catch (const InvalidUserdataException& e) { SN_CAUGHT(e); DBus::MessageError reply(msg, "error.invalid_userdata", DBUS_ERROR_FAILED); conn.send(reply); } catch (const AclException& e) { SN_CAUGHT(e); DBus::MessageError reply(msg, "error.acl_error", DBUS_ERROR_FAILED); conn.send(reply); } catch (const IOErrorException& e) { SN_CAUGHT(e); DBus::MessageError reply(msg, "error.io_error", e.what()); conn.send(reply); } catch (const IsSnapshotMountedFailedException& e) { SN_CAUGHT(e); DBus::MessageError reply(msg, "error.is_snapshot_mounted", DBUS_ERROR_FAILED); conn.send(reply); } catch (const MountSnapshotFailedException& e) { SN_CAUGHT(e); DBus::MessageError reply(msg, "error.mount_snapshot", DBUS_ERROR_FAILED); conn.send(reply); } catch (const UmountSnapshotFailedException& e) { SN_CAUGHT(e); DBus::MessageError reply(msg, "error.umount_snapshot", DBUS_ERROR_FAILED); conn.send(reply); } catch (const InvalidUserException& e) { SN_CAUGHT(e); DBus::MessageError reply(msg, "error.invalid_user", e.what()); conn.send(reply); } catch (const InvalidGroupException& e) { SN_CAUGHT(e); DBus::MessageError reply(msg, "error.invalid_group", e.what()); conn.send(reply); } catch (const QuotaException& e) { SN_CAUGHT(e); DBus::MessageError reply(msg, "error.quota", e.what()); conn.send(reply); } catch (const FreeSpaceException& e) { SN_CAUGHT(e); DBus::MessageError reply(msg, "error.free_space", e.what()); conn.send(reply); } catch (const UnsupportedException& e) { SN_CAUGHT(e); DBus::MessageError reply(msg, "error.unsupported", e.what()); conn.send(reply); } catch (const StreamException& e) { SN_CAUGHT(e); DBus::MessageError reply(msg, "error.stream", DBUS_ERROR_FAILED); conn.send(reply); } catch (const Exception& e) { SN_CAUGHT(e); DBus::MessageError reply(msg, "error.something", DBUS_ERROR_FAILED); conn.send(reply); } catch (const exception& e) { y2err("caught unknown exception (" << e.what() << ")"); DBus::MessageError reply(msg, "error.something", DBUS_ERROR_FAILED); conn.send(reply); } catch (...) { y2err("caught unknown exception"); DBus::MessageError reply(msg, "error.something", DBUS_ERROR_FAILED); conn.send(reply); } } void Client::add_method_call_task(DBus::Connection& conn, DBus::Message& msg) { if (method_call_thread.get_id() == boost::thread::id()) method_call_thread = boost::thread(boost::bind(&Client::method_call_worker, this)); boost::unique_lock lock(method_call_mutex); method_call_tasks.push(MethodCallTask(conn, msg)); lock.unlock(); method_call_condition.notify_one(); } void Client::add_files_transfer_task(shared_ptr files_transfer_task) { if (files_transfer_thread.get_id() == boost::thread::id()) files_transfer_thread = boost::thread(boost::bind(&Client::files_transfer_worker, this)); boost::unique_lock lock(files_transfer_mutex); files_transfer_tasks.push(files_transfer_task); lock.unlock(); files_transfer_condition.notify_one(); } void Client::method_call_worker() { try { while (true) { boost::unique_lock lock(method_call_mutex); while (method_call_tasks.empty()) method_call_condition.wait(lock); MethodCallTask method_call_task = method_call_tasks.front(); method_call_tasks.pop(); lock.unlock(); dispatch(method_call_task.conn, method_call_task.msg); } } catch (const boost::thread_interrupted&) { y2deb("worker interrupted"); } } Clients::Clients(Backgrounds& backgrounds) : bgs(backgrounds) { } Backgrounds& Clients::backgrounds() const { return bgs; } Clients::iterator Clients::find(const string& name) { for (iterator it = entries.begin(); it != entries.end(); ++it) if (it->name == name) return it; return entries.end(); } Clients::iterator Clients::add(const string& name, uid_t uid) { assert(find(name) == entries.end()); entries.emplace_back(name, uid, *this); return --entries.end(); } void Clients::remove_zombies() { for (iterator it = begin(); it != end();) { if (it->zombie && it->method_call_thread.timed_join(boost::posix_time::seconds(0))) it = entries.erase(it); else ++it; } } bool Clients::has_zombies() const { for (const_iterator it = begin(); it != end(); ++it) if (it->zombie) return true; return false; } void Client::files_transfer_worker() { try { while (true) { boost::unique_lock lock(files_transfer_mutex); while (files_transfer_tasks.empty()) files_transfer_condition.wait(lock); shared_ptr ptr(files_transfer_tasks.front()); files_transfer_tasks.pop(); lock.unlock(); try { ptr->run(); } catch (const StreamException& e) { SN_CAUGHT(e); y2err("error occurred during files transfer"); } } } catch (const boost::thread_interrupted&) { y2deb("files transfer worker interrupted"); } }