/*
 *  Derived from mount_bsd.c from the fuse distribution.
 *
 *  FUSE: Filesystem in Userspace
 *  Copyright (C) 2005-2006 Csaba Henk <csaba.henk@creo.hu>
 *  Copyright (C) 2007-2009 Amit Singh <asingh@gmail.com>
 *  Copyright (c) 2010 Gluster, Inc. <http://www.gluster.com>
 *
 *  This program can be distributed under the terms of the GNU LGPLv2.
 *  See the file COPYING.LIB.
 */

#undef _POSIX_C_SOURCE
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <sys/sysctl.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <stddef.h>
#include <fcntl.h>
#include <errno.h>
#include <string.h>
#include <paths.h>

#include <libproc.h>
#include <sys/utsname.h>

#include <sys/param.h>
#include <sys/mount.h>
#include <AssertMacros.h>

#include "fuse_param.h"
#include "fuse_ioctl.h"

#include "glusterfs/glusterfs.h"
#include "glusterfs/logging.h"
#include "glusterfs/common-utils.h"

#define GFFUSE_LOGERR(...) \
        gf_log ("glusterfs-fuse", GF_LOG_ERROR, ## __VA_ARGS__)

int
gf_fuse_mount (const char *mountpoint, char *fsname, char *mnt_param,
               pid_t *mnt_pid, int status_fd) /* Not used on OS X */
{
        int fd       = 0;
        int pid      = 0;
        int ret      = 0;
        char *fdnam  = NULL;
        char *dev    = NULL;
        char vstr[4];
        unsigned vval = 0;
        int i        = 0;

        const char *mountprog              = OSXFUSE_MOUNT_PROG;
        sig_t chldf                        = SIG_ERR;
        char   version[MAXHOSTNAMELEN + 1] = { 0 };
        size_t version_len                 = MAXHOSTNAMELEN;
        size_t version_len_desired         = 0;
        int r                              = 0;
        char devpath[MAXPATHLEN]           = { 0 };;

        if (!mountpoint) {
                gf_log ("glustefs-fuse", GF_LOG_ERROR,
                        "missing or invalid mount point");
                goto err;
        }

        /* mount_fusefs should not try to spawn the daemon */
        setenv("MOUNT_FUSEFS_SAFE", "1", 1);

        /* to notify mount_fusefs it's called from lib */
        setenv("MOUNT_FUSEFS_CALL_BY_LIB", "1", 1);

        chldf = signal(SIGCHLD, SIG_DFL); /* So that we can wait4() below. */

        if (chldf == SIG_ERR) {
                gf_log ("glusterfs-fuse", GF_LOG_ERROR,
                        "signal() returned SIG_ERR: %s",
                        strerror(errno));
                goto err;
        }

        /* check for user<->kernel match. */
        ret = sysctlbyname(SYSCTL_OSXFUSE_VERSION_NUMBER, version,
                              &version_len, NULL, (size_t)0);
        if (ret != 0) {
                gf_log ("glustefs-fuse", GF_LOG_ERROR,
                        "sysctlbyname() returned error: %s",
                        strerror(errno));
                goto err;
        }

        /* sysctlbyname() includes the trailing '\0' in version_len */
        version_len_desired = sizeof ("2.x.y");

        if (version_len != version_len_desired) {
                gf_log ("glusterfs-fuse", GF_LOG_ERROR,
                        "version length mismatch for OSXFUSE %s",
                        version);
                ret = -1;
                goto err;
        }

        for (i = 0; i < 3; i++)
                vstr[i] = version[2*i];
        vstr[3] = '\0';

        vval = strtoul(vstr, NULL, 10);
        if (vval < 264) {
                GFFUSE_LOGERR("OSXFUSE version %s is not supported", version);
                ret = -1;
                goto err;
        }

        gf_log("glusterfs-fuse", GF_LOG_INFO,
               "OSXFUSE kext version supported %s", version);

        fdnam = getenv("FUSE_DEV_FD");
        if (fdnam) {
                fd = strtol(fdnam, NULL, 10);
                if (fd < 0) {
                        GFFUSE_LOGERR("invalid value given in FUSE_DEV_FD");
                        ret = -1;
                        goto err;
                }
                goto mount;
        }

        dev = getenv("FUSE_DEV_NAME");
        if (!dev) {
                for (r = 0; r < OSXFUSE_NDEVICES; r++) {
                        snprintf(devpath, MAXPATHLEN - 1,
                                 _PATH_DEV OSXFUSE_DEVICE_BASENAME "%d", r);
                        if ((fd = open(devpath, O_RDWR)) < 0) {
                                GFFUSE_LOGERR("failed to open device %s (%s)",
                                              devpath,
                                              strerror(errno));
                                goto err;
                        }
                        dev = devpath;
                        goto mount;
                }
        }

        fd = open(dev, O_RDWR);
        if (fd < 0) {
                GFFUSE_LOGERR("failed to open device %s (%s)", dev,
                              strerror(errno));
                ret = -1;
                goto err;
        }

mount:
        signal(SIGCHLD, chldf);

        pid = fork();
        if (pid == -1) {
                GFFUSE_LOGERR("fork() failed (%s)", strerror(errno));
                ret = -1;
                goto err;
        }

        if (pid == 0) {
                pid = fork();
                if (pid == -1) {
                        GFFUSE_LOGERR("fork() failed (%s)", strerror(errno));
                        ret = -1;
                        goto err;
                }

                if (pid == 0) {
                        const char *argv[32];
                        int a = 0;
                        char *opts = NULL;

                        if (asprintf(&opts, "%s,fssubtype=glusterfs",
                                     mnt_param) == -1) {
                                GFFUSE_LOGERR("asprintf() error: %s",
                                              strerror(errno));
                                ret = -1;
                                goto err;
                        }

                        if (!fdnam)
                                asprintf(&fdnam, "%d", fd);

                        argv[a++] = mountprog;
                        if (opts) {
                                argv[a++] = "-o";
                                argv[a++] = opts;
                        }
                        argv[a++] = fdnam;
                        argv[a++] = mountpoint;
                        argv[a++] = NULL;

                        {
                                char title[MAXPATHLEN + 1] = { 0 };
                                u_int32_t len = MAXPATHLEN;
                                int ret = proc_pidpath(getpid(), title, len);
                                if (ret) {
                                        setenv("MOUNT_FUSEFS_DAEMON_PATH",
                                               title, 1);
                                }
                        }
                        execvp(mountprog, (char **) argv);
                        GFFUSE_LOGERR("OSXFUSE: failed to exec mount"
                                      " program (%s)", strerror(errno));
                        _exit(1);
                }
                _exit(0);
        }
        ret = fd;
err:
        if (ret == -1) {
                if (fd > 0) {
                        close(fd);
                }
        }
        return ret;
}

void
gf_fuse_unmount(const char *mountpoint, int fd)
{
        int ret;
        struct stat sbuf;
        char dev[128];
        char resolved_path[PATH_MAX];
        char *ep, *rp = NULL;

        unsigned int hs_complete = 0;

        ret = ioctl(fd, FUSEDEVIOCGETHANDSHAKECOMPLETE, &hs_complete);
        if (ret || !hs_complete) {
                return;
        }

        if (fstat(fd, &sbuf) == -1) {
                return;
        }

        devname_r(sbuf.st_rdev, S_IFCHR, dev, 128);

        if (strncmp(dev, OSXFUSE_DEVICE_BASENAME,
                    sizeof(OSXFUSE_DEVICE_BASENAME) - 1)) {
                return;
        }

        strtol(dev + sizeof(OSXFUSE_DEVICE_BASENAME) - 1, &ep, 10);
        if (*ep != '\0') {
                return;
        }

        rp = realpath(mountpoint, resolved_path);
        if (rp) {
                ret = unmount(resolved_path, 0);
        }

        close(fd);
        return;
}
