/** @file @brief Support for legacy Linux kernel versions @details Copyright (c) 2018-2021 Acronis International GmbH @author Mikhail Krivtsov ([email protected]) @since $Id: $ */ #pragma once #include "debug.h" #include <linux/atomic.h> #include <linux/cred.h> // current_fsuid_fsgid #include <linux/exportfs.h> #include <linux/file.h> #include <linux/fs.h> // vfs_stat #include <linux/mount.h> #include <linux/version.h> #include <linux/namei.h> #include <linux/mm.h> #ifdef KERNEL_MOCK #include <mock/mock.h> #endif // CentOS/RedHat kernel has many backports // 'LINUX_VERSION_CODE' cannot be trusted on RHEL distros so only 'grep' is used for CentOS and CloudLinux // For 'normal kernels', 'LINUX_VERSION_CODE' should be used #ifndef RHEL_RELEASE_VERSION // 'linux/sched/task.h' appeared in 'stable/v4.11' #if LINUX_VERSION_CODE < KERNEL_VERSION(4, 11, 0) # ifdef HAVE_SCHED_TASK_H # undef HAVE_SCHED_TASK_H # endif # ifndef HAVE_SCHED_H # define HAVE_SCHED_H # endif #else # ifndef HAVE_SCHED_TASK_H # define HAVE_SCHED_TASK_H # endif # ifdef HAVE_SCHED_H # undef HAVE_SCHED_H # endif #endif // 'get_fs_pwd()' appeared in 'stable/v2.6.36' #if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 36) # ifdef HAVE_GET_FS_ROOT # undef HAVE_GET_FS_ROOT # endif #else # ifndef HAVE_GET_FS_ROOT # define HAVE_GET_FS_ROOT # endif #endif // Second arg of 'vfs_fstatat()' was made 'const' in 'stable/v2.6.36' #if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 36) # ifdef HAVE_VFS_FSTATAT_CONST # undef HAVE_VFS_FSTATAT_CONST # endif #else # ifndef HAVE_VFS_FSTATAT_CONST # define HAVE_VFS_FSTATAT_CONST # endif #endif // 'get_task_exe_file()' appeared in 'stable/v4.8' #if LINUX_VERSION_CODE < KERNEL_VERSION(4, 8, 0) # ifdef HAVE_GET_TASK_EXE # undef HAVE_GET_TASK_EXE # endif #else # ifndef HAVE_GET_TASK_EXE # define HAVE_GET_TASK_EXE # endif #endif // 'data' arg was added to 'probe' callback in v.2.6.35 #if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 35) # ifdef HAVE_TRACEPOINT_PROBE_REGISTER_DATA # undef HAVE_TRACEPOINT_PROBE_REGISTER_DATA # endif #else # ifndef HAVE_TRACEPOINT_PROBE_REGISTER_DATA # define HAVE_TRACEPOINT_PROBE_REGISTER_DATA # endif // registration interface was modified in 'stable/v3.15' # if LINUX_VERSION_CODE < KERNEL_VERSION(3, 15, 0) # ifdef HAVE_TRACEPOINT_PROBE_REGISTER_STRUCT # undef HAVE_TRACEPOINT_PROBE_REGISTER_STRUCT # endif # else # ifndef HAVE_TRACEPOINT_PROBE_REGISTER_STRUCT # define HAVE_TRACEPOINT_PROBE_REGISTER_STRUCT # endif # endif #endif // rbtree postorder iteration functions appeared in 'stable/v3.12' #if LINUX_VERSION_CODE < KERNEL_VERSION(3, 12, 0) # ifdef HAVE_RB_FIRST_POSTORDER # undef HAVE_RB_FIRST_POSTORDER # endif # ifdef HAVE_RB_NEXT_POSTORDER # undef HAVE_RB_NEXT_POSTORDER # endif #else # ifndef HAVE_RB_FIRST_POSTORDER # define HAVE_RB_FIRST_POSTORDER # endif # ifndef HAVE_RB_NEXT_POSTORDER # define HAVE_RB_NEXT_POSTORDER # endif #endif #endif #ifdef HAVE_SCHED_H #ifndef KERNEL_MOCK #include <linux/sched.h> // put_task_struct() #else #include <mock/mock_sched.h> #endif #endif #ifdef HAVE_SCHED_TASK_H #include <linux/sched/task.h> // put_task_struct() #endif #ifndef HAVE_GET_FS_PWD #include <linux/fs_struct.h> #include <linux/path.h> static inline void get_fs_pwd(struct fs_struct *fs, struct path *pwd) { read_lock(&fs->lock); *pwd = fs->pwd; path_get(pwd); read_unlock(&fs->lock); } #endif static inline struct inode *file_inode_compat(const struct file *f) { return f->f_path.dentry->d_inode; } #if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 15, 0) #define GET_TASK_EXE_NOT_EXPORTED #endif #if !defined(HAVE_GET_TASK_EXE) || defined(GET_TASK_EXE_NOT_EXPORTED) struct file *get_task_exe_file_impl(struct task_struct *task); #endif #define TRACE_CB_NAME(name) trace_##name##_cb #ifndef HAVE_TRACEPOINT_PROBE_REGISTER_DATA #define REGISTER_TRACE(name, probe) register_trace_##name(probe) #define UNREGISTER_TRACE(name, probe) unregister_trace_##name(probe) #define TRACE_CB_PROTO(name, proto) void TRACE_CB_NAME(name)(PARAMS(proto)) #else #ifndef HAVE_TRACEPOINT_PROBE_REGISTER_STRUCT #define REGISTER_TRACE(name, probe) register_trace_##name(probe, NULL) #define UNREGISTER_TRACE(name, probe) unregister_trace_##name(probe, NULL) #else int tracepoint_probe_register_compat(const char *name, void *probe, void *data); #define REGISTER_TRACE(name, probe) \ tracepoint_probe_register_compat(#name, probe, NULL) int tracepoint_probe_unregister_compat(const char *name, void *probe, void *data); #define UNREGISTER_TRACE(name, probe) \ tracepoint_probe_unregister_compat(#name, probe, NULL) #endif #define TRACE_CB_PROTO(name, proto) \ void TRACE_CB_NAME(name)(void *cb_data, PARAMS(proto)) #endif #ifndef HAVE_RB_FIRST_POSTORDER #include <linux/rbtree.h> extern struct rb_node *rb_first_postorder(const struct rb_root *); #endif #ifndef HAVE_RB_NEXT_POSTORDER #include <linux/rbtree.h> extern struct rb_node *rb_next_postorder(const struct rb_node *); #endif #ifndef HAVE_FROM_KUID #define from_kuid_compat(kuid) (kuid) #else #define from_kuid_compat(kuid) from_kuid(&init_user_ns, kuid) #endif #ifndef HAVE_FROM_KGID #define from_kgid_compat(kgid) (kgid) #else #define from_kgid_compat(kgid) from_kgid(&init_user_ns, kgid) #endif static inline uid_t get_current_fsuid_compat(void) { return from_kuid_compat(current_fsuid()); } static inline gid_t get_current_fsgid_compat(void) { return from_kgid_compat(current_fsgid()); } #ifndef current_real_cred #define current_real_cred() current->real_cred #endif static inline struct file* dentry_open_compat(const struct path* path, int flags) { #ifndef HAVE_PATH_IN_DENTRY_OPEN return dentry_open(dget(path->dentry), mntget(path->mnt), flags, current_cred()); #else return dentry_open(path, flags, current_cred()); #endif } static inline int get_unused_fd_compat(void) { #ifdef HAVE_UNUSED_FD_FLAGS return get_unused_fd_flags(0); #else return get_unused_fd(); #endif } /* 2.6.32-71.el6 unsigned int module_refcount(struct module *mod) linux-3.10.0-123.el7 unsigned long module_refcount(struct module *mod) */ #define module_refcount_compat(m) ((unsigned long)module_refcount(m)) static inline struct file *get_task_exe_file_compat(struct task_struct *task) { #if !defined(HAVE_GET_TASK_EXE) || defined(GET_TASK_EXE_NOT_EXPORTED) return get_task_exe_file_impl(task); #else return get_task_exe_file(task); #endif } unsigned long compat_kallsyms_lookup_name(const char *name); #ifndef HAVE_FDGET // Backport 'struct fd' to old kernels // Sadly 'fget_light' is not exported so just use 'fget' instead struct compat_fd { struct file *file; }; static inline struct compat_fd compat_fdget(unsigned int fd) { return (struct compat_fd){ fget(fd) }; } static inline void compat_fdput(struct compat_fd fd) { fput(fd.file); } #else #define compat_fd fd #define compat_fdput fdput #define compat_fdget fdget #endif // Some defines for compiler atomic hacks. We only care about x86_64 here, on other platforms those are NOT valid. // Other platforms must provider their implementations for 'smp_*' & 'READ/WRITE_ONCE' // Generally those convenience memory ordering functions are available in Linux 3.12 & Linux 3.18. // I assume it will not be necessary to support any other architectures other than x86_64 on kernels that old. #ifndef READ_ONCE #define __READ_ONCE_SIZE \ ({ \ switch (size) { \ case 1: *(__u8 *)res = *(volatile __u8 *)p; break; \ case 2: *(__u16 *)res = *(volatile __u16 *)p; break; \ case 4: *(__u32 *)res = *(volatile __u32 *)p; break; \ case 8: *(__u64 *)res = *(volatile __u64 *)p; break; \ default: \ barrier(); \ __builtin_memcpy((void *)res, (const void *)p, size); \ barrier(); \ } \ }) static void inline _do_read_once_size(const volatile void *p, void *res, int size) { __READ_ONCE_SIZE; } #define READ_ONCE(x) \ ({ \ union { typeof(x) __val; char __c[1]; } __u; \ _do_read_once_size(&(x), __u.__c, sizeof(x)); \ smp_read_barrier_depends(); /* Enforce dependency ordering from x */ \ __u.__val; \ }) #endif #ifndef WRITE_ONCE static void inline _do_write_once_size(volatile void *p, void *res, int size) { switch (size) { case 1: *(volatile __u8 *)p = *(__u8 *)res; break; case 2: *(volatile __u16 *)p = *(__u16 *)res; break; case 4: *(volatile __u32 *)p = *(__u32 *)res; break; case 8: *(volatile __u64 *)p = *(__u64 *)res; break; default: barrier(); __builtin_memcpy((void *)p, (const void *)res, size); barrier(); } } #define WRITE_ONCE(x, val) \ ({ \ union { typeof(x) __val; char __c[1]; } __u = \ { .__val = (__force typeof(x)) (val) }; \ _do_write_once_size(&(x), __u.__c, sizeof(x)); \ __u.__val; \ }) #endif static inline void atomic_or_compat(int i, atomic_t *v) { #if defined(HAVE_ATOMIC_OR) || defined(CONFIG_ARCH_HAS_ATOMIC_OR) atomic_or(i, v); #else int old; int new; do { old = atomic_read(v); new = old | i; } while (atomic_cmpxchg(v, old, new) != old); #endif } static inline int atomic_mb_read(atomic_t *p) { int ret = atomic_read(p); smp_rmb(); return ret; } static inline void atomic_mb_set(atomic_t *p, int v) { smp_wmb(); atomic_set(p, v); smp_mb(); } static inline int atomic_mb_cmpxchg(atomic_t *p, int old, int nnew) { int ret; smp_wmb(); ret = atomic_cmpxchg(p, old, nnew); smp_mb(); return ret; } #ifndef VFSMOUNT_HAS_MNT_ID extern int global_mnt_id_offset; #endif static inline int get_mnt_id(struct vfsmount *mount) { #ifdef VFSMOUNT_HAS_MNT_ID return mount->mnt_id; #else int *mnt_ptr = (int *)mount; int *mnt_id; int mnt_offset = READ_ONCE(global_mnt_id_offset); if (mnt_offset == 0) { // WPRINTF("global_mnt_id_offset is not set"); return 0; } mnt_id = (int *)(mnt_ptr + mnt_offset); return *mnt_id; #endif } #ifndef MAX_HANDLE_SZ #define MAX_HANDLE_SZ 128 #endif #ifndef HAVE_FILE_HANDLE struct file_handle { __u32 handle_bytes; int handle_type; /* file identifier */ unsigned char f_handle[]; }; #endif #ifndef HAVE_INODE_GET_MTIME #define inode_get_mtime(inode) (inode)->i_mtime #endif #ifndef HAVE_INODE_GET_CTIME #define inode_get_ctime(inode) (inode)->i_ctime #endif #ifndef HAVE_INODE_GET_ATIME #define inode_get_atime(inode) (inode)->i_atime #endif #ifndef HAVE_I_UID_READ #define i_uid_read(inode) (inode)->i_uid #endif #ifndef HAVE_I_GID_READ #define i_gid_read(inode) (inode)->i_gid #endif extern void compat_init(void); typedef int (*get_cmdline_compat_fn)(struct task_struct *task, char *buffer, int buflen); extern get_cmdline_compat_fn g_get_cmdline_compat; static inline bool has_get_cmdline_compat(void) { return NULL != g_get_cmdline_compat; } static inline int get_cmdline_compat(struct task_struct *task, char *buffer, int buflen) { if (g_get_cmdline_compat) return g_get_cmdline_compat(task, buffer, buflen); return 0; } typedef char* (*d_absolute_path_compat_fn)(const struct path * path, char* name, int size); extern d_absolute_path_compat_fn g_d_absolute_path_compat; static inline bool has_d_absolute_path_compat(void) { return NULL != g_d_absolute_path_compat; } static inline char* d_absolute_path_compat(const struct path * path, char* name, int size) { if (g_d_absolute_path_compat) return g_d_absolute_path_compat(path, name, size); else return d_path(path, name, size); }