/*
 * Copyright (c) 2013-2019 Huawei Technologies Co., Ltd. All rights reserved.
 * Copyright (c) 2020-2021 Huawei Device Co., Ltd. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without modification,
 * are permitted provided that the following conditions are met:
 *
 * 1. Redistributions of source code must retain the above copyright notice, this list of
 *    conditions and the following disclaimer.
 *
 * 2. Redistributions in binary form must reproduce the above copyright notice, this list
 *    of conditions and the following disclaimer in the documentation and/or other materials
 *    provided with the distribution.
 *
 * 3. Neither the name of the copyright holder nor the names of its contributors may be used
 *    to endorse or promote products derived from this software without specific prior written
 *    permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
 * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
 * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

#include "errno.h"
#include "stdlib.h"
#include "string.h"
#include "dirent.h"
#include "unistd.h"
#include "sys/select.h"
#include "sys/mount.h"
#include "sys/stat.h"
#include "sys/statfs.h"
#include "sys/prctl.h"
#include "fs/fd_table.h"
#include "fs/file.h"
#include "linux/spinlock.h"
#include "los_process_pri.h"
#include "los_task_pri.h"
#include "capability_api.h"
#include "vnode.h"

#define MAX_DIR_ENT 1024
int fstat(int fd, struct stat *buf)
{
    struct file *filep = NULL;

    int ret = fs_getfilep(fd, &filep);
    if (ret < 0) {
        return VFS_ERROR;
    }

    return stat(filep->f_path, buf);
}

int fstat64(int fd, struct stat64 *buf)
{
    struct file *filep = NULL;

    int ret = fs_getfilep(fd, &filep);
    if (ret < 0) {
        return VFS_ERROR;
    }

    return stat64(filep->f_path, buf);
}

int lstat(const char *path, struct stat *buffer)
{
    return stat(path, buffer);
}

int VfsVnodePermissionCheck(const struct Vnode *node, int accMode)
{
    uint fuid = node->uid;
    uint fgid = node->gid;
    uint fileMode = node->mode;
    return VfsPermissionCheck(fuid, fgid, fileMode, accMode);
}
int VfsPermissionCheck(uint fuid, uint fgid, uint fileMode, int accMode)
{
    uint uid = OsCurrUserGet()->effUserID;
    mode_t tmpMode = fileMode;

    if (uid == fuid) {
        tmpMode >>= USER_MODE_SHIFT;
    } else if (LOS_CheckInGroups(fgid)) {
        tmpMode >>= GROUP_MODE_SHIFT;
    }

    tmpMode &= (READ_OP | WRITE_OP | EXEC_OP);

    if (((uint)accMode & tmpMode) == accMode) {
        return 0;
    }

    tmpMode = 0;
    if (S_ISDIR(fileMode)) {
        if (IsCapPermit(CAP_DAC_EXECUTE)
            || (!((uint)accMode & WRITE_OP) && IsCapPermit(CAP_DAC_READ_SEARCH))) {
            tmpMode |= EXEC_OP;
        }
    } else {
        if (IsCapPermit(CAP_DAC_EXECUTE) && (fileMode & MODE_IXUGO)) {
            tmpMode |= EXEC_OP;
        }
    }

    if (IsCapPermit(CAP_DAC_WRITE)) {
        tmpMode |= WRITE_OP;
    }

    if (IsCapPermit(CAP_DAC_READ_SEARCH)) {
        tmpMode |= READ_OP;
    }

    if (((uint)accMode & tmpMode) == accMode) {
        return 0;
    }

    return 1;
}

#ifdef VFS_USING_WORKDIR
static int SetWorkDir(const char *dir, size_t len)
{
  errno_t ret;
  uint lock_flags;
  LosProcessCB *curr = OsCurrProcessGet();

  spin_lock_irqsave(&curr->files->workdir_lock, lock_flags);
  ret = strncpy_s(curr->files->workdir, PATH_MAX, dir, len);
  curr->files->workdir[PATH_MAX - 1] = '\0';
  spin_unlock_irqrestore(&curr->files->workdir_lock, lock_flags);
  if (ret != EOK) {
      return -1;
  }

  return 0;
}
#endif

int chdir(const char *path)
{
    int ret;
    char *fullpath = NULL;
    char *fullpath_bak = NULL;
    struct stat statBuff;


    if (!path) {
        set_errno(EFAULT);
        return -1;
    }

    if (!strlen(path)) {
        set_errno(ENOENT);
        return -1;
    }

    if (strlen(path) > PATH_MAX) {
        set_errno(ENAMETOOLONG);
        return -1;
    }

    ret = vfs_normalize_path((const char *)NULL, path, &fullpath);
    if (ret < 0) {
        set_errno(-ret);
        return -1; /* build path failed */
    }
    fullpath_bak = fullpath;
    ret = stat(fullpath, &statBuff);
    if (ret < 0) {
        free(fullpath_bak);
        return -1;
    }

    if (!S_ISDIR(statBuff.st_mode)) {
        set_errno(ENOTDIR);
        free(fullpath_bak);
        return -1;
    }

    if (VfsPermissionCheck(statBuff.st_uid, statBuff.st_gid, statBuff.st_mode, EXEC_OP)) {
        set_errno(EACCES);
        free(fullpath_bak);
        return -1;
    }

#ifdef VFS_USING_WORKDIR
    ret = SetWorkDir(fullpath, strlen(fullpath));
    if (ret != 0)
      {
        PRINT_ERR("chdir path error!\n");
        ret = -1;
      }
#endif

    /* release normalize directory path name */

    free(fullpath_bak);

    return ret;
}

/**
 * this function is a POSIX compliant version, which will return current
 * working directory.
 *
 * @param buf the returned current directory.
 * @param size the buffer size.
 *
 * @return the returned current directory.
 */

char *getcwd(char *buf, size_t n)
{
#ifdef VFS_USING_WORKDIR
    int ret;
    unsigned int len;
    UINTPTR lock_flags;
    LosProcessCB *curr = OsCurrProcessGet();
#endif
    if (buf == NULL) {
        set_errno(EINVAL);
        return buf;
    }
#ifdef VFS_USING_WORKDIR
    spin_lock_irqsave(&curr->files->workdir_lock, lock_flags);
    len = strlen(curr->files->workdir);
    if (n <= len)
      {
        set_errno(ERANGE);
        spin_unlock_irqrestore(&curr->files->workdir_lock, lock_flags);
        return NULL;
      }
    ret = memcpy_s(buf, n, curr->files->workdir, len + 1);
    if (ret != EOK)
      {
        set_errno(ENAMETOOLONG);
        spin_unlock_irqrestore(&curr->files->workdir_lock, lock_flags);
        return NULL;
      }
    spin_unlock_irqrestore(&curr->files->workdir_lock, lock_flags);
#else
    PRINT_ERR("NO_WORKING_DIR\n");
#endif

    return buf;
}

int chmod(const char *path, mode_t mode)
{
    struct IATTR attr = {0};
    attr.attr_chg_mode = mode;
    attr.attr_chg_valid = CHG_MODE; /* change mode */
    int ret;

    ret = chattr(path, &attr);
    if (ret < 0) {
        return VFS_ERROR;
    }

    return OK;
}

int chown(const char *pathname, uid_t owner, gid_t group)
{
    struct IATTR attr = {0};
    attr.attr_chg_valid = 0;
    int ret;

    if (owner != (uid_t)-1) {
        attr.attr_chg_uid = owner;
        attr.attr_chg_valid |= CHG_UID;
    }
    if (group != (gid_t)-1) {
        attr.attr_chg_gid = group;
        attr.attr_chg_valid |= CHG_GID;
    }
    ret = chattr(pathname, &attr);
    if (ret < 0) {
        return VFS_ERROR;
    }

    return OK;
}

int access(const char *path, int amode)
{
    int ret;
    struct stat buf;
    struct statfs fsBuf;

    ret = statfs(path, &fsBuf);
    if (ret != 0) {
        if (get_errno() != ENOSYS) {
            return VFS_ERROR;
        }
        /* dev has no statfs ops, need devfs to handle this in feature */
    }

    if ((fsBuf.f_flags & MS_RDONLY) && ((unsigned int)amode & W_OK)) {
        set_errno(EROFS);
        return VFS_ERROR;
    }

    ret = stat(path, &buf);
    if (ret != 0) {
        return VFS_ERROR;
    }

    if (VfsPermissionCheck(buf.st_uid, buf.st_gid, buf.st_mode, amode)) {
        set_errno(EACCES);
        return VFS_ERROR;
    }

    return OK;
}

static struct dirent **scandir_get_file_list(const char *dir, int *num, int(*filter)(const struct dirent *))
{
    DIR *od = NULL;
    int listSize = MAX_DIR_ENT;
    int n = 0;
    struct dirent **list = NULL;
    struct dirent **newList = NULL;
    struct dirent *ent = NULL;
    struct dirent *p = NULL;
    int err;

    od = opendir(dir);
    if (od == NULL) {
        return NULL;
    }

    list = (struct dirent **)malloc(listSize * sizeof(struct dirent *));
    if (list == NULL) {
        (void)closedir(od);
        return NULL;
    }

    for (ent = readdir(od); ent != NULL; ent = readdir(od)) {
        if (filter && !filter(ent)) {
            continue;
        }

        if (n == listSize) {
            listSize += MAX_DIR_ENT;
            newList = (struct dirent **)malloc(listSize * sizeof(struct dirent *));
            if (newList == NULL) {
                break;
            }

            err = memcpy_s(newList, listSize * sizeof(struct dirent *), list, n * sizeof(struct dirent *));
            if (err != EOK) {
                free(newList);
                break;
            }
            free(list);
            list = newList;
        }

        p = (struct dirent *)malloc(sizeof(struct dirent));
        if (p == NULL) {
            break;
        }

        (void)memcpy_s((void *)p, sizeof(struct dirent), (void *)ent, sizeof(struct dirent));
        list[n] = p;

        n++;
    }

    if (closedir(od) < 0) {
        while (n--) {
            free(list[n]);
        }
        free(list);
        return NULL;
    }

    *num = n;
    return list;
}

int scandir(const char *dir, struct dirent ***namelist,
            int(*filter)(const struct dirent *),
            int(*compar)(const struct dirent **,
                         const struct dirent **))
{
    int n = 0;
    struct dirent **list = NULL;

    if ((dir == NULL) || (namelist == NULL)) {
        return -1;
    }

    list = scandir_get_file_list(dir, &n, filter);
    if (list == NULL) {
        return -1;
    }

    /* Change to return to the array size */

    *namelist = (struct dirent **)malloc(n * sizeof(struct dirent *));
    if (*namelist == NULL && n > 0) {
        *namelist = list;
    } else if (*namelist != NULL) {
        (void)memcpy_s(*namelist, n * sizeof(struct dirent *), list, n * sizeof(struct dirent *));
        free(list);
    } else {
        free(list);
    }

    /* Sort array */

    if (compar && *namelist) {
        qsort((void *)*namelist, (size_t)n, sizeof(struct dirent *), (int (*)(const void *, const void *))*compar);
    }

    return n;
}

int alphasort(const struct dirent **a, const struct dirent **b)
{
    return strcoll((*a)->d_name, (*b)->d_name);
}

char *rindex(const char *s, int c)
{
    if (s == NULL) {
        return NULL;
    }

    /* Don't bother tracing - strrchr can do that */
    return (char *)strrchr(s, c);
}

static char *ls_get_fullpath(const char *path, struct dirent *pdirent)
{
    char *fullpath = NULL;
    int ret = 0;

    if (path[1] != '\0') {
        /* 2: The position of the path character: / and the end character /0 */

        fullpath = (char *)malloc(strlen(path) + strlen(pdirent->d_name) + 2);
        if (fullpath == NULL) {
            goto exit_with_nomem;
        }

        /* 2: The position of the path character: / and the end character /0 */

        ret = snprintf_s(fullpath, strlen(path) + strlen(pdirent->d_name) + 2,
                         strlen(path) + strlen(pdirent->d_name) + 1, "%s/%s", path, pdirent->d_name);
        if (ret < 0) {
            free(fullpath);
            set_errno(ENAMETOOLONG);
            return NULL;
        }
    } else {
        /* 2: The position of the path character: / and the end character /0 */

        fullpath = (char *)malloc(strlen(pdirent->d_name) + 2);
        if (fullpath == NULL) {
            goto exit_with_nomem;
        }

        /* 2: The position of the path character: / and the end character /0 */

        ret = snprintf_s(fullpath, strlen(pdirent->d_name) + 2, strlen(pdirent->d_name) + 1,
                         "/%s", pdirent->d_name);
        if (ret < 0) {
            free(fullpath);
            set_errno(ENAMETOOLONG);
            return NULL;
        }
    }
    return fullpath;

exit_with_nomem:
    set_errno(ENOSPC);
    return (char *)NULL;
}

static void PrintFileInfo64(const struct stat64 *stat64Info, const char *name)
{
    mode_t mode;
    char str[UGO_NUMS][UGO_NUMS + 1] = {0};
    char dirFlag;
    int i;

    for (i = 0; i < UGO_NUMS; i++) {
        mode = stat64Info->st_mode >> (uint)(USER_MODE_SHIFT - i * UGO_NUMS);
        str[i][0] = (mode & READ_OP) ? 'r' : '-';
        str[i][1] = (mode & WRITE_OP) ? 'w' : '-';
        str[i][UGO_NUMS - 1] = (mode & EXEC_OP) ? 'x' : '-';
    }

    if (S_ISDIR(stat64Info->st_mode)) {
        dirFlag = 'd';
    } else if (S_ISLNK(stat64Info->st_mode)) {
        dirFlag = 'l';
    } else {
        dirFlag = '-';
    }

    PRINTK("%c%s%s%s %-8lld u:%-5d g:%-5d %-10s\n", dirFlag,
           str[0], str[1], str[UGO_NUMS - 1], stat64Info->st_size, stat64Info->st_uid, stat64Info->st_gid, name);
}

static void PrintFileInfo(const struct stat *statInfo, const char *name)
{
    mode_t mode;
    char str[UGO_NUMS][UGO_NUMS + 1] = {0};
    char dirFlag;
    int i;

    for (i = 0; i < UGO_NUMS; i++) {
        mode = statInfo->st_mode >> (uint)(USER_MODE_SHIFT - i * UGO_NUMS);
        str[i][0] = (mode & READ_OP) ? 'r' : '-';
        str[i][1] = (mode & WRITE_OP) ? 'w' : '-';
        str[i][UGO_NUMS - 1] = (mode & EXEC_OP) ? 'x' : '-';
    }

    if (S_ISDIR(statInfo->st_mode)) {
        dirFlag = 'd';
    } else if (S_ISLNK(statInfo->st_mode)) {
        dirFlag = 'l';
    } else {
        dirFlag = '-';
    }

    PRINTK("%c%s%s%s %-8lld u:%-5d g:%-5d %-10s\n", dirFlag,
           str[0], str[1], str[UGO_NUMS - 1], statInfo->st_size, statInfo->st_uid, statInfo->st_gid, name);
}

int LsFile(const char *path)
{
    struct stat64 stat64Info;
    struct stat statInfo;

    if (stat64(path, &stat64Info) == 0) {
        PrintFileInfo64(&stat64Info, path);
    } else if (stat(path, &statInfo) == 0) {
        PrintFileInfo(&statInfo, path);
    } else {
        return -1;
    }

    return 0;
}

int LsDir(const char *path)
{
    struct stat statInfo = { 0 };
    struct stat64 stat64Info = { 0 };
    DIR *d = NULL;
    char *fullpath = NULL;
    char *fullpath_bak = NULL;
    struct dirent *pdirent = NULL;

    d = opendir(path);
    if (d == NULL) {
        return -1;
    }

    PRINTK("Directory %s:\n", path);
    do {
        pdirent = readdir(d);
        if (pdirent == NULL) {
            break;
        } else {
            if (!strcmp(pdirent->d_name, ".") || !strcmp(pdirent->d_name, "..")) {
                continue;
            }
            (void)memset_s(&statInfo, sizeof(struct stat), 0, sizeof(struct stat));
            (void)memset_s(&stat64Info, sizeof(struct stat), 0, sizeof(struct stat));
            fullpath = ls_get_fullpath(path, pdirent);
            if (fullpath == NULL) {
                (void)closedir(d);
                return -1;
            }

            fullpath_bak = fullpath;
            if (stat64(fullpath, &stat64Info) == 0) {
                PrintFileInfo64(&stat64Info, pdirent->d_name);
            } else if (stat(fullpath, &statInfo) == 0) {
                PrintFileInfo(&statInfo, pdirent->d_name);
            } else {
                PRINTK("BAD file: %s\n", pdirent->d_name);
            }
            free(fullpath_bak);
        }
    } while (1);
    (void)closedir(d);

    return 0;
}

void ls(const char *pathname)
{
    struct stat statInfo = { 0 };
    char *path = NULL;
    int ret;

    if (pathname == NULL) {
#ifdef VFS_USING_WORKDIR
        UINTPTR lock_flags;
        LosProcessCB *curr = OsCurrProcessGet();

        /* open current working directory */

        spin_lock_irqsave(&curr->files->workdir_lock, lock_flags);
        path = strdup(curr->files->workdir);
        spin_unlock_irqrestore(&curr->files->workdir_lock, lock_flags);
#else
        path = strdup("/");
#endif
        if (path == NULL) {
            return;
        }
    } else {
        ret = vfs_normalize_path(NULL, pathname, &path);
        if (ret < 0) {
            set_errno(-ret);
            return;
        }
    }

    ret = stat(path, &statInfo);
    if (ret < 0) {
        perror("ls error");
        free(path);
        return;
    }

    if (statInfo.st_mode & S_IFDIR) { /* list all directory and file */
        ret = LsDir((pathname == NULL) ? path : pathname);
    } else { /* show the file infomation */
        ret = LsFile(path);
    }
    if (ret < 0) {
        perror("ls error");
    }

    free(path);
    return;
}


char *realpath(const char *path, char *resolved_path)
{
    int ret, result;
    char *new_path = NULL;
    struct stat buf;

    ret = vfs_normalize_path(NULL, path, &new_path);
    if (ret < 0) {
        ret = -ret;
        set_errno(ret);
        return NULL;
    }

    result = stat(new_path, &buf);

    if (resolved_path == NULL) {
        if (result != ENOERR) {
            free(new_path);
            return NULL;
        }
        return new_path;
    }

    ret = strcpy_s(resolved_path, PATH_MAX, new_path);
    if (ret != EOK) {
        ret = -ret;
        set_errno(ret);
        free(new_path);
        return NULL;
    }

    free(new_path);
    if (result != ENOERR) {
        return NULL;
    }
    return resolved_path;
}

void lsfd(void)
{
    struct filelist *f_list = NULL;
    unsigned int i = 3; /* file start fd */
    int ret;
    struct Vnode *node = NULL;

    f_list = &tg_filelist;

    PRINTK("   fd    filename\n");
    ret = sem_wait(&f_list->fl_sem);
    if (ret < 0) {
        PRINTK("sem_wait error, ret=%d\n", ret);
        return;
    }

    while (i < CONFIG_NFILE_DESCRIPTORS) {
        node = files_get_openfile(i);
        if (node) {
            PRINTK("%5d   %s\n", i, f_list->fl_files[i].f_path);
        }
        i++;
    }
    (void)sem_post(&f_list->fl_sem);
}

mode_t GetUmask(void)
{
    return OsCurrProcessGet()->umask;
}

mode_t SysUmask(mode_t mask)
{
    UINT32 intSave;
    mode_t umask;
    mode_t oldUmask;
    umask = mask & UMASK_FULL;
    SCHEDULER_LOCK(intSave);
    oldUmask = OsCurrProcessGet()->umask;
    OsCurrProcessGet()->umask = umask;
    SCHEDULER_UNLOCK(intSave);
    return oldUmask;
}

