From 4fe02b7ed25a3c3d89745a6b02d46f750d2649eb Mon Sep 17 00:00:00 2001 From: Sven Gothel Date: Wed, 31 Aug 2022 06:54:10 +0200 Subject: Complete full `jau::fs::mount()` and `umount()` --- CHANGES.md | 5 + include/jau/file_util.hpp | 138 +++++++++++++++++++--- java_jni/jni/jau/jau_fs_FileUtil.cxx | 53 +++++++-- java_jni/org/jau/fs/FileUtil.java | 75 ++++++++++-- java_jni/org/jau/fs/MountFlags.java | 91 +++++++++++++++ java_jni/org/jau/fs/UnmountFlags.java | 91 +++++++++++++++ java_jni/org/jau/fs/linux/MountFlags.java | 86 ++++++++++++++ java_jni/org/jau/fs/linux/UnmountFlags.java | 65 +++++++++++ scripts/testsudo_java.sh | 6 +- src/file_util.cpp | 156 ++++++++++++++++++++++--- test/java/jau/test/fs/TestsudoFileUtils02.java | 103 +++++++++++++++- test/testsudo_fileutils02.cpp | 14 ++- 12 files changed, 831 insertions(+), 52 deletions(-) create mode 100644 java_jni/org/jau/fs/MountFlags.java create mode 100644 java_jni/org/jau/fs/UnmountFlags.java create mode 100644 java_jni/org/jau/fs/linux/MountFlags.java create mode 100644 java_jni/org/jau/fs/linux/UnmountFlags.java diff --git a/CHANGES.md b/CHANGES.md index 2cd5637..9f6314c 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -9,6 +9,11 @@ * First stable release (TODO) **0.15.0** +* Complete full `jau::fs::mount()` and `umount()`: + - `mount()` for block devices etc (non loop-device image) + - `umount()` of mount-point + - MountFlags and UnmountFlags detailed for GNU/Linux + - Java binding and a manual unit test with TestsudoFileUtils02. * `ByteInStream_URL` available() and read() await `url_header_sync` for `m_content_size` and blocks at read if available - `ByteInStream_Feed::read()` blocks as well with known `m_content_size`, but has no `url_header_sync` mechanism * Introduce `url_header_sync` object, used for async `read_url_stream()` and hence `ByteInStream_URL` diff --git a/include/jau/file_util.hpp b/include/jau/file_util.hpp index b966fb2..be7bd0f 100644 --- a/include/jau/file_util.hpp +++ b/include/jau/file_util.hpp @@ -1128,46 +1128,158 @@ namespace jau { struct mount_ctx { bool mounted; - std::string mount_point; + std::string target; 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(const std::string& target_, const int loop_device_id_) + : mounted(true), target(target_), loop_device_id(loop_device_id_) {} mount_ctx() - : mounted(false), mount_point(), loop_device_id(-1) {} + : mounted(false), target(), loop_device_id(-1) {} }; /** - * Attach the filesystem image named in `image_path` to `target_path`. + * Generic flag bit values for mount() `flags`. + * + * See mount(2) for a detailed description. + */ + typedef uint64_t mountflags_t; + + /** + * Flag bit values for mount() `flags` under GNU/Linux. + * + * See mount(2) for a detailed description. + */ + enum mountflags_linux : mountflags_t { + none = 0, + MS_RDONLY = 1, + MS_NOSUID = 2, + MS_NODEV = 4, + MS_NOEXEC = 8, + MS_SYNCHRONOUS = 16, + MS_REMOUNT = 32, + MS_MANDLOCK = 64, + MS_DIRSYNC = 128, + MS_NOATIME = 1024, + MS_NODIRATIME = 2048, + MS_BIND = 4096, + MS_MOVE = 8192, + MS_REC = 16384, + MS_SILENT = 32768, + MS_POSIXACL = 1 << 16, + MS_UNBINDABLE = 1 << 17, + MS_PRIVATE = 1 << 18, + MS_SLAVE = 1 << 19, + MS_SHARED = 1 << 20, + MS_RELATIME = 1 << 21, + MS_KERNMOUNT = 1 << 22, + MS_I_VERSION = 1 << 23, + MS_STRICTATIME = 1 << 24, + MS_LAZYTIME = 1 << 25, + MS_ACTIVE = 1 << 30, + MS_NOUSER = 1UL << 31 + }; + + /** + * Attach the filesystem image named in `image_path` to `target` + * using an intermediate platform specific filesystem image loop-device. * * This method either requires root permissions
* or the following capabilities: `cap_sys_admin`,`cap_setuid`, `cap_setgid`. * + * Unmounting shall be done via umount() with mount_ctx argument to ensure + * all intermediate resources are released. + * * @param image_path path of image source file - * @param mount_point directory where `image_path` shall be attached to + * @param target directory where `image_path` filesystem shall be attached to + * @param fs_type type of filesystem, e.g. `squashfs`, `tmpfs`, `iso9660`, etc. + * @param flags filesystem agnostic mount flags, see mountflags_linux. + * @param fs_options comma separated options for the filesystem `fs_type`, see mount(8) for available options for the used filesystem. + * @return mount_ctx structure containing mounted status etc + * + * @see mountflags_t + * @see mountflags_linux + * @see mount() + * @see umount() + */ + mount_ctx mount_image(const std::string& image_path, const std::string& target, const std::string& fs_type, + const mountflags_t flags, const std::string fs_options=""); + + /** + * Attach the filesystem named in `source` to `target` + * using the given filesystem source directly. + * + * This method either requires root permissions
+ * or the following capabilities: `cap_sys_admin`,`cap_setuid`, `cap_setgid`. + * + * @param source filesystem path for device, directory, file or dummy-string which shall be attached + * @param target directory where `source` filesystem 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 + * @param flags filesystem agnostic mount flags, see mountflags_linux. + * @param fs_options comma separated options for the filesystem `fs_type`, see mount(8) for available options for the used filesystem. * @return mount_ctx structure containing mounted status etc * + * @see mountflags_t + * @see mountflags_linux + * @see mount_image() * @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=""); + mount_ctx mount(const std::string& source, const std::string& target, const std::string& fs_type, + const mountflags_t flags, const std::string fs_options=""); + + /** + * Generic flag bit values for umount() `flags`. + * + * See umount(2) for a detailed description. + */ + typedef int umountflags_t; + + /** + * Flag bit values for umount() `flags` under GNU/Linux. + * + * See umount(2) for a detailed description. + */ + enum umountflags_linux : umountflags_t { + MNT_FORCE = 1, + MNT_DETACH = 2, + MNT_EXPIRE = 4, + UMOUNT_NOFOLLOW = 8 + }; + + /** + * Detach the given mount_ctx `context` + * + * This method either requires root permissions
+ * or the following capabilities: `cap_sys_admin`,`cap_setuid`, `cap_setgid`. + * + * @param context mount_ctx previously attached via mount_image() or mount() + * @param flags optional umount options, if supported by the system. See umount_options_linux. + * @return true if successful, otherwise false + * + * @see umountflags_t + * @see umountflags_linux + * @see mount() + * @see mount_image() + */ + bool umount(const mount_ctx& context, const umountflags_t flags); /** - * Detach the given mount_ctc `context` + * Detach the topmost filesystem mounted on `target` + * optionally using given `umountflags` options if supported. * * This method either requires root permissions
* or the following capabilities: `cap_sys_admin`,`cap_setuid`, `cap_setgid`. * - * @param context mount_ctx previously attached via mount_image() + * @param target directory of previously attached filesystem + * @param flags optional umount options, if supported by the system. See umount_options_linux. * @return true if successful, otherwise false * + * @see umountflags_t + * @see umountflags_linux + * @see mount() * @see mount_image() */ - bool umount(const mount_ctx& context); + bool umount(const std::string& target, const umountflags_t flags); /**@}*/ diff --git a/java_jni/jni/jau/jau_fs_FileUtil.cxx b/java_jni/jni/jau/jau_fs_FileUtil.cxx index 74a7530..641d3b3 100644 --- a/java_jni/jni/jau/jau_fs_FileUtil.cxx +++ b/java_jni/jni/jau/jau_fs_FileUtil.cxx @@ -445,18 +445,18 @@ void Java_org_jau_fs_FileUtil_sync(JNIEnv *env, jclass cls) { } } -jlong Java_org_jau_fs_FileUtil_mount_1image(JNIEnv *env, jclass cls, - jstring jimage_path, jstring jmount_point, - jstring jfs_type, jlong jmountflags, jstring jfs_options) { +jlong Java_org_jau_fs_FileUtil_mount_1image_1impl(JNIEnv *env, jclass cls, + jstring jimage_path, jstring jtarget, + jstring jfs_type, jlong jmountflags, jstring jfs_options) { (void)cls; try { const std::string image_path = jau::jni::from_jstring_to_string(env, jimage_path); - const std::string mount_point = jau::jni::from_jstring_to_string(env, jmount_point); + const std::string target = jau::jni::from_jstring_to_string(env, jtarget); const std::string fs_type = jau::jni::from_jstring_to_string(env, jfs_type); - const long mountflags = static_cast(jmountflags); + const jau::fs::mountflags_t mountflags = static_cast(jmountflags); const std::string fs_options = jau::jni::from_jstring_to_string(env, jfs_options); - jau::fs::mount_ctx res = jau::fs::mount_image(image_path, mount_point, fs_type, + jau::fs::mount_ctx res = jau::fs::mount_image(image_path, target, fs_type, mountflags, fs_options); if( res.mounted ) { @@ -469,7 +469,31 @@ jlong Java_org_jau_fs_FileUtil_mount_1image(JNIEnv *env, jclass cls, return 0; } -jboolean Java_org_jau_fs_FileUtil_umount(JNIEnv *env, jclass cls, jlong jcontext) { +jlong Java_org_jau_fs_FileUtil_mount_1impl(JNIEnv *env, jclass cls, + jstring jsource, jstring jtarget, + jstring jfs_type, jlong jmountflags, jstring jfs_options) { + (void)cls; + try { + const std::string source = jau::jni::from_jstring_to_string(env, jsource); + const std::string target = jau::jni::from_jstring_to_string(env, jtarget); + const std::string fs_type = jau::jni::from_jstring_to_string(env, jfs_type); + const jau::fs::mountflags_t mountflags = static_cast(jmountflags); + const std::string fs_options = jau::jni::from_jstring_to_string(env, jfs_options); + + jau::fs::mount_ctx res = jau::fs::mount(source, target, fs_type, + mountflags, fs_options); + + if( res.mounted ) { + jau::jni::shared_ptr_ref ref( new jau::fs::mount_ctx(res) ); + return ref.release_to_jlong(); + } + } catch(...) { + rethrow_and_raise_java_exception_jau(env); + } + return 0; +} + +jboolean Java_org_jau_fs_FileUtil_umount1_1impl(JNIEnv *env, jclass cls, jlong jcontext, jint jumountflags) { (void)cls; try { jau::jni::shared_ptr_ref sref(jcontext, false /* throw_on_nullptr */); // hold copy until done @@ -480,7 +504,8 @@ jboolean Java_org_jau_fs_FileUtil_umount(JNIEnv *env, jclass cls, jlong jcontext // umount using copy if !null if( !sref.is_null() ) { - return jau::fs::umount(*sref) ? JNI_TRUE : JNI_FALSE; + const jau::fs::umountflags_t umountflags = static_cast(jumountflags); + return jau::fs::umount(*sref, umountflags) ? JNI_TRUE : JNI_FALSE; } } // dtor copy @@ -489,3 +514,15 @@ jboolean Java_org_jau_fs_FileUtil_umount(JNIEnv *env, jclass cls, jlong jcontext } return JNI_FALSE; } + +jboolean Java_org_jau_fs_FileUtil_umount2_1impl(JNIEnv *env, jclass cls, jstring jtarget, jint jumountflags) { + (void)cls; + try { + const std::string target = jau::jni::from_jstring_to_string(env, jtarget); + const jau::fs::umountflags_t umountflags = static_cast(jumountflags); + return jau::fs::umount(target, umountflags) ? JNI_TRUE : JNI_FALSE; + } catch(...) { + rethrow_and_raise_java_exception_jau(env); + } + return JNI_FALSE; +} diff --git a/java_jni/org/jau/fs/FileUtil.java b/java_jni/org/jau/fs/FileUtil.java index f04362f..8b192da 100644 --- a/java_jni/org/jau/fs/FileUtil.java +++ b/java_jni/org/jau/fs/FileUtil.java @@ -353,33 +353,90 @@ public final class FileUtil { public static native void sync(); /** - * Attach the filesystem image named in `image_path` to `target_path`. + * Attach the filesystem image named in `image_path` to `target` + * using an intermediate platform specific filesystem image loop-device. * * This method either requires root permissions
* or the following capabilities: `cap_sys_admin`,`cap_setuid`, `cap_setgid`. * + * Unmounting shall be done via umount() with mount_ctx argument to ensure + * all intermediate resources are released. + * * @param image_path path of image source file - * @param mount_point directory where `image_path` shall be attached to + * @param target directory where `image_path` filesystem 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 native mount context if successful, otherwise null + * @param flags filesystem agnostic mount flags, see mountflags_linux. + * @param fs_options comma separated options for the filesystem `fs_type`, see mount(8) for available options for the used filesystem. + * @return mount_ctx structure containing mounted status etc + * + * @see mount() + * @see umount() + */ + public static long mount_image(final String image_path, final String target, final String fs_type, + final MountFlags flags, final String fs_options) { + return mount_image_impl(image_path, target, fs_type, flags.value(), fs_options); + } + private static native long mount_image_impl(final String image_path, final String target, final String fs_type, + final long mountflags, final String fs_options); + + /** + * Attach the filesystem named in `source` to `target` + * using the given filesystem source directly. * + * This method either requires root permissions
+ * or the following capabilities: `cap_sys_admin`,`cap_setuid`, `cap_setgid`. + * + * @param source filesystem path for device, directory, file or dummy-string which shall be attached + * @param target directory where `source` filesystem shall be attached to + * @param fs_type type of filesystem, e.g. `squashfs`, `tmpfs`, `iso9660`, etc. + * @param flags mount flags, e.g. `MS_LAZYTIME | MS_NOATIME | MS_RDONLY` for a read-only lazy-time and no-atime filesystem. + * @param fs_options comma separated options for the filesystem `fs_type`, see mount(8) for available options for the used filesystem. + * @return mount_ctx structure containing mounted status etc + * + * @see mount_image() * @see umount() */ - public static native long mount_image(final String image_path, final String mount_point, final String fs_type, + public static long mount(final String source, final String target, final String fs_type, + final MountFlags flags, final String fs_options) { + return mount_impl(source, target, fs_type, flags.value(), fs_options); + } + private static native long mount_impl(final String source, final String target, final String fs_type, final long mountflags, final String fs_options); /** - * Detach the given mount_ctc `context` + * Detach the given mount_ctx `context` * * This method either requires root permissions
* or the following capabilities: `cap_sys_admin`,`cap_setuid`, `cap_setgid`. * - * @param context native mount context, previously attached via mount_image() + * @param context native mount context, previously attached via mount_image() or mount() + * @param flags optional umount options, if supported by the system * @return true if successful, otherwise false * + * @see mount() * @see mount_image() */ - public static native boolean umount(final long context); + public static boolean umount(final long context, final UnmountFlags flags) { + return umount1_impl(context, flags.value()); + } + private static native boolean umount1_impl(final long context, final int unmountflags); + + /** + * Detach the topmost filesystem mounted on `target` + * optionally using given `umountflags` options if supported. + * + * This method either requires root permissions
+ * or the following capabilities: `cap_sys_admin`,`cap_setuid`, `cap_setgid`. + * + * @param target directory of previously attached filesystem + * @param umountflags optional umount options, if supported by the system + * @return true if successful, otherwise false + * + * @see mount() + * @see mount_image() + */ + public static boolean umount(final String target, final UnmountFlags flags) { + return umount2_impl(target, flags.value()); + } + private static native boolean umount2_impl(final String target, final int unmountflags); } diff --git a/java_jni/org/jau/fs/MountFlags.java b/java_jni/org/jau/fs/MountFlags.java new file mode 100644 index 0000000..b26ad45 --- /dev/null +++ b/java_jni/org/jau/fs/MountFlags.java @@ -0,0 +1,91 @@ +/** + * Author: Sven Gothel + * Copyright (c) 2022 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. + */ +package org.jau.fs; + +/** + * Generic flag bit values for mount() `flags`. + * + * See mount(2) for a detailed description. + * + * @see FileUtil#mount_image(String, String, String, long, String) + * @see FileUtil#mount(String, String, String, long, String) + */ +public class MountFlags { + + public static interface Bit { + String name(); + long value(); + } + public static enum Bit0 implements Bit { + none ( 0 ); + + Bit0(final long v) { _value = v; } + private final long _value; + + @Override + public long value() { return _value; } + } + protected Bit[] bit_values() { return Bit0.values(); } + + private long mask; + + public long value() { return mask; } + + protected MountFlags(final long v) { + mask = v; + } + public MountFlags() { + mask = 0; + } + + public boolean isSet(final Bit bit) { return bit.value() == ( mask & bit.value() ); } + + public MountFlags set(final Bit bit) { mask = mask | bit.value(); return this; } + + @Override + public String toString() { + int count = 0; + final StringBuilder out = new StringBuilder(); + for (final Bit dt : bit_values()) { + if( 0 != dt.value() && isSet(dt) ) { + if( 0 < count ) { out.append(", "); } + out.append(dt.name()); count++; + } + } + if( 1 < count ) { + out.insert(0, "["); + out.append("]"); + } + return out.toString(); + } + + @Override + public boolean equals(final Object other) { + if (this == other) { + return true; + } + return (other instanceof MountFlags) && + this.mask == ((MountFlags)other).mask; + } +} diff --git a/java_jni/org/jau/fs/UnmountFlags.java b/java_jni/org/jau/fs/UnmountFlags.java new file mode 100644 index 0000000..8321005 --- /dev/null +++ b/java_jni/org/jau/fs/UnmountFlags.java @@ -0,0 +1,91 @@ +/** + * Author: Sven Gothel + * Copyright (c) 2022 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. + */ +package org.jau.fs; + +/** + * Generic flag bit class for umount() `flags` + * + * See umount(2) for a detailed description. + * + * @see FileUtil#umount(long, int) + * @see FileUtil#umount(String, int) + */ +public class UnmountFlags { + + public static interface Bit { + String name(); + int value(); + } + public static enum Bit0 implements Bit { + none ( 0 ); + + Bit0(final int v) { _value = v; } + private int _value; + + @Override + public int value() { return _value; } + } + protected Bit[] bit_values() { return Bit0.values(); } + + private int mask; + + public int value() { return mask; } + + protected UnmountFlags(final int v) { + mask = v; + } + public UnmountFlags() { + mask = 0; + } + + public boolean isSet(final Bit bit) { return bit.value() == ( mask & bit.value() ); } + + public UnmountFlags set(final Bit bit) { mask = mask | bit.value(); return this; } + + @Override + public String toString() { + int count = 0; + final StringBuilder out = new StringBuilder(); + for (final Bit dt : bit_values()) { + if( 0 != dt.value() && isSet(dt) ) { + if( 0 < count ) { out.append(", "); } + out.append(dt.name()); count++; + } + } + if( 1 < count ) { + out.insert(0, "["); + out.append("]"); + } + return out.toString(); + } + + @Override + public boolean equals(final Object other) { + if (this == other) { + return true; + } + return (other instanceof UnmountFlags) && + this.mask == ((UnmountFlags)other).mask; + } +} \ No newline at end of file diff --git a/java_jni/org/jau/fs/linux/MountFlags.java b/java_jni/org/jau/fs/linux/MountFlags.java new file mode 100644 index 0000000..010e3a9 --- /dev/null +++ b/java_jni/org/jau/fs/linux/MountFlags.java @@ -0,0 +1,86 @@ +/** + * Author: Sven Gothel + * Copyright (c) 2022 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. + */ +package org.jau.fs.linux; + +import org.jau.fs.FileUtil; + +/** + * Flag bit values for mount() `flags` under GNU/Linux. + * + * See mount(2) for a detailed description. + * + * @see FileUtil#mount_image(String, String, String, long, String) + * @see FileUtil#mount(String, String, String, long, String) + */ +public class MountFlags extends org.jau.fs.MountFlags { + + public static enum Bit implements org.jau.fs.MountFlags.Bit { + none ( 0 ), + MS_RDONLY ( 1 ), + MS_NOSUID ( 2 ), + MS_NODEV ( 4 ), + MS_NOEXEC ( 8 ), + MS_SYNCHRONOUS ( 16 ), + MS_REMOUNT ( 32 ), + MS_MANDLOCK ( 64 ), + MS_DIRSYNC ( 128 ), + MS_NOATIME ( 1024 ), + MS_NODIRATIME ( 2048 ), + MS_BIND ( 4096 ), + MS_MOVE ( 8192 ), + MS_REC ( 16384 ), + MS_SILENT ( 32768 ), + MS_POSIXACL ( 1 << 16 ), + MS_UNBINDABLE ( 1 << 17 ), + MS_PRIVATE ( 1 << 18 ), + MS_SLAVE ( 1 << 19 ), + MS_SHARED ( 1 << 20 ), + MS_RELATIME ( 1 << 21 ), + MS_KERNMOUNT ( 1 << 22 ), + MS_I_VERSION ( 1 << 23 ), + MS_STRICTATIME ( 1 << 24 ), + MS_LAZYTIME ( 1 << 25 ), + MS_ACTIVE ( 1 << 30 ), + MS_NOUSER ( 1 << 31 ); + + Bit(final long v) { _value = v; } + private final long _value; + + @Override + public long value() { return _value; } + } + + @Override + protected Bit[] bit_values() { + return Bit.values(); + } + + public MountFlags(final long v) { + super(v); + } + + public MountFlags() { + super(0); + } +} diff --git a/java_jni/org/jau/fs/linux/UnmountFlags.java b/java_jni/org/jau/fs/linux/UnmountFlags.java new file mode 100644 index 0000000..b5ba6e7 --- /dev/null +++ b/java_jni/org/jau/fs/linux/UnmountFlags.java @@ -0,0 +1,65 @@ +/** + * Author: Sven Gothel + * Copyright (c) 2022 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. + */ +package org.jau.fs.linux; + +import org.jau.fs.FileUtil; + +/** + * Flag bit class for umount() `flags` under GNU/Linux + * + * See umount(2) for a detailed description. + * + * @see FileUtil#umount(long, int) + * @see FileUtil#umount(String, int) + */ +public class UnmountFlags extends org.jau.fs.UnmountFlags { + + public static enum Bit implements org.jau.fs.UnmountFlags.Bit { + none ( 0 ), + MNT_FORCE ( 1 ), + MNT_DETACH ( 2 ), + MNT_EXPIRE ( 4 ), + UMOUNT_NOFOLLOW ( 8 ); + + Bit(final int v) { _value = v; } + + private final int _value; + + @Override + public int value() { return _value; } + } + + @Override + protected Bit[] bit_values() { + return Bit.values(); + } + + public UnmountFlags(final int v) { + super(v); + } + + public UnmountFlags() { + super(0); + } +} diff --git a/scripts/testsudo_java.sh b/scripts/testsudo_java.sh index e23fe04..1e13cd9 100755 --- a/scripts/testsudo_java.sh +++ b/scripts/testsudo_java.sh @@ -87,13 +87,13 @@ do_test() { cd ${test_dir} pwd - echo "$EXE_WRAPPER ${JAVA_CMD} ${JAVA_PROPS} -cp ${test_classpath} -Djava.library.path=${rootdir}/dist-${archabi}/lib org.junit.runner.JUnitCore ${test_class} ${*@Q}" + echo "$EXE_WRAPPER ${JAVA_CMD} ${JAVA_PROPS} -cp ${test_classpath} -Djava.library.path=${dist_dir}/lib org.junit.runner.JUnitCore ${test_class} ${*@Q}" ulimit -c unlimited - # $EXE_WRAPPER ${JAVA_CMD} ${JAVA_PROPS} -cp ${test_classpath} -Djava.library.path=${rootdir}/dist-${archabi}/lib org.junit.runner.JUnitCore ${test_class} ${*@Q} + # $EXE_WRAPPER ${JAVA_CMD} ${JAVA_PROPS} -cp ${test_classpath} -Djava.library.path=${dist_dir}/lib org.junit.runner.JUnitCore ${test_class} ${*@Q} # $EXE_WRAPPER ${JAVA_CMD} ${JAVA_PROPS} -cp ${test_classpath} org.junit.runner.JUnitCore ${test_class} ${*@Q} - "/usr/bin/sudo" -E "/sbin/capsh" "--caps=cap_sys_admin,cap_setuid,cap_setgid+eip cap_setpcap+ep" "--keep=1" "--user=${USER}" "--addamb=cap_sys_admin,cap_setuid,cap_setgid+eip" "--" "-c" "ulimit -c unlimited; $EXE_WRAPPER ${JAVA_CMD} ${JAVA_PROPS} -cp ${test_classpath} -Djava.library.path=${rootdir}/dist-${archabi}/lib org.junit.runner.JUnitCore ${test_class} ${*@Q}" + "/usr/bin/sudo" -E "/sbin/capsh" "--caps=cap_sys_admin,cap_setuid,cap_setgid+eip cap_setpcap+ep" "--keep=1" "--user=${USER}" "--addamb=cap_sys_admin,cap_setuid,cap_setgid+eip" "--" "-c" "ulimit -c unlimited; $EXE_WRAPPER ${JAVA_CMD} ${JAVA_PROPS} -cp ${test_classpath} -Djava.library.path=${dist_dir}/lib ${test_class} ${*@Q}" exit $? } diff --git a/src/file_util.cpp b/src/file_util.cpp index c04df02..a6cb8bf 100644 --- a/src/file_util.cpp +++ b/src/file_util.cpp @@ -1861,17 +1861,20 @@ static bool set_effective_uid(::uid_t user_id) { return true; } -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) +jau::fs::mount_ctx jau::fs::mount_image(const std::string& image_path, const std::string& target, const std::string& fs_type, + const mountflags_t flags, const std::string fs_options) { file_stats image_stats(image_path); if( !image_stats.is_file()) { + ERR_PRINT("image_path not a file: %s", image_stats.to_string().c_str()); return mount_ctx(); } - file_stats target_stats(mount_point); + file_stats target_stats(target); if( !target_stats.is_dir()) { + ERR_PRINT("target not a dir: %s", target_stats.to_string().c_str()); return mount_ctx(); } + const std::string target_path(target_stats.path()); int backingfile = __posix_openat64(AT_FDCWD, image_stats.path().c_str(), O_RDWR); if( 0 > backingfile ) { ERR_PRINT("Couldn't open image-file '%s': res %d", image_stats.to_string().c_str(), backingfile); @@ -1927,9 +1930,10 @@ jau::fs::mount_ctx jau::fs::mount_image(const std::string& image_path, const std 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); + mount_res = ::mount(loopname, target_path.c_str(), fs_type.c_str(), flags, fs_options_cstr); if( 0 != mount_res ) { - ERR_PRINT("source_path %s, target_path %s, fs_type %s, res %d", image_path.c_str(), mount_point.c_str(), fs_type.c_str(), mount_res); + ERR_PRINT("source_path %s, target_path %s, fs_type %s, res %d", + image_stats.path().c_str(), target_path.c_str(), fs_type.c_str(), mount_res); ::ioctl(loop_device_fd, LOOP_CLR_FD, 0); goto errout_child; } @@ -1972,12 +1976,12 @@ errout_child: ::close(backingfile); backingfile = -1; } - return mount_ctx(mount_point, loop_device_id); + return mount_ctx(target_path, loop_device_id); errout: #else (void)fs_type; - (void)mountflags; + (void)flags; (void)fs_options; #endif if( 0 <= backingfile ) { @@ -1986,12 +1990,83 @@ errout: return mount_ctx(); } -bool jau::fs::umount(const mount_ctx& context) +mount_ctx jau::fs::mount(const std::string& source, const std::string& target, const std::string& fs_type, + const mountflags_t flags, const std::string fs_options) +{ + if( source.empty() ) { + ERR_PRINT("source is an empty string "); + return mount_ctx(); + } + file_stats source_stats(source); + file_stats target_stats(target); + if( !target_stats.is_dir()) { + ERR_PRINT("target not a dir: %s", target_stats.to_string().c_str()); + return mount_ctx(); + } + const std::string target_path(target_stats.path()); + const ::uid_t caller_uid = ::geteuid(); + + ::pid_t pid = ::fork(); + if( 0 == pid ) { + void* fs_options_cstr = nullptr; + + if( 0 != caller_uid ) { + if( !set_effective_uid(0) ) { + ::_exit( EXIT_FAILURE ); + } + } + if( fs_options.size() > 0 ) { + fs_options_cstr = (void*) fs_options.data(); + } +#ifdef __linux__ + const int mount_res = ::mount(source_stats.path().c_str(), target_path.c_str(), fs_type.c_str(), flags, fs_options_cstr); +#elif defined(__FreeBSD__) + /** + * Non generic due to the 'fstype'_args structure passed via data + * ufs_args data = { .fsspec=source_stats.path().c_str() }; + * ::mount(fs_type.c_str(), target_path.c_str(), mountflags, data); + */ + (void)flags; + (void)fs_options_cstr; + const int mount_res = -1; +#else + #warning Add OS support + const int mount_res = -1; +#endif + if( 0 != mount_res ) { + ERR_PRINT("source_path %s, target_path %s, fs_type %s, flags %" PRIu64 ", res %d", + source_stats.path().c_str(), target_path.c_str(), fs_type.c_str(), flags, mount_res); + ::_exit( EXIT_FAILURE ); + } else { + ::_exit( EXIT_SUCCESS ); + } + + } else if( 0 < pid ) { + int pid_status = 0; + ::pid_t child_pid = ::waitpid(pid, &pid_status, 0); + if( 0 > child_pid ) { + ERR_PRINT("wait(%d) failed: child_pid %d", pid, child_pid); + } else { + if( child_pid != pid ) { + WARN_PRINT("wait(%d) terminated child_pid %d", pid, child_pid); + } + if( WIFEXITED(pid_status) && EXIT_SUCCESS == WEXITSTATUS(pid_status) ) { + return mount_ctx(target_path, -1); + } + WARN_PRINT("wait(%d) terminated abnormally child_pid %d, pid_status %d", pid, child_pid, pid_status); + } + } else { + ERR_PRINT("Couldn't fork() process: res %d", pid); + } + return mount_ctx(); +} + +bool jau::fs::umount(const mount_ctx& context, const umountflags_t flags) { if( !context.mounted) { return false; } - file_stats target_stats(context.mount_point); + file_stats target_stats(context.target); if( !target_stats.is_dir()) { return false; } @@ -2001,19 +2076,19 @@ bool jau::fs::umount(const mount_ctx& context) if( 0 == pid ) { if( 0 != caller_uid ) { if( !set_effective_uid(0) ) { - ::_exit(EXIT_FAILURE); + ::_exit( EXIT_FAILURE ); } } #if defined(__linux__) - const int umount_res = ::umount(target_stats.path().c_str()); + const int umount_res = ::umount2(target_stats.path().c_str(), flags); #elif defined(__FreeBSD__) - const int umount_res = ::unmount(target_stats.path().c_str(), 0); + const int umount_res = ::unmount(target_stats.path().c_str(), flags); #else #warning Add OS support const int umount_res = -1; #endif if( 0 != umount_res ) { - ERR_PRINT("Couldn't umount '%s': res %d\n", target_stats.to_string().c_str(), umount_res); + ERR_PRINT("Couldn't umount '%s', flags %d: res %d\n", target_stats.to_string().c_str(), flags, umount_res); } if( 0 > context.loop_device_id ) { // mounted w/o loop-device, done @@ -2041,14 +2116,14 @@ bool jau::fs::umount(const mount_ctx& context) #endif // No loop-device handling for OS - ::_exit(EXIT_FAILURE); + ::_exit( EXIT_FAILURE ); #ifdef __linux__ errout_child: if( 0 <= loop_device_fd ) { ::close(loop_device_fd); } - ::_exit(EXIT_FAILURE); + ::_exit( EXIT_FAILURE ); #endif } else if( 0 < pid ) { int pid_status = 0; @@ -2070,3 +2145,54 @@ errout_child: return false; } +bool jau::fs::umount(const std::string& target, const umountflags_t flags) +{ + if( target.empty() ) { + return false; + } + file_stats target_stats(target); + if( !target_stats.is_dir()) { + return false; + } + const ::uid_t caller_uid = ::geteuid(); + + ::pid_t pid = ::fork(); + if( 0 == pid ) { + if( 0 != caller_uid ) { + if( !set_effective_uid(0) ) { + ::_exit( EXIT_FAILURE ); + } + } +#if defined(__linux__) + const int umount_res = ::umount2(target_stats.path().c_str(), flags); +#elif defined(__FreeBSD__) + const int umount_res = ::unmount(target_stats.path().c_str(), flags); +#else + #warning Add OS support + const int umount_res = -1; +#endif + if( 0 == umount_res ) { + ::_exit( EXIT_SUCCESS ); + } else { + ERR_PRINT("Couldn't umount '%s', flags %d: res %d\n", target_stats.to_string().c_str(), flags, umount_res); + ::_exit( EXIT_FAILURE ); + } + } else if( 0 < pid ) { + int pid_status = 0; + ::pid_t child_pid = ::waitpid(pid, &pid_status, 0); + if( 0 > child_pid ) { + ERR_PRINT("wait(%d) failed: child_pid %d", pid, child_pid); + } else { + if( child_pid != pid ) { + WARN_PRINT("wait(%d) terminated child_pid %d", pid, child_pid); + } + if( WIFEXITED(pid_status) && EXIT_SUCCESS == WEXITSTATUS(pid_status) ) { + return true; + } + WARN_PRINT("wait(%d) terminated abnormally child_pid %d, pid_status %d", pid, child_pid, pid_status); + } + } else { + ERR_PRINT("Couldn't fork() process: res %d", pid); + } + return false; +} diff --git a/test/java/jau/test/fs/TestsudoFileUtils02.java b/test/java/jau/test/fs/TestsudoFileUtils02.java index fa68fa9..c28642a 100644 --- a/test/java/jau/test/fs/TestsudoFileUtils02.java +++ b/test/java/jau/test/fs/TestsudoFileUtils02.java @@ -24,12 +24,19 @@ package jau.test.fs; +import java.util.List; + import org.jau.fs.CopyOptions; +import org.jau.fs.DirItem; import org.jau.fs.FMode; import org.jau.fs.FileStats; import org.jau.fs.FileUtil; +import org.jau.fs.MountFlags; import org.jau.fs.TraverseOptions; +import org.jau.fs.UnmountFlags; import org.jau.io.PrintUtil; +import org.jau.sys.PlatformProps; +import org.jau.sys.PlatformTypes; import org.junit.Assert; import org.junit.FixMethodOrder; import org.junit.Test; @@ -41,6 +48,11 @@ import jau.pkg.PlatformRuntime; public class TestsudoFileUtils02 extends FileUtilBaseTest { static final boolean DEBUG = false; + private static boolean isFolderEmpty(final String path) { + final List files = FileUtil.get_dir_content(path); + return null == files || 0 == files.size(); + } + @Test(timeout = 10000) public void test50_mount_copy_r_p() { PlatformRuntime.checkInitialized(); @@ -62,8 +74,17 @@ public class TestsudoFileUtils02 extends FileUtilBaseTest { FileUtil.remove(mount_point, TraverseOptions.recursive); // start fresh Assert.assertTrue( true == FileUtil.mkdir(mount_point, FMode.def_dir) ); - final long mctx = FileUtil.mount_image(image_stats.path(), mount_point, "squashfs", 0, ""); + final MountFlags mount_flags; + if( PlatformProps.OS == PlatformTypes.OSType.LINUX ) { + mount_flags = new org.jau.fs.linux.MountFlags(); + mount_flags.set(org.jau.fs.linux.MountFlags.Bit.MS_RDONLY); + } else { + mount_flags = new MountFlags(); + } + PrintUtil.println(System.err, "MountFlags for "+PlatformProps.OS+": "+mount_flags); + final long mctx = FileUtil.mount_image(image_stats.path(), mount_point, "squashfs", mount_flags, ""); Assert.assertTrue( 0 != mctx ); + Assert.assertFalse(isFolderEmpty(mount_point)); final CopyOptions copts = new CopyOptions(); copts.set(CopyOptions.Bit.recursive); @@ -76,15 +97,93 @@ public class TestsudoFileUtils02 extends FileUtilBaseTest { testxx_copy_r_p("test50_mount_copy_r_p", new FileStats(mount_point), 1 /* source_added_dead_links */, root_copy, copts, false /* dest_is_vfat */); Assert.assertTrue( true == FileUtil.remove(root_copy, TraverseOptions.recursive) ); - final boolean umount_ok = FileUtil.umount(mctx); + final UnmountFlags umount_flags; + if( PlatformProps.OS == PlatformTypes.OSType.LINUX ) { + umount_flags = new org.jau.fs.linux.UnmountFlags(); + umount_flags.set(org.jau.fs.linux.UnmountFlags.Bit.MNT_DETACH); // lazy + } else { + umount_flags = new UnmountFlags(); + } + PrintUtil.println(System.err, "UnmountFlags for "+PlatformProps.OS+": "+umount_flags); + final boolean umount_ok = FileUtil.umount(mctx, umount_flags); Assert.assertTrue( true == umount_ok ); + Assert.assertTrue(isFolderEmpty(mount_point)); Assert.assertTrue( true == FileUtil.remove(root_copy, TraverseOptions.recursive) ); Assert.assertTrue( true == FileUtil.remove(mount_point, TraverseOptions.recursive) ); } } + /* pp */ static String test51_devname = null; + /* pp */ static boolean test51_umount = true; + + @Test(timeout = 10000) + public void test51_mount_device() { + PlatformRuntime.checkInitialized(); + PrintUtil.println(System.err, "test51_mount_device\n"); + if( null == test51_devname ) { + return; + } + { + final String device_file = test51_devname; + final FileStats device_stats = new FileStats(device_file); + Assert.assertTrue( true == device_stats.exists() ); + PrintUtil.println(System.err, "Mount "+device_stats); + + final String mount_point = root+"_mount"; + Assert.assertTrue(isFolderEmpty(mount_point)); + FileUtil.remove(mount_point, TraverseOptions.recursive); // start fresh + Assert.assertTrue( true == FileUtil.mkdir(mount_point, FMode.def_dir) ); + + final MountFlags mount_flags; + if( PlatformProps.OS == PlatformTypes.OSType.LINUX ) { + mount_flags = new org.jau.fs.linux.MountFlags(); + mount_flags.set(org.jau.fs.linux.MountFlags.Bit.MS_RDONLY); // failed: MS_PRIVATE, MS_UNBINDABLE + } else { + mount_flags = new MountFlags(); + } + PrintUtil.println(System.err, "MountFlags for "+PlatformProps.OS+": "+mount_flags); + final long mctx = FileUtil.mount(device_stats.path(), mount_point, "vfat", mount_flags, "errors=continue,rodir,sys_immutable,usefree"); + Assert.assertTrue( 0 != mctx ); + Assert.assertFalse(isFolderEmpty(mount_point)); + + final List files = FileUtil.get_dir_content(mount_point); + Assert.assertNotNull(files); + Assert.assertTrue(files.size() > 0 ); + int count=0; + for(final DirItem item : files) { + ++count; + PrintUtil.fprintf_td(System.err, "%,4d: %s\n", count, item); + } + + if( test51_umount ) { + final UnmountFlags umount_flags; + if( PlatformProps.OS == PlatformTypes.OSType.LINUX ) { + umount_flags = new org.jau.fs.linux.UnmountFlags(); + umount_flags.set(org.jau.fs.linux.UnmountFlags.Bit.MNT_DETACH); // lazy + } else { + umount_flags = new UnmountFlags(); + } + PrintUtil.println(System.err, "UnmountFlags for "+PlatformProps.OS+": "+umount_flags); + final boolean umount_ok = FileUtil.umount(mount_point, umount_flags); + Assert.assertTrue( true == umount_ok ); + Assert.assertTrue(isFolderEmpty(mount_point)); + + Assert.assertTrue( true == FileUtil.remove(mount_point, TraverseOptions.recursive) ); + } + } + } + public static void main(final String args[]) { + final int argc = args.length; + for(int i=0; i< argc; i++) { + final String arg = args[i]; + if( arg.equals("-mount_device") && args.length > (i+1) ) { + test51_devname = args[++i]; + } else if( arg.equals("-no_umount") ) { + test51_umount = false; + } + } org.junit.runner.JUnitCore.main(TestsudoFileUtils02.class.getName()); } } \ No newline at end of file diff --git a/test/testsudo_fileutils02.cpp b/test/testsudo_fileutils02.cpp index 7b9aabb..a6ee299 100644 --- a/test/testsudo_fileutils02.cpp +++ b/test/testsudo_fileutils02.cpp @@ -313,7 +313,12 @@ class TestFileUtil02 : TestFileUtilBase { print_creds("pre-mount"); print_caps("pre-mount"); - mctx = jau::fs::mount_image(image_stats.path(), mount_point, "squashfs", /* MS_LAZYTIME | MS_NOATIME | */ MS_RDONLY, ""); + jau::fs::mountflags_t flags = 0; +#ifdef __linux__ + flags |= jau::fs::mountflags_linux::MS_RDONLY; +#endif + jau::fprintf_td(stderr, "MountFlags %" PRIu64 "\n", flags); + mctx = jau::fs::mount_image(image_stats.path(), mount_point, "squashfs", flags, ""); print_creds("post-mount"); print_caps("post-mount"); @@ -336,7 +341,12 @@ class TestFileUtil02 : TestFileUtilBase { print_creds("pre-umount"); print_caps("pre-umount"); - umount_ok = jau::fs::umount(mctx); + jau::fs::umountflags_t flags = 0; +#ifdef __linux__ + flags |= jau::fs::umountflags_linux::MNT_DETACH; // lazy +#endif + jau::fprintf_td(stderr, "UnmountFlags %d\n", flags); + umount_ok = jau::fs::umount(mctx, flags); print_creds("post-umount"); print_caps("post-umount"); -- cgit v1.2.3