diff --git a/CMake/Finde2fs.cmake b/CMake/Finde2fs.cmake index e5615a23..4c947fac 100644 --- a/CMake/Finde2fs.cmake +++ b/CMake/Finde2fs.cmake @@ -4,13 +4,15 @@ if(NOT ORIGIN_EXT2FS) FetchContent_Declare( e2fsprogs GIT_REPOSITORY https://github.com/data-accelerator/e2fsprogs.git - GIT_TAG b4cf6c751196a12b1df9a269d8e0b516b99fe6a7 + GIT_TAG 404deb95e6b0ed0ceb0148d289977e65bee7f8d0 ) FetchContent_GetProperties(e2fsprogs) if(NOT TARGET libext2fs_build) FetchContent_MakeAvailable(e2fsprogs) set(LIBEXT2FS_INSTALL_DIR ${e2fsprogs_SOURCE_DIR}/build/libext2fs CACHE STRING "") + set(E2FS_RESIZE_DIR ${e2fsprogs_SOURCE_DIR}/build/resize CACHE STRING "path to e2fsprogs resize build dir") + set(E2FS_INSTALL_LIB_DIR ${LIBEXT2FS_INSTALL_DIR}/lib CACHE STRING "path to e2fsprogs install-libs output") add_custom_command( OUTPUT ${LIBEXT2FS_INSTALL_DIR}/lib @@ -22,7 +24,8 @@ if(NOT ORIGIN_EXT2FS) set(E2FS_FOUND yes) set(E2FS_LIBRARY ${LIBEXT2FS_INSTALL_DIR}/lib/libext2fs.so) - set(E2FS_LIBRARIES ${E2FS_LIBRARY}) + set(E2FS_COM_ERR_LIBRARY ${e2fsprogs_SOURCE_DIR}/build/lib/libcom_err.a) + set(E2FS_LIBRARIES ${E2FS_LIBRARY} ${E2FS_COM_ERR_LIBRARY}) set(E2FS_INCLUDE_DIR ${LIBEXT2FS_INSTALL_DIR}/include) set(E2FS_INCLUDE_DIRS ${E2FS_INCLUDE_DIR}) diff --git a/CMake/Findphoton.cmake b/CMake/Findphoton.cmake index cdc72d27..c0ade630 100644 --- a/CMake/Findphoton.cmake +++ b/CMake/Findphoton.cmake @@ -1,11 +1,13 @@ include(FetchContent) set(FETCHCONTENT_QUIET false) set(PHOTON_ENABLE_EXTFS ON) +set(PHOTON_ENABLE_RESIZE ON) +add_definitions(-DPHOTON_ENABLE_RESIZE) FetchContent_Declare( photon GIT_REPOSITORY https://github.com/alibaba/PhotonLibOS.git - GIT_TAG v0.6.17 + GIT_TAG 0178d14499d8639759a81e32ad58da5226df5e9b ) if(BUILD_TESTING) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 857b250f..44b0030c 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -33,6 +33,18 @@ target_link_libraries(overlaybd_image_lib ${AIO_LIBRARIES} ) +# Propagate resize .o files to all consumers of overlaybd_image_lib. +# These provide the resize_fs() symbol needed by photon's resize_extfs(). +if (NOT ORIGIN_EXT2FS) + target_link_libraries(overlaybd_image_lib + ${E2FS_RESIZE_DIR}/resize2fs.o + ${E2FS_RESIZE_DIR}/extent.o + ${E2FS_RESIZE_DIR}/resource_track.o + ${E2FS_LIBRARIES} + ) + add_dependencies(overlaybd_image_lib libext2fs_build) +endif() + add_executable(overlaybd-tcmu main.cpp ) diff --git a/src/image_file.cpp b/src/image_file.cpp index 1281e74b..20f2b1f9 100644 --- a/src/image_file.cpp +++ b/src/image_file.cpp @@ -36,6 +36,9 @@ #include "overlaybd/gzip/gz.h" #include "overlaybd/gzindex/gzfile.h" #include "overlaybd/tar/tar_file.h" +#ifdef PHOTON_ENABLE_RESIZE +#include +#endif #define PARALLEL_LOAD_INDEX 32 using namespace photon::fs; @@ -614,3 +617,59 @@ int ImageFile::create_snapshot(const char *new_config_path) { return 0; } + +int ImageFile::resize(uint64_t target_size, bool resize_fs) { + if (read_only) { + LOG_ERROR_RETURN(EROFS, -1, "cannot resize a read-only image (no upper layer)"); + } + + auto rw = (LSMT::IFileRW *)m_file; + struct stat st; + if (m_file->fstat(&st) != 0) { + LOG_ERRNO_RETURN(0, -1, "fstat failed"); + } + uint64_t current = (uint64_t)st.st_size; + + if (target_size == current && !resize_fs) { + LOG_INFO("vsize already equals target (` bytes), nothing to do", target_size); + return 0; + } + + if (target_size > current) { + // Expand: grow block device first, then filesystem + if (rw->update_vsize(target_size) != 0) { + LOG_ERROR_RETURN(0, -1, "failed to update vsize for expand"); + } + } + + if (resize_fs) { +#ifdef PHOTON_ENABLE_RESIZE + if (photon::fs::resize_extfs(m_file, target_size, 0) != 0) { + if (target_size >= current) { + LOG_ERROR("resize_extfs failed. " + "Re-run to retry or use e2fsck to recover."); + } else { + LOG_ERROR("resize_extfs failed during shrink."); + } + return -1; + } +#else + LOG_ERROR_RETURN(ENOSYS, -1, "resize_fs not supported (built without PHOTON_ENABLE_RESIZE)"); +#endif + } + + if (target_size < current) { + // Shrink: shrinking without resizing the filesystem is unsafe. + if (!resize_fs) { + LOG_ERROR_RETURN(EINVAL, -1, "cannot shrink without resizing filesystem"); + } + // Shrink: update vsize after filesystem is shrunk + if (rw->update_vsize(target_size) != 0) { + LOG_ERROR("filesystem resized but failed to update vsize. Re-run to retry."); + return -1; + } + } + + LOG_INFO("resize done: ` -> ` bytes", current, target_size); + return 0; +} diff --git a/src/image_file.h b/src/image_file.h index f5870963..196f3abf 100644 --- a/src/image_file.h +++ b/src/image_file.h @@ -121,6 +121,11 @@ class ImageFile : public photon::fs::ForwardFile { int create_snapshot(const char *new_config_path); + // Resize the block device (and optionally the ext4 filesystem). + // target_size: new block device size in bytes. + // resize_fs: if true, also resize the ext4 filesystem to match. + int resize(uint64_t target_size, bool resize_fs = false); + private: Prefetcher *m_prefetcher = nullptr; ImageConfigNS::ImageConfig conf; diff --git a/src/test/CMakeLists.txt b/src/test/CMakeLists.txt index 15336c9f..7fa107e5 100644 --- a/src/test/CMakeLists.txt +++ b/src/test/CMakeLists.txt @@ -49,3 +49,28 @@ add_test( NAME trace_test COMMAND ${EXECUTABLE_OUTPUT_PATH}/trace_test ) + +if (NOT ORIGIN_EXT2FS) + set_source_files_properties( + ${E2FS_RESIZE_DIR}/resize2fs.o + ${E2FS_RESIZE_DIR}/extent.o + ${E2FS_RESIZE_DIR}/resource_track.o + PROPERTIES GENERATED TRUE EXTERNAL_OBJECT TRUE) + add_executable(resize_test + resize_test.cpp + ${E2FS_RESIZE_DIR}/resize2fs.o + ${E2FS_RESIZE_DIR}/extent.o + ${E2FS_RESIZE_DIR}/resource_track.o + ) + target_include_directories(resize_test PUBLIC + ${PHOTON_INCLUDE_DIR} + ${E2FS_INCLUDE_DIRS} + ${E2FS_RESIZE_DIR} + ) + target_link_libraries(resize_test gtest pthread photon_static ${E2FS_LIBRARIES}) + add_dependencies(resize_test libext2fs_build) + add_test( + NAME resize_test + COMMAND ${EXECUTABLE_OUTPUT_PATH}/resize_test + ) +endif() diff --git a/src/test/resize_test.cpp b/src/test/resize_test.cpp new file mode 100644 index 00000000..a6ecc072 --- /dev/null +++ b/src/test/resize_test.cpp @@ -0,0 +1,369 @@ +/* + Copyright The Overlaybd Authors + + 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. +*/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define MB (1024ULL * 1024) +#define DATA_PATTERN "abcdefghijklmnopqrstuvwxyz012345" +#define DATA_PATTERN_LEN 32 + +static std::string TEST_IMG = std::string("/tmp/resize_test_") + std::to_string(::getpid()) + ".img"; + +static photon::fs::IFile *create_image(uint64_t size_mb) { + auto file = photon::fs::open_localfile_adaptor( + TEST_IMG.c_str(), O_RDWR | O_CREAT | O_TRUNC, 0644, 0); + if (!file) return nullptr; + if (file->ftruncate(size_mb * MB) < 0) { + delete file; + return nullptr; + } + if (photon::fs::make_extfs(file) < 0) { + delete file; + return nullptr; + } + return file; +} + +static void cleanup() { + ::unlink(TEST_IMG.c_str()); +} + +static int write_data_file(photon::fs::IFileSystem *extfs, const char *path, + size_t size) { + auto f = extfs->open(path, O_RDWR | O_CREAT | O_TRUNC, 0644); + if (!f) return -1; + std::string data; + while (data.size() < size) + data.append(DATA_PATTERN, DATA_PATTERN_LEN); + data.resize(size); + auto ret = f->pwrite(data.data(), data.size(), 0); + delete f; + return (ret == (ssize_t)data.size()) ? 0 : -1; +} + +static int verify_data_file(photon::fs::IFileSystem *extfs, const char *path, + size_t size) { + auto f = extfs->open(path, O_RDONLY, 0); + if (!f) return -1; + std::string expected; + while (expected.size() < size) + expected.append(DATA_PATTERN, DATA_PATTERN_LEN); + expected.resize(size); + std::string actual(size, '\0'); + auto ret = f->pread(&actual[0], size, 0); + delete f; + if (ret != (ssize_t)size) return -1; + return (actual == expected) ? 0 : -1; +} + +class ResizeTest : public ::testing::Test { +protected: + void TearDown() override { + cleanup(); + } +}; + +TEST_F(ResizeTest, BasicShrinkExpand) { + auto file = create_image(256); + ASSERT_NE(nullptr, file); + + auto extfs = photon::fs::new_extfs(file); + ASSERT_NE(nullptr, extfs); + ASSERT_EQ(0, write_data_file(extfs, "/testdata", 2 * MB)); + delete extfs; + + ASSERT_EQ(0, photon::fs::resize_extfs(file, 128 * MB)); + + extfs = photon::fs::new_extfs(file); + ASSERT_NE(nullptr, extfs); + ASSERT_EQ(0, verify_data_file(extfs, "/testdata", 2 * MB)); + struct statvfs st; + ASSERT_EQ(0, extfs->statvfs("/", &st)); + EXPECT_LE(st.f_blocks * st.f_bsize, 128 * MB); + delete extfs; + + file->ftruncate(512 * MB); + ASSERT_EQ(0, photon::fs::resize_extfs(file, 512 * MB)); + + extfs = photon::fs::new_extfs(file); + ASSERT_NE(nullptr, extfs); + ASSERT_EQ(0, verify_data_file(extfs, "/testdata", 2 * MB)); + ASSERT_EQ(0, extfs->statvfs("/", &st)); + EXPECT_GE(st.f_blocks * st.f_bsize, 400 * MB); + delete extfs; + + delete file; +} + +TEST_F(ResizeTest, ShrinkThenExpand) { + auto file = create_image(256); + ASSERT_NE(nullptr, file); + + file->ftruncate(512 * MB); + ASSERT_EQ(0, photon::fs::resize_extfs(file, 512 * MB)); + + ASSERT_EQ(0, photon::fs::resize_extfs(file, 128 * MB)); + + file->ftruncate(512 * MB); + ASSERT_EQ(0, photon::fs::resize_extfs(file, 512 * MB)); + + auto extfs = photon::fs::new_extfs(file); + ASSERT_NE(nullptr, extfs); + ASSERT_EQ(0, write_data_file(extfs, "/after_reexpand", 1 * MB)); + ASSERT_EQ(0, verify_data_file(extfs, "/after_reexpand", 1 * MB)); + delete extfs; + + delete file; +} + +TEST_F(ResizeTest, ShrinkWithRelocation) { + auto file = create_image(256); + ASSERT_NE(nullptr, file); + + auto extfs = photon::fs::new_extfs(file); + ASSERT_NE(nullptr, extfs); + extfs->mkdir("/data", 0755); + for (int i = 0; i < 15; i++) { + char path[64]; + snprintf(path, sizeof(path), "/data/fill_%03d", i); + ASSERT_EQ(0, write_data_file(extfs, path, 4 * MB)); + } + delete extfs; + + ASSERT_EQ(0, photon::fs::resize_extfs(file, 128 * MB)); + + extfs = photon::fs::new_extfs(file); + ASSERT_NE(nullptr, extfs); + struct statvfs st; + ASSERT_EQ(0, extfs->statvfs("/", &st)); + EXPECT_LE(st.f_blocks * st.f_bsize, 128 * MB); + for (int i = 0; i < 15; i++) { + char path[64]; + snprintf(path, sizeof(path), "/data/fill_%03d", i); + ASSERT_EQ(0, verify_data_file(extfs, path, 4 * MB)); + } + delete extfs; + + delete file; +} + +TEST_F(ResizeTest, ShrinkTooSmall) { + auto file = create_image(256); + ASSERT_NE(nullptr, file); + + auto extfs = photon::fs::new_extfs(file); + ASSERT_NE(nullptr, extfs); + ASSERT_EQ(0, write_data_file(extfs, "/bigdata", 100 * MB)); + delete extfs; + + EXPECT_NE(0, photon::fs::resize_extfs(file, 64 * MB)); + + extfs = photon::fs::new_extfs(file); + ASSERT_NE(nullptr, extfs); + ASSERT_EQ(0, verify_data_file(extfs, "/bigdata", 100 * MB)); + delete extfs; + + delete file; +} + +TEST_F(ResizeTest, AllGroupsOccupied) { + auto file = create_image(256); + ASSERT_NE(nullptr, file); + + auto extfs = photon::fs::new_extfs(file); + ASSERT_NE(nullptr, extfs); + extfs->mkdir("/data", 0755); + int nfiles = 0; + for (uint64_t offset_mb = 0; offset_mb < 240; offset_mb += 32) { + char path[64]; + snprintf(path, sizeof(path), "/data/file_%03d", nfiles); + auto f = extfs->open(path, O_RDWR | O_CREAT | O_TRUNC, 0644); + ASSERT_NE(nullptr, f); + std::string chunk(4 * MB, 'A' + (nfiles % 26)); + ASSERT_EQ((ssize_t)chunk.size(), f->pwrite(chunk.data(), chunk.size(), 0)); + delete f; + nfiles++; + } + delete extfs; + + ASSERT_EQ(0, photon::fs::resize_extfs(file, 200 * MB)); + + extfs = photon::fs::new_extfs(file); + ASSERT_NE(nullptr, extfs); + for (int i = 0; i < nfiles; i++) { + char path[64]; + snprintf(path, sizeof(path), "/data/file_%03d", i); + auto f = extfs->open(path, O_RDONLY, 0); + ASSERT_NE(nullptr, f); + char buf[32]; + auto ret = f->pread(buf, 32, 0); + EXPECT_EQ(32, ret); + if (ret == 32) { + char expected = 'A' + (i % 26); + EXPECT_EQ(expected, buf[0]); + } + delete f; + } + struct statvfs st; + ASSERT_EQ(0, extfs->statvfs("/", &st)); + EXPECT_LE(st.f_blocks * st.f_bsize, 200 * MB); + delete extfs; + + file->ftruncate(512 * MB); + ASSERT_EQ(0, photon::fs::resize_extfs(file, 512 * MB)); + + extfs = photon::fs::new_extfs(file); + ASSERT_NE(nullptr, extfs); + ASSERT_EQ(0, write_data_file(extfs, "/data/after_expand", 1 * MB)); + ASSERT_EQ(0, verify_data_file(extfs, "/data/after_expand", 1 * MB)); + delete extfs; + + delete file; +} + +TEST_F(ResizeTest, ShrinkToMinimum) { + auto file = create_image(256); + ASSERT_NE(nullptr, file); + + auto extfs = photon::fs::new_extfs(file); + ASSERT_NE(nullptr, extfs); + ASSERT_EQ(0, write_data_file(extfs, "/data", 10 * MB)); + delete extfs; + + ASSERT_EQ(0, photon::fs::resize_extfs(file, 128 * MB)); + + extfs = photon::fs::new_extfs(file); + ASSERT_NE(nullptr, extfs); + ASSERT_EQ(0, verify_data_file(extfs, "/data", 10 * MB)); + struct statvfs st; + ASSERT_EQ(0, extfs->statvfs("/", &st)); + EXPECT_LE(st.f_blocks * st.f_bsize, 128 * MB); + delete extfs; + + delete file; +} + +TEST_F(ResizeTest, ExpandThenWrite) { + auto file = create_image(128); + ASSERT_NE(nullptr, file); + + file->ftruncate(512 * MB); + ASSERT_EQ(0, photon::fs::resize_extfs(file, 512 * MB)); + + auto extfs = photon::fs::new_extfs(file); + ASSERT_NE(nullptr, extfs); + ASSERT_EQ(0, write_data_file(extfs, "/new_space_data", 200 * MB)); + ASSERT_EQ(0, verify_data_file(extfs, "/new_space_data", 200 * MB)); + struct statvfs st; + ASSERT_EQ(0, extfs->statvfs("/", &st)); + EXPECT_GE(st.f_blocks * st.f_bsize, 400 * MB); + delete extfs; + + delete file; +} + +TEST_F(ResizeTest, MultipleResizes) { + auto file = create_image(512); + ASSERT_NE(nullptr, file); + + auto extfs = photon::fs::new_extfs(file); + ASSERT_NE(nullptr, extfs); + ASSERT_EQ(0, write_data_file(extfs, "/persistent", 4 * MB)); + delete extfs; + + ASSERT_EQ(0, photon::fs::resize_extfs(file, 256 * MB)); + file->ftruncate(1024 * MB); + ASSERT_EQ(0, photon::fs::resize_extfs(file, 1024 * MB)); + ASSERT_EQ(0, photon::fs::resize_extfs(file, 128 * MB)); + file->ftruncate(512 * MB); + ASSERT_EQ(0, photon::fs::resize_extfs(file, 512 * MB)); + + extfs = photon::fs::new_extfs(file); + ASSERT_NE(nullptr, extfs); + ASSERT_EQ(0, verify_data_file(extfs, "/persistent", 4 * MB)); + delete extfs; + + delete file; +} + +TEST_F(ResizeTest, EmptyShrinkToMinimum) { + auto file = create_image(512); + ASSERT_NE(nullptr, file); + + ASSERT_EQ(0, photon::fs::resize_extfs(file, 128 * MB)); + + auto extfs = photon::fs::new_extfs(file); + ASSERT_NE(nullptr, extfs); + struct statvfs st; + ASSERT_EQ(0, extfs->statvfs("/", &st)); + EXPECT_LE(st.f_blocks * st.f_bsize, 128 * MB); + ASSERT_EQ(0, write_data_file(extfs, "/after_shrink", 1 * MB)); + ASSERT_EQ(0, verify_data_file(extfs, "/after_shrink", 1 * MB)); + delete extfs; + + delete file; +} + +TEST_F(ResizeTest, ShrinkReducesGroups) { + auto file = create_image(512); + ASSERT_NE(nullptr, file); + + auto extfs = photon::fs::new_extfs(file); + ASSERT_NE(nullptr, extfs); + extfs->mkdir("/data", 0755); + for (int i = 0; i < 20; i++) { + char path[64]; + snprintf(path, sizeof(path), "/data/file_%03d", i); + ASSERT_EQ(0, write_data_file(extfs, path, 1 * MB)); + } + delete extfs; + + ASSERT_EQ(0, photon::fs::resize_extfs(file, 128 * MB)); + + extfs = photon::fs::new_extfs(file); + ASSERT_NE(nullptr, extfs); + for (int i = 0; i < 20; i++) { + char path[64]; + snprintf(path, sizeof(path), "/data/file_%03d", i); + ASSERT_EQ(0, verify_data_file(extfs, path, 1 * MB)); + } + struct statvfs st; + ASSERT_EQ(0, extfs->statvfs("/", &st)); + EXPECT_LE(st.f_blocks * st.f_bsize, 128 * MB); + delete extfs; + + delete file; +} + +int main(int argc, char **argv) { + photon::init(photon::INIT_EVENT_DEFAULT, photon::INIT_IO_DEFAULT); + set_log_output_level(1); + ::testing::InitGoogleTest(&argc, argv); + auto ret = RUN_ALL_TESTS(); + photon::fini(); + return ret; +} diff --git a/src/tools/CMakeLists.txt b/src/tools/CMakeLists.txt index 6e821eaa..43c13733 100644 --- a/src/tools/CMakeLists.txt +++ b/src/tools/CMakeLists.txt @@ -30,6 +30,14 @@ add_library(checksum_lib sha256file.cpp) target_include_directories(checksum_lib PUBLIC ${PHOTON_INCLUDE_DIR}) target_link_libraries(checksum_lib photon_static) +# overlaybd-resize: userspace ext4 resize for overlaybd images +if (NOT ORIGIN_EXT2FS) + add_executable(overlaybd-resize overlaybd-resize.cpp) + target_include_directories(overlaybd-resize PUBLIC ${PHOTON_INCLUDE_DIR}) + target_link_libraries(overlaybd-resize photon_static overlaybd_image_lib) + set_target_properties(overlaybd-resize PROPERTIES INSTALL_RPATH "/opt/overlaybd/lib") +endif() + install(TARGETS overlaybd-commit overlaybd-create @@ -40,3 +48,7 @@ install(TARGETS turboOCI-apply DESTINATION /opt/overlaybd/bin ) + +if (NOT ORIGIN_EXT2FS) + install(TARGETS overlaybd-resize DESTINATION /opt/overlaybd/bin) +endif() diff --git a/src/tools/overlaybd-resize.cpp b/src/tools/overlaybd-resize.cpp new file mode 100644 index 00000000..e838e3bb --- /dev/null +++ b/src/tools/overlaybd-resize.cpp @@ -0,0 +1,118 @@ +/* + Copyright The Overlaybd Authors + + 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. +*/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include "comm_func.h" +#include "CLI11.hpp" +#include "../image_file.h" + +static uint64_t parse_size_gb(const std::string &s) { + if (!s.empty() && s[0] == '-') { + fprintf(stderr, "size must be positive: %s\n", s.c_str()); + return 0; + } + char *end = nullptr; + errno = 0; + uint64_t val = strtoull(s.c_str(), &end, 10); + if (errno == ERANGE) { + fprintf(stderr, "size value out of range: %s\n", s.c_str()); + return 0; + } + if (end == s.c_str() || *end != '\0') { + fprintf(stderr, "invalid size (expected integer in GB): %s\n", s.c_str()); + return 0; + } + if (val == 0) { + fprintf(stderr, "size must be greater than 0\n"); + return 0; + } + if (val > UINT64_MAX / (1024ULL * 1024 * 1024)) { + fprintf(stderr, "size overflow: %s GB\n", s.c_str()); + return 0; + } + return val * 1024ULL * 1024 * 1024; +} + +int main(int argc, char **argv) { + std::string config_path, size_str, service_config_path; + bool verbose = false; + + CLI::App app{"overlaybd-resize: resize ext4 filesystem on overlaybd image in userspace"}; + app.add_option("--config", config_path, + "overlaybd image config (config.v1.json)") + ->type_name("FILEPATH") + ->check(CLI::ExistingFile) + ->required(); + app.add_option("--size", size_str, + "target filesystem size in GB (e.g. 8, 500)") + ->required(); + app.add_option("--service_config_path", service_config_path, + "overlaybd service config") + ->type_name("FILEPATH") + ->default_val("/etc/overlaybd/overlaybd.json"); + app.add_flag("--verbose", verbose, "output debug info")->default_val(false); + CLI11_PARSE(app, argc, argv); + + uint64_t target_size = parse_size_gb(size_str); + if (target_size == 0) { + fprintf(stderr, "invalid size: %s\n", size_str.c_str()); + return 1; + } + + set_log_output_level(verbose ? 0 : 1); + if (photon::init(photon::INIT_EVENT_DEFAULT, photon::INIT_IO_DEFAULT) != 0) { + fprintf(stderr, "failed to initialize photon\n"); + return 1; + } + DEFER({ photon::fini(); }); + + ImageService *imgservice = create_image_service(service_config_path.c_str()); + if (!imgservice) { + fprintf(stderr, "failed to create image service\n"); + return 1; + } + DEFER({ delete imgservice; }); + + auto imgfile = (ImageFile *)imgservice->create_image_file(config_path.c_str(), ""); + if (!imgfile) { + fprintf(stderr, "failed to open overlaybd image\n"); + return 1; + } + DEFER({ delete imgfile; }); + + struct timeval t_start, t_end; + gettimeofday(&t_start, NULL); + + int ret = imgfile->resize(target_size, true); + if (ret != 0) { + fprintf(stderr, "resize failed\n"); + return 1; + } + + gettimeofday(&t_end, NULL); + double elapsed = (t_end.tv_sec - t_start.tv_sec) + (t_end.tv_usec - t_start.tv_usec) / 1000000.0; + + printf("Success: filesystem resized to %llu GB (%.3f seconds)\n", + (unsigned long long)(target_size / 1024 / 1024 / 1024), elapsed); + return 0; +}