/* OpenVAS-Client
 * $Id$
 * Description: Utility functions that deal with copying/moving/deleting files.
 *
 * Authors:
 * Felix Wolfsteller <felix.wolfsteller@intevation.de>
 * Michael Wiegand   <michael.wiegand@intevation.de>
 *
 * Copyright:
 * Copyright (C) 2009 Intevation GmbH
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 2,
 * or, at your option, any later version as published by the Free
 * Software Foundation
 *
 * 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * In addition, as a special exception, you have
 * permission to link the code of this program with the OpenSSL
 * library (or with modified versions of OpenSSL that use the same
 * license as OpenSSL), and distribute linked combinations including
 * the two. You must obey the GNU General Public License in all
 * respects for all of the code used other than OpenSSL. If you
 * modify this file, you may extend this exception to your version
 * of the file, but you are not obligated to do so. If you do not
 * wish to do so, delete this exception statement from your version.
 */

#include "file_utils.h"
#include "nessus_i18n.h"
#include "error_dlg.h"
#include <glib/gstdio.h>
#include "context.h"
#include "includes.h" // For stdio (function: remove)

/**
 * @TODO Within this module, a couple of functions are misplaced, and should
 * be moved when the openvas-module boundaries got a more clear definition.
 */


/**
 * @brief Recursively removes files and directories.
 *
 * This function will recursively call itself to delete a path and any
 * contents of this path.
 *
 * @param pathname The name of the file to be deleted from the filesystem.
 *
 * @return 0 if the name was successfully deleted, -1 if an error occurred.
 * Please note that errno is currently not guaranteed to contain the correct
 * value if -1 is returned.
 */
int
file_utils_rmdir_rf (const gchar * pathname)
{
  if (check_is_dir (pathname) == 1)
    {
      GError *error = NULL;
      GDir *directory = g_dir_open (pathname, 0, &error);

      if (directory == NULL)
        {
          g_warning ("g_dir_open(%s) failed - %s\n", pathname, error->message);
          g_error_free (error);
          // errno should be set when we return -1 to maintain remove()
          // compatibility.
          return -1;
        }
      else
        {
          int ret = 0;
          const gchar *entry = NULL;

          while ((entry = g_dir_read_name (directory)) && (ret == 0))
            {
              ret = file_utils_rmdir_rf (g_build_filename (pathname, entry, NULL));
              if (ret != 0)
                {
                  g_warning ("Failed to remove %s from %s!", entry, pathname);
                  g_dir_close (directory);
                  return ret;
                }
            }
          g_dir_close (directory);
        }
    }

  return g_remove (pathname);
}


/**
 * @brief Reads contents from a source file into a destination file.
 * 
 * The source file is read into memory, so it is inefficient and likely to fail
 * for really big files.
 * If the destination file does exist already, it will be overwritten.
 * 
 * @returns TRUE if successfull, FALSE otherwise (displays error but does not
 *          clean up).
 */
gboolean
file_utils_copy_file (const gchar* source_file, const gchar* dest_file)
{
  gchar* src_file_content = NULL;
  gsize  src_file_size = 0;
  int    bytes_written = 0;
  FILE*  fd = NULL;

  // Read file content into memory
  if ( g_file_get_contents (source_file, &src_file_content, &src_file_size, NULL)
       == FALSE)
    {
      show_error (_("Error reading file %s."), source_file);
      return FALSE;
    }

  // Open destination file
  fd = fopen (dest_file, "wb");
  if (fd == NULL)
    {
      show_error (_("Error opening file %s."), dest_file);
      g_free (src_file_content);
      return FALSE;
    }

  // Write content of src to dst and close it
  bytes_written = fwrite (src_file_content, 1, src_file_size, fd);
  fclose (fd);

  if (bytes_written != src_file_size)
    {
      show_error (_("Error writing to file %s. (%d/%d)"), dest_file, bytes_written, src_file_size);
      g_free (src_file_content);
      return FALSE;
    }
  g_free (src_file_content);

  return TRUE;
}

/**
 * @brief Reads contents from a source file into a destination file
 * @brief and unlinks the source file.
 * 
 * The source file is read into memory, so it is inefficient and likely to fail
 * for really big files.
 * If the destination file does exist already, it will be overwritten.
 * 
 * @returns TRUE if successfull, FALSE otherwise (displays error but does not
 *          clean up).
 */
gboolean
file_utils_move_file (const gchar* source_file, const gchar* dest_file)
{
  // Copy file (will displays errors itself)
  if (file_utils_copy_file (source_file, dest_file) == FALSE)
    return FALSE;

  // Remove source file
  if (remove (source_file) != 0)
    {
      show_error (_("Error removing file %s."), source_file);
      return FALSE;
    }

  return TRUE;
}


/**
 * Ensures the existance of a directory by creating it if it does not exist.
 * ATTENTION: can only create "one more sublevel"!
 * 
 * @param directory Path of directory to check or create.
 * 
 * @return TRUE if directory exists or was successfully created, FALSE otherwise.
 * 
 * @see ensure_dir
 */
static gboolean
ensure_single_dir (char* directory)
{
  //Should be moved together with file functions defined in context.h/c
  // to an own module e.g. file_check.c
  if (check_is_dir (directory) == 1)
    return TRUE;
  // Glib >= 2.8 defines g_mkdir_with_parents
  if (g_mkdir (directory, 0700) == 0)
    return TRUE;
  else
    return FALSE;
}

/**
 * @brief Ensures the existance of a directory, including parents.
 * 
 * This somewhat lazy method checks if a directory and its parent directories
 * exists. If not, it tries to make them. This method will be obselete once
 * depending on GLib >= 2.8 (g_mkdir_with_parents).
 * 
 * @return TRUE if the director(y|ies) exists or was sucessfully created, FALSE
 *         otherwise.
 */
gboolean
file_utils_ensure_dir (const char* dir)
{
  gchar* parentdir = g_strdup (dir);
  GQueue* queue = g_queue_new ();
  gboolean failed = FALSE;

  // Push all non-existing parent directories.
  while (parentdir && check_is_dir (parentdir) == 0)
    {
      g_queue_push_head (queue, parentdir);
      parentdir = g_path_get_dirname (parentdir) ;
    }

  // Pop all non-existing directories, create them, release string.
  parentdir = g_queue_pop_head (queue);
  while ( parentdir )
    {
      if (failed == FALSE )
        failed = ! ensure_single_dir (parentdir);
      g_free (parentdir);
      parentdir = g_queue_pop_head (queue);
    }

  // Clean up and return.
  g_queue_free (queue);
  return !failed;
}
