diff options
-rw-r--r-- | fs_mgr/libsnapshot/Android.bp | 37 | ||||
-rw-r--r-- | fs_mgr/libsnapshot/cow_snapuserd_test.cpp | 117 | ||||
-rw-r--r-- | fs_mgr/libsnapshot/include/libsnapshot/snapuserd.h | 153 | ||||
-rw-r--r-- | fs_mgr/libsnapshot/include/libsnapshot/snapuserd_client.h | 73 | ||||
-rw-r--r-- | fs_mgr/libsnapshot/include/libsnapshot/snapuserd_daemon.h | 47 | ||||
-rw-r--r-- | fs_mgr/libsnapshot/include/libsnapshot/snapuserd_kernel.h | 97 | ||||
-rw-r--r-- | fs_mgr/libsnapshot/include/libsnapshot/snapuserd_server.h | 115 | ||||
-rw-r--r-- | fs_mgr/libsnapshot/snapuserd.cpp | 404 | ||||
-rw-r--r-- | fs_mgr/libsnapshot/snapuserd_client.cpp | 261 | ||||
-rw-r--r-- | fs_mgr/libsnapshot/snapuserd_daemon.cpp | 53 | ||||
-rw-r--r-- | fs_mgr/libsnapshot/snapuserd_server.cpp | 215 |
11 files changed, 1209 insertions, 363 deletions
diff --git a/fs_mgr/libsnapshot/Android.bp b/fs_mgr/libsnapshot/Android.bp index 046ea74b5..ddffef295 100644 --- a/fs_mgr/libsnapshot/Android.bp +++ b/fs_mgr/libsnapshot/Android.bp @@ -163,6 +163,38 @@ cc_library_static { ramdisk_available: true, } +cc_defaults { + name: "libsnapshot_snapuserd_defaults", + defaults: [ + "fs_mgr_defaults", + ], + cflags: [ + "-D_FILE_OFFSET_BITS=64", + "-Wall", + "-Werror", + ], + export_include_dirs: ["include"], + srcs: [ + "snapuserd_client.cpp", + ], +} + +cc_library_static { + name: "libsnapshot_snapuserd", + defaults: [ + "libsnapshot_snapuserd_defaults", + ], + recovery_available: true, + static_libs: [ + "libcutils_sockets", + ], + shared_libs: [ + "libbase", + "liblog", + ], + ramdisk_available: true, +} + cc_library_static { name: "libsnapshot_test_helpers", defaults: ["libsnapshot_defaults"], @@ -363,7 +395,9 @@ cc_defaults { "fs_mgr_defaults", ], srcs: [ + "snapuserd_server.cpp", "snapuserd.cpp", + "snapuserd_daemon.cpp", ], cflags: [ @@ -374,6 +408,7 @@ cc_defaults { static_libs: [ "libbase", "libbrotli", + "libcutils_sockets", "liblog", "libdm", "libz", @@ -513,6 +548,8 @@ cc_test { "libbrotli", "libgtest", "libsnapshot_cow", + "libsnapshot_snapuserd", + "libcutils_sockets", "libz", ], header_libs: [ diff --git a/fs_mgr/libsnapshot/cow_snapuserd_test.cpp b/fs_mgr/libsnapshot/cow_snapuserd_test.cpp index 80acb4ae1..75e54f706 100644 --- a/fs_mgr/libsnapshot/cow_snapuserd_test.cpp +++ b/fs_mgr/libsnapshot/cow_snapuserd_test.cpp @@ -26,6 +26,7 @@ #include <android-base/unique_fd.h> #include <gtest/gtest.h> #include <libsnapshot/cow_writer.h> +#include <libsnapshot/snapuserd_client.h> #include <storage_literals/storage_literals.h> namespace android { @@ -43,17 +44,29 @@ class SnapuserdTest : public ::testing::Test { cow_product_ = std::make_unique<TemporaryFile>(); ASSERT_GE(cow_product_->fd, 0) << strerror(errno); + cow_system_1_ = std::make_unique<TemporaryFile>(); + ASSERT_GE(cow_system_1_->fd, 0) << strerror(errno); + + cow_product_1_ = std::make_unique<TemporaryFile>(); + ASSERT_GE(cow_product_1_->fd, 0) << strerror(errno); + size_ = 100_MiB; } void TearDown() override { cow_system_ = nullptr; cow_product_ = nullptr; + + cow_system_1_ = nullptr; + cow_product_1_ = nullptr; } std::unique_ptr<TemporaryFile> cow_system_; std::unique_ptr<TemporaryFile> cow_product_; + std::unique_ptr<TemporaryFile> cow_system_1_; + std::unique_ptr<TemporaryFile> cow_product_1_; + unique_fd sys_fd_; unique_fd product_fd_; size_t size_; @@ -71,12 +84,14 @@ class SnapuserdTest : public ::testing::Test { void Init(); void CreateCowDevice(std::unique_ptr<TemporaryFile>& cow); - void CreateSystemDmUser(); - void CreateProductDmUser(); + void CreateSystemDmUser(std::unique_ptr<TemporaryFile>& cow); + void CreateProductDmUser(std::unique_ptr<TemporaryFile>& cow); void StartSnapuserdDaemon(); void CreateSnapshotDevices(); + void SwitchSnapshotDevices(); - void TestIO(unique_fd& snapshot_fd, std::unique_ptr<uint8_t[]>&& buf); + void TestIO(unique_fd& snapshot_fd, std::unique_ptr<uint8_t[]>& buffer); + SnapuserdClient client_; }; void SnapuserdTest::Init() { @@ -112,7 +127,7 @@ void SnapuserdTest::Init() { // Read from system partition from offset 0 of size 100MB ASSERT_EQ(ReadFullyAtOffset(sys_fd_, system_buffer_.get(), size_, 0), true); - // Read from system partition from offset 0 of size 100MB + // Read from product partition from offset 0 of size 100MB ASSERT_EQ(ReadFullyAtOffset(product_fd_, product_buffer_.get(), size_, 0), true); } @@ -167,9 +182,10 @@ void SnapuserdTest::CreateCowDevice(std::unique_ptr<TemporaryFile>& cow) { ASSERT_EQ(lseek(cow->fd, 0, SEEK_SET), 0); } -void SnapuserdTest::CreateSystemDmUser() { +void SnapuserdTest::CreateSystemDmUser(std::unique_ptr<TemporaryFile>& cow) { unique_fd system_a_fd; std::string cmd; + system_device_name_.clear(); // Create a COW device. Number of sectors is chosen random which can // hold at least 400MB of data @@ -180,7 +196,7 @@ void SnapuserdTest::CreateSystemDmUser() { int err = ioctl(system_a_fd.get(), BLKGETSIZE, &system_blksize_); ASSERT_GE(err, 0); - std::string str(cow_system_->path); + std::string str(cow->path); std::size_t found = str.find_last_of("/\\"); ASSERT_NE(found, std::string::npos); system_device_name_ = str.substr(found + 1); @@ -189,9 +205,10 @@ void SnapuserdTest::CreateSystemDmUser() { system(cmd.c_str()); } -void SnapuserdTest::CreateProductDmUser() { +void SnapuserdTest::CreateProductDmUser(std::unique_ptr<TemporaryFile>& cow) { unique_fd product_a_fd; std::string cmd; + product_device_name_.clear(); // Create a COW device. Number of sectors is chosen random which can // hold at least 400MB of data @@ -202,7 +219,7 @@ void SnapuserdTest::CreateProductDmUser() { int err = ioctl(product_a_fd.get(), BLKGETSIZE, &product_blksize_); ASSERT_GE(err, 0); - std::string str(cow_product_->path); + std::string str(cow->path); std::size_t found = str.find_last_of("/\\"); ASSERT_NE(found, std::string::npos); product_device_name_ = str.substr(found + 1); @@ -212,15 +229,16 @@ void SnapuserdTest::CreateProductDmUser() { } void SnapuserdTest::StartSnapuserdDaemon() { - // Start the snapuserd daemon - if (fork() == 0) { - const char* argv[] = {"/system/bin/snapuserd", cow_system_->path, - "/dev/block/mapper/system_a", cow_product_->path, - "/dev/block/mapper/product_a", nullptr}; - if (execv(argv[0], const_cast<char**>(argv))) { - ASSERT_TRUE(0); - } - } + int ret; + + ret = client_.StartSnapuserd(); + ASSERT_EQ(ret, 0); + + ret = client_.InitializeSnapuserd(cow_system_->path, "/dev/block/mapper/system_a"); + ASSERT_EQ(ret, 0); + + ret = client_.InitializeSnapuserd(cow_product_->path, "/dev/block/mapper/product_a"); + ASSERT_EQ(ret, 0); } void SnapuserdTest::CreateSnapshotDevices() { @@ -243,9 +261,29 @@ void SnapuserdTest::CreateSnapshotDevices() { system(cmd.c_str()); } -void SnapuserdTest::TestIO(unique_fd& snapshot_fd, std::unique_ptr<uint8_t[]>&& buf) { +void SnapuserdTest::SwitchSnapshotDevices() { + std::string cmd; + + cmd = "dmctl create system-snapshot-1 -ro snapshot 0 " + std::to_string(system_blksize_); + cmd += " /dev/block/mapper/system_a"; + cmd += " /dev/block/mapper/" + system_device_name_; + cmd += " P 8"; + + system(cmd.c_str()); + + cmd.clear(); + + cmd = "dmctl create product-snapshot-1 -ro snapshot 0 " + std::to_string(product_blksize_); + cmd += " /dev/block/mapper/product_a"; + cmd += " /dev/block/mapper/" + product_device_name_; + cmd += " P 8"; + + system(cmd.c_str()); +} + +void SnapuserdTest::TestIO(unique_fd& snapshot_fd, std::unique_ptr<uint8_t[]>& buffer) { loff_t offset = 0; - std::unique_ptr<uint8_t[]> buffer = std::move(buf); + // std::unique_ptr<uint8_t[]> buffer = std::move(buf); std::unique_ptr<uint8_t[]> snapuserd_buffer = std::make_unique<uint8_t[]>(size_); @@ -326,8 +364,8 @@ TEST_F(SnapuserdTest, ReadWrite) { CreateCowDevice(cow_system_); CreateCowDevice(cow_product_); - CreateSystemDmUser(); - CreateProductDmUser(); + CreateSystemDmUser(cow_system_); + CreateProductDmUser(cow_product_); StartSnapuserdDaemon(); @@ -335,11 +373,44 @@ TEST_F(SnapuserdTest, ReadWrite) { snapshot_fd.reset(open("/dev/block/mapper/system-snapshot", O_RDONLY)); ASSERT_TRUE(snapshot_fd > 0); - TestIO(snapshot_fd, std::move(system_buffer_)); + TestIO(snapshot_fd, system_buffer_); snapshot_fd.reset(open("/dev/block/mapper/product-snapshot", O_RDONLY)); ASSERT_TRUE(snapshot_fd > 0); - TestIO(snapshot_fd, std::move(product_buffer_)); + TestIO(snapshot_fd, product_buffer_); + + // Sequence of operations for transition + CreateCowDevice(cow_system_1_); + CreateCowDevice(cow_product_1_); + + CreateSystemDmUser(cow_system_1_); + CreateProductDmUser(cow_product_1_); + + std::vector<std::pair<std::string, std::string>> vec; + vec.push_back(std::make_pair(cow_system_1_->path, "/dev/block/mapper/system_a")); + vec.push_back(std::make_pair(cow_product_1_->path, "/dev/block/mapper/product_a")); + + // Start the second stage deamon and send the devices + ASSERT_EQ(client_.RestartSnapuserd(vec), 0); + + // TODO: This is not switching snapshot device but creates a new table; + // however, it should serve the testing purpose. + SwitchSnapshotDevices(); + + // Stop the first stage daemon + ASSERT_EQ(client_.StopSnapuserd(true), 0); + + // Test the IO again with the second stage daemon + snapshot_fd.reset(open("/dev/block/mapper/system-snapshot-1", O_RDONLY)); + ASSERT_TRUE(snapshot_fd > 0); + TestIO(snapshot_fd, system_buffer_); + + snapshot_fd.reset(open("/dev/block/mapper/product-snapshot-1", O_RDONLY)); + ASSERT_TRUE(snapshot_fd > 0); + TestIO(snapshot_fd, product_buffer_); + + // Stop the second stage daemon + ASSERT_EQ(client_.StopSnapuserd(false), 0); } } // namespace snapshot diff --git a/fs_mgr/libsnapshot/include/libsnapshot/snapuserd.h b/fs_mgr/libsnapshot/include/libsnapshot/snapuserd.h index e75757925..6331edb61 100644 --- a/fs_mgr/libsnapshot/include/libsnapshot/snapuserd.h +++ b/fs_mgr/libsnapshot/include/libsnapshot/snapuserd.h @@ -14,85 +14,94 @@ #pragma once +#include <linux/types.h> #include <stdint.h> +#include <stdlib.h> + +#include <csignal> +#include <cstring> +#include <iostream> +#include <limits> +#include <string> +#include <thread> +#include <vector> + +#include <android-base/file.h> +#include <android-base/logging.h> +#include <android-base/stringprintf.h> +#include <android-base/unique_fd.h> +#include <libdm/dm.h> +#include <libsnapshot/cow_reader.h> +#include <libsnapshot/cow_writer.h> +#include <libsnapshot/snapuserd_kernel.h> namespace android { namespace snapshot { -// Kernel COW header fields -static constexpr uint32_t SNAP_MAGIC = 0x70416e53; - -static constexpr uint32_t SNAPSHOT_DISK_VERSION = 1; - -static constexpr uint32_t NUM_SNAPSHOT_HDR_CHUNKS = 1; - -static constexpr uint32_t SNAPSHOT_VALID = 1; - -/* - * The basic unit of block I/O is a sector. It is used in a number of contexts - * in Linux (blk, bio, genhd). The size of one sector is 512 = 2**9 - * bytes. Variables of type sector_t represent an offset or size that is a - * multiple of 512 bytes. Hence these two constants. - */ -static constexpr uint32_t SECTOR_SHIFT = 9; - -typedef __u64 sector_t; -typedef sector_t chunk_t; - -static constexpr uint32_t CHUNK_SIZE = 8; -static constexpr uint32_t CHUNK_SHIFT = (__builtin_ffs(CHUNK_SIZE) - 1); - -static constexpr uint32_t BLOCK_SIZE = 4096; -static constexpr uint32_t BLOCK_SHIFT = (__builtin_ffs(BLOCK_SIZE) - 1); - -// This structure represents the kernel COW header. -// All the below fields should be in Little Endian format. -struct disk_header { - uint32_t magic; - - /* - * Is this snapshot valid. There is no way of recovering - * an invalid snapshot. - */ - uint32_t valid; - - /* - * Simple, incrementing version. no backward - * compatibility. - */ - uint32_t version; - - /* In sectors */ - uint32_t chunk_size; -} __packed; - -// A disk exception is a mapping of old_chunk to new_chunk -// old_chunk is the chunk ID of a dm-snapshot device. -// new_chunk is the chunk ID of the COW device. -struct disk_exception { - uint64_t old_chunk; - uint64_t new_chunk; -} __packed; - -// Control structures to communicate with dm-user -// It comprises of header and a payload -struct dm_user_header { - __u64 seq; - __u64 type; - __u64 flags; - __u64 sector; - __u64 len; - __u64 io_in_progress; -} __attribute__((packed)); - -struct dm_user_payload { - __u8 buf[]; +using android::base::unique_fd; + +class BufferSink : public IByteSink { + public: + void Initialize(size_t size); + void* GetBufPtr() { return buffer_.get(); } + void Clear() { memset(GetBufPtr(), 0, buffer_size_); } + void* GetPayloadBuffer(size_t size); + void* GetBuffer(size_t requested, size_t* actual) override; + void UpdateBufferOffset(size_t size) { buffer_offset_ += size; } + struct dm_user_header* GetHeaderPtr(); + bool ReturnData(void*, size_t) override { return true; } + void ResetBufferOffset() { buffer_offset_ = 0; } + + private: + std::unique_ptr<uint8_t[]> buffer_; + loff_t buffer_offset_; + size_t buffer_size_; }; -// Message comprising both header and payload -struct dm_user_message { - struct dm_user_header header; - struct dm_user_payload payload; +class Snapuserd final { + public: + Snapuserd(const std::string& in_cow_device, const std::string& in_backing_store_device) + : cow_device_(in_cow_device), + backing_store_device_(in_backing_store_device), + metadata_read_done_(false) {} + + int Init(); + int Run(); + int ReadDmUserHeader(); + int WriteDmUserPayload(size_t size); + int ConstructKernelCowHeader(); + int ReadMetadata(); + int ZerofillDiskExceptions(size_t read_size); + int ReadDiskExceptions(chunk_t chunk, size_t size); + int ReadData(chunk_t chunk, size_t size); + + private: + int ProcessReplaceOp(const CowOperation* cow_op); + int ProcessCopyOp(const CowOperation* cow_op); + int ProcessZeroOp(); + + std::string cow_device_; + std::string backing_store_device_; + + unique_fd cow_fd_; + unique_fd backing_store_fd_; + unique_fd ctrl_fd_; + + uint32_t exceptions_per_area_; + + std::unique_ptr<ICowOpIter> cowop_iter_; + std::unique_ptr<CowReader> reader_; + + // Vector of disk exception which is a + // mapping of old-chunk to new-chunk + std::vector<std::unique_ptr<uint8_t[]>> vec_; + + // Index - Chunk ID + // Value - cow operation + std::vector<const CowOperation*> chunk_vec_; + + bool metadata_read_done_; + BufferSink bufsink_; }; } // namespace snapshot diff --git a/fs_mgr/libsnapshot/include/libsnapshot/snapuserd_client.h b/fs_mgr/libsnapshot/include/libsnapshot/snapuserd_client.h new file mode 100644 index 000000000..2d9d7294f --- /dev/null +++ b/fs_mgr/libsnapshot/include/libsnapshot/snapuserd_client.h @@ -0,0 +1,73 @@ +// Copyright (C) 2020 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#pragma once + +#include <arpa/inet.h> +#include <cutils/sockets.h> +#include <errno.h> +#include <netdb.h> +#include <netinet/in.h> +#include <stdint.h> +#include <stdio.h> +#include <stdlib.h> +#include <sys/socket.h> +#include <sys/types.h> +#include <unistd.h> + +#include <chrono> +#include <cstring> +#include <iostream> +#include <sstream> +#include <string> +#include <thread> +#include <vector> + +namespace android { +namespace snapshot { + +static constexpr uint32_t PACKET_SIZE = 512; +static constexpr uint32_t MAX_CONNECT_RETRY_COUNT = 10; + +class SnapuserdClient { + private: + int sockfd_ = 0; + + int Sendmsg(const char* msg, size_t size); + std::string Receivemsg(); + int StartSnapuserdaemon(std::string socketname); + bool ConnectToServerSocket(std::string socketname); + bool ConnectToServer(); + + void DisconnectFromServer() { close(sockfd_); } + + std::string GetSocketNameFirstStage() { + static std::string snapd_one("snapdone"); + return snapd_one; + } + + std::string GetSocketNameSecondStage() { + static std::string snapd_two("snapdtwo"); + return snapd_two; + } + + public: + int StartSnapuserd(); + int StopSnapuserd(bool firstStageDaemon); + int RestartSnapuserd(std::vector<std::pair<std::string, std::string>>& vec); + int InitializeSnapuserd(std::string cow_device, std::string backing_device); +}; + +} // namespace snapshot +} // namespace android diff --git a/fs_mgr/libsnapshot/include/libsnapshot/snapuserd_daemon.h b/fs_mgr/libsnapshot/include/libsnapshot/snapuserd_daemon.h new file mode 100644 index 000000000..c0d3c5eb9 --- /dev/null +++ b/fs_mgr/libsnapshot/include/libsnapshot/snapuserd_daemon.h @@ -0,0 +1,47 @@ +// Copyright (C) 2020 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#pragma once + +#include <libsnapshot/snapuserd_server.h> + +namespace android { +namespace snapshot { + +class Daemon { + // The Daemon class is a singleton to avoid + // instantiating more than once + public: + static Daemon& Instance() { + static Daemon instance; + return instance; + } + + int StartServer(std::string socketname); + bool IsRunning(); + void Run(); + + private: + bool is_running_; + + Daemon(); + Daemon(Daemon const&) = delete; + void operator=(Daemon const&) = delete; + + SnapuserdServer server_; + static void SignalHandler(int signal); +}; + +} // namespace snapshot +} // namespace android diff --git a/fs_mgr/libsnapshot/include/libsnapshot/snapuserd_kernel.h b/fs_mgr/libsnapshot/include/libsnapshot/snapuserd_kernel.h new file mode 100644 index 000000000..1a6ba8fe4 --- /dev/null +++ b/fs_mgr/libsnapshot/include/libsnapshot/snapuserd_kernel.h @@ -0,0 +1,97 @@ +// Copyright (C) 2020 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#pragma once + +namespace android { +namespace snapshot { + +// Kernel COW header fields +static constexpr uint32_t SNAP_MAGIC = 0x70416e53; + +static constexpr uint32_t SNAPSHOT_DISK_VERSION = 1; + +static constexpr uint32_t NUM_SNAPSHOT_HDR_CHUNKS = 1; + +static constexpr uint32_t SNAPSHOT_VALID = 1; + +/* + * The basic unit of block I/O is a sector. It is used in a number of contexts + * in Linux (blk, bio, genhd). The size of one sector is 512 = 2**9 + * bytes. Variables of type sector_t represent an offset or size that is a + * multiple of 512 bytes. Hence these two constants. + */ +static constexpr uint32_t SECTOR_SHIFT = 9; + +typedef __u64 sector_t; +typedef sector_t chunk_t; + +static constexpr uint32_t CHUNK_SIZE = 8; +static constexpr uint32_t CHUNK_SHIFT = (__builtin_ffs(CHUNK_SIZE) - 1); + +static constexpr uint32_t BLOCK_SIZE = 4096; +static constexpr uint32_t BLOCK_SHIFT = (__builtin_ffs(BLOCK_SIZE) - 1); + +// This structure represents the kernel COW header. +// All the below fields should be in Little Endian format. +struct disk_header { + uint32_t magic; + + /* + * Is this snapshot valid. There is no way of recovering + * an invalid snapshot. + */ + uint32_t valid; + + /* + * Simple, incrementing version. no backward + * compatibility. + */ + uint32_t version; + + /* In sectors */ + uint32_t chunk_size; +} __packed; + +// A disk exception is a mapping of old_chunk to new_chunk +// old_chunk is the chunk ID of a dm-snapshot device. +// new_chunk is the chunk ID of the COW device. +struct disk_exception { + uint64_t old_chunk; + uint64_t new_chunk; +} __packed; + +// Control structures to communicate with dm-user +// It comprises of header and a payload +struct dm_user_header { + __u64 seq; + __u64 type; + __u64 flags; + __u64 sector; + __u64 len; + __u64 io_in_progress; +} __attribute__((packed)); + +struct dm_user_payload { + __u8 buf[]; +}; + +// Message comprising both header and payload +struct dm_user_message { + struct dm_user_header header; + struct dm_user_payload payload; +}; + +} // namespace snapshot +} // namespace android diff --git a/fs_mgr/libsnapshot/include/libsnapshot/snapuserd_server.h b/fs_mgr/libsnapshot/include/libsnapshot/snapuserd_server.h new file mode 100644 index 000000000..79b883a57 --- /dev/null +++ b/fs_mgr/libsnapshot/include/libsnapshot/snapuserd_server.h @@ -0,0 +1,115 @@ +// Copyright (C) 2020 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#pragma once + +#include <stdint.h> + +#include <arpa/inet.h> +#include <cutils/sockets.h> +#include <netinet/in.h> +#include <stdio.h> +#include <stdlib.h> +#include <sys/socket.h> +#include <sys/types.h> +#include <unistd.h> + +#include <errno.h> +#include <cstdio> +#include <cstring> +#include <functional> +#include <future> +#include <iostream> +#include <sstream> +#include <string> +#include <thread> +#include <vector> + +#include <android-base/unique_fd.h> + +namespace android { +namespace snapshot { + +static constexpr uint32_t MAX_PACKET_SIZE = 512; + +enum class DaemonOperations { + START, + QUERY, + TERMINATING, + STOP, + INVALID, +}; + +class Client { + private: + std::unique_ptr<std::thread> threadHandler_; + + public: + void SetThreadHandler(std::function<void(void)> func) { + threadHandler_ = std::make_unique<std::thread>(func); + } + + std::unique_ptr<std::thread>& GetThreadHandler() { return threadHandler_; } +}; + +class Stoppable { + std::promise<void> exitSignal_; + std::future<void> futureObj_; + + public: + Stoppable() : futureObj_(exitSignal_.get_future()) {} + + virtual ~Stoppable() {} + + virtual void ThreadStart(std::string cow_device, std::string backing_device) = 0; + + bool StopRequested() { + // checks if value in future object is available + if (futureObj_.wait_for(std::chrono::milliseconds(0)) == std::future_status::timeout) + return false; + return true; + } + // Request the thread to stop by setting value in promise object + void StopThreads() { exitSignal_.set_value(); } +}; + +class SnapuserdServer : public Stoppable { + private: + android::base::unique_fd sockfd_; + bool terminating_; + std::vector<std::unique_ptr<Client>> clients_vec_; + void ThreadStart(std::string cow_device, std::string backing_device) override; + void ShutdownThreads(); + DaemonOperations Resolveop(std::string& input); + std::string GetDaemonStatus(); + void Parsemsg(std::string const& msg, const char delim, std::vector<std::string>& out); + + void SetTerminating() { terminating_ = true; } + + bool IsTerminating() { return terminating_; } + + public: + ~SnapuserdServer() { clients_vec_.clear(); } + + SnapuserdServer() { terminating_ = false; } + + int Start(std::string socketname); + int AcceptClient(); + int Receivemsg(int fd); + int Sendmsg(int fd, char* msg, size_t len); + std::string Recvmsg(int fd, int* ret); +}; + +} // namespace snapshot +} // namespace android diff --git a/fs_mgr/libsnapshot/snapuserd.cpp b/fs_mgr/libsnapshot/snapuserd.cpp index d3f4f70de..34481b779 100644 --- a/fs_mgr/libsnapshot/snapuserd.cpp +++ b/fs_mgr/libsnapshot/snapuserd.cpp @@ -14,25 +14,11 @@ * limitations under the License. */ -#include <linux/types.h> -#include <stdlib.h> - #include <csignal> -#include <cstring> -#include <iostream> -#include <limits> -#include <string> -#include <thread> -#include <vector> - -#include <android-base/file.h> -#include <android-base/logging.h> -#include <android-base/stringprintf.h> -#include <android-base/unique_fd.h> -#include <libdm/dm.h> -#include <libsnapshot/cow_reader.h> -#include <libsnapshot/cow_writer.h> + #include <libsnapshot/snapuserd.h> +#include <libsnapshot/snapuserd_daemon.h> +#include <libsnapshot/snapuserd_server.h> namespace android { namespace snapshot { @@ -60,140 +46,36 @@ class Target { const std::string uuid_; }; -class Daemon { - // The Daemon class is a singleton to avoid - // instantiating more than once - public: - static Daemon& Instance() { - static Daemon instance; - return instance; - } - - bool IsRunning(); - - private: - bool is_running_; - - Daemon(); - Daemon(Daemon const&) = delete; - void operator=(Daemon const&) = delete; - - static void SignalHandler(int signal); -}; - -Daemon::Daemon() { - is_running_ = true; - signal(SIGINT, Daemon::SignalHandler); - signal(SIGTERM, Daemon::SignalHandler); +void BufferSink::Initialize(size_t size) { + buffer_size_ = size; + buffer_offset_ = 0; + buffer_ = std::make_unique<uint8_t[]>(size); } -bool Daemon::IsRunning() { - return is_running_; -} +void* BufferSink::GetPayloadBuffer(size_t size) { + if ((buffer_size_ - buffer_offset_) < size) return nullptr; -void Daemon::SignalHandler(int signal) { - LOG(DEBUG) << "Snapuserd received signal: " << signal; - switch (signal) { - case SIGINT: - case SIGTERM: { - Daemon::Instance().is_running_ = false; - break; - } - } + char* buffer = reinterpret_cast<char*>(GetBufPtr()); + struct dm_user_message* msg = (struct dm_user_message*)(&(buffer[0])); + return (char*)msg->payload.buf + buffer_offset_; } -class BufferSink : public IByteSink { - public: - void Initialize(size_t size) { - buffer_size_ = size; - buffer_offset_ = 0; - buffer_ = std::make_unique<uint8_t[]>(size); - } - - void* GetBufPtr() { return buffer_.get(); } - - void Clear() { memset(GetBufPtr(), 0, buffer_size_); } - - void* GetPayloadBuffer(size_t size) { - if ((buffer_size_ - buffer_offset_) < size) return nullptr; - - char* buffer = reinterpret_cast<char*>(GetBufPtr()); - struct dm_user_message* msg = (struct dm_user_message*)(&(buffer[0])); - return (char*)msg->payload.buf + buffer_offset_; - } - - void* GetBuffer(size_t requested, size_t* actual) override { - void* buf = GetPayloadBuffer(requested); - if (!buf) { - *actual = 0; - return nullptr; - } - *actual = requested; - return buf; - } - - void UpdateBufferOffset(size_t size) { buffer_offset_ += size; } - - struct dm_user_header* GetHeaderPtr() { - CHECK(sizeof(struct dm_user_header) <= buffer_size_); - char* buf = reinterpret_cast<char*>(GetBufPtr()); - struct dm_user_header* header = (struct dm_user_header*)(&(buf[0])); - return header; +void* BufferSink::GetBuffer(size_t requested, size_t* actual) { + void* buf = GetPayloadBuffer(requested); + if (!buf) { + *actual = 0; + return nullptr; } + *actual = requested; + return buf; +} - bool ReturnData(void*, size_t) override { return true; } - void ResetBufferOffset() { buffer_offset_ = 0; } - - private: - std::unique_ptr<uint8_t[]> buffer_; - loff_t buffer_offset_; - size_t buffer_size_; -}; - -class Snapuserd final { - public: - Snapuserd(const std::string& in_cow_device, const std::string& in_backing_store_device) - : in_cow_device_(in_cow_device), - in_backing_store_device_(in_backing_store_device), - metadata_read_done_(false) {} - - int Run(); - int ReadDmUserHeader(); - int WriteDmUserPayload(size_t size); - int ConstructKernelCowHeader(); - int ReadMetadata(); - int ZerofillDiskExceptions(size_t read_size); - int ReadDiskExceptions(chunk_t chunk, size_t size); - int ReadData(chunk_t chunk, size_t size); - - private: - int ProcessReplaceOp(const CowOperation* cow_op); - int ProcessCopyOp(const CowOperation* cow_op); - int ProcessZeroOp(); - - std::string in_cow_device_; - std::string in_backing_store_device_; - - unique_fd cow_fd_; - unique_fd backing_store_fd_; - unique_fd ctrl_fd_; - - uint32_t exceptions_per_area_; - - std::unique_ptr<ICowOpIter> cowop_iter_; - std::unique_ptr<CowReader> reader_; - - // Vector of disk exception which is a - // mapping of old-chunk to new-chunk - std::vector<std::unique_ptr<uint8_t[]>> vec_; - - // Index - Chunk ID - // Value - cow operation - std::vector<const CowOperation*> chunk_vec_; - - bool metadata_read_done_; - BufferSink bufsink_; -}; +struct dm_user_header* BufferSink::GetHeaderPtr() { + CHECK(sizeof(struct dm_user_header) <= buffer_size_); + char* buf = reinterpret_cast<char*>(GetBufPtr()); + struct dm_user_header* header = (struct dm_user_header*)(&(buf[0])); + return header; +} // Construct kernel COW header in memory // This header will be in sector 0. The IO @@ -581,9 +463,12 @@ void MyLogger(android::base::LogId, android::base::LogSeverity severity, const c // Read Header from dm-user misc device. This gives // us the sector number for which IO is issued by dm-snapshot device int Snapuserd::ReadDmUserHeader() { - if (!android::base::ReadFully(ctrl_fd_, bufsink_.GetBufPtr(), sizeof(struct dm_user_header))) { - PLOG(ERROR) << "Control read failed"; - return -1; + int ret; + + ret = read(ctrl_fd_, bufsink_.GetBufPtr(), sizeof(struct dm_user_header)); + if (ret < 0) { + PLOG(ERROR) << "Control-read failed with: " << ret; + return ret; } return sizeof(struct dm_user_header); @@ -600,22 +485,20 @@ int Snapuserd::WriteDmUserPayload(size_t size) { return sizeof(struct dm_user_header) + size; } -// Start the daemon. -// TODO: Handle signals -int Snapuserd::Run() { - backing_store_fd_.reset(open(in_backing_store_device_.c_str(), O_RDONLY)); +int Snapuserd::Init() { + backing_store_fd_.reset(open(backing_store_device_.c_str(), O_RDONLY)); if (backing_store_fd_ < 0) { - LOG(ERROR) << "Open Failed: " << in_backing_store_device_; + LOG(ERROR) << "Open Failed: " << backing_store_device_; return 1; } - cow_fd_.reset(open(in_cow_device_.c_str(), O_RDWR)); + cow_fd_.reset(open(cow_device_.c_str(), O_RDWR)); if (cow_fd_ < 0) { - LOG(ERROR) << "Open Failed: " << in_cow_device_; + LOG(ERROR) << "Open Failed: " << cow_device_; return 1; } - std::string str(in_cow_device_); + std::string str(cow_device_); std::size_t found = str.find_last_of("/\\"); CHECK(found != std::string::npos); std::string device_name = str.substr(found + 1); @@ -625,7 +508,7 @@ int Snapuserd::Run() { auto& dm = dm::DeviceMapper::Instance(); std::string uuid; if (!dm.GetDmDeviceUuidByName(device_name, &uuid)) { - LOG(ERROR) << "Unable to find UUID for " << in_cow_device_; + LOG(ERROR) << "Unable to find UUID for " << cow_device_; return 1; } @@ -638,8 +521,6 @@ int Snapuserd::Run() { return 1; } - int ret = 0; - // Allocate the buffer which is used to communicate between // daemon and dm-user. The buffer comprises of header and a fixed payload. // If the dm-user requests a big IO, the IO will be broken into chunks @@ -647,138 +528,125 @@ int Snapuserd::Run() { size_t buf_size = sizeof(struct dm_user_header) + PAYLOAD_SIZE; bufsink_.Initialize(buf_size); - while (true) { - struct dm_user_header* header = bufsink_.GetHeaderPtr(); - - bufsink_.Clear(); - - ret = ReadDmUserHeader(); - if (ret < 0) return ret; - - LOG(DEBUG) << "dm-user returned " << ret << " bytes"; - - LOG(DEBUG) << "msg->seq: " << std::hex << header->seq; - LOG(DEBUG) << "msg->type: " << std::hex << header->type; - LOG(DEBUG) << "msg->flags: " << std::hex << header->flags; - LOG(DEBUG) << "msg->sector: " << std::hex << header->sector; - LOG(DEBUG) << "msg->len: " << std::hex << header->len; - - switch (header->type) { - case DM_USER_MAP_READ: { - size_t remaining_size = header->len; - loff_t offset = 0; - header->io_in_progress = 0; - ret = 0; - do { - size_t read_size = std::min(PAYLOAD_SIZE, remaining_size); - - // Request to sector 0 is always for kernel - // representation of COW header. This IO should be only - // once during dm-snapshot device creation. We should - // never see multiple IO requests. Additionally this IO - // will always be a single 4k. - if (header->sector == 0) { - // Read the metadata from internal COW device - // and build the in-memory data structures - // for all the operations in the internal COW. - if (!metadata_read_done_ && ReadMetadata()) { - LOG(ERROR) << "Metadata read failed"; - return 1; - } - metadata_read_done_ = true; + return 0; +} + +int Snapuserd::Run() { + int ret = 0; - CHECK(read_size == BLOCK_SIZE); - ret = ConstructKernelCowHeader(); - if (ret < 0) return ret; + struct dm_user_header* header = bufsink_.GetHeaderPtr(); + + bufsink_.Clear(); + + ret = ReadDmUserHeader(); + if (ret < 0) return ret; + + LOG(DEBUG) << "dm-user returned " << ret << " bytes"; + + LOG(DEBUG) << "msg->seq: " << std::hex << header->seq; + LOG(DEBUG) << "msg->type: " << std::hex << header->type; + LOG(DEBUG) << "msg->flags: " << std::hex << header->flags; + LOG(DEBUG) << "msg->sector: " << std::hex << header->sector; + LOG(DEBUG) << "msg->len: " << std::hex << header->len; + + switch (header->type) { + case DM_USER_MAP_READ: { + size_t remaining_size = header->len; + loff_t offset = 0; + header->io_in_progress = 0; + ret = 0; + do { + size_t read_size = std::min(PAYLOAD_SIZE, remaining_size); + + // Request to sector 0 is always for kernel + // representation of COW header. This IO should be only + // once during dm-snapshot device creation. We should + // never see multiple IO requests. Additionally this IO + // will always be a single 4k. + if (header->sector == 0) { + // Read the metadata from internal COW device + // and build the in-memory data structures + // for all the operations in the internal COW. + if (!metadata_read_done_ && ReadMetadata()) { + LOG(ERROR) << "Metadata read failed"; + return 1; + } + metadata_read_done_ = true; + + CHECK(read_size == BLOCK_SIZE); + ret = ConstructKernelCowHeader(); + if (ret < 0) return ret; + } else { + // Convert the sector number to a chunk ID. + // + // Check if the chunk ID represents a metadata + // page. If the chunk ID is not found in the + // vector, then it points to a metadata page. + chunk_t chunk = (header->sector >> CHUNK_SHIFT); + + if (chunk >= chunk_vec_.size()) { + ret = ZerofillDiskExceptions(read_size); + if (ret < 0) { + LOG(ERROR) << "ZerofillDiskExceptions failed"; + return ret; + } + } else if (chunk_vec_[chunk] == nullptr) { + ret = ReadDiskExceptions(chunk, read_size); + if (ret < 0) { + LOG(ERROR) << "ReadDiskExceptions failed"; + return ret; + } } else { - // Convert the sector number to a chunk ID. - // - // Check if the chunk ID represents a metadata - // page. If the chunk ID is not found in the - // vector, then it points to a metadata page. - chunk_t chunk = (header->sector >> CHUNK_SHIFT); - - if (chunk >= chunk_vec_.size()) { - ret = ZerofillDiskExceptions(read_size); - if (ret < 0) { - LOG(ERROR) << "ZerofillDiskExceptions failed"; - return ret; - } - } else if (chunk_vec_[chunk] == nullptr) { - ret = ReadDiskExceptions(chunk, read_size); - if (ret < 0) { - LOG(ERROR) << "ReadDiskExceptions failed"; - return ret; - } - } else { - chunk_t num_chunks_read = (offset >> BLOCK_SHIFT); - ret = ReadData(chunk + num_chunks_read, read_size); - if (ret < 0) { - LOG(ERROR) << "ReadData failed"; - return ret; - } + chunk_t num_chunks_read = (offset >> BLOCK_SHIFT); + ret = ReadData(chunk + num_chunks_read, read_size); + if (ret < 0) { + LOG(ERROR) << "ReadData failed"; + return ret; } } + } - ssize_t written = WriteDmUserPayload(ret); - if (written < 0) return written; + ssize_t written = WriteDmUserPayload(ret); + if (written < 0) return written; - remaining_size -= ret; - offset += ret; - if (remaining_size) { - LOG(DEBUG) << "Write done ret: " << ret - << " remaining size: " << remaining_size; - bufsink_.GetHeaderPtr()->io_in_progress = 1; - } - } while (remaining_size); + remaining_size -= ret; + offset += ret; + if (remaining_size) { + LOG(DEBUG) << "Write done ret: " << ret + << " remaining size: " << remaining_size; + bufsink_.GetHeaderPtr()->io_in_progress = 1; + } + } while (remaining_size); - break; - } - - case DM_USER_MAP_WRITE: { - // TODO: After merge operation is completed, kernel issues write - // to flush all the exception mappings where the merge is - // completed. If dm-user routes the WRITE IO, we need to clear - // in-memory data structures representing those exception - // mappings. - abort(); - break; - } + break; } - LOG(DEBUG) << "read() finished, next message"; + case DM_USER_MAP_WRITE: { + // TODO: After merge operation is completed, kernel issues write + // to flush all the exception mappings where the merge is + // completed. If dm-user routes the WRITE IO, we need to clear + // in-memory data structures representing those exception + // mappings. + abort(); + break; + } } + LOG(DEBUG) << "read() finished, next message"; + return 0; } } // namespace snapshot } // namespace android -void run_thread(std::string cow_device, std::string backing_device) { - android::snapshot::Snapuserd snapd(cow_device, backing_device); - snapd.Run(); -} - int main([[maybe_unused]] int argc, char** argv) { android::base::InitLogging(argv, &android::base::KernelLogger); android::snapshot::Daemon& daemon = android::snapshot::Daemon::Instance(); - while (daemon.IsRunning()) { - // TODO: This is hardcoded wherein: - // argv[1] = system_cow, argv[2] = /dev/block/mapper/system_a - // argv[3] = product_cow, argv[4] = /dev/block/mapper/product_a - // - // This should be fixed based on some kind of IPC or setup a - // command socket and spin up the thread based when a new - // partition is visible. - std::thread system_a(run_thread, argv[1], argv[2]); - std::thread product_a(run_thread, argv[3], argv[4]); - - system_a.join(); - product_a.join(); - } + daemon.StartServer(argv[1]); + daemon.Run(); return 0; } diff --git a/fs_mgr/libsnapshot/snapuserd_client.cpp b/fs_mgr/libsnapshot/snapuserd_client.cpp new file mode 100644 index 000000000..b10de3572 --- /dev/null +++ b/fs_mgr/libsnapshot/snapuserd_client.cpp @@ -0,0 +1,261 @@ +#include <android-base/logging.h> +#include <libsnapshot/snapuserd_client.h> + +namespace android { +namespace snapshot { + +bool SnapuserdClient::ConnectToServerSocket(std::string socketname) { + sockfd_ = 0; + + sockfd_ = + socket_local_client(socketname.c_str(), ANDROID_SOCKET_NAMESPACE_RESERVED, SOCK_STREAM); + if (sockfd_ < 0) { + LOG(ERROR) << "Failed to connect to " << socketname; + return false; + } + + std::string msg = "query"; + + int sendRet = Sendmsg(msg.c_str(), msg.size()); + if (sendRet < 0) { + LOG(ERROR) << "Failed to send query message to snapuserd daemon with socket " << socketname; + DisconnectFromServer(); + return false; + } + + std::string str = Receivemsg(); + + if (str.find("fail") != std::string::npos) { + LOG(ERROR) << "Failed to receive message from snapuserd daemon with socket " << socketname; + DisconnectFromServer(); + return false; + } + + // If the daemon is passive then fallback to secondary active daemon. Daemon + // is passive during transition phase. Please see RestartSnapuserd() + if (str.find("passive") != std::string::npos) { + LOG(DEBUG) << "Snapuserd is passive with socket " << socketname; + DisconnectFromServer(); + return false; + } + + CHECK(str.find("active") != std::string::npos); + + return true; +} + +bool SnapuserdClient::ConnectToServer() { + if (ConnectToServerSocket(GetSocketNameFirstStage())) return true; + + if (ConnectToServerSocket(GetSocketNameSecondStage())) return true; + + return false; +} + +int SnapuserdClient::Sendmsg(const char* msg, size_t size) { + int numBytesSent = TEMP_FAILURE_RETRY(send(sockfd_, msg, size, 0)); + if (numBytesSent < 0) { + LOG(ERROR) << "Send failed " << strerror(errno); + return -1; + } + + if ((uint)numBytesSent < size) { + LOG(ERROR) << "Partial data sent " << strerror(errno); + return -1; + } + + return 0; +} + +std::string SnapuserdClient::Receivemsg() { + char msg[PACKET_SIZE]; + std::string msgStr("fail"); + int ret; + + ret = TEMP_FAILURE_RETRY(recv(sockfd_, msg, PACKET_SIZE, 0)); + if (ret <= 0) { + LOG(ERROR) << "recv failed " << strerror(errno); + return msgStr; + } + + msgStr.clear(); + msgStr = msg; + return msgStr; +} + +int SnapuserdClient::StopSnapuserd(bool firstStageDaemon) { + if (firstStageDaemon) { + sockfd_ = socket_local_client(GetSocketNameFirstStage().c_str(), + ANDROID_SOCKET_NAMESPACE_RESERVED, SOCK_STREAM); + if (sockfd_ < 0) { + LOG(ERROR) << "Failed to connect to " << GetSocketNameFirstStage(); + return -1; + } + } else { + if (!ConnectToServer()) { + LOG(ERROR) << "Failed to connect to socket " << GetSocketNameSecondStage(); + return -1; + } + } + + std::string msg = "stop"; + + int sendRet = Sendmsg(msg.c_str(), msg.size()); + if (sendRet < 0) { + LOG(ERROR) << "Failed to send stop message to snapuserd daemon"; + return -1; + } + + DisconnectFromServer(); + + return 0; +} + +int SnapuserdClient::StartSnapuserdaemon(std::string socketname) { + int retry_count = 0; + + if (fork() == 0) { + const char* argv[] = {"/system/bin/snapuserd", socketname.c_str(), nullptr}; + if (execv(argv[0], const_cast<char**>(argv))) { + LOG(ERROR) << "Failed to exec snapuserd daemon"; + return -1; + } + } + + // snapuserd is a daemon and will never exit; parent can't wait here + // to get the return code. Since Snapuserd starts the socket server, + // give it some time to fully launch. + // + // Try to connect to server to verify snapuserd server is started + while (retry_count < MAX_CONNECT_RETRY_COUNT) { + if (!ConnectToServer()) { + retry_count++; + std::this_thread::sleep_for(std::chrono::milliseconds(500)); + } else { + close(sockfd_); + return 0; + } + } + + LOG(ERROR) << "Failed to start snapuserd daemon"; + return -1; +} + +int SnapuserdClient::StartSnapuserd() { + if (StartSnapuserdaemon(GetSocketNameFirstStage()) < 0) return -1; + + return 0; +} + +int SnapuserdClient::InitializeSnapuserd(std::string cow_device, std::string backing_device) { + int ret = 0; + + if (!ConnectToServer()) { + LOG(ERROR) << "Failed to connect to server "; + return -1; + } + + std::string msg = "start," + cow_device + "," + backing_device; + + ret = Sendmsg(msg.c_str(), msg.size()); + if (ret < 0) { + LOG(ERROR) << "Failed to send message " << msg << " to snapuserd daemon"; + return -1; + } + + std::string str = Receivemsg(); + + if (str.find("fail") != std::string::npos) { + LOG(ERROR) << "Failed to receive ack for " << msg << " from snapuserd daemon"; + return -1; + } + + DisconnectFromServer(); + + LOG(DEBUG) << "Snapuserd daemon initialized with " << msg; + return 0; +} + +/* + * Transition from first stage snapuserd daemon to second stage daemon involves + * series of steps viz: + * + * 1: Create new dm-user devices - This is done by libsnapshot + * + * 2: Spawn the new snapuserd daemon - This is the second stage daemon which + * will start the server but the dm-user misc devices is not binded yet. + * + * 3: Vector to this function contains pair of cow_device and source device. + * Ex: {{system_cow,system_a}, {product_cow, product_a}, {vendor_cow, + * vendor_a}}. This vector will be populated by the libsnapshot. + * + * 4: Initialize the Second stage daemon passing the information from the + * vector. This will bind the daemon with dm-user misc device and will be ready + * to serve the IO. Up until this point, first stage daemon is still active. + * However, client library will mark the first stage daemon as passive and hence + * all the control message from hereon will be sent to active second stage + * daemon. + * + * 5: Create new dm-snapshot table. This is done by libsnapshot. When new table + * is created, kernel will issue metadata read once again which will be served + * by second stage daemon. However, any active IO will still be served by first + * stage daemon. + * + * 6: Swap the snapshot table atomically - This is done by libsnapshot. Once + * the swapping is done, all the IO will be served by second stage daemon. + * + * 7: Stop the first stage daemon. After this point second stage daemon is + * completely active to serve the IO and merging process. + * + */ +int SnapuserdClient::RestartSnapuserd(std::vector<std::pair<std::string, std::string>>& vec) { + // Connect to first-stage daemon and send a terminate-request control + // message. This will not terminate the daemon but will mark the daemon as + // passive. + if (!ConnectToServer()) { + LOG(ERROR) << "Failed to connect to server "; + return -1; + } + + std::string msg = "terminate-request"; + + int sendRet = Sendmsg(msg.c_str(), msg.size()); + if (sendRet < 0) { + LOG(ERROR) << "Failed to send message " << msg << " to snapuserd daemon"; + return -1; + } + + std::string str = Receivemsg(); + + if (str.find("fail") != std::string::npos) { + LOG(ERROR) << "Failed to receive ack for " << msg << " from snapuserd daemon"; + return -1; + } + + CHECK(str.find("success") != std::string::npos); + + DisconnectFromServer(); + + // Start the new daemon + if (StartSnapuserdaemon(GetSocketNameSecondStage()) < 0) { + LOG(ERROR) << "Failed to start new daemon at socket " << GetSocketNameSecondStage(); + return -1; + } + + LOG(DEBUG) << "Second stage Snapuserd daemon created successfully at socket " + << GetSocketNameSecondStage(); + CHECK(vec.size() % 2 == 0); + + for (int i = 0; i < vec.size(); i++) { + std::string& cow_device = vec[i].first; + std::string& base_device = vec[i].second; + + InitializeSnapuserd(cow_device, base_device); + LOG(DEBUG) << "Daemon initialized with " << cow_device << " and " << base_device; + } + + return 0; +} + +} // namespace snapshot +} // namespace android diff --git a/fs_mgr/libsnapshot/snapuserd_daemon.cpp b/fs_mgr/libsnapshot/snapuserd_daemon.cpp new file mode 100644 index 000000000..c1008b940 --- /dev/null +++ b/fs_mgr/libsnapshot/snapuserd_daemon.cpp @@ -0,0 +1,53 @@ +#include <android-base/logging.h> +#include <libsnapshot/snapuserd_daemon.h> + +namespace android { +namespace snapshot { + +int Daemon::StartServer(std::string socketname) { + int ret; + + ret = server_.Start(socketname); + if (ret < 0) { + LOG(ERROR) << "Snapuserd daemon failed to start..."; + exit(EXIT_FAILURE); + } + + return ret; +} + +Daemon::Daemon() { + is_running_ = true; + // TODO: Mask other signals - Bug 168258493 + signal(SIGINT, Daemon::SignalHandler); + signal(SIGTERM, Daemon::SignalHandler); +} + +bool Daemon::IsRunning() { + return is_running_; +} + +void Daemon::Run() { + while (IsRunning()) { + if (server_.AcceptClient() == static_cast<int>(DaemonOperations::STOP)) { + Daemon::Instance().is_running_ = false; + } + } +} + +void Daemon::SignalHandler(int signal) { + LOG(DEBUG) << "Snapuserd received signal: " << signal; + switch (signal) { + case SIGINT: + case SIGTERM: { + Daemon::Instance().is_running_ = false; + break; + } + default: + LOG(ERROR) << "Received unknown signal " << signal; + break; + } +} + +} // namespace snapshot +} // namespace android diff --git a/fs_mgr/libsnapshot/snapuserd_server.cpp b/fs_mgr/libsnapshot/snapuserd_server.cpp new file mode 100644 index 000000000..1e8b642d3 --- /dev/null +++ b/fs_mgr/libsnapshot/snapuserd_server.cpp @@ -0,0 +1,215 @@ +#include <android-base/logging.h> +#include <libsnapshot/snapuserd.h> +#include <libsnapshot/snapuserd_server.h> + +namespace android { +namespace snapshot { + +DaemonOperations SnapuserdServer::Resolveop(std::string& input) { + if (input == "start") return DaemonOperations::START; + if (input == "stop") return DaemonOperations::STOP; + if (input == "terminate-request") return DaemonOperations::TERMINATING; + if (input == "query") return DaemonOperations::QUERY; + + return DaemonOperations::INVALID; +} + +std::string SnapuserdServer::GetDaemonStatus() { + std::string msg = ""; + + if (IsTerminating()) + msg = "passive"; + else + msg = "active"; + + return msg; +} + +void SnapuserdServer::Parsemsg(std::string const& msg, const char delim, + std::vector<std::string>& out) { + std::stringstream ss(msg); + std::string s; + + while (std::getline(ss, s, delim)) { + out.push_back(s); + } +} + +// new thread +void SnapuserdServer::ThreadStart(std::string cow_device, std::string backing_device) { + Snapuserd snapd(cow_device, backing_device); + if (snapd.Init()) { + PLOG(ERROR) << "Snapuserd: Init failed"; + exit(EXIT_FAILURE); + } + + while (StopRequested() == false) { + int ret = snapd.Run(); + + if (ret == -ETIMEDOUT) continue; + + if (ret < 0) { + PLOG(ERROR) << "snapd.Run() failed..." << ret; + } + } +} + +void SnapuserdServer::ShutdownThreads() { + StopThreads(); + + for (auto& client : clients_vec_) { + auto& th = client->GetThreadHandler(); + + if (th->joinable()) th->join(); + } +} + +int SnapuserdServer::Sendmsg(int fd, char* msg, size_t size) { + int ret = TEMP_FAILURE_RETRY(send(fd, (char*)msg, size, 0)); + if (ret < 0) { + PLOG(ERROR) << "Snapuserd:server: send() failed"; + return -1; + } + + if (ret < size) { + PLOG(ERROR) << "Partial data sent"; + return -1; + } + + return 0; +} + +std::string SnapuserdServer::Recvmsg(int fd, int* ret) { + struct timeval tv; + fd_set set; + char msg[MAX_PACKET_SIZE]; + + tv.tv_sec = 2; + tv.tv_usec = 0; + FD_ZERO(&set); + FD_SET(fd, &set); + *ret = select(fd + 1, &set, NULL, NULL, &tv); + if (*ret == -1) { // select failed + return {}; + } else if (*ret == 0) { // timeout + return {}; + } else { + *ret = TEMP_FAILURE_RETRY(recv(fd, msg, MAX_PACKET_SIZE, 0)); + if (*ret < 0) { + PLOG(ERROR) << "Snapuserd:server: recv failed"; + return {}; + } else if (*ret == 0) { + LOG(DEBUG) << "Snapuserd client disconnected"; + return {}; + } else { + std::string str(msg); + return str; + } + } +} + +int SnapuserdServer::Receivemsg(int fd) { + char msg[MAX_PACKET_SIZE]; + std::unique_ptr<Client> newClient; + int ret = 0; + + while (1) { + memset(msg, '\0', MAX_PACKET_SIZE); + std::string str = Recvmsg(fd, &ret); + + if (ret <= 0) { + LOG(DEBUG) << "recv failed with ret: " << ret; + return 0; + } + + const char delim = ','; + + std::vector<std::string> out; + Parsemsg(str, delim, out); + DaemonOperations op = Resolveop(out[0]); + memset(msg, '\0', MAX_PACKET_SIZE); + + switch (op) { + case DaemonOperations::START: { + // Message format: + // start,<cow_device_path>,<source_device_path> + // + // Start the new thread which binds to dm-user misc device + newClient = std::make_unique<Client>(); + newClient->SetThreadHandler( + std::bind(&SnapuserdServer::ThreadStart, this, out[1], out[2])); + clients_vec_.push_back(std::move(newClient)); + sprintf(msg, "success"); + Sendmsg(fd, msg, MAX_PACKET_SIZE); + return 0; + } + case DaemonOperations::STOP: { + // Message format: stop + // + // Stop all the threads gracefully and then shutdown the + // main thread + ShutdownThreads(); + return static_cast<int>(DaemonOperations::STOP); + } + case DaemonOperations::TERMINATING: { + // Message format: terminate-request + // + // This is invoked during transition. First stage + // daemon will receive this request. First stage daemon + // will be considered as a passive daemon from hereon. + SetTerminating(); + sprintf(msg, "success"); + Sendmsg(fd, msg, MAX_PACKET_SIZE); + return 0; + } + case DaemonOperations::QUERY: { + // Message format: query + // + // As part of transition, Second stage daemon will be + // created before terminating the first stage daemon. Hence, + // for a brief period client may have to distiguish between + // first stage daemon and second stage daemon. + // + // Second stage daemon is marked as active and hence will + // be ready to receive control message. + std::string dstr = GetDaemonStatus(); + memcpy(msg, dstr.c_str(), dstr.size()); + Sendmsg(fd, msg, MAX_PACKET_SIZE); + if (dstr == "active") + break; + else + return 0; + } + default: { + sprintf(msg, "fail"); + Sendmsg(fd, msg, MAX_PACKET_SIZE); + return 0; + } + } + } +} + +int SnapuserdServer::Start(std::string socketname) { + sockfd_.reset(socket_local_server(socketname.c_str(), ANDROID_SOCKET_NAMESPACE_RESERVED, + SOCK_STREAM)); + if (sockfd_ < 0) { + PLOG(ERROR) << "Failed to create server socket " << socketname; + return -1; + } + + LOG(DEBUG) << "Snapuserd server successfully started with socket name " << socketname; + return 0; +} + +int SnapuserdServer::AcceptClient() { + int fd = accept(sockfd_.get(), NULL, NULL); + if (fd < 0) { + PLOG(ERROR) << "Socket accept failed: " << strerror(errno); + return -1; + } + + return Receivemsg(fd); +} + +} // namespace snapshot +} // namespace android |