jaulib v1.3.0
Jau Support Library (C++, Java, ..)
FileUtil.java
Go to the documentation of this file.
1/**
2 * Author: Sven Gothel <sgothel@jausoft.com>
3 * Copyright (c) 2022 Gothel Software e.K.
4 *
5 * Permission is hereby granted, free of charge, to any person obtaining
6 * a copy of this software and associated documentation files (the
7 * "Software"), to deal in the Software without restriction, including
8 * without limitation the rights to use, copy, modify, merge, publish,
9 * distribute, sublicense, and/or sell copies of the Software, and to
10 * permit persons to whom the Software is furnished to do so, subject to
11 * the following conditions:
12 *
13 * The above copyright notice and this permission notice shall be
14 * included in all copies or substantial portions of the Software.
15 *
16 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20 * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21 * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22 * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
23 */
24package org.jau.fs;
25
26import java.time.Instant;
27import java.util.Comparator;
28import java.util.List;
29
30/**
31 * Native file types and functionality.
32 */
33public final class FileUtil {
34 /**
35 * Return the current working directory or empty on failure.
36 */
37 public static native String get_cwd();
38
39 /**
40 * Return stripped last component from given path separated by `/`, excluding the trailing separator `/`.
41 *
42 * If no directory separator `/` is contained, return `.`.
43 *
44 * If only the root path `/` is given, return `/`.
45 *
46 * @param path given path
47 * @return leading directory name w/o slash or `.`
48 */
49 public static native String dirname(final String path);
50
51 /**
52 * Return stripped leading directory components from given path separated by `/`.
53 *
54 * If only the root path `/` is given, return `/`.
55 *
56 * @param path given path
57 * @return last non-slash component or `.`
58 */
59 public static native String basename(final String path);
60
61 /**
62 * Returns platform dependent named file descriptor of given file descriptor, if supported.
63 *
64 * Implementation returns (`%d` stands for integer):
65 * - `/dev/fd/%d` (GNU/Linux, FreeBSD, ..)
66 *
67 * Currently implementation always returns above pattern,
68 * not handling the target OS differences.
69 *
70 * @param fd file descriptor.
71 * @return the named file descriptor or null if fd < 0 or not supported by OS.
72 *
73 * @see from_named_fd()
74 * @see FileStats#has_fd()
75 */
76 public static native String to_named_fd(final int fd);
77
78 /**
79 * Returns the file descriptor from the given named file descriptor.
80 *
81 * Detected named file descriptors are (`%d` stands for integer)
82 * - `/dev/fd/%d` (GNU/Linux, FreeBSD, ..)
83 * - `/proc/self/fd/%d` (GNU/Linux)
84 *
85 * @param named_fd the named file descriptor
86 * @return file descriptor or -1 if invalid or not supported by OS.
87 *
88 * @see to_named_fd()
89 * @see {@link FileStats#has_fd()}
90 */
91 public static native int from_named_fd(final String named_fd);
92
93 /**
94 * Returns the file descriptor from the given FileDescriptor instance.
95 *
96 * @param jfd the FileDescriptor instance
97 * @return file descriptor or -1 if invalid or not supported by OS.
98 *
99 * @see {@link FileStats#has_fd()}
100 */
101 public static native int from_java_fd(final java.io.FileDescriptor jfd);
102
103 /**
104 * Create directory
105 * @param path full path to new directory
106 * @param mode fmode_t POSIX protection bits used, defaults to {@link FMode#def_dir}
107 * @param verbose defaults to false
108 * @return true if successful, otherwise false
109 */
110 public static boolean mkdir(final String path, final FMode mode) {
111 return mkdirImpl(path, mode.mask);
112 }
113 private static native boolean mkdirImpl(final String path, final int mode);
114
115 /**
116 * See {@link #mkdir(String, FMode)} using {@link FMode#def_dir}
117 */
118 public static boolean mkdir(final String path) {
119 return mkdirImpl(path, FMode.def_dir.mask);
120 }
121
122 /**
123 * Touch the file with given atime and mtime and create file if not existing yet.
124 * @param path full path to file
125 * @param atime new access time
126 * @param mtime new modification time
127 * @param mode fmode_t POSIX protection bits used, defaults to {@link FMode#def_file}
128 * @param verbose defaults to false
129 * @return true if successful, otherwise false
130 */
131 public static boolean touch(final String path, final Instant atime, final Instant mtime,
132 final FMode mode) {
133 return touchImpl(path,
134 atime.getEpochSecond(), atime.getNano(),
135 mtime.getEpochSecond(), mtime.getNano(),
136 mode.mask);
137 }
138 private static native boolean touchImpl(final String path,
139 long atime_s, long atime_ns,
140 long mtime_s, long mtime_ns,
141 int mode);
142
143 public static final long UTIME_NOW = ((1l << 30) - 1l);
144
145 /**
146 * Touch the file with current time and create file if not existing yet.
147 * @param path full path to file
148 * @param mode fmode_t POSIX protection bits used, defaults to {@link FMode#def_file}
149 * @param verbose defaults to false
150 * @return true if successful, otherwise false
151 */
152 public static boolean touch(final String path, final FMode mode) {
153 return touchImpl(path, 0, UTIME_NOW, 0, UTIME_NOW, mode.mask);
154 }
155
156 /**
157 * Returns a list of directory elements excluding `.` and `..` for the given path, non recursive.
158 *
159 * @param path path to directory
160 * @return list of DirItem if given path exists, is directory and is readable, otherwise null
161 */
162 public static native List<DirItem> get_dir_content(final String path);
163
164 /**
165 * Path visitor for {@link FileUtil#visit(FileStats, TraverseOptions, PathVisitor)}
166 *
167 * Depth being the recursive directory depth starting with 1 for the initial directory.
168 *
169 * Returning `false` stops traversal in general but {@link TraverseOptions.Bit#dir_check_entry}
170 * will only skip traversing the denied directory.
171 */
172 public static interface PathVisitor {
173 /**
174 *
175 * @param tevt
176 * @param item_stats
177 * @param depth being the recursive directory depth starting with 1 for the initial directory
178 * @return `false` stops traversal in general but at {@link TraverseOptions.Bit#dir_check_entry}
179 * will only skip traversing the denied directory.
180 */
181 boolean visit(TraverseEvent tevt, final FileStats item_stats, final long depth);
182 }
183
184 /**
185 * Visit element(s) of a given path, see traverse_options for detailed settings.
186 *
187 * All elements of type fmode_t::file, fmode_t::dir and fmode_t::no_access or fmode_t::not_existing
188 * will be visited by the given path_visitor `visitor`.
189 *
190 * Depth passed to path_visitor is the recursive directory depth and starts with 1 for the initial directory.
191 *
192 * {@link PathVisitor#visit(TraverseEvent, FileStats, long) path_visitor}
193 * returning `false` stops traversal in general but {@link TraverseOptions.Bit#dir_check_entry}
194 * will only skip traversing the denied directory.
195 *
196 * @param path the starting path
197 * @param topts given traverse_options for this operation
198 * @param visitor path_visitor function `bool visitor(const file_stats& item_stats, size_t depth)`.
199 * @return true if successful including no path_visitor stopped traversal by returning `false` excluding {@link TraverseOptions.Bit#dir_check_entry}.
200 */
201 public static boolean visit(final String path, final TraverseOptions topts, final PathVisitor visitor) {
202 return visit(new FileStats(path), topts, visitor);
203 }
204
205 /**
206 * Visit element(s) of a given path, see traverse_options for detailed settings.
207 *
208 * All elements of type fmode_t::file, fmode_t::dir and fmode_t::no_access or fmode_t::not_existing
209 * will be visited by the given path_visitor `visitor`.
210 *
211 * Depth passed to path_visitor is the recursive directory depth and starts with 1 for the initial directory.
212 *
213 * {@link PathVisitor#visit(TraverseEvent, FileStats, long) path_visitor}
214 * returning `false` stops traversal in general but {@link TraverseOptions.Bit#dir_check_entry}
215 * will only skip traversing the denied directory.
216 *
217 * @param item_stats pre-fetched file_stats for a given dir_item, used for efficiency
218 * @param topts given traverse_options for this operation
219 * @param visitor path_visitor function `bool visitor(const file_stats& item_stats, size_t depth)`.
220 * @return true if successful including no path_visitor stopped traversal by returning `false` excluding {@link TraverseOptions.Bit#dir_check_entry}.
221 */
222 public static boolean visit(final FileStats item_stats, final TraverseOptions topts, final PathVisitor visitor) {
223 return visitImpl(item_stats, topts, visitor, new long[] { 0 });
224 }
225 private static boolean visitImpl(final FileStats item_stats, final TraverseOptions topts, final PathVisitor visitor, final long[] depth) {
226 depth[0]++;
227 if( item_stats.is_dir() ) {
228 if( item_stats.is_link() && !topts.isSet(TraverseOptions.Bit.follow_symlinks) ) {
229 return visitor.visit( TraverseEvent.dir_symlink, item_stats, depth[0] );
230 }
231 if( !topts.isSet(TraverseOptions.Bit.recursive) ) {
232 return visitor.visit( TraverseEvent.dir_non_recursive, item_stats, depth[0] );
233 }
234 if( topts.isSet(TraverseOptions.Bit.dir_check_entry) ) {
235 if( !visitor.visit( TraverseEvent.dir_check_entry, item_stats, depth[0] ) ) {
236 return true; // keep traversing in parent, but skip this directory
237 }
238 }
239 if( topts.isSet(TraverseOptions.Bit.dir_entry) ) {
240 if( !visitor.visit( TraverseEvent.dir_entry, item_stats, depth[0] ) ) {
241 return false;
242 }
243 }
244
245 final Comparator<DirItem> dirItemComparator = new Comparator<DirItem> () {
246 @Override
247 public int compare(final DirItem o1, final DirItem o2) {
248 return o1.basename().compareTo(o2.basename());
249 }
250 };
251
252 final List<DirItem> content = get_dir_content(item_stats.path());
253 if( null != content && content.size() > 0 ) {
254 if( topts.isSet(TraverseOptions.Bit.lexicographical_order) ) {
255 content.sort(dirItemComparator);
256 }
257 for (final DirItem element : content) {
258 final FileStats element_stats = new FileStats( element );
259 if( element_stats.is_dir() ) { // an OK dir
260 if( element_stats.is_link() && !topts.isSet(TraverseOptions.Bit.follow_symlinks) ) {
261 if( !visitor.visit( TraverseEvent.dir_symlink, element_stats, depth[0] ) ) {
262 return false;
263 }
264 } else if( !visitImpl(element_stats, topts, visitor, depth) ) { // recursive
265 return false;
266 }
267 } else if( !visitor.visit( element_stats.is_file() && element_stats.is_link() ? TraverseEvent.file_symlink :
268 ( element_stats.is_file() ? TraverseEvent.file :
269 ( element_stats.is_link() ? TraverseEvent.symlink : TraverseEvent.none ) ),
270 element_stats, depth[0] ) ) {
271 return false;
272 }
273 }
274 }
275 }
276 if( item_stats.is_dir() && topts.isSet(TraverseOptions.Bit.dir_exit) ) {
277 return visitor.visit( TraverseEvent.dir_exit, item_stats, depth[0] );
278 } else if( item_stats.is_file() || !item_stats.ok() ) { // file or error-alike
279 return visitor.visit( item_stats.is_file() && item_stats.is_link() ? TraverseEvent.file_symlink :
280 ( item_stats.is_file() ? TraverseEvent.file :
281 ( item_stats.is_link() ? TraverseEvent.symlink : TraverseEvent.none ) ),
282 item_stats, depth[0] );
283 } else {
284 return true;
285 }
286 }
287
288 /**
289 * Remove the given path. If path represents a director, `recursive` must be set to true.
290 *
291 * The given traverse_options `options` are handled as follows:
292 * - traverse_options::parent_dir_last will be added by implementation to operate correct
293 * - traverse_options::recursive shall shall be set by caller to remove directories
294 * - traverse_options::follow_symlinks shall be set by caller to remove symbolic linked directories recursively, which is kind of dangerous.
295 * If not set, only the symbolic link will be removed (default)
296 *
297 * Implementation is most data-race-free (DRF), utilizes following safeguards
298 * - utilizing parent directory file descriptor and `openat()` and `unlinkat()` operations against concurrent mutation
299 *
300 * @param path path to remove
301 * @param topts given traverse_options for this operation, defaults to traverse_options::none
302 * @return true only if the file or the directory with content has been deleted, otherwise false
303 */
304 public static boolean remove(final String path, final TraverseOptions topts) {
305 return remove_impl(path, topts.mask);
306 }
307 private static native boolean remove_impl(final String path, final short topts);
308
309 /**
310 * Compare the bytes of both files, denoted by source1 and source2.
311 *
312 * @param source1 first source file to compare
313 * @param source2 second source file to compare
314 * @param verbose defaults to false
315 * @return true if both elements are files and their bytes are equal, otherwise false.
316 */
317 public static native boolean compare(final String source1, final String source2, final boolean verbose);
318
319 /**
320 * Copy the given source_path to dest_path using copy_options.
321 *
322 * The behavior is similar like POSIX `cp` commandline tooling.
323 *
324 * The following behavior is being followed regarding dest_path:
325 * - If source_path is a directory and CopyOptions::recursive set
326 * - If dest_path doesn't exist, source_path dir content is copied into the newly created dest_path.
327 * - If dest_path exists as a directory, source_path dir will be copied below the dest_path directory
328 * _if_ CopyOptions::into_existing_dir is not set. Otherwise its content is copied into the existing dest_path.
329 * - Everything else is considered an error
330 * - If source_path is a file
331 * - If dest_path doesn't exist, source_path file is copied to dest_path as a file.
332 * - If dest_path exists as a directory, source_path file will be copied below the dest_path directory.
333 * - If dest_path exists as a file, CopyOptions::overwrite must be set to have it overwritten by the source_path file
334 * - Everything else is considered an error
335 *
336 * Implementation either uses ::sendfile() if running under `GNU/Linux`,
337 * otherwise POSIX ::read() and ::write().
338 *
339 * Implementation is most data-race-free (DRF), utilizes following safeguards on recursive directory copy
340 * - utilizing parent directory file descriptor and `openat()` operations against concurrent mutation
341 * - for each entered *directory*
342 * - new destination directory is create with '.<random_number>' and user-rwx permissions only
343 * - its file descriptor is being opened
344 * - its user-read permission is dropped, remains user-wx permissions only
345 * - its renamed to destination path
346 * - all copy operations are performed inside
347 * - at exit, its permissions are restored, etc.
348 *
349 * See copy_options for details.
350 *
351 * @param source_path
352 * @param dest_path
353 * @param copts
354 * @return true if successful, otherwise false
355 */
356 public static boolean copy(final String source_path, final String dest_path, final CopyOptions copts) {
357 return copy_impl(source_path, dest_path, copts.mask);
358 }
359 private static native boolean copy_impl(final String source_path, final String dest_path, final short copts);
360
361 /**
362 * Rename oldpath to newpath using POSIX `::rename()`, with the following combinations
363 * - oldpath and newpath refer to the same file, a successful no-operation.
364 * - oldpath file
365 * - newpath not-existing file
366 * - newpath existing file to be atomically replaced
367 * - oldpath directory
368 * - newpath not-existing directory
369 * - newpath existing empty directory
370 * - oldpath symlink will be renamed
371 * - newpath symlink will be overwritten
372 *
373 * @param oldpath previous path
374 * @param newpath new path
375 * @return true only if the rename operation was successful, otherwise false
376 */
377 public static native boolean rename(final String oldpath, final String newpath);
378
379 /**
380 * Synchronizes filesystems, i.e. all pending modifications to filesystem metadata and cached file data will be written to the underlying filesystems.
381 */
382 public static native void sync();
383
384 /**
385 * Attach the filesystem image named in `image_path` to `target`
386 * using an intermediate platform specific filesystem image loop-device.
387 *
388 * This method either requires root permissions <br />
389 * or the following capabilities: `cap_sys_admin`,`cap_setuid`, `cap_setgid`.
390 *
391 * Unmounting shall be done via umount() with mount_ctx argument to ensure
392 * all intermediate resources are released.
393 *
394 * @param image_path path of image source file
395 * @param target directory where `image_path` filesystem shall be attached to
396 * @param fs_type type of filesystem, e.g. `squashfs`, `tmpfs`, `iso9660`, etc.
397 * @param flags filesystem agnostic mount flags, see mountflags_linux.
398 * @param fs_options comma separated options for the filesystem `fs_type`, see mount(8) for available options for the used filesystem.
399 * @return mount_ctx structure containing mounted status etc
400 *
401 * @see mount()
402 * @see umount()
403 */
404 public static long mount_image(final String image_path, final String target, final String fs_type,
405 final MountFlags flags, final String fs_options) {
406 return mount_image_impl(image_path, target, fs_type, flags.value(), fs_options);
407 }
408 private static native long mount_image_impl(final String image_path, final String target, final String fs_type,
409 final long mountflags, final String fs_options);
410
411 /**
412 * Attach the filesystem named in `source` to `target`
413 * using the given filesystem source directly.
414 *
415 * This method either requires root permissions <br />
416 * or the following capabilities: `cap_sys_admin`,`cap_setuid`, `cap_setgid`.
417 *
418 * @param source filesystem path for device, directory, file or dummy-string which shall be attached
419 * @param target directory where `source` filesystem shall be attached to
420 * @param fs_type type of filesystem, e.g. `squashfs`, `tmpfs`, `iso9660`, etc.
421 * @param flags mount flags, e.g. `MS_LAZYTIME | MS_NOATIME | MS_RDONLY` for a read-only lazy-time and no-atime filesystem.
422 * @param fs_options comma separated options for the filesystem `fs_type`, see mount(8) for available options for the used filesystem.
423 * @return mount_ctx structure containing mounted status etc
424 *
425 * @see mount_image()
426 * @see umount()
427 */
428 public static long mount(final String source, final String target, final String fs_type,
429 final MountFlags flags, final String fs_options) {
430 return mount_impl(source, target, fs_type, flags.value(), fs_options);
431 }
432 private static native long mount_impl(final String source, final String target, final String fs_type,
433 final long mountflags, final String fs_options);
434
435 /**
436 * Detach the given mount_ctx `context`
437 *
438 * This method either requires root permissions <br />
439 * or the following capabilities: `cap_sys_admin`,`cap_setuid`, `cap_setgid`.
440 *
441 * @param context native mount context, previously attached via mount_image() or mount()
442 * @param flags optional umount options, if supported by the system
443 * @return true if successful, otherwise false
444 *
445 * @see mount()
446 * @see mount_image()
447 */
448 public static boolean umount(final long context, final UnmountFlags flags) {
449 return umount1_impl(context, flags.value());
450 }
451 private static native boolean umount1_impl(final long context, final int unmountflags);
452
453 /**
454 * Detach the topmost filesystem mounted on `target`
455 * optionally using given `umountflags` options if supported.
456 *
457 * This method either requires root permissions <br />
458 * or the following capabilities: `cap_sys_admin`,`cap_setuid`, `cap_setgid`.
459 *
460 * @param target directory of previously attached filesystem
461 * @param umountflags optional umount options, if supported by the system
462 * @return true if successful, otherwise false
463 *
464 * @see mount()
465 * @see mount_image()
466 */
467 public static boolean umount(final String target, final UnmountFlags flags) {
468 return umount2_impl(target, flags.value());
469 }
470 private static native boolean umount2_impl(final String target, final int unmountflags);
471}
Filesystem copy options used to copy() path elements.
Generic file type and POSIX protection mode bits as used in file_stats, touch(), mkdir() etc.
Definition: FMode.java:37
static final FMode def_dir
Default directory protection bit: Safe default: POSIX S_IRWXU | S_IRGRP | S_IXGRP or rwx_usr | read_g...
Definition: FMode.java:158
Platform agnostic representation of POSIX ::lstat() and ::stat() for a given pathname.
Definition: FileStats.java:41
String path()
Returns the unix path representation.
Definition: FileStats.java:261
boolean is_file()
Returns true if entity is a file, might be in combination with is_link().
Definition: FileStats.java:386
boolean ok()
Returns true if no error occurred.
Definition: FileStats.java:356
boolean is_link()
Returns true if entity is a symbolic link, might be in combination with is_file(),...
Definition: FileStats.java:389
boolean is_dir()
Returns true if entity is a directory, might be in combination with is_link().
Definition: FileStats.java:383
Native file types and functionality.
Definition: FileUtil.java:33
static boolean touch(final String path, final Instant atime, final Instant mtime, final FMode mode)
Touch the file with given atime and mtime and create file if not existing yet.
Definition: FileUtil.java:131
static boolean copy(final String source_path, final String dest_path, final CopyOptions copts)
Copy the given source_path to dest_path using copy_options.
Definition: FileUtil.java:356
static native String dirname(final String path)
Return stripped last component from given path separated by /, excluding the trailing separator /.
static long mount_image(final String image_path, final String target, final String fs_type, final MountFlags flags, final String fs_options)
Attach the filesystem image named in image_path to target using an intermediate platform specific fil...
Definition: FileUtil.java:404
static native String basename(final String path)
Return stripped leading directory components from given path separated by /.
static boolean umount(final long context, final UnmountFlags flags)
Detach the given mount_ctx context
Definition: FileUtil.java:448
static boolean mkdir(final String path)
See mkdir(String, FMode) using FMode#def_dir.
Definition: FileUtil.java:118
static boolean mkdir(final String path, final FMode mode)
Create directory.
Definition: FileUtil.java:110
static boolean visit(final FileStats item_stats, final TraverseOptions topts, final PathVisitor visitor)
Visit element(s) of a given path, see traverse_options for detailed settings.
Definition: FileUtil.java:222
static boolean touch(final String path, final FMode mode)
Touch the file with current time and create file if not existing yet.
Definition: FileUtil.java:152
static native void sync()
Synchronizes filesystems, i.e.
static native boolean rename(final String oldpath, final String newpath)
Rename oldpath to newpath using POSIX ::rename(), with the following combinations.
static boolean umount(final String target, final UnmountFlags flags)
Detach the topmost filesystem mounted on target optionally using given umountflags options if support...
Definition: FileUtil.java:467
static native int from_java_fd(final java.io.FileDescriptor jfd)
Returns the file descriptor from the given FileDescriptor instance.
static native String to_named_fd(final int fd)
Returns platform dependent named file descriptor of given file descriptor, if supported.
static native String get_cwd()
Return the current working directory or empty on failure.
static native boolean compare(final String source1, final String source2, final boolean verbose)
Compare the bytes of both files, denoted by source1 and source2.
static final long UTIME_NOW
Definition: FileUtil.java:143
static native List< DirItem > get_dir_content(final String path)
Returns a list of directory elements excluding .
static long mount(final String source, final String target, final String fs_type, final MountFlags flags, final String fs_options)
Attach the filesystem named in source to target using the given filesystem source directly.
Definition: FileUtil.java:428
static boolean visit(final String path, final TraverseOptions topts, final PathVisitor visitor)
Visit element(s) of a given path, see traverse_options for detailed settings.
Definition: FileUtil.java:201
static native int from_named_fd(final String named_fd)
Returns the file descriptor from the given named file descriptor.
Generic flag bit values for mount() flags.
Definition: MountFlags.java:34
Filesystem traverse options used to visit() path elements.
boolean isSet(final Bit bit)
Generic flag bit class for umount() flags
Filesystem traverse event used to call path_visitor for path elements from visit().
dir_symlink
Visiting a symbolic-link to a directory which is not followed, i.e.
follow_symlinks
Traverse through symbolic linked directories if traverse_options::recursive is set,...
Path visitor for FileUtil#visit(FileStats, TraverseOptions, PathVisitor).
Definition: FileUtil.java:172
boolean visit(TraverseEvent tevt, final FileStats item_stats, final long depth)