#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <string.h>
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
extern int errno;
#include <errno.h>
#include <stdlib.h>


/* Check the file and return:
 * - 1 in case it exists
 * - 0 if not exists
 * - (-1) in case of unexpected error, like EPERM)
 * if file exists is_symlink variable is set if the file
 * is a symlink
 */
static int
_file_exists(char *path)
{
    struct stat buf;

    if (stat(path, &buf)) {
        if (errno == ENOENT) {
            /* OK. this symlink was removed by somebody else */
            return 0;
        }else{
            fprintf(stderr,"Error while stat'ing symlink %s: %s (%d)\n",
                    path, strerror(errno), errno);
            return -1;
        }
    }
    return 1;
}

static int
_symlink_exists(char *path)
{
    struct stat buf;

    if (lstat(path, &buf)) {
        return 0;
    }

    if (!S_ISLNK(buf.st_mode)) {
        return -1;
    }
    return 1;
}

static int
_create_locked_cmd(char *path, int cmd)
{
    int ret = 0, fd;
    struct flock flk;

    fd = open(path, O_CREAT | O_RDWR, S_IRUSR | S_IWUSR);
    if (0 > fd) {
        fprintf(stderr,"Error opening file %s: %s (%d)\n",
                path, strerror(errno), errno);
        return -1;
    }


    flk.l_whence = SEEK_SET;
    flk.l_start = 0;
    flk.l_len = 0;
    flk.l_type = F_WRLCK;

    do{
        if (0 == (ret = fcntl(fd, cmd, &flk))) {
            break;
        } else if (EINTR != errno) {
            fprintf(stderr,"Error locking file %s: %s (%d)\n",
                    path, strerror(errno), errno);
            close(fd);
            return -errno;
        }
    } while(EINTR == errno);

    if (0 == ret)
        return fd;

    close(fd);
    return ret;
}

static int
_is_locked(char *path)
{
    int ret, fd;
    struct flock flk;
    memset(&flk, 0, sizeof(flk));

    fd = open(path, O_RDONLY);
    if (0 > fd) {
        if (EEXIST == errno) {
            return 0;
        } else {
            fprintf(stderr,"Error opening file %s: %s (%d)\n",
                    path, strerror(errno), errno);
            return -1;
        }
    }

    do{
        if (0 == (ret = fcntl(fd, F_GETLK, &flk))) {
            break;
        }
    } while(EINTR == errno);

    if (ret) {
        fprintf(stderr,"Error getting file lock information for %s: %s (%d)\n",
                path, strerror(errno), errno);
        return -1;
    }

    if (F_WRLCK == flk.l_type && SEEK_SET == flk.l_whence) {
        return 1;
    }
    return 0;
}

int pmix_leader_is_alive(char *lname)
{
    int ret, error;
    char fname[FILENAME_MAX] = { 0 };
    char lock_name[FILENAME_MAX] = { 0 };

    ret = _symlink_exists(lname);
    if (0 > ret) {
        fprintf(stderr,"Error accessing symlink %s: %d:%s\n",
                lname, error, strerror(error));
        return -1;
    }
    if (0 > readlink(lname, fname, FILENAME_MAX - 1)) {
        if (ENOENT == errno) {
            /* Symlink was removed between _symlink_exists & readlink */
            return 0;
        } else {
            fprintf(stderr,"Error reading symlink %s: %d:%s\n",
                    lname, error, strerror(error));
            return -1;
        }
    }

    snprintf(lock_name, FILENAME_MAX - 1, "%s.lock",fname);
    ret = _file_exists(lock_name);
    /* In case of fatal error (ret < 0) or file abscense */
    if (0 >= ret)
        return ret;
    return _is_locked(lock_name);
}

void pmix_remove_leader_symlink(char *path)
{
    int ret, is_symlink;

    /* Check prior to go further */
    ret = _symlink_exists(path);
    if (0 > ret) {
        fprintf(stderr,"FATAL error\n");
        exit(1);
    }
    if (0 == ret) {
        /* File was already deleted. Nothing to do */
        return;
    }

    char lockname[FILENAME_MAX];
    sprintf(lockname,"%s.lock", path);
    int fd = pmix_create_locked_wait(lockname);

    /* Check prior to go further */
    ret = _symlink_exists(path);
    if (0 > ret) {
        fprintf(stderr,"FATAL error\n");
        exit(1);
    }
    if (0 == ret) {
        /* File was already deleted. Nothing to do */
        goto exit;
    }

    if (pmix_leader_is_alive(path)) {
        /* Somebody already took care about this */
        goto exit;
    }

    if (unlink(path)) {
        if (ENOENT != errno) {
            fprintf(stderr, "unlink(%s): %s", path, strerror(errno));
            exit(1);
        }
    }

exit:
    close(fd);
}

int pmix_create_locked(char *path)
{
    return _create_locked_cmd(path, F_SETLK);
}

int pmix_create_locked_wait(char *path)
{
    return _create_locked_cmd(path, F_SETLKW);
}


#if 0

#define FPREFIX "test"

int main(int argc, char **argv)
{
    char lockfile[FILENAME_MAX], basefile[FILENAME_MAX];
    if (2 > argc) {
        fprintf(stderr, "Not enough parameters\n");
        exit(0);
    }

    sprintf(basefile, FPREFIX ".%s", argv[1]);
    sprintf(lockfile,"%s.lock",basefile);
    if (0 > _create_locked(lockfile)) {
        fprintf(stderr, "Cannot create personal lock file %s\n", basefile);
        exit(0);
    }

    int fd = open(basefile,O_CREAT | O_RDWR, S_IRUSR | S_IWUSR);
    if (fd < 0) {
        fprintf(stderr,"Cannot create basefile\n");
        exit(0);
    }

    while(1) {
        if (!pmix_leader_is_alive(FPREFIX)) {
            pmix_remove_leader_symlink(FPREFIX);
            if (!symlink(basefile, FPREFIX)) {
                while(1) {
                    sleep(1);
                }
            }
        }
        sleep(1);
    }
}
#endif
