/**
@file
@brief Support for legacy Linux kernel versions
@details Copyright (c) 2018-2021 Acronis International GmbH
@author Mikhail Krivtsov (mikhail.krivtsov@acronis.com)
@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/stat.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
// '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 COMPAT_KUID_T uid_t
#define from_kuid_compat(kuid) (kuid)
#else
#define COMPAT_KUID_T kuid_t
#define from_kuid_compat(kuid) from_kuid(&init_user_ns, kuid)
#endif
#ifndef HAVE_FROM_KGID
#define COMPAT_KGID_T gid_t
#define from_kgid_compat(kgid) (kgid)
#else
#define COMPAT_KGID_T kgid_t
#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
#ifndef fd_file
#define fd_file(f) (f.file)
#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 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)
{
char *str;
if (g_d_absolute_path_compat)
{
str = g_d_absolute_path_compat(path, name, size);
if (!IS_ERR(str))
{
return str;
}
}
// d_path will access current->fs expecting is to be valid due to chrooting functionality.
// During exit sequence, 'current->fs' might be already gone so we need to check it explicitly.
if (!current->fs)
{
return (char*) ERR_PTR(-EFAULT);
}
str = d_path(path, name, size);
return str;
}
#ifdef HAVE_TIMESPEC64
#define TIMESPEC struct timespec64
#define getboottime getboottime64
#else
#define TIMESPEC struct timespec
#endif
#ifdef STATX_BTIME
// basic fast flags are type, mode, uid, gid, atime, mtime, ctime, size, btime
// For the sake of stability with mini ids, do not use vfs_getattr
int vfs_getattr_basic_fast(const struct path *path, struct kstat *stat);
#else
static inline int vfs_getattr_basic_fast(const struct path *path, struct kstat *stat)
{
(void) path; (void) stat;
return -EINVAL;
}
#endif
|