/*
* Copyright (c) [2012-2015] Novell, Inc.
- * Copyright (c) [2016,2018] SUSE LLC
+ * Copyright (c) [2016-2022] SUSE LLC
*
* All Rights Reserved.
*
*/
+#include <stdio.h>
#include <iostream>
#include "commands.h"
#include "errors.h"
#include "utils/text.h"
+#include "dbus/DBusPipe.h"
#include "snapper/AppUtil.h"
using namespace std;
DBus::Hihi hihi(reply);
hihi >> files;
- sort(files.begin(), files.end()); // snapperd can have different locale than client
- // so sorting is required here
+ sort(files.begin(), files.end());
+
+ return files;
+}
+
+
+vector<XFile>
+command_get_xfiles_by_pipe(DBus::Connection& conn, const string& config_name, unsigned int number1,
+ unsigned int number2)
+{
+ DBus::MessageMethodCall call(SERVICE, OBJECT, INTERFACE, "GetFilesByPipe");
+
+ DBus::Hoho hoho(call);
+ hoho << config_name << number1 << number2;
+
+ DBus::Message reply = conn.send_with_reply_and_block(call);
+
+ DBus::FileDescriptor fd;
+
+ DBus::Hihi hihi(reply);
+ hihi >> fd;
+
+ vector<XFile> files;
+
+ FILE* fin = fdopen(fd.get_fd(), "r");
+ if (!fin)
+ SN_THROW(IOErrorException("reading pipe failed, fdopen failed: " + stringerror(errno)));
+
+ char* buffer = nullptr;
+ size_t len = 0;
+
+ while (true)
+ {
+ ssize_t n = getline(&buffer, &len, fin);
+ if (n == -1)
+ {
+ if (feof(fin) != 0)
+ break;
+
+ SN_THROW(IOErrorException("reading pipe failed, getline failed: " + stringerror(errno)));
+ }
+
+ if (n > 0 && buffer[n - 1] == '\n')
+ --n;
+
+ const string line = string(buffer, 0, n);
+
+ string::size_type pos1 = line.find(" ");
+ if (pos1 == string::npos)
+ SN_THROW(IOErrorException("reading pipe failed, parse error"));
+
+ string::size_type pos2 = line.find(" ", pos1 + 1);
+
+ XFile file;
+ file.name = DBus::Pipe::unescape(line.substr(0, pos1));
+ file.status = stoi(line.substr(pos1 + 1, pos2 - pos1 - 1));
+ files.push_back(file);
+ }
+
+ free(buffer);
+
+ if (fclose(fin) != 0)
+ SN_THROW(IOErrorException("reading pipe failed, fclose failed: " + stringerror(errno)));
+
+ sort(files.begin(), files.end());
return files;
}
/*
* Copyright (c) [2012-2015] Novell, Inc.
- * Copyright (c) [2016,2018] SUSE LLC
+ * Copyright (c) [2016-2022] SUSE LLC
*
* All Rights Reserved.
*
command_get_xfiles(DBus::Connection& conn, const string& config_name, unsigned int number1,
unsigned int number2);
+vector<XFile>
+command_get_xfiles_by_pipe(DBus::Connection& conn, const string& config_name, unsigned int number1,
+ unsigned int number2);
+
void
command_setup_quota(DBus::Connection& conn, const string& config_name);
/*
- * Copyright (c) [2016-2020] SUSE LLC
+ * Copyright (c) [2016-2022] SUSE LLC
*
* All Rights Reserved.
*
file_paths.post_path = file_paths.system_path;
}
- vector<XFile> tmp1 = command_get_xfiles(backref->conn(), backref->config_name, lhs.getNum(),
- rhs.getNum());
+ vector<XFile> tmp1;
+
+ try
+ {
+ tmp1 = command_get_xfiles_by_pipe(backref->conn(), backref->config_name, lhs.getNum(),
+ rhs.getNum());
+ }
+ catch (const DBus::ErrorException& e)
+ {
+ SN_CAUGHT(e);
+
+ // If snapper was just updated and the old snapperd is still running it might not
+ // know the GetFilesByPipe method.
+
+ if (strcmp(e.name(), "error.unknown_method") != 0)
+ SN_RETHROW(e);
+
+ tmp1 = command_get_xfiles(backref->conn(), backref->config_name, lhs.getNum(),
+ rhs.getNum());
+ }
vector<File> tmp2;
--- /dev/null
+/*
+ * Copyright (c) 2015 Red Hat, Inc.
+ * Copyright (c) 2022 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, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+
+#include <fcntl.h>
+#include <unistd.h>
+
+#include "DBusPipe.h"
+#include "DBusMessage.h"
+
+
+namespace DBus
+{
+
+ void
+ FileDescriptor::close()
+ {
+ if (_fd != -1)
+ {
+ ::close(_fd);
+ _fd = -1;
+ }
+ }
+
+
+ Hihi&
+ operator>>(Hihi& hihi, FileDescriptor& data)
+ {
+ if (hihi.get_type() != DBUS_TYPE_UNIX_FD)
+ throw MarshallingException();
+
+ int fd;
+ dbus_message_iter_get_basic(hihi.top(), &fd);
+ dbus_message_iter_next(hihi.top());
+ data.set_fd(fd);
+
+ return hihi;
+ }
+
+
+ Hoho&
+ operator<<(Hoho& hoho, const FileDescriptor& data)
+ {
+ const int fd = data.get_fd();
+ if (!dbus_message_iter_append_basic(hoho.top(), DBUS_TYPE_UNIX_FD, &fd))
+ throw FatalException();
+
+ return hoho;
+ }
+
+
+ Pipe::Pipe()
+ {
+ int pipefd[2];
+
+ if (pipe2(pipefd, O_CLOEXEC) != 0)
+ throw PipeException();
+
+ _read_end.set_fd(pipefd[0]);
+ _write_end.set_fd(pipefd[1]);
+ }
+
+
+ string
+ Pipe::escape(const string& in)
+ {
+ string out;
+
+ for (const char c : in)
+ {
+ if (c == '\\')
+ {
+ out += "\\\\";
+ }
+ else if (c <= ' ')
+ {
+ char s[5];
+ snprintf(s, 5, "\\x%02x", c);
+ out += string(s);
+ }
+ else
+ {
+ out += c;
+ }
+ }
+
+ return out;
+ }
+
+
+ string
+ Pipe::unescape(const string& in)
+ {
+ return Hihi::unescape(in);
+ }
+
+}
--- /dev/null
+/*
+ * Copyright (c) 2015 Red Hat, Inc.
+ * Copyright (c) 2022 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, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+
+#ifndef SNAPPER_DBUSPIPE_H
+#define SNAPPER_DBUSPIPE_H
+
+
+#include <boost/noncopyable.hpp>
+
+#include "DBusMessage.h"
+
+
+namespace DBus
+{
+
+ class FileDescriptor : boost::noncopyable
+ {
+ public:
+
+ FileDescriptor() = default;
+ FileDescriptor(int fd) : _fd(fd) {}
+ ~FileDescriptor() { close(); }
+
+ void close();
+
+ int get_fd() const { return _fd; }
+ void set_fd(int fd) { _fd = fd; }
+
+ private:
+
+ int _fd = -1;
+
+ };
+
+
+ Hihi& operator>>(Hihi& hoho, FileDescriptor& data);
+ Hoho& operator<<(Hoho& hoho, const FileDescriptor& data);
+
+
+ struct PipeException : public Exception
+ {
+ explicit PipeException() : Exception("pipe exception") {}
+ };
+
+
+ class Pipe : boost::noncopyable
+ {
+ public:
+
+ Pipe();
+
+ FileDescriptor& get_read_end() { return _read_end; }
+ FileDescriptor& get_write_end() { return _write_end; }
+
+ static string escape(const string&);
+ static string unescape(const string&);
+
+ private:
+
+ FileDescriptor _read_end;
+ FileDescriptor _write_end;
+
+ };
+
+}
+
+#endif
libdbus_la_SOURCES = \
DBusMessage.cc DBusMessage.h \
+ DBusPipe.cc DBusPipe.h \
DBusConnection.cc DBusConnection.h \
DBusMainLoop.cc DBusMainLoop.h
+Methods and Signals
+-------------------
+
method ListConfigs
method GetConfig config-name
method SetConfig config-name configdata
method CreateComparison config-name number1 number2 -> num-files
method DeleteComparison config-name number1 number2
-The following command require a successful CreateComparison in advance.
+The following two commands require a successful CreateComparison in advance.
method GetFiles config-name number1 number2 -> list(filename status)
+method GetFilesByPipe config-name number1 number2 -> fd
-Sorting for files is undefined, esp. since server and client can use
-different locales.
+Filenames do not include the subvolume.
+
+GetFilesByPipe returns a file descriptor from which the client can
+read the file list. This avoids the problem of GetFiles with exceeding
+the allowed DBus message size. GetFiles is deprecated.
+
+The file descriptor returned by GetFilesByPipe has one entry per file
+with two fields (separated by spaces), first the filename and second
+the status as an integer. Additional fields must be ignored by
+clients.
Intentionally not documented are SetupQuota, PrepareQuota, QueryQuota
and QueryFreeSpace.
-Filenames do not include the subvolume.
+Encoding and Escaping
+---------------------
+
+Strings passed via DBus are UTF-8. Other characters (e.g. in
+filenames) must be encoded hexadecimal as "\x??". As a consequence,
+"\" is encoded as "\\".
+
+In strings passed via pipe the newline and space are escaped as
+"\x??". As a consequence, "\" is encoded as "\\".
+
+In general any character can be escaped as "\x??".
+
+
+Sorting
+-------
+
+Sorting for files is undefined, esp. since server and client can use
+different locales.
-Strings are UTF-8. Other characters (e.g. in filenames) must be encoded
-hexadecimal as "\x??". As a consequence "\" must be encoded as "\\".
+Misc Notes
+----------
Due to security concerns there are no methods to get, compare or revert
files. This can be done in the client.
+-------------------------------------------------------------------
+Mon Mar 21 17:29:07 CET 2022 - aschnell@suse.com
+
+- transfer filelist by pipe instead of DBus message to avoid
+ exceeding allowed DBus message size
+- version 0.10.0
+
-------------------------------------------------------------------
Wed Nov 24 14:20:38 CET 2021 - aschnell@suse.com
Client::~Client()
{
method_call_thread.interrupt();
+ file_transfer_thread.interrupt();
if (method_call_thread.joinable())
method_call_thread.join();
+ if (file_transfer_thread.joinable())
+ file_transfer_thread.join();
+
for (list<Comparison*>::iterator it = comparisons.begin(); it != comparisons.end(); ++it)
{
delete_comparison(it);
" <arg name='files' type='a(su)' direction='out'/>\n"
" </method>\n"
+ " <method name='GetFilesByPipe'>\n"
+ " <arg name='config-name' type='s' direction='in'/>\n"
+ " <arg name='number1' type='u' direction='in'/>\n"
+ " <arg name='number2' type='u' direction='in'/>\n"
+ " <arg name='fd' type='h' direction='out'/>\n"
+ " </method>\n"
+
" <method name='Sync'>\n"
" <arg name='config-name' type='s' direction='in'/>\n"
" </method>\n"
}
+void
+Client::get_files_by_pipe(DBus::Connection& conn, DBus::Message& msg)
+{
+ string config_name;
+ dbus_uint32_t num1, num2;
+
+ DBus::Hihi hihi(msg);
+ hihi >> config_name >> num1 >> num2;
+
+ y2deb("GetFilesByPipe config_name:" << config_name << " num1:" << num1 << " num2:" << num2);
+
+ boost::unique_lock<boost::shared_mutex> lock(big_mutex);
+
+ MetaSnappers::iterator it = meta_snappers.find(config_name);
+
+ check_permission(conn, msg, *it);
+
+ list<Comparison*>::iterator it2 = find_comparison(it->getSnapper(), num1, num2);
+
+ const Files& files = (*it2)->getFiles();
+
+ DBus::MessageMethodReturn reply(msg);
+
+ DBus::Hoho hoho(reply);
+
+ shared_ptr<FilesTransferTask> file_transfer_task = make_shared<FilesTransferTask>(files);
+
+ hoho << file_transfer_task->get_read_end();
+ conn.send(reply);
+
+ file_transfer_task->get_read_end().close();
+
+ add_file_transfer_task(file_transfer_task);
+}
+
+
void
Client::setup_quota(DBus::Connection& conn, DBus::Message& msg)
{
delete_comparison(conn, msg);
else if (msg.is_method_call(INTERFACE, "GetFiles"))
get_files(conn, msg);
+ else if (msg.is_method_call(INTERFACE, "GetFilesByPipe"))
+ get_files_by_pipe(conn, msg);
else if (msg.is_method_call(INTERFACE, "SetupQuota"))
setup_quota(conn, msg);
else if (msg.is_method_call(INTERFACE, "PrepareQuota"))
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);
}
+void
+Client::add_file_transfer_task(shared_ptr<FilesTransferTask> file_transfer_task)
+{
+ if (file_transfer_thread.get_id() == boost::thread::id())
+ file_transfer_thread = boost::thread(boost::bind(&Client::files_transfer_worker, this));
+
+ boost::unique_lock<boost::mutex> lock(file_transfer_mutex);
+ file_transfer_tasks.push(file_transfer_task);
+ lock.unlock();
+
+ file_transfer_condition.notify_one();
+}
+
+
void
Client::method_call_worker()
{
return false;
}
+
+
+void
+Client::files_transfer_worker()
+{
+ try
+ {
+ while (true)
+ {
+ boost::unique_lock<boost::mutex> lock(file_transfer_mutex);
+ while (file_transfer_tasks.empty())
+ file_transfer_condition.wait(lock);
+
+ shared_ptr<FilesTransferTask> ptr(file_transfer_tasks.front());
+ file_transfer_tasks.pop();
+ lock.unlock();
+
+ try
+ {
+ ptr->run();
+ }
+ catch (const StreamException& e)
+ {
+ SN_CAUGHT(e);
+ y2err("error occured during files transfer");
+ }
+ }
+ }
+ catch (const boost::thread_interrupted&)
+ {
+ y2deb("files transfer worker interrupted");
+ }
+}
#include <dbus/DBusMessage.h>
#include "MetaSnapper.h"
+#include "FilesTransferTask.h"
using namespace std;
void create_comparison(DBus::Connection& conn, DBus::Message& msg);
void delete_comparison(DBus::Connection& conn, DBus::Message& msg);
void get_files(DBus::Connection& conn, DBus::Message& msg);
+ void get_files_by_pipe(DBus::Connection& conn, DBus::Message& msg);
void setup_quota(DBus::Connection& conn, DBus::Message& msg);
void prepare_quota(DBus::Connection& conn, DBus::Message& msg);
void query_quota(DBus::Connection& conn, DBus::Message& msg);
queue<MethodCallTask> method_call_tasks;
void add_method_call_task(DBus::Connection& conn, DBus::Message& msg);
- bool zombie = false;
+ boost::condition_variable file_transfer_condition;
+ boost::mutex file_transfer_mutex;
+ boost::thread file_transfer_thread;
+ queue<shared_ptr<FilesTransferTask>> file_transfer_tasks;
+ void add_file_transfer_task(shared_ptr<FilesTransferTask> file_transfer_task);
+ bool zombie = false;
private:
void method_call_worker();
+ void files_transfer_worker();
const Clients& clients;
--- /dev/null
+/*
+ * Copyright (c) 2015 Red Hat, Inc.
+ * Copyright (c) 2022 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, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+
+#include <stdio.h>
+
+#include "FilesTransferTask.h"
+
+
+FilesTransferTask::FilesTransferTask(const Files& files)
+ : files(files)
+{
+}
+
+
+void
+FilesTransferTask::run()
+{
+ FILE* fout = fdopen(get_write_end().get_fd(), "w");
+ if (!fout)
+ SN_THROW(StreamException());
+
+ for (const File& file : files)
+ {
+ if (fprintf(fout, "%s %d\n", DBus::Pipe::escape(file.getName()).c_str(), file.getPreToPostStatus()) < 4)
+ SN_THROW(StreamException());
+ }
+
+ if (fflush(fout) != 0)
+ SN_THROW(StreamException());
+
+ if (fclose(fout) != 0)
+ SN_THROW(StreamException());
+}
--- /dev/null
+/*
+ * Copyright (c) 2015 Red Hat, Inc.
+ * Copyright (c) 2022 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, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+
+#ifndef SNAPPER_FILES_TRANSFER_TASK_H
+#define SNAPPER_FILES_TRANSFER_TASK_H
+
+
+#include <dbus/DBusPipe.h>
+
+#include <snapper/File.h>
+
+
+using namespace snapper;
+
+
+struct StreamException : Exception
+{
+ explicit StreamException() : Exception("stream exception") {}
+};
+
+
+class FilesTransferTask
+{
+public:
+
+ FilesTransferTask(const Files& files);
+
+ DBus::FileDescriptor& get_read_end() { return pipe.get_read_end(); }
+ DBus::FileDescriptor& get_write_end() { return pipe.get_write_end(); }
+
+ void run();
+
+private:
+
+ // Keep a copy of the files. This way the Comparison can be deleted before the
+ // transfer is complete. If that copy is ever a problem it could be turned into some
+ // shared object.
+ const Files files;
+
+ DBus::Pipe pipe;
+
+};
+
+
+#endif
MetaSnapper.cc MetaSnapper.h \
Background.cc Background.h \
Types.cc Types.h \
- RefCounter.cc RefCounter.h
+ RefCounter.cc RefCounter.h \
+ FilesTransferTask.cc FilesTransferTask.h
snapperd_LDADD = ../snapper/libsnapper.la ../dbus/libdbus.la -lrt
snapperd_LDFLAGS = -lboost_system -lboost_thread -lpthread
#include <stdlib.h>
#include <getopt.h>
+#include <signal.h>
#include <iostream>
#include <string>
{
client->zombie = true;
client->method_call_thread.interrupt();
+ client->file_transfer_thread.interrupt();
}
reset_idle_count();
setLogQuery(&log_query);
}
+ signal(SIGPIPE, SIG_IGN);
+
dbus_threads_init_default();
MyMainLoop mainloop(DBUS_BUS_SYSTEM);
#include <boost/test/unit_test.hpp>
#include <dbus/DBusMessage.h>
+#include <dbus/DBusPipe.h>
using namespace DBus;
-BOOST_AUTO_TEST_CASE(escape)
+BOOST_AUTO_TEST_CASE(hoho_escape)
{
BOOST_CHECK_EQUAL(Hoho::escape("\\"), "\\\\");
}
-BOOST_AUTO_TEST_CASE(unescape)
+BOOST_AUTO_TEST_CASE(hihi_unescape)
{
BOOST_CHECK_EQUAL(Hihi::unescape("\\\\"), "\\");
BOOST_CHECK_THROW(Hihi::unescape("\\x0"), MarshallingException);
BOOST_CHECK_THROW(Hihi::unescape("\\x0?"), MarshallingException);
}
+
+
+BOOST_AUTO_TEST_CASE(pipe_escape)
+{
+ BOOST_CHECK_EQUAL(Pipe::escape("hello world\n"), "hello\\x20world\\x0a");
+}
+
+
+BOOST_AUTO_TEST_CASE(pipe_unescape)
+{
+ BOOST_CHECK_EQUAL(Pipe::unescape("hello\\x20world\\x0a"), "hello world\n");
+}