/*
  Copyright (c) 2008-2012 Red Hat, Inc. <http://www.redhat.com>
  This file is part of GlusterFS.

  This file is licensed to you under your choice of the GNU Lesser
  General Public License, version 3 or any later version (LGPLv3 or
  later), or the GNU General Public License, version 2 (GPLv2), in all
  cases as published by the Free Software Foundation.
*/

#include <stdlib.h>
#include <unistd.h>
#include <stdarg.h>
#include <getopt.h>
#include <sys/types.h>
#include <dirent.h>

#include "glusterfs/logging.h"
#include "glusterfs/compat.h"
#include "glusterfs/iatt.h"
#include "glusterfs/syscall.h"
#include "glusterfs/run.h"
#include "glusterfs/libglusterfs-messages.h"

#ifdef GF_SOLARIS_HOST_OS
int
solaris_fsetxattr(int fd, const char *key, const char *value, size_t size,
                  int flags)
{
    int attrfd = -1;
    int ret = 0;

    attrfd = openat(fd, key, flags | O_CREAT | O_WRONLY | O_XATTR, 0777);
    if (attrfd >= 0) {
        ftruncate(attrfd, 0);
        ret = write(attrfd, value, size);
        close(attrfd);
    } else {
        if (errno != ENOENT)
            gf_msg("libglusterfs", GF_LOG_ERROR, errno,
                   LG_MSG_SET_ATTRIBUTE_FAILED,
                   "Couldn't set "
                   "extended attribute for %d",
                   fd);
        return -1;
    }

    return 0;
}

int
solaris_fgetxattr(int fd, const char *key, char *value, size_t size)
{
    int attrfd = -1;
    int ret = 0;

    attrfd = openat(fd, key, O_RDONLY | O_XATTR);
    if (attrfd >= 0) {
        if (size == 0) {
            struct stat buf;
            fstat(attrfd, &buf);
            ret = buf.st_size;
        } else {
            ret = read(attrfd, value, size);
        }
        close(attrfd);
    } else {
        if (errno != ENOENT)
            gf_msg("libglusterfs", GF_LOG_INFO, errno,
                   LG_MSG_READ_ATTRIBUTE_FAILED,
                   "Couldn't read "
                   "extended attribute for the file %d",
                   fd);
        if (errno == ENOENT)
            errno = ENODATA;
        return -1;
    }

    return ret;
}

/* Solaris does not support xattr for symlinks and dev files. Since gfid and
   other trusted attributes are stored as xattrs, we need to provide support for
   them. A mapped regular file is stored in the /.glusterfs_xattr_inode of the
   export dir. All xattr ops related to the special files are redirected to this
   map file.
*/

int
make_export_path(const char *real_path, char **path)
{
    int ret = -1;
    char *tmp = NULL;
    char *export_path = NULL;
    char *dup = NULL;
    char *ptr = NULL;
    char *freeptr = NULL;
    uuid_t gfid = {
        0,
    };

    export_path = GF_CALLOC(1, sizeof(char) * PATH_MAX, 0);
    if (!export_path)
        goto out;

    dup = gf_strdup(real_path);
    if (!dup)
        goto out;

    freeptr = dup;
    ret = solaris_getxattr("/", GFID_XATTR_KEY, gfid, 16);
    /* Return value of getxattr */
    if (ret == 16) {
        if (__is_root_gfid(gfid)) {
            strcat(export_path, "/");
            ret = 0;
            goto done;
        }
    }

    do {
        ptr = strtok_r(dup, "/", &tmp);
        if (!ptr)
            break;
        strcat(export_path, dup);
        ret = solaris_getxattr(export_path, GFID_XATTR_KEY, gfid, 16);
        if (ret == 16) {
            if (__is_root_gfid(gfid)) {
                ret = 0;
                goto done;
            }
        }
        strcat(export_path, "/");
        dup = tmp;
    } while (ptr);

    goto out;

done:
    if (!ret) {
        *path = export_path;
    }
out:
    GF_FREE(freeptr);
    if (ret && export_path)
        GF_FREE(export_path);

    return ret;
}
int
solaris_xattr_resolve_path(const char *real_path, char **path)
{
    int ret = -1;
    char *export_path = NULL;
    char xattr_path[PATH_MAX] = {
        0,
    };
    struct stat lstatbuf = {
        0,
    };
    struct iatt stbuf = {
        0,
    };
    struct stat statbuf = {
        0,
    };

    ret = lstat(real_path, &lstatbuf);
    if (ret != 0)
        return ret;
    iatt_from_stat(&stbuf, &lstatbuf);
    if (IA_ISREG(stbuf.ia_type) || IA_ISDIR(stbuf.ia_type))
        return -1;

    ret = make_export_path(real_path, &export_path);
    if (!ret && export_path) {
        strcat(export_path, "/" GF_SOLARIS_XATTR_DIR);
        if (lstat(export_path, &statbuf)) {
            ret = mkdir(export_path, 0755);
            if (ret && (errno != EEXIST)) {
                gf_msg_debug(THIS->name, 0,
                             "mkdir failed,"
                             " errno: %d",
                             errno);
                goto out;
            }
        }

        snprintf(xattr_path, PATH_MAX, "%s%s%lu", export_path, "/",
                 stbuf.ia_ino);

        ret = lstat(xattr_path, &statbuf);

        if (ret) {
            ret = mknod(xattr_path, S_IFREG | O_WRONLY, 0);
            if (ret && (errno != EEXIST)) {
                gf_msg(THIS->name, GF_LOG_WARNING, errno, LG_MSG_FILE_OP_FAILED,
                       "Failed to "
                       "create mapped file %s",
                       xattr_path);
                goto out;
            }
        }
        *path = gf_strdup(xattr_path);
    }
out:
    GF_FREE(export_path);
    if (*path)
        return 0;
    else
        return -1;
}

int
solaris_setxattr(const char *path, const char *key, const char *value,
                 size_t size, int flags)
{
    int attrfd = -1;
    int ret = 0;
    char *mapped_path = NULL;

    ret = solaris_xattr_resolve_path(path, &mapped_path);
    if (!ret) {
        attrfd = attropen(mapped_path, key, flags | O_CREAT | O_WRONLY, 0777);
    } else {
        attrfd = attropen(path, key, flags | O_CREAT | O_WRONLY, 0777);
    }
    if (attrfd >= 0) {
        ftruncate(attrfd, 0);
        ret = write(attrfd, value, size);
        close(attrfd);
        ret = 0;
    } else {
        if (errno != ENOENT)
            gf_msg("libglusterfs", GF_LOG_ERROR, errno,
                   LG_MSG_SET_ATTRIBUTE_FAILED,
                   "Couldn't set "
                   "extended attribute for %s",
                   path);
        ret = -1;
    }
    GF_FREE(mapped_path);
    return ret;
}

int
solaris_listxattr(const char *path, char *list, size_t size)
{
    int attrdirfd = -1;
    ssize_t len = 0;
    DIR *dirptr = NULL;
    struct dirent *dent = NULL;
    int newfd = -1;
    char *mapped_path = NULL;
    int ret = -1;

    ret = solaris_xattr_resolve_path(path, &mapped_path);
    if (!ret) {
        attrdirfd = attropen(mapped_path, ".", O_RDONLY, 0);
    } else {
        attrdirfd = attropen(path, ".", O_RDONLY, 0);
    }
    if (attrdirfd >= 0) {
        newfd = dup(attrdirfd);
        dirptr = fdopendir(newfd);
        if (dirptr) {
            while ((dent = readdir(dirptr))) {
                size_t listlen = strlen(dent->d_name);
                if (!strcmp(dent->d_name, ".") || !strcmp(dent->d_name, "..")) {
                    /* we don't want "." and ".." here */
                    continue;
                }
                if (size == 0) {
                    /* return the current size of the list
                       of extended attribute names*/
                    len += listlen + 1;
                } else {
                    /* check size and copy entry + null
                       into list. */
                    if ((len + listlen + 1) > size) {
                        errno = ERANGE;
                        len = -1;
                        break;
                    } else {
                        strncpy(list + len, dent->d_name, listlen);
                        len += listlen;
                        list[len] = '\0';
                        ++len;
                    }
                }
            }

            if (closedir(dirptr) == -1) {
                close(attrdirfd);
                len = -1;
                goto out;
            }
        } else {
            close(attrdirfd);
            len = -1;
            goto out;
        }
        close(attrdirfd);
    }
out:
    GF_FREE(mapped_path);
    return len;
}

int
solaris_flistxattr(int fd, char *list, size_t size)
{
    int attrdirfd = -1;
    ssize_t len = 0;
    DIR *dirptr = NULL;
    struct dirent *dent = NULL;
    int newfd = -1;

    attrdirfd = openat(fd, ".", O_RDONLY, 0);
    if (attrdirfd >= 0) {
        newfd = dup(attrdirfd);
        dirptr = fdopendir(newfd);
        if (dirptr) {
            while ((dent = readdir(dirptr))) {
                size_t listlen = strlen(dent->d_name);
                if (!strcmp(dent->d_name, ".") || !strcmp(dent->d_name, "..")) {
                    /* we don't want "." and ".." here */
                    continue;
                }
                if (size == 0) {
                    /* return the current size of the list
                       of extended attribute names*/
                    len += listlen + 1;
                } else {
                    /* check size and copy entry + null
                       into list. */
                    if ((len + listlen + 1) > size) {
                        errno = ERANGE;
                        len = -1;
                        break;
                    } else {
                        strncpy(list + len, dent->d_name, listlen);
                        len += listlen;
                        list[len] = '\0';
                        ++len;
                    }
                }
            }

            if (closedir(dirptr) == -1) {
                close(attrdirfd);
                return -1;
            }
        } else {
            close(attrdirfd);
            return -1;
        }
        close(attrdirfd);
    }
    return len;
}

int
solaris_removexattr(const char *path, const char *key)
{
    int ret = -1;
    int attrfd = -1;
    char *mapped_path = NULL;

    ret = solaris_xattr_resolve_path(path, &mapped_path);
    if (!ret) {
        attrfd = attropen(mapped_path, ".", O_RDONLY, 0);
    } else {
        attrfd = attropen(path, ".", O_RDONLY, 0);
    }
    if (attrfd >= 0) {
        ret = unlinkat(attrfd, key, 0);
        close(attrfd);
    } else {
        if (errno == ENOENT)
            errno = ENODATA;
        ret = -1;
    }

    GF_FREE(mapped_path);

    return ret;
}

int
solaris_getxattr(const char *path, const char *key, char *value, size_t size)
{
    int attrfd = -1;
    int ret = 0;
    char *mapped_path = NULL;

    ret = solaris_xattr_resolve_path(path, &mapped_path);
    if (!ret) {
        attrfd = attropen(mapped_path, key, O_RDONLY, 0);
    } else {
        attrfd = attropen(path, key, O_RDONLY, 0);
    }

    if (attrfd >= 0) {
        if (size == 0) {
            struct stat buf;
            fstat(attrfd, &buf);
            ret = buf.st_size;
        } else {
            ret = read(attrfd, value, size);
        }
        close(attrfd);
    } else {
        if (errno != ENOENT)
            gf_msg("libglusterfs", GF_LOG_INFO, errno,
                   LG_MSG_READ_ATTRIBUTE_FAILED,
                   "Couldn't read "
                   "extended attribute for the file %s",
                   path);
        if (errno == ENOENT)
            errno = ENODATA;
        ret = -1;
    }
    GF_FREE(mapped_path);
    return ret;
}

char *
strsep(char **str, const char *delims)
{
    char *token;

    if (*str == NULL) {
        /* No more tokens */
        return NULL;
    }

    token = *str;
    while (**str != '\0') {
        if (strchr(delims, **str) != NULL) {
            **str = '\0';
            (*str)++;
            return token;
        }
        (*str)++;
    }
    /* There is no other token */
    *str = NULL;
    return token;
}

/* Code comes from libiberty */

int
vasprintf(char **result, const char *format, va_list args)
{
    return gf_vasprintf(result, format, args);
}

int
asprintf(char **buf, const char *fmt, ...)
{
    int status;
    va_list ap;

    va_start(ap, fmt);
    status = vasprintf(buf, fmt, ap);
    va_end(ap);
    return status;
}

int
solaris_unlink(const char *path)
{
    char *mapped_path = NULL;
    struct stat stbuf = {
        0,
    };
    int ret = -1;

    ret = solaris_xattr_resolve_path(path, &mapped_path);

    if (!ret && mapped_path) {
        if (lstat(path, &stbuf)) {
            gf_msg(THIS->name, GF_LOG_WARNING, errno, LG_MSG_FILE_OP_FAILED,
                   "Stat failed on "
                   "mapped file %s",
                   mapped_path);
            goto out;
        }
        if (stbuf.st_nlink == 1) {
            if (remove(mapped_path))
                gf_msg(THIS->name, GF_LOG_WARNING, errno, LG_MSG_FILE_OP_FAILED,
                       "Failed to "
                       "remove mapped file %s",
                       mapped_path);
        }
    }

out:
    GF_FREE(mapped_path);

    return unlink(path);
}

int
solaris_rename(const char *old_path, const char *new_path)
{
    char *mapped_path = NULL;
    int ret = -1;

    ret = solaris_xattr_resolve_path(new_path, &mapped_path);

    if (!ret && mapped_path) {
        if (!remove(mapped_path))
            gf_msg(THIS->name, GF_LOG_WARNING, errno, LG_MSG_FILE_OP_FAILED,
                   "Failed to remove "
                   "mapped file %s.",
                   mapped_path);
        GF_FREE(mapped_path);
    }

    return rename(old_path, new_path);
}

char *
mkdtemp(char *tempstring)
{
    char *new_string = NULL;
    int ret = 0;

    new_string = mkstemp(tempstring);
    if (!new_string)
        goto out;

    ret = mkdir(new_string, 0700);
    if (ret < 0)
        new_string = NULL;

out:
    return new_string;
}

#endif /* GF_SOLARIS_HOST_OS */

#ifdef GF_BSD_HOST_OS
void
gf_extattr_list_reshape(char *bsd_list, ssize_t size)
{
    /*
     * the format of bsd_list is
     *     <attr_len>attr<attr_len>attr...
     * we try to reformat it as Linux's
     *     attr<\0>attr<\0>...
     * */
    if (NULL == bsd_list || size <= 0)
        return;

    size_t i = 0, j;

    while (i < size) {
        size_t attr_len = bsd_list[i];

        for (j = i; j < i + attr_len; ++j)
            bsd_list[j] = bsd_list[j + 1];
        bsd_list[j] = '\0';

        i += attr_len + 1;
        gf_msg_debug("syscall", 0, "syscall debug: %lu", attr_len);
    }
}
#endif /* GF_BSD_HOST_OS */

#ifndef HAVE_STRNLEN
size_t
strnlen(const char *string, size_t maxlen)
{
    int len = 0;
    while ((len < maxlen) && string[len])
        len++;
    return len;
}
#endif /* STRNLEN */

int
gf_umount_lazy(char *xlname, char *path, int rmdir_flag)
{
    int ret = -1;
    runner_t runner = {
        0,
    };

    runinit(&runner);
#ifdef GF_LINUX_HOST_OS
    runner_add_args(&runner, _PATH_UMOUNT, "-l", path, NULL);
#else
    if (rmdir_flag)
        runner_add_args(&runner, SBIN_DIR "/umountd", "-r", path, NULL);
    else
        runner_add_args(&runner, SBIN_DIR "/umountd", path, NULL);
#endif
    ret = runner_run(&runner);
    if (ret) {
        gf_msg(xlname, GF_LOG_ERROR, errno, LG_MSG_UNMOUNT_FAILED,
               "Lazy unmount of %s", path);
    }

#ifdef GF_LINUX_HOST_OS
    if (!ret && rmdir_flag) {
        ret = sys_rmdir(path);
        if (ret)
            gf_msg(xlname, GF_LOG_WARNING, errno, LG_MSG_DIR_OP_FAILED,
                   "rmdir %s", path);
    }
#endif

    return ret;
}
