aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--CMakeLists.txt8
-rw-r--r--README.md11
-rw-r--r--include/jau/file_util.hpp41
-rw-r--r--scripts/build.sh1
-rwxr-xr-xscripts/mksquashfs_test_data.sh7
-rw-r--r--src/file_util.cpp140
-rw-r--r--test/CMakeLists.txt32
-rw-r--r--test/testsudo_fileutils02.cpp61
-rw-r--r--test_data.sqfsbin0 -> 4096 bytes
9 files changed, 293 insertions, 8 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 853967b..488bfd4 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -36,6 +36,14 @@ include_directories (${SYSTEM_USR_DIR})
add_subdirectory (src)
+message(STATUS "${PROJECT_NAME} TEST_WITH_SUDO = ${TEST_WITH_SUDO} (pre-set)")
+if(NOT DEFINED TEST_WITH_SUDO)
+ set(TEST_WITH_SUDO OFF)
+ message(STATUS "${PROJECT_NAME} TEST_WITH_SUDO ${TEST_WITH_SUDO} (default)")
+else()
+ message(STATUS "${PROJECT_NAME} TEST_WITH_SUDO ${TEST_WITH_SUDO} (user)")
+endif()
+
IF(BUILDJAVA)
configure_file (${CMAKE_CURRENT_SOURCE_DIR}/java_base/manifest.txt.in ${CMAKE_CURRENT_BINARY_DIR}/java_base/manifest.txt)
configure_file (${CMAKE_CURRENT_SOURCE_DIR}/java_jni/manifest.txt.in ${CMAKE_CURRENT_BINARY_DIR}/java_jni/manifest.txt)
diff --git a/README.md b/README.md
index 46357e3..3285f93 100644
--- a/README.md
+++ b/README.md
@@ -114,6 +114,17 @@ Building debug build:
-DDEBUG=ON
~~~~~~~~~~~~~
+Add unit tests to build (default: disabled)
+~~~~~~~~~~~~~
+-DBUILD_TESTING=ON
+~~~~~~~~~~~~~
+
+Add unit tests requiring `sudo` to build (default: disabled),
+this option required `-DBUILD_TESTING=ON` to be effective.
+~~~~~~~~~~~~~
+-DTEST_WITH_SUDO=ON
+~~~~~~~~~~~~~
+
Disable stripping native lib even in non debug build:
~~~~~~~~~~~~~
-DUSE_STRIP=OFF
diff --git a/include/jau/file_util.hpp b/include/jau/file_util.hpp
index efd04f0..0b119d8 100644
--- a/include/jau/file_util.hpp
+++ b/include/jau/file_util.hpp
@@ -851,6 +851,47 @@ namespace jau {
*/
bool copy(const std::string& source_path, const std::string& dest_path, const copy_options copts = copy_options::none) noexcept;
+ struct mount_ctx {
+ bool mounted;
+ std::string mount_point;
+ int loop_device_id;
+
+ mount_ctx(const std::string& mount_point_, const int loop_device_id_)
+ : mounted(true), mount_point(mount_point_), loop_device_id(loop_device_id_) {}
+
+ mount_ctx()
+ : mounted(false), mount_point(), loop_device_id(-1) {}
+ };
+
+ /**
+ * Attach the filesystem image named in `image_path` to `target_path`.
+ *
+ * This method requires root permissions.
+ *
+ * @param image_path path of image source file
+ * @param mount_point directory where `image_path` shall be attached to
+ * @param fs_type type of filesystem, e.g. `squashfs`, `tmpfs`, `iso9660`, etc.
+ * @param mountflags mount flags, e.g. `MS_LAZYTIME | MS_NOATIME | MS_RDONLY` for a read-only lazy-time and no-atime filesystem.
+ * @param fs_options special filesystem options
+ * @return mount_ctx structure containing mounted status etc
+ *
+ * @see umount()
+ */
+ mount_ctx mount_image(const std::string& image_path, const std::string& mount_point, const std::string& fs_type,
+ const unsigned long mountflags, const std::string fs_options="");
+
+ /**
+ * Detach the given mount_ctc `context`
+ *
+ * This method requires root permissions.
+ *
+ * @param context mount_ctx previously attached via mount_image()
+ * @return true if successful, otherwise false
+ *
+ * @see mount_image()
+ */
+ bool umount(const mount_ctx& context);
+
/**@}*/
} /* namespace fs */
diff --git a/scripts/build.sh b/scripts/build.sh
index 696a0e7..f39f550 100644
--- a/scripts/build.sh
+++ b/scripts/build.sh
@@ -41,6 +41,7 @@ buildit() {
# CLANG_ARGS="-DCMAKE_C_COMPILER=/usr/bin/clang -DCMAKE_CXX_COMPILER=/usr/bin/clang++"
cmake $CLANG_ARGS -DCMAKE_INSTALL_PREFIX=$rootdir/dist-$archabi -DBUILDJAVA=ON -DBUILD_TESTING=ON -DUSE_LIBUNWIND=ON -DUSE_LIBCURL=ON ..
+ # cmake $CLANG_ARGS -DCMAKE_INSTALL_PREFIX=$rootdir/dist-$archabi -DBUILDJAVA=ON -DBUILD_TESTING=ON -DUSE_LIBUNWIND=ON -DUSE_LIBCURL=ON -DTEST_WITH_SUDO=ON ..
# cmake $CLANG_ARGS -DCMAKE_INSTALL_PREFIX=$rootdir/dist-$archabi -DBUILDJAVA=ON -DBUILD_TESTING=ON -DDEBUG=ON -DUSE_LIBUNWIND=ON -DUSE_LIBCURL=ON ..
# cmake $CLANG_ARGS -DCMAKE_INSTALL_PREFIX=$rootdir/dist-$archabi -DBUILDJAVA=ON -DBUILD_TESTING=ON ..
diff --git a/scripts/mksquashfs_test_data.sh b/scripts/mksquashfs_test_data.sh
new file mode 100755
index 0000000..59980b1
--- /dev/null
+++ b/scripts/mksquashfs_test_data.sh
@@ -0,0 +1,7 @@
+#! /bin/sh
+
+sdir=`dirname $(readlink -f $0)`
+rootdir=`dirname $sdir`
+
+cd $rootdir
+/usr/bin/mksquashfs test_data test_data.sqfs -comp lzo
diff --git a/src/file_util.cpp b/src/file_util.cpp
index 2db5318..c95a217 100644
--- a/src/file_util.cpp
+++ b/src/file_util.cpp
@@ -34,8 +34,10 @@ extern "C" {
#include <sys/stat.h>
#ifdef __linux__
#include <sys/sendfile.h>
+ #include <linux/loop.h>
#endif
#include <sys/types.h>
+ #include <sys/mount.h>
#include <dirent.h>
#include <fcntl.h>
#include <unistd.h>
@@ -1392,3 +1394,141 @@ bool jau::fs::copy(const std::string& source_path, const std::string& target_pat
return jau::fs::visit(source_stats, topts, pv);
}
+jau::fs::mount_ctx jau::fs::mount_image(const std::string& image_path, const std::string& mount_point, const std::string& fs_type,
+ const unsigned long mountflags, const std::string fs_options)
+{
+ file_stats image_stats(image_path);
+ if( !image_stats.is_file()) {
+ return mount_ctx();
+ }
+ file_stats target_stats(mount_point);
+ if( !target_stats.is_dir()) {
+ return mount_ctx();
+ }
+ int backingfile = ::open64(image_stats.path().c_str(), O_RDWR);
+ if( 0 > backingfile ) {
+ jau::fprintf_td(stderr, "mount: Error: Couldn't open image-file '%s': res %d, errno %d (%s)\n",
+ image_stats.to_string().c_str(), backingfile, errno, ::strerror(errno));
+ return mount_ctx();
+ }
+#ifdef __linux__
+ int loop_device_id = -1;
+ int loop_ctl_fd = -1, loop_device_fd = -1;
+ char loopname[4096];
+ void* fs_options_cstr = nullptr;
+ int mount_res = -1;
+
+ loop_ctl_fd = ::open64("/dev/loop-control", O_RDWR);
+ if( 0 > loop_ctl_fd ) {
+ jau::fprintf_td(stderr, "mount: Error: Couldn't open loop-control: res %d, errno %d (%s)\n",
+ loop_ctl_fd, errno, ::strerror(errno));
+ goto errout;
+ }
+
+ loop_device_id = (int) ::ioctl(loop_ctl_fd, LOOP_CTL_GET_FREE);
+ if( 0 > loop_device_id ) {
+ jau::fprintf_td(stderr, "mount: Error: Couldn't get free loop-device: res %d, errno %d (%s)\n",
+ loop_device_id, errno, ::strerror(errno));
+ goto errout;
+ }
+ ::close(loop_ctl_fd);
+ loop_ctl_fd = -1;
+
+ snprintf(loopname, sizeof(loopname), "/dev/loop%d", loop_device_id);
+ jau::fprintf_td(stderr, "mount: Info: Using loop-device '%s'\n", loopname);
+
+ loop_device_fd = ::open64(loopname, O_RDWR);
+ if( 0 > loop_device_fd ) {
+ jau::fprintf_td(stderr, "mount: Error: Couldn't open loop-device '%s': res %d, errno %d (%s)\n",
+ loopname, loop_device_fd, errno, ::strerror(errno));
+ goto errout;
+ }
+ if( 0 > ::ioctl(loop_device_fd, LOOP_SET_FD, backingfile) ) {
+ jau::fprintf_td(stderr, "mount: Error: Couldn't attach image-file '%s' to loop-device '%s': errno %d (%s)\n",
+ image_stats.to_string().c_str(), loopname, errno, ::strerror(errno));
+ goto errout;
+ }
+ ::close(loop_device_fd);
+ loop_device_fd = -1;
+ ::close(backingfile);
+ backingfile = -1;
+
+ if( fs_options.size() > 0 ) {
+ fs_options_cstr = (void*) fs_options.data();
+ }
+ mount_res = ::mount(loopname, target_stats.path().c_str(), fs_type.c_str(), mountflags, fs_options_cstr);
+ if( 0 != mount_res ) {
+ jau::fprintf_td(stderr, "mount: Error: source_path %s, target_path %s, fs_type %s, res %d, errno %d (%s)\n",
+ image_path.c_str(), mount_point.c_str(), fs_type.c_str(), mount_res, errno, ::strerror(errno));
+ ::ioctl(loop_device_fd, LOOP_CLR_FD, 0);
+ goto errout;
+ }
+ return mount_ctx(mount_point, loop_device_id);
+
+errout:
+ if( 0 <= loop_ctl_fd ) {
+ ::close(loop_ctl_fd);
+ }
+ if( 0 <= loop_device_fd ) {
+ ::close(loop_device_fd);
+ }
+#else
+ (void)fs_type;
+ (void)mountflags;
+ (void)fs_options;
+#endif
+ if( 0 <= backingfile ) {
+ ::close(backingfile);
+ }
+ return mount_ctx();
+}
+
+bool jau::fs::umount(const mount_ctx& context)
+{
+ if( !context.mounted) {
+ return false;
+ }
+ file_stats target_stats(context.mount_point);
+ if( !target_stats.is_dir()) {
+ return false;
+ }
+ const int umount_res = ::umount(target_stats.path().c_str());
+ if( 0 != umount_res ) {
+ jau::fprintf_td(stderr, "umount: Error: Couldn't umount '%s': res %d, errno %d (%s)\n",
+ target_stats.to_string().c_str(), umount_res, errno, ::strerror(errno));
+ }
+ if( 0 > context.loop_device_id ) {
+ // mounted w/o loop-device, done
+ return 0 == umount_res;
+ }
+#ifdef __linux__
+ int loop_device_fd = -1;
+ char loopname[4096];
+
+ snprintf(loopname, sizeof(loopname), "/dev/loop%d", context.loop_device_id);
+ jau::fprintf_td(stderr, "umount: Info: Using loop-device '%s'\n", loopname);
+
+ loop_device_fd = ::open64(loopname, O_RDWR);
+ if( 0 > loop_device_fd ) {
+ jau::fprintf_td(stderr, "umount: Error: Couldn't open loop-device '%s': res %d, errno %d (%s)\n",
+ loopname, loop_device_fd, errno, ::strerror(errno));
+ goto errout;
+ }
+ if( 0 > ::ioctl(loop_device_fd, LOOP_CLR_FD, 0) ) {
+ jau::fprintf_td(stderr, "umount: Error: Couldn't deattach loop-device '%s': errno %d (%s)\n",
+ loopname, errno, ::strerror(errno));
+ goto errout;
+ }
+ ::close(loop_device_fd);
+ loop_device_fd = -1;
+
+ return 0 == umount_res;
+
+errout:
+ if( 0 <= loop_device_fd ) {
+ ::close(loop_device_fd);
+ }
+#endif
+ return false;
+}
+
diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt
index 7ffa6b0..fdd2873 100644
--- a/test/CMakeLists.txt
+++ b/test/CMakeLists.txt
@@ -13,19 +13,35 @@ target_compile_options(catch2 PUBLIC "-DCATCH_AMALGAMATED_CUSTOM_MAIN=1")
# install(TARGETS catch2 LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR})
# These examples use the standard separate compilation
-file(GLOB_RECURSE SOURCES_IDIOMATIC_EXAMPLES RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} "test_*.cpp")
+file(GLOB_RECURSE SOURCES_IDIOMATIC_TEST RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} "test_*.cpp")
-string( REPLACE ".cpp" "" BASENAMES_IDIOMATIC_EXAMPLES "${SOURCES_IDIOMATIC_EXAMPLES}" )
-set( TARGETS_IDIOMATIC_EXAMPLES ${BASENAMES_IDIOMATIC_EXAMPLES} )
+string( REPLACE ".cpp" "" BASENAMES_IDIOMATIC_TEST "${SOURCES_IDIOMATIC_TEST}" )
+set( TARGETS_IDIOMATIC_TEST ${BASENAMES_IDIOMATIC_TEST} )
-foreach( name ${TARGETS_IDIOMATIC_EXAMPLES} )
+foreach( name ${TARGETS_IDIOMATIC_TEST} )
add_executable(${name} ${name}.cpp)
target_link_libraries(${name} jaulib catch2)
add_dependencies(${name} jaulib catch2)
-endforeach()
-
-foreach(name ${TARGETS_IDIOMATIC_EXAMPLES})
add_test (NAME ${name} COMMAND ${name})
endforeach()
-
+IF(TEST_WITH_SUDO)
+ # These examples use the standard separate compilation
+ file(GLOB_RECURSE SOURCES_IDIOMATIC_TESTSUDO RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} "testsudo_*.cpp")
+
+ string( REPLACE ".cpp" "" BASENAMES_IDIOMATIC_TESTSUDO "${SOURCES_IDIOMATIC_TESTSUDO}" )
+ set( TARGETS_IDIOMATIC_TESTSUDO ${BASENAMES_IDIOMATIC_TESTSUDO} )
+
+ foreach( name ${TARGETS_IDIOMATIC_TESTSUDO} )
+ add_executable(${name} ${name}.cpp)
+ target_link_libraries(${name} jaulib catch2)
+ add_dependencies(${name} jaulib catch2)
+ #
+ # cap_sys_admin is not enough to access /dev/loop*
+ # add_test (NAME ${name} COMMAND sudo -E /sbin/capsh --caps=cap_sys_admin+eip\ cap_setpcap,cap_setuid,cap_setgid+ep
+ # --keep=1 --user=$ENV{USER} --addamb=cap_sys_admin+eip
+ # -- -c "ulimit -c unlimited; ./${name}")
+ #
+ add_test (NAME ${name} COMMAND sudo -E ./${name})
+ endforeach()
+ENDIF(TEST_WITH_SUDO)
diff --git a/test/testsudo_fileutils02.cpp b/test/testsudo_fileutils02.cpp
new file mode 100644
index 0000000..51d8b69
--- /dev/null
+++ b/test/testsudo_fileutils02.cpp
@@ -0,0 +1,61 @@
+/*
+ * Author: Sven Gothel <[email protected]>
+ * Copyright (c) 2021 Gothel Software e.K.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+#include "test_fileutils_copy_r_p.hpp"
+
+class TestFileUtil02 : TestFileUtilBase {
+ public:
+
+ void test50_mount_copy_r_p() {
+ INFO_STR("\n\ntest50_mount_copy_r_p\n");
+
+ jau::fs::file_stats root_orig_stats(project_root1);
+ if( !root_orig_stats.exists() ) {
+ root_orig_stats = jau::fs::file_stats(project_root2);
+ }
+ REQUIRE( true == root_orig_stats.exists() );
+
+ std::string image_file = root_orig_stats.path() + ".sqfs";
+ jau::fs::file_stats image_stats(image_file);
+ REQUIRE( true == image_stats.exists() );
+
+ const std::string mount_point = root+"_mount";
+ jau::fs::remove(mount_point, jau::fs::traverse_options::recursive); // start fresh
+ REQUIRE( true == jau::fs::mkdir(mount_point, jau::fs::fmode_t::def_dir_prot) );
+
+ jau::fs::mount_ctx mctx = jau::fs::mount_image(image_stats.path(), mount_point, "squashfs", /* MS_LAZYTIME | MS_NOATIME | */ MS_RDONLY, "");
+ REQUIRE( true == mctx.mounted );
+
+ const std::string root_copy = root+"_copy_test50";
+ testxx_copy_r_p("test50_mount_copy_r_p", mount_point, 1 /* source_added_dead_links */, root_copy);
+
+ REQUIRE( true == jau::fs::umount(mctx) );
+
+ if constexpr ( _remove_target_test_dir ) {
+ REQUIRE( true == jau::fs::remove(mount_point, jau::fs::traverse_options::recursive) );
+ }
+ }
+};
+
+METHOD_AS_TEST_CASE( TestFileUtil02::test50_mount_copy_r_p, "Test TestFileUtil02 - test50_mount_copy_r_p");
diff --git a/test_data.sqfs b/test_data.sqfs
new file mode 100644
index 0000000..c18ed31
--- /dev/null
+++ b/test_data.sqfs
Binary files differ