#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/iversion.h>
#include <linux/fs.h>
#include <linux/namei.h>
#ifndef COMPAT_DETECT_LOOKUP_ONE_LEN_6_16
#include <linux/mnt_idmapping.h>
#endif
#include <linux/dcache.h>

#ifndef COMPAT_DETECT_INODE_UPDATE_TIMESTAMPS_6_6

int inode_update_timestamps(struct inode *inode, int flags)
{
	int updated = 0;
	struct timespec64 now;

	if (flags & (S_MTIME|S_CTIME|S_VERSION)) {
		struct timespec64 ctime = inode_get_ctime(inode);
		struct timespec64 mtime = inode_get_mtime(inode);

		now = inode_set_ctime_current(inode);
		if (!timespec64_equal(&now, &ctime))
			updated |= S_CTIME;
		if (!timespec64_equal(&now, &mtime)) {
			inode_set_mtime_to_ts(inode, now);
			updated |= S_MTIME;
		}
		if (IS_I_VERSION(inode) && inode_maybe_inc_iversion(inode, updated))
			updated |= S_VERSION;
	} else {
		now = current_time(inode);
	}

	if (flags & S_ATIME) {
		struct timespec64 atime = inode_get_atime(inode);

		if (!timespec64_equal(&now, &atime)) {
			inode_set_atime_to_ts(inode, now);
			updated |= S_ATIME;
		}
	}
	return updated;
}
EXPORT_SYMBOL(inode_update_timestamps);

#endif /* COMPAT_DETECT_INODE_UPDATE_TIMESTAMPS_6_6 */

#ifndef COMPAT_DETECT_LOOKUP_ONE_LEN_6_16
struct dentry *lookup_one_len_unlocked(const char *name,
				       struct dentry *base,
				       int len)
{
	struct qstr this = QSTR_INIT(name, len);
	return lookup_one_unlocked(&nop_mnt_idmap, &this, base);
}
EXPORT_SYMBOL_GPL(lookup_one_len_unlocked);

struct dentry *lookup_one_len(const char *name,
			     struct dentry *base, int len)
{
	struct qstr this = QSTR_INIT(name, len);
	return lookup_one(&nop_mnt_idmap, &this, base);
}
EXPORT_SYMBOL_GPL(lookup_one_len);

struct dentry *lookup_positive_unlocked(const char *name,
					struct dentry *base,
					int len)
{
	struct qstr this = QSTR_INIT(name, len);
	return lookup_one_positive_unlocked(&nop_mnt_idmap, &this, base);
}
EXPORT_SYMBOL_GPL(lookup_positive_unlocked);
#endif /* COMPAT_DETECT_LOOKUP_ONE_LEN_6_16 */

#ifndef COMPAT_DETECT_D_HASH_AND_LOOKUP_6_16
struct dentry *d_hash_and_lookup(struct dentry *dir, struct qstr *name)
{
	return try_lookup_noperm(name, dir);
}
EXPORT_SYMBOL_GPL(d_hash_and_lookup);
#endif /* COMPAT_DETECT_D_HASH_AND_LOOKUP_6_16 */

#ifndef COMPAT_DETECT_SIMPLE_DONE_CREATING_6_19

static inline bool is_dot_dotdot_6_19(const char *name, size_t len)
{
	return len && unlikely(name[0] == '.') &&
		(len == 1 || (len == 2 && name[1] == '.'));
}

int lookup_noperm_common_6_19(struct qstr *qname, struct dentry *base)
{
	const char *name = qname->name;
	u32 len = qname->len;

	qname->hash = full_name_hash(base, name, len);
	if (!len)
		return -EACCES;

	if (is_dot_dotdot_6_19(name, len))
		return -EACCES;

	while (len--) {
		unsigned int c = *(const unsigned char *)name++;
		if (c == '/' || c == '\0')
			return -EACCES;
	}
	/*
	 * See if the low-level filesystem might want
	 * to use its own hash..
	 */
	if (base->d_flags & DCACHE_OP_HASH) {
		int err = base->d_op->d_hash(base, qname);
		if (err < 0)
			return err;
	}
	return 0;
}

EXPORT_SYMBOL_GPL(lookup_noperm_common_6_19);

/*
 * lookup_dcache, d_revalidate, lookup_one_qstr_excl are missing from RHEL 9.x.
 *
 * d_revalidate tags along.
 *
 */

static inline int d_revalidate(struct inode *dir, const struct qstr *name,
                               struct dentry *dentry, unsigned int flags)
{
        if (unlikely(dentry->d_flags & DCACHE_OP_REVALIDATE))
#if defined(COMPAT_DETECT_VFS_D_REVALIDATE_6_14)
                return dentry->d_op->d_revalidate(dir, name, dentry, flags);
#else
                return dentry->d_op->d_revalidate(dentry, flags);
#endif
        else
                return 1;
}

static struct dentry *lookup_dcache(const struct qstr *name,
                                    struct dentry *dir,
                                    unsigned int flags)
{
        struct dentry *dentry = d_lookup(dir, name);
        if (dentry) {
                int error = d_revalidate(dir->d_inode, name, dentry, flags);
                if (unlikely(error <= 0)) {
                        if (!error)
                                d_invalidate(dentry);
                        dput(dentry);
                        return ERR_PTR(error);
                }
        }
        return dentry;
}

static struct dentry *lookup_one_qstr_excl_6_19(const struct qstr *name,
						struct dentry *base, unsigned int flags)
{
        struct dentry *dentry;
        struct dentry *old;
        struct inode *dir;

        dentry = lookup_dcache(name, base, flags);
        if (dentry)
                goto found;

        /* Don't create child dentry for a dead directory. */
        dir = base->d_inode;
        if (unlikely(IS_DEADDIR(dir)))
                return ERR_PTR(-ENOENT);

        dentry = d_alloc(base, name);
        if (unlikely(!dentry))
                return ERR_PTR(-ENOMEM);

        old = dir->i_op->lookup(dir, dentry, flags);
        if (unlikely(old)) {
                dput(dentry);
                dentry = old;
        }
found:
        if (IS_ERR(dentry))
                return dentry;
        if (d_is_negative(dentry) && !(flags & LOOKUP_CREATE)) {
                dput(dentry);
                return ERR_PTR(-ENOENT);
        }
        if (d_is_positive(dentry) && (flags & LOOKUP_EXCL)) {
                dput(dentry);
                return ERR_PTR(-EEXIST);
        }
        return dentry;
}

struct dentry *start_dirop_6_19(struct dentry *parent, struct qstr *name,
				unsigned int lookup_flags)
{
	struct dentry *dentry;
	struct inode *dir = d_inode(parent);

	inode_lock_nested(dir, I_MUTEX_PARENT);
	dentry = lookup_one_qstr_excl_6_19(name, parent, lookup_flags);
	if (IS_ERR(dentry))
		inode_unlock(dir);
	return dentry;
}

EXPORT_SYMBOL_GPL(start_dirop_6_19);

#endif
