/*
 * Copyright (C) by Argonne National Laboratory
 *     See COPYRIGHT in top-level directory
 */

/*
 * This file contains a simple implementation of the name server routines,
 * using a file written to a shared file system to communication the
 * data.
 */
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include "mpiimpl.h"
#include "namepub.h"
#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif
#include <errno.h>

/*
=== BEGIN_MPI_T_CVAR_INFO_BLOCK ===

categories:
    - name        : PROCESS_MANAGER
      description : cvars that control the client-side process manager code

cvars:
    - name        : MPIR_CVAR_NAMESERV_FILE_PUBDIR
      category    : PROCESS_MANAGER
      alt-env     : MPIR_CVAR_NAMEPUB_DIR
      type        : string
      default     : NULL
      class       : none
      verbosity   : MPI_T_VERBOSITY_USER_BASIC
      scope       : MPI_T_SCOPE_ALL_EQ
      description : >-
        Sets the directory to use for MPI service publishing in the
        file nameserv implementation.  Allows the user to override
        where the publish and lookup information is placed for
        connect/accept based applications.

=== END_MPI_T_CVAR_INFO_BLOCK ===
*/

/* For writing the name/service pair */
/* style: allow:fprintf:1 sig:0 */

#ifndef MAXPATHLEN
#define MAXPATHLEN 1024
#endif

/* Define the name service handle */
#define MPID_MAX_NAMEPUB 64
struct MPID_NS_Handle {
    int nactive;                /* Number of active files */
    int mypid;                  /* My process id */
    char dirname[MAXPATHLEN];   /* Directory for all files */
    char *(filenames[MPID_MAX_NAMEPUB]);        /* All created files */
};

/* Create a structure that we will use to remember files created for
   publishing.  */
int MPID_NS_Create(const MPIR_Info * info_ptr, MPID_NS_Handle * handle_ptr)
{
    const char *dirname;
    struct stat st;
    int err, ret;

    *handle_ptr = (MPID_NS_Handle) MPL_malloc(sizeof(struct MPID_NS_Handle), MPL_MEM_PM);
    /* --BEGIN ERROR HANDLING-- */
    if (!*handle_ptr) {
        err =
            MPIR_Err_create_code(MPI_SUCCESS, MPIR_ERR_RECOVERABLE, __func__, __LINE__,
                                 MPI_ERR_OTHER, "**nomem", 0);
        return err;
    }
    /* --END ERROR HANDLING-- */
    (*handle_ptr)->nactive = 0;
    (*handle_ptr)->mypid = getpid();

    /* Get the dirname.  Could use an info value of NAMEPUB_CONTACT */
    dirname = MPIR_CVAR_NAMESERV_FILE_PUBDIR;
    if (!dirname) {
        /* user did not specify a directory, try using HOME */
        ret = MPL_env2str("HOME", &dirname);
        if (!ret) {
            /* HOME not found ; use current directory */
            dirname = ".";
        }
    }

    MPL_strncpy((*handle_ptr)->dirname, dirname, MAXPATHLEN);
    MPL_strnapp((*handle_ptr)->dirname, "/.mpinamepub/", MAXPATHLEN);

    /* Make the directory if necessary */
    /* FIXME : Determine if the directory exists before trying to create it */

    if (stat((*handle_ptr)->dirname, &st) || !S_ISDIR(st.st_mode)) {
        /* This mode is rwx by owner only.  */
        if (mkdir((*handle_ptr)->dirname, 0000700)) {
            /* FIXME : An error.  Ignore most ?
             * For example, ignore EEXIST?  */
            ;
        }
    }

    return 0;
}

int MPID_NS_Publish(MPID_NS_Handle handle, const MPIR_Info * info_ptr,
                    const char service_name[], const char port[])
{
    FILE *fp;
    char filename[MAXPATHLEN];
    int err;

    /* Determine file and directory name.  The file name is from
     * the service name */
    MPL_strncpy(filename, handle->dirname, MAXPATHLEN);
    MPL_strnapp(filename, service_name, MAXPATHLEN);

    /* Add the file name to the known files now, in case there is
     * a failure during open or writing */
    if (handle->nactive < MPID_MAX_NAMEPUB) {
        handle->filenames[handle->nactive++] = MPL_strdup(filename);
    } else {
        /* --BEGIN ERROR HANDLING-- */
        err = MPIR_Err_create_code(MPI_SUCCESS, MPIR_ERR_RECOVERABLE,
                                   __func__, __LINE__, MPI_ERR_OTHER, "**nomem", 0);
        return err;
        /* --END ERROR HANDLING-- */
    }

    /* Now, open the file and write out the port name */
    fp = fopen(filename, "w");
    /* --BEGIN ERROR HANDLING-- */
    if (!fp) {
        char *reason;
        /* Generate a better error message */
        /* Check for errno =
         * EACCES (access denied to file or a dir),
         * ENAMETOOLONG (name too long)
         * ENOENT (no such directory)
         * ENOTDIR (a name in the path that should have been a directory
         * wasn't)
         * ELOOP (too many symbolic links in path)
         * ENOMEM (insufficient kernel memory available)
         * There are a few others that aren't covered here
         */
#ifdef HAVE_STRERROR
        reason = strerror(errno);
#else
        /* FIXME : This should use internationalization calls */
        switch (errno) {
            case EACCES:
                reason = "Access denied to some element of the path";
                break;
            case ENAMETOOLONG:
                reason = "File name is too long";
                break;
            case ENOENT:
                reason = "A directory specified in the path does not exist";
                break;
            case ENOTDIR:
                reason =
                    "A name specified in the path exists, but is not a directory and is used where a directory is required";
                break;
            case ENOMEM:
                reason "Insufficient kernel memory available";
            default:
                MPL_snprintf(rstr, sizeof(rstr), "errno = %d", errno);
        }
#endif
        err = MPIR_Err_create_code(MPI_SUCCESS, MPIR_ERR_RECOVERABLE, __func__, __LINE__,
                                   MPI_ERR_OTHER, "**namepubfile",
                                   "**namepubfile %s %s %s", service_name, filename, reason);
        return err;
    }
    /* --END ERROR HANDLING-- */
    /* Should also add date? */
    fprintf(fp, "%s\n%d\n", port, handle->mypid);
    fclose(fp);

    return 0;
}

int MPID_NS_Lookup(MPID_NS_Handle handle, const MPIR_Info * info_ptr,
                   const char service_name[], char port[])
{
    FILE *fp;
    char filename[MAXPATHLEN];
    int mpi_errno = MPI_SUCCESS;

    /* Determine file and directory name.  The file name is from
     * the service name */
    MPL_strncpy(filename, handle->dirname, MAXPATHLEN);
    MPL_strnapp(filename, service_name, MAXPATHLEN);

    fp = fopen(filename, "r");
    if (!fp) {
        /* --BEGIN ERROR HANDLING-- */
        port[0] = 0;
        MPIR_ERR_SET1(mpi_errno, MPI_ERR_NAME,
                      "**namepubnotpub", "**namepubnotpub %s", service_name);
        /* --END ERROR HANDLING-- */
    } else {
        /* The first line is the name, the second is the
         * process that published. We just read the name */
        if (!fgets(port, MPI_MAX_PORT_NAME, fp)) {
            /* --BEGIN ERROR HANDLING-- */
            port[0] = 0;
            MPIR_ERR_SET1(mpi_errno, MPI_ERR_NAME,
                          "**namepubnotfound", "**namepubnotfound %s", service_name);
            /* --END ERROR HANDLING-- */
        } else {
            char *nl;
            /* Remove the newline, if any.  We use fgets instead of fscanf
             * to allow port names to contain blanks */
            nl = strchr(port, '\n');
            if (nl)
                *nl = 0;
            /* printf("Read %s from %s\n", port, filename); */
        }
        fclose(fp);
    }
    return mpi_errno;
}

int MPID_NS_Unpublish(MPID_NS_Handle handle, const MPIR_Info * info_ptr, const char service_name[])
{
    char filename[MAXPATHLEN];
    int err;
    int i;

    /* Remove the file corresponding to the service name */
    /* Determine file and directory name.  The file name is from
     * the service name */
    MPL_strncpy(filename, handle->dirname, MAXPATHLEN);
    MPL_strnapp(filename, service_name, MAXPATHLEN);

    /* Find the filename from the list of published files */
    for (i = 0; i < handle->nactive; i++) {
        if (handle->filenames[i] && strcmp(filename, handle->filenames[i]) == 0) {
            /* unlink the file only if we find it */
            unlink(filename);
            MPL_free(handle->filenames[i]);
            handle->filenames[i] = 0;
            break;
        }
    }

    if (i == handle->nactive) {
        /* --BEGIN ERROR HANDLING-- */
        /* Error: this name was not found */
        err = MPIR_Err_create_code(MPI_SUCCESS, MPIR_ERR_RECOVERABLE, __func__, __LINE__,
                                   MPI_ERR_SERVICE, "**namepubnotpub",
                                   "**namepubnotpub %s", service_name);
        return err;
        /* --END ERROR HANDLING-- */
    }

    /* Later, we can reduce the number of active and compress the list */

    return 0;
}

int MPID_NS_Free(MPID_NS_Handle * handle_ptr)
{
    int i;
    MPID_NS_Handle handle = *handle_ptr;

    for (i = 0; i < handle->nactive; i++) {
        if (handle->filenames[i]) {
            /* Remove the file if it still exists */
            unlink(handle->filenames[i]);
            MPL_free(handle->filenames[i]);
        }
    }
    MPL_free(*handle_ptr);
    *handle_ptr = 0;

    return 0;
}
