/* common/KMDirFd.h
 *
 * Copyright (C) 2023 OpenKylin, Inc
 */

#ifndef __KMDIRFD_H__
#define __KMDIRFD_H__

#include <limits.h>
#include <dirent.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string>

/**
 * @brief : 封装目录文件描述符相关的操作
 */
class KMDirFd
{
public:
    /**
     * @brief : 规范化fd
     */
    static int canonicalize(int dfd);

    /**
     * @brief : 总结起来，如果path是绝对路径，则dfd参数没用。如果path是相对路径，并且dfd的值不是AT_FDCWD，则pathname的参照物是相对于dfd指向的目录，而不是进程的当前工作目录；
     *          反之，如果dfd的值是AT_FDCWD，path则是相对于进程当前工作目录的相对路径
     * @param : [in] dfd, 目录文件描述符，可以是AT_FDCWD或-1
     * @param : [in] path，相对于@dfd的路径
     * @param : [in] follow，如果%TRUE并且@path的最后一个组件是符号链接，follow symbol
     * @return: 返回文件描述符；或如果打开错误返回-1,可以根据errorno获取错误信息::strerror(errno)
     */
    static int opendirat(int dfd, const char *path, bool follow);

    /**
     * @brief : 将fd相对路径转换为可用于期望绝对路径的遗留API的对象。这是特定于Linux的，并且仅在此进程内有效（除非您将子进程设置为具有完全相同的fd编号，但不要尝试
     * @param : [in] dfd, 目录文件描述符，可以是AT_FDCWD或-1
     * @param : [in] path，相对于@dfd的路径
     * @return: 转换后的路径
     */
    static std::string fdRelToAbsPath(int dfd, const char *path);

    /**
     * @brief : 创建相对于dfd的子目录path
     * @param : [in] dfd, 目录文件描述符，可以是AT_FDCWD或-1
     * @param : [in] path，相对于@dfd的路径
     * @param : [in] mode, 路径权限
     * @return: 是否成功, false时可以根据errorno获取错误信息::strerror(errno)
     */
    static bool ensureDir(int dfd, const char *path, mode_t mode);

    /**
     * @brief : 递归创建@dfd和@path目录组合引用的目录
     * @param : [in] dfd, 目录文件描述符，可以是AT_FDCWD或-1
     * @param : [in] path，相对于@dfd的路径；如果@dfd是AT_FDCWD或-1，则@path需要是绝对路径
     * @param : [in] mode, 路径权限
     * @return: 是否成功, false时可以根据errorno获取错误信息::strerror(errno)
     */
    static bool mkdirPAt(int dfd, const std::string &path, int mode);

    /**
     * @brief : 递归删除@dfd和@path目录组合引用的文件名；它可以是一个文件或目录。如果@path不存在，则不会引发错误。
     * @param : [in] dfd, 目录文件描述符，可以是AT_FDCWD或-1
     * @param : [in] path，相对于@dfd的路径；如果@dfd是AT_FDCWD或-1，则@path需要是绝对路径
     * @return: 是否成功, false时可以根据errorno获取错误信息::strerror(errno)
     */
    static bool rmRfAt(int dfd, const char *path);

    /**
     * @brief : 删除@dfd和@path目录组合引用的文件名；它可以是一个文件或目录，如果@path不存在，则不会引发错误。
     * @param : [in] dfd, 目录文件描述符，可以是AT_FDCWD或-1
     * @param : [in] path，相对于@dfd的路径；如果@dfd是AT_FDCWD或-1，则@path需要是绝对路径
     * @param : [in] flags, 标志，如：AT_REMOVEDIR、AT_SYMLINK_NOFOLLOW、AT_SYMLINK_FOLLOW、AT_EMPTY_PATH、AT_RECURSIVE等等
     * @return: 是否成功, false时可以根据errorno获取错误信息::strerror(errno)
     */
    static bool unlinkatAllowNoent(int dfd, const char *path, int flags);

    /**
     * @dfd: 目录文件描述符，可以是AT_FDCWD或-1
     * @path: 相对于@dfd的路径；如果@dfd是AT_FDCWD或-1，则@path需要是绝对路径
     * @buf: （外调用者分配）：返回stat详细信息的位置
     * @flags: 要传递给fstatat（）的标志
     */
    static bool fstatat(int dfd, const std::string &path, struct stat *buf, int flags);

    /**
     * readlinkat:
     * @dfd: 目录文件描述符，可以是AT_FDCWD或-1
     * @subpath: 相对于@dfd的路径；如果@dfd是AT_FDCWD或-1，则@subpath需要是绝对路径
     *
     * 读取符号链接的值，即链接的目标路径
     */
    static std::string readlinkat(int dfd, const std::string &subpath);
};

/**
 * @brief : 通过目录文件描述符操作子目录的迭代器
 */
class KMDirFdIterator
{
public:
    KMDirFdIterator();
    ~KMDirFdIterator();

    /**
     * @brief : 根据dfd和path初始化子目录访问迭代器
     * @param : [in] dfd, 目录文件描述符，可以是AT_FDCWD或-1
     * @param : [in] path，相对于@dfd的路径
     * @param : [in] follow，如果%TRUE并且@path的最后一个组件是符号链接，follow symbol
     * @return: 是否成功, false时可以根据errorno获取错误信息::strerror(errno)
     */
    bool initAt(int dfd, const char *path, bool follow);

    /**
     * @brief : 根据dfd初始化目录访问迭代器
     * @param : [in | out] dfd, 目录文件描述符，可以是AT_FDCWD或-1；拥有权被取走，dfd会被修改为-1，调用者不能再close(dfd)
     * @return: 是否成功, false时可以根据errorno获取错误信息::strerror(errno)
     */
    bool initTakeFd(int &dfd);

    /**
     * @brief : 获取下一个条目信息
     * @param : [out] dent, 遍历到的目录信息，遍历完后返回的值为NULL
     * @return: false-发生了错误，可以根据errno获取错误信息
     */
    bool nextDent(struct dirent **dent);

    /**
     * @brief : 获取下一个条目信息，dent->d_type被填充为fstatat获取的信息
     * @param : [out] dent, 遍历到的目录信息，遍历完后返回的值为NULL
     * @return: false-发生了错误，可以根据errno获取错误信息
     */
    bool nextDentEnsureDtype(struct dirent **dent);

    /**
     * @brief : 回放到@dfd_iter的开头。下一次调用nextDent（）将提供目录中的第一个条目。
     */
    void rewind();

    /**
     * @brief : 递归删除目录子项
     */
    bool rm_rf_children();

    int fd()
    {
        return m_fd;
    }

    operator bool() const
    {
        return m_initialized;
    }

private:
    bool m_initialized = false;
    int m_fd = -1;
    DIR *m_dir = nullptr;
};

#endif // !__KMDIRFD_H__
