/* $Id: flames_fileutils.c,v 1.5 2007-10-31 17:51:10 amodigli Exp $
 *
 *   This file is part of the ESO Common Pipeline Library
 *   Copyright (C) 2001-2006 European Southern Observatory
 *
 *   This program is free software; you can redistribute it and/or modify
 *   it under the terms of the GNU General Public License as published by
 *   the Free Software Foundation; either version 2 of the License, or
 *   (at your option) any later version.
 *
 *   This program is distributed in the hope that it will be useful,
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *   GNU General Public License for more details.
 *
 *   You should have received a copy of the GNU General Public License
 *   along with this program; if not, write to the Free Software
 *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

#ifdef HAVE_WORDEXP
#include <wordexp.h>
#endif


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

#include <ctype.h>

#include <cpl.h>

//#include "er_main.h"
#include "flames_fileutils.h"
#define FLAMES_FILENAME_MAX 4096
#define PATHSET_MAX     1024
#define DEV_BLOCKSIZE   4096
#define FLAMES_TRACE(x)  printf("=> %s()\n",x);

/**
 * @defgroup flames_fileutils EsoRex File Utility Functions
 *
 * Utility functions for hadling files.
 *
 */

/**@{*/
/*
  ^L
 */

/**********************************************************************/
/**
 * @brief  Replaces any environment variables with literal strings
 *
 * @param  path      Input path
 *
 * @return A string with the replaced varaibles.
 *
 * This function performs the replacement of any environment 
 * variables. It only works if the WORDEXP package is available 
 * (detected at "configure" time and indicated as the HAVE_WORDEXP 
 * symbolic constant). If this packages doesn't exist then this 
 * function does nothing.
 */
/**********************************************************************/

#ifdef HAVE_WORDEXP

char *flames_fileutils_env_replace (const char *path)

{

    static char expanded_path[PATHSET_MAX + 1];
    char tpath[PATHSET_MAX + 1];

    wordexp_t pwordexp;



    FLAMES_TRACE(__func__)


    (void) strncpy (expanded_path, path, PATHSET_MAX);

    memset ((void *) &pwordexp, 0, sizeof (pwordexp));

    /* Perform tilde and environment variable expansion */
    if (wordexp (path, &pwordexp, WRDE_NOCMD | WRDE_UNDEF) ||
                    pwordexp.we_wordc > 1)
    {
        cpl_msg_warning (cpl_func, "Unable to expand path '%s'\n", path);
        if (pwordexp.we_wordc > 0)
            wordfree (&pwordexp);
        return expanded_path;
    }
    else
    {
        if (!pwordexp.we_wordv[0] ||
                        strlen (pwordexp.we_wordv[0]) > PATHSET_MAX)
        {
            cpl_msg_warning (cpl_func, "Buffer overflow while expanding "
                            "path '%s'\n", path);
            wordfree (&pwordexp);
            return expanded_path;
        }
        strncpy (tpath, pwordexp.we_wordv[0], PATHSET_MAX);
        wordfree (&pwordexp);
    }

    (void) strcpy (expanded_path, tpath);
    /* Return a pointer to the amended (if at all) string */
    return expanded_path;
}

#endif

/*
  ^L
 */

/**********************************************************************/
/**
 * @brief  Replaces the leading tilde (~) in a name.
 *
 * @param param_list  List of Parameters
 *
 * @return A string with the replaced tilde.
 *
 * This function takes an input string. If the string begins with a tilde
 * (~), then the function returns this string, but with the tilde replaced
 * by the home directory of the current user (using the $HOME environment
 * variable). If not, then the string is returned unmodified. This function
 * also cleans up any multiple or trailing forward-slashes.
 *
 * This function also performs the replacement of any environment 
 * variables.
 */
/**********************************************************************/

const char *flames_fileutils_tilde_replace (const char * name)

{
    static char str[PATHSET_MAX];
    char *marker;

    int  len;



    //FLAMES_TRACE(__func__)


    if (name == NULL) return NULL; 


    /* Only do this, if we start with a tilde (~) */

    if (*name != '~')
    {
        len = (int) strlen(name);
        if (len > (PATHSET_MAX-1))
        {
            cpl_msg_error (cpl_func, "Buffer overflow in filename '%s' - fatal error", name);
            abort();
        }
        (void) strcpy (str, name);
    }

    else
    {        /* get HOME env. var., if we start with a tilde */
        char* cpp = getenv ("HOME");
        if (cpp == NULL)
        {
            cpl_msg_error (cpl_func, "Env. variable HOME not set, could not replace `~'");
            abort();
        }

        (void) strcpy (str,cpp);
        len = (int) strlen(str) + (int) strlen(name);    /* +1 for \0 */
        if (len > PATHSET_MAX)                /* -1 for (name+1) below */
        {
            cpl_msg_error (cpl_func, "Buffer overflow in filename '%s' - fatal error", name);
            abort();
        }

        (void) strcat (str, name + 1);
    }


    /* Remove any multiple slashes */


    marker = strstr (str, "//");
    while (marker != NULL)
    {
        memmove (marker, marker + 1, strlen (marker));
        marker = strstr (str, "//");
    }


    /* Remove any trailing slashes */

    marker = str - 1 + (int) strlen(str);        /* last char of `str' */
    if ((*marker) == '/') *marker = '\0';

#ifdef HAVE_WORDEXP
    return (flames_fileutils_env_replace (str));
#else

    return (str);
#endif

}                            
/*
  ^L
 */

/**********************************************************************/
/**
 * @brief  Replaces the leading dot (.) in a name.
 *
 * @param param_list  List of Parameters
 *
 * @return A string with the replaced ".".
 *
 * This function takes an input string. If the string begins with a
 * single dot (.), then the function returns this string, but with the
 * dot replaced by the current working directory (as an absolute path)
 * using the $PWD environment variable. If not, then the string is 
 * returned unmodified.
 */
/**********************************************************************/

const char *flames_fileutils_dot_replace (const char * name)

{
    static char str[PATHSET_MAX];
    char  *cpp;

    int  len;



    FLAMES_TRACE(__func__)


    if (name == NULL) return NULL;


    /* if we don't start with a dot (.) just pass the name on */

    if (*name != '.')
    {
        len = (int) strlen(name);
        if (len > (PATHSET_MAX-1))
        {
            cpl_msg_error (cpl_func, "Buffer overflow in filename '%s' - fatal error", name);
            abort();
        }

        (void) strcpy (str, name);
        return (str);
    }


    /* Get the environment variable PWD */

    cpp = getenv ("PWD");
    if (cpp != NULL)
    {
        len = (int) strlen(cpp);
        if (len > (PATHSET_MAX-1))
        {
            cpl_msg_error (cpl_func, "Buffer overflow in filename '%s' - fatal error", name);
            abort();
        }

        (void) strcpy (str,cpp);
    }
    else
    {
        cpl_msg_error (cpl_func, "Env. variable PWD not set - fatal errorn");
        abort();
    }


    /* Handle the ".." case (add a "/." as the "." replacement) */

    if (*(name + 1) == '.')
    {
        if ((len+2) > (PATHSET_MAX-1))
        {
            cpl_msg_error (cpl_func, "Buffer overflow in filename '%s' - fatal error", name);
            abort();
        }
        (void) strcat (str, "/.");
        len += 2;
    }

    len = (int) strlen(str) + (int) strlen(name);        /* +1 for \0 */
    if (len > PATHSET_MAX)                    /* -1 for (name+1) below */
    {
        cpl_msg_error (cpl_func, "Buffer overflow in filename '%s'", name);
        cpl_msg_error (cpl_func, "Fatal error replacing current working "
                       "directory symbol due to buffer overflow");
        abort();
    }
    (void) strcat(str, name + 1);        /* Add the rest of the file name */

    return (str);
}                            
/*
  ^L
 */

/**********************************************************************/
/**
 * @brief
 *   Checks whether the given directory actually exists
 *
 * @param directory_name Directory
 *
 * @returns TRUE if directory exists, FALSE otherwise
 */
/**********************************************************************/

int flames_fileutils_directory_exists (const char * directory_name)

{
    DIR *dp;



    FLAMES_TRACE(__func__)


    if (directory_name == NULL) return 0;

    dp = opendir (flames_fileutils_tilde_replace (directory_name));
    if (dp != NULL)
    {
        (void) closedir (dp);
        return 1;            /* that's TRUE */
    }

    return 0;            /* that's FALSE */
}       
/*
  ^L
 */

/**********************************************************************/
/**
 * @brief
 *   Checks whether the given file actually exists
 *
 * @param file_name Filename
 *
 * @returns 1 if file exists, 0 otherwise
 */
/**********************************************************************/

int flames_fileutils_file_exists (const char * file_name)

{

    //FLAMES_TRACE(__func__)


    if (file_name != NULL) 
    {
        int file_descriptor = open (flames_fileutils_tilde_replace (file_name), O_RDONLY);
        if (file_descriptor > -1)
        {
            (void) close (file_descriptor);
            return 1;
        }
    }

    return 0;

}                               /* End of flames_fileutils_file_exists() */
/*
  ^L
 */

/**********************************************************************/
/**
 * @brief
 *   Creates a Fully Qualified File Name based on a directory name
 *   and a filename
 *
 * @param dir_name  Directory
 * @param file_name File
 *
 * @returns A newly allocated string containing FQFN
 */
/**********************************************************************/

char *flames_fileutils_create_fqfname (char * dir_name, char * file_name)

{
    char *fqfn = NULL;

    int n;
    int ldn = 0, lfn = 0, lextra = 2;


    //FLAMES_TRACE(__func__)

    if (file_name == NULL) return NULL;

    if (dir_name != NULL)
    {
        ldn = (int) strlen (dir_name);
        if (dir_name[ldn] == '/') lextra = 1;
    }

    lfn = (int) strlen (file_name);
    n = sizeof (char) * (ldn + lfn + lextra);
    fqfn = (char *) cpl_malloc ((size_t) n);
    if (fqfn == NULL) return NULL;

    (void) strcpy (fqfn, dir_name);
    if (lextra == 2) (void) strcat (fqfn, "/");
    (void) strcat (fqfn, file_name);

    return fqfn;

}                               /* End of flames_fileutils_create_fqfname() */
/*
  ^L
 */

/**********************************************************************/
/**
 * @brief
 *   Extracts filename from a Fully Qualified File Name
 *
 * @param path FQFN
 *
 * @returns A newly allocated string containing Filename
 */
/**********************************************************************/

char *flames_fileutils_fqfname_filename (const char * path)

{
    char  *fname;
    const char *pc;

    int len, j;



    FLAMES_TRACE(__func__)


    if (path == NULL) return NULL;

    len = (int) strlen (path);
    j = len;

    pc = path + len;

    while ((j >= 0) && (*pc != '/'))
    {
        pc--;
        j--;
    }

    fname = (char *) cpl_calloc ((size_t) (len - j), (size_t) sizeof (char));
    if (fname == NULL) return NULL;

    (void) strncpy (fname, &path[j + 1], (size_t) (len-j-1));

    return fname;

}      /* End of flames_fileutils_fqfname_filename() */
/*
  ^L
 */

/**********************************************************************/
/**
 * @brief
 *   Extracts directoryname from a Fully Qualified File Name
 *
 * @param path FQFN
 *
 * @returns A newly allocated string containing directoryname
 */
/**********************************************************************/

char *flames_fileutils_fqfname_dirname (const char * path)

{
    char  *dname;
    const char *pc;

    int len, j;



    FLAMES_TRACE(__func__)


    if (path == NULL) return NULL;

    len = (int) strlen (path);
    j = len;

    pc = path + len;

    while ((j >= 0) && (*pc != '/'))
    {
        pc--;
        j--;
    }

    dname = (char *) cpl_calloc ((size_t) (j+1), (size_t) sizeof (char));
    if (dname == NULL) return NULL;

    (void) strncpy (dname, path, (size_t) j);

    return dname;

} 
/*
  ^L
 */

/*
 * @brief
 *   Copy a file.
 *
 * @param srcpath  Source file name.
 * @param dstpath  Destination file name.
 *
 * @return The function returns 0 if no error occurred, otherwise -1 is
 *    returned and errno is set appropriately.
 *
 * The function provides a very basic way to copy the source file
 * @em srcpath to a destination file @em dstpath.
 *
 * The implementation just uses @b read() and @b write() to do the job.
 * It is by far not comparable with the @b cp shell command. Actually it
 * just writes the source file contents into a new file with the
 * appropriate output name.
 *
 * If an error occurs the destination file is removed before the function
 * returns.
 */

int flames_fileutils_copy (const char * srcpath, const char * dstpath)

{
    char *buf;

    int src, dst;
    int rbytes = 0;
    int wbytes = 0;
    int blksize = DEV_BLOCKSIZE;

    struct stat sb, db;



    //FLAMES_TRACE(__func__)

    if ((src = open (srcpath, O_RDONLY)) == -1) {
    	(void) close (src);
    	return -1;
    }

    if ((fstat (src, &sb) == -1) || (!S_ISREG (sb.st_mode)))
    {
        (void) close (src);
        return -2;
    }

    if ((dst = open (dstpath, O_CREAT | O_WRONLY | O_TRUNC, sb.st_mode)) == -1)
    {
        (void) close (src);
        (void) close (dst);
        return -3;
    }

    if ((fstat (dst, &db) == -1) || (!S_ISREG (db.st_mode)))
    {
        (void) close (src);
        (void) close (dst);
        (void) unlink (dstpath);
        return -4;
    }

#ifdef HAVE_ST_BLKSIZE
    blksize = db.st_blksize;
#else
#   ifdef DEV_BSIZE
    blksize = DEV_BSIZE;
#   endif
#endif

    if ((buf = (char *) cpl_malloc ((size_t)blksize)) == NULL)
    {
        (void) close (src);
        (void) close (dst);
        (void) unlink (dstpath);
        return -5;
    }

    while ((rbytes = (int) read (src, buf, (size_t)blksize)) > 0)
    {
        if ((wbytes = (int) write (dst, buf, (size_t)rbytes)) != rbytes)
        {
            wbytes = -1;
            break;
        }
    }

    (void) close (src);
    (void) close (dst);
    cpl_free (buf);


    if ((rbytes == -1) || (wbytes == -1))
    {
        (void) unlink (dstpath);
        return -6;
    }

    return 0;

}
/*
  ^L
 */

/*
 * @brief
 *   Move a file.
 *
 * @param srcpath  Source file name.
 * @param dstpath  Destination file name.
 *
 * @return The function returns 0 if no error occurred, otherwise -1 is
 *    returned and errno is set appropriately.
 *
 * The function moves the source file @em srcpath to the given
 * destination path @em dstpath.
 *
 * If an error occurs the destination file is removed before the function
 * returns and the source file is left untouched.
 */

int flames_fileutils_move (const char *srcpath, const char *dstpath)

{
    int  ii;

    struct stat sb;


    FLAMES_TRACE(__func__)

    if ((ii = flames_fileutils_copy (srcpath, dstpath)) != 0)
    {
        cpl_msg_error (cpl_func, "copy returned: %d\n",ii);
        return -1;
    }


    /*
    Remove the source, but if the source file cannot be checked or is not
    writable revert to the original state, i.e. remove the file copy.
     */

    if (stat (srcpath, &sb) == -1 || !(sb.st_mode & S_IWUSR))
    {
        (void) unlink (dstpath);
        return -1;
    }

    (void) unlink (srcpath);
    return 0;

}
/*
  ^L
 */

/*
 * @brief
 *   Export a pipeline product file to the archive.
 *
 * @return The function returns @c EXIT_SUCCESS if no error occurred,
 *   otherwise the return value is @c EXIT_FAILURE.
 *
 * @param link_path  Path to the export directory.
 * @param file_path  Absolute path to the product file to export.
 *   
 * The input file given by @em file_path is located in the product
 * directory and it is verified that the file exists and is readable.
 * The product file is exported by creating a link (see the note below)
 * to the product file in the pipeline export directory given by @em link_path.
 * The name of the link will be the same as the name of the product file.
 * 
 * The export directory name @em link_path should not contain a trailing slash.
 * 
 * @warning
 *   In case symbolic links are not supported by the system the product
 *   file is simply copied. To copy the file is safer, since hard
 *   links cannot be used across file systems.
 *
 * @note
 *   This function is a copy of the function pilRecExportProduct() from the
 *   VIMOS system, written by R. Palsa.
 *
 * @author R. Palsa
 */

int flames_fileutils_link (const char *link_path, const char *file_path)

{
    char linkname[FLAMES_FILENAME_MAX + 1];
    char *s;

    int  len;

    struct stat lb;


    //FLAMES_TRACE(__func__)


    /* Check if the input file is readable */

    if (access (file_path, R_OK))
    {
        cpl_msg_error (cpl_func, "Product file is unreadable: %s", file_path);
        return EXIT_FAILURE;
    }


    /* pull out the file name */

    s = strrchr (file_path, '/');            /* points to last '/' */
    if (s == NULL)
        len = (int) strlen(file_path);
    else
    {
        s++;
        len = (int) strlen(s);
    }

    len = (int) strlen(link_path) + len + 1;
    if (len > FLAMES_FILENAME_MAX)
    {
        cpl_msg_error (cpl_func, "Buffer overflow - fatal error");
        return EXIT_FAILURE;
    }
    //(void) snprintf (linkname, (size_t)FLAMES_FILENAME_MAX, "%s/%s", link_path, s);
    (void) snprintf (linkname, (size_t)FLAMES_FILENAME_MAX, "%s", link_path);


    /*
     * To export the file to the archive usually a symbolic link to the
     * product file is created. But symbolic links are not conforming to
     * the ANSI C standard. If symbolic links are not available the file
     * is just copied. Hard links might be an alternative but they cannot
     * be used across file systems, so a real copy is created here.
     */

#if defined HAVE_LSTAT && defined HAVE_SYMLINK
    if (lstat (linkname, &lb))
    {
        if (errno != ENOENT)
        {
            cpl_msg_error (cpl_func, "Cannot get file status: %s", linkname);
            return EXIT_FAILURE;
        }
    }
    else
    {
        if (S_ISLNK (lb.st_mode))
            (void) unlink (linkname);
        else
        {
            cpl_msg_error (cpl_func, "Not a symbolic link: %s", linkname);
            return EXIT_FAILURE;
        }
    }

    if (symlink (file_path, linkname) != 0)
    {
        cpl_msg_error (cpl_func, "Cannot create symbolic link for %s", file_path);
        return EXIT_FAILURE;
    }

#else
    if (stat (linkname, &lb))
    {
        if (errno != ENOENT)
        {
            cpl_msg_error (cpl_func, "Cannot get file status: %s", linkname);
            return EXIT_FAILURE;
        }
    }
    else
        (void) unlink (linkname);

    if (flames_fileutils_copy (file_path, linkname) != 0)
    {
        cpl_msg_error (cpl_func, "Cannot copy %s", file_path);
        return EXIT_FAILURE;
    }
#endif

    return EXIT_SUCCESS;

}                               /* End of flames_fileutils_link() */

/**@}*/


/* End of file */
