/* Copyright (C) 2018 RDA Technologies Limited and/or its affiliates("RDA").
 * All rights reserved.
 *
 * This software is supplied "AS IS" without any warranties.
 * RDA assumes no responsibility or liability for the use of the software,
 * conveys no license or title under any patent, copyright, or mask work
 * right to the product. RDA reserves the right to make changes in the
 * software without notification.  RDA also make no representation or
 * warranty that such application will be suitable for the specified use
 * without further testing or modification.
 */

#ifndef _VFS_H_
#define _VFS_H_

#include <fcntl.h>
#include <sys/stat.h>

#ifdef __cplusplus
extern "C" {
#endif

/**
 * the maximum absolute file path including mount point, terminate \0
 */
#define VFS_PATH_MAX (192)

#define DT_UNKOWN 0 ///< unknown type
#define DT_DIR 4    ///< directory
#define DT_REG 8    ///< regular file

/**
 * read only flag used in \p vfs_remount
 */
#define MS_RDONLY 1

/**
 * DIR data structure
 */
typedef struct
{
    int16_t fs_index;  ///< internal fs index
    int16_t _reserved; ///< reserved
} DIR;

/**
 * dirent data structure
 */
struct dirent
{
    int d_ino;            ///< inode number, file system implementation can use it for any purpose
    unsigned char d_type; ///< type of file
    char d_name[256];     ///< file name
};

struct statvfs
{
    unsigned long f_bsize;   /* file system block size */
    unsigned long f_frsize;  /* fragment size */
    unsigned long f_blocks;  /* size of fs in f_frsize units */
    unsigned long f_bfree;   /* free blocks in fs */
    unsigned long f_bavail;  /* free blocks avail to non-superuser */
    unsigned long f_files;   /* total file nodes in file system */
    unsigned long f_ffree;   /* free file nodes in fs */
    unsigned long f_favail;  /* avail file nodes in fs */
    unsigned long f_fsid;    /* file system id */
    unsigned long f_flag;    /* mount flags */
    unsigned long f_namemax; /* maximum length of filenames */
};

/**
 * unmount file system by file path
 *
 * \p path must be absolute path, starting with '/'. It can be mount point
 * or arbitrary file or directory under the mount point.
 *
 * \param [in] path     file path
 * \return
 *      - 0 on success
 *      - -1 on error
 *          - EINVAL: invalid parameter
 *          - ENOENT: there are no mount points
 */
int vfs_umount(const char *path);

/**
 * remount file system with flags
 *
 * The only supported flags are \p MS_READONLY.
 *
 * \p path must be a mount point.
 *
 * \param [in] path     mount point path
 * \param [in] flags    file system mount flags
 * \return
 *      - 0 on success
 *      - -1 on error
 *          - ENOENT: \p path is not a mount point
 *          - ENOSYS: the file system doesn't support remount
 *          - others: refer to file system implementation
 */
int vfs_remount(const char *path, unsigned flags);

/**
 * open and possibly create a file
 *
 * It is the same as open a file with O_WRONLY, O_CREAT, O_TRUNC.
 *
 * Refer to man (2) creat.
 *
 * \param [in] path     file path
 * \param [in] mode     file mode, may be ignored by file system
 *                      implementation
 * \return
 *      - file descriptor on success on success
 *      - -1 on error. Refer to file system implementation for errno.
 */
int vfs_creat(const char *path, mode_t mode);

/**
 * open and possibly create a file
 *
 * Refer to man (2) open
 *
 * \param [in] path     file path
 * \param [in] flags    O_CREAT, O_ACCMODE, O_TRUNC
 * \param [in] mode     file mode, may be ignored by file system
 *                      implementation
 * \return
 *      - file descriptor on success on success
 *      - -1 on error. Refer to file system implementation for errno.
 */
int vfs_open(const char *path, int flags, ...);

/**
 * close a file descriptor
 *
 * Refer to man (2) close
 *
 * \param [in] fd       file descriptor
 * \return
 *      - 0 on success
 *      - -1 on error. Refer to file system implementation for errno.
 */
int vfs_close(int fd);

/**
 * read from a file descriptor
 *
 * Refer to man (2) read
 *
 * \param [in] fd       file descriptor
 * \param [out] buf     buf for read
 * \param [in] count    byte count
 * \return
 *      - the number of bytes read on success
 *      - -1 on error. Refer to file system implementation for errno.
 */
ssize_t vfs_read(int fd, void *buf, size_t count);

/**
 * write to a file descriptor
 *
 * Refer to man (2) write
 *
 * \param [in] fd       file descriptor
 * \param [in] buf      buf for write
 * \param [in] count    byte count
 * \return
 *      - the number of bytes written on success
 *      - -1 on error. Refer to file system implementation for errno.
 */
ssize_t vfs_write(int fd, const void *buf, size_t count);

/**
 * reposition read/write file offset
 *
 * Refer to man (2) lseek
 *
 * \param [in] fd       file descriptor
 * \param [in] offset   offset according to the directive whence
 * \param [in] whence   SEEK_SET, SEEK_CUR, SEEK_END
 * \return
 *      - offset location as measured in bytes from the beginning of
 *        the file on success.
 *      - -1 on error. Refer to file system implementation for errno.
 */
long vfs_lseek(int fd, long offset, int whence);

/**
 * get file status
 *
 * Refer to man (2) fstat
 *
 * \param [in] fd       file descriptor
 * \param [out] st      file status
 * \return
 *      - 0 on success
 *      - -1 on error. Refer to file system implementation for errno.
 */
int vfs_fstat(int fd, struct stat *st);

/**
 * get file status
 *
 * Refer to man (2) stat
 *
 * \param [in] path     file path
 * \param [out] st      file status
 * \return
 *      - 0 on success
 *      - -1 on error. Refer to file system implementation for errno.
 */
int vfs_stat(const char *path, struct stat *st);

/**
 * truncate a file to a specified length
 *
 * Refer to man (2) truncate
 *
 * \param [in] path     file path
 * \param [in] length   file length
 * \return
 *      - 0 on success
 *      - -1 on error. Refer to file system implementation for errno.
 */
int vfs_truncate(const char *path, long length);

/**
 * truncate a file to a specified length
 *
 * Refer to man (2) ftruncate
 *
 * \param [in] fd       file descriptor
 * \param [in] length   file length
 * \return
 *      - 0 on success
 *      - -1 on error. Refer to file system implementation for errno.
 */
int vfs_ftruncate(int fd, long length);

/**
 * truncate a file to a specified length
 *
 * Refer to man (2) link. It is just a placeholder, and no file systems
 * support it now.
 *
 * \param [in] oldpath  old file path
 * \param [in] newpath  new file path
 * \return
 *      - -1 on error, errno is ENOSYS
 */
int vfs_link(const char *oldpath, const char *newpath);

/**
 * delete a name and possibly the file it refers to
 *
 * Refer to man (2) unlink
 *
 * \param [in] pathname file path name
 * \return
 *      - 0 on success
 *      - -1 on error. Refer to file system implementation for errno.
 */
int vfs_unlink(const char *pathname);

/**
 * change the name or location of a file
 *
 * Refer to man (2) rename
 *
 * \p oldpath and \p newpath must be on the same mounted file system.
 *
 * \param [in] oldpath  old file path
 * \param [in] newpath  new file path
 * \return
 *      - 0 on success
 *      - -1 on error. Refer to file system implementation for errno.
 *          - EXDEV: oldpath and newpath are not on the same mounted
 *            file system.
 */
int vfs_rename(const char *oldpath, const char *newpath);

/**
 * synchronize a file's in-core state with storage device
 *
 * Refer to man (2) fsync
 *
 * \param [in] fd       file descriptor
 * \return
 *      - 0 on success
 *      - -1 on error. Refer to file system implementation for errno.
 */
int vfs_fsync(int fd);

/**
 * manipulate file descriptor
 *
 * Refer to man (2) fcntl. It is just a placeholder, and no file systems
 * support it now.
 *
 * \param [in] fd       file descriptor
 * \param [in] cmd      command
 * \return
 *      - -1 on error, errno is ENOSYS
 */
int vfs_fcntl(int fd, int cmd, ...);

/**
 * control device
 *
 * Refer to man (2) ioctl. It is just a placeholder, and no file systems
 * support it now.
 *
 * \param [in] fd       file descriptor
 * \param [in] cmd      command
 * \return
 *      - -1 on error, errno is ENOSYS
 */
int vfs_ioctl(int fd, unsigned long request, ...);

/**
 * open a directory
 *
 * Refer to man (3) opendir
 *
 * \param [in] name     directory name
 * \return
 *      - 0 on success
 *      - -1 on error. Refer to file system implementation for errno.
 */
DIR *vfs_opendir(const char *name);

/**
 * read a directory
 *
 * Refer to man (3) readdir
 *
 * \param [in] dirp     directory pointer returned by \p vfs_opendir
 * \return
 *      - 0 on success
 *      - -1 on error. Refer to file system implementation for errno.
 */
struct dirent *vfs_readdir(DIR *dirp);

/**
 * read a directory
 *
 * Refer to man (3) readdir_r
 *
 * \param [in] dirp     directory pointer returned by \p vfs_opendir
 * \param [out] entry   dirent buffer
 * \param [out] result  pointer to dirent buffer
 * \return
 *      - 0 on success
 *      - -1 on error. Refer to file system implementation for errno.
 */
int vfs_readdir_r(DIR *dirp, struct dirent *entry, struct dirent **result);

/**
 * return current location in directory stream
 *
 * Refer to man (3) telldir
 *
 * \param [in] dirp     directory pointer returned by \p vfs_opendir
 * \return
 *      - 0 on success
 *      - -1 on error. Refer to file system implementation for errno.
 */
long vfs_telldir(DIR *pdir);

/**
 * set the position of the next readdir() call in the directory stream
 *
 * Refer to man (3) seekdir
 *
 * \param [in] dirp     directory pointer returned by \p vfs_opendir
 * \param [in] loc      location returned by \p vfs_telldir
 * \return
 *      - 0 on success
 *      - -1 on error. Refer to file system implementation for errno.
 */
void vfs_seekdir(DIR *pdir, long loc);

/**
 * reset directory stream
 *
 * Refer to man (3) rewinddir
 *
 * \param [in] dirp     directory pointer returned by \p vfs_opendir
 * \return
 *      - 0 on success
 *      - -1 on error. Refer to file system implementation for errno.
 */
void vfs_rewinddir(DIR *pdir);

/**
 * close a directory
 *
 * Refer to man (3) closedir
 *
 * \param [in] dirp     directory pointer returned by \p vfs_opendir
 * \return
 *      - 0 on success
 *      - -1 on error. Refer to file system implementation for errno.
 */
int vfs_closedir(DIR *pdir);

/**
 * create a directory
 *
 * Refer to man (2) mkdir
 *
 * \param [in] pathname directory path name
 * \param [in] mode     create mode
 * \return
 *      - 0 on success
 *      - -1 on error. Refer to file system implementation for errno.
 */
int vfs_mkdir(const char *pathname, mode_t mode);

/**
 * delete a directory
 *
 * Refer to man (2) rmdir
 *
 * \param [in] pathname directory path name
 * \return
 *      - 0 on success
 *      - -1 on error. Refer to file system implementation for errno.
 */
int vfs_rmdir(const char *pathname);

/**
 * change working directory
 *
 * Refer to man (2) chdir
 *
 * NOTE: in RTOS system, working directory is a global concept. It is
 * dangerous to change working directory. Don't call \p vfs_chdir
 * unless it is absolutely needed, and you really know what you are
 * doing.
 *
 * \param [in] path     directory path name
 * \return
 *      - 0 on success
 *      - -1 on error
 */
int vfs_chdir(const char *path);

/**
 * get current working directory
 *
 * Refer to man (3) getcwd
 *
 * \param [out] buf     buffer for result
 * \param [in] size     buffer size
 * \return
 *      - current working directory on success
 *      - NULL on error
 */
char *vfs_getcwd(char *buf, size_t size);

/**
 * return the canonicalized absolute pathname
 *
 * Refer to man (3) realpath
 *
 * \param [in] path     file path
 * \param [in] resolved_path    buffer for canonicalized absolute pathname
 * \return
 *      - \p resolved_path on success
 *      - NULL on error
 */
char *vfs_realpath(const char *path, char *resolved_path);

/**
 * get filesystem statistics
 *
 * Refer to man (3) statvfs
 *
 * \param [in] path     file path
 * \param [in] buf      filesystem statistics
 * \return
 *      - 0 on success
 *      - -1 on error
 */
int vfs_statvfs(const char *path, struct statvfs *buf);

/**
 * get filesystem statistics
 *
 * Refer to man (3) fstatvfs
 *
 * \param [in] fd       file descriptor
 * \param [in] buf      filesystem statistics
 * \return
 *      - 0 on success
 *      - -1 on error
 */
int vfs_fstatvfs(int fd, struct statvfs *buf);

/**
 * get mounted file system count
 *
 * \return  mounted file system count
 */
int vfs_mount_count(void);

/**
 * get mounted file system mount points
 *
 * \param [in] mp       buffer for mount points
 * \param [in] count    buffer count
 * \return  count of get mount points
 */
int vfs_mount_points(char **mp, size_t count);

/**
 * helper to get canonicalized absolute pathname
 *
 * It is similar to \p vfs_realpath, just with specified base path name.
 *
 * \param [in] base_path    base path name
 * \param [in] path         path related to base_path
 * \param [in] resolved_path    buffer for canonicalized absolute pathname
 * \return
 *      - \p resolved_path on success
 *      - NULL on error
 */
char *vfs_resolve_path(const char *base_path, const char *path, char *resolved_path);

/**
 * create directory with all parents
 *
 * \param [in] path         path of directory to be created
 * \param [in] mode     create mode
 * \return
 *      - 0 on succes
 *      - -1 on error
 */
int vfs_mkpath(const char *path, mode_t mode);

/**
 * create directory with all parents of a file
 *
 * \param [in] path         path of file
 * \param [in] mode     create mode
 * \return
 *      - 0 on succes
 *      - -1 on error
 */
int vfs_mkfilepath(const char *path, mode_t mode);

/**
 * helper to get file size
 *
 * It calls \p vfs_stat to get file status, and return file size only.
 *
 * \param [in] path     file path
 * \return
 *      - file size on success
 *      - -1 on error
 */
ssize_t vfs_file_size(const char *path);

/**
 * helper to read file
 *
 * It calls \p vfs_open, \p vfs_read and \p vfs_close to read bytes from file.
 *
 * \param [in] path     file path
 * \param [in] buf      buf for read
 * \param [in] count    byte count
 * \return
 *      - the number of bytes read on success
 *      - -1 on error
 */
ssize_t vfs_file_read(const char *path, void *buf, size_t count);

/**
 * helper to write file
 *
 * It calls \p vfs_open, \p vfs_write and \p vfs_close to write bytes to file.
 *
 * \param [in] path     file path
 * \param [in] buf      buf for write
 * \param [in] count    byte count
 * \return
 *      - the number of bytes written on success
 *      - -1 on error
 */
ssize_t vfs_file_write(const char *path, const void *buf, size_t count);

/**
 * initial check for safe file
 *
 * *safe file* is just a normal file, just write is special designed for power
 * failure safe write. That is, when there is power failure during file write,
 * the file on file system will either be the original content, or the new
 * content.
 *
 * It is needed to write the whole content in one call. Partial write is not
 * supported.
 *
 * When there is power failure during write, there may exist garbages on file
 * system. \p vfs_sfile_init will clean up the garbages.
 *
 * \param [in] path     safe file path
 * \return
 *      - 0 on success
 *      - -1 if the safe file isn't exist or invalid
 */
int vfs_sfile_init(const char *path);

/**
 * get safe file size
 *
 * It is the same as \p vfs_file_size.
 *
 * \param [in] path     file path
 * \return
 *      - file size on success
 *      - -1 on error
 */
ssize_t vfs_sfile_size(const char *path);

/**
 * read safe file
 *
 * \param [in] path     file path
 * \param [in] buf      buf for read
 * \param [in] count    byte count
 * \return
 *      - the number of bytes read on success
 *      - -1 on error
 */
ssize_t vfs_sfile_read(const char *path, void *buf, size_t count);

/**
 * write safe file
 *
 * \param [in] path     file path
 * \param [in] buf      buf for write
 * \param [in] count    byte count
 * \return
 *      - the number of bytes written on success
 *      - -1 on error
 */
ssize_t vfs_sfile_write(const char *path, const void *buf, size_t count);

/**
 * delete files and subdirectories under a directory
 *
 * \param [in] path     directory path
 * \return
 *      - the number of bytes read on success
 *      - -1 on error
 */
int vfs_rmchildren(const char *path);

/**
 * delete directory and children under the directory
 *
 * \param [in] path     directory path
 * \return
 *      - the number of bytes read on success
 *      - -1 on error
 */
int vfs_rmdir_recursive(const char *path);

/**
 * umount all mounted file system
 *
 * It should only be called in FDL now. And in application, the
 * implementation is empty.
 */
void vfs_umount_all(void);

/**
 * fs handle of a path
 *
 * It is for debug only, get the file system implementation handler.
 *
 * \param [in] path     file path
 * \return
 *      - the file system implementation handler
 */
void *vfs_fs_handle(const char *path);

/**
 * fs handle of mount point
 *
 * It can be used to determine whether a mount point is mounted.
 *
 * \param [in] path     mount point
 * \return
 *      - the file system implementation handler
 */
void *vfs_mount_handle(const char *path);

/**
 * recursive total file size of a directory
 *
 * \param [in] path directory name
 * \return
 *      - recursive total file size of a directory
 *      - -1 on error, invalid parameter or out of memory
 */
int64_t vfs_dir_total_size(const char *path);

typedef struct
{
    ssize_t (*stdin_read)(void *dst, size_t size);
    ssize_t (*stdout_write)(const void *data, size_t size);
    ssize_t (*stderr_write)(const void *data, size_t size);
} vfs_stdio_callback_t;

void vfs_set_stdio_callback(const vfs_stdio_callback_t *cb);

#ifdef __cplusplus
}
#endif
#endif // H
