From d027072dffbd3723a6f17485ad4683561f96a02d Mon Sep 17 00:00:00 2001 From: Arvin Schnell Date: Tue, 22 Mar 2022 09:02:54 +0100 Subject: [PATCH] - transfer filelist by pipe instead of DBus message --- VERSION | 2 +- client/commands.cc | 71 +++++++++++++++++++++- client/commands.h | 6 +- client/proxy-dbus.cc | 24 +++++++- dbus/DBusPipe.cc | 114 ++++++++++++++++++++++++++++++++++++ dbus/DBusPipe.h | 85 +++++++++++++++++++++++++++ dbus/Makefile.am | 1 + doc/dbus-protocol.txt | 41 +++++++++++-- package/snapper.changes | 7 +++ server/Client.cc | 102 ++++++++++++++++++++++++++++++++ server/Client.h | 10 +++- server/FilesTransferTask.cc | 51 ++++++++++++++++ server/FilesTransferTask.h | 63 ++++++++++++++++++++ server/Makefile.am | 3 +- server/snapperd.cc | 4 ++ testsuite/dbus-escape.cc | 17 +++++- 16 files changed, 583 insertions(+), 18 deletions(-) create mode 100644 dbus/DBusPipe.cc create mode 100644 dbus/DBusPipe.h create mode 100644 server/FilesTransferTask.cc create mode 100644 server/FilesTransferTask.h diff --git a/VERSION b/VERSION index f374f666..78bc1abd 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -0.9.1 +0.10.0 diff --git a/client/commands.cc b/client/commands.cc index ecb7ef0a..dda9d3c6 100644 --- a/client/commands.cc +++ b/client/commands.cc @@ -1,6 +1,6 @@ /* * Copyright (c) [2012-2015] Novell, Inc. - * Copyright (c) [2016,2018] SUSE LLC + * Copyright (c) [2016-2022] SUSE LLC * * All Rights Reserved. * @@ -21,11 +21,13 @@ */ +#include #include #include "commands.h" #include "errors.h" #include "utils/text.h" +#include "dbus/DBusPipe.h" #include "snapper/AppUtil.h" using namespace std; @@ -487,8 +489,71 @@ command_get_xfiles(DBus::Connection& conn, const string& config_name, unsigned i 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 +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 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; } diff --git a/client/commands.h b/client/commands.h index 11cbc0e6..c7e00bdc 100644 --- a/client/commands.h +++ b/client/commands.h @@ -1,6 +1,6 @@ /* * Copyright (c) [2012-2015] Novell, Inc. - * Copyright (c) [2016,2018] SUSE LLC + * Copyright (c) [2016-2022] SUSE LLC * * All Rights Reserved. * @@ -126,6 +126,10 @@ vector command_get_xfiles(DBus::Connection& conn, const string& config_name, unsigned int number1, unsigned int number2); +vector +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); diff --git a/client/proxy-dbus.cc b/client/proxy-dbus.cc index 9feab6fb..3e4cfef1 100644 --- a/client/proxy-dbus.cc +++ b/client/proxy-dbus.cc @@ -1,5 +1,5 @@ /* - * Copyright (c) [2016-2020] SUSE LLC + * Copyright (c) [2016-2022] SUSE LLC * * All Rights Reserved. * @@ -378,8 +378,26 @@ ProxyComparisonDbus::ProxyComparisonDbus(ProxySnapperDbus* backref, const ProxyS file_paths.post_path = file_paths.system_path; } - vector tmp1 = command_get_xfiles(backref->conn(), backref->config_name, lhs.getNum(), - rhs.getNum()); + vector 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 tmp2; diff --git a/dbus/DBusPipe.cc b/dbus/DBusPipe.cc new file mode 100644 index 00000000..df26324e --- /dev/null +++ b/dbus/DBusPipe.cc @@ -0,0 +1,114 @@ +/* + * 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 +#include + +#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); + } + +} diff --git a/dbus/DBusPipe.h b/dbus/DBusPipe.h new file mode 100644 index 00000000..8d91fac0 --- /dev/null +++ b/dbus/DBusPipe.h @@ -0,0 +1,85 @@ +/* + * 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 + +#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 diff --git a/dbus/Makefile.am b/dbus/Makefile.am index f9521a8b..560b02f3 100644 --- a/dbus/Makefile.am +++ b/dbus/Makefile.am @@ -8,6 +8,7 @@ noinst_LTLIBRARIES = libdbus.la libdbus_la_SOURCES = \ DBusMessage.cc DBusMessage.h \ + DBusPipe.cc DBusPipe.h \ DBusConnection.cc DBusConnection.h \ DBusMainLoop.cc DBusMainLoop.h diff --git a/doc/dbus-protocol.txt b/doc/dbus-protocol.txt index 2bb3d7a9..dbd925d1 100644 --- a/doc/dbus-protocol.txt +++ b/doc/dbus-protocol.txt @@ -1,4 +1,7 @@ +Methods and Signals +------------------- + method ListConfigs method GetConfig config-name method SetConfig config-name configdata @@ -54,23 +57,49 @@ method Sync config-name 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. diff --git a/package/snapper.changes b/package/snapper.changes index 54ce64a3..67a0e7cf 100644 --- a/package/snapper.changes +++ b/package/snapper.changes @@ -1,3 +1,10 @@ +------------------------------------------------------------------- +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 diff --git a/server/Client.cc b/server/Client.cc index cd2f4e96..1e2ff183 100644 --- a/server/Client.cc +++ b/server/Client.cc @@ -47,10 +47,14 @@ Client::Client(const string& name, uid_t uid, const Clients& clients) 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::iterator it = comparisons.begin(); it != comparisons.end(); ++it) { delete_comparison(it); @@ -370,6 +374,13 @@ Client::introspect(DBus::Connection& conn, DBus::Message& msg) " \n" " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" " \n" " \n" @@ -1442,6 +1453,42 @@ Client::get_files(DBus::Connection& conn, DBus::Message& msg) } +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 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::Hoho hoho(reply); + + shared_ptr file_transfer_task = make_shared(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) { @@ -1700,6 +1747,8 @@ Client::dispatch(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")) @@ -1878,6 +1927,12 @@ Client::dispatch(DBus::Connection& conn, DBus::Message& msg) 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); @@ -1907,6 +1962,20 @@ Client::add_method_call_task(DBus::Connection& conn, DBus::Message& msg) } +void +Client::add_file_transfer_task(shared_ptr 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 lock(file_transfer_mutex); + file_transfer_tasks.push(file_transfer_task); + lock.unlock(); + + file_transfer_condition.notify_one(); +} + + void Client::method_call_worker() { @@ -1988,3 +2057,36 @@ Clients::has_zombies() const return false; } + + +void +Client::files_transfer_worker() +{ + try + { + while (true) + { + boost::unique_lock lock(file_transfer_mutex); + while (file_transfer_tasks.empty()) + file_transfer_condition.wait(lock); + + shared_ptr 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"); + } +} diff --git a/server/Client.h b/server/Client.h index 9f48c048..996360aa 100644 --- a/server/Client.h +++ b/server/Client.h @@ -38,6 +38,7 @@ #include #include "MetaSnapper.h" +#include "FilesTransferTask.h" using namespace std; @@ -111,6 +112,7 @@ public: 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); @@ -162,12 +164,18 @@ public: queue 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> file_transfer_tasks; + void add_file_transfer_task(shared_ptr file_transfer_task); + bool zombie = false; private: void method_call_worker(); + void files_transfer_worker(); const Clients& clients; diff --git a/server/FilesTransferTask.cc b/server/FilesTransferTask.cc new file mode 100644 index 00000000..27d55d9f --- /dev/null +++ b/server/FilesTransferTask.cc @@ -0,0 +1,51 @@ +/* + * 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 + +#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()); +} diff --git a/server/FilesTransferTask.h b/server/FilesTransferTask.h new file mode 100644 index 00000000..a6c60341 --- /dev/null +++ b/server/FilesTransferTask.h @@ -0,0 +1,63 @@ +/* + * 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 + +#include + + +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 diff --git a/server/Makefile.am b/server/Makefile.am index 3b07eaa6..c03f3731 100644 --- a/server/Makefile.am +++ b/server/Makefile.am @@ -12,7 +12,8 @@ snapperd_SOURCES = \ 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 diff --git a/server/snapperd.cc b/server/snapperd.cc index 879c9af2..daa99e02 100644 --- a/server/snapperd.cc +++ b/server/snapperd.cc @@ -23,6 +23,7 @@ #include #include +#include #include #include @@ -131,6 +132,7 @@ MyMainLoop::client_disconnected(const string& name) { client->zombie = true; client->method_call_thread.interrupt(); + client->file_transfer_thread.interrupt(); } reset_idle_count(); @@ -270,6 +272,8 @@ main(int argc, char** argv) setLogQuery(&log_query); } + signal(SIGPIPE, SIG_IGN); + dbus_threads_init_default(); MyMainLoop mainloop(DBUS_BUS_SYSTEM); diff --git a/testsuite/dbus-escape.cc b/testsuite/dbus-escape.cc index c79dca02..038a7b2c 100644 --- a/testsuite/dbus-escape.cc +++ b/testsuite/dbus-escape.cc @@ -5,12 +5,13 @@ #include #include +#include using namespace DBus; -BOOST_AUTO_TEST_CASE(escape) +BOOST_AUTO_TEST_CASE(hoho_escape) { BOOST_CHECK_EQUAL(Hoho::escape("\\"), "\\\\"); @@ -21,7 +22,7 @@ BOOST_AUTO_TEST_CASE(escape) } -BOOST_AUTO_TEST_CASE(unescape) +BOOST_AUTO_TEST_CASE(hihi_unescape) { BOOST_CHECK_EQUAL(Hihi::unescape("\\\\"), "\\"); @@ -35,3 +36,15 @@ BOOST_AUTO_TEST_CASE(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"); +} -- 2.47.3