Merge "libsnapshot:snapuserd: Transitions for snapuserd" am: e19df50b75

Original change: https://android-review.googlesource.com/c/platform/system/core/+/1437277

Change-Id: I50d4f6d0bc2ad83cf7c8782df637ce027206dabd
This commit is contained in:
Akilesh Kailash 2020-10-02 21:03:52 +00:00 committed by Automerger Merge Worker
commit 47014a7514
11 changed files with 1200 additions and 354 deletions

View file

@ -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: [

View file

@ -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

View file

@ -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;
using android::base::unique_fd;
static constexpr uint32_t SNAPSHOT_DISK_VERSION = 1;
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; }
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[];
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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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;
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 Daemon::SignalHandler(int signal) {
LOG(DEBUG) << "Snapuserd received signal: " << signal;
switch (signal) {
case SIGINT:
case SIGTERM: {
Daemon::Instance().is_running_ = false;
break;
}
void* BufferSink::GetBuffer(size_t requested, size_t* actual) {
void* buf = GetPayloadBuffer(requested);
if (!buf) {
*actual = 0;
return nullptr;
}
*actual = requested;
return buf;
}
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;
}
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();
return 0;
}
bufsink_.Clear();
int Snapuserd::Run() {
int ret = 0;
ret = ReadDmUserHeader();
if (ret < 0) return ret;
struct dm_user_header* header = bufsink_.GetHeaderPtr();
LOG(DEBUG) << "dm-user returned " << ret << " bytes";
bufsink_.Clear();
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;
ret = ReadDmUserHeader();
if (ret < 0) return ret;
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);
LOG(DEBUG) << "dm-user returned " << ret << " bytes";
// 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;
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;
}
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 {
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;
}

View file

@ -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

View file

@ -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

View file

@ -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