diff --git a/configure.ac b/configure.ac index e20df24c3eb..1faaaa9b345 100644 --- a/configure.ac +++ b/configure.ac @@ -706,7 +706,20 @@ AS_IF([test "x$ul_cv_syscall_fsconfig" = xno || have_mountfd_api="no" ],[ have_mountfd_api="yes" - AC_DEFINE([HAVE_MOUNTFD_API], [1], [Define to 1 if you want mount API based on FDs.]) + AC_DEFINE([HAVE_MOUNTFD_API], [1], [Define to 1 if you want to use mount API based on FDs.]) + ]) + + +UL_CHECK_SYSCALL([statmount]) +UL_CHECK_SYSCALL([listmount]) + +AS_IF([test "x$ul_cv_syscall_statmount" = xno || + test "x$ul_cv_syscall_listmount" = xno], + [ + have_statmount_api="no" + ],[ + have_statmount_api="yes" + AC_DEFINE([HAVE_STATMOUNT_API], [1], [Define to 1 if you want to use statmount API.]) ]) diff --git a/include/c.h b/include/c.h index d060a4d89eb..01951860e05 100644 --- a/include/c.h +++ b/include/c.h @@ -37,6 +37,8 @@ # define NAME_MAX PATH_MAX #endif +#define BIT(n) (1 << (n)) + /* * __GNUC_PREREQ is deprecated in favour of __has_attribute() and * __has_feature(). The __has macros are supported by clang and gcc>=5. diff --git a/include/mount-api-utils.h b/include/mount-api-utils.h index 8ae546b7ed9..bbd075287ed 100644 --- a/include/mount-api-utils.h +++ b/include/mount-api-utils.h @@ -5,10 +5,16 @@ #ifndef UTIL_LINUX_MOUNT_API_UTILS #define UTIL_LINUX_MOUNT_API_UTILS -#if defined(HAVE_MOUNTFD_API) && defined(HAVE_LINUX_MOUNT_H) - -#include +#ifdef HAVE_LINUX_MOUNT_H +#include #include +#include +#include + +/* + * File descritors based mount API + */ +#ifdef HAVE_MOUNTFD_API /* Accepted by both open_tree() and mount_setattr(). */ #ifndef AT_RECURSIVE @@ -203,6 +209,207 @@ static inline int fspick(int dfd, const char *pathname, unsigned int flags) } #endif -#endif /* HAVE_MOUNTFD_API && HAVE_LINUX_MOUNT_H */ -#endif /* UTIL_LINUX_MOUNT_API_UTILS */ +#endif /* HAVE_MOUNTFD_API */ + +/* + * statmount() and listmount() + */ +#ifdef HAVE_STATMOUNT_API + +#ifndef MNT_ID_REQ_SIZE_VER0 +# define MNT_ID_REQ_SIZE_VER0 24 /* sizeof first published struct */ +#endif +#ifndef MNT_ID_REQ_SIZE_VER1 +# define MNT_ID_REQ_SIZE_VER1 32 /* sizeof second published struct */ +#endif + +/* + * The structs mnt_id_req and statmount may differ between kernel versions, so + * we must ensure that the structs contain everything we need. For now (during + * development), it seems best to define local copies of the structs to avoid + * relying on installed kernel headers and to avoid a storm of #ifdefs. + */ + +/* + * listmount() and statmount() request + */ +struct ul_mnt_id_req { + uint32_t size; + uint32_t spare; + uint64_t mnt_id; + uint64_t param; + uint64_t mnt_ns_id; +}; + +/* + * Please note that due to the variable length of the statmount buffer, the + * struct cannot be versioned by size (like struct mnt_id_req). + */ +struct ul_statmount { + uint32_t size; /* Total size, including strings */ + uint32_t mnt_opts; /* [str] Mount options of the mount */ + uint64_t mask; /* What results were written */ + uint32_t sb_dev_major; /* Device ID */ + uint32_t sb_dev_minor; + uint64_t sb_magic; /* ..._SUPER_MAGIC */ + uint32_t sb_flags; /* SB_{RDONLY,SYNCHRONOUS,DIRSYNC,LAZYTIME} */ + uint32_t fs_type; /* [str] Filesystem type */ + uint64_t mnt_id; /* Unique ID of mount */ + uint64_t mnt_parent_id; /* Unique ID of parent (for root == mnt_id) */ + uint32_t mnt_id_old; /* Reused IDs used in proc/.../mountinfo */ + uint32_t mnt_parent_id_old; + uint64_t mnt_attr; /* MOUNT_ATTR_... */ + uint64_t mnt_propagation; /* MS_{SHARED,SLAVE,PRIVATE,UNBINDABLE} */ + uint64_t mnt_peer_group; /* ID of shared peer group */ + uint64_t mnt_master; /* Mount receives propagation from this ID */ + uint64_t propagate_from; /* Propagation from in current namespace */ + uint32_t mnt_root; /* [str] Root of mount relative to root of fs */ + uint32_t mnt_point; /* [str] Mountpoint relative to current root */ + uint64_t mnt_ns_id; /* ID of the mount namespace */ + uint64_t __spare2[49]; + char str[]; /* Variable size part containing strings */ +}; + +/* sb_flags (defined in kernel include/linux/fs.h) */ +#ifndef SB_RDONLY +# define SB_RDONLY BIT(0) /* Mount read-only */ +# define SB_NOSUID BIT(1) /* Ignore suid and sgid bits */ +# define SB_NODEV BIT(2) /* Disallow access to device special files */ +# define SB_NOEXEC BIT(3) /* Disallow program execution */ +# define SB_SYNCHRONOUS BIT(4) /* Writes are synced at once */ +# define SB_MANDLOCK BIT(6) /* Allow mandatory locks on an FS */ +# define SB_DIRSYNC BIT(7) /* Directory modifications are synchronous */ +# define SB_NOATIME BIT(10) /* Do not update access times. */ +# define SB_NODIRATIME BIT(11) /* Do not update directory access times */ +# define SB_SILENT BIT(15) +# define SB_POSIXACL BIT(16) /* Supports POSIX ACLs */ +# define SB_INLINECRYPT BIT(17) /* Use blk-crypto for encrypted files */ +# define SB_KERNMOUNT BIT(22) /* this is a kern_mount call */ +# define SB_I_VERSION BIT(23) /* Update inode I_version field */ +# define SB_LAZYTIME BIT(25) /* Update the on-disk [acm]times lazily */ +#endif + +/* + * @mask bits for statmount(2) + */ +#ifndef STATMOUNT_SB_BASIC +# define STATMOUNT_SB_BASIC 0x00000001U /* Want/got sb_... */ +#endif +#ifndef STATMOUNT_MNT_BASIC +# define STATMOUNT_MNT_BASIC 0x00000002U /* Want/got mnt_... */ +#endif +#ifndef STATMOUNT_PROPAGATE_FROM +# define STATMOUNT_PROPAGATE_FROM 0x00000004U /* Want/got propagate_from */ +#endif +#ifndef STATMOUNT_MNT_ROOT +# define STATMOUNT_MNT_ROOT 0x00000008U /* Want/got mnt_root */ +#endif +#ifndef STATMOUNT_MNT_POINT +# define STATMOUNT_MNT_POINT 0x00000010U /* Want/got mnt_point */ +#endif +#ifndef STATMOUNT_FS_TYPE +# define STATMOUNT_FS_TYPE 0x00000020U /* Want/got fs_type */ +#endif +#ifndef STATMOUNT_MNT_NS_ID +# define STATMOUNT_MNT_NS_ID 0x00000040U /* Want/got mnt_ns_id */ +#endif +#ifndef STATMOUNT_MNT_OPTS +# define STATMOUNT_MNT_OPTS 0x00000080U /* Want/got mnt_opts */ +#endif +/* + * Special @mnt_id values that can be passed to listmount + */ +#ifdef LSMT_ROOT +# define LSMT_ROOT 0xffffffffffffffff /* root mount */ +#endif + +#ifndef LISTMOUNT_REVERSE +# define LISTMOUNT_REVERSE BIT(0) /* List later mounts first */ +#endif + +#if defined(SYS_statmount) +static inline int ul_statmount(uint64_t mnt_id, + uint64_t ns_id, + uint64_t mask, + struct ul_statmount *buf, + size_t bufsize, unsigned int flags) +{ + struct ul_mnt_id_req req = { + .size = MNT_ID_REQ_SIZE_VER1, + .mnt_id = mnt_id, + .param = mask, + .mnt_ns_id = ns_id + }; + + return syscall(SYS_statmount, &req, buf, bufsize, flags); +} +#endif + +#if defined(SYS_listmount) +static inline ssize_t ul_listmount(uint64_t mnt_id, + uint64_t ns_id, + uint64_t last_mnt_id, + uint64_t list[], size_t num, + unsigned int flags) +{ + struct ul_mnt_id_req req = { + .size = MNT_ID_REQ_SIZE_VER1, + .mnt_id = mnt_id, + .param = last_mnt_id, + .mnt_ns_id = ns_id + }; + + return syscall(SYS_listmount, &req, list, num, flags); +} +#endif + +/* This is a version of statmount() that reallocates @buf to be large enough to + * store data for the requested @id. This function never deallocates; it is the + * caller's responsibility. + */ +static inline int sys_statmount(uint64_t id, + uint64_t ns_id, + uint64_t mask, + struct ul_statmount **buf, + size_t *bufsiz, + unsigned int flags) +{ + size_t sz; + int rc = 0; + + if (!buf || !bufsiz) + return -EINVAL; + + sz = *bufsiz; + if (!sz) + sz = 32 * 1024; + + do { + if (sz > *bufsiz) { + struct ul_statmount *tmp = realloc(*buf, sz); + if (!tmp) + return -ENOMEM; + *buf = tmp; + *bufsiz = sz; + } + + errno = 0; + rc = ul_statmount(id, ns_id, mask, *buf, *bufsiz, flags); + if (!rc) + break; + if (errno != EOVERFLOW) + break; + if (sz >= SIZE_MAX / 2) + break; + sz <<= 1; + } while (rc); + + return rc; +} + +#endif /* HAVE_STATMOUNT_API */ + +#endif /* HAVE_LINUX_MOUNT_H */ + +#endif /* UTIL_LINUX_MOUNT_API_UTILS */ diff --git a/include/timeutils.h b/include/timeutils.h index 27261abd87d..e3cd252ba56 100644 --- a/include/timeutils.h +++ b/include/timeutils.h @@ -112,4 +112,8 @@ static inline bool is_timespecset(const struct timespec *t) return t->tv_sec || t->tv_nsec; } +static inline double time_diff(const struct timeval *a, const struct timeval *b) +{ + return (a->tv_sec - b->tv_sec) + (a->tv_usec - b->tv_usec) / (double) USEC_PER_SEC; +} #endif /* UTIL_LINUX_TIME_UTIL_H */ diff --git a/libmount/docs/libmount-docs.xml b/libmount/docs/libmount-docs.xml index ad816e72ad8..9a2c1b9470f 100644 --- a/libmount/docs/libmount-docs.xml +++ b/libmount/docs/libmount-docs.xml @@ -83,4 +83,24 @@ available from https://www.kernel.org/pub/linux/utils/util-linux/. Index of new symbols in 2.35 + + Index of new symbols in 2.37 + + + + Index of new symbols in 2.38 + + + + Index of new symbols in 2.39 + + + + Index of new symbols in 2.40 + + + + Index of new symbols in 2.41 + + diff --git a/libmount/docs/libmount-sections.txt b/libmount/docs/libmount-sections.txt index 0b4adb5950e..13259fa510c 100644 --- a/libmount/docs/libmount-sections.txt +++ b/libmount/docs/libmount-sections.txt @@ -229,10 +229,12 @@ mnt_fs_get_freq mnt_fs_get_fs_options mnt_fs_get_fstype mnt_fs_get_id +mnt_fs_get_uniq_id mnt_fs_get_option mnt_fs_get_optional_fields mnt_fs_get_options mnt_fs_get_parent_id +mnt_fs_get_parent_uniq_id mnt_fs_get_passno mnt_fs_get_priority mnt_fs_get_propagation @@ -273,15 +275,33 @@ mnt_fs_set_priority mnt_fs_set_root mnt_fs_set_source mnt_fs_set_target +mnt_fs_set_uniq_id mnt_fs_set_userdata mnt_fs_strdup_options mnt_fs_streq_srcpath mnt_fs_streq_target mnt_fs_to_mntent +mnt_fs_fetch_statmount +mnt_fs_get_ns +mnt_fs_set_ns mnt_new_fs mnt_reset_fs +
+statmount +libmnt_statmnt +mnt_new_statmnt +mnt_ref_statmnt +mnt_unref_statmnt +mnt_statmnt_disable_fetching +mnt_statmnt_set_mask +mnt_fs_fetch_statmount +mnt_fs_get_statmnt +mnt_fs_refer_statmnt +mnt_table_refer_statmnt +
+
init mnt_init_debug @@ -348,7 +368,9 @@ mnt_table_add_fs mnt_table_append_intro_comment mnt_table_append_trailing_comment mnt_table_enable_comments +mnt_table_enable_listmount mnt_table_enable_noautofs +mnt_table_fetch_listmount mnt_table_find_devno mnt_table_find_fs mnt_table_find_mountpoint @@ -371,6 +393,9 @@ mnt_table_is_empty mnt_table_is_fs_mounted mnt_table_is_noautofs mnt_table_last_fs +mnt_table_listmount_set_id +mnt_table_listmount_set_ns +mnt_table_listmount_set_stepsiz mnt_table_move_fs mnt_table_next_child_fs mnt_table_next_fs @@ -427,6 +452,7 @@ mnt_get_mtab_path mnt_get_swaps_path mnt_guess_system_root mnt_has_regular_mtab +mnt_id_from_path mnt_mangle mnt_match_fstype mnt_tag_is_valid diff --git a/libmount/meson.build b/libmount/meson.build index a7145c5b454..05b31d4d4f5 100644 --- a/libmount/meson.build +++ b/libmount/meson.build @@ -43,6 +43,8 @@ lib_mount_sources = ''' if LINUX lib_mount_sources += ''' + src/fs_statmount.c + src/tab_listmount.c src/hooks.c src/monitor.c src/optlist.c diff --git a/libmount/samples/Makemodule.am b/libmount/samples/Makemodule.am index b9201e925c5..39c3e81669d 100644 --- a/libmount/samples/Makemodule.am +++ b/libmount/samples/Makemodule.am @@ -1,13 +1,19 @@ if LINUX check_PROGRAMS += \ - sample-mount-overwrite + sample-mount-overwrite \ + sample-mount-statmount \ + sample-mount-listmount endif -sample_mount_cflags = $(AM_CFLAGS) -I$(ul_libmount_incdir) -sample_mount_ldadd = libmount.la $(LDADD) +sample_mount_overwrite_SOURCES = libmount/samples/overwrite.c +sample_mount_overwrite_LDADD = libmount.la $(LDADD) +sample_mount_overwrite_CFLAGS = $(AM_CFLAGS) -I$(ul_libmount_incdir) +sample_mount_statmount_SOURCES = libmount/samples/statmount.c +sample_mount_statmount_LDADD = libmount.la $(LDADD) libcommon.la +sample_mount_statmount_CFLAGS = $(AM_CFLAGS) -I$(ul_libmount_incdir) -sample_mount_overwrite_SOURCES = libmount/samples/overwrite.c -sample_mount_overwrite_LDADD = $(sample_mount_ldadd) -sample_mount_overwrite_CFLAGS = $(sample_mount_cflags) +sample_mount_listmount_SOURCES = libmount/samples/listmount.c +sample_mount_listmount_LDADD = libmount.la $(LDADD) libcommon.la +sample_mount_listmount_CFLAGS = $(AM_CFLAGS) -I$(ul_libmount_incdir) diff --git a/libmount/samples/listmount.c b/libmount/samples/listmount.c new file mode 100644 index 00000000000..e346ebbe584 --- /dev/null +++ b/libmount/samples/listmount.c @@ -0,0 +1,168 @@ +/* + * Copyright (C) 2024 Karel Zak + * + * This file may be redistributed under the terms of the + * GNU Lesser General Public License. + */ +#include + +#include "c.h" +#include "strutils.h" +#include "timeutils.h" +#include "pathnames.h" + +#include "libmount.h" + +#include "mount-api-utils.h" /* fallback for old linux/mount.h */ + +static void __iter_table( struct libmnt_table *tb, + struct libmnt_iter *itr, int output, int reverse) +{ + struct libmnt_fs *fs = NULL; + int rc; + + mnt_reset_iter(itr, reverse ? MNT_ITER_BACKWARD : MNT_ITER_FORWARD); + while ((rc = mnt_table_next_fs(tb, itr, &fs)) == 0) { + const char *tgt = mnt_fs_get_target(fs); + const char *type = mnt_fs_get_fstype(fs); + + if (output) + printf (" %15s %s\n", type, tgt); + } + if (rc < 0) + warn("cannot iterate on filesystems"); +} + +#define fetch_data(t, i) __iter_table(t, i, 0, 0) +#define print_table(t, i) __iter_table(t, i, 1, 0) +#define print_table_reverse(t, i) __iter_table(t, i, 1, 1) + + +int main(int argc, char *argv[]) +{ + struct libmnt_table *tb; + struct libmnt_statmnt *sm; + struct libmnt_iter *itr; + struct timeval start, end; + uint64_t id = 0; + double sec_lsmnt, sec_lsstmnt, sec_mountinfo; + + mnt_init_debug(0); + + + if (argc == 2) { + const char *arg = argv[1]; + if (!isdigit_string(arg)) + mnt_id_from_path(arg, &id, NULL); + else + id = strtou64_or_err(arg, "cannot ID"); + } + + itr = mnt_new_iter(MNT_ITER_FORWARD); + if (!itr) + err(MNT_EX_SYSERR, "failed to initialize libmount iterator"); + + tb = mnt_new_table(); + if (!tb) + err(EXIT_FAILURE, "failed to allocate table handler"); + if (id) + mnt_table_listmount_set_id(tb, id); + + /* + * A) listmount() and statmount() based table + */ + sm = mnt_new_statmnt(); + if (!sm) + err(EXIT_FAILURE, "failed to allocate statmnt handler"); + + /* Without this mask setting, the library will make two statmount() calls + * for each node. */ +#if defined(STATMOUNT_MNT_POINT) && defined(STATMOUNT_FS_TYPE) + mnt_statmnt_set_mask(sm, STATMOUNT_MNT_POINT | STATMOUNT_FS_TYPE); +#endif + + /* force fetch all data + mnt_statmnt_set_mask(sm, + STATMOUNT_SB_BASIC | + STATMOUNT_MNT_BASIC | + STATMOUNT_PROPAGATE_FROM | + STATMOUNT_MNT_ROOT | + STATMOUNT_MNT_POINT | + STATMOUNT_FS_TYPE + STATMOUNT_MNT_OPTS); + */ + + /* enable don-demand statmount() call for all filesystems in the table */ + mnt_table_refer_statmnt(tb, sm); + + /* listmount() only */ + gettimeofday(&start, NULL); + if (mnt_table_fetch_listmount(tb) != 0) + warn("failed to read mount table by listmount()"); + gettimeofday(&end, NULL); + sec_lsmnt = time_diff(&end, &start); + + /* force statmount() for all nodes */ + fetch_data(tb, itr); + gettimeofday(&end, NULL); + sec_lsstmnt = time_diff(&end, &start); + + fprintf(stdout, "listmount() based table:\n"); + print_table(tb, itr); + + + /* disable statmount() and listmount(); reset table */ + mnt_statmnt_disable_fetching(sm, 1); + mnt_table_enable_listmount(tb, 0); + mnt_reset_table(tb); + + /* + * B) /proc/self/mountinfo based table + */ + gettimeofday(&start, NULL); + mnt_table_parse_file(tb, _PATH_PROC_MOUNTINFO); + gettimeofday(&end, NULL); + sec_mountinfo = time_diff(&end, &start); + + fprintf(stdout, "\nprocfs based table:\n"); + print_table(tb, itr); + + fprintf(stdout, "\n" + "%f sec listmount()\n" + "%f sec listmount()+statmount()\n" + "%f sec /proc/sef/mountinfo" + "\n\n", + sec_lsmnt, sec_lsstmnt, sec_mountinfo); + + + mnt_reset_table(tb); + + /* + * C) Instead of reading the entire mount table with one listmount() call, read + * it in smaller steps. This is particularly useful for systems with large + * mount tables, where we may only need to access one specific node (usually the + * last one). + * + * By default, libmount reads 512 nodes in one call. To test this sample + * on normal systems, we will reduce this number to 5 nodes. The listmount() + * function is used as a backend for regular mnt_table_next_fs(). + * There is no need to make any changes to the application, just enable + * on-demand listmount() by using mnt_table_enable_listmount(). + */ + if (mnt_table_listmount_set_stepsiz(tb, 5) != 0) + warn("failed to initialize listmount()"); + mnt_table_enable_listmount(tb, 1); + + /* enable also statmount() */ + mnt_statmnt_disable_fetching(sm, 0); + + fprintf(stdout, "\nlistmount() - small steps (reverse):\n"); + print_table_reverse(tb, itr); + + + mnt_unref_table(tb); + mnt_unref_statmnt(sm); + mnt_free_iter(itr); + + return EXIT_SUCCESS; +} diff --git a/libmount/samples/statmount.c b/libmount/samples/statmount.c new file mode 100644 index 00000000000..3e5f2f8b2b7 --- /dev/null +++ b/libmount/samples/statmount.c @@ -0,0 +1,80 @@ +/* + * Copyright (C) 2023 Karel Zak + * + * This file may be redistributed under the terms of the + * GNU Lesser General Public License. + */ +#include + +#include "c.h" +#include "strutils.h" +#include "libmount.h" + +#include "mount-api-utils.h" /* fallback for old linux/mount.h */ + +int main(int argc, char *argv[]) +{ + struct libmnt_fs *fs; + struct libmnt_statmnt *sm; + const char *mnt; + uint64_t id = 0; + + if (argc != 2) + errx(EXIT_FAILURE, "usage: %s ", + program_invocation_short_name); + + mnt_init_debug(0); + + fs = mnt_new_fs(); + if (!fs) + err(EXIT_FAILURE, "failed to allocate fs handler"); + + /* define target (mountpoint) or mount ID */ + mnt = argv[1]; + if (isdigit_string(mnt)) { + id = strtou64_or_err(mnt, "cannot ID"); + mnt_fs_set_uniq_id(fs, id); + } else + mnt_fs_set_target(fs, mnt); + + /* + * A) fetch all data without reference to libmnt_statmnt + */ + if (mnt_fs_fetch_statmount(fs, 0) != 0) + warn("failed to read data by statmount()"); + mnt_fs_print_debug(fs, stdout); + + /* reset */ + id = mnt_fs_get_uniq_id(fs); + mnt_reset_fs(fs); + mnt_fs_set_uniq_id(fs, id); + + /* + * B) fetch data by on-demand way + */ + sm = mnt_new_statmnt(); + if (!sm) + err(EXIT_FAILURE, "failed to allocate statmount handler"); + + mnt_fs_refer_statmnt(fs, sm); + + /* read fs type, but nothing else */ + mnt_fs_get_fstype(fs); + mnt_fs_print_debug(fs, stdout); + + /* read fs root, but nothing else */ + mnt_fs_get_root(fs); + mnt_fs_print_debug(fs, stdout); + + /* read all mising data */ + mnt_fs_fetch_statmount(fs, 0); + mnt_fs_print_debug(fs, stdout); + + /* see debug, this is no-op for statmount() */ + mnt_fs_get_fstype(fs); + + mnt_unref_fs(fs); + mnt_unref_statmnt(sm); + + return EXIT_SUCCESS; +} diff --git a/libmount/src/Makemodule.am b/libmount/src/Makemodule.am index 66963bb0943..6d42c3cd09c 100644 --- a/libmount/src/Makemodule.am +++ b/libmount/src/Makemodule.am @@ -30,6 +30,8 @@ libmount_la_SOURCES += \ libmount/src/context.c \ libmount/src/context_mount.c \ libmount/src/context_umount.c \ + libmount/src/fs_statmount.c \ + libmount/src/tab_listmount.c \ libmount/src/hooks.c \ libmount/src/hook_mount.c \ libmount/src/hook_mount_legacy.c \ @@ -99,6 +101,7 @@ check_PROGRAMS += \ if LINUX check_PROGRAMS += test_mount_context test_mount_context_mount check_PROGRAMS += test_mount_monitor +check_PROGRAMS += test_mount_listmount test_mount_statmount endif libmount_tests_cflags = -DTEST_PROGRAM $(libmount_la_CFLAGS) @@ -182,6 +185,16 @@ test_mount_debug_CFLAGS = $(libmount_tests_cflags) test_mount_debug_LDFLAGS = $(libmount_tests_ldflags) test_mount_debug_LDADD = $(libmount_tests_ldadd) +test_mount_listmount_SOURCES = libmount/samples/listmount.c +test_mount_listmount_CFLAGS = $(libmount_tests_cflags) +test_mount_listmount_LDFLAGS = $(libmount_tests_ldflags) +test_mount_listmount_LDADD = $(libmount_tests_ldadd) + +test_mount_statmount_SOURCES = libmount/samples/statmount.c +test_mount_statmount_CFLAGS = $(libmount_tests_cflags) +test_mount_statmount_LDFLAGS = $(libmount_tests_ldflags) +test_mount_statmount_LDADD = $(libmount_tests_ldadd) + if FUZZING_ENGINE check_PROGRAMS += test_mount_fuzz diff --git a/libmount/src/btrfs.c b/libmount/src/btrfs.c index a831ce83756..eb569f25825 100644 --- a/libmount/src/btrfs.c +++ b/libmount/src/btrfs.c @@ -15,7 +15,6 @@ #include #include #include -#include #include #include "mountP.h" diff --git a/libmount/src/context.c b/libmount/src/context.c index a9cb4ef26cb..a376a567d0c 100644 --- a/libmount/src/context.c +++ b/libmount/src/context.c @@ -44,8 +44,6 @@ #include #include -#include "mount-api-utils.h" - /** * mnt_new_context: * diff --git a/libmount/src/fs.c b/libmount/src/fs.c index 26f2c69e90a..323b74d7a46 100644 --- a/libmount/src/fs.c +++ b/libmount/src/fs.c @@ -100,6 +100,11 @@ void mnt_reset_fs(struct libmnt_fs *fs) fs->optlist = NULL; fs->opts_age = 0; + fs->propagation = 0; + + mnt_unref_statmnt(fs->stmnt); + fs->stmnt = NULL; + fs->stmnt_done = 0; memset(fs, 0, sizeof(*fs)); INIT_LIST_HEAD(&fs->ents); @@ -610,7 +615,12 @@ int mnt_fs_get_tag(struct libmnt_fs *fs, const char **name, const char **value) */ const char *mnt_fs_get_target(struct libmnt_fs *fs) { - return fs ? fs->target : NULL; + if (!fs) + return NULL; +#ifdef HAVE_STATMOUNT_API + mnt_fs_try_statmount(fs, target, STATMOUNT_MNT_POINT); +#endif + return fs->target;; } /** @@ -656,22 +666,24 @@ int mnt_fs_get_propagation(struct libmnt_fs *fs, unsigned long *flags) { if (!fs || !flags) return -EINVAL; +#ifdef HAVE_STATMOUNT_API + mnt_fs_try_statmount(fs, propagation, STATMOUNT_MNT_BASIC); +#endif + if (!fs->propagation && fs->opt_fields) { + /* + * The optional fields format is incompatible with mount options + * ... we have to parse the field here. + */ + fs->propagation |= strstr(fs->opt_fields, "shared:") ? + MS_SHARED : MS_PRIVATE; + + if (strstr(fs->opt_fields, "master:")) + fs->propagation |= MS_SLAVE; + if (strstr(fs->opt_fields, "unbindable")) + fs->propagation |= MS_UNBINDABLE; + } - *flags = 0; - - if (!fs->opt_fields) - return 0; - - /* - * The optional fields format is incompatible with mount options - * ... we have to parse the field here. - */ - *flags |= strstr(fs->opt_fields, "shared:") ? MS_SHARED : MS_PRIVATE; - - if (strstr(fs->opt_fields, "master:")) - *flags |= MS_SLAVE; - if (strstr(fs->opt_fields, "unbindable")) - *flags |= MS_UNBINDABLE; + *flags = fs->propagation; return 0; } @@ -706,6 +718,11 @@ int mnt_fs_is_swaparea(struct libmnt_fs *fs) */ int mnt_fs_is_pseudofs(struct libmnt_fs *fs) { + if (!fs) + return 0; +#ifdef HAVE_STATMOUNT_API + mnt_fs_try_statmount(fs, fstype, STATMOUNT_FS_TYPE); +#endif return mnt_fs_get_flags(fs) & MNT_FS_PSEUDO ? 1 : 0; } @@ -717,6 +734,11 @@ int mnt_fs_is_pseudofs(struct libmnt_fs *fs) */ int mnt_fs_is_netfs(struct libmnt_fs *fs) { + if (!fs) + return 0; +#ifdef HAVE_STATMOUNT_API + mnt_fs_try_statmount(fs, fstype, STATMOUNT_FS_TYPE); +#endif return mnt_fs_get_flags(fs) & MNT_FS_NET ? 1 : 0; } @@ -743,7 +765,12 @@ int mnt_fs_is_regularfs(struct libmnt_fs *fs) */ const char *mnt_fs_get_fstype(struct libmnt_fs *fs) { - return fs ? fs->fstype : NULL; + if (!fs) + return NULL; +#ifdef HAVE_STATMOUNT_API + mnt_fs_try_statmount(fs, fstype, STATMOUNT_FS_TYPE); +#endif + return fs->fstype; } /* Used by the struct libmnt_file parser only */ @@ -795,18 +822,8 @@ int mnt_fs_set_fstype(struct libmnt_fs *fs, const char *fstype) } /* - * Merges @vfs and @fs options strings into a new string. - * This function cares about 'ro/rw' options. The 'ro' is - * always used if @vfs or @fs is read-only. - * For example: - * - * mnt_merge_optstr("rw,noexec", "ro,journal=update") - * - * returns: "ro,noexec,journal=update" - * - * mnt_merge_optstr("rw,noexec", "rw,journal=update") - * - * returns: "rw,noexec,journal=update" + * Merges @vfs and @fs options strings into a new string. This function cares + * about 'ro/rw' options. The 'ro' is always used if @vfs or @fs is read-only. */ static char *merge_optstr(const char *vfs, const char *fs) { @@ -848,24 +865,10 @@ static char *merge_optstr(const char *vfs, const char *fs) return res; } -/** - * mnt_fs_strdup_options: - * @fs: fstab/mtab/mountinfo entry pointer - * - * Merges all mount options (VFS, FS and userspace) to one options string - * and returns the result. This function does not modify @fs. - * - * Returns: pointer to string (can be freed by free(3)) or NULL in case of error. - */ -char *mnt_fs_strdup_options(struct libmnt_fs *fs) +static char *fs_strdup_options(struct libmnt_fs *fs) { char *res; - if (!fs) - return NULL; - if (fs->optlist) - sync_opts_from_optlist(fs, fs->optlist); - errno = 0; if (fs->optstr) return strdup(fs->optstr); @@ -881,6 +884,30 @@ char *mnt_fs_strdup_options(struct libmnt_fs *fs) return res; } +/** + * mnt_fs_strdup_options: + * @fs: fstab/mtab/mountinfo entry pointer + * + * Merges all mount options (VFS, FS and userspace) to one options string + * and returns the result. This function does not modify @fs. + * + * Returns: pointer to string (can be freed by free(3)) or NULL in case of error. + */ +char *mnt_fs_strdup_options(struct libmnt_fs *fs) +{ + if (!fs) + return NULL; + if (fs->optlist) + sync_opts_from_optlist(fs, fs->optlist); +#ifdef HAVE_STATMOUNT_API + else + mnt_fs_try_statmount(fs, optstr, STATMOUNT_SB_BASIC + | STATMOUNT_MNT_BASIC | STATMOUNT_MNT_OPTS); +#endif + return fs_strdup_options(fs); + +} + /** * mnt_fs_get_options: * @fs: fstab/mtab/mountinfo entry pointer @@ -889,10 +916,19 @@ char *mnt_fs_strdup_options(struct libmnt_fs *fs) */ const char *mnt_fs_get_options(struct libmnt_fs *fs) { - if (fs && fs->optlist) + if (!fs) + return NULL; + if (fs->optlist) sync_opts_from_optlist(fs, fs->optlist); - - return fs ? fs->optstr : NULL; +#ifdef HAVE_STATMOUNT_API + else { + mnt_fs_try_statmount(fs, optstr, STATMOUNT_SB_BASIC + | STATMOUNT_MNT_BASIC | STATMOUNT_MNT_OPTS); + if (!fs->optstr) + fs->optstr = fs_strdup_options(fs); + } +#endif + return fs->optstr; } /** @@ -1061,7 +1097,10 @@ const char *mnt_fs_get_fs_options(struct libmnt_fs *fs) return NULL; if (fs->optlist) sync_opts_from_optlist(fs, fs->optlist); - +#ifdef HAVE_STATMOUNT_API + else + mnt_fs_try_statmount(fs, fs_optstr, STATMOUNT_SB_BASIC | STATMOUNT_MNT_OPTS); +#endif return fs->fs_optstr; } @@ -1077,7 +1116,10 @@ const char *mnt_fs_get_vfs_options(struct libmnt_fs *fs) return NULL; if (fs->optlist) sync_opts_from_optlist(fs, fs->optlist); - +#ifdef HAVE_STATMOUNT_API + else + mnt_fs_try_statmount(fs, vfs_optstr, STATMOUNT_MNT_BASIC); +#endif return fs->vfs_optstr; } @@ -1153,6 +1195,9 @@ const char *mnt_fs_get_attributes(struct libmnt_fs *fs) * that information stored in userspace will not be available for libmount * after CLONE_FS unshare. Be careful, and don't use attributes if possible. * + * Please note that the new mount kernel API calls some VFS flags "mount attributes" + * (MOUNT_ATTR_*), but these flags are not related to the old libmount functionality. + * * Returns: 0 on success or negative number in case of error. */ int mnt_fs_set_attributes(struct libmnt_fs *fs, const char *optstr) @@ -1257,7 +1302,12 @@ int mnt_fs_set_passno(struct libmnt_fs *fs, int passno) */ const char *mnt_fs_get_root(struct libmnt_fs *fs) { - return fs ? fs->root : NULL; + if (!fs) + return NULL; +#ifdef HAVE_STATMOUNT_API + mnt_fs_try_statmount(fs, root, STATMOUNT_MNT_ROOT); +#endif + return fs->root; } /** @@ -1360,11 +1410,60 @@ int mnt_fs_set_bindsrc(struct libmnt_fs *fs, const char *src) * mnt_fs_get_id: * @fs: /proc/self/mountinfo entry * - * Returns: mount ID (unique identifier of the mount) or negative number in case of error. + * This ID is "old" and used in mountinfo only. Since Linux v6.8 there is also unique + * 64-bit ID, see mnt_fs_get_uniq_id(). + * + * Returns: mount ID or negative number in case of error. */ int mnt_fs_get_id(struct libmnt_fs *fs) { - return fs ? fs->id : -EINVAL; + if (!fs) + return 0; +#ifdef HAVE_STATMOUNT_API + mnt_fs_try_statmount(fs, id, STATMOUNT_MNT_BASIC); +#endif + return fs->id; +} + +/** + * mnt_fs_get_uniq_id: + * @fs: filesystem instance + * + * This ID is provided by statmount() or statx(STATX_MNT_ID_UNIQUE) since Linux + * kernel since v6.8. + * + * Returns: unique mount ID + * + * Since: 2.41 + */ +uint64_t mnt_fs_get_uniq_id(struct libmnt_fs *fs) +{ + if (!fs) + return 0; +#ifdef HAVE_STATMOUNT_API + mnt_fs_try_statmount(fs, uniq_id, STATMOUNT_MNT_BASIC); +#endif + return fs->uniq_id; +} + +/** + * mnt_fs_set_uniq_id: + * @fs: filesystem instance + * @id: mount node ID + * + * This ID is provided by statmount() or statx(STATX_MNT_ID_UNIQUE) since Linux + * kernel since v6.8. + * + * Returns: 0 or negative number in case of error. + * + * Since: 2.41 + */ +int mnt_fs_set_uniq_id(struct libmnt_fs *fs, uint64_t id) +{ + if (!fs) + return -EINVAL; + fs->uniq_id = id; + return 0; } /** @@ -1375,9 +1474,70 @@ int mnt_fs_get_id(struct libmnt_fs *fs) */ int mnt_fs_get_parent_id(struct libmnt_fs *fs) { - return fs ? fs->parent : -EINVAL; + if (!fs) + return 0; +#ifdef HAVE_STATMOUNT_API + mnt_fs_try_statmount(fs, parent, STATMOUNT_MNT_BASIC); +#endif + return fs->parent; +} + +/** + * mnt_fs_get_parent_uniq_id: + * @fs: filesystem instance + * + * This ID is provided by statmount() since Linux kernel since v6.8. + * + * Returns: parent mount ID or 0 if not avalable + */ +uint64_t mnt_fs_get_parent_uniq_id(struct libmnt_fs *fs) +{ + if (!fs) + return 0; +#ifdef HAVE_STATMOUNT_API + mnt_fs_try_statmount(fs, uniq_parent, STATMOUNT_MNT_BASIC); +#endif + return fs->uniq_parent; +} + +/** + * mnt_fs_get_ns: + * @fs: filesystem instance + * + * This ID is provided by statmount() since Linux kernel since v6.10 + * + * Returns: parent namespace ID or 0 if not avalable. + * + * Since: 2.41 + */ +uint64_t mnt_fs_get_ns(struct libmnt_fs *fs) +{ + if (!fs) + return 0; +#ifdef HAVE_STATMOUNT_API + mnt_fs_try_statmount(fs, ns_id, STATMOUNT_MNT_NS_ID); +#endif + return fs->ns_id; +} + +/** + * mnt_fs_set_ns: + * @fs: filesystem instance + * @id: namespace ID (or 0) + * + * Returns: 0 or <0 in case of error. + * + * Sinse: 2.41 + */ +int mnt_fs_set_ns(struct libmnt_fs *fs, uint64_t id) +{ + if (!fs) + return -EINVAL; + fs->ns_id = id; + return 0; } + /** * mnt_fs_get_devno: * @fs: /proc/self/mountinfo entry @@ -1386,7 +1546,12 @@ int mnt_fs_get_parent_id(struct libmnt_fs *fs) */ dev_t mnt_fs_get_devno(struct libmnt_fs *fs) { - return fs ? fs->devno : 0; + if (!fs) + return 0; +#ifdef HAVE_STATMOUNT_API + mnt_fs_try_statmount(fs, devno, STATMOUNT_SB_BASIC); +#endif + return fs->devno; } /** @@ -1419,7 +1584,10 @@ int mnt_fs_get_option(struct libmnt_fs *fs, const char *name, if (fs->optlist) sync_opts_from_optlist(fs, fs->optlist); - +#ifdef HAVE_STATMOUNT_API + else + mnt_fs_try_statmount(fs, vfs_optstr, STATMOUNT_SB_BASIC | STATMOUNT_MNT_BASIC); +#endif if (fs->fs_optstr) rc = mnt_optstr_get_option(fs->fs_optstr, name, value, valsz); if (rc == 1 && fs->vfs_optstr) @@ -1523,7 +1691,12 @@ int mnt_fs_match_target(struct libmnt_fs *fs, const char *target, { int rc = 0; - if (!fs || !target || !fs->target) + if (!fs || !target) + return 0; +#ifdef HAVE_STATMOUNT_API + mnt_fs_try_statmount(fs, target, STATMOUNT_MNT_BASIC); +#endif + if (!fs->target) return 0; /* 1) native paths */ @@ -1640,7 +1813,7 @@ int mnt_fs_match_source(struct libmnt_fs *fs, const char *source, */ int mnt_fs_match_fstype(struct libmnt_fs *fs, const char *types) { - return mnt_match_fstype(fs->fstype, types); + return mnt_match_fstype(mnt_fs_get_fstype(fs), types); } /** @@ -1667,16 +1840,25 @@ int mnt_fs_match_options(struct libmnt_fs *fs, const char *options) */ int mnt_fs_print_debug(struct libmnt_fs *fs, FILE *file) { + unsigned long pro = 0; + int stmnt_disabled = 1; + if (!fs || !file) return -EINVAL; if (fs->optlist) sync_opts_from_optlist(fs, fs->optlist); + if (fs->stmnt) + stmnt_disabled = mnt_statmnt_disable_fetching(fs->stmnt, 1); + fprintf(file, "------ fs:\n"); - fprintf(file, "source: %s\n", mnt_fs_get_source(fs)); - fprintf(file, "target: %s\n", mnt_fs_get_target(fs)); - fprintf(file, "fstype: %s\n", mnt_fs_get_fstype(fs)); + if (mnt_fs_get_source(fs)) + fprintf(file, "source: %s\n", mnt_fs_get_source(fs)); + if (mnt_fs_get_target(fs)) + fprintf(file, "target: %s\n", mnt_fs_get_target(fs)); + if (mnt_fs_get_fstype(fs)) + fprintf(file, "fstype: %s\n", mnt_fs_get_fstype(fs)); if (mnt_fs_get_options(fs)) fprintf(file, "optstr: %s\n", mnt_fs_get_options(fs)); @@ -1691,6 +1873,12 @@ int mnt_fs_print_debug(struct libmnt_fs *fs, FILE *file) if (mnt_fs_get_attributes(fs)) fprintf(file, "attributes: %s\n", mnt_fs_get_attributes(fs)); + if (mnt_fs_get_propagation(fs, &pro) == 0 && pro) + fprintf(file, "propagation: %s %s %s\n", + pro & MS_SHARED ? "shared" : "private", + pro & MS_SLAVE ? "slave" : "", + pro & MS_UNBINDABLE ? "unbindable" : ""); + if (mnt_fs_get_root(fs)) fprintf(file, "root: %s\n", mnt_fs_get_root(fs)); @@ -1713,6 +1901,11 @@ int mnt_fs_print_debug(struct libmnt_fs *fs, FILE *file) fprintf(file, "id: %d\n", mnt_fs_get_id(fs)); if (mnt_fs_get_parent_id(fs)) fprintf(file, "parent: %d\n", mnt_fs_get_parent_id(fs)); + if (mnt_fs_get_uniq_id(fs)) + fprintf(file, "uniq-id: %" PRIu64 "\n", mnt_fs_get_uniq_id(fs)); + if (mnt_fs_get_parent_uniq_id(fs)) + fprintf(file, "uniq-parent: %" PRIu64 "\n", mnt_fs_get_parent_uniq_id(fs)); + if (mnt_fs_get_devno(fs)) fprintf(file, "devno: %d:%d\n", major(mnt_fs_get_devno(fs)), minor(mnt_fs_get_devno(fs))); @@ -1721,6 +1914,8 @@ int mnt_fs_print_debug(struct libmnt_fs *fs, FILE *file) if (mnt_fs_get_comment(fs)) fprintf(file, "comment: '%s'\n", mnt_fs_get_comment(fs)); + if (fs->stmnt) + mnt_statmnt_disable_fetching(fs->stmnt, stmnt_disabled); return 0; } diff --git a/libmount/src/fs_statmount.c b/libmount/src/fs_statmount.c new file mode 100644 index 00000000000..ba21a5addd4 --- /dev/null +++ b/libmount/src/fs_statmount.c @@ -0,0 +1,399 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * This file is part of libmount from util-linux project. + * + * Copyright (C) 2024 Karel Zak + * + * libmount is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + */ + +/** + * SECTION: statmount + * @title: statmount setting + * @short_description: Fetches information about mount node from the kernel. + */ +#include "mountP.h" + +#include "mangle.h" + +/** + * mnt_new_statmnt: + * + * The initial refcount is 1, and needs to be decremented to + * release the resources of the filesystem. + * + * Returns: newly allocated struct libmnt_statmnt. + */ +struct libmnt_statmnt *mnt_new_statmnt(void) +{ +#ifdef HAVE_STATMOUNT_API + struct libmnt_statmnt *sm; + + errno = 0; + if (ul_statmount(0, 0, 0, NULL, 0, 0) < 0 && errno == ENOSYS) { + DBG(FS, ul_debug("statmount: unsupported")); + return NULL; + } + + sm = calloc(1, sizeof(*sm)); + if (!sm) + return NULL; + + sm->refcount = 1; + DBG(STATMNT, ul_debugobj(sm, "alloc")); + return sm; +#else + errno = ENOSYS; + return NULL; +#endif +} + +/** + * mnt_ref_statmount: + * @sm: statmount setting + * + * Increments reference counter. + */ +void mnt_ref_statmnt(struct libmnt_statmnt *sm) +{ + if (sm) { + sm->refcount++; + /*DBG(STATMNT, ul_debugobj(sm, "ref=%d", sm->refcount));*/ + } +} + +/** + * mnt_unref_statmnt: + * @sm: statmount setting + * + * De-increments reference counter, on zero the @sm is automatically + * deallocated. + */ +void mnt_unref_statmnt(struct libmnt_statmnt *sm) +{ + if (sm) { + sm->refcount--; + /*DBG(STATMNT, ul_debugobj(sm, "unref=%d", sm->refcount));*/ + if (sm->refcount <= 0) { + free(sm->buf); + free(sm); + } + } +} + +/** + * mnt_statmnt_set_mask: + * @sm: statmount setting + * @mask: default mask for statmount() or 0 + * + * Returns: 0 on succees or or <0 on error. + */ +int mnt_statmnt_set_mask(struct libmnt_statmnt *sm, uint64_t mask) +{ + if (!sm) + return -EINVAL; + sm->mask = mask; + + DBG(STATMNT, ul_debugobj(sm, "mask=0x%" PRIx64, sm->mask)); + return 0; +} + +/** + * mnt_statmnt_disable_fetching: + * @sm: statmount setting + * @disable: 0 or 1 + * + * Disable or enable on-demand statmount() in all libmnt_table of libmnt_fs + * onjects that references this @sm. + * + * Returns: current setting (0 or 1) or <0 on error. + * + * Since: 2.41 + */ +int mnt_statmnt_disable_fetching(struct libmnt_statmnt *sm, int disable) +{ + int old; + + if (!sm) + return -EINVAL; + old = sm->disabled; + sm->disabled = disable ? 1 : 0; + + /* + DBG(STATMNT, ul_debugobj(sm, "statmount() %s", + sm->disabled ? "off" : "on")); + */ + return old; +} + +/** + * mnt_fs_refer_statmnt: + * @fs: filesystem + * @sm: statmount() setting + * + * Add a reference to the statmount() setting. This setting can be overwritten + * if you add @fs into a table that uses a different statmount() setting. See + * mnt_table_refer_statmnt(). It is recommended to use the statmount() setting + * on a table level if you do not want to work with complete mount table. + * + * The function mnt_reset_fs() removes this reference too. + * + * Returns: 0 on success or negative number in case of error. + * + * Since: 2.41 + */ +int mnt_fs_refer_statmnt(struct libmnt_fs *fs, struct libmnt_statmnt *sm) +{ + if (!fs) + return -EINVAL; + if (fs->stmnt == sm) + return 0; + + mnt_unref_statmnt(fs->stmnt); + mnt_ref_statmnt(sm); + + fs->stmnt = sm; + return 0; +} + +/** + * mnt_fs_get_statmnt: + * @fs: filesystem + * + * Returns: pointer to linmnt_statmnt instance used for the filesystem or NULL + * + * Since: 2.41 + */ +struct libmnt_statmnt *mnt_fs_get_statmnt(struct libmnt_fs *fs) +{ + return fs ? fs->stmnt : NULL; +} + +#ifdef HAVE_STATMOUNT_API + +static inline const char *sm_str(struct ul_statmount *sm, uint32_t offset) +{ + return sm->str + offset; +} + +static int apply_statmount(struct libmnt_fs *fs, struct ul_statmount *sm) +{ + int rc = 0; + + if (!sm || !sm->size || !fs) + return -EINVAL; + + if ((sm->mask & STATMOUNT_FS_TYPE) && !fs->fstype) + rc = mnt_fs_set_fstype(fs, sm_str(sm, sm->fs_type)); + + if (!rc && (sm->mask & STATMOUNT_MNT_POINT) && !fs->target) + rc = mnt_fs_set_target(fs, sm_str(sm, sm->mnt_point)); + + if (!rc && (sm->mask & STATMOUNT_MNT_ROOT) && !fs->root) + rc = mnt_fs_set_root(fs, sm_str(sm, sm->mnt_root)); + + if (!rc && (sm->mask & STATMOUNT_MNT_BASIC)) { + if (!fs->propagation) + fs->propagation = sm->mnt_propagation; + if (!fs->parent) + fs->parent = sm->mnt_parent_id_old; + if (!fs->uniq_parent) + fs->uniq_parent = sm->mnt_parent_id; + if (!fs->id) + fs->id = sm->mnt_id_old; + if (!fs->uniq_id) + fs->uniq_id = sm->mnt_id; + if (!fs->vfs_optstr) { + rc = mnt_optstr_append_option(&fs->vfs_optstr, + sm->mnt_attr & MOUNT_ATTR_RDONLY ? "ro" : "rw", NULL); + if (!rc && (sm->mnt_attr & MOUNT_ATTR_NOSUID)) + rc = mnt_optstr_append_option(&fs->vfs_optstr, "nosuid", NULL); + if (!rc && (sm->mnt_attr & MOUNT_ATTR_NODEV)) + rc = mnt_optstr_append_option(&fs->vfs_optstr, "nodev", NULL); + if (!rc && (sm->mnt_attr & MOUNT_ATTR_NOEXEC)) + rc = mnt_optstr_append_option(&fs->vfs_optstr, "noexec", NULL); + if (!rc && (sm->mnt_attr & MOUNT_ATTR_NODIRATIME)) + rc = mnt_optstr_append_option(&fs->vfs_optstr, "nodiratime", NULL); + if (!rc && (sm->mnt_attr & MOUNT_ATTR_NOSYMFOLLOW)) + rc = mnt_optstr_append_option(&fs->vfs_optstr, "nosymfollow", NULL); + + switch (sm->mnt_attr & MOUNT_ATTR__ATIME) { + case MOUNT_ATTR_STRICTATIME: + rc = mnt_optstr_append_option(&fs->vfs_optstr, "strictatime", NULL); + break; + case MOUNT_ATTR_NOATIME: + rc = mnt_optstr_append_option(&fs->vfs_optstr, "noatime", NULL); + break; + case MOUNT_ATTR_RELATIME: + rc = mnt_optstr_append_option(&fs->vfs_optstr, "relatime", NULL); + break; + } + free(fs->optstr); + fs->optstr = NULL; + } + } + + if (!rc && (sm->mask & STATMOUNT_MNT_NS_ID) && !fs->ns_id) + fs->ns_id = sm->mnt_ns_id; + + if (!rc && (sm->mask & STATMOUNT_MNT_OPTS) && !fs->fs_optstr) { + fs->fs_optstr = unmangle(sm_str(sm, sm->mnt_opts), NULL); + free(fs->optstr); + fs->optstr = NULL; + } + + if (!rc && (sm->mask & STATMOUNT_SB_BASIC)) { + if (!fs->devno) + fs->devno = makedev(sm->sb_dev_major, sm->sb_dev_minor); + if (!fs->fs_optstr) { + rc = mnt_optstr_append_option(&fs->fs_optstr, + sm->sb_flags & SB_RDONLY ? "ro" : "rw", NULL); + if (!rc && (sm->sb_flags & SB_SYNCHRONOUS)) + rc = mnt_optstr_append_option(&fs->fs_optstr, "sync", NULL); + if (!rc && (sm->sb_flags & SB_DIRSYNC)) + rc = mnt_optstr_append_option(&fs->fs_optstr, "dirsync", NULL); + if (!rc && (sm->sb_flags & SB_LAZYTIME)) + rc = mnt_optstr_append_option(&fs->fs_optstr, "lazytime", NULL); + free(fs->optstr); + fs->optstr = NULL; + } + } + + fs->flags |= MNT_FS_KERNEL; + + return rc; +} + +/** + * mnt_fs_fetch_statmount: + * @fs: filesystem instance + * @mask: extends the default statmount() mask. + * + * This function retrieves mount node information from the kernel and applies it + * to the @fs. If the @fs is associated with any libmnt_statmnt object (see + * mnt_fs_refer_statmnt()), then this object is used to reduce the overhead of + * allocating statmount buffer and to define default statmount() mask. + * + * The @mask is extended (bitwise-OR) by the mask specified for + * mnt_statmnt_set_mask() if on-demand fetching is enabled. If the mask is + * still 0, then a mask is generated for all missing data in @fs. + * + * The default namespace is the current namespace. This default can be + * overwritten by mnt_fs_set_ns_id(). The namespace ID is also set when @fs + * has been created by mnt_table_fetch_statmount() or on-demand (see + * mnt_table_enable_listmount()). + * + * Returns: 0 or negative number in case of error (if @fs is NULL). + * + * Since: 2.41 + */ +int mnt_fs_fetch_statmount(struct libmnt_fs *fs, uint64_t mask) +{ + int rc = 0, status = 0; + struct ul_statmount *buf = NULL; + size_t bufsiz = 0; + uint64_t ns = 0; + + if (!fs) + return -EINVAL; + + DBG(FS, ul_debugobj(fs, "statmount fetch")); + + /* add default mask if on-demand enabled */ + if (fs->stmnt + && !fs->stmnt->disabled + && fs->stmnt->mask) + mask |= fs->stmnt->mask; + + /* call only for missing stuff */ + if (mask && fs->stmnt_done) { + mask &= ~fs->stmnt_done; /* remove what is already done */ + if (!mask) + return 0; + } + + /* ignore repeated requests */ + if (mask && fs->stmnt_done & mask) + return 0; + + /* temporary disable statmount() to avoid recursive + * mnt_fs_fetch_statmount() from mnt_fs_get...() functions */ + if (fs->stmnt) + status = mnt_statmnt_disable_fetching(fs->stmnt, 1); + + if (!fs->uniq_id) { + if (!fs->target) { + rc = -EINVAL; + goto done; + } + rc = mnt_id_from_path(fs->target, &fs->uniq_id, NULL); + if (rc) + goto done; + DBG(FS, ul_debugobj(fs, " uniq-ID=%" PRIu64, fs->uniq_id)); + } + + /* fetch all missing information by default */ + if (!mask) { + mask = STATMOUNT_SB_BASIC | STATMOUNT_MNT_BASIC; + if (!fs->fstype) + mask |= STATMOUNT_FS_TYPE; + if (!fs->target) + mask |= STATMOUNT_MNT_POINT; + if (!fs->root) + mask |= STATMOUNT_MNT_ROOT; + if (!fs->fs_optstr) + mask |= STATMOUNT_MNT_OPTS; + if (!fs->ns_id) + mask |= STATMOUNT_MNT_NS_ID; + } + if (!mask) + goto done; + + if (fs->ns_id) + ns = fs->ns_id; + + if (fs->stmnt) { + DBG(FS, ul_debugobj(fs, " reuse libmnt_stmnt")); + memset(fs->stmnt->buf, 0, fs->stmnt->bufsiz); + rc = sys_statmount(fs->uniq_id, 0, mask, + &fs->stmnt->buf, &fs->stmnt->bufsiz, 0); + buf = fs->stmnt->buf; + bufsiz = fs->stmnt->bufsiz; + } else { + DBG(FS, ul_debugobj(fs, " use private buffer")); + rc = sys_statmount(fs->uniq_id, 0, mask, &buf, &bufsiz, 0); + } + DBG(FS, ul_debugobj(fs, " statmount [rc=%d bufsiz=%zu ns=%" PRIu64 " mask: %s%s%s%s%s%s]", + rc, bufsiz, ns, + mask & STATMOUNT_SB_BASIC ? "sb-basic " : "", + mask & STATMOUNT_MNT_BASIC ? "mnt-basic " : "", + mask & STATMOUNT_MNT_ROOT ? "mnt-root " : "", + mask & STATMOUNT_MNT_POINT ? "mnt-point " : "", + mask & STATMOUNT_FS_TYPE ? "fs-type " : "", + mask & STATMOUNT_MNT_OPTS ? "mnt-opts " : "")); + + if (!rc) + rc = apply_statmount(fs, buf); +done: + + if (fs->stmnt) + mnt_statmnt_disable_fetching(fs->stmnt, status); + else + free(buf); + + fs->stmnt_done |= mask; + return rc; +} + +#else /* HAVE_STATMOUNT_API */ + +int mnt_fs_fetch_statmount(struct libmnt_fs *fs __attribute__((__unused__)), + uint64_t mask __attribute__((__unused__))) +{ + return -ENOTSUP; +} + +#endif /* HAVE_STATMOUNT_API */ diff --git a/libmount/src/fuzz.c b/libmount/src/fuzz.c index 2c847144302..f4c0f8ae66b 100644 --- a/libmount/src/fuzz.c +++ b/libmount/src/fuzz.c @@ -4,7 +4,6 @@ #include #include -#include int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) { struct libmnt_table *tb = NULL; diff --git a/libmount/src/hook_idmap.c b/libmount/src/hook_idmap.c index 9b2425a77de..ddf1549c900 100644 --- a/libmount/src/hook_idmap.c +++ b/libmount/src/hook_idmap.c @@ -20,12 +20,10 @@ #include #include #include -#include #include "strutils.h" #include "all-io.h" #include "namespace.h" -#include "mount-api-utils.h" #include "mountP.h" diff --git a/libmount/src/hook_mount.c b/libmount/src/hook_mount.c index 736160f0e48..bfb9bc55877 100644 --- a/libmount/src/hook_mount.c +++ b/libmount/src/hook_mount.c @@ -46,11 +46,8 @@ #include "mountP.h" #include "fileutils.h" /* statx() fallback */ #include "strutils.h" -#include "mount-api-utils.h" #include "linux_version.h" -#include - #ifdef USE_LIBMOUNT_MOUNTFD_SUPPORT #define get_sysapi(_cxt) mnt_context_get_sysapi(_cxt) diff --git a/libmount/src/hook_subdir.c b/libmount/src/hook_subdir.c index 1ccf7b520c6..5949af7d824 100644 --- a/libmount/src/hook_subdir.c +++ b/libmount/src/hook_subdir.c @@ -19,7 +19,6 @@ #include "mountP.h" #include "fileutils.h" -#include "mount-api-utils.h" struct hookset_data { char *subdir; diff --git a/libmount/src/hooks.c b/libmount/src/hooks.c index 85c7475d377..79331050c7d 100644 --- a/libmount/src/hooks.c +++ b/libmount/src/hooks.c @@ -27,7 +27,6 @@ * Usually implemented by locally defined 'struct hook_data' in hook_*.c. */ #include "mountP.h" -#include "mount-api-utils.h" /* built-in hooksets */ static const struct libmnt_hookset *hooksets[] = diff --git a/libmount/src/libmount.h.in b/libmount/src/libmount.h.in index 0a2d821996e..555c7be343d 100644 --- a/libmount/src/libmount.h.in +++ b/libmount/src/libmount.h.in @@ -31,6 +31,7 @@ extern "C" { #include #include #include +#include /* Make sure libc MS_* definitions are used by default. Note that MS_* flags * may be already defined by linux/fs.h or another file -- in this case we @@ -140,6 +141,13 @@ struct libmnt_tabdiff; */ struct libmnt_ns; +/** + * libmnt_statmnt + * + * Setting for statmount() + */ +struct libmnt_statmnt; + /* * Actions */ @@ -368,6 +376,8 @@ extern char *mnt_get_mountpoint(const char *path) extern int mnt_guess_system_root(dev_t devno, struct libmnt_cache *cache, char **path) __ul_attribute__((nonnull(3))); +extern int mnt_id_from_path(const char *path, uint64_t *uniq_id, int *id); + /* cache.c */ extern struct libmnt_cache *mnt_new_cache(void) __ul_attribute__((warn_unused_result)); @@ -530,8 +540,17 @@ extern const char *mnt_fs_get_root(struct libmnt_fs *fs); extern int mnt_fs_set_root(struct libmnt_fs *fs, const char *path); extern const char *mnt_fs_get_bindsrc(struct libmnt_fs *fs); extern int mnt_fs_set_bindsrc(struct libmnt_fs *fs, const char *src); + extern int mnt_fs_get_id(struct libmnt_fs *fs); +extern uint64_t mnt_fs_get_uniq_id(struct libmnt_fs *fs); +extern int mnt_fs_set_uniq_id(struct libmnt_fs *fs, uint64_t id); + extern int mnt_fs_get_parent_id(struct libmnt_fs *fs); +extern uint64_t mnt_fs_get_parent_uniq_id(struct libmnt_fs *fs); + +extern uint64_t mnt_fs_get_ns(struct libmnt_fs *fs); +extern int mnt_fs_set_ns(struct libmnt_fs *fs, uint64_t id); + extern dev_t mnt_fs_get_devno(struct libmnt_fs *fs); extern pid_t mnt_fs_get_tid(struct libmnt_fs *fs); @@ -562,7 +581,18 @@ extern int mnt_fs_is_regularfs(struct libmnt_fs *fs); extern void mnt_free_mntent(struct mntent *mnt); extern int mnt_fs_to_mntent(struct libmnt_fs *fs, struct mntent **mnt); -/* tab-parse.c */ +/* fs_statmount.c */ +extern struct libmnt_statmnt *mnt_new_statmnt(void); +extern void mnt_ref_statmnt(struct libmnt_statmnt *sm); +extern void mnt_unref_statmnt(struct libmnt_statmnt *sm); +extern int mnt_statmnt_set_mask(struct libmnt_statmnt *sm, uint64_t mask); +extern int mnt_statmnt_disable_fetching(struct libmnt_statmnt *sm, int disable); + +extern int mnt_fs_refer_statmnt(struct libmnt_fs *fs, struct libmnt_statmnt *sm); +extern struct libmnt_statmnt *mnt_fs_get_statmnt(struct libmnt_fs *fs); +extern int mnt_fs_fetch_statmount(struct libmnt_fs *fs, uint64_t mask); + +/* tab_parse.c */ extern struct libmnt_table *mnt_new_table_from_file(const char *filename) __ul_attribute__((warn_unused_result)); extern struct libmnt_table *mnt_new_table_from_dir(const char *dirname) @@ -590,6 +620,8 @@ extern int mnt_reset_table(struct libmnt_table *tb); extern int mnt_table_get_nents(struct libmnt_table *tb); extern int mnt_table_is_empty(struct libmnt_table *tb); +extern int mnt_table_refer_statmnt(struct libmnt_table *tb, struct libmnt_statmnt *sm); + extern int mnt_table_set_userdata(struct libmnt_table *tb, void *data); extern void *mnt_table_get_userdata(struct libmnt_table *tb); @@ -658,6 +690,14 @@ extern int mnt_table_find_next_fs(struct libmnt_table *tb, extern int mnt_table_is_fs_mounted(struct libmnt_table *tb, struct libmnt_fs *fstab_fs); +/* tab_listmount.c */ +extern int mnt_table_listmount_set_id(struct libmnt_table *tb, uint64_t id); +extern int mnt_table_listmount_set_ns(struct libmnt_table *tb, uint64_t ns); +extern int mnt_table_listmount_set_stepsiz(struct libmnt_table *tb, size_t sz); + +extern int mnt_table_enable_listmount(struct libmnt_table *tb, int enable); +extern int mnt_table_fetch_listmount(struct libmnt_table *tb); + /* tab_update.c */ extern struct libmnt_update *mnt_new_update(void) __ul_attribute__((warn_unused_result)); diff --git a/libmount/src/libmount.sym b/libmount/src/libmount.sym index 2b6b12d5c3b..d09b426351b 100644 --- a/libmount/src/libmount.sym +++ b/libmount/src/libmount.sym @@ -381,3 +381,26 @@ MOUNT_2_40 { mnt_unref_lock; mnt_monitor_veil_kernel; } MOUNT_2_39; + +MOUNT_2_41 { + mnt_fs_fetch_statmount; + mnt_fs_get_ns; + mnt_fs_get_parent_uniq_id; + mnt_fs_get_statmnt; + mnt_fs_get_uniq_id; + mnt_fs_refer_statmnt; + mnt_fs_set_ns; + mnt_fs_set_uniq_id; + mnt_id_from_path; + mnt_new_statmnt; + mnt_ref_statmnt; + mnt_statmnt_disable_fetching; + mnt_statmnt_set_mask; + mnt_table_enable_listmount; + mnt_table_fetch_listmount; + mnt_table_listmount_set_id; + mnt_table_listmount_set_ns; + mnt_table_listmount_set_stepsiz; + mnt_table_refer_statmnt; + mnt_unref_statmnt; +} MOUNT_2_40; diff --git a/libmount/src/mountP.h b/libmount/src/mountP.h index ed5f2ec29f2..2e80056ac20 100644 --- a/libmount/src/mountP.h +++ b/libmount/src/mountP.h @@ -23,13 +23,18 @@ #include #include #include +#include +#include #include "c.h" + #include "list.h" #include "debug.h" #include "buffer.h" #include "libmount.h" +#include "mount-api-utils.h" + /* * Debug */ @@ -50,6 +55,7 @@ #define MNT_DEBUG_VERITY (1 << 14) #define MNT_DEBUG_HOOK (1 << 15) #define MNT_DEBUG_OPTLIST (1 << 16) +#define MNT_DEBUG_STATMNT (1 << 17) #define MNT_DEBUG_ALL 0xFFFFFF @@ -96,6 +102,9 @@ struct libmnt_test { extern int mnt_run_test(struct libmnt_test *tests, int argc, char *argv[]); #endif +/* private tab_listmount.c */ +struct libmnt_listmnt; + /* utils.c */ extern int mnt_valid_tagname(const char *tagname); @@ -134,6 +143,8 @@ extern int mnt_is_path(const char *target); extern int mnt_tmptgt_unshare(int *old_ns_fd); extern int mnt_tmptgt_cleanup(int old_ns_fd); +extern int mnt_id_from_fd(int fd, uint64_t *uniq_id, int *id); + /* tab.c */ extern int is_mountinfo(struct libmnt_table *tb); extern int mnt_table_set_parser_fltrcb( struct libmnt_table *tb, @@ -156,6 +167,11 @@ extern int __mnt_table_is_fs_mounted( struct libmnt_table *tb, extern int mnt_table_enable_noautofs(struct libmnt_table *tb, int ignore); extern int mnt_table_is_noautofs(struct libmnt_table *tb); +/* tab_listmount.c */ +extern int mnt_table_next_lsmnt(struct libmnt_table *tb, int direction); +extern int mnt_table_reset_listmount(struct libmnt_table *tb); +extern int mnt_table_want_listmount(struct libmnt_table *tb); + /* * Generic iterator */ @@ -185,6 +201,20 @@ struct libmnt_iter { } while(0) +/* + * statmount setting; shared between tables and filesystems + */ +struct libmnt_statmnt { + int refcount; + uint64_t mask; /* default statmount() mask */ + + struct ul_statmount *buf; + size_t bufsiz; + + unsigned int disabled: 1; /* enable or disable statmount() */ +}; + + /* * This struct represents one entry in a fstab/mountinfo file. * (note that fstab[1] means the first column from fstab, and so on...) @@ -199,7 +229,11 @@ struct libmnt_fs { struct libmnt_optlist *optlist; int id; /* mountinfo[1]: ID */ + uint64_t uniq_id; /* unique node ID; statx(STATX_MNT_ID_UNIQUE); statmount->mnt_id */ + uint64_t ns_id; /* namespace ID; statmount->mnt_ns_id */ + int parent; /* mountinfo[2]: parent */ + uint64_t uniq_parent; /* unique parent ID; statmount->mnt_parent_id */ dev_t devno; /* mountinfo[3]: st_dev */ char *bindsrc; /* utab, full path from fstab[1] for bind mounts */ @@ -215,7 +249,10 @@ struct libmnt_fs { char *optstr; /* fstab[4], merged options */ char *vfs_optstr; /* mountinfo[6]: fs-independent (VFS) options */ + char *opt_fields; /* mountinfo[7]: optional fields */ + uint64_t propagation; /* statmmount() or parsed opt_fields */ + char *fs_optstr; /* mountinfo[11]: fs-dependent options */ char *user_optstr; /* userspace mount options */ char *attrs; /* mount attributes */ @@ -232,6 +269,9 @@ struct libmnt_fs { int flags; /* MNT_FS_* flags */ pid_t tid; /* /proc//mountinfo otherwise zero */ + uint64_t stmnt_done; /* mask of already called masks */ + struct libmnt_statmnt *stmnt; /* statmount() stuff */ + char *comment; /* fstab comment */ void *userdata; /* library independent data */ @@ -246,6 +286,16 @@ struct libmnt_fs { #define MNT_FS_KERNEL (1 << 4) /* data from /proc/{mounts,self/mountinfo} */ #define MNT_FS_MERGED (1 << 5) /* already merged data from /run/mount/utab */ +#ifdef HAVE_STATMOUNT_API +# define mnt_fs_try_statmount(FS, MEMBER, FLAGS) __extension__ ({ \ + if (!(FS)->MEMBER \ + && (FS)->stmnt \ + && !(FS)->stmnt->disabled \ + && ((FLAGS) & ~((FS)->stmnt_done))) \ + mnt_fs_fetch_statmount((FS), (FLAGS)); }) +#endif + + /* * fstab/mountinfo file */ @@ -265,6 +315,9 @@ struct libmnt_table { int (*fltrcb)(struct libmnt_fs *fs, void *data); void *fltrcb_data; + struct libmnt_listmnt *lsmnt; /* listmount() stuff */ + struct libmnt_statmnt *stmnt; /* statmount() stuff */ + int noautofs; /* ignore autofs mounts */ struct list_head ents; /* list of entries (libmnt_fs) */ diff --git a/libmount/src/optlist.c b/libmount/src/optlist.c index 11a7eed047e..33927cd7d89 100644 --- a/libmount/src/optlist.c +++ b/libmount/src/optlist.c @@ -16,7 +16,6 @@ #include "strutils.h" #include "list.h" #include "mountP.h" -#include "mount-api-utils.h" #define MNT_OL_MAXMAPS 8 diff --git a/libmount/src/tab.c b/libmount/src/tab.c index 2b89552eb2f..ba4eef850ad 100644 --- a/libmount/src/tab.c +++ b/libmount/src/tab.c @@ -116,6 +116,8 @@ int mnt_reset_table(struct libmnt_table *tb) } tb->nents = 0; + mnt_table_reset_listmount(tb); + return 0; } @@ -172,6 +174,13 @@ void mnt_free_table(struct libmnt_table *tb) mnt_unref_cache(tb->cache); free(tb->comm_intro); free(tb->comm_tail); + + free(tb->lsmnt); + tb->lsmnt = NULL; + + mnt_unref_statmnt(tb->stmnt); + tb->stmnt = NULL; + free(tb); } @@ -395,6 +404,39 @@ struct libmnt_cache *mnt_table_get_cache(struct libmnt_table *tb) return tb ? tb->cache : NULL; } +/** + * mnt_table_refer_statmnt: + * @tb: pointer to tab + * @sm: statmount setting or NULL + * + * Add a reference to the statmount() setting in the table (see + * mnt_new_statmnt() function, etc.). This reference will automatically be + * used for any newly added filesystems in the @tb, eliminating the need for + * extra mnt_fs_refer_statmnt() calls for each filesystem. + * + * The reference is not removed by mnt_reset_table(), use NULL as @sm to + * remove the reference. + * + * Returns: 0 on success or negative number in case of error. + * + * Since: 2.41 + */ +int mnt_table_refer_statmnt(struct libmnt_table *tb, struct libmnt_statmnt *sm) +{ + if (!tb) + return -EINVAL; + if (tb->stmnt == sm) + return 0; + + mnt_unref_statmnt(tb->stmnt); + mnt_ref_statmnt(sm); + + DBG(TAB, ul_debugobj(tb, "refer statmnt")); + + tb->stmnt = sm; + return 0; +} + /** * mnt_table_find_fs: * @tb: tab pointer @@ -455,6 +497,9 @@ int mnt_table_add_fs(struct libmnt_table *tb, struct libmnt_fs *fs) DBG(TAB, ul_debugobj(tb, "add entry: %s %s", mnt_fs_get_source(fs), mnt_fs_get_target(fs))); + if (tb->stmnt) + mnt_fs_refer_statmnt(fs, tb->stmnt); + return 0; } @@ -462,18 +507,26 @@ static int __table_insert_fs( struct libmnt_table *tb, int before, struct libmnt_fs *pos, struct libmnt_fs *fs) { - struct list_head *head = pos ? &pos->ents : &tb->ents; - - if (before) - list_add(&fs->ents, head); + if (!pos) + list_add_tail(&fs->ents, &tb->ents); + else if (before) + list_add_tail(&fs->ents, &pos->ents); else - list_add_tail(&fs->ents, head); + list_add(&fs->ents, &pos->ents); fs->tab = tb; tb->nents++; - DBG(TAB, ul_debugobj(tb, "insert entry: %s %s", + if (mnt_fs_get_uniq_id(fs)) { + DBG(TAB, ul_debugobj(tb, "insert entry: %" PRIu64, mnt_fs_get_uniq_id(fs))); + } else { + DBG(TAB, ul_debugobj(tb, "insert entry: %s %s", mnt_fs_get_source(fs), mnt_fs_get_target(fs))); + } + + if (tb->stmnt) + mnt_fs_refer_statmnt(fs, tb->stmnt); + return 0; } @@ -802,7 +855,23 @@ int mnt_table_next_fs(struct libmnt_table *tb, struct libmnt_iter *itr, struct l return -EINVAL; if (fs) *fs = NULL; - +#ifdef HAVE_STATMOUNT_API + if (mnt_table_want_listmount(tb) && + (list_empty(&tb->ents) || itr->p == itr->head)) { + struct list_head *prev = NULL; + + if (itr->p) + prev = IS_ITER_FORWARD(itr) ? itr->p->prev : itr->p->next; + rc = mnt_table_next_lsmnt(tb, itr->direction); + if (rc) + return rc; + MNT_ITER_INIT(itr, &tb->ents); + if (prev) { + itr->p = prev; + MNT_ITER_ITERATE(itr); + } + } +#endif if (!itr->head) MNT_ITER_INIT(itr, &tb->ents); if (itr->p != itr->head) { diff --git a/libmount/src/tab_listmount.c b/libmount/src/tab_listmount.c new file mode 100644 index 00000000000..527613a1f8a --- /dev/null +++ b/libmount/src/tab_listmount.c @@ -0,0 +1,423 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * This file is part of libmount from util-linux project. + * + * Copyright (C) 2024 Karel Zak + * + * libmount is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + */ +#include "mountP.h" + +#ifndef HAVE_STATMOUNT_API + +int mnt_table_listmount_set_id( + struct libmnt_table *tb __attribute__((__unused__)), + uint64_t id __attribute__((__unused__))) +{ + return -ENOSYS; +} + +int mnt_table_listmount_set_ns( + struct libmnt_table *tb __attribute__((__unused__)), + uint64_t ns __attribute__((__unused__))) +{ + return -ENOSYS; +} + +int mnt_table_listmount_set_stepsiz( + struct libmnt_table *tb __attribute__((__unused__)), + size_t sz __attribute__((__unused__))) +{ + return -ENOSYS; +} + +int mnt_table_enable_listmount( + struct libmnt_table *tb __attribute__((__unused__)), + int enable __attribute__((__unused__))) +{ + return -ENOSYS; +} + +int mnt_table_fetch_listmount(struct libmnt_table *tb __attribute__((__unused__))) +{ + return -ENOSYS; +} + +int mnt_table_reset_listmount(struct libmnt_table *tb __attribute__((__unused__))) +{ + return -ENOSYS; +} + +int mnt_table_next_lsmnt(struct libmnt_table *tb __attribute__((__unused__)), + int direction __attribute__((__unused__))) +{ + return -ENOSYS; +} + +#else /* HAVE_STATMOUNT_API */ + +/* +* This struct is not shared between multiple tables, so reference counting is +* not used for it. + */ +struct libmnt_listmnt { + + uint64_t id; /* node ID (LSMT_ROOT for "/") */ + uint64_t ns; /* namespce ID or zero for the current */ + uint64_t last; /* last ID from previous listmount() call */ + size_t stepsiz; /* how many IDs read in one step */ + uint64_t *list; /* buffer for IDs */ + + unsigned int enabled : 1, /* on-demand listmount status */ + done : 1, /* we already have all data */ + reverse : 1; /* current setting */ +}; + +/* default number of IDs read by one listmount() call */ +#define MNT_LSMNT_STEPSIZ 512 + +static int table_init_listmount(struct libmnt_table *tb, size_t stepsiz) +{ + struct libmnt_listmnt *ls = NULL;; + + if (!tb) + return -EINVAL; + if (!stepsiz) + stepsiz = MNT_LSMNT_STEPSIZ; + + ls = tb->lsmnt; + + /* check if supported by current kernel */ + if (!ls) { + uint64_t dummy; + + errno = 0; + if (ul_listmount(LSMT_ROOT, 0, 0, &dummy, 1, LISTMOUNT_REVERSE) != 1) { + if (errno == ENOSYS) + DBG(TAB, ul_debugobj(tb, "listmount: unsupported")); + if (errno == EINVAL) + DBG(TAB, ul_debugobj(tb, "listmount: reverse unsupported")); + return -ENOSYS; + } + } + + /* reset if allocated for a different size */ + if (ls && ls->stepsiz != stepsiz) + ls = NULL; + + /* alloc libmnt_listmnt together with list buffer */ + if (!ls) { + char *x = calloc(1, sizeof(struct libmnt_listmnt) + + (sizeof(uint64_t) * stepsiz)); + if (!x) + return -ENOMEM; + + ls = (struct libmnt_listmnt *) x; + ls->list = (uint64_t *) (x + sizeof(struct libmnt_listmnt)); + ls->stepsiz = stepsiz; + ls->id = LSMT_ROOT; /* default */ + } + + /* reuse old setting */ + if (tb->lsmnt) { + ls->id = tb->lsmnt->id; + ls->ns = tb->lsmnt->ns; + ls->last = tb->lsmnt->last; + ls->enabled = tb->lsmnt->enabled; + ls->reverse = tb->lsmnt->reverse; + free(tb->lsmnt); + } + + tb->lsmnt = ls; + + DBG(TAB, ul_debugobj(tb, "listmount: init [step=%zu]", ls->stepsiz)); + return 0; +} + +/** + * mnt_table_listmount_set_id: + * @tb: mount table + * @id: root ID + * + * Set root ID for the table if the table is read from kernel by + * listmount() syscall. The default is to read all filesystems; use + * statx(STATX_MNT_ID_UNIQUE) for subdirectory. + * + * Returns: 0 on sucess, < 0 on error + * Since: 2.41 + */ +int mnt_table_listmount_set_id(struct libmnt_table *tb, uint64_t id) +{ + int rc = 0; + + if (!tb) + return -EINVAL; + if (!tb->lsmnt && (rc = table_init_listmount(tb, 0)) != 0) + return rc; + tb->lsmnt->id = id; + return 0; +} + +/** + * mnt_table_listmount_set_ns: + * @tb: mount table + * @id: namespace ID + * + * Set namespace ID for listmount(). + * + * Returns: 0 on sucess, < 0 on error + * Since: 2.41 + */ +int mnt_table_listmount_set_ns(struct libmnt_table *tb, uint64_t ns) +{ + int rc = 0; + + if (!tb) + return -EINVAL; + if (!tb->lsmnt && (rc = table_init_listmount(tb, 0)) != 0) + return rc; + tb->lsmnt->ns = ns; + return 0; +} + +/** + * mnt_table_listmount_set_stepsiz: + * @tb: mount table + * @sz: number of nodes read by one libmount() call + * + * Returns: 0 on sucess, < 0 on error + * Since: 2.41 + */ +int mnt_table_listmount_set_stepsiz(struct libmnt_table *tb, size_t sz) +{ + if (!tb) + return -EINVAL; + + return table_init_listmount(tb, sz); +} + +/* + * This function is called by mnt_reset_table() and the table must already be + * empty. + * + * Private; not export to library API! + **/ +int mnt_table_reset_listmount(struct libmnt_table *tb) +{ + if (!tb || !tb->lsmnt) + return 0; + if (tb->nents) + return -EINVAL; + + tb->lsmnt->done = 0; + tb->lsmnt->reverse = 0; + tb->lsmnt->last = 0; + return 0; +} + +/** + * mnt_table_enable_listmount: + * @tb: table + * @enable: 0 or 1 + * + * Enable or disable on-demand listmount() to make it usable by + * mnt_table_next_fs(). This function does not affect + * mnt_table_fetch_listmont(). + * + * Returns: old status (1 or 0) + * Since: 2.41 + */ +int mnt_table_enable_listmount(struct libmnt_table *tb, int enable) +{ + int old = 0; + + if (tb && tb->lsmnt) { + old = tb->lsmnt->enabled; + tb->lsmnt->enabled = enable; + DBG(TAB, ul_debugobj(tb, "listmount() %s", + enable ? "on" : "off")); + } + return old; +} + +/* private; returns 1 if on-demand listmount() possible */ +int mnt_table_want_listmount(struct libmnt_table *tb) +{ + return tb && tb->lsmnt && tb->lsmnt->enabled; +} + +/* add new entries from list[] to table */ +static int lsmnt_to_table( + struct libmnt_table *tb, struct libmnt_listmnt *ls, + size_t nitems, int reverse) +{ + int rc = 0; + size_t i; + struct libmnt_fs *prev = NULL; + + if (reverse) + mnt_table_first_fs(tb, &prev); + else + mnt_table_last_fs(tb, &prev); + if (prev) + mnt_ref_fs(prev); + + DBG(TAB, ul_debugobj(tb, "listmount: insert %zu", nitems)); + + for (i = 0; rc == 0 && i < nitems; i++) { + struct libmnt_fs *fs; + uint64_t id = ls->list[i]; + + if (!id) + continue; + + fs = mnt_new_fs(); + if (fs) { + fs->flags |= MNT_FS_KERNEL; + mnt_fs_set_uniq_id(fs, id); + if (ls && ls->ns) + mnt_fs_set_ns(fs, ls->ns); + + rc = mnt_table_insert_fs(tb, reverse, prev, fs); + } else + rc = -ENOMEM; + + mnt_unref_fs(prev); + prev = fs; + } + + mnt_unref_fs(prev); + return rc; +} + +/* + * Private function, backed of mnt_table_next_fs(). + * + * Return: 0 on success, 1 if not more data, <0 on error. + */ +int mnt_table_next_lsmnt(struct libmnt_table *tb, int direction) +{ + ssize_t n; + int reverse = direction == MNT_ITER_BACKWARD; + struct libmnt_listmnt *ls = NULL; + int rc = 0; + + if (!tb || !tb->lsmnt) + return -EINVAL; + if (tb->lsmnt->done || !tb->lsmnt->enabled) + return 1; + + ls = tb->lsmnt; + + /* disable on-demand fetching */ + mnt_table_enable_listmount(tb, 0); + + /* read all to avoid mixing order in the table */ + if (!mnt_table_is_empty(tb) && ls->reverse != reverse) { + rc = mnt_table_fetch_listmount(tb); + goto done; + } + + ls->reverse = reverse; + + DBG(TAB, ul_debugobj(tb, "listmount: call " + "[id=%" PRIu64", ns=%" PRIu64 + "last=%" PRIu64", sz=%zu %s]", + ls->id, ls->ns, + ls->last, ls->stepsiz, + ls->reverse ? "reverse" : "")); + + n = ul_listmount(ls->id, ls->ns, ls->last, ls->list, ls->stepsiz, + reverse ? LISTMOUNT_REVERSE : 0); + if (n < 0) { + rc = -errno; + goto done; + } + + if (n < (ssize_t) ls->stepsiz) + ls->done = 1; + if (n > 0) { + ls->last = ls->list[ n - 1 ]; + rc = lsmnt_to_table(tb, ls, n, reverse); + } else + rc = 0; +done: + mnt_table_enable_listmount(tb, 1); + + DBG(TAB, ul_debugobj(tb, "listmount: on-demand done [rc=%d]", rc)); + return rc; /* nothing */ +} + +/** + * mnt_table_fetch_listmount: + * @tb: table instance + * + * By default, this function reads all mount nodes in the current namespace + * from the kernel and adds them to the @tb table. This default behavior can + * be modified using mnt_table_listmount_set_...(). + * + * The table is reset (all file systems removed) before new data is added. + * + * Return: 0 on success, <0 on error. + * Since: 2.41 + */ +int mnt_table_fetch_listmount(struct libmnt_table *tb) +{ + int rc = 0, stmnt_status = 0, lsmnt_status = 0; + struct libmnt_listmnt *ls = NULL; + ssize_t n; + + if (!tb) + return -EINVAL; + + DBG(TAB, ul_debugobj(tb, "listmount: fetching all")); + + if (!tb->lsmnt && (rc = table_init_listmount(tb, 0)) != 0) + return rc; + + /* disable on-demand statmount() */ + if (tb->stmnt) + stmnt_status = mnt_statmnt_disable_fetching(tb->stmnt, 1); + /* disable on-demand listmount() */ + lsmnt_status = mnt_table_enable_listmount(tb, 0); + + mnt_reset_table(tb); + + ls = tb->lsmnt; + + do { + DBG(TAB, ul_debugobj(tb, "listmount: call " + "[id=%" PRIu64", ns=%" PRIu64 + "last=%" PRIu64", sz=%zu]", + ls->id, ls->ns, + ls->last, ls->stepsiz)); + + n = ul_listmount(ls->id, ls->ns, ls->last, + ls->list, ls->stepsiz, 0); + if (n < 0) { + rc = -errno; + break; + } + ls->last = ls->list[ n - 1 ]; + rc = lsmnt_to_table(tb, ls, n, 0); + + } while (rc == 0 && n == (ssize_t) ls->stepsiz); + + /* Avoid using on-demand mnt_table_next_lsmnt() if we already + * have all the necessary data (or on error) */ + tb->lsmnt->done = 1; + + /* restore */ + if (tb->stmnt) + mnt_statmnt_disable_fetching(tb->stmnt, stmnt_status); + mnt_table_enable_listmount(tb, lsmnt_status); + + DBG(TAB, ul_debugobj(tb, "listmount: fetching done [rc=%d]", rc)); + + return rc; +} + +#endif /* HAVE_STATMOUNT_API */ diff --git a/libmount/src/tab_parse.c b/libmount/src/tab_parse.c index b8ec244f797..e5db3e385cd 100644 --- a/libmount/src/tab_parse.c +++ b/libmount/src/tab_parse.c @@ -323,7 +323,14 @@ static int mnt_parse_utab_line(struct libmnt_fs *fs, const char *s) if (!*p) break; - if (!fs->id && !strncmp(p, "ID=", 3)) { + if (!fs->uniq_id && !strncmp(p, "UNIQID=", 7)) { + int rc = 0; + + end = next_u64(p + 7, &fs->uniq_id, &rc); + if (!end || rc) + return rc; + + } else if (!fs->id && !strncmp(p, "ID=", 3)) { int rc = 0; end = next_s32(p + 3, &fs->id, &rc); @@ -1178,6 +1185,7 @@ static struct libmnt_fs *mnt_table_merge_user_fs(struct libmnt_table *tb, struct struct libmnt_iter itr; const char *optstr, *src, *target, *root, *attrs; int id; + uint64_t uniq_id; if (!tb || !uf) return NULL; @@ -1190,6 +1198,7 @@ static struct libmnt_fs *mnt_table_merge_user_fs(struct libmnt_table *tb, struct attrs = mnt_fs_get_attributes(uf); root = mnt_fs_get_root(uf); id = mnt_fs_get_id(uf); + uniq_id = mnt_fs_get_uniq_id(uf); if (!src || !target || !root || (!attrs && !optstr)) return NULL; @@ -1202,10 +1211,16 @@ static struct libmnt_fs *mnt_table_merge_user_fs(struct libmnt_table *tb, struct if (fs->flags & MNT_FS_MERGED) continue; - if (id > 0 && mnt_fs_get_id(fs)) { + if (uniq_id > 0 && mnt_fs_get_uniq_id(fs)) { + DBG(TAB, ul_debugobj(tb, " using uniq ID")); + if (mnt_fs_get_uniq_id(fs) == uniq_id) + break; + + } else if (id > 0 && mnt_fs_get_id(fs)) { DBG(TAB, ul_debugobj(tb, " using ID")); if (mnt_fs_get_id(fs) == id) break; + } else if (r && strcmp(r, root) == 0 && mnt_fs_streq_target(fs, target) && mnt_fs_streq_srcpath(fs, src)) diff --git a/libmount/src/tab_update.c b/libmount/src/tab_update.c index fa07dc0d9fb..a256edf8582 100644 --- a/libmount/src/tab_update.c +++ b/libmount/src/tab_update.c @@ -446,9 +446,11 @@ static int fprintf_utab_fs(FILE *f, struct libmnt_fs *fs) if (!fs || !f) return -EINVAL; - if (mnt_fs_get_id(fs) > 0) { + if (mnt_fs_get_id(fs) > 0) rc = fprintf(f, "ID=%d ", mnt_fs_get_id(fs)); - } + if (mnt_fs_get_uniq_id(fs) > 0) + rc = fprintf(f, "UNIQID=%" PRIu64, mnt_fs_get_uniq_id(fs)); + if (rc >= 0) { p = mangle(mnt_fs_get_source(fs)); if (p) { diff --git a/libmount/src/utils.c b/libmount/src/utils.c index 94a877cf051..a090373728b 100644 --- a/libmount/src/utils.c +++ b/libmount/src/utils.c @@ -266,6 +266,73 @@ int mnt_is_readonly(const char *path) return 0; } +#if defined(HAVE_STATX) && defined(HAVE_STRUCT_STATX) && defined(HAVE_STRUCT_STATX_STX_MNT_ID) +static int get_mnt_id( int fd, const char *path, + uint64_t *uniq_id, int *id) +{ + int rc; + struct statx sx = { 0 }; + int flags = AT_STATX_DONT_SYNC | AT_NO_AUTOMOUNT; + + if (!path || !*path) + flags |= AT_EMPTY_PATH; + + if (id) { + rc = statx(fd, path ? path : "", flags, + STATX_MNT_ID, &sx); + if (rc) + return rc; + *id = sx.stx_mnt_id; + } + if (uniq_id) { +# ifdef STATX_MNT_ID_UNIQUE + errno = 0; + rc = statx(fd, path ? path : "", flags, + STATX_MNT_ID_UNIQUE, &sx); + + if (rc && errno == EINVAL) + return -ENOSYS; /* *_ID_UNIQUE unsupported? */ + if (rc) + return rc; + *uniq_id = sx.stx_mnt_id; +# else + return -ENOSYS; +# endif + } + return 0; +} +#else /* HAVE_STATX && HAVE_STRUCT_STATX && AVE_STRUCT_STATX_STX_MNT_ID */ +static int get_mnt_id( int fd __attribute__((__unused__)), + const char *path __attribute__((__unused__)), + uint64_t *uniq_id __attribute__((__unused__)), + int *id __attribute__((__unused__))) +{ + return -ENOSYS; +} +#endif + +int mnt_id_from_fd(int fd, uint64_t *uniq_id, int *id) +{ + return get_mnt_id(fd, NULL, uniq_id, id); +} + +/** + * mnt_id_from_path: + * @path: mountpoint + * @uniq_id: returns STATX_MNT_ID_UNIQUE + * @id: returns STATX_MNT_ID + * + * Converts @path to ID. + * + * Returns: 0 on success, <0 on error + * + * Since: 2.41 + */ +int mnt_id_from_path(const char *path, uint64_t *uniq_id, int *id) +{ + return get_mnt_id(-1, path, uniq_id, id); +} + /** * mnt_mangle: * @str: string diff --git a/libmount/src/version.c b/libmount/src/version.c index b1e34327e89..3b61618b553 100644 --- a/libmount/src/version.c +++ b/libmount/src/version.c @@ -19,7 +19,6 @@ #include #include "mountP.h" -#include "mount-api-utils.h" static const char *lib_version = LIBMOUNT_VERSION; static const char *lib_features[] = { @@ -44,6 +43,9 @@ static const char *lib_features[] = { #ifdef USE_LIBMOUNT_MOUNTFD_SUPPORT "fd-based-mount", #endif +#ifdef HAVE_STATMOUNT_API + "statmount", +#endif #if defined(HAVE_STATX) && defined(HAVE_STRUCT_STATX) && defined(AT_STATX_DONT_SYNC) "statx", #endif diff --git a/libsmartcols/samples/continuous.c b/libsmartcols/samples/continuous.c index fb089c365a4..af3987e0c07 100644 --- a/libsmartcols/samples/continuous.c +++ b/libsmartcols/samples/continuous.c @@ -14,6 +14,7 @@ #include "c.h" #include "nls.h" #include "strutils.h" +#include "timeutils.h" #include "xalloc.h" #include "libsmartcols.h" @@ -22,11 +23,6 @@ enum { COL_NUM, COL_DATA, COL_TIME }; -static double time_diff(struct timeval *a, struct timeval *b) -{ - return (a->tv_sec - b->tv_sec) + (a->tv_usec - b->tv_usec) / 1E6; -} - /* add columns to the @tb */ static void setup_columns(struct libscols_table *tb) { diff --git a/meson.build b/meson.build index 95e6541e920..3699c5ef6cb 100644 --- a/meson.build +++ b/meson.build @@ -103,8 +103,27 @@ have_mountfd_api = cc.has_type('struct mount_attr', prefix : '#include ') conf.set('HAVE_STRUCT_STATX', have_struct_statx ? 1 : false) +have = cc.has_member('struct statx', 'stx_mnt_id', + prefix : '#include ') +conf.set('HAVE_STRUCT_STATX_STX_MNT_ID', have ? 1 : false) + + +have_statmount = cc.has_type('struct statmount', prefix : '#include ') +have_listmount = cc.has_header_symbol('linux/mount.h', 'LSMT_ROOT') + +# kernel headers provides the syscall, but there is not SYS_xxx yet +if have_statmount and not cc.has_header_symbol('bits/syscall.h', 'SYS_statmount') + conf.set('SYS_statmount', '__NR_statmount') +endif +if have_listmount and not cc.has_header_symbol('bits/syscall.h', 'SYS_listmount') + conf.set('SYS_listmount', '__NR_listmount') +endif + +conf.set('HAVE_STATMOUNT_API', have_statmount and have_listmount ? 1 : false) + have_sys_vfs_header = cc.has_header('sys/vfs.h') @@ -687,10 +706,6 @@ have = cc.has_member('struct stat', 'st_mtim.tv_nsec', prefix : '#include ') conf.set('HAVE_STRUCT_STAT_ST_MTIM_TV_NSEC', have ? 1 : false) -have = cc.has_member('struct statx', 'stx_mnt_id', - prefix : '#include ') -conf.set('HAVE_STRUCT_STATX_STX_MNT_ID', have ? 1 : false) - # replacement for AC_STRUCT_TIMEZONE have = cc.has_member('struct tm', 'tm_zone', args : '-D_GNU_SOURCE', diff --git a/misc-utils/findmnt.c b/misc-utils/findmnt.c index 236e8e7a80f..f17f7e9cd89 100644 --- a/misc-utils/findmnt.c +++ b/misc-utils/findmnt.c @@ -91,7 +91,8 @@ enum { enum { TABTYPE_FSTAB = 1, TABTYPE_MTAB, - TABTYPE_KERNEL + TABTYPE_KERNEL_MOUNTINFO, + TABTYPE_KERNEL_LISTMOUNT }; /* column names */ @@ -964,6 +965,7 @@ static int create_treenode(struct libscols_table *table, struct libmnt_table *tb bool filtered = false; if (has_line(table, fs)) goto leave; + line = add_line(table, fs, parent_line, findmnt, &filtered); if (filtered) line = parent_line; @@ -986,7 +988,6 @@ static int create_treenode(struct libscols_table *table, struct libmnt_table *tb (size_t) scols_table_get_nlines(table)) { mnt_reset_iter(itr, MNT_ITER_FORWARD); fs = NULL; - while (mnt_table_next_fs(tb, itr, &fs) == 0) { if (!has_line(table, fs) && match_func(fs, findmnt)) create_treenode(table, tb, fs, NULL, findmnt); @@ -1048,7 +1049,7 @@ static struct libmnt_table *parse_tabfiles(char **files, case TABTYPE_MTAB: rc = mnt_table_parse_mtab(tb, path); break; - case TABTYPE_KERNEL: + case TABTYPE_KERNEL_MOUNTINFO: if (!path) path = access(_PATH_PROC_MOUNTINFO, R_OK) == 0 ? _PATH_PROC_MOUNTINFO : @@ -1056,6 +1057,9 @@ static struct libmnt_table *parse_tabfiles(char **files, rc = mnt_table_parse_file(tb, path); break; + default: + rc = -EINVAL; + break; } if (rc) { mnt_unref_table(tb); @@ -1067,6 +1071,35 @@ static struct libmnt_table *parse_tabfiles(char **files, return tb; } +static struct libmnt_table *fetch_listmount(void) +{ + struct libmnt_table *tb; + struct libmnt_statmnt *sm; + + sm = mnt_new_statmnt(); + if (!sm) { + warn(_("failed to allocate statmnt handler")); + return NULL; + } + + tb = mnt_new_table(); + if (!tb) { + warn(_("failed to initialize libmount table")); + return NULL; + } + + mnt_table_refer_statmnt(tb, sm); + + if (mnt_table_fetch_listmount(tb) != 0) { + warn(_("failed to fetch mount nodes")); + mnt_unref_table(tb); + mnt_unref_statmnt(sm); + return NULL; + } + + return tb; +} + /* * Parses mountinfo and calls mnt_cache_set_targets(cache, mtab). Only * necessary if @tb in main() was read from a non-kernel source. @@ -1705,7 +1738,7 @@ int main(int argc, char *argv[]) { "help", no_argument, NULL, 'h' }, { "invert", no_argument, NULL, 'i' }, { "json", no_argument, NULL, 'J' }, - { "kernel", no_argument, NULL, 'k' }, + { "kernel", optional_argument, NULL, 'k' }, { "list", no_argument, NULL, 'l' }, { "mountpoint", required_argument, NULL, 'M' }, { "mtab", no_argument, NULL, 'm' }, @@ -1765,7 +1798,7 @@ int main(int argc, char *argv[]) findmnt.flags |= FL_TREE; while ((c = getopt_long(argc, argv, - "AabCcDd:ehIiJfF:o:O:p::PQ:klmM:nN:rst:uvRS:T:Uw:VxyH", + "AabCcDd:ehIiJfF:o:O:p::PQ:k::lmM:nN:rst:uvRS:T:Uw:VxyH", longopts, NULL)) != -1) { err_exclusive_options(c, longopts, excl, excl_st); @@ -1861,8 +1894,16 @@ int main(int argc, char *argv[]) tabtype = TABTYPE_FSTAB; findmnt.flags &= ~FL_TREE; break; - case 'k': /* kernel (mountinfo) */ - tabtype = TABTYPE_KERNEL; + case 'k': + if (optarg) { + if (strcmp(optarg, "mountinfo") == 0) + tabtype = TABTYPE_KERNEL_MOUNTINFO; + else if (strcmp(optarg, "listmount") == 0) + tabtype = TABTYPE_KERNEL_LISTMOUNT; + else + errx(EXIT_FAILURE, _("invalid --kernel argument")); + } else + tabtype = TABTYPE_KERNEL_MOUNTINFO; break; case 't': set_match(COL_FSTYPE, optarg); @@ -1878,7 +1919,7 @@ int main(int argc, char *argv[]) findmnt.flags |= FL_NOHEADINGS; break; case 'N': - tabtype = TABTYPE_KERNEL; + tabtype = TABTYPE_KERNEL_MOUNTINFO; tabfiles = append_pid_tabfile(tabfiles, &ntabfiles, strtou32_or_err(optarg, _("invalid TID argument"))); @@ -1979,11 +2020,15 @@ int main(int argc, char *argv[]) return EXIT_FAILURE; if (!tabtype) - tabtype = verify ? TABTYPE_FSTAB : TABTYPE_KERNEL; + tabtype = verify ? TABTYPE_FSTAB : TABTYPE_KERNEL_MOUNTINFO; if ((findmnt.flags & FL_POLL) && ntabfiles > 1) errx(EXIT_FAILURE, _("--poll accepts only one file, but more specified by --tab-file")); + if (ntabfiles && tabtype == TABTYPE_KERNEL_LISTMOUNT) + errx(EXIT_FAILURE, _( + "options --kernel=listmount and --tab-file or --task can't be used together")); + if (optind < argc && (get_match(COL_SOURCE) || get_match(COL_TARGET))) errx(EXIT_FAILURE, _( "options --target and --source can't be used together " @@ -2022,13 +2067,16 @@ int main(int argc, char *argv[]) */ mnt_init_debug(0); - tb = parse_tabfiles(tabfiles, ntabfiles, tabtype); + if (tabtype == TABTYPE_KERNEL_LISTMOUNT) + tb = fetch_listmount(); + else + tb = parse_tabfiles(tabfiles, ntabfiles, tabtype); if (!tb) goto leave; mnt_table_set_userdata(tb, &findmnt); if (tabtype == TABTYPE_MTAB && tab_is_kernel(tb)) - tabtype = TABTYPE_KERNEL; + tabtype = TABTYPE_KERNEL_MOUNTINFO; istree = tab_is_tree(tb); if (istree && force_tree) @@ -2045,7 +2093,7 @@ int main(int argc, char *argv[]) } mnt_table_set_cache(tb, findmnt.cache); - if (tabtype != TABTYPE_KERNEL) + if (tabtype == TABTYPE_FSTAB || tabtype == TABTYPE_MTAB) cache_set_targets(findmnt.cache); } @@ -2082,7 +2130,7 @@ int main(int argc, char *argv[]) rc = add_matching_lines(tb, table, direction, &findmnt); if (rc != 0 - && tabtype == TABTYPE_KERNEL + && tabtype == TABTYPE_KERNEL_MOUNTINFO && (findmnt.flags & FL_NOSWAPMATCH) && !(findmnt.flags & FL_STRICTTARGET) && get_match(COL_TARGET)) { diff --git a/sys-utils/dmesg.c b/sys-utils/dmesg.c index e73cddf5e06..e2c49535f54 100644 --- a/sys-utils/dmesg.c +++ b/sys-utils/dmesg.c @@ -602,11 +602,6 @@ static const char *parse_kmsg_timestamp(const char *str0, struct timeval *tv) return end + 1; /* skip separator */ } -static double time_diff(struct timeval *a, struct timeval *b) -{ - return (a->tv_sec - b->tv_sec) + (a->tv_usec - b->tv_usec) / (double) USEC_PER_SEC; -} - static int get_syslog_buffer_size(void) { int n = klogctl(SYSLOG_ACTION_SIZE_BUFFER, NULL, 0); diff --git a/sys-utils/hwclock-rtc.c b/sys-utils/hwclock-rtc.c index 2796f2e8a5e..c58c4543d77 100644 --- a/sys-utils/hwclock-rtc.c +++ b/sys-utils/hwclock-rtc.c @@ -197,7 +197,7 @@ static int busywait_for_rtc_clock_tick(const struct hwclock_control *ctl, if (rc || start_time.tm_sec != nowtime.tm_sec) break; gettime_monotonic(&now); - if (time_diff(now, begin) > 1.5) { + if (time_diff(&now, &begin) > 1.5) { warnx(_("Timed out waiting for time change.")); return 1; } diff --git a/sys-utils/hwclock.c b/sys-utils/hwclock.c index cea249ebb10..34a725f5f1e 100644 --- a/sys-utils/hwclock.c +++ b/sys-utils/hwclock.c @@ -170,15 +170,6 @@ static struct timeval t2tv(time_t timet) return rettimeval; } -/* - * The difference in seconds between two times in "timeval" format. - */ -double time_diff(struct timeval subtrahend, struct timeval subtractor) -{ - return (subtrahend.tv_sec - subtractor.tv_sec) - + (subtrahend.tv_usec - subtractor.tv_usec) / 1E6; -} - /* * The time, in "timeval" format, which is seconds after the * time . Of course, may be negative. @@ -572,8 +563,8 @@ set_hardware_clock_exact(const struct hwclock_control *ctl, ON_DBG(RANDOM_SLEEP, up_to_1000ms_sleep()); gettimeofday(&nowsystime, NULL); - deltavstarget = time_diff(nowsystime, targetsystime); - ticksize = time_diff(nowsystime, prevsystime); + deltavstarget = time_diff(&nowsystime, &targetsystime); + ticksize = time_diff(&nowsystime, &prevsystime); prevsystime = nowsystime; if (ticksize < 0) { @@ -624,7 +615,7 @@ set_hardware_clock_exact(const struct hwclock_control *ctl, } newhwtime = sethwtime - + round(time_diff(nowsystime, refsystime) + + round(time_diff(&nowsystime, &refsystime) - delay /* don't count this */); if (ctl->verbose) printf(_("%"PRId64".%06"PRId64" is close enough to %"PRId64".%06"PRId64" (%.6f < %.6f)\n" @@ -821,8 +812,8 @@ adjust_drift_factor(const struct hwclock_control *ctl, * hclocktime is fully corrected with the current drift factor. * Its difference from nowtime is the missed drift correction. */ - factor_adjust = time_diff(nowtime, hclocktime) / - (time_diff(nowtime, last_calib) / sec_per_day); + factor_adjust = time_diff(&nowtime, &hclocktime) / + (time_diff(&nowtime, &last_calib) / sec_per_day); drift_factor = adjtime_p->drift_factor + factor_adjust; if (fabs(drift_factor) > MAX_DRIFT) { @@ -838,8 +829,8 @@ adjust_drift_factor(const struct hwclock_control *ctl, "%f seconds\nin spite of a drift factor of " "%f seconds/day.\n" "Adjusting drift factor by %f seconds/day\n"), - time_diff(nowtime, hclocktime), - time_diff(nowtime, last_calib), + time_diff(&nowtime, &hclocktime), + time_diff(&nowtime, &last_calib), adjtime_p->drift_factor, factor_adjust); } @@ -1096,7 +1087,7 @@ manipulate_clock(const struct hwclock_control *ctl, const time_t set_time, hclocktime = time_inc(tdrift, hclocktime.tv_sec); startup_hclocktime = - time_inc(hclocktime, time_diff(startup_time, read_time)); + time_inc(hclocktime, time_diff(&startup_time, &read_time)); } if (ctl->show || ctl->get) { return display_time(startup_hclocktime); diff --git a/sys-utils/hwclock.h b/sys-utils/hwclock.h index 21b7efa3d28..bd8ff449a54 100644 --- a/sys-utils/hwclock.h +++ b/sys-utils/hwclock.h @@ -79,9 +79,6 @@ struct clock_ops { extern const struct clock_ops *probe_for_cmos_clock(void); extern const struct clock_ops *probe_for_rtc_clock(const struct hwclock_control *ctl); -/* hwclock.c */ -extern double time_diff(struct timeval subtrahend, struct timeval subtractor); - /* rtc.c */ #if defined(__linux__) && defined(__alpha__) extern int get_epoch_rtc(const struct hwclock_control *ctl, unsigned long *epoch); diff --git a/tests/commands.sh b/tests/commands.sh index 05d4d313e33..75a3c4bd3d4 100644 --- a/tests/commands.sh +++ b/tests/commands.sh @@ -31,6 +31,8 @@ TS_HELPER_LIBMOUNT_UPDATE="${ts_helpersdir}test_mount_tab_update" TS_HELPER_LIBMOUNT_UTILS="${ts_helpersdir}test_mount_utils" TS_HELPER_LIBMOUNT_DEBUG="${ts_helpersdir}test_mount_debug" TS_HELPER_LIBMOUNT_FUZZ="${ts_helpersdir}test_mount_fuzz" +TS_HELPER_LIBMOUNT_LISTMOUNT="${ts_helpersdir}test_mount_listmount" +TS_HELPER_LIBMOUNT_STATMOUNT="${ts_helpersdir}test_mount_statmount" TS_HELPER_LIBSMARTCOLS_CONTINUOUS_JSON="${ts_helpersdir}sample-scols-continuous-json" TS_HELPER_LIBSMARTCOLS_FROMFILE="${ts_helpersdir}sample-scols-fromfile" TS_HELPER_LIBSMARTCOLS_TITLE="${ts_helpersdir}sample-scols-title" diff --git a/tests/expected/libmount/listmount-tmpfs b/tests/expected/libmount/listmount-tmpfs new file mode 100644 index 00000000000..35821117c87 --- /dev/null +++ b/tests/expected/libmount/listmount-tmpfs @@ -0,0 +1 @@ +Success diff --git a/tests/expected/libmount/statmount-statmount-tmpfs b/tests/expected/libmount/statmount-statmount-tmpfs new file mode 100644 index 00000000000..d0f42f48cf3 --- /dev/null +++ b/tests/expected/libmount/statmount-statmount-tmpfs @@ -0,0 +1,33 @@ +------ fs: +target: ? +fstype: tmpfs +optstr: rw,relatime,inode64 +VFS-optstr: rw,relatime +FS-opstr: inode64 +propagation: shared +root: / +id: ? +parent: ? +uniq-id: ? +uniq-parent: ? +devno: ? +------ fs: +fstype: tmpfs +uniq-id: ? +------ fs: +fstype: tmpfs +root: / +uniq-id: ? +------ fs: +target: ? +fstype: tmpfs +optstr: rw,relatime,inode64 +VFS-optstr: rw,relatime +FS-opstr: inode64 +propagation: shared +root: / +id: ? +parent: ? +uniq-id: ? +uniq-parent: ? +devno: ? diff --git a/tests/expected/libmount/tabfiles-parse-mountinfo b/tests/expected/libmount/tabfiles-parse-mountinfo index d5ba5248e45..3380d7717b9 100644 --- a/tests/expected/libmount/tabfiles-parse-mountinfo +++ b/tests/expected/libmount/tabfiles-parse-mountinfo @@ -358,6 +358,7 @@ optstr: rw,relatime VFS-optstr: rw,relatime FS-opstr: rw optional-fields: 'shared:323' +propagation: shared root: / id: 49 parent: 20 diff --git a/tests/expected/libmount/tabfiles-parse-mountinfo-nosrc b/tests/expected/libmount/tabfiles-parse-mountinfo-nosrc index 3d44ef60cd7..3458607d44b 100644 --- a/tests/expected/libmount/tabfiles-parse-mountinfo-nosrc +++ b/tests/expected/libmount/tabfiles-parse-mountinfo-nosrc @@ -72,6 +72,7 @@ optstr: rw,relatime VFS-optstr: rw,relatime FS-opstr: rw optional-fields: 'shared:212' +propagation: shared root: / id: 21 parent: 20 diff --git a/tests/expected/libmount/tabfiles-parse-swaps b/tests/expected/libmount/tabfiles-parse-swaps index bd781d4e3d7..6e94537a3e9 100644 --- a/tests/expected/libmount/tabfiles-parse-swaps +++ b/tests/expected/libmount/tabfiles-parse-swaps @@ -1,6 +1,5 @@ ------ fs: source: /dev/dm-2 -target: (null) fstype: swap swaptype: partition size: 8151036 @@ -8,14 +7,12 @@ usedsize: 2283436 priority: -2 ------ fs: source: /some/swapfile -target: (null) fstype: swap swaptype: file size: 111 usedsize: 111 ------ fs: source: /some/swapfile2 -target: (null) fstype: swap swaptype: file size: 111 diff --git a/tests/helpers/test_sysinfo.c b/tests/helpers/test_sysinfo.c index 6447e841345..c12116b4ceb 100644 --- a/tests/helpers/test_sysinfo.c +++ b/tests/helpers/test_sysinfo.c @@ -125,7 +125,7 @@ static int hlp_enotty_ok(void) static int hlp_fsopen_ok(void) { -#ifdef FSOPEN_CLOEXEC +#if defined(HAVE_FSOPEN) && defined(FSOPEN_CLOEXEC) errno = 0; fsopen(NULL, FSOPEN_CLOEXEC); #else diff --git a/tests/ts/libmount/listmount b/tests/ts/libmount/listmount new file mode 100644 index 00000000000..658d5ccfdc3 --- /dev/null +++ b/tests/ts/libmount/listmount @@ -0,0 +1,37 @@ +#!/bin/bash + +TS_TOPDIR="${0%/*}/../.." +TS_DESC="listmount" + +. "$TS_TOPDIR"/functions.sh +ts_init "$*" + +ts_check_test_command "$TS_CMD_MOUNT" +ts_check_test_command "$TS_CMD_UMOUNT" + +ts_skip_nonroot + +TESTPROG="$TS_HELPER_LIBMOUNT_LISTMOUNT" + +[ -x $TESTPROG ] || ts_skip "test not compiled" + +ts_init_subtest "listmount-tmpfs" +# Create directories +for i in $(seq 1 2000); do + mkdir -p "listmount-tmpfs$i" +done + +for i in $(seq 1 2000); do + $TS_CMD_MOUNT -t tmpfs none "listmount-tmpfs$i" +done + +$TESTPROG + +for i in $(seq 1 2000); do + $TS_CMD_UMOUNT "listmount-tmpfs$i" + rmdir "listmount-tmpfs$i" +done +ts_log "Success" +ts_finalize_subtest + +ts_finalize diff --git a/tests/ts/libmount/statmount b/tests/ts/libmount/statmount new file mode 100755 index 00000000000..f95662be4b8 --- /dev/null +++ b/tests/ts/libmount/statmount @@ -0,0 +1,36 @@ +#!/bin/bash + +TS_TOPDIR="${0%/*}/../.." +TS_DESC="statmount" + +. "$TS_TOPDIR"/functions.sh +ts_init "$*" + +ts_check_test_command "$TS_CMD_MOUNT" +ts_check_test_command "$TS_CMD_UMOUNT" + +ts_skip_nonroot + +TESTPROG="$TS_HELPER_LIBMOUNT_STATMOUNT" + +[ -x $TESTPROG ] || ts_skip "test not compiled" + +ts_init_subtest "statmount-tmpfs" +mkdir -p "statmount-tmpfs" +$TS_CMD_MOUNT -t tmpfs none "statmount-tmpfs" + +$TESTPROG $PWD/statmount-tmpfs >> $TS_OUTPUT 2>> $TS_ERRLOG + +sed -i 's/target: .*/target: ?/g' $TS_OUTPUT +sed -i 's/id: .*/id: ?/g' $TS_OUTPUT +sed -i 's/parent: .*/parent: ?/g' $TS_OUTPUT +sed -i 's/uniq-id: .*/uniq-id: ?/g' $TS_OUTPUT +sed -i 's/uniq-parent: .*/uniq-parent: ?/g' $TS_OUTPUT +sed -i 's/devno: .*/devno: ?/g' $TS_OUTPUT +sed -i 's/[[:space:]]*$//' $TS_OUTPUT + +$TS_CMD_UMOUNT "statmount-tmpfs" +rmdir "statmount-tmpfs" +ts_finalize_subtest + +ts_finalize