/*
  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.
*/

#ifndef _CONFIG_H
#define _CONFIG_H
#include "config.h"
#endif

#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <errno.h>
#include <dirent.h>
#include <limits.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/mount.h>

#include "glusterfs/globals.h"
#include "glusterfs/glusterfs.h"
#include "glusterfs/logging.h"
#include "glusterfs/syscall.h"
#include "glusterfs/mem-types.h"

static void
usage(void)
{
    fprintf(stderr, "Usage: umountd [-d dev] [-t timeout] [-r] path\n");
    exit(EXIT_FAILURE);
}

static int
sanity_check(char *path, dev_t *devp)
{
    struct stat st;
    struct stat parent_st;
    int ret;
    char pathtmp[PATH_MAX];
    char *parent;

    if (path == NULL)
        usage();

    if ((ret = stat(path, &st)) != 0) {
        switch (errno) {
            case ENOTCONN:
                /* volume is stopped */
                break;
            default:
                gf_log("umountd", GF_LOG_ERROR, "Cannot access %s: %s\n", path,
                       strerror(errno));
                goto out;
        }
    }

    /* If dev was not specified, get it from path */
    if (*devp == -1 && ret == 0)
        *devp = st.st_dev;

    snprintf(pathtmp, PATH_MAX, "%s", path);
    parent = dirname(pathtmp);

    if (stat(parent, &parent_st) != 0) {
        gf_log("umountd", GF_LOG_ERROR, "Cannot access %s: %s\n", parent,
               strerror(errno));
        goto out;
    }

    if (st.st_dev == parent_st.st_dev) {
        gf_log("umountd", GF_LOG_ERROR, "No filesystem mounted on %s\n", path);
        goto out;
    }

    ret = 0;

out:
    return ret;
}

static void
log_rotate(int signum)
{
    gf_log_logrotate(1);

    if (signal(SIGHUP, *log_rotate) == SIG_ERR) {
        gf_log("umountd", GF_LOG_ERROR, "signal () failed");
        exit(EXIT_FAILURE);
    }

    return;
}

static int
logging_init(void)
{
    glusterfs_ctx_t *ctx;
    char log_file[PATH_MAX];
    int ret = -1;

    ctx = glusterfs_ctx_new();
    if (!ctx) {
        fprintf(stderr, "glusterfs_ctx_new failed\n");
        goto out;
    }

    ret = glusterfs_globals_init(ctx);
    if (ret) {
        fprintf(stderr, "glusterfs_globals_init failed\n");
        goto out;
    }

    THIS->ctx = ctx;
    xlator_mem_acct_init(THIS, gf_common_mt_end);

    snprintf(log_file, PATH_MAX, "%s/umountd.log", DEFAULT_LOG_FILE_DIRECTORY);

    ret = gf_log_init(ctx, log_file, "umountd");
    if (ret) {
        fprintf(stderr, "gf_log_init failed\n");
        goto out;
    }

    if (signal(SIGHUP, *log_rotate) == SIG_ERR) {
        gf_log("umountd", GF_LOG_ERROR, "signal () failed");
        goto out;
    }

    ret = 0;
out:
    return ret;
}

static int
umountd_async(char *path, dev_t dev, int frmdir, int timeout)
{
    int ret = -1;
    struct stat stbuf = {
        0,
    };
    int unmount_ret = 0;

    do {
        unmount_ret = unmount(path, 0);
        if (unmount_ret == 0)
            gf_log("umountd", GF_LOG_INFO, "Unmounted %s", path);

        if (unmount_ret != 0 && errno != EBUSY) {
            gf_log("umountd", GF_LOG_WARNING, "umount %s failed: %s", path,
                   strerror(errno));
        }

        ret = sys_lstat(path, &stbuf);
        if (ret != 0) {
            gf_log("umountd", GF_LOG_WARNING, "Cannot stat device from %s",
                   path, strerror(errno));
            break;
        }

        if (stbuf.st_dev != dev) {
            if (unmount_ret != 0)
                gf_log("umountd", GF_LOG_INFO,
                       "device mismatch "
                       "(expect %lld, found %lld), "
                       "someone else unmounted %s",
                       dev, stbuf.st_dev, path);
            ret = 0;
            break;
        }

        sleep(timeout);
    } while (1 /*CONSTCOND*/);

    if (ret) {
        gf_log("umountd", GF_LOG_ERROR, "Asynchronous unmount of %s failed: %s",
               path, strerror(errno));
    } else {
        if (frmdir) {
            ret = rmdir(path);
            if (ret)
                gf_log("umountd", GF_LOG_WARNING, "rmdir %s failed: %s", path,
                       strerror(errno));
            else
                gf_log("umountd", GF_LOG_INFO, "Removed %s", path);
        }
    }

    return ret;
}

int
main(int argc, char **argv)
{
    char *path = NULL;
    dev_t dev = -1;
    int frmdir = 0;
    int timeout = 30;
    int f;

    while ((f = getopt(argc, argv, "d:rt:")) != -1) {
        switch (f) {
            case 'p':
                path = optarg;
                break;
            case 'd':
                dev = strtoll(optarg, NULL, 10);
                break;
            case 't':
                timeout = atoi(optarg);
                break;
            case 'r':
                frmdir = 1;
                break;
            default:
                usage();
                break;
        }
    }

    argc -= optind;
    argv += optind;

    if (argc != 1)
        usage();

    path = argv[0];

    if (logging_init() != 0)
        exit(EXIT_FAILURE);

    if (sanity_check(path, &dev) != 0)
        exit(EXIT_FAILURE);

    if (daemon(0, 0) != 0)
        exit(EXIT_FAILURE);

    if (umountd_async(path, dev, frmdir, timeout) != 0)
        exit(EXIT_FAILURE);

    return EXIT_SUCCESS;
}
